From d3daf02ef3e026a473aa267fdd4facd3aafc6e45 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 27 Jan 2021 20:45:52 +0100 Subject: [PATCH 0001/1818] Bumped version to 2021.2.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 15ea6b7b00dd9a..88eb8e51f41731 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From a05f839d3ddfca2344dc368192f3c3381c0722c4 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 28 Jan 2021 11:21:31 -0500 Subject: [PATCH 0002/1818] Allow Plex playback using provided playqueue ID (#45580) --- homeassistant/components/plex/media_player.py | 25 +++++++++++++------ homeassistant/components/plex/server.py | 4 +++ homeassistant/components/plex/services.py | 22 +++++++++++----- tests/components/plex/conftest.py | 6 +++++ tests/components/plex/test_services.py | 17 +++++++++++++ tests/fixtures/plex/media_100.xml | 8 +++++- tests/fixtures/plex/playqueue_1234.xml | 7 ++++++ 7 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 tests/fixtures/plex/playqueue_1234.xml diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 24e37216b70c38..1a57186bd9b7e2 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -22,6 +22,7 @@ ) from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -495,16 +496,26 @@ def play_media(self, media_type, media_id, **kwargs): if isinstance(src, int): src = {"plex_key": src} - shuffle = src.pop("shuffle", 0) - media = self.plex_server.lookup_media(media_type, **src) + playqueue_id = src.pop("playqueue_id", None) - if media is None: - _LOGGER.error("Media could not be found: %s", media_id) - return + if playqueue_id: + try: + playqueue = self.plex_server.get_playqueue(playqueue_id) + except plexapi.exceptions.NotFound as err: + raise HomeAssistantError( + f"PlayQueue '{playqueue_id}' could not be found" + ) from err + else: + shuffle = src.pop("shuffle", 0) + media = self.plex_server.lookup_media(media_type, **src) + + if media is None: + _LOGGER.error("Media could not be found: %s", media_id) + return - _LOGGER.debug("Attempting to play %s on %s", media, self.name) + _LOGGER.debug("Attempting to play %s on %s", media, self.name) + playqueue = self.plex_server.create_playqueue(media, shuffle=shuffle) - playqueue = self.plex_server.create_playqueue(media, shuffle=shuffle) try: self.device.playMedia(playqueue) except requests.exceptions.ConnectTimeout: diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index f8d55c71fc4759..1baceb78ff1e45 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -593,6 +593,10 @@ def create_playqueue(self, media, **kwargs): """Create playqueue on Plex server.""" return plexapi.playqueue.PlayQueue.create(self._plex_server, media, **kwargs) + def get_playqueue(self, playqueue_id): + """Retrieve existing playqueue from Plex server.""" + return plexapi.playqueue.PlayQueue.get(self._plex_server, playqueue_id) + def fetch_item(self, item): """Fetch item from Plex server.""" return self._plex_server.fetchItem(item) diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index 2e4057b890a0f5..a5faa56a8bbdd6 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -105,15 +105,25 @@ def lookup_plex_media(hass, content_type, content_id): content_type = DOMAIN plex_server_name = content.pop("plex_server", None) - shuffle = content.pop("shuffle", 0) - plex_server = get_plex_server(hass, plex_server_name) - media = plex_server.lookup_media(content_type, **content) - if media is None: - raise HomeAssistantError(f"Plex media not found using payload: '{content_id}'") + playqueue_id = content.pop("playqueue_id", None) + if playqueue_id: + try: + playqueue = plex_server.get_playqueue(playqueue_id) + except NotFound as err: + raise HomeAssistantError( + f"PlayQueue '{playqueue_id}' could not be found" + ) from err + else: + shuffle = content.pop("shuffle", 0) + media = plex_server.lookup_media(content_type, **content) + if media is None: + raise HomeAssistantError( + f"Plex media not found using payload: '{content_id}'" + ) + playqueue = plex_server.create_playqueue(media, shuffle=shuffle) - playqueue = plex_server.create_playqueue(media, shuffle=shuffle) return (playqueue, plex_server) diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index 8fc25a819e8d5b..d3e66cc4989d37 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -168,6 +168,12 @@ def playqueue_created_fixture(): return load_fixture("plex/playqueue_created.xml") +@pytest.fixture(name="playqueue_1234", scope="session") +def playqueue_1234_fixture(): + """Load payload for playqueue 1234 and return it.""" + return load_fixture("plex/playqueue_1234.xml") + + @pytest.fixture(name="plex_server_accounts", scope="session") def plex_server_accounts_fixture(): """Load payload accounts on the Plex server and return it.""" diff --git a/tests/components/plex/test_services.py b/tests/components/plex/test_services.py index 9d1715aa72b235..cf8bc63c5dae27 100644 --- a/tests/components/plex/test_services.py +++ b/tests/components/plex/test_services.py @@ -113,6 +113,7 @@ async def test_sonos_play_media( setup_plex_server, requests_mock, empty_payload, + playqueue_1234, playqueue_created, plextv_account, sonos_resources, @@ -178,3 +179,19 @@ async def test_sonos_play_media( play_on_sonos(hass, MEDIA_TYPE_MUSIC, content_id_bad_media, sonos_speaker_name) assert "Plex media not found" in str(excinfo.value) assert playback_mock.call_count == 3 + + # Test with speakers available and playqueue + requests_mock.get("https://1.2.3.4:32400/playQueues/1234", text=playqueue_1234) + content_id_with_playqueue = '{"playqueue_id": 1234}' + play_on_sonos(hass, MEDIA_TYPE_MUSIC, content_id_with_playqueue, sonos_speaker_name) + assert playback_mock.call_count == 4 + + # Test with speakers available and invalid playqueue + requests_mock.get("https://1.2.3.4:32400/playQueues/1235", status_code=404) + content_id_with_playqueue = '{"playqueue_id": 1235}' + with pytest.raises(HomeAssistantError) as excinfo: + play_on_sonos( + hass, MEDIA_TYPE_MUSIC, content_id_with_playqueue, sonos_speaker_name + ) + assert "PlayQueue '1235' could not be found" in str(excinfo.value) + assert playback_mock.call_count == 4 diff --git a/tests/fixtures/plex/media_100.xml b/tests/fixtures/plex/media_100.xml index e1326a4c8626ea..88ad7048fc0449 100644 --- a/tests/fixtures/plex/media_100.xml +++ b/tests/fixtures/plex/media_100.xml @@ -1 +1,7 @@ - + + + + + + + diff --git a/tests/fixtures/plex/playqueue_1234.xml b/tests/fixtures/plex/playqueue_1234.xml new file mode 100644 index 00000000000000..837c2ffbc3c338 --- /dev/null +++ b/tests/fixtures/plex/playqueue_1234.xml @@ -0,0 +1,7 @@ + + + + + + + From 40b3ed141977eea1c058b8643c6b53543c6401ae Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 28 Jan 2021 09:26:41 +0100 Subject: [PATCH 0003/1818] Add additional error handling for automation script run (#45613) --- homeassistant/components/automation/__init__.py | 6 ++++++ homeassistant/helpers/script.py | 3 +++ tests/components/automation/test_init.py | 1 + 3 files changed, 10 insertions(+) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index f1b6df48bdec4c..201eeb5c456505 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -404,6 +404,12 @@ def started_action(): await self.action_script.async_run( variables, trigger_context, started_action ) + except (vol.Invalid, HomeAssistantError) as err: + self._logger.error( + "Error while executing automation %s: %s", + self.entity_id, + err, + ) except Exception: # pylint: disable=broad-except self._logger.exception("While executing automation %s", self.entity_id) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index ff87312dbc2ac3..f197664f7e6dd6 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -291,6 +291,9 @@ def _log_exception(self, exception): elif isinstance(exception, exceptions.ServiceNotFound): error_desc = "Service not found" + elif isinstance(exception, exceptions.HomeAssistantError): + error_desc = "Error" + else: error_desc = "Unexpected error" level = _LOG_EXCEPTION diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 244f37ecb9c9e6..c31af555e32075 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -947,6 +947,7 @@ async def test_automation_with_error_in_script(hass, caplog): hass.bus.async_fire("test_event") await hass.async_block_till_done() assert "Service not found" in caplog.text + assert "Traceback" not in caplog.text async def test_automation_with_error_in_script_2(hass, caplog): From 800c7f84ff048cc5eb478fe09b94740212a605b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 28 Jan 2021 09:33:18 +0100 Subject: [PATCH 0004/1818] Include relative path in tts get url (#45623) * Include relative path in tts get url * Always cal get_url when requested --- homeassistant/components/tts/__init__.py | 26 ++++++++++++++---------- tests/components/tts/test_init.py | 7 ++++--- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 719f8c52e7a1c0..d278283baaf457 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -27,7 +27,6 @@ CONF_PLATFORM, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, - HTTP_OK, ) from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError @@ -117,7 +116,7 @@ async def async_setup(hass, config): use_cache = conf.get(CONF_CACHE, DEFAULT_CACHE) cache_dir = conf.get(CONF_CACHE_DIR, DEFAULT_CACHE_DIR) time_memory = conf.get(CONF_TIME_MEMORY, DEFAULT_TIME_MEMORY) - base_url = conf.get(CONF_BASE_URL) or get_url(hass) + base_url = conf.get(CONF_BASE_URL) hass.data[BASE_URL_KEY] = base_url await tts.async_init_cache(use_cache, cache_dir, time_memory, base_url) @@ -165,13 +164,16 @@ async def async_say_handle(service): options = service.data.get(ATTR_OPTIONS) try: - url = await tts.async_get_url( + url = await tts.async_get_url_path( p_type, message, cache=cache, language=language, options=options ) except HomeAssistantError as err: _LOGGER.error("Error on init TTS: %s", err) return + base = tts.base_url or get_url(hass) + url = base + url + data = { ATTR_MEDIA_CONTENT_ID: url, ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, @@ -290,7 +292,7 @@ def async_register_engine(self, engine, provider, config): provider.name = engine self.providers[engine] = provider - async def async_get_url( + async def async_get_url_path( self, engine, message, cache=None, language=None, options=None ): """Get URL for play message. @@ -342,7 +344,7 @@ async def async_get_url( engine, key, message, use_cache, language, options ) - return f"{self.base_url}/api/tts_proxy/{filename}" + return f"/api/tts_proxy/{filename}" async def async_get_tts_audio(self, engine, key, message, cache, language, options): """Receive TTS and store for view in cache. @@ -579,15 +581,17 @@ async def post(self, request: web.Request) -> web.Response: options = data.get(ATTR_OPTIONS) try: - url = await self.tts.async_get_url( + path = await self.tts.async_get_url_path( p_type, message, cache=cache, language=language, options=options ) - resp = self.json({"url": url}, HTTP_OK) except HomeAssistantError as err: _LOGGER.error("Error on init tts: %s", err) - resp = self.json({"error": err}, HTTP_BAD_REQUEST) + return self.json({"error": err}, HTTP_BAD_REQUEST) + + base = self.tts.base_url or get_url(self.tts.hass) + url = base + path - return resp + return self.json({"url": url, "path": path}) class TextToSpeechView(HomeAssistantView): @@ -595,7 +599,7 @@ class TextToSpeechView(HomeAssistantView): requires_auth = False url = "/api/tts_proxy/{filename}" - name = "api:tts:speech" + name = "api:tts_speech" def __init__(self, tts): """Initialize a tts view.""" @@ -614,4 +618,4 @@ async def get(self, request: web.Request, filename: str) -> web.Response: def get_base_url(hass): """Get base URL.""" - return hass.data[BASE_URL_KEY] + return hass.data[BASE_URL_KEY] or get_url(hass) diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 61d77b6c8e2238..77fbd3f7170dfa 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -699,9 +699,10 @@ async def test_setup_component_and_web_get_url(hass, hass_client): req = await client.post(url, json=data) assert req.status == 200 response = await req.json() - assert response.get("url") == ( - "http://example.local:8123/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3" - ) + assert response == { + "url": "http://example.local:8123/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3", + "path": "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3", + } async def test_setup_component_and_web_get_url_bad_config(hass, hass_client): From 4dbd2f2a6be97e6fba97a020ec7a5c603f0924cb Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 28 Jan 2021 12:05:09 +0100 Subject: [PATCH 0005/1818] Upgrade pyyaml to 5.4.1 (CVE-2020-14343) (#45624) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c7e181cac19276..f51b0e00e14eb0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ pillow==8.1.0 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2020.5 -pyyaml==5.3.1 +pyyaml==5.4.1 requests==2.25.1 ruamel.yaml==0.15.100 scapy==2.4.4 diff --git a/requirements.txt b/requirements.txt index a4e32888047947..c973f4e4030de5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ cryptography==3.2 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2020.5 -pyyaml==5.3.1 +pyyaml==5.4.1 requests==2.25.1 ruamel.yaml==0.15.100 voluptuous==0.12.1 diff --git a/setup.py b/setup.py index 2b05d2ebb2e8cd..7f77e3795b40a1 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ "pip>=8.0.3,<20.3", "python-slugify==4.0.1", "pytz>=2020.5", - "pyyaml==5.3.1", + "pyyaml==5.4.1", "requests==2.25.1", "ruamel.yaml==0.15.100", "voluptuous==0.12.1", From 385bdc405894c900407e1acf1c02b86b73ed1cff Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 28 Jan 2021 08:55:22 +0100 Subject: [PATCH 0006/1818] Bump hatasmota to 0.2.7 (#45625) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_light.py | 56 ++++++++++++++----- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index a8e17815181f14..bd48cae8e5944a 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.2.6"], + "requirements": ["hatasmota==0.2.7"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index ec9556d433db41..f90acacbe332dd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hass-nabucasa==0.41.0 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.2.6 +hatasmota==0.2.7 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d3f7350928b3be..661192640a426a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -384,7 +384,7 @@ hangups==0.4.11 hass-nabucasa==0.41.0 # homeassistant.components.tasmota -hatasmota==0.2.6 +hatasmota==0.2.7 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index c7968df32f220a..d64e39aacf0d00 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -789,7 +789,7 @@ async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): # Turn the light on and verify MQTT message is sent await common.async_turn_on(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 ON", 0, False + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Power1 ON", 0, False ) mqtt_mock.async_publish.reset_mock() @@ -800,21 +800,21 @@ async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): # Turn the light off and verify MQTT message is sent await common.async_turn_off(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 OFF", 0, False + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Power1 OFF", 0, False ) mqtt_mock.async_publish.reset_mock() # Turn the light on and verify MQTT messages are sent await common.async_turn_on(hass, "light.test", brightness=192) mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Dimmer 75", 0, False + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Dimmer 75", 0, False ) mqtt_mock.async_publish.reset_mock() await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 0]) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Power1 ON;NoDelay;Color2 255,128,0", + "NoDelay;Power1 ON;NoDelay;Color2 255,128,0", 0, False, ) @@ -823,7 +823,7 @@ async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", color_temp=200) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Power1 ON;NoDelay;CT 200", + "NoDelay;Power1 ON;NoDelay;CT 200", 0, False, ) @@ -832,7 +832,7 @@ async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", white_value=128) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Power1 ON;NoDelay;White 50", + "NoDelay;Power1 ON;NoDelay;White 50", 0, False, ) @@ -841,7 +841,7 @@ async def test_sending_mqtt_commands_rgbww(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", effect="Random") mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Power1 ON;NoDelay;Scheme 4", + "NoDelay;Power1 ON;NoDelay;Scheme 4", 0, False, ) @@ -873,7 +873,7 @@ async def test_sending_mqtt_commands_power_unlinked(hass, mqtt_mock, setup_tasmo # Turn the light on and verify MQTT message is sent await common.async_turn_on(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 ON", 0, False + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Power1 ON", 0, False ) mqtt_mock.async_publish.reset_mock() @@ -884,7 +884,7 @@ async def test_sending_mqtt_commands_power_unlinked(hass, mqtt_mock, setup_tasmo # Turn the light off and verify MQTT message is sent await common.async_turn_off(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( - "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Fade 0;NoDelay;Power1 OFF", 0, False + "tasmota_49A3BC/cmnd/Backlog", "NoDelay;Power1 OFF", 0, False ) mqtt_mock.async_publish.reset_mock() @@ -892,7 +892,7 @@ async def test_sending_mqtt_commands_power_unlinked(hass, mqtt_mock, setup_tasmo await common.async_turn_on(hass, "light.test", brightness=192) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Dimmer 75;NoDelay;Power1 ON", + "NoDelay;Dimmer 75;NoDelay;Power1 ON", 0, False, ) @@ -978,6 +978,24 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): ) mqtt_mock.async_publish.reset_mock() + # Fake state update from the light + async_fire_mqtt_message( + hass, "tasmota_49A3BC/tele/STATE", '{"POWER":"ON","Dimmer":100}' + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("brightness") == 255 + + # Dim the light from 100->0: Speed should be 0 + await common.async_turn_off(hass, "light.test", transition=0) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", + "NoDelay;Fade 0;NoDelay;Power1 OFF", + 0, + False, + ) + mqtt_mock.async_publish.reset_mock() + # Fake state update from the light async_fire_mqtt_message( hass, @@ -1121,6 +1139,16 @@ async def test_transition_fixed(hass, mqtt_mock, setup_tasmota): ) mqtt_mock.async_publish.reset_mock() + # Dim the light from 0->50: Speed should be 0 + await common.async_turn_on(hass, "light.test", brightness=128, transition=0) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/Backlog", + "NoDelay;Fade 0;NoDelay;Dimmer 50", + 0, + False, + ) + mqtt_mock.async_publish.reset_mock() + async def test_relay_as_light(hass, mqtt_mock, setup_tasmota): """Test relay show up as light in light mode.""" @@ -1167,7 +1195,7 @@ async def _test_split_light(hass, mqtt_mock, config, num_lights, num_switches): await common.async_turn_on(hass, entity) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - f"NoDelay;Fade 0;NoDelay;Power{idx+num_switches+1} ON", + f"NoDelay;Power{idx+num_switches+1} ON", 0, False, ) @@ -1177,7 +1205,7 @@ async def _test_split_light(hass, mqtt_mock, config, num_lights, num_switches): await common.async_turn_on(hass, entity, brightness=(idx + 1) * 25.5) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - f"NoDelay;Fade 0;NoDelay;Channel{idx+num_switches+1} {(idx+1)*10}", + f"NoDelay;Channel{idx+num_switches+1} {(idx+1)*10}", 0, False, ) @@ -1239,7 +1267,7 @@ async def _test_unlinked_light(hass, mqtt_mock, config, num_switches): await common.async_turn_on(hass, entity) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - f"NoDelay;Fade 0;NoDelay;Power{idx+num_switches+1} ON", + f"NoDelay;Power{idx+num_switches+1} ON", 0, False, ) @@ -1249,7 +1277,7 @@ async def _test_unlinked_light(hass, mqtt_mock, config, num_switches): await common.async_turn_on(hass, entity, brightness=(idx + 1) * 25.5) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - f"NoDelay;Fade 0;NoDelay;Dimmer{idx+1} {(idx+1)*10}", + f"NoDelay;Dimmer{idx+1} {(idx+1)*10}", 0, False, ) From 9e5bf6b9f60865cb03cca318b0cf70e53c409248 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 28 Jan 2021 09:05:02 -0600 Subject: [PATCH 0007/1818] Ensure history LazyState state value is always a string (#45644) Co-authored-by: Paulus Schoutsen --- homeassistant/components/history/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 894c2b15e475d2..1e22e45a89249d 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -699,7 +699,7 @@ def __init__(self, row): # pylint: disable=super-init-not-called """Init the lazy state.""" self._row = row self.entity_id = self._row.entity_id - self.state = self._row.state + self.state = self._row.state or "" self._attributes = None self._last_changed = None self._last_updated = None From d1b677d0f94327f4529f7e63cad16bb66fd9e4b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Jan 2021 02:11:24 -0600 Subject: [PATCH 0008/1818] Update httpcore to prevent unhandled exception on dropped connection (#45667) Co-authored-by: Paulus Schoutsen --- homeassistant/package_constraints.txt | 4 ++++ script/gen_requirements_all.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f51b0e00e14eb0..5bd0896b8bd977 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -40,6 +40,10 @@ urllib3>=1.24.3 # Constrain H11 to ensure we get a new enough version to support non-rfc line endings h11>=0.12.0 +# Constrain httpcore to fix exception when connection dropped +# https://github.com/encode/httpcore/issues/239 +httpcore>=0.12.3 + # Constrain httplib2 to protect against CVE-2020-11078 httplib2>=0.18.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 130fd2cc24532e..dc1ef9a471bb61 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -68,6 +68,10 @@ # Constrain H11 to ensure we get a new enough version to support non-rfc line endings h11>=0.12.0 +# Constrain httpcore to fix exception when connection dropped +# https://github.com/encode/httpcore/issues/239 +httpcore>=0.12.3 + # Constrain httplib2 to protect against CVE-2020-11078 httplib2>=0.18.0 From 80e176aab3e83cfffc7f233cea29d978ab9807a2 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 28 Jan 2021 23:45:36 +0100 Subject: [PATCH 0009/1818] Fix removing nodes in zwave_js integration (#45676) --- homeassistant/components/zwave_js/__init__.py | 23 ++++++++++++++++++- homeassistant/components/zwave_js/api.py | 20 ---------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 4cf5a50460e583..c995749f924c00 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,6 +1,7 @@ """The Z-Wave JS integration.""" import asyncio import logging +from typing import Tuple from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient @@ -36,6 +37,12 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: return True +@callback +def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: + """Get device registry identifier for Z-Wave node.""" + return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") + + @callback def register_node_in_dev_reg( hass: HomeAssistant, @@ -47,7 +54,7 @@ def register_node_in_dev_reg( """Register node in dev reg.""" device = dev_reg.async_get_or_create( config_entry_id=entry.entry_id, - identifiers={(DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}")}, + identifiers={get_device_id(client, node)}, sw_version=node.firmware_version, name=node.name or node.device_config.description or f"Node {node.node_id}", model=node.device_config.label, @@ -118,6 +125,15 @@ def async_on_node_added(node: ZwaveNode) -> None: # some visual feedback that something is (in the process of) being added register_node_in_dev_reg(hass, entry, dev_reg, client, node) + @callback + def async_on_node_removed(node: ZwaveNode) -> None: + """Handle node removed event.""" + # grab device in device registry attached to this node + dev_id = get_device_id(client, node) + device = dev_reg.async_get_device({dev_id}) + # note: removal of entity registry is handled by core + dev_reg.async_remove_device(device.id) + async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await client.disconnect() @@ -171,6 +187,11 @@ async def start_platforms() -> None: client.driver.controller.on( "node added", lambda event: async_on_node_added(event["node"]) ) + # listen for nodes being removed from the mesh + # NOTE: This will not remove nodes that were removed when HA was not running + client.driver.controller.on( + "node removed", lambda event: async_on_node_removed(event["node"]) + ) hass.async_create_task(start_platforms()) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 2027200d8b755b..1a8a197571b1b8 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -5,15 +5,12 @@ from aiohttp import hdrs, web, web_exceptions import voluptuous as vol from zwave_js_server import dump -from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.model.node import Node as ZwaveNode from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import device_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -248,9 +245,6 @@ def node_removed(event: dict) -> None: "node_id": node.node_id, } - # Remove from device registry - hass.async_create_task(remove_from_device_registry(hass, client, node)) - connection.send_message( websocket_api.event_message( msg[ID], {"event": "node removed", "node": node_details} @@ -272,20 +266,6 @@ def node_removed(event: dict) -> None: ) -async def remove_from_device_registry( - hass: HomeAssistant, client: ZwaveClient, node: ZwaveNode -) -> None: - """Remove a node from the device registry.""" - registry = await device_registry.async_get_registry(hass) - device = registry.async_get_device( - {(DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}")} - ) - if device is None: - return - - registry.async_remove_device(device.id) - - class DumpView(HomeAssistantView): """View to dump the state of the Z-Wave JS server.""" From af0ca31d772baa5e0b939847965449550d3baa57 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 29 Jan 2021 00:46:28 +0100 Subject: [PATCH 0010/1818] Update frontend to 20210127.3 (#45679) --- 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 65ddab7f9478a2..6f05ab04e6f920 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.1"], + "requirements": ["home-assistant-frontend==20210127.3"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5bd0896b8bd977..630eb789d00034 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.1 +home-assistant-frontend==20210127.3 httpx==0.16.1 jinja2>=2.11.2 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index f90acacbe332dd..2444514aa6cac1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.1 +home-assistant-frontend==20210127.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 661192640a426a..2ee2c86c971630 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -402,7 +402,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.1 +home-assistant-frontend==20210127.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 6070c7c83a1b59e13ed1c0e80c806c7993e2cad9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 29 Jan 2021 09:13:06 +0100 Subject: [PATCH 0011/1818] Bumped version to 2021.2.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 88eb8e51f41731..52cbee39523eb8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From ace5b58337a9938816f2ff1e4f005b4dc22ed180 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 29 Jan 2021 20:58:57 +0100 Subject: [PATCH 0012/1818] Fix ozw init tests (#45718) --- tests/components/ozw/test_init.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/components/ozw/test_init.py b/tests/components/ozw/test_init.py index c76bfd4a3a05d1..2e57c4c01f3ec4 100644 --- a/tests/components/ozw/test_init.py +++ b/tests/components/ozw/test_init.py @@ -53,6 +53,7 @@ async def test_publish_without_mqtt(hass, caplog): # Sending a message should not error with the MQTT integration not set up. send_message("test_topic", "test_payload") + await hass.async_block_till_done() assert "MQTT integration is not set up" in caplog.text @@ -127,8 +128,8 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) - stop_addon.call_count == 1 - uninstall_addon.call_count == 1 + assert stop_addon.call_count == 1 + assert uninstall_addon.call_count == 1 assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 stop_addon.reset_mock() @@ -141,8 +142,8 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) - stop_addon.call_count == 1 - uninstall_addon.call_count == 0 + assert stop_addon.call_count == 1 + assert uninstall_addon.call_count == 0 assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to stop the OpenZWave add-on" in caplog.text @@ -157,8 +158,8 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) - stop_addon.call_count == 1 - uninstall_addon.call_count == 1 + assert stop_addon.call_count == 1 + assert uninstall_addon.call_count == 1 assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to uninstall the OpenZWave add-on" in caplog.text From 84f506efb719b16d5bf441a44b2e85d863117069 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Fri, 29 Jan 2021 21:11:12 +0100 Subject: [PATCH 0013/1818] Set default position value for cover action (#45670) Co-authored-by: Bram Kragten Co-authored-by: Franck Nijhof --- homeassistant/components/cover/device_action.py | 4 +++- homeassistant/helpers/config_validation.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 29dd97909e30da..490ce162d9a369 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -49,7 +49,9 @@ { vol.Required(CONF_TYPE): vol.In(POSITION_ACTION_TYPES), vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), - vol.Required("position"): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), + vol.Optional("position", default=0): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100) + ), } ) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index acf6139708a51f..d47ba30c114e11 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -266,7 +266,7 @@ def entity_id(value: Any) -> str: if valid_entity_id(str_value): return str_value - raise vol.Invalid(f"Entity ID {value} is an invalid entity id") + raise vol.Invalid(f"Entity ID {value} is an invalid entity ID") def entity_ids(value: Union[str, List]) -> List[str]: From 14c205384171dee59c1a908f8449f9864778b2dc Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 29 Jan 2021 13:30:21 -0700 Subject: [PATCH 0014/1818] Bump simplisafe-python to 9.6.4 (#45716) * Bump simplisafe-python to 9.6.4 * Fix imports --- homeassistant/components/simplisafe/__init__.py | 8 ++++---- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 89f5c40b1ff74f..495ba29fefb524 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -10,10 +10,10 @@ EVENT_CONNECTION_LOST, EVENT_CONNECTION_RESTORED, EVENT_DOORBELL_DETECTED, - EVENT_ENTRY_DETECTED, + EVENT_ENTRY_DELAY, EVENT_LOCK_LOCKED, EVENT_LOCK_UNLOCKED, - EVENT_MOTION_DETECTED, + EVENT_SECRET_ALERT_TRIGGERED, ) import voluptuous as vol @@ -82,8 +82,8 @@ WEBSOCKET_EVENTS_TO_TRIGGER_HASS_EVENT = [ EVENT_CAMERA_MOTION_DETECTED, EVENT_DOORBELL_DETECTED, - EVENT_ENTRY_DETECTED, - EVENT_MOTION_DETECTED, + EVENT_ENTRY_DELAY, + EVENT_SECRET_ALERT_TRIGGERED, ] ATTR_CATEGORY = "category" diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index a502a7908f087c..b18bafb0bbfaa9 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.2"], + "requirements": ["simplisafe-python==9.6.4"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2db6ddc37e62d7..f1755b3ed1b41a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2028,7 +2028,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.2 +simplisafe-python==9.6.4 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 345edcfbb35106..90505f1297c05c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1017,7 +1017,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.2 +simplisafe-python==9.6.4 # homeassistant.components.slack slackclient==2.5.0 From adf8873e5695907fd16673870ec2c063169c1404 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Fri, 29 Jan 2021 23:00:27 +0100 Subject: [PATCH 0015/1818] Remove ggravlingen from codeowners (#45723) --- CODEOWNERS | 1 - homeassistant/components/tradfri/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 73d42f4efcffeb..e3cf58f90564e3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -477,7 +477,6 @@ homeassistant/components/toon/* @frenck homeassistant/components/totalconnect/* @austinmroczek homeassistant/components/tplink/* @rytilahti @thegardenmonkey homeassistant/components/traccar/* @ludeeus -homeassistant/components/tradfri/* @ggravlingen homeassistant/components/trafikverket_train/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/transmission/* @engrbm87 @JPHutchins diff --git a/homeassistant/components/tradfri/manifest.json b/homeassistant/components/tradfri/manifest.json index 5c6bf76a169a57..99b9dff6d22373 100644 --- a/homeassistant/components/tradfri/manifest.json +++ b/homeassistant/components/tradfri/manifest.json @@ -7,5 +7,5 @@ "homekit": { "models": ["TRADFRI"] }, - "codeowners": ["@ggravlingen"] + "codeowners": [] } From f07ffee535980254d39734763158cb6257ca64a0 Mon Sep 17 00:00:00 2001 From: Pascal Reeb Date: Fri, 29 Jan 2021 23:01:25 +0100 Subject: [PATCH 0016/1818] Advanced testing for Nuki config flow (#45721) --- homeassistant/components/nuki/__init__.py | 10 -- homeassistant/components/nuki/lock.py | 1 - tests/components/nuki/test_config_flow.py | 107 ++++++++++++++++++++-- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index 627cf20b16b416..4af3e0d8ed45bf 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -1,7 +1,6 @@ """The nuki component.""" from datetime import timedelta -import logging import voluptuous as vol @@ -12,8 +11,6 @@ from .const import DEFAULT_PORT, DOMAIN -_LOGGER = logging.getLogger(__name__) - NUKI_PLATFORMS = ["lock"] UPDATE_INTERVAL = timedelta(seconds=30) @@ -27,16 +24,10 @@ ) ) -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema(NUKI_SCHEMA)}, - extra=vol.ALLOW_EXTRA, -) - async def async_setup(hass, config): """Set up the Nuki component.""" hass.data.setdefault(DOMAIN, {}) - _LOGGER.debug("Config: %s", config) for platform in NUKI_PLATFORMS: confs = config.get(platform) @@ -44,7 +35,6 @@ async def async_setup(hass, config): continue for conf in confs: - _LOGGER.debug("Conf: %s", conf) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=conf diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index fe024405908b15..818784a2b2e24e 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -45,7 +45,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Nuki lock platform.""" config = config_entry.data - _LOGGER.debug("Config: %s", config) def get_entities(): bridge = NukiBridge( diff --git a/tests/components/nuki/test_config_flow.py b/tests/components/nuki/test_config_flow.py index 45168e42c9d69b..bcdedad371a3f0 100644 --- a/tests/components/nuki/test_config_flow.py +++ b/tests/components/nuki/test_config_flow.py @@ -1,10 +1,14 @@ """Test the nuki config flow.""" from unittest.mock import patch -from homeassistant import config_entries, setup -from homeassistant.components.nuki.config_flow import CannotConnect, InvalidAuth +from pynuki.bridge import InvalidCredentialsException +from requests.exceptions import RequestException + +from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.nuki.const import DOMAIN +from tests.common import MockConfigEntry + async def test_form(hass): """Test we get the form.""" @@ -12,7 +16,7 @@ async def test_form(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "form" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {} mock_info = {"ids": {"hardwareId": "0001"}} @@ -36,7 +40,7 @@ async def test_form(hass): ) await hass.async_block_till_done() - assert result2["type"] == "create_entry" + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "0001" assert result2["data"] == { "host": "1.1.1.1", @@ -47,6 +51,39 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_import(hass): + """Test that the import works.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + mock_info = {"ids": {"hardwareId": "0001"}} + + with patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + return_value=mock_info, + ), patch( + "homeassistant.components.nuki.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.nuki.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": "1.1.1.1", "port": 8080, "token": "test-token"}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "0001" + assert result["data"] == { + "host": "1.1.1.1", + "port": 8080, + "token": "test-token", + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_form_invalid_auth(hass): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -55,7 +92,7 @@ async def test_form_invalid_auth(hass): with patch( "homeassistant.components.nuki.config_flow.NukiBridge.info", - side_effect=InvalidAuth, + side_effect=InvalidCredentialsException, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -66,7 +103,7 @@ 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"} @@ -78,7 +115,7 @@ async def test_form_cannot_connect(hass): with patch( "homeassistant.components.nuki.config_flow.NukiBridge.info", - side_effect=CannotConnect, + side_effect=RequestException, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -89,5 +126,59 @@ 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"} + + +async def test_form_unknown_exception(hass): + """Test we handle unknown exceptions.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "port": 8080, + "token": "test-token", + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + +async def test_form_already_configured(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( + domain="nuki", + unique_id="0001", + data={"host": "1.1.1.1", "port": 8080, "token": "test-token"}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + return_value={"ids": {"hardwareId": "0001"}}, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "port": 8080, + "token": "test-token", + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "already_configured" From 41e2e5043b8c5ede1531fd24d8f8d836ba3a6c6c Mon Sep 17 00:00:00 2001 From: chpego <38792705+chpego@users.noreply.github.com> Date: Fri, 29 Jan 2021 23:14:17 +0100 Subject: [PATCH 0017/1818] Upgrade youtube_dl to version 2021.01.24.1 (#45724) * Upgrade youtube_dl to version 2021.01.24.1 * Update requirements_all.txt --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 5a09171df80c2a..c6ee6ccb8a4c9a 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -2,7 +2,7 @@ "domain": "media_extractor", "name": "Media Extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", - "requirements": ["youtube_dl==2021.01.16"], + "requirements": ["youtube_dl==2021.01.24.1"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index f1755b3ed1b41a..af3f8235a9bd4f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2339,7 +2339,7 @@ yeelight==0.5.4 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2021.01.16 +youtube_dl==2021.01.24.1 # homeassistant.components.onvif zeep[async]==4.0.0 From 87d40ff81581005d1356ef8ead5c59c398bba378 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 30 Jan 2021 00:05:06 +0100 Subject: [PATCH 0018/1818] Do not cache frontend files during dev (#45698) --- homeassistant/components/frontend/__init__.py | 8 +- tests/components/frontend/test_init.py | 217 +++++++++--------- 2 files changed, 107 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 080d786d4e487c..cdf25d22fe8611 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -262,10 +262,10 @@ async def async_setup(hass, config): for path, should_cache in ( ("service_worker.js", False), ("robots.txt", False), - ("onboarding.html", True), - ("static", True), - ("frontend_latest", True), - ("frontend_es5", True), + ("onboarding.html", not is_dev), + ("static", not is_dev), + ("frontend_latest", not is_dev), + ("frontend_es5", not is_dev), ): hass.http.register_static_path(f"/{path}", str(root_path / path), should_cache) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 5ae8d707cb1ee5..0e8e31bb20de98 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -33,44 +33,67 @@ @pytest.fixture -def mock_http_client(hass, aiohttp_client): +async def ignore_frontend_deps(hass): + """Frontend dependencies.""" + frontend = await async_get_integration(hass, "frontend") + for dep in frontend.dependencies: + if dep not in ("http", "websocket_api"): + hass.config.components.add(dep) + + +@pytest.fixture +async def frontend(hass, ignore_frontend_deps): + """Frontend setup with themes.""" + assert await async_setup_component( + hass, + "frontend", + {}, + ) + + +@pytest.fixture +async def frontend_themes(hass): + """Frontend setup with themes.""" + assert await async_setup_component( + hass, + "frontend", + CONFIG_THEMES, + ) + + +@pytest.fixture +async def mock_http_client(hass, aiohttp_client, frontend): """Start the Home Assistant HTTP component.""" - hass.loop.run_until_complete(async_setup_component(hass, "frontend", {})) - return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) + return await aiohttp_client(hass.http.app) @pytest.fixture -def mock_http_client_with_themes(hass, aiohttp_client): +async def themes_ws_client(hass, hass_ws_client, frontend_themes): """Start the Home Assistant HTTP component.""" - hass.loop.run_until_complete( - async_setup_component( - hass, - "frontend", - {DOMAIN: {CONF_THEMES: {"happy": {"primary-color": "red"}}}}, - ) - ) - return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) + return await hass_ws_client(hass) @pytest.fixture -def mock_http_client_with_urls(hass, aiohttp_client): +async def ws_client(hass, hass_ws_client, frontend): """Start the Home Assistant HTTP component.""" - hass.loop.run_until_complete( - async_setup_component( - hass, - "frontend", - { - DOMAIN: { - CONF_JS_VERSION: "auto", - CONF_EXTRA_HTML_URL: ["https://domain.com/my_extra_url.html"], - CONF_EXTRA_HTML_URL_ES5: [ - "https://domain.com/my_extra_url_es5.html" - ], - } - }, - ) + return await hass_ws_client(hass) + + +@pytest.fixture +async def mock_http_client_with_urls(hass, aiohttp_client, ignore_frontend_deps): + """Start the Home Assistant HTTP component.""" + assert await async_setup_component( + hass, + "frontend", + { + DOMAIN: { + CONF_JS_VERSION: "auto", + CONF_EXTRA_HTML_URL: ["https://domain.com/my_extra_url.html"], + CONF_EXTRA_HTML_URL_ES5: ["https://domain.com/my_extra_url_es5.html"], + } + }, ) - return hass.loop.run_until_complete(aiohttp_client(hass.http.app)) + return await aiohttp_client(hass.http.app) @pytest.fixture @@ -118,13 +141,10 @@ async def test_we_cannot_POST_to_root(mock_http_client): assert resp.status == 405 -async def test_themes_api(hass, hass_ws_client): +async def test_themes_api(hass, themes_ws_client): """Test that /api/themes returns correct data.""" - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) - - await client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "default" assert msg["result"]["default_dark_theme"] is None @@ -135,8 +155,8 @@ async def test_themes_api(hass, hass_ws_client): # safe mode hass.config.safe_mode = True - await client.send_json({"id": 6, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 6, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "safe_mode" assert msg["result"]["themes"] == { @@ -144,9 +164,8 @@ async def test_themes_api(hass, hass_ws_client): } -async def test_themes_persist(hass, hass_ws_client, hass_storage): +async def test_themes_persist(hass, hass_storage, hass_ws_client, ignore_frontend_deps): """Test that theme settings are restores after restart.""" - hass_storage[THEMES_STORAGE_KEY] = { "key": THEMES_STORAGE_KEY, "version": 1, @@ -157,26 +176,18 @@ async def test_themes_persist(hass, hass_ws_client, hass_storage): } assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) + themes_ws_client = await hass_ws_client(hass) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "happy" assert msg["result"]["default_dark_theme"] == "dark" -async def test_themes_save_storage(hass, hass_storage): +async def test_themes_save_storage(hass, hass_storage, frontend_themes): """Test that theme settings are restores after restart.""" - hass_storage[THEMES_STORAGE_KEY] = { - "key": THEMES_STORAGE_KEY, - "version": 1, - "data": {}, - } - - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - await hass.services.async_call( DOMAIN, "set_theme", {"name": "happy"}, blocking=True ) @@ -196,17 +207,14 @@ async def test_themes_save_storage(hass, hass_storage): } -async def test_themes_set_theme(hass, hass_ws_client): +async def test_themes_set_theme(hass, themes_ws_client): """Test frontend.set_theme service.""" - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) - await hass.services.async_call( DOMAIN, "set_theme", {"name": "happy"}, blocking=True ) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "happy" @@ -214,8 +222,8 @@ async def test_themes_set_theme(hass, hass_ws_client): DOMAIN, "set_theme", {"name": "default"}, blocking=True ) - await client.send_json({"id": 6, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 6, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "default" @@ -225,39 +233,35 @@ async def test_themes_set_theme(hass, hass_ws_client): await hass.services.async_call(DOMAIN, "set_theme", {"name": "none"}, blocking=True) - await client.send_json({"id": 7, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 7, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "default" -async def test_themes_set_theme_wrong_name(hass, hass_ws_client): +async def test_themes_set_theme_wrong_name(hass, themes_ws_client): """Test frontend.set_theme service called with wrong name.""" - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) await hass.services.async_call( DOMAIN, "set_theme", {"name": "wrong"}, blocking=True ) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + msg = await themes_ws_client.receive_json() assert msg["result"]["default_theme"] == "default" -async def test_themes_set_dark_theme(hass, hass_ws_client): +async def test_themes_set_dark_theme(hass, themes_ws_client): """Test frontend.set_theme service called with dark mode.""" - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) await hass.services.async_call( DOMAIN, "set_theme", {"name": "dark", "mode": "dark"}, blocking=True ) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_dark_theme"] == "dark" @@ -265,8 +269,8 @@ async def test_themes_set_dark_theme(hass, hass_ws_client): DOMAIN, "set_theme", {"name": "default", "mode": "dark"}, blocking=True ) - await client.send_json({"id": 6, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 6, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_dark_theme"] == "default" @@ -274,32 +278,27 @@ async def test_themes_set_dark_theme(hass, hass_ws_client): DOMAIN, "set_theme", {"name": "none", "mode": "dark"}, blocking=True ) - await client.send_json({"id": 7, "type": "frontend/get_themes"}) - msg = await client.receive_json() + await themes_ws_client.send_json({"id": 7, "type": "frontend/get_themes"}) + msg = await themes_ws_client.receive_json() assert msg["result"]["default_dark_theme"] is None -async def test_themes_set_dark_theme_wrong_name(hass, hass_ws_client): +async def test_themes_set_dark_theme_wrong_name(hass, frontend, themes_ws_client): """Test frontend.set_theme service called with mode dark and wrong name.""" - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) - await hass.services.async_call( DOMAIN, "set_theme", {"name": "wrong", "mode": "dark"}, blocking=True ) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + msg = await themes_ws_client.receive_json() assert msg["result"]["default_dark_theme"] is None -async def test_themes_reload_themes(hass, hass_ws_client): +async def test_themes_reload_themes(hass, frontend, themes_ws_client): """Test frontend.reload_themes service.""" - assert await async_setup_component(hass, "frontend", CONFIG_THEMES) - client = await hass_ws_client(hass) with patch( "homeassistant.components.frontend.async_hass_config_yaml", @@ -310,22 +309,19 @@ async def test_themes_reload_themes(hass, hass_ws_client): ) await hass.services.async_call(DOMAIN, "reload_themes", blocking=True) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) + await themes_ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) - msg = await client.receive_json() + msg = await themes_ws_client.receive_json() assert msg["result"]["themes"] == {"sad": {"primary-color": "blue"}} assert msg["result"]["default_theme"] == "default" -async def test_missing_themes(hass, hass_ws_client): +async def test_missing_themes(hass, ws_client): """Test that themes API works when themes are not defined.""" - await async_setup_component(hass, "frontend", {}) + await ws_client.send_json({"id": 5, "type": "frontend/get_themes"}) - client = await hass_ws_client(hass) - await client.send_json({"id": 5, "type": "frontend/get_themes"}) - - msg = await client.receive_json() + msg = await ws_client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT @@ -372,10 +368,10 @@ async def test_get_panels(hass, hass_ws_client, mock_http_client): assert len(events) == 2 -async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user): +async def test_get_panels_non_admin(hass, ws_client, hass_admin_user): """Test get_panels command.""" hass_admin_user.groups = [] - await async_setup_component(hass, "frontend", {}) + hass.components.frontend.async_register_built_in_panel( "map", "Map", "mdi:tooltip-account", require_admin=True ) @@ -383,10 +379,9 @@ async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user): "history", "History", "mdi:history" ) - client = await hass_ws_client(hass) - await client.send_json({"id": 5, "type": "get_panels"}) + await ws_client.send_json({"id": 5, "type": "get_panels"}) - msg = await client.receive_json() + msg = await ws_client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT @@ -395,18 +390,15 @@ async def test_get_panels_non_admin(hass, hass_ws_client, hass_admin_user): assert "map" not in msg["result"] -async def test_get_translations(hass, hass_ws_client): +async def test_get_translations(hass, ws_client): """Test get_translations command.""" - await async_setup_component(hass, "frontend", {}) - client = await hass_ws_client(hass) - with patch( "homeassistant.components.frontend.async_get_translations", side_effect=lambda hass, lang, category, integration, config_flow: { "lang": lang }, ): - await client.send_json( + await ws_client.send_json( { "id": 5, "type": "frontend/get_translations", @@ -414,7 +406,7 @@ async def test_get_translations(hass, hass_ws_client): "category": "lang", } ) - msg = await client.receive_json() + msg = await ws_client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT @@ -422,16 +414,16 @@ async def test_get_translations(hass, hass_ws_client): assert msg["result"] == {"resources": {"lang": "nl"}} -async def test_auth_load(mock_http_client, mock_onboarded): +async def test_auth_load(hass): """Test auth component loaded by default.""" - resp = await mock_http_client.get("/auth/providers") - assert resp.status == 200 + frontend = await async_get_integration(hass, "frontend") + assert "auth" in frontend.dependencies -async def test_onboarding_load(mock_http_client): +async def test_onboarding_load(hass): """Test onboarding component loaded by default.""" - resp = await mock_http_client.get("/api/onboarding") - assert resp.status == 200 + frontend = await async_get_integration(hass, "frontend") + assert "onboarding" in frontend.dependencies async def test_auth_authorize(mock_http_client): @@ -457,7 +449,7 @@ async def test_auth_authorize(mock_http_client): assert "public" in resp.headers.get("cache-control") -async def test_get_version(hass, hass_ws_client): +async def test_get_version(hass, ws_client): """Test get_version command.""" frontend = await async_get_integration(hass, "frontend") cur_version = next( @@ -466,11 +458,8 @@ async def test_get_version(hass, hass_ws_client): if req.startswith("home-assistant-frontend==") ) - await async_setup_component(hass, "frontend", {}) - client = await hass_ws_client(hass) - - await client.send_json({"id": 5, "type": "frontend/get_version"}) - msg = await client.receive_json() + await ws_client.send_json({"id": 5, "type": "frontend/get_version"}) + msg = await ws_client.receive_json() assert msg["id"] == 5 assert msg["type"] == TYPE_RESULT From 8a112721fade61b2e670d4f90bfb6e239eca66f3 Mon Sep 17 00:00:00 2001 From: Ryan Fleming Date: Sat, 30 Jan 2021 02:00:27 -0500 Subject: [PATCH 0019/1818] Fix feedback from UVC (#45630) * Fixing feedback from UVC * Couple of fixes --- homeassistant/components/uvc/camera.py | 10 ++++++---- tests/components/uvc/test_camera.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 4f5cfa3907ed1a..ae10c7db48f0b7 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -2,6 +2,7 @@ from datetime import datetime import logging import re +from typing import Optional import requests from uvcclient import camera as uvc_camera, nvr @@ -111,9 +112,9 @@ def supported_features(self): return 0 @property - def state_attributes(self): + def device_state_attributes(self): """Return the camera state attributes.""" - attr = super().state_attributes + attr = {} if self.motion_detection_enabled: attr["last_recording_start_time"] = timestamp_ms_to_date( self._caminfo["lastRecordingStartTime"] @@ -124,7 +125,7 @@ def state_attributes(self): def is_recording(self): """Return true if the camera is recording.""" recording_state = "DISABLED" - if "recordingIndicator" in self._caminfo.keys(): + if "recordingIndicator" in self._caminfo: recording_state = self._caminfo["recordingIndicator"] return ( @@ -256,7 +257,8 @@ def update(self): self._caminfo = self._nvr.get_camera(self._uuid) -def timestamp_ms_to_date(epoch_ms) -> datetime or None: +def timestamp_ms_to_date(epoch_ms: int) -> Optional[datetime]: """Convert millisecond timestamp to datetime.""" if epoch_ms: return datetime.fromtimestamp(epoch_ms / 1000) + return None diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 00c827b9973bf4..1dd44625ebe955 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -257,7 +257,7 @@ def test_motion_recording_mode_properties(self): assert not self.uvc.is_recording assert ( datetime(2021, 1, 8, 1, 56, 32, 367000) - == self.uvc.state_attributes["last_recording_start_time"] + == self.uvc.device_state_attributes["last_recording_start_time"] ) self.nvr.get_camera.return_value["recordingIndicator"] = "DISABLED" From 07a4422a704aafbb6d42b3155312b9dbbc3428b2 Mon Sep 17 00:00:00 2001 From: Nathan Tilley Date: Sat, 30 Jan 2021 02:05:58 -0500 Subject: [PATCH 0020/1818] Implement person significant change (#45713) --- .../components/person/significant_change.py | 21 +++++++++++++++++++ .../person/test_significant_change.py | 16 ++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 homeassistant/components/person/significant_change.py create mode 100644 tests/components/person/test_significant_change.py diff --git a/homeassistant/components/person/significant_change.py b/homeassistant/components/person/significant_change.py new file mode 100644 index 00000000000000..d9c1ec6cc2347a --- /dev/null +++ b/homeassistant/components/person/significant_change.py @@ -0,0 +1,21 @@ +"""Helper to test significant Person state changes.""" +from typing import Any, Optional + +from homeassistant.core import HomeAssistant, callback + + +@callback +def async_check_significant_change( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + new_state: str, + new_attrs: dict, + **kwargs: Any, +) -> Optional[bool]: + """Test if state significantly changed.""" + + if new_state != old_state: + return True + + return False diff --git a/tests/components/person/test_significant_change.py b/tests/components/person/test_significant_change.py new file mode 100644 index 00000000000000..1b4f6940e90cb7 --- /dev/null +++ b/tests/components/person/test_significant_change.py @@ -0,0 +1,16 @@ +"""Test the Person significant change platform.""" +from homeassistant.components.person.significant_change import ( + async_check_significant_change, +) + + +async def test_significant_change(): + """Detect Person significant changes and ensure that attribute changes do not trigger a significant change.""" + old_attrs = {"source": "device_tracker.wifi_device"} + new_attrs = {"source": "device_tracker.gps_device"} + assert not async_check_significant_change( + None, "home", old_attrs, "home", new_attrs + ) + assert async_check_significant_change( + None, "home", new_attrs, "not_home", new_attrs + ) From 85e6bc581f9475df289ed0f76ccd7c03ed749304 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 30 Jan 2021 01:03:54 -0700 Subject: [PATCH 0021/1818] Add significant change support to lock (#45726) --- .../components/lock/significant_change.py | 20 ++++++++++++++++ .../lock/test_significant_change.py | 23 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 homeassistant/components/lock/significant_change.py create mode 100644 tests/components/lock/test_significant_change.py diff --git a/homeassistant/components/lock/significant_change.py b/homeassistant/components/lock/significant_change.py new file mode 100644 index 00000000000000..59a3b1a95c5a63 --- /dev/null +++ b/homeassistant/components/lock/significant_change.py @@ -0,0 +1,20 @@ +"""Helper to test significant Lock state changes.""" +from typing import Any, Optional + +from homeassistant.core import HomeAssistant, callback + + +@callback +def async_check_significant_change( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + new_state: str, + new_attrs: dict, + **kwargs: Any, +) -> Optional[bool]: + """Test if state significantly changed.""" + if old_state != new_state: + return True + + return False diff --git a/tests/components/lock/test_significant_change.py b/tests/components/lock/test_significant_change.py new file mode 100644 index 00000000000000..a9ffbc0d1c4c33 --- /dev/null +++ b/tests/components/lock/test_significant_change.py @@ -0,0 +1,23 @@ +"""Test the Lock significant change platform.""" +from homeassistant.components.lock.significant_change import ( + async_check_significant_change, +) + + +async def test_significant_change(): + """Detect Lock significant changes.""" + old_attrs = {"attr_1": "a"} + new_attrs = {"attr_1": "b"} + + assert ( + async_check_significant_change(None, "locked", old_attrs, "locked", old_attrs) + is False + ) + assert ( + async_check_significant_change(None, "locked", old_attrs, "locked", new_attrs) + is False + ) + assert ( + async_check_significant_change(None, "locked", old_attrs, "unlocked", old_attrs) + is True + ) From 6bf59dbeabb0a8d211b56e79f58e9d943b74bc0e Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 30 Jan 2021 01:04:35 -0700 Subject: [PATCH 0022/1818] Add significant change support to binary_sensor (#45677) * Add significant change support to binary_sensor --- .../binary_sensor/significant_change.py | 20 +++++++++++++++++++ .../binary_sensor/translations/en.json | 4 ++-- .../binary_sensor/test_significant_change.py | 20 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/binary_sensor/significant_change.py create mode 100644 tests/components/binary_sensor/test_significant_change.py diff --git a/homeassistant/components/binary_sensor/significant_change.py b/homeassistant/components/binary_sensor/significant_change.py new file mode 100644 index 00000000000000..bc2dba04f09c88 --- /dev/null +++ b/homeassistant/components/binary_sensor/significant_change.py @@ -0,0 +1,20 @@ +"""Helper to test significant Binary Sensor state changes.""" +from typing import Any, Optional + +from homeassistant.core import HomeAssistant, callback + + +@callback +def async_check_significant_change( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + new_state: str, + new_attrs: dict, + **kwargs: Any, +) -> Optional[bool]: + """Test if state significantly changed.""" + if old_state != new_state: + return True + + return False diff --git a/homeassistant/components/binary_sensor/translations/en.json b/homeassistant/components/binary_sensor/translations/en.json index 98c8a3a220a9ae..a9a20e3fa50729 100644 --- a/homeassistant/components/binary_sensor/translations/en.json +++ b/homeassistant/components/binary_sensor/translations/en.json @@ -159,8 +159,8 @@ "on": "Plugged in" }, "presence": { - "off": "Away", - "on": "Home" + "off": "[%key:common::state::not_home%]", + "on": "[%key:common::state::home%]" }, "problem": { "off": "OK", diff --git a/tests/components/binary_sensor/test_significant_change.py b/tests/components/binary_sensor/test_significant_change.py new file mode 100644 index 00000000000000..673374a15e4b57 --- /dev/null +++ b/tests/components/binary_sensor/test_significant_change.py @@ -0,0 +1,20 @@ +"""Test the Binary Sensor significant change platform.""" +from homeassistant.components.binary_sensor.significant_change import ( + async_check_significant_change, +) + + +async def test_significant_change(): + """Detect Binary Sensor significant changes.""" + old_attrs = {"attr_1": "value_1"} + new_attrs = {"attr_1": "value_2"} + + assert ( + async_check_significant_change(None, "on", old_attrs, "on", old_attrs) is False + ) + assert ( + async_check_significant_change(None, "on", old_attrs, "on", new_attrs) is False + ) + assert ( + async_check_significant_change(None, "on", old_attrs, "off", old_attrs) is True + ) From d81017f62ef39fd9d87a08e18fde2490e3d78ead Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jan 2021 02:56:51 -0600 Subject: [PATCH 0023/1818] Use DataUpdateCoordinator for solaredge (#45734) Co-authored-by: Martin Hjelmare --- homeassistant/components/solaredge/sensor.py | 262 ++++++++++--------- 1 file changed, 144 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index e3e59676bf5f97..8609e578e5e371 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -1,4 +1,5 @@ """Support for SolarEdge Monitoring API.""" +from abc import abstractmethod from datetime import date, datetime import logging @@ -7,8 +8,14 @@ from stringcase import snakecase from homeassistant.const import CONF_API_KEY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_POWER +from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) from .const import ( CONF_SITE_ID, @@ -37,14 +44,20 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.error("SolarEdge site is not active") return _LOGGER.debug("Credentials correct and site is active") - except KeyError: + except KeyError as ex: _LOGGER.error("Missing details data in SolarEdge response") - return - except (ConnectTimeout, HTTPError): + raise ConfigEntryNotReady from ex + except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Could not retrieve details from SolarEdge API") - return + raise ConfigEntryNotReady from ex + + sensor_factory = SolarEdgeSensorFactory( + hass, entry.title, entry.data[CONF_SITE_ID], api + ) + for service in sensor_factory.all_services: + service.async_setup() + await service.coordinator.async_refresh() - sensor_factory = SolarEdgeSensorFactory(entry.title, entry.data[CONF_SITE_ID], api) entities = [] for sensor_key in SENSOR_TYPES: sensor = sensor_factory.create_sensor(sensor_key) @@ -56,15 +69,17 @@ async def async_setup_entry(hass, entry, async_add_entities): class SolarEdgeSensorFactory: """Factory which creates sensors based on the sensor_key.""" - def __init__(self, platform_name, site_id, api): + def __init__(self, hass, platform_name, site_id, api): """Initialize the factory.""" self.platform_name = platform_name - details = SolarEdgeDetailsDataService(api, site_id) - overview = SolarEdgeOverviewDataService(api, site_id) - inventory = SolarEdgeInventoryDataService(api, site_id) - flow = SolarEdgePowerFlowDataService(api, site_id) - energy = SolarEdgeEnergyDetailsService(api, site_id) + details = SolarEdgeDetailsDataService(hass, api, site_id) + overview = SolarEdgeOverviewDataService(hass, api, site_id) + inventory = SolarEdgeInventoryDataService(hass, api, site_id) + flow = SolarEdgePowerFlowDataService(hass, api, site_id) + energy = SolarEdgeEnergyDetailsService(hass, api, site_id) + + self.all_services = (details, overview, inventory, flow, energy) self.services = {"site_details": (SolarEdgeDetailsSensor, details)} @@ -102,39 +117,30 @@ def create_sensor(self, sensor_key): return sensor_class(self.platform_name, sensor_key, service) -class SolarEdgeSensor(Entity): +class SolarEdgeSensor(CoordinatorEntity, Entity): """Abstract class for a solaredge sensor.""" def __init__(self, platform_name, sensor_key, data_service): """Initialize the sensor.""" + super().__init__(data_service.coordinator) self.platform_name = platform_name self.sensor_key = sensor_key self.data_service = data_service - self._state = None - - self._unit_of_measurement = SENSOR_TYPES[self.sensor_key][2] - self._icon = SENSOR_TYPES[self.sensor_key][3] + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return SENSOR_TYPES[self.sensor_key][2] @property def name(self): """Return the name.""" return "{} ({})".format(self.platform_name, SENSOR_TYPES[self.sensor_key][1]) - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return self._unit_of_measurement - @property def icon(self): """Return the sensor icon.""" - return self._icon - - @property - def state(self): - """Return the state of the sensor.""" - return self._state + return SENSOR_TYPES[self.sensor_key][3] class SolarEdgeOverviewSensor(SolarEdgeSensor): @@ -146,31 +152,24 @@ def __init__(self, platform_name, sensor_key, data_service): self._json_key = SENSOR_TYPES[self.sensor_key][0] - def update(self): - """Get the latest data from the sensor and update the state.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) class SolarEdgeDetailsSensor(SolarEdgeSensor): """Representation of an SolarEdge Monitoring API details sensor.""" - def __init__(self, platform_name, sensor_key, data_service): - """Initialize the details sensor.""" - super().__init__(platform_name, sensor_key, data_service) - - self._attributes = {} - @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes - def update(self): - """Get the latest details and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data - self._attributes = self.data_service.attributes + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data class SolarEdgeInventorySensor(SolarEdgeSensor): @@ -182,18 +181,15 @@ def __init__(self, platform_name, sensor_key, data_service): self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._attributes = {} - @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes.get(self._json_key) - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) - self._attributes = self.data_service.attributes.get(self._json_key) + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) class SolarEdgeEnergyDetailsSensor(SolarEdgeSensor): @@ -205,19 +201,20 @@ def __init__(self, platform_name, sensor_key, data_service): self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._attributes = {} - @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes.get(self._json_key) - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) - self._attributes = self.data_service.attributes.get(self._json_key) - self._unit_of_measurement = self.data_service.unit + @property + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.data_service.unit class SolarEdgePowerFlowSensor(SolarEdgeSensor): @@ -229,24 +226,25 @@ def __init__(self, platform_name, sensor_key, data_service): self._json_key = SENSOR_TYPES[self.sensor_key][0] - self._attributes = {} + @property + def device_class(self): + """Device Class.""" + return DEVICE_CLASS_POWER @property def device_state_attributes(self): """Return the state attributes.""" - return self._attributes + return self.data_service.attributes.get(self._json_key) @property - def device_class(self): - """Device Class.""" - return DEVICE_CLASS_POWER + def state(self): + """Return the state of the sensor.""" + return self.data_service.data.get(self._json_key) - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() - self._state = self.data_service.data.get(self._json_key) - self._attributes = self.data_service.attributes.get(self._json_key) - self._unit_of_measurement = self.data_service.unit + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.data_service.unit class SolarEdgeStorageLevelSensor(SolarEdgeSensor): @@ -263,18 +261,19 @@ def device_class(self): """Return the device_class of the device.""" return DEVICE_CLASS_BATTERY - def update(self): - """Get the latest inventory data and update state and attributes.""" - self.data_service.update() + @property + def state(self): + """Return the state of the sensor.""" attr = self.data_service.attributes.get(self._json_key) if attr and "soc" in attr: - self._state = attr["soc"] + return attr["soc"] + return None class SolarEdgeDataService: """Get and update the latest data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the data object.""" self.api = api self.site_id = site_id @@ -282,22 +281,49 @@ def __init__(self, api, site_id): self.data = {} self.attributes = {} + self.hass = hass + self.coordinator = None + + @callback + def async_setup(self): + """Coordinator creation.""" + self.coordinator = DataUpdateCoordinator( + self.hass, + _LOGGER, + name=str(self), + update_method=self.async_update_data, + update_interval=self.update_interval, + ) + + @property + @abstractmethod + def update_interval(self): + """Update interval.""" + + @abstractmethod + def update(self): + """Update data in executor.""" + + async def async_update_data(self): + """Update data.""" + await self.hass.async_add_executor_job(self.update) + class SolarEdgeOverviewDataService(SolarEdgeDataService): """Get and update the latest overview data.""" - @Throttle(OVERVIEW_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return OVERVIEW_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_overview(self.site_id) overview = data["overview"] - except KeyError: - _LOGGER.error("Missing overview data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing overview data, skipping update") from ex self.data = {} @@ -316,25 +342,25 @@ def update(self): class SolarEdgeDetailsDataService(SolarEdgeDataService): """Get and update the latest details data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the details data service.""" - super().__init__(api, site_id) + super().__init__(hass, api, site_id) self.data = None - @Throttle(DETAILS_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return DETAILS_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_details(self.site_id) details = data["details"] - except KeyError: - _LOGGER.error("Missing details data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing details data, skipping update") from ex self.data = None self.attributes = {} @@ -362,18 +388,18 @@ def update(self): class SolarEdgeInventoryDataService(SolarEdgeDataService): """Get and update the latest inventory data.""" - @Throttle(INVENTORY_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return INVENTORY_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_inventory(self.site_id) inventory = data["Inventory"] - except KeyError: - _LOGGER.error("Missing inventory data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing inventory data, skipping update") from ex self.data = {} self.attributes = {} @@ -388,13 +414,17 @@ def update(self): class SolarEdgeEnergyDetailsService(SolarEdgeDataService): """Get and update the latest power flow data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the power flow data service.""" - super().__init__(api, site_id) + super().__init__(hass, api, site_id) self.unit = None - @Throttle(ENERGY_DETAILS_DELAY) + @property + def update_interval(self): + """Update interval.""" + return ENERGY_DETAILS_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: @@ -409,12 +439,8 @@ def update(self): time_unit="DAY", ) energy_details = data["energyDetails"] - except KeyError: - _LOGGER.error("Missing power flow data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing power flow data, skipping update") from ex if "meters" not in energy_details: _LOGGER.debug( @@ -449,24 +475,24 @@ def update(self): class SolarEdgePowerFlowDataService(SolarEdgeDataService): """Get and update the latest power flow data.""" - def __init__(self, api, site_id): + def __init__(self, hass, api, site_id): """Initialize the power flow data service.""" - super().__init__(api, site_id) + super().__init__(hass, api, site_id) self.unit = None - @Throttle(POWER_FLOW_UPDATE_DELAY) + @property + def update_interval(self): + """Update interval.""" + return POWER_FLOW_UPDATE_DELAY + def update(self): """Update the data from the SolarEdge Monitoring API.""" try: data = self.api.get_current_power_flow(self.site_id) power_flow = data["siteCurrentPowerFlow"] - except KeyError: - _LOGGER.error("Missing power flow data, skipping update") - return - except (ConnectTimeout, HTTPError): - _LOGGER.error("Could not retrieve data, skipping update") - return + except KeyError as ex: + raise UpdateFailed("Missing power flow data, skipping update") from ex power_from = [] power_to = [] From b80571519b39427f23a399ed7a1e6d45ba309902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=98stergaard=20Nielsen?= Date: Sat, 30 Jan 2021 12:46:50 +0100 Subject: [PATCH 0024/1818] IHC service functions support for multiple IHC controllers (#44626) Co-authored-by: Martin Hjelmare --- homeassistant/components/ihc/__init__.py | 31 ++++++++++++++++--- homeassistant/components/ihc/const.py | 1 + .../components/ihc/ihc_auto_setup.yaml | 24 ++++++++++++++ homeassistant/components/ihc/services.yaml | 27 ++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index f200c9651f0ea9..8769f73e365d95 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -23,6 +23,7 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import ( + ATTR_CONTROLLER_ID, ATTR_IHC_ID, ATTR_VALUE, CONF_AUTOSETUP, @@ -186,13 +187,18 @@ def validate_name(config): ) SET_RUNTIME_VALUE_BOOL_SCHEMA = vol.Schema( - {vol.Required(ATTR_IHC_ID): cv.positive_int, vol.Required(ATTR_VALUE): cv.boolean} + { + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Required(ATTR_VALUE): cv.boolean, + vol.Optional(ATTR_CONTROLLER_ID, default=0): cv.positive_int, + } ) SET_RUNTIME_VALUE_INT_SCHEMA = vol.Schema( { vol.Required(ATTR_IHC_ID): cv.positive_int, vol.Required(ATTR_VALUE): vol.Coerce(int), + vol.Optional(ATTR_CONTROLLER_ID, default=0): cv.positive_int, } ) @@ -200,10 +206,16 @@ def validate_name(config): { vol.Required(ATTR_IHC_ID): cv.positive_int, vol.Required(ATTR_VALUE): vol.Coerce(float), + vol.Optional(ATTR_CONTROLLER_ID, default=0): cv.positive_int, } ) -PULSE_SCHEMA = vol.Schema({vol.Required(ATTR_IHC_ID): cv.positive_int}) +PULSE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_IHC_ID): cv.positive_int, + vol.Optional(ATTR_CONTROLLER_ID, default=0): cv.positive_int, + } +) def setup(hass, config): @@ -237,7 +249,9 @@ def ihc_setup(hass, config, 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]} - setup_service_functions(hass, ihc_controller) + # We only want to register the service functions once for the first controller + if controller_id == 0: + setup_service_functions(hass) return True @@ -329,30 +343,39 @@ def get_discovery_info(component_setup, groups, controller_id): return discovery_data -def setup_service_functions(hass: HomeAssistantType, ihc_controller): +def setup_service_functions(hass: HomeAssistantType): """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] + def set_runtime_value_bool(call): """Set a IHC runtime bool value service function.""" ihc_id = call.data[ATTR_IHC_ID] value = call.data[ATTR_VALUE] + ihc_controller = _get_controller(call) ihc_controller.set_runtime_value_bool(ihc_id, value) def set_runtime_value_int(call): """Set a IHC runtime integer value service function.""" ihc_id = call.data[ATTR_IHC_ID] value = call.data[ATTR_VALUE] + ihc_controller = _get_controller(call) ihc_controller.set_runtime_value_int(ihc_id, value) def set_runtime_value_float(call): """Set a IHC runtime float value service function.""" ihc_id = call.data[ATTR_IHC_ID] value = call.data[ATTR_VALUE] + ihc_controller = _get_controller(call) ihc_controller.set_runtime_value_float(ihc_id, value) async def async_pulse_runtime_input(call): """Pulse a IHC controller input function.""" ihc_id = call.data[ATTR_IHC_ID] + ihc_controller = _get_controller(call) await async_pulse(hass, ihc_controller, ihc_id) hass.services.register( diff --git a/homeassistant/components/ihc/const.py b/homeassistant/components/ihc/const.py index 15db19ba58b981..30103e2bdba529 100644 --- a/homeassistant/components/ihc/const.py +++ b/homeassistant/components/ihc/const.py @@ -18,6 +18,7 @@ ATTR_IHC_ID = "ihc_id" ATTR_VALUE = "value" +ATTR_CONTROLLER_ID = "controller_id" SERVICE_SET_RUNTIME_VALUE_BOOL = "set_runtime_value_bool" SERVICE_SET_RUNTIME_VALUE_FLOAT = "set_runtime_value_float" diff --git a/homeassistant/components/ihc/ihc_auto_setup.yaml b/homeassistant/components/ihc/ihc_auto_setup.yaml index d5f8d26e2b79bb..7a94afdae44d3c 100644 --- a/homeassistant/components/ihc/ihc_auto_setup.yaml +++ b/homeassistant/components/ihc/ihc_auto_setup.yaml @@ -34,6 +34,30 @@ binary_sensor: type: "light" light: + # Swedish Wireless dimmer (Mobil VU/Dimmer 1-knapp/touch) + - xpath: './/product_airlink[@product_identifier="_0x4301"]' + node: "airlink_dimming" + dimmable: true + # Swedish Wireless dimmer (Lamputtag/Dimmer 1-knapp/touch) + - xpath: './/product_airlink[@product_identifier="_0x4302"]' + node: "airlink_dimming" + dimmable: true + # Swedish Wireless dimmer (Blind/Dimmer 1-knapp/touch) + - xpath: './/product_airlink[@product_identifier="_0x4305"]' + node: "airlink_dimming" + dimmable: true + # Swedish Wireless dimmer (3-tråds Puck/Dimmer 1-knapp/touch) + - xpath: './/product_airlink[@product_identifier="_0x4307"]' + node: "airlink_dimming" + dimmable: true + # Swedish Wireless dimmer (3-tråds Puck/Dimmer 2-knapp) + - xpath: './/product_airlink[@product_identifier="_0x4308"]' + node: "airlink_dimming" + dimmable: true + # 2 channel RS485 dimmer + - xpath: './/rs485_led_dimmer_channel[@product_identifier="_0x4410"]' + node: "airlink_dimming" + dimmable: true # Wireless Combi dimmer 4 buttons - xpath: './/product_airlink[@product_identifier="_0x4406"]' node: "airlink_dimming" diff --git a/homeassistant/components/ihc/services.yaml b/homeassistant/components/ihc/services.yaml index ad41539162cc10..a65d5f5b78c354 100644 --- a/homeassistant/components/ihc/services.yaml +++ b/homeassistant/components/ihc/services.yaml @@ -3,29 +3,56 @@ set_runtime_value_bool: description: Set a boolean runtime value on the IHC controller. fields: + controller_id: + description: | + If you have multiple controller, this is the index of you controller + starting with 0 (0 is default) + example: 0 ihc_id: description: The integer IHC resource ID. + example: 123456 value: description: The boolean value to set. + example: true set_runtime_value_int: description: Set an integer runtime value on the IHC controller. fields: + controller_id: + description: | + If you have multiple controller, this is the index of you controller + starting with 0 (0 is default) + example: 0 ihc_id: description: The integer IHC resource ID. + example: 123456 value: description: The integer value to set. + example: 50 set_runtime_value_float: description: Set a float runtime value on the IHC controller. fields: + controller_id: + description: | + If you have multiple controller, this is the index of you controller + starting with 0 (0 is default) + example: 0 ihc_id: description: The integer IHC resource ID. + example: 123456 value: description: The float value to set. + example: 1.47 pulse: description: Pulses an input on the IHC controller. fields: + controller_id: + description: | + If you have multiple controller, this is the index of you controller + starting with 0 (0 is default) + example: 0 ihc_id: description: The integer IHC resource ID. + example: 123456 From 0964393002732dead6cc85a14aa56ecd18fd78ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 30 Jan 2021 14:09:16 +0100 Subject: [PATCH 0025/1818] Bump awesomeversion from 21.1.3 to 21.1.6 (#45738) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 5815ddd8ed0915..8bd10f3ed6194e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,7 +13,7 @@ pre-commit==2.9.3 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 -awesomeversion==21.1.3 +awesomeversion==21.1.6 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.10.1 From 1fd3a86239d33952cc083fd91f27ccc9a801c7c0 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 30 Jan 2021 15:38:43 +0100 Subject: [PATCH 0026/1818] Upgrade pysonos to 0.0.40 (#45743) --- homeassistant/components/sonos/manifest.json | 2 +- .../components/sonos/media_player.py | 27 ++++++++++--------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 1852f9c3849234..e208a0e7a32d1a 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": ["pysonos==0.0.37"], + "requirements": ["pysonos==0.0.40"], "after_dependencies": ["plex"], "ssdp": [ { diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 9d89bdf68f84d0..2c69730211b2b8 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -10,11 +10,11 @@ import pysonos from pysonos import alarms from pysonos.core import ( + MUSIC_SRC_LINE_IN, + MUSIC_SRC_RADIO, + MUSIC_SRC_TV, PLAY_MODE_BY_MEANING, PLAY_MODES, - PLAYING_LINE_IN, - PLAYING_RADIO, - PLAYING_TV, ) from pysonos.exceptions import SoCoException, SoCoUPnPException import pysonos.music_library @@ -759,11 +759,12 @@ def update_media(self, event=None): self._status = new_status track_uri = variables["current_track_uri"] if variables else None - whats_playing = self.soco.whats_playing(track_uri) - if whats_playing == PLAYING_TV: + music_source = self.soco.music_source_from_uri(track_uri) + + if music_source == MUSIC_SRC_TV: self.update_media_linein(SOURCE_TV) - elif whats_playing == PLAYING_LINE_IN: + elif music_source == MUSIC_SRC_LINE_IN: self.update_media_linein(SOURCE_LINEIN) else: track_info = self.soco.get_current_track_info() @@ -775,7 +776,7 @@ def update_media(self, event=None): self._media_album_name = track_info.get("album") self._media_title = track_info.get("title") - if whats_playing == PLAYING_RADIO: + if music_source == MUSIC_SRC_RADIO: self.update_media_radio(variables, track_info) else: self.update_media_music(update_position, track_info) @@ -816,7 +817,7 @@ def update_media_radio(self, variables, track_info): uri_meta_data, pysonos.data_structures.DidlAudioBroadcast ) and ( self.state != STATE_PLAYING - or self.soco.is_radio_uri(self._media_title) + or self.soco.music_source_from_uri(self._media_title) == MUSIC_SRC_RADIO or self._media_title in self._uri ): self._media_title = uri_meta_data.title @@ -1117,7 +1118,7 @@ def select_source(self, source): if len(fav) == 1: src = fav.pop() uri = src.reference.get_uri() - if self.soco.is_radio_uri(uri): + if self.soco.music_source_from_uri(uri) == MUSIC_SRC_RADIO: self.soco.play_uri(uri, title=source) else: self.soco.clear_queue() @@ -1201,8 +1202,8 @@ def play_media(self, media_type, media_id, **kwargs): elif media_type in (MEDIA_TYPE_MUSIC, MEDIA_TYPE_TRACK): if kwargs.get(ATTR_MEDIA_ENQUEUE): try: - if self.soco.is_spotify_uri(media_id): - self.soco.add_spotify_uri_to_queue(media_id) + if self.soco.is_service_uri(media_id): + self.soco.add_service_uri_to_queue(media_id) else: self.soco.add_uri_to_queue(media_id) except SoCoUPnPException: @@ -1213,9 +1214,9 @@ def play_media(self, media_type, media_id, **kwargs): media_id, ) else: - if self.soco.is_spotify_uri(media_id): + if self.soco.is_service_uri(media_id): self.soco.clear_queue() - self.soco.add_spotify_uri_to_queue(media_id) + self.soco.add_service_uri_to_queue(media_id) self.soco.play_from_queue(0) else: self.soco.play_uri(media_id) diff --git a/requirements_all.txt b/requirements_all.txt index af3f8235a9bd4f..5e0792afcec35e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1708,7 +1708,7 @@ pysnmp==4.4.12 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.37 +pysonos==0.0.40 # homeassistant.components.spc pyspcwebgw==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 90505f1297c05c..1aa5494a7ca891 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -884,7 +884,7 @@ pysmartthings==0.7.6 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.37 +pysonos==0.0.40 # homeassistant.components.spc pyspcwebgw==0.4.0 From 88c4031e5779cb7e936008712959351a45131b7a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jan 2021 09:45:46 -0600 Subject: [PATCH 0027/1818] Fix exception when a unifi config entry is ignored (#45735) * Fix exception when a unifi config entry is ignored * Fix existing test --- homeassistant/components/unifi/config_flow.py | 2 +- tests/components/unifi/test_config_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index f5e947c5e6fb6b..85fe55a4076b27 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -244,7 +244,7 @@ async def async_step_ssdp(self, discovery_info): def _host_already_configured(self, host): """See if we already have a unifi entry matching the host.""" for entry in self._async_current_entries(): - if not entry.data: + if not entry.data or CONF_CONTROLLER not in entry.data: continue if entry.data[CONF_CONTROLLER][CONF_HOST] == host: return True diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 54c5fe291ea36b..15220e68914faa 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -595,7 +595,7 @@ async def test_form_ssdp_gets_form_with_ignored_entry(hass): await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=UNIFI_DOMAIN, - data={}, + data={"not_controller_key": None}, source=config_entries.SOURCE_IGNORE, ) entry.add_to_hass(hass) From b00086ca1f2d80add5f1cae3c08dfbe58fb964f0 Mon Sep 17 00:00:00 2001 From: Guliver Date: Sat, 30 Jan 2021 17:21:04 +0100 Subject: [PATCH 0028/1818] Use fixed due date only for comparison in todoist (#43300) --- homeassistant/components/todoist/calendar.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 978e58c2500192..1188831c26d786 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -246,7 +246,7 @@ def __init__( data, labels, token, - latest_task_due_date=None, + due_date_days=None, whitelisted_labels=None, whitelisted_projects=None, ): @@ -255,7 +255,7 @@ def __init__( data, labels, token, - latest_task_due_date, + due_date_days, whitelisted_labels, whitelisted_projects, ) @@ -338,7 +338,7 @@ def __init__( project_data, labels, api, - latest_task_due_date=None, + due_date_days=None, whitelisted_labels=None, whitelisted_projects=None, ): @@ -356,12 +356,12 @@ def __init__( self.all_project_tasks = [] - # The latest date a task can be due (for making lists of everything + # The days a task can be due (for making lists of everything # due today, or everything due in the next week, for example). - if latest_task_due_date is not None: - self._latest_due_date = dt.utcnow() + timedelta(days=latest_task_due_date) + if due_date_days is not None: + self._due_date_days = timedelta(days=due_date_days) else: - self._latest_due_date = None + self._due_date_days = None # Only tasks with one of these labels will be included. if whitelisted_labels is not None: @@ -409,8 +409,8 @@ def create_todoist_task(self, data): if data[DUE] is not None: task[END] = _parse_due_date(data[DUE]) - if self._latest_due_date is not None and ( - task[END] > self._latest_due_date + if self._due_date_days is not None and ( + task[END] > dt.utcnow() + self._due_date_days ): # This task is out of range of our due date; # it shouldn't be counted. @@ -430,7 +430,7 @@ def create_todoist_task(self, data): else: # If we ask for everything due before a certain date, don't count # things which have no due dates. - if self._latest_due_date is not None: + if self._due_date_days is not None: return None # Define values for tasks without due dates From 63fb8307fb1189670fad98d0068550e8cc3b4428 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 30 Jan 2021 21:10:42 +0100 Subject: [PATCH 0029/1818] Add initial GitHub Issue Form (#45752) --- .github/ISSUE_TEMPLATE/z_bug_report.yml | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/z_bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/z_bug_report.yml b/.github/ISSUE_TEMPLATE/z_bug_report.yml new file mode 100644 index 00000000000000..85a85b1793a4f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/z_bug_report.yml @@ -0,0 +1,102 @@ +name: Report an issue with Home Assistant Core (Test) +about: Report an issue with Home Assistant Core. +title: "" +issue_body: true +inputs: + - type: description + attributes: + value: | + This issue form is for reporting bugs only! + + If you have a feature or enhancement request, please use the [feature request][fr] section of our [Community Forum][fr]. + [fr]: https://community.home-assistant.io/c/feature-requests + - type: textarea + attributes: + label: The problem + required: true + description: >- + Describe the issue you are experiencing here to communicate to the + maintainers. Tell us what you were trying to do and what happened. + + Provide a clear and concise description of what the problem is. + - type: description + attributes: + value: | + ## Environment + - type: input + attributes: + label: What is version of Home Assistant Core has the issue? + required: true + placeholder: core- + description: > + Can be found in the Configuration panel -> Info. + - type: input + attributes: + label: What was the last working version of Home Assistant Core? + required: false + placeholder: core- + description: > + If known, otherwise leave blank. + - type: dropdown + attributes: + label: What type of installation are you running? + required: true + description: > + If you don't know, you can find it in: Configuration panel -> Info. + choices: + - Home Assistant OS + - Home Assistant Container + - Home Assistant Supervised + - Home Assistant Core + - type: input + attributes: + label: Integration causing the issue + required: false + description: > + The name of the integration, for example, Automation or Philips Hue. + - type: input + attributes: + label: Link to integration documentation on our website + required: false + placeholder: "https://www.home-assistant.io/integrations/..." + description: > + Providing a link [to the documentation][docs] help us categorizing the + issue, while providing a useful reference at the same time. + + [docs]: https://www.home-assistant.io/integrations + + - type: description + attributes: + value: | + # Details + - type: textarea + attributes: + label: Example YAML snippet + required: false + description: | + It this issue has an example piece of YAML that can help reproducing + this problem, please provide. + + This can be an piece of YAML from, e.g., an automation, script, scene + or configuration. + value: | + ```yaml + # Put your YAML below this line + + ``` + - type: textarea + attributes: + label: Anything in the logs that might be useful for us? + description: For example, error message, or stack traces. + required: false + value: | + ```txt + # Put your logs below this line + + ``` + - type: description + attributes: + value: | + If you have any additional information for us, use the field below. + Please note, you can attach screenshots or screen recordings here, + by dragging and dropping files in the field below. From 8a6469cfce598e2d4894a0a91ac54f70692c3a29 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 30 Jan 2021 21:17:36 +0100 Subject: [PATCH 0030/1818] newline --- .github/ISSUE_TEMPLATE/z_bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/z_bug_report.yml b/.github/ISSUE_TEMPLATE/z_bug_report.yml index 85a85b1793a4f2..99d70288ff21c0 100644 --- a/.github/ISSUE_TEMPLATE/z_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/z_bug_report.yml @@ -9,6 +9,7 @@ inputs: This issue form is for reporting bugs only! If you have a feature or enhancement request, please use the [feature request][fr] section of our [Community Forum][fr]. + [fr]: https://community.home-assistant.io/c/feature-requests - type: textarea attributes: From 726bc6210bf9bfb3c546917ef498313e896f3c80 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 30 Jan 2021 21:21:57 +0100 Subject: [PATCH 0031/1818] Tiny tweaks to issue form --- .github/ISSUE_TEMPLATE/z_bug_report.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/z_bug_report.yml b/.github/ISSUE_TEMPLATE/z_bug_report.yml index 99d70288ff21c0..80827cf35e6d93 100644 --- a/.github/ISSUE_TEMPLATE/z_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/z_bug_report.yml @@ -60,7 +60,7 @@ inputs: label: Link to integration documentation on our website required: false placeholder: "https://www.home-assistant.io/integrations/..." - description: > + description: | Providing a link [to the documentation][docs] help us categorizing the issue, while providing a useful reference at the same time. @@ -95,6 +95,11 @@ inputs: # Put your logs below this line ``` + + - type: description + attributes: + value: | + ## Additional information - type: description attributes: value: | From 27407c116077d30aa62168262a17bd771419b248 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 30 Jan 2021 21:24:56 +0100 Subject: [PATCH 0032/1818] Tiny tweaks to issue form --- .github/ISSUE_TEMPLATE/z_bug_report.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/z_bug_report.yml b/.github/ISSUE_TEMPLATE/z_bug_report.yml index 80827cf35e6d93..c672da118199e9 100644 --- a/.github/ISSUE_TEMPLATE/z_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/z_bug_report.yml @@ -75,11 +75,8 @@ inputs: label: Example YAML snippet required: false description: | - It this issue has an example piece of YAML that can help reproducing - this problem, please provide. - - This can be an piece of YAML from, e.g., an automation, script, scene - or configuration. + It this issue has an example piece of YAML that can help reproducing this problem, please provide. + This can be an piece of YAML from, e.g., an automation, script, scene or configuration. value: | ```yaml # Put your YAML below this line @@ -95,14 +92,13 @@ inputs: # Put your logs below this line ``` - - type: description attributes: value: | ## Additional information - type: description attributes: - value: | + value: > If you have any additional information for us, use the field below. - Please note, you can attach screenshots or screen recordings here, - by dragging and dropping files in the field below. + Please note, you can attach screenshots or screen recordings here, by + dragging and dropping files in the field below. From da29855967ac8120f7722359e60102d19db69ed6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 30 Jan 2021 21:32:46 +0100 Subject: [PATCH 0033/1818] Enable issue form as default --- .github/ISSUE_TEMPLATE/BUG_REPORT.md | 53 ------------------- .../{z_bug_report.yml => bug_report.yml} | 2 +- 2 files changed, 1 insertion(+), 54 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/BUG_REPORT.md rename .github/ISSUE_TEMPLATE/{z_bug_report.yml => bug_report.yml} (98%) diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md deleted file mode 100644 index bdadc5678ff72e..00000000000000 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -name: Report a bug with Home Assistant Core -about: Report an issue with Home Assistant Core ---- - -## The problem - - - -## Environment - - -- Home Assistant Core release with the issue: -- Last working Home Assistant Core release (if known): -- Operating environment (OS/Container/Supervised/Core): -- Integration causing this issue: -- Link to integration documentation on our website: - -## Problem-relevant `configuration.yaml` - - -```yaml - -``` - -## Traceback/Error logs - - -```txt - -``` - -## Additional information - diff --git a/.github/ISSUE_TEMPLATE/z_bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml similarity index 98% rename from .github/ISSUE_TEMPLATE/z_bug_report.yml rename to .github/ISSUE_TEMPLATE/bug_report.yml index c672da118199e9..377e1452373a58 100644 --- a/.github/ISSUE_TEMPLATE/z_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,4 +1,4 @@ -name: Report an issue with Home Assistant Core (Test) +name: Report an issue with Home Assistant Core about: Report an issue with Home Assistant Core. title: "" issue_body: true From e43cee163f821b4880658b4eb6cbcafd5066ef9b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 30 Jan 2021 21:36:13 +0100 Subject: [PATCH 0034/1818] Fix typo in issue form --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 377e1452373a58..88e8afed67de95 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -75,7 +75,7 @@ inputs: label: Example YAML snippet required: false description: | - It this issue has an example piece of YAML that can help reproducing this problem, please provide. + If this issue has an example piece of YAML that can help reproducing this problem, please provide. This can be an piece of YAML from, e.g., an automation, script, scene or configuration. value: | ```yaml From d13b58a4e60cfd59a29047dfd6284f0a223680f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 30 Jan 2021 23:33:53 +0200 Subject: [PATCH 0035/1818] Upgrade mypy to 0.800 (#45485) * Upgrade mypy to 0.800 https://mypy-lang.blogspot.com/2021/01/mypy-0800-released.html * Fix issues flagged by mypy 0.800 * Add overloads + small changes * Apply grammar Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: Martin Hjelmare --- homeassistant/core.py | 4 ++-- homeassistant/helpers/event.py | 4 +++- homeassistant/helpers/location.py | 3 +-- homeassistant/util/logging.py | 25 ++++++++++++++++++++----- requirements_test.txt | 2 +- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 58d9d1e67540ad..6d187225685682 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -205,7 +205,7 @@ class CoreState(enum.Enum): def __str__(self) -> str: # pylint: disable=invalid-str-returned """Return the event.""" - return self.value # type: ignore + return self.value class HomeAssistant: @@ -584,7 +584,7 @@ class EventOrigin(enum.Enum): def __str__(self) -> str: # pylint: disable=invalid-str-returned """Return the event.""" - return self.value # type: ignore + return self.value class Event: diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index f06ac8aca3f068..da7f6cd52e8907 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -116,7 +116,9 @@ class TrackTemplateResult: result: Any -def threaded_listener_factory(async_factory: Callable[..., Any]) -> CALLBACK_TYPE: +def threaded_listener_factory( + async_factory: Callable[..., Any] +) -> Callable[..., CALLBACK_TYPE]: """Convert an async event helper to a threaded one.""" @ft.wraps(async_factory) diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index bca2996dfa2da1..19058bc3e7f0ab 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -18,9 +18,8 @@ def has_location(state: State) -> bool: Async friendly. """ - # type ignore: https://github.com/python/mypy/issues/7207 return ( - isinstance(state, State) # type: ignore + isinstance(state, State) and isinstance(state.attributes.get(ATTR_LATITUDE), float) and isinstance(state.attributes.get(ATTR_LONGITUDE), float) ) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index feef339a200e53..9b04c2ab007db0 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -6,7 +6,7 @@ import logging.handlers import queue import traceback -from typing import Any, Callable, Coroutine +from typing import Any, Awaitable, Callable, Coroutine, Union, cast, overload from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.core import HomeAssistant, callback @@ -106,9 +106,23 @@ def log_exception(format_err: Callable[..., Any], *args: Any) -> None: logging.getLogger(module_name).error("%s\n%s", friendly_msg, exc_msg) +@overload +def catch_log_exception( # type: ignore + func: Callable[..., Awaitable[Any]], format_err: Callable[..., Any], *args: Any +) -> Callable[..., Awaitable[None]]: + """Overload for Callables that return an Awaitable.""" + + +@overload def catch_log_exception( func: Callable[..., Any], format_err: Callable[..., Any], *args: Any -) -> Callable[[], None]: +) -> Callable[..., None]: + """Overload for Callables that return Any.""" + + +def catch_log_exception( + func: Callable[..., Any], format_err: Callable[..., Any], *args: Any +) -> Union[Callable[..., None], Callable[..., Awaitable[None]]]: """Decorate a callback to catch and log exceptions.""" # Check for partials to properly determine if coroutine function @@ -116,14 +130,15 @@ def catch_log_exception( while isinstance(check_func, partial): check_func = check_func.func - wrapper_func = None + wrapper_func: Union[Callable[..., None], Callable[..., Awaitable[None]]] if asyncio.iscoroutinefunction(check_func): + async_func = cast(Callable[..., Awaitable[None]], func) - @wraps(func) + @wraps(async_func) async def async_wrapper(*args: Any) -> None: """Catch and log exception.""" try: - await func(*args) + await async_func(*args) except Exception: # pylint: disable=broad-except log_exception(format_err, *args) diff --git a/requirements_test.txt b/requirements_test.txt index 8bd10f3ed6194e..69e66239f83a00 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,7 +8,7 @@ codecov==2.1.10 coverage==5.4 jsonpickle==1.4.1 mock-open==1.4.0 -mypy==0.790 +mypy==0.800 pre-commit==2.9.3 pylint==2.6.0 astroid==2.4.2 From 6b446363445d928bb14a403204df15177154b4ac Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 31 Jan 2021 00:51:33 +0100 Subject: [PATCH 0036/1818] Update frontend to 20210127.6 (#45760) --- 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 eb455a5a6c1f79..9d21be79912c90 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.5"], + "requirements": ["home-assistant-frontend==20210127.6"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0845cebc663460..139f577ff639f0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.5 +home-assistant-frontend==20210127.6 httpx==0.16.1 jinja2>=2.11.2 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 5e0792afcec35e..e8bd1fb8040696 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.5 +home-assistant-frontend==20210127.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1aa5494a7ca891..dbb840b1a1011a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -402,7 +402,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.5 +home-assistant-frontend==20210127.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From ea7aa6af590317f9d352e0f25f51c167cdcd4d04 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jan 2021 23:26:02 -1000 Subject: [PATCH 0037/1818] Update dyson for the new fan entity model (#45762) * Update dyson for the new fan entity model * Fix test * tweak * fix * adj * Update homeassistant/components/dyson/fan.py Co-authored-by: Martin Hjelmare * move percentage is None block * move percentage is None block * no need to list comp Co-authored-by: Martin Hjelmare --- homeassistant/components/dyson/fan.py | 166 +++++++++++++------------- tests/components/dyson/test_fan.py | 51 +++++++- 2 files changed, 129 insertions(+), 88 deletions(-) diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 7a57a75523ea98..7a403902ee8c64 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -1,5 +1,6 @@ """Support for Dyson Pure Cool link fan.""" import logging +import math from typing import Optional from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation @@ -9,15 +10,12 @@ from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State import voluptuous as vol -from homeassistant.components.fan import ( - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SUPPORT_OSCILLATE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) from . import DYSON_DEVICES, DysonEntity @@ -70,40 +68,30 @@ } -SPEED_LIST_HA = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] - -SPEED_LIST_DYSON = [ - int(FanSpeed.FAN_SPEED_1.value), - int(FanSpeed.FAN_SPEED_2.value), - int(FanSpeed.FAN_SPEED_3.value), - int(FanSpeed.FAN_SPEED_4.value), - int(FanSpeed.FAN_SPEED_5.value), - int(FanSpeed.FAN_SPEED_6.value), - int(FanSpeed.FAN_SPEED_7.value), - int(FanSpeed.FAN_SPEED_8.value), - int(FanSpeed.FAN_SPEED_9.value), - int(FanSpeed.FAN_SPEED_10.value), +PRESET_MODE_AUTO = "auto" +PRESET_MODES = [PRESET_MODE_AUTO] + +ORDERED_DYSON_SPEEDS = [ + FanSpeed.FAN_SPEED_1, + FanSpeed.FAN_SPEED_2, + FanSpeed.FAN_SPEED_3, + FanSpeed.FAN_SPEED_4, + FanSpeed.FAN_SPEED_5, + FanSpeed.FAN_SPEED_6, + FanSpeed.FAN_SPEED_7, + FanSpeed.FAN_SPEED_8, + FanSpeed.FAN_SPEED_9, + FanSpeed.FAN_SPEED_10, ] +DYSON_SPEED_TO_INT_VALUE = {k: int(k.value) for k in ORDERED_DYSON_SPEEDS} +INT_VALUE_TO_DYSON_SPEED = {v: k for k, v in DYSON_SPEED_TO_INT_VALUE.items()} -SPEED_DYSON_TO_HA = { - FanSpeed.FAN_SPEED_1.value: SPEED_LOW, - FanSpeed.FAN_SPEED_2.value: SPEED_LOW, - FanSpeed.FAN_SPEED_3.value: SPEED_LOW, - FanSpeed.FAN_SPEED_4.value: SPEED_LOW, - FanSpeed.FAN_SPEED_AUTO.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_5.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_6.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_7.value: SPEED_MEDIUM, - FanSpeed.FAN_SPEED_8.value: SPEED_HIGH, - FanSpeed.FAN_SPEED_9.value: SPEED_HIGH, - FanSpeed.FAN_SPEED_10.value: SPEED_HIGH, -} +SPEED_LIST_DYSON = list(DYSON_SPEED_TO_INT_VALUE.values()) -SPEED_HA_TO_DYSON = { - SPEED_LOW: FanSpeed.FAN_SPEED_4, - SPEED_MEDIUM: FanSpeed.FAN_SPEED_7, - SPEED_HIGH: FanSpeed.FAN_SPEED_10, -} +SPEED_RANGE = ( + SPEED_LIST_DYSON[0], + SPEED_LIST_DYSON[-1], +) # off is not included async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -160,14 +148,23 @@ class DysonFanEntity(DysonEntity, FanEntity): """Representation of a Dyson fan.""" @property - def speed(self): - """Return the current speed.""" - return SPEED_DYSON_TO_HA[self._device.state.speed] + def percentage(self): + """Return the current speed percentage.""" + if self.auto_mode: + return None + return ranged_value_to_percentage(SPEED_RANGE, int(self._device.state.speed)) + + @property + def preset_modes(self): + """Return the available preset modes.""" + return PRESET_MODES @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return SPEED_LIST_HA + def preset_mode(self): + """Return the current preset mode.""" + if self.auto_mode: + return PRESET_MODE_AUTO + return None @property def dyson_speed(self): @@ -206,12 +203,25 @@ def device_state_attributes(self) -> dict: ATTR_DYSON_SPEED_LIST: self.dyson_speed_list, } - def set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - if speed not in SPEED_LIST_HA: - raise ValueError(f'"{speed}" is not a valid speed') - _LOGGER.debug("Set fan speed to: %s", speed) - self.set_dyson_speed(SPEED_HA_TO_DYSON[speed]) + def set_auto_mode(self, auto_mode: bool) -> None: + """Set auto mode.""" + raise NotImplementedError + + def set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + if percentage == 0: + self.turn_off() + return + dyson_speed = INT_VALUE_TO_DYSON_SPEED[ + math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + ] + self.set_dyson_speed(dyson_speed) + + def set_preset_mode(self, preset_mode: str) -> None: + """Set a preset mode on the fan.""" + self._valid_preset_mode_or_raise(preset_mode) + # There currently is only one + self.set_auto_mode(True) def set_dyson_speed(self, speed: FanSpeed) -> None: """Set the exact speed of the fan.""" @@ -225,21 +235,6 @@ def service_set_dyson_speed(self, dyson_speed: str) -> None: speed = FanSpeed(f"{int(dyson_speed):04d}") self.set_dyson_speed(speed) - -class DysonPureCoolLinkEntity(DysonFanEntity): - """Representation of a Dyson fan.""" - - def __init__(self, device): - """Initialize the fan.""" - super().__init__(device, DysonPureCoolState) - - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # def turn_on( self, speed: Optional[str] = None, @@ -248,12 +243,22 @@ def turn_on( **kwargs, ) -> None: """Turn on the fan.""" - _LOGGER.debug("Turn on fan %s with speed %s", self.name, speed) - if speed is not None: - self.set_speed(speed) - else: - # Speed not set, just turn on + _LOGGER.debug("Turn on fan %s with percentage %s", self.name, percentage) + if preset_mode: + self.set_preset_mode(preset_mode) + elif percentage is None: + # percentage not set, just turn on self._device.set_configuration(fan_mode=FanMode.FAN) + else: + self.set_percentage(percentage) + + +class DysonPureCoolLinkEntity(DysonFanEntity): + """Representation of a Dyson fan.""" + + def __init__(self, device): + """Initialize the fan.""" + super().__init__(device, DysonPureCoolState) def turn_off(self, **kwargs) -> None: """Turn off the fan.""" @@ -312,13 +317,6 @@ def __init__(self, device): """Initialize the fan.""" super().__init__(device, DysonPureCoolV2State) - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # def turn_on( self, speed: Optional[str] = None, @@ -327,12 +325,14 @@ def turn_on( **kwargs, ) -> None: """Turn on the fan.""" - _LOGGER.debug("Turn on fan %s", self.name) - - if speed is not None: - self.set_speed(speed) - else: + _LOGGER.debug("Turn on fan %s with percentage %s", self.name, percentage) + if preset_mode: + self.set_preset_mode(preset_mode) + elif percentage is None: + # percentage not set, just turn on self._device.turn_on() + else: + self.set_percentage(percentage) def turn_off(self, **kwargs): """Turn off the fan.""" diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index 310d919713323a..dacde12c5697e6 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -19,16 +19,18 @@ ATTR_HEPA_FILTER, ATTR_NIGHT_MODE, ATTR_TIMER, + PRESET_MODE_AUTO, SERVICE_SET_ANGLE, SERVICE_SET_AUTO_MODE, SERVICE_SET_DYSON_SPEED, SERVICE_SET_FLOW_DIRECTION_FRONT, SERVICE_SET_NIGHT_MODE, SERVICE_SET_TIMER, - SPEED_LOW, ) from homeassistant.components.fan import ( ATTR_OSCILLATING, + ATTR_PERCENTAGE, + ATTR_PRESET_MODE, ATTR_SPEED, ATTR_SPEED_LIST, DOMAIN as PLATFORM_DOMAIN, @@ -37,7 +39,9 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, SPEED_HIGH, + SPEED_LOW, SPEED_MEDIUM, + SPEED_OFF, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, ) @@ -84,8 +88,16 @@ async def test_state_purecoollink( attributes = state.attributes assert attributes[ATTR_NIGHT_MODE] is True assert attributes[ATTR_OSCILLATING] is True + assert attributes[ATTR_PERCENTAGE] == 10 + assert attributes[ATTR_PRESET_MODE] is None assert attributes[ATTR_SPEED] == SPEED_LOW - assert attributes[ATTR_SPEED_LIST] == [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + assert attributes[ATTR_SPEED_LIST] == [ + SPEED_OFF, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_HIGH, + PRESET_MODE_AUTO, + ] assert attributes[ATTR_DYSON_SPEED] == 1 assert attributes[ATTR_DYSON_SPEED_LIST] == list(range(1, 11)) assert attributes[ATTR_AUTO_MODE] is False @@ -106,7 +118,9 @@ async def test_state_purecoollink( attributes = state.attributes assert attributes[ATTR_NIGHT_MODE] is False assert attributes[ATTR_OSCILLATING] is False - assert attributes[ATTR_SPEED] == SPEED_MEDIUM + assert attributes[ATTR_PERCENTAGE] is None + assert attributes[ATTR_PRESET_MODE] == "auto" + assert attributes[ATTR_SPEED] == PRESET_MODE_AUTO assert attributes[ATTR_DYSON_SPEED] == "AUTO" assert attributes[ATTR_AUTO_MODE] is True @@ -125,8 +139,16 @@ async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> Non assert attributes[ATTR_OSCILLATING] is True assert attributes[ATTR_ANGLE_LOW] == 24 assert attributes[ATTR_ANGLE_HIGH] == 254 + assert attributes[ATTR_PERCENTAGE] == 10 + assert attributes[ATTR_PRESET_MODE] is None assert attributes[ATTR_SPEED] == SPEED_LOW - assert attributes[ATTR_SPEED_LIST] == [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + assert attributes[ATTR_SPEED_LIST] == [ + SPEED_OFF, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_HIGH, + PRESET_MODE_AUTO, + ] assert attributes[ATTR_DYSON_SPEED] == 1 assert attributes[ATTR_DYSON_SPEED_LIST] == list(range(1, 11)) assert attributes[ATTR_AUTO_MODE] is False @@ -148,7 +170,9 @@ async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> Non attributes = state.attributes assert attributes[ATTR_NIGHT_MODE] is False assert attributes[ATTR_OSCILLATING] is False - assert attributes[ATTR_SPEED] == SPEED_MEDIUM + assert attributes[ATTR_PERCENTAGE] is None + assert attributes[ATTR_PRESET_MODE] == "auto" + assert attributes[ATTR_SPEED] == PRESET_MODE_AUTO assert attributes[ATTR_DYSON_SPEED] == "AUTO" assert attributes[ATTR_AUTO_MODE] is True assert attributes[ATTR_FLOW_DIRECTION_FRONT] is False @@ -170,6 +194,11 @@ async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> Non {ATTR_SPEED: SPEED_LOW}, {"fan_mode": FanMode.FAN, "fan_speed": FanSpeed.FAN_SPEED_4}, ), + ( + SERVICE_TURN_ON, + {ATTR_PERCENTAGE: 40}, + {"fan_mode": FanMode.FAN, "fan_speed": FanSpeed.FAN_SPEED_4}, + ), (SERVICE_TURN_OFF, {}, {"fan_mode": FanMode.OFF}), ( SERVICE_OSCILLATE, @@ -229,6 +258,18 @@ async def test_commands_purecoollink( "set_fan_speed", [FanSpeed.FAN_SPEED_4], ), + ( + SERVICE_TURN_ON, + {ATTR_PERCENTAGE: 40}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_4], + ), + ( + SERVICE_TURN_ON, + {ATTR_PRESET_MODE: "auto"}, + "enable_auto_mode", + [], + ), (SERVICE_TURN_OFF, {}, "turn_off", []), (SERVICE_OSCILLATE, {ATTR_OSCILLATING: True}, "enable_oscillation", []), (SERVICE_OSCILLATE, {ATTR_OSCILLATING: False}, "disable_oscillation", []), From 275946b96da2ce8c7a2d501b14195d72ea025818 Mon Sep 17 00:00:00 2001 From: MtK Date: Sun, 31 Jan 2021 11:30:26 +0100 Subject: [PATCH 0038/1818] Bump ROVA package requirement (#45755) --- homeassistant/components/rova/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rova/manifest.json b/homeassistant/components/rova/manifest.json index a4ba931da43dd7..b3635b39f38c3a 100644 --- a/homeassistant/components/rova/manifest.json +++ b/homeassistant/components/rova/manifest.json @@ -2,6 +2,6 @@ "domain": "rova", "name": "ROVA", "documentation": "https://www.home-assistant.io/integrations/rova", - "requirements": ["rova==0.1.0"], + "requirements": ["rova==0.2.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index e8bd1fb8040696..dd6671f44123dc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1964,7 +1964,7 @@ roombapy==1.6.2 roonapi==0.0.31 # homeassistant.components.rova -rova==0.1.0 +rova==0.2.1 # homeassistant.components.rpi_power rpi-bad-power==0.1.0 From f372bcf306bd4d757a1c069fe18137f862b060b5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 01:13:55 -1000 Subject: [PATCH 0039/1818] Update insteon to use new fan entity model (#45767) --- homeassistant/components/insteon/fan.py | 60 +++++++++---------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index f9d1c381f496ef..3327f9df5ebd8a 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -1,28 +1,24 @@ """Support for INSTEON fans via PowerLinc Modem.""" +import math + from pyinsteon.constants import FanSpeed from homeassistant.components.fan import ( DOMAIN as FAN_DOMAIN, - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_OFF, SUPPORT_SET_SPEED, FanEntity, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] -SPEED_TO_VALUE = { - SPEED_OFF: FanSpeed.OFF, - SPEED_LOW: FanSpeed.LOW, - SPEED_MEDIUM: FanSpeed.MEDIUM, - SPEED_HIGH: FanSpeed.HIGH, -} +SPEED_RANGE = (1, FanSpeed.HIGH) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): @@ -43,33 +39,17 @@ class InsteonFanEntity(InsteonEntity, FanEntity): """An INSTEON fan entity.""" @property - def speed(self) -> str: - """Return the current speed.""" - if self._insteon_device_group.value == FanSpeed.HIGH: - return SPEED_HIGH - if self._insteon_device_group.value == FanSpeed.MEDIUM: - return SPEED_MEDIUM - if self._insteon_device_group.value == FanSpeed.LOW: - return SPEED_LOW - return SPEED_OFF - - @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return FAN_SPEEDS + def percentage(self) -> str: + """Return the current speed percentage.""" + if self._insteon_device_group.value is None: + return None + return ranged_value_to_percentage(SPEED_RANGE, self._insteon_device_group.value) @property def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # async def async_turn_on( self, speed: str = None, @@ -78,18 +58,18 @@ async def async_turn_on( **kwargs, ) -> None: """Turn on the fan.""" - if speed is None: - speed = SPEED_MEDIUM - await self.async_set_speed(speed) + if percentage is None: + percentage = 50 + await self.async_set_percentage(percentage) async def async_turn_off(self, **kwargs) -> None: """Turn off the fan.""" await self._insteon_device.async_fan_off() - async def async_set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - fan_speed = SPEED_TO_VALUE[speed] - if fan_speed == FanSpeed.OFF: + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + if percentage == 0: await self._insteon_device.async_fan_off() else: - await self._insteon_device.async_fan_on(on_level=fan_speed) + on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + await self._insteon_device.async_fan_on(on_level=on_level) From 78934af6e6cbf68a7cb812fdc4fc82c1044915b1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 31 Jan 2021 13:50:48 +0100 Subject: [PATCH 0040/1818] Disable Osramlightify, upstream package is missing (#45775) --- homeassistant/components/osramlightify/manifest.json | 1 + requirements_all.txt | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/osramlightify/manifest.json b/homeassistant/components/osramlightify/manifest.json index dfe71d4b9e1ec2..7e4b810b223670 100644 --- a/homeassistant/components/osramlightify/manifest.json +++ b/homeassistant/components/osramlightify/manifest.json @@ -1,4 +1,5 @@ { + "disabled": "Upstream package has been removed from PyPi", "domain": "osramlightify", "name": "Osramlightify", "documentation": "https://www.home-assistant.io/integrations/osramlightify", diff --git a/requirements_all.txt b/requirements_all.txt index dd6671f44123dc..a8aab590df557d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -879,9 +879,6 @@ life360==4.1.1 # homeassistant.components.lifx_legacy liffylights==0.9.4 -# homeassistant.components.osramlightify -lightify==1.0.7.2 - # homeassistant.components.lightwave lightwave==0.19 From ca43b3a8bb687c3110f05a3a07b957f90dd899c1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 31 Jan 2021 16:35:29 +0100 Subject: [PATCH 0041/1818] Bump pychromecast to 8.0.0 (#45776) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 5f3deb365522ed..88dabc8d04d0e9 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==7.7.2"], + "requirements": ["pychromecast==8.0.0"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index a8aab590df557d..d41b4c8fe0a7aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1307,7 +1307,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==7.7.2 +pychromecast==8.0.0 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dbb840b1a1011a..f2847be80a2ae2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -678,7 +678,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==7.7.2 +pychromecast==8.0.0 # homeassistant.components.comfoconnect pycomfoconnect==0.4 From ee55223065f3082ea1fdcae5e878dc4c55e2f961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Z=C3=A1hradn=C3=ADk?= Date: Sun, 31 Jan 2021 17:59:14 +0100 Subject: [PATCH 0042/1818] SSDP response decode: replace invalid utf-8 characters (#42681) * SSDP response decode: replace invalid utf-8 characters * Add test to validate replaced data Co-authored-by: Joakim Plate --- homeassistant/components/ssdp/__init__.py | 4 +-- tests/components/ssdp/test_init.py | 43 +++++++++++++++++++++++ tests/test_util/aiohttp.py | 4 +-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index e962c141bef146..f07e88d811a547 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -171,13 +171,13 @@ async def _fetch_description(self, xml_location): session = self.hass.helpers.aiohttp_client.async_get_clientsession() try: resp = await session.get(xml_location, timeout=5) - xml = await resp.text() + xml = await resp.text(errors="replace") # Samsung Smart TV sometimes returns an empty document the # first time. Retry once. if not xml: resp = await session.get(xml_location, timeout=5) - xml = await resp.text() + xml = await resp.text(errors="replace") except (aiohttp.ClientError, asyncio.TimeoutError) as err: _LOGGER.debug("Error fetching %s: %s", xml_location, err) return {} diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 008995cd78dd23..bba809aedbb4b4 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -170,3 +170,46 @@ async def test_scan_description_parse_fail(hass, aioclient_mock): return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], ): await scanner.async_scan(None) + + +async def test_invalid_characters(hass, aioclient_mock): + """Test that we replace bad characters with placeholders.""" + aioclient_mock.get( + "http://1.1.1.1", + text=""" + + + ABC + \xff\xff\xff\xff + + + """, + ) + scanner = ssdp.Scanner( + hass, + { + "mock-domain": [ + { + ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC", + } + ] + }, + ) + + with patch( + "netdisco.ssdp.scan", + return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + ), patch.object( + hass.config_entries.flow, "async_init", return_value=mock_coro() + ) as mock_init: + await scanner.async_scan(None) + + 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": "ssdp"} + assert mock_init.mock_calls[0][2]["data"] == { + "ssdp_location": "http://1.1.1.1", + "ssdp_st": "mock-st", + "deviceType": "ABC", + "serialNumber": "ÿÿÿÿ", + } diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 53949c20b068a9..5219212f1cf19e 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -245,9 +245,9 @@ async def read(self): """Return mock response.""" return self.response - async def text(self, encoding="utf-8"): + async def text(self, encoding="utf-8", errors="strict"): """Return mock response as a string.""" - return self.response.decode(encoding) + return self.response.decode(encoding, errors=errors) async def json(self, encoding="utf-8", content_type=None): """Return mock response as a json.""" From 2d10c83150e6cdb8cefb7b10fc688f83f2271283 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 31 Jan 2021 17:51:31 +0000 Subject: [PATCH 0043/1818] Honeywell Lyric Integration (#39695) Co-authored-by: J. Nick Koston --- .coveragerc | 3 + CODEOWNERS | 1 + homeassistant/components/lyric/__init__.py | 200 +++++++++++++ homeassistant/components/lyric/api.py | 55 ++++ homeassistant/components/lyric/climate.py | 280 ++++++++++++++++++ homeassistant/components/lyric/config_flow.py | 23 ++ homeassistant/components/lyric/const.py | 20 ++ homeassistant/components/lyric/manifest.json | 24 ++ homeassistant/components/lyric/services.yaml | 9 + homeassistant/components/lyric/strings.json | 16 + .../components/lyric/translations/en.json | 17 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/dhcp.py | 15 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/lyric/__init__.py | 1 + tests/components/lyric/test_config_flow.py | 128 ++++++++ 17 files changed, 799 insertions(+) create mode 100644 homeassistant/components/lyric/__init__.py create mode 100644 homeassistant/components/lyric/api.py create mode 100644 homeassistant/components/lyric/climate.py create mode 100644 homeassistant/components/lyric/config_flow.py create mode 100644 homeassistant/components/lyric/const.py create mode 100644 homeassistant/components/lyric/manifest.json create mode 100644 homeassistant/components/lyric/services.yaml create mode 100644 homeassistant/components/lyric/strings.json create mode 100644 homeassistant/components/lyric/translations/en.json create mode 100644 tests/components/lyric/__init__.py create mode 100644 tests/components/lyric/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index c692dfbba5e618..65b499c372f50d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -518,6 +518,9 @@ omit = homeassistant/components/lutron_caseta/switch.py homeassistant/components/lw12wifi/light.py homeassistant/components/lyft/sensor.py + homeassistant/components/lyric/__init__.py + homeassistant/components/lyric/api.py + homeassistant/components/lyric/climate.py homeassistant/components/magicseaweed/sensor.py homeassistant/components/mailgun/notify.py homeassistant/components/map/* diff --git a/CODEOWNERS b/CODEOWNERS index e3cf58f90564e3..dc0129c8b8e941 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -260,6 +260,7 @@ homeassistant/components/luftdaten/* @fabaff homeassistant/components/lupusec/* @majuss homeassistant/components/lutron/* @JonGilmore homeassistant/components/lutron_caseta/* @swails @bdraco +homeassistant/components/lyric/* @timmo001 homeassistant/components/mastodon/* @fabaff homeassistant/components/matrix/* @tinloaf homeassistant/components/mcp23017/* @jardiamj diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py new file mode 100644 index 00000000000000..d29fca09166399 --- /dev/null +++ b/homeassistant/components/lyric/__init__.py @@ -0,0 +1,200 @@ +"""The Honeywell Lyric integration.""" +import asyncio +from datetime import timedelta +import logging +from typing import Any, Dict, Optional + +from aiolyric import Lyric +from aiolyric.objects.device import LyricDevice +from aiolyric.objects.location import LyricLocation +import async_timeout +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import ( + aiohttp_client, + config_entry_oauth2_flow, + config_validation as cv, + device_registry as dr, +) +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .api import ConfigEntryLyricClient, LyricLocalOAuth2Implementation +from .config_flow import OAuth2FlowHandler +from .const import DOMAIN, LYRIC_EXCEPTIONS, OAUTH2_AUTHORIZE, OAUTH2_TOKEN + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["climate"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Honeywell Lyric component.""" + hass.data[DOMAIN] = {} + + if DOMAIN not in config: + return True + + hass.data[DOMAIN][CONF_CLIENT_ID] = config[DOMAIN][CONF_CLIENT_ID] + + OAuth2FlowHandler.async_register_implementation( + hass, + LyricLocalOAuth2Implementation( + hass, + DOMAIN, + config[DOMAIN][CONF_CLIENT_ID], + config[DOMAIN][CONF_CLIENT_SECRET], + OAUTH2_AUTHORIZE, + OAUTH2_TOKEN, + ), + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Honeywell Lyric from a config entry.""" + implementation = ( + await config_entry_oauth2_flow.async_get_config_entry_implementation( + hass, entry + ) + ) + + session = aiohttp_client.async_get_clientsession(hass) + oauth_session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) + + client = ConfigEntryLyricClient(session, oauth_session) + + client_id = hass.data[DOMAIN][CONF_CLIENT_ID] + lyric: Lyric = Lyric(client, client_id) + + async def async_update_data() -> Lyric: + """Fetch data from Lyric.""" + try: + async with async_timeout.timeout(60): + await lyric.get_locations() + return lyric + except (*LYRIC_EXCEPTIONS, TimeoutError) as exception: + raise UpdateFailed(exception) from exception + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="lyric_coordinator", + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=120), + ) + + hass.data[DOMAIN][entry.entry_id] = coordinator + + # Fetch initial data so we have data when entities subscribe + await coordinator.async_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +class LyricEntity(CoordinatorEntity): + """Defines a base Honeywell Lyric entity.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + key: str, + name: str, + icon: Optional[str], + ) -> None: + """Initialize the Honeywell Lyric entity.""" + super().__init__(coordinator) + self._key = key + self._name = name + self._icon = icon + self._location = location + self._mac_id = device.macID + self._device_name = device.name + self._device_model = device.deviceModel + self._update_thermostat = coordinator.data.update_thermostat + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return self._key + + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + + @property + def icon(self) -> str: + """Return the mdi icon of the entity.""" + return self._icon + + @property + def location(self) -> LyricLocation: + """Get the Lyric Location.""" + return self.coordinator.data.locations_dict[self._location.locationID] + + @property + def device(self) -> LyricDevice: + """Get the Lyric Device.""" + return self.location.devices_dict[self._mac_id] + + +class LyricDeviceEntity(LyricEntity): + """Defines a Honeywell Lyric device entity.""" + + @property + def device_info(self) -> Dict[str, Any]: + """Return device information about this Honeywell Lyric instance.""" + return { + "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac_id)}, + "manufacturer": "Honeywell", + "model": self._device_model, + "name": self._device_name, + } diff --git a/homeassistant/components/lyric/api.py b/homeassistant/components/lyric/api.py new file mode 100644 index 00000000000000..a77c6365bafb66 --- /dev/null +++ b/homeassistant/components/lyric/api.py @@ -0,0 +1,55 @@ +"""API for Honeywell Lyric bound to Home Assistant OAuth.""" +import logging +from typing import cast + +from aiohttp import BasicAuth, ClientSession +from aiolyric.client import LyricClient + +from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +_LOGGER = logging.getLogger(__name__) + + +class ConfigEntryLyricClient(LyricClient): + """Provide Honeywell Lyric authentication tied to an OAuth2 based config entry.""" + + def __init__( + self, + websession: ClientSession, + oauth_session: config_entry_oauth2_flow.OAuth2Session, + ): + """Initialize Honeywell Lyric auth.""" + super().__init__(websession) + self._oauth_session = oauth_session + + async def async_get_access_token(self): + """Return a valid access token.""" + if not self._oauth_session.valid_token: + await self._oauth_session.async_ensure_token_valid() + + return self._oauth_session.token["access_token"] + + +class LyricLocalOAuth2Implementation( + config_entry_oauth2_flow.LocalOAuth2Implementation +): + """Lyric Local OAuth2 implementation.""" + + async def _token_request(self, data: dict) -> dict: + """Make a token request.""" + session = async_get_clientsession(self.hass) + + data["client_id"] = self.client_id + + if self.client_secret is not None: + data["client_secret"] = self.client_secret + + headers = { + "Authorization": BasicAuth(self.client_id, self.client_secret).encode(), + "Content-Type": "application/x-www-form-urlencoded", + } + + resp = await session.post(self.token_url, headers=headers, data=data) + resp.raise_for_status() + return cast(dict, await resp.json()) diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py new file mode 100644 index 00000000000000..bcfefef9c93ac1 --- /dev/null +++ b/homeassistant/components/lyric/climate.py @@ -0,0 +1,280 @@ +"""Support for Honeywell Lyric climate platform.""" +import logging +from time import gmtime, strftime, time +from typing import List, Optional + +from aiolyric.objects.device import LyricDevice +from aiolyric.objects.location import LyricLocation +import voluptuous as vol + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_platform +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from . import LyricDeviceEntity +from .const import ( + DOMAIN, + LYRIC_EXCEPTIONS, + PRESET_HOLD_UNTIL, + PRESET_NO_HOLD, + PRESET_PERMANENT_HOLD, + PRESET_TEMPORARY_HOLD, + PRESET_VACATION_HOLD, +) + +_LOGGER = logging.getLogger(__name__) + +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + +LYRIC_HVAC_MODE_OFF = "Off" +LYRIC_HVAC_MODE_HEAT = "Heat" +LYRIC_HVAC_MODE_COOL = "Cool" +LYRIC_HVAC_MODE_HEAT_COOL = "Auto" + +LYRIC_HVAC_MODES = { + HVAC_MODE_OFF: LYRIC_HVAC_MODE_OFF, + HVAC_MODE_HEAT: LYRIC_HVAC_MODE_HEAT, + HVAC_MODE_COOL: LYRIC_HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL: LYRIC_HVAC_MODE_HEAT_COOL, +} + +HVAC_MODES = { + LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF, + LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT, + LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL, + LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, +} + +SERVICE_HOLD_TIME = "set_hold_time" +ATTR_TIME_PERIOD = "time_period" + +SCHEMA_HOLD_TIME = { + vol.Required(ATTR_TIME_PERIOD, default="01:00:00"): vol.All( + cv.time_period, + cv.positive_timedelta, + lambda td: strftime("%H:%M:%S", gmtime(time() + td.total_seconds())), + ) +} + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Honeywell Lyric climate platform based on a config entry.""" + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + entities = [] + + for location in coordinator.data.locations: + for device in location.devices: + entities.append(LyricClimate(hass, coordinator, location, device)) + + async_add_entities(entities, True) + + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_HOLD_TIME, + SCHEMA_HOLD_TIME, + "async_set_hold_time", + ) + + +class LyricClimate(LyricDeviceEntity, ClimateEntity): + """Defines a Honeywell Lyric climate entity.""" + + def __init__( + self, + hass: HomeAssistantType, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + ) -> None: + """Initialize Honeywell Lyric climate entity.""" + self._temperature_unit = hass.config.units.temperature_unit + + # Setup supported hvac modes + self._hvac_modes = [HVAC_MODE_OFF] + + # Add supported lyric thermostat features + if LYRIC_HVAC_MODE_HEAT in device.allowedModes: + self._hvac_modes.append(HVAC_MODE_HEAT) + + if LYRIC_HVAC_MODE_COOL in device.allowedModes: + self._hvac_modes.append(HVAC_MODE_COOL) + + if ( + LYRIC_HVAC_MODE_HEAT in device.allowedModes + and LYRIC_HVAC_MODE_COOL in device.allowedModes + ): + self._hvac_modes.append(HVAC_MODE_HEAT_COOL) + + super().__init__( + coordinator, + location, + device, + f"{device.macID}_thermostat", + device.name, + None, + ) + + @property + def supported_features(self) -> int: + """Return the list of supported features.""" + return SUPPORT_FLAGS + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement.""" + return self._temperature_unit + + @property + def current_temperature(self) -> Optional[float]: + """Return the current temperature.""" + return self.device.indoorTemperature + + @property + def hvac_mode(self) -> str: + """Return the hvac mode.""" + return HVAC_MODES[self.device.changeableValues.mode] + + @property + def hvac_modes(self) -> List[str]: + """List of available hvac modes.""" + return self._hvac_modes + + @property + def target_temperature(self) -> Optional[float]: + """Return the temperature we try to reach.""" + device: LyricDevice = self.device + if not device.hasDualSetpointStatus: + return device.changeableValues.heatSetpoint + + @property + def target_temperature_low(self) -> Optional[float]: + """Return the upper bound temperature we try to reach.""" + device: LyricDevice = self.device + if device.hasDualSetpointStatus: + return device.changeableValues.coolSetpoint + + @property + def target_temperature_high(self) -> Optional[float]: + """Return the upper bound temperature we try to reach.""" + device: LyricDevice = self.device + if device.hasDualSetpointStatus: + return device.changeableValues.heatSetpoint + + @property + def preset_mode(self) -> Optional[str]: + """Return current preset mode.""" + return self.device.changeableValues.thermostatSetpointStatus + + @property + def preset_modes(self) -> Optional[List[str]]: + """Return preset modes.""" + return [ + PRESET_NO_HOLD, + PRESET_HOLD_UNTIL, + PRESET_PERMANENT_HOLD, + PRESET_TEMPORARY_HOLD, + PRESET_VACATION_HOLD, + ] + + @property + def min_temp(self) -> float: + """Identify min_temp in Lyric API or defaults if not available.""" + device: LyricDevice = self.device + if LYRIC_HVAC_MODE_COOL in device.allowedModes: + return device.minCoolSetpoint + return device.minHeatSetpoint + + @property + def max_temp(self) -> float: + """Identify max_temp in Lyric API or defaults if not available.""" + device: LyricDevice = self.device + if LYRIC_HVAC_MODE_HEAT in device.allowedModes: + return device.maxHeatSetpoint + return device.maxCoolSetpoint + + async def async_set_temperature(self, **kwargs) -> None: + """Set new target temperature.""" + target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + + device: LyricDevice = self.device + if device.hasDualSetpointStatus: + if target_temp_low is not None and target_temp_high is not None: + temp = (target_temp_low, target_temp_high) + else: + raise HomeAssistantError( + "Could not find target_temp_low and/or target_temp_high in arguments" + ) + else: + temp = kwargs.get(ATTR_TEMPERATURE) + _LOGGER.debug("Set temperature: %s", temp) + try: + await self._update_thermostat(self.location, device, heatSetpoint=temp) + except LYRIC_EXCEPTIONS as exception: + _LOGGER.error(exception) + await self.coordinator.async_refresh() + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Set hvac mode.""" + _LOGGER.debug("Set hvac mode: %s", hvac_mode) + try: + await self._update_thermostat( + self.location, self.device, mode=LYRIC_HVAC_MODES[hvac_mode] + ) + except LYRIC_EXCEPTIONS as exception: + _LOGGER.error(exception) + await self.coordinator.async_refresh() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set preset (PermanentHold, HoldUntil, NoHold, VacationHold) mode.""" + _LOGGER.debug("Set preset mode: %s", preset_mode) + try: + await self._update_thermostat( + self.location, self.device, thermostatSetpointStatus=preset_mode + ) + except LYRIC_EXCEPTIONS as exception: + _LOGGER.error(exception) + await self.coordinator.async_refresh() + + async def async_set_preset_period(self, period: str) -> None: + """Set preset period (time).""" + try: + await self._update_thermostat( + self.location, self.device, nextPeriodTime=period + ) + except LYRIC_EXCEPTIONS as exception: + _LOGGER.error(exception) + await self.coordinator.async_refresh() + + async def async_set_hold_time(self, time_period: str) -> None: + """Set the time to hold until.""" + _LOGGER.debug("set_hold_time: %s", time_period) + try: + await self._update_thermostat( + self.location, + self.device, + thermostatSetpointStatus=PRESET_HOLD_UNTIL, + nextPeriodTime=time_period, + ) + except LYRIC_EXCEPTIONS as exception: + _LOGGER.error(exception) + await self.coordinator.async_refresh() diff --git a/homeassistant/components/lyric/config_flow.py b/homeassistant/components/lyric/config_flow.py new file mode 100644 index 00000000000000..1370d5e67ea9c8 --- /dev/null +++ b/homeassistant/components/lyric/config_flow.py @@ -0,0 +1,23 @@ +"""Config flow for Honeywell Lyric.""" +import logging + +from homeassistant import config_entries +from homeassistant.helpers import config_entry_oauth2_flow + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class OAuth2FlowHandler( + config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN +): + """Config flow to handle Honeywell Lyric OAuth2 authentication.""" + + DOMAIN = DOMAIN + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH + + @property + def logger(self) -> logging.Logger: + """Return logger.""" + return logging.getLogger(__name__) diff --git a/homeassistant/components/lyric/const.py b/homeassistant/components/lyric/const.py new file mode 100644 index 00000000000000..4f2f72b937b3fd --- /dev/null +++ b/homeassistant/components/lyric/const.py @@ -0,0 +1,20 @@ +"""Constants for the Honeywell Lyric integration.""" +from aiohttp.client_exceptions import ClientResponseError +from aiolyric.exceptions import LyricAuthenticationException, LyricException + +DOMAIN = "lyric" + +OAUTH2_AUTHORIZE = "https://api.honeywell.com/oauth2/authorize" +OAUTH2_TOKEN = "https://api.honeywell.com/oauth2/token" + +PRESET_NO_HOLD = "NoHold" +PRESET_TEMPORARY_HOLD = "TemporaryHold" +PRESET_HOLD_UNTIL = "HoldUntil" +PRESET_PERMANENT_HOLD = "PermanentHold" +PRESET_VACATION_HOLD = "VacationHold" + +LYRIC_EXCEPTIONS = ( + LyricAuthenticationException, + LyricException, + ClientResponseError, +) diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json new file mode 100644 index 00000000000000..460eb6e2a3d631 --- /dev/null +++ b/homeassistant/components/lyric/manifest.json @@ -0,0 +1,24 @@ +{ + "domain": "lyric", + "name": "Honeywell Lyric", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/lyric", + "dependencies": ["http"], + "requirements": ["aiolyric==1.0.5"], + "codeowners": ["@timmo001"], + "quality_scale": "silver", + "dhcp": [ + { + "hostname": "lyric-*", + "macaddress": "48A2E6" + }, + { + "hostname": "lyric-*", + "macaddress": "B82CA0" + }, + { + "hostname": "lyric-*", + "macaddress": "00D02D" + } + ] +} diff --git a/homeassistant/components/lyric/services.yaml b/homeassistant/components/lyric/services.yaml new file mode 100644 index 00000000000000..b4ea74a9644f3c --- /dev/null +++ b/homeassistant/components/lyric/services.yaml @@ -0,0 +1,9 @@ +set_hold_time: + description: "Sets the time to hold until" + fields: + entity_id: + description: Name(s) of entities to change + example: "climate.thermostat" + time_period: + description: Time to hold until + example: "01:00:00" diff --git a/homeassistant/components/lyric/strings.json b/homeassistant/components/lyric/strings.json new file mode 100644 index 00000000000000..4e5f2330840ee7 --- /dev/null +++ b/homeassistant/components/lyric/strings.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + } + }, + "abort": { + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", + "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]" + }, + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } + } +} diff --git a/homeassistant/components/lyric/translations/en.json b/homeassistant/components/lyric/translations/en.json new file mode 100644 index 00000000000000..b183398663ef3f --- /dev/null +++ b/homeassistant/components/lyric/translations/en.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Timeout generating authorize URL.", + "missing_configuration": "The component is not configured. Please follow the documentation." + }, + "create_entry": { + "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + } + } + }, + "title": "Honeywell Lyric" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 4c12ff30e49690..282d039128d687 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -121,6 +121,7 @@ "logi_circle", "luftdaten", "lutron_caseta", + "lyric", "mailgun", "melcloud", "met", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 0b6f5166f8815c..61223bf00f781b 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -41,6 +41,21 @@ "hostname": "flume-gw-*", "macaddress": "B4E62D*" }, + { + "domain": "lyric", + "hostname": "lyric-*", + "macaddress": "48A2E6" + }, + { + "domain": "lyric", + "hostname": "lyric-*", + "macaddress": "B82CA0" + }, + { + "domain": "lyric", + "hostname": "lyric-*", + "macaddress": "00D02D" + }, { "domain": "nest", "macaddress": "18B430*" diff --git a/requirements_all.txt b/requirements_all.txt index d41b4c8fe0a7aa..a8127ed7097eb9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -199,6 +199,9 @@ aiolifx_effects==0.2.2 # homeassistant.components.lutron_caseta aiolip==1.0.1 +# homeassistant.components.lyric +aiolyric==1.0.5 + # homeassistant.components.keyboard_remote aionotify==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2847be80a2ae2..3e8972c9864803 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -118,6 +118,9 @@ aiokafka==0.6.0 # homeassistant.components.lutron_caseta aiolip==1.0.1 +# homeassistant.components.lyric +aiolyric==1.0.5 + # homeassistant.components.notion aionotion==1.1.0 diff --git a/tests/components/lyric/__init__.py b/tests/components/lyric/__init__.py new file mode 100644 index 00000000000000..794c6bf1ba095b --- /dev/null +++ b/tests/components/lyric/__init__.py @@ -0,0 +1 @@ +"""Tests for the Honeywell Lyric integration.""" diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py new file mode 100644 index 00000000000000..24b6b68d731cfe --- /dev/null +++ b/tests/components/lyric/test_config_flow.py @@ -0,0 +1,128 @@ +"""Test the Honeywell Lyric config flow.""" +import asyncio +from unittest.mock import patch + +import pytest + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.http import CONF_BASE_URL, DOMAIN as DOMAIN_HTTP +from homeassistant.components.lyric import config_flow +from homeassistant.components.lyric.const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.helpers import config_entry_oauth2_flow + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" + + +@pytest.fixture() +async def mock_impl(hass): + """Mock implementation.""" + await setup.async_setup_component(hass, "http", {}) + + impl = config_entry_oauth2_flow.LocalOAuth2Implementation( + hass, + DOMAIN, + CLIENT_ID, + CLIENT_SECRET, + OAUTH2_AUTHORIZE, + OAUTH2_TOKEN, + ) + config_flow.OAuth2FlowHandler.async_register_implementation(hass, impl) + return impl + + +async def test_abort_if_no_configuration(hass): + """Check flow abort when no configuration.""" + flow = config_flow.OAuth2FlowHandler() + flow.hass = hass + result = await flow.async_step_user() + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "missing_configuration" + + +async def test_full_flow( + hass, aiohttp_client, aioclient_mock, current_request_with_host +): + """Check full flow.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, + DOMAIN_HTTP: {CONF_BASE_URL: "https://example.com"}, + }, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + + client = await aiohttp_client(hass.http.app) + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + }, + ) + + with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"): + with patch( + "homeassistant.components.lyric.async_setup_entry", return_value=True + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["data"]["auth_implementation"] == DOMAIN + + result["data"]["token"].pop("expires_at") + assert result["data"]["token"] == { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + } + + assert DOMAIN in hass.config.components + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.state == config_entries.ENTRY_STATE_LOADED + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup.mock_calls) == 1 + + +async def test_abort_if_authorization_timeout(hass, mock_impl): + """Check Somfy authorization timeout.""" + flow = config_flow.OAuth2FlowHandler() + flow.hass = hass + + with patch.object( + mock_impl, "async_generate_authorize_url", side_effect=asyncio.TimeoutError + ): + result = await flow.async_step_user() + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "authorize_url_timeout" From f1d3af1a13ada7a521871a437d900b4ebf556085 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 31 Jan 2021 18:59:39 +0100 Subject: [PATCH 0044/1818] Add WLED unload entry result correctly (#45783) --- homeassistant/components/wled/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index d8aacd59881ce3..7cc91d32062bbb 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -72,19 +72,22 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload WLED config entry.""" # Unload entities for this entry/device. - await asyncio.gather( - *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in WLED_COMPONENTS + unload_ok = all( + await asyncio.gather( + *( + hass.config_entries.async_forward_entry_unload(entry, component) + for component in WLED_COMPONENTS + ) ) ) - # Cleanup - del hass.data[DOMAIN][entry.entry_id] + if unload_ok: + del hass.data[DOMAIN][entry.entry_id] + if not hass.data[DOMAIN]: del hass.data[DOMAIN] - return True + return unload_ok def wled_exception_handler(func): From c74ddf47202da6d362eccae33572ffe909224a00 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 31 Jan 2021 19:00:49 +0100 Subject: [PATCH 0045/1818] Upgrade pre-commit to 2.10.0 (#45777) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 69e66239f83a00..91380d14c5e3ea 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -9,7 +9,7 @@ coverage==5.4 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.800 -pre-commit==2.9.3 +pre-commit==2.10.0 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 From e506d8616f7c9e77b87c96ebacb1105d3b74ef58 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 31 Jan 2021 20:22:46 +0100 Subject: [PATCH 0046/1818] Fix polling and update of camera state for synology_dsm (#43683) Co-authored-by: J. Nick Koston --- .../components/synology_dsm/__init__.py | 118 ++++++++++++++++-- .../components/synology_dsm/binary_sensor.py | 6 +- .../components/synology_dsm/camera.py | 99 +++++++++++---- .../components/synology_dsm/const.py | 1 + .../components/synology_dsm/sensor.py | 6 +- .../components/synology_dsm/switch.py | 19 ++- 6 files changed, 205 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 06696865d03a1c..b0d78ca67163f3 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -4,6 +4,7 @@ import logging from typing import Dict +import async_timeout from synology_dsm import SynologyDSM from synology_dsm.api.core.security import SynoCoreSecurity from synology_dsm.api.core.system import SynoCoreSystem @@ -14,6 +15,7 @@ from synology_dsm.api.storage.storage import SynoStorage from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.exceptions import ( + SynologyDSMAPIErrorException, SynologyDSMLoginFailedException, SynologyDSMRequestException, ) @@ -44,10 +46,16 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) from .const import ( CONF_SERIAL, CONF_VOLUMES, + COORDINATOR_SURVEILLANCE, DEFAULT_SCAN_INTERVAL, DEFAULT_USE_SSL, DEFAULT_VERIFY_SSL, @@ -185,7 +193,7 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): try: await api.async_setup() except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.debug("async_setup_entry - Unable to connect to DSM: %s", err) + _LOGGER.debug("async_setup_entry() - Unable to connect to DSM: %s", err) raise ConfigEntryNotReady from err undo_listener = entry.add_update_listener(_async_update_listener) @@ -206,6 +214,35 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): entry, data={**entry.data, CONF_MAC: network.macs} ) + # setup DataUpdateCoordinator + async def async_coordinator_update_data_surveillance_station(): + """Fetch all surveillance station data from api.""" + surveillance_station = api.surveillance_station + try: + async with async_timeout.timeout(10): + await hass.async_add_executor_job(surveillance_station.update) + except SynologyDSMAPIErrorException as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err + + if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: + return + + return { + "cameras": { + camera.id: camera for camera in surveillance_station.get_all_cameras() + } + } + + hass.data[DOMAIN][entry.unique_id][ + COORDINATOR_SURVEILLANCE + ] = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{entry.unique_id}_surveillance_station", + update_method=async_coordinator_update_data_surveillance_station, + update_interval=timedelta(seconds=30), + ) + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) @@ -314,6 +351,7 @@ def signal_sensor_update(self) -> str: async def async_setup(self): """Start interacting with the NAS.""" + # init SynologyDSM object and login self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], @@ -326,9 +364,14 @@ async def async_setup(self): ) await self._hass.async_add_executor_job(self.dsm.login) + # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) + _LOGGER.debug( + "SynoAPI.async_setup() - self._with_surveillance_station:%s", + self._with_surveillance_station, + ) self._async_setup_api_requests() @@ -348,6 +391,9 @@ async def async_setup(self): @callback def subscribe(self, api_key, unique_id): """Subscribe an entity from API fetches.""" + _LOGGER.debug( + "SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id + ) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @@ -362,8 +408,16 @@ def unsubscribe() -> None: @callback def _async_setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" + _LOGGER.debug( + "SynoAPI._async_setup_api_requests() - self._fetching_entities:%s", + self._fetching_entities, + ) + # Entities not added yet, fetch all if not self._fetching_entities: + _LOGGER.debug( + "SynoAPI._async_setup_api_requests() - Entities not added yet, fetch all" + ) return # Determine if we should fetch an API @@ -380,33 +434,39 @@ def _async_setup_api_requests(self): self._fetching_entities.get(SynoDSMInformation.API_KEY) ) self._with_surveillance_station = bool( - self._fetching_entities.get(SynoSurveillanceStation.CAMERA_API_KEY) - ) or bool( - self._fetching_entities.get(SynoSurveillanceStation.HOME_MODE_API_KEY) + self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: + _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable security") self.dsm.reset(self.security) self.security = None if not self._with_storage: + _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable storage") self.dsm.reset(self.storage) self.storage = None if not self._with_system: + _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable system") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: + _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable upgrade") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: + _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable utilisation") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: + _LOGGER.debug( + "SynoAPI._async_setup_api_requests() - disable surveillance_station" + ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -417,34 +477,42 @@ def _fetch_device_configuration(self): self.network.update() if self._with_security: + _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch security") self.security = self.dsm.security if self._with_storage: + _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch storage") self.storage = self.dsm.storage if self._with_upgrade: + _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch upgrade") self.upgrade = self.dsm.upgrade if self._with_system: + _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch system") self.system = self.dsm.system if self._with_utilisation: + _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch utilisation") self.utilisation = self.dsm.utilisation if self._with_surveillance_station: + _LOGGER.debug( + "SynoAPI._fetch_device_configuration() - fetch surveillance_station" + ) self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): """Reboot NAS.""" if not self.system: - _LOGGER.debug("async_reboot - System API not ready: %s", self) + _LOGGER.debug("SynoAPI.async_reboot() - System API not ready: %s", self) return await self._hass.async_add_executor_job(self.system.reboot) async def async_shutdown(self): """Shutdown NAS.""" if not self.system: - _LOGGER.debug("async_shutdown - System API not ready: %s", self) + _LOGGER.debug("SynoAPI.async_shutdown() - System API not ready: %s", self) return await self._hass.async_add_executor_job(self.system.shutdown) @@ -454,6 +522,7 @@ async def async_unload(self): async def async_update(self, now=None): """Update function for updating API information.""" + _LOGGER.debug("SynoAPI.async_update()") self._async_setup_api_requests() try: await self._hass.async_add_executor_job( @@ -463,13 +532,13 @@ async def async_update(self, now=None): _LOGGER.warning( "async_update - connection error during update, fallback by reloading the entry" ) - _LOGGER.debug("async_update - exception: %s", err) + _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return async_dispatcher_send(self._hass, self.signal_sensor_update) -class SynologyDSMEntity(Entity): +class SynologyDSMBaseEntity(Entity): """Representation of a Synology NAS entry.""" def __init__( @@ -479,8 +548,6 @@ def __init__( entity_info: Dict[str, str], ): """Initialize the Synology DSM entity.""" - super().__init__() - self._api = api self._api_key = entity_type.split(":")[0] self.entity_type = entity_type.split(":")[-1] @@ -539,6 +606,20 @@ def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" return self._enable_default + +class SynologyDSMDispatcherEntity(SynologyDSMBaseEntity, Entity): + """Representation of a Synology NAS entry.""" + + def __init__( + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + ): + """Initialize the Synology DSM entity.""" + super().__init__(api, entity_type, entity_info) + Entity.__init__(self) + @property def should_poll(self) -> bool: """No polling needed.""" @@ -562,7 +643,22 @@ async def async_added_to_hass(self): self.async_on_remove(self._api.subscribe(self._api_key, self.unique_id)) -class SynologyDSMDeviceEntity(SynologyDSMEntity): +class SynologyDSMCoordinatorEntity(SynologyDSMBaseEntity, CoordinatorEntity): + """Representation of a Synology NAS entry.""" + + def __init__( + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, + ): + """Initialize the Synology DSM entity.""" + super().__init__(api, entity_type, entity_info) + CoordinatorEntity.__init__(self, coordinator) + + +class SynologyDSMDeviceEntity(SynologyDSMDispatcherEntity): """Representation of a Synology NAS disk or volume entry.""" def __init__( diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 69f217a4b4e9c0..2bbfb8f464107b 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -6,7 +6,7 @@ from homeassistant.const import CONF_DISKS from homeassistant.helpers.typing import HomeAssistantType -from . import SynologyDSMDeviceEntity, SynologyDSMEntity +from . import SynologyDSMDeviceEntity, SynologyDSMDispatcherEntity from .const import ( DOMAIN, SECURITY_BINARY_SENSORS, @@ -50,7 +50,7 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMSecurityBinarySensor(SynologyDSMEntity, BinarySensorEntity): +class SynoDSMSecurityBinarySensor(SynologyDSMDispatcherEntity, BinarySensorEntity): """Representation a Synology Security binary sensor.""" @property @@ -78,7 +78,7 @@ def is_on(self) -> bool: return getattr(self._api.storage, self.entity_type)(self._device_id) -class SynoDSMUpgradeBinarySensor(SynologyDSMEntity, BinarySensorEntity): +class SynoDSMUpgradeBinarySensor(SynologyDSMDispatcherEntity, BinarySensorEntity): """Representation a Synology Upgrade binary sensor.""" @property diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index 1dfd8ff945bd98..f24615bd28e9bd 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -1,15 +1,18 @@ """Support for Synology DSM cameras.""" +import logging from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation -from synology_dsm.api.surveillance_station.camera import SynoCamera +from synology_dsm.exceptions import SynologyDSMAPIErrorException from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMEntity +from . import SynoApi, SynologyDSMCoordinatorEntity from .const import ( + COORDINATOR_SURVEILLANCE, DOMAIN, ENTITY_CLASS, ENTITY_ENABLE, @@ -19,50 +22,72 @@ SYNO_API, ) +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities ) -> None: - """Set up the Synology NAS binary sensor.""" + """Set up the Synology NAS cameras.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = data[SYNO_API] if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: return - surveillance_station = api.surveillance_station - await hass.async_add_executor_job(surveillance_station.update) - cameras = surveillance_station.get_all_cameras() - entities = [SynoDSMCamera(api, camera) for camera in cameras] + # initial data fetch + coordinator = data[COORDINATOR_SURVEILLANCE] + await coordinator.async_refresh() - async_add_entities(entities) + async_add_entities( + SynoDSMCamera(api, coordinator, camera_id) + for camera_id in coordinator.data["cameras"] + ) -class SynoDSMCamera(SynologyDSMEntity, Camera): +class SynoDSMCamera(SynologyDSMCoordinatorEntity, Camera): """Representation a Synology camera.""" - def __init__(self, api: SynoApi, camera: SynoCamera): + def __init__( + self, api: SynoApi, coordinator: DataUpdateCoordinator, camera_id: int + ): """Initialize a Synology camera.""" super().__init__( api, - f"{SynoSurveillanceStation.CAMERA_API_KEY}:{camera.id}", + f"{SynoSurveillanceStation.CAMERA_API_KEY}:{camera_id}", { - ENTITY_NAME: camera.name, + ENTITY_NAME: coordinator.data["cameras"][camera_id].name, + ENTITY_ENABLE: coordinator.data["cameras"][camera_id].is_enabled, ENTITY_CLASS: None, ENTITY_ICON: None, - ENTITY_ENABLE: True, ENTITY_UNIT: None, }, + coordinator, ) - self._camera = camera + Camera.__init__(self) + + self._camera_id = camera_id + self._api = api + + @property + def camera_data(self): + """Camera data.""" + return self.coordinator.data["cameras"][self._camera_id] @property def device_info(self) -> Dict[str, any]: """Return the device information.""" return { - "identifiers": {(DOMAIN, self._api.information.serial, self._camera.id)}, - "name": self._camera.name, - "model": self._camera.model, + "identifiers": { + ( + DOMAIN, + self._api.information.serial, + self.camera_data.id, + ) + }, + "name": self.camera_data.name, + "model": self.camera_data.model, "via_device": ( DOMAIN, self._api.information.serial, @@ -73,7 +98,7 @@ def device_info(self) -> Dict[str, any]: @property def available(self) -> bool: """Return the availability of the camera.""" - return self._camera.is_enabled + return self.camera_data.is_enabled and self.coordinator.last_update_success @property def supported_features(self) -> int: @@ -83,29 +108,53 @@ def supported_features(self) -> int: @property def is_recording(self): """Return true if the device is recording.""" - return self._camera.is_recording + return self.camera_data.is_recording @property def motion_detection_enabled(self): """Return the camera motion detection status.""" - return self._camera.is_motion_detection_enabled + return self.camera_data.is_motion_detection_enabled def camera_image(self) -> bytes: """Return bytes of camera image.""" + _LOGGER.debug( + "SynoDSMCamera.camera_image(%s)", + self.camera_data.name, + ) if not self.available: return None - return self._api.surveillance_station.get_camera_image(self._camera.id) + try: + return self._api.surveillance_station.get_camera_image(self._camera_id) + except (SynologyDSMAPIErrorException) as err: + _LOGGER.debug( + "SynoDSMCamera.camera_image(%s) - Exception:%s", + self.camera_data.name, + err, + ) + return None async def stream_source(self) -> str: """Return the source of the stream.""" + _LOGGER.debug( + "SynoDSMCamera.stream_source(%s)", + self.camera_data.name, + ) if not self.available: return None - return self._camera.live_view.rtsp + return self.camera_data.live_view.rtsp def enable_motion_detection(self): """Enable motion detection in the camera.""" - self._api.surveillance_station.enable_motion_detection(self._camera.id) + _LOGGER.debug( + "SynoDSMCamera.enable_motion_detection(%s)", + self.camera_data.name, + ) + self._api.surveillance_station.enable_motion_detection(self._camera_id) def disable_motion_detection(self): """Disable motion detection in camera.""" - self._api.surveillance_station.disable_motion_detection(self._camera.id) + _LOGGER.debug( + "SynoDSMCamera.disable_motion_detection(%s)", + self.camera_data.name, + ) + self._api.surveillance_station.disable_motion_detection(self._camera_id) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index ba1a8034223a8c..f9bcc8b61b8877 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -19,6 +19,7 @@ DOMAIN = "synology_dsm" PLATFORMS = ["binary_sensor", "camera", "sensor", "switch"] +COORDINATOR_SURVEILLANCE = "coordinator_surveillance_station" # Entry keys SYNO_API = "syno_api" diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 31013451682570..dd2df61165dd39 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -15,7 +15,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow -from . import SynoApi, SynologyDSMDeviceEntity, SynologyDSMEntity +from . import SynoApi, SynologyDSMDeviceEntity, SynologyDSMDispatcherEntity from .const import ( CONF_VOLUMES, DOMAIN, @@ -68,7 +68,7 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMEntity): +class SynoDSMUtilSensor(SynologyDSMDispatcherEntity): """Representation a Synology Utilisation sensor.""" @property @@ -117,7 +117,7 @@ def state(self): return attr -class SynoDSMInfoSensor(SynologyDSMEntity): +class SynoDSMInfoSensor(SynologyDSMDispatcherEntity): """Representation a Synology information sensor.""" def __init__(self, api: SynoApi, entity_type: str, entity_info: Dict[str, str]): diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index ee29c9f2692801..21511757cf315b 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -1,4 +1,5 @@ """Support for Synology DSM switch.""" +import logging from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation @@ -7,9 +8,11 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType -from . import SynoApi, SynologyDSMEntity +from . import SynoApi, SynologyDSMDispatcherEntity from .const import DOMAIN, SURVEILLANCE_SWITCH, SYNO_API +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities @@ -33,7 +36,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class SynoDSMSurveillanceHomeModeToggle(SynologyDSMEntity, ToggleEntity): +class SynoDSMSurveillanceHomeModeToggle(SynologyDSMDispatcherEntity, ToggleEntity): """Representation a Synology Surveillance Station Home Mode toggle.""" def __init__( @@ -62,16 +65,28 @@ def should_poll(self) -> bool: async def async_update(self): """Update the toggle state.""" + _LOGGER.debug( + "SynoDSMSurveillanceHomeModeToggle.async_update(%s)", + self._api.information.serial, + ) self._state = await self.hass.async_add_executor_job( self._api.surveillance_station.get_home_mode_status ) def turn_on(self, **kwargs) -> None: """Turn on Home mode.""" + _LOGGER.debug( + "SynoDSMSurveillanceHomeModeToggle.turn_on(%s)", + self._api.information.serial, + ) self._api.surveillance_station.set_home_mode(True) def turn_off(self, **kwargs) -> None: """Turn off Home mode.""" + _LOGGER.debug( + "SynoDSMSurveillanceHomeModeToggle.turn_off(%s)", + self._api.information.serial, + ) self._api.surveillance_station.set_home_mode(False) @property From 868e530cbb04ca500a266ea60283c58a3e287b9d Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sun, 31 Jan 2021 20:56:42 +0100 Subject: [PATCH 0047/1818] Prevent AttributError for uninitilized KNX ClimateMode (#45793) --- homeassistant/components/knx/knx_entity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index 296bcb2f5405fb..f4597ad230e623 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -40,12 +40,12 @@ async def async_added_to_hass(self) -> None: """Store register state change callback.""" self._device.register_device_updated_cb(self.after_update_callback) - if isinstance(self._device, XknxClimate): + if isinstance(self._device, XknxClimate) and self._device.mode is not None: self._device.mode.register_device_updated_cb(self.after_update_callback) async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" self._device.unregister_device_updated_cb(self.after_update_callback) - if isinstance(self._device, XknxClimate): + if isinstance(self._device, XknxClimate) and self._device.mode is not None: self._device.mode.unregister_device_updated_cb(self.after_update_callback) From 8be357ff4fa9c27b49b7c3b93b69fae07f77eef1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 10:38:08 -1000 Subject: [PATCH 0048/1818] Ensure lutron_caseta is only discovered once (#45792) --- homeassistant/components/lutron_caseta/config_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index bb76c4b4ff77c8..806096d6717bec 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -79,7 +79,9 @@ async def async_step_zeroconf(self, discovery_info): } return await self.async_step_link() - async_step_homekit = async_step_zeroconf + async def async_step_homekit(self, discovery_info): + """Handle a flow initialized by homekit discovery.""" + return await self.async_step_zeroconf(discovery_info) async def async_step_link(self, user_input=None): """Handle pairing with the hub.""" From dac962611274c9b55ee8ac95ba2529af83dc5487 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 10:39:35 -1000 Subject: [PATCH 0049/1818] Resolve homekit cover adjustment slowness (#45730) --- .../components/homekit/accessories.py | 42 +--------- .../components/homekit/type_covers.py | 5 +- tests/components/homekit/common.py | 10 +-- tests/components/homekit/test_accessories.py | 37 +-------- tests/components/homekit/test_homekit.py | 29 ++----- tests/components/homekit/test_type_covers.py | 77 +++++++------------ tests/components/homekit/test_type_fans.py | 41 ++++------ tests/components/homekit/test_type_lights.py | 51 +++++------- 8 files changed, 72 insertions(+), 220 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 51b6508149b776..571c47a521be82 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -1,7 +1,4 @@ """Extend the basic Accessory and Bridge functions.""" -from datetime import timedelta -from functools import partial, wraps -from inspect import getmodule import logging from pyhap.accessory import Accessory, Bridge @@ -37,11 +34,7 @@ __version__, ) from homeassistant.core import Context, callback as ha_callback, split_entity_id -from homeassistant.helpers.event import ( - async_track_state_change_event, - track_point_in_utc_time, -) -from homeassistant.util import dt as dt_util +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.util.decorator import Registry from .const import ( @@ -60,7 +53,6 @@ CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, - DEBOUNCE_TIMEOUT, DEFAULT_LOW_BATTERY_THRESHOLD, DEVICE_CLASS_CO, DEVICE_CLASS_CO2, @@ -98,37 +90,6 @@ TYPES = Registry() -def debounce(func): - """Decorate function to debounce callbacks from HomeKit.""" - - @ha_callback - def call_later_listener(self, *args): - """Handle call_later callback.""" - debounce_params = self.debounce.pop(func.__name__, None) - if debounce_params: - self.hass.async_add_executor_job(func, self, *debounce_params[1:]) - - @wraps(func) - def wrapper(self, *args): - """Start async timer.""" - debounce_params = self.debounce.pop(func.__name__, None) - if debounce_params: - debounce_params[0]() # remove listener - remove_listener = track_point_in_utc_time( - self.hass, - partial(call_later_listener, self), - dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT), - ) - self.debounce[func.__name__] = (remove_listener, *args) - logger.debug( - "%s: Start %s timeout", self.entity_id, func.__name__.replace("set_", "") - ) - - name = getmodule(func).__name__ - logger = logging.getLogger(name) - return wrapper - - def get_accessory(hass, driver, state, aid, config): """Take state and return an accessory object if supported.""" if not aid: @@ -278,7 +239,6 @@ def __init__( self.category = category self.entity_id = entity_id self.hass = hass - self.debounce = {} self._subscriptions = [] self._char_battery = None self._char_charging = None diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 75cabf144839c6..daa782b8d676b5 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -33,7 +33,7 @@ from homeassistant.core import callback from homeassistant.helpers.event import async_track_state_change_event -from .accessories import TYPES, HomeAccessory, debounce +from .accessories import TYPES, HomeAccessory from .const import ( ATTR_OBSTRUCTION_DETECTED, CHAR_CURRENT_DOOR_STATE, @@ -233,7 +233,6 @@ def set_stop(self, value): return self.call_service(DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: self.entity_id}) - @debounce def set_tilt(self, value): """Set tilt to value if call came from HomeKit.""" _LOGGER.info("%s: Set tilt to %d", self.entity_id, value) @@ -284,7 +283,6 @@ def __init__(self, *args, category, service): ) self.async_update_state(state) - @debounce def move_cover(self, value): """Move cover to value if call came from HomeKit.""" _LOGGER.debug("%s: Set position to %d", self.entity_id, value) @@ -360,7 +358,6 @@ def __init__(self, *args): ) self.async_update_state(state) - @debounce def move_cover(self, value): """Move cover to value if call came from HomeKit.""" _LOGGER.debug("%s: Set position to %d", self.entity_id, value) diff --git a/tests/components/homekit/common.py b/tests/components/homekit/common.py index 20aa0e04c2b3b7..6b1d87e3f54fde 100644 --- a/tests/components/homekit/common.py +++ b/tests/components/homekit/common.py @@ -1,17 +1,9 @@ """Collection of fixtures and functions for the HomeKit tests.""" -from unittest.mock import Mock, patch +from unittest.mock import Mock EMPTY_8_6_JPEG = b"empty_8_6" -def patch_debounce(): - """Return patch for debounce method.""" - return patch( - "homeassistant.components.homekit.accessories.debounce", - lambda f: lambda *args, **kwargs: f(*args, **kwargs), - ) - - def mock_turbo_jpeg( first_width=None, second_width=None, first_height=None, second_height=None ): diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 886123062c43ed..8ba2d9fb0b2455 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -2,7 +2,6 @@ This includes tests for all mock object types. """ -from datetime import timedelta from unittest.mock import Mock, patch import pytest @@ -11,7 +10,6 @@ HomeAccessory, HomeBridge, HomeDriver, - debounce, ) from homeassistant.components.homekit.const import ( ATTR_DISPLAY_NAME, @@ -45,41 +43,8 @@ __version__, ) from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS -import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, async_mock_service - - -async def test_debounce(hass): - """Test add_timeout decorator function.""" - - def demo_func(*args): - nonlocal arguments, counter - counter += 1 - arguments = args - - arguments = None - counter = 0 - mock = Mock(hass=hass, debounce={}) - - debounce_demo = debounce(demo_func) - assert debounce_demo.__name__ == "demo_func" - now = dt_util.utcnow() - - with patch("homeassistant.util.dt.utcnow", return_value=now): - await hass.async_add_executor_job(debounce_demo, mock, "value") - async_fire_time_changed(hass, now + timedelta(seconds=3)) - await hass.async_block_till_done() - assert counter == 1 - assert len(arguments) == 2 - - with patch("homeassistant.util.dt.utcnow", return_value=now): - await hass.async_add_executor_job(debounce_demo, mock, "value") - await hass.async_add_executor_job(debounce_demo, mock, "value") - - async_fire_time_changed(hass, now + timedelta(seconds=3)) - await hass.async_block_till_done() - assert counter == 2 +from tests.common import async_mock_service async def test_accessory_cancels_track_state_change_on_stop(hass, hk_driver): diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index c6f897c32a20cd..7b5153b825d778 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -67,7 +67,6 @@ from .util import PATH_HOMEKIT, async_init_entry, async_init_integration from tests.common import MockConfigEntry, mock_device_registry, mock_registry -from tests.components.homekit.common import patch_debounce IP_ADDRESS = "127.0.0.1" @@ -89,14 +88,6 @@ def entity_reg_fixture(hass): return mock_registry(hass) -@pytest.fixture(name="debounce_patcher", scope="module") -def debounce_patcher_fixture(): - """Patch debounce method.""" - patcher = patch_debounce() - yield patcher.start() - patcher.stop() - - async def test_setup_min(hass, mock_zeroconf): """Test async_setup with min config options.""" entry = MockConfigEntry( @@ -485,7 +476,7 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf): mock_get_acc.reset_mock() -async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher): +async def test_homekit_start(hass, hk_driver, device_reg): """Test HomeKit start method.""" entry = await async_init_integration(hass) @@ -573,9 +564,7 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher): assert len(device_reg.devices) == 1 -async def test_homekit_start_with_a_broken_accessory( - hass, hk_driver, debounce_patcher, mock_zeroconf -): +async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf): """Test HomeKit start method.""" pin = b"123-45-678" entry = MockConfigEntry( @@ -754,7 +743,7 @@ def _mock_bridge(*_): async def test_homekit_finds_linked_batteries( - hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf + hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test HomeKit start method.""" entry = await async_init_integration(hass) @@ -840,7 +829,7 @@ def _mock_get_accessory(*args, **kwargs): async def test_homekit_async_get_integration_fails( - hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf + hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test that we continue if async_get_integration fails.""" entry = await async_init_integration(hass) @@ -1072,7 +1061,7 @@ def _write_data(path: str, data: Dict) -> None: async def test_homekit_ignored_missing_devices( - hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf + hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test HomeKit handles a device in the entity registry but missing from the device registry.""" entry = await async_init_integration(hass) @@ -1153,7 +1142,7 @@ def _mock_get_accessory(*args, **kwargs): async def test_homekit_finds_linked_motion_sensors( - hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf + hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test HomeKit start method.""" entry = await async_init_integration(hass) @@ -1228,7 +1217,7 @@ def _mock_get_accessory(*args, **kwargs): async def test_homekit_finds_linked_humidity_sensors( - hass, hk_driver, debounce_patcher, device_reg, entity_reg, mock_zeroconf + hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test HomeKit start method.""" entry = await async_init_integration(hass) @@ -1376,9 +1365,7 @@ def _get_fixtures_base_path(): return os.path.dirname(os.path.dirname(os.path.dirname(__file__))) -async def test_homekit_start_in_accessory_mode( - hass, hk_driver, device_reg, debounce_patcher -): +async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): """Test HomeKit start method in accessory mode.""" entry = await async_init_integration(hass) diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index 48b20e8e0b897c..d39e9cda7d07b4 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -1,7 +1,4 @@ """Test different accessory types: Covers.""" -from collections import namedtuple - -import pytest from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, @@ -22,6 +19,12 @@ HK_DOOR_OPEN, HK_DOOR_OPENING, ) +from homeassistant.components.homekit.type_covers import ( + GarageDoorOpener, + Window, + WindowCovering, + WindowCoveringBasic, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, @@ -40,37 +43,15 @@ from homeassistant.helpers import entity_registry from tests.common import async_mock_service -from tests.components.homekit.common import patch_debounce - - -@pytest.fixture(scope="module") -def cls(): - """Patch debounce decorator during import of type_covers.""" - patcher = patch_debounce() - patcher.start() - _import = __import__( - "homeassistant.components.homekit.type_covers", - fromlist=["GarageDoorOpener", "WindowCovering", "WindowCoveringBasic"], - ) - patcher_tuple = namedtuple( - "Cls", ["window", "windowcovering", "windowcovering_basic", "garage"] - ) - yield patcher_tuple( - window=_import.Window, - windowcovering=_import.WindowCovering, - windowcovering_basic=_import.WindowCoveringBasic, - garage=_import.GarageDoorOpener, - ) - patcher.stop() -async def test_garage_door_open_close(hass, hk_driver, cls, events): +async def test_garage_door_open_close(hass, hk_driver, events): """Test if accessory and HA are updated accordingly.""" entity_id = "cover.garage_door" hass.states.async_set(entity_id, None) await hass.async_block_till_done() - acc = cls.garage(hass, hk_driver, "Garage Door", entity_id, 2, None) + acc = GarageDoorOpener(hass, hk_driver, "Garage Door", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -148,13 +129,13 @@ async def test_garage_door_open_close(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] is None -async def test_windowcovering_set_cover_position(hass, hk_driver, cls, events): +async def test_windowcovering_set_cover_position(hass, hk_driver, events): """Test if accessory and HA are updated accordingly.""" entity_id = "cover.window" hass.states.async_set(entity_id, None) await hass.async_block_till_done() - acc = cls.windowcovering(hass, hk_driver, "Cover", entity_id, 2, None) + acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -218,13 +199,13 @@ async def test_windowcovering_set_cover_position(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == 75 -async def test_window_instantiate(hass, hk_driver, cls, events): +async def test_window_instantiate(hass, hk_driver, events): """Test if Window accessory is instantiated correctly.""" entity_id = "cover.window" hass.states.async_set(entity_id, None) await hass.async_block_till_done() - acc = cls.window(hass, hk_driver, "Window", entity_id, 2, None) + acc = Window(hass, hk_driver, "Window", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -235,7 +216,7 @@ async def test_window_instantiate(hass, hk_driver, cls, events): assert acc.char_target_position.value == 0 -async def test_windowcovering_cover_set_tilt(hass, hk_driver, cls, events): +async def test_windowcovering_cover_set_tilt(hass, hk_driver, events): """Test if accessory and HA update slat tilt accordingly.""" entity_id = "cover.window" @@ -243,7 +224,7 @@ async def test_windowcovering_cover_set_tilt(hass, hk_driver, cls, events): entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_SET_TILT_POSITION} ) await hass.async_block_till_done() - acc = cls.windowcovering(hass, hk_driver, "Cover", entity_id, 2, None) + acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -302,12 +283,12 @@ async def test_windowcovering_cover_set_tilt(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == 75 -async def test_windowcovering_open_close(hass, hk_driver, cls, events): +async def test_windowcovering_open_close(hass, hk_driver, events): """Test if accessory and HA are updated accordingly.""" entity_id = "cover.window" hass.states.async_set(entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: 0}) - acc = cls.windowcovering_basic(hass, hk_driver, "Cover", entity_id, 2, None) + acc = WindowCoveringBasic(hass, hk_driver, "Cover", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -383,14 +364,14 @@ async def test_windowcovering_open_close(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] is None -async def test_windowcovering_open_close_stop(hass, hk_driver, cls, events): +async def test_windowcovering_open_close_stop(hass, hk_driver, events): """Test if accessory and HA are updated accordingly.""" entity_id = "cover.window" hass.states.async_set( entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP} ) - acc = cls.windowcovering_basic(hass, hk_driver, "Cover", entity_id, 2, None) + acc = WindowCoveringBasic(hass, hk_driver, "Cover", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -431,7 +412,7 @@ async def test_windowcovering_open_close_stop(hass, hk_driver, cls, events): async def test_windowcovering_open_close_with_position_and_stop( - hass, hk_driver, cls, events + hass, hk_driver, events ): """Test if accessory and HA are updated accordingly.""" entity_id = "cover.stop_window" @@ -441,7 +422,7 @@ async def test_windowcovering_open_close_with_position_and_stop( STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP | SUPPORT_SET_POSITION}, ) - acc = cls.windowcovering(hass, hk_driver, "Cover", entity_id, 2, None) + acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None) await acc.run_handler() await hass.async_block_till_done() @@ -461,7 +442,7 @@ async def test_windowcovering_open_close_with_position_and_stop( assert events[-1].data[ATTR_VALUE] is None -async def test_windowcovering_basic_restore(hass, hk_driver, cls, events): +async def test_windowcovering_basic_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running @@ -486,22 +467,20 @@ async def test_windowcovering_basic_restore(hass, hk_driver, cls, events): hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) await hass.async_block_till_done() - acc = cls.windowcovering_basic(hass, hk_driver, "Cover", "cover.simple", 2, None) + acc = WindowCoveringBasic(hass, hk_driver, "Cover", "cover.simple", 2, None) assert acc.category == 14 assert acc.char_current_position is not None assert acc.char_target_position is not None assert acc.char_position_state is not None - acc = cls.windowcovering_basic( - hass, hk_driver, "Cover", "cover.all_info_set", 2, None - ) + acc = WindowCoveringBasic(hass, hk_driver, "Cover", "cover.all_info_set", 2, None) assert acc.category == 14 assert acc.char_current_position is not None assert acc.char_target_position is not None assert acc.char_position_state is not None -async def test_windowcovering_restore(hass, hk_driver, cls, events): +async def test_windowcovering_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running @@ -526,20 +505,20 @@ async def test_windowcovering_restore(hass, hk_driver, cls, events): hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) await hass.async_block_till_done() - acc = cls.windowcovering(hass, hk_driver, "Cover", "cover.simple", 2, None) + acc = WindowCovering(hass, hk_driver, "Cover", "cover.simple", 2, None) assert acc.category == 14 assert acc.char_current_position is not None assert acc.char_target_position is not None assert acc.char_position_state is not None - acc = cls.windowcovering(hass, hk_driver, "Cover", "cover.all_info_set", 2, None) + acc = WindowCovering(hass, hk_driver, "Cover", "cover.all_info_set", 2, None) assert acc.category == 14 assert acc.char_current_position is not None assert acc.char_target_position is not None assert acc.char_position_state is not None -async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, cls, events): +async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, events): """Test if accessory and HA are updated accordingly with a linked obstruction sensor.""" linked_obstruction_sensor_entity_id = "binary_sensor.obstruction" entity_id = "cover.garage_door" @@ -547,7 +526,7 @@ async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, cls, hass.states.async_set(linked_obstruction_sensor_entity_id, STATE_OFF) hass.states.async_set(entity_id, None) await hass.async_block_till_done() - acc = cls.garage( + acc = GarageDoorOpener( hass, hk_driver, "Garage Door", diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index fc5ac4344ad90e..8111d256594aff 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -1,8 +1,6 @@ """Test different accessory types: Fans.""" -from collections import namedtuple from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE -import pytest from homeassistant.components.fan import ( ATTR_DIRECTION, @@ -16,6 +14,7 @@ SUPPORT_SET_SPEED, ) from homeassistant.components.homekit.const import ATTR_VALUE +from homeassistant.components.homekit.type_fans import Fan from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, @@ -28,27 +27,15 @@ from homeassistant.helpers import entity_registry from tests.common import async_mock_service -from tests.components.homekit.common import patch_debounce -@pytest.fixture(scope="module") -def cls(): - """Patch debounce decorator during import of type_fans.""" - patcher = patch_debounce() - patcher.start() - _import = __import__("homeassistant.components.homekit.type_fans", fromlist=["Fan"]) - patcher_tuple = namedtuple("Cls", ["fan"]) - yield patcher_tuple(fan=_import.Fan) - patcher.stop() - - -async def test_fan_basic(hass, hk_driver, cls, events): +async def test_fan_basic(hass, hk_driver, events): """Test fan with char state.""" entity_id = "fan.demo" hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) hk_driver.add_accessory(acc) assert acc.aid == 1 @@ -120,7 +107,7 @@ async def test_fan_basic(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] is None -async def test_fan_direction(hass, hk_driver, cls, events): +async def test_fan_direction(hass, hk_driver, events): """Test fan with direction.""" entity_id = "fan.demo" @@ -130,7 +117,7 @@ async def test_fan_direction(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_DIRECTION, ATTR_DIRECTION: DIRECTION_FORWARD}, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) hk_driver.add_accessory(acc) assert acc.char_direction.value == 0 @@ -188,7 +175,7 @@ async def test_fan_direction(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == DIRECTION_REVERSE -async def test_fan_oscillate(hass, hk_driver, cls, events): +async def test_fan_oscillate(hass, hk_driver, events): """Test fan with oscillate.""" entity_id = "fan.demo" @@ -198,7 +185,7 @@ async def test_fan_oscillate(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_OSCILLATE, ATTR_OSCILLATING: False}, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) hk_driver.add_accessory(acc) assert acc.char_swing.value == 0 @@ -257,7 +244,7 @@ async def test_fan_oscillate(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] is True -async def test_fan_speed(hass, hk_driver, cls, events): +async def test_fan_speed(hass, hk_driver, events): """Test fan with speed.""" entity_id = "fan.demo" @@ -270,7 +257,7 @@ async def test_fan_speed(hass, hk_driver, cls, events): }, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the @@ -336,7 +323,7 @@ async def test_fan_speed(hass, hk_driver, cls, events): assert acc.char_active.value == 1 -async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): +async def test_fan_set_all_one_shot(hass, hk_driver, events): """Test fan with speed.""" entity_id = "fan.demo" @@ -353,7 +340,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): }, ) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", entity_id, 1, None) + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the @@ -529,7 +516,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): assert len(call_set_direction) == 2 -async def test_fan_restore(hass, hk_driver, cls, events): +async def test_fan_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running @@ -554,14 +541,14 @@ async def test_fan_restore(hass, hk_driver, cls, events): hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) await hass.async_block_till_done() - acc = cls.fan(hass, hk_driver, "Fan", "fan.simple", 2, None) + acc = Fan(hass, hk_driver, "Fan", "fan.simple", 2, None) assert acc.category == 3 assert acc.char_active is not None assert acc.char_direction is None assert acc.char_speed is None assert acc.char_swing is None - acc = cls.fan(hass, hk_driver, "Fan", "fan.all_info_set", 2, None) + acc = Fan(hass, hk_driver, "Fan", "fan.all_info_set", 2, None) assert acc.category == 3 assert acc.char_active is not None assert acc.char_direction is not None diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index e82bc5bb15ddb6..42ef18f3505b33 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -1,10 +1,9 @@ """Test different accessory types: Lights.""" -from collections import namedtuple 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.type_lights import Light from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, @@ -28,29 +27,15 @@ from homeassistant.helpers import entity_registry from tests.common import async_mock_service -from tests.components.homekit.common import patch_debounce -@pytest.fixture(scope="module") -def cls(): - """Patch debounce decorator during import of type_lights.""" - patcher = patch_debounce() - patcher.start() - _import = __import__( - "homeassistant.components.homekit.type_lights", fromlist=["Light"] - ) - patcher_tuple = namedtuple("Cls", ["light"]) - yield patcher_tuple(light=_import.Light) - patcher.stop() - - -async def test_light_basic(hass, hk_driver, cls, events): +async def test_light_basic(hass, hk_driver, events): """Test light with char state.""" entity_id = "light.demo" hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) hk_driver.add_accessory(acc) assert acc.aid == 1 @@ -113,7 +98,7 @@ async def test_light_basic(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == "Set state to 0" -async def test_light_brightness(hass, hk_driver, cls, events): +async def test_light_brightness(hass, hk_driver, events): """Test light with brightness.""" entity_id = "light.demo" @@ -123,7 +108,7 @@ async def test_light_brightness(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS: 255}, ) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the @@ -231,7 +216,7 @@ async def test_light_brightness(hass, hk_driver, cls, events): assert acc.char_brightness.value == 1 -async def test_light_color_temperature(hass, hk_driver, cls, events): +async def test_light_color_temperature(hass, hk_driver, events): """Test light with color temperature.""" entity_id = "light.demo" @@ -241,7 +226,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR_TEMP, ATTR_COLOR_TEMP: 190}, ) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) hk_driver.add_accessory(acc) assert acc.char_color_temperature.value == 190 @@ -278,7 +263,7 @@ async def test_light_color_temperature(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == "color temperature at 250" -async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, events): +async def test_light_color_temperature_and_rgb_color(hass, hk_driver, events): """Test light with color temperature and rgb color not exposing temperature.""" entity_id = "light.demo" @@ -292,7 +277,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event }, ) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None) + acc = Light(hass, hk_driver, "Light", entity_id, 2, None) assert acc.char_hue.value == 260 assert acc.char_saturation.value == 90 @@ -313,7 +298,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event assert acc.char_saturation.value == 61 -async def test_light_rgb_color(hass, hk_driver, cls, events): +async def test_light_rgb_color(hass, hk_driver, events): """Test light with rgb_color.""" entity_id = "light.demo" @@ -323,7 +308,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events): {ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR, ATTR_HS_COLOR: (260, 90)}, ) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) hk_driver.add_accessory(acc) assert acc.char_hue.value == 260 @@ -365,7 +350,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events): assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" -async def test_light_restore(hass, hk_driver, cls, events): +async def test_light_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running @@ -385,20 +370,20 @@ async def test_light_restore(hass, hk_driver, cls, events): hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", "light.simple", 1, None) + acc = Light(hass, hk_driver, "Light", "light.simple", 1, None) hk_driver.add_accessory(acc) assert acc.category == 5 # Lightbulb assert acc.chars == [] assert acc.char_on.value == 0 - acc = cls.light(hass, hk_driver, "Light", "light.all_info_set", 2, None) + acc = Light(hass, hk_driver, "Light", "light.all_info_set", 2, None) assert acc.category == 5 # Lightbulb assert acc.chars == ["Brightness"] assert acc.char_on.value == 0 -async def test_light_set_brightness_and_color(hass, hk_driver, cls, events): +async def test_light_set_brightness_and_color(hass, hk_driver, events): """Test light with all chars in one go.""" entity_id = "light.demo" @@ -411,7 +396,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events): }, ) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the @@ -474,7 +459,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, cls, events): ) -async def test_light_set_brightness_and_color_temp(hass, hk_driver, cls, events): +async def test_light_set_brightness_and_color_temp(hass, hk_driver, events): """Test light with all chars in one go.""" entity_id = "light.demo" @@ -487,7 +472,7 @@ async def test_light_set_brightness_and_color_temp(hass, hk_driver, cls, events) }, ) await hass.async_block_till_done() - acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None) + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) hk_driver.add_accessory(acc) # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the From 852af7e3721228d0956a6818e62c044cfc319218 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 10:40:24 -1000 Subject: [PATCH 0050/1818] Update homekit for new async library changes (#45731) --- .../components/homekit/accessories.py | 8 +++---- .../components/homekit/manifest.json | 2 +- .../components/homekit/type_cameras.py | 8 ++----- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_type_cameras.py | 23 ++++++++++--------- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 571c47a521be82..6a33b63e89aa66 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -487,17 +487,17 @@ def __init__(self, hass, driver, name): def setup_message(self): """Prevent print of pyhap setup message to terminal.""" - def get_snapshot(self, info): + async def async_get_snapshot(self, info): """Get snapshot from accessory if supported.""" acc = self.accessories.get(info["aid"]) if acc is None: raise ValueError("Requested snapshot for missing accessory") - if not hasattr(acc, "get_snapshot"): + if not hasattr(acc, "async_get_snapshot"): raise ValueError( "Got a request for snapshot, but the Accessory " - 'does not define a "get_snapshot" method' + 'does not define a "async_get_snapshot" method' ) - return acc.get_snapshot(info) + return await acc.async_get_snapshot(info) class HomeDriver(AccessoryDriver): diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index d188dd270ab0b7..23b43958848b2a 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.1.0", + "HAP-python==3.2.0", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index b61a2c57612836..22bf37aa0c3b2b 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -1,5 +1,4 @@ """Class to hold all camera accessories.""" -import asyncio from datetime import timedelta import logging @@ -444,13 +443,10 @@ async def reconfigure_stream(self, session_info, stream_config): """Reconfigure the stream so that it uses the given ``stream_config``.""" return True - def get_snapshot(self, image_size): + async def async_get_snapshot(self, image_size): """Return a jpeg of a snapshot from the camera.""" return scale_jpeg_camera_image( - asyncio.run_coroutine_threadsafe( - self.hass.components.camera.async_get_image(self.entity_id), - self.hass.loop, - ).result(), + await self.hass.components.camera.async_get_image(self.entity_id), image_size["image-width"], image_size["image-height"], ) diff --git a/requirements_all.txt b/requirements_all.txt index a8127ed7097eb9..8281744f2212b5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.1.0 +HAP-python==3.2.0 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3e8972c9864803..d997b91f3387d1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -4,7 +4,7 @@ -r requirements_test.txt # homeassistant.components.homekit -HAP-python==3.1.0 +HAP-python==3.2.0 # homeassistant.components.flick_electric PyFlick==0.0.2 diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index 804e03a4e6c9a2..f4c7169310f3a1 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -212,22 +212,23 @@ async def test_camera_stream_source_configured(hass, run_driver, events): ) with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): TurboJPEGSingleton() - assert await hass.async_add_executor_job( - acc.get_snapshot, {"aid": 2, "image-width": 300, "image-height": 200} + assert await acc.async_get_snapshot( + {"aid": 2, "image-width": 300, "image-height": 200} ) - # Verify the bridge only forwards get_snapshot for + # Verify the bridge only forwards async_get_snapshot for # cameras and valid accessory ids - assert await hass.async_add_executor_job( - bridge.get_snapshot, {"aid": 2, "image-width": 300, "image-height": 200} + assert await bridge.async_get_snapshot( + {"aid": 2, "image-width": 300, "image-height": 200} ) with pytest.raises(ValueError): - assert await hass.async_add_executor_job( - bridge.get_snapshot, {"aid": 3, "image-width": 300, "image-height": 200} + assert await bridge.async_get_snapshot( + {"aid": 3, "image-width": 300, "image-height": 200} ) + with pytest.raises(ValueError): - assert await hass.async_add_executor_job( - bridge.get_snapshot, {"aid": 4, "image-width": 300, "image-height": 200} + assert await bridge.async_get_snapshot( + {"aid": 4, "image-width": 300, "image-height": 200} ) @@ -400,8 +401,8 @@ async def test_camera_with_no_stream(hass, run_driver, events): await _async_stop_all_streams(hass, acc) with pytest.raises(HomeAssistantError): - await hass.async_add_executor_job( - acc.get_snapshot, {"aid": 2, "image-width": 300, "image-height": 200} + assert await acc.async_get_snapshot( + {"aid": 2, "image-width": 300, "image-height": 200} ) From 73d7d80731a5f18915e4e871111b752d1137ff66 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 10:43:00 -1000 Subject: [PATCH 0051/1818] Add timeout to lutron_caseta to prevent it blocking startup (#45769) --- .../components/lutron_caseta/__init__.py | 26 ++++++-- .../components/lutron_caseta/config_flow.py | 47 ++++++++++----- .../components/lutron_caseta/const.py | 2 + .../lutron_caseta/test_config_flow.py | 60 +++++++++++++------ 4 files changed, 97 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 7526d4874f6713..f349dde8921d9c 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -1,10 +1,12 @@ """Component for interacting with a Lutron Caseta system.""" import asyncio import logging +import ssl from aiolip import LIP from aiolip.data import LIPMode from aiolip.protocol import LIP_BUTTON_PRESS +import async_timeout from pylutron_caseta.smartbridge import Smartbridge import voluptuous as vol @@ -29,6 +31,7 @@ BRIDGE_DEVICE_ID, BRIDGE_LEAP, BRIDGE_LIP, + BRIDGE_TIMEOUT, BUTTON_DEVICES, CONF_CA_CERTS, CONF_CERTFILE, @@ -94,15 +97,26 @@ async def async_setup_entry(hass, config_entry): keyfile = hass.config.path(config_entry.data[CONF_KEYFILE]) certfile = hass.config.path(config_entry.data[CONF_CERTFILE]) ca_certs = hass.config.path(config_entry.data[CONF_CA_CERTS]) + bridge = None - bridge = Smartbridge.create_tls( - hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs - ) + try: + bridge = Smartbridge.create_tls( + hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs + ) + except ssl.SSLError: + _LOGGER.error("Invalid certificate used to connect to bridge at %s.", host) + return False + + timed_out = True + try: + with async_timeout.timeout(BRIDGE_TIMEOUT): + await bridge.connect() + timed_out = False + except asyncio.TimeoutError: + _LOGGER.error("Timeout while trying to connect to bridge at %s.", host) - await bridge.connect() - if not bridge.is_connected(): + if timed_out or not bridge.is_connected(): await bridge.close() - _LOGGER.error("Unable to connect to Lutron Caseta bridge at %s", host) raise ConfigEntryNotReady _LOGGER.debug("Connected to Lutron Caseta bridge via LEAP at %s", host) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 806096d6717bec..30cbc03ac47f0c 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -2,7 +2,9 @@ import asyncio import logging import os +import ssl +import async_timeout from pylutron_caseta.pairing import PAIR_CA, PAIR_CERT, PAIR_KEY, async_pair from pylutron_caseta.smartbridge import Smartbridge import voluptuous as vol @@ -15,6 +17,7 @@ from .const import ( ABORT_REASON_ALREADY_CONFIGURED, ABORT_REASON_CANNOT_CONNECT, + BRIDGE_TIMEOUT, CONF_CA_CERTS, CONF_CERTFILE, CONF_KEYFILE, @@ -50,6 +53,8 @@ def __init__(self): """Initialize a Lutron Caseta flow.""" self.data = {} self.lutron_id = None + self.tls_assets_validated = False + self.attempted_tls_validation = False async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" @@ -92,11 +97,16 @@ async def async_step_link(self, user_input=None): self._configure_tls_assets() + 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() + ): + self.tls_assets_validated = True + self.attempted_tls_validation = True + if user_input is not None: - if ( - await self.hass.async_add_executor_job(self._tls_assets_exist) - and await self.async_validate_connectable_bridge_config() - ): + if self.tls_assets_validated: # If we previous paired and the tls assets already exist, # we do not need to go though pairing again. return self.async_create_entry(title=self.bridge_id, data=self.data) @@ -207,6 +217,8 @@ async def async_step_import_failed(self, user_input=None): async def async_validate_connectable_bridge_config(self): """Check if we can connect to the bridge with the current config.""" + bridge = None + try: bridge = Smartbridge.create_tls( hostname=self.data[CONF_HOST], @@ -214,16 +226,23 @@ async def async_validate_connectable_bridge_config(self): certfile=self.hass.config.path(self.data[CONF_CERTFILE]), ca_certs=self.hass.config.path(self.data[CONF_CA_CERTS]), ) - - await bridge.connect() - if not bridge.is_connected(): - return False - - await bridge.close() - return True - except Exception: # pylint: disable=broad-except - _LOGGER.exception( - "Unknown exception while checking connectivity to bridge %s", + except ssl.SSLError: + _LOGGER.error( + "Invalid certificate used to connect to bridge at %s.", self.data[CONF_HOST], ) return False + + connected_ok = False + try: + with async_timeout.timeout(BRIDGE_TIMEOUT): + await bridge.connect() + connected_ok = bridge.is_connected() + except asyncio.TimeoutError: + _LOGGER.error( + "Timeout while trying to connect to bridge at %s.", + self.data[CONF_HOST], + ) + + await bridge.close() + return connected_ok diff --git a/homeassistant/components/lutron_caseta/const.py b/homeassistant/components/lutron_caseta/const.py index 4226be36f05262..fcc647f00bac5f 100644 --- a/homeassistant/components/lutron_caseta/const.py +++ b/homeassistant/components/lutron_caseta/const.py @@ -33,3 +33,5 @@ CONF_TYPE = "type" CONF_SUBTYPE = "subtype" + +BRIDGE_TIMEOUT = 35 diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 8a33fab670bfdf..58377c8e085945 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -1,5 +1,6 @@ """Test the Lutron Caseta config flow.""" import asyncio +import ssl from unittest.mock import AsyncMock, patch from pylutron_caseta.pairing import PAIR_CA, PAIR_CERT, PAIR_KEY @@ -21,6 +22,14 @@ from tests.common import MockConfigEntry +EMPTY_MOCK_CONFIG_ENTRY = { + CONF_HOST: "", + CONF_KEYFILE: "", + CONF_CERTFILE: "", + CONF_CA_CERTS: "", +} + + MOCK_ASYNC_PAIR_SUCCESS = { PAIR_KEY: "mock_key", PAIR_CERT: "mock_cert", @@ -115,21 +124,34 @@ async def test_bridge_cannot_connect(hass): async def test_bridge_cannot_connect_unknown_error(hass): """Test checking for connection and encountering an unknown error.""" - entry_mock_data = { - CONF_HOST: "", - CONF_KEYFILE: "", - CONF_CERTFILE: "", - CONF_CA_CERTS: "", - } - with patch.object(Smartbridge, "create_tls") as create_tls: mock_bridge = MockBridge() - mock_bridge.connect = AsyncMock(side_effect=Exception()) + mock_bridge.connect = AsyncMock(side_effect=asyncio.TimeoutError) create_tls.return_value = mock_bridge result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=entry_mock_data, + data=EMPTY_MOCK_CONFIG_ENTRY, + ) + + assert result["type"] == "form" + assert result["step_id"] == STEP_IMPORT_FAILED + assert result["errors"] == {"base": ERROR_CANNOT_CONNECT} + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == CasetaConfigFlow.ABORT_REASON_CANNOT_CONNECT + + +async def test_bridge_invalid_ssl_error(hass): + """Test checking for connection and encountering invalid ssl certs.""" + + with patch.object(Smartbridge, "create_tls", side_effect=ssl.SSLError): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=EMPTY_MOCK_CONFIG_ENTRY, ) assert result["type"] == "form" @@ -351,23 +373,25 @@ async def test_form_user_reuses_existing_assets_when_pairing_again(hass, tmpdir) assert result["errors"] is None assert result["step_id"] == "user" - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - }, - ) - await hass.async_block_till_done() + with patch.object(Smartbridge, "create_tls") as create_tls: + create_tls.return_value = MockBridge(can_connect=True) + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + }, + ) + await hass.async_block_till_done() + assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch.object(Smartbridge, "create_tls") as create_tls, patch( + with patch( "homeassistant.components.lutron_caseta.async_setup", return_value=True ), patch( "homeassistant.components.lutron_caseta.async_setup_entry", return_value=True, ): - create_tls.return_value = MockBridge(can_connect=True) result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, From 385b7e17efa098439014026656acca291df20a09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 11:36:19 -1000 Subject: [PATCH 0052/1818] Move homekit accessory creation to async (#45788) --- homeassistant/components/homekit/__init__.py | 49 +++++++++----------- tests/components/homekit/test_homekit.py | 12 ++--- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 5cbc9bb6f18aa6..e92c35ffac87f6 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -42,7 +42,21 @@ from homeassistant.loader import IntegrationNotFound, async_get_integration from homeassistant.util import get_local_ip -from .accessories import get_accessory +# pylint: disable=unused-import +from . import ( # noqa: F401 + type_cameras, + type_covers, + type_fans, + type_humidifiers, + type_lights, + type_locks, + type_media_players, + type_security_systems, + type_sensors, + type_switches, + type_thermostats, +) +from .accessories import HomeBridge, HomeDriver, get_accessory from .aidmanager import AccessoryAidStorage from .const import ( AID_STORAGE, @@ -441,9 +455,6 @@ def __init__( def setup(self, zeroconf_instance): """Set up bridge and accessory driver.""" - # pylint: disable=import-outside-toplevel - from .accessories import HomeDriver - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) ip_addr = self._ip_address or get_local_ip() persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) @@ -590,7 +601,7 @@ async def async_start(self, *args): bridged_states.append(state) self._async_register_bridge(dev_reg) - await self.hass.async_add_executor_job(self._start, bridged_states) + await self._async_start(bridged_states) _LOGGER.debug("Driver start for %s", self._name) self.hass.add_job(self.driver.start_service) self.status = STATUS_RUNNING @@ -639,34 +650,20 @@ def _async_purge_old_bridges(self, dev_reg, identifier, connection): for device_id in devices_to_purge: dev_reg.async_remove_device(device_id) - def _start(self, bridged_states): - # pylint: disable=unused-import, import-outside-toplevel - from . import ( # noqa: F401 - type_cameras, - type_covers, - type_fans, - type_humidifiers, - type_lights, - type_locks, - type_media_players, - type_security_systems, - type_sensors, - type_switches, - type_thermostats, - ) - + async def _async_start(self, entity_states): + """Start the accessory.""" if self._homekit_mode == HOMEKIT_MODE_ACCESSORY: - state = bridged_states[0] + state = entity_states[0] conf = self._config.pop(state.entity_id, {}) acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf) self.driver.add_accessory(acc) else: - from .accessories import HomeBridge - self.bridge = HomeBridge(self.hass, self.driver, self._name) - for state in bridged_states: + for state in entity_states: self.add_bridge_accessory(state) - self.driver.add_accessory(self.bridge) + acc = self.bridge + + await self.hass.async_add_executor_job(self.driver.add_accessory, acc) if not self.driver.state.paired: show_setup_message( diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 7b5153b825d778..a8c3c81595e15c 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -201,7 +201,7 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf): hass.states.async_set("light.demo2", "on") zeroconf_mock = MagicMock() with patch( - f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver + f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver ) as mock_driver, patch("homeassistant.util.get_local_ip") as mock_ip: mock_ip.return_value = IP_ADDRESS await hass.async_add_executor_job(homekit.setup, zeroconf_mock) @@ -245,9 +245,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf): mock_zeroconf = MagicMock() path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) - with patch( - f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver - ) as mock_driver: + with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_executor_job(homekit.setup, mock_zeroconf) mock_driver.assert_called_with( hass, @@ -283,9 +281,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf): zeroconf_instance = MagicMock() path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) - with patch( - f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver - ) as mock_driver: + with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: await hass.async_add_executor_job(homekit.setup, zeroconf_instance) mock_driver.assert_called_with( hass, @@ -735,7 +731,7 @@ def _mock_bridge(*_): with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( - f"{PATH_HOMEKIT}.accessories.HomeBridge", _mock_bridge + f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge ): await homekit.async_start() await hass.async_block_till_done() From 3e080f88c650ffb5f4a95f5c35d7654efce27904 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 31 Jan 2021 23:14:20 +0100 Subject: [PATCH 0053/1818] Bump zwave-js-server-python to 0.14.2 (#45800) --- 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 08ca9668a99b1d..5c3b82837ca2f7 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.14.1"], + "requirements": ["zwave-js-server-python==0.14.2"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8281744f2212b5..65936f88e3de07 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.1 +zwave-js-server-python==0.14.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d997b91f3387d1..89489222bd2e2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1200,4 +1200,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.1 +zwave-js-server-python==0.14.2 From 1d94c10bb556d0f08fc46ee3f091bbd858b139d9 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 1 Feb 2021 07:55:18 +0100 Subject: [PATCH 0054/1818] Change via_hub to via_device (#45804) --- homeassistant/components/roon/media_player.py | 2 +- homeassistant/components/somfy/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index 8abcba189daf8e..b2ae62ec250cd7 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -175,7 +175,7 @@ def device_info(self): "name": self.name, "manufacturer": "RoonLabs", "model": dev_model, - "via_hub": (DOMAIN, self._server.roon_id), + "via_device": (DOMAIN, self._server.roon_id), } def update_data(self, player_data=None): diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 2fc83ea71de5a0..75475e52f0658c 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -188,7 +188,7 @@ def device_info(self): "identifiers": {(DOMAIN, self.unique_id)}, "name": self.name, "model": self.device.type, - "via_hub": (DOMAIN, self.device.parent_id), + "via_device": (DOMAIN, self.device.parent_id), # For the moment, Somfy only returns their own device. "manufacturer": "Somfy", } From 03928dbe554516ccf563b632ec79cb57f9bfacde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Mon, 1 Feb 2021 08:34:55 +0100 Subject: [PATCH 0055/1818] Bump pyatv to 0.7.6 (#45799) --- homeassistant/components/apple_tv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index 21b2df308d3f33..66ae2864dc456a 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/apple_tv", "requirements": [ - "pyatv==0.7.5" + "pyatv==0.7.6" ], "zeroconf": [ "_mediaremotetv._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 65936f88e3de07..957583427691c9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1286,7 +1286,7 @@ pyatmo==4.2.2 pyatome==0.1.1 # homeassistant.components.apple_tv -pyatv==0.7.5 +pyatv==0.7.6 # homeassistant.components.bbox pybbox==0.0.5-alpha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 89489222bd2e2d..1e0e4097f020ad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -669,7 +669,7 @@ pyatag==0.3.4.4 pyatmo==4.2.2 # homeassistant.components.apple_tv -pyatv==0.7.5 +pyatv==0.7.6 # homeassistant.components.blackbird pyblackbird==0.5 From 2ffdc4694aef1482e9bbe814b0af567d5af3af8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 1 Feb 2021 09:36:06 +0200 Subject: [PATCH 0056/1818] Remove misleading "for" from custom integration warning message (#45811) --- homeassistant/loader.py | 2 +- tests/test_loader.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 215f552a90837a..bedc04928af2d6 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -49,7 +49,7 @@ PACKAGE_CUSTOM_COMPONENTS = "custom_components" PACKAGE_BUILTIN = "homeassistant.components" CUSTOM_WARNING = ( - "You are using a custom integration for %s which has not " + "You are using a custom integration %s which has not " "been tested by Home Assistant. This component might " "cause stability problems, be sure to disable it if you " "experience issues with Home Assistant." diff --git a/tests/test_loader.py b/tests/test_loader.py index c1c27f56cb7ea9..22f61c0a397863 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -131,10 +131,10 @@ async def test_custom_component_name(hass): async def test_log_warning_custom_component(hass, caplog): """Test that we log a warning when loading a custom component.""" hass.components.test_standalone - assert "You are using a custom integration for test_standalone" in caplog.text + assert "You are using a custom integration test_standalone" in caplog.text await loader.async_get_integration(hass, "test") - assert "You are using a custom integration for test " in caplog.text + assert "You are using a custom integration test " in caplog.text async def test_get_integration(hass): From 0b63510cab085b9b7e57d27e2b2ccae4c891ce44 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Feb 2021 02:45:24 -0600 Subject: [PATCH 0057/1818] Add zwave_js binary sensors property name for Notification CC (#45810) --- homeassistant/components/zwave_js/binary_sensor.py | 8 ++++++++ tests/components/zwave_js/common.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 6dc5cc58df544e..42394fe127ccca 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -349,6 +349,14 @@ def is_on(self) -> bool: return self.info.primary_value.value in self._mapping_info["states"] return bool(self.info.primary_value.value != 0) + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + node_name = self.info.node.name or self.info.node.device_config.description + property_name = self.info.primary_value.property_name + property_key_name = self.info.primary_value.property_key_name + return f"{node_name}: {property_name}: {property_key_name}" + @property def device_class(self) -> Optional[str]: """Return device class.""" diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index a7be137657c9d3..399b009f4c2299 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -6,7 +6,9 @@ LOW_BATTERY_BINARY_SENSOR = "binary_sensor.multisensor_6_low_battery_level" ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any" DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any" -NOTIFICATION_MOTION_BINARY_SENSOR = "binary_sensor.multisensor_6_motion_sensor_status" +NOTIFICATION_MOTION_BINARY_SENSOR = ( + "binary_sensor.multisensor_6_home_security_motion_sensor_status" +) PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( "binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door" ) From 91a54eecb3e0408751aae2323774356afceb1bbb Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 1 Feb 2021 00:48:50 -0800 Subject: [PATCH 0058/1818] Add izone control zone (#43984) --- homeassistant/components/izone/climate.py | 54 +++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 443a80298f1310..776d3f120c90a0 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -110,6 +110,8 @@ def __init__(self, controller: Controller) -> None: self._supported_features = SUPPORT_FAN_MODE + # If mode RAS, or mode master with CtrlZone 13 then can set master temperature, + # otherwise the unit determines which zone to use as target. See interface manual p. 8 if ( controller.ras_mode == "master" and controller.zone_ctrl == 13 ) or controller.ras_mode == "RAS": @@ -269,6 +271,16 @@ def device_state_attributes(self): self.temperature_unit, PRECISION_HALVES, ), + "control_zone": self._controller.zone_ctrl, + "control_zone_name": self.control_zone_name, + # Feature SUPPORT_TARGET_TEMPERATURE controls both displaying target temp & setting it + # As the feature is turned off for zone control, report target temp as extra state attribute + "control_zone_setpoint": show_temp( + self.hass, + self.control_zone_setpoint, + self.temperature_unit, + PRECISION_HALVES, + ), } @property @@ -314,13 +326,35 @@ def current_temperature(self) -> Optional[float]: return self._controller.temp_supply return self._controller.temp_return + @property + def control_zone_name(self): + """Return the zone that currently controls the AC unit (if target temp not set by controller).""" + if self._supported_features & SUPPORT_TARGET_TEMPERATURE: + return None + zone_ctrl = self._controller.zone_ctrl + zone = next((z for z in self.zones.values() if z.zone_index == zone_ctrl), None) + if zone is None: + return None + return zone.name + + @property + def control_zone_setpoint(self) -> Optional[float]: + """Return the temperature setpoint of the zone that currently controls the AC unit (if target temp not set by controller).""" + if self._supported_features & SUPPORT_TARGET_TEMPERATURE: + return None + zone_ctrl = self._controller.zone_ctrl + zone = next((z for z in self.zones.values() if z.zone_index == zone_ctrl), None) + if zone is None: + return None + return zone.target_temperature + @property @_return_on_connection_error() def target_temperature(self) -> Optional[float]: - """Return the temperature we try to reach.""" - if not self._supported_features & SUPPORT_TARGET_TEMPERATURE: - return None - return self._controller.temp_setpoint + """Return the temperature we try to reach (either from control zone or master unit).""" + if self._supported_features & SUPPORT_TARGET_TEMPERATURE: + return self._controller.temp_setpoint + return self.control_zone_setpoint @property def supply_temperature(self) -> float: @@ -569,3 +603,15 @@ async def async_turn_off(self): """Turn device off (close zone).""" await self._controller.wrap_and_catch(self._zone.set_mode(Zone.Mode.CLOSE)) self.async_write_ha_state() + + @property + def zone_index(self): + """Return the zone index for matching to CtrlZone.""" + return self._zone.index + + @property + def device_state_attributes(self): + """Return the optional state attributes.""" + return { + "zone_index": self.zone_index, + } From e0bf18986bab2c92957980ba3db6ecdaf6ba5239 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 23:15:20 -1000 Subject: [PATCH 0059/1818] Fix missing async for lutron_caseta timeout (#45812) --- homeassistant/components/lutron_caseta/__init__.py | 6 +++--- homeassistant/components/lutron_caseta/config_flow.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index f349dde8921d9c..73eb0b83fa6e35 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -104,16 +104,16 @@ async def async_setup_entry(hass, config_entry): hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs ) except ssl.SSLError: - _LOGGER.error("Invalid certificate used to connect to bridge at %s.", host) + _LOGGER.error("Invalid certificate used to connect to bridge at %s", host) return False timed_out = True try: - with async_timeout.timeout(BRIDGE_TIMEOUT): + async with async_timeout.timeout(BRIDGE_TIMEOUT): await bridge.connect() timed_out = False except asyncio.TimeoutError: - _LOGGER.error("Timeout while trying to connect to bridge at %s.", host) + _LOGGER.error("Timeout while trying to connect to bridge at %s", host) if timed_out or not bridge.is_connected(): await bridge.close() diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 30cbc03ac47f0c..ab9865f999a0b9 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -228,19 +228,19 @@ async def async_validate_connectable_bridge_config(self): ) except ssl.SSLError: _LOGGER.error( - "Invalid certificate used to connect to bridge at %s.", + "Invalid certificate used to connect to bridge at %s", self.data[CONF_HOST], ) return False connected_ok = False try: - with async_timeout.timeout(BRIDGE_TIMEOUT): + 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.", + "Timeout while trying to connect to bridge at %s", self.data[CONF_HOST], ) From 31a84555b97789c99c94f61075172e2f61350c5e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 1 Feb 2021 10:54:07 +0100 Subject: [PATCH 0060/1818] Bump zwave-js-server-python to 0.15.0 (#45813) --- 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 5c3b82837ca2f7..586a6492a1a962 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.14.2"], + "requirements": ["zwave-js-server-python==0.15.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 957583427691c9..60df4ea85f058b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.2 +zwave-js-server-python==0.15.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1e0e4097f020ad..f855ab4bacc4f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1200,4 +1200,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.2 +zwave-js-server-python==0.15.0 From 9f59515bb8f300d6cb28830393efb7aa5fd37c8b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 23:54:39 -1000 Subject: [PATCH 0061/1818] Fix shutdown deadlock with run_callback_threadsafe (#45807) --- homeassistant/core.py | 14 ++++++++++- homeassistant/util/async_.py | 41 ++++++++++++++++++++++++++++++++ tests/test_core.py | 24 +++++++++++++++++++ tests/util/test_async.py | 45 +++++++++++++++++++++++++++++++++++- 4 files changed, 122 insertions(+), 2 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 6d187225685682..4294eb530a7403 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -71,7 +71,11 @@ Unauthorized, ) from homeassistant.util import location, network -from homeassistant.util.async_ import fire_coroutine_threadsafe, run_callback_threadsafe +from homeassistant.util.async_ import ( + fire_coroutine_threadsafe, + run_callback_threadsafe, + shutdown_run_callback_threadsafe, +) import homeassistant.util.dt as dt_util from homeassistant.util.timeout import TimeoutManager from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem @@ -548,6 +552,14 @@ async def async_stop(self, exit_code: int = 0, *, force: bool = False) -> None: # stage 3 self.state = CoreState.not_running self.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE) + + # Prevent run_callback_threadsafe from scheduling any additional + # callbacks in the event loop as callbacks created on the futures + # it returns will never run after the final `self.async_block_till_done` + # which will cause the futures to block forever when waiting for + # the `result()` which will cause a deadlock when shutting down the executor. + shutdown_run_callback_threadsafe(self.loop) + try: async with self.timeout.async_timeout(30): await self.async_block_till_done() diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index ded44473038d6f..f61225502ee2fb 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -10,6 +10,8 @@ _LOGGER = logging.getLogger(__name__) +_SHUTDOWN_RUN_CALLBACK_THREADSAFE = "_shutdown_run_callback_threadsafe" + T = TypeVar("T") @@ -58,6 +60,28 @@ def run_callback() -> None: _LOGGER.warning("Exception on lost future: ", exc_info=True) loop.call_soon_threadsafe(run_callback) + + if hasattr(loop, _SHUTDOWN_RUN_CALLBACK_THREADSAFE): + # + # If the final `HomeAssistant.async_block_till_done` in + # `HomeAssistant.async_stop` has already been called, the callback + # will never run and, `future.result()` will block forever which + # will prevent the thread running this code from shutting down which + # will result in a deadlock when the main thread attempts to shutdown + # the executor and `.join()` the thread running this code. + # + # To prevent this deadlock we do the following on shutdown: + # + # 1. Set the _SHUTDOWN_RUN_CALLBACK_THREADSAFE attr on this function + # by calling `shutdown_run_callback_threadsafe` + # 2. Call `hass.async_block_till_done` at least once after shutdown + # to ensure all callbacks have run + # 3. Raise an exception here to ensure `future.result()` can never be + # called and hit the deadlock since once `shutdown_run_callback_threadsafe` + # we cannot promise the callback will be executed. + # + raise RuntimeError("The event loop is in the process of shutting down.") + return future @@ -139,3 +163,20 @@ async def sem_task(task: Awaitable[Any]) -> Any: return await gather( *(sem_task(task) for task in tasks), return_exceptions=return_exceptions ) + + +def shutdown_run_callback_threadsafe(loop: AbstractEventLoop) -> None: + """Call when run_callback_threadsafe should prevent creating new futures. + + We must finish all callbacks before the executor is shutdown + or we can end up in a deadlock state where: + + `executor.result()` is waiting for its `._condition` + and the executor shutdown is trying to `.join()` the + executor thread. + + This function is considered irreversible and should only ever + be called when Home Assistant is going to shutdown and + python is going to exit. + """ + setattr(loop, _SHUTDOWN_RUN_CALLBACK_THREADSAFE, True) diff --git a/tests/test_core.py b/tests/test_core.py index dbed2b8c0bf5b5..0bf00d92c45971 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -169,6 +169,30 @@ async def test_stage_shutdown(hass): assert len(test_all) == 2 +async def test_shutdown_calls_block_till_done_after_shutdown_run_callback_threadsafe( + hass, +): + """Ensure shutdown_run_callback_threadsafe is called before the final async_block_till_done.""" + stop_calls = [] + + async def _record_block_till_done(): + nonlocal stop_calls + stop_calls.append("async_block_till_done") + + def _record_shutdown_run_callback_threadsafe(loop): + nonlocal stop_calls + stop_calls.append(("shutdown_run_callback_threadsafe", loop)) + + with patch.object(hass, "async_block_till_done", _record_block_till_done), patch( + "homeassistant.core.shutdown_run_callback_threadsafe", + _record_shutdown_run_callback_threadsafe, + ): + await hass.async_stop() + + assert stop_calls[-2] == ("shutdown_run_callback_threadsafe", hass.loop) + assert stop_calls[-1] == "async_block_till_done" + + async def test_pending_sheduler(hass): """Add a coro to pending tasks.""" call_count = [] diff --git a/tests/util/test_async.py b/tests/util/test_async.py index db088ada93e703..d4fdce1e9123ec 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -50,7 +50,8 @@ def test_fire_coroutine_threadsafe_from_inside_event_loop( def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _): """Testing calling run_callback_threadsafe from inside an event loop.""" callback = MagicMock() - loop = MagicMock() + + loop = Mock(spec=["call_soon_threadsafe"]) loop._thread_ident = None mock_ident.return_value = 5 @@ -168,3 +169,45 @@ async def _increment_runs_if_in_time(): ) assert results == [2, 2, -1, -1] + + +async def test_shutdown_run_callback_threadsafe(hass): + """Test we can shutdown run_callback_threadsafe.""" + hasync.shutdown_run_callback_threadsafe(hass.loop) + callback = MagicMock() + + with pytest.raises(RuntimeError): + hasync.run_callback_threadsafe(hass.loop, callback) + + +async def test_run_callback_threadsafe(hass): + """Test run_callback_threadsafe runs code in the event loop.""" + it_ran = False + + def callback(): + nonlocal it_ran + it_ran = True + + assert hasync.run_callback_threadsafe(hass.loop, callback) + assert it_ran is False + + # Verify that async_block_till_done will flush + # out the callback + await hass.async_block_till_done() + assert it_ran is True + + +async def test_callback_is_always_scheduled(hass): + """Test run_callback_threadsafe always calls call_soon_threadsafe before checking for shutdown.""" + # We have to check the shutdown state AFTER the callback is scheduled otherwise + # the function could continue on and the caller call `future.result()` after + # the point in the main thread where callbacks are no longer run. + + callback = MagicMock() + hasync.shutdown_run_callback_threadsafe(hass.loop) + + with patch.object(hass.loop, "call_soon_threadsafe") as mock_call_soon_threadsafe: + with pytest.raises(RuntimeError): + hasync.run_callback_threadsafe(hass.loop, callback) + + mock_call_soon_threadsafe.assert_called_once() From a8cf377ed71ac1a723bfb93066929ebc7bf69f4f Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 1 Feb 2021 08:46:36 -0700 Subject: [PATCH 0062/1818] Add stop_cover service for zwave_js (#45805) --- homeassistant/components/zwave_js/cover.py | 17 ++- tests/components/zwave_js/test_cover.py | 125 ++++++++++++++++++--- 2 files changed, 121 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index b7834e8e59cf70..5f473f809575bf 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -21,6 +21,8 @@ LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE +PRESS_BUTTON = True +RELEASE_BUTTON = False async def async_setup_entry( @@ -77,10 +79,17 @@ async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - target_value = self.get_zwave_value("targetValue") - await self.info.node.async_set_value(target_value, 99) + target_value = self.get_zwave_value("Open") + await self.info.node.async_set_value(target_value, PRESS_BUTTON) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - target_value = self.get_zwave_value("targetValue") - await self.info.node.async_set_value(target_value, 0) + target_value = self.get_zwave_value("Close") + await self.info.node.async_set_value(target_value, PRESS_BUTTON) + + async def async_stop_cover(self, **kwargs: Any) -> None: + """Stop cover.""" + target_value = self.get_zwave_value("Open") + await self.info.node.async_set_value(target_value, RELEASE_BUTTON) + target_value = self.get_zwave_value("Close") + await self.info.node.async_set_value(target_value, RELEASE_BUTTON) diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index c327034b61c98a..f014245a5f8a2d 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -95,21 +95,65 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "targetValue", - "propertyName": "targetValue", + "property": "Open", + "propertyName": "Open", "metadata": { - "label": "Target value", - "max": 99, - "min": 0, - "type": "number", + "type": "boolean", "readable": True, "writeable": True, - "label": "Target value", + "label": "Perform a level change (Open)", + "ccSpecific": {"switchType": 3}, }, } - assert args["value"] == 99 + assert args["value"] client.async_send_command.reset_mock() + # Test stop after opening + await hass.services.async_call( + "cover", + "stop_cover", + {"entity_id": WINDOW_COVER_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] + assert open_args["command"] == "node.set_value" + assert open_args["nodeId"] == 6 + assert open_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Open", + "propertyName": "Open", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Open)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not open_args["value"] + + close_args = client.async_send_command.call_args_list[1][0][0] + assert close_args["command"] == "node.set_value" + assert close_args["nodeId"] == 6 + assert close_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Close", + "propertyName": "Close", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Close)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not close_args["value"] # Test position update from value updated event event = Event( @@ -130,6 +174,7 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): }, ) node.receive_event(event) + client.async_send_command.reset_mock() state = hass.states.get(WINDOW_COVER_ENTITY) assert state.state == "open" @@ -141,7 +186,6 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): {"entity_id": WINDOW_COVER_ENTITY}, blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" @@ -150,19 +194,66 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "targetValue", - "propertyName": "targetValue", + "property": "Close", + "propertyName": "Close", "metadata": { - "label": "Target value", - "max": 99, - "min": 0, - "type": "number", + "type": "boolean", "readable": True, "writeable": True, - "label": "Target value", + "label": "Perform a level change (Close)", + "ccSpecific": {"switchType": 3}, }, } - assert args["value"] == 0 + assert args["value"] + + client.async_send_command.reset_mock() + + # Test stop after closing + await hass.services.async_call( + "cover", + "stop_cover", + {"entity_id": WINDOW_COVER_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] + assert open_args["command"] == "node.set_value" + assert open_args["nodeId"] == 6 + assert open_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Open", + "propertyName": "Open", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Open)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not open_args["value"] + + close_args = client.async_send_command.call_args_list[1][0][0] + assert close_args["command"] == "node.set_value" + assert close_args["nodeId"] == 6 + assert close_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Close", + "propertyName": "Close", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Close)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not close_args["value"] client.async_send_command.reset_mock() From 374817fbaaff46d232c42343907e25e763e2f8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 1 Feb 2021 16:54:25 +0100 Subject: [PATCH 0063/1818] Bump awesomeversion from 21.1.6 to 21.2.0 (#45821) --- requirements_test.txt | 2 +- script/hassfest/manifest.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 91380d14c5e3ea..2c10083ecfd261 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,7 +13,7 @@ pre-commit==2.10.0 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 -awesomeversion==21.1.6 +awesomeversion==21.2.0 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.10.1 diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index e02df86f4e1899..3beb6aadfc5fde 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -59,6 +59,7 @@ def verify_version(value: str): AwesomeVersionStrategy.SEMVER, AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.BUILDVER, + AwesomeVersionStrategy.PEP440, ]: raise vol.Invalid( f"'{version}' is not a valid version. This will cause a future version of Home Assistant to block this integration.", From 6e67b943da09a13409e1a61ec34ed7144e1093c3 Mon Sep 17 00:00:00 2001 From: Tobias Bielohlawek Date: Mon, 1 Feb 2021 16:58:00 +0100 Subject: [PATCH 0064/1818] Remove Nuimo integration (#45600) --- .coveragerc | 1 - .../components/nuimo_controller/__init__.py | 190 ------------------ .../components/nuimo_controller/manifest.json | 7 - .../components/nuimo_controller/services.yaml | 17 -- requirements_all.txt | 3 - script/gen_requirements_all.py | 1 - 6 files changed, 219 deletions(-) delete mode 100644 homeassistant/components/nuimo_controller/__init__.py delete mode 100644 homeassistant/components/nuimo_controller/manifest.json delete mode 100644 homeassistant/components/nuimo_controller/services.yaml diff --git a/.coveragerc b/.coveragerc index 65b499c372f50d..0609051f19635b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -623,7 +623,6 @@ omit = homeassistant/components/norway_air/air_quality.py homeassistant/components/notify_events/notify.py homeassistant/components/nsw_fuel_station/sensor.py - homeassistant/components/nuimo_controller/* homeassistant/components/nuki/__init__.py homeassistant/components/nuki/const.py homeassistant/components/nuki/lock.py diff --git a/homeassistant/components/nuimo_controller/__init__.py b/homeassistant/components/nuimo_controller/__init__.py deleted file mode 100644 index 013c2caf23d6c4..00000000000000 --- a/homeassistant/components/nuimo_controller/__init__.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Support for Nuimo device over Bluetooth LE.""" -import logging -import threading -import time - -# pylint: disable=import-error -from nuimo import NuimoController, NuimoDiscoveryManager -import voluptuous as vol - -from homeassistant.const import CONF_MAC, CONF_NAME, EVENT_HOMEASSISTANT_STOP -import homeassistant.helpers.config_validation as cv - -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "nuimo_controller" -EVENT_NUIMO = "nuimo_input" - -DEFAULT_NAME = "None" - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Optional(CONF_MAC): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - -SERVICE_NUIMO = "led_matrix" -DEFAULT_INTERVAL = 2.0 - -SERVICE_NUIMO_SCHEMA = vol.Schema( - { - vol.Required("matrix"): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional("interval", default=DEFAULT_INTERVAL): float, - } -) - -DEFAULT_ADAPTER = "hci0" - - -def setup(hass, config): - """Set up the Nuimo component.""" - conf = config[DOMAIN] - mac = conf.get(CONF_MAC) - name = conf.get(CONF_NAME) - NuimoThread(hass, mac, name).start() - return True - - -class NuimoLogger: - """Handle Nuimo Controller event callbacks.""" - - def __init__(self, hass, name): - """Initialize Logger object.""" - self._hass = hass - self._name = name - - def received_gesture_event(self, event): - """Input Event received.""" - _LOGGER.debug( - "Received event: name=%s, gesture_id=%s,value=%s", - event.name, - event.gesture, - event.value, - ) - self._hass.bus.fire( - EVENT_NUIMO, {"type": event.name, "value": event.value, "name": self._name} - ) - - -class NuimoThread(threading.Thread): - """Manage one Nuimo controller.""" - - def __init__(self, hass, mac, name): - """Initialize thread object.""" - super().__init__() - self._hass = hass - self._mac = mac - self._name = name - self._hass_is_running = True - self._nuimo = None - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) - - def run(self): - """Set up the connection or be idle.""" - while self._hass_is_running: - if not self._nuimo or not self._nuimo.is_connected(): - self._attach() - self._connect() - else: - time.sleep(1) - - if self._nuimo: - self._nuimo.disconnect() - self._nuimo = None - - def stop(self, event): - """Terminate Thread by unsetting flag.""" - _LOGGER.debug("Stopping thread for Nuimo %s", self._mac) - self._hass_is_running = False - - def _attach(self): - """Create a Nuimo object from MAC address or discovery.""" - - if self._nuimo: - self._nuimo.disconnect() - self._nuimo = None - - if self._mac: - self._nuimo = NuimoController(self._mac) - else: - nuimo_manager = NuimoDiscoveryManager( - bluetooth_adapter=DEFAULT_ADAPTER, delegate=DiscoveryLogger() - ) - nuimo_manager.start_discovery() - # Were any Nuimos found? - if not nuimo_manager.nuimos: - _LOGGER.debug("No Nuimo devices detected") - return - # Take the first Nuimo found. - self._nuimo = nuimo_manager.nuimos[0] - self._mac = self._nuimo.addr - - def _connect(self): - """Build up connection and set event delegator and service.""" - if not self._nuimo: - return - - try: - self._nuimo.connect() - _LOGGER.debug("Connected to %s", self._mac) - except RuntimeError as error: - _LOGGER.error("Could not connect to %s: %s", self._mac, error) - time.sleep(1) - return - - nuimo_event_delegate = NuimoLogger(self._hass, self._name) - self._nuimo.set_delegate(nuimo_event_delegate) - - def handle_write_matrix(call): - """Handle led matrix service.""" - matrix = call.data.get("matrix", None) - name = call.data.get(CONF_NAME, DEFAULT_NAME) - interval = call.data.get("interval", DEFAULT_INTERVAL) - if self._name == name and matrix: - self._nuimo.write_matrix(matrix, interval) - - self._hass.services.register( - DOMAIN, SERVICE_NUIMO, handle_write_matrix, schema=SERVICE_NUIMO_SCHEMA - ) - - self._nuimo.write_matrix(HOMEASSIST_LOGO, 2.0) - - -# must be 9x9 matrix -HOMEASSIST_LOGO = ( - " . " - + " ... " - + " ..... " - + " ....... " - + "..... ..." - + " ....... " - + " .. .... " - + " .. .... " - + "........." -) - - -class DiscoveryLogger: - """Handle Nuimo Discovery callbacks.""" - - # pylint: disable=no-self-use - def discovery_started(self): - """Discovery started.""" - _LOGGER.info("Started discovery") - - # pylint: disable=no-self-use - def discovery_finished(self): - """Discovery finished.""" - _LOGGER.info("Finished discovery") - - # pylint: disable=no-self-use - def controller_added(self, nuimo): - """Return that a controller was found.""" - _LOGGER.info("Added Nuimo: %s", nuimo) diff --git a/homeassistant/components/nuimo_controller/manifest.json b/homeassistant/components/nuimo_controller/manifest.json deleted file mode 100644 index dddd4a975231b7..00000000000000 --- a/homeassistant/components/nuimo_controller/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "nuimo_controller", - "name": "Nuimo controller", - "documentation": "https://www.home-assistant.io/integrations/nuimo_controller", - "requirements": ["--only-binary=all nuimo==0.1.0"], - "codeowners": [] -} diff --git a/homeassistant/components/nuimo_controller/services.yaml b/homeassistant/components/nuimo_controller/services.yaml deleted file mode 100644 index d98659caa8b11f..00000000000000 --- a/homeassistant/components/nuimo_controller/services.yaml +++ /dev/null @@ -1,17 +0,0 @@ -led_matrix: - description: Sends an LED Matrix to your display - fields: - matrix: - description: "A string representation of the matrix to be displayed. See the SDK documentation for more info: https://github.com/getSenic/nuimo-linux-python#write-to-nuimos-led-matrix" - example: "........ - 0000000. - .000000. - ..00000. - .0.0000. - .00.000. - .000000. - .000000. - ........" - interval: - description: Display interval in seconds - example: 0.5 diff --git a/requirements_all.txt b/requirements_all.txt index 60df4ea85f058b..804b0495297855 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1,9 +1,6 @@ # Home Assistant Core, full dependency set -r requirements.txt -# homeassistant.components.nuimo_controller -# --only-binary=all nuimo==0.1.0 - # homeassistant.components.dht # Adafruit-DHT==1.4.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index dc1ef9a471bb61..52820bfa5726d3 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -28,7 +28,6 @@ "evdev", "face_recognition", "i2csense", - "nuimo", "opencv-python-headless", "py_noaa", "pybluez", From 2136b3013f42af385e88ad2fa991d58262600812 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 1 Feb 2021 08:48:49 -0800 Subject: [PATCH 0065/1818] Increase test coverage for stream worker (#44161) Co-authored-by: Justin Wong <46082645+uvjustin@users.noreply.github.com> --- tests/components/stream/test_worker.py | 489 +++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 tests/components/stream/test_worker.py diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py new file mode 100644 index 00000000000000..8196899dcf9a84 --- /dev/null +++ b/tests/components/stream/test_worker.py @@ -0,0 +1,489 @@ +"""Test the stream worker corner cases. + +Exercise the stream worker functionality by mocking av.open calls to return a +fake media container as well a fake decoded stream in the form of a series of +packets. This is needed as some of these cases can't be encoded using pyav. It +is preferred to use test_hls.py for example, when possible. + +The worker opens the stream source (typically a URL) and gets back a +container that has audio/video streams. The worker iterates over the sequence +of packets and sends them to the appropriate output buffers. Each test +creates a packet sequence, with a mocked output buffer to capture the segments +pushed to the output streams. The packet sequence can be used to exercise +failure modes or corner cases like how out of order packets are handled. +""" + +import fractions +import math +import threading +from unittest.mock import patch + +import av + +from homeassistant.components.stream import Stream +from homeassistant.components.stream.const import ( + MAX_MISSING_DTS, + MIN_SEGMENT_DURATION, + PACKETS_TO_WAIT_FOR_AUDIO, +) +from homeassistant.components.stream.worker import stream_worker + +STREAM_SOURCE = "some-stream-source" +# Formats here are arbitrary, not exercised by tests +STREAM_OUTPUT_FORMAT = "hls" +AUDIO_STREAM_FORMAT = "mp3" +VIDEO_STREAM_FORMAT = "h264" +VIDEO_FRAME_RATE = 12 +AUDIO_SAMPLE_RATE = 11025 +PACKET_DURATION = fractions.Fraction(1, VIDEO_FRAME_RATE) # in seconds +SEGMENT_DURATION = ( + math.ceil(MIN_SEGMENT_DURATION / PACKET_DURATION) * PACKET_DURATION +) # in seconds +TEST_SEQUENCE_LENGTH = 5 * VIDEO_FRAME_RATE +LONGER_TEST_SEQUENCE_LENGTH = 20 * VIDEO_FRAME_RATE +OUT_OF_ORDER_PACKET_INDEX = 3 * VIDEO_FRAME_RATE +PACKETS_PER_SEGMENT = SEGMENT_DURATION / PACKET_DURATION +SEGMENTS_PER_PACKET = PACKET_DURATION / SEGMENT_DURATION + + +class FakePyAvStream: + """A fake pyav Stream.""" + + def __init__(self, name, rate): + """Initialize the stream.""" + self.name = name + self.time_base = fractions.Fraction(1, rate) + self.profile = "ignored-profile" + + +VIDEO_STREAM = FakePyAvStream(VIDEO_STREAM_FORMAT, VIDEO_FRAME_RATE) +AUDIO_STREAM = FakePyAvStream(AUDIO_STREAM_FORMAT, AUDIO_SAMPLE_RATE) + + +class PacketSequence: + """Creates packets in a sequence for exercising stream worker behavior. + + A test can create a PacketSequence(N) that will raise a StopIteration after + N packets. Each packet has an arbitrary monotomically increasing dts/pts value + that is parseable by the worker, but a test can manipulate the values to + exercise corner cases. + """ + + def __init__(self, num_packets): + """Initialize the sequence with the number of packets it provides.""" + self.packet = 0 + self.num_packets = num_packets + + def __iter__(self): + """Reset the sequence.""" + self.packet = 0 + return self + + def __next__(self): + """Return the next packet.""" + if self.packet >= self.num_packets: + raise StopIteration + self.packet += 1 + + class FakePacket: + time_base = fractions.Fraction(1, VIDEO_FRAME_RATE) + dts = self.packet * PACKET_DURATION / time_base + pts = self.packet * PACKET_DURATION / time_base + duration = PACKET_DURATION / time_base + stream = VIDEO_STREAM + is_keyframe = True + + return FakePacket() + + +class FakePyAvContainer: + """A fake container returned by mock av.open for a stream.""" + + def __init__(self, video_stream, audio_stream): + """Initialize the fake container.""" + # Tests can override this to trigger different worker behavior + self.packets = PacketSequence(0) + + class FakePyAvStreams: + video = video_stream + audio = audio_stream + + self.streams = FakePyAvStreams() + + class FakePyAvFormat: + name = "ignored-format" + + self.format = FakePyAvFormat() + + def demux(self, streams): + """Decode the streams from container, and return a packet sequence.""" + return self.packets + + def close(self): + """Close the container.""" + return + + +class FakePyAvBuffer: + """Holds outputs of the decoded stream for tests to assert on results.""" + + def __init__(self): + """Initialize the FakePyAvBuffer.""" + self.segments = [] + self.audio_packets = [] + self.video_packets = [] + self.finished = False + + def add_stream(self, template=None): + """Create an output buffer that captures packets for test to examine.""" + + class FakeStream: + def __init__(self, capture_packets): + self.capture_packets = capture_packets + + def close(self): + return + + def mux(self, packet): + self.capture_packets.append(packet) + + if template.name == AUDIO_STREAM_FORMAT: + return FakeStream(self.audio_packets) + return FakeStream(self.video_packets) + + def mux(self, packet): + """Capture a packet for tests to examine.""" + # Forward to appropriate FakeStream + packet.stream.mux(packet) + + def close(self): + """Close the buffer.""" + return + + def capture_output_segment(self, segment): + """Capture the output segment for tests to inspect.""" + assert not self.finished + if segment is None: + self.finished = True + else: + self.segments.append(segment) + + +class MockPyAv: + """Mocks out av.open.""" + + def __init__(self, video=True, audio=False): + """Initialize the MockPyAv.""" + video_stream = [VIDEO_STREAM] if video else [] + audio_stream = [AUDIO_STREAM] if audio else [] + self.container = FakePyAvContainer( + video_stream=video_stream, audio_stream=audio_stream + ) + self.capture_buffer = FakePyAvBuffer() + + def open(self, stream_source, *args, **kwargs): + """Return a stream or buffer depending on args.""" + if stream_source == STREAM_SOURCE: + return self.container + return self.capture_buffer + + +async def async_decode_stream(hass, packets, py_av=None): + """Start a stream worker that decodes incoming stream packets into output segments.""" + stream = Stream(hass, STREAM_SOURCE) + stream.add_provider(STREAM_OUTPUT_FORMAT) + + if not py_av: + py_av = MockPyAv() + py_av.container.packets = packets + + with patch("av.open", new=py_av.open), patch( + "homeassistant.components.stream.core.StreamOutput.put", + side_effect=py_av.capture_buffer.capture_output_segment, + ): + stream_worker(hass, stream, threading.Event()) + await hass.async_block_till_done() + + return py_av.capture_buffer + + +async def test_stream_open_fails(hass): + """Test failure on stream open.""" + stream = Stream(hass, STREAM_SOURCE) + stream.add_provider(STREAM_OUTPUT_FORMAT) + with patch("av.open") as av_open: + av_open.side_effect = av.error.InvalidDataError(-2, "error") + stream_worker(hass, stream, threading.Event()) + await hass.async_block_till_done() + av_open.assert_called_once() + + +async def test_stream_worker_success(hass): + """Test a short stream that ends and outputs everything correctly.""" + decoded_stream = await async_decode_stream( + hass, PacketSequence(TEST_SEQUENCE_LENGTH) + ) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check number of segments. A segment is only formed when a packet from the next + # segment arrives, hence the subtraction of one from the sequence length. + assert len(segments) == int((TEST_SEQUENCE_LENGTH - 1) * SEGMENTS_PER_PACKET) + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # Check segment durations + assert all([s.duration == SEGMENT_DURATION for s in segments]) + assert len(decoded_stream.video_packets) == TEST_SEQUENCE_LENGTH + assert len(decoded_stream.audio_packets) == 0 + + +async def test_skip_out_of_order_packet(hass): + """Skip a single out of order packet.""" + packets = list(PacketSequence(TEST_SEQUENCE_LENGTH)) + # This packet is out of order + packets[OUT_OF_ORDER_PACKET_INDEX].dts = -9090 + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # If skipped packet would have been the first packet of a segment, the previous + # segment will be longer by a packet duration + # We also may possibly lose a segment due to the shifting pts boundary + if OUT_OF_ORDER_PACKET_INDEX % PACKETS_PER_SEGMENT == 0: + # Check duration of affected segment and remove it + longer_segment_index = int( + (OUT_OF_ORDER_PACKET_INDEX - 1) * SEGMENTS_PER_PACKET + ) + assert ( + segments[longer_segment_index].duration + == SEGMENT_DURATION + PACKET_DURATION + ) + del segments[longer_segment_index] + # Check number of segments + assert len(segments) == int((len(packets) - 1 - 1) * SEGMENTS_PER_PACKET - 1) + else: # Otherwise segment durations and number of segments are unaffected + # Check number of segments + assert len(segments) == int((len(packets) - 1) * SEGMENTS_PER_PACKET) + # Check remaining segment durations + assert all([s.duration == SEGMENT_DURATION for s in segments]) + assert len(decoded_stream.video_packets) == len(packets) - 1 + assert len(decoded_stream.audio_packets) == 0 + + +async def test_discard_old_packets(hass): + """Skip a series of out of order packets.""" + + packets = list(PacketSequence(TEST_SEQUENCE_LENGTH)) + # Packets after this one are considered out of order + packets[OUT_OF_ORDER_PACKET_INDEX - 1].dts = 9090 + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check number of segments + assert len(segments) == int((OUT_OF_ORDER_PACKET_INDEX - 1) * SEGMENTS_PER_PACKET) + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # Check segment durations + assert all([s.duration == SEGMENT_DURATION for s in segments]) + assert len(decoded_stream.video_packets) == OUT_OF_ORDER_PACKET_INDEX + assert len(decoded_stream.audio_packets) == 0 + + +async def test_packet_overflow(hass): + """Packet is too far out of order, and looks like overflow, ending stream early.""" + + packets = list(PacketSequence(TEST_SEQUENCE_LENGTH)) + # Packet is so far out of order, exceeds max gap and looks like overflow + packets[OUT_OF_ORDER_PACKET_INDEX].dts = -9000000 + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check number of segments + assert len(segments) == int((OUT_OF_ORDER_PACKET_INDEX - 1) * SEGMENTS_PER_PACKET) + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # Check segment durations + assert all([s.duration == SEGMENT_DURATION for s in segments]) + assert len(decoded_stream.video_packets) == OUT_OF_ORDER_PACKET_INDEX + assert len(decoded_stream.audio_packets) == 0 + + +async def test_skip_initial_bad_packets(hass): + """Tests a small number of initial "bad" packets with missing dts.""" + + num_packets = LONGER_TEST_SEQUENCE_LENGTH + packets = list(PacketSequence(num_packets)) + num_bad_packets = MAX_MISSING_DTS - 1 + for i in range(0, num_bad_packets): + packets[i].dts = None + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check number of segments + assert len(segments) == int( + (num_packets - num_bad_packets - 1) * SEGMENTS_PER_PACKET + ) + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # Check segment durations + assert all([s.duration == SEGMENT_DURATION for s in segments]) + assert len(decoded_stream.video_packets) == num_packets - num_bad_packets + assert len(decoded_stream.audio_packets) == 0 + + +async def test_too_many_initial_bad_packets_fails(hass): + """Test initial bad packets are too high, causing it to never start.""" + + num_packets = LONGER_TEST_SEQUENCE_LENGTH + packets = list(PacketSequence(num_packets)) + num_bad_packets = MAX_MISSING_DTS + 1 + for i in range(0, num_bad_packets): + packets[i].dts = None + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + assert len(segments) == 0 + assert len(decoded_stream.video_packets) == 0 + assert len(decoded_stream.audio_packets) == 0 + + +async def test_skip_missing_dts(hass): + """Test packets in the middle of the stream missing DTS are skipped.""" + + num_packets = LONGER_TEST_SEQUENCE_LENGTH + packets = list(PacketSequence(num_packets)) + bad_packet_start = int(LONGER_TEST_SEQUENCE_LENGTH / 2) + num_bad_packets = MAX_MISSING_DTS - 1 + for i in range(bad_packet_start, bad_packet_start + num_bad_packets): + packets[i].dts = None + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # Check segment durations (not counting the elongated segment) + assert ( + sum([segments[i].duration == SEGMENT_DURATION for i in range(len(segments))]) + >= len(segments) - 1 + ) + assert len(decoded_stream.video_packets) == num_packets - num_bad_packets + assert len(decoded_stream.audio_packets) == 0 + + +async def test_too_many_bad_packets(hass): + """Test bad packets are too many, causing it to end.""" + + num_packets = LONGER_TEST_SEQUENCE_LENGTH + packets = list(PacketSequence(num_packets)) + bad_packet_start = int(LONGER_TEST_SEQUENCE_LENGTH / 2) + num_bad_packets = MAX_MISSING_DTS + 1 + for i in range(bad_packet_start, bad_packet_start + num_bad_packets): + packets[i].dts = None + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + assert len(segments) == int((bad_packet_start - 1) * SEGMENTS_PER_PACKET) + assert len(decoded_stream.video_packets) == bad_packet_start + assert len(decoded_stream.audio_packets) == 0 + + +async def test_no_video_stream(hass): + """Test no video stream in the container means no resulting output.""" + py_av = MockPyAv(video=False) + + decoded_stream = await async_decode_stream( + hass, PacketSequence(TEST_SEQUENCE_LENGTH), py_av=py_av + ) + # Note: This failure scenario does not output an end of stream + assert not decoded_stream.finished + segments = decoded_stream.segments + assert len(segments) == 0 + assert len(decoded_stream.video_packets) == 0 + assert len(decoded_stream.audio_packets) == 0 + + +async def test_audio_packets_not_found(hass): + """Set up an audio stream, but no audio packets are found.""" + py_av = MockPyAv(audio=True) + + num_packets = PACKETS_TO_WAIT_FOR_AUDIO + 1 + packets = PacketSequence(num_packets) # Contains only video packets + + decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) + assert decoded_stream.finished + segments = decoded_stream.segments + assert len(segments) == int((num_packets - 1) * SEGMENTS_PER_PACKET) + assert len(decoded_stream.video_packets) == num_packets + assert len(decoded_stream.audio_packets) == 0 + + +async def test_audio_is_first_packet(hass): + """Set up an audio stream and audio packet is the first packet in the stream.""" + py_av = MockPyAv(audio=True) + + num_packets = PACKETS_TO_WAIT_FOR_AUDIO + 1 + packets = list(PacketSequence(num_packets)) + # Pair up an audio packet for each video packet + packets[0].stream = AUDIO_STREAM + packets[0].dts = packets[1].dts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + packets[0].pts = packets[1].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + packets[2].stream = AUDIO_STREAM + packets[2].dts = packets[3].dts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + packets[2].pts = packets[3].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + + decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) + assert decoded_stream.finished + segments = decoded_stream.segments + # The audio packets are segmented with the video packets + assert len(segments) == int((num_packets - 2 - 1) * SEGMENTS_PER_PACKET) + assert len(decoded_stream.video_packets) == num_packets - 2 + assert len(decoded_stream.audio_packets) == 1 + + +async def test_audio_packets_found(hass): + """Set up an audio stream and audio packets are found at the start of the stream.""" + py_av = MockPyAv(audio=True) + + num_packets = PACKETS_TO_WAIT_FOR_AUDIO + 1 + packets = list(PacketSequence(num_packets)) + packets[1].stream = AUDIO_STREAM + packets[1].dts = packets[0].dts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + packets[1].pts = packets[0].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + + decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) + assert decoded_stream.finished + segments = decoded_stream.segments + # The audio packet above is buffered with the video packet + assert len(segments) == int((num_packets - 1 - 1) * SEGMENTS_PER_PACKET) + assert len(decoded_stream.video_packets) == num_packets - 1 + assert len(decoded_stream.audio_packets) == 1 + + +async def test_pts_out_of_order(hass): + """Test pts can be out of order and still be valid.""" + + # Create a sequence of packets with some out of order pts + packets = list(PacketSequence(TEST_SEQUENCE_LENGTH)) + for i, _ in enumerate(packets): + if i % PACKETS_PER_SEGMENT == 1: + packets[i].pts = packets[i - 1].pts - 1 + packets[i].is_keyframe = False + + decoded_stream = await async_decode_stream(hass, iter(packets)) + assert decoded_stream.finished + segments = decoded_stream.segments + # Check number of segments + assert len(segments) == int((TEST_SEQUENCE_LENGTH - 1) * SEGMENTS_PER_PACKET) + # Check sequence numbers + assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) + # Check segment durations + assert all([s.duration == SEGMENT_DURATION for s in segments]) + assert len(decoded_stream.video_packets) == len(packets) + assert len(decoded_stream.audio_packets) == 0 From 83a75b02ea84fe34726c5215044ac9a185c36487 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 1 Feb 2021 17:55:16 +0100 Subject: [PATCH 0066/1818] Code quality improvements to UniFi integration (#45794) * Do less inside try statements * Replace controller id with config entry id since it doesn't serve a purpose anymore * Improve how controller connection state is communicated in the client and device tracker Remove the need to disable arguments-differ lint * Remove broad exception handling from config flow I'm not sure there ever was a reason for this more than to catch all exceptions * Replace site string with constant for SSDP title_placeholders * Unload platforms in the defacto way * Noone reads the method descriptions * Improve file descriptions --- homeassistant/components/unifi/__init__.py | 14 +--- homeassistant/components/unifi/config_flow.py | 67 ++++++++----------- homeassistant/components/unifi/const.py | 2 - homeassistant/components/unifi/controller.py | 53 ++++++++------- .../components/unifi/device_tracker.py | 24 ++++--- homeassistant/components/unifi/sensor.py | 6 +- homeassistant/components/unifi/switch.py | 7 +- tests/components/unifi/test_config_flow.py | 26 ------- tests/components/unifi/test_controller.py | 8 ++- 9 files changed, 89 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 439073497a2bf4..a9d39251838cb9 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -1,9 +1,8 @@ -"""Support for devices connected to UniFi POE.""" +"""Integration to UniFi controllers and its various features.""" from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from .config_flow import get_controller_id_from_config_entry from .const import ( ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN, @@ -82,21 +81,12 @@ async def async_load(self): @callback def get_data(self, config_entry): """Get data related to a specific controller.""" - controller_id = get_controller_id_from_config_entry(config_entry) - key = config_entry.entry_id - if controller_id in self.data: - key = controller_id - - data = self.data.get(key, {"wireless_devices": []}) + data = self.data.get(config_entry.entry_id, {"wireless_devices": []}) return set(data["wireless_devices"]) @callback def update_data(self, data, config_entry): """Update data and schedule to save to file.""" - controller_id = get_controller_id_from_config_entry(config_entry) - if controller_id in self.data: - self.data.pop(controller_id) - self.data[config_entry.entry_id] = {"wireless_devices": list(data)} self._store.async_delay_save(self._data_to_save, SAVE_DELAY) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 85fe55a4076b27..07b81621750fce 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -1,4 +1,10 @@ -"""Config flow for UniFi.""" +"""Config flow for UniFi. + +Provides user initiated configuration flow. +Discovery of controllers hosted on UDM and UDM Pro devices through SSDP. +Reauthentication when issue with credentials are reported. +Configuration of options through options flow. +""" import socket from urllib.parse import urlparse @@ -31,11 +37,9 @@ CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, - CONTROLLER_ID, DEFAULT_DPI_RESTRICTIONS, DEFAULT_POE_CLIENTS, DOMAIN as UNIFI_DOMAIN, - LOGGER, ) from .controller import get_controller from .errors import AuthenticationRequired, CannotConnect @@ -51,15 +55,6 @@ } -@callback -def get_controller_id_from_config_entry(config_entry): - """Return controller with a matching bridge id.""" - return CONTROLLER_ID.format( - host=config_entry.data[CONF_CONTROLLER][CONF_HOST], - site=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID], - ) - - class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): """Handle a UniFi config flow.""" @@ -86,19 +81,26 @@ async def async_step_user(self, user_input=None): if user_input is not None: - try: - self.config = { - CONF_HOST: user_input[CONF_HOST], - CONF_USERNAME: user_input[CONF_USERNAME], - CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_PORT: user_input.get(CONF_PORT), - CONF_VERIFY_SSL: user_input.get(CONF_VERIFY_SSL), - CONF_SITE_ID: DEFAULT_SITE_ID, - } + self.config = { + CONF_HOST: user_input[CONF_HOST], + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_PORT: user_input.get(CONF_PORT), + CONF_VERIFY_SSL: user_input.get(CONF_VERIFY_SSL), + CONF_SITE_ID: DEFAULT_SITE_ID, + } + try: controller = await get_controller(self.hass, **self.config) - sites = await controller.sites() + + except AuthenticationRequired: + errors["base"] = "faulty_credentials" + + except CannotConnect: + errors["base"] = "service_unavailable" + + else: self.sites = {site["name"]: site["desc"] for site in sites.values()} if self.reauth_config.get(CONF_SITE_ID) in self.sites: @@ -108,19 +110,6 @@ async def async_step_user(self, user_input=None): return await self.async_step_site() - except AuthenticationRequired: - errors["base"] = "faulty_credentials" - - except CannotConnect: - errors["base"] = "service_unavailable" - - except Exception: # pylint: disable=broad-except - LOGGER.error( - "Unknown error connecting with UniFi Controller at %s", - user_input[CONF_HOST], - ) - return self.async_abort(reason="unknown") - host = self.config.get(CONF_HOST) if not host and await async_discover_unifi(self.hass): host = "unifi" @@ -214,7 +203,7 @@ async def async_step_reauth(self, config_entry: dict): return await self.async_step_user() async def async_step_ssdp(self, discovery_info): - """Handle a discovered unifi device.""" + """Handle a discovered UniFi device.""" parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) model_description = discovery_info[ssdp.ATTR_UPNP_MODEL_DESCRIPTION] mac_address = format_mac(discovery_info[ssdp.ATTR_UPNP_SERIAL]) @@ -232,7 +221,7 @@ async def async_step_ssdp(self, discovery_info): # pylint: disable=no-member self.context["title_placeholders"] = { CONF_HOST: self.config[CONF_HOST], - CONF_SITE_ID: "default", + CONF_SITE_ID: DEFAULT_SITE_ID, } port = MODEL_PORTS.get(model_description) @@ -242,7 +231,7 @@ async def async_step_ssdp(self, discovery_info): return await self.async_step_user() def _host_already_configured(self, host): - """See if we already have a unifi entry matching the host.""" + """See if we already have a UniFi entry matching the host.""" for entry in self._async_current_entries(): if not entry.data or CONF_CONTROLLER not in entry.data: continue @@ -271,7 +260,7 @@ async def async_step_init(self, user_input=None): return await self.async_step_simple_options() async def async_step_simple_options(self, user_input=None): - """For simple Jack.""" + """For users without advanced settings enabled.""" if user_input is not None: self.options.update(user_input) return await self._update_options() diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index ba16612a903429..94e2fad35edde6 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -4,8 +4,6 @@ LOGGER = logging.getLogger(__package__) DOMAIN = "unifi" -CONTROLLER_ID = "{host}-{site}" - CONF_CONTROLLER = "controller" CONF_SITE_ID = "site" diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 11e02d60a3f261..bd55f4479fad43 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -51,7 +51,6 @@ CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, - CONTROLLER_ID, DEFAULT_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS, DEFAULT_DETECTION_TIME, @@ -109,9 +108,10 @@ def __init__(self, hass, config_entry): def load_config_entry_options(self): """Store attributes to avoid property call overhead since they are called frequently.""" - # Device tracker options options = self.config_entry.options + # Device tracker options + # Config entry option to not track clients. self.option_track_clients = options.get( CONF_TRACK_CLIENTS, DEFAULT_TRACK_CLIENTS @@ -157,11 +157,6 @@ def load_config_entry_options(self): CONF_ALLOW_UPTIME_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS ) - @property - def controller_id(self): - """Return the controller ID.""" - return CONTROLLER_ID.format(host=self.host, site=self.site) - @property def host(self): """Return the host of this controller.""" @@ -260,25 +255,25 @@ def async_unifi_signalling_callback(self, signal, data): @property def signal_reachable(self) -> str: """Integration specific event to signal a change in connection status.""" - return f"unifi-reachable-{self.controller_id}" + return f"unifi-reachable-{self.config_entry.entry_id}" @property - def signal_update(self): + def signal_update(self) -> str: """Event specific per UniFi entry to signal new data.""" - return f"unifi-update-{self.controller_id}" + return f"unifi-update-{self.config_entry.entry_id}" @property - def signal_remove(self): + def signal_remove(self) -> str: """Event specific per UniFi entry to signal removal of entities.""" - return f"unifi-remove-{self.controller_id}" + return f"unifi-remove-{self.config_entry.entry_id}" @property - def signal_options_update(self): + def signal_options_update(self) -> str: """Event specific per UniFi entry to signal new options.""" - return f"unifi-options-{self.controller_id}" + return f"unifi-options-{self.config_entry.entry_id}" @property - def signal_heartbeat_missed(self): + def signal_heartbeat_missed(self) -> str: """Event specific per UniFi device tracker to signal new heartbeat missed.""" return "unifi-heartbeat-missed" @@ -309,14 +304,7 @@ async def async_setup(self): await self.api.initialize() sites = await self.api.sites() - - for site in sites.values(): - if self.site == site["name"]: - self._site_name = site["desc"] - break - description = await self.api.site_description() - self._site_role = description[0]["site_role"] except CannotConnect as err: raise ConfigEntryNotReady from err @@ -331,6 +319,13 @@ async def async_setup(self): ) return False + for site in sites.values(): + if self.site == site["name"]: + self._site_name = site["desc"] + break + + self._site_role = description[0]["site_role"] + # Restore clients that is not a part of active clients list. entity_registry = await self.hass.helpers.entity_registry.async_get_registry() for entity in entity_registry.entities.values(): @@ -452,10 +447,18 @@ async def async_reset(self): """ self.api.stop_websocket() - for platform in SUPPORTED_PLATFORMS: - await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, platform + unload_ok = all( + await asyncio.gather( + *[ + self.hass.config_entries.async_forward_entry_unload( + self.config_entry, platform + ) + for platform in SUPPORTED_PLATFORMS + ] ) + ) + if not unload_ok: + return False for unsub_dispatcher in self.listeners: unsub_dispatcher() diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 6a4d986d5b2ac5..ac28f7475f6582 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -1,4 +1,4 @@ -"""Track devices using UniFi controllers.""" +"""Track both clients and devices using UniFi controllers.""" from datetime import timedelta from aiounifi.api import SOURCE_DATA, SOURCE_EVENT @@ -145,6 +145,7 @@ def __init__(self, client, controller): self.heartbeat_check = False self._is_connected = False + self._controller_connection_state_changed = False if client.last_seen: self._is_connected = ( @@ -175,14 +176,16 @@ async def async_will_remove_from_hass(self) -> None: @callback def async_signal_reachable_callback(self) -> None: """Call when controller connection state change.""" - self.async_update_callback(controller_state_change=True) + self._controller_connection_state_changed = True + super().async_signal_reachable_callback() - # pylint: disable=arguments-differ @callback - def async_update_callback(self, controller_state_change: bool = False) -> None: + def async_update_callback(self) -> None: """Update the clients state.""" - if controller_state_change: + if self._controller_connection_state_changed: + self._controller_connection_state_changed = False + if self.controller.available: self.schedule_update = True @@ -304,6 +307,7 @@ def __init__(self, device, controller): self.device = self._item self._is_connected = device.state == 1 + self._controller_connection_state_changed = False self.schedule_update = False async def async_added_to_hass(self) -> None: @@ -325,14 +329,16 @@ async def async_will_remove_from_hass(self) -> None: @callback def async_signal_reachable_callback(self) -> None: """Call when controller connection state change.""" - self.async_update_callback(controller_state_change=True) + self._controller_connection_state_changed = True + super().async_signal_reachable_callback() - # pylint: disable=arguments-differ @callback - def async_update_callback(self, controller_state_change: bool = False) -> None: + def async_update_callback(self) -> None: """Update the devices' state.""" - if controller_state_change: + if self._controller_connection_state_changed: + self._controller_connection_state_changed = False + if self.controller.available: if self._is_connected: self.schedule_update = True diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index c0b8cea09c2163..f78ec614da15b0 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -1,4 +1,8 @@ -"""Support for bandwidth sensors with UniFi clients.""" +"""Sensor platform for UniFi integration. + +Support for bandwidth sensors of network clients. +Support for uptime sensors of network clients. +""" from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 6aa42b0d2918e5..e596e0b1e2a119 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -1,4 +1,9 @@ -"""Support for devices connected to UniFi POE.""" +"""Switch platform for UniFi integration. + +Support for controlling power supply of clients which are powered over Ethernet (POE). +Support for controlling network access of clients selected in option flow. +Support for controlling deep packet inspection (DPI) restriction groups. +""" import logging from typing import Any diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 15220e68914faa..790e204c1dd4df 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -338,32 +338,6 @@ async def test_flow_fails_controller_unavailable(hass, aioclient_mock): assert result["errors"] == {"base": "service_unavailable"} -async def test_flow_fails_unknown_problem(hass, aioclient_mock): - """Test config flow.""" - result = await hass.config_entries.flow.async_init( - UNIFI_DOMAIN, context={"source": "user"} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - aioclient_mock.get("https://1.2.3.4:1234", status=302) - - with patch("aiounifi.Controller.login", side_effect=Exception): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_HOST: "1.2.3.4", - CONF_USERNAME: "username", - CONF_PASSWORD: "password", - CONF_PORT: 1234, - CONF_VERIFY_SSL: True, - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - - async def test_reauth_flow_update_configuration(hass, aioclient_mock): """Verify reauth flow can update controller configuration.""" controller = await setup_unifi_integration(hass) diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 6acd507eaad7ce..e484e041a883ba 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -201,9 +201,11 @@ async def test_controller_setup(hass): assert controller.mac is None - assert controller.signal_update == "unifi-update-1.2.3.4-site_id" - assert controller.signal_remove == "unifi-remove-1.2.3.4-site_id" - assert controller.signal_options_update == "unifi-options-1.2.3.4-site_id" + assert controller.signal_reachable == "unifi-reachable-1" + assert controller.signal_update == "unifi-update-1" + assert controller.signal_remove == "unifi-remove-1" + assert controller.signal_options_update == "unifi-options-1" + assert controller.signal_heartbeat_missed == "unifi-heartbeat-missed" async def test_controller_mac(hass): From 285bd3aa917943684ba4e1c1883d3348cb5f20ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Nenz=C3=A9n?= Date: Mon, 1 Feb 2021 18:12:56 +0100 Subject: [PATCH 0067/1818] Add support for Keg and Airlock to Plaato using polling API (#34760) Co-authored-by: J. Nick Koston --- .coveragerc | 6 +- homeassistant/components/plaato/__init__.py | 236 +++++++++++---- .../components/plaato/binary_sensor.py | 56 ++++ .../components/plaato/config_flow.py | 227 +++++++++++++- homeassistant/components/plaato/const.py | 26 +- homeassistant/components/plaato/entity.py | 103 +++++++ homeassistant/components/plaato/manifest.json | 6 +- homeassistant/components/plaato/sensor.py | 183 ++++------- homeassistant/components/plaato/strings.json | 45 ++- .../components/plaato/translations/en.json | 41 ++- requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/plaato/__init__.py | 1 + tests/components/plaato/test_config_flow.py | 286 ++++++++++++++++++ 14 files changed, 1022 insertions(+), 200 deletions(-) create mode 100644 homeassistant/components/plaato/binary_sensor.py create mode 100644 homeassistant/components/plaato/entity.py create mode 100644 tests/components/plaato/__init__.py create mode 100644 tests/components/plaato/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 0609051f19635b..3a7f33f6050b91 100644 --- a/.coveragerc +++ b/.coveragerc @@ -702,7 +702,11 @@ omit = homeassistant/components/ping/device_tracker.py homeassistant/components/pioneer/media_player.py homeassistant/components/pjlink/media_player.py - homeassistant/components/plaato/* + homeassistant/components/plaato/__init__.py + homeassistant/components/plaato/binary_sensor.py + homeassistant/components/plaato/const.py + homeassistant/components/plaato/entity.py + homeassistant/components/plaato/sensor.py homeassistant/components/plex/media_player.py homeassistant/components/plum_lightpad/light.py homeassistant/components/pocketcasts/sensor.py diff --git a/homeassistant/components/plaato/__init__.py b/homeassistant/components/plaato/__init__.py index b365c7e0081bed..2cf97d5fd9a5a5 100644 --- a/homeassistant/components/plaato/__init__.py +++ b/homeassistant/components/plaato/__init__.py @@ -1,11 +1,34 @@ -"""Support for Plaato Airlock.""" +"""Support for Plaato devices.""" + +import asyncio +from datetime import timedelta import logging from aiohttp import web +from pyplaato.models.airlock import PlaatoAirlock +from pyplaato.plaato import ( + ATTR_ABV, + ATTR_BATCH_VOLUME, + ATTR_BPM, + ATTR_BUBBLES, + ATTR_CO2_VOLUME, + ATTR_DEVICE_ID, + ATTR_DEVICE_NAME, + ATTR_OG, + ATTR_SG, + ATTR_TEMP, + ATTR_TEMP_UNIT, + ATTR_VOLUME_UNIT, + Plaato, + PlaatoDeviceType, +) import voluptuous as vol from homeassistant.components.sensor import DOMAIN as SENSOR +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + CONF_SCAN_INTERVAL, + CONF_TOKEN, CONF_WEBHOOK_ID, HTTP_OK, TEMP_CELSIUS, @@ -13,31 +36,33 @@ VOLUME_GALLONS, VOLUME_LITERS, ) +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN +from .const import ( + CONF_DEVICE_NAME, + CONF_DEVICE_TYPE, + CONF_USE_WEBHOOK, + COORDINATOR, + DEFAULT_SCAN_INTERVAL, + DEVICE, + DEVICE_ID, + DEVICE_NAME, + DEVICE_TYPE, + DOMAIN, + PLATFORMS, + SENSOR_DATA, + UNDO_UPDATE_LISTENER, +) _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["webhook"] -PLAATO_DEVICE_SENSORS = "sensors" -PLAATO_DEVICE_ATTRS = "attrs" - -ATTR_DEVICE_ID = "device_id" -ATTR_DEVICE_NAME = "device_name" -ATTR_TEMP_UNIT = "temp_unit" -ATTR_VOLUME_UNIT = "volume_unit" -ATTR_BPM = "bpm" -ATTR_TEMP = "temp" -ATTR_SG = "sg" -ATTR_OG = "og" -ATTR_BUBBLES = "bubbles" -ATTR_ABV = "abv" -ATTR_CO2_VOLUME = "co2_volume" -ATTR_BATCH_VOLUME = "batch_volume" - SENSOR_UPDATE = f"{DOMAIN}_sensor_update" SENSOR_DATA_KEY = f"{DOMAIN}.{SENSOR}" @@ -60,31 +85,124 @@ ) -async def async_setup(hass, hass_config): +async def async_setup(hass: HomeAssistant, config: dict): """Set up the Plaato component.""" + hass.data.setdefault(DOMAIN, {}) return True -async def async_setup_entry(hass, entry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Configure based on config entry.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - webhook_id = entry.data[CONF_WEBHOOK_ID] - hass.components.webhook.async_register(DOMAIN, "Plaato", webhook_id, handle_webhook) + use_webhook = entry.data[CONF_USE_WEBHOOK] + + if use_webhook: + async_setup_webhook(hass, entry) + else: + await async_setup_coordinator(hass, entry) - hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, SENSOR)) + for platform in PLATFORMS: + if entry.options.get(platform, True): + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) return True -async def async_unload_entry(hass, entry): +@callback +def async_setup_webhook(hass: HomeAssistant, entry: ConfigEntry): + """Init webhook based on config entry.""" + webhook_id = entry.data[CONF_WEBHOOK_ID] + device_name = entry.data[CONF_DEVICE_NAME] + + _set_entry_data(entry, hass) + + hass.components.webhook.async_register( + DOMAIN, f"{DOMAIN}.{device_name}", webhook_id, handle_webhook + ) + + +async def async_setup_coordinator(hass: HomeAssistant, entry: ConfigEntry): + """Init auth token based on config entry.""" + auth_token = entry.data[CONF_TOKEN] + device_type = entry.data[CONF_DEVICE_TYPE] + + if entry.options.get(CONF_SCAN_INTERVAL): + update_interval = timedelta(minutes=entry.options[CONF_SCAN_INTERVAL]) + else: + update_interval = timedelta(minutes=DEFAULT_SCAN_INTERVAL) + + coordinator = PlaatoCoordinator(hass, auth_token, device_type, update_interval) + await coordinator.async_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + _set_entry_data(entry, hass, coordinator, auth_token) + + for platform in PLATFORMS: + if entry.options.get(platform, True): + coordinator.platforms.append(platform) + + +def _set_entry_data(entry, hass, coordinator=None, device_id=None): + device = { + DEVICE_NAME: entry.data[CONF_DEVICE_NAME], + DEVICE_TYPE: entry.data[CONF_DEVICE_TYPE], + DEVICE_ID: device_id, + } + + hass.data[DOMAIN][entry.entry_id] = { + COORDINATOR: coordinator, + DEVICE: device, + SENSOR_DATA: None, + UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), + } + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) - hass.data[SENSOR_DATA_KEY]() + use_webhook = entry.data[CONF_USE_WEBHOOK] + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() - await hass.config_entries.async_forward_entry_unload(entry, SENSOR) - return True + if use_webhook: + return await async_unload_webhook(hass, entry) + + return await async_unload_coordinator(hass, entry) + + +async def async_unload_webhook(hass: HomeAssistant, entry: ConfigEntry): + """Unload webhook based entry.""" + if entry.data[CONF_WEBHOOK_ID] is not None: + hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) + return await async_unload_platforms(hass, entry, PLATFORMS) + + +async def async_unload_coordinator(hass: HomeAssistant, entry: ConfigEntry): + """Unload auth token based entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] + return await async_unload_platforms(hass, entry, coordinator.platforms) + + +async def async_unload_platforms(hass: HomeAssistant, entry: ConfigEntry, platforms): + """Unload platforms.""" + unloaded = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms + ] + ) + ) + if unloaded: + hass.data[DOMAIN].pop(entry.entry_id) + + return unloaded + + +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) async def handle_webhook(hass, webhook_id, request): @@ -96,31 +214,9 @@ async def handle_webhook(hass, webhook_id, request): return device_id = _device_id(data) + sensor_data = PlaatoAirlock.from_web_hook(data) - attrs = { - ATTR_DEVICE_NAME: data.get(ATTR_DEVICE_NAME), - ATTR_DEVICE_ID: data.get(ATTR_DEVICE_ID), - ATTR_TEMP_UNIT: data.get(ATTR_TEMP_UNIT), - ATTR_VOLUME_UNIT: data.get(ATTR_VOLUME_UNIT), - } - - sensors = { - ATTR_TEMP: data.get(ATTR_TEMP), - ATTR_BPM: data.get(ATTR_BPM), - ATTR_SG: data.get(ATTR_SG), - ATTR_OG: data.get(ATTR_OG), - ATTR_ABV: data.get(ATTR_ABV), - ATTR_CO2_VOLUME: data.get(ATTR_CO2_VOLUME), - ATTR_BATCH_VOLUME: data.get(ATTR_BATCH_VOLUME), - ATTR_BUBBLES: data.get(ATTR_BUBBLES), - } - - hass.data[DOMAIN][device_id] = { - PLAATO_DEVICE_ATTRS: attrs, - PLAATO_DEVICE_SENSORS: sensors, - } - - async_dispatcher_send(hass, SENSOR_UPDATE, device_id) + async_dispatcher_send(hass, SENSOR_UPDATE, *(device_id, sensor_data)) return web.Response(text=f"Saving status for {device_id}", status=HTTP_OK) @@ -128,3 +224,35 @@ async def handle_webhook(hass, webhook_id, request): def _device_id(data): """Return name of device sensor.""" return f"{data.get(ATTR_DEVICE_NAME)}_{data.get(ATTR_DEVICE_ID)}" + + +class PlaatoCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the API.""" + + def __init__( + self, + hass, + auth_token, + device_type: PlaatoDeviceType, + update_interval: timedelta, + ): + """Initialize.""" + self.api = Plaato(auth_token=auth_token) + self.hass = hass + self.device_type = device_type + self.platforms = [] + + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=update_interval, + ) + + async def _async_update_data(self): + """Update data via library.""" + data = await self.api.get_data( + session=aiohttp_client.async_get_clientsession(self.hass), + device_type=self.device_type, + ) + return data diff --git a/homeassistant/components/plaato/binary_sensor.py b/homeassistant/components/plaato/binary_sensor.py new file mode 100644 index 00000000000000..0ee61b7668bd0a --- /dev/null +++ b/homeassistant/components/plaato/binary_sensor.py @@ -0,0 +1,56 @@ +"""Support for Plaato Airlock sensors.""" + +import logging + +from pyplaato.plaato import PlaatoKeg + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) + +from .const import CONF_USE_WEBHOOK, COORDINATOR, DOMAIN +from .entity import PlaatoEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Plaato from a config entry.""" + + if config_entry.data[CONF_USE_WEBHOOK]: + return False + + coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + async_add_entities( + PlaatoBinarySensor( + hass.data[DOMAIN][config_entry.entry_id], + sensor_type, + coordinator, + ) + for sensor_type in coordinator.data.binary_sensors.keys() + ) + + return True + + +class PlaatoBinarySensor(PlaatoEntity, BinarySensorEntity): + """Representation of a Binary Sensor.""" + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + if self._coordinator is not None: + return self._coordinator.data.binary_sensors.get(self._sensor_type) + return False + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + if self._coordinator is None: + return None + if self._sensor_type is PlaatoKeg.Pins.LEAK_DETECTION: + return DEVICE_CLASS_PROBLEM + if self._sensor_type is PlaatoKeg.Pins.POURING: + return DEVICE_CLASS_OPENING diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index 3c616c822fb984..290776f47c1164 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -1,10 +1,223 @@ -"""Config flow for GPSLogger.""" -from homeassistant.helpers import config_entry_flow +"""Config flow for Plaato.""" +import logging -from .const import DOMAIN +from pyplaato.plaato import PlaatoDeviceType +import voluptuous as vol -config_entry_flow.register_webhook_flow( - DOMAIN, - "Webhook", - {"docs_url": "https://www.home-assistant.io/integrations/plaato/"}, +from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_WEBHOOK_ID +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv + +from .const import ( + CONF_CLOUDHOOK, + CONF_DEVICE_NAME, + CONF_DEVICE_TYPE, + CONF_USE_WEBHOOK, + DEFAULT_SCAN_INTERVAL, + DOCS_URL, + PLACEHOLDER_DEVICE_NAME, + PLACEHOLDER_DEVICE_TYPE, + PLACEHOLDER_DOCS_URL, + PLACEHOLDER_WEBHOOK_URL, ) +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__package__) + + +class PlaatoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handles a Plaato config flow.""" + + VERSION = 2 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize.""" + self._init_info = {} + + async def async_step_user(self, user_input=None): + """Handle user step.""" + + if user_input is not None: + self._init_info[CONF_DEVICE_TYPE] = PlaatoDeviceType( + user_input[CONF_DEVICE_TYPE] + ) + self._init_info[CONF_DEVICE_NAME] = user_input[CONF_DEVICE_NAME] + + return await self.async_step_api_method() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_DEVICE_NAME, + default=self._init_info.get(CONF_DEVICE_NAME, None), + ): str, + vol.Required( + CONF_DEVICE_TYPE, + default=self._init_info.get(CONF_DEVICE_TYPE, None), + ): vol.In(list(PlaatoDeviceType)), + } + ), + ) + + async def async_step_api_method(self, user_input=None): + """Handle device type step.""" + + device_type = self._init_info[CONF_DEVICE_TYPE] + + if user_input is not None: + token = user_input.get(CONF_TOKEN, None) + use_webhook = user_input.get(CONF_USE_WEBHOOK, False) + + if not token and not use_webhook: + errors = {"base": PlaatoConfigFlow._get_error(device_type)} + return await self._show_api_method_form(device_type, errors) + + self._init_info[CONF_USE_WEBHOOK] = use_webhook + self._init_info[CONF_TOKEN] = token + return await self.async_step_webhook() + + return await self._show_api_method_form(device_type) + + async def async_step_webhook(self, user_input=None): + """Validate config step.""" + + use_webhook = self._init_info[CONF_USE_WEBHOOK] + + if use_webhook and user_input is None: + webhook_id, webhook_url, cloudhook = await self._get_webhook_id() + self._init_info[CONF_WEBHOOK_ID] = webhook_id + self._init_info[CONF_CLOUDHOOK] = cloudhook + + return self.async_show_form( + step_id="webhook", + description_placeholders={ + PLACEHOLDER_WEBHOOK_URL: webhook_url, + PLACEHOLDER_DOCS_URL: DOCS_URL, + }, + ) + + return await self._async_create_entry() + + async def _async_create_entry(self): + """Create the entry step.""" + + webhook_id = self._init_info.get(CONF_WEBHOOK_ID, None) + auth_token = self._init_info[CONF_TOKEN] + device_name = self._init_info[CONF_DEVICE_NAME] + device_type = self._init_info[CONF_DEVICE_TYPE] + + unique_id = auth_token if auth_token else webhook_id + + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=device_type.name, + data=self._init_info, + description_placeholders={ + PLACEHOLDER_DEVICE_TYPE: device_type.name, + PLACEHOLDER_DEVICE_NAME: device_name, + }, + ) + + async def _show_api_method_form( + self, device_type: PlaatoDeviceType, errors: dict = None + ): + data_scheme = vol.Schema({vol.Optional(CONF_TOKEN, default=""): str}) + + if device_type == PlaatoDeviceType.Airlock: + data_scheme = data_scheme.extend( + {vol.Optional(CONF_USE_WEBHOOK, default=False): bool} + ) + + return self.async_show_form( + step_id="api_method", + data_schema=data_scheme, + errors=errors, + description_placeholders={PLACEHOLDER_DEVICE_TYPE: device_type.name}, + ) + + async def _get_webhook_id(self): + """Generate webhook ID.""" + webhook_id = self.hass.components.webhook.async_generate_id() + if self.hass.components.cloud.async_active_subscription(): + webhook_url = await self.hass.components.cloud.async_create_cloudhook( + webhook_id + ) + cloudhook = True + else: + webhook_url = self.hass.components.webhook.async_generate_url(webhook_id) + cloudhook = False + + return webhook_id, webhook_url, cloudhook + + @staticmethod + def _get_error(device_type: PlaatoDeviceType): + if device_type == PlaatoDeviceType.Airlock: + return "no_api_method" + return "no_auth_token" + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return PlaatoOptionsFlowHandler(config_entry) + + +class PlaatoOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Plaato options.""" + + def __init__(self, config_entry: ConfigEntry): + """Initialize domain options flow.""" + super().__init__() + + self._config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + use_webhook = self._config_entry.data.get(CONF_USE_WEBHOOK, False) + if use_webhook: + return await self.async_step_webhook() + + return await self.async_step_user() + + async def async_step_user(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Optional( + CONF_SCAN_INTERVAL, + default=self._config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ), + ): cv.positive_int + } + ), + ) + + async def async_step_webhook(self, user_input=None): + """Manage the options for webhook device.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + webhook_id = self._config_entry.data.get(CONF_WEBHOOK_ID, None) + webhook_url = ( + "" + if webhook_id is None + else self.hass.components.webhook.async_generate_url(webhook_id) + ) + + return self.async_show_form( + step_id="webhook", + description_placeholders={PLACEHOLDER_WEBHOOK_URL: webhook_url}, + ) diff --git a/homeassistant/components/plaato/const.py b/homeassistant/components/plaato/const.py index cbe8fcd2b6dc43..f50eaaac0ed886 100644 --- a/homeassistant/components/plaato/const.py +++ b/homeassistant/components/plaato/const.py @@ -1,3 +1,27 @@ -"""Const for GPSLogger.""" +"""Const for Plaato.""" +from datetime import timedelta DOMAIN = "plaato" +PLAATO_DEVICE_SENSORS = "sensors" +PLAATO_DEVICE_ATTRS = "attrs" +SENSOR_SIGNAL = f"{DOMAIN}_%s_%s" + +CONF_USE_WEBHOOK = "use_webhook" +CONF_DEVICE_TYPE = "device_type" +CONF_DEVICE_NAME = "device_name" +CONF_CLOUDHOOK = "cloudhook" +PLACEHOLDER_WEBHOOK_URL = "webhook_url" +PLACEHOLDER_DOCS_URL = "docs_url" +PLACEHOLDER_DEVICE_TYPE = "device_type" +PLACEHOLDER_DEVICE_NAME = "device_name" +DOCS_URL = "https://www.home-assistant.io/integrations/plaato/" +PLATFORMS = ["sensor", "binary_sensor"] +SENSOR_DATA = "sensor_data" +COORDINATOR = "coordinator" +DEVICE = "device" +DEVICE_NAME = "device_name" +DEVICE_TYPE = "device_type" +DEVICE_ID = "device_id" +UNDO_UPDATE_LISTENER = "undo_update_listener" +DEFAULT_SCAN_INTERVAL = 5 +MIN_UPDATE_INTERVAL = timedelta(minutes=1) diff --git a/homeassistant/components/plaato/entity.py b/homeassistant/components/plaato/entity.py new file mode 100644 index 00000000000000..6f72c3419a4626 --- /dev/null +++ b/homeassistant/components/plaato/entity.py @@ -0,0 +1,103 @@ +"""PlaatoEntity class.""" +from pyplaato.models.device import PlaatoDevice + +from homeassistant.helpers import entity + +from .const import ( + DEVICE, + DEVICE_ID, + DEVICE_NAME, + DEVICE_TYPE, + DOMAIN, + SENSOR_DATA, + SENSOR_SIGNAL, +) + + +class PlaatoEntity(entity.Entity): + """Representation of a Plaato Entity.""" + + def __init__(self, data, sensor_type, coordinator=None): + """Initialize the sensor.""" + self._coordinator = coordinator + self._entry_data = data + self._sensor_type = sensor_type + self._device_id = data[DEVICE][DEVICE_ID] + self._device_type = data[DEVICE][DEVICE_TYPE] + self._device_name = data[DEVICE][DEVICE_NAME] + self._state = 0 + + @property + def _attributes(self) -> dict: + return PlaatoEntity._to_snake_case(self._sensor_data.attributes) + + @property + def _sensor_name(self) -> str: + return self._sensor_data.get_sensor_name(self._sensor_type) + + @property + def _sensor_data(self) -> PlaatoDevice: + if self._coordinator: + return self._coordinator.data + return self._entry_data[SENSOR_DATA] + + @property + def name(self): + """Return the name of the sensor.""" + return f"{DOMAIN} {self._device_type} {self._device_name} {self._sensor_name}".title() + + @property + def unique_id(self): + """Return the unique ID of this sensor.""" + return f"{self._device_id}_{self._sensor_type}" + + @property + def device_info(self): + """Get device info.""" + device_info = { + "identifiers": {(DOMAIN, self._device_id)}, + "name": self._device_name, + "manufacturer": "Plaato", + "model": self._device_type, + } + + if self._sensor_data.firmware_version != "": + device_info["sw_version"] = self._sensor_data.firmware_version + + return device_info + + @property + def device_state_attributes(self): + """Return the state attributes of the monitored installation.""" + if self._attributes is not None: + return self._attributes + + @property + def available(self): + """Return if sensor is available.""" + if self._coordinator is not None: + return self._coordinator.last_update_success + return True + + @property + def should_poll(self): + """Return the polling state.""" + return False + + async def async_added_to_hass(self): + """When entity is added to hass.""" + if self._coordinator is not None: + self.async_on_remove( + self._coordinator.async_add_listener(self.async_write_ha_state) + ) + else: + self.async_on_remove( + self.hass.helpers.dispatcher.async_dispatcher_connect( + SENSOR_SIGNAL % (self._device_id, self._sensor_type), + self.async_write_ha_state, + ) + ) + + @staticmethod + def _to_snake_case(dictionary: dict): + return {k.lower().replace(" ", "_"): v for k, v in dictionary.items()} diff --git a/homeassistant/components/plaato/manifest.json b/homeassistant/components/plaato/manifest.json index 29e104b13ed394..e3291e5a229d24 100644 --- a/homeassistant/components/plaato/manifest.json +++ b/homeassistant/components/plaato/manifest.json @@ -1,8 +1,10 @@ { "domain": "plaato", - "name": "Plaato Airlock", + "name": "Plaato", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plaato", "dependencies": ["webhook"], - "codeowners": ["@JohNan"] + "after_dependencies": ["cloud"], + "codeowners": ["@JohNan"], + "requirements": ["pyplaato==0.0.15"] } diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index 3f8034698fd843..3f5e467f504a2d 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -1,28 +1,29 @@ """Support for Plaato Airlock sensors.""" import logging +from typing import Optional -from homeassistant.const import PERCENTAGE +from pyplaato.models.device import PlaatoDevice +from pyplaato.plaato import PlaatoKeg + +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) -from homeassistant.helpers.entity import Entity - -from . import ( - ATTR_ABV, - ATTR_BATCH_VOLUME, - ATTR_BPM, - ATTR_CO2_VOLUME, - ATTR_TEMP, - ATTR_TEMP_UNIT, - ATTR_VOLUME_UNIT, - DOMAIN as PLAATO_DOMAIN, - PLAATO_DEVICE_ATTRS, - PLAATO_DEVICE_SENSORS, - SENSOR_DATA_KEY, - SENSOR_UPDATE, + +from . import ATTR_TEMP, SENSOR_UPDATE +from ...core import callback +from .const import ( + CONF_USE_WEBHOOK, + COORDINATOR, + DEVICE, + DEVICE_ID, + DOMAIN, + SENSOR_DATA, + SENSOR_SIGNAL, ) +from .entity import PlaatoEntity _LOGGER = logging.getLogger(__name__) @@ -31,134 +32,58 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """Set up the Plaato sensor.""" -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry(hass, entry, async_add_entities): """Set up Plaato from a config entry.""" - devices = {} - - def get_device(device_id): - """Get a device.""" - return hass.data[PLAATO_DOMAIN].get(device_id, False) - - def get_device_sensors(device_id): - """Get device sensors.""" - return hass.data[PLAATO_DOMAIN].get(device_id).get(PLAATO_DEVICE_SENSORS) + entry_data = hass.data[DOMAIN][entry.entry_id] - async def _update_sensor(device_id): + @callback + async def _async_update_from_webhook(device_id, sensor_data: PlaatoDevice): """Update/Create the sensors.""" - if device_id not in devices and get_device(device_id): - entities = [] - sensors = get_device_sensors(device_id) - - for sensor_type in sensors: - entities.append(PlaatoSensor(device_id, sensor_type)) - - devices[device_id] = entities - - async_add_entities(entities, True) + entry_data[SENSOR_DATA] = sensor_data + + if device_id != entry_data[DEVICE][DEVICE_ID]: + entry_data[DEVICE][DEVICE_ID] = device_id + async_add_entities( + [ + PlaatoSensor(entry_data, sensor_type) + for sensor_type in sensor_data.sensors + ] + ) else: - for entity in devices[device_id]: - async_dispatcher_send(hass, f"{PLAATO_DOMAIN}_{entity.unique_id}") - - hass.data[SENSOR_DATA_KEY] = async_dispatcher_connect( - hass, SENSOR_UPDATE, _update_sensor - ) + for sensor_type in sensor_data.sensors: + async_dispatcher_send(hass, SENSOR_SIGNAL % (device_id, sensor_type)) + + if entry.data[CONF_USE_WEBHOOK]: + async_dispatcher_connect(hass, SENSOR_UPDATE, _async_update_from_webhook) + else: + coordinator = entry_data[COORDINATOR] + async_add_entities( + PlaatoSensor(entry_data, sensor_type, coordinator) + for sensor_type in coordinator.data.sensors.keys() + ) return True -class PlaatoSensor(Entity): - """Representation of a Sensor.""" - - def __init__(self, device_id, sensor_type): - """Initialize the sensor.""" - self._device_id = device_id - self._type = sensor_type - self._state = 0 - self._name = f"{device_id} {sensor_type}" - self._attributes = None - - @property - def name(self): - """Return the name of the sensor.""" - return f"{PLAATO_DOMAIN} {self._name}" +class PlaatoSensor(PlaatoEntity): + """Representation of a Plaato Sensor.""" @property - def unique_id(self): - """Return the unique ID of this sensor.""" - return f"{self._device_id}_{self._type}" - - @property - def device_info(self): - """Get device info.""" - return { - "identifiers": {(PLAATO_DOMAIN, self._device_id)}, - "name": self._device_id, - "manufacturer": "Plaato", - "model": "Airlock", - } - - def get_sensors(self): - """Get device sensors.""" - return ( - self.hass.data[PLAATO_DOMAIN] - .get(self._device_id) - .get(PLAATO_DEVICE_SENSORS, False) - ) - - def get_sensors_unit_of_measurement(self, sensor_type): - """Get unit of measurement for sensor of type.""" - return ( - self.hass.data[PLAATO_DOMAIN] - .get(self._device_id) - .get(PLAATO_DEVICE_ATTRS, []) - .get(sensor_type, "") - ) + def device_class(self) -> Optional[str]: + """Return the class of this device, from component DEVICE_CLASSES.""" + if self._coordinator is not None: + if self._sensor_type == PlaatoKeg.Pins.TEMPERATURE: + return DEVICE_CLASS_TEMPERATURE + if self._sensor_type == ATTR_TEMP: + return DEVICE_CLASS_TEMPERATURE + return None @property def state(self): """Return the state of the sensor.""" - sensors = self.get_sensors() - if sensors is False: - _LOGGER.debug("Device with name %s has no sensors", self.name) - return 0 - - if self._type == ATTR_ABV: - return round(sensors.get(self._type), 2) - if self._type == ATTR_TEMP: - return round(sensors.get(self._type), 1) - if self._type == ATTR_CO2_VOLUME: - return round(sensors.get(self._type), 2) - return sensors.get(self._type) - - @property - def device_state_attributes(self): - """Return the state attributes of the monitored installation.""" - if self._attributes is not None: - return self._attributes + return self._sensor_data.sensors.get(self._sensor_type) @property def unit_of_measurement(self): """Return the unit of measurement.""" - if self._type == ATTR_TEMP: - return self.get_sensors_unit_of_measurement(ATTR_TEMP_UNIT) - if self._type == ATTR_BATCH_VOLUME or self._type == ATTR_CO2_VOLUME: - return self.get_sensors_unit_of_measurement(ATTR_VOLUME_UNIT) - if self._type == ATTR_BPM: - return "bpm" - if self._type == ATTR_ABV: - return PERCENTAGE - - return "" - - @property - def should_poll(self): - """Return the polling state.""" - return False - - async def async_added_to_hass(self): - """Register callbacks.""" - self.async_on_remove( - self.hass.helpers.dispatcher.async_dispatcher_connect( - f"{PLAATO_DOMAIN}_{self.unique_id}", self.async_write_ha_state - ) - ) + return self._sensor_data.get_unit_of_measurement(self._sensor_type) diff --git a/homeassistant/components/plaato/strings.json b/homeassistant/components/plaato/strings.json index 087cee136833f2..852ecc88ddec8b 100644 --- a/homeassistant/components/plaato/strings.json +++ b/homeassistant/components/plaato/strings.json @@ -2,16 +2,53 @@ "config": { "step": { "user": { - "title": "Set up the Plaato Webhook", - "description": "[%key:common::config_flow::description::confirm_setup%]" + "title": "Set up the Plaato devices", + "description": "[%key:common::config_flow::description::confirm_setup%]", + "data": { + "device_name": "Name your device", + "device_type": "Type of Plaato device" + } + }, + "api_method": { + "title": "Select API method", + "description": "To be able to query the API an `auth_token` is required which can be obtained by following [these](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instructions\n\n Selected device: **{device_type}** \n\nIf you rather use the built in webhook method (Airlock only) please check the box below and leave Auth Token blank", + "data": { + "use_webhook": "Use webhook", + "token": "Paste Auth Token here" + } + }, + "webhook": { + "title": "Webhook to use", + "description": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." } }, + "error": { + "invalid_webhook_device": "You have selected a device that doesn't not support sending data to a webhook. It is only available for the Airlock", + "no_auth_token": "You need to add an auth token", + "no_api_method": "You need to add an auth token or select webhook" + }, "abort": { "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]" + "webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" }, "create_entry": { - "default": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." + "default": "Your Plaato {device_type} with name **{device_name}** was successfully setup!" + } + }, + "options": { + "step": { + "webhook": { + "title": "Options for Plaato Airlock", + "description": "Webhook info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n" + }, + "user": { + "title": "Options for Plaato", + "description": "Set the update interval (minutes)", + "data": { + "update_interval": "Update interval (minutes)" + } + } } } } diff --git a/homeassistant/components/plaato/translations/en.json b/homeassistant/components/plaato/translations/en.json index 6f25c15583c2b8..64d41d0091ecd4 100644 --- a/homeassistant/components/plaato/translations/en.json +++ b/homeassistant/components/plaato/translations/en.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "Account is already configured", "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." }, "create_entry": { - "default": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." + "default": "Your Plaato {device_type} with name **{device_name}** was successfully setup!" + }, + "error": { + "invalid_webhook_device": "You have selected a device that doesn't not support sending data to a webhook. It is only available for the Airlock", + "no_api_method": "You need to add an auth token or select webhook", + "no_auth_token": "You need to add an auth token" }, "step": { + "api_method": { + "data": { + "token": "Paste Auth Token here", + "use_webhook": "Use webhook" + }, + "description": "To be able to query the API an `auth_token` is required which can be obtained by following [these](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instructions\n\n Selected device: **{device_type}** \n\nIf you rather use the built in webhook method (Airlock only) please check the box below and leave Auth Token blank", + "title": "Select API method" + }, "user": { + "data": { + "device_name": "Name your device", + "device_type": "Type of Plaato device" + }, "description": "Do you want to start set up?", - "title": "Set up the Plaato Webhook" + "title": "Set up the Plaato devices" + }, + "webhook": { + "description": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.", + "title": "Webhook to use" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Update interval (minutes)" + }, + "description": "Set the update interval (minutes)", + "title": "Options for Plaato" + }, + "webhook": { + "description": "Webhook info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n", + "title": "Options for Plaato Airlock" } } } diff --git a/requirements_all.txt b/requirements_all.txt index 804b0495297855..c192883c95fcca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1615,6 +1615,9 @@ pypck==0.7.9 # homeassistant.components.pjlink pypjlink2==1.2.1 +# homeassistant.components.plaato +pyplaato==0.0.15 + # homeassistant.components.point pypoint==2.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f855ab4bacc4f9..5a30ae13e8c992 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -842,6 +842,9 @@ pyowm==3.1.1 # homeassistant.components.onewire pyownet==0.10.0.post1 +# homeassistant.components.plaato +pyplaato==0.0.15 + # homeassistant.components.point pypoint==2.0.0 diff --git a/tests/components/plaato/__init__.py b/tests/components/plaato/__init__.py new file mode 100644 index 00000000000000..dac4d341790c62 --- /dev/null +++ b/tests/components/plaato/__init__.py @@ -0,0 +1 @@ +"""Tests for the Plaato integration.""" diff --git a/tests/components/plaato/test_config_flow.py b/tests/components/plaato/test_config_flow.py new file mode 100644 index 00000000000000..10562b6aa60b33 --- /dev/null +++ b/tests/components/plaato/test_config_flow.py @@ -0,0 +1,286 @@ +"""Test the Plaato config flow.""" +from unittest.mock import patch + +from pyplaato.models.device import PlaatoDeviceType +import pytest + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.plaato.const import ( + CONF_DEVICE_NAME, + CONF_DEVICE_TYPE, + CONF_USE_WEBHOOK, + DOMAIN, +) +from homeassistant.const import CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_WEBHOOK_ID +from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM + +from tests.common import MockConfigEntry + +BASE_URL = "http://example.com" +WEBHOOK_ID = "webhook_id" +UNIQUE_ID = "plaato_unique_id" + + +@pytest.fixture(name="webhook_id") +def mock_webhook_id(): + """Mock webhook_id.""" + with patch( + "homeassistant.components.webhook.async_generate_id", return_value=WEBHOOK_ID + ), patch( + "homeassistant.components.webhook.async_generate_url", return_value="hook_id" + ): + yield + + +async def test_show_config_form(hass): + """Test show configuration form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + +async def test_show_config_form_device_type_airlock(hass): + """Test show configuration form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data={ + CONF_DEVICE_TYPE: PlaatoDeviceType.Airlock, + CONF_DEVICE_NAME: "device_name", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + assert result["data_schema"].schema.get(CONF_TOKEN) == str + assert result["data_schema"].schema.get(CONF_USE_WEBHOOK) == bool + + +async def test_show_config_form_device_type_keg(hass): + """Test show configuration form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data={CONF_DEVICE_TYPE: PlaatoDeviceType.Keg, CONF_DEVICE_NAME: "device_name"}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + assert result["data_schema"].schema.get(CONF_TOKEN) == str + assert result["data_schema"].schema.get(CONF_USE_WEBHOOK) is None + + +async def test_show_config_form_validate_webhook(hass, webhook_id): + """Test show configuration form.""" + + 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" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_DEVICE_TYPE: PlaatoDeviceType.Airlock, + CONF_DEVICE_NAME: "device_name", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + + async def return_async_value(val): + return val + + hass.config.components.add("cloud") + with patch( + "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value=return_async_value("https://hooks.nabu.casa/ABCD"), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_TOKEN: "", + CONF_USE_WEBHOOK: True, + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "webhook" + + +async def test_show_config_form_validate_token(hass): + """Test show configuration form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_DEVICE_TYPE: PlaatoDeviceType.Keg, + CONF_DEVICE_NAME: "device_name", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + + with patch("homeassistant.components.plaato.async_setup_entry", return_value=True): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: "valid_token"} + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == PlaatoDeviceType.Keg.name + assert result["data"] == { + CONF_USE_WEBHOOK: False, + CONF_TOKEN: "valid_token", + CONF_DEVICE_TYPE: PlaatoDeviceType.Keg, + CONF_DEVICE_NAME: "device_name", + } + + +async def test_show_config_form_no_cloud_webhook(hass, webhook_id): + """Test show configuration form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_DEVICE_TYPE: PlaatoDeviceType.Airlock, + CONF_DEVICE_NAME: "device_name", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_USE_WEBHOOK: True, + CONF_TOKEN: "", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "webhook" + assert result["errors"] is None + + +async def test_show_config_form_api_method_no_auth_token(hass, webhook_id): + """Test show configuration form.""" + + # Using Keg + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_DEVICE_TYPE: PlaatoDeviceType.Keg, + CONF_DEVICE_NAME: "device_name", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: ""} + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + assert len(result["errors"]) == 1 + assert result["errors"]["base"] == "no_auth_token" + + # Using Airlock + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_DEVICE_TYPE: PlaatoDeviceType.Airlock, + CONF_DEVICE_NAME: "device_name", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_TOKEN: ""} + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "api_method" + assert len(result["errors"]) == 1 + assert result["errors"]["base"] == "no_api_method" + + +async def test_options(hass): + """Test updating options.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="NAME", + data={}, + options={CONF_SCAN_INTERVAL: 5}, + ) + + config_entry.add_to_hass(hass) + with patch("homeassistant.components.plaato.async_setup_entry", return_value=True): + 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"] == "user" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_SCAN_INTERVAL: 10}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_SCAN_INTERVAL] == 10 + + +async def test_options_webhook(hass, webhook_id): + """Test updating options.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="NAME", + data={CONF_USE_WEBHOOK: True, CONF_WEBHOOK_ID: None}, + options={CONF_SCAN_INTERVAL: 5}, + ) + + config_entry.add_to_hass(hass) + with patch("homeassistant.components.plaato.async_setup_entry", return_value=True): + 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"] == "webhook" + assert result["description_placeholders"] == {"webhook_url": ""} + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_WEBHOOK_ID: WEBHOOK_ID}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_WEBHOOK_ID] == CONF_WEBHOOK_ID From c90588d35d1ba73f97e11ce3e9dbbb52f2a8d734 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 1 Feb 2021 18:15:34 +0100 Subject: [PATCH 0068/1818] Correct synology_dsm CPU sensor's naming and measurement unit (#45500) --- .../components/synology_dsm/const.py | 21 ++++++++++--------- .../components/synology_dsm/sensor.py | 5 +++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index f9bcc8b61b8877..1c4e004f7490bb 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -37,6 +37,7 @@ DEFAULT_SCAN_INTERVAL = 15 # min DEFAULT_TIMEOUT = 10 # sec +ENTITY_UNIT_LOAD = "load" ENTITY_NAME = "name" ENTITY_UNIT = "unit" @@ -95,50 +96,50 @@ # Sensors UTILISATION_SENSORS = { f"{SynoCoreUtilization.API_KEY}:cpu_other_load": { - ENTITY_NAME: "CPU Load (Other)", + ENTITY_NAME: "CPU Utilization (Other)", ENTITY_UNIT: PERCENTAGE, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: False, }, f"{SynoCoreUtilization.API_KEY}:cpu_user_load": { - ENTITY_NAME: "CPU Load (User)", + ENTITY_NAME: "CPU Utilization (User)", ENTITY_UNIT: PERCENTAGE, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_system_load": { - ENTITY_NAME: "CPU Load (System)", + ENTITY_NAME: "CPU Utilization (System)", ENTITY_UNIT: PERCENTAGE, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: False, }, f"{SynoCoreUtilization.API_KEY}:cpu_total_load": { - ENTITY_NAME: "CPU Load (Total)", + ENTITY_NAME: "CPU Utilization (Total)", ENTITY_UNIT: PERCENTAGE, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_1min_load": { - ENTITY_NAME: "CPU Load (1 min)", - ENTITY_UNIT: PERCENTAGE, + ENTITY_NAME: "CPU Load Averarge (1 min)", + ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: False, }, f"{SynoCoreUtilization.API_KEY}:cpu_5min_load": { - ENTITY_NAME: "CPU Load (5 min)", - ENTITY_UNIT: PERCENTAGE, + ENTITY_NAME: "CPU Load Averarge (5 min)", + ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_15min_load": { - ENTITY_NAME: "CPU Load (15 min)", - ENTITY_UNIT: PERCENTAGE, + ENTITY_NAME: "CPU Load Averarge (15 min)", + ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: True, diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index dd2df61165dd39..7dd4e5e9870ad2 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -19,6 +19,7 @@ from .const import ( CONF_VOLUMES, DOMAIN, + ENTITY_UNIT_LOAD, INFORMATION_SENSORS, STORAGE_DISK_SENSORS, STORAGE_VOL_SENSORS, @@ -88,6 +89,10 @@ def state(self): if self._unit == DATA_RATE_KILOBYTES_PER_SECOND: return round(attr / 1024.0, 1) + # CPU load average + if self._unit == ENTITY_UNIT_LOAD: + return round(attr / 100, 2) + return attr @property From 776b1395de18da6b037eb0aad14512be2a7a13cd Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 1 Feb 2021 20:43:43 +0100 Subject: [PATCH 0069/1818] Bump brother library to version 0.2.0 (#45832) --- homeassistant/components/brother/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 9bb9ba00261c83..52286cd2c687af 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -3,7 +3,7 @@ "name": "Brother Printer", "documentation": "https://www.home-assistant.io/integrations/brother", "codeowners": ["@bieniu"], - "requirements": ["brother==0.1.20"], + "requirements": ["brother==0.2.0"], "zeroconf": [{ "type": "_printer._tcp.local.", "name": "brother*" }], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index c192883c95fcca..285140101a9d46 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -383,7 +383,7 @@ bravia-tv==1.0.8 broadlink==0.16.0 # homeassistant.components.brother -brother==0.1.20 +brother==0.2.0 # homeassistant.components.brottsplatskartan brottsplatskartan==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a30ae13e8c992..a5dc813c407897 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -213,7 +213,7 @@ bravia-tv==1.0.8 broadlink==0.16.0 # homeassistant.components.brother -brother==0.1.20 +brother==0.2.0 # homeassistant.components.bsblan bsblan==0.4.0 From 197c857e1fa1b031195f75f109339b59c34cae37 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Feb 2021 13:46:06 -0600 Subject: [PATCH 0070/1818] Search all endpoints for value in zwave_js (#45809) Co-authored-by: Martin Hjelmare --- homeassistant/components/zwave_js/climate.py | 1 + homeassistant/components/zwave_js/entity.py | 31 +- tests/components/zwave_js/conftest.py | 26 + tests/components/zwave_js/test_climate.py | 9 + ..._ct100_plus_different_endpoints_state.json | 727 ++++++++++++++++++ 5 files changed, 787 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 07d4a3f7d0f419..6c0b4a0335eaaa 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -140,6 +140,7 @@ def __init__( THERMOSTAT_CURRENT_TEMP_PROPERTY, command_class=CommandClass.SENSOR_MULTILEVEL, add_to_watched_value_ids=True, + check_all_endpoints=True, ) self._set_modes_and_presets() diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 5c64ddbc496b0e..84870ba75f4ef5 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -121,6 +121,7 @@ def get_zwave_value( endpoint: Optional[int] = None, value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, + check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted @@ -129,17 +130,33 @@ def get_zwave_value( command_class = self.info.primary_value.command_class if endpoint is None: endpoint = self.info.primary_value.endpoint + + # Build partial event data dictionary so we can change the endpoint later + partial_evt_data = { + "commandClass": command_class, + "property": value_property, + "propertyKeyName": value_property_key_name, + } + # lookup value by value_id value_id = get_value_id( - self.info.node, - { - "commandClass": command_class, - "endpoint": endpoint, - "property": value_property, - "propertyKeyName": value_property_key_name, - }, + self.info.node, {**partial_evt_data, "endpoint": endpoint} ) return_value = self.info.node.values.get(value_id) + + # If we haven't found a value and check_all_endpoints is True, we should + # return the first value we can find on any other endpoint + if return_value is None and check_all_endpoints: + for endpoint_ in self.info.node.endpoints: + if endpoint_.index != self.info.primary_value.endpoint: + value_id = get_value_id( + self.info.node, + {**partial_evt_data, "endpoint": endpoint_.index}, + ) + return_value = self.info.node.values.get(value_id) + if return_value: + break + # add to watched_ids list so we will be triggered when the value updates if ( return_value diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 470b4c0227b8c4..b6cbc911b6aca7 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -111,6 +111,22 @@ def climate_radio_thermostat_ct100_plus_state_fixture(): ) +@pytest.fixture( + name="climate_radio_thermostat_ct100_plus_different_endpoints_state", + scope="session", +) +def climate_radio_thermostat_ct100_plus_different_endpoints_state_fixture(): + """Load the thermostat fixture state with values on different endpoints. + + This device is a radio thermostat ct100. + """ + return json.loads( + load_fixture( + "zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json" + ) + ) + + @pytest.fixture(name="nortek_thermostat_state", scope="session") def nortek_thermostat_state_fixture(): """Load the nortek thermostat node state fixture data.""" @@ -231,6 +247,16 @@ def climate_radio_thermostat_ct100_plus_fixture( return node +@pytest.fixture(name="climate_radio_thermostat_ct100_plus_different_endpoints") +def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( + client, climate_radio_thermostat_ct100_plus_different_endpoints_state +): + """Mock a climate radio thermostat ct100 plus node with values on different endpoints.""" + node = Node(client, climate_radio_thermostat_ct100_plus_different_endpoints_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="nortek_thermostat") def nortek_thermostat_fixture(client, nortek_thermostat_state): """Mock a nortek thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index aca1022f8b0bc7..f7deefc1360f1d 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -324,3 +324,12 @@ async def test_thermostat_v2( }, blocking=True, ) + + +async def test_thermostat_different_endpoints( + hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration +): + """Test an entity with values on a different endpoint from the primary value.""" + state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY) + + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 diff --git a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json new file mode 100644 index 00000000000000..ea38dfd9d6b0f6 --- /dev/null +++ b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json @@ -0,0 +1,727 @@ +{ + "nodeId": 26, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Thermostat General V2", + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 152, + "productId": 256, + "productType": 25602, + "firmwareVersion": "10.7", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 152, + "manufacturer": "Radio Thermostat Company of America (RTC)", + "label": "CT100 Plus", + "description": "Z-Wave Thermostat", + "devices": [{ "productType": "0x6402", "productId": "0x0100" }], + "firmwareVersion": { "min": "0.0", "max": "255.255" }, + "paramInformation": { "_map": {} } + }, + "label": "CT100 Plus", + "neighbors": [1, 2, 3, 4, 23], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 2, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 26, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608 + }, + { "nodeId": 26, "index": 1 }, + { + "nodeId": 26, + "index": 2, + "installerIcon": 3328, + "userIcon": 3333 + } + ], + "values": [ + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "manufacturerId", + "propertyName": "manufacturerId", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 152, + "ccVersion": 2 + }, + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "productType", + "propertyName": "productType", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 25602, + "ccVersion": 2 + }, + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "productId", + "propertyName": "productId", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 256, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "2": "Cool", + "3": "Auto", + "11": "Energy heat", + "12": "Energy cool" + } + }, + "value": 1, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "manufacturerData", + "propertyName": "manufacturerData", + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 1 } + }, + "value": 72, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 2, + "propertyName": "setpoint", + "propertyKeyName": "Cooling", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 2 } + }, + "value": 73, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 11, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Heating", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 11 } + }, + "value": 62, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 12, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Cooling", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 12 } + }, + "value": 85, + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "libraryType", + "propertyName": "libraryType", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3, + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "protocolVersion", + "propertyName": "protocolVersion", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.24", + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["10.7"], + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + }, + "ccVersion": 2 + }, + { + "commandClassName": "Indicator", + "commandClass": 135, + "endpoint": 0, + "property": "value", + "propertyName": "value", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Indicator value", + "ccSpecific": { "indicatorId": 0 } + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Thermostat Operating State", + "commandClass": 66, + "endpoint": 0, + "property": "state", + "propertyName": "state", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Operating state", + "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, + "ccVersion": 2 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 1, + "propertyName": "Temperature Reporting Threshold", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 4, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F" + }, + "label": "Temperature Reporting Threshold", + "description": "Reporting threshold for changes in the ambient temperature", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 2, + "propertyName": "HVAC Settings", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 4, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "HVAC Settings", + "description": "Configured HVAC settings", + "isFromConfig": true + }, + "value": 17891329, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 4, + "propertyName": "Power Status", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 1, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Power Status", + "description": "C-Wire / Battery Status", + "isFromConfig": true + }, + "value": 1, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 5, + "propertyName": "Humidity Reporting Threshold", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "3% RH", + "2": "5% RH", + "3": "10% RH" + }, + "label": "Humidity Reporting Threshold", + "description": "Reporting threshold for changes in the relative humidity", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 6, + "propertyName": "Auxiliary/Emergency", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Auxiliary/Emergency heat disabled", + "1": "Auxiliary/Emergency heat enabled" + }, + "label": "Auxiliary/Emergency", + "description": "Enables or disables auxiliary / emergency heating", + "isFromConfig": true + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 7, + "propertyName": "Thermostat Swing Temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 8, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F", + "5": "2.5\u00b0 F", + "6": "3.0\u00b0 F", + "7": "3.5\u00b0 F", + "8": "4.0\u00b0 F" + }, + "label": "Thermostat Swing Temperature", + "description": "Variance allowed from setpoint to engage HVAC", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 8, + "propertyName": "Thermostat Diff Temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 4, + "max": 12, + "default": 4, + "format": 0, + "allowManualEntry": false, + "states": { + "4": "2.0\u00b0 F", + "8": "4.0\u00b0 F", + "12": "6.0\u00b0 F" + }, + "label": "Thermostat Diff Temperature", + "description": "Configures additional stages", + "isFromConfig": true + }, + "value": 1028, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 9, + "propertyName": "Thermostat Recovery Mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 2, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "Fast recovery mode", + "2": "Economy recovery mode" + }, + "label": "Thermostat Recovery Mode", + "description": "Fast or Economy recovery mode", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 10, + "propertyName": "Temperature Reporting Filter", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 124, + "default": 124, + "format": 0, + "allowManualEntry": true, + "label": "Temperature Reporting Filter", + "description": "Upper/Lower bounds for thermostat temperature reporting", + "isFromConfig": true + }, + "value": 32000, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 11, + "propertyName": "Simple UI Mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Normal mode enabled", + "1": "Simple mode enabled" + }, + "label": "Simple UI Mode", + "description": "Simple mode enable/disable", + "isFromConfig": true + }, + "value": 1, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 12, + "propertyName": "Multicast", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Multicast disabled", + "1": "Multicast enabled" + }, + "label": "Multicast", + "description": "Enable or disables Multicast", + "isFromConfig": true + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 3, + "propertyName": "Utility Lock Enable/Disable", + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Utility lock disabled", + "1": "Utility lock enabled" + }, + "label": "Utility Lock Enable/Disable", + "description": "Prevents setpoint changes at thermostat", + "isFromConfig": true + }, + "ccVersion": 1 + }, + { + "commandClassName": "Battery", + "commandClass": 128, + "endpoint": 0, + "property": "level", + "propertyName": "level", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 100, + "ccVersion": 1 + }, + { + "commandClassName": "Battery", + "commandClass": 128, + "endpoint": 0, + "property": "isLow", + "propertyName": "isLow", + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Low battery level" + }, + "value": false, + "ccVersion": 1 + }, + { + "commandClassName": "Multilevel Sensor", + "commandClass": 49, + "endpoint": 2, + "property": "Air temperature", + "propertyName": "Air temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0F", + "label": "Air temperature", + "ccSpecific": { "sensorType": 1, "scale": 1 } + }, + "value": 72.5, + "ccVersion": 5 + }, + { + "commandClassName": "Multilevel Sensor", + "commandClass": 49, + "endpoint": 2, + "property": "Humidity", + "propertyName": "Humidity", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "%", + "label": "Humidity", + "ccSpecific": { "sensorType": 5, "scale": 0 } + }, + "value": 20, + "ccVersion": 5 + } + ] +} \ No newline at end of file From 3bdf9628385a381d28f1ee092ab686eed23f8163 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 1 Feb 2021 14:38:03 -0700 Subject: [PATCH 0071/1818] Add ability to configure AirVisual with city/state/country in UI (#44116) --- .../components/airvisual/__init__.py | 23 +- .../components/airvisual/config_flow.py | 160 ++++++++------ homeassistant/components/airvisual/const.py | 3 +- homeassistant/components/airvisual/sensor.py | 45 ++-- .../components/airvisual/strings.json | 22 +- .../components/airvisual/translations/en.json | 22 +- .../components/airvisual/test_config_flow.py | 205 +++++++++++++----- 7 files changed, 327 insertions(+), 153 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 956b168a665d55..3a88243b0b9547 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -23,6 +23,7 @@ CONF_STATE, ) from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, @@ -37,7 +38,7 @@ CONF_INTEGRATION_TYPE, DATA_COORDINATOR, DOMAIN, - INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_GEOGRAPHY_COORDS, INTEGRATION_TYPE_NODE_PRO, LOGGER, ) @@ -145,7 +146,7 @@ def _standardize_geography_config_entry(hass, config_entry): # If the config entry data doesn't contain the integration type, add it: entry_updates["data"] = { **config_entry.data, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, } if not entry_updates: @@ -232,7 +233,6 @@ async def async_update_data(): update_method=async_update_data, ) - hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator async_sync_geo_coordinator_update_intervals( hass, config_entry.data[CONF_API_KEY] ) @@ -262,9 +262,11 @@ async def async_update_data(): update_method=async_update_data, ) - hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator - await coordinator.async_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator for component in PLATFORMS: hass.async_create_task( @@ -299,10 +301,14 @@ async def async_migrate_entry(hass, config_entry): # For any geographies that remain, create a new config entry for each one: for geography in geographies: + if CONF_LATITUDE in geography: + source = "geography_by_coords" + else: + source = "geography_by_name" hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, - context={"source": "geography"}, + context={"source": source}, data={CONF_API_KEY: config_entry.data[CONF_API_KEY], **geography}, ) ) @@ -327,7 +333,10 @@ async def async_unload_entry(hass, config_entry): remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) remove_listener() - if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY: + if ( + config_entry.data[CONF_INTEGRATION_TYPE] + == INTEGRATION_TYPE_GEOGRAPHY_COORDS + ): # Re-calculate the update interval period for any remaining consumers of # this API key: async_sync_geo_coordinator_update_intervals( diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index b086aeefc2762d..266f7b7c2c259d 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -2,7 +2,12 @@ import asyncio from pyairvisual import CloudAPI, NodeSamba -from pyairvisual.errors import InvalidKeyError, NodeProError +from pyairvisual.errors import ( + AirVisualError, + InvalidKeyError, + NodeProError, + NotFoundError, +) import voluptuous as vol from homeassistant import config_entries @@ -13,20 +18,46 @@ CONF_LONGITUDE, CONF_PASSWORD, CONF_SHOW_ON_MAP, + CONF_STATE, ) from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv from . import async_get_geography_id from .const import ( # pylint: disable=unused-import - CONF_GEOGRAPHIES, + CONF_CITY, + CONF_COUNTRY, CONF_INTEGRATION_TYPE, DOMAIN, - INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_NODE_PRO, LOGGER, ) +API_KEY_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): cv.string}) +GEOGRAPHY_NAME_SCHEMA = API_KEY_DATA_SCHEMA.extend( + { + vol.Required(CONF_CITY): cv.string, + vol.Required(CONF_STATE): cv.string, + vol.Required(CONF_COUNTRY): cv.string, + } +) +NODE_PRO_SCHEMA = vol.Schema( + {vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PASSWORD): cv.string} +) +PICK_INTEGRATION_TYPE_SCHEMA = vol.Schema( + { + vol.Required("type"): vol.In( + [ + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + INTEGRATION_TYPE_NODE_PRO, + ] + ) + } +) + class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle an AirVisual config flow.""" @@ -36,16 +67,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the config flow.""" + self._entry_data_for_reauth = None self._geo_id = None - self._latitude = None - self._longitude = None - - self.api_key_data_schema = vol.Schema({vol.Required(CONF_API_KEY): str}) @property - def geography_schema(self): + def geography_coords_schema(self): """Return the data schema for the cloud API.""" - return self.api_key_data_schema.extend( + return API_KEY_DATA_SCHEMA.extend( { vol.Required( CONF_LATITUDE, default=self.hass.config.latitude @@ -56,24 +84,6 @@ def geography_schema(self): } ) - @property - def pick_integration_type_schema(self): - """Return the data schema for picking the integration type.""" - return vol.Schema( - { - vol.Required("type"): vol.In( - [INTEGRATION_TYPE_GEOGRAPHY, INTEGRATION_TYPE_NODE_PRO] - ) - } - ) - - @property - def node_pro_schema(self): - """Return the data schema for a Node/Pro.""" - return vol.Schema( - {vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PASSWORD): str} - ) - async def _async_set_unique_id(self, unique_id): """Set the unique ID of the config flow and abort if it already exists.""" await self.async_set_unique_id(unique_id) @@ -85,33 +95,36 @@ def async_get_options_flow(config_entry): """Define the config flow to handle options.""" return AirVisualOptionsFlowHandler(config_entry) - async def async_step_geography(self, user_input=None): + async def async_step_geography(self, user_input, integration_type): """Handle the initialization of the integration via the cloud API.""" - if not user_input: - return self.async_show_form( - step_id="geography", data_schema=self.geography_schema - ) - self._geo_id = async_get_geography_id(user_input) await self._async_set_unique_id(self._geo_id) self._abort_if_unique_id_configured() + return await self.async_step_geography_finish(user_input, integration_type) - # Find older config entries without unique ID: - for entry in self._async_current_entries(): - if entry.version != 1: - continue + async def async_step_geography_by_coords(self, user_input=None): + """Handle the initialization of the cloud API based on latitude/longitude.""" + if not user_input: + return self.async_show_form( + step_id="geography_by_coords", data_schema=self.geography_coords_schema + ) - if any( - self._geo_id == async_get_geography_id(geography) - for geography in entry.data[CONF_GEOGRAPHIES] - ): - return self.async_abort(reason="already_configured") + return await self.async_step_geography( + user_input, INTEGRATION_TYPE_GEOGRAPHY_COORDS + ) - return await self.async_step_geography_finish( - user_input, "geography", self.geography_schema + async def async_step_geography_by_name(self, user_input=None): + """Handle the initialization of the cloud API based on city/state/country.""" + if not user_input: + return self.async_show_form( + step_id="geography_by_name", data_schema=GEOGRAPHY_NAME_SCHEMA + ) + + return await self.async_step_geography( + user_input, INTEGRATION_TYPE_GEOGRAPHY_NAME ) - async def async_step_geography_finish(self, user_input, error_step, error_schema): + async def async_step_geography_finish(self, user_input, integration_type): """Validate a Cloud API key.""" websession = aiohttp_client.async_get_clientsession(self.hass) cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession) @@ -123,16 +136,40 @@ async def async_step_geography_finish(self, user_input, error_step, error_schema "airvisual_checked_api_keys_lock", asyncio.Lock() ) + if integration_type == INTEGRATION_TYPE_GEOGRAPHY_COORDS: + coro = cloud_api.air_quality.nearest_city() + error_schema = self.geography_coords_schema + error_step = "geography_by_coords" + else: + coro = cloud_api.air_quality.city( + user_input[CONF_CITY], user_input[CONF_STATE], user_input[CONF_COUNTRY] + ) + error_schema = GEOGRAPHY_NAME_SCHEMA + error_step = "geography_by_name" + async with valid_keys_lock: if user_input[CONF_API_KEY] not in valid_keys: try: - await cloud_api.air_quality.nearest_city() + await coro except InvalidKeyError: return self.async_show_form( step_id=error_step, data_schema=error_schema, errors={CONF_API_KEY: "invalid_api_key"}, ) + except NotFoundError: + return self.async_show_form( + step_id=error_step, + data_schema=error_schema, + errors={CONF_CITY: "location_not_found"}, + ) + except AirVisualError as err: + LOGGER.error(err) + return self.async_show_form( + step_id=error_step, + data_schema=error_schema, + errors={"base": "unknown"}, + ) valid_keys.add(user_input[CONF_API_KEY]) @@ -143,15 +180,13 @@ async def async_step_geography_finish(self, user_input, error_step, error_schema return self.async_create_entry( title=f"Cloud API ({self._geo_id})", - data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY}, + data={**user_input, CONF_INTEGRATION_TYPE: integration_type}, ) async def async_step_node_pro(self, user_input=None): """Handle the initialization of the integration with a Node/Pro.""" if not user_input: - return self.async_show_form( - step_id="node_pro", data_schema=self.node_pro_schema - ) + return self.async_show_form(step_id="node_pro", data_schema=NODE_PRO_SCHEMA) await self._async_set_unique_id(user_input[CONF_IP_ADDRESS]) @@ -163,7 +198,7 @@ async def async_step_node_pro(self, user_input=None): LOGGER.error("Error connecting to Node/Pro unit: %s", err) return self.async_show_form( step_id="node_pro", - data_schema=self.node_pro_schema, + data_schema=NODE_PRO_SCHEMA, errors={CONF_IP_ADDRESS: "cannot_connect"}, ) @@ -176,39 +211,34 @@ async def async_step_node_pro(self, user_input=None): async def async_step_reauth(self, data): """Handle configuration by re-auth.""" + self._entry_data_for_reauth = data self._geo_id = async_get_geography_id(data) - self._latitude = data[CONF_LATITUDE] - self._longitude = data[CONF_LONGITUDE] - return await self.async_step_reauth_confirm() async def async_step_reauth_confirm(self, user_input=None): """Handle re-auth completion.""" if not user_input: return self.async_show_form( - step_id="reauth_confirm", data_schema=self.api_key_data_schema + step_id="reauth_confirm", data_schema=API_KEY_DATA_SCHEMA ) - conf = { - CONF_API_KEY: user_input[CONF_API_KEY], - CONF_LATITUDE: self._latitude, - CONF_LONGITUDE: self._longitude, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, - } + conf = {CONF_API_KEY: user_input[CONF_API_KEY], **self._entry_data_for_reauth} return await self.async_step_geography_finish( - conf, "reauth_confirm", self.api_key_data_schema + conf, self._entry_data_for_reauth[CONF_INTEGRATION_TYPE] ) async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" if not user_input: return self.async_show_form( - step_id="user", data_schema=self.pick_integration_type_schema + step_id="user", data_schema=PICK_INTEGRATION_TYPE_SCHEMA ) - if user_input["type"] == INTEGRATION_TYPE_GEOGRAPHY: - return await self.async_step_geography() + if user_input["type"] == INTEGRATION_TYPE_GEOGRAPHY_COORDS: + return await self.async_step_geography_by_coords() + if user_input["type"] == INTEGRATION_TYPE_GEOGRAPHY_NAME: + return await self.async_step_geography_by_name() return await self.async_step_node_pro() diff --git a/homeassistant/components/airvisual/const.py b/homeassistant/components/airvisual/const.py index a98a899b762420..510ada2b68c0f9 100644 --- a/homeassistant/components/airvisual/const.py +++ b/homeassistant/components/airvisual/const.py @@ -4,7 +4,8 @@ DOMAIN = "airvisual" LOGGER = logging.getLogger(__package__) -INTEGRATION_TYPE_GEOGRAPHY = "Geographical Location" +INTEGRATION_TYPE_GEOGRAPHY_COORDS = "Geographical Location by Latitude/Longitude" +INTEGRATION_TYPE_GEOGRAPHY_NAME = "Geographical Location by Name" INTEGRATION_TYPE_NODE_PRO = "AirVisual Node/Pro" CONF_CITY = "city" diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index ae9995f36c32c4..680059af41126c 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -27,7 +27,8 @@ CONF_INTEGRATION_TYPE, DATA_COORDINATOR, DOMAIN, - INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, ) _LOGGER = getLogger(__name__) @@ -115,7 +116,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up AirVisual sensors based on a config entry.""" coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] - if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY: + if config_entry.data[CONF_INTEGRATION_TYPE] in [ + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + ]: sensors = [ AirVisualGeographySensor( coordinator, @@ -208,17 +212,32 @@ def update_from_latest_data(self): } ) - if CONF_LATITUDE in self._config_entry.data: - if self._config_entry.options[CONF_SHOW_ON_MAP]: - self._attrs[ATTR_LATITUDE] = self._config_entry.data[CONF_LATITUDE] - self._attrs[ATTR_LONGITUDE] = self._config_entry.data[CONF_LONGITUDE] - self._attrs.pop("lati", None) - self._attrs.pop("long", None) - else: - self._attrs["lati"] = self._config_entry.data[CONF_LATITUDE] - self._attrs["long"] = self._config_entry.data[CONF_LONGITUDE] - self._attrs.pop(ATTR_LATITUDE, None) - self._attrs.pop(ATTR_LONGITUDE, None) + # Displaying the geography on the map relies upon putting the latitude/longitude + # in the entity attributes with "latitude" and "longitude" as the keys. + # Conversely, we can hide the location on the map by using other keys, like + # "lati" and "long". + # + # We use any coordinates in the config entry and, in the case of a geography by + # name, we fall back to the latitude longitude provided in the coordinator data: + latitude = self._config_entry.data.get( + CONF_LATITUDE, + self.coordinator.data["location"]["coordinates"][1], + ) + longitude = self._config_entry.data.get( + CONF_LONGITUDE, + self.coordinator.data["location"]["coordinates"][0], + ) + + if self._config_entry.options[CONF_SHOW_ON_MAP]: + self._attrs[ATTR_LATITUDE] = latitude + self._attrs[ATTR_LONGITUDE] = longitude + self._attrs.pop("lati", None) + self._attrs.pop("long", None) + else: + self._attrs["lati"] = latitude + self._attrs["long"] = longitude + self._attrs.pop(ATTR_LATITUDE, None) + self._attrs.pop(ATTR_LONGITUDE, None) class AirVisualNodeProSensor(AirVisualEntity): diff --git a/homeassistant/components/airvisual/strings.json b/homeassistant/components/airvisual/strings.json index 22f9c80f313a89..8d2dce85a17c79 100644 --- a/homeassistant/components/airvisual/strings.json +++ b/homeassistant/components/airvisual/strings.json @@ -1,15 +1,25 @@ { "config": { "step": { - "geography": { + "geography_by_coords": { "title": "Configure a Geography", - "description": "Use the AirVisual cloud API to monitor a geographical location.", + "description": "Use the AirVisual cloud API to monitor a latitude/longitude.", "data": { "api_key": "[%key:common::config_flow::data::api_key%]", "latitude": "[%key:common::config_flow::data::latitude%]", "longitude": "[%key:common::config_flow::data::longitude%]" } }, + "geography_by_name": { + "title": "Configure a Geography", + "description": "Use the AirVisual cloud API to monitor a city/state/country.", + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "city": "City", + "country": "Country", + "state": "state" + } + }, "node_pro": { "title": "Configure an AirVisual Node/Pro", "description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.", @@ -26,17 +36,13 @@ }, "user": { "title": "Configure AirVisual", - "description": "Pick what type of AirVisual data you want to monitor.", - "data": { - "cloud_api": "Geographical Location", - "node_pro": "AirVisual Node Pro", - "type": "Integration Type" - } + "description": "Pick what type of AirVisual data you want to monitor." } }, "error": { "general_error": "[%key:common::config_flow::error::unknown%]", "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]", + "location_not_found": "Location not found", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index 129abcc29e5598..1e3cb59a5204fe 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -7,16 +7,27 @@ "error": { "cannot_connect": "Failed to connect", "general_error": "Unexpected error", - "invalid_api_key": "Invalid API key" + "invalid_api_key": "Invalid API key", + "location_not_found": "Location not found" }, "step": { - "geography": { + "geography_by_coords": { "data": { "api_key": "API Key", "latitude": "Latitude", "longitude": "Longitude" }, - "description": "Use the AirVisual cloud API to monitor a geographical location.", + "description": "Use the AirVisual cloud API to monitor a latitude/longitude.", + "title": "Configure a Geography" + }, + "geography_by_name": { + "data": { + "api_key": "API Key", + "city": "City", + "country": "Country", + "state": "state" + }, + "description": "Use the AirVisual cloud API to monitor a city/state/country.", "title": "Configure a Geography" }, "node_pro": { @@ -34,11 +45,6 @@ "title": "Re-authenticate AirVisual" }, "user": { - "data": { - "cloud_api": "Geographical Location", - "node_pro": "AirVisual Node Pro", - "type": "Integration Type" - }, "description": "Pick what type of AirVisual data you want to monitor.", "title": "Configure AirVisual" } diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index 4e550d94b09f0c..248abaf6b5f69b 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -1,14 +1,22 @@ """Define tests for the AirVisual config flow.""" from unittest.mock import patch -from pyairvisual.errors import InvalidKeyError, NodeProError +from pyairvisual.errors import ( + AirVisualError, + InvalidKeyError, + NodeProError, + NotFoundError, +) from homeassistant import data_entry_flow -from homeassistant.components.airvisual import ( +from homeassistant.components.airvisual.const import ( + CONF_CITY, + CONF_COUNTRY, CONF_GEOGRAPHIES, CONF_INTEGRATION_TYPE, DOMAIN, - INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_NODE_PRO, ) from homeassistant.config_entries import SOURCE_USER @@ -19,6 +27,7 @@ CONF_LONGITUDE, CONF_PASSWORD, CONF_SHOW_ON_MAP, + CONF_STATE, ) from homeassistant.setup import async_setup_component @@ -38,7 +47,9 @@ async def test_duplicate_error(hass): ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={"type": "Geographical Location"} + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_COORDS}, ) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=geography_conf @@ -64,14 +75,8 @@ async def test_duplicate_error(hass): assert result["reason"] == "already_configured" -async def test_invalid_identifier(hass): - """Test that an invalid API key or Node/Pro ID throws an error.""" - geography_conf = { - CONF_API_KEY: "abcde12345", - CONF_LATITUDE: 51.528308, - CONF_LONGITUDE: -0.3817765, - } - +async def test_invalid_identifier_geography_api_key(hass): + """Test that an invalid API key throws an error.""" with patch( "pyairvisual.air_quality.AirQuality.nearest_city", side_effect=InvalidKeyError, @@ -79,23 +84,96 @@ async def test_invalid_identifier(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={"type": "Geographical Location"}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_COORDS}, ) result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=geography_conf + result["flow_id"], + user_input={ + CONF_API_KEY: "abcde12345", + CONF_LATITUDE: 51.528308, + CONF_LONGITUDE: -0.3817765, + }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} +async def test_invalid_identifier_geography_name(hass): + """Test that an invalid location name throws an error.""" + with patch( + "pyairvisual.air_quality.AirQuality.city", + side_effect=NotFoundError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_NAME}, + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_API_KEY: "abcde12345", + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_CITY: "location_not_found"} + + +async def test_invalid_identifier_geography_unknown(hass): + """Test that an unknown identifier issue throws an error.""" + with patch( + "pyairvisual.air_quality.AirQuality.city", + side_effect=AirVisualError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_NAME}, + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_API_KEY: "abcde12345", + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "unknown"} + + +async def test_invalid_identifier_node_pro(hass): + """Test that an invalid Node/Pro identifier shows an error.""" + node_pro_conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password"} + + with patch( + "pyairvisual.node.NodeSamba.async_connect", + side_effect=NodeProError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=node_pro_conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + async def test_migration(hass): """Test migrating from version 1 to the current version.""" conf = { CONF_API_KEY: "abcde12345", CONF_GEOGRAPHIES: [ {CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765}, - {CONF_LATITUDE: 35.48847, CONF_LONGITUDE: 137.5263065}, + {CONF_CITY: "Beijing", CONF_STATE: "Beijing", CONF_COUNTRY: "China"}, ], } @@ -106,9 +184,9 @@ async def test_migration(hass): assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - with patch("pyairvisual.air_quality.AirQuality.nearest_city"), patch.object( - hass.config_entries, "async_forward_entry_setup" - ): + with patch("pyairvisual.air_quality.AirQuality.city"), patch( + "pyairvisual.air_quality.AirQuality.nearest_city" + ), patch.object(hass.config_entries, "async_forward_entry_setup"): assert await async_setup_component(hass, DOMAIN, {DOMAIN: conf}) await hass.async_block_till_done() @@ -122,37 +200,20 @@ async def test_migration(hass): CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, } - assert config_entries[1].unique_id == "35.48847, 137.5263065" - assert config_entries[1].title == "Cloud API (35.48847, 137.5263065)" + assert config_entries[1].unique_id == "Beijing, Beijing, China" + assert config_entries[1].title == "Cloud API (Beijing, Beijing, China)" assert config_entries[1].data == { CONF_API_KEY: "abcde12345", - CONF_LATITUDE: 35.48847, - CONF_LONGITUDE: 137.5263065, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_NAME, } -async def test_node_pro_error(hass): - """Test that an invalid Node/Pro ID shows an error.""" - node_pro_conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password"} - - with patch( - "pyairvisual.node.NodeSamba.async_connect", - side_effect=NodeProError, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=node_pro_conf - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} - - async def test_options_flow(hass): """Test config flow options.""" geography_conf = { @@ -186,8 +247,8 @@ async def test_options_flow(hass): assert config_entry.options == {CONF_SHOW_ON_MAP: False} -async def test_step_geography(hass): - """Test the geograph (cloud API) step.""" +async def test_step_geography_by_coords(hass): + """Test setting up a geopgraphy entry by latitude/longitude.""" conf = { CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, @@ -200,7 +261,7 @@ async def test_step_geography(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={"type": "Geographical Location"}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_COORDS}, ) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=conf @@ -212,7 +273,39 @@ async def test_step_geography(hass): CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, + } + + +async def test_step_geography_by_name(hass): + """Test setting up a geopgraphy entry by city/state/country.""" + conf = { + CONF_API_KEY: "abcde12345", + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + } + + with patch( + "homeassistant.components.airvisual.async_setup_entry", return_value=True + ), patch("pyairvisual.air_quality.AirQuality.city"): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_NAME}, + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Cloud API (Beijing, Beijing, China)" + assert result["data"] == { + CONF_API_KEY: "abcde12345", + CONF_CITY: "Beijing", + CONF_STATE: "Beijing", + CONF_COUNTRY: "China", + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_NAME, } @@ -244,18 +337,19 @@ async def test_step_node_pro(hass): async def test_step_reauth(hass): """Test that the reauth step works.""" - geography_conf = { + entry_data = { CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, } MockConfigEntry( - domain=DOMAIN, unique_id="51.528308, -0.3817765", data=geography_conf + domain=DOMAIN, unique_id="51.528308, -0.3817765", data=entry_data ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "reauth"}, data=geography_conf + DOMAIN, context={"source": "reauth"}, data=entry_data ) assert result["step_id"] == "reauth_confirm" @@ -287,11 +381,20 @@ async def test_step_user(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={"type": INTEGRATION_TYPE_GEOGRAPHY}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_COORDS}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "geography_by_coords" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY_NAME}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "geography" + assert result["step_id"] == "geography_by_name" result = await hass.config_entries.flow.async_init( DOMAIN, From 8222eb5e3efce2f6f73ca447b0505be80b7bc9db Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Tue, 2 Feb 2021 00:29:31 +0200 Subject: [PATCH 0072/1818] Allow Influxdb CA path in verify_ssl (#45270) --- homeassistant/components/influxdb/__init__.py | 9 +- homeassistant/components/influxdb/const.py | 4 +- .../components/influxdb/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/influxdb/test_init.py | 133 ++++++++++++++++++ 6 files changed, 147 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 16b6971b11fab1..e327f34d128561 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -62,6 +62,7 @@ CONF_PRECISION, CONF_RETRY_COUNT, CONF_SSL, + CONF_SSL_CA_CERT, CONF_TAGS, CONF_TAGS_ATTRIBUTES, CONF_TOKEN, @@ -335,6 +336,9 @@ def get_influx_connection(conf, test_write=False, test_read=False): kwargs[CONF_URL] = conf[CONF_URL] kwargs[CONF_TOKEN] = conf[CONF_TOKEN] kwargs[INFLUX_CONF_ORG] = conf[CONF_ORG] + kwargs[CONF_VERIFY_SSL] = conf[CONF_VERIFY_SSL] + if CONF_SSL_CA_CERT in conf: + kwargs[CONF_SSL_CA_CERT] = conf[CONF_SSL_CA_CERT] bucket = conf.get(CONF_BUCKET) influx = InfluxDBClientV2(**kwargs) query_api = influx.query_api() @@ -392,7 +396,10 @@ def close_v2(): return InfluxClient(buckets, write_v2, query_v2, close_v2) # Else it's a V1 client - kwargs[CONF_VERIFY_SSL] = conf[CONF_VERIFY_SSL] + if CONF_SSL_CA_CERT in conf and conf[CONF_VERIFY_SSL]: + kwargs[CONF_VERIFY_SSL] = conf[CONF_SSL_CA_CERT] + else: + kwargs[CONF_VERIFY_SSL] = conf[CONF_VERIFY_SSL] if CONF_DB_NAME in conf: kwargs[CONF_DB_NAME] = conf[CONF_DB_NAME] diff --git a/homeassistant/components/influxdb/const.py b/homeassistant/components/influxdb/const.py index 1a827c1b63ca39..e66a0fe10c4ba9 100644 --- a/homeassistant/components/influxdb/const.py +++ b/homeassistant/components/influxdb/const.py @@ -31,6 +31,7 @@ CONF_RETRY_COUNT = "max_retries" CONF_IGNORE_ATTRIBUTES = "ignore_attributes" CONF_PRECISION = "precision" +CONF_SSL_CA_CERT = "ssl_ca_cert" CONF_LANGUAGE = "language" CONF_QUERIES = "queries" @@ -139,12 +140,13 @@ vol.Optional(CONF_PATH): cv.string, vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_SSL_CA_CERT): cv.isfile, vol.Optional(CONF_PRECISION): vol.In(["ms", "s", "us", "ns"]), # Connection config for V1 API only. vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, # Connection config for V2 API only. vol.Inclusive(CONF_TOKEN, "v2_authentication"): cv.string, vol.Inclusive(CONF_ORG, "v2_authentication"): cv.string, diff --git a/homeassistant/components/influxdb/manifest.json b/homeassistant/components/influxdb/manifest.json index ec1bd8f9594e4e..c2d6f77e7c1757 100644 --- a/homeassistant/components/influxdb/manifest.json +++ b/homeassistant/components/influxdb/manifest.json @@ -2,6 +2,6 @@ "domain": "influxdb", "name": "InfluxDB", "documentation": "https://www.home-assistant.io/integrations/influxdb", - "requirements": ["influxdb==5.2.3", "influxdb-client==1.8.0"], + "requirements": ["influxdb==5.2.3", "influxdb-client==1.14.0"], "codeowners": ["@fabaff", "@mdegat01"] } diff --git a/requirements_all.txt b/requirements_all.txt index 285140101a9d46..8a74e574f04a68 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -825,7 +825,7 @@ ihcsdk==2.7.0 incomfort-client==0.4.0 # homeassistant.components.influxdb -influxdb-client==1.8.0 +influxdb-client==1.14.0 # homeassistant.components.influxdb influxdb==5.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a5dc813c407897..99f1c997ec1c6e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -436,7 +436,7 @@ iaqualink==0.3.4 icmplib==2.0 # homeassistant.components.influxdb -influxdb-client==1.8.0 +influxdb-client==1.14.0 # homeassistant.components.influxdb influxdb==5.2.3 diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index db22b5c5236369..fd43091f457de2 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -131,6 +131,139 @@ async def test_setup_config_full(hass, mock_client, config_ext, get_write_api): assert get_write_api(mock_client).call_count == 1 +@pytest.mark.parametrize( + "mock_client, config_base, config_ext, expected_client_args", + [ + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + { + "ssl": True, + "verify_ssl": False, + }, + { + "ssl": True, + "verify_ssl": False, + }, + ), + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + { + "ssl": True, + "verify_ssl": True, + }, + { + "ssl": True, + "verify_ssl": True, + }, + ), + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + { + "ssl": True, + "verify_ssl": True, + "ssl_ca_cert": "fake/path/ca.pem", + }, + { + "ssl": True, + "verify_ssl": "fake/path/ca.pem", + }, + ), + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + { + "ssl": True, + "ssl_ca_cert": "fake/path/ca.pem", + }, + { + "ssl": True, + "verify_ssl": "fake/path/ca.pem", + }, + ), + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + { + "ssl": True, + "verify_ssl": False, + "ssl_ca_cert": "fake/path/ca.pem", + }, + { + "ssl": True, + "verify_ssl": False, + }, + ), + ( + influxdb.API_VERSION_2, + BASE_V2_CONFIG, + { + "api_version": influxdb.API_VERSION_2, + "verify_ssl": False, + }, + { + "verify_ssl": False, + }, + ), + ( + influxdb.API_VERSION_2, + BASE_V2_CONFIG, + { + "api_version": influxdb.API_VERSION_2, + "verify_ssl": True, + }, + { + "verify_ssl": True, + }, + ), + ( + influxdb.API_VERSION_2, + BASE_V2_CONFIG, + { + "api_version": influxdb.API_VERSION_2, + "verify_ssl": True, + "ssl_ca_cert": "fake/path/ca.pem", + }, + { + "verify_ssl": True, + "ssl_ca_cert": "fake/path/ca.pem", + }, + ), + ( + influxdb.API_VERSION_2, + BASE_V2_CONFIG, + { + "api_version": influxdb.API_VERSION_2, + "verify_ssl": False, + "ssl_ca_cert": "fake/path/ca.pem", + }, + { + "verify_ssl": False, + "ssl_ca_cert": "fake/path/ca.pem", + }, + ), + ], + indirect=["mock_client"], +) +async def test_setup_config_ssl( + hass, mock_client, config_base, config_ext, expected_client_args +): + """Test the setup with various verify_ssl values.""" + config = {"influxdb": config_base.copy()} + config["influxdb"].update(config_ext) + + with patch("os.access", return_value=True): + with patch("os.path.isfile", return_value=True): + assert await async_setup_component(hass, influxdb.DOMAIN, config) + await hass.async_block_till_done() + + assert hass.bus.listen.called + assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert expected_client_args.items() <= mock_client.call_args.kwargs.items() + + @pytest.mark.parametrize( "mock_client, config_ext, get_write_api", [ From b072a6c91a376eeb536a82eccc3004ab164c11e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Z=C3=A1hradn=C3=ADk?= Date: Sun, 31 Jan 2021 17:59:14 +0100 Subject: [PATCH 0073/1818] SSDP response decode: replace invalid utf-8 characters (#42681) * SSDP response decode: replace invalid utf-8 characters * Add test to validate replaced data Co-authored-by: Joakim Plate --- homeassistant/components/ssdp/__init__.py | 4 +-- tests/components/ssdp/test_init.py | 43 +++++++++++++++++++++++ tests/test_util/aiohttp.py | 4 +-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index e962c141bef146..f07e88d811a547 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -171,13 +171,13 @@ async def _fetch_description(self, xml_location): session = self.hass.helpers.aiohttp_client.async_get_clientsession() try: resp = await session.get(xml_location, timeout=5) - xml = await resp.text() + xml = await resp.text(errors="replace") # Samsung Smart TV sometimes returns an empty document the # first time. Retry once. if not xml: resp = await session.get(xml_location, timeout=5) - xml = await resp.text() + xml = await resp.text(errors="replace") except (aiohttp.ClientError, asyncio.TimeoutError) as err: _LOGGER.debug("Error fetching %s: %s", xml_location, err) return {} diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 008995cd78dd23..bba809aedbb4b4 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -170,3 +170,46 @@ async def test_scan_description_parse_fail(hass, aioclient_mock): return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], ): await scanner.async_scan(None) + + +async def test_invalid_characters(hass, aioclient_mock): + """Test that we replace bad characters with placeholders.""" + aioclient_mock.get( + "http://1.1.1.1", + text=""" + + + ABC + \xff\xff\xff\xff + + + """, + ) + scanner = ssdp.Scanner( + hass, + { + "mock-domain": [ + { + ssdp.ATTR_UPNP_DEVICE_TYPE: "ABC", + } + ] + }, + ) + + with patch( + "netdisco.ssdp.scan", + return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + ), patch.object( + hass.config_entries.flow, "async_init", return_value=mock_coro() + ) as mock_init: + await scanner.async_scan(None) + + 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": "ssdp"} + assert mock_init.mock_calls[0][2]["data"] == { + "ssdp_location": "http://1.1.1.1", + "ssdp_st": "mock-st", + "deviceType": "ABC", + "serialNumber": "ÿÿÿÿ", + } diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 53949c20b068a9..5219212f1cf19e 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -245,9 +245,9 @@ async def read(self): """Return mock response.""" return self.response - async def text(self, encoding="utf-8"): + async def text(self, encoding="utf-8", errors="strict"): """Return mock response as a string.""" - return self.response.decode(encoding) + return self.response.decode(encoding, errors=errors) async def json(self, encoding="utf-8", content_type=None): """Return mock response as a json.""" From 869bc2c4cea3b99d7729e135c2909cfec7b20d05 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 Jan 2021 12:53:35 +0100 Subject: [PATCH 0074/1818] Update docker base image 2021.01.1 (#45697) --- build.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.json b/build.json index a7ce097ae84732..1cf4217146dbfa 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:2021.01.0", - "armhf": "homeassistant/armhf-homeassistant-base:2021.01.0", - "armv7": "homeassistant/armv7-homeassistant-base:2021.01.0", - "amd64": "homeassistant/amd64-homeassistant-base:2021.01.0", - "i386": "homeassistant/i386-homeassistant-base:2021.01.0" + "aarch64": "homeassistant/aarch64-homeassistant-base:2021.01.1", + "armhf": "homeassistant/armhf-homeassistant-base:2021.01.1", + "armv7": "homeassistant/armv7-homeassistant-base:2021.01.1", + "amd64": "homeassistant/amd64-homeassistant-base:2021.01.1", + "i386": "homeassistant/i386-homeassistant-base:2021.01.1" }, "labels": { "io.hass.type": "core" From 7a746adb04bbb2ad462ba51958617e6bf8e797a0 Mon Sep 17 00:00:00 2001 From: bsmappee <58250533+bsmappee@users.noreply.github.com> Date: Fri, 29 Jan 2021 15:01:55 +0100 Subject: [PATCH 0075/1818] Bump pysmappee to 0.2.16 (#45699) --- homeassistant/components/smappee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index ba1005b87d495f..ddbff4e77381e2 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/smappee", "dependencies": ["http"], "requirements": [ - "pysmappee==0.2.13" + "pysmappee==0.2.16" ], "codeowners": [ "@bsmappee" diff --git a/requirements_all.txt b/requirements_all.txt index 2444514aa6cac1..1c732155c0a5a4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1687,7 +1687,7 @@ pyskyqhub==0.1.3 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.2.13 +pysmappee==0.2.16 # homeassistant.components.smartthings pysmartapp==0.3.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2ee2c86c971630..e0671745f1e5c7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -869,7 +869,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.2.13 +pysmappee==0.2.16 # homeassistant.components.smartthings pysmartapp==0.3.3 From 71b67ba57244f1cf89ff80f01a46d5d7a87b915b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 29 Jan 2021 17:57:39 +0100 Subject: [PATCH 0076/1818] Fix mqtt check in ozw (#45709) --- homeassistant/components/ozw/__init__.py | 11 +++++++++-- homeassistant/components/ozw/config_flow.py | 6 +++++- tests/components/ozw/common.py | 3 ++- tests/components/ozw/conftest.py | 12 +++++++++++- tests/components/ozw/test_config_flow.py | 6 ++---- tests/components/ozw/test_init.py | 20 ++++++++++++++++++++ 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/ozw/__init__.py b/homeassistant/components/ozw/__init__.py index 0636671188dd74..a75c05416dcefb 100644 --- a/homeassistant/components/ozw/__init__.py +++ b/homeassistant/components/ozw/__init__.py @@ -21,7 +21,7 @@ from homeassistant.components import mqtt from homeassistant.components.hassio.handler import HassioAPIError -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ENTRY_STATE_LOADED, ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady @@ -95,12 +95,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): manager_options["send_message"] = mqtt_client.send_message else: - if "mqtt" not in hass.config.components: + mqtt_entries = hass.config_entries.async_entries("mqtt") + if not mqtt_entries or mqtt_entries[0].state != ENTRY_STATE_LOADED: _LOGGER.error("MQTT integration is not set up") return False + mqtt_entry = mqtt_entries[0] # MQTT integration only has one entry. + @callback def send_message(topic, payload): + if mqtt_entry.state != ENTRY_STATE_LOADED: + _LOGGER.error("MQTT integration is not set up") + return + mqtt.async_publish(hass, topic, json.dumps(payload)) manager_options["send_message"] = send_message diff --git a/homeassistant/components/ozw/config_flow.py b/homeassistant/components/ozw/config_flow.py index 14d875e0a70faa..00917c0609c932 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -97,7 +97,11 @@ def _async_use_mqtt_integration(self): This is the entry point for the logic that is needed when this integration will depend on the MQTT integration. """ - if "mqtt" not in self.hass.config.components: + mqtt_entries = self.hass.config_entries.async_entries("mqtt") + if ( + not mqtt_entries + or mqtt_entries[0].state != config_entries.ENTRY_STATE_LOADED + ): return self.async_abort(reason="mqtt_required") return self._async_create_entry_from_vars() diff --git a/tests/components/ozw/common.py b/tests/components/ozw/common.py index 6b44d364413632..1467d619afee6b 100644 --- a/tests/components/ozw/common.py +++ b/tests/components/ozw/common.py @@ -10,7 +10,8 @@ async def setup_ozw(hass, entry=None, fixture=None): """Set up OZW and load a dump.""" - hass.config.components.add("mqtt") + mqtt_entry = MockConfigEntry(domain="mqtt", state=config_entries.ENTRY_STATE_LOADED) + mqtt_entry.add_to_hass(hass) if entry is None: entry = MockConfigEntry( diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index 00f8d8e52d2fb7..a59388f118f675 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -4,9 +4,11 @@ import pytest +from homeassistant.config_entries import ENTRY_STATE_LOADED + from .common import MQTTMessage -from tests.common import load_fixture +from tests.common import MockConfigEntry, load_fixture from tests.components.light.conftest import mock_light_profiles # noqa @@ -268,3 +270,11 @@ def mock_get_addon_discovery_info(): "homeassistant.components.hassio.async_get_addon_discovery_info" ) as get_addon_discovery_info: yield get_addon_discovery_info + + +@pytest.fixture(name="mqtt") +async def mock_mqtt_fixture(hass): + """Mock the MQTT integration.""" + mqtt_entry = MockConfigEntry(domain="mqtt", state=ENTRY_STATE_LOADED) + mqtt_entry.add_to_hass(hass) + return mqtt_entry diff --git a/tests/components/ozw/test_config_flow.py b/tests/components/ozw/test_config_flow.py index d1ac413270daa9..0a746398cf9f18 100644 --- a/tests/components/ozw/test_config_flow.py +++ b/tests/components/ozw/test_config_flow.py @@ -79,9 +79,8 @@ def mock_start_addon(): yield start_addon -async def test_user_not_supervisor_create_entry(hass): +async def test_user_not_supervisor_create_entry(hass, mqtt): """Test the user step creates an entry not on Supervisor.""" - hass.config.components.add("mqtt") await setup.async_setup_component(hass, "persistent_notification", {}) with patch( @@ -128,9 +127,8 @@ async def test_one_instance_allowed(hass): assert result["reason"] == "single_instance_allowed" -async def test_not_addon(hass, supervisor): +async def test_not_addon(hass, supervisor, mqtt): """Test opting out of add-on on Supervisor.""" - hass.config.components.add("mqtt") await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/ozw/test_init.py b/tests/components/ozw/test_init.py index ac7ad59f3cbbb2..c76bfd4a3a05d1 100644 --- a/tests/components/ozw/test_init.py +++ b/tests/components/ozw/test_init.py @@ -37,6 +37,26 @@ async def test_setup_entry_without_mqtt(hass): assert not await hass.config_entries.async_setup(entry.entry_id) +async def test_publish_without_mqtt(hass, caplog): + """Test publish without mqtt integration setup.""" + with patch("homeassistant.components.ozw.OZWOptions") as ozw_options: + await setup_ozw(hass) + + send_message = ozw_options.call_args[1]["send_message"] + + mqtt_entries = hass.config_entries.async_entries("mqtt") + mqtt_entry = mqtt_entries[0] + await hass.config_entries.async_remove(mqtt_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.config_entries.async_entries("mqtt") + + # Sending a message should not error with the MQTT integration not set up. + send_message("test_topic", "test_payload") + + assert "MQTT integration is not set up" in caplog.text + + async def test_unload_entry(hass, generic_data, switch_msg, caplog): """Test unload the config entry.""" entry = MockConfigEntry( From 498f8db0d879afbb412b0b9db8fbd197a45b7745 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 29 Jan 2021 20:12:03 +0100 Subject: [PATCH 0077/1818] Updated frontend to 20210127.5 (#45714) --- 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 6f05ab04e6f920..eb455a5a6c1f79 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.3"], + "requirements": ["home-assistant-frontend==20210127.5"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 630eb789d00034..7166874d5aeafd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.3 +home-assistant-frontend==20210127.5 httpx==0.16.1 jinja2>=2.11.2 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 1c732155c0a5a4..7c28eadb2d52a3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.3 +home-assistant-frontend==20210127.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e0671745f1e5c7..c5d913ee485f49 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -402,7 +402,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.3 +home-assistant-frontend==20210127.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 4a2ad442c7fcbfabe8ee607a7a40c63de5739b2c Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 29 Jan 2021 13:30:21 -0700 Subject: [PATCH 0078/1818] Bump simplisafe-python to 9.6.4 (#45716) * Bump simplisafe-python to 9.6.4 * Fix imports --- homeassistant/components/simplisafe/__init__.py | 8 ++++---- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 89f5c40b1ff74f..495ba29fefb524 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -10,10 +10,10 @@ EVENT_CONNECTION_LOST, EVENT_CONNECTION_RESTORED, EVENT_DOORBELL_DETECTED, - EVENT_ENTRY_DETECTED, + EVENT_ENTRY_DELAY, EVENT_LOCK_LOCKED, EVENT_LOCK_UNLOCKED, - EVENT_MOTION_DETECTED, + EVENT_SECRET_ALERT_TRIGGERED, ) import voluptuous as vol @@ -82,8 +82,8 @@ WEBSOCKET_EVENTS_TO_TRIGGER_HASS_EVENT = [ EVENT_CAMERA_MOTION_DETECTED, EVENT_DOORBELL_DETECTED, - EVENT_ENTRY_DETECTED, - EVENT_MOTION_DETECTED, + EVENT_ENTRY_DELAY, + EVENT_SECRET_ALERT_TRIGGERED, ] ATTR_CATEGORY = "category" diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index a502a7908f087c..b18bafb0bbfaa9 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.2"], + "requirements": ["simplisafe-python==9.6.4"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7c28eadb2d52a3..d52e530a09bc91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2028,7 +2028,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.2 +simplisafe-python==9.6.4 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5d913ee485f49..d5923f370493f2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1014,7 +1014,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.2 +simplisafe-python==9.6.4 # homeassistant.components.slack slackclient==2.5.0 From a81a4ad44b2fa0f6df9ed197d29074e4b9836d55 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jan 2021 09:45:46 -0600 Subject: [PATCH 0079/1818] Fix exception when a unifi config entry is ignored (#45735) * Fix exception when a unifi config entry is ignored * Fix existing test --- homeassistant/components/unifi/config_flow.py | 2 +- tests/components/unifi/test_config_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 85203204b2f00e..0ea55c1574770b 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -236,7 +236,7 @@ async def async_step_ssdp(self, discovery_info): def _host_already_configured(self, host): """See if we already have a unifi entry matching the host.""" for entry in self._async_current_entries(): - if not entry.data: + if not entry.data or CONF_CONTROLLER not in entry.data: continue if entry.data[CONF_CONTROLLER][CONF_HOST] == host: return True diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 6eb049b573ac5c..88c1cbe586debe 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -546,7 +546,7 @@ async def test_form_ssdp_gets_form_with_ignored_entry(hass): await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=UNIFI_DOMAIN, - data={}, + data={"not_controller_key": None}, source=config_entries.SOURCE_IGNORE, ) entry.add_to_hass(hass) From 68ea62f5effd10b5fa8bb1448ca5231451c92813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 30 Jan 2021 14:09:16 +0100 Subject: [PATCH 0080/1818] Bump awesomeversion from 21.1.3 to 21.1.6 (#45738) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 5815ddd8ed0915..8bd10f3ed6194e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,7 +13,7 @@ pre-commit==2.9.3 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 -awesomeversion==21.1.3 +awesomeversion==21.1.6 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.10.1 From a8ad51ceb2415bc7fdd92d15807b347a921c532d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 31 Jan 2021 00:51:33 +0100 Subject: [PATCH 0081/1818] Update frontend to 20210127.6 (#45760) --- 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 eb455a5a6c1f79..9d21be79912c90 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.5"], + "requirements": ["home-assistant-frontend==20210127.6"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7166874d5aeafd..d6c26b24a0102c 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.5 +home-assistant-frontend==20210127.6 httpx==0.16.1 jinja2>=2.11.2 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index d52e530a09bc91..d29888056792de 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.5 +home-assistant-frontend==20210127.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d5923f370493f2..18a70e54a409c6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -402,7 +402,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.5 +home-assistant-frontend==20210127.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From bbb18ffec4e5fb0ad1adb47c2fb0c823f5825484 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 10:43:00 -1000 Subject: [PATCH 0082/1818] Add timeout to lutron_caseta to prevent it blocking startup (#45769) --- .../components/lutron_caseta/__init__.py | 26 ++++++-- .../components/lutron_caseta/config_flow.py | 47 ++++++++++----- .../components/lutron_caseta/const.py | 2 + .../lutron_caseta/test_config_flow.py | 60 +++++++++++++------ 4 files changed, 97 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 7526d4874f6713..f349dde8921d9c 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -1,10 +1,12 @@ """Component for interacting with a Lutron Caseta system.""" import asyncio import logging +import ssl from aiolip import LIP from aiolip.data import LIPMode from aiolip.protocol import LIP_BUTTON_PRESS +import async_timeout from pylutron_caseta.smartbridge import Smartbridge import voluptuous as vol @@ -29,6 +31,7 @@ BRIDGE_DEVICE_ID, BRIDGE_LEAP, BRIDGE_LIP, + BRIDGE_TIMEOUT, BUTTON_DEVICES, CONF_CA_CERTS, CONF_CERTFILE, @@ -94,15 +97,26 @@ async def async_setup_entry(hass, config_entry): keyfile = hass.config.path(config_entry.data[CONF_KEYFILE]) certfile = hass.config.path(config_entry.data[CONF_CERTFILE]) ca_certs = hass.config.path(config_entry.data[CONF_CA_CERTS]) + bridge = None - bridge = Smartbridge.create_tls( - hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs - ) + try: + bridge = Smartbridge.create_tls( + hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs + ) + except ssl.SSLError: + _LOGGER.error("Invalid certificate used to connect to bridge at %s.", host) + return False + + timed_out = True + try: + with async_timeout.timeout(BRIDGE_TIMEOUT): + await bridge.connect() + timed_out = False + except asyncio.TimeoutError: + _LOGGER.error("Timeout while trying to connect to bridge at %s.", host) - await bridge.connect() - if not bridge.is_connected(): + if timed_out or not bridge.is_connected(): await bridge.close() - _LOGGER.error("Unable to connect to Lutron Caseta bridge at %s", host) raise ConfigEntryNotReady _LOGGER.debug("Connected to Lutron Caseta bridge via LEAP at %s", host) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index bb76c4b4ff77c8..03aa980c1f8cca 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -2,7 +2,9 @@ import asyncio import logging import os +import ssl +import async_timeout from pylutron_caseta.pairing import PAIR_CA, PAIR_CERT, PAIR_KEY, async_pair from pylutron_caseta.smartbridge import Smartbridge import voluptuous as vol @@ -15,6 +17,7 @@ from .const import ( ABORT_REASON_ALREADY_CONFIGURED, ABORT_REASON_CANNOT_CONNECT, + BRIDGE_TIMEOUT, CONF_CA_CERTS, CONF_CERTFILE, CONF_KEYFILE, @@ -50,6 +53,8 @@ def __init__(self): """Initialize a Lutron Caseta flow.""" self.data = {} self.lutron_id = None + self.tls_assets_validated = False + self.attempted_tls_validation = False async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" @@ -90,11 +95,16 @@ async def async_step_link(self, user_input=None): self._configure_tls_assets() + 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() + ): + self.tls_assets_validated = True + self.attempted_tls_validation = True + if user_input is not None: - if ( - await self.hass.async_add_executor_job(self._tls_assets_exist) - and await self.async_validate_connectable_bridge_config() - ): + if self.tls_assets_validated: # If we previous paired and the tls assets already exist, # we do not need to go though pairing again. return self.async_create_entry(title=self.bridge_id, data=self.data) @@ -205,6 +215,8 @@ async def async_step_import_failed(self, user_input=None): async def async_validate_connectable_bridge_config(self): """Check if we can connect to the bridge with the current config.""" + bridge = None + try: bridge = Smartbridge.create_tls( hostname=self.data[CONF_HOST], @@ -212,16 +224,23 @@ async def async_validate_connectable_bridge_config(self): certfile=self.hass.config.path(self.data[CONF_CERTFILE]), ca_certs=self.hass.config.path(self.data[CONF_CA_CERTS]), ) - - await bridge.connect() - if not bridge.is_connected(): - return False - - await bridge.close() - return True - except Exception: # pylint: disable=broad-except - _LOGGER.exception( - "Unknown exception while checking connectivity to bridge %s", + except ssl.SSLError: + _LOGGER.error( + "Invalid certificate used to connect to bridge at %s.", self.data[CONF_HOST], ) return False + + connected_ok = False + try: + with async_timeout.timeout(BRIDGE_TIMEOUT): + await bridge.connect() + connected_ok = bridge.is_connected() + except asyncio.TimeoutError: + _LOGGER.error( + "Timeout while trying to connect to bridge at %s.", + self.data[CONF_HOST], + ) + + await bridge.close() + return connected_ok diff --git a/homeassistant/components/lutron_caseta/const.py b/homeassistant/components/lutron_caseta/const.py index 5f6032ba6dc220..f8f9ee668c2b06 100644 --- a/homeassistant/components/lutron_caseta/const.py +++ b/homeassistant/components/lutron_caseta/const.py @@ -33,3 +33,5 @@ CONF_TYPE = "type" CONF_SUBTYPE = "subtype" + +BRIDGE_TIMEOUT = 35 diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 8a33fab670bfdf..58377c8e085945 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -1,5 +1,6 @@ """Test the Lutron Caseta config flow.""" import asyncio +import ssl from unittest.mock import AsyncMock, patch from pylutron_caseta.pairing import PAIR_CA, PAIR_CERT, PAIR_KEY @@ -21,6 +22,14 @@ from tests.common import MockConfigEntry +EMPTY_MOCK_CONFIG_ENTRY = { + CONF_HOST: "", + CONF_KEYFILE: "", + CONF_CERTFILE: "", + CONF_CA_CERTS: "", +} + + MOCK_ASYNC_PAIR_SUCCESS = { PAIR_KEY: "mock_key", PAIR_CERT: "mock_cert", @@ -115,21 +124,34 @@ async def test_bridge_cannot_connect(hass): async def test_bridge_cannot_connect_unknown_error(hass): """Test checking for connection and encountering an unknown error.""" - entry_mock_data = { - CONF_HOST: "", - CONF_KEYFILE: "", - CONF_CERTFILE: "", - CONF_CA_CERTS: "", - } - with patch.object(Smartbridge, "create_tls") as create_tls: mock_bridge = MockBridge() - mock_bridge.connect = AsyncMock(side_effect=Exception()) + mock_bridge.connect = AsyncMock(side_effect=asyncio.TimeoutError) create_tls.return_value = mock_bridge result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=entry_mock_data, + data=EMPTY_MOCK_CONFIG_ENTRY, + ) + + assert result["type"] == "form" + assert result["step_id"] == STEP_IMPORT_FAILED + assert result["errors"] == {"base": ERROR_CANNOT_CONNECT} + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == CasetaConfigFlow.ABORT_REASON_CANNOT_CONNECT + + +async def test_bridge_invalid_ssl_error(hass): + """Test checking for connection and encountering invalid ssl certs.""" + + with patch.object(Smartbridge, "create_tls", side_effect=ssl.SSLError): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=EMPTY_MOCK_CONFIG_ENTRY, ) assert result["type"] == "form" @@ -351,23 +373,25 @@ async def test_form_user_reuses_existing_assets_when_pairing_again(hass, tmpdir) assert result["errors"] is None assert result["step_id"] == "user" - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - }, - ) - await hass.async_block_till_done() + with patch.object(Smartbridge, "create_tls") as create_tls: + create_tls.return_value = MockBridge(can_connect=True) + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + }, + ) + await hass.async_block_till_done() + assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch.object(Smartbridge, "create_tls") as create_tls, patch( + with patch( "homeassistant.components.lutron_caseta.async_setup", return_value=True ), patch( "homeassistant.components.lutron_caseta.async_setup_entry", return_value=True, ): - create_tls.return_value = MockBridge(can_connect=True) result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, From 078579de6966d0150a6c7ecf7887749dac48bda2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 31 Jan 2021 16:35:29 +0100 Subject: [PATCH 0083/1818] Bump pychromecast to 8.0.0 (#45776) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 5f3deb365522ed..88dabc8d04d0e9 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==7.7.2"], + "requirements": ["pychromecast==8.0.0"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index d29888056792de..2497674b93f730 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1310,7 +1310,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==7.7.2 +pychromecast==8.0.0 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18a70e54a409c6..76f54932f5bd52 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -678,7 +678,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==7.7.2 +pychromecast==8.0.0 # homeassistant.components.comfoconnect pycomfoconnect==0.4 From 53db89e13d34afdfc2d0cba6de889c2907729be3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 10:38:08 -1000 Subject: [PATCH 0084/1818] Ensure lutron_caseta is only discovered once (#45792) --- homeassistant/components/lutron_caseta/config_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 03aa980c1f8cca..30cbc03ac47f0c 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -84,7 +84,9 @@ async def async_step_zeroconf(self, discovery_info): } return await self.async_step_link() - async_step_homekit = async_step_zeroconf + async def async_step_homekit(self, discovery_info): + """Handle a flow initialized by homekit discovery.""" + return await self.async_step_zeroconf(discovery_info) async def async_step_link(self, user_input=None): """Handle pairing with the hub.""" From 4f731058969acb09ad8be94810cacedec1d4524c Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sun, 31 Jan 2021 20:56:42 +0100 Subject: [PATCH 0085/1818] Prevent AttributError for uninitilized KNX ClimateMode (#45793) --- homeassistant/components/knx/knx_entity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index 296bcb2f5405fb..f4597ad230e623 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -40,12 +40,12 @@ async def async_added_to_hass(self) -> None: """Store register state change callback.""" self._device.register_device_updated_cb(self.after_update_callback) - if isinstance(self._device, XknxClimate): + if isinstance(self._device, XknxClimate) and self._device.mode is not None: self._device.mode.register_device_updated_cb(self.after_update_callback) async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" self._device.unregister_device_updated_cb(self.after_update_callback) - if isinstance(self._device, XknxClimate): + if isinstance(self._device, XknxClimate) and self._device.mode is not None: self._device.mode.unregister_device_updated_cb(self.after_update_callback) From 0df1f8bfbd8c0fc5ca878894d71f97e3848ff36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Mon, 1 Feb 2021 08:34:55 +0100 Subject: [PATCH 0086/1818] Bump pyatv to 0.7.6 (#45799) --- homeassistant/components/apple_tv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index 21b2df308d3f33..66ae2864dc456a 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/apple_tv", "requirements": [ - "pyatv==0.7.5" + "pyatv==0.7.6" ], "zeroconf": [ "_mediaremotetv._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 2497674b93f730..7b4b382051523e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1286,7 +1286,7 @@ pyatmo==4.2.2 pyatome==0.1.1 # homeassistant.components.apple_tv -pyatv==0.7.5 +pyatv==0.7.6 # homeassistant.components.bbox pybbox==0.0.5-alpha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 76f54932f5bd52..7c909313b197c2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -666,7 +666,7 @@ pyatag==0.3.4.4 pyatmo==4.2.2 # homeassistant.components.apple_tv -pyatv==0.7.5 +pyatv==0.7.6 # homeassistant.components.blackbird pyblackbird==0.5 From 18df06d6a6a1b4474d9595fd060aa1f380bb0e92 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 31 Jan 2021 23:14:20 +0100 Subject: [PATCH 0087/1818] Bump zwave-js-server-python to 0.14.2 (#45800) --- 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 08ca9668a99b1d..5c3b82837ca2f7 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.14.1"], + "requirements": ["zwave-js-server-python==0.14.2"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7b4b382051523e..a8d309c319ed45 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.1 +zwave-js-server-python==0.14.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7c909313b197c2..6342a70351951b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1194,4 +1194,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.1 +zwave-js-server-python==0.14.2 From 493f3bc1ce0cc5b11bd5f64c6b536b30729b3a21 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 1 Feb 2021 08:46:36 -0700 Subject: [PATCH 0088/1818] Add stop_cover service for zwave_js (#45805) --- homeassistant/components/zwave_js/cover.py | 17 ++- tests/components/zwave_js/test_cover.py | 125 ++++++++++++++++++--- 2 files changed, 121 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index b7834e8e59cf70..5f473f809575bf 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -21,6 +21,8 @@ LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE +PRESS_BUTTON = True +RELEASE_BUTTON = False async def async_setup_entry( @@ -77,10 +79,17 @@ async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - target_value = self.get_zwave_value("targetValue") - await self.info.node.async_set_value(target_value, 99) + target_value = self.get_zwave_value("Open") + await self.info.node.async_set_value(target_value, PRESS_BUTTON) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - target_value = self.get_zwave_value("targetValue") - await self.info.node.async_set_value(target_value, 0) + target_value = self.get_zwave_value("Close") + await self.info.node.async_set_value(target_value, PRESS_BUTTON) + + async def async_stop_cover(self, **kwargs: Any) -> None: + """Stop cover.""" + target_value = self.get_zwave_value("Open") + await self.info.node.async_set_value(target_value, RELEASE_BUTTON) + target_value = self.get_zwave_value("Close") + await self.info.node.async_set_value(target_value, RELEASE_BUTTON) diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index c327034b61c98a..f014245a5f8a2d 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -95,21 +95,65 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "targetValue", - "propertyName": "targetValue", + "property": "Open", + "propertyName": "Open", "metadata": { - "label": "Target value", - "max": 99, - "min": 0, - "type": "number", + "type": "boolean", "readable": True, "writeable": True, - "label": "Target value", + "label": "Perform a level change (Open)", + "ccSpecific": {"switchType": 3}, }, } - assert args["value"] == 99 + assert args["value"] client.async_send_command.reset_mock() + # Test stop after opening + await hass.services.async_call( + "cover", + "stop_cover", + {"entity_id": WINDOW_COVER_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] + assert open_args["command"] == "node.set_value" + assert open_args["nodeId"] == 6 + assert open_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Open", + "propertyName": "Open", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Open)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not open_args["value"] + + close_args = client.async_send_command.call_args_list[1][0][0] + assert close_args["command"] == "node.set_value" + assert close_args["nodeId"] == 6 + assert close_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Close", + "propertyName": "Close", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Close)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not close_args["value"] # Test position update from value updated event event = Event( @@ -130,6 +174,7 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): }, ) node.receive_event(event) + client.async_send_command.reset_mock() state = hass.states.get(WINDOW_COVER_ENTITY) assert state.state == "open" @@ -141,7 +186,6 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): {"entity_id": WINDOW_COVER_ENTITY}, blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" @@ -150,19 +194,66 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "targetValue", - "propertyName": "targetValue", + "property": "Close", + "propertyName": "Close", "metadata": { - "label": "Target value", - "max": 99, - "min": 0, - "type": "number", + "type": "boolean", "readable": True, "writeable": True, - "label": "Target value", + "label": "Perform a level change (Close)", + "ccSpecific": {"switchType": 3}, }, } - assert args["value"] == 0 + assert args["value"] + + client.async_send_command.reset_mock() + + # Test stop after closing + await hass.services.async_call( + "cover", + "stop_cover", + {"entity_id": WINDOW_COVER_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] + assert open_args["command"] == "node.set_value" + assert open_args["nodeId"] == 6 + assert open_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Open", + "propertyName": "Open", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Open)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not open_args["value"] + + close_args = client.async_send_command.call_args_list[1][0][0] + assert close_args["command"] == "node.set_value" + assert close_args["nodeId"] == 6 + assert close_args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "Close", + "propertyName": "Close", + "metadata": { + "type": "boolean", + "readable": True, + "writeable": True, + "label": "Perform a level change (Close)", + "ccSpecific": {"switchType": 3}, + }, + } + assert not close_args["value"] client.async_send_command.reset_mock() From bf819df3883b5476be6634b0cc675ca0ef93001f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 23:54:39 -1000 Subject: [PATCH 0089/1818] Fix shutdown deadlock with run_callback_threadsafe (#45807) --- homeassistant/core.py | 14 ++++++++++- homeassistant/util/async_.py | 41 ++++++++++++++++++++++++++++++++ tests/test_core.py | 24 +++++++++++++++++++ tests/util/test_async.py | 45 +++++++++++++++++++++++++++++++++++- 4 files changed, 122 insertions(+), 2 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 58d9d1e67540ad..dfdb77a44a8484 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -71,7 +71,11 @@ Unauthorized, ) from homeassistant.util import location, network -from homeassistant.util.async_ import fire_coroutine_threadsafe, run_callback_threadsafe +from homeassistant.util.async_ import ( + fire_coroutine_threadsafe, + run_callback_threadsafe, + shutdown_run_callback_threadsafe, +) import homeassistant.util.dt as dt_util from homeassistant.util.timeout import TimeoutManager from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem @@ -548,6 +552,14 @@ async def async_stop(self, exit_code: int = 0, *, force: bool = False) -> None: # stage 3 self.state = CoreState.not_running self.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE) + + # Prevent run_callback_threadsafe from scheduling any additional + # callbacks in the event loop as callbacks created on the futures + # it returns will never run after the final `self.async_block_till_done` + # which will cause the futures to block forever when waiting for + # the `result()` which will cause a deadlock when shutting down the executor. + shutdown_run_callback_threadsafe(self.loop) + try: async with self.timeout.async_timeout(30): await self.async_block_till_done() diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index ded44473038d6f..f61225502ee2fb 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -10,6 +10,8 @@ _LOGGER = logging.getLogger(__name__) +_SHUTDOWN_RUN_CALLBACK_THREADSAFE = "_shutdown_run_callback_threadsafe" + T = TypeVar("T") @@ -58,6 +60,28 @@ def run_callback() -> None: _LOGGER.warning("Exception on lost future: ", exc_info=True) loop.call_soon_threadsafe(run_callback) + + if hasattr(loop, _SHUTDOWN_RUN_CALLBACK_THREADSAFE): + # + # If the final `HomeAssistant.async_block_till_done` in + # `HomeAssistant.async_stop` has already been called, the callback + # will never run and, `future.result()` will block forever which + # will prevent the thread running this code from shutting down which + # will result in a deadlock when the main thread attempts to shutdown + # the executor and `.join()` the thread running this code. + # + # To prevent this deadlock we do the following on shutdown: + # + # 1. Set the _SHUTDOWN_RUN_CALLBACK_THREADSAFE attr on this function + # by calling `shutdown_run_callback_threadsafe` + # 2. Call `hass.async_block_till_done` at least once after shutdown + # to ensure all callbacks have run + # 3. Raise an exception here to ensure `future.result()` can never be + # called and hit the deadlock since once `shutdown_run_callback_threadsafe` + # we cannot promise the callback will be executed. + # + raise RuntimeError("The event loop is in the process of shutting down.") + return future @@ -139,3 +163,20 @@ async def sem_task(task: Awaitable[Any]) -> Any: return await gather( *(sem_task(task) for task in tasks), return_exceptions=return_exceptions ) + + +def shutdown_run_callback_threadsafe(loop: AbstractEventLoop) -> None: + """Call when run_callback_threadsafe should prevent creating new futures. + + We must finish all callbacks before the executor is shutdown + or we can end up in a deadlock state where: + + `executor.result()` is waiting for its `._condition` + and the executor shutdown is trying to `.join()` the + executor thread. + + This function is considered irreversible and should only ever + be called when Home Assistant is going to shutdown and + python is going to exit. + """ + setattr(loop, _SHUTDOWN_RUN_CALLBACK_THREADSAFE, True) diff --git a/tests/test_core.py b/tests/test_core.py index dbed2b8c0bf5b5..0bf00d92c45971 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -169,6 +169,30 @@ async def test_stage_shutdown(hass): assert len(test_all) == 2 +async def test_shutdown_calls_block_till_done_after_shutdown_run_callback_threadsafe( + hass, +): + """Ensure shutdown_run_callback_threadsafe is called before the final async_block_till_done.""" + stop_calls = [] + + async def _record_block_till_done(): + nonlocal stop_calls + stop_calls.append("async_block_till_done") + + def _record_shutdown_run_callback_threadsafe(loop): + nonlocal stop_calls + stop_calls.append(("shutdown_run_callback_threadsafe", loop)) + + with patch.object(hass, "async_block_till_done", _record_block_till_done), patch( + "homeassistant.core.shutdown_run_callback_threadsafe", + _record_shutdown_run_callback_threadsafe, + ): + await hass.async_stop() + + assert stop_calls[-2] == ("shutdown_run_callback_threadsafe", hass.loop) + assert stop_calls[-1] == "async_block_till_done" + + async def test_pending_sheduler(hass): """Add a coro to pending tasks.""" call_count = [] diff --git a/tests/util/test_async.py b/tests/util/test_async.py index db088ada93e703..d4fdce1e9123ec 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -50,7 +50,8 @@ def test_fire_coroutine_threadsafe_from_inside_event_loop( def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _): """Testing calling run_callback_threadsafe from inside an event loop.""" callback = MagicMock() - loop = MagicMock() + + loop = Mock(spec=["call_soon_threadsafe"]) loop._thread_ident = None mock_ident.return_value = 5 @@ -168,3 +169,45 @@ async def _increment_runs_if_in_time(): ) assert results == [2, 2, -1, -1] + + +async def test_shutdown_run_callback_threadsafe(hass): + """Test we can shutdown run_callback_threadsafe.""" + hasync.shutdown_run_callback_threadsafe(hass.loop) + callback = MagicMock() + + with pytest.raises(RuntimeError): + hasync.run_callback_threadsafe(hass.loop, callback) + + +async def test_run_callback_threadsafe(hass): + """Test run_callback_threadsafe runs code in the event loop.""" + it_ran = False + + def callback(): + nonlocal it_ran + it_ran = True + + assert hasync.run_callback_threadsafe(hass.loop, callback) + assert it_ran is False + + # Verify that async_block_till_done will flush + # out the callback + await hass.async_block_till_done() + assert it_ran is True + + +async def test_callback_is_always_scheduled(hass): + """Test run_callback_threadsafe always calls call_soon_threadsafe before checking for shutdown.""" + # We have to check the shutdown state AFTER the callback is scheduled otherwise + # the function could continue on and the caller call `future.result()` after + # the point in the main thread where callbacks are no longer run. + + callback = MagicMock() + hasync.shutdown_run_callback_threadsafe(hass.loop) + + with patch.object(hass.loop, "call_soon_threadsafe") as mock_call_soon_threadsafe: + with pytest.raises(RuntimeError): + hasync.run_callback_threadsafe(hass.loop, callback) + + mock_call_soon_threadsafe.assert_called_once() From 760b75a5c1ac0bddaacfed954950cdf2d4b38a9a Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Feb 2021 13:46:06 -0600 Subject: [PATCH 0090/1818] Search all endpoints for value in zwave_js (#45809) Co-authored-by: Martin Hjelmare --- homeassistant/components/zwave_js/climate.py | 1 + homeassistant/components/zwave_js/entity.py | 31 +- tests/components/zwave_js/conftest.py | 26 + tests/components/zwave_js/test_climate.py | 9 + ..._ct100_plus_different_endpoints_state.json | 727 ++++++++++++++++++ 5 files changed, 787 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 07d4a3f7d0f419..6c0b4a0335eaaa 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -140,6 +140,7 @@ def __init__( THERMOSTAT_CURRENT_TEMP_PROPERTY, command_class=CommandClass.SENSOR_MULTILEVEL, add_to_watched_value_ids=True, + check_all_endpoints=True, ) self._set_modes_and_presets() diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 5c64ddbc496b0e..84870ba75f4ef5 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -121,6 +121,7 @@ def get_zwave_value( endpoint: Optional[int] = None, value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, + check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted @@ -129,17 +130,33 @@ def get_zwave_value( command_class = self.info.primary_value.command_class if endpoint is None: endpoint = self.info.primary_value.endpoint + + # Build partial event data dictionary so we can change the endpoint later + partial_evt_data = { + "commandClass": command_class, + "property": value_property, + "propertyKeyName": value_property_key_name, + } + # lookup value by value_id value_id = get_value_id( - self.info.node, - { - "commandClass": command_class, - "endpoint": endpoint, - "property": value_property, - "propertyKeyName": value_property_key_name, - }, + self.info.node, {**partial_evt_data, "endpoint": endpoint} ) return_value = self.info.node.values.get(value_id) + + # If we haven't found a value and check_all_endpoints is True, we should + # return the first value we can find on any other endpoint + if return_value is None and check_all_endpoints: + for endpoint_ in self.info.node.endpoints: + if endpoint_.index != self.info.primary_value.endpoint: + value_id = get_value_id( + self.info.node, + {**partial_evt_data, "endpoint": endpoint_.index}, + ) + return_value = self.info.node.values.get(value_id) + if return_value: + break + # add to watched_ids list so we will be triggered when the value updates if ( return_value diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 470b4c0227b8c4..b6cbc911b6aca7 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -111,6 +111,22 @@ def climate_radio_thermostat_ct100_plus_state_fixture(): ) +@pytest.fixture( + name="climate_radio_thermostat_ct100_plus_different_endpoints_state", + scope="session", +) +def climate_radio_thermostat_ct100_plus_different_endpoints_state_fixture(): + """Load the thermostat fixture state with values on different endpoints. + + This device is a radio thermostat ct100. + """ + return json.loads( + load_fixture( + "zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json" + ) + ) + + @pytest.fixture(name="nortek_thermostat_state", scope="session") def nortek_thermostat_state_fixture(): """Load the nortek thermostat node state fixture data.""" @@ -231,6 +247,16 @@ def climate_radio_thermostat_ct100_plus_fixture( return node +@pytest.fixture(name="climate_radio_thermostat_ct100_plus_different_endpoints") +def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( + client, climate_radio_thermostat_ct100_plus_different_endpoints_state +): + """Mock a climate radio thermostat ct100 plus node with values on different endpoints.""" + node = Node(client, climate_radio_thermostat_ct100_plus_different_endpoints_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="nortek_thermostat") def nortek_thermostat_fixture(client, nortek_thermostat_state): """Mock a nortek thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index aca1022f8b0bc7..f7deefc1360f1d 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -324,3 +324,12 @@ async def test_thermostat_v2( }, blocking=True, ) + + +async def test_thermostat_different_endpoints( + hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration +): + """Test an entity with values on a different endpoint from the primary value.""" + state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY) + + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 diff --git a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json new file mode 100644 index 00000000000000..ea38dfd9d6b0f6 --- /dev/null +++ b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json @@ -0,0 +1,727 @@ +{ + "nodeId": 26, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Thermostat General V2", + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 152, + "productId": 256, + "productType": 25602, + "firmwareVersion": "10.7", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 152, + "manufacturer": "Radio Thermostat Company of America (RTC)", + "label": "CT100 Plus", + "description": "Z-Wave Thermostat", + "devices": [{ "productType": "0x6402", "productId": "0x0100" }], + "firmwareVersion": { "min": "0.0", "max": "255.255" }, + "paramInformation": { "_map": {} } + }, + "label": "CT100 Plus", + "neighbors": [1, 2, 3, 4, 23], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 2, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 26, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608 + }, + { "nodeId": 26, "index": 1 }, + { + "nodeId": 26, + "index": 2, + "installerIcon": 3328, + "userIcon": 3333 + } + ], + "values": [ + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "manufacturerId", + "propertyName": "manufacturerId", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 152, + "ccVersion": 2 + }, + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "productType", + "propertyName": "productType", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 25602, + "ccVersion": 2 + }, + { + "commandClassName": "Manufacturer Specific", + "commandClass": 114, + "endpoint": 0, + "property": "productId", + "propertyName": "productId", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 256, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "2": "Cool", + "3": "Auto", + "11": "Energy heat", + "12": "Energy cool" + } + }, + "value": 1, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "manufacturerData", + "propertyName": "manufacturerData", + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 1 } + }, + "value": 72, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 2, + "propertyName": "setpoint", + "propertyKeyName": "Cooling", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 2 } + }, + "value": 73, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 11, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Heating", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 11 } + }, + "value": 62, + "ccVersion": 2 + }, + { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 12, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Cooling", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0F", + "ccSpecific": { "setpointType": 12 } + }, + "value": 85, + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "libraryType", + "propertyName": "libraryType", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3, + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "protocolVersion", + "propertyName": "protocolVersion", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.24", + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["10.7"], + "ccVersion": 2 + }, + { + "commandClassName": "Version", + "commandClass": 134, + "endpoint": 0, + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + }, + "ccVersion": 2 + }, + { + "commandClassName": "Indicator", + "commandClass": 135, + "endpoint": 0, + "property": "value", + "propertyName": "value", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Indicator value", + "ccSpecific": { "indicatorId": 0 } + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Thermostat Operating State", + "commandClass": 66, + "endpoint": 0, + "property": "state", + "propertyName": "state", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Operating state", + "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, + "ccVersion": 2 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 1, + "propertyName": "Temperature Reporting Threshold", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 4, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F" + }, + "label": "Temperature Reporting Threshold", + "description": "Reporting threshold for changes in the ambient temperature", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 2, + "propertyName": "HVAC Settings", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 4, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "HVAC Settings", + "description": "Configured HVAC settings", + "isFromConfig": true + }, + "value": 17891329, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 4, + "propertyName": "Power Status", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 1, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Power Status", + "description": "C-Wire / Battery Status", + "isFromConfig": true + }, + "value": 1, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 5, + "propertyName": "Humidity Reporting Threshold", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "3% RH", + "2": "5% RH", + "3": "10% RH" + }, + "label": "Humidity Reporting Threshold", + "description": "Reporting threshold for changes in the relative humidity", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 6, + "propertyName": "Auxiliary/Emergency", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Auxiliary/Emergency heat disabled", + "1": "Auxiliary/Emergency heat enabled" + }, + "label": "Auxiliary/Emergency", + "description": "Enables or disables auxiliary / emergency heating", + "isFromConfig": true + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 7, + "propertyName": "Thermostat Swing Temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 8, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "0.5\u00b0 F", + "2": "1.0\u00b0 F", + "3": "1.5\u00b0 F", + "4": "2.0\u00b0 F", + "5": "2.5\u00b0 F", + "6": "3.0\u00b0 F", + "7": "3.5\u00b0 F", + "8": "4.0\u00b0 F" + }, + "label": "Thermostat Swing Temperature", + "description": "Variance allowed from setpoint to engage HVAC", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 8, + "propertyName": "Thermostat Diff Temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 4, + "max": 12, + "default": 4, + "format": 0, + "allowManualEntry": false, + "states": { + "4": "2.0\u00b0 F", + "8": "4.0\u00b0 F", + "12": "6.0\u00b0 F" + }, + "label": "Thermostat Diff Temperature", + "description": "Configures additional stages", + "isFromConfig": true + }, + "value": 1028, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 9, + "propertyName": "Thermostat Recovery Mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 2, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "Fast recovery mode", + "2": "Economy recovery mode" + }, + "label": "Thermostat Recovery Mode", + "description": "Fast or Economy recovery mode", + "isFromConfig": true + }, + "value": 2, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 10, + "propertyName": "Temperature Reporting Filter", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 124, + "default": 124, + "format": 0, + "allowManualEntry": true, + "label": "Temperature Reporting Filter", + "description": "Upper/Lower bounds for thermostat temperature reporting", + "isFromConfig": true + }, + "value": 32000, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 11, + "propertyName": "Simple UI Mode", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Normal mode enabled", + "1": "Simple mode enabled" + }, + "label": "Simple UI Mode", + "description": "Simple mode enable/disable", + "isFromConfig": true + }, + "value": 1, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 12, + "propertyName": "Multicast", + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Multicast disabled", + "1": "Multicast enabled" + }, + "label": "Multicast", + "description": "Enable or disables Multicast", + "isFromConfig": true + }, + "value": 0, + "ccVersion": 1 + }, + { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 3, + "propertyName": "Utility Lock Enable/Disable", + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Utility lock disabled", + "1": "Utility lock enabled" + }, + "label": "Utility Lock Enable/Disable", + "description": "Prevents setpoint changes at thermostat", + "isFromConfig": true + }, + "ccVersion": 1 + }, + { + "commandClassName": "Battery", + "commandClass": 128, + "endpoint": 0, + "property": "level", + "propertyName": "level", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 100, + "ccVersion": 1 + }, + { + "commandClassName": "Battery", + "commandClass": 128, + "endpoint": 0, + "property": "isLow", + "propertyName": "isLow", + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Low battery level" + }, + "value": false, + "ccVersion": 1 + }, + { + "commandClassName": "Multilevel Sensor", + "commandClass": 49, + "endpoint": 2, + "property": "Air temperature", + "propertyName": "Air temperature", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0F", + "label": "Air temperature", + "ccSpecific": { "sensorType": 1, "scale": 1 } + }, + "value": 72.5, + "ccVersion": 5 + }, + { + "commandClassName": "Multilevel Sensor", + "commandClass": 49, + "endpoint": 2, + "property": "Humidity", + "propertyName": "Humidity", + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "%", + "label": "Humidity", + "ccSpecific": { "sensorType": 5, "scale": 0 } + }, + "value": 20, + "ccVersion": 5 + } + ] +} \ No newline at end of file From 000def5ded462fd92744635770632a219bc622ea Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Feb 2021 02:45:24 -0600 Subject: [PATCH 0091/1818] Add zwave_js binary sensors property name for Notification CC (#45810) --- homeassistant/components/zwave_js/binary_sensor.py | 8 ++++++++ tests/components/zwave_js/common.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 6dc5cc58df544e..42394fe127ccca 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -349,6 +349,14 @@ def is_on(self) -> bool: return self.info.primary_value.value in self._mapping_info["states"] return bool(self.info.primary_value.value != 0) + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + node_name = self.info.node.name or self.info.node.device_config.description + property_name = self.info.primary_value.property_name + property_key_name = self.info.primary_value.property_key_name + return f"{node_name}: {property_name}: {property_key_name}" + @property def device_class(self) -> Optional[str]: """Return device class.""" diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index a7be137657c9d3..399b009f4c2299 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -6,7 +6,9 @@ LOW_BATTERY_BINARY_SENSOR = "binary_sensor.multisensor_6_low_battery_level" ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any" DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any" -NOTIFICATION_MOTION_BINARY_SENSOR = "binary_sensor.multisensor_6_motion_sensor_status" +NOTIFICATION_MOTION_BINARY_SENSOR = ( + "binary_sensor.multisensor_6_home_security_motion_sensor_status" +) PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( "binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door" ) From 938e5ff435b12848be75dc0c482a7de65dc533b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jan 2021 23:15:20 -1000 Subject: [PATCH 0092/1818] Fix missing async for lutron_caseta timeout (#45812) --- homeassistant/components/lutron_caseta/__init__.py | 6 +++--- homeassistant/components/lutron_caseta/config_flow.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index f349dde8921d9c..73eb0b83fa6e35 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -104,16 +104,16 @@ async def async_setup_entry(hass, config_entry): hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs ) except ssl.SSLError: - _LOGGER.error("Invalid certificate used to connect to bridge at %s.", host) + _LOGGER.error("Invalid certificate used to connect to bridge at %s", host) return False timed_out = True try: - with async_timeout.timeout(BRIDGE_TIMEOUT): + async with async_timeout.timeout(BRIDGE_TIMEOUT): await bridge.connect() timed_out = False except asyncio.TimeoutError: - _LOGGER.error("Timeout while trying to connect to bridge at %s.", host) + _LOGGER.error("Timeout while trying to connect to bridge at %s", host) if timed_out or not bridge.is_connected(): await bridge.close() diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 30cbc03ac47f0c..ab9865f999a0b9 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -228,19 +228,19 @@ async def async_validate_connectable_bridge_config(self): ) except ssl.SSLError: _LOGGER.error( - "Invalid certificate used to connect to bridge at %s.", + "Invalid certificate used to connect to bridge at %s", self.data[CONF_HOST], ) return False connected_ok = False try: - with async_timeout.timeout(BRIDGE_TIMEOUT): + 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.", + "Timeout while trying to connect to bridge at %s", self.data[CONF_HOST], ) From b705468b57d626f2e91038305ed43d45f3c3282b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 1 Feb 2021 10:54:07 +0100 Subject: [PATCH 0093/1818] Bump zwave-js-server-python to 0.15.0 (#45813) --- 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 5c3b82837ca2f7..586a6492a1a962 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.14.2"], + "requirements": ["zwave-js-server-python==0.15.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index a8d309c319ed45..312eb1d80f9f94 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.2 +zwave-js-server-python==0.15.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6342a70351951b..3809293d874a45 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1194,4 +1194,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.14.2 +zwave-js-server-python==0.15.0 From cd1c8b78a125ac7b8d2335e6c82ce8b3a72786d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 1 Feb 2021 16:54:25 +0100 Subject: [PATCH 0094/1818] Bump awesomeversion from 21.1.6 to 21.2.0 (#45821) --- requirements_test.txt | 2 +- script/hassfest/manifest.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8bd10f3ed6194e..380240e3ffc5c2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,7 +13,7 @@ pre-commit==2.9.3 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 -awesomeversion==21.1.6 +awesomeversion==21.2.0 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.10.1 diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index e02df86f4e1899..3beb6aadfc5fde 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -59,6 +59,7 @@ def verify_version(value: str): AwesomeVersionStrategy.SEMVER, AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.BUILDVER, + AwesomeVersionStrategy.PEP440, ]: raise vol.Invalid( f"'{version}' is not a valid version. This will cause a future version of Home Assistant to block this integration.", From 88a5ff4a51251831f55474c0ebb0361cd6b498c8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Feb 2021 22:32:23 +0000 Subject: [PATCH 0095/1818] Bumped version to 2021.2.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 52cbee39523eb8..e946a8ae8e26e4 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From b4559a172c73143e3aca0847ec29491d25937d61 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 1 Feb 2021 23:47:58 +0100 Subject: [PATCH 0096/1818] Add value notification events to zwave_js integration (#45814) --- homeassistant/components/zwave_js/__init__.py | 52 ++++++- homeassistant/components/zwave_js/const.py | 15 ++ homeassistant/components/zwave_js/entity.py | 16 ++- tests/components/zwave_js/test_events.py | 130 ++++++++++++++++++ 4 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 tests/components/zwave_js/test_events.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index c995749f924c00..2b4b33e9b88d59 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,11 +1,11 @@ """The Z-Wave JS integration.""" import asyncio import logging -from typing import Tuple from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode +from zwave_js_server.model.value import ValueNotification from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.config_entries import ConfigEntry @@ -18,14 +18,28 @@ from .api import async_register_api from .const import ( + ATTR_COMMAND_CLASS, + ATTR_COMMAND_CLASS_NAME, + ATTR_DEVICE_ID, + ATTR_DOMAIN, + ATTR_ENDPOINT, + ATTR_HOME_ID, + ATTR_LABEL, + ATTR_NODE_ID, + ATTR_PROPERTY_KEY_NAME, + ATTR_PROPERTY_NAME, + ATTR_TYPE, + ATTR_VALUE, CONF_INTEGRATION_CREATED_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, PLATFORMS, + ZWAVE_JS_EVENT, ) from .discovery import async_discover_values +from .entity import get_device_id LOGGER = logging.getLogger(__name__) CONNECT_TIMEOUT = 10 @@ -37,12 +51,6 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: return True -@callback -def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: - """Get device registry identifier for Z-Wave node.""" - return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") - - @callback def register_node_in_dev_reg( hass: HomeAssistant, @@ -106,6 +114,11 @@ def async_on_node_ready(node: ZwaveNode) -> None: async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) + # add listener for stateless node events (value notification) + node.on( + "value notification", + lambda event: async_on_value_notification(event["value_notification"]), + ) @callback def async_on_node_added(node: ZwaveNode) -> None: @@ -134,6 +147,31 @@ def async_on_node_removed(node: ZwaveNode) -> None: # note: removal of entity registry is handled by core dev_reg.async_remove_device(device.id) + @callback + def async_on_value_notification(notification: ValueNotification) -> None: + """Relay stateless value notification events from Z-Wave nodes to hass.""" + device = dev_reg.async_get_device({get_device_id(client, notification.node)}) + value = notification.value + if notification.metadata.states: + value = notification.metadata.states.get(str(value), value) + hass.bus.async_fire( + ZWAVE_JS_EVENT, + { + ATTR_TYPE: "value_notification", + ATTR_DOMAIN: DOMAIN, + ATTR_NODE_ID: notification.node.node_id, + ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_ENDPOINT: notification.endpoint, + ATTR_DEVICE_ID: device.id, + ATTR_COMMAND_CLASS: notification.command_class, + ATTR_COMMAND_CLASS_NAME: notification.command_class_name, + ATTR_LABEL: notification.metadata.label, + ATTR_PROPERTY_NAME: notification.property_name, + ATTR_PROPERTY_KEY_NAME: notification.property_key_name, + ATTR_VALUE: value, + }, + ) + async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await client.disconnect() diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 526a8429bd429f..163f4fff9ac4b7 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -17,3 +17,18 @@ DATA_UNSUBSCRIBE = "unsubs" EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry" + +# constants for events +ZWAVE_JS_EVENT = f"{DOMAIN}_event" +ATTR_NODE_ID = "node_id" +ATTR_HOME_ID = "home_id" +ATTR_ENDPOINT = "endpoint" +ATTR_LABEL = "label" +ATTR_VALUE = "value" +ATTR_COMMAND_CLASS = "command_class" +ATTR_COMMAND_CLASS_NAME = "command_class_name" +ATTR_TYPE = "type" +ATTR_DOMAIN = "domain" +ATTR_DEVICE_ID = "device_id" +ATTR_PROPERTY_NAME = "property_name" +ATTR_PROPERTY_KEY_NAME = "property_key_name" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 84870ba75f4ef5..9626ae9a888076 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,9 +1,10 @@ """Generic Z-Wave Entity Class.""" import logging -from typing import Optional, Union +from typing import Optional, Tuple, Union from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from homeassistant.config_entries import ConfigEntry @@ -19,6 +20,12 @@ EVENT_VALUE_UPDATED = "value updated" +@callback +def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: + """Get device registry identifier for Z-Wave node.""" + return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") + + class ZWaveBaseEntity(Entity): """Generic Entity Class for a Z-Wave Device.""" @@ -60,12 +67,7 @@ def device_info(self) -> dict: """Return device information for the device registry.""" # device is precreated in main handler return { - "identifiers": { - ( - DOMAIN, - f"{self.client.driver.controller.home_id}-{self.info.node.node_id}", - ) - }, + "identifiers": {get_device_id(self.client, self.info.node)}, } @property diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py new file mode 100644 index 00000000000000..c4280ebb50ddbb --- /dev/null +++ b/tests/components/zwave_js/test_events.py @@ -0,0 +1,130 @@ +"""Test Z-Wave JS (value notification) events.""" +from zwave_js_server.event import Event + +from tests.common import async_capture_events + + +async def test_scenes(hass, hank_binary_switch, integration, client): + """Test scene events.""" + # just pick a random node to fake the value notification events + node = hank_binary_switch + events = async_capture_events(hass, "zwave_js_event") + + # Publish fake Basic Set value notification + event = Event( + type="value notification", + data={ + "source": "node", + "event": "value notification", + "nodeId": 32, + "args": { + "commandClassName": "Basic", + "commandClass": 32, + "endpoint": 0, + "property": "event", + "propertyName": "event", + "value": 255, + "metadata": { + "type": "number", + "readable": True, + "writeable": False, + "min": 0, + "max": 255, + "label": "Event value", + }, + "ccVersion": 1, + }, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["home_id"] == client.driver.controller.home_id + assert events[0].data["node_id"] == 32 + assert events[0].data["endpoint"] == 0 + assert events[0].data["command_class"] == 32 + assert events[0].data["command_class_name"] == "Basic" + assert events[0].data["label"] == "Event value" + assert events[0].data["value"] == 255 + + # Publish fake Scene Activation value notification + event = Event( + type="value notification", + data={ + "source": "node", + "event": "value notification", + "nodeId": 32, + "args": { + "commandClassName": "Scene Activation", + "commandClass": 43, + "endpoint": 0, + "property": "SceneID", + "propertyName": "SceneID", + "value": 16, + "metadata": { + "type": "number", + "readable": True, + "writeable": False, + "min": 0, + "max": 255, + "label": "Scene ID", + }, + "ccVersion": 3, + }, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 2 + assert events[1].data["command_class"] == 43 + assert events[1].data["command_class_name"] == "Scene Activation" + assert events[1].data["label"] == "Scene ID" + assert events[1].data["value"] == 16 + + # Publish fake Central Scene value notification + event = Event( + type="value notification", + data={ + "source": "node", + "event": "value notification", + "nodeId": 32, + "args": { + "commandClassName": "Central Scene", + "commandClass": 91, + "endpoint": 0, + "property": "scene", + "propertyKey": "001", + "propertyName": "scene", + "propertyKeyName": "001", + "value": 4, + "metadata": { + "type": "number", + "readable": True, + "writeable": False, + "min": 0, + "max": 255, + "label": "Scene 001", + "states": { + "0": "KeyPressed", + "1": "KeyReleased", + "2": "KeyHeldDown", + "3": "KeyPressed2x", + "4": "KeyPressed3x", + "5": "KeyPressed4x", + "6": "KeyPressed5x", + }, + }, + "ccVersion": 3, + }, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 3 + assert events[2].data["command_class"] == 91 + assert events[2].data["command_class_name"] == "Central Scene" + assert events[2].data["label"] == "Scene 001" + assert events[2].data["value"] == "KeyPressed3x" From d38d8a542d5d9648be580b396e975765a88d5bc4 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 01:01:19 +0100 Subject: [PATCH 0097/1818] Upgrade colorlog to 4.7.2 (#45840) --- homeassistant/scripts/check_config.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 07a6a54e402eb3..992fce2ac87585 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -17,7 +17,7 @@ # mypy: allow-untyped-calls, allow-untyped-defs -REQUIREMENTS = ("colorlog==4.6.2",) +REQUIREMENTS = ("colorlog==4.7.2",) _LOGGER = logging.getLogger(__name__) # pylint: disable=protected-access diff --git a/requirements_all.txt b/requirements_all.txt index 8a74e574f04a68..41d4c6c3e39afe 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -431,7 +431,7 @@ coinbase==2.1.0 coinmarketcap==5.0.3 # homeassistant.scripts.check_config -colorlog==4.6.2 +colorlog==4.7.2 # homeassistant.components.color_extractor colorthief==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 99f1c997ec1c6e..164c7df2396b26 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -228,7 +228,7 @@ caldav==0.7.1 coinmarketcap==5.0.3 # homeassistant.scripts.check_config -colorlog==4.6.2 +colorlog==4.7.2 # homeassistant.components.color_extractor colorthief==0.2.1 From f2286d481150a61428e2335e597bb9953646817f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 01:01:39 +0100 Subject: [PATCH 0098/1818] Upgrade TwitterAPI to 2.6.5 (#45842) --- homeassistant/components/twitter/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json index acd47253b82963..cd3a12255a2161 100644 --- a/homeassistant/components/twitter/manifest.json +++ b/homeassistant/components/twitter/manifest.json @@ -2,6 +2,6 @@ "domain": "twitter", "name": "Twitter", "documentation": "https://www.home-assistant.io/integrations/twitter", - "requirements": ["TwitterAPI==2.6.3"], + "requirements": ["TwitterAPI==2.6.5"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 41d4c6c3e39afe..80cbcf7cd5f971 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -78,7 +78,7 @@ RtmAPI==0.7.2 TravisPy==0.3.5 # homeassistant.components.twitter -TwitterAPI==2.6.3 +TwitterAPI==2.6.5 # homeassistant.components.tof # VL53L1X2==0.1.5 From 253ae3f4236e208195379471f80adb9a32c9ce13 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 2 Feb 2021 00:09:25 +0000 Subject: [PATCH 0099/1818] Lyric Code Improvements (#45819) Co-authored-by: J. Nick Koston --- homeassistant/components/lyric/__init__.py | 4 +-- homeassistant/components/lyric/climate.py | 35 ++++++++++------------ tests/components/lyric/test_config_flow.py | 14 ++++++--- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index d29fca09166399..0697dfbfd35da8 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -85,7 +85,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: client = ConfigEntryLyricClient(session, oauth_session) client_id = hass.data[DOMAIN][CONF_CLIENT_ID] - lyric: Lyric = Lyric(client, client_id) + lyric = Lyric(client, client_id) async def async_update_data() -> Lyric: """Fetch data from Lyric.""" @@ -93,7 +93,7 @@ async def async_update_data() -> Lyric: async with async_timeout.timeout(60): await lyric.get_locations() return lyric - except (*LYRIC_EXCEPTIONS, TimeoutError) as exception: + except LYRIC_EXCEPTIONS as exception: raise UpdateFailed(exception) from exception coordinator = DataUpdateCoordinator( diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index bcfefef9c93ac1..41e8fa90b671e5 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -82,7 +82,11 @@ async def async_setup_entry( for location in coordinator.data.locations: for device in location.devices: - entities.append(LyricClimate(hass, coordinator, location, device)) + entities.append( + LyricClimate( + coordinator, location, device, hass.config.units.temperature_unit + ) + ) async_add_entities(entities, True) @@ -100,13 +104,13 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): def __init__( self, - hass: HomeAssistantType, coordinator: DataUpdateCoordinator, location: LyricLocation, device: LyricDevice, + temperature_unit: str, ) -> None: """Initialize Honeywell Lyric climate entity.""" - self._temperature_unit = hass.config.units.temperature_unit + self._temperature_unit = temperature_unit # Setup supported hvac modes self._hvac_modes = [HVAC_MODE_OFF] @@ -161,23 +165,26 @@ def hvac_modes(self) -> List[str]: @property def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" - device: LyricDevice = self.device + device = self.device if not device.hasDualSetpointStatus: return device.changeableValues.heatSetpoint + return None @property def target_temperature_low(self) -> Optional[float]: """Return the upper bound temperature we try to reach.""" - device: LyricDevice = self.device + device = self.device if device.hasDualSetpointStatus: return device.changeableValues.coolSetpoint + return None @property def target_temperature_high(self) -> Optional[float]: """Return the upper bound temperature we try to reach.""" - device: LyricDevice = self.device + device = self.device if device.hasDualSetpointStatus: return device.changeableValues.heatSetpoint + return None @property def preset_mode(self) -> Optional[str]: @@ -198,7 +205,7 @@ def preset_modes(self) -> Optional[List[str]]: @property def min_temp(self) -> float: """Identify min_temp in Lyric API or defaults if not available.""" - device: LyricDevice = self.device + device = self.device if LYRIC_HVAC_MODE_COOL in device.allowedModes: return device.minCoolSetpoint return device.minHeatSetpoint @@ -206,7 +213,7 @@ def min_temp(self) -> float: @property def max_temp(self) -> float: """Identify max_temp in Lyric API or defaults if not available.""" - device: LyricDevice = self.device + device = self.device if LYRIC_HVAC_MODE_HEAT in device.allowedModes: return device.maxHeatSetpoint return device.maxCoolSetpoint @@ -216,7 +223,7 @@ async def async_set_temperature(self, **kwargs) -> None: target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) - device: LyricDevice = self.device + device = self.device if device.hasDualSetpointStatus: if target_temp_low is not None and target_temp_high is not None: temp = (target_temp_low, target_temp_high) @@ -255,16 +262,6 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: _LOGGER.error(exception) await self.coordinator.async_refresh() - async def async_set_preset_period(self, period: str) -> None: - """Set preset period (time).""" - try: - await self._update_thermostat( - self.location, self.device, nextPeriodTime=period - ) - except LYRIC_EXCEPTIONS as exception: - _LOGGER.error(exception) - await self.coordinator.async_refresh() - async def async_set_hold_time(self, time_period: str) -> None: """Set the time to hold until.""" _LOGGER.debug("set_hold_time: %s", time_period) diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index 24b6b68d731cfe..78fd90134664af 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -34,9 +34,9 @@ async def mock_impl(hass): async def test_abort_if_no_configuration(hass): """Check flow abort when no configuration.""" - flow = config_flow.OAuth2FlowHandler() - flow.hass = hass - result = await flow.async_step_user() + 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"] == "missing_configuration" @@ -114,8 +114,14 @@ async def test_full_flow( assert len(mock_setup.mock_calls) == 1 -async def test_abort_if_authorization_timeout(hass, mock_impl): +async def test_abort_if_authorization_timeout( + hass, mock_impl, current_request_with_host +): """Check Somfy authorization timeout.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + flow = config_flow.OAuth2FlowHandler() flow.hass = hass From 3a77ef02e4f17eb320a90e69018afaebec022fa6 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 2 Feb 2021 01:51:20 +0000 Subject: [PATCH 0100/1818] Add sensors to Lyric integration (#45791) Co-authored-by: J. Nick Koston --- .coveragerc | 1 + homeassistant/components/lyric/__init__.py | 2 +- homeassistant/components/lyric/sensor.py | 251 +++++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/lyric/sensor.py diff --git a/.coveragerc b/.coveragerc index 3a7f33f6050b91..47d9c84ba0ec1d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -521,6 +521,7 @@ omit = homeassistant/components/lyric/__init__.py homeassistant/components/lyric/api.py homeassistant/components/lyric/climate.py + homeassistant/components/lyric/sensor.py homeassistant/components/magicseaweed/sensor.py homeassistant/components/mailgun/notify.py homeassistant/components/map/* diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 0697dfbfd35da8..12990d66ba93c2 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -44,7 +44,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["climate"] +PLATFORMS = ["climate", "sensor"] async def async_setup(hass: HomeAssistant, config: dict): diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py new file mode 100644 index 00000000000000..c4950f119d9068 --- /dev/null +++ b/homeassistant/components/lyric/sensor.py @@ -0,0 +1,251 @@ +"""Support for Honeywell Lyric sensor platform.""" +from datetime import datetime, timedelta + +from aiolyric.objects.device import LyricDevice +from aiolyric.objects.location import LyricLocation + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, +) +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util import dt as dt_util + +from . import LyricDeviceEntity +from .const import ( + DOMAIN, + PRESET_HOLD_UNTIL, + PRESET_NO_HOLD, + PRESET_PERMANENT_HOLD, + PRESET_TEMPORARY_HOLD, + PRESET_VACATION_HOLD, +) + +LYRIC_SETPOINT_STATUS_NAMES = { + PRESET_NO_HOLD: "Following Schedule", + PRESET_PERMANENT_HOLD: "Held Permanently", + PRESET_TEMPORARY_HOLD: "Held Temporarily", + PRESET_VACATION_HOLD: "Holiday", +} + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Honeywell Lyric sensor platform based on a config entry.""" + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + entities = [] + + for location in coordinator.data.locations: + for device in location.devices: + cls_list = [] + if device.indoorTemperature: + cls_list.append(LyricIndoorTemperatureSensor) + if device.outdoorTemperature: + cls_list.append(LyricOutdoorTemperatureSensor) + if device.displayedOutdoorHumidity: + cls_list.append(LyricOutdoorHumiditySensor) + if device.changeableValues: + if device.changeableValues.nextPeriodTime: + cls_list.append(LyricNextPeriodSensor) + if device.changeableValues.thermostatSetpointStatus: + cls_list.append(LyricSetpointStatusSensor) + for cls in cls_list: + entities.append( + cls( + coordinator, + location, + device, + hass.config.units.temperature_unit, + ) + ) + + async_add_entities(entities, True) + + +class LyricSensor(LyricDeviceEntity): + """Defines a Honeywell Lyric sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + key: str, + name: str, + icon: str, + device_class: str = None, + unit_of_measurement: str = None, + ) -> None: + """Initialize Honeywell Lyric sensor.""" + self._device_class = device_class + self._unit_of_measurement = unit_of_measurement + + super().__init__(coordinator, location, device, key, name, icon) + + @property + def device_class(self) -> str: + """Return the device class of the sensor.""" + return self._device_class + + @property + def unit_of_measurement(self) -> str: + """Return the unit this state is expressed in.""" + return self._unit_of_measurement + + +class LyricIndoorTemperatureSensor(LyricSensor): + """Defines a Honeywell Lyric sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + unit_of_measurement: str = None, + ) -> None: + """Initialize Honeywell Lyric sensor.""" + + super().__init__( + coordinator, + location, + device, + f"{device.macID}_indoor_temperature", + "Indoor Temperature", + None, + DEVICE_CLASS_TEMPERATURE, + unit_of_measurement, + ) + + @property + def state(self) -> str: + """Return the state of the sensor.""" + return self.device.indoorTemperature + + +class LyricOutdoorTemperatureSensor(LyricSensor): + """Defines a Honeywell Lyric sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + unit_of_measurement: str = None, + ) -> None: + """Initialize Honeywell Lyric sensor.""" + + super().__init__( + coordinator, + location, + device, + f"{device.macID}_outdoor_temperature", + "Outdoor Temperature", + None, + DEVICE_CLASS_TEMPERATURE, + unit_of_measurement, + ) + + @property + def state(self) -> str: + """Return the state of the sensor.""" + return self.device.outdoorTemperature + + +class LyricOutdoorHumiditySensor(LyricSensor): + """Defines a Honeywell Lyric sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + unit_of_measurement: str = None, + ) -> None: + """Initialize Honeywell Lyric sensor.""" + + super().__init__( + coordinator, + location, + device, + f"{device.macID}_outdoor_humidity", + "Outdoor Humidity", + None, + DEVICE_CLASS_HUMIDITY, + "%", + ) + + @property + def state(self) -> str: + """Return the state of the sensor.""" + return self.device.displayedOutdoorHumidity + + +class LyricNextPeriodSensor(LyricSensor): + """Defines a Honeywell Lyric sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + unit_of_measurement: str = None, + ) -> None: + """Initialize Honeywell Lyric sensor.""" + + super().__init__( + coordinator, + location, + device, + f"{device.macID}_next_period_time", + "Next Period Time", + None, + DEVICE_CLASS_TIMESTAMP, + ) + + @property + def state(self) -> datetime: + """Return the state of the sensor.""" + device = self.device + time = dt_util.parse_time(device.changeableValues.nextPeriodTime) + now = dt_util.utcnow() + if time <= now.time(): + now = now + timedelta(days=1) + return dt_util.as_utc(datetime.combine(now.date(), time)) + + +class LyricSetpointStatusSensor(LyricSensor): + """Defines a Honeywell Lyric sensor.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator, + location: LyricLocation, + device: LyricDevice, + unit_of_measurement: str = None, + ) -> None: + """Initialize Honeywell Lyric sensor.""" + + super().__init__( + coordinator, + location, + device, + f"{device.macID}_setpoint_status", + "Setpoint Status", + "mdi:thermostat", + None, + ) + + @property + def state(self) -> str: + """Return the state of the sensor.""" + device = self.device + if device.changeableValues.thermostatSetpointStatus == PRESET_HOLD_UNTIL: + return f"Held until {device.changeableValues.nextPeriodTime}" + return LYRIC_SETPOINT_STATUS_NAMES.get( + device.changeableValues.thermostatSetpointStatus, "Unknown" + ) From 60d4dadcb6b5f806209366892415347129c1ca49 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 03:03:51 +0100 Subject: [PATCH 0101/1818] Upgrade sqlalchemy to 1.3.23 (#45845) --- homeassistant/components/recorder/manifest.json | 2 +- homeassistant/components/sql/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index 67d3bdd0f5bf8c..a7e5eb0814d797 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -2,7 +2,7 @@ "domain": "recorder", "name": "Recorder", "documentation": "https://www.home-assistant.io/integrations/recorder", - "requirements": ["sqlalchemy==1.3.22"], + "requirements": ["sqlalchemy==1.3.23"], "codeowners": [], "quality_scale": "internal" } diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index 3b21d32b11078f..7418eb095da785 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -2,6 +2,6 @@ "domain": "sql", "name": "SQL", "documentation": "https://www.home-assistant.io/integrations/sql", - "requirements": ["sqlalchemy==1.3.22"], + "requirements": ["sqlalchemy==1.3.23"], "codeowners": ["@dgomes"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 139f577ff639f0..e7585a1aaa886e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -26,7 +26,7 @@ pyyaml==5.4.1 requests==2.25.1 ruamel.yaml==0.15.100 scapy==2.4.4 -sqlalchemy==1.3.22 +sqlalchemy==1.3.23 voluptuous-serialize==2.4.0 voluptuous==0.12.1 yarl==1.6.3 diff --git a/requirements_all.txt b/requirements_all.txt index 80cbcf7cd5f971..2ff45edcda0bad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2103,7 +2103,7 @@ spotipy==2.16.1 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.3.22 +sqlalchemy==1.3.23 # homeassistant.components.srp_energy srpenergy==1.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 164c7df2396b26..50b1c6670e6ae8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1066,7 +1066,7 @@ spotipy==2.16.1 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.3.22 +sqlalchemy==1.3.23 # homeassistant.components.srp_energy srpenergy==1.3.2 From aea8636c7e0224deb3877e6cb67444609635dfed Mon Sep 17 00:00:00 2001 From: michaeldavie Date: Tue, 2 Feb 2021 02:23:26 -0500 Subject: [PATCH 0102/1818] Fix environment_canada high/low temperature display in evenings. (#45855) --- .../components/environment_canada/weather.py | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index f4fa96b52d6ca0..dd2252a585ff9b 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -184,23 +184,34 @@ def get_forecast(ec_data, forecast_type): if forecast_type == "daily": half_days = ec_data.daily_forecasts + + today = { + ATTR_FORECAST_TIME: dt.now().isoformat(), + ATTR_FORECAST_CONDITION: icon_code_to_condition( + int(half_days[0]["icon_code"]) + ), + ATTR_FORECAST_PRECIPITATION_PROBABILITY: int( + half_days[0]["precip_probability"] + ), + } + if half_days[0]["temperature_class"] == "high": - forecast_array.append( + today.update( { - ATTR_FORECAST_TIME: dt.now().isoformat(), ATTR_FORECAST_TEMP: int(half_days[0]["temperature"]), ATTR_FORECAST_TEMP_LOW: int(half_days[1]["temperature"]), - ATTR_FORECAST_CONDITION: icon_code_to_condition( - int(half_days[0]["icon_code"]) - ), - ATTR_FORECAST_PRECIPITATION_PROBABILITY: int( - half_days[0]["precip_probability"] - ), } ) - half_days = half_days[2:] else: - half_days = half_days[1:] + today.update( + { + ATTR_FORECAST_TEMP_LOW: int(half_days[0]["temperature"]), + ATTR_FORECAST_TEMP: int(half_days[1]["temperature"]), + } + ) + + forecast_array.append(today) + half_days = half_days[2:] for day, high, low in zip(range(1, 6), range(0, 9, 2), range(1, 10, 2)): forecast_array.append( From 411c0a968559073184fe9c3fc95ad0a20f90ce97 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Feb 2021 09:36:00 +0100 Subject: [PATCH 0103/1818] Improve MQTT JSON light to allow non-ambiguous states (#45522) --- .../components/mqtt/light/schema_json.py | 78 +++++++++++-------- tests/components/mqtt/test_light_json.py | 22 ++++++ 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index c6622578a6f8e5..489b424f4eb254 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -175,6 +175,43 @@ def _setup_from_config(self, config): self._supported_features |= config[CONF_XY] and SUPPORT_COLOR self._supported_features |= config[CONF_HS] and SUPPORT_COLOR + def _parse_color(self, values): + try: + red = int(values["color"]["r"]) + green = int(values["color"]["g"]) + blue = int(values["color"]["b"]) + + return color_util.color_RGB_to_hs(red, green, blue) + except KeyError: + pass + except ValueError: + _LOGGER.warning("Invalid RGB color value received") + return self._hs + + try: + x_color = float(values["color"]["x"]) + y_color = float(values["color"]["y"]) + + return color_util.color_xy_to_hs(x_color, y_color) + except KeyError: + pass + except ValueError: + _LOGGER.warning("Invalid XY color value received") + return self._hs + + try: + hue = float(values["color"]["h"]) + saturation = float(values["color"]["s"]) + + return (hue, saturation) + except KeyError: + pass + except ValueError: + _LOGGER.warning("Invalid HS color value received") + return self._hs + + return self._hs + async def _subscribe_topics(self): """(Re)Subscribe to topics.""" last_state = await self.async_get_last_state() @@ -190,37 +227,11 @@ def state_received(msg): elif values["state"] == "OFF": self._state = False - if self._supported_features and SUPPORT_COLOR: - try: - red = int(values["color"]["r"]) - green = int(values["color"]["g"]) - blue = int(values["color"]["b"]) - - self._hs = color_util.color_RGB_to_hs(red, green, blue) - except KeyError: - pass - except ValueError: - _LOGGER.warning("Invalid RGB color value received") - - try: - x_color = float(values["color"]["x"]) - y_color = float(values["color"]["y"]) - - self._hs = color_util.color_xy_to_hs(x_color, y_color) - except KeyError: - pass - except ValueError: - _LOGGER.warning("Invalid XY color value received") - - try: - hue = float(values["color"]["h"]) - saturation = float(values["color"]["s"]) - - self._hs = (hue, saturation) - except KeyError: - pass - except ValueError: - _LOGGER.warning("Invalid HS color value received") + if self._supported_features and SUPPORT_COLOR and "color" in values: + if values["color"] is None: + self._hs = None + else: + self._hs = self._parse_color(values) if self._supported_features and SUPPORT_BRIGHTNESS: try: @@ -236,7 +247,10 @@ def state_received(msg): if self._supported_features and SUPPORT_COLOR_TEMP: try: - self._color_temp = int(values["color_temp"]) + if values["color_temp"] is None: + self._color_temp = None + else: + self._color_temp = int(values["color_temp"]) except KeyError: pass except ValueError: diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 1c9eed0e40423f..022df109f3842e 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -295,11 +295,21 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): light_state = hass.states.get("light.test") assert light_state.attributes.get("hs_color") == (180.0, 50.0) + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "color":null}') + + light_state = hass.states.get("light.test") + assert "hs_color" not in light_state.attributes + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "color_temp":155}') light_state = hass.states.get("light.test") assert light_state.attributes.get("color_temp") == 155 + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "color_temp":null}') + + light_state = hass.states.get("light.test") + assert "color_temp" not in light_state.attributes + async_fire_mqtt_message( hass, "test_light_rgb", '{"state":"ON", "effect":"colorloop"}' ) @@ -1004,6 +1014,18 @@ async def test_invalid_values(hass, mqtt_mock): assert state.attributes.get("white_value") == 255 assert state.attributes.get("color_temp") == 100 + # Empty color value + async_fire_mqtt_message( + hass, + "test_light_rgb", + '{"state":"ON",' '"color":{}}', + ) + + # Color should not have changed + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("rgb_color") == (255, 255, 255) + # Bad HS color values async_fire_mqtt_message( hass, From 3ef7bd6b73d518bae6eefe7cfddd7d483d336b5f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Feb 2021 02:37:42 -0600 Subject: [PATCH 0104/1818] Add notification events to zwave_js integration (#45827) Co-authored-by: Paulus Schoutsen --- homeassistant/components/zwave_js/__init__.py | 25 ++++++++++++++++- homeassistant/components/zwave_js/const.py | 1 + tests/components/zwave_js/test_events.py | 28 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 2b4b33e9b88d59..a4eb466fe8746b 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -5,6 +5,7 @@ from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode +from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification from homeassistant.components.hassio.handler import HassioAPIError @@ -26,6 +27,7 @@ ATTR_HOME_ID, ATTR_LABEL, ATTR_NODE_ID, + ATTR_PARAMETERS, ATTR_PROPERTY_KEY_NAME, ATTR_PROPERTY_NAME, ATTR_TYPE, @@ -114,11 +116,15 @@ def async_on_node_ready(node: ZwaveNode) -> None: async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) - # add listener for stateless node events (value notification) + # add listener for stateless node value notification events node.on( "value notification", lambda event: async_on_value_notification(event["value_notification"]), ) + # add listener for stateless node notification events + node.on( + "notification", lambda event: async_on_notification(event["notification"]) + ) @callback def async_on_node_added(node: ZwaveNode) -> None: @@ -172,6 +178,23 @@ def async_on_value_notification(notification: ValueNotification) -> None: }, ) + @callback + def async_on_notification(notification: Notification) -> None: + """Relay stateless notification events from Z-Wave nodes to hass.""" + device = dev_reg.async_get_device({get_device_id(client, notification.node)}) + hass.bus.async_fire( + ZWAVE_JS_EVENT, + { + ATTR_TYPE: "notification", + ATTR_DOMAIN: DOMAIN, + ATTR_NODE_ID: notification.node.node_id, + ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_DEVICE_ID: device.id, + ATTR_LABEL: notification.notification_label, + ATTR_PARAMETERS: notification.parameters, + }, + ) + async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await client.disconnect() diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 163f4fff9ac4b7..dc2ffaeaa20471 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -32,3 +32,4 @@ ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" +ATTR_PARAMETERS = "parameters" diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index c4280ebb50ddbb..2a347f6afea9de 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -128,3 +128,31 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[2].data["command_class_name"] == "Central Scene" assert events[2].data["label"] == "Scene 001" assert events[2].data["value"] == "KeyPressed3x" + + +async def test_notifications(hass, hank_binary_switch, integration, client): + """Test notification events.""" + # just pick a random node to fake the value notification events + node = hank_binary_switch + events = async_capture_events(hass, "zwave_js_event") + + # Publish fake Basic Set value notification + event = Event( + type="notification", + data={ + "source": "node", + "event": "notification", + "nodeId": 23, + "notificationLabel": "Keypad lock operation", + "parameters": {"userId": 1}, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["type"] == "notification" + assert events[0].data["home_id"] == client.driver.controller.home_id + assert events[0].data["node_id"] == 32 + assert events[0].data["label"] == "Keypad lock operation" + assert events[0].data["parameters"]["userId"] == 1 From 8d9b66e23daba8526d6778f70d0f29ce0beb34f9 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Feb 2021 02:41:00 -0600 Subject: [PATCH 0105/1818] Add current humidity to zwave_js climate platform (#45857) --- homeassistant/components/zwave_js/climate.py | 11 +++++++++++ tests/components/zwave_js/test_climate.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 6c0b4a0335eaaa..417f5aa5e5dc8f 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -142,6 +142,12 @@ def __init__( add_to_watched_value_ids=True, check_all_endpoints=True, ) + self._current_humidity = self.get_zwave_value( + "Humidity", + command_class=CommandClass.SENSOR_MULTILEVEL, + add_to_watched_value_ids=True, + check_all_endpoints=True, + ) self._set_modes_and_presets() def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: @@ -207,6 +213,11 @@ def hvac_action(self) -> Optional[str]: return None return HVAC_CURRENT_MAP.get(int(self._operating_state.value)) + @property + def current_humidity(self) -> Optional[int]: + """Return the current humidity level.""" + return self._current_humidity.value if self._current_humidity else None + @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index f7deefc1360f1d..bede37e6959e31 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -3,6 +3,7 @@ from zwave_js_server.event import Event from homeassistant.components.climate.const import ( + ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, @@ -42,6 +43,7 @@ async def test_thermostat_v2( HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL, ] + assert state.attributes[ATTR_CURRENT_HUMIDITY] == 30 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.2 assert state.attributes[ATTR_TEMPERATURE] == 22.2 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE From 0feda9ce63aa04a3aa31c8d34f2a671d8c3e6610 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Feb 2021 10:06:09 +0100 Subject: [PATCH 0106/1818] Fix sensor discovery for zwave_js integration (#45834) Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- .../components/zwave_js/binary_sensor.py | 173 +++++++----------- .../components/zwave_js/discovery.py | 112 ++++++------ homeassistant/components/zwave_js/entity.py | 3 + homeassistant/components/zwave_js/sensor.py | 56 ++++-- tests/components/zwave_js/common.py | 3 +- tests/components/zwave_js/test_sensor.py | 36 +++- 6 files changed, 204 insertions(+), 179 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 42394fe127ccca..f17d893e371d56 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -14,7 +14,6 @@ DEVICE_CLASS_LOCK, DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOTION, - DEVICE_CLASS_POWER, DEVICE_CLASS_PROBLEM, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, @@ -57,201 +56,144 @@ class NotificationSensorMapping(TypedDict, total=False): """Represent a notification sensor mapping dict type.""" type: int # required - states: List[int] # required + states: List[str] device_class: str enabled: bool # Mappings for Notification sensors +# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json NOTIFICATION_SENSOR_MAPPINGS: List[NotificationSensorMapping] = [ { - # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - # Assuming here that Value 1 and 2 are not present at the same time + # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected "type": NOTIFICATION_SMOKE_ALARM, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_SMOKE, }, { # NotificationType 1: Smoke Alarm - All other State Id's - # Create as disabled sensors "type": NOTIFICATION_SMOKE_ALARM, - "states": [3, 4, 5, 6, 7, 8], - "device_class": DEVICE_CLASS_SMOKE, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 2: Carbon Monoxide - State Id's 1 and 2 "type": NOTIFICATION_CARBON_MONOOXIDE, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_GAS, }, { # NotificationType 2: Carbon Monoxide - All other State Id's "type": NOTIFICATION_CARBON_MONOOXIDE, - "states": [4, 5, 7], - "device_class": DEVICE_CLASS_GAS, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 3: Carbon Dioxide - State Id's 1 and 2 "type": NOTIFICATION_CARBON_DIOXIDE, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_GAS, }, { # NotificationType 3: Carbon Dioxide - All other State Id's "type": NOTIFICATION_CARBON_DIOXIDE, - "states": [4, 5, 7], - "device_class": DEVICE_CLASS_GAS, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 4: Heat - State Id's 1, 2, 5, 6 (heat/underheat) "type": NOTIFICATION_HEAT, - "states": [1, 2, 5, 6], + "states": ["1", "2", "5", "6"], "device_class": DEVICE_CLASS_HEAT, }, { # NotificationType 4: Heat - All other State Id's "type": NOTIFICATION_HEAT, - "states": [3, 4, 8, 10, 11], - "device_class": DEVICE_CLASS_HEAT, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 5: Water - State Id's 1, 2, 3, 4 "type": NOTIFICATION_WATER, - "states": [1, 2, 3, 4], + "states": ["1", "2", "3", "4"], "device_class": DEVICE_CLASS_MOISTURE, }, { # NotificationType 5: Water - All other State Id's "type": NOTIFICATION_WATER, - "states": [5], - "device_class": DEVICE_CLASS_MOISTURE, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 6: Access Control - State Id's 1, 2, 3, 4 (Lock) "type": NOTIFICATION_ACCESS_CONTROL, - "states": [1, 2, 3, 4], + "states": ["1", "2", "3", "4"], "device_class": DEVICE_CLASS_LOCK, }, { - # NotificationType 6: Access Control - State Id 22 (door/window open) + # NotificationType 6: Access Control - State Id 16 (door/window open) "type": NOTIFICATION_ACCESS_CONTROL, - "states": [22], + "states": ["22"], "device_class": DEVICE_CLASS_DOOR, }, + { + # NotificationType 6: Access Control - State Id 17 (door/window closed) + "type": NOTIFICATION_ACCESS_CONTROL, + "states": ["23"], + "enabled": False, + }, { # NotificationType 7: Home Security - State Id's 1, 2 (intrusion) - # Assuming that value 1 and 2 are not present at the same time "type": NOTIFICATION_HOME_SECURITY, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_SAFETY, }, { # NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering) "type": NOTIFICATION_HOME_SECURITY, - "states": [3, 4, 9], + "states": ["3", "4", "9"], "device_class": DEVICE_CLASS_SAFETY, }, { # NotificationType 7: Home Security - State Id's 5, 6 (glass breakage) - # Assuming that value 5 and 6 are not present at the same time "type": NOTIFICATION_HOME_SECURITY, - "states": [5, 6], + "states": ["5", "6"], "device_class": DEVICE_CLASS_SAFETY, }, { # NotificationType 7: Home Security - State Id's 7, 8 (motion) "type": NOTIFICATION_HOME_SECURITY, - "states": [7, 8], + "states": ["7", "8"], "device_class": DEVICE_CLASS_MOTION, }, - { - # NotificationType 8: Power management - Values 1...9 - "type": NOTIFICATION_POWER_MANAGEMENT, - "states": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "device_class": DEVICE_CLASS_POWER, - "enabled": False, - }, - { - # NotificationType 8: Power management - Values 10...15 - # Battery values (mutually exclusive) - "type": NOTIFICATION_POWER_MANAGEMENT, - "states": [10, 11, 12, 13, 14, 15], - "device_class": DEVICE_CLASS_BATTERY, - "enabled": False, - }, { # NotificationType 9: System - State Id's 1, 2, 6, 7 "type": NOTIFICATION_SYSTEM, - "states": [1, 2, 6, 7], + "states": ["1", "2", "6", "7"], "device_class": DEVICE_CLASS_PROBLEM, - "enabled": False, }, { # NotificationType 10: Emergency - State Id's 1, 2, 3 "type": NOTIFICATION_EMERGENCY, - "states": [1, 2, 3], + "states": ["1", "2", "3"], "device_class": DEVICE_CLASS_PROBLEM, }, - { - # NotificationType 11: Clock - State Id's 1, 2 - "type": NOTIFICATION_CLOCK, - "states": [1, 2], - "enabled": False, - }, - { - # NotificationType 12: Appliance - All State Id's - "type": NOTIFICATION_APPLIANCE, - "states": list(range(1, 22)), - }, - { - # NotificationType 13: Home Health - State Id's 1,2,3,4,5 - "type": NOTIFICATION_APPLIANCE, - "states": [1, 2, 3, 4, 5], - }, { # NotificationType 14: Siren "type": NOTIFICATION_SIREN, - "states": [1], + "states": ["1"], "device_class": DEVICE_CLASS_SOUND, }, - { - # NotificationType 15: Water valve - # ignore non-boolean values - "type": NOTIFICATION_WATER_VALVE, - "states": [3, 4], - "device_class": DEVICE_CLASS_PROBLEM, - }, - { - # NotificationType 16: Weather - "type": NOTIFICATION_WEATHER, - "states": [1, 2], - "device_class": DEVICE_CLASS_PROBLEM, - }, - { - # NotificationType 17: Irrigation - # ignore non-boolean values - "type": NOTIFICATION_IRRIGATION, - "states": [1, 2, 3, 4, 5], - }, { # NotificationType 18: Gas "type": NOTIFICATION_GAS, - "states": [1, 2, 3, 4], + "states": ["1", "2", "3", "4"], "device_class": DEVICE_CLASS_GAS, }, { # NotificationType 18: Gas "type": NOTIFICATION_GAS, - "states": [6], + "states": ["6"], "device_class": DEVICE_CLASS_PROBLEM, }, ] + PROPERTY_DOOR_STATUS = "doorStatus" @@ -284,10 +226,17 @@ async def async_setup_entry( @callback def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Binary Sensor.""" - entities: List[ZWaveBaseEntity] = [] + entities: List[BinarySensorEntity] = [] if info.platform_hint == "notification": - entities.append(ZWaveNotificationBinarySensor(config_entry, client, info)) + # Get all sensors from Notification CC states + for state_key in info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + entities.append( + ZWaveNotificationBinarySensor(config_entry, client, info, state_key) + ) elif info.platform_hint == "property": entities.append(ZWavePropertyBinarySensor(config_entry, client, info)) else: @@ -335,58 +284,60 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity): """Representation of a Z-Wave binary_sensor from Notification CommandClass.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + state_key: str, ) -> None: """Initialize a ZWaveNotificationBinarySensor entity.""" super().__init__(config_entry, client, info) + self.state_key = state_key # check if we have a custom mapping for this value self._mapping_info = self._get_sensor_mapping() @property def is_on(self) -> bool: """Return if the sensor is on or off.""" - if self._mapping_info: - return self.info.primary_value.value in self._mapping_info["states"] - return bool(self.info.primary_value.value != 0) + return int(self.info.primary_value.value) == int(self.state_key) @property def name(self) -> str: """Return default name from device name and value name combination.""" node_name = self.info.node.name or self.info.node.device_config.description - property_name = self.info.primary_value.property_name - property_key_name = self.info.primary_value.property_key_name - return f"{node_name}: {property_name}: {property_key_name}" + value_name = self.info.primary_value.property_name + state_label = self.info.primary_value.metadata.states[self.state_key] + return f"{node_name}: {value_name} - {state_label}" @property def device_class(self) -> Optional[str]: """Return device class.""" return self._mapping_info.get("device_class") + @property + def unique_id(self) -> str: + """Return unique id for this entity.""" + return f"{super().unique_id}.{self.state_key}" + @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - # We hide some more advanced sensors by default to not overwhelm users if not self._mapping_info: - # consider value for which we do not have a mapping as advanced. - return False + return True return self._mapping_info.get("enabled", True) @callback def _get_sensor_mapping(self) -> NotificationSensorMapping: """Try to get a device specific mapping for this sensor.""" for mapping in NOTIFICATION_SENSOR_MAPPINGS: - if mapping["type"] != int( - self.info.primary_value.metadata.cc_specific["notificationType"] + if ( + mapping["type"] + != self.info.primary_value.metadata.cc_specific["notificationType"] ): continue - for state_key in self.info.primary_value.metadata.states: - # make sure the key is int - state_key = int(state_key) - if state_key not in mapping["states"]: - continue + if not mapping.get("states") or self.state_key in mapping["states"]: # match found - mapping_info = mapping.copy() - return mapping_info + return mapping return {} diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 1fdd8e12fd6128..0720c28acebbbe 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -143,10 +143,8 @@ class ZWaveDiscoverySchema: platform="sensor", hint="string_sensor", command_class={ - CommandClass.ALARM, CommandClass.SENSOR_ALARM, CommandClass.INDICATOR, - CommandClass.NOTIFICATION, }, type={"string"}, ), @@ -157,14 +155,30 @@ class ZWaveDiscoverySchema: command_class={ CommandClass.SENSOR_MULTILEVEL, CommandClass.METER, - CommandClass.ALARM, CommandClass.SENSOR_ALARM, CommandClass.INDICATOR, CommandClass.BATTERY, + }, + type={"number"}, + ), + # special list sensors (Notification CC) + ZWaveDiscoverySchema( + platform="sensor", + hint="list_sensor", + command_class={ CommandClass.NOTIFICATION, + }, + type={"number"}, + ), + # sensor for basic CC + ZWaveDiscoverySchema( + platform="sensor", + hint="numeric_sensor", + command_class={ CommandClass.BASIC, }, type={"number"}, + property={"currentValue"}, ), # binary switches ZWaveDiscoverySchema( @@ -204,54 +218,44 @@ class ZWaveDiscoverySchema: def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]: """Run discovery on ZWave node and return matching (primary) values.""" for value in node.values.values(): - disc_val = async_discover_value(value) - if disc_val: - yield disc_val - - -@callback -def async_discover_value(value: ZwaveValue) -> Optional[ZwaveDiscoveryInfo]: - """Run discovery on Z-Wave value and return ZwaveDiscoveryInfo if match found.""" - for schema in DISCOVERY_SCHEMAS: - # check device_class_basic - if ( - schema.device_class_basic is not None - and value.node.device_class.basic not in schema.device_class_basic - ): - continue - # check device_class_generic - if ( - schema.device_class_generic is not None - and value.node.device_class.generic not in schema.device_class_generic - ): - continue - # check device_class_specific - if ( - schema.device_class_specific is not None - and value.node.device_class.specific not in schema.device_class_specific - ): - continue - # check command_class - if ( - schema.command_class is not None - and value.command_class not in schema.command_class - ): - continue - # check endpoint - if schema.endpoint is not None and value.endpoint not in schema.endpoint: - continue - # check property - if schema.property is not None and value.property_ not in schema.property: - continue - # check metadata_type - if schema.type is not None and value.metadata.type not in schema.type: - continue - # all checks passed, this value belongs to an entity - return ZwaveDiscoveryInfo( - node=value.node, - primary_value=value, - platform=schema.platform, - platform_hint=schema.hint, - ) - - return None + for schema in DISCOVERY_SCHEMAS: + # check device_class_basic + if ( + schema.device_class_basic is not None + and value.node.device_class.basic not in schema.device_class_basic + ): + continue + # check device_class_generic + if ( + schema.device_class_generic is not None + and value.node.device_class.generic not in schema.device_class_generic + ): + continue + # check device_class_specific + if ( + schema.device_class_specific is not None + and value.node.device_class.specific not in schema.device_class_specific + ): + continue + # check command_class + if ( + schema.command_class is not None + and value.command_class not in schema.command_class + ): + continue + # check endpoint + if schema.endpoint is not None and value.endpoint not in schema.endpoint: + continue + # check property + if schema.property is not None and value.property_ not in schema.property: + continue + # check metadata_type + if schema.type is not None and value.metadata.type not in schema.type: + continue + # all checks passed, this value belongs to an entity + yield ZwaveDiscoveryInfo( + node=value.node, + primary_value=value, + platform=schema.platform, + platform_hint=schema.hint, + ) diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 9626ae9a888076..285824ef2f1132 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -79,6 +79,9 @@ def name(self) -> str: or self.info.primary_value.property_key_name or self.info.primary_value.property_name ) + # append endpoint if > 1 + if self.info.primary_value.endpoint > 1: + value_name += f" ({self.info.primary_value.endpoint})" return f"{node_name}: {value_name}" @property diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index ff48790b5f3078..d5c34742c497b7 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -9,6 +9,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DOMAIN as SENSOR_DOMAIN, ) @@ -39,6 +40,8 @@ def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: entities.append(ZWaveStringSensor(config_entry, client, info)) elif info.platform_hint == "numeric_sensor": entities.append(ZWaveNumericSensor(config_entry, client, info)) + elif info.platform_hint == "list_sensor": + entities.append(ZWaveListSensor(config_entry, client, info)) else: LOGGER.warning( "Sensor not implemented for %s/%s", @@ -67,11 +70,15 @@ def device_class(self) -> Optional[str]: if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY if self.info.primary_value.command_class == CommandClass.METER: - if self.info.primary_value.property_key_name == "kWh_Consumed": + if self.info.primary_value.metadata.unit == "kWh": return DEVICE_CLASS_ENERGY return DEVICE_CLASS_POWER - if self.info.primary_value.property_ == "Air temperature": + if "temperature" in self.info.primary_value.property_.lower(): return DEVICE_CLASS_TEMPERATURE + if self.info.primary_value.metadata.unit == "W": + return DEVICE_CLASS_POWER + if self.info.primary_value.metadata.unit == "Lux": + return DEVICE_CLASS_ILLUMINANCE return None @property @@ -133,17 +140,42 @@ def unit_of_measurement(self) -> Optional[str]: return str(self.info.primary_value.metadata.unit) @property - def device_state_attributes(self) -> Optional[Dict[str, str]]: - """Return the device specific state attributes.""" + def name(self) -> str: + """Return default name from device name and value name combination.""" + if self.info.primary_value.command_class == CommandClass.BASIC: + node_name = self.info.node.name or self.info.node.device_config.description + label = self.info.primary_value.command_class_name + return f"{node_name}: {label}" + return super().name + + +class ZWaveListSensor(ZwaveSensorBase): + """Representation of a Z-Wave Numeric sensor with multiple states.""" + + @property + def state(self) -> Optional[str]: + """Return state of the sensor.""" + if self.info.primary_value.value is None: + return None if ( - self.info.primary_value.value is None - or not self.info.primary_value.metadata.states + not str(self.info.primary_value.value) + in self.info.primary_value.metadata.states ): return None - # add the value's label as property for multi-value (list) items - label = self.info.primary_value.metadata.states.get( - self.info.primary_value.value - ) or self.info.primary_value.metadata.states.get( - str(self.info.primary_value.value) + return str( + self.info.primary_value.metadata.states[str(self.info.primary_value.value)] ) - return {"label": label} + + @property + def device_state_attributes(self) -> Optional[Dict[str, str]]: + """Return the device specific state attributes.""" + # add the value's int value as property for multi-value (list) items + return {"value": self.info.primary_value.value} + + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + node_name = self.info.node.name or self.info.node.device_config.description + prop_name = self.info.primary_value.property_name + prop_key_name = self.info.primary_value.property_key_name + return f"{node_name}: {prop_name} - {prop_key_name}" diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index 399b009f4c2299..63ec9013fa3322 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -7,8 +7,9 @@ ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any" DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any" NOTIFICATION_MOTION_BINARY_SENSOR = ( - "binary_sensor.multisensor_6_home_security_motion_sensor_status" + "binary_sensor.multisensor_6_home_security_motion_detection" ) +NOTIFICATION_MOTION_SENSOR = "sensor.multisensor_6_home_security_motion_sensor_status" PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( "binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door" ) diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 284d2e1a84fa9e..bd6fb9f25691cd 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -7,8 +7,17 @@ POWER_WATT, TEMP_CELSIUS, ) +from homeassistant.helpers.entity_registry import ( + DISABLED_INTEGRATION, + async_get_registry, +) -from .common import AIR_TEMPERATURE_SENSOR, ENERGY_SENSOR, POWER_SENSOR +from .common import ( + AIR_TEMPERATURE_SENSOR, + ENERGY_SENSOR, + NOTIFICATION_MOTION_SENSOR, + POWER_SENSOR, +) async def test_numeric_sensor(hass, multisensor_6, integration): @@ -36,3 +45,28 @@ async def test_energy_sensors(hass, hank_binary_switch, integration): assert state.state == "0.16" assert state.attributes["unit_of_measurement"] == ENERGY_KILO_WATT_HOUR assert state.attributes["device_class"] == DEVICE_CLASS_ENERGY + + +async def test_disabled_notification_sensor(hass, multisensor_6, integration): + """Test sensor is created from Notification CC and is disabled.""" + ent_reg = await async_get_registry(hass) + entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_SENSOR) + + assert entity_entry + assert entity_entry.disabled + assert entity_entry.disabled_by == DISABLED_INTEGRATION + + # Test enabling entity + updated_entry = ent_reg.async_update_entity( + entity_entry.entity_id, **{"disabled_by": None} + ) + assert updated_entry != entity_entry + assert updated_entry.disabled is False + + # reload integration and check if entity is correctly there + await hass.config_entries.async_reload(integration.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(NOTIFICATION_MOTION_SENSOR) + assert state.state == "Motion detection" + assert state.attributes["value"] == 8 From b3e2f8f9049076c7c0d884a2dfded939400bb5c0 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Tue, 2 Feb 2021 04:17:17 -0500 Subject: [PATCH 0107/1818] Add Insteon entities in event loop (#45829) --- homeassistant/components/insteon/binary_sensor.py | 8 +++++--- homeassistant/components/insteon/climate.py | 8 +++++--- homeassistant/components/insteon/cover.py | 8 +++++--- homeassistant/components/insteon/fan.py | 8 +++++--- homeassistant/components/insteon/light.py | 8 +++++--- homeassistant/components/insteon/switch.py | 8 +++++--- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py index ad87c69bd0feef..69a4a5f5280ffd 100644 --- a/homeassistant/components/insteon/binary_sensor.py +++ b/homeassistant/components/insteon/binary_sensor.py @@ -27,6 +27,7 @@ DOMAIN as BINARY_SENSOR_DOMAIN, BinarySensorEntity, ) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import SIGNAL_ADD_ENTITIES @@ -51,7 +52,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Insteon binary sensors from a config entry.""" - def add_entities(discovery_info=None): + @callback + def async_add_insteon_binary_sensor_entities(discovery_info=None): """Add the Insteon entities for the platform.""" async_add_insteon_entities( hass, @@ -62,8 +64,8 @@ def add_entities(discovery_info=None): ) signal = f"{SIGNAL_ADD_ENTITIES}_{BINARY_SENSOR_DOMAIN}" - async_dispatcher_connect(hass, signal, add_entities) - add_entities() + async_dispatcher_connect(hass, signal, async_add_insteon_binary_sensor_entities) + async_add_insteon_binary_sensor_entities() class InsteonBinarySensorEntity(InsteonEntity, BinarySensorEntity): diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 7d4d9543c3fe6c..c699e76c4f3659 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -25,6 +25,7 @@ SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import SIGNAL_ADD_ENTITIES @@ -64,7 +65,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Insteon climate entities from a config entry.""" - def add_entities(discovery_info=None): + @callback + def async_add_insteon_climate_entities(discovery_info=None): """Add the Insteon entities for the platform.""" async_add_insteon_entities( hass, @@ -75,8 +77,8 @@ def add_entities(discovery_info=None): ) signal = f"{SIGNAL_ADD_ENTITIES}_{CLIMATE_DOMAIN}" - async_dispatcher_connect(hass, signal, add_entities) - add_entities() + async_dispatcher_connect(hass, signal, async_add_insteon_climate_entities) + async_add_insteon_climate_entities() class InsteonClimateEntity(InsteonEntity, ClimateEntity): diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index 498d194667c3f7..fd20637b174a8b 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -9,6 +9,7 @@ SUPPORT_SET_POSITION, CoverEntity, ) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import SIGNAL_ADD_ENTITIES @@ -21,15 +22,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Insteon covers from a config entry.""" - def add_entities(discovery_info=None): + @callback + def async_add_insteon_cover_entities(discovery_info=None): """Add the Insteon entities for the platform.""" async_add_insteon_entities( hass, COVER_DOMAIN, InsteonCoverEntity, async_add_entities, discovery_info ) signal = f"{SIGNAL_ADD_ENTITIES}_{COVER_DOMAIN}" - async_dispatcher_connect(hass, signal, add_entities) - add_entities() + async_dispatcher_connect(hass, signal, async_add_insteon_cover_entities) + async_add_insteon_cover_entities() class InsteonCoverEntity(InsteonEntity, CoverEntity): diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 3327f9df5ebd8a..a641d35345025c 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -8,6 +8,7 @@ SUPPORT_SET_SPEED, FanEntity, ) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( percentage_to_ranged_value, @@ -24,15 +25,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Insteon fans from a config entry.""" - def add_entities(discovery_info=None): + @callback + def async_add_insteon_fan_entities(discovery_info=None): """Add the Insteon entities for the platform.""" async_add_insteon_entities( hass, FAN_DOMAIN, InsteonFanEntity, async_add_entities, discovery_info ) signal = f"{SIGNAL_ADD_ENTITIES}_{FAN_DOMAIN}" - async_dispatcher_connect(hass, signal, add_entities) - add_entities() + async_dispatcher_connect(hass, signal, async_add_insteon_fan_entities) + async_add_insteon_fan_entities() class InsteonFanEntity(InsteonEntity, FanEntity): diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index f49dafed2fe7b1..206aa078dc3f0f 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -6,6 +6,7 @@ SUPPORT_BRIGHTNESS, LightEntity, ) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import SIGNAL_ADD_ENTITIES @@ -18,15 +19,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Insteon lights from a config entry.""" - def add_entities(discovery_info=None): + @callback + def async_add_insteon_light_entities(discovery_info=None): """Add the Insteon entities for the platform.""" async_add_insteon_entities( hass, LIGHT_DOMAIN, InsteonDimmerEntity, async_add_entities, discovery_info ) signal = f"{SIGNAL_ADD_ENTITIES}_{LIGHT_DOMAIN}" - async_dispatcher_connect(hass, signal, add_entities) - add_entities() + async_dispatcher_connect(hass, signal, async_add_insteon_light_entities) + async_add_insteon_light_entities() class InsteonDimmerEntity(InsteonEntity, LightEntity): diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py index 43430ceb7a062c..0a1a0253b1df82 100644 --- a/homeassistant/components/insteon/switch.py +++ b/homeassistant/components/insteon/switch.py @@ -1,5 +1,6 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import SIGNAL_ADD_ENTITIES @@ -10,15 +11,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Insteon switches from a config entry.""" - def add_entities(discovery_info=None): + @callback + def async_add_insteon_switch_entities(discovery_info=None): """Add the Insteon entities for the platform.""" async_add_insteon_entities( hass, SWITCH_DOMAIN, InsteonSwitchEntity, async_add_entities, discovery_info ) signal = f"{SIGNAL_ADD_ENTITIES}_{SWITCH_DOMAIN}" - async_dispatcher_connect(hass, signal, add_entities) - add_entities() + async_dispatcher_connect(hass, signal, async_add_insteon_switch_entities) + async_add_insteon_switch_entities() class InsteonSwitchEntity(InsteonEntity, SwitchEntity): From 0e3ba532c721095288b6eb6e8fe6efa74b16395d Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Feb 2021 10:18:44 +0100 Subject: [PATCH 0108/1818] Update zwave_js discovery schema for light platform (#45861) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 0720c28acebbbe..88717d9fc83be3 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -115,6 +115,7 @@ class ZWaveDiscoverySchema: "Binary Tunable Color Light", "Multilevel Remote Switch", "Multilevel Power Switch", + "Multilevel Scene Switch", }, command_class={CommandClass.SWITCH_MULTILEVEL}, property={"currentValue"}, From a64ad50b27d2004c1aa7f43605994548d220b03e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Feb 2021 03:30:12 -0600 Subject: [PATCH 0109/1818] Remove zwave_js devices that the controller is no longer connected to on initialization (#45853) * Remove zwave_js devices that the controller is no longer connected to on initialization * remove extra line break * fix test * Clean up Co-authored-by: Paulus Schoutsen * Lint Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen --- homeassistant/components/zwave_js/__init__.py | 14 +++++++ tests/components/zwave_js/conftest.py | 12 ++++++ tests/components/zwave_js/test_init.py | 41 +++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index a4eb466fe8746b..82e79b83659197 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -102,6 +102,20 @@ async def async_on_initialized() -> None: # update entity availability async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_connection_state") + # Check for nodes that no longer exist and remove them + stored_devices = device_registry.async_entries_for_config_entry( + dev_reg, entry.entry_id + ) + known_devices = [ + dev_reg.async_get_device({get_device_id(client, node)}) + for node in client.driver.controller.nodes.values() + ] + + # Devices that are in the device registry that are not known by the controller can be removed + for device in stored_devices: + if device not in known_devices: + dev_reg.async_remove_device(device.id) + @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b6cbc911b6aca7..0e0ebdee3c6082 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -308,3 +308,15 @@ def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state): node = Node(client, in_wall_smart_fan_control_state) client.driver.controller.nodes[node.node_id] = node 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, climate_radio_thermostat_ct100_plus_state) + client.driver.controller.nodes[node.node_id] = node + node = Node(client, lock_schlage_be469_state) + client.driver.controller.nodes[node.node_id] = node + return client.driver.controller.nodes diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index b17945d05c6baa..86fbf27ab4fdf2 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -7,6 +7,7 @@ from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.zwave_js.const import DOMAIN +from homeassistant.components.zwave_js.entity import get_device_id from homeassistant.config_entries import ( CONN_CLASS_LOCAL_PUSH, ENTRY_STATE_LOADED, @@ -14,6 +15,7 @@ ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry, entity_registry from .common import AIR_TEMPERATURE_SENSOR @@ -290,3 +292,42 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text + + +async def test_removed_device(hass, client, multiple_devices, 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 + + # Make sure there are the same number of devices + dev_reg = await device_registry.async_get_registry(hass) + device_entries = device_registry.async_entries_for_config_entry( + dev_reg, integration.entry_id + ) + assert len(device_entries) == 2 + + # Check how many entities there are + ent_reg = await entity_registry.async_get_registry(hass) + entity_entries = entity_registry.async_entries_for_config_entry( + ent_reg, integration.entry_id + ) + assert len(entity_entries) == 18 + + # Remove a node and reload the entry + old_node = nodes.pop(13) + await hass.config_entries.async_reload(integration.entry_id) + await hass.async_block_till_done() + + # Assert that the node and all of it's entities were removed from the device and + # entity registry + device_entries = device_registry.async_entries_for_config_entry( + dev_reg, integration.entry_id + ) + assert len(device_entries) == 1 + entity_entries = entity_registry.async_entries_for_config_entry( + ent_reg, integration.entry_id + ) + assert len(entity_entries) == 9 + assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None From 463a32819c0ae8f5c84e75a16fdc99aef0a11cae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Feb 2021 03:30:38 -1000 Subject: [PATCH 0110/1818] Ensure homekit never picks a port that another config entry uses (#45433) --- homeassistant/components/homekit/__init__.py | 17 +++++++----- .../components/homekit/config_flow.py | 12 +++------ homeassistant/components/homekit/util.py | 21 ++++++++++++--- tests/components/homekit/test_config_flow.py | 6 ++--- tests/components/homekit/test_homekit.py | 5 +--- tests/components/homekit/test_util.py | 26 ++++++++++++++++--- 6 files changed, 58 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index e92c35ffac87f6..568804ef081ca5 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -238,12 +238,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): port = conf[CONF_PORT] _LOGGER.debug("Begin setup HomeKit for %s", name) - # If the previous instance hasn't cleaned up yet - # we need to wait a bit - if not await hass.async_add_executor_job(port_is_available, port): - _LOGGER.warning("The local port %s is in use", port) - raise ConfigEntryNotReady - if CONF_ENTRY_INDEX in conf and conf[CONF_ENTRY_INDEX] == 0: _LOGGER.debug("Migrating legacy HomeKit data for %s", name) hass.async_add_executor_job( @@ -275,7 +269,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.entry_id, ) zeroconf_instance = await zeroconf.async_get_instance(hass) - await hass.async_add_executor_job(homekit.setup, zeroconf_instance) + + # If the previous instance hasn't cleaned up yet + # we need to wait a bit + try: + await hass.async_add_executor_job(homekit.setup, zeroconf_instance) + except (OSError, AttributeError) as ex: + _LOGGER.warning( + "%s could not be setup because the local port %s is in use", name, port + ) + raise ConfigEntryNotReady from ex undo_listener = entry.add_update_listener(_async_update_listener) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index d8708168e12ad7..a20d9a5b843c54 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -40,7 +40,7 @@ VIDEO_CODEC_COPY, ) from .const import DOMAIN # pylint:disable=unused-import -from .util import find_next_available_port +from .util import async_find_next_available_port CONF_CAMERA_COPY = "camera_copy" CONF_INCLUDE_EXCLUDE_MODE = "include_exclude_mode" @@ -162,7 +162,9 @@ async def async_step_pairing(self, user_input=None): if user_input is not None: return self.async_create_entry(title=self.entry_title, data=self.hk_data) - self.hk_data[CONF_PORT] = await self._async_available_port() + self.hk_data[CONF_PORT] = await async_find_next_available_port( + self.hass, DEFAULT_CONFIG_FLOW_PORT + ) self.hk_data[CONF_NAME] = self._async_available_name( self.hk_data[CONF_HOMEKIT_MODE] ) @@ -205,12 +207,6 @@ async def async_step_import(self, user_input=None): title=f"{user_input[CONF_NAME]}:{user_input[CONF_PORT]}", data=user_input ) - async def _async_available_port(self): - """Return an available port the bridge.""" - return await self.hass.async_add_executor_job( - find_next_available_port, DEFAULT_CONFIG_FLOW_PORT - ) - @callback def _async_current_names(self): """Return a set of bridge names.""" diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 98374b73f40a29..83a9a0a0353bbc 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -15,6 +15,7 @@ ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, + CONF_PORT, CONF_TYPE, TEMP_CELSIUS, ) @@ -445,7 +446,7 @@ def _get_test_socket(): return test_socket -def port_is_available(port: int): +def port_is_available(port: int) -> bool: """Check to see if a port is available.""" test_socket = _get_test_socket() try: @@ -456,10 +457,24 @@ def port_is_available(port: int): return True -def find_next_available_port(start_port: int): +async def async_find_next_available_port(hass: HomeAssistant, start_port: int) -> int: + """Find the next available port not assigned to a config entry.""" + exclude_ports = set() + for entry in hass.config_entries.async_entries(DOMAIN): + if CONF_PORT in entry.data: + exclude_ports.add(entry.data[CONF_PORT]) + + return await hass.async_add_executor_job( + _find_next_available_port, start_port, exclude_ports + ) + + +def _find_next_available_port(start_port: int, exclude_ports: set) -> int: """Find the next available port starting with the given port.""" test_socket = _get_test_socket() for port in range(start_port, MAX_PORT): + if port in exclude_ports: + continue try: test_socket.bind(("", port)) return port @@ -469,7 +484,7 @@ def find_next_available_port(start_port: int): continue -def pid_is_alive(pid): +def pid_is_alive(pid) -> bool: """Check to see if a process is alive.""" try: os.kill(pid, 0) diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index 1dd628af18d105..0e4114d566d3d0 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -49,7 +49,7 @@ async def test_setup_in_bridge_mode(hass): assert result2["step_id"] == "bridge_mode" with patch( - "homeassistant.components.homekit.config_flow.find_next_available_port", + "homeassistant.components.homekit.config_flow.async_find_next_available_port", return_value=12345, ): result3 = await hass.config_entries.flow.async_configure( @@ -108,7 +108,7 @@ async def test_setup_in_accessory_mode(hass): assert result2["step_id"] == "accessory_mode" with patch( - "homeassistant.components.homekit.config_flow.find_next_available_port", + "homeassistant.components.homekit.config_flow.async_find_next_available_port", return_value=12345, ): result3 = await hass.config_entries.flow.async_configure( @@ -629,7 +629,7 @@ async def test_converting_bridge_to_accessory_mode(hass): assert result2["step_id"] == "bridge_mode" with patch( - "homeassistant.components.homekit.config_flow.find_next_available_port", + "homeassistant.components.homekit.config_flow.async_find_next_available_port", return_value=12345, ): result3 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index a8c3c81595e15c..f0d6a8b365fa17 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1020,10 +1020,7 @@ async def test_raise_config_entry_not_ready(hass, mock_zeroconf): ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.homekit.port_is_available", - return_value=False, - ): + with patch(f"{PATH_HOMEKIT}.HomeKit.setup", side_effect=OSError): assert not await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index e0f10a94d69604..afa1408a06bc91 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -3,6 +3,7 @@ import voluptuous as vol from homeassistant.components.homekit.const import ( + BRIDGE_NAME, CONF_FEATURE, CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, @@ -21,11 +22,11 @@ TYPE_VALVE, ) from homeassistant.components.homekit.util import ( + async_find_next_available_port, cleanup_name_for_homekit, convert_to_float, density_to_air_quality, dismiss_setup_message, - find_next_available_port, format_sw_version, port_is_available, show_setup_message, @@ -43,6 +44,7 @@ ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, + CONF_PORT, CONF_TYPE, STATE_UNKNOWN, TEMP_CELSIUS, @@ -52,7 +54,7 @@ from .util import async_init_integration -from tests.common import async_mock_service +from tests.common import MockConfigEntry, async_mock_service def test_validate_entity_config(): @@ -251,10 +253,26 @@ async def test_dismiss_setup_msg(hass): async def test_port_is_available(hass): """Test we can get an available port and it is actually available.""" - next_port = await hass.async_add_executor_job( - find_next_available_port, DEFAULT_CONFIG_FLOW_PORT + next_port = await async_find_next_available_port(hass, DEFAULT_CONFIG_FLOW_PORT) + + assert next_port + + assert await hass.async_add_executor_job(port_is_available, next_port) + + +async def test_port_is_available_skips_existing_entries(hass): + """Test we can get an available port and it is actually available.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_CONFIG_FLOW_PORT}, + options={}, ) + entry.add_to_hass(hass) + + next_port = await async_find_next_available_port(hass, DEFAULT_CONFIG_FLOW_PORT) + assert next_port + assert next_port != DEFAULT_CONFIG_FLOW_PORT assert await hass.async_add_executor_job(port_is_available, next_port) From a96a80e78d032cc6d77077f4d8590ba169dc240a Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Tue, 2 Feb 2021 10:38:13 -0300 Subject: [PATCH 0111/1818] Update alexa/const.py to reflect docs (#45806) The current docs say the Alexa integration is compatible with languages not currently present in the conf validator. See: https://www.home-assistant.io/integrations/alexa.smart_home/#alexa-locale --- homeassistant/components/alexa/const.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index a5a1cde2e15dcb..402cb9e1fb2cb0 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -53,10 +53,13 @@ "en-US", "es-ES", "es-MX", + "es-US", "fr-CA", "fr-FR", + "hi-IN", "it-IT", "ja-JP", + "pt-BR", ) API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"} From d417ee27324d569f1197aa6ba61eabc7cf099daf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Feb 2021 03:39:07 -1000 Subject: [PATCH 0112/1818] Add fan speed percentage support to google assistant (#45835) --- .../components/google_assistant/trait.py | 20 ++++++++++++++----- .../components/google_assistant/test_trait.py | 12 +++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index b5dc2afd3e284c..f2a2274d8a897b 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1276,6 +1276,7 @@ def sync_attributes(self): return { "availableFanSpeeds": {"speeds": speeds, "ordered": True}, "reversible": reversible, + "supportsFanSpeedPercent": True, } def query_attributes(self): @@ -1289,9 +1290,11 @@ def query_attributes(self): response["currentFanSpeedSetting"] = speed if domain == fan.DOMAIN: speed = attrs.get(fan.ATTR_SPEED) + percent = attrs.get(fan.ATTR_PERCENTAGE) or 0 if speed is not None: response["on"] = speed != fan.SPEED_OFF response["currentFanSpeedSetting"] = speed + response["currentFanSpeedPercent"] = percent return response async def execute(self, command, data, params, challenge): @@ -1309,13 +1312,20 @@ async def execute(self, command, data, params, challenge): context=data.context, ) if domain == fan.DOMAIN: + service_params = { + ATTR_ENTITY_ID: self.state.entity_id, + } + if "fanSpeedPercent" in params: + service = fan.SERVICE_SET_PERCENTAGE + service_params[fan.ATTR_PERCENTAGE] = params["fanSpeedPercent"] + else: + service = fan.SERVICE_SET_SPEED + service_params[fan.ATTR_SPEED] = params["fanSpeed"] + await self.hass.services.async_call( fan.DOMAIN, - fan.SERVICE_SET_SPEED, - { - ATTR_ENTITY_ID: self.state.entity_id, - fan.ATTR_SPEED: params["fanSpeed"], - }, + service, + service_params, blocking=True, context=data.context, ) diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 9b573f1cf719dd..74e8ab21eb0c23 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1391,6 +1391,7 @@ async def test_fan_speed(hass): fan.SPEED_HIGH, ], "speed": "low", + "percentage": 33, }, ), BASIC_CONFIG, @@ -1438,11 +1439,13 @@ async def test_fan_speed(hass): ], }, "reversible": False, + "supportsFanSpeedPercent": True, } assert trt.query_attributes() == { "currentFanSpeedSetting": "low", "on": True, + "currentFanSpeedPercent": 33, } assert trt.can_execute(trait.COMMAND_FANSPEED, params={"fanSpeed": "medium"}) @@ -1453,6 +1456,14 @@ async def test_fan_speed(hass): assert len(calls) == 1 assert calls[0].data == {"entity_id": "fan.living_room_fan", "speed": "medium"} + assert trt.can_execute(trait.COMMAND_FANSPEED, params={"fanSpeedPercent": 10}) + + calls = async_mock_service(hass, fan.DOMAIN, fan.SERVICE_SET_PERCENTAGE) + await trt.execute(trait.COMMAND_FANSPEED, BASIC_DATA, {"fanSpeedPercent": 10}, {}) + + assert len(calls) == 1 + assert calls[0].data == {"entity_id": "fan.living_room_fan", "percentage": 10} + async def test_climate_fan_speed(hass): """Test FanSpeed trait speed control support for climate domain.""" @@ -1495,6 +1506,7 @@ async def test_climate_fan_speed(hass): ], }, "reversible": False, + "supportsFanSpeedPercent": True, } assert trt.query_attributes() == { From 0382c932836ff58854fb2076a16b825928f95566 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Tue, 2 Feb 2021 14:45:02 +0100 Subject: [PATCH 0113/1818] Enable Osramlightify again (#45849) --- homeassistant/components/osramlightify/manifest.json | 3 +-- requirements_all.txt | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/osramlightify/manifest.json b/homeassistant/components/osramlightify/manifest.json index 7e4b810b223670..80cfeff6e12bd1 100644 --- a/homeassistant/components/osramlightify/manifest.json +++ b/homeassistant/components/osramlightify/manifest.json @@ -1,8 +1,7 @@ { - "disabled": "Upstream package has been removed from PyPi", "domain": "osramlightify", "name": "Osramlightify", "documentation": "https://www.home-assistant.io/integrations/osramlightify", - "requirements": ["lightify==1.0.7.2"], + "requirements": ["lightify==1.0.7.3"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 2ff45edcda0bad..77ac5fb106be37 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -879,6 +879,9 @@ life360==4.1.1 # homeassistant.components.lifx_legacy liffylights==0.9.4 +# homeassistant.components.osramlightify +lightify==1.0.7.3 + # homeassistant.components.lightwave lightwave==0.19 From 7ff4281b6d43f5177d783ded8c93b4f1544e5726 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 15:10:54 +0100 Subject: [PATCH 0114/1818] Upgrade jinja2 to >=2.11.3 (#45843) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e7585a1aaa886e..5b4ff1dfcd0144 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ emoji==0.5.4 hass-nabucasa==0.41.0 home-assistant-frontend==20210127.6 httpx==0.16.1 -jinja2>=2.11.2 +jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 pillow==8.1.0 diff --git a/requirements.txt b/requirements.txt index ef7d73e1cb501d..4b7ff6d7ef8c72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 httpx==0.16.1 -jinja2>=2.11.2 +jinja2>=2.11.3 PyJWT==1.7.1 cryptography==3.3.1 pip>=8.0.3,<20.3 diff --git a/setup.py b/setup.py index 5998a40a24e591..125776ea4b06d5 100755 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ "certifi>=2020.12.5", "ciso8601==2.1.3", "httpx==0.16.1", - "jinja2>=2.11.2", + "jinja2>=2.11.3", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. "cryptography==3.3.1", From e9b2d33ad85a036a6f30119951b7af0e6bbbd3c1 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 15:18:58 +0100 Subject: [PATCH 0115/1818] Upgrade pytz to >=2021.1 (#45839) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5b4ff1dfcd0144..570a79a3bbd628 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -21,7 +21,7 @@ paho-mqtt==1.5.1 pillow==8.1.0 pip>=8.0.3,<20.3 python-slugify==4.0.1 -pytz>=2020.5 +pytz>=2021.1 pyyaml==5.4.1 requests==2.25.1 ruamel.yaml==0.15.100 diff --git a/requirements.txt b/requirements.txt index 4b7ff6d7ef8c72..c094efe3e46775 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ PyJWT==1.7.1 cryptography==3.3.1 pip>=8.0.3,<20.3 python-slugify==4.0.1 -pytz>=2020.5 +pytz>=2021.1 pyyaml==5.4.1 requests==2.25.1 ruamel.yaml==0.15.100 diff --git a/setup.py b/setup.py index 125776ea4b06d5..7e0df7f95c93d6 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ "cryptography==3.3.1", "pip>=8.0.3,<20.3", "python-slugify==4.0.1", - "pytz>=2020.5", + "pytz>=2021.1", "pyyaml==5.4.1", "requests==2.25.1", "ruamel.yaml==0.15.100", From 6e205965eea87e5af6698b41856d84812a3cd5c7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Feb 2021 15:28:21 +0100 Subject: [PATCH 0116/1818] Fix zwave_js device remove test (#45864) --- tests/components/zwave_js/test_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 86fbf27ab4fdf2..fa61b3deb2721e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -313,7 +313,7 @@ async def test_removed_device(hass, client, multiple_devices, integration): entity_entries = entity_registry.async_entries_for_config_entry( ent_reg, integration.entry_id ) - assert len(entity_entries) == 18 + assert len(entity_entries) == 24 # Remove a node and reload the entry old_node = nodes.pop(13) @@ -329,5 +329,5 @@ async def test_removed_device(hass, client, multiple_devices, integration): entity_entries = entity_registry.async_entries_for_config_entry( ent_reg, integration.entry_id ) - assert len(entity_entries) == 9 + assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None From 63cc2517dd0827be8383831e9d8aabfa4ebb6a64 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 15:53:03 +0100 Subject: [PATCH 0117/1818] Upgrade watchdog to 1.0.2 (#45848) --- homeassistant/components/folder_watcher/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/folder_watcher/manifest.json b/homeassistant/components/folder_watcher/manifest.json index 722b60a952dcc2..60239aeb0d19f1 100644 --- a/homeassistant/components/folder_watcher/manifest.json +++ b/homeassistant/components/folder_watcher/manifest.json @@ -2,7 +2,7 @@ "domain": "folder_watcher", "name": "Folder Watcher", "documentation": "https://www.home-assistant.io/integrations/folder_watcher", - "requirements": ["watchdog==0.8.3"], + "requirements": ["watchdog==1.0.2"], "codeowners": [], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index 77ac5fb106be37..71a9eb7edf2827 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2281,7 +2281,7 @@ wakeonlan==1.1.6 waqiasync==1.0.0 # homeassistant.components.folder_watcher -watchdog==0.8.3 +watchdog==1.0.2 # homeassistant.components.waterfurnace waterfurnace==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 50b1c6670e6ae8..25f8d3d45b7be1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1148,7 +1148,7 @@ vultr==0.1.2 wakeonlan==1.1.6 # homeassistant.components.folder_watcher -watchdog==0.8.3 +watchdog==1.0.2 # homeassistant.components.wiffi wiffi==1.0.1 From 811bbb7acb6657300fcc6c531a99a4d6d62ac871 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Feb 2021 15:56:56 +0100 Subject: [PATCH 0118/1818] Upgrade emoji to 1.2.0 (#45847) --- homeassistant/components/mobile_app/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/mobile_app/manifest.json b/homeassistant/components/mobile_app/manifest.json index 758df70c3d0621..bd8ed7713484fd 100644 --- a/homeassistant/components/mobile_app/manifest.json +++ b/homeassistant/components/mobile_app/manifest.json @@ -3,7 +3,7 @@ "name": "Mobile App", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mobile_app", - "requirements": ["PyNaCl==1.3.0", "emoji==0.5.4"], + "requirements": ["PyNaCl==1.3.0", "emoji==1.2.0"], "dependencies": ["http", "webhook", "person", "tag"], "after_dependencies": ["cloud", "camera", "notify"], "codeowners": ["@robbiet480"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 570a79a3bbd628..8ae31a0bbd2765 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==3.3.1 defusedxml==0.6.0 distro==1.5.0 -emoji==0.5.4 +emoji==1.2.0 hass-nabucasa==0.41.0 home-assistant-frontend==20210127.6 httpx==0.16.1 diff --git a/requirements_all.txt b/requirements_all.txt index 71a9eb7edf2827..2b8efbff9e1f6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -541,7 +541,7 @@ eliqonline==1.2.2 elkm1-lib==0.8.10 # homeassistant.components.mobile_app -emoji==0.5.4 +emoji==1.2.0 # homeassistant.components.emulated_roku emulated_roku==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 25f8d3d45b7be1..1998d1baf0cb57 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -290,7 +290,7 @@ elgato==1.0.0 elkm1-lib==0.8.10 # homeassistant.components.mobile_app -emoji==0.5.4 +emoji==1.2.0 # homeassistant.components.emulated_roku emulated_roku==0.2.1 From c93fec34b3a75de0f19003aab723afbf0fced076 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Feb 2021 16:25:43 +0100 Subject: [PATCH 0119/1818] Fix zwave_js sensor device class attribute error (#45863) --- homeassistant/components/zwave_js/sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index d5c34742c497b7..3d3f782bc1bd6d 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -73,7 +73,10 @@ def device_class(self) -> Optional[str]: if self.info.primary_value.metadata.unit == "kWh": return DEVICE_CLASS_ENERGY return DEVICE_CLASS_POWER - if "temperature" in self.info.primary_value.property_.lower(): + if ( + isinstance(self.info.primary_value.property_, str) + and "temperature" in self.info.primary_value.property_.lower() + ): return DEVICE_CLASS_TEMPERATURE if self.info.primary_value.metadata.unit == "W": return DEVICE_CLASS_POWER From 2e98cfb9ab8c73dc398037a1ac8bedaa7cfb2e3b Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Feb 2021 19:57:08 +0100 Subject: [PATCH 0120/1818] Guard for missing value (#45867) * guard for missing value * update comment --- homeassistant/components/zwave_js/entity.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 285824ef2f1132..b039113270d5b7 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -92,7 +92,13 @@ def unique_id(self) -> str: @property def available(self) -> bool: """Return entity availability.""" - return self.client.connected and bool(self.info.node.ready) + return ( + self.client.connected + and bool(self.info.node.ready) + # a None value indicates something wrong with the device, + # or the value is simply not yet there (it will arrive later). + and self.info.primary_value.value is not None + ) @callback def _value_changed(self, event_data: dict) -> None: From 524b9e7b1ff8b4aeed6dfd49a39a8d92cca807dc Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Feb 2021 20:59:56 +0100 Subject: [PATCH 0121/1818] Use new zwave_js client (#45872) * Use new zwave_js client * Remove client callbacks * Clean up on connect and on disconnect * Clean log * Add stop listen to unsubscribe callbacks * Fix most tests * Adapt to new listen interface * Fix most tests * Remove stale connection state feature * Bump zwave-js-server-python to 0.16.0 * Clean up disconnect --- homeassistant/components/zwave_js/__init__.py | 146 ++++++++++-------- homeassistant/components/zwave_js/entity.py | 9 -- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/conftest.py | 35 ++--- tests/components/zwave_js/test_init.py | 38 ----- 7 files changed, 104 insertions(+), 130 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 82e79b83659197..1a2cdfa70172b5 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,9 +1,11 @@ """The Z-Wave JS integration.""" import asyncio import logging +from typing import Callable, List from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.exceptions import BaseZwaveJSServerError from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification @@ -45,6 +47,8 @@ LOGGER = logging.getLogger(__name__) CONNECT_TIMEOUT = 10 +DATA_CLIENT_LISTEN_TASK = "client_listen_task" +DATA_START_PLATFORM_TASK = "start_platform_task" async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -77,45 +81,8 @@ def register_node_in_dev_reg( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) - connected = asyncio.Event() - initialized = asyncio.Event() dev_reg = await device_registry.async_get_registry(hass) - async def async_on_connect() -> None: - """Handle websocket is (re)connected.""" - LOGGER.info("Connected to Zwave JS Server") - connected.set() - - async def async_on_disconnect() -> None: - """Handle websocket is disconnected.""" - LOGGER.info("Disconnected from Zwave JS Server") - connected.clear() - if initialized.is_set(): - initialized.clear() - # update entity availability - async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_connection_state") - - async def async_on_initialized() -> None: - """Handle initial full state received.""" - LOGGER.info("Connection to Zwave JS Server initialized.") - initialized.set() - # update entity availability - async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_connection_state") - - # Check for nodes that no longer exist and remove them - stored_devices = device_registry.async_entries_for_config_entry( - dev_reg, entry.entry_id - ) - known_devices = [ - dev_reg.async_get_device({get_device_id(client, node)}) - for node in client.driver.controller.nodes.values() - ] - - # Devices that are in the device registry that are not known by the controller can be removed - for device in stored_devices: - if device not in known_devices: - dev_reg.async_remove_device(device.id) - @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -209,32 +176,19 @@ def async_on_notification(notification: Notification) -> None: }, ) - async def handle_ha_shutdown(event: Event) -> None: - """Handle HA shutdown.""" - await client.disconnect() - - # register main event callbacks. - unsubs = [ - client.register_on_initialized(async_on_initialized), - client.register_on_disconnect(async_on_disconnect), - client.register_on_connect(async_on_connect), - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown), - ] - # connect and throw error if connection failed - asyncio.create_task(client.connect()) try: async with timeout(CONNECT_TIMEOUT): - await connected.wait() - except asyncio.TimeoutError as err: - for unsub in unsubs: - unsub() - await client.disconnect() + await client.connect() + except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: raise ConfigEntryNotReady from err + else: + LOGGER.info("Connected to Zwave JS Server") + unsubscribe_callbacks: List[Callable] = [] hass.data[DOMAIN][entry.entry_id] = { DATA_CLIENT: client, - DATA_UNSUBSCRIBE: unsubs, + DATA_UNSUBSCRIBE: unsubscribe_callbacks, } # Set up websocket API @@ -250,9 +204,37 @@ async def start_platforms() -> None: ] ) - # Wait till we're initialized - LOGGER.info("Waiting for Z-Wave to be fully initialized") - await initialized.wait() + driver_ready = asyncio.Event() + + async def handle_ha_shutdown(event: Event) -> None: + """Handle HA shutdown.""" + await disconnect_client(hass, entry, client, listen_task, platform_task) + + listen_task = asyncio.create_task( + client_listen(hass, entry, client, driver_ready) + ) + hass.data[DOMAIN][entry.entry_id][DATA_CLIENT_LISTEN_TASK] = listen_task + unsubscribe_callbacks.append( + hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) + ) + + await driver_ready.wait() + + LOGGER.info("Connection to Zwave JS Server initialized") + + # Check for nodes that no longer exist and remove them + stored_devices = device_registry.async_entries_for_config_entry( + dev_reg, entry.entry_id + ) + known_devices = [ + dev_reg.async_get_device({get_device_id(client, node)}) + for node in client.driver.controller.nodes.values() + ] + + # Devices that are in the device registry that are not known by the controller can be removed + for device in stored_devices: + if device not in known_devices: + dev_reg.async_remove_device(device.id) # run discovery on all ready nodes for node in client.driver.controller.nodes.values(): @@ -268,11 +250,46 @@ async def start_platforms() -> None: "node removed", lambda event: async_on_node_removed(event["node"]) ) - hass.async_create_task(start_platforms()) + platform_task = hass.async_create_task(start_platforms()) + hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task return True +async def client_listen( + hass: HomeAssistant, + entry: ConfigEntry, + client: ZwaveClient, + driver_ready: asyncio.Event, +) -> None: + """Listen with the client.""" + try: + await client.listen(driver_ready) + except BaseZwaveJSServerError: + # The entry needs to be reloaded since a new driver state + # will be acquired on reconnect. + # All model instances will be replaced when the new state is acquired. + hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) + + +async def disconnect_client( + hass: HomeAssistant, + entry: ConfigEntry, + client: ZwaveClient, + listen_task: asyncio.Task, + platform_task: asyncio.Task, +) -> None: + """Disconnect client.""" + await client.disconnect() + + listen_task.cancel() + platform_task.cancel() + + await asyncio.gather(listen_task, platform_task) + + LOGGER.info("Disconnected from Zwave JS Server") + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( @@ -291,7 +308,14 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: for unsub in info[DATA_UNSUBSCRIBE]: unsub() - await info[DATA_CLIENT].disconnect() + if DATA_CLIENT_LISTEN_TASK in info: + await disconnect_client( + hass, + entry, + info[DATA_CLIENT], + info[DATA_CLIENT_LISTEN_TASK], + platform_task=info[DATA_START_PLATFORM_TASK], + ) return True diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index b039113270d5b7..334a2cccd4faaa 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -9,7 +9,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import DOMAIN @@ -54,14 +53,6 @@ async def async_added_to_hass(self) -> None: self.info.node.on(EVENT_VALUE_UPDATED, self._value_changed) ) - self.async_on_remove( - async_dispatcher_connect( - self.hass, - f"{DOMAIN}_{self.config_entry.entry_id}_connection_state", - self.async_write_ha_state, - ) - ) - @property def device_info(self) -> dict: """Return device information for the device registry.""" diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 586a6492a1a962..de77ebbf5e0c36 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.15.0"], + "requirements": ["zwave-js-server-python==0.16.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2b8efbff9e1f6f..21211668779794 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2384,4 +2384,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.15.0 +zwave-js-server-python==0.16.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1998d1baf0cb57..c2d39fd320e828 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1203,4 +1203,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.15.0 +zwave-js-server-python==0.16.0 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 0e0ebdee3c6082..b5301f4cd2ffbd 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,6 +1,7 @@ """Provide common Z-Wave JS fixtures.""" +import asyncio import json -from unittest.mock import DEFAULT, Mock, patch +from unittest.mock import DEFAULT, AsyncMock, patch import pytest from zwave_js_server.event import Event @@ -149,35 +150,31 @@ def in_wall_smart_fan_control_state_fixture(): def mock_client_fixture(controller_state, version_state): """Mock a client.""" - def mock_callback(): - callbacks = [] - - def add_callback(cb): - callbacks.append(cb) - return DEFAULT - - return callbacks, Mock(side_effect=add_callback) - with patch( "homeassistant.components.zwave_js.ZwaveClient", autospec=True ) as client_class: client = client_class.return_value - connect_callback, client.register_on_connect = mock_callback() - initialized_callback, client.register_on_initialized = mock_callback() - async def connect(): - for cb in connect_callback: - await cb() + await asyncio.sleep(0) + client.state = "connected" + client.connected = True - for cb in initialized_callback: - await cb() + async def listen(driver_ready: asyncio.Event) -> None: + driver_ready.set() - client.connect = Mock(side_effect=connect) + async def disconnect(): + client.state = "disconnected" + client.connected = False + + client.connect = AsyncMock(side_effect=connect) + client.listen = AsyncMock(side_effect=listen) + client.disconnect = AsyncMock(side_effect=disconnect) client.driver = Driver(client, controller_state) + client.version = VersionInfo.from_message(version_state) client.ws_server_url = "ws://test:3000/zjs" - client.state = "connected" + yield client diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index fa61b3deb2721e..1aad07400ad87d 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -50,17 +50,11 @@ async def test_entry_setup_unload(hass, client, integration): entry = integration assert client.connect.call_count == 1 - assert client.register_on_initialized.call_count == 1 - assert client.register_on_disconnect.call_count == 1 - assert client.register_on_connect.call_count == 1 assert entry.state == ENTRY_STATE_LOADED await hass.config_entries.async_unload(entry.entry_id) assert client.disconnect.call_count == 1 - assert client.register_on_initialized.return_value.call_count == 1 - assert client.register_on_disconnect.return_value.call_count == 1 - assert client.register_on_connect.return_value.call_count == 1 assert entry.state == ENTRY_STATE_NOT_LOADED @@ -71,38 +65,6 @@ async def test_home_assistant_stop(hass, client, integration): assert client.disconnect.call_count == 1 -async def test_availability_reflect_connection_status( - hass, client, multisensor_6, integration -): - """Test we handle disconnect and reconnect.""" - on_initialized = client.register_on_initialized.call_args[0][0] - on_disconnect = client.register_on_disconnect.call_args[0][0] - state = hass.states.get(AIR_TEMPERATURE_SENSOR) - - assert state - assert state.state != STATE_UNAVAILABLE - - client.connected = False - - await on_disconnect() - await hass.async_block_till_done() - - state = hass.states.get(AIR_TEMPERATURE_SENSOR) - - assert state - assert state.state == STATE_UNAVAILABLE - - client.connected = True - - await on_initialized() - await hass.async_block_till_done() - - state = hass.states.get(AIR_TEMPERATURE_SENSOR) - - assert state - assert state.state != STATE_UNAVAILABLE - - async def test_initialized_timeout(hass, client, connect_timeout): """Test we handle a timeout during client initialization.""" entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) From bf9b3bf9dbca6926dd846cc31dc08e6751ee8204 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Feb 2021 22:45:51 +0100 Subject: [PATCH 0122/1818] Update frontend to 20210127.7 (#45874) --- 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 9d21be79912c90..65a5497d1f9e56 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.6"], + "requirements": ["home-assistant-frontend==20210127.7"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8ae31a0bbd2765..b280c23982d5a3 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.6 +home-assistant-frontend==20210127.7 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 21211668779794..c29204f21ef310 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.6 +home-assistant-frontend==20210127.7 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c2d39fd320e828..05565349d0bba7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -405,7 +405,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.6 +home-assistant-frontend==20210127.7 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From fba781a5358ee32e3e2ef2f083f5a3a6aa4625d1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Feb 2021 09:36:00 +0100 Subject: [PATCH 0123/1818] Improve MQTT JSON light to allow non-ambiguous states (#45522) --- .../components/mqtt/light/schema_json.py | 78 +++++++++++-------- tests/components/mqtt/test_light_json.py | 22 ++++++ 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index c6622578a6f8e5..489b424f4eb254 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -175,6 +175,43 @@ def _setup_from_config(self, config): self._supported_features |= config[CONF_XY] and SUPPORT_COLOR self._supported_features |= config[CONF_HS] and SUPPORT_COLOR + def _parse_color(self, values): + try: + red = int(values["color"]["r"]) + green = int(values["color"]["g"]) + blue = int(values["color"]["b"]) + + return color_util.color_RGB_to_hs(red, green, blue) + except KeyError: + pass + except ValueError: + _LOGGER.warning("Invalid RGB color value received") + return self._hs + + try: + x_color = float(values["color"]["x"]) + y_color = float(values["color"]["y"]) + + return color_util.color_xy_to_hs(x_color, y_color) + except KeyError: + pass + except ValueError: + _LOGGER.warning("Invalid XY color value received") + return self._hs + + try: + hue = float(values["color"]["h"]) + saturation = float(values["color"]["s"]) + + return (hue, saturation) + except KeyError: + pass + except ValueError: + _LOGGER.warning("Invalid HS color value received") + return self._hs + + return self._hs + async def _subscribe_topics(self): """(Re)Subscribe to topics.""" last_state = await self.async_get_last_state() @@ -190,37 +227,11 @@ def state_received(msg): elif values["state"] == "OFF": self._state = False - if self._supported_features and SUPPORT_COLOR: - try: - red = int(values["color"]["r"]) - green = int(values["color"]["g"]) - blue = int(values["color"]["b"]) - - self._hs = color_util.color_RGB_to_hs(red, green, blue) - except KeyError: - pass - except ValueError: - _LOGGER.warning("Invalid RGB color value received") - - try: - x_color = float(values["color"]["x"]) - y_color = float(values["color"]["y"]) - - self._hs = color_util.color_xy_to_hs(x_color, y_color) - except KeyError: - pass - except ValueError: - _LOGGER.warning("Invalid XY color value received") - - try: - hue = float(values["color"]["h"]) - saturation = float(values["color"]["s"]) - - self._hs = (hue, saturation) - except KeyError: - pass - except ValueError: - _LOGGER.warning("Invalid HS color value received") + if self._supported_features and SUPPORT_COLOR and "color" in values: + if values["color"] is None: + self._hs = None + else: + self._hs = self._parse_color(values) if self._supported_features and SUPPORT_BRIGHTNESS: try: @@ -236,7 +247,10 @@ def state_received(msg): if self._supported_features and SUPPORT_COLOR_TEMP: try: - self._color_temp = int(values["color_temp"]) + if values["color_temp"] is None: + self._color_temp = None + else: + self._color_temp = int(values["color_temp"]) except KeyError: pass except ValueError: diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 1c9eed0e40423f..022df109f3842e 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -295,11 +295,21 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): light_state = hass.states.get("light.test") assert light_state.attributes.get("hs_color") == (180.0, 50.0) + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "color":null}') + + light_state = hass.states.get("light.test") + assert "hs_color" not in light_state.attributes + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "color_temp":155}') light_state = hass.states.get("light.test") assert light_state.attributes.get("color_temp") == 155 + async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"ON", "color_temp":null}') + + light_state = hass.states.get("light.test") + assert "color_temp" not in light_state.attributes + async_fire_mqtt_message( hass, "test_light_rgb", '{"state":"ON", "effect":"colorloop"}' ) @@ -1004,6 +1014,18 @@ async def test_invalid_values(hass, mqtt_mock): assert state.attributes.get("white_value") == 255 assert state.attributes.get("color_temp") == 100 + # Empty color value + async_fire_mqtt_message( + hass, + "test_light_rgb", + '{"state":"ON",' '"color":{}}', + ) + + # Color should not have changed + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("rgb_color") == (255, 255, 255) + # Bad HS color values async_fire_mqtt_message( hass, From e169ad93c25b2ead6e6719fa2c78b5236f574fda Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 1 Feb 2021 23:47:58 +0100 Subject: [PATCH 0124/1818] Add value notification events to zwave_js integration (#45814) --- homeassistant/components/zwave_js/__init__.py | 52 ++++++- homeassistant/components/zwave_js/const.py | 15 ++ homeassistant/components/zwave_js/entity.py | 16 ++- tests/components/zwave_js/test_events.py | 130 ++++++++++++++++++ 4 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 tests/components/zwave_js/test_events.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index c995749f924c00..2b4b33e9b88d59 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,11 +1,11 @@ """The Z-Wave JS integration.""" import asyncio import logging -from typing import Tuple from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode +from zwave_js_server.model.value import ValueNotification from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.config_entries import ConfigEntry @@ -18,14 +18,28 @@ from .api import async_register_api from .const import ( + ATTR_COMMAND_CLASS, + ATTR_COMMAND_CLASS_NAME, + ATTR_DEVICE_ID, + ATTR_DOMAIN, + ATTR_ENDPOINT, + ATTR_HOME_ID, + ATTR_LABEL, + ATTR_NODE_ID, + ATTR_PROPERTY_KEY_NAME, + ATTR_PROPERTY_NAME, + ATTR_TYPE, + ATTR_VALUE, CONF_INTEGRATION_CREATED_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, PLATFORMS, + ZWAVE_JS_EVENT, ) from .discovery import async_discover_values +from .entity import get_device_id LOGGER = logging.getLogger(__name__) CONNECT_TIMEOUT = 10 @@ -37,12 +51,6 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: return True -@callback -def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: - """Get device registry identifier for Z-Wave node.""" - return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") - - @callback def register_node_in_dev_reg( hass: HomeAssistant, @@ -106,6 +114,11 @@ def async_on_node_ready(node: ZwaveNode) -> None: async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) + # add listener for stateless node events (value notification) + node.on( + "value notification", + lambda event: async_on_value_notification(event["value_notification"]), + ) @callback def async_on_node_added(node: ZwaveNode) -> None: @@ -134,6 +147,31 @@ def async_on_node_removed(node: ZwaveNode) -> None: # note: removal of entity registry is handled by core dev_reg.async_remove_device(device.id) + @callback + def async_on_value_notification(notification: ValueNotification) -> None: + """Relay stateless value notification events from Z-Wave nodes to hass.""" + device = dev_reg.async_get_device({get_device_id(client, notification.node)}) + value = notification.value + if notification.metadata.states: + value = notification.metadata.states.get(str(value), value) + hass.bus.async_fire( + ZWAVE_JS_EVENT, + { + ATTR_TYPE: "value_notification", + ATTR_DOMAIN: DOMAIN, + ATTR_NODE_ID: notification.node.node_id, + ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_ENDPOINT: notification.endpoint, + ATTR_DEVICE_ID: device.id, + ATTR_COMMAND_CLASS: notification.command_class, + ATTR_COMMAND_CLASS_NAME: notification.command_class_name, + ATTR_LABEL: notification.metadata.label, + ATTR_PROPERTY_NAME: notification.property_name, + ATTR_PROPERTY_KEY_NAME: notification.property_key_name, + ATTR_VALUE: value, + }, + ) + async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await client.disconnect() diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 526a8429bd429f..163f4fff9ac4b7 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -17,3 +17,18 @@ DATA_UNSUBSCRIBE = "unsubs" EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry" + +# constants for events +ZWAVE_JS_EVENT = f"{DOMAIN}_event" +ATTR_NODE_ID = "node_id" +ATTR_HOME_ID = "home_id" +ATTR_ENDPOINT = "endpoint" +ATTR_LABEL = "label" +ATTR_VALUE = "value" +ATTR_COMMAND_CLASS = "command_class" +ATTR_COMMAND_CLASS_NAME = "command_class_name" +ATTR_TYPE = "type" +ATTR_DOMAIN = "domain" +ATTR_DEVICE_ID = "device_id" +ATTR_PROPERTY_NAME = "property_name" +ATTR_PROPERTY_KEY_NAME = "property_key_name" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 84870ba75f4ef5..9626ae9a888076 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,9 +1,10 @@ """Generic Z-Wave Entity Class.""" import logging -from typing import Optional, Union +from typing import Optional, Tuple, Union from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from homeassistant.config_entries import ConfigEntry @@ -19,6 +20,12 @@ EVENT_VALUE_UPDATED = "value updated" +@callback +def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: + """Get device registry identifier for Z-Wave node.""" + return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") + + class ZWaveBaseEntity(Entity): """Generic Entity Class for a Z-Wave Device.""" @@ -60,12 +67,7 @@ def device_info(self) -> dict: """Return device information for the device registry.""" # device is precreated in main handler return { - "identifiers": { - ( - DOMAIN, - f"{self.client.driver.controller.home_id}-{self.info.node.node_id}", - ) - }, + "identifiers": {get_device_id(self.client, self.info.node)}, } @property diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py new file mode 100644 index 00000000000000..c4280ebb50ddbb --- /dev/null +++ b/tests/components/zwave_js/test_events.py @@ -0,0 +1,130 @@ +"""Test Z-Wave JS (value notification) events.""" +from zwave_js_server.event import Event + +from tests.common import async_capture_events + + +async def test_scenes(hass, hank_binary_switch, integration, client): + """Test scene events.""" + # just pick a random node to fake the value notification events + node = hank_binary_switch + events = async_capture_events(hass, "zwave_js_event") + + # Publish fake Basic Set value notification + event = Event( + type="value notification", + data={ + "source": "node", + "event": "value notification", + "nodeId": 32, + "args": { + "commandClassName": "Basic", + "commandClass": 32, + "endpoint": 0, + "property": "event", + "propertyName": "event", + "value": 255, + "metadata": { + "type": "number", + "readable": True, + "writeable": False, + "min": 0, + "max": 255, + "label": "Event value", + }, + "ccVersion": 1, + }, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["home_id"] == client.driver.controller.home_id + assert events[0].data["node_id"] == 32 + assert events[0].data["endpoint"] == 0 + assert events[0].data["command_class"] == 32 + assert events[0].data["command_class_name"] == "Basic" + assert events[0].data["label"] == "Event value" + assert events[0].data["value"] == 255 + + # Publish fake Scene Activation value notification + event = Event( + type="value notification", + data={ + "source": "node", + "event": "value notification", + "nodeId": 32, + "args": { + "commandClassName": "Scene Activation", + "commandClass": 43, + "endpoint": 0, + "property": "SceneID", + "propertyName": "SceneID", + "value": 16, + "metadata": { + "type": "number", + "readable": True, + "writeable": False, + "min": 0, + "max": 255, + "label": "Scene ID", + }, + "ccVersion": 3, + }, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 2 + assert events[1].data["command_class"] == 43 + assert events[1].data["command_class_name"] == "Scene Activation" + assert events[1].data["label"] == "Scene ID" + assert events[1].data["value"] == 16 + + # Publish fake Central Scene value notification + event = Event( + type="value notification", + data={ + "source": "node", + "event": "value notification", + "nodeId": 32, + "args": { + "commandClassName": "Central Scene", + "commandClass": 91, + "endpoint": 0, + "property": "scene", + "propertyKey": "001", + "propertyName": "scene", + "propertyKeyName": "001", + "value": 4, + "metadata": { + "type": "number", + "readable": True, + "writeable": False, + "min": 0, + "max": 255, + "label": "Scene 001", + "states": { + "0": "KeyPressed", + "1": "KeyReleased", + "2": "KeyHeldDown", + "3": "KeyPressed2x", + "4": "KeyPressed3x", + "5": "KeyPressed4x", + "6": "KeyPressed5x", + }, + }, + "ccVersion": 3, + }, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 3 + assert events[2].data["command_class"] == 91 + assert events[2].data["command_class_name"] == "Central Scene" + assert events[2].data["label"] == "Scene 001" + assert events[2].data["value"] == "KeyPressed3x" From a56b250e312e97b57b59ec99a35a90ec99e94e0b Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Feb 2021 02:37:42 -0600 Subject: [PATCH 0125/1818] Add notification events to zwave_js integration (#45827) Co-authored-by: Paulus Schoutsen --- homeassistant/components/zwave_js/__init__.py | 25 ++++++++++++++++- homeassistant/components/zwave_js/const.py | 1 + tests/components/zwave_js/test_events.py | 28 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 2b4b33e9b88d59..a4eb466fe8746b 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -5,6 +5,7 @@ from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode +from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification from homeassistant.components.hassio.handler import HassioAPIError @@ -26,6 +27,7 @@ ATTR_HOME_ID, ATTR_LABEL, ATTR_NODE_ID, + ATTR_PARAMETERS, ATTR_PROPERTY_KEY_NAME, ATTR_PROPERTY_NAME, ATTR_TYPE, @@ -114,11 +116,15 @@ def async_on_node_ready(node: ZwaveNode) -> None: async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) - # add listener for stateless node events (value notification) + # add listener for stateless node value notification events node.on( "value notification", lambda event: async_on_value_notification(event["value_notification"]), ) + # add listener for stateless node notification events + node.on( + "notification", lambda event: async_on_notification(event["notification"]) + ) @callback def async_on_node_added(node: ZwaveNode) -> None: @@ -172,6 +178,23 @@ def async_on_value_notification(notification: ValueNotification) -> None: }, ) + @callback + def async_on_notification(notification: Notification) -> None: + """Relay stateless notification events from Z-Wave nodes to hass.""" + device = dev_reg.async_get_device({get_device_id(client, notification.node)}) + hass.bus.async_fire( + ZWAVE_JS_EVENT, + { + ATTR_TYPE: "notification", + ATTR_DOMAIN: DOMAIN, + ATTR_NODE_ID: notification.node.node_id, + ATTR_HOME_ID: client.driver.controller.home_id, + ATTR_DEVICE_ID: device.id, + ATTR_LABEL: notification.notification_label, + ATTR_PARAMETERS: notification.parameters, + }, + ) + async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await client.disconnect() diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 163f4fff9ac4b7..dc2ffaeaa20471 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -32,3 +32,4 @@ ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" +ATTR_PARAMETERS = "parameters" diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index c4280ebb50ddbb..2a347f6afea9de 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -128,3 +128,31 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[2].data["command_class_name"] == "Central Scene" assert events[2].data["label"] == "Scene 001" assert events[2].data["value"] == "KeyPressed3x" + + +async def test_notifications(hass, hank_binary_switch, integration, client): + """Test notification events.""" + # just pick a random node to fake the value notification events + node = hank_binary_switch + events = async_capture_events(hass, "zwave_js_event") + + # Publish fake Basic Set value notification + event = Event( + type="notification", + data={ + "source": "node", + "event": "notification", + "nodeId": 23, + "notificationLabel": "Keypad lock operation", + "parameters": {"userId": 1}, + }, + ) + node.receive_event(event) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["type"] == "notification" + assert events[0].data["home_id"] == client.driver.controller.home_id + assert events[0].data["node_id"] == 32 + assert events[0].data["label"] == "Keypad lock operation" + assert events[0].data["parameters"]["userId"] == 1 From 67ee2fb82240af1bf98ee206140ae13961c0ad72 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Feb 2021 10:06:09 +0100 Subject: [PATCH 0126/1818] Fix sensor discovery for zwave_js integration (#45834) Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- .../components/zwave_js/binary_sensor.py | 173 +++++++----------- .../components/zwave_js/discovery.py | 112 ++++++------ homeassistant/components/zwave_js/entity.py | 3 + homeassistant/components/zwave_js/sensor.py | 56 ++++-- tests/components/zwave_js/common.py | 3 +- tests/components/zwave_js/test_sensor.py | 36 +++- 6 files changed, 204 insertions(+), 179 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 42394fe127ccca..f17d893e371d56 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -14,7 +14,6 @@ DEVICE_CLASS_LOCK, DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOTION, - DEVICE_CLASS_POWER, DEVICE_CLASS_PROBLEM, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, @@ -57,201 +56,144 @@ class NotificationSensorMapping(TypedDict, total=False): """Represent a notification sensor mapping dict type.""" type: int # required - states: List[int] # required + states: List[str] device_class: str enabled: bool # Mappings for Notification sensors +# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json NOTIFICATION_SENSOR_MAPPINGS: List[NotificationSensorMapping] = [ { - # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - # Assuming here that Value 1 and 2 are not present at the same time + # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected "type": NOTIFICATION_SMOKE_ALARM, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_SMOKE, }, { # NotificationType 1: Smoke Alarm - All other State Id's - # Create as disabled sensors "type": NOTIFICATION_SMOKE_ALARM, - "states": [3, 4, 5, 6, 7, 8], - "device_class": DEVICE_CLASS_SMOKE, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 2: Carbon Monoxide - State Id's 1 and 2 "type": NOTIFICATION_CARBON_MONOOXIDE, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_GAS, }, { # NotificationType 2: Carbon Monoxide - All other State Id's "type": NOTIFICATION_CARBON_MONOOXIDE, - "states": [4, 5, 7], - "device_class": DEVICE_CLASS_GAS, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 3: Carbon Dioxide - State Id's 1 and 2 "type": NOTIFICATION_CARBON_DIOXIDE, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_GAS, }, { # NotificationType 3: Carbon Dioxide - All other State Id's "type": NOTIFICATION_CARBON_DIOXIDE, - "states": [4, 5, 7], - "device_class": DEVICE_CLASS_GAS, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 4: Heat - State Id's 1, 2, 5, 6 (heat/underheat) "type": NOTIFICATION_HEAT, - "states": [1, 2, 5, 6], + "states": ["1", "2", "5", "6"], "device_class": DEVICE_CLASS_HEAT, }, { # NotificationType 4: Heat - All other State Id's "type": NOTIFICATION_HEAT, - "states": [3, 4, 8, 10, 11], - "device_class": DEVICE_CLASS_HEAT, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 5: Water - State Id's 1, 2, 3, 4 "type": NOTIFICATION_WATER, - "states": [1, 2, 3, 4], + "states": ["1", "2", "3", "4"], "device_class": DEVICE_CLASS_MOISTURE, }, { # NotificationType 5: Water - All other State Id's "type": NOTIFICATION_WATER, - "states": [5], - "device_class": DEVICE_CLASS_MOISTURE, - "enabled": False, + "device_class": DEVICE_CLASS_PROBLEM, }, { # NotificationType 6: Access Control - State Id's 1, 2, 3, 4 (Lock) "type": NOTIFICATION_ACCESS_CONTROL, - "states": [1, 2, 3, 4], + "states": ["1", "2", "3", "4"], "device_class": DEVICE_CLASS_LOCK, }, { - # NotificationType 6: Access Control - State Id 22 (door/window open) + # NotificationType 6: Access Control - State Id 16 (door/window open) "type": NOTIFICATION_ACCESS_CONTROL, - "states": [22], + "states": ["22"], "device_class": DEVICE_CLASS_DOOR, }, + { + # NotificationType 6: Access Control - State Id 17 (door/window closed) + "type": NOTIFICATION_ACCESS_CONTROL, + "states": ["23"], + "enabled": False, + }, { # NotificationType 7: Home Security - State Id's 1, 2 (intrusion) - # Assuming that value 1 and 2 are not present at the same time "type": NOTIFICATION_HOME_SECURITY, - "states": [1, 2], + "states": ["1", "2"], "device_class": DEVICE_CLASS_SAFETY, }, { # NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering) "type": NOTIFICATION_HOME_SECURITY, - "states": [3, 4, 9], + "states": ["3", "4", "9"], "device_class": DEVICE_CLASS_SAFETY, }, { # NotificationType 7: Home Security - State Id's 5, 6 (glass breakage) - # Assuming that value 5 and 6 are not present at the same time "type": NOTIFICATION_HOME_SECURITY, - "states": [5, 6], + "states": ["5", "6"], "device_class": DEVICE_CLASS_SAFETY, }, { # NotificationType 7: Home Security - State Id's 7, 8 (motion) "type": NOTIFICATION_HOME_SECURITY, - "states": [7, 8], + "states": ["7", "8"], "device_class": DEVICE_CLASS_MOTION, }, - { - # NotificationType 8: Power management - Values 1...9 - "type": NOTIFICATION_POWER_MANAGEMENT, - "states": [1, 2, 3, 4, 5, 6, 7, 8, 9], - "device_class": DEVICE_CLASS_POWER, - "enabled": False, - }, - { - # NotificationType 8: Power management - Values 10...15 - # Battery values (mutually exclusive) - "type": NOTIFICATION_POWER_MANAGEMENT, - "states": [10, 11, 12, 13, 14, 15], - "device_class": DEVICE_CLASS_BATTERY, - "enabled": False, - }, { # NotificationType 9: System - State Id's 1, 2, 6, 7 "type": NOTIFICATION_SYSTEM, - "states": [1, 2, 6, 7], + "states": ["1", "2", "6", "7"], "device_class": DEVICE_CLASS_PROBLEM, - "enabled": False, }, { # NotificationType 10: Emergency - State Id's 1, 2, 3 "type": NOTIFICATION_EMERGENCY, - "states": [1, 2, 3], + "states": ["1", "2", "3"], "device_class": DEVICE_CLASS_PROBLEM, }, - { - # NotificationType 11: Clock - State Id's 1, 2 - "type": NOTIFICATION_CLOCK, - "states": [1, 2], - "enabled": False, - }, - { - # NotificationType 12: Appliance - All State Id's - "type": NOTIFICATION_APPLIANCE, - "states": list(range(1, 22)), - }, - { - # NotificationType 13: Home Health - State Id's 1,2,3,4,5 - "type": NOTIFICATION_APPLIANCE, - "states": [1, 2, 3, 4, 5], - }, { # NotificationType 14: Siren "type": NOTIFICATION_SIREN, - "states": [1], + "states": ["1"], "device_class": DEVICE_CLASS_SOUND, }, - { - # NotificationType 15: Water valve - # ignore non-boolean values - "type": NOTIFICATION_WATER_VALVE, - "states": [3, 4], - "device_class": DEVICE_CLASS_PROBLEM, - }, - { - # NotificationType 16: Weather - "type": NOTIFICATION_WEATHER, - "states": [1, 2], - "device_class": DEVICE_CLASS_PROBLEM, - }, - { - # NotificationType 17: Irrigation - # ignore non-boolean values - "type": NOTIFICATION_IRRIGATION, - "states": [1, 2, 3, 4, 5], - }, { # NotificationType 18: Gas "type": NOTIFICATION_GAS, - "states": [1, 2, 3, 4], + "states": ["1", "2", "3", "4"], "device_class": DEVICE_CLASS_GAS, }, { # NotificationType 18: Gas "type": NOTIFICATION_GAS, - "states": [6], + "states": ["6"], "device_class": DEVICE_CLASS_PROBLEM, }, ] + PROPERTY_DOOR_STATUS = "doorStatus" @@ -284,10 +226,17 @@ async def async_setup_entry( @callback def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Binary Sensor.""" - entities: List[ZWaveBaseEntity] = [] + entities: List[BinarySensorEntity] = [] if info.platform_hint == "notification": - entities.append(ZWaveNotificationBinarySensor(config_entry, client, info)) + # Get all sensors from Notification CC states + for state_key in info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + entities.append( + ZWaveNotificationBinarySensor(config_entry, client, info, state_key) + ) elif info.platform_hint == "property": entities.append(ZWavePropertyBinarySensor(config_entry, client, info)) else: @@ -335,58 +284,60 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity): """Representation of a Z-Wave binary_sensor from Notification CommandClass.""" def __init__( - self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + state_key: str, ) -> None: """Initialize a ZWaveNotificationBinarySensor entity.""" super().__init__(config_entry, client, info) + self.state_key = state_key # check if we have a custom mapping for this value self._mapping_info = self._get_sensor_mapping() @property def is_on(self) -> bool: """Return if the sensor is on or off.""" - if self._mapping_info: - return self.info.primary_value.value in self._mapping_info["states"] - return bool(self.info.primary_value.value != 0) + return int(self.info.primary_value.value) == int(self.state_key) @property def name(self) -> str: """Return default name from device name and value name combination.""" node_name = self.info.node.name or self.info.node.device_config.description - property_name = self.info.primary_value.property_name - property_key_name = self.info.primary_value.property_key_name - return f"{node_name}: {property_name}: {property_key_name}" + value_name = self.info.primary_value.property_name + state_label = self.info.primary_value.metadata.states[self.state_key] + return f"{node_name}: {value_name} - {state_label}" @property def device_class(self) -> Optional[str]: """Return device class.""" return self._mapping_info.get("device_class") + @property + def unique_id(self) -> str: + """Return unique id for this entity.""" + return f"{super().unique_id}.{self.state_key}" + @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - # We hide some more advanced sensors by default to not overwhelm users if not self._mapping_info: - # consider value for which we do not have a mapping as advanced. - return False + return True return self._mapping_info.get("enabled", True) @callback def _get_sensor_mapping(self) -> NotificationSensorMapping: """Try to get a device specific mapping for this sensor.""" for mapping in NOTIFICATION_SENSOR_MAPPINGS: - if mapping["type"] != int( - self.info.primary_value.metadata.cc_specific["notificationType"] + if ( + mapping["type"] + != self.info.primary_value.metadata.cc_specific["notificationType"] ): continue - for state_key in self.info.primary_value.metadata.states: - # make sure the key is int - state_key = int(state_key) - if state_key not in mapping["states"]: - continue + if not mapping.get("states") or self.state_key in mapping["states"]: # match found - mapping_info = mapping.copy() - return mapping_info + return mapping return {} diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 1fdd8e12fd6128..0720c28acebbbe 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -143,10 +143,8 @@ class ZWaveDiscoverySchema: platform="sensor", hint="string_sensor", command_class={ - CommandClass.ALARM, CommandClass.SENSOR_ALARM, CommandClass.INDICATOR, - CommandClass.NOTIFICATION, }, type={"string"}, ), @@ -157,14 +155,30 @@ class ZWaveDiscoverySchema: command_class={ CommandClass.SENSOR_MULTILEVEL, CommandClass.METER, - CommandClass.ALARM, CommandClass.SENSOR_ALARM, CommandClass.INDICATOR, CommandClass.BATTERY, + }, + type={"number"}, + ), + # special list sensors (Notification CC) + ZWaveDiscoverySchema( + platform="sensor", + hint="list_sensor", + command_class={ CommandClass.NOTIFICATION, + }, + type={"number"}, + ), + # sensor for basic CC + ZWaveDiscoverySchema( + platform="sensor", + hint="numeric_sensor", + command_class={ CommandClass.BASIC, }, type={"number"}, + property={"currentValue"}, ), # binary switches ZWaveDiscoverySchema( @@ -204,54 +218,44 @@ class ZWaveDiscoverySchema: def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]: """Run discovery on ZWave node and return matching (primary) values.""" for value in node.values.values(): - disc_val = async_discover_value(value) - if disc_val: - yield disc_val - - -@callback -def async_discover_value(value: ZwaveValue) -> Optional[ZwaveDiscoveryInfo]: - """Run discovery on Z-Wave value and return ZwaveDiscoveryInfo if match found.""" - for schema in DISCOVERY_SCHEMAS: - # check device_class_basic - if ( - schema.device_class_basic is not None - and value.node.device_class.basic not in schema.device_class_basic - ): - continue - # check device_class_generic - if ( - schema.device_class_generic is not None - and value.node.device_class.generic not in schema.device_class_generic - ): - continue - # check device_class_specific - if ( - schema.device_class_specific is not None - and value.node.device_class.specific not in schema.device_class_specific - ): - continue - # check command_class - if ( - schema.command_class is not None - and value.command_class not in schema.command_class - ): - continue - # check endpoint - if schema.endpoint is not None and value.endpoint not in schema.endpoint: - continue - # check property - if schema.property is not None and value.property_ not in schema.property: - continue - # check metadata_type - if schema.type is not None and value.metadata.type not in schema.type: - continue - # all checks passed, this value belongs to an entity - return ZwaveDiscoveryInfo( - node=value.node, - primary_value=value, - platform=schema.platform, - platform_hint=schema.hint, - ) - - return None + for schema in DISCOVERY_SCHEMAS: + # check device_class_basic + if ( + schema.device_class_basic is not None + and value.node.device_class.basic not in schema.device_class_basic + ): + continue + # check device_class_generic + if ( + schema.device_class_generic is not None + and value.node.device_class.generic not in schema.device_class_generic + ): + continue + # check device_class_specific + if ( + schema.device_class_specific is not None + and value.node.device_class.specific not in schema.device_class_specific + ): + continue + # check command_class + if ( + schema.command_class is not None + and value.command_class not in schema.command_class + ): + continue + # check endpoint + if schema.endpoint is not None and value.endpoint not in schema.endpoint: + continue + # check property + if schema.property is not None and value.property_ not in schema.property: + continue + # check metadata_type + if schema.type is not None and value.metadata.type not in schema.type: + continue + # all checks passed, this value belongs to an entity + yield ZwaveDiscoveryInfo( + node=value.node, + primary_value=value, + platform=schema.platform, + platform_hint=schema.hint, + ) diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 9626ae9a888076..285824ef2f1132 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -79,6 +79,9 @@ def name(self) -> str: or self.info.primary_value.property_key_name or self.info.primary_value.property_name ) + # append endpoint if > 1 + if self.info.primary_value.endpoint > 1: + value_name += f" ({self.info.primary_value.endpoint})" return f"{node_name}: {value_name}" @property diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index ff48790b5f3078..d5c34742c497b7 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -9,6 +9,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DOMAIN as SENSOR_DOMAIN, ) @@ -39,6 +40,8 @@ def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: entities.append(ZWaveStringSensor(config_entry, client, info)) elif info.platform_hint == "numeric_sensor": entities.append(ZWaveNumericSensor(config_entry, client, info)) + elif info.platform_hint == "list_sensor": + entities.append(ZWaveListSensor(config_entry, client, info)) else: LOGGER.warning( "Sensor not implemented for %s/%s", @@ -67,11 +70,15 @@ def device_class(self) -> Optional[str]: if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY if self.info.primary_value.command_class == CommandClass.METER: - if self.info.primary_value.property_key_name == "kWh_Consumed": + if self.info.primary_value.metadata.unit == "kWh": return DEVICE_CLASS_ENERGY return DEVICE_CLASS_POWER - if self.info.primary_value.property_ == "Air temperature": + if "temperature" in self.info.primary_value.property_.lower(): return DEVICE_CLASS_TEMPERATURE + if self.info.primary_value.metadata.unit == "W": + return DEVICE_CLASS_POWER + if self.info.primary_value.metadata.unit == "Lux": + return DEVICE_CLASS_ILLUMINANCE return None @property @@ -133,17 +140,42 @@ def unit_of_measurement(self) -> Optional[str]: return str(self.info.primary_value.metadata.unit) @property - def device_state_attributes(self) -> Optional[Dict[str, str]]: - """Return the device specific state attributes.""" + def name(self) -> str: + """Return default name from device name and value name combination.""" + if self.info.primary_value.command_class == CommandClass.BASIC: + node_name = self.info.node.name or self.info.node.device_config.description + label = self.info.primary_value.command_class_name + return f"{node_name}: {label}" + return super().name + + +class ZWaveListSensor(ZwaveSensorBase): + """Representation of a Z-Wave Numeric sensor with multiple states.""" + + @property + def state(self) -> Optional[str]: + """Return state of the sensor.""" + if self.info.primary_value.value is None: + return None if ( - self.info.primary_value.value is None - or not self.info.primary_value.metadata.states + not str(self.info.primary_value.value) + in self.info.primary_value.metadata.states ): return None - # add the value's label as property for multi-value (list) items - label = self.info.primary_value.metadata.states.get( - self.info.primary_value.value - ) or self.info.primary_value.metadata.states.get( - str(self.info.primary_value.value) + return str( + self.info.primary_value.metadata.states[str(self.info.primary_value.value)] ) - return {"label": label} + + @property + def device_state_attributes(self) -> Optional[Dict[str, str]]: + """Return the device specific state attributes.""" + # add the value's int value as property for multi-value (list) items + return {"value": self.info.primary_value.value} + + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + node_name = self.info.node.name or self.info.node.device_config.description + prop_name = self.info.primary_value.property_name + prop_key_name = self.info.primary_value.property_key_name + return f"{node_name}: {prop_name} - {prop_key_name}" diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index 399b009f4c2299..63ec9013fa3322 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -7,8 +7,9 @@ ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any" DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any" NOTIFICATION_MOTION_BINARY_SENSOR = ( - "binary_sensor.multisensor_6_home_security_motion_sensor_status" + "binary_sensor.multisensor_6_home_security_motion_detection" ) +NOTIFICATION_MOTION_SENSOR = "sensor.multisensor_6_home_security_motion_sensor_status" PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( "binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door" ) diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 284d2e1a84fa9e..bd6fb9f25691cd 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -7,8 +7,17 @@ POWER_WATT, TEMP_CELSIUS, ) +from homeassistant.helpers.entity_registry import ( + DISABLED_INTEGRATION, + async_get_registry, +) -from .common import AIR_TEMPERATURE_SENSOR, ENERGY_SENSOR, POWER_SENSOR +from .common import ( + AIR_TEMPERATURE_SENSOR, + ENERGY_SENSOR, + NOTIFICATION_MOTION_SENSOR, + POWER_SENSOR, +) async def test_numeric_sensor(hass, multisensor_6, integration): @@ -36,3 +45,28 @@ async def test_energy_sensors(hass, hank_binary_switch, integration): assert state.state == "0.16" assert state.attributes["unit_of_measurement"] == ENERGY_KILO_WATT_HOUR assert state.attributes["device_class"] == DEVICE_CLASS_ENERGY + + +async def test_disabled_notification_sensor(hass, multisensor_6, integration): + """Test sensor is created from Notification CC and is disabled.""" + ent_reg = await async_get_registry(hass) + entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_SENSOR) + + assert entity_entry + assert entity_entry.disabled + assert entity_entry.disabled_by == DISABLED_INTEGRATION + + # Test enabling entity + updated_entry = ent_reg.async_update_entity( + entity_entry.entity_id, **{"disabled_by": None} + ) + assert updated_entry != entity_entry + assert updated_entry.disabled is False + + # reload integration and check if entity is correctly there + await hass.config_entries.async_reload(integration.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(NOTIFICATION_MOTION_SENSOR) + assert state.state == "Motion detection" + assert state.attributes["value"] == 8 From 277aa01088e1af4e463c3bb1b23802ce0b05fc0e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 31 Jan 2021 13:50:48 +0100 Subject: [PATCH 0127/1818] Disable Osramlightify, upstream package is missing (#45775) --- homeassistant/components/osramlightify/manifest.json | 1 + requirements_all.txt | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/osramlightify/manifest.json b/homeassistant/components/osramlightify/manifest.json index dfe71d4b9e1ec2..7e4b810b223670 100644 --- a/homeassistant/components/osramlightify/manifest.json +++ b/homeassistant/components/osramlightify/manifest.json @@ -1,4 +1,5 @@ { + "disabled": "Upstream package has been removed from PyPi", "domain": "osramlightify", "name": "Osramlightify", "documentation": "https://www.home-assistant.io/integrations/osramlightify", diff --git a/requirements_all.txt b/requirements_all.txt index 312eb1d80f9f94..fb04a3402aece7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -879,9 +879,6 @@ life360==4.1.1 # homeassistant.components.lifx_legacy liffylights==0.9.4 -# homeassistant.components.osramlightify -lightify==1.0.7.2 - # homeassistant.components.lightwave lightwave==0.19 From bba7c0454c63043478a4bff94be025960fb1da3d Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Tue, 2 Feb 2021 14:45:02 +0100 Subject: [PATCH 0128/1818] Enable Osramlightify again (#45849) --- homeassistant/components/osramlightify/manifest.json | 3 +-- requirements_all.txt | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/osramlightify/manifest.json b/homeassistant/components/osramlightify/manifest.json index 7e4b810b223670..80cfeff6e12bd1 100644 --- a/homeassistant/components/osramlightify/manifest.json +++ b/homeassistant/components/osramlightify/manifest.json @@ -1,8 +1,7 @@ { - "disabled": "Upstream package has been removed from PyPi", "domain": "osramlightify", "name": "Osramlightify", "documentation": "https://www.home-assistant.io/integrations/osramlightify", - "requirements": ["lightify==1.0.7.2"], + "requirements": ["lightify==1.0.7.3"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index fb04a3402aece7..0a7e785668535a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -879,6 +879,9 @@ life360==4.1.1 # homeassistant.components.lifx_legacy liffylights==0.9.4 +# homeassistant.components.osramlightify +lightify==1.0.7.3 + # homeassistant.components.lightwave lightwave==0.19 From 7ff60601c9c97d96c2f805194aa913f9f205a3b6 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Feb 2021 03:30:12 -0600 Subject: [PATCH 0129/1818] Remove zwave_js devices that the controller is no longer connected to on initialization (#45853) * Remove zwave_js devices that the controller is no longer connected to on initialization * remove extra line break * fix test * Clean up Co-authored-by: Paulus Schoutsen * Lint Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen --- homeassistant/components/zwave_js/__init__.py | 14 +++++++ tests/components/zwave_js/conftest.py | 12 ++++++ tests/components/zwave_js/test_init.py | 41 +++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index a4eb466fe8746b..82e79b83659197 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -102,6 +102,20 @@ async def async_on_initialized() -> None: # update entity availability async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_connection_state") + # Check for nodes that no longer exist and remove them + stored_devices = device_registry.async_entries_for_config_entry( + dev_reg, entry.entry_id + ) + known_devices = [ + dev_reg.async_get_device({get_device_id(client, node)}) + for node in client.driver.controller.nodes.values() + ] + + # Devices that are in the device registry that are not known by the controller can be removed + for device in stored_devices: + if device not in known_devices: + dev_reg.async_remove_device(device.id) + @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b6cbc911b6aca7..0e0ebdee3c6082 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -308,3 +308,15 @@ def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state): node = Node(client, in_wall_smart_fan_control_state) client.driver.controller.nodes[node.node_id] = node 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, climate_radio_thermostat_ct100_plus_state) + client.driver.controller.nodes[node.node_id] = node + node = Node(client, lock_schlage_be469_state) + client.driver.controller.nodes[node.node_id] = node + return client.driver.controller.nodes diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index b17945d05c6baa..86fbf27ab4fdf2 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -7,6 +7,7 @@ from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.zwave_js.const import DOMAIN +from homeassistant.components.zwave_js.entity import get_device_id from homeassistant.config_entries import ( CONN_CLASS_LOCAL_PUSH, ENTRY_STATE_LOADED, @@ -14,6 +15,7 @@ ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry, entity_registry from .common import AIR_TEMPERATURE_SENSOR @@ -290,3 +292,42 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text + + +async def test_removed_device(hass, client, multiple_devices, 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 + + # Make sure there are the same number of devices + dev_reg = await device_registry.async_get_registry(hass) + device_entries = device_registry.async_entries_for_config_entry( + dev_reg, integration.entry_id + ) + assert len(device_entries) == 2 + + # Check how many entities there are + ent_reg = await entity_registry.async_get_registry(hass) + entity_entries = entity_registry.async_entries_for_config_entry( + ent_reg, integration.entry_id + ) + assert len(entity_entries) == 18 + + # Remove a node and reload the entry + old_node = nodes.pop(13) + await hass.config_entries.async_reload(integration.entry_id) + await hass.async_block_till_done() + + # Assert that the node and all of it's entities were removed from the device and + # entity registry + device_entries = device_registry.async_entries_for_config_entry( + dev_reg, integration.entry_id + ) + assert len(device_entries) == 1 + entity_entries = entity_registry.async_entries_for_config_entry( + ent_reg, integration.entry_id + ) + assert len(entity_entries) == 9 + assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None From b575b0a700146184806f96ec5e7ebe1761f14e4e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Feb 2021 02:41:00 -0600 Subject: [PATCH 0130/1818] Add current humidity to zwave_js climate platform (#45857) --- homeassistant/components/zwave_js/climate.py | 11 +++++++++++ tests/components/zwave_js/test_climate.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 6c0b4a0335eaaa..417f5aa5e5dc8f 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -142,6 +142,12 @@ def __init__( add_to_watched_value_ids=True, check_all_endpoints=True, ) + self._current_humidity = self.get_zwave_value( + "Humidity", + command_class=CommandClass.SENSOR_MULTILEVEL, + add_to_watched_value_ids=True, + check_all_endpoints=True, + ) self._set_modes_and_presets() def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: @@ -207,6 +213,11 @@ def hvac_action(self) -> Optional[str]: return None return HVAC_CURRENT_MAP.get(int(self._operating_state.value)) + @property + def current_humidity(self) -> Optional[int]: + """Return the current humidity level.""" + return self._current_humidity.value if self._current_humidity else None + @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index f7deefc1360f1d..bede37e6959e31 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -3,6 +3,7 @@ from zwave_js_server.event import Event from homeassistant.components.climate.const import ( + ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, @@ -42,6 +43,7 @@ async def test_thermostat_v2( HVAC_MODE_COOL, HVAC_MODE_HEAT_COOL, ] + assert state.attributes[ATTR_CURRENT_HUMIDITY] == 30 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.2 assert state.attributes[ATTR_TEMPERATURE] == 22.2 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE From d3a92dcac6a90cd65f15e078696886ea398b178d Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Feb 2021 10:18:44 +0100 Subject: [PATCH 0131/1818] Update zwave_js discovery schema for light platform (#45861) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 0720c28acebbbe..88717d9fc83be3 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -115,6 +115,7 @@ class ZWaveDiscoverySchema: "Binary Tunable Color Light", "Multilevel Remote Switch", "Multilevel Power Switch", + "Multilevel Scene Switch", }, command_class={CommandClass.SWITCH_MULTILEVEL}, property={"currentValue"}, From f4ff2708e02e1267cffe13e94d96ca46d354bf3e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Feb 2021 16:25:43 +0100 Subject: [PATCH 0132/1818] Fix zwave_js sensor device class attribute error (#45863) --- homeassistant/components/zwave_js/sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index d5c34742c497b7..3d3f782bc1bd6d 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -73,7 +73,10 @@ def device_class(self) -> Optional[str]: if self.info.primary_value.metadata.unit == "kWh": return DEVICE_CLASS_ENERGY return DEVICE_CLASS_POWER - if "temperature" in self.info.primary_value.property_.lower(): + if ( + isinstance(self.info.primary_value.property_, str) + and "temperature" in self.info.primary_value.property_.lower() + ): return DEVICE_CLASS_TEMPERATURE if self.info.primary_value.metadata.unit == "W": return DEVICE_CLASS_POWER From 34bc5ef0cd1f38c6ecad57636f40506d50172a04 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Feb 2021 15:28:21 +0100 Subject: [PATCH 0133/1818] Fix zwave_js device remove test (#45864) --- tests/components/zwave_js/test_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 86fbf27ab4fdf2..fa61b3deb2721e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -313,7 +313,7 @@ async def test_removed_device(hass, client, multiple_devices, integration): entity_entries = entity_registry.async_entries_for_config_entry( ent_reg, integration.entry_id ) - assert len(entity_entries) == 18 + assert len(entity_entries) == 24 # Remove a node and reload the entry old_node = nodes.pop(13) @@ -329,5 +329,5 @@ async def test_removed_device(hass, client, multiple_devices, integration): entity_entries = entity_registry.async_entries_for_config_entry( ent_reg, integration.entry_id ) - assert len(entity_entries) == 9 + assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None From d759fa3407abfb0533fb5304f298978ba130eb67 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Feb 2021 19:57:08 +0100 Subject: [PATCH 0134/1818] Guard for missing value (#45867) * guard for missing value * update comment --- homeassistant/components/zwave_js/entity.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 285824ef2f1132..b039113270d5b7 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -92,7 +92,13 @@ def unique_id(self) -> str: @property def available(self) -> bool: """Return entity availability.""" - return self.client.connected and bool(self.info.node.ready) + return ( + self.client.connected + and bool(self.info.node.ready) + # a None value indicates something wrong with the device, + # or the value is simply not yet there (it will arrive later). + and self.info.primary_value.value is not None + ) @callback def _value_changed(self, event_data: dict) -> None: From 76c27a7d5a685589d2c7cddd6b56189fa8650a86 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Feb 2021 20:59:56 +0100 Subject: [PATCH 0135/1818] Use new zwave_js client (#45872) * Use new zwave_js client * Remove client callbacks * Clean up on connect and on disconnect * Clean log * Add stop listen to unsubscribe callbacks * Fix most tests * Adapt to new listen interface * Fix most tests * Remove stale connection state feature * Bump zwave-js-server-python to 0.16.0 * Clean up disconnect --- homeassistant/components/zwave_js/__init__.py | 146 ++++++++++-------- homeassistant/components/zwave_js/entity.py | 9 -- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/conftest.py | 35 ++--- tests/components/zwave_js/test_init.py | 38 ----- 7 files changed, 104 insertions(+), 130 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 82e79b83659197..1a2cdfa70172b5 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,9 +1,11 @@ """The Z-Wave JS integration.""" import asyncio import logging +from typing import Callable, List from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.exceptions import BaseZwaveJSServerError from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification @@ -45,6 +47,8 @@ LOGGER = logging.getLogger(__name__) CONNECT_TIMEOUT = 10 +DATA_CLIENT_LISTEN_TASK = "client_listen_task" +DATA_START_PLATFORM_TASK = "start_platform_task" async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -77,45 +81,8 @@ def register_node_in_dev_reg( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) - connected = asyncio.Event() - initialized = asyncio.Event() dev_reg = await device_registry.async_get_registry(hass) - async def async_on_connect() -> None: - """Handle websocket is (re)connected.""" - LOGGER.info("Connected to Zwave JS Server") - connected.set() - - async def async_on_disconnect() -> None: - """Handle websocket is disconnected.""" - LOGGER.info("Disconnected from Zwave JS Server") - connected.clear() - if initialized.is_set(): - initialized.clear() - # update entity availability - async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_connection_state") - - async def async_on_initialized() -> None: - """Handle initial full state received.""" - LOGGER.info("Connection to Zwave JS Server initialized.") - initialized.set() - # update entity availability - async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_connection_state") - - # Check for nodes that no longer exist and remove them - stored_devices = device_registry.async_entries_for_config_entry( - dev_reg, entry.entry_id - ) - known_devices = [ - dev_reg.async_get_device({get_device_id(client, node)}) - for node in client.driver.controller.nodes.values() - ] - - # Devices that are in the device registry that are not known by the controller can be removed - for device in stored_devices: - if device not in known_devices: - dev_reg.async_remove_device(device.id) - @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -209,32 +176,19 @@ def async_on_notification(notification: Notification) -> None: }, ) - async def handle_ha_shutdown(event: Event) -> None: - """Handle HA shutdown.""" - await client.disconnect() - - # register main event callbacks. - unsubs = [ - client.register_on_initialized(async_on_initialized), - client.register_on_disconnect(async_on_disconnect), - client.register_on_connect(async_on_connect), - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown), - ] - # connect and throw error if connection failed - asyncio.create_task(client.connect()) try: async with timeout(CONNECT_TIMEOUT): - await connected.wait() - except asyncio.TimeoutError as err: - for unsub in unsubs: - unsub() - await client.disconnect() + await client.connect() + except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: raise ConfigEntryNotReady from err + else: + LOGGER.info("Connected to Zwave JS Server") + unsubscribe_callbacks: List[Callable] = [] hass.data[DOMAIN][entry.entry_id] = { DATA_CLIENT: client, - DATA_UNSUBSCRIBE: unsubs, + DATA_UNSUBSCRIBE: unsubscribe_callbacks, } # Set up websocket API @@ -250,9 +204,37 @@ async def start_platforms() -> None: ] ) - # Wait till we're initialized - LOGGER.info("Waiting for Z-Wave to be fully initialized") - await initialized.wait() + driver_ready = asyncio.Event() + + async def handle_ha_shutdown(event: Event) -> None: + """Handle HA shutdown.""" + await disconnect_client(hass, entry, client, listen_task, platform_task) + + listen_task = asyncio.create_task( + client_listen(hass, entry, client, driver_ready) + ) + hass.data[DOMAIN][entry.entry_id][DATA_CLIENT_LISTEN_TASK] = listen_task + unsubscribe_callbacks.append( + hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) + ) + + await driver_ready.wait() + + LOGGER.info("Connection to Zwave JS Server initialized") + + # Check for nodes that no longer exist and remove them + stored_devices = device_registry.async_entries_for_config_entry( + dev_reg, entry.entry_id + ) + known_devices = [ + dev_reg.async_get_device({get_device_id(client, node)}) + for node in client.driver.controller.nodes.values() + ] + + # Devices that are in the device registry that are not known by the controller can be removed + for device in stored_devices: + if device not in known_devices: + dev_reg.async_remove_device(device.id) # run discovery on all ready nodes for node in client.driver.controller.nodes.values(): @@ -268,11 +250,46 @@ async def start_platforms() -> None: "node removed", lambda event: async_on_node_removed(event["node"]) ) - hass.async_create_task(start_platforms()) + platform_task = hass.async_create_task(start_platforms()) + hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task return True +async def client_listen( + hass: HomeAssistant, + entry: ConfigEntry, + client: ZwaveClient, + driver_ready: asyncio.Event, +) -> None: + """Listen with the client.""" + try: + await client.listen(driver_ready) + except BaseZwaveJSServerError: + # The entry needs to be reloaded since a new driver state + # will be acquired on reconnect. + # All model instances will be replaced when the new state is acquired. + hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) + + +async def disconnect_client( + hass: HomeAssistant, + entry: ConfigEntry, + client: ZwaveClient, + listen_task: asyncio.Task, + platform_task: asyncio.Task, +) -> None: + """Disconnect client.""" + await client.disconnect() + + listen_task.cancel() + platform_task.cancel() + + await asyncio.gather(listen_task, platform_task) + + LOGGER.info("Disconnected from Zwave JS Server") + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( @@ -291,7 +308,14 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: for unsub in info[DATA_UNSUBSCRIBE]: unsub() - await info[DATA_CLIENT].disconnect() + if DATA_CLIENT_LISTEN_TASK in info: + await disconnect_client( + hass, + entry, + info[DATA_CLIENT], + info[DATA_CLIENT_LISTEN_TASK], + platform_task=info[DATA_START_PLATFORM_TASK], + ) return True diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index b039113270d5b7..334a2cccd4faaa 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -9,7 +9,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import DOMAIN @@ -54,14 +53,6 @@ async def async_added_to_hass(self) -> None: self.info.node.on(EVENT_VALUE_UPDATED, self._value_changed) ) - self.async_on_remove( - async_dispatcher_connect( - self.hass, - f"{DOMAIN}_{self.config_entry.entry_id}_connection_state", - self.async_write_ha_state, - ) - ) - @property def device_info(self) -> dict: """Return device information for the device registry.""" diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 586a6492a1a962..de77ebbf5e0c36 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.15.0"], + "requirements": ["zwave-js-server-python==0.16.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0a7e785668535a..0252952a67743a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.15.0 +zwave-js-server-python==0.16.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3809293d874a45..5af21006da7522 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1194,4 +1194,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.15.0 +zwave-js-server-python==0.16.0 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 0e0ebdee3c6082..b5301f4cd2ffbd 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,6 +1,7 @@ """Provide common Z-Wave JS fixtures.""" +import asyncio import json -from unittest.mock import DEFAULT, Mock, patch +from unittest.mock import DEFAULT, AsyncMock, patch import pytest from zwave_js_server.event import Event @@ -149,35 +150,31 @@ def in_wall_smart_fan_control_state_fixture(): def mock_client_fixture(controller_state, version_state): """Mock a client.""" - def mock_callback(): - callbacks = [] - - def add_callback(cb): - callbacks.append(cb) - return DEFAULT - - return callbacks, Mock(side_effect=add_callback) - with patch( "homeassistant.components.zwave_js.ZwaveClient", autospec=True ) as client_class: client = client_class.return_value - connect_callback, client.register_on_connect = mock_callback() - initialized_callback, client.register_on_initialized = mock_callback() - async def connect(): - for cb in connect_callback: - await cb() + await asyncio.sleep(0) + client.state = "connected" + client.connected = True - for cb in initialized_callback: - await cb() + async def listen(driver_ready: asyncio.Event) -> None: + driver_ready.set() - client.connect = Mock(side_effect=connect) + async def disconnect(): + client.state = "disconnected" + client.connected = False + + client.connect = AsyncMock(side_effect=connect) + client.listen = AsyncMock(side_effect=listen) + client.disconnect = AsyncMock(side_effect=disconnect) client.driver = Driver(client, controller_state) + client.version = VersionInfo.from_message(version_state) client.ws_server_url = "ws://test:3000/zjs" - client.state = "connected" + yield client diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index fa61b3deb2721e..1aad07400ad87d 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -50,17 +50,11 @@ async def test_entry_setup_unload(hass, client, integration): entry = integration assert client.connect.call_count == 1 - assert client.register_on_initialized.call_count == 1 - assert client.register_on_disconnect.call_count == 1 - assert client.register_on_connect.call_count == 1 assert entry.state == ENTRY_STATE_LOADED await hass.config_entries.async_unload(entry.entry_id) assert client.disconnect.call_count == 1 - assert client.register_on_initialized.return_value.call_count == 1 - assert client.register_on_disconnect.return_value.call_count == 1 - assert client.register_on_connect.return_value.call_count == 1 assert entry.state == ENTRY_STATE_NOT_LOADED @@ -71,38 +65,6 @@ async def test_home_assistant_stop(hass, client, integration): assert client.disconnect.call_count == 1 -async def test_availability_reflect_connection_status( - hass, client, multisensor_6, integration -): - """Test we handle disconnect and reconnect.""" - on_initialized = client.register_on_initialized.call_args[0][0] - on_disconnect = client.register_on_disconnect.call_args[0][0] - state = hass.states.get(AIR_TEMPERATURE_SENSOR) - - assert state - assert state.state != STATE_UNAVAILABLE - - client.connected = False - - await on_disconnect() - await hass.async_block_till_done() - - state = hass.states.get(AIR_TEMPERATURE_SENSOR) - - assert state - assert state.state == STATE_UNAVAILABLE - - client.connected = True - - await on_initialized() - await hass.async_block_till_done() - - state = hass.states.get(AIR_TEMPERATURE_SENSOR) - - assert state - assert state.state != STATE_UNAVAILABLE - - async def test_initialized_timeout(hass, client, connect_timeout): """Test we handle a timeout during client initialization.""" entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) From 0435586bb165324f3026f8577601796b45d56f8e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Feb 2021 22:45:51 +0100 Subject: [PATCH 0136/1818] Update frontend to 20210127.7 (#45874) --- 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 9d21be79912c90..65a5497d1f9e56 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.6"], + "requirements": ["home-assistant-frontend==20210127.7"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d6c26b24a0102c..31a7414dc3cd17 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.6 +home-assistant-frontend==20210127.7 httpx==0.16.1 jinja2>=2.11.2 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 0252952a67743a..f68f9abcc08257 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.6 +home-assistant-frontend==20210127.7 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5af21006da7522..d5648e33e89dec 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -402,7 +402,7 @@ hole==0.5.1 holidays==0.10.4 # homeassistant.components.frontend -home-assistant-frontend==20210127.6 +home-assistant-frontend==20210127.7 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From d9dba1b7ab105d3213aa4cba9d5eaa373bf21772 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Feb 2021 22:57:06 +0100 Subject: [PATCH 0137/1818] Bump Freebox to 0.0.9 (#45837) * Bump Freebox to 0.0.9 * Remove @SNoof85 from code owners * Module is now freebox_api --- CODEOWNERS | 2 +- homeassistant/components/freebox/config_flow.py | 2 +- homeassistant/components/freebox/manifest.json | 4 ++-- homeassistant/components/freebox/router.py | 8 ++++---- homeassistant/components/freebox/switch.py | 2 +- requirements_all.txt | 6 +++--- requirements_test_all.txt | 6 +++--- tests/components/freebox/test_config_flow.py | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index dc0129c8b8e941..499b7e131f7a6d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -158,7 +158,7 @@ homeassistant/components/flunearyou/* @bachya homeassistant/components/forked_daapd/* @uvjustin homeassistant/components/fortios/* @kimfrellsen homeassistant/components/foscam/* @skgsergio -homeassistant/components/freebox/* @snoof85 @Quentame +homeassistant/components/freebox/* @hacf-fr @Quentame homeassistant/components/fronius/* @nielstron homeassistant/components/frontend/* @home-assistant/frontend homeassistant/components/garmin_connect/* @cyberjunky diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index d776c34c4f9bdf..2ee52884c88b3f 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure the Freebox integration.""" import logging -from aiofreepybox.exceptions import AuthorizationError, HttpRequestError +from freebox_api.exceptions import AuthorizationError, HttpRequestError import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/freebox/manifest.json b/homeassistant/components/freebox/manifest.json index ae96f7f6510c5c..2739849b547911 100644 --- a/homeassistant/components/freebox/manifest.json +++ b/homeassistant/components/freebox/manifest.json @@ -3,7 +3,7 @@ "name": "Freebox", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/freebox", - "requirements": ["aiofreepybox==0.0.8"], + "requirements": ["freebox-api==0.0.9"], "after_dependencies": ["discovery"], - "codeowners": ["@snoof85", "@Quentame"] + "codeowners": ["@hacf-fr", "@Quentame"] } diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index daa57a89c47cdc..2511280f71930e 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -4,9 +4,9 @@ from pathlib import Path from typing import Any, Dict, List, Optional -from aiofreepybox import Freepybox -from aiofreepybox.api.wifi import Wifi -from aiofreepybox.exceptions import HttpRequestError +from freebox_api import Freepybox +from freebox_api.api.wifi import Wifi +from freebox_api.exceptions import HttpRequestError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT @@ -138,7 +138,7 @@ async def update_sensors(self) -> None: "serial": syst_datas["serial"], } - self.call_list = await self._api.call.get_call_list() + self.call_list = await self._api.call.get_calls_log() async_dispatcher_send(self.hass, self.signal_sensor_update) diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index 00f87e21f47a37..b1cfc93eb53b90 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -2,7 +2,7 @@ import logging from typing import Dict -from aiofreepybox.exceptions import InsufficientPermissionsError +from freebox_api.exceptions import InsufficientPermissionsError from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry diff --git a/requirements_all.txt b/requirements_all.txt index c29204f21ef310..d776522992da80 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -156,9 +156,6 @@ aioesphomeapi==2.6.4 # homeassistant.components.flo aioflo==0.4.1 -# homeassistant.components.freebox -aiofreepybox==0.0.8 - # homeassistant.components.yi aioftp==0.12.0 @@ -613,6 +610,9 @@ foobot_async==1.0.0 # homeassistant.components.fortios fortiosapi==0.10.8 +# homeassistant.components.freebox +freebox-api==0.0.9 + # homeassistant.components.free_mobile freesms==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 05565349d0bba7..95fb6e3dd960b0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -93,9 +93,6 @@ aioesphomeapi==2.6.4 # homeassistant.components.flo aioflo==0.4.1 -# homeassistant.components.freebox -aiofreepybox==0.0.8 - # homeassistant.components.guardian aioguardian==1.0.4 @@ -313,6 +310,9 @@ fnvhash==0.1.0 # homeassistant.components.foobot foobot_async==1.0.0 +# homeassistant.components.freebox +freebox-api==0.0.9 + # homeassistant.components.fritz # homeassistant.components.fritzbox_callmonitor # homeassistant.components.fritzbox_netmonitor diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index f7150df7efc217..197be7bd3a64a5 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -1,7 +1,7 @@ """Tests for the Freebox config flow.""" from unittest.mock import AsyncMock, patch -from aiofreepybox.exceptions import ( +from freebox_api.exceptions import ( AuthorizationError, HttpRequestError, InvalidTokenError, From 66e045f570520442db0c3bcaf47b12bed332d0b5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Feb 2021 22:57:18 +0100 Subject: [PATCH 0138/1818] Bumped version to 2021.2.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e946a8ae8e26e4..e23f47101b4983 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 048f36c77ec488eee058df93efe76929054204ca Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 3 Feb 2021 02:44:34 -0500 Subject: [PATCH 0139/1818] Bump plexapi to 3.4.1 (#45878) --- 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 f0f1e09a15c475..913f405cfcdc48 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.3.0", + "plexapi==4.3.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index d776522992da80..01d6fe0a92ac54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1137,7 +1137,7 @@ pillow==8.1.0 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.3.0 +plexapi==4.3.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 95fb6e3dd960b0..95eab6a7440139 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -574,7 +574,7 @@ pilight==0.1.1 pillow==8.1.0 # homeassistant.components.plex -plexapi==4.3.0 +plexapi==4.3.1 # homeassistant.components.plex plexauth==0.0.6 From 45ac6df76f9425ffed98cb1fdbfe83096321203f Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 3 Feb 2021 10:41:02 +0100 Subject: [PATCH 0140/1818] Update docker base image 2021.02.0 (#45889) --- build.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.json b/build.json index 1cf4217146dbfa..0183b61c67c35b 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:2021.01.1", - "armhf": "homeassistant/armhf-homeassistant-base:2021.01.1", - "armv7": "homeassistant/armv7-homeassistant-base:2021.01.1", - "amd64": "homeassistant/amd64-homeassistant-base:2021.01.1", - "i386": "homeassistant/i386-homeassistant-base:2021.01.1" + "aarch64": "homeassistant/aarch64-homeassistant-base:2021.02.0", + "armhf": "homeassistant/armhf-homeassistant-base:2021.02.0", + "armv7": "homeassistant/armv7-homeassistant-base:2021.02.0", + "amd64": "homeassistant/amd64-homeassistant-base:2021.02.0", + "i386": "homeassistant/i386-homeassistant-base:2021.02.0" }, "labels": { "io.hass.type": "core" From 959ed6d077fba573f71cab5e8299464b372b1997 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:46:49 +0100 Subject: [PATCH 0141/1818] Update translations --- .../components/abode/translations/de.json | 2 +- .../components/abode/translations/es.json | 4 +- .../components/abode/translations/fr.json | 21 +++- .../components/abode/translations/tr.json | 34 ++++++ .../components/abode/translations/uk.json | 35 ++++++ .../accuweather/translations/ca.json | 2 +- .../accuweather/translations/cs.json | 2 +- .../accuweather/translations/de.json | 9 +- .../accuweather/translations/en.json | 2 +- .../accuweather/translations/et.json | 2 +- .../accuweather/translations/fr.json | 3 +- .../accuweather/translations/it.json | 2 +- .../accuweather/translations/no.json | 2 +- .../accuweather/translations/pl.json | 2 +- .../accuweather/translations/ru.json | 2 +- .../accuweather/translations/sensor.uk.json | 9 ++ .../accuweather/translations/tr.json | 38 ++++++ .../accuweather/translations/uk.json | 21 +++- .../accuweather/translations/zh-Hant.json | 2 +- .../components/acmeda/translations/de.json | 5 +- .../components/acmeda/translations/tr.json | 11 ++ .../components/acmeda/translations/uk.json | 15 +++ .../components/adguard/translations/de.json | 6 +- .../components/adguard/translations/no.json | 4 +- .../components/adguard/translations/ru.json | 4 +- .../components/adguard/translations/tr.json | 20 ++++ .../components/adguard/translations/uk.json | 28 +++++ .../advantage_air/translations/de.json | 5 +- .../advantage_air/translations/tr.json | 19 +++ .../advantage_air/translations/uk.json | 20 ++++ .../components/agent_dvr/translations/de.json | 4 +- .../components/agent_dvr/translations/tr.json | 20 ++++ .../components/agent_dvr/translations/uk.json | 20 ++++ .../components/airly/translations/de.json | 7 +- .../components/airly/translations/tr.json | 17 +++ .../components/airly/translations/uk.json | 28 +++++ .../components/airnow/translations/ca.json | 26 +++++ .../components/airnow/translations/cs.json | 23 ++++ .../components/airnow/translations/de.json | 24 ++++ .../components/airnow/translations/en.json | 9 +- .../components/airnow/translations/es.json | 26 +++++ .../components/airnow/translations/et.json | 26 +++++ .../components/airnow/translations/fr.json | 26 +++++ .../components/airnow/translations/it.json | 26 +++++ .../components/airnow/translations/lb.json | 24 ++++ .../components/airnow/translations/no.json | 26 +++++ .../components/airnow/translations/pl.json | 26 +++++ .../components/airnow/translations/pt.json | 23 ++++ .../components/airnow/translations/ru.json | 26 +++++ .../components/airnow/translations/tr.json | 25 ++++ .../components/airnow/translations/uk.json | 26 +++++ .../airnow/translations/zh-Hant.json | 26 +++++ .../components/airvisual/translations/ar.json | 11 ++ .../components/airvisual/translations/ca.json | 24 +++- .../components/airvisual/translations/de.json | 11 +- .../components/airvisual/translations/en.json | 14 +++ .../components/airvisual/translations/et.json | 22 +++- .../components/airvisual/translations/fr.json | 10 +- .../components/airvisual/translations/no.json | 22 +++- .../components/airvisual/translations/pl.json | 22 +++- .../components/airvisual/translations/sv.json | 3 +- .../components/airvisual/translations/tr.json | 59 ++++++++++ .../components/airvisual/translations/uk.json | 57 +++++++++ .../airvisual/translations/zh-Hant.json | 22 +++- .../alarm_control_panel/translations/tr.json | 6 + .../alarm_control_panel/translations/uk.json | 29 ++++- .../alarmdecoder/translations/de.json | 7 +- .../alarmdecoder/translations/tr.json | 48 ++++++++ .../alarmdecoder/translations/uk.json | 74 ++++++++++++ .../components/almond/translations/de.json | 6 +- .../components/almond/translations/no.json | 4 +- .../components/almond/translations/ru.json | 4 +- .../components/almond/translations/tr.json | 8 ++ .../components/almond/translations/uk.json | 19 +++ .../ambiclimate/translations/de.json | 4 +- .../ambiclimate/translations/fr.json | 2 +- .../ambiclimate/translations/tr.json | 7 ++ .../ambiclimate/translations/uk.json | 22 ++++ .../ambient_station/translations/de.json | 4 +- .../ambient_station/translations/tr.json | 17 +++ .../ambient_station/translations/uk.json | 20 ++++ .../components/apple_tv/translations/fr.json | 15 ++- .../components/apple_tv/translations/lb.json | 8 ++ .../components/apple_tv/translations/tr.json | 2 + .../components/apple_tv/translations/uk.json | 64 ++++++++++ .../apple_tv/translations/zh-Hans.json | 17 +++ .../components/arcam_fmj/translations/de.json | 3 +- .../components/arcam_fmj/translations/ro.json | 9 ++ .../components/arcam_fmj/translations/tr.json | 17 +++ .../components/arcam_fmj/translations/uk.json | 27 +++++ .../components/atag/translations/de.json | 6 +- .../components/atag/translations/tr.json | 21 ++++ .../components/atag/translations/uk.json | 21 ++++ .../components/august/translations/de.json | 5 +- .../components/august/translations/tr.json | 31 +++++ .../components/august/translations/uk.json | 32 +++++ .../components/aurora/translations/de.json | 14 ++- .../components/aurora/translations/fr.json | 26 +++++ .../components/aurora/translations/tr.json | 16 +++ .../components/aurora/translations/uk.json | 26 +++++ .../components/auth/translations/de.json | 2 +- .../components/auth/translations/tr.json | 22 ++++ .../components/auth/translations/uk.json | 23 +++- .../components/awair/translations/de.json | 12 +- .../components/awair/translations/tr.json | 27 +++++ .../components/awair/translations/uk.json | 29 +++++ .../components/axis/translations/ca.json | 2 +- .../components/axis/translations/cs.json | 2 +- .../components/axis/translations/de.json | 5 +- .../components/axis/translations/en.json | 2 +- .../components/axis/translations/et.json | 2 +- .../components/axis/translations/it.json | 2 +- .../components/axis/translations/no.json | 2 +- .../components/axis/translations/pl.json | 2 +- .../components/axis/translations/ru.json | 2 +- .../components/axis/translations/tr.json | 23 ++++ .../components/axis/translations/uk.json | 37 ++++++ .../components/axis/translations/zh-Hant.json | 2 +- .../azure_devops/translations/de.json | 7 +- .../azure_devops/translations/fr.json | 2 +- .../azure_devops/translations/tr.json | 28 +++++ .../azure_devops/translations/uk.json | 16 ++- .../binary_sensor/translations/de.json | 16 +++ .../binary_sensor/translations/en.json | 4 +- .../binary_sensor/translations/pl.json | 2 +- .../binary_sensor/translations/tr.json | 22 ++++ .../binary_sensor/translations/uk.json | 108 +++++++++++++++-- .../components/blebox/translations/de.json | 6 +- .../components/blebox/translations/tr.json | 20 ++++ .../components/blebox/translations/uk.json | 24 ++++ .../components/blink/translations/de.json | 5 +- .../components/blink/translations/tr.json | 24 ++++ .../components/blink/translations/uk.json | 40 +++++++ .../bmw_connected_drive/translations/ca.json | 30 +++++ .../bmw_connected_drive/translations/cs.json | 19 +++ .../bmw_connected_drive/translations/de.json | 19 +++ .../bmw_connected_drive/translations/en.json | 1 - .../bmw_connected_drive/translations/es.json | 30 +++++ .../bmw_connected_drive/translations/et.json | 30 +++++ .../bmw_connected_drive/translations/fr.json | 29 +++++ .../bmw_connected_drive/translations/it.json | 30 +++++ .../bmw_connected_drive/translations/lb.json | 19 +++ .../bmw_connected_drive/translations/no.json | 30 +++++ .../bmw_connected_drive/translations/pl.json | 30 +++++ .../bmw_connected_drive/translations/pt.json | 19 +++ .../bmw_connected_drive/translations/ru.json | 30 +++++ .../bmw_connected_drive/translations/tr.json | 19 +++ .../bmw_connected_drive/translations/uk.json | 30 +++++ .../translations/zh-Hant.json | 30 +++++ .../components/bond/translations/de.json | 6 +- .../components/bond/translations/tr.json | 25 ++++ .../components/bond/translations/uk.json | 23 +++- .../components/braviatv/translations/de.json | 7 +- .../components/braviatv/translations/tr.json | 29 +++++ .../components/braviatv/translations/uk.json | 39 +++++++ .../components/broadlink/translations/de.json | 6 +- .../components/broadlink/translations/tr.json | 39 +++++++ .../components/broadlink/translations/uk.json | 47 ++++++++ .../components/brother/translations/de.json | 2 +- .../components/brother/translations/tr.json | 14 +++ .../components/brother/translations/uk.json | 30 +++++ .../components/bsblan/translations/de.json | 2 +- .../components/bsblan/translations/tr.json | 8 ++ .../components/bsblan/translations/uk.json | 24 ++++ .../components/canary/translations/de.json | 3 +- .../components/canary/translations/tr.json | 19 +++ .../components/canary/translations/uk.json | 31 +++++ .../components/cast/translations/de.json | 4 +- .../components/cast/translations/tr.json | 12 ++ .../components/cast/translations/uk.json | 6 +- .../cert_expiry/translations/tr.json | 15 +++ .../cert_expiry/translations/uk.json | 24 ++++ .../components/climate/translations/tr.json | 6 + .../components/climate/translations/uk.json | 14 +-- .../components/cloud/translations/ca.json | 6 +- .../components/cloud/translations/de.json | 15 +++ .../components/cloud/translations/fr.json | 16 +++ .../components/cloud/translations/pl.json | 2 +- .../components/cloud/translations/tr.json | 3 + .../components/cloud/translations/uk.json | 16 +++ .../cloudflare/translations/de.json | 8 ++ .../cloudflare/translations/tr.json | 9 ++ .../cloudflare/translations/uk.json | 35 ++++++ .../components/control4/translations/de.json | 6 +- .../components/control4/translations/tr.json | 21 ++++ .../components/control4/translations/uk.json | 15 ++- .../coolmaster/translations/de.json | 2 +- .../coolmaster/translations/tr.json | 15 +++ .../coolmaster/translations/uk.json | 22 ++++ .../coronavirus/translations/tr.json | 15 +++ .../coronavirus/translations/uk.json | 15 +++ .../components/cover/translations/tr.json | 6 + .../components/cover/translations/uk.json | 21 +++- .../components/daikin/translations/de.json | 6 +- .../components/daikin/translations/pt.json | 2 +- .../components/daikin/translations/tr.json | 22 ++++ .../components/daikin/translations/uk.json | 24 ++++ .../components/deconz/translations/cs.json | 11 +- .../components/deconz/translations/de.json | 12 +- .../components/deconz/translations/no.json | 4 +- .../components/deconz/translations/pl.json | 6 +- .../components/deconz/translations/ru.json | 4 +- .../components/deconz/translations/tr.json | 43 +++++++ .../components/deconz/translations/uk.json | 105 +++++++++++++++++ .../components/demo/translations/tr.json | 12 ++ .../components/demo/translations/uk.json | 21 ++++ .../components/denonavr/translations/de.json | 4 + .../components/denonavr/translations/tr.json | 16 +++ .../components/denonavr/translations/uk.json | 48 ++++++++ .../device_tracker/translations/de.json | 8 +- .../device_tracker/translations/uk.json | 12 +- .../devolo_home_control/translations/de.json | 7 +- .../devolo_home_control/translations/tr.json | 18 +++ .../devolo_home_control/translations/uk.json | 20 ++++ .../components/dexcom/translations/de.json | 3 +- .../components/dexcom/translations/fr.json | 2 +- .../components/dexcom/translations/tr.json | 22 ++++ .../components/dexcom/translations/uk.json | 32 +++++ .../dialogflow/translations/de.json | 4 + .../dialogflow/translations/tr.json | 8 ++ .../dialogflow/translations/uk.json | 17 +++ .../components/directv/translations/tr.json | 21 ++++ .../components/directv/translations/uk.json | 22 ++++ .../components/doorbird/translations/de.json | 2 +- .../components/doorbird/translations/tr.json | 22 ++++ .../components/doorbird/translations/uk.json | 36 ++++++ .../components/dsmr/translations/de.json | 7 ++ .../components/dsmr/translations/fr.json | 10 ++ .../components/dsmr/translations/tr.json | 5 + .../components/dsmr/translations/uk.json | 17 +++ .../components/dunehd/translations/tr.json | 19 +++ .../components/dunehd/translations/uk.json | 21 ++++ .../components/eafm/translations/de.json | 7 ++ .../components/eafm/translations/tr.json | 16 +++ .../components/eafm/translations/uk.json | 17 +++ .../components/ecobee/translations/de.json | 3 + .../components/ecobee/translations/tr.json | 14 +++ .../components/ecobee/translations/uk.json | 24 ++++ .../components/econet/translations/ca.json | 22 ++++ .../components/econet/translations/en.json | 1 + .../components/econet/translations/es.json | 21 ++++ .../components/econet/translations/et.json | 22 ++++ .../components/econet/translations/it.json | 22 ++++ .../components/econet/translations/no.json | 22 ++++ .../components/econet/translations/pl.json | 22 ++++ .../components/econet/translations/ru.json | 22 ++++ .../components/econet/translations/tr.json | 22 ++++ .../econet/translations/zh-Hant.json | 22 ++++ .../components/elgato/translations/de.json | 4 +- .../components/elgato/translations/tr.json | 19 +++ .../components/elgato/translations/uk.json | 25 ++++ .../components/elkm1/translations/de.json | 2 +- .../components/elkm1/translations/tr.json | 21 ++++ .../components/elkm1/translations/uk.json | 27 +++++ .../emulated_roku/translations/de.json | 2 +- .../emulated_roku/translations/tr.json | 7 ++ .../emulated_roku/translations/uk.json | 21 ++++ .../components/enocean/translations/tr.json | 23 ++++ .../components/enocean/translations/uk.json | 25 ++++ .../components/epson/translations/de.json | 6 +- .../components/epson/translations/tr.json | 3 + .../components/epson/translations/uk.json | 16 +++ .../components/esphome/translations/de.json | 3 +- .../components/esphome/translations/pt.json | 2 +- .../components/esphome/translations/tr.json | 19 +++ .../components/esphome/translations/uk.json | 11 +- .../components/fan/translations/tr.json | 10 ++ .../components/fan/translations/uk.json | 12 +- .../fireservicerota/translations/fr.json | 27 +++++ .../fireservicerota/translations/tr.json | 10 ++ .../fireservicerota/translations/uk.json | 29 +++++ .../components/firmata/translations/tr.json | 7 ++ .../components/firmata/translations/uk.json | 7 ++ .../flick_electric/translations/de.json | 2 +- .../flick_electric/translations/tr.json | 20 ++++ .../flick_electric/translations/uk.json | 23 ++++ .../components/flo/translations/de.json | 7 +- .../components/flo/translations/tr.json | 21 ++++ .../components/flo/translations/uk.json | 21 ++++ .../components/flume/translations/de.json | 4 +- .../components/flume/translations/tr.json | 20 ++++ .../components/flume/translations/uk.json | 24 ++++ .../flunearyou/translations/de.json | 2 +- .../flunearyou/translations/tr.json | 18 +++ .../flunearyou/translations/uk.json | 20 ++++ .../forked_daapd/translations/de.json | 2 +- .../forked_daapd/translations/tr.json | 21 ++++ .../forked_daapd/translations/uk.json | 42 +++++++ .../components/foscam/translations/af.json | 11 ++ .../components/foscam/translations/ca.json | 24 ++++ .../components/foscam/translations/cs.json | 23 ++++ .../components/foscam/translations/de.json | 23 ++++ .../components/foscam/translations/en.json | 44 +++---- .../components/foscam/translations/es.json | 24 ++++ .../components/foscam/translations/et.json | 24 ++++ .../components/foscam/translations/fr.json | 24 ++++ .../components/foscam/translations/it.json | 24 ++++ .../components/foscam/translations/lb.json | 16 +++ .../components/foscam/translations/no.json | 24 ++++ .../components/foscam/translations/pl.json | 24 ++++ .../components/foscam/translations/pt.json | 11 ++ .../components/foscam/translations/ru.json | 24 ++++ .../components/foscam/translations/tr.json | 24 ++++ .../foscam/translations/zh-Hant.json | 24 ++++ .../components/freebox/translations/de.json | 6 +- .../components/freebox/translations/tr.json | 20 ++++ .../components/freebox/translations/uk.json | 25 ++++ .../components/fritzbox/translations/ca.json | 10 +- .../components/fritzbox/translations/cs.json | 9 +- .../components/fritzbox/translations/de.json | 12 +- .../components/fritzbox/translations/en.json | 10 +- .../components/fritzbox/translations/et.json | 10 +- .../components/fritzbox/translations/it.json | 10 +- .../components/fritzbox/translations/no.json | 10 +- .../components/fritzbox/translations/pl.json | 10 +- .../components/fritzbox/translations/ru.json | 10 +- .../components/fritzbox/translations/tr.json | 35 ++++++ .../components/fritzbox/translations/uk.json | 31 +++++ .../fritzbox/translations/zh-Hant.json | 10 +- .../fritzbox_callmonitor/translations/ca.json | 41 +++++++ .../fritzbox_callmonitor/translations/cs.json | 21 ++++ .../fritzbox_callmonitor/translations/et.json | 41 +++++++ .../fritzbox_callmonitor/translations/it.json | 41 +++++++ .../fritzbox_callmonitor/translations/lb.json | 33 ++++++ .../fritzbox_callmonitor/translations/no.json | 41 +++++++ .../fritzbox_callmonitor/translations/pl.json | 41 +++++++ .../fritzbox_callmonitor/translations/ru.json | 41 +++++++ .../fritzbox_callmonitor/translations/tr.json | 41 +++++++ .../translations/zh-Hant.json | 41 +++++++ .../garmin_connect/translations/de.json | 8 +- .../garmin_connect/translations/tr.json | 20 ++++ .../garmin_connect/translations/uk.json | 23 ++++ .../components/gdacs/translations/de.json | 2 +- .../components/gdacs/translations/tr.json | 14 +++ .../components/gdacs/translations/uk.json | 15 +++ .../components/geofency/translations/de.json | 4 + .../components/geofency/translations/tr.json | 8 ++ .../components/geofency/translations/uk.json | 17 +++ .../geonetnz_quakes/translations/tr.json | 7 ++ .../geonetnz_quakes/translations/uk.json | 16 +++ .../geonetnz_volcano/translations/de.json | 3 + .../geonetnz_volcano/translations/tr.json | 14 +++ .../geonetnz_volcano/translations/uk.json | 15 +++ .../components/gios/translations/de.json | 4 +- .../components/gios/translations/fr.json | 5 + .../components/gios/translations/it.json | 2 +- .../components/gios/translations/lb.json | 5 + .../components/gios/translations/tr.json | 10 ++ .../components/gios/translations/uk.json | 27 +++++ .../components/glances/translations/de.json | 4 +- .../components/glances/translations/tr.json | 29 +++++ .../components/glances/translations/uk.json | 36 ++++++ .../components/goalzero/translations/de.json | 13 ++- .../components/goalzero/translations/fr.json | 2 +- .../components/goalzero/translations/tr.json | 18 +++ .../components/goalzero/translations/uk.json | 22 ++++ .../components/gogogate2/translations/tr.json | 20 ++++ .../components/gogogate2/translations/uk.json | 22 ++++ .../components/gpslogger/translations/de.json | 4 + .../components/gpslogger/translations/tr.json | 8 ++ .../components/gpslogger/translations/uk.json | 17 +++ .../components/gree/translations/de.json | 13 +++ .../components/gree/translations/tr.json | 12 ++ .../components/gree/translations/uk.json | 13 +++ .../components/griddy/translations/de.json | 4 +- .../components/griddy/translations/tr.json | 3 + .../components/griddy/translations/uk.json | 20 ++++ .../components/group/translations/uk.json | 2 +- .../components/guardian/translations/de.json | 3 +- .../components/guardian/translations/tr.json | 17 +++ .../components/guardian/translations/uk.json | 21 ++++ .../components/hangouts/translations/de.json | 2 +- .../components/hangouts/translations/tr.json | 16 +++ .../components/hangouts/translations/uk.json | 31 +++++ .../components/harmony/translations/de.json | 2 +- .../components/harmony/translations/tr.json | 18 +++ .../components/harmony/translations/uk.json | 36 ++++++ .../components/hassio/translations/ca.json | 2 +- .../components/hassio/translations/de.json | 15 +++ .../components/hassio/translations/en.json | 4 +- .../components/hassio/translations/fr.json | 15 +++ .../components/hassio/translations/tr.json | 8 +- .../components/hassio/translations/uk.json | 16 +++ .../components/heos/translations/de.json | 5 +- .../components/heos/translations/tr.json | 17 +++ .../components/heos/translations/uk.json | 19 +++ .../hisense_aehw4a1/translations/de.json | 2 +- .../hisense_aehw4a1/translations/tr.json | 12 ++ .../hisense_aehw4a1/translations/uk.json | 13 +++ .../components/hlk_sw16/translations/de.json | 6 + .../components/hlk_sw16/translations/tr.json | 21 ++++ .../components/hlk_sw16/translations/uk.json | 21 ++++ .../home_connect/translations/de.json | 7 +- .../home_connect/translations/uk.json | 16 +++ .../homeassistant/translations/de.json | 13 ++- .../homeassistant/translations/fr.json | 21 ++++ .../homeassistant/translations/tr.json | 2 + .../homeassistant/translations/uk.json | 21 ++++ .../components/homekit/translations/ca.json | 31 +++-- .../components/homekit/translations/cs.json | 8 +- .../components/homekit/translations/de.json | 7 +- .../components/homekit/translations/en.json | 9 +- .../components/homekit/translations/et.json | 27 ++++- .../components/homekit/translations/it.json | 25 +++- .../components/homekit/translations/no.json | 29 +++-- .../components/homekit/translations/pl.json | 25 +++- .../components/homekit/translations/ro.json | 9 ++ .../components/homekit/translations/ru.json | 11 +- .../components/homekit/translations/sv.json | 21 ++++ .../components/homekit/translations/tr.json | 60 ++++++++++ .../components/homekit/translations/uk.json | 51 +++++++- .../homekit/translations/zh-Hant.json | 31 +++-- .../homekit_controller/translations/cs.json | 6 +- .../homekit_controller/translations/de.json | 4 +- .../homekit_controller/translations/pl.json | 6 +- .../homekit_controller/translations/tr.json | 35 ++++++ .../homekit_controller/translations/uk.json | 71 ++++++++++++ .../homematicip_cloud/translations/de.json | 8 +- .../homematicip_cloud/translations/tr.json | 9 ++ .../homematicip_cloud/translations/uk.json | 29 +++++ .../huawei_lte/translations/de.json | 5 +- .../huawei_lte/translations/tr.json | 27 +++++ .../huawei_lte/translations/uk.json | 42 +++++++ .../components/hue/translations/cs.json | 2 +- .../components/hue/translations/de.json | 20 ++-- .../components/hue/translations/pl.json | 10 +- .../components/hue/translations/pt.json | 8 +- .../components/hue/translations/tr.json | 48 ++++++++ .../components/hue/translations/uk.json | 67 +++++++++++ .../huisbaasje/translations/ca.json | 21 ++++ .../huisbaasje/translations/cs.json | 21 ++++ .../huisbaasje/translations/en.json | 21 ++++ .../huisbaasje/translations/et.json | 21 ++++ .../huisbaasje/translations/it.json | 21 ++++ .../huisbaasje/translations/no.json | 21 ++++ .../huisbaasje/translations/pl.json | 21 ++++ .../huisbaasje/translations/ru.json | 21 ++++ .../huisbaasje/translations/tr.json | 21 ++++ .../huisbaasje/translations/zh-Hant.json | 21 ++++ .../humidifier/translations/tr.json | 25 ++++ .../humidifier/translations/uk.json | 24 +++- .../translations/tr.json | 18 +++ .../translations/uk.json | 23 ++++ .../hvv_departures/translations/tr.json | 20 ++++ .../hvv_departures/translations/uk.json | 47 ++++++++ .../components/hyperion/translations/fr.json | 4 + .../components/hyperion/translations/it.json | 2 +- .../components/hyperion/translations/tr.json | 12 +- .../components/hyperion/translations/uk.json | 53 +++++++++ .../components/iaqualink/translations/de.json | 6 + .../components/iaqualink/translations/tr.json | 19 +++ .../components/iaqualink/translations/uk.json | 20 ++++ .../components/icloud/translations/de.json | 9 +- .../components/icloud/translations/tr.json | 23 +++- .../components/icloud/translations/uk.json | 46 ++++++++ .../components/ifttt/translations/de.json | 4 + .../components/ifttt/translations/tr.json | 8 ++ .../components/ifttt/translations/uk.json | 17 +++ .../input_boolean/translations/uk.json | 2 +- .../input_datetime/translations/uk.json | 2 +- .../input_number/translations/uk.json | 2 +- .../input_select/translations/uk.json | 2 +- .../input_text/translations/uk.json | 2 +- .../components/insteon/translations/de.json | 6 +- .../components/insteon/translations/tr.json | 89 ++++++++++++++ .../components/insteon/translations/uk.json | 109 ++++++++++++++++++ .../components/ios/translations/de.json | 2 +- .../components/ios/translations/tr.json | 12 ++ .../components/ios/translations/uk.json | 12 ++ .../components/ipma/translations/lb.json | 5 + .../components/ipma/translations/tr.json | 11 ++ .../components/ipma/translations/uk.json | 17 ++- .../components/ipp/translations/de.json | 8 +- .../components/ipp/translations/tr.json | 17 +++ .../components/ipp/translations/uk.json | 35 ++++++ .../components/iqvia/translations/tr.json | 7 ++ .../components/iqvia/translations/uk.json | 19 +++ .../islamic_prayer_times/translations/de.json | 5 + .../islamic_prayer_times/translations/tr.json | 7 ++ .../islamic_prayer_times/translations/uk.json | 23 ++++ .../components/isy994/translations/de.json | 2 +- .../components/isy994/translations/tr.json | 30 +++++ .../components/isy994/translations/uk.json | 40 +++++++ .../components/izone/translations/de.json | 2 +- .../components/izone/translations/tr.json | 12 ++ .../components/izone/translations/uk.json | 13 +++ .../components/juicenet/translations/de.json | 10 +- .../components/juicenet/translations/tr.json | 21 ++++ .../components/juicenet/translations/uk.json | 21 ++++ .../components/kodi/translations/de.json | 4 + .../components/kodi/translations/tr.json | 35 ++++++ .../components/kodi/translations/uk.json | 50 ++++++++ .../components/konnected/translations/de.json | 12 +- .../components/konnected/translations/pl.json | 4 +- .../components/konnected/translations/tr.json | 60 ++++++++++ .../components/konnected/translations/uk.json | 108 +++++++++++++++++ .../components/kulersky/translations/de.json | 2 +- .../components/kulersky/translations/lb.json | 13 +++ .../components/kulersky/translations/tr.json | 8 +- .../components/kulersky/translations/uk.json | 13 +++ .../components/life360/translations/de.json | 2 + .../components/life360/translations/fr.json | 2 +- .../components/life360/translations/tr.json | 11 ++ .../components/life360/translations/uk.json | 27 +++++ .../components/lifx/translations/de.json | 4 +- .../components/lifx/translations/tr.json | 12 ++ .../components/lifx/translations/uk.json | 13 +++ .../components/light/translations/uk.json | 12 ++ .../components/local_ip/translations/de.json | 3 +- .../components/local_ip/translations/es.json | 2 +- .../components/local_ip/translations/tr.json | 17 +++ .../components/local_ip/translations/uk.json | 17 +++ .../components/locative/translations/de.json | 6 +- .../components/locative/translations/tr.json | 8 ++ .../components/locative/translations/uk.json | 17 +++ .../components/lock/translations/pt.json | 3 + .../components/lock/translations/tr.json | 6 + .../components/lock/translations/uk.json | 15 +++ .../logi_circle/translations/de.json | 8 +- .../logi_circle/translations/fr.json | 2 +- .../logi_circle/translations/lb.json | 1 + .../logi_circle/translations/tr.json | 10 ++ .../logi_circle/translations/uk.json | 28 +++++ .../components/lovelace/translations/de.json | 3 + .../components/lovelace/translations/fr.json | 10 ++ .../components/lovelace/translations/tr.json | 3 +- .../components/lovelace/translations/uk.json | 10 ++ .../components/luftdaten/translations/de.json | 1 + .../components/luftdaten/translations/tr.json | 8 ++ .../components/luftdaten/translations/uk.json | 18 +++ .../lutron_caseta/translations/ca.json | 61 +++++++++- .../lutron_caseta/translations/cs.json | 7 ++ .../lutron_caseta/translations/es.json | 36 +++++- .../lutron_caseta/translations/et.json | 61 +++++++++- .../lutron_caseta/translations/it.json | 61 +++++++++- .../lutron_caseta/translations/no.json | 61 +++++++++- .../lutron_caseta/translations/pl.json | 61 +++++++++- .../lutron_caseta/translations/ru.json | 42 ++++++- .../lutron_caseta/translations/tr.json | 72 ++++++++++++ .../lutron_caseta/translations/uk.json | 17 +++ .../lutron_caseta/translations/zh-Hant.json | 61 +++++++++- .../components/lyric/translations/ca.json | 16 +++ .../components/lyric/translations/cs.json | 16 +++ .../components/lyric/translations/en.json | 3 +- .../components/lyric/translations/et.json | 16 +++ .../components/lyric/translations/it.json | 16 +++ .../components/lyric/translations/no.json | 16 +++ .../components/lyric/translations/pl.json | 16 +++ .../components/lyric/translations/tr.json | 16 +++ .../lyric/translations/zh-Hant.json | 16 +++ .../components/mailgun/translations/de.json | 6 +- .../components/mailgun/translations/tr.json | 8 ++ .../components/mailgun/translations/uk.json | 17 +++ .../media_player/translations/tr.json | 6 + .../media_player/translations/uk.json | 13 ++- .../components/melcloud/translations/de.json | 2 +- .../components/melcloud/translations/tr.json | 20 ++++ .../components/melcloud/translations/uk.json | 22 ++++ .../components/met/translations/de.json | 3 + .../components/met/translations/tr.json | 15 +++ .../components/met/translations/uk.json | 19 +++ .../meteo_france/translations/de.json | 4 +- .../meteo_france/translations/tr.json | 21 ++++ .../meteo_france/translations/uk.json | 36 ++++++ .../components/metoffice/translations/de.json | 1 + .../components/metoffice/translations/tr.json | 21 ++++ .../components/metoffice/translations/uk.json | 22 ++++ .../components/mikrotik/translations/de.json | 5 +- .../components/mikrotik/translations/tr.json | 21 ++++ .../components/mikrotik/translations/uk.json | 36 ++++++ .../components/mill/translations/de.json | 3 + .../components/mill/translations/fr.json | 2 +- .../components/mill/translations/tr.json | 18 +++ .../components/mill/translations/uk.json | 18 +++ .../minecraft_server/translations/de.json | 2 +- .../minecraft_server/translations/tr.json | 2 +- .../minecraft_server/translations/uk.json | 22 ++++ .../mobile_app/translations/tr.json | 7 ++ .../mobile_app/translations/uk.json | 10 +- .../components/monoprice/translations/de.json | 2 +- .../components/monoprice/translations/tr.json | 25 ++++ .../components/monoprice/translations/uk.json | 40 +++++++ .../moon/translations/sensor.uk.json | 6 +- .../motion_blinds/translations/ca.json | 19 ++- .../motion_blinds/translations/cs.json | 11 +- .../motion_blinds/translations/de.json | 14 ++- .../motion_blinds/translations/en.json | 19 ++- .../motion_blinds/translations/es.json | 19 ++- .../motion_blinds/translations/et.json | 19 ++- .../motion_blinds/translations/fr.json | 17 +++ .../motion_blinds/translations/it.json | 19 ++- .../motion_blinds/translations/lb.json | 14 +++ .../motion_blinds/translations/no.json | 19 ++- .../motion_blinds/translations/pl.json | 19 ++- .../motion_blinds/translations/pt.json | 15 ++- .../motion_blinds/translations/ru.json | 19 ++- .../motion_blinds/translations/tr.json | 18 ++- .../motion_blinds/translations/uk.json | 37 ++++++ .../motion_blinds/translations/zh-Hant.json | 19 ++- .../components/mqtt/translations/cs.json | 10 +- .../components/mqtt/translations/de.json | 11 +- .../components/mqtt/translations/no.json | 4 +- .../components/mqtt/translations/pl.json | 16 +-- .../components/mqtt/translations/ru.json | 4 +- .../components/mqtt/translations/tr.json | 41 +++++++ .../components/mqtt/translations/uk.json | 70 ++++++++++- .../components/myq/translations/de.json | 4 +- .../components/myq/translations/tr.json | 21 ++++ .../components/myq/translations/uk.json | 21 ++++ .../components/neato/translations/de.json | 4 +- .../components/neato/translations/it.json | 14 +-- .../components/neato/translations/lb.json | 14 ++- .../components/neato/translations/pt.json | 15 ++- .../components/neato/translations/tr.json | 25 ++++ .../components/neato/translations/uk.json | 37 ++++++ .../components/nest/translations/de.json | 21 +++- .../components/nest/translations/fr.json | 3 +- .../components/nest/translations/it.json | 4 +- .../components/nest/translations/lb.json | 7 +- .../components/nest/translations/pl.json | 5 + .../components/nest/translations/pt.json | 7 +- .../components/nest/translations/tr.json | 13 ++- .../components/nest/translations/uk.json | 53 +++++++++ .../components/netatmo/translations/de.json | 6 +- .../components/netatmo/translations/tr.json | 32 +++++ .../components/netatmo/translations/uk.json | 43 +++++++ .../components/nexia/translations/de.json | 4 +- .../components/nexia/translations/tr.json | 21 ++++ .../components/nexia/translations/uk.json | 21 ++++ .../nightscout/translations/de.json | 6 + .../nightscout/translations/no.json | 2 +- .../nightscout/translations/tr.json | 9 +- .../nightscout/translations/uk.json | 23 ++++ .../components/notify/translations/uk.json | 2 +- .../components/notion/translations/de.json | 3 +- .../components/notion/translations/tr.json | 12 ++ .../components/notion/translations/uk.json | 20 ++++ .../components/nuheat/translations/de.json | 4 +- .../components/nuheat/translations/tr.json | 22 ++++ .../components/nuheat/translations/uk.json | 24 ++++ .../components/nuki/translations/ca.json | 18 +++ .../components/nuki/translations/cs.json | 18 +++ .../components/nuki/translations/en.json | 8 +- .../components/nuki/translations/et.json | 18 +++ .../components/nuki/translations/it.json | 18 +++ .../components/nuki/translations/no.json | 18 +++ .../components/nuki/translations/pl.json | 18 +++ .../components/nuki/translations/ru.json | 18 +++ .../components/nuki/translations/tr.json | 18 +++ .../components/nuki/translations/zh-Hant.json | 18 +++ .../components/number/translations/ca.json | 8 ++ .../components/number/translations/cs.json | 8 ++ .../components/number/translations/en.json | 8 ++ .../components/number/translations/et.json | 8 ++ .../components/number/translations/it.json | 8 ++ .../components/number/translations/no.json | 8 ++ .../components/number/translations/pl.json | 8 ++ .../components/number/translations/ru.json | 8 ++ .../components/number/translations/tr.json | 8 ++ .../number/translations/zh-Hant.json | 8 ++ .../components/nut/translations/de.json | 2 +- .../components/nut/translations/tr.json | 41 +++++++ .../components/nut/translations/uk.json | 46 ++++++++ .../components/nws/translations/de.json | 4 +- .../components/nws/translations/tr.json | 21 ++++ .../components/nws/translations/uk.json | 23 ++++ .../components/nzbget/translations/de.json | 8 +- .../components/nzbget/translations/tr.json | 29 +++++ .../components/nzbget/translations/uk.json | 35 ++++++ .../components/omnilogic/translations/de.json | 6 +- .../components/omnilogic/translations/tr.json | 20 ++++ .../components/omnilogic/translations/uk.json | 29 +++++ .../onboarding/translations/uk.json | 7 ++ .../ondilo_ico/translations/ca.json | 17 +++ .../ondilo_ico/translations/cs.json | 17 +++ .../ondilo_ico/translations/de.json | 16 +++ .../ondilo_ico/translations/en.json | 3 +- .../ondilo_ico/translations/es.json | 17 +++ .../ondilo_ico/translations/et.json | 17 +++ .../ondilo_ico/translations/it.json | 17 +++ .../ondilo_ico/translations/lb.json | 11 ++ .../ondilo_ico/translations/no.json | 17 +++ .../ondilo_ico/translations/pl.json | 17 +++ .../ondilo_ico/translations/ru.json | 17 +++ .../ondilo_ico/translations/tr.json | 10 ++ .../ondilo_ico/translations/uk.json | 17 +++ .../ondilo_ico/translations/zh-Hant.json | 17 +++ .../components/onewire/translations/de.json | 3 + .../components/onewire/translations/tr.json | 24 ++++ .../components/onewire/translations/uk.json | 26 +++++ .../components/onvif/translations/de.json | 7 +- .../components/onvif/translations/tr.json | 36 +++++- .../components/onvif/translations/uk.json | 59 ++++++++++ .../opentherm_gw/translations/de.json | 2 +- .../opentherm_gw/translations/tr.json | 16 +++ .../opentherm_gw/translations/uk.json | 30 +++++ .../components/openuv/translations/de.json | 2 +- .../components/openuv/translations/tr.json | 19 +++ .../components/openuv/translations/uk.json | 9 +- .../openweathermap/translations/de.json | 6 +- .../openweathermap/translations/tr.json | 30 +++++ .../openweathermap/translations/uk.json | 35 ++++++ .../ovo_energy/translations/de.json | 8 +- .../ovo_energy/translations/fr.json | 7 +- .../ovo_energy/translations/lb.json | 5 + .../ovo_energy/translations/tr.json | 11 ++ .../ovo_energy/translations/uk.json | 27 +++++ .../components/owntracks/translations/de.json | 3 + .../components/owntracks/translations/tr.json | 7 ++ .../components/owntracks/translations/uk.json | 6 + .../components/ozw/translations/ca.json | 2 +- .../components/ozw/translations/de.json | 12 +- .../components/ozw/translations/lb.json | 9 ++ .../components/ozw/translations/no.json | 18 +-- .../components/ozw/translations/tr.json | 19 ++- .../components/ozw/translations/uk.json | 41 +++++++ .../panasonic_viera/translations/de.json | 16 +-- .../panasonic_viera/translations/tr.json | 19 +++ .../panasonic_viera/translations/uk.json | 30 +++++ .../components/person/translations/uk.json | 2 +- .../components/pi_hole/translations/ca.json | 6 + .../components/pi_hole/translations/cs.json | 5 + .../components/pi_hole/translations/de.json | 6 +- .../components/pi_hole/translations/en.json | 6 + .../components/pi_hole/translations/es.json | 6 + .../components/pi_hole/translations/et.json | 6 + .../components/pi_hole/translations/it.json | 6 + .../components/pi_hole/translations/no.json | 6 + .../components/pi_hole/translations/pl.json | 6 + .../components/pi_hole/translations/ru.json | 6 + .../components/pi_hole/translations/tr.json | 26 +++++ .../components/pi_hole/translations/uk.json | 23 ++++ .../pi_hole/translations/zh-Hant.json | 6 + .../components/plaato/translations/ca.json | 41 ++++++- .../components/plaato/translations/de.json | 6 +- .../components/plaato/translations/et.json | 41 ++++++- .../components/plaato/translations/no.json | 32 +++++ .../components/plaato/translations/pl.json | 41 ++++++- .../components/plaato/translations/tr.json | 43 +++++++ .../components/plaato/translations/uk.json | 17 +++ .../plaato/translations/zh-Hant.json | 41 ++++++- .../components/plant/translations/uk.json | 4 +- .../components/plex/translations/de.json | 5 +- .../components/plex/translations/tr.json | 27 +++++ .../components/plex/translations/uk.json | 62 ++++++++++ .../components/plugwise/translations/de.json | 5 +- .../components/plugwise/translations/lb.json | 3 +- .../components/plugwise/translations/tr.json | 15 ++- .../components/plugwise/translations/uk.json | 42 +++++++ .../plum_lightpad/translations/de.json | 5 +- .../plum_lightpad/translations/tr.json | 18 +++ .../plum_lightpad/translations/uk.json | 18 +++ .../components/point/translations/de.json | 10 +- .../components/point/translations/fr.json | 3 +- .../components/point/translations/tr.json | 11 ++ .../components/point/translations/uk.json | 32 +++++ .../components/poolsense/translations/de.json | 5 +- .../components/poolsense/translations/tr.json | 18 +++ .../components/poolsense/translations/uk.json | 20 ++++ .../components/powerwall/translations/ca.json | 1 + .../components/powerwall/translations/cs.json | 1 + .../components/powerwall/translations/de.json | 5 +- .../components/powerwall/translations/en.json | 36 +++--- .../components/powerwall/translations/es.json | 1 + .../components/powerwall/translations/et.json | 1 + .../components/powerwall/translations/it.json | 1 + .../components/powerwall/translations/no.json | 1 + .../components/powerwall/translations/pl.json | 1 + .../components/powerwall/translations/ru.json | 1 + .../components/powerwall/translations/tr.json | 19 +++ .../components/powerwall/translations/uk.json | 20 ++++ .../powerwall/translations/zh-Hant.json | 1 + .../components/profiler/translations/de.json | 12 ++ .../components/profiler/translations/tr.json | 7 ++ .../components/profiler/translations/uk.json | 12 ++ .../progettihwsw/translations/de.json | 5 +- .../progettihwsw/translations/tr.json | 41 +++++++ .../progettihwsw/translations/uk.json | 41 +++++++ .../components/ps4/translations/de.json | 7 +- .../components/ps4/translations/tr.json | 22 ++++ .../components/ps4/translations/uk.json | 41 +++++++ .../pvpc_hourly_pricing/translations/de.json | 2 +- .../pvpc_hourly_pricing/translations/tr.json | 14 +++ .../pvpc_hourly_pricing/translations/uk.json | 17 +++ .../components/rachio/translations/de.json | 6 +- .../components/rachio/translations/tr.json | 19 +++ .../components/rachio/translations/uk.json | 30 +++++ .../rainmachine/translations/de.json | 5 +- .../rainmachine/translations/tr.json | 17 +++ .../rainmachine/translations/uk.json | 30 +++++ .../recollect_waste/translations/de.json | 22 ++++ .../recollect_waste/translations/es.json | 3 +- .../recollect_waste/translations/lb.json | 18 +++ .../recollect_waste/translations/pl.json | 10 ++ .../recollect_waste/translations/tr.json | 7 ++ .../recollect_waste/translations/uk.json | 28 +++++ .../components/remote/translations/tr.json | 10 ++ .../components/remote/translations/uk.json | 9 ++ .../components/rfxtrx/translations/ca.json | 3 +- .../components/rfxtrx/translations/de.json | 19 ++- .../components/rfxtrx/translations/en.json | 4 +- .../components/rfxtrx/translations/es.json | 3 +- .../components/rfxtrx/translations/et.json | 3 +- .../components/rfxtrx/translations/it.json | 3 +- .../components/rfxtrx/translations/no.json | 3 +- .../components/rfxtrx/translations/pl.json | 3 +- .../components/rfxtrx/translations/ru.json | 3 +- .../components/rfxtrx/translations/tr.json | 32 +++++ .../components/rfxtrx/translations/uk.json | 74 ++++++++++++ .../rfxtrx/translations/zh-Hant.json | 3 +- .../components/ring/translations/tr.json | 19 +++ .../components/ring/translations/uk.json | 26 +++++ .../components/risco/translations/de.json | 14 ++- .../components/risco/translations/lb.json | 4 +- .../components/risco/translations/tr.json | 27 +++++ .../components/risco/translations/uk.json | 55 +++++++++ .../components/roku/translations/ca.json | 4 + .../components/roku/translations/cs.json | 4 + .../components/roku/translations/de.json | 4 + .../components/roku/translations/en.json | 4 + .../components/roku/translations/es.json | 4 + .../components/roku/translations/et.json | 4 + .../components/roku/translations/it.json | 4 + .../components/roku/translations/lb.json | 4 + .../components/roku/translations/no.json | 4 + .../components/roku/translations/pl.json | 10 ++ .../components/roku/translations/ru.json | 4 + .../components/roku/translations/tr.json | 25 ++++ .../components/roku/translations/uk.json | 28 +++++ .../components/roku/translations/zh-Hant.json | 4 + .../components/roomba/translations/ca.json | 32 +++++ .../components/roomba/translations/cs.json | 20 ++++ .../components/roomba/translations/de.json | 33 +++++- .../components/roomba/translations/en.json | 103 +++++++++-------- .../components/roomba/translations/es.json | 32 +++++ .../components/roomba/translations/et.json | 32 +++++ .../components/roomba/translations/fr.json | 29 +++++ .../components/roomba/translations/it.json | 32 +++++ .../components/roomba/translations/lb.json | 26 +++++ .../components/roomba/translations/no.json | 32 +++++ .../components/roomba/translations/pl.json | 32 +++++ .../components/roomba/translations/pt.json | 7 ++ .../components/roomba/translations/ru.json | 32 +++++ .../components/roomba/translations/tr.json | 60 ++++++++++ .../components/roomba/translations/uk.json | 30 +++++ .../roomba/translations/zh-Hant.json | 32 +++++ .../components/roon/translations/ca.json | 2 +- .../components/roon/translations/cs.json | 3 +- .../components/roon/translations/de.json | 11 ++ .../components/roon/translations/en.json | 2 +- .../components/roon/translations/et.json | 2 +- .../components/roon/translations/it.json | 2 +- .../components/roon/translations/no.json | 2 +- .../components/roon/translations/pl.json | 2 +- .../components/roon/translations/ru.json | 2 +- .../components/roon/translations/tr.json | 23 ++++ .../components/roon/translations/uk.json | 24 ++++ .../components/roon/translations/zh-Hant.json | 2 +- .../components/rpi_power/translations/de.json | 13 +++ .../components/rpi_power/translations/lb.json | 5 + .../components/rpi_power/translations/tr.json | 13 +++ .../components/rpi_power/translations/uk.json | 14 +++ .../ruckus_unleashed/translations/de.json | 4 +- .../ruckus_unleashed/translations/tr.json | 21 ++++ .../ruckus_unleashed/translations/uk.json | 21 ++++ .../components/samsungtv/translations/de.json | 4 +- .../components/samsungtv/translations/tr.json | 6 +- .../components/samsungtv/translations/uk.json | 25 ++++ .../components/script/translations/uk.json | 2 +- .../season/translations/sensor.uk.json | 6 + .../components/sense/translations/de.json | 2 +- .../components/sense/translations/tr.json | 20 ++++ .../components/sense/translations/uk.json | 21 ++++ .../components/sensor/translations/tr.json | 27 +++++ .../components/sensor/translations/uk.json | 31 ++++- .../components/sentry/translations/de.json | 3 + .../components/sentry/translations/tr.json | 27 +++++ .../components/sentry/translations/uk.json | 36 ++++++ .../components/sharkiq/translations/de.json | 7 +- .../components/sharkiq/translations/fr.json | 2 +- .../components/sharkiq/translations/tr.json | 29 +++++ .../components/sharkiq/translations/uk.json | 29 +++++ .../components/shelly/translations/ca.json | 16 +++ .../components/shelly/translations/cs.json | 16 +++ .../components/shelly/translations/da.json | 17 +++ .../components/shelly/translations/de.json | 9 +- .../components/shelly/translations/en.json | 18 +-- .../components/shelly/translations/es.json | 16 +++ .../components/shelly/translations/et.json | 16 +++ .../components/shelly/translations/it.json | 16 +++ .../components/shelly/translations/lb.json | 8 ++ .../components/shelly/translations/no.json | 16 +++ .../components/shelly/translations/pl.json | 16 +++ .../components/shelly/translations/ru.json | 16 +++ .../components/shelly/translations/tr.json | 41 +++++++ .../components/shelly/translations/uk.json | 47 ++++++++ .../shelly/translations/zh-Hant.json | 16 +++ .../shopping_list/translations/de.json | 2 +- .../shopping_list/translations/tr.json | 14 +++ .../shopping_list/translations/uk.json | 14 +++ .../simplisafe/translations/de.json | 8 +- .../simplisafe/translations/tr.json | 20 ++++ .../simplisafe/translations/uk.json | 29 ++++- .../components/smappee/translations/de.json | 14 ++- .../components/smappee/translations/tr.json | 26 +++++ .../components/smappee/translations/uk.json | 35 ++++++ .../smart_meter_texas/translations/de.json | 6 +- .../smart_meter_texas/translations/tr.json | 20 ++++ .../smart_meter_texas/translations/uk.json | 20 ++++ .../components/smarthab/translations/de.json | 1 + .../components/smarthab/translations/tr.json | 17 +++ .../components/smarthab/translations/uk.json | 19 +++ .../smartthings/translations/tr.json | 17 +++ .../smartthings/translations/uk.json | 38 ++++++ .../components/smhi/translations/uk.json | 18 +++ .../components/sms/translations/de.json | 6 +- .../components/sms/translations/tr.json | 17 +++ .../components/sms/translations/uk.json | 20 ++++ .../components/solaredge/translations/fr.json | 4 +- .../components/solaredge/translations/lb.json | 5 +- .../components/solaredge/translations/tr.json | 13 +++ .../components/solaredge/translations/uk.json | 25 ++++ .../components/solarlog/translations/de.json | 2 +- .../components/solarlog/translations/tr.json | 18 +++ .../components/solarlog/translations/uk.json | 20 ++++ .../components/soma/translations/tr.json | 12 ++ .../components/soma/translations/uk.json | 24 ++++ .../components/somfy/translations/de.json | 6 +- .../components/somfy/translations/tr.json | 7 ++ .../components/somfy/translations/uk.json | 18 +++ .../somfy_mylink/translations/ca.json | 53 +++++++++ .../somfy_mylink/translations/cs.json | 26 +++++ .../somfy_mylink/translations/de.json | 39 +++++++ .../somfy_mylink/translations/en.json | 87 +++++++------- .../somfy_mylink/translations/es.json | 53 +++++++++ .../somfy_mylink/translations/et.json | 53 +++++++++ .../somfy_mylink/translations/fr.json | 43 +++++++ .../somfy_mylink/translations/it.json | 53 +++++++++ .../somfy_mylink/translations/lb.json | 27 +++++ .../somfy_mylink/translations/no.json | 53 +++++++++ .../somfy_mylink/translations/pl.json | 53 +++++++++ .../somfy_mylink/translations/ru.json | 53 +++++++++ .../somfy_mylink/translations/tr.json | 53 +++++++++ .../somfy_mylink/translations/uk.json | 40 +++++++ .../somfy_mylink/translations/zh-Hant.json | 53 +++++++++ .../components/sonarr/translations/de.json | 12 +- .../components/sonarr/translations/tr.json | 22 ++++ .../components/sonarr/translations/uk.json | 40 +++++++ .../components/songpal/translations/tr.json | 17 +++ .../components/songpal/translations/uk.json | 22 ++++ .../components/sonos/translations/de.json | 2 +- .../components/sonos/translations/tr.json | 12 ++ .../components/sonos/translations/uk.json | 13 +++ .../speedtestdotnet/translations/de.json | 9 +- .../speedtestdotnet/translations/tr.json | 24 ++++ .../speedtestdotnet/translations/uk.json | 24 ++++ .../components/spider/translations/de.json | 7 ++ .../components/spider/translations/tr.json | 19 +++ .../components/spider/translations/uk.json | 20 ++++ .../components/spotify/translations/de.json | 8 +- .../components/spotify/translations/lb.json | 5 + .../components/spotify/translations/uk.json | 27 +++++ .../squeezebox/translations/de.json | 8 +- .../squeezebox/translations/tr.json | 27 +++++ .../squeezebox/translations/uk.json | 31 +++++ .../srp_energy/translations/de.json | 13 ++- .../srp_energy/translations/es.json | 6 +- .../srp_energy/translations/fr.json | 14 +++ .../srp_energy/translations/lb.json | 20 ++++ .../srp_energy/translations/tr.json | 8 +- .../srp_energy/translations/uk.json | 24 ++++ .../components/starline/translations/tr.json | 33 ++++++ .../components/starline/translations/uk.json | 41 +++++++ .../components/sun/translations/pl.json | 2 +- .../components/switch/translations/uk.json | 9 ++ .../components/syncthru/translations/tr.json | 20 ++++ .../components/syncthru/translations/uk.json | 27 +++++ .../synology_dsm/translations/de.json | 11 +- .../synology_dsm/translations/tr.json | 18 ++- .../synology_dsm/translations/uk.json | 55 +++++++++ .../system_health/translations/uk.json | 2 +- .../components/tado/translations/de.json | 8 +- .../components/tado/translations/tr.json | 29 +++++ .../components/tado/translations/uk.json | 33 ++++++ .../components/tag/translations/uk.json | 3 + .../components/tasmota/translations/de.json | 16 +++ .../components/tasmota/translations/tr.json | 16 +++ .../components/tasmota/translations/uk.json | 22 ++++ .../tellduslive/translations/de.json | 5 +- .../tellduslive/translations/fr.json | 3 +- .../tellduslive/translations/lb.json | 3 +- .../tellduslive/translations/tr.json | 19 +++ .../tellduslive/translations/uk.json | 27 +++++ .../components/tesla/translations/de.json | 5 +- .../components/tesla/translations/fr.json | 2 +- .../components/tesla/translations/tr.json | 18 +++ .../components/tesla/translations/uk.json | 29 +++++ .../components/tibber/translations/de.json | 4 +- .../components/tibber/translations/tr.json | 18 +++ .../components/tibber/translations/uk.json | 21 ++++ .../components/tile/translations/de.json | 3 + .../components/tile/translations/tr.json | 28 +++++ .../components/tile/translations/uk.json | 29 +++++ .../components/timer/translations/uk.json | 6 +- .../components/toon/translations/de.json | 3 + .../components/toon/translations/fr.json | 3 +- .../components/toon/translations/lb.json | 3 +- .../components/toon/translations/tr.json | 20 ++++ .../components/toon/translations/uk.json | 25 ++++ .../totalconnect/translations/de.json | 5 +- .../totalconnect/translations/tr.json | 18 +++ .../totalconnect/translations/uk.json | 19 +++ .../components/tplink/translations/de.json | 2 +- .../components/tplink/translations/tr.json | 12 ++ .../components/tplink/translations/uk.json | 13 +++ .../components/traccar/translations/de.json | 6 +- .../components/traccar/translations/lb.json | 3 +- .../components/traccar/translations/tr.json | 4 + .../components/traccar/translations/uk.json | 17 +++ .../components/tradfri/translations/de.json | 4 +- .../components/tradfri/translations/tr.json | 18 +++ .../components/tradfri/translations/uk.json | 12 +- .../transmission/translations/de.json | 5 +- .../transmission/translations/tr.json | 21 ++++ .../transmission/translations/uk.json | 36 ++++++ .../components/tuya/translations/de.json | 8 +- .../components/tuya/translations/lb.json | 3 + .../components/tuya/translations/tr.json | 28 +++++ .../components/tuya/translations/uk.json | 63 ++++++++++ .../twentemilieu/translations/de.json | 5 +- .../twentemilieu/translations/tr.json | 10 ++ .../twentemilieu/translations/uk.json | 22 ++++ .../components/twilio/translations/de.json | 8 +- .../components/twilio/translations/lb.json | 3 +- .../components/twilio/translations/tr.json | 8 ++ .../components/twilio/translations/uk.json | 17 +++ .../components/twinkly/translations/de.json | 5 +- .../components/twinkly/translations/fr.json | 18 +++ .../components/twinkly/translations/lb.json | 7 ++ .../components/twinkly/translations/tr.json | 6 + .../components/twinkly/translations/uk.json | 19 +++ .../components/unifi/translations/ca.json | 5 +- .../components/unifi/translations/cs.json | 4 +- .../components/unifi/translations/da.json | 1 + .../components/unifi/translations/de.json | 8 +- .../components/unifi/translations/en.json | 1 + .../components/unifi/translations/es.json | 4 +- .../components/unifi/translations/et.json | 5 +- .../components/unifi/translations/it.json | 5 +- .../components/unifi/translations/no.json | 5 +- .../components/unifi/translations/pl.json | 5 +- .../components/unifi/translations/ru.json | 4 +- .../components/unifi/translations/tr.json | 13 +++ .../components/unifi/translations/uk.json | 66 +++++++++++ .../unifi/translations/zh-Hant.json | 5 +- .../components/upb/translations/de.json | 11 +- .../components/upb/translations/tr.json | 11 ++ .../components/upb/translations/uk.json | 23 ++++ .../components/upcloud/translations/de.json | 3 +- .../components/upcloud/translations/tr.json | 16 +++ .../components/upcloud/translations/uk.json | 25 ++++ .../components/upnp/translations/ro.json | 7 ++ .../components/upnp/translations/tr.json | 19 +++ .../components/upnp/translations/uk.json | 16 ++- .../components/vacuum/translations/de.json | 2 +- .../components/vacuum/translations/uk.json | 16 ++- .../components/velbus/translations/de.json | 6 +- .../components/velbus/translations/tr.json | 11 ++ .../components/velbus/translations/uk.json | 20 ++++ .../components/vera/translations/tr.json | 14 +++ .../components/vera/translations/uk.json | 30 +++++ .../components/vesync/translations/de.json | 6 + .../components/vesync/translations/tr.json | 19 +++ .../components/vesync/translations/uk.json | 19 +++ .../components/vilfo/translations/de.json | 6 +- .../components/vilfo/translations/tr.json | 20 ++++ .../components/vilfo/translations/uk.json | 22 ++++ .../components/vizio/translations/de.json | 13 ++- .../components/vizio/translations/tr.json | 19 +++ .../components/vizio/translations/uk.json | 54 +++++++++ .../components/volumio/translations/de.json | 14 ++- .../components/volumio/translations/tr.json | 20 ++++ .../components/volumio/translations/uk.json | 6 +- .../water_heater/translations/uk.json | 8 ++ .../components/wemo/translations/de.json | 2 +- .../components/wemo/translations/tr.json | 8 +- .../components/wemo/translations/uk.json | 13 +++ .../components/wiffi/translations/tr.json | 24 ++++ .../components/wiffi/translations/uk.json | 25 ++++ .../components/wilight/translations/de.json | 3 + .../components/wilight/translations/tr.json | 7 ++ .../components/wilight/translations/uk.json | 16 +++ .../components/withings/translations/de.json | 16 ++- .../components/withings/translations/fr.json | 2 +- .../components/withings/translations/tr.json | 18 +++ .../components/withings/translations/uk.json | 33 ++++++ .../components/wled/translations/de.json | 6 +- .../components/wled/translations/tr.json | 23 ++++ .../components/wled/translations/uk.json | 24 ++++ .../components/wolflink/translations/de.json | 8 ++ .../wolflink/translations/sensor.de.json | 1 + .../wolflink/translations/sensor.tr.json | 11 +- .../wolflink/translations/sensor.uk.json | 78 ++++++++++++- .../components/wolflink/translations/tr.json | 20 ++++ .../components/wolflink/translations/uk.json | 13 ++- .../components/xbox/translations/de.json | 5 + .../components/xbox/translations/lb.json | 4 + .../components/xbox/translations/tr.json | 7 ++ .../components/xbox/translations/uk.json | 17 +++ .../xiaomi_aqara/translations/de.json | 15 ++- .../xiaomi_aqara/translations/tr.json | 31 +++++ .../xiaomi_aqara/translations/uk.json | 43 +++++++ .../xiaomi_miio/translations/de.json | 15 +-- .../xiaomi_miio/translations/tr.json | 29 +++++ .../xiaomi_miio/translations/uk.json | 31 +++++ .../components/yeelight/translations/de.json | 10 +- .../components/yeelight/translations/tr.json | 34 ++++++ .../components/yeelight/translations/uk.json | 38 ++++++ .../components/zerproc/translations/tr.json | 8 +- .../components/zerproc/translations/uk.json | 13 +++ .../components/zha/translations/cs.json | 20 ++-- .../components/zha/translations/de.json | 4 +- .../components/zha/translations/pl.json | 32 ++--- .../components/zha/translations/tr.json | 26 +++++ .../components/zha/translations/uk.json | 91 +++++++++++++++ .../zodiac/translations/sensor.tr.json | 18 +++ .../zodiac/translations/sensor.uk.json | 18 +++ .../components/zone/translations/tr.json | 12 ++ .../zoneminder/translations/de.json | 9 ++ .../zoneminder/translations/tr.json | 22 ++++ .../zoneminder/translations/uk.json | 34 ++++++ .../components/zwave/translations/de.json | 3 +- .../components/zwave/translations/tr.json | 4 + .../components/zwave/translations/uk.json | 27 ++++- .../components/zwave_js/translations/ca.json | 56 +++++++++ .../components/zwave_js/translations/cs.json | 30 +++++ .../components/zwave_js/translations/de.json | 18 +++ .../components/zwave_js/translations/en.json | 5 + .../components/zwave_js/translations/es.json | 36 ++++++ .../components/zwave_js/translations/et.json | 56 +++++++++ .../components/zwave_js/translations/fr.json | 20 ++++ .../components/zwave_js/translations/it.json | 56 +++++++++ .../components/zwave_js/translations/lb.json | 20 ++++ .../components/zwave_js/translations/no.json | 56 +++++++++ .../components/zwave_js/translations/pl.json | 56 +++++++++ .../zwave_js/translations/pt-BR.json | 7 ++ .../components/zwave_js/translations/ru.json | 56 +++++++++ .../components/zwave_js/translations/tr.json | 56 +++++++++ .../components/zwave_js/translations/uk.json | 20 ++++ .../zwave_js/translations/zh-Hant.json | 56 +++++++++ 1150 files changed, 19482 insertions(+), 878 deletions(-) create mode 100644 homeassistant/components/abode/translations/tr.json create mode 100644 homeassistant/components/abode/translations/uk.json create mode 100644 homeassistant/components/accuweather/translations/sensor.uk.json create mode 100644 homeassistant/components/accuweather/translations/tr.json create mode 100644 homeassistant/components/acmeda/translations/tr.json create mode 100644 homeassistant/components/acmeda/translations/uk.json create mode 100644 homeassistant/components/adguard/translations/tr.json create mode 100644 homeassistant/components/adguard/translations/uk.json create mode 100644 homeassistant/components/advantage_air/translations/tr.json create mode 100644 homeassistant/components/advantage_air/translations/uk.json create mode 100644 homeassistant/components/agent_dvr/translations/tr.json create mode 100644 homeassistant/components/agent_dvr/translations/uk.json create mode 100644 homeassistant/components/airly/translations/uk.json create mode 100644 homeassistant/components/airnow/translations/ca.json create mode 100644 homeassistant/components/airnow/translations/cs.json create mode 100644 homeassistant/components/airnow/translations/de.json create mode 100644 homeassistant/components/airnow/translations/es.json create mode 100644 homeassistant/components/airnow/translations/et.json create mode 100644 homeassistant/components/airnow/translations/fr.json create mode 100644 homeassistant/components/airnow/translations/it.json create mode 100644 homeassistant/components/airnow/translations/lb.json create mode 100644 homeassistant/components/airnow/translations/no.json create mode 100644 homeassistant/components/airnow/translations/pl.json create mode 100644 homeassistant/components/airnow/translations/pt.json create mode 100644 homeassistant/components/airnow/translations/ru.json create mode 100644 homeassistant/components/airnow/translations/tr.json create mode 100644 homeassistant/components/airnow/translations/uk.json create mode 100644 homeassistant/components/airnow/translations/zh-Hant.json create mode 100644 homeassistant/components/airvisual/translations/ar.json create mode 100644 homeassistant/components/airvisual/translations/tr.json create mode 100644 homeassistant/components/airvisual/translations/uk.json create mode 100644 homeassistant/components/alarmdecoder/translations/tr.json create mode 100644 homeassistant/components/alarmdecoder/translations/uk.json create mode 100644 homeassistant/components/almond/translations/tr.json create mode 100644 homeassistant/components/almond/translations/uk.json create mode 100644 homeassistant/components/ambiclimate/translations/tr.json create mode 100644 homeassistant/components/ambiclimate/translations/uk.json create mode 100644 homeassistant/components/ambient_station/translations/tr.json create mode 100644 homeassistant/components/ambient_station/translations/uk.json create mode 100644 homeassistant/components/apple_tv/translations/uk.json create mode 100644 homeassistant/components/arcam_fmj/translations/ro.json create mode 100644 homeassistant/components/arcam_fmj/translations/tr.json create mode 100644 homeassistant/components/arcam_fmj/translations/uk.json create mode 100644 homeassistant/components/atag/translations/tr.json create mode 100644 homeassistant/components/atag/translations/uk.json create mode 100644 homeassistant/components/august/translations/tr.json create mode 100644 homeassistant/components/august/translations/uk.json create mode 100644 homeassistant/components/aurora/translations/fr.json create mode 100644 homeassistant/components/aurora/translations/tr.json create mode 100644 homeassistant/components/aurora/translations/uk.json create mode 100644 homeassistant/components/auth/translations/tr.json create mode 100644 homeassistant/components/awair/translations/tr.json create mode 100644 homeassistant/components/awair/translations/uk.json create mode 100644 homeassistant/components/axis/translations/tr.json create mode 100644 homeassistant/components/axis/translations/uk.json create mode 100644 homeassistant/components/azure_devops/translations/tr.json create mode 100644 homeassistant/components/blebox/translations/tr.json create mode 100644 homeassistant/components/blebox/translations/uk.json create mode 100644 homeassistant/components/blink/translations/tr.json create mode 100644 homeassistant/components/blink/translations/uk.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/ca.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/cs.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/de.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/es.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/et.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/fr.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/it.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/lb.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/no.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/pl.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/pt.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/ru.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/tr.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/uk.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/zh-Hant.json create mode 100644 homeassistant/components/bond/translations/tr.json create mode 100644 homeassistant/components/braviatv/translations/tr.json create mode 100644 homeassistant/components/braviatv/translations/uk.json create mode 100644 homeassistant/components/broadlink/translations/tr.json create mode 100644 homeassistant/components/broadlink/translations/uk.json create mode 100644 homeassistant/components/brother/translations/uk.json create mode 100644 homeassistant/components/bsblan/translations/uk.json create mode 100644 homeassistant/components/canary/translations/tr.json create mode 100644 homeassistant/components/canary/translations/uk.json create mode 100644 homeassistant/components/cast/translations/tr.json create mode 100644 homeassistant/components/cert_expiry/translations/tr.json create mode 100644 homeassistant/components/cert_expiry/translations/uk.json create mode 100644 homeassistant/components/cloud/translations/de.json create mode 100644 homeassistant/components/cloud/translations/fr.json create mode 100644 homeassistant/components/cloud/translations/uk.json create mode 100644 homeassistant/components/cloudflare/translations/uk.json create mode 100644 homeassistant/components/control4/translations/tr.json create mode 100644 homeassistant/components/coolmaster/translations/tr.json create mode 100644 homeassistant/components/coolmaster/translations/uk.json create mode 100644 homeassistant/components/coronavirus/translations/tr.json create mode 100644 homeassistant/components/coronavirus/translations/uk.json create mode 100644 homeassistant/components/daikin/translations/tr.json create mode 100644 homeassistant/components/daikin/translations/uk.json create mode 100644 homeassistant/components/deconz/translations/uk.json create mode 100644 homeassistant/components/demo/translations/tr.json create mode 100644 homeassistant/components/demo/translations/uk.json create mode 100644 homeassistant/components/denonavr/translations/tr.json create mode 100644 homeassistant/components/denonavr/translations/uk.json create mode 100644 homeassistant/components/devolo_home_control/translations/tr.json create mode 100644 homeassistant/components/devolo_home_control/translations/uk.json create mode 100644 homeassistant/components/dexcom/translations/uk.json create mode 100644 homeassistant/components/dialogflow/translations/tr.json create mode 100644 homeassistant/components/dialogflow/translations/uk.json create mode 100644 homeassistant/components/directv/translations/tr.json create mode 100644 homeassistant/components/directv/translations/uk.json create mode 100644 homeassistant/components/doorbird/translations/tr.json create mode 100644 homeassistant/components/doorbird/translations/uk.json create mode 100644 homeassistant/components/dsmr/translations/de.json create mode 100644 homeassistant/components/dsmr/translations/uk.json create mode 100644 homeassistant/components/dunehd/translations/tr.json create mode 100644 homeassistant/components/dunehd/translations/uk.json create mode 100644 homeassistant/components/eafm/translations/de.json create mode 100644 homeassistant/components/eafm/translations/tr.json create mode 100644 homeassistant/components/eafm/translations/uk.json create mode 100644 homeassistant/components/ecobee/translations/tr.json create mode 100644 homeassistant/components/ecobee/translations/uk.json create mode 100644 homeassistant/components/econet/translations/ca.json create mode 100644 homeassistant/components/econet/translations/es.json create mode 100644 homeassistant/components/econet/translations/et.json create mode 100644 homeassistant/components/econet/translations/it.json create mode 100644 homeassistant/components/econet/translations/no.json create mode 100644 homeassistant/components/econet/translations/pl.json create mode 100644 homeassistant/components/econet/translations/ru.json create mode 100644 homeassistant/components/econet/translations/tr.json create mode 100644 homeassistant/components/econet/translations/zh-Hant.json create mode 100644 homeassistant/components/elgato/translations/tr.json create mode 100644 homeassistant/components/elgato/translations/uk.json create mode 100644 homeassistant/components/elkm1/translations/tr.json create mode 100644 homeassistant/components/elkm1/translations/uk.json create mode 100644 homeassistant/components/emulated_roku/translations/tr.json create mode 100644 homeassistant/components/emulated_roku/translations/uk.json create mode 100644 homeassistant/components/enocean/translations/tr.json create mode 100644 homeassistant/components/enocean/translations/uk.json create mode 100644 homeassistant/components/epson/translations/uk.json create mode 100644 homeassistant/components/fireservicerota/translations/fr.json create mode 100644 homeassistant/components/fireservicerota/translations/uk.json create mode 100644 homeassistant/components/firmata/translations/tr.json create mode 100644 homeassistant/components/firmata/translations/uk.json create mode 100644 homeassistant/components/flick_electric/translations/tr.json create mode 100644 homeassistant/components/flick_electric/translations/uk.json create mode 100644 homeassistant/components/flo/translations/tr.json create mode 100644 homeassistant/components/flo/translations/uk.json create mode 100644 homeassistant/components/flume/translations/tr.json create mode 100644 homeassistant/components/flume/translations/uk.json create mode 100644 homeassistant/components/flunearyou/translations/tr.json create mode 100644 homeassistant/components/flunearyou/translations/uk.json create mode 100644 homeassistant/components/forked_daapd/translations/tr.json create mode 100644 homeassistant/components/forked_daapd/translations/uk.json create mode 100644 homeassistant/components/foscam/translations/af.json create mode 100644 homeassistant/components/foscam/translations/ca.json create mode 100644 homeassistant/components/foscam/translations/cs.json create mode 100644 homeassistant/components/foscam/translations/de.json create mode 100644 homeassistant/components/foscam/translations/es.json create mode 100644 homeassistant/components/foscam/translations/et.json create mode 100644 homeassistant/components/foscam/translations/fr.json create mode 100644 homeassistant/components/foscam/translations/it.json create mode 100644 homeassistant/components/foscam/translations/lb.json create mode 100644 homeassistant/components/foscam/translations/no.json create mode 100644 homeassistant/components/foscam/translations/pl.json create mode 100644 homeassistant/components/foscam/translations/pt.json create mode 100644 homeassistant/components/foscam/translations/ru.json create mode 100644 homeassistant/components/foscam/translations/tr.json create mode 100644 homeassistant/components/foscam/translations/zh-Hant.json create mode 100644 homeassistant/components/freebox/translations/tr.json create mode 100644 homeassistant/components/freebox/translations/uk.json create mode 100644 homeassistant/components/fritzbox/translations/tr.json create mode 100644 homeassistant/components/fritzbox/translations/uk.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/ca.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/cs.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/et.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/it.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/lb.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/no.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/pl.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/ru.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/tr.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json create mode 100644 homeassistant/components/garmin_connect/translations/tr.json create mode 100644 homeassistant/components/garmin_connect/translations/uk.json create mode 100644 homeassistant/components/gdacs/translations/tr.json create mode 100644 homeassistant/components/gdacs/translations/uk.json create mode 100644 homeassistant/components/geofency/translations/tr.json create mode 100644 homeassistant/components/geofency/translations/uk.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/tr.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/uk.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/tr.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/uk.json create mode 100644 homeassistant/components/gios/translations/tr.json create mode 100644 homeassistant/components/gios/translations/uk.json create mode 100644 homeassistant/components/glances/translations/tr.json create mode 100644 homeassistant/components/glances/translations/uk.json create mode 100644 homeassistant/components/goalzero/translations/tr.json create mode 100644 homeassistant/components/goalzero/translations/uk.json create mode 100644 homeassistant/components/gogogate2/translations/tr.json create mode 100644 homeassistant/components/gogogate2/translations/uk.json create mode 100644 homeassistant/components/gpslogger/translations/tr.json create mode 100644 homeassistant/components/gpslogger/translations/uk.json create mode 100644 homeassistant/components/gree/translations/de.json create mode 100644 homeassistant/components/gree/translations/tr.json create mode 100644 homeassistant/components/gree/translations/uk.json create mode 100644 homeassistant/components/griddy/translations/uk.json create mode 100644 homeassistant/components/guardian/translations/tr.json create mode 100644 homeassistant/components/guardian/translations/uk.json create mode 100644 homeassistant/components/hangouts/translations/tr.json create mode 100644 homeassistant/components/hangouts/translations/uk.json create mode 100644 homeassistant/components/harmony/translations/tr.json create mode 100644 homeassistant/components/harmony/translations/uk.json create mode 100644 homeassistant/components/heos/translations/tr.json create mode 100644 homeassistant/components/heos/translations/uk.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/tr.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/uk.json create mode 100644 homeassistant/components/hlk_sw16/translations/tr.json create mode 100644 homeassistant/components/hlk_sw16/translations/uk.json create mode 100644 homeassistant/components/home_connect/translations/uk.json create mode 100644 homeassistant/components/homeassistant/translations/fr.json create mode 100644 homeassistant/components/homeassistant/translations/uk.json create mode 100644 homeassistant/components/homekit/translations/ro.json create mode 100644 homeassistant/components/homekit/translations/tr.json create mode 100644 homeassistant/components/homekit_controller/translations/tr.json create mode 100644 homeassistant/components/homekit_controller/translations/uk.json create mode 100644 homeassistant/components/homematicip_cloud/translations/tr.json create mode 100644 homeassistant/components/homematicip_cloud/translations/uk.json create mode 100644 homeassistant/components/huawei_lte/translations/uk.json create mode 100644 homeassistant/components/hue/translations/tr.json create mode 100644 homeassistant/components/hue/translations/uk.json create mode 100644 homeassistant/components/huisbaasje/translations/ca.json create mode 100644 homeassistant/components/huisbaasje/translations/cs.json create mode 100644 homeassistant/components/huisbaasje/translations/en.json create mode 100644 homeassistant/components/huisbaasje/translations/et.json create mode 100644 homeassistant/components/huisbaasje/translations/it.json create mode 100644 homeassistant/components/huisbaasje/translations/no.json create mode 100644 homeassistant/components/huisbaasje/translations/pl.json create mode 100644 homeassistant/components/huisbaasje/translations/ru.json create mode 100644 homeassistant/components/huisbaasje/translations/tr.json create mode 100644 homeassistant/components/huisbaasje/translations/zh-Hant.json create mode 100644 homeassistant/components/humidifier/translations/tr.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/tr.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/uk.json create mode 100644 homeassistant/components/hvv_departures/translations/tr.json create mode 100644 homeassistant/components/hvv_departures/translations/uk.json create mode 100644 homeassistant/components/hyperion/translations/uk.json create mode 100644 homeassistant/components/iaqualink/translations/tr.json create mode 100644 homeassistant/components/iaqualink/translations/uk.json create mode 100644 homeassistant/components/icloud/translations/uk.json create mode 100644 homeassistant/components/ifttt/translations/tr.json create mode 100644 homeassistant/components/ifttt/translations/uk.json create mode 100644 homeassistant/components/insteon/translations/tr.json create mode 100644 homeassistant/components/insteon/translations/uk.json create mode 100644 homeassistant/components/ios/translations/tr.json create mode 100644 homeassistant/components/ios/translations/uk.json create mode 100644 homeassistant/components/ipp/translations/uk.json create mode 100644 homeassistant/components/iqvia/translations/tr.json create mode 100644 homeassistant/components/iqvia/translations/uk.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/tr.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/uk.json create mode 100644 homeassistant/components/isy994/translations/tr.json create mode 100644 homeassistant/components/isy994/translations/uk.json create mode 100644 homeassistant/components/izone/translations/tr.json create mode 100644 homeassistant/components/izone/translations/uk.json create mode 100644 homeassistant/components/juicenet/translations/tr.json create mode 100644 homeassistant/components/juicenet/translations/uk.json create mode 100644 homeassistant/components/kodi/translations/tr.json create mode 100644 homeassistant/components/kodi/translations/uk.json create mode 100644 homeassistant/components/konnected/translations/tr.json create mode 100644 homeassistant/components/konnected/translations/uk.json create mode 100644 homeassistant/components/kulersky/translations/lb.json create mode 100644 homeassistant/components/kulersky/translations/uk.json create mode 100644 homeassistant/components/life360/translations/uk.json create mode 100644 homeassistant/components/lifx/translations/tr.json create mode 100644 homeassistant/components/lifx/translations/uk.json create mode 100644 homeassistant/components/local_ip/translations/tr.json create mode 100644 homeassistant/components/local_ip/translations/uk.json create mode 100644 homeassistant/components/locative/translations/tr.json create mode 100644 homeassistant/components/locative/translations/uk.json create mode 100644 homeassistant/components/logi_circle/translations/tr.json create mode 100644 homeassistant/components/logi_circle/translations/uk.json create mode 100644 homeassistant/components/lovelace/translations/fr.json create mode 100644 homeassistant/components/lovelace/translations/uk.json create mode 100644 homeassistant/components/luftdaten/translations/tr.json create mode 100644 homeassistant/components/luftdaten/translations/uk.json create mode 100644 homeassistant/components/lutron_caseta/translations/tr.json create mode 100644 homeassistant/components/lutron_caseta/translations/uk.json create mode 100644 homeassistant/components/lyric/translations/ca.json create mode 100644 homeassistant/components/lyric/translations/cs.json create mode 100644 homeassistant/components/lyric/translations/et.json create mode 100644 homeassistant/components/lyric/translations/it.json create mode 100644 homeassistant/components/lyric/translations/no.json create mode 100644 homeassistant/components/lyric/translations/pl.json create mode 100644 homeassistant/components/lyric/translations/tr.json create mode 100644 homeassistant/components/lyric/translations/zh-Hant.json create mode 100644 homeassistant/components/mailgun/translations/tr.json create mode 100644 homeassistant/components/mailgun/translations/uk.json create mode 100644 homeassistant/components/melcloud/translations/tr.json create mode 100644 homeassistant/components/melcloud/translations/uk.json create mode 100644 homeassistant/components/met/translations/tr.json create mode 100644 homeassistant/components/met/translations/uk.json create mode 100644 homeassistant/components/meteo_france/translations/uk.json create mode 100644 homeassistant/components/metoffice/translations/tr.json create mode 100644 homeassistant/components/metoffice/translations/uk.json create mode 100644 homeassistant/components/mikrotik/translations/tr.json create mode 100644 homeassistant/components/mikrotik/translations/uk.json create mode 100644 homeassistant/components/mill/translations/tr.json create mode 100644 homeassistant/components/mill/translations/uk.json create mode 100644 homeassistant/components/minecraft_server/translations/uk.json create mode 100644 homeassistant/components/mobile_app/translations/tr.json create mode 100644 homeassistant/components/monoprice/translations/tr.json create mode 100644 homeassistant/components/monoprice/translations/uk.json create mode 100644 homeassistant/components/motion_blinds/translations/fr.json create mode 100644 homeassistant/components/motion_blinds/translations/uk.json create mode 100644 homeassistant/components/myq/translations/tr.json create mode 100644 homeassistant/components/myq/translations/uk.json create mode 100644 homeassistant/components/neato/translations/tr.json create mode 100644 homeassistant/components/neato/translations/uk.json create mode 100644 homeassistant/components/nest/translations/uk.json create mode 100644 homeassistant/components/netatmo/translations/tr.json create mode 100644 homeassistant/components/netatmo/translations/uk.json create mode 100644 homeassistant/components/nexia/translations/tr.json create mode 100644 homeassistant/components/nexia/translations/uk.json create mode 100644 homeassistant/components/nightscout/translations/uk.json create mode 100644 homeassistant/components/notion/translations/uk.json create mode 100644 homeassistant/components/nuheat/translations/tr.json create mode 100644 homeassistant/components/nuheat/translations/uk.json create mode 100644 homeassistant/components/nuki/translations/ca.json create mode 100644 homeassistant/components/nuki/translations/cs.json create mode 100644 homeassistant/components/nuki/translations/et.json create mode 100644 homeassistant/components/nuki/translations/it.json create mode 100644 homeassistant/components/nuki/translations/no.json create mode 100644 homeassistant/components/nuki/translations/pl.json create mode 100644 homeassistant/components/nuki/translations/ru.json create mode 100644 homeassistant/components/nuki/translations/tr.json create mode 100644 homeassistant/components/nuki/translations/zh-Hant.json create mode 100644 homeassistant/components/number/translations/ca.json create mode 100644 homeassistant/components/number/translations/cs.json create mode 100644 homeassistant/components/number/translations/en.json create mode 100644 homeassistant/components/number/translations/et.json create mode 100644 homeassistant/components/number/translations/it.json create mode 100644 homeassistant/components/number/translations/no.json create mode 100644 homeassistant/components/number/translations/pl.json create mode 100644 homeassistant/components/number/translations/ru.json create mode 100644 homeassistant/components/number/translations/tr.json create mode 100644 homeassistant/components/number/translations/zh-Hant.json create mode 100644 homeassistant/components/nut/translations/tr.json create mode 100644 homeassistant/components/nut/translations/uk.json create mode 100644 homeassistant/components/nws/translations/tr.json create mode 100644 homeassistant/components/nws/translations/uk.json create mode 100644 homeassistant/components/nzbget/translations/tr.json create mode 100644 homeassistant/components/nzbget/translations/uk.json create mode 100644 homeassistant/components/omnilogic/translations/tr.json create mode 100644 homeassistant/components/omnilogic/translations/uk.json create mode 100644 homeassistant/components/onboarding/translations/uk.json create mode 100644 homeassistant/components/ondilo_ico/translations/ca.json create mode 100644 homeassistant/components/ondilo_ico/translations/cs.json create mode 100644 homeassistant/components/ondilo_ico/translations/de.json create mode 100644 homeassistant/components/ondilo_ico/translations/es.json create mode 100644 homeassistant/components/ondilo_ico/translations/et.json create mode 100644 homeassistant/components/ondilo_ico/translations/it.json create mode 100644 homeassistant/components/ondilo_ico/translations/lb.json create mode 100644 homeassistant/components/ondilo_ico/translations/no.json create mode 100644 homeassistant/components/ondilo_ico/translations/pl.json create mode 100644 homeassistant/components/ondilo_ico/translations/ru.json create mode 100644 homeassistant/components/ondilo_ico/translations/tr.json create mode 100644 homeassistant/components/ondilo_ico/translations/uk.json create mode 100644 homeassistant/components/ondilo_ico/translations/zh-Hant.json create mode 100644 homeassistant/components/onewire/translations/tr.json create mode 100644 homeassistant/components/onewire/translations/uk.json create mode 100644 homeassistant/components/onvif/translations/uk.json create mode 100644 homeassistant/components/opentherm_gw/translations/tr.json create mode 100644 homeassistant/components/opentherm_gw/translations/uk.json create mode 100644 homeassistant/components/openuv/translations/tr.json create mode 100644 homeassistant/components/openweathermap/translations/tr.json create mode 100644 homeassistant/components/openweathermap/translations/uk.json create mode 100644 homeassistant/components/ovo_energy/translations/uk.json create mode 100644 homeassistant/components/owntracks/translations/tr.json create mode 100644 homeassistant/components/ozw/translations/uk.json create mode 100644 homeassistant/components/panasonic_viera/translations/tr.json create mode 100644 homeassistant/components/panasonic_viera/translations/uk.json create mode 100644 homeassistant/components/pi_hole/translations/tr.json create mode 100644 homeassistant/components/pi_hole/translations/uk.json create mode 100644 homeassistant/components/plaato/translations/tr.json create mode 100644 homeassistant/components/plaato/translations/uk.json create mode 100644 homeassistant/components/plex/translations/tr.json create mode 100644 homeassistant/components/plex/translations/uk.json create mode 100644 homeassistant/components/plugwise/translations/uk.json create mode 100644 homeassistant/components/plum_lightpad/translations/tr.json create mode 100644 homeassistant/components/plum_lightpad/translations/uk.json create mode 100644 homeassistant/components/point/translations/tr.json create mode 100644 homeassistant/components/point/translations/uk.json create mode 100644 homeassistant/components/poolsense/translations/tr.json create mode 100644 homeassistant/components/poolsense/translations/uk.json create mode 100644 homeassistant/components/powerwall/translations/tr.json create mode 100644 homeassistant/components/powerwall/translations/uk.json create mode 100644 homeassistant/components/profiler/translations/de.json create mode 100644 homeassistant/components/profiler/translations/tr.json create mode 100644 homeassistant/components/profiler/translations/uk.json create mode 100644 homeassistant/components/progettihwsw/translations/tr.json create mode 100644 homeassistant/components/progettihwsw/translations/uk.json create mode 100644 homeassistant/components/ps4/translations/tr.json create mode 100644 homeassistant/components/ps4/translations/uk.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/tr.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/uk.json create mode 100644 homeassistant/components/rachio/translations/tr.json create mode 100644 homeassistant/components/rachio/translations/uk.json create mode 100644 homeassistant/components/rainmachine/translations/uk.json create mode 100644 homeassistant/components/recollect_waste/translations/de.json create mode 100644 homeassistant/components/recollect_waste/translations/lb.json create mode 100644 homeassistant/components/recollect_waste/translations/tr.json create mode 100644 homeassistant/components/recollect_waste/translations/uk.json create mode 100644 homeassistant/components/rfxtrx/translations/tr.json create mode 100644 homeassistant/components/rfxtrx/translations/uk.json create mode 100644 homeassistant/components/ring/translations/tr.json create mode 100644 homeassistant/components/ring/translations/uk.json create mode 100644 homeassistant/components/risco/translations/tr.json create mode 100644 homeassistant/components/risco/translations/uk.json create mode 100644 homeassistant/components/roku/translations/tr.json create mode 100644 homeassistant/components/roku/translations/uk.json create mode 100644 homeassistant/components/roomba/translations/tr.json create mode 100644 homeassistant/components/roomba/translations/uk.json create mode 100644 homeassistant/components/roon/translations/tr.json create mode 100644 homeassistant/components/roon/translations/uk.json create mode 100644 homeassistant/components/rpi_power/translations/de.json create mode 100644 homeassistant/components/rpi_power/translations/tr.json create mode 100644 homeassistant/components/rpi_power/translations/uk.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/tr.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/uk.json create mode 100644 homeassistant/components/samsungtv/translations/uk.json create mode 100644 homeassistant/components/sense/translations/tr.json create mode 100644 homeassistant/components/sense/translations/uk.json create mode 100644 homeassistant/components/sentry/translations/tr.json create mode 100644 homeassistant/components/sentry/translations/uk.json create mode 100644 homeassistant/components/sharkiq/translations/tr.json create mode 100644 homeassistant/components/sharkiq/translations/uk.json create mode 100644 homeassistant/components/shelly/translations/da.json create mode 100644 homeassistant/components/shelly/translations/tr.json create mode 100644 homeassistant/components/shelly/translations/uk.json create mode 100644 homeassistant/components/shopping_list/translations/tr.json create mode 100644 homeassistant/components/shopping_list/translations/uk.json create mode 100644 homeassistant/components/smappee/translations/tr.json create mode 100644 homeassistant/components/smappee/translations/uk.json create mode 100644 homeassistant/components/smart_meter_texas/translations/tr.json create mode 100644 homeassistant/components/smart_meter_texas/translations/uk.json create mode 100644 homeassistant/components/smarthab/translations/tr.json create mode 100644 homeassistant/components/smarthab/translations/uk.json create mode 100644 homeassistant/components/smartthings/translations/tr.json create mode 100644 homeassistant/components/smartthings/translations/uk.json create mode 100644 homeassistant/components/smhi/translations/uk.json create mode 100644 homeassistant/components/sms/translations/tr.json create mode 100644 homeassistant/components/sms/translations/uk.json create mode 100644 homeassistant/components/solaredge/translations/uk.json create mode 100644 homeassistant/components/solarlog/translations/tr.json create mode 100644 homeassistant/components/solarlog/translations/uk.json create mode 100644 homeassistant/components/soma/translations/tr.json create mode 100644 homeassistant/components/soma/translations/uk.json create mode 100644 homeassistant/components/somfy/translations/tr.json create mode 100644 homeassistant/components/somfy/translations/uk.json create mode 100644 homeassistant/components/somfy_mylink/translations/ca.json create mode 100644 homeassistant/components/somfy_mylink/translations/cs.json create mode 100644 homeassistant/components/somfy_mylink/translations/de.json create mode 100644 homeassistant/components/somfy_mylink/translations/es.json create mode 100644 homeassistant/components/somfy_mylink/translations/et.json create mode 100644 homeassistant/components/somfy_mylink/translations/fr.json create mode 100644 homeassistant/components/somfy_mylink/translations/it.json create mode 100644 homeassistant/components/somfy_mylink/translations/lb.json create mode 100644 homeassistant/components/somfy_mylink/translations/no.json create mode 100644 homeassistant/components/somfy_mylink/translations/pl.json create mode 100644 homeassistant/components/somfy_mylink/translations/ru.json create mode 100644 homeassistant/components/somfy_mylink/translations/tr.json create mode 100644 homeassistant/components/somfy_mylink/translations/uk.json create mode 100644 homeassistant/components/somfy_mylink/translations/zh-Hant.json create mode 100644 homeassistant/components/sonarr/translations/tr.json create mode 100644 homeassistant/components/sonarr/translations/uk.json create mode 100644 homeassistant/components/songpal/translations/tr.json create mode 100644 homeassistant/components/songpal/translations/uk.json create mode 100644 homeassistant/components/sonos/translations/tr.json create mode 100644 homeassistant/components/sonos/translations/uk.json create mode 100644 homeassistant/components/speedtestdotnet/translations/tr.json create mode 100644 homeassistant/components/speedtestdotnet/translations/uk.json create mode 100644 homeassistant/components/spider/translations/tr.json create mode 100644 homeassistant/components/spider/translations/uk.json create mode 100644 homeassistant/components/spotify/translations/uk.json create mode 100644 homeassistant/components/squeezebox/translations/tr.json create mode 100644 homeassistant/components/squeezebox/translations/uk.json create mode 100644 homeassistant/components/srp_energy/translations/fr.json create mode 100644 homeassistant/components/srp_energy/translations/lb.json create mode 100644 homeassistant/components/srp_energy/translations/uk.json create mode 100644 homeassistant/components/starline/translations/tr.json create mode 100644 homeassistant/components/starline/translations/uk.json create mode 100644 homeassistant/components/syncthru/translations/tr.json create mode 100644 homeassistant/components/syncthru/translations/uk.json create mode 100644 homeassistant/components/synology_dsm/translations/uk.json create mode 100644 homeassistant/components/tado/translations/tr.json create mode 100644 homeassistant/components/tado/translations/uk.json create mode 100644 homeassistant/components/tag/translations/uk.json create mode 100644 homeassistant/components/tasmota/translations/de.json create mode 100644 homeassistant/components/tasmota/translations/tr.json create mode 100644 homeassistant/components/tasmota/translations/uk.json create mode 100644 homeassistant/components/tellduslive/translations/tr.json create mode 100644 homeassistant/components/tellduslive/translations/uk.json create mode 100644 homeassistant/components/tesla/translations/tr.json create mode 100644 homeassistant/components/tesla/translations/uk.json create mode 100644 homeassistant/components/tibber/translations/tr.json create mode 100644 homeassistant/components/tibber/translations/uk.json create mode 100644 homeassistant/components/tile/translations/tr.json create mode 100644 homeassistant/components/tile/translations/uk.json create mode 100644 homeassistant/components/toon/translations/tr.json create mode 100644 homeassistant/components/toon/translations/uk.json create mode 100644 homeassistant/components/totalconnect/translations/tr.json create mode 100644 homeassistant/components/totalconnect/translations/uk.json create mode 100644 homeassistant/components/tplink/translations/tr.json create mode 100644 homeassistant/components/tplink/translations/uk.json create mode 100644 homeassistant/components/traccar/translations/uk.json create mode 100644 homeassistant/components/tradfri/translations/tr.json create mode 100644 homeassistant/components/transmission/translations/tr.json create mode 100644 homeassistant/components/transmission/translations/uk.json create mode 100644 homeassistant/components/tuya/translations/uk.json create mode 100644 homeassistant/components/twentemilieu/translations/tr.json create mode 100644 homeassistant/components/twentemilieu/translations/uk.json create mode 100644 homeassistant/components/twilio/translations/tr.json create mode 100644 homeassistant/components/twilio/translations/uk.json create mode 100644 homeassistant/components/twinkly/translations/fr.json create mode 100644 homeassistant/components/twinkly/translations/lb.json create mode 100644 homeassistant/components/twinkly/translations/uk.json create mode 100644 homeassistant/components/unifi/translations/uk.json create mode 100644 homeassistant/components/upb/translations/tr.json create mode 100644 homeassistant/components/upb/translations/uk.json create mode 100644 homeassistant/components/upcloud/translations/tr.json create mode 100644 homeassistant/components/upcloud/translations/uk.json create mode 100644 homeassistant/components/upnp/translations/tr.json create mode 100644 homeassistant/components/velbus/translations/tr.json create mode 100644 homeassistant/components/velbus/translations/uk.json create mode 100644 homeassistant/components/vera/translations/tr.json create mode 100644 homeassistant/components/vera/translations/uk.json create mode 100644 homeassistant/components/vesync/translations/tr.json create mode 100644 homeassistant/components/vesync/translations/uk.json create mode 100644 homeassistant/components/vilfo/translations/tr.json create mode 100644 homeassistant/components/vilfo/translations/uk.json create mode 100644 homeassistant/components/vizio/translations/tr.json create mode 100644 homeassistant/components/vizio/translations/uk.json create mode 100644 homeassistant/components/volumio/translations/tr.json create mode 100644 homeassistant/components/water_heater/translations/uk.json create mode 100644 homeassistant/components/wemo/translations/uk.json create mode 100644 homeassistant/components/wiffi/translations/tr.json create mode 100644 homeassistant/components/wiffi/translations/uk.json create mode 100644 homeassistant/components/wilight/translations/tr.json create mode 100644 homeassistant/components/wilight/translations/uk.json create mode 100644 homeassistant/components/withings/translations/tr.json create mode 100644 homeassistant/components/withings/translations/uk.json create mode 100644 homeassistant/components/wled/translations/tr.json create mode 100644 homeassistant/components/wled/translations/uk.json create mode 100644 homeassistant/components/wolflink/translations/tr.json create mode 100644 homeassistant/components/xbox/translations/tr.json create mode 100644 homeassistant/components/xbox/translations/uk.json create mode 100644 homeassistant/components/xiaomi_aqara/translations/uk.json create mode 100644 homeassistant/components/xiaomi_miio/translations/tr.json create mode 100644 homeassistant/components/xiaomi_miio/translations/uk.json create mode 100644 homeassistant/components/yeelight/translations/tr.json create mode 100644 homeassistant/components/yeelight/translations/uk.json create mode 100644 homeassistant/components/zerproc/translations/uk.json create mode 100644 homeassistant/components/zha/translations/tr.json create mode 100644 homeassistant/components/zha/translations/uk.json create mode 100644 homeassistant/components/zodiac/translations/sensor.tr.json create mode 100644 homeassistant/components/zodiac/translations/sensor.uk.json create mode 100644 homeassistant/components/zone/translations/tr.json create mode 100644 homeassistant/components/zoneminder/translations/tr.json create mode 100644 homeassistant/components/zoneminder/translations/uk.json create mode 100644 homeassistant/components/zwave_js/translations/ca.json create mode 100644 homeassistant/components/zwave_js/translations/cs.json create mode 100644 homeassistant/components/zwave_js/translations/de.json create mode 100644 homeassistant/components/zwave_js/translations/es.json create mode 100644 homeassistant/components/zwave_js/translations/et.json create mode 100644 homeassistant/components/zwave_js/translations/fr.json create mode 100644 homeassistant/components/zwave_js/translations/it.json create mode 100644 homeassistant/components/zwave_js/translations/lb.json create mode 100644 homeassistant/components/zwave_js/translations/no.json create mode 100644 homeassistant/components/zwave_js/translations/pl.json create mode 100644 homeassistant/components/zwave_js/translations/pt-BR.json create mode 100644 homeassistant/components/zwave_js/translations/ru.json create mode 100644 homeassistant/components/zwave_js/translations/tr.json create mode 100644 homeassistant/components/zwave_js/translations/uk.json create mode 100644 homeassistant/components/zwave_js/translations/zh-Hant.json diff --git a/homeassistant/components/abode/translations/de.json b/homeassistant/components/abode/translations/de.json index 43d6ba21ca565e..307f5f45065d42 100644 --- a/homeassistant/components/abode/translations/de.json +++ b/homeassistant/components/abode/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "reauth_successful": "Die erneute Authentifizierung war erfolgreich", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von Abode erlaubt." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/abode/translations/es.json b/homeassistant/components/abode/translations/es.json index 9fa8cd8b06b7af..66cb5d13f22318 100644 --- a/homeassistant/components/abode/translations/es.json +++ b/homeassistant/components/abode/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "La reautenticaci\u00f3n fue exitosa", + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "error": { @@ -19,7 +19,7 @@ "reauth_confirm": { "data": { "password": "Contrase\u00f1a", - "username": "Correo electronico" + "username": "Correo electr\u00f3nico" }, "title": "Rellene su informaci\u00f3n de inicio de sesi\u00f3n de Abode" }, diff --git a/homeassistant/components/abode/translations/fr.json b/homeassistant/components/abode/translations/fr.json index 87be79571a4ac3..2ab158cca57f1c 100644 --- a/homeassistant/components/abode/translations/fr.json +++ b/homeassistant/components/abode/translations/fr.json @@ -1,17 +1,32 @@ { "config": { "abort": { - "single_instance_allowed": "Une seule configuration d'Abode est autoris\u00e9e." + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", + "single_instance_allowed": "D\u00e9ja configur\u00e9. Une seule configuration possible." }, "error": { "cannot_connect": "\u00c9chec de connexion", - "invalid_auth": "Authentification invalide" + "invalid_auth": "Authentification invalide", + "invalid_mfa_code": "Code MFA non valide" }, "step": { + "mfa": { + "data": { + "mfa_code": "Code MFA (6 chiffres)" + }, + "title": "Entrez votre code MFA pour Abode" + }, + "reauth_confirm": { + "data": { + "password": "Mot de passe", + "username": "Email" + }, + "title": "Remplissez vos informations de connexion Abode" + }, "user": { "data": { "password": "Mot de passe", - "username": "Adresse e-mail" + "username": "Email" }, "title": "Remplissez vos informations de connexion Abode" } diff --git a/homeassistant/components/abode/translations/tr.json b/homeassistant/components/abode/translations/tr.json new file mode 100644 index 00000000000000..d469e43f1f42ae --- /dev/null +++ b/homeassistant/components/abode/translations/tr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_mfa_code": "Ge\u00e7ersiz MFA kodu" + }, + "step": { + "mfa": { + "data": { + "mfa_code": "MFA kodu (6 basamakl\u0131)" + }, + "title": "Abode i\u00e7in MFA kodunuzu girin" + }, + "reauth_confirm": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "title": "Abode giri\u015f bilgilerinizi doldurun" + }, + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/uk.json b/homeassistant/components/abode/translations/uk.json new file mode 100644 index 00000000000000..7ad57a0ec68a69 --- /dev/null +++ b/homeassistant/components/abode/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_mfa_code": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u043a\u043e\u0434 MFA." + }, + "step": { + "mfa": { + "data": { + "mfa_code": "\u041a\u043e\u0434 MFA (6 \u0446\u0438\u0444\u0440)" + }, + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 MFA \u0434\u043b\u044f Abode" + }, + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "Abode" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "Abode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/ca.json b/homeassistant/components/accuweather/translations/ca.json index 9c33637baa85dc..8178a5caef0907 100644 --- a/homeassistant/components/accuweather/translations/ca.json +++ b/homeassistant/components/accuweather/translations/ca.json @@ -27,7 +27,7 @@ "data": { "forecast": "Previsi\u00f3 meteorol\u00f2gica" }, - "description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions es realitzaran cada 64 minuts en comptes de 32.", + "description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions de dades es faran cada 80 minuts en comptes de cada 40.", "title": "Opcions d'AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/cs.json b/homeassistant/components/accuweather/translations/cs.json index ea954b9f0dbc87..1cf34a42695440 100644 --- a/homeassistant/components/accuweather/translations/cs.json +++ b/homeassistant/components/accuweather/translations/cs.json @@ -27,7 +27,7 @@ "data": { "forecast": "P\u0159edpov\u011b\u010f po\u010das\u00ed" }, - "description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 64 minut nam\u00edsto 32 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.", + "description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 80 minut nam\u00edsto 40 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.", "title": "Mo\u017enosti AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json index fe0319764a7cf7..814e57d1d6cbb0 100644 --- a/homeassistant/components/accuweather/translations/de.json +++ b/homeassistant/components/accuweather/translations/de.json @@ -1,11 +1,16 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" }, "step": { "user": { "data": { + "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", "name": "Name" @@ -25,7 +30,7 @@ }, "system_health": { "info": { - "can_reach_server": "AccuWeather Server erreichen", + "can_reach_server": "AccuWeather-Server erreichen", "remaining_requests": "Verbleibende erlaubte Anfragen" } } diff --git a/homeassistant/components/accuweather/translations/en.json b/homeassistant/components/accuweather/translations/en.json index b737c420a2d9dc..8f2261b93c7de0 100644 --- a/homeassistant/components/accuweather/translations/en.json +++ b/homeassistant/components/accuweather/translations/en.json @@ -27,7 +27,7 @@ "data": { "forecast": "Weather forecast" }, - "description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 64 minutes instead of every 32 minutes.", + "description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes.", "title": "AccuWeather Options" } } diff --git a/homeassistant/components/accuweather/translations/et.json b/homeassistant/components/accuweather/translations/et.json index bed28b62975910..6e2dc1ffd96f1f 100644 --- a/homeassistant/components/accuweather/translations/et.json +++ b/homeassistant/components/accuweather/translations/et.json @@ -27,7 +27,7 @@ "data": { "forecast": "Ilmateade" }, - "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 32 minuti asemel iga 64 minuti j\u00e4rel.", + "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 80 minuti j\u00e4rel (muidu 40 minutit).", "title": "AccuWeatheri valikud" } } diff --git a/homeassistant/components/accuweather/translations/fr.json b/homeassistant/components/accuweather/translations/fr.json index 8e63820541701b..a083ed09bdf6eb 100644 --- a/homeassistant/components/accuweather/translations/fr.json +++ b/homeassistant/components/accuweather/translations/fr.json @@ -34,7 +34,8 @@ }, "system_health": { "info": { - "can_reach_server": "Acc\u00e8s au serveur AccuWeather" + "can_reach_server": "Acc\u00e8s au serveur AccuWeather", + "remaining_requests": "Demandes restantes autoris\u00e9es" } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/it.json b/homeassistant/components/accuweather/translations/it.json index 86aaa213a15fcd..8a1f9b964630bf 100644 --- a/homeassistant/components/accuweather/translations/it.json +++ b/homeassistant/components/accuweather/translations/it.json @@ -27,7 +27,7 @@ "data": { "forecast": "Previsioni meteo" }, - "description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 64 minuti invece che ogni 32 minuti.", + "description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 80 minuti invece che ogni 40.", "title": "Opzioni AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/no.json b/homeassistant/components/accuweather/translations/no.json index 50482cb3e61618..be87b1ab2447b9 100644 --- a/homeassistant/components/accuweather/translations/no.json +++ b/homeassistant/components/accuweather/translations/no.json @@ -27,7 +27,7 @@ "data": { "forecast": "V\u00e6rmelding" }, - "description": "P\u00e5 grunn av begrensningene i gratisversjonen av AccuWeather API-n\u00f8kkelen, n\u00e5r du aktiverer v\u00e6rmelding, vil dataoppdateringer bli utf\u00f8rt hvert 64. minutt i stedet for hvert 32. minutt.", + "description": "P\u00e5 grunn av begrensningene i den gratis versjonen av AccuWeather API-n\u00f8kkelen, vil dataoppdateringer utf\u00f8res hvert 80. minutt i stedet for hvert 40. minutt n\u00e5r du aktiverer v\u00e6rmelding.", "title": "AccuWeather-alternativer" } } diff --git a/homeassistant/components/accuweather/translations/pl.json b/homeassistant/components/accuweather/translations/pl.json index c6e4fb3ba8242d..2794bc8b7b6c69 100644 --- a/homeassistant/components/accuweather/translations/pl.json +++ b/homeassistant/components/accuweather/translations/pl.json @@ -27,7 +27,7 @@ "data": { "forecast": "Prognoza pogody" }, - "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 64 minuty zamiast co 32 minuty.", + "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 80 minut zamiast co 40 minut.", "title": "Opcje AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/ru.json b/homeassistant/components/accuweather/translations/ru.json index 6a675c1724854d..7bc767b1baf73a 100644 --- a/homeassistant/components/accuweather/translations/ru.json +++ b/homeassistant/components/accuweather/translations/ru.json @@ -27,7 +27,7 @@ "data": { "forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b" }, - "description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 64 \u043c\u0438\u043d\u0443\u0442\u044b, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 32 \u043c\u0438\u043d\u0443\u0442\u044b.", + "description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 80 \u043c\u0438\u043d\u0443\u0442, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 40 \u043c\u0438\u043d\u0443\u0442.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/sensor.uk.json b/homeassistant/components/accuweather/translations/sensor.uk.json new file mode 100644 index 00000000000000..81243e0b05da2b --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.uk.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "\u0417\u043d\u0438\u0436\u0435\u043d\u043d\u044f", + "rising": "\u0417\u0440\u043e\u0441\u0442\u0430\u043d\u043d\u044f", + "steady": "\u0421\u0442\u0430\u0431\u0456\u043b\u044c\u043d\u0438\u0439" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/tr.json b/homeassistant/components/accuweather/translations/tr.json new file mode 100644 index 00000000000000..f79f9a0e3270e8 --- /dev/null +++ b/homeassistant/components/accuweather/translations/tr.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam", + "name": "Ad" + }, + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "forecast": "Hava Durumu tahmini" + }, + "title": "AccuWeather Se\u00e7enekleri" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "AccuWeather sunucusuna ula\u015f\u0131n", + "remaining_requests": "Kalan izin verilen istekler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/uk.json b/homeassistant/components/accuweather/translations/uk.json index 8c3f282b35070d..7432d0df484355 100644 --- a/homeassistant/components/accuweather/translations/uk.json +++ b/homeassistant/components/accuweather/translations/uk.json @@ -1,15 +1,22 @@ { "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." + }, "error": { - "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "requests_exceeded": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0437\u0430\u043f\u0438\u0442\u0456\u0432 \u0434\u043e API Accuweather. \u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u043e\u0447\u0435\u043a\u0430\u0442\u0438 \u0430\u0431\u043e \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API." }, "step": { "user": { "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", - "name": "\u041d\u0430\u0437\u0432\u0430 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457" + "name": "\u041d\u0430\u0437\u0432\u0430" }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u044f\u043a\u0449\u043e \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u0430 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c:\n https://www.home-assistant.io/integrations/accuweather/ \n\n\u0417\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0434\u0435\u044f\u043a\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 \u043f\u0440\u0438\u0445\u043e\u0432\u0430\u043d\u0456 \u0456 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0438\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0432 \u0440\u0435\u0454\u0441\u0442\u0440\u0456 \u043e\u0431'\u0454\u043a\u0442\u0456\u0432 \u0456 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438 \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.", "title": "AccuWeather" } } @@ -19,8 +26,16 @@ "user": { "data": { "forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438" - } + }, + "description": "\u0423 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f\u043c\u0438 \u0431\u0435\u0437\u043a\u043e\u0448\u0442\u043e\u0432\u043d\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443 \u043f\u043e\u0433\u043e\u0434\u0438 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u0431\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u043a\u043e\u0436\u043d\u0456 64 \u0445\u0432\u0438\u043b\u0438\u043d\u0438, \u0430 \u043d\u0435 \u043a\u043e\u0436\u043d\u0456 32 \u0445\u0432\u0438\u043b\u0438\u043d\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AccuWeather" } } + }, + "system_health": { + "info": { + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 AccuWeather", + "remaining_requests": "\u0417\u0430\u043f\u0438\u0442\u0456\u0432 \u0437\u0430\u043b\u0438\u0448\u0438\u043b\u043e\u0441\u044c" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/zh-Hant.json b/homeassistant/components/accuweather/translations/zh-Hant.json index ed5fa26f0c002b..eb3729fd2c495a 100644 --- a/homeassistant/components/accuweather/translations/zh-Hant.json +++ b/homeassistant/components/accuweather/translations/zh-Hant.json @@ -27,7 +27,7 @@ "data": { "forecast": "\u5929\u6c23\u9810\u5831" }, - "description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 64 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 32 \u5206\u9418\u3002", + "description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002", "title": "AccuWeather \u9078\u9805" } } diff --git a/homeassistant/components/acmeda/translations/de.json b/homeassistant/components/acmeda/translations/de.json index 86b22e47cda95a..94834cde42768a 100644 --- a/homeassistant/components/acmeda/translations/de.json +++ b/homeassistant/components/acmeda/translations/de.json @@ -1,11 +1,14 @@ { "config": { + "abort": { + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, "step": { "user": { "data": { "id": "Host-ID" }, - "title": "W\u00e4hlen Sie einen Hub zum Hinzuf\u00fcgen aus" + "title": "W\u00e4hle einen Hub zum Hinzuf\u00fcgen aus" } } } diff --git a/homeassistant/components/acmeda/translations/tr.json b/homeassistant/components/acmeda/translations/tr.json new file mode 100644 index 00000000000000..aea81abdcba0ca --- /dev/null +++ b/homeassistant/components/acmeda/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "id": "Ana bilgisayar kimli\u011fi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/uk.json b/homeassistant/components/acmeda/translations/uk.json new file mode 100644 index 00000000000000..245428e9c732ba --- /dev/null +++ b/homeassistant/components/acmeda/translations/uk.json @@ -0,0 +1,15 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0445\u043e\u0441\u0442\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0445\u0430\u0431, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/de.json b/homeassistant/components/adguard/translations/de.json index a02601759be9ab..67746b3abcf0ca 100644 --- a/homeassistant/components/adguard/translations/de.json +++ b/homeassistant/components/adguard/translations/de.json @@ -2,10 +2,10 @@ "config": { "abort": { "existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "hassio_confirm": { @@ -19,7 +19,7 @@ "port": "Port", "ssl": "AdGuard Home verwendet ein SSL-Zertifikat", "username": "Benutzername", - "verify_ssl": "AdGuard Home verwendet ein richtiges Zertifikat" + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, "description": "Richte deine AdGuard Home-Instanz ein um sie zu \u00dcberwachen und zu Steuern." } diff --git a/homeassistant/components/adguard/translations/no.json b/homeassistant/components/adguard/translations/no.json index f5aeea990c30c0..25046c8d38f01b 100644 --- a/homeassistant/components/adguard/translations/no.json +++ b/homeassistant/components/adguard/translations/no.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Hjem gitt av hass.io tillegget {addon}?", - "title": "AdGuard Hjem via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Home gitt av Hass.io-tillegg {addon}?", + "title": "AdGuard Home via Hass.io-tillegg" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index 34c56342b5bcd9..5e8483047f830c 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/tr.json b/homeassistant/components/adguard/translations/tr.json new file mode 100644 index 00000000000000..26bef46408a1e0 --- /dev/null +++ b/homeassistant/components/adguard/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/uk.json b/homeassistant/components/adguard/translations/uk.json new file mode 100644 index 00000000000000..8c24fb0a877495 --- /dev/null +++ b/homeassistant/components/adguard/translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "existing_instance_updated": "\u041a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u043e\u043d\u043e\u0432\u043b\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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "hassio_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u0456 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044e AdGuard Home." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/de.json b/homeassistant/components/advantage_air/translations/de.json index 0d8a0052406daa..3b4066996ebf2f 100644 --- a/homeassistant/components/advantage_air/translations/de.json +++ b/homeassistant/components/advantage_air/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/advantage_air/translations/tr.json b/homeassistant/components/advantage_air/translations/tr.json new file mode 100644 index 00000000000000..db639c593764b0 --- /dev/null +++ b/homeassistant/components/advantage_air/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" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi", + "port": "Port" + }, + "title": "Ba\u011flan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/uk.json b/homeassistant/components/advantage_air/translations/uk.json new file mode 100644 index 00000000000000..14ac18395e25e3 --- /dev/null +++ b/homeassistant/components/advantage_air/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e API \u0412\u0430\u0448\u043e\u0433\u043e \u043d\u0430\u0441\u0442\u0456\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u043d\u0448\u0435\u0442\u0430 Advantage Air.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/de.json b/homeassistant/components/agent_dvr/translations/de.json index 6ea40d0fd007f0..10a8307ada1d16 100644 --- a/homeassistant/components/agent_dvr/translations/de.json +++ b/homeassistant/components/agent_dvr/translations/de.json @@ -4,8 +4,8 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "already_in_progress": "Der Konfigurationsfluss f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/agent_dvr/translations/tr.json b/homeassistant/components/agent_dvr/translations/tr.json new file mode 100644 index 00000000000000..31dddab779548b --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Agent DVR'\u0131 kurun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/uk.json b/homeassistant/components/agent_dvr/translations/uk.json new file mode 100644 index 00000000000000..fef8d45d5a4927 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "Agent DVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/de.json b/homeassistant/components/airly/translations/de.json index 743a68a010ef63..8004444fdb9003 100644 --- a/homeassistant/components/airly/translations/de.json +++ b/homeassistant/components/airly/translations/de.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Die Airly-Integration ist f\u00fcr diese Koordinaten bereits konfiguriert." + "already_configured": "Standort ist bereits konfiguriert" }, "error": { + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", "wrong_location": "Keine Airly Luftmessstation an diesem Ort" }, "step": { @@ -12,7 +13,7 @@ "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", - "name": "Name der Integration" + "name": "Name" }, "description": "Einrichtung der Airly-Luftqualit\u00e4t Integration. Um einen API-Schl\u00fcssel zu generieren, registriere dich auf https://developer.airly.eu/register", "title": "Airly" @@ -21,7 +22,7 @@ }, "system_health": { "info": { - "can_reach_server": "Airly Server erreichen" + "can_reach_server": "Airly-Server erreichen" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/tr.json b/homeassistant/components/airly/translations/tr.json index 1b6e9caa24c4f5..144acc1e1aedd6 100644 --- a/homeassistant/components/airly/translations/tr.json +++ b/homeassistant/components/airly/translations/tr.json @@ -1,4 +1,21 @@ { + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + }, "system_health": { "info": { "can_reach_server": "Airly sunucusuna eri\u015fin" diff --git a/homeassistant/components/airly/translations/uk.json b/homeassistant/components/airly/translations/uk.json new file mode 100644 index 00000000000000..51bcf5195dfd2d --- /dev/null +++ b/homeassistant/components/airly/translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "wrong_location": "\u0423 \u0446\u0456\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0456 \u043d\u0435\u043c\u0430\u0454 \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u0438\u0445 \u0441\u0442\u0430\u043d\u0446\u0456\u0439 Airly." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u0437 \u0430\u043d\u0430\u043b\u0456\u0437\u0443 \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f Airly. \u0429\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c https://developer.airly.eu/register.", + "title": "Airly" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Airly" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ca.json b/homeassistant/components/airnow/translations/ca.json new file mode 100644 index 00000000000000..2db3cfad563d9f --- /dev/null +++ b/homeassistant/components/airnow/translations/ca.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_location": "No s'ha trobat cap resultat per a aquesta ubicaci\u00f3", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud", + "radius": "Radi de l'estaci\u00f3 (milles; opcional)" + }, + "description": "Configura la integraci\u00f3 de qualitat d'aire AirNow. Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/cs.json b/homeassistant/components/airnow/translations/cs.json new file mode 100644 index 00000000000000..d978e44c70af64 --- /dev/null +++ b/homeassistant/components/airnow/translations/cs.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/de.json b/homeassistant/components/airnow/translations/de.json new file mode 100644 index 00000000000000..c98fc6d7415d24 --- /dev/null +++ b/homeassistant/components/airnow/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_location": "F\u00fcr diesen Standort wurden keine Ergebnisse gefunden", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/en.json b/homeassistant/components/airnow/translations/en.json index 5c5259c74e229d..371bb270ac170a 100644 --- a/homeassistant/components/airnow/translations/en.json +++ b/homeassistant/components/airnow/translations/en.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "invalid_location": "No results found for that location", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "Unexpected error" }, "step": { "user": { @@ -15,7 +15,6 @@ "api_key": "API Key", "latitude": "Latitude", "longitude": "Longitude", - "name": "Name of the Entity", "radius": "Station Radius (miles; optional)" }, "description": "Set up AirNow air quality integration. To generate API key go to https://docs.airnowapi.org/account/request/", diff --git a/homeassistant/components/airnow/translations/es.json b/homeassistant/components/airnow/translations/es.json new file mode 100644 index 00000000000000..d6a228a6e27784 --- /dev/null +++ b/homeassistant/components/airnow/translations/es.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "invalid_location": "No se han encontrado resultados para esa ubicaci\u00f3n", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API", + "latitude": "Latitud", + "longitude": "Longitud", + "radius": "Radio de la estaci\u00f3n (millas; opcional)" + }, + "description": "Configurar la integraci\u00f3n de calidad del aire de AirNow. Para generar una clave API, ve a https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/et.json b/homeassistant/components/airnow/translations/et.json new file mode 100644 index 00000000000000..52b2bb618e0246 --- /dev/null +++ b/homeassistant/components/airnow/translations/et.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "invalid_location": "Selle asukoha jaoks ei leitud andmeid", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "radius": "Jaama raadius (miilid; valikuline)" + }, + "description": "Seadista AirNow \u00f5hukvaliteedi sidumine. API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/", + "title": "" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/fr.json b/homeassistant/components/airnow/translations/fr.json new file mode 100644 index 00000000000000..ff85d9318e9408 --- /dev/null +++ b/homeassistant/components/airnow/translations/fr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec \u00e0 la connexion", + "invalid_auth": "Authentification invalide", + "invalid_location": "Aucun r\u00e9sultat trouv\u00e9 pour cet emplacement", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 API", + "latitude": "Latitude", + "longitude": "Longitude", + "radius": "Rayon d'action de la station (en miles, facultatif)" + }, + "description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air AirNow. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/it.json b/homeassistant/components/airnow/translations/it.json new file mode 100644 index 00000000000000..9dda15dfbd205b --- /dev/null +++ b/homeassistant/components/airnow/translations/it.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "invalid_location": "Nessun risultato trovato per quella localit\u00e0", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine", + "radius": "Raggio stazione (miglia; opzionale)" + }, + "description": "Configura l'integrazione per la qualit\u00e0 dell'aria AirNow. Per generare la chiave API, vai su https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/lb.json b/homeassistant/components/airnow/translations/lb.json new file mode 100644 index 00000000000000..a62bd0bf478e4a --- /dev/null +++ b/homeassistant/components/airnow/translations/lb.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "invalid_location": "Keng Resultater fonnt fir d\u00ebse Standuert", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "api_key": "API Schl\u00ebssel", + "latitude": "L\u00e4ngegrad", + "longitude": "Breedegrag" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/no.json b/homeassistant/components/airnow/translations/no.json new file mode 100644 index 00000000000000..19fa7e12207b27 --- /dev/null +++ b/homeassistant/components/airnow/translations/no.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "invalid_location": "Ingen resultater funnet for den plasseringen", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "radius": "Stasjonsradius (miles; valgfritt)" + }, + "description": "Konfigurer integrering av luftkvalitet i AirNow. For \u00e5 generere en API-n\u00f8kkel, g\u00e5r du til https://docs.airnowapi.org/account/request/", + "title": "" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pl.json b/homeassistant/components/airnow/translations/pl.json new file mode 100644 index 00000000000000..fe4310607b9422 --- /dev/null +++ b/homeassistant/components/airnow/translations/pl.json @@ -0,0 +1,26 @@ +{ + "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", + "invalid_location": "Brak wynik\u00f3w dla tej lokalizacji", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "radius": "Promie\u0144 od stacji (w milach; opcjonalnie)" + }, + "description": "Konfiguracja integracji jako\u015bci powietrza AirNow. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pt.json b/homeassistant/components/airnow/translations/pt.json new file mode 100644 index 00000000000000..3aa509dd6e814b --- /dev/null +++ b/homeassistant/components/airnow/translations/pt.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude" + }, + "title": "" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ru.json b/homeassistant/components/airnow/translations/ru.json new file mode 100644 index 00000000000000..650633cc8167c1 --- /dev/null +++ b/homeassistant/components/airnow/translations/ru.json @@ -0,0 +1,26 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_location": "\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 (\u0432 \u043c\u0438\u043b\u044f\u0445; \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" + }, + "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0443 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 AirNow. \u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://docs.airnowapi.org/account/request/.", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/tr.json b/homeassistant/components/airnow/translations/tr.json new file mode 100644 index 00000000000000..06af714dc87426 --- /dev/null +++ b/homeassistant/components/airnow/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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", + "invalid_location": "Bu konum i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam", + "radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/uk.json b/homeassistant/components/airnow/translations/uk.json new file mode 100644 index 00000000000000..bb872123f54945 --- /dev/null +++ b/homeassistant/components/airnow/translations/uk.json @@ -0,0 +1,26 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_location": "\u041d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432 \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 (\u043c\u0438\u043b\u0456; \u043d\u0435\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f AirNow. \u0429\u043e\u0431 \u0437\u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/zh-Hant.json b/homeassistant/components/airnow/translations/zh-Hant.json new file mode 100644 index 00000000000000..0f6008e75a600b --- /dev/null +++ b/homeassistant/components/airnow/translations/zh-Hant.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_location": "\u627e\u4e0d\u5230\u8a72\u4f4d\u7f6e\u7684\u7d50\u679c", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "radius": "\u89c0\u6e2c\u7ad9\u534a\u5f91\uff08\u82f1\u91cc\uff1b\u9078\u9805\uff09" + }, + "description": "\u6b32\u8a2d\u5b9a AirNow \u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u7522\u751f API \u5bc6\u9470", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/ar.json b/homeassistant/components/airvisual/translations/ar.json new file mode 100644 index 00000000000000..771d88e84340ac --- /dev/null +++ b/homeassistant/components/airvisual/translations/ar.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "geography_by_name": { + "data": { + "country": "\u0627\u0644\u062f\u0648\u0644\u0629" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index d7a0ec2bd99362..29df3dc7ca2726 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "general_error": "Error inesperat", - "invalid_api_key": "Clau API inv\u00e0lida" + "invalid_api_key": "Clau API inv\u00e0lida", + "location_not_found": "No s'ha trobat la ubicaci\u00f3" }, "step": { "geography": { @@ -17,7 +18,26 @@ "longitude": "Longitud" }, "description": "Utilitza l'API d'AirVisual per monitoritzar una ubicaci\u00f3 geogr\u00e0fica.", - "title": "Configuraci\u00f3 localitzaci\u00f3 geogr\u00e0fica" + "title": "Configura una ubicaci\u00f3 geogr\u00e0fica" + }, + "geography_by_coords": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud" + }, + "description": "Utilitza l'API d'AirVisual per monitoritzar una latitud/longitud.", + "title": "Configura una ubicaci\u00f3 geogr\u00e0fica" + }, + "geography_by_name": { + "data": { + "api_key": "Clau API", + "city": "Ciutat", + "country": "Pa\u00eds", + "state": "Estat" + }, + "description": "Utilitza l'API d'AirVisual per monitoritzar un/a ciutat/estat/pa\u00eds", + "title": "Configura una ubicaci\u00f3 geogr\u00e0fica" }, "node_pro": { "data": { diff --git a/homeassistant/components/airvisual/translations/de.json b/homeassistant/components/airvisual/translations/de.json index 63012e23da1fbf..a16b02915ee2f5 100644 --- a/homeassistant/components/airvisual/translations/de.json +++ b/homeassistant/components/airvisual/translations/de.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert." + "already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { - "cannot_connect": "Verbindungsfehler", - "general_error": "Es gab einen unbekannten Fehler.", - "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel bereitgestellt." + "cannot_connect": "Verbindung fehlgeschlagen", + "general_error": "Unerwarteter Fehler", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" }, "step": { "geography": { @@ -19,7 +20,7 @@ }, "node_pro": { "data": { - "ip_address": "IP-Adresse/Hostname des Ger\u00e4ts", + "ip_address": "Host", "password": "Passwort" }, "description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.", diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index 1e3cb59a5204fe..64eb11f902c8ab 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -11,6 +11,15 @@ "location_not_found": "Location not found" }, "step": { + "geography": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude" + }, + "description": "Use the AirVisual cloud API to monitor a geographical location.", + "title": "Configure a Geography" + }, "geography_by_coords": { "data": { "api_key": "API Key", @@ -45,6 +54,11 @@ "title": "Re-authenticate AirVisual" }, "user": { + "data": { + "cloud_api": "Geographical Location", + "node_pro": "AirVisual Node Pro", + "type": "Integration Type" + }, "description": "Pick what type of AirVisual data you want to monitor.", "title": "Configure AirVisual" } diff --git a/homeassistant/components/airvisual/translations/et.json b/homeassistant/components/airvisual/translations/et.json index 4bbf04817f961a..9912dbce035051 100644 --- a/homeassistant/components/airvisual/translations/et.json +++ b/homeassistant/components/airvisual/translations/et.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "\u00dchendamine nurjus", "general_error": "Tundmatu viga", - "invalid_api_key": "Vale API v\u00f5ti" + "invalid_api_key": "Vale API v\u00f5ti", + "location_not_found": "Asukohta ei leitud" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "Kasutage AirVisual pilve API-t geograafilise asukoha j\u00e4lgimiseks.", "title": "Seadista Geography" }, + "geography_by_coords": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + }, + "description": "Kasuta AirVisual pilve API-t pikkus/laiuskraadi j\u00e4lgimiseks.", + "title": "Seadista Geography sidumine" + }, + "geography_by_name": { + "data": { + "api_key": "API v\u00f5ti", + "city": "Linn", + "country": "Riik", + "state": "olek" + }, + "description": "Kasuta AirVisual pilve API-t linna/osariigi/riigi j\u00e4lgimiseks.", + "title": "Seadista Geography sidumine" + }, "node_pro": { "data": { "ip_address": "\u00dcksuse IP-aadress / hostinimi", diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index 90857d826eac1b..d1a0d3d511a834 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -6,8 +6,8 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", - "general_error": "Une erreur inconnue est survenue.", - "invalid_api_key": "La cl\u00e9 API fournie n'est pas valide." + "general_error": "Erreur inattendue", + "invalid_api_key": "Cl\u00e9 API invalide" }, "step": { "geography": { @@ -21,11 +21,11 @@ }, "node_pro": { "data": { - "ip_address": "Adresse IP / nom d'h\u00f4te de l'unit\u00e9", + "ip_address": "H\u00f4te", "password": "Mot de passe" }, - "description": "Surveillez une unit\u00e9 AirVisual personnelle. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.", - "title": "Configurer un AirVisual Node/Pro" + "description": "Surveillez une unit\u00e9 personnelle AirVisual. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.", + "title": "Configurer un noeud AirVisual Pro" }, "reauth_confirm": { "data": { diff --git a/homeassistant/components/airvisual/translations/no.json b/homeassistant/components/airvisual/translations/no.json index abf4a9f62e42ef..7c5b0333652387 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Tilkobling mislyktes", "general_error": "Uventet feil", - "invalid_api_key": "Ugyldig API-n\u00f8kkel" + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "location_not_found": "Stedet ble ikke funnet" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en geografisk plassering.", "title": "Konfigurer en Geography" }, + "geography_by_coords": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad" + }, + "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en breddegrad/lengdegrad.", + "title": "Konfigurer en Geography" + }, + "geography_by_name": { + "data": { + "api_key": "API-n\u00f8kkel", + "city": "By", + "country": "Land", + "state": "stat" + }, + "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en by/stat/land.", + "title": "Konfigurer en Geography" + }, "node_pro": { "data": { "ip_address": "Vert", diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index 10af1fc2ee08cf..5590a951641175 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "general_error": "Nieoczekiwany b\u0142\u0105d", - "invalid_api_key": "Nieprawid\u0142owy klucz API" + "invalid_api_key": "Nieprawid\u0142owy klucz API", + "location_not_found": "Nie znaleziono lokalizacji" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "U\u017cyj interfejsu API chmury AirVisual do monitorowania lokalizacji geograficznej.", "title": "Konfiguracja Geography" }, + "geography_by_coords": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna" + }, + "description": "U\u017cyj API chmury AirVisual do monitorowania szeroko\u015bci/d\u0142ugo\u015bci geograficznej.", + "title": "Konfiguracja Geography" + }, + "geography_by_name": { + "data": { + "api_key": "Klucz API", + "city": "Miasto", + "country": "Kraj", + "state": "Stan" + }, + "description": "U\u017cyj API chmury AirVisual do monitorowania miasta/stanu/kraju.", + "title": "Konfiguracja Geography" + }, "node_pro": { "data": { "ip_address": "Nazwa hosta lub adres IP", diff --git a/homeassistant/components/airvisual/translations/sv.json b/homeassistant/components/airvisual/translations/sv.json index 4c4e1271d72464..f375b4fc598519 100644 --- a/homeassistant/components/airvisual/translations/sv.json +++ b/homeassistant/components/airvisual/translations/sv.json @@ -1,7 +1,8 @@ { "config": { "error": { - "general_error": "Ett ok\u00e4nt fel intr\u00e4ffade." + "general_error": "Ett ok\u00e4nt fel intr\u00e4ffade.", + "invalid_api_key": "Ogiltig API-nyckel" }, "step": { "geography": { diff --git a/homeassistant/components/airvisual/translations/tr.json b/homeassistant/components/airvisual/translations/tr.json new file mode 100644 index 00000000000000..3d20c8ea9fc83e --- /dev/null +++ b/homeassistant/components/airvisual/translations/tr.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "general_error": "Beklenmeyen hata", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", + "location_not_found": "Konum bulunamad\u0131" + }, + "step": { + "geography": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + } + }, + "geography_by_coords": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + }, + "description": "Bir enlem / boylam\u0131 izlemek i\u00e7in AirVisual bulut API'sini kullan\u0131n.", + "title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma" + }, + "geography_by_name": { + "data": { + "api_key": "API Anahtar\u0131", + "city": "\u015eehir", + "country": "\u00dclke", + "state": "durum" + }, + "title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma" + }, + "node_pro": { + "data": { + "ip_address": "Ana Bilgisayar", + "password": "Parola" + }, + "description": "Ki\u015fisel bir AirVisual \u00fcnitesini izleyin. Parola, \u00fcnitenin kullan\u0131c\u0131 aray\u00fcz\u00fcnden al\u0131nabilir." + }, + "reauth_confirm": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "AirVisual'\u0131 yap\u0131land\u0131r\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/uk.json b/homeassistant/components/airvisual/translations/uk.json new file mode 100644 index 00000000000000..d99c58de7c07ae --- /dev/null +++ b/homeassistant/components/airvisual/translations/uk.json @@ -0,0 +1,57 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435. \u0410\u0431\u043e \u0446\u0435\u0439 Node / Pro ID \u0432\u0436\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0439.", + "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" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "general_error": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + }, + "step": { + "geography": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" + }, + "description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0445\u043c\u0430\u0440\u043d\u043e\u0433\u043e API AirVisual.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "node_pro": { + "data": { + "ip_address": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual Node / Pro" + }, + "reauth_confirm": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u044e" + }, + "user": { + "data": { + "cloud_api": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "node_pro": "AirVisual Node Pro", + "type": "\u0422\u0438\u043f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u0434\u0430\u043d\u0438\u0445 AirVisual, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438.", + "title": "AirVisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u043d\u0443 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0456" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/zh-Hant.json b/homeassistant/components/airvisual/translations/zh-Hant.json index 4bdc2959047da1..3767d41b519e12 100644 --- a/homeassistant/components/airvisual/translations/zh-Hant.json +++ b/homeassistant/components/airvisual/translations/zh-Hant.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "general_error": "\u672a\u9810\u671f\u932f\u8aa4", - "invalid_api_key": "API \u5bc6\u9470\u7121\u6548" + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", + "location_not_found": "\u627e\u4e0d\u5230\u5730\u9ede" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u5730\u7406\u5ea7\u6a19\u3002", "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" }, + "geography_by_coords": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6" + }, + "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u7d93\u5ea6/\u7def\u5ea6\u3002", + "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" + }, + "geography_by_name": { + "data": { + "api_key": "API \u5bc6\u9470", + "city": "\u57ce\u5e02", + "country": "\u570b\u5bb6", + "state": "\u5dde" + }, + "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u57ce\u5e02/\u5dde/\u570b\u5bb6\u3002", + "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" + }, "node_pro": { "data": { "ip_address": "\u4e3b\u6a5f\u7aef", diff --git a/homeassistant/components/alarm_control_panel/translations/tr.json b/homeassistant/components/alarm_control_panel/translations/tr.json index ebbcf568338883..cc509430436673 100644 --- a/homeassistant/components/alarm_control_panel/translations/tr.json +++ b/homeassistant/components/alarm_control_panel/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "disarmed": "{entity_name} b\u0131rak\u0131ld\u0131", + "triggered": "{entity_name} tetiklendi" + } + }, "state": { "_": { "armed": "Etkin", diff --git a/homeassistant/components/alarm_control_panel/translations/uk.json b/homeassistant/components/alarm_control_panel/translations/uk.json index e618e297019558..b50fd9f459d714 100644 --- a/homeassistant/components/alarm_control_panel/translations/uk.json +++ b/homeassistant/components/alarm_control_panel/translations/uk.json @@ -1,13 +1,36 @@ { + "device_automation": { + "action_type": { + "arm_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "arm_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "arm_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "disarm": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043e\u0445\u043e\u0440\u043e\u043d\u0443 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "trigger": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454" + }, + "condition_type": { + "is_armed_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_armed_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_armed_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_disarmed": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_triggered": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454" + }, + "trigger_type": { + "armed_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "armed_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "armed_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "disarmed": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "triggered": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454" + } + }, "state": { "_": { "armed": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430", - "armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u043d\u0435 \u0432\u0434\u043e\u043c\u0430)", + "armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u041d\u0435 \u0432\u0434\u043e\u043c\u0430)", "armed_custom_bypass": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 \u0437 \u0432\u0438\u043d\u044f\u0442\u043a\u0430\u043c\u0438", - "armed_home": "\u0411\u0443\u0434\u0438\u043d\u043a\u043e\u0432\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430", + "armed_home": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u0412\u0434\u043e\u043c\u0430)", "armed_night": "\u041d\u0456\u0447\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430", "arming": "\u0421\u0442\u0430\u0432\u043b\u044e \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443", - "disarmed": "\u0417\u043d\u044f\u0442\u043e", + "disarmed": "\u0417\u043d\u044f\u0442\u043e \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438", "disarming": "\u0417\u043d\u044f\u0442\u0442\u044f", "pending": "\u041e\u0447\u0456\u043a\u0443\u044e", "triggered": "\u0422\u0440\u0438\u0432\u043e\u0433\u0430" diff --git a/homeassistant/components/alarmdecoder/translations/de.json b/homeassistant/components/alarmdecoder/translations/de.json index 3f1b7ef816ed31..c37fb7b43906f3 100644 --- a/homeassistant/components/alarmdecoder/translations/de.json +++ b/homeassistant/components/alarmdecoder/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "protocol": { @@ -42,7 +45,7 @@ "data": { "zone_number": "Zonennummer" }, - "description": "Geben Sie die Zonennummer ein, die Sie hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chten." + "description": "Gib die die Zonennummer ein, die du hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chtest." } } } diff --git a/homeassistant/components/alarmdecoder/translations/tr.json b/homeassistant/components/alarmdecoder/translations/tr.json new file mode 100644 index 00000000000000..276b733b31fd5f --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/tr.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "protocol": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + }, + "options": { + "error": { + "relay_inclusive": "R\u00f6le Adresi ve R\u00f6le Kanal\u0131 birbirine ba\u011fl\u0131d\u0131r ve birlikte eklenmelidir." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternatif Gece Modu" + } + }, + "init": { + "data": { + "edit_select": "D\u00fczenle" + } + }, + "zone_details": { + "data": { + "zone_name": "B\u00f6lge Ad\u0131", + "zone_relayaddr": "R\u00f6le Adresi", + "zone_relaychan": "R\u00f6le Kanal\u0131" + } + }, + "zone_select": { + "data": { + "zone_number": "B\u00f6lge Numaras\u0131" + }, + "title": "AlarmDecoder'\u0131 yap\u0131land\u0131r\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/uk.json b/homeassistant/components/alarmdecoder/translations/uk.json new file mode 100644 index 00000000000000..c19d00c0ecad1f --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/uk.json @@ -0,0 +1,74 @@ +{ + "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." + }, + "create_entry": { + "default": "\u0423\u0441\u043f\u0456\u0448\u043d\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e AlarmDecoder." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "\u0428\u0432\u0438\u0434\u043a\u0456\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0434\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "device_path": "\u0428\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "user": { + "data": { + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "\u041f\u043e\u043b\u0435 \u043d\u0438\u0436\u0447\u0435 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0446\u0456\u043b\u0438\u043c \u0447\u0438\u0441\u043b\u043e\u043c.", + "loop_range": "RF Loop \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0446\u0456\u043b\u0438\u043c \u0447\u0438\u0441\u043b\u043e\u043c \u0432\u0456\u0434 1 \u0434\u043e 4.", + "loop_rfid": "RF Loop \u043d\u0435 \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0431\u0435\u0437 RF Serial.", + "relay_inclusive": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0440\u0435\u043b\u0435 \u0456 \u043a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435 \u0432\u0437\u0430\u0454\u043c\u043e\u0437\u0430\u043b\u0435\u0436\u043d\u0456 \u0456 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0456 \u0440\u0430\u0437\u043e\u043c." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u043d\u0456\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "auto_bypass": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0438\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u0438 \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0446\u0456 \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443", + "code_arm_required": "\u041a\u043e\u0434, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438" + }, + "description": "\u0429\u043e \u0431 \u0412\u0438 \u0445\u043e\u0442\u0456\u043b\u0438 \u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438?", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "\u041d\u0430\u0437\u0432\u0430 \u0437\u043e\u043d\u0438", + "zone_relayaddr": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0440\u0435\u043b\u0435", + "zone_relaychan": "\u041a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435", + "zone_rfid": "RF Serial", + "zone_type": "\u0422\u0438\u043f \u0437\u043e\u043d\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u0437\u043e\u043d\u0438 {zone_number}. \u0429\u043e\u0431 \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0437\u043e\u043d\u0443 {zone_number}, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u041d\u0430\u0437\u0432\u0430 \u0437\u043e\u043d\u0438\" \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "\u041d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u0438, \u044f\u043a\u0443 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438, \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0430\u0431\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/de.json b/homeassistant/components/almond/translations/de.json index e3a61026774b85..5eb8c4940aa237 100644 --- a/homeassistant/components/almond/translations/de.json +++ b/homeassistant/components/almond/translations/de.json @@ -1,8 +1,10 @@ { "config": { "abort": { - "cannot_connect": "Verbindung zum Almond-Server nicht m\u00f6glich.", - "missing_configuration": "Bitte \u00fcberpr\u00fcfe die Dokumentation zur Einrichtung von Almond." + "cannot_connect": "Verbindung fehlgeschlagen", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index 1b0f03b80182a5..9cd22ca5bc56f9 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io tillegget: {addon}?", - "title": "Almond via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io-tillegg: {addon}?", + "title": "Almond via Hass.io-tillegg" }, "pick_implementation": { "title": "Velg godkjenningsmetode" diff --git a/homeassistant/components/almond/translations/ru.json b/homeassistant/components/almond/translations/ru.json index 27870a46e95ec7..e671651f65de13 100644 --- a/homeassistant/components/almond/translations/ru.json +++ b/homeassistant/components/almond/translations/ru.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" }, "pick_implementation": { "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" diff --git a/homeassistant/components/almond/translations/tr.json b/homeassistant/components/almond/translations/tr.json new file mode 100644 index 00000000000000..dc270099fcd292 --- /dev/null +++ b/homeassistant/components/almond/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/uk.json b/homeassistant/components/almond/translations/uk.json new file mode 100644 index 00000000000000..7f8c12917bbac3 --- /dev/null +++ b/homeassistant/components/almond/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "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." + }, + "step": { + "hassio_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/de.json b/homeassistant/components/ambiclimate/translations/de.json index e5988f761037a8..d91fc15f37d9e6 100644 --- a/homeassistant/components/ambiclimate/translations/de.json +++ b/homeassistant/components/ambiclimate/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "access_token": "Unbekannter Fehler beim Generieren eines Zugriffstokens." + "access_token": "Unbekannter Fehler beim Generieren eines Zugriffstokens.", + "already_configured": "Konto wurde bereits konfiguriert", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." }, "create_entry": { "default": "Erfolgreiche Authentifizierung mit Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/fr.json b/homeassistant/components/ambiclimate/translations/fr.json index bdbfaea20efbe3..37ef95496860de 100644 --- a/homeassistant/components/ambiclimate/translations/fr.json +++ b/homeassistant/components/ambiclimate/translations/fr.json @@ -2,7 +2,7 @@ "config": { "abort": { "access_token": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'un jeton d'acc\u00e8s.", - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." }, "create_entry": { diff --git a/homeassistant/components/ambiclimate/translations/tr.json b/homeassistant/components/ambiclimate/translations/tr.json new file mode 100644 index 00000000000000..bcaeba84558752 --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/uk.json b/homeassistant/components/ambiclimate/translations/uk.json new file mode 100644 index 00000000000000..398665ab667e3a --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "access_token": "\u041f\u0440\u0438 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u0456 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0441\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430.", + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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." + }, + "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." + }, + "error": { + "follow_link": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u0456 \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\".", + "no_token": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430." + }, + "step": { + "auth": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043f\u043e [\u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c]({authorization_url}) \u0456 ** \u0414\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 ** \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0432\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Ambi Climate, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0456 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **.\n(\u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 URL \u0437\u0432\u043e\u0440\u043e\u0442\u043d\u043e\u0433\u043e \u0432\u0438\u043a\u043b\u0438\u043a\u0443 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 {cb_url} )", + "title": "Ambi Climate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/de.json b/homeassistant/components/ambient_station/translations/de.json index 53e6b1f69d6100..c6570fee0e352f 100644 --- a/homeassistant/components/ambient_station/translations/de.json +++ b/homeassistant/components/ambient_station/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieser App-Schl\u00fcssel wird bereits verwendet." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "invalid_key": "Ung\u00fcltiger API Key und / oder Anwendungsschl\u00fcssel", + "invalid_key": "Ung\u00fcltiger API-Schl\u00fcssel", "no_devices": "Keine Ger\u00e4te im Konto gefunden" }, "step": { diff --git a/homeassistant/components/ambient_station/translations/tr.json b/homeassistant/components/ambient_station/translations/tr.json new file mode 100644 index 00000000000000..908d97f5758bfb --- /dev/null +++ b/homeassistant/components/ambient_station/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/uk.json b/homeassistant/components/ambient_station/translations/uk.json new file mode 100644 index 00000000000000..722cf99af7e648 --- /dev/null +++ b/homeassistant/components/ambient_station/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "invalid_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "no_devices": "\u0412 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u0456 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "app_key": "\u041a\u043b\u044e\u0447 \u0434\u043e\u0434\u0430\u0442\u043a\u0443" + }, + "title": "Ambient PWS" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/fr.json b/homeassistant/components/apple_tv/translations/fr.json index a55d37ed588cc3..e1a719b31c90b3 100644 --- a/homeassistant/components/apple_tv/translations/fr.json +++ b/homeassistant/components/apple_tv/translations/fr.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "already_configured_device": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "backoff": "L'appareil n'accepte pas les demandes d'appariement pour le moment (vous avez peut-\u00eatre saisi un code PIN non valide trop de fois), r\u00e9essayez plus tard.", + "device_did_not_pair": "Aucune tentative pour terminer l'appairage n'a \u00e9t\u00e9 effectu\u00e9e \u00e0 partir de l'appareil.", + "invalid_config": "La configuration de cet appareil est incompl\u00e8te. Veuillez r\u00e9essayer de l'ajouter.", + "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", + "unknown": "Erreur inattendue" + }, "error": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "invalid_auth": "Autentification invalide", "no_devices_found": "Aucun appareil d\u00e9tect\u00e9 sur le r\u00e9seau", "no_usable_service": "Un dispositif a \u00e9t\u00e9 trouv\u00e9, mais aucun moyen d\u2019\u00e9tablir un lien avec lui. Si vous continuez \u00e0 voir ce message, essayez de sp\u00e9cifier son adresse IP ou de red\u00e9marrer votre Apple TV.", "unknown": "Erreur innatendue" @@ -19,9 +29,12 @@ "pair_with_pin": { "data": { "pin": "Code PIN" - } + }, + "description": "L'appairage est requis pour le protocole `{protocol}`. Veuillez saisir le code PIN affich\u00e9 \u00e0 l'\u00e9cran. Les z\u00e9ros doivent \u00eatre omis, c'est-\u00e0-dire entrer 123 si le code affich\u00e9 est 0123.", + "title": "Appairage" }, "reconfigure": { + "description": "Cette Apple TV rencontre des difficult\u00e9s de connexion et doit \u00eatre reconfigur\u00e9e.", "title": "Reconfiguration de l'appareil" }, "service_problem": { diff --git a/homeassistant/components/apple_tv/translations/lb.json b/homeassistant/components/apple_tv/translations/lb.json index 945f467c4cf84f..2354033b577050 100644 --- a/homeassistant/components/apple_tv/translations/lb.json +++ b/homeassistant/components/apple_tv/translations/lb.json @@ -3,9 +3,14 @@ "abort": { "already_configured_device": "Apparat ass scho konfigur\u00e9iert", "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", + "invalid_config": "Konfiguratioun fir d\u00ebsen Apparat ass net komplett. Prob\u00e9ier fir et nach emol dob\u00e4i ze setzen.", + "no_devices_found": "Keng Apparater am Netzwierk fonnt", "unknown": "Onerwaarte Feeler" }, "error": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "no_devices_found": "Keng Apparater am Netzwierk fonnt", "unknown": "Onerwaarte Feeler" }, "flow_title": "Apple TV: {name}", @@ -29,6 +34,9 @@ "description": "D\u00ebsen Apple TV huet e puer Verbindungsschwieregkeeten a muss nei konfigur\u00e9iert ginn.", "title": "Apparat Rekonfiguratioun" }, + "service_problem": { + "title": "Feeler beim dob\u00e4isetze vum Service" + }, "user": { "data": { "device_input": "Apparat" diff --git a/homeassistant/components/apple_tv/translations/tr.json b/homeassistant/components/apple_tv/translations/tr.json index 0ddc466a6f7662..f33e3998af6279 100644 --- a/homeassistant/components/apple_tv/translations/tr.json +++ b/homeassistant/components/apple_tv/translations/tr.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "invalid_config": "Bu ayg\u0131t\u0131n yap\u0131land\u0131rmas\u0131 tamamlanmad\u0131. L\u00fctfen tekrar eklemeyi deneyin.", "no_devices_found": "A\u011fda cihaz bulunamad\u0131", "unknown": "Beklenmeyen hata" diff --git a/homeassistant/components/apple_tv/translations/uk.json b/homeassistant/components/apple_tv/translations/uk.json new file mode 100644 index 00000000000000..a1ae2259ada604 --- /dev/null +++ b/homeassistant/components/apple_tv/translations/uk.json @@ -0,0 +1,64 @@ +{ + "config": { + "abort": { + "already_configured_device": "\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.", + "backoff": "\u0412 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0440\u0438\u0439\u043c\u0430\u0454 \u0437\u0430\u043f\u0438\u0442\u0438 \u043d\u0430 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 (\u043c\u043e\u0436\u043b\u0438\u0432\u043e, \u0412\u0438 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u0431\u0430\u0433\u0430\u0442\u043e \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u043b\u0438 \u043d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 PIN-\u043a\u043e\u0434), \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", + "device_did_not_pair": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043d\u0430\u043c\u0430\u0433\u0430\u0432\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", + "invalid_config": "\u041a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0439\u043e\u0433\u043e \u0449\u0435 \u0440\u0430\u0437.", + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "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.", + "no_usable_service": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0441\u043f\u043e\u0441\u0456\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u042f\u043a\u0449\u043e \u0412\u0438 \u0432\u0436\u0435 \u0431\u0430\u0447\u0438\u043b\u0438 \u0446\u0435 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c \u0439\u043e\u0433\u043e.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Apple TV: {name}", + "step": { + "confirm": { + "description": "\u0412\u0438 \u0437\u0431\u0438\u0440\u0430\u0454\u0442\u0435\u0441\u044f \u0434\u043e\u0434\u0430\u0442\u0438 Apple TV `{name}` \u0432 Home Assistant. \n\n ** \u0414\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0443 \u0412\u0430\u043c \u043c\u043e\u0436\u0435 \u0437\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0438\u0441\u044f \u0432\u0432\u0435\u0441\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 PIN-\u043a\u043e\u0434\u0456\u0432. ** \n\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u0412\u0438 *\u043d\u0435* \u0437\u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u043c\u0438\u043a\u0430\u0442\u0438 Apple TV \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457. \u0412 Home Assistant \u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438 \u0442\u0456\u043b\u044c\u043a\u0438 \u043c\u0435\u0434\u0456\u0430\u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0447!", + "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f Apple TV" + }, + "pair_no_pin": { + "description": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043b\u044f \u0441\u043b\u0443\u0436\u0431\u0438 `{protocol}`. \u0414\u043b\u044f \u043f\u0440\u043e\u0434\u043e\u0432\u0436\u0435\u043d\u043d\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434 {pin} \u043d\u0430 \u0412\u0430\u0448\u043e\u043c\u0443 Apple TV.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "pair_with_pin": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 `{protocol}`. \u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434, \u044f\u043a\u0438\u0439 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456. \u041f\u0435\u0440\u0448\u0456 \u043d\u0443\u043b\u0456 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u043e\u043f\u0443\u0449\u0435\u043d\u0456, \u0442\u043e\u0431\u0442\u043e \u0432\u0432\u0435\u0434\u0456\u0442\u044c 123, \u044f\u043a\u0449\u043e \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043a\u043e\u0434 0123.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "reconfigure": { + "description": "\u0423 \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Apple TV \u0432\u0438\u043d\u0438\u043a\u0430\u044e\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043f\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456, \u0439\u043e\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438.", + "title": "\u041f\u0435\u0440\u0435\u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "service_problem": { + "description": "\u0412\u0438\u043d\u0438\u043a\u043b\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043f\u0440\u0438 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u0456 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 `{protocol}`. \u0426\u0435 \u0431\u0443\u0434\u0435 \u043f\u0440\u043e\u0456\u0433\u043d\u043e\u0440\u043e\u0432\u0430\u043d\u043e.", + "title": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0434\u043e\u0434\u0430\u0442\u0438 \u0441\u043b\u0443\u0436\u0431\u0443" + }, + "user": { + "data": { + "device_input": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u0437 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u043d\u0430\u0437\u0432\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, \u041a\u0443\u0445\u043d\u044f \u0430\u0431\u043e \u0421\u043f\u0430\u043b\u044c\u043d\u044f) \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0438 Apple TV, \u044f\u043a\u0443 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438. \u042f\u043a\u0449\u043e \u0431\u0443\u0434\u044c-\u044f\u043a\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0456 \u0443 \u0412\u0430\u0448\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456, \u0432\u043e\u043d\u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0456 \u043d\u0438\u0436\u0447\u0435. \n\n \u042f\u043a\u0449\u043e \u0412\u0438 \u043d\u0435 \u0431\u0430\u0447\u0438\u0442\u0435 \u0441\u0432\u0456\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0430\u0431\u043e \u0432\u0438\u043d\u0438\u043a\u0430\u044e\u0442\u044c \u0431\u0443\u0434\u044c-\u044f\u043a\u0456 \u0456\u043d\u0448\u0456 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043f\u0456\u0434 \u0447\u0430\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \n\n {devices}", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043d\u043e\u0432\u043e\u0433\u043e Apple TV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "\u041d\u0435 \u0432\u043c\u0438\u043a\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 Home Assistant" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "title": "Apple TV" +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/zh-Hans.json b/homeassistant/components/apple_tv/translations/zh-Hans.json index bb1f8e025cad7a..54095a0a633679 100644 --- a/homeassistant/components/apple_tv/translations/zh-Hans.json +++ b/homeassistant/components/apple_tv/translations/zh-Hans.json @@ -1,6 +1,9 @@ { "config": { "step": { + "confirm": { + "description": "\u60a8\u5373\u5c06\u6dfb\u52a0 Apple TV (\u540d\u79f0\u4e3a\u201c{name}\u201d)\u5230 Home Assistant\u3002 \n\n **\u8981\u5b8c\u6210\u6b64\u8fc7\u7a0b\uff0c\u53ef\u80fd\u9700\u8981\u8f93\u5165\u591a\u4e2a PIN \u7801\u3002** \n\n\u8bf7\u6ce8\u610f\uff0c\u6b64\u96c6\u6210*\u4e0d\u80fd*\u5173\u95ed Apple TV \u7684\u7535\u6e90\uff0c\u53ea\u4f1a\u5173\u95ed Home Assistant \u4e2d\u7684\u5a92\u4f53\u64ad\u653e\u5668\uff01" + }, "pair_no_pin": { "title": "\u914d\u5bf9\u4e2d" }, @@ -8,6 +11,20 @@ "data": { "pin": "PIN\u7801" } + }, + "user": { + "description": "\u8981\u5f00\u59cb\uff0c\u8bf7\u8f93\u5165\u8981\u6dfb\u52a0\u7684 Apple TV \u7684\u8bbe\u5907\u540d\u79f0\u6216 IP \u5730\u5740\u3002\u5728\u7f51\u7edc\u4e0a\u81ea\u52a8\u53d1\u73b0\u7684\u8bbe\u5907\u4f1a\u663e\u793a\u5728\u4e0b\u65b9\u3002 \n\n\u5982\u679c\u6ca1\u6709\u53d1\u73b0\u8bbe\u5907\u6216\u9047\u5230\u4efb\u4f55\u95ee\u9898\uff0c\u8bf7\u5c1d\u8bd5\u6307\u5b9a\u8bbe\u5907 IP \u5730\u5740\u3002 \n\n {devices}", + "title": "\u8bbe\u7f6e\u65b0\u7684 Apple TV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "\u542f\u52a8 Home Assistant \u65f6\u4e0d\u6253\u5f00\u8bbe\u5907" + }, + "description": "\u914d\u7f6e\u8bbe\u5907\u901a\u7528\u8bbe\u7f6e" } } }, diff --git a/homeassistant/components/arcam_fmj/translations/de.json b/homeassistant/components/arcam_fmj/translations/de.json index 92ad0e22663046..b7270e730bb931 100644 --- a/homeassistant/components/arcam_fmj/translations/de.json +++ b/homeassistant/components/arcam_fmj/translations/de.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/arcam_fmj/translations/ro.json b/homeassistant/components/arcam_fmj/translations/ro.json new file mode 100644 index 00000000000000..a8008f1e8bcd5c --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/ro.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "few": "Pu\u021bine", + "one": "Unul", + "other": "Altele" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/tr.json b/homeassistant/components/arcam_fmj/translations/tr.json new file mode 100644 index 00000000000000..dd15f57212caa4 --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/uk.json b/homeassistant/components/arcam_fmj/translations/uk.json new file mode 100644 index 00000000000000..4d33a5bc0d9733 --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/uk.json @@ -0,0 +1,27 @@ +{ + "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_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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Arcam FMJ {host}", + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Arcam FMJ `{host}`?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\u0437\u0430\u043f\u0438\u0442\u0430\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/de.json b/homeassistant/components/atag/translations/de.json index 2ced7577fdf937..b94103d898bca6 100644 --- a/homeassistant/components/atag/translations/de.json +++ b/homeassistant/components/atag/translations/de.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "already_configured": "Dieses Ger\u00e4t wurde bereits zu HomeAssistant hinzugef\u00fcgt" + "already_configured": "Dieses Ger\u00e4t wurde bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { "data": { - "email": "Email (Optional)", + "email": "E-Mail", "host": "Host", "port": "Port" }, diff --git a/homeassistant/components/atag/translations/tr.json b/homeassistant/components/atag/translations/tr.json new file mode 100644 index 00000000000000..f7c94d0a976461 --- /dev/null +++ b/homeassistant/components/atag/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unauthorized": "E\u015fle\u015ftirme reddedildi, kimlik do\u011frulama iste\u011fi i\u00e7in cihaz\u0131 kontrol edin" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/uk.json b/homeassistant/components/atag/translations/uk.json new file mode 100644 index 00000000000000..ee0a077d900852 --- /dev/null +++ b/homeassistant/components/atag/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "unauthorized": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0437\u0430\u0431\u043e\u0440\u043e\u043d\u0435\u043d\u043e, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430 \u0437\u0430\u043f\u0438\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/de.json b/homeassistant/components/august/translations/de.json index d46be650e2c5f1..3a5bd70f1af12f 100644 --- a/homeassistant/components/august/translations/de.json +++ b/homeassistant/components/august/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Konto ist bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/august/translations/tr.json b/homeassistant/components/august/translations/tr.json new file mode 100644 index 00000000000000..ccb9e200c820cf --- /dev/null +++ b/homeassistant/components/august/translations/tr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap 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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "login_method": "Giri\u015f Y\u00f6ntemi", + "password": "Parola", + "timeout": "Zaman a\u015f\u0131m\u0131 (saniye)", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Giri\u015f Y\u00f6ntemi 'e-posta' ise, Kullan\u0131c\u0131 Ad\u0131 e-posta adresidir. Giri\u015f Y\u00f6ntemi 'telefon' ise, Kullan\u0131c\u0131 Ad\u0131 '+ NNNNNNNNN' bi\u00e7imindeki telefon numaras\u0131d\u0131r." + }, + "validation": { + "data": { + "code": "Do\u011frulama kodu" + }, + "description": "L\u00fctfen {login_method} ( {username} ) bilgilerinizi kontrol edin ve a\u015fa\u011f\u0131ya do\u011frulama kodunu girin", + "title": "\u0130ki fakt\u00f6rl\u00fc kimlik do\u011frulama" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/uk.json b/homeassistant/components/august/translations/uk.json new file mode 100644 index 00000000000000..e06c5347d73364 --- /dev/null +++ b/homeassistant/components/august/translations/uk.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "login_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u042f\u043a\u0449\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0456\u043d\u043e\u043c \u0454 \u0430\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438. \u042f\u043a\u0449\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0456\u043d\u043e\u043c \u0454 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 '+ NNNNNNNNN'.", + "title": "August" + }, + "validation": { + "data": { + "code": "\u041a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "description": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 {login_method} ({username}) \u0456 \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f.", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/de.json b/homeassistant/components/aurora/translations/de.json index 95312fe7943d09..838673e8d60000 100644 --- a/homeassistant/components/aurora/translations/de.json +++ b/homeassistant/components/aurora/translations/de.json @@ -1,7 +1,16 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "name": "Name" + } + } } }, "options": { @@ -12,5 +21,6 @@ } } } - } + }, + "title": "NOAA Aurora-Sensor" } \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/fr.json b/homeassistant/components/aurora/translations/fr.json new file mode 100644 index 00000000000000..473ecefdbd9693 --- /dev/null +++ b/homeassistant/components/aurora/translations/fr.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec \u00e0 la connexion" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "Seuil (%)" + } + } + } + }, + "title": "Capteur NOAA Aurora" +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/tr.json b/homeassistant/components/aurora/translations/tr.json new file mode 100644 index 00000000000000..0c3bb75ed6e913 --- /dev/null +++ b/homeassistant/components/aurora/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam", + "name": "Ad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/uk.json b/homeassistant/components/aurora/translations/uk.json new file mode 100644 index 00000000000000..0cb3c4fcbceb43 --- /dev/null +++ b/homeassistant/components/aurora/translations/uk.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "\u041f\u043e\u0440\u0456\u0433 (%)" + } + } + } + }, + "title": "NOAA Aurora Sensor" +} \ No newline at end of file diff --git a/homeassistant/components/auth/translations/de.json b/homeassistant/components/auth/translations/de.json index 06da3cde1a132b..93cbf1073ccbe5 100644 --- a/homeassistant/components/auth/translations/de.json +++ b/homeassistant/components/auth/translations/de.json @@ -25,7 +25,7 @@ }, "step": { "init": { - "description": "Um die Zwei-Faktor-Authentifizierung mit zeitbasierten Einmalpassw\u00f6rtern zu aktivieren, scanne den QR-Code mit Ihrer Authentifizierungs-App. Wenn du keine hast, empfehlen wir entweder [Google Authenticator] (https://support.google.com/accounts/answer/1066447) oder [Authy] (https://authy.com/). \n\n {qr_code} \n \nNachdem du den Code gescannt hast, gebe den sechsstelligen Code aus der App ein, um das Setup zu \u00fcberpr\u00fcfen. Wenn es Probleme beim Scannen des QR-Codes gibt, f\u00fchre ein manuelles Setup mit dem Code ** ` {code} ` ** durch.", + "description": "Um die Zwei-Faktor-Authentifizierung mit zeitbasierten Einmalpassw\u00f6rtern zu aktivieren, scanne den QR-Code mit deiner Authentifizierungs-App. Wenn du keine hast, empfehlen wir entweder [Google Authenticator] (https://support.google.com/accounts/answer/1066447) oder [Authy] (https://authy.com/). \n\n {qr_code} \n \nNachdem du den Code gescannt hast, gibst du den sechsstelligen Code aus der App ein, um das Setup zu \u00fcberpr\u00fcfen. Wenn es Probleme beim Scannen des QR-Codes gibt, f\u00fchre ein manuelles Setup mit dem Code ** ` {code} ` ** durch.", "title": "Richte die Zwei-Faktor-Authentifizierung mit TOTP ein" } }, diff --git a/homeassistant/components/auth/translations/tr.json b/homeassistant/components/auth/translations/tr.json new file mode 100644 index 00000000000000..7d273214574684 --- /dev/null +++ b/homeassistant/components/auth/translations/tr.json @@ -0,0 +1,22 @@ +{ + "mfa_setup": { + "notify": { + "step": { + "init": { + "title": "Bilgilendirme bile\u015feni taraf\u0131ndan verilen tek seferlik parolay\u0131 ayarlay\u0131n" + }, + "setup": { + "description": "**bildirim yoluyla tek seferlik bir parola g\u00f6nderildi. {notify_service}**. L\u00fctfen a\u015fa\u011f\u0131da girin:" + } + }, + "title": "Tek Seferlik Parolay\u0131 Bildir" + }, + "totp": { + "step": { + "init": { + "description": "Zamana dayal\u0131 tek seferlik parolalar\u0131 kullanarak iki fakt\u00f6rl\u00fc kimlik do\u011frulamay\u0131 etkinle\u015ftirmek i\u00e7in kimlik do\u011frulama uygulaman\u0131zla QR kodunu taray\u0131n. Hesab\u0131n\u0131z yoksa, [Google Authenticator] (https://support.google.com/accounts/answer/1066447) veya [Authy] (https://authy.com/) \u00f6neririz. \n\n {qr_code}\n\n Kodu tarad\u0131ktan sonra, kurulumu do\u011frulamak i\u00e7in uygulaman\u0131zdan alt\u0131 haneli kodu girin. QR kodunu taramayla ilgili sorun ya\u015f\u0131yorsan\u0131z, ** ` {code} ` manuel kurulum yap\u0131n." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/translations/uk.json b/homeassistant/components/auth/translations/uk.json index f826075078e7b5..eeb8f1ee7c7c2e 100644 --- a/homeassistant/components/auth/translations/uk.json +++ b/homeassistant/components/auth/translations/uk.json @@ -1,14 +1,35 @@ { "mfa_setup": { "notify": { + "abort": { + "no_available_service": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0441\u043b\u0443\u0436\u0431 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c." + }, "error": { "invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." }, "step": { + "init": { + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u043d\u0443 \u0456\u0437 \u0441\u043b\u0443\u0436\u0431 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c:", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u043e\u043b\u0456\u0432 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c" + }, "setup": { + "description": "\u041e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 ** notify.{notify_service} **. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u0439\u043e\u0433\u043e \u043d\u0438\u0436\u0447\u0435:", "title": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" } - } + }, + "title": "\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u043e\u043b\u0456\u0432" + }, + "totp": { + "error": { + "invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443. \u042f\u043a\u0449\u043e \u0412\u0438 \u043f\u043e\u0441\u0442\u0456\u0439\u043d\u043e \u043e\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u0435 \u0446\u044e \u043f\u043e\u043c\u0438\u043b\u043a\u0443, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a \u0443 \u0412\u0430\u0448\u0456\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0456 Home Assistant \u043f\u043e\u043a\u0430\u0437\u0443\u0454 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u0447\u0430\u0441." + }, + "step": { + "init": { + "description": "\u0429\u043e\u0431 \u0430\u043a\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u043e\u043b\u0456\u0432, \u0437\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0445 \u043d\u0430 \u0447\u0430\u0441\u0456, \u0432\u0456\u0434\u0441\u043a\u0430\u043d\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0456. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0457\u0457 \u043d\u0435\u043c\u0430\u0454, \u043c\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u043c\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0430\u0431\u043e [Google Authenticator](https://support.google.com/accounts/answer/1066447), \u0430\u0431\u043e [Authy](https://authy.com/). \n\n{qr_code}\n\n\u041f\u0456\u0441\u043b\u044f \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f QR-\u043a\u043e\u0434\u0443 \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u0438\u0439 \u043a\u043e\u0434 \u0437 \u0412\u0430\u0448\u043e\u0433\u043e \u0437\u0430\u0441\u0442\u043e\u0441\u0443\u0432\u0430\u043d\u043d\u044f, \u0449\u043e\u0431 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437\u0456 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c QR-\u043a\u043e\u0434\u0443, \u0432\u0438\u043a\u043e\u043d\u0430\u0439\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u043a\u043e\u0434\u0443 ** `{code}` **.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c TOTP" + } + }, + "title": "TOTP" } } } \ No newline at end of file diff --git a/homeassistant/components/awair/translations/de.json b/homeassistant/components/awair/translations/de.json index fcdcd0190e3ba2..5b65ece083b069 100644 --- a/homeassistant/components/awair/translations/de.json +++ b/homeassistant/components/awair/translations/de.json @@ -1,17 +1,25 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { - "unknown": "Unbekannter Awair-API-Fehler." + "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", + "unknown": "Unerwarteter Fehler" }, "step": { "reauth": { "data": { + "access_token": "Zugangstoken", "email": "E-Mail" }, - "description": "Bitte geben Sie Ihr Awair-Entwicklerzugriffstoken erneut ein." + "description": "Bitte gib dein Awair-Entwicklerzugriffstoken erneut ein." }, "user": { "data": { + "access_token": "Zugangstoken", "email": "E-Mail" } } diff --git a/homeassistant/components/awair/translations/tr.json b/homeassistant/components/awair/translations/tr.json new file mode 100644 index 00000000000000..84da92b97d39f3 --- /dev/null +++ b/homeassistant/components/awair/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "email": "E-posta" + } + }, + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "email": "E-posta" + }, + "description": "Awair geli\u015ftirici eri\u015fim belirteci i\u00e7in \u015fu adresten kaydolmal\u0131s\u0131n\u0131z: https://developer.getawair.com/onboard/login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/uk.json b/homeassistant/components/awair/translations/uk.json new file mode 100644 index 00000000000000..f8150ad7faf53f --- /dev/null +++ b/homeassistant/components/awair/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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.", + "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" + }, + "error": { + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "reauth": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0412\u0430\u0448 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443." + }, + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0434\u043e Awair \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: https://developer.getawair.com/onboard/login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/ca.json b/homeassistant/components/axis/translations/ca.json index 26da6057dc1c41..3e104c1005e101 100644 --- a/homeassistant/components/axis/translations/ca.json +++ b/homeassistant/components/axis/translations/ca.json @@ -11,7 +11,7 @@ "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" }, - "flow_title": "Dispositiu d'eix: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/cs.json b/homeassistant/components/axis/translations/cs.json index fd99c68ab351ab..4f7c30162354f9 100644 --- a/homeassistant/components/axis/translations/cs.json +++ b/homeassistant/components/axis/translations/cs.json @@ -11,7 +11,7 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, - "flow_title": "Za\u0159\u00edzen\u00ed Axis: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/de.json b/homeassistant/components/axis/translations/de.json index 4706350cdb3edb..1f6aedf5d9c7e4 100644 --- a/homeassistant/components/axis/translations/de.json +++ b/homeassistant/components/axis/translations/de.json @@ -7,8 +7,9 @@ }, "error": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "flow_title": "Achsenger\u00e4t: {name} ({host})", "step": { diff --git a/homeassistant/components/axis/translations/en.json b/homeassistant/components/axis/translations/en.json index 6b01533aefa856..f71e91f6280784 100644 --- a/homeassistant/components/axis/translations/en.json +++ b/homeassistant/components/axis/translations/en.json @@ -11,7 +11,7 @@ "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication" }, - "flow_title": "Axis device: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/et.json b/homeassistant/components/axis/translations/et.json index 6a27e74b28705a..f6f9a523cb6fba 100644 --- a/homeassistant/components/axis/translations/et.json +++ b/homeassistant/components/axis/translations/et.json @@ -11,7 +11,7 @@ "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamise viga" }, - "flow_title": "Axise seade: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/it.json b/homeassistant/components/axis/translations/it.json index 6461b2a6619dc8..7e7aeb1d1b2612 100644 --- a/homeassistant/components/axis/translations/it.json +++ b/homeassistant/components/axis/translations/it.json @@ -11,7 +11,7 @@ "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida" }, - "flow_title": "Dispositivo Axis: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/no.json b/homeassistant/components/axis/translations/no.json index 984d522eba93b8..1fc0640eb9b667 100644 --- a/homeassistant/components/axis/translations/no.json +++ b/homeassistant/components/axis/translations/no.json @@ -11,7 +11,7 @@ "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning" }, - "flow_title": "Axis enhet: {name} ({host})", + "flow_title": "{name} ( {host} )", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/pl.json b/homeassistant/components/axis/translations/pl.json index 84af845ab31dc3..e44816bc2ea93a 100644 --- a/homeassistant/components/axis/translations/pl.json +++ b/homeassistant/components/axis/translations/pl.json @@ -11,7 +11,7 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie" }, - "flow_title": "Urz\u0105dzenie Axis: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index ee1dc8494f3b45..6d979dc9de086d 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -11,7 +11,7 @@ "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." }, - "flow_title": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Axis {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/tr.json b/homeassistant/components/axis/translations/tr.json new file mode 100644 index 00000000000000..b2d609747d1429 --- /dev/null +++ b/homeassistant/components/axis/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/uk.json b/homeassistant/components/axis/translations/uk.json new file mode 100644 index 00000000000000..35b849ce9689ba --- /dev/null +++ b/homeassistant/components/axis/translations/uk.json @@ -0,0 +1,37 @@ +{ + "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.", + "link_local_address": "\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456 \u0430\u0434\u0440\u0435\u0441\u0438 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "not_axis_device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Axis." + }, + "error": { + "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.", + "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." + }, + "flow_title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Axis {name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Axis" + } + } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u043e\u0444\u0456\u043b\u044c \u043f\u043e\u0442\u043e\u043a\u0443 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0432\u0456\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Axis" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/zh-Hant.json b/homeassistant/components/axis/translations/zh-Hant.json index 1d7aaa7c74ed5a..293f08c5f05961 100644 --- a/homeassistant/components/axis/translations/zh-Hant.json +++ b/homeassistant/components/axis/translations/zh-Hant.json @@ -11,7 +11,7 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" }, - "flow_title": "Axis \u88dd\u7f6e\uff1a{name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/azure_devops/translations/de.json b/homeassistant/components/azure_devops/translations/de.json index 1c940ea7a359a5..e7d9e073ec617c 100644 --- a/homeassistant/components/azure_devops/translations/de.json +++ b/homeassistant/components/azure_devops/translations/de.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "reauth": { diff --git a/homeassistant/components/azure_devops/translations/fr.json b/homeassistant/components/azure_devops/translations/fr.json index edcf3dda517de0..5e62d54ec1d44f 100644 --- a/homeassistant/components/azure_devops/translations/fr.json +++ b/homeassistant/components/azure_devops/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s" }, "error": { diff --git a/homeassistant/components/azure_devops/translations/tr.json b/homeassistant/components/azure_devops/translations/tr.json new file mode 100644 index 00000000000000..11a15956f635b3 --- /dev/null +++ b/homeassistant/components/azure_devops/translations/tr.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap 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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "project_error": "Proje bilgileri al\u0131namad\u0131." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "title": "Yeniden kimlik do\u011frulama" + }, + "user": { + "data": { + "organization": "Organizasyon", + "personal_access_token": "Ki\u015fisel Eri\u015fim Belirteci (PAT)", + "project": "Proje" + }, + "description": "Projenize eri\u015fmek i\u00e7in bir Azure DevOps \u00f6rne\u011fi ayarlay\u0131n. Ki\u015fisel Eri\u015fim Jetonu yaln\u0131zca \u00f6zel bir proje i\u00e7in gereklidir.", + "title": "Azure DevOps Projesi Ekle" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/uk.json b/homeassistant/components/azure_devops/translations/uk.json index 4a42fd17fc3933..848528f444e1c0 100644 --- a/homeassistant/components/azure_devops/translations/uk.json +++ b/homeassistant/components/azure_devops/translations/uk.json @@ -1,16 +1,30 @@ { "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "error": { + "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.", + "project_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0440\u043e\u0435\u043a\u0442." + }, "flow_title": "Azure DevOps: {project_url}", "step": { "reauth": { + "data": { + "personal_access_token": "\u041f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 (PAT)" + }, + "description": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 {project_url} . \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" }, "user": { "data": { "organization": "\u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0446\u0456\u044f", "personal_access_token": "\u0422\u043e\u043a\u0435\u043d \u043e\u0441\u043e\u0431\u0438\u0441\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0443 (PAT)", - "project": "\u041f\u0440\u043e\u0454\u043a\u0442" + "project": "\u041f\u0440\u043e\u0435\u043a\u0442" }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c Azure DevOps. \u041f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0432\u043e\u0434\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0434\u043b\u044f \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0456\u0432.", "title": "\u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442 Azure DevOps" } } diff --git a/homeassistant/components/binary_sensor/translations/de.json b/homeassistant/components/binary_sensor/translations/de.json index 3687536eb5b663..a78befb7965e4a 100644 --- a/homeassistant/components/binary_sensor/translations/de.json +++ b/homeassistant/components/binary_sensor/translations/de.json @@ -98,6 +98,10 @@ "off": "Normal", "on": "Schwach" }, + "battery_charging": { + "off": "L\u00e4dt nicht", + "on": "L\u00e4dt" + }, "cold": { "off": "Normal", "on": "Kalt" @@ -122,6 +126,10 @@ "off": "Normal", "on": "Hei\u00df" }, + "light": { + "off": "Kein Licht", + "on": "Licht erkannt" + }, "lock": { "off": "Verriegelt", "on": "Entriegelt" @@ -134,6 +142,10 @@ "off": "Ruhig", "on": "Bewegung erkannt" }, + "moving": { + "off": "Bewegt sich nicht", + "on": "Bewegt sich" + }, "occupancy": { "off": "Frei", "on": "Belegt" @@ -142,6 +154,10 @@ "off": "Geschlossen", "on": "Offen" }, + "plug": { + "off": "Ausgesteckt", + "on": "Eingesteckt" + }, "presence": { "off": "Abwesend", "on": "Zu Hause" diff --git a/homeassistant/components/binary_sensor/translations/en.json b/homeassistant/components/binary_sensor/translations/en.json index a9a20e3fa50729..98c8a3a220a9ae 100644 --- a/homeassistant/components/binary_sensor/translations/en.json +++ b/homeassistant/components/binary_sensor/translations/en.json @@ -159,8 +159,8 @@ "on": "Plugged in" }, "presence": { - "off": "[%key:common::state::not_home%]", - "on": "[%key:common::state::home%]" + "off": "Away", + "on": "Home" }, "problem": { "off": "OK", diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index 7d6e8eab4ba96c..726765aea0255d 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -99,7 +99,7 @@ "on": "roz\u0142adowana" }, "battery_charging": { - "off": "nie \u0142aduje", + "off": "roz\u0142adowywanie", "on": "\u0142adowanie" }, "cold": { diff --git a/homeassistant/components/binary_sensor/translations/tr.json b/homeassistant/components/binary_sensor/translations/tr.json index 3c5cfaeeacf07c..94e1496cc30b7a 100644 --- a/homeassistant/components/binary_sensor/translations/tr.json +++ b/homeassistant/components/binary_sensor/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "moist": "{entity_name} nemli oldu", + "not_opened": "{entity_name} kapat\u0131ld\u0131" + } + }, "state": { "_": { "off": "Kapal\u0131", @@ -8,6 +14,10 @@ "off": "Normal", "on": "D\u00fc\u015f\u00fck" }, + "battery_charging": { + "off": "\u015earj olmuyor", + "on": "\u015earj Oluyor" + }, "cold": { "off": "Normal", "on": "So\u011fuk" @@ -32,6 +42,10 @@ "off": "Normal", "on": "S\u0131cak" }, + "light": { + "off": "I\u015f\u0131k yok", + "on": "I\u015f\u0131k alg\u0131land\u0131" + }, "lock": { "off": "Kilit kapal\u0131", "on": "Kilit a\u00e7\u0131k" @@ -44,6 +58,10 @@ "off": "Temiz", "on": "Alg\u0131land\u0131" }, + "moving": { + "off": "Hareket etmiyor", + "on": "Hareketli" + }, "occupancy": { "off": "Temiz", "on": "Alg\u0131land\u0131" @@ -52,6 +70,10 @@ "off": "Kapal\u0131", "on": "A\u00e7\u0131k" }, + "plug": { + "off": "Fi\u015fi \u00e7ekildi", + "on": "Tak\u0131l\u0131" + }, "presence": { "off": "[%key:common::state::evde_degil%]", "on": "[%key:common::state::evde%]" diff --git a/homeassistant/components/binary_sensor/translations/uk.json b/homeassistant/components/binary_sensor/translations/uk.json index 29767f6d6d62e5..0f8d92749c4cb1 100644 --- a/homeassistant/components/binary_sensor/translations/uk.json +++ b/homeassistant/components/binary_sensor/translations/uk.json @@ -2,15 +2,91 @@ "device_automation": { "condition_type": { "is_bat_low": "{entity_name} \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0440\u0456\u0432\u0435\u043d\u044c \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430", - "is_not_bat_low": "{entity_name} \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u043c \u0437\u0430\u0440\u044f\u0434 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430" + "is_cold": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "is_connected": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_gas": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0433\u0430\u0437", + "is_hot": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043d\u0430\u0433\u0440\u0456\u0432", + "is_light": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0441\u0432\u0456\u0442\u043b\u043e", + "is_locked": "{entity_name} \u0432 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_moist": "{entity_name} \u0432 \u0441\u0442\u0430\u043d\u0456 \"\u0412\u043e\u043b\u043e\u0433\u043e\"", + "is_motion": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0440\u0443\u0445", + "is_moving": "{entity_name} \u043f\u0435\u0440\u0435\u043c\u0456\u0449\u0443\u0454\u0442\u044c\u0441\u044f", + "is_no_gas": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0433\u0430\u0437", + "is_no_light": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0441\u0432\u0456\u0442\u043b\u043e", + "is_no_motion": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0440\u0443\u0445", + "is_no_problem": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c", + "is_no_smoke": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0434\u0438\u043c", + "is_no_sound": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0437\u0432\u0443\u043a", + "is_no_vibration": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e", + "is_not_bat_low": "{entity_name} \u0432 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_not_cold": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "is_not_connected": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_not_hot": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043d\u0430\u0433\u0440\u0456\u0432", + "is_not_locked": "{entity_name} \u0432 \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_not_moist": "{entity_name} \u0432 \u0441\u0442\u0430\u043d\u0456 \"\u0421\u0443\u0445\u043e\"", + "is_not_moving": "{entity_name} \u043d\u0435 \u0440\u0443\u0445\u0430\u0454\u0442\u044c\u0441\u044f", + "is_not_occupied": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_not_open": "{entity_name} \u0432 \u0437\u0430\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_not_plugged_in": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_not_powered": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "is_not_present": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_not_unsafe": "{entity_name} \u0432 \u0431\u0435\u0437\u043f\u0435\u0447\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_occupied": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_open": "{entity_name} \u0443 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_plugged_in": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_powered": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "is_present": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_problem": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443", + "is_smoke": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0434\u0438\u043c", + "is_sound": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0437\u0432\u0443\u043a", + "is_unsafe": "{entity_name} \u0432 \u043d\u0435\u0431\u0435\u0437\u043f\u0435\u0447\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_vibration": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e" }, "trigger_type": { - "bat_low": "{entity_name} \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0437\u0430\u0440\u044f\u0434 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430", - "not_bat_low": "{entity_name} \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440", + "bat_low": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0437\u0430\u0440\u044f\u0434", + "cold": "{entity_name} \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0443\u0454\u0442\u044c\u0441\u044f", + "connected": "{entity_name} \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u0454\u0442\u044c\u0441\u044f", + "gas": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0433\u0430\u0437", + "hot": "{entity_name} \u043d\u0430\u0433\u0440\u0456\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "light": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u043e", + "locked": "{entity_name} \u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f", + "moist": "{entity_name} \u0441\u0442\u0430\u0454 \u0432\u043e\u043b\u043e\u0433\u0438\u043c", + "motion": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0440\u0443\u0445", + "moving": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0440\u0443\u0445\u0430\u0442\u0438\u0441\u044f", + "no_gas": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0433\u0430\u0437", + "no_light": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u043e", + "no_motion": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0440\u0443\u0445", + "no_problem": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443", + "no_smoke": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0434\u0438\u043c", + "no_sound": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0437\u0432\u0443\u043a", + "no_vibration": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e", + "not_bat_low": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u0437\u0430\u0440\u044f\u0434", + "not_cold": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0443\u0432\u0430\u0442\u0438\u0441\u044f", + "not_connected": "{entity_name} \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u0454\u0442\u044c\u0441\u044f", + "not_hot": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u043d\u0430\u0433\u0440\u0456\u0432\u0430\u0442\u0438\u0441\u044f", + "not_locked": "{entity_name} \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f", + "not_moist": "{entity_name} \u0441\u0442\u0430\u0454 \u0441\u0443\u0445\u0438\u043c", + "not_moving": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u043f\u0435\u0440\u0435\u043c\u0456\u0449\u0435\u043d\u043d\u044f", + "not_occupied": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", "not_opened": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0442\u043e", + "not_plugged_in": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "not_powered": "{entity_name} \u043d\u0435 \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "not_present": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0432\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "not_unsafe": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0431\u0435\u0437\u043f\u0435\u043a\u0443", + "occupied": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e", + "plugged_in": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "powered": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "present": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "problem": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443", + "smoke": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0434\u0438\u043c", + "sound": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0437\u0432\u0443\u043a", "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", - "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", + "unsafe": "{entity_name} \u043d\u0435 \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0431\u0435\u0437\u043f\u0435\u043a\u0443", + "vibration": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e" } }, "state": { @@ -22,6 +98,10 @@ "off": "\u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439", "on": "\u041d\u0438\u0437\u044c\u043a\u0438\u0439" }, + "battery_charging": { + "off": "\u041d\u0435 \u0437\u0430\u0440\u044f\u0434\u0436\u0430\u0454\u0442\u044c\u0441\u044f", + "on": "\u0417\u0430\u0440\u044f\u0434\u0436\u0430\u043d\u043d\u044f" + }, "cold": { "off": "\u041d\u043e\u0440\u043c\u0430", "on": "\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f" @@ -46,6 +126,10 @@ "off": "\u041d\u043e\u0440\u043c\u0430", "on": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f" }, + "light": { + "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + }, "lock": { "off": "\u0417\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e", "on": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e" @@ -58,13 +142,21 @@ "off": "\u041d\u0435\u043c\u0430\u0454 \u0440\u0443\u0445\u0443", "on": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0440\u0443\u0445" }, + "moving": { + "off": "\u0420\u0443\u0445\u0443 \u043d\u0435\u043c\u0430\u0454", + "on": "\u0420\u0443\u0445\u0430\u0454\u0442\u044c\u0441\u044f" + }, "occupancy": { "off": "\u0427\u0438\u0441\u0442\u043e", "on": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c" }, "opening": { - "off": "\u0417\u0430\u043a\u0440\u0438\u0442\u043e", - "on": "\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438\u0439" + "off": "\u0417\u0430\u0447\u0438\u043d\u0435\u043d\u043e", + "on": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u043e" + }, + "plug": { + "off": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "on": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, "presence": { "off": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430", @@ -91,8 +183,8 @@ "on": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u0430 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044f" }, "window": { - "off": "\u0417\u0430\u0447\u0438\u043d\u0435\u043d\u0435", - "on": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u0435" + "off": "\u0417\u0430\u0447\u0438\u043d\u0435\u043d\u043e", + "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" diff --git a/homeassistant/components/blebox/translations/de.json b/homeassistant/components/blebox/translations/de.json index baf14ba4897c0f..37c8dde54e5e4d 100644 --- a/homeassistant/components/blebox/translations/de.json +++ b/homeassistant/components/blebox/translations/de.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "Ein BleBox-Ger\u00e4t ist bereits unter {address} konfiguriert.", - "already_configured": "Dieses BleBox-Ger\u00e4t ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung mit dem BleBox-Ger\u00e4t nicht m\u00f6glich. (\u00dcberpr\u00fcfen Sie die Protokolle auf Fehler).", - "unknown": "Unbekannter Fehler beim Anschlie\u00dfen an das BleBox-Ger\u00e4t. (Pr\u00fcfen Sie die Protokolle auf Fehler).", + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler", "unsupported_version": "Das BleBox-Ger\u00e4t hat eine veraltete Firmware. Bitte aktualisieren Sie es zuerst." }, "flow_title": "BleBox-Ger\u00e4t: {name} ( {host} )", diff --git a/homeassistant/components/blebox/translations/tr.json b/homeassistant/components/blebox/translations/tr.json new file mode 100644 index 00000000000000..31df3fb5e3074e --- /dev/null +++ b/homeassistant/components/blebox/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "address_already_configured": "Bir BleBox cihaz\u0131 zaten {address} yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r.", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/uk.json b/homeassistant/components/blebox/translations/uk.json new file mode 100644 index 00000000000000..fb10807acff132 --- /dev/null +++ b/homeassistant/components/blebox/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "address_already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 \u0430\u0434\u0440\u0435\u0441\u043e\u044e {address} \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "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." + }, + "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", + "unsupported_version": "\u041c\u0456\u043a\u0440\u043e\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0430 \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437\u0430\u0441\u0442\u0430\u0440\u0456\u043b\u0430. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u0457\u0457." + }, + "flow_title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 BleBox: {name} ({host})", + "step": { + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 BleBox.", + "title": "BleBox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/de.json b/homeassistant/components/blink/translations/de.json index f5116110a09896..d4f65329f9b076 100644 --- a/homeassistant/components/blink/translations/de.json +++ b/homeassistant/components/blink/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, @@ -13,7 +14,7 @@ "data": { "2fa": "Zwei-Faktor Authentifizierungscode" }, - "description": "Geben Sie die an Ihre E-Mail gesendete Pin ein. Wenn die E-Mail keine PIN enth\u00e4lt, lassen Sie das Feld leer.", + "description": "Gib die an deine E-Mail gesendete Pin ein. Wenn die E-Mail keine PIN enth\u00e4lt, lass das Feld leer.", "title": "Zwei-Faktor-Authentifizierung" }, "user": { diff --git a/homeassistant/components/blink/translations/tr.json b/homeassistant/components/blink/translations/tr.json new file mode 100644 index 00000000000000..8193ff9d8bee78 --- /dev/null +++ b/homeassistant/components/blink/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "2fa": { + "description": "E-postan\u0131za g\u00f6nderilen PIN kodunu girin" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/uk.json b/homeassistant/components/blink/translations/uk.json new file mode 100644 index 00000000000000..c45bf7b66517e2 --- /dev/null +++ b/homeassistant/components/blink/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041a\u043e\u0434 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434, \u043d\u0430\u0434\u0456\u0441\u043b\u0430\u043d\u0438\u0439 \u043d\u0430 \u0412\u0430\u0448\u0443 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443 \u043f\u043e\u0448\u0442\u0443", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Blink" + } + } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Blink", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Blink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/ca.json b/homeassistant/components/bmw_connected_drive/translations/ca.json new file mode 100644 index 00000000000000..d6bd70064c3271 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/ca.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "region": "Regi\u00f3 de ConnectedDrive", + "username": "Nom d'usuari" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Nom\u00e9s de lectura (nom\u00e9s sensors i notificacions, sense execuci\u00f3 de serveis, sense bloqueig)", + "use_location": "Utilitza la ubicaci\u00f3 de Home Assistant per a les crides de localitzaci\u00f3 del cotxe (obligatori per a vehicles que no siguin i3/i8 produ\u00efts abans del 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/cs.json b/homeassistant/components/bmw_connected_drive/translations/cs.json new file mode 100644 index 00000000000000..665dccd443db71 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/de.json b/homeassistant/components/bmw_connected_drive/translations/de.json new file mode 100644 index 00000000000000..12a870b4cc9181 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/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/bmw_connected_drive/translations/en.json b/homeassistant/components/bmw_connected_drive/translations/en.json index f194c8a344448a..dedd84d070b5c1 100644 --- a/homeassistant/components/bmw_connected_drive/translations/en.json +++ b/homeassistant/components/bmw_connected_drive/translations/en.json @@ -11,7 +11,6 @@ "user": { "data": { "password": "Password", - "read_only": "Read-only", "region": "ConnectedDrive Region", "username": "Username" } diff --git a/homeassistant/components/bmw_connected_drive/translations/es.json b/homeassistant/components/bmw_connected_drive/translations/es.json new file mode 100644 index 00000000000000..65ed9643f890f2 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "region": "Regi\u00f3n de ConnectedDrive", + "username": "Nombre de usuario" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "S\u00f3lo lectura (s\u00f3lo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)", + "use_location": "Usar la ubicaci\u00f3n de Home Assistant para las encuestas de localizaci\u00f3n de autom\u00f3viles (necesario para los veh\u00edculos no i3/i8 producidos antes del 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/et.json b/homeassistant/components/bmw_connected_drive/translations/et.json new file mode 100644 index 00000000000000..f28209a1e7a3fb --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "region": "ConnectedDrive'i piirkond", + "username": "Kasutajanimi" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Kirjutuskaitstud (ainult andurid ja teavitused, ei k\u00e4ivita teenuseid, kood puudub)", + "use_location": "Kasuta HA asukohta auto asukoha k\u00fcsitluste jaoks (n\u00f5utav enne 7/2014 toodetud muude kui i3 / i8 s\u00f5idukite jaoks)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/fr.json b/homeassistant/components/bmw_connected_drive/translations/fr.json new file mode 100644 index 00000000000000..1b8f562669fb5f --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/fr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec \u00e0 la connexion", + "invalid_auth": "Authentification invalide" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "region": "R\u00e9gion ConnectedDrive", + "username": "Nom d'utilisateur" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Lecture seule (uniquement capteurs et notification, pas d'ex\u00e9cution de services, pas de verrouillage)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/it.json b/homeassistant/components/bmw_connected_drive/translations/it.json new file mode 100644 index 00000000000000..277ed189c43ced --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/it.json @@ -0,0 +1,30 @@ +{ + "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", + "region": "Regione ConnectedDrive", + "username": "Nome utente" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Sola lettura (solo sensori e notifica, nessuna esecuzione di servizi, nessun blocco)", + "use_location": "Usa la posizione di Home Assistant per richieste sulla posizione dell'auto (richiesto per veicoli non i3/i8 prodotti prima del 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/lb.json b/homeassistant/components/bmw_connected_drive/translations/lb.json new file mode 100644 index 00000000000000..9ebbe919f8badf --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/lb.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Kont ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun" + }, + "step": { + "user": { + "data": { + "password": "Passwuert", + "region": "ConnectedDrive Regioun" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/no.json b/homeassistant/components/bmw_connected_drive/translations/no.json new file mode 100644 index 00000000000000..f1715c550db4dc --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/no.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "region": "ConnectedDrive-region", + "username": "Brukernavn" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Skrivebeskyttet (bare sensorer og varsler, ingen utf\u00f8relse av tjenester, ingen l\u00e5s)", + "use_location": "Bruk Home Assistant plassering for avstemningssteder for biler (p\u00e5krevd for ikke i3 / i8-kj\u00f8ret\u00f8y produsert f\u00f8r 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/pl.json b/homeassistant/components/bmw_connected_drive/translations/pl.json new file mode 100644 index 00000000000000..70467c6f9b93ed --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/pl.json @@ -0,0 +1,30 @@ +{ + "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", + "region": "Region ConnectedDrive", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Tylko odczyt (tylko czujniki i powiadomienia, brak wykonywania us\u0142ug, brak blokady)", + "use_location": "U\u017cyj lokalizacji Home Assistant do sondowania lokalizacji samochodu (wymagane w przypadku pojazd\u00f3w innych ni\u017c i3/i8 wyprodukowanych przed 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/pt.json b/homeassistant/components/bmw_connected_drive/translations/pt.json new file mode 100644 index 00000000000000..3814c892bd16d5 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/ru.json b/homeassistant/components/bmw_connected_drive/translations/ru.json new file mode 100644 index 00000000000000..0840affcef4200 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/ru.json @@ -0,0 +1,30 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d ConnectedDrive", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u0422\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u0435\u043d\u0438\u0435 (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f, \u0431\u0435\u0437 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0436\u0431, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438)", + "use_location": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Home Assistant \u0434\u043b\u044f \u043e\u043f\u0440\u043e\u0441\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439 (\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439 \u043d\u0435 i3/i8, \u0432\u044b\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u0434\u043e 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/tr.json b/homeassistant/components/bmw_connected_drive/translations/tr.json new file mode 100644 index 00000000000000..153aa4126b0668 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/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/bmw_connected_drive/translations/uk.json b/homeassistant/components/bmw_connected_drive/translations/uk.json new file mode 100644 index 00000000000000..68cdee2a66f9df --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "region": "ConnectedDrive Region", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u041b\u0438\u0448\u0435 \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u043d\u043d\u044f (\u043b\u0438\u0448\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0442\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0431\u0435\u0437 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u0441\u0435\u0440\u0432\u0456\u0441\u0456\u0432, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f)", + "use_location": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f Home Assistant \u0434\u043b\u044f \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u044c \u043c\u0456\u0441\u0446\u044f \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0456\u043b\u0456\u0432 (\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0456\u043b\u0456\u0432, \u0449\u043e \u043d\u0435 \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e i3/i8, \u0432\u0438\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u0438\u0445 \u0434\u043e 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json b/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json new file mode 100644 index 00000000000000..fde5e1e3c94e05 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json @@ -0,0 +1,30 @@ +{ + "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", + "region": "ConnectedDrive \u5340\u57df", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u552f\u8b80\uff08\u50c5\u652f\u63f4\u50b3\u611f\u5668\u8207\u901a\u77e5\uff0c\u4e0d\n\u5305\u542b\u670d\u52d9\u8207\u9396\u5b9a\uff09", + "use_location": "\u4f7f\u7528 Home Assistant \u4f4d\u7f6e\u53d6\u5f97\u6c7d\u8eca\u4f4d\u7f6e\uff08\u9700\u8981\u70ba2014/7 \u524d\u751f\u7522\u7684\u975ei3/i8 \u8eca\u6b3e\uff09" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/de.json b/homeassistant/components/bond/translations/de.json index 393232025ddc1f..14f86a30bb285f 100644 --- a/homeassistant/components/bond/translations/de.json +++ b/homeassistant/components/bond/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindung nicht m\u00f6glich", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/bond/translations/tr.json b/homeassistant/components/bond/translations/tr.json new file mode 100644 index 00000000000000..3488480a21845a --- /dev/null +++ b/homeassistant/components/bond/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "confirm": { + "data": { + "access_token": "Eri\u015fim Belirteci" + } + }, + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/uk.json b/homeassistant/components/bond/translations/uk.json index d7da60ea1786af..95ede3d43298ef 100644 --- a/homeassistant/components/bond/translations/uk.json +++ b/homeassistant/components/bond/translations/uk.json @@ -1,7 +1,28 @@ { "config": { "abort": { - "already_configured": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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." + }, + "error": { + "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.", + "old_firmware": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043e\u043d\u043e\u0432\u0438\u0442\u0438 \u043c\u0456\u043a\u0440\u043e\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u043d\u0430 \u0432\u0435\u0440\u0441\u0456\u044f \u0437\u0430\u0441\u0442\u0430\u0440\u0456\u043b\u0430 \u0456 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0454\u044e.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Bond {bond_id} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {bond_id}?" + }, + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "host": "\u0425\u043e\u0441\u0442" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/de.json b/homeassistant/components/braviatv/translations/de.json index b17d42ffaed465..8ac8c09e4fedc3 100644 --- a/homeassistant/components/braviatv/translations/de.json +++ b/homeassistant/components/braviatv/translations/de.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "Dieser Fernseher ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, ung\u00fcltiger Host- oder PIN-Code.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", "unsupported_model": "Ihr TV-Modell wird nicht unterst\u00fctzt." }, "step": { "authorize": { + "data": { + "pin": "PIN-Code" + }, "description": "Geben Sie den auf dem Sony Bravia-Fernseher angezeigten PIN-Code ein. \n\nWenn der PIN-Code nicht angezeigt wird, m\u00fcssen Sie die Registrierung von Home Assistant auf Ihrem Fernseher aufheben, gehen Sie daf\u00fcr zu: Einstellungen -> Netzwerk -> Remote - Ger\u00e4teeinstellungen -> Registrierung des entfernten Ger\u00e4ts aufheben.", "title": "Autorisieren Sie Sony Bravia TV" }, diff --git a/homeassistant/components/braviatv/translations/tr.json b/homeassistant/components/braviatv/translations/tr.json new file mode 100644 index 00000000000000..0853c8028fcb96 --- /dev/null +++ b/homeassistant/components/braviatv/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unsupported_model": "TV modeliniz desteklenmiyor." + }, + "step": { + "authorize": { + "title": "Sony Bravia TV'yi yetkilendirin" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "title": "Sony Bravia TV" + } + } + }, + "options": { + "step": { + "user": { + "title": "Sony Bravia TV i\u00e7in se\u00e7enekler" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/uk.json b/homeassistant/components/braviatv/translations/uk.json new file mode 100644 index 00000000000000..7f66329c57ec6b --- /dev/null +++ b/homeassistant/components/braviatv/translations/uk.json @@ -0,0 +1,39 @@ +{ + "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.", + "no_ip_control": "\u041d\u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0456 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e IP, \u0430\u0431\u043e \u0446\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\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", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unsupported_model": "\u0426\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "step": { + "authorize": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434, \u044f\u043a\u0438\u0439 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Sony Bravia. \n\n\u042f\u043a\u0449\u043e \u0412\u0438 \u043d\u0435 \u0431\u0430\u0447\u0438\u0442\u0435 PIN-\u043a\u043e\u0434, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0441\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438 \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u044e Home Assistant \u043d\u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0456. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 - > \u041c\u0435\u0440\u0435\u0436\u0430 - > \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e - > \u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438 \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u044e \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Sony Bravia" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457:\nhttps://www.home-assistant.io/integrations/braviatv", + "title": "\u0422\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Sony Bravia" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "\u0421\u043f\u0438\u0441\u043e\u043a \u0456\u0433\u043d\u043e\u0440\u043e\u0432\u0430\u043d\u0438\u0445 \u0434\u0436\u0435\u0440\u0435\u043b" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Sony Bravia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/de.json b/homeassistant/components/broadlink/translations/de.json index f915040635f53a..5704efe37c623f 100644 --- a/homeassistant/components/broadlink/translations/de.json +++ b/homeassistant/components/broadlink/translations/de.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP Adresse", "not_supported": "Ger\u00e4t nicht unterst\u00fctzt", "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP Adresse", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/broadlink/translations/tr.json b/homeassistant/components/broadlink/translations/tr.json new file mode 100644 index 00000000000000..d37a3203476704 --- /dev/null +++ b/homeassistant/components/broadlink/translations/tr.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_supported": "Cihaz desteklenmiyor", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "auth": { + "title": "Cihaza kimlik do\u011frulama" + }, + "finish": { + "title": "Cihaz i\u00e7in bir isim se\u00e7in" + }, + "reset": { + "title": "Cihaz\u0131n kilidini a\u00e7\u0131n" + }, + "unlock": { + "data": { + "unlock": "Evet, yap." + }, + "title": "Cihaz\u0131n kilidini a\u00e7\u0131n (iste\u011fe ba\u011fl\u0131)" + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "timeout": "Zaman a\u015f\u0131m\u0131" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/uk.json b/homeassistant/components/broadlink/translations/uk.json new file mode 100644 index 00000000000000..ea3e3e75cd6007 --- /dev/null +++ b/homeassistant/components/broadlink/translations/uk.json @@ -0,0 +1,47 @@ +{ + "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_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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "not_supported": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "{name} ({model}, {host})", + "step": { + "auth": { + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + }, + "finish": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c \u043d\u0430\u0437\u0432\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "reset": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 {name} ({model}, {host}) \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e. \u0414\u043e\u0442\u0440\u0438\u043c\u0443\u0439\u0442\u0435\u0441\u044f \u0446\u0438\u0445 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439, \u0449\u043e\u0431 \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438 \u0439\u043e\u0433\u043e:\n 1. \u0412\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0443 Broadlink.\n 2. \u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439.\n 3. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c `...` \u0432 \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u0432\u0435\u0440\u0445\u043d\u044c\u043e\u043c\u0443 \u043a\u0443\u0442\u0456.\n 4. \u041f\u0440\u043e\u043a\u0440\u0443\u0442\u0456\u0442\u044c \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 \u0432\u043d\u0438\u0437.\n 5. \u0412\u0438\u043c\u043a\u043d\u0456\u0442\u044c \u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f.", + "title": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "unlock": { + "data": { + "unlock": "\u0422\u0430\u043a, \u0437\u0440\u043e\u0431\u0438\u0442\u0438 \u0446\u0435." + }, + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 {name} ({model}, {host}) \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e. \u0426\u0435 \u043c\u043e\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u0434\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0437 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0454\u044e \u0432 Home Assistant. \u0425\u043e\u0447\u0435\u0442\u0435 \u0439\u043e\u0433\u043e \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438?", + "title": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/de.json b/homeassistant/components/brother/translations/de.json index 72bd052cc1d507..c2a7ae8ec76b77 100644 --- a/homeassistant/components/brother/translations/de.json +++ b/homeassistant/components/brother/translations/de.json @@ -5,7 +5,7 @@ "unsupported_model": "Dieses Druckermodell wird nicht unterst\u00fctzt." }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "snmp_error": "SNMP-Server deaktiviert oder Drucker nicht unterst\u00fctzt.", "wrong_host": " Ung\u00fcltiger Hostname oder IP-Adresse" }, diff --git a/homeassistant/components/brother/translations/tr.json b/homeassistant/components/brother/translations/tr.json index 160a5ecc7b78a2..cd91a4852527a3 100644 --- a/homeassistant/components/brother/translations/tr.json +++ b/homeassistant/components/brother/translations/tr.json @@ -1,6 +1,20 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unsupported_model": "Bu yaz\u0131c\u0131 modeli desteklenmiyor." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "Brother Yaz\u0131c\u0131: {model} {serial_number}", "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "type": "Yaz\u0131c\u0131n\u0131n t\u00fcr\u00fc" + } + }, "zeroconf_confirm": { "title": "Ke\u015ffedilen Brother Yaz\u0131c\u0131" } diff --git a/homeassistant/components/brother/translations/uk.json b/homeassistant/components/brother/translations/uk.json new file mode 100644 index 00000000000000..ac5943aa85cc15 --- /dev/null +++ b/homeassistant/components/brother/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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.", + "unsupported_model": "\u0426\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\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", + "snmp_error": "\u0421\u0435\u0440\u0432\u0435\u0440 SNMP \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0430\u0431\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "wrong_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430." + }, + "flow_title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 Brother: {model} {serial_number}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457: https://www.home-assistant.io/integrations/brother." + }, + "zeroconf_confirm": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" + }, + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u043d\u0442\u0435\u0440 Brother {model} \u0437 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440 Brother" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/de.json b/homeassistant/components/bsblan/translations/de.json index 5fd61c0bfedb6c..971e3c1ea8aaab 100644 --- a/homeassistant/components/bsblan/translations/de.json +++ b/homeassistant/components/bsblan/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/bsblan/translations/tr.json b/homeassistant/components/bsblan/translations/tr.json index 94acde2d0a3e46..803b5102a073c6 100644 --- a/homeassistant/components/bsblan/translations/tr.json +++ b/homeassistant/components/bsblan/translations/tr.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { "user": { "data": { + "host": "Ana Bilgisayar", "password": "\u015eifre", + "port": "Port", "username": "Kullan\u0131c\u0131 ad\u0131" } } diff --git a/homeassistant/components/bsblan/translations/uk.json b/homeassistant/components/bsblan/translations/uk.json new file mode 100644 index 00000000000000..619f7c8e8a580a --- /dev/null +++ b/homeassistant/components/bsblan/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "BSB-Lan: {name}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "passkey": "\u041f\u0430\u0440\u043e\u043b\u044c", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 BSB-Lan.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/de.json b/homeassistant/components/canary/translations/de.json index eebc9bd5fc3fd8..bdd746c314988c 100644 --- a/homeassistant/components/canary/translations/de.json +++ b/homeassistant/components/canary/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "Canary: {name}", "step": { diff --git a/homeassistant/components/canary/translations/tr.json b/homeassistant/components/canary/translations/tr.json new file mode 100644 index 00000000000000..6d18629b067921 --- /dev/null +++ b/homeassistant/components/canary/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/uk.json b/homeassistant/components/canary/translations/uk.json new file mode 100644 index 00000000000000..74327f3ebd672b --- /dev/null +++ b/homeassistant/components/canary/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0438, \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432 ffmpeg \u0434\u043b\u044f \u043a\u0430\u043c\u0435\u0440", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 \u0437\u0430\u043f\u0438\u0442\u0443 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/de.json b/homeassistant/components/cast/translations/de.json index 87f8e7cb2bc9d2..7ff1efb8ee0d81 100644 --- a/homeassistant/components/cast/translations/de.json +++ b/homeassistant/components/cast/translations/de.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Keine Google Cast Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von Google Cast ist notwendig." + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden.", + "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/cast/translations/tr.json b/homeassistant/components/cast/translations/tr.json new file mode 100644 index 00000000000000..8de4663957ea85 --- /dev/null +++ b/homeassistant/components/cast/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/uk.json b/homeassistant/components/cast/translations/uk.json index 783defdca258a3..292861e9129dbd 100644 --- a/homeassistant/components/cast/translations/uk.json +++ b/homeassistant/components/cast/translations/uk.json @@ -1,8 +1,12 @@ { "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." + }, "step": { "confirm": { - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Google Cast?" + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" } } } diff --git a/homeassistant/components/cert_expiry/translations/tr.json b/homeassistant/components/cert_expiry/translations/tr.json new file mode 100644 index 00000000000000..6c05bef3a65f64 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/uk.json b/homeassistant/components/cert_expiry/translations/uk.json new file mode 100644 index 00000000000000..997e12a8cb29a9 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "import_failed": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0456\u043c\u043f\u043e\u0440\u0442\u0443 \u0437 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457." + }, + "error": { + "connection_refused": "\u041f\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u0434\u043e \u0445\u043e\u0441\u0442\u0443 \u0431\u0443\u043b\u043e \u0432\u0456\u0434\u043c\u043e\u0432\u043b\u0435\u043d\u043e \u0432 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u0456.", + "connection_timeout": "\u0427\u0430\u0441 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0445\u043e\u0441\u0442\u0430 \u043c\u0438\u043d\u0443\u0432.", + "resolve_failed": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044c \u0434\u043e \u0445\u043e\u0441\u0442\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u0422\u0435\u0440\u043c\u0456\u043d \u0434\u0456\u0457 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430" + } + } + }, + "title": "\u0422\u0435\u0440\u043c\u0456\u043d \u0434\u0456\u0457 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/climate/translations/tr.json b/homeassistant/components/climate/translations/tr.json index 0b027dbd87faa2..201fec4c4b648d 100644 --- a/homeassistant/components/climate/translations/tr.json +++ b/homeassistant/components/climate/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "{entity_name} \u00fczerinde HVAC modunu de\u011fi\u015ftir", + "set_preset_mode": "{entity_name} \u00fczerindeki \u00f6n ayar\u0131 de\u011fi\u015ftir" + } + }, "state": { "_": { "auto": "Otomatik", diff --git a/homeassistant/components/climate/translations/uk.json b/homeassistant/components/climate/translations/uk.json index 8d636c386e5479..de6baff021cec4 100644 --- a/homeassistant/components/climate/translations/uk.json +++ b/homeassistant/components/climate/translations/uk.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "set_hvac_mode": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c HVAC \u043d\u0430 {entity_name}", - "set_preset_mode": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u043f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430 {entity_name}" + "set_hvac_mode": "{entity_name}: \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u0440\u043e\u0431\u043e\u0442\u0438", + "set_preset_mode": "{entity_name}: \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043f\u0440\u0435\u0441\u0435\u0442" }, "condition_type": { - "is_hvac_mode": "{entity_name} \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0432 \u043f\u0435\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c HVAC", + "is_hvac_mode": "{entity_name} \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0434\u0430\u043d\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456 \u0440\u043e\u0431\u043e\u0442\u0438", "is_preset_mode": "{entity_name} \u043d\u0430\u0441\u0442\u0440\u043e\u0454\u043d\u043e \u043d\u0430 \u043f\u0435\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c" }, "trigger_type": { - "current_humidity_changed": "{entity_name} \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u0430 \u0432\u043e\u043b\u043e\u0433\u0456\u0441\u0442\u044c \u0437\u043c\u0456\u043d\u0435\u043d\u0430", - "current_temperature_changed": "{entity_name} \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0443 \u0437\u043c\u0456\u043d\u0435\u043d\u043e", - "hvac_mode_changed": "{entity_name} \u0420\u0435\u0436\u0438\u043c HVAC \u0437\u043c\u0456\u043d\u0435\u043d\u043e" + "current_humidity_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u043e\u0457 \u0432\u043e\u043b\u043e\u0433\u043e\u0441\u0442\u0456", + "current_temperature_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u043e\u0457 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438", + "hvac_mode_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0440\u0435\u0436\u0438\u043c \u0440\u043e\u0431\u043e\u0442\u0438" } }, "state": { @@ -21,7 +21,7 @@ "dry": "\u041e\u0441\u0443\u0448\u0435\u043d\u043d\u044f", "fan_only": "\u041b\u0438\u0448\u0435 \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0442\u043e\u0440", "heat": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f", - "heat_cool": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f/\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "heat_cool": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f / \u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e" } }, diff --git a/homeassistant/components/cloud/translations/ca.json b/homeassistant/components/cloud/translations/ca.json index fede749c7dd935..4e6a14cd2f021e 100644 --- a/homeassistant/components/cloud/translations/ca.json +++ b/homeassistant/components/cloud/translations/ca.json @@ -2,9 +2,9 @@ "system_health": { "info": { "alexa_enabled": "Alexa activada", - "can_reach_cert_server": "Servidor de certificaci\u00f3 accessible", - "can_reach_cloud": "Home Assistant Cloud accessible", - "can_reach_cloud_auth": "Servidor d'autenticaci\u00f3 accessible", + "can_reach_cert_server": "Acc\u00e9s al servidor de certificaci\u00f3", + "can_reach_cloud": "Acc\u00e9s a Home Assistant Cloud", + "can_reach_cloud_auth": "Acc\u00e9s al servidor d'autenticaci\u00f3", "google_enabled": "Google activat", "logged_in": "Sessi\u00f3 iniciada", "relayer_connected": "Encaminador connectat", diff --git a/homeassistant/components/cloud/translations/de.json b/homeassistant/components/cloud/translations/de.json new file mode 100644 index 00000000000000..443a5e3aa72dde --- /dev/null +++ b/homeassistant/components/cloud/translations/de.json @@ -0,0 +1,15 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa aktiviert", + "can_reach_cert_server": "Zertifikatsserver erreichbar", + "can_reach_cloud": "Home Assistant Cloud erreichbar", + "can_reach_cloud_auth": "Authentifizierungsserver erreichbar", + "google_enabled": "Google aktiviert", + "logged_in": "Angemeldet", + "remote_connected": "Remote verbunden", + "remote_enabled": "Remote aktiviert", + "subscription_expiration": "Ablauf des Abonnements" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/fr.json b/homeassistant/components/cloud/translations/fr.json new file mode 100644 index 00000000000000..9bb4029fce0532 --- /dev/null +++ b/homeassistant/components/cloud/translations/fr.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa activ\u00e9", + "can_reach_cert_server": "Acc\u00e9der au serveur de certificats", + "can_reach_cloud": "Acc\u00e9der \u00e0 Home Assistant Cloud", + "can_reach_cloud_auth": "Acc\u00e9der au serveur d'authentification", + "google_enabled": "Google activ\u00e9", + "logged_in": "Connect\u00e9", + "relayer_connected": "Relais connect\u00e9", + "remote_connected": "Contr\u00f4le \u00e0 distance connect\u00e9", + "remote_enabled": "Contr\u00f4le \u00e0 distance activ\u00e9", + "subscription_expiration": "Expiration de l'abonnement" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/pl.json b/homeassistant/components/cloud/translations/pl.json index 30aaeeb77d14be..1df32a14d8e0e2 100644 --- a/homeassistant/components/cloud/translations/pl.json +++ b/homeassistant/components/cloud/translations/pl.json @@ -4,7 +4,7 @@ "alexa_enabled": "Alexa w\u0142\u0105czona", "can_reach_cert_server": "Dost\u0119p do serwera certyfikat\u00f3w", "can_reach_cloud": "Dost\u0119p do chmury Home Assistant", - "can_reach_cloud_auth": "Dost\u0119p do serwera uwierzytelniania", + "can_reach_cloud_auth": "Dost\u0119p do serwera certyfikat\u00f3w", "google_enabled": "Asystent Google w\u0142\u0105czony", "logged_in": "Zalogowany", "relayer_connected": "Relayer pod\u0142\u0105czony", diff --git a/homeassistant/components/cloud/translations/tr.json b/homeassistant/components/cloud/translations/tr.json index 0acb1e6a9a6632..75d1c768bebda4 100644 --- a/homeassistant/components/cloud/translations/tr.json +++ b/homeassistant/components/cloud/translations/tr.json @@ -1,6 +1,9 @@ { "system_health": { "info": { + "alexa_enabled": "Alexa Etkin", + "can_reach_cloud": "Home Assistant Cloud'a ula\u015f\u0131n", + "google_enabled": "Google Etkin", "logged_in": "Giri\u015f Yapt\u0131", "relayer_connected": "Yeniden Katman ba\u011fl\u0131", "remote_connected": "Uzaktan Ba\u011fl\u0131", diff --git a/homeassistant/components/cloud/translations/uk.json b/homeassistant/components/cloud/translations/uk.json new file mode 100644 index 00000000000000..a2e68b911e58f0 --- /dev/null +++ b/homeassistant/components/cloud/translations/uk.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 Alexa", + "can_reach_cert_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0456\u0432", + "can_reach_cloud": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e Home Assistant Cloud", + "can_reach_cloud_auth": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457", + "google_enabled": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 Google", + "logged_in": "\u0412\u0445\u0456\u0434 \u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443", + "relayer_connected": "Relayer \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439", + "remote_connected": "\u0412\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439", + "remote_enabled": "\u0412\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u0438\u0439", + "subscription_expiration": "\u0422\u0435\u0440\u043c\u0456\u043d \u0434\u0456\u0457 \u043f\u0435\u0440\u0435\u0434\u043f\u043b\u0430\u0442\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/de.json b/homeassistant/components/cloudflare/translations/de.json index 809dad5da46e6c..d9858b36f55aff 100644 --- a/homeassistant/components/cloudflare/translations/de.json +++ b/homeassistant/components/cloudflare/translations/de.json @@ -1,12 +1,15 @@ { "config": { "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown": "Unerwarteter Fehler" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_zone": "Ung\u00fcltige Zone" }, + "flow_title": "Cloudflare: {name}", "step": { "records": { "data": { @@ -18,6 +21,11 @@ "api_token": "API Token" }, "title": "Mit Cloudflare verbinden" + }, + "zone": { + "data": { + "zone": "Zone" + } } } } diff --git a/homeassistant/components/cloudflare/translations/tr.json b/homeassistant/components/cloudflare/translations/tr.json index b7c7b438804b0d..5d1180961f6295 100644 --- a/homeassistant/components/cloudflare/translations/tr.json +++ b/homeassistant/components/cloudflare/translations/tr.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown": "Beklenmeyen hata" + }, "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "invalid_zone": "Ge\u00e7ersiz b\u00f6lge" }, "flow_title": "Cloudflare: {name}", @@ -12,6 +18,9 @@ "title": "G\u00fcncellenecek Kay\u0131tlar\u0131 Se\u00e7in" }, "user": { + "data": { + "api_token": "API Belirteci" + }, "title": "Cloudflare'ye ba\u011flan\u0131n" }, "zone": { diff --git a/homeassistant/components/cloudflare/translations/uk.json b/homeassistant/components/cloudflare/translations/uk.json new file mode 100644 index 00000000000000..425ec2733b8f66 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "invalid_zone": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0437\u043e\u043d\u0430" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "\u0417\u0430\u043f\u0438\u0441\u0438" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043b\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "user": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0442\u043e\u043a\u0435\u043d API, \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0437 \u0434\u043e\u0437\u0432\u043e\u043b\u0430\u043c\u0438 Zone: Zone: Read \u0456 Zone: DNS: Edit \u0434\u043b\u044f \u0432\u0441\u0456\u0445 \u0437\u043e\u043d \u0443 \u0432\u0430\u0448\u043e\u043c\u0443 \u043f\u0440\u043e\u0444\u0456\u043b\u0456.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Cloudflare" + }, + "zone": { + "data": { + "zone": "\u0417\u043e\u043d\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0437\u043e\u043d\u0443 \u0434\u043b\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/de.json b/homeassistant/components/control4/translations/de.json index f9a5783cd9117e..399b8d424911e1 100644 --- a/homeassistant/components/control4/translations/de.json +++ b/homeassistant/components/control4/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/control4/translations/tr.json b/homeassistant/components/control4/translations/tr.json new file mode 100644 index 00000000000000..aed7e564a760b5 --- /dev/null +++ b/homeassistant/components/control4/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/uk.json b/homeassistant/components/control4/translations/uk.json index 6c0426eba8fb3c..682d86c5deb4da 100644 --- a/homeassistant/components/control4/translations/uk.json +++ b/homeassistant/components/control4/translations/uk.json @@ -1,10 +1,21 @@ { "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, "step": { "user": { "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - } + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Control4 \u0456 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0412\u0430\u0448\u043e\u0433\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430." } } }, @@ -12,7 +23,7 @@ "step": { "init": { "data": { - "scan_interval": "\u0421\u0435\u043a\u0443\u043d\u0434 \u043c\u0456\u0436 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f\u043c\u0438" + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" } } } diff --git a/homeassistant/components/coolmaster/translations/de.json b/homeassistant/components/coolmaster/translations/de.json index 908dfaa448c1af..4e58b1ed964cd8 100644 --- a/homeassistant/components/coolmaster/translations/de.json +++ b/homeassistant/components/coolmaster/translations/de.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "no_units": "Es wurden keine HVAC-Ger\u00e4te im CoolMasterNet-Host gefunden." }, "step": { diff --git a/homeassistant/components/coolmaster/translations/tr.json b/homeassistant/components/coolmaster/translations/tr.json new file mode 100644 index 00000000000000..4848a34362cc3b --- /dev/null +++ b/homeassistant/components/coolmaster/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "off": "Kapat\u0131labilir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/uk.json b/homeassistant/components/coolmaster/translations/uk.json new file mode 100644 index 00000000000000..038a7bc48f0011 --- /dev/null +++ b/homeassistant/components/coolmaster/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "no_units": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f, \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0456\u0457 \u0442\u0430 \u043a\u043e\u043d\u0434\u0438\u0446\u0456\u043e\u043d\u0443\u0432\u0430\u043d\u043d\u044f." + }, + "step": { + "user": { + "data": { + "cool": "\u0420\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "dry": "\u0420\u0435\u0436\u0438\u043c \u043e\u0441\u0443\u0448\u0435\u043d\u043d\u044f", + "fan_only": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0456\u0457", + "heat": "\u0420\u0435\u0436\u0438\u043c \u043e\u0431\u0456\u0433\u0440\u0456\u0432\u0443", + "heat_cool": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "host": "\u0425\u043e\u0441\u0442", + "off": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "CoolMasterNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/tr.json b/homeassistant/components/coronavirus/translations/tr.json new file mode 100644 index 00000000000000..b608d60f824060 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "country": "\u00dclke" + }, + "title": "\u0130zlemek i\u00e7in bir \u00fclke se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/uk.json b/homeassistant/components/coronavirus/translations/uk.json new file mode 100644 index 00000000000000..151e7b14d3f0ca --- /dev/null +++ b/homeassistant/components/coronavirus/translations/uk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "country": "\u041a\u0440\u0430\u0457\u043d\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043a\u0440\u0430\u0457\u043d\u0443 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/translations/tr.json b/homeassistant/components/cover/translations/tr.json index 98bc8cdb18d7fa..f042233a6d1289 100644 --- a/homeassistant/components/cover/translations/tr.json +++ b/homeassistant/components/cover/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "action_type": { + "close": "{entity_name} kapat", + "open": "{entity_name} a\u00e7\u0131n" + } + }, "state": { "_": { "closed": "Kapal\u0131", diff --git a/homeassistant/components/cover/translations/uk.json b/homeassistant/components/cover/translations/uk.json index 66cd0c77c73c97..ceb49fff3e949d 100644 --- a/homeassistant/components/cover/translations/uk.json +++ b/homeassistant/components/cover/translations/uk.json @@ -1,10 +1,29 @@ { "device_automation": { "action_type": { + "close": "{entity_name}: \u0437\u0430\u043a\u0440\u0438\u0442\u0438", + "close_tilt": "{entity_name}: \u0437\u0430\u043a\u0440\u0438\u0442\u0438 \u043b\u0430\u043c\u0435\u043b\u0456", + "open": "{entity_name}: \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438", + "open_tilt": "{entity_name}: \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043b\u0430\u043c\u0435\u043b\u0456", + "set_position": "{entity_name}: \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044f", + "set_tilt_position": "{entity_name}: \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043d\u0430\u0445\u0438\u043b \u043b\u0430\u043c\u0435\u043b\u0435\u0439", "stop": "\u0417\u0443\u043f\u0438\u043d\u0438\u0442\u0438 {entity_name}" }, + "condition_type": { + "is_closed": "{entity_name} \u0432 \u0437\u0430\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_closing": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "is_open": "{entity_name} \u0443 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_opening": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "is_position": "{entity_name} \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u0456", + "is_tilt_position": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \"{entity_name}\" \u043c\u0430\u0454 \u043d\u0430\u0445\u0438\u043b \u043b\u0430\u043c\u0435\u043b\u0435\u0439" + }, "trigger_type": { - "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e" + "closed": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0442\u043e", + "closing": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e", + "opening": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "position": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044f", + "tilt_position": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u043d\u0430\u0445\u0438\u043b \u043b\u0430\u043c\u0435\u043b\u0435\u0439" } }, "state": { diff --git a/homeassistant/components/daikin/translations/de.json b/homeassistant/components/daikin/translations/de.json index bbac113eb44487..dcec53c15690b1 100644 --- a/homeassistant/components/daikin/translations/de.json +++ b/homeassistant/components/daikin/translations/de.json @@ -2,15 +2,17 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "api_key": "API-Schl\u00fcssel", "host": "Host", "password": "Passwort" }, diff --git a/homeassistant/components/daikin/translations/pt.json b/homeassistant/components/daikin/translations/pt.json index dd9b538ae8b08a..d4188fb10f960f 100644 --- a/homeassistant/components/daikin/translations/pt.json +++ b/homeassistant/components/daikin/translations/pt.json @@ -16,7 +16,7 @@ "host": "Servidor", "password": "Palavra-passe" }, - "description": "Introduza o endere\u00e7o IP do seu Daikin AC.", + "description": "Introduza Endere\u00e7o IP do seu Daikin AC.\n\nAten\u00e7\u00e3o que [%chave:common::config_flow::data::api_key%] e Palavra-passe s\u00f3 s\u00e3o utilizador pelos dispositivos BRP072Cxx e SKYFi, respectivamente.", "title": "Configurar o Daikin AC" } } diff --git a/homeassistant/components/daikin/translations/tr.json b/homeassistant/components/daikin/translations/tr.json new file mode 100644 index 00000000000000..4148bf2b9f1c51 --- /dev/null +++ b/homeassistant/components/daikin/translations/tr.json @@ -0,0 +1,22 @@ +{ + "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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "host": "Ana Bilgisayar", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/uk.json b/homeassistant/components/daikin/translations/uk.json new file mode 100644 index 00000000000000..648d68d7a810e5 --- /dev/null +++ b/homeassistant/components/daikin/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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.", + "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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0412\u0430\u0448\u043e\u0433\u043e Daikin AC. \n\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u041a\u043b\u044e\u0447 API \u0456 \u041f\u0430\u0440\u043e\u043b\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044f\u043c\u0438 BRP072Cxx \u0456 SKYFi \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e.", + "title": "Daikin AC" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/cs.json b/homeassistant/components/deconz/translations/cs.json index 3ad72dc9ac9c65..52cbd607b7f9a8 100644 --- a/homeassistant/components/deconz/translations/cs.json +++ b/homeassistant/components/deconz/translations/cs.json @@ -60,14 +60,15 @@ }, "trigger_type": { "remote_awakened": "Za\u0159\u00edzen\u00ed probuzeno", - "remote_button_double_press": "Dvakr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_double_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dvakr\u00e1t", + "remote_button_long_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dlouze", "remote_button_long_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" po dlouh\u00e9m stisku", - "remote_button_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", - "remote_button_quintuple_press": "P\u011btkr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_quadruple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "remote_button_quintuple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto p\u011btkr\u00e1t", "remote_button_rotation_stopped": "Oto\u010den\u00ed tla\u010d\u00edtka \"{subtype}\" bylo zastaveno", - "remote_button_short_press": "Stiknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto", "remote_button_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\"", - "remote_button_triple_press": "T\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_triple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto t\u0159ikr\u00e1t", "remote_double_tap": "Dvakr\u00e1t poklep\u00e1no na za\u0159\u00edzen\u00ed \"{subtype}\"", "remote_double_tap_any_side": "Za\u0159\u00edzen\u00ed bylo poklep\u00e1no 2x na libovolnou stranu", "remote_flip_180_degrees": "Za\u0159\u00edzen\u00ed p\u0159evr\u00e1ceno o 180 stup\u0148\u016f", diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index f9448705c5dc67..d7553652412a09 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Bridge ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt.", - "no_bridges": "Keine deCON-Bridges entdeckt", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "no_bridges": "Keine deCONZ-Bridges entdeckt", + "no_hardware_available": "Keine Funkhardware an deCONZ angeschlossen", "not_deconz_bridge": "Keine deCONZ Bridge entdeckt", "updated_instance": "deCONZ-Instanz mit neuer Host-Adresse aktualisiert" }, @@ -13,7 +14,7 @@ "flow_title": "deCONZ Zigbee Gateway", "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Add-on hass.io {addon} bereitgestellt wird?", + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Hass.io Add-on {addon} bereitgestellt wird?", "title": "deCONZ Zigbee Gateway \u00fcber das Hass.io Add-on" }, "link": { @@ -28,7 +29,7 @@ }, "user": { "data": { - "host": "W\u00e4hlen Sie das erkannte deCONZ-Gateway aus" + "host": "W\u00e4hle das erkannte deCONZ-Gateway aus" } } } @@ -92,7 +93,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "deCONZ CLIP-Sensoren zulassen", - "allow_deconz_groups": "deCONZ-Lichtgruppen zulassen" + "allow_deconz_groups": "deCONZ-Lichtgruppen zulassen", + "allow_new_devices": "Automatisches Hinzuf\u00fcgen von neuen Ger\u00e4ten zulassen" }, "description": "Sichtbarkeit der deCONZ-Ger\u00e4tetypen konfigurieren", "title": "deCONZ-Optionen" diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index 487163794833f5..c1435dbb186a5e 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -14,8 +14,8 @@ "flow_title": "", "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegget {addon} ?", - "title": "deCONZ Zigbee gateway via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegg {addon} ?", + "title": "deCONZ Zigbee gateway via Hass.io-tillegg" }, "link": { "description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger -> Gateway -> Avansert \n 2. Trykk p\u00e5 \"Autentiser app\" knappen", diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 24a3ba61706696..1b4eba97096ae5 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -38,9 +38,9 @@ "trigger_subtype": { "both_buttons": "oba przyciski", "bottom_buttons": "dolne przyciski", - "button_1": "pierwszy przycisk", - "button_2": "drugi przycisk", - "button_3": "trzeci przycisk", + "button_1": "pierwszy", + "button_2": "drugi", + "button_3": "trzeci", "button_4": "czwarty", "close": "zamknij", "dim_down": "zmniejszenie jasno\u015bci", diff --git a/homeassistant/components/deconz/translations/ru.json b/homeassistant/components/deconz/translations/ru.json index a6bc0daaa3e271..f22975530d8324 100644 --- a/homeassistant/components/deconz/translations/ru.json +++ b/homeassistant/components/deconz/translations/ru.json @@ -14,8 +14,8 @@ "flow_title": "\u0428\u043b\u044e\u0437 Zigbee deCONZ ({host})", "step": { "hassio_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" }, "link": { "description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ -> Gateway -> Advanced.\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", diff --git a/homeassistant/components/deconz/translations/tr.json b/homeassistant/components/deconz/translations/tr.json index e73703043f3ce0..22eea1278d744c 100644 --- a/homeassistant/components/deconz/translations/tr.json +++ b/homeassistant/components/deconz/translations/tr.json @@ -1,4 +1,47 @@ { + "config": { + "abort": { + "already_configured": "K\u00f6pr\u00fc zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "step": { + "manual_input": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "user": { + "data": { + "host": "Ke\u015ffedilen deCONZ a\u011f ge\u00e7idini se\u00e7in" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "side_4": "Yan 4", + "side_5": "Yan 5", + "side_6": "Yan 6" + }, + "trigger_type": { + "remote_awakened": "Cihaz uyand\u0131", + "remote_double_tap": "\" {subtype} \" cihaz\u0131na iki kez hafif\u00e7e vuruldu", + "remote_double_tap_any_side": "Cihaz herhangi bir tarafta \u00e7ift dokundu", + "remote_falling": "Serbest d\u00fc\u015f\u00fc\u015fte cihaz", + "remote_flip_180_degrees": "Cihaz 180 derece d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_flip_90_degrees": "Cihaz 90 derece d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_moved": "Cihaz \" {subtype} \" yukar\u0131 ta\u015f\u0131nd\u0131", + "remote_moved_any_side": "Cihaz herhangi bir taraf\u0131 yukar\u0131 gelecek \u015fekilde ta\u015f\u0131nd\u0131", + "remote_rotate_from_side_1": "Cihaz, \"1. taraftan\" \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_2": "Cihaz, \"2. taraftan\" \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_3": "Cihaz \"3. taraftan\" \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_4": "Cihaz, \"4. taraf\" dan \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_5": "Cihaz, \"5. taraf\" dan \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_turned_clockwise": "Cihaz saat y\u00f6n\u00fcnde d\u00f6nd\u00fc", + "remote_turned_counter_clockwise": "Cihaz saat y\u00f6n\u00fcn\u00fcn tersine d\u00f6nd\u00fc" + } + }, "options": { "step": { "deconz_devices": { diff --git a/homeassistant/components/deconz/translations/uk.json b/homeassistant/components/deconz/translations/uk.json new file mode 100644 index 00000000000000..b5de362a731ee3 --- /dev/null +++ b/homeassistant/components/deconz/translations/uk.json @@ -0,0 +1,105 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e.", + "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.", + "no_bridges": "\u0428\u043b\u044e\u0437\u0438 deCONZ \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456.", + "no_hardware_available": "\u0420\u0430\u0434\u0456\u043e\u043e\u0431\u043b\u0430\u0434\u043d\u0430\u043d\u043d\u044f \u043d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e deCONZ.", + "not_deconz_bridge": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u0448\u043b\u044e\u0437\u043e\u043c deCONZ.", + "updated_instance": "\u0410\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043e." + }, + "error": { + "no_key": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API." + }, + "flow_title": "\u0428\u043b\u044e\u0437 Zigbee deCONZ ({host})", + "step": { + "hassio_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + }, + "link": { + "description": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u0457 \u0432 Home Assistant: \n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0438 deCONZ - > Gateway - > Advanced.\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", + "title": "\u0417\u0432'\u044f\u0437\u043e\u043a \u0437 deCONZ" + }, + "manual_input": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + } + }, + "user": { + "data": { + "host": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0438\u0439 \u0448\u043b\u044e\u0437 deCONZ" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "both_buttons": "\u041e\u0431\u0438\u0434\u0432\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "bottom_buttons": "\u041d\u0438\u0436\u043d\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "close": "\u0417\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "dim_down": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "dim_up": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "left": "\u041b\u0456\u0432\u043e\u0440\u0443\u0447", + "open": "\u0412\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "right": "\u041f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", + "side_1": "\u0413\u0440\u0430\u043d\u044c 1", + "side_2": "\u0413\u0440\u0430\u043d\u044c 2", + "side_3": "\u0413\u0440\u0430\u043d\u044c 3", + "side_4": "\u0413\u0440\u0430\u043d\u044c 4", + "side_5": "\u0413\u0440\u0430\u043d\u044c 5", + "side_6": "\u0413\u0440\u0430\u043d\u044c 6", + "top_buttons": "\u0412\u0435\u0440\u0445\u043d\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "remote_awakened": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0440\u043e\u0437\u0431\u0443\u0434\u0438\u043b\u0438", + "remote_button_double_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438", + "remote_button_long_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_long_release": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_quadruple_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438", + "remote_button_quintuple_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432", + "remote_button_rotated": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0430", + "remote_button_rotated_fast": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0430 \u0448\u0432\u0438\u0434\u043a\u043e", + "remote_button_rotation_stopped": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043f\u0440\u0438\u043f\u0438\u043d\u0438\u043b\u0430 \u043e\u0431\u0435\u0440\u0442\u0430\u043d\u043d\u044f", + "remote_button_short_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_short_release": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_triple_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438", + "remote_double_tap": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c {subtype} \u043f\u043e\u0441\u0442\u0443\u043a\u0430\u043b\u0438 \u0434\u0432\u0456\u0447\u0456", + "remote_double_tap_any_side": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c \u043f\u043e\u0441\u0442\u0443\u043a\u0430\u043b\u0438 \u0434\u0432\u0456\u0447\u0456", + "remote_falling": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0443 \u0432\u0456\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u0430\u0434\u0456\u043d\u043d\u0456", + "remote_flip_180_degrees": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043d\u0430 180 \u0433\u0440\u0430\u0434\u0443\u0441\u0456\u0432", + "remote_flip_90_degrees": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043d\u0430 90 \u0433\u0440\u0430\u0434\u0443\u0441\u0456\u0432", + "remote_gyro_activated": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0442\u0440\u044f\u0441\u043b\u0438", + "remote_moved": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0437\u0440\u0443\u0448\u0438\u043b\u0438, \u043a\u043e\u043b\u0438 {subtype} \u0437\u0432\u0435\u0440\u0445\u0443", + "remote_moved_any_side": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u043b\u0438", + "remote_rotate_from_side_1": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 1 \u043d\u0430 {subtype}", + "remote_rotate_from_side_2": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 2 \u043d\u0430 {subtype}", + "remote_rotate_from_side_3": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 3 \u043d\u0430 {subtype}", + "remote_rotate_from_side_4": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 4 \u043d\u0430 {subtype}", + "remote_rotate_from_side_5": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 5 \u043d\u0430 {subtype}", + "remote_rotate_from_side_6": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 6 \u043d\u0430 {subtype}", + "remote_turned_clockwise": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437\u0430 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u044e \u0441\u0442\u0440\u0456\u043b\u043a\u043e\u044e", + "remote_turned_counter_clockwise": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043f\u0440\u043e\u0442\u0438 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u0457 \u0441\u0442\u0440\u0456\u043b\u043a\u0438" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_clip_sensor": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 deCONZ CLIP", + "allow_deconz_groups": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0433\u0440\u0443\u043f\u0438 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f deCONZ", + "allow_new_devices": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u043d\u043e\u0432\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0456 \u0442\u0438\u043f\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 deCONZ", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f deCONZ" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/demo/translations/tr.json b/homeassistant/components/demo/translations/tr.json new file mode 100644 index 00000000000000..1ca389b0b979b9 --- /dev/null +++ b/homeassistant/components/demo/translations/tr.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "options_1": { + "data": { + "constant": "Sabit" + } + } + } + }, + "title": "Demo" +} \ No newline at end of file diff --git a/homeassistant/components/demo/translations/uk.json b/homeassistant/components/demo/translations/uk.json new file mode 100644 index 00000000000000..5ac1ac74708e18 --- /dev/null +++ b/homeassistant/components/demo/translations/uk.json @@ -0,0 +1,21 @@ +{ + "options": { + "step": { + "options_1": { + "data": { + "bool": "\u041b\u043e\u0433\u0456\u0447\u043d\u0438\u0439", + "constant": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u0430", + "int": "\u0427\u0438\u0441\u043b\u043e\u0432\u0438\u0439" + } + }, + "options_2": { + "data": { + "multi": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0434\u0435\u043a\u0456\u043b\u044c\u043a\u0430", + "select": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e", + "string": "\u0421\u0442\u0440\u043e\u043a\u043e\u0432\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f" + } + } + } + }, + "title": "\u0414\u0435\u043c\u043e" +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json index 5af7d3393e243b..f52e6303091ad1 100644 --- a/homeassistant/components/denonavr/translations/de.json +++ b/homeassistant/components/denonavr/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" + }, "step": { "select": { "data": { diff --git a/homeassistant/components/denonavr/translations/tr.json b/homeassistant/components/denonavr/translations/tr.json new file mode 100644 index 00000000000000..f618d3a3038474 --- /dev/null +++ b/homeassistant/components/denonavr/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flan\u0131lamad\u0131, l\u00fctfen tekrar deneyin, ana g\u00fc\u00e7 ve ethernet kablolar\u0131n\u0131n ba\u011flant\u0131s\u0131n\u0131 kesip yeniden ba\u011flamak yard\u0131mc\u0131 olabilir" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/uk.json b/homeassistant/components/denonavr/translations/uk.json new file mode 100644 index 00000000000000..efb4cb417779fd --- /dev/null +++ b/homeassistant/components/denonavr/translations/uk.json @@ -0,0 +1,48 @@ +{ + "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_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.", + "cannot_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437. \u042f\u043a\u0449\u043e \u0446\u0435 \u043d\u0435 \u0441\u043f\u0440\u0430\u0446\u044e\u0432\u0430\u043b\u043e, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043a\u0430\u0431\u0435\u043b\u044c Ethernet \u0456 \u043a\u0430\u0431\u0435\u043b\u044c \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f.", + "not_denonavr_manufacturer": "\u0426\u0435 \u043d\u0435 \u0440\u0435\u0441\u0438\u0432\u0435\u0440 Denon. \u0412\u0438\u0440\u043e\u0431\u043d\u0438\u043a \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454.", + "not_denonavr_missing": "\u041d\u0435\u043f\u043e\u0432\u043d\u0430 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u0434\u043b\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e." + }, + "error": { + "discovery_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u043d\u0430\u0439\u0442\u0438 \u0440\u0435\u0441\u0438\u0432\u0435\u0440 Denon." + }, + "flow_title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon: {name}", + "step": { + "confirm": { + "description": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u0441\u0438\u0432\u0435\u0440\u0430", + "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + }, + "select": { + "data": { + "select_host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043d\u043e\u0432\u0443, \u044f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0456\u043d\u0448\u0438\u0439 \u0440\u0435\u0441\u0438\u0432\u0435\u0440", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0438\u0432\u0435\u0440, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u042f\u043a\u0449\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0430, \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f", + "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_all_sources": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0432\u0441\u0456 \u0434\u0436\u0435\u0440\u0435\u043b\u0430", + "zone2": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438 2", + "zone3": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438 3" + }, + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", + "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/de.json b/homeassistant/components/device_tracker/translations/de.json index 651805dcb14b86..fe59183e67a808 100644 --- a/homeassistant/components/device_tracker/translations/de.json +++ b/homeassistant/components/device_tracker/translations/de.json @@ -1,8 +1,12 @@ { "device_automation": { "condition_type": { - "is_home": "{entity_name} ist Zuhause", - "is_not_home": "{entity_name} ist nicht zu Hause" + "is_home": "{entity_name} ist zuhause", + "is_not_home": "{entity_name} ist nicht zuhause" + }, + "trigger_type": { + "enters": "{entity_name} betritt einen Bereich", + "leaves": "{entity_name} verl\u00e4sst einen Bereich" } }, "state": { diff --git a/homeassistant/components/device_tracker/translations/uk.json b/homeassistant/components/device_tracker/translations/uk.json index f49c7acc0e3933..87945d2a19a8dd 100644 --- a/homeassistant/components/device_tracker/translations/uk.json +++ b/homeassistant/components/device_tracker/translations/uk.json @@ -1,8 +1,18 @@ { + "device_automation": { + "condition_type": { + "is_home": "{entity_name} \u0432\u0434\u043e\u043c\u0430", + "is_not_home": "{entity_name} \u043d\u0435 \u0432\u0434\u043e\u043c\u0430" + }, + "trigger_type": { + "enters": "{entity_name} \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0437\u043e\u043d\u0443", + "leaves": "{entity_name} \u043f\u043e\u043a\u0438\u0434\u0430\u0454 \u0437\u043e\u043d\u0443" + } + }, "state": { "_": { "home": "\u0412\u0434\u043e\u043c\u0430", - "not_home": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0439" + "not_home": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430" } }, "title": "\u0422\u0440\u0435\u043a\u0435\u0440 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" diff --git a/homeassistant/components/devolo_home_control/translations/de.json b/homeassistant/components/devolo_home_control/translations/de.json index 112daf582b3629..6cf7ed3c82115b 100644 --- a/homeassistant/components/devolo_home_control/translations/de.json +++ b/homeassistant/components/devolo_home_control/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "Diese Home Control Zentral wird bereits verwendet." + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { @@ -9,7 +12,7 @@ "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Passwort", - "username": "E-Mail-Adresse / devolo ID" + "username": "E-Mail / devolo ID" } } } diff --git a/homeassistant/components/devolo_home_control/translations/tr.json b/homeassistant/components/devolo_home_control/translations/tr.json new file mode 100644 index 00000000000000..4c6b158f6946b9 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta / devolo ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/uk.json b/homeassistant/components/devolo_home_control/translations/uk.json new file mode 100644 index 00000000000000..d230d1918f5e09 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "home_control_url": "Home Control URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "mydevolo_url": "mydevolo URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438 / devolo ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/de.json b/homeassistant/components/dexcom/translations/de.json index fadb459a3d35ca..d567dd6b611167 100644 --- a/homeassistant/components/dexcom/translations/de.json +++ b/homeassistant/components/dexcom/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Konto ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/dexcom/translations/fr.json b/homeassistant/components/dexcom/translations/fr.json index d10643a3c1eac7..095c769a1be4e2 100644 --- a/homeassistant/components/dexcom/translations/fr.json +++ b/homeassistant/components/dexcom/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion", diff --git a/homeassistant/components/dexcom/translations/tr.json b/homeassistant/components/dexcom/translations/tr.json index 80638d181b2e58..ec93dc078afb9c 100644 --- a/homeassistant/components/dexcom/translations/tr.json +++ b/homeassistant/components/dexcom/translations/tr.json @@ -2,6 +2,28 @@ "config": { "abort": { "already_configured": "Hesap zaten konfig\u00fcre edilmi\u015fi durumda" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "\u00d6l\u00e7\u00fc birimi" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/uk.json b/homeassistant/components/dexcom/translations/uk.json new file mode 100644 index 00000000000000..66727af90d19af --- /dev/null +++ b/homeassistant/components/dexcom/translations/uk.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "server": "\u0421\u0435\u0440\u0432\u0435\u0440", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "Dexcom" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/de.json b/homeassistant/components/dialogflow/translations/de.json index f1853107cc2374..2035b818b44d10 100644 --- a/homeassistant/components/dialogflow/translations/de.json +++ b/homeassistant/components/dialogflow/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an den Home Assistant zu senden, musst du [Webhook-Integration von Dialogflow]({dialogflow_url}) einrichten. \n\nF\u00fclle die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / json \n\nWeitere Informationen findest du in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/dialogflow/translations/tr.json b/homeassistant/components/dialogflow/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/uk.json b/homeassistant/components/dialogflow/translations/uk.json new file mode 100644 index 00000000000000..625d2db78dcb01 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f [Dialogflow]({dialogflow_url}). \n\n\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json \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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Dialogflow?", + "title": "Dialogflow" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/tr.json b/homeassistant/components/directv/translations/tr.json new file mode 100644 index 00000000000000..daca8f1ef6246b --- /dev/null +++ b/homeassistant/components/directv/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "ssdp_confirm": { + "description": "{name} kurmak istiyor musunuz?" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/uk.json b/homeassistant/components/directv/translations/uk.json new file mode 100644 index 00000000000000..5371f638e3d8a4 --- /dev/null +++ b/homeassistant/components/directv/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/de.json b/homeassistant/components/doorbird/translations/de.json index 62bb11d6a8cb0c..0d6bef7a63fb4f 100644 --- a/homeassistant/components/doorbird/translations/de.json +++ b/homeassistant/components/doorbird/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dieser DoorBird ist bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "link_local_address": "Lokale Linkadressen werden nicht unterst\u00fctzt", "not_doorbird_device": "Dieses Ger\u00e4t ist kein DoorBird" }, diff --git a/homeassistant/components/doorbird/translations/tr.json b/homeassistant/components/doorbird/translations/tr.json new file mode 100644 index 00000000000000..d7a1ca8a93a9f0 --- /dev/null +++ b/homeassistant/components/doorbird/translations/tr.json @@ -0,0 +1,22 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "name": "Cihaz ad\u0131", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/uk.json b/homeassistant/components/doorbird/translations/uk.json new file mode 100644 index 00000000000000..07bbdfacafe69e --- /dev/null +++ b/homeassistant/components/doorbird/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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.", + "link_local_address": "\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456 \u0430\u0434\u0440\u0435\u0441\u0438 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "not_doorbird_device": "\u0426\u0435 \u043d\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 DoorBird." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "DoorBird {name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0456\u0439 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u0443." + }, + "description": "\u0414\u043e\u0434\u0430\u0439\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u0443 \u043d\u0430\u0437\u0432\u0438 \u043f\u043e\u0434\u0456\u0439, \u044f\u043a\u0435 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u043b\u0456\u0434\u043a\u043e\u0432\u0443\u0432\u0430\u0442\u0438. \u041f\u0456\u0441\u043b\u044f \u0446\u044c\u043e\u0433\u043e, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a DoorBird, \u0449\u043e\u0431 \u043f\u0440\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0457\u0445 \u0434\u043e \u043f\u0435\u0432\u043d\u043e\u0457 \u043f\u043e\u0434\u0456\u0457. \u041f\u0440\u0438\u043a\u043b\u0430\u0434: somebody_pressed_the_button, motion. \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 \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: https://www.home-assistant.io/integrations/doorbird/#events." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/de.json b/homeassistant/components/dsmr/translations/de.json new file mode 100644 index 00000000000000..da1d200c2a2099 --- /dev/null +++ b/homeassistant/components/dsmr/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/fr.json b/homeassistant/components/dsmr/translations/fr.json index ea382532a71327..cb08a7865b33a4 100644 --- a/homeassistant/components/dsmr/translations/fr.json +++ b/homeassistant/components/dsmr/translations/fr.json @@ -7,5 +7,15 @@ "one": "", "other": "Autre" } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "Temps minimum entre les mises \u00e0 jour des entit\u00e9s" + }, + "title": "Options DSMR" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/tr.json b/homeassistant/components/dsmr/translations/tr.json index 94c31d0e1563d5..0857160dc51b34 100644 --- a/homeassistant/components/dsmr/translations/tr.json +++ b/homeassistant/components/dsmr/translations/tr.json @@ -1,4 +1,9 @@ { + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/dsmr/translations/uk.json b/homeassistant/components/dsmr/translations/uk.json new file mode 100644 index 00000000000000..9bca6b00c74eda --- /dev/null +++ b/homeassistant/components/dsmr/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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." + } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 DSMR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/tr.json b/homeassistant/components/dunehd/translations/tr.json new file mode 100644 index 00000000000000..0f8c17228fdd1f --- /dev/null +++ b/homeassistant/components/dunehd/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/uk.json b/homeassistant/components/dunehd/translations/uk.json new file mode 100644 index 00000000000000..d2f4eadbdcb7f9 --- /dev/null +++ b/homeassistant/components/dunehd/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Dune HD. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0432\u0438\u043d\u0438\u043a\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438 \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: https://www.home-assistant.io/integrations/dunehd \n\n \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0412\u0430\u0448 \u043f\u043b\u0435\u0454\u0440 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439.", + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/de.json b/homeassistant/components/eafm/translations/de.json new file mode 100644 index 00000000000000..da1d200c2a2099 --- /dev/null +++ b/homeassistant/components/eafm/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/tr.json b/homeassistant/components/eafm/translations/tr.json new file mode 100644 index 00000000000000..4ed0f406e57ce6 --- /dev/null +++ b/homeassistant/components/eafm/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "no_stations": "Ak\u0131\u015f izleme istasyonu bulunamad\u0131." + }, + "step": { + "user": { + "data": { + "station": "\u0130stasyon" + }, + "title": "Ak\u0131\u015f izleme istasyonunu takip edin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/uk.json b/homeassistant/components/eafm/translations/uk.json new file mode 100644 index 00000000000000..4f84eb92722948 --- /dev/null +++ b/homeassistant/components/eafm/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "no_stations": "\u0421\u0442\u0430\u043d\u0446\u0456\u0457 \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u043f\u043e\u0432\u0435\u043d\u0435\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456." + }, + "step": { + "user": { + "data": { + "station": "\u0421\u0442\u0430\u043d\u0446\u0456\u044f" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0456\u044e \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443", + "title": "\u0421\u0442\u0430\u043d\u0446\u0456\u0457 \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u043f\u043e\u0432\u0435\u043d\u0435\u0439" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/de.json b/homeassistant/components/ecobee/translations/de.json index bc65fddebdd047..0c89a696b2c412 100644 --- a/homeassistant/components/ecobee/translations/de.json +++ b/homeassistant/components/ecobee/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits eingerichtet. Es ist nur eine Konfiguration m\u00f6glich." + }, "error": { "pin_request_failed": "Fehler beim Anfordern der PIN von ecobee; Bitte \u00fcberpr\u00fcfe, ob der API-Schl\u00fcssel korrekt ist.", "token_request_failed": "Fehler beim Anfordern eines Token von ecobee; Bitte versuche es erneut." diff --git a/homeassistant/components/ecobee/translations/tr.json b/homeassistant/components/ecobee/translations/tr.json new file mode 100644 index 00000000000000..23ece38682d11b --- /dev/null +++ b/homeassistant/components/ecobee/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/uk.json b/homeassistant/components/ecobee/translations/uk.json new file mode 100644 index 00000000000000..7cf7df534296f6 --- /dev/null +++ b/homeassistant/components/ecobee/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "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.", + "token_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 \u0442\u043e\u043a\u0435\u043d\u0456\u0432 \u0443 ecobee; \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." + }, + "step": { + "authorize": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e https://www.ecobee.com/consumerportal/index.html \u0456 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e PIN-\u043a\u043e\u0434\u0443: \n\n{pin}\n\n\u041f\u043e\u0442\u0456\u043c \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438.", + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u0430 \u043d\u0430 ecobee.com" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u0432\u0456\u0434 ecobee.com.", + "title": "ecobee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/ca.json b/homeassistant/components/econet/translations/ca.json new file mode 100644 index 00000000000000..c53914f8cb990e --- /dev/null +++ b/homeassistant/components/econet/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + }, + "title": "Configuraci\u00f3 del compte Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/en.json b/homeassistant/components/econet/translations/en.json index 4061c094c1ff83..ad499b0e37c739 100644 --- a/homeassistant/components/econet/translations/en.json +++ b/homeassistant/components/econet/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Device is already configured", "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication" }, diff --git a/homeassistant/components/econet/translations/es.json b/homeassistant/components/econet/translations/es.json new file mode 100644 index 00000000000000..ac69f8f7be1f8d --- /dev/null +++ b/homeassistant/components/econet/translations/es.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "email": "Correo electr\u00f3nico", + "password": "Contrase\u00f1a" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/et.json b/homeassistant/components/econet/translations/et.json new file mode 100644 index 00000000000000..349a4d21111587 --- /dev/null +++ b/homeassistant/components/econet/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine" + }, + "step": { + "user": { + "data": { + "email": "E-posti aadress", + "password": "Salas\u00f5na" + }, + "title": "Seadista Rheem EcoNeti konto" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/it.json b/homeassistant/components/econet/translations/it.json new file mode 100644 index 00000000000000..3074c72b083953 --- /dev/null +++ b/homeassistant/components/econet/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Password" + }, + "title": "Imposta account Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/no.json b/homeassistant/components/econet/translations/no.json new file mode 100644 index 00000000000000..f54cedffda84ed --- /dev/null +++ b/homeassistant/components/econet/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "email": "E-post", + "password": "Passord" + }, + "title": "Konfigurer Rheem EcoNet-konto" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/pl.json b/homeassistant/components/econet/translations/pl.json new file mode 100644 index 00000000000000..e5d74de590d54d --- /dev/null +++ b/homeassistant/components/econet/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "user": { + "data": { + "email": "Adres e-mail", + "password": "Has\u0142o" + }, + "title": "Konfiguracja konta Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/ru.json b/homeassistant/components/econet/translations/ru.json new file mode 100644 index 00000000000000..109ded8db998eb --- /dev/null +++ b/homeassistant/components/econet/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "title": "Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/tr.json b/homeassistant/components/econet/translations/tr.json new file mode 100644 index 00000000000000..237a87d02685eb --- /dev/null +++ b/homeassistant/components/econet/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "\u015eifre" + }, + "title": "Rheem EcoNet Hesab\u0131n\u0131 Kur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/zh-Hant.json b/homeassistant/components/econet/translations/zh-Hant.json new file mode 100644 index 00000000000000..50824c198145a4 --- /dev/null +++ b/homeassistant/components/econet/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + }, + "title": "\u8a2d\u5b9a Rheem EcoNet \u5e33\u865f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/de.json b/homeassistant/components/elgato/translations/de.json index 7497460445337c..1df8f91ecd6a2e 100644 --- a/homeassistant/components/elgato/translations/de.json +++ b/homeassistant/components/elgato/translations/de.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "Dieses Elgato Key Light-Ger\u00e4t ist bereits konfiguriert.", - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "Elgato Key Light: {serial_number}", "step": { diff --git a/homeassistant/components/elgato/translations/tr.json b/homeassistant/components/elgato/translations/tr.json new file mode 100644 index 00000000000000..b2d1753fd68b32 --- /dev/null +++ b/homeassistant/components/elgato/translations/tr.json @@ -0,0 +1,19 @@ +{ + "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" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/uk.json b/homeassistant/components/elgato/translations/uk.json new file mode 100644 index 00000000000000..978ff1a310088f --- /dev/null +++ b/homeassistant/components/elgato/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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.", + "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" + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Elgato Key Light \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Home Assistant." + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Elgato Key Light \u0437 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Elgato Key Light" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/de.json b/homeassistant/components/elkm1/translations/de.json index 8c562a7502659c..8157a061d82d5f 100644 --- a/homeassistant/components/elkm1/translations/de.json +++ b/homeassistant/components/elkm1/translations/de.json @@ -5,7 +5,7 @@ "already_configured": "Ein ElkM1 mit diesem Pr\u00e4fix ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/elkm1/translations/tr.json b/homeassistant/components/elkm1/translations/tr.json new file mode 100644 index 00000000000000..9259220985bb1b --- /dev/null +++ b/homeassistant/components/elkm1/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/uk.json b/homeassistant/components/elkm1/translations/uk.json new file mode 100644 index 00000000000000..a8e711a4590822 --- /dev/null +++ b/homeassistant/components/elkm1/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "address_already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 \u0446\u0456\u0454\u044e \u0430\u0434\u0440\u0435\u0441\u043e\u044e \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 \u0446\u0438\u043c \u043f\u0440\u0435\u0444\u0456\u043a\u0441\u043e\u043c \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430, \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e \u043f\u043e\u0441\u043b\u0456\u0434\u043e\u0432\u043d\u0438\u0439 \u043f\u043e\u0440\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "prefix": "\u0423\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0434\u0438\u043d ElkM1)", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "temperature_unit": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0420\u044f\u0434\u043e\u043a \u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'addres[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0456\u0432 'secure' \u0456 'non-secure' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '192.168.1.1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 2101 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'non-secure' \u0456 2601 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'secure'. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'serial' \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 115200.", + "title": "Elk-M1 Control" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/de.json b/homeassistant/components/emulated_roku/translations/de.json index a0bfd9f83aa440..39c8da5197fa1f 100644 --- a/homeassistant/components/emulated_roku/translations/de.json +++ b/homeassistant/components/emulated_roku/translations/de.json @@ -8,7 +8,7 @@ "data": { "advertise_ip": "IP Adresse annoncieren", "advertise_port": "Port annoncieren", - "host_ip": "Host-IP", + "host_ip": "Host-IP-Adresse", "listen_port": "Listen-Port", "name": "Name", "upnp_bind_multicast": "Multicast binden (True/False)" diff --git a/homeassistant/components/emulated_roku/translations/tr.json b/homeassistant/components/emulated_roku/translations/tr.json new file mode 100644 index 00000000000000..5307276a71d3a3 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/uk.json b/homeassistant/components/emulated_roku/translations/uk.json new file mode 100644 index 00000000000000..a299f3a5ebc6d4 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "advertise_ip": "\u041e\u0433\u043e\u043b\u043e\u0448\u0443\u0432\u0430\u0442\u0438 IP", + "advertise_port": "\u041e\u0433\u043e\u043b\u043e\u0448\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0440\u0442", + "host_ip": "\u0425\u043e\u0441\u0442", + "listen_port": "\u041f\u043e\u0440\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "upnp_bind_multicast": "\u041f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 multicast (True / False)" + }, + "title": "EmulatedRoku" + } + } + }, + "title": "Emulated Roku" +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/tr.json b/homeassistant/components/enocean/translations/tr.json new file mode 100644 index 00000000000000..b4e6be555ff467 --- /dev/null +++ b/homeassistant/components/enocean/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "invalid_dongle_path": "Ge\u00e7ersiz dongle yolu", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_dongle_path": "Bu yol i\u00e7in ge\u00e7erli bir dongle bulunamad\u0131" + }, + "step": { + "detect": { + "data": { + "path": "USB dongle yolu" + } + }, + "manual": { + "data": { + "path": "USB dongle yolu" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/uk.json b/homeassistant/components/enocean/translations/uk.json new file mode 100644 index 00000000000000..5c3e2d6eb6ec97 --- /dev/null +++ b/homeassistant/components/enocean/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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." + }, + "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." + }, + "step": { + "detect": { + "data": { + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "ENOcean" + }, + "manual": { + "data": { + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "ENOcean" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/epson/translations/de.json b/homeassistant/components/epson/translations/de.json index c03615a39ff5d5..a91e3831cdb39c 100644 --- a/homeassistant/components/epson/translations/de.json +++ b/homeassistant/components/epson/translations/de.json @@ -1,12 +1,14 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { "data": { - "name": "Name" + "host": "Host", + "name": "Name", + "port": "Port" } } } diff --git a/homeassistant/components/epson/translations/tr.json b/homeassistant/components/epson/translations/tr.json index aafc2e2b30345a..9ffd77fc50f6c8 100644 --- a/homeassistant/components/epson/translations/tr.json +++ b/homeassistant/components/epson/translations/tr.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/epson/translations/uk.json b/homeassistant/components/epson/translations/uk.json new file mode 100644 index 00000000000000..65566a8f4aa5cb --- /dev/null +++ b/homeassistant/components/epson/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/de.json b/homeassistant/components/esphome/translations/de.json index 826574cb7e089d..fdaea452c4560a 100644 --- a/homeassistant/components/esphome/translations/de.json +++ b/homeassistant/components/esphome/translations/de.json @@ -2,10 +2,11 @@ "config": { "abort": { "already_configured": "ESP ist bereits konfiguriert", - "already_in_progress": "Die ESP-Konfiguration wird bereits ausgef\u00fchrt" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" }, "error": { "connection_error": "Keine Verbindung zum ESP m\u00f6glich. Achte darauf, dass deine YAML-Datei eine Zeile 'api:' enth\u00e4lt.", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "resolve_error": "Adresse des ESP kann nicht aufgel\u00f6st werden. Wenn dieser Fehler weiterhin besteht, lege eine statische IP-Adresse fest: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", diff --git a/homeassistant/components/esphome/translations/pt.json b/homeassistant/components/esphome/translations/pt.json index 6ff4d786447909..60eeaa3f4b2103 100644 --- a/homeassistant/components/esphome/translations/pt.json +++ b/homeassistant/components/esphome/translations/pt.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "O ESP j\u00e1 est\u00e1 configurado", + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer" }, "error": { diff --git a/homeassistant/components/esphome/translations/tr.json b/homeassistant/components/esphome/translations/tr.json index 15028c4fe65592..81f85d4980bb0b 100644 --- a/homeassistant/components/esphome/translations/tr.json +++ b/homeassistant/components/esphome/translations/tr.json @@ -1,8 +1,27 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, "step": { + "authenticate": { + "data": { + "password": "Parola" + }, + "description": "L\u00fctfen yap\u0131land\u0131rman\u0131zda {name} i\u00e7in belirledi\u011finiz parolay\u0131 girin." + }, "discovery_confirm": { "title": "Ke\u015ffedilen ESPHome d\u00fc\u011f\u00fcm\u00fc" + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } } } } diff --git a/homeassistant/components/esphome/translations/uk.json b/homeassistant/components/esphome/translations/uk.json index d17ec64e5480e5..4643c19cf5ddeb 100644 --- a/homeassistant/components/esphome/translations/uk.json +++ b/homeassistant/components/esphome/translations/uk.json @@ -1,22 +1,25 @@ { "config": { "abort": { - "already_configured": "ESP \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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." }, "error": { "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u0454\u0442\u044c\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e ESP. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0444\u0430\u0439\u043b YAML \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u0440\u044f\u0434\u043e\u043a \"api:\".", - "resolve_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 ESP. \u042f\u043a\u0449\u043e \u0446\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043d\u0435 \u0437\u043d\u0438\u043a\u0430\u0454, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u0443 IP-\u0430\u0434\u0440\u0435\u0441\u0443: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "resolve_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 ESP. \u042f\u043a\u0449\u043e \u0446\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u044e\u0454\u0442\u044c\u0441\u044f, \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u0443 IP-\u0430\u0434\u0440\u0435\u0441\u0443: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips." }, + "flow_title": "ESPHome: {name}", "step": { "authenticate": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0443 \u0441\u0432\u043e\u0457\u0439 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457." + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u0432 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 {name}." }, "discovery_confirm": { "description": "\u0414\u043e\u0434\u0430\u0442\u0438 ESPHome \u0432\u0443\u0437\u043e\u043b {name} \u0443 Home Assistant?", - "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0432\u0443\u0437\u043e\u043b ESPHome" + "title": "ESPHome" }, "user": { "data": { diff --git a/homeassistant/components/fan/translations/tr.json b/homeassistant/components/fan/translations/tr.json index 4ffc57601bdd53..52a07c35d8326b 100644 --- a/homeassistant/components/fan/translations/tr.json +++ b/homeassistant/components/fan/translations/tr.json @@ -1,4 +1,14 @@ { + "device_automation": { + "action_type": { + "turn_off": "{entity_name} kapat", + "turn_on": "{entity_name} a\u00e7\u0131n" + }, + "trigger_type": { + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "state": { "_": { "off": "Kapal\u0131", diff --git a/homeassistant/components/fan/translations/uk.json b/homeassistant/components/fan/translations/uk.json index 3fd103cd244c56..0e0bafcbfc4f2b 100644 --- a/homeassistant/components/fan/translations/uk.json +++ b/homeassistant/components/fan/translations/uk.json @@ -1,8 +1,16 @@ { "device_automation": { + "action_type": { + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { - "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", - "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + "turned_off": "{entity_name} \u0432\u0438\u043c\u0438\u043a\u0430\u0454\u0442\u044c\u0441\u044f", + "turned_on": "{entity_name} \u0432\u043c\u0438\u043a\u0430\u0454\u0442\u044c\u0441\u044f" } }, "state": { diff --git a/homeassistant/components/fireservicerota/translations/fr.json b/homeassistant/components/fireservicerota/translations/fr.json new file mode 100644 index 00000000000000..a8803f63fca6e5 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/fr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte \u00e0 d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + }, + "create_entry": { + "default": "Autentification r\u00e9ussie" + }, + "error": { + "invalid_auth": "Autentification invalide" + }, + "step": { + "reauth": { + "data": { + "password": "Mot de passe" + } + }, + "user": { + "data": { + "password": "Mot de passe", + "url": "Site web", + "username": "Utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/tr.json b/homeassistant/components/fireservicerota/translations/tr.json index a2d2cab3b7469f..f54d10f6cbf71a 100644 --- a/homeassistant/components/fireservicerota/translations/tr.json +++ b/homeassistant/components/fireservicerota/translations/tr.json @@ -1,5 +1,15 @@ { "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, "step": { "reauth": { "data": { diff --git a/homeassistant/components/fireservicerota/translations/uk.json b/homeassistant/components/fireservicerota/translations/uk.json new file mode 100644 index 00000000000000..2d3bf8c596e037 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0422\u043e\u043a\u0435\u043d\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0456, \u0443\u0432\u0456\u0439\u0434\u0456\u0442\u044c, \u0449\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0457\u0445 \u0437\u0430\u043d\u043e\u0432\u043e." + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "url": "\u0412\u0435\u0431-\u0441\u0430\u0439\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/tr.json b/homeassistant/components/firmata/translations/tr.json new file mode 100644 index 00000000000000..b7d038a229b0df --- /dev/null +++ b/homeassistant/components/firmata/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/uk.json b/homeassistant/components/firmata/translations/uk.json new file mode 100644 index 00000000000000..41b670fbb184cf --- /dev/null +++ b/homeassistant/components/firmata/translations/uk.json @@ -0,0 +1,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" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/de.json b/homeassistant/components/flick_electric/translations/de.json index ed0ef205ff031a..3e3568c45f8e32 100644 --- a/homeassistant/components/flick_electric/translations/de.json +++ b/homeassistant/components/flick_electric/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Dieses Konto ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/flick_electric/translations/tr.json b/homeassistant/components/flick_electric/translations/tr.json new file mode 100644 index 00000000000000..a83e1936fb4a15 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/uk.json b/homeassistant/components/flick_electric/translations/uk.json new file mode 100644 index 00000000000000..4d72844bc74fd0 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "client_id": "ID \u043a\u043b\u0456\u0454\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0456\u0454\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Flick Electric" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/de.json b/homeassistant/components/flo/translations/de.json index 382156757010d5..625c7372347a61 100644 --- a/homeassistant/components/flo/translations/de.json +++ b/homeassistant/components/flo/translations/de.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "host": "Host", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/flo/translations/tr.json b/homeassistant/components/flo/translations/tr.json new file mode 100644 index 00000000000000..40c9c39b967721 --- /dev/null +++ b/homeassistant/components/flo/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/uk.json b/homeassistant/components/flo/translations/uk.json new file mode 100644 index 00000000000000..2df11f744559db --- /dev/null +++ b/homeassistant/components/flo/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/de.json b/homeassistant/components/flume/translations/de.json index 692c38350a8303..c38a5593ac7c12 100644 --- a/homeassistant/components/flume/translations/de.json +++ b/homeassistant/components/flume/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieses Konto ist bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/flume/translations/tr.json b/homeassistant/components/flume/translations/tr.json new file mode 100644 index 00000000000000..a83e1936fb4a15 --- /dev/null +++ b/homeassistant/components/flume/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/uk.json b/homeassistant/components/flume/translations/uk.json new file mode 100644 index 00000000000000..53fb4f3d6d7e1c --- /dev/null +++ b/homeassistant/components/flume/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "client_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043a\u043b\u0456\u0454\u043d\u0442\u0430", + "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0456\u0454\u043d\u0442\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0429\u043e\u0431 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e API Flume, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 'ID \u043a\u043b\u0456\u0454\u043d\u0442\u0430' \u0456 '\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0456\u0454\u043d\u0442\u0430' \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e https://portal.flumetech.com/settings#token.", + "title": "Flume" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/de.json b/homeassistant/components/flunearyou/translations/de.json index cd2934170c9c06..1c94931f405e17 100644 --- a/homeassistant/components/flunearyou/translations/de.json +++ b/homeassistant/components/flunearyou/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten sind bereits registriert." + "already_configured": "Standort ist bereits konfiguriert" }, "error": { "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/flunearyou/translations/tr.json b/homeassistant/components/flunearyou/translations/tr.json new file mode 100644 index 00000000000000..6e749e3c8270fa --- /dev/null +++ b/homeassistant/components/flunearyou/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/uk.json b/homeassistant/components/flunearyou/translations/uk.json new file mode 100644 index 00000000000000..354a04d8e7afff --- /dev/null +++ b/homeassistant/components/flunearyou/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" + }, + "description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0445 \u0456 CDC \u0437\u0432\u0456\u0442\u0456\u0432 \u0437\u0430 \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u043c\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c\u0438.", + "title": "Flu Near You" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/de.json b/homeassistant/components/forked_daapd/translations/de.json index a3cdc53c52a102..e90ffc71f90244 100644 --- a/homeassistant/components/forked_daapd/translations/de.json +++ b/homeassistant/components/forked_daapd/translations/de.json @@ -5,7 +5,7 @@ }, "error": { "unknown_error": "Unbekannter Fehler", - "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte \u00fcberpr\u00fcfen Sie Host und Port.", + "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte Host und Port pr\u00fcfen.", "wrong_password": "Ung\u00fcltiges Passwort", "wrong_server_type": "F\u00fcr die forked-daapd Integration ist ein forked-daapd Server mit der Version > = 27.0 erforderlich." }, diff --git a/homeassistant/components/forked_daapd/translations/tr.json b/homeassistant/components/forked_daapd/translations/tr.json new file mode 100644 index 00000000000000..cf354c5c87f54c --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "unknown_error": "Beklenmeyen hata", + "wrong_password": "Yanl\u0131\u015f parola." + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "name": "Kolay ad", + "password": "API parolas\u0131 (parola yoksa bo\u015f b\u0131rak\u0131n)", + "port": "API ba\u011flant\u0131 noktas\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/uk.json b/homeassistant/components/forked_daapd/translations/uk.json new file mode 100644 index 00000000000000..19caf9b5bd0db4 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/uk.json @@ -0,0 +1,42 @@ +{ + "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.", + "not_forked_daapd": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd." + }, + "error": { + "forbidden": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 forked-daapd.", + "unknown_error": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "websocket_not_enabled": "\u0412\u0435\u0431-\u0441\u043e\u043a\u0435\u0442 forked-daapd \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439.", + "wrong_host_or_port": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u0445\u043e\u0441\u0442\u0430.", + "wrong_password": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", + "wrong_server_type": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd \u0432\u0435\u0440\u0441\u0456\u0457 27.0 \u0430\u0431\u043e \u0432\u0438\u0449\u0435." + }, + "flow_title": "\u0421\u0435\u0440\u0432\u0435\u0440 forked-daapd: {name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c API (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0443 \u0432\u0430\u0441 \u043d\u0435\u043c\u0430\u0454 \u043f\u0430\u0440\u043e\u043b\u044f)", + "port": "\u041f\u043e\u0440\u0442 API" + }, + "title": "forked-daapd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "\u041f\u043e\u0440\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043a\u0430\u043d\u0430\u043b\u043e\u043c librespot-java (\u044f\u043a\u0449\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f)", + "max_playlists": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442\u0456\u0432, \u0449\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u044f\u043a \u0434\u0436\u0435\u0440\u0435\u043b\u0430", + "tts_pause_time": "\u0427\u0430\u0441 \u043f\u0430\u0443\u0437\u0438 \u0434\u043e \u0456 \u043f\u0456\u0441\u043b\u044f TTS (\u0441\u0435\u043a.)", + "tts_volume": "\u0413\u0443\u0447\u043d\u0456\u0441\u0442\u044c TTS (\u0447\u0438\u0441\u043b\u043e \u0432 \u0434\u0456\u0430\u043f\u0430\u0437\u043e\u043d\u0456 \u0432\u0456\u0434 0 \u0434\u043e 1)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 forked-daapd.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/af.json b/homeassistant/components/foscam/translations/af.json new file mode 100644 index 00000000000000..4a9930dd95d355 --- /dev/null +++ b/homeassistant/components/foscam/translations/af.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ca.json b/homeassistant/components/foscam/translations/ca.json new file mode 100644 index 00000000000000..5a6c84f400e0bc --- /dev/null +++ b/homeassistant/components/foscam/translations/ca.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "port": "Port", + "stream": "Flux de v\u00eddeo", + "username": "Nom d'usuari" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/cs.json b/homeassistant/components/foscam/translations/cs.json new file mode 100644 index 00000000000000..b6f3c40abf6897 --- /dev/null +++ b/homeassistant/components/foscam/translations/cs.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json new file mode 100644 index 00000000000000..603be1847cc6b4 --- /dev/null +++ b/homeassistant/components/foscam/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/en.json b/homeassistant/components/foscam/translations/en.json index 521a22076dd3e5..3d1454a4ebd6c7 100644 --- a/homeassistant/components/foscam/translations/en.json +++ b/homeassistant/components/foscam/translations/en.json @@ -1,24 +1,24 @@ { - "config": { - "abort": { - "already_configured": "Device is already configured" - }, - "error": { - "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "step": { - "user": { - "data": { - "host": "Host", - "password": "Password", - "port": "Port", - "stream": "Stream", - "username": "Username" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "stream": "Stream", + "username": "Username" + } + } } - } - } - }, - "title": "Foscam" -} + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/es.json b/homeassistant/components/foscam/translations/es.json new file mode 100644 index 00000000000000..27f7ac36489b36 --- /dev/null +++ b/homeassistant/components/foscam/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Contrase\u00f1a", + "port": "Puerto", + "stream": "Stream", + "username": "Usuario" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/et.json b/homeassistant/components/foscam/translations/et.json new file mode 100644 index 00000000000000..b20a33aec1d974 --- /dev/null +++ b/homeassistant/components/foscam/translations/et.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "port": "Port", + "stream": "Voog", + "username": "Kasutajanimi" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/fr.json b/homeassistant/components/foscam/translations/fr.json new file mode 100644 index 00000000000000..9af8115c305cc1 --- /dev/null +++ b/homeassistant/components/foscam/translations/fr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Echec de connection", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "port": "Port", + "stream": "Flux", + "username": "Nom d'utilisateur" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/it.json b/homeassistant/components/foscam/translations/it.json new file mode 100644 index 00000000000000..0562012b1fab0b --- /dev/null +++ b/homeassistant/components/foscam/translations/it.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Porta", + "stream": "Flusso", + "username": "Nome utente" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/lb.json b/homeassistant/components/foscam/translations/lb.json new file mode 100644 index 00000000000000..123b3f4be76d2f --- /dev/null +++ b/homeassistant/components/foscam/translations/lb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwuert", + "port": "Port", + "stream": "Stream", + "username": "Benotzernumm" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/no.json b/homeassistant/components/foscam/translations/no.json new file mode 100644 index 00000000000000..5e1b494c88a1d1 --- /dev/null +++ b/homeassistant/components/foscam/translations/no.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "port": "Port", + "stream": "Str\u00f8m", + "username": "Brukernavn" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/pl.json b/homeassistant/components/foscam/translations/pl.json new file mode 100644 index 00000000000000..ef0bcda2b3ac96 --- /dev/null +++ b/homeassistant/components/foscam/translations/pl.json @@ -0,0 +1,24 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "stream": "Strumie\u0144", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/pt.json b/homeassistant/components/foscam/translations/pt.json new file mode 100644 index 00000000000000..b8a454fbaba9a4 --- /dev/null +++ b/homeassistant/components/foscam/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json new file mode 100644 index 00000000000000..ad8b7961ca36d7 --- /dev/null +++ b/homeassistant/components/foscam/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." + }, + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "stream": "\u041f\u043e\u0442\u043e\u043a", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/tr.json b/homeassistant/components/foscam/translations/tr.json new file mode 100644 index 00000000000000..b3e964ae08eda2 --- /dev/null +++ b/homeassistant/components/foscam/translations/tr.json @@ -0,0 +1,24 @@ +{ + "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", + "unknown": "Beklenmeyen Hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "\u015eifre", + "port": "Port", + "stream": "Ak\u0131\u015f", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/zh-Hant.json b/homeassistant/components/foscam/translations/zh-Hant.json new file mode 100644 index 00000000000000..2cc6303c17a8bb --- /dev/null +++ b/homeassistant/components/foscam/translations/zh-Hant.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "stream": "\u4e32\u6d41", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/de.json b/homeassistant/components/freebox/translations/de.json index c21e3c6b67fe70..738b9d48f3cdc2 100644 --- a/homeassistant/components/freebox/translations/de.json +++ b/homeassistant/components/freebox/translations/de.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Host bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "register_failed": "Registrieren fehlgeschlagen, bitte versuche es erneut", - "unknown": "Unbekannter Fehler: Bitte versuchen Sie es sp\u00e4ter erneut" + "unknown": "Unerwarteter Fehler" }, "step": { "link": { diff --git a/homeassistant/components/freebox/translations/tr.json b/homeassistant/components/freebox/translations/tr.json new file mode 100644 index 00000000000000..b675d38057dc64 --- /dev/null +++ b/homeassistant/components/freebox/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/uk.json b/homeassistant/components/freebox/translations/uk.json new file mode 100644 index 00000000000000..8676c9164a1902 --- /dev/null +++ b/homeassistant/components/freebox/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "register_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "link": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c '\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438', \u043f\u043e\u0442\u0456\u043c \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u0437\u0456 \u0441\u0442\u0440\u0456\u043b\u043a\u043e\u044e \u043d\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0456, \u0449\u043e\u0431 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438 Freebox \u0432 Home Assistant. \n\n![\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043d\u0430 \u0440\u043e\u0443\u0442\u0435\u0440\u0456] (/ static / images / config_freebox.png)", + "title": "Freebox" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index 8b0122dbe183b6..f8550b5bc3216c 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -4,7 +4,8 @@ "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "no_devices_found": "No s'han trobat dispositius a la xarxa", - "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home." + "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" @@ -18,6 +19,13 @@ }, "description": "Vols configurar {name}?" }, + "reauth_confirm": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Actualitza la informaci\u00f3 d'inici de sessi\u00f3 de {name}." + }, "user": { "data": { "host": "Amfitri\u00f3", diff --git a/homeassistant/components/fritzbox/translations/cs.json b/homeassistant/components/fritzbox/translations/cs.json index 67ff5db7f9952c..b3b41afe3833c1 100644 --- a/homeassistant/components/fritzbox/translations/cs.json +++ b/homeassistant/components/fritzbox/translations/cs.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", - "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" @@ -17,6 +18,12 @@ }, "description": "Chcete nastavit {name}?" }, + "reauth_confirm": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + }, "user": { "data": { "host": "Hostitel", diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 19ca2e809036cb..9b76ad19ff4fe2 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -1,10 +1,14 @@ { "config": { "abort": { - "already_configured": "Diese AVM FRITZ! Box ist bereits konfiguriert.", - "already_in_progress": "Die Konfiguration der AVM FRITZ! Box ist bereits in Bearbeitung.", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", "not_supported": "Verbunden mit AVM FRITZ! Box, kann jedoch keine Smart Home-Ger\u00e4te steuern." }, + "error": { + "invalid_auth": "Ung\u00fcltige Zugangsdaten" + }, "flow_title": "AVM FRITZ! Box: {name}", "step": { "confirm": { @@ -12,7 +16,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "M\u00f6chten Sie {name} einrichten?" + "description": "M\u00f6chtest du {name} einrichten?" }, "user": { "data": { @@ -20,7 +24,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Geben Sie Ihre AVM FRITZ! Box-Informationen ein." + "description": "Gib deine AVM FRITZ! Box-Informationen ein." } } } diff --git a/homeassistant/components/fritzbox/translations/en.json b/homeassistant/components/fritzbox/translations/en.json index 1f22bc30252cbe..61ca1e957bb2b9 100644 --- a/homeassistant/components/fritzbox/translations/en.json +++ b/homeassistant/components/fritzbox/translations/en.json @@ -4,7 +4,8 @@ "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "no_devices_found": "No devices found on the network", - "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices." + "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices.", + "reauth_successful": "Re-authentication was successful" }, "error": { "invalid_auth": "Invalid authentication" @@ -18,6 +19,13 @@ }, "description": "Do you want to set up {name}?" }, + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Username" + }, + "description": "Update your login information for {name}." + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritzbox/translations/et.json b/homeassistant/components/fritzbox/translations/et.json index 702488bce0c8a1..5ee2dc801f4610 100644 --- a/homeassistant/components/fritzbox/translations/et.json +++ b/homeassistant/components/fritzbox/translations/et.json @@ -4,7 +4,8 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "no_devices_found": "V\u00f5rgust ei leitud seadmeid", - "not_supported": "\u00dchendatud AVM FRITZ!Boxiga! kuid see ei saa juhtida Smart Home seadmeid." + "not_supported": "\u00dchendatud AVM FRITZ!Boxiga! kuid see ei saa juhtida Smart Home seadmeid.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "invalid_auth": "Tuvastamise viga" @@ -18,6 +19,13 @@ }, "description": "Kas soovid seadistada {name}?" }, + "reauth_confirm": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "V\u00e4rskenda konto {name} sisselogimisteavet." + }, "user": { "data": { "host": "", diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index ab44ae138631ad..a420b3f6de767a 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -4,7 +4,8 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "no_devices_found": "Nessun dispositivo trovato sulla rete", - "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home." + "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home.", + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { "invalid_auth": "Autenticazione non valida" @@ -18,6 +19,13 @@ }, "description": "Vuoi impostare {name}?" }, + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Aggiorna le tue informazioni di accesso per {name}." + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index 024e25741a7a1a..bd64b428bdf99f 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -4,7 +4,8 @@ "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", - "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter." + "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { "invalid_auth": "Ugyldig godkjenning" @@ -18,6 +19,13 @@ }, "description": "Vil du sette opp {name} ?" }, + "reauth_confirm": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Oppdater p\u00e5loggingsinformasjonen for {name} ." + }, "user": { "data": { "host": "Vert", diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json index fc1623101895e6..dc05e431832259 100644 --- a/homeassistant/components/fritzbox/translations/pl.json +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -4,7 +4,8 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", - "not_supported": "Po\u0142\u0105czony z AVM FRITZ!Box, ale nie jest w stanie kontrolowa\u0107 urz\u0105dze\u0144 Smart Home" + "not_supported": "Po\u0142\u0105czony z AVM FRITZ!Box, ale nie jest w stanie kontrolowa\u0107 urz\u0105dze\u0144 Smart Home", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "invalid_auth": "Niepoprawne uwierzytelnienie" @@ -18,6 +19,13 @@ }, "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, + "reauth_confirm": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Zaktualizuj dane logowania dla {name}" + }, "user": { "data": { "host": "Nazwa hosta lub adres IP", diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 322f677c2afacf..50146b490ba818 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -4,7 +4,8 @@ "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.", "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_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e." + "not_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e.", + "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": { "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." @@ -18,6 +19,13 @@ }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0434\u043b\u044f {name}." + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/fritzbox/translations/tr.json b/homeassistant/components/fritzbox/translations/tr.json new file mode 100644 index 00000000000000..746fe594e19902 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "confirm": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "{name} kurmak istiyor musunuz?" + }, + "reauth_confirm": { + "data": { + "password": "\u015eifre", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Giri\u015f bilgilerinizi {name} i\u00e7in g\u00fcncelleyin." + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/uk.json b/homeassistant/components/fritzbox/translations/uk.json new file mode 100644 index 00000000000000..5a2d8a1c35e06d --- /dev/null +++ b/homeassistant/components/fritzbox/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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_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.", + "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.", + "not_supported": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AVM FRITZ! Box \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e, \u0430\u043b\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044f\u043c\u0438 Smart Home \u043d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 AVM FRITZ! Box." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/zh-Hant.json b/homeassistant/components/fritzbox/translations/zh-Hant.json index 7b85df577ef148..71a74785267681 100644 --- a/homeassistant/components/fritzbox/translations/zh-Hant.json +++ b/homeassistant/components/fritzbox/translations/zh-Hant.json @@ -4,7 +4,8 @@ "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u88dd\u7f6e\u3002" + "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u88dd\u7f6e\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" @@ -18,6 +19,13 @@ }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, + "reauth_confirm": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u66f4\u65b0 {name} \u767b\u5165\u8cc7\u8a0a\u3002" + }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ca.json b/homeassistant/components/fritzbox_callmonitor/translations/ca.json new file mode 100644 index 00000000000000..808b642f4ff680 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/ca.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "insufficient_permissions": "L'usuari no t\u00e9 permisos suficients per accedir a la configuraci\u00f3 d'AVM FRITZ!Box i les seves agendes.", + "no_devices_found": "No s'han trobat dispositius a la xarxa" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "flow_title": "Sensor de trucades d'AVM FRITZ!Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Agenda" + } + }, + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "port": "Port", + "username": "Nom d'usuari" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "El format dels prefixos no \u00e9s correcte, comprova'l." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefixos (llista separada per comes)" + }, + "title": "Configuraci\u00f3 dels prefixos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/cs.json b/homeassistant/components/fritzbox_callmonitor/translations/cs.json new file mode 100644 index 00000000000000..c40da2900bcb80 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/et.json b/homeassistant/components/fritzbox_callmonitor/translations/et.json new file mode 100644 index 00000000000000..7770f31ae0e073 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/et.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "insufficient_permissions": "Kasutajal ei ole piisavalt \u00f5igusi juurdep\u00e4\u00e4suks AVM FRITZ! Box'i seadetele jatelefoniraamatutele.", + "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet" + }, + "error": { + "invalid_auth": "Vigane autentimine" + }, + "flow_title": "AVM FRITZ! K\u00f5nekontroll: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefoniraamat" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "port": "Port", + "username": "Kasutajanimi" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Eesliited on valesti vormindatud, kontrolli nende vormingut." + }, + "step": { + "init": { + "data": { + "prefixes": "Eesliited (komadega eraldatud loend)" + }, + "title": "Eesliidete seadistamine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/it.json b/homeassistant/components/fritzbox_callmonitor/translations/it.json new file mode 100644 index 00000000000000..5696bf86fd1f7d --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/it.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "insufficient_permissions": "L'utente non dispone di autorizzazioni sufficienti per accedere alle impostazioni di AVM FRITZ! Box e alle sue rubriche.", + "no_devices_found": "Nessun dispositivo trovato sulla rete" + }, + "error": { + "invalid_auth": "Autenticazione non valida" + }, + "flow_title": "Monitoraggio chiamate FRITZ! Box AVM: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Rubrica telefonica" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Porta", + "username": "Nome utente" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "I prefissi non sono corretti, controlla il loro formato." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefissi (elenco separato da virgole)" + }, + "title": "Configura prefissi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/lb.json b/homeassistant/components/fritzbox_callmonitor/translations/lb.json new file mode 100644 index 00000000000000..67b5879a557a2c --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/lb.json @@ -0,0 +1,33 @@ +{ + "config": { + "flow_title": "AVM FRITZ!Box Call Monitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Adressbuch" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Passwuert", + "port": "Port", + "username": "Benotzernumm" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Pr\u00e9fixe sinn am falsche Format, iwwerpr\u00e9if dat w.e.g" + }, + "step": { + "init": { + "data": { + "prefixes": "Pr\u00e9fixe (komma getrennte L\u00ebscht)" + }, + "title": "Pr\u00e9fixe konfigur\u00e9ieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/no.json b/homeassistant/components/fritzbox_callmonitor/translations/no.json new file mode 100644 index 00000000000000..12883b0140d518 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/no.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "insufficient_permissions": "Brukeren har utilstrekkelig tillatelse til \u00e5 f\u00e5 tilgang til AVM FRITZ! Box-innstillingene og telefonb\u00f8kene.", + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning" + }, + "flow_title": "AVM FRITZ! Box monitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefonbok" + } + }, + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "port": "Port", + "username": "Brukernavn" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Prefikser er misformet, vennligst sjekk deres format." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefikser (kommaseparert liste)" + }, + "title": "Konfigurer prefiks" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/pl.json b/homeassistant/components/fritzbox_callmonitor/translations/pl.json new file mode 100644 index 00000000000000..fa0317f5c9d913 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/pl.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "insufficient_permissions": "U\u017cytkownik ma niewystarczaj\u0105ce uprawnienia, aby uzyska\u0107 dost\u0119p do ustawie\u0144 AVM FRITZ! Box i jego ksi\u0105\u017cek telefonicznych.", + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "flow_title": "Monitor po\u0142\u0105cze\u0144 AVM FRITZ!Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Ksi\u0105\u017cka telefoniczna" + } + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Prefiksy s\u0105 nieprawid\u0142owe, prosz\u0119 sprawdzi\u0107 ich format." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefiksy (lista oddzielona przecinkami)" + }, + "title": "Skonfiguruj prefiksy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ru.json b/homeassistant/components/fritzbox_callmonitor/translations/ru.json new file mode 100644 index 00000000000000..3eb432532c45b9 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/ru.json @@ -0,0 +1,41 @@ +{ + "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.", + "insufficient_permissions": "\u0423 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u0430\u0432 \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c AVM FRITZ!Box \u0438 \u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u044b\u043c \u043a\u043d\u0438\u0433\u0430\u043c.", + "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": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "flow_title": "AVM FRITZ!Box call monitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u0430\u044f \u043a\u043d\u0438\u0433\u0430" + } + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441\u044b \u0438\u043c\u0435\u044e\u0442 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0438\u0445." + }, + "step": { + "init": { + "data": { + "prefixes": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441\u044b (\u0441\u043f\u0438\u0441\u043e\u043a, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u044f\u0442\u044b\u043c\u0438)" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/tr.json b/homeassistant/components/fritzbox_callmonitor/translations/tr.json new file mode 100644 index 00000000000000..76799f24af824f --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "insufficient_permissions": "Kullan\u0131c\u0131, AVM FRITZ! Box ayarlar\u0131na ve telefon defterlerine eri\u015fmek i\u00e7in yeterli izne sahip de\u011fil.", + "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "flow_title": "AVM FRITZ! Box \u00e7a\u011fr\u0131 monit\u00f6r\u00fc: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefon rehberi" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "\u015eifre", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u00d6nekler yanl\u0131\u015f bi\u00e7imlendirilmi\u015ftir, l\u00fctfen bi\u00e7imlerini kontrol edin." + }, + "step": { + "init": { + "data": { + "prefixes": "\u00d6nekler (virg\u00fclle ayr\u0131lm\u0131\u015f liste)" + }, + "title": "\u00d6nekleri Yap\u0131land\u0131r" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json b/homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json new file mode 100644 index 00000000000000..d159f5df0f9706 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "insufficient_permissions": "\u4f7f\u7528\u8005\u6c92\u6709\u8db3\u5920\u6b0a\u9650\u4ee5\u5b58\u53d6 AVM FRITZ!Box \u8a2d\u5b9a\u53ca\u96fb\u8a71\u7c3f\u3002", + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "flow_title": "AVM FRITZ!Box \u901a\u8a71\u76e3\u63a7\u5668\uff1a{name}", + "step": { + "phonebook": { + "data": { + "phonebook": "\u96fb\u8a71\u7c3f" + } + }, + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u524d\u7db4\u5b57\u9996\u683c\u5f0f\u932f\u8aa4\uff0c\u8acb\u518d\u78ba\u8a8d\u5176\u683c\u5f0f\u3002" + }, + "step": { + "init": { + "data": { + "prefixes": "\u524d\u7db4\u5b57\u9996\uff08\u4ee5\u9017\u865f\u5206\u9694\uff09" + }, + "title": "\u8a2d\u5b9a\u524d\u7db4\u5b57\u9996" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/de.json b/homeassistant/components/garmin_connect/translations/de.json index 54d27e9956e0a7..9186f753a77246 100644 --- a/homeassistant/components/garmin_connect/translations/de.json +++ b/homeassistant/components/garmin_connect/translations/de.json @@ -4,10 +4,10 @@ "already_configured": "Dieses Konto ist bereits konfiguriert." }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.", - "invalid_auth": "Ung\u00fcltige Authentifizierung.", - "too_many_requests": "Zu viele Anfragen, wiederholen Sie es sp\u00e4ter.", - "unknown": "Unerwarteter Fehler." + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "too_many_requests": "Zu viele Anfragen, versuche es sp\u00e4ter erneut.", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/tr.json b/homeassistant/components/garmin_connect/translations/tr.json new file mode 100644 index 00000000000000..a83e1936fb4a15 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/uk.json b/homeassistant/components/garmin_connect/translations/uk.json new file mode 100644 index 00000000000000..aef0632b0f17d7 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "too_many_requests": "\u0417\u0430\u043d\u0430\u0434\u0442\u043e \u0431\u0430\u0433\u0430\u0442\u043e \u0437\u0430\u043f\u0438\u0442\u0456\u0432, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "Garmin Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/de.json b/homeassistant/components/gdacs/translations/de.json index 07d1a4bdb79bf7..a69295f06406a4 100644 --- a/homeassistant/components/gdacs/translations/de.json +++ b/homeassistant/components/gdacs/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Der Standort ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/tr.json b/homeassistant/components/gdacs/translations/tr.json new file mode 100644 index 00000000000000..aeb6a5a345e28a --- /dev/null +++ b/homeassistant/components/gdacs/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "radius": "Yar\u0131\u00e7ap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/uk.json b/homeassistant/components/gdacs/translations/uk.json new file mode 100644 index 00000000000000..0ab20bc55a37e8 --- /dev/null +++ b/homeassistant/components/gdacs/translations/uk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441" + }, + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/de.json b/homeassistant/components/geofency/translations/de.json index 31b8a5eb321501..9c3fd3ea1b0de7 100644 --- a/homeassistant/components/geofency/translations/de.json +++ b/homeassistant/components/geofency/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an den Home Assistant zu senden, musst das Webhook Feature in Geofency konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/geofency/translations/tr.json b/homeassistant/components/geofency/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/geofency/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/uk.json b/homeassistant/components/geofency/translations/uk.json new file mode 100644 index 00000000000000..54a14afb764d9d --- /dev/null +++ b/homeassistant/components/geofency/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Geofency. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Geofency?", + "title": "Geofency" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/tr.json b/homeassistant/components/geonetnz_quakes/translations/tr.json new file mode 100644 index 00000000000000..717f6d72b94e5d --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/uk.json b/homeassistant/components/geonetnz_quakes/translations/uk.json new file mode 100644 index 00000000000000..35653baa945886 --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "mmi": "MMI", + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441" + }, + "title": "GeoNet NZ Quakes" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/de.json b/homeassistant/components/geonetnz_volcano/translations/de.json index b573d93cd5a570..a29555e53ab71d 100644 --- a/homeassistant/components/geonetnz_volcano/translations/de.json +++ b/homeassistant/components/geonetnz_volcano/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_volcano/translations/tr.json b/homeassistant/components/geonetnz_volcano/translations/tr.json new file mode 100644 index 00000000000000..980be333568596 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "radius": "Yar\u0131\u00e7ap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/uk.json b/homeassistant/components/geonetnz_volcano/translations/uk.json new file mode 100644 index 00000000000000..77a4f1eee68568 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/uk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "step": { + "user": { + "data": { + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441" + }, + "title": "GeoNet NZ Volcano" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/de.json b/homeassistant/components/gios/translations/de.json index 0a5cea1819dbc7..7bbb01cf18db27 100644 --- a/homeassistant/components/gios/translations/de.json +++ b/homeassistant/components/gios/translations/de.json @@ -4,14 +4,14 @@ "already_configured": "GIO\u015a integration f\u00fcr diese Messstation ist bereits konfiguriert. " }, "error": { - "cannot_connect": "Es kann keine Verbindung zum GIO\u015a-Server hergestellt werden.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_sensors_data": "Ung\u00fcltige Sensordaten f\u00fcr diese Messstation.", "wrong_station_id": "ID der Messstation ist nicht korrekt." }, "step": { "user": { "data": { - "name": "Name der Integration", + "name": "Name", "station_id": "ID der Messstation" }, "description": "Einrichtung von GIO\u015a (Polnische Hauptinspektion f\u00fcr Umweltschutz) Integration der Luftqualit\u00e4t. Wenn du Hilfe bei der Konfiguration ben\u00f6tigst, schaue hier: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/gios/translations/fr.json b/homeassistant/components/gios/translations/fr.json index b06c41208bc374..2b02b5cfea086d 100644 --- a/homeassistant/components/gios/translations/fr.json +++ b/homeassistant/components/gios/translations/fr.json @@ -18,5 +18,10 @@ "title": "GIO\u015a (Inspection g\u00e9n\u00e9rale polonaise de la protection de l'environnement)" } } + }, + "system_health": { + "info": { + "can_reach_server": "Acc\u00e9der au serveur GIO\u015a" + } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/it.json b/homeassistant/components/gios/translations/it.json index 26bf8386d6669f..5d1e99d17f400f 100644 --- a/homeassistant/components/gios/translations/it.json +++ b/homeassistant/components/gios/translations/it.json @@ -21,7 +21,7 @@ }, "system_health": { "info": { - "can_reach_server": "Raggiungi il server GIO\u015a" + "can_reach_server": "Server GIO\u015a raggiungibile" } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/lb.json b/homeassistant/components/gios/translations/lb.json index 8e8ab861b437e7..cafea72fb78296 100644 --- a/homeassistant/components/gios/translations/lb.json +++ b/homeassistant/components/gios/translations/lb.json @@ -18,5 +18,10 @@ "title": "GIO\u015a (Polnesch Chefinspektorat vum \u00cbmweltschutz)" } } + }, + "system_health": { + "info": { + "can_reach_server": "GIO\u015a Server ereechbar" + } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/tr.json b/homeassistant/components/gios/translations/tr.json new file mode 100644 index 00000000000000..590aec1894cc3b --- /dev/null +++ b/homeassistant/components/gios/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/uk.json b/homeassistant/components/gios/translations/uk.json new file mode 100644 index 00000000000000..f62408c5e8ecdb --- /dev/null +++ b/homeassistant/components/gios/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_sensors_data": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457.", + "wrong_station_id": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 ID \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "station_id": "ID \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457" + }, + "description": "\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u043f\u0440\u043e \u044f\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0456\u0442\u0440\u044f \u0432\u0456\u0434 \u041f\u043e\u043b\u044c\u0441\u044c\u043a\u043e\u0457 \u0456\u043d\u0441\u043f\u0435\u043a\u0446\u0456\u0457 \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \u043d\u0430\u0432\u043a\u043e\u043b\u0438\u0448\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0430 (GIO\u015a). \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457: https://www.home-assistant.io/integrations/gios.", + "title": "GIO\u015a (\u041f\u043e\u043b\u044c\u0441\u044c\u043a\u0430 \u0456\u043d\u0441\u043f\u0435\u043a\u0446\u0456\u044f \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \u043d\u0430\u0432\u043a\u043e\u043b\u0438\u0448\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0430)" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 GIO\u015a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/de.json b/homeassistant/components/glances/translations/de.json index 69c34907f19b64..e464bfdee34106 100644 --- a/homeassistant/components/glances/translations/de.json +++ b/homeassistant/components/glances/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung zum Host nicht m\u00f6glich", + "cannot_connect": "Verbindung fehlgeschlagen", "wrong_version": "Version nicht unterst\u00fctzt (nur 2 oder 3)" }, "step": { diff --git a/homeassistant/components/glances/translations/tr.json b/homeassistant/components/glances/translations/tr.json new file mode 100644 index 00000000000000..69f0cd7ceb123f --- /dev/null +++ b/homeassistant/components/glances/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "G\u00fcncelleme s\u0131kl\u0131\u011f\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/uk.json b/homeassistant/components/glances/translations/uk.json new file mode 100644 index 00000000000000..1fab197fe4291b --- /dev/null +++ b/homeassistant/components/glances/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "wrong_version": "\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u0432\u0435\u0440\u0441\u0456\u0457 2 \u0442\u0430 3." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL", + "version": "\u0412\u0435\u0440\u0441\u0456\u044f API Glances (2 \u0430\u0431\u043e 3)" + }, + "title": "Glances" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "description": "\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 Glances" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/de.json b/homeassistant/components/goalzero/translations/de.json index d79c03f0179fbe..7d8962cdb1164c 100644 --- a/homeassistant/components/goalzero/translations/de.json +++ b/homeassistant/components/goalzero/translations/de.json @@ -1,9 +1,20 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP Adresse", "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/fr.json b/homeassistant/components/goalzero/translations/fr.json index 5c4b7a01580ba1..7bd4929ad929cd 100644 --- a/homeassistant/components/goalzero/translations/fr.json +++ b/homeassistant/components/goalzero/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion", diff --git a/homeassistant/components/goalzero/translations/tr.json b/homeassistant/components/goalzero/translations/tr.json new file mode 100644 index 00000000000000..ae77262b2b3f9a --- /dev/null +++ b/homeassistant/components/goalzero/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/uk.json b/homeassistant/components/goalzero/translations/uk.json new file mode 100644 index 00000000000000..6d67d949c28692 --- /dev/null +++ b/homeassistant/components/goalzero/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Goal Zero: https://www.goalzero.com/product-features/yeti-app/. \n\n \u0414\u043e\u0442\u0440\u0438\u043c\u0443\u0439\u0442\u0435\u0441\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439 \u043f\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044e Yeti \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456 WiFi. \u041f\u043e\u0442\u0456\u043c \u0434\u0456\u0437\u043d\u0430\u0439\u0442\u0435\u0441\u044f IP \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, \u0437 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0442\u0430\u043a\u0438\u043c\u0438, \u0449\u043e\u0431 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0432\u0430\u043b\u0430\u0441\u044c \u0437 \u0447\u0430\u0441\u043e\u043c. \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u0446\u0435 \u0437\u0440\u043e\u0431\u0438\u0442\u0438, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/tr.json b/homeassistant/components/gogogate2/translations/tr.json new file mode 100644 index 00000000000000..e912e7f8012f86 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/uk.json b/homeassistant/components/gogogate2/translations/uk.json new file mode 100644 index 00000000000000..c88b9b603840d3 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 GogoGate2.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f GogoGate2 \u0430\u0431\u043e iSmartGate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/de.json b/homeassistant/components/gpslogger/translations/de.json index d976a5fd6638fa..7215f0c458f623 100644 --- a/homeassistant/components/gpslogger/translations/de.json +++ b/homeassistant/components/gpslogger/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in der GPSLogger konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/gpslogger/translations/tr.json b/homeassistant/components/gpslogger/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/uk.json b/homeassistant/components/gpslogger/translations/uk.json new file mode 100644 index 00000000000000..5b0b6305cdb638 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f GPSLogger. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 GPSLogger?", + "title": "GPSLogger" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/de.json b/homeassistant/components/gree/translations/de.json new file mode 100644 index 00000000000000..96ed09a974f40d --- /dev/null +++ b/homeassistant/components/gree/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "confirm": { + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/tr.json b/homeassistant/components/gree/translations/tr.json new file mode 100644 index 00000000000000..8de4663957ea85 --- /dev/null +++ b/homeassistant/components/gree/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/uk.json b/homeassistant/components/gree/translations/uk.json new file mode 100644 index 00000000000000..292861e9129dbd --- /dev/null +++ b/homeassistant/components/gree/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/de.json b/homeassistant/components/griddy/translations/de.json index ad6a6e10ab0514..4a6c477059ccf4 100644 --- a/homeassistant/components/griddy/translations/de.json +++ b/homeassistant/components/griddy/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Diese Ladezone ist bereits konfiguriert" + "already_configured": "Standort ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/griddy/translations/tr.json b/homeassistant/components/griddy/translations/tr.json index d887b1486584fa..26e0fa73065088 100644 --- a/homeassistant/components/griddy/translations/tr.json +++ b/homeassistant/components/griddy/translations/tr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, "error": { "cannot_connect": "Ba\u011flant\u0131 kurulamad\u0131, l\u00fctfen tekrar deneyin", "unknown": "Beklenmeyen hata" diff --git a/homeassistant/components/griddy/translations/uk.json b/homeassistant/components/griddy/translations/uk.json new file mode 100644 index 00000000000000..e366f0e8b24e0b --- /dev/null +++ b/homeassistant/components/griddy/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "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": { + "loadzone": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f (\u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0430 \u0442\u043e\u0447\u043a\u0430)" + }, + "description": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0443 \u0432\u0430\u0448\u043e\u043c\u0443 \u043f\u0440\u043e\u0444\u0456\u043b\u0456 Griddy \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 Account > Meter > Load Zone.", + "title": "Griddy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/group/translations/uk.json b/homeassistant/components/group/translations/uk.json index 2d57686134a7d8..08cee558f273ce 100644 --- a/homeassistant/components/group/translations/uk.json +++ b/homeassistant/components/group/translations/uk.json @@ -9,7 +9,7 @@ "ok": "\u041e\u041a", "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", "open": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u043e", - "problem": "\u0425\u0430\u043b\u0435\u043f\u0430", + "problem": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430", "unlocked": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e" } }, diff --git a/homeassistant/components/guardian/translations/de.json b/homeassistant/components/guardian/translations/de.json index 27770d690f0ac7..d1218cb23729a9 100644 --- a/homeassistant/components/guardian/translations/de.json +++ b/homeassistant/components/guardian/translations/de.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/guardian/translations/tr.json b/homeassistant/components/guardian/translations/tr.json new file mode 100644 index 00000000000000..1e520a16995d97 --- /dev/null +++ b/homeassistant/components/guardian/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/uk.json b/homeassistant/components/guardian/translations/uk.json new file mode 100644 index 00000000000000..439a225895e8fd --- /dev/null +++ b/homeassistant/components/guardian/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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_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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Elexa Guardian?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/de.json b/homeassistant/components/hangouts/translations/de.json index 5c8ab51cf4e8c8..7b888cf531e079 100644 --- a/homeassistant/components/hangouts/translations/de.json +++ b/homeassistant/components/hangouts/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Google Hangouts ist bereits konfiguriert", - "unknown": "Ein unbekannter Fehler ist aufgetreten." + "unknown": "Unerwarteter Fehler" }, "error": { "invalid_2fa": "Ung\u00fcltige 2-Faktor Authentifizierung, bitte versuche es erneut.", diff --git a/homeassistant/components/hangouts/translations/tr.json b/homeassistant/components/hangouts/translations/tr.json new file mode 100644 index 00000000000000..a204200a2d8438 --- /dev/null +++ b/homeassistant/components/hangouts/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/uk.json b/homeassistant/components/hangouts/translations/uk.json new file mode 100644 index 00000000000000..93eb699d37c80b --- /dev/null +++ b/homeassistant/components/hangouts/translations/uk.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "invalid_2fa": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "invalid_2fa_method": "\u041d\u0435\u043f\u0440\u0438\u043f\u0443\u0441\u0442\u0438\u043c\u0438\u0439 \u0441\u043f\u043e\u0441\u0456\u0431 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 (\u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 \u043d\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0456).", + "invalid_login": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041f\u0456\u043d-\u043a\u043e\u0434 \u0434\u043b\u044f \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "description": "\u043f\u043e\u0440\u043e\u0436\u043d\u044c\u043e", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "authorization_code": "\u041a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 (\u0432\u0438\u043c\u0430\u0433\u0430\u0454\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457)", + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u043f\u043e\u0440\u043e\u0436\u043d\u044c\u043e", + "title": "Google Hangouts" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/de.json b/homeassistant/components/harmony/translations/de.json index f10dfe1432cb56..9cd07f09529f44 100644 --- a/homeassistant/components/harmony/translations/de.json +++ b/homeassistant/components/harmony/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "flow_title": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/tr.json b/homeassistant/components/harmony/translations/tr.json new file mode 100644 index 00000000000000..c77f0f8e07e7ba --- /dev/null +++ b/homeassistant/components/harmony/translations/tr.json @@ -0,0 +1,18 @@ +{ + "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": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/uk.json b/homeassistant/components/harmony/translations/uk.json new file mode 100644 index 00000000000000..5bb2da811f3d5f --- /dev/null +++ b/homeassistant/components/harmony/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "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" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", + "title": "Logitech Harmony Hub" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "\u0410\u043a\u0442\u0438\u0432\u043d\u0456\u0441\u0442\u044c \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c, \u043a\u043e\u043b\u0438 \u0436\u043e\u0434\u043d\u0430 \u0437 \u043d\u0438\u0445 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0430.", + "delay_secs": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 \u043c\u0456\u0436 \u043d\u0430\u0434\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u043a\u043e\u043c\u0430\u043d\u0434." + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 Harmony Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ca.json b/homeassistant/components/hassio/translations/ca.json index 0cdb9318428fc1..19b4316c9ce6b9 100644 --- a/homeassistant/components/hassio/translations/ca.json +++ b/homeassistant/components/hassio/translations/ca.json @@ -12,7 +12,7 @@ "supervisor_version": "Versi\u00f3 del Supervisor", "supported": "Compatible", "update_channel": "Canal d'actualitzaci\u00f3", - "version_api": "API de versions" + "version_api": "Versi\u00f3 d'APIs" } }, "title": "Hass.io" diff --git a/homeassistant/components/hassio/translations/de.json b/homeassistant/components/hassio/translations/de.json index 981cb51c83ab8b..939821edb54aa3 100644 --- a/homeassistant/components/hassio/translations/de.json +++ b/homeassistant/components/hassio/translations/de.json @@ -1,3 +1,18 @@ { + "system_health": { + "info": { + "board": "Board", + "disk_total": "Speicherplatz gesamt", + "disk_used": "Speicherplatz genutzt", + "docker_version": "Docker-Version", + "host_os": "Host-Betriebssystem", + "installed_addons": "Installierte Add-ons", + "supervisor_api": "Supervisor-API", + "supervisor_version": "Supervisor-Version", + "supported": "Unterst\u00fctzt", + "update_channel": "Update-Channel", + "version_api": "Versions-API" + } + }, "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/en.json b/homeassistant/components/hassio/translations/en.json index aadcdabfb941de..230e0c11feae84 100644 --- a/homeassistant/components/hassio/translations/en.json +++ b/homeassistant/components/hassio/translations/en.json @@ -9,11 +9,11 @@ "host_os": "Host Operating System", "installed_addons": "Installed Add-ons", "supervisor_api": "Supervisor API", - "supervisor_version": "Version", + "supervisor_version": "Supervisor Version", "supported": "Supported", "update_channel": "Update Channel", "version_api": "Version API" } }, - "title": "Home Assistant Supervisor" + "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fr.json b/homeassistant/components/hassio/translations/fr.json index 981cb51c83ab8b..2bb52c3c54c106 100644 --- a/homeassistant/components/hassio/translations/fr.json +++ b/homeassistant/components/hassio/translations/fr.json @@ -1,3 +1,18 @@ { + "system_health": { + "info": { + "board": "Tableau de bord", + "disk_total": "Taille total du disque", + "disk_used": "Taille du disque utilis\u00e9", + "docker_version": "Version de Docker", + "healthy": "Sain", + "installed_addons": "Add-ons install\u00e9s", + "supervisor_api": "API du superviseur", + "supervisor_version": "Version du supervisor", + "supported": "Prise en charge", + "update_channel": "Mise \u00e0 jour", + "version_api": "Version API" + } + }, "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/tr.json b/homeassistant/components/hassio/translations/tr.json index d368ac0fb3c903..f2c2d52f60df92 100644 --- a/homeassistant/components/hassio/translations/tr.json +++ b/homeassistant/components/hassio/translations/tr.json @@ -6,7 +6,13 @@ "disk_used": "Kullan\u0131lan Disk", "docker_version": "Docker S\u00fcr\u00fcm\u00fc", "healthy": "Sa\u011fl\u0131kl\u0131", - "host_os": "Ana Bilgisayar \u0130\u015fletim Sistemi" + "host_os": "Ana Bilgisayar \u0130\u015fletim Sistemi", + "installed_addons": "Y\u00fckl\u00fc Eklentiler", + "supervisor_api": "Supervisor API", + "supervisor_version": "S\u00fcperviz\u00f6r S\u00fcr\u00fcm\u00fc", + "supported": "Destekleniyor", + "update_channel": "Kanal\u0131 G\u00fcncelle", + "version_api": "S\u00fcr\u00fcm API" } }, "title": "Hass.io" diff --git a/homeassistant/components/hassio/translations/uk.json b/homeassistant/components/hassio/translations/uk.json index 981cb51c83ab8b..19a40730897bad 100644 --- a/homeassistant/components/hassio/translations/uk.json +++ b/homeassistant/components/hassio/translations/uk.json @@ -1,3 +1,19 @@ { + "system_health": { + "info": { + "board": "\u041f\u043b\u0430\u0442\u0430", + "disk_total": "\u0414\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u0430\u043c'\u044f\u0442\u044c", + "disk_used": "\u041f\u0430\u043c'\u044f\u0442\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043e", + "docker_version": "\u0412\u0435\u0440\u0441\u0456\u044f Docker", + "healthy": "\u0412 \u043d\u043e\u0440\u043c\u0456", + "host_os": "\u041e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0445\u043e\u0441\u0442\u0430", + "installed_addons": "\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0456 \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f", + "supervisor_api": "Supervisor API", + "supervisor_version": "\u0412\u0435\u0440\u0441\u0456\u044f Supervisor", + "supported": "\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430", + "update_channel": "\u041a\u0430\u043d\u0430\u043b \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c", + "version_api": "\u0412\u0435\u0440\u0441\u0456\u044f API" + } + }, "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/heos/translations/de.json b/homeassistant/components/heos/translations/de.json index 92ab6c1c8ff145..ba8a5318951226 100644 --- a/homeassistant/components/heos/translations/de.json +++ b/homeassistant/components/heos/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/heos/translations/tr.json b/homeassistant/components/heos/translations/tr.json new file mode 100644 index 00000000000000..4f1ad7759054c4 --- /dev/null +++ b/homeassistant/components/heos/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/uk.json b/homeassistant/components/heos/translations/uk.json new file mode 100644 index 00000000000000..c0a5fdf04bf94f --- /dev/null +++ b/homeassistant/components/heos/translations/uk.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043c'\u044f \u0445\u043e\u0441\u0442\u0430 \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e HEOS (\u0431\u0430\u0436\u0430\u043d\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0431\u0435\u043b\u044c).", + "title": "HEOS" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/de.json b/homeassistant/components/hisense_aehw4a1/translations/de.json index d5f4f4297401c7..7c0bd96a9c9cee 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/de.json +++ b/homeassistant/components/hisense_aehw4a1/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine Hisense AEH-W4A1-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von Hisense AEH-W4A1 m\u00f6glich." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/tr.json b/homeassistant/components/hisense_aehw4a1/translations/tr.json new file mode 100644 index 00000000000000..a893a653a78c9a --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Hisense AEH-W4A1'i kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/uk.json b/homeassistant/components/hisense_aehw4a1/translations/uk.json new file mode 100644 index 00000000000000..900882513d5128 --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Hisense AEH-W4A1?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/de.json b/homeassistant/components/hlk_sw16/translations/de.json index 94b8d6526d13ba..625c7372347a61 100644 --- a/homeassistant/components/hlk_sw16/translations/de.json +++ b/homeassistant/components/hlk_sw16/translations/de.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "host": "Host", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/hlk_sw16/translations/tr.json b/homeassistant/components/hlk_sw16/translations/tr.json new file mode 100644 index 00000000000000..40c9c39b967721 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/uk.json b/homeassistant/components/hlk_sw16/translations/uk.json new file mode 100644 index 00000000000000..2df11f744559db --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/de.json b/homeassistant/components/home_connect/translations/de.json index 05204c35c4120f..2454c039361105 100644 --- a/homeassistant/components/home_connect/translations/de.json +++ b/homeassistant/components/home_connect/translations/de.json @@ -1,14 +1,15 @@ { "config": { "abort": { - "missing_configuration": "Die Komponente Home Connect ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "create_entry": { - "default": "Erfolgreich mit Home Connect authentifiziert." + "default": "Erfolgreich authentifiziert" }, "step": { "pick_implementation": { - "title": "Authentifizierungsmethode ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/home_connect/translations/uk.json b/homeassistant/components/home_connect/translations/uk.json new file mode 100644 index 00000000000000..247ffd16713cbf --- /dev/null +++ b/homeassistant/components/home_connect/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/de.json b/homeassistant/components/homeassistant/translations/de.json index 45768a9f127009..e24568ff2125ca 100644 --- a/homeassistant/components/homeassistant/translations/de.json +++ b/homeassistant/components/homeassistant/translations/de.json @@ -1,10 +1,21 @@ { "system_health": { "info": { + "arch": "CPU-Architektur", + "chassis": "Chassis", + "dev": "Entwicklung", "docker": "Docker", "docker_version": "Docker", "hassio": "Supervisor", - "host_os": "Home Assistant OS" + "host_os": "Home Assistant OS", + "installation_type": "Installationstyp", + "os_name": "Betriebssystemfamilie", + "os_version": "Betriebssystem-Version", + "python_version": "Python-Version", + "supervisor": "Supervisor", + "timezone": "Zeitzone", + "version": "Version", + "virtualenv": "Virtuelle Umgebung" } } } \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/fr.json b/homeassistant/components/homeassistant/translations/fr.json new file mode 100644 index 00000000000000..194254a0384696 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/fr.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "Architecture du processeur", + "chassis": "Ch\u00e2ssis", + "dev": "D\u00e9veloppement", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Superviseur", + "host_os": "Home Assistant OS", + "installation_type": "Type d'installation", + "os_name": "Famille du syst\u00e8me d'exploitation", + "os_version": "Version du syst\u00e8me d'exploitation", + "python_version": "Version de Python", + "supervisor": "Supervisor", + "timezone": "Fuseau horaire", + "version": "Version", + "virtualenv": "Environnement virtuel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/tr.json b/homeassistant/components/homeassistant/translations/tr.json index 1ff8ea1b3a91ee..c2b7ca1b10cfb5 100644 --- a/homeassistant/components/homeassistant/translations/tr.json +++ b/homeassistant/components/homeassistant/translations/tr.json @@ -2,9 +2,11 @@ "system_health": { "info": { "arch": "CPU Mimarisi", + "chassis": "Ana G\u00f6vde", "dev": "Geli\u015ftirme", "docker": "Konteyner", "docker_version": "Konteyner", + "hassio": "S\u00fcperviz\u00f6r", "host_os": "Home Assistant OS", "installation_type": "Kurulum T\u00fcr\u00fc", "os_name": "\u0130\u015fletim Sistemi Ailesi", diff --git a/homeassistant/components/homeassistant/translations/uk.json b/homeassistant/components/homeassistant/translations/uk.json new file mode 100644 index 00000000000000..19e07c8f822527 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/uk.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "\u0410\u0440\u0445\u0456\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0426\u041f", + "chassis": "\u0428\u0430\u0441\u0456", + "dev": "\u0421\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0435 \u0440\u043e\u0437\u0440\u043e\u0431\u043a\u0438", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Supervisor", + "host_os": "Home Assistant OS", + "installation_type": "\u0422\u0438\u043f \u0456\u043d\u0441\u0442\u0430\u043b\u044f\u0446\u0456\u0457", + "os_name": "\u0421\u0456\u043c\u0435\u0439\u0441\u0442\u0432\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u0438\u0445 \u0441\u0438\u0441\u0442\u0435\u043c", + "os_version": "\u0412\u0435\u0440\u0441\u0456\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438", + "python_version": "\u0412\u0435\u0440\u0441\u0456\u044f Python", + "supervisor": "Supervisor", + "timezone": "\u0427\u0430\u0441\u043e\u0432\u0438\u0439 \u043f\u043e\u044f\u0441", + "version": "\u0412\u0435\u0440\u0441\u0456\u044f", + "virtualenv": "\u0412\u0456\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0435 \u043e\u0442\u043e\u0447\u0435\u043d\u043d\u044f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index 63f461ea3447a8..0870b05a6d1665 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -4,6 +4,20 @@ "port_name_in_use": "Ja hi ha un enlla\u00e7 o accessori configurat amb aquest nom o port." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entitat" + }, + "description": "Escull l'entitat que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat.", + "title": "Selecciona l'entitat a incloure" + }, + "bridge_mode": { + "data": { + "include_domains": "Dominis a incloure" + }, + "description": "Escull els dominis que vulguis incloure. S'inclouran totes les entitats del domini que siguin compatibles.", + "title": "Selecciona els dominis a incloure" + }, "pairing": { "description": "Tan aviat com {name} estigui llest, la vinculaci\u00f3 estar\u00e0 disponible a \"Notificacions\" com a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\".", "title": "Vinculaci\u00f3 HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Autoarrencada (desactiva-ho si fas servir Z-Wave o algun altre sistema d'inici lent)", - "include_domains": "Dominis a incloure" + "include_domains": "Dominis a incloure", + "mode": "Mode" }, - "description": "La integraci\u00f3 HomeKit et permet l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML per l'enlla\u00e7 prinipal.", + "description": "La integraci\u00f3 HomeKit et permetr\u00e0 l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Activaci\u00f3 de HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "[%key::component::homekit::config::step::user::data::auto_start%]", + "auto_start": "Inici autom\u00e0tic (desactiva-ho si crides el servei homekit.start manualment)", "safe_mode": "Mode segur (habilita-ho nom\u00e9s si falla la vinculaci\u00f3)" }, "description": "Aquests par\u00e0metres nom\u00e9s s'han d'ajustar si HomeKit no \u00e9s funcional.", @@ -40,16 +55,16 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis exposar. En mode accessori, nom\u00e9s s'exposa una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret que se seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'exposaran totes les entitats del domini excepte les entitats excloses.", - "title": "Selecci\u00f3 de les entitats a exposar" + "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "title": "Selecciona les entitats a incloure" }, "init": { "data": { - "include_domains": "[%key::component::homekit::config::step::user::data::include_domains%]", + "include_domains": "Dominis a incloure", "mode": "Mode" }, - "description": "HomeKit es pot configurar per exposar un enlla\u00e7 o un sol accessori. En mode accessori, nom\u00e9s es pot utilitzar una entitat. El mode accessori \u00e9s necessari en reproductors multim\u00e8dia amb classe de dispositiu TV perqu\u00e8 funcionin correctament. Les entitats a \"Dominis a incloure\" s'exposaran a HomeKit. A la seg\u00fcent pantalla podr\u00e0s seleccionar quines entitats vols incloure o excloure d'aquesta llista.", - "title": "Selecci\u00f3 dels dominis a exposar." + "description": "HomeKit es pot configurar per exposar un enlla\u00e7 o un sol accessori. En mode accessori, nom\u00e9s es pot utilitzar una entitat. El mode accessori \u00e9s necessari perqu\u00e8 els reproductors multim\u00e8dia amb classe de dispositiu TV funcionin correctament. Les entitats a \"Dominis a incloure\" s'inclouran a HomeKit. A la seg\u00fcent pantalla podr\u00e0s seleccionar quines entitats vols incloure o excloure d'aquesta llista.", + "title": "Selecciona els dominis a incloure." }, "yaml": { "description": "Aquesta entrada es controla en YAML", diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json index c070c852f0978f..faf1b1d74fc0d4 100644 --- a/homeassistant/components/homekit/translations/cs.json +++ b/homeassistant/components/homekit/translations/cs.json @@ -4,12 +4,18 @@ "port_name_in_use": "P\u0159\u00edslu\u0161enstv\u00ed nebo p\u0159emost\u011bn\u00ed se stejn\u00fdm n\u00e1zvem nebo portem je ji\u017e nastaveno." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entita" + } + }, "pairing": { "title": "P\u00e1rov\u00e1n\u00ed s HomeKit" }, "user": { "data": { - "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty" + "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", + "mode": "Re\u017eim" }, "title": "Aktivace HomeKit" } diff --git a/homeassistant/components/homekit/translations/de.json b/homeassistant/components/homekit/translations/de.json index 9b1ec14a1dd20b..6d69c498bacacc 100644 --- a/homeassistant/components/homekit/translations/de.json +++ b/homeassistant/components/homekit/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "port_name_in_use": "A bridge with the same name or port is already configured.\nEine HomeKit Bridge mit dem selben Namen oder Port ist bereits vorhanden" + "port_name_in_use": "Eine HomeKit Bridge mit demselben Namen oder Port ist bereits vorhanden." }, "step": { "pairing": { @@ -37,14 +37,15 @@ }, "init": { "data": { + "include_domains": "Einzubeziehende Domains", "mode": "Modus" }, "description": "HomeKit kann so konfiguriert werden, dass eine Br\u00fccke oder ein einzelnes Zubeh\u00f6r verf\u00fcgbar gemacht wird. Im Zubeh\u00f6rmodus kann nur eine einzelne Entit\u00e4t verwendet werden. F\u00fcr Media Player mit der TV-Ger\u00e4teklasse ist ein Zubeh\u00f6rmodus erforderlich, damit sie ordnungsgem\u00e4\u00df funktionieren. Entit\u00e4ten in den \"einzuschlie\u00dfenden Dom\u00e4nen\" werden f\u00fcr HomeKit verf\u00fcgbar gemacht. Auf dem n\u00e4chsten Bildschirm k\u00f6nnen Sie ausw\u00e4hlen, welche Entit\u00e4ten in diese Liste aufgenommen oder aus dieser ausgeschlossen werden sollen.", - "title": "W\u00e4hlen Sie die zu \u00fcberbr\u00fcckenden Dom\u00e4nen aus." + "title": "W\u00e4hle die zu \u00fcberbr\u00fcckenden Dom\u00e4nen aus." }, "yaml": { "description": "Dieser Eintrag wird \u00fcber YAML gesteuert", - "title": "Passen Sie die HomeKit Bridge-Optionen an" + "title": "Passe die HomeKit Bridge-Optionen an" } } } diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index cc6c8f8dc31225..db0656c0450524 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -24,9 +24,11 @@ }, "user": { "data": { + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", "mode": "Mode" }, - "description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each TV, media player and camera.", + "description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", "title": "Activate HomeKit" } } @@ -35,7 +37,8 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" @@ -69,4 +72,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 76a78602bd3e87..37bff5f9b70e2c 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -4,6 +4,20 @@ "port_name_in_use": "Sama nime v\u00f5i pordiga tarvik v\u00f5i sild on juba konfigureeritud." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Olem" + }, + "description": "Vali kaasatav olem. Lisare\u017eiimis on kaasatav ainult \u00fcks olem.", + "title": "Vali kaasatav olem" + }, + "bridge_mode": { + "data": { + "include_domains": "Kaasatavad domeenid" + }, + "description": "Vali kaasatavad domeenid. Kaasatakse k\u00f5ik domeenis toetatud olemid.", + "title": "Vali kaasatavad domeenid" + }, "pairing": { "description": "Niipea kui {name} on valmis, on sidumine saadaval jaotises \"Notifications\" kui \"HomeKit Bridge Setup\".", "title": "HomeKiti sidumine" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Autostart (keela, kui kasutad Z-Wave'i v\u00f5i muud viivitatud k\u00e4ivituss\u00fcsteemi)", - "include_domains": "Kaasatavad domeenid" + "include_domains": "Kaasatavad domeenid", + "mode": "Re\u017eiim" }, - "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu.", + "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu. Parema tulemuse saavutamiseks ja ootamatute seadmete kadumise v\u00e4ltimiseks loo ja seo eraldi HomeKiti seade tarviku re\u017eiimis kga meediaesitaja ja kaamera jaoks.", "title": "Aktiveeri HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (keela, kui kasutad Z-Wave'i v\u00f5i muud viivitatud k\u00e4ivituss\u00fcsteemi)", + "auto_start": "Autostart (keela kui kasutad homekit.start teenust k\u00e4sitsi)", "safe_mode": "Turvare\u017eiim (luba ainult siis, kui sidumine nurjub)" }, "description": "Neid s\u00e4tteid tuleb muuta ainult siis kui HomeKit ei t\u00f6\u00f6ta.", @@ -40,8 +55,8 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali avaldatavad olemid. Tarvikute re\u017eiimis on avaldatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis avaldatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", - "title": "Vali avaldatavad olemid" + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", + "title": "Vali kaasatavd olemid" }, "init": { "data": { @@ -49,7 +64,7 @@ "mode": "Re\u017eiim" }, "description": "HomeKiti saab seadistada silla v\u00f5i \u00fche lisaseadme avaldamiseks. Lisare\u017eiimis saab kasutada ainult \u00fchte \u00fcksust. Teleriseadmete klassiga meediumipleierite n\u00f5uetekohaseks toimimiseks on vaja lisare\u017eiimi. \u201eKaasatavate domeenide\u201d \u00fcksused puutuvad kokku HomeKitiga. J\u00e4rgmisel ekraanil saad valida, millised \u00fcksused sellesse loendisse lisada v\u00f5i sellest v\u00e4lja j\u00e4tta.", - "title": "Valige avaldatavad domeenid." + "title": "Vali kaasatavad domeenid" }, "yaml": { "description": "Seda sisestust juhitakse YAML-i kaudu", diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 7e9c8a05b9da55..9a85d1e6e9fc52 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -4,6 +4,20 @@ "port_name_in_use": "Un accessorio o un bridge con lo stesso nome o porta \u00e8 gi\u00e0 configurato." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entit\u00e0" + }, + "description": "Scegli l'entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa solo una singola entit\u00e0.", + "title": "Seleziona l'entit\u00e0 da includere" + }, + "bridge_mode": { + "data": { + "include_domains": "Domini da includere" + }, + "description": "Scegli i domini da includere. Verranno incluse tutte le entit\u00e0 supportate nel dominio.", + "title": "Seleziona i domini da includere" + }, "pairing": { "description": "Non appena il {name} \u00e8 pronto, l'associazione sar\u00e0 disponibile in \"Notifiche\" come \"Configurazione HomeKit Bridge\".", "title": "Associa HomeKit" @@ -11,7 +25,8 @@ "user": { "data": { "auto_start": "Avvio automatico (disabilitare se si utilizza Z-Wave o un altro sistema di avvio ritardato)", - "include_domains": "Domini da includere" + "include_domains": "Domini da includere", + "mode": "Modalit\u00e0" }, "description": "L'integrazione di HomeKit ti consentir\u00e0 di accedere alle entit\u00e0 di Home Assistant in HomeKit. In modalit\u00e0 bridge, i bridge HomeKit sono limitati a 150 accessori per istanza, incluso il bridge stesso. Se desideri eseguire il bridge di un numero di accessori superiore a quello massimo, si consiglia di utilizzare pi\u00f9 bridge HomeKit per domini diversi. La configurazione dettagliata dell'entit\u00e0 \u00e8 disponibile solo tramite YAML per il bridge principale.", "title": "Attiva HomeKit" @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Avvio automatico (disabilitare se si utilizza Z-Wave o un altro sistema di avvio ritardato)", + "auto_start": "Avvio automatico (disabilitare se stai chiamando manualmente il servizio homekit.start)", "safe_mode": "Modalit\u00e0 provvisoria (attivare solo in caso di errore di associazione)" }, "description": "Queste impostazioni devono essere regolate solo se HomeKit non funziona.", @@ -40,8 +55,8 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da esporre. In modalit\u00e0 accessorio, \u00e8 esposta una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno esposte, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno esposte, ad eccezione delle entit\u00e0 escluse.", - "title": "Selezionare le entit\u00e0 da esporre" + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali e per evitare una indisponibilit\u00e0 imprevista, creare e associare un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "title": "Seleziona le entit\u00e0 da includere" }, "init": { "data": { @@ -49,7 +64,7 @@ "mode": "Modalit\u00e0" }, "description": "HomeKit pu\u00f2 essere configurato esponendo un bridge o un singolo accessorio. In modalit\u00e0 accessorio, pu\u00f2 essere utilizzata solo una singola entit\u00e0. La modalit\u00e0 accessorio \u00e8 necessaria per il corretto funzionamento dei lettori multimediali con la classe di apparecchi TV. Le entit\u00e0 nei \"Domini da includere\" saranno esposte ad HomeKit. Sar\u00e0 possibile selezionare quali entit\u00e0 includere o escludere da questo elenco nella schermata successiva.", - "title": "Selezionare i domini da esporre." + "title": "Seleziona i domini da includere." }, "yaml": { "description": "Questa voce \u00e8 controllata tramite YAML", diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 7eff4d37668e13..9a64def41569d2 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -4,6 +4,20 @@ "port_name_in_use": "Et tilbeh\u00f8r eller bro med samme navn eller port er allerede konfigurert." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Enhet" + }, + "description": "Velg enheten som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert.", + "title": "Velg enhet som skal inkluderes" + }, + "bridge_mode": { + "data": { + "include_domains": "Domener \u00e5 inkludere" + }, + "description": "Velg domenene som skal inkluderes. Alle st\u00f8ttede enheter i domenet vil bli inkludert.", + "title": "Velg domener som skal inkluderes" + }, "pairing": { "description": "S\u00e5 snart {name} er klart, vil sammenkobling v\u00e6re tilgjengelig i \"Notifications\" som \"HomeKit Bridge Setup\".", "title": "Koble sammen HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Autostart (deaktiver hvis du bruker Z-Wave eller annet forsinket startsystem)", - "include_domains": "Domener \u00e5 inkludere" + "include_domains": "Domener \u00e5 inkludere", + "mode": "Modus" }, - "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-entitetene dine i HomeKit. I bromodus er HomeKit Broer begrenset til 150 tilbeh\u00f8rsenhet per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 \u00f8ke maksimalt antall tilbeh\u00f8rsenheter, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert entitetskonfigurasjon er bare tilgjengelig via YAML for den prim\u00e6re broen.", + "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-enhetene dine i HomeKit. I bromodus er HomeKit Bridges begrenset til 150 tilbeh\u00f8r per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 bygge bro over maksimalt antall tilbeh\u00f8r, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert enhetskonfigurasjon er bare tilgjengelig via YAML. For best ytelse og for \u00e5 forhindre uventet utilgjengelighet, opprett og par sammen en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", "title": "Aktiver HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (deaktiver hvis du bruker Z-Wave eller annet forsinket startsystem)", + "auto_start": "Autostart (deaktiver hvis du ringer til homekit.start-tjenesten manuelt)", "safe_mode": "Sikker modus (aktiver bare hvis sammenkoblingen mislykkes)" }, "description": "Disse innstillingene m\u00e5 bare justeres hvis HomeKit ikke fungerer.", @@ -40,16 +55,16 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg entitene som skal eksponeres. I tilbeh\u00f8rsmodus er bare en enkelt entitet eksponert. I bro-inkluderingsmodus vil alle entiteter i domenet bli eksponert med mindre spesifikke entiteter er valgt. I bro-ekskluderingsmodus vil alle entiteter i domenet bli eksponert bortsett fra de ekskluderte entitetene.", - "title": "Velg entiteter som skal eksponeres" + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare \u00e9n enkelt enhet inkludert. I bridge include-modus inkluderes alle enheter i domenet med mindre bestemte enheter er valgt. I brounnlatingsmodus inkluderes alle enheter i domenet, med unntak av de utelatte enhetene. For best mulig ytelse, og for \u00e5 forhindre uventet utilgjengelighet, opprett og par en separat HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediespiller og kamera.", + "title": "Velg enheter som skal inkluderes" }, "init": { "data": { "include_domains": "Domener \u00e5 inkludere", "mode": "Modus" }, - "description": "HomeKit kan konfigureres for \u00e5 eksponere en bro eller en enkelt tilbeh\u00f8rsenhet. I tilbeh\u00f8rsmodus kan bare en enkelt entitet brukes. Tilbeh\u00f8rsmodus er n\u00f8dvendig for at mediaspillere med TV-enhetsklasse skal fungere skikkelig. Entiteter i \u201cDomains to include\u201d vil bli eksponert for HomeKit. Du vil kunne velge hvilke entiteter du vil inkludere eller ekskludere fra denne listen p\u00e5 neste skjermbilde.", - "title": "Velg domener du vil eksponere." + "description": "HomeKit kan konfigureres vise en bro eller ett enkelt tilbeh\u00f8r. I tilbeh\u00f8rsmodus kan bare \u00e9n enkelt enhet brukes. Tilbeh\u00f8rsmodus kreves for at mediespillere med TV-enhetsklassen skal fungere som de skal. Enheter i \"Domener som skal inkluderes\" inkluderes i HomeKit. Du kan velge hvilke enheter som skal inkluderes eller ekskluderes fra denne listen p\u00e5 neste skjermbilde.", + "title": "Velg domener som skal inkluderes." }, "yaml": { "description": "Denne oppf\u00f8ringen kontrolleres via YAML", diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 3210f0f4430bb7..2679a4de20afb1 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -4,6 +4,20 @@ "port_name_in_use": "Akcesorium lub mostek o tej samej nazwie lub adresie IP jest ju\u017c skonfigurowany" }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Encja" + }, + "description": "Wybierz uwzgl\u0119dniane encje. W trybie akcesori\u00f3w uwzgl\u0119dniana jest tylko jedna encja.", + "title": "Wybierz uwzgl\u0119dniane encje" + }, + "bridge_mode": { + "data": { + "include_domains": "Domeny do uwzgl\u0119dnienia" + }, + "description": "Wybierz uwzgl\u0119dniane domeny. Wszystkie obs\u0142ugiwane encje w domenie zostan\u0105 uwzgl\u0119dnione.", + "title": "Wybierz uwzgl\u0119dniane domeny" + }, "pairing": { "description": "Gdy tylko {name} b\u0119dzie gotowy, opcja parowania b\u0119dzie dost\u0119pna w \u201ePowiadomieniach\u201d jako \u201eKonfiguracja mostka HomeKit\u201d.", "title": "Parowanie z HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli u\u017cywasz Z-Wave lub innej integracji op\u00f3\u017aniaj\u0105cej start systemu)", - "include_domains": "Domeny do uwzgl\u0119dnienia" + "include_domains": "Domeny do uwzgl\u0119dnienia", + "mode": "Tryb" }, - "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka.", + "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", "title": "Aktywacja HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli u\u017cywasz Z-Wave lub innej integracji op\u00f3\u017aniaj\u0105cej start systemu)", + "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli r\u0119cznie uruchamiasz us\u0142ug\u0119 homekit.start)", "safe_mode": "Tryb awaryjny (w\u0142\u0105cz tylko wtedy, gdy parowanie nie powiedzie si\u0119)" }, "description": "Te ustawienia nale\u017cy dostosowa\u0107 tylko wtedy, gdy HomeKit nie dzia\u0142a.", @@ -40,8 +55,8 @@ "entities": "Encje", "mode": "Tryb" }, - "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 widoczne. W trybie \"Akcesorium\", tylko jedna encja jest widoczna. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 widoczne, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 widoczne, z wyj\u0105tkiem tych wybranych.", - "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 widoczne" + "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", + "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione" }, "init": { "data": { diff --git a/homeassistant/components/homekit/translations/ro.json b/homeassistant/components/homekit/translations/ro.json new file mode 100644 index 00000000000000..82e8344417ba17 --- /dev/null +++ b/homeassistant/components/homekit/translations/ro.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "Integrarea HomeKit v\u0103 va permite s\u0103 accesa\u021bi entit\u0103\u021bile Home Assistant din HomeKit. \u00cen modul bridge, HomeKit Bridges sunt limitate la 150 de accesorii pe instan\u021b\u0103, inclusiv bridge-ul \u00een sine. Dac\u0103 dori\u021bi s\u0103 face\u021bi mai mult dec\u00e2t num\u0103rul maxim de accesorii, este recomandat s\u0103 utiliza\u021bi mai multe poduri HomeKit pentru diferite domenii. Configurarea detaliat\u0103 a entit\u0103\u021bii este disponibil\u0103 numai prin YAML. Pentru cele mai bune performan\u021be \u0219i pentru a preveni indisponibilitatea nea\u0219teptat\u0103, crea\u021bi \u0219i \u00eemperechea\u021bi o instan\u021b\u0103 HomeKit separat\u0103 \u00een modul accesoriu pentru fiecare player media TV \u0219i camer\u0103." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 3cb5e84936a8a5..6cf96c2dd7857c 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -4,6 +4,11 @@ "port_name_in_use": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u0438\u043b\u0438 \u043f\u043e\u0440\u0442\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "\u041e\u0431\u044a\u0435\u043a\u0442" + } + }, "pairing": { "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e, \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u0445\" \u043a\u0430\u043a \"HomeKit Bridge Setup\".", "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 HomeKit" @@ -13,7 +18,7 @@ "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b" }, - "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0431\u0440\u0438\u0434\u0436\u0430.", + "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "HomeKit" } } @@ -22,7 +27,7 @@ "step": { "advanced": { "data": { - "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u0435, \u0435\u0441\u043b\u0438 \u0412\u044b \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0435 \u0441\u043b\u0443\u0436\u0431\u0443 homekit.start)", "safe_mode": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c (\u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u0431\u043e\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f)" }, "description": "\u042d\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b, \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 HomeKit \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.", @@ -40,7 +45,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/homekit/translations/sv.json b/homeassistant/components/homekit/translations/sv.json index 5aa9507a85d6fc..1e2fcae04b5bc5 100644 --- a/homeassistant/components/homekit/translations/sv.json +++ b/homeassistant/components/homekit/translations/sv.json @@ -1,4 +1,19 @@ { + "config": { + "step": { + "bridge_mode": { + "data": { + "include_domains": "Dom\u00e4ner att inkludera" + } + }, + "pairing": { + "title": "Para HomeKit" + }, + "user": { + "title": "Aktivera HomeKit" + } + } + }, "options": { "step": { "cameras": { @@ -7,6 +22,12 @@ }, "description": "Kontrollera alla kameror som st\u00f6der inbyggda H.264-str\u00f6mmar. Om kameran inte skickar ut en H.264-str\u00f6m kodar systemet videon till H.264 f\u00f6r HomeKit. Transkodning kr\u00e4ver h\u00f6g prestanda och kommer troligtvis inte att fungera p\u00e5 enkortsdatorer.", "title": "V\u00e4lj kamerans videoavkodare." + }, + "init": { + "data": { + "include_domains": "Dom\u00e4ner att inkludera" + }, + "title": "V\u00e4lj dom\u00e4ner som ska inkluderas." } } } diff --git a/homeassistant/components/homekit/translations/tr.json b/homeassistant/components/homekit/translations/tr.json new file mode 100644 index 00000000000000..f9391fd0686c61 --- /dev/null +++ b/homeassistant/components/homekit/translations/tr.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Ayn\u0131 ada veya ba\u011flant\u0131 noktas\u0131na sahip bir aksesuar veya k\u00f6pr\u00fc zaten yap\u0131land\u0131r\u0131lm\u0131\u015f." + }, + "step": { + "accessory_mode": { + "data": { + "entity_id": "Varl\u0131k" + }, + "description": "Dahil edilecek varl\u0131\u011f\u0131 se\u00e7in. Aksesuar modunda, yaln\u0131zca tek bir varl\u0131k dahildir.", + "title": "Dahil edilecek varl\u0131\u011f\u0131 se\u00e7in" + }, + "bridge_mode": { + "data": { + "include_domains": "\u0130\u00e7erecek etki alanlar\u0131" + }, + "description": "Dahil edilecek alanlar\u0131 se\u00e7in. Etki alan\u0131ndaki t\u00fcm desteklenen varl\u0131klar dahil edilecektir.", + "title": "Dahil edilecek etki alanlar\u0131n\u0131 se\u00e7in" + }, + "pairing": { + "description": "{name} haz\u0131r olur olmaz e\u015fle\u015ftirme, \"Bildirimler\" i\u00e7inde \"HomeKit K\u00f6pr\u00fc Kurulumu\" olarak mevcut olacakt\u0131r.", + "title": "HomeKit'i E\u015fle\u015ftir" + }, + "user": { + "data": { + "mode": "Mod" + } + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "safe_mode": "G\u00fcvenli Mod (yaln\u0131zca e\u015fle\u015ftirme ba\u015far\u0131s\u0131z olursa etkinle\u015ftirin)" + } + }, + "cameras": { + "data": { + "camera_copy": "Yerel H.264 ak\u0131\u015flar\u0131n\u0131 destekleyen kameralar" + }, + "description": "Yerel H.264 ak\u0131\u015flar\u0131n\u0131 destekleyen t\u00fcm kameralar\u0131 kontrol edin. Kamera bir H.264 ak\u0131\u015f\u0131 vermezse, sistem videoyu HomeKit i\u00e7in H.264'e d\u00f6n\u00fc\u015ft\u00fcr\u00fcr. Kod d\u00f6n\u00fc\u015ft\u00fcrme, y\u00fcksek performansl\u0131 bir CPU gerektirir ve tek kartl\u0131 bilgisayarlarda \u00e7al\u0131\u015fma olas\u0131l\u0131\u011f\u0131 d\u00fc\u015f\u00fckt\u00fcr.", + "title": "Kamera video codec bile\u015fenini se\u00e7in." + }, + "include_exclude": { + "data": { + "entities": "Varl\u0131klar", + "mode": "Mod" + }, + "title": "Dahil edilecek varl\u0131klar\u0131 se\u00e7in" + }, + "init": { + "data": { + "mode": "Mod" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/uk.json b/homeassistant/components/homekit/translations/uk.json index 10cd42ccecb6e4..876b200bdf8219 100644 --- a/homeassistant/components/homekit/translations/uk.json +++ b/homeassistant/components/homekit/translations/uk.json @@ -1,10 +1,59 @@ { + "config": { + "abort": { + "port_name_in_use": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437 \u0442\u0430\u043a\u043e\u044e \u0436 \u043d\u0430\u0437\u0432\u043e\u044e \u0430\u0431\u043e \u043f\u043e\u0440\u0442\u043e\u043c \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "step": { + "pairing": { + "description": "\u042f\u043a \u0442\u0456\u043b\u044c\u043a\u0438 {name} \u0431\u0443\u0434\u0435 \u0433\u043e\u0442\u043e\u0432\u0438\u0439, \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 \u0431\u0443\u0434\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0421\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f\u0445\" \u044f\u043a \"HomeKit Bridge Setup\".", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0437 HomeKit" + }, + "user": { + "data": { + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 Z-Wave \u0430\u0431\u043e \u0456\u043d\u0448\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u0432\u0456\u0434\u043a\u043b\u0430\u0434\u0435\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0443)", + "include_domains": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u0438" + }, + "description": "\u0426\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0434\u043e\u0437\u0432\u043e\u043b\u044f\u0454 \u043e\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u043e\u0431'\u0454\u043a\u0442\u0456\u0432 Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u0438\u0439 150 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u0435\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0447\u0438 \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u042f\u043a\u0449\u043e \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0431\u0456\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 HomeKit Bridge \u0434\u043b\u044f \u0440\u0456\u0437\u043d\u0438\u0445 \u0434\u043e\u043c\u0435\u043d\u0456\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u043a\u043e\u0436\u043d\u043e\u0433\u043e \u043e\u0431'\u0454\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0435 \u0442\u0456\u043b\u044c\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 YAML \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043c\u043e\u0441\u0442\u0430.", + "title": "HomeKit" + } + } + }, "options": { "step": { + "advanced": { + "data": { + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 Z-Wave \u0430\u0431\u043e \u0456\u043d\u0448\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u0432\u0456\u0434\u043a\u043b\u0430\u0434\u0435\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0443)", + "safe_mode": "\u0411\u0435\u0437\u043f\u0435\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c (\u0443\u0432\u0456\u043c\u043a\u043d\u0456\u0442\u044c \u0442\u0456\u043b\u044c\u043a\u0438 \u0432 \u0440\u0430\u0437\u0456 \u0437\u0431\u043e\u044e \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f)" + }, + "description": "\u0426\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0456, \u043b\u0438\u0448\u0435 \u044f\u043a\u0449\u043e HomeKit \u043d\u0435 \u043f\u0440\u0430\u0446\u044e\u0454.", + "title": "\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + }, + "cameras": { + "data": { + "camera_copy": "\u041a\u0430\u043c\u0435\u0440\u0438, \u044f\u043a\u0456 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c \u043f\u043e\u0442\u043e\u043a\u0438 H.264" + }, + "description": "\u042f\u043a\u0449\u043e \u043a\u0430\u043c\u0435\u0440\u0430 \u043d\u0435 \u0432\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u0442\u0456\u043a H.264, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u043e\u0432\u0443\u0454 \u0432\u0456\u0434\u0435\u043e \u0432 H.264 \u0434\u043b\u044f HomeKit. \u0422\u0440\u0430\u043d\u0441\u043a\u043e\u0434\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u0432\u0438\u0441\u043e\u043a\u043e\u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u043e\u0440\u0430 \u0456 \u043d\u0430\u0432\u0440\u044f\u0434 \u0447\u0438 \u0431\u0443\u0434\u0435 \u043f\u0440\u0430\u0446\u044e\u0432\u0430\u0442\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043f\u043b\u0430\u0442\u043d\u0438\u0445 \u043a\u043e\u043c\u043f'\u044e\u0442\u0435\u0440\u0430\u0445.", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0456\u0434\u0435\u043e\u043a\u043e\u0434\u0435\u043a \u043a\u0430\u043c\u0435\u0440\u0438." + }, + "include_exclude": { + "data": { + "entities": "\u0421\u0443\u0442\u043d\u043e\u0441\u0442\u0456", + "mode": "\u0420\u0435\u0436\u0438\u043c" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432\u0441\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0443, \u044f\u043a\u0449\u043e \u043d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u0456 \u043f\u0435\u0432\u043d\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432\u0441\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u0456\u043c \u0432\u0438\u0431\u0440\u0430\u043d\u0438\u0445.", + "title": "\u0412\u0438\u0431\u0456\u0440 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit" + }, "init": { "data": { + "include_domains": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u0438", "mode": "\u0420\u0435\u0436\u0438\u043c" - } + }, + "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 HomeKit \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u043c\u043e\u0441\u0442\u0430 \u0430\u0431\u043e \u044f\u043a \u043e\u043a\u0440\u0435\u043c\u0438\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0434\u0438\u043d \u043e\u0431'\u0454\u043a\u0442. \u041c\u0435\u0434\u0456\u0430\u043f\u043b\u0435\u0454\u0440\u0438, \u044f\u043a\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u0432 Home Assistant \u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c 'device_class: tv', \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0457 \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u043e\u0432\u0430\u043d\u0456 \u0432 Homekit \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430. \u041e\u0431'\u0454\u043a\u0442\u0438, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u043d\u0438\u043c \u0434\u043e\u043c\u0435\u043d\u0430\u043c, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432 HomeKit. \u041d\u0430 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u043c\u0443 \u0435\u0442\u0430\u043f\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0412\u0438 \u0437\u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u0431\u0440\u0430\u0442\u0438, \u044f\u043a\u0456 \u043e\u0431'\u0454\u043a\u0442\u0438 \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0437 \u0446\u0438\u0445 \u0434\u043e\u043c\u0435\u043d\u0456\u0432.", + "title": "\u0412\u0438\u0431\u0456\u0440 \u0434\u043e\u043c\u0435\u043d\u0456\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit" + }, + "yaml": { + "description": "\u0426\u0435\u0439 \u0437\u0430\u043f\u0438\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 YAML", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f HomeKit" } } } diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index 0f1093f5b5bbdd..605263c4489728 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -4,6 +4,20 @@ "port_name_in_use": "\u4f7f\u7528\u76f8\u540c\u540d\u7a31\u6216\u901a\u8a0a\u57e0\u7684\u914d\u4ef6\u6216 Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" }, "step": { + "accessory_mode": { + "data": { + "entity_id": "\u5be6\u9ad4" + }, + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\uff0c\u50c5\u80fd\u5305\u542b\u55ae\u4e00\u5be6\u9ad4\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" + }, + "bridge_mode": { + "data": { + "include_domains": "\u5305\u542b\u7db2\u57df" + }, + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df\u3002\u6240\u6709\u7db2\u57df\u5167\u652f\u63f4\u7684\u5be6\u9ad4\u90fd\u6703\u5305\u542b\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" + }, "pairing": { "description": "\u65bc {name} \u5c31\u7dd2\u5f8c\u3001\u5c07\u6703\u65bc\u300c\u901a\u77e5\u300d\u4e2d\u986f\u793a\u300cHomeKit Bridge \u8a2d\u5b9a\u300d\u7684\u914d\u5c0d\u8cc7\u8a0a\u3002", "title": "\u914d\u5c0d HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u9072\u555f\u52d5\u7cfb\u7d71\u6642\u3001\u8acb\u95dc\u9589\uff09", - "include_domains": "\u5305\u542b Domain" + "include_domains": "\u5305\u542b\u7db2\u57df", + "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c Domain \u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002", + "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c\u7db2\u57df\u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", "title": "\u555f\u7528 HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u9072\u555f\u52d5\u7cfb\u7d71\u6642\u3001\u8acb\u95dc\u9589\uff09", + "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u624b\u52d5\u4f7f\u7528 homekit.start \u670d\u52d9\u6642\u3001\u8acb\u95dc\u9589\uff09", "safe_mode": "\u5b89\u5168\u6a21\u5f0f\uff08\u50c5\u65bc\u914d\u5c0d\u5931\u6557\u6642\u4f7f\u7528\uff09" }, "description": "\u50c5\u65bc Homekit \u7121\u6cd5\u6b63\u5e38\u4f7f\u7528\u6642\uff0c\u8abf\u6574\u6b64\u4e9b\u8a2d\u5b9a\u3002", @@ -40,16 +55,16 @@ "entities": "\u5be6\u9ad4", "mode": "\u6a21\u5f0f" }, - "description": "\u9078\u64c7\u9032\u884c\u63a5\u901a\u7684\u5be6\u9ad4\u3002\u65bc\u5305\u542b\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u9032\u884c\u63a5\u901a\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u9032\u884c\u63a5\u901a\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002", - "title": "\u9078\u64c7\u8981\u63a5\u901a\u7684\u5be6\u9ad4" + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" }, "init": { "data": { - "include_domains": "\u5305\u542b Domain", + "include_domains": "\u5305\u542b\u7db2\u57df", "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u80fd\u5920\u8a2d\u5b9a\u63a5\u901a\u6a4b\u63a5\u6216\u55ae\u4e00\u914d\u4ef6\u6a21\u5f0f\u3002\u5a92\u9ad4\u64ad\u653e\u5668\u9700\u8981\u4ee5\u96fb\u8996\u88dd\u7f6e\u914d\u4ef6\u6a21\u5f0f\u624d\u80fd\u6b63\u5e38\u4f7f\u7528\u3002\"\u5305\u542b Domains\"\u4e2d\u7684\u5be6\u9ad4\u5c07\u6703\u6a4b\u63a5\u81f3 Homekit\u3001\u53ef\u4ee5\u65bc\u4e0b\u4e00\u500b\u756b\u9762\u4e2d\u9078\u64c7\u6240\u8981\u5305\u542b\u6216\u6392\u9664\u7684\u5be6\u9ad4\u5217\u8868\u3002", - "title": "\u9078\u64c7\u6240\u8981\u63a5\u901a\u7684 Domain\u3002" + "description": "HomeKit \u80fd\u5920\u8a2d\u5b9a\u63a5\u901a\u6a4b\u63a5\u6216\u55ae\u4e00\u914d\u4ef6\u6a21\u5f0f\u3002 \u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u5a92\u9ad4\u64ad\u653e\u5668\u9700\u8981\u4ee5\u96fb\u8996\u88dd\u7f6e\u914d\u4ef6\u6a21\u5f0f\u624d\u80fd\u6b63\u5e38\u4f7f\u7528\u3002\"\u5305\u542b\u7db2\u57df\" \u4e2d\u7684\u5be6\u9ad4\u5c07\u6703\u6a4b\u63a5\u81f3 Homekit\u3001\u53ef\u4ee5\u65bc\u4e0b\u4e00\u500b\u756b\u9762\u4e2d\u9078\u64c7\u6240\u8981\u5305\u542b\u6216\u6392\u9664\u7684\u5be6\u9ad4\u5217\u8868\u3002", + "title": "\u9078\u64c7\u6240\u8981\u5305\u542b\u7684\u7db2\u57df\u3002" }, "yaml": { "description": "\u6b64\u5be6\u9ad4\u70ba\u900f\u904e YAML \u63a7\u5236", diff --git a/homeassistant/components/homekit_controller/translations/cs.json b/homeassistant/components/homekit_controller/translations/cs.json index 9a2159eda0599d..d5f7a50292118b 100644 --- a/homeassistant/components/homekit_controller/translations/cs.json +++ b/homeassistant/components/homekit_controller/translations/cs.json @@ -62,9 +62,9 @@ "doorbell": "Zvonek" }, "trigger_type": { - "double_press": "Dvakr\u00e1t stisknuto \"{subtype}\"", - "long_press": "Stisknuto a podr\u017eeno \"{subtype}\"", - "single_press": "Stisknuto \"{subtype}\"" + "double_press": "\"{subtype}\" stisknuto dvakr\u00e1t", + "long_press": "\"{subtype}\" stisknuto a podr\u017eeno", + "single_press": "\"{subtype}\" stisknuto" } }, "title": "HomeKit ovlada\u010d" diff --git a/homeassistant/components/homekit_controller/translations/de.json b/homeassistant/components/homekit_controller/translations/de.json index 3d5c538b62b135..7bab8f30574730 100644 --- a/homeassistant/components/homekit_controller/translations/de.json +++ b/homeassistant/components/homekit_controller/translations/de.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Die Kopplung kann nicht durchgef\u00fchrt werden, da das Ger\u00e4t nicht mehr gefunden werden kann.", "already_configured": "Das Zubeh\u00f6r ist mit diesem Controller bereits konfiguriert.", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "already_paired": "Dieses Zubeh\u00f6r ist bereits mit einem anderen Ger\u00e4t gekoppelt. Setze das Zubeh\u00f6r zur\u00fcck und versuche es erneut.", "ignored_model": "Die Unterst\u00fctzung von HomeKit f\u00fcr dieses Modell ist blockiert, da eine vollst\u00e4ndige native Integration verf\u00fcgbar ist.", "invalid_config_entry": "Dieses Ger\u00e4t wird als bereit zum Koppeln angezeigt, es gibt jedoch bereits einen widerspr\u00fcchlichen Konfigurationseintrag in Home Assistant, der zuerst entfernt werden muss.", @@ -30,7 +30,7 @@ "device": "Ger\u00e4t" }, "description": "W\u00e4hle das Ger\u00e4t aus, mit dem du die Kopplung herstellen m\u00f6chtest", - "title": "Mit HomeKit Zubeh\u00f6r koppeln" + "title": "Ger\u00e4teauswahl" } } }, diff --git a/homeassistant/components/homekit_controller/translations/pl.json b/homeassistant/components/homekit_controller/translations/pl.json index 50fdf6a17e425f..3ccdfe452e559b 100644 --- a/homeassistant/components/homekit_controller/translations/pl.json +++ b/homeassistant/components/homekit_controller/translations/pl.json @@ -62,9 +62,9 @@ "doorbell": "dzwonek do drzwi" }, "trigger_type": { - "double_press": "\"{subtype}\" naci\u015bni\u0119ty dwukrotnie", - "long_press": "\"{subtype}\" naci\u015bni\u0119ty i przytrzymany", - "single_press": "\"{subtype}\" naci\u015bni\u0119ty" + "double_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty dwukrotnie", + "long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty i przytrzymany", + "single_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty" } }, "title": "Kontroler HomeKit" diff --git a/homeassistant/components/homekit_controller/translations/tr.json b/homeassistant/components/homekit_controller/translations/tr.json new file mode 100644 index 00000000000000..9d72049ba21920 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Aksesuar zaten bu denetleyici ile yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r.", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "authentication_error": "Yanl\u0131\u015f HomeKit kodu. L\u00fctfen kontrol edip tekrar deneyin.", + "unknown_error": "Cihaz bilinmeyen bir hata bildirdi. E\u015fle\u015ftirme ba\u015far\u0131s\u0131z oldu." + }, + "step": { + "busy_error": { + "title": "Cihaz zaten ba\u015fka bir oyun kumandas\u0131yla e\u015fle\u015fiyor" + }, + "max_tries_error": { + "title": "Maksimum kimlik do\u011frulama giri\u015fimi a\u015f\u0131ld\u0131" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "D\u00fc\u011fme 1", + "button10": "D\u00fc\u011fme 10", + "button2": "D\u00fc\u011fme 2", + "button3": "D\u00fc\u011fme 3", + "button4": "D\u00fc\u011fme 4", + "button5": "D\u00fc\u011fme 5", + "button6": "D\u00fc\u011fme 6", + "button7": "D\u00fc\u011fme 7", + "button8": "D\u00fc\u011fme 8", + "button9": "D\u00fc\u011fme 9", + "doorbell": "Kap\u0131 zili" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/uk.json b/homeassistant/components/homekit_controller/translations/uk.json new file mode 100644 index 00000000000000..66eb1741208dea --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/uk.json @@ -0,0 +1,71 @@ +{ + "config": { + "abort": { + "accessory_not_found_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u0442\u0438 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f, \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "already_configured": "\u0410\u043a\u0441\u0435\u0441\u0443\u0430\u0440 \u0432\u0436\u0435 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0439 \u0437 \u0446\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u043e\u043c.", + "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.", + "already_paired": "\u0426\u0435\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440 \u0432\u0436\u0435 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0439 \u0437 \u0456\u043d\u0448\u0438\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043a\u0438\u043d\u044c\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "ignored_model": "\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 HomeKit \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u043c\u043e\u0434\u0435\u043b\u0456 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u0430, \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0431\u0456\u043b\u044c\u0448 \u043f\u043e\u0432\u043d\u0430 \u043d\u0430\u0442\u0438\u0432\u043d\u0430 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f.", + "invalid_config_entry": "\u0426\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u044f\u043a \u0433\u043e\u0442\u043e\u0432\u0438\u0439 \u0434\u043e \u043e\u0431'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0432 \u043f\u0430\u0440\u0443, \u0430\u043b\u0435 \u0432 Home Assistant \u0432\u0436\u0435 \u0454 \u043a\u043e\u043d\u0444\u043b\u0456\u043a\u0442\u043d\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 \u0434\u043b\u044f \u043d\u044c\u043e\u0433\u043e, \u044f\u043a\u0438\u0439 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438.", + "invalid_properties": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0456 \u0432\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456, \u043e\u0433\u043e\u043b\u043e\u0448\u0435\u043d\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c.", + "no_devices": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043b\u044f \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f, \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456." + }, + "error": { + "authentication_error": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434 HomeKit. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043a\u043e\u0434 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "max_peers_error": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0456\u0434\u0445\u0438\u043b\u0438\u0432 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0447\u0435\u0440\u0435\u0437 \u0432\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c \u0432\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u044f.", + "pairing_failed": "\u041f\u0456\u0434 \u0447\u0430\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 \u0441\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430. \u0426\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0442\u0438\u043c\u0447\u0430\u0441\u043e\u0432\u0438\u0439 \u0437\u0431\u0456\u0439 \u0430\u0431\u043e \u0412\u0430\u0448 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0449\u0435 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "unable_to_pair": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "unknown_error": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u0438\u0432 \u043f\u0440\u043e \u043d\u0435\u0432\u0456\u0434\u043e\u043c\u0443 \u043f\u043e\u043c\u0438\u043b\u043a\u0443. \u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443." + }, + "flow_title": "{name} \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0456\u0432 HomeKit", + "step": { + "busy_error": { + "description": "\u0421\u043a\u0430\u0441\u0443\u0439\u0442\u0435 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u043d\u0430 \u0432\u0441\u0456\u0445 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430\u0445 \u0430\u0431\u043e \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f.", + "title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0443\u0436\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e \u0456\u043d\u0448\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430." + }, + "max_tries_error": { + "description": "\u041f\u043e\u043d\u0430\u0434 100 \u0441\u043f\u0440\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 \u043f\u0440\u043e\u0439\u0448\u043b\u0438 \u043d\u0435\u0432\u0434\u0430\u043b\u043e. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0430 \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f.", + "title": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0441\u043f\u0440\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457." + }, + "pair": { + "data": { + "pairing_code": "\u041a\u043e\u0434 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "description": "HomeKit Controller \u043e\u0431\u043c\u0456\u043d\u044e\u0454\u0442\u044c\u0441\u044f \u0434\u0430\u043d\u0438\u043c\u0438 \u0437 {name} \u043f\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0447\u0438 \u0431\u0435\u0437\u043f\u0435\u0447\u043d\u0435 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0435 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e\u0441\u0442\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u043e\u043a\u0440\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 HomeKit \u0430\u0431\u043e iCloud. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u0441\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044f HomeKit (\u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 XXX-XX-XXX), \u0449\u043e\u0431 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440. \u0426\u0435\u0439 \u043a\u043e\u0434 \u0437\u0430\u0437\u0432\u0438\u0447\u0430\u0439 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0430\u043c\u043e\u043c\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0430\u0431\u043e \u043d\u0430 \u0443\u043f\u0430\u043a\u043e\u0432\u0446\u0456.", + "title": "\u0421\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0456\u0432 HomeKit" + }, + "protocol_error": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u043c\u043e\u0436\u043b\u0438\u0432\u043e, \u043d\u0435 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0456 \u043c\u043e\u0436\u0435 \u0437\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0438\u0441\u044f \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f \u0444\u0456\u0437\u0438\u0447\u043d\u043e\u0457 \u0430\u0431\u043e \u0432\u0456\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0457 \u043a\u043d\u043e\u043f\u043a\u0438. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438, \u0430\u0431\u043e \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0456 \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f.", + "title": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u043e\u043c." + }, + "user": { + "data": { + "device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "description": "HomeKit Controller \u043e\u0431\u043c\u0456\u043d\u044e\u0454\u0442\u044c\u0441\u044f \u0434\u0430\u043d\u0438\u043c\u0438 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456 \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u0431\u0435\u0437\u043f\u0435\u0447\u043d\u043e\u0433\u043e \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043e\u0433\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e\u0441\u0442\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u043e\u043a\u0440\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 HomeKit \u0430\u0431\u043e iCloud. \u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0437 \u044f\u043a\u0438\u043c \u0445\u043e\u0447\u0435\u0442\u0435 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443:", + "title": "\u0412\u0438\u0431\u0456\u0440 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "\u041a\u043d\u043e\u043f\u043a\u0430 1", + "button10": "\u041a\u043d\u043e\u043f\u043a\u0430 10", + "button2": "\u041a\u043d\u043e\u043f\u043a\u0430 2", + "button3": "\u041a\u043d\u043e\u043f\u043a\u0430 3", + "button4": "\u041a\u043d\u043e\u043f\u043a\u0430 4", + "button5": "\u041a\u043d\u043e\u043f\u043a\u0430 5", + "button6": "\u041a\u043d\u043e\u043f\u043a\u0430 6", + "button7": "\u041a\u043d\u043e\u043f\u043a\u0430 7", + "button8": "\u041a\u043d\u043e\u043f\u043a\u0430 8", + "button9": "\u041a\u043d\u043e\u043f\u043a\u0430 9", + "doorbell": "\u0414\u0432\u0435\u0440\u043d\u0438\u0439 \u0434\u0437\u0432\u0456\u043d\u043e\u043a" + }, + "trigger_type": { + "double_press": "\"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0456\u0447\u0456", + "long_press": "\"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0456 \u0443\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f", + "single_press": "\"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430" + } + }, + "title": "HomeKit Controller" +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/de.json b/homeassistant/components/homematicip_cloud/translations/de.json index c421620fd98830..1da1e06c0fb8d8 100644 --- a/homeassistant/components/homematicip_cloud/translations/de.json +++ b/homeassistant/components/homematicip_cloud/translations/de.json @@ -2,11 +2,11 @@ "config": { "abort": { "already_configured": "Der Accesspoint ist bereits konfiguriert", - "connection_aborted": "Konnte nicht mit HMIP Server verbinden", - "unknown": "Ein unbekannter Fehler ist aufgetreten." + "connection_aborted": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" }, "error": { - "invalid_sgtin_or_pin": "Ung\u00fcltige PIN, bitte versuche es erneut.", + "invalid_sgtin_or_pin": "Ung\u00fcltige SGTIN oder PIN-Code, bitte versuche es erneut.", "press_the_button": "Bitte dr\u00fccke die blaue Taste.", "register_failed": "Registrierung fehlgeschlagen, bitte versuche es erneut.", "timeout_button": "Zeit\u00fcberschreitung beim Dr\u00fccken der blauen Taste. Bitte versuche es erneut." @@ -16,7 +16,7 @@ "data": { "hapid": "Accesspoint ID (SGTIN)", "name": "Name (optional, wird als Pr\u00e4fix f\u00fcr alle Ger\u00e4te verwendet)", - "pin": "PIN Code (optional)" + "pin": "PIN-Code" }, "title": "HomematicIP Accesspoint ausw\u00e4hlen" }, diff --git a/homeassistant/components/homematicip_cloud/translations/tr.json b/homeassistant/components/homematicip_cloud/translations/tr.json new file mode 100644 index 00000000000000..72f139217cace4 --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/tr.json @@ -0,0 +1,9 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "connection_aborted": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/uk.json b/homeassistant/components/homematicip_cloud/translations/uk.json new file mode 100644 index 00000000000000..1ed2e317f8b29e --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/uk.json @@ -0,0 +1,29 @@ +{ + "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.", + "connection_aborted": "\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" + }, + "error": { + "invalid_sgtin_or_pin": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 SGTIN \u0430\u0431\u043e PIN-\u043a\u043e\u0434, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "press_the_button": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0441\u0438\u043d\u044e \u043a\u043d\u043e\u043f\u043a\u0443.", + "register_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "timeout_button": "\u0412\u0438 \u043d\u0435 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u043b\u0438 \u043d\u0430 \u0441\u0438\u043d\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u0432 \u043c\u0435\u0436\u0430\u0445 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u0447\u0430\u0441\u0443, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "init": { + "data": { + "hapid": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0442\u043e\u0447\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 (SGTIN)", + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u044f\u043a \u043f\u0440\u0435\u0444\u0456\u043a\u0441 \u0434\u043b\u044f \u043d\u0430\u0437\u0432\u0438 \u0432\u0441\u0456\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432)", + "pin": "PIN-\u043a\u043e\u0434" + }, + "title": "HomematicIP Cloud" + }, + "link": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0441\u0438\u043d\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0442\u043e\u0447\u0446\u0456 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0456 \u043a\u043d\u043e\u043f\u043a\u0443 ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0449\u043e\u0431 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438 HomematicIP \u0432 Home Assistant. \n\n![\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438] (/static/images/config_flows/config_homematicip_cloud.png)", + "title": "\u041f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/de.json b/homeassistant/components/huawei_lte/translations/de.json index 7da997f12d6645..43361e46929aa1 100644 --- a/homeassistant/components/huawei_lte/translations/de.json +++ b/homeassistant/components/huawei_lte/translations/de.json @@ -1,14 +1,15 @@ { "config": { "abort": { - "already_configured": "Dieses Ger\u00e4t wurde bereits konfiguriert", - "already_in_progress": "Dieses Ger\u00e4t wurde bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "not_huawei_lte": "Kein Huawei LTE-Ger\u00e4t" }, "error": { "connection_timeout": "Verbindungszeit\u00fcberschreitung", "incorrect_password": "Ung\u00fcltiges Passwort", "incorrect_username": "Ung\u00fcltiger Benutzername", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_url": "Ung\u00fcltige URL", "login_attempts_exceeded": "Maximale Anzahl von Anmeldeversuchen \u00fcberschritten. Bitte versuche es sp\u00e4ter erneut", "response_error": "Unbekannter Fehler vom Ger\u00e4t", diff --git a/homeassistant/components/huawei_lte/translations/tr.json b/homeassistant/components/huawei_lte/translations/tr.json index a76e31fa483542..ba934acc39b8c8 100644 --- a/homeassistant/components/huawei_lte/translations/tr.json +++ b/homeassistant/components/huawei_lte/translations/tr.json @@ -1,8 +1,35 @@ { + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "connection_timeout": "Ba\u011flant\u0131 zamana\u015f\u0131m\u0131", + "incorrect_password": "Yanl\u0131\u015f parola", + "incorrect_username": "Yanl\u0131\u015f kullan\u0131c\u0131 ad\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_url": "Ge\u00e7ersiz URL", + "login_attempts_exceeded": "Maksimum oturum a\u00e7ma denemesi a\u015f\u0131ld\u0131, l\u00fctfen daha sonra tekrar deneyin", + "response_error": "Cihazdan bilinmeyen hata", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "url": "URL", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Cihaz eri\u015fim ayr\u0131nt\u0131lar\u0131n\u0131 girin. Kullan\u0131c\u0131 ad\u0131 ve parolan\u0131n belirtilmesi iste\u011fe ba\u011fl\u0131d\u0131r, ancak daha fazla entegrasyon \u00f6zelli\u011fi i\u00e7in destek sa\u011flar. \u00d6te yandan, yetkili bir ba\u011flant\u0131n\u0131n kullan\u0131lmas\u0131, entegrasyon aktifken Ev Asistan\u0131 d\u0131\u015f\u0131ndan cihaz web aray\u00fcz\u00fcne eri\u015fimde sorunlara neden olabilir ve tam tersi." + } + } + }, "options": { "step": { "init": { "data": { + "recipient": "SMS bildirimi al\u0131c\u0131lar\u0131", "track_new_devices": "Yeni cihazlar\u0131 izle" } } diff --git a/homeassistant/components/huawei_lte/translations/uk.json b/homeassistant/components/huawei_lte/translations/uk.json new file mode 100644 index 00000000000000..17f3d3b71c3386 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/uk.json @@ -0,0 +1,42 @@ +{ + "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_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.", + "not_huawei_lte": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Huawei LTE" + }, + "error": { + "connection_timeout": "\u0427\u0430\u0441 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u0438\u043d\u0443\u0432.", + "incorrect_password": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", + "incorrect_username": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0456\u043c\u2019\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "invalid_url": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430.", + "login_attempts_exceeded": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0441\u043f\u0440\u043e\u0431 \u0432\u0445\u043e\u0434\u0443, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", + "response_error": "\u041d\u0435\u0432\u0456\u0434\u043e\u043c\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Huawei LTE: {name}", + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u0412\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043b\u043e\u0433\u0456\u043d \u0456 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0430\u043b\u0435 \u0446\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u044c \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0444\u0443\u043d\u043a\u0446\u0456\u0457. \u0417 \u0456\u043d\u0448\u043e\u0433\u043e \u0431\u043e\u043a\u0443, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043e\u0433\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043b\u0438\u043a\u0430\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u0434\u043e \u0432\u0435\u0431-\u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437 Home Assistant, \u043a\u043e\u043b\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430, \u0456 \u043d\u0430\u0432\u043f\u0430\u043a\u0438.", + "title": "Huawei LTE" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430 \u0441\u043b\u0443\u0436\u0431\u0438 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c (\u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a)", + "recipient": "\u041e\u0434\u0435\u0440\u0436\u0443\u0432\u0430\u0447\u0456 SMS-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c", + "track_new_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438 \u043d\u043e\u0432\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/cs.json b/homeassistant/components/hue/translations/cs.json index 76606338320a14..1708abfe750173 100644 --- a/homeassistant/components/hue/translations/cs.json +++ b/homeassistant/components/hue/translations/cs.json @@ -48,7 +48,7 @@ }, "trigger_type": { "remote_button_long_release": "Tla\u010d\u00edtko \"{subtype}\" uvoln\u011bno po dlouh\u00e9m stisku", - "remote_button_short_press": "Stisknuto tla\u010d\u00edtko \"{subtype}\"", + "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" diff --git a/homeassistant/components/hue/translations/de.json b/homeassistant/components/hue/translations/de.json index 0defb33ae5e509..122e1ba6f5c35a 100644 --- a/homeassistant/components/hue/translations/de.json +++ b/homeassistant/components/hue/translations/de.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "all_configured": "Alle Philips Hue Bridges sind bereits konfiguriert", - "already_configured": "Bridge ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt.", - "cannot_connect": "Verbindung zur Bridge nicht m\u00f6glich", - "discover_timeout": "Nicht in der Lage Hue Bridges zu entdecken", - "no_bridges": "Keine Philips Hue Bridges entdeckt", + "all_configured": "Es sind bereits alle Philips Hue Bridges konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen", + "discover_timeout": "Es k\u00f6nnen keine Hue Bridges erkannt werden", + "no_bridges": "Keine Philips Hue Bridges erkannt", "not_hue_bridge": "Keine Philips Hue Bridge entdeckt", "unknown": "Unbekannter Fehler ist aufgetreten" }, "error": { - "linking": "Unbekannter Link-Fehler aufgetreten.", + "linking": "Unerwarteter Fehler", "register_failed": "Registrieren fehlgeschlagen, bitte versuche es erneut" }, "step": { @@ -22,7 +22,7 @@ "title": "W\u00e4hle eine Hue Bridge" }, "link": { - "description": "Dr\u00fccke den Knopf auf der Bridge, um Philips Hue mit Home Assistant zu registrieren.\n\n![Position des Buttons auf der Bridge](/static/images/config_philips_hue.jpg)", + "description": "Dr\u00fccke den Knopf auf der Bridge, um Philips Hue mit Home Assistant zu verkn\u00fcpfen.\n\n![Position des Buttons auf der Bridge](/static/images/config_philips_hue.jpg)", "title": "Hub verbinden" }, "manual": { @@ -58,8 +58,8 @@ "step": { "init": { "data": { - "allow_hue_groups": "Erlaube Hue Gruppen", - "allow_unreachable": "Erlauben Sie unerreichbaren Gl\u00fchbirnen, ihren Zustand korrekt zu melden" + "allow_hue_groups": "Hue-Gruppen erlauben", + "allow_unreachable": "Erlaube nicht erreichbaren Gl\u00fchlampen, ihren Zustand korrekt zu melden" } } } diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index 873f60946d5a80..b144393c3d1bae 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -47,11 +47,11 @@ "turn_on": "w\u0142\u0105cznik" }, "trigger_type": { - "remote_button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", - "remote_button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", - "remote_button_short_release": "\"{subtype}\" zostanie zwolniony", - "remote_double_button_long_press": "oba \"{subtype}\" zostan\u0105 zwolnione po d\u0142ugim naci\u015bni\u0119ciu", - "remote_double_button_short_press": "oba \"{subtype}\" zostan\u0105 zwolnione" + "remote_button_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "remote_button_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "remote_button_short_release": "przycisk \"{subtype}\" zostanie zwolniony", + "remote_double_button_long_press": "oba przyciski \"{subtype}\" zostan\u0105 zwolnione po d\u0142ugim naci\u015bni\u0119ciu", + "remote_double_button_short_press": "oba przyciski \"{subtype}\" zostan\u0105 zwolnione" } }, "options": { diff --git a/homeassistant/components/hue/translations/pt.json b/homeassistant/components/hue/translations/pt.json index 8eabbbb08cc417..09d839cbd5c08a 100644 --- a/homeassistant/components/hue/translations/pt.json +++ b/homeassistant/components/hue/translations/pt.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "Todos os hubs Philips Hue j\u00e1 est\u00e3o configurados", - "already_configured": "Hue j\u00e1 est\u00e1 configurado", + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", - "cannot_connect": "N\u00e3o foi poss\u00edvel conectar-se ao hub", + "cannot_connect": "Falha na liga\u00e7\u00e3o", "discover_timeout": "Nenhum hub Hue descoberto", "no_bridges": "Nenhum hub Philips Hue descoberto", "not_hue_bridge": "N\u00e3o \u00e9 uma bridge Hue", - "unknown": "Ocorreu um erro desconhecido" + "unknown": "Erro inesperado" }, "error": { - "linking": "Ocorreu um erro de liga\u00e7\u00e3o desconhecido.", + "linking": "Erro inesperado", "register_failed": "Falha ao registar, por favor, tente novamente" }, "step": { diff --git a/homeassistant/components/hue/translations/tr.json b/homeassistant/components/hue/translations/tr.json new file mode 100644 index 00000000000000..984c91e8f366b0 --- /dev/null +++ b/homeassistant/components/hue/translations/tr.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "error": { + "linking": "Beklenmeyen hata" + }, + "step": { + "init": { + "data": { + "host": "Ana Bilgisayar" + } + }, + "manual": { + "data": { + "host": "Ana Bilgisayar" + }, + "title": "Bir Hue k\u00f6pr\u00fcs\u00fcn\u00fc manuel olarak yap\u0131land\u0131rma" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u0130lk d\u00fc\u011fme", + "button_2": "\u0130kinci d\u00fc\u011fme", + "button_3": "\u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fme", + "button_4": "D\u00f6rd\u00fcnc\u00fc d\u00fc\u011fme", + "double_buttons_1_3": "Birinci ve \u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fmeler", + "double_buttons_2_4": "\u0130kinci ve D\u00f6rd\u00fcnc\u00fc d\u00fc\u011fmeler", + "turn_off": "Kapat", + "turn_on": "A\u00e7" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_groups": "Hue gruplar\u0131na izin ver", + "allow_unreachable": "Ula\u015f\u0131lamayan ampullerin durumlar\u0131n\u0131 do\u011fru \u015fekilde bildirmesine izin verin" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/uk.json b/homeassistant/components/hue/translations/uk.json new file mode 100644 index 00000000000000..8e9c5ca82cbbf3 --- /dev/null +++ b/homeassistant/components/hue/translations/uk.json @@ -0,0 +1,67 @@ +{ + "config": { + "abort": { + "all_configured": "\u0412\u0441\u0456 \u0448\u043b\u044e\u0437\u0438 Philips Hue \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0456.", + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "discover_timeout": "\u0428\u043b\u044e\u0437 Philips Hue \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "no_bridges": "\u0428\u043b\u044e\u0437\u0438 Philips Hue \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456.", + "not_hue_bridge": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u0448\u043b\u044e\u0437\u043e\u043c Hue.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "linking": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "register_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "init": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0448\u043b\u044e\u0437 Hue" + }, + "link": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0448\u043b\u044e\u0437\u0456 \u0434\u043b\u044f \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u0457 Philips Hue \u0432 Home Assistant. \n\n![\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043d\u0430 \u0448\u043b\u044e\u0437\u0456] (/static/images/config_philips_hue.jpg)", + "title": "Philips Hue" + }, + "manual": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "title": "\u0420\u0443\u0447\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0448\u043b\u044e\u0437\u0443" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "dim_down": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "dim_up": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "double_buttons_1_3": "\u041f\u0435\u0440\u0448\u0430 \u0456 \u0442\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0438", + "double_buttons_2_4": "\u0414\u0440\u0443\u0433\u0430 \u0456 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0438", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "remote_button_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_short_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_double_button_long_press": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u043e \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_double_button_short_press": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u043e \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_groups": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0433\u0440\u0443\u043f\u0438 Hue", + "allow_unreachable": "\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u044f\u0442\u0438 \u0441\u0442\u0430\u043d \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/ca.json b/homeassistant/components/huisbaasje/translations/ca.json new file mode 100644 index 00000000000000..99d99d4340f769 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "connection_exception": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unauthenticated_exception": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/cs.json b/homeassistant/components/huisbaasje/translations/cs.json new file mode 100644 index 00000000000000..07a1d29330b60b --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "connection_exception": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unauthenticated_exception": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/en.json b/homeassistant/components/huisbaasje/translations/en.json new file mode 100644 index 00000000000000..16832be30e75bb --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "connection_exception": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unauthenticated_exception": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/et.json b/homeassistant/components/huisbaasje/translations/et.json new file mode 100644 index 00000000000000..d079bf2a0c7bb1 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "connection_exception": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unauthenticated_exception": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/it.json b/homeassistant/components/huisbaasje/translations/it.json new file mode 100644 index 00000000000000..0171bdcd9f27ca --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "connection_exception": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unauthenticated_exception": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/no.json b/homeassistant/components/huisbaasje/translations/no.json new file mode 100644 index 00000000000000..81351599c1672f --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "connection_exception": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unauthenticated_exception": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/pl.json b/homeassistant/components/huisbaasje/translations/pl.json new file mode 100644 index 00000000000000..ab38d61de0be0d --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "connection_exception": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unauthenticated_exception": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json new file mode 100644 index 00000000000000..ada9aed539afd8 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/ru.json @@ -0,0 +1,21 @@ +{ + "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": { + "connection_exception": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "unauthenticated_exception": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/tr.json b/homeassistant/components/huisbaasje/translations/tr.json new file mode 100644 index 00000000000000..fa5bd3112861b5 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "connection_exception": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unauthenticated_exception": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen Hata" + }, + "step": { + "user": { + "data": { + "password": "\u015eifre", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/zh-Hant.json b/homeassistant/components/huisbaasje/translations/zh-Hant.json new file mode 100644 index 00000000000000..bb120ab60ddad0 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "connection_exception": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unauthenticated_exception": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/tr.json b/homeassistant/components/humidifier/translations/tr.json new file mode 100644 index 00000000000000..7bcdbc46a0b2d7 --- /dev/null +++ b/homeassistant/components/humidifier/translations/tr.json @@ -0,0 +1,25 @@ +{ + "device_automation": { + "action_type": { + "set_mode": "{entity_name} \u00fczerindeki mod de\u011fi\u015ftirme", + "turn_on": "{entity_name} a\u00e7\u0131n" + }, + "condition_type": { + "is_mode": "{entity_name} belirli bir moda ayarland\u0131", + "is_off": "{entity_name} kapal\u0131", + "is_on": "{entity_name} a\u00e7\u0131k" + }, + "trigger_type": { + "target_humidity_changed": "{entity_name} hedef nem de\u011fi\u015fti", + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, + "state": { + "_": { + "off": "Kapal\u0131", + "on": "A\u00e7\u0131k" + } + }, + "title": "Nemlendirici" +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/uk.json b/homeassistant/components/humidifier/translations/uk.json index 4081c4e13fc281..484f014bd92fbe 100644 --- a/homeassistant/components/humidifier/translations/uk.json +++ b/homeassistant/components/humidifier/translations/uk.json @@ -1,8 +1,28 @@ { "device_automation": { + "action_type": { + "set_humidity": "{entity_name}: \u0437\u0430\u0434\u0430\u0442\u0438 \u0432\u043e\u043b\u043e\u0433\u0456\u0441\u0442\u044c", + "set_mode": "{entity_name}: \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c", + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_mode": "{entity_name} \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0434\u0430\u043d\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456 \u0440\u043e\u0431\u043e\u0442\u0438", + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { + "target_humidity_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0437\u0430\u0434\u0430\u043d\u043e\u0457 \u0432\u043e\u043b\u043e\u0433\u043e\u0441\u0442\u0456", "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", - "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u043e" + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } - } + }, + "state": { + "_": { + "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + } + }, + "title": "\u0417\u0432\u043e\u043b\u043e\u0436\u0443\u0432\u0430\u0447" } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/tr.json b/homeassistant/components/hunterdouglas_powerview/translations/tr.json new file mode 100644 index 00000000000000..01b0359789e8e8 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/tr.json @@ -0,0 +1,18 @@ +{ + "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": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/uk.json b/homeassistant/components/hunterdouglas_powerview/translations/uk.json new file mode 100644 index 00000000000000..959fcff12b0cdd --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "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": { + "link": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", + "title": "Hunter Douglas PowerView" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "title": "Hunter Douglas PowerView" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/tr.json b/homeassistant/components/hvv_departures/translations/tr.json new file mode 100644 index 00000000000000..74fc593062b960 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/uk.json b/homeassistant/components/hvv_departures/translations/uk.json new file mode 100644 index 00000000000000..364d351a99c3dc --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/uk.json @@ -0,0 +1,47 @@ +{ + "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." + }, + "error": { + "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.", + "no_results": "\u041d\u0435\u043c\u0430\u0454 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437 \u0456\u043d\u0448\u043e\u044e \u0441\u0442\u0430\u043d\u0446\u0456\u0454\u044e / \u0430\u0434\u0440\u0435\u0441\u043e\u044e." + }, + "step": { + "station": { + "data": { + "station": "\u0421\u0442\u0430\u043d\u0446\u0456\u044f / \u0410\u0434\u0440\u0435\u0441\u0430" + }, + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0456\u044e / \u0430\u0434\u0440\u0435\u0441\u0443" + }, + "station_select": { + "data": { + "station": "\u0421\u0442\u0430\u043d\u0446\u0456\u044f / \u0410\u0434\u0440\u0435\u0441\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0456\u044e / \u0430\u0434\u0440\u0435\u0441\u0443" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e API HVV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043b\u0456\u043d\u0456\u0457", + "offset": "\u0417\u043c\u0456\u0449\u0435\u043d\u043d\u044f (\u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)", + "real_time": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0430\u043d\u0456 \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0447\u0430\u0441\u0443" + }, + "description": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044f", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/fr.json b/homeassistant/components/hyperion/translations/fr.json index 8c1cb919d11f12..90733a8968bd34 100644 --- a/homeassistant/components/hyperion/translations/fr.json +++ b/homeassistant/components/hyperion/translations/fr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "auth_new_token_not_work_error": "\u00c9chec de l'authentification \u00e0 l'aide du jeton nouvellement cr\u00e9\u00e9", + "cannot_connect": "Echec de connection" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/hyperion/translations/it.json b/homeassistant/components/hyperion/translations/it.json index ff3170ffb98450..6fee49ebe14706 100644 --- a/homeassistant/components/hyperion/translations/it.json +++ b/homeassistant/components/hyperion/translations/it.json @@ -8,7 +8,7 @@ "auth_required_error": "Impossibile determinare se \u00e8 necessaria l'autorizzazione", "cannot_connect": "Impossibile connettersi", "no_id": "L'istanza Hyperion Ambilight non ha segnalato il suo ID", - "reauth_successful": "Ri-autenticazione completata con successo" + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/hyperion/translations/tr.json b/homeassistant/components/hyperion/translations/tr.json index 6f46000e3e20b6..7b3f9f845a1329 100644 --- a/homeassistant/components/hyperion/translations/tr.json +++ b/homeassistant/components/hyperion/translations/tr.json @@ -2,11 +2,17 @@ "config": { "abort": { "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "auth_new_token_not_granted_error": "Hyperion UI'de yeni olu\u015fturulan belirte\u00e7 onaylanmad\u0131", "auth_new_token_not_work_error": "Yeni olu\u015fturulan belirte\u00e7 kullan\u0131larak kimlik do\u011frulamas\u0131 ba\u015far\u0131s\u0131z oldu", "auth_required_error": "Yetkilendirmenin gerekli olup olmad\u0131\u011f\u0131 belirlenemedi", "cannot_connect": "Ba\u011flanma hatas\u0131", - "no_id": "Hyperion Ambilight \u00f6rne\u011fi kimli\u011fini bildirmedi" + "no_id": "Hyperion Ambilight \u00f6rne\u011fi kimli\u011fini bildirmedi", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci" }, "step": { "auth": { @@ -15,6 +21,9 @@ "token": "Veya \u00f6nceden varolan belirte\u00e7 leri sa\u011flay\u0131n" } }, + "confirm": { + "title": "Hyperion Ambilight hizmetinin eklenmesini onaylay\u0131n" + }, "create_token": { "title": "Otomatik olarak yeni kimlik do\u011frulama belirteci olu\u015fturun" }, @@ -23,6 +32,7 @@ }, "user": { "data": { + "host": "Ana Bilgisayar", "port": "Port" } } diff --git a/homeassistant/components/hyperion/translations/uk.json b/homeassistant/components/hyperion/translations/uk.json new file mode 100644 index 00000000000000..ae44b0610da56c --- /dev/null +++ b/homeassistant/components/hyperion/translations/uk.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \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.", + "auth_new_token_not_granted_error": "\u0421\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0431\u0443\u0432 \u0441\u0445\u0432\u0430\u043b\u0435\u043d\u0438\u0439 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Hyperion.", + "auth_new_token_not_work_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430.", + "auth_required_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438, \u0447\u0438 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\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", + "no_id": "Hyperion Ambilight \u043d\u0435 \u043d\u0430\u0434\u0430\u0432 \u0441\u0432\u0456\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440.", + "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" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443." + }, + "step": { + "auth": { + "data": { + "create_token": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d", + "token": "\u0410\u0431\u043e \u043d\u0430\u0434\u0430\u0442\u0438 \u043d\u0430\u044f\u0432\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d" + }, + "description": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0456 Hyperion Ambilight." + }, + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Hyperion Ambilight? \n\n ** \u0425\u043e\u0441\u0442: ** {host}\n ** \u041f\u043e\u0440\u0442: ** {port}\n ** ID **: {id}", + "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u0441\u043b\u0443\u0436\u0431\u0438 Hyperion Ambilight" + }, + "create_token": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438 ** \u043d\u0438\u0436\u0447\u0435, \u0449\u043e\u0431 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457. \u0412\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0456 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 Hyperion \u0434\u043b\u044f \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f \u0437\u0430\u043f\u0438\u0442\u0443. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 - \" {auth_id} \"", + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "create_token_external": { + "title": "\u041f\u0440\u0438\u0439\u043d\u044f\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Hyperion" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "\u041f\u0440\u0456\u043e\u0440\u0438\u0442\u0435\u0442 Hyperion \u0434\u043b\u044f \u043a\u043e\u043b\u044c\u043e\u0440\u0456\u0432 \u0456 \u0435\u0444\u0435\u043a\u0442\u0456\u0432" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/de.json b/homeassistant/components/iaqualink/translations/de.json index e7e1002015c36a..0a678baf7ca765 100644 --- a/homeassistant/components/iaqualink/translations/de.json +++ b/homeassistant/components/iaqualink/translations/de.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/iaqualink/translations/tr.json b/homeassistant/components/iaqualink/translations/tr.json new file mode 100644 index 00000000000000..c2c70f3e45b398 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "L\u00fctfen iAqualink hesab\u0131n\u0131z i\u00e7in kullan\u0131c\u0131 ad\u0131 ve parolay\u0131 girin." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/uk.json b/homeassistant/components/iaqualink/translations/uk.json new file mode 100644 index 00000000000000..b855d75572603c --- /dev/null +++ b/homeassistant/components/iaqualink/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043b\u043e\u0433\u0456\u043d \u0456 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0412\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 iAqualink.", + "title": "Jandy iAqualink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/de.json b/homeassistant/components/icloud/translations/de.json index e7441792d91231..64a6bcd885c542 100644 --- a/homeassistant/components/icloud/translations/de.json +++ b/homeassistant/components/icloud/translations/de.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Konto bereits konfiguriert", - "no_device": "Auf keinem Ihrer Ger\u00e4te ist \"Find my iPhone\" aktiviert" + "already_configured": "Konto wurde bereits konfiguriert", + "no_device": "Auf keinem Ihrer Ger\u00e4te ist \"Find my iPhone\" aktiviert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "send_verification_code": "Fehler beim Senden des Best\u00e4tigungscodes", "validate_verification_code": "Verifizierung des Verifizierungscodes fehlgeschlagen. W\u00e4hle ein vertrauensw\u00fcrdiges Ger\u00e4t aus und starte die Verifizierung erneut" }, @@ -12,7 +14,8 @@ "reauth": { "data": { "password": "Passwort" - } + }, + "title": "Integration erneut authentifizieren" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/tr.json b/homeassistant/components/icloud/translations/tr.json index 3d74852ce50ecb..86581625d96e27 100644 --- a/homeassistant/components/icloud/translations/tr.json +++ b/homeassistant/components/icloud/translations/tr.json @@ -1,7 +1,28 @@ { "config": { "abort": { - "no_device": "Hi\u00e7bir cihaz\u0131n\u0131zda \"iPhone'umu bul\" etkin de\u011fil" + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "no_device": "Hi\u00e7bir cihaz\u0131n\u0131zda \"iPhone'umu bul\" etkin de\u011fil", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "validate_verification_code": "Do\u011frulama kodunuzu do\u011frulamay\u0131 ba\u015faramad\u0131n\u0131z, bir g\u00fcven ayg\u0131t\u0131 se\u00e7in ve do\u011frulamay\u0131 yeniden ba\u015flat\u0131n" + }, + "step": { + "reauth": { + "data": { + "password": "Parola" + }, + "description": "{username} i\u00e7in \u00f6nceden girdi\u011finiz \u015fifreniz art\u0131k \u00e7al\u0131\u015fm\u0131yor. Bu entegrasyonu kullanmaya devam etmek i\u00e7in \u015fifrenizi g\u00fcncelleyin." + }, + "user": { + "data": { + "password": "Parola", + "username": "E-posta", + "with_family": "Aileyle" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/uk.json b/homeassistant/components/icloud/translations/uk.json new file mode 100644 index 00000000000000..ac65157f050bd1 --- /dev/null +++ b/homeassistant/components/icloud/translations/uk.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "no_device": "\u041d\u0430 \u0436\u043e\u0434\u043d\u043e\u043c\u0443 \u0437 \u0412\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0456\u044f \"\u0417\u043d\u0430\u0439\u0442\u0438 iPhone\".", + "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" + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "send_verification_code": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u0438\u0442\u0438 \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f.", + "validate_verification_code": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0434\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0442\u0430 \u043f\u043e\u0447\u043d\u0456\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0443 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0420\u0430\u043d\u0456\u0448\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f {username} \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u043f\u0440\u0430\u0446\u044e\u0454. \u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c, \u0449\u043e\u0431 \u043f\u0440\u043e\u0434\u043e\u0432\u0436\u0438\u0442\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0446\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + }, + "trusted_device": { + "data": { + "trusted_device": "\u0414\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0434\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439", + "title": "\u0414\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 iCloud" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "with_family": "\u0417 \u0441\u0456\u043c'\u0454\u044e" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456 iCloud" + }, + "verification_code": { + "data": { + "verification_code": "\u041a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u0432\u0456\u0434 iCloud", + "title": "\u041a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f iCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/de.json b/homeassistant/components/ifttt/translations/de.json index c96928afa18d23..5184e89f29a11a 100644 --- a/homeassistant/components/ifttt/translations/de.json +++ b/homeassistant/components/ifttt/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, musst du die Aktion \"Eine Webanforderung erstellen\" aus dem [IFTTT Webhook Applet]({applet_url}) ausw\u00e4hlen.\n\nF\u00fclle folgende Informationen aus: \n- URL: `{webhook_url}`\n- Methode: POST\n- Inhaltstyp: application/json\n\nIn der Dokumentation ({docs_url}) findest du Informationen zur Konfiguration der Automation eingehender Daten." }, diff --git a/homeassistant/components/ifttt/translations/tr.json b/homeassistant/components/ifttt/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/ifttt/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/uk.json b/homeassistant/components/ifttt/translations/uk.json new file mode 100644 index 00000000000000..8ea8f2b1970da3 --- /dev/null +++ b/homeassistant/components/ifttt/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0456\u044e \"Make a web request\" \u0437 [IFTTT Webhook applet]({applet_url}). \n\n\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json \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 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0456\u0439 \u043f\u043e \u043e\u0431\u0440\u043e\u0431\u0446\u0456 \u0434\u0430\u043d\u0438\u0445, \u0449\u043e \u043d\u0430\u0434\u0445\u043e\u0434\u044f\u0442\u044c." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 IFTTT?", + "title": "IFTTT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/input_boolean/translations/uk.json b/homeassistant/components/input_boolean/translations/uk.json index c677957de475da..be22ae53807113 100644 --- a/homeassistant/components/input_boolean/translations/uk.json +++ b/homeassistant/components/input_boolean/translations/uk.json @@ -5,5 +5,5 @@ "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } }, - "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u043b\u043e\u0433\u0456\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f" + "title": "Input Boolean" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/uk.json b/homeassistant/components/input_datetime/translations/uk.json index bd087e535a5c61..c0aeb11882fe88 100644 --- a/homeassistant/components/input_datetime/translations/uk.json +++ b/homeassistant/components/input_datetime/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u0434\u0430\u0442\u0438" + "title": "Input Datetime" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/uk.json b/homeassistant/components/input_number/translations/uk.json index 0e4265d7ca0afb..e09531134cdffa 100644 --- a/homeassistant/components/input_number/translations/uk.json +++ b/homeassistant/components/input_number/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u043e\u043c\u0435\u0440" + "title": "Input Number" } \ No newline at end of file diff --git a/homeassistant/components/input_select/translations/uk.json b/homeassistant/components/input_select/translations/uk.json index ace44f8d7a79ac..b33e64fbf48159 100644 --- a/homeassistant/components/input_select/translations/uk.json +++ b/homeassistant/components/input_select/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438" + "title": "Input Select" } \ No newline at end of file diff --git a/homeassistant/components/input_text/translations/uk.json b/homeassistant/components/input_text/translations/uk.json index a80f4325203ed6..84bddfe3e07e1d 100644 --- a/homeassistant/components/input_text/translations/uk.json +++ b/homeassistant/components/input_text/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u0442\u0435\u043a\u0441\u0442\u0443" + "title": "Input Text" } \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/de.json b/homeassistant/components/insteon/translations/de.json index dfa4f3f7567c47..6bbc4d5474f063 100644 --- a/homeassistant/components/insteon/translations/de.json +++ b/homeassistant/components/insteon/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" @@ -22,6 +23,9 @@ } }, "plm": { + "data": { + "device": "USB-Ger\u00e4te-Pfad" + }, "title": "Insteon PLM" }, "user": { diff --git a/homeassistant/components/insteon/translations/tr.json b/homeassistant/components/insteon/translations/tr.json new file mode 100644 index 00000000000000..6c41f53b31eb41 --- /dev/null +++ b/homeassistant/components/insteon/translations/tr.json @@ -0,0 +1,89 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "hubv1": { + "data": { + "host": "\u0130p Adresi", + "port": "Port" + }, + "title": "Insteon Hub S\u00fcr\u00fcm 1" + }, + "hubv2": { + "data": { + "host": "\u0130p Adresi", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Insteon Hub S\u00fcr\u00fcm 2'yi yap\u0131land\u0131r\u0131n.", + "title": "Insteon Hub S\u00fcr\u00fcm 2" + }, + "plm": { + "description": "Insteon PowerLink Modemini (PLM) yap\u0131land\u0131r\u0131n." + }, + "user": { + "data": { + "modem_type": "Modem t\u00fcr\u00fc." + }, + "description": "Insteon modem tipini se\u00e7in.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "input_error": "Ge\u00e7ersiz giri\u015fler, l\u00fctfen de\u011ferlerinizi kontrol edin." + }, + "step": { + "add_x10": { + "data": { + "unitcode": "Birim kodu (1-16)" + }, + "description": "Insteon Hub parolas\u0131n\u0131 de\u011fi\u015ftirin.", + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "\u0130p Adresi", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma ekleyin.", + "add_x10": "Bir X10 cihaz\u0131 ekleyin.", + "change_hub_config": "Hub yap\u0131land\u0131rmas\u0131n\u0131 de\u011fi\u015ftirin.", + "remove_override": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma i\u015flemini kald\u0131r\u0131n.", + "remove_x10": "Bir X10 cihaz\u0131n\u0131 \u00e7\u0131kar\u0131n." + }, + "description": "Yap\u0131land\u0131rmak i\u00e7in bir se\u00e7enek se\u00e7in.", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "Kald\u0131r\u0131lacak bir cihaz adresi se\u00e7in" + }, + "description": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lmay\u0131 kald\u0131rma", + "title": "Insteon" + }, + "remove_x10": { + "data": { + "address": "Kald\u0131r\u0131lacak bir cihaz adresi se\u00e7in" + }, + "description": "Bir X10 cihaz\u0131n\u0131 kald\u0131r\u0131n", + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/uk.json b/homeassistant/components/insteon/translations/uk.json new file mode 100644 index 00000000000000..302d8c3676a00d --- /dev/null +++ b/homeassistant/components/insteon/translations/uk.json @@ -0,0 +1,109 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "select_single": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e." + }, + "step": { + "hubv1": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Insteon Hub \u0432\u0435\u0440\u0441\u0456\u0457 1 (\u0434\u043e 2014 \u0440\u043e\u043a\u0443)", + "title": "Insteon Hub. \u0412\u0435\u0440\u0441\u0456\u044f 1" + }, + "hubv2": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Insteon Hub \u0432\u0435\u0440\u0441\u0456\u0457 2", + "title": "Insteon Hub. \u0412\u0435\u0440\u0441\u0456\u044f 2" + }, + "plm": { + "data": { + "device": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u043e\u0434\u0435\u043c\u0443 Insteon PowerLink (PLM)", + "title": "Insteon PLM" + }, + "user": { + "data": { + "modem_type": "\u0422\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443 Insteon.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "input_error": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f.", + "select_single": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e." + }, + "step": { + "add_override": { + "data": { + "address": "\u0410\u0434\u0440\u0435\u0441\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 1a2b3c)", + "cat": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0456\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 0x10)", + "subcat": "\u041f\u0456\u0434\u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0456\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, 0x0a)" + }, + "description": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "title": "Insteon" + }, + "add_x10": { + "data": { + "housecode": "\u041a\u043e\u0434 \u0431\u0443\u0434\u0438\u043d\u043a\u0443 (a - p)", + "platform": "\u041f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430", + "steps": "\u041a\u0440\u043e\u043a \u0434\u0456\u043c\u043c\u0435\u0440\u0430 (\u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u043e\u0441\u0432\u0456\u0442\u043b\u044e\u0432\u0430\u043b\u044c\u043d\u0438\u0445 \u043f\u0440\u0438\u043b\u0430\u0434\u0456\u0432, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c 22)", + "unitcode": "\u042e\u043d\u0456\u0442\u043a\u043e\u0434 (1 - 16)" + }, + "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043e Insteon Hub", + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f Insteon Hub. \u041f\u0456\u0441\u043b\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f \u0446\u0438\u0445 \u0437\u043c\u0456\u043d \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 Home Assistant. \u0426\u0435 \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0454 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0429\u043e\u0431 \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Hub.", + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "add_x10": "\u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 X10", + "change_hub_config": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430", + "remove_override": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "remove_x10": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 X10" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438" + }, + "description": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "title": "Insteon" + }, + "remove_x10": { + "data": { + "address": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438" + }, + "description": "\u0412\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e X10", + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/translations/de.json b/homeassistant/components/ios/translations/de.json index e9e592d18c298f..bc427bd2992cbb 100644 --- a/homeassistant/components/ios/translations/de.json +++ b/homeassistant/components/ios/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Es wird nur eine Konfiguration von Home Assistant iOS ben\u00f6tigt" + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/ios/translations/tr.json b/homeassistant/components/ios/translations/tr.json new file mode 100644 index 00000000000000..8de4663957ea85 --- /dev/null +++ b/homeassistant/components/ios/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/translations/uk.json b/homeassistant/components/ios/translations/uk.json new file mode 100644 index 00000000000000..5f8d69f5f29b89 --- /dev/null +++ b/homeassistant/components/ios/translations/uk.json @@ -0,0 +1,12 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/lb.json b/homeassistant/components/ipma/translations/lb.json index 7b2d374b6f551a..006d80d37869d8 100644 --- a/homeassistant/components/ipma/translations/lb.json +++ b/homeassistant/components/ipma/translations/lb.json @@ -15,5 +15,10 @@ "title": "Standuert" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API Endpunkt ereechbar" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/tr.json b/homeassistant/components/ipma/translations/tr.json index 488ad379942076..a8df63645abe95 100644 --- a/homeassistant/components/ipma/translations/tr.json +++ b/homeassistant/components/ipma/translations/tr.json @@ -1,4 +1,15 @@ { + "config": { + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam", + "mode": "Mod" + } + } + } + }, "system_health": { "info": { "api_endpoint_reachable": "Ula\u015f\u0131labilir IPMA API u\u00e7 noktas\u0131" diff --git a/homeassistant/components/ipma/translations/uk.json b/homeassistant/components/ipma/translations/uk.json index bb294cc5d21e0c..ee84e7d16f2249 100644 --- a/homeassistant/components/ipma/translations/uk.json +++ b/homeassistant/components/ipma/translations/uk.json @@ -1,9 +1,24 @@ { "config": { + "error": { + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f." + }, "step": { "user": { - "title": "\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f" + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041f\u043e\u0440\u0442\u0443\u0433\u0430\u043b\u044c\u0441\u044c\u043a\u0438\u0439 \u0456\u043d\u0441\u0442\u0438\u0442\u0443\u0442 \u043c\u043e\u0440\u044f \u0442\u0430 \u0430\u0442\u043c\u043e\u0441\u0444\u0435\u0440\u0438.", + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e API IPMA" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/de.json b/homeassistant/components/ipp/translations/de.json index 73dd3f69bcc10f..69402c8fdba3d9 100644 --- a/homeassistant/components/ipp/translations/de.json +++ b/homeassistant/components/ipp/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", "connection_upgrade": "Verbindung zum Drucker fehlgeschlagen, da ein Verbindungsupgrade erforderlich ist.", "ipp_error": "IPP-Fehler festgestellt.", "ipp_version_error": "IPP-Version wird vom Drucker nicht unterst\u00fctzt.", @@ -9,6 +10,7 @@ "unique_id_required": "Ger\u00e4t fehlt die f\u00fcr die Entdeckung erforderliche eindeutige Identifizierung." }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", "connection_upgrade": "Verbindung zum Drucker fehlgeschlagen. Bitte versuchen Sie es erneut mit aktivierter SSL / TLS-Option." }, "flow_title": "Drucker: {name}", @@ -18,14 +20,14 @@ "base_path": "Relativer Pfad zum Drucker", "host": "Host", "port": "Port", - "ssl": "Der Drucker unterst\u00fctzt die Kommunikation \u00fcber SSL / TLS", - "verify_ssl": "Der Drucker verwendet ein ordnungsgem\u00e4\u00dfes SSL-Zertifikat" + "ssl": "Verwendet ein SSL-Zertifikat", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, "description": "Richten Sie Ihren Drucker \u00fcber das Internet Printing Protocol (IPP) f\u00fcr die Integration in Home Assistant ein.", "title": "Verbinden Sie Ihren Drucker" }, "zeroconf_confirm": { - "description": "M\u00f6chten Sie den Drucker mit dem Namen \"{name}\" zu Home Assistant hinzuf\u00fcgen?", + "description": "M\u00f6chtest du {name} einrichten?", "title": "Entdeckter Drucker" } } diff --git a/homeassistant/components/ipp/translations/tr.json b/homeassistant/components/ipp/translations/tr.json index dbb14fe825ed01..78b9a868bd2f9f 100644 --- a/homeassistant/components/ipp/translations/tr.json +++ b/homeassistant/components/ipp/translations/tr.json @@ -1,7 +1,24 @@ { "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", + "connection_upgrade": "Yaz\u0131c\u0131ya ba\u011flan\u0131lamad\u0131. L\u00fctfen SSL / TLS se\u00e7ene\u011fi i\u015faretli olarak tekrar deneyin." + }, + "flow_title": "Yaz\u0131c\u0131: {name}", "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Yaz\u0131c\u0131n\u0131z\u0131 ba\u011flay\u0131n" + }, "zeroconf_confirm": { + "description": "{name} kurmak istiyor musunuz?", "title": "Ke\u015ffedilen yaz\u0131c\u0131" } } diff --git a/homeassistant/components/ipp/translations/uk.json b/homeassistant/components/ipp/translations/uk.json new file mode 100644 index 00000000000000..bb6df07f1e491a --- /dev/null +++ b/homeassistant/components/ipp/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_upgrade": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0456\u0441\u0442\u044c \u043f\u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f.", + "ipp_error": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430 IPP.", + "ipp_version_error": "\u0412\u0435\u0440\u0441\u0456\u044f IPP \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c.", + "parse_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0440\u043e\u0437\u0456\u0431\u0440\u0430\u0442\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c \u0432\u0456\u0434 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430.", + "unique_id_required": "\u041d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0432\u0456\u0434\u0441\u0443\u0442\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0430 \u0434\u043b\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_upgrade": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0447\u0435\u0440\u0435\u0437 SSL / TLS." + }, + "flow_title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440: {name}", + "step": { + "user": { + "data": { + "base_path": "\u0412\u0456\u0434\u043d\u043e\u0441\u043d\u0438\u0439 \u0448\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043f\u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 IPP.", + "title": "Internet Printing Protocol (IPP)" + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u043d\u0442\u0435\u0440 `{name}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/tr.json b/homeassistant/components/iqvia/translations/tr.json new file mode 100644 index 00000000000000..717f6d72b94e5d --- /dev/null +++ b/homeassistant/components/iqvia/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/uk.json b/homeassistant/components/iqvia/translations/uk.json new file mode 100644 index 00000000000000..ab9813d6289d97 --- /dev/null +++ b/homeassistant/components/iqvia/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "invalid_zip_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441." + }, + "step": { + "user": { + "data": { + "zip_code": "\u041f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0432\u0456\u0439 \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e \u041a\u0430\u043d\u0430\u0434\u0438).", + "title": "IQVIA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/de.json b/homeassistant/components/islamic_prayer_times/translations/de.json index af38303c9a2f94..b06137bdb0e5f4 100644 --- a/homeassistant/components/islamic_prayer_times/translations/de.json +++ b/homeassistant/components/islamic_prayer_times/translations/de.json @@ -1,3 +1,8 @@ { + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + } + }, "title": "Islamische Gebetszeiten" } \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/tr.json b/homeassistant/components/islamic_prayer_times/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ 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 new file mode 100644 index 00000000000000..9290114899a4ee --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0440\u043e\u0437\u043a\u043b\u0430\u0434 \u0447\u0430\u0441\u0443 \u043d\u0430\u043c\u0430\u0437\u0443?", + "title": "\u0427\u0430\u0441 \u043d\u0430\u043c\u0430\u0437\u0443" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u0443" + } + } + } + }, + "title": "\u0427\u0430\u0441 \u043d\u0430\u043c\u0430\u0437\u0443" +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/de.json b/homeassistant/components/isy994/translations/de.json index 99d11e5d6c97b2..18d6a1603c4ac4 100644 --- a/homeassistant/components/isy994/translations/de.json +++ b/homeassistant/components/isy994/translations/de.json @@ -18,7 +18,7 @@ "username": "Benutzername" }, "description": "Der Hosteintrag muss im vollst\u00e4ndigen URL-Format vorliegen, z. B. http://192.168.10.100:80", - "title": "Stellen Sie eine Verbindung zu Ihrem ISY994 her" + "title": "Stelle eine Verbindung zu deinem ISY994 her" } } }, diff --git a/homeassistant/components/isy994/translations/tr.json b/homeassistant/components/isy994/translations/tr.json new file mode 100644 index 00000000000000..d1423202fe052b --- /dev/null +++ b/homeassistant/components/isy994/translations/tr.json @@ -0,0 +1,30 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "URL", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "variable_sensor_string": "De\u011fi\u015fken Sens\u00f6r Dizesi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/uk.json b/homeassistant/components/isy994/translations/uk.json new file mode 100644 index 00000000000000..c874b8654f58dc --- /dev/null +++ b/homeassistant/components/isy994/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_host": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.10.100:80').", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Universal Devices ISY994 {name} ({host})", + "step": { + "user": { + "data": { + "host": "URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "tls": "\u0412\u0435\u0440\u0441\u0456\u044f TLS \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.10.100:80').", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "options": { + "step": { + "init": { + "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", + "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.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f ISY994" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/de.json b/homeassistant/components/izone/translations/de.json index ea59cc39b27e05..f6e03c3af27c8c 100644 --- a/homeassistant/components/izone/translations/de.json +++ b/homeassistant/components/izone/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine iZone-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von iZone erforderlich." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/tr.json b/homeassistant/components/izone/translations/tr.json new file mode 100644 index 00000000000000..faa20ed0ece1c9 --- /dev/null +++ b/homeassistant/components/izone/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "\u0130Zone'u kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/uk.json b/homeassistant/components/izone/translations/uk.json new file mode 100644 index 00000000000000..8ab6c1e1664374 --- /dev/null +++ b/homeassistant/components/izone/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 iZone?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/de.json b/homeassistant/components/juicenet/translations/de.json index 16f48ef3837022..7a6b5cff541159 100644 --- a/homeassistant/components/juicenet/translations/de.json +++ b/homeassistant/components/juicenet/translations/de.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "Dieses JuiceNet-Konto ist bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { - "api_token": "JuiceNet API Token" + "api_token": "API-Token" }, - "description": "Sie ben\u00f6tigen das API-Token von https://home.juice.net/Manage.", - "title": "Stellen Sie eine Verbindung zu JuiceNet her" + "description": "Du ben\u00f6tigst das API-Token von https://home.juice.net/Manage.", + "title": "Stelle eine Verbindung zu JuiceNet her" } } } diff --git a/homeassistant/components/juicenet/translations/tr.json b/homeassistant/components/juicenet/translations/tr.json new file mode 100644 index 00000000000000..53890eb41e228e --- /dev/null +++ b/homeassistant/components/juicenet/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_token": "API Belirteci" + }, + "description": "API Belirtecine https://home.juice.net/Manage adresinden ihtiyac\u0131n\u0131z olacak.", + "title": "JuiceNet'e ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/uk.json b/homeassistant/components/juicenet/translations/uk.json new file mode 100644 index 00000000000000..903ea5f6e743dd --- /dev/null +++ b/homeassistant/components/juicenet/translations/uk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u0442\u043e\u043a\u0435\u043d API \u0437 \u0441\u0430\u0439\u0442\u0443 https://home.juice.net/Manage.", + "title": "JuiceNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/de.json b/homeassistant/components/kodi/translations/de.json index a0bf05cb5ecd76..1d229e5a428ef1 100644 --- a/homeassistant/components/kodi/translations/de.json +++ b/homeassistant/components/kodi/translations/de.json @@ -1,10 +1,14 @@ { "config": { "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Kodi: {name}", diff --git a/homeassistant/components/kodi/translations/tr.json b/homeassistant/components/kodi/translations/tr.json new file mode 100644 index 00000000000000..54ad8e0b6fdbff --- /dev/null +++ b/homeassistant/components/kodi/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "credentials": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "L\u00fctfen Kodi kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 ve \u015fifrenizi girin. Bunlar Sistem / Ayarlar / A\u011f / Hizmetler'de bulunabilir." + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "ws_port": { + "data": { + "ws_port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/uk.json b/homeassistant/components/kodi/translations/uk.json new file mode 100644 index 00000000000000..d2acde5dffb767 --- /dev/null +++ b/homeassistant/components/kodi/translations/uk.json @@ -0,0 +1,50 @@ +{ + "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.", + "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.", + "no_uuid": "\u0423 \u0434\u0430\u043d\u043e\u0433\u043e \u0435\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 Kodi \u043d\u0435\u043c\u0430\u0454 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440\u0430. \u0406\u043c\u043e\u0432\u0456\u0440\u043d\u043e, \u0446\u0435 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u043e \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u0441\u0442\u0430\u0440\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 Kodi (17.x \u0430\u0431\u043e \u043d\u0438\u0436\u0447\u0435). \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u0432\u0440\u0443\u0447\u043d\u0443 \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 \u043d\u043e\u0432\u0456\u0448\u0443 \u0432\u0435\u0440\u0441\u0456\u044e Kodi.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0456 \u043f\u0430\u0440\u043e\u043b\u044c Kodi. \u0407\u0445 \u043c\u043e\u0436\u043d\u0430 \u0437\u043d\u0430\u0439\u0442\u0438, \u043f\u0435\u0440\u0435\u0439\u0448\u043e\u0432\u0448\u0438 \u0432 \"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\" - \"\u0421\u043b\u0443\u0436\u0431\u0438\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f\"." + }, + "discovery_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Kodi (`{name}`)?", + "title": "Kodi" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL" + }, + "description": "\u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e \"\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043f\u043e HTTP\" \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 \"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\" - \"\u0421\u043b\u0443\u0436\u0431\u0438\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f\"." + }, + "ws_port": { + "data": { + "ws_port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u043e WebSocket. \u0429\u043e\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0447\u0435\u0440\u0435\u0437 WebSocket, \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u0430\u043c\u0438 \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 \"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\" - \"\u0421\u043b\u0443\u0436\u0431\u0438\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f\". \u042f\u043a\u0449\u043e WebSocket \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_off": "\u0437\u0430\u043f\u0438\u0442\u0430\u043d\u043e \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f {entity_name}", + "turn_on": "\u0437\u0430\u043f\u0438\u0442\u0430\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/de.json b/homeassistant/components/konnected/translations/de.json index ad2ed659522fcb..2ec1657990b227 100644 --- a/homeassistant/components/konnected/translations/de.json +++ b/homeassistant/components/konnected/translations/de.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsfluss f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "not_konn_panel": "Kein anerkanntes Konnected.io-Ger\u00e4t", - "unknown": "Unbekannter Fehler ist aufgetreten" + "unknown": "Unerwarteter Fehler" }, "error": { "cannot_connect": "Es konnte keine Verbindung zu einem Konnected-Panel unter {host}:{port} hergestellt werden." @@ -43,7 +43,7 @@ "name": "Name (optional)", "type": "Bin\u00e4rer Sensortyp" }, - "description": "Bitte w\u00e4hlen Sie die Optionen f\u00fcr den an {zone} angeschlossenen Bin\u00e4rsensor", + "description": "Bitte w\u00e4hle die Optionen f\u00fcr den an {zone} angeschlossenen Bin\u00e4rsensor", "title": "Konfigurieren Sie den Bin\u00e4rsensor" }, "options_digital": { @@ -52,7 +52,7 @@ "poll_interval": "Abfrageintervall (Minuten) (optional)", "type": "Sensortyp" }, - "description": "Bitte w\u00e4hlen Sie die Optionen f\u00fcr den an {zone} angeschlossenen digitalen Sensor aus", + "description": "Bitte w\u00e4hle die Optionen f\u00fcr den an {zone} angeschlossenen digitalen Sensor aus", "title": "Konfigurieren Sie den digitalen Sensor" }, "options_io": { @@ -98,9 +98,9 @@ "more_states": "Konfigurieren Sie zus\u00e4tzliche Zust\u00e4nde f\u00fcr diese Zone", "name": "Name (optional)", "pause": "Pause zwischen Impulsen (ms) (optional)", - "repeat": "Zeit zum Wiederholen (-1 = unendlich) (optional)" + "repeat": "Mal wiederholen (-1 = unendlich) (optional)" }, - "description": "Bitte w\u00e4hlen Sie die Ausgabeoptionen f\u00fcr {zone} : Status {state}", + "description": "Bitte w\u00e4hlen die Ausgabeoptionen f\u00fcr {zone} : Status {state}", "title": "Konfigurieren Sie den schaltbaren Ausgang" } } diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index ee6c10cbdd8f22..f6e9a2dbfbc566 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -45,7 +45,7 @@ "name": "Nazwa (opcjonalnie)", "type": "Typ sensora binarnego" }, - "description": "Wybierz opcje dla sensora binarnego powi\u0105zanego ze {zone}", + "description": "Opcje {zone}", "title": "Konfiguracja sensora binarnego" }, "options_digital": { @@ -54,7 +54,7 @@ "poll_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (minuty) (opcjonalnie)", "type": "Typ sensora" }, - "description": "Wybierz opcje dla cyfrowego sensora powi\u0105zanego ze {zone}", + "description": "Opcje {zone}", "title": "Konfiguracja sensora cyfrowego" }, "options_io": { diff --git a/homeassistant/components/konnected/translations/tr.json b/homeassistant/components/konnected/translations/tr.json new file mode 100644 index 00000000000000..a0e759903bdea8 --- /dev/null +++ b/homeassistant/components/konnected/translations/tr.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi", + "port": "Port" + } + } + } + }, + "options": { + "error": { + "bad_host": "Ge\u00e7ersiz, Ge\u00e7ersiz K\u0131lma API ana makine url'si" + }, + "step": { + "options_binary": { + "data": { + "inverse": "A\u00e7\u0131k / kapal\u0131 durumunu tersine \u00e7evirin" + } + }, + "options_io": { + "data": { + "3": "B\u00f6lge 3", + "4": "B\u00f6lge 4", + "5": "B\u00f6lge 5", + "6": "B\u00f6lge 6", + "7": "B\u00f6lge 7", + "out": "OUT" + } + }, + "options_io_ext": { + "data": { + "10": "B\u00f6lge 10", + "11": "B\u00f6lge 11", + "12": "B\u00f6lge 12", + "8": "B\u00f6lge 8", + "9": "B\u00f6lge 9", + "alarm1": "ALARM1", + "alarm2_out2": "OUT2/ALARM2", + "out1": "OUT1" + } + }, + "options_misc": { + "data": { + "api_host": "API ana makine URL'sini ge\u00e7ersiz k\u0131l (iste\u011fe ba\u011fl\u0131)", + "override_api_host": "Varsay\u0131lan Home Assistant API ana bilgisayar paneli URL'sini ge\u00e7ersiz k\u0131l" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/uk.json b/homeassistant/components/konnected/translations/uk.json new file mode 100644 index 00000000000000..92cd3744d945c2 --- /dev/null +++ b/homeassistant/components/konnected/translations/uk.json @@ -0,0 +1,108 @@ +{ + "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_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.", + "not_konn_panel": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Konnected.io \u043d\u0435 \u0440\u043e\u0437\u043f\u0456\u0437\u043d\u0430\u043d\u043e.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "confirm": { + "description": "\u041c\u043e\u0434\u0435\u043b\u044c: {model}\nID: {id}\n\u0425\u043e\u0441\u0442: {host}\n\u041f\u043e\u0440\u0442: {port} \n\n\u0417\u043c\u0456\u043d\u0430 \u043b\u043e\u0433\u0456\u043a\u0438 \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u0430\u043d\u0435\u043b\u0456, \u0430 \u0442\u0430\u043a\u043e\u0436 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u0432\u0445\u043e\u0434\u0456\u0432 \u0456 \u0432\u0438\u0445\u043e\u0434\u0456\u0432 \u0432\u0438\u043a\u043e\u043d\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u043f\u0430\u043d\u0435\u043b\u0456 \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Konnected.", + "title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Konnected \u0433\u043e\u0442\u043e\u0432\u0456\u0439 \u0434\u043e \u0440\u043e\u0431\u043e\u0442\u0438." + }, + "import_confirm": { + "description": "\u041f\u0430\u043d\u0435\u043b\u044c \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Konnected ID {id} \u0440\u0430\u043d\u0456\u0448\u0435 \u0432\u0436\u0435 \u0431\u0443\u043b\u0430 \u0434\u043e\u0434\u0430\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 configuration.yaml. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u0437\u0430\u043f\u0438\u0441 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0434\u0430\u043d\u043e\u0433\u043e \u043f\u043e\u0441\u0456\u0431\u043d\u0438\u043a\u0430 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f.", + "title": "\u0406\u043c\u043f\u043e\u0440\u0442 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Konnected" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0430\u043d\u0435\u043b\u0456 Konnected." + } + } + }, + "options": { + "abort": { + "not_konn_panel": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Konnected.io \u043d\u0435 \u0440\u043e\u0437\u043f\u0456\u0437\u043d\u0430\u043d\u043e." + }, + "error": { + "bad_host": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 URL \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0445\u043e\u0441\u0442\u0430 API." + }, + "step": { + "options_binary": { + "data": { + "inverse": "\u0406\u043d\u0432\u0435\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438\u0439/\u0437\u0430\u043a\u0440\u0438\u0442\u0438\u0439 \u0441\u0442\u0430\u043d", + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "type": "\u0422\u0438\u043f \u0431\u0456\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "description": "\u041e\u043f\u0446\u0456\u0457 {zone}", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0431\u0456\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "options_digital": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "poll_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "type": "\u0422\u0438\u043f \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "description": "\u041e\u043f\u0446\u0456\u0457 {zone}", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "options_io": { + "data": { + "1": "\u0417\u043e\u043d\u0430 1", + "2": "\u0417\u043e\u043d\u0430 2", + "3": "\u0417\u043e\u043d\u0430 3", + "4": "\u0417\u043e\u043d\u0430 4", + "5": "\u0417\u043e\u043d\u0430 5", + "6": "\u0417\u043e\u043d\u0430 6", + "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.", + "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": { + "data": { + "10": "\u0417\u043e\u043d\u0430 10", + "11": "\u0417\u043e\u043d\u0430 11", + "12": "\u0417\u043e\u043d\u0430 12", + "8": "\u0417\u043e\u043d\u0430 8", + "9": "\u0417\u043e\u043d\u0430 9", + "alarm1": "\u0422\u0420\u0418\u0412\u041e\u0413\u04101", + "alarm2_out2": "\u0412\u0418\u0425\u0406\u04142 / \u0422\u0420\u0418\u0412\u041e\u0413\u04102", + "out1": "\u0412\u0418\u0425\u0406\u04141" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0440\u0435\u0448\u0442\u0438 \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432. \u0411\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0431\u0443\u0434\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \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 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0438\u0445 \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432" + }, + "options_misc": { + "data": { + "api_host": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 URL \u0445\u043e\u0441\u0442\u0430 API (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "blink": "LED-\u0456\u043d\u0434\u0438\u043a\u0430\u0446\u0456\u044f \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 \u043f\u0440\u0438 \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u0446\u0456 \u0441\u0442\u0430\u043d\u0443", + "discovery": "\u0412\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0442\u0438 \u043d\u0430 \u0437\u0430\u043f\u0438\u0442\u0438 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u0443 \u0412\u0430\u0448\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456", + "override_api_host": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442-\u043f\u0430\u043d\u0435\u043b\u0456 Home Assistant API" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0431\u0430\u0436\u0430\u043d\u0443 \u043f\u043e\u0432\u0435\u0434\u0456\u043d\u043a\u0443 \u0434\u043b\u044f \u0412\u0430\u0448\u043e\u0457 \u043f\u0430\u043d\u0435\u043b\u0456.", + "title": "\u0406\u043d\u0448\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + }, + "options_switch": { + "data": { + "activation": "\u0412\u0438\u0445\u0456\u0434 \u043f\u0440\u0438 \u0432\u043c\u0438\u043a\u0430\u043d\u043d\u0456", + "momentary": "\u0422\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044c \u0456\u043c\u043f\u0443\u043b\u044c\u0441\u0443 (\u043c\u0441) (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "more_states": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0441\u0442\u0430\u043d\u0438 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0437\u043e\u043d\u0438", + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "pause": "\u041f\u0430\u0443\u0437\u0430 \u043c\u0456\u0436 \u0456\u043c\u043f\u0443\u043b\u044c\u0441\u0430\u043c\u0438 (\u043c\u0441) (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "repeat": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u044c (-1 = \u043d\u0435\u0441\u043a\u0456\u043d\u0447\u0435\u043d\u043d\u043e) (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "description": "{zone}: \u0441\u0442\u0430\u043d {state}", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u044e\u0447\u043e\u0433\u043e \u0432\u0438\u0445\u043e\u0434\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/de.json b/homeassistant/components/kulersky/translations/de.json index 3fc69f85947a70..96ed09a974f40d 100644 --- a/homeassistant/components/kulersky/translations/de.json +++ b/homeassistant/components/kulersky/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Wollen Sie mit der Einrichtung beginnen?" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/kulersky/translations/lb.json b/homeassistant/components/kulersky/translations/lb.json new file mode 100644 index 00000000000000..4ea09574c0ba8f --- /dev/null +++ b/homeassistant/components/kulersky/translations/lb.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Keng Apparater am Netzwierk fonnt", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "step": { + "confirm": { + "description": "Soll den Ariichtungs Prozess gestart ginn?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/tr.json b/homeassistant/components/kulersky/translations/tr.json index 49fa9545e94d2c..3df15466f030f1 100644 --- a/homeassistant/components/kulersky/translations/tr.json +++ b/homeassistant/components/kulersky/translations/tr.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + "no_devices_found": "A\u011fda cihaz bulunamad\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/uk.json b/homeassistant/components/kulersky/translations/uk.json new file mode 100644 index 00000000000000..292861e9129dbd --- /dev/null +++ b/homeassistant/components/kulersky/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/de.json b/homeassistant/components/life360/translations/de.json index 731ebdceef7598..7e495987b45540 100644 --- a/homeassistant/components/life360/translations/de.json +++ b/homeassistant/components/life360/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "create_entry": { @@ -8,6 +9,7 @@ }, "error": { "already_configured": "Konto ist bereits konfiguriert", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_username": "Ung\u00fcltiger Benutzername", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/life360/translations/fr.json b/homeassistant/components/life360/translations/fr.json index 72f56ed87844b6..cb86d8c65907cd 100644 --- a/homeassistant/components/life360/translations/fr.json +++ b/homeassistant/components/life360/translations/fr.json @@ -8,7 +8,7 @@ "default": "Pour d\u00e9finir les options avanc\u00e9es, voir [Documentation de Life360]( {docs_url} )." }, "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "invalid_auth": "Authentification invalide", "invalid_username": "Nom d'utilisateur invalide", "unknown": "Erreur inattendue" diff --git a/homeassistant/components/life360/translations/tr.json b/homeassistant/components/life360/translations/tr.json index 3f923c096cd056..e1e57b39737210 100644 --- a/homeassistant/components/life360/translations/tr.json +++ b/homeassistant/components/life360/translations/tr.json @@ -1,11 +1,22 @@ { "config": { "abort": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "unknown": "Beklenmedik hata" }, "error": { "already_configured": "Hesap zaten konfig\u00fcre edilmi\u015fi durumda", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_username": "Ge\u00e7ersiz kullan\u0131c\u0131 ad\u0131", "unknown": "Beklenmedik hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/life360/translations/uk.json b/homeassistant/components/life360/translations/uk.json new file mode 100644 index 00000000000000..caecf494388971 --- /dev/null +++ b/homeassistant/components/life360/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "create_entry": { + "default": "\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 \u0440\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0438\u0445 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c." + }, + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "invalid_username": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0456\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\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 \u0440\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0438\u0445 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0440\u043e\u0431\u0438\u0442\u0438 \u0446\u0435 \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", + "title": "Life360" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/de.json b/homeassistant/components/lifx/translations/de.json index f88e27ff168c0a..83eded1ddc69e7 100644 --- a/homeassistant/components/lifx/translations/de.json +++ b/homeassistant/components/lifx/translations/de.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Keine LIFX Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von LIFX ist zul\u00e4ssig." + "no_devices_found": "Keine LIFX Ger\u00e4te im Netzwerk gefunden", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/tr.json b/homeassistant/components/lifx/translations/tr.json new file mode 100644 index 00000000000000..fc7532a1e3411d --- /dev/null +++ b/homeassistant/components/lifx/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "LIFX'i kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/uk.json b/homeassistant/components/lifx/translations/uk.json new file mode 100644 index 00000000000000..8c32e79533dc00 --- /dev/null +++ b/homeassistant/components/lifx/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 LIFX?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/light/translations/uk.json b/homeassistant/components/light/translations/uk.json index 67685889c5489d..86eee7d6b23e93 100644 --- a/homeassistant/components/light/translations/uk.json +++ b/homeassistant/components/light/translations/uk.json @@ -1,5 +1,17 @@ { "device_automation": { + "action_type": { + "brightness_decrease": "{entity_name}: \u0437\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "brightness_increase": "{entity_name}: \u0437\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "flash": "{entity_name}: \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043c\u0438\u0433\u0430\u043d\u043d\u044f", + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" diff --git a/homeassistant/components/local_ip/translations/de.json b/homeassistant/components/local_ip/translations/de.json index 072f6ec964d12d..9e2a6eda5c6a27 100644 --- a/homeassistant/components/local_ip/translations/de.json +++ b/homeassistant/components/local_ip/translations/de.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "Es ist nur eine einzige Konfiguration der lokalen IP zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "user": { "data": { "name": "Sensorname" }, + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Lokale IP-Adresse" } } diff --git a/homeassistant/components/local_ip/translations/es.json b/homeassistant/components/local_ip/translations/es.json index fe9a0ad14147ec..a3048d396d567a 100644 --- a/homeassistant/components/local_ip/translations/es.json +++ b/homeassistant/components/local_ip/translations/es.json @@ -8,7 +8,7 @@ "data": { "name": "Nombre del sensor" }, - "description": "\u00bfQuieres empezar a configurar?", + "description": "\u00bfQuieres iniciar la configuraci\u00f3n?", "title": "Direcci\u00f3n IP local" } } diff --git a/homeassistant/components/local_ip/translations/tr.json b/homeassistant/components/local_ip/translations/tr.json new file mode 100644 index 00000000000000..e8e82814f8af2d --- /dev/null +++ b/homeassistant/components/local_ip/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "data": { + "name": "Sens\u00f6r Ad\u0131" + }, + "description": "Kuruluma ba\u015flamak ister misiniz?", + "title": "Yerel IP Adresi" + } + } + }, + "title": "Yerel IP Adresi" +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/uk.json b/homeassistant/components/local_ip/translations/uk.json new file mode 100644 index 00000000000000..b88c1c002bf593 --- /dev/null +++ b/homeassistant/components/local_ip/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430 IP-\u0430\u0434\u0440\u0435\u0441\u0430" + } + } + }, + "title": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430 IP-\u0430\u0434\u0440\u0435\u0441\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/de.json b/homeassistant/components/locative/translations/de.json index 326170941466e5..a6dcf4150d0ff7 100644 --- a/homeassistant/components/locative/translations/de.json +++ b/homeassistant/components/locative/translations/de.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Standorte Home Assistant zu senden, muss das Webhook Feature in der Locative App konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, "step": { "user": { - "description": "M\u00f6chtest du den Locative Webhook wirklich einrichten?", + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Locative Webhook einrichten" } } diff --git a/homeassistant/components/locative/translations/tr.json b/homeassistant/components/locative/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/locative/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/uk.json b/homeassistant/components/locative/translations/uk.json new file mode 100644 index 00000000000000..d9a4713087117f --- /dev/null +++ b/homeassistant/components/locative/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Locative. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "Locative" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lock/translations/pt.json b/homeassistant/components/lock/translations/pt.json index 5ba9f10db141d8..44f30900572344 100644 --- a/homeassistant/components/lock/translations/pt.json +++ b/homeassistant/components/lock/translations/pt.json @@ -5,6 +5,9 @@ "open": "Abrir {entity_name}", "unlock": "Desbloquear {entity_name}" }, + "condition_type": { + "is_unlocked": "{entity_name} est\u00e1 destrancado" + }, "trigger_type": { "locked": "{entity_name} fechada", "unlocked": "{entity_name} aberta" diff --git a/homeassistant/components/lock/translations/tr.json b/homeassistant/components/lock/translations/tr.json index 95b50398fdaad2..ea6ff1a157da23 100644 --- a/homeassistant/components/lock/translations/tr.json +++ b/homeassistant/components/lock/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "locked": "{entity_name} kilitlendi", + "unlocked": "{entity_name} kilidi a\u00e7\u0131ld\u0131" + } + }, "state": { "_": { "locked": "Kilitli", diff --git a/homeassistant/components/lock/translations/uk.json b/homeassistant/components/lock/translations/uk.json index d919252eb56204..96b92012e9d30f 100644 --- a/homeassistant/components/lock/translations/uk.json +++ b/homeassistant/components/lock/translations/uk.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "lock": "{entity_name}: \u0437\u0430\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438", + "open": "{entity_name}: \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438", + "unlock": "{entity_name}: \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438" + }, + "condition_type": { + "is_locked": "{entity_name} \u0432 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_unlocked": "{entity_name} \u0432 \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, + "trigger_type": { + "locked": "{entity_name} \u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f", + "unlocked": "{entity_name} \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f" + } + }, "state": { "_": { "locked": "\u0417\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e", diff --git a/homeassistant/components/logi_circle/translations/de.json b/homeassistant/components/logi_circle/translations/de.json index ab4a194fda00af..1eec1d3c4a5218 100644 --- a/homeassistant/components/logi_circle/translations/de.json +++ b/homeassistant/components/logi_circle/translations/de.json @@ -1,11 +1,15 @@ { "config": { "abort": { + "already_configured": "Konto wurde bereits konfiguriert", "external_error": "Es ist eine Ausnahme in einem anderen Flow aufgetreten.", - "external_setup": "Logi Circle wurde erfolgreich aus einem anderen Flow konfiguriert." + "external_setup": "Logi Circle wurde erfolgreich aus einem anderen Flow konfiguriert.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." }, "error": { - "follow_link": "Bitte folge dem Link und authentifiziere dich, bevor du auf Senden klickst." + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "follow_link": "Bitte folge dem Link und authentifiziere dich, bevor du auf Senden klickst.", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "auth": { diff --git a/homeassistant/components/logi_circle/translations/fr.json b/homeassistant/components/logi_circle/translations/fr.json index 7ac388ccb3f371..6bd22f473e7cca 100644 --- a/homeassistant/components/logi_circle/translations/fr.json +++ b/homeassistant/components/logi_circle/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "external_error": "Une exception est survenue \u00e0 partir d'un autre flux.", "external_setup": "Logi Circle a \u00e9t\u00e9 configur\u00e9 avec succ\u00e8s \u00e0 partir d'un autre flux.", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." diff --git a/homeassistant/components/logi_circle/translations/lb.json b/homeassistant/components/logi_circle/translations/lb.json index fab157b2655920..82be2f6a82d7d9 100644 --- a/homeassistant/components/logi_circle/translations/lb.json +++ b/homeassistant/components/logi_circle/translations/lb.json @@ -7,6 +7,7 @@ "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun." }, "error": { + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", "follow_link": "Follegt w.e.g dem Link an authentifiz\u00e9iert iech ier de op Ofsch\u00e9cken dr\u00e9ckt.", "invalid_auth": "Ong\u00eblteg Authentifikatioun" }, diff --git a/homeassistant/components/logi_circle/translations/tr.json b/homeassistant/components/logi_circle/translations/tr.json new file mode 100644 index 00000000000000..0b0f58116c234f --- /dev/null +++ b/homeassistant/components/logi_circle/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/logi_circle/translations/uk.json b/homeassistant/components/logi_circle/translations/uk.json new file mode 100644 index 00000000000000..2c021992413d79 --- /dev/null +++ b/homeassistant/components/logi_circle/translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "external_error": "\u0412\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0432\u0456\u0434\u0431\u0443\u043b\u043e\u0441\u044f \u0437 \u0456\u043d\u0448\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0443.", + "external_setup": "Logi Circle \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.", + "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." + }, + "error": { + "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.", + "follow_link": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u0456 \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\".", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "auth": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043f\u043e [\u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c]({authorization_url}) \u0456 ** \u0414\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 ** \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0432\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Logi Circle, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0456 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **.", + "title": "Logi Circle" + }, + "user": { + "data": { + "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457, \u0447\u0435\u0440\u0435\u0437 \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0438\u0439 \u0432\u0445\u0456\u0434.", + "title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/de.json b/homeassistant/components/lovelace/translations/de.json index c8680fcb7e5fbc..b6c7562f0ec2e3 100644 --- a/homeassistant/components/lovelace/translations/de.json +++ b/homeassistant/components/lovelace/translations/de.json @@ -1,6 +1,9 @@ { "system_health": { "info": { + "dashboards": "Dashboards", + "mode": "Modus", + "resources": "Ressourcen", "views": "Ansichten" } } diff --git a/homeassistant/components/lovelace/translations/fr.json b/homeassistant/components/lovelace/translations/fr.json new file mode 100644 index 00000000000000..f2847bcc177204 --- /dev/null +++ b/homeassistant/components/lovelace/translations/fr.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "Tableaux de bord", + "mode": "Mode", + "resources": "Ressources", + "views": "Vues" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/tr.json b/homeassistant/components/lovelace/translations/tr.json index 9f763d0d6cc351..d159e058ffa4e5 100644 --- a/homeassistant/components/lovelace/translations/tr.json +++ b/homeassistant/components/lovelace/translations/tr.json @@ -3,7 +3,8 @@ "info": { "dashboards": "Kontrol panelleri", "mode": "Mod", - "resources": "Kaynaklar" + "resources": "Kaynaklar", + "views": "G\u00f6r\u00fcn\u00fcmler" } } } \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/uk.json b/homeassistant/components/lovelace/translations/uk.json new file mode 100644 index 00000000000000..21d97fd14c38bb --- /dev/null +++ b/homeassistant/components/lovelace/translations/uk.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "\u041f\u0430\u043d\u0435\u043b\u0456", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438", + "views": "\u0412\u043a\u043b\u0430\u0434\u043a\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/de.json b/homeassistant/components/luftdaten/translations/de.json index 122dc611870de5..499a65623b004b 100644 --- a/homeassistant/components/luftdaten/translations/de.json +++ b/homeassistant/components/luftdaten/translations/de.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "Der Dienst ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_sensor": "Sensor nicht verf\u00fcgbar oder ung\u00fcltig" }, diff --git a/homeassistant/components/luftdaten/translations/tr.json b/homeassistant/components/luftdaten/translations/tr.json new file mode 100644 index 00000000000000..04565de3d28b2d --- /dev/null +++ b/homeassistant/components/luftdaten/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/uk.json b/homeassistant/components/luftdaten/translations/uk.json new file mode 100644 index 00000000000000..9fd33dc3da2697 --- /dev/null +++ b/homeassistant/components/luftdaten/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \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", + "invalid_sensor": "\u0421\u0435\u043d\u0441\u043e\u0440 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0430\u0431\u043e \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439." + }, + "step": { + "user": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456", + "station_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u043d\u0441\u043e\u0440\u0430 Luftdaten" + }, + "title": "Luftdaten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ca.json b/homeassistant/components/lutron_caseta/translations/ca.json index c3b0e686cc4616..5f2cc5d40872ea 100644 --- a/homeassistant/components/lutron_caseta/translations/ca.json +++ b/homeassistant/components/lutron_caseta/translations/ca.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "cannot_connect": "Ha fallat la connexi\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3", + "not_lutron_device": "El dispositiu descobert no \u00e9s un dispositiu Lutron" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "No s'ha pogut configurar l'enlla\u00e7 (amfitri\u00f3: {host}) importat de configuration.yaml.", "title": "No s'ha pogut importar la configuraci\u00f3 de l'enlla\u00e7 de Cas\u00e9ta." + }, + "link": { + "description": "Per a vincular amb {name} ({host}), despr\u00e9s d'enviar aquest formulari, prem el bot\u00f3 negre de la part posterior de l'enlla\u00e7.", + "title": "Vinculaci\u00f3 amb enlla\u00e7" + }, + "user": { + "data": { + "host": "Amfitri\u00f3" + }, + "description": "Introdueix l'adre\u00e7a IP del dispositiu.", + "title": "Connexi\u00f3 autom\u00e0tica amb l'enlla\u00e7" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primer bot\u00f3", + "button_2": "Segon bot\u00f3", + "button_3": "Tercer bot\u00f3", + "button_4": "Quart bot\u00f3", + "close_1": "Tanca 1", + "close_2": "Tanca 2", + "close_3": "Tanca 3", + "close_4": "Tanca 4", + "close_all": "Tanca-ho tot", + "group_1_button_1": "Primer bot\u00f3 del primer grup", + "group_1_button_2": "Segon bot\u00f3 del primer grup", + "group_2_button_1": "Primer bot\u00f3 del segon grup", + "group_2_button_2": "Segon bot\u00f3 del segon grup", + "lower": "Baixa", + "lower_1": "Baixa 1", + "lower_2": "Baixa 2", + "lower_3": "Baixa 3", + "lower_4": "Baixa 4", + "lower_all": "Baixa-ho tot", + "off": "OFF", + "on": "ON", + "open_1": "Obre 1", + "open_2": "Obre 2", + "open_3": "Obre 3", + "open_4": "Obre 4", + "open_all": "Obre-ho tot", + "raise": "Puja", + "raise_1": "Puja 1", + "raise_2": "Puja 2", + "raise_3": "Puja 3", + "raise_4": "Puja 4", + "raise_all": "Puja-ho tot", + "stop": "Atura (preferit)", + "stop_1": "Atura 1", + "stop_2": "Atura 2", + "stop_3": "Atura 3", + "stop_4": "Atura 4", + "stop_all": "Atura-ho tot" + }, + "trigger_type": { + "press": "\"{subtype}\" premut", + "release": "\"{subtype}\" alliberat" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/cs.json b/homeassistant/components/lutron_caseta/translations/cs.json index 60fa7fddced2c5..4ccfa17e6d3a03 100644 --- a/homeassistant/components/lutron_caseta/translations/cs.json +++ b/homeassistant/components/lutron_caseta/translations/cs.json @@ -6,6 +6,13 @@ }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "step": { + "user": { + "data": { + "host": "Hostitel" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/es.json b/homeassistant/components/lutron_caseta/translations/es.json index cfd8551bab92a9..37b1a0d90728e6 100644 --- a/homeassistant/components/lutron_caseta/translations/es.json +++ b/homeassistant/components/lutron_caseta/translations/es.json @@ -2,16 +2,50 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "cannot_connect": "No se pudo conectar" + "cannot_connect": "No se pudo conectar", + "not_lutron_device": "El dispositivo descubierto no es un dispositivo de Lutron" }, "error": { "cannot_connect": "No se pudo conectar" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "No se puede configurar bridge (host: {host}) importado desde configuration.yaml.", "title": "Error al importar la configuraci\u00f3n del bridge Cas\u00e9ta." + }, + "link": { + "description": "Para emparejar con {name} ({host}), despu\u00e9s de enviar este formulario, presione el bot\u00f3n negro en la parte posterior del puente.", + "title": "Emparejar con el puente" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Introduzca la direcci\u00f3n ip del dispositivo.", + "title": "Conectar autom\u00e1ticamente con el dispositivo" } } + }, + "device_automation": { + "trigger_subtype": { + "open_1": "Abrir 1", + "open_2": "Abrir 2", + "open_3": "Abrir 3", + "open_4": "Abrir 4", + "open_all": "Abrir todo", + "raise": "Levantar", + "raise_1": "Levantar 1", + "raise_2": "Levantar 2", + "raise_3": "Levantar 3", + "raise_4": "Levantar 4", + "raise_all": "Levantar todo", + "stop": "Detener (favorito)", + "stop_1": "Detener 1", + "stop_2": "Detener 2", + "stop_3": "Detener 3", + "stop_4": "Detener 4", + "stop_all": "Detener todo" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/et.json b/homeassistant/components/lutron_caseta/translations/et.json index ed352c7bcc4b58..81fee6d5b4afcc 100644 --- a/homeassistant/components/lutron_caseta/translations/et.json +++ b/homeassistant/components/lutron_caseta/translations/et.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", - "cannot_connect": "\u00dchendamine nurjus" + "cannot_connect": "\u00dchendamine nurjus", + "not_lutron_device": "Avastatud seade ei ole Lutroni seade" }, "error": { "cannot_connect": "\u00dchendamine nurjus" }, + "flow_title": "Lutron Cas\u00e9ta {name} ( {host} )", "step": { "import_failed": { "description": "Silla (host: {host} ) seadistamine configuration.yaml kirje teabest nurjus.", "title": "Cas\u00e9ta Bridge seadete importimine nurjus." + }, + "link": { + "description": "{name} ({host}) sidumiseks vajuta p\u00e4rast selle vormi esitamist silla tagak\u00fcljel olevat musta nuppu.", + "title": "Sillaga sidumine" + }, + "user": { + "data": { + "host": "" + }, + "description": "Sisesta seadme IP-aadress.", + "title": "\u00dchendu sillaga automaatselt" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Esimene nupp", + "button_2": "Teine nupp", + "button_3": "Kolmas nupp", + "button_4": "Neljas nupp", + "close_1": "Sule #1", + "close_2": "Sule #2", + "close_3": "Sule #3", + "close_4": "Sule #4", + "close_all": "Sulge k\u00f5ik", + "group_1_button_1": "Esimese r\u00fchma esimene nupp", + "group_1_button_2": "Esimene r\u00fchma teine nupp", + "group_2_button_1": "Teise r\u00fchma esimene nupp", + "group_2_button_2": "Teise r\u00fchma teine nupp", + "lower": "Langeta", + "lower_1": "Langeta #1", + "lower_2": "Langeta #2", + "lower_3": "Langeta #3", + "lower_4": "Langeta #4", + "lower_all": "Langeta k\u00f5ik", + "off": "V\u00e4ljas", + "on": "Sees", + "open_1": "Ava #1", + "open_2": "Ava #2", + "open_3": "Ava #3", + "open_4": "Ava #4", + "open_all": "Ava k\u00f5ik", + "raise": "T\u00f5sta", + "raise_1": "T\u00f5sta #1", + "raise_2": "T\u00f5sta #2", + "raise_3": "T\u00f5sta #3", + "raise_4": "T\u00f5sta #4", + "raise_all": "T\u00f5sta k\u00f5ik", + "stop": "Peata lemmikasendis", + "stop_1": "Peata #1", + "stop_2": "Peata #2", + "stop_3": "Peata #3", + "stop_4": "Peata #4", + "stop_all": "Peata k\u00f5ik" + }, + "trigger_type": { + "press": "vajutati \" {subtype} \"", + "release": "\" {subtype} \" vabastati" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/it.json b/homeassistant/components/lutron_caseta/translations/it.json index 5bdcf87607dccc..d1b3b754812aa3 100644 --- a/homeassistant/components/lutron_caseta/translations/it.json +++ b/homeassistant/components/lutron_caseta/translations/it.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "cannot_connect": "Impossibile connettersi" + "cannot_connect": "Impossibile connettersi", + "not_lutron_device": "Il dispositivo rilevato non \u00e8 un dispositivo Lutron" }, "error": { "cannot_connect": "Impossibile connettersi" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Impossibile impostare il bridge (host: {host}) importato da configuration.yaml.", "title": "Impossibile importare la configurazione del bridge Cas\u00e9ta." + }, + "link": { + "description": "Per eseguire l'associazione con {name} ({host}), dopo aver inviato questo modulo, premere il pulsante nero sul retro del bridge.", + "title": "Associa con il bridge" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Immettere l'indirizzo IP del dispositivo.", + "title": "Connetti automaticamente al bridge" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primo pulsante", + "button_2": "Secondo pulsante", + "button_3": "Terzo pulsante", + "button_4": "Quarto pulsante", + "close_1": "Chiudi 1", + "close_2": "Chiudi 2", + "close_3": "Chiudi 3", + "close_4": "Chiudi 4", + "close_all": "Chiudi tutti", + "group_1_button_1": "Primo Gruppo primo pulsante", + "group_1_button_2": "Primo Gruppo secondo pulsante", + "group_2_button_1": "Secondo Gruppo primo pulsante", + "group_2_button_2": "Secondo Gruppo secondo pulsante", + "lower": "Abbassa", + "lower_1": "Abbassa 1", + "lower_2": "Abbassa 2", + "lower_3": "Abbassa 3", + "lower_4": "Abbassa 4", + "lower_all": "Abbassa tutti", + "off": "Spento", + "on": "Acceso", + "open_1": "Apri 1", + "open_2": "Apri 2", + "open_3": "Apri 3", + "open_4": "Apri 4", + "open_all": "Apri tutti", + "raise": "Alza", + "raise_1": "Alza 1", + "raise_2": "Alza 2", + "raise_3": "Alza 3", + "raise_4": "Alza 4", + "raise_all": "Alza tutti", + "stop": "Ferma (preferito)", + "stop_1": "Ferma 1", + "stop_2": "Ferma 2", + "stop_3": "Ferma 3", + "stop_4": "Ferma 4", + "stop_all": "Fermare tutti" + }, + "trigger_type": { + "press": "\"{subtype}\" premuto", + "release": "\"{subtype}\" rilasciato" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/no.json b/homeassistant/components/lutron_caseta/translations/no.json index 7afac9c51a5303..477370100af138 100644 --- a/homeassistant/components/lutron_caseta/translations/no.json +++ b/homeassistant/components/lutron_caseta/translations/no.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "cannot_connect": "Tilkobling mislyktes" + "cannot_connect": "Tilkobling mislyktes", + "not_lutron_device": "Oppdaget enhet er ikke en Lutron-enhet" }, "error": { "cannot_connect": "Tilkobling mislyktes" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Kunne ikke konfigurere bridge (host: {host} ) importert fra configuration.yaml.", "title": "Kan ikke importere Cas\u00e9ta bridge-konfigurasjon." + }, + "link": { + "description": "Hvis du vil pare med {name} ({host}), trykker du den svarte knappen p\u00e5 baksiden av broen etter at du har sendt dette skjemaet.", + "title": "Par med broen" + }, + "user": { + "data": { + "host": "Vert" + }, + "description": "Skriv inn ip-adressen til enheten.", + "title": "Koble automatisk til broen" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "F\u00f8rste knapp", + "button_2": "Andre knapp", + "button_3": "Tredje knapp", + "button_4": "Fjerde knapp", + "close_1": "Lukk 1", + "close_2": "Lukk 2", + "close_3": "Lukk 3", + "close_4": "Lukk 4", + "close_all": "Lukk alle", + "group_1_button_1": "F\u00f8rste gruppe f\u00f8rste knapp", + "group_1_button_2": "F\u00f8rste gruppe andre knapp", + "group_2_button_1": "Andre gruppe f\u00f8rste knapp", + "group_2_button_2": "Andre gruppeknapp", + "lower": "Senk", + "lower_1": "Senk 1", + "lower_2": "Senk 2", + "lower_3": "Senk 3", + "lower_4": "Senk 4", + "lower_all": "Senk alle", + "off": "Av", + "on": "P\u00e5", + "open_1": "\u00c5pne 1", + "open_2": "\u00c5pne 2", + "open_3": "\u00c5pne 3", + "open_4": "\u00c5pne 4", + "open_all": "\u00c5pne alle", + "raise": "Hev", + "raise_1": "Hev 1", + "raise_2": "Hev 2", + "raise_3": "Hev 3", + "raise_4": "Hev 4", + "raise_all": "Hev alle", + "stop": "Stopp (favoritt)", + "stop_1": "Stopp 1", + "stop_2": "Stopp 2", + "stop_3": "Stopp 3", + "stop_4": "Stopp 4", + "stop_all": "Stopp alle" + }, + "trigger_type": { + "press": "\"{subtype}\" trykket", + "release": "\"{subtype}\" utgitt" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/pl.json b/homeassistant/components/lutron_caseta/translations/pl.json index 07417b0149e46c..8a8c0a759b01e9 100644 --- a/homeassistant/components/lutron_caseta/translations/pl.json +++ b/homeassistant/components/lutron_caseta/translations/pl.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "not_lutron_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem Lutron" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Nie mo\u017cna skonfigurowa\u0107 mostka (host: {host}) zaimportowanego z pliku configuration.yaml.", "title": "Nie uda\u0142o si\u0119 zaimportowa\u0107 konfiguracji mostka Cas\u00e9ta." + }, + "link": { + "description": "Aby sparowa\u0107 z {name} ({host}), po przes\u0142aniu tego formularza naci\u015bnij czarny przycisk z ty\u0142u mostka.", + "title": "Sparuj z mostkiem" + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wprowad\u017a adres IP urz\u0105dzenia", + "title": "Po\u0142\u0105cz si\u0119 automatycznie z mostkiem" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "pierwszy", + "button_2": "drugi", + "button_3": "trzeci", + "button_4": "czwarty", + "close_1": "zamknij 1", + "close_2": "zamknij 2", + "close_3": "zamknij 3", + "close_4": "zamknij 4", + "close_all": "zamknij wszystkie", + "group_1_button_1": "pierwsza grupa pierwszy przycisk", + "group_1_button_2": "pierwsza grupa drugi przycisk", + "group_2_button_1": "druga grupa pierwszy przycisk", + "group_2_button_2": "druga grupa drugi przycisk", + "lower": "opu\u015b\u0107", + "lower_1": "opu\u015b\u0107 1", + "lower_2": "opu\u015b\u0107 2", + "lower_3": "opu\u015b\u0107 3", + "lower_4": "opu\u015b\u0107 4", + "lower_all": "opu\u015b\u0107 wszystkie", + "off": "wy\u0142\u0105cz", + "on": "w\u0142\u0105cz", + "open_1": "otw\u00f3rz 1", + "open_2": "otw\u00f3rz 2", + "open_3": "otw\u00f3rz 3", + "open_4": "otw\u00f3rz 4", + "open_all": "otw\u00f3rz wszystkie", + "raise": "podnie\u015b", + "raise_1": "podnie\u015b 1", + "raise_2": "podnie\u015b 2", + "raise_3": "podnie\u015b 3", + "raise_4": "podnie\u015b 4", + "raise_all": "podnie\u015b wszystkie", + "stop": "zatrzymaj (ulubione)", + "stop_1": "zatrzymaj 1", + "stop_2": "zatrzymaj 2", + "stop_3": "zatrzymaj 3", + "stop_4": "zatrzymaj 4", + "stop_all": "zatrzymaj wszystkie" + }, + "trigger_type": { + "press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "release": "przycisk \"{subtype}\" zostanie zwolniony" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ru.json b/homeassistant/components/lutron_caseta/translations/ru.json index 05bd4f51c70bf1..edda7af8e9a96a 100644 --- a/homeassistant/components/lutron_caseta/translations/ru.json +++ b/homeassistant/components/lutron_caseta/translations/ru.json @@ -2,16 +2,56 @@ "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." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "not_lutron_device": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 Lutron." }, "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": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 configuration.yaml (\u0445\u043e\u0441\u0442: {host}).", "title": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0448\u043b\u044e\u0437\u0430." + }, + "link": { + "description": "\u0427\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 {name} ({host}), \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u044d\u0442\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u0447\u0435\u0440\u043d\u0443\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0437\u0430\u0434\u043d\u0435\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0448\u043b\u044e\u0437\u0430.", + "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441\u043e \u0448\u043b\u044e\u0437\u043e\u043c" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a \u0448\u043b\u044e\u0437\u0443" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0412\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044c\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "close_1": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 1", + "close_2": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 2", + "close_3": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 3", + "close_4": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 4", + "close_all": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c \u0432\u0441\u0435", + "group_1_button_1": "\u041f\u0435\u0440\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "group_1_button_2": "\u041f\u0435\u0440\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u0432\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "group_2_button_1": "\u0412\u0442\u043e\u0440\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "group_2_button_2": "\u0412\u0442\u043e\u0440\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u0432\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "stop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c (\u043b\u044e\u0431\u0438\u043c\u0430\u044f)", + "stop_1": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 1", + "stop_2": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 2", + "stop_3": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 3", + "stop_4": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 4", + "stop_all": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0441\u0435" + }, + "trigger_type": { + "press": "\"{subtype}\" \u043d\u0430\u0436\u0430\u0442\u0430", + "release": "\"{subtype}\" \u043e\u0442\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u043e\u0441\u043b\u0435 \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044f" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/tr.json b/homeassistant/components/lutron_caseta/translations/tr.json new file mode 100644 index 00000000000000..fdc5e71a7ac518 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/tr.json @@ -0,0 +1,72 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_lutron_device": "Bulunan cihaz bir Lutron cihaz\u0131 de\u011fil" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "Lutron Cas\u00e9ta {name} ( {host} )", + "step": { + "link": { + "description": "{name} ( {host} ) ile e\u015fle\u015ftirmek i\u00e7in, bu formu g\u00f6nderdikten sonra k\u00f6pr\u00fcn\u00fcn arkas\u0131ndaki siyah d\u00fc\u011fmeye bas\u0131n.", + "title": "K\u00f6pr\u00fc ile e\u015fle\u015ftirin" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "description": "Cihaz\u0131n ip adresini girin.", + "title": "K\u00f6pr\u00fcye otomatik olarak ba\u011flan\u0131n" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u0130lk d\u00fc\u011fme", + "button_2": "\u0130kinci d\u00fc\u011fme", + "button_3": "\u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fme", + "button_4": "D\u00f6rd\u00fcnc\u00fc d\u00fc\u011fme", + "close_1": "Kapat 1", + "close_2": "Kapat 2", + "close_3": "Kapat 3", + "close_4": "Kapat 4", + "close_all": "Hepsini kapat", + "group_1_button_1": "Birinci Grup ilk d\u00fc\u011fme", + "group_1_button_2": "Birinci Grup ikinci d\u00fc\u011fme", + "group_2_button_1": "\u0130kinci Grup birinci d\u00fc\u011fme", + "group_2_button_2": "\u0130kinci Grup ikinci d\u00fc\u011fme", + "lower": "Alt", + "lower_1": "Alt 1", + "lower_2": "Alt 2", + "lower_3": "Alt 3", + "lower_4": "Alt 4", + "lower_all": "Hepsini indir", + "off": "Kapal\u0131", + "on": "A\u00e7\u0131k", + "open_1": "A\u00e7 1", + "open_2": "A\u00e7 2", + "open_3": "A\u00e7 3", + "open_4": "A\u00e7\u0131k 4", + "open_all": "Hepsini a\u00e7", + "raise": "Y\u00fckseltmek", + "raise_1": "Y\u00fckselt 1", + "raise_2": "Y\u00fckselt 2", + "raise_3": "Y\u00fckselt 3", + "raise_4": "Y\u00fckselt 4", + "raise_all": "Hepsini Y\u00fckseltin", + "stop": "Durak (favori)", + "stop_1": "Durak 1", + "stop_2": "Durdur 2", + "stop_3": "Durdur 3", + "stop_4": "Durdur 4", + "stop_all": "Hepsini durdur" + }, + "trigger_type": { + "press": "\" {subtype} \" bas\u0131ld\u0131", + "release": "\" {subtype} \" yay\u0131nland\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/uk.json b/homeassistant/components/lutron_caseta/translations/uk.json new file mode 100644 index 00000000000000..238e17405ceea3 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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" + }, + "step": { + "import_failed": { + "description": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0448\u043b\u044e\u0437 \u0437 \u0444\u0430\u0439\u043b\u0443 'configuration.yaml' (\u0445\u043e\u0441\u0442: {host}).", + "title": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0448\u043b\u044e\u0437\u0443." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/zh-Hant.json b/homeassistant/components/lutron_caseta/translations/zh-Hant.json index 4e8df0d5e9f254..50762fafac1836 100644 --- a/homeassistant/components/lutron_caseta/translations/zh-Hant.json +++ b/homeassistant/components/lutron_caseta/translations/zh-Hant.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "cannot_connect": "\u9023\u7dda\u5931\u6557" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "not_lutron_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e Lutron \u88dd\u7f6e" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "\u7121\u6cd5\u8a2d\u5b9a\u7531 configuration.yaml \u532f\u5165\u7684 bridge\uff08\u4e3b\u6a5f\uff1a{host}\uff09\u3002", "title": "\u532f\u5165 Cas\u00e9ta bridge \u8a2d\u5b9a\u5931\u6557\u3002" + }, + "link": { + "description": "\u6b32\u8207 {name} ({host}) \u9032\u884c\u914d\u5c0d\uff0c\u65bc\u50b3\u9001\u8868\u683c\u5f8c\u3001\u4e8c\u4e0b Bridge \u5f8c\u65b9\u7684\u9ed1\u8272\u6309\u9215\u3002", + "title": "\u8207 Bridge \u914d\u5c0d" + }, + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u8f38\u5165\u88dd\u7f6e IP \u4f4d\u5740\u3002", + "title": "\u81ea\u52d5\u9023\u7dda\u81f3 Bridge" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u7b2c\u4e00\u500b\u6309\u9215", + "button_2": "\u7b2c\u4e8c\u500b\u6309\u9215", + "button_3": "\u7b2c\u4e09\u500b\u6309\u9215", + "button_4": "\u7b2c\u56db\u500b\u6309\u9215", + "close_1": "\u95dc\u9589 1", + "close_2": "\u95dc\u9589 2", + "close_3": "\u95dc\u9589 3", + "close_4": "\u95dc\u9589 4", + "close_all": "\u5168\u90e8\u95dc\u9589", + "group_1_button_1": "\u7b2c\u4e00\u7d44\u7b2c\u4e00\u500b\u6309\u9215", + "group_1_button_2": "\u7b2c\u4e00\u7d44\u7b2c\u4e8c\u500b\u6309\u9215", + "group_2_button_1": "\u7b2c\u4e8c\u7d44\u7b2c\u4e00\u500b\u6309\u9215", + "group_2_button_2": "\u7b2c\u4e8c\u7d44\u7b2c\u4e8c\u500b\u6309\u9215", + "lower": "\u964d\u4f4e ", + "lower_1": "\u964d\u4f4e 1", + "lower_2": "\u964d\u4f4e 2", + "lower_3": "\u964d\u4f4e 3", + "lower_4": "\u964d\u4f4e 4", + "lower_all": "\u5168\u90e8\u964d\u4f4e", + "off": "\u95dc\u9589", + "on": "\u958b\u555f", + "open_1": "\u958b\u555f 1", + "open_2": "\u958b\u555f 2", + "open_3": "\u958b\u555f 3", + "open_4": "\u958b\u555f 4", + "open_all": "\u5168\u90e8\u958b\u555f", + "raise": "\u62ac\u8d77", + "raise_1": "\u62ac\u8d77 1", + "raise_2": "\u62ac\u8d77 2", + "raise_3": "\u62ac\u8d77 3", + "raise_4": "\u62ac\u8d77 4", + "raise_all": "\u5168\u90e8\u62ac\u8d77", + "stop": "\u505c\u6b62\uff08\u6700\u611b\uff09", + "stop_1": "\u505c\u6b62 1", + "stop_2": "\u505c\u6b62 2", + "stop_3": "\u505c\u6b62 3", + "stop_4": "\u505c\u6b62 4", + "stop_all": "\u5168\u90e8\u505c\u6b62" + }, + "trigger_type": { + "press": "\"{subtype}\" \u6309\u4e0b", + "release": "\"{subtype}\" \u91cb\u653e" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/ca.json b/homeassistant/components/lyric/translations/ca.json new file mode 100644 index 00000000000000..195d3d59262a9f --- /dev/null +++ b/homeassistant/components/lyric/translations/ca.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/cs.json b/homeassistant/components/lyric/translations/cs.json new file mode 100644 index 00000000000000..2a54a82f41b427 --- /dev/null +++ b/homeassistant/components/lyric/translations/cs.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace." + }, + "create_entry": { + "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno" + }, + "step": { + "pick_implementation": { + "title": "Vyberte metodu ov\u011b\u0159en\u00ed" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/en.json b/homeassistant/components/lyric/translations/en.json index b183398663ef3f..e3849fc17a3aab 100644 --- a/homeassistant/components/lyric/translations/en.json +++ b/homeassistant/components/lyric/translations/en.json @@ -12,6 +12,5 @@ "title": "Pick Authentication Method" } } - }, - "title": "Honeywell Lyric" + } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/et.json b/homeassistant/components/lyric/translations/et.json new file mode 100644 index 00000000000000..c7d46e7e9426d2 --- /dev/null +++ b/homeassistant/components/lyric/translations/et.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/it.json b/homeassistant/components/lyric/translations/it.json new file mode 100644 index 00000000000000..42536508716c7c --- /dev/null +++ b/homeassistant/components/lyric/translations/it.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/no.json b/homeassistant/components/lyric/translations/no.json new file mode 100644 index 00000000000000..a8f6ce4f9a3f3c --- /dev/null +++ b/homeassistant/components/lyric/translations/no.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "step": { + "pick_implementation": { + "title": "Velg godkjenningsmetode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/pl.json b/homeassistant/components/lyric/translations/pl.json new file mode 100644 index 00000000000000..8c75c11dd7c4c3 --- /dev/null +++ b/homeassistant/components/lyric/translations/pl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/tr.json b/homeassistant/components/lyric/translations/tr.json new file mode 100644 index 00000000000000..773577271d2ad3 --- /dev/null +++ b/homeassistant/components/lyric/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Yetki URL'si olu\u015fturulurken zaman a\u015f\u0131m\u0131 olu\u015ftu.", + "missing_configuration": "Bile\u015fen yap\u0131land\u0131r\u0131lmam\u0131\u015f. L\u00fctfen belgeleri takip edin." + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/zh-Hant.json b/homeassistant/components/lyric/translations/zh-Hant.json new file mode 100644 index 00000000000000..b740fd3e063c93 --- /dev/null +++ b/homeassistant/components/lyric/translations/zh-Hant.json @@ -0,0 +1,16 @@ +{ + "config": { + "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" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/de.json b/homeassistant/components/mailgun/translations/de.json index f684f822fd51c6..118192b65160fe 100644 --- a/homeassistant/components/mailgun/translations/de.json +++ b/homeassistant/components/mailgun/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "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." + }, "create_entry": { - "default": "Um Ereignisse an den Home Assistant zu senden, musst [Webhooks mit Mailgun]({mailgun_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhaltstyp: application/json \n\nLies in der [Dokumentation]({docs_url}) wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." + "default": "Um Ereignisse an Home Assistant zu senden, musst du [Webhooks mit Mailgun]({mailgun_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhaltstyp: application/json \n\nLies in der [Dokumentation]({docs_url}), wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/translations/tr.json b/homeassistant/components/mailgun/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/mailgun/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/uk.json b/homeassistant/components/mailgun/translations/uk.json new file mode 100644 index 00000000000000..d999b52085a213 --- /dev/null +++ b/homeassistant/components/mailgun/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f [Mailgun]({mailgun_url}). \n\n\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json \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 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0456\u0439 \u043f\u043e \u043e\u0431\u0440\u043e\u0431\u0446\u0456 \u0434\u0430\u043d\u0438\u0445, \u0449\u043e \u043d\u0430\u0434\u0445\u043e\u0434\u044f\u0442\u044c." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Mailgun?", + "title": "Mailgun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/tr.json b/homeassistant/components/media_player/translations/tr.json index 0130b5fb94cbd7..1f46c6a8bc7c54 100644 --- a/homeassistant/components/media_player/translations/tr.json +++ b/homeassistant/components/media_player/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} bo\u015fta", + "is_off": "{entity_name} kapal\u0131" + } + }, "state": { "_": { "idle": "Bo\u015fta", diff --git a/homeassistant/components/media_player/translations/uk.json b/homeassistant/components/media_player/translations/uk.json index f475829a524119..21c7f2897a3d85 100644 --- a/homeassistant/components/media_player/translations/uk.json +++ b/homeassistant/components/media_player/translations/uk.json @@ -1,7 +1,16 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_paused": "{entity_name} \u043d\u0430 \u043f\u0430\u0443\u0437\u0456", + "is_playing": "{entity_name} \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u044e\u0454 \u043c\u0435\u0434\u0456\u0430" + } + }, "state": { "_": { - "idle": "\u0411\u0435\u0437\u0434\u0456\u044f\u043b\u044c\u043d\u0456\u0441\u0442\u044c", + "idle": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", "paused": "\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0435\u043d\u043e", @@ -9,5 +18,5 @@ "standby": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f" } }, - "title": "\u041c\u0435\u0434\u0456\u0430 \u043f\u043b\u0435\u0454\u0440" + "title": "\u041c\u0435\u0434\u0456\u0430\u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0447" } \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/de.json b/homeassistant/components/melcloud/translations/de.json index 640c96e47c462d..54ae78f8680878 100644 --- a/homeassistant/components/melcloud/translations/de.json +++ b/homeassistant/components/melcloud/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Die MELCloud-Integration ist bereits f\u00fcr diese E-Mail konfiguriert. Das Zugriffstoken wurde aktualisiert." }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/melcloud/translations/tr.json b/homeassistant/components/melcloud/translations/tr.json new file mode 100644 index 00000000000000..6bce50f3de659e --- /dev/null +++ b/homeassistant/components/melcloud/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "MELCloud entegrasyonu bu e-posta i\u00e7in zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Eri\u015fim belirteci yenilendi." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/uk.json b/homeassistant/components/melcloud/translations/uk.json new file mode 100644 index 00000000000000..001239a8b47a48 --- /dev/null +++ b/homeassistant/components/melcloud/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f MELCloud \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0430 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0430\u0434\u0440\u0435\u0441\u0438 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438. \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0456\u0442\u044c\u0441\u044f, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0447\u0438 \u0441\u0432\u0456\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 MELCloud.", + "title": "MELCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/de.json b/homeassistant/components/met/translations/de.json index 901b4fb97b5ee7..e2bb171c749bb1 100644 --- a/homeassistant/components/met/translations/de.json +++ b/homeassistant/components/met/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/met/translations/tr.json b/homeassistant/components/met/translations/tr.json new file mode 100644 index 00000000000000..d256711728c58b --- /dev/null +++ b/homeassistant/components/met/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/uk.json b/homeassistant/components/met/translations/uk.json new file mode 100644 index 00000000000000..d980db91147a56 --- /dev/null +++ b/homeassistant/components/met/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "elevation": "\u0412\u0438\u0441\u043e\u0442\u0430", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041d\u043e\u0440\u0432\u0435\u0437\u044c\u043a\u0438\u0439 \u043c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0456\u0447\u043d\u0438\u0439 \u0456\u043d\u0441\u0442\u0438\u0442\u0443\u0442.", + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/de.json b/homeassistant/components/meteo_france/translations/de.json index 65313f16c41117..74637594d5ff1c 100644 --- a/homeassistant/components/meteo_france/translations/de.json +++ b/homeassistant/components/meteo_france/translations/de.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Stadt bereits konfiguriert", - "unknown": "Unbekannter Fehler: Bitte versuchen Sie es sp\u00e4ter erneut" + "already_configured": "Standort ist bereits konfiguriert", + "unknown": "Unerwarteter Fehler" }, "error": { "empty": "Kein Ergebnis bei der Stadtsuche: Bitte \u00fcberpr\u00fcfe das Stadtfeld" diff --git a/homeassistant/components/meteo_france/translations/tr.json b/homeassistant/components/meteo_france/translations/tr.json index 57fc9f768815fb..59c3886a90055a 100644 --- a/homeassistant/components/meteo_france/translations/tr.json +++ b/homeassistant/components/meteo_france/translations/tr.json @@ -1,7 +1,28 @@ { "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, "error": { "empty": "\u015eehir aramas\u0131nda sonu\u00e7 yok: l\u00fctfen \u015fehir alan\u0131n\u0131 kontrol edin" + }, + "step": { + "user": { + "data": { + "city": "\u015eehir" + }, + "title": "M\u00e9t\u00e9o-Fransa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Tahmin modu" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/uk.json b/homeassistant/components/meteo_france/translations/uk.json new file mode 100644 index 00000000000000..a84c230e21886b --- /dev/null +++ b/homeassistant/components/meteo_france/translations/uk.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "empty": "\u041d\u0435\u043c\u0430\u0454 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432 \u043f\u043e\u0448\u0443\u043a\u0443. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u041c\u0456\u0441\u0442\u043e\"." + }, + "step": { + "cities": { + "data": { + "city": "\u041c\u0456\u0441\u0442\u043e" + }, + "description": "\u041e\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0442\u043e \u0437\u0456 \u0441\u043f\u0438\u0441\u043a\u0443", + "title": "M\u00e9t\u00e9o-France" + }, + "user": { + "data": { + "city": "\u041c\u0456\u0441\u0442\u043e" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0424\u0440\u0430\u043d\u0446\u0456\u0457) \u0430\u0431\u043e \u043d\u0430\u0437\u0432\u0443 \u043c\u0456\u0441\u0442\u0430", + "title": "M\u00e9t\u00e9o-France" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "\u0420\u0435\u0436\u0438\u043c \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/de.json b/homeassistant/components/metoffice/translations/de.json index 74c204b96838e0..7b92af96c992f0 100644 --- a/homeassistant/components/metoffice/translations/de.json +++ b/homeassistant/components/metoffice/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Service ist bereits konfiguriert" }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/metoffice/translations/tr.json b/homeassistant/components/metoffice/translations/tr.json new file mode 100644 index 00000000000000..55064a139ef169 --- /dev/null +++ b/homeassistant/components/metoffice/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + }, + "description": "Enlem ve boylam, en yak\u0131n hava istasyonunu bulmak i\u00e7in kullan\u0131lacakt\u0131r." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/uk.json b/homeassistant/components/metoffice/translations/uk.json new file mode 100644 index 00000000000000..53ab2115e82e32 --- /dev/null +++ b/homeassistant/components/metoffice/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "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": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" + }, + "description": "\u0428\u0438\u0440\u043e\u0442\u0430 \u0456 \u0434\u043e\u0432\u0433\u043e\u0442\u0430 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u0456 \u0434\u043b\u044f \u043f\u043e\u0448\u0443\u043a\u0443 \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457 \u043c\u0435\u0442\u0435\u043e\u0441\u0442\u0430\u043d\u0446\u0456\u0457.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Met Office UK" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/de.json b/homeassistant/components/mikrotik/translations/de.json index 4211077c82ce26..82ea47dc4bf48c 100644 --- a/homeassistant/components/mikrotik/translations/de.json +++ b/homeassistant/components/mikrotik/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Mikrotik ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "name_exists": "Name vorhanden" }, "step": { @@ -25,7 +26,7 @@ "step": { "device_tracker": { "data": { - "arp_ping": "ARP Ping aktivieren", + "arp_ping": "ARP-Ping aktivieren", "force_dhcp": "Erzwingen Sie das Scannen \u00fcber DHCP" } } diff --git a/homeassistant/components/mikrotik/translations/tr.json b/homeassistant/components/mikrotik/translations/tr.json new file mode 100644 index 00000000000000..cffcc65151c6d6 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/uk.json b/homeassistant/components/mikrotik/translations/uk.json new file mode 100644 index 00000000000000..b44d5979d13058 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "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.", + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 SSL" + }, + "title": "MikroTik" + } + } + }, + "options": { + "step": { + "device_tracker": { + "data": { + "arp_ping": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 ARP-\u043f\u0456\u043d\u0433", + "detection_time": "\u0427\u0430\u0441 \u0432\u0456\u0434 \u043e\u0441\u0442\u0430\u043d\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0443 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c (\u0441\u0435\u043a.), \u043f\u043e \u0437\u0430\u043a\u0456\u043d\u0447\u0435\u043d\u043d\u044e \u044f\u043a\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043e\u0442\u0440\u0438\u043c\u0430\u0454 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\".", + "force_dhcp": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u0435 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c DHCP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/de.json b/homeassistant/components/mill/translations/de.json index 886e7e3c4589e2..63b6b7ea6e93ac 100644 --- a/homeassistant/components/mill/translations/de.json +++ b/homeassistant/components/mill/translations/de.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Account ist bereits konfiguriert" }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/mill/translations/fr.json b/homeassistant/components/mill/translations/fr.json index e171086a084e66..ffcff15ade8388 100644 --- a/homeassistant/components/mill/translations/fr.json +++ b/homeassistant/components/mill/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion" diff --git a/homeassistant/components/mill/translations/tr.json b/homeassistant/components/mill/translations/tr.json new file mode 100644 index 00000000000000..0f14728873a538 --- /dev/null +++ b/homeassistant/components/mill/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/uk.json b/homeassistant/components/mill/translations/uk.json new file mode 100644 index 00000000000000..b8a5aea578e7fd --- /dev/null +++ b/homeassistant/components/mill/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/de.json b/homeassistant/components/minecraft_server/translations/de.json index 484be7bd41867a..a0bbe60a842dcc 100644 --- a/homeassistant/components/minecraft_server/translations/de.json +++ b/homeassistant/components/minecraft_server/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Der Host ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { "cannot_connect": "Verbindung zum Server fehlgeschlagen. Bitte \u00fcberpr\u00fcfe den Host und den Port und versuche es erneut. Stelle au\u00dferdem sicher, dass Du mindestens Minecraft Version 1.7 auf Deinem Server ausf\u00fchrst.", diff --git a/homeassistant/components/minecraft_server/translations/tr.json b/homeassistant/components/minecraft_server/translations/tr.json index 7527294a3c7aa0..422dab32a01311 100644 --- a/homeassistant/components/minecraft_server/translations/tr.json +++ b/homeassistant/components/minecraft_server/translations/tr.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Ana Bilgisayar", "name": "Ad" }, "description": "G\u00f6zetmeye izin vermek i\u00e7in Minecraft server nesnesini ayarla.", diff --git a/homeassistant/components/minecraft_server/translations/uk.json b/homeassistant/components/minecraft_server/translations/uk.json new file mode 100644 index 00000000000000..0c8528b2cab1fb --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u0427\u0438 \u043d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0456\u0441\u0442\u044c \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0445 \u0434\u0430\u043d\u0438\u0445 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443. \u0422\u0430\u043a\u043e\u0436 \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u043d\u0430 \u0412\u0430\u0448\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0456 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 Minecraft \u0432\u0435\u0440\u0441\u0456\u0457 1.7, \u0430\u0431\u043e \u0432\u0438\u0449\u0435.", + "invalid_ip": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 MAC-\u0430\u0434\u0440\u0435\u0441\u0443).", + "invalid_port": "\u041f\u043e\u0440\u0442 \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0434\u0456\u0430\u043f\u0430\u0437\u043e\u043d\u0456 \u0432\u0456\u0434 1024 \u0434\u043e 65535." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0446\u0435\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u0412\u0430\u0448\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Minecraft.", + "title": "Minecraft Server" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/tr.json b/homeassistant/components/mobile_app/translations/tr.json new file mode 100644 index 00000000000000..10d79751ec12e7 --- /dev/null +++ b/homeassistant/components/mobile_app/translations/tr.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "action_type": { + "notify": "Bildirim g\u00f6nder" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/uk.json b/homeassistant/components/mobile_app/translations/uk.json index 4a48dd3775d5f7..db471bbdc7fa5a 100644 --- a/homeassistant/components/mobile_app/translations/uk.json +++ b/homeassistant/components/mobile_app/translations/uk.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "install_app": "\u0412\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a, \u0449\u043e\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u0437 Home Assistant. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438]({apps_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0441\u043f\u0438\u0441\u043a\u0443 \u0441\u0443\u043c\u0456\u0441\u043d\u0438\u0445 \u0434\u043e\u0434\u0430\u0442\u043a\u0456\u0432." + }, "step": { "confirm": { - "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430?" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a?" } } + }, + "device_automation": { + "action_type": { + "notify": "\u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f" + } } } \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/de.json b/homeassistant/components/monoprice/translations/de.json index 820d3a972d3151..8f6d1d88196c05 100644 --- a/homeassistant/components/monoprice/translations/de.json +++ b/homeassistant/components/monoprice/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/monoprice/translations/tr.json b/homeassistant/components/monoprice/translations/tr.json new file mode 100644 index 00000000000000..7c622a3cb4a140 --- /dev/null +++ b/homeassistant/components/monoprice/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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": { + "port": "Port", + "source_1": "Kaynak #1 ad\u0131", + "source_2": "Kaynak #2 ad\u0131", + "source_3": "Kaynak #3 ad\u0131", + "source_4": "Kaynak #4 ad\u0131", + "source_5": "Kaynak #5 ad\u0131", + "source_6": "Kaynak #6 ad\u0131" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/uk.json b/homeassistant/components/monoprice/translations/uk.json new file mode 100644 index 00000000000000..08857cc26f9b1a --- /dev/null +++ b/homeassistant/components/monoprice/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "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": { + "port": "\u041f\u043e\u0440\u0442", + "source_1": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #1", + "source_2": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #2", + "source_3": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #3", + "source_4": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #4", + "source_5": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #5", + "source_6": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #6" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #1", + "source_2": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #2", + "source_3": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #3", + "source_4": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #4", + "source_5": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #5", + "source_6": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #6" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u0436\u0435\u0440\u0435\u043b" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.uk.json b/homeassistant/components/moon/translations/sensor.uk.json index 71c2d80eb9804f..f916c03c3a1e87 100644 --- a/homeassistant/components/moon/translations/sensor.uk.json +++ b/homeassistant/components/moon/translations/sensor.uk.json @@ -4,7 +4,11 @@ "first_quarter": "\u041f\u0435\u0440\u0448\u0430 \u0447\u0432\u0435\u0440\u0442\u044c", "full_moon": "\u041f\u043e\u0432\u043d\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", "last_quarter": "\u041e\u0441\u0442\u0430\u043d\u043d\u044f \u0447\u0432\u0435\u0440\u0442\u044c", - "new_moon": "\u041d\u043e\u0432\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c" + "new_moon": "\u041d\u043e\u0432\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waning_crescent": "\u0421\u0442\u0430\u0440\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waning_gibbous": "\u0421\u043f\u0430\u0434\u0430\u044e\u0447\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waxing_crescent": "\u041c\u043e\u043b\u043e\u0434\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waxing_gibbous": "\u041f\u0440\u0438\u0431\u0443\u0432\u0430\u044e\u0447\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c" } } } \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/ca.json b/homeassistant/components/motion_blinds/translations/ca.json index a4bf96457e6f15..b83746b9ccfaca 100644 --- a/homeassistant/components/motion_blinds/translations/ca.json +++ b/homeassistant/components/motion_blinds/translations/ca.json @@ -5,14 +5,31 @@ "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "connection_error": "Ha fallat la connexi\u00f3" }, + "error": { + "discovery_error": "No s'ha pogut descobrir cap Motion Gateway" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Clau API" + }, + "description": "Necessitar\u00e0s la clau API de 16 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Adre\u00e7a IP" + }, + "description": "Torna a executar la configuraci\u00f3 si vols connectar m\u00e9s Motion Gateways", + "title": "Selecciona el Motion Gateway que vulguis connectar" + }, "user": { "data": { "api_key": "Clau API", "host": "Adre\u00e7a IP" }, - "description": "Necessitar\u00e0s el token d'API de 16 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "description": "Connecta el teu Motion Gateway, si no es configura l'adre\u00e7a IP, s'utilitza el descobriment autom\u00e0tic", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/cs.json b/homeassistant/components/motion_blinds/translations/cs.json index 41b5db3c83ec40..899f04d7cd4393 100644 --- a/homeassistant/components/motion_blinds/translations/cs.json +++ b/homeassistant/components/motion_blinds/translations/cs.json @@ -7,12 +7,21 @@ }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Kl\u00ed\u010d API" + } + }, + "select": { + "data": { + "select_ip": "IP adresa" + } + }, "user": { "data": { "api_key": "Kl\u00ed\u010d API", "host": "IP adresa" }, - "description": "Budete pot\u0159ebovat 16m\u00edstn\u00fd API kl\u00ed\u010d, pokyny najdete na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/de.json b/homeassistant/components/motion_blinds/translations/de.json index dd1acc230f1e82..c1a7ac0bc8d40d 100644 --- a/homeassistant/components/motion_blinds/translations/de.json +++ b/homeassistant/components/motion_blinds/translations/de.json @@ -1,16 +1,28 @@ { "config": { "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "connection_error": "Verbindung fehlgeschlagen" }, "flow_title": "Jalousien", "step": { + "connect": { + "data": { + "api_key": "API-Schl\u00fcssel" + } + }, + "select": { + "data": { + "select_ip": "IP-Adresse" + } + }, "user": { "data": { "api_key": "API-Schl\u00fcssel", "host": "IP-Adresse" }, - "description": "Ein 16-Zeichen-API-Schl\u00fcssel wird ben\u00f6tigt, siehe https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "description": "Stelle eine Verbindung zu deinem Motion Gateway her. Wenn die IP-Adresse leer bleibt, wird die automatische Erkennung verwendet", "title": "Jalousien" } } diff --git a/homeassistant/components/motion_blinds/translations/en.json b/homeassistant/components/motion_blinds/translations/en.json index b7830a255fc073..3a968bc6491b91 100644 --- a/homeassistant/components/motion_blinds/translations/en.json +++ b/homeassistant/components/motion_blinds/translations/en.json @@ -5,14 +5,31 @@ "already_in_progress": "Configuration flow is already in progress", "connection_error": "Failed to connect" }, + "error": { + "discovery_error": "Failed to discover a Motion Gateway" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "API Key" + }, + "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP Address" + }, + "description": "Run the setup again if you want to connect additional Motion Gateways", + "title": "Select the Motion Gateway that you wish to connect" + }, "user": { "data": { "api_key": "API Key", "host": "IP Address" }, - "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions", + "description": "Connect to your Motion Gateway, if the IP address is not set, auto-discovery is used", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/es.json b/homeassistant/components/motion_blinds/translations/es.json index bac5ffddbd31d0..7d7c6c1510fc92 100644 --- a/homeassistant/components/motion_blinds/translations/es.json +++ b/homeassistant/components/motion_blinds/translations/es.json @@ -5,14 +5,31 @@ "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "connection_error": "No se pudo conectar" }, + "error": { + "discovery_error": "No se pudo descubrir un detector de movimiento" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Clave API" + }, + "description": "Necesitar\u00e1 la clave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obtener instrucciones", + "title": "Estores motorizados" + }, + "select": { + "data": { + "select_ip": "Direcci\u00f3n IP" + }, + "description": "Ejecute la configuraci\u00f3n de nuevo si desea conectar detectores de movimiento adicionales", + "title": "Selecciona el detector de Movimiento que deseas conectar" + }, "user": { "data": { "api_key": "Clave API", "host": "Direcci\u00f3n IP" }, - "description": "Necesitar\u00e1s la Clave API de 16 caracteres, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para instrucciones", + "description": "Con\u00e9ctate a tu Motion Gateway, si la direcci\u00f3n IP no est\u00e1 establecida, se utilitzar\u00e1 la detecci\u00f3n autom\u00e1tica", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/et.json b/homeassistant/components/motion_blinds/translations/et.json index b55640d8905646..5e585dec1a3d64 100644 --- a/homeassistant/components/motion_blinds/translations/et.json +++ b/homeassistant/components/motion_blinds/translations/et.json @@ -5,14 +5,31 @@ "already_in_progress": "Seadistamine on juba k\u00e4imas", "connection_error": "\u00dchendamine nurjus" }, + "error": { + "discovery_error": "Motion Gateway avastamine nurjus" + }, "flow_title": "", "step": { + "connect": { + "data": { + "api_key": "API v\u00f5ti" + }, + "description": "On vaja 16-kohalist API-v\u00f5tit, juhiste saamiseks vaata https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "" + }, + "select": { + "data": { + "select_ip": "IP aadress" + }, + "description": "K\u00e4ivita seadistamine uuesti kui soovid \u00fchendada t\u00e4iendavaid Motion Gateway sidumisi", + "title": "Vali Motion Gateway, mille soovid \u00fchendada" + }, "user": { "data": { "api_key": "API v\u00f5ti", "host": "IP-aadress" }, - "description": "Vaja on 16-kohalist API-v\u00f5tit. Juhiste saamiseks vt https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "description": "\u00dchenda oma Motion Gatewayga. Kui IP-aadress on m\u00e4\u00e4ramata kasutatakse automaatset avastamist", "title": "" } } diff --git a/homeassistant/components/motion_blinds/translations/fr.json b/homeassistant/components/motion_blinds/translations/fr.json new file mode 100644 index 00000000000000..86d008b9e6d768 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/fr.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "connect": { + "data": { + "api_key": "Cl\u00e9 API" + }, + "description": "Vous aurez besoin de la cl\u00e9 API de 16 caract\u00e8res, voir https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key pour les instructions" + }, + "select": { + "data": { + "select_ip": "Adresse IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/it.json b/homeassistant/components/motion_blinds/translations/it.json index ff56f184ac2e1e..1d79ae28ee5173 100644 --- a/homeassistant/components/motion_blinds/translations/it.json +++ b/homeassistant/components/motion_blinds/translations/it.json @@ -5,14 +5,31 @@ "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "connection_error": "Impossibile connettersi" }, + "error": { + "discovery_error": "Impossibile rilevare un Motion Gateway" + }, "flow_title": "Tende Motion", "step": { + "connect": { + "data": { + "api_key": "Chiave API" + }, + "description": "Avrai bisogno della chiave API di 16 caratteri, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key per le istruzioni", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Indirizzo IP" + }, + "description": "Esegui nuovamente l'installazione se desideri collegare altri Motion Gateway", + "title": "Seleziona il Motion Gateway che vorresti collegare" + }, "user": { "data": { "api_key": "Chiave API", "host": "Indirizzo IP" }, - "description": "Avrai bisogno della chiave API di 16 caratteri, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key per le istruzioni", + "description": "Connetti il tuo Motion Gateway, se l'indirizzo IP non \u00e8 impostato, sar\u00e0 utilizzato il rilevamento automatico", "title": "Tende Motion" } } diff --git a/homeassistant/components/motion_blinds/translations/lb.json b/homeassistant/components/motion_blinds/translations/lb.json index 7a3dcfdbf07eec..85caeea79e52f3 100644 --- a/homeassistant/components/motion_blinds/translations/lb.json +++ b/homeassistant/components/motion_blinds/translations/lb.json @@ -5,7 +5,21 @@ "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", "connection_error": "Feeler beim verbannen" }, + "error": { + "discovery_error": "Feeler beim Entdecken vun enger Motion Gateway" + }, "step": { + "connect": { + "data": { + "api_key": "API Schl\u00ebssel" + }, + "description": "Du brauchs de 16 stellegen API Schl\u00ebssel, kuck https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key fir w\u00e9ider Instruktiounen" + }, + "select": { + "data": { + "select_ip": "IP Adresse" + } + }, "user": { "data": { "api_key": "API Schl\u00ebssel", diff --git a/homeassistant/components/motion_blinds/translations/no.json b/homeassistant/components/motion_blinds/translations/no.json index 9e4061506912ee..e86da7c1fc494d 100644 --- a/homeassistant/components/motion_blinds/translations/no.json +++ b/homeassistant/components/motion_blinds/translations/no.json @@ -5,14 +5,31 @@ "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "connection_error": "Tilkobling mislyktes" }, + "error": { + "discovery_error": "Kunne ikke oppdage en Motion Gateway" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "API-n\u00f8kkel" + }, + "description": "Du trenger API-n\u00f8kkelen med 16 tegn, se https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instruksjoner", + "title": "" + }, + "select": { + "data": { + "select_ip": "IP adresse" + }, + "description": "Kj\u00f8r oppsettet p\u00e5 nytt hvis du vil koble til flere Motion Gateways", + "title": "Velg Motion Gateway som du vil koble til" + }, "user": { "data": { "api_key": "API-n\u00f8kkel", "host": "IP adresse" }, - "description": "Du trenger API-n\u00f8kkelen med 16 tegn, se https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instruksjoner", + "description": "Koble til Motion Gateway. Hvis IP-adressen ikke er angitt, brukes automatisk oppdagelse", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/pl.json b/homeassistant/components/motion_blinds/translations/pl.json index 8f73496fd1d2e2..1d34d22d65e21b 100644 --- a/homeassistant/components/motion_blinds/translations/pl.json +++ b/homeassistant/components/motion_blinds/translations/pl.json @@ -5,14 +5,31 @@ "already_in_progress": "Konfiguracja jest ju\u017c w toku", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, + "error": { + "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 bramki ruchu" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Klucz API" + }, + "description": "B\u0119dziesz potrzebowa\u0142 16-znakowego klucza API, instrukcje znajdziesz na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Adres IP" + }, + "description": "Uruchom ponownie konfiguracj\u0119, je\u015bli chcesz pod\u0142\u0105czy\u0107 dodatkowe bramki ruchu", + "title": "Wybierz bram\u0119 ruchu, z kt\u00f3r\u0105 chcesz si\u0119 po\u0142\u0105czy\u0107" + }, "user": { "data": { "api_key": "Klucz API", "host": "Adres IP" }, - "description": "B\u0119dziesz potrzebowa\u0142 16-znakowego klucza API, instrukcje znajdziesz na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "description": "Po\u0142\u0105cz si\u0119 z bram\u0105 ruchu. Je\u015bli adres IP nie jest ustawiony, u\u017cywane jest automatyczne wykrywanie", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/pt.json b/homeassistant/components/motion_blinds/translations/pt.json index fe188057e4616d..64ccd6061d2e06 100644 --- a/homeassistant/components/motion_blinds/translations/pt.json +++ b/homeassistant/components/motion_blinds/translations/pt.json @@ -5,12 +5,25 @@ "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", "connection_error": "Falha na liga\u00e7\u00e3o" }, + "flow_title": "Cortinas Motion", "step": { + "connect": { + "data": { + "api_key": "API Key" + }, + "title": "Cortinas Motion" + }, + "select": { + "data": { + "select_ip": "Endere\u00e7o IP" + } + }, "user": { "data": { "api_key": "API Key", "host": "Endere\u00e7o IP" - } + }, + "title": "Cortinas Motion" } } } diff --git a/homeassistant/components/motion_blinds/translations/ru.json b/homeassistant/components/motion_blinds/translations/ru.json index 1a249a4fab86a0..ae2d3229c208ca 100644 --- a/homeassistant/components/motion_blinds/translations/ru.json +++ b/homeassistant/components/motion_blinds/translations/ru.json @@ -5,14 +5,31 @@ "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.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, + "error": { + "discovery_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0448\u043b\u044e\u0437 Motion." + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441" + }, + "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0435\u0441\u043b\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0448\u043b\u044e\u0437", + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 Motion" + }, "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", "host": "IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Motion. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0448\u043b\u044e\u0437\u0430, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0443\u0441\u0442\u044b\u043c.", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/tr.json b/homeassistant/components/motion_blinds/translations/tr.json index 545a3547ffcd16..194608780c9853 100644 --- a/homeassistant/components/motion_blinds/translations/tr.json +++ b/homeassistant/components/motion_blinds/translations/tr.json @@ -1,14 +1,30 @@ { "config": { "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "connection_error": "Ba\u011flanma hatas\u0131" }, + "flow_title": "Hareketli Panjurlar", "step": { + "connect": { + "data": { + "api_key": "API Anahtar\u0131" + } + }, + "select": { + "data": { + "select_ip": "\u0130p Adresi" + }, + "title": "Ba\u011flamak istedi\u011finiz Hareket A\u011f Ge\u00e7idini se\u00e7in" + }, "user": { "data": { "api_key": "API Anahtar\u0131", "host": "IP adresi" - } + }, + "description": "Motion Gateway'inize ba\u011flan\u0131n, IP adresi ayarlanmad\u0131ysa, otomatik ke\u015fif kullan\u0131l\u0131r", + "title": "Hareketli Panjurlar" } } } diff --git a/homeassistant/components/motion_blinds/translations/uk.json b/homeassistant/components/motion_blinds/translations/uk.json new file mode 100644 index 00000000000000..99ccb60dc6cce9 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/uk.json @@ -0,0 +1,37 @@ +{ + "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_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.", + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "error": { + "discovery_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u044f\u0432\u0438\u0442\u0438 Motion Gateway" + }, + "flow_title": "Motion Blinds", + "step": { + "connect": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0434\u0438\u0432. https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0449\u0435 \u0440\u0430\u0437, \u044f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 Motion Gateway", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c Motion Gateway, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0457 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "title": "Motion Blinds" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/zh-Hant.json b/homeassistant/components/motion_blinds/translations/zh-Hant.json index 37925ca6288189..0f2f9881ebd09c 100644 --- a/homeassistant/components/motion_blinds/translations/zh-Hant.json +++ b/homeassistant/components/motion_blinds/translations/zh-Hant.json @@ -5,14 +5,31 @@ "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "connection_error": "\u9023\u7dda\u5931\u6557" }, + "error": { + "discovery_error": "\u63a2\u7d22 Motion \u9598\u9053\u5668\u5931\u6557" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "API \u5bc6\u9470" + }, + "description": "\u5c07\u9700\u8981\u8f38\u5165 16 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP \u4f4d\u5740" + }, + "description": "\u5047\u5982\u6b32\u9023\u7dda\u81f3\u5176\u4ed6 Motion \u9598\u9053\u5668\uff0c\u8acb\u518d\u57f7\u884c\u4e00\u6b21\u8a2d\u5b9a\u6b65\u9a5f", + "title": "\u9078\u64c7\u6240\u8981\u9023\u7dda\u7684 Motion \u7db2\u95dc" + }, "user": { "data": { "api_key": "API \u5bc6\u9470", "host": "IP \u4f4d\u5740" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 16 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002", + "description": "\u9023\u7dda\u81f3 Motion \u9598\u9053\u5668\uff0c\u5047\u5982\u672a\u63d0\u4f9b IP \u4f4d\u5740\uff0c\u5c07\u4f7f\u7528\u81ea\u52d5\u63a2\u7d22", "title": "Motion Blinds" } } diff --git a/homeassistant/components/mqtt/translations/cs.json b/homeassistant/components/mqtt/translations/cs.json index 325e8dde09831c..60c323d9051a91 100644 --- a/homeassistant/components/mqtt/translations/cs.json +++ b/homeassistant/components/mqtt/translations/cs.json @@ -38,13 +38,13 @@ "turn_on": "Zapnout" }, "trigger_type": { - "button_double_press": "Dvakr\u00e1t stisknuto \"{subtype}\"", + "button_double_press": "\"{subtype}\" stisknuto dvakr\u00e1t", "button_long_release": "Uvoln\u011bno \"{subtype}\" po dlouh\u00e9m stisku", - "button_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto \"{subtype}\"", - "button_quintuple_press": "P\u011btkr\u00e1t stisknuto \"{subtype}\"", - "button_short_press": "Stiknuto \"{subtype}\"", + "button_quadruple_press": "\"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "button_quintuple_press": "\"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "button_short_press": "\"{subtype}\" stisknuto", "button_short_release": "Uvoln\u011bno \"{subtype}\"", - "button_triple_press": "T\u0159ikr\u00e1t stisknuto \"{subtype}\"" + "button_triple_press": "\"{subtype}\" stisknuto t\u0159ikr\u00e1t" } }, "options": { diff --git a/homeassistant/components/mqtt/translations/de.json b/homeassistant/components/mqtt/translations/de.json index a92886eb0c6a5b..3346abfd53e22a 100644 --- a/homeassistant/components/mqtt/translations/de.json +++ b/homeassistant/components/mqtt/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Nur eine einzige Konfiguration von MQTT ist zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Es konnte keine Verbindung zum Broker hergestellt werden." + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "broker": { @@ -59,12 +59,15 @@ "password": "Passwort", "port": "Port", "username": "Benutzername" - } + }, + "description": "Bitte gib die Verbindungsinformationen deines MQTT-Brokers ein." }, "options": { "data": { + "discovery": "Erkennung aktivieren", "will_enable": "Letzten Willen aktivieren" - } + }, + "description": "Bitte die MQTT-Einstellungen ausw\u00e4hlen." } } } diff --git a/homeassistant/components/mqtt/translations/no.json b/homeassistant/components/mqtt/translations/no.json index 2a9372b3fb0578..12c72603a1fa32 100644 --- a/homeassistant/components/mqtt/translations/no.json +++ b/homeassistant/components/mqtt/translations/no.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktiver oppdagelse" }, - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til en MQTT megler som er levert av Hass.io-tillegget {addon}?", - "title": "MQTT megler via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til en MQTT megler som er levert av Hass.io-tillegg {addon}?", + "title": "MQTT megler via Hass.io-tillegg" } } }, diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index ce41d059b244b9..08b1d2f1974d56 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -38,14 +38,14 @@ "turn_on": "w\u0142\u0105cznik" }, "trigger_type": { - "button_double_press": "\"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", - "button_long_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", - "button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", - "button_quadruple_press": "\"{subtype}\" zostanie czterokrotnie naci\u015bni\u0119ty", - "button_quintuple_press": "\"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", - "button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", - "button_short_release": "\"{subtype}\" zostanie zwolniony", - "button_triple_press": "\"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" + "button_double_press": "przycisk \"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", + "button_long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", + "button_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "button_quadruple_press": "przycisk \"{subtype}\" zostanie czterokrotnie naci\u015bni\u0119ty", + "button_quintuple_press": "przycisk \"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", + "button_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "button_short_release": "przycisk \"{subtype}\" zostanie zwolniony", + "button_triple_press": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" } }, "options": { diff --git a/homeassistant/components/mqtt/translations/ru.json b/homeassistant/components/mqtt/translations/ru.json index 0079481d6f2541..7cc7a84b28c682 100644 --- a/homeassistant/components/mqtt/translations/ru.json +++ b/homeassistant/components/mqtt/translations/ru.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" } } }, diff --git a/homeassistant/components/mqtt/translations/tr.json b/homeassistant/components/mqtt/translations/tr.json index 1b73b94d5a4337..86dce2b6ea4b6e 100644 --- a/homeassistant/components/mqtt/translations/tr.json +++ b/homeassistant/components/mqtt/translations/tr.json @@ -1,11 +1,52 @@ { "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { + "broker": { + "data": { + "password": "Parola", + "port": "Port" + } + }, "hassio_confirm": { "data": { "discovery": "Ke\u015ffetmeyi etkinle\u015ftir" } } } + }, + "device_automation": { + "trigger_subtype": { + "turn_off": "Kapat", + "turn_on": "A\u00e7" + }, + "trigger_type": { + "button_double_press": "\" {subtype} \" \u00e7ift t\u0131kland\u0131", + "button_long_press": "\" {subtype} \" s\u00fcrekli olarak bas\u0131ld\u0131", + "button_quadruple_press": "\" {subtype} \" d\u00f6rt kez t\u0131kland\u0131", + "button_quintuple_press": "\" {subtype} \" be\u015fli t\u0131kland\u0131", + "button_short_press": "\" {subtype} \" bas\u0131ld\u0131", + "button_short_release": "\" {subtype} \" yay\u0131nland\u0131", + "button_triple_press": "\" {subtype} \" \u00fc\u00e7 kez t\u0131kland\u0131" + } + }, + "options": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "broker": { + "data": { + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/uk.json b/homeassistant/components/mqtt/translations/uk.json index 747d190a56d6a9..f871db4aa9d965 100644 --- a/homeassistant/components/mqtt/translations/uk.json +++ b/homeassistant/components/mqtt/translations/uk.json @@ -1,26 +1,84 @@ { "config": { "abort": { - "single_instance_allowed": "\u0414\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e MQTT." + "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." }, "error": { - "cannot_connect": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430." + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "step": { "broker": { "data": { "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", - "discovery": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0448\u0443\u043a", + "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0410\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0432\u0430\u0448\u043e\u0433\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT." + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0437 \u0432\u0430\u0448\u0438\u043c \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c MQTT." }, "hassio_confirm": { "data": { - "discovery": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0448\u0443\u043a" - } + "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0410\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" + }, + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_5": "\u041f'\u044f\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_6": "\u0428\u043e\u0441\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "button_double_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438", + "button_long_press": "{subtype} \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "button_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "button_quadruple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438", + "button_quintuple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432", + "button_short_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "button_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "button_triple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438" + } + }, + "options": { + "error": { + "bad_birth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f.", + "bad_will": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "broker": { + "data": { + "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0437 \u0432\u0430\u0448\u0438\u043c \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c MQTT." + }, + "options": { + "data": { + "birth_enable": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_payload": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_qos": "QoS \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_retain": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_topic": "\u0422\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f (LWT)", + "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f", + "will_enable": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_payload": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_qos": "QoS \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_retain": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_topic": "\u0422\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f (LWT)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 MQTT." } } } diff --git a/homeassistant/components/myq/translations/de.json b/homeassistant/components/myq/translations/de.json index d5c890e41699e6..fafa38c7817646 100644 --- a/homeassistant/components/myq/translations/de.json +++ b/homeassistant/components/myq/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ ist bereits konfiguriert" + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/myq/translations/tr.json b/homeassistant/components/myq/translations/tr.json new file mode 100644 index 00000000000000..7347d18bc34283 --- /dev/null +++ b/homeassistant/components/myq/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "MyQ A\u011f Ge\u00e7idine ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/uk.json b/homeassistant/components/myq/translations/uk.json new file mode 100644 index 00000000000000..12f8406de12b02 --- /dev/null +++ b/homeassistant/components/myq/translations/uk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "MyQ" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/de.json b/homeassistant/components/neato/translations/de.json index 94fcd3c4cb2b54..4c2fc456873181 100644 --- a/homeassistant/components/neato/translations/de.json +++ b/homeassistant/components/neato/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", "invalid_auth": "Ung\u00fcltige Authentifizierung", - "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte beachte die Dokumentation.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler sind [im Hilfebereich]({docs_url}) zu finden", "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, @@ -20,7 +20,7 @@ "title": "W\u00e4hle die Authentifizierungsmethode" }, "reauth_confirm": { - "title": "Wollen Sie mit der Einrichtung beginnen?" + "title": "M\u00f6chtest du mit der Einrichtung beginnen?" }, "user": { "data": { diff --git a/homeassistant/components/neato/translations/it.json b/homeassistant/components/neato/translations/it.json index 100237c33e6dad..95866e918c6a0a 100644 --- a/homeassistant/components/neato/translations/it.json +++ b/homeassistant/components/neato/translations/it.json @@ -2,14 +2,14 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "authorize_url_timeout": "Timeout nella generazione dell'URL di autorizzazione.", + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "invalid_auth": "Autenticazione non valida", - "missing_configuration": "Questo componente non \u00e8 configurato. Per favore segui la documentazione.", - "no_url_available": "Nessun URL disponibile. Per altre informazioni su questo errore, [controlla la sezione di aiuto]({docs_url})", - "reauth_successful": "Ri-autenticazione completata con successo" + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", + "reauth_successful": "La riautenticazione ha avuto successo" }, "create_entry": { - "default": "Autenticato con successo" + "default": "Autenticazione riuscita" }, "error": { "invalid_auth": "Autenticazione non valida", @@ -17,10 +17,10 @@ }, "step": { "pick_implementation": { - "title": "Scegli un metodo di autenticazione" + "title": "Scegli il metodo di autenticazione" }, "reauth_confirm": { - "title": "Vuoi cominciare la configurazione?" + "title": "Vuoi iniziare la configurazione?" }, "user": { "data": { diff --git a/homeassistant/components/neato/translations/lb.json b/homeassistant/components/neato/translations/lb.json index 44d8e4f68119c9..adc42ae840dcc0 100644 --- a/homeassistant/components/neato/translations/lb.json +++ b/homeassistant/components/neato/translations/lb.json @@ -2,7 +2,10 @@ "config": { "abort": { "already_configured": "Apparat ass scho konfigur\u00e9iert", - "invalid_auth": "Ong\u00eblteg Authentifikatioun" + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", + "reauth_successful": "Re-authentifikatioun war erfollegr\u00e4ich" }, "create_entry": { "default": "Kuckt [Neato Dokumentatioun]({docs_url})." @@ -12,6 +15,12 @@ "unknown": "Onerwaarte Feeler" }, "step": { + "pick_implementation": { + "title": "Authentifikatiouns Method auswielen" + }, + "reauth_confirm": { + "title": "Soll den Ariichtungs Prozess gestart ginn?" + }, "user": { "data": { "password": "Passwuert", @@ -22,5 +31,6 @@ "title": "Neato Kont Informatiounen" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/pt.json b/homeassistant/components/neato/translations/pt.json index 0672c9af33f7d2..48e73c763f0034 100644 --- a/homeassistant/components/neato/translations/pt.json +++ b/homeassistant/components/neato/translations/pt.json @@ -2,13 +2,26 @@ "config": { "abort": { "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "authorize_url_timeout": "Tempo excedido a gerar um URL de autoriza\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "Nenhum URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a sec\u00e7\u00e3o de ajuda]({docs_url})", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "reauth_confirm": { + "title": "Quer dar inicio \u00e0 configura\u00e7\u00e3o?" + }, "user": { "data": { "password": "Palavra-passe", diff --git a/homeassistant/components/neato/translations/tr.json b/homeassistant/components/neato/translations/tr.json new file mode 100644 index 00000000000000..53a8e0503cb460 --- /dev/null +++ b/homeassistant/components/neato/translations/tr.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7in" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "Neato Hesap Bilgisi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/uk.json b/homeassistant/components/neato/translations/uk.json new file mode 100644 index 00000000000000..58b56a52f6c4e3 --- /dev/null +++ b/homeassistant/components/neato/translations/uk.json @@ -0,0 +1,37 @@ +{ + "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.", + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\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.", + "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" + }, + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "reauth_confirm": { + "title": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "vendor": "\u0412\u0438\u0440\u043e\u0431\u043d\u0438\u043a" + }, + "description": "\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 \u0440\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0438\u0445 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c.", + "title": "Neato" + } + } + }, + "title": "Neato Botvac" +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/de.json b/homeassistant/components/nest/translations/de.json index 2bc328ff8f6659..3925b7537b220a 100644 --- a/homeassistant/components/nest/translations/de.json +++ b/homeassistant/components/nest/translations/de.json @@ -2,34 +2,43 @@ "config": { "abort": { "authorize_url_fail": "Unbekannter Fehler beim Erstellen der Authorisierungs-URL", - "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL", - "reauth_successful": "Neuathentifizierung erfolgreich", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, "error": { "internal_error": "Ein interner Fehler ist aufgetreten", "invalid_pin": "Ung\u00fcltiger PIN-Code", "timeout": "Ein zeit\u00fcberschreitungs Fehler ist aufgetreten", - "unknown": "Ein unbekannter Fehler ist aufgetreten" + "unknown": "Unerwarteter Fehler" }, "step": { "init": { "data": { "flow_impl": "Anbieter" }, - "description": "W\u00e4hlen, \u00fcber welchen Authentifizierungsanbieter du dich bei Nest authentifizieren m\u00f6chtest.", + "description": "W\u00e4hle die Authentifizierungsmethode", "title": "Authentifizierungsanbieter" }, "link": { "data": { - "code": "PIN Code" + "code": "PIN-Code" }, "description": "[Autorisiere dein Konto] ( {url} ), um deinen Nest-Account zu verkn\u00fcpfen.\n\n F\u00fcge anschlie\u00dfend den erhaltenen PIN Code hier ein.", "title": "Nest-Konto verkn\u00fcpfen" }, + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + }, "reauth_confirm": { "description": "Die Nest-Integration muss das Konto neu authentifizieren", - "title": "Integration neu authentifizieren" + "title": "Integration erneut authentifizieren" } } }, diff --git a/homeassistant/components/nest/translations/fr.json b/homeassistant/components/nest/translations/fr.json index be006913f652b9..03b55458e9bdc3 100644 --- a/homeassistant/components/nest/translations/fr.json +++ b/homeassistant/components/nest/translations/fr.json @@ -5,7 +5,8 @@ "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "create_entry": { "default": "Authentification r\u00e9ussie" diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 958eaea039a4cd..376437d20f0e9b 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -5,7 +5,7 @@ "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", - "reauth_successful": "Riautenticato con successo", + "reauth_successful": "La riautenticazione ha avuto successo", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "unknown_authorize_url_generation": "Errore sconosciuto durante la generazione di un URL di autorizzazione." }, @@ -38,7 +38,7 @@ }, "reauth_confirm": { "description": "L'integrazione di Nest deve autenticare nuovamente il tuo account", - "title": "Autentica nuovamente l'integrazione" + "title": "Reautenticare l'integrazione" } } }, diff --git a/homeassistant/components/nest/translations/lb.json b/homeassistant/components/nest/translations/lb.json index 1f0115a429b8bd..612d1f302589d6 100644 --- a/homeassistant/components/nest/translations/lb.json +++ b/homeassistant/components/nest/translations/lb.json @@ -4,7 +4,12 @@ "authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + "reauth_successful": "Re-authentifikatioun war erfollegr\u00e4ich", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech.", + "unknown_authorize_url_generation": "Onbekannte Feeler beim erstellen vun der Authorisatiouns URL." + }, + "create_entry": { + "default": "Erfollegr\u00e4ich authentifiz\u00e9iert" }, "error": { "internal_error": "Interne Feeler beim valid\u00e9ieren vum Code", diff --git a/homeassistant/components/nest/translations/pl.json b/homeassistant/components/nest/translations/pl.json index 63e45df12fa994..d1147e03afc316 100644 --- a/homeassistant/components/nest/translations/pl.json +++ b/homeassistant/components/nest/translations/pl.json @@ -5,6 +5,7 @@ "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", "no_url_available": "Brak dost\u0119pnego adresu URL. Aby uzyska\u0107 informacje na temat tego b\u0142\u0119du, [sprawd\u017a sekcj\u0119 pomocy] ({docs_url})", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "unknown_authorize_url_generation": "Nieznany b\u0142\u0105d podczas generowania URL autoryzacji" }, @@ -34,6 +35,10 @@ }, "pick_implementation": { "title": "Wybierz metod\u0119 uwierzytelniania" + }, + "reauth_confirm": { + "description": "Integracja Nest wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" } } }, diff --git a/homeassistant/components/nest/translations/pt.json b/homeassistant/components/nest/translations/pt.json index 6da647ac29bfef..33ff857af7e6c8 100644 --- a/homeassistant/components/nest/translations/pt.json +++ b/homeassistant/components/nest/translations/pt.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_fail": "Erro desconhecido ao gerar um URL de autoriza\u00e7\u00e3o.", - "authorize_url_timeout": "Limite temporal ultrapassado ao gerar um URL de autoriza\u00e7\u00e3o.", + "authorize_url_timeout": "Tempo excedido a gerar um URL de autoriza\u00e7\u00e3o", "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_url_available": "Nenhum URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a sec\u00e7\u00e3o de ajuda]({docs_url})", "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel.", @@ -36,5 +36,10 @@ "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "Movimento detectado" + } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/tr.json b/homeassistant/components/nest/translations/tr.json index 484cdaff6ec4a2..003c1ccc0c22d2 100644 --- a/homeassistant/components/nest/translations/tr.json +++ b/homeassistant/components/nest/translations/tr.json @@ -1,9 +1,20 @@ { + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "error": { + "unknown": "Beklenmeyen hata" + } + }, "device_automation": { "trigger_type": { "camera_motion": "Hareket alg\u0131land\u0131", "camera_person": "Ki\u015fi alg\u0131land\u0131", - "camera_sound": "Ses alg\u0131land\u0131" + "camera_sound": "Ses alg\u0131land\u0131", + "doorbell_chime": "Kap\u0131 zili bas\u0131ld\u0131" } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/uk.json b/homeassistant/components/nest/translations/uk.json new file mode 100644 index 00000000000000..f2869a76f4258a --- /dev/null +++ b/homeassistant/components/nest/translations/uk.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "authorize_url_fail": "\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.", + "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.", + "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.", + "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": { + "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." + }, + "error": { + "internal_error": "\u0412\u043d\u0443\u0442\u0440\u0456\u0448\u043d\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u043a\u043e\u0434\u0443.", + "invalid_pin": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 PIN-\u043a\u043e\u0434.", + "timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u043a\u043e\u0434\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "init": { + "data": { + "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457", + "title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "link": { + "data": { + "code": "PIN-\u043a\u043e\u0434" + }, + "description": "[\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c]({url}), \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0441\u0432\u043e\u044e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 Nest. \n \n\u041f\u0456\u0441\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 \u0441\u043a\u043e\u043f\u0456\u044e\u0439\u0442\u0435 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 PIN-\u043a\u043e\u0434.", + "title": "\u041f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 Nest" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "reauth_confirm": { + "description": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Nest", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + } + } + }, + "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_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" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/de.json b/homeassistant/components/netatmo/translations/de.json index 30cfba6dfed9a3..0be425d1e31cf9 100644 --- a/homeassistant/components/netatmo/translations/de.json +++ b/homeassistant/components/netatmo/translations/de.json @@ -1,8 +1,10 @@ { "config": { "abort": { - "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Autorisierungs-URL.", - "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "create_entry": { "default": "Erfolgreich authentifiziert." diff --git a/homeassistant/components/netatmo/translations/tr.json b/homeassistant/components/netatmo/translations/tr.json new file mode 100644 index 00000000000000..94dd5b3fb0f4c6 --- /dev/null +++ b/homeassistant/components/netatmo/translations/tr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "Alan\u0131n ad\u0131", + "lat_ne": "Enlem Kuzey-Do\u011fu k\u00f6\u015fesi", + "lat_sw": "Enlem G\u00fcney-Bat\u0131 k\u00f6\u015fesi", + "lon_ne": "Boylam Kuzey-Do\u011fu k\u00f6\u015fesi", + "lon_sw": "Boylam G\u00fcney-Bat\u0131 k\u00f6\u015fesi", + "mode": "Hesaplama", + "show_on_map": "Haritada g\u00f6ster" + }, + "description": "Bir alan i\u00e7in genel hava durumu sens\u00f6r\u00fc yap\u0131land\u0131r\u0131n.", + "title": "Netatmo genel hava durumu sens\u00f6r\u00fc" + }, + "public_weather_areas": { + "data": { + "new_area": "Alan ad\u0131", + "weather_areas": "Hava alanlar\u0131" + }, + "description": "Genel hava durumu sens\u00f6rlerini yap\u0131land\u0131r\u0131n.", + "title": "Netatmo genel hava durumu sens\u00f6r\u00fc" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/uk.json b/homeassistant/components/netatmo/translations/uk.json new file mode 100644 index 00000000000000..b8c439edfde4fc --- /dev/null +++ b/homeassistant/components/netatmo/translations/uk.json @@ -0,0 +1,43 @@ +{ + "config": { + "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.", + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "\u041d\u0430\u0437\u0432\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0456", + "lat_ne": "\u0428\u0438\u0440\u043e\u0442\u0430 (\u043f\u0456\u0432\u043d\u0456\u0447\u043d\u043e-\u0441\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u0443\u0442)", + "lat_sw": "\u0428\u0438\u0440\u043e\u0442\u0430 (\u044e\u0433\u043e-\u0437\u0430\u043f\u0430\u0434\u043d\u044b\u0439 \u0443\u0433\u043e\u043b)", + "lon_ne": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430 (\u043f\u0456\u0432\u043d\u0456\u0447\u043d\u043e-\u0441\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u0443\u0442)", + "lon_sw": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430 (\u043f\u0456\u0432\u0434\u0435\u043d\u043d\u043e-\u0437\u0430\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u0443\u0442)", + "mode": "\u0420\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043e\u043a", + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u0438 \u0434\u043b\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u0456", + "title": "\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u0438 Netatmo" + }, + "public_weather_areas": { + "data": { + "new_area": "\u041d\u0430\u0437\u0432\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0456", + "weather_areas": "\u041f\u043e\u0433\u043e\u0434\u043d\u0456 \u043e\u0431\u043b\u0430\u0441\u0442\u0456" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u0456\u0432 \u043f\u043e\u0433\u043e\u0434\u0438", + "title": "\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u0438 Netatmo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/de.json b/homeassistant/components/nexia/translations/de.json index 0ff4da3b2e1dd8..f2220f828e833b 100644 --- a/homeassistant/components/nexia/translations/de.json +++ b/homeassistant/components/nexia/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieses Nexia Home ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/nexia/translations/tr.json b/homeassistant/components/nexia/translations/tr.json new file mode 100644 index 00000000000000..47f3d931c46fb8 --- /dev/null +++ b/homeassistant/components/nexia/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "Mynexia.com'a ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/uk.json b/homeassistant/components/nexia/translations/uk.json new file mode 100644 index 00000000000000..8cb2aec836a9e3 --- /dev/null +++ b/homeassistant/components/nexia/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/de.json b/homeassistant/components/nightscout/translations/de.json index 8581b04099d695..510d57ce45f4c4 100644 --- a/homeassistant/components/nightscout/translations/de.json +++ b/homeassistant/components/nightscout/translations/de.json @@ -1,12 +1,18 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API-Schl\u00fcssel", "url": "URL" } } diff --git a/homeassistant/components/nightscout/translations/no.json b/homeassistant/components/nightscout/translations/no.json index db7b8f811cae88..d68fe45c684137 100644 --- a/homeassistant/components/nightscout/translations/no.json +++ b/homeassistant/components/nightscout/translations/no.json @@ -15,7 +15,7 @@ "api_key": "API-n\u00f8kkel", "url": "URL" }, - "description": "- URL: adressen til din nattscout-forekomst. Dvs: https://myhomeassistant.duckdns.org:5423 \n - API-n\u00f8kkel (valgfritt): Bruk bare hvis forekomsten din er beskyttet (auth_default_roles! = Lesbar).", + "description": "- URL: Adressen til din nattscout-forekomst. F. Eks: https://myhomeassistant.duckdns.org:5423 \n- API-n\u00f8kkel (valgfritt): Bruk bare hvis forekomsten din er beskyttet (auth_default_roles! = readable).", "title": "Skriv inn informasjon om Nightscout-serveren." } } diff --git a/homeassistant/components/nightscout/translations/tr.json b/homeassistant/components/nightscout/translations/tr.json index 585aace899de32..95f36a4d124aa0 100644 --- a/homeassistant/components/nightscout/translations/tr.json +++ b/homeassistant/components/nightscout/translations/tr.json @@ -1,11 +1,18 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, "error": { - "cannot_connect": "Ba\u011flan\u0131lamad\u0131" + "cannot_connect": "Ba\u011flan\u0131lamad\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" }, + "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API Anahtar\u0131", "url": "URL" } } diff --git a/homeassistant/components/nightscout/translations/uk.json b/homeassistant/components/nightscout/translations/uk.json new file mode 100644 index 00000000000000..6504b00eb883e0 --- /dev/null +++ b/homeassistant/components/nightscout/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "- URL: \u0430\u0434\u0440\u0435\u0441\u0430 \u0412\u0430\u0448\u043e\u0433\u043e Nightscout. \u041d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: https://myhomeassistant.duckdns.org:5423\n - \u041a\u043b\u044e\u0447 API (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e): \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435, \u043b\u0438\u0448\u0435 \u044f\u043a\u0449\u043e \u0412\u0430\u0448 Nightcout \u0437\u0430\u0445\u0438\u0449\u0435\u043d\u0438\u0439 (auth_default_roles != readable).", + "title": "Nightscout" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notify/translations/uk.json b/homeassistant/components/notify/translations/uk.json index 86821a3e50f7f3..d87752255d58c1 100644 --- a/homeassistant/components/notify/translations/uk.json +++ b/homeassistant/components/notify/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u041f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f" + "title": "\u0421\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f" } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/de.json b/homeassistant/components/notion/translations/de.json index f322826c45b2ab..0b421911aa7dea 100644 --- a/homeassistant/components/notion/translations/de.json +++ b/homeassistant/components/notion/translations/de.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieser Benutzername wird bereits benutzt." + "already_configured": "Konto wurde bereits konfiguriert" }, "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "no_devices": "Keine Ger\u00e4te im Konto gefunden" }, "step": { diff --git a/homeassistant/components/notion/translations/tr.json b/homeassistant/components/notion/translations/tr.json index 8966b79df1b5c4..f89e3fb75338dd 100644 --- a/homeassistant/components/notion/translations/tr.json +++ b/homeassistant/components/notion/translations/tr.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "no_devices": "Hesapta cihaz bulunamad\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/uk.json b/homeassistant/components/notion/translations/uk.json new file mode 100644 index 00000000000000..6dc969c3609592 --- /dev/null +++ b/homeassistant/components/notion/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "no_devices": "\u041d\u0435\u043c\u0430\u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Notion" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/de.json b/homeassistant/components/nuheat/translations/de.json index 52c30681efcad4..8599f7fe1b5a99 100644 --- a/homeassistant/components/nuheat/translations/de.json +++ b/homeassistant/components/nuheat/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Der Thermostat ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_thermostat": "Die Seriennummer des Thermostats ist ung\u00fcltig.", "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/nuheat/translations/tr.json b/homeassistant/components/nuheat/translations/tr.json new file mode 100644 index 00000000000000..5123f1c7d9af15 --- /dev/null +++ b/homeassistant/components/nuheat/translations/tr.json @@ -0,0 +1,22 @@ +{ + "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", + "invalid_thermostat": "Termostat seri numaras\u0131 ge\u00e7ersiz.", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "serial_number": "Termostat\u0131n seri numaras\u0131.", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/uk.json b/homeassistant/components/nuheat/translations/uk.json new file mode 100644 index 00000000000000..21be3968eb733d --- /dev/null +++ b/homeassistant/components/nuheat/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_thermostat": "\u0421\u0435\u0440\u0456\u0439\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430 \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "serial_number": "\u0421\u0435\u0440\u0456\u0439\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0430\u0431\u043e ID \u0412\u0430\u0448\u043e\u0433\u043e \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430, \u043d\u0430 \u0441\u0430\u0439\u0442\u0456 https://MyNuHeat.com.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/ca.json b/homeassistant/components/nuki/translations/ca.json new file mode 100644 index 00000000000000..a08308e78977f1 --- /dev/null +++ b/homeassistant/components/nuki/translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port", + "token": "Token d'acc\u00e9s" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/cs.json b/homeassistant/components/nuki/translations/cs.json new file mode 100644 index 00000000000000..349c92805cf8f1 --- /dev/null +++ b/homeassistant/components/nuki/translations/cs.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "port": "Port", + "token": "P\u0159\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/en.json b/homeassistant/components/nuki/translations/en.json index 70ae9c6a1fe171..135e8de2b2f6d8 100644 --- a/homeassistant/components/nuki/translations/en.json +++ b/homeassistant/components/nuki/translations/en.json @@ -2,15 +2,15 @@ "config": { "error": { "cannot_connect": "Failed to connect", - "invalid_auth": "Could not login with provided token", - "unknown": "Unknown error" + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" }, "step": { "user": { "data": { - "token": "Access Token", "host": "Host", - "port": "Port" + "port": "Port", + "token": "Access Token" } } } diff --git a/homeassistant/components/nuki/translations/et.json b/homeassistant/components/nuki/translations/et.json new file mode 100644 index 00000000000000..750afff003c24e --- /dev/null +++ b/homeassistant/components/nuki/translations/et.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Juurdep\u00e4\u00e4sut\u00f5end" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/it.json b/homeassistant/components/nuki/translations/it.json new file mode 100644 index 00000000000000..899093e1f4182f --- /dev/null +++ b/homeassistant/components/nuki/translations/it.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Porta", + "token": "Token di accesso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/no.json b/homeassistant/components/nuki/translations/no.json new file mode 100644 index 00000000000000..8cdbac230d735a --- /dev/null +++ b/homeassistant/components/nuki/translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "port": "Port", + "token": "Tilgangstoken" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/pl.json b/homeassistant/components/nuki/translations/pl.json new file mode 100644 index 00000000000000..77a7c31ee34e00 --- /dev/null +++ b/homeassistant/components/nuki/translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port", + "token": "Token dost\u0119pu" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/ru.json b/homeassistant/components/nuki/translations/ru.json new file mode 100644 index 00000000000000..bad9f35c076bbf --- /dev/null +++ b/homeassistant/components/nuki/translations/ru.json @@ -0,0 +1,18 @@ +{ + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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", + "port": "\u041f\u043e\u0440\u0442", + "token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/tr.json b/homeassistant/components/nuki/translations/tr.json new file mode 100644 index 00000000000000..ba6a496fa4c049 --- /dev/null +++ b/homeassistant/components/nuki/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port", + "token": "Eri\u015fim Belirteci" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/zh-Hant.json b/homeassistant/components/nuki/translations/zh-Hant.json new file mode 100644 index 00000000000000..662d7ed6ed95c5 --- /dev/null +++ b/homeassistant/components/nuki/translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0", + "token": "\u5b58\u53d6\u5bc6\u9470" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/ca.json b/homeassistant/components/number/translations/ca.json new file mode 100644 index 00000000000000..0058f01aac0f4f --- /dev/null +++ b/homeassistant/components/number/translations/ca.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Estableix el valor de {entity_name}" + } + }, + "title": "N\u00famero" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/cs.json b/homeassistant/components/number/translations/cs.json new file mode 100644 index 00000000000000..a6810f08c61416 --- /dev/null +++ b/homeassistant/components/number/translations/cs.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Nastavit hodnotu pro {entity_name}" + } + }, + "title": "\u010c\u00edslo" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/en.json b/homeassistant/components/number/translations/en.json new file mode 100644 index 00000000000000..4e3fe6536b3451 --- /dev/null +++ b/homeassistant/components/number/translations/en.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Set value for {entity_name}" + } + }, + "title": "Number" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/et.json b/homeassistant/components/number/translations/et.json new file mode 100644 index 00000000000000..36958c0fc77c4a --- /dev/null +++ b/homeassistant/components/number/translations/et.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Olemi {entity_name} v\u00e4\u00e4rtuse m\u00e4\u00e4ramine" + } + }, + "title": "Number" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/it.json b/homeassistant/components/number/translations/it.json new file mode 100644 index 00000000000000..135467cea9bf9f --- /dev/null +++ b/homeassistant/components/number/translations/it.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Imposta il valore per {entity_name}" + } + }, + "title": "Numero" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/no.json b/homeassistant/components/number/translations/no.json new file mode 100644 index 00000000000000..ad82c4ac6d1119 --- /dev/null +++ b/homeassistant/components/number/translations/no.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Angi verdi for {entity_name}" + } + }, + "title": "Nummer" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/pl.json b/homeassistant/components/number/translations/pl.json new file mode 100644 index 00000000000000..93d5dd045990f1 --- /dev/null +++ b/homeassistant/components/number/translations/pl.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "ustaw warto\u015b\u0107 dla {entity_name}" + } + }, + "title": "Number" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/ru.json b/homeassistant/components/number/translations/ru.json new file mode 100644 index 00000000000000..5e250b4e2db8c6 --- /dev/null +++ b/homeassistant/components/number/translations/ru.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043b\u044f {entity_name}" + } + }, + "title": "\u0427\u0438\u0441\u043b\u043e" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/tr.json b/homeassistant/components/number/translations/tr.json new file mode 100644 index 00000000000000..dfdbd90531752a --- /dev/null +++ b/homeassistant/components/number/translations/tr.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name} i\u00e7in de\u011fer ayarlay\u0131n" + } + }, + "title": "Numara" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/zh-Hant.json b/homeassistant/components/number/translations/zh-Hant.json new file mode 100644 index 00000000000000..d36f751682d7ab --- /dev/null +++ b/homeassistant/components/number/translations/zh-Hant.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name} \u8a2d\u5b9a\u503c" + } + }, + "title": "\u865f\u78bc" +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/de.json b/homeassistant/components/nut/translations/de.json index 793ab5bfa7c102..50d37fa8ec4bc8 100644 --- a/homeassistant/components/nut/translations/de.json +++ b/homeassistant/components/nut/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/nut/translations/tr.json b/homeassistant/components/nut/translations/tr.json new file mode 100644 index 00000000000000..b383d7656191ff --- /dev/null +++ b/homeassistant/components/nut/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "resources": { + "data": { + "resources": "Kaynaklar" + } + }, + "ups": { + "data": { + "alias": "Takma ad" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "resources": "Kaynaklar" + }, + "description": "Sens\u00f6r Kaynaklar\u0131'n\u0131 se\u00e7in." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/uk.json b/homeassistant/components/nut/translations/uk.json new file mode 100644 index 00000000000000..b25fe854560190 --- /dev/null +++ b/homeassistant/components/nut/translations/uk.json @@ -0,0 +1,46 @@ +{ + "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." + }, + "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": { + "resources": { + "data": { + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u0438 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + }, + "ups": { + "data": { + "alias": "\u041f\u0441\u0435\u0432\u0434\u043e\u043d\u0456\u043c", + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c UPS \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 NUT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438", + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/de.json b/homeassistant/components/nws/translations/de.json index 1461d86b2e5665..3d409bf885b4b3 100644 --- a/homeassistant/components/nws/translations/de.json +++ b/homeassistant/components/nws/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/nws/translations/tr.json b/homeassistant/components/nws/translations/tr.json new file mode 100644 index 00000000000000..8f51593aedbacf --- /dev/null +++ b/homeassistant/components/nws/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + }, + "description": "Bir METAR istasyon kodu belirtilmezse, en yak\u0131n istasyonu bulmak i\u00e7in enlem ve boylam kullan\u0131lacakt\u0131r. \u015eimdilik bir API Anahtar\u0131 herhangi bir \u015fey olabilir. Ge\u00e7erli bir e-posta adresi kullanman\u0131z tavsiye edilir." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/uk.json b/homeassistant/components/nws/translations/uk.json new file mode 100644 index 00000000000000..1e6886540ae897 --- /dev/null +++ b/homeassistant/components/nws/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "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": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "station": "\u041a\u043e\u0434 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 METAR" + }, + "description": "\u042f\u043a\u0449\u043e \u043a\u043e\u0434 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 METAR \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u043e, \u0434\u043b\u044f \u043f\u043e\u0448\u0443\u043a\u0443 \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0448\u0438\u0440\u043e\u0442\u0430 \u0456 \u0434\u043e\u0432\u0433\u043e\u0442\u0430. \u041d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043b\u044e\u0447 API \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u043c. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0456\u044e\u0447\u0443 \u0430\u0434\u0440\u0435\u0441\u0443 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438.", + "title": "National Weather Service" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/de.json b/homeassistant/components/nzbget/translations/de.json index 018f3870c58693..529eff3d9a2a53 100644 --- a/homeassistant/components/nzbget/translations/de.json +++ b/homeassistant/components/nzbget/translations/de.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown": "Unerwarteter Fehler" }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "flow_title": "NZBGet: {name}", "step": { "user": { @@ -11,7 +15,9 @@ "name": "Name", "password": "Passwort", "port": "Port", - "username": "Benutzername" + "ssl": "Nutzt ein SSL-Zertifikat", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat verfizieren" }, "title": "Mit NZBGet verbinden" } diff --git a/homeassistant/components/nzbget/translations/tr.json b/homeassistant/components/nzbget/translations/tr.json new file mode 100644 index 00000000000000..63b6c489018479 --- /dev/null +++ b/homeassistant/components/nzbget/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "G\u00fcncelle\u015ftirme s\u0131kl\u0131\u011f\u0131 (saniye)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/uk.json b/homeassistant/components/nzbget/translations/uk.json new file mode 100644 index 00000000000000..eba15cca19c175 --- /dev/null +++ b/homeassistant/components/nzbget/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "NZBGet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/de.json b/homeassistant/components/omnilogic/translations/de.json index 382156757010d5..4378d39912d90c 100644 --- a/homeassistant/components/omnilogic/translations/de.json +++ b/homeassistant/components/omnilogic/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/omnilogic/translations/tr.json b/homeassistant/components/omnilogic/translations/tr.json new file mode 100644 index 00000000000000..ab93b71de844f9 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/uk.json b/homeassistant/components/omnilogic/translations/uk.json new file mode 100644 index 00000000000000..21ebf6f4fafa26 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/uk.json @@ -0,0 +1,29 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onboarding/translations/uk.json b/homeassistant/components/onboarding/translations/uk.json new file mode 100644 index 00000000000000..595726cbd34bb8 --- /dev/null +++ b/homeassistant/components/onboarding/translations/uk.json @@ -0,0 +1,7 @@ +{ + "area": { + "bedroom": "\u0421\u043f\u0430\u043b\u044c\u043d\u044f", + "kitchen": "\u041a\u0443\u0445\u043d\u044f", + "living_room": "\u0412\u0456\u0442\u0430\u043b\u044c\u043d\u044f" + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/ca.json b/homeassistant/components/ondilo_ico/translations/ca.json new file mode 100644 index 00000000000000..77453bda398928 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/ca.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/cs.json b/homeassistant/components/ondilo_ico/translations/cs.json new file mode 100644 index 00000000000000..bcb8849839caab --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/cs.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace." + }, + "create_entry": { + "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno" + }, + "step": { + "pick_implementation": { + "title": "Vyberte metodu ov\u011b\u0159en\u00ed" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/de.json b/homeassistant/components/ondilo_ico/translations/de.json new file mode 100644 index 00000000000000..5bab6ed132bf52 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/en.json b/homeassistant/components/ondilo_ico/translations/en.json index e3849fc17a3aab..c88a152ef81fcf 100644 --- a/homeassistant/components/ondilo_ico/translations/en.json +++ b/homeassistant/components/ondilo_ico/translations/en.json @@ -12,5 +12,6 @@ "title": "Pick Authentication Method" } } - } + }, + "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/es.json b/homeassistant/components/ondilo_ico/translations/es.json new file mode 100644 index 00000000000000..2394c610796eaa --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/es.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n." + }, + "create_entry": { + "default": "Autenticado correctamente" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/et.json b/homeassistant/components/ondilo_ico/translations/et.json new file mode 100644 index 00000000000000..132e9849cf104f --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/et.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/it.json b/homeassistant/components/ondilo_ico/translations/it.json new file mode 100644 index 00000000000000..cd75684a4372ab --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/it.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/lb.json b/homeassistant/components/ondilo_ico/translations/lb.json new file mode 100644 index 00000000000000..d9a5cc7482a6ce --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/lb.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", + "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun." + }, + "create_entry": { + "default": "Erfollegr\u00e4ich authentifiz\u00e9iert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/no.json b/homeassistant/components/ondilo_ico/translations/no.json new file mode 100644 index 00000000000000..4a06b93d045de5 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/no.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "step": { + "pick_implementation": { + "title": "Velg godkjenningsmetode" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/pl.json b/homeassistant/components/ondilo_ico/translations/pl.json new file mode 100644 index 00000000000000..f3aa08a250f83e --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/pl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/ru.json b/homeassistant/components/ondilo_ico/translations/ru.json new file mode 100644 index 00000000000000..56bb2d342b7576 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/ru.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438." + }, + "create_entry": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/tr.json b/homeassistant/components/ondilo_ico/translations/tr.json new file mode 100644 index 00000000000000..9672275736570a --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7in" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/uk.json b/homeassistant/components/ondilo_ico/translations/uk.json new file mode 100644 index 00000000000000..31e5834b027aa2 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/zh-Hant.json b/homeassistant/components/ondilo_ico/translations/zh-Hant.json new file mode 100644 index 00000000000000..ea1902b3295535 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/zh-Hant.json @@ -0,0 +1,17 @@ +{ + "config": { + "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" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/de.json b/homeassistant/components/onewire/translations/de.json index 3cc9f9cfc68f75..d3ed8137da3473 100644 --- a/homeassistant/components/onewire/translations/de.json +++ b/homeassistant/components/onewire/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_path": "Verzeichnis nicht gefunden." diff --git a/homeassistant/components/onewire/translations/tr.json b/homeassistant/components/onewire/translations/tr.json new file mode 100644 index 00000000000000..f59da2ab7e74d6 --- /dev/null +++ b/homeassistant/components/onewire/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_path": "Dizin bulunamad\u0131." + }, + "step": { + "owserver": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "user": { + "data": { + "type": "Ba\u011flant\u0131 t\u00fcr\u00fc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/uk.json b/homeassistant/components/onewire/translations/uk.json new file mode 100644 index 00000000000000..9c9705d2993ce0 --- /dev/null +++ b/homeassistant/components/onewire/translations/uk.json @@ -0,0 +1,26 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_path": "\u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + "step": { + "owserver": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e owserver" + }, + "user": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/de.json b/homeassistant/components/onvif/translations/de.json index 25984ecf3e17c6..5289f6479cc050 100644 --- a/homeassistant/components/onvif/translations/de.json +++ b/homeassistant/components/onvif/translations/de.json @@ -1,12 +1,15 @@ { "config": { "abort": { - "already_configured": "Das ONVIF-Ger\u00e4t ist bereits konfiguriert.", - "already_in_progress": "Der Konfigurationsfluss f\u00fcr das ONVIF-Ger\u00e4t wird bereits ausgef\u00fchrt.", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "no_h264": "Es waren keine H264-Streams verf\u00fcgbar. \u00dcberpr\u00fcfen Sie die Profilkonfiguration auf Ihrem Ger\u00e4t.", "no_mac": "Die eindeutige ID f\u00fcr das ONVIF-Ger\u00e4t konnte nicht konfiguriert werden.", "onvif_error": "Fehler beim Einrichten des ONVIF-Ger\u00e4ts. \u00dcberpr\u00fcfen Sie die Protokolle auf weitere Informationen." }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "auth": { "data": { diff --git a/homeassistant/components/onvif/translations/tr.json b/homeassistant/components/onvif/translations/tr.json index 4e3ad18a60d685..683dfbe7b92615 100644 --- a/homeassistant/components/onvif/translations/tr.json +++ b/homeassistant/components/onvif/translations/tr.json @@ -1,8 +1,42 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { + "auth": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "configure_profile": { + "data": { + "include": "Kamera varl\u0131\u011f\u0131 olu\u015ftur" + }, + "title": "Profilleri Yap\u0131land\u0131r" + }, + "device": { + "data": { + "host": "Ke\u015ffedilen ONVIF cihaz\u0131n\u0131 se\u00e7in" + }, + "title": "ONVIF cihaz\u0131n\u0131 se\u00e7in" + }, + "manual_input": { + "data": { + "host": "Ana Bilgisayar", + "name": "Ad", + "port": "Port" + }, + "title": "ONVIF cihaz\u0131n\u0131 yap\u0131land\u0131r\u0131n" + }, "user": { - "description": "G\u00f6nder d\u00fc\u011fmesine t\u0131klad\u0131\u011f\u0131n\u0131zda, Profil S'yi destekleyen ONVIF cihazlar\u0131 i\u00e7in a\u011f\u0131n\u0131zda arama yapaca\u011f\u0131z. \n\n Baz\u0131 \u00fcreticiler varsay\u0131lan olarak ONVIF'i devre d\u0131\u015f\u0131 b\u0131rakmaya ba\u015flad\u0131. L\u00fctfen kameran\u0131z\u0131n yap\u0131land\u0131rmas\u0131nda ONVIF'in etkinle\u015ftirildi\u011finden emin olun." + "description": "G\u00f6nder d\u00fc\u011fmesine t\u0131klad\u0131\u011f\u0131n\u0131zda, Profil S'yi destekleyen ONVIF cihazlar\u0131 i\u00e7in a\u011f\u0131n\u0131zda arama yapaca\u011f\u0131z. \n\n Baz\u0131 \u00fcreticiler varsay\u0131lan olarak ONVIF'i devre d\u0131\u015f\u0131 b\u0131rakmaya ba\u015flad\u0131. L\u00fctfen kameran\u0131z\u0131n yap\u0131land\u0131rmas\u0131nda ONVIF'in etkinle\u015ftirildi\u011finden emin olun.", + "title": "ONVIF cihaz kurulumu" } } }, diff --git a/homeassistant/components/onvif/translations/uk.json b/homeassistant/components/onvif/translations/uk.json new file mode 100644 index 00000000000000..82a816add044e3 --- /dev/null +++ b/homeassistant/components/onvif/translations/uk.json @@ -0,0 +1,59 @@ +{ + "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_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.", + "no_h264": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043f\u043e\u0442\u043e\u043a\u0456\u0432 H264. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043d\u0430 \u0441\u0432\u043e\u0454\u043c\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457.", + "no_mac": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", + "onvif_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "auth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u0423\u0432\u0456\u0439\u0442\u0438" + }, + "configure_profile": { + "data": { + "include": "\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043e\u0431'\u0454\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u0438" + }, + "description": "\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043e\u0431'\u0454\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u0438 \u0434\u043b\u044f {profile} \u0437 \u0440\u043e\u0437\u0434\u0456\u043b\u044c\u043d\u043e\u044e \u0437\u0434\u0430\u0442\u043d\u0456\u0441\u0442\u044e {resolution}?", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u0456\u0432" + }, + "device": { + "data": { + "host": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ONVIF" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ONVIF" + }, + "manual_input": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" + }, + "user": { + "description": "\u041a\u043e\u043b\u0438 \u0412\u0438 \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438, \u043f\u043e\u0447\u043d\u0435\u0442\u044c\u0441\u044f \u043f\u043e\u0448\u0443\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 ONVIF, \u044f\u043a\u0456 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c Profile S. \n\n\u0414\u0435\u044f\u043a\u0456 \u0432\u0438\u0440\u043e\u0431\u043d\u0438\u043a\u0438 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0437\u0430 \u0443\u043c\u043e\u0432\u0447\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u044c ONVIF. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e ONVIF \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0412\u0430\u0448\u043e\u0457 \u043a\u0430\u043c\u0435\u0440\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0438 FFMPEG", + "rtsp_transport": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u0438\u0439 \u043c\u0435\u0445\u0430\u043d\u0456\u0437\u043c RTSP" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/de.json b/homeassistant/components/opentherm_gw/translations/de.json index 6e8d02bc7923c4..36b76592945016 100644 --- a/homeassistant/components/opentherm_gw/translations/de.json +++ b/homeassistant/components/opentherm_gw/translations/de.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Gateway bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "id_exists": "Gateway-ID ist bereits vorhanden" }, diff --git a/homeassistant/components/opentherm_gw/translations/tr.json b/homeassistant/components/opentherm_gw/translations/tr.json new file mode 100644 index 00000000000000..507b71ede5b270 --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "init": { + "data": { + "device": "Yol veya URL" + }, + "title": "OpenTherm A\u011f Ge\u00e7idi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/uk.json b/homeassistant/components/opentherm_gw/translations/uk.json new file mode 100644 index 00000000000000..af7699271136b0 --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "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", + "id_exists": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0448\u043b\u044e\u0437\u0443 \u0432\u0436\u0435 \u0456\u0441\u043d\u0443\u0454." + }, + "step": { + "init": { + "data": { + "device": "\u0428\u043b\u044f\u0445 \u0430\u0431\u043e URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "OpenTherm" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "floor_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043f\u0456\u0434\u043b\u043e\u0433\u0438", + "precision": "\u0422\u043e\u0447\u043d\u0456\u0441\u0442\u044c" + }, + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0443 Opentherm" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/de.json b/homeassistant/components/openuv/translations/de.json index fae3f0f062095b..88f9e69a5b6502 100644 --- a/homeassistant/components/openuv/translations/de.json +++ b/homeassistant/components/openuv/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten sind bereits registriert." + "already_configured": "Standort ist bereits konfiguriert" }, "error": { "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" diff --git a/homeassistant/components/openuv/translations/tr.json b/homeassistant/components/openuv/translations/tr.json new file mode 100644 index 00000000000000..241c588f691865 --- /dev/null +++ b/homeassistant/components/openuv/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/uk.json b/homeassistant/components/openuv/translations/uk.json index fef350a3f3cfd8..bd29fe692e1b3b 100644 --- a/homeassistant/components/openuv/translations/uk.json +++ b/homeassistant/components/openuv/translations/uk.json @@ -1,13 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + }, "step": { "user": { "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", "elevation": "\u0412\u0438\u0441\u043e\u0442\u0430", "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" }, - "title": "\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e" + "title": "OpenUV" } } } diff --git a/homeassistant/components/openweathermap/translations/de.json b/homeassistant/components/openweathermap/translations/de.json index 239b47e2d3e0c1..cac601b71d32cd 100644 --- a/homeassistant/components/openweathermap/translations/de.json +++ b/homeassistant/components/openweathermap/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" }, "step": { "user": { diff --git a/homeassistant/components/openweathermap/translations/tr.json b/homeassistant/components/openweathermap/translations/tr.json new file mode 100644 index 00000000000000..0f845a4df73453 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/tr.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam", + "mode": "Mod" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Mod" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/uk.json b/homeassistant/components/openweathermap/translations/uk.json new file mode 100644 index 00000000000000..7a39cfa078e829 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/uk.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "language": "\u041c\u043e\u0432\u0430", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 OpenWeatherMap. \u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 https://openweathermap.org/appid.", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "\u041c\u043e\u0432\u0430", + "mode": "\u0420\u0435\u0436\u0438\u043c" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/de.json b/homeassistant/components/ovo_energy/translations/de.json index 3bd083e48394bd..761f6a7d247561 100644 --- a/homeassistant/components/ovo_energy/translations/de.json +++ b/homeassistant/components/ovo_energy/translations/de.json @@ -1,8 +1,11 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { "data": { @@ -13,7 +16,8 @@ "data": { "password": "Passwort", "username": "Benutzername" - } + }, + "title": "Ovo Energy Account hinzuf\u00fcgen" } } } diff --git a/homeassistant/components/ovo_energy/translations/fr.json b/homeassistant/components/ovo_energy/translations/fr.json index 86719e87df4fce..351e20641aaff1 100644 --- a/homeassistant/components/ovo_energy/translations/fr.json +++ b/homeassistant/components/ovo_energy/translations/fr.json @@ -1,11 +1,16 @@ { "config": { "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification invalide" }, "step": { + "reauth": { + "data": { + "password": "Mot de passe" + } + }, "user": { "data": { "password": "Mot de passe", diff --git a/homeassistant/components/ovo_energy/translations/lb.json b/homeassistant/components/ovo_energy/translations/lb.json index 0e007924b6a3dc..b27b7d9702c357 100644 --- a/homeassistant/components/ovo_energy/translations/lb.json +++ b/homeassistant/components/ovo_energy/translations/lb.json @@ -6,6 +6,11 @@ "invalid_auth": "Ong\u00eblteg Authentifikatioun" }, "step": { + "reauth": { + "data": { + "password": "Passwuert" + } + }, "user": { "data": { "password": "Passwuert", diff --git a/homeassistant/components/ovo_energy/translations/tr.json b/homeassistant/components/ovo_energy/translations/tr.json index f3784f6de87fcd..714daac3253ccd 100644 --- a/homeassistant/components/ovo_energy/translations/tr.json +++ b/homeassistant/components/ovo_energy/translations/tr.json @@ -1,5 +1,10 @@ { "config": { + "error": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, "flow_title": "OVO Enerji: {username}", "step": { "reauth": { @@ -8,6 +13,12 @@ }, "description": "OVO Energy i\u00e7in kimlik do\u011frulama ba\u015far\u0131s\u0131z oldu. L\u00fctfen mevcut kimlik bilgilerinizi girin.", "title": "Yeniden kimlik do\u011frulama" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } } } } diff --git a/homeassistant/components/ovo_energy/translations/uk.json b/homeassistant/components/ovo_energy/translations/uk.json new file mode 100644 index 00000000000000..8a5f8e2a8ba756 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "OVO Energy: {username}", + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043f\u043e\u0442\u043e\u0447\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 OVO Energy.", + "title": "OVO Energy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/de.json b/homeassistant/components/owntracks/translations/de.json index 9d832cc264a040..0bc533c04690c5 100644 --- a/homeassistant/components/owntracks/translations/de.json +++ b/homeassistant/components/owntracks/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "create_entry": { "default": "\n\n\u00d6ffnen unter Android [die OwnTracks-App]({android_url}) und gehe zu {android_url} - > Verbindung. \u00c4nder die folgenden Einstellungen: \n - Modus: Privates HTTP \n - Host: {webhook_url} \n - Identifizierung: \n - Benutzername: `''` \n - Ger\u00e4te-ID: `''` \n\n\u00d6ffnen unter iOS [die OwnTracks-App]({ios_url}) und tippe auf das Symbol (i) oben links - > Einstellungen. \u00c4nder die folgenden Einstellungen: \n - Modus: HTTP \n - URL: {webhook_url} \n - Aktivieren Sie die Authentifizierung \n - UserID: `''`\n\n {secret} \n \n Weitere Informationen findest du in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/owntracks/translations/tr.json b/homeassistant/components/owntracks/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/owntracks/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/uk.json b/homeassistant/components/owntracks/translations/uk.json index f1f3186424297b..e6a6fc26068e90 100644 --- a/homeassistant/components/owntracks/translations/uk.json +++ b/homeassistant/components/owntracks/translations/uk.json @@ -1,5 +1,11 @@ { "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." + }, + "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." + }, "step": { "user": { "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 OwnTracks?", diff --git a/homeassistant/components/ozw/translations/ca.json b/homeassistant/components/ozw/translations/ca.json index 4553589e36d7e5..835c16eb449562 100644 --- a/homeassistant/components/ozw/translations/ca.json +++ b/homeassistant/components/ozw/translations/ca.json @@ -26,7 +26,7 @@ "data": { "use_addon": "Utilitza el complement OpenZWave Supervisor" }, - "description": "Voleu utilitzar el complement OpenZWave Supervisor?", + "description": "Vols utilitzar el complement Supervisor d'OpenZWave?", "title": "Selecciona el m\u00e8tode de connexi\u00f3" }, "start_addon": { diff --git a/homeassistant/components/ozw/translations/de.json b/homeassistant/components/ozw/translations/de.json index 70eaaaf18df410..afa26fb7e03040 100644 --- a/homeassistant/components/ozw/translations/de.json +++ b/homeassistant/components/ozw/translations/de.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", - "mqtt_required": "Die MQTT-Integration ist nicht eingerichtet" + "mqtt_required": "Die MQTT-Integration ist nicht eingerichtet", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "progress": { "install_addon": "Bitte warten, bis die Installation des OpenZWave-Add-Ons abgeschlossen ist. Dies kann einige Minuten dauern." @@ -14,6 +15,15 @@ }, "install_addon": { "title": "Die Installation des OpenZWave-Add-On wurde gestartet" + }, + "on_supervisor": { + "title": "Verbindungstyp ausw\u00e4hlen" + }, + "start_addon": { + "data": { + "network_key": "Netzwerk-Schl\u00fcssel", + "usb_path": "USB-Ger\u00e4te-Pfad" + } } } } diff --git a/homeassistant/components/ozw/translations/lb.json b/homeassistant/components/ozw/translations/lb.json index f97f026d38b92d..33de9a44953b76 100644 --- a/homeassistant/components/ozw/translations/lb.json +++ b/homeassistant/components/ozw/translations/lb.json @@ -1,8 +1,17 @@ { "config": { "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", "mqtt_required": "MQTT Integratioun ass net ageriicht", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + }, + "step": { + "start_addon": { + "data": { + "network_key": "Netzwierk Schl\u00ebssel" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/no.json b/homeassistant/components/ozw/translations/no.json index 89563ff3533798..652e28fe3fcb4f 100644 --- a/homeassistant/components/ozw/translations/no.json +++ b/homeassistant/components/ozw/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "addon_info_failed": "Kunne ikke hente OpenZWave-tilleggsinfo", - "addon_install_failed": "Kunne ikke installere OpenZWave-tillegget", + "addon_info_failed": "Kunne ikke hente informasjon om OpenZWave-tillegg", + "addon_install_failed": "Kunne ikke installere OpenZWave-tillegg", "addon_set_config_failed": "Kunne ikke angi OpenZWave-konfigurasjon", "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", @@ -10,23 +10,23 @@ "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "addon_start_failed": "Kunne ikke starte OpenZWave-tillegget. Sjekk konfigurasjonen." + "addon_start_failed": "Kunne ikke starte OpenZWave-tillegg. Sjekk konfigurasjonen." }, "progress": { - "install_addon": "Vent mens OpenZWave-tilleggsinstallasjonen er ferdig. Dette kan ta flere minutter." + "install_addon": "Vent mens installasjonen av OpenZWave-tillegg er ferdig. Dette kan ta flere minutter." }, "step": { "hassio_confirm": { - "title": "Sett opp OpenZWave-integrasjon med OpenZWave-tillegget" + "title": "Sett opp OpenZWave-integrasjon med OpenZWave-tillegg" }, "install_addon": { - "title": "Installasjonen av tilleggsprogrammet OpenZWave har startet" + "title": "Installasjonen av OpenZWave-tillegg har startet" }, "on_supervisor": { "data": { - "use_addon": "Bruk OpenZWave Supervisor-tillegget" + "use_addon": "Bruk OpenZWave Supervisor-tillegg" }, - "description": "\u00d8nsker du \u00e5 bruke OpenZWave Supervisor-tillegget?", + "description": "\u00d8nsker du \u00e5 bruke OpenZWave Supervisor-tillegg?", "title": "Velg tilkoblingsmetode" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "Nettverksn\u00f8kkel", "usb_path": "USB enhetsbane" }, - "title": "Angi OpenZWave-tilleggskonfigurasjonen" + "title": "Angi konfigurasjon for OpenZWave-tillegg" } } } diff --git a/homeassistant/components/ozw/translations/tr.json b/homeassistant/components/ozw/translations/tr.json index d0a70d57752f47..99eda8b8311ddc 100644 --- a/homeassistant/components/ozw/translations/tr.json +++ b/homeassistant/components/ozw/translations/tr.json @@ -2,7 +2,12 @@ "config": { "abort": { "addon_info_failed": "OpenZWave eklenti bilgileri al\u0131namad\u0131.", - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + "addon_install_failed": "OpenZWave eklentisi y\u00fcklenemedi.", + "addon_set_config_failed": "OpenZWave yap\u0131land\u0131rmas\u0131 ayarlanamad\u0131.", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "mqtt_required": "MQTT entegrasyonu kurulmam\u0131\u015f", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, "progress": { "install_addon": "OpenZWave eklenti kurulumu bitene kadar l\u00fctfen bekleyin. Bu birka\u00e7 dakika s\u00fcrebilir." @@ -10,6 +15,18 @@ "step": { "install_addon": { "title": "OpenZWave eklenti kurulumu ba\u015flad\u0131" + }, + "on_supervisor": { + "data": { + "use_addon": "OpenZWave Supervisor eklentisini kullan\u0131n" + }, + "description": "OpenZWave Supervisor eklentisini kullanmak istiyor musunuz?", + "title": "Ba\u011flant\u0131 y\u00f6ntemini se\u00e7in" + }, + "start_addon": { + "data": { + "network_key": "A\u011f Anahtar\u0131" + } } } } diff --git a/homeassistant/components/ozw/translations/uk.json b/homeassistant/components/ozw/translations/uk.json new file mode 100644 index 00000000000000..f8fb161aa1c4f6 --- /dev/null +++ b/homeassistant/components/ozw/translations/uk.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "addon_info_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f OpenZWave.", + "addon_install_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 OpenZWave.", + "addon_set_config_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e OpenZWave.", + "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." + }, + "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." + }, + "progress": { + "install_addon": "\u0417\u0430\u0447\u0435\u043a\u0430\u0439\u0442\u0435, \u043f\u043e\u043a\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f OpenZWave. \u0426\u0435 \u043c\u043e\u0436\u0435 \u0437\u0430\u0439\u043d\u044f\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 \u0445\u0432\u0438\u043b\u0438\u043d." + }, + "step": { + "hassio_confirm": { + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f OpenZWave" + }, + "install_addon": { + "title": "\u0420\u043e\u0437\u043f\u043e\u0447\u0430\u0442\u043e \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f Open'Wave" + }, + "on_supervisor": { + "data": { + "use_addon": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Supervisor OpenZWave" + }, + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Supervisor OpenZWave?", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "start_addon": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u043c\u0435\u0440\u0435\u0436\u0456", + "usb_path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 Open'Wave" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/de.json b/homeassistant/components/panasonic_viera/translations/de.json index 4b2c14be9d6185..71090830714681 100644 --- a/homeassistant/components/panasonic_viera/translations/de.json +++ b/homeassistant/components/panasonic_viera/translations/de.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "Dieser Panasonic Viera TV ist bereits konfiguriert.", - "cannot_connect": "Verbindungsfehler", - "unknown": "Ein unbekannter Fehler ist aufgetreten. Weitere Informationen finden Sie in den Logs." + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler", - "invalid_pin_code": "Der von Ihnen eingegebene PIN-Code war ung\u00fcltig" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_pin_code": "Der eingegebene PIN-Code war ung\u00fcltig" }, "step": { "pairing": { "data": { - "pin": "PIN" + "pin": "PIN-Code" }, - "description": "Geben Sie die auf Ihrem Fernseher angezeigte PIN ein", + "description": "Gib den auf deinem TV angezeigten PIN-Code ein", "title": "Kopplung" }, "user": { @@ -22,7 +22,7 @@ "host": "IP-Adresse", "name": "Name" }, - "description": "Geben Sie die IP-Adresse Ihres Panasonic Viera TV ein", + "description": "Gib die IP-Adresse deines Panasonic Viera TV ein", "title": "Richten Sie Ihr Fernsehger\u00e4t ein" } } diff --git a/homeassistant/components/panasonic_viera/translations/tr.json b/homeassistant/components/panasonic_viera/translations/tr.json new file mode 100644 index 00000000000000..d0e573fdcf95e2 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/uk.json b/homeassistant/components/panasonic_viera/translations/uk.json new file mode 100644 index 00000000000000..9722b19ece9cb8 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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.", + "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" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_pin_code": "\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0439 PIN-\u043a\u043e\u0434 \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439." + }, + "step": { + "pairing": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434 , \u0449\u043e \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Panasonic Viera", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/person/translations/uk.json b/homeassistant/components/person/translations/uk.json index 0dba7914da07c8..5e6b186e38ccf7 100644 --- a/homeassistant/components/person/translations/uk.json +++ b/homeassistant/components/person/translations/uk.json @@ -2,7 +2,7 @@ "state": { "_": { "home": "\u0412\u0434\u043e\u043c\u0430", - "not_home": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0439" + "not_home": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430" } }, "title": "\u041b\u044e\u0434\u0438\u043d\u0430" diff --git a/homeassistant/components/pi_hole/translations/ca.json b/homeassistant/components/pi_hole/translations/ca.json index 37d4e890ef4a5e..eb15fa7bf97b8d 100644 --- a/homeassistant/components/pi_hole/translations/ca.json +++ b/homeassistant/components/pi_hole/translations/ca.json @@ -7,6 +7,11 @@ "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { + "api_key": { + "data": { + "api_key": "Clau API" + } + }, "user": { "data": { "api_key": "Clau API", @@ -15,6 +20,7 @@ "name": "Nom", "port": "Port", "ssl": "Utilitza un certificat SSL", + "statistics_only": "Nom\u00e9s les estad\u00edstiques", "verify_ssl": "Verifica el certificat SSL" } } diff --git a/homeassistant/components/pi_hole/translations/cs.json b/homeassistant/components/pi_hole/translations/cs.json index a9057ceabab9e4..fa90fbdb2a005f 100644 --- a/homeassistant/components/pi_hole/translations/cs.json +++ b/homeassistant/components/pi_hole/translations/cs.json @@ -7,6 +7,11 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { + "api_key": { + "data": { + "api_key": "Kl\u00ed\u010d API" + } + }, "user": { "data": { "api_key": "Kl\u00ed\u010d API", diff --git a/homeassistant/components/pi_hole/translations/de.json b/homeassistant/components/pi_hole/translations/de.json index f74c5acb6352f0..34198fcfebeaff 100644 --- a/homeassistant/components/pi_hole/translations/de.json +++ b/homeassistant/components/pi_hole/translations/de.json @@ -4,17 +4,17 @@ "already_configured": "Service ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung konnte nicht hergestellt werden" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { "data": { - "api_key": "API-Schl\u00fcssel (optional)", + "api_key": "API-Schl\u00fcssel", "host": "Host", "location": "Org", "name": "Name", "port": "Port", - "ssl": "SSL verwenden", + "ssl": "Nutzt ein SSL-Zertifikat", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } } diff --git a/homeassistant/components/pi_hole/translations/en.json b/homeassistant/components/pi_hole/translations/en.json index 858e7c230ac085..9053a70c18fe48 100644 --- a/homeassistant/components/pi_hole/translations/en.json +++ b/homeassistant/components/pi_hole/translations/en.json @@ -7,6 +7,11 @@ "cannot_connect": "Failed to connect" }, "step": { + "api_key": { + "data": { + "api_key": "API Key" + } + }, "user": { "data": { "api_key": "API Key", @@ -15,6 +20,7 @@ "name": "Name", "port": "Port", "ssl": "Uses an SSL certificate", + "statistics_only": "Statistics Only", "verify_ssl": "Verify SSL certificate" } } diff --git a/homeassistant/components/pi_hole/translations/es.json b/homeassistant/components/pi_hole/translations/es.json index 48708d6810421e..35597af49f291e 100644 --- a/homeassistant/components/pi_hole/translations/es.json +++ b/homeassistant/components/pi_hole/translations/es.json @@ -7,6 +7,11 @@ "cannot_connect": "No se pudo conectar" }, "step": { + "api_key": { + "data": { + "api_key": "Clave API" + } + }, "user": { "data": { "api_key": "Clave API", @@ -15,6 +20,7 @@ "name": "Nombre", "port": "Puerto", "ssl": "Usar SSL", + "statistics_only": "S\u00f3lo las estad\u00edsticas", "verify_ssl": "Verificar certificado SSL" } } diff --git a/homeassistant/components/pi_hole/translations/et.json b/homeassistant/components/pi_hole/translations/et.json index c68d52c0c107a3..4ff0fdd0ba850e 100644 --- a/homeassistant/components/pi_hole/translations/et.json +++ b/homeassistant/components/pi_hole/translations/et.json @@ -7,6 +7,11 @@ "cannot_connect": "\u00dchendamine nurjus" }, "step": { + "api_key": { + "data": { + "api_key": "API v\u00f5ti" + } + }, "user": { "data": { "api_key": "API v\u00f5ti", @@ -15,6 +20,7 @@ "name": "Nimi", "port": "", "ssl": "Kasuatb SSL serti", + "statistics_only": "Ainult statistika", "verify_ssl": "Kontrolli SSL sertifikaati" } } diff --git a/homeassistant/components/pi_hole/translations/it.json b/homeassistant/components/pi_hole/translations/it.json index 34590ee77bb14d..7d355caf985622 100644 --- a/homeassistant/components/pi_hole/translations/it.json +++ b/homeassistant/components/pi_hole/translations/it.json @@ -7,6 +7,11 @@ "cannot_connect": "Impossibile connettersi" }, "step": { + "api_key": { + "data": { + "api_key": "Chiave API" + } + }, "user": { "data": { "api_key": "Chiave API", @@ -15,6 +20,7 @@ "name": "Nome", "port": "Porta", "ssl": "Utilizza un certificato SSL", + "statistics_only": "Solo Statistiche", "verify_ssl": "Verificare il certificato SSL" } } diff --git a/homeassistant/components/pi_hole/translations/no.json b/homeassistant/components/pi_hole/translations/no.json index 71c815ecd36dd4..7d005fa65163bf 100644 --- a/homeassistant/components/pi_hole/translations/no.json +++ b/homeassistant/components/pi_hole/translations/no.json @@ -7,6 +7,11 @@ "cannot_connect": "Tilkobling mislyktes" }, "step": { + "api_key": { + "data": { + "api_key": "API-n\u00f8kkel" + } + }, "user": { "data": { "api_key": "API-n\u00f8kkel", @@ -15,6 +20,7 @@ "name": "Navn", "port": "Port", "ssl": "Bruker et SSL-sertifikat", + "statistics_only": "Bare statistikk", "verify_ssl": "Verifisere SSL-sertifikat" } } diff --git a/homeassistant/components/pi_hole/translations/pl.json b/homeassistant/components/pi_hole/translations/pl.json index add788ef916027..ee4b6eadd87a27 100644 --- a/homeassistant/components/pi_hole/translations/pl.json +++ b/homeassistant/components/pi_hole/translations/pl.json @@ -7,6 +7,11 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { + "api_key": { + "data": { + "api_key": "Klucz API" + } + }, "user": { "data": { "api_key": "Klucz API", @@ -15,6 +20,7 @@ "name": "Nazwa", "port": "Port", "ssl": "Certyfikat SSL", + "statistics_only": "Tylko statystyki", "verify_ssl": "Weryfikacja certyfikatu SSL" } } diff --git a/homeassistant/components/pi_hole/translations/ru.json b/homeassistant/components/pi_hole/translations/ru.json index eb3cfa62c623ec..eed9596c90746f 100644 --- a/homeassistant/components/pi_hole/translations/ru.json +++ b/homeassistant/components/pi_hole/translations/ru.json @@ -7,6 +7,11 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { + "api_key": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + } + }, "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", @@ -15,6 +20,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", + "statistics_only": "\u0422\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" } } diff --git a/homeassistant/components/pi_hole/translations/tr.json b/homeassistant/components/pi_hole/translations/tr.json new file mode 100644 index 00000000000000..a14e020d360277 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/tr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "api_key": { + "data": { + "api_key": "API Anahtar\u0131" + } + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "host": "Ana Bilgisayar", + "location": "Konum", + "port": "Port", + "statistics_only": "Yaln\u0131zca \u0130statistikler" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/uk.json b/homeassistant/components/pi_hole/translations/uk.json new file mode 100644 index 00000000000000..93413f9abff057 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "host": "\u0425\u043e\u0441\u0442", + "location": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/zh-Hant.json b/homeassistant/components/pi_hole/translations/zh-Hant.json index 1cea5a87f4b5f1..1527b48f580457 100644 --- a/homeassistant/components/pi_hole/translations/zh-Hant.json +++ b/homeassistant/components/pi_hole/translations/zh-Hant.json @@ -7,6 +7,11 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { + "api_key": { + "data": { + "api_key": "API \u5bc6\u9470" + } + }, "user": { "data": { "api_key": "API \u5bc6\u9470", @@ -15,6 +20,7 @@ "name": "\u540d\u7a31", "port": "\u901a\u8a0a\u57e0", "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", + "statistics_only": "\u50c5\u7d71\u8a08\u8cc7\u8a0a", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" } } diff --git a/homeassistant/components/plaato/translations/ca.json b/homeassistant/components/plaato/translations/ca.json index 1dbe125d50d376..c4669b219ab760 100644 --- a/homeassistant/components/plaato/translations/ca.json +++ b/homeassistant/components/plaato/translations/ca.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "El compte ja ha estat configurat", "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." }, "create_entry": { - "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Plaato Airlock.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." + "default": "El dispositiu Plaato {device_type} amb nom **{device_name}** s'ha configurat correctament!" + }, + "error": { + "invalid_webhook_device": "Has seleccionat un dispositiu que no admet l'enviament de dades a un webhook. Nom\u00e9s est\u00e0 disponible per a Airlock", + "no_api_method": "Has d'afegir un token d'autenticaci\u00f3 o seleccionar webhook", + "no_auth_token": "Has d'afegir un token d'autenticaci\u00f3" }, "step": { + "api_method": { + "data": { + "token": "Enganxa el token d'autenticaci\u00f3 aqu\u00ed", + "use_webhook": "Utilitza webhook" + }, + "description": "Per poder consultar l'API, cal un `auth_token` que es pot obtenir seguint aquestes [instruccions](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\n Dispositiu seleccionat: **{device_type}** \n\n Si prefereixes utilitzar el m\u00e8tode webhook integrat (nom\u00e9s per Airlock), marca la casella seg\u00fcent i deixa el token d'autenticaci\u00f3 en blanc", + "title": "Selecciona el m\u00e8tode API" + }, "user": { + "data": { + "device_name": "Posa un nom al dispositiu", + "device_type": "Tipus de dispositiu Plaato" + }, "description": "Vols comen\u00e7ar la configuraci\u00f3?", - "title": "Configuraci\u00f3 del Webhook de Plaato" + "title": "Configura dispositius Plaato" + }, + "webhook": { + "description": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar la opci\u00f3 webhook de Plaato Airlock.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls.", + "title": "Webhook a utilitzar" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Interval d'actualitzaci\u00f3 (minuts)" + }, + "description": "Estableix l'interval d'actualitzaci\u00f3 (minuts)", + "title": "Opcions de Plaato" + }, + "webhook": { + "description": "Informaci\u00f3 del webhook: \n\n - URL: `{webhook_url}`\n - M\u00e8tode: POST\n\n", + "title": "Opcions de Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index f97fe4875f7835..5171baab6544f0 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, "step": { "user": { - "description": "Soll Plaato Airlock wirklich eingerichtet werden?", + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Plaato Webhook einrichten" } } diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json index 75c7a2182ef739..ec7b7e4b1a409c 100644 --- a/homeassistant/components/plaato/translations/et.json +++ b/homeassistant/components/plaato/translations/et.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "Kasutaja on juba seadistatud", "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." }, "create_entry": { - "default": "S\u00fcndmuste saatmiseks Home Assistantile pead seadistama Plaatoo Airlock'i veebihaagi. \n\n Sisesta j\u00e4rgmine teave: \n\n - URL: \" {webhook_url} \" \n - Meetod: POST \n \n Lisateavet leiad [documentation] ( {docs_url} )." + "default": "{device_type} Plaato seade nimega **{device_name}** on edukalt seadistatud!" + }, + "error": { + "invalid_webhook_device": "Oled valinud seadme, mis ei toeta andmete saatmist veebihaagile. See on saadaval ainult Airlocki jaoks", + "no_api_method": "Pead lisama autentimisloa v\u00f5i valima veebihaagi", + "no_auth_token": "Pead lisama autentimisloa" }, "step": { + "api_method": { + "data": { + "token": "Aseta Auth Token siia", + "use_webhook": "Kasuta veebihaaki" + }, + "description": "API p\u00e4ringu esitamiseks on vajalik \"auth_token\", mille saad j\u00e4rgides [neid] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) juhiseid \n\n Valitud seade: ** {device_type} ** \n\n Kui kasutad pigem sisseehitatud veebihaagi meetodit (ainult Airlock), m\u00e4rgi palun allolev ruut ja j\u00e4ta Auth Token t\u00fchjaks", + "title": "Vali API meetod" + }, "user": { + "data": { + "device_name": "Pang oma seadmele nimi", + "device_type": "Plaato seadme t\u00fc\u00fcp" + }, "description": "Kas alustan seadistamist?", - "title": "Plaato Webhooki seadistamine" + "title": "Plaato seadmete h\u00e4\u00e4lestamine" + }, + "webhook": { + "description": "S\u00fcndmuste saatmiseks Home Assistanti pead seadistama Plaato Airlocki veebihaagi. \n\n Sisesta j\u00e4rgmine teave: \n\n - URL: \" {webhook_url} \"\n - Meetod: POST \n\n Lisateavet leiad [dokumentatsioonist] ( {docs_url} ).", + "title": "Kasutatav veebihaak" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "V\u00e4rskendamise intervall (minutites)" + }, + "description": "M\u00e4\u00e4ra v\u00e4rskendamise intervall (minutites)", + "title": "Plaato valikud" + }, + "webhook": { + "description": "Veebihaagi teave: \n\n - URL: `{webhook_url}`\n - Meetod: POST\n\n", + "title": "Plaato Airlocki valikud" } } } diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 1e2da1bfb12661..8873399aaa4282 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -1,16 +1,48 @@ { "config": { "abort": { + "already_configured": "Kontoen er allerede konfigurert", "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" }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." }, + "error": { + "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er kun tilgjengelig for Airlock", + "no_api_method": "Du m\u00e5 legge til et godkjenningstoken eller velge webhook", + "no_auth_token": "Du m\u00e5 legge til et godkjenningstoken" + }, "step": { + "api_method": { + "title": "Velg API-metode" + }, "user": { + "data": { + "device_name": "Navngi enheten din", + "device_type": "Type Platon-enhet" + }, "description": "Vil du starte oppsettet?", "title": "Sett opp Plaato Webhook" + }, + "webhook": { + "description": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer.", + "title": "Webhook \u00e5 bruke" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Oppdateringsintervall (minutter)" + }, + "description": "Still inn oppdateringsintervallet (minutter)", + "title": "Alternativer for Plaato" + }, + "webhook": { + "description": "Webhook info:\n\n- URL-adresse: {webhook_url}\n- Metode: POST\n\n", + "title": "Alternativer for Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index 1f7c8141aa5686..c849f574c9c5cb 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", "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" }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Plaato Airlock. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Tw\u00f3j {device_type} Plaato o nazwie *{device_name}* zosta\u0142o pomy\u015blnie skonfigurowane!" + }, + "error": { + "invalid_webhook_device": "Wybra\u0142e\u015b urz\u0105dzenie, kt\u00f3re nie obs\u0142uguje wysy\u0142ania danych do webhooka. Opcja dost\u0119pna tylko w areometrze Airlock", + "no_api_method": "Musisz doda\u0107 token uwierzytelniania lub wybra\u0107 webhook", + "no_auth_token": "Musisz doda\u0107 token autoryzacji" }, "step": { + "api_method": { + "data": { + "token": "Wklej token autoryzacji", + "use_webhook": "U\u017cyj webhook" + }, + "description": "Aby m\u00f3c przesy\u0142a\u0107 zapytania do API, wymagany jest \u201etoken autoryzacji\u201d, kt\u00f3ry mo\u017cna uzyska\u0107, post\u0119puj\u0105c zgodnie z [t\u0105] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrukcj\u0105\n\nWybrane urz\u0105dzenie: *{device_type}* \n\nJe\u015bli wolisz u\u017cywa\u0107 wbudowanej metody webhook (tylko areomierz Airlock), zaznacz poni\u017csze pole i pozostaw token autoryzacji pusty", + "title": "Wybierz metod\u0119 API" + }, "user": { + "data": { + "device_name": "Nazwij swoje urz\u0105dzenie", + "device_type": "Rodzaj urz\u0105dzenia Plaato" + }, "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?", - "title": "Konfiguracja Plaato Webhook" + "title": "Konfiguracja urz\u0105dze\u0144 Plaato" + }, + "webhook": { + "description": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Plaato Airlock. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y.", + "title": "Webhook do u\u017cycia" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (w minutach)" + }, + "description": "Ustaw cz\u0119stotliwo\u015bci aktualizacji (w minutach)", + "title": "Opcje dla Plaato" + }, + "webhook": { + "description": "Informacje o webhook: \n\n - URL: `{webhook_url}`\n - Metoda: POST \n\n", + "title": "Opcje dla areomierza Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/tr.json b/homeassistant/components/plaato/translations/tr.json new file mode 100644 index 00000000000000..1f21b08ec81157 --- /dev/null +++ b/homeassistant/components/plaato/translations/tr.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "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." + }, + "error": { + "no_auth_token": "Bir kimlik do\u011frulama jetonu eklemeniz gerekiyor" + }, + "step": { + "api_method": { + "data": { + "use_webhook": "Webhook kullan" + }, + "title": "API y\u00f6ntemini se\u00e7in" + }, + "user": { + "data": { + "device_name": "Cihaz\u0131n\u0131z\u0131 adland\u0131r\u0131n", + "device_type": "Plaato cihaz\u0131n\u0131n t\u00fcr\u00fc" + } + }, + "webhook": { + "title": "Webhook kullanmak i\u00e7in" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "G\u00fcncelle\u015ftirme aral\u0131\u011f\u0131 (dakika)" + }, + "description": "G\u00fcncelleme aral\u0131\u011f\u0131n\u0131 ayarlay\u0131n (dakika)", + "title": "Plaato i\u00e7in se\u00e7enekler" + }, + "webhook": { + "title": "Plaato Airlock i\u00e7in se\u00e7enekler" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/uk.json b/homeassistant/components/plaato/translations/uk.json new file mode 100644 index 00000000000000..a4f7de7c6be4ea --- /dev/null +++ b/homeassistant/components/plaato/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0432\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Plaato Airlock. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "Plaato Airlock" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index aec745ea38bc4c..2890c5c31c694a 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "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" }, "create_entry": { - "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Plaato Airlock \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + "default": "\u540d\u7a31\u70ba **{device_name}** \u7684 Plaato {device_type} \u5df2\u6210\u529f\u8a2d\u5b9a\uff01" + }, + "error": { + "invalid_webhook_device": "\u6240\u9078\u64c7\u7684\u88dd\u7f6e\u4e0d\u652f\u63f4\u50b3\u9001\u8cc7\u6599\u81f3 Webhook\u3001AirLock \u50c5\u652f\u63f4\u6b64\u985e\u578b", + "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470\u6216\u9078\u64c7 Webhook", + "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470" }, "step": { + "api_method": { + "data": { + "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u5bc6\u9470", + "use_webhook": "\u4f7f\u7528 Webhook" + }, + "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u5bc6\u9470\u6b04\u4f4d\u7a7a\u767d", + "title": "\u9078\u64c7 API \u65b9\u5f0f" + }, "user": { + "data": { + "device_name": "\u88dd\u7f6e\u540d\u7a31", + "device_type": "Plaato \u88dd\u7f6e\u985e\u578b" + }, "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f", - "title": "\u8a2d\u5b9a Plaato Webhook" + "title": "\u8a2d\u5b9a Plaato \u88dd\u7f6e" + }, + "webhook": { + "description": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Plaato Airlock \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002", + "title": "\u4f7f\u7528\u4e4b Webhook" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "\u66f4\u65b0\u983b\u7387\uff08\u5206\uff09" + }, + "description": "\u8a2d\u5b9a\u66f4\u65b0\u983b\u7387\uff08\u5206\uff09", + "title": "Plaato \u9078\u9805" + }, + "webhook": { + "description": "Webhook \u8a0a\u606f\uff1a\n\n- URL\uff1a`{webhook_url}`\n- \u65b9\u5f0f\uff1aPOST\n\n", + "title": "Plaato Airlock \u9078\u9805" } } } diff --git a/homeassistant/components/plant/translations/uk.json b/homeassistant/components/plant/translations/uk.json index 3204c42a714b9d..25f24b43b80ff3 100644 --- a/homeassistant/components/plant/translations/uk.json +++ b/homeassistant/components/plant/translations/uk.json @@ -1,8 +1,8 @@ { "state": { "_": { - "ok": "\u0422\u0410\u041a", - "problem": "\u0425\u0430\u043b\u0435\u043f\u0430" + "ok": "\u041e\u041a", + "problem": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430" } }, "title": "\u0420\u043e\u0441\u043b\u0438\u043d\u0430" diff --git a/homeassistant/components/plex/translations/de.json b/homeassistant/components/plex/translations/de.json index 961ad4b3ed6e3e..2ba14e65f85f01 100644 --- a/homeassistant/components/plex/translations/de.json +++ b/homeassistant/components/plex/translations/de.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "Alle verkn\u00fcpften Server sind bereits konfiguriert", "already_configured": "Dieser Plex-Server ist bereits konfiguriert", - "already_in_progress": "Plex wird konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "token_request_timeout": "Zeit\u00fcberschreitung beim Erhalt des Tokens", - "unknown": "Aus unbekanntem Grund fehlgeschlagen" + "unknown": "Unerwarteter Fehler" }, "error": { "faulty_credentials": "Autorisierung fehlgeschlagen, Token \u00fcberpr\u00fcfen", diff --git a/homeassistant/components/plex/translations/tr.json b/homeassistant/components/plex/translations/tr.json new file mode 100644 index 00000000000000..93f8cc85eaeee9 --- /dev/null +++ b/homeassistant/components/plex/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Bu Plex sunucusu zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "unknown": "Beklenmeyen hata" + }, + "step": { + "manual_setup": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "user": { + "title": "Plex Medya Sunucusu" + }, + "user_advanced": { + "data": { + "setup_method": "Kurulum y\u00f6ntemi" + }, + "title": "Plex Medya Sunucusu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/uk.json b/homeassistant/components/plex/translations/uk.json new file mode 100644 index 00000000000000..20351cf735abfc --- /dev/null +++ b/homeassistant/components/plex/translations/uk.json @@ -0,0 +1,62 @@ +{ + "config": { + "abort": { + "all_configured": "\u0412\u0441\u0456 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0456 \u0441\u0435\u0440\u0432\u0435\u0440\u0438 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0456.", + "already_configured": "\u0426\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439.", + "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.", + "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", + "token_request_timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0442\u043e\u043a\u0435\u043d\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "faulty_credentials": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0422\u043e\u043a\u0435\u043d.", + "host_or_token": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u043a\u0430\u0437\u0430\u0442\u0438 \u0425\u043e\u0441\u0442 \u0430\u0431\u043e \u0422\u043e\u043a\u0435\u043d.", + "no_servers": "\u041d\u0435\u043c\u0430\u0454 \u0441\u0435\u0440\u0432\u0435\u0440\u0456\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c.", + "not_found": "\u0421\u0435\u0440\u0432\u0435\u0440 Plex \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "ssl_error": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u043c." + }, + "flow_title": "{name} ({host})", + "step": { + "manual_setup": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "token": "\u0422\u043e\u043a\u0435\u043d (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "\u0420\u0443\u0447\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Plex" + }, + "select_server": { + "data": { + "server": "\u0421\u0435\u0440\u0432\u0435\u0440" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u0438\u043d \u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u0456\u0432:", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Plex" + }, + "user": { + "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 [plex.tv](https://plex.tv), \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0434\u043e Home Assistant.", + "title": "Plex Media Server" + }, + "user_advanced": { + "data": { + "setup_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + }, + "title": "Plex Media Server" + } + } + }, + "options": { + "step": { + "plex_mp_settings": { + "data": { + "ignore_new_shared_users": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438 \u043d\u043e\u0432\u0438\u0445 \u043a\u0435\u0440\u043e\u0432\u0430\u043d\u0438\u0445 / \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u0438\u0445 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456\u0432", + "ignore_plex_web_clients": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438 \u0432\u0435\u0431-\u043a\u043b\u0456\u0454\u043d\u0442\u0438 Plex", + "monitored_users": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u043d\u0456 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456", + "use_episode_art": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043e\u0431\u043a\u043b\u0430\u0434\u0438\u043d\u043a\u0438 \u0435\u043f\u0456\u0437\u043e\u0434\u0456\u0432" + }, + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/de.json b/homeassistant/components/plugwise/translations/de.json index 2282e3584fc99c..685cd6fb9ae9f4 100644 --- a/homeassistant/components/plugwise/translations/de.json +++ b/homeassistant/components/plugwise/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Service ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Smile: {name}", @@ -17,6 +18,8 @@ }, "user_gateway": { "data": { + "host": "IP-Adresse", + "password": "Smile ID", "port": "Port" }, "description": "Bitte eingeben" diff --git a/homeassistant/components/plugwise/translations/lb.json b/homeassistant/components/plugwise/translations/lb.json index 4ce9f8b0145c97..a3618bc911eb92 100644 --- a/homeassistant/components/plugwise/translations/lb.json +++ b/homeassistant/components/plugwise/translations/lb.json @@ -21,7 +21,8 @@ "data": { "host": "IP Adress", "password": "Smile ID", - "port": "Port" + "port": "Port", + "username": "Smile Benotzernumm" }, "title": "Mam Smile verbannen" } diff --git a/homeassistant/components/plugwise/translations/tr.json b/homeassistant/components/plugwise/translations/tr.json index d25f1975cf7745..60d6b1f92be30e 100644 --- a/homeassistant/components/plugwise/translations/tr.json +++ b/homeassistant/components/plugwise/translations/tr.json @@ -1,10 +1,23 @@ { "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "Smile: {name}", "step": { "user_gateway": { "data": { + "host": "\u0130p Adresi", + "password": "G\u00fcl\u00fcmseme Kimli\u011fi", + "port": "Port", "username": "Smile Kullan\u0131c\u0131 Ad\u0131" - } + }, + "description": "L\u00fctfen girin" } } } diff --git a/homeassistant/components/plugwise/translations/uk.json b/homeassistant/components/plugwise/translations/uk.json new file mode 100644 index 00000000000000..6c6f54612b1701 --- /dev/null +++ b/homeassistant/components/plugwise/translations/uk.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Smile: {name}", + "step": { + "user": { + "data": { + "flow_type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "description": "\u041f\u0440\u043e\u0434\u0443\u043a\u0442:", + "title": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Plugwise" + }, + "user_gateway": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "Smile ID", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041b\u043e\u0433\u0456\u043d Smile" + }, + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c:", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Smile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Plugwise" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/de.json b/homeassistant/components/plum_lightpad/translations/de.json index accee16a6f5ce8..c94bf9aadabc10 100644 --- a/homeassistant/components/plum_lightpad/translations/de.json +++ b/homeassistant/components/plum_lightpad/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/plum_lightpad/translations/tr.json b/homeassistant/components/plum_lightpad/translations/tr.json new file mode 100644 index 00000000000000..f0dab20775fbc4 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/uk.json b/homeassistant/components/plum_lightpad/translations/uk.json new file mode 100644 index 00000000000000..96b14f793751d5 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/de.json b/homeassistant/components/point/translations/de.json index 8ee83eab727631..343c02055d5deb 100644 --- a/homeassistant/components/point/translations/de.json +++ b/homeassistant/components/point/translations/de.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_setup": "Du kannst nur ein Point-Konto konfigurieren.", + "already_setup": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "authorize_url_fail": "Unbekannter Fehler beim Erstellen der Authorisierungs-URL.", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", "external_setup": "Pointt erfolgreich von einem anderen Flow konfiguriert.", - "no_flows": "Du m\u00fcsst Point konfigurieren, bevor du dich damit authentifizieren kannst. [Bitte lese die Anweisungen] (https://www.home-assistant.io/components/point/).", + "no_flows": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" }, "create_entry": { @@ -17,15 +17,15 @@ }, "step": { "auth": { - "description": "Folge dem Link unten und Akzeptiere Zugriff auf dei Minut-Konto. Kehre dann zur\u00fcck und dr\u00fccke unten auf Senden . \n\n [Link]({authorization_url})", + "description": "Folge dem Link unten und **Best\u00e4tige** den Zugriff auf dein Minut-Konto. Kehre dann zur\u00fcck und dr\u00fccke unten auf **Senden**. \n\n [Link]({authorization_url})", "title": "Point authentifizieren" }, "user": { "data": { "flow_impl": "Anbieter" }, - "description": "W\u00e4hle \u00fcber welchen Authentifizierungsanbieter du sich mit Point authentifizieren m\u00f6chtest.", - "title": "Authentifizierungsanbieter" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/point/translations/fr.json b/homeassistant/components/point/translations/fr.json index 141af3545ba491..ab9cd7af34e8b3 100644 --- a/homeassistant/components/point/translations/fr.json +++ b/homeassistant/components/point/translations/fr.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "external_setup": "Point correctement configur\u00e9 \u00e0 partir d\u2019un autre flux.", - "no_flows": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." + "no_flows": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "create_entry": { "default": "Authentification r\u00e9ussie" diff --git a/homeassistant/components/point/translations/tr.json b/homeassistant/components/point/translations/tr.json new file mode 100644 index 00000000000000..5a4849fad0726e --- /dev/null +++ b/homeassistant/components/point/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_setup": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "error": { + "no_token": "Eri\u015fim Belirteci" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/uk.json b/homeassistant/components/point/translations/uk.json new file mode 100644 index 00000000000000..6b66a39a291767 --- /dev/null +++ b/homeassistant/components/point/translations/uk.json @@ -0,0 +1,32 @@ +{ + "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.", + "authorize_url_fail": "\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.", + "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.", + "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": { + "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." + }, + "error": { + "follow_link": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u0456 \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\".", + "no_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443." + }, + "step": { + "auth": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 [\u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c]({authorization_url}) \u0456 ** \u0414\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 ** \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0432\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Minut, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0442\u0430 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **.", + "title": "Minut Point" + }, + "user": { + "data": { + "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/de.json b/homeassistant/components/poolsense/translations/de.json index c7dfe6d02b211f..5869da61c9c40f 100644 --- a/homeassistant/components/poolsense/translations/de.json +++ b/homeassistant/components/poolsense/translations/de.json @@ -3,13 +3,16 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "user": { "data": { "email": "E-Mail", "password": "Passwort" }, - "description": "Wollen Sie mit der Einrichtung beginnen?" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/poolsense/translations/tr.json b/homeassistant/components/poolsense/translations/tr.json new file mode 100644 index 00000000000000..1e2e9d0c5b8de4 --- /dev/null +++ b/homeassistant/components/poolsense/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/uk.json b/homeassistant/components/poolsense/translations/uk.json new file mode 100644 index 00000000000000..6ac3b97f74167d --- /dev/null +++ b/homeassistant/components/poolsense/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "PoolSense" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index 2c9becb17959e6..4b176fff686d07 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/cs.json b/homeassistant/components/powerwall/translations/cs.json index b64eabcf33bae6..698934ad10e615 100644 --- a/homeassistant/components/powerwall/translations/cs.json +++ b/homeassistant/components/powerwall/translations/cs.json @@ -8,6 +8,7 @@ "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})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index f5317e3046ae74..c30286d874450a 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Die Powerwall ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index ac0d9568154166..6eb0b77708da4c 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -1,21 +1,21 @@ { - "config": { - "flow_title": "Tesla Powerwall ({ip_address})", - "step": { - "user": { - "title": "Connect to the powerwall", - "data": { - "ip_address": "[%key:common::config_flow::data::ip%]" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "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": "Tesla Powerwall ({ip_address})", + "step": { + "user": { + "data": { + "ip_address": "IP Address" + }, + "title": "Connect to the powerwall" + } } - } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 76835e814809f8..373bf29f8bace7 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -8,6 +8,7 @@ "unknown": "Error inesperado", "wrong_version": "Tu powerwall utiliza una versi\u00f3n de software que no es compatible. Considera actualizar o informar de este problema para que pueda resolverse." }, + "flow_title": "Powerwall de Tesla ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index 996b0ea4b309b7..b10dca9b08b6f4 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -8,6 +8,7 @@ "unknown": "Ootamatu t\u00f5rge", "wrong_version": "Teie Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, + "flow_title": "Tesla Powerwall ( {ip_address} )", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index 422a28b6936958..376168f86167ee 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -8,6 +8,7 @@ "unknown": "Errore imprevisto", "wrong_version": "Il tuo powerwall utilizza una versione del software non supportata. Si prega di considerare l'aggiornamento o la segnalazione di questo problema in modo che possa essere risolto." }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index 7ddab100358730..cdc04a006ad5c9 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/translations/no.json @@ -8,6 +8,7 @@ "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": "", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index 8532066608ceb4..dfd4fa21a375ab 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -8,6 +8,7 @@ "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": "Tesla UPS ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index a8713bcd04ae31..faabf2d0ede0af 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/translations/ru.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/tr.json b/homeassistant/components/powerwall/translations/tr.json new file mode 100644 index 00000000000000..dd09a83a78c5ba --- /dev/null +++ b/homeassistant/components/powerwall/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" + }, + "flow_title": "Tesla Powerwall ( {ip_address} )", + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/uk.json b/homeassistant/components/powerwall/translations/uk.json new file mode 100644 index 00000000000000..9b397138c5272c --- /dev/null +++ b/homeassistant/components/powerwall/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "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", + "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": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "title": "Tesla Powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/zh-Hant.json b/homeassistant/components/powerwall/translations/zh-Hant.json index 45edbf2d88ed37..ec0d2e278b693c 100644 --- a/homeassistant/components/powerwall/translations/zh-Hant.json +++ b/homeassistant/components/powerwall/translations/zh-Hant.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/profiler/translations/de.json b/homeassistant/components/profiler/translations/de.json new file mode 100644 index 00000000000000..7137cd2ee4e106 --- /dev/null +++ b/homeassistant/components/profiler/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 mit der Einrichtung beginnen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/tr.json b/homeassistant/components/profiler/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/profiler/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/uk.json b/homeassistant/components/profiler/translations/uk.json new file mode 100644 index 00000000000000..5594895456e987 --- /dev/null +++ b/homeassistant/components/profiler/translations/uk.json @@ -0,0 +1,12 @@ +{ + "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." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/de.json b/homeassistant/components/progettihwsw/translations/de.json index 2e5bed4b668af3..0f773e03c1ded4 100644 --- a/homeassistant/components/progettihwsw/translations/de.json +++ b/homeassistant/components/progettihwsw/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/progettihwsw/translations/tr.json b/homeassistant/components/progettihwsw/translations/tr.json new file mode 100644 index 00000000000000..1d3d77584dd907 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "R\u00f6le 1", + "relay_10": "R\u00f6le 10", + "relay_11": "R\u00f6le 11", + "relay_12": "R\u00f6le 12", + "relay_13": "R\u00f6le 13", + "relay_14": "R\u00f6le 14", + "relay_15": "R\u00f6le 15", + "relay_16": "R\u00f6le 16", + "relay_2": "R\u00f6le 2", + "relay_3": "R\u00f6le 3", + "relay_4": "R\u00f6le 4", + "relay_5": "R\u00f6le 5", + "relay_6": "R\u00f6le 6", + "relay_7": "R\u00f6le 7", + "relay_8": "R\u00f6le 8", + "relay_9": "R\u00f6le 9" + }, + "title": "R\u00f6leleri kur" + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Panoyu kur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/uk.json b/homeassistant/components/progettihwsw/translations/uk.json new file mode 100644 index 00000000000000..7918db8e1584f2 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/uk.json @@ -0,0 +1,41 @@ +{ + "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." + }, + "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": { + "relay_modes": { + "data": { + "relay_1": "\u0420\u0435\u043b\u0435 1", + "relay_10": "\u0420\u0435\u043b\u0435 10", + "relay_11": "\u0420\u0435\u043b\u0435 11", + "relay_12": "\u0420\u0435\u043b\u0435 12", + "relay_13": "\u0420\u0435\u043b\u0435 13", + "relay_14": "\u0420\u0435\u043b\u0435 14", + "relay_15": "\u0420\u0435\u043b\u0435 15", + "relay_16": "\u0420\u0435\u043b\u0435 16", + "relay_2": "\u0420\u0435\u043b\u0435 2", + "relay_3": "\u0420\u0435\u043b\u0435 3", + "relay_4": "\u0420\u0435\u043b\u0435 4", + "relay_5": "\u0420\u0435\u043b\u0435 5", + "relay_6": "\u0420\u0435\u043b\u0435 6", + "relay_7": "\u0420\u0435\u043b\u0435 7", + "relay_8": "\u0420\u0435\u043b\u0435 8", + "relay_9": "\u0420\u0435\u043b\u0435 9" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u043b\u0435" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043b\u0430\u0442\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/de.json b/homeassistant/components/ps4/translations/de.json index 5dd638a717cb1c..d5aa867f1db369 100644 --- a/homeassistant/components/ps4/translations/de.json +++ b/homeassistant/components/ps4/translations/de.json @@ -1,15 +1,16 @@ { "config": { "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "credential_error": "Fehler beim Abrufen der Anmeldeinformationen.", "no_devices_found": "Es wurden keine PlayStation 4 im Netzwerk gefunden.", "port_987_bind_error": "Konnte sich nicht an Port 987 binden. Weitere Informationen findest du in der [Dokumentation] (https://www.home-assistant.io/components/ps4/).", "port_997_bind_error": "Bind to Port 997 nicht m\u00f6glich. Weitere Informationen findest du in der [Dokumentation](https://www.home-assistant.io/components/ps4/)" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "credential_timeout": "Zeit\u00fcberschreitung beim Warten auf den Anmeldedienst. Klicken zum Neustarten auf Senden.", - "login_failed": "Fehler beim Koppeln mit PlayStation 4. \u00dcberpr\u00fcfe, ob die PIN korrekt ist.", + "login_failed": "Fehler beim Koppeln mit der PlayStation 4. \u00dcberpr\u00fcfe, ob der PIN-Code korrekt ist.", "no_ipaddress": "Gib die IP-Adresse der PlayStation 4 ein, die konfiguriert werden soll." }, "step": { @@ -19,7 +20,7 @@ }, "link": { "data": { - "code": "PIN", + "code": "PIN-Code", "ip_address": "IP-Adresse", "name": "Name", "region": "Region" diff --git a/homeassistant/components/ps4/translations/tr.json b/homeassistant/components/ps4/translations/tr.json new file mode 100644 index 00000000000000..4e3e0b53445e79 --- /dev/null +++ b/homeassistant/components/ps4/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "link": { + "data": { + "ip_address": "\u0130p Adresi" + } + }, + "mode": { + "data": { + "ip_address": "\u0130p Adresi (Otomatik Bulma kullan\u0131l\u0131yorsa bo\u015f b\u0131rak\u0131n)." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/uk.json b/homeassistant/components/ps4/translations/uk.json new file mode 100644 index 00000000000000..696a46bf8d7f68 --- /dev/null +++ b/homeassistant/components/ps4/translations/uk.json @@ -0,0 +1,41 @@ +{ + "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.", + "credential_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0445 \u0434\u0430\u043d\u0438\u0445.", + "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.", + "port_987_bind_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u043e\u0440\u0442\u043e\u043c 987. \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](https://www.home-assistant.io/components/ps4/).", + "port_997_bind_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u043e\u0440\u0442\u043e\u043c 997. \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](https://www.home-assistant.io/components/ps4/)." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "credential_timeout": "\u0427\u0430\u0441 \u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0438\u043d\u0443\u0432. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0449\u043e\u0431 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0438 \u0441\u043f\u0440\u043e\u0431\u0443.", + "login_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443 \u0437 PlayStation 4. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e PIN-\u043a\u043e\u0434 \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e.", + "no_ipaddress": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0443 PlayStation 4." + }, + "step": { + "creds": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0430 \u043f\u043e\u0442\u0456\u043c \u0432 \u0434\u043e\u0434\u0430\u0442\u043a\u0443 'PS4 Second Screen' \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 'Home-Assistant'.", + "title": "PlayStation 4" + }, + "link": { + "data": { + "code": "PIN-\u043a\u043e\u0434", + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430", + "region": "\u0420\u0435\u0433\u0456\u043e\u043d" + }, + "description": "\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f PIN-\u043a\u043e\u0434\u0443 \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043f\u0443\u043d\u043a\u0442\u0443 ** \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f ** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0456 PlayStation 4. \u041f\u043e\u0442\u0456\u043c \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 ** \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430 ** \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c ** \u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ** . \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/) \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.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 \u0440\u0435\u0436\u0438\u043c\u0443 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f)", + "mode": "\u0420\u0435\u0436\u0438\u043c" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u041f\u043e\u043b\u0435 'IP-\u0430\u0434\u0440\u0435\u0441\u0430' \u043c\u043e\u0436\u043d\u0430 \u0437\u0430\u043b\u0438\u0448\u0438\u0442\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'Auto Discovery', \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0434\u043e\u0434\u0430\u043d\u0456 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e.", + "title": "PlayStation 4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/de.json b/homeassistant/components/pvpc_hourly_pricing/translations/de.json index 8e3e9b68e42b5d..1b5c4d37658777 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/de.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Die Integration ist bereits mit einem vorhandenen Sensor mit diesem Tarif konfiguriert" + "already_configured": "Der Dienst ist bereits konfiguriert" }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/tr.json b/homeassistant/components/pvpc_hourly_pricing/translations/tr.json new file mode 100644 index 00000000000000..394f876401beb0 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "name": "Sens\u00f6r Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/uk.json b/homeassistant/components/pvpc_hourly_pricing/translations/uk.json new file mode 100644 index 00000000000000..da2136d7765e00 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "tariff": "\u041a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u043d\u0438\u0439 \u0442\u0430\u0440\u0438\u0444 (1, 2 \u0430\u0431\u043e 3 \u043f\u0435\u0440\u0456\u043e\u0434\u0438)" + }, + "description": "\u0426\u0435\u0439 \u0441\u0435\u043d\u0441\u043e\u0440 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454 \u043e\u0444\u0456\u0446\u0456\u0439\u043d\u0438\u0439 API \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f [\u043f\u043e\u0433\u043e\u0434\u0438\u043d\u043d\u043e\u0457 \u0446\u0456\u043d\u0438 \u0437\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u0435\u043d\u0435\u0440\u0433\u0456\u044e (PVPC)] (https://www.esios.ree.es/es/pvpc) \u0432 \u0406\u0441\u043f\u0430\u043d\u0456\u0457.\n\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044c \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0430\u0440\u0438\u0444, \u0437\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u043e\u0441\u0442\u0456 \u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0438\u0445 \u043f\u0435\u0440\u0456\u043e\u0434\u0456\u0432 \u0432 \u0434\u0435\u043d\u044c:\n- 1 \u043f\u0435\u0440\u0456\u043e\u0434: normal\n- 2 \u043f\u0435\u0440\u0456\u043e\u0434\u0438: discrimination (nightly rate)\n- 3 \u043f\u0435\u0440\u0456\u043e\u0434\u0438: electric car (nightly rate of 3 periods)", + "title": "\u0412\u0438\u0431\u0456\u0440 \u0442\u0430\u0440\u0438\u0444\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/de.json b/homeassistant/components/rachio/translations/de.json index e6a4d73cde1ab7..9acd92ce40de22 100644 --- a/homeassistant/components/rachio/translations/de.json +++ b/homeassistant/components/rachio/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, @@ -13,7 +13,7 @@ "data": { "api_key": "API-Schl\u00fcssel" }, - "description": "Sie ben\u00f6tigen den API-Schl\u00fcssel von https://app.rach.io/. W\u00e4hlen Sie \"Kontoeinstellungen\" und klicken Sie dann auf \"API-SCHL\u00dcSSEL ERHALTEN\".", + "description": "Du ben\u00f6tigst den API-Schl\u00fcssel von https://app.rach.io/. Gehe in die Einstellungen und klicke auf \"API-SCHL\u00dcSSEL ANFORDERN\".", "title": "Stellen Sie eine Verbindung zu Ihrem Rachio-Ger\u00e4t her" } } @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "Wie lange, in Minuten, um eine Station einzuschalten, wenn der Schalter aktiviert ist." + "manual_run_mins": "Wie viele Minuten es laufen soll, wenn ein Zonen-Schalter aktiviert wird" } } } diff --git a/homeassistant/components/rachio/translations/tr.json b/homeassistant/components/rachio/translations/tr.json new file mode 100644 index 00000000000000..8bbc4eb1e49db7 --- /dev/null +++ b/homeassistant/components/rachio/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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/uk.json b/homeassistant/components/rachio/translations/uk.json new file mode 100644 index 00000000000000..af5d7cd39d97a7 --- /dev/null +++ b/homeassistant/components/rachio/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043a\u043b\u044e\u0447 API \u0437 \u0441\u0430\u0439\u0442\u0443 https://app.rach.io/. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0430 \u043f\u043e\u0442\u0456\u043c \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c 'GET API KEY'.", + "title": "Rachio" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "\u0422\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044c \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u0440\u0438 \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0456\u0457 \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447\u0430 \u0437\u043e\u043d\u0438 (\u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/de.json b/homeassistant/components/rainmachine/translations/de.json index 92df52bb1481ec..511d85b36b6c4f 100644 --- a/homeassistant/components/rainmachine/translations/de.json +++ b/homeassistant/components/rainmachine/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieser RainMachine-Kontroller ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { diff --git a/homeassistant/components/rainmachine/translations/tr.json b/homeassistant/components/rainmachine/translations/tr.json index 20f74cae994549..80cfc05e568eaa 100644 --- a/homeassistant/components/rainmachine/translations/tr.json +++ b/homeassistant/components/rainmachine/translations/tr.json @@ -1,4 +1,21 @@ { + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "ip_address": "Ana makine ad\u0131 veya IP adresi", + "password": "Parola", + "port": "Port" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/rainmachine/translations/uk.json b/homeassistant/components/rainmachine/translations/uk.json new file mode 100644 index 00000000000000..ff8d7089cecb83 --- /dev/null +++ b/homeassistant/components/rainmachine/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "ip_address": "\u0414\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "RainMachine" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "\u0427\u0430\u0441 \u0440\u043e\u0431\u043e\u0442\u0438 \u0437\u043e\u043d\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f RainMachine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/de.json b/homeassistant/components/recollect_waste/translations/de.json new file mode 100644 index 00000000000000..7cbcea1b25e301 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/de.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "step": { + "user": { + "data": { + "place_id": "Platz-ID", + "service_id": "Dienst-ID" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Recollect Waste konfigurieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/es.json b/homeassistant/components/recollect_waste/translations/es.json index 2fdeb991bfde40..69a39d435ebbe1 100644 --- a/homeassistant/components/recollect_waste/translations/es.json +++ b/homeassistant/components/recollect_waste/translations/es.json @@ -20,7 +20,8 @@ "init": { "data": { "friendly_name": "Utilizar nombres descriptivos para los tipos de recogida (cuando sea posible)" - } + }, + "title": "Configurar la recogida de residuos" } } } diff --git a/homeassistant/components/recollect_waste/translations/lb.json b/homeassistant/components/recollect_waste/translations/lb.json new file mode 100644 index 00000000000000..4e312bb0f23485 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "invalid_place_or_service_id": "Ong\u00eblteg Place oder Service ID" + }, + "step": { + "user": { + "data": { + "place_id": "Place ID", + "service_id": "Service ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/pl.json b/homeassistant/components/recollect_waste/translations/pl.json index 013d0028790658..cc0342e93d75da 100644 --- a/homeassistant/components/recollect_waste/translations/pl.json +++ b/homeassistant/components/recollect_waste/translations/pl.json @@ -14,5 +14,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "U\u017cywaj przyjaznych nazw dla typu odbioru (je\u015bli to mo\u017cliwe)" + }, + "title": "Konfiguracja Recollect Waste" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/tr.json b/homeassistant/components/recollect_waste/translations/tr.json new file mode 100644 index 00000000000000..5307276a71d3a3 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/uk.json b/homeassistant/components/recollect_waste/translations/uk.json new file mode 100644 index 00000000000000..db47699f1bacf0 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/uk.json @@ -0,0 +1,28 @@ +{ + "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." + }, + "error": { + "invalid_place_or_service_id": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 ID \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0431\u043e \u0441\u043b\u0443\u0436\u0431\u0438." + }, + "step": { + "user": { + "data": { + "place_id": "ID \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f", + "service_id": "ID \u0441\u043b\u0443\u0436\u0431\u0438" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0440\u043e\u0437\u0443\u043c\u0456\u043b\u0456 \u0456\u043c\u0435\u043d\u0430 \u0434\u043b\u044f \u0442\u0438\u043f\u0456\u0432 \u0432\u0438\u0431\u043e\u0440\u0443 (\u044f\u043a\u0449\u043e \u043c\u043e\u0436\u043b\u0438\u0432\u043e)" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Recollect Waste" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/remote/translations/tr.json b/homeassistant/components/remote/translations/tr.json index cdc40c6268bb56..5359c99a78a28e 100644 --- a/homeassistant/components/remote/translations/tr.json +++ b/homeassistant/components/remote/translations/tr.json @@ -1,4 +1,14 @@ { + "device_automation": { + "action_type": { + "turn_off": "{entity_name} kapat", + "turn_on": "{entity_name} a\u00e7\u0131n" + }, + "trigger_type": { + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "state": { "_": { "off": "Kapal\u0131", diff --git a/homeassistant/components/remote/translations/uk.json b/homeassistant/components/remote/translations/uk.json index 2feda4928e5847..1f275f5f2ebe87 100644 --- a/homeassistant/components/remote/translations/uk.json +++ b/homeassistant/components/remote/translations/uk.json @@ -1,5 +1,14 @@ { "device_automation": { + "action_type": { + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u043e" diff --git a/homeassistant/components/rfxtrx/translations/ca.json b/homeassistant/components/rfxtrx/translations/ca.json index 6c4e920df02e52..d7db4107e3bb2e 100644 --- a/homeassistant/components/rfxtrx/translations/ca.json +++ b/homeassistant/components/rfxtrx/translations/ca.json @@ -64,7 +64,8 @@ "off_delay": "Retard OFF", "off_delay_enabled": "Activa el retard OFF", "replace_device": "Selecciona el dispositiu a substituir", - "signal_repetitions": "Nombre de repeticions del senyal" + "signal_repetitions": "Nombre de repeticions del senyal", + "venetian_blind_mode": "Mode persiana veneciana" }, "title": "Configuraci\u00f3 de les opcions del dispositiu" } diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index 1979a10cb8a6fd..b1e4197c0f1a2c 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -2,13 +2,17 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert. Nur eine Konfiguration m\u00f6glich.", - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "setup_network": { + "data": { + "host": "Host", + "port": "Port" + }, "title": "Verbindungsadresse ausw\u00e4hlen" }, "setup_serial": { @@ -18,6 +22,9 @@ "title": "Ger\u00e4t" }, "setup_serial_manual_path": { + "data": { + "device": "USB-Ger\u00e4te-Pfad" + }, "title": "Pfad" }, "user": { @@ -30,6 +37,7 @@ }, "options": { "error": { + "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", "unknown": "Unerwarteter Fehler" }, "step": { @@ -37,6 +45,13 @@ "data": { "debug": "Debugging aktivieren" } + }, + "set_device_options": { + "data": { + "off_delay": "Ausschaltverz\u00f6gerung", + "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", + "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll" + } } } } diff --git a/homeassistant/components/rfxtrx/translations/en.json b/homeassistant/components/rfxtrx/translations/en.json index 2d73ac568100c0..5e3f551e0cf95c 100644 --- a/homeassistant/components/rfxtrx/translations/en.json +++ b/homeassistant/components/rfxtrx/translations/en.json @@ -64,8 +64,8 @@ "off_delay": "Off delay", "off_delay_enabled": "Enable off delay", "replace_device": "Select device to replace", - "venetian_blind_mode": "Venetian blind mode (tilt by: US - long press, EU - short press)", - "signal_repetitions": "Number of signal repetitions" + "signal_repetitions": "Number of signal repetitions", + "venetian_blind_mode": "Venetian blind mode" }, "title": "Configure device options" } diff --git a/homeassistant/components/rfxtrx/translations/es.json b/homeassistant/components/rfxtrx/translations/es.json index 86bad8f096f698..c1c4d72735cdf4 100644 --- a/homeassistant/components/rfxtrx/translations/es.json +++ b/homeassistant/components/rfxtrx/translations/es.json @@ -64,7 +64,8 @@ "off_delay": "Retraso de apagado", "off_delay_enabled": "Activar retardo de apagado", "replace_device": "Seleccione el dispositivo que desea reemplazar", - "signal_repetitions": "N\u00famero de repeticiones de la se\u00f1al" + "signal_repetitions": "N\u00famero de repeticiones de la se\u00f1al", + "venetian_blind_mode": "Modo de persiana veneciana" }, "title": "Configurar las opciones del dispositivo" } diff --git a/homeassistant/components/rfxtrx/translations/et.json b/homeassistant/components/rfxtrx/translations/et.json index 1ade1f112c2b48..662664b445480e 100644 --- a/homeassistant/components/rfxtrx/translations/et.json +++ b/homeassistant/components/rfxtrx/translations/et.json @@ -64,7 +64,8 @@ "off_delay": "V\u00e4ljal\u00fclitamise viivitus", "off_delay_enabled": "Luba v\u00e4ljal\u00fclitusviivitus", "replace_device": "Vali asendav seade", - "signal_repetitions": "Signaali korduste arv" + "signal_repetitions": "Signaali korduste arv", + "venetian_blind_mode": "Ribikardinate juhtimine" }, "title": "Seadista seadme valikud" } diff --git a/homeassistant/components/rfxtrx/translations/it.json b/homeassistant/components/rfxtrx/translations/it.json index ff705fdd0a2285..938c471e992a63 100644 --- a/homeassistant/components/rfxtrx/translations/it.json +++ b/homeassistant/components/rfxtrx/translations/it.json @@ -64,7 +64,8 @@ "off_delay": "Ritardo di spegnimento", "off_delay_enabled": "Attivare il ritardo di spegnimento", "replace_device": "Selezionare il dispositivo da sostituire", - "signal_repetitions": "Numero di ripetizioni del segnale" + "signal_repetitions": "Numero di ripetizioni del segnale", + "venetian_blind_mode": "Modalit\u00e0 veneziana" }, "title": "Configurare le opzioni del dispositivo" } diff --git a/homeassistant/components/rfxtrx/translations/no.json b/homeassistant/components/rfxtrx/translations/no.json index 752136dac7fe70..3eb9c9b83df52b 100644 --- a/homeassistant/components/rfxtrx/translations/no.json +++ b/homeassistant/components/rfxtrx/translations/no.json @@ -64,7 +64,8 @@ "off_delay": "Av forsinkelse", "off_delay_enabled": "Aktiver av forsinkelse", "replace_device": "Velg enheten du vil erstatte", - "signal_repetitions": "Antall signalrepetisjoner" + "signal_repetitions": "Antall signalrepetisjoner", + "venetian_blind_mode": "Persiennemodus" }, "title": "Konfigurer enhetsalternativer" } diff --git a/homeassistant/components/rfxtrx/translations/pl.json b/homeassistant/components/rfxtrx/translations/pl.json index bf17d6c5166392..e0e69b2a64a037 100644 --- a/homeassistant/components/rfxtrx/translations/pl.json +++ b/homeassistant/components/rfxtrx/translations/pl.json @@ -75,7 +75,8 @@ "off_delay": "Op\u00f3\u017anienie stanu \"off\"", "off_delay_enabled": "W\u0142\u0105cz op\u00f3\u017anienie stanu \"off\"", "replace_device": "Wybierz urz\u0105dzenie do zast\u0105pienia", - "signal_repetitions": "Liczba powt\u00f3rze\u0144 sygna\u0142u" + "signal_repetitions": "Liczba powt\u00f3rze\u0144 sygna\u0142u", + "venetian_blind_mode": "Tryb \u017caluzji weneckich" }, "title": "Konfiguracja opcji urz\u0105dzenia" } diff --git a/homeassistant/components/rfxtrx/translations/ru.json b/homeassistant/components/rfxtrx/translations/ru.json index 361b051ac2ce4d..5a635766d3f379 100644 --- a/homeassistant/components/rfxtrx/translations/ru.json +++ b/homeassistant/components/rfxtrx/translations/ru.json @@ -64,7 +64,8 @@ "off_delay": "\u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "off_delay_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "replace_device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u0437\u0430\u043c\u0435\u043d\u044b", - "signal_repetitions": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0430" + "signal_repetitions": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0430", + "venetian_blind_mode": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u043d\u0435\u0446\u0438\u0430\u043d\u0441\u043a\u0438\u0445 \u0436\u0430\u043b\u044e\u0437\u0438" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } diff --git a/homeassistant/components/rfxtrx/translations/tr.json b/homeassistant/components/rfxtrx/translations/tr.json new file mode 100644 index 00000000000000..1c3ad8b9e05f86 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/tr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "setup_network": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + }, + "options": { + "error": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "step": { + "set_device_options": { + "data": { + "venetian_blind_mode": "Jaluzi modu" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/uk.json b/homeassistant/components/rfxtrx/translations/uk.json new file mode 100644 index 00000000000000..1b0938b8b70887 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/uk.json @@ -0,0 +1,74 @@ +{ + "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.", + "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" + }, + "step": { + "setup_network": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "setup_serial": { + "data": { + "device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "setup_serial_manual_path": { + "data": { + "device": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "\u0428\u043b\u044f\u0445" + }, + "user": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + } + } + }, + "options": { + "error": { + "already_configured_device": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "invalid_event_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434 \u043f\u043e\u0434\u0456\u0457.", + "invalid_input_2262_off": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f.", + "invalid_input_2262_on": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f.", + "invalid_input_off_delay": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u0437\u0430\u0442\u0440\u0438\u043c\u043a\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f", + "debug": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043d\u0430\u043b\u0430\u0433\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", + "event_code": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0456\u0457", + "remove_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + }, + "set_device_options": { + "data": { + "command_off": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "command_on": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "data_bit": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445", + "fire_event": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "off_delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "off_delay_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0437\u0430\u0442\u0440\u0438\u043c\u043a\u0443 \u0432\u0438\u043c\u0438\u043a\u0430\u043d\u043d\u044f", + "replace_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u0437\u0430\u043c\u0456\u043d\u0438", + "signal_repetitions": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0443", + "venetian_blind_mode": "\u0420\u0435\u0436\u0438\u043c \u0436\u0430\u043b\u044e\u0437\u0456" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ 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 3da2e5f5384221..24e5ee56d76103 100644 --- a/homeassistant/components/rfxtrx/translations/zh-Hant.json +++ b/homeassistant/components/rfxtrx/translations/zh-Hant.json @@ -64,7 +64,8 @@ "off_delay": "\u5ef6\u9072", "off_delay_enabled": "\u958b\u555f\u5ef6\u9072", "replace_device": "\u9078\u64c7\u88dd\u7f6e\u4ee5\u53d6\u4ee3", - "signal_repetitions": "\u8a0a\u865f\u91cd\u8907\u6b21\u6578" + "signal_repetitions": "\u8a0a\u865f\u91cd\u8907\u6b21\u6578", + "venetian_blind_mode": "\u767e\u8449\u7a97\u6a21\u5f0f" }, "title": "\u8a2d\u5b9a\u88dd\u7f6e\u9078\u9805" } diff --git a/homeassistant/components/ring/translations/tr.json b/homeassistant/components/ring/translations/tr.json new file mode 100644 index 00000000000000..caba385d7fa10f --- /dev/null +++ b/homeassistant/components/ring/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/uk.json b/homeassistant/components/ring/translations/uk.json new file mode 100644 index 00000000000000..8d40cdf0d23f59 --- /dev/null +++ b/homeassistant/components/ring/translations/uk.json @@ -0,0 +1,26 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041a\u043e\u0434 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Ring" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/de.json b/homeassistant/components/risco/translations/de.json index ad863f7ff792e0..36d808bd6dec77 100644 --- a/homeassistant/components/risco/translations/de.json +++ b/homeassistant/components/risco/translations/de.json @@ -1,14 +1,18 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { "password": "Passwort", - "pin": "PIN Code", + "pin": "PIN-Code", "username": "Benutzername" } } @@ -16,6 +20,12 @@ }, "options": { "step": { + "init": { + "data": { + "code_arm_required": "PIN-Code zum Entsperren vorgeben", + "code_disarm_required": "PIN-Code zum Entsperren vorgeben" + } + }, "risco_to_ha": { "data": { "A": "Gruppe A", diff --git a/homeassistant/components/risco/translations/lb.json b/homeassistant/components/risco/translations/lb.json index 197dd78c4033b0..ae136cb184305b 100644 --- a/homeassistant/components/risco/translations/lb.json +++ b/homeassistant/components/risco/translations/lb.json @@ -39,7 +39,9 @@ "A": "Grupp A", "B": "Grupp B", "C": "Grupp C", - "D": "Grupp D" + "D": "Grupp D", + "arm": "Aktiv\u00e9iert (\u00cbNNERWEE)", + "partial_arm": "Deelweis Aktiv\u00e9iert (DOHEEM)" } } } diff --git a/homeassistant/components/risco/translations/tr.json b/homeassistant/components/risco/translations/tr.json new file mode 100644 index 00000000000000..02a3b505f84b8b --- /dev/null +++ b/homeassistant/components/risco/translations/tr.json @@ -0,0 +1,27 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Se\u00e7enekleri yap\u0131land\u0131r\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/uk.json b/homeassistant/components/risco/translations/uk.json new file mode 100644 index 00000000000000..53b64344f2edd4 --- /dev/null +++ b/homeassistant/components/risco/translations/uk.json @@ -0,0 +1,55 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "pin": "PIN-\u043a\u043e\u0434", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u041d\u0435 \u0432\u0434\u043e\u043c\u0430)", + "armed_custom_bypass": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 \u0437 \u0432\u0438\u043d\u044f\u0442\u043a\u0430\u043c\u0438", + "armed_home": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u0412\u0434\u043e\u043c\u0430)", + "armed_night": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u043d\u0456\u0447)" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Risco \u043f\u0440\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u0456 \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Home Assistant", + "title": "\u0417\u0456\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u043d\u0456\u0432 Home Assistant \u0456 Risco" + }, + "init": { + "data": { + "code_arm_required": "\u0412\u0438\u043c\u0430\u0433\u0430\u0442\u0438 PIN-\u043a\u043e\u0434 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443", + "code_disarm_required": "\u0412\u0438\u043c\u0430\u0433\u0430\u0442\u0438 PIN-\u043a\u043e\u0434 \u0434\u043b\u044f \u0437\u043d\u044f\u0442\u0442\u044f \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438", + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + }, + "risco_to_ha": { + "data": { + "A": "\u0413\u0440\u0443\u043f\u0430 \u0410", + "B": "\u0413\u0440\u0443\u043f\u0430 B", + "C": "\u0413\u0440\u0443\u043f\u0430 C", + "D": "\u0413\u0440\u0443\u043f\u0430 D", + "arm": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (AWAY)", + "partial_arm": "\u0427\u0430\u0441\u0442\u043a\u043e\u0432\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 (STAY)" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Home Assistant \u043f\u0440\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u0456 \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Risco", + "title": "\u0417\u0456\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u043d\u0456\u0432 Home Assistant \u0456 Risco" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ca.json b/homeassistant/components/roku/translations/ca.json index e9ab61575b5203..eb0564b5bde0cf 100644 --- a/homeassistant/components/roku/translations/ca.json +++ b/homeassistant/components/roku/translations/ca.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Vols configurar {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Vols configurar {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/cs.json b/homeassistant/components/roku/translations/cs.json index 7a83973a6f7856..89ca523af470dd 100644 --- a/homeassistant/components/roku/translations/cs.json +++ b/homeassistant/components/roku/translations/cs.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Chcete nastavit {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Chcete nastavit {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 9899aeba427e4f..4bfb3c7503ddda 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "M\u00f6chtest du {name} einrichten?", + "title": "Roku" + }, "ssdp_confirm": { "data": { "one": "eins", diff --git a/homeassistant/components/roku/translations/en.json b/homeassistant/components/roku/translations/en.json index 6facd1f3a7c26b..08db89f367761a 100644 --- a/homeassistant/components/roku/translations/en.json +++ b/homeassistant/components/roku/translations/en.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Do you want to set up {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Do you want to set up {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/es.json b/homeassistant/components/roku/translations/es.json index 78fb25809271dd..95e42643379c1f 100644 --- a/homeassistant/components/roku/translations/es.json +++ b/homeassistant/components/roku/translations/es.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "\u00bfQuieres configurar {name} ?", + "title": "Roku" + }, "ssdp_confirm": { "description": "\u00bfQuieres configurar {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/et.json b/homeassistant/components/roku/translations/et.json index e4869d044c8d4b..6727f539f57248 100644 --- a/homeassistant/components/roku/translations/et.json +++ b/homeassistant/components/roku/translations/et.json @@ -9,6 +9,10 @@ }, "flow_title": "", "step": { + "discovery_confirm": { + "description": "Kas soovid seadistada {name}?", + "title": "" + }, "ssdp_confirm": { "description": "Kas soovid seadistada {name}?", "title": "" diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 007be91d15532c..100d99924727f2 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Vuoi configurare {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Vuoi impostare {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/lb.json b/homeassistant/components/roku/translations/lb.json index 3aa8e5fa642fe2..04ad814c6b4d4e 100644 --- a/homeassistant/components/roku/translations/lb.json +++ b/homeassistant/components/roku/translations/lb.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Soll {name} konfigur\u00e9iert ginn?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Soll {name} konfigur\u00e9iert ginn?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/no.json b/homeassistant/components/roku/translations/no.json index 029220b58593ab..dd4ce4181415fa 100644 --- a/homeassistant/components/roku/translations/no.json +++ b/homeassistant/components/roku/translations/no.json @@ -9,6 +9,10 @@ }, "flow_title": "", "step": { + "discovery_confirm": { + "description": "Vil du konfigurere {name}?", + "title": "" + }, "ssdp_confirm": { "description": "Vil du sette opp {name} ?", "title": "" diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 3231d6c4bb7367..1d193acc0ff15a 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -9,6 +9,16 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "data": { + "few": "kilka", + "many": "wiele", + "one": "jeden", + "other": "inne" + }, + "description": "Czy chcesz skonfigurowa\u0107 {name}?", + "title": "Roku" + }, "ssdp_confirm": { "data": { "few": "kilka", diff --git a/homeassistant/components/roku/translations/ru.json b/homeassistant/components/roku/translations/ru.json index b5dcddbe555417..f7f36f41b27336 100644 --- a/homeassistant/components/roku/translations/ru.json +++ b/homeassistant/components/roku/translations/ru.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/tr.json b/homeassistant/components/roku/translations/tr.json new file mode 100644 index 00000000000000..0dca1a028b283e --- /dev/null +++ b/homeassistant/components/roku/translations/tr.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "discovery_confirm": { + "description": "{name} kurmak istiyor musunuz?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "{name} kurmak istiyor musunuz?" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/uk.json b/homeassistant/components/roku/translations/uk.json new file mode 100644 index 00000000000000..b7db8875f8e0b8 --- /dev/null +++ b/homeassistant/components/roku/translations/uk.json @@ -0,0 +1,28 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Roku: {name}", + "step": { + "discovery_confirm": { + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?", + "title": "Roku" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e Roku." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/zh-Hant.json b/homeassistant/components/roku/translations/zh-Hant.json index cfa3a4aa3b4ed7..4b0566d66b06dc 100644 --- a/homeassistant/components/roku/translations/zh-Hant.json +++ b/homeassistant/components/roku/translations/zh-Hant.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku\uff1a{name}", "step": { + "discovery_confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", + "title": "Roku" + }, "ssdp_confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", "title": "Roku" diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index af358678144949..b2fe68c876c482 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "not_irobot_device": "El dispositiu descobert no \u00e9s un dispositiu iRobot" + }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Amfitri\u00f3" + }, + "description": "Selecciona un/a Roomba o Braava.", + "title": "Connecta't al dispositiu autom\u00e0ticament" + }, + "link": { + "description": "Mant\u00e9 premut el bot\u00f3 d'inici a {name} fins que el dispositiu emeti un so (aproximadament dos segons).", + "title": "Recupera la contrasenya" + }, + "link_manual": { + "data": { + "password": "Contrasenya" + }, + "description": "No s'ha pogut obtenir la contrasenya del dispositiu autom\u00e0ticament. Segueix els passos de la seg\u00fcent documentaci\u00f3: {auth_help_url}", + "title": "Introdueix contrasenya" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Amfitri\u00f3" + }, + "description": "No s'ha descobert cap Roomba ni cap Braava a la teva xarxa. El BLID \u00e9s la part del nom d'amfitri\u00f3 del dispositiu despr\u00e9s de `iRobot-`. Segueix els passos de la seg\u00fcent documentaci\u00f3: {auth_help_url}", + "title": "Connecta't al dispositiu manualment" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/cs.json b/homeassistant/components/roomba/translations/cs.json index fdf4aff22c90a2..d94d39f8136371 100644 --- a/homeassistant/components/roomba/translations/cs.json +++ b/homeassistant/components/roomba/translations/cs.json @@ -1,9 +1,29 @@ { "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Hostitel" + } + }, + "link_manual": { + "data": { + "password": "Heslo" + } + }, + "manual": { + "data": { + "host": "Hostitel" + } + }, "user": { "data": { "delay": "Zpo\u017ed\u011bn\u00ed", diff --git a/homeassistant/components/roomba/translations/de.json b/homeassistant/components/roomba/translations/de.json index 2f6ef37d13c179..780d406bcafbc6 100644 --- a/homeassistant/components/roomba/translations/de.json +++ b/homeassistant/components/roomba/translations/de.json @@ -1,9 +1,40 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "not_irobot_device": "Das erkannte Ger\u00e4t ist kein iRobot-Ger\u00e4t" + }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut" + "cannot_connect": "Verbindung fehlgeschlagen" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "W\u00e4hle einen Roomba oder Braava aus.", + "title": "Automatisch mit dem Ger\u00e4t verbinden" + }, + "link": { + "description": "Halte die Home-Taste von {name} gedr\u00fcckt, bis das Ger\u00e4t einen Ton erzeugt (ca. zwei Sekunden).", + "title": "Passwort abrufen" + }, + "link_manual": { + "data": { + "password": "Passwort" + }, + "description": "Das Passwort konnte nicht automatisch vom Ger\u00e4t abgerufen werden. Bitte die in der Dokumentation beschriebenen Schritte unter {auth_help_url} befolgen", + "title": "Passwort eingeben" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "title": "Manuell mit dem Ger\u00e4t verbinden" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 276ab4a92a2e4d..8d449e1881526f 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -1,51 +1,62 @@ { - "config": { - "flow_title": "iRobot {name} ({host})", - "step": { - "init": { - "title": "Automaticlly connect to the device", - "description": "Select a Roomba or Braava.", - "data": { - "host": "[%key:common::config_flow::data::host%]" + "config": { + "abort": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", + "not_irobot_device": "Discovered device is not an iRobot device" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "flow_title": "iRobot {name} ({host})", + "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Select a Roomba or Braava.", + "title": "Automaticlly connect to the device" + }, + "link": { + "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds).", + "title": "Retrieve Password" + }, + "link_manual": { + "data": { + "password": "Password" + }, + "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", + "title": "Enter Password" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: {auth_help_url}", + "title": "Manually connect to the device" + }, + "user": { + "data": { + "blid": "BLID", + "continuous": "Continuous", + "delay": "Delay", + "host": "Host", + "password": "Password" + }, + "description": "Currently retrieving the BLID and password is a manual process. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Connect to the device" + } } - }, - "manual": { - "title": "Manually connect to the device", - "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: {auth_help_url}", - "data": { - "host": "[%key:common::config_flow::data::host%]", - "blid": "BLID" - } - }, - "link": { - "title": "Retrieve Password", - "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds)." - }, - "link_manual": { - "title": "Enter Password", - "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", - "data": { - "password": "[%key:common::config_flow::data::password%]" - } - } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, - "abort": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "not_irobot_device": "Discovered device not an iRobot device" - } - }, - "options": { - "step": { - "init": { - "data": { - "continuous": "Continuous", - "delay": "Delay" + "options": { + "step": { + "init": { + "data": { + "continuous": "Continuous", + "delay": "Delay" + } + } } - } } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/es.json b/homeassistant/components/roomba/translations/es.json index a49022c2d3d39d..29f0b47a655eb2 100644 --- a/homeassistant/components/roomba/translations/es.json +++ b/homeassistant/components/roomba/translations/es.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar", + "not_irobot_device": "El dispositivo descubierto no es un dispositivo iRobot" + }, "error": { "cannot_connect": "No se pudo conectar" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Selecciona una Roomba o Braava.", + "title": "Conectar autom\u00e1ticamente con el dispositivo" + }, + "link": { + "description": "Mant\u00e9n pulsado el bot\u00f3n Inicio en {name} hasta que el dispositivo genere un sonido (aproximadamente dos segundos).", + "title": "Recuperar la contrase\u00f1a" + }, + "link_manual": { + "data": { + "password": "Contrase\u00f1a" + }, + "description": "No se pudo recuperar la contrase\u00f1a desde el dispositivo de forma autom\u00e1tica. Por favor, sigue los pasos descritos en la documentaci\u00f3n en: {auth_help_url}", + "title": "Escribe la contrase\u00f1a" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "No se ha descubierto ning\u00fan dispositivo Roomba ni Braava en tu red. El BLID es la parte del nombre de host del dispositivo despu\u00e9s de 'iRobot-'. Por favor, sigue los pasos descritos en la documentaci\u00f3n en: {auth_help_url}", + "title": "Conectar manualmente con el dispositivo" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/et.json b/homeassistant/components/roomba/translations/et.json index 92da58fa146329..e038257c12dea6 100644 --- a/homeassistant/components/roomba/translations/et.json +++ b/homeassistant/components/roomba/translations/et.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "not_irobot_device": "Leitud seade ei ole iRoboti seade" + }, "error": { "cannot_connect": "\u00dchendamine nurjus" }, + "flow_title": "iRobot {name} ( {host} )", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Vali Roomba v\u00f5i Braava seade.", + "title": "\u00dchendu seadmega automaatselt" + }, + "link": { + "description": "Vajuta ja hoia all seadme {name} nuppu Home kuni seade teeb piiksu (umbes kaks sekundit).", + "title": "Hangi salas\u00f5na" + }, + "link_manual": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Salas\u00f5na ei \u00f5nnestunud seadmest automaatselt hankida. J\u00e4rgi dokumentatsioonis toodud juhiseid: {auth_help_url}", + "title": "Sisesta salas\u00f5na" + }, + "manual": { + "data": { + "blid": "", + "host": "Host" + }, + "description": "V\u00f5rgus ei tuvastatud \u00fchtegi Roomba ega Braava seadet. BLID on seadme hostinime osa p\u00e4rast iRobot-`. J\u00e4rgi dokumentatsioonis toodud juhiseid: {auth_help_url}", + "title": "\u00dchenda seadmega k\u00e4sitsi" + }, "user": { "data": { "blid": "", diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 1ec97dd3842e08..8142d3acf1367b 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -1,9 +1,38 @@ { "config": { + "abort": { + "cannot_connect": "Echec de connection" + }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer" }, "step": { + "init": { + "data": { + "host": "H\u00f4te" + }, + "description": "S\u00e9lectionnez un Roomba ou un Braava.", + "title": "Se connecter automatiquement \u00e0 l'appareil" + }, + "link": { + "description": "Appuyez sur le bouton Accueil et maintenez-le enfonc\u00e9 jusqu'\u00e0 ce que l'appareil \u00e9mette un son (environ deux secondes).", + "title": "R\u00e9cup\u00e9rer le mot de passe" + }, + "link_manual": { + "data": { + "password": "Mot de passe" + }, + "description": "Le mot de passe n'a pas pu \u00eatre r\u00e9cup\u00e9r\u00e9 automatiquement \u00e0 partir de l'appareil. Veuillez suivre les \u00e9tapes d\u00e9crites dans la documentation \u00e0 {auth_help_url}", + "title": "Entrer le mot de passe" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "H\u00f4te" + }, + "description": "Aucun Roomba ou Braava d\u00e9couvert sur votre r\u00e9seau. Le BLID est la partie du nom d'h\u00f4te du p\u00e9riph\u00e9rique apr\u00e8s `iRobot-`. Veuillez suivre les \u00e9tapes d\u00e9crites dans la documentation \u00e0 {auth_help_url}", + "title": "Se connecter manuellement \u00e0 l'appareil" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/it.json b/homeassistant/components/roomba/translations/it.json index d109aa8bcc099c..b9e01faf16c338 100644 --- a/homeassistant/components/roomba/translations/it.json +++ b/homeassistant/components/roomba/translations/it.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "not_irobot_device": "Il dispositivo rilevato non \u00e8 un dispositivo iRobot" + }, "error": { "cannot_connect": "Impossibile connettersi" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Seleziona un Roomba o un Braava.", + "title": "Connettiti automaticamente al dispositivo" + }, + "link": { + "description": "Tieni premuto il pulsante Home su {name} fino a quando il dispositivo non genera un suono (circa due secondi).", + "title": "Recupera password" + }, + "link_manual": { + "data": { + "password": "Password" + }, + "description": "La password non pu\u00f2 essere recuperata automaticamente dal dispositivo. Segui le istruzioni indicate sulla documentazione a: {auth_help_url}", + "title": "Inserisci la password" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "Non sono stati trovati Roomba o Braava all'interno della tua rete. Il BLID \u00e8 la porzione del nome host del dispositivo dopo `iRobot-`. Segui le istruzioni indicate sulla documentazione a: {auth_help_url}", + "title": "Connettiti manualmente al dispositivo" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index d3fc631f5dfedd..500aa4fbee6a46 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -1,9 +1,35 @@ { "config": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + }, "error": { "cannot_connect": "Feeler beim verbannen" }, "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Ee Roomba oder Bravaa auswielen.", + "title": "Automatesch mam Apparat verbannen" + }, + "link": { + "title": "Passwuert ausliesen" + }, + "link_manual": { + "data": { + "password": "Passwuert" + }, + "title": "Passwuert aginn" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "title": "Manuell mam Apparat verbannen" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/no.json b/homeassistant/components/roomba/translations/no.json index adf13cb57af879..2bfe9f774d1698 100644 --- a/homeassistant/components/roomba/translations/no.json +++ b/homeassistant/components/roomba/translations/no.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "not_irobot_device": "Oppdaget enhet er ikke en iRobot-enhet" + }, "error": { "cannot_connect": "Tilkobling mislyktes" }, + "flow_title": "", "step": { + "init": { + "data": { + "host": "Vert" + }, + "description": "Velg en Roomba eller Braava", + "title": "Koble automatisk til enheten" + }, + "link": { + "description": "Trykk og hold inne Hjem-knappen p\u00e5 {name} til enheten genererer en lyd (omtrent to sekunder)", + "title": "Hent passord" + }, + "link_manual": { + "data": { + "password": "Passord" + }, + "description": "Passordet kunne ikke hentes automatisk fra enheten. F\u00f8lg trinnene som er beskrevet i dokumentasjonen p\u00e5: {auth_help_url}", + "title": "Skriv inn passord" + }, + "manual": { + "data": { + "blid": "", + "host": "Vert" + }, + "description": "Ingen Roomba eller Braava har blitt oppdaget i nettverket ditt. BLID er delen av enhetens vertsnavn etter `iRobot-`. F\u00f8lg trinnene som er beskrevet i dokumentasjonen p\u00e5: {auth_help_url}", + "title": "Koble til enheten manuelt" + }, "user": { "data": { "blid": "Blid", diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index b2a4ab89cbecdc..e4951a366ddd24 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "not_irobot_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem iRobot" + }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wybierz Roomb\u0119 lub Braava", + "title": "Po\u0142\u0105cz si\u0119 automatycznie z urz\u0105dzeniem" + }, + "link": { + "description": "Naci\u015bnij i przytrzymaj przycisk Home na {name} a\u017c urz\u0105dzenie wygeneruje d\u017awi\u0119k (oko\u0142o dwie sekundy).", + "title": "Odzyskiwanie has\u0142a" + }, + "link_manual": { + "data": { + "password": "Has\u0142o" + }, + "description": "Nie mo\u017cna automatycznie pobra\u0107 has\u0142a z urz\u0105dzenia. Post\u0119puj zgodnie z instrukcjami podanymi w dokumentacji pod adresem: {auth_help_url}", + "title": "Wprowad\u017a has\u0142o" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Nazwa hosta lub adres IP" + }, + "description": "W Twojej sieci nie wykryto urz\u0105dzenia Roomba ani Braava. BLID to cz\u0119\u015b\u0107 nazwy hosta urz\u0105dzenia po `iRobot-`. Post\u0119puj zgodnie z instrukcjami podanymi w dokumentacji pod adresem: {auth_help_url}", + "title": "R\u0119czne po\u0142\u0105czenie z urz\u0105dzeniem" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/pt.json b/homeassistant/components/roomba/translations/pt.json index 0156fd48a62666..6036e870e6c05d 100644 --- a/homeassistant/components/roomba/translations/pt.json +++ b/homeassistant/components/roomba/translations/pt.json @@ -1,9 +1,16 @@ { "config": { + "abort": { + "not_irobot_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo iRobot" + }, "error": { "cannot_connect": "Falha ao conectar, tente novamente" }, + "flow_title": "iRobot {name} ({host})", "step": { + "link": { + "title": "Recuperar Palavra-passe" + }, "user": { "data": { "continuous": "Cont\u00ednuo", diff --git a/homeassistant/components/roomba/translations/ru.json b/homeassistant/components/roomba/translations/ru.json index ee1192f69ec63c..979bb9bc70f7fc 100644 --- a/homeassistant/components/roomba/translations/ru.json +++ b/homeassistant/components/roomba/translations/ru.json @@ -1,9 +1,41 @@ { "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.", + "not_irobot_device": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 iRobot." + }, "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": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u044b\u043b\u0435\u0441\u043e\u0441 \u0438\u0437 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 Roomba \u0438\u043b\u0438 Braava.", + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + }, + "link": { + "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0438 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 Home \u043d\u0430 {name}, \u043f\u043e\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0438\u0437\u0434\u0430\u0441\u0442 \u0437\u0432\u0443\u043a (\u043e\u043a\u043e\u043b\u043e \u0434\u0432\u0443\u0445 \u0441\u0435\u043a\u0443\u043d\u0434).", + "title": "\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f" + }, + "link_manual": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439: {auth_help_url}.", + "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u043f\u044b\u043b\u0435\u0441\u043e\u0441\u043e\u0432 Roomba \u0438\u043b\u0438 Braava. BLID - \u044d\u0442\u043e \u0447\u0430\u0441\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u0441\u043b\u0435 \"iRobot-\". \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439: {auth_help_url}.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 \u0432\u0440\u0443\u0447\u043d\u0443\u044e" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/tr.json b/homeassistant/components/roomba/translations/tr.json new file mode 100644 index 00000000000000..3d85144c1883cb --- /dev/null +++ b/homeassistant/components/roomba/translations/tr.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_irobot_device": "Bulunan cihaz bir iRobot cihaz\u0131 de\u011fil" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "iRobot {name} ( {host} )", + "step": { + "init": { + "data": { + "host": "Ana Bilgisayar" + }, + "description": "Roomba veya Braava'y\u0131 se\u00e7in.", + "title": "Cihaza otomatik olarak ba\u011flan" + }, + "link": { + "description": "Cihaz bir ses olu\u015fturana kadar (yakla\u015f\u0131k iki saniye) {name} \u00fczerindeki Ana Sayfa d\u00fc\u011fmesini bas\u0131l\u0131 tutun.", + "title": "\u015eifre Al" + }, + "link_manual": { + "data": { + "password": "\u015eifre" + }, + "description": "Parola ayg\u0131ttan otomatik olarak al\u0131namad\u0131. L\u00fctfen belgelerde belirtilen ad\u0131mlar\u0131 izleyin: {auth_help_url}", + "title": "\u015eifre Girin" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Ana Bilgisayar" + }, + "title": "Cihaza manuel olarak ba\u011flan\u0131n" + }, + "user": { + "data": { + "continuous": "S\u00fcrekli", + "delay": "Gecikme", + "host": "Ana Bilgisayar", + "password": "Parola" + }, + "description": "\u015eu anda BLID ve parola alma manuel bir i\u015flemdir. L\u00fctfen a\u015fa\u011f\u0131daki belgelerde belirtilen ad\u0131mlar\u0131 izleyin: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Cihaza ba\u011flan\u0131n" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "S\u00fcrekli", + "delay": "Gecikme" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/uk.json b/homeassistant/components/roomba/translations/uk.json new file mode 100644 index 00000000000000..833a35f62f3a38 --- /dev/null +++ b/homeassistant/components/roomba/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "blid": "BLID", + "continuous": "\u0411\u0435\u0437\u043f\u0435\u0440\u0435\u0440\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 (\u0441\u0435\u043a.)", + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u0449\u043e\u0431 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 BLID \u0456 \u043f\u0430\u0440\u043e\u043b\u044c:\nhttps://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "\u0411\u0435\u0437\u043f\u0435\u0440\u0435\u0440\u0432\u043d\u043e", + "delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/zh-Hant.json b/homeassistant/components/roomba/translations/zh-Hant.json index 932e5cadd7567d..790eba79c032d4 100644 --- a/homeassistant/components/roomba/translations/zh-Hant.json +++ b/homeassistant/components/roomba/translations/zh-Hant.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "not_irobot_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e iRobot \u88dd\u7f6e" + }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u9078\u64c7 Roomba \u6216 Braava\u3002", + "title": "\u81ea\u52d5\u9023\u7dda\u81f3\u88dd\u7f6e" + }, + "link": { + "description": "\u8acb\u6309\u4f4f {name} \u4e0a\u7684 Home \u9375\u76f4\u5230\u88dd\u7f6e\u767c\u51fa\u8072\u97f3\uff08\u7d04\u5169\u79d2\uff09\u3002", + "title": "\u91cd\u7f6e\u5bc6\u78bc" + }, + "link_manual": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u5bc6\u78bc\u53ef\u81ea\u52d5\u81ea\u88dd\u7f6e\u4e0a\u53d6\u5f97\u3002\u8acb\u53c3\u95b1\u4ee5\u4e0b\u6587\u4ef6\u7684\u6b65\u9a5f\u9032\u884c\u8a2d\u5b9a\uff1a{auth_help_url}", + "title": "\u8f38\u5165\u5bc6\u78bc" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Roomba \u6216 Braava\u3002BLID \u88dd\u7f6e\u65bc\u4e3b\u6a5f\u7aef\u7684\u90e8\u5206\u540d\u7a31\u70ba `iRobot-` \u958b\u982d\u3002\u8acb\u53c3\u95b1\u4ee5\u4e0b\u6587\u4ef6\u7684\u6b65\u9a5f\u9032\u884c\u8a2d\u5b9a\uff1a{auth_help_url}", + "title": "\u624b\u52d5\u9023\u7dda\u81f3\u88dd\u7f6e" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roon/translations/ca.json b/homeassistant/components/roon/translations/ca.json index 3a1de2208b6033..ef32dd00e75f86 100644 --- a/homeassistant/components/roon/translations/ca.json +++ b/homeassistant/components/roon/translations/ca.json @@ -17,7 +17,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Introdueix el nom d'amfitri\u00f3 o la IP del servidor Roon" + "description": "No s'ha pogut descobrir el servidor Roon, introdueix el nom d'amfitri\u00f3 o la IP." } } } diff --git a/homeassistant/components/roon/translations/cs.json b/homeassistant/components/roon/translations/cs.json index a15e75066a9865..fd01ed1cd25777 100644 --- a/homeassistant/components/roon/translations/cs.json +++ b/homeassistant/components/roon/translations/cs.json @@ -16,8 +16,7 @@ "user": { "data": { "host": "Hostitel" - }, - "description": "Zadejte pros\u00edm n\u00e1zev hostitele nebo IP adresu va\u0161eho Roon serveru." + } } } } diff --git a/homeassistant/components/roon/translations/de.json b/homeassistant/components/roon/translations/de.json index 9918e38670acde..4416589a23ee45 100644 --- a/homeassistant/components/roon/translations/de.json +++ b/homeassistant/components/roon/translations/de.json @@ -1,8 +1,19 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { "duplicate_entry": "Dieser Host wurde bereits hinzugef\u00fcgt.", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/roon/translations/en.json b/homeassistant/components/roon/translations/en.json index 99f2b65bd13c21..b763fbb1e0c1ed 100644 --- a/homeassistant/components/roon/translations/en.json +++ b/homeassistant/components/roon/translations/en.json @@ -17,7 +17,7 @@ "data": { "host": "Host" }, - "description": "Please enter your Roon server Hostname or IP." + "description": "Could not discover Roon server, please enter your the Hostname or IP." } } } diff --git a/homeassistant/components/roon/translations/et.json b/homeassistant/components/roon/translations/et.json index dfe3ad53f48d69..e29b1ccc6c6d1c 100644 --- a/homeassistant/components/roon/translations/et.json +++ b/homeassistant/components/roon/translations/et.json @@ -17,7 +17,7 @@ "data": { "host": "" }, - "description": "Sisesta oma Rooni serveri hostinimi v\u00f5i IP." + "description": "Rooni serverit ei leitud. Sisesta oma Rooni serveri hostinimi v\u00f5i IP." } } } diff --git a/homeassistant/components/roon/translations/it.json b/homeassistant/components/roon/translations/it.json index 5f63482c3c3937..e0450af9d39198 100644 --- a/homeassistant/components/roon/translations/it.json +++ b/homeassistant/components/roon/translations/it.json @@ -17,7 +17,7 @@ "data": { "host": "Host" }, - "description": "Inserisci il nome host o l'IP del tuo server Roon." + "description": "Impossibile individuare il server Roon, inserire l'hostname o l'IP." } } } diff --git a/homeassistant/components/roon/translations/no.json b/homeassistant/components/roon/translations/no.json index 9067e2c6f5331a..e872e03a69d20c 100644 --- a/homeassistant/components/roon/translations/no.json +++ b/homeassistant/components/roon/translations/no.json @@ -17,7 +17,7 @@ "data": { "host": "Vert" }, - "description": "Vennligst skriv inn Roon-serverens vertsnavn eller IP." + "description": "Kunne ikke oppdage Roon-serveren. Angi vertsnavnet eller IP-adressen." } } } diff --git a/homeassistant/components/roon/translations/pl.json b/homeassistant/components/roon/translations/pl.json index e63c5f6b55c675..d763fc12bd26ad 100644 --- a/homeassistant/components/roon/translations/pl.json +++ b/homeassistant/components/roon/translations/pl.json @@ -17,7 +17,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Wprowad\u017a nazw\u0119 hosta lub adres IP swojego serwera Roon." + "description": "Nie wykryto serwera Roon, wprowad\u017a nazw\u0119 hosta lub adres IP." } } } diff --git a/homeassistant/components/roon/translations/ru.json b/homeassistant/components/roon/translations/ru.json index abfbea2ccde294..187151affe2f40 100644 --- a/homeassistant/components/roon/translations/ru.json +++ b/homeassistant/components/roon/translations/ru.json @@ -17,7 +17,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Roon" + "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Roon, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441." } } } diff --git a/homeassistant/components/roon/translations/tr.json b/homeassistant/components/roon/translations/tr.json new file mode 100644 index 00000000000000..97241919c9b5f1 --- /dev/null +++ b/homeassistant/components/roon/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "duplicate_entry": "Bu ana bilgisayar zaten eklendi.", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "link": { + "description": "Roon'da HomeAssistant\u0131 yetkilendirmelisiniz. G\u00f6nder'e t\u0131klad\u0131ktan sonra, Roon Core uygulamas\u0131na gidin, Ayarlar'\u0131 a\u00e7\u0131n ve Uzant\u0131lar sekmesinde HomeAssistant'\u0131 etkinle\u015ftirin.", + "title": "Roon'da HomeAssistant'\u0131 Yetkilendirme" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/uk.json b/homeassistant/components/roon/translations/uk.json new file mode 100644 index 00000000000000..91a530787ae37b --- /dev/null +++ b/homeassistant/components/roon/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "duplicate_entry": "\u0426\u0435\u0439 \u0445\u043e\u0441\u0442 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0438\u0439.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "link": { + "description": "\u041f\u0456\u0441\u043b\u044f \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u00ab\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\u00bb \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Roon Core, \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u00ab\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u00bb \u0456 \u0443\u0432\u0456\u043c\u043a\u043d\u0456\u0442\u044c HomeAssistant \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u0446\u0456 \u00ab\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u043d\u044f\u00bb.", + "title": "Roon" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u0430\u0437\u0432\u0443 \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Roon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/zh-Hant.json b/homeassistant/components/roon/translations/zh-Hant.json index f34bce445f74a9..39099753f39ed7 100644 --- a/homeassistant/components/roon/translations/zh-Hant.json +++ b/homeassistant/components/roon/translations/zh-Hant.json @@ -17,7 +17,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8acb\u8f38\u5165 Roon \u4f3a\u670d\u5668\u4e3b\u6a5f\u540d\u7a31\u6216 IP\u3002" + "description": "\u627e\u4e0d\u5230 Roon \u4f3a\u670d\u5668\uff0c\u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31\u6216 IP\u3002" } } } diff --git a/homeassistant/components/rpi_power/translations/de.json b/homeassistant/components/rpi_power/translations/de.json new file mode 100644 index 00000000000000..9f3851f0c2b304 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "confirm": { + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + } + } + }, + "title": "Raspberry Pi Stromversorgungspr\u00fcfer" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/lb.json b/homeassistant/components/rpi_power/translations/lb.json index 3e145432bae03d..e4bb73893399ab 100644 --- a/homeassistant/components/rpi_power/translations/lb.json +++ b/homeassistant/components/rpi_power/translations/lb.json @@ -3,6 +3,11 @@ "abort": { "no_devices_found": "Kann d\u00e9i Systemklass fir d\u00ebs noutwendeg Komponent net fannen, stell s\u00e9cher dass de Kernel rezent ass an d'Hardware \u00ebnnerst\u00ebtzt g\u00ebtt.", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "step": { + "confirm": { + "description": "Soll den Ariichtungs Prozess gestart ginn?" + } } }, "title": "Raspberry Pi Netzdeel Checker" diff --git a/homeassistant/components/rpi_power/translations/tr.json b/homeassistant/components/rpi_power/translations/tr.json new file mode 100644 index 00000000000000..f1dfcf16667708 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/tr.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + }, + "title": "Raspberry Pi G\u00fc\u00e7 Kayna\u011f\u0131 Denetleyicisi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/uk.json b/homeassistant/components/rpi_power/translations/uk.json new file mode 100644 index 00000000000000..b60160e1c4ec3a --- /dev/null +++ b/homeassistant/components/rpi_power/translations/uk.json @@ -0,0 +1,14 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + }, + "title": "Raspberry Pi power supply checker" +} \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/de.json b/homeassistant/components/ruckus_unleashed/translations/de.json index ae15ec058b5185..625c7372347a61 100644 --- a/homeassistant/components/ruckus_unleashed/translations/de.json +++ b/homeassistant/components/ruckus_unleashed/translations/de.json @@ -4,12 +4,14 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "host": "Host", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/ruckus_unleashed/translations/tr.json b/homeassistant/components/ruckus_unleashed/translations/tr.json new file mode 100644 index 00000000000000..40c9c39b967721 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/uk.json b/homeassistant/components/ruckus_unleashed/translations/uk.json new file mode 100644 index 00000000000000..2df11f744559db --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/de.json b/homeassistant/components/samsungtv/translations/de.json index e33542676300e3..3ba569c87db987 100644 --- a/homeassistant/components/samsungtv/translations/de.json +++ b/homeassistant/components/samsungtv/translations/de.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_configured": "Dieser Samsung TV ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr Samsung TV wird bereits ausgef\u00fchrt.", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "auth_missing": "Home Assistant ist nicht berechtigt, eine Verbindung zu diesem Samsung TV herzustellen. \u00dcberpr\u00fcfe die Einstellungen deines Fernsehger\u00e4ts, um Home Assistant zu autorisieren.", - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "not_supported": "Dieses Samsung TV-Ger\u00e4t wird derzeit nicht unterst\u00fctzt." }, "flow_title": "Samsung TV: {model}", diff --git a/homeassistant/components/samsungtv/translations/tr.json b/homeassistant/components/samsungtv/translations/tr.json index 50e6b21d120d67..6b3900e9aa5651 100644 --- a/homeassistant/components/samsungtv/translations/tr.json +++ b/homeassistant/components/samsungtv/translations/tr.json @@ -4,13 +4,17 @@ "already_configured": "Bu Samsung TV zaten ayarlanm\u0131\u015f.", "already_in_progress": "Samsung TV ayar\u0131 zaten s\u00fcr\u00fcyor.", "auth_missing": "Home Assistant'\u0131n bu Samsung TV'ye ba\u011flanma izni yok. Home Assistant'\u0131 yetkilendirmek i\u00e7in l\u00fctfen TV'nin ayarlar\u0131n\u0131 kontrol et.", + "cannot_connect": "Ba\u011flanma hatas\u0131", "not_supported": "Bu Samsung TV cihaz\u0131 \u015fu anda desteklenmiyor." }, "flow_title": "Samsung TV: {model}", "step": { + "confirm": { + "title": "Samsung TV" + }, "user": { "data": { - "host": "Host veya IP adresi", + "host": "Ana Bilgisayar", "name": "Ad" }, "description": "Samsung TV bilgilerini gir. Daha \u00f6nce hi\u00e7 Home Assistant'a ba\u011flamad\u0131ysan, TV'nde izin isteyen bir pencere g\u00f6receksindir." diff --git a/homeassistant/components/samsungtv/translations/uk.json b/homeassistant/components/samsungtv/translations/uk.json new file mode 100644 index 00000000000000..83bb18e76f172c --- /dev/null +++ b/homeassistant/components/samsungtv/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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_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.", + "auth_missing": "Home Assistant \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "not_supported": "\u0426\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u0432 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "flow_title": "Samsung TV: {model}", + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung {model}? \u042f\u043a\u0449\u043e \u0446\u0435\u0439 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0440\u0430\u043d\u0456\u0448\u0435 \u043d\u0435 \u0431\u0443\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e Home Assistant, \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043c\u0430\u0454 \u0437'\u044f\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u043f\u043b\u0438\u0432\u0430\u044e\u0447\u0435 \u0432\u0456\u043a\u043d\u043e \u0456\u0437 \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430, \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0456 \u0432\u0440\u0443\u0447\u043d\u0443, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0456.", + "title": "\u0422\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung. \u042f\u043a\u0449\u043e \u0446\u0435\u0439 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0440\u0430\u043d\u0456\u0448\u0435 \u043d\u0435 \u0431\u0443\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e Home Assistant, \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043c\u0430\u0454 \u0437'\u044f\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u043f\u043b\u0438\u0432\u0430\u044e\u0447\u0435 \u0432\u0456\u043a\u043d\u043e \u0456\u0437 \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/script/translations/uk.json b/homeassistant/components/script/translations/uk.json index bfff0258c6643c..ee494e264ae40a 100644 --- a/homeassistant/components/script/translations/uk.json +++ b/homeassistant/components/script/translations/uk.json @@ -5,5 +5,5 @@ "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } }, - "title": "\u0421\u0446\u0435\u043d\u0430\u0440\u0456\u0439" + "title": "\u0421\u043a\u0440\u0438\u043f\u0442" } \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.uk.json b/homeassistant/components/season/translations/sensor.uk.json index 2c694e287b1566..fa79d3cff07f51 100644 --- a/homeassistant/components/season/translations/sensor.uk.json +++ b/homeassistant/components/season/translations/sensor.uk.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "\u041e\u0441\u0456\u043d\u044c", + "spring": "\u0412\u0435\u0441\u043d\u0430", + "summer": "\u041b\u0456\u0442\u043e", + "winter": "\u0417\u0438\u043c\u0430" + }, "season__season__": { "autumn": "\u041e\u0441\u0456\u043d\u044c", "spring": "\u0412\u0435\u0441\u043d\u0430", diff --git a/homeassistant/components/sense/translations/de.json b/homeassistant/components/sense/translations/de.json index de9e6877f25a27..9d4845ece79fa1 100644 --- a/homeassistant/components/sense/translations/de.json +++ b/homeassistant/components/sense/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/sense/translations/tr.json b/homeassistant/components/sense/translations/tr.json new file mode 100644 index 00000000000000..0e335265325683 --- /dev/null +++ b/homeassistant/components/sense/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/uk.json b/homeassistant/components/sense/translations/uk.json new file mode 100644 index 00000000000000..8eac9c9d4abebf --- /dev/null +++ b/homeassistant/components/sense/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "title": "Sense Energy Monitor" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/tr.json b/homeassistant/components/sensor/translations/tr.json index 3bf1ba6f3682f0..feca40991eee3a 100644 --- a/homeassistant/components/sensor/translations/tr.json +++ b/homeassistant/components/sensor/translations/tr.json @@ -1,4 +1,31 @@ { + "device_automation": { + "condition_type": { + "is_current": "Mevcut {entity_name} ak\u0131m\u0131", + "is_energy": "Mevcut {entity_name} enerjisi", + "is_power_factor": "Mevcut {entity_name} g\u00fc\u00e7 fakt\u00f6r\u00fc", + "is_signal_strength": "Mevcut {entity_name} sinyal g\u00fcc\u00fc", + "is_temperature": "Mevcut {entity_name} s\u0131cakl\u0131\u011f\u0131", + "is_timestamp": "Mevcut {entity_name} zaman damgas\u0131", + "is_value": "Mevcut {entity_name} de\u011feri", + "is_voltage": "Mevcut {entity_name} voltaj\u0131" + }, + "trigger_type": { + "battery_level": "{entity_name} pil seviyesi de\u011fi\u015fiklikleri", + "current": "{entity_name} ak\u0131m de\u011fi\u015fiklikleri", + "energy": "{entity_name} enerji de\u011fi\u015fiklikleri", + "humidity": "{entity_name} nem de\u011fi\u015fiklikleri", + "illuminance": "{entity_name} ayd\u0131nlatma de\u011fi\u015fiklikleri", + "power": "{entity_name} g\u00fc\u00e7 de\u011fi\u015fiklikleri", + "power_factor": "{entity_name} g\u00fc\u00e7 fakt\u00f6r\u00fc de\u011fi\u015fiklikleri", + "pressure": "{entity_name} bas\u0131n\u00e7 de\u011fi\u015fiklikleri", + "signal_strength": "{entity_name} sinyal g\u00fcc\u00fc de\u011fi\u015fiklikleri", + "temperature": "{entity_name} s\u0131cakl\u0131k de\u011fi\u015fiklikleri", + "timestamp": "{entity_name} zaman damgas\u0131 de\u011fi\u015fiklikleri", + "value": "{entity_name} de\u011fer de\u011fi\u015fiklikleri", + "voltage": "{entity_name} voltaj de\u011fi\u015fiklikleri" + } + }, "state": { "_": { "off": "Kapal\u0131", diff --git a/homeassistant/components/sensor/translations/uk.json b/homeassistant/components/sensor/translations/uk.json index 391415409f5bd1..9e6148c3b8c636 100644 --- a/homeassistant/components/sensor/translations/uk.json +++ b/homeassistant/components/sensor/translations/uk.json @@ -1,7 +1,34 @@ { "device_automation": { "condition_type": { - "is_battery_level": "\u041f\u043e\u0442\u043e\u0447\u043d\u0438\u0439 \u0440\u0456\u0432\u0435\u043d\u044c \u0437\u0430\u0440\u044f\u0434\u0443 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430 {entity_name}" + "is_battery_level": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_current": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0441\u0438\u043b\u0438 \u0441\u0442\u0440\u0443\u043c\u0443", + "is_energy": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "is_humidity": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_illuminance": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_power": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_power_factor": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043a\u043e\u0435\u0444\u0456\u0446\u0456\u0454\u043d\u0442\u0430 \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "is_pressure": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_signal_strength": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_temperature": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_timestamp": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_value": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_voltage": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430\u043f\u0440\u0443\u0433\u0438" + }, + "trigger_type": { + "battery_level": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "current": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0441\u0438\u043b\u0438 \u0441\u0442\u0440\u0443\u043c\u0443", + "energy": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "humidity": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "illuminance": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "power": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "power_factor": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u043a\u043e\u0435\u0444\u0456\u0446\u0456\u0454\u043d\u0442 \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "pressure": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "signal_strength": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "temperature": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "timestamp": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "value": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "voltage": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430\u043f\u0440\u0443\u0433\u0438" } }, "state": { @@ -10,5 +37,5 @@ "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } }, - "title": "\u0414\u0430\u0442\u0447\u0438\u043a" + "title": "\u0421\u0435\u043d\u0441\u043e\u0440" } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/de.json b/homeassistant/components/sentry/translations/de.json index c36bbf258b08f7..8fbcfc1eaa2cc7 100644 --- a/homeassistant/components/sentry/translations/de.json +++ b/homeassistant/components/sentry/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { "bad_dsn": "Ung\u00fcltiger DSN", "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/sentry/translations/tr.json b/homeassistant/components/sentry/translations/tr.json new file mode 100644 index 00000000000000..4dab23fbd949f9 --- /dev/null +++ b/homeassistant/components/sentry/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "bad_dsn": "Ge\u00e7ersiz DSN", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "dsn": "DSN" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Ortam\u0131n iste\u011fe ba\u011fl\u0131 ad\u0131." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/uk.json b/homeassistant/components/sentry/translations/uk.json new file mode 100644 index 00000000000000..01da0308851d37 --- /dev/null +++ b/homeassistant/components/sentry/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "bad_dsn": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 DSN.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "dsn": "DSN" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448 DSN Sentry", + "title": "Sentry" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "\u041d\u0430\u0437\u0432\u0430", + "event_custom_components": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u0437 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0456\u0432", + "event_handled": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u043e\u0431\u0440\u043e\u0431\u043b\u0435\u043d\u0456 \u043f\u043e\u0434\u0456\u0457", + "event_third_party_packages": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u0437 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0456\u0445 \u043f\u0430\u043a\u0435\u0442\u0456\u0432", + "logging_event_level": "\u0417\u0430\u043f\u0438\u0441\u0443\u0432\u0430\u0442\u0438 \u0436\u0443\u0440\u043d\u0430\u043b\u0438 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0445 \u043f\u043e\u0434\u0456\u0439", + "logging_level": "\u0417\u0430\u043f\u0438\u0441\u0443\u0432\u0430\u0442\u0438 \u0436\u0443\u0440\u043d\u0430\u043b\u0438 \u0443 \u0432\u0438\u0433\u043b\u044f\u0434\u0456 \u043d\u0430\u0432\u0456\u0433\u0430\u0446\u0456\u0439\u043d\u0438\u0445 \u043b\u0430\u043d\u0446\u044e\u0436\u043a\u0456\u0432", + "tracing": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0456", + "tracing_sample_rate": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u0438\u0437\u0430\u0446\u0456\u0457 \u0442\u0440\u0430\u0441\u0443\u0432\u0430\u043d\u043d\u044f; \u0432\u0456\u0434 0,0 \u0434\u043e 1,0 (1,0 = 100%)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/de.json b/homeassistant/components/sharkiq/translations/de.json index 2294960d6f261d..8a6f9b14747b5b 100644 --- a/homeassistant/components/sharkiq/translations/de.json +++ b/homeassistant/components/sharkiq/translations/de.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/sharkiq/translations/fr.json b/homeassistant/components/sharkiq/translations/fr.json index 5f05292ec2a8bb..6fa3ba7707cfb4 100644 --- a/homeassistant/components/sharkiq/translations/fr.json +++ b/homeassistant/components/sharkiq/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s", "unknown": "Erreur inattendue" diff --git a/homeassistant/components/sharkiq/translations/tr.json b/homeassistant/components/sharkiq/translations/tr.json new file mode 100644 index 00000000000000..c82f1e8bf051d7 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/uk.json b/homeassistant/components/sharkiq/translations/uk.json new file mode 100644 index 00000000000000..0f78c62fa7ecea --- /dev/null +++ b/homeassistant/components/sharkiq/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \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", + "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", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ca.json b/homeassistant/components/shelly/translations/ca.json index 2bf17c2ba77c28..c2df82c0b16961 100644 --- a/homeassistant/components/shelly/translations/ca.json +++ b/homeassistant/components/shelly/translations/ca.json @@ -27,5 +27,21 @@ "description": "Abans de configurar-lo, els dispositius amb bateria s'han de desperar prement el bot\u00f3 del dispositiu." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Bot\u00f3", + "button1": "Primer bot\u00f3", + "button2": "Segon bot\u00f3", + "button3": "Tercer bot\u00f3" + }, + "trigger_type": { + "double": "{subtype} clicat dues vegades", + "long": "{subtype} clicat durant una estona", + "long_single": "{subtype} clicat durant una estona i despr\u00e9s r\u00e0pid", + "single": "{subtype} clicat una vegada", + "single_long": "{subtype} clicat r\u00e0pid i, despr\u00e9s, durant una estona", + "triple": "{subtype} clicat tres vegades" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/cs.json b/homeassistant/components/shelly/translations/cs.json index 41ca338ab9e6ab..afdfe7c8f56ab7 100644 --- a/homeassistant/components/shelly/translations/cs.json +++ b/homeassistant/components/shelly/translations/cs.json @@ -27,5 +27,21 @@ "description": "P\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." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Tla\u010d\u00edtko", + "button1": "Prvn\u00ed tla\u010d\u00edtko", + "button2": "Druh\u00e9 tla\u010d\u00edtko", + "button3": "T\u0159et\u00ed tla\u010d\u00edtko" + }, + "trigger_type": { + "double": "\"{subtype}\" stisknuto dvakr\u00e1t", + "long": "\"{subtype}\" stisknuto dlouze", + "long_single": "\"{subtype}\" stisknuto dlouze a pak jednou", + "single": "\"{subtype}\" stisknuto jednou", + "single_long": "\"{subtype}\" stisknuto jednou a pak dlouze", + "triple": "\"{subtype}\" stisknuto t\u0159ikr\u00e1t" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/da.json b/homeassistant/components/shelly/translations/da.json new file mode 100644 index 00000000000000..08631bc39e132e --- /dev/null +++ b/homeassistant/components/shelly/translations/da.json @@ -0,0 +1,17 @@ +{ + "device_automation": { + "trigger_subtype": { + "button": "Knap", + "button1": "F\u00f8rste knap", + "button2": "Anden knap", + "button3": "Tredje knap" + }, + "trigger_type": { + "double": "{subtype} dobbelt klik", + "long": "{subtype} langt klik", + "long_single": "{subtype} langt klik og derefter enkelt klik", + "single": "{subtype} enkelt klik", + "single_long": "{subtype} enkelt klik og derefter langt klik" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/de.json b/homeassistant/components/shelly/translations/de.json index 74d0f831c8bd33..4764936a41bd26 100644 --- a/homeassistant/components/shelly/translations/de.json +++ b/homeassistant/components/shelly/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Shelly: {name}", @@ -15,7 +19,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Vor der Einrichtung m\u00fcssen batteriebetriebene Ger\u00e4te durch Dr\u00fccken der Taste am Ger\u00e4t aufgeweckt werden." } } } diff --git a/homeassistant/components/shelly/translations/en.json b/homeassistant/components/shelly/translations/en.json index a1fa6b72598eb1..a9ad6092a08cfa 100644 --- a/homeassistant/components/shelly/translations/en.json +++ b/homeassistant/components/shelly/translations/en.json @@ -31,17 +31,17 @@ "device_automation": { "trigger_subtype": { "button": "Button", - "button1": "First button", + "button1": "First button", "button2": "Second button", "button3": "Third button" }, - "trigger_type": { - "single": "{subtype} single clicked", - "double": "{subtype} double clicked", - "triple": "{subtype} triple clicked", - "long":" {subtype} long clicked", - "single_long": "{subtype} single clicked and then long clicked", - "long_single": "{subtype} long clicked and then single clicked" + "trigger_type": { + "double": "{subtype} double clicked", + "long": " {subtype} long clicked", + "long_single": "{subtype} long clicked and then single clicked", + "single": "{subtype} single clicked", + "single_long": "{subtype} single clicked and then long clicked", + "triple": "{subtype} triple clicked" } } -} +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/es.json b/homeassistant/components/shelly/translations/es.json index 38c72f21dca04b..09cc3f51378445 100644 --- a/homeassistant/components/shelly/translations/es.json +++ b/homeassistant/components/shelly/translations/es.json @@ -27,5 +27,21 @@ "description": "Antes de configurarlo, el dispositivo que funciona con bater\u00eda debe despertarse presionando el bot\u00f3n del dispositivo." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Bot\u00f3n", + "button1": "Primer bot\u00f3n", + "button2": "Segundo bot\u00f3n", + "button3": "Tercer bot\u00f3n" + }, + "trigger_type": { + "double": "Pulsaci\u00f3n doble de {subtype}", + "long": "Pulsaci\u00f3n larga de {subtype}", + "long_single": "Pulsaci\u00f3n larga de {subtype} seguida de una pulsaci\u00f3n simple", + "single": "Pulsaci\u00f3n simple de {subtype}", + "single_long": "Pulsaci\u00f3n simple de {subtype} seguida de una pulsaci\u00f3n larga", + "triple": "Pulsaci\u00f3n triple de {subtype}" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/et.json b/homeassistant/components/shelly/translations/et.json index 12a662f6560a76..d2514876a81223 100644 --- a/homeassistant/components/shelly/translations/et.json +++ b/homeassistant/components/shelly/translations/et.json @@ -27,5 +27,21 @@ "description": "Enne seadistamist tuleb akutoitega seade \u00e4ratada vajutades seadme nuppu." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Nupp", + "button1": "Esimene nupp", + "button2": "Teine nupp", + "button3": "Kolmas nupp" + }, + "trigger_type": { + "double": "Nuppu {subtype} topeltkl\u00f5psati", + "long": "Nuppu \"{subtype}\" hoiti all", + "long_single": "Nuppu {subtype} hoiti all ja seej\u00e4rel kl\u00f5psati", + "single": "Nuppu {subtype} kl\u00f5psati", + "single_long": "Nuppu {subtype} kl\u00f5psati \u00fcks kord ja seej\u00e4rel hoiti all", + "triple": "Nuppu {subtype} kl\u00f5psati kolm korda" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/it.json b/homeassistant/components/shelly/translations/it.json index 61f2f8ccd09661..4d486a8f2fab4c 100644 --- a/homeassistant/components/shelly/translations/it.json +++ b/homeassistant/components/shelly/translations/it.json @@ -27,5 +27,21 @@ "description": "Prima della configurazione, i dispositivi alimentati a batteria devono essere riattivati premendo il pulsante sul dispositivo." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Pulsante", + "button1": "Primo pulsante", + "button2": "Secondo pulsante", + "button3": "Terzo pulsante" + }, + "trigger_type": { + "double": "{subtype} premuto due volte", + "long": "{subtype} premuto a lungo", + "long_single": "{subtype} premuto a lungo e poi singolarmente", + "single": "{subtype} premuto singolarmente", + "single_long": "{subtype} premuto singolarmente e poi a lungo", + "triple": "{subtype} premuto tre volte" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/lb.json b/homeassistant/components/shelly/translations/lb.json index b358c1c728269f..e6c5d8330c674e 100644 --- a/homeassistant/components/shelly/translations/lb.json +++ b/homeassistant/components/shelly/translations/lb.json @@ -27,5 +27,13 @@ "description": "Virum ariichten muss dat Batterie bedriwwen Ger\u00e4t aktiv\u00e9iert ginn andeems de Kn\u00e4ppchen um Apparat gedr\u00e9ckt g\u00ebtt." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Kn\u00e4ppchen", + "button1": "\u00c9ischte Kn\u00e4ppchen", + "button2": "Zweete Kn\u00e4ppchen", + "button3": "Dr\u00ebtte Kn\u00e4ppchen" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/no.json b/homeassistant/components/shelly/translations/no.json index 705c494a4c11b3..1606a1acbb1bcc 100644 --- a/homeassistant/components/shelly/translations/no.json +++ b/homeassistant/components/shelly/translations/no.json @@ -27,5 +27,21 @@ "description": "F\u00f8r du setter opp, m\u00e5 batteridrevne enheter vekkes ved \u00e5 trykke p\u00e5 knappen p\u00e5 enheten." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Knapp", + "button1": "F\u00f8rste knapp", + "button2": "Andre knapp", + "button3": "Tredje knapp" + }, + "trigger_type": { + "double": "{subtype} dobbeltklikket", + "long": "{subtype} lenge klikket", + "long_single": "{subtype} lengre klikk og deretter et enkeltklikk", + "single": "{subtype} enkeltklikket", + "single_long": "{subtype} enkeltklikket og deretter et lengre klikk", + "triple": "{subtype} trippelklikket" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index ebf6041d4bac8f..cd8ffac71386e4 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -27,5 +27,21 @@ "description": "Przed skonfigurowaniem urz\u0105dzenia zasilane bateryjnie nale\u017cy, wybudzi\u0107 naciskaj\u0105c przycisk na urz\u0105dzeniu." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Przycisk", + "button1": "pierwszy", + "button2": "drugi", + "button3": "trzeci" + }, + "trigger_type": { + "double": "przycisk \"{subtype}\" zostanie dwukrotnie naci\u015bni\u0119ty", + "long": "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", + "triple": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index 508a189b849771..5a3a40ac9f8cfb 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -27,5 +27,21 @@ "description": "\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "\u041a\u043d\u043e\u043f\u043a\u0430", + "button1": "\u041f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button2": "\u0412\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button3": "\u0422\u0440\u0435\u0442\u044c\u044f \u043a\u043d\u043e\u043f\u043a\u0430" + }, + "trigger_type": { + "double": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430", + "long": "{subtype} \u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0430", + "long_single": "{subtype} \u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0430 \u0438 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437", + "single": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437", + "single_long": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0438 \u0437\u0430\u0442\u0435\u043c \u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0430", + "triple": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0430" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/tr.json b/homeassistant/components/shelly/translations/tr.json new file mode 100644 index 00000000000000..f577c73787f521 --- /dev/null +++ b/homeassistant/components/shelly/translations/tr.json @@ -0,0 +1,41 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "{name}", + "step": { + "credentials": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "D\u00fc\u011fme", + "button1": "\u0130lk d\u00fc\u011fme", + "button2": "\u0130kinci d\u00fc\u011fme", + "button3": "\u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fme" + }, + "trigger_type": { + "double": "{subtype} \u00e7ift t\u0131kland\u0131", + "long": "{subtype} uzun t\u0131kland\u0131", + "long_single": "{subtype} uzun t\u0131kland\u0131 ve ard\u0131ndan tek t\u0131kland\u0131", + "single": "{subtype} tek t\u0131kland\u0131", + "triple": "{subtype} \u00fc\u00e7 kez t\u0131kland\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/uk.json b/homeassistant/components/shelly/translations/uk.json new file mode 100644 index 00000000000000..7ad70b0f0da915 --- /dev/null +++ b/homeassistant/components/shelly/translations/uk.json @@ -0,0 +1,47 @@ +{ + "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.", + "unsupported_firmware": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454 \u043d\u0435\u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0443 \u0432\u0435\u0440\u0441\u0456\u044e \u043c\u0456\u043a\u0440\u043e\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "{name}", + "step": { + "confirm_discovery": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 {model} ({host})? \n\n\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457, \u0449\u043e \u043f\u0440\u0430\u0446\u044e\u044e\u0442\u044c \u0432\u0456\u0434 \u0431\u0430\u0442\u0430\u0440\u0435\u0457, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0432\u0435\u0441\u0442\u0438 \u0437\u0456 \u0441\u043f\u043b\u044f\u0447\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0443, \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0432\u0448\u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457." + }, + "credentials": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457, \u0449\u043e \u043f\u0440\u0430\u0446\u044e\u044e\u0442\u044c \u0432\u0456\u0434 \u0431\u0430\u0442\u0430\u0440\u0435\u0457, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0432\u0435\u0441\u0442\u0438 \u0437\u0456 \u0441\u043f\u043b\u044f\u0447\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0443, \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0432\u0448\u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "\u041a\u043d\u043e\u043f\u043a\u0430", + "button1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430" + }, + "trigger_type": { + "double": "{subtype} \u043f\u043e\u0434\u0432\u0456\u0439\u043d\u0438\u0439 \u043a\u043b\u0456\u043a", + "long": "{subtype} \u0434\u043e\u0432\u0433\u0438\u0439 \u043a\u043b\u0456\u043a", + "long_single": "{subtype} \u0434\u043e\u0432\u0433\u0438\u0439 \u043a\u043b\u0456\u043a, \u0430 \u043f\u043e\u0442\u0456\u043c \u043e\u0434\u0438\u043d \u043a\u043b\u0456\u043a", + "single": "{subtype} \u043e\u0434\u0438\u043d\u0430\u0440\u043d\u0438\u0439 \u043a\u043b\u0456\u043a", + "single_long": "{subtype} \u043e\u0434\u0438\u043d\u0430\u0440\u043d\u0438\u0439 \u043a\u043b\u0456\u043a, \u043f\u043e\u0442\u0456\u043c \u0434\u043e\u0432\u0433\u0438\u0439 \u043a\u043b\u0456\u043a", + "triple": "{subtype} \u043f\u043e\u0442\u0440\u0456\u0439\u043d\u0438\u0439 \u043a\u043b\u0456\u043a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/zh-Hant.json b/homeassistant/components/shelly/translations/zh-Hant.json index bf0150523b3e39..8f3152081354cc 100644 --- a/homeassistant/components/shelly/translations/zh-Hant.json +++ b/homeassistant/components/shelly/translations/zh-Hant.json @@ -27,5 +27,21 @@ "description": "\u958b\u59cb\u8a2d\u5b9a\u524d\uff0c\u5fc5\u9808\u6309\u4e0b\u88dd\u7f6e\u4e0a\u7684\u6309\u9215\u4ee5\u559a\u9192\u96fb\u6c60\u4f9b\u96fb\u88dd\u7f6e\u3002" } } + }, + "device_automation": { + "trigger_subtype": { + "button": "\u6309\u9215", + "button1": "\u7b2c\u4e00\u500b\u6309\u9215", + "button2": "\u7b2c\u4e8c\u500b\u6309\u9215", + "button3": "\u7b2c\u4e09\u500b\u6309\u9215" + }, + "trigger_type": { + "double": "{subtype} \u96d9\u64ca", + "long": "{subtype} \u9577\u6309", + "long_single": "{subtype} \u9577\u6309\u5f8c\u55ae\u64ca", + "single": "{subtype} \u55ae\u64ca", + "single_long": "{subtype} \u55ae\u64ca\u5f8c\u9577\u6309", + "triple": "{subtype} \u4e09\u9023\u64ca" + } } } \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/de.json b/homeassistant/components/shopping_list/translations/de.json index d2d6a42fe249a8..68372e9f4ac8df 100644 --- a/homeassistant/components/shopping_list/translations/de.json +++ b/homeassistant/components/shopping_list/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Die Einkaufsliste ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/tr.json b/homeassistant/components/shopping_list/translations/tr.json new file mode 100644 index 00000000000000..d139d2f6399971 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "description": "Al\u0131\u015fveri\u015f listesini yap\u0131land\u0131rmak istiyor musunuz?", + "title": "Al\u0131\u015fveri\u015f listesi" + } + } + }, + "title": "Al\u0131\u015fveri\u015f listesi" +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/uk.json b/homeassistant/components/shopping_list/translations/uk.json new file mode 100644 index 00000000000000..b73bd6c702a048 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/uk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a?", + "title": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a" + } + } + }, + "title": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a" +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/de.json b/homeassistant/components/simplisafe/translations/de.json index ab05cf649d8334..5914e8f680c83b 100644 --- a/homeassistant/components/simplisafe/translations/de.json +++ b/homeassistant/components/simplisafe/translations/de.json @@ -1,17 +1,21 @@ { "config": { "abort": { - "already_configured": "Dieses SimpliSafe-Konto wird bereits verwendet." + "already_configured": "Dieses SimpliSafe-Konto wird bereits verwendet.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "identifier_exists": "Konto bereits registriert", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "reauth_confirm": { "data": { "password": "Passwort" - } + }, + "description": "Dein Zugriffstoken ist abgelaufen oder wurde widerrufen. Gib dein Passwort ein, um dein Konto erneut zu verkn\u00fcpfen.", + "title": "Integration erneut authentifizieren" }, "user": { "data": { diff --git a/homeassistant/components/simplisafe/translations/tr.json b/homeassistant/components/simplisafe/translations/tr.json index ec84b1b7c1c51b..94506fb426bba8 100644 --- a/homeassistant/components/simplisafe/translations/tr.json +++ b/homeassistant/components/simplisafe/translations/tr.json @@ -1,6 +1,26 @@ { "config": { + "abort": { + "already_configured": "Bu SimpliSafe hesab\u0131 zaten kullan\u0131mda.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "still_awaiting_mfa": "Hala MFA e-posta t\u0131klamas\u0131 bekleniyor", + "unknown": "Beklenmeyen hata" + }, "step": { + "mfa": { + "description": "SimpliSafe'den bir ba\u011flant\u0131 i\u00e7in e-postan\u0131z\u0131 kontrol edin. Ba\u011flant\u0131y\u0131 do\u011frulad\u0131ktan sonra, entegrasyonun kurulumunu tamamlamak i\u00e7in buraya geri d\u00f6n\u00fcn.", + "title": "SimpliSafe \u00c7ok Fakt\u00f6rl\u00fc Kimlik Do\u011frulama" + }, + "reauth_confirm": { + "data": { + "password": "Parola" + }, + "description": "Eri\u015fim kodunuzun s\u00fcresi doldu veya iptal edildi. Hesab\u0131n\u0131z\u0131 yeniden ba\u011flamak i\u00e7in parolan\u0131z\u0131 girin.", + "title": "Entegrasyonu Yeniden Do\u011frula" + }, "user": { "data": { "password": "Parola", diff --git a/homeassistant/components/simplisafe/translations/uk.json b/homeassistant/components/simplisafe/translations/uk.json index 376fb4468dbf48..0a51f129e5fd26 100644 --- a/homeassistant/components/simplisafe/translations/uk.json +++ b/homeassistant/components/simplisafe/translations/uk.json @@ -1,18 +1,45 @@ { "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "error": { + "identifier_exists": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "still_awaiting_mfa": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u043e \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0456\u0439 \u043f\u043e\u0448\u0442\u0456.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, "step": { + "mfa": { + "description": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0441\u0432\u043e\u044e \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443 \u043f\u043e\u0448\u0442\u0443 \u043d\u0430 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0432\u0456\u0434 SimpliSafe. \u041f\u0456\u0441\u043b\u044f \u0442\u043e\u0433\u043e \u044f\u043a \u0432\u0456\u0434\u043a\u0440\u0438\u0454\u0442\u0435 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f, \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438, \u0449\u043e\u0431 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f SimpliSafe" + }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" - } + }, + "description": "\u0412\u0430\u0448 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0437\u0430\u043a\u0456\u043d\u0447\u0438\u0432\u0441\u044f \u0430\u0431\u043e \u0431\u0443\u0432 \u0430\u043d\u0443\u043b\u044c\u043e\u0432\u0430\u043d\u0438\u0439. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u0449\u043e\u0431 \u0437\u0430\u043d\u043e\u0432\u043e \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" }, "user": { "data": { + "code": "\u041a\u043e\u0434 (\u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Home Assistant)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" }, "title": "\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e" } } + }, + "options": { + "step": { + "init": { + "data": { + "code": "\u041a\u043e\u0434 (\u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Home Assistant)" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f SimpliSafe" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/de.json b/homeassistant/components/smappee/translations/de.json index a609492f4285ae..15fd8d6cd22da1 100644 --- a/homeassistant/components/smappee/translations/de.json +++ b/homeassistant/components/smappee/translations/de.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler" + "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "cannot_connect": "Verbindung fehlgeschlagen", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "flow_title": "Smappee: {name}", "step": { @@ -9,6 +13,14 @@ "data": { "environment": "Umgebung" } + }, + "local": { + "data": { + "host": "Host" + } + }, + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/smappee/translations/tr.json b/homeassistant/components/smappee/translations/tr.json new file mode 100644 index 00000000000000..4ba8a4da9a6ca5 --- /dev/null +++ b/homeassistant/components/smappee/translations/tr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_configured_local_device": "Yerel ayg\u0131t (lar) zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. L\u00fctfen bir bulut cihaz\u0131n\u0131 yap\u0131land\u0131rmadan \u00f6nce bunlar\u0131 kald\u0131r\u0131n.", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_mdns": "Smappee entegrasyonu i\u00e7in desteklenmeyen cihaz." + }, + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "\u00c7evre" + } + }, + "local": { + "data": { + "host": "Ana Bilgisayar" + } + }, + "zeroconf_confirm": { + "title": "Smappee cihaz\u0131 bulundu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/uk.json b/homeassistant/components/smappee/translations/uk.json new file mode 100644 index 00000000000000..a268fa82eacd3f --- /dev/null +++ b/homeassistant/components/smappee/translations/uk.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured_device": "\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_local_device": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435 \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432. \u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0457\u0445 \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0445\u043c\u0430\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_mdns": "\u041d\u0435\u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439.", + "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." + }, + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "\u041e\u0442\u043e\u0447\u0435\u043d\u043d\u044f" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Smappee." + }, + "local": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430, \u0449\u043e\u0431 \u043f\u043e\u0447\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u0437 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0438\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Smappee" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Smappee \u0437 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serialnumber}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Smappee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/de.json b/homeassistant/components/smart_meter_texas/translations/de.json index 382156757010d5..0eee2778d05365 100644 --- a/homeassistant/components/smart_meter_texas/translations/de.json +++ b/homeassistant/components/smart_meter_texas/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/smart_meter_texas/translations/tr.json b/homeassistant/components/smart_meter_texas/translations/tr.json new file mode 100644 index 00000000000000..6ed28a58c793c2 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "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/uk.json b/homeassistant/components/smart_meter_texas/translations/uk.json new file mode 100644 index 00000000000000..49bceaa3f6ea94 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/de.json b/homeassistant/components/smarthab/translations/de.json index 2c76c4d56db9a0..18bb2c77047dc1 100644 --- a/homeassistant/components/smarthab/translations/de.json +++ b/homeassistant/components/smarthab/translations/de.json @@ -1,6 +1,7 @@ { "config": { "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/smarthab/translations/tr.json b/homeassistant/components/smarthab/translations/tr.json new file mode 100644 index 00000000000000..98da6384f8d2e4 --- /dev/null +++ b/homeassistant/components/smarthab/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + }, + "title": "SmartHab'\u0131 kurun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/uk.json b/homeassistant/components/smarthab/translations/uk.json new file mode 100644 index 00000000000000..036ec0a78d4d84 --- /dev/null +++ b/homeassistant/components/smarthab/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "service": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0441\u043f\u0440\u043e\u0431\u0456 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e SmartHab. \u0421\u0435\u0440\u0432\u0456\u0441 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0417 \u0442\u0435\u0445\u043d\u0456\u0447\u043d\u0438\u0445 \u043f\u0440\u0438\u0447\u0438\u043d \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0438\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0434\u043b\u044f Home Assistant. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0457\u0457 \u0432 \u0434\u043e\u0434\u0430\u0442\u043a\u0443 SmartHab.", + "title": "SmartHab" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/tr.json b/homeassistant/components/smartthings/translations/tr.json new file mode 100644 index 00000000000000..5e7463c1c7443e --- /dev/null +++ b/homeassistant/components/smartthings/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "webhook_error": "SmartThings, webhook URL'sini do\u011frulayamad\u0131. L\u00fctfen webhook URL'sinin internetten eri\u015filebilir oldu\u011fundan emin olun ve tekrar deneyin." + }, + "step": { + "pat": { + "data": { + "access_token": "Eri\u015fim Belirteci" + } + }, + "select_location": { + "title": "Konum Se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/uk.json b/homeassistant/components/smartthings/translations/uk.json new file mode 100644 index 00000000000000..6f8a0ed47446ec --- /dev/null +++ b/homeassistant/components/smartthings/translations/uk.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "invalid_webhook_url": "Webhook URL, \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c \u0432\u0456\u0434 SmartThings, \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439:\n > {webhook_url} \n\n\u041e\u043d\u043e\u0432\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e \u0434\u043e [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439] ({component_url}), \u0430 \u043f\u0456\u0441\u043b\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0443 Home Assistant \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "no_available_locations": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0456\u0441\u0446\u044c \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f SmartThings." + }, + "error": { + "app_setup_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 SmartApp. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "token_forbidden": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u0438\u0439 \u0434\u043b\u044f OAuth.", + "token_invalid_format": "\u0422\u043e\u043a\u0435\u043d \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 UID / GUID.", + "token_unauthorized": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u0430\u0431\u043e \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u0438\u0439.", + "webhook_error": "SmartThings \u043d\u0435 \u043c\u043e\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 Webhook URL. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 Webhook URL \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0456\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." + }, + "step": { + "authorize": { + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f Home Assistant" + }, + "pat": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c [\u041e\u0441\u043e\u0431\u0438\u0441\u0442\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 SmartThings] ({token_url}), \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e \u0434\u043e [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457] ({component_url}).", + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "select_location": { + "data": { + "location_id": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0446\u0435 \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f SmartThings, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0432 Home Assistant. \u041f\u0456\u0441\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0432\u0456\u0434\u043a\u0440\u0438\u0454\u0442\u044c\u0441\u044f \u043d\u043e\u0432\u0435 \u0432\u0456\u043a\u043d\u043e, \u0434\u0435 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0431\u0443\u0434\u0435 \u0443\u0432\u0456\u0439\u0442\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0442\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Home Assistant \u0432 \u043e\u0431\u0440\u0430\u043d\u043e\u043c\u0443 \u043c\u0456\u0441\u0446\u0456 \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f.", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "user": { + "description": "SmartThings \u0431\u0443\u0434\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 push-\u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e:\n> {webhook_url} \n\n\u042f\u043a\u0449\u043e \u0446\u0435 \u043d\u0435 \u0442\u0430\u043a, \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c Home Assistant \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f Callback URL" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/uk.json b/homeassistant/components/smhi/translations/uk.json new file mode 100644 index 00000000000000..24af32172baf37 --- /dev/null +++ b/homeassistant/components/smhi/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", + "wrong_location": "\u0422\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0428\u0432\u0435\u0446\u0456\u0457." + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432 \u0428\u0432\u0435\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/de.json b/homeassistant/components/sms/translations/de.json index 1252313a438e23..b262df1486d24e 100644 --- a/homeassistant/components/sms/translations/de.json +++ b/homeassistant/components/sms/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/sms/translations/tr.json b/homeassistant/components/sms/translations/tr.json new file mode 100644 index 00000000000000..1ef2efb8121651 --- /dev/null +++ b/homeassistant/components/sms/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "title": "Modeme ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/uk.json b/homeassistant/components/sms/translations/uk.json new file mode 100644 index 00000000000000..be271a2b6e4896 --- /dev/null +++ b/homeassistant/components/sms/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "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": { + "device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/fr.json b/homeassistant/components/solaredge/translations/fr.json index f8aec1fa2308f7..6fa6fdf264ffa7 100644 --- a/homeassistant/components/solaredge/translations/fr.json +++ b/homeassistant/components/solaredge/translations/fr.json @@ -4,7 +4,9 @@ "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9" }, "error": { - "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9" + "invalid_api_key": "Cl\u00e9 API invalide", + "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9", + "site_not_active": "The site n'est pas actif" }, "step": { "user": { diff --git a/homeassistant/components/solaredge/translations/lb.json b/homeassistant/components/solaredge/translations/lb.json index 4f2f698a6ca751..709a57f070b868 100644 --- a/homeassistant/components/solaredge/translations/lb.json +++ b/homeassistant/components/solaredge/translations/lb.json @@ -1,10 +1,13 @@ { "config": { "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", "site_exists": "D\u00ebs site_id ass scho konfigur\u00e9iert" }, "error": { - "site_exists": "D\u00ebs site_id ass scho konfigur\u00e9iert" + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "site_exists": "D\u00ebs site_id ass scho konfigur\u00e9iert", + "site_not_active": "De Site ass net aktiv" }, "step": { "user": { diff --git a/homeassistant/components/solaredge/translations/tr.json b/homeassistant/components/solaredge/translations/tr.json index 5307276a71d3a3..b8159be58b48c4 100644 --- a/homeassistant/components/solaredge/translations/tr.json +++ b/homeassistant/components/solaredge/translations/tr.json @@ -2,6 +2,19 @@ "config": { "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "could_not_connect": "Solaredge API'ye ba\u011flan\u0131lamad\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", + "site_not_active": "Site aktif de\u011fil" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/uk.json b/homeassistant/components/solaredge/translations/uk.json new file mode 100644 index 00000000000000..5ad67d8768001b --- /dev/null +++ b/homeassistant/components/solaredge/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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.", + "site_exists": "\u0426\u0435\u0439 site_id \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439." + }, + "error": { + "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.", + "could_not_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 API Solaredge.", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "site_exists": "\u0426\u0435\u0439 site_id \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439.", + "site_not_active": "\u0421\u0430\u0439\u0442 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u0438\u0439." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "name": "\u041d\u0430\u0437\u0432\u0430", + "site_id": "site-id" + }, + "title": "SolarEdge" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/de.json b/homeassistant/components/solarlog/translations/de.json index 58e691b733d181..008e10586819fa 100644 --- a/homeassistant/components/solarlog/translations/de.json +++ b/homeassistant/components/solarlog/translations/de.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindung fehlgeschlagen. \u00dcberpr\u00fcfe die Host-Adresse" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/tr.json b/homeassistant/components/solarlog/translations/tr.json new file mode 100644 index 00000000000000..a11d3815eed8ca --- /dev/null +++ b/homeassistant/components/solarlog/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/uk.json b/homeassistant/components/solarlog/translations/uk.json new file mode 100644 index 00000000000000..f4fca695032917 --- /dev/null +++ b/homeassistant/components/solarlog/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041f\u0440\u0435\u0444\u0456\u043a\u0441, \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0434\u043b\u044f \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 Solar-Log" + }, + "title": "Solar-Log" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/tr.json b/homeassistant/components/soma/translations/tr.json new file mode 100644 index 00000000000000..21a477c75a79c4 --- /dev/null +++ b/homeassistant/components/soma/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/uk.json b/homeassistant/components/soma/translations/uk.json new file mode 100644 index 00000000000000..0ec98301d62c93 --- /dev/null +++ b/homeassistant/components/soma/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_setup": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "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.", + "connection_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 SOMA Connect.", + "missing_configuration": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Soma \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439. \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.", + "result_error": "SOMA Connect \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0432 \u0437\u0456 \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u043f\u043e\u043c\u0438\u043b\u043a\u0438." + }, + "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." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e SOMA Connect.", + "title": "SOMA Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/de.json b/homeassistant/components/somfy/translations/de.json index 6b76e2f61befcc..29a959f48ce140 100644 --- a/homeassistant/components/somfy/translations/de.json +++ b/homeassistant/components/somfy/translations/de.json @@ -2,10 +2,12 @@ "config": { "abort": { "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", - "missing_configuration": "Die Somfy-Komponente ist nicht konfiguriert. Folge bitte der Dokumentation." + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "create_entry": { - "default": "Erfolgreich mit Somfy authentifiziert." + "default": "Erfolgreich authentifiziert" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy/translations/tr.json b/homeassistant/components/somfy/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/somfy/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/uk.json b/homeassistant/components/somfy/translations/uk.json new file mode 100644 index 00000000000000..ebf7e41044eef6 --- /dev/null +++ b/homeassistant/components/somfy/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "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.", + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ca.json b/homeassistant/components/somfy_mylink/translations/ca.json new file mode 100644 index 00000000000000..93ae58ca2bfe6b --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/ca.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port", + "system_id": "ID del sistema" + }, + "description": "L'ID de sistema es pot obtenir des de l'aplicaci\u00f3 MyLink dins de Integraci\u00f3, seleccionant qualsevol servei que no sigui al n\u00favol." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La coberta est\u00e0 invertida" + }, + "description": "Opcions de configuraci\u00f3 de `{entity_id}`", + "title": "Configura l'entitat" + }, + "init": { + "data": { + "default_reverse": "Estat d'inversi\u00f3 predeterminat per a cobertes sense configurar", + "entity_id": "Configura una entitat espec\u00edfica.", + "target_id": "Opcions de configuraci\u00f3 de la coberta." + }, + "title": "Configura opcions de MyLink" + }, + "target_config": { + "data": { + "reverse": "La coberta est\u00e0 invertida" + }, + "description": "Opcions de configuraci\u00f3 de `{target_name}`", + "title": "Configura coberta MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/cs.json b/homeassistant/components/somfy_mylink/translations/cs.json new file mode 100644 index 00000000000000..71e05b51544574 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/cs.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Hostitel", + "port": "Port" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/de.json b/homeassistant/components/somfy_mylink/translations/de.json new file mode 100644 index 00000000000000..522e185af5de0b --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/de.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "System-ID" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "entity_config": { + "title": "Entit\u00e4t konfigurieren" + }, + "init": { + "title": "MyLink-Optionen konfigurieren" + }, + "target_config": { + "description": "Konfiguriere die Optionen f\u00fcr `{target_name}`", + "title": "MyLink-Cover konfigurieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/en.json b/homeassistant/components/somfy_mylink/translations/en.json index ca3d83e402b35b..13115b36e5c26e 100644 --- a/homeassistant/components/somfy_mylink/translations/en.json +++ b/homeassistant/components/somfy_mylink/translations/en.json @@ -1,44 +1,53 @@ { - "title": "Somfy MyLink", - "config": { - "flow_title": "Somfy MyLink {mac} ({ip})", - "step": { - "user": { - "description": "The System ID can be obtained in the MyLink app under Integration by selecting any non-Cloud service.", - "data": { - "host": "[%key:common::config_flow::data::host%]", - "port": "[%key:common::config_flow::data::port%]", - "system_id": "System ID" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "System ID" + }, + "description": "The System ID can be obtained in the MyLink app under Integration by selecting any non-Cloud service." + } } - } }, - "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%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" - } - }, - "options": { - "abort": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" - }, - "step": { - "init": { - "title": "Configure MyLink Options", - "data": { - "target_id": "Configure options for a cover." + "options": { + "abort": { + "cannot_connect": "Failed to connect" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Cover is reversed" + }, + "description": "Configure options for `{entity_id}`", + "title": "Configure Entity" + }, + "init": { + "data": { + "default_reverse": "Default reversal status for unconfigured covers", + "entity_id": "Configure a specific entity.", + "target_id": "Configure options for a cover." + }, + "title": "Configure MyLink Options" + }, + "target_config": { + "data": { + "reverse": "Cover is reversed" + }, + "description": "Configure options for `{target_name}`", + "title": "Configure MyLink Cover" + } } - }, - "target_config": { - "title": "Configure MyLink Cover", - "description": "Configure options for `{target_name}`", - "data": { - "reverse": "Cover is reversed" - } - } - } - } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/es.json b/homeassistant/components/somfy_mylink/translations/es.json new file mode 100644 index 00000000000000..40d82a4522a89c --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/es.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto", + "system_id": "ID del sistema" + }, + "description": "El ID del sistema se puede obtener en la aplicaci\u00f3n MyLink en Integraci\u00f3n seleccionando cualquier servicio que no sea de la nube." + } + } + }, + "options": { + "abort": { + "cannot_connect": "No se pudo conectar" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La cubierta est\u00e1 invertida" + }, + "description": "Configurar opciones para `{entity_id}`", + "title": "Configurar entidad" + }, + "init": { + "data": { + "default_reverse": "Estado de inversi\u00f3n predeterminado para cubiertas no configuradas", + "entity_id": "Configurar una entidad espec\u00edfica.", + "target_id": "Configurar opciones para una cubierta." + }, + "title": "Configurar opciones de MyLink" + }, + "target_config": { + "data": { + "reverse": "La cubierta est\u00e1 invertida" + }, + "description": "Configurar opciones para `{target_name}`", + "title": "Configurar la cubierta MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/et.json b/homeassistant/components/somfy_mylink/translations/et.json new file mode 100644 index 00000000000000..6d965220d7efc6 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/et.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "flow_title": "Somfy MyLink {mac} ( {ip} )", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "S\u00fcsteemi ID" + }, + "description": "S\u00fcsteemi ID saab rakenduse MyLink sidumise alt valides mis tahes mitte- pilveteenuse." + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "step": { + "entity_config": { + "data": { + "reverse": "(Akna)kate t\u00f6\u00f6tab vastupidi" + }, + "description": "Olemi {entity_id} suvandite seadmine", + "title": "Seadista olem" + }, + "init": { + "data": { + "default_reverse": "Seadistamata (akna)katete vaikep\u00f6\u00f6rduse olek", + "entity_id": "Seadista konkreetne olem.", + "target_id": "Seadista (akna)katte suvandid" + }, + "title": "Seadista MyLinki suvandid" + }, + "target_config": { + "data": { + "reverse": "(Akna)kate liigub vastupidi" + }, + "description": "Seadme `{target_name}` suvandite seadmine", + "title": "Seadista MyLink Cover" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/fr.json b/homeassistant/components/somfy_mylink/translations/fr.json new file mode 100644 index 00000000000000..96904b6038d7be --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/fr.json @@ -0,0 +1,43 @@ +{ + "config": { + "error": { + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Echec de connection" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La couverture est invers\u00e9e" + }, + "title": "Configurez une entit\u00e9 sp\u00e9cifique" + }, + "init": { + "data": { + "default_reverse": "Statut d'inversion par d\u00e9faut pour les couvertures non configur\u00e9es", + "entity_id": "Configurez une entit\u00e9 sp\u00e9cifique.", + "target_id": "Configurez les options pour la couverture." + }, + "title": "Configurer les options MyLink" + }, + "target_config": { + "data": { + "reverse": "La couverture est invers\u00e9e" + }, + "description": "Configurer les options pour \u00ab {target_name} \u00bb", + "title": "Configurer la couverture MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/it.json b/homeassistant/components/somfy_mylink/translations/it.json new file mode 100644 index 00000000000000..ce049782c43364 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/it.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Porta", + "system_id": "ID sistema" + }, + "description": "L'ID sistema pu\u00f2 essere ottenuto nell'app MyLink alla voce Integrazione selezionando qualsiasi servizio non-Cloud." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Impossibile connettersi" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La tapparella \u00e8 invertita" + }, + "description": "Configura le opzioni per `{entity_id}`", + "title": "Configura entit\u00e0" + }, + "init": { + "data": { + "default_reverse": "Stato d'inversione predefinito per le tapparelle non configurate", + "entity_id": "Configura un'entit\u00e0 specifica.", + "target_id": "Configura opzioni per una tapparella" + }, + "title": "Configura le opzioni MyLink" + }, + "target_config": { + "data": { + "reverse": "La tapparella \u00e8 invertita" + }, + "description": "Configura le opzioni per `{target_name}`", + "title": "Configura tapparelle MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/lb.json b/homeassistant/components/somfy_mylink/translations/lb.json new file mode 100644 index 00000000000000..efaba3ab497400 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/lb.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "System ID" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/no.json b/homeassistant/components/somfy_mylink/translations/no.json new file mode 100644 index 00000000000000..5b9b6608c256fa --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/no.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "flow_title": "", + "step": { + "user": { + "data": { + "host": "Vert", + "port": "Port", + "system_id": "" + }, + "description": "System-ID-en kan f\u00e5s i MyLink-appen under Integrasjon ved \u00e5 velge en hvilken som helst ikke-Cloud-tjeneste." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Tilkobling mislyktes" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Rullegardinet reverseres" + }, + "description": "Konfigurer alternativer for \"{entity_id}\"", + "title": "Konfigurer enhet" + }, + "init": { + "data": { + "default_reverse": "Standard tilbakef\u00f8ringsstatus for ukonfigurerte rullegardiner", + "entity_id": "Konfigurer en bestemt enhet.", + "target_id": "Konfigurer alternativer for et rullgardin" + }, + "title": "Konfigurere MyLink-alternativer" + }, + "target_config": { + "data": { + "reverse": "Rullegardinet reverseres" + }, + "description": "Konfigurer alternativer for \"{target_name}\"", + "title": "Konfigurer MyLink-deksel" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/pl.json b/homeassistant/components/somfy_mylink/translations/pl.json new file mode 100644 index 00000000000000..7e49ecb2bcaa27 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/pl.json @@ -0,0 +1,53 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port", + "system_id": "Identyfikator systemu" + }, + "description": "Identyfikator systemu mo\u017cna uzyska\u0107 w aplikacji MyLink w sekcji Integracja, wybieraj\u0105c dowoln\u0105 us\u0142ug\u0119 spoza chmury." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Roleta/pokrywa jest odwr\u00f3cona" + }, + "description": "Konfiguracja opcji dla \"{entity_id}\"", + "title": "Konfigurowanie encji" + }, + "init": { + "data": { + "default_reverse": "Domy\u015blny stan odwr\u00f3cenia nieskonfigurowanych rolet/pokryw", + "entity_id": "Skonfiguruj okre\u015blon\u0105 encj\u0119.", + "target_id": "Konfiguracja opcji rolety" + }, + "title": "Konfiguracja opcji MyLink" + }, + "target_config": { + "data": { + "reverse": "Roleta/pokrywa jest odwr\u00f3cona" + }, + "description": "Konfiguracja opcji dla \"{target_name}\"", + "title": "Konfiguracja rolety MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ru.json b/homeassistant/components/somfy_mylink/translations/ru.json new file mode 100644 index 00000000000000..e4cc7b717128bf --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/ru.json @@ -0,0 +1,53 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "system_id": "System ID" + }, + "description": "System ID \u043c\u043e\u0436\u043d\u043e \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 MyLink \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u00ab\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u00bb, \u0432\u044b\u0431\u0440\u0430\u0432 \u043b\u044e\u0431\u0443\u044e \u043d\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u0443\u044e \u0441\u043b\u0443\u0436\u0431\u0443." + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "step": { + "entity_config": { + "data": { + "reverse": "\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f `{entity_id}`", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430" + }, + "init": { + "data": { + "default_reverse": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438", + "entity_id": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430", + "target_id": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0448\u0442\u043e\u0440 \u0438\u043b\u0438 \u0436\u0430\u043b\u044e\u0437\u0438." + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 MyLink" + }, + "target_config": { + "data": { + "reverse": "\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f `{target_name}`", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 MyLink Cover" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/tr.json b/homeassistant/components/somfy_mylink/translations/tr.json new file mode 100644 index 00000000000000..29530b65659cfb --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/tr.json @@ -0,0 +1,53 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "Somfy MyLink {mac} ( {ip} )", + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port", + "system_id": "Sistem ID" + }, + "description": "Sistem Kimli\u011fi, MyLink uygulamas\u0131nda Entegrasyon alt\u0131nda Bulut d\u0131\u015f\u0131 herhangi bir hizmet se\u00e7ilerek elde edilebilir." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Kapak ters \u00e7evrildi" + }, + "description": "'{entity_id}' i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n", + "title": "Varl\u0131\u011f\u0131 Yap\u0131land\u0131r" + }, + "init": { + "data": { + "default_reverse": "Yap\u0131land\u0131r\u0131lmam\u0131\u015f kapaklar i\u00e7in varsay\u0131lan geri alma durumu", + "entity_id": "Belirli bir varl\u0131\u011f\u0131 yap\u0131land\u0131r\u0131n.", + "target_id": "Kapak i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n." + }, + "title": "MyLink Se\u00e7eneklerini Yap\u0131land\u0131r\u0131n" + }, + "target_config": { + "data": { + "reverse": "Kapak ters \u00e7evrildi" + }, + "description": "'{target_name}' i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n", + "title": "MyLink Kapa\u011f\u0131n\u0131 Yap\u0131land\u0131r\u0131n" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/uk.json b/homeassistant/components/somfy_mylink/translations/uk.json new file mode 100644 index 00000000000000..2d251531340042 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "system_id": "System ID" + }, + "description": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0456 MyLink \u0443 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 \u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f, \u0432\u0438\u0431\u0440\u0430\u0432\u0448\u0438 \u0431\u0443\u0434\u044c-\u044f\u043a\u0443 \u043d\u0435\u0445\u043c\u0430\u0440\u043d\u0443 \u0441\u043b\u0443\u0436\u0431\u0443." + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "entity_config": { + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u0434\u043b\u044f \"{entity_id}\"", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c" + }, + "init": { + "data": { + "entity_id": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u043f\u0435\u0446\u0438\u0444\u0456\u0447\u043d\u043e\u0457 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456." + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0435\u0439 MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/zh-Hant.json b/homeassistant/components/somfy_mylink/translations/zh-Hant.json new file mode 100644 index 00000000000000..2abb6a64f7c8ab --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/zh-Hant.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0", + "system_id": "\u7cfb\u7d71 ID" + }, + "description": "\u7cfb\u7d71 ID \u53ef\u4ee5\u65bc\u6574\u5408\u5167\u7684 MyLink app \u9078\u64c7\u975e\u96f2\u7aef\u670d\u52d9\u4e2d\u627e\u5230\u3002" + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "step": { + "entity_config": { + "data": { + "reverse": "\u7a97\u7c3e\u53cd\u5411" + }, + "description": "`{entity_id}` \u8a2d\u5b9a\u9078\u9805", + "title": "\u8a2d\u5b9a\u5be6\u9ad4" + }, + "init": { + "data": { + "default_reverse": "\u672a\u8a2d\u5b9a\u7a97\u7c3e\u9810\u8a2d\u70ba\u53cd\u5411", + "entity_id": "\u8a2d\u5b9a\u7279\u5b9a\u5be6\u9ad4\u3002", + "target_id": "\u7a97\u7c3e\u8a2d\u5b9a\u9078\u9805\u3002" + }, + "title": "MyLink \u8a2d\u5b9a\u9078\u9805" + }, + "target_config": { + "data": { + "reverse": "\u7a97\u7c3e\u53cd\u5411" + }, + "description": "`{target_name}` \u8a2d\u5b9a\u9078\u9805", + "title": "\u8a2d\u5b9a MyLink \u7a97\u7c3e" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/de.json b/homeassistant/components/sonarr/translations/de.json index 3abc6b45ef39a9..19a37dbcc4f306 100644 --- a/homeassistant/components/sonarr/translations/de.json +++ b/homeassistant/components/sonarr/translations/de.json @@ -1,19 +1,27 @@ { "config": { "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "unknown": "Unerwateter Fehler" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "title": "Integration erneut authentifizieren" + }, "user": { "data": { "api_key": "API Schl\u00fcssel", "base_path": "Pfad zur API", "host": "Host", - "port": "Port" + "port": "Port", + "ssl": "Verwendet ein SSL-Zertifikat", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } } } diff --git a/homeassistant/components/sonarr/translations/tr.json b/homeassistant/components/sonarr/translations/tr.json new file mode 100644 index 00000000000000..eadf010004587c --- /dev/null +++ b/homeassistant/components/sonarr/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/uk.json b/homeassistant/components/sonarr/translations/uk.json new file mode 100644 index 00000000000000..0b6b7acf26decb --- /dev/null +++ b/homeassistant/components/sonarr/translations/uk.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "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", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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." + }, + "flow_title": "Sonarr: {name}", + "step": { + "reauth_confirm": { + "description": "\u041f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e API Sonarr \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: {host}", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "base_path": "\u0428\u043b\u044f\u0445 \u0434\u043e API", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043c\u0430\u0439\u0431\u0443\u0442\u043d\u0456\u0445 \u0434\u043d\u0456\u0432 \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", + "wanted_max_items": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0456\u0432 \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/tr.json b/homeassistant/components/songpal/translations/tr.json new file mode 100644 index 00000000000000..ab90d4b1067d7c --- /dev/null +++ b/homeassistant/components/songpal/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "endpoint": "Biti\u015f noktas\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/uk.json b/homeassistant/components/songpal/translations/uk.json new file mode 100644 index 00000000000000..893077a826db7b --- /dev/null +++ b/homeassistant/components/songpal/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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.", + "not_songpal_device": "\u0426\u0435 \u043d\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Songpal." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Sony Songpal {name} ({host})", + "step": { + "init": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?" + }, + "user": { + "data": { + "endpoint": "\u041a\u0456\u043d\u0446\u0435\u0432\u0430 \u0442\u043e\u0447\u043a\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/de.json b/homeassistant/components/sonos/translations/de.json index 93b25cf0b9781b..5d66c16811671d 100644 --- a/homeassistant/components/sonos/translations/de.json +++ b/homeassistant/components/sonos/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Keine Sonos Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von Sonos ist notwendig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/tr.json b/homeassistant/components/sonos/translations/tr.json new file mode 100644 index 00000000000000..42bd46ce7c0e35 --- /dev/null +++ b/homeassistant/components/sonos/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Sonos'u kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/uk.json b/homeassistant/components/sonos/translations/uk.json new file mode 100644 index 00000000000000..aff6c9f59b1799 --- /dev/null +++ b/homeassistant/components/sonos/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Sonos?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/de.json b/homeassistant/components/speedtestdotnet/translations/de.json index 56b1a91a89e4e8..3b5ef0b26e17ad 100644 --- a/homeassistant/components/speedtestdotnet/translations/de.json +++ b/homeassistant/components/speedtestdotnet/translations/de.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "wrong_server_id": "Server ID ist ung\u00fcltig" + "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich.", + "wrong_server_id": "Server-ID ist ung\u00fcltig" }, "step": { "user": { - "description": "Einrichtung beginnen?" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" } } }, @@ -13,7 +14,9 @@ "step": { "init": { "data": { - "manual": "Automatische Updates deaktivieren" + "manual": "Automatische Updates deaktivieren", + "scan_interval": "Aktualisierungsfrequenz (Minuten)", + "server_name": "Testserver ausw\u00e4hlen" } } } diff --git a/homeassistant/components/speedtestdotnet/translations/tr.json b/homeassistant/components/speedtestdotnet/translations/tr.json new file mode 100644 index 00000000000000..b13be7c5e0cb7d --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "wrong_server_id": "Sunucu kimli\u011fi ge\u00e7erli de\u011fil" + }, + "step": { + "user": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Otomatik g\u00fcncellemeyi devre d\u0131\u015f\u0131 b\u0131rak\u0131n", + "scan_interval": "G\u00fcncelleme s\u0131kl\u0131\u011f\u0131 (dakika)", + "server_name": "Test sunucusunu se\u00e7in" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/uk.json b/homeassistant/components/speedtestdotnet/translations/uk.json new file mode 100644 index 00000000000000..89ef24440d13e4 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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.", + "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": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f", + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0443 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)", + "server_name": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/de.json b/homeassistant/components/spider/translations/de.json index 6f39806287630f..c57e55e9d2ea1b 100644 --- a/homeassistant/components/spider/translations/de.json +++ b/homeassistant/components/spider/translations/de.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/spider/translations/tr.json b/homeassistant/components/spider/translations/tr.json new file mode 100644 index 00000000000000..9bcc6bb1c41c70 --- /dev/null +++ b/homeassistant/components/spider/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/uk.json b/homeassistant/components/spider/translations/uk.json new file mode 100644 index 00000000000000..b8be2a1488791e --- /dev/null +++ b/homeassistant/components/spider/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u0412\u0445\u0456\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 mijn.ithodaalderop.nl" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/de.json b/homeassistant/components/spotify/translations/de.json index bfd393bbbb8600..281803ec66ed96 100644 --- a/homeassistant/components/spotify/translations/de.json +++ b/homeassistant/components/spotify/translations/de.json @@ -2,14 +2,18 @@ "config": { "abort": { "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", - "missing_configuration": "Die Spotify-Integration ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "missing_configuration": "Die Spotify-Integration ist nicht konfiguriert. Bitte folgen Sie der Dokumentation.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "create_entry": { "default": "Erfolgreich mit Spotify authentifiziert." }, "step": { "pick_implementation": { - "title": "Authentifizierungsmethode ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" + }, + "reauth_confirm": { + "title": "Integration erneut authentifizieren" } } }, diff --git a/homeassistant/components/spotify/translations/lb.json b/homeassistant/components/spotify/translations/lb.json index d7b5dcec0be4d6..92e323d6c4d7c4 100644 --- a/homeassistant/components/spotify/translations/lb.json +++ b/homeassistant/components/spotify/translations/lb.json @@ -17,5 +17,10 @@ "title": "Integratioun re-authentifiz\u00e9ieren" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API Endpunkt ereechbar" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/uk.json b/homeassistant/components/spotify/translations/uk.json new file mode 100644 index 00000000000000..fda84b310a5c24 --- /dev/null +++ b/homeassistant/components/spotify/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "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": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f Spotify \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0430. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044c \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e.", + "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_account_mismatch": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u043e\u0432\u0430\u043d\u0438\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u0443, \u0449\u043e \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "reauth_confirm": { + "description": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u0432 Spotify \u0434\u043b\u044f \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443: {account}", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + } + } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e API Spotify" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/de.json b/homeassistant/components/squeezebox/translations/de.json index 667bf6dbd128c8..742210f3dc6d98 100644 --- a/homeassistant/components/squeezebox/translations/de.json +++ b/homeassistant/components/squeezebox/translations/de.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" }, "step": { "edit": { "data": { + "host": "Host", "password": "Passwort", "port": "Port", "username": "Benutzername" diff --git a/homeassistant/components/squeezebox/translations/tr.json b/homeassistant/components/squeezebox/translations/tr.json new file mode 100644 index 00000000000000..ff249aafa14853 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/tr.json @@ -0,0 +1,27 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "edit": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/uk.json b/homeassistant/components/squeezebox/translations/uk.json new file mode 100644 index 00000000000000..50cd135f6f3d41 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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.", + "no_server_found": "\u0421\u0435\u0440\u0432\u0435\u0440 LMS \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + "error": { + "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.", + "no_server_found": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0432\u0438\u044f\u0432\u0438\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Logitech Squeezebox: {host}", + "step": { + "edit": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/de.json b/homeassistant/components/srp_energy/translations/de.json index 23fe89c73b4d1f..302233d29234b8 100644 --- a/homeassistant/components/srp_energy/translations/de.json +++ b/homeassistant/components/srp_energy/translations/de.json @@ -1,14 +1,21 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Anmeldung", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { - "password": "Passwort" + "password": "Passwort", + "username": "Benutzername" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/es.json b/homeassistant/components/srp_energy/translations/es.json index de15bb805514dd..849c5019d3b5f5 100644 --- a/homeassistant/components/srp_energy/translations/es.json +++ b/homeassistant/components/srp_energy/translations/es.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n." + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "error": { "cannot_connect": "No se pudo conectar", "invalid_account": "El ID de la cuenta debe ser un n\u00famero de 9 d\u00edgitos", - "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, "step": { @@ -15,7 +15,7 @@ "id": "ID de la cuenta", "is_tou": "Es el plan de tiempo de uso", "password": "Contrase\u00f1a", - "username": "Nombre de usuario" + "username": "Usuario" } } } diff --git a/homeassistant/components/srp_energy/translations/fr.json b/homeassistant/components/srp_energy/translations/fr.json new file mode 100644 index 00000000000000..0cc85aff649130 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/fr.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/lb.json b/homeassistant/components/srp_energy/translations/lb.json new file mode 100644 index 00000000000000..1affdcc31e6f18 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/lb.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/tr.json b/homeassistant/components/srp_energy/translations/tr.json index 1b08426f631ac4..ead8238d82c116 100644 --- a/homeassistant/components/srp_energy/translations/tr.json +++ b/homeassistant/components/srp_energy/translations/tr.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, "error": { - "invalid_account": "Hesap kimli\u011fi 9 haneli bir say\u0131 olmal\u0131d\u0131r" + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_account": "Hesap kimli\u011fi 9 haneli bir say\u0131 olmal\u0131d\u0131r", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" }, "step": { "user": { diff --git a/homeassistant/components/srp_energy/translations/uk.json b/homeassistant/components/srp_energy/translations/uk.json new file mode 100644 index 00000000000000..5267aa2a5757f1 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_account": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 9-\u0437\u043d\u0430\u0447\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443", + "is_tou": "\u041f\u043b\u0430\u043d \u0447\u0430\u0441\u0443 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "title": "SRP Energy" +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/tr.json b/homeassistant/components/starline/translations/tr.json new file mode 100644 index 00000000000000..9d52f589e98735 --- /dev/null +++ b/homeassistant/components/starline/translations/tr.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "error_auth_user": "Yanl\u0131\u015f kullan\u0131c\u0131 ad\u0131 ya da parola" + }, + "step": { + "auth_app": { + "title": "Uygulama kimlik bilgileri" + }, + "auth_captcha": { + "data": { + "captcha_code": "G\u00f6r\u00fcnt\u00fcden kod" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "SMS kodu" + }, + "description": "{phone_number} telefona g\u00f6nderilen kodu girin", + "title": "\u0130ki fakt\u00f6rl\u00fc yetkilendirme" + }, + "auth_user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "StarLine hesab\u0131 e-postas\u0131 ve parolas\u0131" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/uk.json b/homeassistant/components/starline/translations/uk.json new file mode 100644 index 00000000000000..8a263044284f6a --- /dev/null +++ b/homeassistant/components/starline/translations/uk.json @@ -0,0 +1,41 @@ +{ + "config": { + "error": { + "error_auth_app": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0434\u0430\u0442\u043a\u0430 \u0430\u0431\u043e \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u0438\u0439 \u043a\u043e\u0434.", + "error_auth_mfa": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434.", + "error_auth_user": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d \u0430\u0431\u043e \u043f\u0430\u0440\u043e\u043b\u044c." + }, + "step": { + "auth_app": { + "data": { + "app_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0434\u0430\u0442\u043a\u0430", + "app_secret": "\u0421\u0435\u043a\u0440\u0435\u0442\u043d\u0438\u0439 \u043a\u043e\u0434" + }, + "description": "ID \u0434\u043e\u0434\u0430\u0442\u043a\u0430 \u0456 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u0438\u0439 \u043a\u043e\u0434 [\u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 \u0440\u043e\u0437\u0440\u043e\u0431\u043d\u0438\u043a\u0430 StarLine] (https://my.starline.ru/developer)", + "title": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456 \u0434\u043e\u0434\u0430\u0442\u043a\u0430" + }, + "auth_captcha": { + "data": { + "captcha_code": "\u041a\u043e\u0434 \u0437 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f" + }, + "description": "{captcha_img}", + "title": "CAPTCHA" + }, + "auth_mfa": { + "data": { + "mfa_code": "\u041a\u043e\u0434 \u0437 SMS" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434, \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u043d\u0430 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443 {phone_number}", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "auth_user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438 \u0442\u0430 \u043f\u0430\u0440\u043e\u043b\u044c \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 StarLine", + "title": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sun/translations/pl.json b/homeassistant/components/sun/translations/pl.json index fb90b9bd232b84..1f00babd1fd07a 100644 --- a/homeassistant/components/sun/translations/pl.json +++ b/homeassistant/components/sun/translations/pl.json @@ -1,7 +1,7 @@ { "state": { "_": { - "above_horizon": "powy\u017cej horyzontu", + "above_horizon": "nad horyzontem", "below_horizon": "poni\u017cej horyzontu" } }, diff --git a/homeassistant/components/switch/translations/uk.json b/homeassistant/components/switch/translations/uk.json index bee9eb957d530f..26b85b3a87397f 100644 --- a/homeassistant/components/switch/translations/uk.json +++ b/homeassistant/components/switch/translations/uk.json @@ -1,5 +1,14 @@ { "device_automation": { + "action_type": { + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" diff --git a/homeassistant/components/syncthru/translations/tr.json b/homeassistant/components/syncthru/translations/tr.json new file mode 100644 index 00000000000000..942457958f820e --- /dev/null +++ b/homeassistant/components/syncthru/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "confirm": { + "data": { + "url": "Web aray\u00fcz\u00fc URL'si" + } + }, + "user": { + "data": { + "name": "Ad", + "url": "Web aray\u00fcz\u00fc URL'si" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/uk.json b/homeassistant/components/syncthru/translations/uk.json new file mode 100644 index 00000000000000..74cccc7ef5a24a --- /dev/null +++ b/homeassistant/components/syncthru/translations/uk.json @@ -0,0 +1,27 @@ +{ + "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." + }, + "error": { + "invalid_url": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430.", + "syncthru_not_supported": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454 SyncThru.", + "unknown_state": "\u0421\u0442\u0430\u043d \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043d\u0435\u0432\u0456\u0434\u043e\u043c\u0438\u0439, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u0442\u0430 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456." + }, + "flow_title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 Samsung SyncThru: {name}", + "step": { + "confirm": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443" + } + }, + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index 303321ea94c6ad..f0d274c3bfe47c 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Host bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "missing_data": "Fehlende Daten: Bitte versuchen Sie es sp\u00e4ter noch einmal oder eine andere Konfiguration", "otp_failed": "Die zweistufige Authentifizierung ist fehlgeschlagen. Versuchen Sie es erneut mit einem neuen Code", - "unknown": "Unbekannter Fehler: Bitte \u00fcberpr\u00fcfen Sie die Protokolle, um weitere Details zu erhalten" + "unknown": "Unerwarteter Fehler" }, "flow_title": "Synology DSM {name} ({host})", "step": { @@ -21,7 +22,7 @@ "data": { "password": "Passwort", "port": "Port", - "ssl": "Verwenden Sie SSL/TLS, um eine Verbindung zu Ihrem NAS herzustellen", + "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL Zertifikat verifizieren" }, @@ -33,7 +34,7 @@ "host": "Host", "password": "Passwort", "port": "Port", - "ssl": "Verwenden Sie SSL/TLS, um eine Verbindung zu Ihrem NAS herzustellen", + "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL Zertifikat verifizieren" }, diff --git a/homeassistant/components/synology_dsm/translations/tr.json b/homeassistant/components/synology_dsm/translations/tr.json index a7598bb343842a..681d85d2ef545e 100644 --- a/homeassistant/components/synology_dsm/translations/tr.json +++ b/homeassistant/components/synology_dsm/translations/tr.json @@ -1,15 +1,31 @@ { "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", + "unknown": "Beklenmeyen hata" + }, "step": { "link": { "data": { + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikalar\u0131n\u0131 do\u011frula" } }, "user": { "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikalar\u0131n\u0131 do\u011frula" - } + }, + "title": "Synology DSM" } } } diff --git a/homeassistant/components/synology_dsm/translations/uk.json b/homeassistant/components/synology_dsm/translations/uk.json new file mode 100644 index 00000000000000..4d80350989fd68 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/uk.json @@ -0,0 +1,55 @@ +{ + "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." + }, + "error": { + "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.", + "missing_data": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456 \u0434\u0430\u043d\u0456: \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0440\u043e\u0431\u0443 \u043f\u0456\u0437\u043d\u0456\u0448\u0435 \u0430\u0431\u043e \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0456\u043d\u0448\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "otp_failed": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u0437 \u043d\u043e\u0432\u0438\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "2sa": { + "data": { + "otp_code": "\u041a\u043e\u0434" + }, + "title": "Synology DSM: \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "link": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", + "title": "Synology DSM" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "Synology DSM" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0456\u0436 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c\u0438 (\u0445\u0432.)", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/system_health/translations/uk.json b/homeassistant/components/system_health/translations/uk.json index 267fcb83a61646..61f30782f04999 100644 --- a/homeassistant/components/system_health/translations/uk.json +++ b/homeassistant/components/system_health/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0411\u0435\u0437\u043f\u0435\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0438" + "title": "\u0421\u0442\u0430\u043d \u0441\u0438\u0441\u0442\u0435\u043c\u0438" } \ No newline at end of file diff --git a/homeassistant/components/tado/translations/de.json b/homeassistant/components/tado/translations/de.json index ffab091f726d7e..9dc410b670e546 100644 --- a/homeassistant/components/tado/translations/de.json +++ b/homeassistant/components/tado/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "no_homes": "Es sind keine Standorte mit diesem Tado-Konto verkn\u00fcpft.", "unknown": "Unerwarteter Fehler" @@ -15,7 +15,7 @@ "password": "Passwort", "username": "Benutzername" }, - "title": "Stellen Sie eine Verbindung zu Ihrem Tado-Konto her" + "title": "Stellen eine Verbindung zu deinem Tado-Konto her" } } }, @@ -23,10 +23,10 @@ "step": { "init": { "data": { - "fallback": "Aktivieren Sie den Fallback-Modus." + "fallback": "Aktivieren den Fallback-Modus." }, "description": "Der Fallback-Modus wechselt beim n\u00e4chsten Zeitplanwechsel nach dem manuellen Anpassen einer Zone zu Smart Schedule.", - "title": "Passen Sie die Tado-Optionen an." + "title": "Passe die Tado-Optionen an." } } } diff --git a/homeassistant/components/tado/translations/tr.json b/homeassistant/components/tado/translations/tr.json new file mode 100644 index 00000000000000..09ffbf8a7d14f7 --- /dev/null +++ b/homeassistant/components/tado/translations/tr.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Geri d\u00f6n\u00fc\u015f modunu etkinle\u015ftirin." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/uk.json b/homeassistant/components/tado/translations/uk.json new file mode 100644 index 00000000000000..f1dcf4d575ba83 --- /dev/null +++ b/homeassistant/components/tado/translations/uk.json @@ -0,0 +1,33 @@ +{ + "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." + }, + "error": { + "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.", + "no_homes": "\u0427\u0438 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0431\u0443\u0434\u0438\u043d\u043a\u0456\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Tado" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c Fallback" + }, + "description": "\u0420\u0435\u0436\u0438\u043c Fallback \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043d\u0430 Smart Schedule \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0443 \u043f\u0456\u0441\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tado" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tag/translations/uk.json b/homeassistant/components/tag/translations/uk.json new file mode 100644 index 00000000000000..fdac700612daf4 --- /dev/null +++ b/homeassistant/components/tag/translations/uk.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/de.json b/homeassistant/components/tasmota/translations/de.json new file mode 100644 index 00000000000000..308747088395bd --- /dev/null +++ b/homeassistant/components/tasmota/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "config": { + "description": "Bitte die Tasmota-Konfiguration einstellen.", + "title": "Tasmota" + }, + "confirm": { + "description": "M\u00f6chtest du Tasmota einrichten?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/tr.json b/homeassistant/components/tasmota/translations/tr.json new file mode 100644 index 00000000000000..a559d0911ee0f5 --- /dev/null +++ b/homeassistant/components/tasmota/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "config": { + "description": "L\u00fctfen Tasmota yap\u0131land\u0131rmas\u0131n\u0131 girin.", + "title": "Tasmota" + }, + "confirm": { + "description": "Tasmota'y\u0131 kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/uk.json b/homeassistant/components/tasmota/translations/uk.json new file mode 100644 index 00000000000000..6639a9c9626eb8 --- /dev/null +++ b/homeassistant/components/tasmota/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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." + }, + "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." + }, + "step": { + "config": { + "data": { + "discovery_prefix": "\u041f\u0440\u0435\u0444\u0456\u043a\u0441 \u0442\u0435\u043c\u0438 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tasmota.", + "title": "Tasmota" + }, + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Tasmota?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/de.json b/homeassistant/components/tellduslive/translations/de.json index a1f6f595a046d0..098ad9c17be221 100644 --- a/homeassistant/components/tellduslive/translations/de.json +++ b/homeassistant/components/tellduslive/translations/de.json @@ -4,9 +4,12 @@ "already_configured": "Dienst ist bereits konfiguriert", "authorize_url_fail": "Unbekannter Fehler beim Erstellen der Authorisierungs-URL", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", - "unknown": "Unbekannter Fehler ist aufgetreten", + "unknown": "Unerwarteter Fehler", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "auth": { "description": "So verkn\u00fcpfest du dein TelldusLive-Konto: \n 1. Klicke auf den Link unten \n 2. Melde dich bei Telldus Live an \n 3. Autorisiere ** {app_name} ** (klicke auf ** Yes **). \n 4. Komme hierher zur\u00fcck und klicke auf ** SUBMIT **. \n\n [Link TelldusLive-Konto]({auth_url})", diff --git a/homeassistant/components/tellduslive/translations/fr.json b/homeassistant/components/tellduslive/translations/fr.json index cde9d9c2c68b31..ef4d7bc44dda47 100644 --- a/homeassistant/components/tellduslive/translations/fr.json +++ b/homeassistant/components/tellduslive/translations/fr.json @@ -4,7 +4,8 @@ "already_configured": "TelldusLive est d\u00e9j\u00e0 configur\u00e9", "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", - "unknown": "Une erreur inconnue s'est produite" + "unknown": "Une erreur inconnue s'est produite", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "error": { "invalid_auth": "Authentification invalide" diff --git a/homeassistant/components/tellduslive/translations/lb.json b/homeassistant/components/tellduslive/translations/lb.json index 5e733c2294d6c3..2b809050677ce8 100644 --- a/homeassistant/components/tellduslive/translations/lb.json +++ b/homeassistant/components/tellduslive/translations/lb.json @@ -4,7 +4,8 @@ "already_configured": "Service ass scho konfigur\u00e9iert", "authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", - "unknown": "Onerwaarte Feeler" + "unknown": "Onerwaarte Feeler", + "unknown_authorize_url_generation": "Onbekannte Feeler beim erstellen vun der Authorisatiouns URL." }, "error": { "invalid_auth": "Ong\u00eblteg Authentifikatioun" diff --git a/homeassistant/components/tellduslive/translations/tr.json b/homeassistant/components/tellduslive/translations/tr.json new file mode 100644 index 00000000000000..300fad68391594 --- /dev/null +++ b/homeassistant/components/tellduslive/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/uk.json b/homeassistant/components/tellduslive/translations/uk.json new file mode 100644 index 00000000000000..ff7b3337bb941b --- /dev/null +++ b/homeassistant/components/tellduslive/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "authorize_url_fail": "\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.", + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "auth": { + "description": "\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0430\u043a\u0430\u0443\u043d\u0442 Telldus Live:\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043f\u043e \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044e, \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043e\u043c\u0443 \u043d\u0438\u0436\u0447\u0435\n2. \u0423\u0432\u0456\u0439\u0434\u0456\u0442\u044c \u0432 Telldus Live\n3. Authorize ** {app_name} ** (\u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** Yes **).\n4. \u041f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0442\u0430 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **. \n\n[\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 Telldus Live]({auth_url})", + "title": "Telldus Live" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u043f\u043e\u0440\u043e\u0436\u043d\u044c\u043e", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043a\u0456\u043d\u0446\u0435\u0432\u0443 \u0442\u043e\u0447\u043a\u0443." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/de.json b/homeassistant/components/tesla/translations/de.json index 09100c355c20a0..558209af411791 100644 --- a/homeassistant/components/tesla/translations/de.json +++ b/homeassistant/components/tesla/translations/de.json @@ -1,7 +1,9 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { @@ -18,6 +20,7 @@ "step": { "init": { "data": { + "enable_wake_on_start": "Aufwachen des Autos beim Start erzwingen", "scan_interval": "Sekunden zwischen den Scans" } } diff --git a/homeassistant/components/tesla/translations/fr.json b/homeassistant/components/tesla/translations/fr.json index c8efc8b4fb5303..6134ff25f6b526 100644 --- a/homeassistant/components/tesla/translations/fr.json +++ b/homeassistant/components/tesla/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification invalide" }, diff --git a/homeassistant/components/tesla/translations/tr.json b/homeassistant/components/tesla/translations/tr.json new file mode 100644 index 00000000000000..cf0d144c1edf0d --- /dev/null +++ b/homeassistant/components/tesla/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "description": "L\u00fctfen bilgilerinizi giriniz." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/uk.json b/homeassistant/components/tesla/translations/uk.json new file mode 100644 index 00000000000000..90d47ec2ff5910 --- /dev/null +++ b/homeassistant/components/tesla/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443.", + "title": "Tesla" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "enable_wake_on_start": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u043e \u0440\u043e\u0437\u0431\u0443\u0434\u0438\u0442\u0438 \u043c\u0430\u0448\u0438\u043d\u0443 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0443", + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0456\u0436 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c\u0438 (\u0441\u0435\u043a.)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/de.json b/homeassistant/components/tibber/translations/de.json index 670f57df8ba9e2..8d49c9d9e6176f 100644 --- a/homeassistant/components/tibber/translations/de.json +++ b/homeassistant/components/tibber/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Ein Tibber-Konto ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", "timeout": "Zeit\u00fcberschreitung beim Verbinden mit Tibber" }, diff --git a/homeassistant/components/tibber/translations/tr.json b/homeassistant/components/tibber/translations/tr.json new file mode 100644 index 00000000000000..5f8e72986b211f --- /dev/null +++ b/homeassistant/components/tibber/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci" + }, + "step": { + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/uk.json b/homeassistant/components/tibber/translations/uk.json new file mode 100644 index 00000000000000..b1240116856403 --- /dev/null +++ b/homeassistant/components/tibber/translations/uk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", + "timeout": "\u0427\u0430\u0441 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u0438\u043d\u0443\u0432." + }, + "step": { + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u043d\u0430 \u0441\u0430\u0439\u0442\u0456 https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/de.json b/homeassistant/components/tile/translations/de.json index 59f48253a18688..1c2af82aa63062 100644 --- a/homeassistant/components/tile/translations/de.json +++ b/homeassistant/components/tile/translations/de.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Konto ist bereits konfiguriert" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tile/translations/tr.json b/homeassistant/components/tile/translations/tr.json new file mode 100644 index 00000000000000..8a04e2f4bbff6f --- /dev/null +++ b/homeassistant/components/tile/translations/tr.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "title": "Karoyu Yap\u0131land\u0131r" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Etkin Olmayan Karolar\u0131 G\u00f6ster" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/uk.json b/homeassistant/components/tile/translations/uk.json new file mode 100644 index 00000000000000..dc28164fd93c2d --- /dev/null +++ b/homeassistant/components/tile/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "Tile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + }, + "title": "Tile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/timer/translations/uk.json b/homeassistant/components/timer/translations/uk.json index df690bded93a51..ce937735406f91 100644 --- a/homeassistant/components/timer/translations/uk.json +++ b/homeassistant/components/timer/translations/uk.json @@ -1,9 +1,9 @@ { "state": { "_": { - "active": "\u0430\u043a\u0442\u0438\u0432\u043d\u0438\u0439", - "idle": "\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", - "paused": "\u043d\u0430 \u043f\u0430\u0443\u0437\u0456" + "active": "\u0410\u043a\u0442\u0438\u0432\u043d\u0438\u0439", + "idle": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", + "paused": "\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0435\u043d\u043e" } } } \ No newline at end of file diff --git a/homeassistant/components/toon/translations/de.json b/homeassistant/components/toon/translations/de.json index d9060a719d8780..c04f3a5f4bb4ab 100644 --- a/homeassistant/components/toon/translations/de.json +++ b/homeassistant/components/toon/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "no_agreements": "Dieses Konto hat keine Toon-Anzeigen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" } } diff --git a/homeassistant/components/toon/translations/fr.json b/homeassistant/components/toon/translations/fr.json index caeed852d0a0e9..3fa6059a58f2c7 100644 --- a/homeassistant/components/toon/translations/fr.json +++ b/homeassistant/components/toon/translations/fr.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Timout de g\u00e9n\u00e9ration de l'URL d'autorisation.", "missing_configuration": "The composant n'est pas configur\u00e9. Veuillez vous r\u00e9f\u00e9rer \u00e0 la documentation.", "no_agreements": "Ce compte n'a pas d'affichages Toon.", - "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )" + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/lb.json b/homeassistant/components/toon/translations/lb.json index 6491c66673863c..e21dfb0c996c24 100644 --- a/homeassistant/components/toon/translations/lb.json +++ b/homeassistant/components/toon/translations/lb.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Z\u00e4itiwwerschraidung beim erstellen vun der Autorisatioun's URL.", "missing_configuration": "Komponent ass net konfigur\u00e9iert. Folleg der Dokumentatioun.", "no_agreements": "D\u00ebse Kont huet keen Toon Ecran.", - "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})", + "unknown_authorize_url_generation": "Onbekannte Feeler beim erstellen vun der Authorisatiouns URL." }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/tr.json b/homeassistant/components/toon/translations/tr.json new file mode 100644 index 00000000000000..97765a99a7f49c --- /dev/null +++ b/homeassistant/components/toon/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Se\u00e7ilen anla\u015fma zaten yap\u0131land\u0131r\u0131lm\u0131\u015f.", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "step": { + "agreement": { + "data": { + "agreement": "Anla\u015fma" + }, + "description": "Eklemek istedi\u011finiz anla\u015fma adresini se\u00e7in.", + "title": "Anla\u015fman\u0131z\u0131 se\u00e7in" + }, + "pick_implementation": { + "title": "Kimlik do\u011frulamak i\u00e7in kirac\u0131n\u0131z\u0131 se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/uk.json b/homeassistant/components/toon/translations/uk.json new file mode 100644 index 00000000000000..51aa28f3984b04 --- /dev/null +++ b/homeassistant/components/toon/translations/uk.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\u041e\u0431\u0440\u0430\u043d\u0430 \u0443\u0433\u043e\u0434\u0430 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0430.", + "authorize_url_fail": "\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.", + "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_agreements": "\u0423 \u0446\u044c\u043e\u043c\u0443 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u0456 \u043d\u0435\u043c\u0430\u0454 \u0434\u0438\u0441\u043f\u043b\u0435\u0457\u0432 Toon.", + "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.", + "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." + }, + "step": { + "agreement": { + "data": { + "agreement": "\u0423\u0433\u043e\u0434\u0430" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u0443\u0433\u043e\u0434\u0438, \u044f\u043a\u0443 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438.", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0412\u0430\u0448\u0443 \u0443\u0433\u043e\u0434\u0443" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0440\u0435\u043d\u0434\u0430\u0440\u044f \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 25069635cca458..530fef95af23f1 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "Konto bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { diff --git a/homeassistant/components/totalconnect/translations/tr.json b/homeassistant/components/totalconnect/translations/tr.json new file mode 100644 index 00000000000000..f941db5ab8942d --- /dev/null +++ b/homeassistant/components/totalconnect/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "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/totalconnect/translations/uk.json b/homeassistant/components/totalconnect/translations/uk.json new file mode 100644 index 00000000000000..f34a279d598ddc --- /dev/null +++ b/homeassistant/components/totalconnect/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/de.json b/homeassistant/components/tplink/translations/de.json index 64bdfc9bf774ec..48571158085223 100644 --- a/homeassistant/components/tplink/translations/de.json +++ b/homeassistant/components/tplink/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine TP-Link-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration erforderlich." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/tr.json b/homeassistant/components/tplink/translations/tr.json new file mode 100644 index 00000000000000..e8f7a5aaf6dd06 --- /dev/null +++ b/homeassistant/components/tplink/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "TP-Link ak\u0131ll\u0131 cihazlar\u0131 kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/uk.json b/homeassistant/components/tplink/translations/uk.json new file mode 100644 index 00000000000000..cfeaf049675f2a --- /dev/null +++ b/homeassistant/components/tplink/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 TP-Link Smart Home?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/de.json b/homeassistant/components/traccar/translations/de.json index 5d5969b2d5111f..7e253c1d05f6e0 100644 --- a/homeassistant/components/traccar/translations/de.json +++ b/homeassistant/components/traccar/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "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." + }, "create_entry": { - "default": "Um Ereignisse an den Heimassistenten zu senden, m\u00fcssen die Webhook-Funktionen in Traccar eingerichtet werden.\n\nVerwende die folgende URL: `{webhook_url}}`\n\nSiehe [die Dokumentation]( {docs_url} ) f\u00fcr weitere Details." + "default": "Um Ereignisse an den Heimassistenten zu senden, m\u00fcssen die Webhook-Funktionen in Traccar eingerichtet werden.\n\nVerwende die folgende URL: `{webhook_url}`\n\nSiehe [Dokumentation]({docs_url}) f\u00fcr weitere Details." }, "step": { "user": { diff --git a/homeassistant/components/traccar/translations/lb.json b/homeassistant/components/traccar/translations/lb.json index d729525200503d..9e7d16fec3f08e 100644 --- a/homeassistant/components/traccar/translations/lb.json +++ b/homeassistant/components/traccar/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech.", + "webhook_not_internet_accessible": "Deng Home Assistant Instanz muss iwwert Internet accessibel si fir Webhook Noriichten z'empf\u00e4nken." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am Traccar ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider Informatiounen." diff --git a/homeassistant/components/traccar/translations/tr.json b/homeassistant/components/traccar/translations/tr.json index 7d044949a6ed6a..9a2b1a119cd832 100644 --- a/homeassistant/components/traccar/translations/tr.json +++ b/homeassistant/components/traccar/translations/tr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "step": { "user": { "title": "Traccar'\u0131 kur" diff --git a/homeassistant/components/traccar/translations/uk.json b/homeassistant/components/traccar/translations/uk.json new file mode 100644 index 00000000000000..5bfb1714a79256 --- /dev/null +++ b/homeassistant/components/traccar/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Traccar. \n\n\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}` \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" + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Traccar?", + "title": "Traccar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/de.json b/homeassistant/components/tradfri/translations/de.json index 3e55cb701d5ce0..b1ebb2aff0b279 100644 --- a/homeassistant/components/tradfri/translations/de.json +++ b/homeassistant/components/tradfri/translations/de.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "Bridge ist bereits konfiguriert.", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt." + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" }, "error": { - "cannot_connect": "Verbindung zum Gateway nicht m\u00f6glich.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_key": "Registrierung mit angegebenem Schl\u00fcssel fehlgeschlagen. Wenn dies weiterhin geschieht, starte den Gateway neu.", "timeout": "Timeout bei der \u00dcberpr\u00fcfung des Codes." }, diff --git a/homeassistant/components/tradfri/translations/tr.json b/homeassistant/components/tradfri/translations/tr.json new file mode 100644 index 00000000000000..e4483536b129cb --- /dev/null +++ b/homeassistant/components/tradfri/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "auth": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/uk.json b/homeassistant/components/tradfri/translations/uk.json index a163a4680e3e3c..abd25d04b6be6e 100644 --- a/homeassistant/components/tradfri/translations/uk.json +++ b/homeassistant/components/tradfri/translations/uk.json @@ -1,14 +1,22 @@ { "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_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." + }, "error": { - "cannot_connect": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0448\u043b\u044e\u0437\u0443." + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_key": "\u0427\u0438 \u043d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0437 \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u043c \u043a\u043b\u044e\u0447\u0435\u043c. \u042f\u043a\u0449\u043e \u0446\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0448\u043b\u044e\u0437.", + "timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u043a\u043e\u0434\u0443." }, "step": { "auth": { "data": { + "host": "\u0425\u043e\u0441\u0442", "security_code": "\u041a\u043e\u0434 \u0431\u0435\u0437\u043f\u0435\u043a\u0438" }, - "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u0431\u0435\u0437\u043f\u0435\u043a\u0438" + "description": "\u041a\u043e\u0434 \u0431\u0435\u0437\u043f\u0435\u043a\u0438 \u043c\u043e\u0436\u043d\u0430 \u0437\u043d\u0430\u0439\u0442\u0438 \u043d\u0430 \u0437\u0430\u0434\u043d\u0456\u0439 \u043f\u0430\u043d\u0435\u043b\u0456 \u0448\u043b\u044e\u0437\u0443.", + "title": "IKEA TR\u00c5DFRI" } } } diff --git a/homeassistant/components/transmission/translations/de.json b/homeassistant/components/transmission/translations/de.json index a133cd363e04a5..2355905d1f7564 100644 --- a/homeassistant/components/transmission/translations/de.json +++ b/homeassistant/components/transmission/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Host ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung zum Host nicht m\u00f6glich", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "name_exists": "Name existiert bereits" }, "step": { diff --git a/homeassistant/components/transmission/translations/tr.json b/homeassistant/components/transmission/translations/tr.json new file mode 100644 index 00000000000000..cffcc65151c6d6 --- /dev/null +++ b/homeassistant/components/transmission/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/uk.json b/homeassistant/components/transmission/translations/uk.json new file mode 100644 index 00000000000000..5bc74f7da2a677 --- /dev/null +++ b/homeassistant/components/transmission/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "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.", + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Transmission" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "limit": "\u041e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f", + "order": "\u041f\u043e\u0440\u044f\u0434\u043e\u043a", + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Transmission" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index 4cdcdfced79b60..67a61f81a1c5b3 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -1,9 +1,13 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "flow_title": "Tuya Konfiguration", "step": { "user": { @@ -11,7 +15,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Geben Sie Ihre Tuya-Anmeldeinformationen ein.", + "description": "Gib deine Tuya-Anmeldeinformationen ein.", "title": "Tuya" } } diff --git a/homeassistant/components/tuya/translations/lb.json b/homeassistant/components/tuya/translations/lb.json index 884eb328fe4af1..0000f9ef6e62a5 100644 --- a/homeassistant/components/tuya/translations/lb.json +++ b/homeassistant/components/tuya/translations/lb.json @@ -23,6 +23,9 @@ } }, "options": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + }, "error": { "dev_multi_type": "Multiple ausgewielte Ger\u00e4ter fir ze konfigur\u00e9ieren musse vum selwechten Typ sinn", "dev_not_config": "Typ vun Apparat net konfigur\u00e9ierbar", diff --git a/homeassistant/components/tuya/translations/tr.json b/homeassistant/components/tuya/translations/tr.json index 5a4de08033cf61..2edf3276b6c503 100644 --- a/homeassistant/components/tuya/translations/tr.json +++ b/homeassistant/components/tuya/translations/tr.json @@ -1,11 +1,39 @@ { + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "flow_title": "Tuya yap\u0131land\u0131rmas\u0131", + "step": { + "user": { + "data": { + "country_code": "Hesap \u00fclke kodunuz (\u00f6r. ABD i\u00e7in 1 veya \u00c7in i\u00e7in 86)", + "password": "Parola", + "platform": "Hesab\u0131n\u0131z\u0131n kay\u0131tl\u0131 oldu\u011fu uygulama", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Tuya kimlik bilgilerinizi girin.", + "title": "Tuya" + } + } + }, "options": { "abort": { "cannot_connect": "Ba\u011flanma hatas\u0131" }, + "error": { + "dev_not_config": "Cihaz t\u00fcr\u00fc yap\u0131land\u0131r\u0131lamaz", + "dev_not_found": "Cihaz bulunamad\u0131" + }, "step": { "device": { "data": { + "brightness_range_mode": "Cihaz\u0131n kulland\u0131\u011f\u0131 parlakl\u0131k aral\u0131\u011f\u0131", "max_temp": "Maksimum hedef s\u0131cakl\u0131k (varsay\u0131lan olarak min ve maks = 0 kullan\u0131n)", "min_kelvin": "Kelvin destekli min renk s\u0131cakl\u0131\u011f\u0131", "min_temp": "Minimum hedef s\u0131cakl\u0131k (varsay\u0131lan i\u00e7in min ve maks = 0 kullan\u0131n)", diff --git a/homeassistant/components/tuya/translations/uk.json b/homeassistant/components/tuya/translations/uk.json new file mode 100644 index 00000000000000..1d2709d260a0c6 --- /dev/null +++ b/homeassistant/components/tuya/translations/uk.json @@ -0,0 +1,63 @@ +{ + "config": { + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya", + "step": { + "user": { + "data": { + "country_code": "\u041a\u043e\u0434 \u043a\u0440\u0430\u0457\u043d\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044e)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "platform": "\u0414\u043e\u0434\u0430\u0442\u043e\u043a, \u0432 \u044f\u043a\u043e\u043c\u0443 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tuya.", + "title": "Tuya" + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "error": { + "dev_multi_type": "\u041a\u0456\u043b\u044c\u043a\u0430 \u043e\u0431\u0440\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0443.", + "dev_not_config": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", + "dev_not_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "\u0414\u0456\u0430\u043f\u0430\u0437\u043e\u043d \u044f\u0441\u043a\u0440\u0430\u0432\u043e\u0441\u0442\u0456, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", + "curr_temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", + "max_kelvin": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", + "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", + "min_kelvin": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", + "min_temp": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", + "support_color": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 \u043a\u043e\u043b\u044c\u043e\u0440\u0443", + "temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u0437\u043d\u0430\u0447\u0435\u043d\u044c \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", + "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u044f\u043a\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u044f\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", + "unit_of_measurement": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438, \u044f\u043a\u0430 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c" + }, + "description": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u0434\u0438\u043c\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u0434\u043b\u044f {device_type} \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e '{device_name}'", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Tuya" + }, + "init": { + "data": { + "discovery_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "list_devices": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0431\u043e \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u0434\u043b\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457", + "query_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0438\u0442\u0443 \u0434\u043b\u044f \u0431\u0456\u043b\u044c\u0448 \u0448\u0432\u0438\u0434\u043a\u043e\u0433\u043e \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0443", + "query_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u041d\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u044e\u0439\u0442\u0435 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u043d\u0438\u0437\u044c\u043a\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443 \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0456\u043d\u0430\u043a\u0448\u0435 \u0432\u0438\u043a\u043b\u0438\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442\u044c \u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u043e \u043f\u043e\u043c\u0438\u043b\u043a\u0443 \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0456.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/de.json b/homeassistant/components/twentemilieu/translations/de.json index 27ba9bb29c7457..38cabb6c22ec37 100644 --- a/homeassistant/components/twentemilieu/translations/de.json +++ b/homeassistant/components/twentemilieu/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_address": "Adresse nicht im Einzugsgebiet von Twente Milieu gefunden." }, "step": { diff --git a/homeassistant/components/twentemilieu/translations/tr.json b/homeassistant/components/twentemilieu/translations/tr.json new file mode 100644 index 00000000000000..590aec1894cc3b --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/uk.json b/homeassistant/components/twentemilieu/translations/uk.json new file mode 100644 index 00000000000000..435bd79fb85feb --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_address": "\u0410\u0434\u0440\u0435\u0441\u0443 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0432 \u0437\u043e\u043d\u0456 \u043e\u0431\u0441\u043b\u0443\u0433\u043e\u0432\u0443\u0432\u0430\u043d\u043d\u044f Twente Milieu." + }, + "step": { + "user": { + "data": { + "house_letter": "\u0414\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f \u0434\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0434\u043e\u043c\u0443", + "house_number": "\u041d\u043e\u043c\u0435\u0440 \u0431\u0443\u0434\u0438\u043d\u043a\u0443", + "post_code": "\u041f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Twente Milieu \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 \u0432\u0438\u0432\u0435\u0437\u0435\u043d\u043d\u044f \u0441\u043c\u0456\u0442\u0442\u044f \u0437\u0430 \u0412\u0430\u0448\u043e\u044e \u0430\u0434\u0440\u0435\u0441\u043e\u044e.", + "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 864fee4c238f22..61df22c10f86c7 100644 --- a/homeassistant/components/twilio/translations/de.json +++ b/homeassistant/components/twilio/translations/de.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "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." + }, "create_entry": { - "default": "Um Ereignisse an den Home Assistant zu senden, musst du [Webhooks mit Twilio]({twilio_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / x-www-form-urlencoded \n\nLies in der [Dokumentation]({docs_url}) wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." + "default": "Um Ereignisse an Home Assistant zu senden, musst du [Webhooks mit Twilio]({twilio_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / x-www-form-urlencoded \n\nLies in der [Dokumentation]({docs_url}), wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." }, "step": { "user": { - "description": "M\u00f6chtest du Twilio wirklich einrichten?", + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Twilio-Webhook einrichten" } } diff --git a/homeassistant/components/twilio/translations/lb.json b/homeassistant/components/twilio/translations/lb.json index 2721402c1f3a53..7889f244c6eca8 100644 --- a/homeassistant/components/twilio/translations/lb.json +++ b/homeassistant/components/twilio/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech.", + "webhook_not_internet_accessible": "Deng Home Assistant Instanz muss iwwert Internet accessibel si fir Webhook Noriichten z'empf\u00e4nken." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Twilio]({twilio_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." diff --git a/homeassistant/components/twilio/translations/tr.json b/homeassistant/components/twilio/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/twilio/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/uk.json b/homeassistant/components/twilio/translations/uk.json new file mode 100644 index 00000000000000..8ea0ce86a37a09 --- /dev/null +++ b/homeassistant/components/twilio/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f [Twilio]({twilio_url}). \n\n\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded \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 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0456\u0439 \u043f\u043e \u043e\u0431\u0440\u043e\u0431\u0446\u0456 \u0434\u0430\u043d\u0438\u0445, \u0449\u043e \u043d\u0430\u0434\u0445\u043e\u0434\u044f\u0442\u044c." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "Twilio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/de.json b/homeassistant/components/twinkly/translations/de.json index 2b4c70a0bad02a..c196f53262dd13 100644 --- a/homeassistant/components/twinkly/translations/de.json +++ b/homeassistant/components/twinkly/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "device_exists": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/twinkly/translations/fr.json b/homeassistant/components/twinkly/translations/fr.json new file mode 100644 index 00000000000000..5071b7e302a6a7 --- /dev/null +++ b/homeassistant/components/twinkly/translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "device_exists": "D\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Connexion impossible" + }, + "step": { + "user": { + "data": { + "host": "Nom r\u00e9seau (ou adresse IP) de votre Twinkly" + }, + "description": "Configurer votre Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/lb.json b/homeassistant/components/twinkly/translations/lb.json new file mode 100644 index 00000000000000..2e00a8ae4dbfb8 --- /dev/null +++ b/homeassistant/components/twinkly/translations/lb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "device_exists": "Apparat ass scho konfigur\u00e9iert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/tr.json b/homeassistant/components/twinkly/translations/tr.json index 14365f988bde6c..d2e7173dad3282 100644 --- a/homeassistant/components/twinkly/translations/tr.json +++ b/homeassistant/components/twinkly/translations/tr.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "device_exists": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/twinkly/translations/uk.json b/homeassistant/components/twinkly/translations/uk.json new file mode 100644 index 00000000000000..bd256d31b0328b --- /dev/null +++ b/homeassistant/components/twinkly/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "device_exists": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0406\u043c'\u044f \u0445\u043e\u0441\u0442\u0430 (\u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430) \u0412\u0430\u0448\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Twinkly" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0432\u0456\u0442\u043b\u043e\u0434\u0456\u043e\u0434\u043d\u043e\u0457 \u0441\u0442\u0440\u0456\u0447\u043a\u0438 Twinkly", + "title": "Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index a07c034fe12a4f..f1cf4a6349b17b 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "El lloc del controlador ja est\u00e0 configurat" + "already_configured": "El lloc del controlador ja est\u00e0 configurat", + "configuration_updated": "S'ha actualitzat la configuraci\u00f3.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "faulty_credentials": "[%key::common::config_flow::error::invalid_auth%]", "service_unavailable": "[%key::common::config_flow::error::cannot_connect%]", "unknown_client_mac": "No hi ha cap client disponible en aquesta adre\u00e7a MAC" }, + "flow_title": "Xarxa UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/cs.json b/homeassistant/components/unifi/translations/cs.json index 1247a97de9dd15..0281dfbb7500c1 100644 --- a/homeassistant/components/unifi/translations/cs.json +++ b/homeassistant/components/unifi/translations/cs.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "Ovlada\u010d je ji\u017e nastaven" + "already_configured": "Ovlada\u010d je ji\u017e nastaven", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "faulty_credentials": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "service_unavailable": "Nepoda\u0159ilo se p\u0159ipojit", "unknown_client_mac": "Na t\u00e9to MAC adrese nen\u00ed dostupn\u00fd \u017e\u00e1dn\u00fd klient" }, + "flow_title": "UniFi s\u00ed\u0165 {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/da.json b/homeassistant/components/unifi/translations/da.json index 15ec878f1cee37..84dafd36e1a45c 100644 --- a/homeassistant/components/unifi/translations/da.json +++ b/homeassistant/components/unifi/translations/da.json @@ -7,6 +7,7 @@ "faulty_credentials": "Ugyldige legitimationsoplysninger", "service_unavailable": "Service utilg\u00e6ngelig" }, + "flow_title": "UniFi-netv\u00e6rket {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index 626236792ea947..be38ddf1a4d760 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Controller-Site ist bereits konfiguriert" }, "error": { - "faulty_credentials": "Ung\u00fcltige Anmeldeinformationen", + "faulty_credentials": "Ung\u00fcltige Authentifizierung", "service_unavailable": "Verbindung fehlgeschlagen", "unknown_client_mac": "Unter dieser MAC-Adresse ist kein Client verf\u00fcgbar." }, @@ -16,7 +16,7 @@ "port": "Port", "site": "Site-ID", "username": "Benutzername", - "verify_ssl": "Controller mit ordnungsgem\u00e4ssem Zertifikat" + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, "title": "UniFi-Controller einrichten" } @@ -51,7 +51,9 @@ }, "simple_options": { "data": { - "track_clients": "Netzwerk Ger\u00e4te \u00fcberwachen" + "block_client": "Clients mit Netzwerkzugriffskontrolle", + "track_clients": "Netzwerger\u00e4te \u00fcberwachen", + "track_devices": "Verfolgen von Netzwerkger\u00e4ten (Ubiquiti-Ger\u00e4te)" }, "description": "Konfigurieren Sie die UniFi-Integration" }, diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 06e8ae1eb60ab3..41507faa430c1d 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Controller site is already configured", + "configuration_updated": "Configuration updated.", "reauth_successful": "Re-authentication was successful" }, "error": { diff --git a/homeassistant/components/unifi/translations/es.json b/homeassistant/components/unifi/translations/es.json index 0fa4aaf2eb7a38..a676d70e88c484 100644 --- a/homeassistant/components/unifi/translations/es.json +++ b/homeassistant/components/unifi/translations/es.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "El sitio del controlador ya est\u00e1 configurado" + "already_configured": "El sitio del controlador ya est\u00e1 configurado", + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "faulty_credentials": "Autenticaci\u00f3n no v\u00e1lida", "service_unavailable": "Error al conectar", "unknown_client_mac": "Ning\u00fan cliente disponible en esa direcci\u00f3n MAC" }, + "flow_title": "Red UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/et.json b/homeassistant/components/unifi/translations/et.json index 8e95da9aa5b265..e9d76520435cf1 100644 --- a/homeassistant/components/unifi/translations/et.json +++ b/homeassistant/components/unifi/translations/et.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Kontroller on juba seadistatud" + "already_configured": "Kontroller on juba seadistatud", + "configuration_updated": "Seaded on v\u00e4rskendatud.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "faulty_credentials": "Tuvastamine nurjus", "service_unavailable": "\u00dchendamine nurjus", "unknown_client_mac": "Sellel MAC-aadressil pole \u00fchtegi klienti saadaval" }, + "flow_title": "UniFi Network {site} ( {host} )", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index 79a7206923e6c1..d50018227c5d0a 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato" + "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato", + "configuration_updated": "Configurazione aggiornata.", + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { "faulty_credentials": "Autenticazione non valida", "service_unavailable": "Impossibile connettersi", "unknown_client_mac": "Nessun client disponibile su quell'indirizzo MAC" }, + "flow_title": "Rete UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/no.json b/homeassistant/components/unifi/translations/no.json index 5cda9ad7ab5748..72944a9d540b8b 100644 --- a/homeassistant/components/unifi/translations/no.json +++ b/homeassistant/components/unifi/translations/no.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Kontroller nettstedet er allerede konfigurert" + "already_configured": "Kontroller nettstedet er allerede konfigurert", + "configuration_updated": "Konfigurasjonen er oppdatert.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { "faulty_credentials": "Ugyldig godkjenning", "service_unavailable": "Tilkobling mislyktes", "unknown_client_mac": "Ingen klient tilgjengelig p\u00e5 den MAC-adressen" }, + "flow_title": "UniFi-nettverk {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index 8ff5f1e4793163..6c8c74e726af8d 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana", + "configuration_updated": "Konfiguracja zaktualizowana", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "faulty_credentials": "Niepoprawne uwierzytelnienie", "service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "unknown_client_mac": "Brak klienta z tym adresem MAC" }, + "flow_title": "Sie\u0107 UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 789212dca17efe..3b69bf0ee33f11 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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": { "faulty_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "service_unavailable": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "unknown_client_mac": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043d\u0430 \u044d\u0442\u043e\u043c MAC-\u0430\u0434\u0440\u0435\u0441\u0435." }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/tr.json b/homeassistant/components/unifi/translations/tr.json index 903a7aaa21f7a0..c39fa08217aa3a 100644 --- a/homeassistant/components/unifi/translations/tr.json +++ b/homeassistant/components/unifi/translations/tr.json @@ -1,9 +1,22 @@ { "config": { + "abort": { + "already_configured": "Denetleyici sitesi zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "configuration_updated": "Yap\u0131land\u0131rma g\u00fcncellendi.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "faulty_credentials": "Ge\u00e7ersiz kimlik do\u011frulama", + "service_unavailable": "Ba\u011flanma hatas\u0131", + "unknown_client_mac": "Bu MAC adresinde kullan\u0131labilir istemci yok" + }, + "flow_title": "UniFi A\u011f\u0131 {site} ( {host} )", "step": { "user": { "data": { + "host": "Ana Bilgisayar", "password": "Parola", + "port": "Port", "username": "Kullan\u0131c\u0131 ad\u0131" } } diff --git a/homeassistant/components/unifi/translations/uk.json b/homeassistant/components/unifi/translations/uk.json new file mode 100644 index 00000000000000..0f83c35840a8d7 --- /dev/null +++ b/homeassistant/components/unifi/translations/uk.json @@ -0,0 +1,66 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "faulty_credentials": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "service_unavailable": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "unknown_client_mac": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 \u043d\u0430 \u0446\u0456\u0439 MAC-\u0430\u0434\u0440\u0435\u0441\u0456." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "site": "ID \u0441\u0430\u0439\u0442\u0443", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "UniFi Controller" + } + } + }, + "options": { + "step": { + "client_control": { + "data": { + "block_client": "\u041a\u043b\u0456\u0454\u043d\u0442\u0438 \u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u043c \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "dpi_restrictions": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u0433\u0440\u0443\u043f\u0430\u043c\u0438 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u044c DPI", + "poe_clients": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 POE \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0456\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f. \n\n\u0421\u0442\u0432\u043e\u0440\u0456\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447\u0456 \u0434\u043b\u044f \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u0445 \u043d\u043e\u043c\u0435\u0440\u0456\u0432, \u0434\u043b\u044f \u044f\u043a\u0438\u0445 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044e\u0432\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f UniFi. \u041a\u0440\u043e\u043a 2." + }, + "device_tracker": { + "data": { + "detection_time": "\u0427\u0430\u0441 \u0432\u0456\u0434 \u043e\u0441\u0442\u0430\u043d\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0443 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c (\u0441\u0435\u043a.), \u043f\u043e \u0437\u0430\u043a\u0456\u043d\u0447\u0435\u043d\u043d\u044e \u044f\u043a\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043e\u0442\u0440\u0438\u043c\u0430\u0454 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\".", + "ignore_wired_bug": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043b\u043e\u0433\u0456\u043a\u0443 \u043f\u043e\u043c\u0438\u043b\u043a\u0438 \u0434\u043b\u044f \u0434\u0440\u043e\u0442\u043e\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 UniFi", + "ssid_filter": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c SSID \u0434\u043b\u044f \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u0431\u0435\u0437\u0434\u0440\u043e\u0442\u043e\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432", + "track_clients": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 \u043c\u0435\u0440\u0435\u0436\u0456", + "track_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 (\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 Ubiquiti)", + "track_wired_clients": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u043e\u0432\u0456\u0434\u043d\u0438\u0445 \u043c\u0435\u0440\u0435\u0436\u043d\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f UniFi. \u041a\u0440\u043e\u043a 1" + }, + "simple_options": { + "data": { + "block_client": "\u041a\u043b\u0456\u0454\u043d\u0442\u0438 \u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u043c \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "track_clients": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 \u043c\u0435\u0440\u0435\u0436\u0456", + "track_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 (\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 Ubiquiti)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 UniFi." + }, + "statistics_sensors": { + "data": { + "allow_bandwidth_sensors": "\u0414\u0430\u0442\u0447\u0438\u043a\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0457 \u0437\u0434\u0430\u0442\u043d\u043e\u0441\u0442\u0456 \u0434\u043b\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432", + "allow_uptime_sensors": "\u0421\u0435\u043d\u0441\u043e\u0440\u0438 \u0447\u0430\u0441\u0443 \u0440\u043e\u0431\u043e\u0442\u0438 \u0434\u043b\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f UniFi. \u043a\u0440\u043e\u043a 3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/zh-Hant.json b/homeassistant/components/unifi/translations/zh-Hant.json index d87f8cf51e08e1..add0a387309e7e 100644 --- a/homeassistant/components/unifi/translations/zh-Hant.json +++ b/homeassistant/components/unifi/translations/zh-Hant.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "\u63a7\u5236\u5668\u4f4d\u5740\u5df2\u7d93\u8a2d\u5b9a" + "already_configured": "\u63a7\u5236\u5668\u4f4d\u5740\u5df2\u7d93\u8a2d\u5b9a", + "configuration_updated": "\u8a2d\u5b9a\u5df2\u66f4\u65b0\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "faulty_credentials": "\u9a57\u8b49\u78bc\u7121\u6548", "service_unavailable": "\u9023\u7dda\u5931\u6557", "unknown_client_mac": "\u8a72 Mac \u4f4d\u5740\u7121\u53ef\u7528\u5ba2\u6236\u7aef" }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/upb/translations/de.json b/homeassistant/components/upb/translations/de.json index ea6f1d3715049d..908db20f22b6a7 100644 --- a/homeassistant/components/upb/translations/de.json +++ b/homeassistant/components/upb/translations/de.json @@ -1,9 +1,12 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Fehler beim Herstellen einer Verbindung zu UPB PIM. Versuchen Sie es erneut.", - "invalid_upb_file": "Fehlende oder ung\u00fcltige UPB UPStart-Exportdatei, \u00fcberpr\u00fcfen Sie den Namen und den Pfad der Datei.", - "unknown": "Unerwarteter Fehler." + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_upb_file": "Fehlende oder ung\u00fcltige UPB UPStart-Exportdatei, \u00fcberpr\u00fcfe den Namen und den Pfad der Datei.", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { @@ -12,7 +15,7 @@ "file_path": "Pfad und Name der UPStart UPB-Exportdatei.", "protocol": "Protokoll" }, - "title": "Stellen Sie eine Verbindung zu UPB PIM her" + "title": "Stelle eine Verbindung zu UPB PIM her" } } } diff --git a/homeassistant/components/upb/translations/tr.json b/homeassistant/components/upb/translations/tr.json new file mode 100644 index 00000000000000..818531fcaa059f --- /dev/null +++ b/homeassistant/components/upb/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/uk.json b/homeassistant/components/upb/translations/uk.json new file mode 100644 index 00000000000000..062503848a8d0c --- /dev/null +++ b/homeassistant/components/upb/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_upb_file": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0439 \u0430\u0431\u043e \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u0444\u0430\u0439\u043b \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u0443 UPB UPStart, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0456\u043c'\u044f \u0456 \u0448\u043b\u044f\u0445 \u0434\u043e \u0444\u0430\u0439\u043b\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "address": "\u0410\u0434\u0440\u0435\u0441\u0430 (\u0434\u0438\u0432. \u043e\u043f\u0438\u0441 \u0432\u0438\u0449\u0435)", + "file_path": "\u0428\u043b\u044f\u0445 \u0456 \u0456\u043c'\u044f \u0444\u0430\u0439\u043b\u0443 \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u0443 UPStart UPB.", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b" + }, + "description": "\u0420\u044f\u0434\u043e\u043a \u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'tcp' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '192.168.1.42'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 2101. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'serial' \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 4800.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e UPB PIM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/de.json b/homeassistant/components/upcloud/translations/de.json index 76bbc70569017a..ee1802f1d38132 100644 --- a/homeassistant/components/upcloud/translations/de.json +++ b/homeassistant/components/upcloud/translations/de.json @@ -1,7 +1,8 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/tr.json b/homeassistant/components/upcloud/translations/tr.json new file mode 100644 index 00000000000000..f1840698493b58 --- /dev/null +++ b/homeassistant/components/upcloud/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "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/upcloud/translations/uk.json b/homeassistant/components/upcloud/translations/uk.json new file mode 100644 index 00000000000000..bf8781c1eb2a4a --- /dev/null +++ b/homeassistant/components/upcloud/translations/uk.json @@ -0,0 +1,25 @@ +{ + "config": { + "error": { + "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." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0456\u043d\u0456\u043c\u0443\u043c 30)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ro.json b/homeassistant/components/upnp/translations/ro.json index ceb1c19131abe1..2fd83a0b371bf4 100644 --- a/homeassistant/components/upnp/translations/ro.json +++ b/homeassistant/components/upnp/translations/ro.json @@ -7,6 +7,13 @@ "few": "", "one": "Unul", "other": "" + }, + "step": { + "init": { + "few": "Pu\u021bine", + "one": "Unul", + "other": "Altele" + } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/tr.json b/homeassistant/components/upnp/translations/tr.json new file mode 100644 index 00000000000000..2715f66e090919 --- /dev/null +++ b/homeassistant/components/upnp/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "flow_title": "UPnP / IGD: {name}", + "step": { + "ssdp_confirm": { + "description": "Bu UPnP / IGD cihaz\u0131n\u0131 kurmak istiyor musunuz?" + }, + "user": { + "data": { + "scan_interval": "G\u00fcncelleme aral\u0131\u011f\u0131 (saniye, minimum 30)", + "usn": "Cihaz" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/uk.json b/homeassistant/components/upnp/translations/uk.json index 0b8747f902ec7f..905958eeca9706 100644 --- a/homeassistant/components/upnp/translations/uk.json +++ b/homeassistant/components/upnp/translations/uk.json @@ -1,7 +1,21 @@ { "config": { "abort": { - "already_configured": "UPnP/IGD \u0432\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0454\u043d\u043e" + "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.", + "incomplete_discovery": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441.", + "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." + }, + "flow_title": "UPnP/IGD: {name}", + "step": { + "ssdp_confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 UPnP / IGD?" + }, + "user": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0456\u043d\u0456\u043c\u0443\u043c 30)", + "usn": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/de.json b/homeassistant/components/vacuum/translations/de.json index be137a5566bd1b..8de386b3506e18 100644 --- a/homeassistant/components/vacuum/translations/de.json +++ b/homeassistant/components/vacuum/translations/de.json @@ -18,7 +18,7 @@ "cleaning": "Reinigen", "docked": "Angedockt", "error": "Fehler", - "idle": "Standby", + "idle": "Unt\u00e4tig", "off": "Aus", "on": "An", "paused": "Pausiert", diff --git a/homeassistant/components/vacuum/translations/uk.json b/homeassistant/components/vacuum/translations/uk.json index 9febc8aff1f3c5..64223a85f74491 100644 --- a/homeassistant/components/vacuum/translations/uk.json +++ b/homeassistant/components/vacuum/translations/uk.json @@ -1,4 +1,18 @@ { + "device_automation": { + "action_type": { + "clean": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u0438\u0442\u0438 {entity_name} \u0440\u043e\u0431\u0438\u0442\u0438 \u043f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", + "dock": "{entity_name}: \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u044e" + }, + "condition_type": { + "is_cleaning": "{entity_name} \u0432\u0438\u043a\u043e\u043d\u0443\u0454 \u043f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", + "is_docked": "{entity_name} \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u0457" + }, + "trigger_type": { + "cleaning": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u043f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", + "docked": "{entity_name} \u0441\u0442\u0438\u043a\u0443\u0454\u0442\u044c\u0441\u044f \u0437 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u0454\u044e" + } + }, "state": { "_": { "cleaning": "\u041f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", @@ -8,7 +22,7 @@ "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", "paused": "\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0435\u043d\u043e", - "returning": "\u041f\u043e\u0432\u0435\u0440\u043d\u0435\u043d\u043d\u044f \u0434\u043e \u0434\u043e\u043a\u0430" + "returning": "\u041f\u043e\u0432\u0435\u0440\u043d\u0435\u043d\u043d\u044f \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u044e" } }, "title": "\u041f\u0438\u043b\u043e\u0441\u043e\u0441" diff --git a/homeassistant/components/velbus/translations/de.json b/homeassistant/components/velbus/translations/de.json index c6c872c85e6c82..9bbb23b1bcd85c 100644 --- a/homeassistant/components/velbus/translations/de.json +++ b/homeassistant/components/velbus/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/velbus/translations/tr.json b/homeassistant/components/velbus/translations/tr.json new file mode 100644 index 00000000000000..e7ee4ea7157311 --- /dev/null +++ b/homeassistant/components/velbus/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/uk.json b/homeassistant/components/velbus/translations/uk.json new file mode 100644 index 00000000000000..6e8b97cc4579bf --- /dev/null +++ b/homeassistant/components/velbus/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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" + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u0420\u044f\u0434\u043e\u043a \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "Velbus" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/tr.json b/homeassistant/components/vera/translations/tr.json new file mode 100644 index 00000000000000..35e81599bb1dc5 --- /dev/null +++ b/homeassistant/components/vera/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "cannot_connect": "{base_url} url'si ile denetleyiciye ba\u011flan\u0131lamad\u0131" + } + }, + "options": { + "step": { + "init": { + "title": "Vera denetleyici se\u00e7enekleri" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/uk.json b/homeassistant/components/vera/translations/uk.json new file mode 100644 index 00000000000000..8c591a1cc105eb --- /dev/null +++ b/homeassistant/components/vera/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u043e\u043c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e {base_url}." + }, + "step": { + "user": { + "data": { + "exclude": "ID \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera, \u0434\u043b\u044f \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0437 Home Assistant", + "lights": "ID \u0432\u0438\u043c\u0438\u043a\u0430\u0447\u0456\u0432 Vera, \u0434\u043b\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443 \u0432 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f", + "vera_controller_url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.1.161:3480').", + "title": "Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera \u0434\u043b\u044f \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0437 Home Assistant.", + "lights": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043f\u0440\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0437 \u0432\u0438\u043c\u0438\u043a\u0430\u0447\u0430 \u0432 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f \u0432 Home Assistant." + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438 \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 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438: https://www.home-assistant.io/integrations/vera/.\n\u0414\u043b\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u0445 \u0437\u043c\u0456\u043d \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Home Assistant. \u0429\u043e\u0431 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f, \u043f\u043e\u0441\u0442\u0430\u0432\u0442\u0435 \u043f\u0440\u043e\u0431\u0456\u043b.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/de.json b/homeassistant/components/vesync/translations/de.json index c52b10c3293db8..ea05a60ff82a37 100644 --- a/homeassistant/components/vesync/translations/de.json +++ b/homeassistant/components/vesync/translations/de.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vesync/translations/tr.json b/homeassistant/components/vesync/translations/tr.json new file mode 100644 index 00000000000000..8b4f8b6063058a --- /dev/null +++ b/homeassistant/components/vesync/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "title": "Kullan\u0131c\u0131 Ad\u0131 ve \u015eifre Girin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/uk.json b/homeassistant/components/vesync/translations/uk.json new file mode 100644 index 00000000000000..7f6b3a46b15524 --- /dev/null +++ b/homeassistant/components/vesync/translations/uk.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "VeSync" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/de.json b/homeassistant/components/vilfo/translations/de.json index 4880154b58e42c..8f20c074ff4913 100644 --- a/homeassistant/components/vilfo/translations/de.json +++ b/homeassistant/components/vilfo/translations/de.json @@ -4,9 +4,9 @@ "already_configured": "Dieser Vilfo Router ist bereits konfiguriert." }, "error": { - "cannot_connect": "Verbindung nicht m\u00f6glich. Bitte \u00fcberpr\u00fcfen Sie die von Ihnen angegebenen Informationen und versuchen Sie es erneut.", - "invalid_auth": "Ung\u00fcltige Authentifizierung. Bitte \u00fcberpr\u00fcfen Sie den Zugriffstoken und versuchen Sie es erneut.", - "unknown": "Beim Einrichten der Integration ist ein unerwarteter Fehler aufgetreten." + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung. Bitte \u00fcberpr\u00fcfe den Zugriffstoken und versuche es erneut.", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/tr.json b/homeassistant/components/vilfo/translations/tr.json new file mode 100644 index 00000000000000..dc66041e35a2aa --- /dev/null +++ b/homeassistant/components/vilfo/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/uk.json b/homeassistant/components/vilfo/translations/uk.json new file mode 100644 index 00000000000000..1a93176f290740 --- /dev/null +++ b/homeassistant/components/vilfo/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Vilfo. \u0412\u043a\u0430\u0436\u0456\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0456 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 API. \u0414\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 \u043f\u0440\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457, \u0432\u0456\u0434\u0432\u0456\u0434\u0430\u0439\u0442\u0435 \u0432\u0435\u0431-\u0441\u0430\u0439\u0442: https://www.home-assistant.io/integrations/vilfo.", + "title": "Vilfo Router" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/de.json b/homeassistant/components/vizio/translations/de.json index ddb68ec09faa98..ad0cc604d133dd 100644 --- a/homeassistant/components/vizio/translations/de.json +++ b/homeassistant/components/vizio/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", "updated_entry": "Dieser Eintrag wurde bereits eingerichtet, aber der Name, die Apps und / oder die in der Konfiguration definierten Optionen stimmen nicht mit der zuvor importierten Konfiguration \u00fcberein, sodass der Konfigurationseintrag entsprechend aktualisiert wurde." }, "error": { @@ -10,17 +11,17 @@ "step": { "pair_tv": { "data": { - "pin": "PIN" + "pin": "PIN-Code" }, "description": "Ihr Fernseher sollte einen Code anzeigen. Geben Sie diesen Code in das Formular ein und fahren Sie mit dem n\u00e4chsten Schritt fort, um die Kopplung abzuschlie\u00dfen.", "title": "Schlie\u00dfen Sie den Pairing-Prozess ab" }, "pairing_complete": { - "description": "Ihr VIZIO SmartCast-Ger\u00e4t ist jetzt mit Home Assistant verbunden.", + "description": "Dein Richten Sie das VIZIO SmartCast-Ger\u00e4t ein ist jetzt mit Home Assistant verbunden.", "title": "Kopplung abgeschlossen" }, "pairing_complete_import": { - "description": "Ihr VIZIO SmartCast-Fernseher ist jetzt mit Home Assistant verbunden. \n\n Ihr Zugriffstoken ist '**{access_token}**'.", + "description": "Dein Richten Sie das VIZIO SmartCast-Ger\u00e4t ein ist jetzt mit Home Assistant verbunden.\n\nDein Zugangstoken ist '**{access_token}**'.", "title": "Kopplung abgeschlossen" }, "user": { @@ -30,7 +31,7 @@ "host": "Host", "name": "Name" }, - "description": "Ein Zugriffstoken wird nur f\u00fcr Fernsehger\u00e4te ben\u00f6tigt. Wenn Sie ein Fernsehger\u00e4t konfigurieren und noch kein Zugriffstoken haben, lassen Sie es leer, um einen Pairing-Vorgang durchzuf\u00fchren.", + "description": "Ein Zugangstoken wird nur f\u00fcr Fernsehger\u00e4te ben\u00f6tigt. Wenn du ein Fernsehger\u00e4t konfigurierst und noch kein Zugangstoken hast, lass es leer, um einen Pairing-Vorgang durchzuf\u00fchren.", "title": "Richten Sie das VIZIO SmartCast-Ger\u00e4t ein" } } @@ -44,7 +45,7 @@ "volume_step": "Lautst\u00e4rken-Schrittgr\u00f6\u00dfe" }, "description": "Wenn Sie \u00fcber ein Smart-TV-Ger\u00e4t verf\u00fcgen, k\u00f6nnen Sie Ihre Quellliste optional filtern, indem Sie ausw\u00e4hlen, welche Apps in Ihre Quellliste aufgenommen oder ausgeschlossen werden sollen.", - "title": "Aktualisieren Sie die VIZIO SmartCast-Optionen" + "title": "Aktualisiere die Richten Sie das VIZIO SmartCast-Ger\u00e4t ein-Optionen" } } } diff --git a/homeassistant/components/vizio/translations/tr.json b/homeassistant/components/vizio/translations/tr.json new file mode 100644 index 00000000000000..4b923cfb4b3b8a --- /dev/null +++ b/homeassistant/components/vizio/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/uk.json b/homeassistant/components/vizio/translations/uk.json new file mode 100644 index 00000000000000..958307d543f92f --- /dev/null +++ b/homeassistant/components/vizio/translations/uk.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured_device": "\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", + "updated_entry": "\u0426\u044f \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439, \u0430\u043b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438, \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u0432 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457, \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u044e\u0442\u044c \u0440\u0430\u043d\u0456\u0448\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u043e\u0432\u0430\u043d\u0438\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f\u043c, \u0442\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 \u0431\u0443\u043b\u0430 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u0438\u043c \u0447\u0438\u043d\u043e\u043c \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "complete_pairing_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438. \u041f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0438 \u0441\u043f\u0440\u043e\u0431\u0443, \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u0412\u0430\u043c\u0438 PIN-\u043a\u043e\u0434 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439, \u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0456 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456.", + "existing_config_entry_found": "\u0406\u0441\u043d\u0443\u044e\u0447\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 VIZIO SmartCast \u0437 \u0442\u0430\u043a\u0438\u043c \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0456\u0441\u043d\u0443\u044e\u0447\u0438\u0439 \u0437\u0430\u043f\u0438\u0441, \u0449\u043e\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0442\u043e\u0447\u043d\u0438\u0439." + }, + "step": { + "pair_tv": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0430\u0448 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0437\u0430\u0440\u0430\u0437 \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u043a\u043e\u0434. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0446\u0435\u0439 \u043a\u043e\u0434 \u0443 \u0444\u043e\u0440\u043c\u0443, \u0430 \u043f\u043e\u0442\u0456\u043c \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u043a\u0440\u043e\u043a\u0443, \u0449\u043e\u0431 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", + "title": "\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0443 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "pairing_complete": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 VIZIO SmartCast \u0442\u0435\u043f\u0435\u0440 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e" + }, + "pairing_complete_import": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 VIZIO SmartCast \u0442\u0435\u043f\u0435\u0440 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant. \n\n \u0412\u0430\u0448 \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 - '** {access_token} **'.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e" + }, + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "device_class": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0456\u0432. \u042f\u043a\u0449\u043e \u0412\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0443\u0454\u0442\u0435 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0456 \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0443 \u0412\u0430\u0441 \u0449\u0435 \u043d\u0435 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u0446\u0435 \u043f\u043e\u043b\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u0449\u043e\u0431 \u0432\u0438\u043a\u043e\u043d\u0430\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", + "title": "VIZIO SmartCast" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0436\u0435\u0440\u0435\u043b", + "include_or_exclude": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0430\u0431\u043e \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u0436\u0435\u0440\u0435\u043b\u0430?", + "volume_step": "\u041a\u0440\u043e\u043a \u0433\u0443\u0447\u043d\u043e\u0441\u0442\u0456" + }, + "description": "\u042f\u043a\u0449\u043e \u0443 \u0432\u0430\u0441 \u0454 Smart TV, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438 \u0431\u0430\u0436\u0430\u043d\u043d\u0456 \u0432\u0456\u0434\u0444\u0456\u043b\u044c\u0442\u0440\u0443\u0432\u0430\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0436\u0435\u0440\u0435\u043b, \u0432\u043a\u043b\u044e\u0447\u0438\u0432\u0448\u0438 \u0430\u0431\u043e \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0432\u0448\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438 \u0437\u0456 \u0441\u043f\u0438\u0441\u043a\u0443.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f VIZIO SmartCast" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/de.json b/homeassistant/components/volumio/translations/de.json index ef455299de6e66..45727d85ee05d0 100644 --- a/homeassistant/components/volumio/translations/de.json +++ b/homeassistant/components/volumio/translations/de.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/tr.json b/homeassistant/components/volumio/translations/tr.json new file mode 100644 index 00000000000000..249bb17d64ecac --- /dev/null +++ b/homeassistant/components/volumio/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ke\u015ffedilen Volumio'ya ba\u011flan\u0131lam\u0131yor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/uk.json b/homeassistant/components/volumio/translations/uk.json index 58947e14e4f7e0..c517eafa2bdbd4 100644 --- a/homeassistant/components/volumio/translations/uk.json +++ b/homeassistant/components/volumio/translations/uk.json @@ -1,14 +1,16 @@ { "config": { "abort": { - "already_configured": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0438\u043c Volumio." }, "error": { - "cannot_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "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": { "discovery_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Volumio `{name}`?", "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e Volumio" }, "user": { diff --git a/homeassistant/components/water_heater/translations/uk.json b/homeassistant/components/water_heater/translations/uk.json new file mode 100644 index 00000000000000..d6558828a8ec91 --- /dev/null +++ b/homeassistant/components/water_heater/translations/uk.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/de.json b/homeassistant/components/wemo/translations/de.json index f20ad5598ab2b2..81694f65ea295f 100644 --- a/homeassistant/components/wemo/translations/de.json +++ b/homeassistant/components/wemo/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine Wemo-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von Wemo ist zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/tr.json b/homeassistant/components/wemo/translations/tr.json index 411a536ceedbad..a87d832eece777 100644 --- a/homeassistant/components/wemo/translations/tr.json +++ b/homeassistant/components/wemo/translations/tr.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "no_devices_found": "A\u011fda Wemo cihaz\u0131 bulunamad\u0131." + "no_devices_found": "A\u011fda Wemo cihaz\u0131 bulunamad\u0131.", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Wemo'yu kurmak istiyor musunuz?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/uk.json b/homeassistant/components/wemo/translations/uk.json new file mode 100644 index 00000000000000..1217d664234241 --- /dev/null +++ b/homeassistant/components/wemo/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Wemo?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/tr.json b/homeassistant/components/wiffi/translations/tr.json new file mode 100644 index 00000000000000..26ec2e61e002e8 --- /dev/null +++ b/homeassistant/components/wiffi/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "addr_in_use": "Sunucu ba\u011flant\u0131 noktas\u0131 zaten kullan\u0131l\u0131yor.", + "start_server_failed": "Ba\u015flatma sunucusu ba\u015far\u0131s\u0131z oldu." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Zaman a\u015f\u0131m\u0131 (dakika)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/uk.json b/homeassistant/components/wiffi/translations/uk.json new file mode 100644 index 00000000000000..dc8dac9cd56ecf --- /dev/null +++ b/homeassistant/components/wiffi/translations/uk.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "addr_in_use": "\u041f\u043e\u0440\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", + "start_server_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440." + }, + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f TCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 WIFFI" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/de.json b/homeassistant/components/wilight/translations/de.json index 07d00495af7f9d..d56e782279aa39 100644 --- a/homeassistant/components/wilight/translations/de.json +++ b/homeassistant/components/wilight/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "flow_title": "WiLight: {name}", "step": { "confirm": { diff --git a/homeassistant/components/wilight/translations/tr.json b/homeassistant/components/wilight/translations/tr.json new file mode 100644 index 00000000000000..5307276a71d3a3 --- /dev/null +++ b/homeassistant/components/wilight/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/uk.json b/homeassistant/components/wilight/translations/uk.json new file mode 100644 index 00000000000000..7517538499e413 --- /dev/null +++ b/homeassistant/components/wilight/translations/uk.json @@ -0,0 +1,16 @@ +{ + "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.", + "not_supported_device": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430\u0440\u0430\u0437\u0456 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "not_wilight_device": "\u0426\u0435 \u043d\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 WiLight." + }, + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 WiLight {name}? \n\n \u0426\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454: {components}", + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/de.json b/homeassistant/components/withings/translations/de.json index d217640e44b835..05d3795a0b01ff 100644 --- a/homeassistant/components/withings/translations/de.json +++ b/homeassistant/components/withings/translations/de.json @@ -1,22 +1,30 @@ { "config": { "abort": { - "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Autorisierungs-URL.", - "missing_configuration": "Die Withings-Integration ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "already_configured": "Konfiguration des Profils aktualisiert.", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "create_entry": { "default": "Erfolgreiche Authentifizierung mit Withings." }, + "error": { + "already_configured": "Konto wurde bereits konfiguriert" + }, "step": { "pick_implementation": { - "title": "Authentifizierungsmethode ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" }, "profile": { "data": { - "profile": "Profil" + "profile": "Profilname" }, "description": "Welches Profil hast du auf der Withings-Website ausgew\u00e4hlt? Es ist wichtig, dass die Profile \u00fcbereinstimmen, da sonst die Daten falsch beschriftet werden.", "title": "Benutzerprofil" + }, + "reauth": { + "title": "Integration erneut authentifizieren" } } } diff --git a/homeassistant/components/withings/translations/fr.json b/homeassistant/components/withings/translations/fr.json index 017a9e63078763..b5f524698f5e96 100644 --- a/homeassistant/components/withings/translations/fr.json +++ b/homeassistant/components/withings/translations/fr.json @@ -10,7 +10,7 @@ "default": "Authentifi\u00e9 avec succ\u00e8s \u00e0 Withings pour le profil s\u00e9lectionn\u00e9." }, "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "flow_title": "Withings: {profile}", "step": { diff --git a/homeassistant/components/withings/translations/tr.json b/homeassistant/components/withings/translations/tr.json new file mode 100644 index 00000000000000..4e0228708ea085 --- /dev/null +++ b/homeassistant/components/withings/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Profil i\u00e7in yap\u0131land\u0131rma g\u00fcncellendi." + }, + "error": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "profile": { + "data": { + "profile": "Profil Ad\u0131" + }, + "title": "Kullan\u0131c\u0131 profili." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/uk.json b/homeassistant/components/withings/translations/uk.json new file mode 100644 index 00000000000000..5efc27042b1756 --- /dev/null +++ b/homeassistant/components/withings/translations/uk.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "\u041e\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u043f\u0440\u043e\u0444\u0456\u043b\u044e.", + "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." + }, + "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." + }, + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "flow_title": "Withings: {profile}", + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "profile": { + "data": { + "profile": "\u041d\u0430\u0437\u0432\u0430 \u043f\u0440\u043e\u0444\u0456\u043b\u044e" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0435 \u0456\u043c'\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u044e \u0434\u043b\u044f \u0446\u0438\u0445 \u0434\u0430\u043d\u0438\u0445. \u042f\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u0446\u0435 \u043d\u0430\u0437\u0432\u0430, \u043e\u0431\u0440\u0430\u043d\u0430 \u043d\u0430 \u043f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e\u043c\u0443 \u043a\u0440\u043e\u0446\u0456.", + "title": "Withings" + }, + "reauth": { + "description": "\u041f\u0440\u043e\u0444\u0456\u043b\u044c \"{profile}\" \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u043e\u0432\u0436\u0435\u043d\u043d\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 Withings.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/de.json b/homeassistant/components/wled/translations/de.json index ff12e429bd6c14..0dd13f763d6f52 100644 --- a/homeassistant/components/wled/translations/de.json +++ b/homeassistant/components/wled/translations/de.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "Dieses WLED-Ger\u00e4t ist bereits konfiguriert." + "already_configured": "Dieses WLED-Ger\u00e4t ist bereits konfiguriert.", + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "WLED: {name}", "step": { diff --git a/homeassistant/components/wled/translations/tr.json b/homeassistant/components/wled/translations/tr.json new file mode 100644 index 00000000000000..f02764c8abab8b --- /dev/null +++ b/homeassistant/components/wled/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": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "description": "WLED'inizi Home Assistant ile t\u00fcmle\u015ftirmek i\u00e7in ayarlay\u0131n." + }, + "zeroconf_confirm": { + "description": "Home Assistant'a '{name}' adl\u0131 WLED'i eklemek istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/uk.json b/homeassistant/components/wled/translations/uk.json new file mode 100644 index 00000000000000..c0280d33993a99 --- /dev/null +++ b/homeassistant/components/wled/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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.", + "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" + }, + "flow_title": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 WLED." + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 WLED `{name}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 WLED" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/de.json b/homeassistant/components/wolflink/translations/de.json index cb7e571d1e6998..71f48a6413dcad 100644 --- a/homeassistant/components/wolflink/translations/de.json +++ b/homeassistant/components/wolflink/translations/de.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, "step": { "device": { "data": { diff --git a/homeassistant/components/wolflink/translations/sensor.de.json b/homeassistant/components/wolflink/translations/sensor.de.json index 373e19895784ed..9680716cd19e2f 100644 --- a/homeassistant/components/wolflink/translations/sensor.de.json +++ b/homeassistant/components/wolflink/translations/sensor.de.json @@ -1,6 +1,7 @@ { "state": { "wolflink__state": { + "permanent": "Permanent", "solarbetrieb": "Solarmodus", "sparbetrieb": "Sparmodus", "sparen": "Sparen", diff --git a/homeassistant/components/wolflink/translations/sensor.tr.json b/homeassistant/components/wolflink/translations/sensor.tr.json index 8b2eb0a8c53052..4b1e2778af13a6 100644 --- a/homeassistant/components/wolflink/translations/sensor.tr.json +++ b/homeassistant/components/wolflink/translations/sensor.tr.json @@ -1,10 +1,19 @@ { "state": { "wolflink__state": { + "glt_betrieb": "BMS modu", + "heizbetrieb": "Is\u0131tma modu", + "kalibration_heizbetrieb": "Is\u0131tma modu kalibrasyonu", + "kalibration_kombibetrieb": "Kombi modu kalibrasyonu", + "reduzierter_betrieb": "S\u0131n\u0131rl\u0131 mod", + "solarbetrieb": "G\u00fcne\u015f modu", + "sparbetrieb": "Ekonomi modu", "standby": "Bekleme", "start": "Ba\u015flat", "storung": "Hata", - "test": "Test" + "test": "Test", + "urlaubsmodus": "Tatil modu", + "warmwasserbetrieb": "DHW modu" } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.uk.json b/homeassistant/components/wolflink/translations/sensor.uk.json index 665ff99992c16a..c8a69f2c007c78 100644 --- a/homeassistant/components/wolflink/translations/sensor.uk.json +++ b/homeassistant/components/wolflink/translations/sensor.uk.json @@ -1,15 +1,87 @@ { "state": { "wolflink__state": { + "1_x_warmwasser": "1 \u0445 \u0413\u0412\u041f", + "abgasklappe": "\u0417\u0430\u0441\u043b\u0456\u043d\u043a\u0430 \u0434\u0438\u043c\u043e\u0432\u0438\u0445 \u0433\u0430\u0437\u0456\u0432", + "absenkbetrieb": "\u0420\u0435\u0436\u0438\u043c \u0430\u0432\u0430\u0440\u0456\u0457", + "absenkstop": "\u0410\u0432\u0430\u0440\u0456\u0439\u043d\u0430 \u0437\u0443\u043f\u0438\u043d\u043a\u0430", + "aktiviert": "\u0410\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u043e", + "antilegionellenfunktion": "\u0424\u0443\u043d\u043a\u0446\u0456\u044f \u0430\u043d\u0442\u0438-\u043b\u0435\u0433\u0438\u043e\u043d\u0435\u043b\u043b\u0438", + "at_abschaltung": "\u041e\u0422 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "at_frostschutz": "\u041e\u0422 \u0437\u0430\u0445\u0438\u0441\u0442 \u0432\u0456\u0434 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f", + "aus": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "auto": "\u0410\u0432\u0442\u043e", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "automatik_ein": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u043c\u0438\u043a\u0430\u043d\u043d\u044f", + "bereit_keine_ladung": "\u0413\u043e\u0442\u043e\u0432\u0438\u0439, \u043d\u0435 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0443\u0454\u0442\u044c\u0441\u044f", + "betrieb_ohne_brenner": "\u0420\u043e\u0431\u043e\u0442\u0430 \u0431\u0435\u0437 \u043f\u0430\u043b\u044c\u043d\u0438\u043a\u0430", + "cooling": "\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "deaktiviert": "\u041d\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e", + "dhw_prior": "DHWPrior", + "eco": "\u0415\u043a\u043e", + "ein": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", + "estrichtrocknung": "\u0421\u0443\u0448\u0456\u043d\u043d\u044f", + "externe_deaktivierung": "\u0417\u043e\u0432\u043d\u0456\u0448\u043d\u044f \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0456\u044f", + "fernschalter_ein": "\u0414\u0438\u0441\u0442\u0430\u043d\u0446\u0456\u0439\u043d\u0435 \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", + "frost_heizkreis": "\u0417\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f \u043a\u043e\u043d\u0442\u0443\u0440\u0443 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "frost_warmwasser": "\u0417\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f \u0413\u0412\u041f", + "frostschutz": "\u0417\u0430\u0445\u0438\u0441\u0442 \u0432\u0456\u0434 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f", + "gasdruck": "\u0422\u0438\u0441\u043a \u0433\u0430\u0437\u0443", + "glt_betrieb": "\u0420\u0435\u0436\u0438\u043c BMS", + "gradienten_uberwachung": "\u0413\u0440\u0430\u0434\u0456\u0454\u043d\u0442\u043d\u0438\u0439 \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433", + "heizbetrieb": "\u0420\u0435\u0436\u0438\u043c \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "heizgerat_mit_speicher": "\u041a\u043e\u0442\u0435\u043b \u0437 \u0446\u0438\u043b\u0456\u043d\u0434\u0440\u043e\u043c", + "heizung": "\u041e\u0431\u0456\u0433\u0440\u0456\u0432", + "initialisierung": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f", + "kalibration": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f", + "kalibration_heizbetrieb": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u0436\u0438\u043c\u0443 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "kalibration_kombibetrieb": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0432 \u043a\u043e\u043c\u0431\u0456\u043d\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456", + "kalibration_warmwasserbetrieb": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0413\u0412\u041f", + "kaskadenbetrieb": "\u041a\u0430\u0441\u043a\u0430\u0434\u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u044f", + "kombibetrieb": "\u041a\u043e\u043c\u0431\u0456\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "kombigerat": "\u0414\u0432\u043e\u043a\u043e\u043d\u0442\u0443\u0440\u043d\u0438\u0439 \u043a\u043e\u0442\u0435\u043b", + "kombigerat_mit_solareinbindung": "\u0414\u0432\u043e\u043a\u043e\u043d\u0442\u0443\u0440\u043d\u0438\u0439 \u043a\u043e\u0442\u0435\u043b \u0437 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0454\u044e \u0441\u043e\u043d\u044f\u0447\u043d\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438", + "mindest_kombizeit": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u043a\u043e\u043c\u0431\u0456\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0447\u0430\u0441", + "nachlauf_heizkreispumpe": "\u0420\u043e\u0431\u043e\u0442\u0430 \u043d\u0430\u0441\u043e\u0441\u0430 \u043a\u043e\u043d\u0442\u0443\u0440\u0443 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "nachspulen": "\u041f\u043e\u0441\u0442-\u043f\u0440\u043e\u043c\u0438\u0432\u043a\u0430", + "nur_heizgerat": "\u0422\u0456\u043b\u044c\u043a\u0438 \u0431\u043e\u0439\u043b\u0435\u0440", + "parallelbetrieb": "\u041f\u0430\u0440\u0430\u043b\u0435\u043b\u044c\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "partymodus": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u0447\u0456\u0440\u043a\u0438", + "perm_cooling": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u0435 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "permanent": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u043e", + "permanentbetrieb": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "reduzierter_betrieb": "\u041e\u0431\u043c\u0435\u0436\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "rt_abschaltung": "RT \u0432\u0438\u043c\u0438\u043a\u0430\u043d\u043d\u044f", + "rt_frostschutz": "RT \u0437\u0430\u0445\u0438\u0441\u0442 \u0432\u0456\u0434 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f", + "ruhekontakt": "\u0420\u0435\u0448\u0442\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0456\u0432", + "schornsteinfeger": "\u0422\u0435\u0441\u0442 \u043d\u0430 \u0432\u0438\u043a\u0438\u0434\u0438", + "smart_grid": "\u0420\u043e\u0437\u0443\u043c\u043d\u0430 \u043c\u0435\u0440\u0435\u0436\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043f\u043e\u0441\u0442\u0430\u0447\u0430\u043d\u043d\u044f", "smart_home": "\u0420\u043e\u0437\u0443\u043c\u043d\u0438\u0439 \u0434\u0456\u043c", + "softstart": "\u041c'\u044f\u043a\u0438\u0439 \u0441\u0442\u0430\u0440\u0442", + "solarbetrieb": "\u0421\u043e\u043d\u044f\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "sparbetrieb": "\u0420\u0435\u0436\u0438\u043c \u0435\u043a\u043e\u043d\u043e\u043c\u0456\u0457", "sparen": "\u0415\u043a\u043e\u043d\u043e\u043c\u0456\u044f", + "spreizung_hoch": "dT \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u0448\u0438\u0440\u043e\u043a\u0438\u0439", + "spreizung_kf": "\u0421\u043f\u0440\u0435\u0434 KF", "stabilisierung": "\u0421\u0442\u0430\u0431\u0456\u043b\u0456\u0437\u0430\u0446\u0456\u044f", "standby": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", - "start": "\u041f\u043e\u0447\u0430\u0442\u043e\u043a", + "start": "\u0417\u0430\u043f\u0443\u0441\u043a", "storung": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430", - "taktsperre": "\u0410\u043d\u0442\u0438\u0446\u0438\u043a\u043b", - "test": "\u0422\u0435\u0441\u0442" + "taktsperre": "\u0410\u043d\u0442\u0438-\u0446\u0438\u043a\u043b", + "telefonfernschalter": "\u0414\u0438\u0441\u0442\u0430\u043d\u0446\u0456\u0439\u043d\u0435 \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0437 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443", + "test": "\u0422\u0435\u0441\u0442", + "tpw": "TPW", + "urlaubsmodus": "\u0420\u0435\u0436\u0438\u043c \"\u0432\u0438\u0445\u0456\u0434\u043d\u0456\"", + "ventilprufung": "\u0422\u0435\u0441\u0442 \u043a\u043b\u0430\u043f\u0430\u043d\u0430", + "vorspulen": "\u041f\u0440\u043e\u043c\u0438\u0432\u0430\u043d\u043d\u044f \u0432\u0445\u043e\u0434\u0443", + "warmwasser": "\u0413\u0412\u041f", + "warmwasser_schnellstart": "\u0428\u0432\u0438\u0434\u043a\u0438\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0413\u0412\u041f", + "warmwasserbetrieb": "\u0420\u0435\u0436\u0438\u043c \u0413\u0412\u041f", + "warmwassernachlauf": "\u0417\u0430\u043f\u0443\u0441\u043a \u0413\u0412\u041f", + "warmwasservorrang": "\u041f\u0440\u0456\u043e\u0440\u0438\u0442\u0435\u0442 \u0413\u0412\u041f", + "zunden": "\u0417\u0430\u043f\u0430\u043b\u044e\u0432\u0430\u043d\u043d\u044f" } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/tr.json b/homeassistant/components/wolflink/translations/tr.json new file mode 100644 index 00000000000000..6ed28a58c793c2 --- /dev/null +++ b/homeassistant/components/wolflink/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/uk.json b/homeassistant/components/wolflink/translations/uk.json index a7fbdfff913754..3fdf20a6aceb6e 100644 --- a/homeassistant/components/wolflink/translations/uk.json +++ b/homeassistant/components/wolflink/translations/uk.json @@ -1,17 +1,26 @@ { "config": { "abort": { - "already_configured": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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." }, "error": { "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.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "step": { "device": { "data": { "device_name": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" - } + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 WOLF" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "WOLF SmartSet" } } } diff --git a/homeassistant/components/xbox/translations/de.json b/homeassistant/components/xbox/translations/de.json index c67f3a49ea4378..04f32e05f8b34a 100644 --- a/homeassistant/components/xbox/translations/de.json +++ b/homeassistant/components/xbox/translations/de.json @@ -1,5 +1,10 @@ { "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "create_entry": { "default": "Erfolgreich authentifiziert" }, diff --git a/homeassistant/components/xbox/translations/lb.json b/homeassistant/components/xbox/translations/lb.json index d305909389ff41..b83b6d0a4995fe 100644 --- a/homeassistant/components/xbox/translations/lb.json +++ b/homeassistant/components/xbox/translations/lb.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "create_entry": { + "default": "Erfollegr\u00e4ich authentifiz\u00e9iert" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/tr.json b/homeassistant/components/xbox/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/xbox/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/uk.json b/homeassistant/components/xbox/translations/uk.json new file mode 100644 index 00000000000000..a1b3f8340fc889 --- /dev/null +++ b/homeassistant/components/xbox/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index f86868987a0696..6b0e25dfcd5206 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" + }, + "error": { + "discovery_error": "Es konnte kein Xiaomi Aqara Gateway gefunden werden, versuche die IP von Home Assistant als Interface zu nutzen", + "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse, schau unter https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_mac": "Ung\u00fcltige MAC-Adresse" + }, "flow_title": "Xiaomi Aqara Gateway: {name}", "step": { "select": { @@ -8,7 +17,11 @@ } }, "user": { - "description": "Stellen Sie eine Verbindung zu Ihrem Xiaomi Aqara Gateway her. Wenn die IP- und Mac-Adressen leer bleiben, wird die automatische Erkennung verwendet", + "data": { + "host": "IP-Adresse", + "mac": "MAC-Adresse" + }, + "description": "Stelle eine Verbindung zu deinem Xiaomi Aqara Gateway her. Wenn die IP- und MAC-Adressen leer bleiben, wird die automatische Erkennung verwendet", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/tr.json b/homeassistant/components/xiaomi_aqara/translations/tr.json index 10d1374187e594..24da29417d14dc 100644 --- a/homeassistant/components/xiaomi_aqara/translations/tr.json +++ b/homeassistant/components/xiaomi_aqara/translations/tr.json @@ -1,7 +1,38 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "not_xiaomi_aqara": "Xiaomi Aqara A\u011f Ge\u00e7idi de\u011fil, ke\u015ffedilen cihaz bilinen a\u011f ge\u00e7itleriyle e\u015fle\u015fmedi" + }, "error": { + "discovery_error": "Bir Xiaomi Aqara A\u011f Ge\u00e7idi ke\u015ffedilemedi, HomeAssistant'\u0131 aray\u00fcz olarak \u00e7al\u0131\u015ft\u0131ran cihaz\u0131n IP'sini kullanmay\u0131 deneyin", + "invalid_interface": "Ge\u00e7ersiz a\u011f aray\u00fcz\u00fc", + "invalid_key": "Ge\u00e7ersiz a\u011f ge\u00e7idi anahtar\u0131", "invalid_mac": "Ge\u00e7ersiz Mac Adresi" + }, + "flow_title": "Xiaomi Aqara A\u011f Ge\u00e7idi: {name}", + "step": { + "select": { + "data": { + "select_ip": "\u0130p Adresi" + }, + "description": "Ek a\u011f ge\u00e7itlerini ba\u011flamak istiyorsan\u0131z kurulumu tekrar \u00e7al\u0131\u015ft\u0131r\u0131n.", + "title": "Ba\u011flamak istedi\u011finiz Xiaomi Aqara A\u011f Ge\u00e7idini se\u00e7in" + }, + "settings": { + "data": { + "key": "A\u011f ge\u00e7idinizin anahtar\u0131", + "name": "A\u011f Ge\u00e7idinin Ad\u0131" + }, + "description": "Anahtar (parola) bu \u00f6\u011fretici kullan\u0131larak al\u0131nabilir: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Anahtar sa\u011flanmazsa, yaln\u0131zca sens\u00f6rlere eri\u015filebilir" + }, + "user": { + "data": { + "host": "\u0130p Adresi (iste\u011fe ba\u011fl\u0131)", + "mac": "Mac Adresi (iste\u011fe ba\u011fl\u0131)" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/uk.json b/homeassistant/components/xiaomi_aqara/translations/uk.json new file mode 100644 index 00000000000000..1598e96b38eecc --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/uk.json @@ -0,0 +1,43 @@ +{ + "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_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.", + "not_xiaomi_aqara": "\u0426\u0435 \u043d\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara. \u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 \u0432\u0456\u0434\u043e\u043c\u0438\u043c \u0448\u043b\u044e\u0437\u0456\u0432." + }, + "error": { + "discovery_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u044f\u0432\u0438\u0442\u0438 \u0448\u043b\u044e\u0437 Xiaomi Aqara, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 HomeAssistant \u0432 \u044f\u043a\u043e\u0441\u0442\u0456 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443.", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430. . \u0421\u043f\u043e\u0441\u043e\u0431\u0438 \u0432\u0438\u0440\u0456\u0448\u0435\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0456 \u0442\u0443\u0442: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem.", + "invalid_interface": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.", + "invalid_key": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 \u0448\u043b\u044e\u0437\u0443.", + "invalid_mac": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 MAC-\u0430\u0434\u0440\u0435\u0441\u0430." + }, + "flow_title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara: {name}", + "step": { + "select": { + "data": { + "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u044e\u0432\u0430\u043d\u043d\u044f \u0437\u043d\u043e\u0432\u0443, \u044f\u043a\u0449\u043e \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438 \u0456\u043d\u0448\u0438\u0439 \u0448\u043b\u044e\u0437", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0448\u043b\u044e\u0437 Xiaomi Aqara" + }, + "settings": { + "data": { + "key": "\u041a\u043b\u044e\u0447", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041a\u043b\u044e\u0447 (\u043f\u0430\u0440\u043e\u043b\u044c) \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0446\u0456\u0454\u0457 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. \u042f\u043a\u0449\u043e \u043a\u043b\u044e\u0447 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u043e, \u0431\u0443\u0434\u0443\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0456 \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u0430\u0442\u0447\u0438\u043a\u0438.", + "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "interface": "\u041c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441", + "mac": "MAC-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437\u0456 \u0448\u043b\u044e\u0437\u043e\u043c Xiaomi Aqara. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u0448\u043b\u044e\u0437\u0443, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u044f IP \u0456 MAC-\u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c\u0438.", + "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index 0b5f593ffcde70..d56a81e14d4582 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -2,27 +2,28 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr dieses Xiaomi Miio-Ger\u00e4t wird bereits ausgef\u00fchrt." + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" }, "error": { - "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hlen Sie ein Ger\u00e4t aus." + "cannot_connect": "Verbindung fehlgeschlagen", + "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus." }, "flow_title": "Xiaomi Miio: {name}", "step": { "gateway": { "data": { - "host": "IP Adresse", + "host": "IP-Adresse", "name": "Name des Gateways", "token": "API-Token" }, - "description": "Sie ben\u00f6tigen den 32 Zeichen langen API-Token. Anweisungen finden Sie unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", - "title": "Stellen Sie eine Verbindung zu einem Xiaomi Gateway her" + "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token. Anweisungen findest du unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "title": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, "user": { "data": { - "gateway": "Stellen Sie eine Verbindung zu einem Xiaomi Gateway her" + "gateway": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, - "description": "W\u00e4hlen Sie aus, mit welchem Ger\u00e4t Sie eine Verbindung herstellen m\u00f6chten.", + "description": "W\u00e4hle aus, mit welchem Ger\u00e4t du eine Verbindung herstellen m\u00f6chtest.", "title": "Xiaomi Miio" } } diff --git a/homeassistant/components/xiaomi_miio/translations/tr.json b/homeassistant/components/xiaomi_miio/translations/tr.json new file mode 100644 index 00000000000000..46a6493ab3a8d2 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "no_device_selected": "Cihaz se\u00e7ilmedi, l\u00fctfen bir cihaz se\u00e7in." + }, + "step": { + "gateway": { + "data": { + "host": "\u0130p Adresi", + "name": "A\u011f Ge\u00e7idinin Ad\u0131", + "token": "API Belirteci" + }, + "title": "Bir Xiaomi A\u011f Ge\u00e7idine ba\u011flan\u0131n" + }, + "user": { + "data": { + "gateway": "Bir Xiaomi A\u011f Ge\u00e7idine ba\u011flan\u0131n" + }, + "description": "Hangi cihaza ba\u011flanmak istedi\u011finizi se\u00e7in.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/uk.json b/homeassistant/components/xiaomi_miio/translations/uk.json new file mode 100644 index 00000000000000..f32105589f6a2e --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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_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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "no_device_selected": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u0438\u043d \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432." + }, + "flow_title": "Xiaomi Miio: {name}", + "step": { + "gateway": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430", + "token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u0438\u0439 \u0422\u043e\u043a\u0435\u043d API . \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0442\u043e\u043a\u0435\u043d, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0442\u0443\u0442:\nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u0446\u0435\u0439 \u0442\u043e\u043a\u0435\u043d \u0432\u0456\u0434\u0440\u0456\u0437\u043d\u044f\u0454\u0442\u044c\u0441\u044f \u0432\u0456\u0434 \u043a\u043b\u044e\u0447\u0430, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Xiaomi Aqara.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0448\u043b\u044e\u0437\u0443 Xiaomi" + }, + "user": { + "data": { + "gateway": "\u0428\u043b\u044e\u0437 Xiaomi" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/de.json b/homeassistant/components/yeelight/translations/de.json index 90c154f882bf04..6eaff2e87a3bb4 100644 --- a/homeassistant/components/yeelight/translations/de.json +++ b/homeassistant/components/yeelight/translations/de.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "pick_device": { "data": { @@ -9,7 +16,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Wenn du den Host leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden." } } }, diff --git a/homeassistant/components/yeelight/translations/tr.json b/homeassistant/components/yeelight/translations/tr.json new file mode 100644 index 00000000000000..322f13f47b04d5 --- /dev/null +++ b/homeassistant/components/yeelight/translations/tr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "pick_device": { + "data": { + "device": "Cihaz" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Model (Opsiyonel)", + "save_on_change": "De\u011fi\u015fiklikte Durumu Kaydet", + "transition": "Ge\u00e7i\u015f S\u00fcresi (ms)", + "use_music_mode": "M\u00fczik Modunu Etkinle\u015ftir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/uk.json b/homeassistant/components/yeelight/translations/uk.json new file mode 100644 index 00000000000000..0a173ccb6e4a4c --- /dev/null +++ b/homeassistant/components/yeelight/translations/uk.json @@ -0,0 +1,38 @@ +{ + "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.", + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "pick_device": { + "data": { + "device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + } + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u042f\u043a\u0449\u043e \u043d\u0435 \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430, \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0456 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "\u041c\u043e\u0434\u0435\u043b\u044c (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "nightlight_switch": "\u041f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447 \u0434\u043b\u044f \u043d\u0456\u0447\u043d\u0438\u043a\u0430", + "save_on_change": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u0438 \u0437\u043c\u0456\u043d\u0456", + "transition": "\u0427\u0430\u0441 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0443 (\u0432 \u043c\u0456\u043b\u0456\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "use_music_mode": "\u041c\u0443\u0437\u0438\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c" + }, + "description": "\u042f\u043a\u0449\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u043e, \u0432\u043e\u043d\u0430 \u0431\u0443\u0434\u0435 \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/tr.json b/homeassistant/components/zerproc/translations/tr.json index 49fa9545e94d2c..3df15466f030f1 100644 --- a/homeassistant/components/zerproc/translations/tr.json +++ b/homeassistant/components/zerproc/translations/tr.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + "no_devices_found": "A\u011fda cihaz bulunamad\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/uk.json b/homeassistant/components/zerproc/translations/uk.json new file mode 100644 index 00000000000000..292861e9129dbd --- /dev/null +++ b/homeassistant/components/zerproc/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/cs.json b/homeassistant/components/zha/translations/cs.json index 1ac4c7c2d6136b..cedf56d73c4d22 100644 --- a/homeassistant/components/zha/translations/cs.json +++ b/homeassistant/components/zha/translations/cs.json @@ -51,20 +51,20 @@ "device_rotated": "Za\u0159\u00edzen\u00ed oto\u010deno \"{subtype}\"", "device_shaken": "Za\u0159\u00edzen\u00ed se zat\u0159\u00e1slo", "device_tilted": "Za\u0159\u00edzen\u00ed naklon\u011bno", - "remote_button_alt_double_press": "Dvakr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", + "remote_button_alt_double_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dvakr\u00e1t (alternativn\u00ed re\u017eim)", "remote_button_alt_long_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" po dlouh\u00e9m stisku (alternativn\u00ed re\u017eim)", - "remote_button_alt_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_alt_quintuple_press": "P\u011btkr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_alt_short_press": "Stiknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", + "remote_button_alt_quadruple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t (alternativn\u00ed re\u017eim)", + "remote_button_alt_quintuple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto p\u011btkr\u00e1t (alternativn\u00ed re\u017eim)", + "remote_button_alt_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto (alternativn\u00ed re\u017eim)", "remote_button_alt_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_alt_triple_press": "T\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_double_press": "Dvakr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_alt_triple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto t\u0159ikr\u00e1t (alternativn\u00ed re\u017eim)", + "remote_button_double_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dvakr\u00e1t", "remote_button_long_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" po dlouh\u00e9m stisku", - "remote_button_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", - "remote_button_quintuple_press": "P\u011btkr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", - "remote_button_short_press": "Stiknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_quadruple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "remote_button_quintuple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto p\u011btkr\u00e1t", + "remote_button_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto", "remote_button_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\"", - "remote_button_triple_press": "T\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"" + "remote_button_triple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto t\u0159ikr\u00e1t" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/de.json b/homeassistant/components/zha/translations/de.json index 592450fcfbc40b..61e9b8e37ba322 100644 --- a/homeassistant/components/zha/translations/de.json +++ b/homeassistant/components/zha/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von ZHA zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Kein Verbindung zu ZHA-Ger\u00e4t m\u00f6glich" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/pl.json b/homeassistant/components/zha/translations/pl.json index ab4402558ba9ca..4cdada49f5013b 100644 --- a/homeassistant/components/zha/translations/pl.json +++ b/homeassistant/components/zha/translations/pl.json @@ -70,22 +70,22 @@ "device_shaken": "nast\u0105pi potrz\u0105\u015bni\u0119cie urz\u0105dzeniem", "device_slid": "nast\u0105pi przesuni\u0119cie urz\u0105dzenia \"{subtype}\"", "device_tilted": "nast\u0105pi przechylenie urz\u0105dzenia", - "remote_button_alt_double_press": "\"{subtype}\" dwukrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_long_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y (tryb alternatywny)", - "remote_button_alt_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu (tryb alternatywny)", - "remote_button_alt_quadruple_press": "\"{subtype}\" czterokrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_quintuple_press": "\"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_short_release": "\"{subtype}\" zostanie zwolniony (tryb alternatywny)", - "remote_button_alt_triple_press": "\"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_double_press": "\"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", - "remote_button_long_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", - "remote_button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", - "remote_button_quadruple_press": "\"{subtype}\" czterokrotnie naci\u015bni\u0119ty", - "remote_button_quintuple_press": "\"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", - "remote_button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", - "remote_button_short_release": "\"{subtype}\" zostanie zwolniony", - "remote_button_triple_press": "\"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" + "remote_button_alt_double_press": "przycisk \"{subtype}\" zostanie dwukrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y (tryb alternatywny)", + "remote_button_alt_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu (tryb alternatywny)", + "remote_button_alt_quadruple_press": "przycisk \"{subtype}\" zostanie czterokrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_quintuple_press": "przycisk \"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_short_release": "przycisk \"{subtype}\" zostanie zwolniony (tryb alternatywny)", + "remote_button_alt_triple_press": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_double_press": "przycisk \"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", + "remote_button_long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", + "remote_button_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "remote_button_quadruple_press": "przycisk \"{subtype}\" czterokrotnie naci\u015bni\u0119ty", + "remote_button_quintuple_press": "przycisk \"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", + "remote_button_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "remote_button_short_release": "przycisk \"{subtype}\" zostanie zwolniony", + "remote_button_triple_press": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/tr.json b/homeassistant/components/zha/translations/tr.json new file mode 100644 index 00000000000000..a74f56a2f4e6be --- /dev/null +++ b/homeassistant/components/zha/translations/tr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "pick_radio": { + "title": "Radyo Tipi" + }, + "port_config": { + "data": { + "path": "Seri cihaz yolu" + }, + "title": "Ayarlar" + } + } + }, + "device_automation": { + "trigger_type": { + "device_offline": "Cihaz \u00e7evrimd\u0131\u015f\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/uk.json b/homeassistant/components/zha/translations/uk.json new file mode 100644 index 00000000000000..7bd62cf26e1dca --- /dev/null +++ b/homeassistant/components/zha/translations/uk.json @@ -0,0 +1,91 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "pick_radio": { + "data": { + "radio_type": "\u0422\u0438\u043f \u0440\u0430\u0434\u0456\u043e\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Zigbee", + "title": "\u0422\u0438\u043f \u0440\u0430\u0434\u0456\u043e\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "port_config": { + "data": { + "baudrate": "\u0448\u0432\u0438\u0434\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0440\u0442\u0443", + "flow_control": "\u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u043e\u043a\u043e\u043c \u0434\u0430\u043d\u0438\u0445", + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0440\u0442\u0443", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + }, + "user": { + "data": { + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u043e\u0441\u043b\u0456\u0434\u043e\u0432\u043d\u0438\u0439 \u043f\u043e\u0440\u0442 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0430 \u043c\u0435\u0440\u0435\u0436\u0456 Zigbee", + "title": "Zigbee Home Automation" + } + } + }, + "device_automation": { + "action_type": { + "squawk": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0434\u0435\u0440", + "warn": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f \u043e\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f" + }, + "trigger_subtype": { + "both_buttons": "\u041e\u0431\u0438\u0434\u0432\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_5": "\u041f'\u044f\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_6": "\u0428\u043e\u0441\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "close": "\u0417\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "dim_down": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "dim_up": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "face_1": "\u041d\u0430 \u043f\u0435\u0440\u0448\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_2": "\u041d\u0430 \u0434\u0440\u0443\u0433\u0438\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_3": "\u041d\u0430 \u0442\u0440\u0435\u0442\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_4": "\u041d\u0430 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_5": "\u041d\u0430 \u043f'\u044f\u0442\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_6": "\u041d\u0430 \u0448\u043e\u0441\u0442\u0438\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_any": "\u041d\u0430 \u0431\u0443\u0434\u044c-\u044f\u043a\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "left": "\u041b\u0456\u0432\u043e\u0440\u0443\u0447", + "open": "\u0412\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "right": "\u041f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "device_dropped": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0441\u043a\u0438\u043d\u0443\u043b\u0438", + "device_flipped": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 {subtype}", + "device_knocked": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c \u043f\u043e\u0441\u0442\u0443\u043a\u0430\u043b\u0438 {subtype}", + "device_offline": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456", + "device_rotated": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0438 {subtype}", + "device_shaken": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0442\u0440\u044f\u0441\u043b\u0438", + "device_slid": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0437\u0440\u0443\u0448\u0438\u043b\u0438 {subtype}", + "device_tilted": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430\u0445\u0438\u043b\u0438\u043b\u0438", + "remote_button_alt_double_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_long_press": "{subtype} \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_quadruple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_quintuple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_short_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_triple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_double_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438", + "remote_button_long_press": "{subtype} \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_quadruple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438", + "remote_button_quintuple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432", + "remote_button_short_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_triple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.tr.json b/homeassistant/components/zodiac/translations/sensor.tr.json new file mode 100644 index 00000000000000..f9e0357799d9b3 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.tr.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Kova", + "aries": "Ko\u00e7", + "cancer": "Yenge\u00e7", + "capricorn": "O\u011flak", + "gemini": "Ikizler", + "leo": "Aslan", + "libra": "Terazi", + "pisces": "Bal\u0131k", + "sagittarius": "Yay", + "scorpio": "Akrep", + "taurus": "Bo\u011fa", + "virgo": "Ba\u015fak" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.uk.json b/homeassistant/components/zodiac/translations/sensor.uk.json new file mode 100644 index 00000000000000..e0c891a8b23d67 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.uk.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "\u0412\u043e\u0434\u043e\u043b\u0456\u0439", + "aries": "\u041e\u0432\u0435\u043d", + "cancer": "\u0420\u0430\u043a", + "capricorn": "\u041a\u043e\u0437\u0435\u0440\u0456\u0433", + "gemini": "\u0411\u043b\u0438\u0437\u043d\u044e\u043a\u0438", + "leo": "\u041b\u0435\u0432", + "libra": "\u0422\u0435\u0440\u0435\u0437\u0438", + "pisces": "\u0420\u0438\u0431\u0438", + "sagittarius": "\u0421\u0442\u0440\u0456\u043b\u0435\u0446\u044c", + "scorpio": "\u0421\u043a\u043e\u0440\u043f\u0456\u043e\u043d", + "taurus": "\u0422\u0435\u043b\u0435\u0446\u044c", + "virgo": "\u0414\u0456\u0432\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zone/translations/tr.json b/homeassistant/components/zone/translations/tr.json new file mode 100644 index 00000000000000..dad65ac92a7c11 --- /dev/null +++ b/homeassistant/components/zone/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "init": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/de.json b/homeassistant/components/zoneminder/translations/de.json index 1362dcbd62dba3..5fa5d0a52345b1 100644 --- a/homeassistant/components/zoneminder/translations/de.json +++ b/homeassistant/components/zoneminder/translations/de.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "flow_title": "ZoneMinder", "step": { "user": { "data": { "password": "Passwort", + "ssl": "Nutzt ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } diff --git a/homeassistant/components/zoneminder/translations/tr.json b/homeassistant/components/zoneminder/translations/tr.json new file mode 100644 index 00000000000000..971f8cc9bd758c --- /dev/null +++ b/homeassistant/components/zoneminder/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "auth_fail": "Kullan\u0131c\u0131 ad\u0131 veya \u015fifre yanl\u0131\u015f.", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "error": { + "auth_fail": "Kullan\u0131c\u0131 ad\u0131 veya \u015fifre yanl\u0131\u015f.", + "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/zoneminder/translations/uk.json b/homeassistant/components/zoneminder/translations/uk.json new file mode 100644 index 00000000000000..e5b04ae124f5e1 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/uk.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d \u0430\u0431\u043e \u043f\u0430\u0440\u043e\u043b\u044c.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 ZoneMinder.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "create_entry": { + "default": "\u0414\u043e\u0434\u0430\u043d\u043e \u0441\u0435\u0440\u0432\u0435\u0440 ZoneMinder." + }, + "error": { + "auth_fail": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d \u0430\u0431\u043e \u043f\u0430\u0440\u043e\u043b\u044c.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 ZoneMinder.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442 \u0456 \u043f\u043e\u0440\u0442 (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 10.10.0.4:8010)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e ZM", + "path_zms": "\u0428\u043b\u044f\u0445 \u0434\u043e ZMS", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "ZoneMinder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/de.json b/homeassistant/components/zwave/translations/de.json index 60b5aa88024605..f592c2243aca53 100644 --- a/homeassistant/components/zwave/translations/de.json +++ b/homeassistant/components/zwave/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Z-Wave ist bereits konfiguriert" + "already_configured": "Z-Wave ist bereits konfiguriert", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "option_error": "Z-Wave-Validierung fehlgeschlagen. Ist der Pfad zum USB-Stick korrekt?" diff --git a/homeassistant/components/zwave/translations/tr.json b/homeassistant/components/zwave/translations/tr.json index 3938868d2808c1..383ccc6cc4f805 100644 --- a/homeassistant/components/zwave/translations/tr.json +++ b/homeassistant/components/zwave/translations/tr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/zwave/translations/uk.json b/homeassistant/components/zwave/translations/uk.json index d00986cae5807f..5cdd6060cc4b3f 100644 --- a/homeassistant/components/zwave/translations/uk.json +++ b/homeassistant/components/zwave/translations/uk.json @@ -1,14 +1,33 @@ { + "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." + }, + "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." + }, + "step": { + "user": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u043c\u0435\u0440\u0435\u0436\u0456 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438)", + "usb_path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438](https://www.home-assistant.io/docs/z-wave/installation/) \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 \u043f\u0440\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", + "title": "Z-Wave" + } + } + }, "state": { "_": { - "dead": "\u041d\u0435\u0440\u043e\u0431\u043e\u0447\u0430", + "dead": "\u041d\u0435\u0441\u043f\u0440\u0430\u0432\u043d\u0438\u0439", "initializing": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f", "ready": "\u0413\u043e\u0442\u043e\u0432\u0438\u0439", - "sleeping": "\u0421\u043f\u043b\u044f\u0447\u043a\u0430" + "sleeping": "\u0420\u0435\u0436\u0438\u043c \u0441\u043d\u0443" }, "query_stage": { - "dead": "\u041d\u0435\u0440\u043e\u0431\u043e\u0447\u0430 ({query_stage})", - "initializing": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f ( {query_stage} )" + "dead": "\u041d\u0435\u0441\u043f\u0440\u0430\u0432\u043d\u0438\u0439", + "initializing": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json new file mode 100644 index 00000000000000..93ec53a644e0fa --- /dev/null +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "No s'ha pogut obtenir la informaci\u00f3 de descobriment del complement Z-Wave JS.", + "addon_info_failed": "No s'ha pogut obtenir la informaci\u00f3 del complement Z-Wave JS.", + "addon_install_failed": "No s'ha pogut instal\u00b7lar el complement Z-Wave JS.", + "addon_missing_discovery_info": "Falta la informaci\u00f3 de descobriment del complement Z-Wave JS.", + "addon_set_config_failed": "No s'ha pogut establir la configuraci\u00f3 de Z-Wave JS.", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "error": { + "addon_start_failed": "No s'ha pogut iniciar el complement Z-Wave JS. Comprova la configuraci\u00f3.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_ws_url": "URL del websocket inv\u00e0lid", + "unknown": "Error inesperat" + }, + "progress": { + "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts." + }, + "step": { + "hassio_confirm": { + "title": "Configura la integraci\u00f3 Z-Wave JS mitjan\u00e7ant el complement Z-Wave JS" + }, + "install_addon": { + "title": "Ha comen\u00e7at la instal\u00b7laci\u00f3 del complement Z-Wave JS" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Utilitza el complement Z-Wave JS Supervisor" + }, + "description": "Vols utilitzar el complement Supervisor de Z-Wave JS?", + "title": "Selecciona el m\u00e8tode de connexi\u00f3" + }, + "start_addon": { + "data": { + "network_key": "Clau de xarxa", + "usb_path": "Ruta del port USB del dispositiu" + }, + "title": "Introdueix la configuraci\u00f3 del complement Z-Wave JS" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/cs.json b/homeassistant/components/zwave_js/translations/cs.json new file mode 100644 index 00000000000000..96073b579ed688 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/cs.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "manual": { + "data": { + "url": "URL" + } + }, + "start_addon": { + "data": { + "usb_path": "Cesta k USB za\u0159\u00edzen\u00ed" + } + }, + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json new file mode 100644 index 00000000000000..d4903bc8c6dae4 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 4aa510df6bea12..977651a576bc9b 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -44,6 +44,11 @@ "usb_path": "USB Device Path" }, "title": "Enter the Z-Wave JS add-on configuration" + }, + "user": { + "data": { + "url": "URL" + } } } }, diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json new file mode 100644 index 00000000000000..e5ee009c0d1014 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/es.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", + "cannot_connect": "No se pudo conectar" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_ws_url": "URL de websocket no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "title": "Selecciona el m\u00e9todo de conexi\u00f3n" + }, + "start_addon": { + "data": { + "network_key": "Clave de red", + "usb_path": "Ruta del dispositivo USB" + } + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json new file mode 100644 index 00000000000000..7a7aadfb84152e --- /dev/null +++ b/homeassistant/components/zwave_js/translations/et.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Z-Wave JS lisandmooduli tuvastusteabe hankimine nurjus.", + "addon_info_failed": "Z-Wave JS lisandmooduli teabe hankimine nurjus.", + "addon_install_failed": "Z-Wave JS lisandmooduli paigaldamine nurjus.", + "addon_missing_discovery_info": "Z-Wave JS lisandmooduli tuvastusteave puudub.", + "addon_set_config_failed": "Z-Wave JS konfiguratsiooni m\u00e4\u00e4ramine nurjus.", + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", + "cannot_connect": "\u00dchendamine nurjus" + }, + "error": { + "addon_start_failed": "Z-Wave JS lisandmooduli k\u00e4ivitamine nurjus. Kontrolli seadistusi.", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_ws_url": "Vale sihtkoha aadress", + "unknown": "Ootamatu t\u00f5rge" + }, + "progress": { + "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit." + }, + "step": { + "hassio_confirm": { + "title": "Seadista Z-Wave JS-i sidumine Z-Wave JS-i lisandmooduliga" + }, + "install_addon": { + "title": "Z-Wave JS lisandmooduli paigaldamine on alanud" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Kasuta lisandmoodulit Z-Wave JS Supervisor" + }, + "description": "Kas soovid kasutada Z-Wave JSi halduri lisandmoodulit?", + "title": "Vali \u00fchendusviis" + }, + "start_addon": { + "data": { + "network_key": "V\u00f5rgu v\u00f5ti", + "usb_path": "USB-seadme asukoha rada" + }, + "title": "Sisesta Z-Wave JS lisandmooduli seaded" + }, + "user": { + "data": { + "url": "" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json new file mode 100644 index 00000000000000..f3a9aff1a29bda --- /dev/null +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Erreur de connection", + "invalid_ws_url": "URL websocket invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json new file mode 100644 index 00000000000000..fc76b309a34ebc --- /dev/null +++ b/homeassistant/components/zwave_js/translations/it.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Impossibile ottenere le informazioni sul rilevamento del componente aggiuntivo Z-Wave JS.", + "addon_info_failed": "Impossibile ottenere le informazioni sul componente aggiuntivo Z-Wave JS.", + "addon_install_failed": "Impossibile installare il componente aggiuntivo Z-Wave JS.", + "addon_missing_discovery_info": "Informazioni sul rilevamento del componente aggiuntivo Z-Wave JS mancanti.", + "addon_set_config_failed": "Impossibile impostare la configurazione di Z-Wave JS.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "cannot_connect": "Impossibile connettersi" + }, + "error": { + "addon_start_failed": "Impossibile avviare il componente aggiuntivo Z-Wave JS. Controlla la configurazione.", + "cannot_connect": "Impossibile connettersi", + "invalid_ws_url": "URL websocket non valido", + "unknown": "Errore imprevisto" + }, + "progress": { + "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti." + }, + "step": { + "hassio_confirm": { + "title": "Configura l'integrazione di Z-Wave JS con il componente aggiuntivo Z-Wave JS" + }, + "install_addon": { + "title": "L'installazione del componente aggiuntivo Z-Wave JS \u00e8 iniziata" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Usa il componente aggiuntivo Z-Wave JS Supervisor" + }, + "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS Supervisor?", + "title": "Seleziona il metodo di connessione" + }, + "start_addon": { + "data": { + "network_key": "Chiave di rete", + "usb_path": "Percorso del dispositivo USB" + }, + "title": "Accedi alla configurazione del componente aggiuntivo Z-Wave JS" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/lb.json b/homeassistant/components/zwave_js/translations/lb.json new file mode 100644 index 00000000000000..302addbd7cf958 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/lb.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_ws_url": "Ong\u00eblteg Websocket URL", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json new file mode 100644 index 00000000000000..e16425b59ec898 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/no.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Kunne ikke hente oppdagelsesinformasjon om Z-Wave JS-tillegg", + "addon_info_failed": "Kunne ikke hente informasjon om Z-Wave JS-tillegg", + "addon_install_failed": "Kunne ikke installere Z-Wave JS-tillegg", + "addon_missing_discovery_info": "Manglende oppdagelsesinformasjon for Z-Wave JS-tillegg", + "addon_set_config_failed": "Kunne ikke angi Z-Wave JS-konfigurasjon", + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "cannot_connect": "Tilkobling mislyktes" + }, + "error": { + "addon_start_failed": "Kunne ikke starte Z-Wave JS-tillegg. Sjekk konfigurasjonen.", + "cannot_connect": "Tilkobling mislyktes", + "invalid_ws_url": "Ugyldig websocket URL", + "unknown": "Uventet feil" + }, + "progress": { + "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter." + }, + "step": { + "hassio_confirm": { + "title": "Sett opp Z-Wave JS-integrasjon med Z-Wave JS-tillegg" + }, + "install_addon": { + "title": "Installasjon av Z-Wave JS-tillegg har startet" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Bruk Z-Wave JS Supervisor-tillegg" + }, + "description": "Vil du bruke Z-Wave JS Supervisor-tillegg?", + "title": "Velg tilkoblingsmetode" + }, + "start_addon": { + "data": { + "network_key": "Nettverksn\u00f8kkel", + "usb_path": "USB enhetsbane" + }, + "title": "Angi konfigurasjon for Z-Wave JS-tillegg" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json new file mode 100644 index 00000000000000..47e263c610192f --- /dev/null +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Nie uda\u0142o si\u0119 uzyska\u0107 informacji wykrywania dodatku Z-Wave JS", + "addon_info_failed": "Nie uda\u0142o si\u0119 uzyska\u0107 informacji o dodatku Z-Wave JS", + "addon_install_failed": "Nie uda\u0142o si\u0119 zainstalowa\u0107 dodatku Z-Wave JS", + "addon_missing_discovery_info": "Brak informacji wykrywania dodatku Z-Wave JS", + "addon_set_config_failed": "Nie uda\u0142o si\u0119 skonfigurowa\u0107 Z-Wave JS", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "error": { + "addon_start_failed": "Nie uda\u0142o si\u0119 uruchomi\u0107 dodatku Z-Wave JS. Sprawd\u017a konfiguracj\u0119", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_ws_url": "Nieprawid\u0142owy URL websocket", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "progress": { + "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut." + }, + "step": { + "hassio_confirm": { + "title": "Skonfiguruj integracj\u0119 Z-Wave JS z dodatkiem Z-Wave JS" + }, + "install_addon": { + "title": "Rozpocz\u0119\u0142a si\u0119 instalacja dodatku Z-Wave JS" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "U\u017cyj dodatku Z-Wave JS Supervisor" + }, + "description": "Czy chcesz skorzysta\u0107 z dodatku Z-Wave JS Supervisor?", + "title": "Wybierz metod\u0119 po\u0142\u0105czenia" + }, + "start_addon": { + "data": { + "network_key": "Klucz sieci", + "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" + }, + "title": "Wprowad\u017a konfiguracj\u0119 dodatku Z-Wave JS" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/pt-BR.json b/homeassistant/components/zwave_js/translations/pt-BR.json new file mode 100644 index 00000000000000..e29d809ebff3dd --- /dev/null +++ b/homeassistant/components/zwave_js/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/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json new file mode 100644 index 00000000000000..2d9609e9d00437 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS.", + "addon_info_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", + "addon_install_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", + "addon_missing_discovery_info": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", + "addon_set_config_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e Z-Wave JS.", + "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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "error": { + "addon_start_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_ws_url": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "progress": { + "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442." + }, + "step": { + "hassio_confirm": { + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Z-Wave JS (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant Z-Wave JS)" + }, + "install_addon": { + "title": "\u041d\u0430\u0447\u0430\u043b\u0430\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS" + }, + "manual": { + "data": { + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + } + }, + "on_supervisor": { + "data": { + "use_addon": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS" + }, + "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS?", + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + }, + "start_addon": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438", + "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + }, + "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS" + }, + "user": { + "data": { + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/tr.json b/homeassistant/components/zwave_js/translations/tr.json new file mode 100644 index 00000000000000..2faa8ba43075b5 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/tr.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Z-Wave JS eklenti ke\u015fif bilgileri al\u0131namad\u0131.", + "addon_info_failed": "Z-Wave JS eklenti bilgileri al\u0131namad\u0131.", + "addon_install_failed": "Z-Wave JS eklentisi y\u00fcklenemedi.", + "addon_missing_discovery_info": "Eksik Z-Wave JS eklenti bulma bilgileri.", + "addon_set_config_failed": "Z-Wave JS yap\u0131land\u0131rmas\u0131 ayarlanamad\u0131.", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "addon_start_failed": "Z-Wave JS eklentisi ba\u015flat\u0131lamad\u0131. Yap\u0131land\u0131rmay\u0131 kontrol edin.", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_ws_url": "Ge\u00e7ersiz websocket URL'si", + "unknown": "Beklenmeyen hata" + }, + "progress": { + "install_addon": "L\u00fctfen Z-Wave JS eklenti kurulumu bitene kadar bekleyin. Bu birka\u00e7 dakika s\u00fcrebilir." + }, + "step": { + "hassio_confirm": { + "title": "Z-Wave JS eklentisiyle Z-Wave JS entegrasyonunu ayarlay\u0131n" + }, + "install_addon": { + "title": "Z-Wave JS eklenti kurulumu ba\u015flad\u0131" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Z-Wave JS Supervisor eklentisini kullan\u0131n" + }, + "description": "Z-Wave JS Supervisor eklentisini kullanmak istiyor musunuz?", + "title": "Ba\u011flant\u0131 y\u00f6ntemini se\u00e7in" + }, + "start_addon": { + "data": { + "network_key": "A\u011f Anahtar\u0131", + "usb_path": "USB Ayg\u0131t Yolu" + }, + "title": "Z-Wave JS eklenti yap\u0131land\u0131rmas\u0131na girin" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/uk.json b/homeassistant/components/zwave_js/translations/uk.json new file mode 100644 index 00000000000000..f5ff5224347ae9 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_ws_url": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u0430", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json new file mode 100644 index 00000000000000..1cbde8f886ba84 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u8cc7\u8a0a\u5931\u6557\u3002", + "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", + "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", + "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "error": { + "addon_start_failed": "Z-Wave JS add-on \u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_ws_url": "Websocket URL \u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "progress": { + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + }, + "step": { + "hassio_confirm": { + "title": "\u4ee5 Z-Wave JS add-on \u8a2d\u5b9a Z-Wave JS \u6574\u5408" + }, + "install_addon": { + "title": "Z-Wave JS add-on \u5b89\u88dd\u5df2\u555f\u52d5" + }, + "manual": { + "data": { + "url": "\u7db2\u5740" + } + }, + "on_supervisor": { + "data": { + "use_addon": "\u4f7f\u7528 Z-Wave JS Supervisor add-on" + }, + "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", + "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" + }, + "start_addon": { + "data": { + "network_key": "\u7db2\u8def\u5bc6\u9470", + "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" + }, + "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u8a2d\u5b9a" + }, + "user": { + "data": { + "url": "\u7db2\u5740" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file From 889cb254b203960abfd8c4fa94648d4869fa8700 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:46:49 +0100 Subject: [PATCH 0142/1818] Update translations --- .../components/abode/translations/de.json | 2 +- .../components/abode/translations/es.json | 4 +- .../components/abode/translations/fr.json | 21 +++- .../components/abode/translations/tr.json | 34 ++++++ .../components/abode/translations/uk.json | 35 ++++++ .../accuweather/translations/ca.json | 2 +- .../accuweather/translations/cs.json | 2 +- .../accuweather/translations/de.json | 9 +- .../accuweather/translations/en.json | 2 +- .../accuweather/translations/et.json | 2 +- .../accuweather/translations/fr.json | 3 +- .../accuweather/translations/it.json | 2 +- .../accuweather/translations/no.json | 2 +- .../accuweather/translations/pl.json | 2 +- .../accuweather/translations/ru.json | 2 +- .../accuweather/translations/sensor.uk.json | 9 ++ .../accuweather/translations/tr.json | 38 ++++++ .../accuweather/translations/uk.json | 21 +++- .../accuweather/translations/zh-Hant.json | 2 +- .../components/acmeda/translations/de.json | 5 +- .../components/acmeda/translations/tr.json | 11 ++ .../components/acmeda/translations/uk.json | 15 +++ .../components/adguard/translations/de.json | 6 +- .../components/adguard/translations/no.json | 4 +- .../components/adguard/translations/ru.json | 4 +- .../components/adguard/translations/tr.json | 20 ++++ .../components/adguard/translations/uk.json | 28 +++++ .../advantage_air/translations/de.json | 5 +- .../advantage_air/translations/tr.json | 19 +++ .../advantage_air/translations/uk.json | 20 ++++ .../components/agent_dvr/translations/de.json | 4 +- .../components/agent_dvr/translations/tr.json | 20 ++++ .../components/agent_dvr/translations/uk.json | 20 ++++ .../components/airly/translations/de.json | 7 +- .../components/airly/translations/tr.json | 17 +++ .../components/airly/translations/uk.json | 28 +++++ .../components/airnow/translations/ca.json | 26 +++++ .../components/airnow/translations/cs.json | 23 ++++ .../components/airnow/translations/de.json | 24 ++++ .../components/airnow/translations/en.json | 9 +- .../components/airnow/translations/es.json | 26 +++++ .../components/airnow/translations/et.json | 26 +++++ .../components/airnow/translations/fr.json | 26 +++++ .../components/airnow/translations/it.json | 26 +++++ .../components/airnow/translations/lb.json | 24 ++++ .../components/airnow/translations/no.json | 26 +++++ .../components/airnow/translations/pl.json | 26 +++++ .../components/airnow/translations/pt.json | 23 ++++ .../components/airnow/translations/ru.json | 26 +++++ .../components/airnow/translations/tr.json | 25 ++++ .../components/airnow/translations/uk.json | 26 +++++ .../airnow/translations/zh-Hant.json | 26 +++++ .../components/airvisual/translations/ar.json | 11 ++ .../components/airvisual/translations/ca.json | 24 +++- .../components/airvisual/translations/de.json | 11 +- .../components/airvisual/translations/en.json | 11 +- .../components/airvisual/translations/et.json | 22 +++- .../components/airvisual/translations/fr.json | 10 +- .../components/airvisual/translations/no.json | 22 +++- .../components/airvisual/translations/pl.json | 22 +++- .../components/airvisual/translations/sv.json | 3 +- .../components/airvisual/translations/tr.json | 59 ++++++++++ .../components/airvisual/translations/uk.json | 57 +++++++++ .../airvisual/translations/zh-Hant.json | 22 +++- .../alarm_control_panel/translations/tr.json | 6 + .../alarm_control_panel/translations/uk.json | 29 ++++- .../alarmdecoder/translations/de.json | 7 +- .../alarmdecoder/translations/tr.json | 48 ++++++++ .../alarmdecoder/translations/uk.json | 74 ++++++++++++ .../components/almond/translations/de.json | 6 +- .../components/almond/translations/no.json | 4 +- .../components/almond/translations/ru.json | 4 +- .../components/almond/translations/tr.json | 8 ++ .../components/almond/translations/uk.json | 19 +++ .../ambiclimate/translations/de.json | 4 +- .../ambiclimate/translations/fr.json | 2 +- .../ambiclimate/translations/tr.json | 7 ++ .../ambiclimate/translations/uk.json | 22 ++++ .../ambient_station/translations/de.json | 4 +- .../ambient_station/translations/tr.json | 17 +++ .../ambient_station/translations/uk.json | 20 ++++ .../components/apple_tv/translations/fr.json | 15 ++- .../components/apple_tv/translations/lb.json | 8 ++ .../components/apple_tv/translations/tr.json | 2 + .../components/apple_tv/translations/uk.json | 64 ++++++++++ .../apple_tv/translations/zh-Hans.json | 17 +++ .../components/arcam_fmj/translations/de.json | 3 +- .../components/arcam_fmj/translations/ro.json | 9 ++ .../components/arcam_fmj/translations/tr.json | 17 +++ .../components/arcam_fmj/translations/uk.json | 27 +++++ .../components/atag/translations/de.json | 6 +- .../components/atag/translations/tr.json | 21 ++++ .../components/atag/translations/uk.json | 21 ++++ .../components/august/translations/de.json | 5 +- .../components/august/translations/tr.json | 31 +++++ .../components/august/translations/uk.json | 32 +++++ .../components/aurora/translations/de.json | 14 ++- .../components/aurora/translations/fr.json | 26 +++++ .../components/aurora/translations/tr.json | 16 +++ .../components/aurora/translations/uk.json | 26 +++++ .../components/auth/translations/de.json | 2 +- .../components/auth/translations/tr.json | 22 ++++ .../components/auth/translations/uk.json | 23 +++- .../components/awair/translations/de.json | 12 +- .../components/awair/translations/tr.json | 27 +++++ .../components/awair/translations/uk.json | 29 +++++ .../components/axis/translations/ca.json | 2 +- .../components/axis/translations/cs.json | 2 +- .../components/axis/translations/de.json | 5 +- .../components/axis/translations/en.json | 2 +- .../components/axis/translations/et.json | 2 +- .../components/axis/translations/it.json | 2 +- .../components/axis/translations/no.json | 2 +- .../components/axis/translations/pl.json | 2 +- .../components/axis/translations/ru.json | 2 +- .../components/axis/translations/tr.json | 23 ++++ .../components/axis/translations/uk.json | 37 ++++++ .../components/axis/translations/zh-Hant.json | 2 +- .../azure_devops/translations/de.json | 7 +- .../azure_devops/translations/fr.json | 2 +- .../azure_devops/translations/tr.json | 28 +++++ .../azure_devops/translations/uk.json | 16 ++- .../binary_sensor/translations/de.json | 16 +++ .../binary_sensor/translations/pl.json | 2 +- .../binary_sensor/translations/tr.json | 22 ++++ .../binary_sensor/translations/uk.json | 108 +++++++++++++++-- .../components/blebox/translations/de.json | 6 +- .../components/blebox/translations/tr.json | 20 ++++ .../components/blebox/translations/uk.json | 24 ++++ .../components/blink/translations/de.json | 5 +- .../components/blink/translations/tr.json | 24 ++++ .../components/blink/translations/uk.json | 40 +++++++ .../bmw_connected_drive/translations/ca.json | 30 +++++ .../bmw_connected_drive/translations/cs.json | 19 +++ .../bmw_connected_drive/translations/de.json | 19 +++ .../bmw_connected_drive/translations/en.json | 1 - .../bmw_connected_drive/translations/es.json | 30 +++++ .../bmw_connected_drive/translations/et.json | 30 +++++ .../bmw_connected_drive/translations/fr.json | 29 +++++ .../bmw_connected_drive/translations/it.json | 30 +++++ .../bmw_connected_drive/translations/lb.json | 19 +++ .../bmw_connected_drive/translations/no.json | 30 +++++ .../bmw_connected_drive/translations/pl.json | 30 +++++ .../bmw_connected_drive/translations/pt.json | 19 +++ .../bmw_connected_drive/translations/ru.json | 30 +++++ .../bmw_connected_drive/translations/tr.json | 19 +++ .../bmw_connected_drive/translations/uk.json | 30 +++++ .../translations/zh-Hant.json | 30 +++++ .../components/bond/translations/de.json | 6 +- .../components/bond/translations/tr.json | 25 ++++ .../components/bond/translations/uk.json | 23 +++- .../components/braviatv/translations/de.json | 7 +- .../components/braviatv/translations/tr.json | 29 +++++ .../components/braviatv/translations/uk.json | 39 +++++++ .../components/broadlink/translations/de.json | 6 +- .../components/broadlink/translations/tr.json | 39 +++++++ .../components/broadlink/translations/uk.json | 47 ++++++++ .../components/brother/translations/de.json | 2 +- .../components/brother/translations/tr.json | 14 +++ .../components/brother/translations/uk.json | 30 +++++ .../components/bsblan/translations/de.json | 2 +- .../components/bsblan/translations/tr.json | 8 ++ .../components/bsblan/translations/uk.json | 24 ++++ .../components/canary/translations/de.json | 3 +- .../components/canary/translations/tr.json | 19 +++ .../components/canary/translations/uk.json | 31 +++++ .../components/cast/translations/de.json | 4 +- .../components/cast/translations/tr.json | 12 ++ .../components/cast/translations/uk.json | 6 +- .../cert_expiry/translations/tr.json | 15 +++ .../cert_expiry/translations/uk.json | 24 ++++ .../components/climate/translations/tr.json | 6 + .../components/climate/translations/uk.json | 14 +-- .../components/cloud/translations/ca.json | 6 +- .../components/cloud/translations/de.json | 15 +++ .../components/cloud/translations/fr.json | 16 +++ .../components/cloud/translations/pl.json | 2 +- .../components/cloud/translations/tr.json | 3 + .../components/cloud/translations/uk.json | 16 +++ .../cloudflare/translations/de.json | 8 ++ .../cloudflare/translations/tr.json | 9 ++ .../cloudflare/translations/uk.json | 35 ++++++ .../components/control4/translations/de.json | 6 +- .../components/control4/translations/tr.json | 21 ++++ .../components/control4/translations/uk.json | 15 ++- .../coolmaster/translations/de.json | 2 +- .../coolmaster/translations/tr.json | 15 +++ .../coolmaster/translations/uk.json | 22 ++++ .../coronavirus/translations/tr.json | 15 +++ .../coronavirus/translations/uk.json | 15 +++ .../components/cover/translations/tr.json | 6 + .../components/cover/translations/uk.json | 21 +++- .../components/daikin/translations/de.json | 6 +- .../components/daikin/translations/pt.json | 2 +- .../components/daikin/translations/tr.json | 22 ++++ .../components/daikin/translations/uk.json | 24 ++++ .../components/deconz/translations/cs.json | 11 +- .../components/deconz/translations/de.json | 12 +- .../components/deconz/translations/no.json | 4 +- .../components/deconz/translations/pl.json | 6 +- .../components/deconz/translations/ru.json | 4 +- .../components/deconz/translations/tr.json | 43 +++++++ .../components/deconz/translations/uk.json | 105 +++++++++++++++++ .../components/demo/translations/tr.json | 12 ++ .../components/demo/translations/uk.json | 21 ++++ .../components/denonavr/translations/de.json | 4 + .../components/denonavr/translations/tr.json | 16 +++ .../components/denonavr/translations/uk.json | 48 ++++++++ .../device_tracker/translations/de.json | 8 +- .../device_tracker/translations/uk.json | 12 +- .../devolo_home_control/translations/de.json | 7 +- .../devolo_home_control/translations/tr.json | 18 +++ .../devolo_home_control/translations/uk.json | 20 ++++ .../components/dexcom/translations/de.json | 3 +- .../components/dexcom/translations/fr.json | 2 +- .../components/dexcom/translations/tr.json | 22 ++++ .../components/dexcom/translations/uk.json | 32 +++++ .../dialogflow/translations/de.json | 4 + .../dialogflow/translations/tr.json | 8 ++ .../dialogflow/translations/uk.json | 17 +++ .../components/directv/translations/tr.json | 21 ++++ .../components/directv/translations/uk.json | 22 ++++ .../components/doorbird/translations/de.json | 2 +- .../components/doorbird/translations/tr.json | 22 ++++ .../components/doorbird/translations/uk.json | 36 ++++++ .../components/dsmr/translations/de.json | 7 ++ .../components/dsmr/translations/fr.json | 10 ++ .../components/dsmr/translations/tr.json | 5 + .../components/dsmr/translations/uk.json | 17 +++ .../components/dunehd/translations/tr.json | 19 +++ .../components/dunehd/translations/uk.json | 21 ++++ .../components/eafm/translations/de.json | 7 ++ .../components/eafm/translations/tr.json | 16 +++ .../components/eafm/translations/uk.json | 17 +++ .../components/ecobee/translations/de.json | 3 + .../components/ecobee/translations/tr.json | 14 +++ .../components/ecobee/translations/uk.json | 24 ++++ .../components/econet/translations/ca.json | 22 ++++ .../components/econet/translations/en.json | 1 + .../components/econet/translations/es.json | 21 ++++ .../components/econet/translations/et.json | 22 ++++ .../components/econet/translations/it.json | 22 ++++ .../components/econet/translations/no.json | 22 ++++ .../components/econet/translations/pl.json | 22 ++++ .../components/econet/translations/ru.json | 22 ++++ .../components/econet/translations/tr.json | 22 ++++ .../econet/translations/zh-Hant.json | 22 ++++ .../components/elgato/translations/de.json | 4 +- .../components/elgato/translations/tr.json | 19 +++ .../components/elgato/translations/uk.json | 25 ++++ .../components/elkm1/translations/de.json | 2 +- .../components/elkm1/translations/tr.json | 21 ++++ .../components/elkm1/translations/uk.json | 27 +++++ .../emulated_roku/translations/de.json | 2 +- .../emulated_roku/translations/tr.json | 7 ++ .../emulated_roku/translations/uk.json | 21 ++++ .../components/enocean/translations/tr.json | 23 ++++ .../components/enocean/translations/uk.json | 25 ++++ .../components/epson/translations/de.json | 6 +- .../components/epson/translations/tr.json | 3 + .../components/epson/translations/uk.json | 16 +++ .../components/esphome/translations/de.json | 3 +- .../components/esphome/translations/pt.json | 2 +- .../components/esphome/translations/tr.json | 19 +++ .../components/esphome/translations/uk.json | 11 +- .../components/fan/translations/tr.json | 10 ++ .../components/fan/translations/uk.json | 12 +- .../fireservicerota/translations/fr.json | 27 +++++ .../fireservicerota/translations/tr.json | 10 ++ .../fireservicerota/translations/uk.json | 29 +++++ .../components/firmata/translations/tr.json | 7 ++ .../components/firmata/translations/uk.json | 7 ++ .../flick_electric/translations/de.json | 2 +- .../flick_electric/translations/tr.json | 20 ++++ .../flick_electric/translations/uk.json | 23 ++++ .../components/flo/translations/de.json | 7 +- .../components/flo/translations/tr.json | 21 ++++ .../components/flo/translations/uk.json | 21 ++++ .../components/flume/translations/de.json | 4 +- .../components/flume/translations/tr.json | 20 ++++ .../components/flume/translations/uk.json | 24 ++++ .../flunearyou/translations/de.json | 2 +- .../flunearyou/translations/tr.json | 18 +++ .../flunearyou/translations/uk.json | 20 ++++ .../forked_daapd/translations/de.json | 2 +- .../forked_daapd/translations/tr.json | 21 ++++ .../forked_daapd/translations/uk.json | 42 +++++++ .../components/foscam/translations/af.json | 11 ++ .../components/foscam/translations/ca.json | 24 ++++ .../components/foscam/translations/cs.json | 23 ++++ .../components/foscam/translations/de.json | 23 ++++ .../components/foscam/translations/en.json | 44 +++---- .../components/foscam/translations/es.json | 24 ++++ .../components/foscam/translations/et.json | 24 ++++ .../components/foscam/translations/fr.json | 24 ++++ .../components/foscam/translations/it.json | 24 ++++ .../components/foscam/translations/lb.json | 16 +++ .../components/foscam/translations/no.json | 24 ++++ .../components/foscam/translations/pl.json | 24 ++++ .../components/foscam/translations/pt.json | 11 ++ .../components/foscam/translations/ru.json | 24 ++++ .../components/foscam/translations/tr.json | 24 ++++ .../foscam/translations/zh-Hant.json | 24 ++++ .../components/freebox/translations/de.json | 6 +- .../components/freebox/translations/tr.json | 20 ++++ .../components/freebox/translations/uk.json | 25 ++++ .../components/fritzbox/translations/ca.json | 10 +- .../components/fritzbox/translations/cs.json | 9 +- .../components/fritzbox/translations/de.json | 12 +- .../components/fritzbox/translations/en.json | 10 +- .../components/fritzbox/translations/et.json | 10 +- .../components/fritzbox/translations/it.json | 10 +- .../components/fritzbox/translations/no.json | 10 +- .../components/fritzbox/translations/pl.json | 10 +- .../components/fritzbox/translations/ru.json | 10 +- .../components/fritzbox/translations/tr.json | 35 ++++++ .../components/fritzbox/translations/uk.json | 31 +++++ .../fritzbox/translations/zh-Hant.json | 10 +- .../fritzbox_callmonitor/translations/ca.json | 41 +++++++ .../fritzbox_callmonitor/translations/cs.json | 21 ++++ .../fritzbox_callmonitor/translations/et.json | 41 +++++++ .../fritzbox_callmonitor/translations/it.json | 41 +++++++ .../fritzbox_callmonitor/translations/lb.json | 33 ++++++ .../fritzbox_callmonitor/translations/no.json | 41 +++++++ .../fritzbox_callmonitor/translations/pl.json | 41 +++++++ .../fritzbox_callmonitor/translations/ru.json | 41 +++++++ .../fritzbox_callmonitor/translations/tr.json | 41 +++++++ .../translations/zh-Hant.json | 41 +++++++ .../garmin_connect/translations/de.json | 8 +- .../garmin_connect/translations/tr.json | 20 ++++ .../garmin_connect/translations/uk.json | 23 ++++ .../components/gdacs/translations/de.json | 2 +- .../components/gdacs/translations/tr.json | 14 +++ .../components/gdacs/translations/uk.json | 15 +++ .../components/geofency/translations/de.json | 4 + .../components/geofency/translations/tr.json | 8 ++ .../components/geofency/translations/uk.json | 17 +++ .../geonetnz_quakes/translations/tr.json | 7 ++ .../geonetnz_quakes/translations/uk.json | 16 +++ .../geonetnz_volcano/translations/de.json | 3 + .../geonetnz_volcano/translations/tr.json | 14 +++ .../geonetnz_volcano/translations/uk.json | 15 +++ .../components/gios/translations/de.json | 4 +- .../components/gios/translations/fr.json | 5 + .../components/gios/translations/it.json | 2 +- .../components/gios/translations/lb.json | 5 + .../components/gios/translations/tr.json | 10 ++ .../components/gios/translations/uk.json | 27 +++++ .../components/glances/translations/de.json | 4 +- .../components/glances/translations/tr.json | 29 +++++ .../components/glances/translations/uk.json | 36 ++++++ .../components/goalzero/translations/de.json | 13 ++- .../components/goalzero/translations/fr.json | 2 +- .../components/goalzero/translations/tr.json | 18 +++ .../components/goalzero/translations/uk.json | 22 ++++ .../components/gogogate2/translations/tr.json | 20 ++++ .../components/gogogate2/translations/uk.json | 22 ++++ .../components/gpslogger/translations/de.json | 4 + .../components/gpslogger/translations/tr.json | 8 ++ .../components/gpslogger/translations/uk.json | 17 +++ .../components/gree/translations/de.json | 13 +++ .../components/gree/translations/tr.json | 12 ++ .../components/gree/translations/uk.json | 13 +++ .../components/griddy/translations/de.json | 4 +- .../components/griddy/translations/tr.json | 3 + .../components/griddy/translations/uk.json | 20 ++++ .../components/group/translations/uk.json | 2 +- .../components/guardian/translations/de.json | 3 +- .../components/guardian/translations/tr.json | 17 +++ .../components/guardian/translations/uk.json | 21 ++++ .../components/hangouts/translations/de.json | 2 +- .../components/hangouts/translations/tr.json | 16 +++ .../components/hangouts/translations/uk.json | 31 +++++ .../components/harmony/translations/de.json | 2 +- .../components/harmony/translations/tr.json | 18 +++ .../components/harmony/translations/uk.json | 36 ++++++ .../components/hassio/translations/ca.json | 2 +- .../components/hassio/translations/de.json | 15 +++ .../components/hassio/translations/en.json | 4 +- .../components/hassio/translations/fr.json | 15 +++ .../components/hassio/translations/tr.json | 8 +- .../components/hassio/translations/uk.json | 16 +++ .../components/heos/translations/de.json | 5 +- .../components/heos/translations/tr.json | 17 +++ .../components/heos/translations/uk.json | 19 +++ .../hisense_aehw4a1/translations/de.json | 2 +- .../hisense_aehw4a1/translations/tr.json | 12 ++ .../hisense_aehw4a1/translations/uk.json | 13 +++ .../components/hlk_sw16/translations/de.json | 6 + .../components/hlk_sw16/translations/tr.json | 21 ++++ .../components/hlk_sw16/translations/uk.json | 21 ++++ .../home_connect/translations/de.json | 7 +- .../home_connect/translations/uk.json | 16 +++ .../homeassistant/translations/de.json | 13 ++- .../homeassistant/translations/fr.json | 21 ++++ .../homeassistant/translations/tr.json | 2 + .../homeassistant/translations/uk.json | 21 ++++ .../components/homekit/translations/ca.json | 31 +++-- .../components/homekit/translations/cs.json | 8 +- .../components/homekit/translations/de.json | 7 +- .../components/homekit/translations/en.json | 19 ++- .../components/homekit/translations/et.json | 27 ++++- .../components/homekit/translations/it.json | 25 +++- .../components/homekit/translations/no.json | 29 +++-- .../components/homekit/translations/pl.json | 25 +++- .../components/homekit/translations/ro.json | 9 ++ .../components/homekit/translations/ru.json | 11 +- .../components/homekit/translations/sv.json | 21 ++++ .../components/homekit/translations/tr.json | 60 ++++++++++ .../components/homekit/translations/uk.json | 51 +++++++- .../homekit/translations/zh-Hant.json | 31 +++-- .../homekit_controller/translations/cs.json | 6 +- .../homekit_controller/translations/de.json | 4 +- .../homekit_controller/translations/pl.json | 6 +- .../homekit_controller/translations/tr.json | 35 ++++++ .../homekit_controller/translations/uk.json | 71 ++++++++++++ .../homematicip_cloud/translations/de.json | 8 +- .../homematicip_cloud/translations/tr.json | 9 ++ .../homematicip_cloud/translations/uk.json | 29 +++++ .../huawei_lte/translations/de.json | 5 +- .../huawei_lte/translations/tr.json | 27 +++++ .../huawei_lte/translations/uk.json | 42 +++++++ .../components/hue/translations/cs.json | 2 +- .../components/hue/translations/de.json | 20 ++-- .../components/hue/translations/pl.json | 10 +- .../components/hue/translations/pt.json | 8 +- .../components/hue/translations/tr.json | 48 ++++++++ .../components/hue/translations/uk.json | 67 +++++++++++ .../huisbaasje/translations/ca.json | 21 ++++ .../huisbaasje/translations/cs.json | 21 ++++ .../huisbaasje/translations/en.json | 21 ++++ .../huisbaasje/translations/et.json | 21 ++++ .../huisbaasje/translations/it.json | 21 ++++ .../huisbaasje/translations/no.json | 21 ++++ .../huisbaasje/translations/pl.json | 21 ++++ .../huisbaasje/translations/ru.json | 21 ++++ .../huisbaasje/translations/tr.json | 21 ++++ .../huisbaasje/translations/zh-Hant.json | 21 ++++ .../humidifier/translations/tr.json | 25 ++++ .../humidifier/translations/uk.json | 24 +++- .../translations/tr.json | 18 +++ .../translations/uk.json | 23 ++++ .../hvv_departures/translations/tr.json | 20 ++++ .../hvv_departures/translations/uk.json | 47 ++++++++ .../components/hyperion/translations/fr.json | 4 + .../components/hyperion/translations/it.json | 2 +- .../components/hyperion/translations/tr.json | 12 +- .../components/hyperion/translations/uk.json | 53 +++++++++ .../components/iaqualink/translations/de.json | 6 + .../components/iaqualink/translations/tr.json | 19 +++ .../components/iaqualink/translations/uk.json | 20 ++++ .../components/icloud/translations/de.json | 9 +- .../components/icloud/translations/tr.json | 23 +++- .../components/icloud/translations/uk.json | 46 ++++++++ .../components/ifttt/translations/de.json | 4 + .../components/ifttt/translations/tr.json | 8 ++ .../components/ifttt/translations/uk.json | 17 +++ .../input_boolean/translations/uk.json | 2 +- .../input_datetime/translations/uk.json | 2 +- .../input_number/translations/uk.json | 2 +- .../input_select/translations/uk.json | 2 +- .../input_text/translations/uk.json | 2 +- .../components/insteon/translations/de.json | 6 +- .../components/insteon/translations/tr.json | 89 ++++++++++++++ .../components/insteon/translations/uk.json | 109 ++++++++++++++++++ .../components/ios/translations/de.json | 2 +- .../components/ios/translations/tr.json | 12 ++ .../components/ios/translations/uk.json | 12 ++ .../components/ipma/translations/lb.json | 5 + .../components/ipma/translations/tr.json | 11 ++ .../components/ipma/translations/uk.json | 17 ++- .../components/ipp/translations/de.json | 8 +- .../components/ipp/translations/tr.json | 17 +++ .../components/ipp/translations/uk.json | 35 ++++++ .../components/iqvia/translations/tr.json | 7 ++ .../components/iqvia/translations/uk.json | 19 +++ .../islamic_prayer_times/translations/de.json | 5 + .../islamic_prayer_times/translations/tr.json | 7 ++ .../islamic_prayer_times/translations/uk.json | 23 ++++ .../components/isy994/translations/de.json | 2 +- .../components/isy994/translations/tr.json | 30 +++++ .../components/isy994/translations/uk.json | 40 +++++++ .../components/izone/translations/de.json | 2 +- .../components/izone/translations/tr.json | 12 ++ .../components/izone/translations/uk.json | 13 +++ .../components/juicenet/translations/de.json | 10 +- .../components/juicenet/translations/tr.json | 21 ++++ .../components/juicenet/translations/uk.json | 21 ++++ .../components/kodi/translations/de.json | 4 + .../components/kodi/translations/tr.json | 35 ++++++ .../components/kodi/translations/uk.json | 50 ++++++++ .../components/konnected/translations/de.json | 12 +- .../components/konnected/translations/pl.json | 4 +- .../components/konnected/translations/tr.json | 60 ++++++++++ .../components/konnected/translations/uk.json | 108 +++++++++++++++++ .../components/kulersky/translations/de.json | 2 +- .../components/kulersky/translations/lb.json | 13 +++ .../components/kulersky/translations/tr.json | 8 +- .../components/kulersky/translations/uk.json | 13 +++ .../components/life360/translations/de.json | 2 + .../components/life360/translations/fr.json | 2 +- .../components/life360/translations/tr.json | 11 ++ .../components/life360/translations/uk.json | 27 +++++ .../components/lifx/translations/de.json | 4 +- .../components/lifx/translations/tr.json | 12 ++ .../components/lifx/translations/uk.json | 13 +++ .../components/light/translations/uk.json | 12 ++ .../components/local_ip/translations/de.json | 3 +- .../components/local_ip/translations/es.json | 2 +- .../components/local_ip/translations/tr.json | 17 +++ .../components/local_ip/translations/uk.json | 17 +++ .../components/locative/translations/de.json | 6 +- .../components/locative/translations/tr.json | 8 ++ .../components/locative/translations/uk.json | 17 +++ .../components/lock/translations/pt.json | 3 + .../components/lock/translations/tr.json | 6 + .../components/lock/translations/uk.json | 15 +++ .../logi_circle/translations/de.json | 8 +- .../logi_circle/translations/fr.json | 2 +- .../logi_circle/translations/lb.json | 1 + .../logi_circle/translations/tr.json | 10 ++ .../logi_circle/translations/uk.json | 28 +++++ .../components/lovelace/translations/de.json | 3 + .../components/lovelace/translations/fr.json | 10 ++ .../components/lovelace/translations/tr.json | 3 +- .../components/lovelace/translations/uk.json | 10 ++ .../components/luftdaten/translations/de.json | 1 + .../components/luftdaten/translations/tr.json | 8 ++ .../components/luftdaten/translations/uk.json | 18 +++ .../lutron_caseta/translations/ca.json | 61 +++++++++- .../lutron_caseta/translations/cs.json | 7 ++ .../lutron_caseta/translations/es.json | 36 +++++- .../lutron_caseta/translations/et.json | 61 +++++++++- .../lutron_caseta/translations/it.json | 61 +++++++++- .../lutron_caseta/translations/no.json | 61 +++++++++- .../lutron_caseta/translations/pl.json | 61 +++++++++- .../lutron_caseta/translations/ru.json | 42 ++++++- .../lutron_caseta/translations/tr.json | 72 ++++++++++++ .../lutron_caseta/translations/uk.json | 17 +++ .../lutron_caseta/translations/zh-Hant.json | 61 +++++++++- .../components/lyric/translations/ca.json | 16 +++ .../components/lyric/translations/cs.json | 16 +++ .../components/lyric/translations/en.json | 16 +++ .../components/lyric/translations/et.json | 16 +++ .../components/lyric/translations/it.json | 16 +++ .../components/lyric/translations/no.json | 16 +++ .../components/lyric/translations/pl.json | 16 +++ .../components/lyric/translations/tr.json | 16 +++ .../lyric/translations/zh-Hant.json | 16 +++ .../components/mailgun/translations/de.json | 6 +- .../components/mailgun/translations/tr.json | 8 ++ .../components/mailgun/translations/uk.json | 17 +++ .../media_player/translations/tr.json | 6 + .../media_player/translations/uk.json | 13 ++- .../components/melcloud/translations/de.json | 2 +- .../components/melcloud/translations/tr.json | 20 ++++ .../components/melcloud/translations/uk.json | 22 ++++ .../components/met/translations/de.json | 3 + .../components/met/translations/tr.json | 15 +++ .../components/met/translations/uk.json | 19 +++ .../meteo_france/translations/de.json | 4 +- .../meteo_france/translations/tr.json | 21 ++++ .../meteo_france/translations/uk.json | 36 ++++++ .../components/metoffice/translations/de.json | 1 + .../components/metoffice/translations/tr.json | 21 ++++ .../components/metoffice/translations/uk.json | 22 ++++ .../components/mikrotik/translations/de.json | 5 +- .../components/mikrotik/translations/tr.json | 21 ++++ .../components/mikrotik/translations/uk.json | 36 ++++++ .../components/mill/translations/de.json | 3 + .../components/mill/translations/fr.json | 2 +- .../components/mill/translations/tr.json | 18 +++ .../components/mill/translations/uk.json | 18 +++ .../minecraft_server/translations/de.json | 2 +- .../minecraft_server/translations/tr.json | 2 +- .../minecraft_server/translations/uk.json | 22 ++++ .../mobile_app/translations/tr.json | 7 ++ .../mobile_app/translations/uk.json | 10 +- .../components/monoprice/translations/de.json | 2 +- .../components/monoprice/translations/tr.json | 25 ++++ .../components/monoprice/translations/uk.json | 40 +++++++ .../moon/translations/sensor.uk.json | 6 +- .../motion_blinds/translations/ca.json | 19 ++- .../motion_blinds/translations/cs.json | 11 +- .../motion_blinds/translations/de.json | 14 ++- .../motion_blinds/translations/en.json | 19 ++- .../motion_blinds/translations/es.json | 19 ++- .../motion_blinds/translations/et.json | 19 ++- .../motion_blinds/translations/fr.json | 17 +++ .../motion_blinds/translations/it.json | 19 ++- .../motion_blinds/translations/lb.json | 14 +++ .../motion_blinds/translations/no.json | 19 ++- .../motion_blinds/translations/pl.json | 19 ++- .../motion_blinds/translations/pt.json | 15 ++- .../motion_blinds/translations/ru.json | 19 ++- .../motion_blinds/translations/tr.json | 18 ++- .../motion_blinds/translations/uk.json | 37 ++++++ .../motion_blinds/translations/zh-Hant.json | 19 ++- .../components/mqtt/translations/cs.json | 10 +- .../components/mqtt/translations/de.json | 11 +- .../components/mqtt/translations/no.json | 4 +- .../components/mqtt/translations/pl.json | 16 +-- .../components/mqtt/translations/ru.json | 4 +- .../components/mqtt/translations/tr.json | 41 +++++++ .../components/mqtt/translations/uk.json | 70 ++++++++++- .../components/myq/translations/de.json | 4 +- .../components/myq/translations/tr.json | 21 ++++ .../components/myq/translations/uk.json | 21 ++++ .../components/neato/translations/de.json | 4 +- .../components/neato/translations/it.json | 14 +-- .../components/neato/translations/lb.json | 14 ++- .../components/neato/translations/pt.json | 15 ++- .../components/neato/translations/tr.json | 25 ++++ .../components/neato/translations/uk.json | 37 ++++++ .../components/nest/translations/de.json | 21 +++- .../components/nest/translations/fr.json | 3 +- .../components/nest/translations/it.json | 4 +- .../components/nest/translations/lb.json | 7 +- .../components/nest/translations/pl.json | 5 + .../components/nest/translations/pt.json | 7 +- .../components/nest/translations/tr.json | 13 ++- .../components/nest/translations/uk.json | 53 +++++++++ .../components/netatmo/translations/de.json | 6 +- .../components/netatmo/translations/tr.json | 32 +++++ .../components/netatmo/translations/uk.json | 43 +++++++ .../components/nexia/translations/de.json | 4 +- .../components/nexia/translations/tr.json | 21 ++++ .../components/nexia/translations/uk.json | 21 ++++ .../nightscout/translations/de.json | 6 + .../nightscout/translations/no.json | 2 +- .../nightscout/translations/tr.json | 9 +- .../nightscout/translations/uk.json | 23 ++++ .../components/notify/translations/uk.json | 2 +- .../components/notion/translations/de.json | 3 +- .../components/notion/translations/tr.json | 12 ++ .../components/notion/translations/uk.json | 20 ++++ .../components/nuheat/translations/de.json | 4 +- .../components/nuheat/translations/tr.json | 22 ++++ .../components/nuheat/translations/uk.json | 24 ++++ .../components/nuki/translations/ca.json | 18 +++ .../components/nuki/translations/cs.json | 18 +++ .../components/nuki/translations/en.json | 18 +++ .../components/nuki/translations/et.json | 18 +++ .../components/nuki/translations/it.json | 18 +++ .../components/nuki/translations/no.json | 18 +++ .../components/nuki/translations/pl.json | 18 +++ .../components/nuki/translations/ru.json | 18 +++ .../components/nuki/translations/tr.json | 18 +++ .../components/nuki/translations/zh-Hant.json | 18 +++ .../components/number/translations/ca.json | 8 ++ .../components/number/translations/cs.json | 8 ++ .../components/number/translations/en.json | 8 ++ .../components/number/translations/et.json | 8 ++ .../components/number/translations/it.json | 8 ++ .../components/number/translations/no.json | 8 ++ .../components/number/translations/pl.json | 8 ++ .../components/number/translations/ru.json | 8 ++ .../components/number/translations/tr.json | 8 ++ .../number/translations/zh-Hant.json | 8 ++ .../components/nut/translations/de.json | 2 +- .../components/nut/translations/tr.json | 41 +++++++ .../components/nut/translations/uk.json | 46 ++++++++ .../components/nws/translations/de.json | 4 +- .../components/nws/translations/tr.json | 21 ++++ .../components/nws/translations/uk.json | 23 ++++ .../components/nzbget/translations/de.json | 8 +- .../components/nzbget/translations/tr.json | 29 +++++ .../components/nzbget/translations/uk.json | 35 ++++++ .../components/omnilogic/translations/de.json | 6 +- .../components/omnilogic/translations/tr.json | 20 ++++ .../components/omnilogic/translations/uk.json | 29 +++++ .../onboarding/translations/uk.json | 7 ++ .../ondilo_ico/translations/ca.json | 17 +++ .../ondilo_ico/translations/cs.json | 17 +++ .../ondilo_ico/translations/de.json | 16 +++ .../ondilo_ico/translations/en.json | 3 +- .../ondilo_ico/translations/es.json | 17 +++ .../ondilo_ico/translations/et.json | 17 +++ .../ondilo_ico/translations/it.json | 17 +++ .../ondilo_ico/translations/lb.json | 11 ++ .../ondilo_ico/translations/no.json | 17 +++ .../ondilo_ico/translations/pl.json | 17 +++ .../ondilo_ico/translations/ru.json | 17 +++ .../ondilo_ico/translations/tr.json | 10 ++ .../ondilo_ico/translations/uk.json | 17 +++ .../ondilo_ico/translations/zh-Hant.json | 17 +++ .../components/onewire/translations/de.json | 3 + .../components/onewire/translations/tr.json | 24 ++++ .../components/onewire/translations/uk.json | 26 +++++ .../components/onvif/translations/de.json | 7 +- .../components/onvif/translations/tr.json | 36 +++++- .../components/onvif/translations/uk.json | 59 ++++++++++ .../opentherm_gw/translations/de.json | 2 +- .../opentherm_gw/translations/tr.json | 16 +++ .../opentherm_gw/translations/uk.json | 30 +++++ .../components/openuv/translations/de.json | 2 +- .../components/openuv/translations/tr.json | 19 +++ .../components/openuv/translations/uk.json | 9 +- .../openweathermap/translations/de.json | 6 +- .../openweathermap/translations/tr.json | 30 +++++ .../openweathermap/translations/uk.json | 35 ++++++ .../ovo_energy/translations/de.json | 8 +- .../ovo_energy/translations/fr.json | 7 +- .../ovo_energy/translations/lb.json | 5 + .../ovo_energy/translations/tr.json | 11 ++ .../ovo_energy/translations/uk.json | 27 +++++ .../components/owntracks/translations/de.json | 3 + .../components/owntracks/translations/tr.json | 7 ++ .../components/owntracks/translations/uk.json | 6 + .../components/ozw/translations/ca.json | 2 +- .../components/ozw/translations/de.json | 12 +- .../components/ozw/translations/lb.json | 9 ++ .../components/ozw/translations/no.json | 18 +-- .../components/ozw/translations/tr.json | 19 ++- .../components/ozw/translations/uk.json | 41 +++++++ .../panasonic_viera/translations/de.json | 16 +-- .../panasonic_viera/translations/tr.json | 19 +++ .../panasonic_viera/translations/uk.json | 30 +++++ .../components/person/translations/uk.json | 2 +- .../components/pi_hole/translations/ca.json | 6 + .../components/pi_hole/translations/cs.json | 5 + .../components/pi_hole/translations/de.json | 6 +- .../components/pi_hole/translations/en.json | 6 + .../components/pi_hole/translations/es.json | 6 + .../components/pi_hole/translations/et.json | 6 + .../components/pi_hole/translations/it.json | 6 + .../components/pi_hole/translations/no.json | 6 + .../components/pi_hole/translations/pl.json | 6 + .../components/pi_hole/translations/ru.json | 6 + .../components/pi_hole/translations/tr.json | 26 +++++ .../components/pi_hole/translations/uk.json | 23 ++++ .../pi_hole/translations/zh-Hant.json | 6 + .../components/plaato/translations/ca.json | 41 ++++++- .../components/plaato/translations/de.json | 6 +- .../components/plaato/translations/et.json | 41 ++++++- .../components/plaato/translations/no.json | 32 +++++ .../components/plaato/translations/pl.json | 41 ++++++- .../components/plaato/translations/tr.json | 43 +++++++ .../components/plaato/translations/uk.json | 17 +++ .../plaato/translations/zh-Hant.json | 41 ++++++- .../components/plant/translations/uk.json | 4 +- .../components/plex/translations/de.json | 5 +- .../components/plex/translations/tr.json | 27 +++++ .../components/plex/translations/uk.json | 62 ++++++++++ .../components/plugwise/translations/de.json | 5 +- .../components/plugwise/translations/lb.json | 3 +- .../components/plugwise/translations/tr.json | 15 ++- .../components/plugwise/translations/uk.json | 42 +++++++ .../plum_lightpad/translations/de.json | 5 +- .../plum_lightpad/translations/tr.json | 18 +++ .../plum_lightpad/translations/uk.json | 18 +++ .../components/point/translations/de.json | 10 +- .../components/point/translations/fr.json | 3 +- .../components/point/translations/tr.json | 11 ++ .../components/point/translations/uk.json | 32 +++++ .../components/poolsense/translations/de.json | 5 +- .../components/poolsense/translations/tr.json | 18 +++ .../components/poolsense/translations/uk.json | 20 ++++ .../components/powerwall/translations/ca.json | 1 + .../components/powerwall/translations/cs.json | 1 + .../components/powerwall/translations/de.json | 5 +- .../components/powerwall/translations/en.json | 36 +++--- .../components/powerwall/translations/es.json | 1 + .../components/powerwall/translations/et.json | 1 + .../components/powerwall/translations/it.json | 1 + .../components/powerwall/translations/no.json | 1 + .../components/powerwall/translations/pl.json | 1 + .../components/powerwall/translations/ru.json | 1 + .../components/powerwall/translations/tr.json | 19 +++ .../components/powerwall/translations/uk.json | 20 ++++ .../powerwall/translations/zh-Hant.json | 1 + .../components/profiler/translations/de.json | 12 ++ .../components/profiler/translations/tr.json | 7 ++ .../components/profiler/translations/uk.json | 12 ++ .../progettihwsw/translations/de.json | 5 +- .../progettihwsw/translations/tr.json | 41 +++++++ .../progettihwsw/translations/uk.json | 41 +++++++ .../components/ps4/translations/de.json | 7 +- .../components/ps4/translations/tr.json | 22 ++++ .../components/ps4/translations/uk.json | 41 +++++++ .../pvpc_hourly_pricing/translations/de.json | 2 +- .../pvpc_hourly_pricing/translations/tr.json | 14 +++ .../pvpc_hourly_pricing/translations/uk.json | 17 +++ .../components/rachio/translations/de.json | 6 +- .../components/rachio/translations/tr.json | 19 +++ .../components/rachio/translations/uk.json | 30 +++++ .../rainmachine/translations/de.json | 5 +- .../rainmachine/translations/tr.json | 17 +++ .../rainmachine/translations/uk.json | 30 +++++ .../recollect_waste/translations/de.json | 22 ++++ .../recollect_waste/translations/es.json | 3 +- .../recollect_waste/translations/lb.json | 18 +++ .../recollect_waste/translations/pl.json | 10 ++ .../recollect_waste/translations/tr.json | 7 ++ .../recollect_waste/translations/uk.json | 28 +++++ .../components/remote/translations/tr.json | 10 ++ .../components/remote/translations/uk.json | 9 ++ .../components/rfxtrx/translations/ca.json | 3 +- .../components/rfxtrx/translations/de.json | 19 ++- .../components/rfxtrx/translations/en.json | 4 +- .../components/rfxtrx/translations/es.json | 3 +- .../components/rfxtrx/translations/et.json | 3 +- .../components/rfxtrx/translations/it.json | 3 +- .../components/rfxtrx/translations/no.json | 3 +- .../components/rfxtrx/translations/pl.json | 3 +- .../components/rfxtrx/translations/ru.json | 3 +- .../components/rfxtrx/translations/tr.json | 32 +++++ .../components/rfxtrx/translations/uk.json | 74 ++++++++++++ .../rfxtrx/translations/zh-Hant.json | 3 +- .../components/ring/translations/tr.json | 19 +++ .../components/ring/translations/uk.json | 26 +++++ .../components/risco/translations/de.json | 14 ++- .../components/risco/translations/lb.json | 4 +- .../components/risco/translations/tr.json | 27 +++++ .../components/risco/translations/uk.json | 55 +++++++++ .../components/roku/translations/ca.json | 4 + .../components/roku/translations/cs.json | 4 + .../components/roku/translations/de.json | 4 + .../components/roku/translations/en.json | 4 + .../components/roku/translations/es.json | 4 + .../components/roku/translations/et.json | 4 + .../components/roku/translations/it.json | 4 + .../components/roku/translations/lb.json | 4 + .../components/roku/translations/no.json | 4 + .../components/roku/translations/pl.json | 10 ++ .../components/roku/translations/ru.json | 4 + .../components/roku/translations/tr.json | 25 ++++ .../components/roku/translations/uk.json | 28 +++++ .../components/roku/translations/zh-Hant.json | 4 + .../components/roomba/translations/ca.json | 32 +++++ .../components/roomba/translations/cs.json | 20 ++++ .../components/roomba/translations/de.json | 33 +++++- .../components/roomba/translations/en.json | 103 +++++++++-------- .../components/roomba/translations/es.json | 32 +++++ .../components/roomba/translations/et.json | 32 +++++ .../components/roomba/translations/fr.json | 29 +++++ .../components/roomba/translations/it.json | 32 +++++ .../components/roomba/translations/lb.json | 26 +++++ .../components/roomba/translations/no.json | 32 +++++ .../components/roomba/translations/pl.json | 32 +++++ .../components/roomba/translations/pt.json | 7 ++ .../components/roomba/translations/ru.json | 32 +++++ .../components/roomba/translations/tr.json | 60 ++++++++++ .../components/roomba/translations/uk.json | 30 +++++ .../roomba/translations/zh-Hant.json | 32 +++++ .../components/roon/translations/ca.json | 2 +- .../components/roon/translations/cs.json | 3 +- .../components/roon/translations/de.json | 11 ++ .../components/roon/translations/en.json | 2 +- .../components/roon/translations/et.json | 2 +- .../components/roon/translations/it.json | 2 +- .../components/roon/translations/no.json | 2 +- .../components/roon/translations/pl.json | 2 +- .../components/roon/translations/ru.json | 2 +- .../components/roon/translations/tr.json | 23 ++++ .../components/roon/translations/uk.json | 24 ++++ .../components/roon/translations/zh-Hant.json | 2 +- .../components/rpi_power/translations/de.json | 13 +++ .../components/rpi_power/translations/lb.json | 5 + .../components/rpi_power/translations/tr.json | 13 +++ .../components/rpi_power/translations/uk.json | 14 +++ .../ruckus_unleashed/translations/de.json | 4 +- .../ruckus_unleashed/translations/tr.json | 21 ++++ .../ruckus_unleashed/translations/uk.json | 21 ++++ .../components/samsungtv/translations/de.json | 4 +- .../components/samsungtv/translations/tr.json | 6 +- .../components/samsungtv/translations/uk.json | 25 ++++ .../components/script/translations/uk.json | 2 +- .../season/translations/sensor.uk.json | 6 + .../components/sense/translations/de.json | 2 +- .../components/sense/translations/tr.json | 20 ++++ .../components/sense/translations/uk.json | 21 ++++ .../components/sensor/translations/tr.json | 27 +++++ .../components/sensor/translations/uk.json | 31 ++++- .../components/sentry/translations/de.json | 3 + .../components/sentry/translations/tr.json | 27 +++++ .../components/sentry/translations/uk.json | 36 ++++++ .../components/sharkiq/translations/de.json | 7 +- .../components/sharkiq/translations/fr.json | 2 +- .../components/sharkiq/translations/tr.json | 29 +++++ .../components/sharkiq/translations/uk.json | 29 +++++ .../components/shelly/translations/ca.json | 16 +++ .../components/shelly/translations/cs.json | 16 +++ .../components/shelly/translations/da.json | 17 +++ .../components/shelly/translations/de.json | 9 +- .../components/shelly/translations/en.json | 18 +-- .../components/shelly/translations/es.json | 16 +++ .../components/shelly/translations/et.json | 16 +++ .../components/shelly/translations/it.json | 16 +++ .../components/shelly/translations/lb.json | 8 ++ .../components/shelly/translations/no.json | 16 +++ .../components/shelly/translations/pl.json | 16 +++ .../components/shelly/translations/ru.json | 16 +++ .../components/shelly/translations/tr.json | 41 +++++++ .../components/shelly/translations/uk.json | 47 ++++++++ .../shelly/translations/zh-Hant.json | 16 +++ .../shopping_list/translations/de.json | 2 +- .../shopping_list/translations/tr.json | 14 +++ .../shopping_list/translations/uk.json | 14 +++ .../simplisafe/translations/de.json | 8 +- .../simplisafe/translations/tr.json | 20 ++++ .../simplisafe/translations/uk.json | 29 ++++- .../components/smappee/translations/de.json | 14 ++- .../components/smappee/translations/tr.json | 26 +++++ .../components/smappee/translations/uk.json | 35 ++++++ .../smart_meter_texas/translations/de.json | 6 +- .../smart_meter_texas/translations/tr.json | 20 ++++ .../smart_meter_texas/translations/uk.json | 20 ++++ .../components/smarthab/translations/de.json | 1 + .../components/smarthab/translations/tr.json | 17 +++ .../components/smarthab/translations/uk.json | 19 +++ .../smartthings/translations/tr.json | 17 +++ .../smartthings/translations/uk.json | 38 ++++++ .../components/smhi/translations/uk.json | 18 +++ .../components/sms/translations/de.json | 6 +- .../components/sms/translations/tr.json | 17 +++ .../components/sms/translations/uk.json | 20 ++++ .../components/solaredge/translations/fr.json | 4 +- .../components/solaredge/translations/lb.json | 5 +- .../components/solaredge/translations/tr.json | 13 +++ .../components/solaredge/translations/uk.json | 25 ++++ .../components/solarlog/translations/de.json | 2 +- .../components/solarlog/translations/tr.json | 18 +++ .../components/solarlog/translations/uk.json | 20 ++++ .../components/soma/translations/tr.json | 12 ++ .../components/soma/translations/uk.json | 24 ++++ .../components/somfy/translations/de.json | 6 +- .../components/somfy/translations/tr.json | 7 ++ .../components/somfy/translations/uk.json | 18 +++ .../somfy_mylink/translations/ca.json | 53 +++++++++ .../somfy_mylink/translations/cs.json | 26 +++++ .../somfy_mylink/translations/de.json | 39 +++++++ .../somfy_mylink/translations/en.json | 87 +++++++------- .../somfy_mylink/translations/es.json | 53 +++++++++ .../somfy_mylink/translations/et.json | 53 +++++++++ .../somfy_mylink/translations/fr.json | 43 +++++++ .../somfy_mylink/translations/it.json | 53 +++++++++ .../somfy_mylink/translations/lb.json | 27 +++++ .../somfy_mylink/translations/no.json | 53 +++++++++ .../somfy_mylink/translations/pl.json | 53 +++++++++ .../somfy_mylink/translations/ru.json | 53 +++++++++ .../somfy_mylink/translations/tr.json | 53 +++++++++ .../somfy_mylink/translations/uk.json | 40 +++++++ .../somfy_mylink/translations/zh-Hant.json | 53 +++++++++ .../components/sonarr/translations/de.json | 12 +- .../components/sonarr/translations/tr.json | 22 ++++ .../components/sonarr/translations/uk.json | 40 +++++++ .../components/songpal/translations/tr.json | 17 +++ .../components/songpal/translations/uk.json | 22 ++++ .../components/sonos/translations/de.json | 2 +- .../components/sonos/translations/tr.json | 12 ++ .../components/sonos/translations/uk.json | 13 +++ .../speedtestdotnet/translations/de.json | 9 +- .../speedtestdotnet/translations/tr.json | 24 ++++ .../speedtestdotnet/translations/uk.json | 24 ++++ .../components/spider/translations/de.json | 7 ++ .../components/spider/translations/tr.json | 19 +++ .../components/spider/translations/uk.json | 20 ++++ .../components/spotify/translations/de.json | 8 +- .../components/spotify/translations/lb.json | 5 + .../components/spotify/translations/uk.json | 27 +++++ .../squeezebox/translations/de.json | 8 +- .../squeezebox/translations/tr.json | 27 +++++ .../squeezebox/translations/uk.json | 31 +++++ .../srp_energy/translations/de.json | 13 ++- .../srp_energy/translations/es.json | 6 +- .../srp_energy/translations/fr.json | 14 +++ .../srp_energy/translations/lb.json | 20 ++++ .../srp_energy/translations/tr.json | 8 +- .../srp_energy/translations/uk.json | 24 ++++ .../components/starline/translations/tr.json | 33 ++++++ .../components/starline/translations/uk.json | 41 +++++++ .../components/sun/translations/pl.json | 2 +- .../components/switch/translations/uk.json | 9 ++ .../components/syncthru/translations/tr.json | 20 ++++ .../components/syncthru/translations/uk.json | 27 +++++ .../synology_dsm/translations/de.json | 11 +- .../synology_dsm/translations/tr.json | 18 ++- .../synology_dsm/translations/uk.json | 55 +++++++++ .../system_health/translations/uk.json | 2 +- .../components/tado/translations/de.json | 8 +- .../components/tado/translations/tr.json | 29 +++++ .../components/tado/translations/uk.json | 33 ++++++ .../components/tag/translations/uk.json | 3 + .../components/tasmota/translations/de.json | 16 +++ .../components/tasmota/translations/tr.json | 16 +++ .../components/tasmota/translations/uk.json | 22 ++++ .../tellduslive/translations/de.json | 5 +- .../tellduslive/translations/fr.json | 3 +- .../tellduslive/translations/lb.json | 3 +- .../tellduslive/translations/tr.json | 19 +++ .../tellduslive/translations/uk.json | 27 +++++ .../components/tesla/translations/de.json | 5 +- .../components/tesla/translations/fr.json | 2 +- .../components/tesla/translations/tr.json | 18 +++ .../components/tesla/translations/uk.json | 29 +++++ .../components/tibber/translations/de.json | 4 +- .../components/tibber/translations/tr.json | 18 +++ .../components/tibber/translations/uk.json | 21 ++++ .../components/tile/translations/de.json | 3 + .../components/tile/translations/tr.json | 28 +++++ .../components/tile/translations/uk.json | 29 +++++ .../components/timer/translations/uk.json | 6 +- .../components/toon/translations/de.json | 3 + .../components/toon/translations/fr.json | 3 +- .../components/toon/translations/lb.json | 3 +- .../components/toon/translations/tr.json | 20 ++++ .../components/toon/translations/uk.json | 25 ++++ .../totalconnect/translations/de.json | 5 +- .../totalconnect/translations/tr.json | 18 +++ .../totalconnect/translations/uk.json | 19 +++ .../components/tplink/translations/de.json | 2 +- .../components/tplink/translations/tr.json | 12 ++ .../components/tplink/translations/uk.json | 13 +++ .../components/traccar/translations/de.json | 6 +- .../components/traccar/translations/lb.json | 3 +- .../components/traccar/translations/tr.json | 4 + .../components/traccar/translations/uk.json | 17 +++ .../components/tradfri/translations/de.json | 4 +- .../components/tradfri/translations/tr.json | 18 +++ .../components/tradfri/translations/uk.json | 12 +- .../transmission/translations/de.json | 5 +- .../transmission/translations/tr.json | 21 ++++ .../transmission/translations/uk.json | 36 ++++++ .../components/tuya/translations/de.json | 8 +- .../components/tuya/translations/lb.json | 3 + .../components/tuya/translations/tr.json | 28 +++++ .../components/tuya/translations/uk.json | 63 ++++++++++ .../twentemilieu/translations/de.json | 5 +- .../twentemilieu/translations/tr.json | 10 ++ .../twentemilieu/translations/uk.json | 22 ++++ .../components/twilio/translations/de.json | 8 +- .../components/twilio/translations/lb.json | 3 +- .../components/twilio/translations/tr.json | 8 ++ .../components/twilio/translations/uk.json | 17 +++ .../components/twinkly/translations/de.json | 5 +- .../components/twinkly/translations/fr.json | 18 +++ .../components/twinkly/translations/lb.json | 7 ++ .../components/twinkly/translations/tr.json | 6 + .../components/twinkly/translations/uk.json | 19 +++ .../components/unifi/translations/ca.json | 5 +- .../components/unifi/translations/cs.json | 4 +- .../components/unifi/translations/da.json | 1 + .../components/unifi/translations/de.json | 8 +- .../components/unifi/translations/en.json | 1 + .../components/unifi/translations/es.json | 4 +- .../components/unifi/translations/et.json | 5 +- .../components/unifi/translations/it.json | 5 +- .../components/unifi/translations/no.json | 5 +- .../components/unifi/translations/pl.json | 5 +- .../components/unifi/translations/ru.json | 4 +- .../components/unifi/translations/tr.json | 13 +++ .../components/unifi/translations/uk.json | 66 +++++++++++ .../unifi/translations/zh-Hant.json | 5 +- .../components/upb/translations/de.json | 11 +- .../components/upb/translations/tr.json | 11 ++ .../components/upb/translations/uk.json | 23 ++++ .../components/upcloud/translations/de.json | 3 +- .../components/upcloud/translations/tr.json | 16 +++ .../components/upcloud/translations/uk.json | 25 ++++ .../components/upnp/translations/ro.json | 7 ++ .../components/upnp/translations/tr.json | 19 +++ .../components/upnp/translations/uk.json | 16 ++- .../components/vacuum/translations/de.json | 2 +- .../components/vacuum/translations/uk.json | 16 ++- .../components/velbus/translations/de.json | 6 +- .../components/velbus/translations/tr.json | 11 ++ .../components/velbus/translations/uk.json | 20 ++++ .../components/vera/translations/tr.json | 14 +++ .../components/vera/translations/uk.json | 30 +++++ .../components/vesync/translations/de.json | 6 + .../components/vesync/translations/tr.json | 19 +++ .../components/vesync/translations/uk.json | 19 +++ .../components/vilfo/translations/de.json | 6 +- .../components/vilfo/translations/tr.json | 20 ++++ .../components/vilfo/translations/uk.json | 22 ++++ .../components/vizio/translations/de.json | 13 ++- .../components/vizio/translations/tr.json | 19 +++ .../components/vizio/translations/uk.json | 54 +++++++++ .../components/volumio/translations/de.json | 14 ++- .../components/volumio/translations/tr.json | 20 ++++ .../components/volumio/translations/uk.json | 6 +- .../water_heater/translations/uk.json | 8 ++ .../components/wemo/translations/de.json | 2 +- .../components/wemo/translations/tr.json | 8 +- .../components/wemo/translations/uk.json | 13 +++ .../components/wiffi/translations/tr.json | 24 ++++ .../components/wiffi/translations/uk.json | 25 ++++ .../components/wilight/translations/de.json | 3 + .../components/wilight/translations/tr.json | 7 ++ .../components/wilight/translations/uk.json | 16 +++ .../components/withings/translations/de.json | 16 ++- .../components/withings/translations/fr.json | 2 +- .../components/withings/translations/tr.json | 18 +++ .../components/withings/translations/uk.json | 33 ++++++ .../components/wled/translations/de.json | 6 +- .../components/wled/translations/tr.json | 23 ++++ .../components/wled/translations/uk.json | 24 ++++ .../components/wolflink/translations/de.json | 8 ++ .../wolflink/translations/sensor.de.json | 1 + .../wolflink/translations/sensor.tr.json | 11 +- .../wolflink/translations/sensor.uk.json | 78 ++++++++++++- .../components/wolflink/translations/tr.json | 20 ++++ .../components/wolflink/translations/uk.json | 13 ++- .../components/xbox/translations/de.json | 5 + .../components/xbox/translations/lb.json | 4 + .../components/xbox/translations/tr.json | 7 ++ .../components/xbox/translations/uk.json | 17 +++ .../xiaomi_aqara/translations/de.json | 15 ++- .../xiaomi_aqara/translations/tr.json | 31 +++++ .../xiaomi_aqara/translations/uk.json | 43 +++++++ .../xiaomi_miio/translations/de.json | 15 +-- .../xiaomi_miio/translations/tr.json | 29 +++++ .../xiaomi_miio/translations/uk.json | 31 +++++ .../components/yeelight/translations/de.json | 10 +- .../components/yeelight/translations/tr.json | 34 ++++++ .../components/yeelight/translations/uk.json | 38 ++++++ .../components/zerproc/translations/tr.json | 8 +- .../components/zerproc/translations/uk.json | 13 +++ .../components/zha/translations/cs.json | 20 ++-- .../components/zha/translations/de.json | 4 +- .../components/zha/translations/pl.json | 32 ++--- .../components/zha/translations/tr.json | 26 +++++ .../components/zha/translations/uk.json | 91 +++++++++++++++ .../zodiac/translations/sensor.tr.json | 18 +++ .../zodiac/translations/sensor.uk.json | 18 +++ .../components/zone/translations/tr.json | 12 ++ .../zoneminder/translations/de.json | 9 ++ .../zoneminder/translations/tr.json | 22 ++++ .../zoneminder/translations/uk.json | 34 ++++++ .../components/zwave/translations/de.json | 3 +- .../components/zwave/translations/tr.json | 4 + .../components/zwave/translations/uk.json | 27 ++++- .../components/zwave_js/translations/ca.json | 56 +++++++++ .../components/zwave_js/translations/cs.json | 30 +++++ .../components/zwave_js/translations/de.json | 18 +++ .../components/zwave_js/translations/en.json | 5 + .../components/zwave_js/translations/es.json | 36 ++++++ .../components/zwave_js/translations/et.json | 56 +++++++++ .../components/zwave_js/translations/fr.json | 20 ++++ .../components/zwave_js/translations/it.json | 56 +++++++++ .../components/zwave_js/translations/lb.json | 20 ++++ .../components/zwave_js/translations/no.json | 56 +++++++++ .../components/zwave_js/translations/pl.json | 56 +++++++++ .../zwave_js/translations/pt-BR.json | 7 ++ .../components/zwave_js/translations/ru.json | 56 +++++++++ .../components/zwave_js/translations/tr.json | 56 +++++++++ .../components/zwave_js/translations/uk.json | 20 ++++ .../zwave_js/translations/zh-Hant.json | 56 +++++++++ 1149 files changed, 19515 insertions(+), 871 deletions(-) create mode 100644 homeassistant/components/abode/translations/tr.json create mode 100644 homeassistant/components/abode/translations/uk.json create mode 100644 homeassistant/components/accuweather/translations/sensor.uk.json create mode 100644 homeassistant/components/accuweather/translations/tr.json create mode 100644 homeassistant/components/acmeda/translations/tr.json create mode 100644 homeassistant/components/acmeda/translations/uk.json create mode 100644 homeassistant/components/adguard/translations/tr.json create mode 100644 homeassistant/components/adguard/translations/uk.json create mode 100644 homeassistant/components/advantage_air/translations/tr.json create mode 100644 homeassistant/components/advantage_air/translations/uk.json create mode 100644 homeassistant/components/agent_dvr/translations/tr.json create mode 100644 homeassistant/components/agent_dvr/translations/uk.json create mode 100644 homeassistant/components/airly/translations/uk.json create mode 100644 homeassistant/components/airnow/translations/ca.json create mode 100644 homeassistant/components/airnow/translations/cs.json create mode 100644 homeassistant/components/airnow/translations/de.json create mode 100644 homeassistant/components/airnow/translations/es.json create mode 100644 homeassistant/components/airnow/translations/et.json create mode 100644 homeassistant/components/airnow/translations/fr.json create mode 100644 homeassistant/components/airnow/translations/it.json create mode 100644 homeassistant/components/airnow/translations/lb.json create mode 100644 homeassistant/components/airnow/translations/no.json create mode 100644 homeassistant/components/airnow/translations/pl.json create mode 100644 homeassistant/components/airnow/translations/pt.json create mode 100644 homeassistant/components/airnow/translations/ru.json create mode 100644 homeassistant/components/airnow/translations/tr.json create mode 100644 homeassistant/components/airnow/translations/uk.json create mode 100644 homeassistant/components/airnow/translations/zh-Hant.json create mode 100644 homeassistant/components/airvisual/translations/ar.json create mode 100644 homeassistant/components/airvisual/translations/tr.json create mode 100644 homeassistant/components/airvisual/translations/uk.json create mode 100644 homeassistant/components/alarmdecoder/translations/tr.json create mode 100644 homeassistant/components/alarmdecoder/translations/uk.json create mode 100644 homeassistant/components/almond/translations/tr.json create mode 100644 homeassistant/components/almond/translations/uk.json create mode 100644 homeassistant/components/ambiclimate/translations/tr.json create mode 100644 homeassistant/components/ambiclimate/translations/uk.json create mode 100644 homeassistant/components/ambient_station/translations/tr.json create mode 100644 homeassistant/components/ambient_station/translations/uk.json create mode 100644 homeassistant/components/apple_tv/translations/uk.json create mode 100644 homeassistant/components/arcam_fmj/translations/ro.json create mode 100644 homeassistant/components/arcam_fmj/translations/tr.json create mode 100644 homeassistant/components/arcam_fmj/translations/uk.json create mode 100644 homeassistant/components/atag/translations/tr.json create mode 100644 homeassistant/components/atag/translations/uk.json create mode 100644 homeassistant/components/august/translations/tr.json create mode 100644 homeassistant/components/august/translations/uk.json create mode 100644 homeassistant/components/aurora/translations/fr.json create mode 100644 homeassistant/components/aurora/translations/tr.json create mode 100644 homeassistant/components/aurora/translations/uk.json create mode 100644 homeassistant/components/auth/translations/tr.json create mode 100644 homeassistant/components/awair/translations/tr.json create mode 100644 homeassistant/components/awair/translations/uk.json create mode 100644 homeassistant/components/axis/translations/tr.json create mode 100644 homeassistant/components/axis/translations/uk.json create mode 100644 homeassistant/components/azure_devops/translations/tr.json create mode 100644 homeassistant/components/blebox/translations/tr.json create mode 100644 homeassistant/components/blebox/translations/uk.json create mode 100644 homeassistant/components/blink/translations/tr.json create mode 100644 homeassistant/components/blink/translations/uk.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/ca.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/cs.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/de.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/es.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/et.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/fr.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/it.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/lb.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/no.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/pl.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/pt.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/ru.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/tr.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/uk.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/zh-Hant.json create mode 100644 homeassistant/components/bond/translations/tr.json create mode 100644 homeassistant/components/braviatv/translations/tr.json create mode 100644 homeassistant/components/braviatv/translations/uk.json create mode 100644 homeassistant/components/broadlink/translations/tr.json create mode 100644 homeassistant/components/broadlink/translations/uk.json create mode 100644 homeassistant/components/brother/translations/uk.json create mode 100644 homeassistant/components/bsblan/translations/uk.json create mode 100644 homeassistant/components/canary/translations/tr.json create mode 100644 homeassistant/components/canary/translations/uk.json create mode 100644 homeassistant/components/cast/translations/tr.json create mode 100644 homeassistant/components/cert_expiry/translations/tr.json create mode 100644 homeassistant/components/cert_expiry/translations/uk.json create mode 100644 homeassistant/components/cloud/translations/de.json create mode 100644 homeassistant/components/cloud/translations/fr.json create mode 100644 homeassistant/components/cloud/translations/uk.json create mode 100644 homeassistant/components/cloudflare/translations/uk.json create mode 100644 homeassistant/components/control4/translations/tr.json create mode 100644 homeassistant/components/coolmaster/translations/tr.json create mode 100644 homeassistant/components/coolmaster/translations/uk.json create mode 100644 homeassistant/components/coronavirus/translations/tr.json create mode 100644 homeassistant/components/coronavirus/translations/uk.json create mode 100644 homeassistant/components/daikin/translations/tr.json create mode 100644 homeassistant/components/daikin/translations/uk.json create mode 100644 homeassistant/components/deconz/translations/uk.json create mode 100644 homeassistant/components/demo/translations/tr.json create mode 100644 homeassistant/components/demo/translations/uk.json create mode 100644 homeassistant/components/denonavr/translations/tr.json create mode 100644 homeassistant/components/denonavr/translations/uk.json create mode 100644 homeassistant/components/devolo_home_control/translations/tr.json create mode 100644 homeassistant/components/devolo_home_control/translations/uk.json create mode 100644 homeassistant/components/dexcom/translations/uk.json create mode 100644 homeassistant/components/dialogflow/translations/tr.json create mode 100644 homeassistant/components/dialogflow/translations/uk.json create mode 100644 homeassistant/components/directv/translations/tr.json create mode 100644 homeassistant/components/directv/translations/uk.json create mode 100644 homeassistant/components/doorbird/translations/tr.json create mode 100644 homeassistant/components/doorbird/translations/uk.json create mode 100644 homeassistant/components/dsmr/translations/de.json create mode 100644 homeassistant/components/dsmr/translations/uk.json create mode 100644 homeassistant/components/dunehd/translations/tr.json create mode 100644 homeassistant/components/dunehd/translations/uk.json create mode 100644 homeassistant/components/eafm/translations/de.json create mode 100644 homeassistant/components/eafm/translations/tr.json create mode 100644 homeassistant/components/eafm/translations/uk.json create mode 100644 homeassistant/components/ecobee/translations/tr.json create mode 100644 homeassistant/components/ecobee/translations/uk.json create mode 100644 homeassistant/components/econet/translations/ca.json create mode 100644 homeassistant/components/econet/translations/es.json create mode 100644 homeassistant/components/econet/translations/et.json create mode 100644 homeassistant/components/econet/translations/it.json create mode 100644 homeassistant/components/econet/translations/no.json create mode 100644 homeassistant/components/econet/translations/pl.json create mode 100644 homeassistant/components/econet/translations/ru.json create mode 100644 homeassistant/components/econet/translations/tr.json create mode 100644 homeassistant/components/econet/translations/zh-Hant.json create mode 100644 homeassistant/components/elgato/translations/tr.json create mode 100644 homeassistant/components/elgato/translations/uk.json create mode 100644 homeassistant/components/elkm1/translations/tr.json create mode 100644 homeassistant/components/elkm1/translations/uk.json create mode 100644 homeassistant/components/emulated_roku/translations/tr.json create mode 100644 homeassistant/components/emulated_roku/translations/uk.json create mode 100644 homeassistant/components/enocean/translations/tr.json create mode 100644 homeassistant/components/enocean/translations/uk.json create mode 100644 homeassistant/components/epson/translations/uk.json create mode 100644 homeassistant/components/fireservicerota/translations/fr.json create mode 100644 homeassistant/components/fireservicerota/translations/uk.json create mode 100644 homeassistant/components/firmata/translations/tr.json create mode 100644 homeassistant/components/firmata/translations/uk.json create mode 100644 homeassistant/components/flick_electric/translations/tr.json create mode 100644 homeassistant/components/flick_electric/translations/uk.json create mode 100644 homeassistant/components/flo/translations/tr.json create mode 100644 homeassistant/components/flo/translations/uk.json create mode 100644 homeassistant/components/flume/translations/tr.json create mode 100644 homeassistant/components/flume/translations/uk.json create mode 100644 homeassistant/components/flunearyou/translations/tr.json create mode 100644 homeassistant/components/flunearyou/translations/uk.json create mode 100644 homeassistant/components/forked_daapd/translations/tr.json create mode 100644 homeassistant/components/forked_daapd/translations/uk.json create mode 100644 homeassistant/components/foscam/translations/af.json create mode 100644 homeassistant/components/foscam/translations/ca.json create mode 100644 homeassistant/components/foscam/translations/cs.json create mode 100644 homeassistant/components/foscam/translations/de.json create mode 100644 homeassistant/components/foscam/translations/es.json create mode 100644 homeassistant/components/foscam/translations/et.json create mode 100644 homeassistant/components/foscam/translations/fr.json create mode 100644 homeassistant/components/foscam/translations/it.json create mode 100644 homeassistant/components/foscam/translations/lb.json create mode 100644 homeassistant/components/foscam/translations/no.json create mode 100644 homeassistant/components/foscam/translations/pl.json create mode 100644 homeassistant/components/foscam/translations/pt.json create mode 100644 homeassistant/components/foscam/translations/ru.json create mode 100644 homeassistant/components/foscam/translations/tr.json create mode 100644 homeassistant/components/foscam/translations/zh-Hant.json create mode 100644 homeassistant/components/freebox/translations/tr.json create mode 100644 homeassistant/components/freebox/translations/uk.json create mode 100644 homeassistant/components/fritzbox/translations/tr.json create mode 100644 homeassistant/components/fritzbox/translations/uk.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/ca.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/cs.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/et.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/it.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/lb.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/no.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/pl.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/ru.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/tr.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json create mode 100644 homeassistant/components/garmin_connect/translations/tr.json create mode 100644 homeassistant/components/garmin_connect/translations/uk.json create mode 100644 homeassistant/components/gdacs/translations/tr.json create mode 100644 homeassistant/components/gdacs/translations/uk.json create mode 100644 homeassistant/components/geofency/translations/tr.json create mode 100644 homeassistant/components/geofency/translations/uk.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/tr.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/uk.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/tr.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/uk.json create mode 100644 homeassistant/components/gios/translations/tr.json create mode 100644 homeassistant/components/gios/translations/uk.json create mode 100644 homeassistant/components/glances/translations/tr.json create mode 100644 homeassistant/components/glances/translations/uk.json create mode 100644 homeassistant/components/goalzero/translations/tr.json create mode 100644 homeassistant/components/goalzero/translations/uk.json create mode 100644 homeassistant/components/gogogate2/translations/tr.json create mode 100644 homeassistant/components/gogogate2/translations/uk.json create mode 100644 homeassistant/components/gpslogger/translations/tr.json create mode 100644 homeassistant/components/gpslogger/translations/uk.json create mode 100644 homeassistant/components/gree/translations/de.json create mode 100644 homeassistant/components/gree/translations/tr.json create mode 100644 homeassistant/components/gree/translations/uk.json create mode 100644 homeassistant/components/griddy/translations/uk.json create mode 100644 homeassistant/components/guardian/translations/tr.json create mode 100644 homeassistant/components/guardian/translations/uk.json create mode 100644 homeassistant/components/hangouts/translations/tr.json create mode 100644 homeassistant/components/hangouts/translations/uk.json create mode 100644 homeassistant/components/harmony/translations/tr.json create mode 100644 homeassistant/components/harmony/translations/uk.json create mode 100644 homeassistant/components/heos/translations/tr.json create mode 100644 homeassistant/components/heos/translations/uk.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/tr.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/uk.json create mode 100644 homeassistant/components/hlk_sw16/translations/tr.json create mode 100644 homeassistant/components/hlk_sw16/translations/uk.json create mode 100644 homeassistant/components/home_connect/translations/uk.json create mode 100644 homeassistant/components/homeassistant/translations/fr.json create mode 100644 homeassistant/components/homeassistant/translations/uk.json create mode 100644 homeassistant/components/homekit/translations/ro.json create mode 100644 homeassistant/components/homekit/translations/tr.json create mode 100644 homeassistant/components/homekit_controller/translations/tr.json create mode 100644 homeassistant/components/homekit_controller/translations/uk.json create mode 100644 homeassistant/components/homematicip_cloud/translations/tr.json create mode 100644 homeassistant/components/homematicip_cloud/translations/uk.json create mode 100644 homeassistant/components/huawei_lte/translations/uk.json create mode 100644 homeassistant/components/hue/translations/tr.json create mode 100644 homeassistant/components/hue/translations/uk.json create mode 100644 homeassistant/components/huisbaasje/translations/ca.json create mode 100644 homeassistant/components/huisbaasje/translations/cs.json create mode 100644 homeassistant/components/huisbaasje/translations/en.json create mode 100644 homeassistant/components/huisbaasje/translations/et.json create mode 100644 homeassistant/components/huisbaasje/translations/it.json create mode 100644 homeassistant/components/huisbaasje/translations/no.json create mode 100644 homeassistant/components/huisbaasje/translations/pl.json create mode 100644 homeassistant/components/huisbaasje/translations/ru.json create mode 100644 homeassistant/components/huisbaasje/translations/tr.json create mode 100644 homeassistant/components/huisbaasje/translations/zh-Hant.json create mode 100644 homeassistant/components/humidifier/translations/tr.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/tr.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/uk.json create mode 100644 homeassistant/components/hvv_departures/translations/tr.json create mode 100644 homeassistant/components/hvv_departures/translations/uk.json create mode 100644 homeassistant/components/hyperion/translations/uk.json create mode 100644 homeassistant/components/iaqualink/translations/tr.json create mode 100644 homeassistant/components/iaqualink/translations/uk.json create mode 100644 homeassistant/components/icloud/translations/uk.json create mode 100644 homeassistant/components/ifttt/translations/tr.json create mode 100644 homeassistant/components/ifttt/translations/uk.json create mode 100644 homeassistant/components/insteon/translations/tr.json create mode 100644 homeassistant/components/insteon/translations/uk.json create mode 100644 homeassistant/components/ios/translations/tr.json create mode 100644 homeassistant/components/ios/translations/uk.json create mode 100644 homeassistant/components/ipp/translations/uk.json create mode 100644 homeassistant/components/iqvia/translations/tr.json create mode 100644 homeassistant/components/iqvia/translations/uk.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/tr.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/uk.json create mode 100644 homeassistant/components/isy994/translations/tr.json create mode 100644 homeassistant/components/isy994/translations/uk.json create mode 100644 homeassistant/components/izone/translations/tr.json create mode 100644 homeassistant/components/izone/translations/uk.json create mode 100644 homeassistant/components/juicenet/translations/tr.json create mode 100644 homeassistant/components/juicenet/translations/uk.json create mode 100644 homeassistant/components/kodi/translations/tr.json create mode 100644 homeassistant/components/kodi/translations/uk.json create mode 100644 homeassistant/components/konnected/translations/tr.json create mode 100644 homeassistant/components/konnected/translations/uk.json create mode 100644 homeassistant/components/kulersky/translations/lb.json create mode 100644 homeassistant/components/kulersky/translations/uk.json create mode 100644 homeassistant/components/life360/translations/uk.json create mode 100644 homeassistant/components/lifx/translations/tr.json create mode 100644 homeassistant/components/lifx/translations/uk.json create mode 100644 homeassistant/components/local_ip/translations/tr.json create mode 100644 homeassistant/components/local_ip/translations/uk.json create mode 100644 homeassistant/components/locative/translations/tr.json create mode 100644 homeassistant/components/locative/translations/uk.json create mode 100644 homeassistant/components/logi_circle/translations/tr.json create mode 100644 homeassistant/components/logi_circle/translations/uk.json create mode 100644 homeassistant/components/lovelace/translations/fr.json create mode 100644 homeassistant/components/lovelace/translations/uk.json create mode 100644 homeassistant/components/luftdaten/translations/tr.json create mode 100644 homeassistant/components/luftdaten/translations/uk.json create mode 100644 homeassistant/components/lutron_caseta/translations/tr.json create mode 100644 homeassistant/components/lutron_caseta/translations/uk.json create mode 100644 homeassistant/components/lyric/translations/ca.json create mode 100644 homeassistant/components/lyric/translations/cs.json create mode 100644 homeassistant/components/lyric/translations/en.json create mode 100644 homeassistant/components/lyric/translations/et.json create mode 100644 homeassistant/components/lyric/translations/it.json create mode 100644 homeassistant/components/lyric/translations/no.json create mode 100644 homeassistant/components/lyric/translations/pl.json create mode 100644 homeassistant/components/lyric/translations/tr.json create mode 100644 homeassistant/components/lyric/translations/zh-Hant.json create mode 100644 homeassistant/components/mailgun/translations/tr.json create mode 100644 homeassistant/components/mailgun/translations/uk.json create mode 100644 homeassistant/components/melcloud/translations/tr.json create mode 100644 homeassistant/components/melcloud/translations/uk.json create mode 100644 homeassistant/components/met/translations/tr.json create mode 100644 homeassistant/components/met/translations/uk.json create mode 100644 homeassistant/components/meteo_france/translations/uk.json create mode 100644 homeassistant/components/metoffice/translations/tr.json create mode 100644 homeassistant/components/metoffice/translations/uk.json create mode 100644 homeassistant/components/mikrotik/translations/tr.json create mode 100644 homeassistant/components/mikrotik/translations/uk.json create mode 100644 homeassistant/components/mill/translations/tr.json create mode 100644 homeassistant/components/mill/translations/uk.json create mode 100644 homeassistant/components/minecraft_server/translations/uk.json create mode 100644 homeassistant/components/mobile_app/translations/tr.json create mode 100644 homeassistant/components/monoprice/translations/tr.json create mode 100644 homeassistant/components/monoprice/translations/uk.json create mode 100644 homeassistant/components/motion_blinds/translations/fr.json create mode 100644 homeassistant/components/motion_blinds/translations/uk.json create mode 100644 homeassistant/components/myq/translations/tr.json create mode 100644 homeassistant/components/myq/translations/uk.json create mode 100644 homeassistant/components/neato/translations/tr.json create mode 100644 homeassistant/components/neato/translations/uk.json create mode 100644 homeassistant/components/nest/translations/uk.json create mode 100644 homeassistant/components/netatmo/translations/tr.json create mode 100644 homeassistant/components/netatmo/translations/uk.json create mode 100644 homeassistant/components/nexia/translations/tr.json create mode 100644 homeassistant/components/nexia/translations/uk.json create mode 100644 homeassistant/components/nightscout/translations/uk.json create mode 100644 homeassistant/components/notion/translations/uk.json create mode 100644 homeassistant/components/nuheat/translations/tr.json create mode 100644 homeassistant/components/nuheat/translations/uk.json create mode 100644 homeassistant/components/nuki/translations/ca.json create mode 100644 homeassistant/components/nuki/translations/cs.json create mode 100644 homeassistant/components/nuki/translations/en.json create mode 100644 homeassistant/components/nuki/translations/et.json create mode 100644 homeassistant/components/nuki/translations/it.json create mode 100644 homeassistant/components/nuki/translations/no.json create mode 100644 homeassistant/components/nuki/translations/pl.json create mode 100644 homeassistant/components/nuki/translations/ru.json create mode 100644 homeassistant/components/nuki/translations/tr.json create mode 100644 homeassistant/components/nuki/translations/zh-Hant.json create mode 100644 homeassistant/components/number/translations/ca.json create mode 100644 homeassistant/components/number/translations/cs.json create mode 100644 homeassistant/components/number/translations/en.json create mode 100644 homeassistant/components/number/translations/et.json create mode 100644 homeassistant/components/number/translations/it.json create mode 100644 homeassistant/components/number/translations/no.json create mode 100644 homeassistant/components/number/translations/pl.json create mode 100644 homeassistant/components/number/translations/ru.json create mode 100644 homeassistant/components/number/translations/tr.json create mode 100644 homeassistant/components/number/translations/zh-Hant.json create mode 100644 homeassistant/components/nut/translations/tr.json create mode 100644 homeassistant/components/nut/translations/uk.json create mode 100644 homeassistant/components/nws/translations/tr.json create mode 100644 homeassistant/components/nws/translations/uk.json create mode 100644 homeassistant/components/nzbget/translations/tr.json create mode 100644 homeassistant/components/nzbget/translations/uk.json create mode 100644 homeassistant/components/omnilogic/translations/tr.json create mode 100644 homeassistant/components/omnilogic/translations/uk.json create mode 100644 homeassistant/components/onboarding/translations/uk.json create mode 100644 homeassistant/components/ondilo_ico/translations/ca.json create mode 100644 homeassistant/components/ondilo_ico/translations/cs.json create mode 100644 homeassistant/components/ondilo_ico/translations/de.json create mode 100644 homeassistant/components/ondilo_ico/translations/es.json create mode 100644 homeassistant/components/ondilo_ico/translations/et.json create mode 100644 homeassistant/components/ondilo_ico/translations/it.json create mode 100644 homeassistant/components/ondilo_ico/translations/lb.json create mode 100644 homeassistant/components/ondilo_ico/translations/no.json create mode 100644 homeassistant/components/ondilo_ico/translations/pl.json create mode 100644 homeassistant/components/ondilo_ico/translations/ru.json create mode 100644 homeassistant/components/ondilo_ico/translations/tr.json create mode 100644 homeassistant/components/ondilo_ico/translations/uk.json create mode 100644 homeassistant/components/ondilo_ico/translations/zh-Hant.json create mode 100644 homeassistant/components/onewire/translations/tr.json create mode 100644 homeassistant/components/onewire/translations/uk.json create mode 100644 homeassistant/components/onvif/translations/uk.json create mode 100644 homeassistant/components/opentherm_gw/translations/tr.json create mode 100644 homeassistant/components/opentherm_gw/translations/uk.json create mode 100644 homeassistant/components/openuv/translations/tr.json create mode 100644 homeassistant/components/openweathermap/translations/tr.json create mode 100644 homeassistant/components/openweathermap/translations/uk.json create mode 100644 homeassistant/components/ovo_energy/translations/uk.json create mode 100644 homeassistant/components/owntracks/translations/tr.json create mode 100644 homeassistant/components/ozw/translations/uk.json create mode 100644 homeassistant/components/panasonic_viera/translations/tr.json create mode 100644 homeassistant/components/panasonic_viera/translations/uk.json create mode 100644 homeassistant/components/pi_hole/translations/tr.json create mode 100644 homeassistant/components/pi_hole/translations/uk.json create mode 100644 homeassistant/components/plaato/translations/tr.json create mode 100644 homeassistant/components/plaato/translations/uk.json create mode 100644 homeassistant/components/plex/translations/tr.json create mode 100644 homeassistant/components/plex/translations/uk.json create mode 100644 homeassistant/components/plugwise/translations/uk.json create mode 100644 homeassistant/components/plum_lightpad/translations/tr.json create mode 100644 homeassistant/components/plum_lightpad/translations/uk.json create mode 100644 homeassistant/components/point/translations/tr.json create mode 100644 homeassistant/components/point/translations/uk.json create mode 100644 homeassistant/components/poolsense/translations/tr.json create mode 100644 homeassistant/components/poolsense/translations/uk.json create mode 100644 homeassistant/components/powerwall/translations/tr.json create mode 100644 homeassistant/components/powerwall/translations/uk.json create mode 100644 homeassistant/components/profiler/translations/de.json create mode 100644 homeassistant/components/profiler/translations/tr.json create mode 100644 homeassistant/components/profiler/translations/uk.json create mode 100644 homeassistant/components/progettihwsw/translations/tr.json create mode 100644 homeassistant/components/progettihwsw/translations/uk.json create mode 100644 homeassistant/components/ps4/translations/tr.json create mode 100644 homeassistant/components/ps4/translations/uk.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/tr.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/uk.json create mode 100644 homeassistant/components/rachio/translations/tr.json create mode 100644 homeassistant/components/rachio/translations/uk.json create mode 100644 homeassistant/components/rainmachine/translations/uk.json create mode 100644 homeassistant/components/recollect_waste/translations/de.json create mode 100644 homeassistant/components/recollect_waste/translations/lb.json create mode 100644 homeassistant/components/recollect_waste/translations/tr.json create mode 100644 homeassistant/components/recollect_waste/translations/uk.json create mode 100644 homeassistant/components/rfxtrx/translations/tr.json create mode 100644 homeassistant/components/rfxtrx/translations/uk.json create mode 100644 homeassistant/components/ring/translations/tr.json create mode 100644 homeassistant/components/ring/translations/uk.json create mode 100644 homeassistant/components/risco/translations/tr.json create mode 100644 homeassistant/components/risco/translations/uk.json create mode 100644 homeassistant/components/roku/translations/tr.json create mode 100644 homeassistant/components/roku/translations/uk.json create mode 100644 homeassistant/components/roomba/translations/tr.json create mode 100644 homeassistant/components/roomba/translations/uk.json create mode 100644 homeassistant/components/roon/translations/tr.json create mode 100644 homeassistant/components/roon/translations/uk.json create mode 100644 homeassistant/components/rpi_power/translations/de.json create mode 100644 homeassistant/components/rpi_power/translations/tr.json create mode 100644 homeassistant/components/rpi_power/translations/uk.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/tr.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/uk.json create mode 100644 homeassistant/components/samsungtv/translations/uk.json create mode 100644 homeassistant/components/sense/translations/tr.json create mode 100644 homeassistant/components/sense/translations/uk.json create mode 100644 homeassistant/components/sentry/translations/tr.json create mode 100644 homeassistant/components/sentry/translations/uk.json create mode 100644 homeassistant/components/sharkiq/translations/tr.json create mode 100644 homeassistant/components/sharkiq/translations/uk.json create mode 100644 homeassistant/components/shelly/translations/da.json create mode 100644 homeassistant/components/shelly/translations/tr.json create mode 100644 homeassistant/components/shelly/translations/uk.json create mode 100644 homeassistant/components/shopping_list/translations/tr.json create mode 100644 homeassistant/components/shopping_list/translations/uk.json create mode 100644 homeassistant/components/smappee/translations/tr.json create mode 100644 homeassistant/components/smappee/translations/uk.json create mode 100644 homeassistant/components/smart_meter_texas/translations/tr.json create mode 100644 homeassistant/components/smart_meter_texas/translations/uk.json create mode 100644 homeassistant/components/smarthab/translations/tr.json create mode 100644 homeassistant/components/smarthab/translations/uk.json create mode 100644 homeassistant/components/smartthings/translations/tr.json create mode 100644 homeassistant/components/smartthings/translations/uk.json create mode 100644 homeassistant/components/smhi/translations/uk.json create mode 100644 homeassistant/components/sms/translations/tr.json create mode 100644 homeassistant/components/sms/translations/uk.json create mode 100644 homeassistant/components/solaredge/translations/uk.json create mode 100644 homeassistant/components/solarlog/translations/tr.json create mode 100644 homeassistant/components/solarlog/translations/uk.json create mode 100644 homeassistant/components/soma/translations/tr.json create mode 100644 homeassistant/components/soma/translations/uk.json create mode 100644 homeassistant/components/somfy/translations/tr.json create mode 100644 homeassistant/components/somfy/translations/uk.json create mode 100644 homeassistant/components/somfy_mylink/translations/ca.json create mode 100644 homeassistant/components/somfy_mylink/translations/cs.json create mode 100644 homeassistant/components/somfy_mylink/translations/de.json create mode 100644 homeassistant/components/somfy_mylink/translations/es.json create mode 100644 homeassistant/components/somfy_mylink/translations/et.json create mode 100644 homeassistant/components/somfy_mylink/translations/fr.json create mode 100644 homeassistant/components/somfy_mylink/translations/it.json create mode 100644 homeassistant/components/somfy_mylink/translations/lb.json create mode 100644 homeassistant/components/somfy_mylink/translations/no.json create mode 100644 homeassistant/components/somfy_mylink/translations/pl.json create mode 100644 homeassistant/components/somfy_mylink/translations/ru.json create mode 100644 homeassistant/components/somfy_mylink/translations/tr.json create mode 100644 homeassistant/components/somfy_mylink/translations/uk.json create mode 100644 homeassistant/components/somfy_mylink/translations/zh-Hant.json create mode 100644 homeassistant/components/sonarr/translations/tr.json create mode 100644 homeassistant/components/sonarr/translations/uk.json create mode 100644 homeassistant/components/songpal/translations/tr.json create mode 100644 homeassistant/components/songpal/translations/uk.json create mode 100644 homeassistant/components/sonos/translations/tr.json create mode 100644 homeassistant/components/sonos/translations/uk.json create mode 100644 homeassistant/components/speedtestdotnet/translations/tr.json create mode 100644 homeassistant/components/speedtestdotnet/translations/uk.json create mode 100644 homeassistant/components/spider/translations/tr.json create mode 100644 homeassistant/components/spider/translations/uk.json create mode 100644 homeassistant/components/spotify/translations/uk.json create mode 100644 homeassistant/components/squeezebox/translations/tr.json create mode 100644 homeassistant/components/squeezebox/translations/uk.json create mode 100644 homeassistant/components/srp_energy/translations/fr.json create mode 100644 homeassistant/components/srp_energy/translations/lb.json create mode 100644 homeassistant/components/srp_energy/translations/uk.json create mode 100644 homeassistant/components/starline/translations/tr.json create mode 100644 homeassistant/components/starline/translations/uk.json create mode 100644 homeassistant/components/syncthru/translations/tr.json create mode 100644 homeassistant/components/syncthru/translations/uk.json create mode 100644 homeassistant/components/synology_dsm/translations/uk.json create mode 100644 homeassistant/components/tado/translations/tr.json create mode 100644 homeassistant/components/tado/translations/uk.json create mode 100644 homeassistant/components/tag/translations/uk.json create mode 100644 homeassistant/components/tasmota/translations/de.json create mode 100644 homeassistant/components/tasmota/translations/tr.json create mode 100644 homeassistant/components/tasmota/translations/uk.json create mode 100644 homeassistant/components/tellduslive/translations/tr.json create mode 100644 homeassistant/components/tellduslive/translations/uk.json create mode 100644 homeassistant/components/tesla/translations/tr.json create mode 100644 homeassistant/components/tesla/translations/uk.json create mode 100644 homeassistant/components/tibber/translations/tr.json create mode 100644 homeassistant/components/tibber/translations/uk.json create mode 100644 homeassistant/components/tile/translations/tr.json create mode 100644 homeassistant/components/tile/translations/uk.json create mode 100644 homeassistant/components/toon/translations/tr.json create mode 100644 homeassistant/components/toon/translations/uk.json create mode 100644 homeassistant/components/totalconnect/translations/tr.json create mode 100644 homeassistant/components/totalconnect/translations/uk.json create mode 100644 homeassistant/components/tplink/translations/tr.json create mode 100644 homeassistant/components/tplink/translations/uk.json create mode 100644 homeassistant/components/traccar/translations/uk.json create mode 100644 homeassistant/components/tradfri/translations/tr.json create mode 100644 homeassistant/components/transmission/translations/tr.json create mode 100644 homeassistant/components/transmission/translations/uk.json create mode 100644 homeassistant/components/tuya/translations/uk.json create mode 100644 homeassistant/components/twentemilieu/translations/tr.json create mode 100644 homeassistant/components/twentemilieu/translations/uk.json create mode 100644 homeassistant/components/twilio/translations/tr.json create mode 100644 homeassistant/components/twilio/translations/uk.json create mode 100644 homeassistant/components/twinkly/translations/fr.json create mode 100644 homeassistant/components/twinkly/translations/lb.json create mode 100644 homeassistant/components/twinkly/translations/uk.json create mode 100644 homeassistant/components/unifi/translations/uk.json create mode 100644 homeassistant/components/upb/translations/tr.json create mode 100644 homeassistant/components/upb/translations/uk.json create mode 100644 homeassistant/components/upcloud/translations/tr.json create mode 100644 homeassistant/components/upcloud/translations/uk.json create mode 100644 homeassistant/components/upnp/translations/tr.json create mode 100644 homeassistant/components/velbus/translations/tr.json create mode 100644 homeassistant/components/velbus/translations/uk.json create mode 100644 homeassistant/components/vera/translations/tr.json create mode 100644 homeassistant/components/vera/translations/uk.json create mode 100644 homeassistant/components/vesync/translations/tr.json create mode 100644 homeassistant/components/vesync/translations/uk.json create mode 100644 homeassistant/components/vilfo/translations/tr.json create mode 100644 homeassistant/components/vilfo/translations/uk.json create mode 100644 homeassistant/components/vizio/translations/tr.json create mode 100644 homeassistant/components/vizio/translations/uk.json create mode 100644 homeassistant/components/volumio/translations/tr.json create mode 100644 homeassistant/components/water_heater/translations/uk.json create mode 100644 homeassistant/components/wemo/translations/uk.json create mode 100644 homeassistant/components/wiffi/translations/tr.json create mode 100644 homeassistant/components/wiffi/translations/uk.json create mode 100644 homeassistant/components/wilight/translations/tr.json create mode 100644 homeassistant/components/wilight/translations/uk.json create mode 100644 homeassistant/components/withings/translations/tr.json create mode 100644 homeassistant/components/withings/translations/uk.json create mode 100644 homeassistant/components/wled/translations/tr.json create mode 100644 homeassistant/components/wled/translations/uk.json create mode 100644 homeassistant/components/wolflink/translations/tr.json create mode 100644 homeassistant/components/xbox/translations/tr.json create mode 100644 homeassistant/components/xbox/translations/uk.json create mode 100644 homeassistant/components/xiaomi_aqara/translations/uk.json create mode 100644 homeassistant/components/xiaomi_miio/translations/tr.json create mode 100644 homeassistant/components/xiaomi_miio/translations/uk.json create mode 100644 homeassistant/components/yeelight/translations/tr.json create mode 100644 homeassistant/components/yeelight/translations/uk.json create mode 100644 homeassistant/components/zerproc/translations/uk.json create mode 100644 homeassistant/components/zha/translations/tr.json create mode 100644 homeassistant/components/zha/translations/uk.json create mode 100644 homeassistant/components/zodiac/translations/sensor.tr.json create mode 100644 homeassistant/components/zodiac/translations/sensor.uk.json create mode 100644 homeassistant/components/zone/translations/tr.json create mode 100644 homeassistant/components/zoneminder/translations/tr.json create mode 100644 homeassistant/components/zoneminder/translations/uk.json create mode 100644 homeassistant/components/zwave_js/translations/ca.json create mode 100644 homeassistant/components/zwave_js/translations/cs.json create mode 100644 homeassistant/components/zwave_js/translations/de.json create mode 100644 homeassistant/components/zwave_js/translations/es.json create mode 100644 homeassistant/components/zwave_js/translations/et.json create mode 100644 homeassistant/components/zwave_js/translations/fr.json create mode 100644 homeassistant/components/zwave_js/translations/it.json create mode 100644 homeassistant/components/zwave_js/translations/lb.json create mode 100644 homeassistant/components/zwave_js/translations/no.json create mode 100644 homeassistant/components/zwave_js/translations/pl.json create mode 100644 homeassistant/components/zwave_js/translations/pt-BR.json create mode 100644 homeassistant/components/zwave_js/translations/ru.json create mode 100644 homeassistant/components/zwave_js/translations/tr.json create mode 100644 homeassistant/components/zwave_js/translations/uk.json create mode 100644 homeassistant/components/zwave_js/translations/zh-Hant.json diff --git a/homeassistant/components/abode/translations/de.json b/homeassistant/components/abode/translations/de.json index 43d6ba21ca565e..307f5f45065d42 100644 --- a/homeassistant/components/abode/translations/de.json +++ b/homeassistant/components/abode/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "reauth_successful": "Die erneute Authentifizierung war erfolgreich", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von Abode erlaubt." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/abode/translations/es.json b/homeassistant/components/abode/translations/es.json index 9fa8cd8b06b7af..66cb5d13f22318 100644 --- a/homeassistant/components/abode/translations/es.json +++ b/homeassistant/components/abode/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "La reautenticaci\u00f3n fue exitosa", + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "error": { @@ -19,7 +19,7 @@ "reauth_confirm": { "data": { "password": "Contrase\u00f1a", - "username": "Correo electronico" + "username": "Correo electr\u00f3nico" }, "title": "Rellene su informaci\u00f3n de inicio de sesi\u00f3n de Abode" }, diff --git a/homeassistant/components/abode/translations/fr.json b/homeassistant/components/abode/translations/fr.json index 87be79571a4ac3..2ab158cca57f1c 100644 --- a/homeassistant/components/abode/translations/fr.json +++ b/homeassistant/components/abode/translations/fr.json @@ -1,17 +1,32 @@ { "config": { "abort": { - "single_instance_allowed": "Une seule configuration d'Abode est autoris\u00e9e." + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", + "single_instance_allowed": "D\u00e9ja configur\u00e9. Une seule configuration possible." }, "error": { "cannot_connect": "\u00c9chec de connexion", - "invalid_auth": "Authentification invalide" + "invalid_auth": "Authentification invalide", + "invalid_mfa_code": "Code MFA non valide" }, "step": { + "mfa": { + "data": { + "mfa_code": "Code MFA (6 chiffres)" + }, + "title": "Entrez votre code MFA pour Abode" + }, + "reauth_confirm": { + "data": { + "password": "Mot de passe", + "username": "Email" + }, + "title": "Remplissez vos informations de connexion Abode" + }, "user": { "data": { "password": "Mot de passe", - "username": "Adresse e-mail" + "username": "Email" }, "title": "Remplissez vos informations de connexion Abode" } diff --git a/homeassistant/components/abode/translations/tr.json b/homeassistant/components/abode/translations/tr.json new file mode 100644 index 00000000000000..d469e43f1f42ae --- /dev/null +++ b/homeassistant/components/abode/translations/tr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_mfa_code": "Ge\u00e7ersiz MFA kodu" + }, + "step": { + "mfa": { + "data": { + "mfa_code": "MFA kodu (6 basamakl\u0131)" + }, + "title": "Abode i\u00e7in MFA kodunuzu girin" + }, + "reauth_confirm": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "title": "Abode giri\u015f bilgilerinizi doldurun" + }, + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/uk.json b/homeassistant/components/abode/translations/uk.json new file mode 100644 index 00000000000000..7ad57a0ec68a69 --- /dev/null +++ b/homeassistant/components/abode/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_mfa_code": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u043a\u043e\u0434 MFA." + }, + "step": { + "mfa": { + "data": { + "mfa_code": "\u041a\u043e\u0434 MFA (6 \u0446\u0438\u0444\u0440)" + }, + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 MFA \u0434\u043b\u044f Abode" + }, + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "Abode" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "Abode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/ca.json b/homeassistant/components/accuweather/translations/ca.json index 9c33637baa85dc..8178a5caef0907 100644 --- a/homeassistant/components/accuweather/translations/ca.json +++ b/homeassistant/components/accuweather/translations/ca.json @@ -27,7 +27,7 @@ "data": { "forecast": "Previsi\u00f3 meteorol\u00f2gica" }, - "description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions es realitzaran cada 64 minuts en comptes de 32.", + "description": "Per culpa de les limitacions de la versi\u00f3 gratu\u00efta l'API d'AccuWeather, quan habilitis la previsi\u00f3 meteorol\u00f2gica, les actualitzacions de dades es faran cada 80 minuts en comptes de cada 40.", "title": "Opcions d'AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/cs.json b/homeassistant/components/accuweather/translations/cs.json index ea954b9f0dbc87..1cf34a42695440 100644 --- a/homeassistant/components/accuweather/translations/cs.json +++ b/homeassistant/components/accuweather/translations/cs.json @@ -27,7 +27,7 @@ "data": { "forecast": "P\u0159edpov\u011b\u010f po\u010das\u00ed" }, - "description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 64 minut nam\u00edsto 32 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.", + "description": "Kdy\u017e povol\u00edte p\u0159edpov\u011b\u010f po\u010das\u00ed, budou aktualizace dat prov\u00e1d\u011bny ka\u017ed\u00fdch 80 minut nam\u00edsto 40 minut z d\u016fvodu omezen\u00ed bezplatn\u00e9 verze AccuWeather.", "title": "Mo\u017enosti AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json index fe0319764a7cf7..814e57d1d6cbb0 100644 --- a/homeassistant/components/accuweather/translations/de.json +++ b/homeassistant/components/accuweather/translations/de.json @@ -1,11 +1,16 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" }, "step": { "user": { "data": { + "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", "name": "Name" @@ -25,7 +30,7 @@ }, "system_health": { "info": { - "can_reach_server": "AccuWeather Server erreichen", + "can_reach_server": "AccuWeather-Server erreichen", "remaining_requests": "Verbleibende erlaubte Anfragen" } } diff --git a/homeassistant/components/accuweather/translations/en.json b/homeassistant/components/accuweather/translations/en.json index b737c420a2d9dc..8f2261b93c7de0 100644 --- a/homeassistant/components/accuweather/translations/en.json +++ b/homeassistant/components/accuweather/translations/en.json @@ -27,7 +27,7 @@ "data": { "forecast": "Weather forecast" }, - "description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 64 minutes instead of every 32 minutes.", + "description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes.", "title": "AccuWeather Options" } } diff --git a/homeassistant/components/accuweather/translations/et.json b/homeassistant/components/accuweather/translations/et.json index bed28b62975910..6e2dc1ffd96f1f 100644 --- a/homeassistant/components/accuweather/translations/et.json +++ b/homeassistant/components/accuweather/translations/et.json @@ -27,7 +27,7 @@ "data": { "forecast": "Ilmateade" }, - "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 32 minuti asemel iga 64 minuti j\u00e4rel.", + "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 80 minuti j\u00e4rel (muidu 40 minutit).", "title": "AccuWeatheri valikud" } } diff --git a/homeassistant/components/accuweather/translations/fr.json b/homeassistant/components/accuweather/translations/fr.json index 8e63820541701b..a083ed09bdf6eb 100644 --- a/homeassistant/components/accuweather/translations/fr.json +++ b/homeassistant/components/accuweather/translations/fr.json @@ -34,7 +34,8 @@ }, "system_health": { "info": { - "can_reach_server": "Acc\u00e8s au serveur AccuWeather" + "can_reach_server": "Acc\u00e8s au serveur AccuWeather", + "remaining_requests": "Demandes restantes autoris\u00e9es" } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/it.json b/homeassistant/components/accuweather/translations/it.json index 86aaa213a15fcd..8a1f9b964630bf 100644 --- a/homeassistant/components/accuweather/translations/it.json +++ b/homeassistant/components/accuweather/translations/it.json @@ -27,7 +27,7 @@ "data": { "forecast": "Previsioni meteo" }, - "description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 64 minuti invece che ogni 32 minuti.", + "description": "A causa delle limitazioni della versione gratuita della chiave API AccuWeather, quando si abilitano le previsioni del tempo, gli aggiornamenti dei dati verranno eseguiti ogni 80 minuti invece che ogni 40.", "title": "Opzioni AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/no.json b/homeassistant/components/accuweather/translations/no.json index 50482cb3e61618..be87b1ab2447b9 100644 --- a/homeassistant/components/accuweather/translations/no.json +++ b/homeassistant/components/accuweather/translations/no.json @@ -27,7 +27,7 @@ "data": { "forecast": "V\u00e6rmelding" }, - "description": "P\u00e5 grunn av begrensningene i gratisversjonen av AccuWeather API-n\u00f8kkelen, n\u00e5r du aktiverer v\u00e6rmelding, vil dataoppdateringer bli utf\u00f8rt hvert 64. minutt i stedet for hvert 32. minutt.", + "description": "P\u00e5 grunn av begrensningene i den gratis versjonen av AccuWeather API-n\u00f8kkelen, vil dataoppdateringer utf\u00f8res hvert 80. minutt i stedet for hvert 40. minutt n\u00e5r du aktiverer v\u00e6rmelding.", "title": "AccuWeather-alternativer" } } diff --git a/homeassistant/components/accuweather/translations/pl.json b/homeassistant/components/accuweather/translations/pl.json index c6e4fb3ba8242d..2794bc8b7b6c69 100644 --- a/homeassistant/components/accuweather/translations/pl.json +++ b/homeassistant/components/accuweather/translations/pl.json @@ -27,7 +27,7 @@ "data": { "forecast": "Prognoza pogody" }, - "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 64 minuty zamiast co 32 minuty.", + "description": "Ze wzgl\u0119du na ograniczenia darmowej wersji klucza API AccuWeather po w\u0142\u0105czeniu prognozy pogody aktualizacje danych b\u0119d\u0105 wykonywane co 80 minut zamiast co 40 minut.", "title": "Opcje AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/ru.json b/homeassistant/components/accuweather/translations/ru.json index 6a675c1724854d..7bc767b1baf73a 100644 --- a/homeassistant/components/accuweather/translations/ru.json +++ b/homeassistant/components/accuweather/translations/ru.json @@ -27,7 +27,7 @@ "data": { "forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b" }, - "description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 64 \u043c\u0438\u043d\u0443\u0442\u044b, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 32 \u043c\u0438\u043d\u0443\u0442\u044b.", + "description": "\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u043f\u043e\u0433\u043e\u0434\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0435 80 \u043c\u0438\u043d\u0443\u0442, \u0430 \u043d\u0435 \u043a\u0430\u0436\u0434\u044b\u0435 40 \u043c\u0438\u043d\u0443\u0442.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 AccuWeather" } } diff --git a/homeassistant/components/accuweather/translations/sensor.uk.json b/homeassistant/components/accuweather/translations/sensor.uk.json new file mode 100644 index 00000000000000..81243e0b05da2b --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.uk.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "\u0417\u043d\u0438\u0436\u0435\u043d\u043d\u044f", + "rising": "\u0417\u0440\u043e\u0441\u0442\u0430\u043d\u043d\u044f", + "steady": "\u0421\u0442\u0430\u0431\u0456\u043b\u044c\u043d\u0438\u0439" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/tr.json b/homeassistant/components/accuweather/translations/tr.json new file mode 100644 index 00000000000000..f79f9a0e3270e8 --- /dev/null +++ b/homeassistant/components/accuweather/translations/tr.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam", + "name": "Ad" + }, + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "forecast": "Hava Durumu tahmini" + }, + "title": "AccuWeather Se\u00e7enekleri" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "AccuWeather sunucusuna ula\u015f\u0131n", + "remaining_requests": "Kalan izin verilen istekler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/uk.json b/homeassistant/components/accuweather/translations/uk.json index 8c3f282b35070d..7432d0df484355 100644 --- a/homeassistant/components/accuweather/translations/uk.json +++ b/homeassistant/components/accuweather/translations/uk.json @@ -1,15 +1,22 @@ { "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." + }, "error": { - "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "requests_exceeded": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0437\u0430\u043f\u0438\u0442\u0456\u0432 \u0434\u043e API Accuweather. \u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u043e\u0447\u0435\u043a\u0430\u0442\u0438 \u0430\u0431\u043e \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API." }, "step": { "user": { "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", - "name": "\u041d\u0430\u0437\u0432\u0430 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457" + "name": "\u041d\u0430\u0437\u0432\u0430" }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u044f\u043a\u0449\u043e \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u0430 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c:\n https://www.home-assistant.io/integrations/accuweather/ \n\n\u0417\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0434\u0435\u044f\u043a\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 \u043f\u0440\u0438\u0445\u043e\u0432\u0430\u043d\u0456 \u0456 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0430\u043a\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0438\u0445 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0432 \u0440\u0435\u0454\u0441\u0442\u0440\u0456 \u043e\u0431'\u0454\u043a\u0442\u0456\u0432 \u0456 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438 \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.", "title": "AccuWeather" } } @@ -19,8 +26,16 @@ "user": { "data": { "forecast": "\u041f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u0438" - } + }, + "description": "\u0423 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f\u043c\u0438 \u0431\u0435\u0437\u043a\u043e\u0448\u0442\u043e\u0432\u043d\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 \u043a\u043b\u044e\u0447\u0430 API AccuWeather, \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443 \u043f\u043e\u0433\u043e\u0434\u0438 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 \u0431\u0443\u0434\u0435 \u0432\u0456\u0434\u0431\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u043a\u043e\u0436\u043d\u0456 64 \u0445\u0432\u0438\u043b\u0438\u043d\u0438, \u0430 \u043d\u0435 \u043a\u043e\u0436\u043d\u0456 32 \u0445\u0432\u0438\u043b\u0438\u043d\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AccuWeather" } } + }, + "system_health": { + "info": { + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 AccuWeather", + "remaining_requests": "\u0417\u0430\u043f\u0438\u0442\u0456\u0432 \u0437\u0430\u043b\u0438\u0448\u0438\u043b\u043e\u0441\u044c" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/zh-Hant.json b/homeassistant/components/accuweather/translations/zh-Hant.json index ed5fa26f0c002b..eb3729fd2c495a 100644 --- a/homeassistant/components/accuweather/translations/zh-Hant.json +++ b/homeassistant/components/accuweather/translations/zh-Hant.json @@ -27,7 +27,7 @@ "data": { "forecast": "\u5929\u6c23\u9810\u5831" }, - "description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 64 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 32 \u5206\u9418\u3002", + "description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002", "title": "AccuWeather \u9078\u9805" } } diff --git a/homeassistant/components/acmeda/translations/de.json b/homeassistant/components/acmeda/translations/de.json index 86b22e47cda95a..94834cde42768a 100644 --- a/homeassistant/components/acmeda/translations/de.json +++ b/homeassistant/components/acmeda/translations/de.json @@ -1,11 +1,14 @@ { "config": { + "abort": { + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, "step": { "user": { "data": { "id": "Host-ID" }, - "title": "W\u00e4hlen Sie einen Hub zum Hinzuf\u00fcgen aus" + "title": "W\u00e4hle einen Hub zum Hinzuf\u00fcgen aus" } } } diff --git a/homeassistant/components/acmeda/translations/tr.json b/homeassistant/components/acmeda/translations/tr.json new file mode 100644 index 00000000000000..aea81abdcba0ca --- /dev/null +++ b/homeassistant/components/acmeda/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "id": "Ana bilgisayar kimli\u011fi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/uk.json b/homeassistant/components/acmeda/translations/uk.json new file mode 100644 index 00000000000000..245428e9c732ba --- /dev/null +++ b/homeassistant/components/acmeda/translations/uk.json @@ -0,0 +1,15 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0445\u043e\u0441\u0442\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0445\u0430\u0431, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/de.json b/homeassistant/components/adguard/translations/de.json index a02601759be9ab..67746b3abcf0ca 100644 --- a/homeassistant/components/adguard/translations/de.json +++ b/homeassistant/components/adguard/translations/de.json @@ -2,10 +2,10 @@ "config": { "abort": { "existing_instance_updated": "Bestehende Konfiguration wurde aktualisiert.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von AdGuard Home zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "hassio_confirm": { @@ -19,7 +19,7 @@ "port": "Port", "ssl": "AdGuard Home verwendet ein SSL-Zertifikat", "username": "Benutzername", - "verify_ssl": "AdGuard Home verwendet ein richtiges Zertifikat" + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, "description": "Richte deine AdGuard Home-Instanz ein um sie zu \u00dcberwachen und zu Steuern." } diff --git a/homeassistant/components/adguard/translations/no.json b/homeassistant/components/adguard/translations/no.json index f5aeea990c30c0..25046c8d38f01b 100644 --- a/homeassistant/components/adguard/translations/no.json +++ b/homeassistant/components/adguard/translations/no.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Hjem gitt av hass.io tillegget {addon}?", - "title": "AdGuard Hjem via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Home gitt av Hass.io-tillegg {addon}?", + "title": "AdGuard Home via Hass.io-tillegg" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index 34c56342b5bcd9..5e8483047f830c 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "AdGuard Home (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/tr.json b/homeassistant/components/adguard/translations/tr.json new file mode 100644 index 00000000000000..26bef46408a1e0 --- /dev/null +++ b/homeassistant/components/adguard/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/uk.json b/homeassistant/components/adguard/translations/uk.json new file mode 100644 index 00000000000000..8c24fb0a877495 --- /dev/null +++ b/homeassistant/components/adguard/translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "existing_instance_updated": "\u041a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u043e\u043d\u043e\u0432\u043b\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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "hassio_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u0456 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044e AdGuard Home." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/de.json b/homeassistant/components/advantage_air/translations/de.json index 0d8a0052406daa..3b4066996ebf2f 100644 --- a/homeassistant/components/advantage_air/translations/de.json +++ b/homeassistant/components/advantage_air/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/advantage_air/translations/tr.json b/homeassistant/components/advantage_air/translations/tr.json new file mode 100644 index 00000000000000..db639c593764b0 --- /dev/null +++ b/homeassistant/components/advantage_air/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" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi", + "port": "Port" + }, + "title": "Ba\u011flan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/uk.json b/homeassistant/components/advantage_air/translations/uk.json new file mode 100644 index 00000000000000..14ac18395e25e3 --- /dev/null +++ b/homeassistant/components/advantage_air/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e API \u0412\u0430\u0448\u043e\u0433\u043e \u043d\u0430\u0441\u0442\u0456\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u043d\u0448\u0435\u0442\u0430 Advantage Air.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/de.json b/homeassistant/components/agent_dvr/translations/de.json index 6ea40d0fd007f0..10a8307ada1d16 100644 --- a/homeassistant/components/agent_dvr/translations/de.json +++ b/homeassistant/components/agent_dvr/translations/de.json @@ -4,8 +4,8 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "already_in_progress": "Der Konfigurationsfluss f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/agent_dvr/translations/tr.json b/homeassistant/components/agent_dvr/translations/tr.json new file mode 100644 index 00000000000000..31dddab779548b --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Agent DVR'\u0131 kurun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/uk.json b/homeassistant/components/agent_dvr/translations/uk.json new file mode 100644 index 00000000000000..fef8d45d5a4927 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "Agent DVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/de.json b/homeassistant/components/airly/translations/de.json index 743a68a010ef63..8004444fdb9003 100644 --- a/homeassistant/components/airly/translations/de.json +++ b/homeassistant/components/airly/translations/de.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Die Airly-Integration ist f\u00fcr diese Koordinaten bereits konfiguriert." + "already_configured": "Standort ist bereits konfiguriert" }, "error": { + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", "wrong_location": "Keine Airly Luftmessstation an diesem Ort" }, "step": { @@ -12,7 +13,7 @@ "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", - "name": "Name der Integration" + "name": "Name" }, "description": "Einrichtung der Airly-Luftqualit\u00e4t Integration. Um einen API-Schl\u00fcssel zu generieren, registriere dich auf https://developer.airly.eu/register", "title": "Airly" @@ -21,7 +22,7 @@ }, "system_health": { "info": { - "can_reach_server": "Airly Server erreichen" + "can_reach_server": "Airly-Server erreichen" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/tr.json b/homeassistant/components/airly/translations/tr.json index 1b6e9caa24c4f5..144acc1e1aedd6 100644 --- a/homeassistant/components/airly/translations/tr.json +++ b/homeassistant/components/airly/translations/tr.json @@ -1,4 +1,21 @@ { + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + }, "system_health": { "info": { "can_reach_server": "Airly sunucusuna eri\u015fin" diff --git a/homeassistant/components/airly/translations/uk.json b/homeassistant/components/airly/translations/uk.json new file mode 100644 index 00000000000000..51bcf5195dfd2d --- /dev/null +++ b/homeassistant/components/airly/translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "wrong_location": "\u0423 \u0446\u0456\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0456 \u043d\u0435\u043c\u0430\u0454 \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u0438\u0445 \u0441\u0442\u0430\u043d\u0446\u0456\u0439 Airly." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0441\u0435\u0440\u0432\u0456\u0441\u0443 \u0437 \u0430\u043d\u0430\u043b\u0456\u0437\u0443 \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f Airly. \u0429\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c https://developer.airly.eu/register.", + "title": "Airly" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Airly" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ca.json b/homeassistant/components/airnow/translations/ca.json new file mode 100644 index 00000000000000..2db3cfad563d9f --- /dev/null +++ b/homeassistant/components/airnow/translations/ca.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_location": "No s'ha trobat cap resultat per a aquesta ubicaci\u00f3", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud", + "radius": "Radi de l'estaci\u00f3 (milles; opcional)" + }, + "description": "Configura la integraci\u00f3 de qualitat d'aire AirNow. Per generar la clau API, v\u00e9s a https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/cs.json b/homeassistant/components/airnow/translations/cs.json new file mode 100644 index 00000000000000..d978e44c70af64 --- /dev/null +++ b/homeassistant/components/airnow/translations/cs.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/de.json b/homeassistant/components/airnow/translations/de.json new file mode 100644 index 00000000000000..c98fc6d7415d24 --- /dev/null +++ b/homeassistant/components/airnow/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_location": "F\u00fcr diesen Standort wurden keine Ergebnisse gefunden", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/en.json b/homeassistant/components/airnow/translations/en.json index 5c5259c74e229d..371bb270ac170a 100644 --- a/homeassistant/components/airnow/translations/en.json +++ b/homeassistant/components/airnow/translations/en.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "invalid_location": "No results found for that location", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "Unexpected error" }, "step": { "user": { @@ -15,7 +15,6 @@ "api_key": "API Key", "latitude": "Latitude", "longitude": "Longitude", - "name": "Name of the Entity", "radius": "Station Radius (miles; optional)" }, "description": "Set up AirNow air quality integration. To generate API key go to https://docs.airnowapi.org/account/request/", diff --git a/homeassistant/components/airnow/translations/es.json b/homeassistant/components/airnow/translations/es.json new file mode 100644 index 00000000000000..d6a228a6e27784 --- /dev/null +++ b/homeassistant/components/airnow/translations/es.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "invalid_location": "No se han encontrado resultados para esa ubicaci\u00f3n", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API", + "latitude": "Latitud", + "longitude": "Longitud", + "radius": "Radio de la estaci\u00f3n (millas; opcional)" + }, + "description": "Configurar la integraci\u00f3n de calidad del aire de AirNow. Para generar una clave API, ve a https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/et.json b/homeassistant/components/airnow/translations/et.json new file mode 100644 index 00000000000000..52b2bb618e0246 --- /dev/null +++ b/homeassistant/components/airnow/translations/et.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "invalid_location": "Selle asukoha jaoks ei leitud andmeid", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "radius": "Jaama raadius (miilid; valikuline)" + }, + "description": "Seadista AirNow \u00f5hukvaliteedi sidumine. API-v\u00f5tme loomiseks mine aadressile https://docs.airnowapi.org/account/request/", + "title": "" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/fr.json b/homeassistant/components/airnow/translations/fr.json new file mode 100644 index 00000000000000..ff85d9318e9408 --- /dev/null +++ b/homeassistant/components/airnow/translations/fr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec \u00e0 la connexion", + "invalid_auth": "Authentification invalide", + "invalid_location": "Aucun r\u00e9sultat trouv\u00e9 pour cet emplacement", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 API", + "latitude": "Latitude", + "longitude": "Longitude", + "radius": "Rayon d'action de la station (en miles, facultatif)" + }, + "description": "Configurez l'int\u00e9gration de la qualit\u00e9 de l'air AirNow. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/it.json b/homeassistant/components/airnow/translations/it.json new file mode 100644 index 00000000000000..9dda15dfbd205b --- /dev/null +++ b/homeassistant/components/airnow/translations/it.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "invalid_location": "Nessun risultato trovato per quella localit\u00e0", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine", + "radius": "Raggio stazione (miglia; opzionale)" + }, + "description": "Configura l'integrazione per la qualit\u00e0 dell'aria AirNow. Per generare la chiave API, vai su https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/lb.json b/homeassistant/components/airnow/translations/lb.json new file mode 100644 index 00000000000000..a62bd0bf478e4a --- /dev/null +++ b/homeassistant/components/airnow/translations/lb.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "invalid_location": "Keng Resultater fonnt fir d\u00ebse Standuert", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "api_key": "API Schl\u00ebssel", + "latitude": "L\u00e4ngegrad", + "longitude": "Breedegrag" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/no.json b/homeassistant/components/airnow/translations/no.json new file mode 100644 index 00000000000000..19fa7e12207b27 --- /dev/null +++ b/homeassistant/components/airnow/translations/no.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "invalid_location": "Ingen resultater funnet for den plasseringen", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "radius": "Stasjonsradius (miles; valgfritt)" + }, + "description": "Konfigurer integrering av luftkvalitet i AirNow. For \u00e5 generere en API-n\u00f8kkel, g\u00e5r du til https://docs.airnowapi.org/account/request/", + "title": "" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pl.json b/homeassistant/components/airnow/translations/pl.json new file mode 100644 index 00000000000000..fe4310607b9422 --- /dev/null +++ b/homeassistant/components/airnow/translations/pl.json @@ -0,0 +1,26 @@ +{ + "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", + "invalid_location": "Brak wynik\u00f3w dla tej lokalizacji", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "radius": "Promie\u0144 od stacji (w milach; opcjonalnie)" + }, + "description": "Konfiguracja integracji jako\u015bci powietrza AirNow. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/pt.json b/homeassistant/components/airnow/translations/pt.json new file mode 100644 index 00000000000000..3aa509dd6e814b --- /dev/null +++ b/homeassistant/components/airnow/translations/pt.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude" + }, + "title": "" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ru.json b/homeassistant/components/airnow/translations/ru.json new file mode 100644 index 00000000000000..650633cc8167c1 --- /dev/null +++ b/homeassistant/components/airnow/translations/ru.json @@ -0,0 +1,26 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_location": "\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0438\u0438 (\u0432 \u043c\u0438\u043b\u044f\u0445; \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)" + }, + "description": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0443 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0432\u043e\u0437\u0434\u0443\u0445\u0430 AirNow. \u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 https://docs.airnowapi.org/account/request/.", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/tr.json b/homeassistant/components/airnow/translations/tr.json new file mode 100644 index 00000000000000..06af714dc87426 --- /dev/null +++ b/homeassistant/components/airnow/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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", + "invalid_location": "Bu konum i\u00e7in hi\u00e7bir sonu\u00e7 bulunamad\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam", + "radius": "\u0130stasyon Yar\u0131\u00e7ap\u0131 (mil; iste\u011fe ba\u011fl\u0131)" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/uk.json b/homeassistant/components/airnow/translations/uk.json new file mode 100644 index 00000000000000..bb872123f54945 --- /dev/null +++ b/homeassistant/components/airnow/translations/uk.json @@ -0,0 +1,26 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_location": "\u041d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432 \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 (\u043c\u0438\u043b\u0456; \u043d\u0435\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u044f\u043a\u043e\u0441\u0442\u0456 \u043f\u043e\u0432\u0456\u0442\u0440\u044f AirNow. \u0429\u043e\u0431 \u0437\u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/zh-Hant.json b/homeassistant/components/airnow/translations/zh-Hant.json new file mode 100644 index 00000000000000..0f6008e75a600b --- /dev/null +++ b/homeassistant/components/airnow/translations/zh-Hant.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_location": "\u627e\u4e0d\u5230\u8a72\u4f4d\u7f6e\u7684\u7d50\u679c", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "radius": "\u89c0\u6e2c\u7ad9\u534a\u5f91\uff08\u82f1\u91cc\uff1b\u9078\u9805\uff09" + }, + "description": "\u6b32\u8a2d\u5b9a AirNow \u7a7a\u6c23\u54c1\u8cea\u6574\u5408\u3002\u8acb\u81f3 https://docs.airnowapi.org/account/request/ \u7522\u751f API \u5bc6\u9470", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/ar.json b/homeassistant/components/airvisual/translations/ar.json new file mode 100644 index 00000000000000..771d88e84340ac --- /dev/null +++ b/homeassistant/components/airvisual/translations/ar.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "geography_by_name": { + "data": { + "country": "\u0627\u0644\u062f\u0648\u0644\u0629" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index d7a0ec2bd99362..29df3dc7ca2726 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "general_error": "Error inesperat", - "invalid_api_key": "Clau API inv\u00e0lida" + "invalid_api_key": "Clau API inv\u00e0lida", + "location_not_found": "No s'ha trobat la ubicaci\u00f3" }, "step": { "geography": { @@ -17,7 +18,26 @@ "longitude": "Longitud" }, "description": "Utilitza l'API d'AirVisual per monitoritzar una ubicaci\u00f3 geogr\u00e0fica.", - "title": "Configuraci\u00f3 localitzaci\u00f3 geogr\u00e0fica" + "title": "Configura una ubicaci\u00f3 geogr\u00e0fica" + }, + "geography_by_coords": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud" + }, + "description": "Utilitza l'API d'AirVisual per monitoritzar una latitud/longitud.", + "title": "Configura una ubicaci\u00f3 geogr\u00e0fica" + }, + "geography_by_name": { + "data": { + "api_key": "Clau API", + "city": "Ciutat", + "country": "Pa\u00eds", + "state": "Estat" + }, + "description": "Utilitza l'API d'AirVisual per monitoritzar un/a ciutat/estat/pa\u00eds", + "title": "Configura una ubicaci\u00f3 geogr\u00e0fica" }, "node_pro": { "data": { diff --git a/homeassistant/components/airvisual/translations/de.json b/homeassistant/components/airvisual/translations/de.json index 63012e23da1fbf..a16b02915ee2f5 100644 --- a/homeassistant/components/airvisual/translations/de.json +++ b/homeassistant/components/airvisual/translations/de.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert." + "already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { - "cannot_connect": "Verbindungsfehler", - "general_error": "Es gab einen unbekannten Fehler.", - "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel bereitgestellt." + "cannot_connect": "Verbindung fehlgeschlagen", + "general_error": "Unerwarteter Fehler", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" }, "step": { "geography": { @@ -19,7 +20,7 @@ }, "node_pro": { "data": { - "ip_address": "IP-Adresse/Hostname des Ger\u00e4ts", + "ip_address": "Host", "password": "Passwort" }, "description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.", diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index 129abcc29e5598..1a52bfb7e3b478 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -19,6 +19,15 @@ "description": "Use the AirVisual cloud API to monitor a geographical location.", "title": "Configure a Geography" }, + "geography_by_coords": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude" + }, + "description": "Use the AirVisual cloud API to monitor a geographical location.", + "title": "Configure a Geography" + }, "node_pro": { "data": { "ip_address": "Host", @@ -54,4 +63,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/airvisual/translations/et.json b/homeassistant/components/airvisual/translations/et.json index 4bbf04817f961a..9912dbce035051 100644 --- a/homeassistant/components/airvisual/translations/et.json +++ b/homeassistant/components/airvisual/translations/et.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "\u00dchendamine nurjus", "general_error": "Tundmatu viga", - "invalid_api_key": "Vale API v\u00f5ti" + "invalid_api_key": "Vale API v\u00f5ti", + "location_not_found": "Asukohta ei leitud" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "Kasutage AirVisual pilve API-t geograafilise asukoha j\u00e4lgimiseks.", "title": "Seadista Geography" }, + "geography_by_coords": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + }, + "description": "Kasuta AirVisual pilve API-t pikkus/laiuskraadi j\u00e4lgimiseks.", + "title": "Seadista Geography sidumine" + }, + "geography_by_name": { + "data": { + "api_key": "API v\u00f5ti", + "city": "Linn", + "country": "Riik", + "state": "olek" + }, + "description": "Kasuta AirVisual pilve API-t linna/osariigi/riigi j\u00e4lgimiseks.", + "title": "Seadista Geography sidumine" + }, "node_pro": { "data": { "ip_address": "\u00dcksuse IP-aadress / hostinimi", diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index 90857d826eac1b..d1a0d3d511a834 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -6,8 +6,8 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", - "general_error": "Une erreur inconnue est survenue.", - "invalid_api_key": "La cl\u00e9 API fournie n'est pas valide." + "general_error": "Erreur inattendue", + "invalid_api_key": "Cl\u00e9 API invalide" }, "step": { "geography": { @@ -21,11 +21,11 @@ }, "node_pro": { "data": { - "ip_address": "Adresse IP / nom d'h\u00f4te de l'unit\u00e9", + "ip_address": "H\u00f4te", "password": "Mot de passe" }, - "description": "Surveillez une unit\u00e9 AirVisual personnelle. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.", - "title": "Configurer un AirVisual Node/Pro" + "description": "Surveillez une unit\u00e9 personnelle AirVisual. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.", + "title": "Configurer un noeud AirVisual Pro" }, "reauth_confirm": { "data": { diff --git a/homeassistant/components/airvisual/translations/no.json b/homeassistant/components/airvisual/translations/no.json index abf4a9f62e42ef..7c5b0333652387 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Tilkobling mislyktes", "general_error": "Uventet feil", - "invalid_api_key": "Ugyldig API-n\u00f8kkel" + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "location_not_found": "Stedet ble ikke funnet" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en geografisk plassering.", "title": "Konfigurer en Geography" }, + "geography_by_coords": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad" + }, + "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en breddegrad/lengdegrad.", + "title": "Konfigurer en Geography" + }, + "geography_by_name": { + "data": { + "api_key": "API-n\u00f8kkel", + "city": "By", + "country": "Land", + "state": "stat" + }, + "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en by/stat/land.", + "title": "Konfigurer en Geography" + }, "node_pro": { "data": { "ip_address": "Vert", diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index 10af1fc2ee08cf..5590a951641175 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "general_error": "Nieoczekiwany b\u0142\u0105d", - "invalid_api_key": "Nieprawid\u0142owy klucz API" + "invalid_api_key": "Nieprawid\u0142owy klucz API", + "location_not_found": "Nie znaleziono lokalizacji" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "U\u017cyj interfejsu API chmury AirVisual do monitorowania lokalizacji geograficznej.", "title": "Konfiguracja Geography" }, + "geography_by_coords": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna" + }, + "description": "U\u017cyj API chmury AirVisual do monitorowania szeroko\u015bci/d\u0142ugo\u015bci geograficznej.", + "title": "Konfiguracja Geography" + }, + "geography_by_name": { + "data": { + "api_key": "Klucz API", + "city": "Miasto", + "country": "Kraj", + "state": "Stan" + }, + "description": "U\u017cyj API chmury AirVisual do monitorowania miasta/stanu/kraju.", + "title": "Konfiguracja Geography" + }, "node_pro": { "data": { "ip_address": "Nazwa hosta lub adres IP", diff --git a/homeassistant/components/airvisual/translations/sv.json b/homeassistant/components/airvisual/translations/sv.json index 4c4e1271d72464..f375b4fc598519 100644 --- a/homeassistant/components/airvisual/translations/sv.json +++ b/homeassistant/components/airvisual/translations/sv.json @@ -1,7 +1,8 @@ { "config": { "error": { - "general_error": "Ett ok\u00e4nt fel intr\u00e4ffade." + "general_error": "Ett ok\u00e4nt fel intr\u00e4ffade.", + "invalid_api_key": "Ogiltig API-nyckel" }, "step": { "geography": { diff --git a/homeassistant/components/airvisual/translations/tr.json b/homeassistant/components/airvisual/translations/tr.json new file mode 100644 index 00000000000000..3d20c8ea9fc83e --- /dev/null +++ b/homeassistant/components/airvisual/translations/tr.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "general_error": "Beklenmeyen hata", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", + "location_not_found": "Konum bulunamad\u0131" + }, + "step": { + "geography": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + } + }, + "geography_by_coords": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + }, + "description": "Bir enlem / boylam\u0131 izlemek i\u00e7in AirVisual bulut API'sini kullan\u0131n.", + "title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma" + }, + "geography_by_name": { + "data": { + "api_key": "API Anahtar\u0131", + "city": "\u015eehir", + "country": "\u00dclke", + "state": "durum" + }, + "title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma" + }, + "node_pro": { + "data": { + "ip_address": "Ana Bilgisayar", + "password": "Parola" + }, + "description": "Ki\u015fisel bir AirVisual \u00fcnitesini izleyin. Parola, \u00fcnitenin kullan\u0131c\u0131 aray\u00fcz\u00fcnden al\u0131nabilir." + }, + "reauth_confirm": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "AirVisual'\u0131 yap\u0131land\u0131r\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/uk.json b/homeassistant/components/airvisual/translations/uk.json new file mode 100644 index 00000000000000..d99c58de7c07ae --- /dev/null +++ b/homeassistant/components/airvisual/translations/uk.json @@ -0,0 +1,57 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435. \u0410\u0431\u043e \u0446\u0435\u0439 Node / Pro ID \u0432\u0436\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0439.", + "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" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "general_error": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + }, + "step": { + "geography": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" + }, + "description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0445\u043c\u0430\u0440\u043d\u043e\u0433\u043e API AirVisual.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "node_pro": { + "data": { + "ip_address": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual Node / Pro" + }, + "reauth_confirm": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u044e" + }, + "user": { + "data": { + "cloud_api": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "node_pro": "AirVisual Node Pro", + "type": "\u0422\u0438\u043f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u0434\u0430\u043d\u0438\u0445 AirVisual, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438.", + "title": "AirVisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u043d\u0443 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0456" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/zh-Hant.json b/homeassistant/components/airvisual/translations/zh-Hant.json index 4bdc2959047da1..3767d41b519e12 100644 --- a/homeassistant/components/airvisual/translations/zh-Hant.json +++ b/homeassistant/components/airvisual/translations/zh-Hant.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "general_error": "\u672a\u9810\u671f\u932f\u8aa4", - "invalid_api_key": "API \u5bc6\u9470\u7121\u6548" + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", + "location_not_found": "\u627e\u4e0d\u5230\u5730\u9ede" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u5730\u7406\u5ea7\u6a19\u3002", "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" }, + "geography_by_coords": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6" + }, + "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u7d93\u5ea6/\u7def\u5ea6\u3002", + "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" + }, + "geography_by_name": { + "data": { + "api_key": "API \u5bc6\u9470", + "city": "\u57ce\u5e02", + "country": "\u570b\u5bb6", + "state": "\u5dde" + }, + "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u57ce\u5e02/\u5dde/\u570b\u5bb6\u3002", + "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" + }, "node_pro": { "data": { "ip_address": "\u4e3b\u6a5f\u7aef", diff --git a/homeassistant/components/alarm_control_panel/translations/tr.json b/homeassistant/components/alarm_control_panel/translations/tr.json index ebbcf568338883..cc509430436673 100644 --- a/homeassistant/components/alarm_control_panel/translations/tr.json +++ b/homeassistant/components/alarm_control_panel/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "disarmed": "{entity_name} b\u0131rak\u0131ld\u0131", + "triggered": "{entity_name} tetiklendi" + } + }, "state": { "_": { "armed": "Etkin", diff --git a/homeassistant/components/alarm_control_panel/translations/uk.json b/homeassistant/components/alarm_control_panel/translations/uk.json index e618e297019558..b50fd9f459d714 100644 --- a/homeassistant/components/alarm_control_panel/translations/uk.json +++ b/homeassistant/components/alarm_control_panel/translations/uk.json @@ -1,13 +1,36 @@ { + "device_automation": { + "action_type": { + "arm_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "arm_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "arm_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "disarm": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043e\u0445\u043e\u0440\u043e\u043d\u0443 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "trigger": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454" + }, + "condition_type": { + "is_armed_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_armed_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_armed_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_disarmed": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "is_triggered": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454" + }, + "trigger_type": { + "armed_away": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "armed_home": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u0412\u0434\u043e\u043c\u0430\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "armed_night": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \"\u041d\u0456\u0447\" \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "disarmed": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 {entity_name}", + "triggered": "{entity_name} \u0441\u043f\u0440\u0430\u0446\u044c\u043e\u0432\u0443\u0454" + } + }, "state": { "_": { "armed": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430", - "armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u043d\u0435 \u0432\u0434\u043e\u043c\u0430)", + "armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u041d\u0435 \u0432\u0434\u043e\u043c\u0430)", "armed_custom_bypass": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 \u0437 \u0432\u0438\u043d\u044f\u0442\u043a\u0430\u043c\u0438", - "armed_home": "\u0411\u0443\u0434\u0438\u043d\u043a\u043e\u0432\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430", + "armed_home": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u0412\u0434\u043e\u043c\u0430)", "armed_night": "\u041d\u0456\u0447\u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430", "arming": "\u0421\u0442\u0430\u0432\u043b\u044e \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443", - "disarmed": "\u0417\u043d\u044f\u0442\u043e", + "disarmed": "\u0417\u043d\u044f\u0442\u043e \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438", "disarming": "\u0417\u043d\u044f\u0442\u0442\u044f", "pending": "\u041e\u0447\u0456\u043a\u0443\u044e", "triggered": "\u0422\u0440\u0438\u0432\u043e\u0433\u0430" diff --git a/homeassistant/components/alarmdecoder/translations/de.json b/homeassistant/components/alarmdecoder/translations/de.json index 3f1b7ef816ed31..c37fb7b43906f3 100644 --- a/homeassistant/components/alarmdecoder/translations/de.json +++ b/homeassistant/components/alarmdecoder/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "protocol": { @@ -42,7 +45,7 @@ "data": { "zone_number": "Zonennummer" }, - "description": "Geben Sie die Zonennummer ein, die Sie hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chten." + "description": "Gib die die Zonennummer ein, die du hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chtest." } } } diff --git a/homeassistant/components/alarmdecoder/translations/tr.json b/homeassistant/components/alarmdecoder/translations/tr.json new file mode 100644 index 00000000000000..276b733b31fd5f --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/tr.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "protocol": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + }, + "options": { + "error": { + "relay_inclusive": "R\u00f6le Adresi ve R\u00f6le Kanal\u0131 birbirine ba\u011fl\u0131d\u0131r ve birlikte eklenmelidir." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternatif Gece Modu" + } + }, + "init": { + "data": { + "edit_select": "D\u00fczenle" + } + }, + "zone_details": { + "data": { + "zone_name": "B\u00f6lge Ad\u0131", + "zone_relayaddr": "R\u00f6le Adresi", + "zone_relaychan": "R\u00f6le Kanal\u0131" + } + }, + "zone_select": { + "data": { + "zone_number": "B\u00f6lge Numaras\u0131" + }, + "title": "AlarmDecoder'\u0131 yap\u0131land\u0131r\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/uk.json b/homeassistant/components/alarmdecoder/translations/uk.json new file mode 100644 index 00000000000000..c19d00c0ecad1f --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/uk.json @@ -0,0 +1,74 @@ +{ + "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." + }, + "create_entry": { + "default": "\u0423\u0441\u043f\u0456\u0448\u043d\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e AlarmDecoder." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "\u0428\u0432\u0438\u0434\u043a\u0456\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0434\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "device_path": "\u0428\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "user": { + "data": { + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "\u041f\u043e\u043b\u0435 \u043d\u0438\u0436\u0447\u0435 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0446\u0456\u043b\u0438\u043c \u0447\u0438\u0441\u043b\u043e\u043c.", + "loop_range": "RF Loop \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0446\u0456\u043b\u0438\u043c \u0447\u0438\u0441\u043b\u043e\u043c \u0432\u0456\u0434 1 \u0434\u043e 4.", + "loop_rfid": "RF Loop \u043d\u0435 \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0431\u0435\u0437 RF Serial.", + "relay_inclusive": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0440\u0435\u043b\u0435 \u0456 \u043a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435 \u0432\u0437\u0430\u0454\u043c\u043e\u0437\u0430\u043b\u0435\u0436\u043d\u0456 \u0456 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0456 \u0440\u0430\u0437\u043e\u043c." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u043d\u0456\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "auto_bypass": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0438\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u0438 \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0446\u0456 \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443", + "code_arm_required": "\u041a\u043e\u0434, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438" + }, + "description": "\u0429\u043e \u0431 \u0412\u0438 \u0445\u043e\u0442\u0456\u043b\u0438 \u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438?", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "\u041d\u0430\u0437\u0432\u0430 \u0437\u043e\u043d\u0438", + "zone_relayaddr": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0440\u0435\u043b\u0435", + "zone_relaychan": "\u041a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435", + "zone_rfid": "RF Serial", + "zone_type": "\u0422\u0438\u043f \u0437\u043e\u043d\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u0437\u043e\u043d\u0438 {zone_number}. \u0429\u043e\u0431 \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0437\u043e\u043d\u0443 {zone_number}, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u041d\u0430\u0437\u0432\u0430 \u0437\u043e\u043d\u0438\" \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "\u041d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u0438, \u044f\u043a\u0443 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438, \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0430\u0431\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/de.json b/homeassistant/components/almond/translations/de.json index e3a61026774b85..5eb8c4940aa237 100644 --- a/homeassistant/components/almond/translations/de.json +++ b/homeassistant/components/almond/translations/de.json @@ -1,8 +1,10 @@ { "config": { "abort": { - "cannot_connect": "Verbindung zum Almond-Server nicht m\u00f6glich.", - "missing_configuration": "Bitte \u00fcberpr\u00fcfe die Dokumentation zur Einrichtung von Almond." + "cannot_connect": "Verbindung fehlgeschlagen", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index 1b0f03b80182a5..9cd22ca5bc56f9 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io tillegget: {addon}?", - "title": "Almond via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io-tillegg: {addon}?", + "title": "Almond via Hass.io-tillegg" }, "pick_implementation": { "title": "Velg godkjenningsmetode" diff --git a/homeassistant/components/almond/translations/ru.json b/homeassistant/components/almond/translations/ru.json index 27870a46e95ec7..e671651f65de13 100644 --- a/homeassistant/components/almond/translations/ru.json +++ b/homeassistant/components/almond/translations/ru.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "Almond (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" }, "pick_implementation": { "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" diff --git a/homeassistant/components/almond/translations/tr.json b/homeassistant/components/almond/translations/tr.json new file mode 100644 index 00000000000000..dc270099fcd292 --- /dev/null +++ b/homeassistant/components/almond/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/uk.json b/homeassistant/components/almond/translations/uk.json new file mode 100644 index 00000000000000..7f8c12917bbac3 --- /dev/null +++ b/homeassistant/components/almond/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "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." + }, + "step": { + "hassio_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/de.json b/homeassistant/components/ambiclimate/translations/de.json index e5988f761037a8..d91fc15f37d9e6 100644 --- a/homeassistant/components/ambiclimate/translations/de.json +++ b/homeassistant/components/ambiclimate/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "access_token": "Unbekannter Fehler beim Generieren eines Zugriffstokens." + "access_token": "Unbekannter Fehler beim Generieren eines Zugriffstokens.", + "already_configured": "Konto wurde bereits konfiguriert", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." }, "create_entry": { "default": "Erfolgreiche Authentifizierung mit Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/fr.json b/homeassistant/components/ambiclimate/translations/fr.json index bdbfaea20efbe3..37ef95496860de 100644 --- a/homeassistant/components/ambiclimate/translations/fr.json +++ b/homeassistant/components/ambiclimate/translations/fr.json @@ -2,7 +2,7 @@ "config": { "abort": { "access_token": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'un jeton d'acc\u00e8s.", - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." }, "create_entry": { diff --git a/homeassistant/components/ambiclimate/translations/tr.json b/homeassistant/components/ambiclimate/translations/tr.json new file mode 100644 index 00000000000000..bcaeba84558752 --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/uk.json b/homeassistant/components/ambiclimate/translations/uk.json new file mode 100644 index 00000000000000..398665ab667e3a --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "access_token": "\u041f\u0440\u0438 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u0456 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0441\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430.", + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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." + }, + "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." + }, + "error": { + "follow_link": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u0456 \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\".", + "no_token": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430." + }, + "step": { + "auth": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043f\u043e [\u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c]({authorization_url}) \u0456 ** \u0414\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 ** \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0432\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Ambi Climate, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0456 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **.\n(\u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 URL \u0437\u0432\u043e\u0440\u043e\u0442\u043d\u043e\u0433\u043e \u0432\u0438\u043a\u043b\u0438\u043a\u0443 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 {cb_url} )", + "title": "Ambi Climate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/de.json b/homeassistant/components/ambient_station/translations/de.json index 53e6b1f69d6100..c6570fee0e352f 100644 --- a/homeassistant/components/ambient_station/translations/de.json +++ b/homeassistant/components/ambient_station/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieser App-Schl\u00fcssel wird bereits verwendet." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "invalid_key": "Ung\u00fcltiger API Key und / oder Anwendungsschl\u00fcssel", + "invalid_key": "Ung\u00fcltiger API-Schl\u00fcssel", "no_devices": "Keine Ger\u00e4te im Konto gefunden" }, "step": { diff --git a/homeassistant/components/ambient_station/translations/tr.json b/homeassistant/components/ambient_station/translations/tr.json new file mode 100644 index 00000000000000..908d97f5758bfb --- /dev/null +++ b/homeassistant/components/ambient_station/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/uk.json b/homeassistant/components/ambient_station/translations/uk.json new file mode 100644 index 00000000000000..722cf99af7e648 --- /dev/null +++ b/homeassistant/components/ambient_station/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "invalid_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "no_devices": "\u0412 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u0456 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "app_key": "\u041a\u043b\u044e\u0447 \u0434\u043e\u0434\u0430\u0442\u043a\u0443" + }, + "title": "Ambient PWS" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/fr.json b/homeassistant/components/apple_tv/translations/fr.json index a55d37ed588cc3..e1a719b31c90b3 100644 --- a/homeassistant/components/apple_tv/translations/fr.json +++ b/homeassistant/components/apple_tv/translations/fr.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "already_configured_device": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", + "backoff": "L'appareil n'accepte pas les demandes d'appariement pour le moment (vous avez peut-\u00eatre saisi un code PIN non valide trop de fois), r\u00e9essayez plus tard.", + "device_did_not_pair": "Aucune tentative pour terminer l'appairage n'a \u00e9t\u00e9 effectu\u00e9e \u00e0 partir de l'appareil.", + "invalid_config": "La configuration de cet appareil est incompl\u00e8te. Veuillez r\u00e9essayer de l'ajouter.", + "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", + "unknown": "Erreur inattendue" + }, "error": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "invalid_auth": "Autentification invalide", "no_devices_found": "Aucun appareil d\u00e9tect\u00e9 sur le r\u00e9seau", "no_usable_service": "Un dispositif a \u00e9t\u00e9 trouv\u00e9, mais aucun moyen d\u2019\u00e9tablir un lien avec lui. Si vous continuez \u00e0 voir ce message, essayez de sp\u00e9cifier son adresse IP ou de red\u00e9marrer votre Apple TV.", "unknown": "Erreur innatendue" @@ -19,9 +29,12 @@ "pair_with_pin": { "data": { "pin": "Code PIN" - } + }, + "description": "L'appairage est requis pour le protocole `{protocol}`. Veuillez saisir le code PIN affich\u00e9 \u00e0 l'\u00e9cran. Les z\u00e9ros doivent \u00eatre omis, c'est-\u00e0-dire entrer 123 si le code affich\u00e9 est 0123.", + "title": "Appairage" }, "reconfigure": { + "description": "Cette Apple TV rencontre des difficult\u00e9s de connexion et doit \u00eatre reconfigur\u00e9e.", "title": "Reconfiguration de l'appareil" }, "service_problem": { diff --git a/homeassistant/components/apple_tv/translations/lb.json b/homeassistant/components/apple_tv/translations/lb.json index 945f467c4cf84f..2354033b577050 100644 --- a/homeassistant/components/apple_tv/translations/lb.json +++ b/homeassistant/components/apple_tv/translations/lb.json @@ -3,9 +3,14 @@ "abort": { "already_configured_device": "Apparat ass scho konfigur\u00e9iert", "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", + "invalid_config": "Konfiguratioun fir d\u00ebsen Apparat ass net komplett. Prob\u00e9ier fir et nach emol dob\u00e4i ze setzen.", + "no_devices_found": "Keng Apparater am Netzwierk fonnt", "unknown": "Onerwaarte Feeler" }, "error": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "no_devices_found": "Keng Apparater am Netzwierk fonnt", "unknown": "Onerwaarte Feeler" }, "flow_title": "Apple TV: {name}", @@ -29,6 +34,9 @@ "description": "D\u00ebsen Apple TV huet e puer Verbindungsschwieregkeeten a muss nei konfigur\u00e9iert ginn.", "title": "Apparat Rekonfiguratioun" }, + "service_problem": { + "title": "Feeler beim dob\u00e4isetze vum Service" + }, "user": { "data": { "device_input": "Apparat" diff --git a/homeassistant/components/apple_tv/translations/tr.json b/homeassistant/components/apple_tv/translations/tr.json index 0ddc466a6f7662..f33e3998af6279 100644 --- a/homeassistant/components/apple_tv/translations/tr.json +++ b/homeassistant/components/apple_tv/translations/tr.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "invalid_config": "Bu ayg\u0131t\u0131n yap\u0131land\u0131rmas\u0131 tamamlanmad\u0131. L\u00fctfen tekrar eklemeyi deneyin.", "no_devices_found": "A\u011fda cihaz bulunamad\u0131", "unknown": "Beklenmeyen hata" diff --git a/homeassistant/components/apple_tv/translations/uk.json b/homeassistant/components/apple_tv/translations/uk.json new file mode 100644 index 00000000000000..a1ae2259ada604 --- /dev/null +++ b/homeassistant/components/apple_tv/translations/uk.json @@ -0,0 +1,64 @@ +{ + "config": { + "abort": { + "already_configured_device": "\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.", + "backoff": "\u0412 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0440\u0438\u0439\u043c\u0430\u0454 \u0437\u0430\u043f\u0438\u0442\u0438 \u043d\u0430 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 (\u043c\u043e\u0436\u043b\u0438\u0432\u043e, \u0412\u0438 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u0431\u0430\u0433\u0430\u0442\u043e \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u043b\u0438 \u043d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 PIN-\u043a\u043e\u0434), \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", + "device_did_not_pair": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043d\u0430\u043c\u0430\u0433\u0430\u0432\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", + "invalid_config": "\u041a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0439\u043e\u0433\u043e \u0449\u0435 \u0440\u0430\u0437.", + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "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.", + "no_usable_service": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0441\u043f\u043e\u0441\u0456\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u042f\u043a\u0449\u043e \u0412\u0438 \u0432\u0436\u0435 \u0431\u0430\u0447\u0438\u043b\u0438 \u0446\u0435 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c \u0439\u043e\u0433\u043e.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Apple TV: {name}", + "step": { + "confirm": { + "description": "\u0412\u0438 \u0437\u0431\u0438\u0440\u0430\u0454\u0442\u0435\u0441\u044f \u0434\u043e\u0434\u0430\u0442\u0438 Apple TV `{name}` \u0432 Home Assistant. \n\n ** \u0414\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0443 \u0412\u0430\u043c \u043c\u043e\u0436\u0435 \u0437\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0438\u0441\u044f \u0432\u0432\u0435\u0441\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 PIN-\u043a\u043e\u0434\u0456\u0432. ** \n\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u0412\u0438 *\u043d\u0435* \u0437\u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u043c\u0438\u043a\u0430\u0442\u0438 Apple TV \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457. \u0412 Home Assistant \u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438 \u0442\u0456\u043b\u044c\u043a\u0438 \u043c\u0435\u0434\u0456\u0430\u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0447!", + "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f Apple TV" + }, + "pair_no_pin": { + "description": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043b\u044f \u0441\u043b\u0443\u0436\u0431\u0438 `{protocol}`. \u0414\u043b\u044f \u043f\u0440\u043e\u0434\u043e\u0432\u0436\u0435\u043d\u043d\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434 {pin} \u043d\u0430 \u0412\u0430\u0448\u043e\u043c\u0443 Apple TV.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "pair_with_pin": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 `{protocol}`. \u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434, \u044f\u043a\u0438\u0439 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456. \u041f\u0435\u0440\u0448\u0456 \u043d\u0443\u043b\u0456 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u043e\u043f\u0443\u0449\u0435\u043d\u0456, \u0442\u043e\u0431\u0442\u043e \u0432\u0432\u0435\u0434\u0456\u0442\u044c 123, \u044f\u043a\u0449\u043e \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043a\u043e\u0434 0123.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "reconfigure": { + "description": "\u0423 \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Apple TV \u0432\u0438\u043d\u0438\u043a\u0430\u044e\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043f\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456, \u0439\u043e\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438.", + "title": "\u041f\u0435\u0440\u0435\u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "service_problem": { + "description": "\u0412\u0438\u043d\u0438\u043a\u043b\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043f\u0440\u0438 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u0456 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 `{protocol}`. \u0426\u0435 \u0431\u0443\u0434\u0435 \u043f\u0440\u043e\u0456\u0433\u043d\u043e\u0440\u043e\u0432\u0430\u043d\u043e.", + "title": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0434\u043e\u0434\u0430\u0442\u0438 \u0441\u043b\u0443\u0436\u0431\u0443" + }, + "user": { + "data": { + "device_input": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u0437 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u043d\u0430\u0437\u0432\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, \u041a\u0443\u0445\u043d\u044f \u0430\u0431\u043e \u0421\u043f\u0430\u043b\u044c\u043d\u044f) \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0438 Apple TV, \u044f\u043a\u0443 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438. \u042f\u043a\u0449\u043e \u0431\u0443\u0434\u044c-\u044f\u043a\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0456 \u0443 \u0412\u0430\u0448\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456, \u0432\u043e\u043d\u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0456 \u043d\u0438\u0436\u0447\u0435. \n\n \u042f\u043a\u0449\u043e \u0412\u0438 \u043d\u0435 \u0431\u0430\u0447\u0438\u0442\u0435 \u0441\u0432\u0456\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0430\u0431\u043e \u0432\u0438\u043d\u0438\u043a\u0430\u044e\u0442\u044c \u0431\u0443\u0434\u044c-\u044f\u043a\u0456 \u0456\u043d\u0448\u0456 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043f\u0456\u0434 \u0447\u0430\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \n\n {devices}", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043d\u043e\u0432\u043e\u0433\u043e Apple TV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "\u041d\u0435 \u0432\u043c\u0438\u043a\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 Home Assistant" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "title": "Apple TV" +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/zh-Hans.json b/homeassistant/components/apple_tv/translations/zh-Hans.json index bb1f8e025cad7a..54095a0a633679 100644 --- a/homeassistant/components/apple_tv/translations/zh-Hans.json +++ b/homeassistant/components/apple_tv/translations/zh-Hans.json @@ -1,6 +1,9 @@ { "config": { "step": { + "confirm": { + "description": "\u60a8\u5373\u5c06\u6dfb\u52a0 Apple TV (\u540d\u79f0\u4e3a\u201c{name}\u201d)\u5230 Home Assistant\u3002 \n\n **\u8981\u5b8c\u6210\u6b64\u8fc7\u7a0b\uff0c\u53ef\u80fd\u9700\u8981\u8f93\u5165\u591a\u4e2a PIN \u7801\u3002** \n\n\u8bf7\u6ce8\u610f\uff0c\u6b64\u96c6\u6210*\u4e0d\u80fd*\u5173\u95ed Apple TV \u7684\u7535\u6e90\uff0c\u53ea\u4f1a\u5173\u95ed Home Assistant \u4e2d\u7684\u5a92\u4f53\u64ad\u653e\u5668\uff01" + }, "pair_no_pin": { "title": "\u914d\u5bf9\u4e2d" }, @@ -8,6 +11,20 @@ "data": { "pin": "PIN\u7801" } + }, + "user": { + "description": "\u8981\u5f00\u59cb\uff0c\u8bf7\u8f93\u5165\u8981\u6dfb\u52a0\u7684 Apple TV \u7684\u8bbe\u5907\u540d\u79f0\u6216 IP \u5730\u5740\u3002\u5728\u7f51\u7edc\u4e0a\u81ea\u52a8\u53d1\u73b0\u7684\u8bbe\u5907\u4f1a\u663e\u793a\u5728\u4e0b\u65b9\u3002 \n\n\u5982\u679c\u6ca1\u6709\u53d1\u73b0\u8bbe\u5907\u6216\u9047\u5230\u4efb\u4f55\u95ee\u9898\uff0c\u8bf7\u5c1d\u8bd5\u6307\u5b9a\u8bbe\u5907 IP \u5730\u5740\u3002 \n\n {devices}", + "title": "\u8bbe\u7f6e\u65b0\u7684 Apple TV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "\u542f\u52a8 Home Assistant \u65f6\u4e0d\u6253\u5f00\u8bbe\u5907" + }, + "description": "\u914d\u7f6e\u8bbe\u5907\u901a\u7528\u8bbe\u7f6e" } } }, diff --git a/homeassistant/components/arcam_fmj/translations/de.json b/homeassistant/components/arcam_fmj/translations/de.json index 92ad0e22663046..b7270e730bb931 100644 --- a/homeassistant/components/arcam_fmj/translations/de.json +++ b/homeassistant/components/arcam_fmj/translations/de.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/arcam_fmj/translations/ro.json b/homeassistant/components/arcam_fmj/translations/ro.json new file mode 100644 index 00000000000000..a8008f1e8bcd5c --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/ro.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "few": "Pu\u021bine", + "one": "Unul", + "other": "Altele" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/tr.json b/homeassistant/components/arcam_fmj/translations/tr.json new file mode 100644 index 00000000000000..dd15f57212caa4 --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/uk.json b/homeassistant/components/arcam_fmj/translations/uk.json new file mode 100644 index 00000000000000..4d33a5bc0d9733 --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/uk.json @@ -0,0 +1,27 @@ +{ + "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_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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Arcam FMJ {host}", + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Arcam FMJ `{host}`?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\u0437\u0430\u043f\u0438\u0442\u0430\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/de.json b/homeassistant/components/atag/translations/de.json index 2ced7577fdf937..b94103d898bca6 100644 --- a/homeassistant/components/atag/translations/de.json +++ b/homeassistant/components/atag/translations/de.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "already_configured": "Dieses Ger\u00e4t wurde bereits zu HomeAssistant hinzugef\u00fcgt" + "already_configured": "Dieses Ger\u00e4t wurde bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { "data": { - "email": "Email (Optional)", + "email": "E-Mail", "host": "Host", "port": "Port" }, diff --git a/homeassistant/components/atag/translations/tr.json b/homeassistant/components/atag/translations/tr.json new file mode 100644 index 00000000000000..f7c94d0a976461 --- /dev/null +++ b/homeassistant/components/atag/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unauthorized": "E\u015fle\u015ftirme reddedildi, kimlik do\u011frulama iste\u011fi i\u00e7in cihaz\u0131 kontrol edin" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/uk.json b/homeassistant/components/atag/translations/uk.json new file mode 100644 index 00000000000000..ee0a077d900852 --- /dev/null +++ b/homeassistant/components/atag/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "unauthorized": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0437\u0430\u0431\u043e\u0440\u043e\u043d\u0435\u043d\u043e, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430 \u0437\u0430\u043f\u0438\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/de.json b/homeassistant/components/august/translations/de.json index d46be650e2c5f1..3a5bd70f1af12f 100644 --- a/homeassistant/components/august/translations/de.json +++ b/homeassistant/components/august/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Konto ist bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/august/translations/tr.json b/homeassistant/components/august/translations/tr.json new file mode 100644 index 00000000000000..ccb9e200c820cf --- /dev/null +++ b/homeassistant/components/august/translations/tr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap 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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "login_method": "Giri\u015f Y\u00f6ntemi", + "password": "Parola", + "timeout": "Zaman a\u015f\u0131m\u0131 (saniye)", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Giri\u015f Y\u00f6ntemi 'e-posta' ise, Kullan\u0131c\u0131 Ad\u0131 e-posta adresidir. Giri\u015f Y\u00f6ntemi 'telefon' ise, Kullan\u0131c\u0131 Ad\u0131 '+ NNNNNNNNN' bi\u00e7imindeki telefon numaras\u0131d\u0131r." + }, + "validation": { + "data": { + "code": "Do\u011frulama kodu" + }, + "description": "L\u00fctfen {login_method} ( {username} ) bilgilerinizi kontrol edin ve a\u015fa\u011f\u0131ya do\u011frulama kodunu girin", + "title": "\u0130ki fakt\u00f6rl\u00fc kimlik do\u011frulama" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/uk.json b/homeassistant/components/august/translations/uk.json new file mode 100644 index 00000000000000..e06c5347d73364 --- /dev/null +++ b/homeassistant/components/august/translations/uk.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "login_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u042f\u043a\u0449\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0456\u043d\u043e\u043c \u0454 \u0430\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438. \u042f\u043a\u0449\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0456\u043d\u043e\u043c \u0454 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 '+ NNNNNNNNN'.", + "title": "August" + }, + "validation": { + "data": { + "code": "\u041a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "description": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 {login_method} ({username}) \u0456 \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f.", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/de.json b/homeassistant/components/aurora/translations/de.json index 95312fe7943d09..838673e8d60000 100644 --- a/homeassistant/components/aurora/translations/de.json +++ b/homeassistant/components/aurora/translations/de.json @@ -1,7 +1,16 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "name": "Name" + } + } } }, "options": { @@ -12,5 +21,6 @@ } } } - } + }, + "title": "NOAA Aurora-Sensor" } \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/fr.json b/homeassistant/components/aurora/translations/fr.json new file mode 100644 index 00000000000000..473ecefdbd9693 --- /dev/null +++ b/homeassistant/components/aurora/translations/fr.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec \u00e0 la connexion" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "Seuil (%)" + } + } + } + }, + "title": "Capteur NOAA Aurora" +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/tr.json b/homeassistant/components/aurora/translations/tr.json new file mode 100644 index 00000000000000..0c3bb75ed6e913 --- /dev/null +++ b/homeassistant/components/aurora/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam", + "name": "Ad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/uk.json b/homeassistant/components/aurora/translations/uk.json new file mode 100644 index 00000000000000..0cb3c4fcbceb43 --- /dev/null +++ b/homeassistant/components/aurora/translations/uk.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "\u041f\u043e\u0440\u0456\u0433 (%)" + } + } + } + }, + "title": "NOAA Aurora Sensor" +} \ No newline at end of file diff --git a/homeassistant/components/auth/translations/de.json b/homeassistant/components/auth/translations/de.json index 06da3cde1a132b..93cbf1073ccbe5 100644 --- a/homeassistant/components/auth/translations/de.json +++ b/homeassistant/components/auth/translations/de.json @@ -25,7 +25,7 @@ }, "step": { "init": { - "description": "Um die Zwei-Faktor-Authentifizierung mit zeitbasierten Einmalpassw\u00f6rtern zu aktivieren, scanne den QR-Code mit Ihrer Authentifizierungs-App. Wenn du keine hast, empfehlen wir entweder [Google Authenticator] (https://support.google.com/accounts/answer/1066447) oder [Authy] (https://authy.com/). \n\n {qr_code} \n \nNachdem du den Code gescannt hast, gebe den sechsstelligen Code aus der App ein, um das Setup zu \u00fcberpr\u00fcfen. Wenn es Probleme beim Scannen des QR-Codes gibt, f\u00fchre ein manuelles Setup mit dem Code ** ` {code} ` ** durch.", + "description": "Um die Zwei-Faktor-Authentifizierung mit zeitbasierten Einmalpassw\u00f6rtern zu aktivieren, scanne den QR-Code mit deiner Authentifizierungs-App. Wenn du keine hast, empfehlen wir entweder [Google Authenticator] (https://support.google.com/accounts/answer/1066447) oder [Authy] (https://authy.com/). \n\n {qr_code} \n \nNachdem du den Code gescannt hast, gibst du den sechsstelligen Code aus der App ein, um das Setup zu \u00fcberpr\u00fcfen. Wenn es Probleme beim Scannen des QR-Codes gibt, f\u00fchre ein manuelles Setup mit dem Code ** ` {code} ` ** durch.", "title": "Richte die Zwei-Faktor-Authentifizierung mit TOTP ein" } }, diff --git a/homeassistant/components/auth/translations/tr.json b/homeassistant/components/auth/translations/tr.json new file mode 100644 index 00000000000000..7d273214574684 --- /dev/null +++ b/homeassistant/components/auth/translations/tr.json @@ -0,0 +1,22 @@ +{ + "mfa_setup": { + "notify": { + "step": { + "init": { + "title": "Bilgilendirme bile\u015feni taraf\u0131ndan verilen tek seferlik parolay\u0131 ayarlay\u0131n" + }, + "setup": { + "description": "**bildirim yoluyla tek seferlik bir parola g\u00f6nderildi. {notify_service}**. L\u00fctfen a\u015fa\u011f\u0131da girin:" + } + }, + "title": "Tek Seferlik Parolay\u0131 Bildir" + }, + "totp": { + "step": { + "init": { + "description": "Zamana dayal\u0131 tek seferlik parolalar\u0131 kullanarak iki fakt\u00f6rl\u00fc kimlik do\u011frulamay\u0131 etkinle\u015ftirmek i\u00e7in kimlik do\u011frulama uygulaman\u0131zla QR kodunu taray\u0131n. Hesab\u0131n\u0131z yoksa, [Google Authenticator] (https://support.google.com/accounts/answer/1066447) veya [Authy] (https://authy.com/) \u00f6neririz. \n\n {qr_code}\n\n Kodu tarad\u0131ktan sonra, kurulumu do\u011frulamak i\u00e7in uygulaman\u0131zdan alt\u0131 haneli kodu girin. QR kodunu taramayla ilgili sorun ya\u015f\u0131yorsan\u0131z, ** ` {code} ` manuel kurulum yap\u0131n." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/auth/translations/uk.json b/homeassistant/components/auth/translations/uk.json index f826075078e7b5..eeb8f1ee7c7c2e 100644 --- a/homeassistant/components/auth/translations/uk.json +++ b/homeassistant/components/auth/translations/uk.json @@ -1,14 +1,35 @@ { "mfa_setup": { "notify": { + "abort": { + "no_available_service": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0441\u043b\u0443\u0436\u0431 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c." + }, "error": { "invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." }, "step": { + "init": { + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u043d\u0443 \u0456\u0437 \u0441\u043b\u0443\u0436\u0431 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c:", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u043e\u043b\u0456\u0432 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c" + }, "setup": { + "description": "\u041e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 ** notify.{notify_service} **. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u0439\u043e\u0433\u043e \u043d\u0438\u0436\u0447\u0435:", "title": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" } - } + }, + "title": "\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u043e\u043b\u0456\u0432" + }, + "totp": { + "error": { + "invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443. \u042f\u043a\u0449\u043e \u0412\u0438 \u043f\u043e\u0441\u0442\u0456\u0439\u043d\u043e \u043e\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u0435 \u0446\u044e \u043f\u043e\u043c\u0438\u043b\u043a\u0443, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a \u0443 \u0412\u0430\u0448\u0456\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0456 Home Assistant \u043f\u043e\u043a\u0430\u0437\u0443\u0454 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u0447\u0430\u0441." + }, + "step": { + "init": { + "description": "\u0429\u043e\u0431 \u0430\u043a\u0442\u0438\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0443 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u043e\u043b\u0456\u0432, \u0437\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0445 \u043d\u0430 \u0447\u0430\u0441\u0456, \u0432\u0456\u0434\u0441\u043a\u0430\u043d\u0443\u0439\u0442\u0435 QR-\u043a\u043e\u0434 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u0456. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0457\u0457 \u043d\u0435\u043c\u0430\u0454, \u043c\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u043c\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0430\u0431\u043e [Google Authenticator](https://support.google.com/accounts/answer/1066447), \u0430\u0431\u043e [Authy](https://authy.com/). \n\n{qr_code}\n\n\u041f\u0456\u0441\u043b\u044f \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f QR-\u043a\u043e\u0434\u0443 \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u0448\u0435\u0441\u0442\u0438\u0437\u043d\u0430\u0447\u043d\u0438\u0439 \u043a\u043e\u0434 \u0437 \u0412\u0430\u0448\u043e\u0433\u043e \u0437\u0430\u0441\u0442\u043e\u0441\u0443\u0432\u0430\u043d\u043d\u044f, \u0449\u043e\u0431 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437\u0456 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c QR-\u043a\u043e\u0434\u0443, \u0432\u0438\u043a\u043e\u043d\u0430\u0439\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u043a\u043e\u0434\u0443 ** `{code}` **.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c TOTP" + } + }, + "title": "TOTP" } } } \ No newline at end of file diff --git a/homeassistant/components/awair/translations/de.json b/homeassistant/components/awair/translations/de.json index fcdcd0190e3ba2..5b65ece083b069 100644 --- a/homeassistant/components/awair/translations/de.json +++ b/homeassistant/components/awair/translations/de.json @@ -1,17 +1,25 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { - "unknown": "Unbekannter Awair-API-Fehler." + "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", + "unknown": "Unerwarteter Fehler" }, "step": { "reauth": { "data": { + "access_token": "Zugangstoken", "email": "E-Mail" }, - "description": "Bitte geben Sie Ihr Awair-Entwicklerzugriffstoken erneut ein." + "description": "Bitte gib dein Awair-Entwicklerzugriffstoken erneut ein." }, "user": { "data": { + "access_token": "Zugangstoken", "email": "E-Mail" } } diff --git a/homeassistant/components/awair/translations/tr.json b/homeassistant/components/awair/translations/tr.json new file mode 100644 index 00000000000000..84da92b97d39f3 --- /dev/null +++ b/homeassistant/components/awair/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "email": "E-posta" + } + }, + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "email": "E-posta" + }, + "description": "Awair geli\u015ftirici eri\u015fim belirteci i\u00e7in \u015fu adresten kaydolmal\u0131s\u0131n\u0131z: https://developer.getawair.com/onboard/login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/uk.json b/homeassistant/components/awair/translations/uk.json new file mode 100644 index 00000000000000..f8150ad7faf53f --- /dev/null +++ b/homeassistant/components/awair/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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.", + "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" + }, + "error": { + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "reauth": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0412\u0430\u0448 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443." + }, + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0434\u043e Awair \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: https://developer.getawair.com/onboard/login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/ca.json b/homeassistant/components/axis/translations/ca.json index 26da6057dc1c41..3e104c1005e101 100644 --- a/homeassistant/components/axis/translations/ca.json +++ b/homeassistant/components/axis/translations/ca.json @@ -11,7 +11,7 @@ "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" }, - "flow_title": "Dispositiu d'eix: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/cs.json b/homeassistant/components/axis/translations/cs.json index fd99c68ab351ab..4f7c30162354f9 100644 --- a/homeassistant/components/axis/translations/cs.json +++ b/homeassistant/components/axis/translations/cs.json @@ -11,7 +11,7 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, - "flow_title": "Za\u0159\u00edzen\u00ed Axis: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/de.json b/homeassistant/components/axis/translations/de.json index 4706350cdb3edb..1f6aedf5d9c7e4 100644 --- a/homeassistant/components/axis/translations/de.json +++ b/homeassistant/components/axis/translations/de.json @@ -7,8 +7,9 @@ }, "error": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "flow_title": "Achsenger\u00e4t: {name} ({host})", "step": { diff --git a/homeassistant/components/axis/translations/en.json b/homeassistant/components/axis/translations/en.json index 6b01533aefa856..f71e91f6280784 100644 --- a/homeassistant/components/axis/translations/en.json +++ b/homeassistant/components/axis/translations/en.json @@ -11,7 +11,7 @@ "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication" }, - "flow_title": "Axis device: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/et.json b/homeassistant/components/axis/translations/et.json index 6a27e74b28705a..f6f9a523cb6fba 100644 --- a/homeassistant/components/axis/translations/et.json +++ b/homeassistant/components/axis/translations/et.json @@ -11,7 +11,7 @@ "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Tuvastamise viga" }, - "flow_title": "Axise seade: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/it.json b/homeassistant/components/axis/translations/it.json index 6461b2a6619dc8..7e7aeb1d1b2612 100644 --- a/homeassistant/components/axis/translations/it.json +++ b/homeassistant/components/axis/translations/it.json @@ -11,7 +11,7 @@ "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida" }, - "flow_title": "Dispositivo Axis: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/no.json b/homeassistant/components/axis/translations/no.json index 984d522eba93b8..1fc0640eb9b667 100644 --- a/homeassistant/components/axis/translations/no.json +++ b/homeassistant/components/axis/translations/no.json @@ -11,7 +11,7 @@ "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning" }, - "flow_title": "Axis enhet: {name} ({host})", + "flow_title": "{name} ( {host} )", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/pl.json b/homeassistant/components/axis/translations/pl.json index 84af845ab31dc3..e44816bc2ea93a 100644 --- a/homeassistant/components/axis/translations/pl.json +++ b/homeassistant/components/axis/translations/pl.json @@ -11,7 +11,7 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie" }, - "flow_title": "Urz\u0105dzenie Axis: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index ee1dc8494f3b45..6d979dc9de086d 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -11,7 +11,7 @@ "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." }, - "flow_title": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Axis {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/tr.json b/homeassistant/components/axis/translations/tr.json new file mode 100644 index 00000000000000..b2d609747d1429 --- /dev/null +++ b/homeassistant/components/axis/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/uk.json b/homeassistant/components/axis/translations/uk.json new file mode 100644 index 00000000000000..35b849ce9689ba --- /dev/null +++ b/homeassistant/components/axis/translations/uk.json @@ -0,0 +1,37 @@ +{ + "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.", + "link_local_address": "\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456 \u0430\u0434\u0440\u0435\u0441\u0438 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "not_axis_device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Axis." + }, + "error": { + "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.", + "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." + }, + "flow_title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Axis {name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Axis" + } + } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u043e\u0444\u0456\u043b\u044c \u043f\u043e\u0442\u043e\u043a\u0443 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0432\u0456\u0434\u0435\u043e\u043f\u043e\u0442\u043e\u043a\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Axis" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/zh-Hant.json b/homeassistant/components/axis/translations/zh-Hant.json index 1d7aaa7c74ed5a..293f08c5f05961 100644 --- a/homeassistant/components/axis/translations/zh-Hant.json +++ b/homeassistant/components/axis/translations/zh-Hant.json @@ -11,7 +11,7 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" }, - "flow_title": "Axis \u88dd\u7f6e\uff1a{name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/azure_devops/translations/de.json b/homeassistant/components/azure_devops/translations/de.json index 1c940ea7a359a5..e7d9e073ec617c 100644 --- a/homeassistant/components/azure_devops/translations/de.json +++ b/homeassistant/components/azure_devops/translations/de.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "reauth": { diff --git a/homeassistant/components/azure_devops/translations/fr.json b/homeassistant/components/azure_devops/translations/fr.json index edcf3dda517de0..5e62d54ec1d44f 100644 --- a/homeassistant/components/azure_devops/translations/fr.json +++ b/homeassistant/components/azure_devops/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s" }, "error": { diff --git a/homeassistant/components/azure_devops/translations/tr.json b/homeassistant/components/azure_devops/translations/tr.json new file mode 100644 index 00000000000000..11a15956f635b3 --- /dev/null +++ b/homeassistant/components/azure_devops/translations/tr.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap 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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "project_error": "Proje bilgileri al\u0131namad\u0131." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "title": "Yeniden kimlik do\u011frulama" + }, + "user": { + "data": { + "organization": "Organizasyon", + "personal_access_token": "Ki\u015fisel Eri\u015fim Belirteci (PAT)", + "project": "Proje" + }, + "description": "Projenize eri\u015fmek i\u00e7in bir Azure DevOps \u00f6rne\u011fi ayarlay\u0131n. Ki\u015fisel Eri\u015fim Jetonu yaln\u0131zca \u00f6zel bir proje i\u00e7in gereklidir.", + "title": "Azure DevOps Projesi Ekle" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/uk.json b/homeassistant/components/azure_devops/translations/uk.json index 4a42fd17fc3933..848528f444e1c0 100644 --- a/homeassistant/components/azure_devops/translations/uk.json +++ b/homeassistant/components/azure_devops/translations/uk.json @@ -1,16 +1,30 @@ { "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "error": { + "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.", + "project_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0440\u043e\u0435\u043a\u0442." + }, "flow_title": "Azure DevOps: {project_url}", "step": { "reauth": { + "data": { + "personal_access_token": "\u041f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 (PAT)" + }, + "description": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 {project_url} . \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" }, "user": { "data": { "organization": "\u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0446\u0456\u044f", "personal_access_token": "\u0422\u043e\u043a\u0435\u043d \u043e\u0441\u043e\u0431\u0438\u0441\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0443 (PAT)", - "project": "\u041f\u0440\u043e\u0454\u043a\u0442" + "project": "\u041f\u0440\u043e\u0435\u043a\u0442" }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c Azure DevOps. \u041f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0432\u043e\u0434\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0434\u043b\u044f \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0456\u0432.", "title": "\u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442 Azure DevOps" } } diff --git a/homeassistant/components/binary_sensor/translations/de.json b/homeassistant/components/binary_sensor/translations/de.json index 3687536eb5b663..a78befb7965e4a 100644 --- a/homeassistant/components/binary_sensor/translations/de.json +++ b/homeassistant/components/binary_sensor/translations/de.json @@ -98,6 +98,10 @@ "off": "Normal", "on": "Schwach" }, + "battery_charging": { + "off": "L\u00e4dt nicht", + "on": "L\u00e4dt" + }, "cold": { "off": "Normal", "on": "Kalt" @@ -122,6 +126,10 @@ "off": "Normal", "on": "Hei\u00df" }, + "light": { + "off": "Kein Licht", + "on": "Licht erkannt" + }, "lock": { "off": "Verriegelt", "on": "Entriegelt" @@ -134,6 +142,10 @@ "off": "Ruhig", "on": "Bewegung erkannt" }, + "moving": { + "off": "Bewegt sich nicht", + "on": "Bewegt sich" + }, "occupancy": { "off": "Frei", "on": "Belegt" @@ -142,6 +154,10 @@ "off": "Geschlossen", "on": "Offen" }, + "plug": { + "off": "Ausgesteckt", + "on": "Eingesteckt" + }, "presence": { "off": "Abwesend", "on": "Zu Hause" diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index 7d6e8eab4ba96c..726765aea0255d 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -99,7 +99,7 @@ "on": "roz\u0142adowana" }, "battery_charging": { - "off": "nie \u0142aduje", + "off": "roz\u0142adowywanie", "on": "\u0142adowanie" }, "cold": { diff --git a/homeassistant/components/binary_sensor/translations/tr.json b/homeassistant/components/binary_sensor/translations/tr.json index 3c5cfaeeacf07c..94e1496cc30b7a 100644 --- a/homeassistant/components/binary_sensor/translations/tr.json +++ b/homeassistant/components/binary_sensor/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "moist": "{entity_name} nemli oldu", + "not_opened": "{entity_name} kapat\u0131ld\u0131" + } + }, "state": { "_": { "off": "Kapal\u0131", @@ -8,6 +14,10 @@ "off": "Normal", "on": "D\u00fc\u015f\u00fck" }, + "battery_charging": { + "off": "\u015earj olmuyor", + "on": "\u015earj Oluyor" + }, "cold": { "off": "Normal", "on": "So\u011fuk" @@ -32,6 +42,10 @@ "off": "Normal", "on": "S\u0131cak" }, + "light": { + "off": "I\u015f\u0131k yok", + "on": "I\u015f\u0131k alg\u0131land\u0131" + }, "lock": { "off": "Kilit kapal\u0131", "on": "Kilit a\u00e7\u0131k" @@ -44,6 +58,10 @@ "off": "Temiz", "on": "Alg\u0131land\u0131" }, + "moving": { + "off": "Hareket etmiyor", + "on": "Hareketli" + }, "occupancy": { "off": "Temiz", "on": "Alg\u0131land\u0131" @@ -52,6 +70,10 @@ "off": "Kapal\u0131", "on": "A\u00e7\u0131k" }, + "plug": { + "off": "Fi\u015fi \u00e7ekildi", + "on": "Tak\u0131l\u0131" + }, "presence": { "off": "[%key:common::state::evde_degil%]", "on": "[%key:common::state::evde%]" diff --git a/homeassistant/components/binary_sensor/translations/uk.json b/homeassistant/components/binary_sensor/translations/uk.json index 29767f6d6d62e5..0f8d92749c4cb1 100644 --- a/homeassistant/components/binary_sensor/translations/uk.json +++ b/homeassistant/components/binary_sensor/translations/uk.json @@ -2,15 +2,91 @@ "device_automation": { "condition_type": { "is_bat_low": "{entity_name} \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0440\u0456\u0432\u0435\u043d\u044c \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430", - "is_not_bat_low": "{entity_name} \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u043c \u0437\u0430\u0440\u044f\u0434 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430" + "is_cold": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "is_connected": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_gas": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0433\u0430\u0437", + "is_hot": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043d\u0430\u0433\u0440\u0456\u0432", + "is_light": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0441\u0432\u0456\u0442\u043b\u043e", + "is_locked": "{entity_name} \u0432 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_moist": "{entity_name} \u0432 \u0441\u0442\u0430\u043d\u0456 \"\u0412\u043e\u043b\u043e\u0433\u043e\"", + "is_motion": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0440\u0443\u0445", + "is_moving": "{entity_name} \u043f\u0435\u0440\u0435\u043c\u0456\u0449\u0443\u0454\u0442\u044c\u0441\u044f", + "is_no_gas": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0433\u0430\u0437", + "is_no_light": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0441\u0432\u0456\u0442\u043b\u043e", + "is_no_motion": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0440\u0443\u0445", + "is_no_problem": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c", + "is_no_smoke": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0434\u0438\u043c", + "is_no_sound": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0437\u0432\u0443\u043a", + "is_no_vibration": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e", + "is_not_bat_low": "{entity_name} \u0432 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_not_cold": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "is_not_connected": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_not_hot": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043d\u0430\u0433\u0440\u0456\u0432", + "is_not_locked": "{entity_name} \u0432 \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_not_moist": "{entity_name} \u0432 \u0441\u0442\u0430\u043d\u0456 \"\u0421\u0443\u0445\u043e\"", + "is_not_moving": "{entity_name} \u043d\u0435 \u0440\u0443\u0445\u0430\u0454\u0442\u044c\u0441\u044f", + "is_not_occupied": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_not_open": "{entity_name} \u0432 \u0437\u0430\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_not_plugged_in": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_not_powered": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "is_not_present": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_not_unsafe": "{entity_name} \u0432 \u0431\u0435\u0437\u043f\u0435\u0447\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_occupied": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_open": "{entity_name} \u0443 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_plugged_in": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "is_powered": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "is_present": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "is_problem": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443", + "is_smoke": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0434\u0438\u043c", + "is_sound": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0437\u0432\u0443\u043a", + "is_unsafe": "{entity_name} \u0432 \u043d\u0435\u0431\u0435\u0437\u043f\u0435\u0447\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_vibration": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e" }, "trigger_type": { - "bat_low": "{entity_name} \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0437\u0430\u0440\u044f\u0434 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430", - "not_bat_low": "{entity_name} \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440", + "bat_low": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0437\u0430\u0440\u044f\u0434", + "cold": "{entity_name} \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0443\u0454\u0442\u044c\u0441\u044f", + "connected": "{entity_name} \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u0454\u0442\u044c\u0441\u044f", + "gas": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0433\u0430\u0437", + "hot": "{entity_name} \u043d\u0430\u0433\u0440\u0456\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "light": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u043e", + "locked": "{entity_name} \u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f", + "moist": "{entity_name} \u0441\u0442\u0430\u0454 \u0432\u043e\u043b\u043e\u0433\u0438\u043c", + "motion": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0440\u0443\u0445", + "moving": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u0440\u0443\u0445\u0430\u0442\u0438\u0441\u044f", + "no_gas": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0433\u0430\u0437", + "no_light": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0441\u0432\u0456\u0442\u043b\u043e", + "no_motion": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0440\u0443\u0445", + "no_problem": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443", + "no_smoke": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0434\u0438\u043c", + "no_sound": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0437\u0432\u0443\u043a", + "no_vibration": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u0432\u0438\u044f\u0432\u043b\u044f\u0442\u0438 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e", + "not_bat_low": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u0437\u0430\u0440\u044f\u0434", + "not_cold": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0443\u0432\u0430\u0442\u0438\u0441\u044f", + "not_connected": "{entity_name} \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u0454\u0442\u044c\u0441\u044f", + "not_hot": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u043d\u0430\u0433\u0440\u0456\u0432\u0430\u0442\u0438\u0441\u044f", + "not_locked": "{entity_name} \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f", + "not_moist": "{entity_name} \u0441\u0442\u0430\u0454 \u0441\u0443\u0445\u0438\u043c", + "not_moving": "{entity_name} \u043f\u0440\u0438\u043f\u0438\u043d\u044f\u0454 \u043f\u0435\u0440\u0435\u043c\u0456\u0449\u0435\u043d\u043d\u044f", + "not_occupied": "{entity_name} \u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", "not_opened": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0442\u043e", + "not_plugged_in": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "not_powered": "{entity_name} \u043d\u0435 \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "not_present": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0432\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "not_unsafe": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0431\u0435\u0437\u043f\u0435\u043a\u0443", + "occupied": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e", + "plugged_in": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "powered": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f", + "present": "{entity_name} \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c", + "problem": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443", + "smoke": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0434\u0438\u043c", + "sound": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0437\u0432\u0443\u043a", "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", - "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", + "unsafe": "{entity_name} \u043d\u0435 \u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0454 \u0431\u0435\u0437\u043f\u0435\u043a\u0443", + "vibration": "{entity_name} \u0432\u0438\u044f\u0432\u043b\u044f\u0454 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044e" } }, "state": { @@ -22,6 +98,10 @@ "off": "\u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439", "on": "\u041d\u0438\u0437\u044c\u043a\u0438\u0439" }, + "battery_charging": { + "off": "\u041d\u0435 \u0437\u0430\u0440\u044f\u0434\u0436\u0430\u0454\u0442\u044c\u0441\u044f", + "on": "\u0417\u0430\u0440\u044f\u0434\u0436\u0430\u043d\u043d\u044f" + }, "cold": { "off": "\u041d\u043e\u0440\u043c\u0430", "on": "\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f" @@ -46,6 +126,10 @@ "off": "\u041d\u043e\u0440\u043c\u0430", "on": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f" }, + "light": { + "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + }, "lock": { "off": "\u0417\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e", "on": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e" @@ -58,13 +142,21 @@ "off": "\u041d\u0435\u043c\u0430\u0454 \u0440\u0443\u0445\u0443", "on": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0440\u0443\u0445" }, + "moving": { + "off": "\u0420\u0443\u0445\u0443 \u043d\u0435\u043c\u0430\u0454", + "on": "\u0420\u0443\u0445\u0430\u0454\u0442\u044c\u0441\u044f" + }, "occupancy": { "off": "\u0427\u0438\u0441\u0442\u043e", "on": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c" }, "opening": { - "off": "\u0417\u0430\u043a\u0440\u0438\u0442\u043e", - "on": "\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438\u0439" + "off": "\u0417\u0430\u0447\u0438\u043d\u0435\u043d\u043e", + "on": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u043e" + }, + "plug": { + "off": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "on": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e" }, "presence": { "off": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430", @@ -91,8 +183,8 @@ "on": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u0430 \u0432\u0456\u0431\u0440\u0430\u0446\u0456\u044f" }, "window": { - "off": "\u0417\u0430\u0447\u0438\u043d\u0435\u043d\u0435", - "on": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u0435" + "off": "\u0417\u0430\u0447\u0438\u043d\u0435\u043d\u043e", + "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" diff --git a/homeassistant/components/blebox/translations/de.json b/homeassistant/components/blebox/translations/de.json index baf14ba4897c0f..37c8dde54e5e4d 100644 --- a/homeassistant/components/blebox/translations/de.json +++ b/homeassistant/components/blebox/translations/de.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "Ein BleBox-Ger\u00e4t ist bereits unter {address} konfiguriert.", - "already_configured": "Dieses BleBox-Ger\u00e4t ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung mit dem BleBox-Ger\u00e4t nicht m\u00f6glich. (\u00dcberpr\u00fcfen Sie die Protokolle auf Fehler).", - "unknown": "Unbekannter Fehler beim Anschlie\u00dfen an das BleBox-Ger\u00e4t. (Pr\u00fcfen Sie die Protokolle auf Fehler).", + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler", "unsupported_version": "Das BleBox-Ger\u00e4t hat eine veraltete Firmware. Bitte aktualisieren Sie es zuerst." }, "flow_title": "BleBox-Ger\u00e4t: {name} ( {host} )", diff --git a/homeassistant/components/blebox/translations/tr.json b/homeassistant/components/blebox/translations/tr.json new file mode 100644 index 00000000000000..31df3fb5e3074e --- /dev/null +++ b/homeassistant/components/blebox/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "address_already_configured": "Bir BleBox cihaz\u0131 zaten {address} yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r.", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/uk.json b/homeassistant/components/blebox/translations/uk.json new file mode 100644 index 00000000000000..fb10807acff132 --- /dev/null +++ b/homeassistant/components/blebox/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "address_already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 \u0430\u0434\u0440\u0435\u0441\u043e\u044e {address} \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "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." + }, + "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", + "unsupported_version": "\u041c\u0456\u043a\u0440\u043e\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0430 \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437\u0430\u0441\u0442\u0430\u0440\u0456\u043b\u0430. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u0457\u0457." + }, + "flow_title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 BleBox: {name} ({host})", + "step": { + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 BleBox.", + "title": "BleBox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/de.json b/homeassistant/components/blink/translations/de.json index f5116110a09896..d4f65329f9b076 100644 --- a/homeassistant/components/blink/translations/de.json +++ b/homeassistant/components/blink/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, @@ -13,7 +14,7 @@ "data": { "2fa": "Zwei-Faktor Authentifizierungscode" }, - "description": "Geben Sie die an Ihre E-Mail gesendete Pin ein. Wenn die E-Mail keine PIN enth\u00e4lt, lassen Sie das Feld leer.", + "description": "Gib die an deine E-Mail gesendete Pin ein. Wenn die E-Mail keine PIN enth\u00e4lt, lass das Feld leer.", "title": "Zwei-Faktor-Authentifizierung" }, "user": { diff --git a/homeassistant/components/blink/translations/tr.json b/homeassistant/components/blink/translations/tr.json new file mode 100644 index 00000000000000..8193ff9d8bee78 --- /dev/null +++ b/homeassistant/components/blink/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "2fa": { + "description": "E-postan\u0131za g\u00f6nderilen PIN kodunu girin" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/uk.json b/homeassistant/components/blink/translations/uk.json new file mode 100644 index 00000000000000..c45bf7b66517e2 --- /dev/null +++ b/homeassistant/components/blink/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041a\u043e\u0434 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434, \u043d\u0430\u0434\u0456\u0441\u043b\u0430\u043d\u0438\u0439 \u043d\u0430 \u0412\u0430\u0448\u0443 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443 \u043f\u043e\u0448\u0442\u0443", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Blink" + } + } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Blink", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Blink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/ca.json b/homeassistant/components/bmw_connected_drive/translations/ca.json new file mode 100644 index 00000000000000..d6bd70064c3271 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/ca.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "region": "Regi\u00f3 de ConnectedDrive", + "username": "Nom d'usuari" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Nom\u00e9s de lectura (nom\u00e9s sensors i notificacions, sense execuci\u00f3 de serveis, sense bloqueig)", + "use_location": "Utilitza la ubicaci\u00f3 de Home Assistant per a les crides de localitzaci\u00f3 del cotxe (obligatori per a vehicles que no siguin i3/i8 produ\u00efts abans del 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/cs.json b/homeassistant/components/bmw_connected_drive/translations/cs.json new file mode 100644 index 00000000000000..665dccd443db71 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/de.json b/homeassistant/components/bmw_connected_drive/translations/de.json new file mode 100644 index 00000000000000..12a870b4cc9181 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/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/bmw_connected_drive/translations/en.json b/homeassistant/components/bmw_connected_drive/translations/en.json index f194c8a344448a..dedd84d070b5c1 100644 --- a/homeassistant/components/bmw_connected_drive/translations/en.json +++ b/homeassistant/components/bmw_connected_drive/translations/en.json @@ -11,7 +11,6 @@ "user": { "data": { "password": "Password", - "read_only": "Read-only", "region": "ConnectedDrive Region", "username": "Username" } diff --git a/homeassistant/components/bmw_connected_drive/translations/es.json b/homeassistant/components/bmw_connected_drive/translations/es.json new file mode 100644 index 00000000000000..65ed9643f890f2 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "region": "Regi\u00f3n de ConnectedDrive", + "username": "Nombre de usuario" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "S\u00f3lo lectura (s\u00f3lo sensores y notificaci\u00f3n, sin ejecuci\u00f3n de servicios, sin bloqueo)", + "use_location": "Usar la ubicaci\u00f3n de Home Assistant para las encuestas de localizaci\u00f3n de autom\u00f3viles (necesario para los veh\u00edculos no i3/i8 producidos antes del 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/et.json b/homeassistant/components/bmw_connected_drive/translations/et.json new file mode 100644 index 00000000000000..f28209a1e7a3fb --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "region": "ConnectedDrive'i piirkond", + "username": "Kasutajanimi" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Kirjutuskaitstud (ainult andurid ja teavitused, ei k\u00e4ivita teenuseid, kood puudub)", + "use_location": "Kasuta HA asukohta auto asukoha k\u00fcsitluste jaoks (n\u00f5utav enne 7/2014 toodetud muude kui i3 / i8 s\u00f5idukite jaoks)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/fr.json b/homeassistant/components/bmw_connected_drive/translations/fr.json new file mode 100644 index 00000000000000..1b8f562669fb5f --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/fr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec \u00e0 la connexion", + "invalid_auth": "Authentification invalide" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "region": "R\u00e9gion ConnectedDrive", + "username": "Nom d'utilisateur" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Lecture seule (uniquement capteurs et notification, pas d'ex\u00e9cution de services, pas de verrouillage)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/it.json b/homeassistant/components/bmw_connected_drive/translations/it.json new file mode 100644 index 00000000000000..277ed189c43ced --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/it.json @@ -0,0 +1,30 @@ +{ + "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", + "region": "Regione ConnectedDrive", + "username": "Nome utente" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Sola lettura (solo sensori e notifica, nessuna esecuzione di servizi, nessun blocco)", + "use_location": "Usa la posizione di Home Assistant per richieste sulla posizione dell'auto (richiesto per veicoli non i3/i8 prodotti prima del 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/lb.json b/homeassistant/components/bmw_connected_drive/translations/lb.json new file mode 100644 index 00000000000000..9ebbe919f8badf --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/lb.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Kont ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun" + }, + "step": { + "user": { + "data": { + "password": "Passwuert", + "region": "ConnectedDrive Regioun" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/no.json b/homeassistant/components/bmw_connected_drive/translations/no.json new file mode 100644 index 00000000000000..f1715c550db4dc --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/no.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "region": "ConnectedDrive-region", + "username": "Brukernavn" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Skrivebeskyttet (bare sensorer og varsler, ingen utf\u00f8relse av tjenester, ingen l\u00e5s)", + "use_location": "Bruk Home Assistant plassering for avstemningssteder for biler (p\u00e5krevd for ikke i3 / i8-kj\u00f8ret\u00f8y produsert f\u00f8r 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/pl.json b/homeassistant/components/bmw_connected_drive/translations/pl.json new file mode 100644 index 00000000000000..70467c6f9b93ed --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/pl.json @@ -0,0 +1,30 @@ +{ + "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", + "region": "Region ConnectedDrive", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Tylko odczyt (tylko czujniki i powiadomienia, brak wykonywania us\u0142ug, brak blokady)", + "use_location": "U\u017cyj lokalizacji Home Assistant do sondowania lokalizacji samochodu (wymagane w przypadku pojazd\u00f3w innych ni\u017c i3/i8 wyprodukowanych przed 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/pt.json b/homeassistant/components/bmw_connected_drive/translations/pt.json new file mode 100644 index 00000000000000..3814c892bd16d5 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/ru.json b/homeassistant/components/bmw_connected_drive/translations/ru.json new file mode 100644 index 00000000000000..0840affcef4200 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/ru.json @@ -0,0 +1,30 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d ConnectedDrive", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u0422\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u0435\u043d\u0438\u0435 (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f, \u0431\u0435\u0437 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u043b\u0443\u0436\u0431, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438)", + "use_location": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Home Assistant \u0434\u043b\u044f \u043e\u043f\u0440\u043e\u0441\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439 (\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439 \u043d\u0435 i3/i8, \u0432\u044b\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u0434\u043e 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/tr.json b/homeassistant/components/bmw_connected_drive/translations/tr.json new file mode 100644 index 00000000000000..153aa4126b0668 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/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/bmw_connected_drive/translations/uk.json b/homeassistant/components/bmw_connected_drive/translations/uk.json new file mode 100644 index 00000000000000..68cdee2a66f9df --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "region": "ConnectedDrive Region", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u041b\u0438\u0448\u0435 \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u043d\u043d\u044f (\u043b\u0438\u0448\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0442\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f, \u0431\u0435\u0437 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 \u0441\u0435\u0440\u0432\u0456\u0441\u0456\u0432, \u0431\u0435\u0437 \u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f)", + "use_location": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f Home Assistant \u0434\u043b\u044f \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u044c \u043c\u0456\u0441\u0446\u044f \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0456\u043b\u0456\u0432 (\u043e\u0431\u043e\u0432\u2019\u044f\u0437\u043a\u043e\u0432\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0456\u043b\u0456\u0432, \u0449\u043e \u043d\u0435 \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e i3/i8, \u0432\u0438\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u0438\u0445 \u0434\u043e 7/2014)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json b/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json new file mode 100644 index 00000000000000..fde5e1e3c94e05 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/zh-Hant.json @@ -0,0 +1,30 @@ +{ + "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", + "region": "ConnectedDrive \u5340\u57df", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\u552f\u8b80\uff08\u50c5\u652f\u63f4\u50b3\u611f\u5668\u8207\u901a\u77e5\uff0c\u4e0d\n\u5305\u542b\u670d\u52d9\u8207\u9396\u5b9a\uff09", + "use_location": "\u4f7f\u7528 Home Assistant \u4f4d\u7f6e\u53d6\u5f97\u6c7d\u8eca\u4f4d\u7f6e\uff08\u9700\u8981\u70ba2014/7 \u524d\u751f\u7522\u7684\u975ei3/i8 \u8eca\u6b3e\uff09" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/de.json b/homeassistant/components/bond/translations/de.json index 393232025ddc1f..14f86a30bb285f 100644 --- a/homeassistant/components/bond/translations/de.json +++ b/homeassistant/components/bond/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindung nicht m\u00f6glich", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/bond/translations/tr.json b/homeassistant/components/bond/translations/tr.json new file mode 100644 index 00000000000000..3488480a21845a --- /dev/null +++ b/homeassistant/components/bond/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "confirm": { + "data": { + "access_token": "Eri\u015fim Belirteci" + } + }, + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/uk.json b/homeassistant/components/bond/translations/uk.json index d7da60ea1786af..95ede3d43298ef 100644 --- a/homeassistant/components/bond/translations/uk.json +++ b/homeassistant/components/bond/translations/uk.json @@ -1,7 +1,28 @@ { "config": { "abort": { - "already_configured": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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." + }, + "error": { + "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.", + "old_firmware": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043e\u043d\u043e\u0432\u0438\u0442\u0438 \u043c\u0456\u043a\u0440\u043e\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u043d\u0430 \u0432\u0435\u0440\u0441\u0456\u044f \u0437\u0430\u0441\u0442\u0430\u0440\u0456\u043b\u0430 \u0456 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0454\u044e.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Bond {bond_id} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {bond_id}?" + }, + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "host": "\u0425\u043e\u0441\u0442" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/de.json b/homeassistant/components/braviatv/translations/de.json index b17d42ffaed465..8ac8c09e4fedc3 100644 --- a/homeassistant/components/braviatv/translations/de.json +++ b/homeassistant/components/braviatv/translations/de.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "Dieser Fernseher ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, ung\u00fcltiger Host- oder PIN-Code.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", "unsupported_model": "Ihr TV-Modell wird nicht unterst\u00fctzt." }, "step": { "authorize": { + "data": { + "pin": "PIN-Code" + }, "description": "Geben Sie den auf dem Sony Bravia-Fernseher angezeigten PIN-Code ein. \n\nWenn der PIN-Code nicht angezeigt wird, m\u00fcssen Sie die Registrierung von Home Assistant auf Ihrem Fernseher aufheben, gehen Sie daf\u00fcr zu: Einstellungen -> Netzwerk -> Remote - Ger\u00e4teeinstellungen -> Registrierung des entfernten Ger\u00e4ts aufheben.", "title": "Autorisieren Sie Sony Bravia TV" }, diff --git a/homeassistant/components/braviatv/translations/tr.json b/homeassistant/components/braviatv/translations/tr.json new file mode 100644 index 00000000000000..0853c8028fcb96 --- /dev/null +++ b/homeassistant/components/braviatv/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unsupported_model": "TV modeliniz desteklenmiyor." + }, + "step": { + "authorize": { + "title": "Sony Bravia TV'yi yetkilendirin" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "title": "Sony Bravia TV" + } + } + }, + "options": { + "step": { + "user": { + "title": "Sony Bravia TV i\u00e7in se\u00e7enekler" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/uk.json b/homeassistant/components/braviatv/translations/uk.json new file mode 100644 index 00000000000000..7f66329c57ec6b --- /dev/null +++ b/homeassistant/components/braviatv/translations/uk.json @@ -0,0 +1,39 @@ +{ + "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.", + "no_ip_control": "\u041d\u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0456 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e IP, \u0430\u0431\u043e \u0446\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\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", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unsupported_model": "\u0426\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "step": { + "authorize": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434, \u044f\u043a\u0438\u0439 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Sony Bravia. \n\n\u042f\u043a\u0449\u043e \u0412\u0438 \u043d\u0435 \u0431\u0430\u0447\u0438\u0442\u0435 PIN-\u043a\u043e\u0434, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0441\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438 \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u044e Home Assistant \u043d\u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0456. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 - > \u041c\u0435\u0440\u0435\u0436\u0430 - > \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e - > \u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438 \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u044e \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Sony Bravia" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457:\nhttps://www.home-assistant.io/integrations/braviatv", + "title": "\u0422\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Sony Bravia" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "\u0421\u043f\u0438\u0441\u043e\u043a \u0456\u0433\u043d\u043e\u0440\u043e\u0432\u0430\u043d\u0438\u0445 \u0434\u0436\u0435\u0440\u0435\u043b" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Sony Bravia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/de.json b/homeassistant/components/broadlink/translations/de.json index f915040635f53a..5704efe37c623f 100644 --- a/homeassistant/components/broadlink/translations/de.json +++ b/homeassistant/components/broadlink/translations/de.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP Adresse", "not_supported": "Ger\u00e4t nicht unterst\u00fctzt", "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP Adresse", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/broadlink/translations/tr.json b/homeassistant/components/broadlink/translations/tr.json new file mode 100644 index 00000000000000..d37a3203476704 --- /dev/null +++ b/homeassistant/components/broadlink/translations/tr.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_supported": "Cihaz desteklenmiyor", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "auth": { + "title": "Cihaza kimlik do\u011frulama" + }, + "finish": { + "title": "Cihaz i\u00e7in bir isim se\u00e7in" + }, + "reset": { + "title": "Cihaz\u0131n kilidini a\u00e7\u0131n" + }, + "unlock": { + "data": { + "unlock": "Evet, yap." + }, + "title": "Cihaz\u0131n kilidini a\u00e7\u0131n (iste\u011fe ba\u011fl\u0131)" + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "timeout": "Zaman a\u015f\u0131m\u0131" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/uk.json b/homeassistant/components/broadlink/translations/uk.json new file mode 100644 index 00000000000000..ea3e3e75cd6007 --- /dev/null +++ b/homeassistant/components/broadlink/translations/uk.json @@ -0,0 +1,47 @@ +{ + "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_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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "not_supported": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "{name} ({model}, {host})", + "step": { + "auth": { + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + }, + "finish": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c \u043d\u0430\u0437\u0432\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "reset": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 {name} ({model}, {host}) \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e. \u0414\u043e\u0442\u0440\u0438\u043c\u0443\u0439\u0442\u0435\u0441\u044f \u0446\u0438\u0445 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439, \u0449\u043e\u0431 \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438 \u0439\u043e\u0433\u043e:\n 1. \u0412\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0443 Broadlink.\n 2. \u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439.\n 3. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c `...` \u0432 \u043f\u0440\u0430\u0432\u043e\u043c\u0443 \u0432\u0435\u0440\u0445\u043d\u044c\u043e\u043c\u0443 \u043a\u0443\u0442\u0456.\n 4. \u041f\u0440\u043e\u043a\u0440\u0443\u0442\u0456\u0442\u044c \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0443 \u0432\u043d\u0438\u0437.\n 5. \u0412\u0438\u043c\u043a\u043d\u0456\u0442\u044c \u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f.", + "title": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "unlock": { + "data": { + "unlock": "\u0422\u0430\u043a, \u0437\u0440\u043e\u0431\u0438\u0442\u0438 \u0446\u0435." + }, + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 {name} ({model}, {host}) \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e. \u0426\u0435 \u043c\u043e\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u0434\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0437 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0454\u044e \u0432 Home Assistant. \u0425\u043e\u0447\u0435\u0442\u0435 \u0439\u043e\u0433\u043e \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438?", + "title": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/de.json b/homeassistant/components/brother/translations/de.json index 72bd052cc1d507..c2a7ae8ec76b77 100644 --- a/homeassistant/components/brother/translations/de.json +++ b/homeassistant/components/brother/translations/de.json @@ -5,7 +5,7 @@ "unsupported_model": "Dieses Druckermodell wird nicht unterst\u00fctzt." }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "snmp_error": "SNMP-Server deaktiviert oder Drucker nicht unterst\u00fctzt.", "wrong_host": " Ung\u00fcltiger Hostname oder IP-Adresse" }, diff --git a/homeassistant/components/brother/translations/tr.json b/homeassistant/components/brother/translations/tr.json index 160a5ecc7b78a2..cd91a4852527a3 100644 --- a/homeassistant/components/brother/translations/tr.json +++ b/homeassistant/components/brother/translations/tr.json @@ -1,6 +1,20 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unsupported_model": "Bu yaz\u0131c\u0131 modeli desteklenmiyor." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "Brother Yaz\u0131c\u0131: {model} {serial_number}", "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "type": "Yaz\u0131c\u0131n\u0131n t\u00fcr\u00fc" + } + }, "zeroconf_confirm": { "title": "Ke\u015ffedilen Brother Yaz\u0131c\u0131" } diff --git a/homeassistant/components/brother/translations/uk.json b/homeassistant/components/brother/translations/uk.json new file mode 100644 index 00000000000000..ac5943aa85cc15 --- /dev/null +++ b/homeassistant/components/brother/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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.", + "unsupported_model": "\u0426\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\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", + "snmp_error": "\u0421\u0435\u0440\u0432\u0435\u0440 SNMP \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u0438\u0439 \u0430\u0431\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "wrong_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430." + }, + "flow_title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 Brother: {model} {serial_number}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457: https://www.home-assistant.io/integrations/brother." + }, + "zeroconf_confirm": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" + }, + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u043d\u0442\u0435\u0440 Brother {model} \u0437 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440 Brother" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/de.json b/homeassistant/components/bsblan/translations/de.json index 5fd61c0bfedb6c..971e3c1ea8aaab 100644 --- a/homeassistant/components/bsblan/translations/de.json +++ b/homeassistant/components/bsblan/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/bsblan/translations/tr.json b/homeassistant/components/bsblan/translations/tr.json index 94acde2d0a3e46..803b5102a073c6 100644 --- a/homeassistant/components/bsblan/translations/tr.json +++ b/homeassistant/components/bsblan/translations/tr.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { "user": { "data": { + "host": "Ana Bilgisayar", "password": "\u015eifre", + "port": "Port", "username": "Kullan\u0131c\u0131 ad\u0131" } } diff --git a/homeassistant/components/bsblan/translations/uk.json b/homeassistant/components/bsblan/translations/uk.json new file mode 100644 index 00000000000000..619f7c8e8a580a --- /dev/null +++ b/homeassistant/components/bsblan/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "BSB-Lan: {name}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "passkey": "\u041f\u0430\u0440\u043e\u043b\u044c", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 BSB-Lan.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/de.json b/homeassistant/components/canary/translations/de.json index eebc9bd5fc3fd8..bdd746c314988c 100644 --- a/homeassistant/components/canary/translations/de.json +++ b/homeassistant/components/canary/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "Canary: {name}", "step": { diff --git a/homeassistant/components/canary/translations/tr.json b/homeassistant/components/canary/translations/tr.json new file mode 100644 index 00000000000000..6d18629b067921 --- /dev/null +++ b/homeassistant/components/canary/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/uk.json b/homeassistant/components/canary/translations/uk.json new file mode 100644 index 00000000000000..74327f3ebd672b --- /dev/null +++ b/homeassistant/components/canary/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0438, \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432 ffmpeg \u0434\u043b\u044f \u043a\u0430\u043c\u0435\u0440", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 \u0437\u0430\u043f\u0438\u0442\u0443 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/de.json b/homeassistant/components/cast/translations/de.json index 87f8e7cb2bc9d2..7ff1efb8ee0d81 100644 --- a/homeassistant/components/cast/translations/de.json +++ b/homeassistant/components/cast/translations/de.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Keine Google Cast Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von Google Cast ist notwendig." + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden.", + "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/cast/translations/tr.json b/homeassistant/components/cast/translations/tr.json new file mode 100644 index 00000000000000..8de4663957ea85 --- /dev/null +++ b/homeassistant/components/cast/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/uk.json b/homeassistant/components/cast/translations/uk.json index 783defdca258a3..292861e9129dbd 100644 --- a/homeassistant/components/cast/translations/uk.json +++ b/homeassistant/components/cast/translations/uk.json @@ -1,8 +1,12 @@ { "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." + }, "step": { "confirm": { - "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Google Cast?" + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" } } } diff --git a/homeassistant/components/cert_expiry/translations/tr.json b/homeassistant/components/cert_expiry/translations/tr.json new file mode 100644 index 00000000000000..6c05bef3a65f64 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/uk.json b/homeassistant/components/cert_expiry/translations/uk.json new file mode 100644 index 00000000000000..997e12a8cb29a9 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "import_failed": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0456\u043c\u043f\u043e\u0440\u0442\u0443 \u0437 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457." + }, + "error": { + "connection_refused": "\u041f\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u0456 \u0434\u043e \u0445\u043e\u0441\u0442\u0443 \u0431\u0443\u043b\u043e \u0432\u0456\u0434\u043c\u043e\u0432\u043b\u0435\u043d\u043e \u0432 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u0456.", + "connection_timeout": "\u0427\u0430\u0441 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0445\u043e\u0441\u0442\u0430 \u043c\u0438\u043d\u0443\u0432.", + "resolve_failed": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044c \u0434\u043e \u0445\u043e\u0441\u0442\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u0422\u0435\u0440\u043c\u0456\u043d \u0434\u0456\u0457 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430" + } + } + }, + "title": "\u0422\u0435\u0440\u043c\u0456\u043d \u0434\u0456\u0457 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/climate/translations/tr.json b/homeassistant/components/climate/translations/tr.json index 0b027dbd87faa2..201fec4c4b648d 100644 --- a/homeassistant/components/climate/translations/tr.json +++ b/homeassistant/components/climate/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "{entity_name} \u00fczerinde HVAC modunu de\u011fi\u015ftir", + "set_preset_mode": "{entity_name} \u00fczerindeki \u00f6n ayar\u0131 de\u011fi\u015ftir" + } + }, "state": { "_": { "auto": "Otomatik", diff --git a/homeassistant/components/climate/translations/uk.json b/homeassistant/components/climate/translations/uk.json index 8d636c386e5479..de6baff021cec4 100644 --- a/homeassistant/components/climate/translations/uk.json +++ b/homeassistant/components/climate/translations/uk.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "set_hvac_mode": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c HVAC \u043d\u0430 {entity_name}", - "set_preset_mode": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u043f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430 {entity_name}" + "set_hvac_mode": "{entity_name}: \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u0440\u043e\u0431\u043e\u0442\u0438", + "set_preset_mode": "{entity_name}: \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043f\u0440\u0435\u0441\u0435\u0442" }, "condition_type": { - "is_hvac_mode": "{entity_name} \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0432 \u043f\u0435\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c HVAC", + "is_hvac_mode": "{entity_name} \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0434\u0430\u043d\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456 \u0440\u043e\u0431\u043e\u0442\u0438", "is_preset_mode": "{entity_name} \u043d\u0430\u0441\u0442\u0440\u043e\u0454\u043d\u043e \u043d\u0430 \u043f\u0435\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c" }, "trigger_type": { - "current_humidity_changed": "{entity_name} \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u0430 \u0432\u043e\u043b\u043e\u0433\u0456\u0441\u0442\u044c \u0437\u043c\u0456\u043d\u0435\u043d\u0430", - "current_temperature_changed": "{entity_name} \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0443 \u0437\u043c\u0456\u043d\u0435\u043d\u043e", - "hvac_mode_changed": "{entity_name} \u0420\u0435\u0436\u0438\u043c HVAC \u0437\u043c\u0456\u043d\u0435\u043d\u043e" + "current_humidity_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u043e\u0457 \u0432\u043e\u043b\u043e\u0433\u043e\u0441\u0442\u0456", + "current_temperature_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u043e\u0457 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438", + "hvac_mode_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0440\u0435\u0436\u0438\u043c \u0440\u043e\u0431\u043e\u0442\u0438" } }, "state": { @@ -21,7 +21,7 @@ "dry": "\u041e\u0441\u0443\u0448\u0435\u043d\u043d\u044f", "fan_only": "\u041b\u0438\u0448\u0435 \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0442\u043e\u0440", "heat": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f", - "heat_cool": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f/\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "heat_cool": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f / \u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e" } }, diff --git a/homeassistant/components/cloud/translations/ca.json b/homeassistant/components/cloud/translations/ca.json index fede749c7dd935..4e6a14cd2f021e 100644 --- a/homeassistant/components/cloud/translations/ca.json +++ b/homeassistant/components/cloud/translations/ca.json @@ -2,9 +2,9 @@ "system_health": { "info": { "alexa_enabled": "Alexa activada", - "can_reach_cert_server": "Servidor de certificaci\u00f3 accessible", - "can_reach_cloud": "Home Assistant Cloud accessible", - "can_reach_cloud_auth": "Servidor d'autenticaci\u00f3 accessible", + "can_reach_cert_server": "Acc\u00e9s al servidor de certificaci\u00f3", + "can_reach_cloud": "Acc\u00e9s a Home Assistant Cloud", + "can_reach_cloud_auth": "Acc\u00e9s al servidor d'autenticaci\u00f3", "google_enabled": "Google activat", "logged_in": "Sessi\u00f3 iniciada", "relayer_connected": "Encaminador connectat", diff --git a/homeassistant/components/cloud/translations/de.json b/homeassistant/components/cloud/translations/de.json new file mode 100644 index 00000000000000..443a5e3aa72dde --- /dev/null +++ b/homeassistant/components/cloud/translations/de.json @@ -0,0 +1,15 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa aktiviert", + "can_reach_cert_server": "Zertifikatsserver erreichbar", + "can_reach_cloud": "Home Assistant Cloud erreichbar", + "can_reach_cloud_auth": "Authentifizierungsserver erreichbar", + "google_enabled": "Google aktiviert", + "logged_in": "Angemeldet", + "remote_connected": "Remote verbunden", + "remote_enabled": "Remote aktiviert", + "subscription_expiration": "Ablauf des Abonnements" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/fr.json b/homeassistant/components/cloud/translations/fr.json new file mode 100644 index 00000000000000..9bb4029fce0532 --- /dev/null +++ b/homeassistant/components/cloud/translations/fr.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa activ\u00e9", + "can_reach_cert_server": "Acc\u00e9der au serveur de certificats", + "can_reach_cloud": "Acc\u00e9der \u00e0 Home Assistant Cloud", + "can_reach_cloud_auth": "Acc\u00e9der au serveur d'authentification", + "google_enabled": "Google activ\u00e9", + "logged_in": "Connect\u00e9", + "relayer_connected": "Relais connect\u00e9", + "remote_connected": "Contr\u00f4le \u00e0 distance connect\u00e9", + "remote_enabled": "Contr\u00f4le \u00e0 distance activ\u00e9", + "subscription_expiration": "Expiration de l'abonnement" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/pl.json b/homeassistant/components/cloud/translations/pl.json index 30aaeeb77d14be..1df32a14d8e0e2 100644 --- a/homeassistant/components/cloud/translations/pl.json +++ b/homeassistant/components/cloud/translations/pl.json @@ -4,7 +4,7 @@ "alexa_enabled": "Alexa w\u0142\u0105czona", "can_reach_cert_server": "Dost\u0119p do serwera certyfikat\u00f3w", "can_reach_cloud": "Dost\u0119p do chmury Home Assistant", - "can_reach_cloud_auth": "Dost\u0119p do serwera uwierzytelniania", + "can_reach_cloud_auth": "Dost\u0119p do serwera certyfikat\u00f3w", "google_enabled": "Asystent Google w\u0142\u0105czony", "logged_in": "Zalogowany", "relayer_connected": "Relayer pod\u0142\u0105czony", diff --git a/homeassistant/components/cloud/translations/tr.json b/homeassistant/components/cloud/translations/tr.json index 0acb1e6a9a6632..75d1c768bebda4 100644 --- a/homeassistant/components/cloud/translations/tr.json +++ b/homeassistant/components/cloud/translations/tr.json @@ -1,6 +1,9 @@ { "system_health": { "info": { + "alexa_enabled": "Alexa Etkin", + "can_reach_cloud": "Home Assistant Cloud'a ula\u015f\u0131n", + "google_enabled": "Google Etkin", "logged_in": "Giri\u015f Yapt\u0131", "relayer_connected": "Yeniden Katman ba\u011fl\u0131", "remote_connected": "Uzaktan Ba\u011fl\u0131", diff --git a/homeassistant/components/cloud/translations/uk.json b/homeassistant/components/cloud/translations/uk.json new file mode 100644 index 00000000000000..a2e68b911e58f0 --- /dev/null +++ b/homeassistant/components/cloud/translations/uk.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 Alexa", + "can_reach_cert_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0456\u0432", + "can_reach_cloud": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e Home Assistant Cloud", + "can_reach_cloud_auth": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457", + "google_enabled": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 Google", + "logged_in": "\u0412\u0445\u0456\u0434 \u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443", + "relayer_connected": "Relayer \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439", + "remote_connected": "\u0412\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439", + "remote_enabled": "\u0412\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0438\u0439 \u0434\u043e\u0441\u0442\u0443\u043f \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u0438\u0439", + "subscription_expiration": "\u0422\u0435\u0440\u043c\u0456\u043d \u0434\u0456\u0457 \u043f\u0435\u0440\u0435\u0434\u043f\u043b\u0430\u0442\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/de.json b/homeassistant/components/cloudflare/translations/de.json index 809dad5da46e6c..d9858b36f55aff 100644 --- a/homeassistant/components/cloudflare/translations/de.json +++ b/homeassistant/components/cloudflare/translations/de.json @@ -1,12 +1,15 @@ { "config": { "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown": "Unerwarteter Fehler" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_zone": "Ung\u00fcltige Zone" }, + "flow_title": "Cloudflare: {name}", "step": { "records": { "data": { @@ -18,6 +21,11 @@ "api_token": "API Token" }, "title": "Mit Cloudflare verbinden" + }, + "zone": { + "data": { + "zone": "Zone" + } } } } diff --git a/homeassistant/components/cloudflare/translations/tr.json b/homeassistant/components/cloudflare/translations/tr.json index b7c7b438804b0d..5d1180961f6295 100644 --- a/homeassistant/components/cloudflare/translations/tr.json +++ b/homeassistant/components/cloudflare/translations/tr.json @@ -1,6 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown": "Beklenmeyen hata" + }, "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "invalid_zone": "Ge\u00e7ersiz b\u00f6lge" }, "flow_title": "Cloudflare: {name}", @@ -12,6 +18,9 @@ "title": "G\u00fcncellenecek Kay\u0131tlar\u0131 Se\u00e7in" }, "user": { + "data": { + "api_token": "API Belirteci" + }, "title": "Cloudflare'ye ba\u011flan\u0131n" }, "zone": { diff --git a/homeassistant/components/cloudflare/translations/uk.json b/homeassistant/components/cloudflare/translations/uk.json new file mode 100644 index 00000000000000..425ec2733b8f66 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "invalid_zone": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0437\u043e\u043d\u0430" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "\u0417\u0430\u043f\u0438\u0441\u0438" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043b\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "user": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0442\u043e\u043a\u0435\u043d API, \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0437 \u0434\u043e\u0437\u0432\u043e\u043b\u0430\u043c\u0438 Zone: Zone: Read \u0456 Zone: DNS: Edit \u0434\u043b\u044f \u0432\u0441\u0456\u0445 \u0437\u043e\u043d \u0443 \u0432\u0430\u0448\u043e\u043c\u0443 \u043f\u0440\u043e\u0444\u0456\u043b\u0456.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Cloudflare" + }, + "zone": { + "data": { + "zone": "\u0417\u043e\u043d\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0437\u043e\u043d\u0443 \u0434\u043b\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/de.json b/homeassistant/components/control4/translations/de.json index f9a5783cd9117e..399b8d424911e1 100644 --- a/homeassistant/components/control4/translations/de.json +++ b/homeassistant/components/control4/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/control4/translations/tr.json b/homeassistant/components/control4/translations/tr.json new file mode 100644 index 00000000000000..aed7e564a760b5 --- /dev/null +++ b/homeassistant/components/control4/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/uk.json b/homeassistant/components/control4/translations/uk.json index 6c0426eba8fb3c..682d86c5deb4da 100644 --- a/homeassistant/components/control4/translations/uk.json +++ b/homeassistant/components/control4/translations/uk.json @@ -1,10 +1,21 @@ { "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, "step": { "user": { "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" - } + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Control4 \u0456 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0412\u0430\u0448\u043e\u0433\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430." } } }, @@ -12,7 +23,7 @@ "step": { "init": { "data": { - "scan_interval": "\u0421\u0435\u043a\u0443\u043d\u0434 \u043c\u0456\u0436 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f\u043c\u0438" + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" } } } diff --git a/homeassistant/components/coolmaster/translations/de.json b/homeassistant/components/coolmaster/translations/de.json index 908dfaa448c1af..4e58b1ed964cd8 100644 --- a/homeassistant/components/coolmaster/translations/de.json +++ b/homeassistant/components/coolmaster/translations/de.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "no_units": "Es wurden keine HVAC-Ger\u00e4te im CoolMasterNet-Host gefunden." }, "step": { diff --git a/homeassistant/components/coolmaster/translations/tr.json b/homeassistant/components/coolmaster/translations/tr.json new file mode 100644 index 00000000000000..4848a34362cc3b --- /dev/null +++ b/homeassistant/components/coolmaster/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "off": "Kapat\u0131labilir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/uk.json b/homeassistant/components/coolmaster/translations/uk.json new file mode 100644 index 00000000000000..038a7bc48f0011 --- /dev/null +++ b/homeassistant/components/coolmaster/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "no_units": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f, \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0456\u0457 \u0442\u0430 \u043a\u043e\u043d\u0434\u0438\u0446\u0456\u043e\u043d\u0443\u0432\u0430\u043d\u043d\u044f." + }, + "step": { + "user": { + "data": { + "cool": "\u0420\u0435\u0436\u0438\u043c \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "dry": "\u0420\u0435\u0436\u0438\u043c \u043e\u0441\u0443\u0448\u0435\u043d\u043d\u044f", + "fan_only": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0456\u0457", + "heat": "\u0420\u0435\u0436\u0438\u043c \u043e\u0431\u0456\u0433\u0440\u0456\u0432\u0443", + "heat_cool": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "host": "\u0425\u043e\u0441\u0442", + "off": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "CoolMasterNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/tr.json b/homeassistant/components/coronavirus/translations/tr.json new file mode 100644 index 00000000000000..b608d60f824060 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "country": "\u00dclke" + }, + "title": "\u0130zlemek i\u00e7in bir \u00fclke se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/uk.json b/homeassistant/components/coronavirus/translations/uk.json new file mode 100644 index 00000000000000..151e7b14d3f0ca --- /dev/null +++ b/homeassistant/components/coronavirus/translations/uk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "country": "\u041a\u0440\u0430\u0457\u043d\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043a\u0440\u0430\u0457\u043d\u0443 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/translations/tr.json b/homeassistant/components/cover/translations/tr.json index 98bc8cdb18d7fa..f042233a6d1289 100644 --- a/homeassistant/components/cover/translations/tr.json +++ b/homeassistant/components/cover/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "action_type": { + "close": "{entity_name} kapat", + "open": "{entity_name} a\u00e7\u0131n" + } + }, "state": { "_": { "closed": "Kapal\u0131", diff --git a/homeassistant/components/cover/translations/uk.json b/homeassistant/components/cover/translations/uk.json index 66cd0c77c73c97..ceb49fff3e949d 100644 --- a/homeassistant/components/cover/translations/uk.json +++ b/homeassistant/components/cover/translations/uk.json @@ -1,10 +1,29 @@ { "device_automation": { "action_type": { + "close": "{entity_name}: \u0437\u0430\u043a\u0440\u0438\u0442\u0438", + "close_tilt": "{entity_name}: \u0437\u0430\u043a\u0440\u0438\u0442\u0438 \u043b\u0430\u043c\u0435\u043b\u0456", + "open": "{entity_name}: \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438", + "open_tilt": "{entity_name}: \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043b\u0430\u043c\u0435\u043b\u0456", + "set_position": "{entity_name}: \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044f", + "set_tilt_position": "{entity_name}: \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043d\u0430\u0445\u0438\u043b \u043b\u0430\u043c\u0435\u043b\u0435\u0439", "stop": "\u0417\u0443\u043f\u0438\u043d\u0438\u0442\u0438 {entity_name}" }, + "condition_type": { + "is_closed": "{entity_name} \u0432 \u0437\u0430\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_closing": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "is_open": "{entity_name} \u0443 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_opening": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "is_position": "{entity_name} \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u0456", + "is_tilt_position": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \"{entity_name}\" \u043c\u0430\u0454 \u043d\u0430\u0445\u0438\u043b \u043b\u0430\u043c\u0435\u043b\u0435\u0439" + }, "trigger_type": { - "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e" + "closed": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0442\u043e", + "closing": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e", + "opening": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "position": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u043d\u044f", + "tilt_position": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u043d\u0430\u0445\u0438\u043b \u043b\u0430\u043c\u0435\u043b\u0435\u0439" } }, "state": { diff --git a/homeassistant/components/daikin/translations/de.json b/homeassistant/components/daikin/translations/de.json index bbac113eb44487..dcec53c15690b1 100644 --- a/homeassistant/components/daikin/translations/de.json +++ b/homeassistant/components/daikin/translations/de.json @@ -2,15 +2,17 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "api_key": "API-Schl\u00fcssel", "host": "Host", "password": "Passwort" }, diff --git a/homeassistant/components/daikin/translations/pt.json b/homeassistant/components/daikin/translations/pt.json index dd9b538ae8b08a..d4188fb10f960f 100644 --- a/homeassistant/components/daikin/translations/pt.json +++ b/homeassistant/components/daikin/translations/pt.json @@ -16,7 +16,7 @@ "host": "Servidor", "password": "Palavra-passe" }, - "description": "Introduza o endere\u00e7o IP do seu Daikin AC.", + "description": "Introduza Endere\u00e7o IP do seu Daikin AC.\n\nAten\u00e7\u00e3o que [%chave:common::config_flow::data::api_key%] e Palavra-passe s\u00f3 s\u00e3o utilizador pelos dispositivos BRP072Cxx e SKYFi, respectivamente.", "title": "Configurar o Daikin AC" } } diff --git a/homeassistant/components/daikin/translations/tr.json b/homeassistant/components/daikin/translations/tr.json new file mode 100644 index 00000000000000..4148bf2b9f1c51 --- /dev/null +++ b/homeassistant/components/daikin/translations/tr.json @@ -0,0 +1,22 @@ +{ + "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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "host": "Ana Bilgisayar", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/uk.json b/homeassistant/components/daikin/translations/uk.json new file mode 100644 index 00000000000000..648d68d7a810e5 --- /dev/null +++ b/homeassistant/components/daikin/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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.", + "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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0412\u0430\u0448\u043e\u0433\u043e Daikin AC. \n\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u041a\u043b\u044e\u0447 API \u0456 \u041f\u0430\u0440\u043e\u043b\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044f\u043c\u0438 BRP072Cxx \u0456 SKYFi \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e.", + "title": "Daikin AC" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/cs.json b/homeassistant/components/deconz/translations/cs.json index 3ad72dc9ac9c65..52cbd607b7f9a8 100644 --- a/homeassistant/components/deconz/translations/cs.json +++ b/homeassistant/components/deconz/translations/cs.json @@ -60,14 +60,15 @@ }, "trigger_type": { "remote_awakened": "Za\u0159\u00edzen\u00ed probuzeno", - "remote_button_double_press": "Dvakr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_double_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dvakr\u00e1t", + "remote_button_long_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dlouze", "remote_button_long_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" po dlouh\u00e9m stisku", - "remote_button_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", - "remote_button_quintuple_press": "P\u011btkr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_quadruple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "remote_button_quintuple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto p\u011btkr\u00e1t", "remote_button_rotation_stopped": "Oto\u010den\u00ed tla\u010d\u00edtka \"{subtype}\" bylo zastaveno", - "remote_button_short_press": "Stiknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto", "remote_button_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\"", - "remote_button_triple_press": "T\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_triple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto t\u0159ikr\u00e1t", "remote_double_tap": "Dvakr\u00e1t poklep\u00e1no na za\u0159\u00edzen\u00ed \"{subtype}\"", "remote_double_tap_any_side": "Za\u0159\u00edzen\u00ed bylo poklep\u00e1no 2x na libovolnou stranu", "remote_flip_180_degrees": "Za\u0159\u00edzen\u00ed p\u0159evr\u00e1ceno o 180 stup\u0148\u016f", diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index f9448705c5dc67..d7553652412a09 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Bridge ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt.", - "no_bridges": "Keine deCON-Bridges entdeckt", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "no_bridges": "Keine deCONZ-Bridges entdeckt", + "no_hardware_available": "Keine Funkhardware an deCONZ angeschlossen", "not_deconz_bridge": "Keine deCONZ Bridge entdeckt", "updated_instance": "deCONZ-Instanz mit neuer Host-Adresse aktualisiert" }, @@ -13,7 +14,7 @@ "flow_title": "deCONZ Zigbee Gateway", "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Add-on hass.io {addon} bereitgestellt wird?", + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Hass.io Add-on {addon} bereitgestellt wird?", "title": "deCONZ Zigbee Gateway \u00fcber das Hass.io Add-on" }, "link": { @@ -28,7 +29,7 @@ }, "user": { "data": { - "host": "W\u00e4hlen Sie das erkannte deCONZ-Gateway aus" + "host": "W\u00e4hle das erkannte deCONZ-Gateway aus" } } } @@ -92,7 +93,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "deCONZ CLIP-Sensoren zulassen", - "allow_deconz_groups": "deCONZ-Lichtgruppen zulassen" + "allow_deconz_groups": "deCONZ-Lichtgruppen zulassen", + "allow_new_devices": "Automatisches Hinzuf\u00fcgen von neuen Ger\u00e4ten zulassen" }, "description": "Sichtbarkeit der deCONZ-Ger\u00e4tetypen konfigurieren", "title": "deCONZ-Optionen" diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index 487163794833f5..c1435dbb186a5e 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -14,8 +14,8 @@ "flow_title": "", "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegget {addon} ?", - "title": "deCONZ Zigbee gateway via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegg {addon} ?", + "title": "deCONZ Zigbee gateway via Hass.io-tillegg" }, "link": { "description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger -> Gateway -> Avansert \n 2. Trykk p\u00e5 \"Autentiser app\" knappen", diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 24a3ba61706696..1b4eba97096ae5 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -38,9 +38,9 @@ "trigger_subtype": { "both_buttons": "oba przyciski", "bottom_buttons": "dolne przyciski", - "button_1": "pierwszy przycisk", - "button_2": "drugi przycisk", - "button_3": "trzeci przycisk", + "button_1": "pierwszy", + "button_2": "drugi", + "button_3": "trzeci", "button_4": "czwarty", "close": "zamknij", "dim_down": "zmniejszenie jasno\u015bci", diff --git a/homeassistant/components/deconz/translations/ru.json b/homeassistant/components/deconz/translations/ru.json index a6bc0daaa3e271..f22975530d8324 100644 --- a/homeassistant/components/deconz/translations/ru.json +++ b/homeassistant/components/deconz/translations/ru.json @@ -14,8 +14,8 @@ "flow_title": "\u0428\u043b\u044e\u0437 Zigbee deCONZ ({host})", "step": { "hassio_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" }, "link": { "description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ -> Gateway -> Advanced.\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", diff --git a/homeassistant/components/deconz/translations/tr.json b/homeassistant/components/deconz/translations/tr.json index e73703043f3ce0..22eea1278d744c 100644 --- a/homeassistant/components/deconz/translations/tr.json +++ b/homeassistant/components/deconz/translations/tr.json @@ -1,4 +1,47 @@ { + "config": { + "abort": { + "already_configured": "K\u00f6pr\u00fc zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "step": { + "manual_input": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "user": { + "data": { + "host": "Ke\u015ffedilen deCONZ a\u011f ge\u00e7idini se\u00e7in" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "side_4": "Yan 4", + "side_5": "Yan 5", + "side_6": "Yan 6" + }, + "trigger_type": { + "remote_awakened": "Cihaz uyand\u0131", + "remote_double_tap": "\" {subtype} \" cihaz\u0131na iki kez hafif\u00e7e vuruldu", + "remote_double_tap_any_side": "Cihaz herhangi bir tarafta \u00e7ift dokundu", + "remote_falling": "Serbest d\u00fc\u015f\u00fc\u015fte cihaz", + "remote_flip_180_degrees": "Cihaz 180 derece d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_flip_90_degrees": "Cihaz 90 derece d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_moved": "Cihaz \" {subtype} \" yukar\u0131 ta\u015f\u0131nd\u0131", + "remote_moved_any_side": "Cihaz herhangi bir taraf\u0131 yukar\u0131 gelecek \u015fekilde ta\u015f\u0131nd\u0131", + "remote_rotate_from_side_1": "Cihaz, \"1. taraftan\" \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_2": "Cihaz, \"2. taraftan\" \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_3": "Cihaz \"3. taraftan\" \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_4": "Cihaz, \"4. taraf\" dan \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_rotate_from_side_5": "Cihaz, \"5. taraf\" dan \" {subtype} \" e d\u00f6nd\u00fcr\u00fcld\u00fc", + "remote_turned_clockwise": "Cihaz saat y\u00f6n\u00fcnde d\u00f6nd\u00fc", + "remote_turned_counter_clockwise": "Cihaz saat y\u00f6n\u00fcn\u00fcn tersine d\u00f6nd\u00fc" + } + }, "options": { "step": { "deconz_devices": { diff --git a/homeassistant/components/deconz/translations/uk.json b/homeassistant/components/deconz/translations/uk.json new file mode 100644 index 00000000000000..b5de362a731ee3 --- /dev/null +++ b/homeassistant/components/deconz/translations/uk.json @@ -0,0 +1,105 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e.", + "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.", + "no_bridges": "\u0428\u043b\u044e\u0437\u0438 deCONZ \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456.", + "no_hardware_available": "\u0420\u0430\u0434\u0456\u043e\u043e\u0431\u043b\u0430\u0434\u043d\u0430\u043d\u043d\u044f \u043d\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e deCONZ.", + "not_deconz_bridge": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u0448\u043b\u044e\u0437\u043e\u043c deCONZ.", + "updated_instance": "\u0410\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043e." + }, + "error": { + "no_key": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u043a\u043b\u044e\u0447 API." + }, + "flow_title": "\u0428\u043b\u044e\u0437 Zigbee deCONZ ({host})", + "step": { + "hassio_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + }, + "link": { + "description": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u0457 \u0432 Home Assistant: \n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0438 deCONZ - > Gateway - > Advanced.\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", + "title": "\u0417\u0432'\u044f\u0437\u043e\u043a \u0437 deCONZ" + }, + "manual_input": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + } + }, + "user": { + "data": { + "host": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0438\u0439 \u0448\u043b\u044e\u0437 deCONZ" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "both_buttons": "\u041e\u0431\u0438\u0434\u0432\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "bottom_buttons": "\u041d\u0438\u0436\u043d\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "close": "\u0417\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "dim_down": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "dim_up": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "left": "\u041b\u0456\u0432\u043e\u0440\u0443\u0447", + "open": "\u0412\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "right": "\u041f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", + "side_1": "\u0413\u0440\u0430\u043d\u044c 1", + "side_2": "\u0413\u0440\u0430\u043d\u044c 2", + "side_3": "\u0413\u0440\u0430\u043d\u044c 3", + "side_4": "\u0413\u0440\u0430\u043d\u044c 4", + "side_5": "\u0413\u0440\u0430\u043d\u044c 5", + "side_6": "\u0413\u0440\u0430\u043d\u044c 6", + "top_buttons": "\u0412\u0435\u0440\u0445\u043d\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "remote_awakened": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0440\u043e\u0437\u0431\u0443\u0434\u0438\u043b\u0438", + "remote_button_double_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438", + "remote_button_long_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_long_release": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_quadruple_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438", + "remote_button_quintuple_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432", + "remote_button_rotated": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0430", + "remote_button_rotated_fast": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0430 \u0448\u0432\u0438\u0434\u043a\u043e", + "remote_button_rotation_stopped": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043f\u0440\u0438\u043f\u0438\u043d\u0438\u043b\u0430 \u043e\u0431\u0435\u0440\u0442\u0430\u043d\u043d\u044f", + "remote_button_short_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_short_release": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_triple_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438", + "remote_double_tap": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c {subtype} \u043f\u043e\u0441\u0442\u0443\u043a\u0430\u043b\u0438 \u0434\u0432\u0456\u0447\u0456", + "remote_double_tap_any_side": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c \u043f\u043e\u0441\u0442\u0443\u043a\u0430\u043b\u0438 \u0434\u0432\u0456\u0447\u0456", + "remote_falling": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0443 \u0432\u0456\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u0430\u0434\u0456\u043d\u043d\u0456", + "remote_flip_180_degrees": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043d\u0430 180 \u0433\u0440\u0430\u0434\u0443\u0441\u0456\u0432", + "remote_flip_90_degrees": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043d\u0430 90 \u0433\u0440\u0430\u0434\u0443\u0441\u0456\u0432", + "remote_gyro_activated": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0442\u0440\u044f\u0441\u043b\u0438", + "remote_moved": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0437\u0440\u0443\u0448\u0438\u043b\u0438, \u043a\u043e\u043b\u0438 {subtype} \u0437\u0432\u0435\u0440\u0445\u0443", + "remote_moved_any_side": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u043b\u0438", + "remote_rotate_from_side_1": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 1 \u043d\u0430 {subtype}", + "remote_rotate_from_side_2": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 2 \u043d\u0430 {subtype}", + "remote_rotate_from_side_3": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 3 \u043d\u0430 {subtype}", + "remote_rotate_from_side_4": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 4 \u043d\u0430 {subtype}", + "remote_rotate_from_side_5": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 5 \u043d\u0430 {subtype}", + "remote_rotate_from_side_6": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437 \u0413\u0440\u0430\u043d\u0456 6 \u043d\u0430 {subtype}", + "remote_turned_clockwise": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u0437\u0430 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u044e \u0441\u0442\u0440\u0456\u043b\u043a\u043e\u044e", + "remote_turned_counter_clockwise": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0438 \u043f\u0440\u043e\u0442\u0438 \u0433\u043e\u0434\u0438\u043d\u043d\u0438\u043a\u043e\u0432\u043e\u0457 \u0441\u0442\u0440\u0456\u043b\u043a\u0438" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_clip_sensor": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0438 deCONZ CLIP", + "allow_deconz_groups": "\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0433\u0440\u0443\u043f\u0438 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f deCONZ", + "allow_new_devices": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u043d\u043e\u0432\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0456 \u0442\u0438\u043f\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 deCONZ", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f deCONZ" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/demo/translations/tr.json b/homeassistant/components/demo/translations/tr.json new file mode 100644 index 00000000000000..1ca389b0b979b9 --- /dev/null +++ b/homeassistant/components/demo/translations/tr.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "options_1": { + "data": { + "constant": "Sabit" + } + } + } + }, + "title": "Demo" +} \ No newline at end of file diff --git a/homeassistant/components/demo/translations/uk.json b/homeassistant/components/demo/translations/uk.json new file mode 100644 index 00000000000000..5ac1ac74708e18 --- /dev/null +++ b/homeassistant/components/demo/translations/uk.json @@ -0,0 +1,21 @@ +{ + "options": { + "step": { + "options_1": { + "data": { + "bool": "\u041b\u043e\u0433\u0456\u0447\u043d\u0438\u0439", + "constant": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u0430", + "int": "\u0427\u0438\u0441\u043b\u043e\u0432\u0438\u0439" + } + }, + "options_2": { + "data": { + "multi": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0434\u0435\u043a\u0456\u043b\u044c\u043a\u0430", + "select": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e", + "string": "\u0421\u0442\u0440\u043e\u043a\u043e\u0432\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f" + } + } + } + }, + "title": "\u0414\u0435\u043c\u043e" +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json index 5af7d3393e243b..f52e6303091ad1 100644 --- a/homeassistant/components/denonavr/translations/de.json +++ b/homeassistant/components/denonavr/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" + }, "step": { "select": { "data": { diff --git a/homeassistant/components/denonavr/translations/tr.json b/homeassistant/components/denonavr/translations/tr.json new file mode 100644 index 00000000000000..f618d3a3038474 --- /dev/null +++ b/homeassistant/components/denonavr/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flan\u0131lamad\u0131, l\u00fctfen tekrar deneyin, ana g\u00fc\u00e7 ve ethernet kablolar\u0131n\u0131n ba\u011flant\u0131s\u0131n\u0131 kesip yeniden ba\u011flamak yard\u0131mc\u0131 olabilir" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/uk.json b/homeassistant/components/denonavr/translations/uk.json new file mode 100644 index 00000000000000..efb4cb417779fd --- /dev/null +++ b/homeassistant/components/denonavr/translations/uk.json @@ -0,0 +1,48 @@ +{ + "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_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.", + "cannot_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437. \u042f\u043a\u0449\u043e \u0446\u0435 \u043d\u0435 \u0441\u043f\u0440\u0430\u0446\u044e\u0432\u0430\u043b\u043e, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043a\u0430\u0431\u0435\u043b\u044c Ethernet \u0456 \u043a\u0430\u0431\u0435\u043b\u044c \u0436\u0438\u0432\u043b\u0435\u043d\u043d\u044f.", + "not_denonavr_manufacturer": "\u0426\u0435 \u043d\u0435 \u0440\u0435\u0441\u0438\u0432\u0435\u0440 Denon. \u0412\u0438\u0440\u043e\u0431\u043d\u0438\u043a \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454.", + "not_denonavr_missing": "\u041d\u0435\u043f\u043e\u0432\u043d\u0430 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u0434\u043b\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e." + }, + "error": { + "discovery_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u043d\u0430\u0439\u0442\u0438 \u0440\u0435\u0441\u0438\u0432\u0435\u0440 Denon." + }, + "flow_title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon: {name}", + "step": { + "confirm": { + "description": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u0441\u0438\u0432\u0435\u0440\u0430", + "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + }, + "select": { + "data": { + "select_host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043d\u043e\u0432\u0443, \u044f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0456\u043d\u0448\u0438\u0439 \u0440\u0435\u0441\u0438\u0432\u0435\u0440", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0438\u0432\u0435\u0440, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u042f\u043a\u0449\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0430, \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f", + "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_all_sources": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0432\u0441\u0456 \u0434\u0436\u0435\u0440\u0435\u043b\u0430", + "zone2": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438 2", + "zone3": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438 3" + }, + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", + "title": "\u0420\u0435\u0441\u0438\u0432\u0435\u0440 Denon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/de.json b/homeassistant/components/device_tracker/translations/de.json index 651805dcb14b86..fe59183e67a808 100644 --- a/homeassistant/components/device_tracker/translations/de.json +++ b/homeassistant/components/device_tracker/translations/de.json @@ -1,8 +1,12 @@ { "device_automation": { "condition_type": { - "is_home": "{entity_name} ist Zuhause", - "is_not_home": "{entity_name} ist nicht zu Hause" + "is_home": "{entity_name} ist zuhause", + "is_not_home": "{entity_name} ist nicht zuhause" + }, + "trigger_type": { + "enters": "{entity_name} betritt einen Bereich", + "leaves": "{entity_name} verl\u00e4sst einen Bereich" } }, "state": { diff --git a/homeassistant/components/device_tracker/translations/uk.json b/homeassistant/components/device_tracker/translations/uk.json index f49c7acc0e3933..87945d2a19a8dd 100644 --- a/homeassistant/components/device_tracker/translations/uk.json +++ b/homeassistant/components/device_tracker/translations/uk.json @@ -1,8 +1,18 @@ { + "device_automation": { + "condition_type": { + "is_home": "{entity_name} \u0432\u0434\u043e\u043c\u0430", + "is_not_home": "{entity_name} \u043d\u0435 \u0432\u0434\u043e\u043c\u0430" + }, + "trigger_type": { + "enters": "{entity_name} \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0437\u043e\u043d\u0443", + "leaves": "{entity_name} \u043f\u043e\u043a\u0438\u0434\u0430\u0454 \u0437\u043e\u043d\u0443" + } + }, "state": { "_": { "home": "\u0412\u0434\u043e\u043c\u0430", - "not_home": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0439" + "not_home": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430" } }, "title": "\u0422\u0440\u0435\u043a\u0435\u0440 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" diff --git a/homeassistant/components/devolo_home_control/translations/de.json b/homeassistant/components/devolo_home_control/translations/de.json index 112daf582b3629..6cf7ed3c82115b 100644 --- a/homeassistant/components/devolo_home_control/translations/de.json +++ b/homeassistant/components/devolo_home_control/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "Diese Home Control Zentral wird bereits verwendet." + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { @@ -9,7 +12,7 @@ "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Passwort", - "username": "E-Mail-Adresse / devolo ID" + "username": "E-Mail / devolo ID" } } } diff --git a/homeassistant/components/devolo_home_control/translations/tr.json b/homeassistant/components/devolo_home_control/translations/tr.json new file mode 100644 index 00000000000000..4c6b158f6946b9 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta / devolo ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/uk.json b/homeassistant/components/devolo_home_control/translations/uk.json new file mode 100644 index 00000000000000..d230d1918f5e09 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "home_control_url": "Home Control URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "mydevolo_url": "mydevolo URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438 / devolo ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/de.json b/homeassistant/components/dexcom/translations/de.json index fadb459a3d35ca..d567dd6b611167 100644 --- a/homeassistant/components/dexcom/translations/de.json +++ b/homeassistant/components/dexcom/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Konto ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/dexcom/translations/fr.json b/homeassistant/components/dexcom/translations/fr.json index d10643a3c1eac7..095c769a1be4e2 100644 --- a/homeassistant/components/dexcom/translations/fr.json +++ b/homeassistant/components/dexcom/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion", diff --git a/homeassistant/components/dexcom/translations/tr.json b/homeassistant/components/dexcom/translations/tr.json index 80638d181b2e58..ec93dc078afb9c 100644 --- a/homeassistant/components/dexcom/translations/tr.json +++ b/homeassistant/components/dexcom/translations/tr.json @@ -2,6 +2,28 @@ "config": { "abort": { "already_configured": "Hesap zaten konfig\u00fcre edilmi\u015fi durumda" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "\u00d6l\u00e7\u00fc birimi" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/uk.json b/homeassistant/components/dexcom/translations/uk.json new file mode 100644 index 00000000000000..66727af90d19af --- /dev/null +++ b/homeassistant/components/dexcom/translations/uk.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "server": "\u0421\u0435\u0440\u0432\u0435\u0440", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "Dexcom" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/de.json b/homeassistant/components/dialogflow/translations/de.json index f1853107cc2374..2035b818b44d10 100644 --- a/homeassistant/components/dialogflow/translations/de.json +++ b/homeassistant/components/dialogflow/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an den Home Assistant zu senden, musst du [Webhook-Integration von Dialogflow]({dialogflow_url}) einrichten. \n\nF\u00fclle die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / json \n\nWeitere Informationen findest du in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/dialogflow/translations/tr.json b/homeassistant/components/dialogflow/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/uk.json b/homeassistant/components/dialogflow/translations/uk.json new file mode 100644 index 00000000000000..625d2db78dcb01 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f [Dialogflow]({dialogflow_url}). \n\n\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json \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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Dialogflow?", + "title": "Dialogflow" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/tr.json b/homeassistant/components/directv/translations/tr.json new file mode 100644 index 00000000000000..daca8f1ef6246b --- /dev/null +++ b/homeassistant/components/directv/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "ssdp_confirm": { + "description": "{name} kurmak istiyor musunuz?" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/uk.json b/homeassistant/components/directv/translations/uk.json new file mode 100644 index 00000000000000..5371f638e3d8a4 --- /dev/null +++ b/homeassistant/components/directv/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/de.json b/homeassistant/components/doorbird/translations/de.json index 62bb11d6a8cb0c..0d6bef7a63fb4f 100644 --- a/homeassistant/components/doorbird/translations/de.json +++ b/homeassistant/components/doorbird/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dieser DoorBird ist bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "link_local_address": "Lokale Linkadressen werden nicht unterst\u00fctzt", "not_doorbird_device": "Dieses Ger\u00e4t ist kein DoorBird" }, diff --git a/homeassistant/components/doorbird/translations/tr.json b/homeassistant/components/doorbird/translations/tr.json new file mode 100644 index 00000000000000..d7a1ca8a93a9f0 --- /dev/null +++ b/homeassistant/components/doorbird/translations/tr.json @@ -0,0 +1,22 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "name": "Cihaz ad\u0131", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/uk.json b/homeassistant/components/doorbird/translations/uk.json new file mode 100644 index 00000000000000..07bbdfacafe69e --- /dev/null +++ b/homeassistant/components/doorbird/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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.", + "link_local_address": "\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456 \u0430\u0434\u0440\u0435\u0441\u0438 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "not_doorbird_device": "\u0426\u0435 \u043d\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 DoorBird." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "DoorBird {name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0456\u0439 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u0443." + }, + "description": "\u0414\u043e\u0434\u0430\u0439\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u0443 \u043d\u0430\u0437\u0432\u0438 \u043f\u043e\u0434\u0456\u0439, \u044f\u043a\u0435 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0456\u0434\u0441\u043b\u0456\u0434\u043a\u043e\u0432\u0443\u0432\u0430\u0442\u0438. \u041f\u0456\u0441\u043b\u044f \u0446\u044c\u043e\u0433\u043e, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a DoorBird, \u0449\u043e\u0431 \u043f\u0440\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0457\u0445 \u0434\u043e \u043f\u0435\u0432\u043d\u043e\u0457 \u043f\u043e\u0434\u0456\u0457. \u041f\u0440\u0438\u043a\u043b\u0430\u0434: somebody_pressed_the_button, motion. \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 \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: https://www.home-assistant.io/integrations/doorbird/#events." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/de.json b/homeassistant/components/dsmr/translations/de.json new file mode 100644 index 00000000000000..da1d200c2a2099 --- /dev/null +++ b/homeassistant/components/dsmr/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/fr.json b/homeassistant/components/dsmr/translations/fr.json index ea382532a71327..cb08a7865b33a4 100644 --- a/homeassistant/components/dsmr/translations/fr.json +++ b/homeassistant/components/dsmr/translations/fr.json @@ -7,5 +7,15 @@ "one": "", "other": "Autre" } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "Temps minimum entre les mises \u00e0 jour des entit\u00e9s" + }, + "title": "Options DSMR" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/tr.json b/homeassistant/components/dsmr/translations/tr.json index 94c31d0e1563d5..0857160dc51b34 100644 --- a/homeassistant/components/dsmr/translations/tr.json +++ b/homeassistant/components/dsmr/translations/tr.json @@ -1,4 +1,9 @@ { + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/dsmr/translations/uk.json b/homeassistant/components/dsmr/translations/uk.json new file mode 100644 index 00000000000000..9bca6b00c74eda --- /dev/null +++ b/homeassistant/components/dsmr/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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." + } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 DSMR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/tr.json b/homeassistant/components/dunehd/translations/tr.json new file mode 100644 index 00000000000000..0f8c17228fdd1f --- /dev/null +++ b/homeassistant/components/dunehd/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/uk.json b/homeassistant/components/dunehd/translations/uk.json new file mode 100644 index 00000000000000..d2f4eadbdcb7f9 --- /dev/null +++ b/homeassistant/components/dunehd/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Dune HD. \u042f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0432\u0438\u043d\u0438\u043a\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438 \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: https://www.home-assistant.io/integrations/dunehd \n\n \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0412\u0430\u0448 \u043f\u043b\u0435\u0454\u0440 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u0439.", + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/de.json b/homeassistant/components/eafm/translations/de.json new file mode 100644 index 00000000000000..da1d200c2a2099 --- /dev/null +++ b/homeassistant/components/eafm/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/tr.json b/homeassistant/components/eafm/translations/tr.json new file mode 100644 index 00000000000000..4ed0f406e57ce6 --- /dev/null +++ b/homeassistant/components/eafm/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "no_stations": "Ak\u0131\u015f izleme istasyonu bulunamad\u0131." + }, + "step": { + "user": { + "data": { + "station": "\u0130stasyon" + }, + "title": "Ak\u0131\u015f izleme istasyonunu takip edin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/uk.json b/homeassistant/components/eafm/translations/uk.json new file mode 100644 index 00000000000000..4f84eb92722948 --- /dev/null +++ b/homeassistant/components/eafm/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "no_stations": "\u0421\u0442\u0430\u043d\u0446\u0456\u0457 \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u043f\u043e\u0432\u0435\u043d\u0435\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456." + }, + "step": { + "user": { + "data": { + "station": "\u0421\u0442\u0430\u043d\u0446\u0456\u044f" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0456\u044e \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443", + "title": "\u0421\u0442\u0430\u043d\u0446\u0456\u0457 \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u043f\u043e\u0432\u0435\u043d\u0435\u0439" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/de.json b/homeassistant/components/ecobee/translations/de.json index bc65fddebdd047..0c89a696b2c412 100644 --- a/homeassistant/components/ecobee/translations/de.json +++ b/homeassistant/components/ecobee/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits eingerichtet. Es ist nur eine Konfiguration m\u00f6glich." + }, "error": { "pin_request_failed": "Fehler beim Anfordern der PIN von ecobee; Bitte \u00fcberpr\u00fcfe, ob der API-Schl\u00fcssel korrekt ist.", "token_request_failed": "Fehler beim Anfordern eines Token von ecobee; Bitte versuche es erneut." diff --git a/homeassistant/components/ecobee/translations/tr.json b/homeassistant/components/ecobee/translations/tr.json new file mode 100644 index 00000000000000..23ece38682d11b --- /dev/null +++ b/homeassistant/components/ecobee/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/uk.json b/homeassistant/components/ecobee/translations/uk.json new file mode 100644 index 00000000000000..7cf7df534296f6 --- /dev/null +++ b/homeassistant/components/ecobee/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "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.", + "token_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 \u0442\u043e\u043a\u0435\u043d\u0456\u0432 \u0443 ecobee; \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." + }, + "step": { + "authorize": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e https://www.ecobee.com/consumerportal/index.html \u0456 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e PIN-\u043a\u043e\u0434\u0443: \n\n{pin}\n\n\u041f\u043e\u0442\u0456\u043c \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438.", + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u0430 \u043d\u0430 ecobee.com" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u0432\u0456\u0434 ecobee.com.", + "title": "ecobee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/ca.json b/homeassistant/components/econet/translations/ca.json new file mode 100644 index 00000000000000..c53914f8cb990e --- /dev/null +++ b/homeassistant/components/econet/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + }, + "title": "Configuraci\u00f3 del compte Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/en.json b/homeassistant/components/econet/translations/en.json index 4061c094c1ff83..ad499b0e37c739 100644 --- a/homeassistant/components/econet/translations/en.json +++ b/homeassistant/components/econet/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Device is already configured", "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication" }, diff --git a/homeassistant/components/econet/translations/es.json b/homeassistant/components/econet/translations/es.json new file mode 100644 index 00000000000000..ac69f8f7be1f8d --- /dev/null +++ b/homeassistant/components/econet/translations/es.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "email": "Correo electr\u00f3nico", + "password": "Contrase\u00f1a" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/et.json b/homeassistant/components/econet/translations/et.json new file mode 100644 index 00000000000000..349a4d21111587 --- /dev/null +++ b/homeassistant/components/econet/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine" + }, + "step": { + "user": { + "data": { + "email": "E-posti aadress", + "password": "Salas\u00f5na" + }, + "title": "Seadista Rheem EcoNeti konto" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/it.json b/homeassistant/components/econet/translations/it.json new file mode 100644 index 00000000000000..3074c72b083953 --- /dev/null +++ b/homeassistant/components/econet/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Password" + }, + "title": "Imposta account Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/no.json b/homeassistant/components/econet/translations/no.json new file mode 100644 index 00000000000000..f54cedffda84ed --- /dev/null +++ b/homeassistant/components/econet/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning" + }, + "step": { + "user": { + "data": { + "email": "E-post", + "password": "Passord" + }, + "title": "Konfigurer Rheem EcoNet-konto" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/pl.json b/homeassistant/components/econet/translations/pl.json new file mode 100644 index 00000000000000..e5d74de590d54d --- /dev/null +++ b/homeassistant/components/econet/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "step": { + "user": { + "data": { + "email": "Adres e-mail", + "password": "Has\u0142o" + }, + "title": "Konfiguracja konta Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/ru.json b/homeassistant/components/econet/translations/ru.json new file mode 100644 index 00000000000000..109ded8db998eb --- /dev/null +++ b/homeassistant/components/econet/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "title": "Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/tr.json b/homeassistant/components/econet/translations/tr.json new file mode 100644 index 00000000000000..237a87d02685eb --- /dev/null +++ b/homeassistant/components/econet/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "\u015eifre" + }, + "title": "Rheem EcoNet Hesab\u0131n\u0131 Kur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/zh-Hant.json b/homeassistant/components/econet/translations/zh-Hant.json new file mode 100644 index 00000000000000..50824c198145a4 --- /dev/null +++ b/homeassistant/components/econet/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + }, + "title": "\u8a2d\u5b9a Rheem EcoNet \u5e33\u865f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/de.json b/homeassistant/components/elgato/translations/de.json index 7497460445337c..1df8f91ecd6a2e 100644 --- a/homeassistant/components/elgato/translations/de.json +++ b/homeassistant/components/elgato/translations/de.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "Dieses Elgato Key Light-Ger\u00e4t ist bereits konfiguriert.", - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "Elgato Key Light: {serial_number}", "step": { diff --git a/homeassistant/components/elgato/translations/tr.json b/homeassistant/components/elgato/translations/tr.json new file mode 100644 index 00000000000000..b2d1753fd68b32 --- /dev/null +++ b/homeassistant/components/elgato/translations/tr.json @@ -0,0 +1,19 @@ +{ + "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" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/uk.json b/homeassistant/components/elgato/translations/uk.json new file mode 100644 index 00000000000000..978ff1a310088f --- /dev/null +++ b/homeassistant/components/elgato/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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.", + "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" + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Elgato Key Light \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Home Assistant." + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Elgato Key Light \u0437 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Elgato Key Light" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/de.json b/homeassistant/components/elkm1/translations/de.json index 8c562a7502659c..8157a061d82d5f 100644 --- a/homeassistant/components/elkm1/translations/de.json +++ b/homeassistant/components/elkm1/translations/de.json @@ -5,7 +5,7 @@ "already_configured": "Ein ElkM1 mit diesem Pr\u00e4fix ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/elkm1/translations/tr.json b/homeassistant/components/elkm1/translations/tr.json new file mode 100644 index 00000000000000..9259220985bb1b --- /dev/null +++ b/homeassistant/components/elkm1/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/uk.json b/homeassistant/components/elkm1/translations/uk.json new file mode 100644 index 00000000000000..a8e711a4590822 --- /dev/null +++ b/homeassistant/components/elkm1/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "address_already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 \u0446\u0456\u0454\u044e \u0430\u0434\u0440\u0435\u0441\u043e\u044e \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 \u0446\u0438\u043c \u043f\u0440\u0435\u0444\u0456\u043a\u0441\u043e\u043c \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430, \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e \u043f\u043e\u0441\u043b\u0456\u0434\u043e\u0432\u043d\u0438\u0439 \u043f\u043e\u0440\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "prefix": "\u0423\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0443 \u0412\u0430\u0441 \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0434\u0438\u043d ElkM1)", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b", + "temperature_unit": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0420\u044f\u0434\u043e\u043a \u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'addres[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0456\u0432 'secure' \u0456 'non-secure' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '192.168.1.1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 2101 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'non-secure' \u0456 2601 \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'secure'. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'serial' \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 115200.", + "title": "Elk-M1 Control" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/de.json b/homeassistant/components/emulated_roku/translations/de.json index a0bfd9f83aa440..39c8da5197fa1f 100644 --- a/homeassistant/components/emulated_roku/translations/de.json +++ b/homeassistant/components/emulated_roku/translations/de.json @@ -8,7 +8,7 @@ "data": { "advertise_ip": "IP Adresse annoncieren", "advertise_port": "Port annoncieren", - "host_ip": "Host-IP", + "host_ip": "Host-IP-Adresse", "listen_port": "Listen-Port", "name": "Name", "upnp_bind_multicast": "Multicast binden (True/False)" diff --git a/homeassistant/components/emulated_roku/translations/tr.json b/homeassistant/components/emulated_roku/translations/tr.json new file mode 100644 index 00000000000000..5307276a71d3a3 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/uk.json b/homeassistant/components/emulated_roku/translations/uk.json new file mode 100644 index 00000000000000..a299f3a5ebc6d4 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "advertise_ip": "\u041e\u0433\u043e\u043b\u043e\u0448\u0443\u0432\u0430\u0442\u0438 IP", + "advertise_port": "\u041e\u0433\u043e\u043b\u043e\u0448\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0440\u0442", + "host_ip": "\u0425\u043e\u0441\u0442", + "listen_port": "\u041f\u043e\u0440\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "upnp_bind_multicast": "\u041f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 multicast (True / False)" + }, + "title": "EmulatedRoku" + } + } + }, + "title": "Emulated Roku" +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/tr.json b/homeassistant/components/enocean/translations/tr.json new file mode 100644 index 00000000000000..b4e6be555ff467 --- /dev/null +++ b/homeassistant/components/enocean/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "invalid_dongle_path": "Ge\u00e7ersiz dongle yolu", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_dongle_path": "Bu yol i\u00e7in ge\u00e7erli bir dongle bulunamad\u0131" + }, + "step": { + "detect": { + "data": { + "path": "USB dongle yolu" + } + }, + "manual": { + "data": { + "path": "USB dongle yolu" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/uk.json b/homeassistant/components/enocean/translations/uk.json new file mode 100644 index 00000000000000..5c3e2d6eb6ec97 --- /dev/null +++ b/homeassistant/components/enocean/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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." + }, + "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." + }, + "step": { + "detect": { + "data": { + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "ENOcean" + }, + "manual": { + "data": { + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "ENOcean" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/epson/translations/de.json b/homeassistant/components/epson/translations/de.json index c03615a39ff5d5..a91e3831cdb39c 100644 --- a/homeassistant/components/epson/translations/de.json +++ b/homeassistant/components/epson/translations/de.json @@ -1,12 +1,14 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { "data": { - "name": "Name" + "host": "Host", + "name": "Name", + "port": "Port" } } } diff --git a/homeassistant/components/epson/translations/tr.json b/homeassistant/components/epson/translations/tr.json index aafc2e2b30345a..9ffd77fc50f6c8 100644 --- a/homeassistant/components/epson/translations/tr.json +++ b/homeassistant/components/epson/translations/tr.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/epson/translations/uk.json b/homeassistant/components/epson/translations/uk.json new file mode 100644 index 00000000000000..65566a8f4aa5cb --- /dev/null +++ b/homeassistant/components/epson/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/de.json b/homeassistant/components/esphome/translations/de.json index 826574cb7e089d..fdaea452c4560a 100644 --- a/homeassistant/components/esphome/translations/de.json +++ b/homeassistant/components/esphome/translations/de.json @@ -2,10 +2,11 @@ "config": { "abort": { "already_configured": "ESP ist bereits konfiguriert", - "already_in_progress": "Die ESP-Konfiguration wird bereits ausgef\u00fchrt" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" }, "error": { "connection_error": "Keine Verbindung zum ESP m\u00f6glich. Achte darauf, dass deine YAML-Datei eine Zeile 'api:' enth\u00e4lt.", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "resolve_error": "Adresse des ESP kann nicht aufgel\u00f6st werden. Wenn dieser Fehler weiterhin besteht, lege eine statische IP-Adresse fest: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", diff --git a/homeassistant/components/esphome/translations/pt.json b/homeassistant/components/esphome/translations/pt.json index 6ff4d786447909..60eeaa3f4b2103 100644 --- a/homeassistant/components/esphome/translations/pt.json +++ b/homeassistant/components/esphome/translations/pt.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "O ESP j\u00e1 est\u00e1 configurado", + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer" }, "error": { diff --git a/homeassistant/components/esphome/translations/tr.json b/homeassistant/components/esphome/translations/tr.json index 15028c4fe65592..81f85d4980bb0b 100644 --- a/homeassistant/components/esphome/translations/tr.json +++ b/homeassistant/components/esphome/translations/tr.json @@ -1,8 +1,27 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, "step": { + "authenticate": { + "data": { + "password": "Parola" + }, + "description": "L\u00fctfen yap\u0131land\u0131rman\u0131zda {name} i\u00e7in belirledi\u011finiz parolay\u0131 girin." + }, "discovery_confirm": { "title": "Ke\u015ffedilen ESPHome d\u00fc\u011f\u00fcm\u00fc" + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } } } } diff --git a/homeassistant/components/esphome/translations/uk.json b/homeassistant/components/esphome/translations/uk.json index d17ec64e5480e5..4643c19cf5ddeb 100644 --- a/homeassistant/components/esphome/translations/uk.json +++ b/homeassistant/components/esphome/translations/uk.json @@ -1,22 +1,25 @@ { "config": { "abort": { - "already_configured": "ESP \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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." }, "error": { "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u0454\u0442\u044c\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e ESP. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0444\u0430\u0439\u043b YAML \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u0440\u044f\u0434\u043e\u043a \"api:\".", - "resolve_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 ESP. \u042f\u043a\u0449\u043e \u0446\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043d\u0435 \u0437\u043d\u0438\u043a\u0430\u0454, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u0443 IP-\u0430\u0434\u0440\u0435\u0441\u0443: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "resolve_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 ESP. \u042f\u043a\u0449\u043e \u0446\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u044e\u0454\u0442\u044c\u0441\u044f, \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u0443 IP-\u0430\u0434\u0440\u0435\u0441\u0443: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips." }, + "flow_title": "ESPHome: {name}", "step": { "authenticate": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0443 \u0441\u0432\u043e\u0457\u0439 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457." + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u0432 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 {name}." }, "discovery_confirm": { "description": "\u0414\u043e\u0434\u0430\u0442\u0438 ESPHome \u0432\u0443\u0437\u043e\u043b {name} \u0443 Home Assistant?", - "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u0432\u0443\u0437\u043e\u043b ESPHome" + "title": "ESPHome" }, "user": { "data": { diff --git a/homeassistant/components/fan/translations/tr.json b/homeassistant/components/fan/translations/tr.json index 4ffc57601bdd53..52a07c35d8326b 100644 --- a/homeassistant/components/fan/translations/tr.json +++ b/homeassistant/components/fan/translations/tr.json @@ -1,4 +1,14 @@ { + "device_automation": { + "action_type": { + "turn_off": "{entity_name} kapat", + "turn_on": "{entity_name} a\u00e7\u0131n" + }, + "trigger_type": { + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "state": { "_": { "off": "Kapal\u0131", diff --git a/homeassistant/components/fan/translations/uk.json b/homeassistant/components/fan/translations/uk.json index 3fd103cd244c56..0e0bafcbfc4f2b 100644 --- a/homeassistant/components/fan/translations/uk.json +++ b/homeassistant/components/fan/translations/uk.json @@ -1,8 +1,16 @@ { "device_automation": { + "action_type": { + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { - "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", - "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + "turned_off": "{entity_name} \u0432\u0438\u043c\u0438\u043a\u0430\u0454\u0442\u044c\u0441\u044f", + "turned_on": "{entity_name} \u0432\u043c\u0438\u043a\u0430\u0454\u0442\u044c\u0441\u044f" } }, "state": { diff --git a/homeassistant/components/fireservicerota/translations/fr.json b/homeassistant/components/fireservicerota/translations/fr.json new file mode 100644 index 00000000000000..a8803f63fca6e5 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/fr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte \u00e0 d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + }, + "create_entry": { + "default": "Autentification r\u00e9ussie" + }, + "error": { + "invalid_auth": "Autentification invalide" + }, + "step": { + "reauth": { + "data": { + "password": "Mot de passe" + } + }, + "user": { + "data": { + "password": "Mot de passe", + "url": "Site web", + "username": "Utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/tr.json b/homeassistant/components/fireservicerota/translations/tr.json index a2d2cab3b7469f..f54d10f6cbf71a 100644 --- a/homeassistant/components/fireservicerota/translations/tr.json +++ b/homeassistant/components/fireservicerota/translations/tr.json @@ -1,5 +1,15 @@ { "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, "step": { "reauth": { "data": { diff --git a/homeassistant/components/fireservicerota/translations/uk.json b/homeassistant/components/fireservicerota/translations/uk.json new file mode 100644 index 00000000000000..2d3bf8c596e037 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0422\u043e\u043a\u0435\u043d\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0456, \u0443\u0432\u0456\u0439\u0434\u0456\u0442\u044c, \u0449\u043e\u0431 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0457\u0445 \u0437\u0430\u043d\u043e\u0432\u043e." + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "url": "\u0412\u0435\u0431-\u0441\u0430\u0439\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/tr.json b/homeassistant/components/firmata/translations/tr.json new file mode 100644 index 00000000000000..b7d038a229b0df --- /dev/null +++ b/homeassistant/components/firmata/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/uk.json b/homeassistant/components/firmata/translations/uk.json new file mode 100644 index 00000000000000..41b670fbb184cf --- /dev/null +++ b/homeassistant/components/firmata/translations/uk.json @@ -0,0 +1,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" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/de.json b/homeassistant/components/flick_electric/translations/de.json index ed0ef205ff031a..3e3568c45f8e32 100644 --- a/homeassistant/components/flick_electric/translations/de.json +++ b/homeassistant/components/flick_electric/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Dieses Konto ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/flick_electric/translations/tr.json b/homeassistant/components/flick_electric/translations/tr.json new file mode 100644 index 00000000000000..a83e1936fb4a15 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/uk.json b/homeassistant/components/flick_electric/translations/uk.json new file mode 100644 index 00000000000000..4d72844bc74fd0 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "client_id": "ID \u043a\u043b\u0456\u0454\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0456\u0454\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Flick Electric" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/de.json b/homeassistant/components/flo/translations/de.json index 382156757010d5..625c7372347a61 100644 --- a/homeassistant/components/flo/translations/de.json +++ b/homeassistant/components/flo/translations/de.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "host": "Host", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/flo/translations/tr.json b/homeassistant/components/flo/translations/tr.json new file mode 100644 index 00000000000000..40c9c39b967721 --- /dev/null +++ b/homeassistant/components/flo/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/uk.json b/homeassistant/components/flo/translations/uk.json new file mode 100644 index 00000000000000..2df11f744559db --- /dev/null +++ b/homeassistant/components/flo/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/de.json b/homeassistant/components/flume/translations/de.json index 692c38350a8303..c38a5593ac7c12 100644 --- a/homeassistant/components/flume/translations/de.json +++ b/homeassistant/components/flume/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieses Konto ist bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/flume/translations/tr.json b/homeassistant/components/flume/translations/tr.json new file mode 100644 index 00000000000000..a83e1936fb4a15 --- /dev/null +++ b/homeassistant/components/flume/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/uk.json b/homeassistant/components/flume/translations/uk.json new file mode 100644 index 00000000000000..53fb4f3d6d7e1c --- /dev/null +++ b/homeassistant/components/flume/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "client_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043a\u043b\u0456\u0454\u043d\u0442\u0430", + "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0456\u0454\u043d\u0442\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0429\u043e\u0431 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e API Flume, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 'ID \u043a\u043b\u0456\u0454\u043d\u0442\u0430' \u0456 '\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0456\u0454\u043d\u0442\u0430' \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e https://portal.flumetech.com/settings#token.", + "title": "Flume" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/de.json b/homeassistant/components/flunearyou/translations/de.json index cd2934170c9c06..1c94931f405e17 100644 --- a/homeassistant/components/flunearyou/translations/de.json +++ b/homeassistant/components/flunearyou/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten sind bereits registriert." + "already_configured": "Standort ist bereits konfiguriert" }, "error": { "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/flunearyou/translations/tr.json b/homeassistant/components/flunearyou/translations/tr.json new file mode 100644 index 00000000000000..6e749e3c8270fa --- /dev/null +++ b/homeassistant/components/flunearyou/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/uk.json b/homeassistant/components/flunearyou/translations/uk.json new file mode 100644 index 00000000000000..354a04d8e7afff --- /dev/null +++ b/homeassistant/components/flunearyou/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" + }, + "description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0445 \u0456 CDC \u0437\u0432\u0456\u0442\u0456\u0432 \u0437\u0430 \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u043c\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c\u0438.", + "title": "Flu Near You" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/de.json b/homeassistant/components/forked_daapd/translations/de.json index a3cdc53c52a102..e90ffc71f90244 100644 --- a/homeassistant/components/forked_daapd/translations/de.json +++ b/homeassistant/components/forked_daapd/translations/de.json @@ -5,7 +5,7 @@ }, "error": { "unknown_error": "Unbekannter Fehler", - "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte \u00fcberpr\u00fcfen Sie Host und Port.", + "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte Host und Port pr\u00fcfen.", "wrong_password": "Ung\u00fcltiges Passwort", "wrong_server_type": "F\u00fcr die forked-daapd Integration ist ein forked-daapd Server mit der Version > = 27.0 erforderlich." }, diff --git a/homeassistant/components/forked_daapd/translations/tr.json b/homeassistant/components/forked_daapd/translations/tr.json new file mode 100644 index 00000000000000..cf354c5c87f54c --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "unknown_error": "Beklenmeyen hata", + "wrong_password": "Yanl\u0131\u015f parola." + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "name": "Kolay ad", + "password": "API parolas\u0131 (parola yoksa bo\u015f b\u0131rak\u0131n)", + "port": "API ba\u011flant\u0131 noktas\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/uk.json b/homeassistant/components/forked_daapd/translations/uk.json new file mode 100644 index 00000000000000..19caf9b5bd0db4 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/uk.json @@ -0,0 +1,42 @@ +{ + "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.", + "not_forked_daapd": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd." + }, + "error": { + "forbidden": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 forked-daapd.", + "unknown_error": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "websocket_not_enabled": "\u0412\u0435\u0431-\u0441\u043e\u043a\u0435\u0442 forked-daapd \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439.", + "wrong_host_or_port": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u0445\u043e\u0441\u0442\u0430.", + "wrong_password": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", + "wrong_server_type": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd \u0432\u0435\u0440\u0441\u0456\u0457 27.0 \u0430\u0431\u043e \u0432\u0438\u0449\u0435." + }, + "flow_title": "\u0421\u0435\u0440\u0432\u0435\u0440 forked-daapd: {name} ({host})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c API (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0443 \u0432\u0430\u0441 \u043d\u0435\u043c\u0430\u0454 \u043f\u0430\u0440\u043e\u043b\u044f)", + "port": "\u041f\u043e\u0440\u0442 API" + }, + "title": "forked-daapd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "\u041f\u043e\u0440\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043a\u0430\u043d\u0430\u043b\u043e\u043c librespot-java (\u044f\u043a\u0449\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f)", + "max_playlists": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442\u0456\u0432, \u0449\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u044f\u043a \u0434\u0436\u0435\u0440\u0435\u043b\u0430", + "tts_pause_time": "\u0427\u0430\u0441 \u043f\u0430\u0443\u0437\u0438 \u0434\u043e \u0456 \u043f\u0456\u0441\u043b\u044f TTS (\u0441\u0435\u043a.)", + "tts_volume": "\u0413\u0443\u0447\u043d\u0456\u0441\u0442\u044c TTS (\u0447\u0438\u0441\u043b\u043e \u0432 \u0434\u0456\u0430\u043f\u0430\u0437\u043e\u043d\u0456 \u0432\u0456\u0434 0 \u0434\u043e 1)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 forked-daapd.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/af.json b/homeassistant/components/foscam/translations/af.json new file mode 100644 index 00000000000000..4a9930dd95d355 --- /dev/null +++ b/homeassistant/components/foscam/translations/af.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Senha" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ca.json b/homeassistant/components/foscam/translations/ca.json new file mode 100644 index 00000000000000..5a6c84f400e0bc --- /dev/null +++ b/homeassistant/components/foscam/translations/ca.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "port": "Port", + "stream": "Flux de v\u00eddeo", + "username": "Nom d'usuari" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/cs.json b/homeassistant/components/foscam/translations/cs.json new file mode 100644 index 00000000000000..b6f3c40abf6897 --- /dev/null +++ b/homeassistant/components/foscam/translations/cs.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json new file mode 100644 index 00000000000000..603be1847cc6b4 --- /dev/null +++ b/homeassistant/components/foscam/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/en.json b/homeassistant/components/foscam/translations/en.json index 521a22076dd3e5..3d1454a4ebd6c7 100644 --- a/homeassistant/components/foscam/translations/en.json +++ b/homeassistant/components/foscam/translations/en.json @@ -1,24 +1,24 @@ { - "config": { - "abort": { - "already_configured": "Device is already configured" - }, - "error": { - "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" - }, - "step": { - "user": { - "data": { - "host": "Host", - "password": "Password", - "port": "Port", - "stream": "Stream", - "username": "Username" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Port", + "stream": "Stream", + "username": "Username" + } + } } - } - } - }, - "title": "Foscam" -} + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/es.json b/homeassistant/components/foscam/translations/es.json new file mode 100644 index 00000000000000..27f7ac36489b36 --- /dev/null +++ b/homeassistant/components/foscam/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Contrase\u00f1a", + "port": "Puerto", + "stream": "Stream", + "username": "Usuario" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/et.json b/homeassistant/components/foscam/translations/et.json new file mode 100644 index 00000000000000..b20a33aec1d974 --- /dev/null +++ b/homeassistant/components/foscam/translations/et.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "port": "Port", + "stream": "Voog", + "username": "Kasutajanimi" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/fr.json b/homeassistant/components/foscam/translations/fr.json new file mode 100644 index 00000000000000..9af8115c305cc1 --- /dev/null +++ b/homeassistant/components/foscam/translations/fr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Echec de connection", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "port": "Port", + "stream": "Flux", + "username": "Nom d'utilisateur" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/it.json b/homeassistant/components/foscam/translations/it.json new file mode 100644 index 00000000000000..0562012b1fab0b --- /dev/null +++ b/homeassistant/components/foscam/translations/it.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Porta", + "stream": "Flusso", + "username": "Nome utente" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/lb.json b/homeassistant/components/foscam/translations/lb.json new file mode 100644 index 00000000000000..123b3f4be76d2f --- /dev/null +++ b/homeassistant/components/foscam/translations/lb.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwuert", + "port": "Port", + "stream": "Stream", + "username": "Benotzernumm" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/no.json b/homeassistant/components/foscam/translations/no.json new file mode 100644 index 00000000000000..5e1b494c88a1d1 --- /dev/null +++ b/homeassistant/components/foscam/translations/no.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "port": "Port", + "stream": "Str\u00f8m", + "username": "Brukernavn" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/pl.json b/homeassistant/components/foscam/translations/pl.json new file mode 100644 index 00000000000000..ef0bcda2b3ac96 --- /dev/null +++ b/homeassistant/components/foscam/translations/pl.json @@ -0,0 +1,24 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "stream": "Strumie\u0144", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/pt.json b/homeassistant/components/foscam/translations/pt.json new file mode 100644 index 00000000000000..b8a454fbaba9a4 --- /dev/null +++ b/homeassistant/components/foscam/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json new file mode 100644 index 00000000000000..ad8b7961ca36d7 --- /dev/null +++ b/homeassistant/components/foscam/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." + }, + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "stream": "\u041f\u043e\u0442\u043e\u043a", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/tr.json b/homeassistant/components/foscam/translations/tr.json new file mode 100644 index 00000000000000..b3e964ae08eda2 --- /dev/null +++ b/homeassistant/components/foscam/translations/tr.json @@ -0,0 +1,24 @@ +{ + "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", + "unknown": "Beklenmeyen Hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "\u015eifre", + "port": "Port", + "stream": "Ak\u0131\u015f", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/zh-Hant.json b/homeassistant/components/foscam/translations/zh-Hant.json new file mode 100644 index 00000000000000..2cc6303c17a8bb --- /dev/null +++ b/homeassistant/components/foscam/translations/zh-Hant.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "stream": "\u4e32\u6d41", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/de.json b/homeassistant/components/freebox/translations/de.json index c21e3c6b67fe70..738b9d48f3cdc2 100644 --- a/homeassistant/components/freebox/translations/de.json +++ b/homeassistant/components/freebox/translations/de.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Host bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "register_failed": "Registrieren fehlgeschlagen, bitte versuche es erneut", - "unknown": "Unbekannter Fehler: Bitte versuchen Sie es sp\u00e4ter erneut" + "unknown": "Unerwarteter Fehler" }, "step": { "link": { diff --git a/homeassistant/components/freebox/translations/tr.json b/homeassistant/components/freebox/translations/tr.json new file mode 100644 index 00000000000000..b675d38057dc64 --- /dev/null +++ b/homeassistant/components/freebox/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/uk.json b/homeassistant/components/freebox/translations/uk.json new file mode 100644 index 00000000000000..8676c9164a1902 --- /dev/null +++ b/homeassistant/components/freebox/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "register_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "link": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c '\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438', \u043f\u043e\u0442\u0456\u043c \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u0437\u0456 \u0441\u0442\u0440\u0456\u043b\u043a\u043e\u044e \u043d\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0456, \u0449\u043e\u0431 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438 Freebox \u0432 Home Assistant. \n\n![\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043d\u0430 \u0440\u043e\u0443\u0442\u0435\u0440\u0456] (/ static / images / config_freebox.png)", + "title": "Freebox" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index 8b0122dbe183b6..f8550b5bc3216c 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -4,7 +4,8 @@ "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "no_devices_found": "No s'han trobat dispositius a la xarxa", - "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home." + "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" @@ -18,6 +19,13 @@ }, "description": "Vols configurar {name}?" }, + "reauth_confirm": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Actualitza la informaci\u00f3 d'inici de sessi\u00f3 de {name}." + }, "user": { "data": { "host": "Amfitri\u00f3", diff --git a/homeassistant/components/fritzbox/translations/cs.json b/homeassistant/components/fritzbox/translations/cs.json index 67ff5db7f9952c..b3b41afe3833c1 100644 --- a/homeassistant/components/fritzbox/translations/cs.json +++ b/homeassistant/components/fritzbox/translations/cs.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", - "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" @@ -17,6 +18,12 @@ }, "description": "Chcete nastavit {name}?" }, + "reauth_confirm": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + }, "user": { "data": { "host": "Hostitel", diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 19ca2e809036cb..9b76ad19ff4fe2 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -1,10 +1,14 @@ { "config": { "abort": { - "already_configured": "Diese AVM FRITZ! Box ist bereits konfiguriert.", - "already_in_progress": "Die Konfiguration der AVM FRITZ! Box ist bereits in Bearbeitung.", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", "not_supported": "Verbunden mit AVM FRITZ! Box, kann jedoch keine Smart Home-Ger\u00e4te steuern." }, + "error": { + "invalid_auth": "Ung\u00fcltige Zugangsdaten" + }, "flow_title": "AVM FRITZ! Box: {name}", "step": { "confirm": { @@ -12,7 +16,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "M\u00f6chten Sie {name} einrichten?" + "description": "M\u00f6chtest du {name} einrichten?" }, "user": { "data": { @@ -20,7 +24,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Geben Sie Ihre AVM FRITZ! Box-Informationen ein." + "description": "Gib deine AVM FRITZ! Box-Informationen ein." } } } diff --git a/homeassistant/components/fritzbox/translations/en.json b/homeassistant/components/fritzbox/translations/en.json index 1f22bc30252cbe..61ca1e957bb2b9 100644 --- a/homeassistant/components/fritzbox/translations/en.json +++ b/homeassistant/components/fritzbox/translations/en.json @@ -4,7 +4,8 @@ "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "no_devices_found": "No devices found on the network", - "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices." + "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices.", + "reauth_successful": "Re-authentication was successful" }, "error": { "invalid_auth": "Invalid authentication" @@ -18,6 +19,13 @@ }, "description": "Do you want to set up {name}?" }, + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Username" + }, + "description": "Update your login information for {name}." + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritzbox/translations/et.json b/homeassistant/components/fritzbox/translations/et.json index 702488bce0c8a1..5ee2dc801f4610 100644 --- a/homeassistant/components/fritzbox/translations/et.json +++ b/homeassistant/components/fritzbox/translations/et.json @@ -4,7 +4,8 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "no_devices_found": "V\u00f5rgust ei leitud seadmeid", - "not_supported": "\u00dchendatud AVM FRITZ!Boxiga! kuid see ei saa juhtida Smart Home seadmeid." + "not_supported": "\u00dchendatud AVM FRITZ!Boxiga! kuid see ei saa juhtida Smart Home seadmeid.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "invalid_auth": "Tuvastamise viga" @@ -18,6 +19,13 @@ }, "description": "Kas soovid seadistada {name}?" }, + "reauth_confirm": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "V\u00e4rskenda konto {name} sisselogimisteavet." + }, "user": { "data": { "host": "", diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index ab44ae138631ad..a420b3f6de767a 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -4,7 +4,8 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "no_devices_found": "Nessun dispositivo trovato sulla rete", - "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home." + "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home.", + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { "invalid_auth": "Autenticazione non valida" @@ -18,6 +19,13 @@ }, "description": "Vuoi impostare {name}?" }, + "reauth_confirm": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Aggiorna le tue informazioni di accesso per {name}." + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index 024e25741a7a1a..bd64b428bdf99f 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -4,7 +4,8 @@ "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", - "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter." + "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { "invalid_auth": "Ugyldig godkjenning" @@ -18,6 +19,13 @@ }, "description": "Vil du sette opp {name} ?" }, + "reauth_confirm": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Oppdater p\u00e5loggingsinformasjonen for {name} ." + }, "user": { "data": { "host": "Vert", diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json index fc1623101895e6..dc05e431832259 100644 --- a/homeassistant/components/fritzbox/translations/pl.json +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -4,7 +4,8 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", - "not_supported": "Po\u0142\u0105czony z AVM FRITZ!Box, ale nie jest w stanie kontrolowa\u0107 urz\u0105dze\u0144 Smart Home" + "not_supported": "Po\u0142\u0105czony z AVM FRITZ!Box, ale nie jest w stanie kontrolowa\u0107 urz\u0105dze\u0144 Smart Home", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "invalid_auth": "Niepoprawne uwierzytelnienie" @@ -18,6 +19,13 @@ }, "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, + "reauth_confirm": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Zaktualizuj dane logowania dla {name}" + }, "user": { "data": { "host": "Nazwa hosta lub adres IP", diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 322f677c2afacf..50146b490ba818 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -4,7 +4,8 @@ "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.", "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_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e." + "not_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e.", + "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": { "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." @@ -18,6 +19,13 @@ }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, + "reauth_confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0434\u043b\u044f {name}." + }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/fritzbox/translations/tr.json b/homeassistant/components/fritzbox/translations/tr.json new file mode 100644 index 00000000000000..746fe594e19902 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "confirm": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "{name} kurmak istiyor musunuz?" + }, + "reauth_confirm": { + "data": { + "password": "\u015eifre", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Giri\u015f bilgilerinizi {name} i\u00e7in g\u00fcncelleyin." + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/uk.json b/homeassistant/components/fritzbox/translations/uk.json new file mode 100644 index 00000000000000..5a2d8a1c35e06d --- /dev/null +++ b/homeassistant/components/fritzbox/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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_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.", + "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.", + "not_supported": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AVM FRITZ! Box \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e, \u0430\u043b\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044f\u043c\u0438 Smart Home \u043d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 AVM FRITZ! Box." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/zh-Hant.json b/homeassistant/components/fritzbox/translations/zh-Hant.json index 7b85df577ef148..71a74785267681 100644 --- a/homeassistant/components/fritzbox/translations/zh-Hant.json +++ b/homeassistant/components/fritzbox/translations/zh-Hant.json @@ -4,7 +4,8 @@ "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", - "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u88dd\u7f6e\u3002" + "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u88dd\u7f6e\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" @@ -18,6 +19,13 @@ }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, + "reauth_confirm": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u66f4\u65b0 {name} \u767b\u5165\u8cc7\u8a0a\u3002" + }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef", diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ca.json b/homeassistant/components/fritzbox_callmonitor/translations/ca.json new file mode 100644 index 00000000000000..808b642f4ff680 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/ca.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "insufficient_permissions": "L'usuari no t\u00e9 permisos suficients per accedir a la configuraci\u00f3 d'AVM FRITZ!Box i les seves agendes.", + "no_devices_found": "No s'han trobat dispositius a la xarxa" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "flow_title": "Sensor de trucades d'AVM FRITZ!Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Agenda" + } + }, + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "port": "Port", + "username": "Nom d'usuari" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "El format dels prefixos no \u00e9s correcte, comprova'l." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefixos (llista separada per comes)" + }, + "title": "Configuraci\u00f3 dels prefixos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/cs.json b/homeassistant/components/fritzbox_callmonitor/translations/cs.json new file mode 100644 index 00000000000000..c40da2900bcb80 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "no_devices_found": "V s\u00edti nebyla nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed" + }, + "error": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/et.json b/homeassistant/components/fritzbox_callmonitor/translations/et.json new file mode 100644 index 00000000000000..7770f31ae0e073 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/et.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "insufficient_permissions": "Kasutajal ei ole piisavalt \u00f5igusi juurdep\u00e4\u00e4suks AVM FRITZ! Box'i seadetele jatelefoniraamatutele.", + "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi seadet" + }, + "error": { + "invalid_auth": "Vigane autentimine" + }, + "flow_title": "AVM FRITZ! K\u00f5nekontroll: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefoniraamat" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "port": "Port", + "username": "Kasutajanimi" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Eesliited on valesti vormindatud, kontrolli nende vormingut." + }, + "step": { + "init": { + "data": { + "prefixes": "Eesliited (komadega eraldatud loend)" + }, + "title": "Eesliidete seadistamine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/it.json b/homeassistant/components/fritzbox_callmonitor/translations/it.json new file mode 100644 index 00000000000000..5696bf86fd1f7d --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/it.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "insufficient_permissions": "L'utente non dispone di autorizzazioni sufficienti per accedere alle impostazioni di AVM FRITZ! Box e alle sue rubriche.", + "no_devices_found": "Nessun dispositivo trovato sulla rete" + }, + "error": { + "invalid_auth": "Autenticazione non valida" + }, + "flow_title": "Monitoraggio chiamate FRITZ! Box AVM: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Rubrica telefonica" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Password", + "port": "Porta", + "username": "Nome utente" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "I prefissi non sono corretti, controlla il loro formato." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefissi (elenco separato da virgole)" + }, + "title": "Configura prefissi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/lb.json b/homeassistant/components/fritzbox_callmonitor/translations/lb.json new file mode 100644 index 00000000000000..67b5879a557a2c --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/lb.json @@ -0,0 +1,33 @@ +{ + "config": { + "flow_title": "AVM FRITZ!Box Call Monitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Adressbuch" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Passwuert", + "port": "Port", + "username": "Benotzernumm" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Pr\u00e9fixe sinn am falsche Format, iwwerpr\u00e9if dat w.e.g" + }, + "step": { + "init": { + "data": { + "prefixes": "Pr\u00e9fixe (komma getrennte L\u00ebscht)" + }, + "title": "Pr\u00e9fixe konfigur\u00e9ieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/no.json b/homeassistant/components/fritzbox_callmonitor/translations/no.json new file mode 100644 index 00000000000000..12883b0140d518 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/no.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "insufficient_permissions": "Brukeren har utilstrekkelig tillatelse til \u00e5 f\u00e5 tilgang til AVM FRITZ! Box-innstillingene og telefonb\u00f8kene.", + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning" + }, + "flow_title": "AVM FRITZ! Box monitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefonbok" + } + }, + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "port": "Port", + "username": "Brukernavn" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Prefikser er misformet, vennligst sjekk deres format." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefikser (kommaseparert liste)" + }, + "title": "Konfigurer prefiks" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/pl.json b/homeassistant/components/fritzbox_callmonitor/translations/pl.json new file mode 100644 index 00000000000000..fa0317f5c9d913 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/pl.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "insufficient_permissions": "U\u017cytkownik ma niewystarczaj\u0105ce uprawnienia, aby uzyska\u0107 dost\u0119p do ustawie\u0144 AVM FRITZ! Box i jego ksi\u0105\u017cek telefonicznych.", + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie" + }, + "flow_title": "Monitor po\u0142\u0105cze\u0144 AVM FRITZ!Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Ksi\u0105\u017cka telefoniczna" + } + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Prefiksy s\u0105 nieprawid\u0142owe, prosz\u0119 sprawdzi\u0107 ich format." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefiksy (lista oddzielona przecinkami)" + }, + "title": "Skonfiguruj prefiksy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ru.json b/homeassistant/components/fritzbox_callmonitor/translations/ru.json new file mode 100644 index 00000000000000..3eb432532c45b9 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/ru.json @@ -0,0 +1,41 @@ +{ + "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.", + "insufficient_permissions": "\u0423 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u0430\u0432 \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c AVM FRITZ!Box \u0438 \u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u044b\u043c \u043a\u043d\u0438\u0433\u0430\u043c.", + "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": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "flow_title": "AVM FRITZ!Box call monitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u043d\u0430\u044f \u043a\u043d\u0438\u0433\u0430" + } + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441\u044b \u0438\u043c\u0435\u044e\u0442 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0438\u0445." + }, + "step": { + "init": { + "data": { + "prefixes": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441\u044b (\u0441\u043f\u0438\u0441\u043e\u043a, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u044f\u0442\u044b\u043c\u0438)" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/tr.json b/homeassistant/components/fritzbox_callmonitor/translations/tr.json new file mode 100644 index 00000000000000..76799f24af824f --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "insufficient_permissions": "Kullan\u0131c\u0131, AVM FRITZ! Box ayarlar\u0131na ve telefon defterlerine eri\u015fmek i\u00e7in yeterli izne sahip de\u011fil.", + "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "flow_title": "AVM FRITZ! Box \u00e7a\u011fr\u0131 monit\u00f6r\u00fc: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefon rehberi" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "\u015eifre", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u00d6nekler yanl\u0131\u015f bi\u00e7imlendirilmi\u015ftir, l\u00fctfen bi\u00e7imlerini kontrol edin." + }, + "step": { + "init": { + "data": { + "prefixes": "\u00d6nekler (virg\u00fclle ayr\u0131lm\u0131\u015f liste)" + }, + "title": "\u00d6nekleri Yap\u0131land\u0131r" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json b/homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json new file mode 100644 index 00000000000000..d159f5df0f9706 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/zh-Hant.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "insufficient_permissions": "\u4f7f\u7528\u8005\u6c92\u6709\u8db3\u5920\u6b0a\u9650\u4ee5\u5b58\u53d6 AVM FRITZ!Box \u8a2d\u5b9a\u53ca\u96fb\u8a71\u7c3f\u3002", + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "flow_title": "AVM FRITZ!Box \u901a\u8a71\u76e3\u63a7\u5668\uff1a{name}", + "step": { + "phonebook": { + "data": { + "phonebook": "\u96fb\u8a71\u7c3f" + } + }, + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "\u524d\u7db4\u5b57\u9996\u683c\u5f0f\u932f\u8aa4\uff0c\u8acb\u518d\u78ba\u8a8d\u5176\u683c\u5f0f\u3002" + }, + "step": { + "init": { + "data": { + "prefixes": "\u524d\u7db4\u5b57\u9996\uff08\u4ee5\u9017\u865f\u5206\u9694\uff09" + }, + "title": "\u8a2d\u5b9a\u524d\u7db4\u5b57\u9996" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/de.json b/homeassistant/components/garmin_connect/translations/de.json index 54d27e9956e0a7..9186f753a77246 100644 --- a/homeassistant/components/garmin_connect/translations/de.json +++ b/homeassistant/components/garmin_connect/translations/de.json @@ -4,10 +4,10 @@ "already_configured": "Dieses Konto ist bereits konfiguriert." }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.", - "invalid_auth": "Ung\u00fcltige Authentifizierung.", - "too_many_requests": "Zu viele Anfragen, wiederholen Sie es sp\u00e4ter.", - "unknown": "Unerwarteter Fehler." + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "too_many_requests": "Zu viele Anfragen, versuche es sp\u00e4ter erneut.", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/tr.json b/homeassistant/components/garmin_connect/translations/tr.json new file mode 100644 index 00000000000000..a83e1936fb4a15 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/uk.json b/homeassistant/components/garmin_connect/translations/uk.json new file mode 100644 index 00000000000000..aef0632b0f17d7 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "too_many_requests": "\u0417\u0430\u043d\u0430\u0434\u0442\u043e \u0431\u0430\u0433\u0430\u0442\u043e \u0437\u0430\u043f\u0438\u0442\u0456\u0432, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "Garmin Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/de.json b/homeassistant/components/gdacs/translations/de.json index 07d1a4bdb79bf7..a69295f06406a4 100644 --- a/homeassistant/components/gdacs/translations/de.json +++ b/homeassistant/components/gdacs/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Der Standort ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/tr.json b/homeassistant/components/gdacs/translations/tr.json new file mode 100644 index 00000000000000..aeb6a5a345e28a --- /dev/null +++ b/homeassistant/components/gdacs/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "radius": "Yar\u0131\u00e7ap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/uk.json b/homeassistant/components/gdacs/translations/uk.json new file mode 100644 index 00000000000000..0ab20bc55a37e8 --- /dev/null +++ b/homeassistant/components/gdacs/translations/uk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441" + }, + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/de.json b/homeassistant/components/geofency/translations/de.json index 31b8a5eb321501..9c3fd3ea1b0de7 100644 --- a/homeassistant/components/geofency/translations/de.json +++ b/homeassistant/components/geofency/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an den Home Assistant zu senden, musst das Webhook Feature in Geofency konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/geofency/translations/tr.json b/homeassistant/components/geofency/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/geofency/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/uk.json b/homeassistant/components/geofency/translations/uk.json new file mode 100644 index 00000000000000..54a14afb764d9d --- /dev/null +++ b/homeassistant/components/geofency/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Geofency. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Geofency?", + "title": "Geofency" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/tr.json b/homeassistant/components/geonetnz_quakes/translations/tr.json new file mode 100644 index 00000000000000..717f6d72b94e5d --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/uk.json b/homeassistant/components/geonetnz_quakes/translations/uk.json new file mode 100644 index 00000000000000..35653baa945886 --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "mmi": "MMI", + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441" + }, + "title": "GeoNet NZ Quakes" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/de.json b/homeassistant/components/geonetnz_volcano/translations/de.json index b573d93cd5a570..a29555e53ab71d 100644 --- a/homeassistant/components/geonetnz_volcano/translations/de.json +++ b/homeassistant/components/geonetnz_volcano/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_volcano/translations/tr.json b/homeassistant/components/geonetnz_volcano/translations/tr.json new file mode 100644 index 00000000000000..980be333568596 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "radius": "Yar\u0131\u00e7ap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/uk.json b/homeassistant/components/geonetnz_volcano/translations/uk.json new file mode 100644 index 00000000000000..77a4f1eee68568 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/uk.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "step": { + "user": { + "data": { + "radius": "\u0420\u0430\u0434\u0456\u0443\u0441" + }, + "title": "GeoNet NZ Volcano" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/de.json b/homeassistant/components/gios/translations/de.json index 0a5cea1819dbc7..7bbb01cf18db27 100644 --- a/homeassistant/components/gios/translations/de.json +++ b/homeassistant/components/gios/translations/de.json @@ -4,14 +4,14 @@ "already_configured": "GIO\u015a integration f\u00fcr diese Messstation ist bereits konfiguriert. " }, "error": { - "cannot_connect": "Es kann keine Verbindung zum GIO\u015a-Server hergestellt werden.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_sensors_data": "Ung\u00fcltige Sensordaten f\u00fcr diese Messstation.", "wrong_station_id": "ID der Messstation ist nicht korrekt." }, "step": { "user": { "data": { - "name": "Name der Integration", + "name": "Name", "station_id": "ID der Messstation" }, "description": "Einrichtung von GIO\u015a (Polnische Hauptinspektion f\u00fcr Umweltschutz) Integration der Luftqualit\u00e4t. Wenn du Hilfe bei der Konfiguration ben\u00f6tigst, schaue hier: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/gios/translations/fr.json b/homeassistant/components/gios/translations/fr.json index b06c41208bc374..2b02b5cfea086d 100644 --- a/homeassistant/components/gios/translations/fr.json +++ b/homeassistant/components/gios/translations/fr.json @@ -18,5 +18,10 @@ "title": "GIO\u015a (Inspection g\u00e9n\u00e9rale polonaise de la protection de l'environnement)" } } + }, + "system_health": { + "info": { + "can_reach_server": "Acc\u00e9der au serveur GIO\u015a" + } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/it.json b/homeassistant/components/gios/translations/it.json index 26bf8386d6669f..5d1e99d17f400f 100644 --- a/homeassistant/components/gios/translations/it.json +++ b/homeassistant/components/gios/translations/it.json @@ -21,7 +21,7 @@ }, "system_health": { "info": { - "can_reach_server": "Raggiungi il server GIO\u015a" + "can_reach_server": "Server GIO\u015a raggiungibile" } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/lb.json b/homeassistant/components/gios/translations/lb.json index 8e8ab861b437e7..cafea72fb78296 100644 --- a/homeassistant/components/gios/translations/lb.json +++ b/homeassistant/components/gios/translations/lb.json @@ -18,5 +18,10 @@ "title": "GIO\u015a (Polnesch Chefinspektorat vum \u00cbmweltschutz)" } } + }, + "system_health": { + "info": { + "can_reach_server": "GIO\u015a Server ereechbar" + } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/tr.json b/homeassistant/components/gios/translations/tr.json new file mode 100644 index 00000000000000..590aec1894cc3b --- /dev/null +++ b/homeassistant/components/gios/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/uk.json b/homeassistant/components/gios/translations/uk.json new file mode 100644 index 00000000000000..f62408c5e8ecdb --- /dev/null +++ b/homeassistant/components/gios/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_sensors_data": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457.", + "wrong_station_id": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 ID \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "station_id": "ID \u0432\u0438\u043c\u0456\u0440\u044e\u0432\u0430\u043b\u044c\u043d\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457" + }, + "description": "\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u043f\u0440\u043e \u044f\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0456\u0442\u0440\u044f \u0432\u0456\u0434 \u041f\u043e\u043b\u044c\u0441\u044c\u043a\u043e\u0457 \u0456\u043d\u0441\u043f\u0435\u043a\u0446\u0456\u0457 \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \u043d\u0430\u0432\u043a\u043e\u043b\u0438\u0448\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0430 (GIO\u015a). \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e \u043f\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457: https://www.home-assistant.io/integrations/gios.", + "title": "GIO\u015a (\u041f\u043e\u043b\u044c\u0441\u044c\u043a\u0430 \u0456\u043d\u0441\u043f\u0435\u043a\u0446\u0456\u044f \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438 \u043d\u0430\u0432\u043a\u043e\u043b\u0438\u0448\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0430)" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 GIO\u015a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/de.json b/homeassistant/components/glances/translations/de.json index 69c34907f19b64..e464bfdee34106 100644 --- a/homeassistant/components/glances/translations/de.json +++ b/homeassistant/components/glances/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung zum Host nicht m\u00f6glich", + "cannot_connect": "Verbindung fehlgeschlagen", "wrong_version": "Version nicht unterst\u00fctzt (nur 2 oder 3)" }, "step": { diff --git a/homeassistant/components/glances/translations/tr.json b/homeassistant/components/glances/translations/tr.json new file mode 100644 index 00000000000000..69f0cd7ceb123f --- /dev/null +++ b/homeassistant/components/glances/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "G\u00fcncelleme s\u0131kl\u0131\u011f\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/uk.json b/homeassistant/components/glances/translations/uk.json new file mode 100644 index 00000000000000..1fab197fe4291b --- /dev/null +++ b/homeassistant/components/glances/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "wrong_version": "\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u0432\u0435\u0440\u0441\u0456\u0457 2 \u0442\u0430 3." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL", + "version": "\u0412\u0435\u0440\u0441\u0456\u044f API Glances (2 \u0430\u0431\u043e 3)" + }, + "title": "Glances" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "description": "\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 Glances" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/de.json b/homeassistant/components/goalzero/translations/de.json index d79c03f0179fbe..7d8962cdb1164c 100644 --- a/homeassistant/components/goalzero/translations/de.json +++ b/homeassistant/components/goalzero/translations/de.json @@ -1,9 +1,20 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP Adresse", "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/fr.json b/homeassistant/components/goalzero/translations/fr.json index 5c4b7a01580ba1..7bd4929ad929cd 100644 --- a/homeassistant/components/goalzero/translations/fr.json +++ b/homeassistant/components/goalzero/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion", diff --git a/homeassistant/components/goalzero/translations/tr.json b/homeassistant/components/goalzero/translations/tr.json new file mode 100644 index 00000000000000..ae77262b2b3f9a --- /dev/null +++ b/homeassistant/components/goalzero/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/uk.json b/homeassistant/components/goalzero/translations/uk.json new file mode 100644 index 00000000000000..6d67d949c28692 --- /dev/null +++ b/homeassistant/components/goalzero/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Goal Zero: https://www.goalzero.com/product-features/yeti-app/. \n\n \u0414\u043e\u0442\u0440\u0438\u043c\u0443\u0439\u0442\u0435\u0441\u044c \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439 \u043f\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044e Yeti \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456 WiFi. \u041f\u043e\u0442\u0456\u043c \u0434\u0456\u0437\u043d\u0430\u0439\u0442\u0435\u0441\u044f IP \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, \u0437 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0442\u0430\u043a\u0438\u043c\u0438, \u0449\u043e\u0431 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0432\u0430\u043b\u0430\u0441\u044c \u0437 \u0447\u0430\u0441\u043e\u043c. \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u0446\u0435 \u0437\u0440\u043e\u0431\u0438\u0442\u0438, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0412\u0430\u0448\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/tr.json b/homeassistant/components/gogogate2/translations/tr.json new file mode 100644 index 00000000000000..e912e7f8012f86 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/uk.json b/homeassistant/components/gogogate2/translations/uk.json new file mode 100644 index 00000000000000..c88b9b603840d3 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 GogoGate2.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f GogoGate2 \u0430\u0431\u043e iSmartGate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/de.json b/homeassistant/components/gpslogger/translations/de.json index d976a5fd6638fa..7215f0c458f623 100644 --- a/homeassistant/components/gpslogger/translations/de.json +++ b/homeassistant/components/gpslogger/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in der GPSLogger konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/gpslogger/translations/tr.json b/homeassistant/components/gpslogger/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/uk.json b/homeassistant/components/gpslogger/translations/uk.json new file mode 100644 index 00000000000000..5b0b6305cdb638 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f GPSLogger. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 GPSLogger?", + "title": "GPSLogger" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/de.json b/homeassistant/components/gree/translations/de.json new file mode 100644 index 00000000000000..96ed09a974f40d --- /dev/null +++ b/homeassistant/components/gree/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "confirm": { + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/tr.json b/homeassistant/components/gree/translations/tr.json new file mode 100644 index 00000000000000..8de4663957ea85 --- /dev/null +++ b/homeassistant/components/gree/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/uk.json b/homeassistant/components/gree/translations/uk.json new file mode 100644 index 00000000000000..292861e9129dbd --- /dev/null +++ b/homeassistant/components/gree/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/de.json b/homeassistant/components/griddy/translations/de.json index ad6a6e10ab0514..4a6c477059ccf4 100644 --- a/homeassistant/components/griddy/translations/de.json +++ b/homeassistant/components/griddy/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Diese Ladezone ist bereits konfiguriert" + "already_configured": "Standort ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/griddy/translations/tr.json b/homeassistant/components/griddy/translations/tr.json index d887b1486584fa..26e0fa73065088 100644 --- a/homeassistant/components/griddy/translations/tr.json +++ b/homeassistant/components/griddy/translations/tr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, "error": { "cannot_connect": "Ba\u011flant\u0131 kurulamad\u0131, l\u00fctfen tekrar deneyin", "unknown": "Beklenmeyen hata" diff --git a/homeassistant/components/griddy/translations/uk.json b/homeassistant/components/griddy/translations/uk.json new file mode 100644 index 00000000000000..e366f0e8b24e0b --- /dev/null +++ b/homeassistant/components/griddy/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "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": { + "loadzone": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f (\u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0430 \u0442\u043e\u0447\u043a\u0430)" + }, + "description": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0443 \u0432\u0430\u0448\u043e\u043c\u0443 \u043f\u0440\u043e\u0444\u0456\u043b\u0456 Griddy \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 Account > Meter > Load Zone.", + "title": "Griddy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/group/translations/uk.json b/homeassistant/components/group/translations/uk.json index 2d57686134a7d8..08cee558f273ce 100644 --- a/homeassistant/components/group/translations/uk.json +++ b/homeassistant/components/group/translations/uk.json @@ -9,7 +9,7 @@ "ok": "\u041e\u041a", "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", "open": "\u0412\u0456\u0434\u0447\u0438\u043d\u0435\u043d\u043e", - "problem": "\u0425\u0430\u043b\u0435\u043f\u0430", + "problem": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430", "unlocked": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e" } }, diff --git a/homeassistant/components/guardian/translations/de.json b/homeassistant/components/guardian/translations/de.json index 27770d690f0ac7..d1218cb23729a9 100644 --- a/homeassistant/components/guardian/translations/de.json +++ b/homeassistant/components/guardian/translations/de.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindungsfehler" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/guardian/translations/tr.json b/homeassistant/components/guardian/translations/tr.json new file mode 100644 index 00000000000000..1e520a16995d97 --- /dev/null +++ b/homeassistant/components/guardian/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/uk.json b/homeassistant/components/guardian/translations/uk.json new file mode 100644 index 00000000000000..439a225895e8fd --- /dev/null +++ b/homeassistant/components/guardian/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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_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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Elexa Guardian?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/de.json b/homeassistant/components/hangouts/translations/de.json index 5c8ab51cf4e8c8..7b888cf531e079 100644 --- a/homeassistant/components/hangouts/translations/de.json +++ b/homeassistant/components/hangouts/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Google Hangouts ist bereits konfiguriert", - "unknown": "Ein unbekannter Fehler ist aufgetreten." + "unknown": "Unerwarteter Fehler" }, "error": { "invalid_2fa": "Ung\u00fcltige 2-Faktor Authentifizierung, bitte versuche es erneut.", diff --git a/homeassistant/components/hangouts/translations/tr.json b/homeassistant/components/hangouts/translations/tr.json new file mode 100644 index 00000000000000..a204200a2d8438 --- /dev/null +++ b/homeassistant/components/hangouts/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/uk.json b/homeassistant/components/hangouts/translations/uk.json new file mode 100644 index 00000000000000..93eb699d37c80b --- /dev/null +++ b/homeassistant/components/hangouts/translations/uk.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "invalid_2fa": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "invalid_2fa_method": "\u041d\u0435\u043f\u0440\u0438\u043f\u0443\u0441\u0442\u0438\u043c\u0438\u0439 \u0441\u043f\u043e\u0441\u0456\u0431 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 (\u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 \u043d\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0456).", + "invalid_login": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041f\u0456\u043d-\u043a\u043e\u0434 \u0434\u043b\u044f \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "description": "\u043f\u043e\u0440\u043e\u0436\u043d\u044c\u043e", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "authorization_code": "\u041a\u043e\u0434 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 (\u0432\u0438\u043c\u0430\u0433\u0430\u0454\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457)", + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u043f\u043e\u0440\u043e\u0436\u043d\u044c\u043e", + "title": "Google Hangouts" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/de.json b/homeassistant/components/harmony/translations/de.json index f10dfe1432cb56..9cd07f09529f44 100644 --- a/homeassistant/components/harmony/translations/de.json +++ b/homeassistant/components/harmony/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "flow_title": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/tr.json b/homeassistant/components/harmony/translations/tr.json new file mode 100644 index 00000000000000..c77f0f8e07e7ba --- /dev/null +++ b/homeassistant/components/harmony/translations/tr.json @@ -0,0 +1,18 @@ +{ + "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": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/uk.json b/homeassistant/components/harmony/translations/uk.json new file mode 100644 index 00000000000000..5bb2da811f3d5f --- /dev/null +++ b/homeassistant/components/harmony/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "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" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", + "title": "Logitech Harmony Hub" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "\u0410\u043a\u0442\u0438\u0432\u043d\u0456\u0441\u0442\u044c \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c, \u043a\u043e\u043b\u0438 \u0436\u043e\u0434\u043d\u0430 \u0437 \u043d\u0438\u0445 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0430.", + "delay_secs": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 \u043c\u0456\u0436 \u043d\u0430\u0434\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u043a\u043e\u043c\u0430\u043d\u0434." + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 Harmony Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ca.json b/homeassistant/components/hassio/translations/ca.json index 0cdb9318428fc1..19b4316c9ce6b9 100644 --- a/homeassistant/components/hassio/translations/ca.json +++ b/homeassistant/components/hassio/translations/ca.json @@ -12,7 +12,7 @@ "supervisor_version": "Versi\u00f3 del Supervisor", "supported": "Compatible", "update_channel": "Canal d'actualitzaci\u00f3", - "version_api": "API de versions" + "version_api": "Versi\u00f3 d'APIs" } }, "title": "Hass.io" diff --git a/homeassistant/components/hassio/translations/de.json b/homeassistant/components/hassio/translations/de.json index 981cb51c83ab8b..939821edb54aa3 100644 --- a/homeassistant/components/hassio/translations/de.json +++ b/homeassistant/components/hassio/translations/de.json @@ -1,3 +1,18 @@ { + "system_health": { + "info": { + "board": "Board", + "disk_total": "Speicherplatz gesamt", + "disk_used": "Speicherplatz genutzt", + "docker_version": "Docker-Version", + "host_os": "Host-Betriebssystem", + "installed_addons": "Installierte Add-ons", + "supervisor_api": "Supervisor-API", + "supervisor_version": "Supervisor-Version", + "supported": "Unterst\u00fctzt", + "update_channel": "Update-Channel", + "version_api": "Versions-API" + } + }, "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/en.json b/homeassistant/components/hassio/translations/en.json index aadcdabfb941de..230e0c11feae84 100644 --- a/homeassistant/components/hassio/translations/en.json +++ b/homeassistant/components/hassio/translations/en.json @@ -9,11 +9,11 @@ "host_os": "Host Operating System", "installed_addons": "Installed Add-ons", "supervisor_api": "Supervisor API", - "supervisor_version": "Version", + "supervisor_version": "Supervisor Version", "supported": "Supported", "update_channel": "Update Channel", "version_api": "Version API" } }, - "title": "Home Assistant Supervisor" + "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fr.json b/homeassistant/components/hassio/translations/fr.json index 981cb51c83ab8b..2bb52c3c54c106 100644 --- a/homeassistant/components/hassio/translations/fr.json +++ b/homeassistant/components/hassio/translations/fr.json @@ -1,3 +1,18 @@ { + "system_health": { + "info": { + "board": "Tableau de bord", + "disk_total": "Taille total du disque", + "disk_used": "Taille du disque utilis\u00e9", + "docker_version": "Version de Docker", + "healthy": "Sain", + "installed_addons": "Add-ons install\u00e9s", + "supervisor_api": "API du superviseur", + "supervisor_version": "Version du supervisor", + "supported": "Prise en charge", + "update_channel": "Mise \u00e0 jour", + "version_api": "Version API" + } + }, "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/tr.json b/homeassistant/components/hassio/translations/tr.json index d368ac0fb3c903..f2c2d52f60df92 100644 --- a/homeassistant/components/hassio/translations/tr.json +++ b/homeassistant/components/hassio/translations/tr.json @@ -6,7 +6,13 @@ "disk_used": "Kullan\u0131lan Disk", "docker_version": "Docker S\u00fcr\u00fcm\u00fc", "healthy": "Sa\u011fl\u0131kl\u0131", - "host_os": "Ana Bilgisayar \u0130\u015fletim Sistemi" + "host_os": "Ana Bilgisayar \u0130\u015fletim Sistemi", + "installed_addons": "Y\u00fckl\u00fc Eklentiler", + "supervisor_api": "Supervisor API", + "supervisor_version": "S\u00fcperviz\u00f6r S\u00fcr\u00fcm\u00fc", + "supported": "Destekleniyor", + "update_channel": "Kanal\u0131 G\u00fcncelle", + "version_api": "S\u00fcr\u00fcm API" } }, "title": "Hass.io" diff --git a/homeassistant/components/hassio/translations/uk.json b/homeassistant/components/hassio/translations/uk.json index 981cb51c83ab8b..19a40730897bad 100644 --- a/homeassistant/components/hassio/translations/uk.json +++ b/homeassistant/components/hassio/translations/uk.json @@ -1,3 +1,19 @@ { + "system_health": { + "info": { + "board": "\u041f\u043b\u0430\u0442\u0430", + "disk_total": "\u0414\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u0430\u043c'\u044f\u0442\u044c", + "disk_used": "\u041f\u0430\u043c'\u044f\u0442\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043e", + "docker_version": "\u0412\u0435\u0440\u0441\u0456\u044f Docker", + "healthy": "\u0412 \u043d\u043e\u0440\u043c\u0456", + "host_os": "\u041e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0445\u043e\u0441\u0442\u0430", + "installed_addons": "\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0456 \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f", + "supervisor_api": "Supervisor API", + "supervisor_version": "\u0412\u0435\u0440\u0441\u0456\u044f Supervisor", + "supported": "\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430", + "update_channel": "\u041a\u0430\u043d\u0430\u043b \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c", + "version_api": "\u0412\u0435\u0440\u0441\u0456\u044f API" + } + }, "title": "Hass.io" } \ No newline at end of file diff --git a/homeassistant/components/heos/translations/de.json b/homeassistant/components/heos/translations/de.json index 92ab6c1c8ff145..ba8a5318951226 100644 --- a/homeassistant/components/heos/translations/de.json +++ b/homeassistant/components/heos/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/heos/translations/tr.json b/homeassistant/components/heos/translations/tr.json new file mode 100644 index 00000000000000..4f1ad7759054c4 --- /dev/null +++ b/homeassistant/components/heos/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/uk.json b/homeassistant/components/heos/translations/uk.json new file mode 100644 index 00000000000000..c0a5fdf04bf94f --- /dev/null +++ b/homeassistant/components/heos/translations/uk.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043c'\u044f \u0445\u043e\u0441\u0442\u0430 \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e HEOS (\u0431\u0430\u0436\u0430\u043d\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0431\u0435\u043b\u044c).", + "title": "HEOS" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/de.json b/homeassistant/components/hisense_aehw4a1/translations/de.json index d5f4f4297401c7..7c0bd96a9c9cee 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/de.json +++ b/homeassistant/components/hisense_aehw4a1/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine Hisense AEH-W4A1-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von Hisense AEH-W4A1 m\u00f6glich." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/tr.json b/homeassistant/components/hisense_aehw4a1/translations/tr.json new file mode 100644 index 00000000000000..a893a653a78c9a --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Hisense AEH-W4A1'i kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/uk.json b/homeassistant/components/hisense_aehw4a1/translations/uk.json new file mode 100644 index 00000000000000..900882513d5128 --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Hisense AEH-W4A1?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/de.json b/homeassistant/components/hlk_sw16/translations/de.json index 94b8d6526d13ba..625c7372347a61 100644 --- a/homeassistant/components/hlk_sw16/translations/de.json +++ b/homeassistant/components/hlk_sw16/translations/de.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "host": "Host", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/hlk_sw16/translations/tr.json b/homeassistant/components/hlk_sw16/translations/tr.json new file mode 100644 index 00000000000000..40c9c39b967721 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/uk.json b/homeassistant/components/hlk_sw16/translations/uk.json new file mode 100644 index 00000000000000..2df11f744559db --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/de.json b/homeassistant/components/home_connect/translations/de.json index 05204c35c4120f..2454c039361105 100644 --- a/homeassistant/components/home_connect/translations/de.json +++ b/homeassistant/components/home_connect/translations/de.json @@ -1,14 +1,15 @@ { "config": { "abort": { - "missing_configuration": "Die Komponente Home Connect ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "create_entry": { - "default": "Erfolgreich mit Home Connect authentifiziert." + "default": "Erfolgreich authentifiziert" }, "step": { "pick_implementation": { - "title": "Authentifizierungsmethode ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/home_connect/translations/uk.json b/homeassistant/components/home_connect/translations/uk.json new file mode 100644 index 00000000000000..247ffd16713cbf --- /dev/null +++ b/homeassistant/components/home_connect/translations/uk.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/de.json b/homeassistant/components/homeassistant/translations/de.json index 45768a9f127009..e24568ff2125ca 100644 --- a/homeassistant/components/homeassistant/translations/de.json +++ b/homeassistant/components/homeassistant/translations/de.json @@ -1,10 +1,21 @@ { "system_health": { "info": { + "arch": "CPU-Architektur", + "chassis": "Chassis", + "dev": "Entwicklung", "docker": "Docker", "docker_version": "Docker", "hassio": "Supervisor", - "host_os": "Home Assistant OS" + "host_os": "Home Assistant OS", + "installation_type": "Installationstyp", + "os_name": "Betriebssystemfamilie", + "os_version": "Betriebssystem-Version", + "python_version": "Python-Version", + "supervisor": "Supervisor", + "timezone": "Zeitzone", + "version": "Version", + "virtualenv": "Virtuelle Umgebung" } } } \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/fr.json b/homeassistant/components/homeassistant/translations/fr.json new file mode 100644 index 00000000000000..194254a0384696 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/fr.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "Architecture du processeur", + "chassis": "Ch\u00e2ssis", + "dev": "D\u00e9veloppement", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Superviseur", + "host_os": "Home Assistant OS", + "installation_type": "Type d'installation", + "os_name": "Famille du syst\u00e8me d'exploitation", + "os_version": "Version du syst\u00e8me d'exploitation", + "python_version": "Version de Python", + "supervisor": "Supervisor", + "timezone": "Fuseau horaire", + "version": "Version", + "virtualenv": "Environnement virtuel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/tr.json b/homeassistant/components/homeassistant/translations/tr.json index 1ff8ea1b3a91ee..c2b7ca1b10cfb5 100644 --- a/homeassistant/components/homeassistant/translations/tr.json +++ b/homeassistant/components/homeassistant/translations/tr.json @@ -2,9 +2,11 @@ "system_health": { "info": { "arch": "CPU Mimarisi", + "chassis": "Ana G\u00f6vde", "dev": "Geli\u015ftirme", "docker": "Konteyner", "docker_version": "Konteyner", + "hassio": "S\u00fcperviz\u00f6r", "host_os": "Home Assistant OS", "installation_type": "Kurulum T\u00fcr\u00fc", "os_name": "\u0130\u015fletim Sistemi Ailesi", diff --git a/homeassistant/components/homeassistant/translations/uk.json b/homeassistant/components/homeassistant/translations/uk.json new file mode 100644 index 00000000000000..19e07c8f822527 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/uk.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "\u0410\u0440\u0445\u0456\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0426\u041f", + "chassis": "\u0428\u0430\u0441\u0456", + "dev": "\u0421\u0435\u0440\u0435\u0434\u043e\u0432\u0438\u0449\u0435 \u0440\u043e\u0437\u0440\u043e\u0431\u043a\u0438", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Supervisor", + "host_os": "Home Assistant OS", + "installation_type": "\u0422\u0438\u043f \u0456\u043d\u0441\u0442\u0430\u043b\u044f\u0446\u0456\u0457", + "os_name": "\u0421\u0456\u043c\u0435\u0439\u0441\u0442\u0432\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u0438\u0445 \u0441\u0438\u0441\u0442\u0435\u043c", + "os_version": "\u0412\u0435\u0440\u0441\u0456\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u0439\u043d\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438", + "python_version": "\u0412\u0435\u0440\u0441\u0456\u044f Python", + "supervisor": "Supervisor", + "timezone": "\u0427\u0430\u0441\u043e\u0432\u0438\u0439 \u043f\u043e\u044f\u0441", + "version": "\u0412\u0435\u0440\u0441\u0456\u044f", + "virtualenv": "\u0412\u0456\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u0435 \u043e\u0442\u043e\u0447\u0435\u043d\u043d\u044f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index 63f461ea3447a8..0870b05a6d1665 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -4,6 +4,20 @@ "port_name_in_use": "Ja hi ha un enlla\u00e7 o accessori configurat amb aquest nom o port." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entitat" + }, + "description": "Escull l'entitat que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat.", + "title": "Selecciona l'entitat a incloure" + }, + "bridge_mode": { + "data": { + "include_domains": "Dominis a incloure" + }, + "description": "Escull els dominis que vulguis incloure. S'inclouran totes les entitats del domini que siguin compatibles.", + "title": "Selecciona els dominis a incloure" + }, "pairing": { "description": "Tan aviat com {name} estigui llest, la vinculaci\u00f3 estar\u00e0 disponible a \"Notificacions\" com a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\".", "title": "Vinculaci\u00f3 HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Autoarrencada (desactiva-ho si fas servir Z-Wave o algun altre sistema d'inici lent)", - "include_domains": "Dominis a incloure" + "include_domains": "Dominis a incloure", + "mode": "Mode" }, - "description": "La integraci\u00f3 HomeKit et permet l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML per l'enlla\u00e7 prinipal.", + "description": "La integraci\u00f3 HomeKit et permetr\u00e0 l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Activaci\u00f3 de HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "[%key::component::homekit::config::step::user::data::auto_start%]", + "auto_start": "Inici autom\u00e0tic (desactiva-ho si crides el servei homekit.start manualment)", "safe_mode": "Mode segur (habilita-ho nom\u00e9s si falla la vinculaci\u00f3)" }, "description": "Aquests par\u00e0metres nom\u00e9s s'han d'ajustar si HomeKit no \u00e9s funcional.", @@ -40,16 +55,16 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis exposar. En mode accessori, nom\u00e9s s'exposa una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret que se seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'exposaran totes les entitats del domini excepte les entitats excloses.", - "title": "Selecci\u00f3 de les entitats a exposar" + "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "title": "Selecciona les entitats a incloure" }, "init": { "data": { - "include_domains": "[%key::component::homekit::config::step::user::data::include_domains%]", + "include_domains": "Dominis a incloure", "mode": "Mode" }, - "description": "HomeKit es pot configurar per exposar un enlla\u00e7 o un sol accessori. En mode accessori, nom\u00e9s es pot utilitzar una entitat. El mode accessori \u00e9s necessari en reproductors multim\u00e8dia amb classe de dispositiu TV perqu\u00e8 funcionin correctament. Les entitats a \"Dominis a incloure\" s'exposaran a HomeKit. A la seg\u00fcent pantalla podr\u00e0s seleccionar quines entitats vols incloure o excloure d'aquesta llista.", - "title": "Selecci\u00f3 dels dominis a exposar." + "description": "HomeKit es pot configurar per exposar un enlla\u00e7 o un sol accessori. En mode accessori, nom\u00e9s es pot utilitzar una entitat. El mode accessori \u00e9s necessari perqu\u00e8 els reproductors multim\u00e8dia amb classe de dispositiu TV funcionin correctament. Les entitats a \"Dominis a incloure\" s'inclouran a HomeKit. A la seg\u00fcent pantalla podr\u00e0s seleccionar quines entitats vols incloure o excloure d'aquesta llista.", + "title": "Selecciona els dominis a incloure." }, "yaml": { "description": "Aquesta entrada es controla en YAML", diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json index c070c852f0978f..faf1b1d74fc0d4 100644 --- a/homeassistant/components/homekit/translations/cs.json +++ b/homeassistant/components/homekit/translations/cs.json @@ -4,12 +4,18 @@ "port_name_in_use": "P\u0159\u00edslu\u0161enstv\u00ed nebo p\u0159emost\u011bn\u00ed se stejn\u00fdm n\u00e1zvem nebo portem je ji\u017e nastaveno." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entita" + } + }, "pairing": { "title": "P\u00e1rov\u00e1n\u00ed s HomeKit" }, "user": { "data": { - "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty" + "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", + "mode": "Re\u017eim" }, "title": "Aktivace HomeKit" } diff --git a/homeassistant/components/homekit/translations/de.json b/homeassistant/components/homekit/translations/de.json index 9b1ec14a1dd20b..6d69c498bacacc 100644 --- a/homeassistant/components/homekit/translations/de.json +++ b/homeassistant/components/homekit/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "port_name_in_use": "A bridge with the same name or port is already configured.\nEine HomeKit Bridge mit dem selben Namen oder Port ist bereits vorhanden" + "port_name_in_use": "Eine HomeKit Bridge mit demselben Namen oder Port ist bereits vorhanden." }, "step": { "pairing": { @@ -37,14 +37,15 @@ }, "init": { "data": { + "include_domains": "Einzubeziehende Domains", "mode": "Modus" }, "description": "HomeKit kann so konfiguriert werden, dass eine Br\u00fccke oder ein einzelnes Zubeh\u00f6r verf\u00fcgbar gemacht wird. Im Zubeh\u00f6rmodus kann nur eine einzelne Entit\u00e4t verwendet werden. F\u00fcr Media Player mit der TV-Ger\u00e4teklasse ist ein Zubeh\u00f6rmodus erforderlich, damit sie ordnungsgem\u00e4\u00df funktionieren. Entit\u00e4ten in den \"einzuschlie\u00dfenden Dom\u00e4nen\" werden f\u00fcr HomeKit verf\u00fcgbar gemacht. Auf dem n\u00e4chsten Bildschirm k\u00f6nnen Sie ausw\u00e4hlen, welche Entit\u00e4ten in diese Liste aufgenommen oder aus dieser ausgeschlossen werden sollen.", - "title": "W\u00e4hlen Sie die zu \u00fcberbr\u00fcckenden Dom\u00e4nen aus." + "title": "W\u00e4hle die zu \u00fcberbr\u00fcckenden Dom\u00e4nen aus." }, "yaml": { "description": "Dieser Eintrag wird \u00fcber YAML gesteuert", - "title": "Passen Sie die HomeKit Bridge-Optionen an" + "title": "Passe die HomeKit Bridge-Optionen an" } } } diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 5ba578f38c3de8..c5ffa2e9aa4b3b 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -13,10 +13,23 @@ "description": "HomeKit can be configured expose a bridge or a single accessory. In accessory mode, only a single entity can be used. Accessory mode is required for media players with the TV device class to function properly. Entities in the \u201cDomains to include\u201d will be exposed to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.", "title": "Select domains to expose." }, - "include_exclude": { + "user": { "data": { - "mode": "[%key:common::config_flow::data::mode%]", - "entities": "Entities" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", + "mode": "Mode" + }, + "description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", + "title": "Activate HomeKit" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "Choose the entities to be exposed. In accessory mode, only a single entity is exposed. In bridge include mode, all entities in the domain will be exposed unless specific entities are selected. In bridge exclude mode, all entities in the domain will be exposed except for the excluded entities. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", "title": "Select entities to be exposed" diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 76a78602bd3e87..37bff5f9b70e2c 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -4,6 +4,20 @@ "port_name_in_use": "Sama nime v\u00f5i pordiga tarvik v\u00f5i sild on juba konfigureeritud." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Olem" + }, + "description": "Vali kaasatav olem. Lisare\u017eiimis on kaasatav ainult \u00fcks olem.", + "title": "Vali kaasatav olem" + }, + "bridge_mode": { + "data": { + "include_domains": "Kaasatavad domeenid" + }, + "description": "Vali kaasatavad domeenid. Kaasatakse k\u00f5ik domeenis toetatud olemid.", + "title": "Vali kaasatavad domeenid" + }, "pairing": { "description": "Niipea kui {name} on valmis, on sidumine saadaval jaotises \"Notifications\" kui \"HomeKit Bridge Setup\".", "title": "HomeKiti sidumine" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Autostart (keela, kui kasutad Z-Wave'i v\u00f5i muud viivitatud k\u00e4ivituss\u00fcsteemi)", - "include_domains": "Kaasatavad domeenid" + "include_domains": "Kaasatavad domeenid", + "mode": "Re\u017eiim" }, - "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu.", + "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu. Parema tulemuse saavutamiseks ja ootamatute seadmete kadumise v\u00e4ltimiseks loo ja seo eraldi HomeKiti seade tarviku re\u017eiimis kga meediaesitaja ja kaamera jaoks.", "title": "Aktiveeri HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (keela, kui kasutad Z-Wave'i v\u00f5i muud viivitatud k\u00e4ivituss\u00fcsteemi)", + "auto_start": "Autostart (keela kui kasutad homekit.start teenust k\u00e4sitsi)", "safe_mode": "Turvare\u017eiim (luba ainult siis, kui sidumine nurjub)" }, "description": "Neid s\u00e4tteid tuleb muuta ainult siis kui HomeKit ei t\u00f6\u00f6ta.", @@ -40,8 +55,8 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali avaldatavad olemid. Tarvikute re\u017eiimis on avaldatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis avaldatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", - "title": "Vali avaldatavad olemid" + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", + "title": "Vali kaasatavd olemid" }, "init": { "data": { @@ -49,7 +64,7 @@ "mode": "Re\u017eiim" }, "description": "HomeKiti saab seadistada silla v\u00f5i \u00fche lisaseadme avaldamiseks. Lisare\u017eiimis saab kasutada ainult \u00fchte \u00fcksust. Teleriseadmete klassiga meediumipleierite n\u00f5uetekohaseks toimimiseks on vaja lisare\u017eiimi. \u201eKaasatavate domeenide\u201d \u00fcksused puutuvad kokku HomeKitiga. J\u00e4rgmisel ekraanil saad valida, millised \u00fcksused sellesse loendisse lisada v\u00f5i sellest v\u00e4lja j\u00e4tta.", - "title": "Valige avaldatavad domeenid." + "title": "Vali kaasatavad domeenid" }, "yaml": { "description": "Seda sisestust juhitakse YAML-i kaudu", diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 7e9c8a05b9da55..9a85d1e6e9fc52 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -4,6 +4,20 @@ "port_name_in_use": "Un accessorio o un bridge con lo stesso nome o porta \u00e8 gi\u00e0 configurato." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entit\u00e0" + }, + "description": "Scegli l'entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa solo una singola entit\u00e0.", + "title": "Seleziona l'entit\u00e0 da includere" + }, + "bridge_mode": { + "data": { + "include_domains": "Domini da includere" + }, + "description": "Scegli i domini da includere. Verranno incluse tutte le entit\u00e0 supportate nel dominio.", + "title": "Seleziona i domini da includere" + }, "pairing": { "description": "Non appena il {name} \u00e8 pronto, l'associazione sar\u00e0 disponibile in \"Notifiche\" come \"Configurazione HomeKit Bridge\".", "title": "Associa HomeKit" @@ -11,7 +25,8 @@ "user": { "data": { "auto_start": "Avvio automatico (disabilitare se si utilizza Z-Wave o un altro sistema di avvio ritardato)", - "include_domains": "Domini da includere" + "include_domains": "Domini da includere", + "mode": "Modalit\u00e0" }, "description": "L'integrazione di HomeKit ti consentir\u00e0 di accedere alle entit\u00e0 di Home Assistant in HomeKit. In modalit\u00e0 bridge, i bridge HomeKit sono limitati a 150 accessori per istanza, incluso il bridge stesso. Se desideri eseguire il bridge di un numero di accessori superiore a quello massimo, si consiglia di utilizzare pi\u00f9 bridge HomeKit per domini diversi. La configurazione dettagliata dell'entit\u00e0 \u00e8 disponibile solo tramite YAML per il bridge principale.", "title": "Attiva HomeKit" @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Avvio automatico (disabilitare se si utilizza Z-Wave o un altro sistema di avvio ritardato)", + "auto_start": "Avvio automatico (disabilitare se stai chiamando manualmente il servizio homekit.start)", "safe_mode": "Modalit\u00e0 provvisoria (attivare solo in caso di errore di associazione)" }, "description": "Queste impostazioni devono essere regolate solo se HomeKit non funziona.", @@ -40,8 +55,8 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da esporre. In modalit\u00e0 accessorio, \u00e8 esposta una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno esposte, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno esposte, ad eccezione delle entit\u00e0 escluse.", - "title": "Selezionare le entit\u00e0 da esporre" + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali e per evitare una indisponibilit\u00e0 imprevista, creare e associare un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "title": "Seleziona le entit\u00e0 da includere" }, "init": { "data": { @@ -49,7 +64,7 @@ "mode": "Modalit\u00e0" }, "description": "HomeKit pu\u00f2 essere configurato esponendo un bridge o un singolo accessorio. In modalit\u00e0 accessorio, pu\u00f2 essere utilizzata solo una singola entit\u00e0. La modalit\u00e0 accessorio \u00e8 necessaria per il corretto funzionamento dei lettori multimediali con la classe di apparecchi TV. Le entit\u00e0 nei \"Domini da includere\" saranno esposte ad HomeKit. Sar\u00e0 possibile selezionare quali entit\u00e0 includere o escludere da questo elenco nella schermata successiva.", - "title": "Selezionare i domini da esporre." + "title": "Seleziona i domini da includere." }, "yaml": { "description": "Questa voce \u00e8 controllata tramite YAML", diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 7eff4d37668e13..9a64def41569d2 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -4,6 +4,20 @@ "port_name_in_use": "Et tilbeh\u00f8r eller bro med samme navn eller port er allerede konfigurert." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Enhet" + }, + "description": "Velg enheten som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert.", + "title": "Velg enhet som skal inkluderes" + }, + "bridge_mode": { + "data": { + "include_domains": "Domener \u00e5 inkludere" + }, + "description": "Velg domenene som skal inkluderes. Alle st\u00f8ttede enheter i domenet vil bli inkludert.", + "title": "Velg domener som skal inkluderes" + }, "pairing": { "description": "S\u00e5 snart {name} er klart, vil sammenkobling v\u00e6re tilgjengelig i \"Notifications\" som \"HomeKit Bridge Setup\".", "title": "Koble sammen HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Autostart (deaktiver hvis du bruker Z-Wave eller annet forsinket startsystem)", - "include_domains": "Domener \u00e5 inkludere" + "include_domains": "Domener \u00e5 inkludere", + "mode": "Modus" }, - "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-entitetene dine i HomeKit. I bromodus er HomeKit Broer begrenset til 150 tilbeh\u00f8rsenhet per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 \u00f8ke maksimalt antall tilbeh\u00f8rsenheter, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert entitetskonfigurasjon er bare tilgjengelig via YAML for den prim\u00e6re broen.", + "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-enhetene dine i HomeKit. I bromodus er HomeKit Bridges begrenset til 150 tilbeh\u00f8r per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 bygge bro over maksimalt antall tilbeh\u00f8r, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert enhetskonfigurasjon er bare tilgjengelig via YAML. For best ytelse og for \u00e5 forhindre uventet utilgjengelighet, opprett og par sammen en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", "title": "Aktiver HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (deaktiver hvis du bruker Z-Wave eller annet forsinket startsystem)", + "auto_start": "Autostart (deaktiver hvis du ringer til homekit.start-tjenesten manuelt)", "safe_mode": "Sikker modus (aktiver bare hvis sammenkoblingen mislykkes)" }, "description": "Disse innstillingene m\u00e5 bare justeres hvis HomeKit ikke fungerer.", @@ -40,16 +55,16 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg entitene som skal eksponeres. I tilbeh\u00f8rsmodus er bare en enkelt entitet eksponert. I bro-inkluderingsmodus vil alle entiteter i domenet bli eksponert med mindre spesifikke entiteter er valgt. I bro-ekskluderingsmodus vil alle entiteter i domenet bli eksponert bortsett fra de ekskluderte entitetene.", - "title": "Velg entiteter som skal eksponeres" + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare \u00e9n enkelt enhet inkludert. I bridge include-modus inkluderes alle enheter i domenet med mindre bestemte enheter er valgt. I brounnlatingsmodus inkluderes alle enheter i domenet, med unntak av de utelatte enhetene. For best mulig ytelse, og for \u00e5 forhindre uventet utilgjengelighet, opprett og par en separat HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediespiller og kamera.", + "title": "Velg enheter som skal inkluderes" }, "init": { "data": { "include_domains": "Domener \u00e5 inkludere", "mode": "Modus" }, - "description": "HomeKit kan konfigureres for \u00e5 eksponere en bro eller en enkelt tilbeh\u00f8rsenhet. I tilbeh\u00f8rsmodus kan bare en enkelt entitet brukes. Tilbeh\u00f8rsmodus er n\u00f8dvendig for at mediaspillere med TV-enhetsklasse skal fungere skikkelig. Entiteter i \u201cDomains to include\u201d vil bli eksponert for HomeKit. Du vil kunne velge hvilke entiteter du vil inkludere eller ekskludere fra denne listen p\u00e5 neste skjermbilde.", - "title": "Velg domener du vil eksponere." + "description": "HomeKit kan konfigureres vise en bro eller ett enkelt tilbeh\u00f8r. I tilbeh\u00f8rsmodus kan bare \u00e9n enkelt enhet brukes. Tilbeh\u00f8rsmodus kreves for at mediespillere med TV-enhetsklassen skal fungere som de skal. Enheter i \"Domener som skal inkluderes\" inkluderes i HomeKit. Du kan velge hvilke enheter som skal inkluderes eller ekskluderes fra denne listen p\u00e5 neste skjermbilde.", + "title": "Velg domener som skal inkluderes." }, "yaml": { "description": "Denne oppf\u00f8ringen kontrolleres via YAML", diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 3210f0f4430bb7..2679a4de20afb1 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -4,6 +4,20 @@ "port_name_in_use": "Akcesorium lub mostek o tej samej nazwie lub adresie IP jest ju\u017c skonfigurowany" }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Encja" + }, + "description": "Wybierz uwzgl\u0119dniane encje. W trybie akcesori\u00f3w uwzgl\u0119dniana jest tylko jedna encja.", + "title": "Wybierz uwzgl\u0119dniane encje" + }, + "bridge_mode": { + "data": { + "include_domains": "Domeny do uwzgl\u0119dnienia" + }, + "description": "Wybierz uwzgl\u0119dniane domeny. Wszystkie obs\u0142ugiwane encje w domenie zostan\u0105 uwzgl\u0119dnione.", + "title": "Wybierz uwzgl\u0119dniane domeny" + }, "pairing": { "description": "Gdy tylko {name} b\u0119dzie gotowy, opcja parowania b\u0119dzie dost\u0119pna w \u201ePowiadomieniach\u201d jako \u201eKonfiguracja mostka HomeKit\u201d.", "title": "Parowanie z HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli u\u017cywasz Z-Wave lub innej integracji op\u00f3\u017aniaj\u0105cej start systemu)", - "include_domains": "Domeny do uwzgl\u0119dnienia" + "include_domains": "Domeny do uwzgl\u0119dnienia", + "mode": "Tryb" }, - "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka.", + "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", "title": "Aktywacja HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli u\u017cywasz Z-Wave lub innej integracji op\u00f3\u017aniaj\u0105cej start systemu)", + "auto_start": "Automatyczne uruchomienie (wy\u0142\u0105cz, je\u015bli r\u0119cznie uruchamiasz us\u0142ug\u0119 homekit.start)", "safe_mode": "Tryb awaryjny (w\u0142\u0105cz tylko wtedy, gdy parowanie nie powiedzie si\u0119)" }, "description": "Te ustawienia nale\u017cy dostosowa\u0107 tylko wtedy, gdy HomeKit nie dzia\u0142a.", @@ -40,8 +55,8 @@ "entities": "Encje", "mode": "Tryb" }, - "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 widoczne. W trybie \"Akcesorium\", tylko jedna encja jest widoczna. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 widoczne, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 widoczne, z wyj\u0105tkiem tych wybranych.", - "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 widoczne" + "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", + "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione" }, "init": { "data": { diff --git a/homeassistant/components/homekit/translations/ro.json b/homeassistant/components/homekit/translations/ro.json new file mode 100644 index 00000000000000..82e8344417ba17 --- /dev/null +++ b/homeassistant/components/homekit/translations/ro.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "Integrarea HomeKit v\u0103 va permite s\u0103 accesa\u021bi entit\u0103\u021bile Home Assistant din HomeKit. \u00cen modul bridge, HomeKit Bridges sunt limitate la 150 de accesorii pe instan\u021b\u0103, inclusiv bridge-ul \u00een sine. Dac\u0103 dori\u021bi s\u0103 face\u021bi mai mult dec\u00e2t num\u0103rul maxim de accesorii, este recomandat s\u0103 utiliza\u021bi mai multe poduri HomeKit pentru diferite domenii. Configurarea detaliat\u0103 a entit\u0103\u021bii este disponibil\u0103 numai prin YAML. Pentru cele mai bune performan\u021be \u0219i pentru a preveni indisponibilitatea nea\u0219teptat\u0103, crea\u021bi \u0219i \u00eemperechea\u021bi o instan\u021b\u0103 HomeKit separat\u0103 \u00een modul accesoriu pentru fiecare player media TV \u0219i camer\u0103." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 3cb5e84936a8a5..6cf96c2dd7857c 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -4,6 +4,11 @@ "port_name_in_use": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u0438\u043b\u0438 \u043f\u043e\u0440\u0442\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "\u041e\u0431\u044a\u0435\u043a\u0442" + } + }, "pairing": { "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e, \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u0445\" \u043a\u0430\u043a \"HomeKit Bridge Setup\".", "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 HomeKit" @@ -13,7 +18,7 @@ "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b" }, - "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0431\u0440\u0438\u0434\u0436\u0430.", + "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "HomeKit" } } @@ -22,7 +27,7 @@ "step": { "advanced": { "data": { - "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u0435, \u0435\u0441\u043b\u0438 \u0412\u044b \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0435 \u0441\u043b\u0443\u0436\u0431\u0443 homekit.start)", "safe_mode": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c (\u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u0431\u043e\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f)" }, "description": "\u042d\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b, \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 HomeKit \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.", @@ -40,7 +45,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/homekit/translations/sv.json b/homeassistant/components/homekit/translations/sv.json index 5aa9507a85d6fc..1e2fcae04b5bc5 100644 --- a/homeassistant/components/homekit/translations/sv.json +++ b/homeassistant/components/homekit/translations/sv.json @@ -1,4 +1,19 @@ { + "config": { + "step": { + "bridge_mode": { + "data": { + "include_domains": "Dom\u00e4ner att inkludera" + } + }, + "pairing": { + "title": "Para HomeKit" + }, + "user": { + "title": "Aktivera HomeKit" + } + } + }, "options": { "step": { "cameras": { @@ -7,6 +22,12 @@ }, "description": "Kontrollera alla kameror som st\u00f6der inbyggda H.264-str\u00f6mmar. Om kameran inte skickar ut en H.264-str\u00f6m kodar systemet videon till H.264 f\u00f6r HomeKit. Transkodning kr\u00e4ver h\u00f6g prestanda och kommer troligtvis inte att fungera p\u00e5 enkortsdatorer.", "title": "V\u00e4lj kamerans videoavkodare." + }, + "init": { + "data": { + "include_domains": "Dom\u00e4ner att inkludera" + }, + "title": "V\u00e4lj dom\u00e4ner som ska inkluderas." } } } diff --git a/homeassistant/components/homekit/translations/tr.json b/homeassistant/components/homekit/translations/tr.json new file mode 100644 index 00000000000000..f9391fd0686c61 --- /dev/null +++ b/homeassistant/components/homekit/translations/tr.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Ayn\u0131 ada veya ba\u011flant\u0131 noktas\u0131na sahip bir aksesuar veya k\u00f6pr\u00fc zaten yap\u0131land\u0131r\u0131lm\u0131\u015f." + }, + "step": { + "accessory_mode": { + "data": { + "entity_id": "Varl\u0131k" + }, + "description": "Dahil edilecek varl\u0131\u011f\u0131 se\u00e7in. Aksesuar modunda, yaln\u0131zca tek bir varl\u0131k dahildir.", + "title": "Dahil edilecek varl\u0131\u011f\u0131 se\u00e7in" + }, + "bridge_mode": { + "data": { + "include_domains": "\u0130\u00e7erecek etki alanlar\u0131" + }, + "description": "Dahil edilecek alanlar\u0131 se\u00e7in. Etki alan\u0131ndaki t\u00fcm desteklenen varl\u0131klar dahil edilecektir.", + "title": "Dahil edilecek etki alanlar\u0131n\u0131 se\u00e7in" + }, + "pairing": { + "description": "{name} haz\u0131r olur olmaz e\u015fle\u015ftirme, \"Bildirimler\" i\u00e7inde \"HomeKit K\u00f6pr\u00fc Kurulumu\" olarak mevcut olacakt\u0131r.", + "title": "HomeKit'i E\u015fle\u015ftir" + }, + "user": { + "data": { + "mode": "Mod" + } + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "safe_mode": "G\u00fcvenli Mod (yaln\u0131zca e\u015fle\u015ftirme ba\u015far\u0131s\u0131z olursa etkinle\u015ftirin)" + } + }, + "cameras": { + "data": { + "camera_copy": "Yerel H.264 ak\u0131\u015flar\u0131n\u0131 destekleyen kameralar" + }, + "description": "Yerel H.264 ak\u0131\u015flar\u0131n\u0131 destekleyen t\u00fcm kameralar\u0131 kontrol edin. Kamera bir H.264 ak\u0131\u015f\u0131 vermezse, sistem videoyu HomeKit i\u00e7in H.264'e d\u00f6n\u00fc\u015ft\u00fcr\u00fcr. Kod d\u00f6n\u00fc\u015ft\u00fcrme, y\u00fcksek performansl\u0131 bir CPU gerektirir ve tek kartl\u0131 bilgisayarlarda \u00e7al\u0131\u015fma olas\u0131l\u0131\u011f\u0131 d\u00fc\u015f\u00fckt\u00fcr.", + "title": "Kamera video codec bile\u015fenini se\u00e7in." + }, + "include_exclude": { + "data": { + "entities": "Varl\u0131klar", + "mode": "Mod" + }, + "title": "Dahil edilecek varl\u0131klar\u0131 se\u00e7in" + }, + "init": { + "data": { + "mode": "Mod" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/uk.json b/homeassistant/components/homekit/translations/uk.json index 10cd42ccecb6e4..876b200bdf8219 100644 --- a/homeassistant/components/homekit/translations/uk.json +++ b/homeassistant/components/homekit/translations/uk.json @@ -1,10 +1,59 @@ { + "config": { + "abort": { + "port_name_in_use": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437 \u0442\u0430\u043a\u043e\u044e \u0436 \u043d\u0430\u0437\u0432\u043e\u044e \u0430\u0431\u043e \u043f\u043e\u0440\u0442\u043e\u043c \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "step": { + "pairing": { + "description": "\u042f\u043a \u0442\u0456\u043b\u044c\u043a\u0438 {name} \u0431\u0443\u0434\u0435 \u0433\u043e\u0442\u043e\u0432\u0438\u0439, \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 \u0431\u0443\u0434\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0421\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f\u0445\" \u044f\u043a \"HomeKit Bridge Setup\".", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0437 HomeKit" + }, + "user": { + "data": { + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 Z-Wave \u0430\u0431\u043e \u0456\u043d\u0448\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u0432\u0456\u0434\u043a\u043b\u0430\u0434\u0435\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0443)", + "include_domains": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u0438" + }, + "description": "\u0426\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0434\u043e\u0437\u0432\u043e\u043b\u044f\u0454 \u043e\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u043e\u0431'\u0454\u043a\u0442\u0456\u0432 Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u0438\u0439 150 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u0435\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0447\u0438 \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u042f\u043a\u0449\u043e \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0431\u0456\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 HomeKit Bridge \u0434\u043b\u044f \u0440\u0456\u0437\u043d\u0438\u0445 \u0434\u043e\u043c\u0435\u043d\u0456\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u043a\u043e\u0436\u043d\u043e\u0433\u043e \u043e\u0431'\u0454\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0435 \u0442\u0456\u043b\u044c\u043a\u0438 \u0447\u0435\u0440\u0435\u0437 YAML \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043c\u043e\u0441\u0442\u0430.", + "title": "HomeKit" + } + } + }, "options": { "step": { + "advanced": { + "data": { + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 Z-Wave \u0430\u0431\u043e \u0456\u043d\u0448\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u0432\u0456\u0434\u043a\u043b\u0430\u0434\u0435\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0443)", + "safe_mode": "\u0411\u0435\u0437\u043f\u0435\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c (\u0443\u0432\u0456\u043c\u043a\u043d\u0456\u0442\u044c \u0442\u0456\u043b\u044c\u043a\u0438 \u0432 \u0440\u0430\u0437\u0456 \u0437\u0431\u043e\u044e \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f)" + }, + "description": "\u0426\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0456, \u043b\u0438\u0448\u0435 \u044f\u043a\u0449\u043e HomeKit \u043d\u0435 \u043f\u0440\u0430\u0446\u044e\u0454.", + "title": "\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + }, + "cameras": { + "data": { + "camera_copy": "\u041a\u0430\u043c\u0435\u0440\u0438, \u044f\u043a\u0456 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c \u043f\u043e\u0442\u043e\u043a\u0438 H.264" + }, + "description": "\u042f\u043a\u0449\u043e \u043a\u0430\u043c\u0435\u0440\u0430 \u043d\u0435 \u0432\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u0442\u0456\u043a H.264, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0435\u0440\u0435\u043a\u043e\u0434\u043e\u0432\u0443\u0454 \u0432\u0456\u0434\u0435\u043e \u0432 H.264 \u0434\u043b\u044f HomeKit. \u0422\u0440\u0430\u043d\u0441\u043a\u043e\u0434\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u0432\u0438\u0441\u043e\u043a\u043e\u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u043e\u0440\u0430 \u0456 \u043d\u0430\u0432\u0440\u044f\u0434 \u0447\u0438 \u0431\u0443\u0434\u0435 \u043f\u0440\u0430\u0446\u044e\u0432\u0430\u0442\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043f\u043b\u0430\u0442\u043d\u0438\u0445 \u043a\u043e\u043c\u043f'\u044e\u0442\u0435\u0440\u0430\u0445.", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0456\u0434\u0435\u043e\u043a\u043e\u0434\u0435\u043a \u043a\u0430\u043c\u0435\u0440\u0438." + }, + "include_exclude": { + "data": { + "entities": "\u0421\u0443\u0442\u043d\u043e\u0441\u0442\u0456", + "mode": "\u0420\u0435\u0436\u0438\u043c" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u0438 \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432\u0441\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0443, \u044f\u043a\u0449\u043e \u043d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u0456 \u043f\u0435\u0432\u043d\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432\u0441\u0456 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u0456\u043c \u0432\u0438\u0431\u0440\u0430\u043d\u0438\u0445.", + "title": "\u0412\u0438\u0431\u0456\u0440 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit" + }, "init": { "data": { + "include_domains": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u0438", "mode": "\u0420\u0435\u0436\u0438\u043c" - } + }, + "description": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0437 HomeKit \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0430 \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u043c\u043e\u0441\u0442\u0430 \u0430\u0431\u043e \u044f\u043a \u043e\u043a\u0440\u0435\u043c\u0438\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440. \u0423 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u043e\u0434\u0438\u043d \u043e\u0431'\u0454\u043a\u0442. \u041c\u0435\u0434\u0456\u0430\u043f\u043b\u0435\u0454\u0440\u0438, \u044f\u043a\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0442\u044c\u0441\u044f \u0432 Home Assistant \u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c 'device_class: tv', \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0457 \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u043e\u0432\u0430\u043d\u0456 \u0432 Homekit \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430. \u041e\u0431'\u0454\u043a\u0442\u0438, \u0449\u043e \u043d\u0430\u043b\u0435\u0436\u0430\u0442\u044c \u043e\u0431\u0440\u0430\u043d\u0438\u043c \u0434\u043e\u043c\u0435\u043d\u0430\u043c, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u0456 \u0432 HomeKit. \u041d\u0430 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u043c\u0443 \u0435\u0442\u0430\u043f\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0412\u0438 \u0437\u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u0431\u0440\u0430\u0442\u0438, \u044f\u043a\u0456 \u043e\u0431'\u0454\u043a\u0442\u0438 \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0437 \u0446\u0438\u0445 \u0434\u043e\u043c\u0435\u043d\u0456\u0432.", + "title": "\u0412\u0438\u0431\u0456\u0440 \u0434\u043e\u043c\u0435\u043d\u0456\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0456 \u0432 HomeKit" + }, + "yaml": { + "description": "\u0426\u0435\u0439 \u0437\u0430\u043f\u0438\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 YAML", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f HomeKit" } } } diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index 0f1093f5b5bbdd..605263c4489728 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -4,6 +4,20 @@ "port_name_in_use": "\u4f7f\u7528\u76f8\u540c\u540d\u7a31\u6216\u901a\u8a0a\u57e0\u7684\u914d\u4ef6\u6216 Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" }, "step": { + "accessory_mode": { + "data": { + "entity_id": "\u5be6\u9ad4" + }, + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\uff0c\u50c5\u80fd\u5305\u542b\u55ae\u4e00\u5be6\u9ad4\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" + }, + "bridge_mode": { + "data": { + "include_domains": "\u5305\u542b\u7db2\u57df" + }, + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df\u3002\u6240\u6709\u7db2\u57df\u5167\u652f\u63f4\u7684\u5be6\u9ad4\u90fd\u6703\u5305\u542b\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" + }, "pairing": { "description": "\u65bc {name} \u5c31\u7dd2\u5f8c\u3001\u5c07\u6703\u65bc\u300c\u901a\u77e5\u300d\u4e2d\u986f\u793a\u300cHomeKit Bridge \u8a2d\u5b9a\u300d\u7684\u914d\u5c0d\u8cc7\u8a0a\u3002", "title": "\u914d\u5c0d HomeKit" @@ -11,9 +25,10 @@ "user": { "data": { "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u9072\u555f\u52d5\u7cfb\u7d71\u6642\u3001\u8acb\u95dc\u9589\uff09", - "include_domains": "\u5305\u542b Domain" + "include_domains": "\u5305\u542b\u7db2\u57df", + "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c Domain \u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002", + "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c\u7db2\u57df\u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", "title": "\u555f\u7528 HomeKit" } } @@ -22,7 +37,7 @@ "step": { "advanced": { "data": { - "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u9072\u555f\u52d5\u7cfb\u7d71\u6642\u3001\u8acb\u95dc\u9589\uff09", + "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u624b\u52d5\u4f7f\u7528 homekit.start \u670d\u52d9\u6642\u3001\u8acb\u95dc\u9589\uff09", "safe_mode": "\u5b89\u5168\u6a21\u5f0f\uff08\u50c5\u65bc\u914d\u5c0d\u5931\u6557\u6642\u4f7f\u7528\uff09" }, "description": "\u50c5\u65bc Homekit \u7121\u6cd5\u6b63\u5e38\u4f7f\u7528\u6642\uff0c\u8abf\u6574\u6b64\u4e9b\u8a2d\u5b9a\u3002", @@ -40,16 +55,16 @@ "entities": "\u5be6\u9ad4", "mode": "\u6a21\u5f0f" }, - "description": "\u9078\u64c7\u9032\u884c\u63a5\u901a\u7684\u5be6\u9ad4\u3002\u65bc\u5305\u542b\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u9032\u884c\u63a5\u901a\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u9032\u884c\u63a5\u901a\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002", - "title": "\u9078\u64c7\u8981\u63a5\u901a\u7684\u5be6\u9ad4" + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" }, "init": { "data": { - "include_domains": "\u5305\u542b Domain", + "include_domains": "\u5305\u542b\u7db2\u57df", "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u80fd\u5920\u8a2d\u5b9a\u63a5\u901a\u6a4b\u63a5\u6216\u55ae\u4e00\u914d\u4ef6\u6a21\u5f0f\u3002\u5a92\u9ad4\u64ad\u653e\u5668\u9700\u8981\u4ee5\u96fb\u8996\u88dd\u7f6e\u914d\u4ef6\u6a21\u5f0f\u624d\u80fd\u6b63\u5e38\u4f7f\u7528\u3002\"\u5305\u542b Domains\"\u4e2d\u7684\u5be6\u9ad4\u5c07\u6703\u6a4b\u63a5\u81f3 Homekit\u3001\u53ef\u4ee5\u65bc\u4e0b\u4e00\u500b\u756b\u9762\u4e2d\u9078\u64c7\u6240\u8981\u5305\u542b\u6216\u6392\u9664\u7684\u5be6\u9ad4\u5217\u8868\u3002", - "title": "\u9078\u64c7\u6240\u8981\u63a5\u901a\u7684 Domain\u3002" + "description": "HomeKit \u80fd\u5920\u8a2d\u5b9a\u63a5\u901a\u6a4b\u63a5\u6216\u55ae\u4e00\u914d\u4ef6\u6a21\u5f0f\u3002 \u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u5a92\u9ad4\u64ad\u653e\u5668\u9700\u8981\u4ee5\u96fb\u8996\u88dd\u7f6e\u914d\u4ef6\u6a21\u5f0f\u624d\u80fd\u6b63\u5e38\u4f7f\u7528\u3002\"\u5305\u542b\u7db2\u57df\" \u4e2d\u7684\u5be6\u9ad4\u5c07\u6703\u6a4b\u63a5\u81f3 Homekit\u3001\u53ef\u4ee5\u65bc\u4e0b\u4e00\u500b\u756b\u9762\u4e2d\u9078\u64c7\u6240\u8981\u5305\u542b\u6216\u6392\u9664\u7684\u5be6\u9ad4\u5217\u8868\u3002", + "title": "\u9078\u64c7\u6240\u8981\u5305\u542b\u7684\u7db2\u57df\u3002" }, "yaml": { "description": "\u6b64\u5be6\u9ad4\u70ba\u900f\u904e YAML \u63a7\u5236", diff --git a/homeassistant/components/homekit_controller/translations/cs.json b/homeassistant/components/homekit_controller/translations/cs.json index 9a2159eda0599d..d5f7a50292118b 100644 --- a/homeassistant/components/homekit_controller/translations/cs.json +++ b/homeassistant/components/homekit_controller/translations/cs.json @@ -62,9 +62,9 @@ "doorbell": "Zvonek" }, "trigger_type": { - "double_press": "Dvakr\u00e1t stisknuto \"{subtype}\"", - "long_press": "Stisknuto a podr\u017eeno \"{subtype}\"", - "single_press": "Stisknuto \"{subtype}\"" + "double_press": "\"{subtype}\" stisknuto dvakr\u00e1t", + "long_press": "\"{subtype}\" stisknuto a podr\u017eeno", + "single_press": "\"{subtype}\" stisknuto" } }, "title": "HomeKit ovlada\u010d" diff --git a/homeassistant/components/homekit_controller/translations/de.json b/homeassistant/components/homekit_controller/translations/de.json index 3d5c538b62b135..7bab8f30574730 100644 --- a/homeassistant/components/homekit_controller/translations/de.json +++ b/homeassistant/components/homekit_controller/translations/de.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Die Kopplung kann nicht durchgef\u00fchrt werden, da das Ger\u00e4t nicht mehr gefunden werden kann.", "already_configured": "Das Zubeh\u00f6r ist mit diesem Controller bereits konfiguriert.", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "already_paired": "Dieses Zubeh\u00f6r ist bereits mit einem anderen Ger\u00e4t gekoppelt. Setze das Zubeh\u00f6r zur\u00fcck und versuche es erneut.", "ignored_model": "Die Unterst\u00fctzung von HomeKit f\u00fcr dieses Modell ist blockiert, da eine vollst\u00e4ndige native Integration verf\u00fcgbar ist.", "invalid_config_entry": "Dieses Ger\u00e4t wird als bereit zum Koppeln angezeigt, es gibt jedoch bereits einen widerspr\u00fcchlichen Konfigurationseintrag in Home Assistant, der zuerst entfernt werden muss.", @@ -30,7 +30,7 @@ "device": "Ger\u00e4t" }, "description": "W\u00e4hle das Ger\u00e4t aus, mit dem du die Kopplung herstellen m\u00f6chtest", - "title": "Mit HomeKit Zubeh\u00f6r koppeln" + "title": "Ger\u00e4teauswahl" } } }, diff --git a/homeassistant/components/homekit_controller/translations/pl.json b/homeassistant/components/homekit_controller/translations/pl.json index 50fdf6a17e425f..3ccdfe452e559b 100644 --- a/homeassistant/components/homekit_controller/translations/pl.json +++ b/homeassistant/components/homekit_controller/translations/pl.json @@ -62,9 +62,9 @@ "doorbell": "dzwonek do drzwi" }, "trigger_type": { - "double_press": "\"{subtype}\" naci\u015bni\u0119ty dwukrotnie", - "long_press": "\"{subtype}\" naci\u015bni\u0119ty i przytrzymany", - "single_press": "\"{subtype}\" naci\u015bni\u0119ty" + "double_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty dwukrotnie", + "long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty i przytrzymany", + "single_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty" } }, "title": "Kontroler HomeKit" diff --git a/homeassistant/components/homekit_controller/translations/tr.json b/homeassistant/components/homekit_controller/translations/tr.json new file mode 100644 index 00000000000000..9d72049ba21920 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Aksesuar zaten bu denetleyici ile yap\u0131land\u0131r\u0131lm\u0131\u015ft\u0131r.", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "authentication_error": "Yanl\u0131\u015f HomeKit kodu. L\u00fctfen kontrol edip tekrar deneyin.", + "unknown_error": "Cihaz bilinmeyen bir hata bildirdi. E\u015fle\u015ftirme ba\u015far\u0131s\u0131z oldu." + }, + "step": { + "busy_error": { + "title": "Cihaz zaten ba\u015fka bir oyun kumandas\u0131yla e\u015fle\u015fiyor" + }, + "max_tries_error": { + "title": "Maksimum kimlik do\u011frulama giri\u015fimi a\u015f\u0131ld\u0131" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "D\u00fc\u011fme 1", + "button10": "D\u00fc\u011fme 10", + "button2": "D\u00fc\u011fme 2", + "button3": "D\u00fc\u011fme 3", + "button4": "D\u00fc\u011fme 4", + "button5": "D\u00fc\u011fme 5", + "button6": "D\u00fc\u011fme 6", + "button7": "D\u00fc\u011fme 7", + "button8": "D\u00fc\u011fme 8", + "button9": "D\u00fc\u011fme 9", + "doorbell": "Kap\u0131 zili" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/uk.json b/homeassistant/components/homekit_controller/translations/uk.json new file mode 100644 index 00000000000000..66eb1741208dea --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/uk.json @@ -0,0 +1,71 @@ +{ + "config": { + "abort": { + "accessory_not_found_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u0442\u0438 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f, \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "already_configured": "\u0410\u043a\u0441\u0435\u0441\u0443\u0430\u0440 \u0432\u0436\u0435 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0439 \u0437 \u0446\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u043e\u043c.", + "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.", + "already_paired": "\u0426\u0435\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440 \u0432\u0436\u0435 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0439 \u0437 \u0456\u043d\u0448\u0438\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043a\u0438\u043d\u044c\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0430 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "ignored_model": "\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 HomeKit \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u043c\u043e\u0434\u0435\u043b\u0456 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u0430, \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0431\u0456\u043b\u044c\u0448 \u043f\u043e\u0432\u043d\u0430 \u043d\u0430\u0442\u0438\u0432\u043d\u0430 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f.", + "invalid_config_entry": "\u0426\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u044f\u043a \u0433\u043e\u0442\u043e\u0432\u0438\u0439 \u0434\u043e \u043e\u0431'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0432 \u043f\u0430\u0440\u0443, \u0430\u043b\u0435 \u0432 Home Assistant \u0432\u0436\u0435 \u0454 \u043a\u043e\u043d\u0444\u043b\u0456\u043a\u0442\u043d\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 \u0434\u043b\u044f \u043d\u044c\u043e\u0433\u043e, \u044f\u043a\u0438\u0439 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438.", + "invalid_properties": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0456 \u0432\u043b\u0430\u0441\u0442\u0438\u0432\u043e\u0441\u0442\u0456, \u043e\u0433\u043e\u043b\u043e\u0448\u0435\u043d\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c.", + "no_devices": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0457, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043b\u044f \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f, \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456." + }, + "error": { + "authentication_error": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434 HomeKit. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043a\u043e\u0434 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "max_peers_error": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0456\u0434\u0445\u0438\u043b\u0438\u0432 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0447\u0435\u0440\u0435\u0437 \u0432\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c \u0432\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u044f.", + "pairing_failed": "\u041f\u0456\u0434 \u0447\u0430\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 \u0441\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430. \u0426\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0442\u0438\u043c\u0447\u0430\u0441\u043e\u0432\u0438\u0439 \u0437\u0431\u0456\u0439 \u0430\u0431\u043e \u0412\u0430\u0448 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0449\u0435 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "unable_to_pair": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "unknown_error": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u0438\u0432 \u043f\u0440\u043e \u043d\u0435\u0432\u0456\u0434\u043e\u043c\u0443 \u043f\u043e\u043c\u0438\u043b\u043a\u0443. \u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443." + }, + "flow_title": "{name} \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0456\u0432 HomeKit", + "step": { + "busy_error": { + "description": "\u0421\u043a\u0430\u0441\u0443\u0439\u0442\u0435 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u043d\u0430 \u0432\u0441\u0456\u0445 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430\u0445 \u0430\u0431\u043e \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f.", + "title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0443\u0436\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e \u0456\u043d\u0448\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430." + }, + "max_tries_error": { + "description": "\u041f\u043e\u043d\u0430\u0434 100 \u0441\u043f\u0440\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457 \u043f\u0440\u043e\u0439\u0448\u043b\u0438 \u043d\u0435\u0432\u0434\u0430\u043b\u043e. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0430 \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f.", + "title": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0441\u043f\u0440\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457." + }, + "pair": { + "data": { + "pairing_code": "\u041a\u043e\u0434 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "description": "HomeKit Controller \u043e\u0431\u043c\u0456\u043d\u044e\u0454\u0442\u044c\u0441\u044f \u0434\u0430\u043d\u0438\u043c\u0438 \u0437 {name} \u043f\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0447\u0438 \u0431\u0435\u0437\u043f\u0435\u0447\u043d\u0435 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0435 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e\u0441\u0442\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u043e\u043a\u0440\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 HomeKit \u0430\u0431\u043e iCloud. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u0441\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044f HomeKit (\u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 XXX-XX-XXX), \u0449\u043e\u0431 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440. \u0426\u0435\u0439 \u043a\u043e\u0434 \u0437\u0430\u0437\u0432\u0438\u0447\u0430\u0439 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0430\u043c\u043e\u043c\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0430\u0431\u043e \u043d\u0430 \u0443\u043f\u0430\u043a\u043e\u0432\u0446\u0456.", + "title": "\u0421\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438 \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u0456\u0432 HomeKit" + }, + "protocol_error": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u043c\u043e\u0436\u043b\u0438\u0432\u043e, \u043d\u0435 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0456 \u043c\u043e\u0436\u0435 \u0437\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0438\u0441\u044f \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f \u0444\u0456\u0437\u0438\u0447\u043d\u043e\u0457 \u0430\u0431\u043e \u0432\u0456\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0457 \u043a\u043d\u043e\u043f\u043a\u0438. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438, \u0430\u0431\u043e \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0456 \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f.", + "title": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u0430\u043a\u0441\u0435\u0441\u0443\u0430\u0440\u043e\u043c." + }, + "user": { + "data": { + "device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "description": "HomeKit Controller \u043e\u0431\u043c\u0456\u043d\u044e\u0454\u0442\u044c\u0441\u044f \u0434\u0430\u043d\u0438\u043c\u0438 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456 \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u0431\u0435\u0437\u043f\u0435\u0447\u043d\u043e\u0433\u043e \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043e\u0433\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e\u0441\u0442\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u043e\u043a\u0440\u0435\u043c\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 HomeKit \u0430\u0431\u043e iCloud. \u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u0437 \u044f\u043a\u0438\u043c \u0445\u043e\u0447\u0435\u0442\u0435 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443:", + "title": "\u0412\u0438\u0431\u0456\u0440 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "\u041a\u043d\u043e\u043f\u043a\u0430 1", + "button10": "\u041a\u043d\u043e\u043f\u043a\u0430 10", + "button2": "\u041a\u043d\u043e\u043f\u043a\u0430 2", + "button3": "\u041a\u043d\u043e\u043f\u043a\u0430 3", + "button4": "\u041a\u043d\u043e\u043f\u043a\u0430 4", + "button5": "\u041a\u043d\u043e\u043f\u043a\u0430 5", + "button6": "\u041a\u043d\u043e\u043f\u043a\u0430 6", + "button7": "\u041a\u043d\u043e\u043f\u043a\u0430 7", + "button8": "\u041a\u043d\u043e\u043f\u043a\u0430 8", + "button9": "\u041a\u043d\u043e\u043f\u043a\u0430 9", + "doorbell": "\u0414\u0432\u0435\u0440\u043d\u0438\u0439 \u0434\u0437\u0432\u0456\u043d\u043e\u043a" + }, + "trigger_type": { + "double_press": "\"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0456\u0447\u0456", + "long_press": "\"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0456 \u0443\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f", + "single_press": "\"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430" + } + }, + "title": "HomeKit Controller" +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/de.json b/homeassistant/components/homematicip_cloud/translations/de.json index c421620fd98830..1da1e06c0fb8d8 100644 --- a/homeassistant/components/homematicip_cloud/translations/de.json +++ b/homeassistant/components/homematicip_cloud/translations/de.json @@ -2,11 +2,11 @@ "config": { "abort": { "already_configured": "Der Accesspoint ist bereits konfiguriert", - "connection_aborted": "Konnte nicht mit HMIP Server verbinden", - "unknown": "Ein unbekannter Fehler ist aufgetreten." + "connection_aborted": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" }, "error": { - "invalid_sgtin_or_pin": "Ung\u00fcltige PIN, bitte versuche es erneut.", + "invalid_sgtin_or_pin": "Ung\u00fcltige SGTIN oder PIN-Code, bitte versuche es erneut.", "press_the_button": "Bitte dr\u00fccke die blaue Taste.", "register_failed": "Registrierung fehlgeschlagen, bitte versuche es erneut.", "timeout_button": "Zeit\u00fcberschreitung beim Dr\u00fccken der blauen Taste. Bitte versuche es erneut." @@ -16,7 +16,7 @@ "data": { "hapid": "Accesspoint ID (SGTIN)", "name": "Name (optional, wird als Pr\u00e4fix f\u00fcr alle Ger\u00e4te verwendet)", - "pin": "PIN Code (optional)" + "pin": "PIN-Code" }, "title": "HomematicIP Accesspoint ausw\u00e4hlen" }, diff --git a/homeassistant/components/homematicip_cloud/translations/tr.json b/homeassistant/components/homematicip_cloud/translations/tr.json new file mode 100644 index 00000000000000..72f139217cace4 --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/tr.json @@ -0,0 +1,9 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "connection_aborted": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/uk.json b/homeassistant/components/homematicip_cloud/translations/uk.json new file mode 100644 index 00000000000000..1ed2e317f8b29e --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/uk.json @@ -0,0 +1,29 @@ +{ + "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.", + "connection_aborted": "\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" + }, + "error": { + "invalid_sgtin_or_pin": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 SGTIN \u0430\u0431\u043e PIN-\u043a\u043e\u0434, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "press_the_button": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0441\u0438\u043d\u044e \u043a\u043d\u043e\u043f\u043a\u0443.", + "register_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443.", + "timeout_button": "\u0412\u0438 \u043d\u0435 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u043b\u0438 \u043d\u0430 \u0441\u0438\u043d\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u0432 \u043c\u0435\u0436\u0430\u0445 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u0447\u0430\u0441\u0443, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "init": { + "data": { + "hapid": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0442\u043e\u0447\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 (SGTIN)", + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u044f\u043a \u043f\u0440\u0435\u0444\u0456\u043a\u0441 \u0434\u043b\u044f \u043d\u0430\u0437\u0432\u0438 \u0432\u0441\u0456\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432)", + "pin": "PIN-\u043a\u043e\u0434" + }, + "title": "HomematicIP Cloud" + }, + "link": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0441\u0438\u043d\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0442\u043e\u0447\u0446\u0456 \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0456 \u043a\u043d\u043e\u043f\u043a\u0443 ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0449\u043e\u0431 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438 HomematicIP \u0432 Home Assistant. \n\n![\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438] (/static/images/config_flows/config_homematicip_cloud.png)", + "title": "\u041f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/de.json b/homeassistant/components/huawei_lte/translations/de.json index 7da997f12d6645..43361e46929aa1 100644 --- a/homeassistant/components/huawei_lte/translations/de.json +++ b/homeassistant/components/huawei_lte/translations/de.json @@ -1,14 +1,15 @@ { "config": { "abort": { - "already_configured": "Dieses Ger\u00e4t wurde bereits konfiguriert", - "already_in_progress": "Dieses Ger\u00e4t wurde bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "not_huawei_lte": "Kein Huawei LTE-Ger\u00e4t" }, "error": { "connection_timeout": "Verbindungszeit\u00fcberschreitung", "incorrect_password": "Ung\u00fcltiges Passwort", "incorrect_username": "Ung\u00fcltiger Benutzername", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_url": "Ung\u00fcltige URL", "login_attempts_exceeded": "Maximale Anzahl von Anmeldeversuchen \u00fcberschritten. Bitte versuche es sp\u00e4ter erneut", "response_error": "Unbekannter Fehler vom Ger\u00e4t", diff --git a/homeassistant/components/huawei_lte/translations/tr.json b/homeassistant/components/huawei_lte/translations/tr.json index a76e31fa483542..ba934acc39b8c8 100644 --- a/homeassistant/components/huawei_lte/translations/tr.json +++ b/homeassistant/components/huawei_lte/translations/tr.json @@ -1,8 +1,35 @@ { + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "connection_timeout": "Ba\u011flant\u0131 zamana\u015f\u0131m\u0131", + "incorrect_password": "Yanl\u0131\u015f parola", + "incorrect_username": "Yanl\u0131\u015f kullan\u0131c\u0131 ad\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_url": "Ge\u00e7ersiz URL", + "login_attempts_exceeded": "Maksimum oturum a\u00e7ma denemesi a\u015f\u0131ld\u0131, l\u00fctfen daha sonra tekrar deneyin", + "response_error": "Cihazdan bilinmeyen hata", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "url": "URL", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Cihaz eri\u015fim ayr\u0131nt\u0131lar\u0131n\u0131 girin. Kullan\u0131c\u0131 ad\u0131 ve parolan\u0131n belirtilmesi iste\u011fe ba\u011fl\u0131d\u0131r, ancak daha fazla entegrasyon \u00f6zelli\u011fi i\u00e7in destek sa\u011flar. \u00d6te yandan, yetkili bir ba\u011flant\u0131n\u0131n kullan\u0131lmas\u0131, entegrasyon aktifken Ev Asistan\u0131 d\u0131\u015f\u0131ndan cihaz web aray\u00fcz\u00fcne eri\u015fimde sorunlara neden olabilir ve tam tersi." + } + } + }, "options": { "step": { "init": { "data": { + "recipient": "SMS bildirimi al\u0131c\u0131lar\u0131", "track_new_devices": "Yeni cihazlar\u0131 izle" } } diff --git a/homeassistant/components/huawei_lte/translations/uk.json b/homeassistant/components/huawei_lte/translations/uk.json new file mode 100644 index 00000000000000..17f3d3b71c3386 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/uk.json @@ -0,0 +1,42 @@ +{ + "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_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.", + "not_huawei_lte": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Huawei LTE" + }, + "error": { + "connection_timeout": "\u0427\u0430\u0441 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u0438\u043d\u0443\u0432.", + "incorrect_password": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", + "incorrect_username": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0456\u043c\u2019\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "invalid_url": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430.", + "login_attempts_exceeded": "\u041f\u0435\u0440\u0435\u0432\u0438\u0449\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0443 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0441\u043f\u0440\u043e\u0431 \u0432\u0445\u043e\u0434\u0443, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435.", + "response_error": "\u041d\u0435\u0432\u0456\u0434\u043e\u043c\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Huawei LTE: {name}", + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u0412\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043b\u043e\u0433\u0456\u043d \u0456 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0430\u043b\u0435 \u0446\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u044c \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0444\u0443\u043d\u043a\u0446\u0456\u0457. \u0417 \u0456\u043d\u0448\u043e\u0433\u043e \u0431\u043e\u043a\u0443, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043e\u0433\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u043c\u043e\u0436\u0435 \u0432\u0438\u043a\u043b\u0438\u043a\u0430\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u0434\u043e \u0432\u0435\u0431-\u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u0437 Home Assistant, \u043a\u043e\u043b\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430, \u0456 \u043d\u0430\u0432\u043f\u0430\u043a\u0438.", + "title": "Huawei LTE" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430 \u0441\u043b\u0443\u0436\u0431\u0438 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u044c (\u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a)", + "recipient": "\u041e\u0434\u0435\u0440\u0436\u0443\u0432\u0430\u0447\u0456 SMS-\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c", + "track_new_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u0442\u0438 \u043d\u043e\u0432\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/cs.json b/homeassistant/components/hue/translations/cs.json index 76606338320a14..1708abfe750173 100644 --- a/homeassistant/components/hue/translations/cs.json +++ b/homeassistant/components/hue/translations/cs.json @@ -48,7 +48,7 @@ }, "trigger_type": { "remote_button_long_release": "Tla\u010d\u00edtko \"{subtype}\" uvoln\u011bno po dlouh\u00e9m stisku", - "remote_button_short_press": "Stisknuto tla\u010d\u00edtko \"{subtype}\"", + "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" diff --git a/homeassistant/components/hue/translations/de.json b/homeassistant/components/hue/translations/de.json index 0defb33ae5e509..122e1ba6f5c35a 100644 --- a/homeassistant/components/hue/translations/de.json +++ b/homeassistant/components/hue/translations/de.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "all_configured": "Alle Philips Hue Bridges sind bereits konfiguriert", - "already_configured": "Bridge ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt.", - "cannot_connect": "Verbindung zur Bridge nicht m\u00f6glich", - "discover_timeout": "Nicht in der Lage Hue Bridges zu entdecken", - "no_bridges": "Keine Philips Hue Bridges entdeckt", + "all_configured": "Es sind bereits alle Philips Hue Bridges konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen", + "discover_timeout": "Es k\u00f6nnen keine Hue Bridges erkannt werden", + "no_bridges": "Keine Philips Hue Bridges erkannt", "not_hue_bridge": "Keine Philips Hue Bridge entdeckt", "unknown": "Unbekannter Fehler ist aufgetreten" }, "error": { - "linking": "Unbekannter Link-Fehler aufgetreten.", + "linking": "Unerwarteter Fehler", "register_failed": "Registrieren fehlgeschlagen, bitte versuche es erneut" }, "step": { @@ -22,7 +22,7 @@ "title": "W\u00e4hle eine Hue Bridge" }, "link": { - "description": "Dr\u00fccke den Knopf auf der Bridge, um Philips Hue mit Home Assistant zu registrieren.\n\n![Position des Buttons auf der Bridge](/static/images/config_philips_hue.jpg)", + "description": "Dr\u00fccke den Knopf auf der Bridge, um Philips Hue mit Home Assistant zu verkn\u00fcpfen.\n\n![Position des Buttons auf der Bridge](/static/images/config_philips_hue.jpg)", "title": "Hub verbinden" }, "manual": { @@ -58,8 +58,8 @@ "step": { "init": { "data": { - "allow_hue_groups": "Erlaube Hue Gruppen", - "allow_unreachable": "Erlauben Sie unerreichbaren Gl\u00fchbirnen, ihren Zustand korrekt zu melden" + "allow_hue_groups": "Hue-Gruppen erlauben", + "allow_unreachable": "Erlaube nicht erreichbaren Gl\u00fchlampen, ihren Zustand korrekt zu melden" } } } diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index 873f60946d5a80..b144393c3d1bae 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -47,11 +47,11 @@ "turn_on": "w\u0142\u0105cznik" }, "trigger_type": { - "remote_button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", - "remote_button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", - "remote_button_short_release": "\"{subtype}\" zostanie zwolniony", - "remote_double_button_long_press": "oba \"{subtype}\" zostan\u0105 zwolnione po d\u0142ugim naci\u015bni\u0119ciu", - "remote_double_button_short_press": "oba \"{subtype}\" zostan\u0105 zwolnione" + "remote_button_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "remote_button_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "remote_button_short_release": "przycisk \"{subtype}\" zostanie zwolniony", + "remote_double_button_long_press": "oba przyciski \"{subtype}\" zostan\u0105 zwolnione po d\u0142ugim naci\u015bni\u0119ciu", + "remote_double_button_short_press": "oba przyciski \"{subtype}\" zostan\u0105 zwolnione" } }, "options": { diff --git a/homeassistant/components/hue/translations/pt.json b/homeassistant/components/hue/translations/pt.json index 8eabbbb08cc417..09d839cbd5c08a 100644 --- a/homeassistant/components/hue/translations/pt.json +++ b/homeassistant/components/hue/translations/pt.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "Todos os hubs Philips Hue j\u00e1 est\u00e3o configurados", - "already_configured": "Hue j\u00e1 est\u00e1 configurado", + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", - "cannot_connect": "N\u00e3o foi poss\u00edvel conectar-se ao hub", + "cannot_connect": "Falha na liga\u00e7\u00e3o", "discover_timeout": "Nenhum hub Hue descoberto", "no_bridges": "Nenhum hub Philips Hue descoberto", "not_hue_bridge": "N\u00e3o \u00e9 uma bridge Hue", - "unknown": "Ocorreu um erro desconhecido" + "unknown": "Erro inesperado" }, "error": { - "linking": "Ocorreu um erro de liga\u00e7\u00e3o desconhecido.", + "linking": "Erro inesperado", "register_failed": "Falha ao registar, por favor, tente novamente" }, "step": { diff --git a/homeassistant/components/hue/translations/tr.json b/homeassistant/components/hue/translations/tr.json new file mode 100644 index 00000000000000..984c91e8f366b0 --- /dev/null +++ b/homeassistant/components/hue/translations/tr.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "error": { + "linking": "Beklenmeyen hata" + }, + "step": { + "init": { + "data": { + "host": "Ana Bilgisayar" + } + }, + "manual": { + "data": { + "host": "Ana Bilgisayar" + }, + "title": "Bir Hue k\u00f6pr\u00fcs\u00fcn\u00fc manuel olarak yap\u0131land\u0131rma" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u0130lk d\u00fc\u011fme", + "button_2": "\u0130kinci d\u00fc\u011fme", + "button_3": "\u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fme", + "button_4": "D\u00f6rd\u00fcnc\u00fc d\u00fc\u011fme", + "double_buttons_1_3": "Birinci ve \u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fmeler", + "double_buttons_2_4": "\u0130kinci ve D\u00f6rd\u00fcnc\u00fc d\u00fc\u011fmeler", + "turn_off": "Kapat", + "turn_on": "A\u00e7" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_groups": "Hue gruplar\u0131na izin ver", + "allow_unreachable": "Ula\u015f\u0131lamayan ampullerin durumlar\u0131n\u0131 do\u011fru \u015fekilde bildirmesine izin verin" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/uk.json b/homeassistant/components/hue/translations/uk.json new file mode 100644 index 00000000000000..8e9c5ca82cbbf3 --- /dev/null +++ b/homeassistant/components/hue/translations/uk.json @@ -0,0 +1,67 @@ +{ + "config": { + "abort": { + "all_configured": "\u0412\u0441\u0456 \u0448\u043b\u044e\u0437\u0438 Philips Hue \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0456.", + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "discover_timeout": "\u0428\u043b\u044e\u0437 Philips Hue \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "no_bridges": "\u0428\u043b\u044e\u0437\u0438 Philips Hue \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456.", + "not_hue_bridge": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0454 \u0448\u043b\u044e\u0437\u043e\u043c Hue.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "linking": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "register_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "init": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0448\u043b\u044e\u0437 Hue" + }, + "link": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0448\u043b\u044e\u0437\u0456 \u0434\u043b\u044f \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u0457 Philips Hue \u0432 Home Assistant. \n\n![\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043d\u0430 \u0448\u043b\u044e\u0437\u0456] (/static/images/config_philips_hue.jpg)", + "title": "Philips Hue" + }, + "manual": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "title": "\u0420\u0443\u0447\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0448\u043b\u044e\u0437\u0443" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "dim_down": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "dim_up": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "double_buttons_1_3": "\u041f\u0435\u0440\u0448\u0430 \u0456 \u0442\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0438", + "double_buttons_2_4": "\u0414\u0440\u0443\u0433\u0430 \u0456 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0438", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "remote_button_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_short_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_double_button_long_press": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u043e \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_double_button_short_press": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u043e \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_groups": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0433\u0440\u0443\u043f\u0438 Hue", + "allow_unreachable": "\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u044f\u0442\u0438 \u0441\u0442\u0430\u043d \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/ca.json b/homeassistant/components/huisbaasje/translations/ca.json new file mode 100644 index 00000000000000..99d99d4340f769 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "connection_exception": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unauthenticated_exception": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/cs.json b/homeassistant/components/huisbaasje/translations/cs.json new file mode 100644 index 00000000000000..07a1d29330b60b --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "connection_exception": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unauthenticated_exception": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/en.json b/homeassistant/components/huisbaasje/translations/en.json new file mode 100644 index 00000000000000..16832be30e75bb --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "connection_exception": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unauthenticated_exception": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/et.json b/homeassistant/components/huisbaasje/translations/et.json new file mode 100644 index 00000000000000..d079bf2a0c7bb1 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "connection_exception": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unauthenticated_exception": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/it.json b/homeassistant/components/huisbaasje/translations/it.json new file mode 100644 index 00000000000000..0171bdcd9f27ca --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "connection_exception": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unauthenticated_exception": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/no.json b/homeassistant/components/huisbaasje/translations/no.json new file mode 100644 index 00000000000000..81351599c1672f --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "connection_exception": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unauthenticated_exception": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/pl.json b/homeassistant/components/huisbaasje/translations/pl.json new file mode 100644 index 00000000000000..ab38d61de0be0d --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "connection_exception": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unauthenticated_exception": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json new file mode 100644 index 00000000000000..ada9aed539afd8 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/ru.json @@ -0,0 +1,21 @@ +{ + "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": { + "connection_exception": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "unauthenticated_exception": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/tr.json b/homeassistant/components/huisbaasje/translations/tr.json new file mode 100644 index 00000000000000..fa5bd3112861b5 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "connection_exception": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unauthenticated_exception": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen Hata" + }, + "step": { + "user": { + "data": { + "password": "\u015eifre", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/zh-Hant.json b/homeassistant/components/huisbaasje/translations/zh-Hant.json new file mode 100644 index 00000000000000..bb120ab60ddad0 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "connection_exception": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unauthenticated_exception": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/tr.json b/homeassistant/components/humidifier/translations/tr.json new file mode 100644 index 00000000000000..7bcdbc46a0b2d7 --- /dev/null +++ b/homeassistant/components/humidifier/translations/tr.json @@ -0,0 +1,25 @@ +{ + "device_automation": { + "action_type": { + "set_mode": "{entity_name} \u00fczerindeki mod de\u011fi\u015ftirme", + "turn_on": "{entity_name} a\u00e7\u0131n" + }, + "condition_type": { + "is_mode": "{entity_name} belirli bir moda ayarland\u0131", + "is_off": "{entity_name} kapal\u0131", + "is_on": "{entity_name} a\u00e7\u0131k" + }, + "trigger_type": { + "target_humidity_changed": "{entity_name} hedef nem de\u011fi\u015fti", + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, + "state": { + "_": { + "off": "Kapal\u0131", + "on": "A\u00e7\u0131k" + } + }, + "title": "Nemlendirici" +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/uk.json b/homeassistant/components/humidifier/translations/uk.json index 4081c4e13fc281..484f014bd92fbe 100644 --- a/homeassistant/components/humidifier/translations/uk.json +++ b/homeassistant/components/humidifier/translations/uk.json @@ -1,8 +1,28 @@ { "device_automation": { + "action_type": { + "set_humidity": "{entity_name}: \u0437\u0430\u0434\u0430\u0442\u0438 \u0432\u043e\u043b\u043e\u0433\u0456\u0441\u0442\u044c", + "set_mode": "{entity_name}: \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c", + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_mode": "{entity_name} \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0434\u0430\u043d\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456 \u0440\u043e\u0431\u043e\u0442\u0438", + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { + "target_humidity_changed": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0437\u0430\u0434\u0430\u043d\u043e\u0457 \u0432\u043e\u043b\u043e\u0433\u043e\u0441\u0442\u0456", "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", - "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u043e" + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } - } + }, + "state": { + "_": { + "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + } + }, + "title": "\u0417\u0432\u043e\u043b\u043e\u0436\u0443\u0432\u0430\u0447" } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/tr.json b/homeassistant/components/hunterdouglas_powerview/translations/tr.json new file mode 100644 index 00000000000000..01b0359789e8e8 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/tr.json @@ -0,0 +1,18 @@ +{ + "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": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/uk.json b/homeassistant/components/hunterdouglas_powerview/translations/uk.json new file mode 100644 index 00000000000000..959fcff12b0cdd --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "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": { + "link": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", + "title": "Hunter Douglas PowerView" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "title": "Hunter Douglas PowerView" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/tr.json b/homeassistant/components/hvv_departures/translations/tr.json new file mode 100644 index 00000000000000..74fc593062b960 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/uk.json b/homeassistant/components/hvv_departures/translations/uk.json new file mode 100644 index 00000000000000..364d351a99c3dc --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/uk.json @@ -0,0 +1,47 @@ +{ + "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." + }, + "error": { + "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.", + "no_results": "\u041d\u0435\u043c\u0430\u0454 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437 \u0456\u043d\u0448\u043e\u044e \u0441\u0442\u0430\u043d\u0446\u0456\u0454\u044e / \u0430\u0434\u0440\u0435\u0441\u043e\u044e." + }, + "step": { + "station": { + "data": { + "station": "\u0421\u0442\u0430\u043d\u0446\u0456\u044f / \u0410\u0434\u0440\u0435\u0441\u0430" + }, + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0456\u044e / \u0430\u0434\u0440\u0435\u0441\u0443" + }, + "station_select": { + "data": { + "station": "\u0421\u0442\u0430\u043d\u0446\u0456\u044f / \u0410\u0434\u0440\u0435\u0441\u0430" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d\u0446\u0456\u044e / \u0430\u0434\u0440\u0435\u0441\u0443" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e API HVV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043b\u0456\u043d\u0456\u0457", + "offset": "\u0417\u043c\u0456\u0449\u0435\u043d\u043d\u044f (\u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)", + "real_time": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0430\u043d\u0456 \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0447\u0430\u0441\u0443" + }, + "description": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430 \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044f", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/fr.json b/homeassistant/components/hyperion/translations/fr.json index 8c1cb919d11f12..90733a8968bd34 100644 --- a/homeassistant/components/hyperion/translations/fr.json +++ b/homeassistant/components/hyperion/translations/fr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "auth_new_token_not_work_error": "\u00c9chec de l'authentification \u00e0 l'aide du jeton nouvellement cr\u00e9\u00e9", + "cannot_connect": "Echec de connection" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/hyperion/translations/it.json b/homeassistant/components/hyperion/translations/it.json index ff3170ffb98450..6fee49ebe14706 100644 --- a/homeassistant/components/hyperion/translations/it.json +++ b/homeassistant/components/hyperion/translations/it.json @@ -8,7 +8,7 @@ "auth_required_error": "Impossibile determinare se \u00e8 necessaria l'autorizzazione", "cannot_connect": "Impossibile connettersi", "no_id": "L'istanza Hyperion Ambilight non ha segnalato il suo ID", - "reauth_successful": "Ri-autenticazione completata con successo" + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/hyperion/translations/tr.json b/homeassistant/components/hyperion/translations/tr.json index 6f46000e3e20b6..7b3f9f845a1329 100644 --- a/homeassistant/components/hyperion/translations/tr.json +++ b/homeassistant/components/hyperion/translations/tr.json @@ -2,11 +2,17 @@ "config": { "abort": { "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "auth_new_token_not_granted_error": "Hyperion UI'de yeni olu\u015fturulan belirte\u00e7 onaylanmad\u0131", "auth_new_token_not_work_error": "Yeni olu\u015fturulan belirte\u00e7 kullan\u0131larak kimlik do\u011frulamas\u0131 ba\u015far\u0131s\u0131z oldu", "auth_required_error": "Yetkilendirmenin gerekli olup olmad\u0131\u011f\u0131 belirlenemedi", "cannot_connect": "Ba\u011flanma hatas\u0131", - "no_id": "Hyperion Ambilight \u00f6rne\u011fi kimli\u011fini bildirmedi" + "no_id": "Hyperion Ambilight \u00f6rne\u011fi kimli\u011fini bildirmedi", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci" }, "step": { "auth": { @@ -15,6 +21,9 @@ "token": "Veya \u00f6nceden varolan belirte\u00e7 leri sa\u011flay\u0131n" } }, + "confirm": { + "title": "Hyperion Ambilight hizmetinin eklenmesini onaylay\u0131n" + }, "create_token": { "title": "Otomatik olarak yeni kimlik do\u011frulama belirteci olu\u015fturun" }, @@ -23,6 +32,7 @@ }, "user": { "data": { + "host": "Ana Bilgisayar", "port": "Port" } } diff --git a/homeassistant/components/hyperion/translations/uk.json b/homeassistant/components/hyperion/translations/uk.json new file mode 100644 index 00000000000000..ae44b0610da56c --- /dev/null +++ b/homeassistant/components/hyperion/translations/uk.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \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.", + "auth_new_token_not_granted_error": "\u0421\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u043d\u0435 \u0431\u0443\u0432 \u0441\u0445\u0432\u0430\u043b\u0435\u043d\u0438\u0439 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Hyperion.", + "auth_new_token_not_work_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043e\u0433\u043e \u0442\u043e\u043a\u0435\u043d\u0430.", + "auth_required_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438, \u0447\u0438 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\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", + "no_id": "Hyperion Ambilight \u043d\u0435 \u043d\u0430\u0434\u0430\u0432 \u0441\u0432\u0456\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440.", + "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" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443." + }, + "step": { + "auth": { + "data": { + "create_token": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d", + "token": "\u0410\u0431\u043e \u043d\u0430\u0434\u0430\u0442\u0438 \u043d\u0430\u044f\u0432\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d" + }, + "description": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0456 Hyperion Ambilight." + }, + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Hyperion Ambilight? \n\n ** \u0425\u043e\u0441\u0442: ** {host}\n ** \u041f\u043e\u0440\u0442: ** {port}\n ** ID **: {id}", + "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0456\u0442\u044c \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u0441\u043b\u0443\u0436\u0431\u0438 Hyperion Ambilight" + }, + "create_token": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438 ** \u043d\u0438\u0436\u0447\u0435, \u0449\u043e\u0431 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457. \u0412\u0438 \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0456 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 Hyperion \u0434\u043b\u044f \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f \u0437\u0430\u043f\u0438\u0442\u0443. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 - \" {auth_id} \"", + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "create_token_external": { + "title": "\u041f\u0440\u0438\u0439\u043d\u044f\u0442\u0438 \u043d\u043e\u0432\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Hyperion" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "\u041f\u0440\u0456\u043e\u0440\u0438\u0442\u0435\u0442 Hyperion \u0434\u043b\u044f \u043a\u043e\u043b\u044c\u043e\u0440\u0456\u0432 \u0456 \u0435\u0444\u0435\u043a\u0442\u0456\u0432" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/de.json b/homeassistant/components/iaqualink/translations/de.json index e7e1002015c36a..0a678baf7ca765 100644 --- a/homeassistant/components/iaqualink/translations/de.json +++ b/homeassistant/components/iaqualink/translations/de.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/iaqualink/translations/tr.json b/homeassistant/components/iaqualink/translations/tr.json new file mode 100644 index 00000000000000..c2c70f3e45b398 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "L\u00fctfen iAqualink hesab\u0131n\u0131z i\u00e7in kullan\u0131c\u0131 ad\u0131 ve parolay\u0131 girin." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/uk.json b/homeassistant/components/iaqualink/translations/uk.json new file mode 100644 index 00000000000000..b855d75572603c --- /dev/null +++ b/homeassistant/components/iaqualink/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043b\u043e\u0433\u0456\u043d \u0456 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0412\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 iAqualink.", + "title": "Jandy iAqualink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/de.json b/homeassistant/components/icloud/translations/de.json index e7441792d91231..64a6bcd885c542 100644 --- a/homeassistant/components/icloud/translations/de.json +++ b/homeassistant/components/icloud/translations/de.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Konto bereits konfiguriert", - "no_device": "Auf keinem Ihrer Ger\u00e4te ist \"Find my iPhone\" aktiviert" + "already_configured": "Konto wurde bereits konfiguriert", + "no_device": "Auf keinem Ihrer Ger\u00e4te ist \"Find my iPhone\" aktiviert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "send_verification_code": "Fehler beim Senden des Best\u00e4tigungscodes", "validate_verification_code": "Verifizierung des Verifizierungscodes fehlgeschlagen. W\u00e4hle ein vertrauensw\u00fcrdiges Ger\u00e4t aus und starte die Verifizierung erneut" }, @@ -12,7 +14,8 @@ "reauth": { "data": { "password": "Passwort" - } + }, + "title": "Integration erneut authentifizieren" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/tr.json b/homeassistant/components/icloud/translations/tr.json index 3d74852ce50ecb..86581625d96e27 100644 --- a/homeassistant/components/icloud/translations/tr.json +++ b/homeassistant/components/icloud/translations/tr.json @@ -1,7 +1,28 @@ { "config": { "abort": { - "no_device": "Hi\u00e7bir cihaz\u0131n\u0131zda \"iPhone'umu bul\" etkin de\u011fil" + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "no_device": "Hi\u00e7bir cihaz\u0131n\u0131zda \"iPhone'umu bul\" etkin de\u011fil", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "validate_verification_code": "Do\u011frulama kodunuzu do\u011frulamay\u0131 ba\u015faramad\u0131n\u0131z, bir g\u00fcven ayg\u0131t\u0131 se\u00e7in ve do\u011frulamay\u0131 yeniden ba\u015flat\u0131n" + }, + "step": { + "reauth": { + "data": { + "password": "Parola" + }, + "description": "{username} i\u00e7in \u00f6nceden girdi\u011finiz \u015fifreniz art\u0131k \u00e7al\u0131\u015fm\u0131yor. Bu entegrasyonu kullanmaya devam etmek i\u00e7in \u015fifrenizi g\u00fcncelleyin." + }, + "user": { + "data": { + "password": "Parola", + "username": "E-posta", + "with_family": "Aileyle" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/uk.json b/homeassistant/components/icloud/translations/uk.json new file mode 100644 index 00000000000000..ac65157f050bd1 --- /dev/null +++ b/homeassistant/components/icloud/translations/uk.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "no_device": "\u041d\u0430 \u0436\u043e\u0434\u043d\u043e\u043c\u0443 \u0437 \u0412\u0430\u0448\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0456\u044f \"\u0417\u043d\u0430\u0439\u0442\u0438 iPhone\".", + "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" + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "send_verification_code": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u0438\u0442\u0438 \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f.", + "validate_verification_code": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0434\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0442\u0430 \u043f\u043e\u0447\u043d\u0456\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0443 \u0437\u043d\u043e\u0432\u0443." + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0420\u0430\u043d\u0456\u0448\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f {username} \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u043f\u0440\u0430\u0446\u044e\u0454. \u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c, \u0449\u043e\u0431 \u043f\u0440\u043e\u0434\u043e\u0432\u0436\u0438\u0442\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0446\u044e \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + }, + "trusted_device": { + "data": { + "trusted_device": "\u0414\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0434\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439", + "title": "\u0414\u043e\u0432\u0456\u0440\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 iCloud" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "with_family": "\u0417 \u0441\u0456\u043c'\u0454\u044e" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456 iCloud" + }, + "verification_code": { + "data": { + "verification_code": "\u041a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u0432\u0456\u0434 iCloud", + "title": "\u041a\u043e\u0434 \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f iCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/de.json b/homeassistant/components/ifttt/translations/de.json index c96928afa18d23..5184e89f29a11a 100644 --- a/homeassistant/components/ifttt/translations/de.json +++ b/homeassistant/components/ifttt/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, musst du die Aktion \"Eine Webanforderung erstellen\" aus dem [IFTTT Webhook Applet]({applet_url}) ausw\u00e4hlen.\n\nF\u00fclle folgende Informationen aus: \n- URL: `{webhook_url}`\n- Methode: POST\n- Inhaltstyp: application/json\n\nIn der Dokumentation ({docs_url}) findest du Informationen zur Konfiguration der Automation eingehender Daten." }, diff --git a/homeassistant/components/ifttt/translations/tr.json b/homeassistant/components/ifttt/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/ifttt/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/uk.json b/homeassistant/components/ifttt/translations/uk.json new file mode 100644 index 00000000000000..8ea8f2b1970da3 --- /dev/null +++ b/homeassistant/components/ifttt/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0456\u044e \"Make a web request\" \u0437 [IFTTT Webhook applet]({applet_url}). \n\n\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json \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 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0456\u0439 \u043f\u043e \u043e\u0431\u0440\u043e\u0431\u0446\u0456 \u0434\u0430\u043d\u0438\u0445, \u0449\u043e \u043d\u0430\u0434\u0445\u043e\u0434\u044f\u0442\u044c." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 IFTTT?", + "title": "IFTTT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/input_boolean/translations/uk.json b/homeassistant/components/input_boolean/translations/uk.json index c677957de475da..be22ae53807113 100644 --- a/homeassistant/components/input_boolean/translations/uk.json +++ b/homeassistant/components/input_boolean/translations/uk.json @@ -5,5 +5,5 @@ "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } }, - "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u043b\u043e\u0433\u0456\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f" + "title": "Input Boolean" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/uk.json b/homeassistant/components/input_datetime/translations/uk.json index bd087e535a5c61..c0aeb11882fe88 100644 --- a/homeassistant/components/input_datetime/translations/uk.json +++ b/homeassistant/components/input_datetime/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u0434\u0430\u0442\u0438" + "title": "Input Datetime" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/uk.json b/homeassistant/components/input_number/translations/uk.json index 0e4265d7ca0afb..e09531134cdffa 100644 --- a/homeassistant/components/input_number/translations/uk.json +++ b/homeassistant/components/input_number/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u043e\u043c\u0435\u0440" + "title": "Input Number" } \ No newline at end of file diff --git a/homeassistant/components/input_select/translations/uk.json b/homeassistant/components/input_select/translations/uk.json index ace44f8d7a79ac..b33e64fbf48159 100644 --- a/homeassistant/components/input_select/translations/uk.json +++ b/homeassistant/components/input_select/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0438\u0431\u0440\u0430\u0442\u0438" + "title": "Input Select" } \ No newline at end of file diff --git a/homeassistant/components/input_text/translations/uk.json b/homeassistant/components/input_text/translations/uk.json index a80f4325203ed6..84bddfe3e07e1d 100644 --- a/homeassistant/components/input_text/translations/uk.json +++ b/homeassistant/components/input_text/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u043d\u044f \u0442\u0435\u043a\u0441\u0442\u0443" + "title": "Input Text" } \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/de.json b/homeassistant/components/insteon/translations/de.json index dfa4f3f7567c47..6bbc4d5474f063 100644 --- a/homeassistant/components/insteon/translations/de.json +++ b/homeassistant/components/insteon/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" @@ -22,6 +23,9 @@ } }, "plm": { + "data": { + "device": "USB-Ger\u00e4te-Pfad" + }, "title": "Insteon PLM" }, "user": { diff --git a/homeassistant/components/insteon/translations/tr.json b/homeassistant/components/insteon/translations/tr.json new file mode 100644 index 00000000000000..6c41f53b31eb41 --- /dev/null +++ b/homeassistant/components/insteon/translations/tr.json @@ -0,0 +1,89 @@ +{ + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "hubv1": { + "data": { + "host": "\u0130p Adresi", + "port": "Port" + }, + "title": "Insteon Hub S\u00fcr\u00fcm 1" + }, + "hubv2": { + "data": { + "host": "\u0130p Adresi", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Insteon Hub S\u00fcr\u00fcm 2'yi yap\u0131land\u0131r\u0131n.", + "title": "Insteon Hub S\u00fcr\u00fcm 2" + }, + "plm": { + "description": "Insteon PowerLink Modemini (PLM) yap\u0131land\u0131r\u0131n." + }, + "user": { + "data": { + "modem_type": "Modem t\u00fcr\u00fc." + }, + "description": "Insteon modem tipini se\u00e7in.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "input_error": "Ge\u00e7ersiz giri\u015fler, l\u00fctfen de\u011ferlerinizi kontrol edin." + }, + "step": { + "add_x10": { + "data": { + "unitcode": "Birim kodu (1-16)" + }, + "description": "Insteon Hub parolas\u0131n\u0131 de\u011fi\u015ftirin.", + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "\u0130p Adresi", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma ekleyin.", + "add_x10": "Bir X10 cihaz\u0131 ekleyin.", + "change_hub_config": "Hub yap\u0131land\u0131rmas\u0131n\u0131 de\u011fi\u015ftirin.", + "remove_override": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lma i\u015flemini kald\u0131r\u0131n.", + "remove_x10": "Bir X10 cihaz\u0131n\u0131 \u00e7\u0131kar\u0131n." + }, + "description": "Yap\u0131land\u0131rmak i\u00e7in bir se\u00e7enek se\u00e7in.", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "Kald\u0131r\u0131lacak bir cihaz adresi se\u00e7in" + }, + "description": "Bir cihaz\u0131 ge\u00e7ersiz k\u0131lmay\u0131 kald\u0131rma", + "title": "Insteon" + }, + "remove_x10": { + "data": { + "address": "Kald\u0131r\u0131lacak bir cihaz adresi se\u00e7in" + }, + "description": "Bir X10 cihaz\u0131n\u0131 kald\u0131r\u0131n", + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/uk.json b/homeassistant/components/insteon/translations/uk.json new file mode 100644 index 00000000000000..302d8c3676a00d --- /dev/null +++ b/homeassistant/components/insteon/translations/uk.json @@ -0,0 +1,109 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "select_single": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e." + }, + "step": { + "hubv1": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Insteon Hub \u0432\u0435\u0440\u0441\u0456\u0457 1 (\u0434\u043e 2014 \u0440\u043e\u043a\u0443)", + "title": "Insteon Hub. \u0412\u0435\u0440\u0441\u0456\u044f 1" + }, + "hubv2": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Insteon Hub \u0432\u0435\u0440\u0441\u0456\u0457 2", + "title": "Insteon Hub. \u0412\u0435\u0440\u0441\u0456\u044f 2" + }, + "plm": { + "data": { + "device": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u043e\u0434\u0435\u043c\u0443 Insteon PowerLink (PLM)", + "title": "Insteon PLM" + }, + "user": { + "data": { + "modem_type": "\u0422\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043c\u043e\u0434\u0435\u043c\u0443 Insteon.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "input_error": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f.", + "select_single": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e." + }, + "step": { + "add_override": { + "data": { + "address": "\u0410\u0434\u0440\u0435\u0441\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 1a2b3c)", + "cat": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0456\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 0x10)", + "subcat": "\u041f\u0456\u0434\u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0456\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434, 0x0a)" + }, + "description": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "title": "Insteon" + }, + "add_x10": { + "data": { + "housecode": "\u041a\u043e\u0434 \u0431\u0443\u0434\u0438\u043d\u043a\u0443 (a - p)", + "platform": "\u041f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430", + "steps": "\u041a\u0440\u043e\u043a \u0434\u0456\u043c\u043c\u0435\u0440\u0430 (\u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u043e\u0441\u0432\u0456\u0442\u043b\u044e\u0432\u0430\u043b\u044c\u043d\u0438\u0445 \u043f\u0440\u0438\u043b\u0430\u0434\u0456\u0432, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c 22)", + "unitcode": "\u042e\u043d\u0456\u0442\u043a\u043e\u0434 (1 - 16)" + }, + "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043e Insteon Hub", + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0417\u043c\u0456\u043d\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f Insteon Hub. \u041f\u0456\u0441\u043b\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f \u0446\u0438\u0445 \u0437\u043c\u0456\u043d \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 Home Assistant. \u0426\u0435 \u043d\u0435 \u0437\u043c\u0456\u043d\u044e\u0454 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0429\u043e\u0431 \u0437\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Hub.", + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "add_x10": "\u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 X10", + "change_hub_config": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0445\u0430\u0431\u0430", + "remove_override": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "remove_x10": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 X10" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u043f\u0446\u0456\u044e \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438" + }, + "description": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "title": "Insteon" + }, + "remove_x10": { + "data": { + "address": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e, \u044f\u043a\u0438\u0439 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438" + }, + "description": "\u0412\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e X10", + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/translations/de.json b/homeassistant/components/ios/translations/de.json index e9e592d18c298f..bc427bd2992cbb 100644 --- a/homeassistant/components/ios/translations/de.json +++ b/homeassistant/components/ios/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Es wird nur eine Konfiguration von Home Assistant iOS ben\u00f6tigt" + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/ios/translations/tr.json b/homeassistant/components/ios/translations/tr.json new file mode 100644 index 00000000000000..8de4663957ea85 --- /dev/null +++ b/homeassistant/components/ios/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ios/translations/uk.json b/homeassistant/components/ios/translations/uk.json new file mode 100644 index 00000000000000..5f8d69f5f29b89 --- /dev/null +++ b/homeassistant/components/ios/translations/uk.json @@ -0,0 +1,12 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/lb.json b/homeassistant/components/ipma/translations/lb.json index 7b2d374b6f551a..006d80d37869d8 100644 --- a/homeassistant/components/ipma/translations/lb.json +++ b/homeassistant/components/ipma/translations/lb.json @@ -15,5 +15,10 @@ "title": "Standuert" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API Endpunkt ereechbar" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/tr.json b/homeassistant/components/ipma/translations/tr.json index 488ad379942076..a8df63645abe95 100644 --- a/homeassistant/components/ipma/translations/tr.json +++ b/homeassistant/components/ipma/translations/tr.json @@ -1,4 +1,15 @@ { + "config": { + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam", + "mode": "Mod" + } + } + } + }, "system_health": { "info": { "api_endpoint_reachable": "Ula\u015f\u0131labilir IPMA API u\u00e7 noktas\u0131" diff --git a/homeassistant/components/ipma/translations/uk.json b/homeassistant/components/ipma/translations/uk.json index bb294cc5d21e0c..ee84e7d16f2249 100644 --- a/homeassistant/components/ipma/translations/uk.json +++ b/homeassistant/components/ipma/translations/uk.json @@ -1,9 +1,24 @@ { "config": { + "error": { + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f." + }, "step": { "user": { - "title": "\u0420\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f" + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041f\u043e\u0440\u0442\u0443\u0433\u0430\u043b\u044c\u0441\u044c\u043a\u0438\u0439 \u0456\u043d\u0441\u0442\u0438\u0442\u0443\u0442 \u043c\u043e\u0440\u044f \u0442\u0430 \u0430\u0442\u043c\u043e\u0441\u0444\u0435\u0440\u0438.", + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e API IPMA" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/de.json b/homeassistant/components/ipp/translations/de.json index 73dd3f69bcc10f..69402c8fdba3d9 100644 --- a/homeassistant/components/ipp/translations/de.json +++ b/homeassistant/components/ipp/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", "connection_upgrade": "Verbindung zum Drucker fehlgeschlagen, da ein Verbindungsupgrade erforderlich ist.", "ipp_error": "IPP-Fehler festgestellt.", "ipp_version_error": "IPP-Version wird vom Drucker nicht unterst\u00fctzt.", @@ -9,6 +10,7 @@ "unique_id_required": "Ger\u00e4t fehlt die f\u00fcr die Entdeckung erforderliche eindeutige Identifizierung." }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", "connection_upgrade": "Verbindung zum Drucker fehlgeschlagen. Bitte versuchen Sie es erneut mit aktivierter SSL / TLS-Option." }, "flow_title": "Drucker: {name}", @@ -18,14 +20,14 @@ "base_path": "Relativer Pfad zum Drucker", "host": "Host", "port": "Port", - "ssl": "Der Drucker unterst\u00fctzt die Kommunikation \u00fcber SSL / TLS", - "verify_ssl": "Der Drucker verwendet ein ordnungsgem\u00e4\u00dfes SSL-Zertifikat" + "ssl": "Verwendet ein SSL-Zertifikat", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, "description": "Richten Sie Ihren Drucker \u00fcber das Internet Printing Protocol (IPP) f\u00fcr die Integration in Home Assistant ein.", "title": "Verbinden Sie Ihren Drucker" }, "zeroconf_confirm": { - "description": "M\u00f6chten Sie den Drucker mit dem Namen \"{name}\" zu Home Assistant hinzuf\u00fcgen?", + "description": "M\u00f6chtest du {name} einrichten?", "title": "Entdeckter Drucker" } } diff --git a/homeassistant/components/ipp/translations/tr.json b/homeassistant/components/ipp/translations/tr.json index dbb14fe825ed01..78b9a868bd2f9f 100644 --- a/homeassistant/components/ipp/translations/tr.json +++ b/homeassistant/components/ipp/translations/tr.json @@ -1,7 +1,24 @@ { "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", + "connection_upgrade": "Yaz\u0131c\u0131ya ba\u011flan\u0131lamad\u0131. L\u00fctfen SSL / TLS se\u00e7ene\u011fi i\u015faretli olarak tekrar deneyin." + }, + "flow_title": "Yaz\u0131c\u0131: {name}", "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Yaz\u0131c\u0131n\u0131z\u0131 ba\u011flay\u0131n" + }, "zeroconf_confirm": { + "description": "{name} kurmak istiyor musunuz?", "title": "Ke\u015ffedilen yaz\u0131c\u0131" } } diff --git a/homeassistant/components/ipp/translations/uk.json b/homeassistant/components/ipp/translations/uk.json new file mode 100644 index 00000000000000..bb6df07f1e491a --- /dev/null +++ b/homeassistant/components/ipp/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_upgrade": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0456\u0441\u0442\u044c \u043f\u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f.", + "ipp_error": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430 IPP.", + "ipp_version_error": "\u0412\u0435\u0440\u0441\u0456\u044f IPP \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c.", + "parse_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0440\u043e\u0437\u0456\u0431\u0440\u0430\u0442\u0438 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u044c \u0432\u0456\u0434 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430.", + "unique_id_required": "\u041d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0432\u0456\u0434\u0441\u0443\u0442\u043d\u044f \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0430 \u0434\u043b\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_upgrade": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0447\u0435\u0440\u0435\u0437 SSL / TLS." + }, + "flow_title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440: {name}", + "step": { + "user": { + "data": { + "base_path": "\u0412\u0456\u0434\u043d\u043e\u0441\u043d\u0438\u0439 \u0448\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043f\u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 IPP.", + "title": "Internet Printing Protocol (IPP)" + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u043d\u0442\u0435\u0440 `{name}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u043d\u0442\u0435\u0440" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/tr.json b/homeassistant/components/iqvia/translations/tr.json new file mode 100644 index 00000000000000..717f6d72b94e5d --- /dev/null +++ b/homeassistant/components/iqvia/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/uk.json b/homeassistant/components/iqvia/translations/uk.json new file mode 100644 index 00000000000000..ab9813d6289d97 --- /dev/null +++ b/homeassistant/components/iqvia/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "invalid_zip_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441." + }, + "step": { + "user": { + "data": { + "zip_code": "\u041f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0432\u0456\u0439 \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e \u041a\u0430\u043d\u0430\u0434\u0438).", + "title": "IQVIA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/de.json b/homeassistant/components/islamic_prayer_times/translations/de.json index af38303c9a2f94..b06137bdb0e5f4 100644 --- a/homeassistant/components/islamic_prayer_times/translations/de.json +++ b/homeassistant/components/islamic_prayer_times/translations/de.json @@ -1,3 +1,8 @@ { + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + } + }, "title": "Islamische Gebetszeiten" } \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/tr.json b/homeassistant/components/islamic_prayer_times/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ 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 new file mode 100644 index 00000000000000..9290114899a4ee --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0440\u043e\u0437\u043a\u043b\u0430\u0434 \u0447\u0430\u0441\u0443 \u043d\u0430\u043c\u0430\u0437\u0443?", + "title": "\u0427\u0430\u0441 \u043d\u0430\u043c\u0430\u0437\u0443" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u0443" + } + } + } + }, + "title": "\u0427\u0430\u0441 \u043d\u0430\u043c\u0430\u0437\u0443" +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/de.json b/homeassistant/components/isy994/translations/de.json index 99d11e5d6c97b2..18d6a1603c4ac4 100644 --- a/homeassistant/components/isy994/translations/de.json +++ b/homeassistant/components/isy994/translations/de.json @@ -18,7 +18,7 @@ "username": "Benutzername" }, "description": "Der Hosteintrag muss im vollst\u00e4ndigen URL-Format vorliegen, z. B. http://192.168.10.100:80", - "title": "Stellen Sie eine Verbindung zu Ihrem ISY994 her" + "title": "Stelle eine Verbindung zu deinem ISY994 her" } } }, diff --git a/homeassistant/components/isy994/translations/tr.json b/homeassistant/components/isy994/translations/tr.json new file mode 100644 index 00000000000000..d1423202fe052b --- /dev/null +++ b/homeassistant/components/isy994/translations/tr.json @@ -0,0 +1,30 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "URL", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "variable_sensor_string": "De\u011fi\u015fken Sens\u00f6r Dizesi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/uk.json b/homeassistant/components/isy994/translations/uk.json new file mode 100644 index 00000000000000..c874b8654f58dc --- /dev/null +++ b/homeassistant/components/isy994/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_host": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.10.100:80').", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Universal Devices ISY994 {name} ({host})", + "step": { + "user": { + "data": { + "host": "URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "tls": "\u0412\u0435\u0440\u0441\u0456\u044f TLS \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.10.100:80').", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "options": { + "step": { + "init": { + "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", + "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.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f ISY994" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/de.json b/homeassistant/components/izone/translations/de.json index ea59cc39b27e05..f6e03c3af27c8c 100644 --- a/homeassistant/components/izone/translations/de.json +++ b/homeassistant/components/izone/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine iZone-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von iZone erforderlich." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/tr.json b/homeassistant/components/izone/translations/tr.json new file mode 100644 index 00000000000000..faa20ed0ece1c9 --- /dev/null +++ b/homeassistant/components/izone/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "\u0130Zone'u kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/uk.json b/homeassistant/components/izone/translations/uk.json new file mode 100644 index 00000000000000..8ab6c1e1664374 --- /dev/null +++ b/homeassistant/components/izone/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 iZone?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/de.json b/homeassistant/components/juicenet/translations/de.json index 16f48ef3837022..7a6b5cff541159 100644 --- a/homeassistant/components/juicenet/translations/de.json +++ b/homeassistant/components/juicenet/translations/de.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "Dieses JuiceNet-Konto ist bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { - "api_token": "JuiceNet API Token" + "api_token": "API-Token" }, - "description": "Sie ben\u00f6tigen das API-Token von https://home.juice.net/Manage.", - "title": "Stellen Sie eine Verbindung zu JuiceNet her" + "description": "Du ben\u00f6tigst das API-Token von https://home.juice.net/Manage.", + "title": "Stelle eine Verbindung zu JuiceNet her" } } } diff --git a/homeassistant/components/juicenet/translations/tr.json b/homeassistant/components/juicenet/translations/tr.json new file mode 100644 index 00000000000000..53890eb41e228e --- /dev/null +++ b/homeassistant/components/juicenet/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_token": "API Belirteci" + }, + "description": "API Belirtecine https://home.juice.net/Manage adresinden ihtiyac\u0131n\u0131z olacak.", + "title": "JuiceNet'e ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/uk.json b/homeassistant/components/juicenet/translations/uk.json new file mode 100644 index 00000000000000..903ea5f6e743dd --- /dev/null +++ b/homeassistant/components/juicenet/translations/uk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u0442\u043e\u043a\u0435\u043d API \u0437 \u0441\u0430\u0439\u0442\u0443 https://home.juice.net/Manage.", + "title": "JuiceNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/de.json b/homeassistant/components/kodi/translations/de.json index a0bf05cb5ecd76..1d229e5a428ef1 100644 --- a/homeassistant/components/kodi/translations/de.json +++ b/homeassistant/components/kodi/translations/de.json @@ -1,10 +1,14 @@ { "config": { "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Kodi: {name}", diff --git a/homeassistant/components/kodi/translations/tr.json b/homeassistant/components/kodi/translations/tr.json new file mode 100644 index 00000000000000..54ad8e0b6fdbff --- /dev/null +++ b/homeassistant/components/kodi/translations/tr.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "credentials": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "L\u00fctfen Kodi kullan\u0131c\u0131 ad\u0131n\u0131z\u0131 ve \u015fifrenizi girin. Bunlar Sistem / Ayarlar / A\u011f / Hizmetler'de bulunabilir." + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "ws_port": { + "data": { + "ws_port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/uk.json b/homeassistant/components/kodi/translations/uk.json new file mode 100644 index 00000000000000..d2acde5dffb767 --- /dev/null +++ b/homeassistant/components/kodi/translations/uk.json @@ -0,0 +1,50 @@ +{ + "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.", + "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.", + "no_uuid": "\u0423 \u0434\u0430\u043d\u043e\u0433\u043e \u0435\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 Kodi \u043d\u0435\u043c\u0430\u0454 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440\u0430. \u0406\u043c\u043e\u0432\u0456\u0440\u043d\u043e, \u0446\u0435 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u043e \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c \u0441\u0442\u0430\u0440\u043e\u0457 \u0432\u0435\u0440\u0441\u0456\u0457 Kodi (17.x \u0430\u0431\u043e \u043d\u0438\u0436\u0447\u0435). \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u0432\u0440\u0443\u0447\u043d\u0443 \u0430\u0431\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 \u043d\u043e\u0432\u0456\u0448\u0443 \u0432\u0435\u0440\u0441\u0456\u044e Kodi.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0456 \u043f\u0430\u0440\u043e\u043b\u044c Kodi. \u0407\u0445 \u043c\u043e\u0436\u043d\u0430 \u0437\u043d\u0430\u0439\u0442\u0438, \u043f\u0435\u0440\u0435\u0439\u0448\u043e\u0432\u0448\u0438 \u0432 \"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\" - \"\u0421\u043b\u0443\u0436\u0431\u0438\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f\"." + }, + "discovery_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Kodi (`{name}`)?", + "title": "Kodi" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL" + }, + "description": "\u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e \"\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u043f\u043e HTTP\" \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 \"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\" - \"\u0421\u043b\u0443\u0436\u0431\u0438\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f\"." + }, + "ws_port": { + "data": { + "ws_port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u043e WebSocket. \u0429\u043e\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0447\u0435\u0440\u0435\u0437 WebSocket, \u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u0430\u043c\u0438 \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 \"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\" - \"\u0421\u043b\u0443\u0436\u0431\u0438\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f\". \u042f\u043a\u0449\u043e WebSocket \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_off": "\u0437\u0430\u043f\u0438\u0442\u0430\u043d\u043e \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f {entity_name}", + "turn_on": "\u0437\u0430\u043f\u0438\u0442\u0430\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/de.json b/homeassistant/components/konnected/translations/de.json index ad2ed659522fcb..2ec1657990b227 100644 --- a/homeassistant/components/konnected/translations/de.json +++ b/homeassistant/components/konnected/translations/de.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsfluss f\u00fcr das Ger\u00e4t wird bereits ausgef\u00fchrt.", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "not_konn_panel": "Kein anerkanntes Konnected.io-Ger\u00e4t", - "unknown": "Unbekannter Fehler ist aufgetreten" + "unknown": "Unerwarteter Fehler" }, "error": { "cannot_connect": "Es konnte keine Verbindung zu einem Konnected-Panel unter {host}:{port} hergestellt werden." @@ -43,7 +43,7 @@ "name": "Name (optional)", "type": "Bin\u00e4rer Sensortyp" }, - "description": "Bitte w\u00e4hlen Sie die Optionen f\u00fcr den an {zone} angeschlossenen Bin\u00e4rsensor", + "description": "Bitte w\u00e4hle die Optionen f\u00fcr den an {zone} angeschlossenen Bin\u00e4rsensor", "title": "Konfigurieren Sie den Bin\u00e4rsensor" }, "options_digital": { @@ -52,7 +52,7 @@ "poll_interval": "Abfrageintervall (Minuten) (optional)", "type": "Sensortyp" }, - "description": "Bitte w\u00e4hlen Sie die Optionen f\u00fcr den an {zone} angeschlossenen digitalen Sensor aus", + "description": "Bitte w\u00e4hle die Optionen f\u00fcr den an {zone} angeschlossenen digitalen Sensor aus", "title": "Konfigurieren Sie den digitalen Sensor" }, "options_io": { @@ -98,9 +98,9 @@ "more_states": "Konfigurieren Sie zus\u00e4tzliche Zust\u00e4nde f\u00fcr diese Zone", "name": "Name (optional)", "pause": "Pause zwischen Impulsen (ms) (optional)", - "repeat": "Zeit zum Wiederholen (-1 = unendlich) (optional)" + "repeat": "Mal wiederholen (-1 = unendlich) (optional)" }, - "description": "Bitte w\u00e4hlen Sie die Ausgabeoptionen f\u00fcr {zone} : Status {state}", + "description": "Bitte w\u00e4hlen die Ausgabeoptionen f\u00fcr {zone} : Status {state}", "title": "Konfigurieren Sie den schaltbaren Ausgang" } } diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index ee6c10cbdd8f22..f6e9a2dbfbc566 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -45,7 +45,7 @@ "name": "Nazwa (opcjonalnie)", "type": "Typ sensora binarnego" }, - "description": "Wybierz opcje dla sensora binarnego powi\u0105zanego ze {zone}", + "description": "Opcje {zone}", "title": "Konfiguracja sensora binarnego" }, "options_digital": { @@ -54,7 +54,7 @@ "poll_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (minuty) (opcjonalnie)", "type": "Typ sensora" }, - "description": "Wybierz opcje dla cyfrowego sensora powi\u0105zanego ze {zone}", + "description": "Opcje {zone}", "title": "Konfiguracja sensora cyfrowego" }, "options_io": { diff --git a/homeassistant/components/konnected/translations/tr.json b/homeassistant/components/konnected/translations/tr.json new file mode 100644 index 00000000000000..a0e759903bdea8 --- /dev/null +++ b/homeassistant/components/konnected/translations/tr.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi", + "port": "Port" + } + } + } + }, + "options": { + "error": { + "bad_host": "Ge\u00e7ersiz, Ge\u00e7ersiz K\u0131lma API ana makine url'si" + }, + "step": { + "options_binary": { + "data": { + "inverse": "A\u00e7\u0131k / kapal\u0131 durumunu tersine \u00e7evirin" + } + }, + "options_io": { + "data": { + "3": "B\u00f6lge 3", + "4": "B\u00f6lge 4", + "5": "B\u00f6lge 5", + "6": "B\u00f6lge 6", + "7": "B\u00f6lge 7", + "out": "OUT" + } + }, + "options_io_ext": { + "data": { + "10": "B\u00f6lge 10", + "11": "B\u00f6lge 11", + "12": "B\u00f6lge 12", + "8": "B\u00f6lge 8", + "9": "B\u00f6lge 9", + "alarm1": "ALARM1", + "alarm2_out2": "OUT2/ALARM2", + "out1": "OUT1" + } + }, + "options_misc": { + "data": { + "api_host": "API ana makine URL'sini ge\u00e7ersiz k\u0131l (iste\u011fe ba\u011fl\u0131)", + "override_api_host": "Varsay\u0131lan Home Assistant API ana bilgisayar paneli URL'sini ge\u00e7ersiz k\u0131l" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/uk.json b/homeassistant/components/konnected/translations/uk.json new file mode 100644 index 00000000000000..92cd3744d945c2 --- /dev/null +++ b/homeassistant/components/konnected/translations/uk.json @@ -0,0 +1,108 @@ +{ + "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_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.", + "not_konn_panel": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Konnected.io \u043d\u0435 \u0440\u043e\u0437\u043f\u0456\u0437\u043d\u0430\u043d\u043e.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "confirm": { + "description": "\u041c\u043e\u0434\u0435\u043b\u044c: {model}\nID: {id}\n\u0425\u043e\u0441\u0442: {host}\n\u041f\u043e\u0440\u0442: {port} \n\n\u0417\u043c\u0456\u043d\u0430 \u043b\u043e\u0433\u0456\u043a\u0438 \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u0430\u043d\u0435\u043b\u0456, \u0430 \u0442\u0430\u043a\u043e\u0436 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044f \u0432\u0445\u043e\u0434\u0456\u0432 \u0456 \u0432\u0438\u0445\u043e\u0434\u0456\u0432 \u0432\u0438\u043a\u043e\u043d\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u043f\u0430\u043d\u0435\u043b\u0456 \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Konnected.", + "title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Konnected \u0433\u043e\u0442\u043e\u0432\u0456\u0439 \u0434\u043e \u0440\u043e\u0431\u043e\u0442\u0438." + }, + "import_confirm": { + "description": "\u041f\u0430\u043d\u0435\u043b\u044c \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Konnected ID {id} \u0440\u0430\u043d\u0456\u0448\u0435 \u0432\u0436\u0435 \u0431\u0443\u043b\u0430 \u0434\u043e\u0434\u0430\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 configuration.yaml. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u0437\u0430\u043f\u0438\u0441 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0434\u0430\u043d\u043e\u0433\u043e \u043f\u043e\u0441\u0456\u0431\u043d\u0438\u043a\u0430 \u0437 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f.", + "title": "\u0406\u043c\u043f\u043e\u0440\u0442 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Konnected" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0430\u043d\u0435\u043b\u0456 Konnected." + } + } + }, + "options": { + "abort": { + "not_konn_panel": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Konnected.io \u043d\u0435 \u0440\u043e\u0437\u043f\u0456\u0437\u043d\u0430\u043d\u043e." + }, + "error": { + "bad_host": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 URL \u043f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0445\u043e\u0441\u0442\u0430 API." + }, + "step": { + "options_binary": { + "data": { + "inverse": "\u0406\u043d\u0432\u0435\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438\u0439/\u0437\u0430\u043a\u0440\u0438\u0442\u0438\u0439 \u0441\u0442\u0430\u043d", + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "type": "\u0422\u0438\u043f \u0431\u0456\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "description": "\u041e\u043f\u0446\u0456\u0457 {zone}", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0431\u0456\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "options_digital": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "poll_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "type": "\u0422\u0438\u043f \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "description": "\u041e\u043f\u0446\u0456\u0457 {zone}", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0430" + }, + "options_io": { + "data": { + "1": "\u0417\u043e\u043d\u0430 1", + "2": "\u0417\u043e\u043d\u0430 2", + "3": "\u0417\u043e\u043d\u0430 3", + "4": "\u0417\u043e\u043d\u0430 4", + "5": "\u0417\u043e\u043d\u0430 5", + "6": "\u0417\u043e\u043d\u0430 6", + "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.", + "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": { + "data": { + "10": "\u0417\u043e\u043d\u0430 10", + "11": "\u0417\u043e\u043d\u0430 11", + "12": "\u0417\u043e\u043d\u0430 12", + "8": "\u0417\u043e\u043d\u0430 8", + "9": "\u0417\u043e\u043d\u0430 9", + "alarm1": "\u0422\u0420\u0418\u0412\u041e\u0413\u04101", + "alarm2_out2": "\u0412\u0418\u0425\u0406\u04142 / \u0422\u0420\u0418\u0412\u041e\u0413\u04102", + "out1": "\u0412\u0418\u0425\u0406\u04141" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0440\u0435\u0448\u0442\u0438 \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432. \u0411\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0431\u0443\u0434\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \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 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0438\u0445 \u0432\u0445\u043e\u0434\u0456\u0432 / \u0432\u0438\u0445\u043e\u0434\u0456\u0432" + }, + "options_misc": { + "data": { + "api_host": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 URL \u0445\u043e\u0441\u0442\u0430 API (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "blink": "LED-\u0456\u043d\u0434\u0438\u043a\u0430\u0446\u0456\u044f \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0456 \u043f\u0440\u0438 \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u0446\u0456 \u0441\u0442\u0430\u043d\u0443", + "discovery": "\u0412\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0442\u0438 \u043d\u0430 \u0437\u0430\u043f\u0438\u0442\u0438 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u0443 \u0412\u0430\u0448\u0456\u0439 \u043c\u0435\u0440\u0435\u0436\u0456", + "override_api_host": "\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442-\u043f\u0430\u043d\u0435\u043b\u0456 Home Assistant API" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0431\u0430\u0436\u0430\u043d\u0443 \u043f\u043e\u0432\u0435\u0434\u0456\u043d\u043a\u0443 \u0434\u043b\u044f \u0412\u0430\u0448\u043e\u0457 \u043f\u0430\u043d\u0435\u043b\u0456.", + "title": "\u0406\u043d\u0448\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + }, + "options_switch": { + "data": { + "activation": "\u0412\u0438\u0445\u0456\u0434 \u043f\u0440\u0438 \u0432\u043c\u0438\u043a\u0430\u043d\u043d\u0456", + "momentary": "\u0422\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044c \u0456\u043c\u043f\u0443\u043b\u044c\u0441\u0443 (\u043c\u0441) (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "more_states": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0441\u0442\u0430\u043d\u0438 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0437\u043e\u043d\u0438", + "name": "\u041d\u0430\u0437\u0432\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "pause": "\u041f\u0430\u0443\u0437\u0430 \u043c\u0456\u0436 \u0456\u043c\u043f\u0443\u043b\u044c\u0441\u0430\u043c\u0438 (\u043c\u0441) (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "repeat": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u044c (-1 = \u043d\u0435\u0441\u043a\u0456\u043d\u0447\u0435\u043d\u043d\u043e) (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "description": "{zone}: \u0441\u0442\u0430\u043d {state}", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u044e\u0447\u043e\u0433\u043e \u0432\u0438\u0445\u043e\u0434\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/de.json b/homeassistant/components/kulersky/translations/de.json index 3fc69f85947a70..96ed09a974f40d 100644 --- a/homeassistant/components/kulersky/translations/de.json +++ b/homeassistant/components/kulersky/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Wollen Sie mit der Einrichtung beginnen?" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/kulersky/translations/lb.json b/homeassistant/components/kulersky/translations/lb.json new file mode 100644 index 00000000000000..4ea09574c0ba8f --- /dev/null +++ b/homeassistant/components/kulersky/translations/lb.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Keng Apparater am Netzwierk fonnt", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "step": { + "confirm": { + "description": "Soll den Ariichtungs Prozess gestart ginn?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/tr.json b/homeassistant/components/kulersky/translations/tr.json index 49fa9545e94d2c..3df15466f030f1 100644 --- a/homeassistant/components/kulersky/translations/tr.json +++ b/homeassistant/components/kulersky/translations/tr.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + "no_devices_found": "A\u011fda cihaz bulunamad\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/uk.json b/homeassistant/components/kulersky/translations/uk.json new file mode 100644 index 00000000000000..292861e9129dbd --- /dev/null +++ b/homeassistant/components/kulersky/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/de.json b/homeassistant/components/life360/translations/de.json index 731ebdceef7598..7e495987b45540 100644 --- a/homeassistant/components/life360/translations/de.json +++ b/homeassistant/components/life360/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "create_entry": { @@ -8,6 +9,7 @@ }, "error": { "already_configured": "Konto ist bereits konfiguriert", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_username": "Ung\u00fcltiger Benutzername", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/life360/translations/fr.json b/homeassistant/components/life360/translations/fr.json index 72f56ed87844b6..cb86d8c65907cd 100644 --- a/homeassistant/components/life360/translations/fr.json +++ b/homeassistant/components/life360/translations/fr.json @@ -8,7 +8,7 @@ "default": "Pour d\u00e9finir les options avanc\u00e9es, voir [Documentation de Life360]( {docs_url} )." }, "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "invalid_auth": "Authentification invalide", "invalid_username": "Nom d'utilisateur invalide", "unknown": "Erreur inattendue" diff --git a/homeassistant/components/life360/translations/tr.json b/homeassistant/components/life360/translations/tr.json index 3f923c096cd056..e1e57b39737210 100644 --- a/homeassistant/components/life360/translations/tr.json +++ b/homeassistant/components/life360/translations/tr.json @@ -1,11 +1,22 @@ { "config": { "abort": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "unknown": "Beklenmedik hata" }, "error": { "already_configured": "Hesap zaten konfig\u00fcre edilmi\u015fi durumda", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "invalid_username": "Ge\u00e7ersiz kullan\u0131c\u0131 ad\u0131", "unknown": "Beklenmedik hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/life360/translations/uk.json b/homeassistant/components/life360/translations/uk.json new file mode 100644 index 00000000000000..caecf494388971 --- /dev/null +++ b/homeassistant/components/life360/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "create_entry": { + "default": "\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 \u0440\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0438\u0445 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c." + }, + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "invalid_username": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0456\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\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 \u0440\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0438\u0445 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0440\u043e\u0431\u0438\u0442\u0438 \u0446\u0435 \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", + "title": "Life360" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/de.json b/homeassistant/components/lifx/translations/de.json index f88e27ff168c0a..83eded1ddc69e7 100644 --- a/homeassistant/components/lifx/translations/de.json +++ b/homeassistant/components/lifx/translations/de.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Keine LIFX Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von LIFX ist zul\u00e4ssig." + "no_devices_found": "Keine LIFX Ger\u00e4te im Netzwerk gefunden", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/tr.json b/homeassistant/components/lifx/translations/tr.json new file mode 100644 index 00000000000000..fc7532a1e3411d --- /dev/null +++ b/homeassistant/components/lifx/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "LIFX'i kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/uk.json b/homeassistant/components/lifx/translations/uk.json new file mode 100644 index 00000000000000..8c32e79533dc00 --- /dev/null +++ b/homeassistant/components/lifx/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 LIFX?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/light/translations/uk.json b/homeassistant/components/light/translations/uk.json index 67685889c5489d..86eee7d6b23e93 100644 --- a/homeassistant/components/light/translations/uk.json +++ b/homeassistant/components/light/translations/uk.json @@ -1,5 +1,17 @@ { "device_automation": { + "action_type": { + "brightness_decrease": "{entity_name}: \u0437\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "brightness_increase": "{entity_name}: \u0437\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "flash": "{entity_name}: \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043c\u0438\u0433\u0430\u043d\u043d\u044f", + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" diff --git a/homeassistant/components/local_ip/translations/de.json b/homeassistant/components/local_ip/translations/de.json index 072f6ec964d12d..9e2a6eda5c6a27 100644 --- a/homeassistant/components/local_ip/translations/de.json +++ b/homeassistant/components/local_ip/translations/de.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "Es ist nur eine einzige Konfiguration der lokalen IP zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "user": { "data": { "name": "Sensorname" }, + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Lokale IP-Adresse" } } diff --git a/homeassistant/components/local_ip/translations/es.json b/homeassistant/components/local_ip/translations/es.json index fe9a0ad14147ec..a3048d396d567a 100644 --- a/homeassistant/components/local_ip/translations/es.json +++ b/homeassistant/components/local_ip/translations/es.json @@ -8,7 +8,7 @@ "data": { "name": "Nombre del sensor" }, - "description": "\u00bfQuieres empezar a configurar?", + "description": "\u00bfQuieres iniciar la configuraci\u00f3n?", "title": "Direcci\u00f3n IP local" } } diff --git a/homeassistant/components/local_ip/translations/tr.json b/homeassistant/components/local_ip/translations/tr.json new file mode 100644 index 00000000000000..e8e82814f8af2d --- /dev/null +++ b/homeassistant/components/local_ip/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "user": { + "data": { + "name": "Sens\u00f6r Ad\u0131" + }, + "description": "Kuruluma ba\u015flamak ister misiniz?", + "title": "Yerel IP Adresi" + } + } + }, + "title": "Yerel IP Adresi" +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/uk.json b/homeassistant/components/local_ip/translations/uk.json new file mode 100644 index 00000000000000..b88c1c002bf593 --- /dev/null +++ b/homeassistant/components/local_ip/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430 IP-\u0430\u0434\u0440\u0435\u0441\u0430" + } + } + }, + "title": "\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430 IP-\u0430\u0434\u0440\u0435\u0441\u0430" +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/de.json b/homeassistant/components/locative/translations/de.json index 326170941466e5..a6dcf4150d0ff7 100644 --- a/homeassistant/components/locative/translations/de.json +++ b/homeassistant/components/locative/translations/de.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Standorte Home Assistant zu senden, muss das Webhook Feature in der Locative App konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, "step": { "user": { - "description": "M\u00f6chtest du den Locative Webhook wirklich einrichten?", + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Locative Webhook einrichten" } } diff --git a/homeassistant/components/locative/translations/tr.json b/homeassistant/components/locative/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/locative/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/uk.json b/homeassistant/components/locative/translations/uk.json new file mode 100644 index 00000000000000..d9a4713087117f --- /dev/null +++ b/homeassistant/components/locative/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Locative. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "Locative" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lock/translations/pt.json b/homeassistant/components/lock/translations/pt.json index 5ba9f10db141d8..44f30900572344 100644 --- a/homeassistant/components/lock/translations/pt.json +++ b/homeassistant/components/lock/translations/pt.json @@ -5,6 +5,9 @@ "open": "Abrir {entity_name}", "unlock": "Desbloquear {entity_name}" }, + "condition_type": { + "is_unlocked": "{entity_name} est\u00e1 destrancado" + }, "trigger_type": { "locked": "{entity_name} fechada", "unlocked": "{entity_name} aberta" diff --git a/homeassistant/components/lock/translations/tr.json b/homeassistant/components/lock/translations/tr.json index 95b50398fdaad2..ea6ff1a157da23 100644 --- a/homeassistant/components/lock/translations/tr.json +++ b/homeassistant/components/lock/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "locked": "{entity_name} kilitlendi", + "unlocked": "{entity_name} kilidi a\u00e7\u0131ld\u0131" + } + }, "state": { "_": { "locked": "Kilitli", diff --git a/homeassistant/components/lock/translations/uk.json b/homeassistant/components/lock/translations/uk.json index d919252eb56204..96b92012e9d30f 100644 --- a/homeassistant/components/lock/translations/uk.json +++ b/homeassistant/components/lock/translations/uk.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "lock": "{entity_name}: \u0437\u0430\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438", + "open": "{entity_name}: \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438", + "unlock": "{entity_name}: \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0432\u0430\u0442\u0438" + }, + "condition_type": { + "is_locked": "{entity_name} \u0432 \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_unlocked": "{entity_name} \u0432 \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, + "trigger_type": { + "locked": "{entity_name} \u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f", + "unlocked": "{entity_name} \u0440\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0454\u0442\u044c\u0441\u044f" + } + }, "state": { "_": { "locked": "\u0417\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e", diff --git a/homeassistant/components/logi_circle/translations/de.json b/homeassistant/components/logi_circle/translations/de.json index ab4a194fda00af..1eec1d3c4a5218 100644 --- a/homeassistant/components/logi_circle/translations/de.json +++ b/homeassistant/components/logi_circle/translations/de.json @@ -1,11 +1,15 @@ { "config": { "abort": { + "already_configured": "Konto wurde bereits konfiguriert", "external_error": "Es ist eine Ausnahme in einem anderen Flow aufgetreten.", - "external_setup": "Logi Circle wurde erfolgreich aus einem anderen Flow konfiguriert." + "external_setup": "Logi Circle wurde erfolgreich aus einem anderen Flow konfiguriert.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." }, "error": { - "follow_link": "Bitte folge dem Link und authentifiziere dich, bevor du auf Senden klickst." + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "follow_link": "Bitte folge dem Link und authentifiziere dich, bevor du auf Senden klickst.", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "auth": { diff --git a/homeassistant/components/logi_circle/translations/fr.json b/homeassistant/components/logi_circle/translations/fr.json index 7ac388ccb3f371..6bd22f473e7cca 100644 --- a/homeassistant/components/logi_circle/translations/fr.json +++ b/homeassistant/components/logi_circle/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "external_error": "Une exception est survenue \u00e0 partir d'un autre flux.", "external_setup": "Logi Circle a \u00e9t\u00e9 configur\u00e9 avec succ\u00e8s \u00e0 partir d'un autre flux.", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." diff --git a/homeassistant/components/logi_circle/translations/lb.json b/homeassistant/components/logi_circle/translations/lb.json index fab157b2655920..82be2f6a82d7d9 100644 --- a/homeassistant/components/logi_circle/translations/lb.json +++ b/homeassistant/components/logi_circle/translations/lb.json @@ -7,6 +7,7 @@ "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun." }, "error": { + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", "follow_link": "Follegt w.e.g dem Link an authentifiz\u00e9iert iech ier de op Ofsch\u00e9cken dr\u00e9ckt.", "invalid_auth": "Ong\u00eblteg Authentifikatioun" }, diff --git a/homeassistant/components/logi_circle/translations/tr.json b/homeassistant/components/logi_circle/translations/tr.json new file mode 100644 index 00000000000000..0b0f58116c234f --- /dev/null +++ b/homeassistant/components/logi_circle/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/logi_circle/translations/uk.json b/homeassistant/components/logi_circle/translations/uk.json new file mode 100644 index 00000000000000..2c021992413d79 --- /dev/null +++ b/homeassistant/components/logi_circle/translations/uk.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "external_error": "\u0412\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0432\u0456\u0434\u0431\u0443\u043b\u043e\u0441\u044f \u0437 \u0456\u043d\u0448\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0443.", + "external_setup": "Logi Circle \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.", + "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." + }, + "error": { + "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.", + "follow_link": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u0456 \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\".", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "auth": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043f\u043e [\u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c]({authorization_url}) \u0456 ** \u0414\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 ** \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0432\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Logi Circle, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0456 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **.", + "title": "Logi Circle" + }, + "user": { + "data": { + "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457, \u0447\u0435\u0440\u0435\u0437 \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0438\u0439 \u0432\u0445\u0456\u0434.", + "title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/de.json b/homeassistant/components/lovelace/translations/de.json index c8680fcb7e5fbc..b6c7562f0ec2e3 100644 --- a/homeassistant/components/lovelace/translations/de.json +++ b/homeassistant/components/lovelace/translations/de.json @@ -1,6 +1,9 @@ { "system_health": { "info": { + "dashboards": "Dashboards", + "mode": "Modus", + "resources": "Ressourcen", "views": "Ansichten" } } diff --git a/homeassistant/components/lovelace/translations/fr.json b/homeassistant/components/lovelace/translations/fr.json new file mode 100644 index 00000000000000..f2847bcc177204 --- /dev/null +++ b/homeassistant/components/lovelace/translations/fr.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "Tableaux de bord", + "mode": "Mode", + "resources": "Ressources", + "views": "Vues" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/tr.json b/homeassistant/components/lovelace/translations/tr.json index 9f763d0d6cc351..d159e058ffa4e5 100644 --- a/homeassistant/components/lovelace/translations/tr.json +++ b/homeassistant/components/lovelace/translations/tr.json @@ -3,7 +3,8 @@ "info": { "dashboards": "Kontrol panelleri", "mode": "Mod", - "resources": "Kaynaklar" + "resources": "Kaynaklar", + "views": "G\u00f6r\u00fcn\u00fcmler" } } } \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/uk.json b/homeassistant/components/lovelace/translations/uk.json new file mode 100644 index 00000000000000..21d97fd14c38bb --- /dev/null +++ b/homeassistant/components/lovelace/translations/uk.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "\u041f\u0430\u043d\u0435\u043b\u0456", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438", + "views": "\u0412\u043a\u043b\u0430\u0434\u043a\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/de.json b/homeassistant/components/luftdaten/translations/de.json index 122dc611870de5..499a65623b004b 100644 --- a/homeassistant/components/luftdaten/translations/de.json +++ b/homeassistant/components/luftdaten/translations/de.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "Der Dienst ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_sensor": "Sensor nicht verf\u00fcgbar oder ung\u00fcltig" }, diff --git a/homeassistant/components/luftdaten/translations/tr.json b/homeassistant/components/luftdaten/translations/tr.json new file mode 100644 index 00000000000000..04565de3d28b2d --- /dev/null +++ b/homeassistant/components/luftdaten/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/uk.json b/homeassistant/components/luftdaten/translations/uk.json new file mode 100644 index 00000000000000..9fd33dc3da2697 --- /dev/null +++ b/homeassistant/components/luftdaten/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \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", + "invalid_sensor": "\u0421\u0435\u043d\u0441\u043e\u0440 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0430\u0431\u043e \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439." + }, + "step": { + "user": { + "data": { + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456", + "station_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0435\u043d\u0441\u043e\u0440\u0430 Luftdaten" + }, + "title": "Luftdaten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ca.json b/homeassistant/components/lutron_caseta/translations/ca.json index c3b0e686cc4616..5f2cc5d40872ea 100644 --- a/homeassistant/components/lutron_caseta/translations/ca.json +++ b/homeassistant/components/lutron_caseta/translations/ca.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "cannot_connect": "Ha fallat la connexi\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3", + "not_lutron_device": "El dispositiu descobert no \u00e9s un dispositiu Lutron" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "No s'ha pogut configurar l'enlla\u00e7 (amfitri\u00f3: {host}) importat de configuration.yaml.", "title": "No s'ha pogut importar la configuraci\u00f3 de l'enlla\u00e7 de Cas\u00e9ta." + }, + "link": { + "description": "Per a vincular amb {name} ({host}), despr\u00e9s d'enviar aquest formulari, prem el bot\u00f3 negre de la part posterior de l'enlla\u00e7.", + "title": "Vinculaci\u00f3 amb enlla\u00e7" + }, + "user": { + "data": { + "host": "Amfitri\u00f3" + }, + "description": "Introdueix l'adre\u00e7a IP del dispositiu.", + "title": "Connexi\u00f3 autom\u00e0tica amb l'enlla\u00e7" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primer bot\u00f3", + "button_2": "Segon bot\u00f3", + "button_3": "Tercer bot\u00f3", + "button_4": "Quart bot\u00f3", + "close_1": "Tanca 1", + "close_2": "Tanca 2", + "close_3": "Tanca 3", + "close_4": "Tanca 4", + "close_all": "Tanca-ho tot", + "group_1_button_1": "Primer bot\u00f3 del primer grup", + "group_1_button_2": "Segon bot\u00f3 del primer grup", + "group_2_button_1": "Primer bot\u00f3 del segon grup", + "group_2_button_2": "Segon bot\u00f3 del segon grup", + "lower": "Baixa", + "lower_1": "Baixa 1", + "lower_2": "Baixa 2", + "lower_3": "Baixa 3", + "lower_4": "Baixa 4", + "lower_all": "Baixa-ho tot", + "off": "OFF", + "on": "ON", + "open_1": "Obre 1", + "open_2": "Obre 2", + "open_3": "Obre 3", + "open_4": "Obre 4", + "open_all": "Obre-ho tot", + "raise": "Puja", + "raise_1": "Puja 1", + "raise_2": "Puja 2", + "raise_3": "Puja 3", + "raise_4": "Puja 4", + "raise_all": "Puja-ho tot", + "stop": "Atura (preferit)", + "stop_1": "Atura 1", + "stop_2": "Atura 2", + "stop_3": "Atura 3", + "stop_4": "Atura 4", + "stop_all": "Atura-ho tot" + }, + "trigger_type": { + "press": "\"{subtype}\" premut", + "release": "\"{subtype}\" alliberat" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/cs.json b/homeassistant/components/lutron_caseta/translations/cs.json index 60fa7fddced2c5..4ccfa17e6d3a03 100644 --- a/homeassistant/components/lutron_caseta/translations/cs.json +++ b/homeassistant/components/lutron_caseta/translations/cs.json @@ -6,6 +6,13 @@ }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "step": { + "user": { + "data": { + "host": "Hostitel" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/es.json b/homeassistant/components/lutron_caseta/translations/es.json index cfd8551bab92a9..37b1a0d90728e6 100644 --- a/homeassistant/components/lutron_caseta/translations/es.json +++ b/homeassistant/components/lutron_caseta/translations/es.json @@ -2,16 +2,50 @@ "config": { "abort": { "already_configured": "El dispositivo ya est\u00e1 configurado", - "cannot_connect": "No se pudo conectar" + "cannot_connect": "No se pudo conectar", + "not_lutron_device": "El dispositivo descubierto no es un dispositivo de Lutron" }, "error": { "cannot_connect": "No se pudo conectar" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "No se puede configurar bridge (host: {host}) importado desde configuration.yaml.", "title": "Error al importar la configuraci\u00f3n del bridge Cas\u00e9ta." + }, + "link": { + "description": "Para emparejar con {name} ({host}), despu\u00e9s de enviar este formulario, presione el bot\u00f3n negro en la parte posterior del puente.", + "title": "Emparejar con el puente" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Introduzca la direcci\u00f3n ip del dispositivo.", + "title": "Conectar autom\u00e1ticamente con el dispositivo" } } + }, + "device_automation": { + "trigger_subtype": { + "open_1": "Abrir 1", + "open_2": "Abrir 2", + "open_3": "Abrir 3", + "open_4": "Abrir 4", + "open_all": "Abrir todo", + "raise": "Levantar", + "raise_1": "Levantar 1", + "raise_2": "Levantar 2", + "raise_3": "Levantar 3", + "raise_4": "Levantar 4", + "raise_all": "Levantar todo", + "stop": "Detener (favorito)", + "stop_1": "Detener 1", + "stop_2": "Detener 2", + "stop_3": "Detener 3", + "stop_4": "Detener 4", + "stop_all": "Detener todo" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/et.json b/homeassistant/components/lutron_caseta/translations/et.json index ed352c7bcc4b58..81fee6d5b4afcc 100644 --- a/homeassistant/components/lutron_caseta/translations/et.json +++ b/homeassistant/components/lutron_caseta/translations/et.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", - "cannot_connect": "\u00dchendamine nurjus" + "cannot_connect": "\u00dchendamine nurjus", + "not_lutron_device": "Avastatud seade ei ole Lutroni seade" }, "error": { "cannot_connect": "\u00dchendamine nurjus" }, + "flow_title": "Lutron Cas\u00e9ta {name} ( {host} )", "step": { "import_failed": { "description": "Silla (host: {host} ) seadistamine configuration.yaml kirje teabest nurjus.", "title": "Cas\u00e9ta Bridge seadete importimine nurjus." + }, + "link": { + "description": "{name} ({host}) sidumiseks vajuta p\u00e4rast selle vormi esitamist silla tagak\u00fcljel olevat musta nuppu.", + "title": "Sillaga sidumine" + }, + "user": { + "data": { + "host": "" + }, + "description": "Sisesta seadme IP-aadress.", + "title": "\u00dchendu sillaga automaatselt" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Esimene nupp", + "button_2": "Teine nupp", + "button_3": "Kolmas nupp", + "button_4": "Neljas nupp", + "close_1": "Sule #1", + "close_2": "Sule #2", + "close_3": "Sule #3", + "close_4": "Sule #4", + "close_all": "Sulge k\u00f5ik", + "group_1_button_1": "Esimese r\u00fchma esimene nupp", + "group_1_button_2": "Esimene r\u00fchma teine nupp", + "group_2_button_1": "Teise r\u00fchma esimene nupp", + "group_2_button_2": "Teise r\u00fchma teine nupp", + "lower": "Langeta", + "lower_1": "Langeta #1", + "lower_2": "Langeta #2", + "lower_3": "Langeta #3", + "lower_4": "Langeta #4", + "lower_all": "Langeta k\u00f5ik", + "off": "V\u00e4ljas", + "on": "Sees", + "open_1": "Ava #1", + "open_2": "Ava #2", + "open_3": "Ava #3", + "open_4": "Ava #4", + "open_all": "Ava k\u00f5ik", + "raise": "T\u00f5sta", + "raise_1": "T\u00f5sta #1", + "raise_2": "T\u00f5sta #2", + "raise_3": "T\u00f5sta #3", + "raise_4": "T\u00f5sta #4", + "raise_all": "T\u00f5sta k\u00f5ik", + "stop": "Peata lemmikasendis", + "stop_1": "Peata #1", + "stop_2": "Peata #2", + "stop_3": "Peata #3", + "stop_4": "Peata #4", + "stop_all": "Peata k\u00f5ik" + }, + "trigger_type": { + "press": "vajutati \" {subtype} \"", + "release": "\" {subtype} \" vabastati" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/it.json b/homeassistant/components/lutron_caseta/translations/it.json index 5bdcf87607dccc..d1b3b754812aa3 100644 --- a/homeassistant/components/lutron_caseta/translations/it.json +++ b/homeassistant/components/lutron_caseta/translations/it.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "cannot_connect": "Impossibile connettersi" + "cannot_connect": "Impossibile connettersi", + "not_lutron_device": "Il dispositivo rilevato non \u00e8 un dispositivo Lutron" }, "error": { "cannot_connect": "Impossibile connettersi" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Impossibile impostare il bridge (host: {host}) importato da configuration.yaml.", "title": "Impossibile importare la configurazione del bridge Cas\u00e9ta." + }, + "link": { + "description": "Per eseguire l'associazione con {name} ({host}), dopo aver inviato questo modulo, premere il pulsante nero sul retro del bridge.", + "title": "Associa con il bridge" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Immettere l'indirizzo IP del dispositivo.", + "title": "Connetti automaticamente al bridge" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primo pulsante", + "button_2": "Secondo pulsante", + "button_3": "Terzo pulsante", + "button_4": "Quarto pulsante", + "close_1": "Chiudi 1", + "close_2": "Chiudi 2", + "close_3": "Chiudi 3", + "close_4": "Chiudi 4", + "close_all": "Chiudi tutti", + "group_1_button_1": "Primo Gruppo primo pulsante", + "group_1_button_2": "Primo Gruppo secondo pulsante", + "group_2_button_1": "Secondo Gruppo primo pulsante", + "group_2_button_2": "Secondo Gruppo secondo pulsante", + "lower": "Abbassa", + "lower_1": "Abbassa 1", + "lower_2": "Abbassa 2", + "lower_3": "Abbassa 3", + "lower_4": "Abbassa 4", + "lower_all": "Abbassa tutti", + "off": "Spento", + "on": "Acceso", + "open_1": "Apri 1", + "open_2": "Apri 2", + "open_3": "Apri 3", + "open_4": "Apri 4", + "open_all": "Apri tutti", + "raise": "Alza", + "raise_1": "Alza 1", + "raise_2": "Alza 2", + "raise_3": "Alza 3", + "raise_4": "Alza 4", + "raise_all": "Alza tutti", + "stop": "Ferma (preferito)", + "stop_1": "Ferma 1", + "stop_2": "Ferma 2", + "stop_3": "Ferma 3", + "stop_4": "Ferma 4", + "stop_all": "Fermare tutti" + }, + "trigger_type": { + "press": "\"{subtype}\" premuto", + "release": "\"{subtype}\" rilasciato" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/no.json b/homeassistant/components/lutron_caseta/translations/no.json index 7afac9c51a5303..477370100af138 100644 --- a/homeassistant/components/lutron_caseta/translations/no.json +++ b/homeassistant/components/lutron_caseta/translations/no.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "cannot_connect": "Tilkobling mislyktes" + "cannot_connect": "Tilkobling mislyktes", + "not_lutron_device": "Oppdaget enhet er ikke en Lutron-enhet" }, "error": { "cannot_connect": "Tilkobling mislyktes" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Kunne ikke konfigurere bridge (host: {host} ) importert fra configuration.yaml.", "title": "Kan ikke importere Cas\u00e9ta bridge-konfigurasjon." + }, + "link": { + "description": "Hvis du vil pare med {name} ({host}), trykker du den svarte knappen p\u00e5 baksiden av broen etter at du har sendt dette skjemaet.", + "title": "Par med broen" + }, + "user": { + "data": { + "host": "Vert" + }, + "description": "Skriv inn ip-adressen til enheten.", + "title": "Koble automatisk til broen" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "F\u00f8rste knapp", + "button_2": "Andre knapp", + "button_3": "Tredje knapp", + "button_4": "Fjerde knapp", + "close_1": "Lukk 1", + "close_2": "Lukk 2", + "close_3": "Lukk 3", + "close_4": "Lukk 4", + "close_all": "Lukk alle", + "group_1_button_1": "F\u00f8rste gruppe f\u00f8rste knapp", + "group_1_button_2": "F\u00f8rste gruppe andre knapp", + "group_2_button_1": "Andre gruppe f\u00f8rste knapp", + "group_2_button_2": "Andre gruppeknapp", + "lower": "Senk", + "lower_1": "Senk 1", + "lower_2": "Senk 2", + "lower_3": "Senk 3", + "lower_4": "Senk 4", + "lower_all": "Senk alle", + "off": "Av", + "on": "P\u00e5", + "open_1": "\u00c5pne 1", + "open_2": "\u00c5pne 2", + "open_3": "\u00c5pne 3", + "open_4": "\u00c5pne 4", + "open_all": "\u00c5pne alle", + "raise": "Hev", + "raise_1": "Hev 1", + "raise_2": "Hev 2", + "raise_3": "Hev 3", + "raise_4": "Hev 4", + "raise_all": "Hev alle", + "stop": "Stopp (favoritt)", + "stop_1": "Stopp 1", + "stop_2": "Stopp 2", + "stop_3": "Stopp 3", + "stop_4": "Stopp 4", + "stop_all": "Stopp alle" + }, + "trigger_type": { + "press": "\"{subtype}\" trykket", + "release": "\"{subtype}\" utgitt" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/pl.json b/homeassistant/components/lutron_caseta/translations/pl.json index 07417b0149e46c..8a8c0a759b01e9 100644 --- a/homeassistant/components/lutron_caseta/translations/pl.json +++ b/homeassistant/components/lutron_caseta/translations/pl.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "not_lutron_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem Lutron" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Nie mo\u017cna skonfigurowa\u0107 mostka (host: {host}) zaimportowanego z pliku configuration.yaml.", "title": "Nie uda\u0142o si\u0119 zaimportowa\u0107 konfiguracji mostka Cas\u00e9ta." + }, + "link": { + "description": "Aby sparowa\u0107 z {name} ({host}), po przes\u0142aniu tego formularza naci\u015bnij czarny przycisk z ty\u0142u mostka.", + "title": "Sparuj z mostkiem" + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wprowad\u017a adres IP urz\u0105dzenia", + "title": "Po\u0142\u0105cz si\u0119 automatycznie z mostkiem" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "pierwszy", + "button_2": "drugi", + "button_3": "trzeci", + "button_4": "czwarty", + "close_1": "zamknij 1", + "close_2": "zamknij 2", + "close_3": "zamknij 3", + "close_4": "zamknij 4", + "close_all": "zamknij wszystkie", + "group_1_button_1": "pierwsza grupa pierwszy przycisk", + "group_1_button_2": "pierwsza grupa drugi przycisk", + "group_2_button_1": "druga grupa pierwszy przycisk", + "group_2_button_2": "druga grupa drugi przycisk", + "lower": "opu\u015b\u0107", + "lower_1": "opu\u015b\u0107 1", + "lower_2": "opu\u015b\u0107 2", + "lower_3": "opu\u015b\u0107 3", + "lower_4": "opu\u015b\u0107 4", + "lower_all": "opu\u015b\u0107 wszystkie", + "off": "wy\u0142\u0105cz", + "on": "w\u0142\u0105cz", + "open_1": "otw\u00f3rz 1", + "open_2": "otw\u00f3rz 2", + "open_3": "otw\u00f3rz 3", + "open_4": "otw\u00f3rz 4", + "open_all": "otw\u00f3rz wszystkie", + "raise": "podnie\u015b", + "raise_1": "podnie\u015b 1", + "raise_2": "podnie\u015b 2", + "raise_3": "podnie\u015b 3", + "raise_4": "podnie\u015b 4", + "raise_all": "podnie\u015b wszystkie", + "stop": "zatrzymaj (ulubione)", + "stop_1": "zatrzymaj 1", + "stop_2": "zatrzymaj 2", + "stop_3": "zatrzymaj 3", + "stop_4": "zatrzymaj 4", + "stop_all": "zatrzymaj wszystkie" + }, + "trigger_type": { + "press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "release": "przycisk \"{subtype}\" zostanie zwolniony" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ru.json b/homeassistant/components/lutron_caseta/translations/ru.json index 05bd4f51c70bf1..edda7af8e9a96a 100644 --- a/homeassistant/components/lutron_caseta/translations/ru.json +++ b/homeassistant/components/lutron_caseta/translations/ru.json @@ -2,16 +2,56 @@ "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." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "not_lutron_device": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 Lutron." }, "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": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0448\u043b\u044e\u0437 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 configuration.yaml (\u0445\u043e\u0441\u0442: {host}).", "title": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0448\u043b\u044e\u0437\u0430." + }, + "link": { + "description": "\u0427\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 {name} ({host}), \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u044d\u0442\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u0447\u0435\u0440\u043d\u0443\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0437\u0430\u0434\u043d\u0435\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0448\u043b\u044e\u0437\u0430.", + "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441\u043e \u0448\u043b\u044e\u0437\u043e\u043c" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a \u0448\u043b\u044e\u0437\u0443" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0412\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044c\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "close_1": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 1", + "close_2": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 2", + "close_3": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 3", + "close_4": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c 4", + "close_all": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c \u0432\u0441\u0435", + "group_1_button_1": "\u041f\u0435\u0440\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "group_1_button_2": "\u041f\u0435\u0440\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u0432\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "group_2_button_1": "\u0412\u0442\u043e\u0440\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "group_2_button_2": "\u0412\u0442\u043e\u0440\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u0432\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "stop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c (\u043b\u044e\u0431\u0438\u043c\u0430\u044f)", + "stop_1": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 1", + "stop_2": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 2", + "stop_3": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 3", + "stop_4": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 4", + "stop_all": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0441\u0435" + }, + "trigger_type": { + "press": "\"{subtype}\" \u043d\u0430\u0436\u0430\u0442\u0430", + "release": "\"{subtype}\" \u043e\u0442\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u043e\u0441\u043b\u0435 \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044f" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/tr.json b/homeassistant/components/lutron_caseta/translations/tr.json new file mode 100644 index 00000000000000..fdc5e71a7ac518 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/tr.json @@ -0,0 +1,72 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_lutron_device": "Bulunan cihaz bir Lutron cihaz\u0131 de\u011fil" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "Lutron Cas\u00e9ta {name} ( {host} )", + "step": { + "link": { + "description": "{name} ( {host} ) ile e\u015fle\u015ftirmek i\u00e7in, bu formu g\u00f6nderdikten sonra k\u00f6pr\u00fcn\u00fcn arkas\u0131ndaki siyah d\u00fc\u011fmeye bas\u0131n.", + "title": "K\u00f6pr\u00fc ile e\u015fle\u015ftirin" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "description": "Cihaz\u0131n ip adresini girin.", + "title": "K\u00f6pr\u00fcye otomatik olarak ba\u011flan\u0131n" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u0130lk d\u00fc\u011fme", + "button_2": "\u0130kinci d\u00fc\u011fme", + "button_3": "\u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fme", + "button_4": "D\u00f6rd\u00fcnc\u00fc d\u00fc\u011fme", + "close_1": "Kapat 1", + "close_2": "Kapat 2", + "close_3": "Kapat 3", + "close_4": "Kapat 4", + "close_all": "Hepsini kapat", + "group_1_button_1": "Birinci Grup ilk d\u00fc\u011fme", + "group_1_button_2": "Birinci Grup ikinci d\u00fc\u011fme", + "group_2_button_1": "\u0130kinci Grup birinci d\u00fc\u011fme", + "group_2_button_2": "\u0130kinci Grup ikinci d\u00fc\u011fme", + "lower": "Alt", + "lower_1": "Alt 1", + "lower_2": "Alt 2", + "lower_3": "Alt 3", + "lower_4": "Alt 4", + "lower_all": "Hepsini indir", + "off": "Kapal\u0131", + "on": "A\u00e7\u0131k", + "open_1": "A\u00e7 1", + "open_2": "A\u00e7 2", + "open_3": "A\u00e7 3", + "open_4": "A\u00e7\u0131k 4", + "open_all": "Hepsini a\u00e7", + "raise": "Y\u00fckseltmek", + "raise_1": "Y\u00fckselt 1", + "raise_2": "Y\u00fckselt 2", + "raise_3": "Y\u00fckselt 3", + "raise_4": "Y\u00fckselt 4", + "raise_all": "Hepsini Y\u00fckseltin", + "stop": "Durak (favori)", + "stop_1": "Durak 1", + "stop_2": "Durdur 2", + "stop_3": "Durdur 3", + "stop_4": "Durdur 4", + "stop_all": "Hepsini durdur" + }, + "trigger_type": { + "press": "\" {subtype} \" bas\u0131ld\u0131", + "release": "\" {subtype} \" yay\u0131nland\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/uk.json b/homeassistant/components/lutron_caseta/translations/uk.json new file mode 100644 index 00000000000000..238e17405ceea3 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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" + }, + "step": { + "import_failed": { + "description": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u0448\u043b\u044e\u0437 \u0437 \u0444\u0430\u0439\u043b\u0443 'configuration.yaml' (\u0445\u043e\u0441\u0442: {host}).", + "title": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0448\u043b\u044e\u0437\u0443." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/zh-Hant.json b/homeassistant/components/lutron_caseta/translations/zh-Hant.json index 4e8df0d5e9f254..50762fafac1836 100644 --- a/homeassistant/components/lutron_caseta/translations/zh-Hant.json +++ b/homeassistant/components/lutron_caseta/translations/zh-Hant.json @@ -2,16 +2,75 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "cannot_connect": "\u9023\u7dda\u5931\u6557" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "not_lutron_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e Lutron \u88dd\u7f6e" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "\u7121\u6cd5\u8a2d\u5b9a\u7531 configuration.yaml \u532f\u5165\u7684 bridge\uff08\u4e3b\u6a5f\uff1a{host}\uff09\u3002", "title": "\u532f\u5165 Cas\u00e9ta bridge \u8a2d\u5b9a\u5931\u6557\u3002" + }, + "link": { + "description": "\u6b32\u8207 {name} ({host}) \u9032\u884c\u914d\u5c0d\uff0c\u65bc\u50b3\u9001\u8868\u683c\u5f8c\u3001\u4e8c\u4e0b Bridge \u5f8c\u65b9\u7684\u9ed1\u8272\u6309\u9215\u3002", + "title": "\u8207 Bridge \u914d\u5c0d" + }, + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u8f38\u5165\u88dd\u7f6e IP \u4f4d\u5740\u3002", + "title": "\u81ea\u52d5\u9023\u7dda\u81f3 Bridge" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u7b2c\u4e00\u500b\u6309\u9215", + "button_2": "\u7b2c\u4e8c\u500b\u6309\u9215", + "button_3": "\u7b2c\u4e09\u500b\u6309\u9215", + "button_4": "\u7b2c\u56db\u500b\u6309\u9215", + "close_1": "\u95dc\u9589 1", + "close_2": "\u95dc\u9589 2", + "close_3": "\u95dc\u9589 3", + "close_4": "\u95dc\u9589 4", + "close_all": "\u5168\u90e8\u95dc\u9589", + "group_1_button_1": "\u7b2c\u4e00\u7d44\u7b2c\u4e00\u500b\u6309\u9215", + "group_1_button_2": "\u7b2c\u4e00\u7d44\u7b2c\u4e8c\u500b\u6309\u9215", + "group_2_button_1": "\u7b2c\u4e8c\u7d44\u7b2c\u4e00\u500b\u6309\u9215", + "group_2_button_2": "\u7b2c\u4e8c\u7d44\u7b2c\u4e8c\u500b\u6309\u9215", + "lower": "\u964d\u4f4e ", + "lower_1": "\u964d\u4f4e 1", + "lower_2": "\u964d\u4f4e 2", + "lower_3": "\u964d\u4f4e 3", + "lower_4": "\u964d\u4f4e 4", + "lower_all": "\u5168\u90e8\u964d\u4f4e", + "off": "\u95dc\u9589", + "on": "\u958b\u555f", + "open_1": "\u958b\u555f 1", + "open_2": "\u958b\u555f 2", + "open_3": "\u958b\u555f 3", + "open_4": "\u958b\u555f 4", + "open_all": "\u5168\u90e8\u958b\u555f", + "raise": "\u62ac\u8d77", + "raise_1": "\u62ac\u8d77 1", + "raise_2": "\u62ac\u8d77 2", + "raise_3": "\u62ac\u8d77 3", + "raise_4": "\u62ac\u8d77 4", + "raise_all": "\u5168\u90e8\u62ac\u8d77", + "stop": "\u505c\u6b62\uff08\u6700\u611b\uff09", + "stop_1": "\u505c\u6b62 1", + "stop_2": "\u505c\u6b62 2", + "stop_3": "\u505c\u6b62 3", + "stop_4": "\u505c\u6b62 4", + "stop_all": "\u5168\u90e8\u505c\u6b62" + }, + "trigger_type": { + "press": "\"{subtype}\" \u6309\u4e0b", + "release": "\"{subtype}\" \u91cb\u653e" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/ca.json b/homeassistant/components/lyric/translations/ca.json new file mode 100644 index 00000000000000..195d3d59262a9f --- /dev/null +++ b/homeassistant/components/lyric/translations/ca.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/cs.json b/homeassistant/components/lyric/translations/cs.json new file mode 100644 index 00000000000000..2a54a82f41b427 --- /dev/null +++ b/homeassistant/components/lyric/translations/cs.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace." + }, + "create_entry": { + "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno" + }, + "step": { + "pick_implementation": { + "title": "Vyberte metodu ov\u011b\u0159en\u00ed" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/en.json b/homeassistant/components/lyric/translations/en.json new file mode 100644 index 00000000000000..e3849fc17a3aab --- /dev/null +++ b/homeassistant/components/lyric/translations/en.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Timeout generating authorize URL.", + "missing_configuration": "The component is not configured. Please follow the documentation." + }, + "create_entry": { + "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/et.json b/homeassistant/components/lyric/translations/et.json new file mode 100644 index 00000000000000..c7d46e7e9426d2 --- /dev/null +++ b/homeassistant/components/lyric/translations/et.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/it.json b/homeassistant/components/lyric/translations/it.json new file mode 100644 index 00000000000000..42536508716c7c --- /dev/null +++ b/homeassistant/components/lyric/translations/it.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/no.json b/homeassistant/components/lyric/translations/no.json new file mode 100644 index 00000000000000..a8f6ce4f9a3f3c --- /dev/null +++ b/homeassistant/components/lyric/translations/no.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "step": { + "pick_implementation": { + "title": "Velg godkjenningsmetode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/pl.json b/homeassistant/components/lyric/translations/pl.json new file mode 100644 index 00000000000000..8c75c11dd7c4c3 --- /dev/null +++ b/homeassistant/components/lyric/translations/pl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/tr.json b/homeassistant/components/lyric/translations/tr.json new file mode 100644 index 00000000000000..773577271d2ad3 --- /dev/null +++ b/homeassistant/components/lyric/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Yetki URL'si olu\u015fturulurken zaman a\u015f\u0131m\u0131 olu\u015ftu.", + "missing_configuration": "Bile\u015fen yap\u0131land\u0131r\u0131lmam\u0131\u015f. L\u00fctfen belgeleri takip edin." + }, + "create_entry": { + "default": "Ba\u015far\u0131yla do\u011fruland\u0131" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/zh-Hant.json b/homeassistant/components/lyric/translations/zh-Hant.json new file mode 100644 index 00000000000000..b740fd3e063c93 --- /dev/null +++ b/homeassistant/components/lyric/translations/zh-Hant.json @@ -0,0 +1,16 @@ +{ + "config": { + "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" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/de.json b/homeassistant/components/mailgun/translations/de.json index f684f822fd51c6..118192b65160fe 100644 --- a/homeassistant/components/mailgun/translations/de.json +++ b/homeassistant/components/mailgun/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "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." + }, "create_entry": { - "default": "Um Ereignisse an den Home Assistant zu senden, musst [Webhooks mit Mailgun]({mailgun_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhaltstyp: application/json \n\nLies in der [Dokumentation]({docs_url}) wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." + "default": "Um Ereignisse an Home Assistant zu senden, musst du [Webhooks mit Mailgun]({mailgun_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhaltstyp: application/json \n\nLies in der [Dokumentation]({docs_url}), wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/translations/tr.json b/homeassistant/components/mailgun/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/mailgun/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/uk.json b/homeassistant/components/mailgun/translations/uk.json new file mode 100644 index 00000000000000..d999b52085a213 --- /dev/null +++ b/homeassistant/components/mailgun/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f [Mailgun]({mailgun_url}). \n\n\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json \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 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0456\u0439 \u043f\u043e \u043e\u0431\u0440\u043e\u0431\u0446\u0456 \u0434\u0430\u043d\u0438\u0445, \u0449\u043e \u043d\u0430\u0434\u0445\u043e\u0434\u044f\u0442\u044c." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Mailgun?", + "title": "Mailgun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/tr.json b/homeassistant/components/media_player/translations/tr.json index 0130b5fb94cbd7..1f46c6a8bc7c54 100644 --- a/homeassistant/components/media_player/translations/tr.json +++ b/homeassistant/components/media_player/translations/tr.json @@ -1,4 +1,10 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} bo\u015fta", + "is_off": "{entity_name} kapal\u0131" + } + }, "state": { "_": { "idle": "Bo\u015fta", diff --git a/homeassistant/components/media_player/translations/uk.json b/homeassistant/components/media_player/translations/uk.json index f475829a524119..21c7f2897a3d85 100644 --- a/homeassistant/components/media_player/translations/uk.json +++ b/homeassistant/components/media_player/translations/uk.json @@ -1,7 +1,16 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} \u0432 \u0440\u0435\u0436\u0438\u043c\u0456 \u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_paused": "{entity_name} \u043d\u0430 \u043f\u0430\u0443\u0437\u0456", + "is_playing": "{entity_name} \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u044e\u0454 \u043c\u0435\u0434\u0456\u0430" + } + }, "state": { "_": { - "idle": "\u0411\u0435\u0437\u0434\u0456\u044f\u043b\u044c\u043d\u0456\u0441\u0442\u044c", + "idle": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", "paused": "\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0435\u043d\u043e", @@ -9,5 +18,5 @@ "standby": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f" } }, - "title": "\u041c\u0435\u0434\u0456\u0430 \u043f\u043b\u0435\u0454\u0440" + "title": "\u041c\u0435\u0434\u0456\u0430\u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0447" } \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/de.json b/homeassistant/components/melcloud/translations/de.json index 640c96e47c462d..54ae78f8680878 100644 --- a/homeassistant/components/melcloud/translations/de.json +++ b/homeassistant/components/melcloud/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Die MELCloud-Integration ist bereits f\u00fcr diese E-Mail konfiguriert. Das Zugriffstoken wurde aktualisiert." }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/melcloud/translations/tr.json b/homeassistant/components/melcloud/translations/tr.json new file mode 100644 index 00000000000000..6bce50f3de659e --- /dev/null +++ b/homeassistant/components/melcloud/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "MELCloud entegrasyonu bu e-posta i\u00e7in zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Eri\u015fim belirteci yenilendi." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/uk.json b/homeassistant/components/melcloud/translations/uk.json new file mode 100644 index 00000000000000..001239a8b47a48 --- /dev/null +++ b/homeassistant/components/melcloud/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f MELCloud \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0430 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0430\u0434\u0440\u0435\u0441\u0438 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438. \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0456\u0442\u044c\u0441\u044f, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u044e\u0447\u0438 \u0441\u0432\u0456\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 MELCloud.", + "title": "MELCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/de.json b/homeassistant/components/met/translations/de.json index 901b4fb97b5ee7..e2bb171c749bb1 100644 --- a/homeassistant/components/met/translations/de.json +++ b/homeassistant/components/met/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "already_configured": "Der Dienst ist bereits konfiguriert" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/met/translations/tr.json b/homeassistant/components/met/translations/tr.json new file mode 100644 index 00000000000000..d256711728c58b --- /dev/null +++ b/homeassistant/components/met/translations/tr.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/uk.json b/homeassistant/components/met/translations/uk.json new file mode 100644 index 00000000000000..d980db91147a56 --- /dev/null +++ b/homeassistant/components/met/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "elevation": "\u0412\u0438\u0441\u043e\u0442\u0430", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041d\u043e\u0440\u0432\u0435\u0437\u044c\u043a\u0438\u0439 \u043c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0456\u0447\u043d\u0438\u0439 \u0456\u043d\u0441\u0442\u0438\u0442\u0443\u0442.", + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/de.json b/homeassistant/components/meteo_france/translations/de.json index 65313f16c41117..74637594d5ff1c 100644 --- a/homeassistant/components/meteo_france/translations/de.json +++ b/homeassistant/components/meteo_france/translations/de.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Stadt bereits konfiguriert", - "unknown": "Unbekannter Fehler: Bitte versuchen Sie es sp\u00e4ter erneut" + "already_configured": "Standort ist bereits konfiguriert", + "unknown": "Unerwarteter Fehler" }, "error": { "empty": "Kein Ergebnis bei der Stadtsuche: Bitte \u00fcberpr\u00fcfe das Stadtfeld" diff --git a/homeassistant/components/meteo_france/translations/tr.json b/homeassistant/components/meteo_france/translations/tr.json index 57fc9f768815fb..59c3886a90055a 100644 --- a/homeassistant/components/meteo_france/translations/tr.json +++ b/homeassistant/components/meteo_france/translations/tr.json @@ -1,7 +1,28 @@ { "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, "error": { "empty": "\u015eehir aramas\u0131nda sonu\u00e7 yok: l\u00fctfen \u015fehir alan\u0131n\u0131 kontrol edin" + }, + "step": { + "user": { + "data": { + "city": "\u015eehir" + }, + "title": "M\u00e9t\u00e9o-Fransa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Tahmin modu" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/uk.json b/homeassistant/components/meteo_france/translations/uk.json new file mode 100644 index 00000000000000..a84c230e21886b --- /dev/null +++ b/homeassistant/components/meteo_france/translations/uk.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "empty": "\u041d\u0435\u043c\u0430\u0454 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432 \u043f\u043e\u0448\u0443\u043a\u0443. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u041c\u0456\u0441\u0442\u043e\"." + }, + "step": { + "cities": { + "data": { + "city": "\u041c\u0456\u0441\u0442\u043e" + }, + "description": "\u041e\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0442\u043e \u0437\u0456 \u0441\u043f\u0438\u0441\u043a\u0443", + "title": "M\u00e9t\u00e9o-France" + }, + "user": { + "data": { + "city": "\u041c\u0456\u0441\u0442\u043e" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441 (\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0424\u0440\u0430\u043d\u0446\u0456\u0457) \u0430\u0431\u043e \u043d\u0430\u0437\u0432\u0443 \u043c\u0456\u0441\u0442\u0430", + "title": "M\u00e9t\u00e9o-France" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "\u0420\u0435\u0436\u0438\u043c \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0443" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/de.json b/homeassistant/components/metoffice/translations/de.json index 74c204b96838e0..7b92af96c992f0 100644 --- a/homeassistant/components/metoffice/translations/de.json +++ b/homeassistant/components/metoffice/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Service ist bereits konfiguriert" }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/metoffice/translations/tr.json b/homeassistant/components/metoffice/translations/tr.json new file mode 100644 index 00000000000000..55064a139ef169 --- /dev/null +++ b/homeassistant/components/metoffice/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + }, + "description": "Enlem ve boylam, en yak\u0131n hava istasyonunu bulmak i\u00e7in kullan\u0131lacakt\u0131r." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/uk.json b/homeassistant/components/metoffice/translations/uk.json new file mode 100644 index 00000000000000..53ab2115e82e32 --- /dev/null +++ b/homeassistant/components/metoffice/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "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": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" + }, + "description": "\u0428\u0438\u0440\u043e\u0442\u0430 \u0456 \u0434\u043e\u0432\u0433\u043e\u0442\u0430 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u0456 \u0434\u043b\u044f \u043f\u043e\u0448\u0443\u043a\u0443 \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457 \u043c\u0435\u0442\u0435\u043e\u0441\u0442\u0430\u043d\u0446\u0456\u0457.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Met Office UK" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/de.json b/homeassistant/components/mikrotik/translations/de.json index 4211077c82ce26..82ea47dc4bf48c 100644 --- a/homeassistant/components/mikrotik/translations/de.json +++ b/homeassistant/components/mikrotik/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Mikrotik ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "name_exists": "Name vorhanden" }, "step": { @@ -25,7 +26,7 @@ "step": { "device_tracker": { "data": { - "arp_ping": "ARP Ping aktivieren", + "arp_ping": "ARP-Ping aktivieren", "force_dhcp": "Erzwingen Sie das Scannen \u00fcber DHCP" } } diff --git a/homeassistant/components/mikrotik/translations/tr.json b/homeassistant/components/mikrotik/translations/tr.json new file mode 100644 index 00000000000000..cffcc65151c6d6 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/uk.json b/homeassistant/components/mikrotik/translations/uk.json new file mode 100644 index 00000000000000..b44d5979d13058 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "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.", + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 SSL" + }, + "title": "MikroTik" + } + } + }, + "options": { + "step": { + "device_tracker": { + "data": { + "arp_ping": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 ARP-\u043f\u0456\u043d\u0433", + "detection_time": "\u0427\u0430\u0441 \u0432\u0456\u0434 \u043e\u0441\u0442\u0430\u043d\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0443 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c (\u0441\u0435\u043a.), \u043f\u043e \u0437\u0430\u043a\u0456\u043d\u0447\u0435\u043d\u043d\u044e \u044f\u043a\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043e\u0442\u0440\u0438\u043c\u0430\u0454 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\".", + "force_dhcp": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u0435 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f \u0437 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f\u043c DHCP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/de.json b/homeassistant/components/mill/translations/de.json index 886e7e3c4589e2..63b6b7ea6e93ac 100644 --- a/homeassistant/components/mill/translations/de.json +++ b/homeassistant/components/mill/translations/de.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Account ist bereits konfiguriert" }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/mill/translations/fr.json b/homeassistant/components/mill/translations/fr.json index e171086a084e66..ffcff15ade8388 100644 --- a/homeassistant/components/mill/translations/fr.json +++ b/homeassistant/components/mill/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de connexion" diff --git a/homeassistant/components/mill/translations/tr.json b/homeassistant/components/mill/translations/tr.json new file mode 100644 index 00000000000000..0f14728873a538 --- /dev/null +++ b/homeassistant/components/mill/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/uk.json b/homeassistant/components/mill/translations/uk.json new file mode 100644 index 00000000000000..b8a5aea578e7fd --- /dev/null +++ b/homeassistant/components/mill/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/de.json b/homeassistant/components/minecraft_server/translations/de.json index 484be7bd41867a..a0bbe60a842dcc 100644 --- a/homeassistant/components/minecraft_server/translations/de.json +++ b/homeassistant/components/minecraft_server/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Der Host ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { "cannot_connect": "Verbindung zum Server fehlgeschlagen. Bitte \u00fcberpr\u00fcfe den Host und den Port und versuche es erneut. Stelle au\u00dferdem sicher, dass Du mindestens Minecraft Version 1.7 auf Deinem Server ausf\u00fchrst.", diff --git a/homeassistant/components/minecraft_server/translations/tr.json b/homeassistant/components/minecraft_server/translations/tr.json index 7527294a3c7aa0..422dab32a01311 100644 --- a/homeassistant/components/minecraft_server/translations/tr.json +++ b/homeassistant/components/minecraft_server/translations/tr.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Ana Bilgisayar", "name": "Ad" }, "description": "G\u00f6zetmeye izin vermek i\u00e7in Minecraft server nesnesini ayarla.", diff --git a/homeassistant/components/minecraft_server/translations/uk.json b/homeassistant/components/minecraft_server/translations/uk.json new file mode 100644 index 00000000000000..0c8528b2cab1fb --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u0427\u0438 \u043d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0456\u0441\u0442\u044c \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0445 \u0434\u0430\u043d\u0438\u0445 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u043d\u043e\u0432\u0443. \u0422\u0430\u043a\u043e\u0436 \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u043d\u0430 \u0412\u0430\u0448\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0456 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 Minecraft \u0432\u0435\u0440\u0441\u0456\u0457 1.7, \u0430\u0431\u043e \u0432\u0438\u0449\u0435.", + "invalid_ip": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 MAC-\u0430\u0434\u0440\u0435\u0441\u0443).", + "invalid_port": "\u041f\u043e\u0440\u0442 \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0434\u0456\u0430\u043f\u0430\u0437\u043e\u043d\u0456 \u0432\u0456\u0434 1024 \u0434\u043e 65535." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0446\u0435\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443 \u0412\u0430\u0448\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Minecraft.", + "title": "Minecraft Server" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/tr.json b/homeassistant/components/mobile_app/translations/tr.json new file mode 100644 index 00000000000000..10d79751ec12e7 --- /dev/null +++ b/homeassistant/components/mobile_app/translations/tr.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "action_type": { + "notify": "Bildirim g\u00f6nder" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/uk.json b/homeassistant/components/mobile_app/translations/uk.json index 4a48dd3775d5f7..db471bbdc7fa5a 100644 --- a/homeassistant/components/mobile_app/translations/uk.json +++ b/homeassistant/components/mobile_app/translations/uk.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "install_app": "\u0412\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a, \u0449\u043e\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u0437 Home Assistant. \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438]({apps_url}) \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0441\u043f\u0438\u0441\u043a\u0443 \u0441\u0443\u043c\u0456\u0441\u043d\u0438\u0445 \u0434\u043e\u0434\u0430\u0442\u043a\u0456\u0432." + }, "step": { "confirm": { - "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430?" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a?" } } + }, + "device_automation": { + "action_type": { + "notify": "\u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438 \u0441\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f" + } } } \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/de.json b/homeassistant/components/monoprice/translations/de.json index 820d3a972d3151..8f6d1d88196c05 100644 --- a/homeassistant/components/monoprice/translations/de.json +++ b/homeassistant/components/monoprice/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/monoprice/translations/tr.json b/homeassistant/components/monoprice/translations/tr.json new file mode 100644 index 00000000000000..7c622a3cb4a140 --- /dev/null +++ b/homeassistant/components/monoprice/translations/tr.json @@ -0,0 +1,25 @@ +{ + "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": { + "port": "Port", + "source_1": "Kaynak #1 ad\u0131", + "source_2": "Kaynak #2 ad\u0131", + "source_3": "Kaynak #3 ad\u0131", + "source_4": "Kaynak #4 ad\u0131", + "source_5": "Kaynak #5 ad\u0131", + "source_6": "Kaynak #6 ad\u0131" + }, + "title": "Cihaza ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/uk.json b/homeassistant/components/monoprice/translations/uk.json new file mode 100644 index 00000000000000..08857cc26f9b1a --- /dev/null +++ b/homeassistant/components/monoprice/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "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": { + "port": "\u041f\u043e\u0440\u0442", + "source_1": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #1", + "source_2": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #2", + "source_3": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #3", + "source_4": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #4", + "source_5": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #5", + "source_6": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #6" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #1", + "source_2": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #2", + "source_3": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #3", + "source_4": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #4", + "source_5": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #5", + "source_6": "\u041d\u0430\u0437\u0432\u0430 \u0434\u0436\u0435\u0440\u0435\u043b\u0430 #6" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u0436\u0435\u0440\u0435\u043b" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.uk.json b/homeassistant/components/moon/translations/sensor.uk.json index 71c2d80eb9804f..f916c03c3a1e87 100644 --- a/homeassistant/components/moon/translations/sensor.uk.json +++ b/homeassistant/components/moon/translations/sensor.uk.json @@ -4,7 +4,11 @@ "first_quarter": "\u041f\u0435\u0440\u0448\u0430 \u0447\u0432\u0435\u0440\u0442\u044c", "full_moon": "\u041f\u043e\u0432\u043d\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", "last_quarter": "\u041e\u0441\u0442\u0430\u043d\u043d\u044f \u0447\u0432\u0435\u0440\u0442\u044c", - "new_moon": "\u041d\u043e\u0432\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c" + "new_moon": "\u041d\u043e\u0432\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waning_crescent": "\u0421\u0442\u0430\u0440\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waning_gibbous": "\u0421\u043f\u0430\u0434\u0430\u044e\u0447\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waxing_crescent": "\u041c\u043e\u043b\u043e\u0434\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c", + "waxing_gibbous": "\u041f\u0440\u0438\u0431\u0443\u0432\u0430\u044e\u0447\u0438\u0439 \u043c\u0456\u0441\u044f\u0446\u044c" } } } \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/ca.json b/homeassistant/components/motion_blinds/translations/ca.json index a4bf96457e6f15..b83746b9ccfaca 100644 --- a/homeassistant/components/motion_blinds/translations/ca.json +++ b/homeassistant/components/motion_blinds/translations/ca.json @@ -5,14 +5,31 @@ "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "connection_error": "Ha fallat la connexi\u00f3" }, + "error": { + "discovery_error": "No s'ha pogut descobrir cap Motion Gateway" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Clau API" + }, + "description": "Necessitar\u00e0s la clau API de 16 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Adre\u00e7a IP" + }, + "description": "Torna a executar la configuraci\u00f3 si vols connectar m\u00e9s Motion Gateways", + "title": "Selecciona el Motion Gateway que vulguis connectar" + }, "user": { "data": { "api_key": "Clau API", "host": "Adre\u00e7a IP" }, - "description": "Necessitar\u00e0s el token d'API de 16 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "description": "Connecta el teu Motion Gateway, si no es configura l'adre\u00e7a IP, s'utilitza el descobriment autom\u00e0tic", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/cs.json b/homeassistant/components/motion_blinds/translations/cs.json index 41b5db3c83ec40..899f04d7cd4393 100644 --- a/homeassistant/components/motion_blinds/translations/cs.json +++ b/homeassistant/components/motion_blinds/translations/cs.json @@ -7,12 +7,21 @@ }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Kl\u00ed\u010d API" + } + }, + "select": { + "data": { + "select_ip": "IP adresa" + } + }, "user": { "data": { "api_key": "Kl\u00ed\u010d API", "host": "IP adresa" }, - "description": "Budete pot\u0159ebovat 16m\u00edstn\u00fd API kl\u00ed\u010d, pokyny najdete na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/de.json b/homeassistant/components/motion_blinds/translations/de.json index dd1acc230f1e82..c1a7ac0bc8d40d 100644 --- a/homeassistant/components/motion_blinds/translations/de.json +++ b/homeassistant/components/motion_blinds/translations/de.json @@ -1,16 +1,28 @@ { "config": { "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "connection_error": "Verbindung fehlgeschlagen" }, "flow_title": "Jalousien", "step": { + "connect": { + "data": { + "api_key": "API-Schl\u00fcssel" + } + }, + "select": { + "data": { + "select_ip": "IP-Adresse" + } + }, "user": { "data": { "api_key": "API-Schl\u00fcssel", "host": "IP-Adresse" }, - "description": "Ein 16-Zeichen-API-Schl\u00fcssel wird ben\u00f6tigt, siehe https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "description": "Stelle eine Verbindung zu deinem Motion Gateway her. Wenn die IP-Adresse leer bleibt, wird die automatische Erkennung verwendet", "title": "Jalousien" } } diff --git a/homeassistant/components/motion_blinds/translations/en.json b/homeassistant/components/motion_blinds/translations/en.json index b7830a255fc073..3a968bc6491b91 100644 --- a/homeassistant/components/motion_blinds/translations/en.json +++ b/homeassistant/components/motion_blinds/translations/en.json @@ -5,14 +5,31 @@ "already_in_progress": "Configuration flow is already in progress", "connection_error": "Failed to connect" }, + "error": { + "discovery_error": "Failed to discover a Motion Gateway" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "API Key" + }, + "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP Address" + }, + "description": "Run the setup again if you want to connect additional Motion Gateways", + "title": "Select the Motion Gateway that you wish to connect" + }, "user": { "data": { "api_key": "API Key", "host": "IP Address" }, - "description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions", + "description": "Connect to your Motion Gateway, if the IP address is not set, auto-discovery is used", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/es.json b/homeassistant/components/motion_blinds/translations/es.json index bac5ffddbd31d0..7d7c6c1510fc92 100644 --- a/homeassistant/components/motion_blinds/translations/es.json +++ b/homeassistant/components/motion_blinds/translations/es.json @@ -5,14 +5,31 @@ "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "connection_error": "No se pudo conectar" }, + "error": { + "discovery_error": "No se pudo descubrir un detector de movimiento" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Clave API" + }, + "description": "Necesitar\u00e1 la clave de API de 16 caracteres, consulte https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para obtener instrucciones", + "title": "Estores motorizados" + }, + "select": { + "data": { + "select_ip": "Direcci\u00f3n IP" + }, + "description": "Ejecute la configuraci\u00f3n de nuevo si desea conectar detectores de movimiento adicionales", + "title": "Selecciona el detector de Movimiento que deseas conectar" + }, "user": { "data": { "api_key": "Clave API", "host": "Direcci\u00f3n IP" }, - "description": "Necesitar\u00e1s la Clave API de 16 caracteres, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key para instrucciones", + "description": "Con\u00e9ctate a tu Motion Gateway, si la direcci\u00f3n IP no est\u00e1 establecida, se utilitzar\u00e1 la detecci\u00f3n autom\u00e1tica", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/et.json b/homeassistant/components/motion_blinds/translations/et.json index b55640d8905646..5e585dec1a3d64 100644 --- a/homeassistant/components/motion_blinds/translations/et.json +++ b/homeassistant/components/motion_blinds/translations/et.json @@ -5,14 +5,31 @@ "already_in_progress": "Seadistamine on juba k\u00e4imas", "connection_error": "\u00dchendamine nurjus" }, + "error": { + "discovery_error": "Motion Gateway avastamine nurjus" + }, "flow_title": "", "step": { + "connect": { + "data": { + "api_key": "API v\u00f5ti" + }, + "description": "On vaja 16-kohalist API-v\u00f5tit, juhiste saamiseks vaata https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "" + }, + "select": { + "data": { + "select_ip": "IP aadress" + }, + "description": "K\u00e4ivita seadistamine uuesti kui soovid \u00fchendada t\u00e4iendavaid Motion Gateway sidumisi", + "title": "Vali Motion Gateway, mille soovid \u00fchendada" + }, "user": { "data": { "api_key": "API v\u00f5ti", "host": "IP-aadress" }, - "description": "Vaja on 16-kohalist API-v\u00f5tit. Juhiste saamiseks vt https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "description": "\u00dchenda oma Motion Gatewayga. Kui IP-aadress on m\u00e4\u00e4ramata kasutatakse automaatset avastamist", "title": "" } } diff --git a/homeassistant/components/motion_blinds/translations/fr.json b/homeassistant/components/motion_blinds/translations/fr.json new file mode 100644 index 00000000000000..86d008b9e6d768 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/fr.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "connect": { + "data": { + "api_key": "Cl\u00e9 API" + }, + "description": "Vous aurez besoin de la cl\u00e9 API de 16 caract\u00e8res, voir https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key pour les instructions" + }, + "select": { + "data": { + "select_ip": "Adresse IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/it.json b/homeassistant/components/motion_blinds/translations/it.json index ff56f184ac2e1e..1d79ae28ee5173 100644 --- a/homeassistant/components/motion_blinds/translations/it.json +++ b/homeassistant/components/motion_blinds/translations/it.json @@ -5,14 +5,31 @@ "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "connection_error": "Impossibile connettersi" }, + "error": { + "discovery_error": "Impossibile rilevare un Motion Gateway" + }, "flow_title": "Tende Motion", "step": { + "connect": { + "data": { + "api_key": "Chiave API" + }, + "description": "Avrai bisogno della chiave API di 16 caratteri, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key per le istruzioni", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Indirizzo IP" + }, + "description": "Esegui nuovamente l'installazione se desideri collegare altri Motion Gateway", + "title": "Seleziona il Motion Gateway che vorresti collegare" + }, "user": { "data": { "api_key": "Chiave API", "host": "Indirizzo IP" }, - "description": "Avrai bisogno della chiave API di 16 caratteri, consulta https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key per le istruzioni", + "description": "Connetti il tuo Motion Gateway, se l'indirizzo IP non \u00e8 impostato, sar\u00e0 utilizzato il rilevamento automatico", "title": "Tende Motion" } } diff --git a/homeassistant/components/motion_blinds/translations/lb.json b/homeassistant/components/motion_blinds/translations/lb.json index 7a3dcfdbf07eec..85caeea79e52f3 100644 --- a/homeassistant/components/motion_blinds/translations/lb.json +++ b/homeassistant/components/motion_blinds/translations/lb.json @@ -5,7 +5,21 @@ "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", "connection_error": "Feeler beim verbannen" }, + "error": { + "discovery_error": "Feeler beim Entdecken vun enger Motion Gateway" + }, "step": { + "connect": { + "data": { + "api_key": "API Schl\u00ebssel" + }, + "description": "Du brauchs de 16 stellegen API Schl\u00ebssel, kuck https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key fir w\u00e9ider Instruktiounen" + }, + "select": { + "data": { + "select_ip": "IP Adresse" + } + }, "user": { "data": { "api_key": "API Schl\u00ebssel", diff --git a/homeassistant/components/motion_blinds/translations/no.json b/homeassistant/components/motion_blinds/translations/no.json index 9e4061506912ee..e86da7c1fc494d 100644 --- a/homeassistant/components/motion_blinds/translations/no.json +++ b/homeassistant/components/motion_blinds/translations/no.json @@ -5,14 +5,31 @@ "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "connection_error": "Tilkobling mislyktes" }, + "error": { + "discovery_error": "Kunne ikke oppdage en Motion Gateway" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "API-n\u00f8kkel" + }, + "description": "Du trenger API-n\u00f8kkelen med 16 tegn, se https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instruksjoner", + "title": "" + }, + "select": { + "data": { + "select_ip": "IP adresse" + }, + "description": "Kj\u00f8r oppsettet p\u00e5 nytt hvis du vil koble til flere Motion Gateways", + "title": "Velg Motion Gateway som du vil koble til" + }, "user": { "data": { "api_key": "API-n\u00f8kkel", "host": "IP adresse" }, - "description": "Du trenger API-n\u00f8kkelen med 16 tegn, se https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instruksjoner", + "description": "Koble til Motion Gateway. Hvis IP-adressen ikke er angitt, brukes automatisk oppdagelse", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/pl.json b/homeassistant/components/motion_blinds/translations/pl.json index 8f73496fd1d2e2..1d34d22d65e21b 100644 --- a/homeassistant/components/motion_blinds/translations/pl.json +++ b/homeassistant/components/motion_blinds/translations/pl.json @@ -5,14 +5,31 @@ "already_in_progress": "Konfiguracja jest ju\u017c w toku", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, + "error": { + "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 bramki ruchu" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "Klucz API" + }, + "description": "B\u0119dziesz potrzebowa\u0142 16-znakowego klucza API, instrukcje znajdziesz na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Adres IP" + }, + "description": "Uruchom ponownie konfiguracj\u0119, je\u015bli chcesz pod\u0142\u0105czy\u0107 dodatkowe bramki ruchu", + "title": "Wybierz bram\u0119 ruchu, z kt\u00f3r\u0105 chcesz si\u0119 po\u0142\u0105czy\u0107" + }, "user": { "data": { "api_key": "Klucz API", "host": "Adres IP" }, - "description": "B\u0119dziesz potrzebowa\u0142 16-znakowego klucza API, instrukcje znajdziesz na https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "description": "Po\u0142\u0105cz si\u0119 z bram\u0105 ruchu. Je\u015bli adres IP nie jest ustawiony, u\u017cywane jest automatyczne wykrywanie", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/pt.json b/homeassistant/components/motion_blinds/translations/pt.json index fe188057e4616d..64ccd6061d2e06 100644 --- a/homeassistant/components/motion_blinds/translations/pt.json +++ b/homeassistant/components/motion_blinds/translations/pt.json @@ -5,12 +5,25 @@ "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", "connection_error": "Falha na liga\u00e7\u00e3o" }, + "flow_title": "Cortinas Motion", "step": { + "connect": { + "data": { + "api_key": "API Key" + }, + "title": "Cortinas Motion" + }, + "select": { + "data": { + "select_ip": "Endere\u00e7o IP" + } + }, "user": { "data": { "api_key": "API Key", "host": "Endere\u00e7o IP" - } + }, + "title": "Cortinas Motion" } } } diff --git a/homeassistant/components/motion_blinds/translations/ru.json b/homeassistant/components/motion_blinds/translations/ru.json index 1a249a4fab86a0..ae2d3229c208ca 100644 --- a/homeassistant/components/motion_blinds/translations/ru.json +++ b/homeassistant/components/motion_blinds/translations/ru.json @@ -5,14 +5,31 @@ "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.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, + "error": { + "discovery_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0448\u043b\u044e\u0437 Motion." + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441" + }, + "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0435\u0441\u043b\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0448\u043b\u044e\u0437", + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 Motion" + }, "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", "host": "IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Motion. \u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0448\u043b\u044e\u0437\u0430, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u0443\u0441\u0442\u044b\u043c.", "title": "Motion Blinds" } } diff --git a/homeassistant/components/motion_blinds/translations/tr.json b/homeassistant/components/motion_blinds/translations/tr.json index 545a3547ffcd16..194608780c9853 100644 --- a/homeassistant/components/motion_blinds/translations/tr.json +++ b/homeassistant/components/motion_blinds/translations/tr.json @@ -1,14 +1,30 @@ { "config": { "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", "connection_error": "Ba\u011flanma hatas\u0131" }, + "flow_title": "Hareketli Panjurlar", "step": { + "connect": { + "data": { + "api_key": "API Anahtar\u0131" + } + }, + "select": { + "data": { + "select_ip": "\u0130p Adresi" + }, + "title": "Ba\u011flamak istedi\u011finiz Hareket A\u011f Ge\u00e7idini se\u00e7in" + }, "user": { "data": { "api_key": "API Anahtar\u0131", "host": "IP adresi" - } + }, + "description": "Motion Gateway'inize ba\u011flan\u0131n, IP adresi ayarlanmad\u0131ysa, otomatik ke\u015fif kullan\u0131l\u0131r", + "title": "Hareketli Panjurlar" } } } diff --git a/homeassistant/components/motion_blinds/translations/uk.json b/homeassistant/components/motion_blinds/translations/uk.json new file mode 100644 index 00000000000000..99ccb60dc6cce9 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/uk.json @@ -0,0 +1,37 @@ +{ + "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_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.", + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "error": { + "discovery_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u044f\u0432\u0438\u0442\u0438 Motion Gateway" + }, + "flow_title": "Motion Blinds", + "step": { + "connect": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0412\u0430\u043c \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0434\u0438\u0432. https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0449\u0435 \u0440\u0430\u0437, \u044f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 Motion Gateway", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c Motion Gateway, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 16-\u0441\u0438\u043c\u0432\u043e\u043b\u044c\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0457 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key.", + "title": "Motion Blinds" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/zh-Hant.json b/homeassistant/components/motion_blinds/translations/zh-Hant.json index 37925ca6288189..0f2f9881ebd09c 100644 --- a/homeassistant/components/motion_blinds/translations/zh-Hant.json +++ b/homeassistant/components/motion_blinds/translations/zh-Hant.json @@ -5,14 +5,31 @@ "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "connection_error": "\u9023\u7dda\u5931\u6557" }, + "error": { + "discovery_error": "\u63a2\u7d22 Motion \u9598\u9053\u5668\u5931\u6557" + }, "flow_title": "Motion Blinds", "step": { + "connect": { + "data": { + "api_key": "API \u5bc6\u9470" + }, + "description": "\u5c07\u9700\u8981\u8f38\u5165 16 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP \u4f4d\u5740" + }, + "description": "\u5047\u5982\u6b32\u9023\u7dda\u81f3\u5176\u4ed6 Motion \u9598\u9053\u5668\uff0c\u8acb\u518d\u57f7\u884c\u4e00\u6b21\u8a2d\u5b9a\u6b65\u9a5f", + "title": "\u9078\u64c7\u6240\u8981\u9023\u7dda\u7684 Motion \u7db2\u95dc" + }, "user": { "data": { "api_key": "API \u5bc6\u9470", "host": "IP \u4f4d\u5740" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 16 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002", + "description": "\u9023\u7dda\u81f3 Motion \u9598\u9053\u5668\uff0c\u5047\u5982\u672a\u63d0\u4f9b IP \u4f4d\u5740\uff0c\u5c07\u4f7f\u7528\u81ea\u52d5\u63a2\u7d22", "title": "Motion Blinds" } } diff --git a/homeassistant/components/mqtt/translations/cs.json b/homeassistant/components/mqtt/translations/cs.json index 325e8dde09831c..60c323d9051a91 100644 --- a/homeassistant/components/mqtt/translations/cs.json +++ b/homeassistant/components/mqtt/translations/cs.json @@ -38,13 +38,13 @@ "turn_on": "Zapnout" }, "trigger_type": { - "button_double_press": "Dvakr\u00e1t stisknuto \"{subtype}\"", + "button_double_press": "\"{subtype}\" stisknuto dvakr\u00e1t", "button_long_release": "Uvoln\u011bno \"{subtype}\" po dlouh\u00e9m stisku", - "button_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto \"{subtype}\"", - "button_quintuple_press": "P\u011btkr\u00e1t stisknuto \"{subtype}\"", - "button_short_press": "Stiknuto \"{subtype}\"", + "button_quadruple_press": "\"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "button_quintuple_press": "\"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "button_short_press": "\"{subtype}\" stisknuto", "button_short_release": "Uvoln\u011bno \"{subtype}\"", - "button_triple_press": "T\u0159ikr\u00e1t stisknuto \"{subtype}\"" + "button_triple_press": "\"{subtype}\" stisknuto t\u0159ikr\u00e1t" } }, "options": { diff --git a/homeassistant/components/mqtt/translations/de.json b/homeassistant/components/mqtt/translations/de.json index a92886eb0c6a5b..3346abfd53e22a 100644 --- a/homeassistant/components/mqtt/translations/de.json +++ b/homeassistant/components/mqtt/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Nur eine einzige Konfiguration von MQTT ist zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Es konnte keine Verbindung zum Broker hergestellt werden." + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "broker": { @@ -59,12 +59,15 @@ "password": "Passwort", "port": "Port", "username": "Benutzername" - } + }, + "description": "Bitte gib die Verbindungsinformationen deines MQTT-Brokers ein." }, "options": { "data": { + "discovery": "Erkennung aktivieren", "will_enable": "Letzten Willen aktivieren" - } + }, + "description": "Bitte die MQTT-Einstellungen ausw\u00e4hlen." } } } diff --git a/homeassistant/components/mqtt/translations/no.json b/homeassistant/components/mqtt/translations/no.json index 2a9372b3fb0578..12c72603a1fa32 100644 --- a/homeassistant/components/mqtt/translations/no.json +++ b/homeassistant/components/mqtt/translations/no.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktiver oppdagelse" }, - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til en MQTT megler som er levert av Hass.io-tillegget {addon}?", - "title": "MQTT megler via Hass.io tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til en MQTT megler som er levert av Hass.io-tillegg {addon}?", + "title": "MQTT megler via Hass.io-tillegg" } } }, diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index ce41d059b244b9..08b1d2f1974d56 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -38,14 +38,14 @@ "turn_on": "w\u0142\u0105cznik" }, "trigger_type": { - "button_double_press": "\"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", - "button_long_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", - "button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", - "button_quadruple_press": "\"{subtype}\" zostanie czterokrotnie naci\u015bni\u0119ty", - "button_quintuple_press": "\"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", - "button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", - "button_short_release": "\"{subtype}\" zostanie zwolniony", - "button_triple_press": "\"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" + "button_double_press": "przycisk \"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", + "button_long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", + "button_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "button_quadruple_press": "przycisk \"{subtype}\" zostanie czterokrotnie naci\u015bni\u0119ty", + "button_quintuple_press": "przycisk \"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", + "button_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "button_short_release": "przycisk \"{subtype}\" zostanie zwolniony", + "button_triple_press": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" } }, "options": { diff --git a/homeassistant/components/mqtt/translations/ru.json b/homeassistant/components/mqtt/translations/ru.json index 0079481d6f2541..7cc7a84b28c682 100644 --- a/homeassistant/components/mqtt/translations/ru.json +++ b/homeassistant/components/mqtt/translations/ru.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant \"{addon}\")?", + "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant)" } } }, diff --git a/homeassistant/components/mqtt/translations/tr.json b/homeassistant/components/mqtt/translations/tr.json index 1b73b94d5a4337..86dce2b6ea4b6e 100644 --- a/homeassistant/components/mqtt/translations/tr.json +++ b/homeassistant/components/mqtt/translations/tr.json @@ -1,11 +1,52 @@ { "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { + "broker": { + "data": { + "password": "Parola", + "port": "Port" + } + }, "hassio_confirm": { "data": { "discovery": "Ke\u015ffetmeyi etkinle\u015ftir" } } } + }, + "device_automation": { + "trigger_subtype": { + "turn_off": "Kapat", + "turn_on": "A\u00e7" + }, + "trigger_type": { + "button_double_press": "\" {subtype} \" \u00e7ift t\u0131kland\u0131", + "button_long_press": "\" {subtype} \" s\u00fcrekli olarak bas\u0131ld\u0131", + "button_quadruple_press": "\" {subtype} \" d\u00f6rt kez t\u0131kland\u0131", + "button_quintuple_press": "\" {subtype} \" be\u015fli t\u0131kland\u0131", + "button_short_press": "\" {subtype} \" bas\u0131ld\u0131", + "button_short_release": "\" {subtype} \" yay\u0131nland\u0131", + "button_triple_press": "\" {subtype} \" \u00fc\u00e7 kez t\u0131kland\u0131" + } + }, + "options": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "broker": { + "data": { + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/uk.json b/homeassistant/components/mqtt/translations/uk.json index 747d190a56d6a9..f871db4aa9d965 100644 --- a/homeassistant/components/mqtt/translations/uk.json +++ b/homeassistant/components/mqtt/translations/uk.json @@ -1,26 +1,84 @@ { "config": { "abort": { - "single_instance_allowed": "\u0414\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e \u043b\u0438\u0448\u0435 \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e MQTT." + "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." }, "error": { - "cannot_connect": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430." + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" }, "step": { "broker": { "data": { "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", - "discovery": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0448\u0443\u043a", + "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0410\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0432\u0430\u0448\u043e\u0433\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT." + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0437 \u0432\u0430\u0448\u0438\u043c \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c MQTT." }, "hassio_confirm": { "data": { - "discovery": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0448\u0443\u043a" - } + "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0410\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" + }, + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", + "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_5": "\u041f'\u044f\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_6": "\u0428\u043e\u0441\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "button_double_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438", + "button_long_press": "{subtype} \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "button_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "button_quadruple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438", + "button_quintuple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432", + "button_short_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "button_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "button_triple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438" + } + }, + "options": { + "error": { + "bad_birth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f.", + "bad_will": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "broker": { + "data": { + "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0437 \u0432\u0430\u0448\u0438\u043c \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c MQTT." + }, + "options": { + "data": { + "birth_enable": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_payload": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_qos": "QoS \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_retain": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "birth_topic": "\u0422\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f (LWT)", + "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f", + "will_enable": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_payload": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_qos": "QoS \u0442\u043e\u043f\u0456\u043a\u0430 \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_retain": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0442\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "will_topic": "\u0422\u043e\u043f\u0456\u043a \u043f\u0440\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f (LWT)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 MQTT." } } } diff --git a/homeassistant/components/myq/translations/de.json b/homeassistant/components/myq/translations/de.json index d5c890e41699e6..fafa38c7817646 100644 --- a/homeassistant/components/myq/translations/de.json +++ b/homeassistant/components/myq/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ ist bereits konfiguriert" + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/myq/translations/tr.json b/homeassistant/components/myq/translations/tr.json new file mode 100644 index 00000000000000..7347d18bc34283 --- /dev/null +++ b/homeassistant/components/myq/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "MyQ A\u011f Ge\u00e7idine ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/uk.json b/homeassistant/components/myq/translations/uk.json new file mode 100644 index 00000000000000..12f8406de12b02 --- /dev/null +++ b/homeassistant/components/myq/translations/uk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "MyQ" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/de.json b/homeassistant/components/neato/translations/de.json index 94fcd3c4cb2b54..4c2fc456873181 100644 --- a/homeassistant/components/neato/translations/de.json +++ b/homeassistant/components/neato/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", "invalid_auth": "Ung\u00fcltige Authentifizierung", - "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte beachte die Dokumentation.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler sind [im Hilfebereich]({docs_url}) zu finden", "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, @@ -20,7 +20,7 @@ "title": "W\u00e4hle die Authentifizierungsmethode" }, "reauth_confirm": { - "title": "Wollen Sie mit der Einrichtung beginnen?" + "title": "M\u00f6chtest du mit der Einrichtung beginnen?" }, "user": { "data": { diff --git a/homeassistant/components/neato/translations/it.json b/homeassistant/components/neato/translations/it.json index 100237c33e6dad..95866e918c6a0a 100644 --- a/homeassistant/components/neato/translations/it.json +++ b/homeassistant/components/neato/translations/it.json @@ -2,14 +2,14 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "authorize_url_timeout": "Timeout nella generazione dell'URL di autorizzazione.", + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "invalid_auth": "Autenticazione non valida", - "missing_configuration": "Questo componente non \u00e8 configurato. Per favore segui la documentazione.", - "no_url_available": "Nessun URL disponibile. Per altre informazioni su questo errore, [controlla la sezione di aiuto]({docs_url})", - "reauth_successful": "Ri-autenticazione completata con successo" + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", + "reauth_successful": "La riautenticazione ha avuto successo" }, "create_entry": { - "default": "Autenticato con successo" + "default": "Autenticazione riuscita" }, "error": { "invalid_auth": "Autenticazione non valida", @@ -17,10 +17,10 @@ }, "step": { "pick_implementation": { - "title": "Scegli un metodo di autenticazione" + "title": "Scegli il metodo di autenticazione" }, "reauth_confirm": { - "title": "Vuoi cominciare la configurazione?" + "title": "Vuoi iniziare la configurazione?" }, "user": { "data": { diff --git a/homeassistant/components/neato/translations/lb.json b/homeassistant/components/neato/translations/lb.json index 44d8e4f68119c9..adc42ae840dcc0 100644 --- a/homeassistant/components/neato/translations/lb.json +++ b/homeassistant/components/neato/translations/lb.json @@ -2,7 +2,10 @@ "config": { "abort": { "already_configured": "Apparat ass scho konfigur\u00e9iert", - "invalid_auth": "Ong\u00eblteg Authentifikatioun" + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", + "reauth_successful": "Re-authentifikatioun war erfollegr\u00e4ich" }, "create_entry": { "default": "Kuckt [Neato Dokumentatioun]({docs_url})." @@ -12,6 +15,12 @@ "unknown": "Onerwaarte Feeler" }, "step": { + "pick_implementation": { + "title": "Authentifikatiouns Method auswielen" + }, + "reauth_confirm": { + "title": "Soll den Ariichtungs Prozess gestart ginn?" + }, "user": { "data": { "password": "Passwuert", @@ -22,5 +31,6 @@ "title": "Neato Kont Informatiounen" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/pt.json b/homeassistant/components/neato/translations/pt.json index 0672c9af33f7d2..48e73c763f0034 100644 --- a/homeassistant/components/neato/translations/pt.json +++ b/homeassistant/components/neato/translations/pt.json @@ -2,13 +2,26 @@ "config": { "abort": { "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", - "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" + "authorize_url_timeout": "Tempo excedido a gerar um URL de autoriza\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", + "no_url_available": "Nenhum URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a sec\u00e7\u00e3o de ajuda]({docs_url})", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" + }, + "create_entry": { + "default": "Autenticado com sucesso" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" }, "step": { + "pick_implementation": { + "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" + }, + "reauth_confirm": { + "title": "Quer dar inicio \u00e0 configura\u00e7\u00e3o?" + }, "user": { "data": { "password": "Palavra-passe", diff --git a/homeassistant/components/neato/translations/tr.json b/homeassistant/components/neato/translations/tr.json new file mode 100644 index 00000000000000..53a8e0503cb460 --- /dev/null +++ b/homeassistant/components/neato/translations/tr.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7in" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "Neato Hesap Bilgisi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/uk.json b/homeassistant/components/neato/translations/uk.json new file mode 100644 index 00000000000000..58b56a52f6c4e3 --- /dev/null +++ b/homeassistant/components/neato/translations/uk.json @@ -0,0 +1,37 @@ +{ + "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.", + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\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.", + "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" + }, + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "reauth_confirm": { + "title": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "vendor": "\u0412\u0438\u0440\u043e\u0431\u043d\u0438\u043a" + }, + "description": "\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 \u0440\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u0438\u0445 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c.", + "title": "Neato" + } + } + }, + "title": "Neato Botvac" +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/de.json b/homeassistant/components/nest/translations/de.json index 2bc328ff8f6659..3925b7537b220a 100644 --- a/homeassistant/components/nest/translations/de.json +++ b/homeassistant/components/nest/translations/de.json @@ -2,34 +2,43 @@ "config": { "abort": { "authorize_url_fail": "Unbekannter Fehler beim Erstellen der Authorisierungs-URL", - "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL", - "reauth_successful": "Neuathentifizierung erfolgreich", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, "error": { "internal_error": "Ein interner Fehler ist aufgetreten", "invalid_pin": "Ung\u00fcltiger PIN-Code", "timeout": "Ein zeit\u00fcberschreitungs Fehler ist aufgetreten", - "unknown": "Ein unbekannter Fehler ist aufgetreten" + "unknown": "Unerwarteter Fehler" }, "step": { "init": { "data": { "flow_impl": "Anbieter" }, - "description": "W\u00e4hlen, \u00fcber welchen Authentifizierungsanbieter du dich bei Nest authentifizieren m\u00f6chtest.", + "description": "W\u00e4hle die Authentifizierungsmethode", "title": "Authentifizierungsanbieter" }, "link": { "data": { - "code": "PIN Code" + "code": "PIN-Code" }, "description": "[Autorisiere dein Konto] ( {url} ), um deinen Nest-Account zu verkn\u00fcpfen.\n\n F\u00fcge anschlie\u00dfend den erhaltenen PIN Code hier ein.", "title": "Nest-Konto verkn\u00fcpfen" }, + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + }, "reauth_confirm": { "description": "Die Nest-Integration muss das Konto neu authentifizieren", - "title": "Integration neu authentifizieren" + "title": "Integration erneut authentifizieren" } } }, diff --git a/homeassistant/components/nest/translations/fr.json b/homeassistant/components/nest/translations/fr.json index be006913f652b9..03b55458e9bdc3 100644 --- a/homeassistant/components/nest/translations/fr.json +++ b/homeassistant/components/nest/translations/fr.json @@ -5,7 +5,8 @@ "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", - "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "create_entry": { "default": "Authentification r\u00e9ussie" diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 958eaea039a4cd..376437d20f0e9b 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -5,7 +5,7 @@ "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", - "reauth_successful": "Riautenticato con successo", + "reauth_successful": "La riautenticazione ha avuto successo", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "unknown_authorize_url_generation": "Errore sconosciuto durante la generazione di un URL di autorizzazione." }, @@ -38,7 +38,7 @@ }, "reauth_confirm": { "description": "L'integrazione di Nest deve autenticare nuovamente il tuo account", - "title": "Autentica nuovamente l'integrazione" + "title": "Reautenticare l'integrazione" } } }, diff --git a/homeassistant/components/nest/translations/lb.json b/homeassistant/components/nest/translations/lb.json index 1f0115a429b8bd..612d1f302589d6 100644 --- a/homeassistant/components/nest/translations/lb.json +++ b/homeassistant/components/nest/translations/lb.json @@ -4,7 +4,12 @@ "authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + "reauth_successful": "Re-authentifikatioun war erfollegr\u00e4ich", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech.", + "unknown_authorize_url_generation": "Onbekannte Feeler beim erstellen vun der Authorisatiouns URL." + }, + "create_entry": { + "default": "Erfollegr\u00e4ich authentifiz\u00e9iert" }, "error": { "internal_error": "Interne Feeler beim valid\u00e9ieren vum Code", diff --git a/homeassistant/components/nest/translations/pl.json b/homeassistant/components/nest/translations/pl.json index 63e45df12fa994..d1147e03afc316 100644 --- a/homeassistant/components/nest/translations/pl.json +++ b/homeassistant/components/nest/translations/pl.json @@ -5,6 +5,7 @@ "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", "no_url_available": "Brak dost\u0119pnego adresu URL. Aby uzyska\u0107 informacje na temat tego b\u0142\u0119du, [sprawd\u017a sekcj\u0119 pomocy] ({docs_url})", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "unknown_authorize_url_generation": "Nieznany b\u0142\u0105d podczas generowania URL autoryzacji" }, @@ -34,6 +35,10 @@ }, "pick_implementation": { "title": "Wybierz metod\u0119 uwierzytelniania" + }, + "reauth_confirm": { + "description": "Integracja Nest wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" } } }, diff --git a/homeassistant/components/nest/translations/pt.json b/homeassistant/components/nest/translations/pt.json index 6da647ac29bfef..33ff857af7e6c8 100644 --- a/homeassistant/components/nest/translations/pt.json +++ b/homeassistant/components/nest/translations/pt.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_fail": "Erro desconhecido ao gerar um URL de autoriza\u00e7\u00e3o.", - "authorize_url_timeout": "Limite temporal ultrapassado ao gerar um URL de autoriza\u00e7\u00e3o.", + "authorize_url_timeout": "Tempo excedido a gerar um URL de autoriza\u00e7\u00e3o", "missing_configuration": "O componente n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o.", "no_url_available": "Nenhum URL dispon\u00edvel. Para obter informa\u00e7\u00f5es sobre esse erro, [verifique a sec\u00e7\u00e3o de ajuda]({docs_url})", "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel.", @@ -36,5 +36,10 @@ "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "Movimento detectado" + } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/tr.json b/homeassistant/components/nest/translations/tr.json index 484cdaff6ec4a2..003c1ccc0c22d2 100644 --- a/homeassistant/components/nest/translations/tr.json +++ b/homeassistant/components/nest/translations/tr.json @@ -1,9 +1,20 @@ { + "config": { + "abort": { + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "error": { + "unknown": "Beklenmeyen hata" + } + }, "device_automation": { "trigger_type": { "camera_motion": "Hareket alg\u0131land\u0131", "camera_person": "Ki\u015fi alg\u0131land\u0131", - "camera_sound": "Ses alg\u0131land\u0131" + "camera_sound": "Ses alg\u0131land\u0131", + "doorbell_chime": "Kap\u0131 zili bas\u0131ld\u0131" } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/uk.json b/homeassistant/components/nest/translations/uk.json new file mode 100644 index 00000000000000..f2869a76f4258a --- /dev/null +++ b/homeassistant/components/nest/translations/uk.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "authorize_url_fail": "\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.", + "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.", + "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.", + "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": { + "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." + }, + "error": { + "internal_error": "\u0412\u043d\u0443\u0442\u0440\u0456\u0448\u043d\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u043a\u043e\u0434\u0443.", + "invalid_pin": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 PIN-\u043a\u043e\u0434.", + "timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u043a\u043e\u0434\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "init": { + "data": { + "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457", + "title": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "link": { + "data": { + "code": "PIN-\u043a\u043e\u0434" + }, + "description": "[\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c]({url}), \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0441\u0432\u043e\u044e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 Nest. \n \n\u041f\u0456\u0441\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457 \u0441\u043a\u043e\u043f\u0456\u044e\u0439\u0442\u0435 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 PIN-\u043a\u043e\u0434.", + "title": "\u041f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 Nest" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "reauth_confirm": { + "description": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Nest", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + } + } + }, + "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_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" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/de.json b/homeassistant/components/netatmo/translations/de.json index 30cfba6dfed9a3..0be425d1e31cf9 100644 --- a/homeassistant/components/netatmo/translations/de.json +++ b/homeassistant/components/netatmo/translations/de.json @@ -1,8 +1,10 @@ { "config": { "abort": { - "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Autorisierungs-URL.", - "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "create_entry": { "default": "Erfolgreich authentifiziert." diff --git a/homeassistant/components/netatmo/translations/tr.json b/homeassistant/components/netatmo/translations/tr.json new file mode 100644 index 00000000000000..94dd5b3fb0f4c6 --- /dev/null +++ b/homeassistant/components/netatmo/translations/tr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "Alan\u0131n ad\u0131", + "lat_ne": "Enlem Kuzey-Do\u011fu k\u00f6\u015fesi", + "lat_sw": "Enlem G\u00fcney-Bat\u0131 k\u00f6\u015fesi", + "lon_ne": "Boylam Kuzey-Do\u011fu k\u00f6\u015fesi", + "lon_sw": "Boylam G\u00fcney-Bat\u0131 k\u00f6\u015fesi", + "mode": "Hesaplama", + "show_on_map": "Haritada g\u00f6ster" + }, + "description": "Bir alan i\u00e7in genel hava durumu sens\u00f6r\u00fc yap\u0131land\u0131r\u0131n.", + "title": "Netatmo genel hava durumu sens\u00f6r\u00fc" + }, + "public_weather_areas": { + "data": { + "new_area": "Alan ad\u0131", + "weather_areas": "Hava alanlar\u0131" + }, + "description": "Genel hava durumu sens\u00f6rlerini yap\u0131land\u0131r\u0131n.", + "title": "Netatmo genel hava durumu sens\u00f6r\u00fc" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/uk.json b/homeassistant/components/netatmo/translations/uk.json new file mode 100644 index 00000000000000..b8c439edfde4fc --- /dev/null +++ b/homeassistant/components/netatmo/translations/uk.json @@ -0,0 +1,43 @@ +{ + "config": { + "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.", + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "\u041d\u0430\u0437\u0432\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0456", + "lat_ne": "\u0428\u0438\u0440\u043e\u0442\u0430 (\u043f\u0456\u0432\u043d\u0456\u0447\u043d\u043e-\u0441\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u0443\u0442)", + "lat_sw": "\u0428\u0438\u0440\u043e\u0442\u0430 (\u044e\u0433\u043e-\u0437\u0430\u043f\u0430\u0434\u043d\u044b\u0439 \u0443\u0433\u043e\u043b)", + "lon_ne": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430 (\u043f\u0456\u0432\u043d\u0456\u0447\u043d\u043e-\u0441\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u0443\u0442)", + "lon_sw": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430 (\u043f\u0456\u0432\u0434\u0435\u043d\u043d\u043e-\u0437\u0430\u0445\u0456\u0434\u043d\u0438\u0439 \u043a\u0443\u0442)", + "mode": "\u0420\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043e\u043a", + "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0430 \u043c\u0430\u043f\u0456" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u0438 \u0434\u043b\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u0456", + "title": "\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u0438 Netatmo" + }, + "public_weather_areas": { + "data": { + "new_area": "\u041d\u0430\u0437\u0432\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0456", + "weather_areas": "\u041f\u043e\u0433\u043e\u0434\u043d\u0456 \u043e\u0431\u043b\u0430\u0441\u0442\u0456" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u0456\u0432 \u043f\u043e\u0433\u043e\u0434\u0438", + "title": "\u0417\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a \u043f\u043e\u0433\u043e\u0434\u0438 Netatmo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/de.json b/homeassistant/components/nexia/translations/de.json index 0ff4da3b2e1dd8..f2220f828e833b 100644 --- a/homeassistant/components/nexia/translations/de.json +++ b/homeassistant/components/nexia/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieses Nexia Home ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/nexia/translations/tr.json b/homeassistant/components/nexia/translations/tr.json new file mode 100644 index 00000000000000..47f3d931c46fb8 --- /dev/null +++ b/homeassistant/components/nexia/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "title": "Mynexia.com'a ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/uk.json b/homeassistant/components/nexia/translations/uk.json new file mode 100644 index 00000000000000..8cb2aec836a9e3 --- /dev/null +++ b/homeassistant/components/nexia/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/de.json b/homeassistant/components/nightscout/translations/de.json index 8581b04099d695..510d57ce45f4c4 100644 --- a/homeassistant/components/nightscout/translations/de.json +++ b/homeassistant/components/nightscout/translations/de.json @@ -1,12 +1,18 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API-Schl\u00fcssel", "url": "URL" } } diff --git a/homeassistant/components/nightscout/translations/no.json b/homeassistant/components/nightscout/translations/no.json index db7b8f811cae88..d68fe45c684137 100644 --- a/homeassistant/components/nightscout/translations/no.json +++ b/homeassistant/components/nightscout/translations/no.json @@ -15,7 +15,7 @@ "api_key": "API-n\u00f8kkel", "url": "URL" }, - "description": "- URL: adressen til din nattscout-forekomst. Dvs: https://myhomeassistant.duckdns.org:5423 \n - API-n\u00f8kkel (valgfritt): Bruk bare hvis forekomsten din er beskyttet (auth_default_roles! = Lesbar).", + "description": "- URL: Adressen til din nattscout-forekomst. F. Eks: https://myhomeassistant.duckdns.org:5423 \n- API-n\u00f8kkel (valgfritt): Bruk bare hvis forekomsten din er beskyttet (auth_default_roles! = readable).", "title": "Skriv inn informasjon om Nightscout-serveren." } } diff --git a/homeassistant/components/nightscout/translations/tr.json b/homeassistant/components/nightscout/translations/tr.json index 585aace899de32..95f36a4d124aa0 100644 --- a/homeassistant/components/nightscout/translations/tr.json +++ b/homeassistant/components/nightscout/translations/tr.json @@ -1,11 +1,18 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, "error": { - "cannot_connect": "Ba\u011flan\u0131lamad\u0131" + "cannot_connect": "Ba\u011flan\u0131lamad\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" }, + "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API Anahtar\u0131", "url": "URL" } } diff --git a/homeassistant/components/nightscout/translations/uk.json b/homeassistant/components/nightscout/translations/uk.json new file mode 100644 index 00000000000000..6504b00eb883e0 --- /dev/null +++ b/homeassistant/components/nightscout/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "- URL: \u0430\u0434\u0440\u0435\u0441\u0430 \u0412\u0430\u0448\u043e\u0433\u043e Nightscout. \u041d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: https://myhomeassistant.duckdns.org:5423\n - \u041a\u043b\u044e\u0447 API (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e): \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435, \u043b\u0438\u0448\u0435 \u044f\u043a\u0449\u043e \u0412\u0430\u0448 Nightcout \u0437\u0430\u0445\u0438\u0449\u0435\u043d\u0438\u0439 (auth_default_roles != readable).", + "title": "Nightscout" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notify/translations/uk.json b/homeassistant/components/notify/translations/uk.json index 86821a3e50f7f3..d87752255d58c1 100644 --- a/homeassistant/components/notify/translations/uk.json +++ b/homeassistant/components/notify/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u041f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f" + "title": "\u0421\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f" } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/de.json b/homeassistant/components/notion/translations/de.json index f322826c45b2ab..0b421911aa7dea 100644 --- a/homeassistant/components/notion/translations/de.json +++ b/homeassistant/components/notion/translations/de.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieser Benutzername wird bereits benutzt." + "already_configured": "Konto wurde bereits konfiguriert" }, "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "no_devices": "Keine Ger\u00e4te im Konto gefunden" }, "step": { diff --git a/homeassistant/components/notion/translations/tr.json b/homeassistant/components/notion/translations/tr.json index 8966b79df1b5c4..f89e3fb75338dd 100644 --- a/homeassistant/components/notion/translations/tr.json +++ b/homeassistant/components/notion/translations/tr.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", "no_devices": "Hesapta cihaz bulunamad\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/uk.json b/homeassistant/components/notion/translations/uk.json new file mode 100644 index 00000000000000..6dc969c3609592 --- /dev/null +++ b/homeassistant/components/notion/translations/uk.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "no_devices": "\u041d\u0435\u043c\u0430\u0454 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Notion" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/de.json b/homeassistant/components/nuheat/translations/de.json index 52c30681efcad4..8599f7fe1b5a99 100644 --- a/homeassistant/components/nuheat/translations/de.json +++ b/homeassistant/components/nuheat/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Der Thermostat ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_thermostat": "Die Seriennummer des Thermostats ist ung\u00fcltig.", "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/nuheat/translations/tr.json b/homeassistant/components/nuheat/translations/tr.json new file mode 100644 index 00000000000000..5123f1c7d9af15 --- /dev/null +++ b/homeassistant/components/nuheat/translations/tr.json @@ -0,0 +1,22 @@ +{ + "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", + "invalid_thermostat": "Termostat seri numaras\u0131 ge\u00e7ersiz.", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "serial_number": "Termostat\u0131n seri numaras\u0131.", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/uk.json b/homeassistant/components/nuheat/translations/uk.json new file mode 100644 index 00000000000000..21be3968eb733d --- /dev/null +++ b/homeassistant/components/nuheat/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "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.", + "invalid_thermostat": "\u0421\u0435\u0440\u0456\u0439\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430 \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "serial_number": "\u0421\u0435\u0440\u0456\u0439\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0430\u0431\u043e ID \u0412\u0430\u0448\u043e\u0433\u043e \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430, \u043d\u0430 \u0441\u0430\u0439\u0442\u0456 https://MyNuHeat.com.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/ca.json b/homeassistant/components/nuki/translations/ca.json new file mode 100644 index 00000000000000..a08308e78977f1 --- /dev/null +++ b/homeassistant/components/nuki/translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port", + "token": "Token d'acc\u00e9s" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/cs.json b/homeassistant/components/nuki/translations/cs.json new file mode 100644 index 00000000000000..349c92805cf8f1 --- /dev/null +++ b/homeassistant/components/nuki/translations/cs.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "port": "Port", + "token": "P\u0159\u00edstupov\u00fd token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/en.json b/homeassistant/components/nuki/translations/en.json new file mode 100644 index 00000000000000..135e8de2b2f6d8 --- /dev/null +++ b/homeassistant/components/nuki/translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Access Token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/et.json b/homeassistant/components/nuki/translations/et.json new file mode 100644 index 00000000000000..750afff003c24e --- /dev/null +++ b/homeassistant/components/nuki/translations/et.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Juurdep\u00e4\u00e4sut\u00f5end" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/it.json b/homeassistant/components/nuki/translations/it.json new file mode 100644 index 00000000000000..899093e1f4182f --- /dev/null +++ b/homeassistant/components/nuki/translations/it.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Porta", + "token": "Token di accesso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/no.json b/homeassistant/components/nuki/translations/no.json new file mode 100644 index 00000000000000..8cdbac230d735a --- /dev/null +++ b/homeassistant/components/nuki/translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "port": "Port", + "token": "Tilgangstoken" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/pl.json b/homeassistant/components/nuki/translations/pl.json new file mode 100644 index 00000000000000..77a7c31ee34e00 --- /dev/null +++ b/homeassistant/components/nuki/translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port", + "token": "Token dost\u0119pu" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/ru.json b/homeassistant/components/nuki/translations/ru.json new file mode 100644 index 00000000000000..bad9f35c076bbf --- /dev/null +++ b/homeassistant/components/nuki/translations/ru.json @@ -0,0 +1,18 @@ +{ + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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", + "port": "\u041f\u043e\u0440\u0442", + "token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/tr.json b/homeassistant/components/nuki/translations/tr.json new file mode 100644 index 00000000000000..ba6a496fa4c049 --- /dev/null +++ b/homeassistant/components/nuki/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port", + "token": "Eri\u015fim Belirteci" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/zh-Hant.json b/homeassistant/components/nuki/translations/zh-Hant.json new file mode 100644 index 00000000000000..662d7ed6ed95c5 --- /dev/null +++ b/homeassistant/components/nuki/translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0", + "token": "\u5b58\u53d6\u5bc6\u9470" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/ca.json b/homeassistant/components/number/translations/ca.json new file mode 100644 index 00000000000000..0058f01aac0f4f --- /dev/null +++ b/homeassistant/components/number/translations/ca.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Estableix el valor de {entity_name}" + } + }, + "title": "N\u00famero" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/cs.json b/homeassistant/components/number/translations/cs.json new file mode 100644 index 00000000000000..a6810f08c61416 --- /dev/null +++ b/homeassistant/components/number/translations/cs.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Nastavit hodnotu pro {entity_name}" + } + }, + "title": "\u010c\u00edslo" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/en.json b/homeassistant/components/number/translations/en.json new file mode 100644 index 00000000000000..4e3fe6536b3451 --- /dev/null +++ b/homeassistant/components/number/translations/en.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Set value for {entity_name}" + } + }, + "title": "Number" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/et.json b/homeassistant/components/number/translations/et.json new file mode 100644 index 00000000000000..36958c0fc77c4a --- /dev/null +++ b/homeassistant/components/number/translations/et.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Olemi {entity_name} v\u00e4\u00e4rtuse m\u00e4\u00e4ramine" + } + }, + "title": "Number" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/it.json b/homeassistant/components/number/translations/it.json new file mode 100644 index 00000000000000..135467cea9bf9f --- /dev/null +++ b/homeassistant/components/number/translations/it.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Imposta il valore per {entity_name}" + } + }, + "title": "Numero" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/no.json b/homeassistant/components/number/translations/no.json new file mode 100644 index 00000000000000..ad82c4ac6d1119 --- /dev/null +++ b/homeassistant/components/number/translations/no.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Angi verdi for {entity_name}" + } + }, + "title": "Nummer" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/pl.json b/homeassistant/components/number/translations/pl.json new file mode 100644 index 00000000000000..93d5dd045990f1 --- /dev/null +++ b/homeassistant/components/number/translations/pl.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "ustaw warto\u015b\u0107 dla {entity_name}" + } + }, + "title": "Number" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/ru.json b/homeassistant/components/number/translations/ru.json new file mode 100644 index 00000000000000..5e250b4e2db8c6 --- /dev/null +++ b/homeassistant/components/number/translations/ru.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043b\u044f {entity_name}" + } + }, + "title": "\u0427\u0438\u0441\u043b\u043e" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/tr.json b/homeassistant/components/number/translations/tr.json new file mode 100644 index 00000000000000..dfdbd90531752a --- /dev/null +++ b/homeassistant/components/number/translations/tr.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name} i\u00e7in de\u011fer ayarlay\u0131n" + } + }, + "title": "Numara" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/zh-Hant.json b/homeassistant/components/number/translations/zh-Hant.json new file mode 100644 index 00000000000000..d36f751682d7ab --- /dev/null +++ b/homeassistant/components/number/translations/zh-Hant.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name} \u8a2d\u5b9a\u503c" + } + }, + "title": "\u865f\u78bc" +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/de.json b/homeassistant/components/nut/translations/de.json index 793ab5bfa7c102..50d37fa8ec4bc8 100644 --- a/homeassistant/components/nut/translations/de.json +++ b/homeassistant/components/nut/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/nut/translations/tr.json b/homeassistant/components/nut/translations/tr.json new file mode 100644 index 00000000000000..b383d7656191ff --- /dev/null +++ b/homeassistant/components/nut/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "resources": { + "data": { + "resources": "Kaynaklar" + } + }, + "ups": { + "data": { + "alias": "Takma ad" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "resources": "Kaynaklar" + }, + "description": "Sens\u00f6r Kaynaklar\u0131'n\u0131 se\u00e7in." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/uk.json b/homeassistant/components/nut/translations/uk.json new file mode 100644 index 00000000000000..b25fe854560190 --- /dev/null +++ b/homeassistant/components/nut/translations/uk.json @@ -0,0 +1,46 @@ +{ + "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." + }, + "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": { + "resources": { + "data": { + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u0438 \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + }, + "ups": { + "data": { + "alias": "\u041f\u0441\u0435\u0432\u0434\u043e\u043d\u0456\u043c", + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c UPS \u0434\u043b\u044f \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433\u0443" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 NUT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "resources": "\u0420\u0435\u0441\u0443\u0440\u0441\u0438", + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u0438 \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/de.json b/homeassistant/components/nws/translations/de.json index 1461d86b2e5665..3d409bf885b4b3 100644 --- a/homeassistant/components/nws/translations/de.json +++ b/homeassistant/components/nws/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/nws/translations/tr.json b/homeassistant/components/nws/translations/tr.json new file mode 100644 index 00000000000000..8f51593aedbacf --- /dev/null +++ b/homeassistant/components/nws/translations/tr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + }, + "description": "Bir METAR istasyon kodu belirtilmezse, en yak\u0131n istasyonu bulmak i\u00e7in enlem ve boylam kullan\u0131lacakt\u0131r. \u015eimdilik bir API Anahtar\u0131 herhangi bir \u015fey olabilir. Ge\u00e7erli bir e-posta adresi kullanman\u0131z tavsiye edilir." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/uk.json b/homeassistant/components/nws/translations/uk.json new file mode 100644 index 00000000000000..1e6886540ae897 --- /dev/null +++ b/homeassistant/components/nws/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "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": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "station": "\u041a\u043e\u0434 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 METAR" + }, + "description": "\u042f\u043a\u0449\u043e \u043a\u043e\u0434 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 METAR \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u043e, \u0434\u043b\u044f \u043f\u043e\u0448\u0443\u043a\u0443 \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457 \u0441\u0442\u0430\u043d\u0446\u0456\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0448\u0438\u0440\u043e\u0442\u0430 \u0456 \u0434\u043e\u0432\u0433\u043e\u0442\u0430. \u041d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043b\u044e\u0447 API \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u043c. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0454\u0442\u044c\u0441\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u0456\u044e\u0447\u0443 \u0430\u0434\u0440\u0435\u0441\u0443 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438.", + "title": "National Weather Service" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/de.json b/homeassistant/components/nzbget/translations/de.json index 018f3870c58693..529eff3d9a2a53 100644 --- a/homeassistant/components/nzbget/translations/de.json +++ b/homeassistant/components/nzbget/translations/de.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "unknown": "Unerwarteter Fehler" }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "flow_title": "NZBGet: {name}", "step": { "user": { @@ -11,7 +15,9 @@ "name": "Name", "password": "Passwort", "port": "Port", - "username": "Benutzername" + "ssl": "Nutzt ein SSL-Zertifikat", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat verfizieren" }, "title": "Mit NZBGet verbinden" } diff --git a/homeassistant/components/nzbget/translations/tr.json b/homeassistant/components/nzbget/translations/tr.json new file mode 100644 index 00000000000000..63b6c489018479 --- /dev/null +++ b/homeassistant/components/nzbget/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "G\u00fcncelle\u015ftirme s\u0131kl\u0131\u011f\u0131 (saniye)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/uk.json b/homeassistant/components/nzbget/translations/uk.json new file mode 100644 index 00000000000000..eba15cca19c175 --- /dev/null +++ b/homeassistant/components/nzbget/translations/uk.json @@ -0,0 +1,35 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "NZBGet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/de.json b/homeassistant/components/omnilogic/translations/de.json index 382156757010d5..4378d39912d90c 100644 --- a/homeassistant/components/omnilogic/translations/de.json +++ b/homeassistant/components/omnilogic/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/omnilogic/translations/tr.json b/homeassistant/components/omnilogic/translations/tr.json new file mode 100644 index 00000000000000..ab93b71de844f9 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/uk.json b/homeassistant/components/omnilogic/translations/uk.json new file mode 100644 index 00000000000000..21ebf6f4fafa26 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/uk.json @@ -0,0 +1,29 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onboarding/translations/uk.json b/homeassistant/components/onboarding/translations/uk.json new file mode 100644 index 00000000000000..595726cbd34bb8 --- /dev/null +++ b/homeassistant/components/onboarding/translations/uk.json @@ -0,0 +1,7 @@ +{ + "area": { + "bedroom": "\u0421\u043f\u0430\u043b\u044c\u043d\u044f", + "kitchen": "\u041a\u0443\u0445\u043d\u044f", + "living_room": "\u0412\u0456\u0442\u0430\u043b\u044c\u043d\u044f" + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/ca.json b/homeassistant/components/ondilo_ico/translations/ca.json new file mode 100644 index 00000000000000..77453bda398928 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/ca.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/cs.json b/homeassistant/components/ondilo_ico/translations/cs.json new file mode 100644 index 00000000000000..bcb8849839caab --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/cs.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace." + }, + "create_entry": { + "default": "\u00dasp\u011b\u0161n\u011b ov\u011b\u0159eno" + }, + "step": { + "pick_implementation": { + "title": "Vyberte metodu ov\u011b\u0159en\u00ed" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/de.json b/homeassistant/components/ondilo_ico/translations/de.json new file mode 100644 index 00000000000000..5bab6ed132bf52 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/en.json b/homeassistant/components/ondilo_ico/translations/en.json index e3849fc17a3aab..c88a152ef81fcf 100644 --- a/homeassistant/components/ondilo_ico/translations/en.json +++ b/homeassistant/components/ondilo_ico/translations/en.json @@ -12,5 +12,6 @@ "title": "Pick Authentication Method" } } - } + }, + "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/es.json b/homeassistant/components/ondilo_ico/translations/es.json new file mode 100644 index 00000000000000..2394c610796eaa --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/es.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n." + }, + "create_entry": { + "default": "Autenticado correctamente" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/et.json b/homeassistant/components/ondilo_ico/translations/et.json new file mode 100644 index 00000000000000..132e9849cf104f --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/et.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/it.json b/homeassistant/components/ondilo_ico/translations/it.json new file mode 100644 index 00000000000000..cd75684a4372ab --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/it.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/lb.json b/homeassistant/components/ondilo_ico/translations/lb.json new file mode 100644 index 00000000000000..d9a5cc7482a6ce --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/lb.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", + "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun." + }, + "create_entry": { + "default": "Erfollegr\u00e4ich authentifiz\u00e9iert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/no.json b/homeassistant/components/ondilo_ico/translations/no.json new file mode 100644 index 00000000000000..4a06b93d045de5 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/no.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen" + }, + "create_entry": { + "default": "Vellykket godkjenning" + }, + "step": { + "pick_implementation": { + "title": "Velg godkjenningsmetode" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/pl.json b/homeassistant/components/ondilo_ico/translations/pl.json new file mode 100644 index 00000000000000..f3aa08a250f83e --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/pl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." + }, + "create_entry": { + "default": "Pomy\u015blnie uwierzytelniono" + }, + "step": { + "pick_implementation": { + "title": "Wybierz metod\u0119 uwierzytelniania" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/ru.json b/homeassistant/components/ondilo_ico/translations/ru.json new file mode 100644 index 00000000000000..56bb2d342b7576 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/ru.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438." + }, + "create_entry": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/tr.json b/homeassistant/components/ondilo_ico/translations/tr.json new file mode 100644 index 00000000000000..9672275736570a --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "pick_implementation": { + "title": "Kimlik Do\u011frulama Y\u00f6ntemini Se\u00e7in" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/uk.json b/homeassistant/components/ondilo_ico/translations/uk.json new file mode 100644 index 00000000000000..31e5834b027aa2 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/zh-Hant.json b/homeassistant/components/ondilo_ico/translations/zh-Hant.json new file mode 100644 index 00000000000000..ea1902b3295535 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/zh-Hant.json @@ -0,0 +1,17 @@ +{ + "config": { + "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" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/de.json b/homeassistant/components/onewire/translations/de.json index 3cc9f9cfc68f75..d3ed8137da3473 100644 --- a/homeassistant/components/onewire/translations/de.json +++ b/homeassistant/components/onewire/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_path": "Verzeichnis nicht gefunden." diff --git a/homeassistant/components/onewire/translations/tr.json b/homeassistant/components/onewire/translations/tr.json new file mode 100644 index 00000000000000..f59da2ab7e74d6 --- /dev/null +++ b/homeassistant/components/onewire/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_path": "Dizin bulunamad\u0131." + }, + "step": { + "owserver": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "user": { + "data": { + "type": "Ba\u011flant\u0131 t\u00fcr\u00fc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/uk.json b/homeassistant/components/onewire/translations/uk.json new file mode 100644 index 00000000000000..9c9705d2993ce0 --- /dev/null +++ b/homeassistant/components/onewire/translations/uk.json @@ -0,0 +1,26 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_path": "\u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + "step": { + "owserver": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e owserver" + }, + "user": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/de.json b/homeassistant/components/onvif/translations/de.json index 25984ecf3e17c6..5289f6479cc050 100644 --- a/homeassistant/components/onvif/translations/de.json +++ b/homeassistant/components/onvif/translations/de.json @@ -1,12 +1,15 @@ { "config": { "abort": { - "already_configured": "Das ONVIF-Ger\u00e4t ist bereits konfiguriert.", - "already_in_progress": "Der Konfigurationsfluss f\u00fcr das ONVIF-Ger\u00e4t wird bereits ausgef\u00fchrt.", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "no_h264": "Es waren keine H264-Streams verf\u00fcgbar. \u00dcberpr\u00fcfen Sie die Profilkonfiguration auf Ihrem Ger\u00e4t.", "no_mac": "Die eindeutige ID f\u00fcr das ONVIF-Ger\u00e4t konnte nicht konfiguriert werden.", "onvif_error": "Fehler beim Einrichten des ONVIF-Ger\u00e4ts. \u00dcberpr\u00fcfen Sie die Protokolle auf weitere Informationen." }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "auth": { "data": { diff --git a/homeassistant/components/onvif/translations/tr.json b/homeassistant/components/onvif/translations/tr.json index 4e3ad18a60d685..683dfbe7b92615 100644 --- a/homeassistant/components/onvif/translations/tr.json +++ b/homeassistant/components/onvif/translations/tr.json @@ -1,8 +1,42 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { + "auth": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "configure_profile": { + "data": { + "include": "Kamera varl\u0131\u011f\u0131 olu\u015ftur" + }, + "title": "Profilleri Yap\u0131land\u0131r" + }, + "device": { + "data": { + "host": "Ke\u015ffedilen ONVIF cihaz\u0131n\u0131 se\u00e7in" + }, + "title": "ONVIF cihaz\u0131n\u0131 se\u00e7in" + }, + "manual_input": { + "data": { + "host": "Ana Bilgisayar", + "name": "Ad", + "port": "Port" + }, + "title": "ONVIF cihaz\u0131n\u0131 yap\u0131land\u0131r\u0131n" + }, "user": { - "description": "G\u00f6nder d\u00fc\u011fmesine t\u0131klad\u0131\u011f\u0131n\u0131zda, Profil S'yi destekleyen ONVIF cihazlar\u0131 i\u00e7in a\u011f\u0131n\u0131zda arama yapaca\u011f\u0131z. \n\n Baz\u0131 \u00fcreticiler varsay\u0131lan olarak ONVIF'i devre d\u0131\u015f\u0131 b\u0131rakmaya ba\u015flad\u0131. L\u00fctfen kameran\u0131z\u0131n yap\u0131land\u0131rmas\u0131nda ONVIF'in etkinle\u015ftirildi\u011finden emin olun." + "description": "G\u00f6nder d\u00fc\u011fmesine t\u0131klad\u0131\u011f\u0131n\u0131zda, Profil S'yi destekleyen ONVIF cihazlar\u0131 i\u00e7in a\u011f\u0131n\u0131zda arama yapaca\u011f\u0131z. \n\n Baz\u0131 \u00fcreticiler varsay\u0131lan olarak ONVIF'i devre d\u0131\u015f\u0131 b\u0131rakmaya ba\u015flad\u0131. L\u00fctfen kameran\u0131z\u0131n yap\u0131land\u0131rmas\u0131nda ONVIF'in etkinle\u015ftirildi\u011finden emin olun.", + "title": "ONVIF cihaz kurulumu" } } }, diff --git a/homeassistant/components/onvif/translations/uk.json b/homeassistant/components/onvif/translations/uk.json new file mode 100644 index 00000000000000..82a816add044e3 --- /dev/null +++ b/homeassistant/components/onvif/translations/uk.json @@ -0,0 +1,59 @@ +{ + "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_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.", + "no_h264": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043f\u043e\u0442\u043e\u043a\u0456\u0432 H264. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043d\u0430 \u0441\u0432\u043e\u0454\u043c\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457.", + "no_mac": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", + "onvif_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "auth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u0423\u0432\u0456\u0439\u0442\u0438" + }, + "configure_profile": { + "data": { + "include": "\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043e\u0431'\u0454\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u0438" + }, + "description": "\u0421\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043e\u0431'\u0454\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u0438 \u0434\u043b\u044f {profile} \u0437 \u0440\u043e\u0437\u0434\u0456\u043b\u044c\u043d\u043e\u044e \u0437\u0434\u0430\u0442\u043d\u0456\u0441\u0442\u044e {resolution}?", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u0456\u0432" + }, + "device": { + "data": { + "host": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ONVIF" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ONVIF" + }, + "manual_input": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" + }, + "user": { + "description": "\u041a\u043e\u043b\u0438 \u0412\u0438 \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u041d\u0430\u0434\u0456\u0441\u043b\u0430\u0442\u0438, \u043f\u043e\u0447\u043d\u0435\u0442\u044c\u0441\u044f \u043f\u043e\u0448\u0443\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 ONVIF, \u044f\u043a\u0456 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u044e\u0442\u044c Profile S. \n\n\u0414\u0435\u044f\u043a\u0456 \u0432\u0438\u0440\u043e\u0431\u043d\u0438\u043a\u0438 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0437\u0430 \u0443\u043c\u043e\u0432\u0447\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u044c ONVIF. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e ONVIF \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u0445 \u0412\u0430\u0448\u043e\u0457 \u043a\u0430\u043c\u0435\u0440\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0438 FFMPEG", + "rtsp_transport": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u0438\u0439 \u043c\u0435\u0445\u0430\u043d\u0456\u0437\u043c RTSP" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/de.json b/homeassistant/components/opentherm_gw/translations/de.json index 6e8d02bc7923c4..36b76592945016 100644 --- a/homeassistant/components/opentherm_gw/translations/de.json +++ b/homeassistant/components/opentherm_gw/translations/de.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Gateway bereits konfiguriert", + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "id_exists": "Gateway-ID ist bereits vorhanden" }, diff --git a/homeassistant/components/opentherm_gw/translations/tr.json b/homeassistant/components/opentherm_gw/translations/tr.json new file mode 100644 index 00000000000000..507b71ede5b270 --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "init": { + "data": { + "device": "Yol veya URL" + }, + "title": "OpenTherm A\u011f Ge\u00e7idi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/uk.json b/homeassistant/components/opentherm_gw/translations/uk.json new file mode 100644 index 00000000000000..af7699271136b0 --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "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", + "id_exists": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0448\u043b\u044e\u0437\u0443 \u0432\u0436\u0435 \u0456\u0441\u043d\u0443\u0454." + }, + "step": { + "init": { + "data": { + "device": "\u0428\u043b\u044f\u0445 \u0430\u0431\u043e URL-\u0430\u0434\u0440\u0435\u0441\u0430", + "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "OpenTherm" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "floor_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043f\u0456\u0434\u043b\u043e\u0433\u0438", + "precision": "\u0422\u043e\u0447\u043d\u0456\u0441\u0442\u044c" + }, + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0443 Opentherm" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/de.json b/homeassistant/components/openuv/translations/de.json index fae3f0f062095b..88f9e69a5b6502 100644 --- a/homeassistant/components/openuv/translations/de.json +++ b/homeassistant/components/openuv/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten sind bereits registriert." + "already_configured": "Standort ist bereits konfiguriert" }, "error": { "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" diff --git a/homeassistant/components/openuv/translations/tr.json b/homeassistant/components/openuv/translations/tr.json new file mode 100644 index 00000000000000..241c588f691865 --- /dev/null +++ b/homeassistant/components/openuv/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/uk.json b/homeassistant/components/openuv/translations/uk.json index fef350a3f3cfd8..bd29fe692e1b3b 100644 --- a/homeassistant/components/openuv/translations/uk.json +++ b/homeassistant/components/openuv/translations/uk.json @@ -1,13 +1,20 @@ { "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + }, "step": { "user": { "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", "elevation": "\u0412\u0438\u0441\u043e\u0442\u0430", "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430" }, - "title": "\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e" + "title": "OpenUV" } } } diff --git a/homeassistant/components/openweathermap/translations/de.json b/homeassistant/components/openweathermap/translations/de.json index 239b47e2d3e0c1..cac601b71d32cd 100644 --- a/homeassistant/components/openweathermap/translations/de.json +++ b/homeassistant/components/openweathermap/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" }, "step": { "user": { diff --git a/homeassistant/components/openweathermap/translations/tr.json b/homeassistant/components/openweathermap/translations/tr.json new file mode 100644 index 00000000000000..0f845a4df73453 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/tr.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "latitude": "Enlem", + "longitude": "Boylam", + "mode": "Mod" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Mod" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/uk.json b/homeassistant/components/openweathermap/translations/uk.json new file mode 100644 index 00000000000000..7a39cfa078e829 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/uk.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "language": "\u041c\u043e\u0432\u0430", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 OpenWeatherMap. \u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043a\u043b\u044e\u0447\u0430 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 https://openweathermap.org/appid.", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "\u041c\u043e\u0432\u0430", + "mode": "\u0420\u0435\u0436\u0438\u043c" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/de.json b/homeassistant/components/ovo_energy/translations/de.json index 3bd083e48394bd..761f6a7d247561 100644 --- a/homeassistant/components/ovo_energy/translations/de.json +++ b/homeassistant/components/ovo_energy/translations/de.json @@ -1,8 +1,11 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { "data": { @@ -13,7 +16,8 @@ "data": { "password": "Passwort", "username": "Benutzername" - } + }, + "title": "Ovo Energy Account hinzuf\u00fcgen" } } } diff --git a/homeassistant/components/ovo_energy/translations/fr.json b/homeassistant/components/ovo_energy/translations/fr.json index 86719e87df4fce..351e20641aaff1 100644 --- a/homeassistant/components/ovo_energy/translations/fr.json +++ b/homeassistant/components/ovo_energy/translations/fr.json @@ -1,11 +1,16 @@ { "config": { "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification invalide" }, "step": { + "reauth": { + "data": { + "password": "Mot de passe" + } + }, "user": { "data": { "password": "Mot de passe", diff --git a/homeassistant/components/ovo_energy/translations/lb.json b/homeassistant/components/ovo_energy/translations/lb.json index 0e007924b6a3dc..b27b7d9702c357 100644 --- a/homeassistant/components/ovo_energy/translations/lb.json +++ b/homeassistant/components/ovo_energy/translations/lb.json @@ -6,6 +6,11 @@ "invalid_auth": "Ong\u00eblteg Authentifikatioun" }, "step": { + "reauth": { + "data": { + "password": "Passwuert" + } + }, "user": { "data": { "password": "Passwuert", diff --git a/homeassistant/components/ovo_energy/translations/tr.json b/homeassistant/components/ovo_energy/translations/tr.json index f3784f6de87fcd..714daac3253ccd 100644 --- a/homeassistant/components/ovo_energy/translations/tr.json +++ b/homeassistant/components/ovo_energy/translations/tr.json @@ -1,5 +1,10 @@ { "config": { + "error": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, "flow_title": "OVO Enerji: {username}", "step": { "reauth": { @@ -8,6 +13,12 @@ }, "description": "OVO Energy i\u00e7in kimlik do\u011frulama ba\u015far\u0131s\u0131z oldu. L\u00fctfen mevcut kimlik bilgilerinizi girin.", "title": "Yeniden kimlik do\u011frulama" + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } } } } diff --git a/homeassistant/components/ovo_energy/translations/uk.json b/homeassistant/components/ovo_energy/translations/uk.json new file mode 100644 index 00000000000000..8a5f8e2a8ba756 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "OVO Energy: {username}", + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0456 \u043f\u043e\u0442\u043e\u0447\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 OVO Energy.", + "title": "OVO Energy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/de.json b/homeassistant/components/owntracks/translations/de.json index 9d832cc264a040..0bc533c04690c5 100644 --- a/homeassistant/components/owntracks/translations/de.json +++ b/homeassistant/components/owntracks/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "create_entry": { "default": "\n\n\u00d6ffnen unter Android [die OwnTracks-App]({android_url}) und gehe zu {android_url} - > Verbindung. \u00c4nder die folgenden Einstellungen: \n - Modus: Privates HTTP \n - Host: {webhook_url} \n - Identifizierung: \n - Benutzername: `''` \n - Ger\u00e4te-ID: `''` \n\n\u00d6ffnen unter iOS [die OwnTracks-App]({ios_url}) und tippe auf das Symbol (i) oben links - > Einstellungen. \u00c4nder die folgenden Einstellungen: \n - Modus: HTTP \n - URL: {webhook_url} \n - Aktivieren Sie die Authentifizierung \n - UserID: `''`\n\n {secret} \n \n Weitere Informationen findest du in der [Dokumentation]({docs_url})." }, diff --git a/homeassistant/components/owntracks/translations/tr.json b/homeassistant/components/owntracks/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/owntracks/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/uk.json b/homeassistant/components/owntracks/translations/uk.json index f1f3186424297b..e6a6fc26068e90 100644 --- a/homeassistant/components/owntracks/translations/uk.json +++ b/homeassistant/components/owntracks/translations/uk.json @@ -1,5 +1,11 @@ { "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." + }, + "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." + }, "step": { "user": { "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 OwnTracks?", diff --git a/homeassistant/components/ozw/translations/ca.json b/homeassistant/components/ozw/translations/ca.json index 4553589e36d7e5..835c16eb449562 100644 --- a/homeassistant/components/ozw/translations/ca.json +++ b/homeassistant/components/ozw/translations/ca.json @@ -26,7 +26,7 @@ "data": { "use_addon": "Utilitza el complement OpenZWave Supervisor" }, - "description": "Voleu utilitzar el complement OpenZWave Supervisor?", + "description": "Vols utilitzar el complement Supervisor d'OpenZWave?", "title": "Selecciona el m\u00e8tode de connexi\u00f3" }, "start_addon": { diff --git a/homeassistant/components/ozw/translations/de.json b/homeassistant/components/ozw/translations/de.json index 70eaaaf18df410..afa26fb7e03040 100644 --- a/homeassistant/components/ozw/translations/de.json +++ b/homeassistant/components/ozw/translations/de.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", - "mqtt_required": "Die MQTT-Integration ist nicht eingerichtet" + "mqtt_required": "Die MQTT-Integration ist nicht eingerichtet", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "progress": { "install_addon": "Bitte warten, bis die Installation des OpenZWave-Add-Ons abgeschlossen ist. Dies kann einige Minuten dauern." @@ -14,6 +15,15 @@ }, "install_addon": { "title": "Die Installation des OpenZWave-Add-On wurde gestartet" + }, + "on_supervisor": { + "title": "Verbindungstyp ausw\u00e4hlen" + }, + "start_addon": { + "data": { + "network_key": "Netzwerk-Schl\u00fcssel", + "usb_path": "USB-Ger\u00e4te-Pfad" + } } } } diff --git a/homeassistant/components/ozw/translations/lb.json b/homeassistant/components/ozw/translations/lb.json index f97f026d38b92d..33de9a44953b76 100644 --- a/homeassistant/components/ozw/translations/lb.json +++ b/homeassistant/components/ozw/translations/lb.json @@ -1,8 +1,17 @@ { "config": { "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "already_in_progress": "Konfiguratioun's Oflaf ass schon am gaang", "mqtt_required": "MQTT Integratioun ass net ageriicht", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + }, + "step": { + "start_addon": { + "data": { + "network_key": "Netzwierk Schl\u00ebssel" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/no.json b/homeassistant/components/ozw/translations/no.json index 89563ff3533798..652e28fe3fcb4f 100644 --- a/homeassistant/components/ozw/translations/no.json +++ b/homeassistant/components/ozw/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "addon_info_failed": "Kunne ikke hente OpenZWave-tilleggsinfo", - "addon_install_failed": "Kunne ikke installere OpenZWave-tillegget", + "addon_info_failed": "Kunne ikke hente informasjon om OpenZWave-tillegg", + "addon_install_failed": "Kunne ikke installere OpenZWave-tillegg", "addon_set_config_failed": "Kunne ikke angi OpenZWave-konfigurasjon", "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", @@ -10,23 +10,23 @@ "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "addon_start_failed": "Kunne ikke starte OpenZWave-tillegget. Sjekk konfigurasjonen." + "addon_start_failed": "Kunne ikke starte OpenZWave-tillegg. Sjekk konfigurasjonen." }, "progress": { - "install_addon": "Vent mens OpenZWave-tilleggsinstallasjonen er ferdig. Dette kan ta flere minutter." + "install_addon": "Vent mens installasjonen av OpenZWave-tillegg er ferdig. Dette kan ta flere minutter." }, "step": { "hassio_confirm": { - "title": "Sett opp OpenZWave-integrasjon med OpenZWave-tillegget" + "title": "Sett opp OpenZWave-integrasjon med OpenZWave-tillegg" }, "install_addon": { - "title": "Installasjonen av tilleggsprogrammet OpenZWave har startet" + "title": "Installasjonen av OpenZWave-tillegg har startet" }, "on_supervisor": { "data": { - "use_addon": "Bruk OpenZWave Supervisor-tillegget" + "use_addon": "Bruk OpenZWave Supervisor-tillegg" }, - "description": "\u00d8nsker du \u00e5 bruke OpenZWave Supervisor-tillegget?", + "description": "\u00d8nsker du \u00e5 bruke OpenZWave Supervisor-tillegg?", "title": "Velg tilkoblingsmetode" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "Nettverksn\u00f8kkel", "usb_path": "USB enhetsbane" }, - "title": "Angi OpenZWave-tilleggskonfigurasjonen" + "title": "Angi konfigurasjon for OpenZWave-tillegg" } } } diff --git a/homeassistant/components/ozw/translations/tr.json b/homeassistant/components/ozw/translations/tr.json index d0a70d57752f47..99eda8b8311ddc 100644 --- a/homeassistant/components/ozw/translations/tr.json +++ b/homeassistant/components/ozw/translations/tr.json @@ -2,7 +2,12 @@ "config": { "abort": { "addon_info_failed": "OpenZWave eklenti bilgileri al\u0131namad\u0131.", - "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + "addon_install_failed": "OpenZWave eklentisi y\u00fcklenemedi.", + "addon_set_config_failed": "OpenZWave yap\u0131land\u0131rmas\u0131 ayarlanamad\u0131.", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "mqtt_required": "MQTT entegrasyonu kurulmam\u0131\u015f", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." }, "progress": { "install_addon": "OpenZWave eklenti kurulumu bitene kadar l\u00fctfen bekleyin. Bu birka\u00e7 dakika s\u00fcrebilir." @@ -10,6 +15,18 @@ "step": { "install_addon": { "title": "OpenZWave eklenti kurulumu ba\u015flad\u0131" + }, + "on_supervisor": { + "data": { + "use_addon": "OpenZWave Supervisor eklentisini kullan\u0131n" + }, + "description": "OpenZWave Supervisor eklentisini kullanmak istiyor musunuz?", + "title": "Ba\u011flant\u0131 y\u00f6ntemini se\u00e7in" + }, + "start_addon": { + "data": { + "network_key": "A\u011f Anahtar\u0131" + } } } } diff --git a/homeassistant/components/ozw/translations/uk.json b/homeassistant/components/ozw/translations/uk.json new file mode 100644 index 00000000000000..f8fb161aa1c4f6 --- /dev/null +++ b/homeassistant/components/ozw/translations/uk.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "addon_info_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f OpenZWave.", + "addon_install_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 OpenZWave.", + "addon_set_config_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e OpenZWave.", + "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." + }, + "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." + }, + "progress": { + "install_addon": "\u0417\u0430\u0447\u0435\u043a\u0430\u0439\u0442\u0435, \u043f\u043e\u043a\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f OpenZWave. \u0426\u0435 \u043c\u043e\u0436\u0435 \u0437\u0430\u0439\u043d\u044f\u0442\u0438 \u043a\u0456\u043b\u044c\u043a\u0430 \u0445\u0432\u0438\u043b\u0438\u043d." + }, + "step": { + "hassio_confirm": { + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f OpenZWave" + }, + "install_addon": { + "title": "\u0420\u043e\u0437\u043f\u043e\u0447\u0430\u0442\u043e \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0434\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f Open'Wave" + }, + "on_supervisor": { + "data": { + "use_addon": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Supervisor OpenZWave" + }, + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Supervisor OpenZWave?", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "start_addon": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u043c\u0435\u0440\u0435\u0436\u0456", + "usb_path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 Open'Wave" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/de.json b/homeassistant/components/panasonic_viera/translations/de.json index 4b2c14be9d6185..71090830714681 100644 --- a/homeassistant/components/panasonic_viera/translations/de.json +++ b/homeassistant/components/panasonic_viera/translations/de.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "Dieser Panasonic Viera TV ist bereits konfiguriert.", - "cannot_connect": "Verbindungsfehler", - "unknown": "Ein unbekannter Fehler ist aufgetreten. Weitere Informationen finden Sie in den Logs." + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler", - "invalid_pin_code": "Der von Ihnen eingegebene PIN-Code war ung\u00fcltig" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_pin_code": "Der eingegebene PIN-Code war ung\u00fcltig" }, "step": { "pairing": { "data": { - "pin": "PIN" + "pin": "PIN-Code" }, - "description": "Geben Sie die auf Ihrem Fernseher angezeigte PIN ein", + "description": "Gib den auf deinem TV angezeigten PIN-Code ein", "title": "Kopplung" }, "user": { @@ -22,7 +22,7 @@ "host": "IP-Adresse", "name": "Name" }, - "description": "Geben Sie die IP-Adresse Ihres Panasonic Viera TV ein", + "description": "Gib die IP-Adresse deines Panasonic Viera TV ein", "title": "Richten Sie Ihr Fernsehger\u00e4t ein" } } diff --git a/homeassistant/components/panasonic_viera/translations/tr.json b/homeassistant/components/panasonic_viera/translations/tr.json new file mode 100644 index 00000000000000..d0e573fdcf95e2 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/uk.json b/homeassistant/components/panasonic_viera/translations/uk.json new file mode 100644 index 00000000000000..9722b19ece9cb8 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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.", + "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" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_pin_code": "\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0439 PIN-\u043a\u043e\u0434 \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439." + }, + "step": { + "pairing": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c PIN-\u043a\u043e\u0434 , \u0449\u043e \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454\u0442\u044c\u0441\u044f \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 Panasonic Viera", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/person/translations/uk.json b/homeassistant/components/person/translations/uk.json index 0dba7914da07c8..5e6b186e38ccf7 100644 --- a/homeassistant/components/person/translations/uk.json +++ b/homeassistant/components/person/translations/uk.json @@ -2,7 +2,7 @@ "state": { "_": { "home": "\u0412\u0434\u043e\u043c\u0430", - "not_home": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0439" + "not_home": "\u041d\u0435 \u0432\u0434\u043e\u043c\u0430" } }, "title": "\u041b\u044e\u0434\u0438\u043d\u0430" diff --git a/homeassistant/components/pi_hole/translations/ca.json b/homeassistant/components/pi_hole/translations/ca.json index 37d4e890ef4a5e..eb15fa7bf97b8d 100644 --- a/homeassistant/components/pi_hole/translations/ca.json +++ b/homeassistant/components/pi_hole/translations/ca.json @@ -7,6 +7,11 @@ "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { + "api_key": { + "data": { + "api_key": "Clau API" + } + }, "user": { "data": { "api_key": "Clau API", @@ -15,6 +20,7 @@ "name": "Nom", "port": "Port", "ssl": "Utilitza un certificat SSL", + "statistics_only": "Nom\u00e9s les estad\u00edstiques", "verify_ssl": "Verifica el certificat SSL" } } diff --git a/homeassistant/components/pi_hole/translations/cs.json b/homeassistant/components/pi_hole/translations/cs.json index a9057ceabab9e4..fa90fbdb2a005f 100644 --- a/homeassistant/components/pi_hole/translations/cs.json +++ b/homeassistant/components/pi_hole/translations/cs.json @@ -7,6 +7,11 @@ "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, "step": { + "api_key": { + "data": { + "api_key": "Kl\u00ed\u010d API" + } + }, "user": { "data": { "api_key": "Kl\u00ed\u010d API", diff --git a/homeassistant/components/pi_hole/translations/de.json b/homeassistant/components/pi_hole/translations/de.json index f74c5acb6352f0..34198fcfebeaff 100644 --- a/homeassistant/components/pi_hole/translations/de.json +++ b/homeassistant/components/pi_hole/translations/de.json @@ -4,17 +4,17 @@ "already_configured": "Service ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung konnte nicht hergestellt werden" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { "data": { - "api_key": "API-Schl\u00fcssel (optional)", + "api_key": "API-Schl\u00fcssel", "host": "Host", "location": "Org", "name": "Name", "port": "Port", - "ssl": "SSL verwenden", + "ssl": "Nutzt ein SSL-Zertifikat", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } } diff --git a/homeassistant/components/pi_hole/translations/en.json b/homeassistant/components/pi_hole/translations/en.json index 858e7c230ac085..9053a70c18fe48 100644 --- a/homeassistant/components/pi_hole/translations/en.json +++ b/homeassistant/components/pi_hole/translations/en.json @@ -7,6 +7,11 @@ "cannot_connect": "Failed to connect" }, "step": { + "api_key": { + "data": { + "api_key": "API Key" + } + }, "user": { "data": { "api_key": "API Key", @@ -15,6 +20,7 @@ "name": "Name", "port": "Port", "ssl": "Uses an SSL certificate", + "statistics_only": "Statistics Only", "verify_ssl": "Verify SSL certificate" } } diff --git a/homeassistant/components/pi_hole/translations/es.json b/homeassistant/components/pi_hole/translations/es.json index 48708d6810421e..35597af49f291e 100644 --- a/homeassistant/components/pi_hole/translations/es.json +++ b/homeassistant/components/pi_hole/translations/es.json @@ -7,6 +7,11 @@ "cannot_connect": "No se pudo conectar" }, "step": { + "api_key": { + "data": { + "api_key": "Clave API" + } + }, "user": { "data": { "api_key": "Clave API", @@ -15,6 +20,7 @@ "name": "Nombre", "port": "Puerto", "ssl": "Usar SSL", + "statistics_only": "S\u00f3lo las estad\u00edsticas", "verify_ssl": "Verificar certificado SSL" } } diff --git a/homeassistant/components/pi_hole/translations/et.json b/homeassistant/components/pi_hole/translations/et.json index c68d52c0c107a3..4ff0fdd0ba850e 100644 --- a/homeassistant/components/pi_hole/translations/et.json +++ b/homeassistant/components/pi_hole/translations/et.json @@ -7,6 +7,11 @@ "cannot_connect": "\u00dchendamine nurjus" }, "step": { + "api_key": { + "data": { + "api_key": "API v\u00f5ti" + } + }, "user": { "data": { "api_key": "API v\u00f5ti", @@ -15,6 +20,7 @@ "name": "Nimi", "port": "", "ssl": "Kasuatb SSL serti", + "statistics_only": "Ainult statistika", "verify_ssl": "Kontrolli SSL sertifikaati" } } diff --git a/homeassistant/components/pi_hole/translations/it.json b/homeassistant/components/pi_hole/translations/it.json index 34590ee77bb14d..7d355caf985622 100644 --- a/homeassistant/components/pi_hole/translations/it.json +++ b/homeassistant/components/pi_hole/translations/it.json @@ -7,6 +7,11 @@ "cannot_connect": "Impossibile connettersi" }, "step": { + "api_key": { + "data": { + "api_key": "Chiave API" + } + }, "user": { "data": { "api_key": "Chiave API", @@ -15,6 +20,7 @@ "name": "Nome", "port": "Porta", "ssl": "Utilizza un certificato SSL", + "statistics_only": "Solo Statistiche", "verify_ssl": "Verificare il certificato SSL" } } diff --git a/homeassistant/components/pi_hole/translations/no.json b/homeassistant/components/pi_hole/translations/no.json index 71c815ecd36dd4..7d005fa65163bf 100644 --- a/homeassistant/components/pi_hole/translations/no.json +++ b/homeassistant/components/pi_hole/translations/no.json @@ -7,6 +7,11 @@ "cannot_connect": "Tilkobling mislyktes" }, "step": { + "api_key": { + "data": { + "api_key": "API-n\u00f8kkel" + } + }, "user": { "data": { "api_key": "API-n\u00f8kkel", @@ -15,6 +20,7 @@ "name": "Navn", "port": "Port", "ssl": "Bruker et SSL-sertifikat", + "statistics_only": "Bare statistikk", "verify_ssl": "Verifisere SSL-sertifikat" } } diff --git a/homeassistant/components/pi_hole/translations/pl.json b/homeassistant/components/pi_hole/translations/pl.json index add788ef916027..ee4b6eadd87a27 100644 --- a/homeassistant/components/pi_hole/translations/pl.json +++ b/homeassistant/components/pi_hole/translations/pl.json @@ -7,6 +7,11 @@ "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { + "api_key": { + "data": { + "api_key": "Klucz API" + } + }, "user": { "data": { "api_key": "Klucz API", @@ -15,6 +20,7 @@ "name": "Nazwa", "port": "Port", "ssl": "Certyfikat SSL", + "statistics_only": "Tylko statystyki", "verify_ssl": "Weryfikacja certyfikatu SSL" } } diff --git a/homeassistant/components/pi_hole/translations/ru.json b/homeassistant/components/pi_hole/translations/ru.json index eb3cfa62c623ec..eed9596c90746f 100644 --- a/homeassistant/components/pi_hole/translations/ru.json +++ b/homeassistant/components/pi_hole/translations/ru.json @@ -7,6 +7,11 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { + "api_key": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + } + }, "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", @@ -15,6 +20,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", + "statistics_only": "\u0422\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430", "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" } } diff --git a/homeassistant/components/pi_hole/translations/tr.json b/homeassistant/components/pi_hole/translations/tr.json new file mode 100644 index 00000000000000..a14e020d360277 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/tr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "api_key": { + "data": { + "api_key": "API Anahtar\u0131" + } + }, + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "host": "Ana Bilgisayar", + "location": "Konum", + "port": "Port", + "statistics_only": "Yaln\u0131zca \u0130statistikler" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/uk.json b/homeassistant/components/pi_hole/translations/uk.json new file mode 100644 index 00000000000000..93413f9abff057 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/uk.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "host": "\u0425\u043e\u0441\u0442", + "location": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/zh-Hant.json b/homeassistant/components/pi_hole/translations/zh-Hant.json index 1cea5a87f4b5f1..1527b48f580457 100644 --- a/homeassistant/components/pi_hole/translations/zh-Hant.json +++ b/homeassistant/components/pi_hole/translations/zh-Hant.json @@ -7,6 +7,11 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { + "api_key": { + "data": { + "api_key": "API \u5bc6\u9470" + } + }, "user": { "data": { "api_key": "API \u5bc6\u9470", @@ -15,6 +20,7 @@ "name": "\u540d\u7a31", "port": "\u901a\u8a0a\u57e0", "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", + "statistics_only": "\u50c5\u7d71\u8a08\u8cc7\u8a0a", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" } } diff --git a/homeassistant/components/plaato/translations/ca.json b/homeassistant/components/plaato/translations/ca.json index 1dbe125d50d376..c4669b219ab760 100644 --- a/homeassistant/components/plaato/translations/ca.json +++ b/homeassistant/components/plaato/translations/ca.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "El compte ja ha estat configurat", "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." }, "create_entry": { - "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Plaato Airlock.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." + "default": "El dispositiu Plaato {device_type} amb nom **{device_name}** s'ha configurat correctament!" + }, + "error": { + "invalid_webhook_device": "Has seleccionat un dispositiu que no admet l'enviament de dades a un webhook. Nom\u00e9s est\u00e0 disponible per a Airlock", + "no_api_method": "Has d'afegir un token d'autenticaci\u00f3 o seleccionar webhook", + "no_auth_token": "Has d'afegir un token d'autenticaci\u00f3" }, "step": { + "api_method": { + "data": { + "token": "Enganxa el token d'autenticaci\u00f3 aqu\u00ed", + "use_webhook": "Utilitza webhook" + }, + "description": "Per poder consultar l'API, cal un `auth_token` que es pot obtenir seguint aquestes [instruccions](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\n Dispositiu seleccionat: **{device_type}** \n\n Si prefereixes utilitzar el m\u00e8tode webhook integrat (nom\u00e9s per Airlock), marca la casella seg\u00fcent i deixa el token d'autenticaci\u00f3 en blanc", + "title": "Selecciona el m\u00e8tode API" + }, "user": { + "data": { + "device_name": "Posa un nom al dispositiu", + "device_type": "Tipus de dispositiu Plaato" + }, "description": "Vols comen\u00e7ar la configuraci\u00f3?", - "title": "Configuraci\u00f3 del Webhook de Plaato" + "title": "Configura dispositius Plaato" + }, + "webhook": { + "description": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar la opci\u00f3 webhook de Plaato Airlock.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls.", + "title": "Webhook a utilitzar" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Interval d'actualitzaci\u00f3 (minuts)" + }, + "description": "Estableix l'interval d'actualitzaci\u00f3 (minuts)", + "title": "Opcions de Plaato" + }, + "webhook": { + "description": "Informaci\u00f3 del webhook: \n\n - URL: `{webhook_url}`\n - M\u00e8tode: POST\n\n", + "title": "Opcions de Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index f97fe4875f7835..5171baab6544f0 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "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." + }, "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, "step": { "user": { - "description": "Soll Plaato Airlock wirklich eingerichtet werden?", + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Plaato Webhook einrichten" } } diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json index 75c7a2182ef739..ec7b7e4b1a409c 100644 --- a/homeassistant/components/plaato/translations/et.json +++ b/homeassistant/components/plaato/translations/et.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "Kasutaja on juba seadistatud", "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." }, "create_entry": { - "default": "S\u00fcndmuste saatmiseks Home Assistantile pead seadistama Plaatoo Airlock'i veebihaagi. \n\n Sisesta j\u00e4rgmine teave: \n\n - URL: \" {webhook_url} \" \n - Meetod: POST \n \n Lisateavet leiad [documentation] ( {docs_url} )." + "default": "{device_type} Plaato seade nimega **{device_name}** on edukalt seadistatud!" + }, + "error": { + "invalid_webhook_device": "Oled valinud seadme, mis ei toeta andmete saatmist veebihaagile. See on saadaval ainult Airlocki jaoks", + "no_api_method": "Pead lisama autentimisloa v\u00f5i valima veebihaagi", + "no_auth_token": "Pead lisama autentimisloa" }, "step": { + "api_method": { + "data": { + "token": "Aseta Auth Token siia", + "use_webhook": "Kasuta veebihaaki" + }, + "description": "API p\u00e4ringu esitamiseks on vajalik \"auth_token\", mille saad j\u00e4rgides [neid] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) juhiseid \n\n Valitud seade: ** {device_type} ** \n\n Kui kasutad pigem sisseehitatud veebihaagi meetodit (ainult Airlock), m\u00e4rgi palun allolev ruut ja j\u00e4ta Auth Token t\u00fchjaks", + "title": "Vali API meetod" + }, "user": { + "data": { + "device_name": "Pang oma seadmele nimi", + "device_type": "Plaato seadme t\u00fc\u00fcp" + }, "description": "Kas alustan seadistamist?", - "title": "Plaato Webhooki seadistamine" + "title": "Plaato seadmete h\u00e4\u00e4lestamine" + }, + "webhook": { + "description": "S\u00fcndmuste saatmiseks Home Assistanti pead seadistama Plaato Airlocki veebihaagi. \n\n Sisesta j\u00e4rgmine teave: \n\n - URL: \" {webhook_url} \"\n - Meetod: POST \n\n Lisateavet leiad [dokumentatsioonist] ( {docs_url} ).", + "title": "Kasutatav veebihaak" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "V\u00e4rskendamise intervall (minutites)" + }, + "description": "M\u00e4\u00e4ra v\u00e4rskendamise intervall (minutites)", + "title": "Plaato valikud" + }, + "webhook": { + "description": "Veebihaagi teave: \n\n - URL: `{webhook_url}`\n - Meetod: POST\n\n", + "title": "Plaato Airlocki valikud" } } } diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 1e2da1bfb12661..8873399aaa4282 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -1,16 +1,48 @@ { "config": { "abort": { + "already_configured": "Kontoen er allerede konfigurert", "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" }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." }, + "error": { + "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er kun tilgjengelig for Airlock", + "no_api_method": "Du m\u00e5 legge til et godkjenningstoken eller velge webhook", + "no_auth_token": "Du m\u00e5 legge til et godkjenningstoken" + }, "step": { + "api_method": { + "title": "Velg API-metode" + }, "user": { + "data": { + "device_name": "Navngi enheten din", + "device_type": "Type Platon-enhet" + }, "description": "Vil du starte oppsettet?", "title": "Sett opp Plaato Webhook" + }, + "webhook": { + "description": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer.", + "title": "Webhook \u00e5 bruke" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Oppdateringsintervall (minutter)" + }, + "description": "Still inn oppdateringsintervallet (minutter)", + "title": "Alternativer for Plaato" + }, + "webhook": { + "description": "Webhook info:\n\n- URL-adresse: {webhook_url}\n- Metode: POST\n\n", + "title": "Alternativer for Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index 1f7c8141aa5686..c849f574c9c5cb 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", "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" }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Plaato Airlock. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Tw\u00f3j {device_type} Plaato o nazwie *{device_name}* zosta\u0142o pomy\u015blnie skonfigurowane!" + }, + "error": { + "invalid_webhook_device": "Wybra\u0142e\u015b urz\u0105dzenie, kt\u00f3re nie obs\u0142uguje wysy\u0142ania danych do webhooka. Opcja dost\u0119pna tylko w areometrze Airlock", + "no_api_method": "Musisz doda\u0107 token uwierzytelniania lub wybra\u0107 webhook", + "no_auth_token": "Musisz doda\u0107 token autoryzacji" }, "step": { + "api_method": { + "data": { + "token": "Wklej token autoryzacji", + "use_webhook": "U\u017cyj webhook" + }, + "description": "Aby m\u00f3c przesy\u0142a\u0107 zapytania do API, wymagany jest \u201etoken autoryzacji\u201d, kt\u00f3ry mo\u017cna uzyska\u0107, post\u0119puj\u0105c zgodnie z [t\u0105] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrukcj\u0105\n\nWybrane urz\u0105dzenie: *{device_type}* \n\nJe\u015bli wolisz u\u017cywa\u0107 wbudowanej metody webhook (tylko areomierz Airlock), zaznacz poni\u017csze pole i pozostaw token autoryzacji pusty", + "title": "Wybierz metod\u0119 API" + }, "user": { + "data": { + "device_name": "Nazwij swoje urz\u0105dzenie", + "device_type": "Rodzaj urz\u0105dzenia Plaato" + }, "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?", - "title": "Konfiguracja Plaato Webhook" + "title": "Konfiguracja urz\u0105dze\u0144 Plaato" + }, + "webhook": { + "description": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Plaato Airlock. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y.", + "title": "Webhook do u\u017cycia" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (w minutach)" + }, + "description": "Ustaw cz\u0119stotliwo\u015bci aktualizacji (w minutach)", + "title": "Opcje dla Plaato" + }, + "webhook": { + "description": "Informacje o webhook: \n\n - URL: `{webhook_url}`\n - Metoda: POST \n\n", + "title": "Opcje dla areomierza Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/tr.json b/homeassistant/components/plaato/translations/tr.json new file mode 100644 index 00000000000000..1f21b08ec81157 --- /dev/null +++ b/homeassistant/components/plaato/translations/tr.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "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." + }, + "error": { + "no_auth_token": "Bir kimlik do\u011frulama jetonu eklemeniz gerekiyor" + }, + "step": { + "api_method": { + "data": { + "use_webhook": "Webhook kullan" + }, + "title": "API y\u00f6ntemini se\u00e7in" + }, + "user": { + "data": { + "device_name": "Cihaz\u0131n\u0131z\u0131 adland\u0131r\u0131n", + "device_type": "Plaato cihaz\u0131n\u0131n t\u00fcr\u00fc" + } + }, + "webhook": { + "title": "Webhook kullanmak i\u00e7in" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "G\u00fcncelle\u015ftirme aral\u0131\u011f\u0131 (dakika)" + }, + "description": "G\u00fcncelleme aral\u0131\u011f\u0131n\u0131 ayarlay\u0131n (dakika)", + "title": "Plaato i\u00e7in se\u00e7enekler" + }, + "webhook": { + "title": "Plaato Airlock i\u00e7in se\u00e7enekler" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/uk.json b/homeassistant/components/plaato/translations/uk.json new file mode 100644 index 00000000000000..a4f7de7c6be4ea --- /dev/null +++ b/homeassistant/components/plaato/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0432\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Plaato Airlock. \n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST \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." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "Plaato Airlock" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index aec745ea38bc4c..2890c5c31c694a 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "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" }, "create_entry": { - "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Plaato Airlock \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + "default": "\u540d\u7a31\u70ba **{device_name}** \u7684 Plaato {device_type} \u5df2\u6210\u529f\u8a2d\u5b9a\uff01" + }, + "error": { + "invalid_webhook_device": "\u6240\u9078\u64c7\u7684\u88dd\u7f6e\u4e0d\u652f\u63f4\u50b3\u9001\u8cc7\u6599\u81f3 Webhook\u3001AirLock \u50c5\u652f\u63f4\u6b64\u985e\u578b", + "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470\u6216\u9078\u64c7 Webhook", + "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470" }, "step": { + "api_method": { + "data": { + "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u5bc6\u9470", + "use_webhook": "\u4f7f\u7528 Webhook" + }, + "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u5bc6\u9470\u6b04\u4f4d\u7a7a\u767d", + "title": "\u9078\u64c7 API \u65b9\u5f0f" + }, "user": { + "data": { + "device_name": "\u88dd\u7f6e\u540d\u7a31", + "device_type": "Plaato \u88dd\u7f6e\u985e\u578b" + }, "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f", - "title": "\u8a2d\u5b9a Plaato Webhook" + "title": "\u8a2d\u5b9a Plaato \u88dd\u7f6e" + }, + "webhook": { + "description": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Plaato Airlock \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002", + "title": "\u4f7f\u7528\u4e4b Webhook" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "\u66f4\u65b0\u983b\u7387\uff08\u5206\uff09" + }, + "description": "\u8a2d\u5b9a\u66f4\u65b0\u983b\u7387\uff08\u5206\uff09", + "title": "Plaato \u9078\u9805" + }, + "webhook": { + "description": "Webhook \u8a0a\u606f\uff1a\n\n- URL\uff1a`{webhook_url}`\n- \u65b9\u5f0f\uff1aPOST\n\n", + "title": "Plaato Airlock \u9078\u9805" } } } diff --git a/homeassistant/components/plant/translations/uk.json b/homeassistant/components/plant/translations/uk.json index 3204c42a714b9d..25f24b43b80ff3 100644 --- a/homeassistant/components/plant/translations/uk.json +++ b/homeassistant/components/plant/translations/uk.json @@ -1,8 +1,8 @@ { "state": { "_": { - "ok": "\u0422\u0410\u041a", - "problem": "\u0425\u0430\u043b\u0435\u043f\u0430" + "ok": "\u041e\u041a", + "problem": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430" } }, "title": "\u0420\u043e\u0441\u043b\u0438\u043d\u0430" diff --git a/homeassistant/components/plex/translations/de.json b/homeassistant/components/plex/translations/de.json index 961ad4b3ed6e3e..2ba14e65f85f01 100644 --- a/homeassistant/components/plex/translations/de.json +++ b/homeassistant/components/plex/translations/de.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "Alle verkn\u00fcpften Server sind bereits konfiguriert", "already_configured": "Dieser Plex-Server ist bereits konfiguriert", - "already_in_progress": "Plex wird konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "token_request_timeout": "Zeit\u00fcberschreitung beim Erhalt des Tokens", - "unknown": "Aus unbekanntem Grund fehlgeschlagen" + "unknown": "Unerwarteter Fehler" }, "error": { "faulty_credentials": "Autorisierung fehlgeschlagen, Token \u00fcberpr\u00fcfen", diff --git a/homeassistant/components/plex/translations/tr.json b/homeassistant/components/plex/translations/tr.json new file mode 100644 index 00000000000000..93f8cc85eaeee9 --- /dev/null +++ b/homeassistant/components/plex/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Bu Plex sunucusu zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "unknown": "Beklenmeyen hata" + }, + "step": { + "manual_setup": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + }, + "user": { + "title": "Plex Medya Sunucusu" + }, + "user_advanced": { + "data": { + "setup_method": "Kurulum y\u00f6ntemi" + }, + "title": "Plex Medya Sunucusu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/uk.json b/homeassistant/components/plex/translations/uk.json new file mode 100644 index 00000000000000..20351cf735abfc --- /dev/null +++ b/homeassistant/components/plex/translations/uk.json @@ -0,0 +1,62 @@ +{ + "config": { + "abort": { + "all_configured": "\u0412\u0441\u0456 \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0456 \u0441\u0435\u0440\u0432\u0435\u0440\u0438 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0456.", + "already_configured": "\u0426\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439.", + "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.", + "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", + "token_request_timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0442\u043e\u043a\u0435\u043d\u0430.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "faulty_credentials": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0422\u043e\u043a\u0435\u043d.", + "host_or_token": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u043a\u0430\u0437\u0430\u0442\u0438 \u0425\u043e\u0441\u0442 \u0430\u0431\u043e \u0422\u043e\u043a\u0435\u043d.", + "no_servers": "\u041d\u0435\u043c\u0430\u0454 \u0441\u0435\u0440\u0432\u0435\u0440\u0456\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c.", + "not_found": "\u0421\u0435\u0440\u0432\u0435\u0440 Plex \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "ssl_error": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u043c." + }, + "flow_title": "{name} ({host})", + "step": { + "manual_setup": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "token": "\u0422\u043e\u043a\u0435\u043d (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "\u0420\u0443\u0447\u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Plex" + }, + "select_server": { + "data": { + "server": "\u0421\u0435\u0440\u0432\u0435\u0440" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u0438\u043d \u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u0456\u0432:", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Plex" + }, + "user": { + "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043d\u0430 [plex.tv](https://plex.tv), \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0434\u043e Home Assistant.", + "title": "Plex Media Server" + }, + "user_advanced": { + "data": { + "setup_method": "\u0421\u043f\u043e\u0441\u0456\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + }, + "title": "Plex Media Server" + } + } + }, + "options": { + "step": { + "plex_mp_settings": { + "data": { + "ignore_new_shared_users": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438 \u043d\u043e\u0432\u0438\u0445 \u043a\u0435\u0440\u043e\u0432\u0430\u043d\u0438\u0445 / \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u0438\u0445 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456\u0432", + "ignore_plex_web_clients": "\u0406\u0433\u043d\u043e\u0440\u0443\u0432\u0430\u0442\u0438 \u0432\u0435\u0431-\u043a\u043b\u0456\u0454\u043d\u0442\u0438 Plex", + "monitored_users": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0443\u0432\u0430\u043d\u0456 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456", + "use_episode_art": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043e\u0431\u043a\u043b\u0430\u0434\u0438\u043d\u043a\u0438 \u0435\u043f\u0456\u0437\u043e\u0434\u0456\u0432" + }, + "description": "\u0414\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/de.json b/homeassistant/components/plugwise/translations/de.json index 2282e3584fc99c..685cd6fb9ae9f4 100644 --- a/homeassistant/components/plugwise/translations/de.json +++ b/homeassistant/components/plugwise/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Service ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Smile: {name}", @@ -17,6 +18,8 @@ }, "user_gateway": { "data": { + "host": "IP-Adresse", + "password": "Smile ID", "port": "Port" }, "description": "Bitte eingeben" diff --git a/homeassistant/components/plugwise/translations/lb.json b/homeassistant/components/plugwise/translations/lb.json index 4ce9f8b0145c97..a3618bc911eb92 100644 --- a/homeassistant/components/plugwise/translations/lb.json +++ b/homeassistant/components/plugwise/translations/lb.json @@ -21,7 +21,8 @@ "data": { "host": "IP Adress", "password": "Smile ID", - "port": "Port" + "port": "Port", + "username": "Smile Benotzernumm" }, "title": "Mam Smile verbannen" } diff --git a/homeassistant/components/plugwise/translations/tr.json b/homeassistant/components/plugwise/translations/tr.json index d25f1975cf7745..60d6b1f92be30e 100644 --- a/homeassistant/components/plugwise/translations/tr.json +++ b/homeassistant/components/plugwise/translations/tr.json @@ -1,10 +1,23 @@ { "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "Smile: {name}", "step": { "user_gateway": { "data": { + "host": "\u0130p Adresi", + "password": "G\u00fcl\u00fcmseme Kimli\u011fi", + "port": "Port", "username": "Smile Kullan\u0131c\u0131 Ad\u0131" - } + }, + "description": "L\u00fctfen girin" } } } diff --git a/homeassistant/components/plugwise/translations/uk.json b/homeassistant/components/plugwise/translations/uk.json new file mode 100644 index 00000000000000..6c6f54612b1701 --- /dev/null +++ b/homeassistant/components/plugwise/translations/uk.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Smile: {name}", + "step": { + "user": { + "data": { + "flow_type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "description": "\u041f\u0440\u043e\u0434\u0443\u043a\u0442:", + "title": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Plugwise" + }, + "user_gateway": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "Smile ID", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041b\u043e\u0433\u0456\u043d Smile" + }, + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0432\u0435\u0434\u0456\u0442\u044c:", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Smile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Plugwise" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/de.json b/homeassistant/components/plum_lightpad/translations/de.json index accee16a6f5ce8..c94bf9aadabc10 100644 --- a/homeassistant/components/plum_lightpad/translations/de.json +++ b/homeassistant/components/plum_lightpad/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/plum_lightpad/translations/tr.json b/homeassistant/components/plum_lightpad/translations/tr.json new file mode 100644 index 00000000000000..f0dab20775fbc4 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/uk.json b/homeassistant/components/plum_lightpad/translations/uk.json new file mode 100644 index 00000000000000..96b14f793751d5 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/de.json b/homeassistant/components/point/translations/de.json index 8ee83eab727631..343c02055d5deb 100644 --- a/homeassistant/components/point/translations/de.json +++ b/homeassistant/components/point/translations/de.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_setup": "Du kannst nur ein Point-Konto konfigurieren.", + "already_setup": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich.", "authorize_url_fail": "Unbekannter Fehler beim Erstellen der Authorisierungs-URL.", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", "external_setup": "Pointt erfolgreich von einem anderen Flow konfiguriert.", - "no_flows": "Du m\u00fcsst Point konfigurieren, bevor du dich damit authentifizieren kannst. [Bitte lese die Anweisungen] (https://www.home-assistant.io/components/point/).", + "no_flows": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" }, "create_entry": { @@ -17,15 +17,15 @@ }, "step": { "auth": { - "description": "Folge dem Link unten und Akzeptiere Zugriff auf dei Minut-Konto. Kehre dann zur\u00fcck und dr\u00fccke unten auf Senden . \n\n [Link]({authorization_url})", + "description": "Folge dem Link unten und **Best\u00e4tige** den Zugriff auf dein Minut-Konto. Kehre dann zur\u00fcck und dr\u00fccke unten auf **Senden**. \n\n [Link]({authorization_url})", "title": "Point authentifizieren" }, "user": { "data": { "flow_impl": "Anbieter" }, - "description": "W\u00e4hle \u00fcber welchen Authentifizierungsanbieter du sich mit Point authentifizieren m\u00f6chtest.", - "title": "Authentifizierungsanbieter" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/point/translations/fr.json b/homeassistant/components/point/translations/fr.json index 141af3545ba491..ab9cd7af34e8b3 100644 --- a/homeassistant/components/point/translations/fr.json +++ b/homeassistant/components/point/translations/fr.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "external_setup": "Point correctement configur\u00e9 \u00e0 partir d\u2019un autre flux.", - "no_flows": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." + "no_flows": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "create_entry": { "default": "Authentification r\u00e9ussie" diff --git a/homeassistant/components/point/translations/tr.json b/homeassistant/components/point/translations/tr.json new file mode 100644 index 00000000000000..5a4849fad0726e --- /dev/null +++ b/homeassistant/components/point/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_setup": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "error": { + "no_token": "Eri\u015fim Belirteci" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/uk.json b/homeassistant/components/point/translations/uk.json new file mode 100644 index 00000000000000..6b66a39a291767 --- /dev/null +++ b/homeassistant/components/point/translations/uk.json @@ -0,0 +1,32 @@ +{ + "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.", + "authorize_url_fail": "\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.", + "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.", + "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": { + "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." + }, + "error": { + "follow_link": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u0456 \u043f\u0440\u043e\u0439\u0434\u0456\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\".", + "no_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443." + }, + "step": { + "auth": { + "description": "\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0437\u0430 [\u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c]({authorization_url}) \u0456 ** \u0414\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 ** \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u0432\u0430\u0448\u043e\u0433\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 Minut, \u043f\u043e\u0442\u0456\u043c \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0442\u0430 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **.", + "title": "Minut Point" + }, + "user": { + "data": { + "flow_impl": "\u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/de.json b/homeassistant/components/poolsense/translations/de.json index c7dfe6d02b211f..5869da61c9c40f 100644 --- a/homeassistant/components/poolsense/translations/de.json +++ b/homeassistant/components/poolsense/translations/de.json @@ -3,13 +3,16 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "user": { "data": { "email": "E-Mail", "password": "Passwort" }, - "description": "Wollen Sie mit der Einrichtung beginnen?" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/poolsense/translations/tr.json b/homeassistant/components/poolsense/translations/tr.json new file mode 100644 index 00000000000000..1e2e9d0c5b8de4 --- /dev/null +++ b/homeassistant/components/poolsense/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/uk.json b/homeassistant/components/poolsense/translations/uk.json new file mode 100644 index 00000000000000..6ac3b97f74167d --- /dev/null +++ b/homeassistant/components/poolsense/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "PoolSense" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index 2c9becb17959e6..4b176fff686d07 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/cs.json b/homeassistant/components/powerwall/translations/cs.json index b64eabcf33bae6..698934ad10e615 100644 --- a/homeassistant/components/powerwall/translations/cs.json +++ b/homeassistant/components/powerwall/translations/cs.json @@ -8,6 +8,7 @@ "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})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index f5317e3046ae74..c30286d874450a 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Die Powerwall ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index ac0d9568154166..6eb0b77708da4c 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -1,21 +1,21 @@ { - "config": { - "flow_title": "Tesla Powerwall ({ip_address})", - "step": { - "user": { - "title": "Connect to the powerwall", - "data": { - "ip_address": "[%key:common::config_flow::data::ip%]" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "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": "Tesla Powerwall ({ip_address})", + "step": { + "user": { + "data": { + "ip_address": "IP Address" + }, + "title": "Connect to the powerwall" + } } - } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 76835e814809f8..373bf29f8bace7 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -8,6 +8,7 @@ "unknown": "Error inesperado", "wrong_version": "Tu powerwall utiliza una versi\u00f3n de software que no es compatible. Considera actualizar o informar de este problema para que pueda resolverse." }, + "flow_title": "Powerwall de Tesla ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index 996b0ea4b309b7..b10dca9b08b6f4 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -8,6 +8,7 @@ "unknown": "Ootamatu t\u00f5rge", "wrong_version": "Teie Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, + "flow_title": "Tesla Powerwall ( {ip_address} )", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index 422a28b6936958..376168f86167ee 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -8,6 +8,7 @@ "unknown": "Errore imprevisto", "wrong_version": "Il tuo powerwall utilizza una versione del software non supportata. Si prega di considerare l'aggiornamento o la segnalazione di questo problema in modo che possa essere risolto." }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index 7ddab100358730..cdc04a006ad5c9 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/translations/no.json @@ -8,6 +8,7 @@ "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": "", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index 8532066608ceb4..dfd4fa21a375ab 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -8,6 +8,7 @@ "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": "Tesla UPS ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index a8713bcd04ae31..faabf2d0ede0af 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/translations/ru.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/tr.json b/homeassistant/components/powerwall/translations/tr.json new file mode 100644 index 00000000000000..dd09a83a78c5ba --- /dev/null +++ b/homeassistant/components/powerwall/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" + }, + "flow_title": "Tesla Powerwall ( {ip_address} )", + "step": { + "user": { + "data": { + "ip_address": "\u0130p Adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/uk.json b/homeassistant/components/powerwall/translations/uk.json new file mode 100644 index 00000000000000..9b397138c5272c --- /dev/null +++ b/homeassistant/components/powerwall/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "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", + "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": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "title": "Tesla Powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/zh-Hant.json b/homeassistant/components/powerwall/translations/zh-Hant.json index 45edbf2d88ed37..ec0d2e278b693c 100644 --- a/homeassistant/components/powerwall/translations/zh-Hant.json +++ b/homeassistant/components/powerwall/translations/zh-Hant.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { diff --git a/homeassistant/components/profiler/translations/de.json b/homeassistant/components/profiler/translations/de.json new file mode 100644 index 00000000000000..7137cd2ee4e106 --- /dev/null +++ b/homeassistant/components/profiler/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 mit der Einrichtung beginnen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/tr.json b/homeassistant/components/profiler/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/profiler/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/uk.json b/homeassistant/components/profiler/translations/uk.json new file mode 100644 index 00000000000000..5594895456e987 --- /dev/null +++ b/homeassistant/components/profiler/translations/uk.json @@ -0,0 +1,12 @@ +{ + "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." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/de.json b/homeassistant/components/progettihwsw/translations/de.json index 2e5bed4b668af3..0f773e03c1ded4 100644 --- a/homeassistant/components/progettihwsw/translations/de.json +++ b/homeassistant/components/progettihwsw/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/progettihwsw/translations/tr.json b/homeassistant/components/progettihwsw/translations/tr.json new file mode 100644 index 00000000000000..1d3d77584dd907 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/tr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "R\u00f6le 1", + "relay_10": "R\u00f6le 10", + "relay_11": "R\u00f6le 11", + "relay_12": "R\u00f6le 12", + "relay_13": "R\u00f6le 13", + "relay_14": "R\u00f6le 14", + "relay_15": "R\u00f6le 15", + "relay_16": "R\u00f6le 16", + "relay_2": "R\u00f6le 2", + "relay_3": "R\u00f6le 3", + "relay_4": "R\u00f6le 4", + "relay_5": "R\u00f6le 5", + "relay_6": "R\u00f6le 6", + "relay_7": "R\u00f6le 7", + "relay_8": "R\u00f6le 8", + "relay_9": "R\u00f6le 9" + }, + "title": "R\u00f6leleri kur" + }, + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + }, + "title": "Panoyu kur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/uk.json b/homeassistant/components/progettihwsw/translations/uk.json new file mode 100644 index 00000000000000..7918db8e1584f2 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/uk.json @@ -0,0 +1,41 @@ +{ + "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." + }, + "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": { + "relay_modes": { + "data": { + "relay_1": "\u0420\u0435\u043b\u0435 1", + "relay_10": "\u0420\u0435\u043b\u0435 10", + "relay_11": "\u0420\u0435\u043b\u0435 11", + "relay_12": "\u0420\u0435\u043b\u0435 12", + "relay_13": "\u0420\u0435\u043b\u0435 13", + "relay_14": "\u0420\u0435\u043b\u0435 14", + "relay_15": "\u0420\u0435\u043b\u0435 15", + "relay_16": "\u0420\u0435\u043b\u0435 16", + "relay_2": "\u0420\u0435\u043b\u0435 2", + "relay_3": "\u0420\u0435\u043b\u0435 3", + "relay_4": "\u0420\u0435\u043b\u0435 4", + "relay_5": "\u0420\u0435\u043b\u0435 5", + "relay_6": "\u0420\u0435\u043b\u0435 6", + "relay_7": "\u0420\u0435\u043b\u0435 7", + "relay_8": "\u0420\u0435\u043b\u0435 8", + "relay_9": "\u0420\u0435\u043b\u0435 9" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u043b\u0435" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043b\u0430\u0442\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/de.json b/homeassistant/components/ps4/translations/de.json index 5dd638a717cb1c..d5aa867f1db369 100644 --- a/homeassistant/components/ps4/translations/de.json +++ b/homeassistant/components/ps4/translations/de.json @@ -1,15 +1,16 @@ { "config": { "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", "credential_error": "Fehler beim Abrufen der Anmeldeinformationen.", "no_devices_found": "Es wurden keine PlayStation 4 im Netzwerk gefunden.", "port_987_bind_error": "Konnte sich nicht an Port 987 binden. Weitere Informationen findest du in der [Dokumentation] (https://www.home-assistant.io/components/ps4/).", "port_997_bind_error": "Bind to Port 997 nicht m\u00f6glich. Weitere Informationen findest du in der [Dokumentation](https://www.home-assistant.io/components/ps4/)" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "credential_timeout": "Zeit\u00fcberschreitung beim Warten auf den Anmeldedienst. Klicken zum Neustarten auf Senden.", - "login_failed": "Fehler beim Koppeln mit PlayStation 4. \u00dcberpr\u00fcfe, ob die PIN korrekt ist.", + "login_failed": "Fehler beim Koppeln mit der PlayStation 4. \u00dcberpr\u00fcfe, ob der PIN-Code korrekt ist.", "no_ipaddress": "Gib die IP-Adresse der PlayStation 4 ein, die konfiguriert werden soll." }, "step": { @@ -19,7 +20,7 @@ }, "link": { "data": { - "code": "PIN", + "code": "PIN-Code", "ip_address": "IP-Adresse", "name": "Name", "region": "Region" diff --git a/homeassistant/components/ps4/translations/tr.json b/homeassistant/components/ps4/translations/tr.json new file mode 100644 index 00000000000000..4e3e0b53445e79 --- /dev/null +++ b/homeassistant/components/ps4/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "link": { + "data": { + "ip_address": "\u0130p Adresi" + } + }, + "mode": { + "data": { + "ip_address": "\u0130p Adresi (Otomatik Bulma kullan\u0131l\u0131yorsa bo\u015f b\u0131rak\u0131n)." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/uk.json b/homeassistant/components/ps4/translations/uk.json new file mode 100644 index 00000000000000..696a46bf8d7f68 --- /dev/null +++ b/homeassistant/components/ps4/translations/uk.json @@ -0,0 +1,41 @@ +{ + "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.", + "credential_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0445 \u0434\u0430\u043d\u0438\u0445.", + "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.", + "port_987_bind_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u043e\u0440\u0442\u043e\u043c 987. \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](https://www.home-assistant.io/components/ps4/).", + "port_997_bind_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043f\u043e\u0440\u0442\u043e\u043c 997. \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](https://www.home-assistant.io/components/ps4/)." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "credential_timeout": "\u0427\u0430\u0441 \u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0438\u043d\u0443\u0432. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0449\u043e\u0431 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0438 \u0441\u043f\u0440\u043e\u0431\u0443.", + "login_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u043f\u0430\u0440\u0443 \u0437 PlayStation 4. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e PIN-\u043a\u043e\u0434 \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e.", + "no_ipaddress": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441\u0443 PlayStation 4." + }, + "step": { + "creds": { + "description": "\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **, \u0430 \u043f\u043e\u0442\u0456\u043c \u0432 \u0434\u043e\u0434\u0430\u0442\u043a\u0443 'PS4 Second Screen' \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 'Home-Assistant'.", + "title": "PlayStation 4" + }, + "link": { + "data": { + "code": "PIN-\u043a\u043e\u0434", + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430", + "region": "\u0420\u0435\u0433\u0456\u043e\u043d" + }, + "description": "\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f PIN-\u043a\u043e\u0434\u0443 \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043f\u0443\u043d\u043a\u0442\u0443 ** \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f ** \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0456 PlayStation 4. \u041f\u043e\u0442\u0456\u043c \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 ** \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430 ** \u0456 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c ** \u0414\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 ** . \u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438](https://www.home-assistant.io/components/ps4/) \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.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u043f\u0440\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u0456 \u0440\u0435\u0436\u0438\u043c\u0443 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f)", + "mode": "\u0420\u0435\u0436\u0438\u043c" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f. \u041f\u043e\u043b\u0435 'IP-\u0430\u0434\u0440\u0435\u0441\u0430' \u043c\u043e\u0436\u043d\u0430 \u0437\u0430\u043b\u0438\u0448\u0438\u0442\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u044f\u043a\u0449\u043e \u0432\u0438\u0431\u0440\u0430\u043d\u043e 'Auto Discovery', \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0434\u043e\u0434\u0430\u043d\u0456 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e.", + "title": "PlayStation 4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/de.json b/homeassistant/components/pvpc_hourly_pricing/translations/de.json index 8e3e9b68e42b5d..1b5c4d37658777 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/de.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Die Integration ist bereits mit einem vorhandenen Sensor mit diesem Tarif konfiguriert" + "already_configured": "Der Dienst ist bereits konfiguriert" }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/tr.json b/homeassistant/components/pvpc_hourly_pricing/translations/tr.json new file mode 100644 index 00000000000000..394f876401beb0 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "data": { + "name": "Sens\u00f6r Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/uk.json b/homeassistant/components/pvpc_hourly_pricing/translations/uk.json new file mode 100644 index 00000000000000..da2136d7765e00 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "tariff": "\u041a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\u043d\u0438\u0439 \u0442\u0430\u0440\u0438\u0444 (1, 2 \u0430\u0431\u043e 3 \u043f\u0435\u0440\u0456\u043e\u0434\u0438)" + }, + "description": "\u0426\u0435\u0439 \u0441\u0435\u043d\u0441\u043e\u0440 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454 \u043e\u0444\u0456\u0446\u0456\u0439\u043d\u0438\u0439 API \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f [\u043f\u043e\u0433\u043e\u0434\u0438\u043d\u043d\u043e\u0457 \u0446\u0456\u043d\u0438 \u0437\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u0435\u043d\u0435\u0440\u0433\u0456\u044e (PVPC)] (https://www.esios.ree.es/es/pvpc) \u0432 \u0406\u0441\u043f\u0430\u043d\u0456\u0457.\n\u0414\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0431\u0456\u043b\u044c\u0448 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044c \u0437 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0456\u0454\u044e] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0430\u0440\u0438\u0444, \u0437\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u043e\u0441\u0442\u0456 \u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0438\u0445 \u043f\u0435\u0440\u0456\u043e\u0434\u0456\u0432 \u0432 \u0434\u0435\u043d\u044c:\n- 1 \u043f\u0435\u0440\u0456\u043e\u0434: normal\n- 2 \u043f\u0435\u0440\u0456\u043e\u0434\u0438: discrimination (nightly rate)\n- 3 \u043f\u0435\u0440\u0456\u043e\u0434\u0438: electric car (nightly rate of 3 periods)", + "title": "\u0412\u0438\u0431\u0456\u0440 \u0442\u0430\u0440\u0438\u0444\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/de.json b/homeassistant/components/rachio/translations/de.json index e6a4d73cde1ab7..9acd92ce40de22 100644 --- a/homeassistant/components/rachio/translations/de.json +++ b/homeassistant/components/rachio/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, @@ -13,7 +13,7 @@ "data": { "api_key": "API-Schl\u00fcssel" }, - "description": "Sie ben\u00f6tigen den API-Schl\u00fcssel von https://app.rach.io/. W\u00e4hlen Sie \"Kontoeinstellungen\" und klicken Sie dann auf \"API-SCHL\u00dcSSEL ERHALTEN\".", + "description": "Du ben\u00f6tigst den API-Schl\u00fcssel von https://app.rach.io/. Gehe in die Einstellungen und klicke auf \"API-SCHL\u00dcSSEL ANFORDERN\".", "title": "Stellen Sie eine Verbindung zu Ihrem Rachio-Ger\u00e4t her" } } @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "Wie lange, in Minuten, um eine Station einzuschalten, wenn der Schalter aktiviert ist." + "manual_run_mins": "Wie viele Minuten es laufen soll, wenn ein Zonen-Schalter aktiviert wird" } } } diff --git a/homeassistant/components/rachio/translations/tr.json b/homeassistant/components/rachio/translations/tr.json new file mode 100644 index 00000000000000..8bbc4eb1e49db7 --- /dev/null +++ b/homeassistant/components/rachio/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", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/uk.json b/homeassistant/components/rachio/translations/uk.json new file mode 100644 index 00000000000000..af5d7cd39d97a7 --- /dev/null +++ b/homeassistant/components/rachio/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API" + }, + "description": "\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043a\u043b\u044e\u0447 API \u0437 \u0441\u0430\u0439\u0442\u0443 https://app.rach.io/. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0430 \u043f\u043e\u0442\u0456\u043c \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c 'GET API KEY'.", + "title": "Rachio" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "\u0422\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044c \u0440\u043e\u0431\u043e\u0442\u0438 \u043f\u0440\u0438 \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0456\u0457 \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447\u0430 \u0437\u043e\u043d\u0438 (\u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/de.json b/homeassistant/components/rainmachine/translations/de.json index 92df52bb1481ec..511d85b36b6c4f 100644 --- a/homeassistant/components/rainmachine/translations/de.json +++ b/homeassistant/components/rainmachine/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "Dieser RainMachine-Kontroller ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { diff --git a/homeassistant/components/rainmachine/translations/tr.json b/homeassistant/components/rainmachine/translations/tr.json index 20f74cae994549..80cfc05e568eaa 100644 --- a/homeassistant/components/rainmachine/translations/tr.json +++ b/homeassistant/components/rainmachine/translations/tr.json @@ -1,4 +1,21 @@ { + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "ip_address": "Ana makine ad\u0131 veya IP adresi", + "password": "Parola", + "port": "Port" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/rainmachine/translations/uk.json b/homeassistant/components/rainmachine/translations/uk.json new file mode 100644 index 00000000000000..ff8d7089cecb83 --- /dev/null +++ b/homeassistant/components/rainmachine/translations/uk.json @@ -0,0 +1,30 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "ip_address": "\u0414\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "RainMachine" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "\u0427\u0430\u0441 \u0440\u043e\u0431\u043e\u0442\u0438 \u0437\u043e\u043d\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f RainMachine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/de.json b/homeassistant/components/recollect_waste/translations/de.json new file mode 100644 index 00000000000000..7cbcea1b25e301 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/de.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "step": { + "user": { + "data": { + "place_id": "Platz-ID", + "service_id": "Dienst-ID" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Recollect Waste konfigurieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/es.json b/homeassistant/components/recollect_waste/translations/es.json index 2fdeb991bfde40..69a39d435ebbe1 100644 --- a/homeassistant/components/recollect_waste/translations/es.json +++ b/homeassistant/components/recollect_waste/translations/es.json @@ -20,7 +20,8 @@ "init": { "data": { "friendly_name": "Utilizar nombres descriptivos para los tipos de recogida (cuando sea posible)" - } + }, + "title": "Configurar la recogida de residuos" } } } diff --git a/homeassistant/components/recollect_waste/translations/lb.json b/homeassistant/components/recollect_waste/translations/lb.json new file mode 100644 index 00000000000000..4e312bb0f23485 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "invalid_place_or_service_id": "Ong\u00eblteg Place oder Service ID" + }, + "step": { + "user": { + "data": { + "place_id": "Place ID", + "service_id": "Service ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/pl.json b/homeassistant/components/recollect_waste/translations/pl.json index 013d0028790658..cc0342e93d75da 100644 --- a/homeassistant/components/recollect_waste/translations/pl.json +++ b/homeassistant/components/recollect_waste/translations/pl.json @@ -14,5 +14,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "U\u017cywaj przyjaznych nazw dla typu odbioru (je\u015bli to mo\u017cliwe)" + }, + "title": "Konfiguracja Recollect Waste" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/tr.json b/homeassistant/components/recollect_waste/translations/tr.json new file mode 100644 index 00000000000000..5307276a71d3a3 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/uk.json b/homeassistant/components/recollect_waste/translations/uk.json new file mode 100644 index 00000000000000..db47699f1bacf0 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/uk.json @@ -0,0 +1,28 @@ +{ + "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." + }, + "error": { + "invalid_place_or_service_id": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 ID \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0431\u043e \u0441\u043b\u0443\u0436\u0431\u0438." + }, + "step": { + "user": { + "data": { + "place_id": "ID \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f", + "service_id": "ID \u0441\u043b\u0443\u0436\u0431\u0438" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0440\u043e\u0437\u0443\u043c\u0456\u043b\u0456 \u0456\u043c\u0435\u043d\u0430 \u0434\u043b\u044f \u0442\u0438\u043f\u0456\u0432 \u0432\u0438\u0431\u043e\u0440\u0443 (\u044f\u043a\u0449\u043e \u043c\u043e\u0436\u043b\u0438\u0432\u043e)" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Recollect Waste" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/remote/translations/tr.json b/homeassistant/components/remote/translations/tr.json index cdc40c6268bb56..5359c99a78a28e 100644 --- a/homeassistant/components/remote/translations/tr.json +++ b/homeassistant/components/remote/translations/tr.json @@ -1,4 +1,14 @@ { + "device_automation": { + "action_type": { + "turn_off": "{entity_name} kapat", + "turn_on": "{entity_name} a\u00e7\u0131n" + }, + "trigger_type": { + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "state": { "_": { "off": "Kapal\u0131", diff --git a/homeassistant/components/remote/translations/uk.json b/homeassistant/components/remote/translations/uk.json index 2feda4928e5847..1f275f5f2ebe87 100644 --- a/homeassistant/components/remote/translations/uk.json +++ b/homeassistant/components/remote/translations/uk.json @@ -1,5 +1,14 @@ { "device_automation": { + "action_type": { + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u043e" diff --git a/homeassistant/components/rfxtrx/translations/ca.json b/homeassistant/components/rfxtrx/translations/ca.json index 6c4e920df02e52..d7db4107e3bb2e 100644 --- a/homeassistant/components/rfxtrx/translations/ca.json +++ b/homeassistant/components/rfxtrx/translations/ca.json @@ -64,7 +64,8 @@ "off_delay": "Retard OFF", "off_delay_enabled": "Activa el retard OFF", "replace_device": "Selecciona el dispositiu a substituir", - "signal_repetitions": "Nombre de repeticions del senyal" + "signal_repetitions": "Nombre de repeticions del senyal", + "venetian_blind_mode": "Mode persiana veneciana" }, "title": "Configuraci\u00f3 de les opcions del dispositiu" } diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index 1979a10cb8a6fd..b1e4197c0f1a2c 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -2,13 +2,17 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert. Nur eine Konfiguration m\u00f6glich.", - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "setup_network": { + "data": { + "host": "Host", + "port": "Port" + }, "title": "Verbindungsadresse ausw\u00e4hlen" }, "setup_serial": { @@ -18,6 +22,9 @@ "title": "Ger\u00e4t" }, "setup_serial_manual_path": { + "data": { + "device": "USB-Ger\u00e4te-Pfad" + }, "title": "Pfad" }, "user": { @@ -30,6 +37,7 @@ }, "options": { "error": { + "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", "unknown": "Unerwarteter Fehler" }, "step": { @@ -37,6 +45,13 @@ "data": { "debug": "Debugging aktivieren" } + }, + "set_device_options": { + "data": { + "off_delay": "Ausschaltverz\u00f6gerung", + "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", + "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll" + } } } } diff --git a/homeassistant/components/rfxtrx/translations/en.json b/homeassistant/components/rfxtrx/translations/en.json index 2d73ac568100c0..5e3f551e0cf95c 100644 --- a/homeassistant/components/rfxtrx/translations/en.json +++ b/homeassistant/components/rfxtrx/translations/en.json @@ -64,8 +64,8 @@ "off_delay": "Off delay", "off_delay_enabled": "Enable off delay", "replace_device": "Select device to replace", - "venetian_blind_mode": "Venetian blind mode (tilt by: US - long press, EU - short press)", - "signal_repetitions": "Number of signal repetitions" + "signal_repetitions": "Number of signal repetitions", + "venetian_blind_mode": "Venetian blind mode" }, "title": "Configure device options" } diff --git a/homeassistant/components/rfxtrx/translations/es.json b/homeassistant/components/rfxtrx/translations/es.json index 86bad8f096f698..c1c4d72735cdf4 100644 --- a/homeassistant/components/rfxtrx/translations/es.json +++ b/homeassistant/components/rfxtrx/translations/es.json @@ -64,7 +64,8 @@ "off_delay": "Retraso de apagado", "off_delay_enabled": "Activar retardo de apagado", "replace_device": "Seleccione el dispositivo que desea reemplazar", - "signal_repetitions": "N\u00famero de repeticiones de la se\u00f1al" + "signal_repetitions": "N\u00famero de repeticiones de la se\u00f1al", + "venetian_blind_mode": "Modo de persiana veneciana" }, "title": "Configurar las opciones del dispositivo" } diff --git a/homeassistant/components/rfxtrx/translations/et.json b/homeassistant/components/rfxtrx/translations/et.json index 1ade1f112c2b48..662664b445480e 100644 --- a/homeassistant/components/rfxtrx/translations/et.json +++ b/homeassistant/components/rfxtrx/translations/et.json @@ -64,7 +64,8 @@ "off_delay": "V\u00e4ljal\u00fclitamise viivitus", "off_delay_enabled": "Luba v\u00e4ljal\u00fclitusviivitus", "replace_device": "Vali asendav seade", - "signal_repetitions": "Signaali korduste arv" + "signal_repetitions": "Signaali korduste arv", + "venetian_blind_mode": "Ribikardinate juhtimine" }, "title": "Seadista seadme valikud" } diff --git a/homeassistant/components/rfxtrx/translations/it.json b/homeassistant/components/rfxtrx/translations/it.json index ff705fdd0a2285..938c471e992a63 100644 --- a/homeassistant/components/rfxtrx/translations/it.json +++ b/homeassistant/components/rfxtrx/translations/it.json @@ -64,7 +64,8 @@ "off_delay": "Ritardo di spegnimento", "off_delay_enabled": "Attivare il ritardo di spegnimento", "replace_device": "Selezionare il dispositivo da sostituire", - "signal_repetitions": "Numero di ripetizioni del segnale" + "signal_repetitions": "Numero di ripetizioni del segnale", + "venetian_blind_mode": "Modalit\u00e0 veneziana" }, "title": "Configurare le opzioni del dispositivo" } diff --git a/homeassistant/components/rfxtrx/translations/no.json b/homeassistant/components/rfxtrx/translations/no.json index 752136dac7fe70..3eb9c9b83df52b 100644 --- a/homeassistant/components/rfxtrx/translations/no.json +++ b/homeassistant/components/rfxtrx/translations/no.json @@ -64,7 +64,8 @@ "off_delay": "Av forsinkelse", "off_delay_enabled": "Aktiver av forsinkelse", "replace_device": "Velg enheten du vil erstatte", - "signal_repetitions": "Antall signalrepetisjoner" + "signal_repetitions": "Antall signalrepetisjoner", + "venetian_blind_mode": "Persiennemodus" }, "title": "Konfigurer enhetsalternativer" } diff --git a/homeassistant/components/rfxtrx/translations/pl.json b/homeassistant/components/rfxtrx/translations/pl.json index bf17d6c5166392..e0e69b2a64a037 100644 --- a/homeassistant/components/rfxtrx/translations/pl.json +++ b/homeassistant/components/rfxtrx/translations/pl.json @@ -75,7 +75,8 @@ "off_delay": "Op\u00f3\u017anienie stanu \"off\"", "off_delay_enabled": "W\u0142\u0105cz op\u00f3\u017anienie stanu \"off\"", "replace_device": "Wybierz urz\u0105dzenie do zast\u0105pienia", - "signal_repetitions": "Liczba powt\u00f3rze\u0144 sygna\u0142u" + "signal_repetitions": "Liczba powt\u00f3rze\u0144 sygna\u0142u", + "venetian_blind_mode": "Tryb \u017caluzji weneckich" }, "title": "Konfiguracja opcji urz\u0105dzenia" } diff --git a/homeassistant/components/rfxtrx/translations/ru.json b/homeassistant/components/rfxtrx/translations/ru.json index 361b051ac2ce4d..5a635766d3f379 100644 --- a/homeassistant/components/rfxtrx/translations/ru.json +++ b/homeassistant/components/rfxtrx/translations/ru.json @@ -64,7 +64,8 @@ "off_delay": "\u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "off_delay_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", "replace_device": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043b\u044f \u0437\u0430\u043c\u0435\u043d\u044b", - "signal_repetitions": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0430" + "signal_repetitions": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0430", + "venetian_blind_mode": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u043d\u0435\u0446\u0438\u0430\u043d\u0441\u043a\u0438\u0445 \u0436\u0430\u043b\u044e\u0437\u0438" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } diff --git a/homeassistant/components/rfxtrx/translations/tr.json b/homeassistant/components/rfxtrx/translations/tr.json new file mode 100644 index 00000000000000..1c3ad8b9e05f86 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/tr.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "setup_network": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + }, + "options": { + "error": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "step": { + "set_device_options": { + "data": { + "venetian_blind_mode": "Jaluzi modu" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/uk.json b/homeassistant/components/rfxtrx/translations/uk.json new file mode 100644 index 00000000000000..1b0938b8b70887 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/uk.json @@ -0,0 +1,74 @@ +{ + "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.", + "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" + }, + "step": { + "setup_network": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "setup_serial": { + "data": { + "device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "title": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "setup_serial_manual_path": { + "data": { + "device": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "title": "\u0428\u043b\u044f\u0445" + }, + "user": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + } + } + }, + "options": { + "error": { + "already_configured_device": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "invalid_event_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434 \u043f\u043e\u0434\u0456\u0457.", + "invalid_input_2262_off": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f.", + "invalid_input_2262_on": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f.", + "invalid_input_off_delay": "\u041d\u0435\u0432\u0456\u0440\u043d\u0456 \u0434\u0430\u043d\u0456 \u0434\u043b\u044f \u0437\u0430\u0442\u0440\u0438\u043c\u043a\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f", + "debug": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043d\u0430\u043b\u0430\u0433\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f", + "event_code": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0456\u0457", + "remove_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u043d\u044f" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + }, + "set_device_options": { + "data": { + "command_off": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "command_on": "\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "data_bit": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0431\u0456\u0442\u0456\u0432 \u0434\u0430\u043d\u0438\u0445", + "fire_event": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "off_delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "off_delay_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0437\u0430\u0442\u0440\u0438\u043c\u043a\u0443 \u0432\u0438\u043c\u0438\u043a\u0430\u043d\u043d\u044f", + "replace_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0434\u043b\u044f \u0437\u0430\u043c\u0456\u043d\u0438", + "signal_repetitions": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0432 \u0441\u0438\u0433\u043d\u0430\u043b\u0443", + "venetian_blind_mode": "\u0420\u0435\u0436\u0438\u043c \u0436\u0430\u043b\u044e\u0437\u0456" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ 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 3da2e5f5384221..24e5ee56d76103 100644 --- a/homeassistant/components/rfxtrx/translations/zh-Hant.json +++ b/homeassistant/components/rfxtrx/translations/zh-Hant.json @@ -64,7 +64,8 @@ "off_delay": "\u5ef6\u9072", "off_delay_enabled": "\u958b\u555f\u5ef6\u9072", "replace_device": "\u9078\u64c7\u88dd\u7f6e\u4ee5\u53d6\u4ee3", - "signal_repetitions": "\u8a0a\u865f\u91cd\u8907\u6b21\u6578" + "signal_repetitions": "\u8a0a\u865f\u91cd\u8907\u6b21\u6578", + "venetian_blind_mode": "\u767e\u8449\u7a97\u6a21\u5f0f" }, "title": "\u8a2d\u5b9a\u88dd\u7f6e\u9078\u9805" } diff --git a/homeassistant/components/ring/translations/tr.json b/homeassistant/components/ring/translations/tr.json new file mode 100644 index 00000000000000..caba385d7fa10f --- /dev/null +++ b/homeassistant/components/ring/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/uk.json b/homeassistant/components/ring/translations/uk.json new file mode 100644 index 00000000000000..8d40cdf0d23f59 --- /dev/null +++ b/homeassistant/components/ring/translations/uk.json @@ -0,0 +1,26 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041a\u043e\u0434 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Ring" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/de.json b/homeassistant/components/risco/translations/de.json index ad863f7ff792e0..36d808bd6dec77 100644 --- a/homeassistant/components/risco/translations/de.json +++ b/homeassistant/components/risco/translations/de.json @@ -1,14 +1,18 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { "password": "Passwort", - "pin": "PIN Code", + "pin": "PIN-Code", "username": "Benutzername" } } @@ -16,6 +20,12 @@ }, "options": { "step": { + "init": { + "data": { + "code_arm_required": "PIN-Code zum Entsperren vorgeben", + "code_disarm_required": "PIN-Code zum Entsperren vorgeben" + } + }, "risco_to_ha": { "data": { "A": "Gruppe A", diff --git a/homeassistant/components/risco/translations/lb.json b/homeassistant/components/risco/translations/lb.json index 197dd78c4033b0..ae136cb184305b 100644 --- a/homeassistant/components/risco/translations/lb.json +++ b/homeassistant/components/risco/translations/lb.json @@ -39,7 +39,9 @@ "A": "Grupp A", "B": "Grupp B", "C": "Grupp C", - "D": "Grupp D" + "D": "Grupp D", + "arm": "Aktiv\u00e9iert (\u00cbNNERWEE)", + "partial_arm": "Deelweis Aktiv\u00e9iert (DOHEEM)" } } } diff --git a/homeassistant/components/risco/translations/tr.json b/homeassistant/components/risco/translations/tr.json new file mode 100644 index 00000000000000..02a3b505f84b8b --- /dev/null +++ b/homeassistant/components/risco/translations/tr.json @@ -0,0 +1,27 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Se\u00e7enekleri yap\u0131land\u0131r\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/uk.json b/homeassistant/components/risco/translations/uk.json new file mode 100644 index 00000000000000..53b64344f2edd4 --- /dev/null +++ b/homeassistant/components/risco/translations/uk.json @@ -0,0 +1,55 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "pin": "PIN-\u043a\u043e\u0434", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u041d\u0435 \u0432\u0434\u043e\u043c\u0430)", + "armed_custom_bypass": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 \u0437 \u0432\u0438\u043d\u044f\u0442\u043a\u0430\u043c\u0438", + "armed_home": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u0412\u0434\u043e\u043c\u0430)", + "armed_night": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (\u043d\u0456\u0447)" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Risco \u043f\u0440\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u0456 \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Home Assistant", + "title": "\u0417\u0456\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u043d\u0456\u0432 Home Assistant \u0456 Risco" + }, + "init": { + "data": { + "code_arm_required": "\u0412\u0438\u043c\u0430\u0433\u0430\u0442\u0438 PIN-\u043a\u043e\u0434 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0443", + "code_disarm_required": "\u0412\u0438\u043c\u0430\u0433\u0430\u0442\u0438 PIN-\u043a\u043e\u0434 \u0434\u043b\u044f \u0437\u043d\u044f\u0442\u0442\u044f \u0437 \u043e\u0445\u043e\u0440\u043e\u043d\u0438", + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + }, + "risco_to_ha": { + "data": { + "A": "\u0413\u0440\u0443\u043f\u0430 \u0410", + "B": "\u0413\u0440\u0443\u043f\u0430 B", + "C": "\u0413\u0440\u0443\u043f\u0430 C", + "D": "\u0413\u0440\u0443\u043f\u0430 D", + "arm": "\u041e\u0445\u043e\u0440\u043e\u043d\u0430 (AWAY)", + "partial_arm": "\u0427\u0430\u0441\u0442\u043a\u043e\u0432\u0430 \u043e\u0445\u043e\u0440\u043e\u043d\u0430 (STAY)" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0442\u0430\u043d \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Home Assistant \u043f\u0440\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u0456 \u0441\u0438\u0433\u043d\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457 Risco", + "title": "\u0417\u0456\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u043d\u0456\u0432 Home Assistant \u0456 Risco" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ca.json b/homeassistant/components/roku/translations/ca.json index e9ab61575b5203..eb0564b5bde0cf 100644 --- a/homeassistant/components/roku/translations/ca.json +++ b/homeassistant/components/roku/translations/ca.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Vols configurar {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Vols configurar {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/cs.json b/homeassistant/components/roku/translations/cs.json index 7a83973a6f7856..89ca523af470dd 100644 --- a/homeassistant/components/roku/translations/cs.json +++ b/homeassistant/components/roku/translations/cs.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Chcete nastavit {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Chcete nastavit {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 9899aeba427e4f..4bfb3c7503ddda 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "M\u00f6chtest du {name} einrichten?", + "title": "Roku" + }, "ssdp_confirm": { "data": { "one": "eins", diff --git a/homeassistant/components/roku/translations/en.json b/homeassistant/components/roku/translations/en.json index 6facd1f3a7c26b..08db89f367761a 100644 --- a/homeassistant/components/roku/translations/en.json +++ b/homeassistant/components/roku/translations/en.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Do you want to set up {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Do you want to set up {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/es.json b/homeassistant/components/roku/translations/es.json index 78fb25809271dd..95e42643379c1f 100644 --- a/homeassistant/components/roku/translations/es.json +++ b/homeassistant/components/roku/translations/es.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "\u00bfQuieres configurar {name} ?", + "title": "Roku" + }, "ssdp_confirm": { "description": "\u00bfQuieres configurar {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/et.json b/homeassistant/components/roku/translations/et.json index e4869d044c8d4b..6727f539f57248 100644 --- a/homeassistant/components/roku/translations/et.json +++ b/homeassistant/components/roku/translations/et.json @@ -9,6 +9,10 @@ }, "flow_title": "", "step": { + "discovery_confirm": { + "description": "Kas soovid seadistada {name}?", + "title": "" + }, "ssdp_confirm": { "description": "Kas soovid seadistada {name}?", "title": "" diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 007be91d15532c..100d99924727f2 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Vuoi configurare {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Vuoi impostare {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/lb.json b/homeassistant/components/roku/translations/lb.json index 3aa8e5fa642fe2..04ad814c6b4d4e 100644 --- a/homeassistant/components/roku/translations/lb.json +++ b/homeassistant/components/roku/translations/lb.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "Soll {name} konfigur\u00e9iert ginn?", + "title": "Roku" + }, "ssdp_confirm": { "description": "Soll {name} konfigur\u00e9iert ginn?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/no.json b/homeassistant/components/roku/translations/no.json index 029220b58593ab..dd4ce4181415fa 100644 --- a/homeassistant/components/roku/translations/no.json +++ b/homeassistant/components/roku/translations/no.json @@ -9,6 +9,10 @@ }, "flow_title": "", "step": { + "discovery_confirm": { + "description": "Vil du konfigurere {name}?", + "title": "" + }, "ssdp_confirm": { "description": "Vil du sette opp {name} ?", "title": "" diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 3231d6c4bb7367..1d193acc0ff15a 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -9,6 +9,16 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "data": { + "few": "kilka", + "many": "wiele", + "one": "jeden", + "other": "inne" + }, + "description": "Czy chcesz skonfigurowa\u0107 {name}?", + "title": "Roku" + }, "ssdp_confirm": { "data": { "few": "kilka", diff --git a/homeassistant/components/roku/translations/ru.json b/homeassistant/components/roku/translations/ru.json index b5dcddbe555417..f7f36f41b27336 100644 --- a/homeassistant/components/roku/translations/ru.json +++ b/homeassistant/components/roku/translations/ru.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", + "title": "Roku" + }, "ssdp_confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", "title": "Roku" diff --git a/homeassistant/components/roku/translations/tr.json b/homeassistant/components/roku/translations/tr.json new file mode 100644 index 00000000000000..0dca1a028b283e --- /dev/null +++ b/homeassistant/components/roku/translations/tr.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "discovery_confirm": { + "description": "{name} kurmak istiyor musunuz?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "{name} kurmak istiyor musunuz?" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/uk.json b/homeassistant/components/roku/translations/uk.json new file mode 100644 index 00000000000000..b7db8875f8e0b8 --- /dev/null +++ b/homeassistant/components/roku/translations/uk.json @@ -0,0 +1,28 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Roku: {name}", + "step": { + "discovery_confirm": { + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name}?", + "title": "Roku" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e Roku." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/zh-Hant.json b/homeassistant/components/roku/translations/zh-Hant.json index cfa3a4aa3b4ed7..4b0566d66b06dc 100644 --- a/homeassistant/components/roku/translations/zh-Hant.json +++ b/homeassistant/components/roku/translations/zh-Hant.json @@ -9,6 +9,10 @@ }, "flow_title": "Roku\uff1a{name}", "step": { + "discovery_confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", + "title": "Roku" + }, "ssdp_confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", "title": "Roku" diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index af358678144949..b2fe68c876c482 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "not_irobot_device": "El dispositiu descobert no \u00e9s un dispositiu iRobot" + }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Amfitri\u00f3" + }, + "description": "Selecciona un/a Roomba o Braava.", + "title": "Connecta't al dispositiu autom\u00e0ticament" + }, + "link": { + "description": "Mant\u00e9 premut el bot\u00f3 d'inici a {name} fins que el dispositiu emeti un so (aproximadament dos segons).", + "title": "Recupera la contrasenya" + }, + "link_manual": { + "data": { + "password": "Contrasenya" + }, + "description": "No s'ha pogut obtenir la contrasenya del dispositiu autom\u00e0ticament. Segueix els passos de la seg\u00fcent documentaci\u00f3: {auth_help_url}", + "title": "Introdueix contrasenya" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Amfitri\u00f3" + }, + "description": "No s'ha descobert cap Roomba ni cap Braava a la teva xarxa. El BLID \u00e9s la part del nom d'amfitri\u00f3 del dispositiu despr\u00e9s de `iRobot-`. Segueix els passos de la seg\u00fcent documentaci\u00f3: {auth_help_url}", + "title": "Connecta't al dispositiu manualment" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/cs.json b/homeassistant/components/roomba/translations/cs.json index fdf4aff22c90a2..d94d39f8136371 100644 --- a/homeassistant/components/roomba/translations/cs.json +++ b/homeassistant/components/roomba/translations/cs.json @@ -1,9 +1,29 @@ { "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, "error": { "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Hostitel" + } + }, + "link_manual": { + "data": { + "password": "Heslo" + } + }, + "manual": { + "data": { + "host": "Hostitel" + } + }, "user": { "data": { "delay": "Zpo\u017ed\u011bn\u00ed", diff --git a/homeassistant/components/roomba/translations/de.json b/homeassistant/components/roomba/translations/de.json index 2f6ef37d13c179..780d406bcafbc6 100644 --- a/homeassistant/components/roomba/translations/de.json +++ b/homeassistant/components/roomba/translations/de.json @@ -1,9 +1,40 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "not_irobot_device": "Das erkannte Ger\u00e4t ist kein iRobot-Ger\u00e4t" + }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut" + "cannot_connect": "Verbindung fehlgeschlagen" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "W\u00e4hle einen Roomba oder Braava aus.", + "title": "Automatisch mit dem Ger\u00e4t verbinden" + }, + "link": { + "description": "Halte die Home-Taste von {name} gedr\u00fcckt, bis das Ger\u00e4t einen Ton erzeugt (ca. zwei Sekunden).", + "title": "Passwort abrufen" + }, + "link_manual": { + "data": { + "password": "Passwort" + }, + "description": "Das Passwort konnte nicht automatisch vom Ger\u00e4t abgerufen werden. Bitte die in der Dokumentation beschriebenen Schritte unter {auth_help_url} befolgen", + "title": "Passwort eingeben" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "title": "Manuell mit dem Ger\u00e4t verbinden" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 276ab4a92a2e4d..8d449e1881526f 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -1,51 +1,62 @@ { - "config": { - "flow_title": "iRobot {name} ({host})", - "step": { - "init": { - "title": "Automaticlly connect to the device", - "description": "Select a Roomba or Braava.", - "data": { - "host": "[%key:common::config_flow::data::host%]" + "config": { + "abort": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", + "not_irobot_device": "Discovered device is not an iRobot device" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "flow_title": "iRobot {name} ({host})", + "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Select a Roomba or Braava.", + "title": "Automaticlly connect to the device" + }, + "link": { + "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds).", + "title": "Retrieve Password" + }, + "link_manual": { + "data": { + "password": "Password" + }, + "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", + "title": "Enter Password" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: {auth_help_url}", + "title": "Manually connect to the device" + }, + "user": { + "data": { + "blid": "BLID", + "continuous": "Continuous", + "delay": "Delay", + "host": "Host", + "password": "Password" + }, + "description": "Currently retrieving the BLID and password is a manual process. Please follow the steps outlined in the documentation at: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Connect to the device" + } } - }, - "manual": { - "title": "Manually connect to the device", - "description": "No Roomba or Braava have been discovered on your network. The BLID is the portion of the device hostname after `iRobot-`. Please follow the steps outlined in the documentation at: {auth_help_url}", - "data": { - "host": "[%key:common::config_flow::data::host%]", - "blid": "BLID" - } - }, - "link": { - "title": "Retrieve Password", - "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds)." - }, - "link_manual": { - "title": "Enter Password", - "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", - "data": { - "password": "[%key:common::config_flow::data::password%]" - } - } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, - "abort": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "not_irobot_device": "Discovered device not an iRobot device" - } - }, - "options": { - "step": { - "init": { - "data": { - "continuous": "Continuous", - "delay": "Delay" + "options": { + "step": { + "init": { + "data": { + "continuous": "Continuous", + "delay": "Delay" + } + } } - } } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/es.json b/homeassistant/components/roomba/translations/es.json index a49022c2d3d39d..29f0b47a655eb2 100644 --- a/homeassistant/components/roomba/translations/es.json +++ b/homeassistant/components/roomba/translations/es.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar", + "not_irobot_device": "El dispositivo descubierto no es un dispositivo iRobot" + }, "error": { "cannot_connect": "No se pudo conectar" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Selecciona una Roomba o Braava.", + "title": "Conectar autom\u00e1ticamente con el dispositivo" + }, + "link": { + "description": "Mant\u00e9n pulsado el bot\u00f3n Inicio en {name} hasta que el dispositivo genere un sonido (aproximadamente dos segundos).", + "title": "Recuperar la contrase\u00f1a" + }, + "link_manual": { + "data": { + "password": "Contrase\u00f1a" + }, + "description": "No se pudo recuperar la contrase\u00f1a desde el dispositivo de forma autom\u00e1tica. Por favor, sigue los pasos descritos en la documentaci\u00f3n en: {auth_help_url}", + "title": "Escribe la contrase\u00f1a" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "No se ha descubierto ning\u00fan dispositivo Roomba ni Braava en tu red. El BLID es la parte del nombre de host del dispositivo despu\u00e9s de 'iRobot-'. Por favor, sigue los pasos descritos en la documentaci\u00f3n en: {auth_help_url}", + "title": "Conectar manualmente con el dispositivo" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/et.json b/homeassistant/components/roomba/translations/et.json index 92da58fa146329..e038257c12dea6 100644 --- a/homeassistant/components/roomba/translations/et.json +++ b/homeassistant/components/roomba/translations/et.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "not_irobot_device": "Leitud seade ei ole iRoboti seade" + }, "error": { "cannot_connect": "\u00dchendamine nurjus" }, + "flow_title": "iRobot {name} ( {host} )", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Vali Roomba v\u00f5i Braava seade.", + "title": "\u00dchendu seadmega automaatselt" + }, + "link": { + "description": "Vajuta ja hoia all seadme {name} nuppu Home kuni seade teeb piiksu (umbes kaks sekundit).", + "title": "Hangi salas\u00f5na" + }, + "link_manual": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Salas\u00f5na ei \u00f5nnestunud seadmest automaatselt hankida. J\u00e4rgi dokumentatsioonis toodud juhiseid: {auth_help_url}", + "title": "Sisesta salas\u00f5na" + }, + "manual": { + "data": { + "blid": "", + "host": "Host" + }, + "description": "V\u00f5rgus ei tuvastatud \u00fchtegi Roomba ega Braava seadet. BLID on seadme hostinime osa p\u00e4rast iRobot-`. J\u00e4rgi dokumentatsioonis toodud juhiseid: {auth_help_url}", + "title": "\u00dchenda seadmega k\u00e4sitsi" + }, "user": { "data": { "blid": "", diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 1ec97dd3842e08..8142d3acf1367b 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -1,9 +1,38 @@ { "config": { + "abort": { + "cannot_connect": "Echec de connection" + }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer" }, "step": { + "init": { + "data": { + "host": "H\u00f4te" + }, + "description": "S\u00e9lectionnez un Roomba ou un Braava.", + "title": "Se connecter automatiquement \u00e0 l'appareil" + }, + "link": { + "description": "Appuyez sur le bouton Accueil et maintenez-le enfonc\u00e9 jusqu'\u00e0 ce que l'appareil \u00e9mette un son (environ deux secondes).", + "title": "R\u00e9cup\u00e9rer le mot de passe" + }, + "link_manual": { + "data": { + "password": "Mot de passe" + }, + "description": "Le mot de passe n'a pas pu \u00eatre r\u00e9cup\u00e9r\u00e9 automatiquement \u00e0 partir de l'appareil. Veuillez suivre les \u00e9tapes d\u00e9crites dans la documentation \u00e0 {auth_help_url}", + "title": "Entrer le mot de passe" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "H\u00f4te" + }, + "description": "Aucun Roomba ou Braava d\u00e9couvert sur votre r\u00e9seau. Le BLID est la partie du nom d'h\u00f4te du p\u00e9riph\u00e9rique apr\u00e8s `iRobot-`. Veuillez suivre les \u00e9tapes d\u00e9crites dans la documentation \u00e0 {auth_help_url}", + "title": "Se connecter manuellement \u00e0 l'appareil" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/it.json b/homeassistant/components/roomba/translations/it.json index d109aa8bcc099c..b9e01faf16c338 100644 --- a/homeassistant/components/roomba/translations/it.json +++ b/homeassistant/components/roomba/translations/it.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "not_irobot_device": "Il dispositivo rilevato non \u00e8 un dispositivo iRobot" + }, "error": { "cannot_connect": "Impossibile connettersi" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Seleziona un Roomba o un Braava.", + "title": "Connettiti automaticamente al dispositivo" + }, + "link": { + "description": "Tieni premuto il pulsante Home su {name} fino a quando il dispositivo non genera un suono (circa due secondi).", + "title": "Recupera password" + }, + "link_manual": { + "data": { + "password": "Password" + }, + "description": "La password non pu\u00f2 essere recuperata automaticamente dal dispositivo. Segui le istruzioni indicate sulla documentazione a: {auth_help_url}", + "title": "Inserisci la password" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "Non sono stati trovati Roomba o Braava all'interno della tua rete. Il BLID \u00e8 la porzione del nome host del dispositivo dopo `iRobot-`. Segui le istruzioni indicate sulla documentazione a: {auth_help_url}", + "title": "Connettiti manualmente al dispositivo" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index d3fc631f5dfedd..500aa4fbee6a46 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -1,9 +1,35 @@ { "config": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + }, "error": { "cannot_connect": "Feeler beim verbannen" }, "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Ee Roomba oder Bravaa auswielen.", + "title": "Automatesch mam Apparat verbannen" + }, + "link": { + "title": "Passwuert ausliesen" + }, + "link_manual": { + "data": { + "password": "Passwuert" + }, + "title": "Passwuert aginn" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "title": "Manuell mam Apparat verbannen" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/no.json b/homeassistant/components/roomba/translations/no.json index adf13cb57af879..2bfe9f774d1698 100644 --- a/homeassistant/components/roomba/translations/no.json +++ b/homeassistant/components/roomba/translations/no.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "not_irobot_device": "Oppdaget enhet er ikke en iRobot-enhet" + }, "error": { "cannot_connect": "Tilkobling mislyktes" }, + "flow_title": "", "step": { + "init": { + "data": { + "host": "Vert" + }, + "description": "Velg en Roomba eller Braava", + "title": "Koble automatisk til enheten" + }, + "link": { + "description": "Trykk og hold inne Hjem-knappen p\u00e5 {name} til enheten genererer en lyd (omtrent to sekunder)", + "title": "Hent passord" + }, + "link_manual": { + "data": { + "password": "Passord" + }, + "description": "Passordet kunne ikke hentes automatisk fra enheten. F\u00f8lg trinnene som er beskrevet i dokumentasjonen p\u00e5: {auth_help_url}", + "title": "Skriv inn passord" + }, + "manual": { + "data": { + "blid": "", + "host": "Vert" + }, + "description": "Ingen Roomba eller Braava har blitt oppdaget i nettverket ditt. BLID er delen av enhetens vertsnavn etter `iRobot-`. F\u00f8lg trinnene som er beskrevet i dokumentasjonen p\u00e5: {auth_help_url}", + "title": "Koble til enheten manuelt" + }, "user": { "data": { "blid": "Blid", diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index b2a4ab89cbecdc..e4951a366ddd24 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "not_irobot_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem iRobot" + }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wybierz Roomb\u0119 lub Braava", + "title": "Po\u0142\u0105cz si\u0119 automatycznie z urz\u0105dzeniem" + }, + "link": { + "description": "Naci\u015bnij i przytrzymaj przycisk Home na {name} a\u017c urz\u0105dzenie wygeneruje d\u017awi\u0119k (oko\u0142o dwie sekundy).", + "title": "Odzyskiwanie has\u0142a" + }, + "link_manual": { + "data": { + "password": "Has\u0142o" + }, + "description": "Nie mo\u017cna automatycznie pobra\u0107 has\u0142a z urz\u0105dzenia. Post\u0119puj zgodnie z instrukcjami podanymi w dokumentacji pod adresem: {auth_help_url}", + "title": "Wprowad\u017a has\u0142o" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Nazwa hosta lub adres IP" + }, + "description": "W Twojej sieci nie wykryto urz\u0105dzenia Roomba ani Braava. BLID to cz\u0119\u015b\u0107 nazwy hosta urz\u0105dzenia po `iRobot-`. Post\u0119puj zgodnie z instrukcjami podanymi w dokumentacji pod adresem: {auth_help_url}", + "title": "R\u0119czne po\u0142\u0105czenie z urz\u0105dzeniem" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/pt.json b/homeassistant/components/roomba/translations/pt.json index 0156fd48a62666..6036e870e6c05d 100644 --- a/homeassistant/components/roomba/translations/pt.json +++ b/homeassistant/components/roomba/translations/pt.json @@ -1,9 +1,16 @@ { "config": { + "abort": { + "not_irobot_device": "O dispositivo descoberto n\u00e3o \u00e9 um dispositivo iRobot" + }, "error": { "cannot_connect": "Falha ao conectar, tente novamente" }, + "flow_title": "iRobot {name} ({host})", "step": { + "link": { + "title": "Recuperar Palavra-passe" + }, "user": { "data": { "continuous": "Cont\u00ednuo", diff --git a/homeassistant/components/roomba/translations/ru.json b/homeassistant/components/roomba/translations/ru.json index ee1192f69ec63c..979bb9bc70f7fc 100644 --- a/homeassistant/components/roomba/translations/ru.json +++ b/homeassistant/components/roomba/translations/ru.json @@ -1,9 +1,41 @@ { "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.", + "not_irobot_device": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 iRobot." + }, "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": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u044b\u043b\u0435\u0441\u043e\u0441 \u0438\u0437 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 Roomba \u0438\u043b\u0438 Braava.", + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + }, + "link": { + "description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0438 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 Home \u043d\u0430 {name}, \u043f\u043e\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0438\u0437\u0434\u0430\u0441\u0442 \u0437\u0432\u0443\u043a (\u043e\u043a\u043e\u043b\u043e \u0434\u0432\u0443\u0445 \u0441\u0435\u043a\u0443\u043d\u0434).", + "title": "\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f" + }, + "link_manual": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439: {auth_help_url}.", + "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u043f\u044b\u043b\u0435\u0441\u043e\u0441\u043e\u0432 Roomba \u0438\u043b\u0438 Braava. BLID - \u044d\u0442\u043e \u0447\u0430\u0441\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u043c\u0435\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u043e\u0441\u043b\u0435 \"iRobot-\". \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439: {auth_help_url}.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 \u0432\u0440\u0443\u0447\u043d\u0443\u044e" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roomba/translations/tr.json b/homeassistant/components/roomba/translations/tr.json new file mode 100644 index 00000000000000..3d85144c1883cb --- /dev/null +++ b/homeassistant/components/roomba/translations/tr.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "not_irobot_device": "Bulunan cihaz bir iRobot cihaz\u0131 de\u011fil" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "flow_title": "iRobot {name} ( {host} )", + "step": { + "init": { + "data": { + "host": "Ana Bilgisayar" + }, + "description": "Roomba veya Braava'y\u0131 se\u00e7in.", + "title": "Cihaza otomatik olarak ba\u011flan" + }, + "link": { + "description": "Cihaz bir ses olu\u015fturana kadar (yakla\u015f\u0131k iki saniye) {name} \u00fczerindeki Ana Sayfa d\u00fc\u011fmesini bas\u0131l\u0131 tutun.", + "title": "\u015eifre Al" + }, + "link_manual": { + "data": { + "password": "\u015eifre" + }, + "description": "Parola ayg\u0131ttan otomatik olarak al\u0131namad\u0131. L\u00fctfen belgelerde belirtilen ad\u0131mlar\u0131 izleyin: {auth_help_url}", + "title": "\u015eifre Girin" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Ana Bilgisayar" + }, + "title": "Cihaza manuel olarak ba\u011flan\u0131n" + }, + "user": { + "data": { + "continuous": "S\u00fcrekli", + "delay": "Gecikme", + "host": "Ana Bilgisayar", + "password": "Parola" + }, + "description": "\u015eu anda BLID ve parola alma manuel bir i\u015flemdir. L\u00fctfen a\u015fa\u011f\u0131daki belgelerde belirtilen ad\u0131mlar\u0131 izleyin: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Cihaza ba\u011flan\u0131n" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "S\u00fcrekli", + "delay": "Gecikme" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/uk.json b/homeassistant/components/roomba/translations/uk.json new file mode 100644 index 00000000000000..833a35f62f3a38 --- /dev/null +++ b/homeassistant/components/roomba/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "blid": "BLID", + "continuous": "\u0411\u0435\u0437\u043f\u0435\u0440\u0435\u0440\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430 (\u0441\u0435\u043a.)", + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438, \u0449\u043e\u0431 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 BLID \u0456 \u043f\u0430\u0440\u043e\u043b\u044c:\nhttps://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "\u0411\u0435\u0437\u043f\u0435\u0440\u0435\u0440\u0432\u043d\u043e", + "delay": "\u0417\u0430\u0442\u0440\u0438\u043c\u043a\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/zh-Hant.json b/homeassistant/components/roomba/translations/zh-Hant.json index 932e5cadd7567d..790eba79c032d4 100644 --- a/homeassistant/components/roomba/translations/zh-Hant.json +++ b/homeassistant/components/roomba/translations/zh-Hant.json @@ -1,9 +1,41 @@ { "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "not_irobot_device": "\u6240\u767c\u73fe\u7684\u88dd\u7f6e\u4e26\u975e iRobot \u88dd\u7f6e" + }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557" }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u9078\u64c7 Roomba \u6216 Braava\u3002", + "title": "\u81ea\u52d5\u9023\u7dda\u81f3\u88dd\u7f6e" + }, + "link": { + "description": "\u8acb\u6309\u4f4f {name} \u4e0a\u7684 Home \u9375\u76f4\u5230\u88dd\u7f6e\u767c\u51fa\u8072\u97f3\uff08\u7d04\u5169\u79d2\uff09\u3002", + "title": "\u91cd\u7f6e\u5bc6\u78bc" + }, + "link_manual": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u5bc6\u78bc\u53ef\u81ea\u52d5\u81ea\u88dd\u7f6e\u4e0a\u53d6\u5f97\u3002\u8acb\u53c3\u95b1\u4ee5\u4e0b\u6587\u4ef6\u7684\u6b65\u9a5f\u9032\u884c\u8a2d\u5b9a\uff1a{auth_help_url}", + "title": "\u8f38\u5165\u5bc6\u78bc" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "\u4e3b\u6a5f\u7aef" + }, + "description": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Roomba \u6216 Braava\u3002BLID \u88dd\u7f6e\u65bc\u4e3b\u6a5f\u7aef\u7684\u90e8\u5206\u540d\u7a31\u70ba `iRobot-` \u958b\u982d\u3002\u8acb\u53c3\u95b1\u4ee5\u4e0b\u6587\u4ef6\u7684\u6b65\u9a5f\u9032\u884c\u8a2d\u5b9a\uff1a{auth_help_url}", + "title": "\u624b\u52d5\u9023\u7dda\u81f3\u88dd\u7f6e" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roon/translations/ca.json b/homeassistant/components/roon/translations/ca.json index 3a1de2208b6033..ef32dd00e75f86 100644 --- a/homeassistant/components/roon/translations/ca.json +++ b/homeassistant/components/roon/translations/ca.json @@ -17,7 +17,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Introdueix el nom d'amfitri\u00f3 o la IP del servidor Roon" + "description": "No s'ha pogut descobrir el servidor Roon, introdueix el nom d'amfitri\u00f3 o la IP." } } } diff --git a/homeassistant/components/roon/translations/cs.json b/homeassistant/components/roon/translations/cs.json index a15e75066a9865..fd01ed1cd25777 100644 --- a/homeassistant/components/roon/translations/cs.json +++ b/homeassistant/components/roon/translations/cs.json @@ -16,8 +16,7 @@ "user": { "data": { "host": "Hostitel" - }, - "description": "Zadejte pros\u00edm n\u00e1zev hostitele nebo IP adresu va\u0161eho Roon serveru." + } } } } diff --git a/homeassistant/components/roon/translations/de.json b/homeassistant/components/roon/translations/de.json index 9918e38670acde..4416589a23ee45 100644 --- a/homeassistant/components/roon/translations/de.json +++ b/homeassistant/components/roon/translations/de.json @@ -1,8 +1,19 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { "duplicate_entry": "Dieser Host wurde bereits hinzugef\u00fcgt.", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/roon/translations/en.json b/homeassistant/components/roon/translations/en.json index 99f2b65bd13c21..b763fbb1e0c1ed 100644 --- a/homeassistant/components/roon/translations/en.json +++ b/homeassistant/components/roon/translations/en.json @@ -17,7 +17,7 @@ "data": { "host": "Host" }, - "description": "Please enter your Roon server Hostname or IP." + "description": "Could not discover Roon server, please enter your the Hostname or IP." } } } diff --git a/homeassistant/components/roon/translations/et.json b/homeassistant/components/roon/translations/et.json index dfe3ad53f48d69..e29b1ccc6c6d1c 100644 --- a/homeassistant/components/roon/translations/et.json +++ b/homeassistant/components/roon/translations/et.json @@ -17,7 +17,7 @@ "data": { "host": "" }, - "description": "Sisesta oma Rooni serveri hostinimi v\u00f5i IP." + "description": "Rooni serverit ei leitud. Sisesta oma Rooni serveri hostinimi v\u00f5i IP." } } } diff --git a/homeassistant/components/roon/translations/it.json b/homeassistant/components/roon/translations/it.json index 5f63482c3c3937..e0450af9d39198 100644 --- a/homeassistant/components/roon/translations/it.json +++ b/homeassistant/components/roon/translations/it.json @@ -17,7 +17,7 @@ "data": { "host": "Host" }, - "description": "Inserisci il nome host o l'IP del tuo server Roon." + "description": "Impossibile individuare il server Roon, inserire l'hostname o l'IP." } } } diff --git a/homeassistant/components/roon/translations/no.json b/homeassistant/components/roon/translations/no.json index 9067e2c6f5331a..e872e03a69d20c 100644 --- a/homeassistant/components/roon/translations/no.json +++ b/homeassistant/components/roon/translations/no.json @@ -17,7 +17,7 @@ "data": { "host": "Vert" }, - "description": "Vennligst skriv inn Roon-serverens vertsnavn eller IP." + "description": "Kunne ikke oppdage Roon-serveren. Angi vertsnavnet eller IP-adressen." } } } diff --git a/homeassistant/components/roon/translations/pl.json b/homeassistant/components/roon/translations/pl.json index e63c5f6b55c675..d763fc12bd26ad 100644 --- a/homeassistant/components/roon/translations/pl.json +++ b/homeassistant/components/roon/translations/pl.json @@ -17,7 +17,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Wprowad\u017a nazw\u0119 hosta lub adres IP swojego serwera Roon." + "description": "Nie wykryto serwera Roon, wprowad\u017a nazw\u0119 hosta lub adres IP." } } } diff --git a/homeassistant/components/roon/translations/ru.json b/homeassistant/components/roon/translations/ru.json index abfbea2ccde294..187151affe2f40 100644 --- a/homeassistant/components/roon/translations/ru.json +++ b/homeassistant/components/roon/translations/ru.json @@ -17,7 +17,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Roon" + "description": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Roon, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441." } } } diff --git a/homeassistant/components/roon/translations/tr.json b/homeassistant/components/roon/translations/tr.json new file mode 100644 index 00000000000000..97241919c9b5f1 --- /dev/null +++ b/homeassistant/components/roon/translations/tr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "duplicate_entry": "Bu ana bilgisayar zaten eklendi.", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "link": { + "description": "Roon'da HomeAssistant\u0131 yetkilendirmelisiniz. G\u00f6nder'e t\u0131klad\u0131ktan sonra, Roon Core uygulamas\u0131na gidin, Ayarlar'\u0131 a\u00e7\u0131n ve Uzant\u0131lar sekmesinde HomeAssistant'\u0131 etkinle\u015ftirin.", + "title": "Roon'da HomeAssistant'\u0131 Yetkilendirme" + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/uk.json b/homeassistant/components/roon/translations/uk.json new file mode 100644 index 00000000000000..91a530787ae37b --- /dev/null +++ b/homeassistant/components/roon/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "duplicate_entry": "\u0426\u0435\u0439 \u0445\u043e\u0441\u0442 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0438\u0439.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "link": { + "description": "\u041f\u0456\u0441\u043b\u044f \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u00ab\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0438\u00bb \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0432 \u0434\u043e\u0434\u0430\u0442\u043e\u043a Roon Core, \u0432\u0456\u0434\u043a\u0440\u0438\u0439\u0442\u0435 \u00ab\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u00bb \u0456 \u0443\u0432\u0456\u043c\u043a\u043d\u0456\u0442\u044c HomeAssistant \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u0446\u0456 \u00ab\u0420\u043e\u0437\u0448\u0438\u0440\u0435\u043d\u043d\u044f\u00bb.", + "title": "Roon" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043d\u0430\u0437\u0432\u0443 \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Roon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/zh-Hant.json b/homeassistant/components/roon/translations/zh-Hant.json index f34bce445f74a9..39099753f39ed7 100644 --- a/homeassistant/components/roon/translations/zh-Hant.json +++ b/homeassistant/components/roon/translations/zh-Hant.json @@ -17,7 +17,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8acb\u8f38\u5165 Roon \u4f3a\u670d\u5668\u4e3b\u6a5f\u540d\u7a31\u6216 IP\u3002" + "description": "\u627e\u4e0d\u5230 Roon \u4f3a\u670d\u5668\uff0c\u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31\u6216 IP\u3002" } } } diff --git a/homeassistant/components/rpi_power/translations/de.json b/homeassistant/components/rpi_power/translations/de.json new file mode 100644 index 00000000000000..9f3851f0c2b304 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "confirm": { + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + } + } + }, + "title": "Raspberry Pi Stromversorgungspr\u00fcfer" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/lb.json b/homeassistant/components/rpi_power/translations/lb.json index 3e145432bae03d..e4bb73893399ab 100644 --- a/homeassistant/components/rpi_power/translations/lb.json +++ b/homeassistant/components/rpi_power/translations/lb.json @@ -3,6 +3,11 @@ "abort": { "no_devices_found": "Kann d\u00e9i Systemklass fir d\u00ebs noutwendeg Komponent net fannen, stell s\u00e9cher dass de Kernel rezent ass an d'Hardware \u00ebnnerst\u00ebtzt g\u00ebtt.", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "step": { + "confirm": { + "description": "Soll den Ariichtungs Prozess gestart ginn?" + } } }, "title": "Raspberry Pi Netzdeel Checker" diff --git a/homeassistant/components/rpi_power/translations/tr.json b/homeassistant/components/rpi_power/translations/tr.json new file mode 100644 index 00000000000000..f1dfcf16667708 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/tr.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + }, + "title": "Raspberry Pi G\u00fc\u00e7 Kayna\u011f\u0131 Denetleyicisi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/uk.json b/homeassistant/components/rpi_power/translations/uk.json new file mode 100644 index 00000000000000..b60160e1c4ec3a --- /dev/null +++ b/homeassistant/components/rpi_power/translations/uk.json @@ -0,0 +1,14 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + }, + "title": "Raspberry Pi power supply checker" +} \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/de.json b/homeassistant/components/ruckus_unleashed/translations/de.json index ae15ec058b5185..625c7372347a61 100644 --- a/homeassistant/components/ruckus_unleashed/translations/de.json +++ b/homeassistant/components/ruckus_unleashed/translations/de.json @@ -4,12 +4,14 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { + "host": "Host", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/ruckus_unleashed/translations/tr.json b/homeassistant/components/ruckus_unleashed/translations/tr.json new file mode 100644 index 00000000000000..40c9c39b967721 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ruckus_unleashed/translations/uk.json b/homeassistant/components/ruckus_unleashed/translations/uk.json new file mode 100644 index 00000000000000..2df11f744559db --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/de.json b/homeassistant/components/samsungtv/translations/de.json index e33542676300e3..3ba569c87db987 100644 --- a/homeassistant/components/samsungtv/translations/de.json +++ b/homeassistant/components/samsungtv/translations/de.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_configured": "Dieser Samsung TV ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr Samsung TV wird bereits ausgef\u00fchrt.", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "auth_missing": "Home Assistant ist nicht berechtigt, eine Verbindung zu diesem Samsung TV herzustellen. \u00dcberpr\u00fcfe die Einstellungen deines Fernsehger\u00e4ts, um Home Assistant zu autorisieren.", - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "not_supported": "Dieses Samsung TV-Ger\u00e4t wird derzeit nicht unterst\u00fctzt." }, "flow_title": "Samsung TV: {model}", diff --git a/homeassistant/components/samsungtv/translations/tr.json b/homeassistant/components/samsungtv/translations/tr.json index 50e6b21d120d67..6b3900e9aa5651 100644 --- a/homeassistant/components/samsungtv/translations/tr.json +++ b/homeassistant/components/samsungtv/translations/tr.json @@ -4,13 +4,17 @@ "already_configured": "Bu Samsung TV zaten ayarlanm\u0131\u015f.", "already_in_progress": "Samsung TV ayar\u0131 zaten s\u00fcr\u00fcyor.", "auth_missing": "Home Assistant'\u0131n bu Samsung TV'ye ba\u011flanma izni yok. Home Assistant'\u0131 yetkilendirmek i\u00e7in l\u00fctfen TV'nin ayarlar\u0131n\u0131 kontrol et.", + "cannot_connect": "Ba\u011flanma hatas\u0131", "not_supported": "Bu Samsung TV cihaz\u0131 \u015fu anda desteklenmiyor." }, "flow_title": "Samsung TV: {model}", "step": { + "confirm": { + "title": "Samsung TV" + }, "user": { "data": { - "host": "Host veya IP adresi", + "host": "Ana Bilgisayar", "name": "Ad" }, "description": "Samsung TV bilgilerini gir. Daha \u00f6nce hi\u00e7 Home Assistant'a ba\u011flamad\u0131ysan, TV'nde izin isteyen bir pencere g\u00f6receksindir." diff --git a/homeassistant/components/samsungtv/translations/uk.json b/homeassistant/components/samsungtv/translations/uk.json new file mode 100644 index 00000000000000..83bb18e76f172c --- /dev/null +++ b/homeassistant/components/samsungtv/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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_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.", + "auth_missing": "Home Assistant \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0446\u044c\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "not_supported": "\u0426\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u0432 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "flow_title": "Samsung TV: {model}", + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung {model}? \u042f\u043a\u0449\u043e \u0446\u0435\u0439 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0440\u0430\u043d\u0456\u0448\u0435 \u043d\u0435 \u0431\u0443\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e Home Assistant, \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043c\u0430\u0454 \u0437'\u044f\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u043f\u043b\u0438\u0432\u0430\u044e\u0447\u0435 \u0432\u0456\u043a\u043d\u043e \u0456\u0437 \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457. \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430, \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0456 \u0432\u0440\u0443\u0447\u043d\u0443, \u0431\u0443\u0434\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0456.", + "title": "\u0422\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 Samsung. \u042f\u043a\u0449\u043e \u0446\u0435\u0439 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0440\u0430\u043d\u0456\u0448\u0435 \u043d\u0435 \u0431\u0443\u0432 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e Home Assistant, \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0456 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0430 \u043c\u0430\u0454 \u0437'\u044f\u0432\u0438\u0442\u0438\u0441\u044f \u0441\u043f\u043b\u0438\u0432\u0430\u044e\u0447\u0435 \u0432\u0456\u043a\u043d\u043e \u0456\u0437 \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u0457." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/script/translations/uk.json b/homeassistant/components/script/translations/uk.json index bfff0258c6643c..ee494e264ae40a 100644 --- a/homeassistant/components/script/translations/uk.json +++ b/homeassistant/components/script/translations/uk.json @@ -5,5 +5,5 @@ "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } }, - "title": "\u0421\u0446\u0435\u043d\u0430\u0440\u0456\u0439" + "title": "\u0421\u043a\u0440\u0438\u043f\u0442" } \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.uk.json b/homeassistant/components/season/translations/sensor.uk.json index 2c694e287b1566..fa79d3cff07f51 100644 --- a/homeassistant/components/season/translations/sensor.uk.json +++ b/homeassistant/components/season/translations/sensor.uk.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "\u041e\u0441\u0456\u043d\u044c", + "spring": "\u0412\u0435\u0441\u043d\u0430", + "summer": "\u041b\u0456\u0442\u043e", + "winter": "\u0417\u0438\u043c\u0430" + }, "season__season__": { "autumn": "\u041e\u0441\u0456\u043d\u044c", "spring": "\u0412\u0435\u0441\u043d\u0430", diff --git a/homeassistant/components/sense/translations/de.json b/homeassistant/components/sense/translations/de.json index de9e6877f25a27..9d4845ece79fa1 100644 --- a/homeassistant/components/sense/translations/de.json +++ b/homeassistant/components/sense/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, diff --git a/homeassistant/components/sense/translations/tr.json b/homeassistant/components/sense/translations/tr.json new file mode 100644 index 00000000000000..0e335265325683 --- /dev/null +++ b/homeassistant/components/sense/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/uk.json b/homeassistant/components/sense/translations/uk.json new file mode 100644 index 00000000000000..8eac9c9d4abebf --- /dev/null +++ b/homeassistant/components/sense/translations/uk.json @@ -0,0 +1,21 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "title": "Sense Energy Monitor" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/tr.json b/homeassistant/components/sensor/translations/tr.json index 3bf1ba6f3682f0..feca40991eee3a 100644 --- a/homeassistant/components/sensor/translations/tr.json +++ b/homeassistant/components/sensor/translations/tr.json @@ -1,4 +1,31 @@ { + "device_automation": { + "condition_type": { + "is_current": "Mevcut {entity_name} ak\u0131m\u0131", + "is_energy": "Mevcut {entity_name} enerjisi", + "is_power_factor": "Mevcut {entity_name} g\u00fc\u00e7 fakt\u00f6r\u00fc", + "is_signal_strength": "Mevcut {entity_name} sinyal g\u00fcc\u00fc", + "is_temperature": "Mevcut {entity_name} s\u0131cakl\u0131\u011f\u0131", + "is_timestamp": "Mevcut {entity_name} zaman damgas\u0131", + "is_value": "Mevcut {entity_name} de\u011feri", + "is_voltage": "Mevcut {entity_name} voltaj\u0131" + }, + "trigger_type": { + "battery_level": "{entity_name} pil seviyesi de\u011fi\u015fiklikleri", + "current": "{entity_name} ak\u0131m de\u011fi\u015fiklikleri", + "energy": "{entity_name} enerji de\u011fi\u015fiklikleri", + "humidity": "{entity_name} nem de\u011fi\u015fiklikleri", + "illuminance": "{entity_name} ayd\u0131nlatma de\u011fi\u015fiklikleri", + "power": "{entity_name} g\u00fc\u00e7 de\u011fi\u015fiklikleri", + "power_factor": "{entity_name} g\u00fc\u00e7 fakt\u00f6r\u00fc de\u011fi\u015fiklikleri", + "pressure": "{entity_name} bas\u0131n\u00e7 de\u011fi\u015fiklikleri", + "signal_strength": "{entity_name} sinyal g\u00fcc\u00fc de\u011fi\u015fiklikleri", + "temperature": "{entity_name} s\u0131cakl\u0131k de\u011fi\u015fiklikleri", + "timestamp": "{entity_name} zaman damgas\u0131 de\u011fi\u015fiklikleri", + "value": "{entity_name} de\u011fer de\u011fi\u015fiklikleri", + "voltage": "{entity_name} voltaj de\u011fi\u015fiklikleri" + } + }, "state": { "_": { "off": "Kapal\u0131", diff --git a/homeassistant/components/sensor/translations/uk.json b/homeassistant/components/sensor/translations/uk.json index 391415409f5bd1..9e6148c3b8c636 100644 --- a/homeassistant/components/sensor/translations/uk.json +++ b/homeassistant/components/sensor/translations/uk.json @@ -1,7 +1,34 @@ { "device_automation": { "condition_type": { - "is_battery_level": "\u041f\u043e\u0442\u043e\u0447\u043d\u0438\u0439 \u0440\u0456\u0432\u0435\u043d\u044c \u0437\u0430\u0440\u044f\u0434\u0443 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430 {entity_name}" + "is_battery_level": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_current": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0441\u0438\u043b\u0438 \u0441\u0442\u0440\u0443\u043c\u0443", + "is_energy": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "is_humidity": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_illuminance": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_power": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_power_factor": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043a\u043e\u0435\u0444\u0456\u0446\u0456\u0454\u043d\u0442\u0430 \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "is_pressure": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_signal_strength": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_temperature": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_timestamp": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_value": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "is_voltage": "{entity_name} \u043c\u0430\u0454 \u043f\u043e\u0442\u043e\u0447\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430\u043f\u0440\u0443\u0433\u0438" + }, + "trigger_type": { + "battery_level": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "current": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0441\u0438\u043b\u0438 \u0441\u0442\u0440\u0443\u043c\u0443", + "energy": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "humidity": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "illuminance": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "power": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "power_factor": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u043a\u043e\u0435\u0444\u0456\u0446\u0456\u0454\u043d\u0442 \u043f\u043e\u0442\u0443\u0436\u043d\u043e\u0441\u0442\u0456", + "pressure": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "signal_strength": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "temperature": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "timestamp": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "value": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f", + "voltage": "{entity_name} \u0437\u043c\u0456\u043d\u044e\u0454 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430\u043f\u0440\u0443\u0433\u0438" } }, "state": { @@ -10,5 +37,5 @@ "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" } }, - "title": "\u0414\u0430\u0442\u0447\u0438\u043a" + "title": "\u0421\u0435\u043d\u0441\u043e\u0440" } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/de.json b/homeassistant/components/sentry/translations/de.json index c36bbf258b08f7..8fbcfc1eaa2cc7 100644 --- a/homeassistant/components/sentry/translations/de.json +++ b/homeassistant/components/sentry/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { "bad_dsn": "Ung\u00fcltiger DSN", "unknown": "Unerwarteter Fehler" diff --git a/homeassistant/components/sentry/translations/tr.json b/homeassistant/components/sentry/translations/tr.json new file mode 100644 index 00000000000000..4dab23fbd949f9 --- /dev/null +++ b/homeassistant/components/sentry/translations/tr.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "bad_dsn": "Ge\u00e7ersiz DSN", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "dsn": "DSN" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Ortam\u0131n iste\u011fe ba\u011fl\u0131 ad\u0131." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/uk.json b/homeassistant/components/sentry/translations/uk.json new file mode 100644 index 00000000000000..01da0308851d37 --- /dev/null +++ b/homeassistant/components/sentry/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "bad_dsn": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 DSN.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "dsn": "DSN" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448 DSN Sentry", + "title": "Sentry" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "\u041d\u0430\u0437\u0432\u0430", + "event_custom_components": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u0437 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0446\u044c\u043a\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0456\u0432", + "event_handled": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u043e\u0431\u0440\u043e\u0431\u043b\u0435\u043d\u0456 \u043f\u043e\u0434\u0456\u0457", + "event_third_party_packages": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u0438 \u043f\u043e\u0434\u0456\u0457 \u0437 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0456\u0445 \u043f\u0430\u043a\u0435\u0442\u0456\u0432", + "logging_event_level": "\u0417\u0430\u043f\u0438\u0441\u0443\u0432\u0430\u0442\u0438 \u0436\u0443\u0440\u043d\u0430\u043b\u0438 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0445 \u043f\u043e\u0434\u0456\u0439", + "logging_level": "\u0417\u0430\u043f\u0438\u0441\u0443\u0432\u0430\u0442\u0438 \u0436\u0443\u0440\u043d\u0430\u043b\u0438 \u0443 \u0432\u0438\u0433\u043b\u044f\u0434\u0456 \u043d\u0430\u0432\u0456\u0433\u0430\u0446\u0456\u0439\u043d\u0438\u0445 \u043b\u0430\u043d\u0446\u044e\u0436\u043a\u0456\u0432", + "tracing": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0456", + "tracing_sample_rate": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u0434\u0438\u0441\u043a\u0440\u0435\u0442\u0438\u0437\u0430\u0446\u0456\u0457 \u0442\u0440\u0430\u0441\u0443\u0432\u0430\u043d\u043d\u044f; \u0432\u0456\u0434 0,0 \u0434\u043e 1,0 (1,0 = 100%)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/de.json b/homeassistant/components/sharkiq/translations/de.json index 2294960d6f261d..8a6f9b14747b5b 100644 --- a/homeassistant/components/sharkiq/translations/de.json +++ b/homeassistant/components/sharkiq/translations/de.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "unknown": "Unerwarteter Fehler" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/sharkiq/translations/fr.json b/homeassistant/components/sharkiq/translations/fr.json index 5f05292ec2a8bb..6fa3ba7707cfb4 100644 --- a/homeassistant/components/sharkiq/translations/fr.json +++ b/homeassistant/components/sharkiq/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s", "unknown": "Erreur inattendue" diff --git a/homeassistant/components/sharkiq/translations/tr.json b/homeassistant/components/sharkiq/translations/tr.json new file mode 100644 index 00000000000000..c82f1e8bf051d7 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "reauth": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/uk.json b/homeassistant/components/sharkiq/translations/uk.json new file mode 100644 index 00000000000000..0f78c62fa7ecea --- /dev/null +++ b/homeassistant/components/sharkiq/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \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", + "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", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ca.json b/homeassistant/components/shelly/translations/ca.json index 2bf17c2ba77c28..c2df82c0b16961 100644 --- a/homeassistant/components/shelly/translations/ca.json +++ b/homeassistant/components/shelly/translations/ca.json @@ -27,5 +27,21 @@ "description": "Abans de configurar-lo, els dispositius amb bateria s'han de desperar prement el bot\u00f3 del dispositiu." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Bot\u00f3", + "button1": "Primer bot\u00f3", + "button2": "Segon bot\u00f3", + "button3": "Tercer bot\u00f3" + }, + "trigger_type": { + "double": "{subtype} clicat dues vegades", + "long": "{subtype} clicat durant una estona", + "long_single": "{subtype} clicat durant una estona i despr\u00e9s r\u00e0pid", + "single": "{subtype} clicat una vegada", + "single_long": "{subtype} clicat r\u00e0pid i, despr\u00e9s, durant una estona", + "triple": "{subtype} clicat tres vegades" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/cs.json b/homeassistant/components/shelly/translations/cs.json index 41ca338ab9e6ab..afdfe7c8f56ab7 100644 --- a/homeassistant/components/shelly/translations/cs.json +++ b/homeassistant/components/shelly/translations/cs.json @@ -27,5 +27,21 @@ "description": "P\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." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Tla\u010d\u00edtko", + "button1": "Prvn\u00ed tla\u010d\u00edtko", + "button2": "Druh\u00e9 tla\u010d\u00edtko", + "button3": "T\u0159et\u00ed tla\u010d\u00edtko" + }, + "trigger_type": { + "double": "\"{subtype}\" stisknuto dvakr\u00e1t", + "long": "\"{subtype}\" stisknuto dlouze", + "long_single": "\"{subtype}\" stisknuto dlouze a pak jednou", + "single": "\"{subtype}\" stisknuto jednou", + "single_long": "\"{subtype}\" stisknuto jednou a pak dlouze", + "triple": "\"{subtype}\" stisknuto t\u0159ikr\u00e1t" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/da.json b/homeassistant/components/shelly/translations/da.json new file mode 100644 index 00000000000000..08631bc39e132e --- /dev/null +++ b/homeassistant/components/shelly/translations/da.json @@ -0,0 +1,17 @@ +{ + "device_automation": { + "trigger_subtype": { + "button": "Knap", + "button1": "F\u00f8rste knap", + "button2": "Anden knap", + "button3": "Tredje knap" + }, + "trigger_type": { + "double": "{subtype} dobbelt klik", + "long": "{subtype} langt klik", + "long_single": "{subtype} langt klik og derefter enkelt klik", + "single": "{subtype} enkelt klik", + "single_long": "{subtype} enkelt klik og derefter langt klik" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/de.json b/homeassistant/components/shelly/translations/de.json index 74d0f831c8bd33..4764936a41bd26 100644 --- a/homeassistant/components/shelly/translations/de.json +++ b/homeassistant/components/shelly/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Shelly: {name}", @@ -15,7 +19,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Vor der Einrichtung m\u00fcssen batteriebetriebene Ger\u00e4te durch Dr\u00fccken der Taste am Ger\u00e4t aufgeweckt werden." } } } diff --git a/homeassistant/components/shelly/translations/en.json b/homeassistant/components/shelly/translations/en.json index a1fa6b72598eb1..a9ad6092a08cfa 100644 --- a/homeassistant/components/shelly/translations/en.json +++ b/homeassistant/components/shelly/translations/en.json @@ -31,17 +31,17 @@ "device_automation": { "trigger_subtype": { "button": "Button", - "button1": "First button", + "button1": "First button", "button2": "Second button", "button3": "Third button" }, - "trigger_type": { - "single": "{subtype} single clicked", - "double": "{subtype} double clicked", - "triple": "{subtype} triple clicked", - "long":" {subtype} long clicked", - "single_long": "{subtype} single clicked and then long clicked", - "long_single": "{subtype} long clicked and then single clicked" + "trigger_type": { + "double": "{subtype} double clicked", + "long": " {subtype} long clicked", + "long_single": "{subtype} long clicked and then single clicked", + "single": "{subtype} single clicked", + "single_long": "{subtype} single clicked and then long clicked", + "triple": "{subtype} triple clicked" } } -} +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/es.json b/homeassistant/components/shelly/translations/es.json index 38c72f21dca04b..09cc3f51378445 100644 --- a/homeassistant/components/shelly/translations/es.json +++ b/homeassistant/components/shelly/translations/es.json @@ -27,5 +27,21 @@ "description": "Antes de configurarlo, el dispositivo que funciona con bater\u00eda debe despertarse presionando el bot\u00f3n del dispositivo." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Bot\u00f3n", + "button1": "Primer bot\u00f3n", + "button2": "Segundo bot\u00f3n", + "button3": "Tercer bot\u00f3n" + }, + "trigger_type": { + "double": "Pulsaci\u00f3n doble de {subtype}", + "long": "Pulsaci\u00f3n larga de {subtype}", + "long_single": "Pulsaci\u00f3n larga de {subtype} seguida de una pulsaci\u00f3n simple", + "single": "Pulsaci\u00f3n simple de {subtype}", + "single_long": "Pulsaci\u00f3n simple de {subtype} seguida de una pulsaci\u00f3n larga", + "triple": "Pulsaci\u00f3n triple de {subtype}" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/et.json b/homeassistant/components/shelly/translations/et.json index 12a662f6560a76..d2514876a81223 100644 --- a/homeassistant/components/shelly/translations/et.json +++ b/homeassistant/components/shelly/translations/et.json @@ -27,5 +27,21 @@ "description": "Enne seadistamist tuleb akutoitega seade \u00e4ratada vajutades seadme nuppu." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Nupp", + "button1": "Esimene nupp", + "button2": "Teine nupp", + "button3": "Kolmas nupp" + }, + "trigger_type": { + "double": "Nuppu {subtype} topeltkl\u00f5psati", + "long": "Nuppu \"{subtype}\" hoiti all", + "long_single": "Nuppu {subtype} hoiti all ja seej\u00e4rel kl\u00f5psati", + "single": "Nuppu {subtype} kl\u00f5psati", + "single_long": "Nuppu {subtype} kl\u00f5psati \u00fcks kord ja seej\u00e4rel hoiti all", + "triple": "Nuppu {subtype} kl\u00f5psati kolm korda" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/it.json b/homeassistant/components/shelly/translations/it.json index 61f2f8ccd09661..4d486a8f2fab4c 100644 --- a/homeassistant/components/shelly/translations/it.json +++ b/homeassistant/components/shelly/translations/it.json @@ -27,5 +27,21 @@ "description": "Prima della configurazione, i dispositivi alimentati a batteria devono essere riattivati premendo il pulsante sul dispositivo." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Pulsante", + "button1": "Primo pulsante", + "button2": "Secondo pulsante", + "button3": "Terzo pulsante" + }, + "trigger_type": { + "double": "{subtype} premuto due volte", + "long": "{subtype} premuto a lungo", + "long_single": "{subtype} premuto a lungo e poi singolarmente", + "single": "{subtype} premuto singolarmente", + "single_long": "{subtype} premuto singolarmente e poi a lungo", + "triple": "{subtype} premuto tre volte" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/lb.json b/homeassistant/components/shelly/translations/lb.json index b358c1c728269f..e6c5d8330c674e 100644 --- a/homeassistant/components/shelly/translations/lb.json +++ b/homeassistant/components/shelly/translations/lb.json @@ -27,5 +27,13 @@ "description": "Virum ariichten muss dat Batterie bedriwwen Ger\u00e4t aktiv\u00e9iert ginn andeems de Kn\u00e4ppchen um Apparat gedr\u00e9ckt g\u00ebtt." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Kn\u00e4ppchen", + "button1": "\u00c9ischte Kn\u00e4ppchen", + "button2": "Zweete Kn\u00e4ppchen", + "button3": "Dr\u00ebtte Kn\u00e4ppchen" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/no.json b/homeassistant/components/shelly/translations/no.json index 705c494a4c11b3..1606a1acbb1bcc 100644 --- a/homeassistant/components/shelly/translations/no.json +++ b/homeassistant/components/shelly/translations/no.json @@ -27,5 +27,21 @@ "description": "F\u00f8r du setter opp, m\u00e5 batteridrevne enheter vekkes ved \u00e5 trykke p\u00e5 knappen p\u00e5 enheten." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Knapp", + "button1": "F\u00f8rste knapp", + "button2": "Andre knapp", + "button3": "Tredje knapp" + }, + "trigger_type": { + "double": "{subtype} dobbeltklikket", + "long": "{subtype} lenge klikket", + "long_single": "{subtype} lengre klikk og deretter et enkeltklikk", + "single": "{subtype} enkeltklikket", + "single_long": "{subtype} enkeltklikket og deretter et lengre klikk", + "triple": "{subtype} trippelklikket" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index ebf6041d4bac8f..cd8ffac71386e4 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -27,5 +27,21 @@ "description": "Przed skonfigurowaniem urz\u0105dzenia zasilane bateryjnie nale\u017cy, wybudzi\u0107 naciskaj\u0105c przycisk na urz\u0105dzeniu." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Przycisk", + "button1": "pierwszy", + "button2": "drugi", + "button3": "trzeci" + }, + "trigger_type": { + "double": "przycisk \"{subtype}\" zostanie dwukrotnie naci\u015bni\u0119ty", + "long": "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", + "triple": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index 508a189b849771..5a3a40ac9f8cfb 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -27,5 +27,21 @@ "description": "\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "\u041a\u043d\u043e\u043f\u043a\u0430", + "button1": "\u041f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button2": "\u0412\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button3": "\u0422\u0440\u0435\u0442\u044c\u044f \u043a\u043d\u043e\u043f\u043a\u0430" + }, + "trigger_type": { + "double": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430", + "long": "{subtype} \u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0430", + "long_single": "{subtype} \u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0430 \u0438 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437", + "single": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437", + "single_long": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0438 \u0437\u0430\u0442\u0435\u043c \u0434\u043e\u043b\u0433\u043e \u043d\u0430\u0436\u0430\u0442\u0430", + "triple": "{subtype} \u043d\u0430\u0436\u0430\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0430" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/tr.json b/homeassistant/components/shelly/translations/tr.json new file mode 100644 index 00000000000000..f577c73787f521 --- /dev/null +++ b/homeassistant/components/shelly/translations/tr.json @@ -0,0 +1,41 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "{name}", + "step": { + "credentials": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "D\u00fc\u011fme", + "button1": "\u0130lk d\u00fc\u011fme", + "button2": "\u0130kinci d\u00fc\u011fme", + "button3": "\u00dc\u00e7\u00fcnc\u00fc d\u00fc\u011fme" + }, + "trigger_type": { + "double": "{subtype} \u00e7ift t\u0131kland\u0131", + "long": "{subtype} uzun t\u0131kland\u0131", + "long_single": "{subtype} uzun t\u0131kland\u0131 ve ard\u0131ndan tek t\u0131kland\u0131", + "single": "{subtype} tek t\u0131kland\u0131", + "triple": "{subtype} \u00fc\u00e7 kez t\u0131kland\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/uk.json b/homeassistant/components/shelly/translations/uk.json new file mode 100644 index 00000000000000..7ad70b0f0da915 --- /dev/null +++ b/homeassistant/components/shelly/translations/uk.json @@ -0,0 +1,47 @@ +{ + "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.", + "unsupported_firmware": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454 \u043d\u0435\u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0443 \u0432\u0435\u0440\u0441\u0456\u044e \u043c\u0456\u043a\u0440\u043e\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "{name}", + "step": { + "confirm_discovery": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 {model} ({host})? \n\n\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457, \u0449\u043e \u043f\u0440\u0430\u0446\u044e\u044e\u0442\u044c \u0432\u0456\u0434 \u0431\u0430\u0442\u0430\u0440\u0435\u0457, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0432\u0435\u0441\u0442\u0438 \u0437\u0456 \u0441\u043f\u043b\u044f\u0447\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0443, \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0432\u0448\u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457." + }, + "credentials": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457, \u0449\u043e \u043f\u0440\u0430\u0446\u044e\u044e\u0442\u044c \u0432\u0456\u0434 \u0431\u0430\u0442\u0430\u0440\u0435\u0457, \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0432\u0435\u0441\u0442\u0438 \u0437\u0456 \u0441\u043f\u043b\u044f\u0447\u043e\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0443, \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0432\u0448\u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "\u041a\u043d\u043e\u043f\u043a\u0430", + "button1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430" + }, + "trigger_type": { + "double": "{subtype} \u043f\u043e\u0434\u0432\u0456\u0439\u043d\u0438\u0439 \u043a\u043b\u0456\u043a", + "long": "{subtype} \u0434\u043e\u0432\u0433\u0438\u0439 \u043a\u043b\u0456\u043a", + "long_single": "{subtype} \u0434\u043e\u0432\u0433\u0438\u0439 \u043a\u043b\u0456\u043a, \u0430 \u043f\u043e\u0442\u0456\u043c \u043e\u0434\u0438\u043d \u043a\u043b\u0456\u043a", + "single": "{subtype} \u043e\u0434\u0438\u043d\u0430\u0440\u043d\u0438\u0439 \u043a\u043b\u0456\u043a", + "single_long": "{subtype} \u043e\u0434\u0438\u043d\u0430\u0440\u043d\u0438\u0439 \u043a\u043b\u0456\u043a, \u043f\u043e\u0442\u0456\u043c \u0434\u043e\u0432\u0433\u0438\u0439 \u043a\u043b\u0456\u043a", + "triple": "{subtype} \u043f\u043e\u0442\u0440\u0456\u0439\u043d\u0438\u0439 \u043a\u043b\u0456\u043a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/zh-Hant.json b/homeassistant/components/shelly/translations/zh-Hant.json index bf0150523b3e39..8f3152081354cc 100644 --- a/homeassistant/components/shelly/translations/zh-Hant.json +++ b/homeassistant/components/shelly/translations/zh-Hant.json @@ -27,5 +27,21 @@ "description": "\u958b\u59cb\u8a2d\u5b9a\u524d\uff0c\u5fc5\u9808\u6309\u4e0b\u88dd\u7f6e\u4e0a\u7684\u6309\u9215\u4ee5\u559a\u9192\u96fb\u6c60\u4f9b\u96fb\u88dd\u7f6e\u3002" } } + }, + "device_automation": { + "trigger_subtype": { + "button": "\u6309\u9215", + "button1": "\u7b2c\u4e00\u500b\u6309\u9215", + "button2": "\u7b2c\u4e8c\u500b\u6309\u9215", + "button3": "\u7b2c\u4e09\u500b\u6309\u9215" + }, + "trigger_type": { + "double": "{subtype} \u96d9\u64ca", + "long": "{subtype} \u9577\u6309", + "long_single": "{subtype} \u9577\u6309\u5f8c\u55ae\u64ca", + "single": "{subtype} \u55ae\u64ca", + "single_long": "{subtype} \u55ae\u64ca\u5f8c\u9577\u6309", + "triple": "{subtype} \u4e09\u9023\u64ca" + } } } \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/de.json b/homeassistant/components/shopping_list/translations/de.json index d2d6a42fe249a8..68372e9f4ac8df 100644 --- a/homeassistant/components/shopping_list/translations/de.json +++ b/homeassistant/components/shopping_list/translations/de.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Die Einkaufsliste ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/tr.json b/homeassistant/components/shopping_list/translations/tr.json new file mode 100644 index 00000000000000..d139d2f6399971 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "user": { + "description": "Al\u0131\u015fveri\u015f listesini yap\u0131land\u0131rmak istiyor musunuz?", + "title": "Al\u0131\u015fveri\u015f listesi" + } + } + }, + "title": "Al\u0131\u015fveri\u015f listesi" +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/uk.json b/homeassistant/components/shopping_list/translations/uk.json new file mode 100644 index 00000000000000..b73bd6c702a048 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/uk.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a?", + "title": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a" + } + } + }, + "title": "\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a" +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/de.json b/homeassistant/components/simplisafe/translations/de.json index ab05cf649d8334..5914e8f680c83b 100644 --- a/homeassistant/components/simplisafe/translations/de.json +++ b/homeassistant/components/simplisafe/translations/de.json @@ -1,17 +1,21 @@ { "config": { "abort": { - "already_configured": "Dieses SimpliSafe-Konto wird bereits verwendet." + "already_configured": "Dieses SimpliSafe-Konto wird bereits verwendet.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "identifier_exists": "Konto bereits registriert", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { "reauth_confirm": { "data": { "password": "Passwort" - } + }, + "description": "Dein Zugriffstoken ist abgelaufen oder wurde widerrufen. Gib dein Passwort ein, um dein Konto erneut zu verkn\u00fcpfen.", + "title": "Integration erneut authentifizieren" }, "user": { "data": { diff --git a/homeassistant/components/simplisafe/translations/tr.json b/homeassistant/components/simplisafe/translations/tr.json index ec84b1b7c1c51b..94506fb426bba8 100644 --- a/homeassistant/components/simplisafe/translations/tr.json +++ b/homeassistant/components/simplisafe/translations/tr.json @@ -1,6 +1,26 @@ { "config": { + "abort": { + "already_configured": "Bu SimpliSafe hesab\u0131 zaten kullan\u0131mda.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "still_awaiting_mfa": "Hala MFA e-posta t\u0131klamas\u0131 bekleniyor", + "unknown": "Beklenmeyen hata" + }, "step": { + "mfa": { + "description": "SimpliSafe'den bir ba\u011flant\u0131 i\u00e7in e-postan\u0131z\u0131 kontrol edin. Ba\u011flant\u0131y\u0131 do\u011frulad\u0131ktan sonra, entegrasyonun kurulumunu tamamlamak i\u00e7in buraya geri d\u00f6n\u00fcn.", + "title": "SimpliSafe \u00c7ok Fakt\u00f6rl\u00fc Kimlik Do\u011frulama" + }, + "reauth_confirm": { + "data": { + "password": "Parola" + }, + "description": "Eri\u015fim kodunuzun s\u00fcresi doldu veya iptal edildi. Hesab\u0131n\u0131z\u0131 yeniden ba\u011flamak i\u00e7in parolan\u0131z\u0131 girin.", + "title": "Entegrasyonu Yeniden Do\u011frula" + }, "user": { "data": { "password": "Parola", diff --git a/homeassistant/components/simplisafe/translations/uk.json b/homeassistant/components/simplisafe/translations/uk.json index 376fb4468dbf48..0a51f129e5fd26 100644 --- a/homeassistant/components/simplisafe/translations/uk.json +++ b/homeassistant/components/simplisafe/translations/uk.json @@ -1,18 +1,45 @@ { "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant.", + "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" + }, + "error": { + "identifier_exists": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "still_awaiting_mfa": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f, \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e\u0433\u043e \u043f\u043e \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0456\u0439 \u043f\u043e\u0448\u0442\u0456.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, "step": { + "mfa": { + "description": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0441\u0432\u043e\u044e \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u0443 \u043f\u043e\u0448\u0442\u0443 \u043d\u0430 \u043d\u0430\u044f\u0432\u043d\u0456\u0441\u0442\u044c \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u0432\u0456\u0434 SimpliSafe. \u041f\u0456\u0441\u043b\u044f \u0442\u043e\u0433\u043e \u044f\u043a \u0432\u0456\u0434\u043a\u0440\u0438\u0454\u0442\u0435 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f, \u043f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438, \u0449\u043e\u0431 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457.", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f SimpliSafe" + }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" - } + }, + "description": "\u0412\u0430\u0448 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0437\u0430\u043a\u0456\u043d\u0447\u0438\u0432\u0441\u044f \u0430\u0431\u043e \u0431\u0443\u0432 \u0430\u043d\u0443\u043b\u044c\u043e\u0432\u0430\u043d\u0438\u0439. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u0449\u043e\u0431 \u0437\u0430\u043d\u043e\u0432\u043e \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" }, "user": { "data": { + "code": "\u041a\u043e\u0434 (\u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Home Assistant)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" }, "title": "\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e" } } + }, + "options": { + "step": { + "init": { + "data": { + "code": "\u041a\u043e\u0434 (\u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 Home Assistant)" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f SimpliSafe" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/de.json b/homeassistant/components/smappee/translations/de.json index a609492f4285ae..15fd8d6cd22da1 100644 --- a/homeassistant/components/smappee/translations/de.json +++ b/homeassistant/components/smappee/translations/de.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler" + "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "cannot_connect": "Verbindung fehlgeschlagen", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "flow_title": "Smappee: {name}", "step": { @@ -9,6 +13,14 @@ "data": { "environment": "Umgebung" } + }, + "local": { + "data": { + "host": "Host" + } + }, + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" } } } diff --git a/homeassistant/components/smappee/translations/tr.json b/homeassistant/components/smappee/translations/tr.json new file mode 100644 index 00000000000000..4ba8a4da9a6ca5 --- /dev/null +++ b/homeassistant/components/smappee/translations/tr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_configured_local_device": "Yerel ayg\u0131t (lar) zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. L\u00fctfen bir bulut cihaz\u0131n\u0131 yap\u0131land\u0131rmadan \u00f6nce bunlar\u0131 kald\u0131r\u0131n.", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_mdns": "Smappee entegrasyonu i\u00e7in desteklenmeyen cihaz." + }, + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "\u00c7evre" + } + }, + "local": { + "data": { + "host": "Ana Bilgisayar" + } + }, + "zeroconf_confirm": { + "title": "Smappee cihaz\u0131 bulundu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/uk.json b/homeassistant/components/smappee/translations/uk.json new file mode 100644 index 00000000000000..a268fa82eacd3f --- /dev/null +++ b/homeassistant/components/smappee/translations/uk.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured_device": "\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_local_device": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435 \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432. \u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0457\u0445 \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0445\u043c\u0430\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e.", + "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.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_mdns": "\u041d\u0435\u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0438\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439.", + "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." + }, + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "\u041e\u0442\u043e\u0447\u0435\u043d\u043d\u044f" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Smappee." + }, + "local": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430, \u0449\u043e\u0431 \u043f\u043e\u0447\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e \u0437 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0438\u043c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c Smappee" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Smappee \u0437 \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serialnumber}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Smappee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/de.json b/homeassistant/components/smart_meter_texas/translations/de.json index 382156757010d5..0eee2778d05365 100644 --- a/homeassistant/components/smart_meter_texas/translations/de.json +++ b/homeassistant/components/smart_meter_texas/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/smart_meter_texas/translations/tr.json b/homeassistant/components/smart_meter_texas/translations/tr.json new file mode 100644 index 00000000000000..6ed28a58c793c2 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "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/uk.json b/homeassistant/components/smart_meter_texas/translations/uk.json new file mode 100644 index 00000000000000..49bceaa3f6ea94 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/de.json b/homeassistant/components/smarthab/translations/de.json index 2c76c4d56db9a0..18bb2c77047dc1 100644 --- a/homeassistant/components/smarthab/translations/de.json +++ b/homeassistant/components/smarthab/translations/de.json @@ -1,6 +1,7 @@ { "config": { "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/smarthab/translations/tr.json b/homeassistant/components/smarthab/translations/tr.json new file mode 100644 index 00000000000000..98da6384f8d2e4 --- /dev/null +++ b/homeassistant/components/smarthab/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "email": "E-posta", + "password": "Parola" + }, + "title": "SmartHab'\u0131 kurun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/uk.json b/homeassistant/components/smarthab/translations/uk.json new file mode 100644 index 00000000000000..036ec0a78d4d84 --- /dev/null +++ b/homeassistant/components/smarthab/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "service": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0441\u043f\u0440\u043e\u0431\u0456 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e SmartHab. \u0421\u0435\u0440\u0432\u0456\u0441 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0417 \u0442\u0435\u0445\u043d\u0456\u0447\u043d\u0438\u0445 \u043f\u0440\u0438\u0447\u0438\u043d \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0438\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0434\u043b\u044f Home Assistant. \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0442\u0432\u043e\u0440\u0438\u0442\u0438 \u0457\u0457 \u0432 \u0434\u043e\u0434\u0430\u0442\u043a\u0443 SmartHab.", + "title": "SmartHab" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/tr.json b/homeassistant/components/smartthings/translations/tr.json new file mode 100644 index 00000000000000..5e7463c1c7443e --- /dev/null +++ b/homeassistant/components/smartthings/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "webhook_error": "SmartThings, webhook URL'sini do\u011frulayamad\u0131. L\u00fctfen webhook URL'sinin internetten eri\u015filebilir oldu\u011fundan emin olun ve tekrar deneyin." + }, + "step": { + "pat": { + "data": { + "access_token": "Eri\u015fim Belirteci" + } + }, + "select_location": { + "title": "Konum Se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/uk.json b/homeassistant/components/smartthings/translations/uk.json new file mode 100644 index 00000000000000..6f8a0ed47446ec --- /dev/null +++ b/homeassistant/components/smartthings/translations/uk.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "invalid_webhook_url": "Webhook URL, \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c \u0432\u0456\u0434 SmartThings, \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439:\n > {webhook_url} \n\n\u041e\u043d\u043e\u0432\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e \u0434\u043e [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439] ({component_url}), \u0430 \u043f\u0456\u0441\u043b\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0443 Home Assistant \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "no_available_locations": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0456\u0441\u0446\u044c \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f SmartThings." + }, + "error": { + "app_setup_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 SmartApp. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "token_forbidden": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u0438\u0439 \u0434\u043b\u044f OAuth.", + "token_invalid_format": "\u0422\u043e\u043a\u0435\u043d \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 UID / GUID.", + "token_unauthorized": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u0430\u0431\u043e \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u0438\u0439.", + "webhook_error": "SmartThings \u043d\u0435 \u043c\u043e\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 Webhook URL. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 Webhook URL \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0456\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." + }, + "step": { + "authorize": { + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f Home Assistant" + }, + "pat": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c [\u041e\u0441\u043e\u0431\u0438\u0441\u0442\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 SmartThings] ({token_url}), \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e \u0434\u043e [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457] ({component_url}).", + "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "select_location": { + "data": { + "location_id": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0446\u0435 \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f SmartThings, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0432 Home Assistant. \u041f\u0456\u0441\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0432\u0456\u0434\u043a\u0440\u0438\u0454\u0442\u044c\u0441\u044f \u043d\u043e\u0432\u0435 \u0432\u0456\u043a\u043d\u043e, \u0434\u0435 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0431\u0443\u0434\u0435 \u0443\u0432\u0456\u0439\u0442\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0442\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Home Assistant \u0432 \u043e\u0431\u0440\u0430\u043d\u043e\u043c\u0443 \u043c\u0456\u0441\u0446\u0456 \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f.", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" + }, + "user": { + "description": "SmartThings \u0431\u0443\u0434\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 push-\u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e:\n> {webhook_url} \n\n\u042f\u043a\u0449\u043e \u0446\u0435 \u043d\u0435 \u0442\u0430\u043a, \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c Home Assistant \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", + "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f Callback URL" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/uk.json b/homeassistant/components/smhi/translations/uk.json new file mode 100644 index 00000000000000..24af32172baf37 --- /dev/null +++ b/homeassistant/components/smhi/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", + "wrong_location": "\u0422\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0428\u0432\u0435\u0446\u0456\u0457." + }, + "step": { + "user": { + "data": { + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "title": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432 \u0428\u0432\u0435\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/de.json b/homeassistant/components/sms/translations/de.json index 1252313a438e23..b262df1486d24e 100644 --- a/homeassistant/components/sms/translations/de.json +++ b/homeassistant/components/sms/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/sms/translations/tr.json b/homeassistant/components/sms/translations/tr.json new file mode 100644 index 00000000000000..1ef2efb8121651 --- /dev/null +++ b/homeassistant/components/sms/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "title": "Modeme ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/uk.json b/homeassistant/components/sms/translations/uk.json new file mode 100644 index 00000000000000..be271a2b6e4896 --- /dev/null +++ b/homeassistant/components/sms/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "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": { + "device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + }, + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/fr.json b/homeassistant/components/solaredge/translations/fr.json index f8aec1fa2308f7..6fa6fdf264ffa7 100644 --- a/homeassistant/components/solaredge/translations/fr.json +++ b/homeassistant/components/solaredge/translations/fr.json @@ -4,7 +4,9 @@ "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9" }, "error": { - "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9" + "invalid_api_key": "Cl\u00e9 API invalide", + "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9", + "site_not_active": "The site n'est pas actif" }, "step": { "user": { diff --git a/homeassistant/components/solaredge/translations/lb.json b/homeassistant/components/solaredge/translations/lb.json index 4f2f698a6ca751..709a57f070b868 100644 --- a/homeassistant/components/solaredge/translations/lb.json +++ b/homeassistant/components/solaredge/translations/lb.json @@ -1,10 +1,13 @@ { "config": { "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", "site_exists": "D\u00ebs site_id ass scho konfigur\u00e9iert" }, "error": { - "site_exists": "D\u00ebs site_id ass scho konfigur\u00e9iert" + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "site_exists": "D\u00ebs site_id ass scho konfigur\u00e9iert", + "site_not_active": "De Site ass net aktiv" }, "step": { "user": { diff --git a/homeassistant/components/solaredge/translations/tr.json b/homeassistant/components/solaredge/translations/tr.json index 5307276a71d3a3..b8159be58b48c4 100644 --- a/homeassistant/components/solaredge/translations/tr.json +++ b/homeassistant/components/solaredge/translations/tr.json @@ -2,6 +2,19 @@ "config": { "abort": { "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "could_not_connect": "Solaredge API'ye ba\u011flan\u0131lamad\u0131", + "invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131", + "site_not_active": "Site aktif de\u011fil" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/uk.json b/homeassistant/components/solaredge/translations/uk.json new file mode 100644 index 00000000000000..5ad67d8768001b --- /dev/null +++ b/homeassistant/components/solaredge/translations/uk.json @@ -0,0 +1,25 @@ +{ + "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.", + "site_exists": "\u0426\u0435\u0439 site_id \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439." + }, + "error": { + "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.", + "could_not_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 API Solaredge.", + "invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API", + "site_exists": "\u0426\u0435\u0439 site_id \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439.", + "site_not_active": "\u0421\u0430\u0439\u0442 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u0438\u0439." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "name": "\u041d\u0430\u0437\u0432\u0430", + "site_id": "site-id" + }, + "title": "SolarEdge" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/de.json b/homeassistant/components/solarlog/translations/de.json index 58e691b733d181..008e10586819fa 100644 --- a/homeassistant/components/solarlog/translations/de.json +++ b/homeassistant/components/solarlog/translations/de.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindung fehlgeschlagen. \u00dcberpr\u00fcfe die Host-Adresse" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/tr.json b/homeassistant/components/solarlog/translations/tr.json new file mode 100644 index 00000000000000..a11d3815eed8ca --- /dev/null +++ b/homeassistant/components/solarlog/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/uk.json b/homeassistant/components/solarlog/translations/uk.json new file mode 100644 index 00000000000000..f4fca695032917 --- /dev/null +++ b/homeassistant/components/solarlog/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041f\u0440\u0435\u0444\u0456\u043a\u0441, \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0434\u043b\u044f \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 Solar-Log" + }, + "title": "Solar-Log" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/tr.json b/homeassistant/components/soma/translations/tr.json new file mode 100644 index 00000000000000..21a477c75a79c4 --- /dev/null +++ b/homeassistant/components/soma/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/uk.json b/homeassistant/components/soma/translations/uk.json new file mode 100644 index 00000000000000..0ec98301d62c93 --- /dev/null +++ b/homeassistant/components/soma/translations/uk.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_setup": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435.", + "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.", + "connection_error": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 SOMA Connect.", + "missing_configuration": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Soma \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439. \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.", + "result_error": "SOMA Connect \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0432 \u0437\u0456 \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u043f\u043e\u043c\u0438\u043b\u043a\u0438." + }, + "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." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e SOMA Connect.", + "title": "SOMA Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/de.json b/homeassistant/components/somfy/translations/de.json index 6b76e2f61befcc..29a959f48ce140 100644 --- a/homeassistant/components/somfy/translations/de.json +++ b/homeassistant/components/somfy/translations/de.json @@ -2,10 +2,12 @@ "config": { "abort": { "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", - "missing_configuration": "Die Somfy-Komponente ist nicht konfiguriert. Folge bitte der Dokumentation." + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "create_entry": { - "default": "Erfolgreich mit Somfy authentifiziert." + "default": "Erfolgreich authentifiziert" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy/translations/tr.json b/homeassistant/components/somfy/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/somfy/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/uk.json b/homeassistant/components/somfy/translations/uk.json new file mode 100644 index 00000000000000..ebf7e41044eef6 --- /dev/null +++ b/homeassistant/components/somfy/translations/uk.json @@ -0,0 +1,18 @@ +{ + "config": { + "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.", + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ca.json b/homeassistant/components/somfy_mylink/translations/ca.json new file mode 100644 index 00000000000000..93ae58ca2bfe6b --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/ca.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port", + "system_id": "ID del sistema" + }, + "description": "L'ID de sistema es pot obtenir des de l'aplicaci\u00f3 MyLink dins de Integraci\u00f3, seleccionant qualsevol servei que no sigui al n\u00favol." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La coberta est\u00e0 invertida" + }, + "description": "Opcions de configuraci\u00f3 de `{entity_id}`", + "title": "Configura l'entitat" + }, + "init": { + "data": { + "default_reverse": "Estat d'inversi\u00f3 predeterminat per a cobertes sense configurar", + "entity_id": "Configura una entitat espec\u00edfica.", + "target_id": "Opcions de configuraci\u00f3 de la coberta." + }, + "title": "Configura opcions de MyLink" + }, + "target_config": { + "data": { + "reverse": "La coberta est\u00e0 invertida" + }, + "description": "Opcions de configuraci\u00f3 de `{target_name}`", + "title": "Configura coberta MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/cs.json b/homeassistant/components/somfy_mylink/translations/cs.json new file mode 100644 index 00000000000000..71e05b51544574 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/cs.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Hostitel", + "port": "Port" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/de.json b/homeassistant/components/somfy_mylink/translations/de.json new file mode 100644 index 00000000000000..522e185af5de0b --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/de.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "System-ID" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "entity_config": { + "title": "Entit\u00e4t konfigurieren" + }, + "init": { + "title": "MyLink-Optionen konfigurieren" + }, + "target_config": { + "description": "Konfiguriere die Optionen f\u00fcr `{target_name}`", + "title": "MyLink-Cover konfigurieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/en.json b/homeassistant/components/somfy_mylink/translations/en.json index ca3d83e402b35b..13115b36e5c26e 100644 --- a/homeassistant/components/somfy_mylink/translations/en.json +++ b/homeassistant/components/somfy_mylink/translations/en.json @@ -1,44 +1,53 @@ { - "title": "Somfy MyLink", - "config": { - "flow_title": "Somfy MyLink {mac} ({ip})", - "step": { - "user": { - "description": "The System ID can be obtained in the MyLink app under Integration by selecting any non-Cloud service.", - "data": { - "host": "[%key:common::config_flow::data::host%]", - "port": "[%key:common::config_flow::data::port%]", - "system_id": "System ID" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "System ID" + }, + "description": "The System ID can be obtained in the MyLink app under Integration by selecting any non-Cloud service." + } } - } }, - "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%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" - } - }, - "options": { - "abort": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" - }, - "step": { - "init": { - "title": "Configure MyLink Options", - "data": { - "target_id": "Configure options for a cover." + "options": { + "abort": { + "cannot_connect": "Failed to connect" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Cover is reversed" + }, + "description": "Configure options for `{entity_id}`", + "title": "Configure Entity" + }, + "init": { + "data": { + "default_reverse": "Default reversal status for unconfigured covers", + "entity_id": "Configure a specific entity.", + "target_id": "Configure options for a cover." + }, + "title": "Configure MyLink Options" + }, + "target_config": { + "data": { + "reverse": "Cover is reversed" + }, + "description": "Configure options for `{target_name}`", + "title": "Configure MyLink Cover" + } } - }, - "target_config": { - "title": "Configure MyLink Cover", - "description": "Configure options for `{target_name}`", - "data": { - "reverse": "Cover is reversed" - } - } - } - } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/es.json b/homeassistant/components/somfy_mylink/translations/es.json new file mode 100644 index 00000000000000..40d82a4522a89c --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/es.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto", + "system_id": "ID del sistema" + }, + "description": "El ID del sistema se puede obtener en la aplicaci\u00f3n MyLink en Integraci\u00f3n seleccionando cualquier servicio que no sea de la nube." + } + } + }, + "options": { + "abort": { + "cannot_connect": "No se pudo conectar" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La cubierta est\u00e1 invertida" + }, + "description": "Configurar opciones para `{entity_id}`", + "title": "Configurar entidad" + }, + "init": { + "data": { + "default_reverse": "Estado de inversi\u00f3n predeterminado para cubiertas no configuradas", + "entity_id": "Configurar una entidad espec\u00edfica.", + "target_id": "Configurar opciones para una cubierta." + }, + "title": "Configurar opciones de MyLink" + }, + "target_config": { + "data": { + "reverse": "La cubierta est\u00e1 invertida" + }, + "description": "Configurar opciones para `{target_name}`", + "title": "Configurar la cubierta MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/et.json b/homeassistant/components/somfy_mylink/translations/et.json new file mode 100644 index 00000000000000..6d965220d7efc6 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/et.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "flow_title": "Somfy MyLink {mac} ( {ip} )", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "S\u00fcsteemi ID" + }, + "description": "S\u00fcsteemi ID saab rakenduse MyLink sidumise alt valides mis tahes mitte- pilveteenuse." + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "step": { + "entity_config": { + "data": { + "reverse": "(Akna)kate t\u00f6\u00f6tab vastupidi" + }, + "description": "Olemi {entity_id} suvandite seadmine", + "title": "Seadista olem" + }, + "init": { + "data": { + "default_reverse": "Seadistamata (akna)katete vaikep\u00f6\u00f6rduse olek", + "entity_id": "Seadista konkreetne olem.", + "target_id": "Seadista (akna)katte suvandid" + }, + "title": "Seadista MyLinki suvandid" + }, + "target_config": { + "data": { + "reverse": "(Akna)kate liigub vastupidi" + }, + "description": "Seadme `{target_name}` suvandite seadmine", + "title": "Seadista MyLink Cover" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/fr.json b/homeassistant/components/somfy_mylink/translations/fr.json new file mode 100644 index 00000000000000..96904b6038d7be --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/fr.json @@ -0,0 +1,43 @@ +{ + "config": { + "error": { + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Echec de connection" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La couverture est invers\u00e9e" + }, + "title": "Configurez une entit\u00e9 sp\u00e9cifique" + }, + "init": { + "data": { + "default_reverse": "Statut d'inversion par d\u00e9faut pour les couvertures non configur\u00e9es", + "entity_id": "Configurez une entit\u00e9 sp\u00e9cifique.", + "target_id": "Configurez les options pour la couverture." + }, + "title": "Configurer les options MyLink" + }, + "target_config": { + "data": { + "reverse": "La couverture est invers\u00e9e" + }, + "description": "Configurer les options pour \u00ab {target_name} \u00bb", + "title": "Configurer la couverture MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/it.json b/homeassistant/components/somfy_mylink/translations/it.json new file mode 100644 index 00000000000000..ce049782c43364 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/it.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Porta", + "system_id": "ID sistema" + }, + "description": "L'ID sistema pu\u00f2 essere ottenuto nell'app MyLink alla voce Integrazione selezionando qualsiasi servizio non-Cloud." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Impossibile connettersi" + }, + "step": { + "entity_config": { + "data": { + "reverse": "La tapparella \u00e8 invertita" + }, + "description": "Configura le opzioni per `{entity_id}`", + "title": "Configura entit\u00e0" + }, + "init": { + "data": { + "default_reverse": "Stato d'inversione predefinito per le tapparelle non configurate", + "entity_id": "Configura un'entit\u00e0 specifica.", + "target_id": "Configura opzioni per una tapparella" + }, + "title": "Configura le opzioni MyLink" + }, + "target_config": { + "data": { + "reverse": "La tapparella \u00e8 invertita" + }, + "description": "Configura le opzioni per `{target_name}`", + "title": "Configura tapparelle MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/lb.json b/homeassistant/components/somfy_mylink/translations/lb.json new file mode 100644 index 00000000000000..efaba3ab497400 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/lb.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "System ID" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/no.json b/homeassistant/components/somfy_mylink/translations/no.json new file mode 100644 index 00000000000000..5b9b6608c256fa --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/no.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "flow_title": "", + "step": { + "user": { + "data": { + "host": "Vert", + "port": "Port", + "system_id": "" + }, + "description": "System-ID-en kan f\u00e5s i MyLink-appen under Integrasjon ved \u00e5 velge en hvilken som helst ikke-Cloud-tjeneste." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Tilkobling mislyktes" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Rullegardinet reverseres" + }, + "description": "Konfigurer alternativer for \"{entity_id}\"", + "title": "Konfigurer enhet" + }, + "init": { + "data": { + "default_reverse": "Standard tilbakef\u00f8ringsstatus for ukonfigurerte rullegardiner", + "entity_id": "Konfigurer en bestemt enhet.", + "target_id": "Konfigurer alternativer for et rullgardin" + }, + "title": "Konfigurere MyLink-alternativer" + }, + "target_config": { + "data": { + "reverse": "Rullegardinet reverseres" + }, + "description": "Konfigurer alternativer for \"{target_name}\"", + "title": "Konfigurer MyLink-deksel" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/pl.json b/homeassistant/components/somfy_mylink/translations/pl.json new file mode 100644 index 00000000000000..7e49ecb2bcaa27 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/pl.json @@ -0,0 +1,53 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port", + "system_id": "Identyfikator systemu" + }, + "description": "Identyfikator systemu mo\u017cna uzyska\u0107 w aplikacji MyLink w sekcji Integracja, wybieraj\u0105c dowoln\u0105 us\u0142ug\u0119 spoza chmury." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Roleta/pokrywa jest odwr\u00f3cona" + }, + "description": "Konfiguracja opcji dla \"{entity_id}\"", + "title": "Konfigurowanie encji" + }, + "init": { + "data": { + "default_reverse": "Domy\u015blny stan odwr\u00f3cenia nieskonfigurowanych rolet/pokryw", + "entity_id": "Skonfiguruj okre\u015blon\u0105 encj\u0119.", + "target_id": "Konfiguracja opcji rolety" + }, + "title": "Konfiguracja opcji MyLink" + }, + "target_config": { + "data": { + "reverse": "Roleta/pokrywa jest odwr\u00f3cona" + }, + "description": "Konfiguracja opcji dla \"{target_name}\"", + "title": "Konfiguracja rolety MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ru.json b/homeassistant/components/somfy_mylink/translations/ru.json new file mode 100644 index 00000000000000..e4cc7b717128bf --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/ru.json @@ -0,0 +1,53 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "system_id": "System ID" + }, + "description": "System ID \u043c\u043e\u0436\u043d\u043e \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 MyLink \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u00ab\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u00bb, \u0432\u044b\u0431\u0440\u0430\u0432 \u043b\u044e\u0431\u0443\u044e \u043d\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u0443\u044e \u0441\u043b\u0443\u0436\u0431\u0443." + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "step": { + "entity_config": { + "data": { + "reverse": "\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f `{entity_id}`", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430" + }, + "init": { + "data": { + "default_reverse": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438", + "entity_id": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430", + "target_id": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0448\u0442\u043e\u0440 \u0438\u043b\u0438 \u0436\u0430\u043b\u044e\u0437\u0438." + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 MyLink" + }, + "target_config": { + "data": { + "reverse": "\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0448\u0442\u043e\u0440 \u0438 \u0436\u0430\u043b\u044e\u0437\u0438" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f `{target_name}`", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 MyLink Cover" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/tr.json b/homeassistant/components/somfy_mylink/translations/tr.json new file mode 100644 index 00000000000000..29530b65659cfb --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/tr.json @@ -0,0 +1,53 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "flow_title": "Somfy MyLink {mac} ( {ip} )", + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port", + "system_id": "Sistem ID" + }, + "description": "Sistem Kimli\u011fi, MyLink uygulamas\u0131nda Entegrasyon alt\u0131nda Bulut d\u0131\u015f\u0131 herhangi bir hizmet se\u00e7ilerek elde edilebilir." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Kapak ters \u00e7evrildi" + }, + "description": "'{entity_id}' i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n", + "title": "Varl\u0131\u011f\u0131 Yap\u0131land\u0131r" + }, + "init": { + "data": { + "default_reverse": "Yap\u0131land\u0131r\u0131lmam\u0131\u015f kapaklar i\u00e7in varsay\u0131lan geri alma durumu", + "entity_id": "Belirli bir varl\u0131\u011f\u0131 yap\u0131land\u0131r\u0131n.", + "target_id": "Kapak i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n." + }, + "title": "MyLink Se\u00e7eneklerini Yap\u0131land\u0131r\u0131n" + }, + "target_config": { + "data": { + "reverse": "Kapak ters \u00e7evrildi" + }, + "description": "'{target_name}' i\u00e7in se\u00e7enekleri yap\u0131land\u0131r\u0131n", + "title": "MyLink Kapa\u011f\u0131n\u0131 Yap\u0131land\u0131r\u0131n" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/uk.json b/homeassistant/components/somfy_mylink/translations/uk.json new file mode 100644 index 00000000000000..2d251531340042 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/uk.json @@ -0,0 +1,40 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "system_id": "System ID" + }, + "description": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0441\u0438\u0441\u0442\u0435\u043c\u0438 \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0456 MyLink \u0443 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 \u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f, \u0432\u0438\u0431\u0440\u0430\u0432\u0448\u0438 \u0431\u0443\u0434\u044c-\u044f\u043a\u0443 \u043d\u0435\u0445\u043c\u0430\u0440\u043d\u0443 \u0441\u043b\u0443\u0436\u0431\u0443." + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "entity_config": { + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0456\u0432 \u0434\u043b\u044f \"{entity_id}\"", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0441\u0443\u0442\u043d\u0456\u0441\u0442\u044c" + }, + "init": { + "data": { + "entity_id": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u043f\u0435\u0446\u0438\u0444\u0456\u0447\u043d\u043e\u0457 \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0456." + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0443\u0442\u043d\u043e\u0441\u0442\u0435\u0439 MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/zh-Hant.json b/homeassistant/components/somfy_mylink/translations/zh-Hant.json new file mode 100644 index 00000000000000..2abb6a64f7c8ab --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/zh-Hant.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0", + "system_id": "\u7cfb\u7d71 ID" + }, + "description": "\u7cfb\u7d71 ID \u53ef\u4ee5\u65bc\u6574\u5408\u5167\u7684 MyLink app \u9078\u64c7\u975e\u96f2\u7aef\u670d\u52d9\u4e2d\u627e\u5230\u3002" + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "step": { + "entity_config": { + "data": { + "reverse": "\u7a97\u7c3e\u53cd\u5411" + }, + "description": "`{entity_id}` \u8a2d\u5b9a\u9078\u9805", + "title": "\u8a2d\u5b9a\u5be6\u9ad4" + }, + "init": { + "data": { + "default_reverse": "\u672a\u8a2d\u5b9a\u7a97\u7c3e\u9810\u8a2d\u70ba\u53cd\u5411", + "entity_id": "\u8a2d\u5b9a\u7279\u5b9a\u5be6\u9ad4\u3002", + "target_id": "\u7a97\u7c3e\u8a2d\u5b9a\u9078\u9805\u3002" + }, + "title": "MyLink \u8a2d\u5b9a\u9078\u9805" + }, + "target_config": { + "data": { + "reverse": "\u7a97\u7c3e\u53cd\u5411" + }, + "description": "`{target_name}` \u8a2d\u5b9a\u9078\u9805", + "title": "\u8a2d\u5b9a MyLink \u7a97\u7c3e" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/de.json b/homeassistant/components/sonarr/translations/de.json index 3abc6b45ef39a9..19a37dbcc4f306 100644 --- a/homeassistant/components/sonarr/translations/de.json +++ b/homeassistant/components/sonarr/translations/de.json @@ -1,19 +1,27 @@ { "config": { "abort": { + "already_configured": "Der Dienst ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", "unknown": "Unerwateter Fehler" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "title": "Integration erneut authentifizieren" + }, "user": { "data": { "api_key": "API Schl\u00fcssel", "base_path": "Pfad zur API", "host": "Host", - "port": "Port" + "port": "Port", + "ssl": "Verwendet ein SSL-Zertifikat", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } } } diff --git a/homeassistant/components/sonarr/translations/tr.json b/homeassistant/components/sonarr/translations/tr.json new file mode 100644 index 00000000000000..eadf010004587c --- /dev/null +++ b/homeassistant/components/sonarr/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu", + "unknown": "Beklenmeyen hata" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "api_key": "API Anahtar\u0131", + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/uk.json b/homeassistant/components/sonarr/translations/uk.json new file mode 100644 index 00000000000000..0b6b7acf26decb --- /dev/null +++ b/homeassistant/components/sonarr/translations/uk.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "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", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "error": { + "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." + }, + "flow_title": "Sonarr: {name}", + "step": { + "reauth_confirm": { + "description": "\u041f\u043e\u0442\u0440\u0456\u0431\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e API Sonarr \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e: {host}", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "base_path": "\u0428\u043b\u044f\u0445 \u0434\u043e API", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "\u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043c\u0430\u0439\u0431\u0443\u0442\u043d\u0456\u0445 \u0434\u043d\u0456\u0432 \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f", + "wanted_max_items": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0456\u0432 \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/tr.json b/homeassistant/components/songpal/translations/tr.json new file mode 100644 index 00000000000000..ab90d4b1067d7c --- /dev/null +++ b/homeassistant/components/songpal/translations/tr.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "endpoint": "Biti\u015f noktas\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/uk.json b/homeassistant/components/songpal/translations/uk.json new file mode 100644 index 00000000000000..893077a826db7b --- /dev/null +++ b/homeassistant/components/songpal/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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.", + "not_songpal_device": "\u0426\u0435 \u043d\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 Songpal." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "flow_title": "Sony Songpal {name} ({host})", + "step": { + "init": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?" + }, + "user": { + "data": { + "endpoint": "\u041a\u0456\u043d\u0446\u0435\u0432\u0430 \u0442\u043e\u0447\u043a\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/de.json b/homeassistant/components/sonos/translations/de.json index 93b25cf0b9781b..5d66c16811671d 100644 --- a/homeassistant/components/sonos/translations/de.json +++ b/homeassistant/components/sonos/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Keine Sonos Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von Sonos ist notwendig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/tr.json b/homeassistant/components/sonos/translations/tr.json new file mode 100644 index 00000000000000..42bd46ce7c0e35 --- /dev/null +++ b/homeassistant/components/sonos/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Sonos'u kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/uk.json b/homeassistant/components/sonos/translations/uk.json new file mode 100644 index 00000000000000..aff6c9f59b1799 --- /dev/null +++ b/homeassistant/components/sonos/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Sonos?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/de.json b/homeassistant/components/speedtestdotnet/translations/de.json index 56b1a91a89e4e8..3b5ef0b26e17ad 100644 --- a/homeassistant/components/speedtestdotnet/translations/de.json +++ b/homeassistant/components/speedtestdotnet/translations/de.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "wrong_server_id": "Server ID ist ung\u00fcltig" + "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich.", + "wrong_server_id": "Server-ID ist ung\u00fcltig" }, "step": { "user": { - "description": "Einrichtung beginnen?" + "description": "M\u00f6chtest du mit der Einrichtung beginnen?" } } }, @@ -13,7 +14,9 @@ "step": { "init": { "data": { - "manual": "Automatische Updates deaktivieren" + "manual": "Automatische Updates deaktivieren", + "scan_interval": "Aktualisierungsfrequenz (Minuten)", + "server_name": "Testserver ausw\u00e4hlen" } } } diff --git a/homeassistant/components/speedtestdotnet/translations/tr.json b/homeassistant/components/speedtestdotnet/translations/tr.json new file mode 100644 index 00000000000000..b13be7c5e0cb7d --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr.", + "wrong_server_id": "Sunucu kimli\u011fi ge\u00e7erli de\u011fil" + }, + "step": { + "user": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Otomatik g\u00fcncellemeyi devre d\u0131\u015f\u0131 b\u0131rak\u0131n", + "scan_interval": "G\u00fcncelleme s\u0131kl\u0131\u011f\u0131 (dakika)", + "server_name": "Test sunucusunu se\u00e7in" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/uk.json b/homeassistant/components/speedtestdotnet/translations/uk.json new file mode 100644 index 00000000000000..89ef24440d13e4 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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.", + "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": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f", + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0443 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)", + "server_name": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0443\u0432\u0430\u043d\u043d\u044f" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/de.json b/homeassistant/components/spider/translations/de.json index 6f39806287630f..c57e55e9d2ea1b 100644 --- a/homeassistant/components/spider/translations/de.json +++ b/homeassistant/components/spider/translations/de.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/spider/translations/tr.json b/homeassistant/components/spider/translations/tr.json new file mode 100644 index 00000000000000..9bcc6bb1c41c70 --- /dev/null +++ b/homeassistant/components/spider/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/uk.json b/homeassistant/components/spider/translations/uk.json new file mode 100644 index 00000000000000..b8be2a1488791e --- /dev/null +++ b/homeassistant/components/spider/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u0412\u0445\u0456\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 mijn.ithodaalderop.nl" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/de.json b/homeassistant/components/spotify/translations/de.json index bfd393bbbb8600..281803ec66ed96 100644 --- a/homeassistant/components/spotify/translations/de.json +++ b/homeassistant/components/spotify/translations/de.json @@ -2,14 +2,18 @@ "config": { "abort": { "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", - "missing_configuration": "Die Spotify-Integration ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "missing_configuration": "Die Spotify-Integration ist nicht konfiguriert. Bitte folgen Sie der Dokumentation.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "create_entry": { "default": "Erfolgreich mit Spotify authentifiziert." }, "step": { "pick_implementation": { - "title": "Authentifizierungsmethode ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" + }, + "reauth_confirm": { + "title": "Integration erneut authentifizieren" } } }, diff --git a/homeassistant/components/spotify/translations/lb.json b/homeassistant/components/spotify/translations/lb.json index d7b5dcec0be4d6..92e323d6c4d7c4 100644 --- a/homeassistant/components/spotify/translations/lb.json +++ b/homeassistant/components/spotify/translations/lb.json @@ -17,5 +17,10 @@ "title": "Integratioun re-authentifiz\u00e9ieren" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API Endpunkt ereechbar" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/uk.json b/homeassistant/components/spotify/translations/uk.json new file mode 100644 index 00000000000000..fda84b310a5c24 --- /dev/null +++ b/homeassistant/components/spotify/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "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": "\u0406\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044f Spotify \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0430. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044c \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0454\u044e.", + "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_account_mismatch": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u043e\u0432\u0430\u043d\u0438\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u0443, \u0449\u043e \u0432\u0438\u043c\u0430\u0433\u0430\u0454 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "reauth_confirm": { + "description": "\u041d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f \u0432 Spotify \u0434\u043b\u044f \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443: {account}", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + } + } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "\u0414\u043e\u0441\u0442\u0443\u043f \u0434\u043e API Spotify" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/de.json b/homeassistant/components/squeezebox/translations/de.json index 667bf6dbd128c8..742210f3dc6d98 100644 --- a/homeassistant/components/squeezebox/translations/de.json +++ b/homeassistant/components/squeezebox/translations/de.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" }, "step": { "edit": { "data": { + "host": "Host", "password": "Passwort", "port": "Port", "username": "Benutzername" diff --git a/homeassistant/components/squeezebox/translations/tr.json b/homeassistant/components/squeezebox/translations/tr.json new file mode 100644 index 00000000000000..ff249aafa14853 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/tr.json @@ -0,0 +1,27 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "edit": { + "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/uk.json b/homeassistant/components/squeezebox/translations/uk.json new file mode 100644 index 00000000000000..50cd135f6f3d41 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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.", + "no_server_found": "\u0421\u0435\u0440\u0432\u0435\u0440 LMS \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + "error": { + "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.", + "no_server_found": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0432\u0438\u044f\u0432\u0438\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Logitech Squeezebox: {host}", + "step": { + "edit": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u043f\u0440\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/de.json b/homeassistant/components/srp_energy/translations/de.json index 23fe89c73b4d1f..302233d29234b8 100644 --- a/homeassistant/components/srp_energy/translations/de.json +++ b/homeassistant/components/srp_energy/translations/de.json @@ -1,14 +1,21 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Anmeldung", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { "data": { - "password": "Passwort" + "password": "Passwort", + "username": "Benutzername" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/es.json b/homeassistant/components/srp_energy/translations/es.json index de15bb805514dd..849c5019d3b5f5 100644 --- a/homeassistant/components/srp_energy/translations/es.json +++ b/homeassistant/components/srp_energy/translations/es.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n." + "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, "error": { "cannot_connect": "No se pudo conectar", "invalid_account": "El ID de la cuenta debe ser un n\u00famero de 9 d\u00edgitos", - "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, "step": { @@ -15,7 +15,7 @@ "id": "ID de la cuenta", "is_tou": "Es el plan de tiempo de uso", "password": "Contrase\u00f1a", - "username": "Nombre de usuario" + "username": "Usuario" } } } diff --git a/homeassistant/components/srp_energy/translations/fr.json b/homeassistant/components/srp_energy/translations/fr.json new file mode 100644 index 00000000000000..0cc85aff649130 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/fr.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/lb.json b/homeassistant/components/srp_energy/translations/lb.json new file mode 100644 index 00000000000000..1affdcc31e6f18 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/lb.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/tr.json b/homeassistant/components/srp_energy/translations/tr.json index 1b08426f631ac4..ead8238d82c116 100644 --- a/homeassistant/components/srp_energy/translations/tr.json +++ b/homeassistant/components/srp_energy/translations/tr.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, "error": { - "invalid_account": "Hesap kimli\u011fi 9 haneli bir say\u0131 olmal\u0131d\u0131r" + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_account": "Hesap kimli\u011fi 9 haneli bir say\u0131 olmal\u0131d\u0131r", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "unknown": "Beklenmeyen hata" }, "step": { "user": { diff --git a/homeassistant/components/srp_energy/translations/uk.json b/homeassistant/components/srp_energy/translations/uk.json new file mode 100644 index 00000000000000..5267aa2a5757f1 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_account": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 9-\u0437\u043d\u0430\u0447\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443", + "is_tou": "\u041f\u043b\u0430\u043d \u0447\u0430\u0441\u0443 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "title": "SRP Energy" +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/tr.json b/homeassistant/components/starline/translations/tr.json new file mode 100644 index 00000000000000..9d52f589e98735 --- /dev/null +++ b/homeassistant/components/starline/translations/tr.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "error_auth_user": "Yanl\u0131\u015f kullan\u0131c\u0131 ad\u0131 ya da parola" + }, + "step": { + "auth_app": { + "title": "Uygulama kimlik bilgileri" + }, + "auth_captcha": { + "data": { + "captcha_code": "G\u00f6r\u00fcnt\u00fcden kod" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "SMS kodu" + }, + "description": "{phone_number} telefona g\u00f6nderilen kodu girin", + "title": "\u0130ki fakt\u00f6rl\u00fc yetkilendirme" + }, + "auth_user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "StarLine hesab\u0131 e-postas\u0131 ve parolas\u0131" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/uk.json b/homeassistant/components/starline/translations/uk.json new file mode 100644 index 00000000000000..8a263044284f6a --- /dev/null +++ b/homeassistant/components/starline/translations/uk.json @@ -0,0 +1,41 @@ +{ + "config": { + "error": { + "error_auth_app": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0434\u0430\u0442\u043a\u0430 \u0430\u0431\u043e \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u0438\u0439 \u043a\u043e\u0434.", + "error_auth_mfa": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434.", + "error_auth_user": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d \u0430\u0431\u043e \u043f\u0430\u0440\u043e\u043b\u044c." + }, + "step": { + "auth_app": { + "data": { + "app_id": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0434\u0430\u0442\u043a\u0430", + "app_secret": "\u0421\u0435\u043a\u0440\u0435\u0442\u043d\u0438\u0439 \u043a\u043e\u0434" + }, + "description": "ID \u0434\u043e\u0434\u0430\u0442\u043a\u0430 \u0456 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u0438\u0439 \u043a\u043e\u0434 [\u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 \u0440\u043e\u0437\u0440\u043e\u0431\u043d\u0438\u043a\u0430 StarLine] (https://my.starline.ru/developer)", + "title": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456 \u0434\u043e\u0434\u0430\u0442\u043a\u0430" + }, + "auth_captcha": { + "data": { + "captcha_code": "\u041a\u043e\u0434 \u0437 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f" + }, + "description": "{captcha_img}", + "title": "CAPTCHA" + }, + "auth_mfa": { + "data": { + "mfa_code": "\u041a\u043e\u0434 \u0437 SMS" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434, \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439 \u043d\u0430 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443 {phone_number}", + "title": "\u0414\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "auth_user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438 \u0442\u0430 \u043f\u0430\u0440\u043e\u043b\u044c \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 StarLine", + "title": "\u041e\u0431\u043b\u0456\u043a\u043e\u0432\u0456 \u0434\u0430\u043d\u0456 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sun/translations/pl.json b/homeassistant/components/sun/translations/pl.json index fb90b9bd232b84..1f00babd1fd07a 100644 --- a/homeassistant/components/sun/translations/pl.json +++ b/homeassistant/components/sun/translations/pl.json @@ -1,7 +1,7 @@ { "state": { "_": { - "above_horizon": "powy\u017cej horyzontu", + "above_horizon": "nad horyzontem", "below_horizon": "poni\u017cej horyzontu" } }, diff --git a/homeassistant/components/switch/translations/uk.json b/homeassistant/components/switch/translations/uk.json index bee9eb957d530f..26b85b3a87397f 100644 --- a/homeassistant/components/switch/translations/uk.json +++ b/homeassistant/components/switch/translations/uk.json @@ -1,5 +1,14 @@ { "device_automation": { + "action_type": { + "toggle": "{entity_name}: \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u0438", + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "condition_type": { + "is_off": "{entity_name} \u0443 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456", + "is_on": "{entity_name} \u0443 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e\u043c\u0443 \u0441\u0442\u0430\u043d\u0456" + }, "trigger_type": { "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" diff --git a/homeassistant/components/syncthru/translations/tr.json b/homeassistant/components/syncthru/translations/tr.json new file mode 100644 index 00000000000000..942457958f820e --- /dev/null +++ b/homeassistant/components/syncthru/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "confirm": { + "data": { + "url": "Web aray\u00fcz\u00fc URL'si" + } + }, + "user": { + "data": { + "name": "Ad", + "url": "Web aray\u00fcz\u00fc URL'si" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/uk.json b/homeassistant/components/syncthru/translations/uk.json new file mode 100644 index 00000000000000..74cccc7ef5a24a --- /dev/null +++ b/homeassistant/components/syncthru/translations/uk.json @@ -0,0 +1,27 @@ +{ + "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." + }, + "error": { + "invalid_url": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430.", + "syncthru_not_supported": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454 SyncThru.", + "unknown_state": "\u0421\u0442\u0430\u043d \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043d\u0435\u0432\u0456\u0434\u043e\u043c\u0438\u0439, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u0442\u0430 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456." + }, + "flow_title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 Samsung SyncThru: {name}", + "step": { + "confirm": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443" + } + }, + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index 303321ea94c6ad..f0d274c3bfe47c 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Host bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "missing_data": "Fehlende Daten: Bitte versuchen Sie es sp\u00e4ter noch einmal oder eine andere Konfiguration", "otp_failed": "Die zweistufige Authentifizierung ist fehlgeschlagen. Versuchen Sie es erneut mit einem neuen Code", - "unknown": "Unbekannter Fehler: Bitte \u00fcberpr\u00fcfen Sie die Protokolle, um weitere Details zu erhalten" + "unknown": "Unerwarteter Fehler" }, "flow_title": "Synology DSM {name} ({host})", "step": { @@ -21,7 +22,7 @@ "data": { "password": "Passwort", "port": "Port", - "ssl": "Verwenden Sie SSL/TLS, um eine Verbindung zu Ihrem NAS herzustellen", + "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL Zertifikat verifizieren" }, @@ -33,7 +34,7 @@ "host": "Host", "password": "Passwort", "port": "Port", - "ssl": "Verwenden Sie SSL/TLS, um eine Verbindung zu Ihrem NAS herzustellen", + "ssl": "Verwendet ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL Zertifikat verifizieren" }, diff --git a/homeassistant/components/synology_dsm/translations/tr.json b/homeassistant/components/synology_dsm/translations/tr.json index a7598bb343842a..681d85d2ef545e 100644 --- a/homeassistant/components/synology_dsm/translations/tr.json +++ b/homeassistant/components/synology_dsm/translations/tr.json @@ -1,15 +1,31 @@ { "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", + "unknown": "Beklenmeyen hata" + }, "step": { "link": { "data": { + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikalar\u0131n\u0131 do\u011frula" } }, "user": { "data": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131", "verify_ssl": "SSL sertifikalar\u0131n\u0131 do\u011frula" - } + }, + "title": "Synology DSM" } } } diff --git a/homeassistant/components/synology_dsm/translations/uk.json b/homeassistant/components/synology_dsm/translations/uk.json new file mode 100644 index 00000000000000..4d80350989fd68 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/uk.json @@ -0,0 +1,55 @@ +{ + "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." + }, + "error": { + "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.", + "missing_data": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456 \u0434\u0430\u043d\u0456: \u043f\u043e\u0432\u0442\u043e\u0440\u0456\u0442\u044c \u0441\u043f\u0440\u043e\u0431\u0443 \u043f\u0456\u0437\u043d\u0456\u0448\u0435 \u0430\u0431\u043e \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0456\u043d\u0448\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e.", + "otp_failed": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0457 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u0437 \u043d\u043e\u0432\u0438\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "2sa": { + "data": { + "otp_code": "\u041a\u043e\u0434" + }, + "title": "Synology DSM: \u0434\u0432\u043e\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f" + }, + "link": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 {name} ({host})?", + "title": "Synology DSM" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "Synology DSM" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0456\u0436 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c\u0438 (\u0445\u0432.)", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/system_health/translations/uk.json b/homeassistant/components/system_health/translations/uk.json index 267fcb83a61646..61f30782f04999 100644 --- a/homeassistant/components/system_health/translations/uk.json +++ b/homeassistant/components/system_health/translations/uk.json @@ -1,3 +1,3 @@ { - "title": "\u0411\u0435\u0437\u043f\u0435\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0438" + "title": "\u0421\u0442\u0430\u043d \u0441\u0438\u0441\u0442\u0435\u043c\u0438" } \ No newline at end of file diff --git a/homeassistant/components/tado/translations/de.json b/homeassistant/components/tado/translations/de.json index ffab091f726d7e..9dc410b670e546 100644 --- a/homeassistant/components/tado/translations/de.json +++ b/homeassistant/components/tado/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "no_homes": "Es sind keine Standorte mit diesem Tado-Konto verkn\u00fcpft.", "unknown": "Unerwarteter Fehler" @@ -15,7 +15,7 @@ "password": "Passwort", "username": "Benutzername" }, - "title": "Stellen Sie eine Verbindung zu Ihrem Tado-Konto her" + "title": "Stellen eine Verbindung zu deinem Tado-Konto her" } } }, @@ -23,10 +23,10 @@ "step": { "init": { "data": { - "fallback": "Aktivieren Sie den Fallback-Modus." + "fallback": "Aktivieren den Fallback-Modus." }, "description": "Der Fallback-Modus wechselt beim n\u00e4chsten Zeitplanwechsel nach dem manuellen Anpassen einer Zone zu Smart Schedule.", - "title": "Passen Sie die Tado-Optionen an." + "title": "Passe die Tado-Optionen an." } } } diff --git a/homeassistant/components/tado/translations/tr.json b/homeassistant/components/tado/translations/tr.json new file mode 100644 index 00000000000000..09ffbf8a7d14f7 --- /dev/null +++ b/homeassistant/components/tado/translations/tr.json @@ -0,0 +1,29 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Geri d\u00f6n\u00fc\u015f modunu etkinle\u015ftirin." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/uk.json b/homeassistant/components/tado/translations/uk.json new file mode 100644 index 00000000000000..f1dcf4d575ba83 --- /dev/null +++ b/homeassistant/components/tado/translations/uk.json @@ -0,0 +1,33 @@ +{ + "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." + }, + "error": { + "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.", + "no_homes": "\u0427\u0438 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0431\u0443\u0434\u0438\u043d\u043a\u0456\u0432, \u043f\u043e\u0432'\u044f\u0437\u0430\u043d\u0438\u0445 \u0437 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u043c \u0437\u0430\u043f\u0438\u0441\u043e\u043c.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Tado" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c Fallback" + }, + "description": "\u0420\u0435\u0436\u0438\u043c Fallback \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043d\u0430 Smart Schedule \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0443 \u043f\u0456\u0441\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u043e\u043d\u0438.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tado" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tag/translations/uk.json b/homeassistant/components/tag/translations/uk.json new file mode 100644 index 00000000000000..fdac700612daf4 --- /dev/null +++ b/homeassistant/components/tag/translations/uk.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/de.json b/homeassistant/components/tasmota/translations/de.json new file mode 100644 index 00000000000000..308747088395bd --- /dev/null +++ b/homeassistant/components/tasmota/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "config": { + "description": "Bitte die Tasmota-Konfiguration einstellen.", + "title": "Tasmota" + }, + "confirm": { + "description": "M\u00f6chtest du Tasmota einrichten?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/tr.json b/homeassistant/components/tasmota/translations/tr.json new file mode 100644 index 00000000000000..a559d0911ee0f5 --- /dev/null +++ b/homeassistant/components/tasmota/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "config": { + "description": "L\u00fctfen Tasmota yap\u0131land\u0131rmas\u0131n\u0131 girin.", + "title": "Tasmota" + }, + "confirm": { + "description": "Tasmota'y\u0131 kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/uk.json b/homeassistant/components/tasmota/translations/uk.json new file mode 100644 index 00000000000000..6639a9c9626eb8 --- /dev/null +++ b/homeassistant/components/tasmota/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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." + }, + "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." + }, + "step": { + "config": { + "data": { + "discovery_prefix": "\u041f\u0440\u0435\u0444\u0456\u043a\u0441 \u0442\u0435\u043c\u0438 \u0430\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tasmota.", + "title": "Tasmota" + }, + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Tasmota?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/de.json b/homeassistant/components/tellduslive/translations/de.json index a1f6f595a046d0..098ad9c17be221 100644 --- a/homeassistant/components/tellduslive/translations/de.json +++ b/homeassistant/components/tellduslive/translations/de.json @@ -4,9 +4,12 @@ "already_configured": "Dienst ist bereits konfiguriert", "authorize_url_fail": "Unbekannter Fehler beim Erstellen der Authorisierungs-URL", "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", - "unknown": "Unbekannter Fehler ist aufgetreten", + "unknown": "Unerwarteter Fehler", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "auth": { "description": "So verkn\u00fcpfest du dein TelldusLive-Konto: \n 1. Klicke auf den Link unten \n 2. Melde dich bei Telldus Live an \n 3. Autorisiere ** {app_name} ** (klicke auf ** Yes **). \n 4. Komme hierher zur\u00fcck und klicke auf ** SUBMIT **. \n\n [Link TelldusLive-Konto]({auth_url})", diff --git a/homeassistant/components/tellduslive/translations/fr.json b/homeassistant/components/tellduslive/translations/fr.json index cde9d9c2c68b31..ef4d7bc44dda47 100644 --- a/homeassistant/components/tellduslive/translations/fr.json +++ b/homeassistant/components/tellduslive/translations/fr.json @@ -4,7 +4,8 @@ "already_configured": "TelldusLive est d\u00e9j\u00e0 configur\u00e9", "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", - "unknown": "Une erreur inconnue s'est produite" + "unknown": "Une erreur inconnue s'est produite", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "error": { "invalid_auth": "Authentification invalide" diff --git a/homeassistant/components/tellduslive/translations/lb.json b/homeassistant/components/tellduslive/translations/lb.json index 5e733c2294d6c3..2b809050677ce8 100644 --- a/homeassistant/components/tellduslive/translations/lb.json +++ b/homeassistant/components/tellduslive/translations/lb.json @@ -4,7 +4,8 @@ "already_configured": "Service ass scho konfigur\u00e9iert", "authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", - "unknown": "Onerwaarte Feeler" + "unknown": "Onerwaarte Feeler", + "unknown_authorize_url_generation": "Onbekannte Feeler beim erstellen vun der Authorisatiouns URL." }, "error": { "invalid_auth": "Ong\u00eblteg Authentifikatioun" diff --git a/homeassistant/components/tellduslive/translations/tr.json b/homeassistant/components/tellduslive/translations/tr.json new file mode 100644 index 00000000000000..300fad68391594 --- /dev/null +++ b/homeassistant/components/tellduslive/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "unknown": "Beklenmeyen hata", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/uk.json b/homeassistant/components/tellduslive/translations/uk.json new file mode 100644 index 00000000000000..ff7b3337bb941b --- /dev/null +++ b/homeassistant/components/tellduslive/translations/uk.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant.", + "authorize_url_fail": "\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.", + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430", + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "auth": { + "description": "\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0449\u043e\u0431 \u043f\u0440\u0438\u0432'\u044f\u0437\u0430\u0442\u0438 \u0430\u043a\u0430\u0443\u043d\u0442 Telldus Live:\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u043f\u043e \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044e, \u0437\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043e\u043c\u0443 \u043d\u0438\u0436\u0447\u0435\n2. \u0423\u0432\u0456\u0439\u0434\u0456\u0442\u044c \u0432 Telldus Live\n3. Authorize ** {app_name} ** (\u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** Yes **).\n4. \u041f\u043e\u0432\u0435\u0440\u043d\u0456\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0438 \u0442\u0430 \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c ** \u041f\u0406\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u0418 **. \n\n[\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 Telldus Live]({auth_url})", + "title": "Telldus Live" + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u043f\u043e\u0440\u043e\u0436\u043d\u044c\u043e", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043a\u0456\u043d\u0446\u0435\u0432\u0443 \u0442\u043e\u0447\u043a\u0443." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/de.json b/homeassistant/components/tesla/translations/de.json index 09100c355c20a0..558209af411791 100644 --- a/homeassistant/components/tesla/translations/de.json +++ b/homeassistant/components/tesla/translations/de.json @@ -1,7 +1,9 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { @@ -18,6 +20,7 @@ "step": { "init": { "data": { + "enable_wake_on_start": "Aufwachen des Autos beim Start erzwingen", "scan_interval": "Sekunden zwischen den Scans" } } diff --git a/homeassistant/components/tesla/translations/fr.json b/homeassistant/components/tesla/translations/fr.json index c8efc8b4fb5303..6134ff25f6b526 100644 --- a/homeassistant/components/tesla/translations/fr.json +++ b/homeassistant/components/tesla/translations/fr.json @@ -1,7 +1,7 @@ { "config": { "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification invalide" }, diff --git a/homeassistant/components/tesla/translations/tr.json b/homeassistant/components/tesla/translations/tr.json new file mode 100644 index 00000000000000..cf0d144c1edf0d --- /dev/null +++ b/homeassistant/components/tesla/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "description": "L\u00fctfen bilgilerinizi giriniz." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/uk.json b/homeassistant/components/tesla/translations/uk.json new file mode 100644 index 00000000000000..90d47ec2ff5910 --- /dev/null +++ b/homeassistant/components/tesla/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \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", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0434\u0430\u043d\u0456 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443.", + "title": "Tesla" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "enable_wake_on_start": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u043e \u0440\u043e\u0437\u0431\u0443\u0434\u0438\u0442\u0438 \u043c\u0430\u0448\u0438\u043d\u0443 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0443", + "scan_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0456\u0436 \u0441\u043a\u0430\u043d\u0443\u0432\u0430\u043d\u043d\u044f\u043c\u0438 (\u0441\u0435\u043a.)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/de.json b/homeassistant/components/tibber/translations/de.json index 670f57df8ba9e2..8d49c9d9e6176f 100644 --- a/homeassistant/components/tibber/translations/de.json +++ b/homeassistant/components/tibber/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Ein Tibber-Konto ist bereits konfiguriert." + "already_configured": "Der Dienst ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_access_token": "Ung\u00fcltiger Zugriffs-Token", "timeout": "Zeit\u00fcberschreitung beim Verbinden mit Tibber" }, diff --git a/homeassistant/components/tibber/translations/tr.json b/homeassistant/components/tibber/translations/tr.json new file mode 100644 index 00000000000000..5f8e72986b211f --- /dev/null +++ b/homeassistant/components/tibber/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hizmet zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_access_token": "Ge\u00e7ersiz eri\u015fim belirteci" + }, + "step": { + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/uk.json b/homeassistant/components/tibber/translations/uk.json new file mode 100644 index 00000000000000..b1240116856403 --- /dev/null +++ b/homeassistant/components/tibber/translations/uk.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u044f \u0441\u043b\u0443\u0436\u0431\u0430 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_access_token": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443.", + "timeout": "\u0427\u0430\u0441 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043c\u0438\u043d\u0443\u0432." + }, + "step": { + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" + }, + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443, \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u0438\u0439 \u043d\u0430 \u0441\u0430\u0439\u0442\u0456 https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/de.json b/homeassistant/components/tile/translations/de.json index 59f48253a18688..1c2af82aa63062 100644 --- a/homeassistant/components/tile/translations/de.json +++ b/homeassistant/components/tile/translations/de.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Konto ist bereits konfiguriert" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tile/translations/tr.json b/homeassistant/components/tile/translations/tr.json new file mode 100644 index 00000000000000..8a04e2f4bbff6f --- /dev/null +++ b/homeassistant/components/tile/translations/tr.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "title": "Karoyu Yap\u0131land\u0131r" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Etkin Olmayan Karolar\u0131 G\u00f6ster" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/uk.json b/homeassistant/components/tile/translations/uk.json new file mode 100644 index 00000000000000..dc28164fd93c2d --- /dev/null +++ b/homeassistant/components/tile/translations/uk.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "Tile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457" + }, + "title": "Tile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/timer/translations/uk.json b/homeassistant/components/timer/translations/uk.json index df690bded93a51..ce937735406f91 100644 --- a/homeassistant/components/timer/translations/uk.json +++ b/homeassistant/components/timer/translations/uk.json @@ -1,9 +1,9 @@ { "state": { "_": { - "active": "\u0430\u043a\u0442\u0438\u0432\u043d\u0438\u0439", - "idle": "\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", - "paused": "\u043d\u0430 \u043f\u0430\u0443\u0437\u0456" + "active": "\u0410\u043a\u0442\u0438\u0432\u043d\u0438\u0439", + "idle": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", + "paused": "\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0435\u043d\u043e" } } } \ No newline at end of file diff --git a/homeassistant/components/toon/translations/de.json b/homeassistant/components/toon/translations/de.json index d9060a719d8780..c04f3a5f4bb4ab 100644 --- a/homeassistant/components/toon/translations/de.json +++ b/homeassistant/components/toon/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", "no_agreements": "Dieses Konto hat keine Toon-Anzeigen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).", "unknown_authorize_url_generation": "Beim Generieren einer Authentifizierungs-URL ist ein unbekannter Fehler aufgetreten" } } diff --git a/homeassistant/components/toon/translations/fr.json b/homeassistant/components/toon/translations/fr.json index caeed852d0a0e9..3fa6059a58f2c7 100644 --- a/homeassistant/components/toon/translations/fr.json +++ b/homeassistant/components/toon/translations/fr.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Timout de g\u00e9n\u00e9ration de l'URL d'autorisation.", "missing_configuration": "The composant n'est pas configur\u00e9. Veuillez vous r\u00e9f\u00e9rer \u00e0 la documentation.", "no_agreements": "Ce compte n'a pas d'affichages Toon.", - "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )" + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", + "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/lb.json b/homeassistant/components/toon/translations/lb.json index 6491c66673863c..e21dfb0c996c24 100644 --- a/homeassistant/components/toon/translations/lb.json +++ b/homeassistant/components/toon/translations/lb.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Z\u00e4itiwwerschraidung beim erstellen vun der Autorisatioun's URL.", "missing_configuration": "Komponent ass net konfigur\u00e9iert. Folleg der Dokumentatioun.", "no_agreements": "D\u00ebse Kont huet keen Toon Ecran.", - "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})", + "unknown_authorize_url_generation": "Onbekannte Feeler beim erstellen vun der Authorisatiouns URL." }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/tr.json b/homeassistant/components/toon/translations/tr.json new file mode 100644 index 00000000000000..97765a99a7f49c --- /dev/null +++ b/homeassistant/components/toon/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Se\u00e7ilen anla\u015fma zaten yap\u0131land\u0131r\u0131lm\u0131\u015f.", + "unknown_authorize_url_generation": "Yetkilendirme url'si olu\u015fturulurken bilinmeyen hata." + }, + "step": { + "agreement": { + "data": { + "agreement": "Anla\u015fma" + }, + "description": "Eklemek istedi\u011finiz anla\u015fma adresini se\u00e7in.", + "title": "Anla\u015fman\u0131z\u0131 se\u00e7in" + }, + "pick_implementation": { + "title": "Kimlik do\u011frulamak i\u00e7in kirac\u0131n\u0131z\u0131 se\u00e7in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/uk.json b/homeassistant/components/toon/translations/uk.json new file mode 100644 index 00000000000000..51aa28f3984b04 --- /dev/null +++ b/homeassistant/components/toon/translations/uk.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\u041e\u0431\u0440\u0430\u043d\u0430 \u0443\u0433\u043e\u0434\u0430 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0430.", + "authorize_url_fail": "\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.", + "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_agreements": "\u0423 \u0446\u044c\u043e\u043c\u0443 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u0456 \u043d\u0435\u043c\u0430\u0454 \u0434\u0438\u0441\u043f\u043b\u0435\u0457\u0432 Toon.", + "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.", + "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." + }, + "step": { + "agreement": { + "data": { + "agreement": "\u0423\u0433\u043e\u0434\u0430" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0430\u0434\u0440\u0435\u0441\u0443 \u0443\u0433\u043e\u0434\u0438, \u044f\u043a\u0443 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438.", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0412\u0430\u0448\u0443 \u0443\u0433\u043e\u0434\u0443" + }, + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0440\u0435\u043d\u0434\u0430\u0440\u044f \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 25069635cca458..530fef95af23f1 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "Konto bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { diff --git a/homeassistant/components/totalconnect/translations/tr.json b/homeassistant/components/totalconnect/translations/tr.json new file mode 100644 index 00000000000000..f941db5ab8942d --- /dev/null +++ b/homeassistant/components/totalconnect/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "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/totalconnect/translations/uk.json b/homeassistant/components/totalconnect/translations/uk.json new file mode 100644 index 00000000000000..f34a279d598ddc --- /dev/null +++ b/homeassistant/components/totalconnect/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/de.json b/homeassistant/components/tplink/translations/de.json index 64bdfc9bf774ec..48571158085223 100644 --- a/homeassistant/components/tplink/translations/de.json +++ b/homeassistant/components/tplink/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine TP-Link-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration erforderlich." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/tr.json b/homeassistant/components/tplink/translations/tr.json new file mode 100644 index 00000000000000..e8f7a5aaf6dd06 --- /dev/null +++ b/homeassistant/components/tplink/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "TP-Link ak\u0131ll\u0131 cihazlar\u0131 kurmak istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/uk.json b/homeassistant/components/tplink/translations/uk.json new file mode 100644 index 00000000000000..cfeaf049675f2a --- /dev/null +++ b/homeassistant/components/tplink/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 TP-Link Smart Home?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/de.json b/homeassistant/components/traccar/translations/de.json index 5d5969b2d5111f..7e253c1d05f6e0 100644 --- a/homeassistant/components/traccar/translations/de.json +++ b/homeassistant/components/traccar/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "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." + }, "create_entry": { - "default": "Um Ereignisse an den Heimassistenten zu senden, m\u00fcssen die Webhook-Funktionen in Traccar eingerichtet werden.\n\nVerwende die folgende URL: `{webhook_url}}`\n\nSiehe [die Dokumentation]( {docs_url} ) f\u00fcr weitere Details." + "default": "Um Ereignisse an den Heimassistenten zu senden, m\u00fcssen die Webhook-Funktionen in Traccar eingerichtet werden.\n\nVerwende die folgende URL: `{webhook_url}`\n\nSiehe [Dokumentation]({docs_url}) f\u00fcr weitere Details." }, "step": { "user": { diff --git a/homeassistant/components/traccar/translations/lb.json b/homeassistant/components/traccar/translations/lb.json index d729525200503d..9e7d16fec3f08e 100644 --- a/homeassistant/components/traccar/translations/lb.json +++ b/homeassistant/components/traccar/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech.", + "webhook_not_internet_accessible": "Deng Home Assistant Instanz muss iwwert Internet accessibel si fir Webhook Noriichten z'empf\u00e4nken." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am Traccar ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider Informatiounen." diff --git a/homeassistant/components/traccar/translations/tr.json b/homeassistant/components/traccar/translations/tr.json index 7d044949a6ed6a..9a2b1a119cd832 100644 --- a/homeassistant/components/traccar/translations/tr.json +++ b/homeassistant/components/traccar/translations/tr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "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." + }, "step": { "user": { "title": "Traccar'\u0131 kur" diff --git a/homeassistant/components/traccar/translations/uk.json b/homeassistant/components/traccar/translations/uk.json new file mode 100644 index 00000000000000..5bfb1714a79256 --- /dev/null +++ b/homeassistant/components/traccar/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f Traccar. \n\n\u0414\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}` \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" + }, + "step": { + "user": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Traccar?", + "title": "Traccar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/de.json b/homeassistant/components/tradfri/translations/de.json index 3e55cb701d5ce0..b1ebb2aff0b279 100644 --- a/homeassistant/components/tradfri/translations/de.json +++ b/homeassistant/components/tradfri/translations/de.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "Bridge ist bereits konfiguriert.", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr die Bridge wird bereits ausgef\u00fchrt." + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" }, "error": { - "cannot_connect": "Verbindung zum Gateway nicht m\u00f6glich.", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_key": "Registrierung mit angegebenem Schl\u00fcssel fehlgeschlagen. Wenn dies weiterhin geschieht, starte den Gateway neu.", "timeout": "Timeout bei der \u00dcberpr\u00fcfung des Codes." }, diff --git a/homeassistant/components/tradfri/translations/tr.json b/homeassistant/components/tradfri/translations/tr.json new file mode 100644 index 00000000000000..e4483536b129cb --- /dev/null +++ b/homeassistant/components/tradfri/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "auth": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/uk.json b/homeassistant/components/tradfri/translations/uk.json index a163a4680e3e3c..abd25d04b6be6e 100644 --- a/homeassistant/components/tradfri/translations/uk.json +++ b/homeassistant/components/tradfri/translations/uk.json @@ -1,14 +1,22 @@ { "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_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." + }, "error": { - "cannot_connect": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0448\u043b\u044e\u0437\u0443." + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_key": "\u0427\u0438 \u043d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438\u0441\u044f \u0437 \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u043c \u043a\u043b\u044e\u0447\u0435\u043c. \u042f\u043a\u0449\u043e \u0446\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c\u0441\u044f, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0448\u043b\u044e\u0437.", + "timeout": "\u041c\u0438\u043d\u0443\u0432 \u0447\u0430\u0441 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0438 \u043a\u043e\u0434\u0443." }, "step": { "auth": { "data": { + "host": "\u0425\u043e\u0441\u0442", "security_code": "\u041a\u043e\u0434 \u0431\u0435\u0437\u043f\u0435\u043a\u0438" }, - "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043a\u043e\u0434 \u0431\u0435\u0437\u043f\u0435\u043a\u0438" + "description": "\u041a\u043e\u0434 \u0431\u0435\u0437\u043f\u0435\u043a\u0438 \u043c\u043e\u0436\u043d\u0430 \u0437\u043d\u0430\u0439\u0442\u0438 \u043d\u0430 \u0437\u0430\u0434\u043d\u0456\u0439 \u043f\u0430\u043d\u0435\u043b\u0456 \u0448\u043b\u044e\u0437\u0443.", + "title": "IKEA TR\u00c5DFRI" } } } diff --git a/homeassistant/components/transmission/translations/de.json b/homeassistant/components/transmission/translations/de.json index a133cd363e04a5..2355905d1f7564 100644 --- a/homeassistant/components/transmission/translations/de.json +++ b/homeassistant/components/transmission/translations/de.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Host ist bereits konfiguriert." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { - "cannot_connect": "Verbindung zum Host nicht m\u00f6glich", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "name_exists": "Name existiert bereits" }, "step": { diff --git a/homeassistant/components/transmission/translations/tr.json b/homeassistant/components/transmission/translations/tr.json new file mode 100644 index 00000000000000..cffcc65151c6d6 --- /dev/null +++ b/homeassistant/components/transmission/translations/tr.json @@ -0,0 +1,21 @@ +{ + "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": { + "host": "Ana Bilgisayar", + "password": "Parola", + "port": "Port", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/uk.json b/homeassistant/components/transmission/translations/uk.json new file mode 100644 index 00000000000000..5bc74f7da2a677 --- /dev/null +++ b/homeassistant/components/transmission/translations/uk.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "error": { + "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.", + "name_exists": "\u0426\u044f \u043d\u0430\u0437\u0432\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "Transmission" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "limit": "\u041e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f", + "order": "\u041f\u043e\u0440\u044f\u0434\u043e\u043a", + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Transmission" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index 4cdcdfced79b60..67a61f81a1c5b3 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -1,9 +1,13 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "flow_title": "Tuya Konfiguration", "step": { "user": { @@ -11,7 +15,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Geben Sie Ihre Tuya-Anmeldeinformationen ein.", + "description": "Gib deine Tuya-Anmeldeinformationen ein.", "title": "Tuya" } } diff --git a/homeassistant/components/tuya/translations/lb.json b/homeassistant/components/tuya/translations/lb.json index 884eb328fe4af1..0000f9ef6e62a5 100644 --- a/homeassistant/components/tuya/translations/lb.json +++ b/homeassistant/components/tuya/translations/lb.json @@ -23,6 +23,9 @@ } }, "options": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + }, "error": { "dev_multi_type": "Multiple ausgewielte Ger\u00e4ter fir ze konfigur\u00e9ieren musse vum selwechten Typ sinn", "dev_not_config": "Typ vun Apparat net konfigur\u00e9ierbar", diff --git a/homeassistant/components/tuya/translations/tr.json b/homeassistant/components/tuya/translations/tr.json index 5a4de08033cf61..2edf3276b6c503 100644 --- a/homeassistant/components/tuya/translations/tr.json +++ b/homeassistant/components/tuya/translations/tr.json @@ -1,11 +1,39 @@ { + "config": { + "abort": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "flow_title": "Tuya yap\u0131land\u0131rmas\u0131", + "step": { + "user": { + "data": { + "country_code": "Hesap \u00fclke kodunuz (\u00f6r. ABD i\u00e7in 1 veya \u00c7in i\u00e7in 86)", + "password": "Parola", + "platform": "Hesab\u0131n\u0131z\u0131n kay\u0131tl\u0131 oldu\u011fu uygulama", + "username": "Kullan\u0131c\u0131 Ad\u0131" + }, + "description": "Tuya kimlik bilgilerinizi girin.", + "title": "Tuya" + } + } + }, "options": { "abort": { "cannot_connect": "Ba\u011flanma hatas\u0131" }, + "error": { + "dev_not_config": "Cihaz t\u00fcr\u00fc yap\u0131land\u0131r\u0131lamaz", + "dev_not_found": "Cihaz bulunamad\u0131" + }, "step": { "device": { "data": { + "brightness_range_mode": "Cihaz\u0131n kulland\u0131\u011f\u0131 parlakl\u0131k aral\u0131\u011f\u0131", "max_temp": "Maksimum hedef s\u0131cakl\u0131k (varsay\u0131lan olarak min ve maks = 0 kullan\u0131n)", "min_kelvin": "Kelvin destekli min renk s\u0131cakl\u0131\u011f\u0131", "min_temp": "Minimum hedef s\u0131cakl\u0131k (varsay\u0131lan i\u00e7in min ve maks = 0 kullan\u0131n)", diff --git a/homeassistant/components/tuya/translations/uk.json b/homeassistant/components/tuya/translations/uk.json new file mode 100644 index 00000000000000..1d2709d260a0c6 --- /dev/null +++ b/homeassistant/components/tuya/translations/uk.json @@ -0,0 +1,63 @@ +{ + "config": { + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya", + "step": { + "user": { + "data": { + "country_code": "\u041a\u043e\u0434 \u043a\u0440\u0430\u0457\u043d\u0438 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0438\u0441\u0443 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0430\u0431\u043e 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044e)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "platform": "\u0414\u043e\u0434\u0430\u0442\u043e\u043a, \u0432 \u044f\u043a\u043e\u043c\u0443 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u043e \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 Tuya.", + "title": "Tuya" + } + } + }, + "options": { + "abort": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "error": { + "dev_multi_type": "\u041a\u0456\u043b\u044c\u043a\u0430 \u043e\u0431\u0440\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0431\u0443\u0442\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0443.", + "dev_not_config": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u043d\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", + "dev_not_found": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "\u0414\u0456\u0430\u043f\u0430\u0437\u043e\u043d \u044f\u0441\u043a\u0440\u0430\u0432\u043e\u0441\u0442\u0456, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", + "curr_temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", + "max_kelvin": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", + "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", + "min_kelvin": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0456\u043d\u0430\u0445)", + "min_temp": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0430 \u0446\u0456\u043b\u044c\u043e\u0432\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 min \u0456 max = 0)", + "support_color": "\u041f\u0440\u0438\u043c\u0443\u0441\u043e\u0432\u0430 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 \u043a\u043e\u043b\u044c\u043e\u0440\u0443", + "temp_divider": "\u0414\u0456\u043b\u044c\u043d\u0438\u043a \u0437\u043d\u0430\u0447\u0435\u043d\u044c \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438 (0 = \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c)", + "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430 \u043a\u043e\u043b\u0456\u0440\u043d\u0430 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u044f\u043a\u0430 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u044f\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c", + "unit_of_measurement": "\u041e\u0434\u0438\u043d\u0438\u0446\u044f \u0432\u0438\u043c\u0456\u0440\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0438, \u044f\u043a\u0430 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c" + }, + "description": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u0434\u0438\u043c\u043e\u0457 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u0434\u043b\u044f {device_type} \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e '{device_name}'", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Tuya" + }, + "init": { + "data": { + "discovery_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "list_devices": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0430\u0431\u043e \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u0434\u043b\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457", + "query_device": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0431\u0443\u0434\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0438\u0442\u0443 \u0434\u043b\u044f \u0431\u0456\u043b\u044c\u0448 \u0448\u0432\u0438\u0434\u043a\u043e\u0433\u043e \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f \u0441\u0442\u0430\u0442\u0443\u0441\u0443", + "query_interval": "\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u041d\u0435 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u044e\u0439\u0442\u0435 \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u043d\u0438\u0437\u044c\u043a\u0456 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443 \u043e\u043f\u0438\u0442\u0443\u0432\u0430\u043d\u043d\u044f, \u0456\u043d\u0430\u043a\u0448\u0435 \u0432\u0438\u043a\u043b\u0438\u043a\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442\u044c \u0433\u0435\u043d\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u043e \u043f\u043e\u043c\u0438\u043b\u043a\u0443 \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0456.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f Tuya" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/de.json b/homeassistant/components/twentemilieu/translations/de.json index 27ba9bb29c7457..38cabb6c22ec37 100644 --- a/homeassistant/components/twentemilieu/translations/de.json +++ b/homeassistant/components/twentemilieu/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler", + "cannot_connect": "Verbindung fehlgeschlagen", "invalid_address": "Adresse nicht im Einzugsgebiet von Twente Milieu gefunden." }, "step": { diff --git a/homeassistant/components/twentemilieu/translations/tr.json b/homeassistant/components/twentemilieu/translations/tr.json new file mode 100644 index 00000000000000..590aec1894cc3b --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/tr.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/uk.json b/homeassistant/components/twentemilieu/translations/uk.json new file mode 100644 index 00000000000000..435bd79fb85feb --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/uk.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_address": "\u0410\u0434\u0440\u0435\u0441\u0443 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0432 \u0437\u043e\u043d\u0456 \u043e\u0431\u0441\u043b\u0443\u0433\u043e\u0432\u0443\u0432\u0430\u043d\u043d\u044f Twente Milieu." + }, + "step": { + "user": { + "data": { + "house_letter": "\u0414\u043e\u043f\u043e\u0432\u043d\u0435\u043d\u043d\u044f \u0434\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0434\u043e\u043c\u0443", + "house_number": "\u041d\u043e\u043c\u0435\u0440 \u0431\u0443\u0434\u0438\u043d\u043a\u0443", + "post_code": "\u041f\u043e\u0448\u0442\u043e\u0432\u0438\u0439 \u0456\u043d\u0434\u0435\u043a\u0441" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Twente Milieu \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 \u0432\u0438\u0432\u0435\u0437\u0435\u043d\u043d\u044f \u0441\u043c\u0456\u0442\u0442\u044f \u0437\u0430 \u0412\u0430\u0448\u043e\u044e \u0430\u0434\u0440\u0435\u0441\u043e\u044e.", + "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 864fee4c238f22..61df22c10f86c7 100644 --- a/homeassistant/components/twilio/translations/de.json +++ b/homeassistant/components/twilio/translations/de.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "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." + }, "create_entry": { - "default": "Um Ereignisse an den Home Assistant zu senden, musst du [Webhooks mit Twilio]({twilio_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / x-www-form-urlencoded \n\nLies in der [Dokumentation]({docs_url}) wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." + "default": "Um Ereignisse an Home Assistant zu senden, musst du [Webhooks mit Twilio]({twilio_url}) einrichten. \n\n F\u00fclle die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / x-www-form-urlencoded \n\nLies in der [Dokumentation]({docs_url}), wie du Automationen f\u00fcr die Verarbeitung eingehender Daten konfigurierst." }, "step": { "user": { - "description": "M\u00f6chtest du Twilio wirklich einrichten?", + "description": "M\u00f6chtest du mit der Einrichtung beginnen?", "title": "Twilio-Webhook einrichten" } } diff --git a/homeassistant/components/twilio/translations/lb.json b/homeassistant/components/twilio/translations/lb.json index 2721402c1f3a53..7889f244c6eca8 100644 --- a/homeassistant/components/twilio/translations/lb.json +++ b/homeassistant/components/twilio/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech.", + "webhook_not_internet_accessible": "Deng Home Assistant Instanz muss iwwert Internet accessibel si fir Webhook Noriichten z'empf\u00e4nken." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Twilio]({twilio_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." diff --git a/homeassistant/components/twilio/translations/tr.json b/homeassistant/components/twilio/translations/tr.json new file mode 100644 index 00000000000000..84adcdf8225c43 --- /dev/null +++ b/homeassistant/components/twilio/translations/tr.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/uk.json b/homeassistant/components/twilio/translations/uk.json new file mode 100644 index 00000000000000..8ea0ce86a37a09 --- /dev/null +++ b/homeassistant/components/twilio/translations/uk.json @@ -0,0 +1,17 @@ +{ + "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.", + "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": { + "default": "\u0414\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e\u0434\u0456\u0439 \u0432 Home Assistant \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Webhook \u0434\u043b\u044f [Twilio]({twilio_url}). \n\n\u0417\u0430\u043f\u043e\u0432\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0443 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044e: \n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded \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 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0456\u0439 \u043f\u043e \u043e\u0431\u0440\u043e\u0431\u0446\u0456 \u0434\u0430\u043d\u0438\u0445, \u0449\u043e \u043d\u0430\u0434\u0445\u043e\u0434\u044f\u0442\u044c." + }, + "step": { + "user": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?", + "title": "Twilio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/de.json b/homeassistant/components/twinkly/translations/de.json index 2b4c70a0bad02a..c196f53262dd13 100644 --- a/homeassistant/components/twinkly/translations/de.json +++ b/homeassistant/components/twinkly/translations/de.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "device_exists": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/twinkly/translations/fr.json b/homeassistant/components/twinkly/translations/fr.json new file mode 100644 index 00000000000000..5071b7e302a6a7 --- /dev/null +++ b/homeassistant/components/twinkly/translations/fr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "device_exists": "D\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Connexion impossible" + }, + "step": { + "user": { + "data": { + "host": "Nom r\u00e9seau (ou adresse IP) de votre Twinkly" + }, + "description": "Configurer votre Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/lb.json b/homeassistant/components/twinkly/translations/lb.json new file mode 100644 index 00000000000000..2e00a8ae4dbfb8 --- /dev/null +++ b/homeassistant/components/twinkly/translations/lb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "device_exists": "Apparat ass scho konfigur\u00e9iert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/tr.json b/homeassistant/components/twinkly/translations/tr.json index 14365f988bde6c..d2e7173dad3282 100644 --- a/homeassistant/components/twinkly/translations/tr.json +++ b/homeassistant/components/twinkly/translations/tr.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "device_exists": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/twinkly/translations/uk.json b/homeassistant/components/twinkly/translations/uk.json new file mode 100644 index 00000000000000..bd256d31b0328b --- /dev/null +++ b/homeassistant/components/twinkly/translations/uk.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "device_exists": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "user": { + "data": { + "host": "\u0406\u043c'\u044f \u0445\u043e\u0441\u0442\u0430 (\u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430) \u0412\u0430\u0448\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Twinkly" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0432\u0456\u0442\u043b\u043e\u0434\u0456\u043e\u0434\u043d\u043e\u0457 \u0441\u0442\u0440\u0456\u0447\u043a\u0438 Twinkly", + "title": "Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index a07c034fe12a4f..f1cf4a6349b17b 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "El lloc del controlador ja est\u00e0 configurat" + "already_configured": "El lloc del controlador ja est\u00e0 configurat", + "configuration_updated": "S'ha actualitzat la configuraci\u00f3.", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "faulty_credentials": "[%key::common::config_flow::error::invalid_auth%]", "service_unavailable": "[%key::common::config_flow::error::cannot_connect%]", "unknown_client_mac": "No hi ha cap client disponible en aquesta adre\u00e7a MAC" }, + "flow_title": "Xarxa UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/cs.json b/homeassistant/components/unifi/translations/cs.json index 1247a97de9dd15..0281dfbb7500c1 100644 --- a/homeassistant/components/unifi/translations/cs.json +++ b/homeassistant/components/unifi/translations/cs.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "Ovlada\u010d je ji\u017e nastaven" + "already_configured": "Ovlada\u010d je ji\u017e nastaven", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" }, "error": { "faulty_credentials": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "service_unavailable": "Nepoda\u0159ilo se p\u0159ipojit", "unknown_client_mac": "Na t\u00e9to MAC adrese nen\u00ed dostupn\u00fd \u017e\u00e1dn\u00fd klient" }, + "flow_title": "UniFi s\u00ed\u0165 {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/da.json b/homeassistant/components/unifi/translations/da.json index 15ec878f1cee37..84dafd36e1a45c 100644 --- a/homeassistant/components/unifi/translations/da.json +++ b/homeassistant/components/unifi/translations/da.json @@ -7,6 +7,7 @@ "faulty_credentials": "Ugyldige legitimationsoplysninger", "service_unavailable": "Service utilg\u00e6ngelig" }, + "flow_title": "UniFi-netv\u00e6rket {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index 626236792ea947..be38ddf1a4d760 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Controller-Site ist bereits konfiguriert" }, "error": { - "faulty_credentials": "Ung\u00fcltige Anmeldeinformationen", + "faulty_credentials": "Ung\u00fcltige Authentifizierung", "service_unavailable": "Verbindung fehlgeschlagen", "unknown_client_mac": "Unter dieser MAC-Adresse ist kein Client verf\u00fcgbar." }, @@ -16,7 +16,7 @@ "port": "Port", "site": "Site-ID", "username": "Benutzername", - "verify_ssl": "Controller mit ordnungsgem\u00e4ssem Zertifikat" + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" }, "title": "UniFi-Controller einrichten" } @@ -51,7 +51,9 @@ }, "simple_options": { "data": { - "track_clients": "Netzwerk Ger\u00e4te \u00fcberwachen" + "block_client": "Clients mit Netzwerkzugriffskontrolle", + "track_clients": "Netzwerger\u00e4te \u00fcberwachen", + "track_devices": "Verfolgen von Netzwerkger\u00e4ten (Ubiquiti-Ger\u00e4te)" }, "description": "Konfigurieren Sie die UniFi-Integration" }, diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 06e8ae1eb60ab3..41507faa430c1d 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Controller site is already configured", + "configuration_updated": "Configuration updated.", "reauth_successful": "Re-authentication was successful" }, "error": { diff --git a/homeassistant/components/unifi/translations/es.json b/homeassistant/components/unifi/translations/es.json index 0fa4aaf2eb7a38..a676d70e88c484 100644 --- a/homeassistant/components/unifi/translations/es.json +++ b/homeassistant/components/unifi/translations/es.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "El sitio del controlador ya est\u00e1 configurado" + "already_configured": "El sitio del controlador ya est\u00e1 configurado", + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "faulty_credentials": "Autenticaci\u00f3n no v\u00e1lida", "service_unavailable": "Error al conectar", "unknown_client_mac": "Ning\u00fan cliente disponible en esa direcci\u00f3n MAC" }, + "flow_title": "Red UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/et.json b/homeassistant/components/unifi/translations/et.json index 8e95da9aa5b265..e9d76520435cf1 100644 --- a/homeassistant/components/unifi/translations/et.json +++ b/homeassistant/components/unifi/translations/et.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Kontroller on juba seadistatud" + "already_configured": "Kontroller on juba seadistatud", + "configuration_updated": "Seaded on v\u00e4rskendatud.", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "faulty_credentials": "Tuvastamine nurjus", "service_unavailable": "\u00dchendamine nurjus", "unknown_client_mac": "Sellel MAC-aadressil pole \u00fchtegi klienti saadaval" }, + "flow_title": "UniFi Network {site} ( {host} )", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index 79a7206923e6c1..d50018227c5d0a 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato" + "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato", + "configuration_updated": "Configurazione aggiornata.", + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { "faulty_credentials": "Autenticazione non valida", "service_unavailable": "Impossibile connettersi", "unknown_client_mac": "Nessun client disponibile su quell'indirizzo MAC" }, + "flow_title": "Rete UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/no.json b/homeassistant/components/unifi/translations/no.json index 5cda9ad7ab5748..72944a9d540b8b 100644 --- a/homeassistant/components/unifi/translations/no.json +++ b/homeassistant/components/unifi/translations/no.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Kontroller nettstedet er allerede konfigurert" + "already_configured": "Kontroller nettstedet er allerede konfigurert", + "configuration_updated": "Konfigurasjonen er oppdatert.", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { "faulty_credentials": "Ugyldig godkjenning", "service_unavailable": "Tilkobling mislyktes", "unknown_client_mac": "Ingen klient tilgjengelig p\u00e5 den MAC-adressen" }, + "flow_title": "UniFi-nettverk {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index 8ff5f1e4793163..6c8c74e726af8d 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana" + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana", + "configuration_updated": "Konfiguracja zaktualizowana", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "faulty_credentials": "Niepoprawne uwierzytelnienie", "service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "unknown_client_mac": "Brak klienta z tym adresem MAC" }, + "flow_title": "Sie\u0107 UniFi {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 789212dca17efe..3b69bf0ee33f11 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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": { "faulty_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "service_unavailable": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "unknown_client_mac": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043d\u0430 \u044d\u0442\u043e\u043c MAC-\u0430\u0434\u0440\u0435\u0441\u0435." }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/tr.json b/homeassistant/components/unifi/translations/tr.json index 903a7aaa21f7a0..c39fa08217aa3a 100644 --- a/homeassistant/components/unifi/translations/tr.json +++ b/homeassistant/components/unifi/translations/tr.json @@ -1,9 +1,22 @@ { "config": { + "abort": { + "already_configured": "Denetleyici sitesi zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "configuration_updated": "Yap\u0131land\u0131rma g\u00fcncellendi.", + "reauth_successful": "Yeniden kimlik do\u011frulama ba\u015far\u0131l\u0131 oldu" + }, + "error": { + "faulty_credentials": "Ge\u00e7ersiz kimlik do\u011frulama", + "service_unavailable": "Ba\u011flanma hatas\u0131", + "unknown_client_mac": "Bu MAC adresinde kullan\u0131labilir istemci yok" + }, + "flow_title": "UniFi A\u011f\u0131 {site} ( {host} )", "step": { "user": { "data": { + "host": "Ana Bilgisayar", "password": "Parola", + "port": "Port", "username": "Kullan\u0131c\u0131 ad\u0131" } } diff --git a/homeassistant/components/unifi/translations/uk.json b/homeassistant/components/unifi/translations/uk.json new file mode 100644 index 00000000000000..0f83c35840a8d7 --- /dev/null +++ b/homeassistant/components/unifi/translations/uk.json @@ -0,0 +1,66 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." + }, + "error": { + "faulty_credentials": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f.", + "service_unavailable": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "unknown_client_mac": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 \u043d\u0430 \u0446\u0456\u0439 MAC-\u0430\u0434\u0440\u0435\u0441\u0456." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "site": "ID \u0441\u0430\u0439\u0442\u0443", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "UniFi Controller" + } + } + }, + "options": { + "step": { + "client_control": { + "data": { + "block_client": "\u041a\u043b\u0456\u0454\u043d\u0442\u0438 \u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u043c \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "dpi_restrictions": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f \u0433\u0440\u0443\u043f\u0430\u043c\u0438 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u044c DPI", + "poe_clients": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 POE \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0456\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0456\u043d\u043d\u044f. \n\n\u0421\u0442\u0432\u043e\u0440\u0456\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447\u0456 \u0434\u043b\u044f \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u0445 \u043d\u043e\u043c\u0435\u0440\u0456\u0432, \u0434\u043b\u044f \u044f\u043a\u0438\u0445 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044e\u0432\u0430\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f UniFi. \u041a\u0440\u043e\u043a 2." + }, + "device_tracker": { + "data": { + "detection_time": "\u0427\u0430\u0441 \u0432\u0456\u0434 \u043e\u0441\u0442\u0430\u043d\u043d\u044c\u043e\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0443 \u0437\u0432'\u044f\u0437\u043a\u0443 \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c (\u0441\u0435\u043a.), \u043f\u043e \u0437\u0430\u043a\u0456\u043d\u0447\u0435\u043d\u043d\u044e \u044f\u043a\u043e\u0433\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043e\u0442\u0440\u0438\u043c\u0430\u0454 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0432\u0434\u043e\u043c\u0430\".", + "ignore_wired_bug": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043b\u043e\u0433\u0456\u043a\u0443 \u043f\u043e\u043c\u0438\u043b\u043a\u0438 \u0434\u043b\u044f \u0434\u0440\u043e\u0442\u043e\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 UniFi", + "ssid_filter": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c SSID \u0434\u043b\u044f \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u0431\u0435\u0437\u0434\u0440\u043e\u0442\u043e\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432", + "track_clients": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 \u043c\u0435\u0440\u0435\u0436\u0456", + "track_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 (\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 Ubiquiti)", + "track_wired_clients": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u043f\u0440\u043e\u0432\u0456\u0434\u043d\u0438\u0445 \u043c\u0435\u0440\u0435\u0436\u043d\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0432\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f UniFi. \u041a\u0440\u043e\u043a 1" + }, + "simple_options": { + "data": { + "block_client": "\u041a\u043b\u0456\u0454\u043d\u0442\u0438 \u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u043c \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "track_clients": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432 \u043c\u0435\u0440\u0435\u0436\u0456", + "track_devices": "\u0412\u0456\u0434\u0441\u0442\u0435\u0436\u0435\u043d\u043d\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 (\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 Ubiquiti)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 UniFi." + }, + "statistics_sensors": { + "data": { + "allow_bandwidth_sensors": "\u0414\u0430\u0442\u0447\u0438\u043a\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043d\u043e\u0457 \u0437\u0434\u0430\u0442\u043d\u043e\u0441\u0442\u0456 \u0434\u043b\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432", + "allow_uptime_sensors": "\u0421\u0435\u043d\u0441\u043e\u0440\u0438 \u0447\u0430\u0441\u0443 \u0440\u043e\u0431\u043e\u0442\u0438 \u0434\u043b\u044f \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0445 \u043a\u043b\u0456\u0454\u043d\u0442\u0456\u0432" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0441\u0435\u043d\u0441\u043e\u0440\u0456\u0432 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f UniFi. \u043a\u0440\u043e\u043a 3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/zh-Hant.json b/homeassistant/components/unifi/translations/zh-Hant.json index d87f8cf51e08e1..add0a387309e7e 100644 --- a/homeassistant/components/unifi/translations/zh-Hant.json +++ b/homeassistant/components/unifi/translations/zh-Hant.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "\u63a7\u5236\u5668\u4f4d\u5740\u5df2\u7d93\u8a2d\u5b9a" + "already_configured": "\u63a7\u5236\u5668\u4f4d\u5740\u5df2\u7d93\u8a2d\u5b9a", + "configuration_updated": "\u8a2d\u5b9a\u5df2\u66f4\u65b0\u3002", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "faulty_credentials": "\u9a57\u8b49\u78bc\u7121\u6548", "service_unavailable": "\u9023\u7dda\u5931\u6557", "unknown_client_mac": "\u8a72 Mac \u4f4d\u5740\u7121\u53ef\u7528\u5ba2\u6236\u7aef" }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/upb/translations/de.json b/homeassistant/components/upb/translations/de.json index ea6f1d3715049d..908db20f22b6a7 100644 --- a/homeassistant/components/upb/translations/de.json +++ b/homeassistant/components/upb/translations/de.json @@ -1,9 +1,12 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Fehler beim Herstellen einer Verbindung zu UPB PIM. Versuchen Sie es erneut.", - "invalid_upb_file": "Fehlende oder ung\u00fcltige UPB UPStart-Exportdatei, \u00fcberpr\u00fcfen Sie den Namen und den Pfad der Datei.", - "unknown": "Unerwarteter Fehler." + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_upb_file": "Fehlende oder ung\u00fcltige UPB UPStart-Exportdatei, \u00fcberpr\u00fcfe den Namen und den Pfad der Datei.", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { @@ -12,7 +15,7 @@ "file_path": "Pfad und Name der UPStart UPB-Exportdatei.", "protocol": "Protokoll" }, - "title": "Stellen Sie eine Verbindung zu UPB PIM her" + "title": "Stelle eine Verbindung zu UPB PIM her" } } } diff --git a/homeassistant/components/upb/translations/tr.json b/homeassistant/components/upb/translations/tr.json new file mode 100644 index 00000000000000..818531fcaa059f --- /dev/null +++ b/homeassistant/components/upb/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/uk.json b/homeassistant/components/upb/translations/uk.json new file mode 100644 index 00000000000000..062503848a8d0c --- /dev/null +++ b/homeassistant/components/upb/translations/uk.json @@ -0,0 +1,23 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_upb_file": "\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0456\u0439 \u0430\u0431\u043e \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u0444\u0430\u0439\u043b \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u0443 UPB UPStart, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0456\u043c'\u044f \u0456 \u0448\u043b\u044f\u0445 \u0434\u043e \u0444\u0430\u0439\u043b\u0443.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "address": "\u0410\u0434\u0440\u0435\u0441\u0430 (\u0434\u0438\u0432. \u043e\u043f\u0438\u0441 \u0432\u0438\u0449\u0435)", + "file_path": "\u0428\u043b\u044f\u0445 \u0456 \u0456\u043c'\u044f \u0444\u0430\u0439\u043b\u0443 \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u0443 UPStart UPB.", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b" + }, + "description": "\u0420\u044f\u0434\u043e\u043a \u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' \u0434\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'tcp' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '192.168.1.42'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'port' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 2101. \u0414\u043b\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 'serial' \u0430\u0434\u0440\u0435\u0441\u0430 \u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'tty[:baud]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: '/dev/ttyS1'). \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 'baud' \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e, \u0437\u0430 \u0437\u0430\u043c\u043e\u0432\u0447\u0443\u0432\u0430\u043d\u043d\u044f\u043c \u0432\u0456\u043d \u0434\u043e\u0440\u0456\u0432\u043d\u044e\u0454 4800.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e UPB PIM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/de.json b/homeassistant/components/upcloud/translations/de.json index 76bbc70569017a..ee1802f1d38132 100644 --- a/homeassistant/components/upcloud/translations/de.json +++ b/homeassistant/components/upcloud/translations/de.json @@ -1,7 +1,8 @@ { "config": { "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/tr.json b/homeassistant/components/upcloud/translations/tr.json new file mode 100644 index 00000000000000..f1840698493b58 --- /dev/null +++ b/homeassistant/components/upcloud/translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "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/upcloud/translations/uk.json b/homeassistant/components/upcloud/translations/uk.json new file mode 100644 index 00000000000000..bf8781c1eb2a4a --- /dev/null +++ b/homeassistant/components/upcloud/translations/uk.json @@ -0,0 +1,25 @@ +{ + "config": { + "error": { + "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." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0456\u043d\u0456\u043c\u0443\u043c 30)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ro.json b/homeassistant/components/upnp/translations/ro.json index ceb1c19131abe1..2fd83a0b371bf4 100644 --- a/homeassistant/components/upnp/translations/ro.json +++ b/homeassistant/components/upnp/translations/ro.json @@ -7,6 +7,13 @@ "few": "", "one": "Unul", "other": "" + }, + "step": { + "init": { + "few": "Pu\u021bine", + "one": "Unul", + "other": "Altele" + } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/tr.json b/homeassistant/components/upnp/translations/tr.json new file mode 100644 index 00000000000000..2715f66e090919 --- /dev/null +++ b/homeassistant/components/upnp/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "flow_title": "UPnP / IGD: {name}", + "step": { + "ssdp_confirm": { + "description": "Bu UPnP / IGD cihaz\u0131n\u0131 kurmak istiyor musunuz?" + }, + "user": { + "data": { + "scan_interval": "G\u00fcncelleme aral\u0131\u011f\u0131 (saniye, minimum 30)", + "usn": "Cihaz" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/uk.json b/homeassistant/components/upnp/translations/uk.json index 0b8747f902ec7f..905958eeca9706 100644 --- a/homeassistant/components/upnp/translations/uk.json +++ b/homeassistant/components/upnp/translations/uk.json @@ -1,7 +1,21 @@ { "config": { "abort": { - "already_configured": "UPnP/IGD \u0432\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0454\u043d\u043e" + "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.", + "incomplete_discovery": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441.", + "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." + }, + "flow_title": "UPnP/IGD: {name}", + "step": { + "ssdp_confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u0446\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 UPnP / IGD?" + }, + "user": { + "data": { + "scan_interval": "\u0427\u0430\u0441\u0442\u043e\u0442\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0456\u043d\u0456\u043c\u0443\u043c 30)", + "usn": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/de.json b/homeassistant/components/vacuum/translations/de.json index be137a5566bd1b..8de386b3506e18 100644 --- a/homeassistant/components/vacuum/translations/de.json +++ b/homeassistant/components/vacuum/translations/de.json @@ -18,7 +18,7 @@ "cleaning": "Reinigen", "docked": "Angedockt", "error": "Fehler", - "idle": "Standby", + "idle": "Unt\u00e4tig", "off": "Aus", "on": "An", "paused": "Pausiert", diff --git a/homeassistant/components/vacuum/translations/uk.json b/homeassistant/components/vacuum/translations/uk.json index 9febc8aff1f3c5..64223a85f74491 100644 --- a/homeassistant/components/vacuum/translations/uk.json +++ b/homeassistant/components/vacuum/translations/uk.json @@ -1,4 +1,18 @@ { + "device_automation": { + "action_type": { + "clean": "\u0412\u0456\u0434\u043f\u0440\u0430\u0432\u0438\u0442\u0438 {entity_name} \u0440\u043e\u0431\u0438\u0442\u0438 \u043f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", + "dock": "{entity_name}: \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u0438 \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u044e" + }, + "condition_type": { + "is_cleaning": "{entity_name} \u0432\u0438\u043a\u043e\u043d\u0443\u0454 \u043f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", + "is_docked": "{entity_name} \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u0457" + }, + "trigger_type": { + "cleaning": "{entity_name} \u043f\u043e\u0447\u0438\u043d\u0430\u0454 \u043f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", + "docked": "{entity_name} \u0441\u0442\u0438\u043a\u0443\u0454\u0442\u044c\u0441\u044f \u0437 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u0454\u044e" + } + }, "state": { "_": { "cleaning": "\u041f\u0440\u0438\u0431\u0438\u0440\u0430\u043d\u043d\u044f", @@ -8,7 +22,7 @@ "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", "on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", "paused": "\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0435\u043d\u043e", - "returning": "\u041f\u043e\u0432\u0435\u0440\u043d\u0435\u043d\u043d\u044f \u0434\u043e \u0434\u043e\u043a\u0430" + "returning": "\u041f\u043e\u0432\u0435\u0440\u043d\u0435\u043d\u043d\u044f \u043d\u0430 \u0434\u043e\u043a-\u0441\u0442\u0430\u043d\u0446\u0456\u044e" } }, "title": "\u041f\u0438\u043b\u043e\u0441\u043e\u0441" diff --git a/homeassistant/components/velbus/translations/de.json b/homeassistant/components/velbus/translations/de.json index c6c872c85e6c82..9bbb23b1bcd85c 100644 --- a/homeassistant/components/velbus/translations/de.json +++ b/homeassistant/components/velbus/translations/de.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "user": { diff --git a/homeassistant/components/velbus/translations/tr.json b/homeassistant/components/velbus/translations/tr.json new file mode 100644 index 00000000000000..e7ee4ea7157311 --- /dev/null +++ b/homeassistant/components/velbus/translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/uk.json b/homeassistant/components/velbus/translations/uk.json new file mode 100644 index 00000000000000..6e8b97cc4579bf --- /dev/null +++ b/homeassistant/components/velbus/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "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" + }, + "step": { + "user": { + "data": { + "name": "\u041d\u0430\u0437\u0432\u0430", + "port": "\u0420\u044f\u0434\u043e\u043a \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f" + }, + "title": "Velbus" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/tr.json b/homeassistant/components/vera/translations/tr.json new file mode 100644 index 00000000000000..35e81599bb1dc5 --- /dev/null +++ b/homeassistant/components/vera/translations/tr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "cannot_connect": "{base_url} url'si ile denetleyiciye ba\u011flan\u0131lamad\u0131" + } + }, + "options": { + "step": { + "init": { + "title": "Vera denetleyici se\u00e7enekleri" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/uk.json b/homeassistant/components/vera/translations/uk.json new file mode 100644 index 00000000000000..8c591a1cc105eb --- /dev/null +++ b/homeassistant/components/vera/translations/uk.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u043e\u043c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e {base_url}." + }, + "step": { + "user": { + "data": { + "exclude": "ID \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera, \u0434\u043b\u044f \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0437 Home Assistant", + "lights": "ID \u0432\u0438\u043c\u0438\u043a\u0430\u0447\u0456\u0432 Vera, \u0434\u043b\u044f \u0456\u043c\u043f\u043e\u0440\u0442\u0443 \u0432 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f", + "vera_controller_url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c URL-\u0430\u0434\u0440\u0435\u0441\u0443 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 'http://192.168.1.161:3480').", + "title": "Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera \u0434\u043b\u044f \u0432\u0438\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0437 Home Assistant.", + "lights": "\u0406\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440\u0438 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 Vera \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043f\u0440\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u0437 \u0432\u0438\u043c\u0438\u043a\u0430\u0447\u0430 \u0432 \u043e\u0441\u0432\u0456\u0442\u043b\u0435\u043d\u043d\u044f \u0432 Home Assistant." + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438 \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 \u0434\u043e\u0434\u0430\u0442\u043a\u043e\u0432\u0456 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438: https://www.home-assistant.io/integrations/vera/.\n\u0414\u043b\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f \u0431\u0443\u0434\u044c-\u044f\u043a\u0438\u0445 \u0437\u043c\u0456\u043d \u043f\u043e\u0442\u0440\u0456\u0431\u0435\u043d \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0430 Home Assistant. \u0429\u043e\u0431 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f, \u043f\u043e\u0441\u0442\u0430\u0432\u0442\u0435 \u043f\u0440\u043e\u0431\u0456\u043b.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0430 Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/de.json b/homeassistant/components/vesync/translations/de.json index c52b10c3293db8..ea05a60ff82a37 100644 --- a/homeassistant/components/vesync/translations/de.json +++ b/homeassistant/components/vesync/translations/de.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vesync/translations/tr.json b/homeassistant/components/vesync/translations/tr.json new file mode 100644 index 00000000000000..8b4f8b6063058a --- /dev/null +++ b/homeassistant/components/vesync/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta" + }, + "title": "Kullan\u0131c\u0131 Ad\u0131 ve \u015eifre Girin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/uk.json b/homeassistant/components/vesync/translations/uk.json new file mode 100644 index 00000000000000..7f6b3a46b15524 --- /dev/null +++ b/homeassistant/components/vesync/translations/uk.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0410\u0434\u0440\u0435\u0441\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0457 \u043f\u043e\u0448\u0442\u0438" + }, + "title": "VeSync" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/de.json b/homeassistant/components/vilfo/translations/de.json index 4880154b58e42c..8f20c074ff4913 100644 --- a/homeassistant/components/vilfo/translations/de.json +++ b/homeassistant/components/vilfo/translations/de.json @@ -4,9 +4,9 @@ "already_configured": "Dieser Vilfo Router ist bereits konfiguriert." }, "error": { - "cannot_connect": "Verbindung nicht m\u00f6glich. Bitte \u00fcberpr\u00fcfen Sie die von Ihnen angegebenen Informationen und versuchen Sie es erneut.", - "invalid_auth": "Ung\u00fcltige Authentifizierung. Bitte \u00fcberpr\u00fcfen Sie den Zugriffstoken und versuchen Sie es erneut.", - "unknown": "Beim Einrichten der Integration ist ein unerwarteter Fehler aufgetreten." + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung. Bitte \u00fcberpr\u00fcfe den Zugriffstoken und versuche es erneut.", + "unknown": "Unerwarteter Fehler" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/tr.json b/homeassistant/components/vilfo/translations/tr.json new file mode 100644 index 00000000000000..dc66041e35a2aa --- /dev/null +++ b/homeassistant/components/vilfo/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/uk.json b/homeassistant/components/vilfo/translations/uk.json new file mode 100644 index 00000000000000..1a93176f290740 --- /dev/null +++ b/homeassistant/components/vilfo/translations/uk.json @@ -0,0 +1,22 @@ +{ + "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." + }, + "error": { + "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.", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Vilfo. \u0412\u043a\u0430\u0436\u0456\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0456 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 API. \u0414\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 \u043f\u0440\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457, \u0432\u0456\u0434\u0432\u0456\u0434\u0430\u0439\u0442\u0435 \u0432\u0435\u0431-\u0441\u0430\u0439\u0442: https://www.home-assistant.io/integrations/vilfo.", + "title": "Vilfo Router" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/de.json b/homeassistant/components/vizio/translations/de.json index ddb68ec09faa98..ad0cc604d133dd 100644 --- a/homeassistant/components/vizio/translations/de.json +++ b/homeassistant/components/vizio/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "cannot_connect": "Verbindungsfehler", + "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", "updated_entry": "Dieser Eintrag wurde bereits eingerichtet, aber der Name, die Apps und / oder die in der Konfiguration definierten Optionen stimmen nicht mit der zuvor importierten Konfiguration \u00fcberein, sodass der Konfigurationseintrag entsprechend aktualisiert wurde." }, "error": { @@ -10,17 +11,17 @@ "step": { "pair_tv": { "data": { - "pin": "PIN" + "pin": "PIN-Code" }, "description": "Ihr Fernseher sollte einen Code anzeigen. Geben Sie diesen Code in das Formular ein und fahren Sie mit dem n\u00e4chsten Schritt fort, um die Kopplung abzuschlie\u00dfen.", "title": "Schlie\u00dfen Sie den Pairing-Prozess ab" }, "pairing_complete": { - "description": "Ihr VIZIO SmartCast-Ger\u00e4t ist jetzt mit Home Assistant verbunden.", + "description": "Dein Richten Sie das VIZIO SmartCast-Ger\u00e4t ein ist jetzt mit Home Assistant verbunden.", "title": "Kopplung abgeschlossen" }, "pairing_complete_import": { - "description": "Ihr VIZIO SmartCast-Fernseher ist jetzt mit Home Assistant verbunden. \n\n Ihr Zugriffstoken ist '**{access_token}**'.", + "description": "Dein Richten Sie das VIZIO SmartCast-Ger\u00e4t ein ist jetzt mit Home Assistant verbunden.\n\nDein Zugangstoken ist '**{access_token}**'.", "title": "Kopplung abgeschlossen" }, "user": { @@ -30,7 +31,7 @@ "host": "Host", "name": "Name" }, - "description": "Ein Zugriffstoken wird nur f\u00fcr Fernsehger\u00e4te ben\u00f6tigt. Wenn Sie ein Fernsehger\u00e4t konfigurieren und noch kein Zugriffstoken haben, lassen Sie es leer, um einen Pairing-Vorgang durchzuf\u00fchren.", + "description": "Ein Zugangstoken wird nur f\u00fcr Fernsehger\u00e4te ben\u00f6tigt. Wenn du ein Fernsehger\u00e4t konfigurierst und noch kein Zugangstoken hast, lass es leer, um einen Pairing-Vorgang durchzuf\u00fchren.", "title": "Richten Sie das VIZIO SmartCast-Ger\u00e4t ein" } } @@ -44,7 +45,7 @@ "volume_step": "Lautst\u00e4rken-Schrittgr\u00f6\u00dfe" }, "description": "Wenn Sie \u00fcber ein Smart-TV-Ger\u00e4t verf\u00fcgen, k\u00f6nnen Sie Ihre Quellliste optional filtern, indem Sie ausw\u00e4hlen, welche Apps in Ihre Quellliste aufgenommen oder ausgeschlossen werden sollen.", - "title": "Aktualisieren Sie die VIZIO SmartCast-Optionen" + "title": "Aktualisiere die Richten Sie das VIZIO SmartCast-Ger\u00e4t ein-Optionen" } } } diff --git a/homeassistant/components/vizio/translations/tr.json b/homeassistant/components/vizio/translations/tr.json new file mode 100644 index 00000000000000..4b923cfb4b3b8a --- /dev/null +++ b/homeassistant/components/vizio/translations/tr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured_device": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "user": { + "data": { + "access_token": "Eri\u015fim Belirteci", + "host": "Ana Bilgisayar" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/uk.json b/homeassistant/components/vizio/translations/uk.json new file mode 100644 index 00000000000000..958307d543f92f --- /dev/null +++ b/homeassistant/components/vizio/translations/uk.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured_device": "\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", + "updated_entry": "\u0426\u044f \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439, \u0430\u043b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438, \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u0432 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457, \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u044e\u0442\u044c \u0440\u0430\u043d\u0456\u0448\u0435 \u0456\u043c\u043f\u043e\u0440\u0442\u043e\u0432\u0430\u043d\u0438\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f\u043c, \u0442\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457 \u0431\u0443\u043b\u0430 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u0438\u043c \u0447\u0438\u043d\u043e\u043c \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "complete_pairing_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438. \u041f\u0435\u0440\u0448 \u043d\u0456\u0436 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0438 \u0441\u043f\u0440\u043e\u0431\u0443, \u043f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u0412\u0430\u043c\u0438 PIN-\u043a\u043e\u0434 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439, \u0430 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0456 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 \u0434\u043e \u043c\u0435\u0440\u0435\u0436\u0456.", + "existing_config_entry_found": "\u0406\u0441\u043d\u0443\u044e\u0447\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 VIZIO SmartCast \u0437 \u0442\u0430\u043a\u0438\u043c \u0441\u0435\u0440\u0456\u0439\u043d\u0438\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e. \u0412\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0456\u0441\u043d\u0443\u044e\u0447\u0438\u0439 \u0437\u0430\u043f\u0438\u0441, \u0449\u043e\u0431 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0442\u043e\u0447\u043d\u0438\u0439." + }, + "step": { + "pair_tv": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0430\u0448 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0437\u0430\u0440\u0430\u0437 \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u043a\u043e\u0434. \u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0446\u0435\u0439 \u043a\u043e\u0434 \u0443 \u0444\u043e\u0440\u043c\u0443, \u0430 \u043f\u043e\u0442\u0456\u043c \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u043a\u0440\u043e\u043a\u0443, \u0449\u043e\u0431 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0438 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", + "title": "\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0443 \u0441\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f" + }, + "pairing_complete": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 VIZIO SmartCast \u0442\u0435\u043f\u0435\u0440 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e" + }, + "pairing_complete_import": { + "description": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 VIZIO SmartCast \u0442\u0435\u043f\u0435\u0440 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u043e Home Assistant. \n\n \u0412\u0430\u0448 \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 - '** {access_token} **'.", + "title": "\u0421\u043f\u0430\u0440\u044e\u0432\u0430\u043d\u043d\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043e" + }, + "user": { + "data": { + "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443", + "device_class": "\u0422\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e", + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0439 \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440\u0456\u0432. \u042f\u043a\u0449\u043e \u0412\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0443\u0454\u0442\u0435 \u0442\u0435\u043b\u0435\u0432\u0456\u0437\u043e\u0440 \u0456 \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0443 \u0412\u0430\u0441 \u0449\u0435 \u043d\u0435 \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043e, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u0446\u0435 \u043f\u043e\u043b\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c, \u0449\u043e\u0431 \u0432\u0438\u043a\u043e\u043d\u0430\u0442\u0438 \u043f\u0440\u043e\u0446\u0435\u0441 \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f \u043f\u0430\u0440\u0438.", + "title": "VIZIO SmartCast" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0436\u0435\u0440\u0435\u043b", + "include_or_exclude": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0430\u0431\u043e \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0442\u0438 \u0434\u0436\u0435\u0440\u0435\u043b\u0430?", + "volume_step": "\u041a\u0440\u043e\u043a \u0433\u0443\u0447\u043d\u043e\u0441\u0442\u0456" + }, + "description": "\u042f\u043a\u0449\u043e \u0443 \u0432\u0430\u0441 \u0454 Smart TV, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438 \u0431\u0430\u0436\u0430\u043d\u043d\u0456 \u0432\u0456\u0434\u0444\u0456\u043b\u044c\u0442\u0440\u0443\u0432\u0430\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0436\u0435\u0440\u0435\u043b, \u0432\u043a\u043b\u044e\u0447\u0438\u0432\u0448\u0438 \u0430\u0431\u043e \u0432\u0438\u043a\u043b\u044e\u0447\u0438\u0432\u0448\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438 \u0437\u0456 \u0441\u043f\u0438\u0441\u043a\u0443.", + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f VIZIO SmartCast" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/de.json b/homeassistant/components/volumio/translations/de.json index ef455299de6e66..45727d85ee05d0 100644 --- a/homeassistant/components/volumio/translations/de.json +++ b/homeassistant/components/volumio/translations/de.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "error": { - "cannot_connect": "Verbindungsfehler" + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/tr.json b/homeassistant/components/volumio/translations/tr.json new file mode 100644 index 00000000000000..249bb17d64ecac --- /dev/null +++ b/homeassistant/components/volumio/translations/tr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "cannot_connect": "Ke\u015ffedilen Volumio'ya ba\u011flan\u0131lam\u0131yor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/uk.json b/homeassistant/components/volumio/translations/uk.json index 58947e14e4f7e0..c517eafa2bdbd4 100644 --- a/homeassistant/components/volumio/translations/uk.json +++ b/homeassistant/components/volumio/translations/uk.json @@ -1,14 +1,16 @@ { "config": { "abort": { - "already_configured": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437'\u0454\u0434\u043d\u0430\u043d\u043d\u044f \u0456\u0437 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0438\u043c Volumio." }, "error": { - "cannot_connect": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "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": { "discovery_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 Volumio `{name}`?", "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e Volumio" }, "user": { diff --git a/homeassistant/components/water_heater/translations/uk.json b/homeassistant/components/water_heater/translations/uk.json new file mode 100644 index 00000000000000..d6558828a8ec91 --- /dev/null +++ b/homeassistant/components/water_heater/translations/uk.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "{entity_name}: \u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "{entity_name}: \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/de.json b/homeassistant/components/wemo/translations/de.json index f20ad5598ab2b2..81694f65ea295f 100644 --- a/homeassistant/components/wemo/translations/de.json +++ b/homeassistant/components/wemo/translations/de.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Es wurden keine Wemo-Ger\u00e4te im Netzwerk gefunden.", - "single_instance_allowed": "Nur eine einzige Konfiguration von Wemo ist zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/tr.json b/homeassistant/components/wemo/translations/tr.json index 411a536ceedbad..a87d832eece777 100644 --- a/homeassistant/components/wemo/translations/tr.json +++ b/homeassistant/components/wemo/translations/tr.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "no_devices_found": "A\u011fda Wemo cihaz\u0131 bulunamad\u0131." + "no_devices_found": "A\u011fda Wemo cihaz\u0131 bulunamad\u0131.", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Wemo'yu kurmak istiyor musunuz?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/uk.json b/homeassistant/components/wemo/translations/uk.json new file mode 100644 index 00000000000000..1217d664234241 --- /dev/null +++ b/homeassistant/components/wemo/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 Wemo?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/tr.json b/homeassistant/components/wiffi/translations/tr.json new file mode 100644 index 00000000000000..26ec2e61e002e8 --- /dev/null +++ b/homeassistant/components/wiffi/translations/tr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "addr_in_use": "Sunucu ba\u011flant\u0131 noktas\u0131 zaten kullan\u0131l\u0131yor.", + "start_server_failed": "Ba\u015flatma sunucusu ba\u015far\u0131s\u0131z oldu." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Zaman a\u015f\u0131m\u0131 (dakika)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/uk.json b/homeassistant/components/wiffi/translations/uk.json new file mode 100644 index 00000000000000..dc8dac9cd56ecf --- /dev/null +++ b/homeassistant/components/wiffi/translations/uk.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "addr_in_use": "\u041f\u043e\u0440\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f.", + "start_server_failed": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440." + }, + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f TCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432 WIFFI" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0445\u0432\u0438\u043b\u0438\u043d\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/de.json b/homeassistant/components/wilight/translations/de.json index 07d00495af7f9d..d56e782279aa39 100644 --- a/homeassistant/components/wilight/translations/de.json +++ b/homeassistant/components/wilight/translations/de.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, "flow_title": "WiLight: {name}", "step": { "confirm": { diff --git a/homeassistant/components/wilight/translations/tr.json b/homeassistant/components/wilight/translations/tr.json new file mode 100644 index 00000000000000..5307276a71d3a3 --- /dev/null +++ b/homeassistant/components/wilight/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/uk.json b/homeassistant/components/wilight/translations/uk.json new file mode 100644 index 00000000000000..7517538499e413 --- /dev/null +++ b/homeassistant/components/wilight/translations/uk.json @@ -0,0 +1,16 @@ +{ + "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.", + "not_supported_device": "\u0426\u0435\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430\u0440\u0430\u0437\u0456 \u043d\u0435 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454\u0442\u044c\u0441\u044f.", + "not_wilight_device": "\u0426\u0435 \u043d\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 WiLight." + }, + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 WiLight {name}? \n\n \u0426\u0435 \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0443\u0454: {components}", + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/de.json b/homeassistant/components/withings/translations/de.json index d217640e44b835..05d3795a0b01ff 100644 --- a/homeassistant/components/withings/translations/de.json +++ b/homeassistant/components/withings/translations/de.json @@ -1,22 +1,30 @@ { "config": { "abort": { - "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Autorisierungs-URL.", - "missing_configuration": "Die Withings-Integration ist nicht konfiguriert. Bitte folgen Sie der Dokumentation." + "already_configured": "Konfiguration des Profils aktualisiert.", + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url})." }, "create_entry": { "default": "Erfolgreiche Authentifizierung mit Withings." }, + "error": { + "already_configured": "Konto wurde bereits konfiguriert" + }, "step": { "pick_implementation": { - "title": "Authentifizierungsmethode ausw\u00e4hlen" + "title": "W\u00e4hle die Authentifizierungsmethode" }, "profile": { "data": { - "profile": "Profil" + "profile": "Profilname" }, "description": "Welches Profil hast du auf der Withings-Website ausgew\u00e4hlt? Es ist wichtig, dass die Profile \u00fcbereinstimmen, da sonst die Daten falsch beschriftet werden.", "title": "Benutzerprofil" + }, + "reauth": { + "title": "Integration erneut authentifizieren" } } } diff --git a/homeassistant/components/withings/translations/fr.json b/homeassistant/components/withings/translations/fr.json index 017a9e63078763..b5f524698f5e96 100644 --- a/homeassistant/components/withings/translations/fr.json +++ b/homeassistant/components/withings/translations/fr.json @@ -10,7 +10,7 @@ "default": "Authentifi\u00e9 avec succ\u00e8s \u00e0 Withings pour le profil s\u00e9lectionn\u00e9." }, "error": { - "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" }, "flow_title": "Withings: {profile}", "step": { diff --git a/homeassistant/components/withings/translations/tr.json b/homeassistant/components/withings/translations/tr.json new file mode 100644 index 00000000000000..4e0228708ea085 --- /dev/null +++ b/homeassistant/components/withings/translations/tr.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Profil i\u00e7in yap\u0131land\u0131rma g\u00fcncellendi." + }, + "error": { + "already_configured": "Hesap zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "step": { + "profile": { + "data": { + "profile": "Profil Ad\u0131" + }, + "title": "Kullan\u0131c\u0131 profili." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/uk.json b/homeassistant/components/withings/translations/uk.json new file mode 100644 index 00000000000000..5efc27042b1756 --- /dev/null +++ b/homeassistant/components/withings/translations/uk.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "\u041e\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u043f\u0440\u043e\u0444\u0456\u043b\u044e.", + "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." + }, + "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." + }, + "error": { + "already_configured": "\u0426\u0435\u0439 \u043e\u0431\u043b\u0456\u043a\u043e\u0432\u0438\u0439 \u0437\u0430\u043f\u0438\u0441 \u0432\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0432 Home Assistant." + }, + "flow_title": "Withings: {profile}", + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + }, + "profile": { + "data": { + "profile": "\u041d\u0430\u0437\u0432\u0430 \u043f\u0440\u043e\u0444\u0456\u043b\u044e" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0435 \u0456\u043c'\u044f \u043f\u0440\u043e\u0444\u0456\u043b\u044e \u0434\u043b\u044f \u0446\u0438\u0445 \u0434\u0430\u043d\u0438\u0445. \u042f\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u0446\u0435 \u043d\u0430\u0437\u0432\u0430, \u043e\u0431\u0440\u0430\u043d\u0430 \u043d\u0430 \u043f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e\u043c\u0443 \u043a\u0440\u043e\u0446\u0456.", + "title": "Withings" + }, + "reauth": { + "description": "\u041f\u0440\u043e\u0444\u0456\u043b\u044c \"{profile}\" \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u043e\u0432\u0436\u0435\u043d\u043d\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445 Withings.", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u044e" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/de.json b/homeassistant/components/wled/translations/de.json index ff12e429bd6c14..0dd13f763d6f52 100644 --- a/homeassistant/components/wled/translations/de.json +++ b/homeassistant/components/wled/translations/de.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "Dieses WLED-Ger\u00e4t ist bereits konfiguriert." + "already_configured": "Dieses WLED-Ger\u00e4t ist bereits konfiguriert.", + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "WLED: {name}", "step": { diff --git a/homeassistant/components/wled/translations/tr.json b/homeassistant/components/wled/translations/tr.json new file mode 100644 index 00000000000000..f02764c8abab8b --- /dev/null +++ b/homeassistant/components/wled/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": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "Ana Bilgisayar" + }, + "description": "WLED'inizi Home Assistant ile t\u00fcmle\u015ftirmek i\u00e7in ayarlay\u0131n." + }, + "zeroconf_confirm": { + "description": "Home Assistant'a '{name}' adl\u0131 WLED'i eklemek istiyor musunuz?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/uk.json b/homeassistant/components/wled/translations/uk.json new file mode 100644 index 00000000000000..c0280d33993a99 --- /dev/null +++ b/homeassistant/components/wled/translations/uk.json @@ -0,0 +1,24 @@ +{ + "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.", + "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" + }, + "flow_title": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437 WLED." + }, + "zeroconf_confirm": { + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 WLED `{name}`?", + "title": "\u0412\u0438\u044f\u0432\u043b\u0435\u043d\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 WLED" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/de.json b/homeassistant/components/wolflink/translations/de.json index cb7e571d1e6998..71f48a6413dcad 100644 --- a/homeassistant/components/wolflink/translations/de.json +++ b/homeassistant/components/wolflink/translations/de.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, "step": { "device": { "data": { diff --git a/homeassistant/components/wolflink/translations/sensor.de.json b/homeassistant/components/wolflink/translations/sensor.de.json index 373e19895784ed..9680716cd19e2f 100644 --- a/homeassistant/components/wolflink/translations/sensor.de.json +++ b/homeassistant/components/wolflink/translations/sensor.de.json @@ -1,6 +1,7 @@ { "state": { "wolflink__state": { + "permanent": "Permanent", "solarbetrieb": "Solarmodus", "sparbetrieb": "Sparmodus", "sparen": "Sparen", diff --git a/homeassistant/components/wolflink/translations/sensor.tr.json b/homeassistant/components/wolflink/translations/sensor.tr.json index 8b2eb0a8c53052..4b1e2778af13a6 100644 --- a/homeassistant/components/wolflink/translations/sensor.tr.json +++ b/homeassistant/components/wolflink/translations/sensor.tr.json @@ -1,10 +1,19 @@ { "state": { "wolflink__state": { + "glt_betrieb": "BMS modu", + "heizbetrieb": "Is\u0131tma modu", + "kalibration_heizbetrieb": "Is\u0131tma modu kalibrasyonu", + "kalibration_kombibetrieb": "Kombi modu kalibrasyonu", + "reduzierter_betrieb": "S\u0131n\u0131rl\u0131 mod", + "solarbetrieb": "G\u00fcne\u015f modu", + "sparbetrieb": "Ekonomi modu", "standby": "Bekleme", "start": "Ba\u015flat", "storung": "Hata", - "test": "Test" + "test": "Test", + "urlaubsmodus": "Tatil modu", + "warmwasserbetrieb": "DHW modu" } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.uk.json b/homeassistant/components/wolflink/translations/sensor.uk.json index 665ff99992c16a..c8a69f2c007c78 100644 --- a/homeassistant/components/wolflink/translations/sensor.uk.json +++ b/homeassistant/components/wolflink/translations/sensor.uk.json @@ -1,15 +1,87 @@ { "state": { "wolflink__state": { + "1_x_warmwasser": "1 \u0445 \u0413\u0412\u041f", + "abgasklappe": "\u0417\u0430\u0441\u043b\u0456\u043d\u043a\u0430 \u0434\u0438\u043c\u043e\u0432\u0438\u0445 \u0433\u0430\u0437\u0456\u0432", + "absenkbetrieb": "\u0420\u0435\u0436\u0438\u043c \u0430\u0432\u0430\u0440\u0456\u0457", + "absenkstop": "\u0410\u0432\u0430\u0440\u0456\u0439\u043d\u0430 \u0437\u0443\u043f\u0438\u043d\u043a\u0430", + "aktiviert": "\u0410\u043a\u0442\u0438\u0432\u043e\u0432\u0430\u043d\u043e", + "antilegionellenfunktion": "\u0424\u0443\u043d\u043a\u0446\u0456\u044f \u0430\u043d\u0442\u0438-\u043b\u0435\u0433\u0438\u043e\u043d\u0435\u043b\u043b\u0438", + "at_abschaltung": "\u041e\u0422 \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f", + "at_frostschutz": "\u041e\u0422 \u0437\u0430\u0445\u0438\u0441\u0442 \u0432\u0456\u0434 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f", + "aus": "\u0412\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "auto": "\u0410\u0432\u0442\u043e", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043d\u044f", + "automatik_ein": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0432\u043c\u0438\u043a\u0430\u043d\u043d\u044f", + "bereit_keine_ladung": "\u0413\u043e\u0442\u043e\u0432\u0438\u0439, \u043d\u0435 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0443\u0454\u0442\u044c\u0441\u044f", + "betrieb_ohne_brenner": "\u0420\u043e\u0431\u043e\u0442\u0430 \u0431\u0435\u0437 \u043f\u0430\u043b\u044c\u043d\u0438\u043a\u0430", + "cooling": "\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "deaktiviert": "\u041d\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e", + "dhw_prior": "DHWPrior", + "eco": "\u0415\u043a\u043e", + "ein": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", + "estrichtrocknung": "\u0421\u0443\u0448\u0456\u043d\u043d\u044f", + "externe_deaktivierung": "\u0417\u043e\u0432\u043d\u0456\u0448\u043d\u044f \u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0456\u044f", + "fernschalter_ein": "\u0414\u0438\u0441\u0442\u0430\u043d\u0446\u0456\u0439\u043d\u0435 \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e", + "frost_heizkreis": "\u0417\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f \u043a\u043e\u043d\u0442\u0443\u0440\u0443 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "frost_warmwasser": "\u0417\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f \u0413\u0412\u041f", + "frostschutz": "\u0417\u0430\u0445\u0438\u0441\u0442 \u0432\u0456\u0434 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f", + "gasdruck": "\u0422\u0438\u0441\u043a \u0433\u0430\u0437\u0443", + "glt_betrieb": "\u0420\u0435\u0436\u0438\u043c BMS", + "gradienten_uberwachung": "\u0413\u0440\u0430\u0434\u0456\u0454\u043d\u0442\u043d\u0438\u0439 \u043c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433", + "heizbetrieb": "\u0420\u0435\u0436\u0438\u043c \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "heizgerat_mit_speicher": "\u041a\u043e\u0442\u0435\u043b \u0437 \u0446\u0438\u043b\u0456\u043d\u0434\u0440\u043e\u043c", + "heizung": "\u041e\u0431\u0456\u0433\u0440\u0456\u0432", + "initialisierung": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f", + "kalibration": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f", + "kalibration_heizbetrieb": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0440\u0435\u0436\u0438\u043c\u0443 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "kalibration_kombibetrieb": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0432 \u043a\u043e\u043c\u0431\u0456\u043d\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0440\u0435\u0436\u0438\u043c\u0456", + "kalibration_warmwasserbetrieb": "\u041a\u0430\u043b\u0456\u0431\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0413\u0412\u041f", + "kaskadenbetrieb": "\u041a\u0430\u0441\u043a\u0430\u0434\u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0456\u044f", + "kombibetrieb": "\u041a\u043e\u043c\u0431\u0456\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "kombigerat": "\u0414\u0432\u043e\u043a\u043e\u043d\u0442\u0443\u0440\u043d\u0438\u0439 \u043a\u043e\u0442\u0435\u043b", + "kombigerat_mit_solareinbindung": "\u0414\u0432\u043e\u043a\u043e\u043d\u0442\u0443\u0440\u043d\u0438\u0439 \u043a\u043e\u0442\u0435\u043b \u0437 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0454\u044e \u0441\u043e\u043d\u044f\u0447\u043d\u043e\u0457 \u0441\u0438\u0441\u0442\u0435\u043c\u0438", + "mindest_kombizeit": "\u041c\u0456\u043d\u0456\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u043a\u043e\u043c\u0431\u0456\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0447\u0430\u0441", + "nachlauf_heizkreispumpe": "\u0420\u043e\u0431\u043e\u0442\u0430 \u043d\u0430\u0441\u043e\u0441\u0430 \u043a\u043e\u043d\u0442\u0443\u0440\u0443 \u043e\u043f\u0430\u043b\u0435\u043d\u043d\u044f", + "nachspulen": "\u041f\u043e\u0441\u0442-\u043f\u0440\u043e\u043c\u0438\u0432\u043a\u0430", + "nur_heizgerat": "\u0422\u0456\u043b\u044c\u043a\u0438 \u0431\u043e\u0439\u043b\u0435\u0440", + "parallelbetrieb": "\u041f\u0430\u0440\u0430\u043b\u0435\u043b\u044c\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "partymodus": "\u0420\u0435\u0436\u0438\u043c \u0432\u0435\u0447\u0456\u0440\u043a\u0438", + "perm_cooling": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u0435 \u043e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "permanent": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u043e", + "permanentbetrieb": "\u041f\u043e\u0441\u0442\u0456\u0439\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "reduzierter_betrieb": "\u041e\u0431\u043c\u0435\u0436\u0435\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "rt_abschaltung": "RT \u0432\u0438\u043c\u0438\u043a\u0430\u043d\u043d\u044f", + "rt_frostschutz": "RT \u0437\u0430\u0445\u0438\u0441\u0442 \u0432\u0456\u0434 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u043d\u044f", + "ruhekontakt": "\u0420\u0435\u0448\u0442\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0456\u0432", + "schornsteinfeger": "\u0422\u0435\u0441\u0442 \u043d\u0430 \u0432\u0438\u043a\u0438\u0434\u0438", + "smart_grid": "\u0420\u043e\u0437\u0443\u043c\u043d\u0430 \u043c\u0435\u0440\u0435\u0436\u0430 \u0435\u043b\u0435\u043a\u0442\u0440\u043e\u043f\u043e\u0441\u0442\u0430\u0447\u0430\u043d\u043d\u044f", "smart_home": "\u0420\u043e\u0437\u0443\u043c\u043d\u0438\u0439 \u0434\u0456\u043c", + "softstart": "\u041c'\u044f\u043a\u0438\u0439 \u0441\u0442\u0430\u0440\u0442", + "solarbetrieb": "\u0421\u043e\u043d\u044f\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c", + "sparbetrieb": "\u0420\u0435\u0436\u0438\u043c \u0435\u043a\u043e\u043d\u043e\u043c\u0456\u0457", "sparen": "\u0415\u043a\u043e\u043d\u043e\u043c\u0456\u044f", + "spreizung_hoch": "dT \u0437\u0430\u043d\u0430\u0434\u0442\u043e \u0448\u0438\u0440\u043e\u043a\u0438\u0439", + "spreizung_kf": "\u0421\u043f\u0440\u0435\u0434 KF", "stabilisierung": "\u0421\u0442\u0430\u0431\u0456\u043b\u0456\u0437\u0430\u0446\u0456\u044f", "standby": "\u041e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u043d\u044f", - "start": "\u041f\u043e\u0447\u0430\u0442\u043e\u043a", + "start": "\u0417\u0430\u043f\u0443\u0441\u043a", "storung": "\u041f\u043e\u043c\u0438\u043b\u043a\u0430", - "taktsperre": "\u0410\u043d\u0442\u0438\u0446\u0438\u043a\u043b", - "test": "\u0422\u0435\u0441\u0442" + "taktsperre": "\u0410\u043d\u0442\u0438-\u0446\u0438\u043a\u043b", + "telefonfernschalter": "\u0414\u0438\u0441\u0442\u0430\u043d\u0446\u0456\u0439\u043d\u0435 \u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u0437 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443", + "test": "\u0422\u0435\u0441\u0442", + "tpw": "TPW", + "urlaubsmodus": "\u0420\u0435\u0436\u0438\u043c \"\u0432\u0438\u0445\u0456\u0434\u043d\u0456\"", + "ventilprufung": "\u0422\u0435\u0441\u0442 \u043a\u043b\u0430\u043f\u0430\u043d\u0430", + "vorspulen": "\u041f\u0440\u043e\u043c\u0438\u0432\u0430\u043d\u043d\u044f \u0432\u0445\u043e\u0434\u0443", + "warmwasser": "\u0413\u0412\u041f", + "warmwasser_schnellstart": "\u0428\u0432\u0438\u0434\u043a\u0438\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0413\u0412\u041f", + "warmwasserbetrieb": "\u0420\u0435\u0436\u0438\u043c \u0413\u0412\u041f", + "warmwassernachlauf": "\u0417\u0430\u043f\u0443\u0441\u043a \u0413\u0412\u041f", + "warmwasservorrang": "\u041f\u0440\u0456\u043e\u0440\u0438\u0442\u0435\u0442 \u0413\u0412\u041f", + "zunden": "\u0417\u0430\u043f\u0430\u043b\u044e\u0432\u0430\u043d\u043d\u044f" } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/tr.json b/homeassistant/components/wolflink/translations/tr.json new file mode 100644 index 00000000000000..6ed28a58c793c2 --- /dev/null +++ b/homeassistant/components/wolflink/translations/tr.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Beklenmeyen hata" + }, + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/uk.json b/homeassistant/components/wolflink/translations/uk.json index a7fbdfff913754..3fdf20a6aceb6e 100644 --- a/homeassistant/components/wolflink/translations/uk.json +++ b/homeassistant/components/wolflink/translations/uk.json @@ -1,17 +1,26 @@ { "config": { "abort": { - "already_configured": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0432\u0436\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u043e" + "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." }, "error": { "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.", "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" }, "step": { "device": { "data": { "device_name": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" - } + }, + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 WOLF" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" + }, + "title": "WOLF SmartSet" } } } diff --git a/homeassistant/components/xbox/translations/de.json b/homeassistant/components/xbox/translations/de.json index c67f3a49ea4378..04f32e05f8b34a 100644 --- a/homeassistant/components/xbox/translations/de.json +++ b/homeassistant/components/xbox/translations/de.json @@ -1,5 +1,10 @@ { "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, "create_entry": { "default": "Erfolgreich authentifiziert" }, diff --git a/homeassistant/components/xbox/translations/lb.json b/homeassistant/components/xbox/translations/lb.json index d305909389ff41..b83b6d0a4995fe 100644 --- a/homeassistant/components/xbox/translations/lb.json +++ b/homeassistant/components/xbox/translations/lb.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL.", "missing_configuration": "Komponent net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." + }, + "create_entry": { + "default": "Erfollegr\u00e4ich authentifiz\u00e9iert" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/tr.json b/homeassistant/components/xbox/translations/tr.json new file mode 100644 index 00000000000000..a152eb194683cb --- /dev/null +++ b/homeassistant/components/xbox/translations/tr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/uk.json b/homeassistant/components/xbox/translations/uk.json new file mode 100644 index 00000000000000..a1b3f8340fc889 --- /dev/null +++ b/homeassistant/components/xbox/translations/uk.json @@ -0,0 +1,17 @@ +{ + "config": { + "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." + }, + "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." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index f86868987a0696..6b0e25dfcd5206 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" + }, + "error": { + "discovery_error": "Es konnte kein Xiaomi Aqara Gateway gefunden werden, versuche die IP von Home Assistant als Interface zu nutzen", + "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse, schau unter https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_mac": "Ung\u00fcltige MAC-Adresse" + }, "flow_title": "Xiaomi Aqara Gateway: {name}", "step": { "select": { @@ -8,7 +17,11 @@ } }, "user": { - "description": "Stellen Sie eine Verbindung zu Ihrem Xiaomi Aqara Gateway her. Wenn die IP- und Mac-Adressen leer bleiben, wird die automatische Erkennung verwendet", + "data": { + "host": "IP-Adresse", + "mac": "MAC-Adresse" + }, + "description": "Stelle eine Verbindung zu deinem Xiaomi Aqara Gateway her. Wenn die IP- und MAC-Adressen leer bleiben, wird die automatische Erkennung verwendet", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/tr.json b/homeassistant/components/xiaomi_aqara/translations/tr.json index 10d1374187e594..24da29417d14dc 100644 --- a/homeassistant/components/xiaomi_aqara/translations/tr.json +++ b/homeassistant/components/xiaomi_aqara/translations/tr.json @@ -1,7 +1,38 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "not_xiaomi_aqara": "Xiaomi Aqara A\u011f Ge\u00e7idi de\u011fil, ke\u015ffedilen cihaz bilinen a\u011f ge\u00e7itleriyle e\u015fle\u015fmedi" + }, "error": { + "discovery_error": "Bir Xiaomi Aqara A\u011f Ge\u00e7idi ke\u015ffedilemedi, HomeAssistant'\u0131 aray\u00fcz olarak \u00e7al\u0131\u015ft\u0131ran cihaz\u0131n IP'sini kullanmay\u0131 deneyin", + "invalid_interface": "Ge\u00e7ersiz a\u011f aray\u00fcz\u00fc", + "invalid_key": "Ge\u00e7ersiz a\u011f ge\u00e7idi anahtar\u0131", "invalid_mac": "Ge\u00e7ersiz Mac Adresi" + }, + "flow_title": "Xiaomi Aqara A\u011f Ge\u00e7idi: {name}", + "step": { + "select": { + "data": { + "select_ip": "\u0130p Adresi" + }, + "description": "Ek a\u011f ge\u00e7itlerini ba\u011flamak istiyorsan\u0131z kurulumu tekrar \u00e7al\u0131\u015ft\u0131r\u0131n.", + "title": "Ba\u011flamak istedi\u011finiz Xiaomi Aqara A\u011f Ge\u00e7idini se\u00e7in" + }, + "settings": { + "data": { + "key": "A\u011f ge\u00e7idinizin anahtar\u0131", + "name": "A\u011f Ge\u00e7idinin Ad\u0131" + }, + "description": "Anahtar (parola) bu \u00f6\u011fretici kullan\u0131larak al\u0131nabilir: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Anahtar sa\u011flanmazsa, yaln\u0131zca sens\u00f6rlere eri\u015filebilir" + }, + "user": { + "data": { + "host": "\u0130p Adresi (iste\u011fe ba\u011fl\u0131)", + "mac": "Mac Adresi (iste\u011fe ba\u011fl\u0131)" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/uk.json b/homeassistant/components/xiaomi_aqara/translations/uk.json new file mode 100644 index 00000000000000..1598e96b38eecc --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/uk.json @@ -0,0 +1,43 @@ +{ + "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_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.", + "not_xiaomi_aqara": "\u0426\u0435 \u043d\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara. \u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u0430\u0454 \u0432\u0456\u0434\u043e\u043c\u0438\u043c \u0448\u043b\u044e\u0437\u0456\u0432." + }, + "error": { + "discovery_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0432\u0438\u044f\u0432\u0438\u0442\u0438 \u0448\u043b\u044e\u0437 Xiaomi Aqara, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u0442\u0438 IP-\u0430\u0434\u0440\u0435\u0441\u0443 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e \u0437 HomeAssistant \u0432 \u044f\u043a\u043e\u0441\u0442\u0456 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443.", + "invalid_host": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u0435 \u0456\u043c'\u044f \u0430\u0431\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430. . \u0421\u043f\u043e\u0441\u043e\u0431\u0438 \u0432\u0438\u0440\u0456\u0448\u0435\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0456 \u0442\u0443\u0442: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem.", + "invalid_interface": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.", + "invalid_key": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 \u0448\u043b\u044e\u0437\u0443.", + "invalid_mac": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 MAC-\u0430\u0434\u0440\u0435\u0441\u0430." + }, + "flow_title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara: {name}", + "step": { + "select": { + "data": { + "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441\u0430" + }, + "description": "\u041f\u043e\u0447\u043d\u0456\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u044e\u0432\u0430\u043d\u043d\u044f \u0437\u043d\u043e\u0432\u0443, \u044f\u043a\u0449\u043e \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0434\u043e\u0434\u0430\u0442\u0438 \u0456\u043d\u0448\u0438\u0439 \u0448\u043b\u044e\u0437", + "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0448\u043b\u044e\u0437 Xiaomi Aqara" + }, + "settings": { + "data": { + "key": "\u041a\u043b\u044e\u0447", + "name": "\u041d\u0430\u0437\u0432\u0430" + }, + "description": "\u041a\u043b\u044e\u0447 (\u043f\u0430\u0440\u043e\u043b\u044c) \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e \u0446\u0456\u0454\u0457 \u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. \u042f\u043a\u0449\u043e \u043a\u043b\u044e\u0447 \u043d\u0435 \u0432\u043a\u0430\u0437\u0430\u043d\u043e, \u0431\u0443\u0434\u0443\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0456 \u0442\u0456\u043b\u044c\u043a\u0438 \u0434\u0430\u0442\u0447\u0438\u043a\u0438.", + "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "interface": "\u041c\u0435\u0440\u0435\u0436\u0435\u0432\u0438\u0439 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441", + "mac": "MAC-\u0430\u0434\u0440\u0435\u0441\u0430 (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)" + }, + "description": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 \u0437\u0456 \u0448\u043b\u044e\u0437\u043e\u043c Xiaomi Aqara. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0433\u043e \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u0448\u043b\u044e\u0437\u0443, \u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u043b\u044f IP \u0456 MAC-\u0430\u0434\u0440\u0435\u0441\u0438 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c\u0438.", + "title": "\u0428\u043b\u044e\u0437 Xiaomi Aqara" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index 0b5f593ffcde70..d56a81e14d4582 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -2,27 +2,28 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf f\u00fcr dieses Xiaomi Miio-Ger\u00e4t wird bereits ausgef\u00fchrt." + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" }, "error": { - "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hlen Sie ein Ger\u00e4t aus." + "cannot_connect": "Verbindung fehlgeschlagen", + "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus." }, "flow_title": "Xiaomi Miio: {name}", "step": { "gateway": { "data": { - "host": "IP Adresse", + "host": "IP-Adresse", "name": "Name des Gateways", "token": "API-Token" }, - "description": "Sie ben\u00f6tigen den 32 Zeichen langen API-Token. Anweisungen finden Sie unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", - "title": "Stellen Sie eine Verbindung zu einem Xiaomi Gateway her" + "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token. Anweisungen findest du unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "title": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, "user": { "data": { - "gateway": "Stellen Sie eine Verbindung zu einem Xiaomi Gateway her" + "gateway": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, - "description": "W\u00e4hlen Sie aus, mit welchem Ger\u00e4t Sie eine Verbindung herstellen m\u00f6chten.", + "description": "W\u00e4hle aus, mit welchem Ger\u00e4t du eine Verbindung herstellen m\u00f6chtest.", "title": "Xiaomi Miio" } } diff --git a/homeassistant/components/xiaomi_miio/translations/tr.json b/homeassistant/components/xiaomi_miio/translations/tr.json new file mode 100644 index 00000000000000..46a6493ab3a8d2 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/tr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131", + "no_device_selected": "Cihaz se\u00e7ilmedi, l\u00fctfen bir cihaz se\u00e7in." + }, + "step": { + "gateway": { + "data": { + "host": "\u0130p Adresi", + "name": "A\u011f Ge\u00e7idinin Ad\u0131", + "token": "API Belirteci" + }, + "title": "Bir Xiaomi A\u011f Ge\u00e7idine ba\u011flan\u0131n" + }, + "user": { + "data": { + "gateway": "Bir Xiaomi A\u011f Ge\u00e7idine ba\u011flan\u0131n" + }, + "description": "Hangi cihaza ba\u011flanmak istedi\u011finizi se\u00e7in.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/uk.json b/homeassistant/components/xiaomi_miio/translations/uk.json new file mode 100644 index 00000000000000..f32105589f6a2e --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/uk.json @@ -0,0 +1,31 @@ +{ + "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_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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "no_device_selected": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u0438\u043d \u0437 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432." + }, + "flow_title": "Xiaomi Miio: {name}", + "step": { + "gateway": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430", + "token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u0438\u0439 \u0422\u043e\u043a\u0435\u043d API . \u041f\u0440\u043e \u0442\u0435, \u044f\u043a \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0442\u043e\u043a\u0435\u043d, \u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0442\u0443\u0442:\nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u0417\u0432\u0435\u0440\u043d\u0456\u0442\u044c \u0443\u0432\u0430\u0433\u0443, \u0449\u043e \u0446\u0435\u0439 \u0442\u043e\u043a\u0435\u043d \u0432\u0456\u0434\u0440\u0456\u0437\u043d\u044f\u0454\u0442\u044c\u0441\u044f \u0432\u0456\u0434 \u043a\u043b\u044e\u0447\u0430, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Xiaomi Aqara.", + "title": "\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0448\u043b\u044e\u0437\u0443 Xiaomi" + }, + "user": { + "data": { + "gateway": "\u0428\u043b\u044e\u0437 Xiaomi" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u0440\u0438\u0441\u0442\u0440\u0456\u0439, \u044f\u043a\u0438\u0439 \u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/de.json b/homeassistant/components/yeelight/translations/de.json index 90c154f882bf04..6eaff2e87a3bb4 100644 --- a/homeassistant/components/yeelight/translations/de.json +++ b/homeassistant/components/yeelight/translations/de.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, "step": { "pick_device": { "data": { @@ -9,7 +16,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Wenn du den Host leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden." } } }, diff --git a/homeassistant/components/yeelight/translations/tr.json b/homeassistant/components/yeelight/translations/tr.json new file mode 100644 index 00000000000000..322f13f47b04d5 --- /dev/null +++ b/homeassistant/components/yeelight/translations/tr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "pick_device": { + "data": { + "device": "Cihaz" + } + }, + "user": { + "data": { + "host": "Ana Bilgisayar" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Model (Opsiyonel)", + "save_on_change": "De\u011fi\u015fiklikte Durumu Kaydet", + "transition": "Ge\u00e7i\u015f S\u00fcresi (ms)", + "use_music_mode": "M\u00fczik Modunu Etkinle\u015ftir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/uk.json b/homeassistant/components/yeelight/translations/uk.json new file mode 100644 index 00000000000000..0a173ccb6e4a4c --- /dev/null +++ b/homeassistant/components/yeelight/translations/uk.json @@ -0,0 +1,38 @@ +{ + "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.", + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "pick_device": { + "data": { + "device": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439" + } + }, + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442" + }, + "description": "\u042f\u043a\u0449\u043e \u043d\u0435 \u0432\u043a\u0430\u0437\u0443\u0432\u0430\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0443 \u0445\u043e\u0441\u0442\u0430, \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457 \u0431\u0443\u0434\u0443\u0442\u044c \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u0456 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "\u041c\u043e\u0434\u0435\u043b\u044c (\u043d\u0435\u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u043e)", + "nightlight_switch": "\u041f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447 \u0434\u043b\u044f \u043d\u0456\u0447\u043d\u0438\u043a\u0430", + "save_on_change": "\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u0442\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u0438 \u0437\u043c\u0456\u043d\u0456", + "transition": "\u0427\u0430\u0441 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0443 (\u0432 \u043c\u0456\u043b\u0456\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "use_music_mode": "\u041c\u0443\u0437\u0438\u0447\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c" + }, + "description": "\u042f\u043a\u0449\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u0432\u0438\u0431\u0440\u0430\u043d\u043e, \u0432\u043e\u043d\u0430 \u0431\u0443\u0434\u0435 \u0432\u0438\u0437\u043d\u0430\u0447\u0435\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/tr.json b/homeassistant/components/zerproc/translations/tr.json index 49fa9545e94d2c..3df15466f030f1 100644 --- a/homeassistant/components/zerproc/translations/tr.json +++ b/homeassistant/components/zerproc/translations/tr.json @@ -1,7 +1,13 @@ { "config": { "abort": { - "no_devices_found": "A\u011fda cihaz bulunamad\u0131" + "no_devices_found": "A\u011fda cihaz bulunamad\u0131", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "step": { + "confirm": { + "description": "Kuruluma ba\u015flamak ister misiniz?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/uk.json b/homeassistant/components/zerproc/translations/uk.json new file mode 100644 index 00000000000000..292861e9129dbd --- /dev/null +++ b/homeassistant/components/zerproc/translations/uk.json @@ -0,0 +1,13 @@ +{ + "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." + }, + "step": { + "confirm": { + "description": "\u0425\u043e\u0447\u0435\u0442\u0435 \u043f\u043e\u0447\u0430\u0442\u0438 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/cs.json b/homeassistant/components/zha/translations/cs.json index 1ac4c7c2d6136b..cedf56d73c4d22 100644 --- a/homeassistant/components/zha/translations/cs.json +++ b/homeassistant/components/zha/translations/cs.json @@ -51,20 +51,20 @@ "device_rotated": "Za\u0159\u00edzen\u00ed oto\u010deno \"{subtype}\"", "device_shaken": "Za\u0159\u00edzen\u00ed se zat\u0159\u00e1slo", "device_tilted": "Za\u0159\u00edzen\u00ed naklon\u011bno", - "remote_button_alt_double_press": "Dvakr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", + "remote_button_alt_double_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dvakr\u00e1t (alternativn\u00ed re\u017eim)", "remote_button_alt_long_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" po dlouh\u00e9m stisku (alternativn\u00ed re\u017eim)", - "remote_button_alt_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_alt_quintuple_press": "P\u011btkr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_alt_short_press": "Stiknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", + "remote_button_alt_quadruple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t (alternativn\u00ed re\u017eim)", + "remote_button_alt_quintuple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto p\u011btkr\u00e1t (alternativn\u00ed re\u017eim)", + "remote_button_alt_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto (alternativn\u00ed re\u017eim)", "remote_button_alt_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_alt_triple_press": "T\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\" (alternativn\u00ed re\u017eim)", - "remote_button_double_press": "Dvakr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_alt_triple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto t\u0159ikr\u00e1t (alternativn\u00ed re\u017eim)", + "remote_button_double_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto dvakr\u00e1t", "remote_button_long_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\" po dlouh\u00e9m stisku", - "remote_button_quadruple_press": "\u010cty\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", - "remote_button_quintuple_press": "P\u011btkr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"", - "remote_button_short_press": "Stiknuto tla\u010d\u00edtko \"{subtype}\"", + "remote_button_quadruple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto \u010dty\u0159ikr\u00e1t", + "remote_button_quintuple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto p\u011btkr\u00e1t", + "remote_button_short_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto", "remote_button_short_release": "Uvoln\u011bno tla\u010d\u00edtko \"{subtype}\"", - "remote_button_triple_press": "T\u0159ikr\u00e1t stisknuto tla\u010d\u00edtko \"{subtype}\"" + "remote_button_triple_press": "Tla\u010d\u00edtko \"{subtype}\" stisknuto t\u0159ikr\u00e1t" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/de.json b/homeassistant/components/zha/translations/de.json index 592450fcfbc40b..61e9b8e37ba322 100644 --- a/homeassistant/components/zha/translations/de.json +++ b/homeassistant/components/zha/translations/de.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von ZHA zul\u00e4ssig." + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { - "cannot_connect": "Kein Verbindung zu ZHA-Ger\u00e4t m\u00f6glich" + "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/pl.json b/homeassistant/components/zha/translations/pl.json index ab4402558ba9ca..4cdada49f5013b 100644 --- a/homeassistant/components/zha/translations/pl.json +++ b/homeassistant/components/zha/translations/pl.json @@ -70,22 +70,22 @@ "device_shaken": "nast\u0105pi potrz\u0105\u015bni\u0119cie urz\u0105dzeniem", "device_slid": "nast\u0105pi przesuni\u0119cie urz\u0105dzenia \"{subtype}\"", "device_tilted": "nast\u0105pi przechylenie urz\u0105dzenia", - "remote_button_alt_double_press": "\"{subtype}\" dwukrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_long_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y (tryb alternatywny)", - "remote_button_alt_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu (tryb alternatywny)", - "remote_button_alt_quadruple_press": "\"{subtype}\" czterokrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_quintuple_press": "\"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_alt_short_release": "\"{subtype}\" zostanie zwolniony (tryb alternatywny)", - "remote_button_alt_triple_press": "\"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty (tryb alternatywny)", - "remote_button_double_press": "\"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", - "remote_button_long_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", - "remote_button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", - "remote_button_quadruple_press": "\"{subtype}\" czterokrotnie naci\u015bni\u0119ty", - "remote_button_quintuple_press": "\"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", - "remote_button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", - "remote_button_short_release": "\"{subtype}\" zostanie zwolniony", - "remote_button_triple_press": "\"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" + "remote_button_alt_double_press": "przycisk \"{subtype}\" zostanie dwukrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y (tryb alternatywny)", + "remote_button_alt_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu (tryb alternatywny)", + "remote_button_alt_quadruple_press": "przycisk \"{subtype}\" zostanie czterokrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_quintuple_press": "przycisk \"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_alt_short_release": "przycisk \"{subtype}\" zostanie zwolniony (tryb alternatywny)", + "remote_button_alt_triple_press": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty (tryb alternatywny)", + "remote_button_double_press": "przycisk \"{subtype}\" zostanie podw\u00f3jnie naci\u015bni\u0119ty", + "remote_button_long_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty w spos\u00f3b ci\u0105g\u0142y", + "remote_button_long_release": "przycisk \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "remote_button_quadruple_press": "przycisk \"{subtype}\" czterokrotnie naci\u015bni\u0119ty", + "remote_button_quintuple_press": "przycisk \"{subtype}\" zostanie pi\u0119ciokrotnie naci\u015bni\u0119ty", + "remote_button_short_press": "przycisk \"{subtype}\" zostanie naci\u015bni\u0119ty", + "remote_button_short_release": "przycisk \"{subtype}\" zostanie zwolniony", + "remote_button_triple_press": "przycisk \"{subtype}\" zostanie trzykrotnie naci\u015bni\u0119ty" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/tr.json b/homeassistant/components/zha/translations/tr.json new file mode 100644 index 00000000000000..a74f56a2f4e6be --- /dev/null +++ b/homeassistant/components/zha/translations/tr.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, + "error": { + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "step": { + "pick_radio": { + "title": "Radyo Tipi" + }, + "port_config": { + "data": { + "path": "Seri cihaz yolu" + }, + "title": "Ayarlar" + } + } + }, + "device_automation": { + "trigger_type": { + "device_offline": "Cihaz \u00e7evrimd\u0131\u015f\u0131" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/uk.json b/homeassistant/components/zha/translations/uk.json new file mode 100644 index 00000000000000..7bd62cf26e1dca --- /dev/null +++ b/homeassistant/components/zha/translations/uk.json @@ -0,0 +1,91 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f" + }, + "step": { + "pick_radio": { + "data": { + "radio_type": "\u0422\u0438\u043f \u0440\u0430\u0434\u0456\u043e\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0438\u043f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e Zigbee", + "title": "\u0422\u0438\u043f \u0440\u0430\u0434\u0456\u043e\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "port_config": { + "data": { + "baudrate": "\u0448\u0432\u0438\u0434\u043a\u0456\u0441\u0442\u044c \u043f\u043e\u0440\u0442\u0443", + "flow_control": "\u043a\u0435\u0440\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0442\u043e\u043a\u043e\u043c \u0434\u0430\u043d\u0438\u0445", + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u0412\u043a\u0430\u0436\u0456\u0442\u044c \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043f\u043e\u0440\u0442\u0443", + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438" + }, + "user": { + "data": { + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043f\u043e\u0441\u043b\u0456\u0434\u043e\u0432\u043d\u0438\u0439 \u043f\u043e\u0440\u0442 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0430 \u043c\u0435\u0440\u0435\u0436\u0456 Zigbee", + "title": "Zigbee Home Automation" + } + } + }, + "device_automation": { + "action_type": { + "squawk": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u043d\u0434\u0435\u0440", + "warn": "\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043d\u044f \u043e\u043f\u043e\u0432\u0456\u0449\u0435\u043d\u043d\u044f" + }, + "trigger_subtype": { + "both_buttons": "\u041e\u0431\u0438\u0434\u0432\u0456 \u043a\u043d\u043e\u043f\u043a\u0438", + "button_1": "\u041f\u0435\u0440\u0448\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_2": "\u0414\u0440\u0443\u0433\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_3": "\u0422\u0440\u0435\u0442\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "button_4": "\u0427\u0435\u0442\u0432\u0435\u0440\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_5": "\u041f'\u044f\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "button_6": "\u0428\u043e\u0441\u0442\u0430 \u043a\u043d\u043e\u043f\u043a\u0430", + "close": "\u0417\u0430\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "dim_down": "\u0417\u043c\u0435\u043d\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "dim_up": "\u0417\u0431\u0456\u043b\u044c\u0448\u0438\u0442\u0438 \u044f\u0441\u043a\u0440\u0430\u0432\u0456\u0441\u0442\u044c", + "face_1": "\u041d\u0430 \u043f\u0435\u0440\u0448\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_2": "\u041d\u0430 \u0434\u0440\u0443\u0433\u0438\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_3": "\u041d\u0430 \u0442\u0440\u0435\u0442\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_4": "\u041d\u0430 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_5": "\u041d\u0430 \u043f'\u044f\u0442\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_6": "\u041d\u0430 \u0448\u043e\u0441\u0442\u0438\u0439 \u0433\u0440\u0430\u043d\u0456", + "face_any": "\u041d\u0430 \u0431\u0443\u0434\u044c-\u044f\u043a\u0456\u0439 \u0433\u0440\u0430\u043d\u0456", + "left": "\u041b\u0456\u0432\u043e\u0440\u0443\u0447", + "open": "\u0412\u0456\u0434\u043a\u0440\u0438\u0432\u0430\u0454\u0442\u044c\u0441\u044f", + "right": "\u041f\u0440\u0430\u0432\u043e\u0440\u0443\u0447", + "turn_off": "\u0412\u0438\u043c\u043a\u043d\u0443\u0442\u0438", + "turn_on": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438" + }, + "trigger_type": { + "device_dropped": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0441\u043a\u0438\u043d\u0443\u043b\u0438", + "device_flipped": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u043b\u0438 {subtype}", + "device_knocked": "\u041f\u0440\u0438\u0441\u0442\u0440\u043e\u0454\u043c \u043f\u043e\u0441\u0442\u0443\u043a\u0430\u043b\u0438 {subtype}", + "device_offline": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0435 \u0432 \u043c\u0435\u0440\u0435\u0436\u0456", + "device_rotated": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u043b\u0438 {subtype}", + "device_shaken": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043f\u043e\u0442\u0440\u044f\u0441\u043b\u0438", + "device_slid": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u0437\u0440\u0443\u0448\u0438\u043b\u0438 {subtype}", + "device_tilted": "\u041f\u0440\u0438\u0441\u0442\u0440\u0456\u0439 \u043d\u0430\u0445\u0438\u043b\u0438\u043b\u0438", + "remote_button_alt_double_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_long_press": "{subtype} \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_quadruple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_quintuple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_short_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_alt_triple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438 (\u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c)", + "remote_button_double_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0434\u0432\u0430 \u0440\u0430\u0437\u0438", + "remote_button_long_press": "{subtype} \u0434\u043e\u0432\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_long_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u0434\u043e\u0432\u0433\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_quadruple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0447\u043e\u0442\u0438\u0440\u0438 \u0440\u0430\u0437\u0438", + "remote_button_quintuple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u043f'\u044f\u0442\u044c \u0440\u0430\u0437\u0456\u0432", + "remote_button_short_press": "\u041a\u043d\u043e\u043f\u043a\u0430 \"{subtype}\" \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430", + "remote_button_short_release": "{subtype} \u0432\u0456\u0434\u043f\u0443\u0449\u0435\u043d\u0430 \u043f\u0456\u0441\u043b\u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0433\u043e \u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u043d\u044f", + "remote_button_triple_press": "{subtype} \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0430 \u0442\u0440\u0438 \u0440\u0430\u0437\u0438" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.tr.json b/homeassistant/components/zodiac/translations/sensor.tr.json new file mode 100644 index 00000000000000..f9e0357799d9b3 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.tr.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Kova", + "aries": "Ko\u00e7", + "cancer": "Yenge\u00e7", + "capricorn": "O\u011flak", + "gemini": "Ikizler", + "leo": "Aslan", + "libra": "Terazi", + "pisces": "Bal\u0131k", + "sagittarius": "Yay", + "scorpio": "Akrep", + "taurus": "Bo\u011fa", + "virgo": "Ba\u015fak" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.uk.json b/homeassistant/components/zodiac/translations/sensor.uk.json new file mode 100644 index 00000000000000..e0c891a8b23d67 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.uk.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "\u0412\u043e\u0434\u043e\u043b\u0456\u0439", + "aries": "\u041e\u0432\u0435\u043d", + "cancer": "\u0420\u0430\u043a", + "capricorn": "\u041a\u043e\u0437\u0435\u0440\u0456\u0433", + "gemini": "\u0411\u043b\u0438\u0437\u043d\u044e\u043a\u0438", + "leo": "\u041b\u0435\u0432", + "libra": "\u0422\u0435\u0440\u0435\u0437\u0438", + "pisces": "\u0420\u0438\u0431\u0438", + "sagittarius": "\u0421\u0442\u0440\u0456\u043b\u0435\u0446\u044c", + "scorpio": "\u0421\u043a\u043e\u0440\u043f\u0456\u043e\u043d", + "taurus": "\u0422\u0435\u043b\u0435\u0446\u044c", + "virgo": "\u0414\u0456\u0432\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zone/translations/tr.json b/homeassistant/components/zone/translations/tr.json new file mode 100644 index 00000000000000..dad65ac92a7c11 --- /dev/null +++ b/homeassistant/components/zone/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "init": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/de.json b/homeassistant/components/zoneminder/translations/de.json index 1362dcbd62dba3..5fa5d0a52345b1 100644 --- a/homeassistant/components/zoneminder/translations/de.json +++ b/homeassistant/components/zoneminder/translations/de.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, "flow_title": "ZoneMinder", "step": { "user": { "data": { "password": "Passwort", + "ssl": "Nutzt ein SSL-Zertifikat", "username": "Benutzername", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } diff --git a/homeassistant/components/zoneminder/translations/tr.json b/homeassistant/components/zoneminder/translations/tr.json new file mode 100644 index 00000000000000..971f8cc9bd758c --- /dev/null +++ b/homeassistant/components/zoneminder/translations/tr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "auth_fail": "Kullan\u0131c\u0131 ad\u0131 veya \u015fifre yanl\u0131\u015f.", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" + }, + "error": { + "auth_fail": "Kullan\u0131c\u0131 ad\u0131 veya \u015fifre yanl\u0131\u015f.", + "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/zoneminder/translations/uk.json b/homeassistant/components/zoneminder/translations/uk.json new file mode 100644 index 00000000000000..e5b04ae124f5e1 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/uk.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d \u0430\u0431\u043e \u043f\u0430\u0440\u043e\u043b\u044c.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 ZoneMinder.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "create_entry": { + "default": "\u0414\u043e\u0434\u0430\u043d\u043e \u0441\u0435\u0440\u0432\u0435\u0440 ZoneMinder." + }, + "error": { + "auth_fail": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043b\u043e\u0433\u0456\u043d \u0430\u0431\u043e \u043f\u0430\u0440\u043e\u043b\u044c.", + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "connection_error": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0434\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 ZoneMinder.", + "invalid_auth": "\u041d\u0435\u0432\u0456\u0440\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u044f." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442 \u0456 \u043f\u043e\u0440\u0442 (\u043d\u0430\u043f\u0440\u0438\u043a\u043b\u0430\u0434: 10.10.0.4:8010)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "path": "\u0428\u043b\u044f\u0445 \u0434\u043e ZM", + "path_zms": "\u0428\u043b\u044f\u0445 \u0434\u043e ZMS", + "ssl": "\u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442 SSL", + "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430", + "verify_ssl": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0441\u0435\u0440\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u0430 SSL" + }, + "title": "ZoneMinder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/de.json b/homeassistant/components/zwave/translations/de.json index 60b5aa88024605..f592c2243aca53 100644 --- a/homeassistant/components/zwave/translations/de.json +++ b/homeassistant/components/zwave/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Z-Wave ist bereits konfiguriert" + "already_configured": "Z-Wave ist bereits konfiguriert", + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "error": { "option_error": "Z-Wave-Validierung fehlgeschlagen. Ist der Pfad zum USB-Stick korrekt?" diff --git a/homeassistant/components/zwave/translations/tr.json b/homeassistant/components/zwave/translations/tr.json index 3938868d2808c1..383ccc6cc4f805 100644 --- a/homeassistant/components/zwave/translations/tr.json +++ b/homeassistant/components/zwave/translations/tr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/zwave/translations/uk.json b/homeassistant/components/zwave/translations/uk.json index d00986cae5807f..5cdd6060cc4b3f 100644 --- a/homeassistant/components/zwave/translations/uk.json +++ b/homeassistant/components/zwave/translations/uk.json @@ -1,14 +1,33 @@ { + "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." + }, + "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." + }, + "step": { + "user": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u043c\u0435\u0440\u0435\u0436\u0456 (\u0437\u0430\u043b\u0438\u0448\u0442\u0435 \u043f\u043e\u0440\u043e\u0436\u043d\u0456\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438)", + "usb_path": "\u0428\u043b\u044f\u0445 \u0434\u043e USB-\u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e" + }, + "description": "\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044f \u0437 [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u044f\u043c\u0438](https://www.home-assistant.io/docs/z-wave/installation/) \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 \u043f\u0440\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", + "title": "Z-Wave" + } + } + }, "state": { "_": { - "dead": "\u041d\u0435\u0440\u043e\u0431\u043e\u0447\u0430", + "dead": "\u041d\u0435\u0441\u043f\u0440\u0430\u0432\u043d\u0438\u0439", "initializing": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f", "ready": "\u0413\u043e\u0442\u043e\u0432\u0438\u0439", - "sleeping": "\u0421\u043f\u043b\u044f\u0447\u043a\u0430" + "sleeping": "\u0420\u0435\u0436\u0438\u043c \u0441\u043d\u0443" }, "query_stage": { - "dead": "\u041d\u0435\u0440\u043e\u0431\u043e\u0447\u0430 ({query_stage})", - "initializing": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f ( {query_stage} )" + "dead": "\u041d\u0435\u0441\u043f\u0440\u0430\u0432\u043d\u0438\u0439", + "initializing": "\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json new file mode 100644 index 00000000000000..93ec53a644e0fa --- /dev/null +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "No s'ha pogut obtenir la informaci\u00f3 de descobriment del complement Z-Wave JS.", + "addon_info_failed": "No s'ha pogut obtenir la informaci\u00f3 del complement Z-Wave JS.", + "addon_install_failed": "No s'ha pogut instal\u00b7lar el complement Z-Wave JS.", + "addon_missing_discovery_info": "Falta la informaci\u00f3 de descobriment del complement Z-Wave JS.", + "addon_set_config_failed": "No s'ha pogut establir la configuraci\u00f3 de Z-Wave JS.", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "error": { + "addon_start_failed": "No s'ha pogut iniciar el complement Z-Wave JS. Comprova la configuraci\u00f3.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_ws_url": "URL del websocket inv\u00e0lid", + "unknown": "Error inesperat" + }, + "progress": { + "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts." + }, + "step": { + "hassio_confirm": { + "title": "Configura la integraci\u00f3 Z-Wave JS mitjan\u00e7ant el complement Z-Wave JS" + }, + "install_addon": { + "title": "Ha comen\u00e7at la instal\u00b7laci\u00f3 del complement Z-Wave JS" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Utilitza el complement Z-Wave JS Supervisor" + }, + "description": "Vols utilitzar el complement Supervisor de Z-Wave JS?", + "title": "Selecciona el m\u00e8tode de connexi\u00f3" + }, + "start_addon": { + "data": { + "network_key": "Clau de xarxa", + "usb_path": "Ruta del port USB del dispositiu" + }, + "title": "Introdueix la configuraci\u00f3 del complement Z-Wave JS" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/cs.json b/homeassistant/components/zwave_js/translations/cs.json new file mode 100644 index 00000000000000..96073b579ed688 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/cs.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "manual": { + "data": { + "url": "URL" + } + }, + "start_addon": { + "data": { + "usb_path": "Cesta k USB za\u0159\u00edzen\u00ed" + } + }, + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json new file mode 100644 index 00000000000000..d4903bc8c6dae4 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 4aa510df6bea12..977651a576bc9b 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -44,6 +44,11 @@ "usb_path": "USB Device Path" }, "title": "Enter the Z-Wave JS add-on configuration" + }, + "user": { + "data": { + "url": "URL" + } } } }, diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json new file mode 100644 index 00000000000000..e5ee009c0d1014 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/es.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", + "cannot_connect": "No se pudo conectar" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_ws_url": "URL de websocket no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "title": "Selecciona el m\u00e9todo de conexi\u00f3n" + }, + "start_addon": { + "data": { + "network_key": "Clave de red", + "usb_path": "Ruta del dispositivo USB" + } + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json new file mode 100644 index 00000000000000..7a7aadfb84152e --- /dev/null +++ b/homeassistant/components/zwave_js/translations/et.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Z-Wave JS lisandmooduli tuvastusteabe hankimine nurjus.", + "addon_info_failed": "Z-Wave JS lisandmooduli teabe hankimine nurjus.", + "addon_install_failed": "Z-Wave JS lisandmooduli paigaldamine nurjus.", + "addon_missing_discovery_info": "Z-Wave JS lisandmooduli tuvastusteave puudub.", + "addon_set_config_failed": "Z-Wave JS konfiguratsiooni m\u00e4\u00e4ramine nurjus.", + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", + "cannot_connect": "\u00dchendamine nurjus" + }, + "error": { + "addon_start_failed": "Z-Wave JS lisandmooduli k\u00e4ivitamine nurjus. Kontrolli seadistusi.", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_ws_url": "Vale sihtkoha aadress", + "unknown": "Ootamatu t\u00f5rge" + }, + "progress": { + "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit." + }, + "step": { + "hassio_confirm": { + "title": "Seadista Z-Wave JS-i sidumine Z-Wave JS-i lisandmooduliga" + }, + "install_addon": { + "title": "Z-Wave JS lisandmooduli paigaldamine on alanud" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Kasuta lisandmoodulit Z-Wave JS Supervisor" + }, + "description": "Kas soovid kasutada Z-Wave JSi halduri lisandmoodulit?", + "title": "Vali \u00fchendusviis" + }, + "start_addon": { + "data": { + "network_key": "V\u00f5rgu v\u00f5ti", + "usb_path": "USB-seadme asukoha rada" + }, + "title": "Sisesta Z-Wave JS lisandmooduli seaded" + }, + "user": { + "data": { + "url": "" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json new file mode 100644 index 00000000000000..f3a9aff1a29bda --- /dev/null +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Erreur de connection", + "invalid_ws_url": "URL websocket invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json new file mode 100644 index 00000000000000..fc76b309a34ebc --- /dev/null +++ b/homeassistant/components/zwave_js/translations/it.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Impossibile ottenere le informazioni sul rilevamento del componente aggiuntivo Z-Wave JS.", + "addon_info_failed": "Impossibile ottenere le informazioni sul componente aggiuntivo Z-Wave JS.", + "addon_install_failed": "Impossibile installare il componente aggiuntivo Z-Wave JS.", + "addon_missing_discovery_info": "Informazioni sul rilevamento del componente aggiuntivo Z-Wave JS mancanti.", + "addon_set_config_failed": "Impossibile impostare la configurazione di Z-Wave JS.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "cannot_connect": "Impossibile connettersi" + }, + "error": { + "addon_start_failed": "Impossibile avviare il componente aggiuntivo Z-Wave JS. Controlla la configurazione.", + "cannot_connect": "Impossibile connettersi", + "invalid_ws_url": "URL websocket non valido", + "unknown": "Errore imprevisto" + }, + "progress": { + "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti." + }, + "step": { + "hassio_confirm": { + "title": "Configura l'integrazione di Z-Wave JS con il componente aggiuntivo Z-Wave JS" + }, + "install_addon": { + "title": "L'installazione del componente aggiuntivo Z-Wave JS \u00e8 iniziata" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Usa il componente aggiuntivo Z-Wave JS Supervisor" + }, + "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS Supervisor?", + "title": "Seleziona il metodo di connessione" + }, + "start_addon": { + "data": { + "network_key": "Chiave di rete", + "usb_path": "Percorso del dispositivo USB" + }, + "title": "Accedi alla configurazione del componente aggiuntivo Z-Wave JS" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/lb.json b/homeassistant/components/zwave_js/translations/lb.json new file mode 100644 index 00000000000000..302addbd7cf958 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/lb.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_ws_url": "Ong\u00eblteg Websocket URL", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json new file mode 100644 index 00000000000000..e16425b59ec898 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/no.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Kunne ikke hente oppdagelsesinformasjon om Z-Wave JS-tillegg", + "addon_info_failed": "Kunne ikke hente informasjon om Z-Wave JS-tillegg", + "addon_install_failed": "Kunne ikke installere Z-Wave JS-tillegg", + "addon_missing_discovery_info": "Manglende oppdagelsesinformasjon for Z-Wave JS-tillegg", + "addon_set_config_failed": "Kunne ikke angi Z-Wave JS-konfigurasjon", + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "cannot_connect": "Tilkobling mislyktes" + }, + "error": { + "addon_start_failed": "Kunne ikke starte Z-Wave JS-tillegg. Sjekk konfigurasjonen.", + "cannot_connect": "Tilkobling mislyktes", + "invalid_ws_url": "Ugyldig websocket URL", + "unknown": "Uventet feil" + }, + "progress": { + "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter." + }, + "step": { + "hassio_confirm": { + "title": "Sett opp Z-Wave JS-integrasjon med Z-Wave JS-tillegg" + }, + "install_addon": { + "title": "Installasjon av Z-Wave JS-tillegg har startet" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Bruk Z-Wave JS Supervisor-tillegg" + }, + "description": "Vil du bruke Z-Wave JS Supervisor-tillegg?", + "title": "Velg tilkoblingsmetode" + }, + "start_addon": { + "data": { + "network_key": "Nettverksn\u00f8kkel", + "usb_path": "USB enhetsbane" + }, + "title": "Angi konfigurasjon for Z-Wave JS-tillegg" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json new file mode 100644 index 00000000000000..47e263c610192f --- /dev/null +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Nie uda\u0142o si\u0119 uzyska\u0107 informacji wykrywania dodatku Z-Wave JS", + "addon_info_failed": "Nie uda\u0142o si\u0119 uzyska\u0107 informacji o dodatku Z-Wave JS", + "addon_install_failed": "Nie uda\u0142o si\u0119 zainstalowa\u0107 dodatku Z-Wave JS", + "addon_missing_discovery_info": "Brak informacji wykrywania dodatku Z-Wave JS", + "addon_set_config_failed": "Nie uda\u0142o si\u0119 skonfigurowa\u0107 Z-Wave JS", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "error": { + "addon_start_failed": "Nie uda\u0142o si\u0119 uruchomi\u0107 dodatku Z-Wave JS. Sprawd\u017a konfiguracj\u0119", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_ws_url": "Nieprawid\u0142owy URL websocket", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "progress": { + "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut." + }, + "step": { + "hassio_confirm": { + "title": "Skonfiguruj integracj\u0119 Z-Wave JS z dodatkiem Z-Wave JS" + }, + "install_addon": { + "title": "Rozpocz\u0119\u0142a si\u0119 instalacja dodatku Z-Wave JS" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "U\u017cyj dodatku Z-Wave JS Supervisor" + }, + "description": "Czy chcesz skorzysta\u0107 z dodatku Z-Wave JS Supervisor?", + "title": "Wybierz metod\u0119 po\u0142\u0105czenia" + }, + "start_addon": { + "data": { + "network_key": "Klucz sieci", + "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" + }, + "title": "Wprowad\u017a konfiguracj\u0119 dodatku Z-Wave JS" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/pt-BR.json b/homeassistant/components/zwave_js/translations/pt-BR.json new file mode 100644 index 00000000000000..e29d809ebff3dd --- /dev/null +++ b/homeassistant/components/zwave_js/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/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json new file mode 100644 index 00000000000000..2d9609e9d00437 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS.", + "addon_info_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", + "addon_install_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", + "addon_missing_discovery_info": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", + "addon_set_config_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e Z-Wave JS.", + "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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "error": { + "addon_start_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_ws_url": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "progress": { + "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442." + }, + "step": { + "hassio_confirm": { + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Z-Wave JS (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant Z-Wave JS)" + }, + "install_addon": { + "title": "\u041d\u0430\u0447\u0430\u043b\u0430\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS" + }, + "manual": { + "data": { + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + } + }, + "on_supervisor": { + "data": { + "use_addon": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS" + }, + "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS?", + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + }, + "start_addon": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438", + "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + }, + "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS" + }, + "user": { + "data": { + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/tr.json b/homeassistant/components/zwave_js/translations/tr.json new file mode 100644 index 00000000000000..2faa8ba43075b5 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/tr.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Z-Wave JS eklenti ke\u015fif bilgileri al\u0131namad\u0131.", + "addon_info_failed": "Z-Wave JS eklenti bilgileri al\u0131namad\u0131.", + "addon_install_failed": "Z-Wave JS eklentisi y\u00fcklenemedi.", + "addon_missing_discovery_info": "Eksik Z-Wave JS eklenti bulma bilgileri.", + "addon_set_config_failed": "Z-Wave JS yap\u0131land\u0131rmas\u0131 ayarlanamad\u0131.", + "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f", + "already_in_progress": "Yap\u0131land\u0131rma ak\u0131\u015f\u0131 zaten devam ediyor", + "cannot_connect": "Ba\u011flanma hatas\u0131" + }, + "error": { + "addon_start_failed": "Z-Wave JS eklentisi ba\u015flat\u0131lamad\u0131. Yap\u0131land\u0131rmay\u0131 kontrol edin.", + "cannot_connect": "Ba\u011flanma hatas\u0131", + "invalid_ws_url": "Ge\u00e7ersiz websocket URL'si", + "unknown": "Beklenmeyen hata" + }, + "progress": { + "install_addon": "L\u00fctfen Z-Wave JS eklenti kurulumu bitene kadar bekleyin. Bu birka\u00e7 dakika s\u00fcrebilir." + }, + "step": { + "hassio_confirm": { + "title": "Z-Wave JS eklentisiyle Z-Wave JS entegrasyonunu ayarlay\u0131n" + }, + "install_addon": { + "title": "Z-Wave JS eklenti kurulumu ba\u015flad\u0131" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Z-Wave JS Supervisor eklentisini kullan\u0131n" + }, + "description": "Z-Wave JS Supervisor eklentisini kullanmak istiyor musunuz?", + "title": "Ba\u011flant\u0131 y\u00f6ntemini se\u00e7in" + }, + "start_addon": { + "data": { + "network_key": "A\u011f Anahtar\u0131", + "usb_path": "USB Ayg\u0131t Yolu" + }, + "title": "Z-Wave JS eklenti yap\u0131land\u0131rmas\u0131na girin" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/uk.json b/homeassistant/components/zwave_js/translations/uk.json new file mode 100644 index 00000000000000..f5ff5224347ae9 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/uk.json @@ -0,0 +1,20 @@ +{ + "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." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f", + "invalid_ws_url": "\u041d\u0435\u0434\u0456\u0439\u0441\u043d\u0430 URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432\u0435\u0431-\u0441\u043e\u043a\u0435\u0442\u0430", + "unknown": "\u041d\u0435\u043e\u0447\u0456\u043a\u0443\u0432\u0430\u043d\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430" + }, + "step": { + "user": { + "data": { + "url": "URL-\u0430\u0434\u0440\u0435\u0441\u0430" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json new file mode 100644 index 00000000000000..1cbde8f886ba84 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u8cc7\u8a0a\u5931\u6557\u3002", + "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", + "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", + "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "error": { + "addon_start_failed": "Z-Wave JS add-on \u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_ws_url": "Websocket URL \u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "progress": { + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + }, + "step": { + "hassio_confirm": { + "title": "\u4ee5 Z-Wave JS add-on \u8a2d\u5b9a Z-Wave JS \u6574\u5408" + }, + "install_addon": { + "title": "Z-Wave JS add-on \u5b89\u88dd\u5df2\u555f\u52d5" + }, + "manual": { + "data": { + "url": "\u7db2\u5740" + } + }, + "on_supervisor": { + "data": { + "use_addon": "\u4f7f\u7528 Z-Wave JS Supervisor add-on" + }, + "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", + "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" + }, + "start_addon": { + "data": { + "network_key": "\u7db2\u8def\u5bc6\u9470", + "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" + }, + "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u8a2d\u5b9a" + }, + "user": { + "data": { + "url": "\u7db2\u5740" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file From a2ec1a47d550c01087ba4daa7cec58c4c5b0cab4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:54:00 +0100 Subject: [PATCH 0143/1818] Mark Z-Wave as deprecated (#45896) --- homeassistant/components/zwave/manifest.json | 2 +- homeassistant/components/zwave/strings.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave/manifest.json b/homeassistant/components/zwave/manifest.json index a3a2b5e0d83e39..6623036d2fea94 100644 --- a/homeassistant/components/zwave/manifest.json +++ b/homeassistant/components/zwave/manifest.json @@ -1,6 +1,6 @@ { "domain": "zwave", - "name": "Z-Wave", + "name": "Z-Wave (deprecated)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave", "requirements": ["homeassistant-pyozw==0.1.10", "pydispatcher==2.0.5"], diff --git a/homeassistant/components/zwave/strings.json b/homeassistant/components/zwave/strings.json index 852b8ca22fab11..69401b171e2e7d 100644 --- a/homeassistant/components/zwave/strings.json +++ b/homeassistant/components/zwave/strings.json @@ -2,8 +2,7 @@ "config": { "step": { "user": { - "title": "Set up Z-Wave", - "description": "See https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables", + "description": "This integration is no longer maintained. For new installations, use Z-Wave JS instead.\n\nSee https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables", "data": { "usb_path": "[%key:common::config_flow::data::usb_path%]", "network_key": "Network Key (leave blank to auto-generate)" From 40ba182144254f172875e3a6f4004cda9f8e0614 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:58:46 +0100 Subject: [PATCH 0144/1818] Upgrade Z-Wave JS Python to 0.17.0 (#45895) --- homeassistant/components/zwave_js/__init__.py | 23 ++++++++++++------- homeassistant/components/zwave_js/api.py | 2 +- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/conftest.py | 4 ++-- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 1a2cdfa70172b5..01b8f4785c5d5b 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -45,7 +45,7 @@ from .discovery import async_discover_values from .entity import get_device_id -LOGGER = logging.getLogger(__name__) +LOGGER = logging.getLogger(__package__) CONNECT_TIMEOUT = 10 DATA_CLIENT_LISTEN_TASK = "client_listen_task" DATA_START_PLATFORM_TASK = "start_platform_task" @@ -263,13 +263,20 @@ async def client_listen( driver_ready: asyncio.Event, ) -> None: """Listen with the client.""" + should_reload = True try: await client.listen(driver_ready) + except asyncio.CancelledError: + should_reload = False except BaseZwaveJSServerError: - # The entry needs to be reloaded since a new driver state - # will be acquired on reconnect. - # All model instances will be replaced when the new state is acquired. - hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) + pass + + # The entry needs to be reloaded since a new driver state + # will be acquired on reconnect. + # All model instances will be replaced when the new state is acquired. + if should_reload: + LOGGER.info("Disconnected from server. Reloading integration") + asyncio.create_task(hass.config_entries.async_reload(entry.entry_id)) async def disconnect_client( @@ -280,14 +287,14 @@ async def disconnect_client( platform_task: asyncio.Task, ) -> None: """Disconnect client.""" - await client.disconnect() - listen_task.cancel() platform_task.cancel() await asyncio.gather(listen_task, platform_task) - LOGGER.info("Disconnected from Zwave JS Server") + if client.connected: + await client.disconnect() + LOGGER.info("Disconnected from Zwave JS Server") async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 1a8a197571b1b8..03a917217a9909 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -51,7 +51,7 @@ def websocket_network_status( data = { "client": { "ws_server_url": client.ws_server_url, - "state": client.state, + "state": "connected" if client.connected else "disconnected", "driver_version": client.version.driver_version, "server_version": client.version.server_version, }, diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index de77ebbf5e0c36..7df75d7aed2e58 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.16.0"], + "requirements": ["zwave-js-server-python==0.17.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 01d6fe0a92ac54..c25ea05aa33478 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2384,4 +2384,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.16.0 +zwave-js-server-python==0.17.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 95eab6a7440139..b156395df1329b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1203,4 +1203,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.16.0 +zwave-js-server-python==0.17.0 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b5301f4cd2ffbd..984ec42b9f3223 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -157,14 +157,14 @@ def mock_client_fixture(controller_state, version_state): async def connect(): await asyncio.sleep(0) - client.state = "connected" client.connected = True async def listen(driver_ready: asyncio.Event) -> None: driver_ready.set() + await asyncio.sleep(30) + assert False, "Listen wasn't canceled!" async def disconnect(): - client.state = "disconnected" client.connected = False client.connect = AsyncMock(side_effect=connect) From 53f1dba78483b9adbaa5c71f4d4ef60e58a2fd3b Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 3 Feb 2021 02:44:34 -0500 Subject: [PATCH 0145/1818] Bump plexapi to 3.4.1 (#45878) --- 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 f0f1e09a15c475..913f405cfcdc48 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.3.0", + "plexapi==4.3.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index f68f9abcc08257..69979d5db256d8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1137,7 +1137,7 @@ pillow==8.1.0 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.3.0 +plexapi==4.3.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d5648e33e89dec..ca7312d83d3c2b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -571,7 +571,7 @@ pilight==0.1.1 pillow==8.1.0 # homeassistant.components.plex -plexapi==4.3.0 +plexapi==4.3.1 # homeassistant.components.plex plexauth==0.0.6 From 8d0d4d31334b440dad15c7a7d911256082cb50aa Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 3 Feb 2021 10:41:02 +0100 Subject: [PATCH 0146/1818] Update docker base image 2021.02.0 (#45889) --- build.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.json b/build.json index 1cf4217146dbfa..0183b61c67c35b 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:2021.01.1", - "armhf": "homeassistant/armhf-homeassistant-base:2021.01.1", - "armv7": "homeassistant/armv7-homeassistant-base:2021.01.1", - "amd64": "homeassistant/amd64-homeassistant-base:2021.01.1", - "i386": "homeassistant/i386-homeassistant-base:2021.01.1" + "aarch64": "homeassistant/aarch64-homeassistant-base:2021.02.0", + "armhf": "homeassistant/armhf-homeassistant-base:2021.02.0", + "armv7": "homeassistant/armv7-homeassistant-base:2021.02.0", + "amd64": "homeassistant/amd64-homeassistant-base:2021.02.0", + "i386": "homeassistant/i386-homeassistant-base:2021.02.0" }, "labels": { "io.hass.type": "core" From 2369fda0d0c10f4a02e559a302c161b8cb60b7c6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:58:46 +0100 Subject: [PATCH 0147/1818] Upgrade Z-Wave JS Python to 0.17.0 (#45895) --- homeassistant/components/zwave_js/__init__.py | 23 ++++++++++++------- homeassistant/components/zwave_js/api.py | 2 +- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/conftest.py | 4 ++-- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 1a2cdfa70172b5..01b8f4785c5d5b 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -45,7 +45,7 @@ from .discovery import async_discover_values from .entity import get_device_id -LOGGER = logging.getLogger(__name__) +LOGGER = logging.getLogger(__package__) CONNECT_TIMEOUT = 10 DATA_CLIENT_LISTEN_TASK = "client_listen_task" DATA_START_PLATFORM_TASK = "start_platform_task" @@ -263,13 +263,20 @@ async def client_listen( driver_ready: asyncio.Event, ) -> None: """Listen with the client.""" + should_reload = True try: await client.listen(driver_ready) + except asyncio.CancelledError: + should_reload = False except BaseZwaveJSServerError: - # The entry needs to be reloaded since a new driver state - # will be acquired on reconnect. - # All model instances will be replaced when the new state is acquired. - hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) + pass + + # The entry needs to be reloaded since a new driver state + # will be acquired on reconnect. + # All model instances will be replaced when the new state is acquired. + if should_reload: + LOGGER.info("Disconnected from server. Reloading integration") + asyncio.create_task(hass.config_entries.async_reload(entry.entry_id)) async def disconnect_client( @@ -280,14 +287,14 @@ async def disconnect_client( platform_task: asyncio.Task, ) -> None: """Disconnect client.""" - await client.disconnect() - listen_task.cancel() platform_task.cancel() await asyncio.gather(listen_task, platform_task) - LOGGER.info("Disconnected from Zwave JS Server") + if client.connected: + await client.disconnect() + LOGGER.info("Disconnected from Zwave JS Server") async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 1a8a197571b1b8..03a917217a9909 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -51,7 +51,7 @@ def websocket_network_status( data = { "client": { "ws_server_url": client.ws_server_url, - "state": client.state, + "state": "connected" if client.connected else "disconnected", "driver_version": client.version.driver_version, "server_version": client.version.server_version, }, diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index de77ebbf5e0c36..7df75d7aed2e58 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.16.0"], + "requirements": ["zwave-js-server-python==0.17.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 69979d5db256d8..309b77acfe9bee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.16.0 +zwave-js-server-python==0.17.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ca7312d83d3c2b..cd388ff403caf8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1194,4 +1194,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.16.0 +zwave-js-server-python==0.17.0 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b5301f4cd2ffbd..984ec42b9f3223 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -157,14 +157,14 @@ def mock_client_fixture(controller_state, version_state): async def connect(): await asyncio.sleep(0) - client.state = "connected" client.connected = True async def listen(driver_ready: asyncio.Event) -> None: driver_ready.set() + await asyncio.sleep(30) + assert False, "Listen wasn't canceled!" async def disconnect(): - client.state = "disconnected" client.connected = False client.connect = AsyncMock(side_effect=connect) From c3786ee1866c7e6d3bbe44253b8a778826db089d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:54:00 +0100 Subject: [PATCH 0148/1818] Mark Z-Wave as deprecated (#45896) --- homeassistant/components/zwave/manifest.json | 2 +- homeassistant/components/zwave/strings.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave/manifest.json b/homeassistant/components/zwave/manifest.json index a3a2b5e0d83e39..6623036d2fea94 100644 --- a/homeassistant/components/zwave/manifest.json +++ b/homeassistant/components/zwave/manifest.json @@ -1,6 +1,6 @@ { "domain": "zwave", - "name": "Z-Wave", + "name": "Z-Wave (deprecated)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave", "requirements": ["homeassistant-pyozw==0.1.10", "pydispatcher==2.0.5"], diff --git a/homeassistant/components/zwave/strings.json b/homeassistant/components/zwave/strings.json index 852b8ca22fab11..69401b171e2e7d 100644 --- a/homeassistant/components/zwave/strings.json +++ b/homeassistant/components/zwave/strings.json @@ -2,8 +2,7 @@ "config": { "step": { "user": { - "title": "Set up Z-Wave", - "description": "See https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables", + "description": "This integration is no longer maintained. For new installations, use Z-Wave JS instead.\n\nSee https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables", "data": { "usb_path": "[%key:common::config_flow::data::usb_path%]", "network_key": "Network Key (leave blank to auto-generate)" From 9070ee0851105d582830464daea776f81c0f5f75 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 11:59:33 +0100 Subject: [PATCH 0149/1818] Bumped version to 2021.2.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e23f47101b4983..60e9e745f6c745 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From eaa9fff3ba00ee3125da295b8ae9cee4c69bd33f Mon Sep 17 00:00:00 2001 From: Jesse Campbell Date: Wed, 3 Feb 2021 06:02:49 -0500 Subject: [PATCH 0150/1818] Remove v4 multilevel transitional currentValue workaround in zwave_js (#45884) * Remove v4 multilevel transitional currentValue workaround This was only needed because the get-after-set was reporting a transitional currentValue instead of the final one. zwave-js v6.1.1 removes the get-after-set functionality completely, so this is no longer required (and breaks status reporting entirely) * Fix tests to check currentValue instead of targetValue as well --- homeassistant/components/zwave_js/light.py | 8 -------- tests/components/zwave_js/conftest.py | 2 +- tests/components/zwave_js/test_light.py | 4 ++-- tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json | 4 ++-- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index acfa22e5847979..dd444fdb40d30e 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -105,14 +105,6 @@ def brightness(self) -> int: Z-Wave multilevel switches use a range of [0, 99] to control brightness. """ - # prefer targetValue only if CC Version >= 4 - # otherwise use currentValue (pre V4 dimmers) - if ( - self._target_value - and self._target_value.value is not None - and self._target_value.cc_version >= 4 - ): - return round((self._target_value.value / 99) * 255) if self.info.primary_value.value is not None: return round((self.info.primary_value.value / 99) * 255) return 0 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 984ec42b9f3223..903de6d3bd5182 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -88,7 +88,7 @@ def bulb_6_multi_color_state_fixture(): @pytest.fixture(name="eaton_rf9640_dimmer_state", scope="session") def eaton_rf9640_dimmer_state_fixture(): - """Load the bulb 6 multi-color node state fixture data.""" + """Load the eaton rf9640 dimmer node state fixture data.""" return json.loads(load_fixture("zwave_js/eaton_rf9640_dimmer_state.json")) diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index a1b2318022bab1..b60c72818742cf 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -395,5 +395,5 @@ async def test_v4_dimmer_light(hass, client, eaton_rf9640_dimmer, integration): assert state assert state.state == STATE_ON - # the light should pick targetvalue which has zwave value 20 - assert state.attributes[ATTR_BRIGHTNESS] == 52 + # the light should pick currentvalue which has zwave value 22 + assert state.attributes[ATTR_BRIGHTNESS] == 57 diff --git a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json index 38cbb63b1c6c53..0f2f45d01e3906 100644 --- a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json +++ b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json @@ -124,7 +124,7 @@ "max": 99, "label": "Current value" }, - "value": 0, + "value": 22, "ccVersion": 4 }, { @@ -779,4 +779,4 @@ "ccVersion": 3 } ] -} \ No newline at end of file +} From 9998fe368480fa762f9ceda029a6594e859036bf Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 3 Feb 2021 13:08:00 +0100 Subject: [PATCH 0151/1818] Update discovery scheme for Meter CC in zwave_js integration (#45897) --- homeassistant/components/zwave_js/discovery.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 88717d9fc83be3..8c322b8e0e318b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -155,13 +155,22 @@ class ZWaveDiscoverySchema: hint="numeric_sensor", command_class={ CommandClass.SENSOR_MULTILEVEL, - CommandClass.METER, CommandClass.SENSOR_ALARM, CommandClass.INDICATOR, CommandClass.BATTERY, }, type={"number"}, ), + # numeric sensors for Meter CC + ZWaveDiscoverySchema( + platform="sensor", + hint="numeric_sensor", + command_class={ + CommandClass.METER, + }, + type={"number"}, + property={"value"}, + ), # special list sensors (Notification CC) ZWaveDiscoverySchema( platform="sensor", From 5615ab4c2574998837a48caf55e37a8877c5e8ae Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 3 Feb 2021 13:59:19 +0100 Subject: [PATCH 0152/1818] Add support for climate setpoint thermostats to zwave_js (#45890) --- homeassistant/components/zwave_js/climate.py | 16 +- .../components/zwave_js/discovery.py | 14 + tests/components/zwave_js/conftest.py | 28 + tests/components/zwave_js/test_climate.py | 96 ++ .../zwave_js/climate_danfoss_lc_13_state.json | 368 +++++ .../zwave_js/climate_heatit_z_trm3_state.json | 1181 +++++++++++++++++ 6 files changed, 1699 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json create mode 100644 tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 417f5aa5e5dc8f..b125c8bcd6a4ac 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -5,6 +5,7 @@ from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( THERMOSTAT_CURRENT_TEMP_PROPERTY, + THERMOSTAT_MODE_PROPERTY, THERMOSTAT_MODE_SETPOINT_MAP, THERMOSTAT_MODES, THERMOSTAT_OPERATING_STATE_PROPERTY, @@ -119,7 +120,9 @@ def __init__( self._hvac_presets: Dict[str, Optional[int]] = {} self._unit_value: ZwaveValue = None - self._current_mode = self.info.primary_value + self._current_mode = self.get_zwave_value( + THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE + ) self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: self._setpoint_values[enum] = self.get_zwave_value( @@ -165,10 +168,12 @@ def _set_modes_and_presets(self) -> None: # Z-Wave uses one list for both modes and presets. # Iterate over all Z-Wave ThermostatModes and extract the hvac modes and presets. - current_mode = self._current_mode - if not current_mode: + if self._current_mode is None: + self._hvac_modes = { + ZW_HVAC_MODE_MAP[ThermostatMode.HEAT]: ThermostatMode.HEAT + } return - for mode_id, mode_name in current_mode.metadata.states.items(): + for mode_id, mode_name in self._current_mode.metadata.states.items(): mode_id = int(mode_id) if mode_id in THERMOSTAT_MODES: # treat value as hvac mode @@ -184,6 +189,9 @@ def _set_modes_and_presets(self) -> None: @property def _current_mode_setpoint_enums(self) -> List[Optional[ThermostatSetpointType]]: """Return the list of enums that are relevant to the current thermostat mode.""" + 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 @property diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 8c322b8e0e318b..d741946a1c9a7b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -56,6 +56,8 @@ class ZWaveDiscoverySchema: type: Optional[Set[str]] = None +# For device class mapping see: +# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json DISCOVERY_SCHEMAS = [ # locks ZWaveDiscoverySchema( @@ -105,6 +107,18 @@ class ZWaveDiscoverySchema: property={"mode"}, type={"number"}, ), + # climate + # setpoint thermostats + ZWaveDiscoverySchema( + platform="climate", + device_class_generic={"Thermostat"}, + device_class_specific={ + "Setpoint Thermostat", + }, + command_class={CommandClass.THERMOSTAT_SETPOINT}, + property={"setpoint"}, + type={"number"}, + ), # lights # primary value is the currentValue (brightness) ZWaveDiscoverySchema( diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 903de6d3bd5182..9cb950ba6e706c 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -128,6 +128,18 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_state_fixture(): ) +@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.""" + return json.loads(load_fixture("zwave_js/climate_danfoss_lc_13_state.json")) + + +@pytest.fixture(name="climate_heatit_z_trm3_state", scope="session") +def climate_heatit_z_trm3_state_fixture(): + """Load the climate HEATIT Z-TRM3 thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/climate_heatit_z_trm3_state.json")) + + @pytest.fixture(name="nortek_thermostat_state", scope="session") def nortek_thermostat_state_fixture(): """Load the nortek thermostat node state fixture data.""" @@ -254,6 +266,22 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( 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.""" + node = Node(client, climate_danfoss_lc_13_state) + client.driver.controller.nodes[node.node_id] = node + return node + + +@pytest.fixture(name="climate_heatit_z_trm3") +def climate_heatit_z_trm3_fixture(client, climate_heatit_z_trm3_state): + """Mock a climate radio HEATIT Z-TRM3 node.""" + node = Node(client, climate_heatit_z_trm3_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="nortek_thermostat") def nortek_thermostat_fixture(client, nortek_thermostat_state): """Mock a nortek thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index bede37e6959e31..b2455f3cbbd8f2 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -26,6 +26,8 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat_thermostat_mode" +CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat_heating" +CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat_thermostat_mode" async def test_thermostat_v2( @@ -335,3 +337,97 @@ async def test_thermostat_different_endpoints( state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY) assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 + + +async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integration): + """Test a setpoint thermostat command class entity.""" + node = climate_danfoss_lc_13 + state = hass.states.get(CLIMATE_DANFOSS_LC13_ENTITY) + + assert state + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 25 + assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + client.async_send_command.reset_mock() + + # Test setting temperature + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: CLIMATE_DANFOSS_LC13_ENTITY, + ATTR_TEMPERATURE: 21.5, + }, + 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"] == 5 + assert args["valueId"] == { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "unit": "\u00b0C", + "ccSpecific": {"setpointType": 1}, + }, + "value": 25, + } + assert args["value"] == 21.5 + + client.async_send_command.reset_mock() + + # Test setpoint mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 5, + "args": { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 1, + "propertyKeyName": "Heating", + "propertyName": "setpoint", + "newValue": 23, + "prevValue": 21.5, + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(CLIMATE_DANFOSS_LC13_ENTITY) + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 23 + + client.async_send_command.reset_mock() + + +async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integration): + """Test a thermostat v2 command class entity.""" + state = hass.states.get(CLIMATE_FLOOR_THERMOSTAT_ENTITY) + + assert state + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + ] + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.9 + assert state.attributes[ATTR_TEMPERATURE] == 22.5 + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE diff --git a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json new file mode 100644 index 00000000000000..e218d3b6a0e55d --- /dev/null +++ b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json @@ -0,0 +1,368 @@ +{ + "nodeId": 5, + "index": 0, + "status": 1, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Setpoint Thermostat", + "mandatorySupportedCCs": [ + "Manufacturer Specific", + "Multi Command", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": false, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 2, + "productId": 4, + "productType": 5, + "firmwareVersion": "1.1", + "deviceConfig": { + "manufacturerId": 2, + "manufacturer": "Danfoss", + "label": "LC-13", + "description": "Living Connect Z Thermostat", + "devices": [ + { + "productType": "0x0005", + "productId": "0x0004" + }, + { + "productType": "0x8005", + "productId": "0x0001" + }, + { + "productType": "0x8005", + "productId": "0x0002" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "compat": { + "valueIdRegex": {}, + "queryOnWakeup": [ + [ + "Battery", + "get" + ], + [ + "Thermostat Setpoint", + "get", + 1 + ] + ] + } + }, + "label": "LC-13", + "neighbors": [ + 1, + 14 + ], + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 5, + "index": 0 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0C", + "ccSpecific": { + "setpointType": 1 + } + }, + "value": 25 + }, + { + "endpoint": 0, + "commandClass": 70, + "commandClassName": "Climate Control Schedule", + "property": "changeCounter", + "propertyName": "changeCounter", + "ccVersion": 0, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 70, + "commandClassName": "Climate Control Schedule", + "property": "overrideType", + "propertyName": "overrideType", + "ccVersion": 0, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 70, + "commandClassName": "Climate Control Schedule", + "property": "overrideState", + "propertyName": "overrideState", + "ccVersion": 0, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "Unused" + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "rf", + "propertyName": "rf", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "RF protection state", + "states": {} + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "exclusiveControlNodeId", + "propertyName": "exclusiveControlNodeId", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "timeout", + "propertyName": "timeout", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 53 + }, + { + "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": 132, + "commandClassName": "Wake Up", + "property": "wakeUpInterval", + "propertyName": "wakeUpInterval", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "min": 60, + "max": 1800, + "label": "Wake Up interval", + "steps": 60, + "default": 300 + }, + "value": 300 + }, + { + "endpoint": 0, + "commandClass": 132, + "commandClassName": "Wake Up", + "property": "controllerNodeId", + "propertyName": "controllerNodeId", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Node ID of the controller" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "3.67" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.1" + ] + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json new file mode 100644 index 00000000000000..066811c7374b75 --- /dev/null +++ b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json @@ -0,0 +1,1181 @@ +{ + "nodeId": 24, + "index": 0, + "installerIcon": 4608, + "userIcon": 4609, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Thermostat General V2", + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 411, + "productId": 515, + "productType": 3, + "firmwareVersion": "4.0", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 411, + "manufacturer": "ThermoFloor", + "label": "Heatit Z-TRM3", + "description": "Floor thermostat", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0203" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + }, + "compat": { + "valueIdRegex": {}, + "overrideFloatEncoding": { + "size": 2 + }, + "addCCs": {} + } + }, + "label": "Heatit Z-TRM3", + "neighbors": [ + 1, + 2, + 3, + 4, + 6, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 17, + 18, + 19, + 25, + 26, + 28 + ], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 4, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 24, + "index": 0, + "installerIcon": 4608, + "userIcon": 4609 + }, + { + "nodeId": 24, + "index": 1, + "installerIcon": 4608, + "userIcon": 4609 + }, + { + "nodeId": 24, + "index": 2, + "installerIcon": 3328, + "userIcon": 3329 + }, + { + "nodeId": 24, + "index": 3, + "installerIcon": 3328, + "userIcon": 3329 + }, + { + "nodeId": 24, + "index": 4, + "installerIcon": 3328, + "userIcon": 3329 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "param001", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "Sensor mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 4, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "F-mode, floor sensor mode", + "1": "A-mode, internal room sensor mode", + "2": "AF-mode, internal sensor and floor sensor mode", + "3": "A2-mode, external room sensor mode", + "4": "A2F-mode, external sensor with floor limitation" + }, + "label": "Sensor mode", + "description": "Sensor mode", + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Floor sensor type", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 5, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "10K-NTC", + "1": "12K-NTC", + "2": "15K-NTC", + "3": "22K-NTC", + "4": "33K-NTC", + "5": "47K-NTC" + }, + "label": "Floor sensor type", + "description": "Floor sensor type", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Temperature control hysteresis (DIFF I)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 3, + "max": 30, + "default": 5, + "format": 0, + "allowManualEntry": true, + "label": "Temperature control hysteresis (DIFF I)", + "description": "Temperature control hysteresis (DIFF I), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Floor minimum temperature limit (FLo)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Floor minimum temperature limit (FLo)", + "description": "Floor minimum temperature limit (FLo), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Floor maximum temperature (FHi)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 400, + "format": 0, + "allowManualEntry": true, + "label": "Floor maximum temperature (FHi)", + "description": "Floor maximum temperature (FHi), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 400 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Air minimum temperature limit (ALo)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Air minimum temperature limit (ALo)", + "description": "Air minimum temperature limit (ALo), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Air maximum temperature limit (AHi)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 400, + "format": 0, + "allowManualEntry": true, + "label": "Air maximum temperature limit (AHi)", + "description": "Air maximum temperature limit (AHi), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 400 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 9, + "propertyName": "param009", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 225 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 10, + "propertyName": "Room sensor calibration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -60, + "max": 60, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Room sensor calibration", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 11, + "propertyName": "Floor sensor calibration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -60, + "max": 60, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Floor sensor calibration", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 12, + "propertyName": "External sensor calibration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -60, + "max": 60, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "External sensor calibration", + "isFromConfig": true + }, + "value": -42 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 13, + "propertyName": "Temperature display", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Display setpoint temperature", + "1": "Display calculated temperature" + }, + "label": "Temperature display", + "description": "Selects which temperature is shown on the display.", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 14, + "propertyName": "Button brightness - dimmed state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Button brightness - dimmed state", + "description": "Button brightness - dimmed state", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 15, + "propertyName": "Button brightness - active state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 100, + "format": 0, + "allowManualEntry": true, + "label": "Button brightness - active state", + "description": "Button brightness - active state", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 16, + "propertyName": "Display brightness - dimmed state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Display brightness - dimmed state", + "description": "Display brightness - dimmed state", + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 17, + "propertyName": "Display brightness - active state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 100, + "format": 0, + "allowManualEntry": true, + "label": "Display brightness - active state", + "description": "Display brightness - active state", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 18, + "propertyName": "Temperature report interval", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 60, + "format": 0, + "allowManualEntry": true, + "label": "Temperature report interval", + "description": "Temperature report interval", + "isFromConfig": true + }, + "value": 360 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 19, + "propertyName": "Temperature report hysteresis", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 100, + "default": 10, + "format": 0, + "allowManualEntry": true, + "label": "Temperature report hysteresis", + "description": "Temperature report hysteresis", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 20, + "propertyName": "Meter report interval", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 90, + "format": 0, + "allowManualEntry": true, + "label": "Meter report interval", + "description": "Meter report interval", + "isFromConfig": true + }, + "value": 3600 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 21, + "propertyName": "Meter report delta value", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 10, + "format": 1, + "allowManualEntry": true, + "label": "Meter report delta value", + "description": "Meter report delta value", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 411 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 515 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "6.7" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "4.0" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "sdkVersion", + "propertyName": "sdkVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "6.81.6" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkAPIVersion", + "propertyName": "applicationFrameworkAPIVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "4.3.0" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkBuildNumber", + "propertyName": "applicationFrameworkBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 52445 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceVersion", + "propertyName": "hostInterfaceVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "unused" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceBuildNumber", + "propertyName": "hostInterfaceBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolVersion", + "propertyName": "zWaveProtocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "6.7.0" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolBuildNumber", + "propertyName": "zWaveProtocolBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 97 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationVersion", + "propertyName": "applicationVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "4.0.33" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationBuildNumber", + "propertyName": "applicationBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 52445 + }, + { + "endpoint": 1, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 5, + "max": 35, + "unit": "\u00b0C", + "ccSpecific": { + "setpointType": 1 + } + }, + "value": 22.5 + }, + { + "endpoint": 1, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat" + } + }, + "value": 1 + }, + { + "endpoint": 1, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 1, + "commandClass": 66, + "commandClassName": "Thermostat Operating State", + "property": "state", + "propertyName": "state", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Operating state", + "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": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyName": "value", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Value (Electric, Consumed)", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 369.2 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyName": "deltaTime", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Time since the previous reading", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyName": "value", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Value (Electric, Consumed)", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0.09 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyName": "deltaTime", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Time since the previous reading", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyName": "value", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Value (Electric, Consumed)", + "unit": "V", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 4 + } + }, + "value": 238 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyName": "deltaTime", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Time since the previous reading", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 4 + } + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyName": "previousValue", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Previous value (Electric, Consumed)", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + } + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyName": "previousValue", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Previous value (Electric, Consumed)", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + } + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyName": "previousValue", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Previous value (Electric, Consumed)", + "unit": "V", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 4 + } + } + }, + { + "endpoint": 2, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 22.9 + }, + { + "endpoint": 3, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 0 + }, + { + "endpoint": 4, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 25.5 + } + ] +} \ No newline at end of file From 90973f471f991de9c270c39c540abd4d6770e2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 3 Feb 2021 14:40:11 +0100 Subject: [PATCH 0153/1818] Add integration name to the deprecation warnings (#45901) --- homeassistant/helpers/deprecation.py | 30 +++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 0022f8888296e7..7478a7fede98ed 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -4,6 +4,8 @@ import logging from typing import Any, Callable, Dict, Optional +from ..helpers.frame import MissingIntegrationFrame, get_integration_frame + def deprecated_substitute(substitute_name: str) -> Callable[..., Callable]: """Help migrate properties to new names. @@ -86,11 +88,29 @@ def deprecated_decorator(func: Callable) -> Callable: def deprecated_func(*args: tuple, **kwargs: Dict[str, Any]) -> Any: """Wrap for the original function.""" logger = logging.getLogger(func.__module__) - logger.warning( - "%s is a deprecated function. Use %s instead", - func.__name__, - replacement, - ) + try: + _, integration, path = get_integration_frame() + if path == "custom_components/": + logger.warning( + "%s was called from %s, this is a deprecated function. Use %s instead, please report this to the maintainer of %s", + func.__name__, + integration, + replacement, + integration, + ) + else: + logger.warning( + "%s was called from %s, this is a deprecated function. Use %s instead", + func.__name__, + integration, + replacement, + ) + except MissingIntegrationFrame: + logger.warning( + "%s is a deprecated function. Use %s instead", + func.__name__, + replacement, + ) return func(*args, **kwargs) return deprecated_func From f2c2a9722815462db1e1d4c4407e69b56700f6c4 Mon Sep 17 00:00:00 2001 From: Jesse Campbell Date: Wed, 3 Feb 2021 06:02:49 -0500 Subject: [PATCH 0154/1818] Remove v4 multilevel transitional currentValue workaround in zwave_js (#45884) * Remove v4 multilevel transitional currentValue workaround This was only needed because the get-after-set was reporting a transitional currentValue instead of the final one. zwave-js v6.1.1 removes the get-after-set functionality completely, so this is no longer required (and breaks status reporting entirely) * Fix tests to check currentValue instead of targetValue as well --- homeassistant/components/zwave_js/light.py | 8 -------- tests/components/zwave_js/conftest.py | 2 +- tests/components/zwave_js/test_light.py | 4 ++-- tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json | 4 ++-- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index acfa22e5847979..dd444fdb40d30e 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -105,14 +105,6 @@ def brightness(self) -> int: Z-Wave multilevel switches use a range of [0, 99] to control brightness. """ - # prefer targetValue only if CC Version >= 4 - # otherwise use currentValue (pre V4 dimmers) - if ( - self._target_value - and self._target_value.value is not None - and self._target_value.cc_version >= 4 - ): - return round((self._target_value.value / 99) * 255) if self.info.primary_value.value is not None: return round((self.info.primary_value.value / 99) * 255) return 0 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 984ec42b9f3223..903de6d3bd5182 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -88,7 +88,7 @@ def bulb_6_multi_color_state_fixture(): @pytest.fixture(name="eaton_rf9640_dimmer_state", scope="session") def eaton_rf9640_dimmer_state_fixture(): - """Load the bulb 6 multi-color node state fixture data.""" + """Load the eaton rf9640 dimmer node state fixture data.""" return json.loads(load_fixture("zwave_js/eaton_rf9640_dimmer_state.json")) diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index a1b2318022bab1..b60c72818742cf 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -395,5 +395,5 @@ async def test_v4_dimmer_light(hass, client, eaton_rf9640_dimmer, integration): assert state assert state.state == STATE_ON - # the light should pick targetvalue which has zwave value 20 - assert state.attributes[ATTR_BRIGHTNESS] == 52 + # the light should pick currentvalue which has zwave value 22 + assert state.attributes[ATTR_BRIGHTNESS] == 57 diff --git a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json index 38cbb63b1c6c53..0f2f45d01e3906 100644 --- a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json +++ b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json @@ -124,7 +124,7 @@ "max": 99, "label": "Current value" }, - "value": 0, + "value": 22, "ccVersion": 4 }, { @@ -779,4 +779,4 @@ "ccVersion": 3 } ] -} \ No newline at end of file +} From 557e13e01065185ac46e52b8b2c632abb2c99c50 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 3 Feb 2021 13:59:19 +0100 Subject: [PATCH 0155/1818] Add support for climate setpoint thermostats to zwave_js (#45890) --- homeassistant/components/zwave_js/climate.py | 16 +- .../components/zwave_js/discovery.py | 14 + tests/components/zwave_js/conftest.py | 28 + tests/components/zwave_js/test_climate.py | 96 ++ .../zwave_js/climate_danfoss_lc_13_state.json | 368 +++++ .../zwave_js/climate_heatit_z_trm3_state.json | 1181 +++++++++++++++++ 6 files changed, 1699 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json create mode 100644 tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 417f5aa5e5dc8f..b125c8bcd6a4ac 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -5,6 +5,7 @@ from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( THERMOSTAT_CURRENT_TEMP_PROPERTY, + THERMOSTAT_MODE_PROPERTY, THERMOSTAT_MODE_SETPOINT_MAP, THERMOSTAT_MODES, THERMOSTAT_OPERATING_STATE_PROPERTY, @@ -119,7 +120,9 @@ def __init__( self._hvac_presets: Dict[str, Optional[int]] = {} self._unit_value: ZwaveValue = None - self._current_mode = self.info.primary_value + self._current_mode = self.get_zwave_value( + THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE + ) self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: self._setpoint_values[enum] = self.get_zwave_value( @@ -165,10 +168,12 @@ def _set_modes_and_presets(self) -> None: # Z-Wave uses one list for both modes and presets. # Iterate over all Z-Wave ThermostatModes and extract the hvac modes and presets. - current_mode = self._current_mode - if not current_mode: + if self._current_mode is None: + self._hvac_modes = { + ZW_HVAC_MODE_MAP[ThermostatMode.HEAT]: ThermostatMode.HEAT + } return - for mode_id, mode_name in current_mode.metadata.states.items(): + for mode_id, mode_name in self._current_mode.metadata.states.items(): mode_id = int(mode_id) if mode_id in THERMOSTAT_MODES: # treat value as hvac mode @@ -184,6 +189,9 @@ def _set_modes_and_presets(self) -> None: @property def _current_mode_setpoint_enums(self) -> List[Optional[ThermostatSetpointType]]: """Return the list of enums that are relevant to the current thermostat mode.""" + 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 @property diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 88717d9fc83be3..c22ed9fa3db031 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -56,6 +56,8 @@ class ZWaveDiscoverySchema: type: Optional[Set[str]] = None +# For device class mapping see: +# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json DISCOVERY_SCHEMAS = [ # locks ZWaveDiscoverySchema( @@ -105,6 +107,18 @@ class ZWaveDiscoverySchema: property={"mode"}, type={"number"}, ), + # climate + # setpoint thermostats + ZWaveDiscoverySchema( + platform="climate", + device_class_generic={"Thermostat"}, + device_class_specific={ + "Setpoint Thermostat", + }, + command_class={CommandClass.THERMOSTAT_SETPOINT}, + property={"setpoint"}, + type={"number"}, + ), # lights # primary value is the currentValue (brightness) ZWaveDiscoverySchema( diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 903de6d3bd5182..9cb950ba6e706c 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -128,6 +128,18 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_state_fixture(): ) +@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.""" + return json.loads(load_fixture("zwave_js/climate_danfoss_lc_13_state.json")) + + +@pytest.fixture(name="climate_heatit_z_trm3_state", scope="session") +def climate_heatit_z_trm3_state_fixture(): + """Load the climate HEATIT Z-TRM3 thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/climate_heatit_z_trm3_state.json")) + + @pytest.fixture(name="nortek_thermostat_state", scope="session") def nortek_thermostat_state_fixture(): """Load the nortek thermostat node state fixture data.""" @@ -254,6 +266,22 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( 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.""" + node = Node(client, climate_danfoss_lc_13_state) + client.driver.controller.nodes[node.node_id] = node + return node + + +@pytest.fixture(name="climate_heatit_z_trm3") +def climate_heatit_z_trm3_fixture(client, climate_heatit_z_trm3_state): + """Mock a climate radio HEATIT Z-TRM3 node.""" + node = Node(client, climate_heatit_z_trm3_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="nortek_thermostat") def nortek_thermostat_fixture(client, nortek_thermostat_state): """Mock a nortek thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index bede37e6959e31..b2455f3cbbd8f2 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -26,6 +26,8 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat_thermostat_mode" +CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat_heating" +CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat_thermostat_mode" async def test_thermostat_v2( @@ -335,3 +337,97 @@ async def test_thermostat_different_endpoints( state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY) assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 + + +async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integration): + """Test a setpoint thermostat command class entity.""" + node = climate_danfoss_lc_13 + state = hass.states.get(CLIMATE_DANFOSS_LC13_ENTITY) + + assert state + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 25 + assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + client.async_send_command.reset_mock() + + # Test setting temperature + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: CLIMATE_DANFOSS_LC13_ENTITY, + ATTR_TEMPERATURE: 21.5, + }, + 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"] == 5 + assert args["valueId"] == { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "unit": "\u00b0C", + "ccSpecific": {"setpointType": 1}, + }, + "value": 25, + } + assert args["value"] == 21.5 + + client.async_send_command.reset_mock() + + # Test setpoint mode update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 5, + "args": { + "commandClassName": "Thermostat Setpoint", + "commandClass": 67, + "endpoint": 0, + "property": "setpoint", + "propertyKey": 1, + "propertyKeyName": "Heating", + "propertyName": "setpoint", + "newValue": 23, + "prevValue": 21.5, + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(CLIMATE_DANFOSS_LC13_ENTITY) + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 23 + + client.async_send_command.reset_mock() + + +async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integration): + """Test a thermostat v2 command class entity.""" + state = hass.states.get(CLIMATE_FLOOR_THERMOSTAT_ENTITY) + + assert state + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + ] + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.9 + assert state.attributes[ATTR_TEMPERATURE] == 22.5 + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE diff --git a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json new file mode 100644 index 00000000000000..e218d3b6a0e55d --- /dev/null +++ b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json @@ -0,0 +1,368 @@ +{ + "nodeId": 5, + "index": 0, + "status": 1, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Setpoint Thermostat", + "mandatorySupportedCCs": [ + "Manufacturer Specific", + "Multi Command", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": false, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 2, + "productId": 4, + "productType": 5, + "firmwareVersion": "1.1", + "deviceConfig": { + "manufacturerId": 2, + "manufacturer": "Danfoss", + "label": "LC-13", + "description": "Living Connect Z Thermostat", + "devices": [ + { + "productType": "0x0005", + "productId": "0x0004" + }, + { + "productType": "0x8005", + "productId": "0x0001" + }, + { + "productType": "0x8005", + "productId": "0x0002" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "compat": { + "valueIdRegex": {}, + "queryOnWakeup": [ + [ + "Battery", + "get" + ], + [ + "Thermostat Setpoint", + "get", + 1 + ] + ] + } + }, + "label": "LC-13", + "neighbors": [ + 1, + 14 + ], + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 5, + "index": 0 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "unit": "\u00b0C", + "ccSpecific": { + "setpointType": 1 + } + }, + "value": 25 + }, + { + "endpoint": 0, + "commandClass": 70, + "commandClassName": "Climate Control Schedule", + "property": "changeCounter", + "propertyName": "changeCounter", + "ccVersion": 0, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 70, + "commandClassName": "Climate Control Schedule", + "property": "overrideType", + "propertyName": "overrideType", + "ccVersion": 0, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 70, + "commandClassName": "Climate Control Schedule", + "property": "overrideState", + "propertyName": "overrideState", + "ccVersion": 0, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "Unused" + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "rf", + "propertyName": "rf", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "RF protection state", + "states": {} + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "exclusiveControlNodeId", + "propertyName": "exclusiveControlNodeId", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "timeout", + "propertyName": "timeout", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 53 + }, + { + "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": 132, + "commandClassName": "Wake Up", + "property": "wakeUpInterval", + "propertyName": "wakeUpInterval", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "min": 60, + "max": 1800, + "label": "Wake Up interval", + "steps": 60, + "default": 300 + }, + "value": 300 + }, + { + "endpoint": 0, + "commandClass": 132, + "commandClassName": "Wake Up", + "property": "controllerNodeId", + "propertyName": "controllerNodeId", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Node ID of the controller" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "3.67" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.1" + ] + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json new file mode 100644 index 00000000000000..066811c7374b75 --- /dev/null +++ b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json @@ -0,0 +1,1181 @@ +{ + "nodeId": 24, + "index": 0, + "installerIcon": 4608, + "userIcon": 4609, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Thermostat General V2", + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 411, + "productId": 515, + "productType": 3, + "firmwareVersion": "4.0", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 411, + "manufacturer": "ThermoFloor", + "label": "Heatit Z-TRM3", + "description": "Floor thermostat", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0203" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + }, + "compat": { + "valueIdRegex": {}, + "overrideFloatEncoding": { + "size": 2 + }, + "addCCs": {} + } + }, + "label": "Heatit Z-TRM3", + "neighbors": [ + 1, + 2, + 3, + 4, + 6, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 17, + 18, + 19, + 25, + 26, + 28 + ], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 4, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 24, + "index": 0, + "installerIcon": 4608, + "userIcon": 4609 + }, + { + "nodeId": 24, + "index": 1, + "installerIcon": 4608, + "userIcon": 4609 + }, + { + "nodeId": 24, + "index": 2, + "installerIcon": 3328, + "userIcon": 3329 + }, + { + "nodeId": 24, + "index": 3, + "installerIcon": 3328, + "userIcon": 3329 + }, + { + "nodeId": 24, + "index": 4, + "installerIcon": 3328, + "userIcon": 3329 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "param001", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "Sensor mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 4, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "F-mode, floor sensor mode", + "1": "A-mode, internal room sensor mode", + "2": "AF-mode, internal sensor and floor sensor mode", + "3": "A2-mode, external room sensor mode", + "4": "A2F-mode, external sensor with floor limitation" + }, + "label": "Sensor mode", + "description": "Sensor mode", + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Floor sensor type", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 5, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "10K-NTC", + "1": "12K-NTC", + "2": "15K-NTC", + "3": "22K-NTC", + "4": "33K-NTC", + "5": "47K-NTC" + }, + "label": "Floor sensor type", + "description": "Floor sensor type", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Temperature control hysteresis (DIFF I)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 3, + "max": 30, + "default": 5, + "format": 0, + "allowManualEntry": true, + "label": "Temperature control hysteresis (DIFF I)", + "description": "Temperature control hysteresis (DIFF I), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Floor minimum temperature limit (FLo)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Floor minimum temperature limit (FLo)", + "description": "Floor minimum temperature limit (FLo), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Floor maximum temperature (FHi)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 400, + "format": 0, + "allowManualEntry": true, + "label": "Floor maximum temperature (FHi)", + "description": "Floor maximum temperature (FHi), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 400 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Air minimum temperature limit (ALo)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Air minimum temperature limit (ALo)", + "description": "Air minimum temperature limit (ALo), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Air maximum temperature limit (AHi)", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 50, + "max": 400, + "default": 400, + "format": 0, + "allowManualEntry": true, + "label": "Air maximum temperature limit (AHi)", + "description": "Air maximum temperature limit (AHi), 1 equals 0.1 \u00b0C", + "isFromConfig": true + }, + "value": 400 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 9, + "propertyName": "param009", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 225 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 10, + "propertyName": "Room sensor calibration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -60, + "max": 60, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Room sensor calibration", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 11, + "propertyName": "Floor sensor calibration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -60, + "max": 60, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Floor sensor calibration", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 12, + "propertyName": "External sensor calibration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -60, + "max": 60, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "External sensor calibration", + "isFromConfig": true + }, + "value": -42 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 13, + "propertyName": "Temperature display", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Display setpoint temperature", + "1": "Display calculated temperature" + }, + "label": "Temperature display", + "description": "Selects which temperature is shown on the display.", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 14, + "propertyName": "Button brightness - dimmed state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Button brightness - dimmed state", + "description": "Button brightness - dimmed state", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 15, + "propertyName": "Button brightness - active state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 100, + "format": 0, + "allowManualEntry": true, + "label": "Button brightness - active state", + "description": "Button brightness - active state", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 16, + "propertyName": "Display brightness - dimmed state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Display brightness - dimmed state", + "description": "Display brightness - dimmed state", + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 17, + "propertyName": "Display brightness - active state", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 100, + "format": 0, + "allowManualEntry": true, + "label": "Display brightness - active state", + "description": "Display brightness - active state", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 18, + "propertyName": "Temperature report interval", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 60, + "format": 0, + "allowManualEntry": true, + "label": "Temperature report interval", + "description": "Temperature report interval", + "isFromConfig": true + }, + "value": 360 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 19, + "propertyName": "Temperature report hysteresis", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 100, + "default": 10, + "format": 0, + "allowManualEntry": true, + "label": "Temperature report hysteresis", + "description": "Temperature report hysteresis", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 20, + "propertyName": "Meter report interval", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 90, + "format": 0, + "allowManualEntry": true, + "label": "Meter report interval", + "description": "Meter report interval", + "isFromConfig": true + }, + "value": 3600 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 21, + "propertyName": "Meter report delta value", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 10, + "format": 1, + "allowManualEntry": true, + "label": "Meter report delta value", + "description": "Meter report delta value", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 411 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 515 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "6.7" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "4.0" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "sdkVersion", + "propertyName": "sdkVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "6.81.6" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkAPIVersion", + "propertyName": "applicationFrameworkAPIVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "4.3.0" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkBuildNumber", + "propertyName": "applicationFrameworkBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 52445 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceVersion", + "propertyName": "hostInterfaceVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "unused" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceBuildNumber", + "propertyName": "hostInterfaceBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolVersion", + "propertyName": "zWaveProtocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "6.7.0" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolBuildNumber", + "propertyName": "zWaveProtocolBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 97 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationVersion", + "propertyName": "applicationVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "4.0.33" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationBuildNumber", + "propertyName": "applicationBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 52445 + }, + { + "endpoint": 1, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 5, + "max": 35, + "unit": "\u00b0C", + "ccSpecific": { + "setpointType": 1 + } + }, + "value": 22.5 + }, + { + "endpoint": 1, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat" + } + }, + "value": 1 + }, + { + "endpoint": 1, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 1, + "commandClass": 66, + "commandClassName": "Thermostat Operating State", + "property": "state", + "propertyName": "state", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Operating state", + "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": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyName": "value", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Value (Electric, Consumed)", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 369.2 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyName": "deltaTime", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Time since the previous reading", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyName": "value", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Value (Electric, Consumed)", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0.09 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyName": "deltaTime", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Time since the previous reading", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyName": "value", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Value (Electric, Consumed)", + "unit": "V", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 4 + } + }, + "value": 238 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyName": "deltaTime", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Time since the previous reading", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 4 + } + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyName": "previousValue", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Previous value (Electric, Consumed)", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + } + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyName": "previousValue", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Previous value (Electric, Consumed)", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + } + }, + { + "endpoint": 1, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyName": "previousValue", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Previous value (Electric, Consumed)", + "unit": "V", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 4 + } + } + }, + { + "endpoint": 2, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 22.9 + }, + { + "endpoint": 3, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 0 + }, + { + "endpoint": 4, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 25.5 + } + ] +} \ No newline at end of file From 878740d53f96b5a20babca5af49d9438d44540f5 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 3 Feb 2021 13:08:00 +0100 Subject: [PATCH 0156/1818] Update discovery scheme for Meter CC in zwave_js integration (#45897) --- homeassistant/components/zwave_js/discovery.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index c22ed9fa3db031..d741946a1c9a7b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -169,13 +169,22 @@ class ZWaveDiscoverySchema: hint="numeric_sensor", command_class={ CommandClass.SENSOR_MULTILEVEL, - CommandClass.METER, CommandClass.SENSOR_ALARM, CommandClass.INDICATOR, CommandClass.BATTERY, }, type={"number"}, ), + # numeric sensors for Meter CC + ZWaveDiscoverySchema( + platform="sensor", + hint="numeric_sensor", + command_class={ + CommandClass.METER, + }, + type={"number"}, + property={"value"}, + ), # special list sensors (Notification CC) ZWaveDiscoverySchema( platform="sensor", From 92365b65e96ecba46b093e0fc6128898087f3130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 3 Feb 2021 14:40:11 +0100 Subject: [PATCH 0157/1818] Add integration name to the deprecation warnings (#45901) --- homeassistant/helpers/deprecation.py | 30 +++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 0022f8888296e7..7478a7fede98ed 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -4,6 +4,8 @@ import logging from typing import Any, Callable, Dict, Optional +from ..helpers.frame import MissingIntegrationFrame, get_integration_frame + def deprecated_substitute(substitute_name: str) -> Callable[..., Callable]: """Help migrate properties to new names. @@ -86,11 +88,29 @@ def deprecated_decorator(func: Callable) -> Callable: def deprecated_func(*args: tuple, **kwargs: Dict[str, Any]) -> Any: """Wrap for the original function.""" logger = logging.getLogger(func.__module__) - logger.warning( - "%s is a deprecated function. Use %s instead", - func.__name__, - replacement, - ) + try: + _, integration, path = get_integration_frame() + if path == "custom_components/": + logger.warning( + "%s was called from %s, this is a deprecated function. Use %s instead, please report this to the maintainer of %s", + func.__name__, + integration, + replacement, + integration, + ) + else: + logger.warning( + "%s was called from %s, this is a deprecated function. Use %s instead", + func.__name__, + integration, + replacement, + ) + except MissingIntegrationFrame: + logger.warning( + "%s is a deprecated function. Use %s instead", + func.__name__, + replacement, + ) return func(*args, **kwargs) return deprecated_func From f8426f62a94a282c3fa9e53d8981c83c57dbc1b8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Feb 2021 15:27:56 +0100 Subject: [PATCH 0158/1818] Bumped version to 2021.2.0b5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 60e9e745f6c745..bab77c63a94d09 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From fcc14933d0526c3da5cb59053224368ebefcd4f1 Mon Sep 17 00:00:00 2001 From: Keith Lamprecht <1894492+Nixon506E@users.noreply.github.com> Date: Wed, 3 Feb 2021 09:42:52 -0500 Subject: [PATCH 0159/1818] Add transitiontime to hue scene service (#45785) --- homeassistant/components/hue/bridge.py | 15 +++++++++-- homeassistant/components/hue/const.py | 2 ++ tests/components/hue/test_bridge.py | 35 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 880d9abcc350c5..201e9f3a546382 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -19,6 +19,7 @@ CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_UNREACHABLE, + DEFAULT_SCENE_TRANSITION, LOGGER, ) from .errors import AuthenticationRequired, CannotConnect @@ -28,8 +29,15 @@ SERVICE_HUE_SCENE = "hue_activate_scene" ATTR_GROUP_NAME = "group_name" ATTR_SCENE_NAME = "scene_name" +ATTR_TRANSITION = "transition" SCENE_SCHEMA = vol.Schema( - {vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string} + { + vol.Required(ATTR_GROUP_NAME): cv.string, + vol.Required(ATTR_SCENE_NAME): cv.string, + vol.Optional( + ATTR_TRANSITION, default=DEFAULT_SCENE_TRANSITION + ): cv.positive_int, + } ) # How long should we sleep if the hub is busy HUB_BUSY_SLEEP = 0.5 @@ -201,6 +209,7 @@ async def hue_activate_scene(self, call, updated=False, hide_warnings=False): """Service to call directly into bridge to set scenes.""" group_name = call.data[ATTR_GROUP_NAME] scene_name = call.data[ATTR_SCENE_NAME] + transition = call.data.get(ATTR_TRANSITION, DEFAULT_SCENE_TRANSITION) group = next( (group for group in self.api.groups.values() if group.name == group_name), @@ -236,7 +245,9 @@ async def hue_activate_scene(self, call, updated=False, hide_warnings=False): LOGGER.warning("Unable to find scene %s", scene_name) return False - return await self.async_request_call(partial(group.set_action, scene=scene.id)) + return await self.async_request_call( + partial(group.set_action, scene=scene.id, transitiontime=transition) + ) async def handle_unauthorized_error(self): """Create a new config flow when the authorization is no longer valid.""" diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index e2189515482819..4fa11f2ad584a9 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -13,3 +13,5 @@ CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" DEFAULT_ALLOW_HUE_GROUPS = True + +DEFAULT_SCENE_TRANSITION = 4 diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 3e6465d6bc8a42..29bc2acf03aea6 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -192,6 +192,41 @@ async def test_hue_activate_scene(hass, mock_api): assert mock_api.mock_requests[2]["path"] == "groups/group_1/action" +async def test_hue_activate_scene_transition(hass, mock_api): + """Test successful hue_activate_scene with transition.""" + config_entry = config_entries.ConfigEntry( + 1, + hue.DOMAIN, + "Mock Title", + {"host": "mock-host", "username": "mock-username"}, + "test", + config_entries.CONN_CLASS_LOCAL_POLL, + system_options={}, + options={CONF_ALLOW_HUE_GROUPS: True, CONF_ALLOW_UNREACHABLE: False}, + ) + hue_bridge = bridge.HueBridge(hass, config_entry) + + mock_api.mock_group_responses.append(GROUP_RESPONSE) + mock_api.mock_scene_responses.append(SCENE_RESPONSE) + + with patch("aiohue.Bridge", return_value=mock_api), patch.object( + hass.config_entries, "async_forward_entry_setup" + ): + assert await hue_bridge.async_setup() is True + + assert hue_bridge.api is mock_api + + call = Mock() + call.data = {"group_name": "Group 1", "scene_name": "Cozy dinner", "transition": 30} + with patch("aiohue.Bridge", return_value=mock_api): + assert await hue_bridge.hue_activate_scene(call) is None + + assert len(mock_api.mock_requests) == 3 + assert mock_api.mock_requests[2]["json"]["scene"] == "scene_1" + assert mock_api.mock_requests[2]["json"]["transitiontime"] == 30 + assert mock_api.mock_requests[2]["path"] == "groups/group_1/action" + + async def test_hue_activate_scene_group_not_found(hass, mock_api): """Test failed hue_activate_scene due to missing group.""" config_entry = config_entries.ConfigEntry( From 0875f654c800492fd093c7be6b006a8ffd972c2b Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Wed, 3 Feb 2021 18:03:22 +0200 Subject: [PATCH 0160/1818] Add support for Shelly battery operated devices (#45406) Co-authored-by: Paulus Schoutsen --- homeassistant/components/shelly/__init__.py | 178 +++++++++++------- .../components/shelly/binary_sensor.py | 42 ++++- .../components/shelly/config_flow.py | 23 ++- homeassistant/components/shelly/const.py | 5 +- homeassistant/components/shelly/entity.py | 135 ++++++++++++- homeassistant/components/shelly/manifest.json | 2 +- homeassistant/components/shelly/sensor.py | 30 ++- homeassistant/components/shelly/utils.py | 32 +++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/shelly/conftest.py | 7 +- tests/components/shelly/test_config_flow.py | 13 +- 12 files changed, 366 insertions(+), 105 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index d2df03b44a55e8..84bc73f3c0f315 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -17,12 +17,7 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import ( - aiohttp_client, - device_registry, - singleton, - update_coordinator, -) +from homeassistant.helpers import aiohttp_client, device_registry, update_coordinator from .const import ( AIOSHELLY_DEVICE_TIMEOUT_SEC, @@ -32,36 +27,23 @@ BATTERY_DEVICES_WITH_PERMANENT_CONNECTION, COAP, DATA_CONFIG_ENTRY, + DEVICE, DOMAIN, EVENT_SHELLY_CLICK, INPUTS_EVENTS_DICT, - POLLING_TIMEOUT_MULTIPLIER, + POLLING_TIMEOUT_SEC, REST, REST_SENSORS_UPDATE_INTERVAL, SLEEP_PERIOD_MULTIPLIER, UPDATE_PERIOD_MULTIPLIER, ) -from .utils import get_device_name +from .utils import get_coap_context, get_device_name, get_device_sleep_period PLATFORMS = ["binary_sensor", "cover", "light", "sensor", "switch"] +SLEEPING_PLATFORMS = ["binary_sensor", "sensor"] _LOGGER = logging.getLogger(__name__) -@singleton.singleton("shelly_coap") -async def get_coap_context(hass): - """Get CoAP context to be used in all Shelly devices.""" - context = aioshelly.COAP() - await context.initialize() - - @callback - def shutdown_listener(ev): - context.close() - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener) - - return context - - async def async_setup(hass: HomeAssistant, config: dict): """Set up the Shelly component.""" hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}} @@ -70,6 +52,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Shelly from a config entry.""" + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {} + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None + temperature_unit = "C" if hass.config.units.is_metric else "F" ip_address = await hass.async_add_executor_job(gethostbyname, entry.data[CONF_HOST]) @@ -83,33 +68,79 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): coap_context = await get_coap_context(hass) - try: - async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC): - device = await aioshelly.Device.create( - aiohttp_client.async_get_clientsession(hass), - coap_context, - options, - ) - except (asyncio.TimeoutError, OSError) as err: - raise ConfigEntryNotReady from err + device = await aioshelly.Device.create( + aiohttp_client.async_get_clientsession(hass), + coap_context, + options, + False, + ) - hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {} - coap_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][ + dev_reg = await device_registry.async_get_registry(hass) + identifier = (DOMAIN, entry.unique_id) + device_entry = dev_reg.async_get_device(identifiers={identifier}, connections=set()) + + sleep_period = entry.data.get("sleep_period") + + @callback + def _async_device_online(_): + _LOGGER.debug("Device %s is online, resuming setup", entry.title) + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None + + if sleep_period is None: + data = {**entry.data} + data["sleep_period"] = get_device_sleep_period(device.settings) + data["model"] = device.settings["device"]["type"] + hass.config_entries.async_update_entry(entry, data=data) + + hass.async_create_task(async_device_setup(hass, entry, device)) + + if sleep_period == 0: + # Not a sleeping device, finish setup + _LOGGER.debug("Setting up online device %s", entry.title) + try: + async with async_timeout.timeout(AIOSHELLY_DEVICE_TIMEOUT_SEC): + await device.initialize(True) + except (asyncio.TimeoutError, OSError) as err: + raise ConfigEntryNotReady from err + + await async_device_setup(hass, entry, device) + elif sleep_period is None or device_entry is None: + # Need to get sleep info or first time sleeping device setup, wait for device + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = device + _LOGGER.debug( + "Setup for device %s will resume when device is online", entry.title + ) + device.subscribe_updates(_async_device_online) + else: + # Restore sensors for sleeping device + _LOGGER.debug("Setting up offline device %s", entry.title) + await async_device_setup(hass, entry, device) + + return True + + +async def async_device_setup( + hass: HomeAssistant, entry: ConfigEntry, device: aioshelly.Device +): + """Set up a device that is online.""" + device_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][ COAP ] = ShellyDeviceWrapper(hass, entry, device) - await coap_wrapper.async_setup() + await device_wrapper.async_setup() + + platforms = SLEEPING_PLATFORMS - hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][ - REST - ] = ShellyDeviceRestWrapper(hass, device) + if not entry.data.get("sleep_period"): + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][ + REST + ] = ShellyDeviceRestWrapper(hass, device) + platforms = PLATFORMS - for component in PLATFORMS: + for component in platforms: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) - return True - class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator): """Wrapper for a Shelly device with Home Assistant specific functions.""" @@ -117,43 +148,40 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator): def __init__(self, hass, entry, device: aioshelly.Device): """Initialize the Shelly device wrapper.""" self.device_id = None - sleep_mode = device.settings.get("sleep_mode") - - if sleep_mode: - sleep_period = sleep_mode["period"] - if sleep_mode["unit"] == "h": - sleep_period *= 60 # hours to minutes + sleep_period = entry.data["sleep_period"] - update_interval = ( - SLEEP_PERIOD_MULTIPLIER * sleep_period * 60 - ) # minutes to seconds + if sleep_period: + update_interval = SLEEP_PERIOD_MULTIPLIER * sleep_period else: update_interval = ( UPDATE_PERIOD_MULTIPLIER * device.settings["coiot"]["update_period"] ) + device_name = get_device_name(device) if device.initialized else entry.title super().__init__( hass, _LOGGER, - name=get_device_name(device), + name=device_name, update_interval=timedelta(seconds=update_interval), ) self.hass = hass self.entry = entry self.device = device - self.device.subscribe_updates(self.async_set_updated_data) - - self._async_remove_input_events_handler = self.async_add_listener( - self._async_input_events_handler + self._async_remove_device_updates_handler = self.async_add_listener( + self._async_device_updates_handler ) - self._last_input_events_count = dict() + self._last_input_events_count = {} hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop) @callback - def _async_input_events_handler(self): - """Handle device input events.""" + def _async_device_updates_handler(self): + """Handle device updates.""" + if not self.device.initialized: + return + + # Check for input events for block in self.device.blocks: if ( "inputEvent" not in block.sensor_ids @@ -192,13 +220,9 @@ def _async_input_events_handler(self): async def _async_update_data(self): """Fetch data.""" - _LOGGER.debug("Polling Shelly Device - %s", self.name) try: - async with async_timeout.timeout( - POLLING_TIMEOUT_MULTIPLIER - * self.device.settings["coiot"]["update_period"] - ): + async with async_timeout.timeout(POLLING_TIMEOUT_SEC): return await self.device.update() except OSError as err: raise update_coordinator.UpdateFailed("Error fetching data") from err @@ -206,18 +230,17 @@ async def _async_update_data(self): @property def model(self): """Model of the device.""" - return self.device.settings["device"]["type"] + return self.entry.data["model"] @property def mac(self): """Mac address of the device.""" - return self.device.settings["device"]["mac"] + return self.entry.unique_id async def async_setup(self): """Set up the wrapper.""" - dev_reg = await device_registry.async_get_registry(self.hass) - model_type = self.device.settings["device"]["type"] + sw_version = self.device.settings["fw"] if self.device.initialized else "" entry = dev_reg.async_get_or_create( config_entry_id=self.entry.entry_id, name=self.name, @@ -225,15 +248,16 @@ async def async_setup(self): # This is duplicate but otherwise via_device can't work identifiers={(DOMAIN, self.mac)}, manufacturer="Shelly", - model=aioshelly.MODEL_NAMES.get(model_type, model_type), - sw_version=self.device.settings["fw"], + model=aioshelly.MODEL_NAMES.get(self.model, self.model), + sw_version=sw_version, ) self.device_id = entry.id + self.device.subscribe_updates(self.async_set_updated_data) def shutdown(self): """Shutdown the wrapper.""" self.device.shutdown() - self._async_remove_input_events_handler() + self._async_remove_device_updates_handler() @callback def _handle_ha_stop(self, _): @@ -282,11 +306,23 @@ def mac(self): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + device = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id].get(DEVICE) + if device is not None: + # If device is present, device wrapper is not setup yet + device.shutdown() + return True + + platforms = SLEEPING_PLATFORMS + + if not entry.data.get("sleep_period"): + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][REST] = None + platforms = PLATFORMS + unload_ok = all( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + for component in platforms ] ) ) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index d53f089054a20a..8f99e6a7a6e0ed 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -9,6 +9,7 @@ DEVICE_CLASS_PROBLEM, DEVICE_CLASS_SMOKE, DEVICE_CLASS_VIBRATION, + STATE_ON, BinarySensorEntity, ) @@ -17,6 +18,7 @@ RestAttributeDescription, ShellyBlockAttributeEntity, ShellyRestAttributeEntity, + ShellySleepingBlockAttributeEntity, async_setup_entry_attribute_entities, async_setup_entry_rest, ) @@ -98,13 +100,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up sensors for device.""" - await async_setup_entry_attribute_entities( - hass, config_entry, async_add_entities, SENSORS, ShellyBinarySensor - ) - - await async_setup_entry_rest( - hass, config_entry, async_add_entities, REST_SENSORS, ShellyRestBinarySensor - ) + if config_entry.data["sleep_period"]: + await async_setup_entry_attribute_entities( + hass, + config_entry, + async_add_entities, + SENSORS, + ShellySleepingBinarySensor, + ) + else: + await async_setup_entry_attribute_entities( + hass, config_entry, async_add_entities, SENSORS, ShellyBinarySensor + ) + await async_setup_entry_rest( + hass, + config_entry, + async_add_entities, + REST_SENSORS, + ShellyRestBinarySensor, + ) class ShellyBinarySensor(ShellyBlockAttributeEntity, BinarySensorEntity): @@ -123,3 +137,17 @@ class ShellyRestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity): def is_on(self): """Return true if REST sensor state is on.""" return bool(self.attribute_value) + + +class ShellySleepingBinarySensor( + ShellySleepingBlockAttributeEntity, BinarySensorEntity +): + """Represent a shelly sleeping binary sensor.""" + + @property + def is_on(self): + """Return true if sensor state is on.""" + if self.block is not None: + return bool(self.attribute_value) + + return self.last_state == STATE_ON diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index b47c76cbb7a2cc..09fc477e512ec7 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -17,9 +17,9 @@ ) from homeassistant.helpers import aiohttp_client -from . import get_coap_context from .const import AIOSHELLY_DEVICE_TIMEOUT_SEC from .const import DOMAIN # pylint:disable=unused-import +from .utils import get_coap_context, get_device_sleep_period _LOGGER = logging.getLogger(__name__) @@ -53,6 +53,8 @@ async def validate_input(hass: core.HomeAssistant, host, data): return { "title": device.settings["name"], "hostname": device.settings["device"]["hostname"], + "sleep_period": get_device_sleep_period(device.settings), + "model": device.settings["device"]["type"], } @@ -95,7 +97,11 @@ async def async_step_user(self, user_input=None): else: return self.async_create_entry( title=device_info["title"] or device_info["hostname"], - data=user_input, + data={ + **user_input, + "sleep_period": device_info["sleep_period"], + "model": device_info["model"], + }, ) return self.async_show_form( @@ -121,7 +127,12 @@ async def async_step_credentials(self, user_input=None): else: return self.async_create_entry( title=device_info["title"] or device_info["hostname"], - data={**user_input, CONF_HOST: self.host}, + data={ + **user_input, + CONF_HOST: self.host, + "sleep_period": device_info["sleep_period"], + "model": device_info["model"], + }, ) else: user_input = {} @@ -172,7 +183,11 @@ async def async_step_confirm_discovery(self, user_input=None): else: return self.async_create_entry( title=device_info["title"] or device_info["hostname"], - data={"host": self.host}, + data={ + "host": self.host, + "sleep_period": device_info["sleep_period"], + "model": device_info["model"], + }, ) return self.async_show_form( diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index a5922d0b9c0071..9d1c333b2017a6 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -2,11 +2,12 @@ COAP = "coap" DATA_CONFIG_ENTRY = "config_entry" +DEVICE = "device" DOMAIN = "shelly" REST = "rest" -# Used to calculate the timeout in "_async_update_data" used for polling data from devices. -POLLING_TIMEOUT_MULTIPLIER = 1.2 +# Used in "_async_update_data" as timeout for polling data from devices. +POLLING_TIMEOUT_SEC = 18 # Refresh interval for REST sensors REST_SENSORS_UPDATE_INTERVAL = 60 diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index b4df2d486f8976..b934a41728febe 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -1,24 +1,49 @@ """Shelly entity helper.""" from dataclasses import dataclass +import logging from typing import Any, Callable, Optional, Union import aioshelly +from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback -from homeassistant.helpers import device_registry, entity, update_coordinator +from homeassistant.helpers import ( + device_registry, + entity, + entity_registry, + update_coordinator, +) +from homeassistant.helpers.restore_state import RestoreEntity from . import ShellyDeviceRestWrapper, ShellyDeviceWrapper from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN, REST from .utils import async_remove_shelly_entity, get_entity_name +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, sensors, sensor_class ): - """Set up entities for block attributes.""" + """Set up entities for attributes.""" wrapper: ShellyDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][ config_entry.entry_id ][COAP] + + if wrapper.device.initialized: + await async_setup_block_attribute_entities( + hass, async_add_entities, wrapper, sensors, sensor_class + ) + else: + await async_restore_block_attribute_entities( + hass, config_entry, async_add_entities, wrapper, sensor_class + ) + + +async def async_setup_block_attribute_entities( + hass, async_add_entities, wrapper, sensors, sensor_class +): + """Set up entities for block attributes.""" blocks = [] for block in wrapper.device.blocks: @@ -36,9 +61,7 @@ async def async_setup_entry_attribute_entities( wrapper.device.settings, block ): domain = sensor_class.__module__.split(".")[-1] - unique_id = sensor_class( - wrapper, block, sensor_id, description - ).unique_id + unique_id = f"{wrapper.mac}-{block.description}-{sensor_id}" await async_remove_shelly_entity(hass, domain, unique_id) else: blocks.append((block, sensor_id, description)) @@ -54,6 +77,39 @@ async def async_setup_entry_attribute_entities( ) +async def async_restore_block_attribute_entities( + hass, config_entry, async_add_entities, wrapper, sensor_class +): + """Restore block attributes entities.""" + entities = [] + + ent_reg = await entity_registry.async_get_registry(hass) + entries = entity_registry.async_entries_for_config_entry( + ent_reg, config_entry.entry_id + ) + + domain = sensor_class.__module__.split(".")[-1] + + for entry in entries: + if entry.domain != domain: + continue + + attribute = entry.unique_id.split("-")[-1] + description = BlockAttributeDescription( + name="", + icon=entry.original_icon, + unit=entry.unit_of_measurement, + device_class=entry.device_class, + ) + + entities.append(sensor_class(wrapper, None, attribute, description, entry)) + + if not entities: + return + + async_add_entities(entities) + + async def async_setup_entry_rest( hass, config_entry, async_add_entities, sensors, sensor_class ): @@ -163,7 +219,7 @@ def _update_callback(self): class ShellyBlockAttributeEntity(ShellyBlockEntity, entity.Entity): - """Switch that controls a relay block on Shelly devices.""" + """Helper class to represent a block attribute.""" def __init__( self, @@ -176,12 +232,11 @@ def __init__( super().__init__(wrapper, block) self.attribute = attribute self.description = description - self.info = block.info(attribute) unit = self.description.unit if callable(unit): - unit = unit(self.info) + unit = unit(block.info(attribute)) self._unit = unit self._unique_id = f"{super().unique_id}-{self.attribute}" @@ -320,3 +375,67 @@ def device_state_attributes(self) -> dict: return None return self.description.device_state_attributes(self.wrapper.device.status) + + +class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEntity): + """Represent a shelly sleeping block attribute entity.""" + + # pylint: disable=super-init-not-called + def __init__( + self, + wrapper: ShellyDeviceWrapper, + block: aioshelly.Block, + attribute: str, + description: BlockAttributeDescription, + entry: Optional[ConfigEntry] = None, + ) -> None: + """Initialize the sleeping sensor.""" + self.last_state = None + self.wrapper = wrapper + self.attribute = attribute + self.block = block + self.description = description + self._unit = self.description.unit + + if block is not None: + if callable(self._unit): + self._unit = self._unit(block.info(attribute)) + + self._unique_id = f"{self.wrapper.mac}-{block.description}-{attribute}" + self._name = get_entity_name( + self.wrapper.device, block, self.description.name + ) + else: + self._unique_id = entry.unique_id + self._name = entry.original_name + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + await super().async_added_to_hass() + + last_state = await self.async_get_last_state() + + if last_state is not None: + self.last_state = last_state.state + + @callback + def _update_callback(self): + """Handle device update.""" + if self.block is not None: + super()._update_callback() + return + + _, entity_block, entity_sensor = self.unique_id.split("-") + + for block in self.wrapper.device.blocks: + if block.description != entity_block: + continue + + for sensor_id in block.sensor_ids: + if sensor_id != entity_sensor: + continue + + self.block = block + _LOGGER.debug("Entity %s attached to block", self.name) + super()._update_callback() + return diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 923bcdced34e02..fffa98b6870d2b 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==0.5.3"], + "requirements": ["aioshelly==0.5.4"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index b92b90c1b46a41..36656740b92f8d 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -18,6 +18,7 @@ RestAttributeDescription, ShellyBlockAttributeEntity, ShellyRestAttributeEntity, + ShellySleepingBlockAttributeEntity, async_setup_entry_attribute_entities, async_setup_entry_rest, ) @@ -185,12 +186,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up sensors for device.""" - await async_setup_entry_attribute_entities( - hass, config_entry, async_add_entities, SENSORS, ShellySensor - ) - await async_setup_entry_rest( - hass, config_entry, async_add_entities, REST_SENSORS, ShellyRestSensor - ) + if config_entry.data["sleep_period"]: + await async_setup_entry_attribute_entities( + hass, config_entry, async_add_entities, SENSORS, ShellySleepingSensor + ) + else: + await async_setup_entry_attribute_entities( + hass, config_entry, async_add_entities, SENSORS, ShellySensor + ) + await async_setup_entry_rest( + hass, config_entry, async_add_entities, REST_SENSORS, ShellyRestSensor + ) class ShellySensor(ShellyBlockAttributeEntity): @@ -209,3 +215,15 @@ class ShellyRestSensor(ShellyRestAttributeEntity): def state(self): """Return value of sensor.""" return self.attribute_value + + +class ShellySleepingSensor(ShellySleepingBlockAttributeEntity): + """Represent a shelly sleeping sensor.""" + + @property + def state(self): + """Return value of sensor.""" + if self.block is not None: + return self.attribute_value + + return self.last_state diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 97d8bda609bc4a..b4148801b3575d 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -6,8 +6,9 @@ import aioshelly -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.core import HomeAssistant +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import singleton from homeassistant.util.dt import parse_datetime, utcnow from .const import ( @@ -182,3 +183,30 @@ def get_device_wrapper(hass: HomeAssistant, device_id: str): return wrapper return None + + +@singleton.singleton("shelly_coap") +async def get_coap_context(hass): + """Get CoAP context to be used in all Shelly devices.""" + context = aioshelly.COAP() + await context.initialize() + + @callback + def shutdown_listener(ev): + context.close() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener) + + return context + + +def get_device_sleep_period(settings: dict) -> int: + """Return the device sleep period in seconds or 0 for non sleeping devices.""" + sleep_period = 0 + + if settings.get("sleep_mode", False): + sleep_period = settings["sleep_mode"]["period"] + if settings["sleep_mode"]["unit"] == "h": + sleep_period *= 60 # hours to minutes + + return sleep_period * 60 # minutes to seconds diff --git a/requirements_all.txt b/requirements_all.txt index c25ea05aa33478..4315c577283db7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.3 +aioshelly==0.5.4 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b156395df1329b..d8c7d112fba51d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -137,7 +137,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.3 +aioshelly==0.5.4 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 804d5a75952bfb..7e7bd0688422df 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -91,7 +91,11 @@ async def coap_wrapper(hass): """Setups a coap wrapper with mocked device.""" await async_setup_component(hass, "shelly", {}) - config_entry = MockConfigEntry(domain=DOMAIN, data={}) + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"sleep_period": 0, "model": "SHSW-25"}, + unique_id="12345678", + ) config_entry.add_to_hass(hass) device = Mock( @@ -99,6 +103,7 @@ async def coap_wrapper(hass): settings=MOCK_SETTINGS, shelly=MOCK_SHELLY, update=AsyncMock(), + initialized=True, ) hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}} diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 60f899296f6022..1d5099cec1cd2a 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -13,7 +13,8 @@ MOCK_SETTINGS = { "name": "Test name", - "device": {"mac": "test-mac", "hostname": "test-host"}, + "device": {"mac": "test-mac", "hostname": "test-host", "type": "SHSW-1"}, + "sleep_period": 0, } DISCOVERY_INFO = { "host": "1.1.1.1", @@ -57,6 +58,8 @@ async def test_form(hass): assert result2["title"] == "Test name" assert result2["data"] == { "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 0, } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -101,6 +104,8 @@ async def test_title_without_name(hass): assert result2["title"] == "shelly1pm-12345" assert result2["data"] == { "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 0, } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -149,6 +154,8 @@ async def test_form_auth(hass): assert result3["title"] == "Test name" assert result3["data"] == { "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 0, "username": "test username", "password": "test password", } @@ -369,6 +376,8 @@ async def test_zeroconf(hass): assert result2["title"] == "Test name" assert result2["data"] == { "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 0, } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -502,6 +511,8 @@ async def test_zeroconf_require_auth(hass): assert result3["title"] == "Test name" assert result3["data"] == { "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 0, "username": "test username", "password": "test password", } From 6458ff774f22ade7904227d1b8d93bd4cd3fa09f Mon Sep 17 00:00:00 2001 From: badguy99 <61918526+badguy99@users.noreply.github.com> Date: Wed, 3 Feb 2021 16:05:20 +0000 Subject: [PATCH 0161/1818] Homeconnect remote states (#45610) Co-authored-by: Paulus Schoutsen --- homeassistant/components/home_connect/api.py | 143 +++++++++++++++--- .../components/home_connect/binary_sensor.py | 40 +++-- .../components/home_connect/const.py | 2 + .../components/home_connect/sensor.py | 9 +- 4 files changed, 157 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/home_connect/api.py b/homeassistant/components/home_connect/api.py index 8db8afa3a6bb5c..44669ed200fbac 100644 --- a/homeassistant/components/home_connect/api.py +++ b/homeassistant/components/home_connect/api.py @@ -13,6 +13,7 @@ from .const import ( BSH_ACTIVE_PROGRAM, + BSH_OPERATION_STATE, BSH_POWER_OFF, BSH_POWER_STANDBY, SIGNAL_UPDATE_ENTITIES, @@ -156,6 +157,25 @@ def get_program_sensors(self): ] +class DeviceWithOpState(HomeConnectDevice): + """Device that has an operation state sensor.""" + + def get_opstate_sensor(self): + """Get a list with info about operation state sensors.""" + + return [ + { + "device": self, + "desc": "Operation State", + "unit": None, + "key": BSH_OPERATION_STATE, + "icon": "mdi:state-machine", + "device_class": None, + "sign": 1, + } + ] + + class DeviceWithDoor(HomeConnectDevice): """Device that has a door sensor.""" @@ -164,6 +184,7 @@ def get_door_entity(self): return { "device": self, "desc": "Door", + "sensor_type": "door", "device_class": "door", } @@ -173,11 +194,7 @@ class DeviceWithLight(HomeConnectDevice): def get_light_entity(self): """Get a dictionary with info about the lighting.""" - return { - "device": self, - "desc": "Light", - "ambient": None, - } + return {"device": self, "desc": "Light", "ambient": None} class DeviceWithAmbientLight(HomeConnectDevice): @@ -185,14 +202,36 @@ class DeviceWithAmbientLight(HomeConnectDevice): def get_ambientlight_entity(self): """Get a dictionary with info about the ambient lighting.""" + return {"device": self, "desc": "AmbientLight", "ambient": True} + + +class DeviceWithRemoteControl(HomeConnectDevice): + """Device that has Remote Control binary sensor.""" + + def get_remote_control(self): + """Get a dictionary with info about the remote control sensor.""" return { "device": self, - "desc": "AmbientLight", - "ambient": True, + "desc": "Remote Control", + "sensor_type": "remote_control", } -class Dryer(DeviceWithDoor, DeviceWithPrograms): +class DeviceWithRemoteStart(HomeConnectDevice): + """Device that has a Remote Start binary sensor.""" + + def get_remote_start(self): + """Get a dictionary with info about the remote start sensor.""" + return {"device": self, "desc": "Remote Start", "sensor_type": "remote_start"} + + +class Dryer( + DeviceWithDoor, + DeviceWithOpState, + DeviceWithPrograms, + DeviceWithRemoteControl, + DeviceWithRemoteStart, +): """Dryer class.""" PROGRAMS = [ @@ -217,16 +256,26 @@ class Dryer(DeviceWithDoor, DeviceWithPrograms): def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() + remote_control = self.get_remote_control() + remote_start = self.get_remote_start() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() return { - "binary_sensor": [door_entity], + "binary_sensor": [door_entity, remote_control, remote_start], "switch": program_switches, - "sensor": program_sensors, + "sensor": program_sensors + op_state_sensor, } -class Dishwasher(DeviceWithDoor, DeviceWithAmbientLight, DeviceWithPrograms): +class Dishwasher( + DeviceWithDoor, + DeviceWithAmbientLight, + DeviceWithOpState, + DeviceWithPrograms, + DeviceWithRemoteControl, + DeviceWithRemoteStart, +): """Dishwasher class.""" PROGRAMS = [ @@ -257,16 +306,25 @@ class Dishwasher(DeviceWithDoor, DeviceWithAmbientLight, DeviceWithPrograms): def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() + remote_control = self.get_remote_control() + remote_start = self.get_remote_start() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() return { - "binary_sensor": [door_entity], + "binary_sensor": [door_entity, remote_control, remote_start], "switch": program_switches, - "sensor": program_sensors, + "sensor": program_sensors + op_state_sensor, } -class Oven(DeviceWithDoor, DeviceWithPrograms): +class Oven( + DeviceWithDoor, + DeviceWithOpState, + DeviceWithPrograms, + DeviceWithRemoteControl, + DeviceWithRemoteStart, +): """Oven class.""" PROGRAMS = [ @@ -282,16 +340,25 @@ class Oven(DeviceWithDoor, DeviceWithPrograms): def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() + remote_control = self.get_remote_control() + remote_start = self.get_remote_start() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() return { - "binary_sensor": [door_entity], + "binary_sensor": [door_entity, remote_control, remote_start], "switch": program_switches, - "sensor": program_sensors, + "sensor": program_sensors + op_state_sensor, } -class Washer(DeviceWithDoor, DeviceWithPrograms): +class Washer( + DeviceWithDoor, + DeviceWithOpState, + DeviceWithPrograms, + DeviceWithRemoteControl, + DeviceWithRemoteStart, +): """Washer class.""" PROGRAMS = [ @@ -321,16 +388,19 @@ class Washer(DeviceWithDoor, DeviceWithPrograms): def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" door_entity = self.get_door_entity() + remote_control = self.get_remote_control() + remote_start = self.get_remote_start() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() return { - "binary_sensor": [door_entity], + "binary_sensor": [door_entity, remote_control, remote_start], "switch": program_switches, - "sensor": program_sensors, + "sensor": program_sensors + op_state_sensor, } -class CoffeeMaker(DeviceWithPrograms): +class CoffeeMaker(DeviceWithOpState, DeviceWithPrograms, DeviceWithRemoteStart): """Coffee maker class.""" PROGRAMS = [ @@ -354,12 +424,25 @@ class CoffeeMaker(DeviceWithPrograms): def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" + remote_start = self.get_remote_start() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() - return {"switch": program_switches, "sensor": program_sensors} + return { + "binary_sensor": [remote_start], + "switch": program_switches, + "sensor": program_sensors + op_state_sensor, + } -class Hood(DeviceWithLight, DeviceWithAmbientLight, DeviceWithPrograms): +class Hood( + DeviceWithLight, + DeviceWithAmbientLight, + DeviceWithOpState, + DeviceWithPrograms, + DeviceWithRemoteControl, + DeviceWithRemoteStart, +): """Hood class.""" PROGRAMS = [ @@ -370,13 +453,17 @@ class Hood(DeviceWithLight, DeviceWithAmbientLight, DeviceWithPrograms): def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" + remote_control = self.get_remote_control() + remote_start = self.get_remote_start() light_entity = self.get_light_entity() ambientlight_entity = self.get_ambientlight_entity() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() return { + "binary_sensor": [remote_control, remote_start], "switch": program_switches, - "sensor": program_sensors, + "sensor": program_sensors + op_state_sensor, "light": [light_entity, ambientlight_entity], } @@ -390,13 +477,19 @@ def get_entity_info(self): return {"binary_sensor": [door_entity]} -class Hob(DeviceWithPrograms): +class Hob(DeviceWithOpState, DeviceWithPrograms, DeviceWithRemoteControl): """Hob class.""" PROGRAMS = [{"name": "Cooking.Hob.Program.PowerLevelMode"}] def get_entity_info(self): """Get a dictionary with infos about the associated entities.""" + remote_control = self.get_remote_control() + op_state_sensor = self.get_opstate_sensor() program_sensors = self.get_program_sensors() program_switches = self.get_program_switches() - return {"switch": program_switches, "sensor": program_sensors} + return { + "binary_sensor": [remote_control], + "switch": program_switches, + "sensor": program_sensors + op_state_sensor, + } diff --git a/homeassistant/components/home_connect/binary_sensor.py b/homeassistant/components/home_connect/binary_sensor.py index 4810231b432075..1713d34809ae2a 100644 --- a/homeassistant/components/home_connect/binary_sensor.py +++ b/homeassistant/components/home_connect/binary_sensor.py @@ -3,7 +3,12 @@ from homeassistant.components.binary_sensor import BinarySensorEntity -from .const import BSH_DOOR_STATE, DOMAIN +from .const import ( + BSH_DOOR_STATE, + BSH_REMOTE_CONTROL_ACTIVATION_STATE, + BSH_REMOTE_START_ALLOWANCE_STATE, + DOMAIN, +) from .entity import HomeConnectEntity _LOGGER = logging.getLogger(__name__) @@ -26,11 +31,27 @@ def get_entities(): class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity): """Binary sensor for Home Connect.""" - def __init__(self, device, desc, device_class): + def __init__(self, device, desc, sensor_type, device_class=None): """Initialize the entity.""" super().__init__(device, desc) - self._device_class = device_class self._state = None + self._device_class = device_class + self._type = sensor_type + if self._type == "door": + self._update_key = BSH_DOOR_STATE + self._false_value_list = ( + "BSH.Common.EnumType.DoorState.Closed", + "BSH.Common.EnumType.DoorState.Locked", + ) + self._true_value_list = ["BSH.Common.EnumType.DoorState.Open"] + elif self._type == "remote_control": + self._update_key = BSH_REMOTE_CONTROL_ACTIVATION_STATE + self._false_value_list = [False] + self._true_value_list = [True] + elif self._type == "remote_start": + self._update_key = BSH_REMOTE_START_ALLOWANCE_STATE + self._false_value_list = [False] + self._true_value_list = [True] @property def is_on(self): @@ -44,18 +65,17 @@ def available(self): async def async_update(self): """Update the binary sensor's status.""" - state = self.device.appliance.status.get(BSH_DOOR_STATE, {}) + state = self.device.appliance.status.get(self._update_key, {}) if not state: self._state = None - elif state.get("value") in [ - "BSH.Common.EnumType.DoorState.Closed", - "BSH.Common.EnumType.DoorState.Locked", - ]: + elif state.get("value") in self._false_value_list: self._state = False - elif state.get("value") == "BSH.Common.EnumType.DoorState.Open": + elif state.get("value") in self._true_value_list: self._state = True else: - _LOGGER.warning("Unexpected value for HomeConnect door state: %s", state) + _LOGGER.warning( + "Unexpected value for HomeConnect %s state: %s", self._type, state + ) self._state = None _LOGGER.debug("Updated, new state: %s", self._state) diff --git a/homeassistant/components/home_connect/const.py b/homeassistant/components/home_connect/const.py index 22ce4dba6768fe..98dd8d383bd0b7 100644 --- a/homeassistant/components/home_connect/const.py +++ b/homeassistant/components/home_connect/const.py @@ -11,6 +11,8 @@ BSH_POWER_STANDBY = "BSH.Common.EnumType.PowerState.Standby" BSH_ACTIVE_PROGRAM = "BSH.Common.Root.ActiveProgram" BSH_OPERATION_STATE = "BSH.Common.Status.OperationState" +BSH_REMOTE_CONTROL_ACTIVATION_STATE = "BSH.Common.Status.RemoteControlActive" +BSH_REMOTE_START_ALLOWANCE_STATE = "BSH.Common.Status.RemoteControlStartAllowed" COOKING_LIGHTING = "Cooking.Common.Setting.Lighting" COOKING_LIGHTING_BRIGHTNESS = "Cooking.Common.Setting.LightingBrightness" diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index 0ae5a9fcd3693a..e51efe06057e91 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -6,7 +6,7 @@ from homeassistant.const import DEVICE_CLASS_TIMESTAMP import homeassistant.util.dt as dt_util -from .const import DOMAIN +from .const import BSH_OPERATION_STATE, DOMAIN from .entity import HomeConnectEntity _LOGGER = logging.getLogger(__name__) @@ -51,7 +51,7 @@ def available(self): return self._state is not None async def async_update(self): - """Update the sensos status.""" + """Update the sensor's status.""" status = self.device.appliance.status if self._key not in status: self._state = None @@ -74,6 +74,11 @@ async def async_update(self): ).isoformat() else: self._state = status[self._key].get("value") + if self._key == BSH_OPERATION_STATE: + # Value comes back as an enum, we only really care about the + # last part, so split it off + # https://developer.home-connect.com/docs/status/operation_state + self._state = self._state.split(".")[-1] _LOGGER.debug("Updated, new state: %s", self._state) @property From a584ad5ac3a143e6526105ee06adc6b2120ef04d Mon Sep 17 00:00:00 2001 From: Berni Moses Date: Wed, 3 Feb 2021 17:06:02 +0100 Subject: [PATCH 0162/1818] Fix duplicate lg_soundbar entities and disable polling (#42044) Co-authored-by: Paulus Schoutsen --- .../components/lg_soundbar/media_player.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index ee396a7a9ee3bb..c2d196196f9997 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -29,12 +29,11 @@ class LGDevice(MediaPlayerEntity): def __init__(self, discovery_info): """Initialize the LG speakers.""" - self._host = discovery_info.get("host") - self._port = discovery_info.get("port") - properties = discovery_info.get("properties") - self._uuid = properties.get("UUID") + self._host = discovery_info["host"] + self._port = discovery_info["port"] + self._hostname = discovery_info["hostname"] - self._name = "" + self._name = self._hostname.split(".")[0] self._volume = 0 self._volume_min = 0 self._volume_max = 0 @@ -122,9 +121,9 @@ def update(self): self._device.get_product_info() @property - def unique_id(self): - """Return the device's unique ID.""" - return self._uuid + def should_poll(self): + """No polling needed.""" + return False @property def name(self): From 4b208746e5a91997aed59a77714cf799e96287a9 Mon Sep 17 00:00:00 2001 From: Brandon Rothweiler Date: Wed, 3 Feb 2021 11:38:12 -0500 Subject: [PATCH 0163/1818] Add Mazda Connected Services integration (#45768) --- CODEOWNERS | 1 + homeassistant/components/mazda/__init__.py | 173 ++++++++++ homeassistant/components/mazda/config_flow.py | 117 +++++++ homeassistant/components/mazda/const.py | 8 + homeassistant/components/mazda/manifest.json | 9 + homeassistant/components/mazda/sensor.py | 263 +++++++++++++++ homeassistant/components/mazda/strings.json | 35 ++ .../components/mazda/translations/en.json | 35 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/mazda/__init__.py | 53 +++ tests/components/mazda/test_config_flow.py | 310 ++++++++++++++++++ tests/components/mazda/test_init.py | 100 ++++++ tests/components/mazda/test_sensor.py | 160 +++++++++ tests/fixtures/mazda/get_vehicle_status.json | 37 +++ tests/fixtures/mazda/get_vehicles.json | 17 + 17 files changed, 1325 insertions(+) create mode 100644 homeassistant/components/mazda/__init__.py create mode 100644 homeassistant/components/mazda/config_flow.py create mode 100644 homeassistant/components/mazda/const.py create mode 100644 homeassistant/components/mazda/manifest.json create mode 100644 homeassistant/components/mazda/sensor.py create mode 100644 homeassistant/components/mazda/strings.json create mode 100644 homeassistant/components/mazda/translations/en.json create mode 100644 tests/components/mazda/__init__.py create mode 100644 tests/components/mazda/test_config_flow.py create mode 100644 tests/components/mazda/test_init.py create mode 100644 tests/components/mazda/test_sensor.py create mode 100644 tests/fixtures/mazda/get_vehicle_status.json create mode 100644 tests/fixtures/mazda/get_vehicles.json diff --git a/CODEOWNERS b/CODEOWNERS index 499b7e131f7a6d..cb69b86ed8c4e7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -263,6 +263,7 @@ homeassistant/components/lutron_caseta/* @swails @bdraco homeassistant/components/lyric/* @timmo001 homeassistant/components/mastodon/* @fabaff homeassistant/components/matrix/* @tinloaf +homeassistant/components/mazda/* @bdr99 homeassistant/components/mcp23017/* @jardiamj homeassistant/components/media_source/* @hunterjm homeassistant/components/mediaroom/* @dgomes diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py new file mode 100644 index 00000000000000..14b33df66c0f71 --- /dev/null +++ b/homeassistant/components/mazda/__init__.py @@ -0,0 +1,173 @@ +"""The Mazda Connected Services integration.""" +import asyncio +from datetime import timedelta +import logging + +import async_timeout +from pymazda import ( + Client as MazdaAPI, + MazdaAccountLockedException, + MazdaAPIEncryptionException, + MazdaAuthenticationException, + MazdaException, + MazdaTokenExpiredException, +) + +from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) +from homeassistant.util.async_ import gather_with_concurrency + +from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["sensor"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Mazda Connected Services component.""" + hass.data[DOMAIN] = {} + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Mazda Connected Services from a config entry.""" + email = entry.data[CONF_EMAIL] + password = entry.data[CONF_PASSWORD] + region = entry.data[CONF_REGION] + + websession = aiohttp_client.async_get_clientsession(hass) + mazda_client = MazdaAPI(email, password, region, websession) + + try: + await mazda_client.validate_credentials() + except MazdaAuthenticationException: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_REAUTH}, + data=entry.data, + ) + ) + return False + except ( + MazdaException, + MazdaAccountLockedException, + MazdaTokenExpiredException, + MazdaAPIEncryptionException, + ) as ex: + _LOGGER.error("Error occurred during Mazda login request: %s", ex) + raise ConfigEntryNotReady from ex + + async def async_update_data(): + """Fetch data from Mazda API.""" + + async def with_timeout(task): + async with async_timeout.timeout(10): + return await task + + try: + vehicles = await with_timeout(mazda_client.get_vehicles()) + + vehicle_status_tasks = [ + with_timeout(mazda_client.get_vehicle_status(vehicle["id"])) + for vehicle in vehicles + ] + statuses = await gather_with_concurrency(5, *vehicle_status_tasks) + + for vehicle, status in zip(vehicles, statuses): + vehicle["status"] = status + + return vehicles + except MazdaAuthenticationException as ex: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_REAUTH}, + data=entry.data, + ) + ) + raise UpdateFailed("Not authenticated with Mazda API") from ex + except Exception as ex: + _LOGGER.exception( + "Unknown error occurred during Mazda update request: %s", ex + ) + raise UpdateFailed(ex) from ex + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=DOMAIN, + update_method=async_update_data, + update_interval=timedelta(seconds=60), + ) + + hass.data[DOMAIN][entry.entry_id] = { + DATA_CLIENT: mazda_client, + DATA_COORDINATOR: coordinator, + } + + # Fetch initial data so we have data when entities subscribe + await coordinator.async_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + # Setup components + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +class MazdaEntity(CoordinatorEntity): + """Defines a base Mazda entity.""" + + def __init__(self, coordinator, index): + """Initialize the Mazda entity.""" + super().__init__(coordinator) + self.index = index + self.vin = self.coordinator.data[self.index]["vin"] + + @property + def device_info(self): + """Return device info for the Mazda entity.""" + data = self.coordinator.data[self.index] + return { + "identifiers": {(DOMAIN, self.vin)}, + "name": self.get_vehicle_name(), + "manufacturer": "Mazda", + "model": f"{data['modelYear']} {data['carlineName']}", + } + + def get_vehicle_name(self): + """Return the vehicle name, to be used as a prefix for names of other entities.""" + data = self.coordinator.data[self.index] + if "nickname" in data and len(data["nickname"]) > 0: + return data["nickname"] + return f"{data['modelYear']} {data['carlineName']}" diff --git a/homeassistant/components/mazda/config_flow.py b/homeassistant/components/mazda/config_flow.py new file mode 100644 index 00000000000000..53c08b9bd69ee0 --- /dev/null +++ b/homeassistant/components/mazda/config_flow.py @@ -0,0 +1,117 @@ +"""Config flow for Mazda Connected Services integration.""" +import logging + +import aiohttp +from pymazda import ( + Client as MazdaAPI, + MazdaAccountLockedException, + MazdaAuthenticationException, +) +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION +from homeassistant.helpers import aiohttp_client + +# https://github.com/PyCQA/pylint/issues/3202 +from .const import DOMAIN # pylint: disable=unused-import +from .const import MAZDA_REGIONS + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_REGION): vol.In(MAZDA_REGIONS), + } +) + + +class MazdaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Mazda Connected Services.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + + if user_input is not None: + await self.async_set_unique_id(user_input[CONF_EMAIL].lower()) + + try: + websession = aiohttp_client.async_get_clientsession(self.hass) + mazda_client = MazdaAPI( + user_input[CONF_EMAIL], + user_input[CONF_PASSWORD], + user_input[CONF_REGION], + websession, + ) + await mazda_client.validate_credentials() + except MazdaAuthenticationException: + errors["base"] = "invalid_auth" + except MazdaAccountLockedException: + errors["base"] = "account_locked" + except aiohttp.ClientError: + errors["base"] = "cannot_connect" + except Exception as ex: # pylint: disable=broad-except + errors["base"] = "unknown" + _LOGGER.exception( + "Unknown error occurred during Mazda login request: %s", ex + ) + else: + return self.async_create_entry( + title=user_input[CONF_EMAIL], data=user_input + ) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + async def async_step_reauth(self, user_input=None): + """Perform reauth if the user credentials have changed.""" + errors = {} + + if user_input is not None: + try: + websession = aiohttp_client.async_get_clientsession(self.hass) + mazda_client = MazdaAPI( + user_input[CONF_EMAIL], + user_input[CONF_PASSWORD], + user_input[CONF_REGION], + websession, + ) + await mazda_client.validate_credentials() + except MazdaAuthenticationException: + errors["base"] = "invalid_auth" + except MazdaAccountLockedException: + errors["base"] = "account_locked" + except aiohttp.ClientError: + errors["base"] = "cannot_connect" + except Exception as ex: # pylint: disable=broad-except + errors["base"] = "unknown" + _LOGGER.exception( + "Unknown error occurred during Mazda login request: %s", ex + ) + else: + await self.async_set_unique_id(user_input[CONF_EMAIL].lower()) + + for entry in self._async_current_entries(): + if entry.unique_id == self.unique_id: + self.hass.config_entries.async_update_entry( + entry, data=user_input + ) + + # Reload the config entry otherwise devices will remain unavailable + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + + return self.async_abort(reason="reauth_successful") + errors["base"] = "unknown" + + return self.async_show_form( + step_id="reauth", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/mazda/const.py b/homeassistant/components/mazda/const.py new file mode 100644 index 00000000000000..c75f6bf3b77b03 --- /dev/null +++ b/homeassistant/components/mazda/const.py @@ -0,0 +1,8 @@ +"""Constants for the Mazda Connected Services integration.""" + +DOMAIN = "mazda" + +DATA_CLIENT = "mazda_client" +DATA_COORDINATOR = "coordinator" + +MAZDA_REGIONS = {"MNAO": "North America", "MME": "Europe", "MJO": "Japan"} diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json new file mode 100644 index 00000000000000..b3826d42318a6b --- /dev/null +++ b/homeassistant/components/mazda/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "mazda", + "name": "Mazda Connected Services", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/mazda", + "requirements": ["pymazda==0.0.8"], + "codeowners": ["@bdr99"], + "quality_scale": "platinum" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py new file mode 100644 index 00000000000000..fa03eb7f4108d0 --- /dev/null +++ b/homeassistant/components/mazda/sensor.py @@ -0,0 +1,263 @@ +"""Platform for Mazda sensor integration.""" +from homeassistant.const import ( + CONF_UNIT_SYSTEM_IMPERIAL, + LENGTH_KILOMETERS, + LENGTH_MILES, + PERCENTAGE, + PRESSURE_PSI, +) + +from . import MazdaEntity +from .const import DATA_COORDINATOR, DOMAIN + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the sensor platform.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] + + entities = [] + + for index, _ in enumerate(coordinator.data): + entities.append(MazdaFuelRemainingSensor(coordinator, index)) + entities.append(MazdaFuelDistanceSensor(coordinator, index)) + entities.append(MazdaOdometerSensor(coordinator, index)) + entities.append(MazdaFrontLeftTirePressureSensor(coordinator, index)) + entities.append(MazdaFrontRightTirePressureSensor(coordinator, index)) + entities.append(MazdaRearLeftTirePressureSensor(coordinator, index)) + entities.append(MazdaRearRightTirePressureSensor(coordinator, index)) + + async_add_entities(entities) + + +class MazdaFuelRemainingSensor(MazdaEntity): + """Class for the fuel remaining sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Fuel Remaining Percentage" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_fuel_remaining_percentage" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return PERCENTAGE + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:gas-station" + + @property + def state(self): + """Return the state of the sensor.""" + return self.coordinator.data[self.index]["status"]["fuelRemainingPercent"] + + +class MazdaFuelDistanceSensor(MazdaEntity): + """Class for the fuel distance sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Fuel Distance Remaining" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_fuel_distance_remaining" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + if self.hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL: + return LENGTH_MILES + return LENGTH_KILOMETERS + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:gas-station" + + @property + def state(self): + """Return the state of the sensor.""" + fuel_distance_km = self.coordinator.data[self.index]["status"][ + "fuelDistanceRemainingKm" + ] + return round(self.hass.config.units.length(fuel_distance_km, LENGTH_KILOMETERS)) + + +class MazdaOdometerSensor(MazdaEntity): + """Class for the odometer sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Odometer" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_odometer" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + if self.hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL: + return LENGTH_MILES + return LENGTH_KILOMETERS + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:speedometer" + + @property + def state(self): + """Return the state of the sensor.""" + odometer_km = self.coordinator.data[self.index]["status"]["odometerKm"] + return round(self.hass.config.units.length(odometer_km, LENGTH_KILOMETERS)) + + +class MazdaFrontLeftTirePressureSensor(MazdaEntity): + """Class for the front left tire pressure sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Front Left Tire Pressure" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_front_left_tire_pressure" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return PRESSURE_PSI + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:car-tire-alert" + + @property + def state(self): + """Return the state of the sensor.""" + return round( + self.coordinator.data[self.index]["status"]["tirePressure"][ + "frontLeftTirePressurePsi" + ] + ) + + +class MazdaFrontRightTirePressureSensor(MazdaEntity): + """Class for the front right tire pressure sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Front Right Tire Pressure" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_front_right_tire_pressure" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return PRESSURE_PSI + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:car-tire-alert" + + @property + def state(self): + """Return the state of the sensor.""" + return round( + self.coordinator.data[self.index]["status"]["tirePressure"][ + "frontRightTirePressurePsi" + ] + ) + + +class MazdaRearLeftTirePressureSensor(MazdaEntity): + """Class for the rear left tire pressure sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Rear Left Tire Pressure" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_rear_left_tire_pressure" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return PRESSURE_PSI + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:car-tire-alert" + + @property + def state(self): + """Return the state of the sensor.""" + return round( + self.coordinator.data[self.index]["status"]["tirePressure"][ + "rearLeftTirePressurePsi" + ] + ) + + +class MazdaRearRightTirePressureSensor(MazdaEntity): + """Class for the rear right tire pressure sensor.""" + + @property + def name(self): + """Return the name of the sensor.""" + vehicle_name = self.get_vehicle_name() + return f"{vehicle_name} Rear Right Tire Pressure" + + @property + def unique_id(self): + """Return a unique identifier for this entity.""" + return f"{self.vin}_rear_right_tire_pressure" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return PRESSURE_PSI + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return "mdi:car-tire-alert" + + @property + def state(self): + """Return the state of the sensor.""" + return round( + self.coordinator.data[self.index]["status"]["tirePressure"][ + "rearRightTirePressurePsi" + ] + ) diff --git a/homeassistant/components/mazda/strings.json b/homeassistant/components/mazda/strings.json new file mode 100644 index 00000000000000..1950260bfcbea5 --- /dev/null +++ b/homeassistant/components/mazda/strings.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + }, + "error": { + "account_locked": "Account locked. Please try again later.", + "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%]" + }, + "step": { + "reauth": { + "data": { + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]", + "region": "Region" + }, + "description": "Authentication failed for Mazda Connected Services. Please enter your current credentials.", + "title": "Mazda Connected Services - Authentication Failed" + }, + "user": { + "data": { + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]", + "region": "Region" + }, + "description": "Please enter the email address and password you use to log into the MyMazda mobile app.", + "title": "Mazda Connected Services - Add Account" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/en.json b/homeassistant/components/mazda/translations/en.json new file mode 100644 index 00000000000000..b9e02fb3a412f8 --- /dev/null +++ b/homeassistant/components/mazda/translations/en.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" + }, + "error": { + "account_locked": "Account locked. Please try again later.", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "reauth": { + "data": { + "email": "Email", + "password": "Password", + "region": "Region" + }, + "description": "Authentication failed for Mazda Connected Services. Please enter your current credentials.", + "title": "Mazda Connected Services - Authentication Failed" + }, + "user": { + "data": { + "email": "Email", + "password": "Password", + "region": "Region" + }, + "description": "Please enter the email address and password you use to log into the MyMazda mobile app.", + "title": "Mazda Connected Services - Add Account" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 282d039128d687..c2e36d9f846872 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -123,6 +123,7 @@ "lutron_caseta", "lyric", "mailgun", + "mazda", "melcloud", "met", "meteo_france", diff --git a/requirements_all.txt b/requirements_all.txt index 4315c577283db7..93922e9f93418f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1517,6 +1517,9 @@ pymailgunner==1.4 # homeassistant.components.firmata pymata-express==1.19 +# homeassistant.components.mazda +pymazda==0.0.8 + # homeassistant.components.mediaroom pymediaroom==0.6.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d8c7d112fba51d..9e3796120600ba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -789,6 +789,9 @@ pymailgunner==1.4 # homeassistant.components.firmata pymata-express==1.19 +# homeassistant.components.mazda +pymazda==0.0.8 + # homeassistant.components.melcloud pymelcloud==2.5.2 diff --git a/tests/components/mazda/__init__.py b/tests/components/mazda/__init__.py new file mode 100644 index 00000000000000..f7a267a511033c --- /dev/null +++ b/tests/components/mazda/__init__.py @@ -0,0 +1,53 @@ +"""Tests for the Mazda Connected Services integration.""" + +import json +from unittest.mock import AsyncMock, MagicMock, patch + +from pymazda import Client as MazdaAPI + +from homeassistant.components.mazda.const import DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION +from homeassistant.core import HomeAssistant +from homeassistant.helpers import aiohttp_client + +from tests.common import MockConfigEntry, load_fixture + +FIXTURE_USER_INPUT = { + CONF_EMAIL: "example@example.com", + CONF_PASSWORD: "password", + CONF_REGION: "MNAO", +} + + +async def init_integration(hass: HomeAssistant, use_nickname=True) -> MockConfigEntry: + """Set up the Mazda Connected Services integration in Home Assistant.""" + get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) + if not use_nickname: + get_vehicles_fixture[0].pop("nickname") + + get_vehicle_status_fixture = json.loads( + load_fixture("mazda/get_vehicle_status.json") + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) + config_entry.add_to_hass(hass) + + client_mock = MagicMock( + MazdaAPI( + FIXTURE_USER_INPUT[CONF_EMAIL], + FIXTURE_USER_INPUT[CONF_PASSWORD], + FIXTURE_USER_INPUT[CONF_REGION], + aiohttp_client.async_get_clientsession(hass), + ) + ) + client_mock.get_vehicles = AsyncMock(return_value=get_vehicles_fixture) + client_mock.get_vehicle_status = AsyncMock(return_value=get_vehicle_status_fixture) + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI", + return_value=client_mock, + ), patch("homeassistant.components.mazda.MazdaAPI", return_value=client_mock): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry diff --git a/tests/components/mazda/test_config_flow.py b/tests/components/mazda/test_config_flow.py new file mode 100644 index 00000000000000..fbdd74bfdfae24 --- /dev/null +++ b/tests/components/mazda/test_config_flow.py @@ -0,0 +1,310 @@ +"""Test the Mazda Connected Services config flow.""" +from unittest.mock import patch + +import aiohttp + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.mazda.config_flow import ( + MazdaAccountLockedException, + MazdaAuthenticationException, +) +from homeassistant.components.mazda.const import DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +FIXTURE_USER_INPUT = { + CONF_EMAIL: "example@example.com", + CONF_PASSWORD: "password", + CONF_REGION: "MNAO", +} +FIXTURE_USER_INPUT_REAUTH = { + CONF_EMAIL: "example@example.com", + CONF_PASSWORD: "password_fixed", + CONF_REGION: "MNAO", +} + + +async def test_form(hass): + """Test the entire flow.""" + 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"] == {} + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + return_value=True, + ), patch( + "homeassistant.components.mazda.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mazda.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == FIXTURE_USER_INPUT[CONF_EMAIL] + assert result2["data"] == FIXTURE_USER_INPUT + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass: HomeAssistant) -> None: + """Test we handle invalid auth.""" + 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"] == {} + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=MazdaAuthenticationException("Failed to authenticate"), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_account_locked(hass: HomeAssistant) -> None: + """Test we handle account locked error.""" + 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"] == {} + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=MazdaAccountLockedException("Account locked"), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "account_locked"} + + +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( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=aiohttp.ClientError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unknown_error(hass): + """Test we handle unknown error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +async def test_reauth_flow(hass: HomeAssistant) -> None: + """Test reauth works.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=MazdaAuthenticationException("Failed to authenticate"), + ): + mock_config = MockConfigEntry( + domain=DOMAIN, + unique_id=FIXTURE_USER_INPUT[CONF_EMAIL], + data=FIXTURE_USER_INPUT, + ) + mock_config.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_config.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth" + assert result["errors"] == {"base": "invalid_auth"} + + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "reauth", "unique_id": FIXTURE_USER_INPUT[CONF_EMAIL]}, + data=FIXTURE_USER_INPUT_REAUTH, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + + +async def test_reauth_authorization_error(hass: HomeAssistant) -> None: + """Test we show user form on authorization error.""" + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=MazdaAuthenticationException("Failed to authenticate"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT_REAUTH, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "reauth" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_reauth_account_locked(hass: HomeAssistant) -> None: + """Test we show user form on account_locked error.""" + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=MazdaAccountLockedException("Account locked"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT_REAUTH, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "reauth" + assert result2["errors"] == {"base": "account_locked"} + + +async def test_reauth_connection_error(hass: HomeAssistant) -> None: + """Test we show user form on connection error.""" + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=aiohttp.ClientError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT_REAUTH, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "reauth" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_reauth_unknown_error(hass: HomeAssistant) -> None: + """Test we show user form on unknown error.""" + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + side_effect=Exception, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT_REAUTH, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "reauth" + assert result2["errors"] == {"base": "unknown"} + + +async def test_reauth_unique_id_not_found(hass: HomeAssistant) -> None: + """Test we show user form when unique id not found during reauth.""" + with patch( + "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth" + + # Change the unique_id of the flow in order to cause a mismatch + flows = hass.config_entries.flow.async_progress() + flows[0]["context"]["unique_id"] = "example2@example.com" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + FIXTURE_USER_INPUT_REAUTH, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "reauth" + assert result2["errors"] == {"base": "unknown"} diff --git a/tests/components/mazda/test_init.py b/tests/components/mazda/test_init.py new file mode 100644 index 00000000000000..d0352682f53cb2 --- /dev/null +++ b/tests/components/mazda/test_init.py @@ -0,0 +1,100 @@ +"""Tests for the Mazda Connected Services integration.""" +from unittest.mock import patch + +from pymazda import MazdaAuthenticationException, MazdaException + +from homeassistant.components.mazda.const import DATA_COORDINATOR, DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.mazda import init_integration + +FIXTURE_USER_INPUT = { + CONF_EMAIL: "example@example.com", + CONF_PASSWORD: "password", + CONF_REGION: "MNAO", +} + + +async def test_config_entry_not_ready(hass: HomeAssistant) -> None: + """Test the Mazda configuration entry not ready.""" + config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.mazda.MazdaAPI.validate_credentials", + side_effect=MazdaException("Unknown error"), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_init_auth_failure(hass: HomeAssistant): + """Test auth failure during setup.""" + with patch( + "homeassistant.components.mazda.MazdaAPI.validate_credentials", + side_effect=MazdaAuthenticationException("Login failed"), + ): + config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + assert entries[0].state == ENTRY_STATE_SETUP_ERROR + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["step_id"] == "reauth" + + +async def test_update_auth_failure(hass: HomeAssistant): + """Test auth failure during data update.""" + with patch( + "homeassistant.components.mazda.MazdaAPI.validate_credentials", + return_value=True, + ), patch("homeassistant.components.mazda.MazdaAPI.get_vehicles", return_value={}): + config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + assert entries[0].state == ENTRY_STATE_LOADED + + coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] + with patch( + "homeassistant.components.mazda.MazdaAPI.validate_credentials", + side_effect=MazdaAuthenticationException("Login failed"), + ), patch( + "homeassistant.components.mazda.MazdaAPI.get_vehicles", + side_effect=MazdaAuthenticationException("Login failed"), + ): + await coordinator.async_refresh() + await hass.async_block_till_done() + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["step_id"] == "reauth" + + +async def test_unload_config_entry(hass: HomeAssistant) -> None: + """Test the Mazda configuration entry unloading.""" + entry = await init_integration(hass) + assert hass.data[DOMAIN] + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert not hass.data.get(DOMAIN) diff --git a/tests/components/mazda/test_sensor.py b/tests/components/mazda/test_sensor.py new file mode 100644 index 00000000000000..1cb9f7ac4b79e6 --- /dev/null +++ b/tests/components/mazda/test_sensor.py @@ -0,0 +1,160 @@ +"""The sensor tests for the Mazda Connected Services integration.""" + +from homeassistant.components.mazda.const import DOMAIN +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + LENGTH_KILOMETERS, + LENGTH_MILES, + PERCENTAGE, + PRESSURE_PSI, +) +from homeassistant.util.unit_system import IMPERIAL_SYSTEM + +from tests.components.mazda import init_integration + + +async def test_device_nickname(hass): + """Test creation of the device when vehicle has a nickname.""" + await init_integration(hass, use_nickname=True) + + device_registry = await hass.helpers.device_registry.async_get_registry() + reg_device = device_registry.async_get_device( + identifiers={(DOMAIN, "JM000000000000000")}, + ) + + assert reg_device.model == "2021 MAZDA3 2.5 S SE AWD" + assert reg_device.manufacturer == "Mazda" + assert reg_device.name == "My Mazda3" + + +async def test_device_no_nickname(hass): + """Test creation of the device when vehicle has no nickname.""" + await init_integration(hass, use_nickname=False) + + device_registry = await hass.helpers.device_registry.async_get_registry() + reg_device = device_registry.async_get_device( + identifiers={(DOMAIN, "JM000000000000000")}, + ) + + assert reg_device.model == "2021 MAZDA3 2.5 S SE AWD" + assert reg_device.manufacturer == "Mazda" + assert reg_device.name == "2021 MAZDA3 2.5 S SE AWD" + + +async def test_sensors(hass): + """Test creation of the sensors.""" + await init_integration(hass) + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + # Fuel Remaining Percentage + state = hass.states.get("sensor.my_mazda3_fuel_remaining_percentage") + assert state + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == "My Mazda3 Fuel Remaining Percentage" + ) + assert state.attributes.get(ATTR_ICON) == "mdi:gas-station" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE + assert state.state == "87.0" + entry = entity_registry.async_get("sensor.my_mazda3_fuel_remaining_percentage") + assert entry + assert entry.unique_id == "JM000000000000000_fuel_remaining_percentage" + + # Fuel Distance Remaining + state = hass.states.get("sensor.my_mazda3_fuel_distance_remaining") + assert state + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Fuel Distance Remaining" + ) + assert state.attributes.get(ATTR_ICON) == "mdi:gas-station" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_KILOMETERS + assert state.state == "381" + entry = entity_registry.async_get("sensor.my_mazda3_fuel_distance_remaining") + assert entry + assert entry.unique_id == "JM000000000000000_fuel_distance_remaining" + + # Odometer + state = hass.states.get("sensor.my_mazda3_odometer") + assert state + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Odometer" + assert state.attributes.get(ATTR_ICON) == "mdi:speedometer" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_KILOMETERS + assert state.state == "2796" + entry = entity_registry.async_get("sensor.my_mazda3_odometer") + assert entry + assert entry.unique_id == "JM000000000000000_odometer" + + # Front Left Tire Pressure + state = hass.states.get("sensor.my_mazda3_front_left_tire_pressure") + assert state + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Front Left Tire Pressure" + ) + assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI + assert state.state == "35" + entry = entity_registry.async_get("sensor.my_mazda3_front_left_tire_pressure") + assert entry + assert entry.unique_id == "JM000000000000000_front_left_tire_pressure" + + # Front Right Tire Pressure + state = hass.states.get("sensor.my_mazda3_front_right_tire_pressure") + assert state + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == "My Mazda3 Front Right Tire Pressure" + ) + assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI + assert state.state == "35" + entry = entity_registry.async_get("sensor.my_mazda3_front_right_tire_pressure") + assert entry + assert entry.unique_id == "JM000000000000000_front_right_tire_pressure" + + # Rear Left Tire Pressure + state = hass.states.get("sensor.my_mazda3_rear_left_tire_pressure") + assert state + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear Left Tire Pressure" + ) + assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI + assert state.state == "33" + entry = entity_registry.async_get("sensor.my_mazda3_rear_left_tire_pressure") + assert entry + assert entry.unique_id == "JM000000000000000_rear_left_tire_pressure" + + # Rear Right Tire Pressure + state = hass.states.get("sensor.my_mazda3_rear_right_tire_pressure") + assert state + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "My Mazda3 Rear Right Tire Pressure" + ) + assert state.attributes.get(ATTR_ICON) == "mdi:car-tire-alert" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PRESSURE_PSI + assert state.state == "33" + entry = entity_registry.async_get("sensor.my_mazda3_rear_right_tire_pressure") + assert entry + assert entry.unique_id == "JM000000000000000_rear_right_tire_pressure" + + +async def test_sensors_imperial_units(hass): + """Test that the sensors work properly with imperial units.""" + hass.config.units = IMPERIAL_SYSTEM + + await init_integration(hass) + + # Fuel Distance Remaining + state = hass.states.get("sensor.my_mazda3_fuel_distance_remaining") + assert state + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_MILES + assert state.state == "237" + + # Odometer + state = hass.states.get("sensor.my_mazda3_odometer") + assert state + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_MILES + assert state.state == "1737" diff --git a/tests/fixtures/mazda/get_vehicle_status.json b/tests/fixtures/mazda/get_vehicle_status.json new file mode 100644 index 00000000000000..f170b222b318fe --- /dev/null +++ b/tests/fixtures/mazda/get_vehicle_status.json @@ -0,0 +1,37 @@ +{ + "lastUpdatedTimestamp": "20210123143809", + "latitude": 1.234567, + "longitude": -2.345678, + "positionTimestamp": "20210123143808", + "fuelRemainingPercent": 87.0, + "fuelDistanceRemainingKm": 380.8, + "odometerKm": 2795.8, + "doors": { + "driverDoorOpen": false, + "passengerDoorOpen": false, + "rearLeftDoorOpen": false, + "rearRightDoorOpen": false, + "trunkOpen": false, + "hoodOpen": false, + "fuelLidOpen": false + }, + "doorLocks": { + "driverDoorUnlocked": false, + "passengerDoorUnlocked": false, + "rearLeftDoorUnlocked": false, + "rearRightDoorUnlocked": false + }, + "windows": { + "driverWindowOpen": false, + "passengerWindowOpen": false, + "rearLeftWindowOpen": false, + "rearRightWindowOpen": false + }, + "hazardLightsOn": false, + "tirePressure": { + "frontLeftTirePressurePsi": 35.0, + "frontRightTirePressurePsi": 35.0, + "rearLeftTirePressurePsi": 33.0, + "rearRightTirePressurePsi": 33.0 + } +} \ No newline at end of file diff --git a/tests/fixtures/mazda/get_vehicles.json b/tests/fixtures/mazda/get_vehicles.json new file mode 100644 index 00000000000000..871eeb9d2ecd06 --- /dev/null +++ b/tests/fixtures/mazda/get_vehicles.json @@ -0,0 +1,17 @@ +[ + { + "vin": "JM000000000000000", + "id": 12345, + "nickname": "My Mazda3", + "carlineCode": "M3S", + "carlineName": "MAZDA3 2.5 S SE AWD", + "modelYear": "2021", + "modelCode": "M3S SE XA", + "modelName": "W/ SELECT PKG AWD SDN", + "automaticTransmission": true, + "interiorColorCode": "BY3", + "interiorColorName": "BLACK", + "exteriorColorCode": "42M", + "exteriorColorName": "DEEP CRYSTAL BLUE MICA" + } +] \ No newline at end of file From 39ef1131e1c55b128334f69ca8149d64f3938a4f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Feb 2021 17:57:14 +0100 Subject: [PATCH 0164/1818] Bumped version to 2021.2.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index bab77c63a94d09..63e11720c1ef65 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From a775b79d4b6651de73a8bdd611050af2ec27e8ad Mon Sep 17 00:00:00 2001 From: Niccolo Zapponi Date: Wed, 3 Feb 2021 18:18:31 +0000 Subject: [PATCH 0165/1818] Add support for iCloud 2FA (#45818) * Add support for iCloud 2FA * Updated dependency for iCloud * Updated dependency and logic fix * Added logic for handling incorrect 2FA code * Bug fix on failing test * Added myself to codeowners * Added check for 2FA on setup * Updated error message --- CODEOWNERS | 2 +- homeassistant/components/icloud/account.py | 34 ++++--- .../components/icloud/config_flow.py | 54 ++++++++--- homeassistant/components/icloud/const.py | 2 +- homeassistant/components/icloud/manifest.json | 4 +- homeassistant/components/icloud/strings.json | 2 +- .../components/icloud/translations/en.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/icloud/test_config_flow.py | 90 +++++++++++++++++++ 10 files changed, 164 insertions(+), 30 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index cb69b86ed8c4e7..efb338dd4b4ccf 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -211,7 +211,7 @@ homeassistant/components/hydrawise/* @ptcryan homeassistant/components/hyperion/* @dermotduffy homeassistant/components/iammeter/* @lewei50 homeassistant/components/iaqualink/* @flz -homeassistant/components/icloud/* @Quentame +homeassistant/components/icloud/* @Quentame @nzapponi homeassistant/components/ign_sismologia/* @exxamalte homeassistant/components/image/* @home-assistant/core homeassistant/components/incomfort/* @zxdavb diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index e6337085e04e2b..4221cf635ba585 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -113,6 +113,12 @@ def setup(self) -> None: self._icloud_dir.path, with_family=self._with_family, ) + + if not self.api.is_trusted_session or self.api.requires_2fa: + # Session is no longer trusted + # Trigger a new log in to ensure the user enters the 2FA code again. + raise PyiCloudFailedLoginException + except PyiCloudFailedLoginException: self.api = None # Login failed which means credentials need to be updated. @@ -125,16 +131,7 @@ def setup(self) -> None: self._config_entry.data[CONF_USERNAME], ) - self.hass.add_job( - self.hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_REAUTH}, - data={ - **self._config_entry.data, - "unique_id": self._config_entry.unique_id, - }, - ) - ) + self._require_reauth() return try: @@ -165,6 +162,10 @@ def update_devices(self) -> None: if self.api is None: return + if not self.api.is_trusted_session or self.api.requires_2fa: + self._require_reauth() + return + api_devices = {} try: api_devices = self.api.devices @@ -228,6 +229,19 @@ def update_devices(self) -> None: utcnow() + timedelta(minutes=self._fetch_interval), ) + def _require_reauth(self): + """Require the user to log in again.""" + self.hass.add_job( + self.hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_REAUTH}, + data={ + **self._config_entry.data, + "unique_id": self._config_entry.unique_id, + }, + ) + ) + def _determine_interval(self) -> int: """Calculate new interval between two API fetch (in minutes).""" intervals = {"default": self._max_interval} diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index d447790e43229f..c79024c4f64e8c 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -125,6 +125,9 @@ async def _validate_and_create_entry(self, user_input, step_id): errors = {CONF_PASSWORD: "invalid_auth"} return self._show_setup_form(user_input, errors, step_id) + if self.api.requires_2fa: + return await self.async_step_verification_code() + if self.api.requires_2sa: return await self.async_step_trusted_device() @@ -243,22 +246,29 @@ async def _show_trusted_device_form( errors=errors or {}, ) - async def async_step_verification_code(self, user_input=None): + async def async_step_verification_code(self, user_input=None, errors=None): """Ask the verification code to the user.""" - errors = {} + if errors is None: + errors = {} if user_input is None: - return await self._show_verification_code_form(user_input) + return await self._show_verification_code_form(user_input, errors) self._verification_code = user_input[CONF_VERIFICATION_CODE] try: - if not await self.hass.async_add_executor_job( - self.api.validate_verification_code, - self._trusted_device, - self._verification_code, - ): - raise PyiCloudException("The code you entered is not valid.") + if self.api.requires_2fa: + if not await self.hass.async_add_executor_job( + self.api.validate_2fa_code, self._verification_code + ): + raise PyiCloudException("The code you entered is not valid.") + else: + if not await self.hass.async_add_executor_job( + self.api.validate_verification_code, + self._trusted_device, + self._verification_code, + ): + raise PyiCloudException("The code you entered is not valid.") except PyiCloudException as error: # Reset to the initial 2FA state to allow the user to retry _LOGGER.error("Failed to verify verification code: %s", error) @@ -266,7 +276,27 @@ async def async_step_verification_code(self, user_input=None): self._verification_code = None errors["base"] = "validate_verification_code" - return await self.async_step_trusted_device(None, errors) + if self.api.requires_2fa: + try: + self.api = await self.hass.async_add_executor_job( + PyiCloudService, + self._username, + self._password, + self.hass.helpers.storage.Store( + STORAGE_VERSION, STORAGE_KEY + ).path, + True, + None, + self._with_family, + ) + return await self.async_step_verification_code(None, errors) + except PyiCloudFailedLoginException as error: + _LOGGER.error("Error logging into iCloud service: %s", error) + self.api = None + errors = {CONF_PASSWORD: "invalid_auth"} + return self._show_setup_form(user_input, errors, "user") + else: + return await self.async_step_trusted_device(None, errors) return await self.async_step_user( { @@ -278,11 +308,11 @@ async def async_step_verification_code(self, user_input=None): } ) - async def _show_verification_code_form(self, user_input=None): + async def _show_verification_code_form(self, user_input=None, errors=None): """Show the verification_code form to the user.""" return self.async_show_form( step_id=CONF_VERIFICATION_CODE, data_schema=vol.Schema({vol.Required(CONF_VERIFICATION_CODE): str}), - errors=None, + errors=errors or {}, ) diff --git a/homeassistant/components/icloud/const.py b/homeassistant/components/icloud/const.py index d62bacf12120a5..58c62f8a868702 100644 --- a/homeassistant/components/icloud/const.py +++ b/homeassistant/components/icloud/const.py @@ -12,7 +12,7 @@ # to store the cookie STORAGE_KEY = DOMAIN -STORAGE_VERSION = 1 +STORAGE_VERSION = 2 PLATFORMS = ["device_tracker", "sensor"] diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json index 40b58cbf2d0ae0..4d96f42b8cbfa2 100644 --- a/homeassistant/components/icloud/manifest.json +++ b/homeassistant/components/icloud/manifest.json @@ -3,6 +3,6 @@ "name": "Apple iCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/icloud", - "requirements": ["pyicloud==0.9.7"], - "codeowners": ["@Quentame"] + "requirements": ["pyicloud==0.10.2"], + "codeowners": ["@Quentame", "@nzapponi"] } diff --git a/homeassistant/components/icloud/strings.json b/homeassistant/components/icloud/strings.json index d07b3c3b870005..70ab11157d37ce 100644 --- a/homeassistant/components/icloud/strings.json +++ b/homeassistant/components/icloud/strings.json @@ -35,7 +35,7 @@ "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "send_verification_code": "Failed to send verification code", - "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" + "validate_verification_code": "Failed to verify your verification code, try again" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", diff --git a/homeassistant/components/icloud/translations/en.json b/homeassistant/components/icloud/translations/en.json index 3097302ded2954..36e657011e3242 100644 --- a/homeassistant/components/icloud/translations/en.json +++ b/homeassistant/components/icloud/translations/en.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Invalid authentication", "send_verification_code": "Failed to send verification code", - "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" + "validate_verification_code": "Failed to verify your verification code, try again" }, "step": { "reauth": { diff --git a/requirements_all.txt b/requirements_all.txt index 93922e9f93418f..37422a65972e16 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1443,7 +1443,7 @@ pyhomematic==0.1.71 pyhomeworks==0.0.6 # homeassistant.components.icloud -pyicloud==0.9.7 +pyicloud==0.10.2 # homeassistant.components.insteon pyinsteon==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9e3796120600ba..2cd33ee3fc9c66 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -745,7 +745,7 @@ pyheos==0.7.2 pyhomematic==0.1.71 # homeassistant.components.icloud -pyicloud==0.9.7 +pyicloud==0.10.2 # homeassistant.components.insteon pyinsteon==1.0.9 diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py index a774e61f3ece98..998a69c575a297 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -51,6 +51,7 @@ def mock_controller_service(): with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) @@ -58,15 +59,31 @@ def mock_controller_service(): yield service_mock +@pytest.fixture(name="service_2fa") +def mock_controller_2fa_service(): + """Mock a successful 2fa service.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = True + service_mock.return_value.requires_2sa = True + service_mock.return_value.validate_2fa_code = Mock(return_value=True) + service_mock.return_value.is_trusted_session = False + yield service_mock + + @pytest.fixture(name="service_authenticated") def mock_controller_service_authenticated(): """Mock a successful service while already authenticate.""" with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = False + service_mock.return_value.is_trusted_session = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) + service_mock.return_value.validate_2fa_code = Mock(return_value=True) service_mock.return_value.validate_verification_code = Mock(return_value=True) yield service_mock @@ -77,6 +94,7 @@ def mock_controller_service_authenticated_no_device(): with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = False service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) @@ -85,24 +103,53 @@ def mock_controller_service_authenticated_no_device(): yield service_mock +@pytest.fixture(name="service_authenticated_not_trusted") +def mock_controller_service_authenticated_not_trusted(): + """Mock a successful service while already authenticated, but the session is not trusted.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = False + service_mock.return_value.requires_2sa = False + service_mock.return_value.is_trusted_session = False + service_mock.return_value.trusted_devices = TRUSTED_DEVICES + service_mock.return_value.send_verification_code = Mock(return_value=True) + service_mock.return_value.validate_2fa_code = Mock(return_value=True) + service_mock.return_value.validate_verification_code = Mock(return_value=True) + yield service_mock + + @pytest.fixture(name="service_send_verification_code_failed") def mock_controller_service_send_verification_code_failed(): """Mock a failed service during sending verification code step.""" with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=False) yield service_mock +@pytest.fixture(name="service_validate_2fa_code_failed") +def mock_controller_service_validate_2fa_code_failed(): + """Mock a failed service during validation of 2FA verification code step.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = True + service_mock.return_value.validate_2fa_code = Mock(return_value=False) + yield service_mock + + @pytest.fixture(name="service_validate_verification_code_failed") def mock_controller_service_validate_verification_code_failed(): """Mock a failed service during validation of verification code step.""" with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) @@ -409,6 +456,49 @@ async def test_validate_verification_code_failed( assert result["errors"] == {"base": "validate_verification_code"} +async def test_2fa_code_success(hass: HomeAssistantType, service_2fa: MagicMock): + """Test 2fa step success.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + service_2fa.return_value.requires_2fa = False + service_2fa.return_value.requires_2sa = False + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_VERIFICATION_CODE: "0"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == USERNAME + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_WITH_FAMILY] == DEFAULT_WITH_FAMILY + assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL + assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD + + +async def test_validate_2fa_code_failed( + hass: HomeAssistantType, service_validate_2fa_code_failed: MagicMock +): + """Test when we have errors during validate_verification_code.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_VERIFICATION_CODE: "0"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_VERIFICATION_CODE + assert result["errors"] == {"base": "validate_verification_code"} + + async def test_password_update( hass: HomeAssistantType, service_authenticated: MagicMock ): From 51e695fd45d5250be8e32b0c92923ec6c538df67 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 3 Feb 2021 15:35:05 -0500 Subject: [PATCH 0166/1818] add api to refresh topology (#44840) --- homeassistant/components/zha/api.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 91fe51f7793bdf..2bd712ff681c24 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -469,6 +469,19 @@ async def websocket_reconfigure_node(hass, connection, msg): hass.async_create_task(device.async_configure()) +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zha/topology/update", + } +) +async def websocket_update_topology(hass, connection, msg): + """Update the ZHA network topology.""" + zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + hass.async_create_task(zha_gateway.application_controller.topology.scan()) + + @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( @@ -1143,6 +1156,7 @@ async def warning_device_warn(service): websocket_api.async_register_command(hass, websocket_get_bindable_devices) websocket_api.async_register_command(hass, websocket_bind_devices) websocket_api.async_register_command(hass, websocket_unbind_devices) + websocket_api.async_register_command(hass, websocket_update_topology) @callback From adf38f7074764174076ce5a90e93304f87d7e700 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 4 Feb 2021 00:06:54 +0000 Subject: [PATCH 0167/1818] [ci skip] Translation update --- .../components/icloud/translations/ca.json | 2 +- .../components/mazda/translations/ca.json | 35 +++++++++++++++++++ .../components/mazda/translations/et.json | 35 +++++++++++++++++++ .../components/zwave/translations/ca.json | 2 +- .../components/zwave/translations/en.json | 2 +- .../components/zwave/translations/et.json | 2 +- 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/mazda/translations/ca.json create mode 100644 homeassistant/components/mazda/translations/et.json diff --git a/homeassistant/components/icloud/translations/ca.json b/homeassistant/components/icloud/translations/ca.json index a0d74aba98caa5..6e92897161aff5 100644 --- a/homeassistant/components/icloud/translations/ca.json +++ b/homeassistant/components/icloud/translations/ca.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "send_verification_code": "No s'ha pogut enviar el codi de verificaci\u00f3", - "validate_verification_code": "No s'ha pogut verificar el codi de verificaci\u00f3, tria un dispositiu de confian\u00e7a i torna a iniciar el proc\u00e9s" + "validate_verification_code": "No s'ha pogut verificar el codi de verificaci\u00f3, torna-ho a provar" }, "step": { "reauth": { diff --git a/homeassistant/components/mazda/translations/ca.json b/homeassistant/components/mazda/translations/ca.json new file mode 100644 index 00000000000000..d45b9177c3f1d8 --- /dev/null +++ b/homeassistant/components/mazda/translations/ca.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "error": { + "account_locked": "Compte bloquejat. Intenta-ho m\u00e9s tard.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "reauth": { + "data": { + "email": "Correu electr\u00f2nic", + "password": "Contrasenya", + "region": "Regi\u00f3" + }, + "description": "Ha fallat l'autenticaci\u00f3 dels Serveis connectats de Mazda. Introdueix les teves credencials actuals.", + "title": "Serveis connectats de Mazda - Ha fallat l'autenticaci\u00f3" + }, + "user": { + "data": { + "email": "Correu electr\u00f2nic", + "password": "Contrasenya", + "region": "Regi\u00f3" + }, + "description": "Introdueix el correu electr\u00f2nic i la contrasenya que utilitzes per iniciar sessi\u00f3 a l'aplicaci\u00f3 de m\u00f2bil MyMazda.", + "title": "Serveis connectats de Mazda - Afegeix un compte" + } + } + }, + "title": "Serveis connectats de Mazda" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/et.json b/homeassistant/components/mazda/translations/et.json new file mode 100644 index 00000000000000..4ce2e2fa5f373f --- /dev/null +++ b/homeassistant/components/mazda/translations/et.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "account_locked": "Konto on lukus. Proovi hiljem uuesti.", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "reauth": { + "data": { + "email": "E-posti aadress", + "password": "Salas\u00f5na", + "region": "Piirkond" + }, + "description": "Mazda Connected Services tuvastamine nurjus. Sisesta oma kehtivad andmed.", + "title": "Mazda Connected Services - tuvastamine nurjus" + }, + "user": { + "data": { + "email": "E-posti aadress", + "password": "Salas\u00f5na", + "region": "Piirkond" + }, + "description": "Sisesta e-posti aadress ja salas\u00f5na mida kasutad MyMazda mobiilirakendusse sisselogimiseks.", + "title": "Mazda Connected Services - lisa konto" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/ca.json b/homeassistant/components/zwave/translations/ca.json index 3c97d8c212f25c..13805a2d1ed9e3 100644 --- a/homeassistant/components/zwave/translations/ca.json +++ b/homeassistant/components/zwave/translations/ca.json @@ -13,7 +13,7 @@ "network_key": "Clau de xarxa (deixa-ho en blanc per generar-la autom\u00e0ticament)", "usb_path": "Ruta del port USB del dispositiu" }, - "description": "Consulta https://www.home-assistant.io/docs/z-wave/installation/ per obtenir informaci\u00f3 sobre les variables de configuraci\u00f3", + "description": "Aquesta integraci\u00f3 ja no s'actualitzar\u00e0. Utilitza Z-Wave JS per a instal\u00b7lacions noves.\n\nConsulta https://www.home-assistant.io/docs/z-wave/installation/ per a m\u00e9s informaci\u00f3 sobre les variables de configuraci\u00f3", "title": "Configuraci\u00f3 de Z-Wave" } } diff --git a/homeassistant/components/zwave/translations/en.json b/homeassistant/components/zwave/translations/en.json index d13e5575e610ac..2fe3e15646a866 100644 --- a/homeassistant/components/zwave/translations/en.json +++ b/homeassistant/components/zwave/translations/en.json @@ -13,7 +13,7 @@ "network_key": "Network Key (leave blank to auto-generate)", "usb_path": "USB Device Path" }, - "description": "See https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables", + "description": "This integration is no longer maintained. For new installations, use Z-Wave JS instead.\n\nSee https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables", "title": "Set up Z-Wave" } } diff --git a/homeassistant/components/zwave/translations/et.json b/homeassistant/components/zwave/translations/et.json index ef36101b3ad057..b1fa6127076fd5 100644 --- a/homeassistant/components/zwave/translations/et.json +++ b/homeassistant/components/zwave/translations/et.json @@ -13,7 +13,7 @@ "network_key": "V\u00f5rguv\u00f5ti (j\u00e4ta automaatse genereerimise jaoks t\u00fchjaks)", "usb_path": "USB seadme rada" }, - "description": "Konfiguratsioonimuutujate kohta leiad teavet https://www.home-assistant.io/docs/z-wave/installation/", + "description": "Seda sidumist enam ei hallata. Uueks sidumiseks kasuta Z-Wave JS.\n\nKonfiguratsioonimuutujate kohta leiad teavet https://www.home-assistant.io/docs/z-wave/installation/", "title": "Seadista Z-Wave" } } From 04f39d7dd4fcfae02783ef0cf4d7bcc772f8bec1 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 3 Feb 2021 19:19:22 -0500 Subject: [PATCH 0168/1818] Use core constants for command_line auth provider (#45907) --- homeassistant/auth/providers/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index d194d8119d1d25..b2b192219790cd 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -8,12 +8,12 @@ import voluptuous as vol +from homeassistant.const import CONF_COMMAND from homeassistant.exceptions import HomeAssistantError from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from ..models import Credentials, UserMeta -CONF_COMMAND = "command" CONF_ARGS = "args" CONF_META = "meta" From 44914c01ac9ecfedac9a268bc8b7fe690557b8be Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 4 Feb 2021 01:26:32 +0100 Subject: [PATCH 0169/1818] Fix typo in Roomba strings (#45928) --- homeassistant/components/roomba/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 512e27a758e36d..48e130df4f5123 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -23,7 +23,7 @@ }, "link_manual": { "title": "Enter Password", - "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", + "description": "The password could not be retrieved from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", "data": { "password": "[%key:common::config_flow::data::password%]" } From 5a0715d388487f7a47659722876b9f4c280905cd Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 4 Feb 2021 01:43:07 +0100 Subject: [PATCH 0170/1818] Consistent spelling of IT abbreviations / protocol / format names (#45913) --- homeassistant/components/ambiclimate/strings.json | 2 +- homeassistant/components/cast/services.yaml | 2 +- homeassistant/components/color_extractor/services.yaml | 2 +- homeassistant/components/deconz/services.yaml | 2 +- homeassistant/components/denonavr/services.yaml | 2 +- homeassistant/components/frontend/services.yaml | 2 +- homeassistant/components/goalzero/strings.json | 2 +- homeassistant/components/habitica/services.yaml | 4 ++-- homeassistant/components/homekit/services.yaml | 2 +- homeassistant/components/konnected/strings.json | 2 +- homeassistant/components/lovelace/services.yaml | 2 +- homeassistant/components/lutron_caseta/strings.json | 2 +- homeassistant/components/media_extractor/services.yaml | 2 +- homeassistant/components/mqtt/services.yaml | 2 +- homeassistant/components/soma/strings.json | 2 +- homeassistant/components/spotify/strings.json | 2 +- homeassistant/components/traccar/strings.json | 2 +- homeassistant/components/vera/strings.json | 4 ++-- homeassistant/components/xiaomi_aqara/strings.json | 4 ++-- homeassistant/strings.json | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/ambiclimate/strings.json b/homeassistant/components/ambiclimate/strings.json index b08b8919da2dcf..c51c25a2f61cbe 100644 --- a/homeassistant/components/ambiclimate/strings.json +++ b/homeassistant/components/ambiclimate/strings.json @@ -3,7 +3,7 @@ "step": { "auth": { "title": "Authenticate Ambiclimate", - "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback url is {cb_url})" + "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback URL is {cb_url})" } }, "create_entry": { diff --git a/homeassistant/components/cast/services.yaml b/homeassistant/components/cast/services.yaml index d1c29281aad690..8e4466c349ce4f 100644 --- a/homeassistant/components/cast/services.yaml +++ b/homeassistant/components/cast/services.yaml @@ -5,7 +5,7 @@ show_lovelace_view: description: Media Player entity to show the Lovelace view on. example: "media_player.kitchen" dashboard_path: - description: The url path of the Lovelace dashboard to show. + description: The URL path of the Lovelace dashboard to show. example: lovelace-cast view_path: description: The path of the Lovelace view to show. diff --git a/homeassistant/components/color_extractor/services.yaml b/homeassistant/components/color_extractor/services.yaml index fa97dacf3d13c0..33055fd41b9438 100644 --- a/homeassistant/components/color_extractor/services.yaml +++ b/homeassistant/components/color_extractor/services.yaml @@ -1,5 +1,5 @@ turn_on: - description: Set the light RGB to the predominant color found in the image provided by url or file path. + description: Set the light RGB to the predominant color found in the image provided by URL or file path. fields: color_extract_url: description: The URL of the image we want to extract RGB values from. Must be allowed in allowlist_external_urls. diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml index 9d85e76d8d3a53..1703037fc64c5f 100644 --- a/homeassistant/components/deconz/services.yaml +++ b/homeassistant/components/deconz/services.yaml @@ -11,7 +11,7 @@ configure: entity (when entity is specified). example: '"/lights/1/state" or "/state"' data: - description: Data is a json object with what data you want to alter. + description: Data is a JSON object with what data you want to alter. example: '{"on": true}' bridgeid: description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name. diff --git a/homeassistant/components/denonavr/services.yaml b/homeassistant/components/denonavr/services.yaml index c9831a68aa5294..35dedd8fb7f26e 100644 --- a/homeassistant/components/denonavr/services.yaml +++ b/homeassistant/components/denonavr/services.yaml @@ -1,7 +1,7 @@ # Describes the format for available webostv services get_command: - description: "Send a generic http get command." + description: "Send a generic HTTP get command." fields: entity_id: description: Name(s) of the denonavr entities where to run the API method. diff --git a/homeassistant/components/frontend/services.yaml b/homeassistant/components/frontend/services.yaml index cc0d6bde21645b..075b73986ffc1d 100644 --- a/homeassistant/components/frontend/services.yaml +++ b/homeassistant/components/frontend/services.yaml @@ -11,4 +11,4 @@ set_theme: example: "dark" reload_themes: - description: Reload themes from yaml configuration. + description: Reload themes from YAML configuration. diff --git a/homeassistant/components/goalzero/strings.json b/homeassistant/components/goalzero/strings.json index 0a1b8909f543b0..bd59cd5e7f5f43 100644 --- a/homeassistant/components/goalzero/strings.json +++ b/homeassistant/components/goalzero/strings.json @@ -3,7 +3,7 @@ "step": { "user": { "title": "Goal Zero Yeti", - "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wifi network. Then get the host ip from your router. DHCP must be set up in your router settings for the device to ensure the host ip does not change. Refer to your router's user manual.", + "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wifi network. Then get the host IP from your router. DHCP must be set up in your router settings for the device to ensure the host IP does not change. Refer to your router's user manual.", "data": { "host": "[%key:common::config_flow::data::host%]", "name": "[%key:common::config_flow::data::name%]" diff --git a/homeassistant/components/habitica/services.yaml b/homeassistant/components/habitica/services.yaml index 20794b4c47bfce..6fa8589ba4c119 100644 --- a/homeassistant/components/habitica/services.yaml +++ b/homeassistant/components/habitica/services.yaml @@ -1,6 +1,6 @@ # Describes the format for Habitica service api_call: - description: Call Habitica api + description: Call Habitica API fields: name: description: Habitica's username to call for @@ -9,5 +9,5 @@ api_call: description: "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks" example: '["tasks", "user", "post"]' args: - description: Any additional json or url parameter arguments. See apidoc mentioned for path. Example uses same api endpoint + description: Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint example: '{"text": "Use API from Home Assistant", "type": "todo"}' diff --git a/homeassistant/components/homekit/services.yaml b/homeassistant/components/homekit/services.yaml index c2dde2cac6c975..96971f7030033d 100644 --- a/homeassistant/components/homekit/services.yaml +++ b/homeassistant/components/homekit/services.yaml @@ -4,7 +4,7 @@ start: description: Starts the HomeKit driver. reload: - description: Reload homekit and re-process yaml configuration. + description: Reload homekit and re-process YAML configuration. reset_accessory: description: Reset a HomeKit accessory. This can be useful when changing a media_player’s device class to tv, linking a battery, or whenever Home Assistant adds support for new HomeKit features to existing entities. diff --git a/homeassistant/components/konnected/strings.json b/homeassistant/components/konnected/strings.json index 4fd62dee8e7c9d..e53838ad0d7db6 100644 --- a/homeassistant/components/konnected/strings.json +++ b/homeassistant/components/konnected/strings.json @@ -99,7 +99,7 @@ } }, "error": { - "bad_host": "Invalid Override API host url" + "bad_host": "Invalid Override API host URL" }, "abort": { "not_konn_panel": "Not a recognized Konnected.io device" diff --git a/homeassistant/components/lovelace/services.yaml b/homeassistant/components/lovelace/services.yaml index 1147f287e596af..20450942dce572 100644 --- a/homeassistant/components/lovelace/services.yaml +++ b/homeassistant/components/lovelace/services.yaml @@ -1,4 +1,4 @@ # Describes the format for available lovelace services reload_resources: - description: Reload Lovelace resources from yaml configuration. + description: Reload Lovelace resources from YAML configuration. diff --git a/homeassistant/components/lutron_caseta/strings.json b/homeassistant/components/lutron_caseta/strings.json index bdaec22e776475..604a3c24ab2e57 100644 --- a/homeassistant/components/lutron_caseta/strings.json +++ b/homeassistant/components/lutron_caseta/strings.json @@ -8,7 +8,7 @@ }, "user": { "title": "Automaticlly connect to the bridge", - "description": "Enter the ip address of the device.", + "description": "Enter the IP address of the device.", "data": { "host": "[%key:common::config_flow::data::host%]" } diff --git a/homeassistant/components/media_extractor/services.yaml b/homeassistant/components/media_extractor/services.yaml index 17abffee89d31a..1e58c19baf1beb 100644 --- a/homeassistant/components/media_extractor/services.yaml +++ b/homeassistant/components/media_extractor/services.yaml @@ -1,5 +1,5 @@ play_media: - description: Downloads file from given url. + description: Downloads file from given URL. fields: entity_id: description: Name(s) of entities to play media on. diff --git a/homeassistant/components/mqtt/services.yaml b/homeassistant/components/mqtt/services.yaml index 04dce23f5def94..992dd1b3545112 100644 --- a/homeassistant/components/mqtt/services.yaml +++ b/homeassistant/components/mqtt/services.yaml @@ -37,4 +37,4 @@ dump: default: 5 reload: - description: Reload all mqtt entities from yaml. + description: Reload all MQTT entities from YAML. diff --git a/homeassistant/components/soma/strings.json b/homeassistant/components/soma/strings.json index 8ab7335dd5c63f..a31b404dad72df 100644 --- a/homeassistant/components/soma/strings.json +++ b/homeassistant/components/soma/strings.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "You can only configure one Soma account.", - "authorize_url_timeout": "Timeout generating authorize url.", + "authorize_url_timeout": "Timeout generating authorize URL.", "missing_configuration": "The Soma component is not configured. Please follow the documentation.", "result_error": "SOMA Connect responded with error status.", "connection_error": "Failed to connect to SOMA Connect." diff --git a/homeassistant/components/spotify/strings.json b/homeassistant/components/spotify/strings.json index 74df79c4d78fcb..f775e5df85d4ce 100644 --- a/homeassistant/components/spotify/strings.json +++ b/homeassistant/components/spotify/strings.json @@ -10,7 +10,7 @@ } }, "abort": { - "authorize_url_timeout": "Timeout generating authorize url.", + "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.", "reauth_account_mismatch": "The Spotify account authenticated with, does not match the account needed re-authentication." diff --git a/homeassistant/components/traccar/strings.json b/homeassistant/components/traccar/strings.json index d9d9fff4bd3fb2..89689ee43df9a0 100644 --- a/homeassistant/components/traccar/strings.json +++ b/homeassistant/components/traccar/strings.json @@ -11,7 +11,7 @@ "webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]" }, "create_entry": { - "default": "To send events to Home Assistant, you will need to setup the webhook feature in Traccar.\n\nUse the following url: `{webhook_url}`\n\nSee [the documentation]({docs_url}) for further details." + "default": "To send events to Home Assistant, you will need to setup the webhook feature in Traccar.\n\nUse the following URL: `{webhook_url}`\n\nSee [the documentation]({docs_url}) for further details." } } } diff --git a/homeassistant/components/vera/strings.json b/homeassistant/components/vera/strings.json index 844d1777f5d752..66958f44a62560 100644 --- a/homeassistant/components/vera/strings.json +++ b/homeassistant/components/vera/strings.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "cannot_connect": "Could not connect to controller with url {base_url}" + "cannot_connect": "Could not connect to controller with URL {base_url}" }, "step": { "user": { "title": "Setup Vera controller", - "description": "Provide a Vera controller url below. It should look like this: http://192.168.1.161:3480.", + "description": "Provide a Vera controller URL below. It should look like this: http://192.168.1.161:3480.", "data": { "vera_controller_url": "Controller URL", "lights": "Vera switch device ids to treat as lights in Home Assistant.", diff --git a/homeassistant/components/xiaomi_aqara/strings.json b/homeassistant/components/xiaomi_aqara/strings.json index d5fb25d2c3f3ea..a2c8a226c95eac 100644 --- a/homeassistant/components/xiaomi_aqara/strings.json +++ b/homeassistant/components/xiaomi_aqara/strings.json @@ -4,7 +4,7 @@ "step": { "user": { "title": "Xiaomi Aqara Gateway", - "description": "Connect to your Xiaomi Aqara Gateway, if the IP and mac addresses are left empty, auto-discovery is used", + "description": "Connect to your Xiaomi Aqara Gateway, if the IP and MAC addresses are left empty, auto-discovery is used", "data": { "interface": "The network interface to use", "host": "[%key:common::config_flow::data::ip%] (optional)", @@ -21,7 +21,7 @@ }, "select": { "title": "Select the Xiaomi Aqara Gateway that you wish to connect", - "description": "Run the setup again if you want to connect aditional gateways", + "description": "Run the setup again if you want to connect additional gateways", "data": { "select_ip": "[%key:common::config_flow::data::ip%]" } diff --git a/homeassistant/strings.json b/homeassistant/strings.json index e2a85637fbbc64..b8e7dee299615a 100644 --- a/homeassistant/strings.json +++ b/homeassistant/strings.json @@ -71,7 +71,7 @@ "oauth2_authorize_url_timeout": "Timeout generating authorize URL.", "oauth2_no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", "reauth_successful": "Re-authentication was successful", - "unknown_authorize_url_generation": "Unknown error generating an authorize url." + "unknown_authorize_url_generation": "Unknown error generating an authorize URL." } } } From 14d914e300ff5e8f2e3b607d4f1e7d108cd1f690 Mon Sep 17 00:00:00 2001 From: denes44 <60078357+denes44@users.noreply.github.com> Date: Thu, 4 Feb 2021 02:35:27 +0100 Subject: [PATCH 0171/1818] Enable emulated_hue setting XY color and transition time by client (#45844) * Enable setting XY color and transition time by client * New test for setting XY color value * Correct block outdent * New test for setting transition time value * Fixed commented out code --- .../components/emulated_hue/hue_api.py | 30 ++++++++++ tests/components/emulated_hue/test_hue_api.py | 58 ++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 51580a28adf710..1630405a73ec75 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -43,9 +43,12 @@ ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, + ATTR_TRANSITION, + ATTR_XY_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + SUPPORT_TRANSITION, ) from homeassistant.components.media_player.const import ( ATTR_MEDIA_VOLUME_LEVEL, @@ -82,6 +85,8 @@ STATE_HUE = "hue" STATE_SATURATION = "sat" STATE_COLOR_TEMP = "ct" +STATE_TRANSITON = "tt" +STATE_XY = "xy" # Hue API states, defined separately in case they change HUE_API_STATE_ON = "on" @@ -90,7 +95,9 @@ HUE_API_STATE_HUE = "hue" HUE_API_STATE_SAT = "sat" HUE_API_STATE_CT = "ct" +HUE_API_STATE_XY = "xy" HUE_API_STATE_EFFECT = "effect" +HUE_API_STATE_TRANSITION = "transitiontime" # Hue API min/max values - https://developers.meethue.com/develop/hue-api/lights-api/ HUE_API_STATE_BRI_MIN = 1 # Brightness @@ -357,6 +364,8 @@ async def put(self, request, username, entity_number): STATE_HUE: None, STATE_SATURATION: None, STATE_COLOR_TEMP: None, + STATE_XY: None, + STATE_TRANSITON: None, } if HUE_API_STATE_ON in request_json: @@ -372,6 +381,7 @@ async def put(self, request, username, entity_number): (HUE_API_STATE_HUE, STATE_HUE), (HUE_API_STATE_SAT, STATE_SATURATION), (HUE_API_STATE_CT, STATE_COLOR_TEMP), + (HUE_API_STATE_TRANSITION, STATE_TRANSITON), ): if key in request_json: try: @@ -379,6 +389,17 @@ async def put(self, request, username, entity_number): except ValueError: _LOGGER.error("Unable to parse data (2): %s", request_json) return self.json_message("Bad request", HTTP_BAD_REQUEST) + if HUE_API_STATE_XY in request_json: + try: + parsed[STATE_XY] = tuple( + ( + float(request_json[HUE_API_STATE_XY][0]), + float(request_json[HUE_API_STATE_XY][1]), + ) + ) + except ValueError: + _LOGGER.error("Unable to parse data (2): %s", request_json) + return self.json_message("Bad request", HTTP_BAD_REQUEST) if HUE_API_STATE_BRI in request_json: if entity.domain == light.DOMAIN: @@ -444,10 +465,17 @@ async def put(self, request, username, entity_number): data[ATTR_HS_COLOR] = (hue, sat) + if parsed[STATE_XY] is not None: + data[ATTR_XY_COLOR] = parsed[STATE_XY] + if entity_features & SUPPORT_COLOR_TEMP: if parsed[STATE_COLOR_TEMP] is not None: data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP] + if entity_features & SUPPORT_TRANSITION: + if parsed[STATE_TRANSITON] is not None: + data[ATTR_TRANSITION] = parsed[STATE_TRANSITON] / 10 + # If the requested entity is a script, add some variables elif entity.domain == script.DOMAIN: data["variables"] = { @@ -557,6 +585,8 @@ async def put(self, request, username, entity_number): (STATE_HUE, HUE_API_STATE_HUE), (STATE_SATURATION, HUE_API_STATE_SAT), (STATE_COLOR_TEMP, HUE_API_STATE_CT), + (STATE_XY, HUE_API_STATE_XY), + (STATE_TRANSITON, HUE_API_STATE_TRANSITION), ): if parsed[key] is not None: json_response.append( diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 04832f4adc7223..e3f965616f903b 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -28,6 +28,8 @@ HUE_API_STATE_HUE, HUE_API_STATE_ON, HUE_API_STATE_SAT, + HUE_API_STATE_TRANSITION, + HUE_API_STATE_XY, HUE_API_USERNAME, HueAllGroupsStateView, HueAllLightsStateView, @@ -39,6 +41,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONTENT_TYPE_JSON, HTTP_NOT_FOUND, HTTP_OK, @@ -51,7 +54,11 @@ from homeassistant.core import callback import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, get_test_instance_port +from tests.common import ( + async_fire_time_changed, + async_mock_service, + get_test_instance_port, +) HTTP_SERVER_PORT = get_test_instance_port() BRIDGE_SERVER_PORT = get_test_instance_port() @@ -663,6 +670,25 @@ async def test_put_light_state(hass, hass_hue, hue_client): assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 assert ceiling_json["state"][HUE_API_STATE_SAT] == 127 + # update light state through api + await perform_put_light_state( + hass_hue, + hue_client, + "light.ceiling_lights", + True, + brightness=100, + xy=((0.488, 0.48)), + ) + + # go through api to get the state back + ceiling_json = await perform_get_light_state( + hue_client, "light.ceiling_lights", HTTP_OK + ) + assert ceiling_json["state"][HUE_API_STATE_BRI] == 100 + assert hass.states.get("light.ceiling_lights").attributes[light.ATTR_XY_COLOR] == ( + (0.488, 0.48) + ) + # Go through the API to turn it off ceiling_result = await perform_put_light_state( hass_hue, hue_client, "light.ceiling_lights", False @@ -714,6 +740,30 @@ async def test_put_light_state(hass, hass_hue, hue_client): == 50 ) + # mock light.turn_on call + hass.states.async_set( + "light.ceiling_lights", STATE_ON, {ATTR_SUPPORTED_FEATURES: 55} + ) + call_turn_on = async_mock_service(hass, "light", "turn_on") + + # update light state through api + await perform_put_light_state( + hass_hue, + hue_client, + "light.ceiling_lights", + True, + brightness=99, + xy=((0.488, 0.48)), + transitiontime=60, + ) + + await hass.async_block_till_done() + assert call_turn_on[0] + assert call_turn_on[0].data[ATTR_ENTITY_ID] == ["light.ceiling_lights"] + assert call_turn_on[0].data[light.ATTR_BRIGHTNESS] == 99 + assert call_turn_on[0].data[light.ATTR_XY_COLOR] == ((0.488, 0.48)) + assert call_turn_on[0].data[light.ATTR_TRANSITION] == 6 + async def test_put_light_state_script(hass, hass_hue, hue_client): """Test the setting of script variables.""" @@ -1173,6 +1223,8 @@ async def perform_put_light_state( saturation=None, color_temp=None, with_state=True, + xy=None, + transitiontime=None, ): """Test the setting of a light state.""" req_headers = {"Content-Type": content_type} @@ -1188,8 +1240,12 @@ async def perform_put_light_state( data[HUE_API_STATE_HUE] = hue if saturation is not None: data[HUE_API_STATE_SAT] = saturation + if xy is not None: + data[HUE_API_STATE_XY] = xy if color_temp is not None: data[HUE_API_STATE_CT] = color_temp + if transitiontime is not None: + data[HUE_API_STATE_TRANSITION] = transitiontime entity_number = ENTITY_NUMBERS_BY_ID[entity_id] result = await client.put( From 1b6ee8301a5c076f93d0799b9f7fcb82cc6eb902 Mon Sep 17 00:00:00 2001 From: Christopher Gozdziewski Date: Thu, 4 Feb 2021 01:32:43 -0600 Subject: [PATCH 0172/1818] Convert ozw climate values to correct units (#45369) * Convert ozw climate values to correct units * Remove debugger logging * Fix black code formatting * Remove extra spaces * Add method descriptions and change to use setpoint * Fix build and respond to comments * Remove self from convert_units call * Move method to top * Move method outside class * Add blank lines * Fix test to use farenheit * Update another value to farenheit * Change to celsius * Another test fix * test fix * Fix a value * missed one * Add unit test for convert_units * fix unit test import * Add new line to end of test file * fix convert units import * Reorder imports * Grab const from different import Co-authored-by: Trevor --- homeassistant/components/ozw/climate.py | 51 +++++++++++++++++++++---- tests/components/ozw/test_climate.py | 20 ++++++---- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index a74fd869f0f443..67bbe5cdc4dd06 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -28,6 +28,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.temperature import convert as convert_temperature from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity @@ -154,6 +155,13 @@ def async_add_climate(values): ) +def convert_units(units): + """Return units as a farenheit or celsius constant.""" + if units == "F": + return TEMP_FAHRENHEIT + return TEMP_CELSIUS + + class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): """Representation of a Z-Wave Climate device.""" @@ -199,16 +207,18 @@ def fan_modes(self): @property def temperature_unit(self): """Return the unit of measurement.""" - if self.values.temperature is not None and self.values.temperature.units == "F": - return TEMP_FAHRENHEIT - return TEMP_CELSIUS + return convert_units(self._current_mode_setpoint_values[0].units) @property def current_temperature(self): """Return the current temperature.""" if not self.values.temperature: return None - return self.values.temperature.value + return convert_temperature( + self.values.temperature.value, + convert_units(self._current_mode_setpoint_values[0].units), + self.temperature_unit, + ) @property def hvac_action(self): @@ -236,17 +246,29 @@ def preset_modes(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._current_mode_setpoint_values[0].value + return convert_temperature( + self._current_mode_setpoint_values[0].value, + convert_units(self._current_mode_setpoint_values[0].units), + self.temperature_unit, + ) @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return self._current_mode_setpoint_values[0].value + return convert_temperature( + self._current_mode_setpoint_values[0].value, + convert_units(self._current_mode_setpoint_values[0].units), + self.temperature_unit, + ) @property def target_temperature_high(self) -> Optional[float]: """Return the highbound target temperature we try to reach.""" - return self._current_mode_setpoint_values[1].value + return convert_temperature( + self._current_mode_setpoint_values[1].value, + convert_units(self._current_mode_setpoint_values[1].units), + self.temperature_unit, + ) async def async_set_temperature(self, **kwargs): """Set new target temperature. @@ -262,14 +284,29 @@ async def async_set_temperature(self, **kwargs): setpoint = self._current_mode_setpoint_values[0] target_temp = kwargs.get(ATTR_TEMPERATURE) if setpoint is not None and target_temp is not None: + target_temp = convert_temperature( + target_temp, + self.temperature_unit, + convert_units(setpoint.units), + ) setpoint.send_value(target_temp) elif len(self._current_mode_setpoint_values) == 2: (setpoint_low, setpoint_high) = self._current_mode_setpoint_values target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if setpoint_low is not None and target_temp_low is not None: + target_temp_low = convert_temperature( + target_temp_low, + self.temperature_unit, + convert_units(setpoint_low.units), + ) setpoint_low.send_value(target_temp_low) if setpoint_high is not None and target_temp_high is not None: + target_temp_high = convert_temperature( + target_temp_high, + self.temperature_unit, + convert_units(setpoint_high.units), + ) setpoint_high.send_value(target_temp_high) async def async_set_fan_mode(self, fan_mode): diff --git a/tests/components/ozw/test_climate.py b/tests/components/ozw/test_climate.py index 3414e6c483279a..e251a93c11520c 100644 --- a/tests/components/ozw/test_climate.py +++ b/tests/components/ozw/test_climate.py @@ -16,6 +16,8 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, ) +from homeassistant.components.ozw.climate import convert_units +from homeassistant.const import TEMP_FAHRENHEIT from .common import setup_ozw @@ -36,8 +38,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): HVAC_MODE_HEAT_COOL, ] assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 23.1 - assert state.attributes[ATTR_TEMPERATURE] == 21.1 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 73.5 + assert state.attributes[ATTR_TEMPERATURE] == 70.0 assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None assert state.attributes[ATTR_FAN_MODE] == "Auto Low" @@ -54,7 +56,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 78.98 + assert round(msg["payload"]["Value"], 2) == 26.1 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test hvac_mode with set_temperature @@ -72,7 +74,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 75.38 + assert round(msg["payload"]["Value"], 2) == 24.1 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test set mode @@ -127,8 +129,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert state is not None assert state.state == HVAC_MODE_HEAT_COOL assert state.attributes.get(ATTR_TEMPERATURE) is None - assert state.attributes[ATTR_TARGET_TEMP_LOW] == 21.1 - assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.6 + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 70.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 78.0 # Test setting high/low temp on multiple setpoints await hass.services.async_call( @@ -144,11 +146,11 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert len(sent_messages) == 7 # 2 messages ! msg = sent_messages[-2] # low setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 68.0 + assert round(msg["payload"]["Value"], 2) == 20.0 assert msg["payload"]["ValueIDKey"] == 281475099443218 msg = sent_messages[-1] # high setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 77.0 + assert round(msg["payload"]["Value"], 2) == 25.0 assert msg["payload"]["ValueIDKey"] == 562950076153874 # Test basic/single-setpoint thermostat (node 16 in dump) @@ -325,3 +327,5 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): ) assert len(sent_messages) == 12 assert "does not support setting a mode" in caplog.text + + assert convert_units("F") == TEMP_FAHRENHEIT From 8256acb8efb3f32ff302f4766a85a6830f306cd5 Mon Sep 17 00:00:00 2001 From: olijouve <17448560+olijouve@users.noreply.github.com> Date: Thu, 4 Feb 2021 09:25:35 +0100 Subject: [PATCH 0173/1818] Fix onvif ConnectionResetError (#45899) Fix "ConnectionResetError: Cannot write to closing transport" error we can have on lots of chinese cams(like Goke GK7102 based IP cameras) Those non full onvif compliant cams can "crash" when calling non implemented functions like events or ptz and they are likely react by closing transport, leaving the request in a uncatched error state. My camera used to fail on setup, and now it run nicely with that simple fix. --- homeassistant/components/onvif/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 84761a4777f890..c0851cbe32f04a 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -250,14 +250,14 @@ async def async_get_capabilities(self): pullpoint = False try: pullpoint = await self.events.async_start() - except (ONVIFError, Fault): + except (ONVIFError, Fault, RequestError): pass ptz = False try: self.device.get_definition("ptz") ptz = True - except ONVIFError: + except (ONVIFError, Fault, RequestError): pass return Capabilities(snapshot, pullpoint, ptz) From 06e6005fbba2420f708bcf4046e21dbcb6dcd8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Feb 2021 09:59:41 +0100 Subject: [PATCH 0174/1818] Add warning to custom integrations without version (#45919) Co-authored-by: Paulus Schoutsen --- homeassistant/loader.py | 63 ++++++++++++++++++++++++--- homeassistant/package_constraints.txt | 1 + requirements.txt | 1 + requirements_test.txt | 1 - script/bootstrap | 2 +- script/hassfest/manifest.py | 15 ++----- setup.py | 1 + tests/hassfest/test_version.py | 47 ++++++++++++++++++++ tests/test_loader.py | 62 +++++++++++++++++++++++++- 9 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 tests/hassfest/test_version.py diff --git a/homeassistant/loader.py b/homeassistant/loader.py index bedc04928af2d6..f1a0ccc07309b5 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -26,6 +26,9 @@ cast, ) +from awesomeversion import AwesomeVersion +from awesomeversion.strategy import AwesomeVersionStrategy + from homeassistant.generated.dhcp import DHCP from homeassistant.generated.mqtt import MQTT from homeassistant.generated.ssdp import SSDP @@ -52,7 +55,19 @@ "You are using a custom integration %s which has not " "been tested by Home Assistant. This component might " "cause stability problems, be sure to disable it if you " - "experience issues with Home Assistant." + "experience issues with Home Assistant" +) +CUSTOM_WARNING_VERSION_MISSING = ( + "No 'version' key in the manifest file for " + "custom integration '%s'. This will not be " + "allowed in a future version of Home " + "Assistant. Please report this to the " + "maintainer of '%s'" +) +CUSTOM_WARNING_VERSION_TYPE = ( + "'%s' is not a valid version for " + "custom integration '%s'. " + "Please report this to the maintainer of '%s'" ) _UNDEF = object() # Internal; not helpers.typing.UNDEFINED due to circular dependency @@ -83,6 +98,7 @@ class Manifest(TypedDict, total=False): dhcp: List[Dict[str, str]] homekit: Dict[str, List[str]] is_built_in: bool + version: str codeowners: List[str] @@ -417,6 +433,13 @@ def is_built_in(self) -> bool: """Test if package is a built-in integration.""" return self.pkg_path.startswith(PACKAGE_BUILTIN) + @property + def version(self) -> Optional[AwesomeVersion]: + """Return the version of the integration.""" + if "version" not in self.manifest: + return None + return AwesomeVersion(self.manifest["version"]) + @property def all_dependencies(self) -> Set[str]: """Return all dependencies including sub-dependencies.""" @@ -513,7 +536,7 @@ async def async_get_integration(hass: "HomeAssistant", domain: str) -> Integrati # components to find the integration. integration = (await async_get_custom_components(hass)).get(domain) if integration is not None: - _LOGGER.warning(CUSTOM_WARNING, domain) + custom_integration_warning(integration) cache[domain] = integration event.set() return integration @@ -531,6 +554,7 @@ async def async_get_integration(hass: "HomeAssistant", domain: str) -> Integrati integration = Integration.resolve_legacy(hass, domain) if integration is not None: + custom_integration_warning(integration) cache[domain] = integration else: # Remove event from cache. @@ -605,9 +629,6 @@ def _load_file( cache[comp_or_platform] = module - if module.__name__.startswith(PACKAGE_CUSTOM_COMPONENTS): - _LOGGER.warning(CUSTOM_WARNING, comp_or_platform) - return module except ImportError as err: @@ -756,3 +777,35 @@ def _lookup_path(hass: "HomeAssistant") -> List[str]: if hass.config.safe_mode: return [PACKAGE_BUILTIN] return [PACKAGE_CUSTOM_COMPONENTS, PACKAGE_BUILTIN] + + +def validate_custom_integration_version(version: str) -> bool: + """Validate the version of custom integrations.""" + return AwesomeVersion(version).strategy in ( + AwesomeVersionStrategy.CALVER, + AwesomeVersionStrategy.SEMVER, + AwesomeVersionStrategy.SIMPLEVER, + AwesomeVersionStrategy.BUILDVER, + AwesomeVersionStrategy.PEP440, + ) + + +def custom_integration_warning(integration: Integration) -> None: + """Create logs for custom integrations.""" + if not integration.pkg_path.startswith(PACKAGE_CUSTOM_COMPONENTS): + return None + + _LOGGER.warning(CUSTOM_WARNING, integration.domain) + + if integration.manifest.get("version") is None: + _LOGGER.warning( + CUSTOM_WARNING_VERSION_MISSING, integration.domain, integration.domain + ) + else: + if not validate_custom_integration_version(integration.manifest["version"]): + _LOGGER.warning( + CUSTOM_WARNING_VERSION_TYPE, + integration.domain, + integration.manifest["version"], + integration.domain, + ) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b280c23982d5a3..69f75af396b2d2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -5,6 +5,7 @@ aiohttp_cors==0.7.0 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 +awesomeversion==21.2.0 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/requirements.txt b/requirements.txt index c094efe3e46775..17bb82d472f1dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ aiohttp==3.7.3 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 +awesomeversion==21.2.0 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/requirements_test.txt b/requirements_test.txt index 2c10083ecfd261..077c895293f5fa 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,7 +13,6 @@ pre-commit==2.10.0 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 -awesomeversion==21.2.0 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.10.1 diff --git a/script/bootstrap b/script/bootstrap index 32e9d11bc4d39d..f27fef8a07d511 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -16,4 +16,4 @@ fi echo "Installing development dependencies..." python3 -m pip install wheel --constraint homeassistant/package_constraints.txt -python3 -m pip install tox 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_test.txt) --constraint homeassistant/package_constraints.txt +python3 -m pip install tox 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 diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 3beb6aadfc5fde..583cafc8161e9d 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -2,11 +2,11 @@ from typing import Dict from urllib.parse import urlparse -from awesomeversion import AwesomeVersion -from awesomeversion.strategy import AwesomeVersionStrategy import voluptuous as vol from voluptuous.humanize import humanize_error +from homeassistant.loader import validate_custom_integration_version + from .model import Integration DOCUMENTATION_URL_SCHEMA = "https" @@ -53,16 +53,9 @@ def verify_uppercase(value: str): def verify_version(value: str): """Verify the version.""" - version = AwesomeVersion(value) - if version.strategy not in [ - AwesomeVersionStrategy.CALVER, - AwesomeVersionStrategy.SEMVER, - AwesomeVersionStrategy.SIMPLEVER, - AwesomeVersionStrategy.BUILDVER, - AwesomeVersionStrategy.PEP440, - ]: + if not validate_custom_integration_version(value): raise vol.Invalid( - f"'{version}' is not a valid version. This will cause a future version of Home Assistant to block this integration.", + f"'{value}' is not a valid version. This will cause a future version of Home Assistant to block this integration.", ) return value diff --git a/setup.py b/setup.py index 7e0df7f95c93d6..fc2c250ec0fa5d 100755 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", + "awesomeversion==21.2.0", "bcrypt==3.1.7", "certifi>=2020.12.5", "ciso8601==2.1.3", diff --git a/tests/hassfest/test_version.py b/tests/hassfest/test_version.py new file mode 100644 index 00000000000000..203042e79beb2a --- /dev/null +++ b/tests/hassfest/test_version.py @@ -0,0 +1,47 @@ +"""Tests for hassfest version.""" +import pytest +import voluptuous as vol + +from script.hassfest.manifest import ( + CUSTOM_INTEGRATION_MANIFEST_SCHEMA, + validate_version, +) +from script.hassfest.model import Integration + + +@pytest.fixture +def integration(): + """Fixture for hassfest integration model.""" + integration = Integration("") + integration.manifest = { + "domain": "test", + "documentation": "https://example.com", + "name": "test", + "codeowners": ["@awesome"], + } + return integration + + +def test_validate_version_no_key(integration: Integration): + """Test validate version with no key.""" + validate_version(integration) + assert ( + "No 'version' key in the manifest file. This will cause a future version of Home Assistant to block this integration." + in [x.error for x in integration.warnings] + ) + + +def test_validate_custom_integration_manifest(integration: Integration): + """Test validate custom integration manifest.""" + + with pytest.raises(vol.Invalid): + integration.manifest["version"] = "lorem_ipsum" + CUSTOM_INTEGRATION_MANIFEST_SCHEMA(integration.manifest) + + with pytest.raises(vol.Invalid): + integration.manifest["version"] = None + CUSTOM_INTEGRATION_MANIFEST_SCHEMA(integration.manifest) + + integration.manifest["version"] = "1" + schema = CUSTOM_INTEGRATION_MANIFEST_SCHEMA(integration.manifest) + assert schema["version"] == "1" diff --git a/tests/test_loader.py b/tests/test_loader.py index 22f61c0a397863..8acc8a7de4f5c0 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -130,13 +130,69 @@ async def test_custom_component_name(hass): async def test_log_warning_custom_component(hass, caplog): """Test that we log a warning when loading a custom component.""" - hass.components.test_standalone + await loader.async_get_integration(hass, "test_standalone") assert "You are using a custom integration test_standalone" in caplog.text await loader.async_get_integration(hass, "test") assert "You are using a custom integration test " in caplog.text +async def test_custom_integration_missing_version(hass, caplog): + """Test that we log a warning when custom integrations are missing a version.""" + test_integration_1 = loader.Integration( + hass, "custom_components.test1", None, {"domain": "test1"} + ) + test_integration_2 = loader.Integration( + hass, + "custom_components.test2", + None, + loader.manifest_from_legacy_module("test2", "custom_components.test2"), + ) + + with patch("homeassistant.loader.async_get_custom_components") as mock_get: + mock_get.return_value = { + "test1": test_integration_1, + "test2": test_integration_2, + } + + await loader.async_get_integration(hass, "test1") + assert ( + "No 'version' key in the manifest file for custom integration 'test1'." + in caplog.text + ) + + await loader.async_get_integration(hass, "test2") + assert ( + "No 'version' key in the manifest file for custom integration 'test2'." + in caplog.text + ) + + +async def test_no_version_warning_for_none_custom_integrations(hass, caplog): + """Test that we do not log a warning when core integrations are missing a version.""" + await loader.async_get_integration(hass, "hue") + assert ( + "No 'version' key in the manifest file for custom integration 'hue'." + not in caplog.text + ) + + +async def test_custom_integration_version_not_valid(hass, caplog): + """Test that we log a warning when custom integrations have a invalid version.""" + test_integration = loader.Integration( + hass, "custom_components.test", None, {"domain": "test", "version": "test"} + ) + + with patch("homeassistant.loader.async_get_custom_components") as mock_get: + mock_get.return_value = {"test": test_integration} + + await loader.async_get_integration(hass, "test") + assert ( + "'test' is not a valid version for custom integration 'test'." + in caplog.text + ) + + async def test_get_integration(hass): """Test resolving integration.""" integration = await loader.async_get_integration(hass, "hue") @@ -154,7 +210,6 @@ async def test_get_integration_legacy(hass): async def test_get_integration_custom_component(hass, enable_custom_integrations): """Test resolving integration.""" integration = await loader.async_get_integration(hass, "test_package") - print(integration) assert integration.get_component().DOMAIN == "test_package" assert integration.name == "Test Package" @@ -189,6 +244,7 @@ def test_integration_properties(hass): {"manufacturer": "Signify", "modelName": "Philips hue bridge 2015"}, ], "mqtt": ["hue/discovery"], + "version": "1.0.0", }, ) assert integration.name == "Philips Hue" @@ -215,6 +271,7 @@ def test_integration_properties(hass): assert integration.dependencies == ["test-dep"] assert integration.requirements == ["test-req==1.0.0"] assert integration.is_built_in is True + assert integration.version == "1.0.0" integration = loader.Integration( hass, @@ -233,6 +290,7 @@ def test_integration_properties(hass): assert integration.dhcp is None assert integration.ssdp is None assert integration.mqtt is None + assert integration.version is None integration = loader.Integration( hass, From 7e9500e46585b326235ffe4518f3d6f81e6763b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Feb 2021 10:41:28 +0100 Subject: [PATCH 0175/1818] Use bootstrap in devcontainer (#45968) --- .devcontainer/devcontainer.json | 3 ++- .pre-commit-config.yaml | 4 ++-- script/bootstrap | 9 +++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 26e4b2e78adaab..94d3c284f1a0e7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,8 @@ "name": "Home Assistant Dev", "context": "..", "dockerFile": "../Dockerfile.dev", - "postCreateCommand": "mkdir -p config && pip3 install -e .", + "postCreateCommand": "script/bootstrap", + "containerEnv": { "DEVCONTAINER": "1" }, "appPort": 8123, "runArgs": ["-e", "GIT_EDITOR=code --wait"], "extensions": [ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8944a69d9ed195..efd4b86e8ac2c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,8 +59,8 @@ repos: rev: v1.24.2 hooks: - id: yamllint - - repo: https://github.com/prettier/prettier - rev: 2.0.4 + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.2.1 hooks: - id: prettier stages: [manual] diff --git a/script/bootstrap b/script/bootstrap index f27fef8a07d511..12a2e5da707281 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -17,3 +17,12 @@ fi echo "Installing development dependencies..." python3 -m pip install wheel --constraint homeassistant/package_constraints.txt python3 -m pip install tox 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 + +if [ -n "$DEVCONTAINER" ];then + pre-commit install + pre-commit install-hooks + python3 -m pip install -e . --constraint homeassistant/package_constraints.txt + + mkdir -p config + hass --script ensure_config -c config +fi \ No newline at end of file From fefe4a20217f1d7847e6cc2ce7a6804d65e5dd2e Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 4 Feb 2021 12:07:30 +0200 Subject: [PATCH 0176/1818] Fix exception in Shelly sleeping device that switches to polling (#45930) --- homeassistant/components/shelly/__init__.py | 4 ++++ homeassistant/components/shelly/entity.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 84bc73f3c0f315..537caf9707f478 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -220,6 +220,10 @@ def _async_device_updates_handler(self): async def _async_update_data(self): """Fetch data.""" + if self.entry.data.get("sleep_period"): + # Sleeping device, no point polling it, just mark it unavailable + raise update_coordinator.UpdateFailed("Sleeping device did not update") + _LOGGER.debug("Polling Shelly Device - %s", self.name) try: async with async_timeout.timeout(POLLING_TIMEOUT_SEC): diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index b934a41728febe..71ab4703c79033 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -421,7 +421,7 @@ async def async_added_to_hass(self): @callback def _update_callback(self): """Handle device update.""" - if self.block is not None: + if self.block is not None or not self.wrapper.device.initialized: super()._update_callback() return From dd150bb79795bcf7323276e268358b0243f887c3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 4 Feb 2021 11:08:10 +0100 Subject: [PATCH 0177/1818] Allow manual configuration of ignored singleton config entries (#45161) Co-authored-by: Paulus Schoutsen --- homeassistant/config_entries.py | 18 ++++++++++----- tests/test_config_entries.py | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index abc6b2f46afc57..122b6f15e41421 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -905,7 +905,7 @@ def _abort_if_unique_id_configured( if self.unique_id is None: return - for entry in self._async_current_entries(): + for entry in self._async_current_entries(include_ignore=True): if entry.unique_id == self.unique_id: if updates is not None: changed = self.hass.config_entries.async_update_entry( @@ -949,17 +949,25 @@ async def async_set_unique_id( if progress["context"].get("unique_id") == DEFAULT_DISCOVERY_UNIQUE_ID: self.hass.config_entries.flow.async_abort(progress["flow_id"]) - for entry in self._async_current_entries(): + for entry in self._async_current_entries(include_ignore=True): if entry.unique_id == unique_id: return entry return None @callback - def _async_current_entries(self) -> List[ConfigEntry]: - """Return current entries.""" + def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEntry]: + """Return current entries. + + If the flow is user initiated, filter out ignored entries unless include_ignore is True. + """ assert self.hass is not None - return self.hass.config_entries.async_entries(self.handler) + config_entries = self.hass.config_entries.async_entries(self.handler) + + if include_ignore or self.source != SOURCE_USER: + return config_entries + + return [entry for entry in config_entries if entry.source != SOURCE_IGNORE] @callback def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 24444f6a6c0a73..435f2a11cc2a9e 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1585,6 +1585,45 @@ async def async_step_user(self, user_input=None): assert len(async_reload.mock_calls) == 0 +async def test_manual_add_overrides_ignored_entry_singleton(hass, manager): + """Test that we can ignore manually add entry, overriding ignored entry.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Test user step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 1 + p_hass, p_entry = mock_setup_entry.mock_calls[0][1] + + assert p_hass is hass + assert p_entry.data == {"token": "supersecret"} + + async def test_unignore_step_form(hass, manager): """Test that we can ignore flows that are in progress and have a unique ID, then rediscover them.""" async_setup_entry = AsyncMock(return_value=True) From afa7fd923a41c68e30bf01f98d4a9d7c50ce09c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Feb 2021 12:51:38 +0100 Subject: [PATCH 0178/1818] Update yarnpkg GPG key (#45973) --- Dockerfile.dev | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index d72ebcaed016e3..0d86c1a7eec18d 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,7 +1,11 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.8 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + RUN \ - apt-get update && apt-get install -y --no-install-recommends \ + curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ libudev-dev \ libavformat-dev \ libavcodec-dev \ From 8625b772e3bba923c7b14064667d2b48ea583ddc Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 07:05:46 -0500 Subject: [PATCH 0179/1818] Use core constants for alert (#45935) --- homeassistant/components/alert/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 53b1a1248dce05..73bea193394c22 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -14,6 +14,7 @@ ATTR_ENTITY_ID, CONF_ENTITY_ID, CONF_NAME, + CONF_REPEAT, CONF_STATE, SERVICE_TOGGLE, SERVICE_TURN_OFF, @@ -33,7 +34,6 @@ CONF_CAN_ACK = "can_acknowledge" CONF_NOTIFIERS = "notifiers" -CONF_REPEAT = "repeat" CONF_SKIP_FIRST = "skip_first" CONF_ALERT_MESSAGE = "message" CONF_DONE_MESSAGE = "done_message" From d44c941efe9e9aa8f88bf7742476b060b9dfe086 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 07:05:56 -0500 Subject: [PATCH 0180/1818] Use core constants for alexa (#45937) --- homeassistant/components/alexa/__init__.py | 8 ++++++-- homeassistant/components/alexa/const.py | 1 - homeassistant/components/alexa/flash_briefings.py | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index 5180a8d55b63bb..70d426905e98d3 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -1,7 +1,12 @@ """Support for Alexa skill service end point.""" import voluptuous as vol -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_NAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_NAME, + CONF_PASSWORD, +) from homeassistant.helpers import config_validation as cv, entityfilter from . import flash_briefings, intent, smart_home_http @@ -14,7 +19,6 @@ CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE, - CONF_PASSWORD, CONF_SUPPORTED_LOCALES, CONF_TEXT, CONF_TITLE, diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index 402cb9e1fb2cb0..ca0d8435e02450 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -19,7 +19,6 @@ CONF_ENTITY_CONFIG = "entity_config" CONF_ENDPOINT = "endpoint" CONF_LOCALE = "locale" -CONF_PASSWORD = "password" ATTR_UID = "uid" ATTR_UPDATE_DATE = "updateDate" diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index b8f78705e10be5..50463810bbfb77 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -5,7 +5,7 @@ import uuid from homeassistant.components import http -from homeassistant.const import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED +from homeassistant.const import CONF_PASSWORD, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED from homeassistant.core import callback from homeassistant.helpers import template import homeassistant.util.dt as dt_util @@ -20,7 +20,6 @@ ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, - CONF_PASSWORD, CONF_TEXT, CONF_TITLE, CONF_UID, From 9c6c2a77abaca10150a71b4e8f050fc4e36d8d28 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 07:06:09 -0500 Subject: [PATCH 0181/1818] Use core constants for amazon polly (#45938) --- homeassistant/components/amazon_polly/tts.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/amazon_polly/tts.py b/homeassistant/components/amazon_polly/tts.py index fb9560832ca552..bdb46abda9a1b4 100644 --- a/homeassistant/components/amazon_polly/tts.py +++ b/homeassistant/components/amazon_polly/tts.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.components.tts import PLATFORM_SCHEMA, Provider +from homeassistant.const import ATTR_CREDENTIALS, CONF_PROFILE_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -13,8 +14,6 @@ CONF_REGION = "region_name" CONF_ACCESS_KEY_ID = "aws_access_key_id" CONF_SECRET_ACCESS_KEY = "aws_secret_access_key" -CONF_PROFILE_NAME = "profile_name" -ATTR_CREDENTIALS = "credentials" DEFAULT_REGION = "us-east-1" SUPPORTED_REGIONS = [ From 1a74709757959ee7708d38a57a7b7061e16d3bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Feb 2021 13:31:17 +0100 Subject: [PATCH 0182/1818] Throw error in hassfest when integration is missing version (#45976) --- script/hassfest/manifest.py | 2 +- tests/hassfest/test_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 583cafc8161e9d..c6f8f71f2d9389 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -119,7 +119,7 @@ def validate_version(integration: Integration): Will be removed when the version key is no longer optional for custom integrations. """ if not integration.manifest.get("version"): - integration.add_warning( + integration.add_error( "manifest", "No 'version' key in the manifest file. This will cause a future version of Home Assistant to block this integration.", ) diff --git a/tests/hassfest/test_version.py b/tests/hassfest/test_version.py index 203042e79beb2a..f99ee911a695f1 100644 --- a/tests/hassfest/test_version.py +++ b/tests/hassfest/test_version.py @@ -27,7 +27,7 @@ def test_validate_version_no_key(integration: Integration): validate_version(integration) assert ( "No 'version' key in the manifest file. This will cause a future version of Home Assistant to block this integration." - in [x.error for x in integration.warnings] + in [x.error for x in integration.errors] ) From 134b1d3f63f33d52dd361babdb49caf716170723 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 4 Feb 2021 14:16:09 +0100 Subject: [PATCH 0183/1818] Fix entities device_info property in Harmony integration (#45964) --- homeassistant/components/harmony/remote.py | 2 +- homeassistant/components/harmony/switch.py | 5 +++++ tests/components/harmony/conftest.py | 1 + tests/components/harmony/test_config_flow.py | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 8409983789b225..9b3d53c21fa81f 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -161,7 +161,7 @@ async def async_added_to_hass(self): @property def device_info(self): """Return device info.""" - self._data.device_info(DOMAIN) + return self._data.device_info(DOMAIN) @property def unique_id(self): diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index 5fae07c431ba35..2832872c2eff82 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -46,6 +46,11 @@ def unique_id(self): """Return the unique id.""" return f"{self._data.unique_id}-{self._activity}" + @property + def device_info(self): + """Return device info.""" + return self._data.device_info(DOMAIN) + @property def is_on(self): """Return if the current activity is the one for this switch.""" diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index e758a2795a94e3..cde8c43fe89d50 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -49,6 +49,7 @@ def __init__( self.change_channel = AsyncMock() self.sync = AsyncMock() self._callbacks = callbacks + self.fw_version = "123.456" async def connect(self): """Connect and call the appropriate callbacks.""" diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 52ef71fc8bcd19..2a7f80d5c2ffd8 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -153,7 +153,7 @@ async def test_form_cannot_connect(hass): assert result2["errors"] == {"base": "cannot_connect"} -async def test_options_flow(hass, mock_hc): +async def test_options_flow(hass, mock_hc, mock_write_config): """Test config flow options.""" config_entry = MockConfigEntry( domain=DOMAIN, From 61a987061e9da549ccdf466a91869af0c3ab2a5f Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 4 Feb 2021 14:18:51 +0100 Subject: [PATCH 0184/1818] Don't log missing mpd artwork inappropriately (#45908) This can get unnecessarily spammy and doesn't represent an actual actionable issue. Fixes: #45235 --- homeassistant/components/mpd/manifest.json | 2 +- homeassistant/components/mpd/media_player.py | 18 ++++++++++-------- requirements_all.txt | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mpd/manifest.json b/homeassistant/components/mpd/manifest.json index 12ce5b61b74bc9..a11b9fedd801c6 100644 --- a/homeassistant/components/mpd/manifest.json +++ b/homeassistant/components/mpd/manifest.json @@ -2,6 +2,6 @@ "domain": "mpd", "name": "Music Player Daemon (MPD)", "documentation": "https://www.home-assistant.io/integrations/mpd", - "requirements": ["python-mpd2==3.0.3"], + "requirements": ["python-mpd2==3.0.4"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 6685347b3e38cb..371d20606801a9 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -281,20 +281,22 @@ async def async_get_media_image(self): try: response = await self._client.readpicture(file) except mpd.CommandError as error: - _LOGGER.warning( - "Retrieving artwork through `readpicture` command failed: %s", - error, - ) + if error.errno is not mpd.FailureResponseCode.NO_EXIST: + _LOGGER.warning( + "Retrieving artwork through `readpicture` command failed: %s", + error, + ) # read artwork contained in the media directory (cover.{jpg,png,tiff,bmp}) if none is embedded if can_albumart and not response: try: response = await self._client.albumart(file) except mpd.CommandError as error: - _LOGGER.warning( - "Retrieving artwork through `albumart` command failed: %s", - error, - ) + if error.errno is not mpd.FailureResponseCode.NO_EXIST: + _LOGGER.warning( + "Retrieving artwork through `albumart` command failed: %s", + error, + ) if not response: return None, None diff --git a/requirements_all.txt b/requirements_all.txt index 37422a65972e16..f0758ae96462bb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1795,7 +1795,7 @@ python-juicenet==1.0.1 python-miio==0.5.4 # homeassistant.components.mpd -python-mpd2==3.0.3 +python-mpd2==3.0.4 # homeassistant.components.mystrom python-mystrom==1.1.2 From b80c1688ad2898fbed13e3fc6ce81476564aa1be Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Feb 2021 16:29:41 +0100 Subject: [PATCH 0185/1818] Bump zwave-js-server-python to 0.17.1 (#45988) --- 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 7df75d7aed2e58..7083d6c372b8d2 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.17.0"], + "requirements": ["zwave-js-server-python==0.17.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index f0758ae96462bb..c1f914f6011173 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2387,4 +2387,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.0 +zwave-js-server-python==0.17.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2cd33ee3fc9c66..f356ab5ada576b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1206,4 +1206,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.0 +zwave-js-server-python==0.17.1 From 56b8e82a6901a227b9ba2ccd2f65a4cac24c447e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Feb 2021 16:45:59 +0100 Subject: [PATCH 0186/1818] Bump awesomeversion from 21.2.0 to 21.2.2 (#45993) --- homeassistant/loader.py | 3 +-- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index f1a0ccc07309b5..de02db524a71ec 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -26,8 +26,7 @@ cast, ) -from awesomeversion import AwesomeVersion -from awesomeversion.strategy import AwesomeVersionStrategy +from awesomeversion import AwesomeVersion, AwesomeVersionStrategy from homeassistant.generated.dhcp import DHCP from homeassistant.generated.mqtt import MQTT diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 69f75af396b2d2..45a871081d1dd9 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -5,7 +5,7 @@ aiohttp_cors==0.7.0 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 -awesomeversion==21.2.0 +awesomeversion==21.2.2 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/requirements.txt b/requirements.txt index 17bb82d472f1dd..42be3cfdf49379 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ aiohttp==3.7.3 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 -awesomeversion==21.2.0 +awesomeversion==21.2.2 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/setup.py b/setup.py index fc2c250ec0fa5d..1b4b52ff26d4e4 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", - "awesomeversion==21.2.0", + "awesomeversion==21.2.2", "bcrypt==3.1.7", "certifi>=2020.12.5", "ciso8601==2.1.3", From 7b280bdbe72d5615bb83126fb28b34fcb5ce8cd1 Mon Sep 17 00:00:00 2001 From: DeadEnd <45110141+DeadEnded@users.noreply.github.com> Date: Thu, 4 Feb 2021 11:02:56 -0500 Subject: [PATCH 0187/1818] Fix Local Media in Media Browser (#45987) Co-authored-by: Paulus Schoutsen --- homeassistant/components/media_source/local_source.py | 6 +++--- tests/components/media_source/test_local_source.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index d7a2bdfd9383dd..fa62ba48c5f1d1 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -10,7 +10,7 @@ from homeassistant.components.media_player.errors import BrowseError from homeassistant.components.media_source.error import Unresolvable from homeassistant.core import HomeAssistant, callback -from homeassistant.util import raise_if_invalid_filename +from homeassistant.util import raise_if_invalid_path from .const import DOMAIN, MEDIA_CLASS_MAP, MEDIA_MIME_TYPES from .models import BrowseMediaSource, MediaSource, MediaSourceItem, PlayMedia @@ -51,7 +51,7 @@ def async_parse_identifier(self, item: MediaSourceItem) -> Tuple[str, str]: raise Unresolvable("Unknown source directory.") try: - raise_if_invalid_filename(location) + raise_if_invalid_path(location) except ValueError as err: raise Unresolvable("Invalid path.") from err @@ -192,7 +192,7 @@ async def get( ) -> web.FileResponse: """Start a GET request.""" try: - raise_if_invalid_filename(location) + raise_if_invalid_path(location) except ValueError as err: raise web.HTTPBadRequest() from err diff --git a/tests/components/media_source/test_local_source.py b/tests/components/media_source/test_local_source.py index ad10df7cfd3d7b..e3e2a3f1617d61 100644 --- a/tests/components/media_source/test_local_source.py +++ b/tests/components/media_source/test_local_source.py @@ -23,7 +23,7 @@ async def test_async_browse_media(hass): await media_source.async_browse_media( hass, f"{const.URI_SCHEME}{const.DOMAIN}/local/test/not/exist" ) - assert str(excinfo.value) == "Invalid path." + assert str(excinfo.value) == "Path does not exist." # Test browse file with pytest.raises(media_source.BrowseError) as excinfo: From 912b816117c9fea85587d9c3812279a32aa8c771 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Feb 2021 20:44:40 +0100 Subject: [PATCH 0188/1818] Bump zwave-js-server-python to 0.17.2 (#46010) --- 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 7083d6c372b8d2..4bd12baa685198 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.17.1"], + "requirements": ["zwave-js-server-python==0.17.2"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index c1f914f6011173..82ace0f443827c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2387,4 +2387,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.1 +zwave-js-server-python==0.17.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f356ab5ada576b..cf6c291b519a9a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1206,4 +1206,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.1 +zwave-js-server-python==0.17.2 From c7febacd9f55c44253fa20d7bf6cee0dac2c599d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 4 Feb 2021 23:32:56 +0100 Subject: [PATCH 0189/1818] Upgrade holidays to 0.10.5.2 (#46013) --- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 4fb25c766ccc69..3351d796e93ef2 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.10.4"], + "requirements": ["holidays==0.10.5.2"], "codeowners": ["@fabaff"], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index 82ace0f443827c..ffa20b1c028a8b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -762,7 +762,7 @@ hlk-sw16==0.0.9 hole==0.5.1 # homeassistant.components.workday -holidays==0.10.4 +holidays==0.10.5.2 # homeassistant.components.frontend home-assistant-frontend==20210127.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cf6c291b519a9a..be9f18717e585e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -402,7 +402,7 @@ hlk-sw16==0.0.9 hole==0.5.1 # homeassistant.components.workday -holidays==0.10.4 +holidays==0.10.5.2 # homeassistant.components.frontend home-assistant-frontend==20210127.7 From 5d3dcff7c960e9cc2d87c523ab23b60cc3527a89 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 17:34:39 -0500 Subject: [PATCH 0190/1818] Use core constants for asuswrt (#46015) --- homeassistant/components/asuswrt/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 1829d00a353cb2..9cd47d803dede3 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -10,6 +10,7 @@ CONF_PASSWORD, CONF_PORT, CONF_PROTOCOL, + CONF_SENSORS, CONF_USERNAME, ) from homeassistant.helpers import config_validation as cv @@ -22,7 +23,6 @@ CONF_INTERFACE = "interface" CONF_PUB_KEY = "pub_key" CONF_REQUIRE_IP = "require_ip" -CONF_SENSORS = "sensors" CONF_SSH_KEY = "ssh_key" DOMAIN = "asuswrt" @@ -65,7 +65,6 @@ async def async_setup(hass, config, retry_delay=FIRST_RETRY_TIME): """Set up the asuswrt component.""" - conf = config[DOMAIN] api = AsusWrt( From 60268e63d947fd540831f4726d34912bc092e7b0 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 17:36:35 -0500 Subject: [PATCH 0191/1818] Use core constants for aws (#46017) --- homeassistant/components/aws/__init__.py | 9 ++++++--- homeassistant/components/aws/const.py | 2 -- homeassistant/components/aws/notify.py | 15 ++++++--------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index 600874b0d25d34..19563efa14477c 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -7,7 +7,12 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import ATTR_CREDENTIALS, CONF_NAME, CONF_PROFILE_NAME +from homeassistant.const import ( + ATTR_CREDENTIALS, + CONF_NAME, + CONF_PROFILE_NAME, + CONF_SERVICE, +) from homeassistant.helpers import config_validation as cv, discovery # Loading the config flow file will register the flow @@ -20,7 +25,6 @@ CONF_NOTIFY, CONF_REGION, CONF_SECRET_ACCESS_KEY, - CONF_SERVICE, CONF_VALIDATE, DATA_CONFIG, DATA_HASS_CONFIG, @@ -152,7 +156,6 @@ async def async_setup_entry(hass, entry): async def _validate_aws_credentials(hass, credential): """Validate AWS credential config.""" - aws_config = credential.copy() del aws_config[CONF_NAME] del aws_config[CONF_VALIDATE] diff --git a/homeassistant/components/aws/const.py b/homeassistant/components/aws/const.py index 499f4413596d39..8be6afec7ff65e 100644 --- a/homeassistant/components/aws/const.py +++ b/homeassistant/components/aws/const.py @@ -10,8 +10,6 @@ CONF_CREDENTIAL_NAME = "credential_name" CONF_CREDENTIALS = "credentials" CONF_NOTIFY = "notify" -CONF_PROFILE_NAME = "profile_name" CONF_REGION = "region_name" CONF_SECRET_ACCESS_KEY = "aws_secret_access_key" -CONF_SERVICE = "service" CONF_VALIDATE = "validate" diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py index 13fa189a318235..f487bc7aab3f32 100644 --- a/homeassistant/components/aws/notify.py +++ b/homeassistant/components/aws/notify.py @@ -12,24 +12,21 @@ ATTR_TITLE_DEFAULT, BaseNotificationService, ) -from homeassistant.const import CONF_NAME, CONF_PLATFORM -from homeassistant.helpers.json import JSONEncoder - -from .const import ( - CONF_CONTEXT, - CONF_CREDENTIAL_NAME, +from homeassistant.const import ( + CONF_NAME, + CONF_PLATFORM, CONF_PROFILE_NAME, - CONF_REGION, CONF_SERVICE, - DATA_SESSIONS, ) +from homeassistant.helpers.json import JSONEncoder + +from .const import CONF_CONTEXT, CONF_CREDENTIAL_NAME, CONF_REGION, DATA_SESSIONS _LOGGER = logging.getLogger(__name__) async def get_available_regions(hass, service): """Get available regions for a service.""" - session = aiobotocore.get_session() # get_available_regions is not a coroutine since it does not perform # network I/O. But it still perform file I/O heavily, so put it into From b9f9de0c1d199c3a601d658414f1f3fd324a964e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Feb 2021 12:39:44 -1000 Subject: [PATCH 0192/1818] dhcp does not need promisc mode. Disable it in scapy (#46018) --- homeassistant/components/dhcp/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index a71db430da41cd..f0de561c76ae01 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -282,4 +282,6 @@ def _verify_l2socket_creation_permission(): thread so we will not be able to capture any permission or bind errors. """ + # disable scapy promiscuous mode as we do not need it + conf.sniff_promisc = 0 conf.L2socket() From 62de921422aa3decd3afa827626c658b19a885da Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 4 Feb 2021 23:41:24 +0100 Subject: [PATCH 0193/1818] Upgrade slixmpp to 1.7.0 (#46019) --- homeassistant/components/xmpp/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xmpp/manifest.json b/homeassistant/components/xmpp/manifest.json index b56d43b9c9c1db..ced8bd19e4090e 100644 --- a/homeassistant/components/xmpp/manifest.json +++ b/homeassistant/components/xmpp/manifest.json @@ -2,6 +2,6 @@ "domain": "xmpp", "name": "Jabber (XMPP)", "documentation": "https://www.home-assistant.io/integrations/xmpp", - "requirements": ["slixmpp==1.6.0"], + "requirements": ["slixmpp==1.7.0"], "codeowners": ["@fabaff", "@flowolf"] } diff --git a/requirements_all.txt b/requirements_all.txt index ffa20b1c028a8b..223ebe32e76e05 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2049,7 +2049,7 @@ slackclient==2.5.0 sleepyq==0.8.1 # homeassistant.components.xmpp -slixmpp==1.6.0 +slixmpp==1.7.0 # homeassistant.components.smart_meter_texas smart-meter-texas==0.4.0 From 097a4e6b593b8fd09de1795019228427f28d40ed Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 4 Feb 2021 23:42:10 +0100 Subject: [PATCH 0194/1818] Upgrade praw to 7.1.2 (#46012) --- homeassistant/components/reddit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reddit/manifest.json b/homeassistant/components/reddit/manifest.json index fc3356b310c047..e19ae570d0f333 100644 --- a/homeassistant/components/reddit/manifest.json +++ b/homeassistant/components/reddit/manifest.json @@ -2,6 +2,6 @@ "domain": "reddit", "name": "Reddit", "documentation": "https://www.home-assistant.io/integrations/reddit", - "requirements": ["praw==7.1.0"], + "requirements": ["praw==7.1.2"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 223ebe32e76e05..2a78b25a162418 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1159,7 +1159,7 @@ pmsensor==0.4 poolsense==0.0.8 # homeassistant.components.reddit -praw==7.1.0 +praw==7.1.2 # homeassistant.components.islamic_prayer_times prayer_times_calculator==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be9f18717e585e..024de6596b654b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -596,7 +596,7 @@ pmsensor==0.4 poolsense==0.0.8 # homeassistant.components.reddit -praw==7.1.0 +praw==7.1.2 # homeassistant.components.islamic_prayer_times prayer_times_calculator==0.0.3 From 01e73911d629700199eda8fac36aeebb5b6eeb51 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Feb 2021 13:36:55 -1000 Subject: [PATCH 0195/1818] Do not listen for dhcp packets if the filter cannot be setup (#46006) --- homeassistant/components/dhcp/__init__.py | 19 ++++++++++ tests/components/dhcp/test_init.py | 44 +++++++++++++++++++---- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index f0de561c76ae01..d33c6159888fa7 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -7,6 +7,7 @@ import os import threading +from scapy.arch.common import compile_filter from scapy.config import conf from scapy.error import Scapy_Exception from scapy.layers.dhcp import DHCP @@ -217,6 +218,15 @@ async def async_start(self): ) return + try: + await _async_verify_working_pcap(self.hass, FILTER) + except (Scapy_Exception, ImportError) as ex: + _LOGGER.error( + "Cannot watch for dhcp packets without a functional packet filter: %s", + ex, + ) + return + self._sniffer = AsyncSniffer( filter=FILTER, started_callback=self._started.set, @@ -285,3 +295,12 @@ def _verify_l2socket_creation_permission(): # disable scapy promiscuous mode as we do not need it conf.sniff_promisc = 0 conf.L2socket() + + +async def _async_verify_working_pcap(hass, cap_filter): + """Verify we can create a packet filter. + + If we cannot create a filter we will be listening for + all traffic which is too intensive. + """ + await hass.async_add_executor_job(compile_filter, cap_filter) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 049128248a7b8e..fc24c8201e26b1 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -280,7 +280,11 @@ async def test_setup_and_stop(hass): ) await hass.async_block_till_done() - with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call: + with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call, patch( + "homeassistant.components.dhcp._verify_l2socket_creation_permission", + ), patch( + "homeassistant.components.dhcp.compile_filter", + ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -325,21 +329,49 @@ async def test_setup_fails_non_root(hass, caplog): ) await hass.async_block_till_done() - wait_event = threading.Event() - with patch("os.geteuid", return_value=10), patch( "homeassistant.components.dhcp._verify_l2socket_creation_permission", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - wait_event.set() assert "Cannot watch for dhcp packets without root or CAP_NET_RAW" in caplog.text +async def test_setup_fails_with_broken_libpcap(hass, caplog): + """Test we abort if libpcap is missing or broken.""" + + assert await async_setup_component( + hass, + dhcp.DOMAIN, + {}, + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.dhcp._verify_l2socket_creation_permission", + ), patch( + "homeassistant.components.dhcp.compile_filter", + side_effect=ImportError, + ) as compile_filter, patch( + "homeassistant.components.dhcp.AsyncSniffer", + ) as async_sniffer: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + assert compile_filter.called + assert not async_sniffer.called + assert ( + "Cannot watch for dhcp packets without a functional packet filter" + in caplog.text + ) + + async def test_device_tracker_hostname_and_macaddress_exists_before_start(hass): """Test matching based on hostname and macaddress before start.""" hass.states.async_set( From 5b5755f5e2f0bd7154b34d0ae3ce51d529fbc954 Mon Sep 17 00:00:00 2001 From: Christopher Gozdziewski Date: Thu, 4 Feb 2021 01:32:43 -0600 Subject: [PATCH 0196/1818] Convert ozw climate values to correct units (#45369) * Convert ozw climate values to correct units * Remove debugger logging * Fix black code formatting * Remove extra spaces * Add method descriptions and change to use setpoint * Fix build and respond to comments * Remove self from convert_units call * Move method to top * Move method outside class * Add blank lines * Fix test to use farenheit * Update another value to farenheit * Change to celsius * Another test fix * test fix * Fix a value * missed one * Add unit test for convert_units * fix unit test import * Add new line to end of test file * fix convert units import * Reorder imports * Grab const from different import Co-authored-by: Trevor --- homeassistant/components/ozw/climate.py | 51 +++++++++++++++++++++---- tests/components/ozw/test_climate.py | 20 ++++++---- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index a74fd869f0f443..67bbe5cdc4dd06 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -28,6 +28,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.temperature import convert as convert_temperature from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity @@ -154,6 +155,13 @@ def async_add_climate(values): ) +def convert_units(units): + """Return units as a farenheit or celsius constant.""" + if units == "F": + return TEMP_FAHRENHEIT + return TEMP_CELSIUS + + class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): """Representation of a Z-Wave Climate device.""" @@ -199,16 +207,18 @@ def fan_modes(self): @property def temperature_unit(self): """Return the unit of measurement.""" - if self.values.temperature is not None and self.values.temperature.units == "F": - return TEMP_FAHRENHEIT - return TEMP_CELSIUS + return convert_units(self._current_mode_setpoint_values[0].units) @property def current_temperature(self): """Return the current temperature.""" if not self.values.temperature: return None - return self.values.temperature.value + return convert_temperature( + self.values.temperature.value, + convert_units(self._current_mode_setpoint_values[0].units), + self.temperature_unit, + ) @property def hvac_action(self): @@ -236,17 +246,29 @@ def preset_modes(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._current_mode_setpoint_values[0].value + return convert_temperature( + self._current_mode_setpoint_values[0].value, + convert_units(self._current_mode_setpoint_values[0].units), + self.temperature_unit, + ) @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return self._current_mode_setpoint_values[0].value + return convert_temperature( + self._current_mode_setpoint_values[0].value, + convert_units(self._current_mode_setpoint_values[0].units), + self.temperature_unit, + ) @property def target_temperature_high(self) -> Optional[float]: """Return the highbound target temperature we try to reach.""" - return self._current_mode_setpoint_values[1].value + return convert_temperature( + self._current_mode_setpoint_values[1].value, + convert_units(self._current_mode_setpoint_values[1].units), + self.temperature_unit, + ) async def async_set_temperature(self, **kwargs): """Set new target temperature. @@ -262,14 +284,29 @@ async def async_set_temperature(self, **kwargs): setpoint = self._current_mode_setpoint_values[0] target_temp = kwargs.get(ATTR_TEMPERATURE) if setpoint is not None and target_temp is not None: + target_temp = convert_temperature( + target_temp, + self.temperature_unit, + convert_units(setpoint.units), + ) setpoint.send_value(target_temp) elif len(self._current_mode_setpoint_values) == 2: (setpoint_low, setpoint_high) = self._current_mode_setpoint_values target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if setpoint_low is not None and target_temp_low is not None: + target_temp_low = convert_temperature( + target_temp_low, + self.temperature_unit, + convert_units(setpoint_low.units), + ) setpoint_low.send_value(target_temp_low) if setpoint_high is not None and target_temp_high is not None: + target_temp_high = convert_temperature( + target_temp_high, + self.temperature_unit, + convert_units(setpoint_high.units), + ) setpoint_high.send_value(target_temp_high) async def async_set_fan_mode(self, fan_mode): diff --git a/tests/components/ozw/test_climate.py b/tests/components/ozw/test_climate.py index 3414e6c483279a..e251a93c11520c 100644 --- a/tests/components/ozw/test_climate.py +++ b/tests/components/ozw/test_climate.py @@ -16,6 +16,8 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, ) +from homeassistant.components.ozw.climate import convert_units +from homeassistant.const import TEMP_FAHRENHEIT from .common import setup_ozw @@ -36,8 +38,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): HVAC_MODE_HEAT_COOL, ] assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 23.1 - assert state.attributes[ATTR_TEMPERATURE] == 21.1 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 73.5 + assert state.attributes[ATTR_TEMPERATURE] == 70.0 assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None assert state.attributes[ATTR_FAN_MODE] == "Auto Low" @@ -54,7 +56,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 78.98 + assert round(msg["payload"]["Value"], 2) == 26.1 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test hvac_mode with set_temperature @@ -72,7 +74,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 75.38 + assert round(msg["payload"]["Value"], 2) == 24.1 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test set mode @@ -127,8 +129,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert state is not None assert state.state == HVAC_MODE_HEAT_COOL assert state.attributes.get(ATTR_TEMPERATURE) is None - assert state.attributes[ATTR_TARGET_TEMP_LOW] == 21.1 - assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.6 + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 70.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 78.0 # Test setting high/low temp on multiple setpoints await hass.services.async_call( @@ -144,11 +146,11 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert len(sent_messages) == 7 # 2 messages ! msg = sent_messages[-2] # low setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 68.0 + assert round(msg["payload"]["Value"], 2) == 20.0 assert msg["payload"]["ValueIDKey"] == 281475099443218 msg = sent_messages[-1] # high setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 77.0 + assert round(msg["payload"]["Value"], 2) == 25.0 assert msg["payload"]["ValueIDKey"] == 562950076153874 # Test basic/single-setpoint thermostat (node 16 in dump) @@ -325,3 +327,5 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): ) assert len(sent_messages) == 12 assert "does not support setting a mode" in caplog.text + + assert convert_units("F") == TEMP_FAHRENHEIT From 42606cedef27adb0197623a897624bf684628ed0 Mon Sep 17 00:00:00 2001 From: Niccolo Zapponi Date: Wed, 3 Feb 2021 18:18:31 +0000 Subject: [PATCH 0197/1818] Add support for iCloud 2FA (#45818) * Add support for iCloud 2FA * Updated dependency for iCloud * Updated dependency and logic fix * Added logic for handling incorrect 2FA code * Bug fix on failing test * Added myself to codeowners * Added check for 2FA on setup * Updated error message --- CODEOWNERS | 2 +- homeassistant/components/icloud/account.py | 34 ++++--- .../components/icloud/config_flow.py | 54 ++++++++--- homeassistant/components/icloud/const.py | 2 +- homeassistant/components/icloud/manifest.json | 4 +- homeassistant/components/icloud/strings.json | 2 +- .../components/icloud/translations/en.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/icloud/test_config_flow.py | 90 +++++++++++++++++++ 10 files changed, 164 insertions(+), 30 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b8175614fb54ba..ae62631dcea22a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -211,7 +211,7 @@ homeassistant/components/hydrawise/* @ptcryan homeassistant/components/hyperion/* @dermotduffy homeassistant/components/iammeter/* @lewei50 homeassistant/components/iaqualink/* @flz -homeassistant/components/icloud/* @Quentame +homeassistant/components/icloud/* @Quentame @nzapponi homeassistant/components/ign_sismologia/* @exxamalte homeassistant/components/image/* @home-assistant/core homeassistant/components/incomfort/* @zxdavb diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index e6337085e04e2b..4221cf635ba585 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -113,6 +113,12 @@ def setup(self) -> None: self._icloud_dir.path, with_family=self._with_family, ) + + if not self.api.is_trusted_session or self.api.requires_2fa: + # Session is no longer trusted + # Trigger a new log in to ensure the user enters the 2FA code again. + raise PyiCloudFailedLoginException + except PyiCloudFailedLoginException: self.api = None # Login failed which means credentials need to be updated. @@ -125,16 +131,7 @@ def setup(self) -> None: self._config_entry.data[CONF_USERNAME], ) - self.hass.add_job( - self.hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_REAUTH}, - data={ - **self._config_entry.data, - "unique_id": self._config_entry.unique_id, - }, - ) - ) + self._require_reauth() return try: @@ -165,6 +162,10 @@ def update_devices(self) -> None: if self.api is None: return + if not self.api.is_trusted_session or self.api.requires_2fa: + self._require_reauth() + return + api_devices = {} try: api_devices = self.api.devices @@ -228,6 +229,19 @@ def update_devices(self) -> None: utcnow() + timedelta(minutes=self._fetch_interval), ) + def _require_reauth(self): + """Require the user to log in again.""" + self.hass.add_job( + self.hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_REAUTH}, + data={ + **self._config_entry.data, + "unique_id": self._config_entry.unique_id, + }, + ) + ) + def _determine_interval(self) -> int: """Calculate new interval between two API fetch (in minutes).""" intervals = {"default": self._max_interval} diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index d447790e43229f..c79024c4f64e8c 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -125,6 +125,9 @@ async def _validate_and_create_entry(self, user_input, step_id): errors = {CONF_PASSWORD: "invalid_auth"} return self._show_setup_form(user_input, errors, step_id) + if self.api.requires_2fa: + return await self.async_step_verification_code() + if self.api.requires_2sa: return await self.async_step_trusted_device() @@ -243,22 +246,29 @@ async def _show_trusted_device_form( errors=errors or {}, ) - async def async_step_verification_code(self, user_input=None): + async def async_step_verification_code(self, user_input=None, errors=None): """Ask the verification code to the user.""" - errors = {} + if errors is None: + errors = {} if user_input is None: - return await self._show_verification_code_form(user_input) + return await self._show_verification_code_form(user_input, errors) self._verification_code = user_input[CONF_VERIFICATION_CODE] try: - if not await self.hass.async_add_executor_job( - self.api.validate_verification_code, - self._trusted_device, - self._verification_code, - ): - raise PyiCloudException("The code you entered is not valid.") + if self.api.requires_2fa: + if not await self.hass.async_add_executor_job( + self.api.validate_2fa_code, self._verification_code + ): + raise PyiCloudException("The code you entered is not valid.") + else: + if not await self.hass.async_add_executor_job( + self.api.validate_verification_code, + self._trusted_device, + self._verification_code, + ): + raise PyiCloudException("The code you entered is not valid.") except PyiCloudException as error: # Reset to the initial 2FA state to allow the user to retry _LOGGER.error("Failed to verify verification code: %s", error) @@ -266,7 +276,27 @@ async def async_step_verification_code(self, user_input=None): self._verification_code = None errors["base"] = "validate_verification_code" - return await self.async_step_trusted_device(None, errors) + if self.api.requires_2fa: + try: + self.api = await self.hass.async_add_executor_job( + PyiCloudService, + self._username, + self._password, + self.hass.helpers.storage.Store( + STORAGE_VERSION, STORAGE_KEY + ).path, + True, + None, + self._with_family, + ) + return await self.async_step_verification_code(None, errors) + except PyiCloudFailedLoginException as error: + _LOGGER.error("Error logging into iCloud service: %s", error) + self.api = None + errors = {CONF_PASSWORD: "invalid_auth"} + return self._show_setup_form(user_input, errors, "user") + else: + return await self.async_step_trusted_device(None, errors) return await self.async_step_user( { @@ -278,11 +308,11 @@ async def async_step_verification_code(self, user_input=None): } ) - async def _show_verification_code_form(self, user_input=None): + async def _show_verification_code_form(self, user_input=None, errors=None): """Show the verification_code form to the user.""" return self.async_show_form( step_id=CONF_VERIFICATION_CODE, data_schema=vol.Schema({vol.Required(CONF_VERIFICATION_CODE): str}), - errors=None, + errors=errors or {}, ) diff --git a/homeassistant/components/icloud/const.py b/homeassistant/components/icloud/const.py index d62bacf12120a5..58c62f8a868702 100644 --- a/homeassistant/components/icloud/const.py +++ b/homeassistant/components/icloud/const.py @@ -12,7 +12,7 @@ # to store the cookie STORAGE_KEY = DOMAIN -STORAGE_VERSION = 1 +STORAGE_VERSION = 2 PLATFORMS = ["device_tracker", "sensor"] diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json index 40b58cbf2d0ae0..4d96f42b8cbfa2 100644 --- a/homeassistant/components/icloud/manifest.json +++ b/homeassistant/components/icloud/manifest.json @@ -3,6 +3,6 @@ "name": "Apple iCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/icloud", - "requirements": ["pyicloud==0.9.7"], - "codeowners": ["@Quentame"] + "requirements": ["pyicloud==0.10.2"], + "codeowners": ["@Quentame", "@nzapponi"] } diff --git a/homeassistant/components/icloud/strings.json b/homeassistant/components/icloud/strings.json index d07b3c3b870005..70ab11157d37ce 100644 --- a/homeassistant/components/icloud/strings.json +++ b/homeassistant/components/icloud/strings.json @@ -35,7 +35,7 @@ "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "send_verification_code": "Failed to send verification code", - "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" + "validate_verification_code": "Failed to verify your verification code, try again" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", diff --git a/homeassistant/components/icloud/translations/en.json b/homeassistant/components/icloud/translations/en.json index 3097302ded2954..36e657011e3242 100644 --- a/homeassistant/components/icloud/translations/en.json +++ b/homeassistant/components/icloud/translations/en.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Invalid authentication", "send_verification_code": "Failed to send verification code", - "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" + "validate_verification_code": "Failed to verify your verification code, try again" }, "step": { "reauth": { diff --git a/requirements_all.txt b/requirements_all.txt index 309b77acfe9bee..8658e56f706876 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1443,7 +1443,7 @@ pyhomematic==0.1.71 pyhomeworks==0.0.6 # homeassistant.components.icloud -pyicloud==0.9.7 +pyicloud==0.10.2 # homeassistant.components.insteon pyinsteon==1.0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd388ff403caf8..aa2eb7af342e6c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -742,7 +742,7 @@ pyheos==0.7.2 pyhomematic==0.1.71 # homeassistant.components.icloud -pyicloud==0.9.7 +pyicloud==0.10.2 # homeassistant.components.insteon pyinsteon==1.0.8 diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py index a774e61f3ece98..998a69c575a297 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -51,6 +51,7 @@ def mock_controller_service(): with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) @@ -58,15 +59,31 @@ def mock_controller_service(): yield service_mock +@pytest.fixture(name="service_2fa") +def mock_controller_2fa_service(): + """Mock a successful 2fa service.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = True + service_mock.return_value.requires_2sa = True + service_mock.return_value.validate_2fa_code = Mock(return_value=True) + service_mock.return_value.is_trusted_session = False + yield service_mock + + @pytest.fixture(name="service_authenticated") def mock_controller_service_authenticated(): """Mock a successful service while already authenticate.""" with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = False + service_mock.return_value.is_trusted_session = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) + service_mock.return_value.validate_2fa_code = Mock(return_value=True) service_mock.return_value.validate_verification_code = Mock(return_value=True) yield service_mock @@ -77,6 +94,7 @@ def mock_controller_service_authenticated_no_device(): with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = False service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) @@ -85,24 +103,53 @@ def mock_controller_service_authenticated_no_device(): yield service_mock +@pytest.fixture(name="service_authenticated_not_trusted") +def mock_controller_service_authenticated_not_trusted(): + """Mock a successful service while already authenticated, but the session is not trusted.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = False + service_mock.return_value.requires_2sa = False + service_mock.return_value.is_trusted_session = False + service_mock.return_value.trusted_devices = TRUSTED_DEVICES + service_mock.return_value.send_verification_code = Mock(return_value=True) + service_mock.return_value.validate_2fa_code = Mock(return_value=True) + service_mock.return_value.validate_verification_code = Mock(return_value=True) + yield service_mock + + @pytest.fixture(name="service_send_verification_code_failed") def mock_controller_service_send_verification_code_failed(): """Mock a failed service during sending verification code step.""" with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=False) yield service_mock +@pytest.fixture(name="service_validate_2fa_code_failed") +def mock_controller_service_validate_2fa_code_failed(): + """Mock a failed service during validation of 2FA verification code step.""" + with patch( + "homeassistant.components.icloud.config_flow.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = True + service_mock.return_value.validate_2fa_code = Mock(return_value=False) + yield service_mock + + @pytest.fixture(name="service_validate_verification_code_failed") def mock_controller_service_validate_verification_code_failed(): """Mock a failed service during validation of verification code step.""" with patch( "homeassistant.components.icloud.config_flow.PyiCloudService" ) as service_mock: + service_mock.return_value.requires_2fa = False service_mock.return_value.requires_2sa = True service_mock.return_value.trusted_devices = TRUSTED_DEVICES service_mock.return_value.send_verification_code = Mock(return_value=True) @@ -409,6 +456,49 @@ async def test_validate_verification_code_failed( assert result["errors"] == {"base": "validate_verification_code"} +async def test_2fa_code_success(hass: HomeAssistantType, service_2fa: MagicMock): + """Test 2fa step success.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + service_2fa.return_value.requires_2fa = False + service_2fa.return_value.requires_2sa = False + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_VERIFICATION_CODE: "0"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == USERNAME + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_WITH_FAMILY] == DEFAULT_WITH_FAMILY + assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL + assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD + + +async def test_validate_2fa_code_failed( + hass: HomeAssistantType, service_validate_2fa_code_failed: MagicMock +): + """Test when we have errors during validate_verification_code.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_VERIFICATION_CODE: "0"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == CONF_VERIFICATION_CODE + assert result["errors"] == {"base": "validate_verification_code"} + + async def test_password_update( hass: HomeAssistantType, service_authenticated: MagicMock ): From b98d55f1c7b89281bfcc203e74075f6ea1d34b74 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 4 Feb 2021 14:18:51 +0100 Subject: [PATCH 0198/1818] Don't log missing mpd artwork inappropriately (#45908) This can get unnecessarily spammy and doesn't represent an actual actionable issue. Fixes: #45235 --- homeassistant/components/mpd/manifest.json | 2 +- homeassistant/components/mpd/media_player.py | 18 ++++++++++-------- requirements_all.txt | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mpd/manifest.json b/homeassistant/components/mpd/manifest.json index 12ce5b61b74bc9..a11b9fedd801c6 100644 --- a/homeassistant/components/mpd/manifest.json +++ b/homeassistant/components/mpd/manifest.json @@ -2,6 +2,6 @@ "domain": "mpd", "name": "Music Player Daemon (MPD)", "documentation": "https://www.home-assistant.io/integrations/mpd", - "requirements": ["python-mpd2==3.0.3"], + "requirements": ["python-mpd2==3.0.4"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 6685347b3e38cb..371d20606801a9 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -281,20 +281,22 @@ async def async_get_media_image(self): try: response = await self._client.readpicture(file) except mpd.CommandError as error: - _LOGGER.warning( - "Retrieving artwork through `readpicture` command failed: %s", - error, - ) + if error.errno is not mpd.FailureResponseCode.NO_EXIST: + _LOGGER.warning( + "Retrieving artwork through `readpicture` command failed: %s", + error, + ) # read artwork contained in the media directory (cover.{jpg,png,tiff,bmp}) if none is embedded if can_albumart and not response: try: response = await self._client.albumart(file) except mpd.CommandError as error: - _LOGGER.warning( - "Retrieving artwork through `albumart` command failed: %s", - error, - ) + if error.errno is not mpd.FailureResponseCode.NO_EXIST: + _LOGGER.warning( + "Retrieving artwork through `albumart` command failed: %s", + error, + ) if not response: return None, None diff --git a/requirements_all.txt b/requirements_all.txt index 8658e56f706876..8b5beab64fa095 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1789,7 +1789,7 @@ python-juicenet==1.0.1 python-miio==0.5.4 # homeassistant.components.mpd -python-mpd2==3.0.3 +python-mpd2==3.0.4 # homeassistant.components.mystrom python-mystrom==1.1.2 From 39dd590576faef7231e6a8d9197006d527fbd2d5 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 4 Feb 2021 14:16:09 +0100 Subject: [PATCH 0199/1818] Fix entities device_info property in Harmony integration (#45964) --- homeassistant/components/harmony/remote.py | 2 +- homeassistant/components/harmony/switch.py | 5 +++++ tests/components/harmony/conftest.py | 1 + tests/components/harmony/test_config_flow.py | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 8409983789b225..9b3d53c21fa81f 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -161,7 +161,7 @@ async def async_added_to_hass(self): @property def device_info(self): """Return device info.""" - self._data.device_info(DOMAIN) + return self._data.device_info(DOMAIN) @property def unique_id(self): diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index 5fae07c431ba35..2832872c2eff82 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -46,6 +46,11 @@ def unique_id(self): """Return the unique id.""" return f"{self._data.unique_id}-{self._activity}" + @property + def device_info(self): + """Return device info.""" + return self._data.device_info(DOMAIN) + @property def is_on(self): """Return if the current activity is the one for this switch.""" diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index e758a2795a94e3..cde8c43fe89d50 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -49,6 +49,7 @@ def __init__( self.change_channel = AsyncMock() self.sync = AsyncMock() self._callbacks = callbacks + self.fw_version = "123.456" async def connect(self): """Connect and call the appropriate callbacks.""" diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 52ef71fc8bcd19..2a7f80d5c2ffd8 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -153,7 +153,7 @@ async def test_form_cannot_connect(hass): assert result2["errors"] == {"base": "cannot_connect"} -async def test_options_flow(hass, mock_hc): +async def test_options_flow(hass, mock_hc, mock_write_config): """Test config flow options.""" config_entry = MockConfigEntry( domain=DOMAIN, From 8e531a301facbc2642333e408ce0fb50b62d5977 Mon Sep 17 00:00:00 2001 From: DeadEnd <45110141+DeadEnded@users.noreply.github.com> Date: Thu, 4 Feb 2021 11:02:56 -0500 Subject: [PATCH 0200/1818] Fix Local Media in Media Browser (#45987) Co-authored-by: Paulus Schoutsen --- homeassistant/components/media_source/local_source.py | 6 +++--- tests/components/media_source/test_local_source.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index d7a2bdfd9383dd..fa62ba48c5f1d1 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -10,7 +10,7 @@ from homeassistant.components.media_player.errors import BrowseError from homeassistant.components.media_source.error import Unresolvable from homeassistant.core import HomeAssistant, callback -from homeassistant.util import raise_if_invalid_filename +from homeassistant.util import raise_if_invalid_path from .const import DOMAIN, MEDIA_CLASS_MAP, MEDIA_MIME_TYPES from .models import BrowseMediaSource, MediaSource, MediaSourceItem, PlayMedia @@ -51,7 +51,7 @@ def async_parse_identifier(self, item: MediaSourceItem) -> Tuple[str, str]: raise Unresolvable("Unknown source directory.") try: - raise_if_invalid_filename(location) + raise_if_invalid_path(location) except ValueError as err: raise Unresolvable("Invalid path.") from err @@ -192,7 +192,7 @@ async def get( ) -> web.FileResponse: """Start a GET request.""" try: - raise_if_invalid_filename(location) + raise_if_invalid_path(location) except ValueError as err: raise web.HTTPBadRequest() from err diff --git a/tests/components/media_source/test_local_source.py b/tests/components/media_source/test_local_source.py index ad10df7cfd3d7b..e3e2a3f1617d61 100644 --- a/tests/components/media_source/test_local_source.py +++ b/tests/components/media_source/test_local_source.py @@ -23,7 +23,7 @@ async def test_async_browse_media(hass): await media_source.async_browse_media( hass, f"{const.URI_SCHEME}{const.DOMAIN}/local/test/not/exist" ) - assert str(excinfo.value) == "Invalid path." + assert str(excinfo.value) == "Path does not exist." # Test browse file with pytest.raises(media_source.BrowseError) as excinfo: From c68847def7db972994c95006b6fe97a7c705c149 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Feb 2021 16:29:41 +0100 Subject: [PATCH 0201/1818] Bump zwave-js-server-python to 0.17.1 (#45988) --- 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 7df75d7aed2e58..7083d6c372b8d2 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.17.0"], + "requirements": ["zwave-js-server-python==0.17.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8b5beab64fa095..b0de3eb79e504a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.0 +zwave-js-server-python==0.17.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa2eb7af342e6c..43686d5410f265 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1194,4 +1194,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.0 +zwave-js-server-python==0.17.1 From 2b1093332545187fe241319ff6952940f5124b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Feb 2021 16:45:59 +0100 Subject: [PATCH 0202/1818] Bump awesomeversion from 21.2.0 to 21.2.2 (#45993) --- homeassistant/package_constraints.txt | 1 + requirements.txt | 1 + setup.py | 1 + 3 files changed, 3 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 31a7414dc3cd17..adf5cd6088a496 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -5,6 +5,7 @@ aiohttp_cors==0.7.0 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 +awesomeversion==21.2.2 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/requirements.txt b/requirements.txt index c973f4e4030de5..4a983b0ba70c51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ aiohttp==3.7.3 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 +awesomeversion==21.2.2 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/setup.py b/setup.py index 7f77e3795b40a1..84b19d15762836 100755 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", + "awesomeversion==21.2.2", "bcrypt==3.1.7", "certifi>=2020.12.5", "ciso8601==2.1.3", From e312ed98c8f494d25fb5973b85016967eb58bf8e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Feb 2021 13:36:55 -1000 Subject: [PATCH 0203/1818] Do not listen for dhcp packets if the filter cannot be setup (#46006) --- homeassistant/components/dhcp/__init__.py | 19 ++++++++++ tests/components/dhcp/test_init.py | 44 +++++++++++++++++++---- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index a71db430da41cd..9c2c3d3bc3566e 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -7,6 +7,7 @@ import os import threading +from scapy.arch.common import compile_filter from scapy.config import conf from scapy.error import Scapy_Exception from scapy.layers.dhcp import DHCP @@ -217,6 +218,15 @@ async def async_start(self): ) return + try: + await _async_verify_working_pcap(self.hass, FILTER) + except (Scapy_Exception, ImportError) as ex: + _LOGGER.error( + "Cannot watch for dhcp packets without a functional packet filter: %s", + ex, + ) + return + self._sniffer = AsyncSniffer( filter=FILTER, started_callback=self._started.set, @@ -283,3 +293,12 @@ def _verify_l2socket_creation_permission(): any permission or bind errors. """ conf.L2socket() + + +async def _async_verify_working_pcap(hass, cap_filter): + """Verify we can create a packet filter. + + If we cannot create a filter we will be listening for + all traffic which is too intensive. + """ + await hass.async_add_executor_job(compile_filter, cap_filter) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 049128248a7b8e..fc24c8201e26b1 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -280,7 +280,11 @@ async def test_setup_and_stop(hass): ) await hass.async_block_till_done() - with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call: + with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call, patch( + "homeassistant.components.dhcp._verify_l2socket_creation_permission", + ), patch( + "homeassistant.components.dhcp.compile_filter", + ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -325,21 +329,49 @@ async def test_setup_fails_non_root(hass, caplog): ) await hass.async_block_till_done() - wait_event = threading.Event() - with patch("os.geteuid", return_value=10), patch( "homeassistant.components.dhcp._verify_l2socket_creation_permission", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - wait_event.set() assert "Cannot watch for dhcp packets without root or CAP_NET_RAW" in caplog.text +async def test_setup_fails_with_broken_libpcap(hass, caplog): + """Test we abort if libpcap is missing or broken.""" + + assert await async_setup_component( + hass, + dhcp.DOMAIN, + {}, + ) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.dhcp._verify_l2socket_creation_permission", + ), patch( + "homeassistant.components.dhcp.compile_filter", + side_effect=ImportError, + ) as compile_filter, patch( + "homeassistant.components.dhcp.AsyncSniffer", + ) as async_sniffer: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + assert compile_filter.called + assert not async_sniffer.called + assert ( + "Cannot watch for dhcp packets without a functional packet filter" + in caplog.text + ) + + async def test_device_tracker_hostname_and_macaddress_exists_before_start(hass): """Test matching based on hostname and macaddress before start.""" hass.states.async_set( From 8cce60b1accd8acd1cdbfa08fb07a5c4e1128b7c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Feb 2021 20:44:40 +0100 Subject: [PATCH 0204/1818] Bump zwave-js-server-python to 0.17.2 (#46010) --- 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 7083d6c372b8d2..4bd12baa685198 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.17.1"], + "requirements": ["zwave-js-server-python==0.17.2"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index b0de3eb79e504a..086c444b3d81ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2381,4 +2381,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.1 +zwave-js-server-python==0.17.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 43686d5410f265..c2ae196767cc06 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1194,4 +1194,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.1 +zwave-js-server-python==0.17.2 From 77887f123ed76916cc4abe416ac74867c6a7d87a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Feb 2021 12:39:44 -1000 Subject: [PATCH 0205/1818] dhcp does not need promisc mode. Disable it in scapy (#46018) --- homeassistant/components/dhcp/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 9c2c3d3bc3566e..d33c6159888fa7 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -292,6 +292,8 @@ def _verify_l2socket_creation_permission(): thread so we will not be able to capture any permission or bind errors. """ + # disable scapy promiscuous mode as we do not need it + conf.sniff_promisc = 0 conf.L2socket() From ac0f856378d9364bcb7d1004814555e26bf78ad5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Feb 2021 23:41:52 +0000 Subject: [PATCH 0206/1818] Bumped version to 2021.2.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 63e11720c1ef65..08d9dd751db68e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From c6bd5b1b7162265d65345c7041b33c9bfd4a92b6 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 5 Feb 2021 00:03:54 +0000 Subject: [PATCH 0207/1818] [ci skip] Translation update --- .../components/airvisual/translations/et.json | 2 +- .../components/airvisual/translations/it.json | 22 +++++++++- .../components/airvisual/translations/ru.json | 22 +++++++++- .../ambiclimate/translations/ca.json | 2 +- .../ambiclimate/translations/en.json | 2 +- .../ambiclimate/translations/et.json | 2 +- .../ambiclimate/translations/zh-Hant.json | 2 +- .../components/atag/translations/et.json | 2 +- .../components/auth/translations/et.json | 4 +- .../binary_sensor/translations/pl.json | 10 ++--- .../binary_sensor/translations/tr.json | 4 +- .../components/braviatv/translations/et.json | 2 +- .../components/brother/translations/et.json | 2 +- .../components/deconz/translations/et.json | 2 +- .../dialogflow/translations/et.json | 2 +- .../components/goalzero/translations/ca.json | 2 +- .../components/goalzero/translations/en.json | 2 +- .../components/goalzero/translations/et.json | 2 +- .../components/goalzero/translations/it.json | 2 +- .../components/group/translations/tr.json | 4 +- .../components/hangouts/translations/et.json | 6 +-- .../components/homekit/translations/ru.json | 14 ++++++- .../homekit_controller/translations/et.json | 2 +- .../components/hue/translations/et.json | 4 +- .../components/icloud/translations/et.json | 2 +- .../components/icloud/translations/it.json | 2 +- .../components/icloud/translations/pl.json | 2 +- .../icloud/translations/zh-Hant.json | 2 +- .../components/insteon/translations/et.json | 2 +- .../components/ipp/translations/et.json | 6 +-- .../components/konnected/translations/en.json | 2 +- .../components/konnected/translations/it.json | 2 +- .../lutron_caseta/translations/en.json | 2 +- .../components/lyric/translations/ru.json | 16 ++++++++ .../components/mazda/translations/it.json | 35 ++++++++++++++++ .../components/mazda/translations/pl.json | 35 ++++++++++++++++ .../components/mazda/translations/ru.json | 35 ++++++++++++++++ .../mazda/translations/zh-Hant.json | 35 ++++++++++++++++ .../minecraft_server/translations/et.json | 2 +- .../mobile_app/translations/et.json | 2 +- .../motion_blinds/translations/pl.json | 4 +- .../components/mqtt/translations/et.json | 2 +- .../components/nest/translations/en.json | 2 +- .../nightscout/translations/et.json | 2 +- .../openweathermap/translations/et.json | 2 +- .../components/owntracks/translations/et.json | 2 +- .../components/plaato/translations/it.json | 41 ++++++++++++++++++- .../components/plaato/translations/pl.json | 2 +- .../components/plaato/translations/ru.json | 37 +++++++++++++++++ .../components/point/translations/en.json | 2 +- .../components/risco/translations/et.json | 2 +- .../components/risco/translations/pl.json | 2 +- .../components/roomba/translations/en.json | 2 +- .../components/rpi_power/translations/et.json | 2 +- .../simplisafe/translations/et.json | 2 +- .../components/soma/translations/en.json | 2 +- .../components/soma/translations/it.json | 2 +- .../components/sonarr/translations/et.json | 2 +- .../components/spotify/translations/en.json | 2 +- .../components/spotify/translations/et.json | 2 +- .../components/spotify/translations/it.json | 2 +- .../components/spotify/translations/pl.json | 2 +- .../tellduslive/translations/en.json | 2 +- .../components/toon/translations/en.json | 2 +- .../components/toon/translations/et.json | 2 +- .../components/traccar/translations/ca.json | 2 +- .../components/traccar/translations/en.json | 2 +- .../components/traccar/translations/it.json | 2 +- .../traccar/translations/zh-Hant.json | 2 +- .../components/tuya/translations/pl.json | 2 +- .../components/unifi/translations/pl.json | 2 +- .../components/unifi/translations/ru.json | 1 + .../components/vera/translations/ca.json | 4 +- .../components/vera/translations/en.json | 4 +- .../components/vera/translations/it.json | 4 +- .../xiaomi_aqara/translations/en.json | 4 +- .../xiaomi_aqara/translations/it.json | 4 +- .../xiaomi_aqara/translations/zh-Hant.json | 2 +- .../zoneminder/translations/et.json | 2 +- .../components/zwave/translations/it.json | 2 +- .../components/zwave/translations/pl.json | 2 +- .../zwave/translations/zh-Hant.json | 2 +- 82 files changed, 376 insertions(+), 95 deletions(-) create mode 100644 homeassistant/components/lyric/translations/ru.json create mode 100644 homeassistant/components/mazda/translations/it.json create mode 100644 homeassistant/components/mazda/translations/pl.json create mode 100644 homeassistant/components/mazda/translations/ru.json create mode 100644 homeassistant/components/mazda/translations/zh-Hant.json diff --git a/homeassistant/components/airvisual/translations/et.json b/homeassistant/components/airvisual/translations/et.json index 9912dbce035051..0fae2bcc57bb65 100644 --- a/homeassistant/components/airvisual/translations/et.json +++ b/homeassistant/components/airvisual/translations/et.json @@ -17,7 +17,7 @@ "latitude": "Laiuskraad", "longitude": "Pikkuskraad" }, - "description": "Kasutage AirVisual pilve API-t geograafilise asukoha j\u00e4lgimiseks.", + "description": "Kasuta AirVisual pilve API-t geograafilise asukoha j\u00e4lgimiseks.", "title": "Seadista Geography" }, "geography_by_coords": { diff --git a/homeassistant/components/airvisual/translations/it.json b/homeassistant/components/airvisual/translations/it.json index 7a4062fbe7604c..be493669a641fb 100644 --- a/homeassistant/components/airvisual/translations/it.json +++ b/homeassistant/components/airvisual/translations/it.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Impossibile connettersi", "general_error": "Errore imprevisto", - "invalid_api_key": "Chiave API non valida" + "invalid_api_key": "Chiave API non valida", + "location_not_found": "Posizione non trovata" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "Utilizzare l'API di AirVisual cloud per monitorare una posizione geografica.", "title": "Configurare una Geografia" }, + "geography_by_coords": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine" + }, + "description": "Usa l'API cloud di AirVisual per monitorare una latitudine/longitudine.", + "title": "Configurare un'area geografica" + }, + "geography_by_name": { + "data": { + "api_key": "Chiave API", + "city": "Citt\u00e0", + "country": "Nazione", + "state": "Stato" + }, + "description": "Usa l'API cloud di AirVisual per monitorare una citt\u00e0/stato/paese.", + "title": "Configurare un'area geografica" + }, "node_pro": { "data": { "ip_address": "Host", diff --git a/homeassistant/components/airvisual/translations/ru.json b/homeassistant/components/airvisual/translations/ru.json index de9e5a730fecee..bc648c84bfe72a 100644 --- a/homeassistant/components/airvisual/translations/ru.json +++ b/homeassistant/components/airvisual/translations/ru.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "general_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", - "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API." + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "location_not_found": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e API AirVisual.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f" }, + "geography_by_coords": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430" + }, + "description": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0448\u0438\u0440\u043e\u0442\u044b/\u0434\u043e\u043b\u0433\u043e\u0442\u044b.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f" + }, + "geography_by_name": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "city": "\u0413\u043e\u0440\u043e\u0434", + "country": "\u0421\u0442\u0440\u0430\u043d\u0430", + "state": "\u0448\u0442\u0430\u0442" + }, + "description": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0433\u043e\u0440\u043e\u0434\u0430/\u0448\u0442\u0430\u0442\u0430/\u0441\u0442\u0440\u0430\u043d\u044b.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f" + }, "node_pro": { "data": { "ip_address": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/ambiclimate/translations/ca.json b/homeassistant/components/ambiclimate/translations/ca.json index b635d877ffe90c..8e54a222217516 100644 --- a/homeassistant/components/ambiclimate/translations/ca.json +++ b/homeassistant/components/ambiclimate/translations/ca.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "V\u00e9s a l'[enlla\u00e7]({authorization_url}) i **Permet** l'acc\u00e9s al teu compte de Ambiclimate, despr\u00e9s torna i prem **Envia** (a sota).\n(Assegura't que l'enlla\u00e7 de retorn \u00e9s el seg\u00fcent {cb_url})", + "description": "V\u00e9s a l'[enlla\u00e7]({authorization_url}) i **Permet** l'acc\u00e9s al teu compte de Ambiclimate, despr\u00e9s torna i prem **Envia** a sota.\n(Assegura't que l'enlla\u00e7 de retorn \u00e9s el seg\u00fcent {cb_url})", "title": "Autenticaci\u00f3 amb Ambi Climate" } } diff --git a/homeassistant/components/ambiclimate/translations/en.json b/homeassistant/components/ambiclimate/translations/en.json index 01c52875250f63..8621b0e247c7ab 100644 --- a/homeassistant/components/ambiclimate/translations/en.json +++ b/homeassistant/components/ambiclimate/translations/en.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback url is {cb_url})", + "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback URL is {cb_url})", "title": "Authenticate Ambiclimate" } } diff --git a/homeassistant/components/ambiclimate/translations/et.json b/homeassistant/components/ambiclimate/translations/et.json index f9da8f8f7cd910..ff2264c3e0ebf3 100644 --- a/homeassistant/components/ambiclimate/translations/et.json +++ b/homeassistant/components/ambiclimate/translations/et.json @@ -9,7 +9,7 @@ "default": "Ambiclimate autentimine \u00f5nnestus" }, "error": { - "follow_link": "Enne Esita nupu vajutamist j\u00e4rgige linki ja autentige", + "follow_link": "Enne Esita nupu vajutamist j\u00e4rgi linki ja autendi", "no_token": "Ambiclimate ei ole autenditud" }, "step": { diff --git a/homeassistant/components/ambiclimate/translations/zh-Hant.json b/homeassistant/components/ambiclimate/translations/zh-Hant.json index f91c38dd36e6f7..e50accd732763e 100644 --- a/homeassistant/components/ambiclimate/translations/zh-Hant.json +++ b/homeassistant/components/ambiclimate/translations/zh-Hant.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "\u8acb\u4f7f\u7528\u6b64[\u9023\u7d50]\uff08{authorization_url}\uff09\u4e26\u9ede\u9078 **\u5141\u8a31** \u4ee5\u5b58\u53d6 Ambiclimate \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684 **\u50b3\u9001**\u3002\n\uff08\u78ba\u5b9a Callback url \u70ba {cb_url}\uff09", + "description": "\u8acb\u4f7f\u7528\u6b64 [\u9023\u7d50]\uff08{authorization_url}\uff09\u4e26\u9ede\u9078**\u5141\u8a31**\u4ee5\u5b58\u53d6 Ambiclimate \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684**\u50b3\u9001**\u3002\n\uff08\u78ba\u5b9a\u6307\u5b9a Callback URL \u70ba {cb_url}\uff09", "title": "\u8a8d\u8b49 Ambiclimate" } } diff --git a/homeassistant/components/atag/translations/et.json b/homeassistant/components/atag/translations/et.json index 2a4094806ed81b..fd0651a219c628 100644 --- a/homeassistant/components/atag/translations/et.json +++ b/homeassistant/components/atag/translations/et.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", - "unauthorized": "Sidumine on keelatud, kontrollige seadme tuvastamistaotlust" + "unauthorized": "Sidumine on keelatud, kontrolli seadme tuvastamistaotlust" }, "step": { "user": { diff --git a/homeassistant/components/auth/translations/et.json b/homeassistant/components/auth/translations/et.json index 03fc42f38e2ac2..9b22951e7fa6f7 100644 --- a/homeassistant/components/auth/translations/et.json +++ b/homeassistant/components/auth/translations/et.json @@ -10,7 +10,7 @@ "step": { "init": { "description": "Vali \u00fcks teavitusteenustest:", - "title": "Seadistage Notify poolt edastatud \u00fchekordne parool" + "title": "Seadista Notify poolt edastatud \u00fchekordne parool" }, "setup": { "description": "\u00dchekordne parool on saadetud **notify. {notify_service}**. Palun sisesta see allpool:", @@ -26,7 +26,7 @@ "step": { "init": { "description": "Kahefaktorilise autentimise aktiveerimiseks ajap\u00f5histe \u00fchekordsete paroolide abil skanni QR-kood oma autentimisrakendusega. Kui seda pole, soovitame kas [Google Authenticator] (https://support.google.com/accounts/answer/1066447) v\u00f5i [Authy] (https://authy.com/).\n\n {qr_code}\n\n P\u00e4rast koodi skannimist sisesta seadistuse kinnitamiseks rakenduse kuuekohaline kood. Kui on probleeme QR-koodi skannimisega, tehke koodiga **' {code}' ** k\u00e4sitsi seadistamine.", - "title": "Seadistage TOTP-ga kaheastmeline autentimine" + "title": "Seadista TOTP-ga kaheastmeline autentimine" } }, "title": "" diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index 726765aea0255d..59fd573d5b3b18 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -95,8 +95,8 @@ "on": "w\u0142." }, "battery": { - "off": "na\u0142adowana", - "on": "roz\u0142adowana" + "off": "Normalna", + "on": "Niska" }, "battery_charging": { "off": "roz\u0142adowywanie", @@ -107,8 +107,8 @@ "on": "zimno" }, "connectivity": { - "off": "offline", - "on": "online" + "off": "Roz\u0142\u0105czony", + "on": "Po\u0142\u0105czony" }, "door": { "off": "zamkni\u0119te", @@ -135,7 +135,7 @@ "on": "otwarty" }, "moisture": { - "off": "brak wilgoci", + "off": "Sucho", "on": "wilgo\u0107" }, "motion": { diff --git a/homeassistant/components/binary_sensor/translations/tr.json b/homeassistant/components/binary_sensor/translations/tr.json index 94e1496cc30b7a..daf44cc967b554 100644 --- a/homeassistant/components/binary_sensor/translations/tr.json +++ b/homeassistant/components/binary_sensor/translations/tr.json @@ -75,8 +75,8 @@ "on": "Tak\u0131l\u0131" }, "presence": { - "off": "[%key:common::state::evde_degil%]", - "on": "[%key:common::state::evde%]" + "off": "D\u0131\u015farda", + "on": "Evde" }, "problem": { "off": "Tamam", diff --git a/homeassistant/components/braviatv/translations/et.json b/homeassistant/components/braviatv/translations/et.json index b69844ee839b1b..6930186aeba9e0 100644 --- a/homeassistant/components/braviatv/translations/et.json +++ b/homeassistant/components/braviatv/translations/et.json @@ -21,7 +21,7 @@ "data": { "host": "" }, - "description": "Seadista Sony Bravia TV sidumine. Kuion probleeme seadetega mine: https://www.home-assistant.io/integrations/braviatv \n\nVeenduge, et teler on sisse l\u00fclitatud.", + "description": "Seadista Sony Bravia TV sidumine. Kuion probleeme seadetega mine: https://www.home-assistant.io/integrations/braviatv \n\nVeendu, et teler on sisse l\u00fclitatud.", "title": "" } } diff --git a/homeassistant/components/brother/translations/et.json b/homeassistant/components/brother/translations/et.json index 190db6ed7687c3..7b2b7c1b4a5abc 100644 --- a/homeassistant/components/brother/translations/et.json +++ b/homeassistant/components/brother/translations/et.json @@ -16,7 +16,7 @@ "host": "Host", "type": "Printeri t\u00fc\u00fcp" }, - "description": "Seadistage Brotheri printeri sidumine. Kui teil on seadistamisega probleeme minge aadressile https://www.home-assistant.io/integrations/brother" + "description": "Seadista Brotheri printeri sidumine. Kui seadistamisega on probleeme mine aadressile https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/deconz/translations/et.json b/homeassistant/components/deconz/translations/et.json index ad5c07b6607519..9f6644ea18669b 100644 --- a/homeassistant/components/deconz/translations/et.json +++ b/homeassistant/components/deconz/translations/et.json @@ -18,7 +18,7 @@ "title": "deCONZ Zigbee v\u00e4rav Hass.io pistikprogrammi kaudu" }, "link": { - "description": "Home Assistanti registreerumiseks ava deCONZ-i l\u00fc\u00fcs.\n\n 1. Minge deCONZ Settings - > Gateway - > Advanced\n 2. Vajutage nuppu \"Authenticate app\"", + "description": "Home Assistanti registreerumiseks ava deCONZ-i l\u00fc\u00fcs.\n\n 1. Mine deCONZ Settings - > Gateway - > Advanced\n 2. Vajuta nuppu \"Authenticate app\"", "title": "\u00dchenda deCONZ-iga" }, "manual_input": { diff --git a/homeassistant/components/dialogflow/translations/et.json b/homeassistant/components/dialogflow/translations/et.json index 989db1c25648d6..8ffe23497efe89 100644 --- a/homeassistant/components/dialogflow/translations/et.json +++ b/homeassistant/components/dialogflow/translations/et.json @@ -10,7 +10,7 @@ "step": { "user": { "description": "Kas oled kindel, et soovid seadistada Dialogflow?", - "title": "Seadistage Dialogflow veebihaak" + "title": "Seadista Dialogflow veebihaak" } } } diff --git a/homeassistant/components/goalzero/translations/ca.json b/homeassistant/components/goalzero/translations/ca.json index 2d301d5ef07800..ac4c2a696e23e6 100644 --- a/homeassistant/components/goalzero/translations/ca.json +++ b/homeassistant/components/goalzero/translations/ca.json @@ -14,7 +14,7 @@ "host": "Amfitri\u00f3", "name": "Nom" }, - "description": "En primer lloc, has de baixar-te l'aplicaci\u00f3 Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nSegueix les instruccions per connectar el teu Yeti a la teva xarxa Wifi. A continuaci\u00f3, has d'obtenir la IP d'amfitri\u00f3 del teu encaminador (router). Cal que aquest tingui la configuraci\u00f3 DHCP activada per al teu dispositiu per aix\u00ed garantir que la IP no canvi\u00ef. Si cal, consulta el manual del teu encaminador.", + "description": "En primer lloc, has de baixar-te l'aplicaci\u00f3 Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nSegueix les instruccions per connectar el Yeti a la xarxa Wifi. A continuaci\u00f3, has d'obtenir la IP d'amfitri\u00f3 del teu router. Cal que aquest tingui la configuraci\u00f3 DHCP activada per al teu dispositiu, per aix\u00ed garantir que la IP no canvi\u00ef. Si cal, consulta el manual del router.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/en.json b/homeassistant/components/goalzero/translations/en.json index 08c823e2ad2e4f..25aa32e4b754cf 100644 --- a/homeassistant/components/goalzero/translations/en.json +++ b/homeassistant/components/goalzero/translations/en.json @@ -14,7 +14,7 @@ "host": "Host", "name": "Name" }, - "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wifi network. Then get the host ip from your router. DHCP must be set up in your router settings for the device to ensure the host ip does not change. Refer to your router's user manual.", + "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wifi network. Then get the host IP from your router. DHCP must be set up in your router settings for the device to ensure the host IP does not change. Refer to your router's user manual.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/goalzero/translations/et.json b/homeassistant/components/goalzero/translations/et.json index 4479ba8669e0eb..74f84f1d72b092 100644 --- a/homeassistant/components/goalzero/translations/et.json +++ b/homeassistant/components/goalzero/translations/et.json @@ -14,7 +14,7 @@ "host": "", "name": "Nimi" }, - "description": "Alustuseks peate alla laadima rakenduse Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\n Yeti Wifi-v\u00f5rguga \u00fchendamiseks j\u00e4rgige juhiseid. Seej\u00e4rel hankige oma ruuterilt host IP. DHCP peab olema ruuteri seadetes seadistatud, et tagada, et host-IP ei muutuks. Vaadake ruuteri kasutusjuhendit.", + "description": "Alustuseks pead alla laadima rakenduse Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\n Yeti Wifi-v\u00f5rguga \u00fchendamiseks j\u00e4rgi juhiseid. Seej\u00e4rel hangi oma ruuterilt host IP. DHCP peab olema ruuteri seadetes seadistatud, et tagada, et host-IP ei muutuks. Vaata ruuteri kasutusjuhendit.", "title": "" } } diff --git a/homeassistant/components/goalzero/translations/it.json b/homeassistant/components/goalzero/translations/it.json index 10df269d59aef8..24f04a0bafebd5 100644 --- a/homeassistant/components/goalzero/translations/it.json +++ b/homeassistant/components/goalzero/translations/it.json @@ -14,7 +14,7 @@ "host": "Host", "name": "Nome" }, - "description": "Innanzitutto, devi scaricare l'app Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nSegui le istruzioni per connettere il tuo Yeti alla tua rete Wifi. Quindi ottieni l'ip host dal tuo router. Il DHCP deve essere configurato nelle impostazioni del router affinch\u00e9 il dispositivo assicuri che l'ip host non cambi. Fare riferimento al manuale utente del router.", + "description": "Innanzitutto, devi scaricare l'app Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nSegui le istruzioni per connettere il tuo Yeti alla tua rete Wifi. Quindi ottieni l'ip host dal tuo router. Il DHCP deve essere configurato nelle impostazioni del router affinch\u00e9 assicuri che l'ip host non cambi. Fare riferimento al manuale utente del router.", "title": "Goal Zero Yeti" } } diff --git a/homeassistant/components/group/translations/tr.json b/homeassistant/components/group/translations/tr.json index f92785a737f6ff..5a596efdf01089 100644 --- a/homeassistant/components/group/translations/tr.json +++ b/homeassistant/components/group/translations/tr.json @@ -2,9 +2,9 @@ "state": { "_": { "closed": "Kapand\u0131", - "home": "[%key:common::state::evde%]", + "home": "Evde", "locked": "Kilitli", - "not_home": "[%key:common::state::evde_degil%]", + "not_home": "D\u0131\u015far\u0131da", "off": "Kapal\u0131", "ok": "Tamam", "on": "A\u00e7\u0131k", diff --git a/homeassistant/components/hangouts/translations/et.json b/homeassistant/components/hangouts/translations/et.json index a587edcd632b17..6bcc19d2043132 100644 --- a/homeassistant/components/hangouts/translations/et.json +++ b/homeassistant/components/hangouts/translations/et.json @@ -5,9 +5,9 @@ "unknown": "Tundmatu viga" }, "error": { - "invalid_2fa": "Vale 2-teguriline autentimine, proovige uuesti.", - "invalid_2fa_method": "Kehtetu 2FA meetod (kontrollige telefoni teel).", - "invalid_login": "Vale Kasutajanimi, palun proovige uuesti." + "invalid_2fa": "Vale 2-teguriline autentimine, proovi uuesti.", + "invalid_2fa_method": "Kehtetu 2FA meetod (kontrolli telefoni teel).", + "invalid_login": "Vale kasutajanimi, palun proovi uuesti." }, "step": { "2fa": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 6cf96c2dd7857c..84346aed2ef66c 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -7,7 +7,16 @@ "accessory_mode": { "data": { "entity_id": "\u041e\u0431\u044a\u0435\u043a\u0442" - } + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442.", + "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" + }, + "bridge_mode": { + "data": { + "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u044b. \u0411\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438\u0437 \u0434\u043e\u043c\u0435\u043d\u0430.", + "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "pairing": { "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e, \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u0445\" \u043a\u0430\u043a \"HomeKit Bridge Setup\".", @@ -16,7 +25,8 @@ "user": { "data": { "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", - "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b" + "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b", + "mode": "\u0420\u0435\u0436\u0438\u043c" }, "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "HomeKit" diff --git a/homeassistant/components/homekit_controller/translations/et.json b/homeassistant/components/homekit_controller/translations/et.json index 40537554ee2a69..6df49751478c2c 100644 --- a/homeassistant/components/homekit_controller/translations/et.json +++ b/homeassistant/components/homekit_controller/translations/et.json @@ -13,7 +13,7 @@ "error": { "authentication_error": "Vale HomeKiti kood. Kontrolli seda ja proovi uuesti.", "max_peers_error": "Seade keeldus sidumist lisamast kuna puudub piisav salvestusruum.", - "pairing_failed": "Selle seadmega sidumise katsel ilmnes tundmatu t\u00f5rge. See v\u00f5ib olla ajutine t\u00f5rge v\u00f5i tseadet ei toetata praegu.", + "pairing_failed": "Selle seadmega sidumise katsel ilmnes tundmatu t\u00f5rge. See v\u00f5ib olla ajutine t\u00f5rge v\u00f5i seadet ei toetata praegu.", "unable_to_pair": "Ei saa siduda, proovi uuesti.", "unknown_error": "Seade teatas tundmatust t\u00f5rkest. Sidumine nurjus." }, diff --git a/homeassistant/components/hue/translations/et.json b/homeassistant/components/hue/translations/et.json index fcec56b9d0b4e2..afde880690e6d4 100644 --- a/homeassistant/components/hue/translations/et.json +++ b/homeassistant/components/hue/translations/et.json @@ -12,7 +12,7 @@ }, "error": { "linking": "Ilmnes tundmatu linkimist\u00f5rge.", - "register_failed": "Registreerimine nurjus. Proovige uuesti" + "register_failed": "Registreerimine nurjus. Proovi uuesti" }, "step": { "init": { @@ -22,7 +22,7 @@ "title": "Vali Hue sild" }, "link": { - "description": "Vajutage silla nuppu, et registreerida Philips Hue Home Assistant abil. \n\n ! [Nupu asukoht sillal] (/ static / images / config_philips_hue.jpg)", + "description": "Vajuta silla nuppu, et registreerida Philips Hue Home Assistant abil. \n\n ! [Nupu asukoht sillal] (/ static / images / config_philips_hue.jpg)", "title": "\u00dchenda jaotusseade" }, "manual": { diff --git a/homeassistant/components/icloud/translations/et.json b/homeassistant/components/icloud/translations/et.json index 29b24aabf5a4b2..af3457bb0dbd81 100644 --- a/homeassistant/components/icloud/translations/et.json +++ b/homeassistant/components/icloud/translations/et.json @@ -15,7 +15,7 @@ "data": { "password": "Salas\u00f5na" }, - "description": "Varem sisestatud salas\u00f5na kasutajale {username} ei t\u00f6\u00f6ta enam. Selle sidumise kasutamise j\u00e4tkamiseks v\u00e4rskendage oma salas\u00f5na.", + "description": "Varem sisestatud salas\u00f5na kasutajale {username} ei t\u00f6\u00f6ta enam. Selle sidumise kasutamise j\u00e4tkamiseks v\u00e4rskenda oma salas\u00f5na.", "title": "iCloudi tuvastusandmed" }, "trusted_device": { diff --git a/homeassistant/components/icloud/translations/it.json b/homeassistant/components/icloud/translations/it.json index 4fde8b33526447..32931d96a32649 100644 --- a/homeassistant/components/icloud/translations/it.json +++ b/homeassistant/components/icloud/translations/it.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Autenticazione non valida", "send_verification_code": "Impossibile inviare il codice di verifica", - "validate_verification_code": "Impossibile verificare il codice di verifica, scegliere un dispositivo attendibile e riavviare la verifica" + "validate_verification_code": "Impossibile verificare il codice di verifica, riprovare" }, "step": { "reauth": { diff --git a/homeassistant/components/icloud/translations/pl.json b/homeassistant/components/icloud/translations/pl.json index 4ac02d1f3f0dcc..e111518710b35e 100644 --- a/homeassistant/components/icloud/translations/pl.json +++ b/homeassistant/components/icloud/translations/pl.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Niepoprawne uwierzytelnienie", "send_verification_code": "Nie uda\u0142o si\u0119 wys\u0142a\u0107 kodu weryfikacyjnego", - "validate_verification_code": "Nie uda\u0142o si\u0119 zweryfikowa\u0107 kodu weryfikacyjnego, wybierz urz\u0105dzenie zaufane i ponownie rozpocznij weryfikacj\u0119" + "validate_verification_code": "Nie uda\u0142o si\u0119 zweryfikowa\u0107 kodu weryfikacyjnego, spr\u00f3buj ponownie" }, "step": { "reauth": { diff --git a/homeassistant/components/icloud/translations/zh-Hant.json b/homeassistant/components/icloud/translations/zh-Hant.json index 1c16db77faf56e..fe421275e2a61d 100644 --- a/homeassistant/components/icloud/translations/zh-Hant.json +++ b/homeassistant/components/icloud/translations/zh-Hant.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "send_verification_code": "\u50b3\u9001\u9a57\u8b49\u78bc\u5931\u6557", - "validate_verification_code": "\u7121\u6cd5\u9a57\u8b49\u8f38\u5165\u9a57\u8b49\u78bc\uff0c\u9078\u64c7\u4e00\u90e8\u4fe1\u4efb\u88dd\u7f6e\u3001\u7136\u5f8c\u91cd\u65b0\u57f7\u884c\u9a57\u8b49\u3002" + "validate_verification_code": "\u9a57\u8b49\u8f38\u5165\u9a57\u8b49\u78bc\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002" }, "step": { "reauth": { diff --git a/homeassistant/components/insteon/translations/et.json b/homeassistant/components/insteon/translations/et.json index 5fee63e1219b93..69368300c7e969 100644 --- a/homeassistant/components/insteon/translations/et.json +++ b/homeassistant/components/insteon/translations/et.json @@ -76,7 +76,7 @@ "port": "", "username": "Kasutajanimi" }, - "description": "Muutda Insteon Hubi \u00fchenduse teavet. P\u00e4rast selle muudatuse tegemist pead Home Assistanti taask\u00e4ivitama. See ei muuda jaoturi enda konfiguratsiooni. Hubis muudatuste tegemiseks kasutage rakendust Hub.", + "description": "Muutda Insteon Hubi \u00fchenduse teavet. P\u00e4rast selle muudatuse tegemist pead Home Assistanti taask\u00e4ivitama. See ei muuda jaoturi enda konfiguratsiooni. Hubis muudatuste tegemiseks kasuta rakendust Hub.", "title": "" }, "init": { diff --git a/homeassistant/components/ipp/translations/et.json b/homeassistant/components/ipp/translations/et.json index 622b71d758ad9e..5a0a2e69cfb4d9 100644 --- a/homeassistant/components/ipp/translations/et.json +++ b/homeassistant/components/ipp/translations/et.json @@ -11,7 +11,7 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", - "connection_upgrade": "Printeriga \u00fchenduse loomine nurjus. Proovige uuesti kui SSL/TLS-i suvand on m\u00e4rgitud." + "connection_upgrade": "Printeriga \u00fchenduse loomine nurjus. Proovi uuesti kui SSL/TLS-i suvand on m\u00e4rgitud." }, "flow_title": "Printer: {name}", "step": { @@ -23,8 +23,8 @@ "ssl": "Printer toetab SSL/TLS \u00fchendust", "verify_ssl": "Printer kasutab \u00f5iget SSL-serti" }, - "description": "Seadistage oma printer Interneti-printimisprotokolli (IPP) kaudu, et see integreeruks Home Assistantiga.", - "title": "Linkige oma printer" + "description": "Seadista oma printer Interneti-printimisprotokolli (IPP) kaudu, et see integreeruks Home Assistantiga.", + "title": "Lingi oma printer" }, "zeroconf_confirm": { "description": "Kas soovite seadistada {name}?", diff --git a/homeassistant/components/konnected/translations/en.json b/homeassistant/components/konnected/translations/en.json index 920e1453e499e5..32cf120e8af1f9 100644 --- a/homeassistant/components/konnected/translations/en.json +++ b/homeassistant/components/konnected/translations/en.json @@ -32,7 +32,7 @@ "not_konn_panel": "Not a recognized Konnected.io device" }, "error": { - "bad_host": "Invalid Override API host url" + "bad_host": "Invalid Override API host URL" }, "step": { "options_binary": { diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index b618ee04b48e51..da88fb0ac4de7e 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -32,7 +32,7 @@ "not_konn_panel": "Non \u00e8 un dispositivo Konnected.io riconosciuto" }, "error": { - "bad_host": "URL dell'host API di sostituzione non valido" + "bad_host": "URL host API di sostituzione non valido" }, "step": { "options_binary": { diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 8ea0672a3f3c39..96c00d6cb42b05 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -22,7 +22,7 @@ "data": { "host": "Host" }, - "description": "Enter the ip address of the device.", + "description": "Enter the IP address of the device.", "title": "Automaticlly connect to the bridge" } } diff --git a/homeassistant/components/lyric/translations/ru.json b/homeassistant/components/lyric/translations/ru.json new file mode 100644 index 00000000000000..8d41a95fd29936 --- /dev/null +++ b/homeassistant/components/lyric/translations/ru.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438." + }, + "create_entry": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/it.json b/homeassistant/components/mazda/translations/it.json new file mode 100644 index 00000000000000..5eb995a4dfb277 --- /dev/null +++ b/homeassistant/components/mazda/translations/it.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La riautenticazione ha avuto successo" + }, + "error": { + "account_locked": "Account bloccato. Per favore riprova pi\u00f9 tardi.", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "reauth": { + "data": { + "email": "E-mail", + "password": "Password", + "region": "Area geografica" + }, + "description": "Autenticazione non riuscita per Mazda Connected Services. Inserisci le tue credenziali attuali.", + "title": "Mazda Connected Services - Autenticazione non riuscita" + }, + "user": { + "data": { + "email": "E-mail", + "password": "Password", + "region": "Area geografica" + }, + "description": "Inserisci l'indirizzo e-mail e la password che utilizzi per accedere all'app mobile MyMazda.", + "title": "Mazda Connected Services - Aggiungi account" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/pl.json b/homeassistant/components/mazda/translations/pl.json new file mode 100644 index 00000000000000..12254f20662653 --- /dev/null +++ b/homeassistant/components/mazda/translations/pl.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "account_locked": "Konto zablokowane. Spr\u00f3buj ponownie p\u00f3\u017aniej.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "reauth": { + "data": { + "email": "Adres e-mail", + "password": "Has\u0142o", + "region": "Region" + }, + "description": "Uwierzytelnianie dla Mazda Connected Services nie powiod\u0142o si\u0119. Wprowad\u017a aktualne dane uwierzytelniaj\u0105ce.", + "title": "Mazda Connected Services - Uwierzytelnianie nie powiod\u0142o si\u0119" + }, + "user": { + "data": { + "email": "Adres e-mail", + "password": "Has\u0142o", + "region": "Region" + }, + "description": "Wprowad\u017a adres e-mail i has\u0142o, kt\u00f3rych u\u017cywasz do logowania si\u0119 do aplikacji mobilnej MyMazda.", + "title": "Mazda Connected Services - Dodawanie konta" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/ru.json b/homeassistant/components/mazda/translations/ru.json new file mode 100644 index 00000000000000..e41babd499dd61 --- /dev/null +++ b/homeassistant/components/mazda/translations/ru.json @@ -0,0 +1,35 @@ +{ + "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.", + "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": { + "account_locked": "\u0410\u043a\u043a\u0430\u0443\u043d\u0442 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "reauth": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + }, + "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. \u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", + "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", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0412\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 MyMazda.", + "title": "Mazda Connected Services" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/zh-Hant.json b/homeassistant/components/mazda/translations/zh-Hant.json new file mode 100644 index 00000000000000..48232664683c30 --- /dev/null +++ b/homeassistant/components/mazda/translations/zh-Hant.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "account_locked": "\u5e33\u865f\u5df2\u9396\u5b9a\uff0c\u8acb\u7a0d\u5f8c\u518d\u8a66\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "reauth": { + "data": { + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc", + "region": "\u5340\u57df" + }, + "description": "Mazda Connected \u670d\u52d9\u8a8d\u8b49\u5931\u6557\u3002\u8acb\u8f38\u5165\u76ee\u524d\u6191\u8b49\u3002", + "title": "Mazda Connected \u670d\u52d9 - \u8a8d\u8b49\u5931\u6557" + }, + "user": { + "data": { + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc", + "region": "\u5340\u57df" + }, + "description": "\u8acb\u8f38\u5165\u767b\u5165MyMazda \u884c\u52d5 App \u4e4b Email \u5730\u5740\u8207\u5bc6\u78bc\u3002", + "title": "Mazda Connected \u670d\u52d9 - \u65b0\u589e\u5e33\u865f" + } + } + }, + "title": "Mazda Connected \u670d\u52d9" +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/et.json b/homeassistant/components/minecraft_server/translations/et.json index a92449b0512b55..f8de21662aa5c2 100644 --- a/homeassistant/components/minecraft_server/translations/et.json +++ b/homeassistant/components/minecraft_server/translations/et.json @@ -4,7 +4,7 @@ "already_configured": "Teenus on juba seadistatud" }, "error": { - "cannot_connect": "Serveriga \u00fchenduse loomine nurjus. Kontrollige hosti ja porti ning proovige uuesti. Samuti veenduge, et kasutate oma serveris v\u00e4hemalt Minecrafti versiooni 1.7.", + "cannot_connect": "Serveriga \u00fchenduse loomine nurjus. Kontrolli hosti ja porti ning proovi uuesti. Samuti veendu, et kasutad oma serveris v\u00e4hemalt Minecrafti versiooni 1.7.", "invalid_ip": "IP-aadress on vale (MAC-aadressi ei \u00f5nnestunud tuvastada). Paranda ja proovi uuesti.", "invalid_port": "Lubatud pordivahemik on 1024\u201365535. Paranda ja proovi uuesti." }, diff --git a/homeassistant/components/mobile_app/translations/et.json b/homeassistant/components/mobile_app/translations/et.json index 41d2be9d455bd9..27f5fce2bdf991 100644 --- a/homeassistant/components/mobile_app/translations/et.json +++ b/homeassistant/components/mobile_app/translations/et.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "install_app": "Home Assistantiga sidumiseks avage mobiilirakendus. \u00dchilduvate rakenduste loendi leiate jaotisest [dokumendid] ( {apps_url} )." + "install_app": "Home Assistantiga sidumiseks ava mobiilirakendus. \u00dchilduvate rakenduste loendi leiate jaotisest [dokumendid] ( {apps_url} )." }, "step": { "confirm": { diff --git a/homeassistant/components/motion_blinds/translations/pl.json b/homeassistant/components/motion_blinds/translations/pl.json index 1d34d22d65e21b..61e9d22c3cf91f 100644 --- a/homeassistant/components/motion_blinds/translations/pl.json +++ b/homeassistant/components/motion_blinds/translations/pl.json @@ -8,7 +8,7 @@ "error": { "discovery_error": "Nie uda\u0142o si\u0119 wykry\u0107 bramki ruchu" }, - "flow_title": "Motion Blinds", + "flow_title": "Rolety Motion", "step": { "connect": { "data": { @@ -30,7 +30,7 @@ "host": "Adres IP" }, "description": "Po\u0142\u0105cz si\u0119 z bram\u0105 ruchu. Je\u015bli adres IP nie jest ustawiony, u\u017cywane jest automatyczne wykrywanie", - "title": "Motion Blinds" + "title": "Rolety Motion" } } } diff --git a/homeassistant/components/mqtt/translations/et.json b/homeassistant/components/mqtt/translations/et.json index 53d6d391e8f8df..d2b863cab46292 100644 --- a/homeassistant/components/mqtt/translations/et.json +++ b/homeassistant/components/mqtt/translations/et.json @@ -78,7 +78,7 @@ "will_retain": "L\u00f5petamisteate j\u00e4\u00e4dvustamine", "will_topic": "L\u00f5petamisteade" }, - "description": "Valige MQTT s\u00e4tted." + "description": "Vali MQTT s\u00e4tted." } } } diff --git a/homeassistant/components/nest/translations/en.json b/homeassistant/components/nest/translations/en.json index 6693c2e5614e32..f45c9ea489fc5c 100644 --- a/homeassistant/components/nest/translations/en.json +++ b/homeassistant/components/nest/translations/en.json @@ -7,7 +7,7 @@ "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", "reauth_successful": "Re-authentication was successful", "single_instance_allowed": "Already configured. Only a single configuration possible.", - "unknown_authorize_url_generation": "Unknown error generating an authorize url." + "unknown_authorize_url_generation": "Unknown error generating an authorize URL." }, "create_entry": { "default": "Successfully authenticated" diff --git a/homeassistant/components/nightscout/translations/et.json b/homeassistant/components/nightscout/translations/et.json index 1e77907f2af652..361b47893288a0 100644 --- a/homeassistant/components/nightscout/translations/et.json +++ b/homeassistant/components/nightscout/translations/et.json @@ -15,7 +15,7 @@ "api_key": "API v\u00f5ti", "url": "" }, - "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasutage ainult siis kui teie eksemplar on kaitstud (auth_default_roles! = readable).", + "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui teie eksemplar on kaitstud (auth_default_roles! = readable).", "title": "Sisesta oma Nightscouti serveri teave." } } diff --git a/homeassistant/components/openweathermap/translations/et.json b/homeassistant/components/openweathermap/translations/et.json index 26c688482fd9d3..e548c07236ee80 100644 --- a/homeassistant/components/openweathermap/translations/et.json +++ b/homeassistant/components/openweathermap/translations/et.json @@ -17,7 +17,7 @@ "mode": "Re\u017eiim", "name": "Sidumise nimi" }, - "description": "Seadistage OpenWeatherMapi sidumine. API-v\u00f5tme loomiseks minge aadressile https://openweathermap.org/appid", + "description": "Seadista OpenWeatherMapi sidumine. API-v\u00f5tme loomiseks mine aadressile https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/owntracks/translations/et.json b/homeassistant/components/owntracks/translations/et.json index 16ef569d9a4617..2ee171365a4ce2 100644 --- a/homeassistant/components/owntracks/translations/et.json +++ b/homeassistant/components/owntracks/translations/et.json @@ -4,7 +4,7 @@ "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." }, "create_entry": { - "default": "\n\nAva Android seadmes [rakendus OwnTracks] ( {android_url} ), mine eelistustele - > \u00fchendus. Muuda j\u00e4rgmisi seadeid:\n - Re\u017eiim: privaatne HTTP\n - Host: {webhook_url}\n - Identifitseerimine:\n - kasutajanimi: \" \"\n - seadme ID: \" \"\n\n IOS-is ava rakendus [OwnTracks] ( {ios_url} ), puuduta vasakus \u00fclanurgas ikooni (i) - > seaded. Muuda j\u00e4rgmisi seadeid:\n - Re\u017eiim: HTTP\n - URL: {webhook_url}\n - L\u00fclitage autentimine sisse\n - UserID: \" \" \"\n\n {secret}\n\n Lisateavet leiad [dokumentatsioonist] ( {docs_url} )." + "default": "\n\nAva Android seadmes [rakendus OwnTracks] ( {android_url} ), mine eelistustele - > \u00fchendus. Muuda j\u00e4rgmisi seadeid:\n - Re\u017eiim: privaatne HTTP\n - Host: {webhook_url}\n - Identifitseerimine:\n - kasutajanimi: \" \"\n - seadme ID: \" \"\n\n IOS-is ava rakendus [OwnTracks] ( {ios_url} ), puuduta vasakus \u00fclanurgas ikooni (i) - > seaded. Muuda j\u00e4rgmisi seadeid:\n - Re\u017eiim: HTTP\n - URL: {webhook_url}\n - L\u00fclita autentimine sisse\n - UserID: \" \" \"\n\n {secret}\n\n Lisateavet leiad [dokumentatsioonist] ( {docs_url} )." }, "step": { "user": { diff --git a/homeassistant/components/plaato/translations/it.json b/homeassistant/components/plaato/translations/it.json index ad289fa758f4b5..acd2fcfa3f4185 100644 --- a/homeassistant/components/plaato/translations/it.json +++ b/homeassistant/components/plaato/translations/it.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", "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." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai impostare la funzione webhook in Plaato Airlock. \n\n Inserisci le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Metodo: POST \n\n Vedi [la documentazione]({docs_url}) per ulteriori dettagli." + "default": "Il tuo Plaato {device_type} con nome **{device_name}** \u00e8 stato configurato con successo!" + }, + "error": { + "invalid_webhook_device": "Hai selezionato un dispositivo che non supporta l'invio di dati a un webhook. \u00c8 disponibile solo per Airlock", + "no_api_method": "Devi aggiungere un token di autenticazione o selezionare webhook", + "no_auth_token": "\u00c8 necessario aggiungere un token di autorizzazione" }, "step": { + "api_method": { + "data": { + "token": "Incolla il token di autenticazione qui", + "use_webhook": "Usa webhook" + }, + "description": "Per poter interrogare l'API \u00e8 necessario un `auth_token` che pu\u00f2 essere ottenuto seguendo [queste] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) istruzioni \n\n Dispositivo selezionato: **{device_type}** \n\n Se preferisci utilizzare il metodo webhook integrato (solo Airlock), seleziona la casella sottostante e lascia vuoto il token di autenticazione", + "title": "Seleziona il metodo API" + }, "user": { + "data": { + "device_name": "Assegna un nome al dispositivo", + "device_type": "Tipo di dispositivo Plaato" + }, "description": "Vuoi iniziare la configurazione?", - "title": "Configura il webhook di Plaato" + "title": "Imposta i dispositivi Plaato" + }, + "webhook": { + "description": "Per inviare eventi a Home Assistant, dovrai configurare la funzione webhook in Plaato Airlock. \n\n Compila le seguenti informazioni: \n\n - URL: \"{webhook_url}\"\n - Metodo: POST \n\n Vedere [la documentazione] ({docs_url}) per ulteriori dettagli.", + "title": "Webhook da utilizzare" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Intervallo di aggiornamento (minuti)" + }, + "description": "Imposta l'intervallo di aggiornamento (minuti)", + "title": "Opzioni per Plaato" + }, + "webhook": { + "description": "Informazioni webhook:\n\n- URL: \"{webhook_url}\"\n- Metodo: POST\n\n", + "title": "Opzioni per Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index c849f574c9c5cb..57df32c3f4eddf 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -46,7 +46,7 @@ "title": "Opcje dla Plaato" }, "webhook": { - "description": "Informacje o webhook: \n\n - URL: `{webhook_url}`\n - Metoda: POST \n\n", + "description": "Informacje o webhooku: \n\n - URL: `{webhook_url}`\n - Metoda: POST \n\n", "title": "Opcje dla areomierza Plaato Airlock" } } diff --git a/homeassistant/components/plaato/translations/ru.json b/homeassistant/components/plaato/translations/ru.json index 99e28ac9e04746..befce3d7e84c75 100644 --- a/homeassistant/components/plaato/translations/ru.json +++ b/homeassistant/components/plaato/translations/ru.json @@ -1,16 +1,53 @@ { "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.", "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." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Plaato Airlock.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." }, + "error": { + "invalid_webhook_device": "\u0412\u044b \u0432\u044b\u0431\u0440\u0430\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 Webhook. \u042d\u0442\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0430.", + "no_api_method": "\u041d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438\u043b\u0438 \u0432\u044b\u0431\u0440\u0430\u0442\u044c Webhook.", + "no_auth_token": "\u041d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + }, "step": { + "api_method": { + "data": { + "token": "\u0412\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0442\u043e\u043a\u0435\u043d \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u044e\u0434\u0430", + "use_webhook": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Webhook" + }, + "description": "\u0427\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044c API, \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f `auth_token`, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c, \u0441\u043b\u0435\u0434\u0443\u044f [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \n\n\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e: **{device_type}** \n\n\u0415\u0441\u043b\u0438 \u0412\u044b \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 Webhook (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f Airlock), \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0444\u043b\u0430\u0436\u043e\u043a \u043d\u0438\u0436\u0435 \u0438 \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u0443\u0441\u0442\u044b\u043c.", + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 API" + }, "user": { + "data": { + "device_name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "device_type": "\u0422\u0438\u043f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Plaato" + }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?", "title": "Plaato Airlock" + }, + "webhook": { + "description": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Plaato Airlock.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", + "title": "Webhook" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + }, + "description": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)", + "title": "Plaato" + }, + "webhook": { + "description": "Webhook:\n\n- URL: `{webhook_url}`\n- Method: POST", + "title": "Plaato Airlock" } } } diff --git a/homeassistant/components/point/translations/en.json b/homeassistant/components/point/translations/en.json index 50e9e4f3ce04bf..685a16cbbf5c8a 100644 --- a/homeassistant/components/point/translations/en.json +++ b/homeassistant/components/point/translations/en.json @@ -6,7 +6,7 @@ "authorize_url_timeout": "Timeout generating authorize URL.", "external_setup": "Point successfully configured from another flow.", "no_flows": "The component is not configured. Please follow the documentation.", - "unknown_authorize_url_generation": "Unknown error generating an authorize url." + "unknown_authorize_url_generation": "Unknown error generating an authorize URL." }, "create_entry": { "default": "Successfully authenticated" diff --git a/homeassistant/components/risco/translations/et.json b/homeassistant/components/risco/translations/et.json index 9bd35ec22db58f..c57d5d73fecb67 100644 --- a/homeassistant/components/risco/translations/et.json +++ b/homeassistant/components/risco/translations/et.json @@ -27,7 +27,7 @@ "armed_home": "Valves kodus", "armed_night": "Valves \u00f6ine" }, - "description": "Valige millisesse olekusse l\u00fcltub Risco alarm kui valvestada Home Assistant", + "description": "Vali millisesse olekusse l\u00fcltub Risco alarm kui valvestada Home Assistant", "title": "Lisa Risco olekud Home Assistanti olekutesse" }, "init": { diff --git a/homeassistant/components/risco/translations/pl.json b/homeassistant/components/risco/translations/pl.json index ef7ed9f13e0944..b39c2cde23b307 100644 --- a/homeassistant/components/risco/translations/pl.json +++ b/homeassistant/components/risco/translations/pl.json @@ -34,7 +34,7 @@ "data": { "code_arm_required": "Wymagaj kodu PIN do uzbrojenia", "code_disarm_required": "Wymagaj kodu PIN do rozbrojenia", - "scan_interval": "Cz\u0119stotliwo\u015b\u0107 od\u015bwie\u017cania (w sekundach)" + "scan_interval": "Jak cz\u0119sto odpytywa\u0107 Risco (w sekundach)" }, "title": "Opcje" }, diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 8d449e1881526f..9c373d649aa81e 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -25,7 +25,7 @@ "data": { "password": "Password" }, - "description": "The password could not be retrivied from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", + "description": "The password could not be retrieved from the device automatically. Please follow the steps outlined in the documentation at: {auth_help_url}", "title": "Enter Password" }, "manual": { diff --git a/homeassistant/components/rpi_power/translations/et.json b/homeassistant/components/rpi_power/translations/et.json index fdf32414ba7c58..ec8475d2dac4a7 100644 --- a/homeassistant/components/rpi_power/translations/et.json +++ b/homeassistant/components/rpi_power/translations/et.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "no_devices_found": "Ei leia selle komponendi jaoks vajalikku s\u00fcsteemiklassi. Veenduge, et teie kernel on v\u00e4rske ja riistvara on toetatud", + "no_devices_found": "Ei leia selle komponendi jaoks vajalikku s\u00fcsteemiklassi. Veendu, et kernel on v\u00e4rske ja riistvara on toetatud", "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." }, "step": { diff --git a/homeassistant/components/simplisafe/translations/et.json b/homeassistant/components/simplisafe/translations/et.json index b98a121046a507..7b6e317b9228df 100644 --- a/homeassistant/components/simplisafe/translations/et.json +++ b/homeassistant/components/simplisafe/translations/et.json @@ -12,7 +12,7 @@ }, "step": { "mfa": { - "description": "Kontrollige oma e-posti: link SimpliSafe-lt. P\u00e4rast lingi kontrollimist naase siia, et viia l\u00f5pule sidumise installimine.", + "description": "Kontrolli oma e-posti: link SimpliSafe-lt. P\u00e4rast lingi kontrollimist naase siia, et viia l\u00f5pule sidumise installimine.", "title": "SimpliSafe mitmeastmeline autentimine" }, "reauth_confirm": { diff --git a/homeassistant/components/soma/translations/en.json b/homeassistant/components/soma/translations/en.json index 6f28ee53ae26b6..fb5d17ac59de3d 100644 --- a/homeassistant/components/soma/translations/en.json +++ b/homeassistant/components/soma/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "You can only configure one Soma account.", - "authorize_url_timeout": "Timeout generating authorize url.", + "authorize_url_timeout": "Timeout generating authorize URL.", "connection_error": "Failed to connect to SOMA Connect.", "missing_configuration": "The Soma component is not configured. Please follow the documentation.", "result_error": "SOMA Connect responded with error status." diff --git a/homeassistant/components/soma/translations/it.json b/homeassistant/components/soma/translations/it.json index 0119fca7388ebf..237ce347cb0873 100644 --- a/homeassistant/components/soma/translations/it.json +++ b/homeassistant/components/soma/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "\u00c8 possibile configurare un solo account Soma.", - "authorize_url_timeout": "Timeout durante la generazione dell'URL di autorizzazione.", + "authorize_url_timeout": "Tempo scaduto nella generazione dell'URL di autorizzazione.", "connection_error": "Impossibile connettersi a SOMA Connect.", "missing_configuration": "Il componente Soma non \u00e8 configurato. Si prega di seguire la documentazione.", "result_error": "SOMA Connect ha risposto con stato di errore." diff --git a/homeassistant/components/sonarr/translations/et.json b/homeassistant/components/sonarr/translations/et.json index 957b3c74eae0f4..c95b2e9dc88d90 100644 --- a/homeassistant/components/sonarr/translations/et.json +++ b/homeassistant/components/sonarr/translations/et.json @@ -13,7 +13,7 @@ "step": { "reauth_confirm": { "description": "Sonarr-i sidumine tuleb k\u00e4sitsi taastuvastada Sonarr API abil: {host}", - "title": "Autentige uuesti Sonarriga" + "title": "Autendi Sonarriga uuesti" }, "user": { "data": { diff --git a/homeassistant/components/spotify/translations/en.json b/homeassistant/components/spotify/translations/en.json index 73ea219105bd75..7136e5a8e710ba 100644 --- a/homeassistant/components/spotify/translations/en.json +++ b/homeassistant/components/spotify/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "Timeout generating authorize url.", + "authorize_url_timeout": "Timeout generating authorize URL.", "missing_configuration": "The Spotify integration is not configured. Please follow the documentation.", "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", "reauth_account_mismatch": "The Spotify account authenticated with, does not match the account needed re-authentication." diff --git a/homeassistant/components/spotify/translations/et.json b/homeassistant/components/spotify/translations/et.json index 01583d1b0f0d37..c5cee44acca879 100644 --- a/homeassistant/components/spotify/translations/et.json +++ b/homeassistant/components/spotify/translations/et.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_timeout": "Kinnituse URLi ajal\u00f5pp", - "missing_configuration": "Spotify sidumine pole h\u00e4\u00e4lestatud. Palun j\u00e4rgige dokumentatsiooni.", + "missing_configuration": "Spotify sidumine pole h\u00e4\u00e4lestatud. Palun j\u00e4rgi dokumentatsiooni.", "no_url_available": "URL pole saadaval. Rohkem teavet [check the help section]({docs_url})", "reauth_account_mismatch": "Spotify konto mida autenditi ei vasta kontole mis vajas uuesti autentimist." }, diff --git a/homeassistant/components/spotify/translations/it.json b/homeassistant/components/spotify/translations/it.json index 6911d38be0020b..595e13865e1296 100644 --- a/homeassistant/components/spotify/translations/it.json +++ b/homeassistant/components/spotify/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione", + "authorize_url_timeout": "Tempo scaduto nella generazione dell'URL di autorizzazione.", "missing_configuration": "L'integrazione di Spotify non \u00e8 configurata. Si prega di seguire la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", "reauth_account_mismatch": "L'account Spotify con cui si \u00e8 autenticati non corrisponde all'account necessario per la ri-autenticazione." diff --git a/homeassistant/components/spotify/translations/pl.json b/homeassistant/components/spotify/translations/pl.json index f9e6f42921458b..52028d4d368522 100644 --- a/homeassistant/components/spotify/translations/pl.json +++ b/homeassistant/components/spotify/translations/pl.json @@ -21,7 +21,7 @@ }, "system_health": { "info": { - "api_endpoint_reachable": "Dost\u0119pno\u015b\u0107 punktu ko\u0144cowego API Spotify" + "api_endpoint_reachable": "Punkt ko\u0144cowy Spotify API osi\u0105galny" } } } \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/en.json b/homeassistant/components/tellduslive/translations/en.json index 7b14df15fa8fe5..b1b9cd9ab10086 100644 --- a/homeassistant/components/tellduslive/translations/en.json +++ b/homeassistant/components/tellduslive/translations/en.json @@ -5,7 +5,7 @@ "authorize_url_fail": "Unknown error generating an authorize url.", "authorize_url_timeout": "Timeout generating authorize URL.", "unknown": "Unexpected error", - "unknown_authorize_url_generation": "Unknown error generating an authorize url." + "unknown_authorize_url_generation": "Unknown error generating an authorize URL." }, "error": { "invalid_auth": "Invalid authentication" diff --git a/homeassistant/components/toon/translations/en.json b/homeassistant/components/toon/translations/en.json index c64913cfb6c1c3..3351c16d8d8d63 100644 --- a/homeassistant/components/toon/translations/en.json +++ b/homeassistant/components/toon/translations/en.json @@ -7,7 +7,7 @@ "missing_configuration": "The component is not configured. Please follow the documentation.", "no_agreements": "This account has no Toon displays.", "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", - "unknown_authorize_url_generation": "Unknown error generating an authorize url." + "unknown_authorize_url_generation": "Unknown error generating an authorize URL." }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/et.json b/homeassistant/components/toon/translations/et.json index 7b70eae433e8a7..f93fb684b25726 100644 --- a/homeassistant/components/toon/translations/et.json +++ b/homeassistant/components/toon/translations/et.json @@ -18,7 +18,7 @@ "title": "Vali oma leping" }, "pick_implementation": { - "title": "Valige oma rentnik, kellega autentida" + "title": "Vali oma rentnik, kellega autentida" } } } diff --git a/homeassistant/components/traccar/translations/ca.json b/homeassistant/components/traccar/translations/ca.json index 1b00aab4b3e86a..62c15e0ca20096 100644 --- a/homeassistant/components/traccar/translations/ca.json +++ b/homeassistant/components/traccar/translations/ca.json @@ -5,7 +5,7 @@ "webhook_not_internet_accessible": "La teva inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per poder rebre missatges webhook." }, "create_entry": { - "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Traccar.\n\nUtilitza el seg\u00fcent enlla\u00e7: `{webhook_url}`\n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." + "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Traccar.\n\nUtilitza el seg\u00fcent URL: `{webhook_url}`\n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." }, "step": { "user": { diff --git a/homeassistant/components/traccar/translations/en.json b/homeassistant/components/traccar/translations/en.json index 2231d53ceb8e6f..c6d7f0f189245e 100644 --- a/homeassistant/components/traccar/translations/en.json +++ b/homeassistant/components/traccar/translations/en.json @@ -5,7 +5,7 @@ "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages." }, "create_entry": { - "default": "To send events to Home Assistant, you will need to setup the webhook feature in Traccar.\n\nUse the following url: `{webhook_url}`\n\nSee [the documentation]({docs_url}) for further details." + "default": "To send events to Home Assistant, you will need to setup the webhook feature in Traccar.\n\nUse the following URL: `{webhook_url}`\n\nSee [the documentation]({docs_url}) for further details." }, "step": { "user": { diff --git a/homeassistant/components/traccar/translations/it.json b/homeassistant/components/traccar/translations/it.json index 6d4de5bb13d02c..8c95b3cd022f45 100644 --- a/homeassistant/components/traccar/translations/it.json +++ b/homeassistant/components/traccar/translations/it.json @@ -5,7 +5,7 @@ "webhook_not_internet_accessible": "L'istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi webhook." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, \u00e8 necessario configurare la funzionalit\u00e0 webhook in Traccar.\n\nUtilizzare l'URL seguente: `{webhook_url}`\n\nPer ulteriori dettagli, vedere [la documentazione]({docs_url}) ." + "default": "Per inviare eventi a Home Assistant, \u00e8 necessario impostare la funzione webhook in Traccar.\n\nUsa il seguente URL: `{webhook_url}`.\n\nVedi [la documentazione]({docs_url}) per ulteriori dettagli." }, "step": { "user": { diff --git a/homeassistant/components/traccar/translations/zh-Hant.json b/homeassistant/components/traccar/translations/zh-Hant.json index 2204e7c3323c6e..ee7c75d84086ff 100644 --- a/homeassistant/components/traccar/translations/zh-Hant.json +++ b/homeassistant/components/traccar/translations/zh-Hant.json @@ -5,7 +5,7 @@ "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": { - "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Traccar \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u4f7f\u7528 url: `{webhook_url}`\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Traccar \u5167\u8a2d\u5b9a Webhook \u529f\u80fd\u3002\n\n\u8acb\u4f7f\u7528 URL\uff1a`{webhook_url}`\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6] ({docs_url}) \u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" }, "step": { "user": { diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index a24c1dbe265221..dc57f064d57a71 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -53,7 +53,7 @@ "discovery_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania nowych urz\u0105dze\u0144 (w sekundach)", "list_devices": "Wybierz urz\u0105dzenia do skonfigurowania lub pozostaw puste, aby zapisa\u0107 konfiguracj\u0119", "query_device": "Wybierz urz\u0105dzenie, kt\u00f3re b\u0119dzie u\u017cywa\u0107 metody odpytywania w celu szybszej aktualizacji statusu", - "query_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania odpytywanego urz\u0105dzenia (w sekundach)" + "query_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania odpytywanego urz\u0105dzenia w sekundach" }, "description": "Nie ustawiaj zbyt niskich warto\u015bci skanowania, bo zako\u0144cz\u0105 si\u0119 niepowodzeniem, generuj\u0105c komunikat o b\u0142\u0119dzie w logu", "title": "Konfiguracja opcji Tuya" diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index 6c8c74e726af8d..719120eeb5ee18 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -70,7 +70,7 @@ "allow_uptime_sensors": "Sensory czasu pracy dla klient\u00f3w sieciowych" }, "description": "Konfiguracja sensora statystyk", - "title": "Opcje UniFi" + "title": "Opcje UniFi 3/3" } } } diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 3b69bf0ee33f11..df2150c98ecea8 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "configuration_updated": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0430.", "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": { diff --git a/homeassistant/components/vera/translations/ca.json b/homeassistant/components/vera/translations/ca.json index 63ec236ef89a6b..13a889cb7db45c 100644 --- a/homeassistant/components/vera/translations/ca.json +++ b/homeassistant/components/vera/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "No s'ha pogut connectar amb el controlador amb l'URL {base_url}" + "cannot_connect": "No s'ha pogut connectar amb el controlador amb URL {base_url}" }, "step": { "user": { @@ -10,7 +10,7 @@ "lights": "Identificadors de dispositiu dels commutadors Vera a tractar com a llums a Home Assistant.", "vera_controller_url": "URL del controlador" }, - "description": "Proporciona un URL pel controlador Vera. Hauria de quedar aix\u00ed: http://192.168.1.161:3480.", + "description": "Proporciona un URL pel controlador Vera. Hauria de ser similar al seg\u00fcent: http://192.168.1.161:3480.", "title": "Configuraci\u00f3 del controlador Vera" } } diff --git a/homeassistant/components/vera/translations/en.json b/homeassistant/components/vera/translations/en.json index 5503d6b1034fa0..94f490d71ee1e2 100644 --- a/homeassistant/components/vera/translations/en.json +++ b/homeassistant/components/vera/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Could not connect to controller with url {base_url}" + "cannot_connect": "Could not connect to controller with URL {base_url}" }, "step": { "user": { @@ -10,7 +10,7 @@ "lights": "Vera switch device ids to treat as lights in Home Assistant.", "vera_controller_url": "Controller URL" }, - "description": "Provide a Vera controller url below. It should look like this: http://192.168.1.161:3480.", + "description": "Provide a Vera controller URL below. It should look like this: http://192.168.1.161:3480.", "title": "Setup Vera controller" } } diff --git a/homeassistant/components/vera/translations/it.json b/homeassistant/components/vera/translations/it.json index e144bf251cd6c1..3ec026beaac5b7 100644 --- a/homeassistant/components/vera/translations/it.json +++ b/homeassistant/components/vera/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Impossibile connettersi al controllore con l'url {base_url}" + "cannot_connect": "Impossibile connettersi al controller con l'URL {base_url}" }, "step": { "user": { @@ -10,7 +10,7 @@ "lights": "Gli ID dei dispositivi switch Vera da trattare come luci in Home Assistant.", "vera_controller_url": "URL del controller" }, - "description": "Fornire un url di controllo Vera di seguito. Dovrebbe avere questo aspetto: http://192.168.1.161:3480.", + "description": "Fornisci un URL controller Vera di seguito. Dovrebbe assomigliare a questo: http://192.168.1.161:3480.", "title": "Configurazione controller Vera" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/en.json b/homeassistant/components/xiaomi_aqara/translations/en.json index 075c8d4a194bcc..d51687a079056c 100644 --- a/homeassistant/components/xiaomi_aqara/translations/en.json +++ b/homeassistant/components/xiaomi_aqara/translations/en.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP Address" }, - "description": "Run the setup again if you want to connect aditional gateways", + "description": "Run the setup again if you want to connect additional gateways", "title": "Select the Xiaomi Aqara Gateway that you wish to connect" }, "settings": { @@ -35,7 +35,7 @@ "interface": "The network interface to use", "mac": "Mac Address (optional)" }, - "description": "Connect to your Xiaomi Aqara Gateway, if the IP and mac addresses are left empty, auto-discovery is used", + "description": "Connect to your Xiaomi Aqara Gateway, if the IP and MAC addresses are left empty, auto-discovery is used", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/it.json b/homeassistant/components/xiaomi_aqara/translations/it.json index 3299fa092f8293..275729e4e81a1d 100644 --- a/homeassistant/components/xiaomi_aqara/translations/it.json +++ b/homeassistant/components/xiaomi_aqara/translations/it.json @@ -18,7 +18,7 @@ "data": { "select_ip": "Indirizzo IP" }, - "description": "Eseguire nuovamente l'installazione se si desidera connettere gateway adizionali", + "description": "Esegui di nuovo la configurazione se desideri connettere gateway aggiuntivi", "title": "Selezionare il Gateway Xiaomi Aqara che si desidera collegare" }, "settings": { @@ -35,7 +35,7 @@ "interface": "L'interfaccia di rete da utilizzare", "mac": "Indirizzo Mac (opzionale)" }, - "description": "Connettiti al tuo Xiaomi Aqara Gateway, se gli indirizzi IP e mac sono lasciati vuoti, verr\u00e0 utilizzato il rilevamento automatico", + "description": "Connettiti al tuo Xiaomi Aqara Gateway, se gli indirizzi IP e MAC sono lasciati vuoti, verr\u00e0 utilizzato il rilevamento automatico", "title": "Xiaomi Aqara Gateway" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json index 582aea354c6f01..5d2d097e832d53 100644 --- a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP \u4f4d\u5740" }, - "description": "\u5982\u679c\u9084\u6709\u5176\u4ed6\u7db2\u95dc\u9700\u8981\u9023\u7dda\uff0c\u8acb\u518d\u57f7\u884c\u4e00\u6b21\u8a2d\u5b9a", + "description": "\u5982\u679c\u9084\u9700\u8981\u9023\u7dda\u81f3\u5176\u4ed6\u7db2\u95dc\uff0c\u8acb\u518d\u57f7\u884c\u4e00\u6b21\u8a2d\u5b9a", "title": "\u9078\u64c7\u6240\u8981\u9023\u7dda\u7684\u5c0f\u7c73 Aqara \u7db2\u95dc" }, "settings": { diff --git a/homeassistant/components/zoneminder/translations/et.json b/homeassistant/components/zoneminder/translations/et.json index 087158a450d13c..c8ccef3a44b835 100644 --- a/homeassistant/components/zoneminder/translations/et.json +++ b/homeassistant/components/zoneminder/translations/et.json @@ -23,7 +23,7 @@ "password": "Salas\u00f5na", "path": "ZM aadress", "path_zms": "ZMS-i aadress", - "ssl": "Kasutage ZoneMinderiga \u00fchenduse loomiseks SSL-i", + "ssl": "Kasuta ZoneMinderiga \u00fchenduse loomiseks SSL-i", "username": "Kasutajanimi", "verify_ssl": "Kontrolli SSL sertifikaati" }, diff --git a/homeassistant/components/zwave/translations/it.json b/homeassistant/components/zwave/translations/it.json index 0534d54f32f493..d3522cf0889551 100644 --- a/homeassistant/components/zwave/translations/it.json +++ b/homeassistant/components/zwave/translations/it.json @@ -13,7 +13,7 @@ "network_key": "Chiave di rete (lascia vuoto per generare automaticamente)", "usb_path": "Percorso del dispositivo USB" }, - "description": "Vai su https://www.home-assistant.io/docs/z-wave/installation/ per le informazioni sulle variabili di configurazione", + "description": "Questa integrazione non viene pi\u00f9 mantenuta. Per le nuove installazioni, usa invece Z-Wave JS. \n\nVedere https://www.home-assistant.io/docs/z-wave/installation/ per informazioni sulle variabili di configurazione", "title": "Configura Z-Wave" } } diff --git a/homeassistant/components/zwave/translations/pl.json b/homeassistant/components/zwave/translations/pl.json index 90ff1a37894c52..0a4b6a4828c78e 100644 --- a/homeassistant/components/zwave/translations/pl.json +++ b/homeassistant/components/zwave/translations/pl.json @@ -13,7 +13,7 @@ "network_key": "Klucz sieciowy (pozostaw pusty, by generowa\u0107 automatycznie)", "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" }, - "description": "Przejd\u017a na https://www.home-assistant.io/docs/z-wave/installation/, aby uzyska\u0107 informacje na temat zmiennych konfiguracyjnych", + "description": "Ta integracja nie jest ju\u017c wspierana. Dla nowych instalacji, u\u017cyj Z-Wave JS.\n\nPrzejd\u017a na https://www.home-assistant.io/docs/z-wave/installation/, aby uzyska\u0107 informacje na temat zmiennych konfiguracyjnych", "title": "Konfiguracja Z-Wave" } } diff --git a/homeassistant/components/zwave/translations/zh-Hant.json b/homeassistant/components/zwave/translations/zh-Hant.json index f5c07a9efc9326..545da7b2ee74f2 100644 --- a/homeassistant/components/zwave/translations/zh-Hant.json +++ b/homeassistant/components/zwave/translations/zh-Hant.json @@ -13,7 +13,7 @@ "network_key": "\u7db2\u8def\u5bc6\u9470\uff08\u4fdd\u7559\u7a7a\u767d\u5c07\u6703\u81ea\u52d5\u7522\u751f\uff09", "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" }, - "description": "\u95dc\u65bc\u8a2d\u5b9a\u8b8a\u6578\u8cc7\u8a0a\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/docs/z-wave/installation/", + "description": "\u6b64\u6574\u5408\u5df2\u7d93\u4e0d\u518d\u9032\u884c\u7dad\u8b77\uff0c\u8acb\u4f7f\u7528 Z-Wave JS \u53d6\u4ee3\u70ba\u65b0\u5b89\u88dd\u65b9\u5f0f\u3002\n\n\u8acb\u53c3\u95b1 https://www.home-assistant.io/docs/z-wave/installation/ \u4ee5\n\u7372\u5f97\u8a2d\u5b9a\u8b8a\u6578\u8cc7\u8a0a", "title": "\u8a2d\u5b9a Z-Wave" } } From d1b7d25a5db8456958fe07eca836f93ee7a120ba Mon Sep 17 00:00:00 2001 From: obelix05 Date: Fri, 5 Feb 2021 02:31:47 +0100 Subject: [PATCH 0208/1818] Prevent fritzbox callmonitor phonebook_id 0 from being ignored (#45990) --- homeassistant/components/fritzbox_callmonitor/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fritzbox_callmonitor/base.py b/homeassistant/components/fritzbox_callmonitor/base.py index 79f82de95b7959..ec62e1968550c4 100644 --- a/homeassistant/components/fritzbox_callmonitor/base.py +++ b/homeassistant/components/fritzbox_callmonitor/base.py @@ -41,7 +41,7 @@ def init_phonebook(self): @Throttle(MIN_TIME_PHONEBOOK_UPDATE) def update_phonebook(self): """Update the phone book dictionary.""" - if not self.phonebook_id: + if self.phonebook_id is None: return self.phonebook_dict = self.fph.get_all_names(self.phonebook_id) From 374aa3aee1c7ee82a5d73b40b6f23439ad78e085 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Feb 2021 15:39:07 -1000 Subject: [PATCH 0209/1818] Fix homekit options not being prefilled (#45926) * Fix homekit options not being prefilled When changing homekit options, the existing ones were not being prefilled on the form. * hide camera screen if no cameras --- .../components/homekit/config_flow.py | 38 ++++++----- tests/components/homekit/test_config_flow.py | 65 ++++++++++++++++--- 2 files changed, 78 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index a20d9a5b843c54..f16d14e9330e98 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -91,7 +91,8 @@ DOMAINS_PREFER_ACCESSORY_MODE = ["camera", "media_player"] -CAMERA_ENTITY_PREFIX = "camera." +CAMERA_DOMAIN = "camera" +CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}." _EMPTY_ENTITY_FILTER = { CONF_INCLUDE_DOMAINS: [], @@ -356,15 +357,26 @@ async def async_step_include_exclude(self, user_input=None): if domain not in domains_with_entities_selected ] - for entity_id in list(self.included_cameras): - if entity_id not in entities: - self.included_cameras.remove(entity_id) + self.included_cameras = { + entity_id + for entity_id in entities + if entity_id.startswith(CAMERA_ENTITY_PREFIX) + } else: entity_filter[CONF_INCLUDE_DOMAINS] = self.hk_options[CONF_DOMAINS] entity_filter[CONF_EXCLUDE_ENTITIES] = entities - for entity_id in entities: - if entity_id in self.included_cameras: - self.included_cameras.remove(entity_id) + if CAMERA_DOMAIN in entity_filter[CONF_INCLUDE_DOMAINS]: + camera_entities = _async_get_matching_entities( + self.hass, + domains=[CAMERA_DOMAIN], + ) + self.included_cameras = { + entity_id + for entity_id in camera_entities + if entity_id not in entities + } + else: + self.included_cameras = set() self.hk_options[CONF_FILTER] = entity_filter @@ -378,11 +390,6 @@ async def async_step_include_exclude(self, user_input=None): self.hass, domains=self.hk_options[CONF_DOMAINS], ) - self.included_cameras = { - entity_id - for entity_id in all_supported_entities - if entity_id.startswith(CAMERA_ENTITY_PREFIX) - } data_schema = {} entities = entity_filter.get(CONF_INCLUDE_ENTITIES, []) @@ -416,10 +423,9 @@ async def async_step_init(self, user_input=None): self.hk_options.update(user_input) return await self.async_step_include_exclude() - hk_options = dict(self.config_entry.options) - entity_filter = hk_options.get(CONF_FILTER, {}) - - homekit_mode = hk_options.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE) + self.hk_options = dict(self.config_entry.options) + entity_filter = self.hk_options.get(CONF_FILTER, {}) + homekit_mode = self.hk_options.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE) domains = entity_filter.get(CONF_INCLUDE_DOMAINS, []) include_entities = entity_filter.get(CONF_INCLUDE_ENTITIES) if include_entities: diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index 0e4114d566d3d0..34c7ab2ecc0440 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -492,6 +492,18 @@ async def test_options_flow_include_mode_with_cameras(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" + assert result["data_schema"]({}) == { + "domains": ["fan", "vacuum", "climate", "camera"], + "mode": "bridge", + } + schema = result["data_schema"].schema + assert _get_schema_default(schema, "domains") == [ + "fan", + "vacuum", + "climate", + "camera", + ] + assert _get_schema_default(schema, "mode") == "bridge" result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -500,6 +512,16 @@ async def test_options_flow_include_mode_with_cameras(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "include_exclude" + assert result["data_schema"]({}) == { + "entities": ["camera.native_h264", "camera.transcode_h264"], + "include_exclude_mode": "include", + } + schema = result["data_schema"].schema + assert _get_schema_default(schema, "entities") == [ + "camera.native_h264", + "camera.transcode_h264", + ] + assert _get_schema_default(schema, "include_exclude_mode") == "include" result2 = await hass.config_entries.options.async_configure( result["flow_id"], @@ -510,6 +532,9 @@ async def test_options_flow_include_mode_with_cameras(hass): ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["step_id"] == "cameras" + assert result2["data_schema"]({}) == {"camera_copy": ["camera.native_h264"]} + schema = result2["data_schema"].schema + assert _get_schema_default(schema, "camera_copy") == ["camera.native_h264"] result3 = await hass.config_entries.options.async_configure( result2["flow_id"], @@ -519,14 +544,14 @@ async def test_options_flow_include_mode_with_cameras(hass): assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert config_entry.options == { "auto_start": True, - "mode": "bridge", + "entity_config": {"camera.native_h264": {}}, "filter": { "exclude_domains": [], "exclude_entities": ["climate.old", "camera.excluded"], "include_domains": ["fan", "vacuum", "climate", "camera"], "include_entities": [], }, - "entity_config": {}, + "mode": "bridge", } @@ -586,6 +611,17 @@ async def test_options_flow_include_mode_basic_accessory(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" + assert result["data_schema"]({}) == { + "domains": [ + "fan", + "humidifier", + "vacuum", + "media_player", + "climate", + "alarm_control_panel", + ], + "mode": "bridge", + } result2 = await hass.config_entries.options.async_configure( result["flow_id"], @@ -594,6 +630,7 @@ async def test_options_flow_include_mode_basic_accessory(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["step_id"] == "include_exclude" + assert _get_schema_default(result2["data_schema"].schema, "entities") == [] result3 = await hass.config_entries.options.async_configure( result2["flow_id"], @@ -612,7 +649,7 @@ async def test_options_flow_include_mode_basic_accessory(hass): } -async def test_converting_bridge_to_accessory_mode(hass): +async def test_converting_bridge_to_accessory_mode(hass, hk_driver): """Test we can convert a bridge to accessory mode.""" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -639,12 +676,12 @@ async def test_converting_bridge_to_accessory_mode(hass): assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM assert result3["step_id"] == "pairing" + # We need to actually setup the config entry or the data + # will not get migrated to options with patch( - "homeassistant.components.homekit.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.homekit.async_setup_entry", + "homeassistant.components.homekit.HomeKit.async_start", return_value=True, - ) as mock_setup_entry: + ) as mock_async_start: result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], {}, @@ -665,8 +702,7 @@ async def test_converting_bridge_to_accessory_mode(hass): "name": bridge_name, "port": 12345, } - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_async_start.mock_calls) == 1 config_entry = result4["result"] @@ -681,6 +717,9 @@ async def test_converting_bridge_to_accessory_mode(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" + schema = result["data_schema"].schema + assert _get_schema_default(schema, "mode") == "bridge" + assert _get_schema_default(schema, "domains") == ["light"] result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -714,3 +753,11 @@ async def test_converting_bridge_to_accessory_mode(hass): "include_entities": ["camera.tv"], }, } + + +def _get_schema_default(schema, key_name): + """Iterate schema to find a key.""" + for schema_key in schema: + if schema_key == key_name: + return schema_key.default() + raise KeyError(f"{key_name} not found in schema") From bcefbe2dcaa38c86867a6b63db596c7c9b5daed0 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 21:41:43 -0500 Subject: [PATCH 0210/1818] Use core constants for automation (#46016) --- homeassistant/components/automation/__init__.py | 2 +- homeassistant/components/automation/config.py | 3 +-- homeassistant/components/automation/const.py | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 201eeb5c456505..295450e8211b67 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -10,6 +10,7 @@ ATTR_ENTITY_ID, ATTR_NAME, CONF_ALIAS, + CONF_CONDITION, CONF_DEVICE_ID, CONF_ENTITY_ID, CONF_ID, @@ -57,7 +58,6 @@ from .config import async_validate_config_item from .const import ( CONF_ACTION, - CONF_CONDITION, CONF_INITIAL_STATE, CONF_TRIGGER, DEFAULT_INITIAL_STATE, diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 9c26f3552aa9c3..89d5e18474867d 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -8,7 +8,7 @@ InvalidDeviceAutomationConfig, ) from homeassistant.config import async_log_exception, config_without_domain -from homeassistant.const import CONF_ALIAS, CONF_ID, CONF_VARIABLES +from homeassistant.const import CONF_ALIAS, CONF_CONDITION, CONF_ID, CONF_VARIABLES from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, config_validation as cv, script from homeassistant.helpers.condition import async_validate_condition_config @@ -17,7 +17,6 @@ from .const import ( CONF_ACTION, - CONF_CONDITION, CONF_DESCRIPTION, CONF_HIDE_ENTITY, CONF_INITIAL_STATE, diff --git a/homeassistant/components/automation/const.py b/homeassistant/components/automation/const.py index c8db3aa01e56fe..ffb89ba0907bfa 100644 --- a/homeassistant/components/automation/const.py +++ b/homeassistant/components/automation/const.py @@ -1,7 +1,6 @@ """Constants for the automation integration.""" import logging -CONF_CONDITION = "condition" CONF_ACTION = "action" CONF_TRIGGER = "trigger" DOMAIN = "automation" From 6e9aa254d55c1e4ca0f84fda8837e73b13123d53 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 4 Feb 2021 21:43:04 -0500 Subject: [PATCH 0211/1818] Use core constants for delijn (#46027) --- homeassistant/components/delijn/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 8c73fecf26ee6e..0058816d3185d9 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, DEVICE_CLASS_TIMESTAMP +from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -17,7 +17,6 @@ CONF_NEXT_DEPARTURE = "next_departure" CONF_STOP_ID = "stop_id" -CONF_API_KEY = "api_key" CONF_NUMBER_OF_DEPARTURES = "number_of_departures" DEFAULT_NAME = "De Lijn" From 64ab495318c266cd1bdff758ccb25fdc1e495461 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Feb 2021 10:05:04 +0100 Subject: [PATCH 0212/1818] Revert aioshelly (#46038) --- 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 923bcdced34e02..b3511d4f6b0f96 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==0.5.3"], + "requirements": ["aioshelly==0.5.1.beta0"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/requirements_all.txt b/requirements_all.txt index 086c444b3d81ae..49e21a56a053ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,7 +221,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.3 +aioshelly==0.5.1.beta0 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c2ae196767cc06..21fdf1a48b39e9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -137,7 +137,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.3 +aioshelly==0.5.1.beta0 # homeassistant.components.switcher_kis aioswitcher==1.2.1 From 7c5af4e7e6afa4a5b45d66e5362281e73e0f9bd0 Mon Sep 17 00:00:00 2001 From: obelix05 Date: Fri, 5 Feb 2021 02:31:47 +0100 Subject: [PATCH 0213/1818] Prevent fritzbox callmonitor phonebook_id 0 from being ignored (#45990) --- homeassistant/components/fritzbox_callmonitor/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fritzbox_callmonitor/base.py b/homeassistant/components/fritzbox_callmonitor/base.py index 79f82de95b7959..ec62e1968550c4 100644 --- a/homeassistant/components/fritzbox_callmonitor/base.py +++ b/homeassistant/components/fritzbox_callmonitor/base.py @@ -41,7 +41,7 @@ def init_phonebook(self): @Throttle(MIN_TIME_PHONEBOOK_UPDATE) def update_phonebook(self): """Update the phone book dictionary.""" - if not self.phonebook_id: + if self.phonebook_id is None: return self.phonebook_dict = self.fph.get_all_names(self.phonebook_id) From 8e8646b93acb26ce787e88248c3c83479b429be6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 4 Feb 2021 23:32:56 +0100 Subject: [PATCH 0214/1818] Upgrade holidays to 0.10.5.2 (#46013) --- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 4fb25c766ccc69..3351d796e93ef2 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.10.4"], + "requirements": ["holidays==0.10.5.2"], "codeowners": ["@fabaff"], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index 49e21a56a053ae..56b9f7a331c0b0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -762,7 +762,7 @@ hlk-sw16==0.0.9 hole==0.5.1 # homeassistant.components.workday -holidays==0.10.4 +holidays==0.10.5.2 # homeassistant.components.frontend home-assistant-frontend==20210127.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 21fdf1a48b39e9..c0a44a96f0a969 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -399,7 +399,7 @@ hlk-sw16==0.0.9 hole==0.5.1 # homeassistant.components.workday -holidays==0.10.4 +holidays==0.10.5.2 # homeassistant.components.frontend home-assistant-frontend==20210127.7 From 2b17ba1dc49343f904b74eb295c14783947ac879 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 5 Feb 2021 04:29:59 -0500 Subject: [PATCH 0215/1818] User core constants for deutsche_bahn (#46028) --- homeassistant/components/deutsche_bahn/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index 01752f0373f1de..b3e4cd432ac0a7 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -5,13 +5,13 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_OFFSET import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_DESTINATION = "to" CONF_START = "from" -CONF_OFFSET = "offset" DEFAULT_OFFSET = timedelta(minutes=0) CONF_ONLY_DIRECT = "only_direct" DEFAULT_ONLY_DIRECT = False @@ -87,7 +87,6 @@ class SchieneData: def __init__(self, start, goal, offset, only_direct): """Initialize the sensor.""" - self.start = start self.goal = goal self.offset = offset From 92886cafe94735819591f73838da410b9108edec Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Fri, 5 Feb 2021 02:48:47 -0700 Subject: [PATCH 0216/1818] Fix zwave_js cover control for Up/Down and Open/Close (#45965) * Fix issue with control of cover when the target value is Up/Down instead of Open/Close * Adjust open/close/stop cover control to account for no Open/Up or Close/Down targets * Revert back to using values of 0/99 to close/open a cover since it is supported by all covers * Replace RELEASE_BUTTON with False and remove unused PRESS_BUTTON in zwave_js cover --- homeassistant/components/zwave_js/cover.py | 20 ++++++++--------- tests/components/zwave_js/test_cover.py | 26 +++++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 5f473f809575bf..b86cbeba944df7 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -21,8 +21,6 @@ LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE -PRESS_BUTTON = True -RELEASE_BUTTON = False async def async_setup_entry( @@ -79,17 +77,19 @@ async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - target_value = self.get_zwave_value("Open") - await self.info.node.async_set_value(target_value, PRESS_BUTTON) + target_value = self.get_zwave_value("targetValue") + await self.info.node.async_set_value(target_value, 99) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - target_value = self.get_zwave_value("Close") - await self.info.node.async_set_value(target_value, PRESS_BUTTON) + target_value = self.get_zwave_value("targetValue") + await self.info.node.async_set_value(target_value, 0) async def async_stop_cover(self, **kwargs: Any) -> None: """Stop cover.""" - target_value = self.get_zwave_value("Open") - await self.info.node.async_set_value(target_value, RELEASE_BUTTON) - target_value = self.get_zwave_value("Close") - await self.info.node.async_set_value(target_value, RELEASE_BUTTON) + target_value = self.get_zwave_value("Open") or self.get_zwave_value("Up") + if target_value: + await self.info.node.async_set_value(target_value, False) + target_value = self.get_zwave_value("Close") or self.get_zwave_value("Down") + if target_value: + await self.info.node.async_set_value(target_value, False) diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index f014245a5f8a2d..52e0a444ec9c9e 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -95,14 +95,16 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "Open", - "propertyName": "Open", + "property": "targetValue", + "propertyName": "targetValue", "metadata": { - "type": "boolean", + "label": "Target value", + "max": 99, + "min": 0, + "type": "number", "readable": True, "writeable": True, - "label": "Perform a level change (Open)", - "ccSpecific": {"switchType": 3}, + "label": "Target value", }, } assert args["value"] @@ -194,17 +196,19 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "Close", - "propertyName": "Close", + "property": "targetValue", + "propertyName": "targetValue", "metadata": { - "type": "boolean", + "label": "Target value", + "max": 99, + "min": 0, + "type": "number", "readable": True, "writeable": True, - "label": "Perform a level change (Close)", - "ccSpecific": {"switchType": 3}, + "label": "Target value", }, } - assert args["value"] + assert args["value"] == 0 client.async_send_command.reset_mock() From 6404f91d0090404c8169cf83c77bbd935a60236e Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 5 Feb 2021 02:51:55 -0700 Subject: [PATCH 0217/1818] Standardize AirVisual helper method in config flow (#45999) * Standardize AirVisual helper method in config flow * Code review --- .../components/airvisual/config_flow.py | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index 266f7b7c2c259d..12dec114349e23 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -84,47 +84,7 @@ def geography_coords_schema(self): } ) - async def _async_set_unique_id(self, unique_id): - """Set the unique ID of the config flow and abort if it already exists.""" - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() - - @staticmethod - @callback - def async_get_options_flow(config_entry): - """Define the config flow to handle options.""" - return AirVisualOptionsFlowHandler(config_entry) - - async def async_step_geography(self, user_input, integration_type): - """Handle the initialization of the integration via the cloud API.""" - self._geo_id = async_get_geography_id(user_input) - await self._async_set_unique_id(self._geo_id) - self._abort_if_unique_id_configured() - return await self.async_step_geography_finish(user_input, integration_type) - - async def async_step_geography_by_coords(self, user_input=None): - """Handle the initialization of the cloud API based on latitude/longitude.""" - if not user_input: - return self.async_show_form( - step_id="geography_by_coords", data_schema=self.geography_coords_schema - ) - - return await self.async_step_geography( - user_input, INTEGRATION_TYPE_GEOGRAPHY_COORDS - ) - - async def async_step_geography_by_name(self, user_input=None): - """Handle the initialization of the cloud API based on city/state/country.""" - if not user_input: - return self.async_show_form( - step_id="geography_by_name", data_schema=GEOGRAPHY_NAME_SCHEMA - ) - - return await self.async_step_geography( - user_input, INTEGRATION_TYPE_GEOGRAPHY_NAME - ) - - async def async_step_geography_finish(self, user_input, integration_type): + async def _async_finish_geography(self, user_input, integration_type): """Validate a Cloud API key.""" websession = aiohttp_client.async_get_clientsession(self.hass) cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession) @@ -183,6 +143,46 @@ async def async_step_geography_finish(self, user_input, integration_type): data={**user_input, CONF_INTEGRATION_TYPE: integration_type}, ) + async def _async_init_geography(self, user_input, integration_type): + """Handle the initialization of the integration via the cloud API.""" + self._geo_id = async_get_geography_id(user_input) + await self._async_set_unique_id(self._geo_id) + self._abort_if_unique_id_configured() + return await self._async_finish_geography(user_input, integration_type) + + async def _async_set_unique_id(self, unique_id): + """Set the unique ID of the config flow and abort if it already exists.""" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Define the config flow to handle options.""" + return AirVisualOptionsFlowHandler(config_entry) + + async def async_step_geography_by_coords(self, user_input=None): + """Handle the initialization of the cloud API based on latitude/longitude.""" + if not user_input: + return self.async_show_form( + step_id="geography_by_coords", data_schema=self.geography_coords_schema + ) + + return await self._async_init_geography( + user_input, INTEGRATION_TYPE_GEOGRAPHY_COORDS + ) + + async def async_step_geography_by_name(self, user_input=None): + """Handle the initialization of the cloud API based on city/state/country.""" + if not user_input: + return self.async_show_form( + step_id="geography_by_name", data_schema=GEOGRAPHY_NAME_SCHEMA + ) + + return await self._async_init_geography( + user_input, INTEGRATION_TYPE_GEOGRAPHY_NAME + ) + async def async_step_node_pro(self, user_input=None): """Handle the initialization of the integration with a Node/Pro.""" if not user_input: @@ -224,7 +224,7 @@ async def async_step_reauth_confirm(self, user_input=None): conf = {CONF_API_KEY: user_input[CONF_API_KEY], **self._entry_data_for_reauth} - return await self.async_step_geography_finish( + return await self._async_finish_geography( conf, self._entry_data_for_reauth[CONF_INTEGRATION_TYPE] ) From 725dcb5cacd24059122eb71c5d73373c4745716a Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 5 Feb 2021 06:17:46 -0500 Subject: [PATCH 0218/1818] Use core constants for doods (#46043) --- homeassistant/components/doods/image_processing.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index f4180ffcffa5d1..fb2b6daecdab84 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -16,7 +16,7 @@ PLATFORM_SCHEMA, ImageProcessingEntity, ) -from homeassistant.const import CONF_TIMEOUT +from homeassistant.const import CONF_COVERS, CONF_TIMEOUT, CONF_URL from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv @@ -29,12 +29,10 @@ ATTR_TOTAL_MATCHES = "total_matches" ATTR_PROCESS_TIME = "process_time" -CONF_URL = "url" CONF_AUTH_KEY = "auth_key" CONF_DETECTOR = "detector" CONF_LABELS = "labels" CONF_AREA = "area" -CONF_COVERS = "covers" CONF_TOP = "top" CONF_BOTTOM = "bottom" CONF_RIGHT = "right" From 2a0c36589f7c57de86336b8d5794812168e11fb4 Mon Sep 17 00:00:00 2001 From: Yuval Aboulafia Date: Fri, 5 Feb 2021 13:41:36 +0200 Subject: [PATCH 0219/1818] Centralize some Airly constants (#45985) --- homeassistant/components/airly/__init__.py | 2 +- homeassistant/components/airly/air_quality.py | 5 ++--- homeassistant/components/airly/const.py | 7 +++++++ homeassistant/components/airly/sensor.py | 10 ++++------ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index 9d6b46f82e5dfa..6a9c23624f0ab7 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -1,4 +1,4 @@ -"""The Airly component.""" +"""The Airly integration.""" import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/airly/air_quality.py b/homeassistant/components/airly/air_quality.py index e43a76b34187fe..4c4c239a84b5c4 100644 --- a/homeassistant/components/airly/air_quality.py +++ b/homeassistant/components/airly/air_quality.py @@ -19,14 +19,13 @@ ATTR_API_PM25, ATTR_API_PM25_LIMIT, ATTR_API_PM25_PERCENT, + ATTRIBUTION, DEFAULT_NAME, DOMAIN, + LABEL_ADVICE, MANUFACTURER, ) -ATTRIBUTION = "Data provided by Airly" - -LABEL_ADVICE = "advice" LABEL_AQI_DESCRIPTION = f"{ATTR_AQI}_description" LABEL_AQI_LEVEL = f"{ATTR_AQI}_level" LABEL_PM_2_5_LIMIT = f"{ATTR_PM_2_5}_limit" diff --git a/homeassistant/components/airly/const.py b/homeassistant/components/airly/const.py index b4711b50dd2309..b8d2270c3c426a 100644 --- a/homeassistant/components/airly/const.py +++ b/homeassistant/components/airly/const.py @@ -1,4 +1,5 @@ """Constants for Airly integration.""" + ATTR_API_ADVICE = "ADVICE" ATTR_API_CAQI = "CAQI" ATTR_API_CAQI_DESCRIPTION = "DESCRIPTION" @@ -13,9 +14,15 @@ ATTR_API_PM25_PERCENT = "PM25_PERCENT" ATTR_API_PRESSURE = "PRESSURE" ATTR_API_TEMPERATURE = "TEMPERATURE" + +ATTR_LABEL = "label" +ATTR_UNIT = "unit" + +ATTRIBUTION = "Data provided by Airly" CONF_USE_NEAREST = "use_nearest" DEFAULT_NAME = "Airly" DOMAIN = "airly" +LABEL_ADVICE = "advice" MANUFACTURER = "Airly sp. z o.o." MAX_REQUESTS_PER_DAY = 100 NO_AIRLY_SENSORS = "There are no Airly sensors in this area yet." diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index 420d11a5963139..789dbbb4657052 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -2,6 +2,7 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, + ATTR_ICON, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONF_NAME, DEVICE_CLASS_HUMIDITY, @@ -18,17 +19,14 @@ ATTR_API_PM1, ATTR_API_PRESSURE, ATTR_API_TEMPERATURE, + ATTR_LABEL, + ATTR_UNIT, + ATTRIBUTION, DEFAULT_NAME, DOMAIN, MANUFACTURER, ) -ATTRIBUTION = "Data provided by Airly" - -ATTR_ICON = "icon" -ATTR_LABEL = "label" -ATTR_UNIT = "unit" - PARALLEL_UPDATES = 1 SENSOR_TYPES = { From e01ca40d5603c0d671a9c5cce58d2fa2ea12b7ff Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Fri, 5 Feb 2021 12:20:15 +0000 Subject: [PATCH 0220/1818] Force Vera refresh after starting subscription (#46001) * Force vera refresh after starting subscription. * Request refresh on device load. * Update init test. --- homeassistant/components/vera/binary_sensor.py | 3 ++- homeassistant/components/vera/climate.py | 3 ++- homeassistant/components/vera/cover.py | 3 ++- homeassistant/components/vera/light.py | 3 ++- homeassistant/components/vera/lock.py | 3 ++- homeassistant/components/vera/scene.py | 2 +- homeassistant/components/vera/sensor.py | 3 ++- homeassistant/components/vera/switch.py | 3 ++- tests/components/vera/test_init.py | 5 +++++ 9 files changed, 20 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index a84764209b2003..7932fa14f4c3ec 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -27,7 +27,8 @@ async def async_setup_entry( [ VeraBinarySensor(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 70694af012f229..9abfc485268d1f 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -44,7 +44,8 @@ async def async_setup_entry( [ VeraThermostat(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 69e412bdadeac7..43f68fba786884 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -28,7 +28,8 @@ async def async_setup_entry( [ VeraCover(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 7daaf095a5ce87..c52627d340f663 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -32,7 +32,8 @@ async def async_setup_entry( [ VeraLight(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index 36a5f4cf2f3e2a..b77f17d3b0ab18 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -31,7 +31,8 @@ async def async_setup_entry( [ VeraLock(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index a031aadb66cd79..2274b67f6835f6 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -21,7 +21,7 @@ async def async_setup_entry( """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) async_add_entities( - [VeraScene(device, controller_data) for device in controller_data.scenes] + [VeraScene(device, controller_data) for device in controller_data.scenes], True ) diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index e39b752bbf3539..ea7dbf0ae30d3a 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -28,7 +28,8 @@ async def async_setup_entry( [ VeraSensor(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index d0cbeba669c765..5dfeba6f5b2bd6 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -28,7 +28,8 @@ async def async_setup_entry( [ VeraSwitch(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) - ] + ], + True, ) diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index b1d6010336a59b..33b6843d7e528a 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -206,6 +206,7 @@ async def test_exclude_and_light_ids( vera_device3.name = "dev3" vera_device3.category = pv.CATEGORY_SWITCH vera_device3.is_switched_on = MagicMock(return_value=False) + entity_id3 = "switch.dev3_3" vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch @@ -214,6 +215,10 @@ async def test_exclude_and_light_ids( vera_device4.name = "dev4" vera_device4.category = pv.CATEGORY_SWITCH vera_device4.is_switched_on = MagicMock(return_value=False) + vera_device4.get_brightness = MagicMock(return_value=0) + vera_device4.get_color = MagicMock(return_value=[0, 0, 0]) + vera_device4.is_dimmable = True + entity_id4 = "light.dev4_4" component_data = await vera_component_factory.configure_component( From 9a570d7c3215d1196a8a80f4148d4c19bbdde8ec Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 5 Feb 2021 08:02:28 -0500 Subject: [PATCH 0221/1818] Use core constants for bmw_connected_drive (#46042) --- homeassistant/components/bmw_connected_drive/__init__.py | 2 +- homeassistant/components/bmw_connected_drive/config_flow.py | 4 ++-- homeassistant/components/bmw_connected_drive/const.py | 1 - tests/components/bmw_connected_drive/test_config_flow.py | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index e9f6a0d7f6f68f..ac25636a06660e 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -12,6 +12,7 @@ ATTR_ATTRIBUTION, CONF_NAME, CONF_PASSWORD, + CONF_REGION, CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback @@ -28,7 +29,6 @@ CONF_ACCOUNT, CONF_ALLOWED_REGIONS, CONF_READ_ONLY, - CONF_REGION, CONF_USE_LOCATION, DATA_ENTRIES, DATA_HASS_CONFIG, diff --git a/homeassistant/components/bmw_connected_drive/config_flow.py b/homeassistant/components/bmw_connected_drive/config_flow.py index a6081d5ccc1cca..8315a1322e20f2 100644 --- a/homeassistant/components/bmw_connected_drive/config_flow.py +++ b/homeassistant/components/bmw_connected_drive/config_flow.py @@ -6,11 +6,11 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_PASSWORD, CONF_SOURCE, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_SOURCE, CONF_USERNAME from homeassistant.core import callback from . import DOMAIN # pylint: disable=unused-import -from .const import CONF_ALLOWED_REGIONS, CONF_READ_ONLY, CONF_REGION, CONF_USE_LOCATION +from .const import CONF_ALLOWED_REGIONS, CONF_READ_ONLY, CONF_USE_LOCATION _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bmw_connected_drive/const.py b/homeassistant/components/bmw_connected_drive/const.py index 65dc7fde595910..7af24496838c54 100644 --- a/homeassistant/components/bmw_connected_drive/const.py +++ b/homeassistant/components/bmw_connected_drive/const.py @@ -1,7 +1,6 @@ """Const file for the BMW Connected Drive integration.""" ATTRIBUTION = "Data provided by BMW Connected Drive" -CONF_REGION = "region" CONF_ALLOWED_REGIONS = ["china", "north_america", "rest_of_world"] CONF_READ_ONLY = "read_only" CONF_USE_LOCATION = "use_location" diff --git a/tests/components/bmw_connected_drive/test_config_flow.py b/tests/components/bmw_connected_drive/test_config_flow.py index 52433f2f58f943..d56978deb2707f 100644 --- a/tests/components/bmw_connected_drive/test_config_flow.py +++ b/tests/components/bmw_connected_drive/test_config_flow.py @@ -5,10 +5,9 @@ from homeassistant.components.bmw_connected_drive.config_flow import DOMAIN from homeassistant.components.bmw_connected_drive.const import ( CONF_READ_ONLY, - CONF_REGION, CONF_USE_LOCATION, ) -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from tests.common import MockConfigEntry From 6fdb12c09d00cbc7eef243f0295e5d72d951d11f Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 5 Feb 2021 09:07:17 -0500 Subject: [PATCH 0222/1818] Use core constants for bluetooth_tracker (#46041) --- .../components/bluetooth_tracker/device_tracker.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index af49266bef4b0d..380d8091bd683f 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -20,6 +20,7 @@ YAML_DEVICES, async_load_config, ) +from homeassistant.const import CONF_DEVICE_ID import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType @@ -32,8 +33,6 @@ CONF_REQUEST_RSSI = "request_rssi" -CONF_DEVICE_ID = "device_id" - DEFAULT_DEVICE_ID = -1 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -131,7 +130,6 @@ async def async_setup_scanner( async def perform_bluetooth_update(): """Discover Bluetooth devices and update status.""" - _LOGGER.debug("Performing Bluetooth devices discovery and update") tasks = [] @@ -164,7 +162,6 @@ async def perform_bluetooth_update(): async def update_bluetooth(now=None): """Lookup Bluetooth devices and update status.""" - # If an update is in progress, we don't do anything if update_bluetooth_lock.locked(): _LOGGER.debug( @@ -178,7 +175,6 @@ async def update_bluetooth(now=None): async def handle_manual_update_bluetooth(call): """Update bluetooth devices on demand.""" - await update_bluetooth() hass.async_create_task(update_bluetooth()) From 7144c5f316c3008960f58c7beddb8006a2ac1425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Fri, 5 Feb 2021 15:08:28 +0100 Subject: [PATCH 0223/1818] Fix demo number entity (#45991) --- homeassistant/components/demo/number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/demo/number.py b/homeassistant/components/demo/number.py index 5a6ce5f5c64954..f3fd815f621b2c 100644 --- a/homeassistant/components/demo/number.py +++ b/homeassistant/components/demo/number.py @@ -21,7 +21,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= DemoNumber( "pwm1", "PWM 1", - 42.0, + 0.42, "mdi:square-wave", False, 0.0, From ae2c7e4c742c9f83dd157a0999fe45c0bd552ae2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Feb 2021 16:31:47 +0100 Subject: [PATCH 0224/1818] Improve UniFi tests (#45871) --- tests/components/unifi/test_config_flow.py | 30 +-- tests/components/unifi/test_controller.py | 194 +++++++++++------- tests/components/unifi/test_device_tracker.py | 127 +++++++----- tests/components/unifi/test_init.py | 13 +- tests/components/unifi/test_sensor.py | 35 ++-- tests/components/unifi/test_switch.py | 190 +++++++++-------- 6 files changed, 315 insertions(+), 274 deletions(-) diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 790e204c1dd4df..302564dbbd5034 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -191,7 +191,7 @@ async def test_flow_works_multiple_sites(hass, aioclient_mock): async def test_flow_raise_already_configured(hass, aioclient_mock): """Test config flow aborts since a connected config entry already exists.""" - await setup_unifi_integration(hass) + await setup_unifi_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( UNIFI_DOMAIN, context={"source": "user"} @@ -200,6 +200,8 @@ async def test_flow_raise_already_configured(hass, aioclient_mock): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" + aioclient_mock.clear_requests() + aioclient_mock.get("https://1.2.3.4:1234", status=302) aioclient_mock.post( @@ -340,17 +342,19 @@ async def test_flow_fails_controller_unavailable(hass, aioclient_mock): async def test_reauth_flow_update_configuration(hass, aioclient_mock): """Verify reauth flow can update controller configuration.""" - controller = await setup_unifi_integration(hass) + config_entry = await setup_unifi_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( UNIFI_DOMAIN, context={"source": SOURCE_REAUTH}, - data=controller.config_entry, + data=config_entry, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == SOURCE_USER + aioclient_mock.clear_requests() + aioclient_mock.get("https://1.2.3.4:1234", status=302) aioclient_mock.post( @@ -381,15 +385,16 @@ async def test_reauth_flow_update_configuration(hass, aioclient_mock): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "reauth_successful" - assert controller.host == "1.2.3.4" - assert controller.config_entry.data[CONF_CONTROLLER][CONF_USERNAME] == "new_name" - assert controller.config_entry.data[CONF_CONTROLLER][CONF_PASSWORD] == "new_pass" + assert config_entry.data[CONF_CONTROLLER][CONF_HOST] == "1.2.3.4" + assert config_entry.data[CONF_CONTROLLER][CONF_USERNAME] == "new_name" + assert config_entry.data[CONF_CONTROLLER][CONF_PASSWORD] == "new_pass" -async def test_advanced_option_flow(hass): +async def test_advanced_option_flow(hass, aioclient_mock): """Test advanced config flow options.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, clients_response=CLIENTS, devices_response=DEVICES, wlans_response=WLANS, @@ -398,7 +403,7 @@ async def test_advanced_option_flow(hass): ) result = await hass.config_entries.options.async_init( - controller.config_entry.entry_id, context={"show_advanced_options": True} + config_entry.entry_id, context={"show_advanced_options": True} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -457,10 +462,11 @@ async def test_advanced_option_flow(hass): } -async def test_simple_option_flow(hass): +async def test_simple_option_flow(hass, aioclient_mock): """Test simple config flow options.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, clients_response=CLIENTS, wlans_response=WLANS, dpigroup_response=DPI_GROUPS, @@ -468,7 +474,7 @@ async def test_simple_option_flow(hass): ) result = await hass.config_entries.options.async_init( - controller.config_entry.entry_id, context={"show_advanced_options": False} + config_entry.entry_id, context={"show_advanced_options": False} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index e484e041a883ba..077481a9320b55 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -1,5 +1,5 @@ """Test UniFi Controller.""" -from collections import deque + from copy import deepcopy from datetime import timedelta from unittest.mock import patch @@ -33,14 +33,18 @@ CONF_PORT, CONF_USERNAME, CONF_VERIFY_SSL, + CONTENT_TYPE_JSON, ) from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry +DEFAULT_HOST = "1.2.3.4" +DEFAULT_SITE = "site_id" + CONTROLLER_HOST = { "hostname": "controller_host", - "ip": "1.2.3.4", + "ip": DEFAULT_HOST, "is_wired": True, "last_seen": 1562600145, "mac": "10:00:00:00:00:01", @@ -54,11 +58,11 @@ } CONTROLLER_DATA = { - CONF_HOST: "1.2.3.4", + CONF_HOST: DEFAULT_HOST, CONF_USERNAME: "username", CONF_PASSWORD: "password", CONF_PORT: 1234, - CONF_SITE_ID: "site_id", + CONF_SITE_ID: DEFAULT_SITE, CONF_VERIFY_SSL: False, } @@ -67,22 +71,90 @@ CONFIGURATION = [] -SITES = {"Site name": {"desc": "Site name", "name": "site_id", "role": "admin"}} +SITE = [{"desc": "Site name", "name": "site_id", "role": "admin"}] DESCRIPTION = [{"name": "username", "site_name": "site_id", "site_role": "admin"}] +def mock_default_unifi_requests( + aioclient_mock, + host, + site_id, + sites=None, + description=None, + clients_response=None, + clients_all_response=None, + devices_response=None, + dpiapp_response=None, + dpigroup_response=None, + wlans_response=None, +): + """Mock default UniFi requests responses.""" + aioclient_mock.get(f"https://{host}:1234", status=302) # Check UniFi OS + + aioclient_mock.post( + f"https://{host}:1234/api/login", + json={"data": "login successful", "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + + aioclient_mock.get( + f"https://{host}:1234/api/self/sites", + json={"data": sites or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/self", + json={"data": description or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/stat/sta", + json={"data": clients_response or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/rest/user", + json={"data": clients_all_response or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/stat/device", + json={"data": devices_response or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/rest/dpiapp", + json={"data": dpiapp_response or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/rest/dpigroup", + json={"data": dpigroup_response or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + aioclient_mock.get( + f"https://{host}:1234/api/s/{site_id}/rest/wlanconf", + json={"data": wlans_response or [], "meta": {"rc": "ok"}}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + + async def setup_unifi_integration( hass, + aioclient_mock=None, + *, config=ENTRY_CONFIG, options=ENTRY_OPTIONS, - sites=SITES, + sites=SITE, site_description=DESCRIPTION, clients_response=None, - devices_response=None, clients_all_response=None, - wlans_response=None, - dpigroup_response=None, + devices_response=None, dpiapp_response=None, + dpigroup_response=None, + wlans_response=None, known_wireless_clients=None, controllers=None, ): @@ -102,82 +174,39 @@ async def setup_unifi_integration( known_wireless_clients, config_entry ) - mock_client_responses = deque() - if clients_response: - mock_client_responses.append(clients_response) - - mock_device_responses = deque() - if devices_response: - mock_device_responses.append(devices_response) - - mock_client_all_responses = deque() - if clients_all_response: - mock_client_all_responses.append(clients_all_response) - - mock_wlans_responses = deque() - if wlans_response: - mock_wlans_responses.append(wlans_response) - - mock_dpigroup_responses = deque() - if dpigroup_response: - mock_dpigroup_responses.append(dpigroup_response) - - mock_dpiapp_responses = deque() - if dpiapp_response: - mock_dpiapp_responses.append(dpiapp_response) - - mock_requests = [] - - async def mock_request(self, method, path, json=None): - mock_requests.append({"method": method, "path": path, "json": json}) - - if path == "/stat/sta" and mock_client_responses: - return mock_client_responses.popleft() - if path == "/stat/device" and mock_device_responses: - return mock_device_responses.popleft() - if path == "/rest/user" and mock_client_all_responses: - return mock_client_all_responses.popleft() - if path == "/rest/wlanconf" and mock_wlans_responses: - return mock_wlans_responses.popleft() - if path == "/rest/dpigroup" and mock_dpigroup_responses: - return mock_dpigroup_responses.popleft() - if path == "/rest/dpiapp" and mock_dpiapp_responses: - return mock_dpiapp_responses.popleft() - return {} + if aioclient_mock: + mock_default_unifi_requests( + aioclient_mock, + host=config_entry.data[CONF_CONTROLLER][CONF_HOST], + site_id=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID], + sites=sites, + description=site_description, + clients_response=clients_response, + clients_all_response=clients_all_response, + devices_response=devices_response, + dpiapp_response=dpiapp_response, + dpigroup_response=dpigroup_response, + wlans_response=wlans_response, + ) - with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( - "aiounifi.Controller.login", - return_value=True, - ), patch("aiounifi.Controller.sites", return_value=sites), patch( - "aiounifi.Controller.site_description", return_value=site_description - ), patch( - "aiounifi.Controller.request", new=mock_request - ), patch.object( - aiounifi.websocket.WSClient, "start", return_value=True - ): + with patch.object(aiounifi.websocket.WSClient, "start", return_value=True): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() if config_entry.entry_id not in hass.data[UNIFI_DOMAIN]: return None - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - - controller.mock_client_responses = mock_client_responses - controller.mock_device_responses = mock_device_responses - controller.mock_client_all_responses = mock_client_all_responses - controller.mock_wlans_responses = mock_wlans_responses - controller.mock_requests = mock_requests - return controller + return config_entry -async def test_controller_setup(hass): +async def test_controller_setup(hass, aioclient_mock): """Successful setup.""" with patch( "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", return_value=True, ) as forward_entry_setup: - controller = await setup_unifi_integration(hass) + config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] entry = controller.config_entry assert len(forward_entry_setup.mock_calls) == len(SUPPORTED_PLATFORMS) @@ -187,8 +216,8 @@ async def test_controller_setup(hass): assert controller.host == CONTROLLER_DATA[CONF_HOST] assert controller.site == CONTROLLER_DATA[CONF_SITE_ID] - assert controller.site_name in SITES - assert controller.site_role == SITES[controller.site_name]["role"] + assert controller.site_name == SITE[0]["desc"] + assert controller.site_role == SITE[0]["role"] assert controller.option_allow_bandwidth_sensors == DEFAULT_ALLOW_BANDWIDTH_SENSORS assert controller.option_allow_uptime_sensors == DEFAULT_ALLOW_UPTIME_SENSORS @@ -208,9 +237,12 @@ async def test_controller_setup(hass): assert controller.signal_heartbeat_missed == "unifi-heartbeat-missed" -async def test_controller_mac(hass): +async def test_controller_mac(hass, aioclient_mock): """Test that it is possible to identify controller mac.""" - controller = await setup_unifi_integration(hass, clients_response=[CONTROLLER_HOST]) + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[CONTROLLER_HOST] + ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert controller.mac == CONTROLLER_HOST["mac"] @@ -245,9 +277,10 @@ async def test_controller_unknown_error(hass): assert hass.data[UNIFI_DOMAIN] == {} -async def test_reset_after_successful_setup(hass): +async def test_reset_after_successful_setup(hass, aioclient_mock): """Calling reset when the entry has been setup.""" - controller = await setup_unifi_integration(hass) + config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(controller.listeners) == 6 @@ -258,9 +291,12 @@ async def test_reset_after_successful_setup(hass): assert len(controller.listeners) == 0 -async def test_wireless_client_event_calls_update_wireless_devices(hass): +async def test_wireless_client_event_calls_update_wireless_devices( + hass, aioclient_mock +): """Call update_wireless_devices method when receiving wireless client event.""" - controller = await setup_unifi_integration(hass) + config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] with patch( "homeassistant.components.unifi.controller.UniFiController.update_wireless_clients", diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 6462fcba943740..39465a34aefbd9 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -25,7 +25,6 @@ ) from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import entity_registry -from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, setup_unifi_integration @@ -151,27 +150,19 @@ } -async def test_platform_manually_configured(hass): - """Test that nothing happens when configuring unifi through device tracker platform.""" - assert ( - await async_setup_component( - hass, TRACKER_DOMAIN, {TRACKER_DOMAIN: {"platform": UNIFI_DOMAIN}} - ) - is False - ) - assert UNIFI_DOMAIN not in hass.data - - -async def test_no_clients(hass): +async def test_no_clients(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" - await setup_unifi_integration(hass) + await setup_unifi_integration(hass, aioclient_mock) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 0 -async def test_tracked_wireless_clients(hass): +async def test_tracked_wireless_clients(hass, aioclient_mock): """Test the update_items function with some clients.""" - controller = await setup_unifi_integration(hass, clients_response=[CLIENT_1]) + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[CLIENT_1] + ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 client_1 = hass.states.get("device_tracker.client_1") @@ -224,17 +215,19 @@ async def test_tracked_wireless_clients(hass): assert client_1.state == "home" -async def test_tracked_clients(hass): +async def test_tracked_clients(hass, aioclient_mock): """Test the update_items function with some clients.""" client_4_copy = copy(CLIENT_4) client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], known_wireless_clients=(CLIENT_4["mac"],), ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 client_1 = hass.states.get("device_tracker.client_1") @@ -269,12 +262,14 @@ async def test_tracked_clients(hass): assert client_1.state == "home" -async def test_tracked_devices(hass): +async def test_tracked_devices(hass, aioclient_mock): """Test the update_items function with some devices.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, devices_response=[DEVICE_1, DEVICE_2], ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 device_1 = hass.states.get("device_tracker.device_1") @@ -338,11 +333,12 @@ async def test_tracked_devices(hass): assert device.sw_version == EVENT_DEVICE_2_UPGRADED["version_to"] -async def test_remove_clients(hass): +async def test_remove_clients(hass, aioclient_mock): """Test the remove_items function with some clients.""" - controller = await setup_unifi_integration( - hass, clients_response=[CLIENT_1, CLIENT_2] + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[CLIENT_1, CLIENT_2] ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -367,13 +363,15 @@ async def test_remove_clients(hass): assert wired_client is not None -async def test_controller_state_change(hass): +async def test_controller_state_change(hass, aioclient_mock): """Verify entities state reflect on controller becoming unavailable.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -405,10 +403,11 @@ async def test_controller_state_change(hass): assert device_1.state == "home" -async def test_option_track_clients(hass): +async def test_option_track_clients(hass, aioclient_mock): """Test the tracking of clients can be turned off.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) @@ -424,7 +423,7 @@ async def test_option_track_clients(hass): assert device_1 is not None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_CLIENTS: False}, ) await hass.async_block_till_done() @@ -439,7 +438,7 @@ async def test_option_track_clients(hass): assert device_1 is not None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_CLIENTS: True}, ) await hass.async_block_till_done() @@ -454,10 +453,11 @@ async def test_option_track_clients(hass): assert device_1 is not None -async def test_option_track_wired_clients(hass): +async def test_option_track_wired_clients(hass, aioclient_mock): """Test the tracking of wired clients can be turned off.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) @@ -473,7 +473,7 @@ async def test_option_track_wired_clients(hass): assert device_1 is not None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_WIRED_CLIENTS: False}, ) await hass.async_block_till_done() @@ -488,7 +488,7 @@ async def test_option_track_wired_clients(hass): assert device_1 is not None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_WIRED_CLIENTS: True}, ) await hass.async_block_till_done() @@ -503,10 +503,11 @@ async def test_option_track_wired_clients(hass): assert device_1 is not None -async def test_option_track_devices(hass): +async def test_option_track_devices(hass, aioclient_mock): """Test the tracking of devices can be turned off.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) @@ -522,7 +523,7 @@ async def test_option_track_devices(hass): assert device_1 is not None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_DEVICES: False}, ) await hass.async_block_till_done() @@ -537,7 +538,7 @@ async def test_option_track_devices(hass): assert device_1 is None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_DEVICES: True}, ) await hass.async_block_till_done() @@ -552,7 +553,7 @@ async def test_option_track_devices(hass): assert device_1 is not None -async def test_option_ssid_filter(hass): +async def test_option_ssid_filter(hass, aioclient_mock): """Test the SSID filter works. Client 1 will travel from a supported SSID to an unsupported ssid. @@ -561,9 +562,10 @@ async def test_option_ssid_filter(hass): client_1_copy = copy(CLIENT_1) client_1_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - controller = await setup_unifi_integration( - hass, clients_response=[client_1_copy, CLIENT_3] + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[client_1_copy, CLIENT_3] ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -574,7 +576,7 @@ async def test_option_ssid_filter(hass): # Setting SSID filter will remove clients outside of filter hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_SSID_FILTER: ["ssid"]}, ) await hass.async_block_till_done() @@ -609,7 +611,7 @@ async def test_option_ssid_filter(hass): # Remove SSID filter hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_SSID_FILTER: []}, ) await hass.async_block_till_done() @@ -656,7 +658,7 @@ async def test_option_ssid_filter(hass): assert client_3.state == "not_home" -async def test_wireless_client_go_wired_issue(hass): +async def test_wireless_client_go_wired_issue(hass, aioclient_mock): """Test the solution to catch wireless device go wired UniFi issue. UniFi has a known issue that when a wireless device goes away it sometimes gets marked as wired. @@ -664,7 +666,10 @@ async def test_wireless_client_go_wired_issue(hass): client_1_client = copy(CLIENT_1) client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - controller = await setup_unifi_integration(hass, clients_response=[client_1_client]) + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[client_1_client] + ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless @@ -717,14 +722,18 @@ async def test_wireless_client_go_wired_issue(hass): assert client_1.attributes["is_wired"] is False -async def test_option_ignore_wired_bug(hass): +async def test_option_ignore_wired_bug(hass, aioclient_mock): """Test option to ignore wired bug.""" client_1_client = copy(CLIENT_1) client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - controller = await setup_unifi_integration( - hass, options={CONF_IGNORE_WIRED_BUG: True}, clients_response=[client_1_client] + config_entry = await setup_unifi_integration( + hass, + aioclient_mock, + options={CONF_IGNORE_WIRED_BUG: True}, + clients_response=[client_1_client], ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless @@ -777,7 +786,7 @@ async def test_option_ignore_wired_bug(hass): assert client_1.attributes["is_wired"] is False -async def test_restoring_client(hass): +async def test_restoring_client(hass, aioclient_mock): """Test the update_items function with some clients.""" config_entry = config_entries.ConfigEntry( version=1, @@ -809,6 +818,7 @@ async def test_restoring_client(hass): await setup_unifi_integration( hass, + aioclient_mock, options={CONF_BLOCK_CLIENT: True}, clients_response=[CLIENT_2], clients_all_response=[CLIENT_1], @@ -819,10 +829,11 @@ async def test_restoring_client(hass): assert device_1 is not None -async def test_dont_track_clients(hass): +async def test_dont_track_clients(hass, aioclient_mock): """Test don't track clients config works.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_TRACK_CLIENTS: False}, clients_response=[CLIENT_1], devices_response=[DEVICE_1], @@ -836,7 +847,7 @@ async def test_dont_track_clients(hass): assert device_1 is not None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_CLIENTS: True}, ) await hass.async_block_till_done() @@ -850,10 +861,11 @@ async def test_dont_track_clients(hass): assert device_1 is not None -async def test_dont_track_devices(hass): +async def test_dont_track_devices(hass, aioclient_mock): """Test don't track devices config works.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_TRACK_DEVICES: False}, clients_response=[CLIENT_1], devices_response=[DEVICE_1], @@ -867,7 +879,7 @@ async def test_dont_track_devices(hass): assert device_1 is None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_DEVICES: True}, ) await hass.async_block_till_done() @@ -881,10 +893,11 @@ async def test_dont_track_devices(hass): assert device_1 is not None -async def test_dont_track_wired_clients(hass): +async def test_dont_track_wired_clients(hass, aioclient_mock): """Test don't track wired clients config works.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_TRACK_WIRED_CLIENTS: False}, clients_response=[CLIENT_1, CLIENT_2], ) @@ -897,7 +910,7 @@ async def test_dont_track_wired_clients(hass): assert client_2 is None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_TRACK_WIRED_CLIENTS: True}, ) await hass.async_block_till_done() diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index cc2a4b3e4a3c38..841e9ec7576a0c 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -11,14 +11,14 @@ async def test_setup_with_no_config(hass): - """Test that we do not discover anything or try to set up a bridge.""" + """Test that we do not discover anything or try to set up a controller.""" assert await async_setup_component(hass, UNIFI_DOMAIN, {}) is True assert UNIFI_DOMAIN not in hass.data -async def test_successful_config_entry(hass): +async def test_successful_config_entry(hass, aioclient_mock): """Test that configured options for a host are loaded via config entry.""" - await setup_unifi_integration(hass) + await setup_unifi_integration(hass, aioclient_mock) assert hass.data[UNIFI_DOMAIN] @@ -44,7 +44,6 @@ async def test_controller_no_mac(hass): "site": "default", "verify_ssl": True, }, - "poe_control": True, }, ) entry.add_to_hass(hass) @@ -64,10 +63,10 @@ async def test_controller_no_mac(hass): assert len(mock_registry.mock_calls) == 0 -async def test_unload_entry(hass): +async def test_unload_entry(hass, aioclient_mock): """Test being able to unload an entry.""" - controller = await setup_unifi_integration(hass) + config_entry = await setup_unifi_integration(hass, aioclient_mock) assert hass.data[UNIFI_DOMAIN] - assert await unifi.async_unload_entry(hass, controller.config_entry) + assert await unifi.async_unload_entry(hass, config_entry) assert not hass.data[UNIFI_DOMAIN] diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index dc2fea634c91e0..c668bf3789f8ec 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -14,7 +14,6 @@ DOMAIN as UNIFI_DOMAIN, ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.setup import async_setup_component from .test_controller import setup_unifi_integration @@ -50,35 +49,25 @@ ] -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a controller.""" - assert ( - await async_setup_component( - hass, SENSOR_DOMAIN, {SENSOR_DOMAIN: {"platform": UNIFI_DOMAIN}} - ) - is True - ) - assert UNIFI_DOMAIN not in hass.data - - -async def test_no_clients(hass): +async def test_no_clients(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" - controller = await setup_unifi_integration( + await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True, }, ) - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 -async def test_sensors(hass): +async def test_sensors(hass, aioclient_mock): """Test the update_items function with some clients.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True, @@ -87,8 +76,8 @@ async def test_sensors(hass): }, clients_response=CLIENTS, ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 wired_client_rx = hass.states.get("sensor.wired_client_name_rx") @@ -129,7 +118,7 @@ async def test_sensors(hass): assert wireless_client_uptime.state == "2020-09-15T14:41:00+00:00" hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={ CONF_ALLOW_BANDWIDTH_SENSORS: False, CONF_ALLOW_UPTIME_SENSORS: False, @@ -150,7 +139,7 @@ async def test_sensors(hass): assert wireless_client_uptime is None hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={ CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True, @@ -189,16 +178,18 @@ async def test_sensors(hass): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 -async def test_remove_sensors(hass): +async def test_remove_sensors(hass, aioclient_mock): """Test the remove_items function with some clients.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True, }, clients_response=CLIENTS, ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 903db479d34c7c..e5a3a7eccc4120 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -17,7 +17,6 @@ ) from homeassistant.components.unifi.switch import POE_SWITCH from homeassistant.helpers import entity_registry -from homeassistant.setup import async_setup_component from .test_controller import ( CONTROLLER_HOST, @@ -282,21 +281,11 @@ ] -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a controller.""" - assert ( - await async_setup_component( - hass, SWITCH_DOMAIN, {SWITCH_DOMAIN: {"platform": UNIFI_DOMAIN}} - ) - is True - ) - assert UNIFI_DOMAIN not in hass.data - - -async def test_no_clients(hass): +async def test_no_clients(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" - controller = await setup_unifi_integration( + await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False, @@ -304,45 +293,46 @@ async def test_no_clients(hass): }, ) - assert len(controller.mock_requests) == 6 + assert aioclient_mock.call_count == 10 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_controller_not_client(hass): +async def test_controller_not_client(hass, aioclient_mock): """Test that the controller doesn't become a switch.""" - controller = await setup_unifi_integration( + await setup_unifi_integration( hass, + aioclient_mock, options={CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False}, clients_response=[CONTROLLER_HOST], devices_response=[DEVICE_1], ) - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 cloudkey = hass.states.get("switch.cloud_key") assert cloudkey is None -async def test_not_admin(hass): +async def test_not_admin(hass, aioclient_mock): """Test that switch platform only work on an admin account.""" description = deepcopy(DESCRIPTION) description[0]["site_role"] = "not admin" - controller = await setup_unifi_integration( + await setup_unifi_integration( hass, + aioclient_mock, options={CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False}, site_description=description, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_switches(hass): +async def test_switches(hass, aioclient_mock): """Test the update_items function with some clients.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]], CONF_TRACK_CLIENTS: False, @@ -354,8 +344,8 @@ async def test_switches(hass): dpigroup_response=DPI_GROUPS, dpiapp_response=DPI_APPS, ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4 switch_1 = hass.states.get("switch.poe_client_1") @@ -381,38 +371,44 @@ async def test_switches(hass): assert dpi_switch is not None assert dpi_switch.state == "on" + # Block and unblock client + + aioclient_mock.post( + f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", + ) + await hass.services.async_call( SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.block_client_1"}, blocking=True ) - assert len(controller.mock_requests) == 7 - assert controller.mock_requests[6] == { - "json": {"mac": "00:00:00:00:01:01", "cmd": "block-sta"}, - "method": "post", - "path": "/cmd/stamgr", + assert aioclient_mock.call_count == 11 + assert aioclient_mock.mock_calls[10][2] == { + "mac": "00:00:00:00:01:01", + "cmd": "block-sta", } await hass.services.async_call( SWITCH_DOMAIN, "turn_on", {"entity_id": "switch.block_client_1"}, blocking=True ) - assert len(controller.mock_requests) == 8 - assert controller.mock_requests[7] == { - "json": {"mac": "00:00:00:00:01:01", "cmd": "unblock-sta"}, - "method": "post", - "path": "/cmd/stamgr", + assert aioclient_mock.call_count == 12 + assert aioclient_mock.mock_calls[11][2] == { + "mac": "00:00:00:00:01:01", + "cmd": "unblock-sta", } + # Enable and disable DPI + + aioclient_mock.put( + f"https://{controller.host}:1234/api/s/{controller.site}/rest/dpiapp/5f976f62e3c58f018ec7e17d", + ) + await hass.services.async_call( SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.block_media_streaming"}, blocking=True, ) - assert len(controller.mock_requests) == 9 - assert controller.mock_requests[8] == { - "json": {"enabled": False}, - "method": "put", - "path": "/rest/dpiapp/5f976f62e3c58f018ec7e17d", - } + assert aioclient_mock.call_count == 13 + assert aioclient_mock.mock_calls[12][2] == {"enabled": False} await hass.services.async_call( SWITCH_DOMAIN, @@ -420,22 +416,20 @@ async def test_switches(hass): {"entity_id": "switch.block_media_streaming"}, blocking=True, ) - assert len(controller.mock_requests) == 10 - assert controller.mock_requests[9] == { - "json": {"enabled": True}, - "method": "put", - "path": "/rest/dpiapp/5f976f62e3c58f018ec7e17d", - } + assert aioclient_mock.call_count == 14 + assert aioclient_mock.mock_calls[13][2] == {"enabled": True} -async def test_remove_switches(hass): +async def test_remove_switches(hass, aioclient_mock): """Test the update_items function with some clients.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, clients_response=[CLIENT_1, UNBLOCKED], devices_response=[DEVICE_1], ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 poe_switch = hass.states.get("switch.poe_client_1") @@ -460,10 +454,11 @@ async def test_remove_switches(hass): assert block_switch is None -async def test_block_switches(hass): +async def test_block_switches(hass, aioclient_mock): """Test the update_items function with some clients.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]], CONF_TRACK_CLIENTS: False, @@ -472,6 +467,7 @@ async def test_block_switches(hass): clients_response=[UNBLOCKED], clients_all_response=[BLOCKED], ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 @@ -507,31 +503,34 @@ async def test_block_switches(hass): assert blocked is not None assert blocked.state == "off" + aioclient_mock.post( + f"https://{controller.host}:1234/api/s/{controller.site}/cmd/stamgr", + ) + await hass.services.async_call( SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.block_client_1"}, blocking=True ) - assert len(controller.mock_requests) == 7 - assert controller.mock_requests[6] == { - "json": {"mac": "00:00:00:00:01:01", "cmd": "block-sta"}, - "method": "post", - "path": "/cmd/stamgr", + assert aioclient_mock.call_count == 11 + assert aioclient_mock.mock_calls[10][2] == { + "mac": "00:00:00:00:01:01", + "cmd": "block-sta", } await hass.services.async_call( SWITCH_DOMAIN, "turn_on", {"entity_id": "switch.block_client_1"}, blocking=True ) - assert len(controller.mock_requests) == 8 - assert controller.mock_requests[7] == { - "json": {"mac": "00:00:00:00:01:01", "cmd": "unblock-sta"}, - "method": "post", - "path": "/cmd/stamgr", + assert aioclient_mock.call_count == 12 + assert aioclient_mock.mock_calls[11][2] == { + "mac": "00:00:00:00:01:01", + "cmd": "unblock-sta", } -async def test_new_client_discovered_on_block_control(hass): +async def test_new_client_discovered_on_block_control(hass, aioclient_mock): """Test if 2nd update has a new client.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_BLOCK_CLIENT: [BLOCKED["mac"]], CONF_TRACK_CLIENTS: False, @@ -539,8 +538,8 @@ async def test_new_client_discovered_on_block_control(hass): CONF_DPI_RESTRICTIONS: False, }, ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 blocked = hass.states.get("switch.block_client_1") @@ -567,10 +566,11 @@ async def test_new_client_discovered_on_block_control(hass): assert blocked is not None -async def test_option_block_clients(hass): +async def test_option_block_clients(hass, aioclient_mock): """Test the changes to option reflects accordingly.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]}, clients_all_response=[BLOCKED, UNBLOCKED], ) @@ -578,7 +578,7 @@ async def test_option_block_clients(hass): # Add a second switch hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() @@ -586,7 +586,7 @@ async def test_option_block_clients(hass): # Remove the second switch again hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]}, ) await hass.async_block_till_done() @@ -594,7 +594,7 @@ async def test_option_block_clients(hass): # Enable one and remove another one hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() @@ -602,17 +602,18 @@ async def test_option_block_clients(hass): # Remove one hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_BLOCK_CLIENT: []}, ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_option_remove_switches(hass): +async def test_option_remove_switches(hass, aioclient_mock): """Test removal of DPI switch when options updated.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False, @@ -626,23 +627,24 @@ async def test_option_remove_switches(hass): # Disable DPI Switches hass.config_entries.async_update_entry( - controller.config_entry, + config_entry, options={CONF_DPI_RESTRICTIONS: False, CONF_POE_CLIENTS: False}, ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_new_client_discovered_on_poe_control(hass): +async def test_new_client_discovered_on_poe_control(hass, aioclient_mock): """Test if 2nd update has a new client.""" - controller = await setup_unifi_integration( + config_entry = await setup_unifi_integration( hass, + aioclient_mock, options={CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False}, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 controller.api.websocket._data = { @@ -665,47 +667,41 @@ async def test_new_client_discovered_on_poe_control(hass): switch_2 = hass.states.get("switch.poe_client_2") assert switch_2 is not None + aioclient_mock.put( + f"https://{controller.host}:1234/api/s/{controller.site}/rest/device/mock-id", + ) + await hass.services.async_call( SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.poe_client_1"}, blocking=True ) - assert len(controller.mock_requests) == 7 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 - assert controller.mock_requests[6] == { - "json": { - "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "off"}] - }, - "method": "put", - "path": "/rest/device/mock-id", + assert aioclient_mock.call_count == 11 + assert aioclient_mock.mock_calls[10][2] == { + "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "off"}] } await hass.services.async_call( SWITCH_DOMAIN, "turn_on", {"entity_id": "switch.poe_client_1"}, blocking=True ) - assert len(controller.mock_requests) == 8 - assert controller.mock_requests[7] == { - "json": { - "port_overrides": [ - {"port_idx": 1, "portconf_id": "1a1", "poe_mode": "auto"} - ] - }, - "method": "put", - "path": "/rest/device/mock-id", + assert aioclient_mock.call_count == 12 + assert aioclient_mock.mock_calls[11][2] == { + "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "auto"}] } -async def test_ignore_multiple_poe_clients_on_same_port(hass): +async def test_ignore_multiple_poe_clients_on_same_port(hass, aioclient_mock): """Ignore when there are multiple POE driven clients on same port. If there is a non-UniFi switch powered by POE, clients will be transparently marked as having POE as well. """ - controller = await setup_unifi_integration( + await setup_unifi_integration( hass, + aioclient_mock, clients_response=POE_SWITCH_CLIENTS, devices_response=[DEVICE_1], ) - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 switch_1 = hass.states.get("switch.poe_client_1") @@ -714,7 +710,7 @@ async def test_ignore_multiple_poe_clients_on_same_port(hass): assert switch_2 is None -async def test_restoring_client(hass): +async def test_restoring_client(hass, aioclient_mock): """Test the update_items function with some clients.""" config_entry = config_entries.ConfigEntry( version=1, @@ -744,8 +740,9 @@ async def test_restoring_client(hass): config_entry=config_entry, ) - controller = await setup_unifi_integration( + await setup_unifi_integration( hass, + aioclient_mock, options={ CONF_BLOCK_CLIENT: ["random mac"], CONF_TRACK_CLIENTS: False, @@ -756,7 +753,6 @@ async def test_restoring_client(hass): clients_all_response=[CLIENT_1], ) - assert len(controller.mock_requests) == 6 assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 device_1 = hass.states.get("switch.client_1") From 55f81a8a0414373a9a8e37602e84c6e77fdce1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Nenz=C3=A9n?= Date: Fri, 5 Feb 2021 18:28:06 +0100 Subject: [PATCH 0225/1818] Address Plaato post merge review (#46024) --- .../components/plaato/binary_sensor.py | 6 +- .../components/plaato/config_flow.py | 8 +- homeassistant/components/plaato/const.py | 9 +++ homeassistant/components/plaato/entity.py | 10 ++- homeassistant/components/plaato/sensor.py | 4 +- tests/components/plaato/test_config_flow.py | 73 ++++++++++++------- 6 files changed, 72 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/plaato/binary_sensor.py b/homeassistant/components/plaato/binary_sensor.py index 0ee61b7668bd0a..27150692d6f0e8 100644 --- a/homeassistant/components/plaato/binary_sensor.py +++ b/homeassistant/components/plaato/binary_sensor.py @@ -20,7 +20,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Plaato from a config entry.""" if config_entry.data[CONF_USE_WEBHOOK]: - return False + return coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] async_add_entities( @@ -29,11 +29,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): sensor_type, coordinator, ) - for sensor_type in coordinator.data.binary_sensors.keys() + for sensor_type in coordinator.data.binary_sensors ) - return True - class PlaatoBinarySensor(PlaatoEntity, BinarySensorEntity): """Representation of a Binary Sensor.""" diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index 290776f47c1164..8dbf6d50fca9c9 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -30,7 +30,7 @@ class PlaatoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handles a Plaato config flow.""" - VERSION = 2 + VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL def __init__(self): @@ -128,16 +128,16 @@ async def _async_create_entry(self): async def _show_api_method_form( self, device_type: PlaatoDeviceType, errors: dict = None ): - data_scheme = vol.Schema({vol.Optional(CONF_TOKEN, default=""): str}) + data_schema = vol.Schema({vol.Optional(CONF_TOKEN, default=""): str}) if device_type == PlaatoDeviceType.Airlock: - data_scheme = data_scheme.extend( + data_schema = data_schema.extend( {vol.Optional(CONF_USE_WEBHOOK, default=False): bool} ) return self.async_show_form( step_id="api_method", - data_schema=data_scheme, + data_schema=data_schema, errors=errors, description_placeholders={PLACEHOLDER_DEVICE_TYPE: device_type.name}, ) diff --git a/homeassistant/components/plaato/const.py b/homeassistant/components/plaato/const.py index f50eaaac0ed886..1700b803775d6a 100644 --- a/homeassistant/components/plaato/const.py +++ b/homeassistant/components/plaato/const.py @@ -25,3 +25,12 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" DEFAULT_SCAN_INTERVAL = 5 MIN_UPDATE_INTERVAL = timedelta(minutes=1) + +DEVICE_STATE_ATTRIBUTES = { + "beer_name": "beer_name", + "keg_date": "keg_date", + "mode": "mode", + "original_gravity": "original_gravity", + "final_gravity": "final_gravity", + "alcohol_by_volume": "alcohol_by_volume", +} diff --git a/homeassistant/components/plaato/entity.py b/homeassistant/components/plaato/entity.py index 6f72c3419a4626..7cb1a77a9fb7b0 100644 --- a/homeassistant/components/plaato/entity.py +++ b/homeassistant/components/plaato/entity.py @@ -7,6 +7,7 @@ DEVICE, DEVICE_ID, DEVICE_NAME, + DEVICE_STATE_ATTRIBUTES, DEVICE_TYPE, DOMAIN, SENSOR_DATA, @@ -69,8 +70,13 @@ def device_info(self): @property def device_state_attributes(self): """Return the state attributes of the monitored installation.""" - if self._attributes is not None: - return self._attributes + if self._attributes: + return { + attr_key: self._attributes[plaato_key] + for attr_key, plaato_key in DEVICE_STATE_ATTRIBUTES.items() + if plaato_key in self._attributes + and self._attributes[plaato_key] is not None + } @property def available(self): diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index 3f5e467f504a2d..1dd347305e10f7 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -59,11 +59,9 @@ async def _async_update_from_webhook(device_id, sensor_data: PlaatoDevice): coordinator = entry_data[COORDINATOR] async_add_entities( PlaatoSensor(entry_data, sensor_type, coordinator) - for sensor_type in coordinator.data.sensors.keys() + for sensor_type in coordinator.data.sensors ) - return True - class PlaatoSensor(PlaatoEntity): """Representation of a Plaato Sensor.""" diff --git a/tests/components/plaato/test_config_flow.py b/tests/components/plaato/test_config_flow.py index 10562b6aa60b33..7966882a97794b 100644 --- a/tests/components/plaato/test_config_flow.py +++ b/tests/components/plaato/test_config_flow.py @@ -95,15 +95,12 @@ async def test_show_config_form_validate_webhook(hass, webhook_id): assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "api_method" - async def return_async_value(val): - return val - hass.config.components.add("cloud") with patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( "homeassistant.components.cloud.async_create_cloudhook", - return_value=return_async_value("https://hooks.nabu.casa/ABCD"), + return_value="https://hooks.nabu.casa/ABCD", ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -243,21 +240,34 @@ async def test_options(hass): data={}, options={CONF_SCAN_INTERVAL: 5}, ) - config_entry.add_to_hass(hass) - with patch("homeassistant.components.plaato.async_setup_entry", return_value=True): + + with patch( + "homeassistant.components.plaato.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.plaato.async_setup_entry", return_value=True + ) as mock_setup_entry: + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + 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"] == "user" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_SCAN_INTERVAL: 10}, - ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_SCAN_INTERVAL: 10}, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_SCAN_INTERVAL] == 10 - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_SCAN_INTERVAL] == 10 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 async def test_options_webhook(hass, webhook_id): @@ -268,19 +278,32 @@ async def test_options_webhook(hass, webhook_id): data={CONF_USE_WEBHOOK: True, CONF_WEBHOOK_ID: None}, options={CONF_SCAN_INTERVAL: 5}, ) - config_entry.add_to_hass(hass) - with patch("homeassistant.components.plaato.async_setup_entry", return_value=True): + + with patch( + "homeassistant.components.plaato.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.plaato.async_setup_entry", return_value=True + ) as mock_setup_entry: + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + 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"] == "webhook" - assert result["description_placeholders"] == {"webhook_url": ""} + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "webhook" + assert result["description_placeholders"] == {"webhook_url": ""} - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_WEBHOOK_ID: WEBHOOK_ID}, - ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_WEBHOOK_ID: WEBHOOK_ID}, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_WEBHOOK_ID] == CONF_WEBHOOK_ID - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_WEBHOOK_ID] == CONF_WEBHOOK_ID + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 From 55f9d985234e51e450add2370c4dd171a848e498 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:12:23 +0100 Subject: [PATCH 0226/1818] Fix deprecated method isAlive() (#46062) --- homeassistant/components/zwave/light.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index 244b4a557e1ee3..52014e37eea278 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -175,7 +175,7 @@ def _refresh_value(): self._refreshing = True self.values.primary.refresh() - if self._timer is not None and self._timer.isAlive(): + if self._timer is not None and self._timer.is_alive(): self._timer.cancel() self._timer = Timer(self._delay, _refresh_value) From 0d620eb7c3139d4e425715584f7f11c43737ee35 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Feb 2021 19:38:08 +0100 Subject: [PATCH 0227/1818] Add unique id to UniFi config entries using the unique id of the site it is controlling (#45737) * Add unique id to UniFi config entries using the unique id of the site it is controlling * Fix failing test --- homeassistant/components/unifi/__init__.py | 5 ++ homeassistant/components/unifi/config_flow.py | 51 ++++++++++--------- homeassistant/components/unifi/controller.py | 2 + tests/components/unifi/test_config_flow.py | 36 +++++++++---- tests/components/unifi/test_controller.py | 3 +- tests/components/unifi/test_init.py | 1 + 6 files changed, 61 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index a9d39251838cb9..5f19a01ce4558c 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -32,6 +32,11 @@ async def async_setup_entry(hass, config_entry): if not await controller.async_setup(): return False + if config_entry.unique_id is None: + hass.config_entries.async_update_entry( + config_entry, unique_id=controller.site_id + ) + hass.data[UNIFI_DOMAIN][config_entry.entry_id] = controller hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, controller.shutdown) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 07b81621750fce..1d89215dc89904 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -70,7 +70,8 @@ def async_get_options_flow(config_entry): def __init__(self): """Initialize the UniFi flow.""" self.config = {} - self.sites = None + self.site_ids = {} + self.site_names = {} self.reauth_config_entry = None self.reauth_config = {} self.reauth_schema = {} @@ -101,11 +102,15 @@ async def async_step_user(self, user_input=None): errors["base"] = "service_unavailable" else: - self.sites = {site["name"]: site["desc"] for site in sites.values()} + self.site_ids = {site["_id"]: site["name"] for site in sites.values()} + self.site_names = {site["_id"]: site["desc"] for site in sites.values()} - if self.reauth_config.get(CONF_SITE_ID) in self.sites: + if ( + self.reauth_config_entry + and self.reauth_config_entry.unique_id in self.site_names + ): return await self.async_step_site( - {CONF_SITE_ID: self.reauth_config[CONF_SITE_ID]} + {CONF_SITE_ID: self.reauth_config_entry.unique_id} ) return await self.async_step_site() @@ -136,26 +141,18 @@ async def async_step_site(self, user_input=None): if user_input is not None: - self.config[CONF_SITE_ID] = user_input[CONF_SITE_ID] + unique_id = user_input[CONF_SITE_ID] + self.config[CONF_SITE_ID] = self.site_ids[unique_id] data = {CONF_CONTROLLER: self.config} - if self.reauth_config_entry: - self.hass.config_entries.async_update_entry( - self.reauth_config_entry, data=data - ) - await self.hass.config_entries.async_reload( - self.reauth_config_entry.entry_id - ) - return self.async_abort(reason="reauth_successful") + config_entry = await self.async_set_unique_id(unique_id) + abort_reason = "configuration_updated" - for config_entry in self._async_current_entries(): - controller_data = config_entry.data[CONF_CONTROLLER] - if ( - controller_data[CONF_HOST] != self.config[CONF_HOST] - or controller_data[CONF_SITE_ID] != self.config[CONF_SITE_ID] - ): - continue + if self.reauth_config_entry: + config_entry = self.reauth_config_entry + abort_reason = "reauth_successful" + if config_entry: controller = self.hass.data.get(UNIFI_DOMAIN, {}).get( config_entry.entry_id ) @@ -165,17 +162,21 @@ async def async_step_site(self, user_input=None): self.hass.config_entries.async_update_entry(config_entry, data=data) await self.hass.config_entries.async_reload(config_entry.entry_id) - return self.async_abort(reason="configuration_updated") + return self.async_abort(reason=abort_reason) - site_nice_name = self.sites[self.config[CONF_SITE_ID]] + site_nice_name = self.site_names[unique_id] return self.async_create_entry(title=site_nice_name, data=data) - if len(self.sites) == 1: - return await self.async_step_site({CONF_SITE_ID: next(iter(self.sites))}) + if len(self.site_names) == 1: + return await self.async_step_site( + {CONF_SITE_ID: next(iter(self.site_names))} + ) return self.async_show_form( step_id="site", - data_schema=vol.Schema({vol.Required(CONF_SITE_ID): vol.In(self.sites)}), + data_schema=vol.Schema( + {vol.Required(CONF_SITE_ID): vol.In(self.site_names)} + ), errors=errors, ) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index bd55f4479fad43..5d5e679e75ed7d 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -95,6 +95,7 @@ def __init__(self, hass, config_entry): self.wireless_clients = None self.listeners = [] + self.site_id: str = "" self._site_name = None self._site_role = None @@ -321,6 +322,7 @@ async def async_setup(self): for site in sites.values(): if self.site == site["name"]: + self.site_id = site["_id"] self._site_name = site["desc"] break diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 302564dbbd5034..096e6ba779137c 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -112,7 +112,9 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): aioclient_mock.get( "https://1.2.3.4:1234/api/self/sites", json={ - "data": [{"desc": "Site name", "name": "site_id", "role": "admin"}], + "data": [ + {"desc": "Site name", "name": "site_id", "role": "admin", "_id": "1"} + ], "meta": {"rc": "ok"}, }, headers={"content-type": CONTENT_TYPE_JSON}, @@ -143,7 +145,7 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): } -async def test_flow_works_multiple_sites(hass, aioclient_mock): +async def test_flow_multiple_sites(hass, aioclient_mock): """Test config flow works when finding multiple sites.""" result = await hass.config_entries.flow.async_init( UNIFI_DOMAIN, context={"source": "user"} @@ -164,8 +166,8 @@ async def test_flow_works_multiple_sites(hass, aioclient_mock): "https://1.2.3.4:1234/api/self/sites", json={ "data": [ - {"name": "default", "role": "admin", "desc": "site name"}, - {"name": "site2", "role": "admin", "desc": "site2 name"}, + {"name": "default", "role": "admin", "desc": "site name", "_id": "1"}, + {"name": "site2", "role": "admin", "desc": "site2 name", "_id": "2"}, ], "meta": {"rc": "ok"}, }, @@ -185,8 +187,8 @@ async def test_flow_works_multiple_sites(hass, aioclient_mock): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "site" - assert result["data_schema"]({"site": "default"}) - assert result["data_schema"]({"site": "site2"}) + assert result["data_schema"]({"site": "1"}) + assert result["data_schema"]({"site": "2"}) async def test_flow_raise_already_configured(hass, aioclient_mock): @@ -213,7 +215,9 @@ async def test_flow_raise_already_configured(hass, aioclient_mock): aioclient_mock.get( "https://1.2.3.4:1234/api/self/sites", json={ - "data": [{"desc": "Site name", "name": "site_id", "role": "admin"}], + "data": [ + {"desc": "Site name", "name": "site_id", "role": "admin", "_id": "1"} + ], "meta": {"rc": "ok"}, }, headers={"content-type": CONTENT_TYPE_JSON}, @@ -237,12 +241,16 @@ async def test_flow_raise_already_configured(hass, aioclient_mock): async def test_flow_aborts_configuration_updated(hass, aioclient_mock): """Test config flow aborts since a connected config entry already exists.""" entry = MockConfigEntry( - domain=UNIFI_DOMAIN, data={"controller": {"host": "1.2.3.4", "site": "office"}} + domain=UNIFI_DOMAIN, + data={"controller": {"host": "1.2.3.4", "site": "office"}}, + unique_id="2", ) entry.add_to_hass(hass) entry = MockConfigEntry( - domain=UNIFI_DOMAIN, data={"controller": {"host": "1.2.3.4", "site": "site_id"}} + domain=UNIFI_DOMAIN, + data={"controller": {"host": "1.2.3.4", "site": "site_id"}}, + unique_id="1", ) entry.add_to_hass(hass) @@ -264,7 +272,9 @@ async def test_flow_aborts_configuration_updated(hass, aioclient_mock): aioclient_mock.get( "https://1.2.3.4:1234/api/self/sites", json={ - "data": [{"desc": "Site name", "name": "site_id", "role": "admin"}], + "data": [ + {"desc": "Site name", "name": "site_id", "role": "admin", "_id": "1"} + ], "meta": {"rc": "ok"}, }, headers={"content-type": CONTENT_TYPE_JSON}, @@ -343,6 +353,8 @@ async def test_flow_fails_controller_unavailable(hass, aioclient_mock): async def test_reauth_flow_update_configuration(hass, aioclient_mock): """Verify reauth flow can update controller configuration.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + controller.available = False result = await hass.config_entries.flow.async_init( UNIFI_DOMAIN, @@ -366,7 +378,9 @@ async def test_reauth_flow_update_configuration(hass, aioclient_mock): aioclient_mock.get( "https://1.2.3.4:1234/api/self/sites", json={ - "data": [{"desc": "Site name", "name": "site_id", "role": "admin"}], + "data": [ + {"desc": "Site name", "name": "site_id", "role": "admin", "_id": "1"} + ], "meta": {"rc": "ok"}, }, headers={"content-type": CONTENT_TYPE_JSON}, diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 077481a9320b55..3ecd44b3db729e 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -71,7 +71,7 @@ CONFIGURATION = [] -SITE = [{"desc": "Site name", "name": "site_id", "role": "admin"}] +SITE = [{"desc": "Site name", "name": "site_id", "role": "admin", "_id": "1"}] DESCRIPTION = [{"name": "username", "site_name": "site_id", "site_role": "admin"}] @@ -166,6 +166,7 @@ async def setup_unifi_integration( data=deepcopy(config), options=deepcopy(options), entry_id=1, + unique_id="1", ) config_entry.add_to_hass(hass) diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 841e9ec7576a0c..9de8b0a0990259 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -45,6 +45,7 @@ async def test_controller_no_mac(hass): "verify_ssl": True, }, }, + unique_id="1", ) entry.add_to_hass(hass) mock_registry = Mock() From c01e01f7973cb0f577d2d62eb044c72649d1deba Mon Sep 17 00:00:00 2001 From: functionpointer Date: Fri, 5 Feb 2021 22:13:57 +0100 Subject: [PATCH 0228/1818] MySensors config flow (#45421) * MySensors: Add type annotations Adds a bunch of type annotations that were created while understanding the code. * MySensors: Change GatewayId to string In preparation for config flow. The GatewayId used to be id(gateway). With config flows, every gateway will have its own ConfigEntry. Every ConfigEntry has a unique id. Thus we would have two separate but one-to-one related ID systems. This commit removes this unneeded duplication by using the id of the ConfigEntry as GatewayId. * MySensors: Add unique_id to all entities This allows entities to work well with the frontend. * MySensors: Add device_info to all entities Entities belonging to the same node_id will now by grouped as a device. * MySensors: clean up device.py a bit * MySensors: Add config flow support With this change the MySensors can be fully configured from the GUI. Legacy configuration.yaml configs will be migrated by reading them once. Note that custom node names are not migrated. Users will have to re-enter the names in the front-end. Since there is no straight-forward way to configure global settings, all previously global settings are now per-gateway. These settings include: - MQTT retain - optimistic - persistence enable - MySensors version When a MySensors integration is loaded, it works as follows: 1. __init__.async_setup_entry is called 2. for every platform, async_forward_entry_setup is called 3. the platform's async_setup_entry is called 4. __init__.setup_mysensors_platform is called 5. the entity's constructor (e.g. MySensorsCover) is called 6. the created entity is stored in a dict in the hass object * MySensors: Fix linter errors * MySensors: Remove unused import * MySensors: Feedback from @MartinHjelmare * MySensors: Multi-step config flow * MySensors: More feedback * MySensors: Move all storage in hass object under DOMAIN The integration now stores everything under hass.data["mysensors"] instead of using several top level keys. * MySensors: await shutdown of gateway instead of creating a task * MySensors: Rename Ethernet to TCP * MySensors: Absolute imports and cosmetic changes * MySensors: fix gw_stop * MySensors: Allow user to specify persistence file * MySensors: Nicer log message * MySensors: Add lots of unit tests * MySensors: Fix legacy import of persistence file name Turns out tests help to find bugs :D * MySensors: Improve test coverage * MySensors: Use json persistence files by default * MySensors: Code style improvements * MySensors: Stop adding attributes to existing objects This commit removes the extra attributes that were being added to the gateway objects from pymysensors. Most attributes were easy to remove, except for the gateway id. The MySensorsDevice class needs the gateway id as it is part of its DevId as well as the unique_id and device_info. Most MySensorsDevices actually end up being Entities. Entities have access to their ConfigEntry via self.platform.config_entry. However, the device_tracker platform does not become an Entity. For this reason, the gateway id is not fetched from self.plaform but given as an argument. Additionally, MySensorsDevices expose the address of the gateway (CONF_DEVICE). Entities can easily fetch this information via self.platform, but the device_tracker cannot. This commit chooses to remove the gateway address from device_tracker. While this could in theory break some automations, the simplicity of this solution was deemed worth it. The alternative of adding the entire ConfigEntry as an argument to MySensorsDevices is not viable, because device_tracker is initialized by the async_setup_scanner function that isn't supplied a ConfigEntry. It only gets discovery_info. Adding the entire ConfigEntry doesn't seem appropriate for this edge case. * MySensors: Fix gw_stop and the translations * MySensors: Fix incorrect function calls * MySensors: Fewer comments in const.py * MySensors: Remove union from _get_gateway and remove id from try_connect * MySensors: Deprecate nodes option in configuration.yaml * MySensors: Use version parser from packaging * MySensors: Remove prefix from unique_id and change some private property names * MySensors: Change _get_gateway function signature * MySensors: add packaging==20.8 for the version parser * MySensors: Rename some stuff * MySensors: use pytest.mark.parametrize * MySensors: Clean up test cases * MySensors: Remove unneeded parameter from devices * Revert "MySensors: add packaging==20.8 for the version parser" This reverts commit 6b200ee01a3c0eee98176380bdd0b73e5a25b2dd. * MySensors: Use core interface for testing configuration.yaml import * MySensors: Fix test_init * MySensors: Rename a few variables * MySensors: cosmetic changes * MySensors: Update strings.json * MySensors: Still more feedback from @MartinHjelmare * MySensors: Remove unused strings Co-authored-by: Martin Hjelmare * MySensors: Fix typo and remove another unused string * MySensors: More strings.json * MySensors: Fix gateway ready handler * MySensors: Add duplicate detection to config flows * MySensors: Deal with non-existing topics and ports. Includes unit tests for these cases. * MySensors: Use awesomeversion instead of packaging * Add string already_configured * MySensors: Abort config flow when config is found to be invalid while importing * MySensors: Copy all error messages to also be abort messages All error strings may now also be used as an abort reason, so the strings should be defined * Use string references Co-authored-by: Martin Hjelmare --- .coveragerc | 15 +- CODEOWNERS | 2 +- .../components/mysensors/__init__.py | 205 ++++- .../components/mysensors/binary_sensor.py | 38 +- homeassistant/components/mysensors/climate.py | 49 +- .../components/mysensors/config_flow.py | 300 +++++++ homeassistant/components/mysensors/const.py | 120 ++- homeassistant/components/mysensors/cover.py | 50 +- homeassistant/components/mysensors/device.py | 130 +++- .../components/mysensors/device_tracker.py | 33 +- homeassistant/components/mysensors/gateway.py | 238 ++++-- homeassistant/components/mysensors/handler.py | 72 +- homeassistant/components/mysensors/helpers.py | 120 ++- homeassistant/components/mysensors/light.py | 55 +- .../components/mysensors/manifest.json | 14 +- homeassistant/components/mysensors/sensor.py | 38 +- .../components/mysensors/strings.json | 79 ++ homeassistant/components/mysensors/switch.py | 54 +- .../components/mysensors/translations/en.json | 79 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/mysensors/__init__.py | 1 + .../components/mysensors/test_config_flow.py | 735 ++++++++++++++++++ tests/components/mysensors/test_gateway.py | 30 + tests/components/mysensors/test_init.py | 251 ++++++ 26 files changed, 2376 insertions(+), 338 deletions(-) create mode 100644 homeassistant/components/mysensors/config_flow.py create mode 100644 homeassistant/components/mysensors/strings.json create mode 100644 homeassistant/components/mysensors/translations/en.json create mode 100644 tests/components/mysensors/__init__.py create mode 100644 tests/components/mysensors/test_config_flow.py create mode 100644 tests/components/mysensors/test_gateway.py create mode 100644 tests/components/mysensors/test_init.py diff --git a/.coveragerc b/.coveragerc index 47d9c84ba0ec1d..3a274cd004f8b4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -578,7 +578,20 @@ omit = homeassistant/components/mychevy/* homeassistant/components/mycroft/* homeassistant/components/mycroft/notify.py - homeassistant/components/mysensors/* + homeassistant/components/mysensors/__init__.py + homeassistant/components/mysensors/binary_sensor.py + homeassistant/components/mysensors/climate.py + homeassistant/components/mysensors/const.py + homeassistant/components/mysensors/cover.py + homeassistant/components/mysensors/device.py + homeassistant/components/mysensors/device_tracker.py + homeassistant/components/mysensors/gateway.py + homeassistant/components/mysensors/handler.py + homeassistant/components/mysensors/helpers.py + homeassistant/components/mysensors/light.py + homeassistant/components/mysensors/notify.py + homeassistant/components/mysensors/sensor.py + homeassistant/components/mysensors/switch.py homeassistant/components/mystrom/binary_sensor.py homeassistant/components/mystrom/light.py homeassistant/components/mystrom/switch.py diff --git a/CODEOWNERS b/CODEOWNERS index efb338dd4b4ccf..8785ce382cb6b1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -288,7 +288,7 @@ homeassistant/components/mpd/* @fabaff homeassistant/components/mqtt/* @home-assistant/core @emontnemery homeassistant/components/msteams/* @peroyvind homeassistant/components/myq/* @bdraco -homeassistant/components/mysensors/* @MartinHjelmare +homeassistant/components/mysensors/* @MartinHjelmare @functionpointer homeassistant/components/mystrom/* @fabaff homeassistant/components/neato/* @dshokouhi @Santobert homeassistant/components/nederlandse_spoorwegen/* @YarmoM diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 43e398b142fd2e..25b4d3106da5c9 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,12 +1,18 @@ """Connect to a MySensors gateway via pymysensors API.""" +import asyncio import logging +from typing import Callable, Dict, List, Optional, Tuple, Type, Union +from mysensors import BaseAsyncGateway import voluptuous as vol +from homeassistant import config_entries from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_OPTIMISTIC from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( ATTR_DEVICES, @@ -23,9 +29,14 @@ CONF_VERSION, DOMAIN, MYSENSORS_GATEWAYS, + MYSENSORS_ON_UNLOAD, + SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, + DevId, + GatewayId, + SensorType, ) -from .device import get_mysensors_devices -from .gateway import finish_setup, get_mysensors_gateway, setup_gateways +from .device import MySensorsDevice, MySensorsEntity, get_mysensors_devices +from .gateway import finish_setup, get_mysensors_gateway, gw_stop, setup_gateway _LOGGER = logging.getLogger(__name__) @@ -81,29 +92,38 @@ def validator(config): NODE_SCHEMA = vol.Schema({cv.positive_int: {vol.Required(CONF_NODE_NAME): cv.string}}) -GATEWAY_SCHEMA = { - vol.Required(CONF_DEVICE): cv.string, - vol.Optional(CONF_PERSISTENCE_FILE): vol.All(cv.string, is_persistence_file), - vol.Optional(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE): cv.positive_int, - vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT): cv.port, - vol.Optional(CONF_TOPIC_IN_PREFIX): valid_subscribe_topic, - vol.Optional(CONF_TOPIC_OUT_PREFIX): valid_publish_topic, - vol.Optional(CONF_NODES, default={}): NODE_SCHEMA, -} +GATEWAY_SCHEMA = vol.Schema( + vol.All( + deprecated(CONF_NODES), + { + vol.Required(CONF_DEVICE): cv.string, + vol.Optional(CONF_PERSISTENCE_FILE): vol.All( + cv.string, is_persistence_file + ), + vol.Optional(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE): cv.positive_int, + vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT): cv.port, + vol.Optional(CONF_TOPIC_IN_PREFIX): valid_subscribe_topic, + vol.Optional(CONF_TOPIC_OUT_PREFIX): valid_publish_topic, + vol.Optional(CONF_NODES, default={}): NODE_SCHEMA, + }, + ) +) CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( vol.All( deprecated(CONF_DEBUG), + deprecated(CONF_OPTIMISTIC), + deprecated(CONF_PERSISTENCE), { vol.Required(CONF_GATEWAYS): vol.All( cv.ensure_list, has_all_unique_files, [GATEWAY_SCHEMA] ), - vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, - vol.Optional(CONF_PERSISTENCE, default=True): cv.boolean, vol.Optional(CONF_RETAIN, default=True): cv.boolean, vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + vol.Optional(CONF_PERSISTENCE, default=True): cv.boolean, }, ) ) @@ -112,69 +132,168 @@ def validator(config): ) -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up the MySensors component.""" - gateways = await setup_gateways(hass, config) + if DOMAIN not in config or bool(hass.config_entries.async_entries(DOMAIN)): + return True + + config = config[DOMAIN] + user_inputs = [ + { + CONF_DEVICE: gw[CONF_DEVICE], + CONF_BAUD_RATE: gw[CONF_BAUD_RATE], + CONF_TCP_PORT: gw[CONF_TCP_PORT], + CONF_TOPIC_OUT_PREFIX: gw.get(CONF_TOPIC_OUT_PREFIX, ""), + CONF_TOPIC_IN_PREFIX: gw.get(CONF_TOPIC_IN_PREFIX, ""), + CONF_RETAIN: config[CONF_RETAIN], + CONF_VERSION: config[CONF_VERSION], + CONF_PERSISTENCE_FILE: gw.get(CONF_PERSISTENCE_FILE) + # nodes config ignored at this time. renaming nodes can now be done from the frontend. + } + for gw in config[CONF_GATEWAYS] + ] + user_inputs = [ + {k: v for k, v in userinput.items() if v is not None} + for userinput in user_inputs + ] + + # there is an actual configuration in configuration.yaml, so we have to process it + for user_input in user_inputs: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=user_input, + ) + ) + + return True - if not gateways: - _LOGGER.error("No devices could be setup as gateways, check your configuration") + +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Set up an instance of the MySensors integration. + + Every instance has a connection to exactly one Gateway. + """ + gateway = await setup_gateway(hass, entry) + + if not gateway: + _LOGGER.error("Gateway setup failed for %s", entry.data) return False - hass.data[MYSENSORS_GATEWAYS] = gateways + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} - hass.async_create_task(finish_setup(hass, config, gateways)) + if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: + hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} + hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway + + async def finish(): + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT + ] + ) + await finish_setup(hass, entry, gateway) + + hass.async_create_task(finish()) return True -def _get_mysensors_name(gateway, node_id, child_id): - """Return a name for a node child.""" - node_name = f"{gateway.sensors[node_id].sketch_name} {node_id}" - node_name = next( - ( - node[CONF_NODE_NAME] - for conf_id, node in gateway.nodes_config.items() - if node.get(CONF_NODE_NAME) is not None and conf_id == node_id - ), - node_name, +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Remove an instance of the MySensors integration.""" + + gateway = get_mysensors_gateway(hass, entry.entry_id) + + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT + ] + ) ) - return f"{node_name} {child_id}" + if not unload_ok: + return False + + key = MYSENSORS_ON_UNLOAD.format(entry.entry_id) + if key in hass.data[DOMAIN]: + for fnct in hass.data[DOMAIN][key]: + fnct() + + del hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] + + await gw_stop(hass, entry, gateway) + return True + + +async def on_unload( + hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable +) -> None: + """Register a callback to be called when entry is unloaded. + + This function is used by platforms to cleanup after themselves + """ + if isinstance(entry, GatewayId): + uniqueid = entry + else: + uniqueid = entry.entry_id + key = MYSENSORS_ON_UNLOAD.format(uniqueid) + if key not in hass.data[DOMAIN]: + hass.data[DOMAIN][key] = [] + hass.data[DOMAIN][key].append(fnct) @callback def setup_mysensors_platform( hass, - domain, - discovery_info, - device_class, - device_args=None, - async_add_entities=None, -): - """Set up a MySensors platform.""" + domain: str, # hass platform name + discovery_info: Optional[Dict[str, List[DevId]]], + device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]], + device_args: Optional[ + Tuple + ] = None, # extra arguments that will be given to the entity constructor + async_add_entities: Callable = None, +) -> Optional[List[MySensorsDevice]]: + """Set up a MySensors platform. + + Sets up a bunch of instances of a single platform that is supported by this integration. + The function is given a list of device ids, each one describing an instance to set up. + The function is also given a class. + A new instance of the class is created for every device id, and the device id is given to the constructor of the class + """ # Only act if called via MySensors by discovery event. # Otherwise gateway is not set up. if not discovery_info: + _LOGGER.debug("Skipping setup due to no discovery info") return None if device_args is None: device_args = () - new_devices = [] - new_dev_ids = discovery_info[ATTR_DEVICES] + new_devices: List[MySensorsDevice] = [] + new_dev_ids: List[DevId] = discovery_info[ATTR_DEVICES] for dev_id in new_dev_ids: - devices = get_mysensors_devices(hass, domain) + devices: Dict[DevId, MySensorsDevice] = get_mysensors_devices(hass, domain) if dev_id in devices: + _LOGGER.debug( + "Skipping setup of %s for platform %s as it already exists", + dev_id, + domain, + ) continue gateway_id, node_id, child_id, value_type = dev_id - gateway = get_mysensors_gateway(hass, gateway_id) + gateway: Optional[BaseAsyncGateway] = get_mysensors_gateway(hass, gateway_id) if not gateway: + _LOGGER.warning("Skipping setup of %s, no gateway found", dev_id) continue device_class_copy = device_class if isinstance(device_class, dict): child = gateway.sensors[node_id].children[child_id] s_type = gateway.const.Presentation(child.type).name device_class_copy = device_class[s_type] - name = _get_mysensors_name(gateway, node_id, child_id) - args_copy = (*device_args, gateway, node_id, child_id, name, value_type) + args_copy = (*device_args, gateway_id, gateway, node_id, child_id, value_type) devices[dev_id] = device_class_copy(*args_copy) new_devices.append(devices[dev_id]) if new_devices: diff --git a/homeassistant/components/mysensors/binary_sensor.py b/homeassistant/components/mysensors/binary_sensor.py index 4ec3c6e0abd65c..c4e12d170c01ec 100644 --- a/homeassistant/components/mysensors/binary_sensor.py +++ b/homeassistant/components/mysensors/binary_sensor.py @@ -1,4 +1,6 @@ """Support for MySensors binary sensors.""" +from typing import Callable + from homeassistant.components import mysensors from homeassistant.components.binary_sensor import ( DEVICE_CLASS_MOISTURE, @@ -10,7 +12,13 @@ DOMAIN, BinarySensorEntity, ) +from homeassistant.components.mysensors import on_unload +from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY +from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType SENSORS = { "S_DOOR": "door", @@ -24,14 +32,30 @@ } -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the mysensors platform for binary sensors.""" - mysensors.setup_mysensors_platform( +async def async_setup_entry( + hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable +): + """Set up this platform for a specific ConfigEntry(==Gateway).""" + + @callback + def async_discover(discovery_info): + """Discover and add a MySensors binary_sensor.""" + mysensors.setup_mysensors_platform( + hass, + DOMAIN, + discovery_info, + MySensorsBinarySensor, + async_add_entities=async_add_entities, + ) + + await on_unload( hass, - DOMAIN, - discovery_info, - MySensorsBinarySensor, - async_add_entities=async_add_entities, + config_entry, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(config_entry.entry_id, DOMAIN), + async_discover, + ), ) diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index c318ccf7ec614f..b1916fc4ed104c 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -1,4 +1,6 @@ """MySensors platform that offers a Climate (MySensors-HVAC) component.""" +from typing import Callable + from homeassistant.components import mysensors from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -13,7 +15,12 @@ SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) +from homeassistant.components.mysensors import on_unload +from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType DICT_HA_TO_MYS = { HVAC_MODE_AUTO: "AutoChangeOver", @@ -32,14 +39,29 @@ OPERATION_LIST = [HVAC_MODE_OFF, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_HEAT] -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the mysensors climate.""" - mysensors.setup_mysensors_platform( +async def async_setup_entry( + hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable +): + """Set up this platform for a specific ConfigEntry(==Gateway).""" + + async def async_discover(discovery_info): + """Discover and add a MySensors climate.""" + mysensors.setup_mysensors_platform( + hass, + DOMAIN, + discovery_info, + MySensorsHVAC, + async_add_entities=async_add_entities, + ) + + await on_unload( hass, - DOMAIN, - discovery_info, - MySensorsHVAC, - async_add_entities=async_add_entities, + config_entry, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(config_entry.entry_id, DOMAIN), + async_discover, + ), ) @@ -62,15 +84,10 @@ def supported_features(self): features = features | SUPPORT_TARGET_TEMPERATURE return features - @property - def assumed_state(self): - """Return True if unable to access real state of entity.""" - return self.gateway.optimistic - @property def temperature_unit(self): """Return the unit of measurement.""" - return TEMP_CELSIUS if self.gateway.metric else TEMP_FAHRENHEIT + return TEMP_CELSIUS if self.hass.config.units.is_metric else TEMP_FAHRENHEIT @property def current_temperature(self): @@ -159,7 +176,7 @@ async def async_set_temperature(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, value_type, value, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that device has changed state self._values[value_type] = value self.async_write_ha_state() @@ -170,7 +187,7 @@ async def async_set_fan_mode(self, fan_mode): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan_mode, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that device has changed state self._values[set_req.V_HVAC_SPEED] = fan_mode self.async_write_ha_state() @@ -184,7 +201,7 @@ async def async_set_hvac_mode(self, hvac_mode): DICT_HA_TO_MYS[hvac_mode], ack=1, ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that device has changed state self._values[self.value_type] = hvac_mode self.async_write_ha_state() diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py new file mode 100644 index 00000000000000..058b782d208eef --- /dev/null +++ b/homeassistant/components/mysensors/config_flow.py @@ -0,0 +1,300 @@ +"""Config flow for MySensors.""" +import logging +import os +from typing import Any, Dict, Optional + +from awesomeversion import ( + AwesomeVersion, + AwesomeVersionStrategy, + AwesomeVersionStrategyException, +) +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic +from homeassistant.components.mysensors import ( + CONF_DEVICE, + DEFAULT_BAUD_RATE, + DEFAULT_TCP_PORT, + is_persistence_file, +) +from homeassistant.config_entries import ConfigEntry +import homeassistant.helpers.config_validation as cv + +from . import CONF_RETAIN, CONF_VERSION, DEFAULT_VERSION + +# pylint: disable=unused-import +from .const import ( + CONF_BAUD_RATE, + CONF_GATEWAY_TYPE, + CONF_GATEWAY_TYPE_ALL, + CONF_GATEWAY_TYPE_MQTT, + CONF_GATEWAY_TYPE_SERIAL, + CONF_GATEWAY_TYPE_TCP, + CONF_PERSISTENCE_FILE, + CONF_TCP_PORT, + CONF_TOPIC_IN_PREFIX, + CONF_TOPIC_OUT_PREFIX, + DOMAIN, + ConfGatewayType, +) +from .gateway import MQTT_COMPONENT, is_serial_port, is_socket_address, try_connect + +_LOGGER = logging.getLogger(__name__) + + +def _get_schema_common() -> dict: + """Create a schema with options common to all gateway types.""" + schema = { + vol.Required( + CONF_VERSION, default="", description={"suggested_value": DEFAULT_VERSION} + ): str, + vol.Optional( + CONF_PERSISTENCE_FILE, + ): str, + } + return schema + + +def _validate_version(version: str) -> Dict[str, str]: + """Validate a version string from the user.""" + version_okay = False + try: + version_okay = bool( + AwesomeVersion.ensure_strategy( + version, + [AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.SEMVER], + ) + ) + except AwesomeVersionStrategyException: + pass + if version_okay: + return {} + return {CONF_VERSION: "invalid_version"} + + +def _is_same_device( + gw_type: ConfGatewayType, user_input: Dict[str, str], entry: ConfigEntry +): + """Check if another ConfigDevice is actually the same as user_input. + + This function only compares addresses and tcp ports, so it is possible to fool it with tricks like port forwarding. + """ + if entry.data[CONF_DEVICE] != user_input[CONF_DEVICE]: + return False + if gw_type == CONF_GATEWAY_TYPE_TCP: + return entry.data[CONF_TCP_PORT] == user_input[CONF_TCP_PORT] + if gw_type == CONF_GATEWAY_TYPE_MQTT: + entry_topics = { + entry.data[CONF_TOPIC_IN_PREFIX], + entry.data[CONF_TOPIC_OUT_PREFIX], + } + return ( + user_input.get(CONF_TOPIC_IN_PREFIX) in entry_topics + or user_input.get(CONF_TOPIC_OUT_PREFIX) in entry_topics + ) + return True + + +class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow.""" + + async def async_step_import(self, user_input: Optional[Dict[str, str]] = None): + """Import a config entry. + + This method is called by async_setup and it has already + prepared the dict to be compatible with what a user would have + entered from the frontend. + Therefore we process it as though it came from the frontend. + """ + if user_input[CONF_DEVICE] == MQTT_COMPONENT: + user_input[CONF_GATEWAY_TYPE] = CONF_GATEWAY_TYPE_MQTT + else: + try: + await self.hass.async_add_executor_job( + is_serial_port, user_input[CONF_DEVICE] + ) + except vol.Invalid: + user_input[CONF_GATEWAY_TYPE] = CONF_GATEWAY_TYPE_TCP + else: + user_input[CONF_GATEWAY_TYPE] = CONF_GATEWAY_TYPE_SERIAL + + result: Dict[str, Any] = await self.async_step_user(user_input=user_input) + if result["type"] == "form": + return self.async_abort(reason=next(iter(result["errors"].values()))) + return result + + async def async_step_user(self, user_input: Optional[Dict[str, str]] = None): + """Create a config entry from frontend user input.""" + schema = {vol.Required(CONF_GATEWAY_TYPE): vol.In(CONF_GATEWAY_TYPE_ALL)} + schema = vol.Schema(schema) + + if user_input is not None: + gw_type = user_input[CONF_GATEWAY_TYPE] + input_pass = user_input if CONF_DEVICE in user_input else None + if gw_type == CONF_GATEWAY_TYPE_MQTT: + return await self.async_step_gw_mqtt(input_pass) + if gw_type == CONF_GATEWAY_TYPE_TCP: + return await self.async_step_gw_tcp(input_pass) + if gw_type == CONF_GATEWAY_TYPE_SERIAL: + return await self.async_step_gw_serial(input_pass) + + return self.async_show_form(step_id="user", data_schema=schema) + + async def async_step_gw_serial(self, user_input: Optional[Dict[str, str]] = None): + """Create config entry for a serial gateway.""" + errors = {} + if user_input is not None: + errors.update( + await self.validate_common(CONF_GATEWAY_TYPE_SERIAL, errors, user_input) + ) + if not errors: + return self.async_create_entry( + title=f"{user_input[CONF_DEVICE]}", data=user_input + ) + + schema = _get_schema_common() + schema[ + vol.Required(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE) + ] = cv.positive_int + schema[vol.Required(CONF_DEVICE, default="/dev/ttyACM0")] = str + + schema = vol.Schema(schema) + return self.async_show_form( + step_id="gw_serial", data_schema=schema, errors=errors + ) + + async def async_step_gw_tcp(self, user_input: Optional[Dict[str, str]] = None): + """Create a config entry for a tcp gateway.""" + errors = {} + if user_input is not None: + if CONF_TCP_PORT in user_input: + port: int = user_input[CONF_TCP_PORT] + if not (0 < port <= 65535): + errors[CONF_TCP_PORT] = "port_out_of_range" + + errors.update( + await self.validate_common(CONF_GATEWAY_TYPE_TCP, errors, user_input) + ) + if not errors: + return self.async_create_entry( + title=f"{user_input[CONF_DEVICE]}", data=user_input + ) + + schema = _get_schema_common() + schema[vol.Required(CONF_DEVICE, default="127.0.0.1")] = str + # Don't use cv.port as that would show a slider *facepalm* + schema[vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT)] = vol.Coerce(int) + + schema = vol.Schema(schema) + return self.async_show_form(step_id="gw_tcp", data_schema=schema, errors=errors) + + def _check_topic_exists(self, topic: str) -> bool: + for other_config in self.hass.config_entries.async_entries(DOMAIN): + if topic == other_config.data.get( + CONF_TOPIC_IN_PREFIX + ) or topic == other_config.data.get(CONF_TOPIC_OUT_PREFIX): + return True + return False + + async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): + """Create a config entry for a mqtt gateway.""" + errors = {} + if user_input is not None: + user_input[CONF_DEVICE] = MQTT_COMPONENT + + try: + valid_subscribe_topic(user_input[CONF_TOPIC_IN_PREFIX]) + except vol.Invalid: + errors[CONF_TOPIC_IN_PREFIX] = "invalid_subscribe_topic" + else: + if self._check_topic_exists(user_input[CONF_TOPIC_IN_PREFIX]): + errors[CONF_TOPIC_IN_PREFIX] = "duplicate_topic" + + try: + valid_publish_topic(user_input[CONF_TOPIC_OUT_PREFIX]) + except vol.Invalid: + errors[CONF_TOPIC_OUT_PREFIX] = "invalid_publish_topic" + if not errors: + if ( + user_input[CONF_TOPIC_IN_PREFIX] + == user_input[CONF_TOPIC_OUT_PREFIX] + ): + errors[CONF_TOPIC_OUT_PREFIX] = "same_topic" + elif self._check_topic_exists(user_input[CONF_TOPIC_OUT_PREFIX]): + errors[CONF_TOPIC_OUT_PREFIX] = "duplicate_topic" + + errors.update( + await self.validate_common(CONF_GATEWAY_TYPE_MQTT, errors, user_input) + ) + if not errors: + return self.async_create_entry( + title=f"{user_input[CONF_DEVICE]}", data=user_input + ) + schema = _get_schema_common() + schema[vol.Required(CONF_RETAIN, default=True)] = bool + schema[vol.Required(CONF_TOPIC_IN_PREFIX)] = str + schema[vol.Required(CONF_TOPIC_OUT_PREFIX)] = str + + schema = vol.Schema(schema) + return self.async_show_form( + step_id="gw_mqtt", data_schema=schema, errors=errors + ) + + def _normalize_persistence_file(self, path: str) -> str: + return os.path.realpath(os.path.normcase(self.hass.config.path(path))) + + async def validate_common( + self, + gw_type: ConfGatewayType, + errors: Dict[str, str], + user_input: Optional[Dict[str, str]] = None, + ) -> Dict[str, str]: + """Validate parameters common to all gateway types.""" + if user_input is not None: + errors.update(_validate_version(user_input.get(CONF_VERSION))) + + if gw_type != CONF_GATEWAY_TYPE_MQTT: + if gw_type == CONF_GATEWAY_TYPE_TCP: + verification_func = is_socket_address + else: + verification_func = is_serial_port + + try: + await self.hass.async_add_executor_job( + verification_func, user_input.get(CONF_DEVICE) + ) + except vol.Invalid: + errors[CONF_DEVICE] = ( + "invalid_ip" + if gw_type == CONF_GATEWAY_TYPE_TCP + else "invalid_serial" + ) + if CONF_PERSISTENCE_FILE in user_input: + try: + is_persistence_file(user_input[CONF_PERSISTENCE_FILE]) + except vol.Invalid: + errors[CONF_PERSISTENCE_FILE] = "invalid_persistence_file" + else: + real_persistence_path = self._normalize_persistence_file( + user_input[CONF_PERSISTENCE_FILE] + ) + for other_entry in self.hass.config_entries.async_entries(DOMAIN): + if CONF_PERSISTENCE_FILE not in other_entry.data: + continue + if real_persistence_path == self._normalize_persistence_file( + other_entry.data[CONF_PERSISTENCE_FILE] + ): + errors[CONF_PERSISTENCE_FILE] = "duplicate_persistence_file" + break + + for other_entry in self.hass.config_entries.async_entries(DOMAIN): + if _is_same_device(gw_type, user_input, other_entry): + errors["base"] = "already_configured" + break + + # if no errors so far, try to connect + if not errors and not await try_connect(self.hass, user_input): + errors["base"] = "cannot_connect" + + return errors diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index ccb646eb47eec6..66bee128d4d27e 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -1,33 +1,69 @@ """MySensors constants.""" from collections import defaultdict - -ATTR_DEVICES = "devices" - -CONF_BAUD_RATE = "baud_rate" -CONF_DEVICE = "device" -CONF_GATEWAYS = "gateways" -CONF_NODES = "nodes" -CONF_PERSISTENCE = "persistence" -CONF_PERSISTENCE_FILE = "persistence_file" -CONF_RETAIN = "retain" -CONF_TCP_PORT = "tcp_port" -CONF_TOPIC_IN_PREFIX = "topic_in_prefix" -CONF_TOPIC_OUT_PREFIX = "topic_out_prefix" -CONF_VERSION = "version" - -DOMAIN = "mysensors" -MYSENSORS_GATEWAY_READY = "mysensors_gateway_ready_{}" -MYSENSORS_GATEWAYS = "mysensors_gateways" -PLATFORM = "platform" -SCHEMA = "schema" -CHILD_CALLBACK = "mysensors_child_callback_{}_{}_{}_{}" -NODE_CALLBACK = "mysensors_node_callback_{}_{}" -TYPE = "type" -UPDATE_DELAY = 0.1 - -SERVICE_SEND_IR_CODE = "send_ir_code" - -BINARY_SENSOR_TYPES = { +from typing import Dict, List, Literal, Set, Tuple + +ATTR_DEVICES: str = "devices" +ATTR_GATEWAY_ID: str = "gateway_id" + +CONF_BAUD_RATE: str = "baud_rate" +CONF_DEVICE: str = "device" +CONF_GATEWAYS: str = "gateways" +CONF_NODES: str = "nodes" +CONF_PERSISTENCE: str = "persistence" +CONF_PERSISTENCE_FILE: str = "persistence_file" +CONF_RETAIN: str = "retain" +CONF_TCP_PORT: str = "tcp_port" +CONF_TOPIC_IN_PREFIX: str = "topic_in_prefix" +CONF_TOPIC_OUT_PREFIX: str = "topic_out_prefix" +CONF_VERSION: str = "version" +CONF_GATEWAY_TYPE: str = "gateway_type" +ConfGatewayType = Literal["Serial", "TCP", "MQTT"] +CONF_GATEWAY_TYPE_SERIAL: ConfGatewayType = "Serial" +CONF_GATEWAY_TYPE_TCP: ConfGatewayType = "TCP" +CONF_GATEWAY_TYPE_MQTT: ConfGatewayType = "MQTT" +CONF_GATEWAY_TYPE_ALL: List[str] = [ + CONF_GATEWAY_TYPE_MQTT, + CONF_GATEWAY_TYPE_SERIAL, + CONF_GATEWAY_TYPE_TCP, +] + + +DOMAIN: str = "mysensors" +MYSENSORS_GATEWAY_READY: str = "mysensors_gateway_ready_{}" +MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}" +MYSENSORS_GATEWAYS: str = "mysensors_gateways" +PLATFORM: str = "platform" +SCHEMA: str = "schema" +CHILD_CALLBACK: str = "mysensors_child_callback_{}_{}_{}_{}" +NODE_CALLBACK: str = "mysensors_node_callback_{}_{}" +MYSENSORS_DISCOVERY = "mysensors_discovery_{}_{}" +MYSENSORS_ON_UNLOAD = "mysensors_on_unload_{}" +TYPE: str = "type" +UPDATE_DELAY: float = 0.1 + +SERVICE_SEND_IR_CODE: str = "send_ir_code" + +SensorType = str +# S_DOOR, S_MOTION, S_SMOKE, ... + +ValueType = str +# V_TRIPPED, V_ARMED, V_STATUS, V_PERCENTAGE, ... + +GatewayId = str +# a unique id generated by config_flow.py and stored in the ConfigEntry as the entry id. +# +# Gateway may be fetched by giving the gateway id to get_mysensors_gateway() + +DevId = Tuple[GatewayId, int, int, int] +# describes the backend of a hass entity. Contents are: GatewayId, node_id, child_id, v_type as int +# +# The string version of v_type can be looked up in the enum gateway.const.SetReq of the appropriate BaseAsyncGateway +# Home Assistant Entities are quite limited and only ever do one thing. +# MySensors Nodes have multiple child_ids each with a s_type several associated v_types +# The MySensors integration brings these together by creating an entity for every v_type of every child_id of every node. +# The DevId tuple perfectly captures this. + +BINARY_SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { "S_DOOR": {"V_TRIPPED"}, "S_MOTION": {"V_TRIPPED"}, "S_SMOKE": {"V_TRIPPED"}, @@ -38,21 +74,23 @@ "S_MOISTURE": {"V_TRIPPED"}, } -CLIMATE_TYPES = {"S_HVAC": {"V_HVAC_FLOW_STATE"}} +CLIMATE_TYPES: Dict[SensorType, Set[ValueType]] = {"S_HVAC": {"V_HVAC_FLOW_STATE"}} -COVER_TYPES = {"S_COVER": {"V_DIMMER", "V_PERCENTAGE", "V_LIGHT", "V_STATUS"}} +COVER_TYPES: Dict[SensorType, Set[ValueType]] = { + "S_COVER": {"V_DIMMER", "V_PERCENTAGE", "V_LIGHT", "V_STATUS"} +} -DEVICE_TRACKER_TYPES = {"S_GPS": {"V_POSITION"}} +DEVICE_TRACKER_TYPES: Dict[SensorType, Set[ValueType]] = {"S_GPS": {"V_POSITION"}} -LIGHT_TYPES = { +LIGHT_TYPES: Dict[SensorType, Set[ValueType]] = { "S_DIMMER": {"V_DIMMER", "V_PERCENTAGE"}, "S_RGB_LIGHT": {"V_RGB"}, "S_RGBW_LIGHT": {"V_RGBW"}, } -NOTIFY_TYPES = {"S_INFO": {"V_TEXT"}} +NOTIFY_TYPES: Dict[SensorType, Set[ValueType]] = {"S_INFO": {"V_TEXT"}} -SENSOR_TYPES = { +SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { "S_SOUND": {"V_LEVEL"}, "S_VIBRATION": {"V_LEVEL"}, "S_MOISTURE": {"V_LEVEL"}, @@ -80,7 +118,7 @@ "S_DUST": {"V_DUST_LEVEL", "V_LEVEL"}, } -SWITCH_TYPES = { +SWITCH_TYPES: Dict[SensorType, Set[ValueType]] = { "S_LIGHT": {"V_LIGHT"}, "S_BINARY": {"V_STATUS"}, "S_DOOR": {"V_ARMED"}, @@ -97,7 +135,7 @@ } -PLATFORM_TYPES = { +PLATFORM_TYPES: Dict[str, Dict[SensorType, Set[ValueType]]] = { "binary_sensor": BINARY_SENSOR_TYPES, "climate": CLIMATE_TYPES, "cover": COVER_TYPES, @@ -108,13 +146,19 @@ "switch": SWITCH_TYPES, } -FLAT_PLATFORM_TYPES = { +FLAT_PLATFORM_TYPES: Dict[Tuple[str, SensorType], Set[ValueType]] = { (platform, s_type_name): v_type_name for platform, platform_types in PLATFORM_TYPES.items() for s_type_name, v_type_name in platform_types.items() } -TYPE_TO_PLATFORMS = defaultdict(list) +TYPE_TO_PLATFORMS: Dict[SensorType, List[str]] = defaultdict(list) + for platform, platform_types in PLATFORM_TYPES.items(): for s_type_name in platform_types: TYPE_TO_PLATFORMS[s_type_name].append(platform) + +SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT = set(PLATFORM_TYPES.keys()) - { + "notify", + "device_tracker", +} diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index f2ede69793fde0..782ab88c48899e 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -1,28 +1,48 @@ """Support for MySensors covers.""" +import logging +from typing import Callable + from homeassistant.components import mysensors from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverEntity +from homeassistant.components.mysensors import on_unload +from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY +from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType + +_LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the mysensors platform for covers.""" - mysensors.setup_mysensors_platform( +async def async_setup_entry( + hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable +): + """Set up this platform for a specific ConfigEntry(==Gateway).""" + + async def async_discover(discovery_info): + """Discover and add a MySensors cover.""" + mysensors.setup_mysensors_platform( + hass, + DOMAIN, + discovery_info, + MySensorsCover, + async_add_entities=async_add_entities, + ) + + await on_unload( hass, - DOMAIN, - discovery_info, - MySensorsCover, - async_add_entities=async_add_entities, + config_entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(config_entry.entry_id, DOMAIN), + async_discover, + ), ) class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity): """Representation of the value of a MySensors Cover child node.""" - @property - def assumed_state(self): - """Return True if unable to access real state of entity.""" - return self.gateway.optimistic - @property def is_closed(self): """Return True if cover is closed.""" @@ -46,7 +66,7 @@ async def async_open_cover(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_UP, 1, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that cover has changed state. if set_req.V_DIMMER in self._values: self._values[set_req.V_DIMMER] = 100 @@ -60,7 +80,7 @@ async def async_close_cover(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_DOWN, 1, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that cover has changed state. if set_req.V_DIMMER in self._values: self._values[set_req.V_DIMMER] = 0 @@ -75,7 +95,7 @@ async def async_set_cover_position(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_DIMMER, position, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that cover has changed state. self._values[set_req.V_DIMMER] = position self.async_write_ha_state() diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 9c1c4b54367646..6841486734520f 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -1,13 +1,26 @@ """Handle MySensors devices.""" from functools import partial import logging +from typing import Any, Dict, Optional + +from mysensors import BaseAsyncGateway, Sensor +from mysensors.sensor import ChildSensor from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from .const import CHILD_CALLBACK, NODE_CALLBACK, UPDATE_DELAY +from .const import ( + CHILD_CALLBACK, + CONF_DEVICE, + DOMAIN, + NODE_CALLBACK, + PLATFORM_TYPES, + UPDATE_DELAY, + DevId, + GatewayId, +) _LOGGER = logging.getLogger(__name__) @@ -19,33 +32,94 @@ MYSENSORS_PLATFORM_DEVICES = "mysensors_devices_{}" -def get_mysensors_devices(hass, domain): - """Return MySensors devices for a platform.""" - if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data: - hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] = {} - return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] - - class MySensorsDevice: """Representation of a MySensors device.""" - def __init__(self, gateway, node_id, child_id, name, value_type): + def __init__( + self, + gateway_id: GatewayId, + gateway: BaseAsyncGateway, + node_id: int, + child_id: int, + value_type: int, + ): """Set up the MySensors device.""" - self.gateway = gateway - self.node_id = node_id - self.child_id = child_id - self._name = name - self.value_type = value_type - child = gateway.sensors[node_id].children[child_id] - self.child_type = child.type + self.gateway_id: GatewayId = gateway_id + self.gateway: BaseAsyncGateway = gateway + self.node_id: int = node_id + self.child_id: int = child_id + self.value_type: int = value_type # value_type as int. string variant can be looked up in gateway consts + self.child_type = self._child.type self._values = {} self._update_scheduled = False self.hass = None + @property + def dev_id(self) -> DevId: + """Return the DevId of this device. + + It is used to route incoming MySensors messages to the correct device/entity. + """ + return self.gateway_id, self.node_id, self.child_id, self.value_type + + @property + def _logger(self): + return logging.getLogger(f"{__name__}.{self.name}") + + async def async_will_remove_from_hass(self): + """Remove this entity from home assistant.""" + for platform in PLATFORM_TYPES: + platform_str = MYSENSORS_PLATFORM_DEVICES.format(platform) + if platform_str in self.hass.data[DOMAIN]: + 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 + ) + + @property + def _node(self) -> Sensor: + return self.gateway.sensors[self.node_id] + + @property + def _child(self) -> ChildSensor: + return self._node.children[self.child_id] + + @property + def sketch_name(self) -> str: + """Return the name of the sketch running on the whole node (will be the same for several entities!).""" + return self._node.sketch_name + + @property + def sketch_version(self) -> str: + """Return the version of the sketch running on the whole node (will be the same for several entities!).""" + return self._node.sketch_version + + @property + def node_name(self) -> str: + """Name of the whole node (will be the same for several entities!).""" + return f"{self.sketch_name} {self.node_id}" + + @property + def unique_id(self) -> str: + """Return a unique ID for use in home assistant.""" + return f"{self.gateway_id}-{self.node_id}-{self.child_id}-{self.value_type}" + + @property + def device_info(self) -> Optional[Dict[str, Any]]: + """Return a dict that allows home assistant to puzzle all entities belonging to a node together.""" + return { + "identifiers": {(DOMAIN, f"{self.gateway_id}-{self.node_id}")}, + "name": self.node_name, + "manufacturer": DOMAIN, + "sw_version": self.sketch_version, + } + @property def name(self): """Return the name of this entity.""" - return self._name + return f"{self.node_name} {self.child_id}" @property def device_state_attributes(self): @@ -57,9 +131,12 @@ def device_state_attributes(self): ATTR_HEARTBEAT: node.heartbeat, ATTR_CHILD_ID: self.child_id, ATTR_DESCRIPTION: child.description, - ATTR_DEVICE: self.gateway.device, ATTR_NODE_ID: self.node_id, } + # This works when we are actually an Entity (i.e. all platforms except device_tracker) + if hasattr(self, "platform"): + # pylint: disable=no-member + attr[ATTR_DEVICE] = self.platform.config_entry.data[CONF_DEVICE] set_req = self.gateway.const.SetReq @@ -76,7 +153,7 @@ async def async_update(self): for value_type, value in child.values.items(): _LOGGER.debug( "Entity update: %s: value_type %s, value = %s", - self._name, + self.name, value_type, value, ) @@ -116,6 +193,13 @@ async def update(): self.hass.loop.call_later(UPDATE_DELAY, delayed_update) +def get_mysensors_devices(hass, domain: str) -> Dict[DevId, MySensorsDevice]: + """Return MySensors devices for a hass platform name.""" + if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data[DOMAIN]: + hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] = {} + return hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] + + class MySensorsEntity(MySensorsDevice, Entity): """Representation of a MySensors entity.""" @@ -135,17 +219,17 @@ async def _async_update_callback(self): async def async_added_to_hass(self): """Register update callback.""" - gateway_id = id(self.gateway) - dev_id = gateway_id, self.node_id, self.child_id, self.value_type self.async_on_remove( async_dispatcher_connect( - self.hass, CHILD_CALLBACK.format(*dev_id), self.async_update_callback + self.hass, + CHILD_CALLBACK.format(*self.dev_id), + self.async_update_callback, ) ) self.async_on_remove( async_dispatcher_connect( self.hass, - NODE_CALLBACK.format(gateway_id, self.node_id), + NODE_CALLBACK.format(self.gateway_id, self.node_id), self.async_update_callback, ) ) diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index 1bf1e072cebfd2..b395a48f28b7da 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -1,11 +1,16 @@ """Support for tracking MySensors devices.""" from homeassistant.components import mysensors from homeassistant.components.device_tracker import DOMAIN +from homeassistant.components.mysensors import DevId, on_unload +from homeassistant.components.mysensors.const import ATTR_GATEWAY_ID, GatewayId from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import slugify -async def async_setup_scanner(hass, config, async_see, discovery_info=None): +async def async_setup_scanner( + hass: HomeAssistantType, config, async_see, discovery_info=None +): """Set up the MySensors device scanner.""" new_devices = mysensors.setup_mysensors_platform( hass, @@ -18,17 +23,25 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): return False for device in new_devices: - gateway_id = id(device.gateway) - dev_id = (gateway_id, device.node_id, device.child_id, device.value_type) - async_dispatcher_connect( + gateway_id: GatewayId = discovery_info[ATTR_GATEWAY_ID] + dev_id: DevId = (gateway_id, device.node_id, device.child_id, device.value_type) + await on_unload( hass, - mysensors.const.CHILD_CALLBACK.format(*dev_id), - device.async_update_callback, + gateway_id, + async_dispatcher_connect( + hass, + mysensors.const.CHILD_CALLBACK.format(*dev_id), + device.async_update_callback, + ), ) - async_dispatcher_connect( + await on_unload( hass, - mysensors.const.NODE_CALLBACK.format(gateway_id, device.node_id), - device.async_update_callback, + gateway_id, + async_dispatcher_connect( + hass, + mysensors.const.NODE_CALLBACK.format(gateway_id, device.node_id), + device.async_update_callback, + ), ) return True @@ -37,7 +50,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): class MySensorsDeviceScanner(mysensors.device.MySensorsDevice): """Represent a MySensors scanner.""" - def __init__(self, hass, async_see, *args): + def __init__(self, hass: HomeAssistantType, async_see, *args): """Set up instance.""" super().__init__(*args) self.async_see = async_see diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index f9450b798ace1e..b618004b62222d 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -4,22 +4,21 @@ import logging import socket import sys +from typing import Any, Callable, Coroutine, Dict, Optional import async_timeout -from mysensors import mysensors +from mysensors import BaseAsyncGateway, Message, Sensor, mysensors import voluptuous as vol -from homeassistant.const import CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Event, callback import homeassistant.helpers.config_validation as cv -from homeassistant.setup import async_setup_component +from homeassistant.helpers.typing import HomeAssistantType from .const import ( CONF_BAUD_RATE, CONF_DEVICE, - CONF_GATEWAYS, - CONF_NODES, - CONF_PERSISTENCE, CONF_PERSISTENCE_FILE, CONF_RETAIN, CONF_TCP_PORT, @@ -28,7 +27,9 @@ CONF_VERSION, DOMAIN, MYSENSORS_GATEWAY_READY, + MYSENSORS_GATEWAY_START_TASK, MYSENSORS_GATEWAYS, + GatewayId, ) from .handler import HANDLERS from .helpers import discover_mysensors_platform, validate_child, validate_node @@ -58,48 +59,114 @@ def is_socket_address(value): raise vol.Invalid("Device is not a valid domain name or ip address") from err -def get_mysensors_gateway(hass, gateway_id): - """Return MySensors gateway.""" - if MYSENSORS_GATEWAYS not in hass.data: - hass.data[MYSENSORS_GATEWAYS] = {} - gateways = hass.data.get(MYSENSORS_GATEWAYS) - return gateways.get(gateway_id) - +async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bool: + """Try to connect to a gateway and report if it worked.""" + if user_input[CONF_DEVICE] == MQTT_COMPONENT: + return True # dont validate mqtt. mqtt gateways dont send ready messages :( + try: + gateway_ready = asyncio.Future() + + def gateway_ready_callback(msg): + msg_type = msg.gateway.const.MessageType(msg.type) + _LOGGER.debug("Received MySensors msg type %s: %s", msg_type.name, msg) + if msg_type.name != "internal": + return + internal = msg.gateway.const.Internal(msg.sub_type) + if internal.name != "I_GATEWAY_READY": + return + _LOGGER.debug("Received gateway ready") + gateway_ready.set_result(True) + + gateway: Optional[BaseAsyncGateway] = await _get_gateway( + hass, + device=user_input[CONF_DEVICE], + version=user_input[CONF_VERSION], + event_callback=gateway_ready_callback, + persistence_file=None, + baud_rate=user_input.get(CONF_BAUD_RATE), + tcp_port=user_input.get(CONF_TCP_PORT), + topic_in_prefix=None, + topic_out_prefix=None, + retain=False, + persistence=False, + ) + if gateway is None: + return False -async def setup_gateways(hass, config): - """Set up all gateways.""" - conf = config[DOMAIN] - gateways = {} + connect_task = None + try: + connect_task = asyncio.create_task(gateway.start()) + with async_timeout.timeout(5): + await gateway_ready + return True + except asyncio.TimeoutError: + _LOGGER.info("Try gateway connect failed with timeout") + return False + finally: + if connect_task is not None and not connect_task.done(): + connect_task.cancel() + asyncio.create_task(gateway.stop()) + except OSError as err: + _LOGGER.info("Try gateway connect failed with exception", exc_info=err) + return False - for index, gateway_conf in enumerate(conf[CONF_GATEWAYS]): - persistence_file = gateway_conf.get( - CONF_PERSISTENCE_FILE, - hass.config.path(f"mysensors{index + 1}.pickle"), - ) - ready_gateway = await _get_gateway(hass, config, gateway_conf, persistence_file) - if ready_gateway is not None: - gateways[id(ready_gateway)] = ready_gateway - return gateways +def get_mysensors_gateway( + hass: HomeAssistantType, gateway_id: GatewayId +) -> Optional[BaseAsyncGateway]: + """Return the Gateway for a given GatewayId.""" + if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: + hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} + gateways = hass.data[DOMAIN].get(MYSENSORS_GATEWAYS) + return gateways.get(gateway_id) -async def _get_gateway(hass, config, gateway_conf, persistence_file): +async def setup_gateway( + hass: HomeAssistantType, entry: ConfigEntry +) -> Optional[BaseAsyncGateway]: + """Set up the Gateway for the given ConfigEntry.""" + + ready_gateway = await _get_gateway( + hass, + device=entry.data[CONF_DEVICE], + version=entry.data[CONF_VERSION], + event_callback=_gw_callback_factory(hass, entry.entry_id), + persistence_file=entry.data.get( + CONF_PERSISTENCE_FILE, f"mysensors_{entry.entry_id}.json" + ), + baud_rate=entry.data.get(CONF_BAUD_RATE), + tcp_port=entry.data.get(CONF_TCP_PORT), + topic_in_prefix=entry.data.get(CONF_TOPIC_IN_PREFIX), + topic_out_prefix=entry.data.get(CONF_TOPIC_OUT_PREFIX), + retain=entry.data.get(CONF_RETAIN, False), + ) + return ready_gateway + + +async def _get_gateway( + hass: HomeAssistantType, + device: str, + version: str, + event_callback: Callable[[Message], None], + persistence_file: Optional[str] = None, + baud_rate: Optional[int] = None, + tcp_port: Optional[int] = None, + topic_in_prefix: Optional[str] = None, + topic_out_prefix: Optional[str] = None, + retain: bool = False, + persistence: bool = True, # old persistence option has been deprecated. kwarg is here so we can run try_connect() without persistence +) -> Optional[BaseAsyncGateway]: """Return gateway after setup of the gateway.""" - conf = config[DOMAIN] - persistence = conf[CONF_PERSISTENCE] - version = conf[CONF_VERSION] - device = gateway_conf[CONF_DEVICE] - baud_rate = gateway_conf[CONF_BAUD_RATE] - tcp_port = gateway_conf[CONF_TCP_PORT] - in_prefix = gateway_conf.get(CONF_TOPIC_IN_PREFIX, "") - out_prefix = gateway_conf.get(CONF_TOPIC_OUT_PREFIX, "") + if persistence_file is not None: + # interpret relative paths to be in hass config folder. absolute paths will be left as they are + persistence_file = hass.config.path(persistence_file) if device == MQTT_COMPONENT: - if not await async_setup_component(hass, MQTT_COMPONENT, config): - return None + # what is the purpose of this? + # if not await async_setup_component(hass, MQTT_COMPONENT, entry): + # return None mqtt = hass.components.mqtt - retain = conf[CONF_RETAIN] def pub_callback(topic, payload, qos, retain): """Call MQTT publish function.""" @@ -118,8 +185,8 @@ def internal_callback(msg): gateway = mysensors.AsyncMQTTGateway( pub_callback, sub_callback, - in_prefix=in_prefix, - out_prefix=out_prefix, + in_prefix=topic_in_prefix, + out_prefix=topic_out_prefix, retain=retain, loop=hass.loop, event_callback=None, @@ -154,25 +221,23 @@ def internal_callback(msg): ) except vol.Invalid: # invalid ip address + _LOGGER.error("Connect failed: Invalid device %s", device) return None - gateway.metric = hass.config.units.is_metric - gateway.optimistic = conf[CONF_OPTIMISTIC] - gateway.device = device - gateway.event_callback = _gw_callback_factory(hass, config) - gateway.nodes_config = gateway_conf[CONF_NODES] + gateway.event_callback = event_callback if persistence: await gateway.start_persistence() return gateway -async def finish_setup(hass, hass_config, gateways): +async def finish_setup( + hass: HomeAssistantType, entry: ConfigEntry, gateway: BaseAsyncGateway +): """Load any persistent devices and platforms and start gateway.""" discover_tasks = [] start_tasks = [] - for gateway in gateways.values(): - discover_tasks.append(_discover_persistent_devices(hass, hass_config, gateway)) - start_tasks.append(_gw_start(hass, gateway)) + discover_tasks.append(_discover_persistent_devices(hass, entry, gateway)) + start_tasks.append(_gw_start(hass, entry, gateway)) if discover_tasks: # Make sure all devices and platforms are loaded before gateway start. await asyncio.wait(discover_tasks) @@ -180,43 +245,58 @@ async def finish_setup(hass, hass_config, gateways): await asyncio.wait(start_tasks) -async def _discover_persistent_devices(hass, hass_config, gateway): +async def _discover_persistent_devices( + hass: HomeAssistantType, entry: ConfigEntry, gateway: BaseAsyncGateway +): """Discover platforms for devices loaded via persistence file.""" tasks = [] new_devices = defaultdict(list) for node_id in gateway.sensors: if not validate_node(gateway, node_id): continue - node = gateway.sensors[node_id] - for child in node.children.values(): - validated = validate_child(gateway, node_id, child) + node: Sensor = gateway.sensors[node_id] + for child in node.children.values(): # child is of type ChildSensor + validated = validate_child(entry.entry_id, gateway, node_id, child) for platform, dev_ids in validated.items(): new_devices[platform].extend(dev_ids) + _LOGGER.debug("discovering persistent devices: %s", new_devices) for platform, dev_ids in new_devices.items(): - tasks.append(discover_mysensors_platform(hass, hass_config, platform, dev_ids)) + discover_mysensors_platform(hass, entry.entry_id, platform, dev_ids) if tasks: await asyncio.wait(tasks) -async def _gw_start(hass, gateway): +async def gw_stop(hass, entry: ConfigEntry, gateway: BaseAsyncGateway): + """Stop the gateway.""" + connect_task = hass.data[DOMAIN].get( + MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) + ) + if connect_task is not None and not connect_task.done(): + connect_task.cancel() + await gateway.stop() + + +async def _gw_start( + hass: HomeAssistantType, entry: ConfigEntry, gateway: BaseAsyncGateway +): """Start the gateway.""" # Don't use hass.async_create_task to avoid holding up setup indefinitely. - connect_task = hass.loop.create_task(gateway.start()) + hass.data[DOMAIN][ + MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) + ] = asyncio.create_task( + gateway.start() + ) # store the connect task so it can be cancelled in gw_stop - @callback - def gw_stop(event): - """Trigger to stop the gateway.""" - hass.async_create_task(gateway.stop()) - if not connect_task.done(): - connect_task.cancel() - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gw_stop) - if gateway.device == "mqtt": + async def stop_this_gw(_: Event): + await gw_stop(hass, entry, gateway) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw) + if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return gateway_ready = asyncio.Future() - gateway_ready_key = MYSENSORS_GATEWAY_READY.format(id(gateway)) - hass.data[gateway_ready_key] = gateway_ready + gateway_ready_key = MYSENSORS_GATEWAY_READY.format(entry.entry_id) + hass.data[DOMAIN][gateway_ready_key] = gateway_ready try: with async_timeout.timeout(GATEWAY_READY_TIMEOUT): @@ -224,27 +304,35 @@ def gw_stop(event): except asyncio.TimeoutError: _LOGGER.warning( "Gateway %s not ready after %s secs so continuing with setup", - gateway.device, + entry.data[CONF_DEVICE], GATEWAY_READY_TIMEOUT, ) finally: - hass.data.pop(gateway_ready_key, None) + hass.data[DOMAIN].pop(gateway_ready_key, None) -def _gw_callback_factory(hass, hass_config): +def _gw_callback_factory( + hass: HomeAssistantType, gateway_id: GatewayId +) -> Callable[[Message], None]: """Return a new callback for the gateway.""" @callback - def mysensors_callback(msg): - """Handle messages from a MySensors gateway.""" + def mysensors_callback(msg: Message): + """Handle messages from a MySensors gateway. + + All MySenors messages are received here. + The messages are passed to handler functions depending on their type. + """ _LOGGER.debug("Node update: node %s child %s", msg.node_id, msg.child_id) msg_type = msg.gateway.const.MessageType(msg.type) - msg_handler = HANDLERS.get(msg_type.name) + msg_handler: Callable[ + [Any, GatewayId, Message], Coroutine[None] + ] = HANDLERS.get(msg_type.name) if msg_handler is None: return - hass.async_create_task(msg_handler(hass, hass_config, msg)) + hass.async_create_task(msg_handler(hass, gateway_id, msg)) return mysensors_callback diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index b5b8b511aee110..10165a171e0a03 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -1,9 +1,21 @@ """Handle MySensors messages.""" +from typing import Dict, List + +from mysensors import Message + from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import decorator -from .const import CHILD_CALLBACK, MYSENSORS_GATEWAY_READY, NODE_CALLBACK +from .const import ( + CHILD_CALLBACK, + DOMAIN, + MYSENSORS_GATEWAY_READY, + NODE_CALLBACK, + DevId, + GatewayId, +) from .device import get_mysensors_devices from .helpers import discover_mysensors_platform, validate_set_msg @@ -11,75 +23,91 @@ @HANDLERS.register("set") -async def handle_set(hass, hass_config, msg): +async def handle_set( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle a mysensors set message.""" - validated = validate_set_msg(msg) - _handle_child_update(hass, hass_config, validated) + validated = validate_set_msg(gateway_id, msg) + _handle_child_update(hass, gateway_id, validated) @HANDLERS.register("internal") -async def handle_internal(hass, hass_config, msg): +async def handle_internal( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle a mysensors internal message.""" internal = msg.gateway.const.Internal(msg.sub_type) handler = HANDLERS.get(internal.name) if handler is None: return - await handler(hass, hass_config, msg) + await handler(hass, gateway_id, msg) @HANDLERS.register("I_BATTERY_LEVEL") -async def handle_battery_level(hass, hass_config, msg): +async def handle_battery_level( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle an internal battery level message.""" - _handle_node_update(hass, msg) + _handle_node_update(hass, gateway_id, msg) @HANDLERS.register("I_HEARTBEAT_RESPONSE") -async def handle_heartbeat(hass, hass_config, msg): +async def handle_heartbeat( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle an heartbeat.""" - _handle_node_update(hass, msg) + _handle_node_update(hass, gateway_id, msg) @HANDLERS.register("I_SKETCH_NAME") -async def handle_sketch_name(hass, hass_config, msg): +async def handle_sketch_name( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle an internal sketch name message.""" - _handle_node_update(hass, msg) + _handle_node_update(hass, gateway_id, msg) @HANDLERS.register("I_SKETCH_VERSION") -async def handle_sketch_version(hass, hass_config, msg): +async def handle_sketch_version( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle an internal sketch version message.""" - _handle_node_update(hass, msg) + _handle_node_update(hass, gateway_id, msg) @HANDLERS.register("I_GATEWAY_READY") -async def handle_gateway_ready(hass, hass_config, msg): +async def handle_gateway_ready( + hass: HomeAssistantType, gateway_id: GatewayId, msg: Message +) -> None: """Handle an internal gateway ready message. Set asyncio future result if gateway is ready. """ - gateway_ready = hass.data.get(MYSENSORS_GATEWAY_READY.format(id(msg.gateway))) + gateway_ready = hass.data[DOMAIN].get(MYSENSORS_GATEWAY_READY.format(gateway_id)) if gateway_ready is None or gateway_ready.cancelled(): return gateway_ready.set_result(True) @callback -def _handle_child_update(hass, hass_config, validated): +def _handle_child_update( + hass: HomeAssistantType, gateway_id: GatewayId, validated: Dict[str, List[DevId]] +): """Handle a child update.""" - signals = [] + signals: List[str] = [] # Update all platforms for the device via dispatcher. # Add/update entity for validated children. for platform, dev_ids in validated.items(): devices = get_mysensors_devices(hass, platform) - new_dev_ids = [] + new_dev_ids: List[DevId] = [] for dev_id in dev_ids: if dev_id in devices: signals.append(CHILD_CALLBACK.format(*dev_id)) else: new_dev_ids.append(dev_id) if new_dev_ids: - discover_mysensors_platform(hass, hass_config, platform, new_dev_ids) + discover_mysensors_platform(hass, gateway_id, platform, new_dev_ids) for signal in set(signals): # Only one signal per device is needed. # A device can have multiple platforms, ie multiple schemas. @@ -87,7 +115,7 @@ def _handle_child_update(hass, hass_config, validated): @callback -def _handle_node_update(hass, msg): +def _handle_node_update(hass: HomeAssistantType, gateway_id: GatewayId, msg: Message): """Handle a node update.""" - signal = NODE_CALLBACK.format(id(msg.gateway), msg.node_id) + signal = NODE_CALLBACK.format(gateway_id, msg.node_id) async_dispatcher_send(hass, signal) diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 20b266e550e619..d06bf0dee2fe57 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -1,78 +1,109 @@ """Helper functions for mysensors package.""" from collections import defaultdict +from enum import IntEnum import logging +from typing import DefaultDict, Dict, List, Optional, Set +from mysensors import BaseAsyncGateway, Message +from mysensors.sensor import ChildSensor import voluptuous as vol from homeassistant.const import CONF_NAME from homeassistant.core import callback -from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util.decorator import Registry -from .const import ATTR_DEVICES, DOMAIN, FLAT_PLATFORM_TYPES, TYPE_TO_PLATFORMS +from .const import ( + ATTR_DEVICES, + ATTR_GATEWAY_ID, + DOMAIN, + FLAT_PLATFORM_TYPES, + MYSENSORS_DISCOVERY, + TYPE_TO_PLATFORMS, + DevId, + GatewayId, + SensorType, + ValueType, +) _LOGGER = logging.getLogger(__name__) SCHEMAS = Registry() @callback -def discover_mysensors_platform(hass, hass_config, platform, new_devices): +def discover_mysensors_platform( + hass, gateway_id: GatewayId, platform: str, new_devices: List[DevId] +) -> None: """Discover a MySensors platform.""" - task = hass.async_create_task( - discovery.async_load_platform( - hass, - platform, - DOMAIN, - {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN}, - hass_config, - ) + _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices) + async_dispatcher_send( + hass, + MYSENSORS_DISCOVERY.format(gateway_id, platform), + { + ATTR_DEVICES: new_devices, + CONF_NAME: DOMAIN, + ATTR_GATEWAY_ID: gateway_id, + }, ) - return task -def default_schema(gateway, child, value_type_name): +def default_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +) -> vol.Schema: """Return a default validation schema for value types.""" schema = {value_type_name: cv.string} return get_child_schema(gateway, child, value_type_name, schema) @SCHEMAS.register(("light", "V_DIMMER")) -def light_dimmer_schema(gateway, child, value_type_name): +def light_dimmer_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +) -> vol.Schema: """Return a validation schema for V_DIMMER.""" schema = {"V_DIMMER": cv.string, "V_LIGHT": cv.string} return get_child_schema(gateway, child, value_type_name, schema) @SCHEMAS.register(("light", "V_PERCENTAGE")) -def light_percentage_schema(gateway, child, value_type_name): +def light_percentage_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +) -> vol.Schema: """Return a validation schema for V_PERCENTAGE.""" schema = {"V_PERCENTAGE": cv.string, "V_STATUS": cv.string} return get_child_schema(gateway, child, value_type_name, schema) @SCHEMAS.register(("light", "V_RGB")) -def light_rgb_schema(gateway, child, value_type_name): +def light_rgb_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +) -> vol.Schema: """Return a validation schema for V_RGB.""" schema = {"V_RGB": cv.string, "V_STATUS": cv.string} return get_child_schema(gateway, child, value_type_name, schema) @SCHEMAS.register(("light", "V_RGBW")) -def light_rgbw_schema(gateway, child, value_type_name): +def light_rgbw_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +) -> vol.Schema: """Return a validation schema for V_RGBW.""" schema = {"V_RGBW": cv.string, "V_STATUS": cv.string} return get_child_schema(gateway, child, value_type_name, schema) @SCHEMAS.register(("switch", "V_IR_SEND")) -def switch_ir_send_schema(gateway, child, value_type_name): +def switch_ir_send_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +) -> vol.Schema: """Return a validation schema for V_IR_SEND.""" schema = {"V_IR_SEND": cv.string, "V_LIGHT": cv.string} return get_child_schema(gateway, child, value_type_name, schema) -def get_child_schema(gateway, child, value_type_name, schema): +def get_child_schema( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType, schema +) -> vol.Schema: """Return a child schema.""" set_req = gateway.const.SetReq child_schema = child.get_schema(gateway.protocol_version) @@ -88,7 +119,9 @@ def get_child_schema(gateway, child, value_type_name, schema): return schema -def invalid_msg(gateway, child, value_type_name): +def invalid_msg( + gateway: BaseAsyncGateway, child: ChildSensor, value_type_name: ValueType +): """Return a message for an invalid child during schema validation.""" pres = gateway.const.Presentation set_req = gateway.const.SetReq @@ -97,15 +130,15 @@ def invalid_msg(gateway, child, value_type_name): ) -def validate_set_msg(msg): +def validate_set_msg(gateway_id: GatewayId, msg: Message) -> Dict[str, List[DevId]]: """Validate a set message.""" if not validate_node(msg.gateway, msg.node_id): return {} child = msg.gateway.sensors[msg.node_id].children[msg.child_id] - return validate_child(msg.gateway, msg.node_id, child, msg.sub_type) + return validate_child(gateway_id, msg.gateway, msg.node_id, child, msg.sub_type) -def validate_node(gateway, node_id): +def validate_node(gateway: BaseAsyncGateway, node_id: int) -> bool: """Validate a node.""" if gateway.sensors[node_id].sketch_name is None: _LOGGER.debug("Node %s is missing sketch name", node_id) @@ -113,31 +146,39 @@ def validate_node(gateway, node_id): return True -def validate_child(gateway, node_id, child, value_type=None): - """Validate a child.""" - validated = defaultdict(list) - pres = gateway.const.Presentation - set_req = gateway.const.SetReq - child_type_name = next( +def validate_child( + gateway_id: GatewayId, + gateway: BaseAsyncGateway, + node_id: int, + child: ChildSensor, + value_type: Optional[int] = None, +) -> DefaultDict[str, List[DevId]]: + """Validate a child. Returns a dict mapping hass platform names to list of DevId.""" + validated: DefaultDict[str, List[DevId]] = defaultdict(list) + pres: IntEnum = gateway.const.Presentation + set_req: IntEnum = gateway.const.SetReq + child_type_name: Optional[SensorType] = next( (member.name for member in pres if member.value == child.type), None ) - value_types = {value_type} if value_type else {*child.values} - value_type_names = { + value_types: Set[int] = {value_type} if value_type else {*child.values} + value_type_names: Set[ValueType] = { member.name for member in set_req if member.value in value_types } - platforms = TYPE_TO_PLATFORMS.get(child_type_name, []) + platforms: List[str] = TYPE_TO_PLATFORMS.get(child_type_name, []) if not platforms: _LOGGER.warning("Child type %s is not supported", child.type) return validated for platform in platforms: - platform_v_names = FLAT_PLATFORM_TYPES[platform, child_type_name] - v_names = platform_v_names & value_type_names + platform_v_names: Set[ValueType] = FLAT_PLATFORM_TYPES[ + platform, child_type_name + ] + v_names: Set[ValueType] = platform_v_names & value_type_names if not v_names: - child_value_names = { + child_value_names: Set[ValueType] = { member.name for member in set_req if member.value in child.values } - v_names = platform_v_names & child_value_names + v_names: Set[ValueType] = platform_v_names & child_value_names for v_name in v_names: child_schema_gen = SCHEMAS.get((platform, v_name), default_schema) @@ -153,7 +194,12 @@ def validate_child(gateway, node_id, child, value_type=None): exc, ) continue - dev_id = id(gateway), node_id, child.id, set_req[v_name].value + dev_id: DevId = ( + gateway_id, + node_id, + child.id, + set_req[v_name].value, + ) validated[platform].append(dev_id) return validated diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index ffbcba6f032971..f90f9c5c81c77b 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -1,4 +1,6 @@ """Support for MySensors lights.""" +from typing import Callable + from homeassistant.components import mysensors from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -10,27 +12,47 @@ SUPPORT_WHITE_VALUE, LightEntity, ) +from homeassistant.components.mysensors import on_unload +from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY +from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util from homeassistant.util.color import rgb_hex_to_rgb_list SUPPORT_MYSENSORS_RGBW = SUPPORT_COLOR | SUPPORT_WHITE_VALUE -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the mysensors platform for lights.""" +async def async_setup_entry( + hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable +): + """Set up this platform for a specific ConfigEntry(==Gateway).""" device_class_map = { "S_DIMMER": MySensorsLightDimmer, "S_RGB_LIGHT": MySensorsLightRGB, "S_RGBW_LIGHT": MySensorsLightRGBW, } - mysensors.setup_mysensors_platform( + + async def async_discover(discovery_info): + """Discover and add a MySensors light.""" + mysensors.setup_mysensors_platform( + hass, + DOMAIN, + discovery_info, + device_class_map, + async_add_entities=async_add_entities, + ) + + await on_unload( hass, - DOMAIN, - discovery_info, - device_class_map, - async_add_entities=async_add_entities, + config_entry, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(config_entry.entry_id, DOMAIN), + async_discover, + ), ) @@ -60,11 +82,6 @@ def white_value(self): """Return the white value of this light between 0..255.""" return self._white - @property - def assumed_state(self): - """Return true if unable to access real state of entity.""" - return self.gateway.optimistic - @property def is_on(self): """Return true if device is on.""" @@ -80,7 +97,7 @@ def _turn_on_light(self): self.node_id, self.child_id, set_req.V_LIGHT, 1, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # optimistically assume that light has changed state self._state = True self._values[set_req.V_LIGHT] = STATE_ON @@ -102,7 +119,7 @@ def _turn_on_dimmer(self, **kwargs): self.node_id, self.child_id, set_req.V_DIMMER, percent, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # optimistically assume that light has changed state self._brightness = brightness self._values[set_req.V_DIMMER] = percent @@ -135,7 +152,7 @@ def _turn_on_rgb_and_w(self, hex_template, **kwargs): self.node_id, self.child_id, self.value_type, hex_color, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # optimistically assume that light has changed state self._hs = color_util.color_RGB_to_hs(*rgb) self._white = white @@ -145,7 +162,7 @@ async def async_turn_off(self, **kwargs): """Turn the device off.""" value_type = self.gateway.const.SetReq.V_LIGHT self.gateway.set_child_value(self.node_id, self.child_id, value_type, 0, ack=1) - if self.gateway.optimistic: + if self.assumed_state: # optimistically assume that light has changed state self._state = False self._values[value_type] = STATE_OFF @@ -188,7 +205,7 @@ async def async_turn_on(self, **kwargs): """Turn the device on.""" self._turn_on_light() self._turn_on_dimmer(**kwargs) - if self.gateway.optimistic: + if self.assumed_state: self.async_write_ha_state() async def async_update(self): @@ -214,7 +231,7 @@ async def async_turn_on(self, **kwargs): self._turn_on_light() self._turn_on_dimmer(**kwargs) self._turn_on_rgb_and_w("%02x%02x%02x", **kwargs) - if self.gateway.optimistic: + if self.assumed_state: self.async_write_ha_state() async def async_update(self): @@ -241,5 +258,5 @@ async def async_turn_on(self, **kwargs): self._turn_on_light() self._turn_on_dimmer(**kwargs) self._turn_on_rgb_and_w("%02x%02x%02x%02x", **kwargs) - if self.gateway.optimistic: + if self.assumed_state: self.async_write_ha_state() diff --git a/homeassistant/components/mysensors/manifest.json b/homeassistant/components/mysensors/manifest.json index afeeb5d57cc99b..8371f2930c2fb1 100644 --- a/homeassistant/components/mysensors/manifest.json +++ b/homeassistant/components/mysensors/manifest.json @@ -2,7 +2,15 @@ "domain": "mysensors", "name": "MySensors", "documentation": "https://www.home-assistant.io/integrations/mysensors", - "requirements": ["pymysensors==0.18.0"], - "after_dependencies": ["mqtt"], - "codeowners": ["@MartinHjelmare"] + "requirements": [ + "pymysensors==0.20.1" + ], + "after_dependencies": [ + "mqtt" + ], + "codeowners": [ + "@MartinHjelmare", + "@functionpointer" + ], + "config_flow": true } diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index bab6bf3fc402e8..a09f8af139459e 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -1,6 +1,11 @@ """Support for MySensors sensors.""" +from typing import Callable + from homeassistant.components import mysensors +from homeassistant.components.mysensors import on_unload +from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY from homeassistant.components.sensor import DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONDUCTIVITY, DEGREE, @@ -18,6 +23,8 @@ VOLT, VOLUME_CUBIC_METERS, ) +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType SENSORS = { "V_TEMP": [None, "mdi:thermometer"], @@ -54,14 +61,29 @@ } -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the MySensors platform for sensors.""" - mysensors.setup_mysensors_platform( +async def async_setup_entry( + hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable +): + """Set up this platform for a specific ConfigEntry(==Gateway).""" + + async def async_discover(discovery_info): + """Discover and add a MySensors sensor.""" + mysensors.setup_mysensors_platform( + hass, + DOMAIN, + discovery_info, + MySensorsSensor, + async_add_entities=async_add_entities, + ) + + await on_unload( hass, - DOMAIN, - discovery_info, - MySensorsSensor, - async_add_entities=async_add_entities, + config_entry, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(config_entry.entry_id, DOMAIN), + async_discover, + ), ) @@ -105,7 +127,7 @@ def _get_sensor_type(self): pres = self.gateway.const.Presentation set_req = self.gateway.const.SetReq SENSORS[set_req.V_TEMP.name][0] = ( - TEMP_CELSIUS if self.gateway.metric else TEMP_FAHRENHEIT + TEMP_CELSIUS if self.hass.config.units.is_metric else TEMP_FAHRENHEIT ) sensor_type = SENSORS.get(set_req(self.value_type).name, [None, None]) if isinstance(sensor_type, dict): diff --git a/homeassistant/components/mysensors/strings.json b/homeassistant/components/mysensors/strings.json new file mode 100644 index 00000000000000..43a68f61e247a0 --- /dev/null +++ b/homeassistant/components/mysensors/strings.json @@ -0,0 +1,79 @@ +{ + "title": "MySensors", + "config": { + "step": { + "user": { + "data": { + "gateway_type": "Gateway type" + }, + "description": "Choose connection method to the gateway" + }, + "gw_tcp": { + "description": "Ethernet gateway setup", + "data": { + "device": "IP address of the gateway", + "tcp_port": "port", + "version": "MySensors version", + "persistence_file": "persistence file (leave empty to auto-generate)" + } + }, + "gw_serial": { + "description": "Serial gateway setup", + "data": { + "device": "Serial port", + "baud_rate": "baud rate", + "version": "MySensors version", + "persistence_file": "persistence file (leave empty to auto-generate)" + } + }, + "gw_mqtt": { + "description": "MQTT gateway setup", + "data": { + "retain": "mqtt retain", + "topic_in_prefix": "prefix for input topics (topic_in_prefix)", + "topic_out_prefix": "prefix for output topics (topic_out_prefix)", + "version": "MySensors version", + "persistence_file": "persistence file (leave empty to auto-generate)" + } + } + }, + "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%]", + "invalid_subscribe_topic": "Invalid subscribe topic", + "invalid_publish_topic": "Invalid publish topic", + "duplicate_topic": "Topic already in use", + "same_topic": "Subscribe and publish topics are the same", + "invalid_port": "Invalid port number", + "invalid_persistence_file": "Invalid persistence file", + "duplicate_persistence_file": "Persistence file already in use", + "invalid_ip": "Invalid IP address", + "invalid_serial": "Invalid serial port", + "invalid_device": "Invalid device", + "invalid_version": "Invalid MySensors version", + "not_a_number": "Please enter a number", + "port_out_of_range": "Port number must be at least 1 and at most 65535", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "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%]", + "invalid_subscribe_topic": "Invalid subscribe topic", + "invalid_publish_topic": "Invalid publish topic", + "duplicate_topic": "Topic already in use", + "same_topic": "Subscribe and publish topics are the same", + "invalid_port": "Invalid port number", + "invalid_persistence_file": "Invalid persistence file", + "duplicate_persistence_file": "Persistence file already in use", + "invalid_ip": "Invalid IP address", + "invalid_serial": "Invalid serial port", + "invalid_device": "Invalid device", + "invalid_version": "Invalid MySensors version", + "not_a_number": "Please enter a number", + "port_out_of_range": "Port number must be at least 1 and at most 65535", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index 0da8bfe7030f57..14911e11090fb4 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -1,4 +1,6 @@ """Support for MySensors switches.""" +from typing import Callable + import voluptuous as vol from homeassistant.components import mysensors @@ -6,7 +8,11 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv -from .const import DOMAIN as MYSENSORS_DOMAIN, SERVICE_SEND_IR_CODE +from . import on_unload +from ...config_entries import ConfigEntry +from ...helpers.dispatcher import async_dispatcher_connect +from ...helpers.typing import HomeAssistantType +from .const import DOMAIN as MYSENSORS_DOMAIN, MYSENSORS_DISCOVERY, SERVICE_SEND_IR_CODE ATTR_IR_CODE = "V_IR_SEND" @@ -15,8 +21,10 @@ ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the mysensors platform for switches.""" +async def async_setup_entry( + hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable +): + """Set up this platform for a specific ConfigEntry(==Gateway).""" device_class_map = { "S_DOOR": MySensorsSwitch, "S_MOTION": MySensorsSwitch, @@ -32,13 +40,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "S_MOISTURE": MySensorsSwitch, "S_WATER_QUALITY": MySensorsSwitch, } - mysensors.setup_mysensors_platform( - hass, - DOMAIN, - discovery_info, - device_class_map, - async_add_entities=async_add_entities, - ) + + async def async_discover(discovery_info): + """Discover and add a MySensors switch.""" + mysensors.setup_mysensors_platform( + hass, + DOMAIN, + discovery_info, + device_class_map, + async_add_entities=async_add_entities, + ) async def async_send_ir_code_service(service): """Set IR code as device state attribute.""" @@ -71,15 +82,20 @@ async def async_send_ir_code_service(service): schema=SEND_IR_CODE_SERVICE_SCHEMA, ) + await on_unload( + hass, + config_entry, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(config_entry.entry_id, DOMAIN), + async_discover, + ), + ) + class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity): """Representation of the value of a MySensors Switch child node.""" - @property - def assumed_state(self): - """Return True if unable to access real state of entity.""" - return self.gateway.optimistic - @property def current_power_w(self): """Return the current power usage in W.""" @@ -96,7 +112,7 @@ async def async_turn_on(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, 1, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that switch has changed state self._values[self.value_type] = STATE_ON self.async_write_ha_state() @@ -106,7 +122,7 @@ async def async_turn_off(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, self.value_type, 0, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that switch has changed state self._values[self.value_type] = STATE_OFF self.async_write_ha_state() @@ -137,7 +153,7 @@ async def async_turn_on(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_LIGHT, 1, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that switch has changed state self._values[self.value_type] = self._ir_code self._values[set_req.V_LIGHT] = STATE_ON @@ -151,7 +167,7 @@ async def async_turn_off(self, **kwargs): self.gateway.set_child_value( self.node_id, self.child_id, set_req.V_LIGHT, 0, ack=1 ) - if self.gateway.optimistic: + if self.assumed_state: # Optimistically assume that switch has changed state self._values[set_req.V_LIGHT] = STATE_OFF self.async_write_ha_state() diff --git a/homeassistant/components/mysensors/translations/en.json b/homeassistant/components/mysensors/translations/en.json new file mode 100644 index 00000000000000..d7730ba09b6e74 --- /dev/null +++ b/homeassistant/components/mysensors/translations/en.json @@ -0,0 +1,79 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", + "invalid_subscribe_topic": "Invalid subscribe topic", + "invalid_publish_topic": "Invalid publish topic", + "duplicate_topic": "Topic already in use", + "same_topic": "Subscribe and publish topics are the same", + "invalid_port": "Invalid port number", + "invalid_persistence_file": "Invalid persistence file", + "duplicate_persistence_file": "Persistence file already in use", + "invalid_ip": "Invalid IP address", + "invalid_serial": "Invalid serial port", + "invalid_device": "Invalid device", + "invalid_version": "Invalid MySensors version", + "not_a_number": "Please enter a number", + "port_out_of_range": "Port number must be at least 1 and at most 65535", + "unknown": "Unexpected error" + }, + "error": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", + "invalid_subscribe_topic": "Invalid subscribe topic", + "invalid_publish_topic": "Invalid publish topic", + "duplicate_topic": "Topic already in use", + "same_topic": "Subscribe and publish topics are the same", + "invalid_port": "Invalid port number", + "invalid_persistence_file": "Invalid persistence file", + "duplicate_persistence_file": "Persistence file already in use", + "invalid_ip": "Invalid IP address", + "invalid_serial": "Invalid serial port", + "invalid_device": "Invalid device", + "invalid_version": "Invalid MySensors version", + "not_a_number": "Please enter a number", + "port_out_of_range": "Port number must be at least 1 and at most 65535", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "optimistic": "optimistic", + "persistence": "persistence", + "gateway_type": "Gateway type" + }, + "description": "Choose connection method to the gateway" + }, + "gw_tcp": { + "description": "Ethernet gateway setup", + "data": { + "device": "IP address of the gateway", + "tcp_port": "port", + "version": "MySensors version", + "persistence_file": "persistence file (leave empty to auto-generate)" + } + }, + "gw_serial": { + "description": "Serial gateway setup", + "data": { + "device": "Serial port", + "baud_rate": "baud rate", + "version": "MySensors version", + "persistence_file": "persistence file (leave empty to auto-generate)" + } + }, + "gw_mqtt": { + "description": "MQTT gateway setup", + "data": { + "retain": "mqtt retain", + "topic_in_prefix": "prefix for input topics (topic_in_prefix)", + "topic_out_prefix": "prefix for output topics (topic_out_prefix)", + "version": "MySensors version", + "persistence_file": "persistence file (leave empty to auto-generate)" + } + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c2e36d9f846872..6366a3eb8879c6 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -136,6 +136,7 @@ "motion_blinds", "mqtt", "myq", + "mysensors", "neato", "nest", "netatmo", diff --git a/requirements_all.txt b/requirements_all.txt index 2a78b25a162418..2cc29c3be4b5e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1551,7 +1551,7 @@ pymusiccast==0.1.6 pymyq==2.0.14 # homeassistant.components.mysensors -pymysensors==0.18.0 +pymysensors==0.20.1 # homeassistant.components.nanoleaf pynanoleaf==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 024de6596b654b..2a447b539ef58b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -810,6 +810,9 @@ pymonoprice==0.3 # homeassistant.components.myq pymyq==2.0.14 +# homeassistant.components.mysensors +pymysensors==0.20.1 + # homeassistant.components.nuki pynuki==1.3.8 diff --git a/tests/components/mysensors/__init__.py b/tests/components/mysensors/__init__.py new file mode 100644 index 00000000000000..68fc6d7b4d7053 --- /dev/null +++ b/tests/components/mysensors/__init__.py @@ -0,0 +1 @@ +"""Tests for the MySensors integration.""" diff --git a/tests/components/mysensors/test_config_flow.py b/tests/components/mysensors/test_config_flow.py new file mode 100644 index 00000000000000..6bfec3b102e585 --- /dev/null +++ b/tests/components/mysensors/test_config_flow.py @@ -0,0 +1,735 @@ +"""Test the MySensors config flow.""" +from typing import Dict, Optional, Tuple +from unittest.mock import patch + +import pytest + +from homeassistant import config_entries, setup +from homeassistant.components.mysensors.const import ( + CONF_BAUD_RATE, + CONF_DEVICE, + CONF_GATEWAY_TYPE, + CONF_GATEWAY_TYPE_MQTT, + CONF_GATEWAY_TYPE_SERIAL, + CONF_GATEWAY_TYPE_TCP, + CONF_PERSISTENCE, + CONF_PERSISTENCE_FILE, + CONF_RETAIN, + CONF_TCP_PORT, + CONF_TOPIC_IN_PREFIX, + CONF_TOPIC_OUT_PREFIX, + CONF_VERSION, + DOMAIN, + ConfGatewayType, +) +from homeassistant.helpers.typing import HomeAssistantType + +from tests.common import MockConfigEntry + + +async def get_form( + hass: HomeAssistantType, gatway_type: ConfGatewayType, expected_step_id: str +): + """Get a form for the given gateway type.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + stepuser = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert stepuser["type"] == "form" + assert not stepuser["errors"] + + result = await hass.config_entries.flow.async_configure( + stepuser["flow_id"], + {CONF_GATEWAY_TYPE: gatway_type}, + ) + await hass.async_block_till_done() + assert result["type"] == "form" + assert result["step_id"] == expected_step_id + + return result + + +async def test_config_mqtt(hass: HomeAssistantType): + """Test configuring a mqtt gateway.""" + step = await get_form(hass, CONF_GATEWAY_TYPE_MQTT, "gw_mqtt") + flow_id = step["flow_id"] + + with patch( + "homeassistant.components.mysensors.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_RETAIN: True, + CONF_TOPIC_IN_PREFIX: "bla", + CONF_TOPIC_OUT_PREFIX: "blub", + CONF_VERSION: "2.4", + }, + ) + await hass.async_block_till_done() + + if "errors" in result2: + assert not result2["errors"] + assert result2["type"] == "create_entry" + assert result2["title"] == "mqtt" + assert result2["data"] == { + CONF_DEVICE: "mqtt", + CONF_RETAIN: True, + CONF_TOPIC_IN_PREFIX: "bla", + CONF_TOPIC_OUT_PREFIX: "blub", + CONF_VERSION: "2.4", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_config_serial(hass: HomeAssistantType): + """Test configuring a gateway via serial.""" + step = await get_form(hass, CONF_GATEWAY_TYPE_SERIAL, "gw_serial") + flow_id = step["flow_id"] + + with patch( # mock is_serial_port because otherwise the test will be platform dependent (/dev/ttyACMx vs COMx) + "homeassistant.components.mysensors.config_flow.is_serial_port", + return_value=True, + ), patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=True + ), patch( + "homeassistant.components.mysensors.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_BAUD_RATE: 115200, + CONF_DEVICE: "/dev/ttyACM0", + CONF_VERSION: "2.4", + }, + ) + await hass.async_block_till_done() + + if "errors" in result2: + assert not result2["errors"] + assert result2["type"] == "create_entry" + assert result2["title"] == "/dev/ttyACM0" + assert result2["data"] == { + CONF_DEVICE: "/dev/ttyACM0", + CONF_BAUD_RATE: 115200, + CONF_VERSION: "2.4", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_config_tcp(hass: HomeAssistantType): + """Test configuring a gateway via tcp.""" + step = await get_form(hass, CONF_GATEWAY_TYPE_TCP, "gw_tcp") + flow_id = step["flow_id"] + + with patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=True + ), patch( + "homeassistant.components.mysensors.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "2.4", + }, + ) + await hass.async_block_till_done() + + if "errors" in result2: + assert not result2["errors"] + assert result2["type"] == "create_entry" + assert result2["title"] == "127.0.0.1" + assert result2["data"] == { + CONF_DEVICE: "127.0.0.1", + CONF_TCP_PORT: 5003, + CONF_VERSION: "2.4", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_fail_to_connect(hass: HomeAssistantType): + """Test configuring a gateway via tcp.""" + step = await get_form(hass, CONF_GATEWAY_TYPE_TCP, "gw_tcp") + flow_id = step["flow_id"] + + with patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=False + ), patch( + "homeassistant.components.mysensors.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "2.4", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "form" + assert "errors" in result2 + assert "base" in result2["errors"] + assert result2["errors"]["base"] == "cannot_connect" + assert len(mock_setup.mock_calls) == 0 + assert len(mock_setup_entry.mock_calls) == 0 + + +@pytest.mark.parametrize( + "gateway_type, expected_step_id, user_input, err_field, err_string", + [ + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 600_000, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "2.4", + }, + CONF_TCP_PORT, + "port_out_of_range", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 0, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "2.4", + }, + CONF_TCP_PORT, + "port_out_of_range", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "a", + }, + CONF_VERSION, + "invalid_version", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "a.b", + }, + CONF_VERSION, + "invalid_version", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + }, + CONF_VERSION, + "invalid_version", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "4", + }, + CONF_VERSION, + "invalid_version", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.1", + CONF_VERSION: "v3", + }, + CONF_VERSION, + "invalid_version", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "127.0.0.", + }, + CONF_DEVICE, + "invalid_ip", + ), + ( + CONF_GATEWAY_TYPE_TCP, + "gw_tcp", + { + CONF_TCP_PORT: 5003, + CONF_DEVICE: "abcd", + }, + CONF_DEVICE, + "invalid_ip", + ), + ( + CONF_GATEWAY_TYPE_MQTT, + "gw_mqtt", + { + CONF_RETAIN: True, + CONF_TOPIC_IN_PREFIX: "bla", + CONF_TOPIC_OUT_PREFIX: "blub", + CONF_PERSISTENCE_FILE: "asdf.zip", + CONF_VERSION: "2.4", + }, + CONF_PERSISTENCE_FILE, + "invalid_persistence_file", + ), + ( + CONF_GATEWAY_TYPE_MQTT, + "gw_mqtt", + { + CONF_RETAIN: True, + CONF_TOPIC_IN_PREFIX: "/#/#", + CONF_TOPIC_OUT_PREFIX: "blub", + CONF_VERSION: "2.4", + }, + CONF_TOPIC_IN_PREFIX, + "invalid_subscribe_topic", + ), + ( + CONF_GATEWAY_TYPE_MQTT, + "gw_mqtt", + { + CONF_RETAIN: True, + CONF_TOPIC_IN_PREFIX: "asdf", + CONF_TOPIC_OUT_PREFIX: "/#/#", + CONF_VERSION: "2.4", + }, + CONF_TOPIC_OUT_PREFIX, + "invalid_publish_topic", + ), + ( + CONF_GATEWAY_TYPE_MQTT, + "gw_mqtt", + { + CONF_RETAIN: True, + CONF_TOPIC_IN_PREFIX: "asdf", + CONF_TOPIC_OUT_PREFIX: "asdf", + CONF_VERSION: "2.4", + }, + CONF_TOPIC_OUT_PREFIX, + "same_topic", + ), + ], +) +async def test_config_invalid( + hass: HomeAssistantType, + gateway_type: ConfGatewayType, + expected_step_id: str, + user_input: Dict[str, any], + err_field, + err_string, +): + """Perform a test that is expected to generate an error.""" + step = await get_form(hass, gateway_type, expected_step_id) + flow_id = step["flow_id"] + + with patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=True + ), patch( + "homeassistant.components.mysensors.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow_id, + user_input, + ) + await hass.async_block_till_done() + + assert result2["type"] == "form" + assert "errors" in result2 + assert err_field in result2["errors"] + assert result2["errors"][err_field] == err_string + assert len(mock_setup.mock_calls) == 0 + assert len(mock_setup_entry.mock_calls) == 0 + + +@pytest.mark.parametrize( + "user_input", + [ + { + CONF_DEVICE: "COM5", + CONF_BAUD_RATE: 57600, + CONF_TCP_PORT: 5003, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "bla.json", + }, + { + CONF_DEVICE: "COM5", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 57600, + CONF_TCP_PORT: 5003, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: True, + }, + { + CONF_DEVICE: "mqtt", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + CONF_TOPIC_IN_PREFIX: "intopic", + CONF_TOPIC_OUT_PREFIX: "outtopic", + CONF_VERSION: "2.4", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "127.0.0.1", + CONF_PERSISTENCE_FILE: "blub.pickle", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 343, + CONF_VERSION: "2.4", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + ], +) +async def test_import(hass: HomeAssistantType, user_input: Dict): + """Test importing a gateway.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch("sys.platform", "win32"), patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=True + ), patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, data=user_input, context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + + +@pytest.mark.parametrize( + "first_input, second_input, expected_result", + [ + ( + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "same2", + }, + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "same2", + }, + (CONF_TOPIC_IN_PREFIX, "duplicate_topic"), + ), + ( + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "different1", + CONF_TOPIC_OUT_PREFIX: "different2", + }, + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "different3", + CONF_TOPIC_OUT_PREFIX: "different4", + }, + None, + ), + ( + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "different2", + }, + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "different4", + }, + (CONF_TOPIC_IN_PREFIX, "duplicate_topic"), + ), + ( + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "different2", + }, + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "different1", + CONF_TOPIC_OUT_PREFIX: "same1", + }, + (CONF_TOPIC_OUT_PREFIX, "duplicate_topic"), + ), + ( + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "different2", + }, + { + CONF_DEVICE: "mqtt", + CONF_VERSION: "2.3", + CONF_TOPIC_IN_PREFIX: "same1", + CONF_TOPIC_OUT_PREFIX: "different1", + }, + (CONF_TOPIC_IN_PREFIX, "duplicate_topic"), + ), + ( + { + CONF_DEVICE: "127.0.0.1", + CONF_PERSISTENCE_FILE: "same.json", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "192.168.1.2", + CONF_PERSISTENCE_FILE: "same.json", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + ("persistence_file", "duplicate_persistence_file"), + ), + ( + { + CONF_DEVICE: "127.0.0.1", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "192.168.1.2", + CONF_PERSISTENCE_FILE: "same.json", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + None, + ), + ( + { + CONF_DEVICE: "127.0.0.1", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "192.168.1.2", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + None, + ), + ( + { + CONF_DEVICE: "192.168.1.2", + CONF_PERSISTENCE_FILE: "different1.json", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "192.168.1.2", + CONF_PERSISTENCE_FILE: "different2.json", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + ("base", "already_configured"), + ), + ( + { + CONF_DEVICE: "192.168.1.2", + CONF_PERSISTENCE_FILE: "different1.json", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "192.168.1.2", + CONF_PERSISTENCE_FILE: "different2.json", + CONF_TCP_PORT: 5003, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + None, + ), + ( + { + CONF_DEVICE: "192.168.1.2", + CONF_TCP_PORT: 5003, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + { + CONF_DEVICE: "192.168.1.3", + CONF_TCP_PORT: 5003, + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + }, + None, + ), + ( + { + CONF_DEVICE: "COM5", + CONF_TCP_PORT: 5003, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "different1.json", + }, + { + CONF_DEVICE: "COM5", + CONF_TCP_PORT: 5003, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "different2.json", + }, + ("base", "already_configured"), + ), + ( + { + CONF_DEVICE: "COM6", + CONF_BAUD_RATE: 57600, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + }, + { + CONF_DEVICE: "COM5", + CONF_TCP_PORT: 5003, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + }, + None, + ), + ( + { + CONF_DEVICE: "COM5", + CONF_BAUD_RATE: 115200, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "different1.json", + }, + { + CONF_DEVICE: "COM5", + CONF_BAUD_RATE: 57600, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "different2.json", + }, + ("base", "already_configured"), + ), + ( + { + CONF_DEVICE: "COM5", + CONF_BAUD_RATE: 115200, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "same.json", + }, + { + CONF_DEVICE: "COM6", + CONF_BAUD_RATE: 57600, + CONF_RETAIN: True, + CONF_VERSION: "2.3", + CONF_PERSISTENCE_FILE: "same.json", + }, + ("persistence_file", "duplicate_persistence_file"), + ), + ( + { + CONF_DEVICE: "mqtt", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + CONF_VERSION: "1.4", + }, + { + CONF_DEVICE: "COM6", + CONF_PERSISTENCE_FILE: "bla2.json", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + CONF_VERSION: "1.4", + }, + None, + ), + ], +) +async def test_duplicate( + hass: HomeAssistantType, + first_input: Dict, + second_input: Dict, + expected_result: Optional[Tuple[str, str]], +): + """Test duplicate detection.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch("sys.platform", "win32"), patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=True + ), patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ): + MockConfigEntry(domain=DOMAIN, data=first_input).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, data=second_input, context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + if expected_result is None: + assert result["type"] == "create_entry" + else: + assert result["type"] == "abort" + assert result["reason"] == expected_result[1] diff --git a/tests/components/mysensors/test_gateway.py b/tests/components/mysensors/test_gateway.py new file mode 100644 index 00000000000000..d3e360e0b9f8a5 --- /dev/null +++ b/tests/components/mysensors/test_gateway.py @@ -0,0 +1,30 @@ +"""Test function in gateway.py.""" +from unittest.mock import patch + +import pytest +import voluptuous as vol + +from homeassistant.components.mysensors.gateway import is_serial_port +from homeassistant.helpers.typing import HomeAssistantType + + +@pytest.mark.parametrize( + "port, expect_valid", + [ + ("COM5", True), + ("asdf", False), + ("COM17", True), + ("COM", False), + ("/dev/ttyACM0", False), + ], +) +def test_is_serial_port_windows(hass: HomeAssistantType, port: str, expect_valid: bool): + """Test windows serial port.""" + + with patch("sys.platform", "win32"): + try: + is_serial_port(port) + except vol.Invalid: + assert not expect_valid + else: + assert expect_valid diff --git a/tests/components/mysensors/test_init.py b/tests/components/mysensors/test_init.py new file mode 100644 index 00000000000000..2775b73efd6299 --- /dev/null +++ b/tests/components/mysensors/test_init.py @@ -0,0 +1,251 @@ +"""Test function in __init__.py.""" +from typing import Dict +from unittest.mock import patch + +import pytest + +from homeassistant.components.mysensors import ( + CONF_BAUD_RATE, + CONF_DEVICE, + CONF_GATEWAYS, + CONF_PERSISTENCE, + CONF_PERSISTENCE_FILE, + CONF_RETAIN, + CONF_TCP_PORT, + CONF_VERSION, + DEFAULT_VERSION, + DOMAIN, +) +from homeassistant.components.mysensors.const import ( + CONF_GATEWAY_TYPE, + CONF_GATEWAY_TYPE_MQTT, + CONF_GATEWAY_TYPE_SERIAL, + CONF_GATEWAY_TYPE_TCP, + CONF_TOPIC_IN_PREFIX, + CONF_TOPIC_OUT_PREFIX, +) +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.setup import async_setup_component + + +@pytest.mark.parametrize( + "config, expected_calls, expected_to_succeed, expected_config_flow_user_input", + [ + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "COM5", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 57600, + CONF_TCP_PORT: 5003, + } + ], + CONF_VERSION: "2.3", + CONF_PERSISTENCE: False, + CONF_RETAIN: True, + } + }, + 1, + True, + { + CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL, + CONF_DEVICE: "COM5", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 57600, + CONF_VERSION: "2.3", + }, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "127.0.0.1", + CONF_PERSISTENCE_FILE: "blub.pickle", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 343, + } + ], + CONF_VERSION: "2.4", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 1, + True, + { + CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP, + CONF_DEVICE: "127.0.0.1", + CONF_PERSISTENCE_FILE: "blub.pickle", + CONF_TCP_PORT: 343, + CONF_VERSION: "2.4", + }, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "127.0.0.1", + } + ], + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 1, + True, + { + CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP, + CONF_DEVICE: "127.0.0.1", + CONF_TCP_PORT: 5003, + CONF_VERSION: DEFAULT_VERSION, + }, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "mqtt", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + CONF_TOPIC_IN_PREFIX: "intopic", + CONF_TOPIC_OUT_PREFIX: "outtopic", + } + ], + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 1, + True, + { + CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_MQTT, + CONF_DEVICE: "mqtt", + CONF_VERSION: DEFAULT_VERSION, + CONF_TOPIC_OUT_PREFIX: "outtopic", + CONF_TOPIC_IN_PREFIX: "intopic", + }, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "mqtt", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + } + ], + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 0, + True, + {}, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "mqtt", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_TOPIC_OUT_PREFIX: "out", + CONF_TOPIC_IN_PREFIX: "in", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + }, + { + CONF_DEVICE: "COM6", + CONF_PERSISTENCE_FILE: "bla2.json", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + }, + ], + CONF_VERSION: "2.4", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 2, + True, + {}, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "mqtt", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + }, + { + CONF_DEVICE: "COM6", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + }, + ], + CONF_VERSION: "2.4", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 0, + False, + {}, + ), + ( + { + DOMAIN: { + CONF_GATEWAYS: [ + { + CONF_DEVICE: "COMx", + CONF_PERSISTENCE_FILE: "bla.json", + CONF_BAUD_RATE: 115200, + CONF_TCP_PORT: 5003, + }, + ], + CONF_VERSION: "2.4", + CONF_PERSISTENCE: False, + CONF_RETAIN: False, + } + }, + 0, + True, + {}, + ), + ], +) +async def test_import( + hass: HomeAssistantType, + config: ConfigType, + expected_calls: int, + expected_to_succeed: bool, + expected_config_flow_user_input: Dict[str, any], +): + """Test importing a gateway.""" + with patch("sys.platform", "win32"), patch( + "homeassistant.components.mysensors.config_flow.try_connect", return_value=True + ), patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await async_setup_component(hass, DOMAIN, config) + assert result == expected_to_succeed + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == expected_calls + + if expected_calls > 0: + config_flow_user_input = mock_setup_entry.mock_calls[0][1][1].data + for key, value in expected_config_flow_user_input.items(): + assert key in config_flow_user_input + assert config_flow_user_input[key] == value From 2c74befd4f6c2dd5aace996cd1e6b0088296a425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Conde=20G=C3=B3mez?= Date: Fri, 5 Feb 2021 22:39:31 +0100 Subject: [PATCH 0229/1818] Fix foscam to work again with non-admin accounts and make RTSP port configurable again (#45975) * Do not require admin account for foscam cameras. Foscam cameras require admin account for getting the MAC address, requiring an admin account in the integration is not desirable as an operator one is good enough (and a good practice). Old entries using the MAC address as unique_id are migrated to the new unique_id format so everything is consistent. Also fixed unhandled invalid responses from the camera in the config flow process. * Make RTSP port configurable again as some cameras reports wrong port * Remove periods from new log lines * Set new Config Flow version to 2 and adjust the entity migration * Create a proper error message for the InvalidResponse exception * Change crafted unique_id to use entry_id in the entity * Abort if same host and port is already configured * Fix entry tracking to use entry_id instead of unique_id * Remove unique_id from mocked config entry in tests --- homeassistant/components/foscam/__init__.py | 53 +++++++- homeassistant/components/foscam/camera.py | 45 ++++--- .../components/foscam/config_flow.py | 54 +++++++- homeassistant/components/foscam/const.py | 1 + homeassistant/components/foscam/strings.json | 2 + .../components/foscam/translations/en.json | 2 + tests/components/foscam/test_config_flow.py | 124 +++++++++++++++--- 7 files changed, 234 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/foscam/__init__.py b/homeassistant/components/foscam/__init__.py index e5b82817d4bea4..6a2c961544f69d 100644 --- a/homeassistant/components/foscam/__init__.py +++ b/homeassistant/components/foscam/__init__.py @@ -1,10 +1,15 @@ """The foscam component.""" import asyncio +from libpyfoscam import FoscamCamera + from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_registry import async_migrate_entries -from .const import DOMAIN, SERVICE_PTZ, SERVICE_PTZ_PRESET +from .config_flow import DEFAULT_RTSP_PORT +from .const import CONF_RTSP_PORT, DOMAIN, LOGGER, SERVICE_PTZ, SERVICE_PTZ_PRESET PLATFORMS = ["camera"] @@ -22,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.config_entries.async_forward_entry_setup(entry, component) ) - hass.data[DOMAIN][entry.unique_id] = entry.data + hass.data[DOMAIN][entry.entry_id] = entry.data return True @@ -39,10 +44,50 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) if unload_ok: - hass.data[DOMAIN].pop(entry.unique_id) + hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.services.async_remove(domain=DOMAIN, service=SERVICE_PTZ) hass.services.async_remove(domain=DOMAIN, service=SERVICE_PTZ_PRESET) return unload_ok + + +async def async_migrate_entry(hass, config_entry: ConfigEntry): + """Migrate old entry.""" + LOGGER.debug("Migrating from version %s", config_entry.version) + + if config_entry.version == 1: + # Change unique id + @callback + def update_unique_id(entry): + return {"new_unique_id": config_entry.entry_id} + + await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + + config_entry.unique_id = None + + # Get RTSP port from the camera or use the fallback one and store it in data + camera = FoscamCamera( + config_entry.data[CONF_HOST], + config_entry.data[CONF_PORT], + config_entry.data[CONF_USERNAME], + config_entry.data[CONF_PASSWORD], + verbose=False, + ) + + ret, response = await hass.async_add_executor_job(camera.get_port_info) + + rtsp_port = DEFAULT_RTSP_PORT + + if ret != 0: + rtsp_port = response.get("rtspPort") or response.get("mediaPort") + + config_entry.data = {**config_entry.data, CONF_RTSP_PORT: rtsp_port} + + # Change entry version + config_entry.version = 2 + + LOGGER.info("Migration to version %s successful", config_entry.version) + + return True diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index f66ad31c2a827d..d600546c3b04ed 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -15,7 +15,14 @@ ) from homeassistant.helpers import config_validation as cv, entity_platform -from .const import CONF_STREAM, DOMAIN, LOGGER, SERVICE_PTZ, SERVICE_PTZ_PRESET +from .const import ( + CONF_RTSP_PORT, + CONF_STREAM, + DOMAIN, + LOGGER, + SERVICE_PTZ, + SERVICE_PTZ_PRESET, +) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -24,7 +31,7 @@ vol.Required(CONF_USERNAME): cv.string, vol.Optional(CONF_NAME, default="Foscam Camera"): cv.string, vol.Optional(CONF_PORT, default=88): cv.port, - vol.Optional("rtsp_port"): cv.port, + vol.Optional(CONF_RTSP_PORT): cv.port, } ) @@ -71,6 +78,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_USERNAME: config[CONF_USERNAME], CONF_PASSWORD: config[CONF_PASSWORD], CONF_STREAM: "Main", + CONF_RTSP_PORT: config.get(CONF_RTSP_PORT, 554), } hass.async_create_task( @@ -134,8 +142,8 @@ def __init__(self, camera, config_entry): self._username = config_entry.data[CONF_USERNAME] self._password = config_entry.data[CONF_PASSWORD] self._stream = config_entry.data[CONF_STREAM] - self._unique_id = config_entry.unique_id - self._rtsp_port = None + self._unique_id = config_entry.entry_id + self._rtsp_port = config_entry.data[CONF_RTSP_PORT] self._motion_status = False async def async_added_to_hass(self): @@ -145,7 +153,13 @@ async def async_added_to_hass(self): self._foscam_session.get_motion_detect_config ) - if ret != 0: + if ret == -3: + LOGGER.info( + "Can't get motion detection status, camera %s configured with non-admin user", + self._name, + ) + + elif ret != 0: LOGGER.error( "Error getting motion detection status of %s: %s", self._name, ret ) @@ -153,17 +167,6 @@ async def async_added_to_hass(self): else: self._motion_status = response == 1 - # Get RTSP port - ret, response = await self.hass.async_add_executor_job( - self._foscam_session.get_port_info - ) - - if ret != 0: - LOGGER.error("Error getting RTSP port of %s: %s", self._name, ret) - - else: - self._rtsp_port = response.get("rtspPort") or response.get("mediaPort") - @property def unique_id(self): """Return the entity unique ID.""" @@ -205,6 +208,11 @@ def enable_motion_detection(self): ret = self._foscam_session.enable_motion_detection() if ret != 0: + if ret == -3: + LOGGER.info( + "Can't set motion detection status, camera %s configured with non-admin user", + self._name, + ) return self._motion_status = True @@ -220,6 +228,11 @@ def disable_motion_detection(self): ret = self._foscam_session.disable_motion_detection() if ret != 0: + if ret == -3: + LOGGER.info( + "Can't set motion detection status, camera %s configured with non-admin user", + self._name, + ) return self._motion_status = False diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index 7bb8cb50a5109a..bfeefb9e406055 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -1,6 +1,10 @@ """Config flow for foscam integration.""" from libpyfoscam import FoscamCamera -from libpyfoscam.foscam import ERROR_FOSCAM_AUTH, ERROR_FOSCAM_UNAVAILABLE +from libpyfoscam.foscam import ( + ERROR_FOSCAM_AUTH, + ERROR_FOSCAM_UNAVAILABLE, + FOSCAM_SUCCESS, +) import voluptuous as vol from homeassistant import config_entries, exceptions @@ -13,12 +17,13 @@ ) from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_STREAM, LOGGER +from .const import CONF_RTSP_PORT, CONF_STREAM, LOGGER from .const import DOMAIN # pylint:disable=unused-import STREAMS = ["Main", "Sub"] DEFAULT_PORT = 88 +DEFAULT_RTSP_PORT = 554 DATA_SCHEMA = vol.Schema( @@ -28,6 +33,7 @@ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(CONF_STREAM, default=STREAMS[0]): vol.In(STREAMS), + vol.Required(CONF_RTSP_PORT, default=DEFAULT_RTSP_PORT): int, } ) @@ -35,7 +41,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for foscam.""" - VERSION = 1 + VERSION = 2 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL async def _validate_and_create(self, data): @@ -43,6 +49,14 @@ async def _validate_and_create(self, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ + + for entry in self.hass.config_entries.async_entries(DOMAIN): + if ( + entry.data[CONF_HOST] == data[CONF_HOST] + and entry.data[CONF_PORT] == data[CONF_PORT] + ): + raise AbortFlow("already_configured") + camera = FoscamCamera( data[CONF_HOST], data[CONF_PORT], @@ -52,7 +66,7 @@ async def _validate_and_create(self, data): ) # Validate data by sending a request to the camera - ret, response = await self.hass.async_add_executor_job(camera.get_dev_info) + ret, _ = await self.hass.async_add_executor_job(camera.get_product_all_info) if ret == ERROR_FOSCAM_UNAVAILABLE: raise CannotConnect @@ -60,10 +74,23 @@ async def _validate_and_create(self, data): if ret == ERROR_FOSCAM_AUTH: raise InvalidAuth - await self.async_set_unique_id(response["mac"]) - self._abort_if_unique_id_configured() + if ret != FOSCAM_SUCCESS: + LOGGER.error( + "Unexpected error code from camera %s:%s: %s", + data[CONF_HOST], + data[CONF_PORT], + ret, + ) + raise InvalidResponse + + # Try to get camera name (only possible with admin account) + ret, response = await self.hass.async_add_executor_job(camera.get_dev_info) - name = data.pop(CONF_NAME, response["devName"]) + dev_name = response.get( + "devName", f"Foscam {data[CONF_HOST]}:{data[CONF_PORT]}" + ) + + name = data.pop(CONF_NAME, dev_name) return self.async_create_entry(title=name, data=data) @@ -81,6 +108,9 @@ async def async_step_user(self, user_input=None): except InvalidAuth: errors["base"] = "invalid_auth" + except InvalidResponse: + errors["base"] = "invalid_response" + except AbortFlow: raise @@ -105,6 +135,12 @@ async def async_step_import(self, import_config): LOGGER.error("Error importing foscam platform config: invalid auth.") return self.async_abort(reason="invalid_auth") + except InvalidResponse: + LOGGER.exception( + "Error importing foscam platform config: invalid response from camera." + ) + return self.async_abort(reason="invalid_response") + except AbortFlow: raise @@ -121,3 +157,7 @@ class CannotConnect(exceptions.HomeAssistantError): class InvalidAuth(exceptions.HomeAssistantError): """Error to indicate there is invalid auth.""" + + +class InvalidResponse(exceptions.HomeAssistantError): + """Error to indicate there is invalid response.""" diff --git a/homeassistant/components/foscam/const.py b/homeassistant/components/foscam/const.py index a42b430993e027..d5ac0f5c567d25 100644 --- a/homeassistant/components/foscam/const.py +++ b/homeassistant/components/foscam/const.py @@ -5,6 +5,7 @@ DOMAIN = "foscam" +CONF_RTSP_PORT = "rtsp_port" CONF_STREAM = "stream" SERVICE_PTZ = "ptz" diff --git a/homeassistant/components/foscam/strings.json b/homeassistant/components/foscam/strings.json index 6033fa099cd99a..5c0622af9d17b2 100644 --- a/homeassistant/components/foscam/strings.json +++ b/homeassistant/components/foscam/strings.json @@ -8,6 +8,7 @@ "port": "[%key:common::config_flow::data::port%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", + "rtsp_port": "RTSP port", "stream": "Stream" } } @@ -15,6 +16,7 @@ "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "invalid_response": "Invalid response from the device", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/homeassistant/components/foscam/translations/en.json b/homeassistant/components/foscam/translations/en.json index 3d1454a4ebd6c7..16a7d0b78001a5 100644 --- a/homeassistant/components/foscam/translations/en.json +++ b/homeassistant/components/foscam/translations/en.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", + "invalid_response": "Invalid response from the device", "unknown": "Unexpected error" }, "step": { @@ -14,6 +15,7 @@ "host": "Host", "password": "Password", "port": "Port", + "rtsp_port": "RTSP port", "stream": "Stream", "username": "Username" } diff --git a/tests/components/foscam/test_config_flow.py b/tests/components/foscam/test_config_flow.py index 8087ac1894f1a2..3b8910c4dbc94f 100644 --- a/tests/components/foscam/test_config_flow.py +++ b/tests/components/foscam/test_config_flow.py @@ -1,7 +1,12 @@ """Test the Foscam config flow.""" from unittest.mock import patch -from libpyfoscam.foscam import ERROR_FOSCAM_AUTH, ERROR_FOSCAM_UNAVAILABLE +from libpyfoscam.foscam import ( + ERROR_FOSCAM_AUTH, + ERROR_FOSCAM_CMD, + ERROR_FOSCAM_UNAVAILABLE, + ERROR_FOSCAM_UNKNOWN, +) from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.foscam import config_flow @@ -14,6 +19,13 @@ config_flow.CONF_USERNAME: "admin", config_flow.CONF_PASSWORD: "1234", config_flow.CONF_STREAM: "Main", + config_flow.CONF_RTSP_PORT: 554, +} +OPERATOR_CONFIG = { + config_flow.CONF_USERNAME: "operator", +} +INVALID_RESPONSE_CONFIG = { + config_flow.CONF_USERNAME: "interr", } CAMERA_NAME = "Mocked Foscam Camera" CAMERA_MAC = "C0:C1:D0:F4:B4:D4" @@ -23,26 +35,39 @@ def setup_mock_foscam_camera(mock_foscam_camera): """Mock FoscamCamera simulating behaviour using a base valid config.""" def configure_mock_on_init(host, port, user, passwd, verbose=False): - return_code = 0 - data = {} + product_all_info_rc = 0 + dev_info_rc = 0 + dev_info_data = {} if ( host != VALID_CONFIG[config_flow.CONF_HOST] or port != VALID_CONFIG[config_flow.CONF_PORT] ): - return_code = ERROR_FOSCAM_UNAVAILABLE + product_all_info_rc = dev_info_rc = ERROR_FOSCAM_UNAVAILABLE elif ( - user != VALID_CONFIG[config_flow.CONF_USERNAME] + user + not in [ + VALID_CONFIG[config_flow.CONF_USERNAME], + OPERATOR_CONFIG[config_flow.CONF_USERNAME], + INVALID_RESPONSE_CONFIG[config_flow.CONF_USERNAME], + ] or passwd != VALID_CONFIG[config_flow.CONF_PASSWORD] ): - return_code = ERROR_FOSCAM_AUTH + product_all_info_rc = dev_info_rc = ERROR_FOSCAM_AUTH + + elif user == INVALID_RESPONSE_CONFIG[config_flow.CONF_USERNAME]: + product_all_info_rc = dev_info_rc = ERROR_FOSCAM_UNKNOWN + + elif user == OPERATOR_CONFIG[config_flow.CONF_USERNAME]: + dev_info_rc = ERROR_FOSCAM_CMD else: - data["devName"] = CAMERA_NAME - data["mac"] = CAMERA_MAC + dev_info_data["devName"] = CAMERA_NAME + dev_info_data["mac"] = CAMERA_MAC - mock_foscam_camera.get_dev_info.return_value = (return_code, data) + mock_foscam_camera.get_product_all_info.return_value = (product_all_info_rc, {}) + mock_foscam_camera.get_dev_info.return_value = (dev_info_rc, dev_info_data) return mock_foscam_camera @@ -142,12 +167,44 @@ async def test_user_cannot_connect(hass): assert result["errors"] == {"base": "cannot_connect"} +async def test_user_invalid_response(hass): + """Test we handle invalid response error from user input.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.foscam.config_flow.FoscamCamera", + ) as mock_foscam_camera: + setup_mock_foscam_camera(mock_foscam_camera) + + invalid_response = VALID_CONFIG.copy() + invalid_response[config_flow.CONF_USERNAME] = INVALID_RESPONSE_CONFIG[ + config_flow.CONF_USERNAME + ] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + invalid_response, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_response"} + + async def test_user_already_configured(hass): """Test we handle already configured from user input.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC + domain=config_flow.DOMAIN, + data=VALID_CONFIG, ) entry.add_to_hass(hass) @@ -201,6 +258,8 @@ async def test_user_unknown_exception(hass): async def test_import_user_valid(hass): """Test valid config from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", ) as mock_foscam_camera, patch( @@ -229,6 +288,8 @@ async def test_import_user_valid(hass): async def test_import_user_valid_with_name(hass): """Test valid config with extra name from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", ) as mock_foscam_camera, patch( @@ -261,10 +322,7 @@ async def test_import_user_valid_with_name(hass): async def test_import_invalid_auth(hass): """Test we handle invalid auth from import.""" - entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC - ) - entry.add_to_hass(hass) + await setup.async_setup_component(hass, "persistent_notification", {}) with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", @@ -287,11 +345,8 @@ async def test_import_invalid_auth(hass): async def test_import_cannot_connect(hass): - """Test we handle invalid auth from import.""" - entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC - ) - entry.add_to_hass(hass) + """Test we handle cannot connect error from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", @@ -313,10 +368,39 @@ async def test_import_cannot_connect(hass): assert result["reason"] == "cannot_connect" +async def test_import_invalid_response(hass): + """Test we handle invalid response error from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "homeassistant.components.foscam.config_flow.FoscamCamera", + ) as mock_foscam_camera: + setup_mock_foscam_camera(mock_foscam_camera) + + invalid_response = VALID_CONFIG.copy() + invalid_response[config_flow.CONF_USERNAME] = INVALID_RESPONSE_CONFIG[ + config_flow.CONF_USERNAME + ] + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=invalid_response, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "invalid_response" + + async def test_import_already_configured(hass): """Test we handle already configured from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC + domain=config_flow.DOMAIN, + data=VALID_CONFIG, ) entry.add_to_hass(hass) From 434b4dfa583037c755cbf86f53465941329f6fc9 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Feb 2021 23:07:12 +0100 Subject: [PATCH 0230/1818] Improve deCONZ logbook to be more robust in different situations (#46063) --- homeassistant/components/deconz/logbook.py | 38 ++++++++++--- tests/components/deconz/test_logbook.py | 62 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/deconz/logbook.py b/homeassistant/components/deconz/logbook.py index 73c157ac8f67ff..85982244364fc3 100644 --- a/homeassistant/components/deconz/logbook.py +++ b/homeassistant/components/deconz/logbook.py @@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import Event -from .const import DOMAIN as DECONZ_DOMAIN +from .const import CONF_GESTURE, DOMAIN as DECONZ_DOMAIN from .deconz_event import CONF_DECONZ_EVENT, DeconzEvent from .device_trigger import ( CONF_BOTH_BUTTONS, @@ -107,8 +107,12 @@ def _get_device_event_description(modelid: str, event: str) -> tuple: device_event_descriptions: dict = REMOTES[modelid] for event_type_tuple, event_dict in device_event_descriptions.items(): - if event == event_dict[CONF_EVENT]: + if event == event_dict.get(CONF_EVENT): return event_type_tuple + if event == event_dict.get(CONF_GESTURE): + return event_type_tuple + + return (None, None) @callback @@ -125,15 +129,35 @@ def async_describe_deconz_event(event: Event) -> dict: hass, event.data[ATTR_DEVICE_ID] ) - if deconz_event.device.modelid not in REMOTES: + action = None + interface = None + data = event.data.get(CONF_EVENT) or event.data.get(CONF_GESTURE, "") + + if data and deconz_event.device.modelid in REMOTES: + action, interface = _get_device_event_description( + deconz_event.device.modelid, data + ) + + # Unknown event + if not data: return { "name": f"{deconz_event.device.name}", - "message": f"fired event '{event.data[CONF_EVENT]}'.", + "message": "fired an unknown event.", } - action, interface = _get_device_event_description( - deconz_event.device.modelid, event.data[CONF_EVENT] - ) + # No device event match + if not action: + return { + "name": f"{deconz_event.device.name}", + "message": f"fired event '{data}'.", + } + + # Gesture event + if not interface: + return { + "name": f"{deconz_event.device.name}", + "message": f"fired event '{ACTIONS[action]}'.", + } return { "name": f"{deconz_event.device.name}", diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 7315a766d5c7fd..500ca03b7edc10 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -3,6 +3,7 @@ from copy import deepcopy from homeassistant.components import logbook +from homeassistant.components.deconz.const import CONF_GESTURE from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID @@ -34,6 +35,23 @@ async def test_humanifying_deconz_event(hass): "config": {}, "uniqueid": "00:00:00:00:00:00:00:02-00", }, + "2": { + "id": "Xiaomi cube id", + "name": "Xiaomi cube", + "type": "ZHASwitch", + "modelid": "lumi.sensor_cube", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "3": { + "id": "faulty", + "name": "Faulty event", + "type": "ZHASwitch", + "state": {}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, } config_entry = await setup_deconz_integration(hass, get_state_response=data) gateway = get_gateway_from_config_entry(hass, config_entry) @@ -46,6 +64,7 @@ async def test_humanifying_deconz_event(hass): logbook.humanify( hass, [ + # Event without matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { @@ -55,6 +74,7 @@ async def test_humanifying_deconz_event(hass): CONF_UNIQUE_ID: gateway.events[0].serial, }, ), + # Event with matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { @@ -64,6 +84,36 @@ async def test_humanifying_deconz_event(hass): CONF_UNIQUE_ID: gateway.events[1].serial, }, ), + # Gesture with matching device trigger + MockLazyEventPartialState( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_GESTURE: 1, + CONF_ID: gateway.events[2].event_id, + CONF_UNIQUE_ID: gateway.events[2].serial, + }, + ), + # Unsupported device trigger + MockLazyEventPartialState( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_GESTURE: "unsupported_gesture", + CONF_ID: gateway.events[2].event_id, + CONF_UNIQUE_ID: gateway.events[2].serial, + }, + ), + # Unknown event + MockLazyEventPartialState( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: gateway.events[3].device_id, + "unknown_event": None, + CONF_ID: gateway.events[3].event_id, + CONF_UNIQUE_ID: gateway.events[3].serial, + }, + ), ], entity_attr_cache, {}, @@ -77,3 +127,15 @@ async def test_humanifying_deconz_event(hass): assert events[1]["name"] == "Hue remote" assert events[1]["domain"] == "deconz" assert events[1]["message"] == "'Long press' event for 'Dim up' was fired." + + assert events[2]["name"] == "Xiaomi cube" + assert events[2]["domain"] == "deconz" + assert events[2]["message"] == "fired event 'Shake'." + + assert events[3]["name"] == "Xiaomi cube" + assert events[3]["domain"] == "deconz" + assert events[3]["message"] == "fired event 'unsupported_gesture'." + + assert events[4]["name"] == "Faulty event" + assert events[4]["domain"] == "deconz" + assert events[4]["message"] == "fired an unknown event." From 67392338da9babb705525723862c551f8dda1ccd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Feb 2021 23:23:47 +0100 Subject: [PATCH 0231/1818] Activate manual ZHA config flow when no comports detected (#46077) --- homeassistant/components/zha/config_flow.py | 4 ++++ tests/components/zha/test_config_flow.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index 473d39c6f7a139..f59f53c79950f8 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -45,6 +45,10 @@ async def async_step_user(self, user_input=None): + (f" - {p.manufacturer}" if p.manufacturer else "") for p in ports ] + + if not list_of_ports: + return await self.async_step_pick_radio() + list_of_ports.append(CONF_MANUAL_PATH) if user_input is not None: diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index fe65def839d2dc..b3dbefbdbf0096 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -74,6 +74,7 @@ async def test_user_flow_not_detected(detect_mock, hass): assert detect_mock.await_args[0][0] == port.device +@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()])) async def test_user_flow_show_form(hass): """Test user step form.""" result = await hass.config_entries.flow.async_init( @@ -85,6 +86,17 @@ async def test_user_flow_show_form(hass): assert result["step_id"] == "user" +async def test_user_flow_show_manual(hass): + """Test user flow manual entry when no comport detected.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "pick_radio" + + async def test_user_flow_manual(hass): """Test user flow manual entry.""" From 33169cf8cd96fff693367f489f4d40ac85cfaec6 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Feb 2021 16:36:42 -0600 Subject: [PATCH 0232/1818] Fix zwave_js Notification CC sensors and binary sensors (#46072) * only include property key name in sensor name if it exists * add endpoint to binary_sensor and sensor notification CC entities if > 0 * refactor to have helper method generate name * change default behavior of generate_name * return value for notification sensor when we can't find the state * store generated name --- .../components/zwave_js/binary_sensor.py | 12 ++---- homeassistant/components/zwave_js/entity.py | 31 ++++++++++--- homeassistant/components/zwave_js/sensor.py | 43 +++++++++++-------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index f17d893e371d56..bb2e4355f168f2 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -293,6 +293,10 @@ def __init__( """Initialize a ZWaveNotificationBinarySensor entity.""" super().__init__(config_entry, client, info) self.state_key = state_key + self._name = self.generate_name( + self.info.primary_value.property_name, + [self.info.primary_value.metadata.states[self.state_key]], + ) # check if we have a custom mapping for this value self._mapping_info = self._get_sensor_mapping() @@ -301,14 +305,6 @@ def is_on(self) -> bool: """Return if the sensor is on or off.""" return int(self.info.primary_value.value) == int(self.state_key) - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" - node_name = self.info.node.name or self.info.node.device_config.description - value_name = self.info.primary_value.property_name - state_label = self.info.primary_value.metadata.states[self.state_key] - return f"{node_name}: {value_name} - {state_label}" - @property def device_class(self) -> Optional[str]: """Return device class.""" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 334a2cccd4faaa..08571ad5d8c318 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,7 +1,7 @@ """Generic Z-Wave Entity Class.""" import logging -from typing import Optional, Tuple, Union +from typing import List, Optional, Tuple, Union from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode @@ -35,6 +35,7 @@ def __init__( self.config_entry = config_entry self.client = client self.info = info + self._name = self.generate_name() # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} @@ -61,19 +62,35 @@ def device_info(self) -> dict: "identifiers": {get_device_id(self.client, self.info.node)}, } - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" + def generate_name( + self, + alternate_value_name: Optional[str] = None, + additional_info: Optional[List[str]] = None, + ) -> str: + """Generate entity name.""" + if additional_info is None: + additional_info = [] node_name = self.info.node.name or self.info.node.device_config.description value_name = ( - self.info.primary_value.metadata.label + alternate_value_name + or self.info.primary_value.metadata.label or self.info.primary_value.property_key_name or self.info.primary_value.property_name ) + name = f"{node_name}: {value_name}" + for item in additional_info: + if item: + name += f" - {item}" # append endpoint if > 1 if self.info.primary_value.endpoint > 1: - value_name += f" ({self.info.primary_value.endpoint})" - return f"{node_name}: {value_name}" + name += f" ({self.info.primary_value.endpoint})" + + return name + + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + return self._name @property def unique_id(self) -> str: diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 3d3f782bc1bd6d..78b536b81f7dcc 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -123,6 +123,17 @@ def unit_of_measurement(self) -> Optional[str]: class ZWaveNumericSensor(ZwaveSensorBase): """Representation of a Z-Wave Numeric sensor.""" + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveNumericSensor entity.""" + super().__init__(config_entry, client, info) + if self.info.primary_value.command_class == CommandClass.BASIC: + self._name = self.generate_name(self.info.primary_value.command_class_name) + @property def state(self) -> float: """Return state of the sensor.""" @@ -142,19 +153,23 @@ def unit_of_measurement(self) -> Optional[str]: return str(self.info.primary_value.metadata.unit) - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" - if self.info.primary_value.command_class == CommandClass.BASIC: - node_name = self.info.node.name or self.info.node.device_config.description - label = self.info.primary_value.command_class_name - return f"{node_name}: {label}" - return super().name - class ZWaveListSensor(ZwaveSensorBase): """Representation of a Z-Wave Numeric sensor with multiple states.""" + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveListSensor entity.""" + super().__init__(config_entry, client, info) + self._name = self.generate_name( + self.info.primary_value.property_name, + [self.info.primary_value.property_key_name], + ) + @property def state(self) -> Optional[str]: """Return state of the sensor.""" @@ -164,7 +179,7 @@ def state(self) -> Optional[str]: not str(self.info.primary_value.value) in self.info.primary_value.metadata.states ): - return None + return str(self.info.primary_value.value) return str( self.info.primary_value.metadata.states[str(self.info.primary_value.value)] ) @@ -174,11 +189,3 @@ def device_state_attributes(self) -> Optional[Dict[str, str]]: """Return the device specific state attributes.""" # add the value's int value as property for multi-value (list) items return {"value": self.info.primary_value.value} - - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" - node_name = self.info.node.name or self.info.node.device_config.description - prop_name = self.info.primary_value.property_name - prop_key_name = self.info.primary_value.property_key_name - return f"{node_name}: {prop_name} - {prop_key_name}" From ce159d7db33cc494bc3038f71e340acea1922ab6 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 6 Feb 2021 00:07:22 +0000 Subject: [PATCH 0233/1818] [ci skip] Translation update --- .../components/airvisual/translations/fr.json | 22 ++++- .../components/airvisual/translations/sv.json | 3 +- .../components/august/translations/sv.json | 8 +- .../bmw_connected_drive/translations/fr.json | 3 +- .../cert_expiry/translations/sv.json | 3 + .../components/cover/translations/sv.json | 4 + .../components/deconz/translations/sv.json | 3 +- .../components/doorbird/translations/sv.json | 3 +- .../components/econet/translations/fr.json | 22 +++++ .../fireservicerota/translations/fr.json | 3 +- .../components/fritzbox/translations/fr.json | 7 ++ .../fritzbox_callmonitor/translations/fr.json | 41 ++++++++++ .../components/hassio/translations/fr.json | 1 + .../components/homekit/translations/fr.json | 17 +++- .../huisbaasje/translations/fr.json | 21 +++++ .../components/hyperion/translations/fr.json | 37 ++++++++- .../components/icloud/translations/sv.json | 3 +- .../components/ipma/translations/fr.json | 5 ++ .../components/kodi/translations/fr.json | 1 + .../components/kulersky/translations/fr.json | 3 +- .../components/light/translations/sv.json | 2 + .../lutron_caseta/translations/fr.json | 51 +++++++++++- .../components/lyric/translations/fr.json | 7 ++ .../components/mazda/translations/fr.json | 34 ++++++++ .../components/mazda/translations/no.json | 35 ++++++++ .../mobile_app/translations/fr.json | 5 ++ .../motion_blinds/translations/fr.json | 23 +++++- .../components/myq/translations/sv.json | 7 +- .../components/mysensors/translations/en.json | 80 +++++++++---------- .../components/mysensors/translations/no.json | 34 ++++++++ .../components/neato/translations/fr.json | 6 +- .../components/nest/translations/fr.json | 11 +++ .../components/nexia/translations/sv.json | 3 +- .../components/nuheat/translations/sv.json | 5 +- .../components/nuki/translations/fr.json | 17 ++++ .../components/number/translations/fr.json | 8 ++ .../ondilo_ico/translations/fr.json | 3 + .../ovo_energy/translations/fr.json | 5 +- .../components/ozw/translations/fr.json | 3 + .../components/pi_hole/translations/fr.json | 6 ++ .../components/plaato/translations/fr.json | 37 +++++++++ .../components/plugwise/translations/fr.json | 3 +- .../components/powerwall/translations/fr.json | 1 + .../components/rachio/translations/sv.json | 3 +- .../recollect_waste/translations/fr.json | 28 +++++++ .../components/rfxtrx/translations/fr.json | 3 +- .../components/roku/translations/fr.json | 8 ++ .../components/roku/translations/sv.json | 12 +++ .../components/roomba/translations/fr.json | 5 +- .../components/shelly/translations/fr.json | 15 ++++ .../shopping_list/translations/sv.json | 14 ++++ .../simplisafe/translations/sv.json | 3 + .../components/solaredge/translations/fr.json | 3 + .../somfy_mylink/translations/fr.json | 14 +++- .../components/spotify/translations/fr.json | 5 ++ .../srp_energy/translations/fr.json | 14 +++- .../components/twinkly/translations/fr.json | 3 +- .../components/unifi/translations/fr.json | 4 +- .../components/unifi/translations/sv.json | 9 ++- .../components/vizio/translations/fr.json | 1 + .../components/vizio/translations/sv.json | 13 +++ .../components/wled/translations/sv.json | 11 ++- .../components/zwave_js/translations/fr.json | 36 ++++++++- 63 files changed, 726 insertions(+), 74 deletions(-) create mode 100644 homeassistant/components/econet/translations/fr.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/fr.json create mode 100644 homeassistant/components/huisbaasje/translations/fr.json create mode 100644 homeassistant/components/lyric/translations/fr.json create mode 100644 homeassistant/components/mazda/translations/fr.json create mode 100644 homeassistant/components/mazda/translations/no.json create mode 100644 homeassistant/components/mysensors/translations/no.json create mode 100644 homeassistant/components/nuki/translations/fr.json create mode 100644 homeassistant/components/number/translations/fr.json create mode 100644 homeassistant/components/ondilo_ico/translations/fr.json create mode 100644 homeassistant/components/recollect_waste/translations/fr.json create mode 100644 homeassistant/components/shopping_list/translations/sv.json diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index d1a0d3d511a834..62c144e075dde3 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "\u00c9chec de connexion", "general_error": "Erreur inattendue", - "invalid_api_key": "Cl\u00e9 API invalide" + "invalid_api_key": "Cl\u00e9 API invalide", + "location_not_found": "Emplacement introuvable" }, "step": { "geography": { @@ -19,6 +20,25 @@ "description": "Utilisez l'API cloud AirVisual pour surveiller une position g\u00e9ographique.", "title": "Configurer une g\u00e9ographie" }, + "geography_by_coords": { + "data": { + "api_key": "Clef d'API", + "latitude": "Latitude", + "longitude": "Longitude" + }, + "description": "Utilisez l'API cloud AirVisual pour surveiller une latitude / longitude.", + "title": "Configurer un lieu g\u00e9ographique" + }, + "geography_by_name": { + "data": { + "api_key": "Clef d'API", + "city": "Ville", + "country": "Pays", + "state": "Etat" + }, + "description": "Utilisez l'API cloud AirVisual pour surveiller une ville / un \u00e9tat / un pays.", + "title": "Configurer un lieu g\u00e9ographique" + }, "node_pro": { "data": { "ip_address": "H\u00f4te", diff --git a/homeassistant/components/airvisual/translations/sv.json b/homeassistant/components/airvisual/translations/sv.json index f375b4fc598519..ecc1c397ec46d7 100644 --- a/homeassistant/components/airvisual/translations/sv.json +++ b/homeassistant/components/airvisual/translations/sv.json @@ -21,7 +21,8 @@ "data": { "cloud_api": "Geografisk Plats", "type": "Integrationstyp" - } + }, + "title": "Konfigurera AirVisual" } } } diff --git a/homeassistant/components/august/translations/sv.json b/homeassistant/components/august/translations/sv.json index df72f5daaf33fe..a3a0b891bc6391 100644 --- a/homeassistant/components/august/translations/sv.json +++ b/homeassistant/components/august/translations/sv.json @@ -13,10 +13,16 @@ "data": { "login_method": "Inloggningsmetod", "password": "L\u00f6senord", + "timeout": "Timeout (sekunder)", "username": "Anv\u00e4ndarnamn" - } + }, + "description": "Om inloggningsmetoden \u00e4r \"e-post\" \u00e4r anv\u00e4ndarnamnet e-postadressen. Om inloggningsmetoden \u00e4r \"telefon\" \u00e4r anv\u00e4ndarnamnet telefonnumret i formatet \"+ NNNNNNNN\".", + "title": "St\u00e4ll in ett August-konto" }, "validation": { + "data": { + "code": "Verifieringskod" + }, "title": "Tv\u00e5faktorsautentisering" } } diff --git a/homeassistant/components/bmw_connected_drive/translations/fr.json b/homeassistant/components/bmw_connected_drive/translations/fr.json index 1b8f562669fb5f..900b352ecb6c20 100644 --- a/homeassistant/components/bmw_connected_drive/translations/fr.json +++ b/homeassistant/components/bmw_connected_drive/translations/fr.json @@ -21,7 +21,8 @@ "step": { "account_options": { "data": { - "read_only": "Lecture seule (uniquement capteurs et notification, pas d'ex\u00e9cution de services, pas de verrouillage)" + "read_only": "Lecture seule (uniquement capteurs et notification, pas d'ex\u00e9cution de services, pas de verrouillage)", + "use_location": "Utilisez la localisation de Home Assistant pour les sondages de localisation de voiture (obligatoire pour les v\u00e9hicules non i3 / i8 produits avant 7/2014)" } } } diff --git a/homeassistant/components/cert_expiry/translations/sv.json b/homeassistant/components/cert_expiry/translations/sv.json index 23703f11e5bedf..f00fc236d093c8 100644 --- a/homeassistant/components/cert_expiry/translations/sv.json +++ b/homeassistant/components/cert_expiry/translations/sv.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Tj\u00e4nsten har redan konfigurerats" + }, "error": { "connection_refused": "Anslutningen blev tillbakavisad under anslutning till v\u00e4rd.", "connection_timeout": "Timeout vid anslutning till den h\u00e4r v\u00e4rden", diff --git a/homeassistant/components/cover/translations/sv.json b/homeassistant/components/cover/translations/sv.json index 0a8dbecf124427..a950974033099b 100644 --- a/homeassistant/components/cover/translations/sv.json +++ b/homeassistant/components/cover/translations/sv.json @@ -1,5 +1,9 @@ { "device_automation": { + "action_type": { + "close": "St\u00e4ng {entity_name}", + "open": "\u00d6ppna {entity_name}" + }, "condition_type": { "is_closed": "{entity_name} \u00e4r st\u00e4ngd", "is_closing": "{entity_name} st\u00e4ngs", diff --git a/homeassistant/components/deconz/translations/sv.json b/homeassistant/components/deconz/translations/sv.json index 225b9f0b4e3a1a..4d709a43af1808 100644 --- a/homeassistant/components/deconz/translations/sv.json +++ b/homeassistant/components/deconz/translations/sv.json @@ -87,7 +87,8 @@ "allow_clip_sensor": "Till\u00e5t deCONZ CLIP-sensorer", "allow_deconz_groups": "Till\u00e5t deCONZ ljusgrupper" }, - "description": "Konfigurera synlighet f\u00f6r deCONZ-enhetstyper" + "description": "Konfigurera synlighet f\u00f6r deCONZ-enhetstyper", + "title": "deCONZ-inst\u00e4llningar" } } } diff --git a/homeassistant/components/doorbird/translations/sv.json b/homeassistant/components/doorbird/translations/sv.json index b2a809a576ef0d..546535fb93736b 100644 --- a/homeassistant/components/doorbird/translations/sv.json +++ b/homeassistant/components/doorbird/translations/sv.json @@ -9,7 +9,8 @@ "host": "V\u00e4rd (IP-adress)", "name": "Enhetsnamn", "username": "Anv\u00e4ndarnamn" - } + }, + "title": "Anslut till DoorBird" } } } diff --git a/homeassistant/components/econet/translations/fr.json b/homeassistant/components/econet/translations/fr.json new file mode 100644 index 00000000000000..64fd39c852ad51 --- /dev/null +++ b/homeassistant/components/econet/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", + "cannot_connect": "\u00c9chec de la connexion ", + "invalid_auth": "Authentification invalide " + }, + "error": { + "cannot_connect": "\u00c9chec de la connexion", + "invalid_auth": "Authentification invalide " + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Mot de passe" + }, + "title": "Configurer le compte Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/fr.json b/homeassistant/components/fireservicerota/translations/fr.json index a8803f63fca6e5..d0ce81458e329c 100644 --- a/homeassistant/components/fireservicerota/translations/fr.json +++ b/homeassistant/components/fireservicerota/translations/fr.json @@ -13,7 +13,8 @@ "reauth": { "data": { "password": "Mot de passe" - } + }, + "description": "Les jetons d'authentification sont invalides, connectez-vous pour les recr\u00e9er." }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json index e7a8acaa76221b..0cd425410e6072 100644 --- a/homeassistant/components/fritzbox/translations/fr.json +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -18,6 +18,13 @@ }, "description": "Voulez-vous configurer {name} ?" }, + "reauth_confirm": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Mettez \u00e0 jour vos informations de connexion pour {name} ." + }, "user": { "data": { "host": "Nom d'h\u00f4te ou adresse IP", diff --git a/homeassistant/components/fritzbox_callmonitor/translations/fr.json b/homeassistant/components/fritzbox_callmonitor/translations/fr.json new file mode 100644 index 00000000000000..cde9023273c68b --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/fr.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", + "insufficient_permissions": "L'utilisateur ne dispose pas des autorisations n\u00e9cessaires pour acc\u00e9der aux param\u00e8tres d'AVM FRITZ! Box et \u00e0 ses r\u00e9pertoires.", + "no_devices_found": "Aucun appreil trouv\u00e9 sur le r\u00e9seau " + }, + "error": { + "invalid_auth": "Authentification invalide" + }, + "flow_title": "Moniteur d'appels AVM FRITZ! Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Annuaire" + } + }, + "user": { + "data": { + "host": "Hote", + "password": "Mot de passe", + "port": "Port", + "username": "Nom d'utilisateur " + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Les pr\u00e9fixes sont mal form\u00e9s, veuillez v\u00e9rifier leur format." + }, + "step": { + "init": { + "data": { + "prefixes": "Pr\u00e9fixes (liste s\u00e9par\u00e9e par des virgules)" + }, + "title": "Configurer les pr\u00e9fixes" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fr.json b/homeassistant/components/hassio/translations/fr.json index 2bb52c3c54c106..cef14b258c452a 100644 --- a/homeassistant/components/hassio/translations/fr.json +++ b/homeassistant/components/hassio/translations/fr.json @@ -6,6 +6,7 @@ "disk_used": "Taille du disque utilis\u00e9", "docker_version": "Version de Docker", "healthy": "Sain", + "host_os": "Syst\u00e8me d'exploitation h\u00f4te", "installed_addons": "Add-ons install\u00e9s", "supervisor_api": "API du superviseur", "supervisor_version": "Version du supervisor", diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index be7d30c30ee59a..a0f10c9684a5a0 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -4,6 +4,20 @@ "port_name_in_use": "Une passerelle avec le m\u00eame nom ou port est d\u00e9j\u00e0 configur\u00e9e." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entit\u00e9" + }, + "description": "Choisissez l'entit\u00e9 \u00e0 inclure. En mode accessoire, une seule entit\u00e9 est incluse.", + "title": "S\u00e9lectionnez l'entit\u00e9 \u00e0 inclure" + }, + "bridge_mode": { + "data": { + "include_domains": "Domaines \u00e0 inclure" + }, + "description": "Choisissez les domaines \u00e0 inclure. Toutes les entit\u00e9s prises en charge dans le domaine seront incluses.", + "title": "S\u00e9lectionnez les domaines \u00e0 inclure" + }, "pairing": { "description": "D\u00e8s que le pont {name} est pr\u00eat, l'appairage sera disponible dans \"Notifications\" sous \"Configuration de la Passerelle HomeKit\".", "title": "Appairage de la Passerelle Homekit" @@ -11,7 +25,8 @@ "user": { "data": { "auto_start": "D\u00e9marrage automatique (d\u00e9sactiver si vous utilisez Z-Wave ou un autre syst\u00e8me de d\u00e9marrage diff\u00e9r\u00e9)", - "include_domains": "Domaines \u00e0 inclure" + "include_domains": "Domaines \u00e0 inclure", + "mode": "Mode" }, "description": "La passerelle HomeKit vous permettra d'acc\u00e9der \u00e0 vos entit\u00e9s Home Assistant dans HomeKit. Les passerelles HomeKit sont limit\u00e9es \u00e0 150 accessoires par instance, y compris la passerelle elle-m\u00eame. Si vous souhaitez connecter plus que le nombre maximum d'accessoires, il est recommand\u00e9 d'utiliser plusieurs passerelles HomeKit pour diff\u00e9rents domaines. La configuration d\u00e9taill\u00e9e des entit\u00e9s est uniquement disponible via YAML pour la passerelle principale.", "title": "Activer la Passerelle HomeKit" diff --git a/homeassistant/components/huisbaasje/translations/fr.json b/homeassistant/components/huisbaasje/translations/fr.json new file mode 100644 index 00000000000000..9f78d7d882605e --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 " + }, + "error": { + "connection_exception": "\u00c9chec de la connexion ", + "invalid_auth": "Authentification invalide ", + "unauthenticated_exception": "Authentification invalide ", + "unknown": "Erreur inatendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/fr.json b/homeassistant/components/hyperion/translations/fr.json index 90733a8968bd34..4b374f097a4ae6 100644 --- a/homeassistant/components/hyperion/translations/fr.json +++ b/homeassistant/components/hyperion/translations/fr.json @@ -1,10 +1,36 @@ { "config": { "abort": { + "already_configured": "Le service est d\u00e9ja configur\u00e9 ", + "auth_new_token_not_granted_error": "Le jeton nouvellement cr\u00e9\u00e9 n'a pas \u00e9t\u00e9 approuv\u00e9 sur l'interface utilisateur Hyperion", "auth_new_token_not_work_error": "\u00c9chec de l'authentification \u00e0 l'aide du jeton nouvellement cr\u00e9\u00e9", - "cannot_connect": "Echec de connection" + "auth_required_error": "Impossible de d\u00e9terminer si une autorisation est requise", + "cannot_connect": "Echec de connection", + "no_id": "L'instance Hyperion Ambilight n'a pas signal\u00e9 son identifiant" + }, + "error": { + "cannot_connect": "Echec de la connexion ", + "invalid_access_token": "jeton d'acc\u00e8s Invalide" }, "step": { + "auth": { + "data": { + "create_token": "Cr\u00e9er automatiquement un nouveau jeton", + "token": "Ou fournir un jeton pr\u00e9existant" + }, + "description": "Configurer l'autorisation sur votre serveur Hyperion Ambilight" + }, + "confirm": { + "description": "Voulez-vous ajouter cet Hyperion Ambilight \u00e0 Home Assistant? \n\n ** H\u00f4te: ** {host}\n ** Port: ** {port}\n ** ID **: {id}", + "title": "Confirmer l'ajout du service Hyperion Ambilight" + }, + "create_token": { + "description": "Choisissez ** Soumettre ** ci-dessous pour demander un nouveau jeton d'authentification. Vous serez redirig\u00e9 vers l'interface utilisateur Hyperion pour approuver la demande. Veuillez v\u00e9rifier que l'identifiant affich\u00e9 est \" {auth_id} \"", + "title": "Cr\u00e9er automatiquement un nouveau jeton d'authentification" + }, + "create_token_external": { + "title": "Accepter un nouveau jeton dans l'interface utilisateur Hyperion" + }, "user": { "data": { "host": "H\u00f4te", @@ -12,5 +38,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "Priorit\u00e9 Hyperion \u00e0 utiliser pour les couleurs et les effets" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/sv.json b/homeassistant/components/icloud/translations/sv.json index 6caf02f56c56aa..2bba72d49dfd3a 100644 --- a/homeassistant/components/icloud/translations/sv.json +++ b/homeassistant/components/icloud/translations/sv.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Kontot har redan konfigurerats" + "already_configured": "Kontot har redan konfigurerats", + "no_device": "Ingen av dina enheter har \"Hitta min iPhone\" aktiverat" }, "error": { "send_verification_code": "Det gick inte att skicka verifieringskod", diff --git a/homeassistant/components/ipma/translations/fr.json b/homeassistant/components/ipma/translations/fr.json index 9a3a11a7a739e8..eaff9d211db431 100644 --- a/homeassistant/components/ipma/translations/fr.json +++ b/homeassistant/components/ipma/translations/fr.json @@ -15,5 +15,10 @@ "title": "Emplacement" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Point de terminaison de l'API IPMA accessible" + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/fr.json b/homeassistant/components/kodi/translations/fr.json index fd0d3e38e81602..a7c4b3f34a1649 100644 --- a/homeassistant/components/kodi/translations/fr.json +++ b/homeassistant/components/kodi/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification erron\u00e9e", + "no_uuid": "L'instance Kodi n'a pas d'identifiant unique. Cela est probablement d\u00fb \u00e0 une ancienne version de Kodi (17.x ou inf\u00e9rieure). Vous pouvez configurer l'int\u00e9gration manuellement ou passer \u00e0 une version plus r\u00e9cente de Kodi.", "unknown": "Erreur inattendue" }, "error": { diff --git a/homeassistant/components/kulersky/translations/fr.json b/homeassistant/components/kulersky/translations/fr.json index 4c984a556904b5..649a3d387bd1f2 100644 --- a/homeassistant/components/kulersky/translations/fr.json +++ b/homeassistant/components/kulersky/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Aucun appareil n'a \u00e9t\u00e9 d\u00e9tect\u00e9 sur le r\u00e9seau" + "no_devices_found": "Aucun appareil n'a \u00e9t\u00e9 d\u00e9tect\u00e9 sur le r\u00e9seau", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Seulement une seule configuration est possible " } } } \ No newline at end of file diff --git a/homeassistant/components/light/translations/sv.json b/homeassistant/components/light/translations/sv.json index 0d0e29a87ed93b..d5f0bdaf767143 100644 --- a/homeassistant/components/light/translations/sv.json +++ b/homeassistant/components/light/translations/sv.json @@ -1,6 +1,8 @@ { "device_automation": { "action_type": { + "brightness_decrease": "Minska ljusstyrkan f\u00f6r {entity_name}", + "brightness_increase": "\u00d6ka ljusstyrkan f\u00f6r {entity_name}", "toggle": "V\u00e4xla {entity_name}", "turn_off": "St\u00e4ng av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" diff --git a/homeassistant/components/lutron_caseta/translations/fr.json b/homeassistant/components/lutron_caseta/translations/fr.json index 0674172e97581f..4d7dccd0acf018 100644 --- a/homeassistant/components/lutron_caseta/translations/fr.json +++ b/homeassistant/components/lutron_caseta/translations/fr.json @@ -2,16 +2,65 @@ "config": { "abort": { "already_configured": "Pont Cas\u00e9ta d\u00e9j\u00e0 configur\u00e9.", - "cannot_connect": "Installation annul\u00e9e du pont Cas\u00e9ta en raison d'un \u00e9chec de connexion." + "cannot_connect": "Installation annul\u00e9e du pont Cas\u00e9ta en raison d'un \u00e9chec de connexion.", + "not_lutron_device": "L'appareil d\u00e9couvert n'est pas un appareil Lutron" }, "error": { "cannot_connect": "\u00c9chec de la connexion \u00e0 la passerelle Cas\u00e9ta; v\u00e9rifiez la configuration de votre h\u00f4te et de votre certificat." }, + "flow_title": "Lutron Cas\u00e9ta {name} ( {host} )", "step": { "import_failed": { "description": "Impossible de configurer la passerelle (h\u00f4te: {host} ) import\u00e9 \u00e0 partir de configuration.yaml.", "title": "\u00c9chec de l'importation de la configuration de la passerelle Cas\u00e9ta." + }, + "link": { + "description": "Pour jumeler avec {name} ( {host} ), apr\u00e8s avoir soumis ce formulaire, appuyez sur le bouton noir \u00e0 l'arri\u00e8re du pont.", + "title": "Paire avec le pont" + }, + "user": { + "data": { + "host": "Hote" + }, + "description": "Saisissez l'adresse IP de l'appareil.", + "title": "Se connecter automatiquement au pont" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Premier bouton", + "button_2": "Deuxi\u00e8me bouton", + "button_3": "Troisi\u00e8me bouton", + "button_4": "Quatri\u00e8me bouton", + "close_1": "Fermer 1", + "close_2": "Fermer 2", + "close_3": "Fermer 3", + "close_4": "Fermer 4", + "close_all": "Ferme tout", + "group_1_button_1": "Premier bouton du premier groupe", + "group_1_button_2": "Premier groupe deuxi\u00e8me bouton", + "group_2_button_1": "Premier bouton du deuxi\u00e8me groupe", + "group_2_button_2": "Deuxi\u00e8me bouton du deuxi\u00e8me groupe", + "lower_all": "Tout baisser", + "off": "Eteint", + "on": "Allumer", + "open_1": "Ouvrir 1", + "open_2": "Ouvrir 2", + "open_3": "Ouvrir 3", + "open_4": "Ouvrir 4", + "open_all": "Ouvre tout", + "raise_all": "Lever tout", + "stop": "Stop (favori)", + "stop_1": "Arr\u00eat 1", + "stop_2": "Arr\u00eat 2", + "stop_3": "Arr\u00eat 3", + "stop_4": "Arr\u00eat 4", + "stop_all": "Arr\u00eate tout" + }, + "trigger_type": { + "press": "\" {subtype} \" appuy\u00e9", + "release": "\" {subtype} \" publi\u00e9" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/fr.json b/homeassistant/components/lyric/translations/fr.json new file mode 100644 index 00000000000000..794e85b7fa6624 --- /dev/null +++ b/homeassistant/components/lyric/translations/fr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/fr.json b/homeassistant/components/mazda/translations/fr.json new file mode 100644 index 00000000000000..e9ccb013b5eef2 --- /dev/null +++ b/homeassistant/components/mazda/translations/fr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9ja configur\u00e9" + }, + "error": { + "account_locked": "Compte bloqu\u00e9. Veuillez r\u00e9essayer plus tard.", + "cannot_connect": "Echec de la connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "reauth": { + "data": { + "email": "Email", + "password": "Mot de passe", + "region": "R\u00e9gion" + }, + "description": "L'authentification a \u00e9chou\u00e9 pour les services connect\u00e9s Mazda. Veuillez saisir vos informations d'identification actuelles.", + "title": "Services connect\u00e9s Mazda - \u00c9chec de l'authentification" + }, + "user": { + "data": { + "email": "Email", + "password": "Mot de passe", + "region": "R\u00e9gion" + }, + "description": "Veuillez saisir l'adresse e-mail et le mot de passe que vous utilisez pour vous connecter \u00e0 l'application mobile MyMazda.", + "title": "Services connect\u00e9s Mazda - Ajouter un compte" + } + } + }, + "title": "Services connect\u00e9s Mazda" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/no.json b/homeassistant/components/mazda/translations/no.json new file mode 100644 index 00000000000000..e3a05de51f9e63 --- /dev/null +++ b/homeassistant/components/mazda/translations/no.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "account_locked": "Kontoen er l\u00e5st. Pr\u00f8v igjen senere.", + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "reauth": { + "data": { + "email": "E-post", + "password": "Passord", + "region": "Region" + }, + "description": "Autentisering mislyktes for Mazda Connected Services. Vennligst skriv inn din n\u00e5v\u00e6rende legitimasjon.", + "title": "Mazda Connected Services - Autentisering mislyktes" + }, + "user": { + "data": { + "email": "E-post", + "password": "Passord", + "region": "Region" + }, + "description": "Vennligst skriv inn e-postadressen og passordet du bruker for \u00e5 logge p\u00e5 MyMazda-mobilappen.", + "title": "Mazda Connected Services - Legg til konto" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/fr.json b/homeassistant/components/mobile_app/translations/fr.json index 09317e4a00df4b..f4b0f590e48527 100644 --- a/homeassistant/components/mobile_app/translations/fr.json +++ b/homeassistant/components/mobile_app/translations/fr.json @@ -8,5 +8,10 @@ "description": "Voulez-vous configurer le composant Application mobile?" } } + }, + "device_automation": { + "action_type": { + "notify": "Envoyer une notification" + } } } \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/fr.json b/homeassistant/components/motion_blinds/translations/fr.json index 86d008b9e6d768..da8abbcc5645cf 100644 --- a/homeassistant/components/motion_blinds/translations/fr.json +++ b/homeassistant/components/motion_blinds/translations/fr.json @@ -1,16 +1,35 @@ { "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", + "connection_error": "\u00c9chec de la connexion " + }, + "error": { + "discovery_error": "Impossible de d\u00e9couvrir une Motion Gateway" + }, + "flow_title": "Stores de mouvement", "step": { "connect": { "data": { "api_key": "Cl\u00e9 API" }, - "description": "Vous aurez besoin de la cl\u00e9 API de 16 caract\u00e8res, voir https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key pour les instructions" + "description": "Vous aurez besoin de la cl\u00e9 API de 16 caract\u00e8res, voir https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key pour les instructions", + "title": "Stores de mouvement" }, "select": { "data": { "select_ip": "Adresse IP" - } + }, + "description": "Ex\u00e9cutez \u00e0 nouveau la configuration si vous souhaitez connecter des passerelles Motion suppl\u00e9mentaires", + "title": "S\u00e9lectionnez la Motion Gateway que vous souhaitez connecter" + }, + "user": { + "data": { + "api_key": "Clef d'API", + "host": "Adresse IP" + }, + "description": "Connectez-vous \u00e0 votre Motion Gateway, si l'adresse IP n'est pas d\u00e9finie, la d\u00e9tection automatique est utilis\u00e9e", + "title": "Stores de mouvement" } } } diff --git a/homeassistant/components/myq/translations/sv.json b/homeassistant/components/myq/translations/sv.json index 1243ca600f0d83..da06f32aa924b8 100644 --- a/homeassistant/components/myq/translations/sv.json +++ b/homeassistant/components/myq/translations/sv.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "Tj\u00e4nsten har redan konfigurerats" + }, "error": { + "cannot_connect": "Anslutningen misslyckades", "invalid_auth": "Ogiltig autentisering", "unknown": "Ov\u00e4ntat fel" }, @@ -9,7 +13,8 @@ "data": { "password": "L\u00f6senord", "username": "Anv\u00e4ndarnamn" - } + }, + "title": "Anslut till MyQ Gateway" } } } diff --git a/homeassistant/components/mysensors/translations/en.json b/homeassistant/components/mysensors/translations/en.json index d7730ba09b6e74..63af85488f0a09 100644 --- a/homeassistant/components/mysensors/translations/en.json +++ b/homeassistant/components/mysensors/translations/en.json @@ -3,75 +3,75 @@ "abort": { "already_configured": "Device is already configured", "cannot_connect": "Failed to connect", - "invalid_subscribe_topic": "Invalid subscribe topic", - "invalid_publish_topic": "Invalid publish topic", - "duplicate_topic": "Topic already in use", - "same_topic": "Subscribe and publish topics are the same", - "invalid_port": "Invalid port number", - "invalid_persistence_file": "Invalid persistence file", "duplicate_persistence_file": "Persistence file already in use", + "duplicate_topic": "Topic already in use", + "invalid_auth": "Invalid authentication", + "invalid_device": "Invalid device", "invalid_ip": "Invalid IP address", + "invalid_persistence_file": "Invalid persistence file", + "invalid_port": "Invalid port number", + "invalid_publish_topic": "Invalid publish topic", "invalid_serial": "Invalid serial port", - "invalid_device": "Invalid device", + "invalid_subscribe_topic": "Invalid subscribe topic", "invalid_version": "Invalid MySensors version", "not_a_number": "Please enter a number", "port_out_of_range": "Port number must be at least 1 and at most 65535", + "same_topic": "Subscribe and publish topics are the same", "unknown": "Unexpected error" }, "error": { "already_configured": "Device is already configured", "cannot_connect": "Failed to connect", - "invalid_subscribe_topic": "Invalid subscribe topic", - "invalid_publish_topic": "Invalid publish topic", - "duplicate_topic": "Topic already in use", - "same_topic": "Subscribe and publish topics are the same", - "invalid_port": "Invalid port number", - "invalid_persistence_file": "Invalid persistence file", "duplicate_persistence_file": "Persistence file already in use", + "duplicate_topic": "Topic already in use", + "invalid_auth": "Invalid authentication", + "invalid_device": "Invalid device", "invalid_ip": "Invalid IP address", + "invalid_persistence_file": "Invalid persistence file", + "invalid_port": "Invalid port number", + "invalid_publish_topic": "Invalid publish topic", "invalid_serial": "Invalid serial port", - "invalid_device": "Invalid device", + "invalid_subscribe_topic": "Invalid subscribe topic", "invalid_version": "Invalid MySensors version", "not_a_number": "Please enter a number", "port_out_of_range": "Port number must be at least 1 and at most 65535", + "same_topic": "Subscribe and publish topics are the same", "unknown": "Unexpected error" }, "step": { - "user": { + "gw_mqtt": { "data": { - "optimistic": "optimistic", - "persistence": "persistence", - "gateway_type": "Gateway type" + "persistence_file": "persistence file (leave empty to auto-generate)", + "retain": "mqtt retain", + "topic_in_prefix": "prefix for input topics (topic_in_prefix)", + "topic_out_prefix": "prefix for output topics (topic_out_prefix)", + "version": "MySensors version" }, - "description": "Choose connection method to the gateway" + "description": "MQTT gateway setup" + }, + "gw_serial": { + "data": { + "baud_rate": "baud rate", + "device": "Serial port", + "persistence_file": "persistence file (leave empty to auto-generate)", + "version": "MySensors version" + }, + "description": "Serial gateway setup" }, "gw_tcp": { - "description": "Ethernet gateway setup", "data": { "device": "IP address of the gateway", + "persistence_file": "persistence file (leave empty to auto-generate)", "tcp_port": "port", - "version": "MySensors version", - "persistence_file": "persistence file (leave empty to auto-generate)" - } - }, - "gw_serial": { - "description": "Serial gateway setup", - "data": { - "device": "Serial port", - "baud_rate": "baud rate", - "version": "MySensors version", - "persistence_file": "persistence file (leave empty to auto-generate)" - } + "version": "MySensors version" + }, + "description": "Ethernet gateway setup" }, - "gw_mqtt": { - "description": "MQTT gateway setup", + "user": { "data": { - "retain": "mqtt retain", - "topic_in_prefix": "prefix for input topics (topic_in_prefix)", - "topic_out_prefix": "prefix for output topics (topic_out_prefix)", - "version": "MySensors version", - "persistence_file": "persistence file (leave empty to auto-generate)" - } + "gateway_type": "Gateway type" + }, + "description": "Choose connection method to the gateway" } } }, diff --git a/homeassistant/components/mysensors/translations/no.json b/homeassistant/components/mysensors/translations/no.json new file mode 100644 index 00000000000000..a13140df64026d --- /dev/null +++ b/homeassistant/components/mysensors/translations/no.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "error": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "gw_mqtt": { + "data": { + "version": "MySensors versjon" + } + }, + "gw_serial": { + "data": { + "version": "MySensors versjon" + } + }, + "user": { + "data": { + "gateway_type": "" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/fr.json b/homeassistant/components/neato/translations/fr.json index 69f2186c54c6e6..4b71a93a783903 100644 --- a/homeassistant/components/neato/translations/fr.json +++ b/homeassistant/components/neato/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "D\u00e9j\u00e0 configur\u00e9", - "invalid_auth": "Authentification invalide" + "invalid_auth": "Authentification invalide", + "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation " }, "create_entry": { "default": "Voir [Documentation Neato]({docs_url})." @@ -22,5 +23,6 @@ "title": "Informations compte Neato" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/fr.json b/homeassistant/components/nest/translations/fr.json index 03b55458e9bdc3..0d1f5b761f569f 100644 --- a/homeassistant/components/nest/translations/fr.json +++ b/homeassistant/components/nest/translations/fr.json @@ -34,7 +34,18 @@ }, "pick_implementation": { "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + }, + "reauth_confirm": { + "description": "L'int\u00e9gration Nest doit r\u00e9-authentifier votre compte" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "Mouvement d\u00e9tect\u00e9", + "camera_person": "Personne d\u00e9tect\u00e9e", + "camera_sound": "Son d\u00e9tect\u00e9", + "doorbell_chime": "Sonnette enfonc\u00e9e" + } } } \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/sv.json b/homeassistant/components/nexia/translations/sv.json index 9cfd620ac7329c..60044361f658d8 100644 --- a/homeassistant/components/nexia/translations/sv.json +++ b/homeassistant/components/nexia/translations/sv.json @@ -10,7 +10,8 @@ "data": { "password": "L\u00f6senord", "username": "Anv\u00e4ndarnamn" - } + }, + "title": "Anslut till mynexia.com" } } } diff --git a/homeassistant/components/nuheat/translations/sv.json b/homeassistant/components/nuheat/translations/sv.json index 9cfd620ac7329c..327bdf8c4caa6f 100644 --- a/homeassistant/components/nuheat/translations/sv.json +++ b/homeassistant/components/nuheat/translations/sv.json @@ -3,14 +3,17 @@ "error": { "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", "invalid_auth": "Ogiltig autentisering", + "invalid_thermostat": "Termostatens serienummer \u00e4r ogiltigt.", "unknown": "Ov\u00e4ntat fel" }, "step": { "user": { "data": { "password": "L\u00f6senord", + "serial_number": "Termostatens serienummer.", "username": "Anv\u00e4ndarnamn" - } + }, + "description": "F\u00e5 tillg\u00e5ng till din termostats serienummer eller ID genom att logga in p\u00e5 https://MyNuHeat.com och v\u00e4lja din termostat." } } } diff --git a/homeassistant/components/nuki/translations/fr.json b/homeassistant/components/nuki/translations/fr.json new file mode 100644 index 00000000000000..26a949038d5681 --- /dev/null +++ b/homeassistant/components/nuki/translations/fr.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec de la connexion ", + "invalid_auth": "Authentification invalide " + }, + "step": { + "user": { + "data": { + "host": "Hote", + "port": "Port", + "token": "jeton d'acc\u00e8s" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/fr.json b/homeassistant/components/number/translations/fr.json new file mode 100644 index 00000000000000..9f49c3fb96224d --- /dev/null +++ b/homeassistant/components/number/translations/fr.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "D\u00e9finir la valeur de {entity_name}" + } + }, + "title": "Nombre" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/fr.json b/homeassistant/components/ondilo_ico/translations/fr.json new file mode 100644 index 00000000000000..33271e594a3a67 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/fr.json @@ -0,0 +1,3 @@ +{ + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/fr.json b/homeassistant/components/ovo_energy/translations/fr.json index 351e20641aaff1..9be6b4d3c114cc 100644 --- a/homeassistant/components/ovo_energy/translations/fr.json +++ b/homeassistant/components/ovo_energy/translations/fr.json @@ -5,11 +5,14 @@ "cannot_connect": "\u00c9chec de connexion", "invalid_auth": "Authentification invalide" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { "data": { "password": "Mot de passe" - } + }, + "description": "L'authentification a \u00e9chou\u00e9 pour OVO Energy. Veuillez saisir vos informations d'identification actuelles.", + "title": "R\u00e9authentification" }, "user": { "data": { diff --git a/homeassistant/components/ozw/translations/fr.json b/homeassistant/components/ozw/translations/fr.json index c4ea835d86c4c1..5eed478549df05 100644 --- a/homeassistant/components/ozw/translations/fr.json +++ b/homeassistant/components/ozw/translations/fr.json @@ -18,6 +18,9 @@ "hassio_confirm": { "title": "Configurer l\u2019int\u00e9gration OpenZWave avec l\u2019add-on OpenZWave" }, + "install_addon": { + "title": "L'installation du module compl\u00e9mentaire OpenZWave a commenc\u00e9" + }, "on_supervisor": { "data": { "use_addon": "Utiliser l'add-on OpenZWave Supervisor" diff --git a/homeassistant/components/pi_hole/translations/fr.json b/homeassistant/components/pi_hole/translations/fr.json index 1ccc5ac7d761f9..152fb0f3def637 100644 --- a/homeassistant/components/pi_hole/translations/fr.json +++ b/homeassistant/components/pi_hole/translations/fr.json @@ -7,6 +7,11 @@ "cannot_connect": "Connexion impossible" }, "step": { + "api_key": { + "data": { + "api_key": "Clef d'API" + } + }, "user": { "data": { "api_key": "Cl\u00e9 d'API", @@ -15,6 +20,7 @@ "name": "Nom", "port": "Port", "ssl": "Utiliser SSL", + "statistics_only": "Statistiques uniquement", "verify_ssl": "V\u00e9rifier le certificat SSL" } } diff --git a/homeassistant/components/plaato/translations/fr.json b/homeassistant/components/plaato/translations/fr.json index bc442a04c60188..ab3c01144ddf3d 100644 --- a/homeassistant/components/plaato/translations/fr.json +++ b/homeassistant/components/plaato/translations/fr.json @@ -1,16 +1,53 @@ { "config": { "abort": { + "already_configured": "Le compte est d\u00e9ja configur\u00e9", "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." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonction Webhook dans Plaato Airlock. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." }, + "error": { + "invalid_webhook_device": "Vous avez s\u00e9lectionn\u00e9 un appareil qui ne prend pas en charge l'envoi de donn\u00e9es vers un webhook. Il n'est disponible que pour le sas", + "no_api_method": "Vous devez ajouter un jeton d'authentification ou s\u00e9lectionner un webhook", + "no_auth_token": "Vous devez ajouter un jeton d'authentification" + }, "step": { + "api_method": { + "data": { + "token": "Collez le jeton d'authentification ici", + "use_webhook": "Utiliser le webhook" + }, + "description": "Pour pouvoir interroger l'API, un \u00abauth_token\u00bb est n\u00e9cessaire. Il peut \u00eatre obtenu en suivant [ces] instructions (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \n\n Appareil s\u00e9lectionn\u00e9: ** {device_type} ** \n\n Si vous pr\u00e9f\u00e9rez utiliser la m\u00e9thode Webhook int\u00e9gr\u00e9e (Airlock uniquement), veuillez cocher la case ci-dessous et laisser le jeton d'authentification vide", + "title": "S\u00e9lectionnez la m\u00e9thode API" + }, "user": { + "data": { + "device_name": "Nommez votre appareil", + "device_type": "Type d'appareil Plaato" + }, "description": "\u00cates-vous s\u00fbr de vouloir installer le Plaato Airlock ?", "title": "Configurer le Webhook Plaato" + }, + "webhook": { + "description": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonction Webhook dans Plaato Airlock. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails.", + "title": "Webhook \u00e0 utiliser" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Intervalle de mise \u00e0 jour (minutes)" + }, + "description": "D\u00e9finir l'intervalle de mise \u00e0 jour (minutes)", + "title": "Options pour Plaato" + }, + "webhook": { + "description": "Informations sur le webhook: \n\n - URL: ` {webhook_url} `\n - M\u00e9thode: POST \n\n", + "title": "Options pour Plaato Airlock" } } } diff --git a/homeassistant/components/plugwise/translations/fr.json b/homeassistant/components/plugwise/translations/fr.json index 2fdc3502571439..f89c850913627c 100644 --- a/homeassistant/components/plugwise/translations/fr.json +++ b/homeassistant/components/plugwise/translations/fr.json @@ -21,7 +21,8 @@ "data": { "host": "Adresse IP", "password": "ID Smile", - "port": "Port" + "port": "Port", + "username": "Nom d'utilisateur de sourire" }, "description": "Veuillez saisir :", "title": "Se connecter \u00e0 Smile" diff --git a/homeassistant/components/powerwall/translations/fr.json b/homeassistant/components/powerwall/translations/fr.json index 3ddc6634557258..2086393dfefdf8 100644 --- a/homeassistant/components/powerwall/translations/fr.json +++ b/homeassistant/components/powerwall/translations/fr.json @@ -8,6 +8,7 @@ "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": "Tesla Powerwall ( {ip_address} )", "step": { "user": { "data": { diff --git a/homeassistant/components/rachio/translations/sv.json b/homeassistant/components/rachio/translations/sv.json index c2da7a1c01d7f4..4932b17ebfa49d 100644 --- a/homeassistant/components/rachio/translations/sv.json +++ b/homeassistant/components/rachio/translations/sv.json @@ -12,7 +12,8 @@ "user": { "data": { "api_key": "API nyckel" - } + }, + "title": "Anslut till Rachio-enheten" } } } diff --git a/homeassistant/components/recollect_waste/translations/fr.json b/homeassistant/components/recollect_waste/translations/fr.json new file mode 100644 index 00000000000000..dc62c8f520a384 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/fr.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 " + }, + "error": { + "invalid_place_or_service_id": "ID de lieu ou de service non valide" + }, + "step": { + "user": { + "data": { + "place_id": "Identifiant de lieu", + "service_id": "ID de service" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "Utilisez des noms conviviaux pour les types de ramassage (si possible)" + }, + "title": "Configurer Recollect Waste" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/fr.json b/homeassistant/components/rfxtrx/translations/fr.json index baf8d0f5148678..c0df7233458983 100644 --- a/homeassistant/components/rfxtrx/translations/fr.json +++ b/homeassistant/components/rfxtrx/translations/fr.json @@ -64,7 +64,8 @@ "off_delay": "D\u00e9lai d'arr\u00eat", "off_delay_enabled": "Activer le d\u00e9lai d'arr\u00eat", "replace_device": "S\u00e9lectionnez l'appareil \u00e0 remplacer", - "signal_repetitions": "Nombre de r\u00e9p\u00e9titions du signal" + "signal_repetitions": "Nombre de r\u00e9p\u00e9titions du signal", + "venetian_blind_mode": "Mode store v\u00e9nitien" }, "title": "Configurer les options de l'appareil" } diff --git a/homeassistant/components/roku/translations/fr.json b/homeassistant/components/roku/translations/fr.json index 7aba1ef0489fd6..6d2379925921eb 100644 --- a/homeassistant/components/roku/translations/fr.json +++ b/homeassistant/components/roku/translations/fr.json @@ -9,6 +9,14 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "data": { + "one": "Vide", + "other": "Vide" + }, + "description": "Voulez-vous configurer {name} ?", + "title": "Roku" + }, "ssdp_confirm": { "data": { "one": "Vide", diff --git a/homeassistant/components/roku/translations/sv.json b/homeassistant/components/roku/translations/sv.json index 6d6f9223466dd2..524e7753548a21 100644 --- a/homeassistant/components/roku/translations/sv.json +++ b/homeassistant/components/roku/translations/sv.json @@ -2,6 +2,18 @@ "config": { "abort": { "unknown": "Ov\u00e4ntat fel" + }, + "flow_title": "Roku: {name}", + "step": { + "ssdp_confirm": { + "description": "Vill du konfigurera {name}?", + "title": "Roku" + }, + "user": { + "data": { + "host": "V\u00e4rd" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 8142d3acf1367b..b4bc615e4e3d78 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "cannot_connect": "Echec de connection" + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", + "cannot_connect": "Echec de connection", + "not_irobot_device": "L'appareil d\u00e9couvert n'est pas un appareil iRobot" }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer" }, + "flow_title": "iRobot {name} ( {host} )", "step": { "init": { "data": { diff --git a/homeassistant/components/shelly/translations/fr.json b/homeassistant/components/shelly/translations/fr.json index e40da9f5e681fa..0dea9111c628ec 100644 --- a/homeassistant/components/shelly/translations/fr.json +++ b/homeassistant/components/shelly/translations/fr.json @@ -27,5 +27,20 @@ "description": "Avant la configuration, l'appareil aliment\u00e9 par batterie doit \u00eatre r\u00e9veill\u00e9 en appuyant sur le bouton de l'appareil." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Bouton", + "button1": "Premier bouton", + "button2": "Deuxi\u00e8me bouton", + "button3": "Troisi\u00e8me bouton" + }, + "trigger_type": { + "double": "{subtype} double-cliqu\u00e9", + "long": " {sous-type} long cliqu\u00e9", + "single": "{subtype} simple clic", + "single_long": "{subtype} simple clic, puis un clic long", + "triple": "{subtype} cliqu\u00e9 trois fois" + } } } \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/sv.json b/homeassistant/components/shopping_list/translations/sv.json new file mode 100644 index 00000000000000..0202ae3a53fa23 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/sv.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Tj\u00e4nsten har redan konfigurerats" + }, + "step": { + "user": { + "description": "Vill du konfigurera ink\u00f6pslistan?", + "title": "Ink\u00f6pslista" + } + } + }, + "title": "Ink\u00f6pslista" +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/sv.json b/homeassistant/components/simplisafe/translations/sv.json index a1bfb4400be850..a4e8e0520733b6 100644 --- a/homeassistant/components/simplisafe/translations/sv.json +++ b/homeassistant/components/simplisafe/translations/sv.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Det h\u00e4r SimpliSafe-kontot har redan konfigurerats." + }, "error": { "identifier_exists": "Kontot \u00e4r redan registrerat" }, diff --git a/homeassistant/components/solaredge/translations/fr.json b/homeassistant/components/solaredge/translations/fr.json index 6fa6fdf264ffa7..3eea6678d03e40 100644 --- a/homeassistant/components/solaredge/translations/fr.json +++ b/homeassistant/components/solaredge/translations/fr.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9" }, "error": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", + "could_not_connect": "Impossible de se connecter \u00e0 l'API solaredge", "invalid_api_key": "Cl\u00e9 API invalide", "site_exists": "Ce site est d\u00e9j\u00e0 configur\u00e9", "site_not_active": "The site n'est pas actif" diff --git a/homeassistant/components/somfy_mylink/translations/fr.json b/homeassistant/components/somfy_mylink/translations/fr.json index 96904b6038d7be..bee2ea3ba1361e 100644 --- a/homeassistant/components/somfy_mylink/translations/fr.json +++ b/homeassistant/components/somfy_mylink/translations/fr.json @@ -1,13 +1,22 @@ { "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9ja configur\u00e9 " + }, "error": { + "cannot_connect": "\u00c9chec de la connexion ", + "invalid_auth": "Authentification invalide ", "unknown": "Erreur inattendue" }, + "flow_title": "Somfy MyLink {mac} ( {ip} )", "step": { "user": { "data": { - "host": "H\u00f4te" - } + "host": "H\u00f4te", + "port": "Port", + "system_id": "ID syst\u00e8me" + }, + "description": "L'ID syst\u00e8me peut \u00eatre obtenu dans l'application MyLink sous Int\u00e9gration en s\u00e9lectionnant n'importe quel service non cloud." } } }, @@ -20,6 +29,7 @@ "data": { "reverse": "La couverture est invers\u00e9e" }, + "description": "Configurer les options pour \u00ab {entity_id} \u00bb", "title": "Configurez une entit\u00e9 sp\u00e9cifique" }, "init": { diff --git a/homeassistant/components/spotify/translations/fr.json b/homeassistant/components/spotify/translations/fr.json index f4f6566e88dfea..d6b5838feb5e8e 100644 --- a/homeassistant/components/spotify/translations/fr.json +++ b/homeassistant/components/spotify/translations/fr.json @@ -18,5 +18,10 @@ "title": "R\u00e9-authentifier avec Spotify" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Point de terminaison de l'API Spotify accessible" + } } } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/fr.json b/homeassistant/components/srp_energy/translations/fr.json index 0cc85aff649130..b9b33cfa9306ab 100644 --- a/homeassistant/components/srp_energy/translations/fr.json +++ b/homeassistant/components/srp_energy/translations/fr.json @@ -1,14 +1,24 @@ { "config": { + "abort": { + "single_instance_allowed": "D\u00e9ja configur\u00e9. Seulement une seule configuration est possible " + }, "error": { + "cannot_connect": "\u00c9chec de la connexion ", + "invalid_account": "L'ID de compte doit \u00eatre un num\u00e9ro \u00e0 9 chiffres", + "invalid_auth": "Authentification invalide ", "unknown": "Erreur inattendue" }, "step": { "user": { "data": { - "password": "Mot de passe" + "id": "Identifiant de compte", + "is_tou": "Est le plan de temps d'utilisation", + "password": "Mot de passe", + "username": "Nom d'utilisateur " } } } - } + }, + "title": "\u00c9nergie SRP" } \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/fr.json b/homeassistant/components/twinkly/translations/fr.json index 5071b7e302a6a7..c26edea54eea0e 100644 --- a/homeassistant/components/twinkly/translations/fr.json +++ b/homeassistant/components/twinkly/translations/fr.json @@ -11,7 +11,8 @@ "data": { "host": "Nom r\u00e9seau (ou adresse IP) de votre Twinkly" }, - "description": "Configurer votre Twinkly" + "description": "Configurer votre Twinkly", + "title": "Twinkly" } } } diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index 6e5412ba3d2204..49d9c68c01c06c 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9", + "configuration_updated": "Configuration mise \u00e0 jour." }, "error": { "faulty_credentials": "Authentification invalide", "service_unavailable": "\u00c9chec de connexion", "unknown_client_mac": "Aucun client disponible sur cette adresse MAC" }, + "flow_title": "UniFi Network {site} ( {host} )", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/sv.json b/homeassistant/components/unifi/translations/sv.json index eb4e0d8ee3d43d..2e4851e70ed026 100644 --- a/homeassistant/components/unifi/translations/sv.json +++ b/homeassistant/components/unifi/translations/sv.json @@ -24,13 +24,17 @@ }, "options": { "step": { + "client_control": { + "title": "UniFi-inst\u00e4llningar 2/3" + }, "device_tracker": { "data": { "detection_time": "Tid i sekunder fr\u00e5n senast sett tills den anses borta", "track_clients": "Sp\u00e5ra n\u00e4tverksklienter", "track_devices": "Sp\u00e5ra n\u00e4tverksenheter (Ubiquiti-enheter)", "track_wired_clients": "Inkludera tr\u00e5dbundna n\u00e4tverksklienter" - } + }, + "title": "UniFi-inst\u00e4llningar 1/3" }, "init": { "data": { @@ -44,7 +48,8 @@ "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Skapa bandbreddsanv\u00e4ndningssensorer f\u00f6r n\u00e4tverksklienter" - } + }, + "title": "UniFi-inst\u00e4llningar 2/3" } } } diff --git a/homeassistant/components/vizio/translations/fr.json b/homeassistant/components/vizio/translations/fr.json index 89c46fd5959681..5fc9158c80345a 100644 --- a/homeassistant/components/vizio/translations/fr.json +++ b/homeassistant/components/vizio/translations/fr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de la connexion ", "updated_entry": "Cette entr\u00e9e a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9e mais le nom et/ou les options d\u00e9finis dans la configuration ne correspondent pas \u00e0 la configuration pr\u00e9c\u00e9demment import\u00e9e, de sorte que l'entr\u00e9e de configuration a \u00e9t\u00e9 mise \u00e0 jour en cons\u00e9quence." }, "error": { diff --git a/homeassistant/components/vizio/translations/sv.json b/homeassistant/components/vizio/translations/sv.json index 8e5ebe47c43378..82483d80fe8d49 100644 --- a/homeassistant/components/vizio/translations/sv.json +++ b/homeassistant/components/vizio/translations/sv.json @@ -4,6 +4,19 @@ "updated_entry": "Den h\u00e4r posten har redan konfigurerats, men namnet och/eller alternativen som definierats i konfigurationen matchar inte den tidigare importerade konfigurationen och d\u00e4rf\u00f6r har konfigureringsposten uppdaterats i enlighet med detta." }, "step": { + "pair_tv": { + "data": { + "pin": "PIN-kod" + }, + "description": "Din TV borde visa en kod. Skriv koden i formul\u00e4ret och forts\u00e4tt sedan till n\u00e4sta steg f\u00f6r att slutf\u00f6ra parningen.", + "title": "Slutf\u00f6r parningsprocessen" + }, + "pairing_complete": { + "title": "Parkopplingen slutf\u00f6rd" + }, + "pairing_complete_import": { + "title": "Parkopplingen slutf\u00f6rd" + }, "user": { "data": { "access_token": "\u00c5tkomstnyckel", diff --git a/homeassistant/components/wled/translations/sv.json b/homeassistant/components/wled/translations/sv.json index 3c802a87007f64..aea858c5bfcbbb 100644 --- a/homeassistant/components/wled/translations/sv.json +++ b/homeassistant/components/wled/translations/sv.json @@ -1,10 +1,19 @@ { "config": { + "abort": { + "already_configured": "Enheten har redan konfigurerats" + }, + "flow_title": "WLED: {name}", "step": { "user": { "data": { "host": "V\u00e4rd eller IP-adress" - } + }, + "description": "St\u00e4ll in din WLED f\u00f6r att integrera med Home Assistant." + }, + "zeroconf_confirm": { + "description": "Vill du l\u00e4gga till WLED-enheten `{name}` till Home Assistant?", + "title": "Uppt\u00e4ckte WLED-enhet" } } } diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index f3a9aff1a29bda..e52552fa9860cb 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -1,14 +1,48 @@ { "config": { "abort": { - "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9" + "addon_get_discovery_info_failed": "Impossible d'obtenir les informations de d\u00e9couverte du module compl\u00e9mentaire Z-Wave JS.", + "addon_info_failed": "Impossible d'obtenir les informations sur le module compl\u00e9mentaire Z-Wave JS.", + "addon_install_failed": "\u00c9chec de l'installation du module compl\u00e9mentaire Z-Wave JS.", + "addon_missing_discovery_info": "Informations manquantes sur la d\u00e9couverte du module compl\u00e9mentaire Z-Wave JS.", + "addon_set_config_failed": "\u00c9chec de la d\u00e9finition de la configuration Z-Wave JS.", + "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de la connexion " }, "error": { + "addon_start_failed": "\u00c9chec du d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS. V\u00e9rifiez la configuration.", "cannot_connect": "Erreur de connection", "invalid_ws_url": "URL websocket invalide", "unknown": "Erreur inattendue" }, + "progress": { + "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes." + }, "step": { + "hassio_confirm": { + "title": "Configurer l'int\u00e9gration Z-Wave JS avec le module compl\u00e9mentaire Z-Wave JS" + }, + "install_addon": { + "title": "L'installation du module compl\u00e9mentaire Z-Wave JS a d\u00e9marr\u00e9" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor" + }, + "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", + "title": "S\u00e9lectionner la m\u00e9thode de connexion" + }, + "start_addon": { + "data": { + "network_key": "Cl\u00e9 r\u00e9seau" + }, + "title": "Entrez la configuration du module compl\u00e9mentaire Z-Wave JS" + }, "user": { "data": { "url": "URL" From f2d9e6f70c7f73489705a086482ac7f52d093f56 Mon Sep 17 00:00:00 2001 From: djtimca <60706061+djtimca@users.noreply.github.com> Date: Sat, 6 Feb 2021 02:05:39 -0500 Subject: [PATCH 0234/1818] Add sensor platform for Aurora integration (#43148) * Removed pylint disable on unused after updating CI files that were out of date. * Pylint still fails due to bug on DOMAIN import. Added disable check. * Addressed PR comments * Added import for ClientError to test_config_flow.py Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 + homeassistant/components/aurora/__init__.py | 67 +++++++++++++++++-- .../components/aurora/binary_sensor.py | 59 ++-------------- .../components/aurora/config_flow.py | 12 +++- homeassistant/components/aurora/const.py | 1 + homeassistant/components/aurora/sensor.py | 36 ++++++++++ tests/components/aurora/test_config_flow.py | 4 +- 7 files changed, 119 insertions(+), 61 deletions(-) create mode 100644 homeassistant/components/aurora/sensor.py diff --git a/.coveragerc b/.coveragerc index 3a274cd004f8b4..581c6350a05b11 100644 --- a/.coveragerc +++ b/.coveragerc @@ -72,6 +72,7 @@ omit = homeassistant/components/aurora/__init__.py homeassistant/components/aurora/binary_sensor.py homeassistant/components/aurora/const.py + homeassistant/components/aurora/sensor.py homeassistant/components/aurora_abb_powerone/sensor.py homeassistant/components/avea/light.py homeassistant/components/avion/light.py diff --git a/homeassistant/components/aurora/__init__.py b/homeassistant/components/aurora/__init__.py index 260a3bd735d463..a187288e2e4b88 100644 --- a/homeassistant/components/aurora/__init__.py +++ b/homeassistant/components/aurora/__init__.py @@ -4,16 +4,26 @@ from datetime import timedelta import logging +from aiohttp import ClientError from auroranoaa import AuroraForecast from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import ATTR_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) from .const import ( + ATTR_ENTRY_TYPE, + ATTR_IDENTIFIERS, + ATTR_MANUFACTURER, + ATTR_MODEL, + ATTRIBUTION, AURORA_API, CONF_THRESHOLD, COORDINATOR, @@ -24,7 +34,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["binary_sensor"] +PLATFORMS = ["binary_sensor", "sensor"] async def async_setup(hass: HomeAssistant, config: dict): @@ -126,5 +136,54 @@ async def _async_update_data(self): try: return await self.api.get_forecast_data(self.longitude, self.latitude) - except ConnectionError as error: + except ClientError as error: raise UpdateFailed(f"Error updating from NOAA: {error}") from error + + +class AuroraEntity(CoordinatorEntity): + """Implementation of the base Aurora Entity.""" + + def __init__( + self, + coordinator: AuroraDataUpdateCoordinator, + name: str, + icon: str, + ): + """Initialize the Aurora Entity.""" + + super().__init__(coordinator=coordinator) + + self._name = name + self._unique_id = f"{self.coordinator.latitude}_{self.coordinator.longitude}" + self._icon = icon + + @property + def unique_id(self): + """Define the unique id based on the latitude and longitude.""" + return self._unique_id + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return {"attribution": ATTRIBUTION} + + @property + def icon(self): + """Return the icon for the sensor.""" + return self._icon + + @property + def device_info(self): + """Define the device based on name.""" + return { + ATTR_IDENTIFIERS: {(DOMAIN, self._unique_id)}, + ATTR_NAME: self.coordinator.name, + ATTR_MANUFACTURER: "NOAA", + ATTR_MODEL: "Aurora Visibility Sensor", + ATTR_ENTRY_TYPE: "service", + } diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index 82be366ce6ddde..3ea1faa7949b74 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -1,19 +1,10 @@ -"""Support for aurora forecast data sensor.""" +"""Support for Aurora Forecast binary sensor.""" import logging from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import ATTR_NAME -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import AuroraDataUpdateCoordinator -from .const import ( - ATTR_IDENTIFIERS, - ATTR_MANUFACTURER, - ATTR_MODEL, - ATTRIBUTION, - COORDINATOR, - DOMAIN, -) +from . import AuroraEntity +from .const import COORDINATOR, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -21,55 +12,17 @@ async def async_setup_entry(hass, entry, async_add_entries): """Set up the binary_sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] - name = coordinator.name + name = f"{coordinator.name} Aurora Visibility Alert" - entity = AuroraSensor(coordinator, name) + entity = AuroraSensor(coordinator=coordinator, name=name, icon="mdi:hazard-lights") async_add_entries([entity]) -class AuroraSensor(CoordinatorEntity, BinarySensorEntity): +class AuroraSensor(AuroraEntity, BinarySensorEntity): """Implementation of an aurora sensor.""" - def __init__(self, coordinator: AuroraDataUpdateCoordinator, name): - """Define the binary sensor for the Aurora integration.""" - super().__init__(coordinator=coordinator) - - self._name = name - self.coordinator = coordinator - self._unique_id = f"{self.coordinator.latitude}_{self.coordinator.longitude}" - - @property - def unique_id(self): - """Define the unique id based on the latitude and longitude.""" - return self._unique_id - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - @property def is_on(self): """Return true if aurora is visible.""" return self.coordinator.data > self.coordinator.threshold - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return {"attribution": ATTRIBUTION} - - @property - def icon(self): - """Return the icon for the sensor.""" - return "mdi:hazard-lights" - - @property - def device_info(self): - """Define the device based on name.""" - return { - ATTR_IDENTIFIERS: {(DOMAIN, self._unique_id)}, - ATTR_NAME: self.coordinator.name, - ATTR_MANUFACTURER: "NOAA", - ATTR_MODEL: "Aurora Visibility Sensor", - } diff --git a/homeassistant/components/aurora/config_flow.py b/homeassistant/components/aurora/config_flow.py index 37885cc87cf3d2..24161c059c122b 100644 --- a/homeassistant/components/aurora/config_flow.py +++ b/homeassistant/components/aurora/config_flow.py @@ -1,6 +1,7 @@ """Config flow for SpaceX Launches and Starman.""" import logging +from aiohttp import ClientError from auroranoaa import AuroraForecast import voluptuous as vol @@ -9,7 +10,12 @@ from homeassistant.core import callback from homeassistant.helpers import aiohttp_client -from .const import CONF_THRESHOLD, DEFAULT_NAME, DEFAULT_THRESHOLD, DOMAIN +from .const import ( # pylint: disable=unused-import + CONF_THRESHOLD, + DEFAULT_NAME, + DEFAULT_THRESHOLD, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) @@ -40,14 +46,14 @@ async def async_step_user(self, user_input=None): try: await api.get_forecast_data(longitude, latitude) - except ConnectionError: + except ClientError: errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: await self.async_set_unique_id( - f"{DOMAIN}_{user_input[CONF_LONGITUDE]}_{user_input[CONF_LATITUDE]}" + f"{user_input[CONF_LONGITUDE]}_{user_input[CONF_LATITUDE]}" ) self._abort_if_unique_id_configured() return self.async_create_entry( diff --git a/homeassistant/components/aurora/const.py b/homeassistant/components/aurora/const.py index f4451de863d171..cd6f54a3d0cd0a 100644 --- a/homeassistant/components/aurora/const.py +++ b/homeassistant/components/aurora/const.py @@ -6,6 +6,7 @@ ATTR_IDENTIFIERS = "identifiers" ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" +ATTR_ENTRY_TYPE = "entry_type" DEFAULT_POLLING_INTERVAL = 5 CONF_THRESHOLD = "forecast_threshold" DEFAULT_THRESHOLD = 75 diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py new file mode 100644 index 00000000000000..51ccb3dc4dd4b2 --- /dev/null +++ b/homeassistant/components/aurora/sensor.py @@ -0,0 +1,36 @@ +"""Support for Aurora Forecast sensor.""" +import logging + +from homeassistant.const import PERCENTAGE + +from . import AuroraEntity +from .const import COORDINATOR, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entries): + """Set up the sensor platform.""" + coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] + + entity = AuroraSensor( + coordinator=coordinator, + name=f"{coordinator.name} Aurora Visibility %", + icon="mdi:gauge", + ) + + async_add_entries([entity]) + + +class AuroraSensor(AuroraEntity): + """Implementation of an aurora sensor.""" + + @property + def state(self): + """Return % chance the aurora is visible.""" + return self.coordinator.data + + @property + def unit_of_measurement(self): + """Return the unit of measure.""" + return PERCENTAGE diff --git a/tests/components/aurora/test_config_flow.py b/tests/components/aurora/test_config_flow.py index b9e0496f66803e..d9d0c4fd12870e 100644 --- a/tests/components/aurora/test_config_flow.py +++ b/tests/components/aurora/test_config_flow.py @@ -2,6 +2,8 @@ from unittest.mock import patch +from aiohttp import ClientError + from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.aurora.const import DOMAIN @@ -55,7 +57,7 @@ async def test_form_cannot_connect(hass): with patch( "homeassistant.components.aurora.AuroraForecast.get_forecast_data", - side_effect=ConnectionError, + side_effect=ClientError, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], From 8a7e0241ab0c91e2ed6b8672188988969b64ef14 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 6 Feb 2021 10:01:30 +0100 Subject: [PATCH 0235/1818] Fix race in script wait for trigger step (#46055) * Fix race in script wait for trigger step * Update script.py * Update script.py --- homeassistant/helpers/script.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index f197664f7e6dd6..60a1be6103ae53 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -623,6 +623,8 @@ async def _async_wait_for_trigger_step(self): variables = {**self._variables} self._variables["wait"] = {"remaining": delay, "trigger": None} + done = asyncio.Event() + async def async_done(variables, context=None): self._variables["wait"] = { "remaining": to_context.remaining if to_context else delay, @@ -647,7 +649,6 @@ def log_cb(level, msg, **kwargs): return self._changed() - done = asyncio.Event() tasks = [ self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] From ee98ea89ddffe9e7363d8f687a04bc69a9ecfe55 Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Sat, 6 Feb 2021 02:55:21 -0700 Subject: [PATCH 0236/1818] Bump aioharmony from 0.2.6 to 0.2.7 (#46075) --- homeassistant/components/harmony/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/harmony/manifest.json b/homeassistant/components/harmony/manifest.json index 7509f3d4f4df6c..eb7a99fffa8eae 100644 --- a/homeassistant/components/harmony/manifest.json +++ b/homeassistant/components/harmony/manifest.json @@ -2,7 +2,7 @@ "domain": "harmony", "name": "Logitech Harmony Hub", "documentation": "https://www.home-assistant.io/integrations/harmony", - "requirements": ["aioharmony==0.2.6"], + "requirements": ["aioharmony==0.2.7"], "codeowners": ["@ehendrix23", "@bramkragten", "@bdraco", "@mkeesey"], "ssdp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 2cc29c3be4b5e6..1962b4d393e431 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -163,7 +163,7 @@ aioftp==0.12.0 aioguardian==1.0.4 # homeassistant.components.harmony -aioharmony==0.2.6 +aioharmony==0.2.7 # homeassistant.components.homekit_controller aiohomekit==0.2.60 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2a447b539ef58b..65b19d7aabb9e8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ aioflo==0.4.1 aioguardian==1.0.4 # homeassistant.components.harmony -aioharmony==0.2.6 +aioharmony==0.2.7 # homeassistant.components.homekit_controller aiohomekit==0.2.60 From af4e6f856f076453aa74d0981d885825c269b728 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 6 Feb 2021 05:08:25 -0600 Subject: [PATCH 0237/1818] Use better names for zwave_js platforms that are self describing (#46083) * use better names for platforms that are self describing * add missing light change * fix tests * only use value_name in sensors and binary_sensors --- .../components/zwave_js/binary_sensor.py | 16 +++++++++++++-- homeassistant/components/zwave_js/entity.py | 18 +++++++++-------- homeassistant/components/zwave_js/sensor.py | 20 ++++++++++++++++--- tests/components/zwave_js/common.py | 2 +- tests/components/zwave_js/test_climate.py | 6 +++--- tests/components/zwave_js/test_cover.py | 2 +- tests/components/zwave_js/test_fan.py | 2 +- tests/components/zwave_js/test_light.py | 4 ++-- tests/components/zwave_js/test_lock.py | 2 +- 9 files changed, 50 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index bb2e4355f168f2..8a483c34e12b86 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -257,6 +257,16 @@ def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None: class ZWaveBooleanBinarySensor(ZWaveBaseEntity, BinarySensorEntity): """Representation of a Z-Wave binary_sensor.""" + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveBooleanBinarySensor entity.""" + super().__init__(config_entry, client, info) + self._name = self.generate_name(include_value_name=True) + @property def is_on(self) -> bool: """Return if the sensor is on or off.""" @@ -294,8 +304,9 @@ def __init__( super().__init__(config_entry, client, info) self.state_key = state_key self._name = self.generate_name( - self.info.primary_value.property_name, - [self.info.primary_value.metadata.states[self.state_key]], + include_value_name=True, + alternate_value_name=self.info.primary_value.property_name, + additional_info=[self.info.primary_value.metadata.states[self.state_key]], ) # check if we have a custom mapping for this value self._mapping_info = self._get_sensor_mapping() @@ -347,6 +358,7 @@ def __init__( super().__init__(config_entry, client, info) # check if we have a custom mapping for this value self._mapping_info = self._get_sensor_mapping() + self._name = self.generate_name(include_value_name=True) @property def is_on(self) -> bool: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 08571ad5d8c318..5d45cb9511ac2a 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -64,20 +64,22 @@ def device_info(self) -> dict: def generate_name( self, + include_value_name: bool = False, alternate_value_name: Optional[str] = None, additional_info: Optional[List[str]] = None, ) -> str: """Generate entity name.""" if additional_info is None: additional_info = [] - node_name = self.info.node.name or self.info.node.device_config.description - value_name = ( - alternate_value_name - or self.info.primary_value.metadata.label - or self.info.primary_value.property_key_name - or self.info.primary_value.property_name - ) - name = f"{node_name}: {value_name}" + name: str = self.info.node.name or self.info.node.device_config.description + if include_value_name: + value_name = ( + alternate_value_name + or self.info.primary_value.metadata.label + or self.info.primary_value.property_key_name + or self.info.primary_value.property_name + ) + name = f"{name}: {value_name}" for item in additional_info: if item: name += f" - {item}" diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 78b536b81f7dcc..8e22323c733fd0 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -64,6 +64,16 @@ def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: class ZwaveSensorBase(ZWaveBaseEntity): """Basic Representation of a Z-Wave sensor.""" + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveSensorBase entity.""" + super().__init__(config_entry, client, info) + self._name = self.generate_name(include_value_name=True) + @property def device_class(self) -> Optional[str]: """Return the device class of the sensor.""" @@ -132,7 +142,10 @@ def __init__( """Initialize a ZWaveNumericSensor entity.""" super().__init__(config_entry, client, info) if self.info.primary_value.command_class == CommandClass.BASIC: - self._name = self.generate_name(self.info.primary_value.command_class_name) + self._name = self.generate_name( + include_value_name=True, + alternate_value_name=self.info.primary_value.command_class_name, + ) @property def state(self) -> float: @@ -166,8 +179,9 @@ def __init__( """Initialize a ZWaveListSensor entity.""" super().__init__(config_entry, client, info) self._name = self.generate_name( - self.info.primary_value.property_name, - [self.info.primary_value.property_key_name], + include_value_name=True, + alternate_value_name=self.info.primary_value.property_name, + additional_info=[self.info.primary_value.property_key_name], ) @property diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index 63ec9013fa3322..9c6adb100fa721 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -2,7 +2,7 @@ AIR_TEMPERATURE_SENSOR = "sensor.multisensor_6_air_temperature" ENERGY_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed_2" POWER_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" -SWITCH_ENTITY = "switch.smart_plug_with_two_usb_ports_current_value" +SWITCH_ENTITY = "switch.smart_plug_with_two_usb_ports" LOW_BATTERY_BINARY_SENSOR = "binary_sensor.multisensor_6_low_battery_level" ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any" DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index b2455f3cbbd8f2..e11d3b75c47637 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -25,9 +25,9 @@ ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE -CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat_thermostat_mode" -CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat_heating" -CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat_thermostat_mode" +CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" +CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" +CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" async def test_thermostat_v2( diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index 52e0a444ec9c9e..5e50ffb226e831 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -3,7 +3,7 @@ from homeassistant.components.cover import ATTR_CURRENT_POSITION -WINDOW_COVER_ENTITY = "cover.zws_12_current_value" +WINDOW_COVER_ENTITY = "cover.zws_12" async def test_cover(hass, client, chain_actuator_zws12, integration): diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index a817a551f9b536..5bd856c664a02c 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -4,7 +4,7 @@ from homeassistant.components.fan import ATTR_SPEED, SPEED_MEDIUM -FAN_ENTITY = "fan.in_wall_smart_fan_control_current_value" +FAN_ENTITY = "fan.in_wall_smart_fan_control" async def test_fan(hass, client, in_wall_smart_fan_control, integration): diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index b60c72818742cf..bcd9a2edd9f92d 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -12,8 +12,8 @@ ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON -BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color_current_value" -EATON_RF9640_ENTITY = "light.allloaddimmer_current_value" +BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" +EATON_RF9640_ENTITY = "light.allloaddimmer" async def test_light(hass, client, bulb_6_multi_color, integration): diff --git a/tests/components/zwave_js/test_lock.py b/tests/components/zwave_js/test_lock.py index 069b3497a55aaf..9ddc7abdd88eaa 100644 --- a/tests/components/zwave_js/test_lock.py +++ b/tests/components/zwave_js/test_lock.py @@ -14,7 +14,7 @@ ) from homeassistant.const import ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED -SCHLAGE_BE469_LOCK_ENTITY = "lock.touchscreen_deadbolt_current_lock_mode" +SCHLAGE_BE469_LOCK_ENTITY = "lock.touchscreen_deadbolt" async def test_door_lock(hass, client, lock_schlage_be469, integration): From 369616a6c38f73f8f1c89f83b995e149abc75297 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 6 Feb 2021 12:19:41 +0100 Subject: [PATCH 0238/1818] Exclude disabled rfxtrx entities from async_entries_for_device (#46102) --- homeassistant/components/rfxtrx/config_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 5eeb9b38411ff4..da4d6447e76cb1 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -344,7 +344,9 @@ async def _async_replace_device(self, replace_device): new_device_id = "_".join(x for x in new_device_data[CONF_DEVICE_ID]) entity_registry = await async_get_entity_registry(self.hass) - entity_entries = async_entries_for_device(entity_registry, old_device) + entity_entries = async_entries_for_device( + entity_registry, old_device, include_disabled_entities=True + ) entity_migration_map = {} for entry in entity_entries: unique_id = entry.unique_id From a74ae3585a40dd89c38a830dd030e9694094e685 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Feb 2021 01:48:18 -1000 Subject: [PATCH 0239/1818] Fix backwards compatiblity with fan update to new model (#45951) * Fix backwards compatiblity with fans update to new model There were some non-speeds and devices that report a none speed. These problems were discovered when updating zha tasmota and vesync to the new model in #45407 * Update coverage * fix check --- homeassistant/components/demo/fan.py | 27 ++++++++-- homeassistant/components/fan/__init__.py | 28 ++++++++-- tests/components/demo/test_fan.py | 68 +++++++++++++++++++++++- 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index bd6661b6c2b6ae..cb6036a8938abb 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -15,6 +15,8 @@ PRESET_MODE_AUTO = "auto" PRESET_MODE_SMART = "smart" +PRESET_MODE_SLEEP = "sleep" +PRESET_MODE_ON = "on" FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION LIMITED_SUPPORT = SUPPORT_SET_SPEED @@ -38,6 +40,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= SPEED_HIGH, PRESET_MODE_AUTO, PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, ], ), DemoFan( @@ -54,7 +58,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "fan3", "Percentage Full Fan", FULL_SUPPORT, - [PRESET_MODE_AUTO, PRESET_MODE_SMART], + [ + PRESET_MODE_AUTO, + PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, + ], None, ), DemoPercentageFan( @@ -62,7 +71,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "fan4", "Percentage Limited Fan", LIMITED_SUPPORT, - [PRESET_MODE_AUTO, PRESET_MODE_SMART], + [ + PRESET_MODE_AUTO, + PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, + ], None, ), AsyncDemoPercentageFan( @@ -70,7 +84,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "fan5", "Preset Only Limited Fan", SUPPORT_PRESET_MODE, - [PRESET_MODE_AUTO, PRESET_MODE_SMART], + [ + PRESET_MODE_AUTO, + PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, + ], [], ), ] @@ -99,7 +118,7 @@ def __init__( self._unique_id = unique_id self._supported_features = supported_features self._speed = SPEED_OFF - self._percentage = 0 + self._percentage = None self._speed_list = speed_list self._preset_modes = preset_modes self._preset_mode = None diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 7b6b083c964059..8d6fcbea2c9658 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -64,18 +64,22 @@ # into core integrations at some point so we are temporarily # accommodating them in the transition to percentages. _NOT_SPEED_OFF = "off" +_NOT_SPEED_ON = "on" _NOT_SPEED_AUTO = "auto" _NOT_SPEED_SMART = "smart" _NOT_SPEED_INTERVAL = "interval" _NOT_SPEED_IDLE = "idle" _NOT_SPEED_FAVORITE = "favorite" +_NOT_SPEED_SLEEP = "sleep" _NOT_SPEEDS_FILTER = { _NOT_SPEED_OFF, + _NOT_SPEED_ON, _NOT_SPEED_AUTO, _NOT_SPEED_SMART, _NOT_SPEED_INTERVAL, _NOT_SPEED_IDLE, + _NOT_SPEED_SLEEP, _NOT_SPEED_FAVORITE, } @@ -83,6 +87,8 @@ OFF_SPEED_VALUES = [SPEED_OFF, None] +LEGACY_SPEED_LIST = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + class NoValidSpeedsError(ValueError): """Exception class when there are no valid speeds.""" @@ -386,7 +392,10 @@ def speed(self) -> Optional[str]: if preset_mode: return preset_mode if self._implemented_percentage: - return self.percentage_to_speed(self.percentage) + percentage = self.percentage + if percentage is None: + return None + return self.percentage_to_speed(percentage) return None @property @@ -404,7 +413,7 @@ def speed_list(self) -> list: """Get the list of available speeds.""" speeds = [] if self._implemented_percentage: - speeds += [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + speeds += [SPEED_OFF, *LEGACY_SPEED_LIST] if self._implemented_preset_mode: speeds += self.preset_modes return speeds @@ -434,6 +443,17 @@ def capability_attributes(self): return attrs + @property + def _speed_list_without_preset_modes(self) -> list: + """Return the speed list without preset modes. + + This property provides forward and backwards + compatibility for conversion to percentage speeds. + """ + if not self._implemented_speed: + return LEGACY_SPEED_LIST + return speed_list_without_preset_modes(self.speed_list) + def speed_to_percentage(self, speed: str) -> int: """ Map a speed to a percentage. @@ -453,7 +473,7 @@ def speed_to_percentage(self, speed: str) -> int: if speed in OFF_SPEED_VALUES: return 0 - speed_list = speed_list_without_preset_modes(self.speed_list) + speed_list = self._speed_list_without_preset_modes if speed_list and speed not in speed_list: raise NotValidSpeedError(f"The speed {speed} is not a valid speed.") @@ -487,7 +507,7 @@ def percentage_to_speed(self, percentage: int) -> str: if percentage == 0: return SPEED_OFF - speed_list = speed_list_without_preset_modes(self.speed_list) + speed_list = self._speed_list_without_preset_modes try: return percentage_to_ordered_list_item(speed_list, percentage) diff --git a/tests/components/demo/test_fan.py b/tests/components/demo/test_fan.py index 5297e64bda9041..2439d49685c8ab 100644 --- a/tests/components/demo/test_fan.py +++ b/tests/components/demo/test_fan.py @@ -2,7 +2,12 @@ import pytest from homeassistant.components import fan -from homeassistant.components.demo.fan import PRESET_MODE_AUTO, PRESET_MODE_SMART +from homeassistant.components.demo.fan import ( + PRESET_MODE_AUTO, + PRESET_MODE_ON, + PRESET_MODE_SLEEP, + PRESET_MODE_SMART, +) from homeassistant.const import ( ATTR_ENTITY_ID, ENTITY_MATCH_ALL, @@ -60,6 +65,28 @@ async def test_turn_on_with_speed_and_percentage(hass, fan_entity_id): assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH assert state.attributes[fan.ATTR_PERCENTAGE] == 100 + await hass.services.async_call( + fan.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_MEDIUM}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + + await hass.services.async_call( + fan.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_LOW}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + await hass.services.async_call( fan.DOMAIN, SERVICE_TURN_ON, @@ -71,6 +98,39 @@ async def test_turn_on_with_speed_and_percentage(hass, fan_entity_id): assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH assert state.attributes[fan.ATTR_PERCENTAGE] == 100 + await hass.services.async_call( + fan.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 66}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + + await hass.services.async_call( + fan.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 33}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + + await hass.services.async_call( + fan.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 0}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.state == STATE_OFF + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF + assert state.attributes[fan.ATTR_PERCENTAGE] == 0 + @pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODE_ONLY) async def test_turn_on_with_preset_mode_only(hass, fan_entity_id): @@ -89,6 +149,8 @@ async def test_turn_on_with_preset_mode_only(hass, fan_entity_id): assert state.attributes[fan.ATTR_PRESET_MODES] == [ PRESET_MODE_AUTO, PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, ] await hass.services.async_call( @@ -145,10 +207,14 @@ async def test_turn_on_with_preset_mode_and_speed(hass, fan_entity_id): fan.SPEED_HIGH, PRESET_MODE_AUTO, PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, ] assert state.attributes[fan.ATTR_PRESET_MODES] == [ PRESET_MODE_AUTO, PRESET_MODE_SMART, + PRESET_MODE_SLEEP, + PRESET_MODE_ON, ] await hass.services.async_call( From 94ecb792ec2fea66e2f9a65f0767693d1172eb8b Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 6 Feb 2021 13:17:52 +0100 Subject: [PATCH 0240/1818] Use async_update_entry rather than updating config_entry.data directly in Axis (#46078) Don't step version in migrate_entry to support rollbacking --- homeassistant/components/axis/__init__.py | 15 ++++++++++----- tests/components/axis/test_init.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index c467359c17e6ff..8722c41c3e0cbc 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -48,8 +48,11 @@ async def async_migrate_entry(hass, config_entry): # Flatten configuration but keep old data if user rollbacks HASS prior to 0.106 if config_entry.version == 1: - config_entry.data = {**config_entry.data, **config_entry.data[CONF_DEVICE]} - config_entry.unique_id = config_entry.data[CONF_MAC] + unique_id = config_entry.data[CONF_MAC] + data = {**config_entry.data, **config_entry.data[CONF_DEVICE]} + hass.config_entries.async_update_entry( + config_entry, unique_id=unique_id, data=data + ) config_entry.version = 2 # Normalise MAC address of device which also affects entity unique IDs @@ -66,10 +69,12 @@ def update_unique_id(entity_entry): ) } - await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + if old_unique_id != new_unique_id: + await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) - config_entry.unique_id = new_unique_id - config_entry.version = 3 + hass.config_entries.async_update_entry( + config_entry, unique_id=new_unique_id + ) _LOGGER.info("Migration to version %s successful", config_entry.version) diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index b7faceaf10d7bf..36a603ea7b339d 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -109,7 +109,7 @@ async def test_migrate_entry(hass): CONF_MODEL: "model", CONF_NAME: "name", } - assert entry.version == 3 + assert entry.version == 2 # Keep version to support rollbacking assert entry.unique_id == "00:40:8c:12:34:56" vmd4_entity = registry.async_get("binary_sensor.vmd4") From fb68bf85ae09f97b0f3158839eb88606c302d465 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 6 Feb 2021 13:40:15 +0100 Subject: [PATCH 0241/1818] Don't defer formatting of log messages (#44873) * Make fast logging optional * Remove fast logging support --- homeassistant/util/logging.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 9b04c2ab007db0..423685fe9d47f2 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -30,13 +30,6 @@ def filter(self, record: logging.LogRecord) -> bool: class HomeAssistantQueueHandler(logging.handlers.QueueHandler): """Process the log in another thread.""" - def emit(self, record: logging.LogRecord) -> None: - """Emit a log record.""" - try: - self.enqueue(record) - except Exception: # pylint: disable=broad-except - self.handleError(record) - def handle(self, record: logging.LogRecord) -> Any: """ Conditionally emit the specified logging record. From 242ff045b92ed31bc2e3f298b06d77b02f6190cc Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sat, 6 Feb 2021 14:02:03 +0100 Subject: [PATCH 0242/1818] Handle missing value in all platforms of zwave_js (#46081) --- homeassistant/components/zwave_js/climate.py | 15 +++++++++++++++ homeassistant/components/zwave_js/cover.py | 12 +++++++++--- homeassistant/components/zwave_js/entity.py | 8 +------- homeassistant/components/zwave_js/fan.py | 10 ++++++++-- homeassistant/components/zwave_js/lock.py | 3 +++ homeassistant/components/zwave_js/switch.py | 7 +++++-- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index b125c8bcd6a4ac..a0b0648932cfa9 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -207,6 +207,9 @@ def hvac_mode(self) -> str: if self._current_mode is None: # Thermostat(valve) with no support for setting a mode is considered heating-only return HVAC_MODE_HEAT + if self._current_mode.value is None: + # guard missing value + return HVAC_MODE_HEAT return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVAC_MODE_HEAT_COOL) @property @@ -219,6 +222,9 @@ def hvac_action(self) -> Optional[str]: """Return the current running hvac operation if supported.""" if not self._operating_state: return None + if self._operating_state.value is None: + # guard missing value + return None return HVAC_CURRENT_MAP.get(int(self._operating_state.value)) @property @@ -234,12 +240,18 @@ def current_temperature(self) -> Optional[float]: @property def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) return temp.value if temp else None @property def target_temperature_high(self) -> Optional[float]: """Return the highbound target temperature we try to reach.""" + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) return temp.value if temp else None @@ -251,6 +263,9 @@ def target_temperature_low(self) -> Optional[float]: @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES: return_val: str = self._current_mode.metadata.states.get( self._current_mode.value diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index b86cbeba944df7..38c891f7376310 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -1,6 +1,6 @@ """Support for Z-Wave cover devices.""" import logging -from typing import Any, Callable, List +from typing import Any, Callable, List, Optional from zwave_js_server.client import Client as ZwaveClient @@ -59,13 +59,19 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): """Representation of a Z-Wave Cover device.""" @property - def is_closed(self) -> bool: + def is_closed(self) -> Optional[bool]: """Return true if cover is closed.""" + if self.info.primary_value.value is None: + # guard missing value + return None return bool(self.info.primary_value.value == 0) @property - def current_cover_position(self) -> int: + def current_cover_position(self) -> Optional[int]: """Return the current position of cover where 0 means closed and 100 is fully open.""" + if self.info.primary_value.value is None: + # guard missing value + return None return round((self.info.primary_value.value / 99) * 100) async def async_set_cover_position(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 5d45cb9511ac2a..911b0b7b2f8a1f 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -102,13 +102,7 @@ def unique_id(self) -> str: @property def available(self) -> bool: """Return entity availability.""" - return ( - self.client.connected - and bool(self.info.node.ready) - # a None value indicates something wrong with the device, - # or the value is simply not yet there (it will arrive later). - and self.info.primary_value.value is not None - ) + return self.client.connected and bool(self.info.node.ready) @callback def _value_changed(self, event_data: dict) -> None: diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index 6e62869f74983d..58b85eabef13a4 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -84,13 +84,19 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self.info.node.async_set_value(target_value, 0) @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: # type: ignore """Return true if device is on (speed above 0).""" + if self.info.primary_value.value is None: + # guard missing value + return None return bool(self.info.primary_value.value > 0) @property - def percentage(self) -> int: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" + if self.info.primary_value.value is None: + # guard missing value + return None return ranged_value_to_percentage(SPEED_RANGE, self.info.primary_value.value) @property diff --git a/homeassistant/components/zwave_js/lock.py b/homeassistant/components/zwave_js/lock.py index dedaf9a5e45500..6f2a1a72c7d93d 100644 --- a/homeassistant/components/zwave_js/lock.py +++ b/homeassistant/components/zwave_js/lock.py @@ -90,6 +90,9 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): @property def is_locked(self) -> Optional[bool]: """Return true if the lock is locked.""" + if self.info.primary_value.value is None: + # guard missing value + return None return int( LOCK_CMD_CLASS_TO_LOCKED_STATE_MAP[ CommandClass(self.info.primary_value.command_class) diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index 2060894684c859..8feba5911f8d82 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -1,7 +1,7 @@ """Representation of Z-Wave switches.""" import logging -from typing import Any, Callable, List +from typing import Any, Callable, List, Optional from zwave_js_server.client import Client as ZwaveClient @@ -44,8 +44,11 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity): """Representation of a Z-Wave switch.""" @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: # type: ignore """Return a boolean for the state of the switch.""" + if self.info.primary_value.value is None: + # guard missing value + return None return bool(self.info.primary_value.value) async def async_turn_on(self, **kwargs: Any) -> None: From 08163a848c3a063f30260d4a24f86310bf197fd6 Mon Sep 17 00:00:00 2001 From: Steven Rollason <2099542+gadgetchnnel@users.noreply.github.com> Date: Sat, 6 Feb 2021 13:05:50 +0000 Subject: [PATCH 0243/1818] Fix downloader path validation on subdir (#46061) --- homeassistant/components/downloader/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 94617ce43aa63b..3856df696ad00d 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -70,8 +70,9 @@ def do_download(): overwrite = service.data.get(ATTR_OVERWRITE) - # Check the path - raise_if_invalid_path(subdir) + if subdir: + # Check the path + raise_if_invalid_path(subdir) final_path = None From 60e3fce7dcbed9beba4c43edadc5873f4d413e93 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 6 Feb 2021 14:32:17 +0100 Subject: [PATCH 0244/1818] Convert old deCONZ groups unique ids (#46093) * Convert old groups unique ids Work around for walrus operator not working properly :/ * Remove CONF_GROUP_ID_BASE once entities unique id are updated * Don't use migrate_entry mechanism to update unique_ids of groups * Remove walrus operator :( * Fix review comments * Walrusify assignment to old_unique_id --- homeassistant/components/deconz/__init__.py | 53 +++++++++---- homeassistant/components/deconz/light.py | 6 +- tests/components/deconz/test_gateway.py | 2 + tests/components/deconz/test_init.py | 85 ++++++++++++++++++++- 4 files changed, 123 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index fec7b82e365b27..1b8c47d36a2f41 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -1,11 +1,17 @@ """Support for deCONZ devices.""" import voluptuous as vol -from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers.typing import UNDEFINED +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.core import callback +from homeassistant.helpers.entity_registry import async_migrate_entries from .config_flow import get_master_gateway -from .const import CONF_BRIDGE_ID, CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN +from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN from .gateway import DeconzGateway from .services import async_setup_services, async_unload_services @@ -28,6 +34,8 @@ async def async_setup_entry(hass, config_entry): if DOMAIN not in hass.data: hass.data[DOMAIN] = {} + await async_update_group_unique_id(hass, config_entry) + if not config_entry.options: await async_update_master_gateway(hass, config_entry) @@ -36,18 +44,6 @@ async def async_setup_entry(hass, config_entry): if not await gateway.async_setup(): return False - # 0.104 introduced config entry unique id, this makes upgrading possible - if config_entry.unique_id is None: - - new_data = UNDEFINED - if CONF_BRIDGE_ID in config_entry.data: - new_data = dict(config_entry.data) - new_data[CONF_GROUP_ID_BASE] = config_entry.data[CONF_BRIDGE_ID] - - hass.config_entries.async_update_entry( - config_entry, unique_id=gateway.api.config.bridgeid, data=new_data - ) - hass.data[DOMAIN][config_entry.unique_id] = gateway await gateway.async_update_device_registry() @@ -84,3 +80,30 @@ async def async_update_master_gateway(hass, config_entry): options = {**config_entry.options, CONF_MASTER_GATEWAY: master} hass.config_entries.async_update_entry(config_entry, options=options) + + +async def async_update_group_unique_id(hass, config_entry) -> None: + """Update unique ID entities based on deCONZ groups.""" + if not (old_unique_id := config_entry.data.get(CONF_GROUP_ID_BASE)): + return + + new_unique_id: str = config_entry.unique_id + + @callback + def update_unique_id(entity_entry): + """Update unique ID of entity entry.""" + if f"{old_unique_id}-" not in entity_entry.unique_id: + return None + return { + "new_unique_id": entity_entry.unique_id.replace( + old_unique_id, new_unique_id + ) + } + + await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + data = { + CONF_API_KEY: config_entry.data[CONF_API_KEY], + CONF_HOST: config_entry.data[CONF_HOST], + CONF_PORT: config_entry.data[CONF_PORT], + } + hass.config_entries.async_update_entry(config_entry, data=data) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 9080160c76fe19..2da435c5530828 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -26,7 +26,6 @@ import homeassistant.util.color as color_util from .const import ( - CONF_GROUP_ID_BASE, COVER_TYPES, DOMAIN as DECONZ_DOMAIN, LOCK_TYPES, @@ -248,10 +247,7 @@ class DeconzGroup(DeconzBaseLight): def __init__(self, device, gateway): """Set up group and create an unique id.""" - group_id_base = gateway.config_entry.unique_id - if CONF_GROUP_ID_BASE in gateway.config_entry.data: - group_id_base = gateway.config_entry.data[CONF_GROUP_ID_BASE] - self._unique_id = f"{group_id_base}-{device.deconz_id}" + self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" super().__init__(device, gateway) diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 1790b6ed6e1f44..f670f2a1d10cf8 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -66,6 +66,7 @@ async def setup_deconz_integration( options=ENTRY_OPTIONS, get_state_response=DECONZ_WEB_REQUEST, entry_id="1", + unique_id=BRIDGEID, source="user", ): """Create the deCONZ gateway.""" @@ -76,6 +77,7 @@ async def setup_deconz_integration( connection_class=CONN_CLASS_LOCAL_PUSH, options=deepcopy(options), entry_id=entry_id, + unique_id=unique_id, ) config_entry.add_to_hass(hass) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index d408d764d0e9fc..43c0c48440c053 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -8,12 +8,21 @@ DeconzGateway, async_setup_entry, async_unload_entry, + async_update_group_unique_id, +) +from homeassistant.components.deconz.const import ( + CONF_GROUP_ID_BASE, + DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +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 from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.common import MockConfigEntry + ENTRY1_HOST = "1.2.3.4" ENTRY1_PORT = 80 ENTRY1_API_KEY = "1234567890ABCDEF" @@ -67,7 +76,7 @@ async def test_setup_entry_multiple_gateways(hass): data = deepcopy(DECONZ_WEB_REQUEST) data["config"]["bridgeid"] = "01234E56789B" config_entry2 = await setup_deconz_integration( - hass, get_state_response=data, entry_id="2" + hass, get_state_response=data, entry_id="2", unique_id="01234E56789B" ) gateway2 = get_gateway_from_config_entry(hass, config_entry2) @@ -92,7 +101,7 @@ async def test_unload_entry_multiple_gateways(hass): data = deepcopy(DECONZ_WEB_REQUEST) data["config"]["bridgeid"] = "01234E56789B" config_entry2 = await setup_deconz_integration( - hass, get_state_response=data, entry_id="2" + hass, get_state_response=data, entry_id="2", unique_id="01234E56789B" ) gateway2 = get_gateway_from_config_entry(hass, config_entry2) @@ -102,3 +111,73 @@ async def test_unload_entry_multiple_gateways(hass): assert len(hass.data[DECONZ_DOMAIN]) == 1 assert hass.data[DECONZ_DOMAIN][gateway2.bridgeid].master + + +async def test_update_group_unique_id(hass): + """Test successful migration of entry data.""" + old_unique_id = "123" + new_unique_id = "1234" + entry = MockConfigEntry( + domain=DECONZ_DOMAIN, + unique_id=new_unique_id, + data={ + CONF_API_KEY: "1", + CONF_HOST: "2", + CONF_GROUP_ID_BASE: old_unique_id, + CONF_PORT: "3", + }, + ) + + registry = await entity_registry.async_get_registry(hass) + # Create entity entry to migrate to new unique ID + registry.async_get_or_create( + LIGHT_DOMAIN, + DECONZ_DOMAIN, + f"{old_unique_id}-OLD", + suggested_object_id="old", + config_entry=entry, + ) + # Create entity entry with new unique ID + registry.async_get_or_create( + LIGHT_DOMAIN, + DECONZ_DOMAIN, + f"{new_unique_id}-NEW", + suggested_object_id="new", + config_entry=entry, + ) + + await async_update_group_unique_id(hass, entry) + + assert entry.data == {CONF_API_KEY: "1", CONF_HOST: "2", CONF_PORT: "3"} + + old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") + assert old_entity.unique_id == f"{new_unique_id}-OLD" + + new_entity = registry.async_get(f"{LIGHT_DOMAIN}.new") + assert new_entity.unique_id == f"{new_unique_id}-NEW" + + +async def test_update_group_unique_id_no_legacy_group_id(hass): + """Test migration doesn't trigger without old legacy group id in entry data.""" + old_unique_id = "123" + new_unique_id = "1234" + entry = MockConfigEntry( + domain=DECONZ_DOMAIN, + unique_id=new_unique_id, + data={}, + ) + + registry = await entity_registry.async_get_registry(hass) + # Create entity entry to migrate to new unique ID + registry.async_get_or_create( + LIGHT_DOMAIN, + DECONZ_DOMAIN, + f"{old_unique_id}-OLD", + suggested_object_id="old", + config_entry=entry, + ) + + await async_update_group_unique_id(hass, entry) + + old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") + assert old_entity.unique_id == f"{old_unique_id}-OLD" From cefde8721de1a1831624310e1dc81b688ea03a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Sat, 6 Feb 2021 21:29:48 +0100 Subject: [PATCH 0245/1818] Add new features to Apple TV media player (#45828) --- homeassistant/components/apple_tv/__init__.py | 15 ++-- .../components/apple_tv/media_player.py | 89 +++++++++++++++---- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index eca5e91ddeb80b..b41a107d126cbf 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -180,20 +180,23 @@ def connection_lost(self, _): This is a callback function from pyatv.interface.DeviceListener. """ - _LOGGER.warning('Connection lost to Apple TV "%s"', self.atv.name) - if self.atv: - self.atv.close() - self.atv = None + _LOGGER.warning( + 'Connection lost to Apple TV "%s"', self.config_entry.data.get(CONF_NAME) + ) self._connection_was_lost = True - self._dispatch_send(SIGNAL_DISCONNECTED) - self._start_connect_loop() + self._handle_disconnect() def connection_closed(self): """Device connection was (intentionally) closed. This is a callback function from pyatv.interface.DeviceListener. """ + self._handle_disconnect() + + 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) diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 81bb79dc50b585..a855fc6b53e03c 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -1,22 +1,35 @@ """Support for Apple TV media player.""" import logging -from pyatv.const import DeviceState, FeatureName, FeatureState, MediaType +from pyatv.const import ( + DeviceState, + FeatureName, + FeatureState, + MediaType, + RepeatState, + ShuffleState, +) from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, + REPEAT_MODE_ALL, + REPEAT_MODE_OFF, + REPEAT_MODE_ONE, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, + SUPPORT_REPEAT_SET, SUPPORT_SEEK, + SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( CONF_NAME, @@ -46,6 +59,9 @@ | SUPPORT_STOP | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK + | SUPPORT_VOLUME_STEP + | SUPPORT_REPEAT_SET + | SUPPORT_SHUFFLE_SET ) @@ -110,17 +126,15 @@ def playstatus_error(self, _, exception): @property def app_id(self): """ID of the current running app.""" - if self.atv: - if self.atv.features.in_state(FeatureState.Available, FeatureName.App): - return self.atv.metadata.app.identifier + if self._is_feature_available(FeatureName.App): + return self.atv.metadata.app.identifier return None @property def app_name(self): """Name of the current running app.""" - if self.atv: - if self.atv.features.in_state(FeatureState.Available, FeatureName.App): - return self.atv.metadata.app.name + if self._is_feature_available(FeatureName.App): + return self.atv.metadata.app.name return None @property @@ -198,6 +212,23 @@ def media_album_name(self): return self._playing.album return None + @property + def repeat(self): + """Return current repeat mode.""" + if self._is_feature_available(FeatureName.Repeat): + return { + RepeatState.Track: REPEAT_MODE_ONE, + RepeatState.All: REPEAT_MODE_ALL, + }.get(self._playing.repeat, REPEAT_MODE_OFF) + return None + + @property + def shuffle(self): + """Boolean if shuffle is enabled.""" + if self._is_feature_available(FeatureName.Shuffle): + return self._playing.shuffle != ShuffleState.Off + return None + @property def supported_features(self): """Flag media player features that are supported.""" @@ -221,39 +252,61 @@ async def async_turn_off(self): async def async_media_play_pause(self): """Pause media on media player.""" if self._playing: - state = self.state - if state == STATE_PAUSED: - await self.atv.remote_control.play() - elif state == STATE_PLAYING: - await self.atv.remote_control.pause() + await self.atv.remote_control.play_pause() return None async def async_media_play(self): """Play media.""" - if self._playing: + if self.atv: await self.atv.remote_control.play() async def async_media_stop(self): """Stop the media player.""" - if self._playing: + if self.atv: await self.atv.remote_control.stop() async def async_media_pause(self): """Pause the media player.""" - if self._playing: + if self.atv: await self.atv.remote_control.pause() async def async_media_next_track(self): """Send next track command.""" - if self._playing: + if self.atv: await self.atv.remote_control.next() async def async_media_previous_track(self): """Send previous track command.""" - if self._playing: + if self.atv: await self.atv.remote_control.previous() async def async_media_seek(self, position): """Send seek command.""" - if self._playing: + if self.atv: await self.atv.remote_control.set_position(position) + + async def async_volume_up(self): + """Turn volume up for media player.""" + if self.atv: + await self.atv.remote_control.volume_up() + + async def async_volume_down(self): + """Turn volume down for media player.""" + if self.atv: + await self.atv.remote_control.volume_down() + + async def async_set_repeat(self, repeat): + """Set repeat mode.""" + if self.atv: + mode = { + REPEAT_MODE_ONE: RepeatState.Track, + REPEAT_MODE_ALL: RepeatState.All, + }.get(repeat, RepeatState.Off) + await self.atv.remote_control.set_repeat(mode) + + async def async_set_shuffle(self, shuffle): + """Enable/disable shuffle mode.""" + if self.atv: + await self.atv.remote_control.set_shuffle( + ShuffleState.Songs if shuffle else ShuffleState.Off + ) From 618fcda8211a66d0202c1c804839a1a5a12c7400 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 6 Feb 2021 21:32:18 +0100 Subject: [PATCH 0246/1818] Simplify UniFi entry configuration data (#45759) * Simplify configuration structure by removing the controller key * Fix flake8 * Fix review comments * Don't use migrate_entry mechanism to flatten configuration Keep legacy configuration when creating new entries as well --- homeassistant/components/unifi/__init__.py | 16 ++++++++ homeassistant/components/unifi/config_flow.py | 38 +++++++++++-------- homeassistant/components/unifi/controller.py | 20 +++++++--- tests/components/unifi/test_config_flow.py | 24 ++++++------ tests/components/unifi/test_controller.py | 7 ++-- tests/components/unifi/test_init.py | 28 ++++++++------ 6 files changed, 87 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 5f19a01ce4558c..8d24a9b642ffdf 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -5,6 +5,7 @@ from .const import ( ATTR_MANUFACTURER, + CONF_CONTROLLER, DOMAIN as UNIFI_DOMAIN, LOGGER, UNIFI_WIRELESS_CLIENTS, @@ -28,10 +29,14 @@ async def async_setup_entry(hass, config_entry): """Set up the UniFi component.""" hass.data.setdefault(UNIFI_DOMAIN, {}) + # Flat configuration was introduced with 2021.3 + await async_flatten_entry_data(hass, config_entry) + controller = UniFiController(hass, config_entry) if not await controller.async_setup(): return False + # Unique ID was introduced with 2021.3 if config_entry.unique_id is None: hass.config_entries.async_update_entry( config_entry, unique_id=controller.site_id @@ -64,6 +69,17 @@ async def async_unload_entry(hass, config_entry): return await controller.async_reset() +async def async_flatten_entry_data(hass, config_entry): + """Simpler configuration structure for entry data. + + Keep controller key layer in case user rollbacks. + """ + + data: dict = {**config_entry.data, **config_entry.data[CONF_CONTROLLER]} + if config_entry.data != data: + hass.config_entries.async_update_entry(config_entry, data=data) + + class UnifiWirelessClients: """Class to store clients known to be wireless. diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 1d89215dc89904..8e83f53d198630 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -73,7 +73,6 @@ def __init__(self): self.site_ids = {} self.site_names = {} self.reauth_config_entry = None - self.reauth_config = {} self.reauth_schema = {} async def async_step_user(self, user_input=None): @@ -92,7 +91,16 @@ async def async_step_user(self, user_input=None): } try: - controller = await get_controller(self.hass, **self.config) + controller = await get_controller( + self.hass, + host=self.config[CONF_HOST], + username=self.config[CONF_USERNAME], + password=self.config[CONF_PASSWORD], + port=self.config[CONF_PORT], + site=self.config[CONF_SITE_ID], + verify_ssl=self.config[CONF_VERIFY_SSL], + ) + sites = await controller.sites() except AuthenticationRequired: @@ -143,7 +151,8 @@ async def async_step_site(self, user_input=None): unique_id = user_input[CONF_SITE_ID] self.config[CONF_SITE_ID] = self.site_ids[unique_id] - data = {CONF_CONTROLLER: self.config} + # Backwards compatible config + self.config[CONF_CONTROLLER] = self.config.copy() config_entry = await self.async_set_unique_id(unique_id) abort_reason = "configuration_updated" @@ -160,12 +169,14 @@ async def async_step_site(self, user_input=None): if controller and controller.available: return self.async_abort(reason="already_configured") - self.hass.config_entries.async_update_entry(config_entry, data=data) + self.hass.config_entries.async_update_entry( + config_entry, data=self.config + ) await self.hass.config_entries.async_reload(config_entry.entry_id) return self.async_abort(reason=abort_reason) site_nice_name = self.site_names[unique_id] - return self.async_create_entry(title=site_nice_name, data=data) + return self.async_create_entry(title=site_nice_name, data=self.config) if len(self.site_names) == 1: return await self.async_step_site( @@ -183,21 +194,20 @@ async def async_step_site(self, user_input=None): async def async_step_reauth(self, config_entry: dict): """Trigger a reauthentication flow.""" self.reauth_config_entry = config_entry - self.reauth_config = config_entry.data[CONF_CONTROLLER] # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { - CONF_HOST: self.reauth_config[CONF_HOST], + CONF_HOST: config_entry.data[CONF_HOST], CONF_SITE_ID: config_entry.title, } self.reauth_schema = { - vol.Required(CONF_HOST, default=self.reauth_config[CONF_HOST]): str, - vol.Required(CONF_USERNAME, default=self.reauth_config[CONF_USERNAME]): str, + vol.Required(CONF_HOST, default=config_entry.data[CONF_HOST]): str, + vol.Required(CONF_USERNAME, default=config_entry.data[CONF_USERNAME]): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_PORT, default=self.reauth_config[CONF_PORT]): int, + vol.Required(CONF_PORT, default=config_entry.data[CONF_PORT]): int, vol.Required( - CONF_VERIFY_SSL, default=self.reauth_config[CONF_VERIFY_SSL] + CONF_VERIFY_SSL, default=config_entry.data[CONF_VERIFY_SSL] ): bool, } @@ -217,7 +227,7 @@ async def async_step_ssdp(self, discovery_info): return self.async_abort(reason="already_configured") await self.async_set_unique_id(mac_address) - self._abort_if_unique_id_configured(updates={CONF_HOST: self.config[CONF_HOST]}) + self._abort_if_unique_id_configured(updates=self.config) # pylint: disable=no-member self.context["title_placeholders"] = { @@ -234,9 +244,7 @@ async def async_step_ssdp(self, discovery_info): def _host_already_configured(self, host): """See if we already have a UniFi entry matching the host.""" for entry in self._async_current_entries(): - if not entry.data or CONF_CONTROLLER not in entry.data: - continue - if entry.data[CONF_CONTROLLER][CONF_HOST] == host: + if entry.data.get(CONF_HOST) == host: return True return False diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 5d5e679e75ed7d..128f01079848d2 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -29,7 +29,13 @@ from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import SOURCE_REAUTH -from homeassistant.const import CONF_HOST +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + CONF_VERIFY_SSL, +) from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client @@ -41,7 +47,6 @@ CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_UPTIME_SENSORS, CONF_BLOCK_CLIENT, - CONF_CONTROLLER, CONF_DETECTION_TIME, CONF_DPI_RESTRICTIONS, CONF_IGNORE_WIRED_BUG, @@ -161,12 +166,12 @@ def load_config_entry_options(self): @property def host(self): """Return the host of this controller.""" - return self.config_entry.data[CONF_CONTROLLER][CONF_HOST] + return self.config_entry.data[CONF_HOST] @property def site(self): """Return the site of this config entry.""" - return self.config_entry.data[CONF_CONTROLLER][CONF_SITE_ID] + return self.config_entry.data[CONF_SITE_ID] @property def site_name(self): @@ -299,7 +304,12 @@ async def async_setup(self): try: self.api = await get_controller( self.hass, - **self.config_entry.data[CONF_CONTROLLER], + host=self.config_entry.data[CONF_HOST], + username=self.config_entry.data[CONF_USERNAME], + password=self.config_entry.data[CONF_PASSWORD], + port=self.config_entry.data[CONF_PORT], + site=self.config_entry.data[CONF_SITE_ID], + verify_ssl=self.config_entry.data[CONF_VERIFY_SSL], async_callback=self.async_unifi_signalling_callback, ) await self.api.initialize() diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 096e6ba779137c..a28f5f5f7c5b2c 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -134,6 +134,12 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Site name" assert result["data"] == { + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_PORT: 1234, + CONF_SITE_ID: "site_id", + CONF_VERIFY_SSL: True, CONF_CONTROLLER: { CONF_HOST: "1.2.3.4", CONF_USERNAME: "username", @@ -141,7 +147,7 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): CONF_PORT: 1234, CONF_SITE_ID: "site_id", CONF_VERIFY_SSL: True, - } + }, } @@ -241,16 +247,12 @@ async def test_flow_raise_already_configured(hass, aioclient_mock): async def test_flow_aborts_configuration_updated(hass, aioclient_mock): """Test config flow aborts since a connected config entry already exists.""" entry = MockConfigEntry( - domain=UNIFI_DOMAIN, - data={"controller": {"host": "1.2.3.4", "site": "office"}}, - unique_id="2", + domain=UNIFI_DOMAIN, data={"host": "1.2.3.4", "site": "office"}, unique_id="2" ) entry.add_to_hass(hass) entry = MockConfigEntry( - domain=UNIFI_DOMAIN, - data={"controller": {"host": "1.2.3.4", "site": "site_id"}}, - unique_id="1", + domain=UNIFI_DOMAIN, data={"host": "1.2.3.4", "site": "site_id"}, unique_id="1" ) entry.add_to_hass(hass) @@ -399,9 +401,9 @@ async def test_reauth_flow_update_configuration(hass, aioclient_mock): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "reauth_successful" - assert config_entry.data[CONF_CONTROLLER][CONF_HOST] == "1.2.3.4" - assert config_entry.data[CONF_CONTROLLER][CONF_USERNAME] == "new_name" - assert config_entry.data[CONF_CONTROLLER][CONF_PASSWORD] == "new_pass" + assert config_entry.data[CONF_HOST] == "1.2.3.4" + assert config_entry.data[CONF_USERNAME] == "new_name" + assert config_entry.data[CONF_PASSWORD] == "new_pass" async def test_advanced_option_flow(hass, aioclient_mock): @@ -544,7 +546,7 @@ async def test_form_ssdp_aborts_if_host_already_exists(hass): await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=UNIFI_DOMAIN, - data={"controller": {"host": "192.168.208.1", "site": "site_id"}}, + data={"host": "192.168.208.1", "site": "site_id"}, ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 3ecd44b3db729e..00865b4e910d4d 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -66,7 +66,7 @@ CONF_VERIFY_SSL: False, } -ENTRY_CONFIG = {CONF_CONTROLLER: CONTROLLER_DATA} +ENTRY_CONFIG = {**CONTROLLER_DATA, CONF_CONTROLLER: CONTROLLER_DATA} ENTRY_OPTIONS = {} CONFIGURATION = [] @@ -167,6 +167,7 @@ async def setup_unifi_integration( options=deepcopy(options), entry_id=1, unique_id="1", + version=1, ) config_entry.add_to_hass(hass) @@ -178,8 +179,8 @@ async def setup_unifi_integration( if aioclient_mock: mock_default_unifi_requests( aioclient_mock, - host=config_entry.data[CONF_CONTROLLER][CONF_HOST], - site_id=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID], + host=config_entry.data[CONF_HOST], + site_id=config_entry.data[CONF_SITE_ID], sites=sites, description=site_description, clients_response=clients_response, diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 9de8b0a0990259..6d8b894fc347c1 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -2,10 +2,11 @@ from unittest.mock import AsyncMock, Mock, patch from homeassistant.components import unifi -from homeassistant.components.unifi.const import DOMAIN as UNIFI_DOMAIN +from homeassistant.components.unifi import async_flatten_entry_data +from homeassistant.components.unifi.const import CONF_CONTROLLER, DOMAIN as UNIFI_DOMAIN from homeassistant.setup import async_setup_component -from .test_controller import setup_unifi_integration +from .test_controller import CONTROLLER_DATA, ENTRY_CONFIG, setup_unifi_integration from tests.common import MockConfigEntry, mock_coro @@ -35,17 +36,9 @@ async def test_controller_no_mac(hass): """Test that configured options for a host are loaded via config entry.""" entry = MockConfigEntry( domain=UNIFI_DOMAIN, - data={ - "controller": { - "host": "0.0.0.0", - "username": "user", - "password": "pass", - "port": 80, - "site": "default", - "verify_ssl": True, - }, - }, + data=ENTRY_CONFIG, unique_id="1", + version=1, ) entry.add_to_hass(hass) mock_registry = Mock() @@ -64,6 +57,17 @@ async def test_controller_no_mac(hass): assert len(mock_registry.mock_calls) == 0 +async def test_flatten_entry_data(hass): + """Verify entry data can be flattened.""" + entry = MockConfigEntry( + domain=UNIFI_DOMAIN, + data={CONF_CONTROLLER: CONTROLLER_DATA}, + ) + await async_flatten_entry_data(hass, entry) + + assert entry.data == ENTRY_CONFIG + + async def test_unload_entry(hass, aioclient_mock): """Test being able to unload an entry.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) From 818501216ea6496d09580a6bde972e03ca92be8b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 7 Feb 2021 00:06:57 +0000 Subject: [PATCH 0247/1818] [ci skip] Translation update --- .../components/airvisual/translations/es.json | 18 ++++- .../components/foscam/translations/ca.json | 2 + .../components/foscam/translations/et.json | 2 + .../components/foscam/translations/it.json | 2 + .../components/foscam/translations/ru.json | 2 + .../foscam/translations/zh-Hant.json | 2 + .../components/fritzbox/translations/es.json | 9 ++- .../fritzbox_callmonitor/translations/es.json | 27 +++++++ .../components/homekit/translations/es.json | 14 +++- .../huisbaasje/translations/es.json | 21 +++++ .../lutron_caseta/translations/es.json | 6 ++ .../components/lyric/translations/es.json | 16 ++++ .../components/mysensors/translations/ca.json | 79 +++++++++++++++++++ .../components/mysensors/translations/es.json | 32 ++++++++ .../components/mysensors/translations/et.json | 79 +++++++++++++++++++ .../components/mysensors/translations/it.json | 79 +++++++++++++++++++ .../components/mysensors/translations/ru.json | 79 +++++++++++++++++++ .../mysensors/translations/zh-Hant.json | 77 ++++++++++++++++++ .../components/nuki/translations/es.json | 18 +++++ .../components/number/translations/es.json | 3 + .../components/plaato/translations/es.json | 27 +++++++ .../components/zwave_js/translations/es.json | 17 ++++ 22 files changed, 608 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/es.json create mode 100644 homeassistant/components/huisbaasje/translations/es.json create mode 100644 homeassistant/components/lyric/translations/es.json create mode 100644 homeassistant/components/mysensors/translations/ca.json create mode 100644 homeassistant/components/mysensors/translations/es.json create mode 100644 homeassistant/components/mysensors/translations/et.json create mode 100644 homeassistant/components/mysensors/translations/it.json create mode 100644 homeassistant/components/mysensors/translations/ru.json create mode 100644 homeassistant/components/mysensors/translations/zh-Hant.json create mode 100644 homeassistant/components/nuki/translations/es.json create mode 100644 homeassistant/components/number/translations/es.json diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index 4325f1561b9e43..9b3ac467b84075 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "No se pudo conectar", "general_error": "Se ha producido un error desconocido.", - "invalid_api_key": "Se proporciona una clave API no v\u00e1lida." + "invalid_api_key": "Se proporciona una clave API no v\u00e1lida.", + "location_not_found": "Ubicaci\u00f3n no encontrada" }, "step": { "geography": { @@ -19,6 +20,21 @@ "description": "Utilizar la API en la nube de AirVisual para monitorizar una ubicaci\u00f3n geogr\u00e1fica.", "title": "Configurar una Geograf\u00eda" }, + "geography_by_coords": { + "data": { + "api_key": "Clave API", + "latitude": "Latitud", + "longitude": "Longitud" + } + }, + "geography_by_name": { + "data": { + "api_key": "Clave API", + "city": "Ciudad", + "country": "Pa\u00eds", + "state": "estado" + } + }, "node_pro": { "data": { "ip_address": "Direcci\u00f3n IP/Nombre de host de la Unidad", diff --git a/homeassistant/components/foscam/translations/ca.json b/homeassistant/components/foscam/translations/ca.json index 5a6c84f400e0bc..b7f71c8c9229cc 100644 --- a/homeassistant/components/foscam/translations/ca.json +++ b/homeassistant/components/foscam/translations/ca.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_response": "Resposta del dispositiu inv\u00e0lida", "unknown": "Error inesperat" }, "step": { @@ -14,6 +15,7 @@ "host": "Amfitri\u00f3", "password": "Contrasenya", "port": "Port", + "rtsp_port": "Port RTSP", "stream": "Flux de v\u00eddeo", "username": "Nom d'usuari" } diff --git a/homeassistant/components/foscam/translations/et.json b/homeassistant/components/foscam/translations/et.json index b20a33aec1d974..c21ffa0cdd1f53 100644 --- a/homeassistant/components/foscam/translations/et.json +++ b/homeassistant/components/foscam/translations/et.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u00dchendamine nurjus", "invalid_auth": "Vigane autentimine", + "invalid_response": "Seadme vastus on vigane", "unknown": "Ootamatu t\u00f5rge" }, "step": { @@ -14,6 +15,7 @@ "host": "Host", "password": "Salas\u00f5na", "port": "Port", + "rtsp_port": "RTSP port", "stream": "Voog", "username": "Kasutajanimi" } diff --git a/homeassistant/components/foscam/translations/it.json b/homeassistant/components/foscam/translations/it.json index 0562012b1fab0b..63868a0f07fd63 100644 --- a/homeassistant/components/foscam/translations/it.json +++ b/homeassistant/components/foscam/translations/it.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", + "invalid_response": "Risposta non valida dal dispositivo", "unknown": "Errore imprevisto" }, "step": { @@ -14,6 +15,7 @@ "host": "Host", "password": "Password", "port": "Porta", + "rtsp_port": "Porta RTSP", "stream": "Flusso", "username": "Nome utente" } diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json index ad8b7961ca36d7..01e0494a07ecd4 100644 --- a/homeassistant/components/foscam/translations/ru.json +++ b/homeassistant/components/foscam/translations/ru.json @@ -6,6 +6,7 @@ "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_response": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "step": { @@ -14,6 +15,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", + "rtsp_port": "\u041f\u043e\u0440\u0442 RTSP", "stream": "\u041f\u043e\u0442\u043e\u043a", "username": "\u041b\u043e\u0433\u0438\u043d" } diff --git a/homeassistant/components/foscam/translations/zh-Hant.json b/homeassistant/components/foscam/translations/zh-Hant.json index 2cc6303c17a8bb..a0920c935487e7 100644 --- a/homeassistant/components/foscam/translations/zh-Hant.json +++ b/homeassistant/components/foscam/translations/zh-Hant.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_response": "\u4f86\u81ea\u88dd\u7f6e\u56de\u61c9\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { @@ -14,6 +15,7 @@ "host": "\u4e3b\u6a5f\u7aef", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", + "rtsp_port": "RTSP \u57e0", "stream": "\u4e32\u6d41", "username": "\u4f7f\u7528\u8005\u540d\u7a31" } diff --git a/homeassistant/components/fritzbox/translations/es.json b/homeassistant/components/fritzbox/translations/es.json index 5a9544df4e50e3..c9b67ca59f1c54 100644 --- a/homeassistant/components/fritzbox/translations/es.json +++ b/homeassistant/components/fritzbox/translations/es.json @@ -4,7 +4,8 @@ "already_configured": "Este AVM FRITZ!Box ya est\u00e1 configurado.", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "no_devices_found": "No se encontraron dispositivos en la red", - "not_supported": "Conectado a AVM FRITZ!Box pero no es capaz de controlar dispositivos Smart Home." + "not_supported": "Conectado a AVM FRITZ!Box pero no es capaz de controlar dispositivos Smart Home.", + "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" @@ -18,6 +19,12 @@ }, "description": "\u00bfQuieres configurar {name}?" }, + "reauth_confirm": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritzbox_callmonitor/translations/es.json b/homeassistant/components/fritzbox_callmonitor/translations/es.json new file mode 100644 index 00000000000000..899a50507552f7 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/es.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Contrase\u00f1a", + "port": "Puerto", + "username": "Usuario" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Los prefijos tienen un formato incorrecto, comprueba el formato." + }, + "step": { + "init": { + "title": "Configurar prefijos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/es.json b/homeassistant/components/homekit/translations/es.json index aeb75f838c1507..8ceee3f3016e24 100644 --- a/homeassistant/components/homekit/translations/es.json +++ b/homeassistant/components/homekit/translations/es.json @@ -4,6 +4,17 @@ "port_name_in_use": "Ya est\u00e1 configurada una pasarela con el mismo nombre o puerto." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entidad" + } + }, + "bridge_mode": { + "data": { + "include_domains": "Dominios a incluir" + }, + "title": "Selecciona los dominios a incluir" + }, "pairing": { "description": "Tan pronto como la pasarela {name} est\u00e9 lista, la vinculaci\u00f3n estar\u00e1 disponible en \"Notificaciones\" como \"configuraci\u00f3n de pasarela Homekit\"", "title": "Vincular pasarela Homekit" @@ -11,7 +22,8 @@ "user": { "data": { "auto_start": "Arranque autom\u00e1tico (desactivado si se utiliza Z-Wave u otro sistema de arranque retardado)", - "include_domains": "Dominios para incluir" + "include_domains": "Dominios para incluir", + "mode": "Modo" }, "description": "Una pasarela Homekit permitir\u00e1 a Homekit acceder a sus entidades de Home Assistant. La pasarela Homekit est\u00e1 limitada a 150 accesorios por instancia incluyendo la propia pasarela. Si desea enlazar m\u00e1s del m\u00e1ximo n\u00famero de accesorios, se recomienda que use multiples pasarelas Homekit para diferentes dominios. Configuraci\u00f3n detallada de la entidad solo est\u00e1 disponible via YAML para la pasarela primaria.", "title": "Activar pasarela Homekit" diff --git a/homeassistant/components/huisbaasje/translations/es.json b/homeassistant/components/huisbaasje/translations/es.json new file mode 100644 index 00000000000000..def06b0941dec3 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/es.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "connection_exception": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unauthenticated_exception": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/es.json b/homeassistant/components/lutron_caseta/translations/es.json index 37b1a0d90728e6..460d4fc5e69f2e 100644 --- a/homeassistant/components/lutron_caseta/translations/es.json +++ b/homeassistant/components/lutron_caseta/translations/es.json @@ -29,6 +29,12 @@ }, "device_automation": { "trigger_subtype": { + "button_1": "Primer bot\u00f3n", + "button_3": "Tercer bot\u00f3n", + "close_1": "Cerrar 1", + "close_2": "Cerrar 2", + "off": "Apagado", + "on": "Encendido", "open_1": "Abrir 1", "open_2": "Abrir 2", "open_3": "Abrir 3", diff --git a/homeassistant/components/lyric/translations/es.json b/homeassistant/components/lyric/translations/es.json new file mode 100644 index 00000000000000..db8d744d17620f --- /dev/null +++ b/homeassistant/components/lyric/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", + "missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n." + }, + "create_entry": { + "default": "Autenticado correctamente" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ca.json b/homeassistant/components/mysensors/translations/ca.json new file mode 100644 index 00000000000000..844d9e51da1de9 --- /dev/null +++ b/homeassistant/components/mysensors/translations/ca.json @@ -0,0 +1,79 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "duplicate_persistence_file": "Fitxer de persist\u00e8ncia ja en \u00fas", + "duplicate_topic": "Topic ja en \u00fas", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_device": "Dispositiu no v\u00e0lid", + "invalid_ip": "Adre\u00e7a IP inv\u00e0lida", + "invalid_persistence_file": "Fitxer de persist\u00e8ncia inv\u00e0lid", + "invalid_port": "N\u00famero de port inv\u00e0lid", + "invalid_publish_topic": "Topic de publicaci\u00f3 inv\u00e0lid", + "invalid_serial": "Port s\u00e8rie inv\u00e0lid", + "invalid_subscribe_topic": "Topic de subscripci\u00f3 inv\u00e0lid", + "invalid_version": "Versi\u00f3 de MySensors inv\u00e0lida", + "not_a_number": "Introdueix un n\u00famero", + "port_out_of_range": "El n\u00famero de port ha d'estar entre 1 i 65535", + "same_topic": "Els topics de publicaci\u00f3 i subscripci\u00f3 son els mateixos", + "unknown": "Error inesperat" + }, + "error": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", + "duplicate_persistence_file": "Fitxer de persist\u00e8ncia ja en \u00fas", + "duplicate_topic": "Topic ja en \u00fas", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_device": "Dispositiu no v\u00e0lid", + "invalid_ip": "Adre\u00e7a IP inv\u00e0lida", + "invalid_persistence_file": "Fitxer de persist\u00e8ncia inv\u00e0lid", + "invalid_port": "N\u00famero de port inv\u00e0lid", + "invalid_publish_topic": "Topic de publicaci\u00f3 inv\u00e0lid", + "invalid_serial": "Port s\u00e8rie inv\u00e0lid", + "invalid_subscribe_topic": "Topic de subscripci\u00f3 inv\u00e0lid", + "invalid_version": "Versi\u00f3 de MySensors inv\u00e0lida", + "not_a_number": "Introdueix un n\u00famero", + "port_out_of_range": "El n\u00famero de port ha d'estar entre 1 i 65535", + "same_topic": "Els topics de publicaci\u00f3 i subscripci\u00f3 son els mateixos", + "unknown": "Error inesperat" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "fitxer de persist\u00e8ncia (deixa-ho buit per generar-lo autom\u00e0ticament)", + "retain": "retenci\u00f3 mqtt", + "topic_in_prefix": "prefix per als topics d'entrada (topic_in_prefix)", + "topic_out_prefix": "prefix per als topics de sortida (topic_out_prefix)", + "version": "Versi\u00f3 de MySensors" + }, + "description": "Configuraci\u00f3 de passarel\u00b7la MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "Velocitat, en baudis", + "device": "Port s\u00e8rie", + "persistence_file": "fitxer de persist\u00e8ncia (deixa-ho buit per generar-lo autom\u00e0ticament)", + "version": "Versi\u00f3 de MySensors" + }, + "description": "Configuraci\u00f3 de passarel\u00b7la s\u00e8rie" + }, + "gw_tcp": { + "data": { + "device": "Adre\u00e7a IP de la passarel\u00b7la", + "persistence_file": "fitxer de persist\u00e8ncia (deixa-ho buit per generar-lo autom\u00e0ticament)", + "tcp_port": "port", + "version": "Versi\u00f3 de MySensors" + }, + "description": "Configuraci\u00f3 de passarel\u00b7la Ethernet" + }, + "user": { + "data": { + "gateway_type": "Tipus de passarel\u00b7la" + }, + "description": "Tria el m\u00e8tode de connexi\u00f3 a la passarel\u00b7la" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/es.json b/homeassistant/components/mysensors/translations/es.json new file mode 100644 index 00000000000000..f43762b9701286 --- /dev/null +++ b/homeassistant/components/mysensors/translations/es.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "invalid_device": "Dispositivo no v\u00e1lido", + "invalid_ip": "Direcci\u00f3n IP no v\u00e1lida", + "invalid_port": "N\u00famero de puerto no v\u00e1lido", + "invalid_serial": "Puerto serie no v\u00e1lido", + "invalid_version": "Versi\u00f3n no v\u00e1lida de MySensors", + "not_a_number": "Por favor, introduce un n\u00famero", + "port_out_of_range": "El n\u00famero de puerto debe ser como m\u00ednimo 1 y como m\u00e1ximo 65535", + "unknown": "Error inesperado" + }, + "step": { + "gw_mqtt": { + "data": { + "version": "Versi\u00f3n de MySensors" + }, + "description": "Configuraci\u00f3n del gateway MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "tasa de baudios", + "device": "Puerto serie", + "version": "Versi\u00f3n de MySensors" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/et.json b/homeassistant/components/mysensors/translations/et.json new file mode 100644 index 00000000000000..0682610be97fef --- /dev/null +++ b/homeassistant/components/mysensors/translations/et.json @@ -0,0 +1,79 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "duplicate_persistence_file": "P\u00fcsivusfail on juba kasutusel", + "duplicate_topic": "Teema on juba kasutusel", + "invalid_auth": "Vigane autentimine", + "invalid_device": "Sobimatu seade", + "invalid_ip": "Sobimatu IP-aadress", + "invalid_persistence_file": "Sobimatu p\u00fcsivusfail", + "invalid_port": "Lubamatu pordinumber", + "invalid_publish_topic": "Kehtetu avaldamisteema", + "invalid_serial": "Sobimatu jadaport", + "invalid_subscribe_topic": "Kehtetu tellimisteema", + "invalid_version": "Sobimatu MySensors versioon", + "not_a_number": "Sisesta number", + "port_out_of_range": "Pordi number peab olema v\u00e4hemalt 1 ja k\u00f5ige rohkem 65535", + "same_topic": "Tellimise ja avaldamise teemad kattuvad", + "unknown": "Ootamatu t\u00f5rge" + }, + "error": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendamine nurjus", + "duplicate_persistence_file": "P\u00fcsivusfail on juba kasutusel", + "duplicate_topic": "Teema on juba kasutusel", + "invalid_auth": "Vigane autentimine", + "invalid_device": "Sobimatu seade", + "invalid_ip": "Sobimatu IP-aadress", + "invalid_persistence_file": "Sobimatu p\u00fcsivusfail", + "invalid_port": "Lubamatu pordinumber", + "invalid_publish_topic": "Kehtetu avaldamisteema", + "invalid_serial": "Sobimatu jadaport", + "invalid_subscribe_topic": "Kehtetu tellimisteema", + "invalid_version": "Sobimatu MySensors versioon", + "not_a_number": "Sisesta number", + "port_out_of_range": "Pordi number peab olema v\u00e4hemalt 1 ja k\u00f5ige rohkem 65535", + "same_topic": "Tellimise ja avaldamise teemad kattuvad", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "p\u00fcsivusfail (j\u00e4ta automaatse genereerimise jaoks t\u00fchjaks)", + "retain": "mqtt oleku s\u00e4ilitamine", + "topic_in_prefix": "sisendteemade eesliide (topic_in_prefix)", + "topic_out_prefix": "v\u00e4ljunditeemade eesliide (topic_out_prefix)", + "version": "MySensors versioon" + }, + "description": "MQTT-l\u00fc\u00fcsi seadistamine" + }, + "gw_serial": { + "data": { + "baud_rate": "andmeedastuskiirus", + "device": "Jadaport", + "persistence_file": "p\u00fcsivusfail (j\u00e4ta automaatse genereerimise jaoks t\u00fchjaks)", + "version": "MySensors versioon" + }, + "description": "Jadal\u00fc\u00fcsi h\u00e4\u00e4lestus" + }, + "gw_tcp": { + "data": { + "device": "L\u00fc\u00fcsi IP-aadress", + "persistence_file": "p\u00fcsivusfail (j\u00e4ta automaatse genereerimise jaoks t\u00fchjaks)", + "tcp_port": "port", + "version": "MySensors versioon" + }, + "description": "Etherneti l\u00fc\u00fcsi seadistamine" + }, + "user": { + "data": { + "gateway_type": "L\u00fc\u00fcsi t\u00fc\u00fcp" + }, + "description": "Vali l\u00fc\u00fcsi \u00fchendusviis" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/it.json b/homeassistant/components/mysensors/translations/it.json new file mode 100644 index 00000000000000..f256ddb95eb274 --- /dev/null +++ b/homeassistant/components/mysensors/translations/it.json @@ -0,0 +1,79 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "duplicate_persistence_file": "File di persistenza gi\u00e0 in uso", + "duplicate_topic": "Argomento gi\u00e0 in uso", + "invalid_auth": "Autenticazione non valida", + "invalid_device": "Dispositivo non valido", + "invalid_ip": "Indirizzo IP non valido", + "invalid_persistence_file": "File di persistenza non valido", + "invalid_port": "Numero di porta non valido", + "invalid_publish_topic": "Argomento di pubblicazione non valido", + "invalid_serial": "Porta seriale non valida", + "invalid_subscribe_topic": "Argomento di sottoscrizione non valido", + "invalid_version": "Versione di MySensors non valida", + "not_a_number": "Per favore inserisci un numero", + "port_out_of_range": "Il numero di porta deve essere almeno 1 e al massimo 65535", + "same_topic": "Gli argomenti di sottoscrizione e pubblicazione sono gli stessi", + "unknown": "Errore imprevisto" + }, + "error": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", + "duplicate_persistence_file": "File di persistenza gi\u00e0 in uso", + "duplicate_topic": "Argomento gi\u00e0 in uso", + "invalid_auth": "Autenticazione non valida", + "invalid_device": "Dispositivo non valido", + "invalid_ip": "Indirizzo IP non valido", + "invalid_persistence_file": "File di persistenza non valido", + "invalid_port": "Numero di porta non valido", + "invalid_publish_topic": "Argomento di pubblicazione non valido", + "invalid_serial": "Porta seriale non valida", + "invalid_subscribe_topic": "Argomento di sottoscrizione non valido", + "invalid_version": "Versione di MySensors non valida", + "not_a_number": "Per favore inserisci un numero", + "port_out_of_range": "Il numero di porta deve essere almeno 1 e al massimo 65535", + "same_topic": "Gli argomenti di sottoscrizione e pubblicazione sono gli stessi", + "unknown": "Errore imprevisto" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "file di persistenza (lasciare vuoto per generare automaticamente)", + "retain": "mqtt conserva", + "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" + }, + "description": "Configurazione del gateway MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "velocit\u00e0 di trasmissione", + "device": "Porta seriale", + "persistence_file": "file di persistenza (lasciare vuoto per generare automaticamente)", + "version": "Versione MySensors" + }, + "description": "Configurazione del gateway seriale" + }, + "gw_tcp": { + "data": { + "device": "Indirizzo IP del gateway", + "persistence_file": "file di persistenza (lasciare vuoto per generare automaticamente)", + "tcp_port": "porta", + "version": "Versione MySensors" + }, + "description": "Configurazione del gateway Ethernet" + }, + "user": { + "data": { + "gateway_type": "Tipo di gateway" + }, + "description": "Scegli il metodo di connessione al gateway" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ru.json b/homeassistant/components/mysensors/translations/ru.json new file mode 100644 index 00000000000000..e78685e3f6bdde --- /dev/null +++ b/homeassistant/components/mysensors/translations/ru.json @@ -0,0 +1,79 @@ +{ + "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.", + "duplicate_persistence_file": "\u042d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", + "duplicate_topic": "\u042d\u0442\u043e\u0442 \u0442\u043e\u043f\u0438\u043a \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_device": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", + "invalid_ip": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441.", + "invalid_persistence_file": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439.", + "invalid_port": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430.", + "invalid_publish_topic": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_serial": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442.", + "invalid_subscribe_topic": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438.", + "invalid_version": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f MySensors.", + "not_a_number": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0447\u0438\u0441\u043b\u043e.", + "port_out_of_range": "\u041d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0442 1 \u0434\u043e 65535.", + "same_topic": "\u0422\u043e\u043f\u0438\u043a\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "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.", + "duplicate_persistence_file": "\u042d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", + "duplicate_topic": "\u042d\u0442\u043e\u0442 \u0442\u043e\u043f\u0438\u043a \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_device": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", + "invalid_ip": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441.", + "invalid_persistence_file": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439.", + "invalid_port": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430.", + "invalid_publish_topic": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_serial": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442.", + "invalid_subscribe_topic": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438.", + "invalid_version": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f MySensors.", + "not_a_number": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0447\u0438\u0441\u043b\u043e.", + "port_out_of_range": "\u041d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0442 1 \u0434\u043e 65535.", + "same_topic": "\u0422\u043e\u043f\u0438\u043a\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "\u0424\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f)", + "retain": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f MQTT", + "topic_in_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u043e\u043f\u0438\u043a\u043e\u0432 (topic_in_prefix)", + "topic_out_prefix": "\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u0434\u043b\u044f \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u043e\u043f\u0438\u043a\u043e\u0432 (topic_out_prefix)", + "version": "\u0412\u0435\u0440\u0441\u0438\u044f MySensors" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0448\u043b\u044e\u0437\u0430 MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445", + "device": "\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442", + "persistence_file": "\u0424\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f)", + "version": "\u0412\u0435\u0440\u0441\u0438\u044f MySensors" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0448\u043b\u044e\u0437\u0430" + }, + "gw_tcp": { + "data": { + "device": "IP-\u0430\u0434\u0440\u0435\u0441 \u0448\u043b\u044e\u0437\u0430", + "persistence_file": "\u0424\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f)", + "tcp_port": "\u041f\u043e\u0440\u0442", + "version": "\u0412\u0435\u0440\u0441\u0438\u044f MySensors" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0448\u043b\u044e\u0437\u0430 Ethernet" + }, + "user": { + "data": { + "gateway_type": "\u0422\u0438\u043f \u0448\u043b\u044e\u0437\u0430" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0448\u043b\u044e\u0437\u0443" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/zh-Hant.json b/homeassistant/components/mysensors/translations/zh-Hant.json new file mode 100644 index 00000000000000..0d4db4502e5378 --- /dev/null +++ b/homeassistant/components/mysensors/translations/zh-Hant.json @@ -0,0 +1,77 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "duplicate_topic": "\u4e3b\u984c\u5df2\u4f7f\u7528\u4e2d", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_device": "\u88dd\u7f6e\u7121\u6548", + "invalid_ip": "IP \u4f4d\u5740\u7121\u6548", + "invalid_port": "\u901a\u8a0a\u57e0\u865f\u78bc\u7121\u6548", + "invalid_publish_topic": "\u767c\u5e03\u4e3b\u984c\u7121\u6548", + "invalid_serial": "\u5e8f\u5217\u57e0\u7121\u6548", + "invalid_subscribe_topic": "\u8a02\u95b1\u4e3b\u984c\u7121\u6548", + "invalid_version": "MySensors \u7248\u672c\u7121\u6548", + "not_a_number": "\u8acb\u8f38\u5165\u865f\u78bc", + "port_out_of_range": "\u8acb\u8f38\u5165\u4ecb\u65bc 1 \u81f3 65535 \u4e4b\u9593\u7684\u865f\u78bc", + "same_topic": "\u8a02\u95b1\u8207\u767c\u4f48\u4e3b\u984c\u76f8\u540c", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "error": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "duplicate_persistence_file": "Persistence \u6a94\u6848\u5df2\u4f7f\u7528\u4e2d", + "duplicate_topic": "\u4e3b\u984c\u5df2\u4f7f\u7528\u4e2d", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "invalid_device": "\u88dd\u7f6e\u7121\u6548", + "invalid_ip": "IP \u4f4d\u5740\u7121\u6548", + "invalid_persistence_file": "Persistence \u6a94\u6848\u7121\u6548", + "invalid_port": "\u901a\u8a0a\u57e0\u865f\u78bc\u7121\u6548", + "invalid_publish_topic": "\u767c\u5e03\u4e3b\u984c\u7121\u6548", + "invalid_serial": "\u5e8f\u5217\u57e0\u7121\u6548", + "invalid_subscribe_topic": "\u8a02\u95b1\u4e3b\u984c\u7121\u6548", + "invalid_version": "MySensors \u7248\u672c\u7121\u6548", + "not_a_number": "\u8acb\u8f38\u5165\u865f\u78bc", + "port_out_of_range": "\u8acb\u8f38\u5165\u4ecb\u65bc 1 \u81f3 65535 \u4e4b\u9593\u7684\u865f\u78bc", + "same_topic": "\u8a02\u95b1\u8207\u767c\u4f48\u4e3b\u984c\u76f8\u540c", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "Persistence \u6a94\u6848\uff08\u4fdd\u7559\u7a7a\u767d\u5c07\u81ea\u52d5\u7522\u751f\uff09", + "retain": "mqtt retain", + "topic_in_prefix": "\u8f38\u5165\u4e3b\u984c\u524d\u7db4\uff08topic_in_prefix\uff09", + "topic_out_prefix": "\u8f38\u51fa\u4e3b\u984c\u524d\u7db4\uff08topic_out_prefix\uff09", + "version": "MySensors \u7248\u672c" + }, + "description": "MQTT \u9598\u9053\u5668\u8a2d\u5b9a" + }, + "gw_serial": { + "data": { + "baud_rate": "\u50b3\u8f38\u7387", + "device": "\u5e8f\u5217\u57e0", + "persistence_file": "Persistence \u6a94\u6848\uff08\u4fdd\u7559\u7a7a\u767d\u5c07\u81ea\u52d5\u7522\u751f\uff09", + "version": "MySensors \u7248\u672c" + }, + "description": "\u9598\u9053\u5668\u8a0a\u5217\u57e0\u8a2d\u5b9a" + }, + "gw_tcp": { + "data": { + "device": "\u7db2\u95dc IP \u4f4d\u5740", + "persistence_file": "Persistence \u6a94\u6848\uff08\u4fdd\u7559\u7a7a\u767d\u5c07\u81ea\u52d5\u7522\u751f\uff09", + "tcp_port": "\u901a\u8a0a\u57e0", + "version": "MySensors \u7248\u672c" + }, + "description": "\u9598\u9053\u5668\u4e59\u592a\u7db2\u8def\u8a2d\u5b9a" + }, + "user": { + "data": { + "gateway_type": "\u9598\u9053\u5668\u985e\u578b" + }, + "description": "\u9078\u64c7\u9598\u9053\u5668\u9023\u7dda\u65b9\u5f0f" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/es.json b/homeassistant/components/nuki/translations/es.json new file mode 100644 index 00000000000000..8def4e2780d2ef --- /dev/null +++ b/homeassistant/components/nuki/translations/es.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto", + "token": "Token de acceso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/es.json b/homeassistant/components/number/translations/es.json new file mode 100644 index 00000000000000..e77258e777d0ee --- /dev/null +++ b/homeassistant/components/number/translations/es.json @@ -0,0 +1,3 @@ +{ + "title": "N\u00famero" +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/es.json b/homeassistant/components/plaato/translations/es.json index 0f030e56b4eb0c..6ff49fc707c401 100644 --- a/homeassistant/components/plaato/translations/es.json +++ b/homeassistant/components/plaato/translations/es.json @@ -1,16 +1,43 @@ { "config": { "abort": { + "already_configured": "La cuenta ya ha sido configurada", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n.", "webhook_not_internet_accessible": "Tu instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes webhook." }, "create_entry": { "default": "Para enviar eventos a Home Assistant, necesitar\u00e1s configurar la funci\u00f3n de webhook en Plaato Airlock.\n\nCompleta la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n\nEcha un vistazo a [la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." }, + "error": { + "invalid_webhook_device": "Has seleccionado un dispositivo que no admite el env\u00edo de datos a un webhook. Solo est\u00e1 disponible para Airlock", + "no_api_method": "Necesitas a\u00f1adir un token de autenticaci\u00f3n o seleccionar un webhook", + "no_auth_token": "Es necesario a\u00f1adir un token de autenticaci\u00f3n" + }, "step": { + "api_method": { + "data": { + "token": "Pega el token de autenticaci\u00f3n aqu\u00ed", + "use_webhook": "Usar webhook" + }, + "title": "Selecciona el m\u00e9todo API" + }, "user": { "description": "\u00bfEst\u00e1s seguro de que quieres configurar el Airlock de Plaato?", "title": "Configurar el webhook de Plaato" + }, + "webhook": { + "title": "Webhook a utilizar" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Intervalo de actualizaci\u00f3n (minutos)" + }, + "description": "Intervalo de actualizaci\u00f3n (minutos)", + "title": "Opciones de Plaato" } } } diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index e5ee009c0d1014..257d26eefd6ce6 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -1,22 +1,39 @@ { "config": { "abort": { + "addon_info_failed": "No se pudo obtener la informaci\u00f3n del complemento Z-Wave JS.", + "addon_install_failed": "No se ha podido instalar el complemento Z-Wave JS.", + "addon_set_config_failed": "Fallo en la configuraci\u00f3n de Z-Wave JS.", "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar" }, "error": { + "addon_start_failed": "No se pudo iniciar el complemento Z-Wave JS. Comprueba la configuraci\u00f3n.", "cannot_connect": "No se pudo conectar", "invalid_ws_url": "URL de websocket no v\u00e1lida", "unknown": "Error inesperado" }, + "progress": { + "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos." + }, "step": { + "hassio_confirm": { + "title": "Configurar la integraci\u00f3n de Z-Wave JS con el complemento Z-Wave JS" + }, + "install_addon": { + "title": "La instalaci\u00f3n del complemento Z-Wave JS ha comenzado" + }, "manual": { "data": { "url": "URL" } }, "on_supervisor": { + "data": { + "use_addon": "Usar el complemento Z-Wave JS Supervisor" + }, + "description": "\u00bfQuieres utilizar el complemento Z-Wave JS Supervisor?", "title": "Selecciona el m\u00e9todo de conexi\u00f3n" }, "start_addon": { From 94eb31025c57a4745163146c3d12d235980ebca3 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sun, 7 Feb 2021 01:27:58 +0100 Subject: [PATCH 0248/1818] xknx 0.16.3 (#46128) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 93daee0e348c15..60fb097128c4ae 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.16.2"], + "requirements": ["xknx==0.16.3"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index 1962b4d393e431..1f5704aa3b70f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2323,7 +2323,7 @@ xboxapi==2.0.1 xfinity-gateway==0.0.4 # homeassistant.components.knx -xknx==0.16.2 +xknx==0.16.3 # homeassistant.components.bluesound # homeassistant.components.rest From 74053b5f2d953349a13e497c0e0c9fb610a632b2 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Feb 2021 13:28:40 -0500 Subject: [PATCH 0249/1818] Use core constants for envisalink (#46136) --- homeassistant/components/envisalink/__init__.py | 9 ++++++--- .../components/envisalink/alarm_control_panel.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index 636cf0c19df44b..73e20eea92cfc7 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -5,7 +5,12 @@ from pyenvisalink import EnvisalinkAlarmPanel import voluptuous as vol -from homeassistant.const import CONF_HOST, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_CODE, + CONF_HOST, + CONF_TIMEOUT, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform @@ -18,7 +23,6 @@ DATA_EVL = "envisalink" -CONF_CODE = "code" CONF_EVL_KEEPALIVE = "keepalive_interval" CONF_EVL_PORT = "port" CONF_EVL_VERSION = "evl_version" @@ -99,7 +103,6 @@ async def async_setup(hass, config): """Set up for Envisalink devices.""" - conf = config.get(DOMAIN) host = conf.get(CONF_HOST) diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index 670dc78392f4d8..dff434a68ee931 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -15,6 +15,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + CONF_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, @@ -28,7 +29,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import ( - CONF_CODE, CONF_PANIC, CONF_PARTITIONNAME, DATA_EVL, From 19e9515bec9e76603deada69a87c4e0f4db833ee Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Feb 2021 13:47:16 -0500 Subject: [PATCH 0250/1818] Use core constants for ffmpeg_motion (#46137) --- homeassistant/components/ffmpeg_motion/binary_sensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py index 314fbbd22107ee..ecbf6f3b1aecb2 100644 --- a/homeassistant/components/ffmpeg_motion/binary_sensor.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -14,13 +14,12 @@ DATA_FFMPEG, FFmpegBase, ) -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_REPEAT from homeassistant.core import callback import homeassistant.helpers.config_validation as cv CONF_RESET = "reset" CONF_CHANGES = "changes" -CONF_REPEAT = "repeat" CONF_REPEAT_TIME = "repeat_time" DEFAULT_NAME = "FFmpeg Motion" @@ -88,7 +87,6 @@ class FFmpegMotion(FFmpegBinarySensor): def __init__(self, hass, manager, config): """Initialize FFmpeg motion binary sensor.""" - super().__init__(config) self.ffmpeg = ffmpeg_sensor.SensorMotion(manager.binary, self._async_callback) From 8e06fa017d7e472ce54866486b9373999d0785e8 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Feb 2021 13:52:48 -0500 Subject: [PATCH 0251/1818] Use core constants for emulated_hue (#46092) --- homeassistant/components/emulated_hue/__init__.py | 9 ++++++--- homeassistant/components/emulated_hue/upnp.py | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index b4a49c7efcdc14..11ad80688a30d2 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -5,7 +5,12 @@ import voluptuous as vol from homeassistant import util -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_ENTITIES, + CONF_TYPE, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json @@ -31,7 +36,6 @@ CONF_ADVERTISE_IP = "advertise_ip" CONF_ADVERTISE_PORT = "advertise_port" -CONF_ENTITIES = "entities" CONF_ENTITY_HIDDEN = "hidden" CONF_ENTITY_NAME = "name" CONF_EXPOSE_BY_DEFAULT = "expose_by_default" @@ -40,7 +44,6 @@ CONF_LIGHTS_ALL_DIMMABLE = "lights_all_dimmable" CONF_LISTEN_PORT = "listen_port" CONF_OFF_MAPS_TO_ON_DOMAINS = "off_maps_to_on_domains" -CONF_TYPE = "type" CONF_UPNP_BIND_MULTICAST = "upnp_bind_multicast" TYPE_ALEXA = "alexa" diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index 58f964d4984dc5..8ff7eb85b39ae3 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -63,7 +63,6 @@ def create_upnp_datagram_endpoint( advertise_port, ): """Create the UPNP socket and protocol.""" - # Listen for UDP port 1900 packets sent to SSDP multicast address ssdp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ssdp_socket.setblocking(False) From aa00c6230262151ef01d0552ccae10333ca97242 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 8 Feb 2021 00:07:01 +0000 Subject: [PATCH 0252/1818] [ci skip] Translation update --- .../components/binary_sensor/translations/pl.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/binary_sensor/translations/pl.json b/homeassistant/components/binary_sensor/translations/pl.json index 59fd573d5b3b18..726765aea0255d 100644 --- a/homeassistant/components/binary_sensor/translations/pl.json +++ b/homeassistant/components/binary_sensor/translations/pl.json @@ -95,8 +95,8 @@ "on": "w\u0142." }, "battery": { - "off": "Normalna", - "on": "Niska" + "off": "na\u0142adowana", + "on": "roz\u0142adowana" }, "battery_charging": { "off": "roz\u0142adowywanie", @@ -107,8 +107,8 @@ "on": "zimno" }, "connectivity": { - "off": "Roz\u0142\u0105czony", - "on": "Po\u0142\u0105czony" + "off": "offline", + "on": "online" }, "door": { "off": "zamkni\u0119te", @@ -135,7 +135,7 @@ "on": "otwarty" }, "moisture": { - "off": "Sucho", + "off": "brak wilgoci", "on": "wilgo\u0107" }, "motion": { From d02b78c634f6eb7198e42d40e0024abc6d216eb4 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Sun, 7 Feb 2021 22:05:10 -0800 Subject: [PATCH 0253/1818] Revert "Convert ozw climate values to correct units (#45369)" (#46163) This reverts commit 1b6ee8301a5c076f93d0799b9f7fcb82cc6eb902. --- homeassistant/components/ozw/climate.py | 51 ++++--------------------- tests/components/ozw/test_climate.py | 20 ++++------ 2 files changed, 15 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index 67bbe5cdc4dd06..a74fd869f0f443 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -28,7 +28,6 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util.temperature import convert as convert_temperature from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity @@ -155,13 +154,6 @@ def async_add_climate(values): ) -def convert_units(units): - """Return units as a farenheit or celsius constant.""" - if units == "F": - return TEMP_FAHRENHEIT - return TEMP_CELSIUS - - class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): """Representation of a Z-Wave Climate device.""" @@ -207,18 +199,16 @@ def fan_modes(self): @property def temperature_unit(self): """Return the unit of measurement.""" - return convert_units(self._current_mode_setpoint_values[0].units) + if self.values.temperature is not None and self.values.temperature.units == "F": + return TEMP_FAHRENHEIT + return TEMP_CELSIUS @property def current_temperature(self): """Return the current temperature.""" if not self.values.temperature: return None - return convert_temperature( - self.values.temperature.value, - convert_units(self._current_mode_setpoint_values[0].units), - self.temperature_unit, - ) + return self.values.temperature.value @property def hvac_action(self): @@ -246,29 +236,17 @@ def preset_modes(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - return convert_temperature( - self._current_mode_setpoint_values[0].value, - convert_units(self._current_mode_setpoint_values[0].units), - self.temperature_unit, - ) + return self._current_mode_setpoint_values[0].value @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return convert_temperature( - self._current_mode_setpoint_values[0].value, - convert_units(self._current_mode_setpoint_values[0].units), - self.temperature_unit, - ) + return self._current_mode_setpoint_values[0].value @property def target_temperature_high(self) -> Optional[float]: """Return the highbound target temperature we try to reach.""" - return convert_temperature( - self._current_mode_setpoint_values[1].value, - convert_units(self._current_mode_setpoint_values[1].units), - self.temperature_unit, - ) + return self._current_mode_setpoint_values[1].value async def async_set_temperature(self, **kwargs): """Set new target temperature. @@ -284,29 +262,14 @@ async def async_set_temperature(self, **kwargs): setpoint = self._current_mode_setpoint_values[0] target_temp = kwargs.get(ATTR_TEMPERATURE) if setpoint is not None and target_temp is not None: - target_temp = convert_temperature( - target_temp, - self.temperature_unit, - convert_units(setpoint.units), - ) setpoint.send_value(target_temp) elif len(self._current_mode_setpoint_values) == 2: (setpoint_low, setpoint_high) = self._current_mode_setpoint_values target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if setpoint_low is not None and target_temp_low is not None: - target_temp_low = convert_temperature( - target_temp_low, - self.temperature_unit, - convert_units(setpoint_low.units), - ) setpoint_low.send_value(target_temp_low) if setpoint_high is not None and target_temp_high is not None: - target_temp_high = convert_temperature( - target_temp_high, - self.temperature_unit, - convert_units(setpoint_high.units), - ) setpoint_high.send_value(target_temp_high) async def async_set_fan_mode(self, fan_mode): diff --git a/tests/components/ozw/test_climate.py b/tests/components/ozw/test_climate.py index e251a93c11520c..3414e6c483279a 100644 --- a/tests/components/ozw/test_climate.py +++ b/tests/components/ozw/test_climate.py @@ -16,8 +16,6 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, ) -from homeassistant.components.ozw.climate import convert_units -from homeassistant.const import TEMP_FAHRENHEIT from .common import setup_ozw @@ -38,8 +36,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): HVAC_MODE_HEAT_COOL, ] assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 73.5 - assert state.attributes[ATTR_TEMPERATURE] == 70.0 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 23.1 + assert state.attributes[ATTR_TEMPERATURE] == 21.1 assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None assert state.attributes[ATTR_FAN_MODE] == "Auto Low" @@ -56,7 +54,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 26.1 + assert round(msg["payload"]["Value"], 2) == 78.98 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test hvac_mode with set_temperature @@ -74,7 +72,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 24.1 + assert round(msg["payload"]["Value"], 2) == 75.38 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test set mode @@ -129,8 +127,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert state is not None assert state.state == HVAC_MODE_HEAT_COOL assert state.attributes.get(ATTR_TEMPERATURE) is None - assert state.attributes[ATTR_TARGET_TEMP_LOW] == 70.0 - assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 78.0 + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 21.1 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.6 # Test setting high/low temp on multiple setpoints await hass.services.async_call( @@ -146,11 +144,11 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert len(sent_messages) == 7 # 2 messages ! msg = sent_messages[-2] # low setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 20.0 + assert round(msg["payload"]["Value"], 2) == 68.0 assert msg["payload"]["ValueIDKey"] == 281475099443218 msg = sent_messages[-1] # high setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 25.0 + assert round(msg["payload"]["Value"], 2) == 77.0 assert msg["payload"]["ValueIDKey"] == 562950076153874 # Test basic/single-setpoint thermostat (node 16 in dump) @@ -327,5 +325,3 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): ) assert len(sent_messages) == 12 assert "does not support setting a mode" in caplog.text - - assert convert_units("F") == TEMP_FAHRENHEIT From 66ecd2e0f2e9f67b88404684f8d99ab026363c5b Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 02:32:24 -0500 Subject: [PATCH 0254/1818] Remove unused config_flows (#46188) --- homeassistant/components/aws/__init__.py | 1 - homeassistant/components/daikin/__init__.py | 1 - homeassistant/components/mqtt/__init__.py | 1 - homeassistant/components/tellduslive/__init__.py | 1 - homeassistant/components/tradfri/__init__.py | 1 - homeassistant/components/zwave/__init__.py | 1 - 6 files changed, 6 deletions(-) diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index 19563efa14477c..da8c27d74455d2 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -16,7 +16,6 @@ from homeassistant.helpers import config_validation as cv, discovery # Loading the config flow file will register the flow -from . import config_flow # noqa: F401 from .const import ( CONF_ACCESS_KEY_ID, CONF_CONTEXT, diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index b4950b8b05bcd3..16fd2b2ff56c26 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -16,7 +16,6 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle -from . import config_flow # noqa: F401 from .const import CONF_UUID, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 788f8d1957ed80..098099f0a03f34 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -40,7 +40,6 @@ from homeassistant.util.logging import catch_log_exception # Loading the config flow file will register the flow -from . import config_flow # noqa: F401 pylint: disable=unused-import from . import debug_info, discovery from .const import ( ATTR_PAYLOAD, diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index ae98a5d85047c0..5d4721e60e6544 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -12,7 +12,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from . import config_flow # noqa: F401 from .const import ( CONF_HOST, DOMAIN, diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 8d82df07bbbd3f..4c984067ada45e 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -16,7 +16,6 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util.json import load_json -from . import config_flow # noqa: F401 from .const import ( ATTR_TRADFRI_GATEWAY, ATTR_TRADFRI_GATEWAY_MODEL, diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 27f6c0a4801fb9..3acf361dd52f43 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -36,7 +36,6 @@ from homeassistant.util import convert import homeassistant.util.dt as dt_util -from . import config_flow # noqa: F401 pylint: disable=unused-import from . import const, websocket_api as wsapi, workaround from .const import ( CONF_AUTOHEAL, From 5755598c95c772da108e48d2c945acb0570104de Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 03:31:02 -0500 Subject: [PATCH 0255/1818] Use core constants for fixer (#46173) --- homeassistant/components/fixer/sensor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index e3dfd432a416a9..99ebbdd6bb63d4 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME +from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME, CONF_TARGET import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -17,8 +17,6 @@ ATTR_TARGET = "Target currency" ATTRIBUTION = "Data provided by the European Central Bank (ECB)" -CONF_TARGET = "target" - DEFAULT_BASE = "USD" DEFAULT_NAME = "Exchange rate" @@ -37,7 +35,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Fixer.io sensor.""" - api_key = config.get(CONF_API_KEY) name = config.get(CONF_NAME) target = config.get(CONF_TARGET) @@ -103,7 +100,6 @@ class ExchangeData: def __init__(self, target_currency, api_key): """Initialize the data object.""" - self.api_key = api_key self.rate = None self.target_currency = target_currency From 04c1578f15a55aedfa3e853a2f2b0da8364bd1bc Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 03:32:01 -0500 Subject: [PATCH 0256/1818] Use core constants for file integration (#46171) --- homeassistant/components/file/sensor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index e928541a724ad1..3368bd878d5b25 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -5,14 +5,17 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE +from homeassistant.const import ( + CONF_FILE_PATH, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + CONF_VALUE_TEMPLATE, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_FILE_PATH = "file_path" - DEFAULT_NAME = "File" ICON = "mdi:file" From ca87bf49b6b5ed7ee3fdee35492c2119143be1a0 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 8 Feb 2021 09:34:12 +0100 Subject: [PATCH 0257/1818] Upgrade praw to 7.1.3 (#46073) --- homeassistant/components/reddit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reddit/manifest.json b/homeassistant/components/reddit/manifest.json index e19ae570d0f333..d270f994159d19 100644 --- a/homeassistant/components/reddit/manifest.json +++ b/homeassistant/components/reddit/manifest.json @@ -2,6 +2,6 @@ "domain": "reddit", "name": "Reddit", "documentation": "https://www.home-assistant.io/integrations/reddit", - "requirements": ["praw==7.1.2"], + "requirements": ["praw==7.1.3"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 1f5704aa3b70f7..d21c11fb466ead 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1159,7 +1159,7 @@ pmsensor==0.4 poolsense==0.0.8 # homeassistant.components.reddit -praw==7.1.2 +praw==7.1.3 # homeassistant.components.islamic_prayer_times prayer_times_calculator==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 65b19d7aabb9e8..d0febd473261ed 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -596,7 +596,7 @@ pmsensor==0.4 poolsense==0.0.8 # homeassistant.components.reddit -praw==7.1.2 +praw==7.1.3 # homeassistant.components.islamic_prayer_times prayer_times_calculator==0.0.3 From 28ef3f68f3d8e913598122fd096ed963c1fc0468 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Feb 2021 09:36:14 +0100 Subject: [PATCH 0258/1818] Add media_player device triggers (#45430) * Add media player device triggers * Update tests --- .../components/media_player/device_trigger.py | 90 +++++++++++ .../components/media_player/strings.json | 7 + .../arcam_fmj/test_device_trigger.py | 9 +- .../specific_devices/test_lg_tv.py | 5 +- tests/components/kodi/test_device_trigger.py | 8 +- .../media_player/test_device_trigger.py | 147 ++++++++++++++++++ 6 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/media_player/device_trigger.py create mode 100644 tests/components/media_player/test_device_trigger.py diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py new file mode 100644 index 00000000000000..6db5f16cf01a60 --- /dev/null +++ b/homeassistant/components/media_player/device_trigger.py @@ -0,0 +1,90 @@ +"""Provides device automations for Media player.""" +from typing import List + +import voluptuous as vol + +from homeassistant.components.automation import AutomationActionType +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.homeassistant.triggers import state as state_trigger +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, + STATE_IDLE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.typing import ConfigType + +from . import DOMAIN + +TRIGGER_TYPES = {"turned_on", "turned_off", "idle", "paused", "playing"} + +TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + } +) + + +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device triggers for Media player entities.""" + registry = await entity_registry.async_get_registry(hass) + triggers = [] + + # Get all the integration entities for this device + for entry in entity_registry.async_entries_for_device(registry, device_id): + if entry.domain != DOMAIN: + continue + + # Add triggers for each entity that belongs to this integration + triggers += [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: trigger, + } + for trigger in TRIGGER_TYPES + ] + + return triggers + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: dict, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + config = TRIGGER_SCHEMA(config) + + if config[CONF_TYPE] == "turned_on": + to_state = STATE_ON + elif config[CONF_TYPE] == "turned_off": + to_state = STATE_OFF + elif config[CONF_TYPE] == "idle": + to_state = STATE_IDLE + elif config[CONF_TYPE] == "paused": + to_state = STATE_PAUSED + else: + to_state = STATE_PLAYING + + state_config = { + CONF_PLATFORM: "state", + CONF_ENTITY_ID: config[CONF_ENTITY_ID], + state_trigger.CONF_TO: to_state, + } + state_config = state_trigger.TRIGGER_SCHEMA(state_config) + return await state_trigger.async_attach_trigger( + hass, state_config, action, automation_info, platform_type="device" + ) diff --git a/homeassistant/components/media_player/strings.json b/homeassistant/components/media_player/strings.json index 14f1eea131c7f7..64841413f12c45 100644 --- a/homeassistant/components/media_player/strings.json +++ b/homeassistant/components/media_player/strings.json @@ -7,6 +7,13 @@ "is_idle": "{entity_name} is idle", "is_paused": "{entity_name} is paused", "is_playing": "{entity_name} is playing" + }, + "trigger_type": { + "turned_on": "{entity_name} turned on", + "turned_off": "{entity_name} turned off", + "idle": "{entity_name} becomes idle", + "paused": "{entity_name} is paused", + "playing": "{entity_name} starts playing" } }, "state": { diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py index 0f2cfaf28932ce..0cae565f7bbb6d 100644 --- a/tests/components/arcam_fmj/test_device_trigger.py +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -7,7 +7,6 @@ from tests.common import ( MockConfigEntry, - assert_lists_same, async_get_device_automations, async_mock_service, mock_device_registry, @@ -55,7 +54,13 @@ async def test_get_triggers(hass, device_reg, entity_reg): }, ] triggers = await async_get_device_automations(hass, "trigger", device_entry.id) - assert_lists_same(triggers, expected_triggers) + + # Test triggers are either arcam_fmj specific or media_player entity triggers + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + for expected_trigger in expected_triggers: + assert expected_trigger in triggers + for trigger in triggers: + assert trigger in expected_triggers or trigger["domain"] == "media_player" async def test_if_fires_on_turn_on_request(hass, calls, player_setup, state): diff --git a/tests/components/homekit_controller/specific_devices/test_lg_tv.py b/tests/components/homekit_controller/specific_devices/test_lg_tv.py index cd3f57137bfd3b..ebc50fda8bc65b 100644 --- a/tests/components/homekit_controller/specific_devices/test_lg_tv.py +++ b/tests/components/homekit_controller/specific_devices/test_lg_tv.py @@ -63,6 +63,7 @@ async def test_lg_tv(hass): assert device.sw_version == "04.71.04" assert device.via_device_id is None - # A TV doesn't have any triggers + # A TV has media player device triggers triggers = await async_get_device_automations(hass, "trigger", device.id) - assert triggers == [] + for trigger in triggers: + assert trigger["domain"] == "media_player" diff --git a/tests/components/kodi/test_device_trigger.py b/tests/components/kodi/test_device_trigger.py index 8cf6c635393d53..0dd75b9c3577f6 100644 --- a/tests/components/kodi/test_device_trigger.py +++ b/tests/components/kodi/test_device_trigger.py @@ -10,7 +10,6 @@ from tests.common import ( MockConfigEntry, - assert_lists_same, async_get_device_automations, async_mock_service, mock_device_registry, @@ -69,8 +68,13 @@ async def test_get_triggers(hass, device_reg, entity_reg): "entity_id": f"{MP_DOMAIN}.kodi_5678", }, ] + + # Test triggers are either kodi specific triggers or media_player entity triggers triggers = await async_get_device_automations(hass, "trigger", device_entry.id) - assert_lists_same(triggers, expected_triggers) + for expected_trigger in expected_triggers: + assert expected_trigger in triggers + for trigger in triggers: + assert trigger in expected_triggers or trigger["domain"] == "media_player" async def test_if_fires_on_state_change(hass, calls, kodi_media_player): diff --git a/tests/components/media_player/test_device_trigger.py b/tests/components/media_player/test_device_trigger.py new file mode 100644 index 00000000000000..93d9127f8b8648 --- /dev/null +++ b/tests/components/media_player/test_device_trigger.py @@ -0,0 +1,147 @@ +"""The tests for Media player device triggers.""" +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.media_player import DOMAIN +from homeassistant.const import ( + STATE_IDLE, + STATE_OFF, + STATE_ON, + STATE_PAUSED, + STATE_PLAYING, +) +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component + +from tests.common import ( + MockConfigEntry, + assert_lists_same, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass): + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_triggers(hass, device_reg, entity_reg): + """Test we get the expected triggers from a media player.""" + 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={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) + + trigger_types = {"turned_on", "turned_off", "idle", "paused", "playing"} + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": trigger, + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + } + for trigger in trigger_types + ] + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + assert_lists_same(triggers, expected_triggers) + + +async def test_if_fires_on_state_change(hass, calls): + """Test triggers firing.""" + hass.states.async_set("media_player.entity", STATE_OFF) + + data_template = ( + "{label} - {{{{ trigger.platform}}}} - " + "{{{{ trigger.entity_id}}}} - {{{{ trigger.from_state.state}}}} - " + "{{{{ trigger.to_state.state}}}} - {{{{ trigger.for }}}}" + ) + trigger_types = {"turned_on", "turned_off", "idle", "paused", "playing"} + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": "media_player.entity", + "type": trigger, + }, + "action": { + "service": "test.automation", + "data_template": {"some": data_template.format(label=trigger)}, + }, + } + for trigger in trigger_types + ] + }, + ) + + # Fake that the entity is turning on. + hass.states.async_set("media_player.entity", STATE_ON) + await hass.async_block_till_done() + assert len(calls) == 1 + assert ( + calls[0].data["some"] + == "turned_on - device - media_player.entity - off - on - None" + ) + + # Fake that the entity is turning off. + hass.states.async_set("media_player.entity", STATE_OFF) + await hass.async_block_till_done() + assert len(calls) == 2 + assert ( + calls[1].data["some"] + == "turned_off - device - media_player.entity - on - off - None" + ) + + # Fake that the entity becomes idle. + hass.states.async_set("media_player.entity", STATE_IDLE) + await hass.async_block_till_done() + assert len(calls) == 3 + assert ( + calls[2].data["some"] + == "idle - device - media_player.entity - off - idle - None" + ) + + # Fake that the entity starts playing. + hass.states.async_set("media_player.entity", STATE_PLAYING) + await hass.async_block_till_done() + assert len(calls) == 4 + assert ( + calls[3].data["some"] + == "playing - device - media_player.entity - idle - playing - None" + ) + + # Fake that the entity is paused. + hass.states.async_set("media_player.entity", STATE_PAUSED) + await hass.async_block_till_done() + assert len(calls) == 5 + assert ( + calls[4].data["some"] + == "paused - device - media_player.entity - playing - paused - None" + ) From 840891e4f4f92a9660d0f7f708d052c23f627ce5 Mon Sep 17 00:00:00 2001 From: Adam Outler Date: Mon, 8 Feb 2021 03:37:23 -0500 Subject: [PATCH 0259/1818] Increase skybell scan time to reduce timeouts (#46169) --- homeassistant/components/skybell/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index 512731ab355ebf..8949a58fa018f8 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -14,7 +14,7 @@ from . import DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice -SCAN_INTERVAL = timedelta(seconds=5) +SCAN_INTERVAL = timedelta(seconds=10) # Sensor types: Name, device_class, event SENSOR_TYPES = { From 352bba1f15b62eb6d2088699911d6f6363279422 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 03:45:05 -0500 Subject: [PATCH 0260/1818] Use core constants for efergy (#46090) --- homeassistant/components/efergy/sensor.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index 8c16317beda402..02bc8fa0ccbd0c 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -5,7 +5,13 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_CURRENCY, ENERGY_KILO_WATT_HOUR, POWER_WATT +from homeassistant.const import ( + CONF_CURRENCY, + CONF_MONITORED_VARIABLES, + CONF_TYPE, + ENERGY_KILO_WATT_HOUR, + POWER_WATT, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -14,8 +20,6 @@ CONF_APPTOKEN = "app_token" CONF_UTC_OFFSET = "utc_offset" -CONF_MONITORED_VARIABLES = "monitored_variables" -CONF_SENSOR_TYPE = "type" CONF_PERIOD = "period" @@ -40,7 +44,7 @@ SENSORS_SCHEMA = vol.Schema( { - vol.Required(CONF_SENSOR_TYPE): TYPES_SCHEMA, + vol.Required(CONF_TYPE): TYPES_SCHEMA, vol.Optional(CONF_CURRENCY, default=""): cv.string, vol.Optional(CONF_PERIOD, default=DEFAULT_PERIOD): cv.string, } @@ -62,14 +66,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] for variable in config[CONF_MONITORED_VARIABLES]: - if variable[CONF_SENSOR_TYPE] == CONF_CURRENT_VALUES: + if variable[CONF_TYPE] == CONF_CURRENT_VALUES: url_string = f"{_RESOURCE}getCurrentValuesSummary?token={app_token}" response = requests.get(url_string, timeout=10) for sensor in response.json(): sid = sensor["sid"] dev.append( EfergySensor( - variable[CONF_SENSOR_TYPE], + variable[CONF_TYPE], app_token, utc_offset, variable[CONF_PERIOD], @@ -79,7 +83,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) dev.append( EfergySensor( - variable[CONF_SENSOR_TYPE], + variable[CONF_TYPE], app_token, utc_offset, variable[CONF_PERIOD], From 75519d2d6cb3ab530adc6994d3f2c4502d719203 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 09:59:07 +0100 Subject: [PATCH 0261/1818] Bump actions/cache from v2 to v2.1.4 (#46197) Bumps [actions/cache](https://github.com/actions/cache) from v2 to v2.1.4. - [Release notes](https://github.com/actions/cache/releases) - [Commits](https://github.com/actions/cache/compare/v2...26968a09c0ea4f3e233fdddbafd1166051a095f6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6356803cbef7b6..28410123914505 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,7 +30,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -52,7 +52,7 @@ jobs: pip install -r requirements.txt -r requirements_test.txt - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -79,7 +79,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -95,7 +95,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -124,7 +124,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -140,7 +140,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -169,7 +169,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -185,7 +185,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -236,7 +236,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -252,7 +252,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -284,7 +284,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -300,7 +300,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -332,7 +332,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -348,7 +348,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -377,7 +377,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -393,7 +393,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -425,7 +425,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -441,7 +441,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -481,7 +481,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -497,7 +497,7 @@ jobs: exit 1 - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: ${{ env.PRE_COMMIT_HOME }} key: | @@ -528,7 +528,7 @@ jobs: uses: actions/checkout@v2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -560,7 +560,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -591,7 +591,7 @@ jobs: uses: actions/checkout@v2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -630,7 +630,7 @@ jobs: uses: actions/checkout@v2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -664,7 +664,7 @@ jobs: uses: actions/checkout@v2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -700,7 +700,7 @@ jobs: uses: actions/checkout@v2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- @@ -760,7 +760,7 @@ jobs: uses: actions/checkout@v2 - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache@v2 + uses: actions/cache@v2.1.4 with: path: venv key: >- From c7a957192034129337ea0b58a927a2db54398408 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 10:06:38 +0100 Subject: [PATCH 0262/1818] Bump actions/stale from v3.0.15 to v3.0.16 (#46196) --- .github/workflows/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6daeccc4aca6c3..fd8ca4eb477771 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: # - No PRs marked as no-stale # - No issues marked as no-stale or help-wanted - name: 90 days stale issues & PRs policy - uses: actions/stale@v3.0.15 + uses: actions/stale@v3.0.16 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 90 @@ -53,7 +53,7 @@ jobs: # - No PRs marked as no-stale or new-integrations # - No issues (-1) - name: 30 days stale PRs policy - uses: actions/stale@v3.0.15 + uses: actions/stale@v3.0.16 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -78,7 +78,7 @@ jobs: # - No Issues marked as no-stale or help-wanted # - No PRs (-1) - name: Needs more information stale issues policy - uses: actions/stale@v3.0.15 + uses: actions/stale@v3.0.16 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "needs-more-information" From aa005af2665078f4ff26153734abb1c07cf1ac0f Mon Sep 17 00:00:00 2001 From: Aaron Godfrey Date: Mon, 8 Feb 2021 01:39:33 -0800 Subject: [PATCH 0263/1818] Fix dyson service name in services.yaml (#46176) --- homeassistant/components/dyson/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dyson/services.yaml b/homeassistant/components/dyson/services.yaml index 73f7bc7587449a..f96aa9315c1c2d 100644 --- a/homeassistant/components/dyson/services.yaml +++ b/homeassistant/components/dyson/services.yaml @@ -33,7 +33,7 @@ set_angle: description: The angle at which the oscillation should end example: 255 -flow_direction_front: +set_flow_direction_front: description: Set the fan flow direction. fields: entity_id: From 9e07910ab06b909c8410e9c785da3c76bff681fc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Feb 2021 10:45:46 +0100 Subject: [PATCH 0264/1818] Mark entities as unavailable when they are removed but are still registered (#45528) * Mark entities as unavailable when they are removed but are still registered * Add sync_entity_lifecycle to collection helper * Remove debug print * Lint * Fix tests * Fix tests * Update zha * Update zone * Fix tests * Update hyperion * Update rfxtrx * Fix tests * Pass force_remove=True from integrations Co-authored-by: Erik --- homeassistant/components/acmeda/base.py | 2 +- homeassistant/components/counter/__init__.py | 11 ++-- homeassistant/components/esphome/__init__.py | 3 +- .../components/gdacs/geo_location.py | 2 +- .../geo_json_events/geo_location.py | 2 +- .../geonetnz_quakes/geo_location.py | 2 +- .../homematicip_cloud/generic_entity.py | 2 +- homeassistant/components/hue/helpers.py | 2 +- homeassistant/components/hyperion/light.py | 3 +- homeassistant/components/hyperion/switch.py | 3 +- .../components/ign_sismologia/geo_location.py | 2 +- .../components/input_boolean/__init__.py | 28 +++++----- .../components/input_datetime/__init__.py | 11 ++-- .../components/input_number/__init__.py | 11 ++-- .../components/input_select/__init__.py | 11 ++-- .../components/input_text/__init__.py | 11 ++-- .../components/insteon/insteon_entity.py | 7 ++- homeassistant/components/mqtt/mixins.py | 2 +- .../geo_location.py | 2 +- .../components/owntracks/messages.py | 3 +- homeassistant/components/ozw/entity.py | 2 +- homeassistant/components/person/__init__.py | 23 ++++---- .../components/qld_bushfire/geo_location.py | 2 +- homeassistant/components/rfxtrx/__init__.py | 4 +- .../components/seventeentrack/sensor.py | 2 +- homeassistant/components/timer/__init__.py | 11 ++-- homeassistant/components/tuya/__init__.py | 2 +- .../components/unifi/unifi_entity_base.py | 2 +- .../usgs_earthquakes_feed/geo_location.py | 2 +- homeassistant/components/wled/light.py | 2 +- homeassistant/components/zha/entity.py | 5 +- homeassistant/components/zone/__init__.py | 40 ++++++-------- homeassistant/components/zwave/node_entity.py | 4 +- homeassistant/helpers/collection.py | 36 ++++--------- homeassistant/helpers/entity.py | 28 ++++++++-- homeassistant/helpers/entity_platform.py | 2 +- homeassistant/helpers/entity_registry.py | 54 ++++++++++--------- tests/components/cert_expiry/test_init.py | 12 ++++- tests/components/deconz/test_binary_sensor.py | 6 ++- tests/components/deconz/test_climate.py | 14 ++++- tests/components/deconz/test_cover.py | 14 ++++- tests/components/deconz/test_deconz_event.py | 9 ++++ tests/components/deconz/test_fan.py | 9 +++- tests/components/deconz/test_light.py | 8 +++ tests/components/deconz/test_lock.py | 14 ++++- tests/components/deconz/test_sensor.py | 8 +++ tests/components/deconz/test_switch.py | 16 +++++- tests/components/dynalite/test_light.py | 20 +++++-- tests/components/eafm/test_sensor.py | 9 ++-- .../forked_daapd/test_media_player.py | 6 +-- tests/components/fritzbox/test_init.py | 20 +++++-- tests/components/heos/test_media_player.py | 4 +- .../homekit_controller/test_light.py | 15 ++++-- tests/components/huisbaasje/test_init.py | 10 +++- tests/components/input_boolean/test_init.py | 2 +- tests/components/met/test_weather.py | 2 + tests/components/nest/camera_sdm_test.py | 1 + tests/components/nws/test_init.py | 10 +++- tests/components/ozw/test_init.py | 12 ++++- tests/components/panasonic_viera/test_init.py | 8 +-- tests/components/plex/test_media_players.py | 2 +- .../smartthings/test_binary_sensor.py | 7 ++- tests/components/smartthings/test_cover.py | 4 +- tests/components/smartthings/test_fan.py | 8 ++- tests/components/smartthings/test_light.py | 8 ++- tests/components/smartthings/test_lock.py | 3 +- tests/components/smartthings/test_scene.py | 4 +- tests/components/smartthings/test_sensor.py | 3 +- tests/components/smartthings/test_switch.py | 3 +- tests/components/vizio/test_init.py | 11 +++- tests/components/yeelight/test_init.py | 8 ++- tests/helpers/test_collection.py | 2 +- tests/helpers/test_entity.py | 28 +++++++++- 73 files changed, 439 insertions(+), 222 deletions(-) diff --git a/homeassistant/components/acmeda/base.py b/homeassistant/components/acmeda/base.py index b325e2c944afaf..15f9716db47ef8 100644 --- a/homeassistant/components/acmeda/base.py +++ b/homeassistant/components/acmeda/base.py @@ -32,7 +32,7 @@ async def async_remove_and_unregister(self): device.id, remove_config_entry_id=self.registry_entry.config_entry_id ) - await self.async_remove() + await self.async_remove(force_remove=True) async def async_added_to_hass(self): """Entity has been added to hass.""" diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index ad5e400011687e..d23c90bcb937bd 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -108,8 +108,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, Counter.from_yaml + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, Counter.from_yaml ) storage_collection = CounterStorageCollection( @@ -117,8 +117,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, Counter + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, Counter ) await yaml_collection.async_load( @@ -130,9 +130,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment") component.async_register_entity_service(SERVICE_DECREMENT, {}, "async_decrement") component.async_register_entity_service(SERVICE_RESET, {}, "async_reset") diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index c0c3d02ec56825..0b3a7522845133 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -1,5 +1,6 @@ """Support for esphome devices.""" import asyncio +import functools import logging import math from typing import Any, Callable, Dict, List, Optional @@ -520,7 +521,7 @@ async def async_added_to_hass(self) -> None: f"esphome_{self._entry_id}_remove_" f"{self._component_key}_{self._key}" ), - self.async_remove, + functools.partial(self.async_remove, force_remove=True), ) ) diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index c45d6e56425adf..890c9f8e050c7c 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -116,7 +116,7 @@ async def async_will_remove_from_hass(self) -> None: @callback def _delete_callback(self): """Remove this entity.""" - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index bb2d86539e98a2..40386648138ca9 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -144,7 +144,7 @@ def _delete_callback(self): """Remove this entity.""" self._remove_signal_delete() self._remove_signal_update() - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index ed0b9f9f714453..718b4c06b9c3f3 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -102,7 +102,7 @@ async def async_will_remove_from_hass(self) -> None: @callback def _delete_callback(self): """Remove this entity.""" - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index a8df0107eebe40..65e5ade7d1df27 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -172,7 +172,7 @@ def _async_device_removed(self, *args, **kwargs) -> None: """Handle hmip device removal.""" # Set marker showing that the HmIP device hase been removed. self.hmip_device_removed = True - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @property def name(self) -> str: diff --git a/homeassistant/components/hue/helpers.py b/homeassistant/components/hue/helpers.py index 1760c59a69dc8f..739e27d3360d09 100644 --- a/homeassistant/components/hue/helpers.py +++ b/homeassistant/components/hue/helpers.py @@ -17,7 +17,7 @@ async def remove_devices(bridge, api_ids, current): # Device is removed from Hue, so we remove it from Home Assistant entity = current[item_id] removed_items.append(item_id) - await entity.async_remove() + await entity.async_remove(force_remove=True) ent_registry = await get_ent_reg(bridge.hass) if entity.entity_id in ent_registry.entities: ent_registry.async_remove(entity.entity_id) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index ce672194b9adf6..7bb8a75dfc7451 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -1,6 +1,7 @@ """Support for Hyperion-NG remotes.""" from __future__ import annotations +import functools import logging from types import MappingProxyType from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple @@ -401,7 +402,7 @@ async def async_added_to_hass(self) -> None: async_dispatcher_connect( self.hass, SIGNAL_ENTITY_REMOVE.format(self._unique_id), - self.async_remove, + functools.partial(self.async_remove, force_remove=True), ) ) diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index 372e9876c35418..9d90e1e12ef4e9 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -1,5 +1,6 @@ """Switch platform for Hyperion.""" +import functools from typing import Any, Callable, Dict, Optional from hyperion import client @@ -199,7 +200,7 @@ async def async_added_to_hass(self) -> None: async_dispatcher_connect( self.hass, SIGNAL_ENTITY_REMOVE.format(self._unique_id), - self.async_remove, + functools.partial(self.async_remove, force_remove=True), ) ) diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index cc06110c111130..0db580701d038f 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -165,7 +165,7 @@ def _delete_callback(self): """Remove this entity.""" self._remove_signal_delete() self._remove_signal_update() - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index f123d6d32979fc..1b996722c01867 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -89,8 +89,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, lambda conf: InputBoolean(conf, from_yaml=True) + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, InputBoolean.from_yaml ) storage_collection = InputBooleanStorageCollection( @@ -98,8 +98,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, InputBoolean + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, InputBoolean ) await yaml_collection.async_load( @@ -111,9 +111,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - async def reload_service_handler(service_call: ServiceCallType) -> None: """Remove all input booleans and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) @@ -146,14 +143,19 @@ async def reload_service_handler(service_call: ServiceCallType) -> None: class InputBoolean(ToggleEntity, RestoreEntity): """Representation of a boolean input.""" - def __init__(self, config: typing.Optional[dict], from_yaml: bool = False): + def __init__(self, config: typing.Optional[dict]): """Initialize a boolean input.""" self._config = config - self._editable = True + self.editable = True self._state = config.get(CONF_INITIAL) - if from_yaml: - self._editable = False - self.entity_id = f"{DOMAIN}.{self.unique_id}" + + @classmethod + def from_yaml(cls, config: typing.Dict) -> "InputBoolean": + """Return entity instance initialized from yaml storage.""" + input_bool = cls(config) + input_bool.entity_id = f"{DOMAIN}.{config[CONF_ID]}" + input_bool.editable = False + return input_bool @property def should_poll(self): @@ -168,7 +170,7 @@ def name(self): @property def state_attributes(self): """Return the state attributes of the entity.""" - return {ATTR_EDITABLE: self._editable} + return {ATTR_EDITABLE: self.editable} @property def icon(self): diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 0eab810245ddea..9589fe9a7ea08a 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -108,8 +108,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, InputDatetime.from_yaml + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, InputDatetime.from_yaml ) storage_collection = DateTimeStorageCollection( @@ -117,8 +117,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, InputDatetime + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, InputDatetime ) await yaml_collection.async_load( @@ -130,9 +130,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - async def reload_service_handler(service_call: ServiceCallType) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 1f979cad7a9074..5cad0f49c887c9 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -119,8 +119,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, InputNumber.from_yaml + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, InputNumber.from_yaml ) storage_collection = NumberStorageCollection( @@ -128,8 +128,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, InputNumber + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, InputNumber ) await yaml_collection.async_load( @@ -141,9 +141,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - async def reload_service_handler(service_call: ServiceCallType) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 6272992f24363f..a390d8e190150f 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -94,8 +94,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, InputSelect.from_yaml + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, InputSelect.from_yaml ) storage_collection = InputSelectStorageCollection( @@ -103,8 +103,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, InputSelect + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, InputSelect ) await yaml_collection.async_load( @@ -116,9 +116,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - async def reload_service_handler(service_call: ServiceCallType) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index c512bc221db1b9..76eb51eedd5575 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -119,8 +119,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, InputText.from_yaml + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, InputText.from_yaml ) storage_collection = InputTextStorageCollection( @@ -128,8 +128,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, InputText + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, InputText ) await yaml_collection.async_load( @@ -141,9 +141,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - async def reload_service_handler(service_call: ServiceCallType) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index e2b9dd39f34fa8..2234eb4750c36e 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -1,4 +1,5 @@ """Insteon base entity.""" +import functools import logging from pyinsteon import devices @@ -122,7 +123,11 @@ async def async_added_to_hass(self): ) remove_signal = f"{self._insteon_device.address.id}_{SIGNAL_REMOVE_ENTITY}" self.async_on_remove( - async_dispatcher_connect(self.hass, remove_signal, self.async_remove) + async_dispatcher_connect( + self.hass, + remove_signal, + functools.partial(self.async_remove, force_remove=True), + ) ) async def async_will_remove_from_hass(self): diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 1ab2054b355f41..8d9c9533ed3fb3 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -387,7 +387,7 @@ async def _async_remove_state_and_registry_entry(self) -> None: entity_registry.async_remove(self.entity_id) await cleanup_device_registry(self.hass, entity_entry.device_id) else: - await self.async_remove() + await self.async_remove(force_remove=True) async def discovery_callback(payload): """Handle discovery update.""" diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py index c8eda3690efbc4..12ae9d8990ad46 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py +++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py @@ -210,7 +210,7 @@ async def async_will_remove_from_hass(self) -> None: @callback def _delete_callback(self): """Remove this entity.""" - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index 3a4aac6bfd1bde..bd01284329b9ed 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -304,7 +304,7 @@ async def async_handle_waypoint(hass, name_base, waypoint): if hass.states.get(entity_id) is not None: return - zone = zone_comp.Zone( + zone = zone_comp.Zone.from_yaml( { zone_comp.CONF_NAME: pretty_name, zone_comp.CONF_LATITUDE: lat, @@ -313,7 +313,6 @@ async def async_handle_waypoint(hass, name_base, waypoint): zone_comp.CONF_ICON: zone_comp.ICON_IMPORT, zone_comp.CONF_PASSIVE: False, }, - False, ) zone.hass = hass zone.entity_id = entity_id diff --git a/homeassistant/components/ozw/entity.py b/homeassistant/components/ozw/entity.py index 9c494a514e000a..c1cb9617a5c40b 100644 --- a/homeassistant/components/ozw/entity.py +++ b/homeassistant/components/ozw/entity.py @@ -268,7 +268,7 @@ async def _delete_callback(self, values_id): if not self.values: return # race condition: delete already requested if values_id == self.values.values_id: - await self.async_remove() + await self.async_remove(force_remove=True) def create_device_name(node: OZWNode): diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index d0c0e9eccc81dd..d3e17d904eae86 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -306,14 +306,12 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): yaml_collection, ) - collection.attach_entity_component_collection( - entity_component, yaml_collection, lambda conf: Person(conf, False) + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, entity_component, yaml_collection, Person ) - collection.attach_entity_component_collection( - entity_component, storage_collection, lambda conf: Person(conf, True) + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, entity_component, storage_collection, Person.from_yaml ) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) await yaml_collection.async_load( await filter_yaml_data(hass, config.get(DOMAIN, [])) @@ -358,10 +356,10 @@ async def async_reload_yaml(call: ServiceCall): class Person(RestoreEntity): """Represent a tracked person.""" - def __init__(self, config, editable): + def __init__(self, config): """Set up person.""" self._config = config - self._editable = editable + self.editable = True self._latitude = None self._longitude = None self._gps_accuracy = None @@ -369,6 +367,13 @@ def __init__(self, config, editable): self._state = None self._unsub_track_device = None + @classmethod + def from_yaml(cls, config): + """Return entity instance initialized from yaml storage.""" + person = cls(config) + person.editable = False + return person + @property def name(self): """Return the name of the entity.""" @@ -395,7 +400,7 @@ def state(self): @property def state_attributes(self): """Return the state attributes of the person.""" - data = {ATTR_EDITABLE: self._editable, ATTR_ID: self.unique_id} + data = {ATTR_EDITABLE: self.editable, ATTR_ID: self.unique_id} if self._latitude is not None: data[ATTR_LATITUDE] = self._latitude if self._longitude is not None: diff --git a/homeassistant/components/qld_bushfire/geo_location.py b/homeassistant/components/qld_bushfire/geo_location.py index 8efb1a32705eaa..f608f6e12aedbf 100644 --- a/homeassistant/components/qld_bushfire/geo_location.py +++ b/homeassistant/components/qld_bushfire/geo_location.py @@ -167,7 +167,7 @@ def _delete_callback(self): """Remove this entity.""" self._remove_signal_delete() self._remove_signal_update() - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 067ffeb5313567..5952cb62a71804 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -3,6 +3,7 @@ import binascii from collections import OrderedDict import copy +import functools import logging import RFXtrx as rfxtrxmod @@ -488,7 +489,8 @@ async def async_added_to_hass(self): self.async_on_remove( self.hass.helpers.dispatcher.async_dispatcher_connect( - f"{DOMAIN}_{CONF_REMOVE_DEVICE}_{self._device_id}", self.async_remove + f"{DOMAIN}_{CONF_REMOVE_DEVICE}_{self._device_id}", + functools.partial(self.async_remove, force_remove=True), ) ) diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 94efe9b98c7ddf..fa94ca4e3848ed 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -244,7 +244,7 @@ async def async_update(self): async def _remove(self, *_): """Remove entity itself.""" - await self.async_remove() + await self.async_remove(force_remove=True) reg = await self.hass.helpers.entity_registry.async_get_registry() entity_id = reg.async_get_entity_id( diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 64d651b4cd8e35..b123bbadf7dd6f 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -107,8 +107,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: yaml_collection = collection.YamlCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, Timer.from_yaml + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, Timer.from_yaml ) storage_collection = TimerStorageCollection( @@ -116,7 +116,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection(component, storage_collection, Timer) + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, Timer + ) await yaml_collection.async_load( [{CONF_ID: id_, **cfg} for id_, cfg in config.get(DOMAIN, {}).items()] @@ -127,9 +129,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, yaml_collection) - collection.attach_entity_registry_cleaner(hass, DOMAIN, DOMAIN, storage_collection) - async def reload_service_handler(service_call: ServiceCallType) -> None: """Reload yaml entities.""" conf = await component.async_prepare_reload(skip_reset=True) diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 5876331ea97f26..7f6ba6b26fd210 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -392,7 +392,7 @@ async def _delete_callback(self, dev_id): entity_registry.async_remove(self.entity_id) await cleanup_device_registry(self.hass, entity_entry.device_id) else: - await self.async_remove() + await self.async_remove(force_remove=True) @callback def _update_callback(self): diff --git a/homeassistant/components/unifi/unifi_entity_base.py b/homeassistant/components/unifi/unifi_entity_base.py index 904348f6324b12..03c63ce4e84a8a 100644 --- a/homeassistant/components/unifi/unifi_entity_base.py +++ b/homeassistant/components/unifi/unifi_entity_base.py @@ -91,7 +91,7 @@ async def remove_item(self, keys: set) -> None: entity_registry = await self.hass.helpers.entity_registry.async_get_registry() entity_entry = entity_registry.async_get(self.entity_id) if not entity_entry: - await self.async_remove() + await self.async_remove(force_remove=True) return device_registry = await self.hass.helpers.device_registry.async_get_registry() diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 40a544a2e21a79..2b149fcac26630 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -210,7 +210,7 @@ def _delete_callback(self): """Remove this entity.""" self._remove_signal_delete() self._remove_signal_update() - self.hass.async_create_task(self.async_remove()) + self.hass.async_create_task(self.async_remove(force_remove=True)) @callback def _update_callback(self): diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index 527d985a47b2e3..f89cf06a44c592 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -442,7 +442,7 @@ async def async_remove_entity( ) -> None: """Remove WLED segment light from Home Assistant.""" entity = current[index] - await entity.async_remove() + await entity.async_remove(force_remove=True) registry = await async_get_entity_registry(coordinator.hass) if entity.entity_id in registry.entities: registry.async_remove(entity.entity_id) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 96f005ba288242..db30e9e178ce2b 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -1,6 +1,7 @@ """Entity for Zigbee Home Automation.""" import asyncio +import functools import logging from typing import Any, Awaitable, Dict, List, Optional @@ -165,7 +166,7 @@ async def async_added_to_hass(self) -> None: self.async_accept_signal( None, f"{SIGNAL_REMOVE}_{self.zha_device.ieee}", - self.async_remove, + functools.partial(self.async_remove, force_remove=True), signal_override=True, ) @@ -239,7 +240,7 @@ async def _handle_group_membership_changed(self): return self._handled_group_membership = True - await self.async_remove() + await self.async_remove(force_remove=True) async def async_added_to_hass(self) -> None: """Register callbacks.""" diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 01a8b9aa0f427f..1eef9636e36318 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -25,7 +25,6 @@ config_validation as cv, entity, entity_component, - entity_registry, service, storage, ) @@ -183,8 +182,8 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool: yaml_collection = collection.IDLessCollection( logging.getLogger(f"{__name__}.yaml_collection"), id_manager ) - collection.attach_entity_component_collection( - component, yaml_collection, lambda conf: Zone(conf, False) + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, yaml_collection, Zone.from_yaml ) storage_collection = ZoneStorageCollection( @@ -192,8 +191,8 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool: logging.getLogger(f"{__name__}.storage_collection"), id_manager, ) - collection.attach_entity_component_collection( - component, storage_collection, lambda conf: Zone(conf, True) + collection.sync_entity_lifecycle( + hass, DOMAIN, DOMAIN, component, storage_collection, Zone ) if config[DOMAIN]: @@ -205,18 +204,6 @@ async def async_setup(hass: HomeAssistant, config: Dict) -> bool: storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) - async def _collection_changed(change_type: str, item_id: str, config: Dict) -> None: - """Handle a collection change: clean up entity registry on removals.""" - if change_type != collection.CHANGE_REMOVED: - return - - ent_reg = await entity_registry.async_get_registry(hass) - ent_reg.async_remove( - cast(str, ent_reg.async_get_entity_id(DOMAIN, DOMAIN, item_id)) - ) - - storage_collection.async_add_listener(_collection_changed) - async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all zones and load new ones from config.""" conf = await component.async_prepare_reload(skip_reset=True) @@ -235,10 +222,7 @@ async def reload_service_handler(service_call: ServiceCall) -> None: if component.get_entity("zone.home"): return True - home_zone = Zone( - _home_conf(hass), - True, - ) + home_zone = Zone(_home_conf(hass)) home_zone.entity_id = ENTITY_ID_HOME await component.async_add_entities([home_zone]) @@ -293,13 +277,21 @@ async def async_unload_entry( class Zone(entity.Entity): """Representation of a Zone.""" - def __init__(self, config: Dict, editable: bool): + def __init__(self, config: Dict): """Initialize the zone.""" self._config = config - self._editable = editable + self.editable = True self._attrs: Optional[Dict] = None self._generate_attrs() + @classmethod + def from_yaml(cls, config: Dict) -> "Zone": + """Return entity instance initialized from yaml storage.""" + zone = cls(config) + zone.editable = False + zone._generate_attrs() # pylint:disable=protected-access + return zone + @property def state(self) -> str: """Return the state property really does nothing for a zone.""" @@ -346,5 +338,5 @@ def _generate_attrs(self) -> None: ATTR_LONGITUDE: self._config[CONF_LONGITUDE], ATTR_RADIUS: self._config[CONF_RADIUS], ATTR_PASSIVE: self._config[CONF_PASSIVE], - ATTR_EDITABLE: self._editable, + ATTR_EDITABLE: self.editable, } diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 56dea1639a3068..faaea30e0ee4d9 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -95,7 +95,7 @@ def try_remove_and_add(self): """Remove this entity and add it back.""" async def _async_remove_and_add(): - await self.async_remove() + await self.async_remove(force_remove=True) self.entity_id = None await self.platform.async_add_entities([self]) @@ -104,7 +104,7 @@ async def _async_remove_and_add(): async def node_removed(self): """Call when a node is removed from the Z-Wave network.""" - await self.async_remove() + await self.async_remove(force_remove=True) registry = await async_get_registry(self.hass) if self.entity_id not in registry.entities: diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 6733b1d3dbd66f..4af524bbbc9ef3 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -301,7 +301,10 @@ async def async_load(self, data: List[dict]) -> None: @callback -def attach_entity_component_collection( +def sync_entity_lifecycle( + hass: HomeAssistantType, + domain: str, + platform: str, entity_component: EntityComponent, collection: ObservableCollection, create_entity: Callable[[dict], Entity], @@ -318,8 +321,13 @@ async def _collection_changed(change_type: str, item_id: str, config: dict) -> N return if change_type == CHANGE_REMOVED: - entity = entities.pop(item_id) - await entity.async_remove() + ent_reg = await entity_registry.async_get_registry(hass) + ent_to_remove = ent_reg.async_get_entity_id(domain, platform, item_id) + if ent_to_remove is not None: + ent_reg.async_remove(ent_to_remove) + else: + await entities[item_id].async_remove(force_remove=True) + entities.pop(item_id) return # CHANGE_UPDATED @@ -328,28 +336,6 @@ async def _collection_changed(change_type: str, item_id: str, config: dict) -> N collection.async_add_listener(_collection_changed) -@callback -def attach_entity_registry_cleaner( - hass: HomeAssistantType, - domain: str, - platform: str, - collection: ObservableCollection, -) -> None: - """Attach a listener to clean up entity registry on collection changes.""" - - async def _collection_changed(change_type: str, item_id: str, config: Dict) -> None: - """Handle a collection change: clean up entity registry on removals.""" - if change_type != CHANGE_REMOVED: - return - - ent_reg = await entity_registry.async_get_registry(hass) - ent_to_remove = ent_reg.async_get_entity_id(domain, platform, item_id) - if ent_to_remove is not None: - ent_reg.async_remove(ent_to_remove) - - collection.async_add_listener(_collection_changed) - - class StorageCollectionWebsocket: """Class to expose storage collection management over websocket.""" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 03342a9f235392..04c07ef0f36f1b 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -530,8 +530,16 @@ async def add_to_platform_finish(self) -> None: await self.async_added_to_hass() self.async_write_ha_state() - async def async_remove(self) -> None: - """Remove entity from Home Assistant.""" + async def async_remove(self, *, force_remove: bool = False) -> None: + """Remove entity from Home Assistant. + + If the entity has a non disabled entry in the entity registry, + the entity's state will be set to unavailable, in the same way + as when the entity registry is loaded. + + If the entity doesn't have a non disabled entry in the entity registry, + or if force_remove=True, its state will be removed. + """ assert self.hass is not None if self.platform and not self._added: @@ -548,7 +556,16 @@ async def async_remove(self) -> None: await self.async_internal_will_remove_from_hass() await self.async_will_remove_from_hass() - self.hass.states.async_remove(self.entity_id, context=self._context) + # Check if entry still exists in entity registry (e.g. unloading config entry) + if ( + not force_remove + and self.registry_entry + and not self.registry_entry.disabled + ): + # Set the entity's state will to unavailable + ATTR_RESTORED: True + self.registry_entry.write_unavailable_state(self.hass) + else: + self.hass.states.async_remove(self.entity_id, context=self._context) async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass. @@ -606,6 +623,7 @@ async def _async_registry_updated(self, event: Event) -> None: data = event.data if data["action"] == "remove": await self.async_removed_from_registry() + self.registry_entry = None await self.async_remove() if data["action"] != "update": @@ -617,7 +635,7 @@ async def _async_registry_updated(self, event: Event) -> None: self.registry_entry = ent_reg.async_get(data["entity_id"]) assert self.registry_entry is not None - if self.registry_entry.disabled_by is not None: + if self.registry_entry.disabled: await self.async_remove() return @@ -626,7 +644,7 @@ async def _async_registry_updated(self, event: Event) -> None: self.async_write_ha_state() return - await self.async_remove() + await self.async_remove(force_remove=True) assert self.platform is not None self.entity_id = self.registry_entry.entity_id diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index bd687ab7ce8e2a..26fec28c047e7e 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -517,7 +517,7 @@ async def async_reset(self) -> None: if not self.entities: return - tasks = [self.async_remove_entity(entity_id) for entity_id in self.entities] + tasks = [entity.async_remove() for entity in self.entities.values()] await asyncio.gather(*tasks) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 0628c1e0eb5b3e..15218afc227fab 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -115,6 +115,33 @@ def disabled(self) -> bool: """Return if entry is disabled.""" return self.disabled_by is not None + @callback + def write_unavailable_state(self, hass: HomeAssistantType) -> None: + """Write the unavailable state to the state machine.""" + attrs: Dict[str, Any] = {ATTR_RESTORED: True} + + if self.capabilities is not None: + attrs.update(self.capabilities) + + if self.supported_features is not None: + attrs[ATTR_SUPPORTED_FEATURES] = self.supported_features + + if self.device_class is not None: + attrs[ATTR_DEVICE_CLASS] = self.device_class + + if self.unit_of_measurement is not None: + attrs[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement + + name = self.name or self.original_name + if name is not None: + attrs[ATTR_FRIENDLY_NAME] = name + + icon = self.icon or self.original_icon + if icon is not None: + attrs[ATTR_ICON] = icon + + hass.states.async_set(self.entity_id, STATE_UNAVAILABLE, attrs) + class EntityRegistry: """Class to hold a registry of entities.""" @@ -616,36 +643,13 @@ def cleanup_restored_states(event: Event) -> None: @callback def _write_unavailable_states(_: Event) -> None: """Make sure state machine contains entry for each registered entity.""" - states = hass.states - existing = set(states.async_entity_ids()) + existing = set(hass.states.async_entity_ids()) for entry in registry.entities.values(): if entry.entity_id in existing or entry.disabled: continue - attrs: Dict[str, Any] = {ATTR_RESTORED: True} - - if entry.capabilities is not None: - attrs.update(entry.capabilities) - - if entry.supported_features is not None: - attrs[ATTR_SUPPORTED_FEATURES] = entry.supported_features - - if entry.device_class is not None: - attrs[ATTR_DEVICE_CLASS] = entry.device_class - - if entry.unit_of_measurement is not None: - attrs[ATTR_UNIT_OF_MEASUREMENT] = entry.unit_of_measurement - - name = entry.name or entry.original_name - if name is not None: - attrs[ATTR_FRIENDLY_NAME] = name - - icon = entry.icon or entry.original_icon - if icon is not None: - attrs[ATTR_ICON] = icon - - states.async_set(entry.entity_id, STATE_UNAVAILABLE, attrs) + entry.write_unavailable_state(hass) hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _write_unavailable_states) diff --git a/tests/components/cert_expiry/test_init.py b/tests/components/cert_expiry/test_init.py index ea31ba50ea0000..1c62782107b7e9 100644 --- a/tests/components/cert_expiry/test_init.py +++ b/tests/components/cert_expiry/test_init.py @@ -5,7 +5,12 @@ from homeassistant.components.cert_expiry.const import DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED -from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_START +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_START, + STATE_UNAVAILABLE, +) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -94,4 +99,9 @@ async def test_unload_config_entry(mock_now, hass): assert entry.state == ENTRY_STATE_NOT_LOADED state = hass.states.get("sensor.cert_expiry_timestamp_example_com") + assert state.state == STATE_UNAVAILABLE + + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + state = hass.states.get("sensor.cert_expiry_timestamp_example_com") assert state is None diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 3611e30f66500b..f64a4c4c259395 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -16,7 +16,7 @@ ) from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.setup import async_setup_component @@ -111,6 +111,10 @@ async def test_binary_sensors(hass): 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 diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 4d68ba2a6a71a9..fcb6e16f07f8a6 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -39,7 +39,12 @@ DOMAIN as DECONZ_DOMAIN, ) from homeassistant.components.deconz.gateway import get_gateway_from_config_entry -from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_OFF +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + STATE_OFF, + STATE_UNAVAILABLE, +) from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -361,6 +366,13 @@ async def test_climate_device_without_cooling_support(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 2 + for state in states: + assert state.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 diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 5314a41b315be8..43364208f4f681 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -19,7 +19,12 @@ ) from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry -from homeassistant.const import ATTR_ENTITY_ID, STATE_CLOSED, STATE_OPEN +from homeassistant.const import ( + ATTR_ENTITY_ID, + STATE_CLOSED, + STATE_OPEN, + STATE_UNAVAILABLE, +) from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -251,6 +256,13 @@ async def test_cover(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 5 + for state in states: + assert state.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 diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 14faf1a938c5c8..232de5eacd2030 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -4,6 +4,7 @@ from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.const import STATE_UNAVAILABLE from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -121,5 +122,13 @@ async def test_deconz_events(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 3 + for state in states: + assert state.state == STATE_UNAVAILABLE + assert len(gateway.events) == 0 + + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 assert len(gateway.events) == 0 diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index b9c154a2791c07..7f225196744f2f 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -18,7 +18,7 @@ SPEED_MEDIUM, SPEED_OFF, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -207,4 +207,11 @@ async def test_fans(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 2 + for state in states: + assert state.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 diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 20fb50247eeb9d..bdb7fbb8aefec6 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -31,6 +31,7 @@ ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, ) from homeassistant.setup import async_setup_component @@ -296,6 +297,13 @@ async def test_lights_and_groups(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 6 + for state in states: + assert state.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 diff --git a/tests/components/deconz/test_lock.py b/tests/components/deconz/test_lock.py index 7e9b8233778e95..d53da74dfddcb4 100644 --- a/tests/components/deconz/test_lock.py +++ b/tests/components/deconz/test_lock.py @@ -10,7 +10,12 @@ SERVICE_LOCK, SERVICE_UNLOCK, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED +from homeassistant.const import ( + ATTR_ENTITY_ID, + STATE_LOCKED, + STATE_UNAVAILABLE, + STATE_UNLOCKED, +) from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -104,4 +109,11 @@ async def test_locks(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 1 + for state in states: + assert state.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 diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index def2a1412e50c8..426a88b8bb6303 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -12,6 +12,7 @@ DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, + STATE_UNAVAILABLE, ) from homeassistant.setup import async_setup_component @@ -165,6 +166,13 @@ async def test_sensors(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 5 + for state in states: + assert state.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 diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index e42e89d903eb57..22ce182cb62173 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -10,7 +10,7 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -139,6 +139,13 @@ async def test_power_plugs(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 4 + for state in states: + assert state.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 @@ -202,4 +209,11 @@ async def test_sirens(hass): await hass.config_entries.async_unload(config_entry.entry_id) + states = hass.states.async_all() + assert len(hass.states.async_all()) == 2 + for state in states: + assert state.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 diff --git a/tests/components/dynalite/test_light.py b/tests/components/dynalite/test_light.py index 7df10fb08e8e45..230e7584d70abc 100644 --- a/tests/components/dynalite/test_light.py +++ b/tests/components/dynalite/test_light.py @@ -4,7 +4,11 @@ import pytest from homeassistant.components.light import SUPPORT_BRIGHTNESS -from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_SUPPORTED_FEATURES +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ATTR_SUPPORTED_FEATURES, + STATE_UNAVAILABLE, +) from .common import ( ATTR_METHOD, @@ -40,11 +44,21 @@ async def test_light_setup(hass, mock_device): ) -async def test_remove_entity(hass, mock_device): - """Test when an entity is removed from HA.""" +async def test_unload_config_entry(hass, mock_device): + """Test when a config entry is unloaded from HA.""" await create_entity_from_device(hass, mock_device) assert hass.states.get("light.name") entry_id = await get_entry_id_from_hass(hass) assert await hass.config_entries.async_unload(entry_id) await hass.async_block_till_done() + assert hass.states.get("light.name").state == STATE_UNAVAILABLE + + +async def test_remove_config_entry(hass, mock_device): + """Test when a config entry is removed from HA.""" + await create_entity_from_device(hass, mock_device) + assert hass.states.get("light.name") + entry_id = await get_entry_id_from_hass(hass) + assert await hass.config_entries.async_remove(entry_id) + await hass.async_block_till_done() assert not hass.states.get("light.name") diff --git a/tests/components/eafm/test_sensor.py b/tests/components/eafm/test_sensor.py index a7ee0403c7cd9d..3f2eb72a8e39bb 100644 --- a/tests/components/eafm/test_sensor.py +++ b/tests/components/eafm/test_sensor.py @@ -5,7 +5,7 @@ import pytest from homeassistant import config_entries -from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT +from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNAVAILABLE from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -428,5 +428,8 @@ async def test_unload_entry(hass, mock_get_station): assert await entry.async_unload(hass) - # And the entity should be gone - assert not hass.states.get("sensor.my_station_water_level_stage") + # And the entity should be unavailable + assert ( + hass.states.get("sensor.my_station_water_level_stage").state + == STATE_UNAVAILABLE + ) diff --git a/tests/components/forked_daapd/test_media_player.py b/tests/components/forked_daapd/test_media_player.py index 149cbdae4e2560..ffbf7a569f9bea 100644 --- a/tests/components/forked_daapd/test_media_player.py +++ b/tests/components/forked_daapd/test_media_player.py @@ -345,12 +345,12 @@ async def pause_side_effect(): async def test_unload_config_entry(hass, config_entry, mock_api_object): - """Test the player is removed when the config entry is unloaded.""" + """Test the player is set unavailable when the config entry is unloaded.""" assert hass.states.get(TEST_MASTER_ENTITY_NAME) assert hass.states.get(TEST_ZONE_ENTITY_NAMES[0]) await config_entry.async_unload(hass) - assert not hass.states.get(TEST_MASTER_ENTITY_NAME) - assert not hass.states.get(TEST_ZONE_ENTITY_NAMES[0]) + assert hass.states.get(TEST_MASTER_ENTITY_NAME).state == STATE_UNAVAILABLE + assert hass.states.get(TEST_ZONE_ENTITY_NAMES[0]).state == STATE_UNAVAILABLE def test_master_state(hass, mock_api_object): diff --git a/tests/components/fritzbox/test_init.py b/tests/components/fritzbox/test_init.py index 11067c1aa51bf5..08655033f4dea8 100644 --- a/tests/components/fritzbox/test_init.py +++ b/tests/components/fritzbox/test_init.py @@ -4,7 +4,13 @@ from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED -from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_DEVICES, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + STATE_UNAVAILABLE, +) from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component @@ -45,8 +51,8 @@ async def test_setup_duplicate_config(hass: HomeAssistantType, fritz: Mock, capl assert "duplicate host entries found" in caplog.text -async def test_unload(hass: HomeAssistantType, fritz: Mock): - """Test unload of integration.""" +async def test_unload_remove(hass: HomeAssistantType, fritz: Mock): + """Test unload and remove of integration.""" fritz().get_devices.return_value = [FritzDeviceSwitchMock()] entity_id = f"{SWITCH_DOMAIN}.fake_name" @@ -70,6 +76,14 @@ async def test_unload(hass: HomeAssistantType, fritz: Mock): await hass.config_entries.async_unload(entry.entry_id) + assert fritz().logout.call_count == 1 + assert entry.state == ENTRY_STATE_NOT_LOADED + state = hass.states.get(entity_id) + assert state.state == STATE_UNAVAILABLE + + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + assert fritz().logout.call_count == 1 assert entry.state == ENTRY_STATE_NOT_LOADED state = hass.states.get(entity_id) diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index ef7285ab1859da..4d979f8e5560e1 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -587,10 +587,10 @@ async def test_select_input_command_error( async def test_unload_config_entry(hass, config_entry, config, controller): - """Test the player is removed when the config entry is unloaded.""" + """Test the player is set unavailable when the config entry is unloaded.""" await setup_platform(hass, config_entry, config) await config_entry.async_unload(hass) - assert not hass.states.get("media_player.test_player") + assert hass.states.get("media_player.test_player").state == STATE_UNAVAILABLE async def test_play_media_url(hass, config_entry, config, controller, caplog): diff --git a/tests/components/homekit_controller/test_light.py b/tests/components/homekit_controller/test_light.py index e443e36b910719..f4950512063332 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -3,6 +3,7 @@ from aiohomekit.model.services import ServicesTypes from homeassistant.components.homekit_controller.const import KNOWN_DEVICES +from homeassistant.const import STATE_UNAVAILABLE from tests.components.homekit_controller.common import setup_test_component @@ -209,8 +210,8 @@ async def test_light_becomes_unavailable_but_recovers(hass, utcnow): assert state.attributes["color_temp"] == 400 -async def test_light_unloaded(hass, utcnow): - """Test entity and HKDevice are correctly unloaded.""" +async def test_light_unloaded_removed(hass, utcnow): + """Test entity and HKDevice are correctly unloaded and removed.""" helper = await setup_test_component(hass, create_lightbulb_service_with_color_temp) # Initial state is that the light is off @@ -220,9 +221,15 @@ async def test_light_unloaded(hass, utcnow): unload_result = await helper.config_entry.async_unload(hass) assert unload_result is True - # Make sure entity is unloaded - assert hass.states.get(helper.entity_id) is None + # Make sure entity is set to unavailable state + assert hass.states.get(helper.entity_id).state == STATE_UNAVAILABLE # Make sure HKDevice is no longer set to poll this accessory conn = hass.data[KNOWN_DEVICES]["00:00:00:00:00:00"] assert not conn.pollable_characteristics + + await helper.config_entry.async_remove(hass) + await hass.async_block_till_done() + + # Make sure entity is removed + assert hass.states.get(helper.entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/huisbaasje/test_init.py b/tests/components/huisbaasje/test_init.py index 96be450f7e42a4..3de6af83e46f07 100644 --- a/tests/components/huisbaasje/test_init.py +++ b/tests/components/huisbaasje/test_init.py @@ -11,7 +11,7 @@ ENTRY_STATE_SETUP_ERROR, ConfigEntry, ) -from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component @@ -145,6 +145,14 @@ async def test_unload_entry(hass: HomeAssistant): await hass.config_entries.async_unload(config_entry.entry_id) assert config_entry.state == ENTRY_STATE_NOT_LOADED entities = hass.states.async_entity_ids("sensor") + assert len(entities) == 14 + for entity in entities: + assert hass.states.get(entity).state == STATE_UNAVAILABLE + + # Remove config entry + await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() + entities = hass.states.async_entity_ids("sensor") assert len(entities) == 0 # Assert mocks are called diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index 88562678436653..c5d4d40e0d59b5 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -264,7 +264,7 @@ async def test_reload(hass, hass_admin_user): assert "mdi:work_reloaded" == state_2.attributes.get(ATTR_ICON) -async def test_load_person_storage(hass, storage_setup): +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") diff --git a/tests/components/met/test_weather.py b/tests/components/met/test_weather.py index 242352c2498de0..24a81be3896430 100644 --- a/tests/components/met/test_weather.py +++ b/tests/components/met/test_weather.py @@ -30,6 +30,7 @@ async def test_tracking_home(hass, mock_weather): entry = hass.config_entries.async_entries()[0] await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() assert len(hass.states.async_entity_ids("weather")) == 0 @@ -63,4 +64,5 @@ async def test_not_tracking_home(hass, mock_weather): entry = hass.config_entries.async_entries()[0] await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() assert len(hass.states.async_entity_ids("weather")) == 0 diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py index 84deef92d62f52..117c8e97884b8a 100644 --- a/tests/components/nest/camera_sdm_test.py +++ b/tests/components/nest/camera_sdm_test.py @@ -339,6 +339,7 @@ async def test_camera_removed(hass, auth): 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() assert len(hass.states.async_all()) == 0 diff --git a/tests/components/nws/test_init.py b/tests/components/nws/test_init.py index 44b5193d79c5c6..01a203aa07b482 100644 --- a/tests/components/nws/test_init.py +++ b/tests/components/nws/test_init.py @@ -1,6 +1,7 @@ """Tests for init module.""" from homeassistant.components.nws.const import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN +from homeassistant.const import STATE_UNAVAILABLE from tests.common import MockConfigEntry from tests.components.nws.const import NWS_CONFIG @@ -25,5 +26,12 @@ async def test_unload_entry(hass, mock_simple_nws): assert len(entries) == 1 assert await hass.config_entries.async_unload(entries[0].entry_id) - assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 0 + entities = hass.states.async_entity_ids(WEATHER_DOMAIN) + assert len(entities) == 1 + for entity in entities: + assert hass.states.get(entity).state == STATE_UNAVAILABLE assert DOMAIN not in hass.data + + assert await hass.config_entries.async_remove(entries[0].entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 0 diff --git a/tests/components/ozw/test_init.py b/tests/components/ozw/test_init.py index 2e57c4c01f3ec4..339b690f4e4a67 100644 --- a/tests/components/ozw/test_init.py +++ b/tests/components/ozw/test_init.py @@ -4,6 +4,7 @@ from homeassistant import config_entries from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.ozw import DOMAIN, PLATFORMS, const +from homeassistant.const import ATTR_RESTORED, STATE_UNAVAILABLE from .common import setup_ozw @@ -76,14 +77,21 @@ async def test_unload_entry(hass, generic_data, switch_msg, caplog): await hass.config_entries.async_unload(entry.entry_id) assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED - assert len(hass.states.async_entity_ids("switch")) == 0 + entities = hass.states.async_entity_ids("switch") + assert len(entities) == 1 + for entity in entities: + assert hass.states.get(entity).state == STATE_UNAVAILABLE + assert hass.states.get(entity).attributes.get(ATTR_RESTORED) # Send a message for a switch from the broker to check that # all entity topic subscribers are unsubscribed. receive_message(switch_msg) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids("switch")) == 1 + for entity in entities: + assert hass.states.get(entity).state == STATE_UNAVAILABLE + assert hass.states.get(entity).attributes.get(ATTR_RESTORED) # Load the integration again and check that there are no errors when # adding the entities. diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py index 8f95043f4fa8bb..5c9bf183c6f60a 100644 --- a/tests/components/panasonic_viera/test_init.py +++ b/tests/components/panasonic_viera/test_init.py @@ -17,7 +17,7 @@ DOMAIN, ) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_UNAVAILABLE from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -253,9 +253,11 @@ async def test_setup_unload_entry(hass): await hass.async_block_till_done() await hass.config_entries.async_unload(mock_entry.entry_id) - assert mock_entry.state == ENTRY_STATE_NOT_LOADED - state = hass.states.get("media_player.panasonic_viera_tv") + assert state.state == STATE_UNAVAILABLE + await hass.config_entries.async_remove(mock_entry.entry_id) + await hass.async_block_till_done() + state = hass.states.get("media_player.panasonic_viera_tv") assert state is None diff --git a/tests/components/plex/test_media_players.py b/tests/components/plex/test_media_players.py index 092d7e09008ed8..fbd1205b2ef76d 100644 --- a/tests/components/plex/test_media_players.py +++ b/tests/components/plex/test_media_players.py @@ -22,7 +22,7 @@ async def test_plex_tv_clients( media_players_after = len(hass.states.async_entity_ids("media_player")) assert media_players_after == media_players_before + 1 - await hass.config_entries.async_unload(entry.entry_id) + await hass.config_entries.async_remove(entry.entry_id) # Ensure only plex.tv resource client is found with patch("plexapi.server.PlexServer.sessions", return_value=[]): diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index 6931b3dfbb548a..e10d63a2e07e82 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -12,7 +12,7 @@ ) from homeassistant.components.smartthings import binary_sensor from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE -from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -93,4 +93,7 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, "binary_sensor") # Assert - assert not hass.states.get("binary_sensor.motion_sensor_1_motion") + assert ( + hass.states.get("binary_sensor.motion_sensor_1_motion").state + == STATE_UNAVAILABLE + ) diff --git a/tests/components/smartthings/test_cover.py b/tests/components/smartthings/test_cover.py index 0483480cb8a5df..178c905208e6a9 100644 --- a/tests/components/smartthings/test_cover.py +++ b/tests/components/smartthings/test_cover.py @@ -19,7 +19,7 @@ STATE_OPENING, ) from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE -from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID +from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -193,4 +193,4 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, COVER_DOMAIN) # Assert - assert not hass.states.get("cover.garage") + assert hass.states.get("cover.garage").state == STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_fan.py b/tests/components/smartthings/test_fan.py index 0ebef7e7323822..1f837d58bf821e 100644 --- a/tests/components/smartthings/test_fan.py +++ b/tests/components/smartthings/test_fan.py @@ -17,7 +17,11 @@ SUPPORT_SET_SPEED, ) from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE -from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + STATE_UNAVAILABLE, +) from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -184,4 +188,4 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, "fan") # Assert - assert not hass.states.get("fan.fan_1") + assert hass.states.get("fan.fan_1").state == STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index bd9557c6b9716d..f6d7d8dd9f40e5 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -19,7 +19,11 @@ SUPPORT_TRANSITION, ) from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE -from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + STATE_UNAVAILABLE, +) from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -304,4 +308,4 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, "light") # Assert - assert not hass.states.get("light.color_dimmer_2") + assert hass.states.get("light.color_dimmer_2").state == STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_lock.py b/tests/components/smartthings/test_lock.py index 0492f2281ce538..185eae22ccfe4a 100644 --- a/tests/components/smartthings/test_lock.py +++ b/tests/components/smartthings/test_lock.py @@ -9,6 +9,7 @@ from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -104,4 +105,4 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, "lock") # Assert - assert not hass.states.get("lock.lock_1") + assert hass.states.get("lock.lock_1").state == STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_scene.py b/tests/components/smartthings/test_scene.py index a9e6443d2bff11..6ab4bc080807dc 100644 --- a/tests/components/smartthings/test_scene.py +++ b/tests/components/smartthings/test_scene.py @@ -5,7 +5,7 @@ real HTTP calls are not initiated during testing. """ from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNAVAILABLE from .conftest import setup_platform @@ -46,4 +46,4 @@ async def test_unload_config_entry(hass, scene): # Act await hass.config_entries.async_forward_entry_unload(config_entry, SCENE_DOMAIN) # Assert - assert not hass.states.get("scene.test_scene") + assert hass.states.get("scene.test_scene").state == STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py index 3faf0f621a3e35..53f4b2c72442c0 100644 --- a/tests/components/smartthings/test_sensor.py +++ b/tests/components/smartthings/test_sensor.py @@ -13,6 +13,7 @@ ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, + STATE_UNAVAILABLE, STATE_UNKNOWN, ) from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -117,4 +118,4 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") # Assert - assert not hass.states.get("sensor.sensor_1_battery") + assert hass.states.get("sensor.sensor_1_battery").state == STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index 3ac86426eeb4c7..27ed5050beecfe 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -12,6 +12,7 @@ ATTR_TODAY_ENERGY_KWH, DOMAIN as SWITCH_DOMAIN, ) +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -96,4 +97,4 @@ async def test_unload_config_entry(hass, device_factory): # Act await hass.config_entries.async_forward_entry_unload(config_entry, "switch") # Assert - assert not hass.states.get("switch.switch_1") + assert hass.states.get("switch.switch_1").state == STATE_UNAVAILABLE diff --git a/tests/components/vizio/test_init.py b/tests/components/vizio/test_init.py index cd6116625977e2..b223202d5b18fb 100644 --- a/tests/components/vizio/test_init.py +++ b/tests/components/vizio/test_init.py @@ -3,6 +3,7 @@ from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN from homeassistant.components.vizio.const import DOMAIN +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component @@ -41,7 +42,10 @@ async def test_tv_load_and_unload( assert await config_entry.async_unload(hass) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 0 + entities = hass.states.async_entity_ids(MP_DOMAIN) + assert len(entities) == 1 + for entity in entities: + assert hass.states.get(entity).state == STATE_UNAVAILABLE assert DOMAIN not in hass.data @@ -62,5 +66,8 @@ async def test_speaker_load_and_unload( assert await config_entry.async_unload(hass) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 0 + entities = hass.states.async_entity_ids(MP_DOMAIN) + assert len(entities) == 1 + for entity in entities: + assert hass.states.get(entity).state == STATE_UNAVAILABLE assert DOMAIN not in hass.data diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index c91ae33d9860d9..05a0bd0d8d4bb3 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -11,7 +11,7 @@ DOMAIN, NIGHTLIGHT_SWITCH_TYPE_LIGHT, ) -from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_NAME +from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_NAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component @@ -50,6 +50,12 @@ async def test_setup_discovery(hass: HomeAssistant): # Unload assert await hass.config_entries.async_unload(config_entry.entry_id) + assert hass.states.get(ENTITY_BINARY_SENSOR).state == STATE_UNAVAILABLE + assert hass.states.get(ENTITY_LIGHT).state == STATE_UNAVAILABLE + + # Remove + assert await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() assert hass.states.get(ENTITY_BINARY_SENSOR) is None assert hass.states.get(ENTITY_LIGHT) is None diff --git a/tests/helpers/test_collection.py b/tests/helpers/test_collection.py index d5a8526b6da8d7..11ab0f46ce4527 100644 --- a/tests/helpers/test_collection.py +++ b/tests/helpers/test_collection.py @@ -226,7 +226,7 @@ async def test_attach_entity_component_collection(hass): """Test attaching collection to entity component.""" ent_comp = entity_component.EntityComponent(_LOGGER, "test", hass) coll = collection.ObservableCollection(_LOGGER) - collection.attach_entity_component_collection(ent_comp, coll, MockEntity) + collection.sync_entity_lifecycle(hass, "test", "test", ent_comp, coll, MockEntity) await coll.notify_changes( [ diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 52149b060e4758..b8d0fc7dc9ce7f 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -7,7 +7,7 @@ import pytest -from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE +from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import Context from homeassistant.helpers import entity, entity_registry @@ -718,3 +718,29 @@ async def test_setup_source(hass): await platform.async_reset() assert entity.entity_sources(hass) == {} + + +async def test_removing_entity_unavailable(hass): + """Test removing an entity that is still registered creates an unavailable state.""" + entry = entity_registry.RegistryEntry( + entity_id="hello.world", + unique_id="test-unique-id", + platform="test-platform", + disabled_by=None, + ) + + ent = entity.Entity() + ent.hass = hass + ent.entity_id = "hello.world" + ent.registry_entry = entry + ent.async_write_ha_state() + + state = hass.states.get("hello.world") + assert state is not None + assert state.state == STATE_UNKNOWN + + await ent.async_remove() + + state = hass.states.get("hello.world") + assert state is not None + assert state.state == STATE_UNAVAILABLE From b9b1caf4d7e26208aa4abfa54b4ab4f6bc66d488 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 8 Feb 2021 10:47:57 +0100 Subject: [PATCH 0265/1818] Raise ConditionError for numeric_state errors (#45923) --- .../components/automation/__init__.py | 8 +- .../components/bayesian/binary_sensor.py | 21 +-- .../homeassistant/triggers/numeric_state.py | 17 ++- homeassistant/exceptions.py | 4 + homeassistant/helpers/condition.py | 51 ++++--- homeassistant/helpers/script.py | 39 ++++-- tests/components/automation/test_init.py | 12 +- .../triggers/test_numeric_state.py | 26 ++++ tests/helpers/test_condition.py | 128 ++++++++++++++++-- tests/helpers/test_script.py | 109 +++++++++++++++ 10 files changed, 352 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 295450e8211b67..94f2cedd58e029 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -32,7 +32,7 @@ callback, split_entity_id, ) -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import ConditionError, HomeAssistantError from homeassistant.helpers import condition, extract_domain_configs, template import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity @@ -588,7 +588,11 @@ async def _async_process_if(hass, config, p_config): def if_action(variables=None): """AND all conditions.""" - return all(check(hass, variables) for check in checks) + try: + return all(check(hass, variables) for check in checks) + except ConditionError as ex: + LOGGER.warning("Error in 'condition' evaluation: %s", ex) + return False if_action.config = if_configs diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 4768b3f4fe6261..15176d453491c0 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -17,7 +17,7 @@ STATE_UNKNOWN, ) from homeassistant.core import callback -from homeassistant.exceptions import TemplateError +from homeassistant.exceptions import ConditionError, TemplateError from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( @@ -340,14 +340,17 @@ def _process_numeric_state(self, entity_observation): """Return True if numeric condition is met.""" entity = entity_observation["entity_id"] - return condition.async_numeric_state( - self.hass, - entity, - entity_observation.get("below"), - entity_observation.get("above"), - None, - entity_observation, - ) + try: + return condition.async_numeric_state( + self.hass, + entity, + entity_observation.get("below"), + entity_observation.get("above"), + None, + entity_observation, + ) + except ConditionError: + return False def _process_state(self, entity_observation): """Return True if state conditions are met.""" diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 7cfee8fad933d1..55e875c90de15d 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -96,13 +96,20 @@ def variables(entity_id): @callback def check_numeric_state(entity_id, from_s, to_s): """Return True if criteria are now met.""" - if to_s is None: + try: + return condition.async_numeric_state( + hass, + to_s, + below, + above, + value_template, + variables(entity_id), + attribute, + ) + except exceptions.ConditionError as err: + _LOGGER.warning("%s", err) return False - return condition.async_numeric_state( - hass, to_s, below, above, value_template, variables(entity_id), attribute - ) - @callback def state_automation_listener(event): """Listen for state changes and calls action.""" diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index e37f68a07bf2f0..852795ebb4ac88 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -25,6 +25,10 @@ def __init__(self, exception: Exception) -> None: super().__init__(f"{exception.__class__.__name__}: {exception}") +class ConditionError(HomeAssistantError): + """Error during condition evaluation.""" + + class PlatformNotReady(HomeAssistantError): """Error to indicate that platform is not ready.""" diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 5ace4c91bcf752..e47374a9d17066 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -36,7 +36,7 @@ WEEKDAYS, ) from homeassistant.core import HomeAssistant, State, callback -from homeassistant.exceptions import HomeAssistantError, TemplateError +from homeassistant.exceptions import ConditionError, HomeAssistantError, TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.sun import get_astral_event_date from homeassistant.helpers.template import Template @@ -204,11 +204,22 @@ def async_numeric_state( attribute: Optional[str] = None, ) -> bool: """Test a numeric state condition.""" + if entity is None: + raise ConditionError("No entity specified") + if isinstance(entity, str): + entity_id = entity entity = hass.states.get(entity) - if entity is None or (attribute is not None and attribute not in entity.attributes): - return False + if entity is None: + raise ConditionError(f"Unknown entity {entity_id}") + else: + entity_id = entity.entity_id + + if attribute is not None and attribute not in entity.attributes: + raise ConditionError( + f"Attribute '{attribute}' (of entity {entity_id}) does not exist" + ) value: Any = None if value_template is None: @@ -222,30 +233,27 @@ def async_numeric_state( try: value = value_template.async_render(variables) except TemplateError as ex: - _LOGGER.error("Template error: %s", ex) - return False + raise ConditionError(f"Template error: {ex}") from ex if value in (STATE_UNAVAILABLE, STATE_UNKNOWN): - return False + raise ConditionError("State is not available") try: fvalue = float(value) - except ValueError: - _LOGGER.warning( - "Value cannot be processed as a number: %s (Offending entity: %s)", - entity, - value, - ) - return False + except ValueError as ex: + raise ConditionError( + f"Entity {entity_id} state '{value}' cannot be processed as a number" + ) from ex if below is not None: if isinstance(below, str): below_entity = hass.states.get(below) - if ( - not below_entity - or below_entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) - or fvalue >= float(below_entity.state) + if not below_entity or below_entity.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, ): + raise ConditionError(f"The below entity {below} is not available") + if fvalue >= float(below_entity.state): return False elif fvalue >= below: return False @@ -253,11 +261,12 @@ def async_numeric_state( if above is not None: if isinstance(above, str): above_entity = hass.states.get(above) - if ( - not above_entity - or above_entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) - or fvalue <= float(above_entity.state) + if not above_entity or above_entity.state in ( + STATE_UNAVAILABLE, + STATE_UNKNOWN, ): + raise ConditionError(f"The above entity {above} is not available") + if fvalue <= float(above_entity.state): return False elif fvalue <= above: return False diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 60a1be6103ae53..8706f765b50579 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -519,7 +519,12 @@ async def _async_condition_step(self): CONF_ALIAS, self._action[CONF_CONDITION] ) cond = await self._async_get_condition(self._action) - check = cond(self._hass, self._variables) + try: + check = cond(self._hass, self._variables) + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'condition' evaluation: %s", ex) + check = False + self._log("Test condition %s: %s", self._script.last_action, check) if not check: raise _StopScript @@ -570,10 +575,15 @@ async def async_run_sequence(iteration, extra_msg=""): ] for iteration in itertools.count(1): set_repeat_var(iteration) - if self._stop.is_set() or not all( - cond(self._hass, self._variables) for cond in conditions - ): + try: + if self._stop.is_set() or not all( + cond(self._hass, self._variables) for cond in conditions + ): + break + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'while' evaluation: %s", ex) break + await async_run_sequence(iteration) elif CONF_UNTIL in repeat: @@ -583,9 +593,13 @@ async def async_run_sequence(iteration, extra_msg=""): for iteration in itertools.count(1): set_repeat_var(iteration) await async_run_sequence(iteration) - if self._stop.is_set() or all( - cond(self._hass, self._variables) for cond in conditions - ): + try: + if self._stop.is_set() or all( + cond(self._hass, self._variables) for cond in conditions + ): + break + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'until' evaluation: %s", ex) break if saved_repeat_vars: @@ -599,9 +613,14 @@ async def _async_choose_step(self) -> None: choose_data = await self._script._async_get_choose_data(self._step) for conditions, script in choose_data["choices"]: - if all(condition(self._hass, self._variables) for condition in conditions): - await self._async_run_script(script) - return + try: + if all( + condition(self._hass, self._variables) for condition in conditions + ): + await self._async_run_script(script) + return + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'choose' evaluation: %s", ex) if choose_data["default"]: await self._async_run_script(choose_data["default"]) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index c31af555e32075..0dbc4b2cc69a28 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -162,18 +162,20 @@ async def test_trigger_service_ignoring_condition(hass, calls): "alias": "test", "trigger": [{"platform": "event", "event_type": "test_event"}], "condition": { - "condition": "state", + "condition": "numeric_state", "entity_id": "non.existing", - "state": "beer", + "above": "1", }, "action": {"service": "test.automation"}, } }, ) - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - assert len(calls) == 0 + with patch("homeassistant.components.automation.LOGGER.warning") as logwarn: + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 0 + assert len(logwarn.mock_calls) == 1 await hass.services.async_call( "automation", "trigger", {"entity_id": "automation.test"}, blocking=True diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index b9696fffe06742..979c5bad5d7e9f 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -572,6 +572,32 @@ async def test_if_not_fires_if_entity_not_match(hass, calls, below): assert len(calls) == 0 +async def test_if_not_fires_and_warns_if_below_entity_unknown(hass, calls): + """Test if warns with unknown below entity.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "numeric_state", + "entity_id": "test.entity", + "below": "input_number.unknown", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + with patch( + "homeassistant.components.homeassistant.triggers.numeric_state._LOGGER.warning" + ) as logwarn: + hass.states.async_set("test.entity", 1) + await hass.async_block_till_done() + assert len(calls) == 0 + assert len(logwarn.mock_calls) == 1 + + @pytest.mark.parametrize("below", (10, "input_number.value_10")) async def test_if_fires_on_entity_change_below_with_attribute(hass, calls, below): """Test attributes change.""" diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index fe2a9aa4406dcc..5c388aa6db4a3c 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -4,7 +4,7 @@ import pytest -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import ConditionError, HomeAssistantError from homeassistant.helpers import condition from homeassistant.helpers.template import Template from homeassistant.setup import async_setup_component @@ -338,8 +338,8 @@ async def test_time_using_input_datetime(hass): assert not condition.time(hass, before="input_datetime.not_existing") -async def test_if_numeric_state_not_raise_on_unavailable(hass): - """Test numeric_state doesn't raise on unavailable/unknown state.""" +async def test_if_numeric_state_raises_on_unavailable(hass): + """Test numeric_state raises on unavailable/unknown state.""" test = await condition.async_from_config( hass, {"condition": "numeric_state", "entity_id": "sensor.temperature", "below": 42}, @@ -347,11 +347,13 @@ async def test_if_numeric_state_not_raise_on_unavailable(hass): with patch("homeassistant.helpers.condition._LOGGER.warning") as logwarn: hass.states.async_set("sensor.temperature", "unavailable") - assert not test(hass) + with pytest.raises(ConditionError): + test(hass) assert len(logwarn.mock_calls) == 0 hass.states.async_set("sensor.temperature", "unknown") - assert not test(hass) + with pytest.raises(ConditionError): + test(hass) assert len(logwarn.mock_calls) == 0 @@ -550,6 +552,108 @@ async def test_state_using_input_entities(hass): assert test(hass) +async def test_numeric_state_raises(hass): + """Test that numeric_state raises ConditionError on errors.""" + # Unknown entity_id + with pytest.raises(ConditionError, match="Unknown entity"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature_unknown", + "above": 0, + }, + ) + + assert test(hass) + + # Unknown attribute + with pytest.raises(ConditionError, match=r"Attribute .* does not exist"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "attribute": "temperature", + "above": 0, + }, + ) + + hass.states.async_set("sensor.temperature", 50) + test(hass) + + # Template error + with pytest.raises(ConditionError, match="ZeroDivisionError"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "value_template": "{{ 1 / 0 }}", + "above": 0, + }, + ) + + hass.states.async_set("sensor.temperature", 50) + test(hass) + + # Unavailable state + with pytest.raises(ConditionError, match="State is not available"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "above": 0, + }, + ) + + hass.states.async_set("sensor.temperature", "unavailable") + test(hass) + + # Bad number + with pytest.raises(ConditionError, match="cannot be processed as a number"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "above": 0, + }, + ) + + hass.states.async_set("sensor.temperature", "fifty") + test(hass) + + # Below entity missing + with pytest.raises(ConditionError, match="below entity"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "below": "input_number.missing", + }, + ) + + hass.states.async_set("sensor.temperature", 50) + test(hass) + + # Above entity missing + with pytest.raises(ConditionError, match="above entity"): + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "above": "input_number.missing", + }, + ) + + hass.states.async_set("sensor.temperature", 50) + test(hass) + + async def test_numeric_state_multiple_entities(hass): """Test with multiple entities in condition.""" test = await condition.async_from_config( @@ -660,12 +764,14 @@ async def test_numeric_state_using_input_number(hass): ) assert test(hass) - assert not condition.async_numeric_state( - hass, entity="sensor.temperature", below="input_number.not_exist" - ) - assert not condition.async_numeric_state( - hass, entity="sensor.temperature", above="input_number.not_exist" - ) + with pytest.raises(ConditionError): + condition.async_numeric_state( + hass, entity="sensor.temperature", below="input_number.not_exist" + ) + with pytest.raises(ConditionError): + condition.async_numeric_state( + hass, entity="sensor.temperature", above="input_number.not_exist" + ) async def test_zone_multiple_entities(hass): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 65d8b442bf08d7..003e903de14280 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -990,6 +990,32 @@ async def async_attach_trigger_mock(*args, **kwargs): assert "something bad" in caplog.text +async def test_condition_warning(hass): + """Test warning on condition.""" + event = "test_event" + events = async_capture_events(hass, event) + sequence = cv.SCRIPT_SCHEMA( + [ + {"event": event}, + { + "condition": "numeric_state", + "entity_id": "test.entity", + "above": 0, + }, + {"event": event}, + ] + ) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + + hass.states.async_set("test.entity", "string") + with patch("homeassistant.helpers.script._LOGGER.warning") as logwarn: + await script_obj.async_run(context=Context()) + await hass.async_block_till_done() + assert len(logwarn.mock_calls) == 1 + + assert len(events) == 1 + + async def test_condition_basic(hass): """Test if we can use conditions in a script.""" event = "test_event" @@ -1100,6 +1126,44 @@ async def test_repeat_count(hass): assert event.data.get("last") == (index == count - 1) +@pytest.mark.parametrize("condition", ["while", "until"]) +async def test_repeat_condition_warning(hass, condition): + """Test warning on repeat conditions.""" + event = "test_event" + events = async_capture_events(hass, event) + count = 0 if condition == "while" else 1 + + sequence = { + "repeat": { + "sequence": [ + { + "event": event, + }, + ], + } + } + sequence["repeat"][condition] = { + "condition": "numeric_state", + "entity_id": "sensor.test", + "value_template": "{{ unassigned_variable }}", + "above": "0", + } + + script_obj = script.Script( + hass, cv.SCRIPT_SCHEMA(sequence), f"Test {condition}", "test_domain" + ) + + # wait_started = async_watch_for_action(script_obj, "wait") + hass.states.async_set("sensor.test", "1") + + with patch("homeassistant.helpers.script._LOGGER.warning") as logwarn: + hass.async_create_task(script_obj.async_run(context=Context())) + await asyncio.wait_for(hass.async_block_till_done(), 1) + assert len(logwarn.mock_calls) == 1 + + assert len(events) == count + + @pytest.mark.parametrize("condition", ["while", "until"]) @pytest.mark.parametrize("direct_template", [False, True]) async def test_repeat_conditional(hass, condition, direct_template): @@ -1305,6 +1369,51 @@ async def test_repeat_nested(hass, variables, first_last, inside_x): } +async def test_choose_warning(hass): + """Test warning on choose.""" + event = "test_event" + events = async_capture_events(hass, event) + + sequence = cv.SCRIPT_SCHEMA( + { + "choose": [ + { + "conditions": { + "condition": "numeric_state", + "entity_id": "test.entity", + "value_template": "{{ undefined_a + undefined_b }}", + "above": 1, + }, + "sequence": {"event": event, "event_data": {"choice": "first"}}, + }, + { + "conditions": { + "condition": "numeric_state", + "entity_id": "test.entity", + "value_template": "{{ 'string' }}", + "above": 2, + }, + "sequence": {"event": event, "event_data": {"choice": "second"}}, + }, + ], + "default": {"event": event, "event_data": {"choice": "default"}}, + } + ) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + + hass.states.async_set("test.entity", "9") + await hass.async_block_till_done() + + with patch("homeassistant.helpers.script._LOGGER.warning") as logwarn: + await script_obj.async_run(context=Context()) + await hass.async_block_till_done() + print(logwarn.mock_calls) + assert len(logwarn.mock_calls) == 2 + + assert len(events) == 1 + assert events[0].data["choice"] == "default" + + @pytest.mark.parametrize("var,result", [(1, "first"), (2, "second"), (3, "default")]) async def test_choose(hass, var, result): """Test choose action.""" From 047f16772f7369aaf55a96f98e64b6011449b5ab Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Feb 2021 10:50:38 +0100 Subject: [PATCH 0266/1818] Support templating MQTT triggers (#45614) * Add support for limited templates (no HASS access) * Pass variables to automation triggers * Support templates in MQTT triggers * Spelling * Handle trigger referenced by variables * Raise on unsupported function in limited templates * Validate MQTT trigger schema in MQTT device trigger * Add trigger_variables to automation config schema * Don't print stacktrace when setting up trigger throws * Make pylint happy * Add trigger_variables to variables * Add debug prints, document limited template * Add tests * Validate MQTT trigger topic early when possible * Improve valid_subscribe_topic_template --- .../components/automation/__init__.py | 29 +++++- homeassistant/components/automation/config.py | 2 + homeassistant/components/automation/const.py | 1 + .../components/mqtt/device_trigger.py | 2 + homeassistant/components/mqtt/trigger.py | 25 +++++- homeassistant/components/mqtt/util.py | 12 ++- homeassistant/helpers/config_validation.py | 2 +- homeassistant/helpers/script_variables.py | 5 +- homeassistant/helpers/template.py | 54 ++++++++++-- homeassistant/helpers/trigger.py | 5 +- tests/components/automation/test_init.py | 88 +++++++++++++++++++ tests/components/mqtt/test_trigger.py | 52 +++++++++++ 12 files changed, 262 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 94f2cedd58e029..ae8c71b4fb8d08 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -60,6 +60,7 @@ CONF_ACTION, CONF_INITIAL_STATE, CONF_TRIGGER, + CONF_TRIGGER_VARIABLES, DEFAULT_INITIAL_STATE, DOMAIN, LOGGER, @@ -221,6 +222,7 @@ def __init__( action_script, initial_state, variables, + trigger_variables, ): """Initialize an automation entity.""" self._id = automation_id @@ -236,6 +238,7 @@ def __init__( self._referenced_devices: Optional[Set[str]] = None self._logger = LOGGER self._variables: ScriptVariables = variables + self._trigger_variables: ScriptVariables = trigger_variables @property def name(self): @@ -471,6 +474,16 @@ async def _async_attach_triggers( def log_cb(level, msg, **kwargs): self._logger.log(level, "%s %s", msg, self._name, **kwargs) + variables = None + if self._trigger_variables: + try: + variables = self._trigger_variables.async_render( + cast(HomeAssistant, self.hass), None, limited=True + ) + except template.TemplateError as err: + self._logger.error("Error rendering trigger variables: %s", err) + return None + return await async_initialize_triggers( cast(HomeAssistant, self.hass), self._trigger_config, @@ -479,6 +492,7 @@ def log_cb(level, msg, **kwargs): self._name, log_cb, home_assistant_start, + variables, ) @property @@ -556,6 +570,18 @@ async def _async_process_config( else: cond_func = None + # Add trigger variables to variables + variables = None + if CONF_TRIGGER_VARIABLES in config_block: + variables = ScriptVariables( + dict(config_block[CONF_TRIGGER_VARIABLES].as_dict()) + ) + if CONF_VARIABLES in config_block: + if variables: + variables.variables.update(config_block[CONF_VARIABLES].as_dict()) + else: + variables = config_block[CONF_VARIABLES] + entity = AutomationEntity( automation_id, name, @@ -563,7 +589,8 @@ async def _async_process_config( cond_func, action_script, initial_state, - config_block.get(CONF_VARIABLES), + variables, + config_block.get(CONF_TRIGGER_VARIABLES), ) entities.append(entity) diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 89d5e18474867d..32ad92cb86e382 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -21,6 +21,7 @@ CONF_HIDE_ENTITY, CONF_INITIAL_STATE, CONF_TRIGGER, + CONF_TRIGGER_VARIABLES, DOMAIN, ) from .helpers import async_get_blueprints @@ -43,6 +44,7 @@ vol.Required(CONF_TRIGGER): cv.TRIGGER_SCHEMA, vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA, + vol.Optional(CONF_TRIGGER_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA, vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, }, script.SCRIPT_MODE_SINGLE, diff --git a/homeassistant/components/automation/const.py b/homeassistant/components/automation/const.py index ffb89ba0907bfa..829f78590e0803 100644 --- a/homeassistant/components/automation/const.py +++ b/homeassistant/components/automation/const.py @@ -3,6 +3,7 @@ CONF_ACTION = "action" CONF_TRIGGER = "trigger" +CONF_TRIGGER_VARIABLES = "trigger_variables" DOMAIN = "automation" CONF_DESCRIPTION = "description" diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 6a04fd48049753..8969072553cd5d 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -89,12 +89,14 @@ class TriggerInstance: async def async_attach_trigger(self): """Attach MQTT trigger.""" mqtt_config = { + mqtt_trigger.CONF_PLATFORM: mqtt.DOMAIN, mqtt_trigger.CONF_TOPIC: self.trigger.topic, mqtt_trigger.CONF_ENCODING: DEFAULT_ENCODING, mqtt_trigger.CONF_QOS: self.trigger.qos, } if self.trigger.payload: mqtt_config[CONF_PAYLOAD] = self.trigger.payload + mqtt_config = mqtt_trigger.TRIGGER_SCHEMA(mqtt_config) if self.remove: self.remove() diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 1c96b3de266a91..a82ea355343e4d 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -1,11 +1,12 @@ """Offer MQTT listening automation rules.""" import json +import logging import voluptuous as vol from homeassistant.const import CONF_PAYLOAD, CONF_PLATFORM from homeassistant.core import HassJob, callback -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, template from .. import mqtt @@ -20,8 +21,8 @@ TRIGGER_SCHEMA = vol.Schema( { vol.Required(CONF_PLATFORM): mqtt.DOMAIN, - vol.Required(CONF_TOPIC): mqtt.util.valid_subscribe_topic, - vol.Optional(CONF_PAYLOAD): cv.string, + vol.Required(CONF_TOPIC): mqtt.util.valid_subscribe_topic_template, + vol.Optional(CONF_PAYLOAD): cv.template, vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All( vol.Coerce(int), vol.In([0, 1, 2]) @@ -29,6 +30,8 @@ } ) +_LOGGER = logging.getLogger(__name__) + async def async_attach_trigger(hass, config, action, automation_info): """Listen for state changes based on configuration.""" @@ -37,6 +40,18 @@ async def async_attach_trigger(hass, config, action, automation_info): encoding = config[CONF_ENCODING] or None qos = config[CONF_QOS] job = HassJob(action) + variables = None + if automation_info: + variables = automation_info.get("variables") + + template.attach(hass, payload) + if payload: + payload = payload.async_render(variables, limited=True) + + template.attach(hass, topic) + if isinstance(topic, template.Template): + topic = topic.async_render(variables, limited=True) + topic = mqtt.util.valid_subscribe_topic(topic) @callback def mqtt_automation_listener(mqttmsg): @@ -57,6 +72,10 @@ def mqtt_automation_listener(mqttmsg): hass.async_run_hass_job(job, {"trigger": data}) + _LOGGER.debug( + "Attaching MQTT trigger for topic: '%s', payload: '%s'", topic, payload + ) + remove = await mqtt.async_subscribe( hass, topic, mqtt_automation_listener, encoding=encoding, qos=qos ) diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index 651fe48fe3d37c..b8fca50a153c06 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -4,7 +4,7 @@ import voluptuous as vol from homeassistant.const import CONF_PAYLOAD -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, template from .const import ( ATTR_PAYLOAD, @@ -61,6 +61,16 @@ def valid_subscribe_topic(value: Any) -> str: return value +def valid_subscribe_topic_template(value: Any) -> template.Template: + """Validate either a jinja2 template or a valid MQTT subscription topic.""" + tpl = template.Template(value) + + if tpl.is_static: + valid_subscribe_topic(value) + + return tpl + + def valid_publish_topic(value: Any) -> str: """Validate that we can publish using this MQTT topic.""" value = valid_topic(value) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index d47ba30c114e11..4af4744e509d9d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -572,7 +572,7 @@ def dynamic_template(value: Optional[Any]) -> template_helper.Template: if isinstance(value, (list, dict, template_helper.Template)): raise vol.Invalid("template value should be a string") if not template_helper.is_template_string(str(value)): - raise vol.Invalid("template value does not contain a dynmamic template") + raise vol.Invalid("template value does not contain a dynamic template") template_value = template_helper.Template(str(value)) # type: ignore try: diff --git a/homeassistant/helpers/script_variables.py b/homeassistant/helpers/script_variables.py index 3140fc4dcedcb4..818263c9dd56b8 100644 --- a/homeassistant/helpers/script_variables.py +++ b/homeassistant/helpers/script_variables.py @@ -21,6 +21,7 @@ def async_render( run_variables: Optional[Mapping[str, Any]], *, render_as_defaults: bool = True, + limited: bool = False, ) -> Dict[str, Any]: """Render script variables. @@ -55,7 +56,9 @@ def async_render( if render_as_defaults and key in rendered_variables: continue - rendered_variables[key] = template.render_complex(value, rendered_variables) + rendered_variables[key] = template.render_complex( + value, rendered_variables, limited + ) return rendered_variables diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 5f506c02eef003..af63cab10eb95a 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -84,7 +84,9 @@ def attach(hass: HomeAssistantType, obj: Any) -> None: obj.hass = hass -def render_complex(value: Any, variables: TemplateVarsType = None) -> Any: +def render_complex( + value: Any, variables: TemplateVarsType = None, limited: bool = False +) -> Any: """Recursive template creator helper function.""" if isinstance(value, list): return [render_complex(item, variables) for item in value] @@ -94,7 +96,7 @@ def render_complex(value: Any, variables: TemplateVarsType = None) -> Any: for key, item in value.items() } if isinstance(value, Template): - return value.async_render(variables) + return value.async_render(variables, limited=limited) return value @@ -279,6 +281,7 @@ class Template: "is_static", "_compiled_code", "_compiled", + "_limited", ) def __init__(self, template, hass=None): @@ -291,10 +294,11 @@ def __init__(self, template, hass=None): self._compiled: Optional[Template] = None self.hass = hass self.is_static = not is_template_string(template) + self._limited = None @property def _env(self) -> "TemplateEnvironment": - if self.hass is None: + if self.hass is None or self._limited: return _NO_HASS_ENV ret: Optional[TemplateEnvironment] = self.hass.data.get(_ENVIRONMENT) if ret is None: @@ -315,9 +319,13 @@ def render( self, variables: TemplateVarsType = None, parse_result: bool = True, + limited: bool = False, **kwargs: Any, ) -> Any: - """Render given template.""" + """Render given template. + + If limited is True, the template is not allowed to access any function or filter depending on hass or the state machine. + """ if self.is_static: if self.hass.config.legacy_templates or not parse_result: return self.template @@ -325,7 +333,7 @@ def render( return run_callback_threadsafe( self.hass.loop, - partial(self.async_render, variables, parse_result, **kwargs), + partial(self.async_render, variables, parse_result, limited, **kwargs), ).result() @callback @@ -333,18 +341,21 @@ def async_render( self, variables: TemplateVarsType = None, parse_result: bool = True, + limited: bool = False, **kwargs: Any, ) -> Any: """Render given template. This method must be run in the event loop. + + If limited is True, the template is not allowed to access any function or filter depending on hass or the state machine. """ if self.is_static: if self.hass.config.legacy_templates or not parse_result: return self.template return self._parse_result(self.template) - compiled = self._compiled or self._ensure_compiled() + compiled = self._compiled or self._ensure_compiled(limited) if variables is not None: kwargs.update(variables) @@ -519,12 +530,16 @@ def async_render_with_possible_json_value( ) return value if error_value is _SENTINEL else error_value - def _ensure_compiled(self) -> "Template": + def _ensure_compiled(self, limited: bool = False) -> "Template": """Bind a template to a specific hass instance.""" self.ensure_valid() assert self.hass is not None, "hass variable not set on template" + assert ( + self._limited is None or self._limited == limited + ), "can't change between limited and non limited template" + self._limited = limited env = self._env self._compiled = cast( @@ -1352,6 +1367,31 @@ def __init__(self, hass): self.globals["strptime"] = strptime self.globals["urlencode"] = urlencode if hass is None: + + def unsupported(name): + def warn_unsupported(*args, **kwargs): + raise TemplateError( + f"Use of '{name}' is not supported in limited templates" + ) + + return warn_unsupported + + hass_globals = [ + "closest", + "distance", + "expand", + "is_state", + "is_state_attr", + "state_attr", + "states", + "utcnow", + "now", + ] + hass_filters = ["closest", "expand"] + for glob in hass_globals: + self.globals[glob] = unsupported(glob) + for filt in hass_filters: + self.filters[filt] = unsupported(filt) return # We mark these as a context functions to ensure they get diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 2c7275a9cc3e2e..58ac71a515e1cb 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -8,6 +8,7 @@ from homeassistant.const import CONF_PLATFORM from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.loader import IntegrationNotFound, async_get_integration @@ -79,7 +80,9 @@ async def async_initialize_triggers( removes = [] for result in attach_results: - if isinstance(result, Exception): + if isinstance(result, HomeAssistantError): + log_cb(logging.ERROR, f"Got error '{result}' when setting up triggers for") + elif isinstance(result, Exception): log_cb(logging.ERROR, "Error setting up trigger", exc_info=result) elif result is None: log_cb( diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 0dbc4b2cc69a28..16d56c84cb01d5 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1237,6 +1237,94 @@ async def test_automation_variables(hass, caplog): assert len(calls) == 3 +async def test_automation_trigger_variables(hass, caplog): + """Test automation trigger variables.""" + calls = async_mock_service(hass, "test", "automation") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "variables": { + "event_type": "{{ trigger.event.event_type }}", + }, + "trigger_variables": { + "test_var": "defined_in_config", + }, + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": { + "service": "test.automation", + "data": { + "value": "{{ test_var }}", + "event_type": "{{ event_type }}", + }, + }, + }, + { + "variables": { + "event_type": "{{ trigger.event.event_type }}", + "test_var": "overridden_in_config", + }, + "trigger_variables": { + "test_var": "defined_in_config", + }, + "trigger": {"platform": "event", "event_type": "test_event_2"}, + "action": { + "service": "test.automation", + "data": { + "value": "{{ test_var }}", + "event_type": "{{ event_type }}", + }, + }, + }, + ] + }, + ) + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["value"] == "defined_in_config" + assert calls[0].data["event_type"] == "test_event" + + hass.bus.async_fire("test_event_2") + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["value"] == "overridden_in_config" + assert calls[1].data["event_type"] == "test_event_2" + + assert "Error rendering variables" not in caplog.text + + +async def test_automation_bad_trigger_variables(hass, caplog): + """Test automation trigger variables accessing hass is rejected.""" + calls = async_mock_service(hass, "test", "automation") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger_variables": { + "test_var": "{{ states('foo.bar') }}", + }, + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": { + "service": "test.automation", + }, + }, + ] + }, + ) + hass.bus.async_fire("test_event") + assert "Use of 'states' is not supported in limited templates" in caplog.text + + await hass.async_block_till_done() + assert len(calls) == 0 + + async def test_blueprint_automation(hass, calls): """Test blueprint automation.""" assert await async_setup_component( diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index b27af2b9bd014e..537a4f8dc64caf 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -81,6 +81,58 @@ async def test_if_fires_on_topic_and_payload_match(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_templated_topic_and_payload_match(hass, calls): + """Test if message is fired on templated topic and payload match.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "mqtt", + "topic": "test-topic-{{ sqrt(16)|round }}", + "payload": '{{ "foo"|regex_replace("foo", "bar") }}', + }, + "action": {"service": "test.automation"}, + } + }, + ) + + async_fire_mqtt_message(hass, "test-topic-", "foo") + await hass.async_block_till_done() + assert len(calls) == 0 + + async_fire_mqtt_message(hass, "test-topic-4", "foo") + await hass.async_block_till_done() + assert len(calls) == 0 + + async_fire_mqtt_message(hass, "test-topic-4", "bar") + await hass.async_block_till_done() + assert len(calls) == 1 + + +async def test_non_allowed_templates(hass, calls, caplog): + """Test non allowed function in template.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "mqtt", + "topic": "test-topic-{{ states() }}", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert ( + "Got error 'TemplateError: str: Use of 'states' is not supported in limited templates' when setting up triggers" + in caplog.text + ) + + async def test_if_not_fires_on_topic_but_no_payload_match(hass, calls): """Test if message is not fired on topic but no payload.""" assert await async_setup_component( From 8efb5eea4ddf9b8440ff8700f708ddbe4fed1e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Mon, 8 Feb 2021 11:00:23 +0100 Subject: [PATCH 0267/1818] Bump python-verisure to version 1.7.2 (#46177) --- homeassistant/components/verisure/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 22c5e0c2362c9e..814b5f148fa049 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -2,6 +2,6 @@ "domain": "verisure", "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", - "requirements": ["jsonpath==0.82", "vsure==1.6.1"], + "requirements": ["jsonpath==0.82", "vsure==1.7.2"], "codeowners": ["@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index d21c11fb466ead..66c8bef2101516 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2269,7 +2269,7 @@ volkszaehler==0.2.1 volvooncall==0.8.12 # homeassistant.components.verisure -vsure==1.6.1 +vsure==1.7.2 # homeassistant.components.vasttrafik vtjp==0.1.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d0febd473261ed..38ed8b427b686f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1145,7 +1145,7 @@ uvcclient==0.11.0 vilfo-api-client==0.3.2 # homeassistant.components.verisure -vsure==1.6.1 +vsure==1.7.2 # homeassistant.components.vultr vultr==0.1.2 From f99c27c6d4159f3b27852bb7af8e024d959b7091 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Feb 2021 11:09:45 +0100 Subject: [PATCH 0268/1818] Remove unneeded from_state from device triggers (#45152) --- .../components/alarm_control_panel/device_trigger.py | 4 ---- homeassistant/components/binary_sensor/device_trigger.py | 3 --- homeassistant/components/fan/device_trigger.py | 3 --- homeassistant/components/lock/device_trigger.py | 3 --- homeassistant/components/vacuum/device_trigger.py | 5 +---- .../device_trigger/integration/device_trigger.py | 3 --- .../alarm_control_panel/test_device_trigger.py | 9 +++------ 7 files changed, 4 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index bb5d82c52b177c..5669340c2ce5f2 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -132,14 +132,12 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Attach a trigger.""" config = TRIGGER_SCHEMA(config) - from_state = None if config[CONF_TYPE] == "triggered": to_state = STATE_ALARM_TRIGGERED elif config[CONF_TYPE] == "disarmed": to_state = STATE_ALARM_DISARMED elif config[CONF_TYPE] == "arming": - from_state = STATE_ALARM_DISARMED to_state = STATE_ALARM_ARMING elif config[CONF_TYPE] == "armed_home": to_state = STATE_ALARM_ARMED_HOME @@ -153,8 +151,6 @@ async def async_attach_trigger( CONF_ENTITY_ID: config[CONF_ENTITY_ID], state_trigger.CONF_TO: to_state, } - if from_state: - state_config[state_trigger.CONF_FROM] = from_state state_config = state_trigger.TRIGGER_SCHEMA(state_config) return await state_trigger.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" diff --git a/homeassistant/components/binary_sensor/device_trigger.py b/homeassistant/components/binary_sensor/device_trigger.py index f7f0c53a6988a8..b87a761a7a1138 100644 --- a/homeassistant/components/binary_sensor/device_trigger.py +++ b/homeassistant/components/binary_sensor/device_trigger.py @@ -190,16 +190,13 @@ async def async_attach_trigger(hass, config, action, automation_info): """Listen for state changes based on configuration.""" trigger_type = config[CONF_TYPE] if trigger_type in TURNED_ON: - from_state = "off" to_state = "on" else: - from_state = "on" to_state = "off" state_config = { state_trigger.CONF_PLATFORM: "state", state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_trigger.CONF_FROM: from_state, state_trigger.CONF_TO: to_state, } if CONF_FOR in config: diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index c78ebcfffe4cb8..95f4b429a243d4 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -74,16 +74,13 @@ async def async_attach_trigger( config = TRIGGER_SCHEMA(config) if config[CONF_TYPE] == "turned_on": - from_state = STATE_OFF to_state = STATE_ON else: - from_state = STATE_ON to_state = STATE_OFF state_config = { state_trigger.CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_trigger.CONF_FROM: from_state, state_trigger.CONF_TO: to_state, } state_config = state_trigger.TRIGGER_SCHEMA(state_config) diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 091811446b575c..05d5041ca65001 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -74,16 +74,13 @@ async def async_attach_trigger( config = TRIGGER_SCHEMA(config) if config[CONF_TYPE] == "locked": - from_state = STATE_UNLOCKED to_state = STATE_LOCKED else: - from_state = STATE_LOCKED to_state = STATE_UNLOCKED state_config = { CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_trigger.CONF_FROM: from_state, state_trigger.CONF_TO: to_state, } state_config = state_trigger.TRIGGER_SCHEMA(state_config) diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 29fc5628b22794..21a2ae5e8c2b4a 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -17,7 +17,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.helpers.typing import ConfigType -from . import DOMAIN, STATE_CLEANING, STATE_DOCKED, STATES +from . import DOMAIN, STATE_CLEANING, STATE_DOCKED TRIGGER_TYPES = {"cleaning", "docked"} @@ -71,16 +71,13 @@ async def async_attach_trigger( config = TRIGGER_SCHEMA(config) if config[CONF_TYPE] == "cleaning": - from_state = [state for state in STATES if state != STATE_CLEANING] to_state = STATE_CLEANING else: - from_state = [state for state in STATES if state != STATE_DOCKED] to_state = STATE_DOCKED state_config = { CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state_trigger.CONF_FROM: from_state, state_trigger.CONF_TO: to_state, } state_config = state_trigger.TRIGGER_SCHEMA(state_config) diff --git a/script/scaffold/templates/device_trigger/integration/device_trigger.py b/script/scaffold/templates/device_trigger/integration/device_trigger.py index 7709813957ed4d..2fa59d4eac8294 100644 --- a/script/scaffold/templates/device_trigger/integration/device_trigger.py +++ b/script/scaffold/templates/device_trigger/integration/device_trigger.py @@ -84,16 +84,13 @@ async def async_attach_trigger( # Use the existing state or event triggers from the automation integration. if config[CONF_TYPE] == "turned_on": - from_state = STATE_OFF to_state = STATE_ON else: - from_state = STATE_ON to_state = STATE_OFF state_config = { state.CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state.CONF_FROM: from_state, state.CONF_TO: to_state, } state_config = state.TRIGGER_SCHEMA(state_config) diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 82432bc37aba1f..56316026c9a0bd 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -230,31 +230,28 @@ async def test_if_fires_on_state_change(hass, calls): ) # Fake that the entity is armed home. - hass.states.async_set("alarm_control_panel.entity", STATE_ALARM_PENDING) hass.states.async_set("alarm_control_panel.entity", STATE_ALARM_ARMED_HOME) await hass.async_block_till_done() assert len(calls) == 3 assert ( calls[2].data["some"] - == "armed_home - device - alarm_control_panel.entity - pending - armed_home - None" + == "armed_home - device - alarm_control_panel.entity - disarmed - armed_home - None" ) # Fake that the entity is armed away. - hass.states.async_set("alarm_control_panel.entity", STATE_ALARM_PENDING) hass.states.async_set("alarm_control_panel.entity", STATE_ALARM_ARMED_AWAY) await hass.async_block_till_done() assert len(calls) == 4 assert ( calls[3].data["some"] - == "armed_away - device - alarm_control_panel.entity - pending - armed_away - None" + == "armed_away - device - alarm_control_panel.entity - armed_home - armed_away - None" ) # Fake that the entity is armed night. - hass.states.async_set("alarm_control_panel.entity", STATE_ALARM_PENDING) hass.states.async_set("alarm_control_panel.entity", STATE_ALARM_ARMED_NIGHT) await hass.async_block_till_done() assert len(calls) == 5 assert ( calls[4].data["some"] - == "armed_night - device - alarm_control_panel.entity - pending - armed_night - None" + == "armed_night - device - alarm_control_panel.entity - armed_away - armed_night - None" ) From e7ca0ff71a96066d95bbd6f2c49a0f3849bc2902 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 8 Feb 2021 11:23:50 +0100 Subject: [PATCH 0269/1818] Enable KNX auto_reconnect for auto-discovered connections (#46178) --- homeassistant/components/knx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 7dbeb513e091f1..1492e5df7b7f6c 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -290,7 +290,7 @@ def connection_config(self): if CONF_KNX_ROUTING in self.config[DOMAIN]: return self.connection_config_routing() # config from xknx.yaml always has priority later on - return ConnectionConfig() + return ConnectionConfig(auto_reconnect=True) def connection_config_routing(self): """Return the connection_config if routing is configured.""" From 9b0955b67e18dc15da2d4d2c4916792180e6214f Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 05:26:57 -0500 Subject: [PATCH 0270/1818] Use core constants for flux (#46201) --- homeassistant/components/flux/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py index 4d45f217a59832..ab0d296928f963 100644 --- a/homeassistant/components/flux/switch.py +++ b/homeassistant/components/flux/switch.py @@ -22,6 +22,7 @@ from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.const import ( ATTR_ENTITY_ID, + CONF_BRIGHTNESS, CONF_LIGHTS, CONF_MODE, CONF_NAME, @@ -49,7 +50,6 @@ CONF_START_CT = "start_colortemp" CONF_SUNSET_CT = "sunset_colortemp" CONF_STOP_CT = "stop_colortemp" -CONF_BRIGHTNESS = "brightness" CONF_DISABLE_BRIGHTNESS_ADJUST = "disable_brightness_adjust" CONF_INTERVAL = "interval" From 5faf463205a0d70e4b5689b2b87d223c51995a5a Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 05:36:45 -0500 Subject: [PATCH 0271/1818] Use core constants for frontend component (#46203) --- homeassistant/components/frontend/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index cdf25d22fe8611..bb503ee86733df 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -14,7 +14,7 @@ from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView from homeassistant.config import async_hass_config_yaml -from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED +from homeassistant.const import CONF_MODE, CONF_NAME, EVENT_THEMES_UPDATED from homeassistant.core import callback from homeassistant.helpers import service import homeassistant.helpers.config_validation as cv @@ -113,7 +113,6 @@ SERVICE_SET_THEME = "set_theme" SERVICE_RELOAD_THEMES = "reload_themes" -CONF_MODE = "mode" class Panel: From 87c36d6b6b8e6d68a1777eeaf97588ff696f9fb4 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 05:36:59 -0500 Subject: [PATCH 0272/1818] Use core constants for google_assistant (#46204) --- homeassistant/components/google_assistant/__init__.py | 3 +-- homeassistant/components/google_assistant/trait.py | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index 8f4ee3b51c4b34..00c09242517e8c 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -5,7 +5,7 @@ import voluptuous as vol # Typing imports -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_API_KEY, CONF_NAME from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import config_validation as cv @@ -35,7 +35,6 @@ _LOGGER = logging.getLogger(__name__) CONF_ALLOW_UNLOCK = "allow_unlock" -CONF_API_KEY = "api_key" ENTITY_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index f2a2274d8a897b..9e0da39b58a266 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1938,12 +1938,10 @@ def sync_attributes(self): def query_attributes(self): """Return the attributes of this trait for this entity.""" - return {} async def execute(self, command, data, params, challenge): """Execute a media command.""" - service_attrs = {ATTR_ENTITY_ID: self.state.entity_id} if command == COMMAND_MEDIA_SEEK_RELATIVE: From a23e05d1f68d70ac2dda9a91c6ef30f538b3952b Mon Sep 17 00:00:00 2001 From: Hmmbob <33529490+hmmbob@users.noreply.github.com> Date: Mon, 8 Feb 2021 11:43:30 +0100 Subject: [PATCH 0273/1818] Fix Google translate TTS by bumping gTTS from 2.2.1 to 2.2.2 (#46110) --- homeassistant/components/google_translate/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google_translate/manifest.json b/homeassistant/components/google_translate/manifest.json index c5b3edc879869f..64d19bed277228 100644 --- a/homeassistant/components/google_translate/manifest.json +++ b/homeassistant/components/google_translate/manifest.json @@ -2,6 +2,6 @@ "domain": "google_translate", "name": "Google Translate Text-to-Speech", "documentation": "https://www.home-assistant.io/integrations/google_translate", - "requirements": ["gTTS==2.2.1"], + "requirements": ["gTTS==2.2.2"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 66c8bef2101516..1cfd894c9adfc3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -622,7 +622,7 @@ freesms==0.1.2 fritzconnection==1.4.0 # homeassistant.components.google_translate -gTTS==2.2.1 +gTTS==2.2.2 # homeassistant.components.garmin_connect garminconnect==0.1.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 38ed8b427b686f..befb0187c0c30b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -319,7 +319,7 @@ freebox-api==0.0.9 fritzconnection==1.4.0 # homeassistant.components.google_translate -gTTS==2.2.1 +gTTS==2.2.2 # homeassistant.components.garmin_connect garminconnect==0.1.16 From 5a4e1eeb0e87b86cc1fdea367e4f7356b1867e58 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 8 Feb 2021 11:46:58 +0100 Subject: [PATCH 0274/1818] Upgrade praw to 7.1.4 (#46202) --- homeassistant/components/reddit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reddit/manifest.json b/homeassistant/components/reddit/manifest.json index d270f994159d19..252052ac5c2211 100644 --- a/homeassistant/components/reddit/manifest.json +++ b/homeassistant/components/reddit/manifest.json @@ -2,6 +2,6 @@ "domain": "reddit", "name": "Reddit", "documentation": "https://www.home-assistant.io/integrations/reddit", - "requirements": ["praw==7.1.3"], + "requirements": ["praw==7.1.4"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 1cfd894c9adfc3..04dbcdf9d1ac67 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1159,7 +1159,7 @@ pmsensor==0.4 poolsense==0.0.8 # homeassistant.components.reddit -praw==7.1.3 +praw==7.1.4 # homeassistant.components.islamic_prayer_times prayer_times_calculator==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index befb0187c0c30b..548d6ba1117cff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -596,7 +596,7 @@ pmsensor==0.4 poolsense==0.0.8 # homeassistant.components.reddit -praw==7.1.3 +praw==7.1.4 # homeassistant.components.islamic_prayer_times prayer_times_calculator==0.0.3 From 54dce1c50545e49fea8896c9adb8f1156f5917c4 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 05:47:30 -0500 Subject: [PATCH 0275/1818] Use core constants for fleetgo (#46200) --- homeassistant/components/fleetgo/device_tracker.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/fleetgo/device_tracker.py b/homeassistant/components/fleetgo/device_tracker.py index d46bbc9c85bc91..1f4c0d0ddfcb58 100644 --- a/homeassistant/components/fleetgo/device_tracker.py +++ b/homeassistant/components/fleetgo/device_tracker.py @@ -9,6 +9,7 @@ from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, + CONF_INCLUDE, CONF_PASSWORD, CONF_USERNAME, ) @@ -17,8 +18,6 @@ _LOGGER = logging.getLogger(__name__) -CONF_INCLUDE = "include" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_USERNAME): cv.string, @@ -44,7 +43,6 @@ class FleetGoDeviceScanner: def __init__(self, config, see): """Initialize FleetGoDeviceScanner.""" - self._include = config.get(CONF_INCLUDE) self._see = see From 82607977efaa385e6097509ada5a1153f95214b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 8 Feb 2021 12:59:46 +0200 Subject: [PATCH 0276/1818] Various type hint improvements (#46144) --- homeassistant/config.py | 5 +++-- homeassistant/helpers/area_registry.py | 4 +++- homeassistant/helpers/discovery.py | 13 ++++++++++--- homeassistant/helpers/entity_component.py | 4 +++- homeassistant/helpers/reload.py | 8 ++++---- homeassistant/helpers/script.py | 2 +- homeassistant/helpers/service.py | 8 ++++++-- homeassistant/helpers/update_coordinator.py | 6 ++++-- 8 files changed, 34 insertions(+), 16 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 2da9b0331c97f0..5cd4a0700e5f88 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -51,6 +51,7 @@ from homeassistant.helpers import config_per_platform, extract_domain_configs import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_values import EntityValues +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import Integration, IntegrationNotFound from homeassistant.requirements import ( RequirementsNotFound, @@ -734,8 +735,8 @@ async def merge_packages_config( async def async_process_component_config( - hass: HomeAssistant, config: Dict, integration: Integration -) -> Optional[Dict]: + hass: HomeAssistant, config: ConfigType, integration: Integration +) -> Optional[ConfigType]: """Check component configuration and return processed configuration. Returns None on error. diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 1a919996f86f0f..bdd231686e22ee 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -11,6 +11,8 @@ from .typing import HomeAssistantType +# mypy: disallow-any-generics + DATA_REGISTRY = "area_registry" EVENT_AREA_REGISTRY_UPDATED = "area_registry_updated" STORAGE_KEY = "core.area_registry" @@ -25,7 +27,7 @@ class AreaEntry: name: str = attr.ib() id: Optional[str] = attr.ib(default=None) - def generate_id(self, existing_ids: Container) -> None: + def generate_id(self, existing_ids: Container[str]) -> None: """Initialize ID.""" suggestion = suggestion_base = slugify(self.name) tries = 1 diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index acde8d73a50be9..0770e6798f19c1 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -9,6 +9,7 @@ from homeassistant import core, setup from homeassistant.const import ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED +from homeassistant.core import CALLBACK_TYPE from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe @@ -16,10 +17,14 @@ EVENT_LOAD_PLATFORM = "load_platform.{}" ATTR_PLATFORM = "platform" +# mypy: disallow-any-generics + @bind_hass def listen( - hass: core.HomeAssistant, service: Union[str, Collection[str]], callback: Callable + hass: core.HomeAssistant, + service: Union[str, Collection[str]], + callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. @@ -31,7 +36,9 @@ def listen( @core.callback @bind_hass def async_listen( - hass: core.HomeAssistant, service: Union[str, Collection[str]], callback: Callable + hass: core.HomeAssistant, + service: Union[str, Collection[str]], + callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. @@ -94,7 +101,7 @@ async def async_discover( @bind_hass def listen_platform( - hass: core.HomeAssistant, component: str, callback: Callable + hass: core.HomeAssistant, component: str, callback: CALLBACK_TYPE ) -> None: """Register a platform loader listener.""" run_callback_threadsafe( diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 0f1f04e3aec10e..6fb8696d8450a1 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -272,7 +272,9 @@ async def async_remove_entity(self, entity_id: str) -> None: if found: await found.async_remove_entity(entity_id) - async def async_prepare_reload(self, *, skip_reset: bool = False) -> Optional[dict]: + async def async_prepare_reload( + self, *, skip_reset: bool = False + ) -> Optional[ConfigType]: """Prepare reloading this entity component. This method must be run in the event loop. diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index e596027b7e1658..8ff454eab6fe47 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -2,7 +2,7 @@ import asyncio import logging -from typing import Any, Dict, Iterable, List, Optional +from typing import Dict, Iterable, List, Optional from homeassistant import config as conf_util from homeassistant.const import SERVICE_RELOAD @@ -10,7 +10,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform from homeassistant.helpers.entity_platform import EntityPlatform, async_get_platforms -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component @@ -49,7 +49,7 @@ async def _resetup_platform( hass: HomeAssistantType, integration_name: str, integration_platform: str, - unprocessed_conf: Dict, + unprocessed_conf: ConfigType, ) -> None: """Resetup a platform.""" integration = await async_get_integration(hass, integration_platform) @@ -129,7 +129,7 @@ async def _async_reconfig_platform( async def async_integration_yaml_config( hass: HomeAssistantType, integration_name: str -) -> Optional[Dict[Any, Any]]: +) -> Optional[ConfigType]: """Fetch the latest yaml configuration for an integration.""" integration = await async_get_integration(hass, integration_name) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 8706f765b50579..56accf9cf4953d 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -779,7 +779,7 @@ async def _async_stop_scripts_at_shutdown(hass, event): _VarsType = Union[Dict[str, Any], MappingProxyType] -def _referenced_extract_ids(data: Dict, key: str, found: Set[str]) -> None: +def _referenced_extract_ids(data: Dict[str, Any], key: str, found: Set[str]) -> None: """Extract referenced IDs.""" if not data: return diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index c95f942c6dc3d3..c83fa4a7763fff 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -669,10 +669,14 @@ async def admin_handler(call: ha.ServiceCall) -> None: @bind_hass @ha.callback -def verify_domain_control(hass: HomeAssistantType, domain: str) -> Callable: +def verify_domain_control( + hass: HomeAssistantType, domain: str +) -> Callable[[Callable[[ha.ServiceCall], Any]], Callable[[ha.ServiceCall], Any]]: """Ensure permission to access any entity under domain in service call.""" - def decorator(service_handler: Callable[[ha.ServiceCall], Any]) -> Callable: + def decorator( + service_handler: Callable[[ha.ServiceCall], Any] + ) -> Callable[[ha.ServiceCall], Any]: """Decorate.""" if not asyncio.iscoroutinefunction(service_handler): raise HomeAssistantError("Can only decorate async functions.") diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 8df2c57b1e7f74..b2424a06927d15 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta import logging from time import monotonic -from typing import Awaitable, Callable, Generic, List, Optional, TypeVar +from typing import Any, Awaitable, Callable, Generic, List, Optional, TypeVar import urllib.error import aiohttp @@ -21,6 +21,8 @@ T = TypeVar("T") +# mypy: disallow-any-generics + class UpdateFailed(Exception): """Raised when an update has failed.""" @@ -231,7 +233,7 @@ def _async_stop_refresh(self, _: Event) -> None: class CoordinatorEntity(entity.Entity): """A class for entities using DataUpdateCoordinator.""" - def __init__(self, coordinator: DataUpdateCoordinator) -> None: + def __init__(self, coordinator: DataUpdateCoordinator[Any]) -> None: """Create the entity with a DataUpdateCoordinator.""" self.coordinator = coordinator From 92e5bf978660c02aa79e3a3bda9431564d6c300d Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 06:24:48 -0500 Subject: [PATCH 0277/1818] Use core constants for google (#46210) --- homeassistant/components/google/__init__.py | 13 ++++++++----- homeassistant/components/google/calendar.py | 5 +---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 78ea1616f99f03..b46d48848daaee 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -15,7 +15,14 @@ from voluptuous.error import Error as VoluptuousError import yaml -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_DEVICE_ID, + CONF_ENTITIES, + CONF_NAME, + CONF_OFFSET, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id @@ -30,12 +37,8 @@ CONF_TRACK_NEW = "track_new_calendar" CONF_CAL_ID = "cal_id" -CONF_DEVICE_ID = "device_id" -CONF_NAME = "name" -CONF_ENTITIES = "entities" CONF_TRACK = "track" CONF_SEARCH = "search" -CONF_OFFSET = "offset" CONF_IGNORE_AVAILABILITY = "ignore_availability" CONF_MAX_RESULTS = "max_results" diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 6448e035171d80..2fcde78354bf69 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -11,17 +11,14 @@ calculate_offset, is_offset_reached, ) +from homeassistant.const import CONF_DEVICE_ID, CONF_ENTITIES, CONF_NAME, CONF_OFFSET from homeassistant.helpers.entity import generate_entity_id from homeassistant.util import Throttle, dt from . import ( CONF_CAL_ID, - CONF_DEVICE_ID, - CONF_ENTITIES, CONF_IGNORE_AVAILABILITY, CONF_MAX_RESULTS, - CONF_NAME, - CONF_OFFSET, CONF_SEARCH, CONF_TRACK, DEFAULT_CONF_OFFSET, From 9d9c4b47ee308ea0b770255f4d2b8b09be6c18ae Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Feb 2021 13:21:31 +0100 Subject: [PATCH 0278/1818] Pass variables to numeric state trigger templates (#46209) --- .../homeassistant/triggers/numeric_state.py | 7 +- .../triggers/test_numeric_state.py | 90 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 55e875c90de15d..4f406de23caacb 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -78,12 +78,16 @@ async def async_attach_trigger( attribute = config.get(CONF_ATTRIBUTE) job = HassJob(action) + _variables = {} + if automation_info: + _variables = automation_info.get("variables") or {} + if value_template is not None: value_template.hass = hass def variables(entity_id): """Return a dict with trigger variables.""" - return { + trigger_info = { "trigger": { "platform": "numeric_state", "entity_id": entity_id, @@ -92,6 +96,7 @@ def variables(entity_id): "attribute": attribute, } } + return {**_variables, **trigger_info} @callback def check_numeric_state(entity_id, from_s, to_s): diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 979c5bad5d7e9f..e58bccb0bccae1 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -1647,3 +1647,93 @@ async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() assert len(calls) == 1 + + +@pytest.mark.parametrize( + "above, below", + ((8, 12),), +) +async def test_variables_priority(hass, calls, above, below): + """Test an externally defined trigger variable is overridden.""" + hass.states.async_set("test.entity_1", 0) + hass.states.async_set("test.entity_2", 0) + await hass.async_block_till_done() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"trigger": "illegal"}, + "trigger": { + "platform": "numeric_state", + "entity_id": ["test.entity_1", "test.entity_2"], + "above": above, + "below": below, + "for": '{{ 5 if trigger.entity_id == "test.entity_1"' + " else 10 }}", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "{{ trigger.entity_id }} - {{ trigger.for }}" + }, + }, + } + }, + ) + await hass.async_block_till_done() + + utcnow = dt_util.utcnow() + with patch("homeassistant.util.dt.utcnow") as mock_utcnow: + mock_utcnow.return_value = utcnow + hass.states.async_set("test.entity_1", 9) + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set("test.entity_2", 9) + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set("test.entity_2", 15) + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set("test.entity_2", 9) + await hass.async_block_till_done() + assert len(calls) == 0 + mock_utcnow.return_value += timedelta(seconds=3) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1 - 0:00:05" + + +@pytest.mark.parametrize("multiplier", (1, 5)) +async def test_template_variable(hass, calls, multiplier): + """Test template variable.""" + hass.states.async_set("test.entity", "entity", {"test_attribute": [11, 15, 11]}) + await hass.async_block_till_done() + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"multiplier": multiplier}, + "trigger": { + "platform": "numeric_state", + "entity_id": "test.entity", + "value_template": "{{ state.attributes.test_attribute[2] * multiplier}}", + "below": 10, + }, + "action": {"service": "test.automation"}, + } + }, + ) + # 3 is below 10 + hass.states.async_set("test.entity", "entity", {"test_attribute": [11, 15, 3]}) + await hass.async_block_till_done() + if multiplier * 3 < 10: + assert len(calls) == 1 + else: + assert len(calls) == 0 From 2744d64a3ed8c2c1337fd6d4cca87cb903f3a8f5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Feb 2021 13:22:01 +0100 Subject: [PATCH 0279/1818] Pass variables to state trigger templates (#46208) * Pass variables to state trigger templates * Remove non working test --- .../homeassistant/triggers/state.py | 7 +- .../homeassistant/triggers/test_state.py | 88 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 5dd7335f56e6fb..8a03905d98d197 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -85,6 +85,10 @@ async def async_attach_trigger( attribute = config.get(CONF_ATTRIBUTE) job = HassJob(action) + _variables = {} + if automation_info: + _variables = automation_info.get("variables") or {} + @callback def state_automation_listener(event: Event): """Listen for state changes and calls action.""" @@ -143,7 +147,7 @@ def call_action(): call_action() return - variables = { + trigger_info = { "trigger": { "platform": "state", "entity_id": entity, @@ -151,6 +155,7 @@ def call_action(): "to_state": to_s, } } + variables = {**_variables, **trigger_info} try: period[entity] = cv.positive_time_period( diff --git a/tests/components/homeassistant/triggers/test_state.py b/tests/components/homeassistant/triggers/test_state.py index dd98dbc429cbc3..2cf2081f018621 100644 --- a/tests/components/homeassistant/triggers/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -984,6 +984,33 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_change_with_for_template_4(hass, calls): + """Test for firing on change with for template.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"seconds": 5}, + "trigger": { + "platform": "state", + "entity_id": "test.entity", + "to": "world", + "for": {"seconds": "{{ seconds }}"}, + }, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.states.async_set("test.entity", "world") + await hass.async_block_till_done() + assert len(calls) == 0 + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_on_change_from_with_for(hass, calls): """Test for firing on change with from/for.""" assert await async_setup_component( @@ -1269,3 +1296,64 @@ async def test_attribute_if_fires_on_entity_change_with_both_filters_boolean( hass.states.async_set("test.entity", "bla", {"happening": True}) await hass.async_block_till_done() assert len(calls) == 1 + + +async def test_variables_priority(hass, calls): + """Test an externally defined trigger variable is overridden.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"trigger": "illegal"}, + "trigger": { + "platform": "state", + "entity_id": ["test.entity_1", "test.entity_2"], + "to": "world", + "for": '{{ 5 if trigger.entity_id == "test.entity_1"' + " else 10 }}", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": "{{ trigger.entity_id }} - {{ trigger.for }}" + }, + }, + } + }, + ) + await hass.async_block_till_done() + + utcnow = dt_util.utcnow() + with patch("homeassistant.core.dt_util.utcnow") as mock_utcnow: + mock_utcnow.return_value = utcnow + hass.states.async_set("test.entity_1", "world") + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set("test.entity_2", "world") + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set("test.entity_2", "hello") + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set("test.entity_2", "world") + await hass.async_block_till_done() + assert len(calls) == 0 + mock_utcnow.return_value += timedelta(seconds=3) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1 - 0:00:05" + + mock_utcnow.return_value += timedelta(seconds=3) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert len(calls) == 1 + mock_utcnow.return_value += timedelta(seconds=5) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2 - 0:00:10" From eaa2d371a7b5e36f5a55660c1ae1c2b41bf72e95 Mon Sep 17 00:00:00 2001 From: Matteo Agnoletto Date: Mon, 8 Feb 2021 14:03:26 +0100 Subject: [PATCH 0280/1818] Add select selector for blueprints (#45803) Co-authored-by: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/selector.py | 9 +++++++++ tests/helpers/test_selector.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index b48ffb6e96435a..68511a771d3ae7 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -176,3 +176,12 @@ class StringSelector(Selector): """Selector for a multi-line text string.""" CONFIG_SCHEMA = vol.Schema({vol.Optional("multiline", default=False): bool}) + + +@SELECTORS.register("select") +class SelectSelector(Selector): + """Selector for an single-choice input select.""" + + CONFIG_SCHEMA = vol.Schema( + {vol.Required("options"): vol.All([str], vol.Length(min=1))} + ) diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 2916d6167039d6..c43ed4097e0856 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -187,3 +187,26 @@ def test_object_selector_schema(schema): def test_text_selector_schema(schema): """Test text selector.""" selector.validate_selector({"text": schema}) + + +@pytest.mark.parametrize( + "schema", + ({"options": ["red", "green", "blue"]},), +) +def test_select_selector_schema(schema): + """Test select selector.""" + selector.validate_selector({"select": schema}) + + +@pytest.mark.parametrize( + "schema", + ( + {}, + {"options": {"hello": "World"}}, + {"options": []}, + ), +) +def test_select_selector_schema_error(schema): + """Test select selector.""" + with pytest.raises(vol.Invalid): + selector.validate_selector({"select": schema}) From 0780e52ca455b394e54a92b441a266d74a237266 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Feb 2021 14:06:27 +0100 Subject: [PATCH 0281/1818] Support templates in event triggers (#46207) * Support templates in event triggers * Don't validate trigger schemas twice --- .../homeassistant/triggers/event.py | 42 ++++++--- .../lutron_caseta/device_trigger.py | 20 ++--- .../components/shelly/device_trigger.py | 20 ++--- homeassistant/helpers/event.py | 2 +- .../homeassistant/triggers/test_event.py | 87 ++++++++++++++++++- 5 files changed, 135 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index b7ab081d266c15..7665ee1b4d7151 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -3,7 +3,7 @@ from homeassistant.const import CONF_PLATFORM from homeassistant.core import HassJob, callback -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, template # mypy: allow-untyped-defs @@ -14,9 +14,9 @@ TRIGGER_SCHEMA = vol.Schema( { vol.Required(CONF_PLATFORM): "event", - vol.Required(CONF_EVENT_TYPE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EVENT_DATA): dict, - vol.Optional(CONF_EVENT_CONTEXT): dict, + vol.Required(CONF_EVENT_TYPE): vol.All(cv.ensure_list, [cv.template]), + vol.Optional(CONF_EVENT_DATA): vol.All(dict, cv.template_complex), + vol.Optional(CONF_EVENT_CONTEXT): vol.All(dict, cv.template_complex), } ) @@ -32,25 +32,43 @@ async def async_attach_trigger( hass, config, action, automation_info, *, platform_type="event" ): """Listen for events based on configuration.""" - event_types = config.get(CONF_EVENT_TYPE) + variables = None + if automation_info: + variables = automation_info.get("variables") + + template.attach(hass, config[CONF_EVENT_TYPE]) + event_types = template.render_complex( + config[CONF_EVENT_TYPE], variables, limited=True + ) removes = [] event_data_schema = None - if config.get(CONF_EVENT_DATA): + if CONF_EVENT_DATA in config: + # Render the schema input + template.attach(hass, config[CONF_EVENT_DATA]) + event_data = {} + event_data.update( + template.render_complex(config[CONF_EVENT_DATA], variables, limited=True) + ) + # Build the schema event_data_schema = vol.Schema( - { - vol.Required(key): value - for key, value in config.get(CONF_EVENT_DATA).items() - }, + {vol.Required(key): value for key, value in event_data.items()}, extra=vol.ALLOW_EXTRA, ) event_context_schema = None - if config.get(CONF_EVENT_CONTEXT): + if CONF_EVENT_CONTEXT in config: + # Render the schema input + template.attach(hass, config[CONF_EVENT_CONTEXT]) + event_context = {} + event_context.update( + template.render_complex(config[CONF_EVENT_CONTEXT], variables, limited=True) + ) + # Build the schema event_context_schema = vol.Schema( { vol.Required(key): _schema_value(value) - for key, value in config.get(CONF_EVENT_CONTEXT).items() + for key, value in event_context.items() }, extra=vol.ALLOW_EXTRA, ) diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index 402db7286afb29..80d147191e6cc8 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -265,17 +265,15 @@ async def async_attach_trigger( schema = DEVICE_TYPE_SCHEMA_MAP.get(device["type"]) valid_buttons = DEVICE_TYPE_SUBTYPE_MAP.get(device["type"]) config = schema(config) - event_config = event_trigger.TRIGGER_SCHEMA( - { - event_trigger.CONF_PLATFORM: CONF_EVENT, - event_trigger.CONF_EVENT_TYPE: LUTRON_CASETA_BUTTON_EVENT, - event_trigger.CONF_EVENT_DATA: { - ATTR_SERIAL: device["serial"], - ATTR_BUTTON_NUMBER: valid_buttons[config[CONF_SUBTYPE]], - ATTR_ACTION: config[CONF_TYPE], - }, - } - ) + event_config = { + event_trigger.CONF_PLATFORM: CONF_EVENT, + event_trigger.CONF_EVENT_TYPE: LUTRON_CASETA_BUTTON_EVENT, + event_trigger.CONF_EVENT_DATA: { + ATTR_SERIAL: device["serial"], + ATTR_BUTTON_NUMBER: valid_buttons[config[CONF_SUBTYPE]], + ATTR_ACTION: config[CONF_TYPE], + }, + } event_config = event_trigger.TRIGGER_SCHEMA(event_config) return await event_trigger.async_attach_trigger( hass, event_config, action, automation_info, platform_type="device" diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index f6cdfaee19f2be..9d4851c92a4eb9 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -93,17 +93,15 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Attach a trigger.""" config = TRIGGER_SCHEMA(config) - event_config = event_trigger.TRIGGER_SCHEMA( - { - event_trigger.CONF_PLATFORM: CONF_EVENT, - event_trigger.CONF_EVENT_TYPE: EVENT_SHELLY_CLICK, - event_trigger.CONF_EVENT_DATA: { - ATTR_DEVICE_ID: config[CONF_DEVICE_ID], - ATTR_CHANNEL: INPUTS_EVENTS_SUBTYPES[config[CONF_SUBTYPE]], - ATTR_CLICK_TYPE: config[CONF_TYPE], - }, - } - ) + event_config = { + event_trigger.CONF_PLATFORM: CONF_EVENT, + event_trigger.CONF_EVENT_TYPE: EVENT_SHELLY_CLICK, + event_trigger.CONF_EVENT_DATA: { + ATTR_DEVICE_ID: config[CONF_DEVICE_ID], + ATTR_CHANNEL: INPUTS_EVENTS_SUBTYPES[config[CONF_SUBTYPE]], + ATTR_CLICK_TYPE: config[CONF_TYPE], + }, + } event_config = event_trigger.TRIGGER_SCHEMA(event_config) return await event_trigger.async_attach_trigger( hass, event_config, action, automation_info, platform_type="device" diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index da7f6cd52e8907..44cbd89fde7f06 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -259,7 +259,7 @@ def _async_state_change_dispatcher(event: Event) -> None: hass.async_run_hass_job(job, event) except Exception: # pylint: disable=broad-except _LOGGER.exception( - "Error while processing state changed for %s", entity_id + "Error while processing state change for %s", entity_id ) hass.data[TRACK_STATE_CHANGE_LISTENER] = hass.bus.async_listen( diff --git a/tests/components/homeassistant/triggers/test_event.py b/tests/components/homeassistant/triggers/test_event.py index 8fedaac381594d..f1ff35640656d3 100644 --- a/tests/components/homeassistant/triggers/test_event.py +++ b/tests/components/homeassistant/triggers/test_event.py @@ -17,7 +17,7 @@ def calls(hass): @pytest.fixture def context_with_user(): - """Track calls to a mock service.""" + """Create a context with default user_id.""" return Context(user_id="test_user_id") @@ -59,6 +59,39 @@ async def test_if_fires_on_event(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_templated_event(hass, calls): + """Test the firing of events.""" + context = Context() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"event_type": "test_event"}, + "trigger": {"platform": "event", "event_type": "{{event_type}}"}, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.bus.async_fire("test_event", context=context) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].context.parent_id == context.id + + await hass.services.async_call( + automation.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_MATCH_ALL}, + blocking=True, + ) + + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_on_multiple_events(hass, calls): """Test the firing of events.""" context = Context() @@ -161,6 +194,58 @@ async def test_if_fires_on_event_with_data_and_context(hass, calls, context_with assert len(calls) == 1 +async def test_if_fires_on_event_with_templated_data_and_context( + hass, calls, context_with_user +): + """Test the firing of events with templated data and context.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": { + "attr_1_val": "milk", + "attr_2_val": "beer", + "user_id": context_with_user.user_id, + }, + "trigger": { + "platform": "event", + "event_type": "test_event", + "event_data": { + "attr_1": "{{attr_1_val}}", + "attr_2": "{{attr_2_val}}", + }, + "context": {"user_id": "{{user_id}}"}, + }, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.bus.async_fire( + "test_event", + {"attr_1": "milk", "another": "value", "attr_2": "beer"}, + context=context_with_user, + ) + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.bus.async_fire( + "test_event", + {"attr_1": "milk", "another": "value"}, + context=context_with_user, + ) + await hass.async_block_till_done() + assert len(calls) == 1 # No new call + + hass.bus.async_fire( + "test_event", + {"attr_1": "milk", "another": "value", "attr_2": "beer"}, + ) + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_on_event_with_empty_data_and_context_config( hass, calls, context_with_user ): From 48002f47f4bc3b41d2b73ab0f69ca12015e79bb2 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 8 Feb 2021 14:33:57 +0100 Subject: [PATCH 0282/1818] Use caplog fixture for log capturing (#46214) --- tests/components/automation/test_init.py | 17 ++++--- .../triggers/test_numeric_state.py | 21 +++++---- tests/helpers/test_condition.py | 26 ++++++----- tests/helpers/test_script.py | 44 ++++++++++++------- 4 files changed, 65 insertions(+), 43 deletions(-) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 16d56c84cb01d5..3e498b52a08910 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,5 +1,6 @@ """The tests for the automation component.""" import asyncio +import logging from unittest.mock import Mock, patch import pytest @@ -152,7 +153,7 @@ async def test_two_triggers(hass, calls): assert len(calls) == 2 -async def test_trigger_service_ignoring_condition(hass, calls): +async def test_trigger_service_ignoring_condition(hass, caplog, calls): """Test triggers.""" assert await async_setup_component( hass, @@ -171,11 +172,15 @@ async def test_trigger_service_ignoring_condition(hass, calls): }, ) - with patch("homeassistant.components.automation.LOGGER.warning") as logwarn: - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - assert len(calls) == 0 - assert len(logwarn.mock_calls) == 1 + caplog.clear() + caplog.set_level(logging.WARNING) + + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert len(calls) == 0 + + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][1] == logging.WARNING await hass.services.async_call( "automation", "trigger", {"entity_id": "automation.test"}, blocking=True diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index e58bccb0bccae1..85dc68c770da61 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -1,5 +1,6 @@ """The tests for numeric state automation.""" from datetime import timedelta +import logging from unittest.mock import patch import pytest @@ -572,7 +573,7 @@ async def test_if_not_fires_if_entity_not_match(hass, calls, below): assert len(calls) == 0 -async def test_if_not_fires_and_warns_if_below_entity_unknown(hass, calls): +async def test_if_not_fires_and_warns_if_below_entity_unknown(hass, caplog, calls): """Test if warns with unknown below entity.""" assert await async_setup_component( hass, @@ -589,13 +590,15 @@ async def test_if_not_fires_and_warns_if_below_entity_unknown(hass, calls): }, ) - with patch( - "homeassistant.components.homeassistant.triggers.numeric_state._LOGGER.warning" - ) as logwarn: - hass.states.async_set("test.entity", 1) - await hass.async_block_till_done() - assert len(calls) == 0 - assert len(logwarn.mock_calls) == 1 + caplog.clear() + caplog.set_level(logging.WARNING) + + hass.states.async_set("test.entity", 1) + await hass.async_block_till_done() + assert len(calls) == 0 + + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][1] == logging.WARNING @pytest.mark.parametrize("below", (10, "input_number.value_10")) @@ -1203,7 +1206,7 @@ async def test_wait_template_with_trigger(hass, calls, above): hass.states.async_set("test.entity", "8") await hass.async_block_till_done() assert len(calls) == 1 - assert "numeric_state - test.entity - 12" == calls[0].data["some"] + assert calls[0].data["some"] == "numeric_state - test.entity - 12" @pytest.mark.parametrize( diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 5c388aa6db4a3c..cd4039f5262608 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,5 +1,5 @@ """Test the condition helper.""" -from logging import ERROR +from logging import ERROR, WARNING from unittest.mock import patch import pytest @@ -338,23 +338,25 @@ async def test_time_using_input_datetime(hass): assert not condition.time(hass, before="input_datetime.not_existing") -async def test_if_numeric_state_raises_on_unavailable(hass): +async def test_if_numeric_state_raises_on_unavailable(hass, caplog): """Test numeric_state raises on unavailable/unknown state.""" test = await condition.async_from_config( hass, {"condition": "numeric_state", "entity_id": "sensor.temperature", "below": 42}, ) - with patch("homeassistant.helpers.condition._LOGGER.warning") as logwarn: - hass.states.async_set("sensor.temperature", "unavailable") - with pytest.raises(ConditionError): - test(hass) - assert len(logwarn.mock_calls) == 0 - - hass.states.async_set("sensor.temperature", "unknown") - with pytest.raises(ConditionError): - test(hass) - assert len(logwarn.mock_calls) == 0 + caplog.clear() + caplog.set_level(WARNING) + + hass.states.async_set("sensor.temperature", "unavailable") + with pytest.raises(ConditionError): + test(hass) + assert len(caplog.record_tuples) == 0 + + hass.states.async_set("sensor.temperature", "unknown") + with pytest.raises(ConditionError): + test(hass) + assert len(caplog.record_tuples) == 0 async def test_state_multiple_entities(hass): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 003e903de14280..5cd9a9d2449ab3 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -990,7 +990,7 @@ async def async_attach_trigger_mock(*args, **kwargs): assert "something bad" in caplog.text -async def test_condition_warning(hass): +async def test_condition_warning(hass, caplog): """Test warning on condition.""" event = "test_event" events = async_capture_events(hass, event) @@ -1007,11 +1007,15 @@ async def test_condition_warning(hass): ) script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + caplog.clear() + caplog.set_level(logging.WARNING) + hass.states.async_set("test.entity", "string") - with patch("homeassistant.helpers.script._LOGGER.warning") as logwarn: - await script_obj.async_run(context=Context()) - await hass.async_block_till_done() - assert len(logwarn.mock_calls) == 1 + await script_obj.async_run(context=Context()) + await hass.async_block_till_done() + + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][1] == logging.WARNING assert len(events) == 1 @@ -1127,7 +1131,7 @@ async def test_repeat_count(hass): @pytest.mark.parametrize("condition", ["while", "until"]) -async def test_repeat_condition_warning(hass, condition): +async def test_repeat_condition_warning(hass, caplog, condition): """Test warning on repeat conditions.""" event = "test_event" events = async_capture_events(hass, event) @@ -1156,10 +1160,14 @@ async def test_repeat_condition_warning(hass, condition): # wait_started = async_watch_for_action(script_obj, "wait") hass.states.async_set("sensor.test", "1") - with patch("homeassistant.helpers.script._LOGGER.warning") as logwarn: - hass.async_create_task(script_obj.async_run(context=Context())) - await asyncio.wait_for(hass.async_block_till_done(), 1) - assert len(logwarn.mock_calls) == 1 + caplog.clear() + caplog.set_level(logging.WARNING) + + hass.async_create_task(script_obj.async_run(context=Context())) + await asyncio.wait_for(hass.async_block_till_done(), 1) + + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][1] == logging.WARNING assert len(events) == count @@ -1369,7 +1377,7 @@ async def test_repeat_nested(hass, variables, first_last, inside_x): } -async def test_choose_warning(hass): +async def test_choose_warning(hass, caplog): """Test warning on choose.""" event = "test_event" events = async_capture_events(hass, event) @@ -1404,11 +1412,15 @@ async def test_choose_warning(hass): hass.states.async_set("test.entity", "9") await hass.async_block_till_done() - with patch("homeassistant.helpers.script._LOGGER.warning") as logwarn: - await script_obj.async_run(context=Context()) - await hass.async_block_till_done() - print(logwarn.mock_calls) - assert len(logwarn.mock_calls) == 2 + caplog.clear() + caplog.set_level(logging.WARNING) + + await script_obj.async_run(context=Context()) + await hass.async_block_till_done() + + assert len(caplog.record_tuples) == 2 + assert caplog.record_tuples[0][1] == logging.WARNING + assert caplog.record_tuples[1][1] == logging.WARNING assert len(events) == 1 assert events[0].data["choice"] == "default" From 6f446cf627263fb9cb0d1a5be73f1986ce50dbc4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Feb 2021 14:44:46 +0100 Subject: [PATCH 0283/1818] Add my component (#46058) Co-authored-by: Franck Nijhof Co-authored-by: Martin Hjelmare --- CODEOWNERS | 1 + .../components/default_config/manifest.json | 1 + homeassistant/components/http/__init__.py | 7 ++++--- homeassistant/components/my/__init__.py | 12 ++++++++++++ homeassistant/components/my/manifest.json | 7 +++++++ tests/components/http/test_init.py | 5 ++++- tests/components/my/__init__.py | 1 + tests/components/my/test_init.py | 17 +++++++++++++++++ 8 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/my/__init__.py create mode 100644 homeassistant/components/my/manifest.json create mode 100644 tests/components/my/__init__.py create mode 100644 tests/components/my/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index 8785ce382cb6b1..7c45a66e166c77 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -287,6 +287,7 @@ homeassistant/components/motion_blinds/* @starkillerOG homeassistant/components/mpd/* @fabaff homeassistant/components/mqtt/* @home-assistant/core @emontnemery homeassistant/components/msteams/* @peroyvind +homeassistant/components/my/* @home-assistant/core homeassistant/components/myq/* @bdraco homeassistant/components/mysensors/* @MartinHjelmare @functionpointer homeassistant/components/mystrom/* @fabaff diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json index f8be3c9fe2aedc..0f4b940cc36811 100644 --- a/homeassistant/components/default_config/manifest.json +++ b/homeassistant/components/default_config/manifest.json @@ -18,6 +18,7 @@ "map", "media_source", "mobile_app", + "my", "person", "scene", "script", diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 7f70d49f6861e5..63c88427a5ba8c 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -58,8 +58,9 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_DEVELOPMENT = "0" -# To be able to load custom cards. -DEFAULT_CORS = "https://cast.home-assistant.io" +# Cast to be able to load custom cards. +# My to be able to check url and version info. +DEFAULT_CORS = ["https://cast.home-assistant.io", "https://my.home-assistant.io"] NO_LOGIN_ATTEMPT_THRESHOLD = -1 MAX_CLIENT_SIZE: int = 1024 ** 2 * 16 @@ -80,7 +81,7 @@ vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_PEER_CERTIFICATE): cv.isfile, vol.Optional(CONF_SSL_KEY): cv.isfile, - vol.Optional(CONF_CORS_ORIGINS, default=[DEFAULT_CORS]): vol.All( + vol.Optional(CONF_CORS_ORIGINS, default=DEFAULT_CORS): vol.All( cv.ensure_list, [cv.string] ), vol.Inclusive(CONF_USE_X_FORWARDED_FOR, "proxy"): cv.boolean, diff --git a/homeassistant/components/my/__init__.py b/homeassistant/components/my/__init__.py new file mode 100644 index 00000000000000..8cc725cb9a566b --- /dev/null +++ b/homeassistant/components/my/__init__.py @@ -0,0 +1,12 @@ +"""Support for my.home-assistant.io redirect service.""" + +DOMAIN = "my" +URL_PATH = "_my_redirect" + + +async def async_setup(hass, config): + """Register hidden _my_redirect panel.""" + hass.components.frontend.async_register_built_in_panel( + DOMAIN, frontend_url_path=URL_PATH + ) + return True diff --git a/homeassistant/components/my/manifest.json b/homeassistant/components/my/manifest.json new file mode 100644 index 00000000000000..3b9e253f353279 --- /dev/null +++ b/homeassistant/components/my/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "my", + "name": "My Home Assistant", + "documentation": "https://www.home-assistant.io/integrations/my", + "dependencies": ["frontend"], + "codeowners": ["@home-assistant/core"] +} diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 3dd587cd7a4d80..e7a3884c481cb0 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -242,7 +242,10 @@ async def test_cors_defaults(hass): assert await async_setup_component(hass, "http", {}) assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == ["https://cast.home-assistant.io"] + assert mock_setup.mock_calls[0][1][1] == [ + "https://cast.home-assistant.io", + "https://my.home-assistant.io", + ] async def test_storing_config(hass, aiohttp_client, aiohttp_unused_port): diff --git a/tests/components/my/__init__.py b/tests/components/my/__init__.py new file mode 100644 index 00000000000000..82953c8dac2c98 --- /dev/null +++ b/tests/components/my/__init__.py @@ -0,0 +1 @@ +"""Tests for the my component.""" diff --git a/tests/components/my/test_init.py b/tests/components/my/test_init.py new file mode 100644 index 00000000000000..86929271be9fa8 --- /dev/null +++ b/tests/components/my/test_init.py @@ -0,0 +1,17 @@ +"""Test the my init.""" + +from unittest import mock + +from homeassistant.components.my import URL_PATH +from homeassistant.setup import async_setup_component + + +async def test_setup(hass): + """Test setup.""" + with mock.patch( + "homeassistant.components.frontend.async_register_built_in_panel" + ) as mock_register_panel: + assert await async_setup_component(hass, "my", {"foo": "bar"}) + assert mock_register_panel.call_args == mock.call( + hass, "my", frontend_url_path=URL_PATH + ) From 568180632edfca977395d8a33f81ececa614250b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 8 Feb 2021 15:00:17 +0100 Subject: [PATCH 0284/1818] Fix sync oath2 scaffold template (#46219) --- .../config_flow_oauth2/integration/__init__.py | 2 +- .../templates/config_flow_oauth2/integration/api.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index 20b5a03206e848..c51061b57fed37 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) # If using a requests-based API lib - hass.data[DOMAIN][entry.entry_id] = api.ConfigEntryAuth(hass, entry, session) + hass.data[DOMAIN][entry.entry_id] = api.ConfigEntryAuth(hass, session) # If using an aiohttp-based API lib hass.data[DOMAIN][entry.entry_id] = api.AsyncConfigEntryAuth( diff --git a/script/scaffold/templates/config_flow_oauth2/integration/api.py b/script/scaffold/templates/config_flow_oauth2/integration/api.py index 710c76600fb4cc..50b54399579f64 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/api.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/api.py @@ -4,7 +4,7 @@ from aiohttp import ClientSession import my_pypi_package -from homeassistant import config_entries, core +from homeassistant import core from homeassistant.helpers import config_entry_oauth2_flow # TODO the following two API examples are based on our suggested best practices @@ -18,15 +18,11 @@ class ConfigEntryAuth(my_pypi_package.AbstractAuth): def __init__( self, hass: core.HomeAssistant, - config_entry: config_entries.ConfigEntry, - implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation, + oauth_session: config_entry_oauth2_flow.OAuth2Session, ): """Initialize NEW_NAME Auth.""" self.hass = hass - self.config_entry = config_entry - self.session = config_entry_oauth2_flow.OAuth2Session( - hass, config_entry, implementation - ) + self.session = oauth_session super().__init__(self.session.token) def refresh_tokens(self) -> str: From 48808978c422216250f8cfb0aa0fc6b91fe5884b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Feb 2021 15:05:11 +0100 Subject: [PATCH 0285/1818] Upgrade pre-commit to 2.10.1 (#46211) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 077c895293f5fa..4683c927085d70 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -9,7 +9,7 @@ coverage==5.4 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.800 -pre-commit==2.10.0 +pre-commit==2.10.1 pylint==2.6.0 astroid==2.4.2 pipdeptree==1.0.0 From 2811e39c5cdfb210275b04f489d187cc9b2168a6 Mon Sep 17 00:00:00 2001 From: Joeri <2417500+yurnih@users.noreply.github.com> Date: Mon, 8 Feb 2021 15:18:36 +0100 Subject: [PATCH 0286/1818] Add entity specific force_update for DSMR (#46111) --- homeassistant/components/dsmr/sensor.py | 97 ++++++++++++++++--------- tests/components/dsmr/test_sensor.py | 2 +- 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 78cd317bb3eb2c..897fcd4e77b175 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -80,35 +80,59 @@ async def async_setup_entry( dsmr_version = config[CONF_DSMR_VERSION] - # Define list of name,obis mappings to generate entities + # Define list of name,obis,force_update mappings to generate entities obis_mapping = [ - ["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE], - ["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY], - ["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF], - ["Energy Consumption (tarif 1)", obis_ref.ELECTRICITY_USED_TARIFF_1], - ["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2], - ["Energy Production (tarif 1)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1], - ["Energy Production (tarif 2)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_2], - ["Power Consumption Phase L1", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE], - ["Power Consumption Phase L2", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE], - ["Power Consumption Phase L3", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE], - ["Power Production Phase L1", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE], - ["Power Production Phase L2", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE], - ["Power Production Phase L3", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE], - ["Short Power Failure Count", obis_ref.SHORT_POWER_FAILURE_COUNT], - ["Long Power Failure Count", obis_ref.LONG_POWER_FAILURE_COUNT], - ["Voltage Sags Phase L1", obis_ref.VOLTAGE_SAG_L1_COUNT], - ["Voltage Sags Phase L2", obis_ref.VOLTAGE_SAG_L2_COUNT], - ["Voltage Sags Phase L3", obis_ref.VOLTAGE_SAG_L3_COUNT], - ["Voltage Swells Phase L1", obis_ref.VOLTAGE_SWELL_L1_COUNT], - ["Voltage Swells Phase L2", obis_ref.VOLTAGE_SWELL_L2_COUNT], - ["Voltage Swells Phase L3", obis_ref.VOLTAGE_SWELL_L3_COUNT], - ["Voltage Phase L1", obis_ref.INSTANTANEOUS_VOLTAGE_L1], - ["Voltage Phase L2", obis_ref.INSTANTANEOUS_VOLTAGE_L2], - ["Voltage Phase L3", obis_ref.INSTANTANEOUS_VOLTAGE_L3], - ["Current Phase L1", obis_ref.INSTANTANEOUS_CURRENT_L1], - ["Current Phase L2", obis_ref.INSTANTANEOUS_CURRENT_L2], - ["Current Phase L3", obis_ref.INSTANTANEOUS_CURRENT_L3], + ["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE, True], + ["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY, True], + ["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF, False], + ["Energy Consumption (tarif 1)", obis_ref.ELECTRICITY_USED_TARIFF_1, True], + ["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2, True], + ["Energy Production (tarif 1)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1, True], + ["Energy Production (tarif 2)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_2, True], + [ + "Power Consumption Phase L1", + obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE, + False, + ], + [ + "Power Consumption Phase L2", + obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE, + False, + ], + [ + "Power Consumption Phase L3", + obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE, + False, + ], + [ + "Power Production Phase L1", + obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE, + False, + ], + [ + "Power Production Phase L2", + obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE, + False, + ], + [ + "Power Production Phase L3", + obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE, + False, + ], + ["Short Power Failure Count", obis_ref.SHORT_POWER_FAILURE_COUNT, False], + ["Long Power Failure Count", obis_ref.LONG_POWER_FAILURE_COUNT, False], + ["Voltage Sags Phase L1", obis_ref.VOLTAGE_SAG_L1_COUNT, False], + ["Voltage Sags Phase L2", obis_ref.VOLTAGE_SAG_L2_COUNT, False], + ["Voltage Sags Phase L3", obis_ref.VOLTAGE_SAG_L3_COUNT, False], + ["Voltage Swells Phase L1", obis_ref.VOLTAGE_SWELL_L1_COUNT, False], + ["Voltage Swells Phase L2", obis_ref.VOLTAGE_SWELL_L2_COUNT, False], + ["Voltage Swells Phase L3", obis_ref.VOLTAGE_SWELL_L3_COUNT, False], + ["Voltage Phase L1", obis_ref.INSTANTANEOUS_VOLTAGE_L1, False], + ["Voltage Phase L2", obis_ref.INSTANTANEOUS_VOLTAGE_L2, False], + ["Voltage Phase L3", obis_ref.INSTANTANEOUS_VOLTAGE_L3, False], + ["Current Phase L1", obis_ref.INSTANTANEOUS_CURRENT_L1, False], + ["Current Phase L2", obis_ref.INSTANTANEOUS_CURRENT_L2, False], + ["Current Phase L3", obis_ref.INSTANTANEOUS_CURRENT_L3, False], ] if dsmr_version == "5L": @@ -117,22 +141,26 @@ async def async_setup_entry( [ "Energy Consumption (total)", obis_ref.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL, + True, ], [ "Energy Production (total)", obis_ref.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL, + True, ], ] ) else: obis_mapping.extend( - [["Energy Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL]] + [["Energy Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL, True]] ) # Generate device entities devices = [ - DSMREntity(name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config) - for name, obis in obis_mapping + DSMREntity( + name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config, force_update + ) + for name, obis, force_update in obis_mapping ] # Protocol version specific obis @@ -152,6 +180,7 @@ async def async_setup_entry( config[CONF_SERIAL_ID_GAS], gas_obis, config, + True, ), DerivativeDSMREntity( "Hourly Gas Consumption", @@ -159,6 +188,7 @@ async def async_setup_entry( config[CONF_SERIAL_ID_GAS], gas_obis, config, + False, ), ] @@ -257,7 +287,7 @@ async def connect_and_reconnect(): class DSMREntity(Entity): """Entity reading values from DSMR telegram.""" - def __init__(self, name, device_name, device_serial, obis, config): + def __init__(self, name, device_name, device_serial, obis, config, force_update): """Initialize entity.""" self._name = name self._obis = obis @@ -266,6 +296,7 @@ def __init__(self, name, device_name, device_serial, obis, config): self._device_name = device_name self._device_serial = device_serial + self._force_update = force_update self._unique_id = f"{device_serial}_{name}".replace(" ", "_") @callback @@ -341,7 +372,7 @@ def device_info(self) -> Dict[str, any]: @property def force_update(self): """Force update.""" - return True + return self._force_update @property def should_poll(self): diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index dde66c6bfb7f2a..31c4f2be8db608 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -183,7 +183,7 @@ async def test_derivative(): config = {"platform": "dsmr"} - entity = DerivativeDSMREntity("test", "test_device", "5678", "1.0.0", config) + entity = DerivativeDSMREntity("test", "test_device", "5678", "1.0.0", config, False) await entity.async_update() assert entity.state is None, "initial state not unknown" From b1ffe429cdda8613f5fa204642ac4f9a9d1ed319 Mon Sep 17 00:00:00 2001 From: Henco Appel Date: Mon, 8 Feb 2021 14:24:18 +0000 Subject: [PATCH 0287/1818] Fix BT Smarthub device tracker (#44813) --- .../components/bt_smarthub/device_tracker.py | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/bt_smarthub/device_tracker.py b/homeassistant/components/bt_smarthub/device_tracker.py index 383f724decd632..107eb5598d9d2c 100644 --- a/homeassistant/components/bt_smarthub/device_tracker.py +++ b/homeassistant/components/bt_smarthub/device_tracker.py @@ -1,4 +1,5 @@ """Support for BT Smart Hub (Sometimes referred to as BT Home Hub 6).""" +from collections import namedtuple import logging from btsmarthub_devicelist import BTSmartHub @@ -31,19 +32,30 @@ def get_scanner(hass, config): smarthub_client = BTSmartHub( router_ip=info[CONF_HOST], smarthub_model=info.get(CONF_SMARTHUB_MODEL) ) - scanner = BTSmartHubScanner(smarthub_client) - return scanner if scanner.success_init else None +def _create_device(data): + """Create new device from the dict.""" + ip_address = data.get("IPAddress") + mac = data.get("PhysAddress") + host = data.get("UserHostName") + status = data.get("Active") + name = data.get("name") + return _Device(ip_address, mac, host, status, name) + + +_Device = namedtuple("_Device", ["ip_address", "mac", "host", "status", "name"]) + + class BTSmartHubScanner(DeviceScanner): """This class queries a BT Smart Hub.""" def __init__(self, smarthub_client): """Initialise the scanner.""" self.smarthub = smarthub_client - self.last_results = {} + self.last_results = [] self.success_init = False # Test the router is accessible @@ -56,15 +68,15 @@ def __init__(self, smarthub_client): def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" self._update_info() - return [client["mac"] for client in self.last_results] + return [device.mac for device in self.last_results] def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" if not self.last_results: return None - for client in self.last_results: - if client["mac"] == device: - return client["host"] + for result_device in self.last_results: + if result_device.mac == device: + return result_device.name or result_device.host return None def _update_info(self): @@ -77,26 +89,10 @@ def _update_info(self): if not data: _LOGGER.warning("Error scanning devices") return - - clients = list(data.values()) - self.last_results = clients + self.last_results = data def get_bt_smarthub_data(self): """Retrieve data from BT Smart Hub and return parsed result.""" - # Request data from bt smarthub into a list of dicts. data = self.smarthub.get_devicelist(only_active_devices=True) - - # Renaming keys from parsed result. - devices = {} - for device in data: - try: - devices[device["UserHostName"]] = { - "ip": device["IPAddress"], - "mac": device["PhysAddress"], - "host": device["UserHostName"], - "status": device["Active"], - } - except KeyError: - pass - return devices + return [_create_device(d) for d in data if d.get("PhysAddress")] From 8f4ea3818d7f4ec843dcf7af9b97f30bd068171f Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Mon, 8 Feb 2021 14:25:54 +0000 Subject: [PATCH 0288/1818] Add unavailable to Vera (#46064) --- homeassistant/components/vera/__init__.py | 10 ++++++++++ homeassistant/components/vera/binary_sensor.py | 1 + homeassistant/components/vera/light.py | 1 + homeassistant/components/vera/manifest.json | 2 +- homeassistant/components/vera/sensor.py | 2 +- homeassistant/components/vera/switch.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/vera/test_binary_sensor.py | 1 + tests/components/vera/test_climate.py | 2 ++ tests/components/vera/test_cover.py | 1 + tests/components/vera/test_light.py | 1 + tests/components/vera/test_lock.py | 1 + tests/components/vera/test_sensor.py | 2 ++ tests/components/vera/test_switch.py | 1 + 15 files changed, 26 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 3fd1c189b63647..4bfa72b5eb648d 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -232,6 +232,11 @@ def _update_callback(self, _device: DeviceType) -> None: """Update the state.""" self.schedule_update_ha_state(True) + def update(self): + """Force a refresh from the device if the device is unavailable.""" + if not self.available: + self.vera_device.refresh() + @property def name(self) -> str: """Return the name of the device.""" @@ -276,6 +281,11 @@ def device_state_attributes(self) -> Optional[Dict[str, Any]]: return attr + @property + def available(self): + """If device communications have failed return false.""" + return not self.vera_device.comm_failure + @property def unique_id(self) -> str: """Return a unique ID. diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 7932fa14f4c3ec..00d4fb3a75827d 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -50,4 +50,5 @@ def is_on(self) -> Optional[bool]: def update(self) -> None: """Get the latest data and update the state.""" + super().update() self._state = self.vera_device.is_tripped diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index c52627d340f663..30c4e93a2ba7d3 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -93,6 +93,7 @@ def is_on(self) -> bool: def update(self) -> None: """Call to update state.""" + super().update() self._state = self.vera_device.is_switched_on() if self.vera_device.is_dimmable: # If it is dimmable, both functions exist. In case color diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 264f44782f5fb6..1f180b39750071 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -3,6 +3,6 @@ "name": "Vera", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vera", - "requirements": ["pyvera==0.3.11"], + "requirements": ["pyvera==0.3.13"], "codeowners": ["@vangorra"] } diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index ea7dbf0ae30d3a..007290807e691f 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -68,7 +68,7 @@ def unit_of_measurement(self) -> Optional[str]: def update(self) -> None: """Update the state.""" - + super().update() if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: self.current_value = self.vera_device.temperature diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index 5dfeba6f5b2bd6..f567893e5b0f5e 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -70,4 +70,5 @@ def is_on(self) -> bool: def update(self) -> None: """Update device state.""" + super().update() self._state = self.vera_device.is_switched_on() diff --git a/requirements_all.txt b/requirements_all.txt index 04dbcdf9d1ac67..6a8b5f63fa280b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1880,7 +1880,7 @@ pyuptimerobot==0.0.5 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.3.11 +pyvera==0.3.13 # homeassistant.components.versasense pyversasense==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 548d6ba1117cff..54f6651ad0cf10 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -953,7 +953,7 @@ pytraccar==0.9.0 pytradfri[async]==7.0.6 # homeassistant.components.vera -pyvera==0.3.11 +pyvera==0.3.13 # homeassistant.components.vesync pyvesync==1.2.0 diff --git a/tests/components/vera/test_binary_sensor.py b/tests/components/vera/test_binary_sensor.py index 1bcb8d1a183496..b3b8d2d6ae194c 100644 --- a/tests/components/vera/test_binary_sensor.py +++ b/tests/components/vera/test_binary_sensor.py @@ -14,6 +14,7 @@ async def test_binary_sensor( """Test function.""" vera_device = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor vera_device.device_id = 1 + vera_device.comm_failure = False vera_device.vera_device_id = vera_device.device_id vera_device.name = "dev1" vera_device.is_tripped = False diff --git a/tests/components/vera/test_climate.py b/tests/components/vera/test_climate.py index 076b51997a0e63..5ec39f07953710 100644 --- a/tests/components/vera/test_climate.py +++ b/tests/components/vera/test_climate.py @@ -23,6 +23,7 @@ async def test_climate( vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_THERMOSTAT vera_device.power = 10 @@ -133,6 +134,7 @@ async def test_climate_f( vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_THERMOSTAT vera_device.power = 10 diff --git a/tests/components/vera/test_cover.py b/tests/components/vera/test_cover.py index 0c05d84e2dbc1d..cfc33fb2dcfcc5 100644 --- a/tests/components/vera/test_cover.py +++ b/tests/components/vera/test_cover.py @@ -15,6 +15,7 @@ async def test_cover( vera_device = MagicMock(spec=pv.VeraCurtain) # type: pv.VeraCurtain vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_CURTAIN vera_device.is_closed = False diff --git a/tests/components/vera/test_light.py b/tests/components/vera/test_light.py index 3b14aba742992c..ad5ad7e0259952 100644 --- a/tests/components/vera/test_light.py +++ b/tests/components/vera/test_light.py @@ -16,6 +16,7 @@ async def test_light( vera_device = MagicMock(spec=pv.VeraDimmer) # type: pv.VeraDimmer vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_DIMMER vera_device.is_switched_on = MagicMock(return_value=False) diff --git a/tests/components/vera/test_lock.py b/tests/components/vera/test_lock.py index c288ac8709e1ab..171f799f87bae6 100644 --- a/tests/components/vera/test_lock.py +++ b/tests/components/vera/test_lock.py @@ -16,6 +16,7 @@ async def test_lock( vera_device = MagicMock(spec=pv.VeraLock) # type: pv.VeraLock vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_LOCK vera_device.is_locked = MagicMock(return_value=False) diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index 437776428164a5..62639df3a35ae4 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -23,6 +23,7 @@ async def run_sensor_test( vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = category setattr(vera_device, class_property, "33") @@ -178,6 +179,7 @@ async def test_scene_controller_sensor( vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_SCENE_CONTROLLER vera_device.get_last_scene_id = MagicMock(return_value="id0") diff --git a/tests/components/vera/test_switch.py b/tests/components/vera/test_switch.py index b61564c56bc577..ac90edc9ded216 100644 --- a/tests/components/vera/test_switch.py +++ b/tests/components/vera/test_switch.py @@ -15,6 +15,7 @@ async def test_switch( vera_device = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id + vera_device.comm_failure = False vera_device.name = "dev1" vera_device.category = pv.CATEGORY_SWITCH vera_device.is_switched_on = MagicMock(return_value=False) From 81c88cd63914bd47b403e4e170f53dce3f573c67 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 8 Feb 2021 17:02:12 +0200 Subject: [PATCH 0289/1818] Enhance MQTT cover platform (#46059) * Enhance MQTT cover platform Allow combining of position and state of MQTT cover Add template and fix optimistic in set tilt position Add tests * Add abbreviations * Add tests and stopped state * Cleanup & fix range for templates * Apply suggestions from code review Co-authored-by: Erik Montnemery --- .../components/mqtt/abbreviations.py | 3 + homeassistant/components/mqtt/cover.py | 108 ++++-- tests/components/mqtt/test_cover.py | 347 ++++++++++++++++++ 3 files changed, 434 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 4b209f6f364b0c..8868d487f934be 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -136,6 +136,7 @@ "set_pos_tpl": "set_position_template", "set_pos_t": "set_position_topic", "pos_t": "position_topic", + "pos_tpl": "position_template", "spd_cmd_t": "speed_command_topic", "spd_stat_t": "speed_state_topic", "spd_val_tpl": "speed_value_template", @@ -147,6 +148,7 @@ "stat_on": "state_on", "stat_open": "state_open", "stat_opening": "state_opening", + "stat_stopped": "state_stopped", "stat_locked": "state_locked", "stat_unlocked": "state_unlocked", "stat_t": "state_topic", @@ -173,6 +175,7 @@ "temp_unit": "temperature_unit", "tilt_clsd_val": "tilt_closed_value", "tilt_cmd_t": "tilt_command_topic", + "tilt_cmd_tpl": "tilt_command_template", "tilt_inv_stat": "tilt_invert_state", "tilt_max": "tilt_max", "tilt_min": "tilt_min", diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index dc2cba0efab6ee..4b428027a4d98f 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -59,9 +59,11 @@ _LOGGER = logging.getLogger(__name__) CONF_GET_POSITION_TOPIC = "position_topic" -CONF_SET_POSITION_TEMPLATE = "set_position_template" +CONF_GET_POSITION_TEMPLATE = "position_template" CONF_SET_POSITION_TOPIC = "set_position_topic" +CONF_SET_POSITION_TEMPLATE = "set_position_template" CONF_TILT_COMMAND_TOPIC = "tilt_command_topic" +CONF_TILT_COMMAND_TEMPLATE = "tilt_command_template" CONF_TILT_STATUS_TOPIC = "tilt_status_topic" CONF_TILT_STATUS_TEMPLATE = "tilt_status_template" @@ -74,6 +76,7 @@ CONF_STATE_CLOSING = "state_closing" CONF_STATE_OPEN = "state_open" CONF_STATE_OPENING = "state_opening" +CONF_STATE_STOPPED = "state_stopped" CONF_TILT_CLOSED_POSITION = "tilt_closed_value" CONF_TILT_INVERT_STATE = "tilt_invert_state" CONF_TILT_MAX = "tilt_max" @@ -92,6 +95,7 @@ DEFAULT_POSITION_CLOSED = 0 DEFAULT_POSITION_OPEN = 100 DEFAULT_RETAIN = False +DEFAULT_STATE_STOPPED = "stopped" DEFAULT_TILT_CLOSED_POSITION = 0 DEFAULT_TILT_INVERT_STATE = False DEFAULT_TILT_MAX = 100 @@ -115,8 +119,27 @@ def validate_options(value): """ if CONF_SET_POSITION_TOPIC in value and CONF_GET_POSITION_TOPIC not in value: raise vol.Invalid( - "set_position_topic must be set together with position_topic." + "'set_position_topic' must be set together with 'position_topic'." + ) + + if ( + CONF_GET_POSITION_TOPIC in value + and CONF_STATE_TOPIC not in value + and CONF_VALUE_TEMPLATE in value + ): + _LOGGER.warning( + "using 'value_template' for 'position_topic' is deprecated " + "and will be removed from Home Assistant in version 2021.6" + "please replace it with 'position_template'" ) + + if CONF_TILT_INVERT_STATE in value: + _LOGGER.warning( + "'tilt_invert_state' is deprecated " + "and will be removed from Home Assistant in version 2021.6" + "please invert tilt using 'tilt_min' & 'tilt_max'" + ) + return value @@ -143,6 +166,7 @@ def validate_options(value): vol.Optional(CONF_STATE_CLOSING, default=STATE_CLOSING): cv.string, vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, vol.Optional(CONF_STATE_OPENING, default=STATE_OPENING): cv.string, + vol.Optional(CONF_STATE_STOPPED, default=DEFAULT_STATE_STOPPED): cv.string, vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional( CONF_TILT_CLOSED_POSITION, default=DEFAULT_TILT_CLOSED_POSITION @@ -163,6 +187,8 @@ def validate_options(value): vol.Optional(CONF_TILT_STATUS_TEMPLATE): cv.template, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_GET_POSITION_TEMPLATE): cv.template, + vol.Optional(CONF_TILT_COMMAND_TEMPLATE): cv.template, } ) .extend(MQTT_AVAILABILITY_SCHEMA.schema) @@ -228,6 +254,9 @@ def _setup_from_config(self, config): set_position_template = self._config.get(CONF_SET_POSITION_TEMPLATE) if set_position_template is not None: set_position_template.hass = self.hass + set_tilt_template = self._config.get(CONF_TILT_COMMAND_TEMPLATE) + if set_tilt_template is not None: + set_tilt_template.hass = self.hass tilt_status_template = self._config.get(CONF_TILT_STATUS_TEMPLATE) if tilt_status_template is not None: tilt_status_template.hass = self.hass @@ -266,17 +295,31 @@ def state_message_received(msg): if template is not None: payload = template.async_render_with_possible_json_value(payload) - if payload == self._config[CONF_STATE_OPEN]: - self._state = STATE_OPEN + if payload == self._config[CONF_STATE_STOPPED]: + if ( + self._optimistic + or self._config.get(CONF_GET_POSITION_TOPIC) is None + ): + self._state = ( + STATE_CLOSED if self._state == STATE_CLOSING else STATE_OPEN + ) + else: + self._state = ( + STATE_CLOSED + if self._position == DEFAULT_POSITION_CLOSED + else STATE_OPEN + ) elif payload == self._config[CONF_STATE_OPENING]: self._state = STATE_OPENING - elif payload == self._config[CONF_STATE_CLOSED]: - self._state = STATE_CLOSED elif payload == self._config[CONF_STATE_CLOSING]: self._state = STATE_CLOSING + elif payload == self._config[CONF_STATE_OPEN]: + self._state = STATE_OPEN + elif payload == self._config[CONF_STATE_CLOSED]: + self._state = STATE_CLOSED else: _LOGGER.warning( - "Payload is not supported (e.g. open, closed, opening, closing): %s", + "Payload is not supported (e.g. open, closed, opening, closing, stopped): %s", payload, ) return @@ -286,9 +329,16 @@ def state_message_received(msg): @callback @log_messages(self.hass, self.entity_id) def position_message_received(msg): - """Handle new MQTT state messages.""" + """Handle new MQTT position messages.""" payload = msg.payload - template = self._config.get(CONF_VALUE_TEMPLATE) + + template = self._config.get(CONF_GET_POSITION_TEMPLATE) + + # To be removed in 2021.6: + # allow using `value_template` as position template if no `state_topic` + if template is None and self._config.get(CONF_STATE_TOPIC) is None: + template = self._config.get(CONF_VALUE_TEMPLATE) + if template is not None: payload = template.async_render_with_possible_json_value(payload) @@ -297,13 +347,14 @@ def position_message_received(msg): float(payload), COVER_PAYLOAD ) self._position = percentage_payload - self._state = ( - STATE_CLOSED - if percentage_payload == DEFAULT_POSITION_CLOSED - else STATE_OPEN - ) + if self._config.get(CONF_STATE_TOPIC) is None: + self._state = ( + STATE_CLOSED + if percentage_payload == DEFAULT_POSITION_CLOSED + else STATE_OPEN + ) else: - _LOGGER.warning("Payload is not integer within range: %s", payload) + _LOGGER.warning("Payload '%s' is not numeric", payload) return self.async_write_ha_state() @@ -313,13 +364,18 @@ def position_message_received(msg): "msg_callback": position_message_received, "qos": self._config[CONF_QOS], } - elif self._config.get(CONF_STATE_TOPIC): + + if self._config.get(CONF_STATE_TOPIC): topics["state_topic"] = { "topic": self._config.get(CONF_STATE_TOPIC), "msg_callback": state_message_received, "qos": self._config[CONF_QOS], } - else: + + if ( + self._config.get(CONF_GET_POSITION_TOPIC) is None + and self._config.get(CONF_STATE_TOPIC) is None + ): # Force into optimistic mode. self._optimistic = True @@ -488,28 +544,32 @@ async def async_close_cover_tilt(self, **kwargs): async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" - position = kwargs[ATTR_TILT_POSITION] - - # The position needs to be between min and max - level = self.find_in_range_from_percent(position) + set_tilt_template = self._config.get(CONF_TILT_COMMAND_TEMPLATE) + tilt = kwargs[ATTR_TILT_POSITION] + percentage_tilt = tilt + tilt = self.find_in_range_from_percent(tilt) + if set_tilt_template is not None: + tilt = set_tilt_template.async_render(parse_result=False, **kwargs) mqtt.async_publish( self.hass, self._config.get(CONF_TILT_COMMAND_TOPIC), - level, + tilt, self._config[CONF_QOS], self._config[CONF_RETAIN], ) + if self._tilt_optimistic: + self._tilt_value = percentage_tilt + self.async_write_ha_state() async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" set_position_template = self._config.get(CONF_SET_POSITION_TEMPLATE) position = kwargs[ATTR_POSITION] percentage_position = position + position = self.find_in_range_from_percent(position, COVER_PAYLOAD) if set_position_template is not None: position = set_position_template.async_render(parse_result=False, **kwargs) - else: - position = self.find_in_range_from_percent(position, COVER_PAYLOAD) mqtt.async_publish( self.hass, diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 019f0e19911721..87b016e2d59d19 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1082,6 +1082,23 @@ async def test_tilt_given_value_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() + await hass.services.async_call( + cover.DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + {ATTR_ENTITY_ID: "cover.test", ATTR_TILT_POSITION: 50}, + blocking=True, + ) + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 50 + + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "50", 0, False + ) + mqtt_mock.async_publish.reset_mock() + await hass.services.async_call( cover.DOMAIN, SERVICE_CLOSE_COVER_TILT, @@ -1381,6 +1398,41 @@ async def test_tilt_position(hass, mqtt_mock): ) +async def test_tilt_position_templated(hass, mqtt_mock): + """Test tilt position via template.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_command_template": "{{100-32}}", + } + }, + ) + await hass.async_block_till_done() + + await hass.services.async_call( + cover.DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + {ATTR_ENTITY_ID: "cover.test", ATTR_TILT_POSITION: 100}, + blocking=True, + ) + + mqtt_mock.async_publish.assert_called_once_with( + "tilt-command-topic", "68", 0, False + ) + + async def test_tilt_position_altered_range(hass, mqtt_mock): """Test tilt via method invocation with altered range.""" assert await async_setup_component( @@ -1978,3 +2030,298 @@ async def test_entity_debug_info_message(hass, mqtt_mock): await help_test_entity_debug_info_message( hass, mqtt_mock, cover.DOMAIN, DEFAULT_CONFIG ) + + +async def test_deprecated_value_template_for_position_topic_warnning( + hass, caplog, mqtt_mock +): + """Test warnning when value_template is used for position_topic.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "set_position_topic": "set-position-topic", + "position_topic": "position-topic", + "value_template": "{{100-62}}", + } + }, + ) + await hass.async_block_till_done() + + assert ( + "using 'value_template' for 'position_topic' is deprecated " + "and will be removed from Home Assistant in version 2021.6" + "please replace it with 'position_template'" + ) in caplog.text + + +async def test_deprecated_tilt_invert_state_warnning(hass, caplog, mqtt_mock): + """Test warnning when tilt_invert_state is used.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "tilt_invert_state": True, + } + }, + ) + await hass.async_block_till_done() + + assert ( + "'tilt_invert_state' is deprecated " + "and will be removed from Home Assistant in version 2021.6" + "please invert tilt using 'tilt_min' & 'tilt_max'" + ) in caplog.text + + +async def test_no_deprecated_warning_for_position_topic_using_position_template( + hass, caplog, mqtt_mock +): + """Test no warning when position_template is used for position_topic.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "set_position_topic": "set-position-topic", + "position_topic": "position-topic", + "position_template": "{{100-62}}", + } + }, + ) + await hass.async_block_till_done() + + assert ( + "using 'value_template' for 'position_topic' is deprecated " + "and will be removed from Home Assistant in version 2021.6" + "please replace it with 'position_template'" + ) not in caplog.text + + +async def test_state_and_position_topics_state_not_set_via_position_topic( + hass, mqtt_mock +): + """Test state is not set via position topic when both state and position topics are set.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "position_topic": "get-position-topic", + "position_open": 100, + "position_closed": 0, + "state_open": "OPEN", + "state_closed": "CLOSE", + "command_topic": "command-topic", + "qos": 0, + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("cover.test") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "state-topic", "OPEN") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "get-position-topic", "0") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "get-position-topic", "100") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "state-topic", "CLOSE") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED + + async_fire_mqtt_message(hass, "get-position-topic", "0") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED + + async_fire_mqtt_message(hass, "get-position-topic", "100") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED + + +async def test_set_state_via_position_using_stopped_state(hass, mqtt_mock): + """Test the controlling state via position topic using stopped state.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "position_topic": "get-position-topic", + "position_open": 100, + "position_closed": 0, + "state_open": "OPEN", + "state_closed": "CLOSE", + "state_stopped": "STOPPED", + "command_topic": "command-topic", + "qos": 0, + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("cover.test") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "state-topic", "OPEN") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "get-position-topic", "0") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "state-topic", "STOPPED") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED + + async_fire_mqtt_message(hass, "get-position-topic", "100") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED + + async_fire_mqtt_message(hass, "state-topic", "STOPPED") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + +async def test_set_state_via_stopped_state_optimistic(hass, mqtt_mock): + """Test the controlling state via stopped state in optimistic mode.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "position_topic": "get-position-topic", + "position_open": 100, + "position_closed": 0, + "state_open": "OPEN", + "state_closed": "CLOSE", + "state_stopped": "STOPPED", + "state_opening": "OPENING", + "state_closing": "CLOSING", + "command_topic": "command-topic", + "qos": 0, + "optimistic": True, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "state-topic", "OPEN") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "get-position-topic", "50") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "state-topic", "OPENING") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPENING + + async_fire_mqtt_message(hass, "state-topic", "STOPPED") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "state-topic", "CLOSING") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSING + + async_fire_mqtt_message(hass, "state-topic", "STOPPED") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED + + +async def test_set_state_via_stopped_state_no_position_topic(hass, mqtt_mock): + """Test the controlling state via stopped state when no position topic.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "state_open": "OPEN", + "state_closed": "CLOSE", + "state_stopped": "STOPPED", + "state_opening": "OPENING", + "state_closing": "CLOSING", + "command_topic": "command-topic", + "qos": 0, + "optimistic": False, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "state-topic", "OPEN") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "state-topic", "OPENING") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPENING + + async_fire_mqtt_message(hass, "state-topic", "STOPPED") + + state = hass.states.get("cover.test") + assert state.state == STATE_OPEN + + async_fire_mqtt_message(hass, "state-topic", "CLOSING") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSING + + async_fire_mqtt_message(hass, "state-topic", "STOPPED") + + state = hass.states.get("cover.test") + assert state.state == STATE_CLOSED From e20a814926e6fecc252688aaca6a83e8cd62d00c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Feb 2021 16:16:40 +0100 Subject: [PATCH 0290/1818] Call setup during devcontainer create (#46224) --- .devcontainer/devcontainer.json | 3 ++- script/bootstrap | 17 ----------------- script/setup | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 94d3c284f1a0e7..efcc0380748116 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,8 @@ "name": "Home Assistant Dev", "context": "..", "dockerFile": "../Dockerfile.dev", - "postCreateCommand": "script/bootstrap", + "postCreateCommand": "script/setup", + "postStartCommand": "script/bootstrap", "containerEnv": { "DEVCONTAINER": "1" }, "appPort": 8123, "runArgs": ["-e", "GIT_EDITOR=code --wait"], diff --git a/script/bootstrap b/script/bootstrap index 12a2e5da707281..3ffac11852b89a 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -6,23 +6,6 @@ set -e cd "$(dirname "$0")/.." -# Add default vscode settings if not existing -SETTINGS_FILE=./.vscode/settings.json -SETTINGS_TEMPLATE_FILE=./.vscode/settings.default.json -if [ ! -f "$SETTINGS_FILE" ]; then - echo "Copy $SETTINGS_TEMPLATE_FILE to $SETTINGS_FILE." - cp "$SETTINGS_TEMPLATE_FILE" "$SETTINGS_FILE" -fi - echo "Installing development dependencies..." python3 -m pip install wheel --constraint homeassistant/package_constraints.txt python3 -m pip install tox 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 - -if [ -n "$DEVCONTAINER" ];then - pre-commit install - pre-commit install-hooks - python3 -m pip install -e . --constraint homeassistant/package_constraints.txt - - mkdir -p config - hass --script ensure_config -c config -fi \ No newline at end of file diff --git a/script/setup b/script/setup index 83c2d24f038f39..46865ecfcb6547 100755 --- a/script/setup +++ b/script/setup @@ -6,10 +6,20 @@ set -e cd "$(dirname "$0")/.." +# Add default vscode settings if not existing +SETTINGS_FILE=./.vscode/settings.json +SETTINGS_TEMPLATE_FILE=./.vscode/settings.default.json +if [ ! -f "$SETTINGS_FILE" ]; then + echo "Copy $SETTINGS_TEMPLATE_FILE to $SETTINGS_FILE." + cp "$SETTINGS_TEMPLATE_FILE" "$SETTINGS_FILE" +fi + mkdir -p config -python3 -m venv venv -source venv/bin/activate +if [ ! -n "$DEVCONTAINER" ];then + python3 -m venv venv + source venv/bin/activate +fi script/bootstrap From dca6a9389854380a27baea3b694320786fc34c68 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 8 Feb 2021 07:19:41 -0800 Subject: [PATCH 0291/1818] Centralize keepalive logic in Stream class (#45850) * Remove dependencies on keepalive from StremaOutput and stream_worker Pull logic from StreamOutput and stream_worker into the Stream class, unifying keepalive and idle timeout logic. This prepares for future changes to preserve hls state across stream url changes. --- homeassistant/components/stream/__init__.py | 68 ++++++++++++--- homeassistant/components/stream/const.py | 2 + homeassistant/components/stream/core.py | 92 +++++++++++++-------- homeassistant/components/stream/recorder.py | 16 ++-- homeassistant/components/stream/worker.py | 38 --------- tests/components/stream/conftest.py | 28 +++---- tests/components/stream/test_hls.py | 5 +- tests/components/stream/test_init.py | 4 +- tests/components/stream/test_recorder.py | 8 +- tests/components/stream/test_worker.py | 20 +---- 10 files changed, 142 insertions(+), 139 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index c7d1dad4835e5a..6980f7ead8f072 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -2,6 +2,7 @@ import logging import secrets import threading +import time from types import MappingProxyType import voluptuous as vol @@ -20,9 +21,12 @@ CONF_STREAM_SOURCE, DOMAIN, MAX_SEGMENTS, + OUTPUT_IDLE_TIMEOUT, SERVICE_RECORD, + STREAM_RESTART_INCREMENT, + STREAM_RESTART_RESET_TIME, ) -from .core import PROVIDERS +from .core import PROVIDERS, IdleTimer from .hls import async_setup_hls _LOGGER = logging.getLogger(__name__) @@ -142,18 +146,27 @@ def outputs(self): # without concern about self._outputs being modified from another thread. return MappingProxyType(self._outputs.copy()) - def add_provider(self, fmt): + def add_provider(self, fmt, timeout=OUTPUT_IDLE_TIMEOUT): """Add provider output stream.""" if not self._outputs.get(fmt): - provider = PROVIDERS[fmt](self) + + @callback + def idle_callback(): + if not self.keepalive and fmt in self._outputs: + self.remove_provider(self._outputs[fmt]) + self.check_idle() + + provider = PROVIDERS[fmt]( + self.hass, IdleTimer(self.hass, timeout, idle_callback) + ) self._outputs[fmt] = provider return self._outputs[fmt] def remove_provider(self, provider): """Remove provider output stream.""" if provider.name in self._outputs: + self._outputs[provider.name].cleanup() del self._outputs[provider.name] - self.check_idle() if not self._outputs: self.stop() @@ -165,10 +178,6 @@ def check_idle(self): def start(self): """Start a stream.""" - # Keep import here so that we can import stream integration without installing reqs - # pylint: disable=import-outside-toplevel - from .worker import stream_worker - if self._thread is None or not self._thread.is_alive(): if self._thread is not None: # The thread must have crashed/exited. Join to clean up the @@ -177,12 +186,48 @@ def start(self): self._thread_quit = threading.Event() self._thread = threading.Thread( name="stream_worker", - target=stream_worker, - args=(self.hass, self, self._thread_quit), + target=self._run_worker, ) self._thread.start() _LOGGER.info("Started stream: %s", self.source) + def _run_worker(self): + """Handle consuming streams and restart keepalive streams.""" + # Keep import here so that we can import stream integration without installing reqs + # pylint: disable=import-outside-toplevel + from .worker import stream_worker + + wait_timeout = 0 + while not self._thread_quit.wait(timeout=wait_timeout): + start_time = time.time() + stream_worker(self.hass, self, self._thread_quit) + if not self.keepalive or self._thread_quit.is_set(): + break + + # To avoid excessive restarts, wait before restarting + # As the required recovery time may be different for different setups, start + # with trying a short wait_timeout and increase it on each reconnection attempt. + # Reset the wait_timeout after the worker has been up for several minutes + if time.time() - start_time > STREAM_RESTART_RESET_TIME: + wait_timeout = 0 + wait_timeout += STREAM_RESTART_INCREMENT + _LOGGER.debug( + "Restarting stream worker in %d seconds: %s", + wait_timeout, + self.source, + ) + self._worker_finished() + + def _worker_finished(self): + """Schedule cleanup of all outputs.""" + + @callback + def remove_outputs(): + for provider in self.outputs.values(): + self.remove_provider(provider) + + self.hass.loop.call_soon_threadsafe(remove_outputs) + def stop(self): """Remove outputs and access token.""" self._outputs = {} @@ -223,9 +268,8 @@ async def async_handle_record_service(hass, call): if recorder: raise HomeAssistantError(f"Stream already recording to {recorder.video_path}!") - recorder = stream.add_provider("recorder") + recorder = stream.add_provider("recorder", timeout=duration) recorder.video_path = video_path - recorder.timeout = duration stream.start() diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 181808e549ed66..45fa3d9e76a179 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -15,6 +15,8 @@ FORMAT_CONTENT_TYPE = {"hls": "application/vnd.apple.mpegurl"} +OUTPUT_IDLE_TIMEOUT = 300 # Idle timeout due to inactivity + MAX_SEGMENTS = 3 # Max number of segments to keep around MIN_SEGMENT_DURATION = 1.5 # Each segment is at least this many seconds diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 5158ba185b13fb..5427172a55c11c 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -8,7 +8,7 @@ import attr from homeassistant.components.http import HomeAssistantView -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import async_call_later from homeassistant.util.decorator import Registry @@ -36,24 +36,69 @@ class Segment: duration: float = attr.ib() +class IdleTimer: + """Invoke a callback after an inactivity timeout. + + The IdleTimer invokes the callback after some timeout has passed. The awake() method + resets the internal alarm, extending the inactivity time. + """ + + def __init__( + self, hass: HomeAssistant, timeout: int, idle_callback: Callable[[], None] + ): + """Initialize IdleTimer.""" + self._hass = hass + self._timeout = timeout + self._callback = idle_callback + self._unsub = None + self.idle = False + + def start(self): + """Start the idle timer if not already started.""" + self.idle = False + if self._unsub is None: + self._unsub = async_call_later(self._hass, self._timeout, self.fire) + + def awake(self): + """Keep the idle time alive by resetting the timeout.""" + self.idle = False + # Reset idle timeout + self.clear() + self._unsub = async_call_later(self._hass, self._timeout, self.fire) + + def clear(self): + """Clear and disable the timer.""" + if self._unsub is not None: + self._unsub() + + def fire(self, _now=None): + """Invoke the idle timeout callback, called when the alarm fires.""" + self.idle = True + self._unsub = None + self._callback() + + class StreamOutput: """Represents a stream output.""" - def __init__(self, stream, timeout: int = 300) -> None: + def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: """Initialize a stream output.""" - self.idle = False - self.timeout = timeout - self._stream = stream + self._hass = hass + self._idle_timer = idle_timer self._cursor = None self._event = asyncio.Event() self._segments = deque(maxlen=MAX_SEGMENTS) - self._unsub = None @property def name(self) -> str: """Return provider name.""" return None + @property + def idle(self) -> bool: + """Return True if the output is idle.""" + return self._idle_timer.idle + @property def format(self) -> str: """Return container format.""" @@ -90,11 +135,7 @@ def target_duration(self) -> int: def get_segment(self, sequence: int = None) -> Any: """Retrieve a specific segment, or the whole list.""" - self.idle = False - # Reset idle timeout - if self._unsub is not None: - self._unsub() - self._unsub = async_call_later(self._stream.hass, self.timeout, self._timeout) + self._idle_timer.awake() if not sequence: return self._segments @@ -119,43 +160,22 @@ async def recv(self) -> Segment: def put(self, segment: Segment) -> None: """Store output.""" - self._stream.hass.loop.call_soon_threadsafe(self._async_put, segment) + self._hass.loop.call_soon_threadsafe(self._async_put, segment) @callback def _async_put(self, segment: Segment) -> None: """Store output from event loop.""" # Start idle timeout when we start receiving data - if self._unsub is None: - self._unsub = async_call_later( - self._stream.hass, self.timeout, self._timeout - ) - - if segment is None: - self._event.set() - # Cleanup provider - if self._unsub is not None: - self._unsub() - self.cleanup() - return - + self._idle_timer.start() self._segments.append(segment) self._event.set() self._event.clear() - @callback - def _timeout(self, _now=None): - """Handle stream timeout.""" - self._unsub = None - if self._stream.keepalive: - self.idle = True - self._stream.check_idle() - else: - self.cleanup() - def cleanup(self): """Handle cleanup.""" + self._event.set() + self._idle_timer.clear() self._segments = deque(maxlen=MAX_SEGMENTS) - self._stream.remove_provider(self) class StreamView(HomeAssistantView): diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index cf923de85c2eb6..7db9997f870688 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -6,9 +6,9 @@ import av -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback -from .core import PROVIDERS, Segment, StreamOutput +from .core import PROVIDERS, IdleTimer, Segment, StreamOutput _LOGGER = logging.getLogger(__name__) @@ -72,9 +72,9 @@ def recorder_save_worker(file_out: str, segments: List[Segment], container_forma class RecorderOutput(StreamOutput): """Represents HLS Output formats.""" - def __init__(self, stream, timeout: int = 30) -> None: + def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: """Initialize recorder output.""" - super().__init__(stream, timeout) + super().__init__(hass, idle_timer) self.video_path = None self._segments = [] @@ -104,12 +104,6 @@ def prepend(self, segments: List[Segment]) -> None: segments = [s for s in segments if s.sequence not in own_segments] self._segments = segments + self._segments - @callback - def _timeout(self, _now=None): - """Handle recorder timeout.""" - self._unsub = None - self.cleanup() - def cleanup(self): """Write recording and clean up.""" _LOGGER.debug("Starting recorder worker thread") @@ -120,5 +114,5 @@ def cleanup(self): ) thread.start() + super().cleanup() self._segments = [] - self._stream.remove_provider(self) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index cccbfd1b48babd..510d0ebd460c34 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -2,7 +2,6 @@ from collections import deque import io import logging -import time import av @@ -11,8 +10,6 @@ MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO, - STREAM_RESTART_INCREMENT, - STREAM_RESTART_RESET_TIME, STREAM_TIMEOUT, ) from .core import Segment, StreamBuffer @@ -47,32 +44,6 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): def stream_worker(hass, stream, quit_event): - """Handle consuming streams and restart keepalive streams.""" - - wait_timeout = 0 - while not quit_event.wait(timeout=wait_timeout): - start_time = time.time() - try: - _stream_worker_internal(hass, stream, quit_event) - except av.error.FFmpegError: # pylint: disable=c-extension-no-member - _LOGGER.exception("Stream connection failed: %s", stream.source) - if not stream.keepalive or quit_event.is_set(): - break - # To avoid excessive restarts, wait before restarting - # As the required recovery time may be different for different setups, start - # with trying a short wait_timeout and increase it on each reconnection attempt. - # Reset the wait_timeout after the worker has been up for several minutes - if time.time() - start_time > STREAM_RESTART_RESET_TIME: - wait_timeout = 0 - wait_timeout += STREAM_RESTART_INCREMENT - _LOGGER.debug( - "Restarting stream worker in %d seconds: %s", - wait_timeout, - stream.source, - ) - - -def _stream_worker_internal(hass, stream, quit_event): """Handle consuming streams.""" try: @@ -183,7 +154,6 @@ def peek_first_pts(): _LOGGER.error( "Error demuxing stream while finding first packet: %s", str(ex) ) - finalize_stream() return False return True @@ -220,12 +190,6 @@ def mux_audio_packet(packet): packet.stream = output_streams[audio_stream] buffer.output.mux(packet) - def finalize_stream(): - if not stream.keepalive: - # End of stream, clear listeners and stop thread - for fmt in stream.outputs: - stream.outputs[fmt].put(None) - if not peek_first_pts(): container.close() return @@ -249,7 +213,6 @@ def finalize_stream(): missing_dts = 0 except (av.AVError, StopIteration) as ex: _LOGGER.error("Error demuxing stream: %s", str(ex)) - finalize_stream() break # Discard packet if dts is not monotonic @@ -263,7 +226,6 @@ def finalize_stream(): last_dts[packet.stream], packet.dts, ) - finalize_stream() break continue diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index 1b2f0645f9b77e..75ac9377b7cb18 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -9,14 +9,13 @@ allows the tests to pause the worker thread before finalizing the stream so that it can inspect the output. """ - import logging import threading from unittest.mock import patch import pytest -from homeassistant.components.stream.core import Segment, StreamOutput +from homeassistant.components.stream import Stream class WorkerSync: @@ -25,7 +24,7 @@ class WorkerSync: def __init__(self): """Initialize WorkerSync.""" self._event = None - self._put_original = StreamOutput.put + self._original = Stream._worker_finished def pause(self): """Pause the worker before it finalizes the stream.""" @@ -35,17 +34,16 @@ def resume(self): """Allow the worker thread to finalize the stream.""" self._event.set() - def blocking_put(self, stream_output: StreamOutput, segment: Segment): - """Proxy StreamOutput.put, intercepted for test to pause worker.""" - if segment is None and self._event: - # Worker is ending the stream, which clears all output buffers. - # Block the worker thread until the test has a chance to verify - # the segments under test. - logging.error("blocking worker") - self._event.wait() + def blocking_finish(self, stream: Stream): + """Intercept call to pause stream worker.""" + # Worker is ending the stream, which clears all output buffers. + # Block the worker thread until the test has a chance to verify + # the segments under test. + logging.debug("blocking worker") + self._event.wait() - # Forward to actual StreamOutput.put - self._put_original(stream_output, segment) + # Forward to actual implementation + self._original(stream) @pytest.fixture() @@ -53,8 +51,8 @@ def stream_worker_sync(hass): """Patch StreamOutput to allow test to synchronize worker stream end.""" sync = WorkerSync() with patch( - "homeassistant.components.stream.core.StreamOutput.put", - side_effect=sync.blocking_put, + "homeassistant.components.stream.Stream._worker_finished", + side_effect=sync.blocking_finish, autospec=True, ): yield sync diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 790222b1630670..ab49a56ca029c7 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -98,6 +98,7 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): # Wait 5 minutes future = dt_util.utcnow() + timedelta(minutes=5) async_fire_time_changed(hass, future) + await hass.async_block_till_done() # Ensure playlist not accessible fail_response = await http_client.get(parsed_url.path) @@ -155,9 +156,9 @@ def time_side_effect(): return cur_time with patch("av.open") as av_open, patch( - "homeassistant.components.stream.worker.time" + "homeassistant.components.stream.time" ) as mock_time, patch( - "homeassistant.components.stream.worker.STREAM_RESTART_INCREMENT", 0 + "homeassistant.components.stream.STREAM_RESTART_INCREMENT", 0 ): av_open.side_effect = av.error.InvalidDataError(-2, "error") mock_time.time.side_effect = time_side_effect diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py index 1515ff1a490bad..2e13493b641f82 100644 --- a/tests/components/stream/test_init.py +++ b/tests/components/stream/test_init.py @@ -80,5 +80,7 @@ async def test_record_service_lookback(hass): await hass.services.async_call(DOMAIN, SERVICE_RECORD, data, blocking=True) assert stream_mock.called - stream_mock.return_value.add_provider.assert_called_once_with("recorder") + stream_mock.return_value.add_provider.assert_called_once_with( + "recorder", timeout=30 + ) assert hls_mock.recv.called diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 1b46738c8f2c16..bda53a9cc17a72 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -106,13 +106,11 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): stream_worker_sync.pause() - with patch( - "homeassistant.components.stream.recorder.RecorderOutput.cleanup" - ) as mock_cleanup: + with patch("homeassistant.components.stream.IdleTimer.fire") as mock_timeout: # Setup demo track source = generate_h264_video() stream = preload_stream(hass, source) - recorder = stream.add_provider("recorder") + recorder = stream.add_provider("recorder", timeout=30) stream.start() await recorder.recv() @@ -122,7 +120,7 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert mock_cleanup.called + assert mock_timeout.called stream_worker_sync.resume() stream.stop() diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 8196899dcf9a84..91d02664d744d3 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -132,7 +132,6 @@ def __init__(self): self.segments = [] self.audio_packets = [] self.video_packets = [] - self.finished = False def add_stream(self, template=None): """Create an output buffer that captures packets for test to examine.""" @@ -162,11 +161,7 @@ def close(self): def capture_output_segment(self, segment): """Capture the output segment for tests to inspect.""" - assert not self.finished - if segment is None: - self.finished = True - else: - self.segments.append(segment) + self.segments.append(segment) class MockPyAv: @@ -223,7 +218,6 @@ async def test_stream_worker_success(hass): decoded_stream = await async_decode_stream( hass, PacketSequence(TEST_SEQUENCE_LENGTH) ) - assert decoded_stream.finished segments = decoded_stream.segments # Check number of segments. A segment is only formed when a packet from the next # segment arrives, hence the subtraction of one from the sequence length. @@ -243,7 +237,6 @@ async def test_skip_out_of_order_packet(hass): packets[OUT_OF_ORDER_PACKET_INDEX].dts = -9090 decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments # Check sequence numbers assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) @@ -279,7 +272,6 @@ async def test_discard_old_packets(hass): packets[OUT_OF_ORDER_PACKET_INDEX - 1].dts = 9090 decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments # Check number of segments assert len(segments) == int((OUT_OF_ORDER_PACKET_INDEX - 1) * SEGMENTS_PER_PACKET) @@ -299,7 +291,6 @@ async def test_packet_overflow(hass): packets[OUT_OF_ORDER_PACKET_INDEX].dts = -9000000 decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments # Check number of segments assert len(segments) == int((OUT_OF_ORDER_PACKET_INDEX - 1) * SEGMENTS_PER_PACKET) @@ -321,7 +312,6 @@ async def test_skip_initial_bad_packets(hass): packets[i].dts = None decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments # Check number of segments assert len(segments) == int( @@ -345,7 +335,6 @@ async def test_too_many_initial_bad_packets_fails(hass): packets[i].dts = None decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments assert len(segments) == 0 assert len(decoded_stream.video_packets) == 0 @@ -363,7 +352,6 @@ async def test_skip_missing_dts(hass): packets[i].dts = None decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments # Check sequence numbers assert all([segments[i].sequence == i + 1 for i in range(len(segments))]) @@ -387,7 +375,6 @@ async def test_too_many_bad_packets(hass): packets[i].dts = None decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments assert len(segments) == int((bad_packet_start - 1) * SEGMENTS_PER_PACKET) assert len(decoded_stream.video_packets) == bad_packet_start @@ -402,7 +389,6 @@ async def test_no_video_stream(hass): hass, PacketSequence(TEST_SEQUENCE_LENGTH), py_av=py_av ) # Note: This failure scenario does not output an end of stream - assert not decoded_stream.finished segments = decoded_stream.segments assert len(segments) == 0 assert len(decoded_stream.video_packets) == 0 @@ -417,7 +403,6 @@ async def test_audio_packets_not_found(hass): packets = PacketSequence(num_packets) # Contains only video packets decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) - assert decoded_stream.finished segments = decoded_stream.segments assert len(segments) == int((num_packets - 1) * SEGMENTS_PER_PACKET) assert len(decoded_stream.video_packets) == num_packets @@ -439,7 +424,6 @@ async def test_audio_is_first_packet(hass): packets[2].pts = packets[3].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) - assert decoded_stream.finished segments = decoded_stream.segments # The audio packets are segmented with the video packets assert len(segments) == int((num_packets - 2 - 1) * SEGMENTS_PER_PACKET) @@ -458,7 +442,6 @@ async def test_audio_packets_found(hass): packets[1].pts = packets[0].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) - assert decoded_stream.finished segments = decoded_stream.segments # The audio packet above is buffered with the video packet assert len(segments) == int((num_packets - 1 - 1) * SEGMENTS_PER_PACKET) @@ -477,7 +460,6 @@ async def test_pts_out_of_order(hass): packets[i].is_keyframe = False decoded_stream = await async_decode_stream(hass, iter(packets)) - assert decoded_stream.finished segments = decoded_stream.segments # Check number of segments assert len(segments) == int((TEST_SEQUENCE_LENGTH - 1) * SEGMENTS_PER_PACKET) From 86fe5d0561373c3a55028be93e8307f242cc2b9e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Feb 2021 16:42:33 +0100 Subject: [PATCH 0292/1818] Update frontend to 20210208.0 (#46225) --- homeassistant/components/frontend/manifest.json | 10 +++++++--- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 65a5497d1f9e56..7faac6c99cb346 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,9 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20210127.7"], + "requirements": [ + "home-assistant-frontend==20210208.0" + ], "dependencies": [ "api", "auth", @@ -15,6 +17,8 @@ "system_log", "websocket_api" ], - "codeowners": ["@home-assistant/frontend"], + "codeowners": [ + "@home-assistant/frontend" + ], "quality_scale": "internal" -} +} \ No newline at end of file diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 45a871081d1dd9..d926471660ea2f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210127.7 +home-assistant-frontend==20210208.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 6a8b5f63fa280b..a28eee2096efe9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,7 +765,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210127.7 +home-assistant-frontend==20210208.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 54f6651ad0cf10..f2f022e9ffe5ae 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -405,7 +405,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210127.7 +home-assistant-frontend==20210208.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 1b194e3b2f12aac2c3260dd7b20729b9e225478f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Mon, 8 Feb 2021 17:08:13 +0100 Subject: [PATCH 0293/1818] Add noltari to Tado code owners (#46216) --- CODEOWNERS | 2 +- homeassistant/components/tado/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7c45a66e166c77..0e33076105f0ef 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -458,7 +458,7 @@ homeassistant/components/syncthru/* @nielstron homeassistant/components/synology_dsm/* @hacf-fr @Quentame @mib1185 homeassistant/components/synology_srm/* @aerialls homeassistant/components/syslog/* @fabaff -homeassistant/components/tado/* @michaelarnauts @bdraco +homeassistant/components/tado/* @michaelarnauts @bdraco @noltari homeassistant/components/tag/* @balloob @dmulcahey homeassistant/components/tahoma/* @philklei homeassistant/components/tankerkoenig/* @guillempages diff --git a/homeassistant/components/tado/manifest.json b/homeassistant/components/tado/manifest.json index 9b166027df3c06..27c7ecff4116d9 100644 --- a/homeassistant/components/tado/manifest.json +++ b/homeassistant/components/tado/manifest.json @@ -3,7 +3,7 @@ "name": "Tado", "documentation": "https://www.home-assistant.io/integrations/tado", "requirements": ["python-tado==0.10.0"], - "codeowners": ["@michaelarnauts", "@bdraco"], + "codeowners": ["@michaelarnauts", "@bdraco", "@noltari"], "config_flow": true, "homekit": { "models": ["tado", "AC02"] From e27619fe5041e088e7dd413a79ded103fe68fd12 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Feb 2021 17:19:55 +0100 Subject: [PATCH 0294/1818] Allow discovery info accessible from CORS enabled domains (#46226) --- homeassistant/components/api/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index f383f982abc87b..e7bac8532eec63 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -178,6 +178,7 @@ class APIDiscoveryView(HomeAssistantView): requires_auth = False url = URL_API_DISCOVERY_INFO name = "api:discovery" + cors_allowed = True async def get(self, request): """Get discovery information.""" From be779d8712bf8ffbaeb1b786c7649381be69f1e5 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 8 Feb 2021 17:56:19 +0100 Subject: [PATCH 0295/1818] update discovery scheme for zwave_js light platform (#46082) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index d741946a1c9a7b..03593ab79ad102 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -130,6 +130,7 @@ class ZWaveDiscoverySchema: "Multilevel Remote Switch", "Multilevel Power Switch", "Multilevel Scene Switch", + "Unused", }, command_class={CommandClass.SWITCH_MULTILEVEL}, property={"currentValue"}, From 829131fe51bd09ff9032766c57bb293c89db7614 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 8 Feb 2021 17:57:22 +0100 Subject: [PATCH 0296/1818] Update zwave_js discovery scheme for boolean sensors in the Alarm CC (#46085) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 03593ab79ad102..be6d9b698d453e 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -143,6 +143,7 @@ class ZWaveDiscoverySchema: command_class={ CommandClass.SENSOR_BINARY, CommandClass.BATTERY, + CommandClass.SENSOR_ALARM, }, type={"boolean"}, ), From 71d7ae5992c8a2241c6ddbb73492264cb6688f00 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 11:48:02 -1000 Subject: [PATCH 0297/1818] Downgrade and improve lutron caseta LIP error message (#46236) --- homeassistant/components/lutron_caseta/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 73eb0b83fa6e35..220096fe0bfed6 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -158,7 +158,11 @@ async def async_setup_lip(hass, config_entry, lip_devices): try: await lip.async_connect(host) except asyncio.TimeoutError: - _LOGGER.error("Failed to connect to via LIP at %s:23", host) + _LOGGER.warning( + "Failed to connect to via LIP at %s:23, Pico and Shade remotes will not be available; " + "Enable Telnet Support in the Lutron app under Settings >> Advanced >> Integration", + host, + ) return _LOGGER.debug("Connected to Lutron Caseta bridge via LIP at %s:23", host) From fcae8406418694566167dbf25de513366f7d6356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Mon, 8 Feb 2021 22:49:46 +0100 Subject: [PATCH 0298/1818] Fix Tado Power and Link binary sensors (#46235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Power and Link aren't converted from strings to booleans by python-tado, so we need to properly parse before assigning the string value to binary sensors. Fixes: 067f2d0098d1 ("Add tado zone binary sensors (#44576)") Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/tado/binary_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index 1acefdb4c1689b..71b529310131bd 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -244,10 +244,10 @@ def _async_update_zone_data(self): return if self.zone_variable == "power": - self._state = self._tado_zone_data.power + self._state = self._tado_zone_data.power == "ON" elif self.zone_variable == "link": - self._state = self._tado_zone_data.link + self._state = self._tado_zone_data.link == "ONLINE" elif self.zone_variable == "overlay": self._state = self._tado_zone_data.overlay_active From c0a1fc2916207190024146f958612aba0f91c2b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 11:51:46 -1000 Subject: [PATCH 0299/1818] Handle empty mylink response at startup (#46241) --- homeassistant/components/somfy_mylink/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index d15ea029530eef..d371fd96310ca0 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -108,6 +108,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): ) return False + if "result" not in mylink_status: + raise ConfigEntryNotReady("The Somfy MyLink device returned an empty result") + _async_migrate_entity_config(hass, entry, mylink_status) undo_listener = entry.add_update_listener(_async_update_listener) From 00bbf8c3a2c566dfde58639e8710fda14e6b5111 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 16:52:28 -0500 Subject: [PATCH 0300/1818] Use core constants for group component (#46239) --- homeassistant/components/group/__init__.py | 3 +-- homeassistant/components/group/reproduce_state.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 32a9bd41014cf0..f185601ce87e1f 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -13,6 +13,7 @@ ATTR_ENTITY_ID, ATTR_ICON, ATTR_NAME, + CONF_ENTITIES, CONF_ICON, CONF_NAME, ENTITY_MATCH_ALL, @@ -41,7 +42,6 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}" -CONF_ENTITIES = "entities" CONF_ALL = "all" ATTR_ADD_ENTITIES = "add_entities" @@ -345,7 +345,6 @@ async def groups_service_handler(service): async def _process_group_platform(hass, domain, platform): """Process a group platform.""" - current_domain.set(domain) platform.async_describe_on_off_states(hass, hass.data[REG_KEY]) diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index 95915412e4f997..adeb0cfee0aee7 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -16,7 +16,6 @@ async def async_reproduce_states( reproduce_options: Optional[Dict[str, Any]] = None, ) -> None: """Reproduce component states.""" - states_copy = [] for state in states: members = get_entity_ids(hass, state.entity_id) From c2302784c27d824b48bdb997a9e47438da704fd0 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 16:53:17 -0500 Subject: [PATCH 0301/1818] Use core constants for helpers (#46240) --- homeassistant/helpers/collection.py | 1 - homeassistant/helpers/config_validation.py | 2 -- homeassistant/helpers/entity.py | 1 - homeassistant/helpers/event.py | 4 ---- homeassistant/helpers/frame.py | 1 - homeassistant/helpers/httpx_client.py | 1 - homeassistant/helpers/reload.py | 3 --- homeassistant/helpers/service.py | 2 +- homeassistant/helpers/storage.py | 1 - homeassistant/helpers/sun.py | 1 - homeassistant/helpers/template.py | 1 - homeassistant/helpers/update_coordinator.py | 1 - 12 files changed, 1 insertion(+), 18 deletions(-) diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 4af524bbbc9ef3..abeef0f0d680a1 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -136,7 +136,6 @@ class YamlCollection(ObservableCollection): async def async_load(self, data: List[dict]) -> None: """Load the YAML collection. Overrides existing data.""" - old_ids = set(self.data) change_sets = [] diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 4af4744e509d9d..6f1c6f465997da 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -549,7 +549,6 @@ def temperature_unit(value: Any) -> str: def template(value: Optional[Any]) -> template_helper.Template: """Validate a jinja2 template.""" - if value is None: raise vol.Invalid("template value is None") if isinstance(value, (list, dict, template_helper.Template)): @@ -566,7 +565,6 @@ def template(value: Optional[Any]) -> template_helper.Template: def dynamic_template(value: Optional[Any]) -> template_helper.Template: """Validate a dynamic (non static) jinja2 template.""" - if value is None: raise vol.Invalid("template value is None") if isinstance(value, (list, dict, template_helper.Template)): diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 04c07ef0f36f1b..7d0e38ab119bff 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -65,7 +65,6 @@ def async_generate_entity_id( hass: Optional[HomeAssistant] = None, ) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" - name = (name or DEVICE_DEFAULT_NAME).lower() preferred_string = entity_id_format.format(slugify(name)) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 44cbd89fde7f06..102a84863bd767 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -299,7 +299,6 @@ def _async_remove_indexed_listeners( job: HassJob, ) -> None: """Remove a listener.""" - callbacks = hass.data[data_key] for storage_key in storage_keys: @@ -686,7 +685,6 @@ def async_track_template( Callable to unregister the listener. """ - job = HassJob(action) @callback @@ -1105,7 +1103,6 @@ def async_track_point_in_time( point_in_time: datetime, ) -> CALLBACK_TYPE: """Add a listener that fires once after a specific point in time.""" - job = action if isinstance(action, HassJob) else HassJob(action) @callback @@ -1329,7 +1326,6 @@ def async_track_utc_time_change( local: bool = False, ) -> CALLBACK_TYPE: """Add a listener that will fire if time matches a pattern.""" - job = HassJob(action) # We do not have to wrap the function with time pattern matching logic # if no pattern given diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index def2508ff925e1..a0517338ec89fb 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -70,7 +70,6 @@ def report_integration( Async friendly. """ - found_frame, integration, path = integration_frame index = found_frame.filename.index(path) diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index 0f1719b388de5d..b86223964b3231 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -51,7 +51,6 @@ def create_async_httpx_client( This method must be run in the event loop. """ - client = httpx.AsyncClient( verify=verify_ssl, headers={USER_AGENT: SERVER_SOFTWARE}, diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index 8ff454eab6fe47..4a768a7932080f 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -157,13 +157,11 @@ async def async_setup_reload_service( hass: HomeAssistantType, domain: str, platforms: Iterable ) -> None: """Create the reload service for the domain.""" - if hass.services.has_service(domain, SERVICE_RELOAD): return async def _reload_config(call: Event) -> None: """Reload the platforms.""" - await async_reload_integration_platforms(hass, domain, platforms) hass.bus.async_fire(f"event_{domain}_reloaded", context=call.context) @@ -176,7 +174,6 @@ def setup_reload_service( hass: HomeAssistantType, domain: str, platforms: Iterable ) -> None: """Sync version of async_setup_reload_service.""" - asyncio.run_coroutine_threadsafe( async_setup_reload_service(hass, domain, platforms), hass.loop, diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index c83fa4a7763fff..13dcd779b25747 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -26,6 +26,7 @@ ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_SERVICE, + CONF_SERVICE_DATA, CONF_SERVICE_TEMPLATE, CONF_TARGET, ENTITY_MATCH_ALL, @@ -62,7 +63,6 @@ CONF_SERVICE_ENTITY_ID = "entity_id" -CONF_SERVICE_DATA = "data" CONF_SERVICE_DATA_TEMPLATE = "data_template" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index a969b2cad9a7ee..2bc13fbdf44190 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -202,7 +202,6 @@ async def _async_callback_final_write(self, _event): async def _async_handle_write_data(self, *_args): """Handle writing the config.""" - async with self._write_lock: self._async_cleanup_delay_listener() self._async_cleanup_final_write_listener() diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 818010c3410599..a2385ba397c808 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -19,7 +19,6 @@ @bind_hass def get_astral_location(hass: HomeAssistantType) -> "astral.Location": """Get an astral location for the current Home Assistant configuration.""" - from astral import Location # pylint: disable=import-outside-toplevel latitude = hass.config.latitude diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index af63cab10eb95a..9da0cbc09eb89a 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1292,7 +1292,6 @@ def relative_time(value): If the input are not a datetime object the input will be returned unmodified. """ - if not isinstance(value, datetime): return value if not value.tzinfo: diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index b2424a06927d15..8ba355e648974a 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -264,7 +264,6 @@ async def async_update(self) -> None: Only used by the generic entity update service. """ - # Ignore manual update requests if the entity is disabled if not self.enabled: return From 6b340415b2b6b3585d835f6e17aec7914fa29cd2 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 16:53:46 -0500 Subject: [PATCH 0302/1818] Use core constants for greeneye_monitor (#46238) --- homeassistant/components/greeneye_monitor/__init__.py | 5 ++--- homeassistant/components/greeneye_monitor/sensor.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/greeneye_monitor/__init__.py b/homeassistant/components/greeneye_monitor/__init__.py index 697a96649ab4a1..51471739e98b65 100644 --- a/homeassistant/components/greeneye_monitor/__init__.py +++ b/homeassistant/components/greeneye_monitor/__init__.py @@ -7,6 +7,8 @@ from homeassistant.const import ( CONF_NAME, CONF_PORT, + CONF_SENSOR_TYPE, + CONF_SENSORS, CONF_TEMPERATURE_UNIT, EVENT_HOMEASSISTANT_STOP, TIME_HOURS, @@ -27,8 +29,6 @@ CONF_NUMBER = "number" CONF_PULSE_COUNTERS = "pulse_counters" CONF_SERIAL_NUMBER = "serial_number" -CONF_SENSORS = "sensors" -CONF_SENSOR_TYPE = "sensor_type" CONF_TEMPERATURE_SENSORS = "temperature_sensors" CONF_TIME_UNIT = "time_unit" CONF_VOLTAGE_SENSORS = "voltage" @@ -119,7 +119,6 @@ async def async_setup(hass, config): """Set up the GreenEye Monitor component.""" - monitors = Monitors() hass.data[DATA_GREENEYE_MONITOR] = monitors diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index c8cd1669f0594b..f026bdfe3a4e28 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -1,6 +1,7 @@ """Support for the sensors in a GreenEye Monitor.""" from homeassistant.const import ( CONF_NAME, + CONF_SENSOR_TYPE, CONF_TEMPERATURE_UNIT, POWER_WATT, TIME_HOURS, @@ -16,7 +17,6 @@ CONF_MONITOR_SERIAL_NUMBER, CONF_NET_METERING, CONF_NUMBER, - CONF_SENSOR_TYPE, CONF_TIME_UNIT, DATA_GREENEYE_MONITOR, SENSOR_TYPE_CURRENT, From dc26fd51495348b3e9f7a8a2e39c3fa8b2091d76 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 12:22:38 -1000 Subject: [PATCH 0303/1818] Ensure creating an index that already exists is forgiving for postgresql (#46185) Unlikely sqlite and mysql, postgresql throws ProgrammingError instead of InternalError or OperationalError when trying to create an index that already exists. --- .../components/recorder/migration.py | 16 +++++----- tests/components/recorder/test_migrate.py | 30 ++++++++++++++++++- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 4501b25385e0c0..aeb62cc111da0a 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -3,7 +3,12 @@ from sqlalchemy import ForeignKeyConstraint, MetaData, Table, text from sqlalchemy.engine import reflection -from sqlalchemy.exc import InternalError, OperationalError, SQLAlchemyError +from sqlalchemy.exc import ( + InternalError, + OperationalError, + ProgrammingError, + SQLAlchemyError, +) from sqlalchemy.schema import AddConstraint, DropConstraint from .const import DOMAIN @@ -69,7 +74,7 @@ def _create_index(engine, table_name, index_name): ) try: index.create(engine) - except OperationalError as err: + except (InternalError, ProgrammingError, OperationalError) as err: lower_err_str = str(err).lower() if "already exists" not in lower_err_str and "duplicate" not in lower_err_str: @@ -78,13 +83,6 @@ def _create_index(engine, table_name, index_name): _LOGGER.warning( "Index %s already exists on %s, continuing", index_name, table_name ) - except InternalError as err: - if "duplicate" not in str(err).lower(): - raise - - _LOGGER.warning( - "Index %s already exists on %s, continuing", index_name, table_name - ) _LOGGER.debug("Finished creating %s", index_name) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index d10dad43d7518d..c29dad2d495fa2 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -1,9 +1,10 @@ """The tests for the Recorder component.""" # pylint: disable=protected-access -from unittest.mock import call, patch +from unittest.mock import Mock, PropertyMock, call, patch import pytest from sqlalchemy import create_engine +from sqlalchemy.exc import InternalError, OperationalError, ProgrammingError from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component @@ -79,3 +80,30 @@ def test_forgiving_add_index(): engine = create_engine("sqlite://", poolclass=StaticPool) models.Base.metadata.create_all(engine) migration._create_index(engine, "states", "ix_states_context_id") + + +@pytest.mark.parametrize( + "exception_type", [OperationalError, ProgrammingError, InternalError] +) +def test_forgiving_add_index_with_other_db_types(caplog, exception_type): + """Test that add index will continue if index exists on mysql and postgres.""" + mocked_index = Mock() + type(mocked_index).name = "ix_states_context_id" + mocked_index.create = Mock( + side_effect=exception_type( + "CREATE INDEX ix_states_old_state_id ON states (old_state_id);", + [], + 'relation "ix_states_old_state_id" already exists', + ) + ) + + mocked_table = Mock() + type(mocked_table).indexes = PropertyMock(return_value=[mocked_index]) + + with patch( + "homeassistant.components.recorder.migration.Table", return_value=mocked_table + ): + migration._create_index(Mock(), "states", "ix_states_context_id") + + assert "already exists on states" in caplog.text + assert "continuing" in caplog.text From 6467eff09cf66e035c1003577e1422d8e55808ba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 12:23:02 -1000 Subject: [PATCH 0304/1818] Fix incorrect current temperature for homekit water heaters (#46076) --- .../components/homekit/type_thermostats.py | 13 +++++++++---- tests/components/homekit/test_type_thermostats.py | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 54e2e9f92a8616..a1c1343261470c 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -595,10 +595,15 @@ def set_target_temperature(self, value): def async_update_state(self, new_state): """Update water_heater state after state change.""" # Update current and target temperature - temperature = _get_target_temperature(new_state, self._unit) - if temperature is not None: - if temperature != self.char_current_temp.value: - self.char_target_temp.set_value(temperature) + target_temperature = _get_target_temperature(new_state, self._unit) + if target_temperature is not None: + if target_temperature != self.char_target_temp.value: + self.char_target_temp.set_value(target_temperature) + + current_temperature = _get_current_temperature(new_state, self._unit) + if current_temperature is not None: + if current_temperature != self.char_current_temp.value: + self.char_current_temp.set_value(current_temperature) # Update display units if self._unit and self._unit in UNIT_HASS_TO_HOMEKIT: diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index ce17cf7ea07e05..79b5ca2109752f 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -1599,11 +1599,15 @@ async def test_water_heater(hass, hk_driver, events): hass.states.async_set( entity_id, HVAC_MODE_HEAT, - {ATTR_HVAC_MODE: HVAC_MODE_HEAT, ATTR_TEMPERATURE: 56.0}, + { + ATTR_HVAC_MODE: HVAC_MODE_HEAT, + ATTR_TEMPERATURE: 56.0, + ATTR_CURRENT_TEMPERATURE: 35.0, + }, ) await hass.async_block_till_done() assert acc.char_target_temp.value == 56.0 - assert acc.char_current_temp.value == 50.0 + assert acc.char_current_temp.value == 35.0 assert acc.char_target_heat_cool.value == 1 assert acc.char_current_heat_cool.value == 1 assert acc.char_display_units.value == 0 From 58b9394a5f8acf2f06beef05065fd76d24c27a69 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Fri, 5 Feb 2021 02:48:47 -0700 Subject: [PATCH 0305/1818] Fix zwave_js cover control for Up/Down and Open/Close (#45965) * Fix issue with control of cover when the target value is Up/Down instead of Open/Close * Adjust open/close/stop cover control to account for no Open/Up or Close/Down targets * Revert back to using values of 0/99 to close/open a cover since it is supported by all covers * Replace RELEASE_BUTTON with False and remove unused PRESS_BUTTON in zwave_js cover --- homeassistant/components/zwave_js/cover.py | 20 ++++++++--------- tests/components/zwave_js/test_cover.py | 26 +++++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 5f473f809575bf..b86cbeba944df7 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -21,8 +21,6 @@ LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE -PRESS_BUTTON = True -RELEASE_BUTTON = False async def async_setup_entry( @@ -79,17 +77,19 @@ async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - target_value = self.get_zwave_value("Open") - await self.info.node.async_set_value(target_value, PRESS_BUTTON) + target_value = self.get_zwave_value("targetValue") + await self.info.node.async_set_value(target_value, 99) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - target_value = self.get_zwave_value("Close") - await self.info.node.async_set_value(target_value, PRESS_BUTTON) + target_value = self.get_zwave_value("targetValue") + await self.info.node.async_set_value(target_value, 0) async def async_stop_cover(self, **kwargs: Any) -> None: """Stop cover.""" - target_value = self.get_zwave_value("Open") - await self.info.node.async_set_value(target_value, RELEASE_BUTTON) - target_value = self.get_zwave_value("Close") - await self.info.node.async_set_value(target_value, RELEASE_BUTTON) + target_value = self.get_zwave_value("Open") or self.get_zwave_value("Up") + if target_value: + await self.info.node.async_set_value(target_value, False) + target_value = self.get_zwave_value("Close") or self.get_zwave_value("Down") + if target_value: + await self.info.node.async_set_value(target_value, False) diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index f014245a5f8a2d..52e0a444ec9c9e 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -95,14 +95,16 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "Open", - "propertyName": "Open", + "property": "targetValue", + "propertyName": "targetValue", "metadata": { - "type": "boolean", + "label": "Target value", + "max": 99, + "min": 0, + "type": "number", "readable": True, "writeable": True, - "label": "Perform a level change (Open)", - "ccSpecific": {"switchType": 3}, + "label": "Target value", }, } assert args["value"] @@ -194,17 +196,19 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): "commandClassName": "Multilevel Switch", "commandClass": 38, "endpoint": 0, - "property": "Close", - "propertyName": "Close", + "property": "targetValue", + "propertyName": "targetValue", "metadata": { - "type": "boolean", + "label": "Target value", + "max": 99, + "min": 0, + "type": "number", "readable": True, "writeable": True, - "label": "Perform a level change (Close)", - "ccSpecific": {"switchType": 3}, + "label": "Target value", }, } - assert args["value"] + assert args["value"] == 0 client.async_send_command.reset_mock() From 88485d384c2147ec7ed210f47c1b985f4b39314c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Conde=20G=C3=B3mez?= Date: Fri, 5 Feb 2021 22:39:31 +0100 Subject: [PATCH 0306/1818] Fix foscam to work again with non-admin accounts and make RTSP port configurable again (#45975) * Do not require admin account for foscam cameras. Foscam cameras require admin account for getting the MAC address, requiring an admin account in the integration is not desirable as an operator one is good enough (and a good practice). Old entries using the MAC address as unique_id are migrated to the new unique_id format so everything is consistent. Also fixed unhandled invalid responses from the camera in the config flow process. * Make RTSP port configurable again as some cameras reports wrong port * Remove periods from new log lines * Set new Config Flow version to 2 and adjust the entity migration * Create a proper error message for the InvalidResponse exception * Change crafted unique_id to use entry_id in the entity * Abort if same host and port is already configured * Fix entry tracking to use entry_id instead of unique_id * Remove unique_id from mocked config entry in tests --- homeassistant/components/foscam/__init__.py | 53 +++++++- homeassistant/components/foscam/camera.py | 45 ++++--- .../components/foscam/config_flow.py | 54 +++++++- homeassistant/components/foscam/const.py | 1 + homeassistant/components/foscam/strings.json | 2 + .../components/foscam/translations/en.json | 2 + tests/components/foscam/test_config_flow.py | 124 +++++++++++++++--- 7 files changed, 234 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/foscam/__init__.py b/homeassistant/components/foscam/__init__.py index e5b82817d4bea4..6a2c961544f69d 100644 --- a/homeassistant/components/foscam/__init__.py +++ b/homeassistant/components/foscam/__init__.py @@ -1,10 +1,15 @@ """The foscam component.""" import asyncio +from libpyfoscam import FoscamCamera + from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_registry import async_migrate_entries -from .const import DOMAIN, SERVICE_PTZ, SERVICE_PTZ_PRESET +from .config_flow import DEFAULT_RTSP_PORT +from .const import CONF_RTSP_PORT, DOMAIN, LOGGER, SERVICE_PTZ, SERVICE_PTZ_PRESET PLATFORMS = ["camera"] @@ -22,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.config_entries.async_forward_entry_setup(entry, component) ) - hass.data[DOMAIN][entry.unique_id] = entry.data + hass.data[DOMAIN][entry.entry_id] = entry.data return True @@ -39,10 +44,50 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) if unload_ok: - hass.data[DOMAIN].pop(entry.unique_id) + hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.services.async_remove(domain=DOMAIN, service=SERVICE_PTZ) hass.services.async_remove(domain=DOMAIN, service=SERVICE_PTZ_PRESET) return unload_ok + + +async def async_migrate_entry(hass, config_entry: ConfigEntry): + """Migrate old entry.""" + LOGGER.debug("Migrating from version %s", config_entry.version) + + if config_entry.version == 1: + # Change unique id + @callback + def update_unique_id(entry): + return {"new_unique_id": config_entry.entry_id} + + await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + + config_entry.unique_id = None + + # Get RTSP port from the camera or use the fallback one and store it in data + camera = FoscamCamera( + config_entry.data[CONF_HOST], + config_entry.data[CONF_PORT], + config_entry.data[CONF_USERNAME], + config_entry.data[CONF_PASSWORD], + verbose=False, + ) + + ret, response = await hass.async_add_executor_job(camera.get_port_info) + + rtsp_port = DEFAULT_RTSP_PORT + + if ret != 0: + rtsp_port = response.get("rtspPort") or response.get("mediaPort") + + config_entry.data = {**config_entry.data, CONF_RTSP_PORT: rtsp_port} + + # Change entry version + config_entry.version = 2 + + LOGGER.info("Migration to version %s successful", config_entry.version) + + return True diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index f66ad31c2a827d..d600546c3b04ed 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -15,7 +15,14 @@ ) from homeassistant.helpers import config_validation as cv, entity_platform -from .const import CONF_STREAM, DOMAIN, LOGGER, SERVICE_PTZ, SERVICE_PTZ_PRESET +from .const import ( + CONF_RTSP_PORT, + CONF_STREAM, + DOMAIN, + LOGGER, + SERVICE_PTZ, + SERVICE_PTZ_PRESET, +) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -24,7 +31,7 @@ vol.Required(CONF_USERNAME): cv.string, vol.Optional(CONF_NAME, default="Foscam Camera"): cv.string, vol.Optional(CONF_PORT, default=88): cv.port, - vol.Optional("rtsp_port"): cv.port, + vol.Optional(CONF_RTSP_PORT): cv.port, } ) @@ -71,6 +78,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_USERNAME: config[CONF_USERNAME], CONF_PASSWORD: config[CONF_PASSWORD], CONF_STREAM: "Main", + CONF_RTSP_PORT: config.get(CONF_RTSP_PORT, 554), } hass.async_create_task( @@ -134,8 +142,8 @@ def __init__(self, camera, config_entry): self._username = config_entry.data[CONF_USERNAME] self._password = config_entry.data[CONF_PASSWORD] self._stream = config_entry.data[CONF_STREAM] - self._unique_id = config_entry.unique_id - self._rtsp_port = None + self._unique_id = config_entry.entry_id + self._rtsp_port = config_entry.data[CONF_RTSP_PORT] self._motion_status = False async def async_added_to_hass(self): @@ -145,7 +153,13 @@ async def async_added_to_hass(self): self._foscam_session.get_motion_detect_config ) - if ret != 0: + if ret == -3: + LOGGER.info( + "Can't get motion detection status, camera %s configured with non-admin user", + self._name, + ) + + elif ret != 0: LOGGER.error( "Error getting motion detection status of %s: %s", self._name, ret ) @@ -153,17 +167,6 @@ async def async_added_to_hass(self): else: self._motion_status = response == 1 - # Get RTSP port - ret, response = await self.hass.async_add_executor_job( - self._foscam_session.get_port_info - ) - - if ret != 0: - LOGGER.error("Error getting RTSP port of %s: %s", self._name, ret) - - else: - self._rtsp_port = response.get("rtspPort") or response.get("mediaPort") - @property def unique_id(self): """Return the entity unique ID.""" @@ -205,6 +208,11 @@ def enable_motion_detection(self): ret = self._foscam_session.enable_motion_detection() if ret != 0: + if ret == -3: + LOGGER.info( + "Can't set motion detection status, camera %s configured with non-admin user", + self._name, + ) return self._motion_status = True @@ -220,6 +228,11 @@ def disable_motion_detection(self): ret = self._foscam_session.disable_motion_detection() if ret != 0: + if ret == -3: + LOGGER.info( + "Can't set motion detection status, camera %s configured with non-admin user", + self._name, + ) return self._motion_status = False diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index 7bb8cb50a5109a..bfeefb9e406055 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -1,6 +1,10 @@ """Config flow for foscam integration.""" from libpyfoscam import FoscamCamera -from libpyfoscam.foscam import ERROR_FOSCAM_AUTH, ERROR_FOSCAM_UNAVAILABLE +from libpyfoscam.foscam import ( + ERROR_FOSCAM_AUTH, + ERROR_FOSCAM_UNAVAILABLE, + FOSCAM_SUCCESS, +) import voluptuous as vol from homeassistant import config_entries, exceptions @@ -13,12 +17,13 @@ ) from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_STREAM, LOGGER +from .const import CONF_RTSP_PORT, CONF_STREAM, LOGGER from .const import DOMAIN # pylint:disable=unused-import STREAMS = ["Main", "Sub"] DEFAULT_PORT = 88 +DEFAULT_RTSP_PORT = 554 DATA_SCHEMA = vol.Schema( @@ -28,6 +33,7 @@ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(CONF_STREAM, default=STREAMS[0]): vol.In(STREAMS), + vol.Required(CONF_RTSP_PORT, default=DEFAULT_RTSP_PORT): int, } ) @@ -35,7 +41,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for foscam.""" - VERSION = 1 + VERSION = 2 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL async def _validate_and_create(self, data): @@ -43,6 +49,14 @@ async def _validate_and_create(self, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ + + for entry in self.hass.config_entries.async_entries(DOMAIN): + if ( + entry.data[CONF_HOST] == data[CONF_HOST] + and entry.data[CONF_PORT] == data[CONF_PORT] + ): + raise AbortFlow("already_configured") + camera = FoscamCamera( data[CONF_HOST], data[CONF_PORT], @@ -52,7 +66,7 @@ async def _validate_and_create(self, data): ) # Validate data by sending a request to the camera - ret, response = await self.hass.async_add_executor_job(camera.get_dev_info) + ret, _ = await self.hass.async_add_executor_job(camera.get_product_all_info) if ret == ERROR_FOSCAM_UNAVAILABLE: raise CannotConnect @@ -60,10 +74,23 @@ async def _validate_and_create(self, data): if ret == ERROR_FOSCAM_AUTH: raise InvalidAuth - await self.async_set_unique_id(response["mac"]) - self._abort_if_unique_id_configured() + if ret != FOSCAM_SUCCESS: + LOGGER.error( + "Unexpected error code from camera %s:%s: %s", + data[CONF_HOST], + data[CONF_PORT], + ret, + ) + raise InvalidResponse + + # Try to get camera name (only possible with admin account) + ret, response = await self.hass.async_add_executor_job(camera.get_dev_info) - name = data.pop(CONF_NAME, response["devName"]) + dev_name = response.get( + "devName", f"Foscam {data[CONF_HOST]}:{data[CONF_PORT]}" + ) + + name = data.pop(CONF_NAME, dev_name) return self.async_create_entry(title=name, data=data) @@ -81,6 +108,9 @@ async def async_step_user(self, user_input=None): except InvalidAuth: errors["base"] = "invalid_auth" + except InvalidResponse: + errors["base"] = "invalid_response" + except AbortFlow: raise @@ -105,6 +135,12 @@ async def async_step_import(self, import_config): LOGGER.error("Error importing foscam platform config: invalid auth.") return self.async_abort(reason="invalid_auth") + except InvalidResponse: + LOGGER.exception( + "Error importing foscam platform config: invalid response from camera." + ) + return self.async_abort(reason="invalid_response") + except AbortFlow: raise @@ -121,3 +157,7 @@ class CannotConnect(exceptions.HomeAssistantError): class InvalidAuth(exceptions.HomeAssistantError): """Error to indicate there is invalid auth.""" + + +class InvalidResponse(exceptions.HomeAssistantError): + """Error to indicate there is invalid response.""" diff --git a/homeassistant/components/foscam/const.py b/homeassistant/components/foscam/const.py index a42b430993e027..d5ac0f5c567d25 100644 --- a/homeassistant/components/foscam/const.py +++ b/homeassistant/components/foscam/const.py @@ -5,6 +5,7 @@ DOMAIN = "foscam" +CONF_RTSP_PORT = "rtsp_port" CONF_STREAM = "stream" SERVICE_PTZ = "ptz" diff --git a/homeassistant/components/foscam/strings.json b/homeassistant/components/foscam/strings.json index 6033fa099cd99a..5c0622af9d17b2 100644 --- a/homeassistant/components/foscam/strings.json +++ b/homeassistant/components/foscam/strings.json @@ -8,6 +8,7 @@ "port": "[%key:common::config_flow::data::port%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", + "rtsp_port": "RTSP port", "stream": "Stream" } } @@ -15,6 +16,7 @@ "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "invalid_response": "Invalid response from the device", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/homeassistant/components/foscam/translations/en.json b/homeassistant/components/foscam/translations/en.json index 3d1454a4ebd6c7..16a7d0b78001a5 100644 --- a/homeassistant/components/foscam/translations/en.json +++ b/homeassistant/components/foscam/translations/en.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", + "invalid_response": "Invalid response from the device", "unknown": "Unexpected error" }, "step": { @@ -14,6 +15,7 @@ "host": "Host", "password": "Password", "port": "Port", + "rtsp_port": "RTSP port", "stream": "Stream", "username": "Username" } diff --git a/tests/components/foscam/test_config_flow.py b/tests/components/foscam/test_config_flow.py index 8087ac1894f1a2..3b8910c4dbc94f 100644 --- a/tests/components/foscam/test_config_flow.py +++ b/tests/components/foscam/test_config_flow.py @@ -1,7 +1,12 @@ """Test the Foscam config flow.""" from unittest.mock import patch -from libpyfoscam.foscam import ERROR_FOSCAM_AUTH, ERROR_FOSCAM_UNAVAILABLE +from libpyfoscam.foscam import ( + ERROR_FOSCAM_AUTH, + ERROR_FOSCAM_CMD, + ERROR_FOSCAM_UNAVAILABLE, + ERROR_FOSCAM_UNKNOWN, +) from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.foscam import config_flow @@ -14,6 +19,13 @@ config_flow.CONF_USERNAME: "admin", config_flow.CONF_PASSWORD: "1234", config_flow.CONF_STREAM: "Main", + config_flow.CONF_RTSP_PORT: 554, +} +OPERATOR_CONFIG = { + config_flow.CONF_USERNAME: "operator", +} +INVALID_RESPONSE_CONFIG = { + config_flow.CONF_USERNAME: "interr", } CAMERA_NAME = "Mocked Foscam Camera" CAMERA_MAC = "C0:C1:D0:F4:B4:D4" @@ -23,26 +35,39 @@ def setup_mock_foscam_camera(mock_foscam_camera): """Mock FoscamCamera simulating behaviour using a base valid config.""" def configure_mock_on_init(host, port, user, passwd, verbose=False): - return_code = 0 - data = {} + product_all_info_rc = 0 + dev_info_rc = 0 + dev_info_data = {} if ( host != VALID_CONFIG[config_flow.CONF_HOST] or port != VALID_CONFIG[config_flow.CONF_PORT] ): - return_code = ERROR_FOSCAM_UNAVAILABLE + product_all_info_rc = dev_info_rc = ERROR_FOSCAM_UNAVAILABLE elif ( - user != VALID_CONFIG[config_flow.CONF_USERNAME] + user + not in [ + VALID_CONFIG[config_flow.CONF_USERNAME], + OPERATOR_CONFIG[config_flow.CONF_USERNAME], + INVALID_RESPONSE_CONFIG[config_flow.CONF_USERNAME], + ] or passwd != VALID_CONFIG[config_flow.CONF_PASSWORD] ): - return_code = ERROR_FOSCAM_AUTH + product_all_info_rc = dev_info_rc = ERROR_FOSCAM_AUTH + + elif user == INVALID_RESPONSE_CONFIG[config_flow.CONF_USERNAME]: + product_all_info_rc = dev_info_rc = ERROR_FOSCAM_UNKNOWN + + elif user == OPERATOR_CONFIG[config_flow.CONF_USERNAME]: + dev_info_rc = ERROR_FOSCAM_CMD else: - data["devName"] = CAMERA_NAME - data["mac"] = CAMERA_MAC + dev_info_data["devName"] = CAMERA_NAME + dev_info_data["mac"] = CAMERA_MAC - mock_foscam_camera.get_dev_info.return_value = (return_code, data) + mock_foscam_camera.get_product_all_info.return_value = (product_all_info_rc, {}) + mock_foscam_camera.get_dev_info.return_value = (dev_info_rc, dev_info_data) return mock_foscam_camera @@ -142,12 +167,44 @@ async def test_user_cannot_connect(hass): assert result["errors"] == {"base": "cannot_connect"} +async def test_user_invalid_response(hass): + """Test we handle invalid response error from user input.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.foscam.config_flow.FoscamCamera", + ) as mock_foscam_camera: + setup_mock_foscam_camera(mock_foscam_camera) + + invalid_response = VALID_CONFIG.copy() + invalid_response[config_flow.CONF_USERNAME] = INVALID_RESPONSE_CONFIG[ + config_flow.CONF_USERNAME + ] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + invalid_response, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_response"} + + async def test_user_already_configured(hass): """Test we handle already configured from user input.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC + domain=config_flow.DOMAIN, + data=VALID_CONFIG, ) entry.add_to_hass(hass) @@ -201,6 +258,8 @@ async def test_user_unknown_exception(hass): async def test_import_user_valid(hass): """Test valid config from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", ) as mock_foscam_camera, patch( @@ -229,6 +288,8 @@ async def test_import_user_valid(hass): async def test_import_user_valid_with_name(hass): """Test valid config with extra name from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", ) as mock_foscam_camera, patch( @@ -261,10 +322,7 @@ async def test_import_user_valid_with_name(hass): async def test_import_invalid_auth(hass): """Test we handle invalid auth from import.""" - entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC - ) - entry.add_to_hass(hass) + await setup.async_setup_component(hass, "persistent_notification", {}) with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", @@ -287,11 +345,8 @@ async def test_import_invalid_auth(hass): async def test_import_cannot_connect(hass): - """Test we handle invalid auth from import.""" - entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC - ) - entry.add_to_hass(hass) + """Test we handle cannot connect error from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) with patch( "homeassistant.components.foscam.config_flow.FoscamCamera", @@ -313,10 +368,39 @@ async def test_import_cannot_connect(hass): assert result["reason"] == "cannot_connect" +async def test_import_invalid_response(hass): + """Test we handle invalid response error from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "homeassistant.components.foscam.config_flow.FoscamCamera", + ) as mock_foscam_camera: + setup_mock_foscam_camera(mock_foscam_camera) + + invalid_response = VALID_CONFIG.copy() + invalid_response[config_flow.CONF_USERNAME] = INVALID_RESPONSE_CONFIG[ + config_flow.CONF_USERNAME + ] + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=invalid_response, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "invalid_response" + + async def test_import_already_configured(hass): """Test we handle already configured from import.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( - domain=config_flow.DOMAIN, data=VALID_CONFIG, unique_id=CAMERA_MAC + domain=config_flow.DOMAIN, + data=VALID_CONFIG, ) entry.add_to_hass(hass) From 285fd3d43ccdebf0fec21901c798d1ff41f8f813 Mon Sep 17 00:00:00 2001 From: Steven Rollason <2099542+gadgetchnnel@users.noreply.github.com> Date: Sat, 6 Feb 2021 13:05:50 +0000 Subject: [PATCH 0307/1818] Fix downloader path validation on subdir (#46061) --- homeassistant/components/downloader/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 94617ce43aa63b..3856df696ad00d 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -70,8 +70,9 @@ def do_download(): overwrite = service.data.get(ATTR_OVERWRITE) - # Check the path - raise_if_invalid_path(subdir) + if subdir: + # Check the path + raise_if_invalid_path(subdir) final_path = None From 1e59fa2cb2ed8a8663402391210b260e1e250abc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:12:23 +0100 Subject: [PATCH 0308/1818] Fix deprecated method isAlive() (#46062) --- homeassistant/components/zwave/light.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index 244b4a557e1ee3..52014e37eea278 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -175,7 +175,7 @@ def _refresh_value(): self._refreshing = True self.values.primary.refresh() - if self._timer is not None and self._timer.isAlive(): + if self._timer is not None and self._timer.is_alive(): self._timer.cancel() self._timer = Timer(self._delay, _refresh_value) From 96d8d432d7a078350a9490b93d5a133081615393 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Feb 2021 23:07:12 +0100 Subject: [PATCH 0309/1818] Improve deCONZ logbook to be more robust in different situations (#46063) --- homeassistant/components/deconz/logbook.py | 38 ++++++++++--- tests/components/deconz/test_logbook.py | 62 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/deconz/logbook.py b/homeassistant/components/deconz/logbook.py index 73c157ac8f67ff..85982244364fc3 100644 --- a/homeassistant/components/deconz/logbook.py +++ b/homeassistant/components/deconz/logbook.py @@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import Event -from .const import DOMAIN as DECONZ_DOMAIN +from .const import CONF_GESTURE, DOMAIN as DECONZ_DOMAIN from .deconz_event import CONF_DECONZ_EVENT, DeconzEvent from .device_trigger import ( CONF_BOTH_BUTTONS, @@ -107,8 +107,12 @@ def _get_device_event_description(modelid: str, event: str) -> tuple: device_event_descriptions: dict = REMOTES[modelid] for event_type_tuple, event_dict in device_event_descriptions.items(): - if event == event_dict[CONF_EVENT]: + if event == event_dict.get(CONF_EVENT): return event_type_tuple + if event == event_dict.get(CONF_GESTURE): + return event_type_tuple + + return (None, None) @callback @@ -125,15 +129,35 @@ def async_describe_deconz_event(event: Event) -> dict: hass, event.data[ATTR_DEVICE_ID] ) - if deconz_event.device.modelid not in REMOTES: + action = None + interface = None + data = event.data.get(CONF_EVENT) or event.data.get(CONF_GESTURE, "") + + if data and deconz_event.device.modelid in REMOTES: + action, interface = _get_device_event_description( + deconz_event.device.modelid, data + ) + + # Unknown event + if not data: return { "name": f"{deconz_event.device.name}", - "message": f"fired event '{event.data[CONF_EVENT]}'.", + "message": "fired an unknown event.", } - action, interface = _get_device_event_description( - deconz_event.device.modelid, event.data[CONF_EVENT] - ) + # No device event match + if not action: + return { + "name": f"{deconz_event.device.name}", + "message": f"fired event '{data}'.", + } + + # Gesture event + if not interface: + return { + "name": f"{deconz_event.device.name}", + "message": f"fired event '{ACTIONS[action]}'.", + } return { "name": f"{deconz_event.device.name}", diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 7315a766d5c7fd..500ca03b7edc10 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -3,6 +3,7 @@ from copy import deepcopy from homeassistant.components import logbook +from homeassistant.components.deconz.const import CONF_GESTURE from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID @@ -34,6 +35,23 @@ async def test_humanifying_deconz_event(hass): "config": {}, "uniqueid": "00:00:00:00:00:00:00:02-00", }, + "2": { + "id": "Xiaomi cube id", + "name": "Xiaomi cube", + "type": "ZHASwitch", + "modelid": "lumi.sensor_cube", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "3": { + "id": "faulty", + "name": "Faulty event", + "type": "ZHASwitch", + "state": {}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, } config_entry = await setup_deconz_integration(hass, get_state_response=data) gateway = get_gateway_from_config_entry(hass, config_entry) @@ -46,6 +64,7 @@ async def test_humanifying_deconz_event(hass): logbook.humanify( hass, [ + # Event without matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { @@ -55,6 +74,7 @@ async def test_humanifying_deconz_event(hass): CONF_UNIQUE_ID: gateway.events[0].serial, }, ), + # Event with matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { @@ -64,6 +84,36 @@ async def test_humanifying_deconz_event(hass): CONF_UNIQUE_ID: gateway.events[1].serial, }, ), + # Gesture with matching device trigger + MockLazyEventPartialState( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_GESTURE: 1, + CONF_ID: gateway.events[2].event_id, + CONF_UNIQUE_ID: gateway.events[2].serial, + }, + ), + # Unsupported device trigger + MockLazyEventPartialState( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_GESTURE: "unsupported_gesture", + CONF_ID: gateway.events[2].event_id, + CONF_UNIQUE_ID: gateway.events[2].serial, + }, + ), + # Unknown event + MockLazyEventPartialState( + CONF_DECONZ_EVENT, + { + CONF_DEVICE_ID: gateway.events[3].device_id, + "unknown_event": None, + CONF_ID: gateway.events[3].event_id, + CONF_UNIQUE_ID: gateway.events[3].serial, + }, + ), ], entity_attr_cache, {}, @@ -77,3 +127,15 @@ async def test_humanifying_deconz_event(hass): assert events[1]["name"] == "Hue remote" assert events[1]["domain"] == "deconz" assert events[1]["message"] == "'Long press' event for 'Dim up' was fired." + + assert events[2]["name"] == "Xiaomi cube" + assert events[2]["domain"] == "deconz" + assert events[2]["message"] == "fired event 'Shake'." + + assert events[3]["name"] == "Xiaomi cube" + assert events[3]["domain"] == "deconz" + assert events[3]["message"] == "fired event 'unsupported_gesture'." + + assert events[4]["name"] == "Faulty event" + assert events[4]["domain"] == "deconz" + assert events[4]["message"] == "fired an unknown event." From a9eb9d613663becc20b90b595d56a076d4bad6d4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Feb 2021 16:36:42 -0600 Subject: [PATCH 0310/1818] Fix zwave_js Notification CC sensors and binary sensors (#46072) * only include property key name in sensor name if it exists * add endpoint to binary_sensor and sensor notification CC entities if > 0 * refactor to have helper method generate name * change default behavior of generate_name * return value for notification sensor when we can't find the state * store generated name --- .../components/zwave_js/binary_sensor.py | 12 ++---- homeassistant/components/zwave_js/entity.py | 31 ++++++++++--- homeassistant/components/zwave_js/sensor.py | 43 +++++++++++-------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index f17d893e371d56..bb2e4355f168f2 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -293,6 +293,10 @@ def __init__( """Initialize a ZWaveNotificationBinarySensor entity.""" super().__init__(config_entry, client, info) self.state_key = state_key + self._name = self.generate_name( + self.info.primary_value.property_name, + [self.info.primary_value.metadata.states[self.state_key]], + ) # check if we have a custom mapping for this value self._mapping_info = self._get_sensor_mapping() @@ -301,14 +305,6 @@ def is_on(self) -> bool: """Return if the sensor is on or off.""" return int(self.info.primary_value.value) == int(self.state_key) - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" - node_name = self.info.node.name or self.info.node.device_config.description - value_name = self.info.primary_value.property_name - state_label = self.info.primary_value.metadata.states[self.state_key] - return f"{node_name}: {value_name} - {state_label}" - @property def device_class(self) -> Optional[str]: """Return device class.""" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 334a2cccd4faaa..08571ad5d8c318 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,7 +1,7 @@ """Generic Z-Wave Entity Class.""" import logging -from typing import Optional, Tuple, Union +from typing import List, Optional, Tuple, Union from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode @@ -35,6 +35,7 @@ def __init__( self.config_entry = config_entry self.client = client self.info = info + self._name = self.generate_name() # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} @@ -61,19 +62,35 @@ def device_info(self) -> dict: "identifiers": {get_device_id(self.client, self.info.node)}, } - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" + def generate_name( + self, + alternate_value_name: Optional[str] = None, + additional_info: Optional[List[str]] = None, + ) -> str: + """Generate entity name.""" + if additional_info is None: + additional_info = [] node_name = self.info.node.name or self.info.node.device_config.description value_name = ( - self.info.primary_value.metadata.label + alternate_value_name + or self.info.primary_value.metadata.label or self.info.primary_value.property_key_name or self.info.primary_value.property_name ) + name = f"{node_name}: {value_name}" + for item in additional_info: + if item: + name += f" - {item}" # append endpoint if > 1 if self.info.primary_value.endpoint > 1: - value_name += f" ({self.info.primary_value.endpoint})" - return f"{node_name}: {value_name}" + name += f" ({self.info.primary_value.endpoint})" + + return name + + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + return self._name @property def unique_id(self) -> str: diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 3d3f782bc1bd6d..78b536b81f7dcc 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -123,6 +123,17 @@ def unit_of_measurement(self) -> Optional[str]: class ZWaveNumericSensor(ZwaveSensorBase): """Representation of a Z-Wave Numeric sensor.""" + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveNumericSensor entity.""" + super().__init__(config_entry, client, info) + if self.info.primary_value.command_class == CommandClass.BASIC: + self._name = self.generate_name(self.info.primary_value.command_class_name) + @property def state(self) -> float: """Return state of the sensor.""" @@ -142,19 +153,23 @@ def unit_of_measurement(self) -> Optional[str]: return str(self.info.primary_value.metadata.unit) - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" - if self.info.primary_value.command_class == CommandClass.BASIC: - node_name = self.info.node.name or self.info.node.device_config.description - label = self.info.primary_value.command_class_name - return f"{node_name}: {label}" - return super().name - class ZWaveListSensor(ZwaveSensorBase): """Representation of a Z-Wave Numeric sensor with multiple states.""" + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveListSensor entity.""" + super().__init__(config_entry, client, info) + self._name = self.generate_name( + self.info.primary_value.property_name, + [self.info.primary_value.property_key_name], + ) + @property def state(self) -> Optional[str]: """Return state of the sensor.""" @@ -164,7 +179,7 @@ def state(self) -> Optional[str]: not str(self.info.primary_value.value) in self.info.primary_value.metadata.states ): - return None + return str(self.info.primary_value.value) return str( self.info.primary_value.metadata.states[str(self.info.primary_value.value)] ) @@ -174,11 +189,3 @@ def device_state_attributes(self) -> Optional[Dict[str, str]]: """Return the device specific state attributes.""" # add the value's int value as property for multi-value (list) items return {"value": self.info.primary_value.value} - - @property - def name(self) -> str: - """Return default name from device name and value name combination.""" - node_name = self.info.node.name or self.info.node.device_config.description - prop_name = self.info.primary_value.property_name - prop_key_name = self.info.primary_value.property_key_name - return f"{node_name}: {prop_name} - {prop_key_name}" From c57fa16a1ffc74bacf730dd5147b4626f945c693 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 12:23:02 -1000 Subject: [PATCH 0311/1818] Fix incorrect current temperature for homekit water heaters (#46076) --- .../components/homekit/type_thermostats.py | 13 +++++++++---- tests/components/homekit/test_type_thermostats.py | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 54e2e9f92a8616..a1c1343261470c 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -595,10 +595,15 @@ def set_target_temperature(self, value): def async_update_state(self, new_state): """Update water_heater state after state change.""" # Update current and target temperature - temperature = _get_target_temperature(new_state, self._unit) - if temperature is not None: - if temperature != self.char_current_temp.value: - self.char_target_temp.set_value(temperature) + target_temperature = _get_target_temperature(new_state, self._unit) + if target_temperature is not None: + if target_temperature != self.char_target_temp.value: + self.char_target_temp.set_value(target_temperature) + + current_temperature = _get_current_temperature(new_state, self._unit) + if current_temperature is not None: + if current_temperature != self.char_current_temp.value: + self.char_current_temp.set_value(current_temperature) # Update display units if self._unit and self._unit in UNIT_HASS_TO_HOMEKIT: diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index ce17cf7ea07e05..79b5ca2109752f 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -1599,11 +1599,15 @@ async def test_water_heater(hass, hk_driver, events): hass.states.async_set( entity_id, HVAC_MODE_HEAT, - {ATTR_HVAC_MODE: HVAC_MODE_HEAT, ATTR_TEMPERATURE: 56.0}, + { + ATTR_HVAC_MODE: HVAC_MODE_HEAT, + ATTR_TEMPERATURE: 56.0, + ATTR_CURRENT_TEMPERATURE: 35.0, + }, ) await hass.async_block_till_done() assert acc.char_target_temp.value == 56.0 - assert acc.char_current_temp.value == 50.0 + assert acc.char_current_temp.value == 35.0 assert acc.char_target_heat_cool.value == 1 assert acc.char_current_heat_cool.value == 1 assert acc.char_display_units.value == 0 From 483b1dfa61587fd4281cd295d4d2b928a7356c55 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 6 Feb 2021 13:17:52 +0100 Subject: [PATCH 0312/1818] Use async_update_entry rather than updating config_entry.data directly in Axis (#46078) Don't step version in migrate_entry to support rollbacking --- homeassistant/components/axis/__init__.py | 15 ++++++++++----- tests/components/axis/test_init.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index c467359c17e6ff..8722c41c3e0cbc 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -48,8 +48,11 @@ async def async_migrate_entry(hass, config_entry): # Flatten configuration but keep old data if user rollbacks HASS prior to 0.106 if config_entry.version == 1: - config_entry.data = {**config_entry.data, **config_entry.data[CONF_DEVICE]} - config_entry.unique_id = config_entry.data[CONF_MAC] + unique_id = config_entry.data[CONF_MAC] + data = {**config_entry.data, **config_entry.data[CONF_DEVICE]} + hass.config_entries.async_update_entry( + config_entry, unique_id=unique_id, data=data + ) config_entry.version = 2 # Normalise MAC address of device which also affects entity unique IDs @@ -66,10 +69,12 @@ def update_unique_id(entity_entry): ) } - await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) + if old_unique_id != new_unique_id: + await async_migrate_entries(hass, config_entry.entry_id, update_unique_id) - config_entry.unique_id = new_unique_id - config_entry.version = 3 + hass.config_entries.async_update_entry( + config_entry, unique_id=new_unique_id + ) _LOGGER.info("Migration to version %s successful", config_entry.version) diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index b7faceaf10d7bf..36a603ea7b339d 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -109,7 +109,7 @@ async def test_migrate_entry(hass): CONF_MODEL: "model", CONF_NAME: "name", } - assert entry.version == 3 + assert entry.version == 2 # Keep version to support rollbacking assert entry.unique_id == "00:40:8c:12:34:56" vmd4_entity = registry.async_get("binary_sensor.vmd4") From d16d6f3f3f5324cfccb04c6018d8ad48855de8af Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sat, 6 Feb 2021 14:02:03 +0100 Subject: [PATCH 0313/1818] Handle missing value in all platforms of zwave_js (#46081) --- homeassistant/components/zwave_js/climate.py | 15 +++++++++++++++ homeassistant/components/zwave_js/cover.py | 12 +++++++++--- homeassistant/components/zwave_js/entity.py | 8 +------- homeassistant/components/zwave_js/fan.py | 9 ++++++++- homeassistant/components/zwave_js/lock.py | 3 +++ homeassistant/components/zwave_js/switch.py | 7 +++++-- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index b125c8bcd6a4ac..a0b0648932cfa9 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -207,6 +207,9 @@ def hvac_mode(self) -> str: if self._current_mode is None: # Thermostat(valve) with no support for setting a mode is considered heating-only return HVAC_MODE_HEAT + if self._current_mode.value is None: + # guard missing value + return HVAC_MODE_HEAT return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVAC_MODE_HEAT_COOL) @property @@ -219,6 +222,9 @@ def hvac_action(self) -> Optional[str]: """Return the current running hvac operation if supported.""" if not self._operating_state: return None + if self._operating_state.value is None: + # guard missing value + return None return HVAC_CURRENT_MAP.get(int(self._operating_state.value)) @property @@ -234,12 +240,18 @@ def current_temperature(self) -> Optional[float]: @property def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) return temp.value if temp else None @property def target_temperature_high(self) -> Optional[float]: """Return the highbound target temperature we try to reach.""" + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) return temp.value if temp else None @@ -251,6 +263,9 @@ def target_temperature_low(self) -> Optional[float]: @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., home, away, temp.""" + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES: return_val: str = self._current_mode.metadata.states.get( self._current_mode.value diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index b86cbeba944df7..38c891f7376310 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -1,6 +1,6 @@ """Support for Z-Wave cover devices.""" import logging -from typing import Any, Callable, List +from typing import Any, Callable, List, Optional from zwave_js_server.client import Client as ZwaveClient @@ -59,13 +59,19 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): """Representation of a Z-Wave Cover device.""" @property - def is_closed(self) -> bool: + def is_closed(self) -> Optional[bool]: """Return true if cover is closed.""" + if self.info.primary_value.value is None: + # guard missing value + return None return bool(self.info.primary_value.value == 0) @property - def current_cover_position(self) -> int: + def current_cover_position(self) -> Optional[int]: """Return the current position of cover where 0 means closed and 100 is fully open.""" + if self.info.primary_value.value is None: + # guard missing value + return None return round((self.info.primary_value.value / 99) * 100) async def async_set_cover_position(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 08571ad5d8c318..a17e43e2f2315a 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -100,13 +100,7 @@ def unique_id(self) -> str: @property def available(self) -> bool: """Return entity availability.""" - return ( - self.client.connected - and bool(self.info.node.ready) - # a None value indicates something wrong with the device, - # or the value is simply not yet there (it will arrive later). - and self.info.primary_value.value is not None - ) + return self.client.connected and bool(self.info.node.ready) @callback def _value_changed(self, event_data: dict) -> None: diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index 7113272d2ead7e..360f907e74a70c 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -87,8 +87,11 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self.info.node.async_set_value(target_value, 0) @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: # type: ignore """Return true if device is on (speed above 0).""" + if self.info.primary_value.value is None: + # guard missing value + return None return bool(self.info.primary_value.value > 0) @property @@ -98,6 +101,10 @@ def speed(self) -> Optional[str]: The Z-Wave speed value is a byte 0-255. 255 means previous value. The normal range of the speed is 0-99. 0 means off. """ + if self.info.primary_value.value is None: + # guard missing value + return None + value = math.ceil(self.info.primary_value.value * 3 / 100) return VALUE_TO_SPEED.get(value, self._previous_speed) diff --git a/homeassistant/components/zwave_js/lock.py b/homeassistant/components/zwave_js/lock.py index dedaf9a5e45500..6f2a1a72c7d93d 100644 --- a/homeassistant/components/zwave_js/lock.py +++ b/homeassistant/components/zwave_js/lock.py @@ -90,6 +90,9 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): @property def is_locked(self) -> Optional[bool]: """Return true if the lock is locked.""" + if self.info.primary_value.value is None: + # guard missing value + return None return int( LOCK_CMD_CLASS_TO_LOCKED_STATE_MAP[ CommandClass(self.info.primary_value.command_class) diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index 2060894684c859..8feba5911f8d82 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -1,7 +1,7 @@ """Representation of Z-Wave switches.""" import logging -from typing import Any, Callable, List +from typing import Any, Callable, List, Optional from zwave_js_server.client import Client as ZwaveClient @@ -44,8 +44,11 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity): """Representation of a Z-Wave switch.""" @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: # type: ignore """Return a boolean for the state of the switch.""" + if self.info.primary_value.value is None: + # guard missing value + return None return bool(self.info.primary_value.value) async def async_turn_on(self, **kwargs: Any) -> None: From f9f681ac3698b8cb69c6286a582a88b01b5f2fe9 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 8 Feb 2021 17:56:19 +0100 Subject: [PATCH 0314/1818] update discovery scheme for zwave_js light platform (#46082) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index d741946a1c9a7b..03593ab79ad102 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -130,6 +130,7 @@ class ZWaveDiscoverySchema: "Multilevel Remote Switch", "Multilevel Power Switch", "Multilevel Scene Switch", + "Unused", }, command_class={CommandClass.SWITCH_MULTILEVEL}, property={"currentValue"}, From fd19f3ebdbbbdbf335edde16ca744a36ce6dd249 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 8 Feb 2021 17:57:22 +0100 Subject: [PATCH 0315/1818] Update zwave_js discovery scheme for boolean sensors in the Alarm CC (#46085) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 03593ab79ad102..be6d9b698d453e 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -143,6 +143,7 @@ class ZWaveDiscoverySchema: command_class={ CommandClass.SENSOR_BINARY, CommandClass.BATTERY, + CommandClass.SENSOR_ALARM, }, type={"boolean"}, ), From 39a7d975f65f30f7cddf1286b2871f772bb2a960 Mon Sep 17 00:00:00 2001 From: Hmmbob <33529490+hmmbob@users.noreply.github.com> Date: Mon, 8 Feb 2021 11:43:30 +0100 Subject: [PATCH 0316/1818] Fix Google translate TTS by bumping gTTS from 2.2.1 to 2.2.2 (#46110) --- homeassistant/components/google_translate/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google_translate/manifest.json b/homeassistant/components/google_translate/manifest.json index c5b3edc879869f..64d19bed277228 100644 --- a/homeassistant/components/google_translate/manifest.json +++ b/homeassistant/components/google_translate/manifest.json @@ -2,6 +2,6 @@ "domain": "google_translate", "name": "Google Translate Text-to-Speech", "documentation": "https://www.home-assistant.io/integrations/google_translate", - "requirements": ["gTTS==2.2.1"], + "requirements": ["gTTS==2.2.2"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 56b9f7a331c0b0..258474f97f3a7a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -622,7 +622,7 @@ freesms==0.1.2 fritzconnection==1.4.0 # homeassistant.components.google_translate -gTTS==2.2.1 +gTTS==2.2.2 # homeassistant.components.garmin_connect garminconnect==0.1.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c0a44a96f0a969..9583d901193f3b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -316,7 +316,7 @@ foobot_async==1.0.0 fritzconnection==1.4.0 # homeassistant.components.google_translate -gTTS==2.2.1 +gTTS==2.2.2 # homeassistant.components.garmin_connect garminconnect==0.1.16 From 983e98055403290b03e3f9b184becea468959b1c Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Sun, 7 Feb 2021 22:05:10 -0800 Subject: [PATCH 0317/1818] Revert "Convert ozw climate values to correct units (#45369)" (#46163) This reverts commit 1b6ee8301a5c076f93d0799b9f7fcb82cc6eb902. --- homeassistant/components/ozw/climate.py | 51 ++++--------------------- tests/components/ozw/test_climate.py | 20 ++++------ 2 files changed, 15 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index 67bbe5cdc4dd06..a74fd869f0f443 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -28,7 +28,6 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util.temperature import convert as convert_temperature from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity @@ -155,13 +154,6 @@ def async_add_climate(values): ) -def convert_units(units): - """Return units as a farenheit or celsius constant.""" - if units == "F": - return TEMP_FAHRENHEIT - return TEMP_CELSIUS - - class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): """Representation of a Z-Wave Climate device.""" @@ -207,18 +199,16 @@ def fan_modes(self): @property def temperature_unit(self): """Return the unit of measurement.""" - return convert_units(self._current_mode_setpoint_values[0].units) + if self.values.temperature is not None and self.values.temperature.units == "F": + return TEMP_FAHRENHEIT + return TEMP_CELSIUS @property def current_temperature(self): """Return the current temperature.""" if not self.values.temperature: return None - return convert_temperature( - self.values.temperature.value, - convert_units(self._current_mode_setpoint_values[0].units), - self.temperature_unit, - ) + return self.values.temperature.value @property def hvac_action(self): @@ -246,29 +236,17 @@ def preset_modes(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - return convert_temperature( - self._current_mode_setpoint_values[0].value, - convert_units(self._current_mode_setpoint_values[0].units), - self.temperature_unit, - ) + return self._current_mode_setpoint_values[0].value @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return convert_temperature( - self._current_mode_setpoint_values[0].value, - convert_units(self._current_mode_setpoint_values[0].units), - self.temperature_unit, - ) + return self._current_mode_setpoint_values[0].value @property def target_temperature_high(self) -> Optional[float]: """Return the highbound target temperature we try to reach.""" - return convert_temperature( - self._current_mode_setpoint_values[1].value, - convert_units(self._current_mode_setpoint_values[1].units), - self.temperature_unit, - ) + return self._current_mode_setpoint_values[1].value async def async_set_temperature(self, **kwargs): """Set new target temperature. @@ -284,29 +262,14 @@ async def async_set_temperature(self, **kwargs): setpoint = self._current_mode_setpoint_values[0] target_temp = kwargs.get(ATTR_TEMPERATURE) if setpoint is not None and target_temp is not None: - target_temp = convert_temperature( - target_temp, - self.temperature_unit, - convert_units(setpoint.units), - ) setpoint.send_value(target_temp) elif len(self._current_mode_setpoint_values) == 2: (setpoint_low, setpoint_high) = self._current_mode_setpoint_values target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) if setpoint_low is not None and target_temp_low is not None: - target_temp_low = convert_temperature( - target_temp_low, - self.temperature_unit, - convert_units(setpoint_low.units), - ) setpoint_low.send_value(target_temp_low) if setpoint_high is not None and target_temp_high is not None: - target_temp_high = convert_temperature( - target_temp_high, - self.temperature_unit, - convert_units(setpoint_high.units), - ) setpoint_high.send_value(target_temp_high) async def async_set_fan_mode(self, fan_mode): diff --git a/tests/components/ozw/test_climate.py b/tests/components/ozw/test_climate.py index e251a93c11520c..3414e6c483279a 100644 --- a/tests/components/ozw/test_climate.py +++ b/tests/components/ozw/test_climate.py @@ -16,8 +16,6 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, ) -from homeassistant.components.ozw.climate import convert_units -from homeassistant.const import TEMP_FAHRENHEIT from .common import setup_ozw @@ -38,8 +36,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): HVAC_MODE_HEAT_COOL, ] assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 73.5 - assert state.attributes[ATTR_TEMPERATURE] == 70.0 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 23.1 + assert state.attributes[ATTR_TEMPERATURE] == 21.1 assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None assert state.attributes[ATTR_FAN_MODE] == "Auto Low" @@ -56,7 +54,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 26.1 + assert round(msg["payload"]["Value"], 2) == 78.98 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test hvac_mode with set_temperature @@ -74,7 +72,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): msg = sent_messages[-1] assert msg["topic"] == "OpenZWave/1/command/setvalue/" # Celsius is converted to Fahrenheit here! - assert round(msg["payload"]["Value"], 2) == 24.1 + assert round(msg["payload"]["Value"], 2) == 75.38 assert msg["payload"]["ValueIDKey"] == 281475099443218 # Test set mode @@ -129,8 +127,8 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert state is not None assert state.state == HVAC_MODE_HEAT_COOL assert state.attributes.get(ATTR_TEMPERATURE) is None - assert state.attributes[ATTR_TARGET_TEMP_LOW] == 70.0 - assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 78.0 + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 21.1 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.6 # Test setting high/low temp on multiple setpoints await hass.services.async_call( @@ -146,11 +144,11 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): assert len(sent_messages) == 7 # 2 messages ! msg = sent_messages[-2] # low setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 20.0 + assert round(msg["payload"]["Value"], 2) == 68.0 assert msg["payload"]["ValueIDKey"] == 281475099443218 msg = sent_messages[-1] # high setpoint assert msg["topic"] == "OpenZWave/1/command/setvalue/" - assert round(msg["payload"]["Value"], 2) == 25.0 + assert round(msg["payload"]["Value"], 2) == 77.0 assert msg["payload"]["ValueIDKey"] == 562950076153874 # Test basic/single-setpoint thermostat (node 16 in dump) @@ -327,5 +325,3 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): ) assert len(sent_messages) == 12 assert "does not support setting a mode" in caplog.text - - assert convert_units("F") == TEMP_FAHRENHEIT From 47464b89af199ca412978ead28923aadacbddcdd Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 8 Feb 2021 11:23:50 +0100 Subject: [PATCH 0318/1818] Enable KNX auto_reconnect for auto-discovered connections (#46178) --- homeassistant/components/knx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 7dbeb513e091f1..1492e5df7b7f6c 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -290,7 +290,7 @@ def connection_config(self): if CONF_KNX_ROUTING in self.config[DOMAIN]: return self.connection_config_routing() # config from xknx.yaml always has priority later on - return ConnectionConfig() + return ConnectionConfig(auto_reconnect=True) def connection_config_routing(self): """Return the connection_config if routing is configured.""" From 384a5ae053260a4ab89539b53c99cbb1ec711374 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 12:22:38 -1000 Subject: [PATCH 0319/1818] Ensure creating an index that already exists is forgiving for postgresql (#46185) Unlikely sqlite and mysql, postgresql throws ProgrammingError instead of InternalError or OperationalError when trying to create an index that already exists. --- .../components/recorder/migration.py | 16 +++++----- tests/components/recorder/test_migrate.py | 30 ++++++++++++++++++- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 4501b25385e0c0..aeb62cc111da0a 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -3,7 +3,12 @@ from sqlalchemy import ForeignKeyConstraint, MetaData, Table, text from sqlalchemy.engine import reflection -from sqlalchemy.exc import InternalError, OperationalError, SQLAlchemyError +from sqlalchemy.exc import ( + InternalError, + OperationalError, + ProgrammingError, + SQLAlchemyError, +) from sqlalchemy.schema import AddConstraint, DropConstraint from .const import DOMAIN @@ -69,7 +74,7 @@ def _create_index(engine, table_name, index_name): ) try: index.create(engine) - except OperationalError as err: + except (InternalError, ProgrammingError, OperationalError) as err: lower_err_str = str(err).lower() if "already exists" not in lower_err_str and "duplicate" not in lower_err_str: @@ -78,13 +83,6 @@ def _create_index(engine, table_name, index_name): _LOGGER.warning( "Index %s already exists on %s, continuing", index_name, table_name ) - except InternalError as err: - if "duplicate" not in str(err).lower(): - raise - - _LOGGER.warning( - "Index %s already exists on %s, continuing", index_name, table_name - ) _LOGGER.debug("Finished creating %s", index_name) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index d10dad43d7518d..c29dad2d495fa2 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -1,9 +1,10 @@ """The tests for the Recorder component.""" # pylint: disable=protected-access -from unittest.mock import call, patch +from unittest.mock import Mock, PropertyMock, call, patch import pytest from sqlalchemy import create_engine +from sqlalchemy.exc import InternalError, OperationalError, ProgrammingError from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component @@ -79,3 +80,30 @@ def test_forgiving_add_index(): engine = create_engine("sqlite://", poolclass=StaticPool) models.Base.metadata.create_all(engine) migration._create_index(engine, "states", "ix_states_context_id") + + +@pytest.mark.parametrize( + "exception_type", [OperationalError, ProgrammingError, InternalError] +) +def test_forgiving_add_index_with_other_db_types(caplog, exception_type): + """Test that add index will continue if index exists on mysql and postgres.""" + mocked_index = Mock() + type(mocked_index).name = "ix_states_context_id" + mocked_index.create = Mock( + side_effect=exception_type( + "CREATE INDEX ix_states_old_state_id ON states (old_state_id);", + [], + 'relation "ix_states_old_state_id" already exists', + ) + ) + + mocked_table = Mock() + type(mocked_table).indexes = PropertyMock(return_value=[mocked_index]) + + with patch( + "homeassistant.components.recorder.migration.Table", return_value=mocked_table + ): + migration._create_index(Mock(), "states", "ix_states_context_id") + + assert "already exists on states" in caplog.text + assert "continuing" in caplog.text From 387301f024481b51cc3ef827ee2029091765d49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Mon, 8 Feb 2021 22:49:46 +0100 Subject: [PATCH 0320/1818] Fix Tado Power and Link binary sensors (#46235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Power and Link aren't converted from strings to booleans by python-tado, so we need to properly parse before assigning the string value to binary sensors. Fixes: 067f2d0098d1 ("Add tado zone binary sensors (#44576)") Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/tado/binary_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index 1acefdb4c1689b..71b529310131bd 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -244,10 +244,10 @@ def _async_update_zone_data(self): return if self.zone_variable == "power": - self._state = self._tado_zone_data.power + self._state = self._tado_zone_data.power == "ON" elif self.zone_variable == "link": - self._state = self._tado_zone_data.link + self._state = self._tado_zone_data.link == "ONLINE" elif self.zone_variable == "overlay": self._state = self._tado_zone_data.overlay_active From c02870686a78a4c2de26e5be9dd2ae3b9f439cb5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 11:51:46 -1000 Subject: [PATCH 0321/1818] Handle empty mylink response at startup (#46241) --- homeassistant/components/somfy_mylink/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index d15ea029530eef..d371fd96310ca0 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -108,6 +108,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): ) return False + if "result" not in mylink_status: + raise ConfigEntryNotReady("The Somfy MyLink device returned an empty result") + _async_migrate_entity_config(hass, entry, mylink_status) undo_listener = entry.add_update_listener(_async_update_listener) From 6ec49c696c0a25e56a853771077acbd1602e7806 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Feb 2021 22:37:01 +0000 Subject: [PATCH 0322/1818] Bumped version to 2021.2.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 08d9dd751db68e..5dba56f0247cbc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From c602c619a27d8b387c93049acef19415fc0fd6ac Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 8 Feb 2021 18:13:58 -0500 Subject: [PATCH 0323/1818] Use core constants for hikvision (#46247) --- homeassistant/components/hikvision/binary_sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index 359f966d119174..90c4b6ce8b918e 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -14,6 +14,7 @@ from homeassistant.const import ( ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE, + CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -30,7 +31,6 @@ _LOGGER = logging.getLogger(__name__) CONF_IGNORED = "ignored" -CONF_DELAY = "delay" DEFAULT_PORT = 80 DEFAULT_IGNORED = False @@ -139,7 +139,6 @@ class HikvisionData: def __init__(self, hass, url, port, name, username, password): """Initialize the data object.""" - self._url = url self._port = port self._name = name From 58b4a91a5b4fcf03bb9236ad85a8383c6f4cb47a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Feb 2021 00:34:18 +0100 Subject: [PATCH 0324/1818] Test that variables are passed to wait_for_trigger script action (#46221) --- tests/common.py | 72 +++++++++++++++++++++++++++++++++++- tests/helpers/test_script.py | 43 +++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/tests/common.py b/tests/common.py index 2621f2f4b158ee..ab5da25e38d363 100644 --- a/tests/common.py +++ b/tests/common.py @@ -12,6 +12,9 @@ import pathlib import threading import time +from time import monotonic +import types +from typing import Any, Awaitable, Collection, Optional from unittest.mock import AsyncMock, Mock, patch import uuid @@ -43,7 +46,7 @@ STATE_OFF, STATE_ON, ) -from homeassistant.core import State +from homeassistant.core import BLOCK_LOG_TIMEOUT, State from homeassistant.helpers import ( area_registry, device_registry, @@ -190,9 +193,76 @@ def async_create_task(coroutine): return orig_async_create_task(coroutine) + async def async_wait_for_task_count(self, max_remaining_tasks: int = 0) -> None: + """Block until at most max_remaining_tasks remain. + + Based on HomeAssistant.async_block_till_done + """ + # To flush out any call_soon_threadsafe + await asyncio.sleep(0) + start_time: Optional[float] = None + + while len(self._pending_tasks) > max_remaining_tasks: + pending = [ + task for task in self._pending_tasks if not task.done() + ] # type: Collection[Awaitable[Any]] + self._pending_tasks.clear() + if len(pending) > max_remaining_tasks: + remaining_pending = await self._await_count_and_log_pending( + pending, max_remaining_tasks=max_remaining_tasks + ) + self._pending_tasks.extend(remaining_pending) + + if start_time is None: + # Avoid calling monotonic() until we know + # we may need to start logging blocked tasks. + start_time = 0 + elif start_time == 0: + # If we have waited twice then we set the start + # time + start_time = monotonic() + elif monotonic() - start_time > BLOCK_LOG_TIMEOUT: + # We have waited at least three loops and new tasks + # continue to block. At this point we start + # logging all waiting tasks. + for task in pending: + _LOGGER.debug("Waiting for task: %s", task) + else: + self._pending_tasks.extend(pending) + await asyncio.sleep(0) + + async def _await_count_and_log_pending( + self, pending: Collection[Awaitable[Any]], max_remaining_tasks: int = 0 + ) -> Collection[Awaitable[Any]]: + """Block at most max_remaining_tasks remain and log tasks that take a long time. + + Based on HomeAssistant._await_and_log_pending + """ + wait_time = 0 + + return_when = asyncio.ALL_COMPLETED + if max_remaining_tasks: + return_when = asyncio.FIRST_COMPLETED + + while len(pending) > max_remaining_tasks: + _, pending = await asyncio.wait( + pending, timeout=BLOCK_LOG_TIMEOUT, return_when=return_when + ) + if not pending or max_remaining_tasks: + return pending + wait_time += BLOCK_LOG_TIMEOUT + for task in pending: + _LOGGER.debug("Waited %s seconds for task: %s", wait_time, task) + + return [] + hass.async_add_job = async_add_job hass.async_add_executor_job = async_add_executor_job hass.async_create_task = async_create_task + hass.async_wait_for_task_count = types.MethodType(async_wait_for_task_count, hass) + hass._await_count_and_log_pending = types.MethodType( + _await_count_and_log_pending, hass + ) hass.data[loader.DATA_CUSTOM_COMPONENTS] = {} diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 5cd9a9d2449ab3..a22cf27acdc802 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -545,6 +545,49 @@ async def test_wait_basic(hass, action_type): assert script_obj.last_action is None +async def test_wait_for_trigger_variables(hass): + """Test variables are passed to wait_for_trigger action.""" + context = Context() + wait_alias = "wait step" + actions = [ + { + "alias": "variables", + "variables": {"seconds": 5}, + }, + { + "alias": wait_alias, + "wait_for_trigger": { + "platform": "state", + "entity_id": "switch.test", + "to": "off", + "for": {"seconds": "{{ seconds }}"}, + }, + }, + ] + sequence = cv.SCRIPT_SCHEMA(actions) + sequence = await script.async_validate_actions_config(hass, sequence) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + wait_started_flag = async_watch_for_action(script_obj, wait_alias) + + try: + hass.states.async_set("switch.test", "on") + hass.async_create_task(script_obj.async_run(context=context)) + await asyncio.wait_for(wait_started_flag.wait(), 1) + assert script_obj.is_running + assert script_obj.last_action == wait_alias + hass.states.async_set("switch.test", "off") + # the script task + 2 tasks created by wait_for_trigger script step + await hass.async_wait_for_task_count(3) + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + except (AssertionError, asyncio.TimeoutError): + await script_obj.async_stop() + raise + else: + assert not script_obj.is_running + assert script_obj.last_action is None + + @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_wait_basic_times_out(hass, action_type): """Test wait actions times out when the action does not happen.""" From 93fafedf7213d72115cf74196ea4df95bfb3700f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 13:37:32 -1000 Subject: [PATCH 0325/1818] Cleanup bond identifiers and device info (#46192) --- homeassistant/components/bond/__init__.py | 26 ++++++++-- homeassistant/components/bond/const.py | 2 + homeassistant/components/bond/entity.py | 18 ++++++- homeassistant/components/bond/utils.py | 29 ++++++++++- tests/components/bond/test_init.py | 60 ++++++++++++++++++++++- 5 files changed, 126 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 9af92f6e7e7a81..88ea084d25fc8e 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -7,12 +7,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST -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.entity import SLOW_UPDATE_WARNING -from .const import DOMAIN +from .const import BRIDGE_MAKE, DOMAIN from .utils import BondHub PLATFORMS = ["cover", "fan", "light", "switch"] @@ -29,6 +29,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Bond from a config entry.""" host = entry.data[CONF_HOST] token = entry.data[CONF_ACCESS_TOKEN] + config_entry_id = entry.entry_id bond = Bond(host=host, token=token, timeout=ClientTimeout(total=_API_TIMEOUT)) hub = BondHub(bond) @@ -37,21 +38,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except (ClientError, AsyncIOTimeoutError, OSError) as error: raise ConfigEntryNotReady from error - hass.data[DOMAIN][entry.entry_id] = hub + hass.data[DOMAIN][config_entry_id] = hub if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id) device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( - config_entry_id=entry.entry_id, + config_entry_id=config_entry_id, identifiers={(DOMAIN, hub.bond_id)}, - manufacturer="Olibra", + manufacturer=BRIDGE_MAKE, name=hub.bond_id, model=hub.target, sw_version=hub.fw_ver, ) + _async_remove_old_device_identifiers(config_entry_id, device_registry, hub) + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) @@ -75,3 +78,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok + + +@callback +def _async_remove_old_device_identifiers( + config_entry_id: str, device_registry: dr.DeviceRegistry, hub: BondHub +): + """Remove the non-unique device registry entries.""" + for device in hub.devices: + dev = device_registry.async_get_device(identifiers={(DOMAIN, device.device_id)}) + if dev is None: + continue + if config_entry_id in dev.config_entries: + device_registry.async_remove_device(dev.id) diff --git a/homeassistant/components/bond/const.py b/homeassistant/components/bond/const.py index 843c3f9f1dc651..3031c159b0f5eb 100644 --- a/homeassistant/components/bond/const.py +++ b/homeassistant/components/bond/const.py @@ -1,5 +1,7 @@ """Constants for the Bond integration.""" +BRIDGE_MAKE = "Olibra" + DOMAIN = "bond" CONF_BOND_ID: str = "bond_id" diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 501d65749600b2..2819182c9b51f6 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -43,11 +43,25 @@ def name(self) -> Optional[str]: @property def device_info(self) -> Optional[Dict[str, Any]]: """Get a an HA device representing this Bond controlled device.""" - return { + device_info = { ATTR_NAME: self.name, - "identifiers": {(DOMAIN, self._device.device_id)}, + "manufacturer": self._hub.make, + "identifiers": {(DOMAIN, self._hub.bond_id, self._device.device_id)}, "via_device": (DOMAIN, self._hub.bond_id), } + if not self._hub.is_bridge: + device_info["model"] = self._hub.model + device_info["sw_version"] = self._hub.fw_ver + else: + model_data = [] + if self._device.branding_profile: + model_data.append(self._device.branding_profile) + if self._device.template: + model_data.append(self._device.template) + if model_data: + device_info["model"] = " ".join(model_data) + + return device_info @property def assumed_state(self) -> bool: diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 5a9fff692faec6..df3373ed7a1aaa 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -4,6 +4,8 @@ from bond_api import Action, Bond +from .const import BRIDGE_MAKE + _LOGGER = logging.getLogger(__name__) @@ -34,6 +36,21 @@ def type(self) -> str: """Get the type of this device.""" return self._attrs["type"] + @property + def location(self) -> str: + """Get the location of this device.""" + return self._attrs["location"] + + @property + def template(self) -> str: + """Return this model template.""" + return self._attrs.get("template") + + @property + def branding_profile(self) -> str: + """Return this branding profile.""" + return self.props.get("branding_profile") + @property def trust_state(self) -> bool: """Check if Trust State is turned on.""" @@ -100,9 +117,19 @@ def bond_id(self) -> str: @property def target(self) -> str: - """Return this hub model.""" + """Return this hub target.""" return self._version.get("target") + @property + def model(self) -> str: + """Return this hub model.""" + return self._version.get("model") + + @property + def make(self) -> str: + """Return this hub make.""" + return self._version.get("make", BRIDGE_MAKE) + @property def fw_ver(self) -> str: """Return this hub firmware version.""" diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index 98d86058c49d5c..e2bb63141260d4 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -1,5 +1,6 @@ """Tests for the Bond module.""" from aiohttp import ClientConnectionError +from bond_api import DeviceType from homeassistant.components.bond.const import DOMAIN from homeassistant.config_entries import ( @@ -12,7 +13,15 @@ from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component -from .common import patch_bond_version, patch_setup_entry, setup_bond_entity +from .common import ( + patch_bond_device, + patch_bond_device_ids, + patch_bond_device_properties, + patch_bond_device_state, + patch_bond_version, + patch_setup_entry, + setup_bond_entity, +) from tests.common import MockConfigEntry @@ -105,3 +114,52 @@ async def test_unload_config_entry(hass: HomeAssistant): assert config_entry.entry_id not in hass.data[DOMAIN] assert config_entry.state == ENTRY_STATE_NOT_LOADED + + +async def test_old_identifiers_are_removed(hass: HomeAssistant): + """Test we remove the old non-unique identifiers.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, + ) + + old_identifers = (DOMAIN, "device_id") + new_identifiers = (DOMAIN, "test-bond-id", "device_id") + device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={old_identifers}, + manufacturer="any", + name="old", + ) + + config_entry.add_to_hass(hass) + + with patch_bond_version( + return_value={ + "bondid": "test-bond-id", + "target": "test-model", + "fw_ver": "test-version", + } + ), patch_bond_device_ids( + return_value=["bond-device-id", "device_id"] + ), patch_bond_device( + return_value={ + "name": "test1", + "type": DeviceType.GENERIC_DEVICE, + } + ), patch_bond_device_properties( + return_value={} + ), patch_bond_device_state( + return_value={} + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) is True + await hass.async_block_till_done() + + assert config_entry.entry_id in hass.data[DOMAIN] + assert config_entry.state == ENTRY_STATE_LOADED + assert config_entry.unique_id == "test-bond-id" + + # verify the device info is cleaned up + assert device_registry.async_get_device(identifiers={old_identifers}) is None + assert device_registry.async_get_device(identifiers={new_identifiers}) is not None From 6563c37ab196f638585f3b202159d73c0dcc6806 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 13:39:21 -1000 Subject: [PATCH 0326/1818] Add support for generic lights to bond (#46193) --- homeassistant/components/bond/light.py | 8 +++++++- tests/components/bond/test_light.py | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index 77771167e147b1..c0809a0aee7c3b 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -47,7 +47,13 @@ async def async_setup_entry( if DeviceType.is_fireplace(device.type) and device.supports_light() ] - async_add_entities(fan_lights + fireplaces + fp_lights, True) + lights: List[Entity] = [ + BondLight(hub, device) + for device in hub.devices + if DeviceType.is_light(device.type) + ] + + async_add_entities(fan_lights + fireplaces + fp_lights + lights, True) class BondLight(BondEntity, LightEntity): diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index 6d871187e26e11..3f7a3ef62f9847 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -29,6 +29,15 @@ from tests.common import async_fire_time_changed +def light(name: str): + """Create a light with a given name.""" + return { + "name": name, + "type": DeviceType.LIGHT, + "actions": [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF, Action.SET_BRIGHTNESS], + } + + def ceiling_fan(name: str): """Create a ceiling fan (that has built-in light) with given name.""" return { @@ -117,6 +126,21 @@ async def test_fireplace_with_light_entity_registry(hass: core.HomeAssistant): assert entity_light.unique_id == "test-hub-id_test-device-id_light" +async def test_light_entity_registry(hass: core.HomeAssistant): + """Tests lights are registered in the entity registry.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + light("light-name"), + bond_version={"bondid": "test-hub-id"}, + bond_device_id="test-device-id", + ) + + registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + entity = registry.entities["light.light_name"] + assert entity.unique_id == "test-hub-id_test-device-id" + + async def test_sbb_trust_state(hass: core.HomeAssistant): """Assumed state should be False if device is a Smart by Bond.""" version = { From 936ee7d7336888b2fbb3f62b7500b5f701a4c989 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 9 Feb 2021 00:07:22 +0000 Subject: [PATCH 0327/1818] [ci skip] Translation update --- homeassistant/components/media_player/translations/ca.json | 7 +++++++ homeassistant/components/media_player/translations/en.json | 7 +++++++ homeassistant/components/media_player/translations/et.json | 7 +++++++ .../components/media_player/translations/zh-Hant.json | 7 +++++++ .../components/mysensors/translations/zh-Hant.json | 2 ++ 5 files changed, 30 insertions(+) diff --git a/homeassistant/components/media_player/translations/ca.json b/homeassistant/components/media_player/translations/ca.json index 67f7aad655b268..e1fce3340531a1 100644 --- a/homeassistant/components/media_player/translations/ca.json +++ b/homeassistant/components/media_player/translations/ca.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} est\u00e0 enc\u00e8s", "is_paused": "{entity_name} est\u00e0 en pausa", "is_playing": "{entity_name} est\u00e0 reproduint" + }, + "trigger_type": { + "idle": "{entity_name} es torna inactiu", + "paused": "{entity_name} est\u00e0 en pausa", + "playing": "{entity_name} comen\u00e7a a reproduir", + "turned_off": "{entity_name} s'ha apagat", + "turned_on": "{entity_name} s'ha engegat" } }, "state": { diff --git a/homeassistant/components/media_player/translations/en.json b/homeassistant/components/media_player/translations/en.json index 3a96a2b3a906b4..aa995be9904aff 100644 --- a/homeassistant/components/media_player/translations/en.json +++ b/homeassistant/components/media_player/translations/en.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} is on", "is_paused": "{entity_name} is paused", "is_playing": "{entity_name} is playing" + }, + "trigger_type": { + "idle": "{entity_name} becomes idle", + "paused": "{entity_name} is paused", + "playing": "{entity_name} starts playing", + "turned_off": "{entity_name} turned off", + "turned_on": "{entity_name} turned on" } }, "state": { diff --git a/homeassistant/components/media_player/translations/et.json b/homeassistant/components/media_player/translations/et.json index 4d71a30a8ac2f8..687a0e5953df54 100644 --- a/homeassistant/components/media_player/translations/et.json +++ b/homeassistant/components/media_player/translations/et.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} on sisse l\u00fclitatud", "is_paused": "{entity_name} on peatatud", "is_playing": "{entity_name} m\u00e4ngib" + }, + "trigger_type": { + "idle": "{entity_name} muutub j\u00f5udeolekusse", + "paused": "{entity_name} on pausil", + "playing": "{entity_name} alustab taasesitamist", + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse" } }, "state": { diff --git a/homeassistant/components/media_player/translations/zh-Hant.json b/homeassistant/components/media_player/translations/zh-Hant.json index 3ae786cbed945c..a3a4b82380e8e2 100644 --- a/homeassistant/components/media_player/translations/zh-Hant.json +++ b/homeassistant/components/media_player/translations/zh-Hant.json @@ -6,6 +6,13 @@ "is_on": "{entity_name}\u958b\u555f", "is_paused": "{entity_name}\u5df2\u66ab\u505c", "is_playing": "{entity_name}\u6b63\u5728\u64ad\u653e" + }, + "trigger_type": { + "idle": "{entity_name}\u8b8a\u6210\u9592\u7f6e", + "paused": "{entity_name}\u5df2\u66ab\u505c", + "playing": "{entity_name}\u958b\u59cb\u64ad\u653e", + "turned_off": "{entity_name}\u5df2\u95dc\u9589", + "turned_on": "{entity_name}\u5df2\u958b\u555f" } }, "state": { diff --git a/homeassistant/components/mysensors/translations/zh-Hant.json b/homeassistant/components/mysensors/translations/zh-Hant.json index 0d4db4502e5378..d0067c2d0ced0d 100644 --- a/homeassistant/components/mysensors/translations/zh-Hant.json +++ b/homeassistant/components/mysensors/translations/zh-Hant.json @@ -3,10 +3,12 @@ "abort": { "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "cannot_connect": "\u9023\u7dda\u5931\u6557", + "duplicate_persistence_file": "Persistence \u6a94\u6848\u5df2\u4f7f\u7528\u4e2d", "duplicate_topic": "\u4e3b\u984c\u5df2\u4f7f\u7528\u4e2d", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_device": "\u88dd\u7f6e\u7121\u6548", "invalid_ip": "IP \u4f4d\u5740\u7121\u6548", + "invalid_persistence_file": "Persistence \u6a94\u6848\u7121\u6548", "invalid_port": "\u901a\u8a0a\u57e0\u865f\u78bc\u7121\u6548", "invalid_publish_topic": "\u767c\u5e03\u4e3b\u984c\u7121\u6548", "invalid_serial": "\u5e8f\u5217\u57e0\u7121\u6548", From 889baef4567e573d9da3f6fbb4af8bf08e74fc39 Mon Sep 17 00:00:00 2001 From: Pascal Reeb Date: Tue, 9 Feb 2021 04:11:27 +0100 Subject: [PATCH 0328/1818] Add DHCP discovery support to Nuki integration (#46032) --- homeassistant/components/nuki/config_flow.py | 27 ++++++- homeassistant/components/nuki/manifest.json | 3 +- homeassistant/generated/dhcp.py | 4 + tests/components/nuki/mock.py | 25 ++++++ tests/components/nuki/test_config_flow.py | 81 +++++++++++++++----- 5 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 tests/components/nuki/mock.py diff --git a/homeassistant/components/nuki/config_flow.py b/homeassistant/components/nuki/config_flow.py index 9af74cb4423645..f065f1c27ef7f2 100644 --- a/homeassistant/components/nuki/config_flow.py +++ b/homeassistant/components/nuki/config_flow.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant import config_entries, exceptions +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN from .const import ( # pylint: disable=unused-import @@ -54,6 +55,10 @@ async def validate_input(hass, data): class NukiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Nuki config flow.""" + def __init__(self): + """Initialize the Nuki config flow.""" + self.discovery_schema = {} + async def async_step_import(self, user_input=None): """Handle a flow initiated by import.""" return await self.async_step_validate(user_input) @@ -62,7 +67,23 @@ async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" return await self.async_step_validate(user_input) - async def async_step_validate(self, user_input): + async def async_step_dhcp(self, discovery_info: dict): + """Prepare configuration for a DHCP discovered Nuki bridge.""" + await self.async_set_unique_id(int(discovery_info.get(HOSTNAME)[12:], 16)) + + self._abort_if_unique_id_configured() + + self.discovery_schema = vol.Schema( + { + vol.Required(CONF_HOST, default=discovery_info[IP_ADDRESS]): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + vol.Required(CONF_TOKEN): str, + } + ) + + return await self.async_step_validate() + + async def async_step_validate(self, user_input=None): """Handle init step of a flow.""" errors = {} @@ -84,8 +105,10 @@ async def async_step_validate(self, user_input): title=info["ids"]["hardwareId"], data=user_input ) + data_schema = self.discovery_schema or USER_SCHEMA + return self.async_show_form( - step_id="user", data_schema=USER_SCHEMA, errors=errors + step_id="user", data_schema=data_schema, errors=errors ) diff --git a/homeassistant/components/nuki/manifest.json b/homeassistant/components/nuki/manifest.json index 9385821845ad30..7fb9a134c4c55a 100644 --- a/homeassistant/components/nuki/manifest.json +++ b/homeassistant/components/nuki/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nuki", "requirements": ["pynuki==1.3.8"], "codeowners": ["@pschmitt", "@pvizeli", "@pree"], - "config_flow": true + "config_flow": true, + "dhcp": [{ "hostname": "nuki_bridge_*" }] } \ No newline at end of file diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 61223bf00f781b..31ee42bc48cce8 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -70,6 +70,10 @@ "hostname": "nuheat", "macaddress": "002338*" }, + { + "domain": "nuki", + "hostname": "nuki_bridge_*" + }, { "domain": "powerwall", "hostname": "1118431-*", diff --git a/tests/components/nuki/mock.py b/tests/components/nuki/mock.py new file mode 100644 index 00000000000000..a7870ce0906d14 --- /dev/null +++ b/tests/components/nuki/mock.py @@ -0,0 +1,25 @@ +"""Mockup Nuki device.""" +from homeassistant import setup + +from tests.common import MockConfigEntry + +NAME = "Nuki_Bridge_75BCD15" +HOST = "1.1.1.1" +MAC = "01:23:45:67:89:ab" + +HW_ID = 123456789 + +MOCK_INFO = {"ids": {"hardwareId": HW_ID}} + + +async def setup_nuki_integration(hass): + """Create the Nuki device.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( + domain="nuki", + unique_id=HW_ID, + data={"host": HOST, "port": 8080, "token": "test-token"}, + ) + entry.add_to_hass(hass) + + return entry diff --git a/tests/components/nuki/test_config_flow.py b/tests/components/nuki/test_config_flow.py index bcdedad371a3f0..4933ea52b77a30 100644 --- a/tests/components/nuki/test_config_flow.py +++ b/tests/components/nuki/test_config_flow.py @@ -5,9 +5,10 @@ from requests.exceptions import RequestException from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS, MAC_ADDRESS from homeassistant.components.nuki.const import DOMAIN -from tests.common import MockConfigEntry +from .mock import HOST, MAC, MOCK_INFO, NAME, setup_nuki_integration async def test_form(hass): @@ -19,11 +20,9 @@ async def test_form(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {} - mock_info = {"ids": {"hardwareId": "0001"}} - with patch( "homeassistant.components.nuki.config_flow.NukiBridge.info", - return_value=mock_info, + return_value=MOCK_INFO, ), patch( "homeassistant.components.nuki.async_setup", return_value=True ) as mock_setup, patch( @@ -41,7 +40,7 @@ async def test_form(hass): await hass.async_block_till_done() assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result2["title"] == "0001" + assert result2["title"] == 123456789 assert result2["data"] == { "host": "1.1.1.1", "port": 8080, @@ -55,11 +54,9 @@ async def test_import(hass): """Test that the import works.""" await setup.async_setup_component(hass, "persistent_notification", {}) - mock_info = {"ids": {"hardwareId": "0001"}} - with patch( "homeassistant.components.nuki.config_flow.NukiBridge.info", - return_value=mock_info, + return_value=MOCK_INFO, ), patch( "homeassistant.components.nuki.async_setup", return_value=True ) as mock_setup, patch( @@ -72,7 +69,7 @@ async def test_import(hass): data={"host": "1.1.1.1", "port": 8080, "token": "test-token"}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "0001" + assert result["title"] == 123456789 assert result["data"] == { "host": "1.1.1.1", "port": 8080, @@ -155,21 +152,14 @@ async def test_form_unknown_exception(hass): async def test_form_already_configured(hass): """Test we get the form.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - entry = MockConfigEntry( - domain="nuki", - unique_id="0001", - data={"host": "1.1.1.1", "port": 8080, "token": "test-token"}, - ) - entry.add_to_hass(hass) - + await setup_nuki_integration(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) with patch( "homeassistant.components.nuki.config_flow.NukiBridge.info", - return_value={"ids": {"hardwareId": "0001"}}, + return_value=MOCK_INFO, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -182,3 +172,58 @@ async def test_form_already_configured(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result2["reason"] == "already_configured" + + +async def test_dhcp_flow(hass): + """Test that DHCP discovery for new bridge works.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={HOSTNAME: NAME, IP_ADDRESS: HOST, MAC_ADDRESS: MAC}, + context={"source": config_entries.SOURCE_DHCP}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == config_entries.SOURCE_USER + + with patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + return_value=MOCK_INFO, + ), patch( + "homeassistant.components.nuki.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.nuki.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "port": 8080, + "token": "test-token", + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == 123456789 + assert result2["data"] == { + "host": "1.1.1.1", + "port": 8080, + "token": "test-token", + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_dhcp_flow_already_configured(hass): + """Test that DHCP doesn't setup already configured devices.""" + await setup_nuki_integration(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={HOSTNAME: NAME, IP_ADDRESS: HOST, MAC_ADDRESS: MAC}, + context={"source": config_entries.SOURCE_DHCP}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 2bcf87b980b7f394b842c5af6b592049d2fff09b Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 8 Feb 2021 19:53:28 -0800 Subject: [PATCH 0329/1818] Change the API boundary between camera and stream with initial improvement for nest expiring stream urls (#45431) * Change the API boundary between stream and camera Shift more of the stream lifecycle management to the camera. The motivation is to support stream urls that expire giving the camera the ability to change the stream once it is created. * Document stream lifecycle and simplify stream/camera interaction * Reorder create_stream function to reduce diffs * Increase test coverage for camera_sdm.py * Fix ffmpeg typo. * Add a stream identifier for each stream, managed by camera * Remove stream record service * Update homeassistant/components/stream/__init__.py Co-authored-by: Paulus Schoutsen * Unroll changes to Stream interface back into camera component * Fix preload stream to actually start the background worker * Reduce unncessary diffs for readability * Remove redundant camera stream start code Co-authored-by: Paulus Schoutsen --- homeassistant/components/camera/__init__.py | 132 ++++++---------- homeassistant/components/camera/const.py | 5 + homeassistant/components/nest/camera_sdm.py | 7 + homeassistant/components/stream/__init__.py | 149 +++++++----------- homeassistant/components/stream/const.py | 7 - homeassistant/components/stream/core.py | 6 +- homeassistant/components/stream/services.yaml | 15 -- tests/components/camera/test_init.py | 56 +++---- tests/components/generic/test_camera.py | 49 ++++-- tests/components/nest/camera_sdm_test.py | 6 + tests/components/stream/common.py | 10 -- tests/components/stream/test_hls.py | 30 ++-- tests/components/stream/test_init.py | 86 ---------- tests/components/stream/test_recorder.py | 58 +++++-- 14 files changed, 257 insertions(+), 359 deletions(-) delete mode 100644 homeassistant/components/stream/services.yaml delete mode 100644 tests/components/stream/test_init.py diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 25505800709284..cbc98edf19b1f8 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -23,16 +23,8 @@ DOMAIN as DOMAIN_MP, SERVICE_PLAY_MEDIA, ) -from homeassistant.components.stream import request_stream -from homeassistant.components.stream.const import ( - CONF_DURATION, - CONF_LOOKBACK, - CONF_STREAM_SOURCE, - DOMAIN as DOMAIN_STREAM, - FORMAT_CONTENT_TYPE, - OUTPUT_FORMATS, - SERVICE_RECORD, -) +from homeassistant.components.stream import Stream, create_stream +from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, OUTPUT_FORMATS from homeassistant.const import ( ATTR_ENTITY_ID, CONF_FILENAME, @@ -53,7 +45,13 @@ from homeassistant.helpers.network import get_url from homeassistant.loader import bind_hass -from .const import DATA_CAMERA_PREFS, DOMAIN +from .const import ( + CONF_DURATION, + CONF_LOOKBACK, + DATA_CAMERA_PREFS, + DOMAIN, + SERVICE_RECORD, +) from .prefs import CameraPreferences # mypy: allow-untyped-calls, allow-untyped-defs @@ -130,23 +128,7 @@ class Image: async def async_request_stream(hass, entity_id, fmt): """Request a stream for a camera entity.""" camera = _get_camera_from_entity_id(hass, entity_id) - camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) - - async with async_timeout.timeout(10): - source = await camera.stream_source() - - if not source: - raise HomeAssistantError( - f"{camera.entity_id} does not support play stream service" - ) - - return request_stream( - hass, - source, - fmt=fmt, - keepalive=camera_prefs.preload_stream, - options=camera.stream_options, - ) + return await _async_stream_endpoint_url(hass, camera, fmt) @bind_hass @@ -267,14 +249,11 @@ async def preload_stream(_): camera_prefs = prefs.get(camera.entity_id) if not camera_prefs.preload_stream: continue - - async with async_timeout.timeout(10): - source = await camera.stream_source() - - if not source: + stream = await camera.create_stream() + if not stream: continue - - request_stream(hass, source, keepalive=True, options=camera.stream_options) + stream.add_provider("hls") + stream.start() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream) @@ -330,6 +309,7 @@ class Camera(Entity): def __init__(self): """Initialize a camera.""" self.is_streaming = False + self.stream = None self.stream_options = {} self.content_type = DEFAULT_CONTENT_TYPE self.access_tokens: collections.deque = collections.deque([], 2) @@ -375,6 +355,17 @@ def frame_interval(self): """Return the interval between frames of the mjpeg stream.""" return 0.5 + async def create_stream(self) -> Stream: + """Create a Stream for stream_source.""" + # There is at most one stream (a decode worker) per camera + if not self.stream: + async with async_timeout.timeout(10): + source = await self.stream_source() + if not source: + return None + self.stream = create_stream(self.hass, source, options=self.stream_options) + return self.stream + async def stream_source(self): """Return the source of the stream.""" return None @@ -586,24 +577,7 @@ async def ws_camera_stream(hass, connection, msg): try: entity_id = msg["entity_id"] camera = _get_camera_from_entity_id(hass, entity_id) - camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) - - async with async_timeout.timeout(10): - source = await camera.stream_source() - - if not source: - raise HomeAssistantError( - f"{camera.entity_id} does not support play stream service" - ) - - fmt = msg["format"] - url = request_stream( - hass, - source, - fmt=fmt, - keepalive=camera_prefs.preload_stream, - options=camera.stream_options, - ) + url = await _async_stream_endpoint_url(hass, camera, fmt=msg["format"]) connection.send_result(msg["id"], {"url": url}) except HomeAssistantError as ex: _LOGGER.error("Error requesting stream: %s", ex) @@ -676,32 +650,17 @@ def _write_image(to_file, image_data): async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" - async with async_timeout.timeout(10): - source = await camera.stream_source() - - if not source: - raise HomeAssistantError( - f"{camera.entity_id} does not support play stream service" - ) - - hass = camera.hass - camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] - entity_ids = service_call.data[ATTR_MEDIA_PLAYER] + url = await _async_stream_endpoint_url(camera.hass, camera, fmt) - url = request_stream( - hass, - source, - fmt=fmt, - keepalive=camera_prefs.preload_stream, - options=camera.stream_options, - ) + hass = camera.hass data = { 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] cast_entity_ids = [ entity for entity, source in entity_sources(hass).items() @@ -740,12 +699,28 @@ async def async_handle_play_stream_service(camera, service_call): ) +async def _async_stream_endpoint_url(hass, camera, fmt): + stream = await camera.create_stream() + if not stream: + raise HomeAssistantError( + f"{camera.entity_id} does not support play stream service" + ) + + # Update keepalive setting which manages idle shutdown + camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) + stream.keepalive = camera_prefs.preload_stream + + stream.add_provider(fmt) + stream.start() + return stream.endpoint_url(fmt) + + async def async_handle_record_service(camera, call): """Handle stream recording service calls.""" async with async_timeout.timeout(10): - source = await camera.stream_source() + stream = await camera.create_stream() - if not source: + if not stream: raise HomeAssistantError(f"{camera.entity_id} does not support record service") hass = camera.hass @@ -753,13 +728,6 @@ async def async_handle_record_service(camera, call): filename.hass = hass video_path = filename.async_render(variables={ATTR_ENTITY_ID: camera}) - data = { - CONF_STREAM_SOURCE: source, - CONF_FILENAME: video_path, - CONF_DURATION: call.data[CONF_DURATION], - CONF_LOOKBACK: call.data[CONF_LOOKBACK], - } - - await hass.services.async_call( - DOMAIN_STREAM, SERVICE_RECORD, data, blocking=True, context=call.context + await stream.async_record( + video_path, duration=call.data[CONF_DURATION], lookback=call.data[CONF_LOOKBACK] ) diff --git a/homeassistant/components/camera/const.py b/homeassistant/components/camera/const.py index 563f0554f0fa68..615e54b0ecae4a 100644 --- a/homeassistant/components/camera/const.py +++ b/homeassistant/components/camera/const.py @@ -4,3 +4,8 @@ DATA_CAMERA_PREFS = "camera_prefs" PREF_PRELOAD_STREAM = "preload_stream" + +SERVICE_RECORD = "record" + +CONF_LOOKBACK = "lookback" +CONF_DURATION = "duration" diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index aa8e100059abc0..8f5ba88fdd46ab 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -146,6 +146,13 @@ async def _handle_stream_refresh(self, now): # Next attempt to catch a url will get a new one self._stream = None return + # Stop any existing stream worker since the url is invalid. The next + # request for this stream will restart it with the right url. + # Issue #42793 tracks improvements (e.g. preserve keepalive, smoother + # transitions across streams) + if self.stream: + self.stream.stop() + self.stream = None self._schedule_stream_refresh() async def async_will_remove_from_hass(self): diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 6980f7ead8f072..a8b344a98e949b 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -1,28 +1,35 @@ -"""Provide functionality to stream video source.""" +"""Provide functionality to stream video source. + +Components use create_stream with a stream source (e.g. an rtsp url) to create +a new Stream object. Stream manages: + - Background work to fetch and decode a stream + - Desired output formats + - Home Assistant URLs for viewing a stream + - Access tokens for URLs for viewing a stream + +A Stream consists of a background worker, and one or more output formats each +with their own idle timeout managed by the stream component. When an output +format is no longer in use, the stream component will expire it. When there +are no active output formats, the background worker is shut down and access +tokens are expired. Alternatively, a Stream can be configured with keepalive +to always keep workers active. +""" import logging import secrets import threading import time from types import MappingProxyType -import voluptuous as vol - -from homeassistant.const import CONF_FILENAME, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError -import homeassistant.helpers.config_validation as cv -from homeassistant.loader import bind_hass from .const import ( ATTR_ENDPOINTS, ATTR_STREAMS, - CONF_DURATION, - CONF_LOOKBACK, - CONF_STREAM_SOURCE, DOMAIN, MAX_SEGMENTS, OUTPUT_IDLE_TIMEOUT, - SERVICE_RECORD, STREAM_RESTART_INCREMENT, STREAM_RESTART_RESET_TIME, ) @@ -31,20 +38,13 @@ _LOGGER = logging.getLogger(__name__) -STREAM_SERVICE_SCHEMA = vol.Schema({vol.Required(CONF_STREAM_SOURCE): cv.string}) - -SERVICE_RECORD_SCHEMA = STREAM_SERVICE_SCHEMA.extend( - { - vol.Required(CONF_FILENAME): cv.string, - vol.Optional(CONF_DURATION, default=30): int, - vol.Optional(CONF_LOOKBACK, default=0): int, - } -) +def create_stream(hass, stream_source, options=None): + """Create a stream with the specified identfier based on the source url. -@bind_hass -def request_stream(hass, stream_source, *, fmt="hls", keepalive=False, options=None): - """Set up stream with token.""" + The stream_source is typically an rtsp url and options are passed into + pyav / ffmpeg as options. + """ if DOMAIN not in hass.config.components: raise HomeAssistantError("Stream integration is not set up.") @@ -59,25 +59,9 @@ def request_stream(hass, stream_source, *, fmt="hls", keepalive=False, options=N **options, } - try: - streams = hass.data[DOMAIN][ATTR_STREAMS] - stream = streams.get(stream_source) - if not stream: - stream = Stream(hass, stream_source, options=options, keepalive=keepalive) - streams[stream_source] = stream - else: - # Update keepalive option on existing stream - stream.keepalive = keepalive - - # Add provider - stream.add_provider(fmt) - - if not stream.access_token: - stream.access_token = secrets.token_hex() - stream.start() - return hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(stream.access_token) - except Exception as err: - raise HomeAssistantError("Unable to get stream") from err + stream = Stream(hass, stream_source, options=options) + hass.data[DOMAIN][ATTR_STREAMS].append(stream) + return stream async def async_setup(hass, config): @@ -92,7 +76,7 @@ async def async_setup(hass, config): hass.data[DOMAIN] = {} hass.data[DOMAIN][ATTR_ENDPOINTS] = {} - hass.data[DOMAIN][ATTR_STREAMS] = {} + hass.data[DOMAIN][ATTR_STREAMS] = [] # Setup HLS hls_endpoint = async_setup_hls(hass) @@ -104,33 +88,25 @@ async def async_setup(hass, config): @callback def shutdown(event): """Stop all stream workers.""" - for stream in hass.data[DOMAIN][ATTR_STREAMS].values(): + for stream in hass.data[DOMAIN][ATTR_STREAMS]: stream.keepalive = False stream.stop() _LOGGER.info("Stopped stream workers") hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) - async def async_record(call): - """Call record stream service handler.""" - await async_handle_record_service(hass, call) - - hass.services.async_register( - DOMAIN, SERVICE_RECORD, async_record, schema=SERVICE_RECORD_SCHEMA - ) - return True class Stream: """Represents a single stream.""" - def __init__(self, hass, source, options=None, keepalive=False): + def __init__(self, hass, source, options=None): """Initialize a stream.""" self.hass = hass self.source = source self.options = options - self.keepalive = keepalive + self.keepalive = False self.access_token = None self._thread = None self._thread_quit = None @@ -139,6 +115,14 @@ def __init__(self, hass, source, options=None, keepalive=False): if self.options is None: self.options = {} + def endpoint_url(self, fmt): + """Start the stream and returns a url for the output format.""" + if fmt not in self._outputs: + raise ValueError(f"Stream is not configured for format '{fmt}'") + if not self.access_token: + self.access_token = secrets.token_hex() + return self.hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(self.access_token) + @property def outputs(self): """Return a copy of the stream outputs.""" @@ -244,39 +228,28 @@ def _stop(self): self._thread = None _LOGGER.info("Stopped stream: %s", self.source) + async def async_record(self, video_path, duration=30, lookback=5): + """Make a .mp4 recording from a provided stream.""" -async def async_handle_record_service(hass, call): - """Handle save video service calls.""" - stream_source = call.data[CONF_STREAM_SOURCE] - video_path = call.data[CONF_FILENAME] - duration = call.data[CONF_DURATION] - lookback = call.data[CONF_LOOKBACK] - - # Check for file access - if not hass.config.is_allowed_path(video_path): - raise HomeAssistantError(f"Can't write {video_path}, no access to path!") - - # Check for active stream - streams = hass.data[DOMAIN][ATTR_STREAMS] - stream = streams.get(stream_source) - if not stream: - stream = Stream(hass, stream_source) - streams[stream_source] = stream - - # Add recorder - recorder = stream.outputs.get("recorder") - if recorder: - raise HomeAssistantError(f"Stream already recording to {recorder.video_path}!") - - recorder = stream.add_provider("recorder", timeout=duration) - recorder.video_path = video_path - - stream.start() - - # Take advantage of lookback - hls = stream.outputs.get("hls") - if lookback > 0 and hls: - num_segments = min(int(lookback // hls.target_duration), MAX_SEGMENTS) - # Wait for latest segment, then add the lookback - await hls.recv() - recorder.prepend(list(hls.get_segment())[-num_segments:]) + # Check for file access + if not self.hass.config.is_allowed_path(video_path): + raise HomeAssistantError(f"Can't write {video_path}, no access to path!") + + # Add recorder + recorder = self.outputs.get("recorder") + if recorder: + raise HomeAssistantError( + f"Stream already recording to {recorder.video_path}!" + ) + recorder = self.add_provider("recorder", timeout=duration) + recorder.video_path = video_path + + self.start() + + # Take advantage of lookback + hls = self.outputs.get("hls") + if lookback > 0 and hls: + num_segments = min(int(lookback // hls.target_duration), MAX_SEGMENTS) + # Wait for latest segment, then add the lookback + await hls.recv() + recorder.prepend(list(hls.get_segment())[-num_segments:]) diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 45fa3d9e76a179..4ee9f2a9814e00 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -1,15 +1,8 @@ """Constants for Stream component.""" DOMAIN = "stream" -CONF_STREAM_SOURCE = "stream_source" -CONF_LOOKBACK = "lookback" -CONF_DURATION = "duration" - ATTR_ENDPOINTS = "endpoints" ATTR_STREAMS = "streams" -ATTR_KEEPALIVE = "keepalive" - -SERVICE_RECORD = "record" OUTPUT_FORMATS = ["hls"] diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 5427172a55c11c..31c7940b8e1372 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -194,11 +194,7 @@ async def get(self, request, token, sequence=None): hass = request.app["hass"] stream = next( - ( - s - for s in hass.data[DOMAIN][ATTR_STREAMS].values() - if s.access_token == token - ), + (s for s in hass.data[DOMAIN][ATTR_STREAMS] if s.access_token == token), None, ) diff --git a/homeassistant/components/stream/services.yaml b/homeassistant/components/stream/services.yaml deleted file mode 100644 index a8652335bf1a20..00000000000000 --- a/homeassistant/components/stream/services.yaml +++ /dev/null @@ -1,15 +0,0 @@ -record: - description: Make a .mp4 recording from a provided stream. - fields: - stream_source: - description: The input source for the stream. - example: "rtsp://my.stream.feed:554" - filename: - description: The file name string. - example: "/tmp/my_stream.mp4" - duration: - description: "Target recording length (in seconds). Default: 30" - example: 30 - lookback: - description: "Target lookback period (in seconds) to include in addition to duration. Only available if there is currently an active HLS stream for stream_source. Default: 0" - example: 5 diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 2c2d744deb9b39..340a4b5d7567e3 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -155,25 +155,20 @@ async def test_websocket_camera_thumbnail(hass, hass_ws_client, mock_camera): async def test_websocket_stream_no_source( hass, hass_ws_client, mock_camera, mock_stream ): - """Test camera/stream websocket command.""" + """Test camera/stream websocket command with camera with no source.""" await async_setup_component(hass, "camera", {}) - with patch( - "homeassistant.components.camera.request_stream", - return_value="http://home.assistant/playlist.m3u8", - ) as mock_request_stream: - # Request playlist through WebSocket - client = await hass_ws_client(hass) - await client.send_json( - {"id": 6, "type": "camera/stream", "entity_id": "camera.demo_camera"} - ) - msg = await client.receive_json() + # Request playlist through WebSocket + client = await hass_ws_client(hass) + await client.send_json( + {"id": 6, "type": "camera/stream", "entity_id": "camera.demo_camera"} + ) + msg = await client.receive_json() - # Assert WebSocket response - assert not mock_request_stream.called - assert msg["id"] == 6 - assert msg["type"] == TYPE_RESULT - assert not msg["success"] + # Assert WebSocket response + assert msg["id"] == 6 + assert msg["type"] == TYPE_RESULT + assert not msg["success"] async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_stream): @@ -181,9 +176,9 @@ async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_s await async_setup_component(hass, "camera", {}) with patch( - "homeassistant.components.camera.request_stream", + "homeassistant.components.camera.Stream.endpoint_url", return_value="http://home.assistant/playlist.m3u8", - ) as mock_request_stream, patch( + ) as mock_stream_view_url, patch( "homeassistant.components.demo.camera.DemoCamera.stream_source", return_value="http://example.com", ): @@ -195,7 +190,7 @@ async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_s msg = await client.receive_json() # Assert WebSocket response - assert mock_request_stream.called + assert mock_stream_view_url.called assert msg["id"] == 6 assert msg["type"] == TYPE_RESULT assert msg["success"] @@ -248,9 +243,7 @@ async def test_play_stream_service_no_source(hass, mock_camera, mock_stream): ATTR_ENTITY_ID: "camera.demo_camera", camera.ATTR_MEDIA_PLAYER: "media_player.test", } - with patch("homeassistant.components.camera.request_stream"), pytest.raises( - HomeAssistantError - ): + with pytest.raises(HomeAssistantError): # Call service await hass.services.async_call( camera.DOMAIN, camera.SERVICE_PLAY_STREAM, data, blocking=True @@ -265,7 +258,7 @@ async def test_handle_play_stream_service(hass, mock_camera, mock_stream): ) await async_setup_component(hass, "media_player", {}) with patch( - "homeassistant.components.camera.request_stream" + "homeassistant.components.camera.Stream.endpoint_url", ) as mock_request_stream, patch( "homeassistant.components.demo.camera.DemoCamera.stream_source", return_value="http://example.com", @@ -289,7 +282,7 @@ async def test_no_preload_stream(hass, mock_stream): """Test camera preload preference.""" demo_prefs = CameraEntityPreferences({PREF_PRELOAD_STREAM: False}) with patch( - "homeassistant.components.camera.request_stream" + "homeassistant.components.camera.Stream.endpoint_url", ) as mock_request_stream, patch( "homeassistant.components.camera.prefs.CameraPreferences.get", return_value=demo_prefs, @@ -308,8 +301,8 @@ async def test_preload_stream(hass, mock_stream): """Test camera preload preference.""" demo_prefs = CameraEntityPreferences({PREF_PRELOAD_STREAM: True}) with patch( - "homeassistant.components.camera.request_stream" - ) as mock_request_stream, patch( + "homeassistant.components.camera.create_stream" + ) as mock_create_stream, patch( "homeassistant.components.camera.prefs.CameraPreferences.get", return_value=demo_prefs, ), patch( @@ -322,7 +315,7 @@ async def test_preload_stream(hass, mock_stream): await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - assert mock_request_stream.called + assert mock_create_stream.called async def test_record_service_invalid_path(hass, mock_camera): @@ -348,10 +341,9 @@ async def test_record_service(hass, mock_camera, mock_stream): "homeassistant.components.demo.camera.DemoCamera.stream_source", return_value="http://example.com", ), patch( - "homeassistant.components.stream.async_handle_record_service", - ) as mock_record_service, patch.object( - hass.config, "is_allowed_path", return_value=True - ): + "homeassistant.components.stream.Stream.async_record", + autospec=True, + ) as mock_record: # Call service await hass.services.async_call( camera.DOMAIN, @@ -361,4 +353,4 @@ async def test_record_service(hass, mock_camera, mock_stream): ) # So long as we call stream.record, the rest should be covered # by those tests. - assert mock_record_service.called + assert mock_record.called diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 9a147995541074..65f5306c4d8fbc 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -176,17 +176,18 @@ async def test_stream_source(aioclient_mock, hass, hass_client, hass_ws_client): "still_image_url": "https://example.com", "stream_source": 'http://example.com/{{ states.sensor.temp.state + "a" }}', "limit_refetch_to_url_change": True, - } + }, }, ) + assert await async_setup_component(hass, "stream", {}) await hass.async_block_till_done() hass.states.async_set("sensor.temp", "5") with patch( - "homeassistant.components.camera.request_stream", + "homeassistant.components.camera.Stream.endpoint_url", return_value="http://home.assistant/playlist.m3u8", - ) as mock_request_stream: + ) as mock_stream_url: # Request playlist through WebSocket client = await hass_ws_client(hass) @@ -196,25 +197,47 @@ async def test_stream_source(aioclient_mock, hass, hass_client, hass_ws_client): msg = await client.receive_json() # Assert WebSocket response - assert mock_request_stream.call_count == 1 - assert mock_request_stream.call_args[0][1] == "http://example.com/5a" + assert mock_stream_url.call_count == 1 assert msg["id"] == 1 assert msg["type"] == TYPE_RESULT assert msg["success"] assert msg["result"]["url"][-13:] == "playlist.m3u8" - # Cause a template render error - hass.states.async_remove("sensor.temp") + +async def test_stream_source_error(aioclient_mock, hass, hass_client, hass_ws_client): + """Test that the stream source has an error.""" + assert await async_setup_component( + hass, + "camera", + { + "camera": { + "name": "config_test", + "platform": "generic", + "still_image_url": "https://example.com", + # Does not exist + "stream_source": 'http://example.com/{{ states.sensor.temp.state + "a" }}', + "limit_refetch_to_url_change": True, + }, + }, + ) + assert await async_setup_component(hass, "stream", {}) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.camera.Stream.endpoint_url", + return_value="http://home.assistant/playlist.m3u8", + ) as mock_stream_url: + # Request playlist through WebSocket + client = await hass_ws_client(hass) await client.send_json( - {"id": 2, "type": "camera/stream", "entity_id": "camera.config_test"} + {"id": 1, "type": "camera/stream", "entity_id": "camera.config_test"} ) msg = await client.receive_json() - # Assert that no new call to the stream request should have been made - assert mock_request_stream.call_count == 1 - # Assert the websocket error message - assert msg["id"] == 2 + # Assert WebSocket response + assert mock_stream_url.call_count == 0 + assert msg["id"] == 1 assert msg["type"] == TYPE_RESULT assert msg["success"] is False assert msg["error"] == { @@ -240,7 +263,7 @@ async def test_no_stream_source(aioclient_mock, hass, hass_client, hass_ws_clien await hass.async_block_till_done() with patch( - "homeassistant.components.camera.request_stream", + "homeassistant.components.camera.Stream.endpoint_url", return_value="http://home.assistant/playlist.m3u8", ) as mock_request_stream: # Request playlist through WebSocket diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py index 117c8e97884b8a..956d6036aed544 100644 --- a/tests/components/nest/camera_sdm_test.py +++ b/tests/components/nest/camera_sdm_test.py @@ -16,6 +16,7 @@ from homeassistant.components import camera from homeassistant.components.camera import STATE_IDLE from homeassistant.exceptions import HomeAssistantError +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from .common import async_setup_sdm_platform @@ -245,12 +246,17 @@ async def test_refresh_expired_stream_token(hass, auth): DEVICE_TRAITS, auth=auth, ) + assert await async_setup_component(hass, "stream", {}) assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") assert cam is not None assert cam.state == STATE_IDLE + # Request a stream for the camera entity to exercise nest cam + camera interaction + # and shutdown on url expiration + await camera.async_request_stream(hass, cam.entity_id, "hls") + stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.1.streamingToken" diff --git a/tests/components/stream/common.py b/tests/components/stream/common.py index c99cdef79841f5..5ec4f4217ce3aa 100644 --- a/tests/components/stream/common.py +++ b/tests/components/stream/common.py @@ -5,9 +5,6 @@ import av import numpy as np -from homeassistant.components.stream import Stream -from homeassistant.components.stream.const import ATTR_STREAMS, DOMAIN - AUDIO_SAMPLE_RATE = 8000 @@ -93,10 +90,3 @@ def generate_audio_frame(pcm_mulaw=False): output.seek(0) return output - - -def preload_stream(hass, stream_source): - """Preload a stream for use in tests.""" - stream = Stream(hass, stream_source) - hass.data[DOMAIN][ATTR_STREAMS][stream_source] = stream - return stream diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index ab49a56ca029c7..b575b3877fa539 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -5,13 +5,13 @@ import av -from homeassistant.components.stream import request_stream +from homeassistant.components.stream import create_stream from homeassistant.const import HTTP_NOT_FOUND from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed -from tests.components.stream.common import generate_h264_video, preload_stream +from tests.components.stream.common import generate_h264_video async def test_hls_stream(hass, hass_client, stream_worker_sync): @@ -27,11 +27,12 @@ async def test_hls_stream(hass, hass_client, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() - stream = preload_stream(hass, source) - stream.add_provider("hls") + stream = create_stream(hass, source) # Request stream - url = request_stream(hass, source) + stream.add_provider("hls") + stream.start() + url = stream.endpoint_url("hls") http_client = await hass_client() @@ -72,11 +73,12 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() - stream = preload_stream(hass, source) - stream.add_provider("hls") + stream = create_stream(hass, source) # Request stream - url = request_stream(hass, source) + stream.add_provider("hls") + stream.start() + url = stream.endpoint_url("hls") http_client = await hass_client() @@ -113,11 +115,13 @@ async def test_stream_ended(hass, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() - stream = preload_stream(hass, source) + stream = create_stream(hass, source) track = stream.add_provider("hls") # Request stream - request_stream(hass, source) + stream.add_provider("hls") + stream.start() + stream.endpoint_url("hls") # Run it dead while True: @@ -142,9 +146,10 @@ async def test_stream_keepalive(hass): # Setup demo HLS track source = "test_stream_keepalive_source" - stream = preload_stream(hass, source) + stream = create_stream(hass, source) track = stream.add_provider("hls") track.num_segments = 2 + stream.start() cur_time = 0 @@ -163,7 +168,8 @@ def time_side_effect(): av_open.side_effect = av.error.InvalidDataError(-2, "error") mock_time.time.side_effect = time_side_effect # Request stream - request_stream(hass, source, keepalive=True) + stream.keepalive = True + stream.start() stream._thread.join() stream._thread = None assert av_open.call_count == 2 diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py deleted file mode 100644 index 2e13493b641f82..00000000000000 --- a/tests/components/stream/test_init.py +++ /dev/null @@ -1,86 +0,0 @@ -"""The tests for stream.""" -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest - -from homeassistant.components.stream.const import ( - ATTR_STREAMS, - CONF_LOOKBACK, - CONF_STREAM_SOURCE, - DOMAIN, - SERVICE_RECORD, -) -from homeassistant.const import CONF_FILENAME -from homeassistant.exceptions import HomeAssistantError -from homeassistant.setup import async_setup_component - - -async def test_record_service_invalid_file(hass): - """Test record service call with invalid file.""" - await async_setup_component(hass, "stream", {"stream": {}}) - data = {CONF_STREAM_SOURCE: "rtsp://my.video", CONF_FILENAME: "/my/invalid/path"} - with pytest.raises(HomeAssistantError): - await hass.services.async_call(DOMAIN, SERVICE_RECORD, data, blocking=True) - - -async def test_record_service_init_stream(hass): - """Test record service call with invalid file.""" - await async_setup_component(hass, "stream", {"stream": {}}) - data = {CONF_STREAM_SOURCE: "rtsp://my.video", CONF_FILENAME: "/my/invalid/path"} - with patch("homeassistant.components.stream.Stream") as stream_mock, patch.object( - hass.config, "is_allowed_path", return_value=True - ): - # Setup stubs - stream_mock.return_value.outputs = {} - - # Call Service - await hass.services.async_call(DOMAIN, SERVICE_RECORD, data, blocking=True) - - # Assert - assert stream_mock.called - - -async def test_record_service_existing_record_session(hass): - """Test record service call with invalid file.""" - await async_setup_component(hass, "stream", {"stream": {}}) - source = "rtsp://my.video" - data = {CONF_STREAM_SOURCE: source, CONF_FILENAME: "/my/invalid/path"} - - # Setup stubs - stream_mock = MagicMock() - stream_mock.return_value.outputs = {"recorder": MagicMock()} - hass.data[DOMAIN][ATTR_STREAMS][source] = stream_mock - - with patch.object(hass.config, "is_allowed_path", return_value=True), pytest.raises( - HomeAssistantError - ): - # Call Service - await hass.services.async_call(DOMAIN, SERVICE_RECORD, data, blocking=True) - - -async def test_record_service_lookback(hass): - """Test record service call with invalid file.""" - await async_setup_component(hass, "stream", {"stream": {}}) - data = { - CONF_STREAM_SOURCE: "rtsp://my.video", - CONF_FILENAME: "/my/invalid/path", - CONF_LOOKBACK: 4, - } - - with patch("homeassistant.components.stream.Stream") as stream_mock, patch.object( - hass.config, "is_allowed_path", return_value=True - ): - # Setup stubs - hls_mock = MagicMock() - hls_mock.target_duration = 2 - hls_mock.recv = AsyncMock(return_value=None) - stream_mock.return_value.outputs = {"hls": hls_mock} - - # Call Service - await hass.services.async_call(DOMAIN, SERVICE_RECORD, data, blocking=True) - - assert stream_mock.called - stream_mock.return_value.add_provider.assert_called_once_with( - "recorder", timeout=30 - ) - assert hls_mock.recv.called diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index bda53a9cc17a72..9d418c360b1f8f 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -8,13 +8,15 @@ import av import pytest +from homeassistant.components.stream import create_stream from homeassistant.components.stream.core import Segment from homeassistant.components.stream.recorder import recorder_save_worker +from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed -from tests.components.stream.common import generate_h264_video, preload_stream +from tests.components.stream.common import generate_h264_video TEST_TIMEOUT = 10 @@ -75,10 +77,11 @@ async def test_record_stream(hass, hass_client, stream_worker_sync, record_worke # Setup demo track source = generate_h264_video() - stream = preload_stream(hass, source) - recorder = stream.add_provider("recorder") - stream.start() + stream = create_stream(hass, source) + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path") + recorder = stream.add_provider("recorder") while True: segment = await recorder.recv() if not segment: @@ -95,6 +98,27 @@ async def test_record_stream(hass, hass_client, stream_worker_sync, record_worke record_worker_sync.join() +async def test_record_lookback( + hass, hass_client, stream_worker_sync, record_worker_sync +): + """Exercise record with loopback.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + source = generate_h264_video() + stream = create_stream(hass, source) + + # Start an HLS feed to enable lookback + stream.add_provider("hls") + stream.start() + + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path", lookback=4) + + # This test does not need recorder cleanup since it is not fully exercised + + stream.stop() + + async def test_recorder_timeout(hass, hass_client, stream_worker_sync): """ Test recorder timeout. @@ -109,9 +133,11 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): with patch("homeassistant.components.stream.IdleTimer.fire") as mock_timeout: # Setup demo track source = generate_h264_video() - stream = preload_stream(hass, source) - recorder = stream.add_provider("recorder", timeout=30) - stream.start() + + stream = create_stream(hass, source) + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path") + recorder = stream.add_provider("recorder") await recorder.recv() @@ -128,6 +154,19 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): await hass.async_block_till_done() +async def test_record_path_not_allowed(hass, hass_client): + """Test where the output path is not allowed by home assistant configuration.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + # Setup demo track + source = generate_h264_video() + stream = create_stream(hass, source) + with patch.object( + hass.config, "is_allowed_path", return_value=False + ), pytest.raises(HomeAssistantError): + await stream.async_record("/example/path") + + async def test_recorder_save(tmpdir): """Test recorder save.""" # Setup @@ -165,9 +204,10 @@ async def test_record_stream_audio( source = generate_h264_video( container_format="mov", audio_codec=a_codec ) # mov can store PCM - stream = preload_stream(hass, source) + stream = create_stream(hass, source) + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path") recorder = stream.add_provider("recorder") - stream.start() while True: segment = await recorder.recv() From b33753f334b96faa1cfd6f66ab44507d11d8a610 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 8 Feb 2021 21:21:14 -0800 Subject: [PATCH 0330/1818] Move camera timeouts to constants (#46262) Addresses feedback from pr #45431. Also removes an redundant `create_stream` timeout. --- homeassistant/components/camera/__init__.py | 9 +++++---- homeassistant/components/camera/const.py | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index cbc98edf19b1f8..ba61f15547360f 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -46,6 +46,8 @@ from homeassistant.loader import bind_hass from .const import ( + CAMERA_IMAGE_TIMEOUT, + CAMERA_STREAM_SOURCE_TIMEOUT, CONF_DURATION, CONF_LOOKBACK, DATA_CAMERA_PREFS, @@ -359,7 +361,7 @@ async def create_stream(self) -> Stream: """Create a Stream for stream_source.""" # There is at most one stream (a decode worker) per camera if not self.stream: - async with async_timeout.timeout(10): + async with async_timeout.timeout(CAMERA_STREAM_SOURCE_TIMEOUT): source = await self.stream_source() if not source: return None @@ -506,7 +508,7 @@ class CameraImageView(CameraView): async def handle(self, request: web.Request, camera: Camera) -> web.Response: """Serve camera image.""" with suppress(asyncio.CancelledError, asyncio.TimeoutError): - async with async_timeout.timeout(10): + async with async_timeout.timeout(CAMERA_IMAGE_TIMEOUT): image = await camera.async_camera_image() if image: @@ -717,8 +719,7 @@ async def _async_stream_endpoint_url(hass, camera, fmt): async def async_handle_record_service(camera, call): """Handle stream recording service calls.""" - async with async_timeout.timeout(10): - stream = await camera.create_stream() + stream = await camera.create_stream() if not stream: raise HomeAssistantError(f"{camera.entity_id} does not support record service") diff --git a/homeassistant/components/camera/const.py b/homeassistant/components/camera/const.py index 615e54b0ecae4a..7218b19f8fe845 100644 --- a/homeassistant/components/camera/const.py +++ b/homeassistant/components/camera/const.py @@ -9,3 +9,6 @@ CONF_LOOKBACK = "lookback" CONF_DURATION = "duration" + +CAMERA_STREAM_SOURCE_TIMEOUT = 10 +CAMERA_IMAGE_TIMEOUT = 10 From 20f45f8ab9e744de7488fe66c5e7f8aca9b60e2d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 9 Feb 2021 08:31:29 +0100 Subject: [PATCH 0331/1818] Improve deCONZ tests by using aioclient_mock rather than patching web requests (#45927) * Don't patch web requests, use aioclient_mock instead * Remove stale prints * Remove tests for old way of loading platforms * Remove unused imports --- tests/components/deconz/test_binary_sensor.py | 60 ++-- tests/components/deconz/test_climate.py | 290 ++++++++---------- tests/components/deconz/test_config_flow.py | 34 +- tests/components/deconz/test_cover.py | 265 +++++++--------- tests/components/deconz/test_deconz_event.py | 6 +- .../components/deconz/test_device_trigger.py | 16 +- tests/components/deconz/test_fan.py | 145 ++++----- tests/components/deconz/test_gateway.py | 61 +++- tests/components/deconz/test_init.py | 45 +-- tests/components/deconz/test_light.py | 249 +++++++-------- tests/components/deconz/test_lock.py | 64 ++-- tests/components/deconz/test_logbook.py | 6 +- tests/components/deconz/test_scene.py | 52 ++-- tests/components/deconz/test_sensor.py | 53 ++-- tests/components/deconz/test_services.py | 105 ++++--- tests/components/deconz/test_switch.py | 116 +++---- 16 files changed, 705 insertions(+), 862 deletions(-) diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index f64a4c4c259395..70d3db4149b6fb 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -1,12 +1,10 @@ """deCONZ binary sensor platform tests.""" from copy import deepcopy -from unittest.mock import patch from homeassistant.components.binary_sensor import ( DEVICE_CLASS_MOTION, DEVICE_CLASS_VIBRATION, - DOMAIN as BINARY_SENSOR_DOMAIN, ) from homeassistant.components.deconz.const import ( CONF_ALLOW_CLIP_SENSOR, @@ -18,9 +16,12 @@ from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.helpers.entity_registry import async_entries_for_config_entry -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_request, + setup_deconz_integration, +) SENSORS = { "1": { @@ -63,28 +64,19 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, BINARY_SENSOR_DOMAIN, {"binary_sensor": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_binary_sensors(hass): +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) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_binary_sensors(hass): +async def test_binary_sensors(hass, aioclient_mock): """Test successful creation of binary sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 3 @@ -118,12 +110,13 @@ async def test_binary_sensors(hass): assert len(hass.states.async_all()) == 0 -async def test_allow_clip_sensor(hass): +async def test_allow_clip_sensor(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) config_entry = await setup_deconz_integration( hass, + aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True}, get_state_response=data, ) @@ -155,9 +148,9 @@ async def test_allow_clip_sensor(hass): assert hass.states.get("binary_sensor.clip_presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor(hass): +async def test_add_new_binary_sensor(hass, aioclient_mock): """Test that adding a new binary sensor works.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 0 @@ -175,10 +168,11 @@ async def test_add_new_binary_sensor(hass): assert hass.states.get("binary_sensor.presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor_ignored(hass): +async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): """Test that adding a new binary sensor is not allowed.""" config_entry = await setup_deconz_integration( hass, + aioclient_mock, options={CONF_MASTER_GATEWAY: True, CONF_ALLOW_NEW_DEVICES: False}, ) gateway = get_gateway_from_config_entry(hass, config_entry) @@ -202,16 +196,16 @@ async def test_add_new_binary_sensor_ignored(hass): len(async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 0 ) - with patch( - "pydeconz.DeconzSession.request", - return_value={ - "groups": {}, - "lights": {}, - "sensors": {"1": deepcopy(SENSORS["1"])}, - }, - ): - await hass.services.async_call(DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH) - await hass.async_block_till_done() + aioclient_mock.clear_requests() + data = { + "groups": {}, + "lights": {}, + "sensors": {"1": deepcopy(SENSORS["1"])}, + } + mock_deconz_request(aioclient_mock, config_entry.data, data) + + await hass.services.async_call(DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH) + await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 assert hass.states.get("binary_sensor.presence_sensor") diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index fcb6e16f07f8a6..0a37debade3c6c 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -1,7 +1,6 @@ """deCONZ climate platform tests.""" from copy import deepcopy -from unittest.mock import patch import pytest @@ -34,10 +33,7 @@ DECONZ_FAN_SMART, DECONZ_PRESET_MANUAL, ) -from homeassistant.components.deconz.const import ( - CONF_ALLOW_CLIP_SENSOR, - DOMAIN as DECONZ_DOMAIN, -) +from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import ( ATTR_ENTITY_ID, @@ -45,9 +41,12 @@ STATE_OFF, STATE_UNAVAILABLE, ) -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) SENSORS = { "1": { @@ -75,24 +74,13 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, CLIMATE_DOMAIN, {"climate": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_sensors(hass): +async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no climate entities.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_simple_climate_device(hass): +async def test_simple_climate_device(hass, aioclient_mock): """Test successful creation of climate entities. This is a simple water heater that only supports setting temperature and on and off. @@ -130,7 +118,9 @@ async def test_simple_climate_device(hass): "uniqueid": "14:b4:57:ff:fe:d5:4e:77-01-0201", } } - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 @@ -174,37 +164,31 @@ async def test_simple_climate_device(hass): # Verify service calls - thermostat_device = gateway.api.sensors["0"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config") # Service turn on thermostat - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_HEAT}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/sensors/0/config", json={"on": True}) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True} # Service turn on thermostat - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_OFF}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/sensors/0/config", json={"on": False}) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"on": False} # Service set HVAC mode to unsupported value - with patch.object( - thermostat_device, "_request", return_value=True - ) as set_callback, pytest.raises(ValueError): + with pytest.raises(ValueError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, @@ -213,11 +197,13 @@ async def test_simple_climate_device(hass): ) -async def test_climate_device_without_cooling_support(hass): +async def test_climate_device_without_cooling_support(hass, aioclient_mock): """Test successful creation of sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 @@ -280,54 +266,41 @@ async def test_climate_device_without_cooling_support(hass): # Verify service calls - thermostat_device = gateway.api.sensors["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/1/config") # Service set HVAC mode to auto - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_AUTO}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/sensors/1/config", json={"mode": "auto"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_AUTO}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"mode": "auto"} # Service set HVAC mode to heat - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_HEAT}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/sensors/1/config", json={"mode": "heat"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"mode": "heat"} # Service set HVAC mode to off - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_HVAC_MODE, - {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_OFF}, - blocking=True, - ) - set_callback.assert_called_with( - "put", "/sensors/1/config", json={"mode": "off"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: "climate.thermostat", ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"mode": "off"} # Service set HVAC mode to unsupported value - with patch.object( - thermostat_device, "_request", return_value=True - ) as set_callback, pytest.raises(ValueError): + with pytest.raises(ValueError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, @@ -337,22 +310,17 @@ async def test_climate_device_without_cooling_support(hass): # Service set temperature to 20 - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.thermostat", ATTR_TEMPERATURE: 20}, - blocking=True, - ) - set_callback.assert_called_with( - "put", "/sensors/1/config", json={"heatsetpoint": 2000.0} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: "climate.thermostat", ATTR_TEMPERATURE: 20}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"heatsetpoint": 2000.0} # Service set temperature without providing temperature attribute - with patch.object( - thermostat_device, "_request", return_value=True - ) as set_callback, pytest.raises(ValueError): + with pytest.raises(ValueError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -376,7 +344,7 @@ async def test_climate_device_without_cooling_support(hass): assert len(hass.states.async_all()) == 0 -async def test_climate_device_with_cooling_support(hass): +async def test_climate_device_with_cooling_support(hass, aioclient_mock): """Test successful creation of sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { @@ -406,7 +374,9 @@ async def test_climate_device_with_cooling_support(hass): "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", } } - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 @@ -438,23 +408,20 @@ async def test_climate_device_with_cooling_support(hass): # Verify service calls - thermostat_device = gateway.api.sensors["0"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config") # Service set temperature to 20 - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_TEMPERATURE, - {ATTR_ENTITY_ID: "climate.zen_01", ATTR_TEMPERATURE: 20}, - blocking=True, - ) - set_callback.assert_called_with( - "put", "/sensors/0/config", json={"coolsetpoint": 2000.0} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: "climate.zen_01", ATTR_TEMPERATURE: 20}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"coolsetpoint": 2000.0} -async def test_climate_device_with_fan_support(hass): +async def test_climate_device_with_fan_support(hass, aioclient_mock): """Test successful creation of sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { @@ -484,7 +451,9 @@ async def test_climate_device_with_fan_support(hass): "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", } } - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 @@ -546,39 +515,31 @@ async def test_climate_device_with_fan_support(hass): # Verify service calls - thermostat_device = gateway.api.sensors["0"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config") # Service set fan mode to off - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.zen_01", ATTR_FAN_MODE: FAN_OFF}, - blocking=True, - ) - set_callback.assert_called_with( - "put", "/sensors/0/config", json={"fanmode": "off"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: "climate.zen_01", ATTR_FAN_MODE: FAN_OFF}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"fanmode": "off"} # Service set fan mode to custom deCONZ mode smart - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: "climate.zen_01", ATTR_FAN_MODE: DECONZ_FAN_SMART}, - blocking=True, - ) - set_callback.assert_called_with( - "put", "/sensors/0/config", json={"fanmode": "smart"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: "climate.zen_01", ATTR_FAN_MODE: DECONZ_FAN_SMART}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"fanmode": "smart"} # Service set fan mode to unsupported value - with patch.object( - thermostat_device, "_request", return_value=True - ) as set_callback, pytest.raises(ValueError): + with pytest.raises(ValueError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, @@ -587,7 +548,7 @@ async def test_climate_device_with_fan_support(hass): ) -async def test_climate_device_with_preset(hass): +async def test_climate_device_with_preset(hass, aioclient_mock): """Test successful creation of sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { @@ -618,7 +579,9 @@ async def test_climate_device_with_preset(hass): "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", } } - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 @@ -671,41 +634,31 @@ async def test_climate_device_with_preset(hass): # Verify service calls - thermostat_device = gateway.api.sensors["0"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/sensors/0/config") # Service set preset to HASS preset - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - {ATTR_ENTITY_ID: "climate.zen_01", ATTR_PRESET_MODE: PRESET_COMFORT}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/sensors/0/config", json={"preset": "comfort"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: "climate.zen_01", ATTR_PRESET_MODE: PRESET_COMFORT}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"preset": "comfort"} # Service set preset to custom deCONZ preset - with patch.object(thermostat_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - {ATTR_ENTITY_ID: "climate.zen_01", ATTR_PRESET_MODE: DECONZ_PRESET_MANUAL}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/sensors/0/config", json={"preset": "manual"} - ) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: "climate.zen_01", ATTR_PRESET_MODE: DECONZ_PRESET_MANUAL}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"preset": "manual"} # Service set preset to unsupported value - with patch.object( - thermostat_device, "_request", return_value=True - ) as set_callback, pytest.raises(ValueError): + with pytest.raises(ValueError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, @@ -714,12 +667,13 @@ async def test_climate_device_with_preset(hass): ) -async def test_clip_climate_device(hass): +async def test_clip_climate_device(hass, aioclient_mock): """Test successful creation of sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) config_entry = await setup_deconz_integration( hass, + aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True}, get_state_response=data, ) @@ -751,11 +705,13 @@ async def test_clip_climate_device(hass): assert hass.states.get("climate.clip_thermostat").state == HVAC_MODE_HEAT -async def test_verify_state_update(hass): +async def test_verify_state_update(hass, aioclient_mock): """Test that state update properly.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO @@ -774,9 +730,9 @@ async def test_verify_state_update(hass): assert gateway.api.sensors["1"].changed_keys == {"state", "r", "t", "on", "e", "id"} -async def test_add_new_climate_device(hass): +async def test_add_new_climate_device(hass, aioclient_mock): """Test that adding a new climate device works.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 0 diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index e18418ff9aeced..19f544fabc9dd6 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -212,7 +212,7 @@ async def test_manual_configuration_after_discovery_ResponseError(hass, aioclien async def test_manual_configuration_update_configuration(hass, aioclient_mock): """Test that manual configuration can update existing config entry.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.get( pydeconz.utils.URL_DISCOVER, @@ -258,7 +258,7 @@ async def test_manual_configuration_update_configuration(hass, aioclient_mock): async def test_manual_configuration_dont_update_configuration(hass, aioclient_mock): """Test that _create_entry work and that bridgeid can be requested.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.get( pydeconz.utils.URL_DISCOVER, @@ -374,7 +374,7 @@ async def test_link_get_api_key_ResponseError(hass, aioclient_mock): async def test_reauth_flow_update_configuration(hass, aioclient_mock): """Verify reauth flow can update gateway API key.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( DECONZ_DOMAIN, @@ -442,9 +442,9 @@ async def test_flow_ssdp_discovery(hass, aioclient_mock): } -async def test_ssdp_discovery_update_configuration(hass): +async def test_ssdp_discovery_update_configuration(hass, aioclient_mock): """Test if a discovered bridge is configured but updates with new attributes.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) with patch( "homeassistant.components.deconz.async_setup_entry", @@ -467,9 +467,9 @@ async def test_ssdp_discovery_update_configuration(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_ssdp_discovery_dont_update_configuration(hass): +async def test_ssdp_discovery_dont_update_configuration(hass, aioclient_mock): """Test if a discovered bridge has already been configured.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( DECONZ_DOMAIN, @@ -486,9 +486,13 @@ async def test_ssdp_discovery_dont_update_configuration(hass): assert config_entry.data[CONF_HOST] == "1.2.3.4" -async def test_ssdp_discovery_dont_update_existing_hassio_configuration(hass): +async def test_ssdp_discovery_dont_update_existing_hassio_configuration( + hass, aioclient_mock +): """Test to ensure the SSDP discovery does not update an Hass.io entry.""" - config_entry = await setup_deconz_integration(hass, source=SOURCE_HASSIO) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, source=SOURCE_HASSIO + ) result = await hass.config_entries.flow.async_init( DECONZ_DOMAIN, @@ -543,9 +547,9 @@ async def test_flow_hassio_discovery(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_hassio_discovery_update_configuration(hass): +async def test_hassio_discovery_update_configuration(hass, aioclient_mock): """Test we can update an existing config entry.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) with patch( "homeassistant.components.deconz.async_setup_entry", @@ -571,9 +575,9 @@ async def test_hassio_discovery_update_configuration(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_hassio_discovery_dont_update_configuration(hass): +async def test_hassio_discovery_dont_update_configuration(hass, aioclient_mock): """Test we can update an existing config entry.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( DECONZ_DOMAIN, @@ -590,9 +594,9 @@ async def test_hassio_discovery_dont_update_configuration(hass): assert result["reason"] == "already_configured" -async def test_option_flow(hass): +async def test_option_flow(hass, aioclient_mock): """Test config flow options.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) result = await hass.config_entries.options.async_init(config_entry.entry_id) diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 43364208f4f681..e48d44fb61e386 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -1,7 +1,6 @@ """deCONZ cover platform tests.""" from copy import deepcopy -from unittest.mock import patch from homeassistant.components.cover import ( ATTR_CURRENT_TILT_POSITION, @@ -17,7 +16,6 @@ SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, ) -from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import ( ATTR_ENTITY_ID, @@ -25,9 +23,12 @@ STATE_OPEN, STATE_UNAVAILABLE, ) -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) COVERS = { "1": { @@ -72,28 +73,19 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, COVER_DOMAIN, {"cover": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_covers(hass): +async def test_no_covers(hass, aioclient_mock): """Test that no cover entities are created.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_cover(hass): +async def test_cover(hass, aioclient_mock): """Test that all supported cover entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = deepcopy(COVERS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 5 @@ -119,123 +111,91 @@ async def test_cover(hass): # Verify service calls for cover - windows_covering_device = gateway.api.lights["2"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/2/state") # Service open cover - with patch.object( - windows_covering_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.window_covering_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/2/state", json={"open": True}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.window_covering_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"open": True} # Service close cover - with patch.object( - windows_covering_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.window_covering_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/2/state", json={"open": False}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.window_covering_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"open": False} # Service set cover position - with patch.object( - windows_covering_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_SET_COVER_POSITION, - {ATTR_ENTITY_ID: "cover.window_covering_device", ATTR_POSITION: 40}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/2/state", json={"lift": 60}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: "cover.window_covering_device", ATTR_POSITION: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"lift": 60} # Service stop cover movement - with patch.object( - windows_covering_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_STOP_COVER, - {ATTR_ENTITY_ID: "cover.window_covering_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/2/state", json={"stop": True}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER, + {ATTR_ENTITY_ID: "cover.window_covering_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"stop": True} # Verify service calls for legacy cover - level_controllable_cover_device = gateway.api.lights["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service open cover - with patch.object( - level_controllable_cover_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"on": False}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"on": False} # Service close cover - with patch.object( - level_controllable_cover_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"on": True}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"on": True} # Service set cover position - with patch.object( - level_controllable_cover_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_SET_COVER_POSITION, - {ATTR_ENTITY_ID: "cover.level_controllable_cover", ATTR_POSITION: 40}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"bri": 152}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: "cover.level_controllable_cover", ATTR_POSITION: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[7][2] == {"bri": 152} # Service stop cover movement - with patch.object( - level_controllable_cover_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_STOP_COVER, - {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"bri_inc": 0}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER, + {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[8][2] == {"bri_inc": 0} # Test that a reported cover position of 255 (deconz-rest-api < 2.05.73) is interpreted correctly. assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN @@ -266,7 +226,7 @@ async def test_cover(hass): assert len(hass.states.async_all()) == 0 -async def test_tilt_cover(hass): +async def test_tilt_cover(hass, aioclient_mock): """Test that tilting a cover works.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = { @@ -290,54 +250,55 @@ async def test_tilt_cover(hass): "uniqueid": "00:24:46:00:00:12:34:56-01", } } - config_entry = await setup_deconz_integration(hass, get_state_response=data) - gateway = get_gateway_from_config_entry(hass, config_entry) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) assert len(hass.states.async_all()) == 1 entity = hass.states.get("cover.covering_device") assert entity.state == STATE_OPEN assert entity.attributes[ATTR_CURRENT_TILT_POSITION] == 100 - covering_device = gateway.api.lights["0"] - - with patch.object(covering_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_SET_COVER_TILT_POSITION, - {ATTR_ENTITY_ID: "cover.covering_device", ATTR_TILT_POSITION: 40}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/0/state", json={"tilt": 60}) - - with patch.object(covering_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER_TILT, - {ATTR_ENTITY_ID: "cover.covering_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/0/state", json={"tilt": 0}) - - with patch.object(covering_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER_TILT, - {ATTR_ENTITY_ID: "cover.covering_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/0/state", json={"tilt": 100}) + # Verify service calls for tilting cover + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state") + + # Service set tilt cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + {ATTR_ENTITY_ID: "cover.covering_device", ATTR_TILT_POSITION: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"tilt": 60} + + # Service open tilt cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER_TILT, + {ATTR_ENTITY_ID: "cover.covering_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"tilt": 0} + + # Service close tilt cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER_TILT, + {ATTR_ENTITY_ID: "cover.covering_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"tilt": 100} # Service stop cover movement - with patch.object(covering_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_STOP_COVER_TILT, - {ATTR_ENTITY_ID: "cover.covering_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/0/state", json={"stop": True}) + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER_TILT, + {ATTR_ENTITY_ID: "cover.covering_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"stop": True} diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 232de5eacd2030..1212d72a6ee6ea 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -54,11 +54,13 @@ } -async def test_deconz_events(hass): +async def test_deconz_events(hass, aioclient_mock): """Test successful creation of deconz events.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 3 diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index a5399fe479638e..b9d538588cdc49 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -44,11 +44,13 @@ } -async def test_get_triggers(hass): +async def test_get_triggers(hass, aioclient_mock): """Test triggers work.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) device_id = gateway.events[0].device_id triggers = await async_get_device_automations(hass, "trigger", device_id) @@ -108,20 +110,22 @@ async def test_get_triggers(hass): assert_lists_same(triggers, expected_triggers) -async def test_helper_successful(hass): +async def test_helper_successful(hass, aioclient_mock): """Verify trigger helper.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) device_id = gateway.events[0].device_id deconz_event = device_trigger._get_deconz_event_from_device_id(hass, device_id) assert deconz_event == gateway.events[0] -async def test_helper_no_match(hass): +async def test_helper_no_match(hass, aioclient_mock): """Verify trigger helper returns None when no event could be matched.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") assert deconz_event is None diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index 7f225196744f2f..c6acbb7f6aa343 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -1,11 +1,9 @@ """deCONZ fan platform tests.""" from copy import deepcopy -from unittest.mock import patch import pytest -from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.fan import ( ATTR_SPEED, @@ -19,9 +17,12 @@ SPEED_OFF, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) FANS = { "1": { @@ -44,28 +45,19 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, FAN_DOMAIN, {"fan": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_fans(hass): +async def test_no_fans(hass, aioclient_mock): """Test that no fan entities are created.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_fans(hass): +async def test_fans(hass, aioclient_mock): """Test that all supported fan entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = deepcopy(FANS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 # Light and fan @@ -91,104 +83,77 @@ async def test_fans(hass): # Test service calls - ceiling_fan_device = gateway.api.lights["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service turn on fan - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - FAN_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "fan.ceiling_fan"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"speed": 4}) + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"speed": 4} # Service turn off fan - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - FAN_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "fan.ceiling_fan"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"speed": 0}) + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "fan.ceiling_fan"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"speed": 0} # Service set fan speed to low - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_LOW}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"speed": 1}) + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_SPEED, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_LOW}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"speed": 1} # Service set fan speed to medium - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_MEDIUM}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"speed": 2}) + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_SPEED, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_MEDIUM}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"speed": 2} # Service set fan speed to high - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_HIGH}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"speed": 4}) + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_SPEED, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_HIGH}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"speed": 4} # Service set fan speed to off - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_OFF}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"speed": 0}) + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_SPEED, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_OFF}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"speed": 0} # Service set fan speed to unsupported value - with patch.object( - ceiling_fan_device, "_request", return_value=True - ) as set_callback, pytest.raises(ValueError): + with pytest.raises(ValueError): await hass.services.async_call( FAN_DOMAIN, SERVICE_SET_SPEED, {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: "bad value"}, blocking=True, ) - await hass.async_block_till_done() # Events with an unsupported speed gets converted to default speed "medium" diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index f670f2a1d10cf8..5c1642ba8f738f 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -29,21 +29,25 @@ ) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import CONN_CLASS_LOCAL_PUSH, SOURCE_SSDP -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON from homeassistant.helpers.dispatcher import async_dispatcher_connect from tests.common import MockConfigEntry API_KEY = "1234567890ABCDEF" BRIDGEID = "01234E56789A" +HOST = "1.2.3.4" +PORT = 80 -ENTRY_CONFIG = {CONF_API_KEY: API_KEY, CONF_HOST: "1.2.3.4", CONF_PORT: 80} +DEFAULT_URL = f"http://{HOST}:{PORT}/api/{API_KEY}" + +ENTRY_CONFIG = {CONF_API_KEY: API_KEY, CONF_HOST: HOST, CONF_PORT: PORT} ENTRY_OPTIONS = {} DECONZ_CONFIG = { "bridgeid": BRIDGEID, - "ipaddress": "1.2.3.4", + "ipaddress": HOST, "mac": "00:11:22:33:44:55", "modelid": "deCONZ", "name": "deCONZ mock gateway", @@ -60,8 +64,36 @@ } +def mock_deconz_request(aioclient_mock, config, data): + """Mock a deCONZ get request.""" + host = config[CONF_HOST] + port = config[CONF_PORT] + api_key = config[CONF_API_KEY] + + aioclient_mock.get( + f"http://{host}:{port}/api/{api_key}", + json=deepcopy(data), + headers={"content-type": CONTENT_TYPE_JSON}, + ) + + +def mock_deconz_put_request(aioclient_mock, config, path): + """Mock a deCONZ put request.""" + host = config[CONF_HOST] + port = config[CONF_PORT] + api_key = config[CONF_API_KEY] + + aioclient_mock.put( + f"http://{host}:{port}/api/{api_key}{path}", + json={}, + headers={"content-type": CONTENT_TYPE_JSON}, + ) + + async def setup_deconz_integration( hass, + aioclient_mock=None, + *, config=ENTRY_CONFIG, options=ENTRY_OPTIONS, get_state_response=DECONZ_WEB_REQUEST, @@ -81,22 +113,23 @@ async def setup_deconz_integration( ) config_entry.add_to_hass(hass) - with patch( - "pydeconz.DeconzSession.request", return_value=deepcopy(get_state_response) - ), patch("pydeconz.DeconzSession.start", return_value=True): + if aioclient_mock: + mock_deconz_request(aioclient_mock, config, get_state_response) + + with patch("pydeconz.DeconzSession.start", return_value=True): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() return config_entry -async def test_gateway_setup(hass): +async def test_gateway_setup(hass, aioclient_mock): """Successful setup.""" with patch( "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", return_value=True, ) as forward_entry_setup: - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) assert gateway.bridgeid == BRIDGEID assert gateway.master is True @@ -140,9 +173,9 @@ async def test_gateway_setup_fails(hass): assert not hass.data[DECONZ_DOMAIN] -async def test_connection_status_signalling(hass): +async def test_connection_status_signalling(hass, aioclient_mock): """Make sure that connection status triggers a dispatcher send.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) event_call = Mock() @@ -157,9 +190,9 @@ async def test_connection_status_signalling(hass): unsub() -async def test_update_address(hass): +async def test_update_address(hass, aioclient_mock): """Make sure that connection status triggers a dispatcher send.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) assert gateway.api.host == "1.2.3.4" @@ -195,9 +228,9 @@ async def test_gateway_trigger_reauth_flow(hass): assert hass.data[DECONZ_DOMAIN] == {} -async def test_reset_after_successful_setup(hass): +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) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) result = await gateway.async_reset() diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 43c0c48440c053..ed7655bf620b06 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -14,7 +14,6 @@ CONF_GROUP_ID_BASE, DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry 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 @@ -58,59 +57,65 @@ async def test_setup_entry_no_available_bridge(hass): assert not hass.data[DECONZ_DOMAIN] -async def test_setup_entry_successful(hass): +async def test_setup_entry_successful(hass, aioclient_mock): """Test setup entry is successful.""" - config_entry = await setup_deconz_integration(hass) - gateway = get_gateway_from_config_entry(hass, config_entry) + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert hass.data[DECONZ_DOMAIN] - assert gateway.bridgeid in hass.data[DECONZ_DOMAIN] - assert hass.data[DECONZ_DOMAIN][gateway.bridgeid].master + assert config_entry.unique_id in hass.data[DECONZ_DOMAIN] + assert hass.data[DECONZ_DOMAIN][config_entry.unique_id].master -async def test_setup_entry_multiple_gateways(hass): +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) - gateway = get_gateway_from_config_entry(hass, config_entry) + config_entry = await setup_deconz_integration(hass, aioclient_mock) + aioclient_mock.clear_requests() data = deepcopy(DECONZ_WEB_REQUEST) data["config"]["bridgeid"] = "01234E56789B" config_entry2 = await setup_deconz_integration( - hass, get_state_response=data, entry_id="2", unique_id="01234E56789B" + hass, + aioclient_mock, + get_state_response=data, + entry_id="2", + unique_id="01234E56789B", ) - gateway2 = get_gateway_from_config_entry(hass, config_entry2) assert len(hass.data[DECONZ_DOMAIN]) == 2 - assert hass.data[DECONZ_DOMAIN][gateway.bridgeid].master - assert not hass.data[DECONZ_DOMAIN][gateway2.bridgeid].master + assert hass.data[DECONZ_DOMAIN][config_entry.unique_id].master + assert not hass.data[DECONZ_DOMAIN][config_entry2.unique_id].master -async def test_unload_entry(hass): +async def test_unload_entry(hass, aioclient_mock): """Test being able to unload an entry.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert hass.data[DECONZ_DOMAIN] assert await async_unload_entry(hass, config_entry) assert not hass.data[DECONZ_DOMAIN] -async def test_unload_entry_multiple_gateways(hass): +async def test_unload_entry_multiple_gateways(hass, aioclient_mock): """Test being able to unload an entry and master gateway gets moved.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) + aioclient_mock.clear_requests() data = deepcopy(DECONZ_WEB_REQUEST) data["config"]["bridgeid"] = "01234E56789B" config_entry2 = await setup_deconz_integration( - hass, get_state_response=data, entry_id="2", unique_id="01234E56789B" + hass, + aioclient_mock, + get_state_response=data, + entry_id="2", + unique_id="01234E56789B", ) - gateway2 = get_gateway_from_config_entry(hass, config_entry2) assert len(hass.data[DECONZ_DOMAIN]) == 2 assert await async_unload_entry(hass, config_entry) assert len(hass.data[DECONZ_DOMAIN]) == 1 - assert hass.data[DECONZ_DOMAIN][gateway2.bridgeid].master + assert hass.data[DECONZ_DOMAIN][config_entry2.unique_id].master async def test_update_group_unique_id(hass): diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index bdb7fbb8aefec6..c7f7fab1868aab 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1,14 +1,10 @@ """deCONZ light platform tests.""" from copy import deepcopy -from unittest.mock import patch import pytest -from homeassistant.components.deconz.const import ( - CONF_ALLOW_DECONZ_GROUPS, - DOMAIN as DECONZ_DOMAIN, -) +from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -33,9 +29,12 @@ STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) GROUPS = { "1": { @@ -107,29 +106,20 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, LIGHT_DOMAIN, {"light": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_lights_or_groups(hass): +async def test_no_lights_or_groups(hass, aioclient_mock): """Test that no lights or groups entities are created.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_lights_and_groups(hass): +async def test_lights_and_groups(hass, aioclient_mock): """Test that lights or groups entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["groups"] = deepcopy(GROUPS) data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 6 @@ -183,73 +173,63 @@ async def test_lights_and_groups(hass): # Verify service calls - rgb_light_device = gateway.api.lights["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service turn on light with short color loop - with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - { - ATTR_ENTITY_ID: "light.rgb_light", - ATTR_COLOR_TEMP: 2500, - ATTR_BRIGHTNESS: 200, - ATTR_TRANSITION: 5, - ATTR_FLASH: FLASH_SHORT, - ATTR_EFFECT: EFFECT_COLORLOOP, - }, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", - "/lights/1/state", - json={ - "ct": 2500, - "bri": 200, - "transitiontime": 50, - "alert": "select", - "effect": "colorloop", - }, - ) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.rgb_light", + ATTR_COLOR_TEMP: 2500, + ATTR_BRIGHTNESS: 200, + ATTR_TRANSITION: 5, + ATTR_FLASH: FLASH_SHORT, + ATTR_EFFECT: EFFECT_COLORLOOP, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == { + "ct": 2500, + "bri": 200, + "transitiontime": 50, + "alert": "select", + "effect": "colorloop", + } # Service turn on light disabling color loop with long flashing - with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - { - ATTR_ENTITY_ID: "light.rgb_light", - ATTR_HS_COLOR: (20, 30), - ATTR_FLASH: FLASH_LONG, - ATTR_EFFECT: "None", - }, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", - "/lights/1/state", - json={"xy": (0.411, 0.351), "alert": "lselect", "effect": "none"}, - ) - - # Service turn on light with short flashing - - with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - { - ATTR_ENTITY_ID: "light.rgb_light", - ATTR_TRANSITION: 5, - ATTR_FLASH: FLASH_SHORT, - }, - blocking=True, - ) - await hass.async_block_till_done() - assert not set_callback.called + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.rgb_light", + ATTR_HS_COLOR: (20, 30), + ATTR_FLASH: FLASH_LONG, + ATTR_EFFECT: "None", + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == { + "xy": (0.411, 0.351), + "alert": "lselect", + "effect": "none", + } + + # Service turn on light with short flashing not supported + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: "light.rgb_light", + ATTR_TRANSITION: 5, + ATTR_FLASH: FLASH_SHORT, + }, + blocking=True, + ) + assert len(aioclient_mock.mock_calls) == 3 # Not called state_changed_event = { "t": "event", @@ -263,37 +243,31 @@ async def test_lights_and_groups(hass): # Service turn off light with short flashing - with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - { - ATTR_ENTITY_ID: "light.rgb_light", - ATTR_TRANSITION: 5, - ATTR_FLASH: FLASH_SHORT, - }, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", - "/lights/1/state", - json={"bri": 0, "transitiontime": 50, "alert": "select"}, - ) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: "light.rgb_light", + ATTR_TRANSITION: 5, + ATTR_FLASH: FLASH_SHORT, + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == { + "bri": 0, + "transitiontime": 50, + "alert": "select", + } # Service turn off light with long flashing - with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.rgb_light", ATTR_FLASH: FLASH_LONG}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/lights/1/state", json={"alert": "lselect"} - ) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.rgb_light", ATTR_FLASH: FLASH_LONG}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"alert": "lselect"} await hass.config_entries.async_unload(config_entry.entry_id) @@ -307,13 +281,14 @@ async def test_lights_and_groups(hass): assert len(hass.states.async_all()) == 0 -async def test_disable_light_groups(hass): +async def test_disable_light_groups(hass, aioclient_mock): """Test disallowing light groups work.""" data = deepcopy(DECONZ_WEB_REQUEST) data["groups"] = deepcopy(GROUPS) data["lights"] = deepcopy(LIGHTS) config_entry = await setup_deconz_integration( hass, + aioclient_mock, options={CONF_ALLOW_DECONZ_GROUPS: False}, get_state_response=data, ) @@ -341,7 +316,7 @@ async def test_disable_light_groups(hass): assert hass.states.get("light.light_group") is None -async def test_configuration_tool(hass): +async def test_configuration_tool(hass, aioclient_mock): """Test that lights or groups entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = { @@ -359,12 +334,12 @@ async def test_configuration_tool(hass): "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", } } - await setup_deconz_integration(hass, get_state_response=data) + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) assert len(hass.states.async_all()) == 0 -async def test_lidl_christmas_light(hass): +async def test_lidl_christmas_light(hass, aioclient_mock): """Test that lights or groups entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = { @@ -390,33 +365,27 @@ async def test_lidl_christmas_light(hass): "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", } } - config_entry = await setup_deconz_integration(hass, get_state_response=data) - gateway = get_gateway_from_config_entry(hass, config_entry) - xmas_light_device = gateway.api.lights["0"] + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) - assert len(hass.states.async_all()) == 1 + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state") - with patch.object(xmas_light_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - { - ATTR_ENTITY_ID: "light.xmas_light", - ATTR_HS_COLOR: (20, 30), - }, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", - "/lights/0/state", - json={"on": True, "hue": 3640, "sat": 76}, - ) + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.xmas_light", + ATTR_HS_COLOR: (20, 30), + }, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True, "hue": 3640, "sat": 76} assert hass.states.get("light.xmas_light") -async def test_non_color_light_reports_color(hass): +async def test_non_color_light_reports_color(hass, aioclient_mock): """Verify hs_color does not crash when a group gets updated with a bad color value. After calling a scene color temp light of certain manufacturers @@ -500,7 +469,9 @@ async def test_non_color_light_reports_color(hass): "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, } - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 3 @@ -531,7 +502,7 @@ async def test_non_color_light_reports_color(hass): assert hass.states.get("light.all").attributes[ATTR_HS_COLOR] -async def test_verify_group_supported_features(hass): +async def test_verify_group_supported_features(hass, aioclient_mock): """Test that group supported features reflect what included lights support.""" data = deepcopy(DECONZ_WEB_REQUEST) data["groups"] = deepcopy( @@ -581,7 +552,7 @@ async def test_verify_group_supported_features(hass): }, } ) - await setup_deconz_integration(hass, get_state_response=data) + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) assert len(hass.states.async_all()) == 4 diff --git a/tests/components/deconz/test_lock.py b/tests/components/deconz/test_lock.py index d53da74dfddcb4..a6b4caaec19553 100644 --- a/tests/components/deconz/test_lock.py +++ b/tests/components/deconz/test_lock.py @@ -1,9 +1,7 @@ """deCONZ lock platform tests.""" from copy import deepcopy -from unittest.mock import patch -from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.lock import ( DOMAIN as LOCK_DOMAIN, @@ -16,9 +14,12 @@ STATE_UNAVAILABLE, STATE_UNLOCKED, ) -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) LOCKS = { "1": { @@ -37,28 +38,19 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, LOCK_DOMAIN, {"lock": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_locks(hass): +async def test_no_locks(hass, aioclient_mock): """Test that no lock entities are created.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_locks(hass): +async def test_locks(hass, aioclient_mock): """Test that all supported lock entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = deepcopy(LOCKS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 1 @@ -81,31 +73,27 @@ async def test_locks(hass): # Verify service calls - door_lock_device = gateway.api.lights["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service lock door - with patch.object(door_lock_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_LOCK, - {ATTR_ENTITY_ID: "lock.door_lock"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"on": True}) + await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_LOCK, + {ATTR_ENTITY_ID: "lock.door_lock"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True} # Service unlock door - with patch.object(door_lock_device, "_request", return_value=True) as set_callback: - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_UNLOCK, - {ATTR_ENTITY_ID: "lock.door_lock"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"on": False}) + await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_UNLOCK, + {ATTR_ENTITY_ID: "lock.door_lock"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"on": False} await hass.config_entries.async_unload(config_entry.entry_id) diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 500ca03b7edc10..5886a29a8bfd04 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -14,7 +14,7 @@ from tests.components.logbook.test_init import MockLazyEventPartialState -async def test_humanifying_deconz_event(hass): +async def test_humanifying_deconz_event(hass, aioclient_mock): """Test humanifying deCONZ event.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { @@ -53,7 +53,9 @@ async def test_humanifying_deconz_event(hass): "uniqueid": "00:00:00:00:00:00:00:04-00", }, } - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) hass.config.components.add("recorder") diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index ca8df2c04259c1..229111bf9ae215 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -1,15 +1,15 @@ """deCONZ scene platform tests.""" from copy import deepcopy -from unittest.mock import patch -from homeassistant.components.deconz import DOMAIN as DECONZ_DOMAIN -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.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) GROUPS = { "1": { @@ -24,48 +24,38 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, SCENE_DOMAIN, {"scene": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_scenes(hass): +async def test_no_scenes(hass, aioclient_mock): """Test that scenes can be loaded without scenes being available.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_scenes(hass): +async def test_scenes(hass, aioclient_mock): """Test that scenes works.""" data = deepcopy(DECONZ_WEB_REQUEST) data["groups"] = deepcopy(GROUPS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) - gateway = get_gateway_from_config_entry(hass, config_entry) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) assert len(hass.states.async_all()) == 1 assert hass.states.get("scene.light_group_scene") # Verify service calls - group_scene = gateway.api.groups["1"].scenes["1"] + mock_deconz_put_request( + aioclient_mock, config_entry.data, "/groups/1/scenes/1/recall" + ) # Service turn on scene - with patch.object(group_scene, "_request", return_value=True) as set_callback: - await hass.services.async_call( - SCENE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "scene.light_group_scene"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/groups/1/scenes/1/recall", json={}) + await hass.services.async_call( + SCENE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "scene.light_group_scene"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {} await hass.config_entries.async_unload(config_entry.entry_id) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 426a88b8bb6303..8a00385ccb97b8 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -2,19 +2,14 @@ from copy import deepcopy -from homeassistant.components.deconz.const import ( - CONF_ALLOW_CLIP_SENSOR, - DOMAIN as DECONZ_DOMAIN, -) +from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR from homeassistant.components.deconz.gateway import get_gateway_from_config_entry -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, STATE_UNAVAILABLE, ) -from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -86,28 +81,19 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, SENSOR_DOMAIN, {"sensor": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_sensors(hass): +async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_sensors(hass): +async def test_sensors(hass, aioclient_mock): """Test successful creation of sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 5 @@ -176,12 +162,13 @@ async def test_sensors(hass): assert len(hass.states.async_all()) == 0 -async def test_allow_clip_sensors(hass): +async def test_allow_clip_sensors(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = deepcopy(SENSORS) config_entry = await setup_deconz_integration( hass, + aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True}, get_state_response=data, ) @@ -210,9 +197,9 @@ async def test_allow_clip_sensors(hass): assert hass.states.get("sensor.clip_light_level_sensor") -async def test_add_new_sensor(hass): +async def test_add_new_sensor(hass, aioclient_mock): """Test that adding a new sensor works.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 0 @@ -230,11 +217,13 @@ async def test_add_new_sensor(hass): assert hass.states.get("sensor.light_level_sensor").state == "999.8" -async def test_add_battery_later(hass): +async def test_add_battery_later(hass, aioclient_mock): """Test that a sensor without an initial battery state creates a battery sensor once state exist.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = {"1": deepcopy(SENSORS["3"])} - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) remote = gateway.api.sensors["1"] @@ -252,7 +241,7 @@ async def test_add_battery_later(hass): assert hass.states.get("sensor.switch_1_battery_level") -async def test_air_quality_sensor(hass): +async def test_air_quality_sensor(hass, aioclient_mock): """Test successful creation of air quality sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { @@ -274,7 +263,7 @@ async def test_air_quality_sensor(hass): "uniqueid": "00:12:4b:00:14:4d:00:07-02-fdef", } } - await setup_deconz_integration(hass, get_state_response=data) + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) assert len(hass.states.async_all()) == 1 @@ -282,7 +271,7 @@ async def test_air_quality_sensor(hass): assert air_quality.state == "poor" -async def test_time_sensor(hass): +async def test_time_sensor(hass, aioclient_mock): """Test successful creation of time sensor entities.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { @@ -305,7 +294,7 @@ async def test_time_sensor(hass): "uniqueid": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", } } - await setup_deconz_integration(hass, get_state_response=data) + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) assert len(hass.states.async_all()) == 2 @@ -316,13 +305,13 @@ async def test_time_sensor(hass): assert time_battery.state == "40" -async def test_unsupported_sensor(hass): +async def test_unsupported_sensor(hass, aioclient_mock): """Test that unsupported sensors doesn't break anything.""" data = deepcopy(DECONZ_WEB_REQUEST) data["sensors"] = { "0": {"type": "not supported", "name": "name", "state": {}, "config": {}} } - await setup_deconz_integration(hass, get_state_response=data) + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) assert len(hass.states.async_all()) == 1 diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index faa1d3485bbd4d..41eefa95785a72 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -25,7 +25,13 @@ from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.helpers.entity_registry import async_entries_for_config_entry -from .test_gateway import BRIDGEID, DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + BRIDGEID, + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + mock_deconz_request, + setup_deconz_integration, +) GROUP = { "1": { @@ -114,72 +120,66 @@ async def test_service_unload_not_registered(hass): async_remove.assert_not_called() -async def test_configure_service_with_field(hass): +async def test_configure_service_with_field(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) data = { - SERVICE_FIELD: "/light/2", + SERVICE_FIELD: "/lights/2", CONF_BRIDGE_ID: BRIDGEID, SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } - with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: - await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data - ) - await hass.async_block_till_done() - put_state.assert_called_with( - "put", "/light/2", json={"on": True, "attr1": 10, "attr2": 20} - ) + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/2") + + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data, blocking=True + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True, "attr1": 10, "attr2": 20} -async def test_configure_service_with_entity(hass): +async def test_configure_service_with_entity(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.deconz_ids["light.test"] = "/light/1" + gateway.deconz_ids["light.test"] = "/lights/1" data = { SERVICE_ENTITY: "light.test", SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } - with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: - await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data - ) - await hass.async_block_till_done() - put_state.assert_called_with( - "put", "/light/1", json={"on": True, "attr1": 10, "attr2": 20} - ) + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1") + + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data, blocking=True + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True, "attr1": 10, "attr2": 20} -async def test_configure_service_with_entity_and_field(hass): +async def test_configure_service_with_entity_and_field(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.deconz_ids["light.test"] = "/light/1" + gateway.deconz_ids["light.test"] = "/lights/1" data = { SERVICE_ENTITY: "light.test", SERVICE_FIELD: "/state", SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, } - with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: - await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data - ) - await hass.async_block_till_done() - put_state.assert_called_with( - "put", "/light/1/state", json={"on": True, "attr1": 10, "attr2": 20} - ) + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data, blocking=True + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True, "attr1": 10, "attr2": 20} -async def test_configure_service_with_faulty_field(hass): + +async def test_configure_service_with_faulty_field(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) data = {SERVICE_FIELD: "light/2", SERVICE_DATA: {}} @@ -190,9 +190,9 @@ async def test_configure_service_with_faulty_field(hass): await hass.async_block_till_done() -async def test_configure_service_with_faulty_entity(hass): +async def test_configure_service_with_faulty_entity(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) data = { SERVICE_ENTITY: "light.nonexisting", @@ -207,21 +207,24 @@ async def test_configure_service_with_faulty_entity(hass): put_state.assert_not_called() -async def test_service_refresh_devices(hass): +async def test_service_refresh_devices(hass, aioclient_mock): """Test that service can refresh devices.""" - config_entry = await setup_deconz_integration(hass) + config_entry = await setup_deconz_integration(hass, aioclient_mock) gateway = get_gateway_from_config_entry(hass, config_entry) + aioclient_mock.clear_requests() data = {CONF_BRIDGE_ID: BRIDGEID} - with patch( - "pydeconz.DeconzSession.request", - return_value={"groups": GROUP, "lights": LIGHT, "sensors": SENSOR}, - ): - await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data=data - ) - await hass.async_block_till_done() + mock_deconz_request( + aioclient_mock, + config_entry.data, + {"groups": GROUP, "lights": LIGHT, "sensors": SENSOR}, + ) + + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data=data + ) + await hass.async_block_till_done() assert gateway.deconz_ids == { "light.group_1_name": "/groups/1", @@ -231,12 +234,14 @@ async def test_service_refresh_devices(hass): } -async def test_remove_orphaned_entries_service(hass): +async def test_remove_orphaned_entries_service(hass, aioclient_mock): """Test service works and also don't remove more than expected.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = deepcopy(LIGHT) data["sensors"] = deepcopy(SWITCH) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) data = {CONF_BRIDGE_ID: BRIDGEID} diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 22ce182cb62173..6aafac1bd4220a 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -1,9 +1,7 @@ """deCONZ switch platform tests.""" from copy import deepcopy -from unittest.mock import patch -from homeassistant.components.deconz import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, @@ -11,9 +9,12 @@ SERVICE_TURN_ON, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE -from homeassistant.setup import async_setup_component -from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from .test_gateway import ( + DECONZ_WEB_REQUEST, + mock_deconz_put_request, + setup_deconz_integration, +) POWER_PLUGS = { "1": { @@ -64,28 +65,19 @@ } -async def test_platform_manually_configured(hass): - """Test that we do not discover anything or try to set up a gateway.""" - assert ( - await async_setup_component( - hass, SWITCH_DOMAIN, {"switch": {"platform": DECONZ_DOMAIN}} - ) - is True - ) - assert DECONZ_DOMAIN not in hass.data - - -async def test_no_switches(hass): +async def test_no_switches(hass, aioclient_mock): """Test that no switch entities are created.""" - await setup_deconz_integration(hass) + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 -async def test_power_plugs(hass): +async def test_power_plugs(hass, aioclient_mock): """Test that all supported switch entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = deepcopy(POWER_PLUGS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 4 @@ -107,35 +99,27 @@ async def test_power_plugs(hass): # Verify service calls - on_off_switch_device = gateway.api.lights["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service turn on power plug - with patch.object( - on_off_switch_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.on_off_switch"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"on": True}) + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.on_off_switch"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"on": True} # Service turn off power plug - with patch.object( - on_off_switch_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "switch.on_off_switch"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/1/state", json={"on": False}) + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.on_off_switch"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"on": False} await hass.config_entries.async_unload(config_entry.entry_id) @@ -149,11 +133,13 @@ async def test_power_plugs(hass): assert len(hass.states.async_all()) == 0 -async def test_sirens(hass): +async def test_sirens(hass, aioclient_mock): """Test that siren entities are created.""" data = deepcopy(DECONZ_WEB_REQUEST) data["lights"] = deepcopy(SIRENS) - config_entry = await setup_deconz_integration(hass, get_state_response=data) + config_entry = await setup_deconz_integration( + hass, aioclient_mock, get_state_response=data + ) gateway = get_gateway_from_config_entry(hass, config_entry) assert len(hass.states.async_all()) == 2 @@ -173,39 +159,27 @@ async def test_sirens(hass): # Verify service calls - warning_device_device = gateway.api.lights["1"] + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service turn on siren - with patch.object( - warning_device_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.warning_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/lights/1/state", json={"alert": "lselect"} - ) + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.warning_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"alert": "lselect"} # Service turn off siren - with patch.object( - warning_device_device, "_request", return_value=True - ) as set_callback: - await hass.services.async_call( - SWITCH_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "switch.warning_device"}, - blocking=True, - ) - await hass.async_block_till_done() - set_callback.assert_called_with( - "put", "/lights/1/state", json={"alert": "none"} - ) + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.warning_device"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"alert": "none"} await hass.config_entries.async_unload(config_entry.entry_id) From 2fc1c19a45ce2bd7479c027a02b225454c0fe1f6 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 9 Feb 2021 09:28:40 +0100 Subject: [PATCH 0332/1818] Allow to setup of a previously discovered sleeping Shelly device (#46124) Co-authored-by: Franck Nijhof --- .../components/shelly/config_flow.py | 18 ++++ homeassistant/components/shelly/strings.json | 4 +- tests/components/shelly/test_config_flow.py | 98 ++++++++++++++++++- 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 09fc477e512ec7..026021a992ffa4 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -65,6 +65,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH host = None info = None + device_info = None async def async_step_user(self, user_input=None): """Handle the initial step.""" @@ -160,6 +161,13 @@ async def async_step_zeroconf(self, zeroconf_info): await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] + + if not info["auth"] and info.get("sleep_mode", False): + try: + self.device_info = await validate_input(self.hass, self.host, {}) + except HTTP_CONNECT_ERRORS: + return self.async_abort(reason="cannot_connect") + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { "name": zeroconf_info.get("name", "").split(".")[0] @@ -173,6 +181,16 @@ async def async_step_confirm_discovery(self, user_input=None): if self.info["auth"]: return await self.async_step_credentials() + if self.device_info: + return self.async_create_entry( + title=self.device_info["title"] or self.device_info["hostname"], + data={ + "host": self.host, + "sleep_period": self.device_info["sleep_period"], + "model": self.device_info["model"], + }, + ) + try: device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 341328801cc3c1..85a1fa87d0c070 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -3,7 +3,7 @@ "flow_title": "{name}", "step": { "user": { - "description": "Before set up, battery-powered devices must be woken up by pressing the button on the device.", + "description": "Before set up, battery-powered devices must be woken up, you can now wake the device up using a button on it.", "data": { "host": "[%key:common::config_flow::data::host%]" } @@ -15,7 +15,7 @@ } }, "confirm_discovery": { - "description": "Do you want to set up the {model} at {host}?\n\nBefore set up, battery-powered devices must be woken up by pressing the button on the device." + "description": "Do you want to set up the {model} at {host}?\n\nBattery-powered devices that are password protected must be woken up before continuing with setting up.\nBattery-powered devices that are not password protected will be added when the device wakes up, you can now manually wake the device up using a button on it or wait for the next data update from the device." } }, "error": { diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 1d5099cec1cd2a..450bf8efb24ddf 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -14,7 +14,6 @@ MOCK_SETTINGS = { "name": "Test name", "device": {"mac": "test-mac", "hostname": "test-host", "type": "SHSW-1"}, - "sleep_period": 0, } DISCOVERY_INFO = { "host": "1.1.1.1", @@ -383,6 +382,103 @@ async def test_zeroconf(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_zeroconf_sleeping_device(hass): + """Test sleeping device configuration via zeroconf.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "sleep_mode": True, + }, + ), patch( + "aioshelly.Device.create", + new=AsyncMock( + return_value=Mock( + settings={ + "name": "Test name", + "device": { + "mac": "test-mac", + "hostname": "test-host", + "type": "SHSW-1", + }, + "sleep_mode": {"period": 10, "unit": "m"}, + }, + ) + ), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + context = next( + flow["context"] + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert context["title_placeholders"]["name"] == "shelly1pm-12345" + with patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.shelly.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"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Test name" + assert result2["data"] == { + "host": "1.1.1.1", + "model": "SHSW-1", + "sleep_period": 600, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + "error", + [ + (aiohttp.ClientResponseError(Mock(), (), status=400), "cannot_connect"), + (asyncio.TimeoutError, "cannot_connect"), + ], +) +async def test_zeroconf_sleeping_device_error(hass, error): + """Test sleeping device configuration via zeroconf with error.""" + exc = error + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "sleep_mode": True, + }, + ), patch( + "aioshelly.Device.create", + new=AsyncMock(side_effect=exc), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + @pytest.mark.parametrize( "error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")] ) From 6a62ebb6a490c7f22ca2fac98f7a813fbeb27d4c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Feb 2021 22:43:38 -1000 Subject: [PATCH 0333/1818] Add BPUP (push updates) support to bond (#45550) --- homeassistant/components/bond/__init__.py | 17 +++- homeassistant/components/bond/config_flow.py | 2 +- homeassistant/components/bond/const.py | 5 ++ homeassistant/components/bond/cover.py | 14 +-- homeassistant/components/bond/entity.py | 90 +++++++++++++++++--- homeassistant/components/bond/fan.py | 16 ++-- homeassistant/components/bond/light.py | 28 +++--- homeassistant/components/bond/manifest.json | 2 +- homeassistant/components/bond/switch.py | 14 +-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bond/common.py | 20 +++-- tests/components/bond/test_init.py | 3 +- 13 files changed, 163 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 88ea084d25fc8e..4e6705cbe09e9e 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -3,7 +3,7 @@ from asyncio import TimeoutError as AsyncIOTimeoutError from aiohttp import ClientError, ClientTimeout -from bond_api import Bond +from bond_api import Bond, BPUPSubscriptions, start_bpup from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST @@ -12,7 +12,7 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import SLOW_UPDATE_WARNING -from .const import BRIDGE_MAKE, DOMAIN +from .const import BPUP_STOP, BPUP_SUBS, BRIDGE_MAKE, DOMAIN, HUB from .utils import BondHub PLATFORMS = ["cover", "fan", "light", "switch"] @@ -38,7 +38,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except (ClientError, AsyncIOTimeoutError, OSError) as error: raise ConfigEntryNotReady from error - hass.data[DOMAIN][config_entry_id] = hub + bpup_subs = BPUPSubscriptions() + stop_bpup = await start_bpup(host, bpup_subs) + + hass.data[DOMAIN][entry.entry_id] = { + HUB: hub, + BPUP_SUBS: bpup_subs, + BPUP_STOP: stop_bpup, + } if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id) @@ -74,6 +81,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) ) + data = hass.data[DOMAIN][entry.entry_id] + if BPUP_STOP in data: + data[BPUP_STOP]() + if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 6666cd57ca357e..2004da0c81e8cd 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -55,7 +55,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Bond.""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH _discovered: dict = None diff --git a/homeassistant/components/bond/const.py b/homeassistant/components/bond/const.py index 3031c159b0f5eb..818288a5764ca6 100644 --- a/homeassistant/components/bond/const.py +++ b/homeassistant/components/bond/const.py @@ -5,3 +5,8 @@ DOMAIN = "bond" CONF_BOND_ID: str = "bond_id" + + +HUB = "hub" +BPUP_SUBS = "bpup_subs" +BPUP_STOP = "bpup_stop" diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index dc0fc6d500c026..6b3c8d6bc02828 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -1,14 +1,14 @@ """Support for Bond covers.""" from typing import Any, Callable, List, Optional -from bond_api import Action, DeviceType +from bond_api import Action, BPUPSubscriptions, DeviceType from homeassistant.components.cover import DEVICE_CLASS_SHADE, CoverEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity -from .const import DOMAIN +from .const import BPUP_SUBS, DOMAIN, HUB from .entity import BondEntity from .utils import BondDevice, BondHub @@ -19,10 +19,12 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Bond cover devices.""" - hub: BondHub = hass.data[DOMAIN][entry.entry_id] + data = hass.data[DOMAIN][entry.entry_id] + hub: BondHub = data[HUB] + bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] covers = [ - BondCover(hub, device) + BondCover(hub, device, bpup_subs) for device in hub.devices if device.type == DeviceType.MOTORIZED_SHADES ] @@ -33,9 +35,9 @@ async def async_setup_entry( class BondCover(BondEntity, CoverEntity): """Representation of a Bond cover.""" - def __init__(self, hub: BondHub, device: BondDevice): + def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions): """Create HA entity representing Bond cover.""" - super().__init__(hub, device) + super().__init__(hub, device, bpup_subs) self._closed: Optional[bool] = None diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 2819182c9b51f6..769794a31e8b1a 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -1,37 +1,51 @@ """An abstract class common to all Bond entities.""" from abc import abstractmethod -from asyncio import TimeoutError as AsyncIOTimeoutError +from asyncio import Lock, TimeoutError as AsyncIOTimeoutError +from datetime import timedelta import logging from typing import Any, Dict, Optional from aiohttp import ClientError +from bond_api import BPUPSubscriptions from homeassistant.const import ATTR_NAME +from homeassistant.core import callback from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import async_track_time_interval from .const import DOMAIN from .utils import BondDevice, BondHub _LOGGER = logging.getLogger(__name__) +_FALLBACK_SCAN_INTERVAL = timedelta(seconds=10) + class BondEntity(Entity): """Generic Bond entity encapsulating common features of any Bond controlled device.""" def __init__( - self, hub: BondHub, device: BondDevice, sub_device: Optional[str] = None + self, + hub: BondHub, + device: BondDevice, + bpup_subs: BPUPSubscriptions, + sub_device: Optional[str] = None, ): """Initialize entity with API and device info.""" self._hub = hub self._device = device + self._device_id = device.device_id self._sub_device = sub_device self._available = True + self._bpup_subs = bpup_subs + self._update_lock = None + self._initialized = False @property def unique_id(self) -> Optional[str]: """Get unique ID for the entity.""" hub_id = self._hub.bond_id - device_id = self._device.device_id + device_id = self._device_id sub_device_id: str = f"_{self._sub_device}" if self._sub_device else "" return f"{hub_id}_{device_id}{sub_device_id}" @@ -40,13 +54,18 @@ def name(self) -> Optional[str]: """Get entity name.""" return self._device.name + @property + def should_poll(self): + """No polling needed.""" + return False + @property def device_info(self) -> Optional[Dict[str, Any]]: """Get a an HA device representing this Bond controlled device.""" device_info = { ATTR_NAME: self.name, "manufacturer": self._hub.make, - "identifiers": {(DOMAIN, self._hub.bond_id, self._device.device_id)}, + "identifiers": {(DOMAIN, self._hub.bond_id, self._device_id)}, "via_device": (DOMAIN, self._hub.bond_id), } if not self._hub.is_bridge: @@ -75,8 +94,29 @@ def available(self) -> bool: async def async_update(self): """Fetch assumed state of the cover from the hub using API.""" + await self._async_update_from_api() + + async def _async_update_if_bpup_not_alive(self, *_): + """Fetch via the API if BPUP is not alive.""" + if self._bpup_subs.alive and self._initialized: + return + + if self._update_lock.locked(): + _LOGGER.warning( + "Updating %s took longer than the scheduled update interval %s", + self.entity_id, + _FALLBACK_SCAN_INTERVAL, + ) + return + + async with self._update_lock: + await self._async_update_from_api() + self.async_write_ha_state() + + async def _async_update_from_api(self): + """Fetch via the API.""" try: - state: dict = await self._hub.bond.device_state(self._device.device_id) + state: dict = await self._hub.bond.device_state(self._device_id) except (ClientError, AsyncIOTimeoutError, OSError) as error: if self._available: _LOGGER.warning( @@ -84,12 +124,42 @@ async def async_update(self): ) self._available = False else: - _LOGGER.debug("Device state for %s is:\n%s", self.entity_id, state) - if not self._available: - _LOGGER.info("Entity %s has come back", self.entity_id) - self._available = True - self._apply_state(state) + self._async_state_callback(state) @abstractmethod def _apply_state(self, state: dict): raise NotImplementedError + + @callback + def _async_state_callback(self, state): + """Process a state change.""" + self._initialized = True + if not self._available: + _LOGGER.info("Entity %s has come back", self.entity_id) + self._available = True + _LOGGER.debug( + "Device state for %s (%s) is:\n%s", self.name, self.entity_id, state + ) + self._apply_state(state) + + @callback + def _async_bpup_callback(self, state): + """Process a state change from BPUP.""" + self._async_state_callback(state) + self.async_write_ha_state() + + async def async_added_to_hass(self): + """Subscribe to BPUP and start polling.""" + await super().async_added_to_hass() + self._update_lock = Lock() + self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) + self.async_on_remove( + async_track_time_interval( + self.hass, self._async_update_if_bpup_not_alive, _FALLBACK_SCAN_INTERVAL + ) + ) + + async def async_will_remove_from_hass(self) -> None: + """Unsubscribe from BPUP data on remove.""" + await super().async_will_remove_from_hass() + self._bpup_subs.unsubscribe(self._device_id, self._async_bpup_callback) diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 18eeb912ed8192..9b70195db5d631 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -3,7 +3,7 @@ import math from typing import Any, Callable, List, Optional, Tuple -from bond_api import Action, DeviceType, Direction +from bond_api import Action, BPUPSubscriptions, DeviceType, Direction from homeassistant.components.fan import ( DIRECTION_FORWARD, @@ -20,7 +20,7 @@ ranged_value_to_percentage, ) -from .const import DOMAIN +from .const import BPUP_SUBS, DOMAIN, HUB from .entity import BondEntity from .utils import BondDevice, BondHub @@ -33,10 +33,14 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Bond fan devices.""" - hub: BondHub = hass.data[DOMAIN][entry.entry_id] + data = hass.data[DOMAIN][entry.entry_id] + hub: BondHub = data[HUB] + bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] fans = [ - BondFan(hub, device) for device in hub.devices if DeviceType.is_fan(device.type) + BondFan(hub, device, bpup_subs) + for device in hub.devices + if DeviceType.is_fan(device.type) ] async_add_entities(fans, True) @@ -45,9 +49,9 @@ async def async_setup_entry( class BondFan(BondEntity, FanEntity): """Representation of a Bond fan.""" - def __init__(self, hub: BondHub, device: BondDevice): + def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions): """Create HA entity representing Bond fan.""" - super().__init__(hub, device) + super().__init__(hub, device, bpup_subs) self._power: Optional[bool] = None self._speed: Optional[int] = None diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index c0809a0aee7c3b..8d0dfe85246397 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -2,7 +2,7 @@ import logging from typing import Any, Callable, List, Optional -from bond_api import Action, DeviceType +from bond_api import Action, BPUPSubscriptions, DeviceType from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -14,7 +14,7 @@ from homeassistant.helpers.entity import Entity from . import BondHub -from .const import DOMAIN +from .const import BPUP_SUBS, DOMAIN, HUB from .entity import BondEntity from .utils import BondDevice @@ -27,28 +27,30 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Bond light devices.""" - hub: BondHub = hass.data[DOMAIN][entry.entry_id] + data = hass.data[DOMAIN][entry.entry_id] + hub: BondHub = data[HUB] + bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] fan_lights: List[Entity] = [ - BondLight(hub, device) + BondLight(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) and device.supports_light() ] fireplaces: List[Entity] = [ - BondFireplace(hub, device) + BondFireplace(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fireplace(device.type) ] fp_lights: List[Entity] = [ - BondLight(hub, device, "light") + BondLight(hub, device, bpup_subs, "light") for device in hub.devices if DeviceType.is_fireplace(device.type) and device.supports_light() ] lights: List[Entity] = [ - BondLight(hub, device) + BondLight(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_light(device.type) ] @@ -60,10 +62,14 @@ class BondLight(BondEntity, LightEntity): """Representation of a Bond light.""" def __init__( - self, hub: BondHub, device: BondDevice, sub_device: Optional[str] = None + self, + hub: BondHub, + device: BondDevice, + bpup_subs: BPUPSubscriptions, + sub_device: Optional[str] = None, ): """Create HA entity representing Bond fan.""" - super().__init__(hub, device, sub_device) + super().__init__(hub, device, bpup_subs, sub_device) self._brightness: Optional[int] = None self._light: Optional[int] = None @@ -110,9 +116,9 @@ async def async_turn_off(self, **kwargs: Any) -> None: class BondFireplace(BondEntity, LightEntity): """Representation of a Bond-controlled fireplace.""" - def __init__(self, hub: BondHub, device: BondDevice): + def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions): """Create HA entity representing Bond fireplace.""" - super().__init__(hub, device) + super().__init__(hub, device, bpup_subs) self._power: Optional[bool] = None # Bond flame level, 0-100 diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index 3f62403dba786e..e1ec5e5dd46e84 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -3,7 +3,7 @@ "name": "Bond", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/bond", - "requirements": ["bond-api==0.1.8"], + "requirements": ["bond-api==0.1.9"], "zeroconf": ["_bond._tcp.local."], "codeowners": ["@prystupa"], "quality_scale": "platinum" diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index d2f1797225d4bf..8319d31c714054 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -1,14 +1,14 @@ """Support for Bond generic devices.""" from typing import Any, Callable, List, Optional -from bond_api import Action, DeviceType +from bond_api import Action, BPUPSubscriptions, DeviceType from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity -from .const import DOMAIN +from .const import BPUP_SUBS, DOMAIN, HUB from .entity import BondEntity from .utils import BondDevice, BondHub @@ -19,10 +19,12 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Bond generic devices.""" - hub: BondHub = hass.data[DOMAIN][entry.entry_id] + data = hass.data[DOMAIN][entry.entry_id] + hub: BondHub = data[HUB] + bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] switches = [ - BondSwitch(hub, device) + BondSwitch(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_generic(device.type) ] @@ -33,9 +35,9 @@ async def async_setup_entry( class BondSwitch(BondEntity, SwitchEntity): """Representation of a Bond generic device.""" - def __init__(self, hub: BondHub, device: BondDevice): + def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions): """Create HA entity representing Bond generic device (switch).""" - super().__init__(hub, device) + super().__init__(hub, device, bpup_subs) self._power: Optional[bool] = None diff --git a/requirements_all.txt b/requirements_all.txt index a28eee2096efe9..e785542768f022 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -367,7 +367,7 @@ blockchain==1.4.4 # bme680==1.0.5 # homeassistant.components.bond -bond-api==0.1.8 +bond-api==0.1.9 # homeassistant.components.amazon_polly # homeassistant.components.route53 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2f022e9ffe5ae..fd6d5800461bc2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -201,7 +201,7 @@ blebox_uniapi==1.3.2 blinkpy==0.16.4 # homeassistant.components.bond -bond-api==0.1.8 +bond-api==0.1.9 # homeassistant.components.braviatv bravia-tv==1.0.8 diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 9aaaf9a249d0d4..ba4d10c8892f35 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -3,7 +3,7 @@ from contextlib import nullcontext from datetime import timedelta from typing import Any, Dict, Optional -from unittest.mock import patch +from unittest.mock import MagicMock, patch from homeassistant import core from homeassistant.components.bond.const import DOMAIN as BOND_DOMAIN @@ -33,9 +33,11 @@ async def setup_bond_entity( """Set up Bond entity.""" config_entry.add_to_hass(hass) - with patch_bond_version(enabled=patch_version), patch_bond_device_ids( - enabled=patch_device_ids - ), patch_setup_entry("cover", enabled=patch_platforms), patch_setup_entry( + with patch_start_bpup(), patch_bond_version( + enabled=patch_version + ), patch_bond_device_ids(enabled=patch_device_ids), patch_setup_entry( + "cover", enabled=patch_platforms + ), patch_setup_entry( "fan", enabled=patch_platforms ), patch_setup_entry( "light", enabled=patch_platforms @@ -65,7 +67,7 @@ async def setup_platform( with patch("homeassistant.components.bond.PLATFORMS", [platform]): with patch_bond_version(return_value=bond_version), patch_bond_device_ids( return_value=[bond_device_id] - ), patch_bond_device( + ), patch_start_bpup(), patch_bond_device( return_value=discovered_device ), patch_bond_device_properties( return_value=props @@ -118,6 +120,14 @@ def patch_bond_device(return_value=None): ) +def patch_start_bpup(): + """Patch start_bpup.""" + return patch( + "homeassistant.components.bond.start_bpup", + return_value=MagicMock(), + ) + + def patch_bond_action(): """Patch Bond API action endpoint.""" return patch("homeassistant.components.bond.Bond.action") diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index e2bb63141260d4..4dc7ae5c8d4fcf 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -20,6 +20,7 @@ patch_bond_device_state, patch_bond_version, patch_setup_entry, + patch_start_bpup, setup_bond_entity, ) @@ -141,7 +142,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant): "target": "test-model", "fw_ver": "test-version", } - ), patch_bond_device_ids( + ), patch_start_bpup(), patch_bond_device_ids( return_value=["bond-device-id", "device_id"] ), patch_bond_device( return_value={ From f27066e77369d09563d24f835ee8e03c2267ffd6 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Tue, 9 Feb 2021 09:46:36 +0100 Subject: [PATCH 0334/1818] Raise ConditionError for state errors (#46244) --- .../components/bayesian/binary_sensor.py | 7 +++- homeassistant/helpers/condition.py | 15 ++++++-- tests/helpers/test_condition.py | 36 +++++++++++++++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 15176d453491c0..69553e921eba50 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -356,7 +356,12 @@ def _process_state(self, entity_observation): """Return True if state conditions are met.""" entity = entity_observation["entity_id"] - return condition.state(self.hass, entity, entity_observation.get("to_state")) + try: + return condition.state( + self.hass, entity, entity_observation.get("to_state") + ) + except ConditionError: + return False @property def name(self): diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index e47374a9d17066..126513608c7fd8 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -314,11 +314,22 @@ def state( Async friendly. """ + if entity is None: + raise ConditionError("No entity specified") + if isinstance(entity, str): + entity_id = entity entity = hass.states.get(entity) - if entity is None or (attribute is not None and attribute not in entity.attributes): - return False + if entity is None: + raise ConditionError(f"Unknown entity {entity_id}") + else: + entity_id = entity.entity_id + + if attribute is not None and attribute not in entity.attributes: + raise ConditionError( + f"Attribute '{attribute}' (of entity {entity_id}) does not exist" + ) assert isinstance(entity, State) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index cd4039f5262608..485c51a8bb7b43 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -359,6 +359,37 @@ async def test_if_numeric_state_raises_on_unavailable(hass, caplog): assert len(caplog.record_tuples) == 0 +async def test_state_raises(hass): + """Test that state raises ConditionError on errors.""" + # Unknown entity_id + with pytest.raises(ConditionError, match="Unknown entity"): + test = await condition.async_from_config( + hass, + { + "condition": "state", + "entity_id": "sensor.door_unknown", + "state": "open", + }, + ) + + test(hass) + + # Unknown attribute + with pytest.raises(ConditionError, match=r"Attribute .* does not exist"): + test = await condition.async_from_config( + hass, + { + "condition": "state", + "entity_id": "sensor.door", + "attribute": "model", + "state": "acme", + }, + ) + + hass.states.async_set("sensor.door", "open") + test(hass) + + async def test_state_multiple_entities(hass): """Test with multiple entities in condition.""" test = await condition.async_from_config( @@ -466,7 +497,8 @@ async def test_state_attribute_boolean(hass): assert not test(hass) hass.states.async_set("sensor.temperature", 100, {"no_happening": 201}) - assert not test(hass) + with pytest.raises(ConditionError): + test(hass) hass.states.async_set("sensor.temperature", 100, {"happening": False}) assert test(hass) @@ -567,7 +599,7 @@ async def test_numeric_state_raises(hass): }, ) - assert test(hass) + test(hass) # Unknown attribute with pytest.raises(ConditionError, match=r"Attribute .* does not exist"): From da67cde369a52b6461189c2a6e4f6d7dddc191ab Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 9 Feb 2021 06:02:53 -0500 Subject: [PATCH 0335/1818] Use core constants for homematic (#46248) --- homeassistant/components/homematic/__init__.py | 7 +++---- homeassistant/components/homematic/const.py | 3 --- homeassistant/components/homematic/entity.py | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 1f727bab4e1a1a..6df738037bfe5d 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -10,10 +10,13 @@ ATTR_ENTITY_ID, ATTR_MODE, ATTR_NAME, + ATTR_TIME, CONF_HOST, CONF_HOSTS, CONF_PASSWORD, + CONF_PATH, CONF_PLATFORM, + CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, @@ -37,7 +40,6 @@ ATTR_PARAMSET, ATTR_PARAMSET_KEY, ATTR_RX_MODE, - ATTR_TIME, ATTR_UNIQUE_ID, ATTR_VALUE, ATTR_VALUE_TYPE, @@ -47,8 +49,6 @@ CONF_JSONPORT, CONF_LOCAL_IP, CONF_LOCAL_PORT, - CONF_PATH, - CONF_PORT, CONF_RESOLVENAMES, CONF_RESOLVENAMES_OPTIONS, DATA_CONF, @@ -209,7 +209,6 @@ def setup(hass, config): """Set up the Homematic component.""" - conf = config[DOMAIN] hass.data[DATA_CONF] = remotes = {} hass.data[DATA_STORE] = set() diff --git a/homeassistant/components/homematic/const.py b/homeassistant/components/homematic/const.py index a6ff19a6eeab08..e8fa272b0e57c4 100644 --- a/homeassistant/components/homematic/const.py +++ b/homeassistant/components/homematic/const.py @@ -21,7 +21,6 @@ ATTR_INTERFACE = "interface" ATTR_ERRORCODE = "error" ATTR_MESSAGE = "message" -ATTR_TIME = "time" ATTR_UNIQUE_ID = "unique_id" ATTR_PARAMSET_KEY = "paramset_key" ATTR_PARAMSET = "paramset" @@ -232,8 +231,6 @@ CONF_INTERFACES = "interfaces" CONF_LOCAL_IP = "local_ip" CONF_LOCAL_PORT = "local_port" -CONF_PORT = "port" -CONF_PATH = "path" CONF_CALLBACK_IP = "callback_ip" CONF_CALLBACK_PORT = "callback_port" CONF_RESOLVENAMES = "resolvenames" diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index a391fa80461798..bb87d691fc01db 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -73,7 +73,6 @@ def available(self): @property def device_state_attributes(self): """Return device specific state attributes.""" - # Static attributes attr = { "id": self._hmdevice.ADDRESS, From c69c493cf963c913a507c7bd0c02804cdb6e8e1e Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 9 Feb 2021 08:03:14 -0500 Subject: [PATCH 0336/1818] Use core constants for image_processing (#46269) --- homeassistant/components/image_processing/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 82672c22015490..261278da4012d6 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -5,7 +5,13 @@ import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_ENTITY_ID, CONF_NAME +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_NAME, + CONF_ENTITY_ID, + CONF_NAME, + CONF_SOURCE, +) from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv @@ -39,7 +45,6 @@ ATTR_MOTION = "motion" ATTR_TOTAL_FACES = "total_faces" -CONF_SOURCE = "source" CONF_CONFIDENCE = "confidence" DEFAULT_TIMEOUT = 10 From f46dc3c48e3af445ad9c266ef7d5b59964ed722e Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 9 Feb 2021 14:20:20 -0500 Subject: [PATCH 0337/1818] Use core constants for elkm1 (#46091) --- homeassistant/components/elkm1/__init__.py | 3 +-- homeassistant/components/elkm1/config_flow.py | 4 ++-- homeassistant/components/elkm1/const.py | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index e33e1722edfce4..d50c5d65d90622 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -13,6 +13,7 @@ CONF_HOST, CONF_INCLUDE, CONF_PASSWORD, + CONF_PREFIX, CONF_TEMPERATURE_UNIT, CONF_USERNAME, TEMP_CELSIUS, @@ -38,7 +39,6 @@ CONF_KEYPAD, CONF_OUTPUT, CONF_PLC, - CONF_PREFIX, CONF_SETTING, CONF_TASK, CONF_THERMOSTAT, @@ -197,7 +197,6 @@ def _async_find_matching_config_entry(hass, prefix): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Elk-M1 Control from a config entry.""" - conf = entry.data _LOGGER.debug("Setting up elkm1 %s", conf["host"]) diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index 0248025795b3e4..b72cfa19335f2d 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -11,6 +11,7 @@ CONF_ADDRESS, CONF_HOST, CONF_PASSWORD, + CONF_PREFIX, CONF_PROTOCOL, CONF_TEMPERATURE_UNIT, CONF_USERNAME, @@ -20,7 +21,7 @@ from homeassistant.util import slugify from . import async_wait_for_elk_to_sync -from .const import CONF_AUTO_CONFIGURE, CONF_PREFIX +from .const import CONF_AUTO_CONFIGURE from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) @@ -50,7 +51,6 @@ async def validate_input(data): Data has the keys from DATA_SCHEMA with values provided by the user. """ - userid = data.get(CONF_USERNAME) password = data.get(CONF_PASSWORD) diff --git a/homeassistant/components/elkm1/const.py b/homeassistant/components/elkm1/const.py index 71646582c99732..4d2dac4b1de1cf 100644 --- a/homeassistant/components/elkm1/const.py +++ b/homeassistant/components/elkm1/const.py @@ -3,7 +3,7 @@ from elkm1_lib.const import Max import voluptuous as vol -from homeassistant.const import ATTR_CODE +from homeassistant.const import ATTR_CODE, CONF_ZONE DOMAIN = "elkm1" @@ -17,8 +17,6 @@ CONF_SETTING = "setting" CONF_TASK = "task" CONF_THERMOSTAT = "thermostat" -CONF_ZONE = "zone" -CONF_PREFIX = "prefix" BARE_TEMP_FAHRENHEIT = "F" From 57ce18295959de1b6a2f661c265499056a3c7c41 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 9 Feb 2021 14:21:04 -0500 Subject: [PATCH 0338/1818] Remove unnecessary constant from ihc (#46268) --- homeassistant/components/ihc/__init__.py | 2 -- homeassistant/components/ihc/const.py | 1 - 2 files changed, 3 deletions(-) diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index 8769f73e365d95..c539156b7595e7 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -230,7 +230,6 @@ def setup(hass, config): def ihc_setup(hass, config, conf, controller_id): """Set up the IHC component.""" - url = conf[CONF_URL] username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] @@ -289,7 +288,6 @@ def autosetup_ihc_products( hass: HomeAssistantType, config, ihc_controller, controller_id ): """Auto setup of IHC products from the IHC project file.""" - project_xml = ihc_controller.get_project() if not project_xml: _LOGGER.error("Unable to read project from IHC controller") diff --git a/homeassistant/components/ihc/const.py b/homeassistant/components/ihc/const.py index 30103e2bdba529..c751d7990e4740 100644 --- a/homeassistant/components/ihc/const.py +++ b/homeassistant/components/ihc/const.py @@ -6,7 +6,6 @@ CONF_INFO = "info" CONF_INVERTING = "inverting" CONF_LIGHT = "light" -CONF_NAME = "name" CONF_NODE = "node" CONF_NOTE = "note" CONF_OFF_ID = "off_id" From 1c1b2f497a9a212efed2facd2e8ce4ca349ec10a Mon Sep 17 00:00:00 2001 From: bsmappee <58250533+bsmappee@users.noreply.github.com> Date: Tue, 9 Feb 2021 20:21:51 +0100 Subject: [PATCH 0339/1818] bump pysmappee (#46270) --- homeassistant/components/smappee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index ddbff4e77381e2..a6dda75ac723d8 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/smappee", "dependencies": ["http"], "requirements": [ - "pysmappee==0.2.16" + "pysmappee==0.2.17" ], "codeowners": [ "@bsmappee" diff --git a/requirements_all.txt b/requirements_all.txt index e785542768f022..608887a8bdecd3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1693,7 +1693,7 @@ pyskyqhub==0.1.3 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.2.16 +pysmappee==0.2.17 # homeassistant.components.smartthings pysmartapp==0.3.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd6d5800461bc2..259f33e01f766c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -884,7 +884,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.2.16 +pysmappee==0.2.17 # homeassistant.components.smartthings pysmartapp==0.3.3 From a26cf7aeec58422fae1873d3f8610fd4b8074176 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 9 Feb 2021 14:23:02 -0500 Subject: [PATCH 0340/1818] Remove unnecessary variable definition in firmata (#46172) --- homeassistant/components/firmata/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/firmata/const.py b/homeassistant/components/firmata/const.py index 6259582b5f790b..0d859363e2b489 100644 --- a/homeassistant/components/firmata/const.py +++ b/homeassistant/components/firmata/const.py @@ -10,7 +10,6 @@ CONF_ARDUINO_WAIT = "arduino_wait" CONF_DIFFERENTIAL = "differential" CONF_INITIAL_STATE = "initial" -CONF_NAME = "name" CONF_NEGATE_STATE = "negate" CONF_PINS = "pins" CONF_PIN_MODE = "pin_mode" From 6f4cb18fa88d357eb873101b25159c989c0f9125 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 9 Feb 2021 14:23:46 -0500 Subject: [PATCH 0341/1818] Use core constants for here_travel_time (#46246) --- homeassistant/components/here_travel_time/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index afc6534d0c6b7e..e51e7a067fcd0b 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -12,6 +12,7 @@ ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_MODE, + CONF_API_KEY, CONF_MODE, CONF_NAME, CONF_UNIT_SYSTEM, @@ -35,7 +36,6 @@ CONF_ORIGIN_LATITUDE = "origin_latitude" CONF_ORIGIN_LONGITUDE = "origin_longitude" CONF_ORIGIN_ENTITY_ID = "origin_entity_id" -CONF_API_KEY = "api_key" CONF_TRAFFIC_MODE = "traffic_mode" CONF_ROUTE_MODE = "route_mode" CONF_ARRIVAL = "arrival" @@ -148,7 +148,6 @@ async def async_setup_platform( discovery_info: Optional[DiscoveryInfoType] = None, ) -> None: """Set up the HERE travel time platform.""" - api_key = config[CONF_API_KEY] here_client = herepy.RoutingApi(api_key) From 3381e2f65a0cd5e8e4456bdc21d4703560445b7b Mon Sep 17 00:00:00 2001 From: Khole Date: Tue, 9 Feb 2021 21:03:49 +0000 Subject: [PATCH 0342/1818] Convert Hive to Async (#46117) * Convert Hive to Async * Update Refresh System * Update load platform to Async * Changes from review feedback * Review Round 2 * Updated service * Updated dict keys * Convert Hive to Async * Update Refresh System * Update load platform to Async * Changes from review feedback * Review Round 2 * Updated service * Updated dict keys * Convert Hive to Async * Update Refresh System * Update load platform to Async * Changes from review feedback * Review Round 2 * Updated service * Updated dict keys * Updated Refresh System --- homeassistant/components/hive/__init__.py | 101 ++++++++--------- .../components/hive/binary_sensor.py | 53 ++++++--- homeassistant/components/hive/climate.py | 87 +++++++-------- homeassistant/components/hive/light.py | 102 +++++++++--------- homeassistant/components/hive/manifest.json | 11 +- homeassistant/components/hive/sensor.py | 78 ++++++++------ homeassistant/components/hive/switch.py | 55 ++++++---- homeassistant/components/hive/water_heater.py | 65 ++++++++--- requirements_all.txt | 2 +- 9 files changed, 309 insertions(+), 245 deletions(-) diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 98d625cbb1d264..6245db5ea7efde 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -2,7 +2,7 @@ from functools import wraps import logging -from pyhiveapi import Pyhiveapi +from pyhiveapi import Hive import voluptuous as vol from homeassistant.const import ( @@ -13,12 +13,17 @@ CONF_USERNAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import load_platform -from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) +ATTR_AVAILABLE = "available" +ATTR_MODE = "mode" DOMAIN = "hive" DATA_HIVE = "data_hive" SERVICES = ["Heating", "HotWater", "TRV"] @@ -69,28 +74,15 @@ ) -class HiveSession: - """Initiate Hive Session Class.""" - - entity_lookup = {} - core = None - heating = None - hotwater = None - light = None - sensor = None - switch = None - weather = None - attributes = None - trv = None - - -def setup(hass, config): +async def async_setup(hass, config): """Set up the Hive Component.""" - def heating_boost(service): + async def heating_boost(service): """Handle the service call.""" - node_id = HiveSession.entity_lookup.get(service.data[ATTR_ENTITY_ID]) - if not node_id: + + entity_lookup = hass.data[DOMAIN]["entity_lookup"] + hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID]) + if not hive_id: # log or raise error _LOGGER.error("Cannot boost entity id entered") return @@ -98,12 +90,13 @@ def heating_boost(service): minutes = service.data[ATTR_TIME_PERIOD] temperature = service.data[ATTR_TEMPERATURE] - session.heating.turn_boost_on(node_id, minutes, temperature) + hive.heating.turn_boost_on(hive_id, minutes, temperature) - def hot_water_boost(service): + async def hot_water_boost(service): """Handle the service call.""" - node_id = HiveSession.entity_lookup.get(service.data[ATTR_ENTITY_ID]) - if not node_id: + entity_lookup = hass.data[DOMAIN]["entity_lookup"] + hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID]) + if not hive_id: # log or raise error _LOGGER.error("Cannot boost entity id entered") return @@ -111,45 +104,41 @@ def hot_water_boost(service): mode = service.data[ATTR_MODE] if mode == "on": - session.hotwater.turn_boost_on(node_id, minutes) + hive.hotwater.turn_boost_on(hive_id, minutes) elif mode == "off": - session.hotwater.turn_boost_off(node_id) + hive.hotwater.turn_boost_off(hive_id) - session = HiveSession() - session.core = Pyhiveapi() + hive = Hive() - username = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] - update_interval = config[DOMAIN][CONF_SCAN_INTERVAL] + config = {} + config["username"] = config[DOMAIN][CONF_USERNAME] + config["password"] = config[DOMAIN][CONF_PASSWORD] + config["update_interval"] = config[DOMAIN][CONF_SCAN_INTERVAL] - devices = session.core.initialise_api(username, password, update_interval) + devices = await hive.session.startSession(config) if devices is None: _LOGGER.error("Hive API initialization failed") return False - session.sensor = Pyhiveapi.Sensor() - session.heating = Pyhiveapi.Heating() - session.hotwater = Pyhiveapi.Hotwater() - session.light = Pyhiveapi.Light() - session.switch = Pyhiveapi.Switch() - session.weather = Pyhiveapi.Weather() - session.attributes = Pyhiveapi.Attributes() - hass.data[DATA_HIVE] = session + hass.data[DOMAIN][DATA_HIVE] = hive + hass.data[DOMAIN]["entity_lookup"] = {} for ha_type in DEVICETYPES: devicelist = devices.get(DEVICETYPES[ha_type]) if devicelist: - load_platform(hass, ha_type, DOMAIN, devicelist, config) + hass.async_create_task( + async_load_platform(hass, ha_type, DOMAIN, devicelist, config) + ) if ha_type == "climate": - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_BOOST_HEATING, heating_boost, schema=BOOST_HEATING_SCHEMA, ) if ha_type == "water_heater": - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_BOOST_HOT_WATER, hot_water_boost, @@ -163,9 +152,9 @@ def refresh_system(func): """Force update all entities after state change.""" @wraps(func) - def wrapper(self, *args, **kwargs): - func(self, *args, **kwargs) - dispatcher_send(self.hass, DOMAIN) + async def wrapper(self, *args, **kwargs): + await func(self, *args, **kwargs) + async_dispatcher_send(self.hass, DOMAIN) return wrapper @@ -173,20 +162,18 @@ def wrapper(self, *args, **kwargs): class HiveEntity(Entity): """Initiate Hive Base Class.""" - def __init__(self, session, hive_device): + def __init__(self, hive, hive_device): """Initialize the instance.""" - self.node_id = hive_device["Hive_NodeID"] - self.node_name = hive_device["Hive_NodeName"] - self.device_type = hive_device["HA_DeviceType"] - self.node_device_type = hive_device["Hive_DeviceType"] - self.session = session + self.hive = hive + self.device = hive_device self.attributes = {} - self._unique_id = f"{self.node_id}-{self.device_type}" + self._unique_id = f'{self.device["hiveID"]}-{self.device["hiveType"]}' async def async_added_to_hass(self): """When entity is added to Home Assistant.""" self.async_on_remove( async_dispatcher_connect(self.hass, DOMAIN, self.async_write_ha_state) ) - if self.device_type in SERVICES: - self.session.entity_lookup[self.entity_id] = self.node_id + if self.device["hiveType"] in SERVICES: + entity_lookup = self.hass.data[DOMAIN]["entity_lookup"] + entity_lookup[self.entity_id] = self.device["hiveID"] diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 120148a8f813f4..30e5ae049f096c 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -1,28 +1,36 @@ """Support for the Hive binary sensors.""" +from datetime import timedelta + from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_MOTION, DEVICE_CLASS_OPENING, BinarySensorEntity, ) -from . import DATA_HIVE, DOMAIN, HiveEntity +from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity -DEVICETYPE_DEVICE_CLASS = { - "motionsensor": DEVICE_CLASS_MOTION, +DEVICETYPE = { "contactsensor": DEVICE_CLASS_OPENING, + "motionsensor": DEVICE_CLASS_MOTION, + "Connectivity": DEVICE_CLASS_CONNECTIVITY, } +PARALLEL_UPDATES = 0 +SCAN_INTERVAL = timedelta(seconds=15) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Hive sensor devices.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Hive Binary Sensor.""" if discovery_info is None: return - session = hass.data.get(DATA_HIVE) - devs = [] - for dev in discovery_info: - devs.append(HiveBinarySensorEntity(session, dev)) - add_entities(devs) + hive = hass.data[DOMAIN].get(DATA_HIVE) + devices = hive.devices.get("binary_sensor") + entities = [] + if devices: + for dev in devices: + entities.append(HiveBinarySensorEntity(hive, dev)) + async_add_entities(entities, True) class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): @@ -41,24 +49,35 @@ def device_info(self): @property def device_class(self): """Return the class of this sensor.""" - return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type) + return DEVICETYPE.get(self.device["hiveType"]) @property def name(self): """Return the name of the binary sensor.""" - return self.node_name + return self.device["haName"] + + @property + def available(self): + """Return if the device is available.""" + if self.device["hiveType"] != "Connectivity": + return self.device["deviceData"]["online"] + return True @property def device_state_attributes(self): """Show Device Attributes.""" - return self.attributes + return { + ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), + ATTR_MODE: self.attributes.get(ATTR_MODE), + } @property def is_on(self): """Return true if the binary sensor is on.""" - return self.session.sensor.get_state(self.node_id, self.node_device_type) + return self.device["status"]["state"] - def update(self): + async def async_update(self): """Update all Node data from Hive.""" - self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes(self.node_id) + await self.hive.session.updateData(self.device) + self.device = await self.hive.sensor.get_sensor(self.device) + self.attributes = self.device.get("attributes", {}) diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index 33c8fed4ecad41..f1901147f17eb5 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,4 +1,6 @@ """Support for the Hive climate devices.""" +from datetime import timedelta + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, @@ -12,9 +14,9 @@ SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT -from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system HIVE_TO_HASS_STATE = { "SCHEDULE": HVAC_MODE_AUTO, @@ -34,21 +36,27 @@ True: CURRENT_HVAC_HEAT, } +TEMP_UNIT = {"C": TEMP_CELSIUS, "F": TEMP_FAHRENHEIT} + SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] SUPPORT_PRESET = [PRESET_NONE, PRESET_BOOST] +PARALLEL_UPDATES = 0 +SCAN_INTERVAL = timedelta(seconds=15) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Hive climate devices.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Hive thermostat.""" if discovery_info is None: return - session = hass.data.get(DATA_HIVE) - devs = [] - for dev in discovery_info: - devs.append(HiveClimateEntity(session, dev)) - add_entities(devs) + hive = hass.data[DOMAIN].get(DATA_HIVE) + devices = hive.devices.get("climate") + entities = [] + if devices: + for dev in devices: + entities.append(HiveClimateEntity(hive, dev)) + async_add_entities(entities, True) class HiveClimateEntity(HiveEntity, ClimateEntity): @@ -57,7 +65,8 @@ class HiveClimateEntity(HiveEntity, ClimateEntity): def __init__(self, hive_session, hive_device): """Initialize the Climate device.""" super().__init__(hive_session, hive_device) - self.thermostat_node_id = hive_device["Thermostat_NodeID"] + self.thermostat_node_id = hive_device["device_id"] + self.temperature_type = TEMP_UNIT.get(hive_device["temperatureunit"]) @property def unique_id(self): @@ -77,19 +86,17 @@ def supported_features(self): @property def name(self): """Return the name of the Climate device.""" - friendly_name = "Heating" - if self.node_name is not None: - if self.device_type == "TRV": - friendly_name = self.node_name - else: - friendly_name = f"{self.node_name} {friendly_name}" + return self.device["haName"] - return friendly_name + @property + def available(self): + """Return if the device is available.""" + return self.device["deviceData"]["online"] @property def device_state_attributes(self): """Show Device Attributes.""" - return self.attributes + return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} @property def hvac_modes(self): @@ -105,47 +112,42 @@ def hvac_mode(self): Need to be one of HVAC_MODE_*. """ - return HIVE_TO_HASS_STATE[self.session.heating.get_mode(self.node_id)] + return HIVE_TO_HASS_STATE[self.device["status"]["mode"]] @property def hvac_action(self): """Return current HVAC action.""" - return HIVE_TO_HASS_HVAC_ACTION[ - self.session.heating.operational_status(self.node_id, self.device_type) - ] + return HIVE_TO_HASS_HVAC_ACTION[self.device["status"]["action"]] @property def temperature_unit(self): """Return the unit of measurement.""" - return TEMP_CELSIUS + return self.temperature_type @property def current_temperature(self): """Return the current temperature.""" - return self.session.heating.current_temperature(self.node_id) + return self.device["status"]["current_temperature"] @property def target_temperature(self): """Return the target temperature.""" - return self.session.heating.get_target_temperature(self.node_id) + return self.device["status"]["target_temperature"] @property def min_temp(self): """Return minimum temperature.""" - return self.session.heating.min_temperature(self.node_id) + return self.device["min_temp"] @property def max_temp(self): """Return the maximum temperature.""" - return self.session.heating.max_temperature(self.node_id) + return self.device["max_temp"] @property def preset_mode(self): """Return the current preset mode, e.g., home, away, temp.""" - if ( - self.device_type == "Heating" - and self.session.heating.get_boost(self.node_id) == "ON" - ): + if self.device["status"]["boost"] == "ON": return PRESET_BOOST return None @@ -155,31 +157,30 @@ def preset_modes(self): return SUPPORT_PRESET @refresh_system - def set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" new_mode = HASS_TO_HIVE_STATE[hvac_mode] - self.session.heating.set_mode(self.node_id, new_mode) + await self.hive.heating.set_mode(self.device, new_mode) @refresh_system - def set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" new_temperature = kwargs.get(ATTR_TEMPERATURE) if new_temperature is not None: - self.session.heating.set_target_temperature(self.node_id, new_temperature) + await self.hive.heating.set_target_temperature(self.device, new_temperature) @refresh_system - def set_preset_mode(self, preset_mode): + async def async_set_preset_mode(self, preset_mode): """Set new preset mode.""" if preset_mode == PRESET_NONE and self.preset_mode == PRESET_BOOST: - self.session.heating.turn_boost_off(self.node_id) + await self.hive.heating.turn_boost_off(self.device) elif preset_mode == PRESET_BOOST: curtemp = round(self.current_temperature * 2) / 2 temperature = curtemp + 0.5 - self.session.heating.turn_boost_on(self.node_id, 30, temperature) + await self.hive.heating.turn_boost_on(self.device, 30, temperature) - def update(self): + async def async_update(self): """Update all Node data from Hive.""" - self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes( - self.thermostat_node_id - ) + await self.hive.session.updateData(self.device) + self.device = await self.hive.heating.get_heating(self.device) + self.attributes.update(self.device.get("attributes", {})) diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 7659d43aeba294..f458c27d019523 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -1,4 +1,6 @@ -"""Support for the Hive lights.""" +"""Support for Hive light devices.""" +from datetime import timedelta + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -10,29 +12,29 @@ ) import homeassistant.util.color as color_util -from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system + +PARALLEL_UPDATES = 0 +SCAN_INTERVAL = timedelta(seconds=15) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Hive light devices.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Hive Light.""" if discovery_info is None: return - session = hass.data.get(DATA_HIVE) - devs = [] - for dev in discovery_info: - devs.append(HiveDeviceLight(session, dev)) - add_entities(devs) + hive = hass.data[DOMAIN].get(DATA_HIVE) + devices = hive.devices.get("light") + entities = [] + if devices: + for dev in devices: + entities.append(HiveDeviceLight(hive, dev)) + async_add_entities(entities, True) class HiveDeviceLight(HiveEntity, LightEntity): """Hive Active Light Device.""" - def __init__(self, hive_session, hive_device): - """Initialize the Light device.""" - super().__init__(hive_session, hive_device) - self.light_device_type = hive_device["Hive_Light_DeviceType"] - @property def unique_id(self): """Return unique ID of entity.""" @@ -46,59 +48,56 @@ def device_info(self): @property def name(self): """Return the display name of this light.""" - return self.node_name + return self.device["haName"] + + @property + def available(self): + """Return if the device is available.""" + return self.device["deviceData"]["online"] @property def device_state_attributes(self): """Show Device Attributes.""" - return self.attributes + return { + ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), + ATTR_MODE: self.attributes.get(ATTR_MODE), + } @property def brightness(self): """Brightness of the light (an integer in the range 1-255).""" - return self.session.light.get_brightness(self.node_id) + return self.device["status"]["brightness"] @property def min_mireds(self): """Return the coldest color_temp that this light supports.""" - if ( - self.light_device_type == "tuneablelight" - or self.light_device_type == "colourtuneablelight" - ): - return self.session.light.get_min_color_temp(self.node_id) + return self.device.get("min_mireds") @property def max_mireds(self): """Return the warmest color_temp that this light supports.""" - if ( - self.light_device_type == "tuneablelight" - or self.light_device_type == "colourtuneablelight" - ): - return self.session.light.get_max_color_temp(self.node_id) + return self.device.get("max_mireds") @property def color_temp(self): """Return the CT color value in mireds.""" - if ( - self.light_device_type == "tuneablelight" - or self.light_device_type == "colourtuneablelight" - ): - return self.session.light.get_color_temp(self.node_id) + return self.device["status"].get("color_temp") @property - def hs_color(self) -> tuple: + def hs_color(self): """Return the hs color value.""" - if self.light_device_type == "colourtuneablelight": - rgb = self.session.light.get_color(self.node_id) + if self.device["status"]["mode"] == "COLOUR": + rgb = self.device["status"].get("hs_color") return color_util.color_RGB_to_hs(*rgb) + return None @property def is_on(self): """Return true if light is on.""" - return self.session.light.get_state(self.node_id) + return self.device["status"]["state"] @refresh_system - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Instruct the light to turn on.""" new_brightness = None new_color_temp = None @@ -116,35 +115,32 @@ def turn_on(self, **kwargs): get_new_color = kwargs.get(ATTR_HS_COLOR) hue = int(get_new_color[0]) saturation = int(get_new_color[1]) - new_color = (hue, saturation, self.brightness) - - self.session.light.turn_on( - self.node_id, - self.light_device_type, - new_brightness, - new_color_temp, - new_color, + new_color = (hue, saturation, 100) + + await self.hive.light.turn_on( + self.device, new_brightness, new_color_temp, new_color ) @refresh_system - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Instruct the light to turn off.""" - self.session.light.turn_off(self.node_id) + await self.hive.light.turn_off(self.device) @property def supported_features(self): """Flag supported features.""" supported_features = None - if self.light_device_type == "warmwhitelight": + if self.device["hiveType"] == "warmwhitelight": supported_features = SUPPORT_BRIGHTNESS - elif self.light_device_type == "tuneablelight": + elif self.device["hiveType"] == "tuneablelight": supported_features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP - elif self.light_device_type == "colourtuneablelight": + elif self.device["hiveType"] == "colourtuneablelight": supported_features = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR return supported_features - def update(self): + async def async_update(self): """Update all Node data from Hive.""" - self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes(self.node_id) + await self.hive.session.updateData(self.device) + self.device = await self.hive.light.get_light(self.device) + self.attributes.update(self.device.get("attributes", {})) diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index f8fb9bc8c2a181..27f235949bfb28 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -2,6 +2,11 @@ "domain": "hive", "name": "Hive", "documentation": "https://www.home-assistant.io/integrations/hive", - "requirements": ["pyhiveapi==0.2.20.2"], - "codeowners": ["@Rendili", "@KJonline"] -} + "requirements": [ + "pyhiveapi==0.3.4.4" + ], + "codeowners": [ + "@Rendili", + "@KJonline" + ] +} \ No newline at end of file diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index 360fb61bfbee82..e828dff9b4e67f 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -1,31 +1,32 @@ -"""Support for the Hive sensors.""" -from homeassistant.const import TEMP_CELSIUS -from homeassistant.helpers.entity import Entity +"""Support for the Hive sesnors.""" -from . import DATA_HIVE, DOMAIN, HiveEntity +from datetime import timedelta -FRIENDLY_NAMES = { - "Hub_OnlineStatus": "Hive Hub Status", - "Hive_OutsideTemperature": "Outside Temperature", -} +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY +from homeassistant.helpers.entity import Entity + +from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity -DEVICETYPE_ICONS = { - "Hub_OnlineStatus": "mdi:switch", - "Hive_OutsideTemperature": "mdi:thermometer", +PARALLEL_UPDATES = 0 +SCAN_INTERVAL = timedelta(seconds=15) +DEVICETYPE = { + "Battery": {"unit": " % ", "type": DEVICE_CLASS_BATTERY}, } -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Hive sensor devices.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Hive Sensor.""" if discovery_info is None: return - session = hass.data.get(DATA_HIVE) - devs = [] - for dev in discovery_info: - if dev["HA_DeviceType"] in FRIENDLY_NAMES: - devs.append(HiveSensorEntity(session, dev)) - add_entities(devs) + hive = hass.data[DOMAIN].get(DATA_HIVE) + devices = hive.devices.get("sensor") + entities = [] + if devices: + for dev in devices: + if dev["hiveType"] in DEVICETYPE: + entities.append(HiveSensorEntity(hive, dev)) + async_add_entities(entities, True) class HiveSensorEntity(HiveEntity, Entity): @@ -42,29 +43,36 @@ def device_info(self): return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} @property - def name(self): - """Return the name of the sensor.""" - return FRIENDLY_NAMES.get(self.device_type) + def available(self): + """Return if sensor is available.""" + return self.device.get("deviceData", {}).get("online") @property - def state(self): - """Return the state of the sensor.""" - if self.device_type == "Hub_OnlineStatus": - return self.session.sensor.hub_online_status(self.node_id) - if self.device_type == "Hive_OutsideTemperature": - return self.session.weather.temperature() + def device_class(self): + """Device class of the entity.""" + return DEVICETYPE[self.device["hiveType"]].get("type") @property def unit_of_measurement(self): """Return the unit of measurement.""" - if self.device_type == "Hive_OutsideTemperature": - return TEMP_CELSIUS + return DEVICETYPE[self.device["hiveType"]].get("unit") + + @property + def name(self): + """Return the name of the sensor.""" + return self.device["haName"] + + @property + def state(self): + """Return the state of the sensor.""" + return self.device["status"]["state"] @property - def icon(self): - """Return the icon to use.""" - return DEVICETYPE_ICONS.get(self.device_type) + def device_state_attributes(self): + """Return the state attributes.""" + return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} - def update(self): + async def async_update(self): """Update all Node data from Hive.""" - self.session.core.update_data(self.node_id) + await self.hive.session.updateData(self.device) + self.device = await self.hive.sensor.get_sensor(self.device) diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 734581b0db3785..8ab820589cff76 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -1,19 +1,26 @@ """Support for the Hive switches.""" +from datetime import timedelta + from homeassistant.components.switch import SwitchEntity -from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system + +PARALLEL_UPDATES = 0 +SCAN_INTERVAL = timedelta(seconds=15) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up Hive switches.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Hive Switch.""" if discovery_info is None: return - session = hass.data.get(DATA_HIVE) - devs = [] - for dev in discovery_info: - devs.append(HiveDevicePlug(session, dev)) - add_entities(devs) + hive = hass.data[DOMAIN].get(DATA_HIVE) + devices = hive.devices.get("switch") + entities = [] + if devices: + for dev in devices: + entities.append(HiveDevicePlug(hive, dev)) + async_add_entities(entities, True) class HiveDevicePlug(HiveEntity, SwitchEntity): @@ -32,34 +39,44 @@ def device_info(self): @property def name(self): """Return the name of this Switch device if any.""" - return self.node_name + return self.device["haName"] + + @property + def available(self): + """Return if the device is available.""" + return self.device["deviceData"].get("online") @property def device_state_attributes(self): """Show Device Attributes.""" - return self.attributes + return { + ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), + ATTR_MODE: self.attributes.get(ATTR_MODE), + } @property def current_power_w(self): """Return the current power usage in W.""" - return self.session.switch.get_power_usage(self.node_id) + return self.device["status"]["power_usage"] @property def is_on(self): """Return true if switch is on.""" - return self.session.switch.get_state(self.node_id) + return self.device["status"]["state"] @refresh_system - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Turn the switch on.""" - self.session.switch.turn_on(self.node_id) + if self.device["hiveType"] == "activeplug": + await self.hive.switch.turn_on(self.device) @refresh_system - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Turn the device off.""" - self.session.switch.turn_off(self.node_id) + if self.device["hiveType"] == "activeplug": + await self.hive.switch.turn_off(self.device) - def update(self): + async def async_update(self): """Update all Node data from Hive.""" - self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes(self.node_id) + await self.hive.session.updateData(self.device) + self.device = await self.hive.switch.get_plug(self.device) diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index 693fd6f322b8e8..56e98a690b8ff4 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -1,4 +1,7 @@ """Support for hive water heaters.""" + +from datetime import timedelta + from homeassistant.components.water_heater import ( STATE_ECO, STATE_OFF, @@ -11,22 +14,36 @@ from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE +HOTWATER_NAME = "Hot Water" +PARALLEL_UPDATES = 0 +SCAN_INTERVAL = timedelta(seconds=15) +HIVE_TO_HASS_STATE = { + "SCHEDULE": STATE_ECO, + "ON": STATE_ON, + "OFF": STATE_OFF, +} + +HASS_TO_HIVE_STATE = { + STATE_ECO: "SCHEDULE", + STATE_ON: "MANUAL", + STATE_OFF: "OFF", +} -HIVE_TO_HASS_STATE = {"SCHEDULE": STATE_ECO, "ON": STATE_ON, "OFF": STATE_OFF} -HASS_TO_HIVE_STATE = {STATE_ECO: "SCHEDULE", STATE_ON: "ON", STATE_OFF: "OFF"} SUPPORT_WATER_HEATER = [STATE_ECO, STATE_ON, STATE_OFF] -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Hive water heater devices.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Hive Hotwater.""" if discovery_info is None: return - session = hass.data.get(DATA_HIVE) - devs = [] - for dev in discovery_info: - devs.append(HiveWaterHeater(session, dev)) - add_entities(devs) + hive = hass.data[DOMAIN].get(DATA_HIVE) + devices = hive.devices.get("water_heater") + entities = [] + if devices: + for dev in devices: + entities.append(HiveWaterHeater(hive, dev)) + async_add_entities(entities, True) class HiveWaterHeater(HiveEntity, WaterHeaterEntity): @@ -50,9 +67,12 @@ def supported_features(self): @property def name(self): """Return the name of the water heater.""" - if self.node_name is None: - self.node_name = "Hot Water" - return self.node_name + return HOTWATER_NAME + + @property + def available(self): + """Return if the device is available.""" + return self.device["deviceData"]["online"] @property def temperature_unit(self): @@ -62,7 +82,7 @@ def temperature_unit(self): @property def current_operation(self): """Return current operation.""" - return HIVE_TO_HASS_STATE[self.session.hotwater.get_mode(self.node_id)] + return HIVE_TO_HASS_STATE[self.device["status"]["current_operation"]] @property def operation_list(self): @@ -70,11 +90,22 @@ def operation_list(self): return SUPPORT_WATER_HEATER @refresh_system - def set_operation_mode(self, operation_mode): + async def async_turn_on(self, **kwargs): + """Turn on hotwater.""" + await self.hive.hotwater.set_mode(self.device, "MANUAL") + + @refresh_system + async def async_turn_off(self, **kwargs): + """Turn on hotwater.""" + await self.hive.hotwater.set_mode(self.device, "OFF") + + @refresh_system + async def async_set_operation_mode(self, operation_mode): """Set operation mode.""" new_mode = HASS_TO_HIVE_STATE[operation_mode] - self.session.hotwater.set_mode(self.node_id, new_mode) + await self.hive.hotwater.set_mode(self.device, new_mode) - def update(self): + async def async_update(self): """Update all Node data from Hive.""" - self.session.core.update_data(self.node_id) + await self.hive.session.updateData(self.device) + self.device = await self.hive.hotwater.get_hotwater(self.device) diff --git a/requirements_all.txt b/requirements_all.txt index 608887a8bdecd3..63c8b98f73d753 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1434,7 +1434,7 @@ pyheos==0.7.2 pyhik==0.2.8 # homeassistant.components.hive -pyhiveapi==0.2.20.2 +pyhiveapi==0.3.4.4 # homeassistant.components.homematic pyhomematic==0.1.71 From 5fcb948e28e6f58af1e6385c8b4b8db618904381 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 10 Feb 2021 00:05:10 +0000 Subject: [PATCH 0343/1818] [ci skip] Translation update --- homeassistant/components/media_player/translations/ru.json | 7 +++++++ homeassistant/components/shelly/translations/ca.json | 4 ++-- homeassistant/components/shelly/translations/en.json | 4 ++-- homeassistant/components/shelly/translations/et.json | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/media_player/translations/ru.json b/homeassistant/components/media_player/translations/ru.json index 8ed46953675f48..df0b00d2482a1a 100644 --- a/homeassistant/components/media_player/translations/ru.json +++ b/homeassistant/components/media_player/translations/ru.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} \u0432\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438", "is_paused": "{entity_name} \u043d\u0430 \u043f\u0430\u0443\u0437\u0435", "is_playing": "{entity_name} \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442 \u043c\u0435\u0434\u0438\u0430" + }, + "trigger_type": { + "idle": "{entity_name} \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u0432 \u0440\u0435\u0436\u0438\u043c \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f", + "paused": "{entity_name} \u043d\u0430 \u043f\u0430\u0443\u0437\u0435", + "playing": "{entity_name} \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435", + "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f" } }, "state": { diff --git a/homeassistant/components/shelly/translations/ca.json b/homeassistant/components/shelly/translations/ca.json index c2df82c0b16961..13cc79ac3d8af8 100644 --- a/homeassistant/components/shelly/translations/ca.json +++ b/homeassistant/components/shelly/translations/ca.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Vols configurar el {model} a {host}? \n\nAbans de configurar-lo, els dispositius amb bateria s'han de desperar prement el bot\u00f3 del dispositiu." + "description": "Vols configurar el {model} a {host}? \n\nAbans de configurar-lo, els dispositius amb bateria protegits amb contrasenya s'han de desperar prement el bot\u00f3 del dispositiu.\nEls dispositius que no tinguin contrasenya s'afegiran tan bon punt es despertin. Ja pots despertar el dispositiu manualment mitjan\u00e7ant el bot\u00f3 o esperar a la seg\u00fcent transmissi\u00f3 de dades del dispositiu." }, "credentials": { "data": { @@ -24,7 +24,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Abans de configurar-lo, els dispositius amb bateria s'han de desperar prement el bot\u00f3 del dispositiu." + "description": "Abans de configurar-lo, els dispositius amb bateria s'han de desperar, ja pots clicar el bot\u00f3 del dispositiu per a despertar-lo." } } }, diff --git a/homeassistant/components/shelly/translations/en.json b/homeassistant/components/shelly/translations/en.json index a9ad6092a08cfa..b60d9dfbe3eebe 100644 --- a/homeassistant/components/shelly/translations/en.json +++ b/homeassistant/components/shelly/translations/en.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Do you want to set up the {model} at {host}?\n\nBefore set up, battery-powered devices must be woken up by pressing the button on the device." + "description": "Do you want to set up the {model} at {host}?\n\nBattery-powered devices that are password protected must be woken up before continuing with setting up.\nBattery-powered devices that are not password protected will be added when the device wakes up, you can now manually wake the device up using a button on it or wait for the next data update from the device." }, "credentials": { "data": { @@ -24,7 +24,7 @@ "data": { "host": "Host" }, - "description": "Before set up, battery-powered devices must be woken up by pressing the button on the device." + "description": "Before set up, battery-powered devices must be woken up, you can now wake the device up using a button on it." } } }, diff --git a/homeassistant/components/shelly/translations/et.json b/homeassistant/components/shelly/translations/et.json index d2514876a81223..7059ce6b3d3f27 100644 --- a/homeassistant/components/shelly/translations/et.json +++ b/homeassistant/components/shelly/translations/et.json @@ -12,7 +12,7 @@ "flow_title": "", "step": { "confirm_discovery": { - "description": "Kas soovid seadistada {model} saidil {host} ?\n\n Enne seadistamist tuleb akutoitega seade \u00e4ratada vajutades seadmel nuppu." + "description": "Kas soovid seadistada seadet {model} saidil {host} ? \n\n Enne seadistamise j\u00e4tkamist tuleb parooliga kaitstud akutoitega seadmed \u00e4ratada.\n Patareitoitega seadmed, mis pole parooliga kaitstud, lisatakse seadme \u00e4rkamisel. N\u00fc\u00fcd saad seadme k\u00e4sitsi \u00fcles \u00e4ratada, kasutades sellel olevat nuppu v\u00f5i oodata seadme j\u00e4rgmist andmete v\u00e4rskendamist." }, "credentials": { "data": { From 00aebec90d349dff6ce9b27adb633b595f6d7731 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 9 Feb 2021 21:59:49 -0800 Subject: [PATCH 0344/1818] Fix bug in test found by manual log inspection (#46309) --- tests/components/stream/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index 75ac9377b7cb18..1b017667ee6e3e 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -40,7 +40,8 @@ def blocking_finish(self, stream: Stream): # Block the worker thread until the test has a chance to verify # the segments under test. logging.debug("blocking worker") - self._event.wait() + if self._event: + self._event.wait() # Forward to actual implementation self._original(stream) From 26f455223bb5a8adf70b1f43c892ce3ef972f922 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 9 Feb 2021 23:53:34 -0800 Subject: [PATCH 0345/1818] Update nest stream URLs expiration (#46311) --- homeassistant/components/nest/camera_sdm.py | 8 +- homeassistant/components/stream/__init__.py | 18 ++++- tests/components/stream/test_worker.py | 82 ++++++++++++++++++++- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index 8f5ba88fdd46ab..cc2730fad8a14e 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -146,13 +146,9 @@ async def _handle_stream_refresh(self, now): # Next attempt to catch a url will get a new one self._stream = None return - # Stop any existing stream worker since the url is invalid. The next - # request for this stream will restart it with the right url. - # Issue #42793 tracks improvements (e.g. preserve keepalive, smoother - # transitions across streams) + # Update the stream worker with the latest valid url if self.stream: - self.stream.stop() - self.stream = None + self.stream.update_source(self._stream.rtsp_stream_url) self._schedule_stream_refresh() async def async_will_remove_from_hass(self): diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index a8b344a98e949b..e871963d2baec9 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -109,8 +109,9 @@ def __init__(self, hass, source, options=None): self.keepalive = False self.access_token = None self._thread = None - self._thread_quit = None + self._thread_quit = threading.Event() self._outputs = {} + self._fast_restart_once = False if self.options is None: self.options = {} @@ -167,7 +168,7 @@ def start(self): # The thread must have crashed/exited. Join to clean up the # previous thread. self._thread.join(timeout=0) - self._thread_quit = threading.Event() + self._thread_quit.clear() self._thread = threading.Thread( name="stream_worker", target=self._run_worker, @@ -175,6 +176,13 @@ def start(self): self._thread.start() _LOGGER.info("Started stream: %s", self.source) + def update_source(self, new_source): + """Restart the stream with a new stream source.""" + _LOGGER.debug("Updating stream source %s", self.source) + self.source = new_source + self._fast_restart_once = True + self._thread_quit.set() + def _run_worker(self): """Handle consuming streams and restart keepalive streams.""" # Keep import here so that we can import stream integration without installing reqs @@ -186,8 +194,12 @@ def _run_worker(self): start_time = time.time() stream_worker(self.hass, self, self._thread_quit) if not self.keepalive 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 + self._thread_quit.clear() + continue break - # To avoid excessive restarts, wait before restarting # As the required recovery time may be different for different setups, start # with trying a short wait_timeout and increase it on each reconnection attempt. diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 91d02664d744d3..0d5be68d93c203 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -14,6 +14,7 @@ """ import fractions +import io import math import threading from unittest.mock import patch @@ -44,6 +45,7 @@ OUT_OF_ORDER_PACKET_INDEX = 3 * VIDEO_FRAME_RATE PACKETS_PER_SEGMENT = SEGMENT_DURATION / PACKET_DURATION SEGMENTS_PER_PACKET = PACKET_DURATION / SEGMENT_DURATION +TIMEOUT = 15 class FakePyAvStream: @@ -178,9 +180,9 @@ def __init__(self, video=True, audio=False): def open(self, stream_source, *args, **kwargs): """Return a stream or buffer depending on args.""" - if stream_source == STREAM_SOURCE: - return self.container - return self.capture_buffer + if isinstance(stream_source, io.BytesIO): + return self.capture_buffer + return self.container async def async_decode_stream(hass, packets, py_av=None): @@ -469,3 +471,77 @@ async def test_pts_out_of_order(hass): assert all([s.duration == SEGMENT_DURATION for s in segments]) assert len(decoded_stream.video_packets) == len(packets) assert len(decoded_stream.audio_packets) == 0 + + +async def test_stream_stopped_while_decoding(hass): + """Tests that worker quits when stop() is called while decodign.""" + # Add some synchronization so that the test can pause the background + # worker. When the worker is stopped, the test invokes stop() which + # will cause the worker thread to exit once it enters the decode + # loop + worker_open = threading.Event() + worker_wake = threading.Event() + + stream = Stream(hass, STREAM_SOURCE) + stream.add_provider(STREAM_OUTPUT_FORMAT) + + py_av = MockPyAv() + py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) + + def blocking_open(stream_source, *args, **kwargs): + # Let test know the thread is running + worker_open.set() + # Block worker thread until test wakes up + worker_wake.wait() + return py_av.open(stream_source, args, kwargs) + + with patch("av.open", new=blocking_open): + stream.start() + assert worker_open.wait(TIMEOUT) + # Note: There is a race here where the worker could start as soon + # as the wake event is sent, completing all decode work. + worker_wake.set() + stream.stop() + + +async def test_update_stream_source(hass): + """Tests that the worker is re-invoked when the stream source is updated.""" + worker_open = threading.Event() + worker_wake = threading.Event() + + stream = Stream(hass, STREAM_SOURCE) + stream.add_provider(STREAM_OUTPUT_FORMAT) + # Note that keepalive is not set here. The stream is "restarted" even though + # it is not stopping due to failure. + + py_av = MockPyAv() + py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) + + last_stream_source = None + + def blocking_open(stream_source, *args, **kwargs): + nonlocal last_stream_source + if not isinstance(stream_source, io.BytesIO): + last_stream_source = stream_source + # Let test know the thread is running + worker_open.set() + # Block worker thread until test wakes up + worker_wake.wait() + return py_av.open(stream_source, args, kwargs) + + with patch("av.open", new=blocking_open): + stream.start() + assert worker_open.wait(TIMEOUT) + assert last_stream_source == STREAM_SOURCE + + # Update the stream source, then the test wakes up the worker and assert + # that it re-opens the new stream (the test again waits on thread_started) + worker_open.clear() + stream.update_source(STREAM_SOURCE + "-updated-source") + worker_wake.set() + assert worker_open.wait(TIMEOUT) + assert last_stream_source == STREAM_SOURCE + "-updated-source" + worker_wake.set() + + # Ccleanup + stream.stop() From 175f2f0275c89d2e778a8be8235fd1a89ff29129 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 10 Feb 2021 09:09:34 +0100 Subject: [PATCH 0346/1818] Add fan platform to knx (#46161) --- homeassistant/components/knx/__init__.py | 4 + homeassistant/components/knx/const.py | 7 +- homeassistant/components/knx/factory.py | 22 ++++++ homeassistant/components/knx/fan.py | 93 ++++++++++++++++++++++++ homeassistant/components/knx/schema.py | 22 ++++++ 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/knx/fan.py diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 1492e5df7b7f6c..1879d9a6415e46 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -45,6 +45,7 @@ ConnectionSchema, CoverSchema, ExposeSchema, + FanSchema, LightSchema, NotifySchema, SceneSchema, @@ -136,6 +137,9 @@ vol.Optional(SupportedPlatforms.weather.value): vol.All( cv.ensure_list, [WeatherSchema.SCHEMA] ), + vol.Optional(SupportedPlatforms.fan.value): vol.All( + cv.ensure_list, [FanSchema.SCHEMA] + ), } ), ) diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index e434aed395d3e9..6a76de6a97f81c 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -33,14 +33,15 @@ class ColorTempModes(Enum): class SupportedPlatforms(Enum): """Supported platforms.""" - cover = "cover" - light = "light" binary_sensor = "binary_sensor" climate = "climate" - switch = "switch" + cover = "cover" + fan = "fan" + light = "light" notify = "notify" scene = "scene" sensor = "sensor" + switch = "switch" weather = "weather" diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index c1e73733b22f9e..20a887b628d10f 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -8,6 +8,7 @@ ClimateMode as XknxClimateMode, Cover as XknxCover, Device as XknxDevice, + Fan as XknxFan, Light as XknxLight, Notification as XknxNotification, Scene as XknxScene, @@ -24,6 +25,7 @@ BinarySensorSchema, ClimateSchema, CoverSchema, + FanSchema, LightSchema, SceneSchema, SensorSchema, @@ -65,6 +67,9 @@ def create_knx_device( if platform is SupportedPlatforms.weather: return _create_weather(knx_module, config) + if platform is SupportedPlatforms.fan: + return _create_fan(knx_module, config) + def _create_cover(knx_module: XKNX, config: ConfigType) -> XknxCover: """Return a KNX Cover device to be used within XKNX.""" @@ -353,3 +358,20 @@ def _create_weather(knx_module: XKNX, config: ConfigType) -> XknxWeather: ), group_address_humidity=config.get(WeatherSchema.CONF_KNX_HUMIDITY_ADDRESS), ) + + +def _create_fan(knx_module: XKNX, config: ConfigType) -> XknxFan: + """Return a KNX Fan device to be used within XKNX.""" + + fan = XknxFan( + knx_module, + name=config[CONF_NAME], + group_address_speed=config.get(CONF_ADDRESS), + group_address_speed_state=config.get(FanSchema.CONF_STATE_ADDRESS), + group_address_oscillation=config.get(FanSchema.CONF_OSCILLATION_ADDRESS), + group_address_oscillation_state=config.get( + FanSchema.CONF_OSCILLATION_STATE_ADDRESS + ), + max_step=config.get(FanSchema.CONF_MAX_STEP), + ) + return fan diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py new file mode 100644 index 00000000000000..d5dfb25ccd4f97 --- /dev/null +++ b/homeassistant/components/knx/fan.py @@ -0,0 +1,93 @@ +"""Support for KNX/IP fans.""" +import math +from typing import Any, Optional + +from xknx.devices import Fan as XknxFan +from xknx.devices.fan import FanSpeedMode + +from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) + +from .const import DOMAIN +from .knx_entity import KnxEntity + +DEFAULT_PERCENTAGE = 50 + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up fans for KNX platform.""" + entities = [] + for device in hass.data[DOMAIN].xknx.devices: + if isinstance(device, XknxFan): + entities.append(KNXFan(device)) + async_add_entities(entities) + + +class KNXFan(KnxEntity, FanEntity): + """Representation of a KNX fan.""" + + def __init__(self, device: XknxFan): + """Initialize of KNX fan.""" + super().__init__(device) + + if self._device.mode == FanSpeedMode.Step: + self._step_range = (1, device.max_step) + + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed of the fan, as a percentage.""" + if self._device.mode == FanSpeedMode.Step: + step = math.ceil(percentage_to_ranged_value(self._step_range, percentage)) + await self._device.set_speed(step) + else: + await self._device.set_speed(percentage) + + @property + def supported_features(self) -> int: + """Flag supported features.""" + flags = SUPPORT_SET_SPEED + + if self._device.supports_oscillation: + flags |= SUPPORT_OSCILLATE + + return flags + + @property + def percentage(self) -> Optional[int]: + """Return the current speed as a percentage.""" + if self._device.current_speed is None: + return None + + if self._device.mode == FanSpeedMode.Step: + return ranged_value_to_percentage( + self._step_range, self._device.current_speed + ) + return self._device.current_speed + + async def async_turn_on( + self, + speed: Optional[str] = None, + percentage: Optional[int] = None, + preset_mode: Optional[str] = None, + **kwargs, + ) -> None: + """Turn on the fan.""" + if percentage is None: + await self.async_set_percentage(DEFAULT_PERCENTAGE) + else: + await self.async_set_percentage(percentage) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the fan off.""" + await self.async_set_percentage(0) + + async def async_oscillate(self, oscillating: bool) -> None: + """Oscillate the fan.""" + await self._device.set_oscillation(oscillating) + + @property + def oscillating(self): + """Return whether or not the fan is currently oscillating.""" + return self._device.current_oscillation diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 22599014d0fd08..a9b65b853523e5 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -430,3 +430,25 @@ class WeatherSchema: vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): cv.string, } ) + + +class FanSchema: + """Voluptuous schema for KNX fans.""" + + CONF_STATE_ADDRESS = CONF_STATE_ADDRESS + CONF_OSCILLATION_ADDRESS = "oscillation_address" + CONF_OSCILLATION_STATE_ADDRESS = "oscillation_state_address" + CONF_MAX_STEP = "max_step" + + DEFAULT_NAME = "KNX Fan" + + SCHEMA = vol.Schema( + { + vol.Required(CONF_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_STATE_ADDRESS): cv.string, + vol.Optional(CONF_OSCILLATION_ADDRESS): cv.string, + vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): cv.string, + vol.Optional(CONF_MAX_STEP): cv.byte, + } + ) From b0b81246f0de0777e688aa30e4f50314db6915d2 Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Wed, 10 Feb 2021 09:27:25 +0000 Subject: [PATCH 0347/1818] Bump roonapi to 0.0.32 (#46286) --- homeassistant/components/roon/manifest.json | 2 +- homeassistant/components/roon/server.py | 11 ----------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/roon/manifest.json b/homeassistant/components/roon/manifest.json index 0d5d0c131ae491..e4c4a25dcb540e 100644 --- a/homeassistant/components/roon/manifest.json +++ b/homeassistant/components/roon/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roon", "requirements": [ - "roonapi==0.0.31" + "roonapi==0.0.32" ], "codeowners": [ "@pavoni" diff --git a/homeassistant/components/roon/server.py b/homeassistant/components/roon/server.py index d5b8d81c2aa5f4..83b620e176ea81 100644 --- a/homeassistant/components/roon/server.py +++ b/homeassistant/components/roon/server.py @@ -141,17 +141,6 @@ async def async_update_players(self): async_dispatcher_send(self.hass, "roon_media_player", player_data) self.offline_devices.add(dev_id) - async def async_update_playlists(self): - """Store lists in memory with all playlists - could be used by a custom lovelace card.""" - all_playlists = [] - roon_playlists = self.roonapi.playlists() - if roon_playlists and "items" in roon_playlists: - all_playlists += [item["title"] for item in roon_playlists["items"]] - roon_playlists = self.roonapi.internet_radio() - if roon_playlists and "items" in roon_playlists: - all_playlists += [item["title"] for item in roon_playlists["items"]] - self.all_playlists = all_playlists - async def async_create_player_data(self, zone, output): """Create player object dict by combining zone with output.""" new_dict = zone.copy() diff --git a/requirements_all.txt b/requirements_all.txt index 63c8b98f73d753..f7955c9eb1f2f8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1967,7 +1967,7 @@ rokuecp==0.6.0 roombapy==1.6.2 # homeassistant.components.roon -roonapi==0.0.31 +roonapi==0.0.32 # homeassistant.components.rova rova==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 259f33e01f766c..df82a9d7429539 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -998,7 +998,7 @@ rokuecp==0.6.0 roombapy==1.6.2 # homeassistant.components.roon -roonapi==0.0.31 +roonapi==0.0.32 # homeassistant.components.rpi_power rpi-bad-power==0.1.0 From 78b7fbf7b16f64c426c747f3473e6f68703ff481 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Feb 2021 10:50:44 +0100 Subject: [PATCH 0348/1818] Fix race in EntityRegistry.async_device_modified (#46319) --- homeassistant/helpers/entity_registry.py | 4 ++- tests/helpers/test_entity_registry.py | 33 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 15218afc227fab..052e7398ba16a0 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -334,7 +334,9 @@ async def async_device_modified(self, event: Event) -> None: device_registry = await self.hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(event.data["device_id"]) - if not device.disabled: + + # The device may be deleted already if the event handling is late + if not device or not device.disabled: entities = async_entries_for_device( self, event.data["device_id"], include_disabled_entities=True ) diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 21f4392122e592..0fa2d486a9f690 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -710,6 +710,39 @@ async def test_remove_device_removes_entities(hass, registry): assert not registry.async_is_registered(entry.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) + config_entry = MockConfigEntry(domain="light") + + # Create device + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + # Updatete it + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={("bridgeid", "0123")}, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + # Add entity to the device + entry = registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + device_id=device_entry.id, + ) + + assert registry.async_is_registered(entry.entity_id) + + device_registry.async_remove_device(device_entry.id) + await hass.async_block_till_done() + + assert not registry.async_is_registered(entry.entity_id) + + async def test_disable_device_disables_entities(hass, registry): """Test that we disable entities tied to a device.""" device_registry = mock_device_registry(hass) From 1fea24502c1769e7c4d1db0c9460c240e717ff67 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Wed, 10 Feb 2021 18:14:03 +0800 Subject: [PATCH 0349/1818] Bump pyav version to 8.03 (#46315) --- homeassistant/components/stream/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 3d194bdf0d4e7c..19b9e7b2e8a6b2 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -2,7 +2,7 @@ "domain": "stream", "name": "Stream", "documentation": "https://www.home-assistant.io/integrations/stream", - "requirements": ["av==8.0.2"], + "requirements": ["av==8.0.3"], "dependencies": ["http"], "codeowners": ["@hunterjm", "@uvjustin"], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index f7955c9eb1f2f8..3c6e4f8a6596f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -297,7 +297,7 @@ auroranoaa==0.0.2 aurorapy==0.2.6 # homeassistant.components.stream -av==8.0.2 +av==8.0.3 # homeassistant.components.avea # avea==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index df82a9d7429539..6c3a1f9dc88ace 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -177,7 +177,7 @@ async-upnp-client==0.14.13 auroranoaa==0.0.2 # homeassistant.components.stream -av==8.0.2 +av==8.0.3 # homeassistant.components.axis axis==43 From bfd5a62bad68011db8b7da5de3b69d498f843490 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Feb 2021 11:31:51 +0100 Subject: [PATCH 0350/1818] Fix typo (#46321) --- tests/helpers/test_entity_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 0fa2d486a9f690..b176f7022d5357 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -720,7 +720,7 @@ async def test_update_device_race(hass, registry): config_entry_id=config_entry.entry_id, connections={("mac", "12:34:56:AB:CD:EF")}, ) - # Updatete it + # Update it device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={("bridgeid", "0123")}, From 7d2d98fc3c50b20c3d4fa802b1d49f0e387c70cb Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Wed, 10 Feb 2021 13:38:16 +0200 Subject: [PATCH 0351/1818] Revert multiple interfaces (#46300) --- 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 fffa98b6870d2b..c38869b3e0df13 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==0.5.4"], + "requirements": ["aioshelly==0.6.0"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3c6e4f8a6596f3..ca536e1cc8b2b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.4 +aioshelly==0.6.0 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6c3a1f9dc88ace..7f1f5df2d814fc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -137,7 +137,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.4 +aioshelly==0.6.0 # homeassistant.components.switcher_kis aioswitcher==1.2.1 From 4b493c5ab951bf978b691fc0ce5e9bae2a739208 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Feb 2021 12:42:28 +0100 Subject: [PATCH 0352/1818] Add target to service call API (#45898) * Add target to service call API * Fix _async_call_service_step * CONF_SERVICE_ENTITY_ID overrules target * Move merging up before processing schema * Restore services.yaml * Add test --- homeassistant/components/api/__init__.py | 2 +- .../components/websocket_api/commands.py | 2 + homeassistant/core.py | 9 +++- homeassistant/helpers/script.py | 12 +++--- homeassistant/helpers/service.py | 32 +++++++++++---- .../components/websocket_api/test_commands.py | 41 +++++++++++++++++++ 6 files changed, 82 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index e7bac8532eec63..a82309094e3095 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -378,7 +378,7 @@ async def post(self, request, domain, service): with AsyncTrackStates(hass) as changed_states: try: await hass.services.async_call( - domain, service, data, True, self.context(request) + domain, service, data, blocking=True, context=self.context(request) ) except (vol.Invalid, ServiceNotFound) as ex: raise HTTPBadRequest() from ex diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 77521c1ed98e94..ddd7548cd684e3 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -121,6 +121,7 @@ def handle_unsubscribe_events(hass, connection, msg): vol.Required("type"): "call_service", vol.Required("domain"): str, vol.Required("service"): str, + vol.Optional("target"): cv.ENTITY_SERVICE_FIELDS, vol.Optional("service_data"): dict, } ) @@ -139,6 +140,7 @@ async def handle_call_service(hass, connection, msg): msg.get("service_data"), blocking, context, + target=msg.get("target"), ) connection.send_message( messages.result_message(msg["id"], {"context": context}) diff --git a/homeassistant/core.py b/homeassistant/core.py index 4294eb530a7403..13f8b153047322 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1358,6 +1358,7 @@ def call( blocking: bool = False, context: Optional[Context] = None, limit: Optional[float] = SERVICE_CALL_LIMIT, + target: Optional[Dict] = None, ) -> Optional[bool]: """ Call a service. @@ -1365,7 +1366,9 @@ def call( See description of async_call for details. """ return asyncio.run_coroutine_threadsafe( - self.async_call(domain, service, service_data, blocking, context, limit), + self.async_call( + domain, service, service_data, blocking, context, limit, target + ), self._hass.loop, ).result() @@ -1377,6 +1380,7 @@ async def async_call( blocking: bool = False, context: Optional[Context] = None, limit: Optional[float] = SERVICE_CALL_LIMIT, + target: Optional[Dict] = None, ) -> Optional[bool]: """ Call a service. @@ -1404,6 +1408,9 @@ async def async_call( except KeyError: raise ServiceNotFound(domain, service) from None + if target: + service_data.update(target) + if handler.schema: try: processed_data = handler.schema(service_data) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 56accf9cf4953d..a0e8311048e516 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -433,14 +433,14 @@ async def _async_call_service_step(self): self._script.last_action = self._action.get(CONF_ALIAS, "call service") self._log("Executing step %s", self._script.last_action) - domain, service_name, service_data = service.async_prepare_call_from_config( + params = service.async_prepare_call_from_config( self._hass, self._action, self._variables ) running_script = ( - domain == "automation" - and service_name == "trigger" - or domain in ("python_script", "script") + params["domain"] == "automation" + and params["service_name"] == "trigger" + or params["domain"] in ("python_script", "script") ) # If this might start a script then disable the call timeout. # Otherwise use the normal service call limit. @@ -451,9 +451,7 @@ async def _async_call_service_step(self): service_task = self._hass.async_create_task( self._hass.services.async_call( - domain, - service_name, - service_data, + **params, blocking=True, context=self._context, limit=limit, diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 13dcd779b25747..a13b866a418ce6 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -14,6 +14,7 @@ Optional, Set, Tuple, + TypedDict, Union, cast, ) @@ -70,6 +71,15 @@ SERVICE_DESCRIPTION_CACHE = "service_description_cache" +class ServiceParams(TypedDict): + """Type for service call parameters.""" + + domain: str + service: str + service_data: Dict[str, Any] + target: Optional[Dict] + + @dataclasses.dataclass class SelectedEntities: """Class to hold the selected entities.""" @@ -136,7 +146,7 @@ async def async_call_from_config( raise _LOGGER.error(ex) else: - await hass.services.async_call(*params, blocking, context) + await hass.services.async_call(**params, blocking=blocking, context=context) @ha.callback @@ -146,7 +156,7 @@ def async_prepare_call_from_config( config: ConfigType, variables: TemplateVarsType = None, validate_config: bool = False, -) -> Tuple[str, str, Dict[str, Any]]: +) -> ServiceParams: """Prepare to call a service based on a config hash.""" if validate_config: try: @@ -177,10 +187,9 @@ def async_prepare_call_from_config( domain, service = domain_service.split(".", 1) - service_data = {} + target = config.get(CONF_TARGET) - if CONF_TARGET in config: - service_data.update(config[CONF_TARGET]) + service_data = {} for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]: if conf not in config: @@ -192,9 +201,17 @@ def async_prepare_call_from_config( raise HomeAssistantError(f"Error rendering data template: {ex}") from ex if CONF_SERVICE_ENTITY_ID in config: - service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] + if target: + target[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] + else: + target = {ATTR_ENTITY_ID: config[CONF_SERVICE_ENTITY_ID]} - return domain, service, service_data + return { + "domain": domain, + "service": service, + "service_data": service_data, + "target": target, + } @bind_hass @@ -431,6 +448,7 @@ async def async_get_all_descriptions( description = descriptions_cache[cache_key] = { "description": yaml_description.get("description", ""), + "target": yaml_description.get("target"), "fields": yaml_description.get("fields", {}), } diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index a7aa17db6d3cae..1f7abc42c4e939 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -52,6 +52,47 @@ def service_call(call): assert call.data == {"hello": "world"} +async def test_call_service_target(hass, websocket_client): + """Test call service command with target.""" + calls = [] + + @callback + def service_call(call): + calls.append(call) + + hass.services.async_register("domain_test", "test_service", service_call) + + await websocket_client.send_json( + { + "id": 5, + "type": "call_service", + "domain": "domain_test", + "service": "test_service", + "service_data": {"hello": "world"}, + "target": { + "entity_id": ["entity.one", "entity.two"], + "device_id": "deviceid", + }, + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + + assert len(calls) == 1 + call = calls[0] + + assert call.domain == "domain_test" + assert call.service == "test_service" + assert call.data == { + "hello": "world", + "entity_id": ["entity.one", "entity.two"], + "device_id": ["deviceid"], + } + + async def test_call_service_not_found(hass, websocket_client): """Test call service command.""" await websocket_client.send_json( From a6358430b46a68b6aee74dd223da29511ecb9bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 10 Feb 2021 15:16:58 +0200 Subject: [PATCH 0353/1818] Fix deprecated asyncio.wait use with coroutines (#44981) https://docs.python.org/3/library/asyncio-task.html#asyncio-example-wait-coroutine --- homeassistant/components/config/__init__.py | 4 ++-- homeassistant/components/device_tracker/legacy.py | 2 +- homeassistant/components/forked_daapd/media_player.py | 2 +- homeassistant/components/image_processing/__init__.py | 2 +- homeassistant/components/mailbox/__init__.py | 2 +- homeassistant/components/microsoft_face/__init__.py | 4 +++- homeassistant/components/notify/__init__.py | 2 +- homeassistant/components/stt/__init__.py | 2 +- homeassistant/components/tts/__init__.py | 2 +- homeassistant/helpers/script.py | 2 +- homeassistant/helpers/service.py | 6 ++++-- 11 files changed, 17 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 1098594a04c642..7d07710a4d00ce 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -65,11 +65,11 @@ def component_loaded(event): hass.bus.async_listen(EVENT_COMPONENT_LOADED, component_loaded) - tasks = [setup_panel(panel_name) for panel_name in SECTIONS] + tasks = [asyncio.create_task(setup_panel(panel_name)) for panel_name in SECTIONS] for panel_name in ON_DEMAND: if panel_name in hass.config.components: - tasks.append(setup_panel(panel_name)) + tasks.append(asyncio.create_task(setup_panel(panel_name))) if tasks: await asyncio.wait(tasks) diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 5f60d84f406b7b..b7583d80f82800 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -153,7 +153,7 @@ async def async_setup_integration(hass: HomeAssistantType, config: ConfigType) - legacy_platforms = await async_extract_config(hass, config) setup_tasks = [ - legacy_platform.async_setup_legacy(hass, tracker) + asyncio.create_task(legacy_platform.async_setup_legacy(hass, tracker)) for legacy_platform in legacy_platforms ] diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index 195ebf7e2cf4c1..724db80fabdac7 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -855,7 +855,7 @@ async def _update(self, update_types): ) if update_events: await asyncio.wait( - [event.wait() for event in update_events.values()] + [asyncio.create_task(event.wait()) for event in update_events.values()] ) # make sure callbacks done before update async_dispatcher_send( self.hass, SIGNAL_UPDATE_MASTER.format(self._entry_id), True diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 261278da4012d6..e885a9ca7a967e 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -81,7 +81,7 @@ async def async_scan_service(service): update_tasks = [] for entity in image_entities: entity.async_set_context(service.context) - update_tasks.append(entity.async_update_ha_state(True)) + update_tasks.append(asyncio.create_task(entity.async_update_ha_state(True))) if update_tasks: await asyncio.wait(update_tasks) diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index e5a0f16863d219..5d05596fb23af3 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -84,7 +84,7 @@ async def async_setup_platform(p_type, p_config=None, discovery_info=None): await component.async_add_entities([mailbox_entity]) setup_tasks = [ - async_setup_platform(p_type, p_config) + asyncio.create_task(async_setup_platform(p_type, p_config)) for p_type, p_config in config_per_platform(config, DOMAIN) ] diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index 69a738724c3225..b904642960388e 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -275,7 +275,9 @@ async def update_store(self): for person in persons: self._store[g_id][person["name"]] = person["personId"] - tasks.append(self._entities[g_id].async_update_ha_state()) + tasks.append( + asyncio.create_task(self._entities[g_id].async_update_ha_state()) + ) if tasks: await asyncio.wait(tasks) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index d3439baf4fb403..1e9c7d8595a9d9 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -312,7 +312,7 @@ async def async_setup_platform( ) setup_tasks = [ - async_setup_platform(integration_name, p_config) + asyncio.create_task(async_setup_platform(integration_name, p_config)) for integration_name, p_config in config_per_platform(config, DOMAIN) ] diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index 43ef01a497e30f..0ad621f070703a 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -62,7 +62,7 @@ async def async_setup_platform(p_type, p_config=None, discovery_info=None): return setup_tasks = [ - async_setup_platform(p_type, p_config) + asyncio.create_task(async_setup_platform(p_type, p_config)) for p_type, p_config in config_per_platform(config, DOMAIN) ] diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index d278283baaf457..ff1bf946e830a8 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -194,7 +194,7 @@ async def async_say_handle(service): ) setup_tasks = [ - async_setup_platform(p_type, p_config) + asyncio.create_task(async_setup_platform(p_type, p_config)) for p_type, p_config in config_per_platform(config, DOMAIN) ] diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index a0e8311048e516..3cc8348961f150 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1055,7 +1055,7 @@ async def async_run( raise async def _async_stop(self, update_state): - aws = [run.async_stop() for run in self._runs] + aws = [asyncio.create_task(run.async_stop()) for run in self._runs] if not aws: return await asyncio.wait(aws) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index a13b866a418ce6..b2fa97d51cc3d0 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -602,8 +602,10 @@ async def entity_service_call( done, pending = await asyncio.wait( [ - entity.async_request_call( - _handle_entity_call(hass, entity, func, data, call.context) + asyncio.create_task( + entity.async_request_call( + _handle_entity_call(hass, entity, func, data, call.context) + ) ) for entity in entities ] From 22389043eb7e72d8f8e6215b41743b2686e62903 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Feb 2021 14:31:11 +0100 Subject: [PATCH 0354/1818] Remove base_url fallback (#46316) --- homeassistant/components/http/__init__.py | 78 +---- homeassistant/core.py | 35 +-- homeassistant/helpers/network.py | 79 +---- .../ambiclimate/test_config_flow.py | 13 +- tests/components/http/test_init.py | 191 ------------ tests/components/ifttt/test_init.py | 15 +- tests/components/twilio/test_init.py | 14 +- tests/helpers/test_network.py | 284 +----------------- tests/test_core.py | 35 --- 9 files changed, 38 insertions(+), 706 deletions(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 63c88427a5ba8c..d09cfe754a9425 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -4,7 +4,6 @@ import logging import os import ssl -from traceback import extract_stack from typing import Dict, Optional, cast from aiohttp import web @@ -125,69 +124,6 @@ def __init__( self.port = port self.use_ssl = use_ssl - host = host.rstrip("/") - if host.startswith(("http://", "https://")): - self.deprecated_base_url = host - elif use_ssl: - self.deprecated_base_url = f"https://{host}" - else: - self.deprecated_base_url = f"http://{host}" - - if port is not None: - self.deprecated_base_url += f":{port}" - - @property - def base_url(self) -> str: - """Proxy property to find caller of this deprecated property.""" - found_frame = None - for frame in reversed(extract_stack()[:-1]): - for path in ("custom_components/", "homeassistant/components/"): - try: - index = frame.filename.index(path) - - # Skip webhook from the stack - if frame.filename[index:].startswith( - "homeassistant/components/webhook/" - ): - continue - - found_frame = frame - break - except ValueError: - continue - - if found_frame is not None: - break - - # Did not source from an integration? Hard error. - if found_frame is None: - raise RuntimeError( - "Detected use of deprecated `base_url` property in the Home Assistant core. Please report this issue." - ) - - # If a frame was found, it originated from an integration - if found_frame: - start = index + len(path) - end = found_frame.filename.index("/", start) - - integration = found_frame.filename[start:end] - - if path == "custom_components/": - extra = " to the custom component author" - else: - extra = "" - - _LOGGER.warning( - "Detected use of deprecated `base_url` property, use `homeassistant.helpers.network.get_url` method instead. Please report issue%s for %s using this method at %s, line %s: %s", - extra, - integration, - found_frame.filename[index:], - found_frame.lineno, - found_frame.line.strip(), - ) - - return self.deprecated_base_url - async def async_setup(hass, config): """Set up the HTTP API and debug interface.""" @@ -256,20 +192,16 @@ async def async_wait_frontend_load(event: Event) -> None: hass.http = server - host = conf.get(CONF_BASE_URL) local_ip = await hass.async_add_executor_job(hass_util.get_local_ip) - if host: - port = None - elif server_host is not None: + host = local_ip + if server_host is not None: # Assume the first server host name provided as API host host = server_host[0] - port = server_port - else: - host = local_ip - port = server_port - hass.config.api = ApiConfig(local_ip, host, port, ssl_certificate is not None) + hass.config.api = ApiConfig( + local_ip, host, server_port, ssl_certificate is not None + ) return True diff --git a/homeassistant/core.py b/homeassistant/core.py index 13f8b153047322..fff16cdd31f915 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -8,7 +8,6 @@ import datetime import enum import functools -from ipaddress import ip_address import logging import os import pathlib @@ -70,7 +69,7 @@ ServiceNotFound, Unauthorized, ) -from homeassistant.util import location, network +from homeassistant.util import location from homeassistant.util.async_ import ( fire_coroutine_threadsafe, run_callback_threadsafe, @@ -1687,39 +1686,7 @@ async def async_load(self) -> None: ) data = await store.async_load() - async def migrate_base_url(_: Event) -> None: - """Migrate base_url to internal_url/external_url.""" - if self.hass.config.api is None: - return - - base_url = yarl.URL(self.hass.config.api.deprecated_base_url) - - # Check if this is an internal URL - if str(base_url.host).endswith(".local") or ( - network.is_ip_address(str(base_url.host)) - and network.is_private(ip_address(base_url.host)) - ): - await self.async_update( - internal_url=network.normalize_url(str(base_url)) - ) - return - - # External, ensure this is not a loopback address - if not ( - network.is_ip_address(str(base_url.host)) - and network.is_loopback(ip_address(base_url.host)) - ): - await self.async_update( - external_url=network.normalize_url(str(base_url)) - ) - if data: - # Try to migrate base_url to internal_url/external_url - if "external_url" not in data: - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, migrate_base_url - ) - self._update( source=SOURCE_STORAGE, latitude=data.get("latitude"), diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 4e066eaa13c5e6..21f69dc539a6ba 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -8,13 +8,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass -from homeassistant.util.network import ( - is_ip_address, - is_local, - is_loopback, - is_private, - normalize_url, -) +from homeassistant.util.network import is_ip_address, is_loopback, normalize_url TYPE_URL_INTERNAL = "internal_url" TYPE_URL_EXTERNAL = "external_url" @@ -151,19 +145,6 @@ def _get_internal_url( ): return normalize_url(str(internal_url)) - # Fallback to old base_url - try: - return _get_deprecated_base_url( - hass, - internal=True, - allow_ip=allow_ip, - require_current_request=require_current_request, - require_ssl=require_ssl, - require_standard_port=require_standard_port, - ) - except NoURLAvailableError: - pass - # Fallback to detected local IP if allow_ip and not ( require_ssl or hass.config.api is None or hass.config.api.use_ssl @@ -217,17 +198,6 @@ def _get_external_url( ): return normalize_url(str(external_url)) - try: - return _get_deprecated_base_url( - hass, - allow_ip=allow_ip, - require_current_request=require_current_request, - require_ssl=require_ssl, - require_standard_port=require_standard_port, - ) - except NoURLAvailableError: - pass - if allow_cloud: try: return _get_cloud_url(hass, require_current_request=require_current_request) @@ -250,50 +220,3 @@ def _get_cloud_url(hass: HomeAssistant, require_current_request: bool = False) - return normalize_url(str(cloud_url)) raise NoURLAvailableError - - -@bind_hass -def _get_deprecated_base_url( - hass: HomeAssistant, - *, - internal: bool = False, - allow_ip: bool = True, - require_current_request: bool = False, - require_ssl: bool = False, - require_standard_port: bool = False, -) -> str: - """Work with the deprecated `base_url`, used as fallback.""" - if hass.config.api is None or not hass.config.api.deprecated_base_url: - raise NoURLAvailableError - - base_url = yarl.URL(hass.config.api.deprecated_base_url) - # Rules that apply to both internal and external - if ( - (allow_ip or not is_ip_address(str(base_url.host))) - and (not require_current_request or base_url.host == _get_request_host()) - and (not require_ssl or base_url.scheme == "https") - and (not require_standard_port or base_url.is_default_port()) - ): - # Check to ensure an internal URL - if internal and ( - str(base_url.host).endswith(".local") - or ( - is_ip_address(str(base_url.host)) - and not is_loopback(ip_address(base_url.host)) - and is_private(ip_address(base_url.host)) - ) - ): - return normalize_url(str(base_url)) - - # Check to ensure an external URL (a little) - if ( - not internal - and not str(base_url.host).endswith(".local") - and not ( - is_ip_address(str(base_url.host)) - and is_local(ip_address(str(base_url.host))) - ) - ): - return normalize_url(str(base_url)) - - raise NoURLAvailableError diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index b87c217181575c..3a325490064841 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -5,6 +5,7 @@ from homeassistant import data_entry_flow from homeassistant.components.ambiclimate import config_flow +from homeassistant.config import async_process_ha_core_config from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.setup import async_setup_component from homeassistant.util import aiohttp @@ -12,9 +13,11 @@ async def init_config_flow(hass): """Init a configuration flow.""" - await async_setup_component( - hass, "http", {"http": {"base_url": "https://hass.com"}} + await async_process_ha_core_config( + hass, + {"external_url": "https://example.com"}, ) + await async_setup_component(hass, "http", {}) config_flow.register_flow_implementation(hass, "id", "secret") flow = config_flow.AmbiclimateFlowHandler() @@ -58,20 +61,20 @@ async def test_full_flow_implementation(hass): assert result["step_id"] == "auth" assert ( result["description_placeholders"]["cb_url"] - == "https://hass.com/api/ambiclimate" + == "https://example.com/api/ambiclimate" ) url = result["description_placeholders"]["authorization_url"] assert "https://api.ambiclimate.com/oauth2/authorize" in url assert "client_id=id" in url assert "response_type=code" in url - assert "redirect_uri=https%3A%2F%2Fhass.com%2Fapi%2Fambiclimate" in url + assert "redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Fambiclimate" in url with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value="test"): result = await flow.async_step_code("123ABC") assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Ambiclimate" - assert result["data"]["callback_url"] == "https://hass.com/api/ambiclimate" + assert result["data"]["callback_url"] == "https://example.com/api/ambiclimate" assert result["data"][CONF_CLIENT_SECRET] == "secret" assert result["data"][CONF_CLIENT_ID] == "id" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index e7a3884c481cb0..9621b2690818b7 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -60,73 +60,6 @@ async def test_registering_view_while_running( hass.http.register_view(TestView) -def test_api_base_url_with_domain(mock_stack): - """Test setting API URL with domain.""" - api_config = http.ApiConfig("127.0.0.1", "example.com") - assert api_config.base_url == "http://example.com:8123" - - -def test_api_base_url_with_ip(mock_stack): - """Test setting API URL with IP.""" - api_config = http.ApiConfig("127.0.0.1", "1.1.1.1") - assert api_config.base_url == "http://1.1.1.1:8123" - - -def test_api_base_url_with_ip_and_port(mock_stack): - """Test setting API URL with IP and port.""" - api_config = http.ApiConfig("127.0.0.1", "1.1.1.1", 8124) - assert api_config.base_url == "http://1.1.1.1:8124" - - -def test_api_base_url_with_protocol(mock_stack): - """Test setting API URL with protocol.""" - api_config = http.ApiConfig("127.0.0.1", "https://example.com") - assert api_config.base_url == "https://example.com:8123" - - -def test_api_base_url_with_protocol_and_port(mock_stack): - """Test setting API URL with protocol and port.""" - api_config = http.ApiConfig("127.0.0.1", "https://example.com", 433) - assert api_config.base_url == "https://example.com:433" - - -def test_api_base_url_with_ssl_enable(mock_stack): - """Test setting API URL with use_ssl enabled.""" - api_config = http.ApiConfig("127.0.0.1", "example.com", use_ssl=True) - assert api_config.base_url == "https://example.com:8123" - - -def test_api_base_url_with_ssl_enable_and_port(mock_stack): - """Test setting API URL with use_ssl enabled and port.""" - api_config = http.ApiConfig("127.0.0.1", "1.1.1.1", use_ssl=True, port=8888) - assert api_config.base_url == "https://1.1.1.1:8888" - - -def test_api_base_url_with_protocol_and_ssl_enable(mock_stack): - """Test setting API URL with specific protocol and use_ssl enabled.""" - api_config = http.ApiConfig("127.0.0.1", "http://example.com", use_ssl=True) - assert api_config.base_url == "http://example.com:8123" - - -def test_api_base_url_removes_trailing_slash(mock_stack): - """Test a trialing slash is removed when setting the API URL.""" - api_config = http.ApiConfig("127.0.0.1", "http://example.com/") - assert api_config.base_url == "http://example.com:8123" - - -def test_api_local_ip(mock_stack): - """Test a trialing slash is removed when setting the API URL.""" - api_config = http.ApiConfig("127.0.0.1", "http://example.com/") - assert api_config.local_ip == "127.0.0.1" - - -async def test_api_no_base_url(hass, mock_stack): - """Test setting api url.""" - result = await async_setup_component(hass, "http", {"http": {}}) - assert result - assert hass.config.api.base_url == "http://127.0.0.1:8123" - - async def test_not_log_password(hass, aiohttp_client, caplog, legacy_auth): """Test access with password doesn't get logged.""" assert await async_setup_component(hass, "api", {"http": {}}) @@ -263,127 +196,3 @@ async def test_storing_config(hass, aiohttp_client, aiohttp_unused_port): restored["trusted_proxies"][0] = ip_network(restored["trusted_proxies"][0]) assert restored == http.HTTP_SCHEMA(config) - - -async def test_use_of_base_url(hass): - """Test detection base_url usage when called without integration context.""" - await async_setup_component(hass, "http", {"http": {}}) - with patch( - "homeassistant.components.http.extract_stack", - return_value=[ - Mock( - filename="/home/frenck/homeassistant/core.py", - lineno="21", - line="do_something()", - ), - Mock( - filename="/home/frenck/homeassistant/core.py", - lineno="42", - line="url = hass.config.api.base_url", - ), - Mock( - filename="/home/frenck/example/client.py", - lineno="21", - line="something()", - ), - ], - ), pytest.raises(RuntimeError): - hass.config.api.base_url - - -async def test_use_of_base_url_integration(hass, caplog): - """Test detection base_url usage when called with integration context.""" - await async_setup_component(hass, "http", {"http": {}}) - with patch( - "homeassistant.components.http.extract_stack", - return_value=[ - Mock( - filename="/home/frenck/homeassistant/core.py", - lineno="21", - line="do_something()", - ), - Mock( - filename="/home/frenck/homeassistant/components/example/__init__.py", - lineno="42", - line="url = hass.config.api.base_url", - ), - Mock( - filename="/home/frenck/example/client.py", - lineno="21", - line="something()", - ), - ], - ): - assert hass.config.api.base_url == "http://127.0.0.1:8123" - - assert ( - "Detected use of deprecated `base_url` property, use `homeassistant.helpers.network.get_url` method instead. Please report issue for example using this method at homeassistant/components/example/__init__.py, line 42: url = hass.config.api.base_url" - in caplog.text - ) - - -async def test_use_of_base_url_integration_webhook(hass, caplog): - """Test detection base_url usage when called with integration context.""" - await async_setup_component(hass, "http", {"http": {}}) - with patch( - "homeassistant.components.http.extract_stack", - return_value=[ - Mock( - filename="/home/frenck/homeassistant/core.py", - lineno="21", - line="do_something()", - ), - Mock( - filename="/home/frenck/homeassistant/components/example/__init__.py", - lineno="42", - line="url = hass.config.api.base_url", - ), - Mock( - filename="/home/frenck/homeassistant/components/webhook/__init__.py", - lineno="42", - line="return get_url(hass)", - ), - Mock( - filename="/home/frenck/example/client.py", - lineno="21", - line="something()", - ), - ], - ): - assert hass.config.api.base_url == "http://127.0.0.1:8123" - - assert ( - "Detected use of deprecated `base_url` property, use `homeassistant.helpers.network.get_url` method instead. Please report issue for example using this method at homeassistant/components/example/__init__.py, line 42: url = hass.config.api.base_url" - in caplog.text - ) - - -async def test_use_of_base_url_custom_component(hass, caplog): - """Test detection base_url usage when called with custom component context.""" - await async_setup_component(hass, "http", {"http": {}}) - with patch( - "homeassistant.components.http.extract_stack", - return_value=[ - Mock( - filename="/home/frenck/homeassistant/core.py", - lineno="21", - line="do_something()", - ), - Mock( - filename="/home/frenck/.homeassistant/custom_components/example/__init__.py", - lineno="42", - line="url = hass.config.api.base_url", - ), - Mock( - filename="/home/frenck/example/client.py", - lineno="21", - line="something()", - ), - ], - ): - assert hass.config.api.base_url == "http://127.0.0.1:8123" - - assert ( - "Detected use of deprecated `base_url` property, use `homeassistant.helpers.network.get_url` method instead. Please report issue to the custom component author for example using this method at custom_components/example/__init__.py, line 42: url = hass.config.api.base_url" - in caplog.text - ) diff --git a/tests/components/ifttt/test_init.py b/tests/components/ifttt/test_init.py index d10df2492d42dc..41885f0cd26b81 100644 --- a/tests/components/ifttt/test_init.py +++ b/tests/components/ifttt/test_init.py @@ -1,17 +1,20 @@ """Test the init file of IFTTT.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components import ifttt +from homeassistant.config import async_process_ha_core_config from homeassistant.core import callback async def test_config_flow_registers_webhook(hass, aiohttp_client): """Test setting up IFTTT and sending webhook.""" - with patch("homeassistant.util.get_local_ip", return_value="example.com"): - result = await hass.config_entries.flow.async_init( - "ifttt", context={"source": "user"} - ) + await async_process_ha_core_config( + hass, + {"internal_url": "http://example.local:8123"}, + ) + + result = await hass.config_entries.flow.async_init( + "ifttt", context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM, result result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) diff --git a/tests/components/twilio/test_init.py b/tests/components/twilio/test_init.py index ee7f072a65c7c5..580e5f83ebf85c 100644 --- a/tests/components/twilio/test_init.py +++ b/tests/components/twilio/test_init.py @@ -1,17 +1,19 @@ """Test the init file of Twilio.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components import twilio +from homeassistant.config import async_process_ha_core_config from homeassistant.core import callback async def test_config_flow_registers_webhook(hass, aiohttp_client): """Test setting up Twilio and sending webhook.""" - with patch("homeassistant.util.get_local_ip", return_value="example.com"): - result = await hass.config_entries.flow.async_init( - "twilio", context={"source": "user"} - ) + await async_process_ha_core_config( + hass, + {"internal_url": "http://example.local:8123"}, + ) + result = await hass.config_entries.flow.async_init( + "twilio", context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM, result result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index 06158558d5e0c3..aad37e2fd49c89 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -9,7 +9,6 @@ from homeassistant.helpers.network import ( NoURLAvailableError, _get_cloud_url, - _get_deprecated_base_url, _get_external_url, _get_internal_url, _get_request_host, @@ -166,9 +165,7 @@ async def test_get_url_internal_fallback(hass: HomeAssistant): """Test getting an instance URL when the user has not set an internal URL.""" assert hass.config.internal_url is None - hass.config.api = Mock( - use_ssl=False, port=8123, deprecated_base_url=None, local_ip="192.168.123.123" - ) + hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123") assert _get_internal_url(hass) == "http://192.168.123.123:8123" with pytest.raises(NoURLAvailableError): @@ -180,9 +177,7 @@ async def test_get_url_internal_fallback(hass: HomeAssistant): with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) - hass.config.api = Mock( - use_ssl=False, port=80, deprecated_base_url=None, local_ip="192.168.123.123" - ) + hass.config.api = Mock(use_ssl=False, port=80, local_ip="192.168.123.123") assert _get_internal_url(hass) == "http://192.168.123.123" assert ( _get_internal_url(hass, require_standard_port=True) == "http://192.168.123.123" @@ -194,7 +189,7 @@ async def test_get_url_internal_fallback(hass: HomeAssistant): with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_ssl=True) - hass.config.api = Mock(use_ssl=True, port=443, deprecated_base_url=None) + hass.config.api = Mock(use_ssl=True, port=443) with pytest.raises(NoURLAvailableError): _get_internal_url(hass) @@ -208,9 +203,7 @@ async def test_get_url_internal_fallback(hass: HomeAssistant): _get_internal_url(hass, require_ssl=True) # Do no accept any local loopback address as fallback - hass.config.api = Mock( - use_ssl=False, port=80, deprecated_base_url=None, local_ip="127.0.0.1" - ) + hass.config.api = Mock(use_ssl=False, port=80, local_ip="127.0.0.1") with pytest.raises(NoURLAvailableError): _get_internal_url(hass) @@ -457,9 +450,7 @@ async def test_get_url(hass: HomeAssistant): with pytest.raises(NoURLAvailableError): get_url(hass) - hass.config.api = Mock( - use_ssl=False, port=8123, deprecated_base_url=None, local_ip="192.168.123.123" - ) + hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123") assert get_url(hass) == "http://192.168.123.123:8123" assert get_url(hass, prefer_external=True) == "http://192.168.123.123:8123" @@ -543,274 +534,11 @@ async def test_get_request_host(hass: HomeAssistant): assert _get_request_host() == "example.com" -async def test_get_deprecated_base_url_internal(hass: HomeAssistant): - """Test getting an internal instance URL from the deprecated base_url.""" - # Test with SSL local URL - hass.config.api = Mock(deprecated_base_url="https://example.local") - assert _get_deprecated_base_url(hass, internal=True) == "https://example.local" - assert ( - _get_deprecated_base_url(hass, internal=True, allow_ip=False) - == "https://example.local" - ) - assert ( - _get_deprecated_base_url(hass, internal=True, require_ssl=True) - == "https://example.local" - ) - assert ( - _get_deprecated_base_url(hass, internal=True, require_standard_port=True) - == "https://example.local" - ) - - # Test with no SSL, local IP URL - hass.config.api = Mock(deprecated_base_url="http://10.10.10.10:8123") - assert _get_deprecated_base_url(hass, internal=True) == "http://10.10.10.10:8123" - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, allow_ip=False) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, require_standard_port=True) - - # Test with SSL, local IP URL - hass.config.api = Mock(deprecated_base_url="https://10.10.10.10") - assert _get_deprecated_base_url(hass, internal=True) == "https://10.10.10.10" - assert ( - _get_deprecated_base_url(hass, internal=True, require_ssl=True) - == "https://10.10.10.10" - ) - assert ( - _get_deprecated_base_url(hass, internal=True, require_standard_port=True) - == "https://10.10.10.10" - ) - - # Test external URL - hass.config.api = Mock(deprecated_base_url="https://example.com") - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, require_standard_port=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, allow_ip=False) - - # Test with loopback - hass.config.api = Mock(deprecated_base_url="https://127.0.0.42") - with pytest.raises(NoURLAvailableError): - assert _get_deprecated_base_url(hass, internal=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, allow_ip=False) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, internal=True, require_standard_port=True) - - -async def test_get_deprecated_base_url_external(hass: HomeAssistant): - """Test getting an external instance URL from the deprecated base_url.""" - # Test with SSL and external domain on standard port - hass.config.api = Mock(deprecated_base_url="https://example.com:443/") - assert _get_deprecated_base_url(hass) == "https://example.com" - assert _get_deprecated_base_url(hass, require_ssl=True) == "https://example.com" - assert ( - _get_deprecated_base_url(hass, require_standard_port=True) - == "https://example.com" - ) - - # Test without SSL and external domain on non-standard port - hass.config.api = Mock(deprecated_base_url="http://example.com:8123/") - assert _get_deprecated_base_url(hass) == "http://example.com:8123" - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_standard_port=True) - - # Test SSL on external IP - hass.config.api = Mock(deprecated_base_url="https://1.1.1.1") - assert _get_deprecated_base_url(hass) == "https://1.1.1.1" - assert _get_deprecated_base_url(hass, require_ssl=True) == "https://1.1.1.1" - assert ( - _get_deprecated_base_url(hass, require_standard_port=True) == "https://1.1.1.1" - ) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, allow_ip=False) - - # Test with private IP - hass.config.api = Mock(deprecated_base_url="https://10.10.10.10") - with pytest.raises(NoURLAvailableError): - assert _get_deprecated_base_url(hass) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, allow_ip=False) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_standard_port=True) - - # Test with local domain - hass.config.api = Mock(deprecated_base_url="https://example.local") - with pytest.raises(NoURLAvailableError): - assert _get_deprecated_base_url(hass) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, allow_ip=False) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_standard_port=True) - - # Test with loopback - hass.config.api = Mock(deprecated_base_url="https://127.0.0.42") - with pytest.raises(NoURLAvailableError): - assert _get_deprecated_base_url(hass) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, allow_ip=False) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_ssl=True) - - with pytest.raises(NoURLAvailableError): - _get_deprecated_base_url(hass, require_standard_port=True) - - -async def test_get_internal_url_with_base_url_fallback(hass: HomeAssistant): - """Test getting an internal instance URL with the deprecated base_url fallback.""" - hass.config.api = Mock( - use_ssl=False, port=8123, deprecated_base_url=None, local_ip="192.168.123.123" - ) - assert hass.config.internal_url is None - assert _get_internal_url(hass) == "http://192.168.123.123:8123" - - with pytest.raises(NoURLAvailableError): - _get_internal_url(hass, allow_ip=False) - - with pytest.raises(NoURLAvailableError): - _get_internal_url(hass, require_standard_port=True) - - with pytest.raises(NoURLAvailableError): - _get_internal_url(hass, require_ssl=True) - - # Add base_url - hass.config.api = Mock( - use_ssl=False, port=8123, deprecated_base_url="https://example.local" - ) - assert _get_internal_url(hass) == "https://example.local" - assert _get_internal_url(hass, allow_ip=False) == "https://example.local" - assert ( - _get_internal_url(hass, require_standard_port=True) == "https://example.local" - ) - assert _get_internal_url(hass, require_ssl=True) == "https://example.local" - - # Add internal URL - await async_process_ha_core_config( - hass, - {"internal_url": "https://internal.local"}, - ) - assert _get_internal_url(hass) == "https://internal.local" - assert _get_internal_url(hass, allow_ip=False) == "https://internal.local" - assert ( - _get_internal_url(hass, require_standard_port=True) == "https://internal.local" - ) - assert _get_internal_url(hass, require_ssl=True) == "https://internal.local" - - # Add internal URL, mixed results - await async_process_ha_core_config( - hass, - {"internal_url": "http://internal.local:8123"}, - ) - assert _get_internal_url(hass) == "http://internal.local:8123" - assert _get_internal_url(hass, allow_ip=False) == "http://internal.local:8123" - assert ( - _get_internal_url(hass, require_standard_port=True) == "https://example.local" - ) - assert _get_internal_url(hass, require_ssl=True) == "https://example.local" - - # Add internal URL set to an IP - await async_process_ha_core_config( - hass, - {"internal_url": "http://10.10.10.10:8123"}, - ) - assert _get_internal_url(hass) == "http://10.10.10.10:8123" - assert _get_internal_url(hass, allow_ip=False) == "https://example.local" - assert ( - _get_internal_url(hass, require_standard_port=True) == "https://example.local" - ) - assert _get_internal_url(hass, require_ssl=True) == "https://example.local" - - -async def test_get_external_url_with_base_url_fallback(hass: HomeAssistant): - """Test getting an external instance URL with the deprecated base_url fallback.""" - hass.config.api = Mock(use_ssl=False, port=8123, deprecated_base_url=None) - assert hass.config.internal_url is None - - with pytest.raises(NoURLAvailableError): - _get_external_url(hass) - - # Test with SSL and external domain on standard port - hass.config.api = Mock(deprecated_base_url="https://example.com:443/") - assert _get_external_url(hass) == "https://example.com" - assert _get_external_url(hass, allow_ip=False) == "https://example.com" - assert _get_external_url(hass, require_ssl=True) == "https://example.com" - assert _get_external_url(hass, require_standard_port=True) == "https://example.com" - - # Add external URL - await async_process_ha_core_config( - hass, - {"external_url": "https://external.example.com"}, - ) - assert _get_external_url(hass) == "https://external.example.com" - assert _get_external_url(hass, allow_ip=False) == "https://external.example.com" - assert ( - _get_external_url(hass, require_standard_port=True) - == "https://external.example.com" - ) - assert _get_external_url(hass, require_ssl=True) == "https://external.example.com" - - # Add external URL, mixed results - await async_process_ha_core_config( - hass, - {"external_url": "http://external.example.com:8123"}, - ) - assert _get_external_url(hass) == "http://external.example.com:8123" - assert _get_external_url(hass, allow_ip=False) == "http://external.example.com:8123" - assert _get_external_url(hass, require_standard_port=True) == "https://example.com" - assert _get_external_url(hass, require_ssl=True) == "https://example.com" - - # Add external URL set to an IP - await async_process_ha_core_config( - hass, - {"external_url": "http://1.1.1.1:8123"}, - ) - assert _get_external_url(hass) == "http://1.1.1.1:8123" - assert _get_external_url(hass, allow_ip=False) == "https://example.com" - assert _get_external_url(hass, require_standard_port=True) == "https://example.com" - assert _get_external_url(hass, require_ssl=True) == "https://example.com" - - async def test_get_current_request_url_with_known_host( hass: HomeAssistant, current_request ): """Test getting current request URL with known hosts addresses.""" - hass.config.api = Mock( - use_ssl=False, port=8123, local_ip="127.0.0.1", deprecated_base_url=None - ) + hass.config.api = Mock(use_ssl=False, port=8123, local_ip="127.0.0.1") assert hass.config.internal_url is None with pytest.raises(NoURLAvailableError): diff --git a/tests/test_core.py b/tests/test_core.py index 0bf00d92c45971..dfd5b925e1c6a8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1294,41 +1294,6 @@ def test_valid_entity_id(): assert ha.valid_entity_id(valid), valid -async def test_migration_base_url(hass, hass_storage): - """Test that we migrate base url to internal/external url.""" - config = ha.Config(hass) - stored = {"version": 1, "data": {}} - hass_storage[ha.CORE_STORAGE_KEY] = stored - with patch.object(hass.bus, "async_listen_once") as mock_listen: - # Empty config - await config.async_load() - assert len(mock_listen.mock_calls) == 0 - - # With just a name - stored["data"] = {"location_name": "Test Name"} - await config.async_load() - assert len(mock_listen.mock_calls) == 1 - - # With external url - stored["data"]["external_url"] = "https://example.com" - await config.async_load() - assert len(mock_listen.mock_calls) == 1 - - # Test that the event listener works - assert mock_listen.mock_calls[0][1][0] == EVENT_HOMEASSISTANT_START - - # External - hass.config.api = Mock(deprecated_base_url="https://loaded-example.com") - await mock_listen.mock_calls[0][1][1](None) - assert config.external_url == "https://loaded-example.com" - - # Internal - for internal in ("http://hass.local", "http://192.168.1.100:8123"): - hass.config.api = Mock(deprecated_base_url=internal) - await mock_listen.mock_calls[0][1][1](None) - assert config.internal_url == internal - - async def test_additional_data_in_core_config(hass, hass_storage): """Test that we can handle additional data in core configuration.""" config = ha.Config(hass) From 6e1f3b78617553e3848536b1a0b4d3c375e01572 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 08:35:11 -0500 Subject: [PATCH 0355/1818] Use core constants for joaoapps_join (#46291) --- homeassistant/components/joaoapps_join/__init__.py | 4 +--- homeassistant/components/joaoapps_join/notify.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py index 1bc4ae298c4785..a65a7ffd7fe14e 100644 --- a/homeassistant/components/joaoapps_join/__init__.py +++ b/homeassistant/components/joaoapps_join/__init__.py @@ -12,14 +12,13 @@ ) import voluptuous as vol -from homeassistant.const import CONF_API_KEY, CONF_NAME +from homeassistant.const import CONF_API_KEY, CONF_DEVICE_ID, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DOMAIN = "joaoapps_join" -CONF_DEVICE_ID = "device_id" CONF_DEVICE_IDS = "device_ids" CONF_DEVICE_NAMES = "device_names" @@ -115,7 +114,6 @@ def send_sms_service(service): def setup(hass, config): """Set up the Join services.""" - for device in config[DOMAIN]: api_key = device.get(CONF_API_KEY) device_id = device.get(CONF_DEVICE_ID) diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py index d01e49c77d8f84..7ba089e5dab0c5 100644 --- a/homeassistant/components/joaoapps_join/notify.py +++ b/homeassistant/components/joaoapps_join/notify.py @@ -11,12 +11,11 @@ PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import CONF_API_KEY +from homeassistant.const import CONF_API_KEY, CONF_DEVICE_ID import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_DEVICE_ID = "device_id" CONF_DEVICE_IDS = "device_ids" CONF_DEVICE_NAMES = "device_names" @@ -61,7 +60,6 @@ def __init__(self, api_key, device_id, device_ids, device_names): def send_message(self, message="", **kwargs): """Send a message to a user.""" - title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) data = kwargs.get(ATTR_DATA) or {} send_notification( From ad400d91bc8202b5388fa32618c00eaa746c312e Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 08:36:05 -0500 Subject: [PATCH 0356/1818] Use core constants for sensor integration (#46290) --- homeassistant/components/integration/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index a776920b8e644c..6c59035adb4397 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, + CONF_METHOD, CONF_NAME, STATE_UNAVAILABLE, STATE_UNKNOWN, @@ -31,7 +32,6 @@ CONF_UNIT_PREFIX = "unit_prefix" CONF_UNIT_TIME = "unit_time" CONF_UNIT_OF_MEASUREMENT = "unit" -CONF_METHOD = "method" TRAPEZOIDAL_METHOD = "trapezoidal" LEFT_METHOD = "left" From c66d9ea25c9b5e8025d6ea9da6b4c032d432bab8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Feb 2021 14:39:10 +0100 Subject: [PATCH 0357/1818] Hide volume control for cast devices with fixed volume (#46328) --- homeassistant/components/cast/manifest.json | 2 +- homeassistant/components/cast/media_player.py | 7 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/test_media_player.py | 65 +++++++++++++++++++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 88dabc8d04d0e9..5963e93cf8cb62 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==8.0.0"], + "requirements": ["pychromecast==8.1.0"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 6bedae1cac58c2..981d67f0caa561 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -10,6 +10,7 @@ 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 ( CONNECTION_STATUS_CONNECTED, @@ -82,8 +83,6 @@ | SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON - | SUPPORT_VOLUME_MUTE - | SUPPORT_VOLUME_SET ) @@ -743,6 +742,10 @@ def supported_features(self): support = SUPPORT_CAST media_status = self._media_status()[0] + if self.cast_status: + if self.cast_status.volume_control_type != VOLUME_CONTROL_TYPE_FIXED: + support |= SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET + if media_status: if media_status.supports_queue_next: support |= SUPPORT_PREVIOUS_TRACK diff --git a/requirements_all.txt b/requirements_all.txt index ca536e1cc8b2b1..9f2aef26012158 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1310,7 +1310,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==8.0.0 +pychromecast==8.1.0 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7f1f5df2d814fc..143ec43a5d4466 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -681,7 +681,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==8.0.0 +pychromecast==8.1.0 # homeassistant.components.comfoconnect pycomfoconnect==0.4 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 050d6a6932db8e..be24afcb5382d8 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -11,6 +11,19 @@ from homeassistant.components import tts from homeassistant.components.cast import media_player as cast from homeassistant.components.cast.media_player import ChromecastInfo +from homeassistant.components.media_player.const import ( + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SEEK, + SUPPORT_STOP, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, +) from homeassistant.config import async_process_ha_core_config from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import PlatformNotReady @@ -662,6 +675,17 @@ async def test_entity_cast_status(hass: HomeAssistantType): assert state.state == "unknown" assert entity_id == reg.async_get_entity_id("media_player", "cast", full_info.uuid) + assert state.attributes.get("supported_features") == ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET + ) + cast_status = MagicMock() cast_status.volume_level = 0.5 cast_status.volume_muted = False @@ -680,6 +704,21 @@ async def test_entity_cast_status(hass: HomeAssistantType): assert state.attributes.get("volume_level") == 0.2 assert state.attributes.get("is_volume_muted") + # Disable support for volume control + cast_status = MagicMock() + cast_status.volume_control_type = "fixed" + cast_status_cb(cast_status) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.attributes.get("supported_features") == ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + ) + async def test_entity_play_media(hass: HomeAssistantType): """Test playing media.""" @@ -894,6 +933,17 @@ async def test_entity_control(hass: HomeAssistantType): assert state.state == "unknown" assert entity_id == reg.async_get_entity_id("media_player", "cast", full_info.uuid) + assert state.attributes.get("supported_features") == ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET + ) + # Turn on await common.async_turn_on(hass, entity_id) chromecast.play_media.assert_called_once_with( @@ -940,6 +990,21 @@ async def test_entity_control(hass: HomeAssistantType): media_status_cb(media_status) await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.attributes.get("supported_features") == ( + SUPPORT_PAUSE + | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_STOP + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_NEXT_TRACK + | SUPPORT_SEEK + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET + ) + # Media previous await common.async_media_previous_track(hass, entity_id) chromecast.media_controller.queue_prev.assert_called_once_with() From b7e11347d5e65e8380234e856e3b95c70844c686 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Feb 2021 14:56:54 +0100 Subject: [PATCH 0358/1818] Remove defunct Crime Reports integration (#46312) --- .coveragerc | 1 - .../components/crimereports/__init__.py | 1 - .../components/crimereports/manifest.json | 7 - .../components/crimereports/sensor.py | 127 ------------------ requirements_all.txt | 3 - 5 files changed, 139 deletions(-) delete mode 100644 homeassistant/components/crimereports/__init__.py delete mode 100644 homeassistant/components/crimereports/manifest.json delete mode 100644 homeassistant/components/crimereports/sensor.py diff --git a/.coveragerc b/.coveragerc index 581c6350a05b11..cd1d6a9f6d3fde 100644 --- a/.coveragerc +++ b/.coveragerc @@ -157,7 +157,6 @@ omit = homeassistant/components/coolmaster/const.py homeassistant/components/cppm_tracker/device_tracker.py homeassistant/components/cpuspeed/sensor.py - homeassistant/components/crimereports/sensor.py homeassistant/components/cups/sensor.py homeassistant/components/currencylayer/sensor.py homeassistant/components/daikin/* diff --git a/homeassistant/components/crimereports/__init__.py b/homeassistant/components/crimereports/__init__.py deleted file mode 100644 index 57af9df4dbfe6d..00000000000000 --- a/homeassistant/components/crimereports/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The crimereports component.""" diff --git a/homeassistant/components/crimereports/manifest.json b/homeassistant/components/crimereports/manifest.json deleted file mode 100644 index 624d812f5f334e..00000000000000 --- a/homeassistant/components/crimereports/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "crimereports", - "name": "Crime Reports", - "documentation": "https://www.home-assistant.io/integrations/crimereports", - "requirements": ["crimereports==1.0.1"], - "codeowners": [] -} diff --git a/homeassistant/components/crimereports/sensor.py b/homeassistant/components/crimereports/sensor.py deleted file mode 100644 index 8919b2d09b127e..00000000000000 --- a/homeassistant/components/crimereports/sensor.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Sensor for Crime Reports.""" -from collections import defaultdict -from datetime import timedelta - -import crimereports -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, - ATTR_LATITUDE, - ATTR_LONGITUDE, - CONF_EXCLUDE, - CONF_INCLUDE, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_NAME, - CONF_RADIUS, - LENGTH_KILOMETERS, - LENGTH_METERS, -) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import slugify -from homeassistant.util.distance import convert -from homeassistant.util.dt import now - -DOMAIN = "crimereports" - -EVENT_INCIDENT = f"{DOMAIN}_incident" - -SCAN_INTERVAL = timedelta(minutes=30) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_RADIUS): vol.Coerce(float), - vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude, - vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude, - vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]), - } -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Crime Reports platform.""" - latitude = config.get(CONF_LATITUDE, hass.config.latitude) - longitude = config.get(CONF_LONGITUDE, hass.config.longitude) - name = config[CONF_NAME] - radius = config[CONF_RADIUS] - include = config.get(CONF_INCLUDE) - exclude = config.get(CONF_EXCLUDE) - - add_entities( - [CrimeReportsSensor(hass, name, latitude, longitude, radius, include, exclude)], - True, - ) - - -class CrimeReportsSensor(Entity): - """Representation of a Crime Reports Sensor.""" - - def __init__(self, hass, name, latitude, longitude, radius, include, exclude): - """Initialize the Crime Reports sensor.""" - self._hass = hass - self._name = name - self._include = include - self._exclude = exclude - radius_kilometers = convert(radius, LENGTH_METERS, LENGTH_KILOMETERS) - self._crimereports = crimereports.CrimeReports( - (latitude, longitude), radius_kilometers - ) - self._attributes = None - self._state = None - self._previous_incidents = set() - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self._attributes - - def _incident_event(self, incident): - """Fire if an event occurs.""" - data = { - "type": incident.get("type"), - "description": incident.get("friendly_description"), - "timestamp": incident.get("timestamp"), - "location": incident.get("location"), - } - if incident.get("coordinates"): - data.update( - { - ATTR_LATITUDE: incident.get("coordinates")[0], - ATTR_LONGITUDE: incident.get("coordinates")[1], - } - ) - self._hass.bus.fire(EVENT_INCIDENT, data) - - def update(self): - """Update device state.""" - incident_counts = defaultdict(int) - incidents = self._crimereports.get_incidents( - now().date(), include=self._include, exclude=self._exclude - ) - fire_events = len(self._previous_incidents) > 0 - if len(incidents) < len(self._previous_incidents): - self._previous_incidents = set() - for incident in incidents: - incident_type = slugify(incident.get("type")) - incident_counts[incident_type] += 1 - if fire_events and incident.get("id") not in self._previous_incidents: - self._incident_event(incident) - self._previous_incidents.add(incident.get("id")) - self._attributes = {ATTR_ATTRIBUTION: crimereports.ATTRIBUTION} - self._attributes.update(incident_counts) - self._state = len(incidents) diff --git a/requirements_all.txt b/requirements_all.txt index 9f2aef26012158..202049341e5b66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -450,9 +450,6 @@ coronavirus==1.1.1 # homeassistant.scripts.credstash # credstash==1.15.0 -# homeassistant.components.crimereports -crimereports==1.0.1 - # homeassistant.components.datadog datadog==0.15.0 From ad727152121d4e7ab127432cf80a409f9b2d10b5 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 08:57:53 -0500 Subject: [PATCH 0359/1818] Use core constants for konnected (#46322) --- homeassistant/components/konnected/__init__.py | 4 ++-- homeassistant/components/konnected/config_flow.py | 4 ++-- homeassistant/components/konnected/const.py | 2 -- homeassistant/components/konnected/panel.py | 4 ++-- homeassistant/components/konnected/switch.py | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index a6bc7eff5ca8ba..348eaeda3ac5aa 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -18,11 +18,13 @@ CONF_ACCESS_TOKEN, CONF_BINARY_SENSORS, CONF_DEVICES, + CONF_DISCOVERY, CONF_HOST, CONF_ID, CONF_NAME, CONF_PIN, CONF_PORT, + CONF_REPEAT, CONF_SENSORS, CONF_SWITCHES, CONF_TYPE, @@ -48,12 +50,10 @@ CONF_ACTIVATION, CONF_API_HOST, CONF_BLINK, - CONF_DISCOVERY, CONF_INVERSE, CONF_MOMENTARY, CONF_PAUSE, CONF_POLL_INTERVAL, - CONF_REPEAT, DOMAIN, PIN_TO_ZONE, STATE_HIGH, diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index 4e8d13c999e71d..dc15e7a86c411d 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -17,10 +17,12 @@ from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_BINARY_SENSORS, + CONF_DISCOVERY, CONF_HOST, CONF_ID, CONF_NAME, CONF_PORT, + CONF_REPEAT, CONF_SENSORS, CONF_SWITCHES, CONF_TYPE, @@ -34,13 +36,11 @@ CONF_API_HOST, CONF_BLINK, CONF_DEFAULT_OPTIONS, - CONF_DISCOVERY, CONF_INVERSE, CONF_MODEL, CONF_MOMENTARY, CONF_PAUSE, CONF_POLL_INTERVAL, - CONF_REPEAT, DOMAIN, STATE_HIGH, STATE_LOW, diff --git a/homeassistant/components/konnected/const.py b/homeassistant/components/konnected/const.py index c1e7d6b6f269b5..270b26045383e4 100644 --- a/homeassistant/components/konnected/const.py +++ b/homeassistant/components/konnected/const.py @@ -9,10 +9,8 @@ CONF_PAUSE = "pause" CONF_POLL_INTERVAL = "poll_interval" CONF_PRECISION = "precision" -CONF_REPEAT = "repeat" CONF_INVERSE = "inverse" CONF_BLINK = "blink" -CONF_DISCOVERY = "discovery" CONF_DHT_SENSORS = "dht_sensors" CONF_DS18B20_SENSORS = "ds18b20_sensors" CONF_MODEL = "model" diff --git a/homeassistant/components/konnected/panel.py b/homeassistant/components/konnected/panel.py index 76e751592904e8..18f2ed64a1dcd9 100644 --- a/homeassistant/components/konnected/panel.py +++ b/homeassistant/components/konnected/panel.py @@ -10,11 +10,13 @@ CONF_ACCESS_TOKEN, CONF_BINARY_SENSORS, CONF_DEVICES, + CONF_DISCOVERY, CONF_HOST, CONF_ID, CONF_NAME, CONF_PIN, CONF_PORT, + CONF_REPEAT, CONF_SENSORS, CONF_SWITCHES, CONF_TYPE, @@ -31,13 +33,11 @@ CONF_BLINK, CONF_DEFAULT_OPTIONS, CONF_DHT_SENSORS, - CONF_DISCOVERY, CONF_DS18B20_SENSORS, CONF_INVERSE, CONF_MOMENTARY, CONF_PAUSE, CONF_POLL_INTERVAL, - CONF_REPEAT, DOMAIN, ENDPOINT_ROOT, STATE_LOW, diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py index 1d26f7875c76cd..b599fe5524255d 100644 --- a/homeassistant/components/konnected/switch.py +++ b/homeassistant/components/konnected/switch.py @@ -5,6 +5,7 @@ ATTR_STATE, CONF_DEVICES, CONF_NAME, + CONF_REPEAT, CONF_SWITCHES, CONF_ZONE, ) @@ -14,7 +15,6 @@ CONF_ACTIVATION, CONF_MOMENTARY, CONF_PAUSE, - CONF_REPEAT, DOMAIN as KONNECTED_DOMAIN, STATE_HIGH, STATE_LOW, From 7928cda080f2932dd36784cc146ff836555f3c15 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Feb 2021 15:25:24 +0100 Subject: [PATCH 0360/1818] Add `already_in_progress` string to roku config flow (#46333) --- homeassistant/components/roku/strings.json | 1 + homeassistant/components/roku/translations/en.json | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/roku/strings.json b/homeassistant/components/roku/strings.json index 55b533d4f1cfc6..3523615ff33a44 100644 --- a/homeassistant/components/roku/strings.json +++ b/homeassistant/components/roku/strings.json @@ -19,6 +19,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "unknown": "[%key:common::config_flow::error::unknown%]" } } diff --git a/homeassistant/components/roku/translations/en.json b/homeassistant/components/roku/translations/en.json index 08db89f367761a..2b54cafe8909a1 100644 --- a/homeassistant/components/roku/translations/en.json +++ b/homeassistant/components/roku/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", "unknown": "Unexpected error" }, "error": { From ea4ad854883b77d39c8b2f5c5cf3915f258e780c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 10 Feb 2021 15:25:44 +0100 Subject: [PATCH 0361/1818] Replace StrictVersion with AwesomeVersion (#46331) --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 13497da82902db..9d65bb4c5d4b3f 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -1,10 +1,10 @@ """Support to check for available updates.""" import asyncio from datetime import timedelta -from distutils.version import StrictVersion import logging import async_timeout +from awesomeversion import AwesomeVersion from distro import linux_distribution # pylint: disable=import-error import voluptuous as vol @@ -83,16 +83,16 @@ async def check_new_version() -> Updater: # Validate version update_available = False - if StrictVersion(newest) > StrictVersion(current_version): + if AwesomeVersion(newest) > AwesomeVersion(current_version): _LOGGER.debug( "The latest available version of Home Assistant is %s", newest ) update_available = True - elif StrictVersion(newest) == StrictVersion(current_version): + elif AwesomeVersion(newest) == AwesomeVersion(current_version): _LOGGER.debug( "You are on the latest version (%s) of Home Assistant", newest ) - elif StrictVersion(newest) < StrictVersion(current_version): + elif AwesomeVersion(newest) < AwesomeVersion(current_version): _LOGGER.debug( "Local version (%s) is newer than the latest available version (%s)", current_version, From dbb98e6cac6cef0ff394633fabf06cf36e9de10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 10 Feb 2021 15:26:38 +0100 Subject: [PATCH 0362/1818] Replace LooseVersion with AwesomeVersion (#46330) --- homeassistant/config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 5cd4a0700e5f88..73d0273d1c041b 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1,6 +1,5 @@ """Module to help with parsing and generating configuration files.""" from collections import OrderedDict -from distutils.version import LooseVersion # pylint: disable=import-error import logging import os import re @@ -8,6 +7,7 @@ from types import ModuleType from typing import Any, Callable, Dict, Optional, Sequence, Set, Tuple, Union +from awesomeversion import AwesomeVersion import voluptuous as vol from voluptuous.humanize import humanize_error @@ -364,15 +364,15 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: "Upgrading configuration directory from %s to %s", conf_version, __version__ ) - version_obj = LooseVersion(conf_version) + version_obj = AwesomeVersion(conf_version) - if version_obj < LooseVersion("0.50"): + if version_obj < AwesomeVersion("0.50"): # 0.50 introduced persistent deps dir. lib_path = hass.config.path("deps") if os.path.isdir(lib_path): shutil.rmtree(lib_path) - if version_obj < LooseVersion("0.92"): + if version_obj < AwesomeVersion("0.92"): # 0.92 moved google/tts.py to google_translate/tts.py config_path = hass.config.path(YAML_CONFIG_FILE) @@ -388,7 +388,7 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: except OSError: _LOGGER.exception("Migrating to google_translate tts failed") - if version_obj < LooseVersion("0.94") and is_docker_env(): + if version_obj < AwesomeVersion("0.94") and is_docker_env(): # In 0.94 we no longer install packages inside the deps folder when # running inside a Docker container. lib_path = hass.config.path("deps") From 917a616ce1b29f51f2a536df67a285656c0ece7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 10 Feb 2021 15:58:26 +0100 Subject: [PATCH 0363/1818] Replace parse_version with AwesomeVersion (#46329) --- homeassistant/components/blueprint/models.py | 4 ++-- homeassistant/components/hyperion/__init__.py | 4 ++-- tests/components/hyperion/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py index 32fc30b60b97be..84931a04310d69 100644 --- a/homeassistant/components/blueprint/models.py +++ b/homeassistant/components/blueprint/models.py @@ -5,7 +5,7 @@ import shutil from typing import Any, Dict, List, Optional, Union -from pkg_resources import parse_version +from awesomeversion import AwesomeVersion import voluptuous as vol from voluptuous.humanize import humanize_error @@ -114,7 +114,7 @@ def validate(self) -> Optional[List[str]]: metadata = self.metadata min_version = metadata.get(CONF_HOMEASSISTANT, {}).get(CONF_MIN_VERSION) - if min_version is not None and parse_version(__version__) < parse_version( + if min_version is not None and AwesomeVersion(__version__) < AwesomeVersion( min_version ): errors.append(f"Requires at least Home Assistant {min_version}") diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index b3606880f8c12c..9e35ae2e6b8562 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -4,8 +4,8 @@ import logging from typing import Any, Callable, Dict, List, Optional, Set, Tuple, cast +from awesomeversion import AwesomeVersion from hyperion import client, const as hyperion_const -from pkg_resources import parse_version from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -159,7 +159,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b version = await hyperion_client.async_sysinfo_version() if version is not None: try: - if parse_version(version) < parse_version(HYPERION_VERSION_WARN_CUTOFF): + if AwesomeVersion(version) < AwesomeVersion(HYPERION_VERSION_WARN_CUTOFF): _LOGGER.warning( "Using a Hyperion server version < %s is not recommended -- " "some features may be unavailable or may not function correctly. " diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index f3b2ad383bd732..e50de207d00ca5 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -98,7 +98,7 @@ def create_mock_client() -> Mock: ) mock_client.async_sysinfo_id = AsyncMock(return_value=TEST_SYSINFO_ID) - mock_client.async_sysinfo_version = AsyncMock(return_value=TEST_SYSINFO_ID) + mock_client.async_sysinfo_version = AsyncMock(return_value=TEST_SYSINFO_VERSION) mock_client.async_client_switch_instance = AsyncMock(return_value=True) mock_client.async_client_login = AsyncMock(return_value=True) mock_client.async_get_serverinfo = AsyncMock( From 74647e1fa862fcc537494fa384550a5d2c4d1448 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 10 Feb 2021 16:30:16 +0100 Subject: [PATCH 0364/1818] Add guards for missing value in binary_sensor platform of zwave_js integration (#46293) --- homeassistant/components/zwave_js/binary_sensor.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 8a483c34e12b86..8c56869449adcc 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -268,8 +268,10 @@ def __init__( self._name = self.generate_name(include_value_name=True) @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: """Return if the sensor is on or off.""" + if self.info.primary_value.value is None: + return None return bool(self.info.primary_value.value) @property @@ -312,8 +314,10 @@ def __init__( self._mapping_info = self._get_sensor_mapping() @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: """Return if the sensor is on or off.""" + if self.info.primary_value.value is None: + return None return int(self.info.primary_value.value) == int(self.state_key) @property @@ -361,8 +365,10 @@ def __init__( self._name = self.generate_name(include_value_name=True) @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: """Return if the sensor is on or off.""" + if self.info.primary_value.value is None: + return None return self.info.primary_value.value in self._mapping_info["on_states"] @property From 538df17a28269f3b85e1c6a4f0fd0070e1d47e97 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Feb 2021 16:30:29 +0100 Subject: [PATCH 0365/1818] Restore Google/Alexa extra significant change checks (#46335) --- .../components/alexa/state_report.py | 38 ++++++-- .../google_assistant/report_state.py | 35 +++++-- homeassistant/helpers/significant_change.py | 92 +++++++++++++------ tests/components/alexa/test_state_report.py | 25 ++++- .../google_assistant/test_report_state.py | 18 ++++ tests/helpers/test_significant_change.py | 30 +++++- 6 files changed, 190 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index aa4110ea68698c..d66906810b2ef6 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -8,7 +8,7 @@ import async_timeout from homeassistant.const import HTTP_ACCEPTED, MATCH_ALL, STATE_ON -from homeassistant.core import State +from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers.significant_change import create_checker import homeassistant.util.dt as dt_util @@ -28,7 +28,20 @@ async def async_enable_proactive_mode(hass, smart_home_config): # Validate we can get access token. await smart_home_config.async_get_access_token() - checker = await create_checker(hass, DOMAIN) + @callback + def extra_significant_check( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + old_extra_arg: dict, + new_state: str, + new_attrs: dict, + new_extra_arg: dict, + ): + """Check if the serialized data has changed.""" + return old_extra_arg is not None and old_extra_arg != new_extra_arg + + checker = await create_checker(hass, DOMAIN, extra_significant_check) async def async_entity_state_listener( changed_entity: str, @@ -70,15 +83,22 @@ async def async_entity_state_listener( if not should_report and not should_doorbell: return - if not checker.async_is_significant_change(new_state): - return - if should_doorbell: should_report = False + if should_report: + alexa_properties = list(alexa_changed_entity.serialize_properties()) + else: + alexa_properties = None + + if not checker.async_is_significant_change( + new_state, extra_arg=alexa_properties + ): + return + if should_report: await async_send_changereport_message( - hass, smart_home_config, alexa_changed_entity + hass, smart_home_config, alexa_changed_entity, alexa_properties ) elif should_doorbell: @@ -92,7 +112,7 @@ async def async_entity_state_listener( async def async_send_changereport_message( - hass, config, alexa_entity, *, invalidate_access_token=True + hass, config, alexa_entity, alexa_properties, *, invalidate_access_token=True ): """Send a ChangeReport message for an Alexa entity. @@ -107,7 +127,7 @@ async def async_send_changereport_message( payload = { API_CHANGE: { "cause": {"type": Cause.APP_INTERACTION}, - "properties": list(alexa_entity.serialize_properties()), + "properties": alexa_properties, } } @@ -146,7 +166,7 @@ async def async_send_changereport_message( ): config.async_invalidate_access_token() return await async_send_changereport_message( - hass, config, alexa_entity, invalidate_access_token=False + hass, config, alexa_entity, alexa_properties, invalidate_access_token=False ) _LOGGER.error( diff --git a/homeassistant/components/google_assistant/report_state.py b/homeassistant/components/google_assistant/report_state.py index 8943d4d211e70e..cdfb06c5c39631 100644 --- a/homeassistant/components/google_assistant/report_state.py +++ b/homeassistant/components/google_assistant/report_state.py @@ -38,41 +38,58 @@ async def async_entity_state_listener(changed_entity, old_state, new_state): if not entity.is_supported(): return - if not checker.async_is_significant_change(new_state): - return - try: entity_data = entity.query_serialize() except SmartHomeError as err: _LOGGER.debug("Not reporting state for %s: %s", changed_entity, err.code) return + if not checker.async_is_significant_change(new_state, extra_arg=entity_data): + return + _LOGGER.debug("Reporting state for %s: %s", changed_entity, entity_data) await google_config.async_report_state_all( {"devices": {"states": {changed_entity: entity_data}}} ) + @callback + def extra_significant_check( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + old_extra_arg: dict, + new_state: str, + new_attrs: dict, + new_extra_arg: dict, + ): + """Check if the serialized data has changed.""" + return old_extra_arg != new_extra_arg + async def inital_report(_now): """Report initially all states.""" nonlocal unsub, checker entities = {} - checker = await create_checker(hass, DOMAIN) + checker = await create_checker(hass, DOMAIN, extra_significant_check) for entity in async_get_entities(hass, google_config): if not entity.should_expose(): continue + try: + entity_data = entity.query_serialize() + except SmartHomeError: + continue + # Tell our significant change checker that we're reporting # So it knows with subsequent changes what was already reported. - if not checker.async_is_significant_change(entity.state): + if not checker.async_is_significant_change( + entity.state, extra_arg=entity_data + ): continue - try: - entities[entity.entity_id] = entity.query_serialize() - except SmartHomeError: - continue + entities[entity.entity_id] = entity_data if not entities: return diff --git a/homeassistant/helpers/significant_change.py b/homeassistant/helpers/significant_change.py index 0a5b6aae10d24c..694acfcf2bd759 100644 --- a/homeassistant/helpers/significant_change.py +++ b/homeassistant/helpers/significant_change.py @@ -27,7 +27,7 @@ async def async_check_significant_change( - state adding/removing """ from types import MappingProxyType -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, Dict, Optional, Tuple, Union from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State, callback @@ -47,13 +47,28 @@ async def async_check_significant_change( Optional[bool], ] +ExtraCheckTypeFunc = Callable[ + [ + HomeAssistant, + str, + Union[dict, MappingProxyType], + Any, + str, + Union[dict, MappingProxyType], + Any, + ], + Optional[bool], +] + async def create_checker( - hass: HomeAssistant, _domain: str + hass: HomeAssistant, + _domain: str, + extra_significant_check: Optional[ExtraCheckTypeFunc] = None, ) -> "SignificantlyChangedChecker": """Create a significantly changed checker for a domain.""" await _initialize(hass) - return SignificantlyChangedChecker(hass) + return SignificantlyChangedChecker(hass, extra_significant_check) # Marked as singleton so multiple calls all wait for same output. @@ -105,34 +120,46 @@ class SignificantlyChangedChecker: Will always compare the entity to the last entity that was considered significant. """ - def __init__(self, hass: HomeAssistant) -> None: + def __init__( + self, + hass: HomeAssistant, + extra_significant_check: Optional[ExtraCheckTypeFunc] = None, + ) -> None: """Test if an entity has significantly changed.""" self.hass = hass - self.last_approved_entities: Dict[str, State] = {} + self.last_approved_entities: Dict[str, Tuple[State, Any]] = {} + self.extra_significant_check = extra_significant_check @callback - def async_is_significant_change(self, new_state: State) -> bool: - """Return if this was a significant change.""" - old_state: Optional[State] = self.last_approved_entities.get( + def async_is_significant_change( + self, new_state: State, *, extra_arg: Optional[Any] = None + ) -> bool: + """Return if this was a significant change. + + Extra kwargs are passed to the extra significant checker. + """ + old_data: Optional[Tuple[State, Any]] = self.last_approved_entities.get( new_state.entity_id ) # First state change is always ok to report - if old_state is None: - self.last_approved_entities[new_state.entity_id] = new_state + if old_data is None: + self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True + old_state, old_extra_arg = old_data + # Handle state unknown or unavailable if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): if new_state.state == old_state.state: return False - self.last_approved_entities[new_state.entity_id] = new_state + self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True # If last state was unknown/unavailable, also significant. if old_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): - self.last_approved_entities[new_state.entity_id] = new_state + self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True functions: Optional[Dict[str, CheckTypeFunc]] = self.hass.data.get( @@ -144,23 +171,36 @@ def async_is_significant_change(self, new_state: State) -> bool: check_significantly_changed = functions.get(new_state.domain) - # No platform available means always true. - if check_significantly_changed is None: - self.last_approved_entities[new_state.entity_id] = new_state - return True + if check_significantly_changed is not None: + result = check_significantly_changed( + self.hass, + old_state.state, + old_state.attributes, + new_state.state, + new_state.attributes, + ) - result = check_significantly_changed( - self.hass, - old_state.state, - old_state.attributes, - new_state.state, - new_state.attributes, - ) + if result is False: + return False - if result is False: - return False + if self.extra_significant_check is not None: + result = self.extra_significant_check( + self.hass, + old_state.state, + old_state.attributes, + old_extra_arg, + new_state.state, + new_state.attributes, + extra_arg, + ) + + if result is False: + return False # Result is either True or None. # None means the function doesn't know. For now assume it's True - self.last_approved_entities[new_state.entity_id] = new_state + self.last_approved_entities[new_state.entity_id] = ( + new_state, + extra_arg, + ) return True diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index 809bca5638b263..a057eada531e42 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -178,6 +178,7 @@ async def test_doorbell_event(hass, aioclient_mock): async def test_proactive_mode_filter_states(hass, aioclient_mock): """Test all the cases that filter states.""" + aioclient_mock.post(TEST_URL, text="", status=202) await state_report.async_enable_proactive_mode(hass, DEFAULT_CONFIG) # First state should report @@ -186,7 +187,8 @@ async def test_proactive_mode_filter_states(hass, aioclient_mock): "on", {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - assert len(aioclient_mock.mock_calls) == 0 + await hass.async_block_till_done() + assert len(aioclient_mock.mock_calls) == 1 aioclient_mock.clear_requests() @@ -238,3 +240,24 @@ async def test_proactive_mode_filter_states(hass, aioclient_mock): await hass.async_block_till_done() await hass.async_block_till_done() assert len(aioclient_mock.mock_calls) == 0 + + # If serializes to same properties, it should not report + aioclient_mock.post(TEST_URL, text="", status=202) + with patch( + "homeassistant.components.alexa.entities.AlexaEntity.serialize_properties", + return_value=[{"same": "info"}], + ): + hass.states.async_set( + "binary_sensor.same_serialize", + "off", + {"friendly_name": "Test Contact Sensor", "device_class": "door"}, + ) + await hass.async_block_till_done() + hass.states.async_set( + "binary_sensor.same_serialize", + "off", + {"friendly_name": "Test Contact Sensor", "device_class": "door"}, + ) + + await hass.async_block_till_done() + assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index 72130dbfdb9848..f464be60bb93cd 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -46,6 +46,24 @@ async def test_report_state(hass, caplog, legacy_patchable_time): "devices": {"states": {"light.kitchen": {"on": True, "online": True}}} } + # Test that if serialize returns same value, we don't send + with patch( + "homeassistant.components.google_assistant.report_state.GoogleEntity.query_serialize", + return_value={"same": "info"}, + ), patch.object(BASIC_CONFIG, "async_report_state_all", AsyncMock()) as mock_report: + # New state, so reported + hass.states.async_set("light.double_report", "on") + await hass.async_block_till_done() + + # Changed, but serialize is same, so filtered out by extra check + hass.states.async_set("light.double_report", "off") + await hass.async_block_till_done() + + assert len(mock_report.mock_calls) == 1 + assert mock_report.mock_calls[0][1][0] == { + "devices": {"states": {"light.double_report": {"same": "info"}}} + } + # Test that only significant state changes are reported with patch.object( BASIC_CONFIG, "async_report_state_all", AsyncMock() diff --git a/tests/helpers/test_significant_change.py b/tests/helpers/test_significant_change.py index e72951d36dd2f9..79f3dd3fe3e4bd 100644 --- a/tests/helpers/test_significant_change.py +++ b/tests/helpers/test_significant_change.py @@ -5,7 +5,6 @@ from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import State from homeassistant.helpers import significant_change -from homeassistant.setup import async_setup_component @pytest.fixture(name="checker") @@ -26,8 +25,6 @@ def async_check_significant_change( async def test_signicant_change(hass, checker): """Test initialize helper works.""" - assert await async_setup_component(hass, "sensor", {}) - ent_id = "test_domain.test_entity" attrs = {ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY} @@ -48,3 +45,30 @@ async def test_signicant_change(hass, checker): # State turned unavailable assert checker.async_is_significant_change(State(ent_id, "100", attrs)) assert checker.async_is_significant_change(State(ent_id, STATE_UNAVAILABLE, attrs)) + + +async def test_significant_change_extra(hass, checker): + """Test extra significant checker works.""" + ent_id = "test_domain.test_entity" + attrs = {ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY} + + assert checker.async_is_significant_change(State(ent_id, "100", attrs), extra_arg=1) + assert checker.async_is_significant_change(State(ent_id, "200", attrs), extra_arg=1) + + # Reset the last significiant change to 100 to repeat test but with + # extra checker installed. + assert checker.async_is_significant_change(State(ent_id, "100", attrs), extra_arg=1) + + def extra_significant_check( + hass, old_state, old_attrs, old_extra_arg, new_state, new_attrs, new_extra_arg + ): + return old_extra_arg != new_extra_arg + + checker.extra_significant_check = extra_significant_check + + # This is normally a significant change (100 -> 200), but the extra arg check marks it + # as insignificant. + assert not checker.async_is_significant_change( + State(ent_id, "200", attrs), extra_arg=1 + ) + assert checker.async_is_significant_change(State(ent_id, "200", attrs), extra_arg=2) From cdd78316c440750c7dc4423244f4bd81d6027151 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Wed, 10 Feb 2021 10:01:24 -0800 Subject: [PATCH 0366/1818] Use oauthv3 for Tesla (#45766) --- homeassistant/components/tesla/__init__.py | 4 ++++ homeassistant/components/tesla/config_flow.py | 2 ++ homeassistant/components/tesla/manifest.json | 8 ++++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tesla/test_config_flow.py | 2 ++ 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index 51090d342718a1..8981b269a56d9e 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -103,6 +103,8 @@ def _update_entry(email, data=None, options=None): _update_entry( email, data={ + CONF_USERNAME: email, + CONF_PASSWORD: password, CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN], CONF_TOKEN: info[CONF_TOKEN], }, @@ -136,6 +138,8 @@ async def async_setup_entry(hass, config_entry): try: controller = TeslaAPI( websession, + email=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), refresh_token=config[CONF_TOKEN], access_token=config[CONF_ACCESS_TOKEN], update_interval=config_entry.options.get( diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py index debe896c9cf682..683ef314a06426 100644 --- a/homeassistant/components/tesla/config_flow.py +++ b/homeassistant/components/tesla/config_flow.py @@ -140,6 +140,8 @@ async def validate_input(hass: core.HomeAssistant, data): (config[CONF_TOKEN], config[CONF_ACCESS_TOKEN]) = await controller.connect( test_login=True ) + config[CONF_USERNAME] = data[CONF_USERNAME] + config[CONF_PASSWORD] = data[CONF_PASSWORD] except TeslaException as ex: if ex.code == HTTP_UNAUTHORIZED: _LOGGER.error("Invalid credentials: %s", ex) diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index 3679c0f74d1012..9236aae7fb6eea 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -3,11 +3,11 @@ "name": "Tesla", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", - "requirements": ["teslajsonpy==0.10.4"], + "requirements": ["teslajsonpy==0.11.5"], "codeowners": ["@zabuldon", "@alandtse"], "dhcp": [ - {"hostname":"tesla_*","macaddress":"4CFCAA*"}, - {"hostname":"tesla_*","macaddress":"044EAF*"}, - {"hostname":"tesla_*","macaddress":"98ED5C*"} + { "hostname": "tesla_*", "macaddress": "4CFCAA*" }, + { "hostname": "tesla_*", "macaddress": "044EAF*" }, + { "hostname": "tesla_*", "macaddress": "98ED5C*" } ] } diff --git a/requirements_all.txt b/requirements_all.txt index 202049341e5b66..d0959d87e8fe35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2184,7 +2184,7 @@ temperusb==1.5.3 tesla-powerwall==0.3.3 # homeassistant.components.tesla -teslajsonpy==0.10.4 +teslajsonpy==0.11.5 # homeassistant.components.tensorflow # tf-models-official==2.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 143ec43a5d4466..ec4bce771f6f91 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1105,7 +1105,7 @@ tellduslive==0.10.11 tesla-powerwall==0.3.3 # homeassistant.components.tesla -teslajsonpy==0.10.4 +teslajsonpy==0.11.5 # homeassistant.components.toon toonapi==0.2.0 diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index 7fb308ecc43ef4..136633c9a5cea0 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -48,6 +48,8 @@ async def test_form(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "test@email.com" assert result2["data"] == { + CONF_USERNAME: "test@email.com", + CONF_PASSWORD: "test", CONF_TOKEN: "test-refresh-token", CONF_ACCESS_TOKEN: "test-access-token", } From 2db102e0239ae937b58c0e68b36260a50da78835 Mon Sep 17 00:00:00 2001 From: Leonardo Figueiro Date: Wed, 10 Feb 2021 16:08:39 -0300 Subject: [PATCH 0367/1818] Add WiLight Cover (#46065) Co-authored-by: J. Nick Koston --- homeassistant/components/wilight/__init__.py | 5 +- .../components/wilight/config_flow.py | 4 +- homeassistant/components/wilight/const.py | 14 -- homeassistant/components/wilight/cover.py | 105 ++++++++++++++ homeassistant/components/wilight/light.py | 17 +-- .../components/wilight/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wilight/__init__.py | 13 +- tests/components/wilight/test_config_flow.py | 2 +- tests/components/wilight/test_cover.py | 136 ++++++++++++++++++ tests/components/wilight/test_init.py | 2 +- 12 files changed, 264 insertions(+), 40 deletions(-) delete mode 100644 homeassistant/components/wilight/const.py create mode 100644 homeassistant/components/wilight/cover.py create mode 100644 tests/components/wilight/test_cover.py diff --git a/homeassistant/components/wilight/__init__.py b/homeassistant/components/wilight/__init__.py index 97b482571031bc..0e08fec2c31e45 100644 --- a/homeassistant/components/wilight/__init__.py +++ b/homeassistant/components/wilight/__init__.py @@ -1,16 +1,17 @@ """The WiLight integration.""" import asyncio +from pywilight.const import DOMAIN + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import Entity -from .const import DOMAIN from .parent_device import WiLightParent # List the platforms that you want to support. -PLATFORMS = ["fan", "light"] +PLATFORMS = ["cover", "fan", "light"] async def async_setup(hass: HomeAssistant, config: dict): diff --git a/homeassistant/components/wilight/config_flow.py b/homeassistant/components/wilight/config_flow.py index 3f1b12395ba898..c3148a4d04578d 100644 --- a/homeassistant/components/wilight/config_flow.py +++ b/homeassistant/components/wilight/config_flow.py @@ -7,7 +7,7 @@ from homeassistant.config_entries import CONN_CLASS_LOCAL_PUSH, ConfigFlow from homeassistant.const import CONF_HOST -from .const import DOMAIN # pylint: disable=unused-import +DOMAIN = "wilight" CONF_SERIAL_NUMBER = "serial_number" CONF_MODEL_NAME = "model_name" @@ -15,7 +15,7 @@ WILIGHT_MANUFACTURER = "All Automacao Ltda" # List the components supported by this integration. -ALLOWED_WILIGHT_COMPONENTS = ["light", "fan"] +ALLOWED_WILIGHT_COMPONENTS = ["cover", "fan", "light"] class WiLightFlowHandler(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/wilight/const.py b/homeassistant/components/wilight/const.py deleted file mode 100644 index a3d77da44efccd..00000000000000 --- a/homeassistant/components/wilight/const.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Constants for the WiLight integration.""" - -DOMAIN = "wilight" - -# Item types -ITEM_LIGHT = "light" - -# Light types -LIGHT_ON_OFF = "light_on_off" -LIGHT_DIMMER = "light_dimmer" -LIGHT_COLOR = "light_rgb" - -# Light service support -SUPPORT_NONE = 0 diff --git a/homeassistant/components/wilight/cover.py b/homeassistant/components/wilight/cover.py new file mode 100644 index 00000000000000..bbe723b413a372 --- /dev/null +++ b/homeassistant/components/wilight/cover.py @@ -0,0 +1,105 @@ +"""Support for WiLight Cover.""" + +from pywilight.const import ( + COVER_V1, + DOMAIN, + ITEM_COVER, + WL_CLOSE, + WL_CLOSING, + WL_OPEN, + WL_OPENING, + WL_STOP, + WL_STOPPED, +) + +from homeassistant.components.cover import ATTR_POSITION, CoverEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import WiLightDevice + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities +): + """Set up WiLight covers from a config entry.""" + parent = hass.data[DOMAIN][entry.entry_id] + + # Handle a discovered WiLight device. + entities = [] + for item in parent.api.items: + if item["type"] != ITEM_COVER: + continue + index = item["index"] + item_name = item["name"] + if item["sub_type"] != COVER_V1: + continue + entity = WiLightCover(parent.api, index, item_name) + entities.append(entity) + + async_add_entities(entities) + + +def wilight_to_hass_position(value): + """Convert wilight position 1..255 to hass format 0..100.""" + return min(100, round((value * 100) / 255)) + + +def hass_to_wilight_position(value): + """Convert hass position 0..100 to wilight 1..255 scale.""" + return min(255, round((value * 255) / 100)) + + +class WiLightCover(WiLightDevice, CoverEntity): + """Representation of a WiLights cover.""" + + @property + def current_cover_position(self): + """Return current position of cover. + + None is unknown, 0 is closed, 100 is fully open. + """ + if "position_current" in self._status: + return wilight_to_hass_position(self._status["position_current"]) + return None + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + if "motor_state" not in self._status: + return None + return self._status["motor_state"] == WL_OPENING + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + if "motor_state" not in self._status: + return None + return self._status["motor_state"] == WL_CLOSING + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + if "motor_state" not in self._status or "position_current" not in self._status: + return None + return ( + self._status["motor_state"] == WL_STOPPED + and wilight_to_hass_position(self._status["position_current"]) == 0 + ) + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + await self._client.cover_command(self._index, WL_OPEN) + + async def async_close_cover(self, **kwargs): + """Close cover.""" + await self._client.cover_command(self._index, WL_CLOSE) + + async def async_set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + position = hass_to_wilight_position(kwargs[ATTR_POSITION]) + await self._client.set_cover_position(self._index, position) + + async def async_stop_cover(self, **kwargs): + """Stop the cover.""" + await self._client.cover_command(self._index, WL_STOP) diff --git a/homeassistant/components/wilight/light.py b/homeassistant/components/wilight/light.py index e4bf504165daf8..2f3c9e3c5f22cd 100644 --- a/homeassistant/components/wilight/light.py +++ b/homeassistant/components/wilight/light.py @@ -1,5 +1,14 @@ """Support for WiLight lights.""" +from pywilight.const import ( + DOMAIN, + ITEM_LIGHT, + LIGHT_COLOR, + LIGHT_DIMMER, + LIGHT_ON_OFF, + SUPPORT_NONE, +) + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, @@ -11,14 +20,6 @@ from homeassistant.core import HomeAssistant from . import WiLightDevice -from .const import ( - DOMAIN, - ITEM_LIGHT, - LIGHT_COLOR, - LIGHT_DIMMER, - LIGHT_ON_OFF, - SUPPORT_NONE, -) def entities_from_discovered_wilight(hass, api_device): diff --git a/homeassistant/components/wilight/manifest.json b/homeassistant/components/wilight/manifest.json index c9f4fb049fce45..5b8a93c60392f2 100644 --- a/homeassistant/components/wilight/manifest.json +++ b/homeassistant/components/wilight/manifest.json @@ -3,7 +3,7 @@ "name": "WiLight", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wilight", - "requirements": ["pywilight==0.0.66"], + "requirements": ["pywilight==0.0.68"], "ssdp": [ { "manufacturer": "All Automacao Ltda" diff --git a/requirements_all.txt b/requirements_all.txt index d0959d87e8fe35..4c890be38df2d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1901,7 +1901,7 @@ pywebpush==1.9.2 pywemo==0.6.1 # homeassistant.components.wilight -pywilight==0.0.66 +pywilight==0.0.68 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ec4bce771f6f91..4f7c0344da714e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -971,7 +971,7 @@ pywebpush==1.9.2 pywemo==0.6.1 # homeassistant.components.wilight -pywilight==0.0.66 +pywilight==0.0.68 # homeassistant.components.zerproc pyzerproc==0.4.7 diff --git a/tests/components/wilight/__init__.py b/tests/components/wilight/__init__.py index e1c3134523598e..7ee7f0119a460c 100644 --- a/tests/components/wilight/__init__.py +++ b/tests/components/wilight/__init__.py @@ -1,4 +1,7 @@ """Tests for the WiLight component.""" + +from pywilight.const import DOMAIN + from homeassistant.components.ssdp import ( ATTR_SSDP_LOCATION, ATTR_UPNP_MANUFACTURER, @@ -10,7 +13,6 @@ CONF_MODEL_NAME, CONF_SERIAL_NUMBER, ) -from homeassistant.components.wilight.const import DOMAIN from homeassistant.const import CONF_HOST from homeassistant.helpers.typing import HomeAssistantType @@ -24,6 +26,7 @@ UPNP_MODEL_NAME_DIMMER = "WiLight 0100001700020009-10010010" UPNP_MODEL_NAME_COLOR = "WiLight 0107001800020009-11010" UPNP_MODEL_NAME_LIGHT_FAN = "WiLight 0104001800010009-10" +UPNP_MODEL_NAME_COVER = "WiLight 0103001800010009-10" UPNP_MODEL_NUMBER = "123456789012345678901234567890123456" UPNP_SERIAL = "000000000099" UPNP_MAC_ADDRESS = "5C:CF:7F:8B:CA:56" @@ -53,14 +56,6 @@ ATTR_UPNP_SERIAL: ATTR_UPNP_SERIAL, } -MOCK_SSDP_DISCOVERY_INFO_LIGHT_FAN = { - ATTR_SSDP_LOCATION: SSDP_LOCATION, - ATTR_UPNP_MANUFACTURER: UPNP_MANUFACTURER, - ATTR_UPNP_MODEL_NAME: UPNP_MODEL_NAME_LIGHT_FAN, - ATTR_UPNP_MODEL_NUMBER: UPNP_MODEL_NUMBER, - ATTR_UPNP_SERIAL: ATTR_UPNP_SERIAL, -} - async def setup_integration( hass: HomeAssistantType, diff --git a/tests/components/wilight/test_config_flow.py b/tests/components/wilight/test_config_flow.py index d44780092ec28d..9888dbe3ef945f 100644 --- a/tests/components/wilight/test_config_flow.py +++ b/tests/components/wilight/test_config_flow.py @@ -2,12 +2,12 @@ from unittest.mock import patch import pytest +from pywilight.const import DOMAIN from homeassistant.components.wilight.config_flow import ( CONF_MODEL_NAME, CONF_SERIAL_NUMBER, ) -from homeassistant.components.wilight.const import DOMAIN from homeassistant.config_entries import SOURCE_SSDP from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE from homeassistant.data_entry_flow import ( diff --git a/tests/components/wilight/test_cover.py b/tests/components/wilight/test_cover.py new file mode 100644 index 00000000000000..85f62c9d120a48 --- /dev/null +++ b/tests/components/wilight/test_cover.py @@ -0,0 +1,136 @@ +"""Tests for the WiLight integration.""" +from unittest.mock import patch + +import pytest +import pywilight + +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, + ATTR_POSITION, + DOMAIN as COVER_DOMAIN, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, +) +from homeassistant.helpers.typing import HomeAssistantType + +from . import ( + HOST, + UPNP_MAC_ADDRESS, + UPNP_MODEL_NAME_COVER, + UPNP_MODEL_NUMBER, + UPNP_SERIAL, + WILIGHT_ID, + setup_integration, +) + + +@pytest.fixture(name="dummy_device_from_host_cover") +def mock_dummy_device_from_host_light_fan(): + """Mock a valid api_devce.""" + + device = pywilight.wilight_from_discovery( + f"http://{HOST}:45995/wilight.xml", + UPNP_MAC_ADDRESS, + UPNP_MODEL_NAME_COVER, + UPNP_SERIAL, + UPNP_MODEL_NUMBER, + ) + + device.set_dummy(True) + + with patch( + "pywilight.device_from_host", + return_value=device, + ): + yield device + + +async def test_loading_cover( + hass: HomeAssistantType, + dummy_device_from_host_cover, +) -> None: + """Test the WiLight configuration entry loading.""" + + entry = await setup_integration(hass) + assert entry + assert entry.unique_id == WILIGHT_ID + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + # First segment of the strip + state = hass.states.get("cover.wl000000000099_1") + assert state + assert state.state == STATE_CLOSED + + entry = entity_registry.async_get("cover.wl000000000099_1") + assert entry + assert entry.unique_id == "WL000000000099_0" + + +async def test_open_close_cover_state( + hass: HomeAssistantType, dummy_device_from_host_cover +) -> None: + """Test the change of state of the cover.""" + await setup_integration(hass) + + # Open + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.wl000000000099_1"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("cover.wl000000000099_1") + assert state + assert state.state == STATE_OPENING + + # Close + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.wl000000000099_1"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("cover.wl000000000099_1") + assert state + assert state.state == STATE_CLOSING + + # Set position + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_POSITION: 50, ATTR_ENTITY_ID: "cover.wl000000000099_1"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("cover.wl000000000099_1") + assert state + assert state.state == STATE_OPEN + assert state.attributes.get(ATTR_CURRENT_POSITION) == 50 + + # Stop + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER, + {ATTR_ENTITY_ID: "cover.wl000000000099_1"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("cover.wl000000000099_1") + assert state + assert state.state == STATE_OPEN diff --git a/tests/components/wilight/test_init.py b/tests/components/wilight/test_init.py index c1557fb44d3438..1441564b640d49 100644 --- a/tests/components/wilight/test_init.py +++ b/tests/components/wilight/test_init.py @@ -3,8 +3,8 @@ import pytest import pywilight +from pywilight.const import DOMAIN -from homeassistant.components.wilight.const import DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED, From fc4fc487635373e14fc79856092b2766780035df Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Feb 2021 20:42:34 +0100 Subject: [PATCH 0368/1818] Bump hatasmota to 0.2.8 (#46340) --- homeassistant/components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index bd48cae8e5944a..12604d3ed81a02 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.2.7"], + "requirements": ["hatasmota==0.2.8"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 4c890be38df2d7..10f81b8813dc2e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -735,7 +735,7 @@ hass-nabucasa==0.41.0 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.2.7 +hatasmota==0.2.8 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f7c0344da714e..a9f2dab232db33 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -387,7 +387,7 @@ hangups==0.4.11 hass-nabucasa==0.41.0 # homeassistant.components.tasmota -hatasmota==0.2.7 +hatasmota==0.2.8 # homeassistant.components.jewish_calendar hdate==0.9.12 From 2e2eab662b5d3780a17647c4644b4519bd67a633 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 09:48:15 -1000 Subject: [PATCH 0369/1818] Fix Lutron Integration Protocol reconnect logic (#46264) --- 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 34ab75dc0cd764..88c6eddd0bf294 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -3,7 +3,7 @@ "name": "Lutron Caséta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta", "requirements": [ - "pylutron-caseta==0.9.0", "aiolip==1.0.1" + "pylutron-caseta==0.9.0", "aiolip==1.1.4" ], "config_flow": true, "zeroconf": ["_leap._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index 10f81b8813dc2e..8a3d5e2b5c8f5d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiolifx==0.6.9 aiolifx_effects==0.2.2 # homeassistant.components.lutron_caseta -aiolip==1.0.1 +aiolip==1.1.4 # homeassistant.components.lyric aiolyric==1.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a9f2dab232db33..d55d406cdb088b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -113,7 +113,7 @@ aiohue==2.1.0 aiokafka==0.6.0 # homeassistant.components.lutron_caseta -aiolip==1.0.1 +aiolip==1.1.4 # homeassistant.components.lyric aiolyric==1.0.5 From 884df409517bcc126bc8d3937493c69e6c454a5c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 09:50:38 -1000 Subject: [PATCH 0370/1818] Update powerwall for new authentication requirements (#46254) Co-authored-by: badguy99 <61918526+badguy99@users.noreply.github.com> --- .../components/powerwall/__init__.py | 82 ++++++++++++---- .../components/powerwall/config_flow.py | 58 ++++++++---- .../components/powerwall/manifest.json | 2 +- .../components/powerwall/strings.json | 10 +- .../components/powerwall/translations/en.json | 10 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/powerwall/test_config_flow.py | 94 ++++++++++++++++--- 8 files changed, 201 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 54b7310b7adb32..b392b71374142d 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -4,10 +4,15 @@ import logging import requests -from tesla_powerwall import MissingAttributeError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import ( + AccessDeniedError, + MissingAttributeError, + Powerwall, + PowerwallUnreachableError, +) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry @@ -93,11 +98,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN].setdefault(entry_id, {}) http_session = requests.Session() + + password = entry.data.get(CONF_PASSWORD) power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session) try: - await hass.async_add_executor_job(power_wall.detect_and_pin_version) - await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) - powerwall_data = await hass.async_add_executor_job(call_base_info, power_wall) + powerwall_data = await hass.async_add_executor_job( + _login_and_fetch_base_info, power_wall, password + ) except PowerwallUnreachableError as err: http_session.close() raise ConfigEntryNotReady from err @@ -105,6 +112,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): http_session.close() await _async_handle_api_changed_error(hass, err) return False + except AccessDeniedError as err: + _LOGGER.debug("Authentication failed", exc_info=err) + http_session.close() + _async_start_reauth(hass, entry) + return False await _migrate_old_unique_ids(hass, entry_id, powerwall_data) @@ -112,22 +124,20 @@ async def async_update_data(): """Fetch data from API endpoint.""" # Check if we had an error before _LOGGER.debug("Checking if update failed") - if not hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: - _LOGGER.debug("Updating 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 - else: + if hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data + _LOGGER.debug("Updating data") + try: + return await _async_update_powerwall_data(hass, entry, power_wall) + except AccessDeniedError: + if password is None: + raise + + # If the session expired, relogin, and try again + await hass.async_add_executor_job(power_wall.login, "", password) + return await _async_update_powerwall_data(hass, entry, power_wall) + coordinator = DataUpdateCoordinator( hass, _LOGGER, @@ -156,6 +166,40 @@ async def async_update_data(): 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 _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "reauth"}, + data=entry.data, + ) + ) + _LOGGER.error("Password is no longer valid. Please reauthenticate") + + +def _login_and_fetch_base_info(power_wall: Powerwall, password: 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 call_base_info(power_wall) + + def call_base_info(power_wall): """Wrap powerwall properties to be a callable.""" serial_numbers = power_wall.get_serial_numbers() diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 37ee2730bb49d1..b649b16008535f 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,12 +1,17 @@ """Config flow for Tesla Powerwall integration.""" import logging -from tesla_powerwall import MissingAttributeError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import ( + AccessDeniedError, + MissingAttributeError, + Powerwall, + PowerwallUnreachableError, +) import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.components.dhcp import IP_ADDRESS -from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.core import callback from .const import DOMAIN # pylint:disable=unused-import @@ -14,6 +19,14 @@ _LOGGER = logging.getLogger(__name__) +def _login_and_fetch_site_info(power_wall: Powerwall, password: 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() + + async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. @@ -21,12 +34,12 @@ async def validate_input(hass: core.HomeAssistant, data): """ power_wall = Powerwall(data[CONF_IP_ADDRESS]) + password = data[CONF_PASSWORD] try: - await hass.async_add_executor_job(power_wall.detect_and_pin_version) - site_info = await hass.async_add_executor_job(power_wall.get_site_info) - except PowerwallUnreachableError as err: - raise CannotConnect from err + site_info = await hass.async_add_executor_job( + _login_and_fetch_site_info, power_wall, password + ) except MissingAttributeError as err: # Only log the exception without the traceback _LOGGER.error(str(err)) @@ -62,27 +75,44 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: info = await validate_input(self.hass, user_input) - except CannotConnect: - errors["base"] = "cannot_connect" + 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" - if "base" not in errors: - await self.async_set_unique_id(user_input[CONF_IP_ADDRESS]) - self._abort_if_unique_id_configured() + 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 + ) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") return self.async_create_entry(title=info["title"], data=user_input) return self.async_show_form( step_id="user", data_schema=vol.Schema( - {vol.Required(CONF_IP_ADDRESS, default=self.ip_address): str} + { + vol.Required(CONF_IP_ADDRESS, default=self.ip_address): str, + 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() + @callback def _async_ip_address_already_configured(self, ip_address): """See if we already have an entry matching the ip_address.""" @@ -92,9 +122,5 @@ def _async_ip_address_already_configured(self, ip_address): return False -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" - - class WrongVersion(exceptions.HomeAssistantError): """Error to indicate the powerwall uses a software version we cannot interact with.""" diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 6b7b147d3c5d35..40d0a6c50fe117 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.3"], + "requirements": ["tesla-powerwall==0.3.5"], "codeowners": ["@bdraco", "@jrester"], "dhcp": [ {"hostname":"1118431-*","macaddress":"88DA1A*"}, diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index ac0d9568154166..c576d931756b98 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -4,18 +4,22 @@ "step": { "user": { "title": "Connect to the powerwall", + "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Telsa app; or the last 5 characters of the password found inside the door for Backup Gateway 2.", "data": { - "ip_address": "[%key:common::config_flow::data::ip%]" + "ip_address": "[%key:common::config_flow::data::ip%]", + "password": "[%key:common::config_flow::data::password%]" } } }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "[%key:common::config_flow::error::unknown%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "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/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 6eb0b77708da4c..4ebe1e9d5efaa7 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/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", + "invalid_auth": "Invalid authentication", "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." }, @@ -12,10 +14,12 @@ "step": { "user": { "data": { - "ip_address": "IP Address" + "ip_address": "IP Address", + "password": "Password" }, + "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Telsa app; or the last 5 characters of the password found inside the door for Backup Gateway 2.", "title": "Connect to the powerwall" } } } -} \ No newline at end of file +} diff --git a/requirements_all.txt b/requirements_all.txt index 8a3d5e2b5c8f5d..ee86d62270abfd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2181,7 +2181,7 @@ temperusb==1.5.3 # tensorflow==2.3.0 # homeassistant.components.powerwall -tesla-powerwall==0.3.3 +tesla-powerwall==0.3.5 # homeassistant.components.tesla teslajsonpy==0.11.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d55d406cdb088b..5d63ba30e1cd9c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1102,7 +1102,7 @@ synologydsm-api==1.0.1 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.3.3 +tesla-powerwall==0.3.5 # homeassistant.components.tesla teslajsonpy==0.11.5 diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index 0955c16c9ec29f..be071b4594779a 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -2,17 +2,23 @@ from unittest.mock import patch -from tesla_powerwall import MissingAttributeError, PowerwallUnreachableError +from tesla_powerwall import ( + AccessDeniedError, + MissingAttributeError, + PowerwallUnreachableError, +) from homeassistant import config_entries, setup from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS, MAC_ADDRESS from homeassistant.components.powerwall.const import DOMAIN -from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from .mocks import _mock_powerwall_side_effect, _mock_powerwall_site_name from tests.common import MockConfigEntry +VALID_CONFIG = {CONF_IP_ADDRESS: "1.2.3.4", CONF_PASSWORD: "00GGX"} + async def test_form_source_user(hass): """Test we get config flow setup form as a user.""" @@ -36,13 +42,13 @@ async def test_form_source_user(hass): ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + VALID_CONFIG, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "My site" - assert result2["data"] == {CONF_IP_ADDRESS: "1.2.3.4"} + assert result2["data"] == VALID_CONFIG assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -61,11 +67,32 @@ async def test_form_cannot_connect(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + VALID_CONFIG, ) assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} + assert result2["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + +async def test_invalid_auth(hass): + """Test we handle invalid auth error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_powerwall = _mock_powerwall_side_effect(site_info=AccessDeniedError("any")) + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + VALID_CONFIG, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_PASSWORD: "invalid_auth"} async def test_form_unknown_exeption(hass): @@ -81,8 +108,7 @@ async def test_form_unknown_exeption(hass): return_value=mock_powerwall, ): result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + result["flow_id"], VALID_CONFIG ) assert result2["type"] == "form" @@ -105,7 +131,7 @@ async def test_form_wrong_version(hass): ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + VALID_CONFIG, ) assert result3["type"] == "form" @@ -178,16 +204,54 @@ async def test_dhcp_discovery(hass): ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_IP_ADDRESS: "1.1.1.1", - }, + VALID_CONFIG, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Some site" - assert result2["data"] == { - CONF_IP_ADDRESS: "1.1.1.1", - } + assert result2["data"] == VALID_CONFIG + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_reauth(hass): + """Test reauthenticate.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data=VALID_CONFIG, + unique_id="1.2.3.4", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=entry.data + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_powerwall = await _mock_powerwall_site_name(hass, "My site") + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.async_setup", return_value=True + ) as mock_setup, 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"], + { + CONF_IP_ADDRESS: "1.2.3.4", + CONF_PASSWORD: "new-test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 From 67ab86443e39da1ecb74a56f9e9edf4d0c45fcc1 Mon Sep 17 00:00:00 2001 From: "J.P. Hutchins" <34154542+JPHutchins@users.noreply.github.com> Date: Wed, 10 Feb 2021 11:53:31 -0800 Subject: [PATCH 0371/1818] Revert transmission to check torrent lists by name rather than object (#46190) --- .../components/transmission/__init__.py | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index d020bfe97450f9..76d9aedd8d53d9 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -397,42 +397,49 @@ def init_torrent_list(self): def check_completed_torrent(self): """Get completed torrent functionality.""" + old_completed_torrent_names = { + torrent.name for torrent in self._completed_torrents + } + current_completed_torrents = [ torrent for torrent in self._torrents if torrent.status == "seeding" ] - freshly_completed_torrents = set(current_completed_torrents).difference( - self._completed_torrents - ) - self._completed_torrents = current_completed_torrents - for torrent in freshly_completed_torrents: - self.hass.bus.fire( - EVENT_DOWNLOADED_TORRENT, {"name": torrent.name, "id": torrent.id} - ) + for torrent in current_completed_torrents: + if torrent.name not in old_completed_torrent_names: + self.hass.bus.fire( + EVENT_DOWNLOADED_TORRENT, {"name": torrent.name, "id": torrent.id} + ) + + self._completed_torrents = current_completed_torrents def check_started_torrent(self): """Get started torrent functionality.""" + old_started_torrent_names = {torrent.name for torrent in self._started_torrents} + current_started_torrents = [ torrent for torrent in self._torrents if torrent.status == "downloading" ] - freshly_started_torrents = set(current_started_torrents).difference( - self._started_torrents - ) - self._started_torrents = current_started_torrents - for torrent in freshly_started_torrents: - self.hass.bus.fire( - EVENT_STARTED_TORRENT, {"name": torrent.name, "id": torrent.id} - ) + for torrent in current_started_torrents: + if torrent.name not in old_started_torrent_names: + self.hass.bus.fire( + EVENT_STARTED_TORRENT, {"name": torrent.name, "id": torrent.id} + ) + + self._started_torrents = current_started_torrents def check_removed_torrent(self): """Get removed torrent functionality.""" - freshly_removed_torrents = set(self._all_torrents).difference(self._torrents) - self._all_torrents = self._torrents - for torrent in freshly_removed_torrents: - self.hass.bus.fire( - EVENT_REMOVED_TORRENT, {"name": torrent.name, "id": torrent.id} - ) + current_torrent_names = {torrent.name for torrent in self._torrents} + + for torrent in self._all_torrents: + if torrent.name not in current_torrent_names: + self.hass.bus.fire( + EVENT_REMOVED_TORRENT, {"name": torrent.name, "id": torrent.id} + ) + + self._all_torrents = self._torrents.copy() def start_torrents(self): """Start all torrents.""" From c59b1c72c5616cd58fcffd9e5cd944dc3e44bfc1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 09:55:06 -1000 Subject: [PATCH 0372/1818] Add reauth support for tesla (#46307) --- homeassistant/components/tesla/__init__.py | 43 +++++-- homeassistant/components/tesla/config_flow.py | 105 ++++++++++-------- homeassistant/components/tesla/strings.json | 4 + .../components/tesla/translations/en.json | 4 + tests/components/tesla/test_config_flow.py | 39 ++++++- 5 files changed, 138 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index 8981b269a56d9e..b31f8ae6dd39df 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -5,10 +5,11 @@ import logging import async_timeout -from teslajsonpy import Controller as TeslaAPI, TeslaException +from teslajsonpy import Controller as TeslaAPI +from teslajsonpy.exceptions import IncompleteCredentials, TeslaException import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, @@ -17,8 +18,9 @@ CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_USERNAME, + HTTP_UNAUTHORIZED, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.update_coordinator import ( @@ -28,12 +30,7 @@ ) from homeassistant.util import slugify -from .config_flow import ( - CannotConnect, - InvalidAuth, - configured_instances, - validate_input, -) +from .config_flow import CannotConnect, InvalidAuth, validate_input from .const import ( CONF_WAKE_ON_START, DATA_LISTENER, @@ -75,6 +72,16 @@ def _async_save_tokens(hass, config_entry, access_token, refresh_token): ) +@callback +def _async_configured_emails(hass): + """Return a set of configured Tesla emails.""" + return { + entry.data[CONF_USERNAME] + for entry in hass.config_entries.async_entries(DOMAIN) + if CONF_USERNAME in entry.data + } + + async def async_setup(hass, base_config): """Set up of Tesla component.""" @@ -95,7 +102,7 @@ def _update_entry(email, data=None, options=None): email = config[CONF_USERNAME] password = config[CONF_PASSWORD] scan_interval = config[CONF_SCAN_INTERVAL] - if email in configured_instances(hass): + if email in _async_configured_emails(hass): try: info = await validate_input(hass, config) except (CannotConnect, InvalidAuth): @@ -151,7 +158,12 @@ async def async_setup_entry(hass, config_entry): CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START ) ) + except IncompleteCredentials: + _async_start_reauth(hass, config_entry) + return False except TeslaException as ex: + if ex.code == HTTP_UNAUTHORIZED: + _async_start_reauth(hass, config_entry) _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message) return False _async_save_tokens(hass, config_entry, access_token, refresh_token) @@ -206,6 +218,17 @@ async def async_unload_entry(hass, config_entry) -> bool: return False +def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "reauth"}, + data=entry.data, + ) + ) + _LOGGER.error("Credentials are no longer valid. Please reauthenticate") + + async def update_listener(hass, config_entry): """Update when config_entry options update.""" controller = hass.data[DOMAIN][config_entry.entry_id]["coordinator"].controller diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py index 683ef314a06426..194ea71a3b75a4 100644 --- a/homeassistant/components/tesla/config_flow.py +++ b/homeassistant/components/tesla/config_flow.py @@ -20,22 +20,12 @@ CONF_WAKE_ON_START, DEFAULT_SCAN_INTERVAL, DEFAULT_WAKE_ON_START, - DOMAIN, MIN_SCAN_INTERVAL, ) +from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema( - {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} -) - - -@callback -def configured_instances(hass): - """Return a set of configured Tesla instances.""" - return {entry.title for entry in hass.config_entries.async_entries(DOMAIN)} - class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Tesla.""" @@ -43,46 +33,56 @@ class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + def __init__(self): + """Initialize the tesla flow.""" + self.username = None + async def async_step_import(self, import_config): """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" + errors = {} + + if user_input is not None: + existing_entry = self._async_entry_for_username(user_input[CONF_USERNAME]) + if ( + existing_entry + and existing_entry.data[CONF_PASSWORD] == user_input[CONF_PASSWORD] + ): + return self.async_abort(reason="already_configured") + + try: + info = await validate_input(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + + if not errors: + if existing_entry: + self.hass.config_entries.async_update_entry( + existing_entry, data=info + ) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=info + ) + + return self.async_show_form( + step_id="user", + data_schema=self._async_schema(), + errors=errors, + description_placeholders={}, + ) - if not user_input: - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={}, - description_placeholders={}, - ) - - if user_input[CONF_USERNAME] in configured_instances(self.hass): - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={CONF_USERNAME: "already_configured"}, - description_placeholders={}, - ) - - try: - info = await validate_input(self.hass, user_input) - except CannotConnect: - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={"base": "cannot_connect"}, - description_placeholders={}, - ) - except InvalidAuth: - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={"base": "invalid_auth"}, - description_placeholders={}, - ) - return self.async_create_entry(title=user_input[CONF_USERNAME], data=info) + async def async_step_reauth(self, data): + """Handle configuration by re-auth.""" + self.username = data[CONF_USERNAME] + return await self.async_step_user() @staticmethod @callback @@ -90,6 +90,23 @@ def async_get_options_flow(config_entry): """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) + @callback + def _async_schema(self): + """Fetch schema with defaults.""" + return vol.Schema( + { + vol.Required(CONF_USERNAME, default=self.username): str, + vol.Required(CONF_PASSWORD): str, + } + ) + + @callback + def _async_entry_for_username(self, username): + """Find an existing entry for a username.""" + for entry in self._async_current_entries(): + if entry.data.get(CONF_USERNAME) == username: + return entry + class OptionsFlowHandler(config_entries.OptionsFlow): """Handle a option flow for Tesla.""" diff --git a/homeassistant/components/tesla/strings.json b/homeassistant/components/tesla/strings.json index 503124eedd4e8f..c75562528defb1 100644 --- a/homeassistant/components/tesla/strings.json +++ b/homeassistant/components/tesla/strings.json @@ -5,6 +5,10 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, + "abort": { + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tesla/translations/en.json b/homeassistant/components/tesla/translations/en.json index f2b888552b9f1a..53b213ac19b254 100644 --- a/homeassistant/components/tesla/translations/en.json +++ b/homeassistant/components/tesla/translations/en.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" + }, "error": { "already_configured": "Account is already configured", "cannot_connect": "Failed to connect", diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index 136633c9a5cea0..b35ab0039d0f26 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -97,7 +97,12 @@ async def test_form_cannot_connect(hass): async def test_form_repeat_identifier(hass): """Test we handle repeat identifiers.""" - entry = MockConfigEntry(domain=DOMAIN, title="test-username", data={}, options=None) + entry = MockConfigEntry( + domain=DOMAIN, + title="test-username", + data={"username": "test-username", "password": "test-password"}, + options=None, + ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -112,8 +117,36 @@ async def test_form_repeat_identifier(hass): {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, ) - assert result2["type"] == "form" - assert result2["errors"] == {CONF_USERNAME: "already_configured"} + assert result2["type"] == "abort" + assert result2["reason"] == "already_configured" + + +async def test_form_reauth(hass): + """Test we handle reauth.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="test-username", + data={"username": "test-username", "password": "same"}, + options=None, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_REAUTH}, + data={"username": "test-username"}, + ) + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + return_value=("test-refresh-token", "test-access-token"), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"}, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" async def test_import(hass): From 9bc3c6c1300a5bf16ae3f6c2d9e94950c77c4714 Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Wed, 10 Feb 2021 13:30:52 -0700 Subject: [PATCH 0373/1818] Bump pymyq to 3.0.1 (#46079) Co-authored-by: J. Nick Koston --- homeassistant/components/myq/__init__.py | 12 +- homeassistant/components/myq/binary_sensor.py | 7 +- homeassistant/components/myq/const.py | 20 ++-- homeassistant/components/myq/cover.py | 103 ++++++++---------- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/myq/util.py | 34 ++++-- 8 files changed, 96 insertions(+), 86 deletions(-) diff --git a/homeassistant/components/myq/__init__.py b/homeassistant/components/myq/__init__.py index 959000da3b3abd..6b3a52ba7b06f1 100644 --- a/homeassistant/components/myq/__init__.py +++ b/homeassistant/components/myq/__init__.py @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, PLATFORMS, UPDATE_INTERVAL @@ -40,11 +40,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except MyQError as err: raise ConfigEntryNotReady from err + # Called by DataUpdateCoordinator, allows to capture any MyQError exceptions and to throw an HASS UpdateFailed + # exception instead, preventing traceback in HASS logs. + async def async_update_data(): + try: + return await myq.update_device_info() + except MyQError as err: + raise UpdateFailed(str(err)) from err + coordinator = DataUpdateCoordinator( hass, _LOGGER, name="myq devices", - update_method=myq.update_device_info, + update_method=async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) diff --git a/homeassistant/components/myq/binary_sensor.py b/homeassistant/components/myq/binary_sensor.py index 57bd2451d2ab2c..e3832458b9bd2c 100644 --- a/homeassistant/components/myq/binary_sensor.py +++ b/homeassistant/components/myq/binary_sensor.py @@ -1,7 +1,5 @@ """Support for MyQ gateways.""" from pymyq.const import ( - DEVICE_FAMILY as MYQ_DEVICE_FAMILY, - DEVICE_FAMILY_GATEWAY as MYQ_DEVICE_FAMILY_GATEWAY, DEVICE_STATE as MYQ_DEVICE_STATE, DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE, KNOWN_MODELS, @@ -25,9 +23,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] - for device in myq.devices.values(): - if device.device_json[MYQ_DEVICE_FAMILY] == MYQ_DEVICE_FAMILY_GATEWAY: - entities.append(MyQBinarySensorEntity(coordinator, device)) + for device in myq.gateways.values(): + entities.append(MyQBinarySensorEntity(coordinator, device)) async_add_entities(entities, True) diff --git a/homeassistant/components/myq/const.py b/homeassistant/components/myq/const.py index 9251bce7447ea3..6189b1601eaa1f 100644 --- a/homeassistant/components/myq/const.py +++ b/homeassistant/components/myq/const.py @@ -1,9 +1,9 @@ """The MyQ integration.""" -from pymyq.device import ( - STATE_CLOSED as MYQ_STATE_CLOSED, - STATE_CLOSING as MYQ_STATE_CLOSING, - STATE_OPEN as MYQ_STATE_OPEN, - STATE_OPENING as MYQ_STATE_OPENING, +from pymyq.garagedoor import ( + STATE_CLOSED as MYQ_COVER_STATE_CLOSED, + STATE_CLOSING as MYQ_COVER_STATE_CLOSING, + STATE_OPEN as MYQ_COVER_STATE_OPEN, + STATE_OPENING as MYQ_COVER_STATE_OPENING, ) from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING @@ -13,10 +13,10 @@ PLATFORMS = ["cover", "binary_sensor"] MYQ_TO_HASS = { - MYQ_STATE_CLOSED: STATE_CLOSED, - MYQ_STATE_CLOSING: STATE_CLOSING, - MYQ_STATE_OPEN: STATE_OPEN, - MYQ_STATE_OPENING: STATE_OPENING, + MYQ_COVER_STATE_CLOSED: STATE_CLOSED, + MYQ_COVER_STATE_CLOSING: STATE_CLOSING, + MYQ_COVER_STATE_OPEN: STATE_OPEN, + MYQ_COVER_STATE_OPENING: STATE_OPENING, } MYQ_GATEWAY = "myq_gateway" @@ -24,7 +24,7 @@ # myq has some ratelimits in place # and 61 seemed to be work every time -UPDATE_INTERVAL = 61 +UPDATE_INTERVAL = 15 # Estimated time it takes myq to start transition from one # state to the next. diff --git a/homeassistant/components/myq/cover.py b/homeassistant/components/myq/cover.py index 6fef6b25bab22c..e26a969e724bde 100644 --- a/homeassistant/components/myq/cover.py +++ b/homeassistant/components/myq/cover.py @@ -1,14 +1,14 @@ """Support for MyQ-Enabled Garage Doors.""" -import time +import logging from pymyq.const import ( DEVICE_STATE as MYQ_DEVICE_STATE, DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE, - DEVICE_TYPE as MYQ_DEVICE_TYPE, DEVICE_TYPE_GATE as MYQ_DEVICE_TYPE_GATE, KNOWN_MODELS, MANUFACTURER, ) +from pymyq.errors import MyQError from homeassistant.components.cover import ( DEVICE_CLASS_GARAGE, @@ -17,19 +17,12 @@ SUPPORT_OPEN, CoverEntity, ) -from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING -from homeassistant.core import callback -from homeassistant.helpers.event import async_call_later +from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ( - DOMAIN, - MYQ_COORDINATOR, - MYQ_GATEWAY, - MYQ_TO_HASS, - TRANSITION_COMPLETE_DURATION, - TRANSITION_START_DURATION, -) +from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, MYQ_TO_HASS + +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): @@ -50,13 +43,11 @@ def __init__(self, coordinator, device): """Initialize with API object, device id.""" super().__init__(coordinator) self._device = device - self._last_action_timestamp = 0 - self._scheduled_transition_update = None @property def device_class(self): """Define this cover as a garage door.""" - device_type = self._device.device_json.get(MYQ_DEVICE_TYPE) + device_type = self._device.device_type if device_type is not None and device_type == MYQ_DEVICE_TYPE_GATE: return DEVICE_CLASS_GATE return DEVICE_CLASS_GARAGE @@ -87,6 +78,11 @@ def is_closing(self): """Return if the cover is closing or not.""" return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSING + @property + def is_open(self): + """Return if the cover is opening or not.""" + return MYQ_TO_HASS.get(self._device.state) == STATE_OPEN + @property def is_opening(self): """Return if the cover is opening or not.""" @@ -104,37 +100,48 @@ def unique_id(self): async def async_close_cover(self, **kwargs): """Issue close command to cover.""" - self._last_action_timestamp = time.time() - await self._device.close() - self._async_schedule_update_for_transition() + if self.is_closing or self.is_closed: + return + + try: + wait_task = await self._device.close(wait_for_state=False) + except MyQError as err: + _LOGGER.error( + "Closing of cover %s failed with error: %s", self._device.name, str(err) + ) + + return + + # Write closing state to HASS + self.async_write_ha_state() + + if not await wait_task: + _LOGGER.error("Closing of cover %s failed", self._device.name) + + # Write final state to HASS + self.async_write_ha_state() async def async_open_cover(self, **kwargs): """Issue open command to cover.""" - self._last_action_timestamp = time.time() - await self._device.open() - self._async_schedule_update_for_transition() + if self.is_opening or self.is_open: + return - @callback - def _async_schedule_update_for_transition(self): + try: + wait_task = await self._device.open(wait_for_state=False) + except MyQError as err: + _LOGGER.error( + "Opening of cover %s failed with error: %s", self._device.name, str(err) + ) + return + + # Write opening state to HASS self.async_write_ha_state() - # Cancel any previous updates - if self._scheduled_transition_update: - self._scheduled_transition_update() - - # Schedule an update for when we expect the transition - # to be completed so the garage door or gate does not - # seem like its closing or opening for a long time - self._scheduled_transition_update = async_call_later( - self.hass, - TRANSITION_COMPLETE_DURATION, - self._async_complete_schedule_update, - ) + if not await wait_task: + _LOGGER.error("Opening of cover %s failed", self._device.name) - async def _async_complete_schedule_update(self, _): - """Update status of the cover via coordinator.""" - self._scheduled_transition_update = None - await self.coordinator.async_request_refresh() + # Write final state to HASS + self.async_write_ha_state() @property def device_info(self): @@ -152,22 +159,8 @@ def device_info(self): device_info["via_device"] = (DOMAIN, self._device.parent_device_id) return device_info - @callback - def _async_consume_update(self): - if time.time() - self._last_action_timestamp <= TRANSITION_START_DURATION: - # If we just started a transition we need - # to prevent a bouncy state - return - - self.async_write_ha_state() - async def async_added_to_hass(self): """Subscribe to updates.""" self.async_on_remove( - self.coordinator.async_add_listener(self._async_consume_update) + self.coordinator.async_add_listener(self.async_write_ha_state) ) - - async def async_will_remove_from_hass(self): - """Undo subscription.""" - if self._scheduled_transition_update: - self._scheduled_transition_update() diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index aba2f24b5bd1ec..9dc8719ed4e87a 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.14"], + "requirements": ["pymyq==3.0.1"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index ee86d62270abfd..12ade1a446bcf2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1545,7 +1545,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.14 +pymyq==3.0.1 # homeassistant.components.mysensors pymysensors==0.20.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5d63ba30e1cd9c..f5a629a771fd4f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -808,7 +808,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.14 +pymyq==3.0.1 # homeassistant.components.mysensors pymysensors==0.20.1 diff --git a/tests/components/myq/util.py b/tests/components/myq/util.py index 84e85723918bf2..8cb0d17f592f9f 100644 --- a/tests/components/myq/util.py +++ b/tests/components/myq/util.py @@ -1,14 +1,18 @@ """Tests for the myq integration.""" - import json +import logging from unittest.mock import patch +from pymyq.const import ACCOUNTS_ENDPOINT, DEVICES_ENDPOINT + from homeassistant.components.myq.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture +_LOGGER = logging.getLogger(__name__) + async def async_init_integration( hass: HomeAssistant, @@ -20,16 +24,24 @@ async def async_init_integration( devices_json = load_fixture(devices_fixture) devices_dict = json.loads(devices_json) - def _handle_mock_api_request(method, endpoint, **kwargs): - if endpoint == "Login": - return {"SecurityToken": 1234} - if endpoint == "My": - return {"Account": {"Id": 1}} - if endpoint == "Accounts/1/Devices": - return devices_dict - return {} - - with patch("pymyq.api.API.request", side_effect=_handle_mock_api_request): + def _handle_mock_api_oauth_authenticate(): + return 1234, 1800 + + def _handle_mock_api_request(method, returns, url, **kwargs): + _LOGGER.debug("URL: %s", url) + if url == ACCOUNTS_ENDPOINT: + _LOGGER.debug("Accounts") + return None, {"accounts": [{"id": 1, "name": "mock"}]} + if url == DEVICES_ENDPOINT.format(account_id=1): + _LOGGER.debug("Devices") + return None, devices_dict + _LOGGER.debug("Something else") + return None, {} + + with patch( + "pymyq.api.API._oauth_authenticate", + side_effect=_handle_mock_api_oauth_authenticate, + ), patch("pymyq.api.API.request", side_effect=_handle_mock_api_request): entry = MockConfigEntry( domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"} ) From acde33dbbc82a8cedfa4bd3d5c077dc595e16b2f Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Thu, 11 Feb 2021 04:51:16 +0800 Subject: [PATCH 0374/1818] Keep 1 extra segment around after playlist removal (#46310) * Keep 1 extra segment around after playlist removal * Remove segments length check --- homeassistant/components/stream/const.py | 3 ++- homeassistant/components/stream/hls.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 4ee9f2a9814e00..41df806d020c4f 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -10,7 +10,8 @@ OUTPUT_IDLE_TIMEOUT = 300 # Idle timeout due to inactivity -MAX_SEGMENTS = 3 # Max number of segments to keep around +NUM_PLAYLIST_SEGMENTS = 3 # Number of segments to use in HLS playlist +MAX_SEGMENTS = 4 # Max number of segments to keep around MIN_SEGMENT_DURATION = 1.5 # Each segment is at least this many seconds PACKETS_TO_WAIT_FOR_AUDIO = 20 # Some streams have an audio stream with no audio diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 2b305442b808cb..bd5fbd5e9aebb1 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -6,7 +6,7 @@ from homeassistant.core import callback -from .const import FORMAT_CONTENT_TYPE +from .const import FORMAT_CONTENT_TYPE, NUM_PLAYLIST_SEGMENTS from .core import PROVIDERS, StreamOutput, StreamView from .fmp4utils import get_codec_string, get_init, get_m4s @@ -77,7 +77,7 @@ def render_preamble(track): @staticmethod def render_playlist(track): """Render playlist.""" - segments = track.segments + segments = track.segments[-NUM_PLAYLIST_SEGMENTS:] if not segments: return [] From fdd815995570c99708c5342b519ca280a23ddf78 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 10 Feb 2021 22:32:48 +0100 Subject: [PATCH 0375/1818] Fix SNMP engine memory leak in Brother integration (#46272) * Fix SNMP engine memory leak * Fix pylint error --- homeassistant/components/brother/__init__.py | 42 ++++++++----------- .../components/brother/config_flow.py | 10 +++-- homeassistant/components/brother/const.py | 4 ++ .../components/brother/manifest.json | 2 +- homeassistant/components/brother/sensor.py | 3 +- homeassistant/components/brother/utils.py | 30 +++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 homeassistant/components/brother/utils.py diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index 8c5cdb2d7edd35..d7cf906a87ca88 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -6,12 +6,13 @@ from brother import Brother, SnmpError, UnsupportedModel from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_TYPE, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.core import Config, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import DATA_CONFIG_ENTRY, DOMAIN, SNMP +from .utils import get_snmp_engine PLATFORMS = ["sensor"] @@ -30,15 +31,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): host = entry.data[CONF_HOST] kind = entry.data[CONF_TYPE] - coordinator = BrotherDataUpdateCoordinator(hass, host=host, kind=kind) + snmp_engine = get_snmp_engine(hass) + + coordinator = BrotherDataUpdateCoordinator( + hass, host=host, kind=kind, snmp_engine=snmp_engine + ) await coordinator.async_refresh() if not coordinator.last_update_success: - coordinator.shutdown() raise ConfigEntryNotReady hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = coordinator + hass.data[DOMAIN].setdefault(DATA_CONFIG_ENTRY, {}) + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = coordinator + hass.data[DOMAIN][SNMP] = snmp_engine for component in PLATFORMS: hass.async_create_task( @@ -59,7 +65,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id).shutdown() + hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id) + if not hass.data[DOMAIN][DATA_CONFIG_ENTRY]: + hass.data[DOMAIN].pop(SNMP) + hass.data[DOMAIN].pop(DATA_CONFIG_ENTRY) return unload_ok @@ -67,12 +76,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): class BrotherDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching Brother data from the printer.""" - def __init__(self, hass, host, kind): + def __init__(self, hass, host, kind, snmp_engine): """Initialize.""" - self.brother = Brother(host, kind=kind) - self._unsub_stop = hass.bus.async_listen( - EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop - ) + self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine) super().__init__( hass, @@ -83,22 +89,8 @@ def __init__(self, hass, host, kind): async def _async_update_data(self): """Update data via library.""" - # Race condition on shutdown. Stop all the fetches. - if self._unsub_stop is None: - return None - try: await self.brother.async_update() except (ConnectionError, SnmpError, UnsupportedModel) as error: raise UpdateFailed(error) from error return self.brother.data - - def shutdown(self): - """Shutdown the Brother coordinator.""" - self._unsub_stop() - self._unsub_stop = None - self.brother.shutdown() - - def _handle_ha_stop(self, _): - """Handle Home Assistant stopping.""" - self.shutdown() diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index aa9d7ce53a3e40..6a9d2ca6746440 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -9,6 +9,7 @@ from homeassistant.const import CONF_HOST, CONF_TYPE from .const import DOMAIN, PRINTER_TYPES # pylint:disable=unused-import +from .utils import get_snmp_engine DATA_SCHEMA = vol.Schema( { @@ -48,9 +49,10 @@ async def async_step_user(self, user_input=None): if not host_valid(user_input[CONF_HOST]): raise InvalidHost() - brother = Brother(user_input[CONF_HOST]) + snmp_engine = get_snmp_engine(self.hass) + + brother = Brother(user_input[CONF_HOST], snmp_engine=snmp_engine) await brother.async_update() - brother.shutdown() await self.async_set_unique_id(brother.serial.lower()) self._abort_if_unique_id_configured() @@ -83,7 +85,9 @@ async def async_step_zeroconf(self, discovery_info): # Hostname is format: brother.local. self.host = discovery_info["hostname"].rstrip(".") - self.brother = Brother(self.host) + snmp_engine = get_snmp_engine(self.hass) + + self.brother = Brother(self.host, snmp_engine=snmp_engine) try: await self.brother.async_update() except (ConnectionError, SnmpError, UnsupportedModel): diff --git a/homeassistant/components/brother/const.py b/homeassistant/components/brother/const.py index 5aecde16327d53..5ae459c79aa144 100644 --- a/homeassistant/components/brother/const.py +++ b/homeassistant/components/brother/const.py @@ -41,12 +41,16 @@ ATTR_YELLOW_INK_REMAINING = "yellow_ink_remaining" ATTR_YELLOW_TONER_REMAINING = "yellow_toner_remaining" +DATA_CONFIG_ENTRY = "config_entry" + DOMAIN = "brother" UNIT_PAGES = "p" PRINTER_TYPES = ["laser", "ink"] +SNMP = "snmp" + SENSOR_TYPES = { ATTR_STATUS: { ATTR_ICON: "mdi:printer", diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 9bb9ba00261c83..3f275338949da3 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -3,7 +3,7 @@ "name": "Brother Printer", "documentation": "https://www.home-assistant.io/integrations/brother", "codeowners": ["@bieniu"], - "requirements": ["brother==0.1.20"], + "requirements": ["brother==0.1.21"], "zeroconf": [{ "type": "_printer._tcp.local.", "name": "brother*" }], "config_flow": true, "quality_scale": "platinum" diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 40e2deae67dc71..a379d9b415461b 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -24,6 +24,7 @@ ATTR_YELLOW_DRUM_COUNTER, ATTR_YELLOW_DRUM_REMAINING_LIFE, ATTR_YELLOW_DRUM_REMAINING_PAGES, + DATA_CONFIG_ENTRY, DOMAIN, SENSOR_TYPES, ) @@ -37,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Add Brother entities from a config_entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] sensors = [] diff --git a/homeassistant/components/brother/utils.py b/homeassistant/components/brother/utils.py new file mode 100644 index 00000000000000..3a53f4c04a2d60 --- /dev/null +++ b/homeassistant/components/brother/utils.py @@ -0,0 +1,30 @@ +"""Brother helpers functions.""" +import logging + +import pysnmp.hlapi.asyncio as hlapi +from pysnmp.hlapi.asyncio.cmdgen import lcd + +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import callback +from homeassistant.helpers import singleton + +from .const import DOMAIN, SNMP + +_LOGGER = logging.getLogger(__name__) + + +@singleton.singleton("snmp_engine") +def get_snmp_engine(hass): + """Get SNMP engine.""" + _LOGGER.debug("Creating SNMP engine") + snmp_engine = hlapi.SnmpEngine() + + @callback + def shutdown_listener(ev): + if hass.data.get(DOMAIN): + _LOGGER.debug("Unconfiguring SNMP engine") + lcd.unconfigure(hass.data[DOMAIN][SNMP], None) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener) + + return snmp_engine diff --git a/requirements_all.txt b/requirements_all.txt index 258474f97f3a7a..b3e9b89a30cf2c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -383,7 +383,7 @@ bravia-tv==1.0.8 broadlink==0.16.0 # homeassistant.components.brother -brother==0.1.20 +brother==0.1.21 # homeassistant.components.brottsplatskartan brottsplatskartan==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9583d901193f3b..d634190551250e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -210,7 +210,7 @@ bravia-tv==1.0.8 broadlink==0.16.0 # homeassistant.components.brother -brother==0.1.20 +brother==0.1.21 # homeassistant.components.bsblan bsblan==0.4.0 From d8a2e0e0514aa7ba938398e582fe4b865765ddef Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 17:17:37 -0500 Subject: [PATCH 0376/1818] Remove unnecessary variables from logbook (#46350) --- homeassistant/components/logbook/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index e2d8a22c251f6a..3b77e6e6409cc2 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -54,8 +54,6 @@ ATTR_MESSAGE = "message" -CONF_DOMAINS = "domains" -CONF_ENTITIES = "entities" CONTINUOUS_DOMAINS = ["proximity", "sensor"] DOMAIN = "logbook" @@ -417,7 +415,6 @@ def _get_events( entity_matches_only=False, ): """Get events for a period of time.""" - entity_attr_cache = EntityAttributeCache(hass) context_lookup = {None: None} From 281fbe1dfac1f7a93f51622578b55075977a9fa5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 12:49:52 -1000 Subject: [PATCH 0377/1818] Update wilight for new fan entity model (#45869) --- homeassistant/components/wilight/fan.py | 45 ++++++++++--------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index d59b9398d9e9b6..948d99ac81db81 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -14,19 +14,20 @@ from homeassistant.components.fan import ( DIRECTION_FORWARD, - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, SUPPORT_DIRECTION, SUPPORT_SET_SPEED, FanEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, +) from . import WiLightDevice -SUPPORTED_SPEEDS = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] +ORDERED_NAMED_FAN_SPEEDS = [WL_SPEED_LOW, WL_SPEED_MEDIUM, WL_SPEED_HIGH] SUPPORTED_FEATURES = SUPPORT_SET_SPEED | SUPPORT_DIRECTION @@ -77,14 +78,12 @@ def is_on(self): return self._status.get("direction", WL_DIRECTION_OFF) != WL_DIRECTION_OFF @property - def speed(self) -> str: - """Return the current speed.""" - return self._status.get("speed", SPEED_HIGH) - - @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return SUPPORTED_SPEEDS + def percentage(self) -> str: + """Return the current speed percentage.""" + wl_speed = self._status.get("speed") + if wl_speed is None: + return None + return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, wl_speed) @property def current_direction(self) -> str: @@ -94,13 +93,6 @@ def current_direction(self) -> str: self._direction = self._status["direction"] return self._direction - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # async def async_turn_on( self, speed: str = None, @@ -109,18 +101,17 @@ async def async_turn_on( **kwargs, ) -> None: """Turn on the fan.""" - if speed is None: + if percentage is None: await self._client.set_fan_direction(self._index, self._direction) else: - await self.async_set_speed(speed) + await self.async_set_percentage(percentage) - async def async_set_speed(self, speed: str): + async def async_set_percentage(self, percentage: int): """Set the speed of the fan.""" - wl_speed = WL_SPEED_HIGH - if speed == SPEED_LOW: - wl_speed = WL_SPEED_LOW - if speed == SPEED_MEDIUM: - wl_speed = WL_SPEED_MEDIUM + if percentage == 0: + await self._client.set_fan_direction(self._index, WL_DIRECTION_OFF) + return + wl_speed = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage) await self._client.set_fan_speed(self._index, wl_speed) async def async_set_direction(self, direction: str): From 6182fedf3f427af3280344f1e2874f0b6d9dd8b7 Mon Sep 17 00:00:00 2001 From: Leonardo Figueiro Date: Wed, 10 Feb 2021 20:09:03 -0300 Subject: [PATCH 0378/1818] Update wilight tests for new fan entity model (#46358) --- tests/components/wilight/test_fan.py | 29 +++++++++++++--------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/components/wilight/test_fan.py b/tests/components/wilight/test_fan.py index 9b656236b93419..1247b622ae7b08 100644 --- a/tests/components/wilight/test_fan.py +++ b/tests/components/wilight/test_fan.py @@ -6,15 +6,12 @@ from homeassistant.components.fan import ( ATTR_DIRECTION, - ATTR_SPEED, + ATTR_PERCENTAGE, DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN as FAN_DOMAIN, SERVICE_SET_DIRECTION, - SERVICE_SET_SPEED, - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, + SERVICE_SET_PERCENTAGE, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -102,7 +99,7 @@ async def test_on_off_fan_state( await hass.services.async_call( FAN_DOMAIN, SERVICE_TURN_ON, - {ATTR_SPEED: SPEED_LOW, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, + {ATTR_PERCENTAGE: 30, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, blocking=True, ) @@ -110,7 +107,7 @@ async def test_on_off_fan_state( state = hass.states.get("fan.wl000000000099_2") assert state assert state.state == STATE_ON - assert state.attributes.get(ATTR_SPEED) == SPEED_LOW + assert state.attributes.get(ATTR_PERCENTAGE) == 33 # Turn off await hass.services.async_call( @@ -135,41 +132,41 @@ async def test_speed_fan_state( # Set speed Low await hass.services.async_call( FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_SPEED: SPEED_LOW, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, + SERVICE_SET_PERCENTAGE, + {ATTR_PERCENTAGE: 30, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, blocking=True, ) await hass.async_block_till_done() state = hass.states.get("fan.wl000000000099_2") assert state - assert state.attributes.get(ATTR_SPEED) == SPEED_LOW + assert state.attributes.get(ATTR_PERCENTAGE) == 33 # Set speed Medium await hass.services.async_call( FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_SPEED: SPEED_MEDIUM, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, + SERVICE_SET_PERCENTAGE, + {ATTR_PERCENTAGE: 50, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, blocking=True, ) await hass.async_block_till_done() state = hass.states.get("fan.wl000000000099_2") assert state - assert state.attributes.get(ATTR_SPEED) == SPEED_MEDIUM + assert state.attributes.get(ATTR_PERCENTAGE) == 66 # Set speed High await hass.services.async_call( FAN_DOMAIN, - SERVICE_SET_SPEED, - {ATTR_SPEED: SPEED_HIGH, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, + SERVICE_SET_PERCENTAGE, + {ATTR_PERCENTAGE: 90, ATTR_ENTITY_ID: "fan.wl000000000099_2"}, blocking=True, ) await hass.async_block_till_done() state = hass.states.get("fan.wl000000000099_2") assert state - assert state.attributes.get(ATTR_SPEED) == SPEED_HIGH + assert state.attributes.get(ATTR_PERCENTAGE) == 100 async def test_direction_fan_state( From 8007391244c59408c604e195d9b02e4f22a12ecd Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 11 Feb 2021 00:02:56 +0000 Subject: [PATCH 0379/1818] [ci skip] Translation update --- .../ambiclimate/translations/no.json | 2 +- .../components/foscam/translations/no.json | 2 + .../components/goalzero/translations/no.json | 2 +- .../components/icloud/translations/no.json | 2 +- .../components/konnected/translations/no.json | 2 +- .../lutron_caseta/translations/no.json | 2 +- .../media_player/translations/it.json | 7 +++ .../media_player/translations/no.json | 7 +++ .../components/mysensors/translations/no.json | 51 +++++++++++++++++-- .../components/nest/translations/no.json | 4 +- .../components/plaato/translations/no.json | 9 +++- .../components/point/translations/no.json | 4 +- .../components/powerwall/translations/en.json | 2 +- .../components/roku/translations/ca.json | 1 + .../components/roku/translations/it.json | 1 + .../components/roku/translations/ru.json | 1 + .../components/roku/translations/zh-Hant.json | 1 + .../components/roomba/translations/no.json | 2 +- .../components/shelly/translations/it.json | 4 +- .../components/shelly/translations/no.json | 4 +- .../components/soma/translations/no.json | 2 +- .../components/spotify/translations/no.json | 2 +- .../tellduslive/translations/no.json | 4 +- .../components/toon/translations/no.json | 4 +- .../components/traccar/translations/no.json | 2 +- .../components/vera/translations/no.json | 4 +- .../xiaomi_aqara/translations/no.json | 4 +- .../components/zwave/translations/no.json | 2 +- 28 files changed, 102 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/ambiclimate/translations/no.json b/homeassistant/components/ambiclimate/translations/no.json index c39aa7637f8b57..6feaabadacc5b2 100644 --- a/homeassistant/components/ambiclimate/translations/no.json +++ b/homeassistant/components/ambiclimate/translations/no.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Vennligst f\u00f8lg denne [linken]({authorization_url}) og **Tillat** tilgang til din Ambiclimate konto, kom deretter tilbake og trykk **Send** nedenfor.\n(Kontroller at den angitte URL-adressen for tilbakeringing er {cb_url})", + "description": "F\u00f8lg denne [linken]({authorization_url}) og **Tillat** tilgang til Ambiclimate-kontoen din, og kom deretter tilbake og trykk **Send** nedenfor.\n(Kontroller at den angitte url-adressen for tilbakeringing er {cb_url})", "title": "Godkjenn Ambiclimate" } } diff --git a/homeassistant/components/foscam/translations/no.json b/homeassistant/components/foscam/translations/no.json index 5e1b494c88a1d1..0184213de27bf1 100644 --- a/homeassistant/components/foscam/translations/no.json +++ b/homeassistant/components/foscam/translations/no.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Tilkobling mislyktes", "invalid_auth": "Ugyldig godkjenning", + "invalid_response": "Ugyldig respons fra enheten", "unknown": "Uventet feil" }, "step": { @@ -14,6 +15,7 @@ "host": "Vert", "password": "Passord", "port": "Port", + "rtsp_port": "RTSP-port", "stream": "Str\u00f8m", "username": "Brukernavn" } diff --git a/homeassistant/components/goalzero/translations/no.json b/homeassistant/components/goalzero/translations/no.json index 1aaf27d1b09985..4ae6f564a99751 100644 --- a/homeassistant/components/goalzero/translations/no.json +++ b/homeassistant/components/goalzero/translations/no.json @@ -14,7 +14,7 @@ "host": "Vert", "name": "Navn" }, - "description": "F\u00f8rst m\u00e5 du laste ned appen Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n F\u00f8lg instruksjonene for \u00e5 koble Yeti til Wifi-nettverket. S\u00e5 f\u00e5 verts-ip fra ruteren din. DHCP m\u00e5 v\u00e6re satt opp i ruteren innstillinger for enheten for \u00e5 sikre at verts-IP ikke endres. Se brukerh\u00e5ndboken til ruteren.", + "description": "F\u00f8rst m\u00e5 du laste ned appen Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n F\u00f8lg instruksjonene for \u00e5 koble Yeti til Wifi-nettverket. S\u00e5 f\u00e5 verts-IP-en fra ruteren din. DHCP m\u00e5 v\u00e6re satt opp i ruteren innstillinger for enheten for \u00e5 sikre at verts-IP ikke endres. Se ruteren din.", "title": "" } } diff --git a/homeassistant/components/icloud/translations/no.json b/homeassistant/components/icloud/translations/no.json index 62e123eb84c8e3..3e20aef032eb92 100644 --- a/homeassistant/components/icloud/translations/no.json +++ b/homeassistant/components/icloud/translations/no.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "Ugyldig godkjenning", "send_verification_code": "Kunne ikke sende bekreftelseskode", - "validate_verification_code": "Kunne ikke bekrefte bekreftelseskoden din, velg en tillitsenhet og start bekreftelsen p\u00e5 nytt" + "validate_verification_code": "Kunne ikke bekrefte bekreftelseskoden, pr\u00f8v p\u00e5 nytt" }, "step": { "reauth": { diff --git a/homeassistant/components/konnected/translations/no.json b/homeassistant/components/konnected/translations/no.json index 1a11c7c76d3869..47be4c20bf00ff 100644 --- a/homeassistant/components/konnected/translations/no.json +++ b/homeassistant/components/konnected/translations/no.json @@ -32,7 +32,7 @@ "not_konn_panel": "Ikke en anerkjent Konnected.io-enhet" }, "error": { - "bad_host": "Ugyldig overstyr API-vertsadresse" + "bad_host": "Url-adresse for ugyldig overstyring av API-vert" }, "step": { "options_binary": { diff --git a/homeassistant/components/lutron_caseta/translations/no.json b/homeassistant/components/lutron_caseta/translations/no.json index 477370100af138..b985c87caf03ca 100644 --- a/homeassistant/components/lutron_caseta/translations/no.json +++ b/homeassistant/components/lutron_caseta/translations/no.json @@ -22,7 +22,7 @@ "data": { "host": "Vert" }, - "description": "Skriv inn ip-adressen til enheten.", + "description": "Skriv inn IP-adressen til enheten.", "title": "Koble automatisk til broen" } } diff --git a/homeassistant/components/media_player/translations/it.json b/homeassistant/components/media_player/translations/it.json index 23d1afa0625985..a3ebfdfe4112ae 100644 --- a/homeassistant/components/media_player/translations/it.json +++ b/homeassistant/components/media_player/translations/it.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} \u00e8 acceso", "is_paused": "{entity_name} \u00e8 in pausa", "is_playing": "{entity_name} \u00e8 in esecuzione" + }, + "trigger_type": { + "idle": "{entity_name} diventa inattivo", + "paused": "{entity_name} \u00e8 in pausa", + "playing": "{entity_name} inizia l'esecuzione", + "turned_off": "{entity_name} disattivato", + "turned_on": "{entity_name} attivato" } }, "state": { diff --git a/homeassistant/components/media_player/translations/no.json b/homeassistant/components/media_player/translations/no.json index 691ec894a7b9f6..fa5618efc35962 100644 --- a/homeassistant/components/media_player/translations/no.json +++ b/homeassistant/components/media_player/translations/no.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} er sl\u00e5tt p\u00e5", "is_paused": "{entity_name} er satt p\u00e5 pause", "is_playing": "{entity_name} spiller n\u00e5" + }, + "trigger_type": { + "idle": "{entity_name} blir inaktiv", + "paused": "{entity_name} er satt p\u00e5 pause", + "playing": "{entity_name} begynner \u00e5 spille", + "turned_off": "{entity_name} sl\u00e5tt av", + "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } }, "state": { diff --git a/homeassistant/components/mysensors/translations/no.json b/homeassistant/components/mysensors/translations/no.json index a13140df64026d..9d028260a7686a 100644 --- a/homeassistant/components/mysensors/translations/no.json +++ b/homeassistant/components/mysensors/translations/no.json @@ -3,30 +3,75 @@ "abort": { "already_configured": "Enheten er allerede konfigurert", "cannot_connect": "Tilkobling mislyktes", + "duplicate_persistence_file": "Persistensfil allerede i bruk", + "duplicate_topic": "Emnet er allerede i bruk", "invalid_auth": "Ugyldig godkjenning", + "invalid_device": "Ugyldig enhet", + "invalid_ip": "Ugyldig IP-adresse", + "invalid_persistence_file": "Ugyldig utholdenhetsfil", + "invalid_port": "Ugyldig portnummer", + "invalid_publish_topic": "Ugyldig publiseringsemne", + "invalid_serial": "Ugyldig serieport", + "invalid_subscribe_topic": "Ugyldig abonnementsemne", + "invalid_version": "Ugyldig MySensors-versjon", + "not_a_number": "Vennligst skriv inn et nummer", + "port_out_of_range": "Portnummer m\u00e5 v\u00e6re minst 1 og maksimalt 65535", + "same_topic": "Abonner og publiser emner er de samme", "unknown": "Uventet feil" }, "error": { "already_configured": "Enheten er allerede konfigurert", "cannot_connect": "Tilkobling mislyktes", + "duplicate_persistence_file": "Persistensfil allerede i bruk", + "duplicate_topic": "Emnet er allerede i bruk", "invalid_auth": "Ugyldig godkjenning", + "invalid_device": "Ugyldig enhet", + "invalid_ip": "Ugyldig IP-adresse", + "invalid_persistence_file": "Ugyldig utholdenhetsfil", + "invalid_port": "Ugyldig portnummer", + "invalid_publish_topic": "Ugyldig publiseringsemne", + "invalid_serial": "Ugyldig serieport", + "invalid_subscribe_topic": "Ugyldig abonnementsemne", + "invalid_version": "Ugyldig MySensors-versjon", + "not_a_number": "Vennligst skriv inn et nummer", + "port_out_of_range": "Portnummer m\u00e5 v\u00e6re minst 1 og maksimalt 65535", + "same_topic": "Abonner og publiser emner er de samme", "unknown": "Uventet feil" }, "step": { "gw_mqtt": { "data": { + "persistence_file": "Persistensfil (la den v\u00e6re tom for automatisk generering)", + "retain": "mqtt beholde", + "topic_in_prefix": "prefiks for input-emner (topic_in_prefix)", + "topic_out_prefix": "prefiks for utgangstemaer (topic_out_prefix)", "version": "MySensors versjon" - } + }, + "description": "MQTT gateway-oppsett" }, "gw_serial": { "data": { + "baud_rate": "Overf\u00f8ringshastighet", + "device": "Seriell port", + "persistence_file": "persistensfil (la den v\u00e6re tom for automatisk generering)", "version": "MySensors versjon" - } + }, + "description": "Seriell gatewayoppsett" + }, + "gw_tcp": { + "data": { + "device": "IP-adressen til gatewayen", + "persistence_file": "persistensfil (la den v\u00e6re tom for automatisk generering)", + "tcp_port": "Port", + "version": "MySensors versjon" + }, + "description": "Ethernet gateway-oppsett" }, "user": { "data": { "gateway_type": "" - } + }, + "description": "Velg tilkoblingsmetode til gatewayen" } } }, diff --git a/homeassistant/components/nest/translations/no.json b/homeassistant/components/nest/translations/no.json index dfaf33b3969d68..14166f7d9a6e17 100644 --- a/homeassistant/components/nest/translations/no.json +++ b/homeassistant/components/nest/translations/no.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "authorize_url_fail": "Ukjent feil ved generering av godkjenningsadresse", + "authorize_url_fail": "Ukjent feil under generering av en autoriserings-URL.", "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen", "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk hjelpseksjonen]({docs_url})", "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", - "unknown_authorize_url_generation": "Ukjent feil ved generering av godkjenningsadresse" + "unknown_authorize_url_generation": "Ukjent feil under generering av en autoriserings-URL." }, "create_entry": { "default": "Vellykket godkjenning" diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 8873399aaa4282..8efbf07945f2ee 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, "create_entry": { - "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." + "default": "Plaato {device_type} med navnet **{device_name}** ble konfigurert!" }, "error": { "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er kun tilgjengelig for Airlock", @@ -15,6 +15,11 @@ }, "step": { "api_method": { + "data": { + "token": "Lim inn Auth Token her", + "use_webhook": "Bruk webhook" + }, + "description": "For \u00e5 kunne s\u00f8ke p\u00e5 API-en kreves det en `auth_token` som kan oppn\u00e5s ved \u00e5 f\u00f8lge [disse] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instruksjonene \n\n Valgt enhet: **{device_type}** \n\n Hvis du heller bruker den innebygde webhook-metoden (kun luftsperre), vennligst merk av i ruten nedenfor og la Auth Token v\u00e6re tom.", "title": "Velg API-metode" }, "user": { @@ -23,7 +28,7 @@ "device_type": "Type Platon-enhet" }, "description": "Vil du starte oppsettet?", - "title": "Sett opp Plaato Webhook" + "title": "Sett opp Plaato-enhetene" }, "webhook": { "description": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer.", diff --git a/homeassistant/components/point/translations/no.json b/homeassistant/components/point/translations/no.json index d0d0b9114fb555..a72a8083f6fb10 100644 --- a/homeassistant/components/point/translations/no.json +++ b/homeassistant/components/point/translations/no.json @@ -2,11 +2,11 @@ "config": { "abort": { "already_setup": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", - "authorize_url_fail": "Ukjent feil ved generering av godkjenningsadresse", + "authorize_url_fail": "Ukjent feil under generering av en autoriserings-URL.", "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", "external_setup": "Punktet er konfigurert fra en annen flyt.", "no_flows": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen", - "unknown_authorize_url_generation": "Ukjent feil ved generering av godkjenningsadresse" + "unknown_authorize_url_generation": "Ukjent feil under generering av en autoriserings-URL." }, "create_entry": { "default": "Vellykket godkjenning" diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 4ebe1e9d5efaa7..ae8122589bec27 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -22,4 +22,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ca.json b/homeassistant/components/roku/translations/ca.json index eb0564b5bde0cf..b60b8f83eb9d38 100644 --- a/homeassistant/components/roku/translations/ca.json +++ b/homeassistant/components/roku/translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "unknown": "Error inesperat" }, "error": { diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 100d99924727f2..3c11aa4d8ae12e 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "unknown": "Errore imprevisto" }, "error": { diff --git a/homeassistant/components/roku/translations/ru.json b/homeassistant/components/roku/translations/ru.json index f7f36f41b27336..c3ae135ed76ae2 100644 --- a/homeassistant/components/roku/translations/ru.json +++ b/homeassistant/components/roku/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.", + "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.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { diff --git a/homeassistant/components/roku/translations/zh-Hant.json b/homeassistant/components/roku/translations/zh-Hant.json index 4b0566d66b06dc..429c03a991ea83 100644 --- a/homeassistant/components/roku/translations/zh-Hant.json +++ b/homeassistant/components/roku/translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { diff --git a/homeassistant/components/roomba/translations/no.json b/homeassistant/components/roomba/translations/no.json index 2bfe9f774d1698..67df735719c9b5 100644 --- a/homeassistant/components/roomba/translations/no.json +++ b/homeassistant/components/roomba/translations/no.json @@ -25,7 +25,7 @@ "data": { "password": "Passord" }, - "description": "Passordet kunne ikke hentes automatisk fra enheten. F\u00f8lg trinnene som er beskrevet i dokumentasjonen p\u00e5: {auth_help_url}", + "description": "Passordet kan ikke hentes fra enheten automatisk. F\u00f8lg trinnene som er beskrevet i dokumentasjonen p\u00e5: {auth_help_url}", "title": "Skriv inn passord" }, "manual": { diff --git a/homeassistant/components/shelly/translations/it.json b/homeassistant/components/shelly/translations/it.json index 4d486a8f2fab4c..051cf88dc38ef8 100644 --- a/homeassistant/components/shelly/translations/it.json +++ b/homeassistant/components/shelly/translations/it.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Vuoi impostare {model} su {host} ?\n\n Prima della configurazione, i dispositivi alimentati a batteria devono essere riattivati premendo il pulsante sul dispositivo." + "description": "Vuoi impostare {model} su {host}? \n\nI dispositivi alimentati a batteria protetti da password devono essere riattivati prima di continuare con la configurazione.\nI dispositivi alimentati a batteria che non sono protetti da password verranno aggiunti quando il dispositivo si riattiver\u00e0, ora puoi riattivare manualmente il dispositivo utilizzando un pulsante su di esso o attendere il prossimo aggiornamento dei dati dal dispositivo." }, "credentials": { "data": { @@ -24,7 +24,7 @@ "data": { "host": "Host" }, - "description": "Prima della configurazione, i dispositivi alimentati a batteria devono essere riattivati premendo il pulsante sul dispositivo." + "description": "Prima della configurazione, i dispositivi alimentati a batteria devono essere riattivati, ora puoi riattivare il dispositivo utilizzando un pulsante su di esso." } } }, diff --git a/homeassistant/components/shelly/translations/no.json b/homeassistant/components/shelly/translations/no.json index 1606a1acbb1bcc..90cfe3ca906581 100644 --- a/homeassistant/components/shelly/translations/no.json +++ b/homeassistant/components/shelly/translations/no.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Vil du konfigurere {model} p\u00e5 {host} ?\n\n F\u00f8r du setter opp, m\u00e5 batteridrevne enheter vekkes ved \u00e5 trykke p\u00e5 knappen p\u00e5 enheten." + "description": "Vil du konfigurere {model} p\u00e5 {host} ? \n\n Batteridrevne enheter som er passordbeskyttet, m\u00e5 vekkes f\u00f8r du fortsetter med konfigurasjonen.\n Batteridrevne enheter som ikke er passordbeskyttet, blir lagt til n\u00e5r enheten v\u00e5kner, du kan n\u00e5 vekke enheten manuelt med en knapp p\u00e5 den eller vente p\u00e5 neste dataoppdatering fra enheten." }, "credentials": { "data": { @@ -24,7 +24,7 @@ "data": { "host": "Vert" }, - "description": "F\u00f8r du setter opp, m\u00e5 batteridrevne enheter vekkes ved \u00e5 trykke p\u00e5 knappen p\u00e5 enheten." + "description": "F\u00f8r du setter opp, m\u00e5 batteridrevne enheter vekkes, du kan n\u00e5 vekke enheten med en knapp p\u00e5 den." } } }, diff --git a/homeassistant/components/soma/translations/no.json b/homeassistant/components/soma/translations/no.json index f9b64dc848377b..a399f430329b51 100644 --- a/homeassistant/components/soma/translations/no.json +++ b/homeassistant/components/soma/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "Du kan bare konfigurere \u00e9n Soma-konto.", - "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "authorize_url_timeout": "Tidsavbrudd genererer godkjennelses-URL.", "connection_error": "Kunne ikke koble til SOMA Connect.", "missing_configuration": "Soma-komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", "result_error": "SOMA Connect svarte med feilstatus." diff --git a/homeassistant/components/spotify/translations/no.json b/homeassistant/components/spotify/translations/no.json index 8e2ec3d36c0f95..54e3ca1f8b4b79 100644 --- a/homeassistant/components/spotify/translations/no.json +++ b/homeassistant/components/spotify/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", + "authorize_url_timeout": "Tidsavbrudd genererer godkjennelses-URL.", "missing_configuration": "Spotify-integrasjonen er ikke konfigurert. F\u00f8lg dokumentasjonen.", "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk hjelpseksjonen]({docs_url})", "reauth_account_mismatch": "Spotify-kontoen som er godkjent samsvarer ikke med kontoen som trenger godkjenning p\u00e5 nytt" diff --git a/homeassistant/components/tellduslive/translations/no.json b/homeassistant/components/tellduslive/translations/no.json index 649de0f86e4d04..563359d266a945 100644 --- a/homeassistant/components/tellduslive/translations/no.json +++ b/homeassistant/components/tellduslive/translations/no.json @@ -2,10 +2,10 @@ "config": { "abort": { "already_configured": "Tjenesten er allerede konfigurert", - "authorize_url_fail": "Ukjent feil ved generering av godkjenningsadresse", + "authorize_url_fail": "Ukjent feil under generering av en autoriserings-URL.", "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", "unknown": "Uventet feil", - "unknown_authorize_url_generation": "Ukjent feil ved generering av godkjenningsadresse" + "unknown_authorize_url_generation": "Ukjent feil under generering av en autoriserings-URL." }, "error": { "invalid_auth": "Ugyldig godkjenning" diff --git a/homeassistant/components/toon/translations/no.json b/homeassistant/components/toon/translations/no.json index a64a64ab74e173..41246c42f0eed7 100644 --- a/homeassistant/components/toon/translations/no.json +++ b/homeassistant/components/toon/translations/no.json @@ -2,12 +2,12 @@ "config": { "abort": { "already_configured": "Den valgte avtalen er allerede konfigurert.", - "authorize_url_fail": "Ukjent feil ved generering av godkjenningsadresse", + "authorize_url_fail": "Ukjent feil under generering av en autoriserings-URL.", "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse", "missing_configuration": "Komponenten er ikke konfigurert, vennligst f\u00f8lg dokumentasjonen", "no_agreements": "Denne kontoen har ingen Toon skjermer.", "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk hjelpseksjonen]({docs_url})", - "unknown_authorize_url_generation": "Ukjent feil ved generering av godkjenningsadresse" + "unknown_authorize_url_generation": "Ukjent feil under generering av en autoriserings-URL." }, "step": { "agreement": { diff --git a/homeassistant/components/traccar/translations/no.json b/homeassistant/components/traccar/translations/no.json index 38faa4dc1c1ca1..e2051be22b6262 100644 --- a/homeassistant/components/traccar/translations/no.json +++ b/homeassistant/components/traccar/translations/no.json @@ -5,7 +5,7 @@ "webhook_not_internet_accessible": "Home Assistant forekomsten din m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta webhook meldinger" }, "create_entry": { - "default": "Hvis du vil sende hendelser til Home Assistant, m\u00e5 du konfigurere webhook-funksjonen i Traccar.\n\nBruk f\u00f8lgende URL-adresse: `{webhook_url}`\n\nSe [dokumentasjonen]({docs_url}) for mer informasjon." + "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du konfigurere webhook-funksjonen i Traccar. \n\n Bruk f\u00f8lgende URL: \"{webhook_url}\" \n\n Se [dokumentasjonen] ({docs_url}) for mer informasjon." }, "step": { "user": { diff --git a/homeassistant/components/vera/translations/no.json b/homeassistant/components/vera/translations/no.json index 7ec6850a7c8b8c..f1454be67998fa 100644 --- a/homeassistant/components/vera/translations/no.json +++ b/homeassistant/components/vera/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Kunne ikke koble til kontrolleren med url {base_url}" + "cannot_connect": "Kan ikke koble til kontrolleren med URL-adressen {base_url}" }, "step": { "user": { @@ -10,7 +10,7 @@ "lights": "Vera bytter enhets ID-er for \u00e5 behandle som lys i Home Assistant", "vera_controller_url": "URL-adresse for kontroller" }, - "description": "Oppgi en Vera-kontroller-url nedenfor. Det skal se slik ut: http://192.168.1.161:3480.", + "description": "Gi en Vera-kontroller-URL nedenfor. Det skal se slik ut: http://192.168.1.161:3480.", "title": "Oppsett Vera-kontroller" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/no.json b/homeassistant/components/xiaomi_aqara/translations/no.json index 523e2da898c4c7..5a46d66fcf0464 100644 --- a/homeassistant/components/xiaomi_aqara/translations/no.json +++ b/homeassistant/components/xiaomi_aqara/translations/no.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP adresse" }, - "description": "Kj\u00f8r oppsettet igjen hvis du vil koble til tilleggsportaler", + "description": "Kj\u00f8r oppsettet p\u00e5 nytt hvis du vil koble til flere gatewayer", "title": "Velg Xiaomi Aqara Gateway som du \u00f8nsker \u00e5 koble til" }, "settings": { @@ -35,7 +35,7 @@ "interface": "Nettverksgrensesnittet som skal brukes", "mac": "MAC-adresse (valgfritt)" }, - "description": "Koble til Xiaomi Aqara Gateway, hvis IP- og MAC-adressene er tomme, brukes automatisk oppdagelse", + "description": "Koble til Xiaomi Aqara Gateway, hvis IP- og MAC-adressene blir tomme, brukes automatisk oppdagelse", "title": "" } } diff --git a/homeassistant/components/zwave/translations/no.json b/homeassistant/components/zwave/translations/no.json index ba875354f7f6f7..ab5a405f975392 100644 --- a/homeassistant/components/zwave/translations/no.json +++ b/homeassistant/components/zwave/translations/no.json @@ -13,7 +13,7 @@ "network_key": "Nettverksn\u00f8kkel (la v\u00e6re tom for automatisk oppretting)", "usb_path": "USB enhetsbane" }, - "description": "Se [www.home-assistant.io/docs/z-wave/installation/](https://www.home-assistant.io/docs/z-wave/installation/) for informasjon om konfigurasjon variablene", + "description": "Denne integrasjonen opprettholdes ikke lenger. For nye installasjoner, bruk Z-Wave JS i stedet. \n\n Se https://www.home-assistant.io/docs/z-wave/installation/ for informasjon om konfigurasjonsvariablene", "title": "Sett opp Z-Wave" } } From 7f8fa7feafc4d8b48071817f240f67427a764279 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 21:20:40 -0500 Subject: [PATCH 0380/1818] Use core constants for logi_circle (#46359) --- homeassistant/components/logi_circle/__init__.py | 3 +-- homeassistant/components/logi_circle/config_flow.py | 5 ++--- homeassistant/components/logi_circle/const.py | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py index d3551765079340..056783ef6dace6 100644 --- a/homeassistant/components/logi_circle/__init__.py +++ b/homeassistant/components/logi_circle/__init__.py @@ -11,6 +11,7 @@ from homeassistant.components.camera import ATTR_FILENAME, CAMERA_SERVICE_SCHEMA from homeassistant.const import ( ATTR_MODE, + CONF_API_KEY, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_MONITORED_CONDITIONS, @@ -22,7 +23,6 @@ from . import config_flow from .const import ( - CONF_API_KEY, CONF_REDIRECT_URI, DATA_LOGI, DEFAULT_CACHEDB, @@ -117,7 +117,6 @@ async def async_setup(hass, config): async def async_setup_entry(hass, entry): """Set up Logi Circle from a config entry.""" - logi_circle = LogiCircle( client_id=entry.data[CONF_CLIENT_ID], client_secret=entry.data[CONF_CLIENT_SECRET], diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index 279c305201085a..00fd0edc4372f9 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -10,6 +10,7 @@ from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( + CONF_API_KEY, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_SENSORS, @@ -17,7 +18,7 @@ ) from homeassistant.core import callback -from .const import CONF_API_KEY, CONF_REDIRECT_URI, DEFAULT_CACHEDB, DOMAIN +from .const import CONF_REDIRECT_URI, DEFAULT_CACHEDB, DOMAIN _TIMEOUT = 15 # seconds @@ -120,7 +121,6 @@ async def async_step_auth(self, user_input=None): def _get_authorization_url(self): """Create temporary Circle session and generate authorization url.""" - flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl] client_id = flow[CONF_CLIENT_ID] client_secret = flow[CONF_CLIENT_SECRET] @@ -147,7 +147,6 @@ async def async_step_code(self, code=None): async def _async_create_session(self, code): """Create Logi Circle session and entries.""" - flow = self.hass.data[DATA_FLOW_IMPL][DOMAIN] client_id = flow[CONF_CLIENT_ID] client_secret = flow[CONF_CLIENT_SECRET] diff --git a/homeassistant/components/logi_circle/const.py b/homeassistant/components/logi_circle/const.py index fb22338b2c7589..92967d2eb84845 100644 --- a/homeassistant/components/logi_circle/const.py +++ b/homeassistant/components/logi_circle/const.py @@ -4,7 +4,6 @@ DOMAIN = "logi_circle" DATA_LOGI = DOMAIN -CONF_API_KEY = "api_key" CONF_REDIRECT_URI = "redirect_uri" DEFAULT_CACHEDB = ".logi_cache.pickle" From af2fa17e8ebb22c18955c7cedacd29c49d5dac24 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 21:21:35 -0500 Subject: [PATCH 0381/1818] Use core constants for local_file (#46349) --- homeassistant/components/local_file/camera.py | 10 ++-------- homeassistant/components/local_file/const.py | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/local_file/camera.py b/homeassistant/components/local_file/camera.py index 1d06efeb708554..b0b846771835b1 100644 --- a/homeassistant/components/local_file/camera.py +++ b/homeassistant/components/local_file/camera.py @@ -10,16 +10,10 @@ PLATFORM_SCHEMA, Camera, ) -from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME +from homeassistant.const import ATTR_ENTITY_ID, CONF_FILE_PATH, CONF_NAME from homeassistant.helpers import config_validation as cv -from .const import ( - CONF_FILE_PATH, - DATA_LOCAL_FILE, - DEFAULT_NAME, - DOMAIN, - SERVICE_UPDATE_FILE_PATH, -) +from .const import DATA_LOCAL_FILE, DEFAULT_NAME, DOMAIN, SERVICE_UPDATE_FILE_PATH _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/local_file/const.py b/homeassistant/components/local_file/const.py index 5225a70daedc7f..3ea98f89c0e285 100644 --- a/homeassistant/components/local_file/const.py +++ b/homeassistant/components/local_file/const.py @@ -1,6 +1,5 @@ """Constants for the Local File Camera component.""" DOMAIN = "local_file" SERVICE_UPDATE_FILE_PATH = "update_file_path" -CONF_FILE_PATH = "file_path" DATA_LOCAL_FILE = "local_file_cameras" DEFAULT_NAME = "Local File" From 56adc9dadb43aa9045080e00647b5d5447027c9d Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 10 Feb 2021 21:22:32 -0500 Subject: [PATCH 0382/1818] Use core constants for lcn (#46348) --- homeassistant/components/lcn/binary_sensor.py | 4 ++-- homeassistant/components/lcn/climate.py | 8 ++++++-- homeassistant/components/lcn/const.py | 2 -- homeassistant/components/lcn/light.py | 2 -- homeassistant/components/lcn/scene.py | 3 +-- homeassistant/components/lcn/schemas.py | 4 ++-- homeassistant/components/lcn/sensor.py | 3 +-- homeassistant/components/lcn/switch.py | 1 - 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py index 415668f5924b6b..56a5ea6e6462c6 100644 --- a/homeassistant/components/lcn/binary_sensor.py +++ b/homeassistant/components/lcn/binary_sensor.py @@ -2,10 +2,10 @@ import pypck from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_ADDRESS +from homeassistant.const import CONF_ADDRESS, CONF_SOURCE from . import LcnEntity -from .const import BINSENSOR_PORTS, CONF_CONNECTIONS, CONF_SOURCE, DATA_LCN, SETPOINTS +from .const import BINSENSOR_PORTS, CONF_CONNECTIONS, DATA_LCN, SETPOINTS from .helpers import get_connection diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index ece3994f651300..e3269a51cd62f1 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -3,7 +3,12 @@ import pypck from homeassistant.components.climate import ClimateEntity, const -from homeassistant.const import ATTR_TEMPERATURE, CONF_ADDRESS, CONF_UNIT_OF_MEASUREMENT +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_ADDRESS, + CONF_SOURCE, + CONF_UNIT_OF_MEASUREMENT, +) from . import LcnEntity from .const import ( @@ -12,7 +17,6 @@ CONF_MAX_TEMP, CONF_MIN_TEMP, CONF_SETPOINT, - CONF_SOURCE, DATA_LCN, ) from .helpers import get_connection diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index 821a7102154712..3dcac6fb55ff1b 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -25,7 +25,6 @@ CONF_VARIABLE = "variable" CONF_VALUE = "value" CONF_RELVARREF = "value_reference" -CONF_SOURCE = "source" CONF_SETPOINT = "setpoint" CONF_LED = "led" CONF_KEYS = "keys" @@ -40,7 +39,6 @@ CONF_MIN_TEMP = "min_temp" CONF_SCENES = "scenes" CONF_REGISTER = "register" -CONF_SCENE = "scene" CONF_OUTPUTS = "outputs" CONF_REVERSE_TIME = "reverse_time" diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 5242ed1cc59a00..8a76056ff46cc5 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -166,7 +166,6 @@ def is_on(self): async def async_turn_on(self, **kwargs): """Turn the entity on.""" - states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8 states[self.output.value] = pypck.lcn_defs.RelayStateModifier.ON if not await self.device_connection.control_relays(states): @@ -176,7 +175,6 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): """Turn the entity off.""" - states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8 states[self.output.value] = pypck.lcn_defs.RelayStateModifier.OFF if not await self.device_connection.control_relays(states): diff --git a/homeassistant/components/lcn/scene.py b/homeassistant/components/lcn/scene.py index ed211473e29747..1c359607fb2eed 100644 --- a/homeassistant/components/lcn/scene.py +++ b/homeassistant/components/lcn/scene.py @@ -4,14 +4,13 @@ import pypck from homeassistant.components.scene import Scene -from homeassistant.const import CONF_ADDRESS +from homeassistant.const import CONF_ADDRESS, CONF_SCENE from . import LcnEntity from .const import ( CONF_CONNECTIONS, CONF_OUTPUTS, CONF_REGISTER, - CONF_SCENE, CONF_TRANSITION, DATA_LCN, OUTPUT_PORTS, diff --git a/homeassistant/components/lcn/schemas.py b/homeassistant/components/lcn/schemas.py index 1cc51f400da565..5244bac3b6b341 100644 --- a/homeassistant/components/lcn/schemas.py +++ b/homeassistant/components/lcn/schemas.py @@ -11,7 +11,9 @@ CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_SCENE, CONF_SENSORS, + CONF_SOURCE, CONF_SWITCHES, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME, @@ -32,11 +34,9 @@ CONF_OUTPUTS, CONF_REGISTER, CONF_REVERSE_TIME, - CONF_SCENE, CONF_SCENES, CONF_SETPOINT, CONF_SK_NUM_TRIES, - CONF_SOURCE, CONF_TRANSITION, DIM_MODES, DOMAIN, diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 4d4be5e125995f..11932dccea8f9f 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -1,12 +1,11 @@ """Support for LCN sensors.""" import pypck -from homeassistant.const import CONF_ADDRESS, CONF_UNIT_OF_MEASUREMENT +from homeassistant.const import CONF_ADDRESS, CONF_SOURCE, CONF_UNIT_OF_MEASUREMENT from . import LcnEntity from .const import ( CONF_CONNECTIONS, - CONF_SOURCE, DATA_LCN, LED_PORTS, S0_INPUTS, diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 6f9cc25db99033..5fe624b04bfc0c 100644 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -117,7 +117,6 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): """Turn the entity off.""" - states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8 states[self.output.value] = pypck.lcn_defs.RelayStateModifier.OFF if not await self.device_connection.control_relays(states): From 3ffa42e56a0c0792292a2da5b37e6085d32bb824 Mon Sep 17 00:00:00 2001 From: Leonardo Figueiro Date: Thu, 11 Feb 2021 04:25:42 -0300 Subject: [PATCH 0383/1818] Update WiLight Cover Fan Light (#46366) --- homeassistant/components/wilight/__init__.py | 4 ++-- homeassistant/components/wilight/config_flow.py | 2 +- homeassistant/components/wilight/cover.py | 3 +-- homeassistant/components/wilight/fan.py | 9 +++++++-- homeassistant/components/wilight/light.py | 3 +-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/wilight/__init__.py b/homeassistant/components/wilight/__init__.py index 0e08fec2c31e45..67433772551ef6 100644 --- a/homeassistant/components/wilight/__init__.py +++ b/homeassistant/components/wilight/__init__.py @@ -1,8 +1,6 @@ """The WiLight integration.""" import asyncio -from pywilight.const import DOMAIN - from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady @@ -10,6 +8,8 @@ from .parent_device import WiLightParent +DOMAIN = "wilight" + # List the platforms that you want to support. PLATFORMS = ["cover", "fan", "light"] diff --git a/homeassistant/components/wilight/config_flow.py b/homeassistant/components/wilight/config_flow.py index c3148a4d04578d..40643b4372fdfb 100644 --- a/homeassistant/components/wilight/config_flow.py +++ b/homeassistant/components/wilight/config_flow.py @@ -7,7 +7,7 @@ from homeassistant.config_entries import CONN_CLASS_LOCAL_PUSH, ConfigFlow from homeassistant.const import CONF_HOST -DOMAIN = "wilight" +from . import DOMAIN # pylint: disable=unused-import CONF_SERIAL_NUMBER = "serial_number" CONF_MODEL_NAME = "model_name" diff --git a/homeassistant/components/wilight/cover.py b/homeassistant/components/wilight/cover.py index bbe723b413a372..93c9a8c450307e 100644 --- a/homeassistant/components/wilight/cover.py +++ b/homeassistant/components/wilight/cover.py @@ -2,7 +2,6 @@ from pywilight.const import ( COVER_V1, - DOMAIN, ITEM_COVER, WL_CLOSE, WL_CLOSING, @@ -16,7 +15,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from . import WiLightDevice +from . import DOMAIN, WiLightDevice async def async_setup_entry( diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index 948d99ac81db81..ece79874ccf394 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -1,7 +1,6 @@ """Support for WiLight Fan.""" from pywilight.const import ( - DOMAIN, FAN_V1, ITEM_FAN, WL_DIRECTION_FORWARD, @@ -25,7 +24,7 @@ percentage_to_ordered_list_item, ) -from . import WiLightDevice +from . import DOMAIN, WiLightDevice ORDERED_NAMED_FAN_SPEEDS = [WL_SPEED_LOW, WL_SPEED_MEDIUM, WL_SPEED_HIGH] @@ -80,6 +79,9 @@ def is_on(self): @property def percentage(self) -> str: """Return the current speed percentage.""" + if "direction" in self._status: + if self._status["direction"] == WL_DIRECTION_OFF: + return 0 wl_speed = self._status.get("speed") if wl_speed is None: return None @@ -111,6 +113,9 @@ async def async_set_percentage(self, percentage: int): if percentage == 0: await self._client.set_fan_direction(self._index, WL_DIRECTION_OFF) return + if "direction" in self._status: + if self._status["direction"] == WL_DIRECTION_OFF: + await self._client.set_fan_direction(self._index, self._direction) wl_speed = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage) await self._client.set_fan_speed(self._index, wl_speed) diff --git a/homeassistant/components/wilight/light.py b/homeassistant/components/wilight/light.py index 2f3c9e3c5f22cd..0c7206be00cde8 100644 --- a/homeassistant/components/wilight/light.py +++ b/homeassistant/components/wilight/light.py @@ -1,7 +1,6 @@ """Support for WiLight lights.""" from pywilight.const import ( - DOMAIN, ITEM_LIGHT, LIGHT_COLOR, LIGHT_DIMMER, @@ -19,7 +18,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from . import WiLightDevice +from . import DOMAIN, WiLightDevice def entities_from_discovered_wilight(hass, api_device): From f549ec5ec949a1615aaa717fe60df5369a02ec13 Mon Sep 17 00:00:00 2001 From: Mike Keesey Date: Thu, 11 Feb 2021 00:50:27 -0700 Subject: [PATCH 0384/1818] Use activity ids for unique_id for Harmony switches (#46139) --- homeassistant/components/harmony/__init__.py | 33 +++++++++ homeassistant/components/harmony/data.py | 18 +++-- homeassistant/components/harmony/switch.py | 15 ++-- tests/components/harmony/conftest.py | 11 ++- tests/components/harmony/const.py | 5 ++ tests/components/harmony/test_init.py | 72 ++++++++++++++++++++ 6 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 tests/components/harmony/test_init.py diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index 6ba63ee0f81eaa..8445c7be937d85 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -1,16 +1,20 @@ """The Logitech Harmony Hub integration.""" import asyncio +import logging from homeassistant.components.remote import ATTR_ACTIVITY, ATTR_DELAY_SECS from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import entity_registry from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DOMAIN, HARMONY_OPTIONS_UPDATE, PLATFORMS from .data import HarmonyData +_LOGGER = logging.getLogger(__name__) + async def async_setup(hass: HomeAssistant, config: dict): """Set up the Logitech Harmony Hub component.""" @@ -40,6 +44,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = data + await _migrate_old_unique_ids(hass, entry.entry_id, data) + entry.add_update_listener(_update_listener) for component in PLATFORMS: @@ -50,6 +56,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True +async def _migrate_old_unique_ids( + hass: HomeAssistant, entry_id: str, data: HarmonyData +): + names_to_ids = {activity["label"]: activity["id"] for activity in data.activities} + + @callback + def _async_migrator(entity_entry: entity_registry.RegistryEntry): + # Old format for switches was {remote_unique_id}-{activity_name} + # New format is activity_{activity_id} + parts = entity_entry.unique_id.split("-", 1) + if len(parts) > 1: # old format + activity_name = parts[1] + activity_id = names_to_ids.get(activity_name) + + if activity_id is not None: + _LOGGER.info( + "Migrating unique_id from [%s] to [%s]", + entity_entry.unique_id, + activity_id, + ) + return {"new_unique_id": f"activity_{activity_id}"} + + return None + + await entity_registry.async_migrate_entries(hass, entry_id, _async_migrator) + + @callback def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: ConfigEntry): options = dict(entry.options) diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index 6c3ad874fa9d2c..8c1d137bc854f5 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -34,18 +34,22 @@ def __init__(self, hass, address: str, name: str, unique_id: str): ip_address=address, callbacks=ClientCallbackType(**callbacks) ) + @property + def activities(self): + """List of all non-poweroff activity objects.""" + activity_infos = self._client.config.get("activity", []) + return [ + info + for info in activity_infos + if info["label"] is not None and info["label"] != ACTIVITY_POWER_OFF + ] + @property def activity_names(self): """Names of all the remotes activities.""" - activity_infos = self._client.config.get("activity", []) + activity_infos = self.activities activities = [activity["label"] for activity in activity_infos] - # Remove both ways of representing PowerOff - if None in activities: - activities.remove(None) - if ACTIVITY_POWER_OFF in activities: - activities.remove(ACTIVITY_POWER_OFF) - return activities @property diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index 2832872c2eff82..5aac145e749934 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -15,12 +15,12 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up harmony activity switches.""" data = hass.data[DOMAIN][entry.entry_id] - activities = data.activity_names + activities = data.activities switches = [] for activity in activities: _LOGGER.debug("creating switch for activity: %s", activity) - name = f"{entry.data[CONF_NAME]} {activity}" + name = f"{entry.data[CONF_NAME]} {activity['label']}" switches.append(HarmonyActivitySwitch(name, activity, data)) async_add_entities(switches, True) @@ -29,11 +29,12 @@ async def async_setup_entry(hass, entry, async_add_entities): class HarmonyActivitySwitch(ConnectionStateMixin, SwitchEntity): """Switch representation of a Harmony activity.""" - def __init__(self, name: str, activity: str, data: HarmonyData): + def __init__(self, name: str, activity: dict, data: HarmonyData): """Initialize HarmonyActivitySwitch class.""" super().__init__() self._name = name - self._activity = activity + self._activity_name = activity["label"] + self._activity_id = activity["id"] self._data = data @property @@ -44,7 +45,7 @@ def name(self): @property def unique_id(self): """Return the unique id.""" - return f"{self._data.unique_id}-{self._activity}" + return f"activity_{self._activity_id}" @property def device_info(self): @@ -55,7 +56,7 @@ def device_info(self): def is_on(self): """Return if the current activity is the one for this switch.""" _, activity_name = self._data.current_activity - return activity_name == self._activity + return activity_name == self._activity_name @property def should_poll(self): @@ -69,7 +70,7 @@ def available(self): async def async_turn_on(self, **kwargs): """Start this activity.""" - await self._data.async_start_activity(self._activity) + await self._data.async_start_activity(self._activity_name) async def async_turn_off(self, **kwargs): """Stop this activity.""" diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index cde8c43fe89d50..6ca483b2588500 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -7,21 +7,22 @@ from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF -_LOGGER = logging.getLogger(__name__) +from .const import NILE_TV_ACTIVITY_ID, PLAY_MUSIC_ACTIVITY_ID, WATCH_TV_ACTIVITY_ID -WATCH_TV_ACTIVITY_ID = 123 -PLAY_MUSIC_ACTIVITY_ID = 456 +_LOGGER = logging.getLogger(__name__) ACTIVITIES_TO_IDS = { ACTIVITY_POWER_OFF: -1, "Watch TV": WATCH_TV_ACTIVITY_ID, "Play Music": PLAY_MUSIC_ACTIVITY_ID, + "Nile-TV": NILE_TV_ACTIVITY_ID, } IDS_TO_ACTIVITIES = { -1: ACTIVITY_POWER_OFF, WATCH_TV_ACTIVITY_ID: "Watch TV", PLAY_MUSIC_ACTIVITY_ID: "Play Music", + NILE_TV_ACTIVITY_ID: "Nile-TV", } TV_DEVICE_ID = 1234 @@ -111,6 +112,7 @@ def hub_config(self): return_value=[ {"name": "Watch TV", "id": WATCH_TV_ACTIVITY_ID}, {"name": "Play Music", "id": PLAY_MUSIC_ACTIVITY_ID}, + {"name": "Nile-TV", "id": NILE_TV_ACTIVITY_ID}, ] ) type(config).devices = PropertyMock( @@ -121,8 +123,11 @@ def hub_config(self): type(config).config = PropertyMock( return_value={ "activity": [ + {"id": 10000, "label": None}, + {"id": -1, "label": "PowerOff"}, {"id": WATCH_TV_ACTIVITY_ID, "label": "Watch TV"}, {"id": PLAY_MUSIC_ACTIVITY_ID, "label": "Play Music"}, + {"id": NILE_TV_ACTIVITY_ID, "label": "Nile-TV"}, ] } ) diff --git a/tests/components/harmony/const.py b/tests/components/harmony/const.py index 1911ea949aff07..488fe30dec36b1 100644 --- a/tests/components/harmony/const.py +++ b/tests/components/harmony/const.py @@ -4,3 +4,8 @@ ENTITY_REMOTE = "remote.guest_room" ENTITY_WATCH_TV = "switch.guest_room_watch_tv" ENTITY_PLAY_MUSIC = "switch.guest_room_play_music" +ENTITY_NILE_TV = "switch.guest_room_nile_tv" + +WATCH_TV_ACTIVITY_ID = 123 +PLAY_MUSIC_ACTIVITY_ID = 456 +NILE_TV_ACTIVITY_ID = 789 diff --git a/tests/components/harmony/test_init.py b/tests/components/harmony/test_init.py new file mode 100644 index 00000000000000..c63727f87380a3 --- /dev/null +++ b/tests/components/harmony/test_init.py @@ -0,0 +1,72 @@ +"""Test init of Logitch Harmony Hub integration.""" +from homeassistant.components.harmony.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.helpers import entity_registry +from homeassistant.setup import async_setup_component + +from .const import ( + ENTITY_NILE_TV, + ENTITY_PLAY_MUSIC, + ENTITY_WATCH_TV, + HUB_NAME, + NILE_TV_ACTIVITY_ID, + PLAY_MUSIC_ACTIVITY_ID, + WATCH_TV_ACTIVITY_ID, +) + +from tests.common import MockConfigEntry, mock_registry + + +async def test_unique_id_migration(mock_hc, hass, mock_write_config): + """Test migration of switch unique ids to stable ones.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} + ) + + entry.add_to_hass(hass) + mock_registry( + hass, + { + # old format + ENTITY_WATCH_TV: entity_registry.RegistryEntry( + entity_id=ENTITY_WATCH_TV, + unique_id="123443-Watch TV", + platform="harmony", + config_entry_id=entry.entry_id, + ), + # old format, activity name with - + ENTITY_NILE_TV: entity_registry.RegistryEntry( + entity_id=ENTITY_NILE_TV, + unique_id="123443-Nile-TV", + platform="harmony", + config_entry_id=entry.entry_id, + ), + # new format + ENTITY_PLAY_MUSIC: entity_registry.RegistryEntry( + entity_id=ENTITY_PLAY_MUSIC, + unique_id=f"activity_{PLAY_MUSIC_ACTIVITY_ID}", + platform="harmony", + config_entry_id=entry.entry_id, + ), + # old entity which no longer has a matching activity on the hub. skipped. + "switch.some_other_activity": entity_registry.RegistryEntry( + entity_id="switch.some_other_activity", + unique_id="123443-Some Other Activity", + platform="harmony", + config_entry_id=entry.entry_id, + ), + }, + ) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + ent_reg = await entity_registry.async_get_registry(hass) + + switch_tv = ent_reg.async_get(ENTITY_WATCH_TV) + assert switch_tv.unique_id == f"activity_{WATCH_TV_ACTIVITY_ID}" + + switch_nile = ent_reg.async_get(ENTITY_NILE_TV) + assert switch_nile.unique_id == f"activity_{NILE_TV_ACTIVITY_ID}" + + switch_music = ent_reg.async_get(ENTITY_PLAY_MUSIC) + assert switch_music.unique_id == f"activity_{PLAY_MUSIC_ACTIVITY_ID}" From 379f5455e58d691738f550b780c948da4db629d8 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 04:13:18 -0500 Subject: [PATCH 0385/1818] Use core constants for lovelace (#46368) --- homeassistant/components/lovelace/__init__.py | 4 +--- homeassistant/components/lovelace/const.py | 4 +--- homeassistant/components/lovelace/resources.py | 3 +-- homeassistant/components/lovelace/system_health.py | 3 ++- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 99b00a92289c04..e673b2a470b1c8 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -5,7 +5,7 @@ from homeassistant.components import frontend from homeassistant.config import async_hass_config_yaml, async_process_component_config -from homeassistant.const import CONF_FILENAME +from homeassistant.const import CONF_FILENAME, CONF_MODE, CONF_RESOURCES from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection, config_validation as cv @@ -16,9 +16,7 @@ from . import dashboard, resources, websocket from .const import ( CONF_ICON, - CONF_MODE, CONF_REQUIRE_ADMIN, - CONF_RESOURCES, CONF_SHOW_IN_SIDEBAR, CONF_TITLE, CONF_URL_PATH, diff --git a/homeassistant/components/lovelace/const.py b/homeassistant/components/lovelace/const.py index e93649de451f43..6952a80a214a62 100644 --- a/homeassistant/components/lovelace/const.py +++ b/homeassistant/components/lovelace/const.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.const import CONF_ICON, CONF_TYPE, CONF_URL +from homeassistant.const import CONF_ICON, CONF_MODE, CONF_TYPE, CONF_URL from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.util import slugify @@ -13,13 +13,11 @@ DEFAULT_ICON = "hass:view-dashboard" -CONF_MODE = "mode" MODE_YAML = "yaml" MODE_STORAGE = "storage" MODE_AUTO = "auto-gen" LOVELACE_CONFIG_FILE = "ui-lovelace.yaml" -CONF_RESOURCES = "resources" CONF_URL_PATH = "url_path" CONF_RESOURCE_TYPE_WS = "res_type" diff --git a/homeassistant/components/lovelace/resources.py b/homeassistant/components/lovelace/resources.py index 78a23540ed497f..0a3e36892d5015 100644 --- a/homeassistant/components/lovelace/resources.py +++ b/homeassistant/components/lovelace/resources.py @@ -5,14 +5,13 @@ import voluptuous as vol -from homeassistant.const import CONF_TYPE +from homeassistant.const import CONF_RESOURCES, CONF_TYPE from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection, storage from .const import ( CONF_RESOURCE_TYPE_WS, - CONF_RESOURCES, DOMAIN, RESOURCE_CREATE_FIELDS, RESOURCE_SCHEMA, diff --git a/homeassistant/components/lovelace/system_health.py b/homeassistant/components/lovelace/system_health.py index a148427c9bd8cd..2f4cfc6af76dee 100644 --- a/homeassistant/components/lovelace/system_health.py +++ b/homeassistant/components/lovelace/system_health.py @@ -2,9 +2,10 @@ import asyncio from homeassistant.components import system_health +from homeassistant.const import CONF_MODE from homeassistant.core import HomeAssistant, callback -from .const import CONF_MODE, DOMAIN, MODE_AUTO, MODE_STORAGE, MODE_YAML +from .const import DOMAIN, MODE_AUTO, MODE_STORAGE, MODE_YAML @callback From 29d8b8a22fb1eb93fbd75dbdcde2d4c9dcf3c964 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 04:19:39 -0500 Subject: [PATCH 0386/1818] Some code cleanups for ESPHome (#46367) --- homeassistant/components/esphome/__init__.py | 11 +++++------ homeassistant/components/esphome/config_flow.py | 9 ++++----- homeassistant/components/esphome/entry_data.py | 2 -- tests/components/esphome/test_config_flow.py | 12 ++++++------ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 0b3a7522845133..23b4044fc9e940 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -40,8 +40,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType # Import config flow so that it's added to the registry -from .config_flow import EsphomeFlowHandler # noqa: F401 -from .entry_data import DATA_KEY, RuntimeEntryData +from .entry_data import RuntimeEntryData DOMAIN = "esphome" _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up the esphome component.""" - hass.data.setdefault(DATA_KEY, {}) + hass.data.setdefault(DOMAIN, {}) host = entry.data[CONF_HOST] port = entry.data[CONF_PORT] @@ -84,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool store = Store( hass, STORAGE_VERSION, f"esphome.{entry.entry_id}", encoder=JSONEncoder ) - entry_data = hass.data[DATA_KEY][entry.entry_id] = RuntimeEntryData( + entry_data = hass.data[DOMAIN][entry.entry_id] = RuntimeEntryData( client=cli, entry_id=entry.entry_id, store=store ) @@ -363,7 +362,7 @@ async def _cleanup_instance( hass: HomeAssistantType, entry: ConfigEntry ) -> RuntimeEntryData: """Cleanup the esphome client if it exists.""" - data: RuntimeEntryData = hass.data[DATA_KEY].pop(entry.entry_id) + data: RuntimeEntryData = hass.data[DOMAIN].pop(entry.entry_id) if data.reconnect_task is not None: data.reconnect_task.cancel() for disconnect_cb in data.disconnect_callbacks: @@ -545,7 +544,7 @@ def _on_device_update(self) -> None: @property def _entry_data(self) -> RuntimeEntryData: - return self.hass.data[DATA_KEY][self._entry_id] + return self.hass.data[DOMAIN][self._entry_id] @property def _static_info(self) -> EntityInfo: diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 1d1fc421bb8e6e..34b168cdd8cdeb 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -11,9 +11,8 @@ from homeassistant.core import callback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .entry_data import DATA_KEY, RuntimeEntryData - -DOMAIN = "esphome" +from . import DOMAIN +from .entry_data import RuntimeEntryData class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): @@ -107,9 +106,9 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): ]: # Is this address or IP address already configured? already_configured = True - elif entry.entry_id in self.hass.data.get(DATA_KEY, {}): + elif entry.entry_id in self.hass.data.get(DOMAIN, {}): # Does a config entry with this name already exist? - data: RuntimeEntryData = self.hass.data[DATA_KEY][entry.entry_id] + data: RuntimeEntryData = self.hass.data[DOMAIN][entry.entry_id] # Node names are unique in the network if data.device_info is not None: diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 54da1ed5562db2..58b20d18e12f42 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -29,8 +29,6 @@ if TYPE_CHECKING: from . import APIClient -DATA_KEY = "esphome" - SAVE_DELAY = 120 # Mapping from ESPHome info type to HA platform diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index f3afce0d43b109..233255c1a890df 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -4,7 +4,7 @@ import pytest -from homeassistant.components.esphome import DATA_KEY +from homeassistant.components.esphome import DOMAIN from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -207,7 +207,7 @@ async def test_discovery_initiation(hass, mock_client): async def test_discovery_already_configured_hostname(hass, mock_client): """Test discovery aborts if already configured via hostname.""" entry = MockConfigEntry( - domain="esphome", + domain=DOMAIN, data={CONF_HOST: "test8266.local", CONF_PORT: 6053, CONF_PASSWORD: ""}, ) @@ -232,7 +232,7 @@ async def test_discovery_already_configured_hostname(hass, mock_client): async def test_discovery_already_configured_ip(hass, mock_client): """Test discovery aborts if already configured via static IP.""" entry = MockConfigEntry( - domain="esphome", + domain=DOMAIN, data={CONF_HOST: "192.168.43.183", CONF_PORT: 6053, CONF_PASSWORD: ""}, ) @@ -257,14 +257,14 @@ async def test_discovery_already_configured_ip(hass, mock_client): async def test_discovery_already_configured_name(hass, mock_client): """Test discovery aborts if already configured via name.""" entry = MockConfigEntry( - domain="esphome", + domain=DOMAIN, data={CONF_HOST: "192.168.43.183", CONF_PORT: 6053, CONF_PASSWORD: ""}, ) entry.add_to_hass(hass) mock_entry_data = MagicMock() mock_entry_data.device_info.name = "test8266" - hass.data[DATA_KEY] = {entry.entry_id: mock_entry_data} + hass.data[DOMAIN] = {entry.entry_id: mock_entry_data} service_info = { "host": "192.168.43.184", @@ -310,7 +310,7 @@ async def test_discovery_duplicate_data(hass, mock_client): async def test_discovery_updates_unique_id(hass, mock_client): """Test a duplicate discovery host aborts and updates existing entry.""" entry = MockConfigEntry( - domain="esphome", + domain=DOMAIN, data={CONF_HOST: "192.168.43.183", CONF_PORT: 6053, CONF_PASSWORD: ""}, ) From f9f4c0aeede27261d5d5d1aa3b3b21821a92041c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 23:24:31 -1000 Subject: [PATCH 0387/1818] Fix explict return in tesla config flow (#46377) --- homeassistant/components/tesla/config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py index 194ea71a3b75a4..8dce5e238accf6 100644 --- a/homeassistant/components/tesla/config_flow.py +++ b/homeassistant/components/tesla/config_flow.py @@ -106,6 +106,7 @@ def _async_entry_for_username(self, username): for entry in self._async_current_entries(): if entry.data.get(CONF_USERNAME) == username: return entry + return None class OptionsFlowHandler(config_entries.OptionsFlow): From e013ad2413c0d94035b57b970451e9d7fef917ae Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 04:25:43 -0500 Subject: [PATCH 0388/1818] Use core constants for microsoft (#46369) --- homeassistant/components/microsoft/tts.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/microsoft/tts.py b/homeassistant/components/microsoft/tts.py index c349589ebdd84d..bbfe9b0379e2e6 100644 --- a/homeassistant/components/microsoft/tts.py +++ b/homeassistant/components/microsoft/tts.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider -from homeassistant.const import CONF_API_KEY, CONF_TYPE, PERCENTAGE +from homeassistant.const import CONF_API_KEY, CONF_REGION, CONF_TYPE, PERCENTAGE import homeassistant.helpers.config_validation as cv CONF_GENDER = "gender" @@ -15,8 +15,6 @@ CONF_VOLUME = "volume" CONF_PITCH = "pitch" CONF_CONTOUR = "contour" -CONF_REGION = "region" - _LOGGER = logging.getLogger(__name__) SUPPORTED_LANGUAGES = [ From 1f5fb8f28aefebe0ae0cf42f0da5a901c2f994e8 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Thu, 11 Feb 2021 10:30:09 +0100 Subject: [PATCH 0389/1818] Raise ConditionError for template errors (#46245) --- homeassistant/helpers/condition.py | 3 +-- tests/helpers/test_condition.py | 13 ++++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 126513608c7fd8..da4fdab07d79e7 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -475,8 +475,7 @@ def async_template( try: value: str = value_template.async_render(variables, parse_result=False) except TemplateError as ex: - _LOGGER.error("Error during template condition: %s", ex) - return False + raise ConditionError(f"Error in 'template' condition: {ex}") from ex return value.lower() == "true" diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 485c51a8bb7b43..dd1abdecb72110 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,5 +1,5 @@ """Test the condition helper.""" -from logging import ERROR, WARNING +from logging import WARNING from unittest.mock import patch import pytest @@ -1041,19 +1041,14 @@ async def test_extract_devices(): ) -async def test_condition_template_error(hass, caplog): +async def test_condition_template_error(hass): """Test invalid template.""" - caplog.set_level(ERROR) - test = await condition.async_from_config( hass, {"condition": "template", "value_template": "{{ undefined.state }}"} ) - assert not test(hass) - assert len(caplog.records) == 1 - assert caplog.records[0].message.startswith( - "Error during template condition: UndefinedError:" - ) + with pytest.raises(ConditionError, match="template"): + test(hass) async def test_condition_template_invalid_results(hass): From db557a094cd01d785a40da084decb18cb1c48a79 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Wed, 10 Feb 2021 10:01:24 -0800 Subject: [PATCH 0390/1818] Use oauthv3 for Tesla (#45766) --- homeassistant/components/tesla/__init__.py | 4 ++++ homeassistant/components/tesla/config_flow.py | 2 ++ homeassistant/components/tesla/manifest.json | 8 ++++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tesla/test_config_flow.py | 2 ++ 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index 51090d342718a1..8981b269a56d9e 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -103,6 +103,8 @@ def _update_entry(email, data=None, options=None): _update_entry( email, data={ + CONF_USERNAME: email, + CONF_PASSWORD: password, CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN], CONF_TOKEN: info[CONF_TOKEN], }, @@ -136,6 +138,8 @@ async def async_setup_entry(hass, config_entry): try: controller = TeslaAPI( websession, + email=config.get(CONF_USERNAME), + password=config.get(CONF_PASSWORD), refresh_token=config[CONF_TOKEN], access_token=config[CONF_ACCESS_TOKEN], update_interval=config_entry.options.get( diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py index debe896c9cf682..683ef314a06426 100644 --- a/homeassistant/components/tesla/config_flow.py +++ b/homeassistant/components/tesla/config_flow.py @@ -140,6 +140,8 @@ async def validate_input(hass: core.HomeAssistant, data): (config[CONF_TOKEN], config[CONF_ACCESS_TOKEN]) = await controller.connect( test_login=True ) + config[CONF_USERNAME] = data[CONF_USERNAME] + config[CONF_PASSWORD] = data[CONF_PASSWORD] except TeslaException as ex: if ex.code == HTTP_UNAUTHORIZED: _LOGGER.error("Invalid credentials: %s", ex) diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index 3679c0f74d1012..9236aae7fb6eea 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -3,11 +3,11 @@ "name": "Tesla", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", - "requirements": ["teslajsonpy==0.10.4"], + "requirements": ["teslajsonpy==0.11.5"], "codeowners": ["@zabuldon", "@alandtse"], "dhcp": [ - {"hostname":"tesla_*","macaddress":"4CFCAA*"}, - {"hostname":"tesla_*","macaddress":"044EAF*"}, - {"hostname":"tesla_*","macaddress":"98ED5C*"} + { "hostname": "tesla_*", "macaddress": "4CFCAA*" }, + { "hostname": "tesla_*", "macaddress": "044EAF*" }, + { "hostname": "tesla_*", "macaddress": "98ED5C*" } ] } diff --git a/requirements_all.txt b/requirements_all.txt index b3e9b89a30cf2c..684c8f0f50aed5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2181,7 +2181,7 @@ temperusb==1.5.3 tesla-powerwall==0.3.3 # homeassistant.components.tesla -teslajsonpy==0.10.4 +teslajsonpy==0.11.5 # homeassistant.components.tensorflow # tf-models-official==2.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d634190551250e..8f65280eefa5d9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1090,7 +1090,7 @@ tellduslive==0.10.11 tesla-powerwall==0.3.3 # homeassistant.components.tesla -teslajsonpy==0.10.4 +teslajsonpy==0.11.5 # homeassistant.components.toon toonapi==0.2.0 diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index 7fb308ecc43ef4..136633c9a5cea0 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -48,6 +48,8 @@ async def test_form(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result2["title"] == "test@email.com" assert result2["data"] == { + CONF_USERNAME: "test@email.com", + CONF_PASSWORD: "test", CONF_TOKEN: "test-refresh-token", CONF_ACCESS_TOKEN: "test-access-token", } From d2d2bed16be2847d8e5196d6d4c435a4f3b9b49d Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Wed, 10 Feb 2021 13:30:52 -0700 Subject: [PATCH 0391/1818] Bump pymyq to 3.0.1 (#46079) Co-authored-by: J. Nick Koston --- homeassistant/components/myq/__init__.py | 12 +- homeassistant/components/myq/binary_sensor.py | 7 +- homeassistant/components/myq/const.py | 20 ++-- homeassistant/components/myq/cover.py | 103 ++++++++---------- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/myq/util.py | 34 ++++-- 8 files changed, 96 insertions(+), 86 deletions(-) diff --git a/homeassistant/components/myq/__init__.py b/homeassistant/components/myq/__init__.py index 959000da3b3abd..6b3a52ba7b06f1 100644 --- a/homeassistant/components/myq/__init__.py +++ b/homeassistant/components/myq/__init__.py @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, PLATFORMS, UPDATE_INTERVAL @@ -40,11 +40,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except MyQError as err: raise ConfigEntryNotReady from err + # Called by DataUpdateCoordinator, allows to capture any MyQError exceptions and to throw an HASS UpdateFailed + # exception instead, preventing traceback in HASS logs. + async def async_update_data(): + try: + return await myq.update_device_info() + except MyQError as err: + raise UpdateFailed(str(err)) from err + coordinator = DataUpdateCoordinator( hass, _LOGGER, name="myq devices", - update_method=myq.update_device_info, + update_method=async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) diff --git a/homeassistant/components/myq/binary_sensor.py b/homeassistant/components/myq/binary_sensor.py index 57bd2451d2ab2c..e3832458b9bd2c 100644 --- a/homeassistant/components/myq/binary_sensor.py +++ b/homeassistant/components/myq/binary_sensor.py @@ -1,7 +1,5 @@ """Support for MyQ gateways.""" from pymyq.const import ( - DEVICE_FAMILY as MYQ_DEVICE_FAMILY, - DEVICE_FAMILY_GATEWAY as MYQ_DEVICE_FAMILY_GATEWAY, DEVICE_STATE as MYQ_DEVICE_STATE, DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE, KNOWN_MODELS, @@ -25,9 +23,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] - for device in myq.devices.values(): - if device.device_json[MYQ_DEVICE_FAMILY] == MYQ_DEVICE_FAMILY_GATEWAY: - entities.append(MyQBinarySensorEntity(coordinator, device)) + for device in myq.gateways.values(): + entities.append(MyQBinarySensorEntity(coordinator, device)) async_add_entities(entities, True) diff --git a/homeassistant/components/myq/const.py b/homeassistant/components/myq/const.py index 9251bce7447ea3..6189b1601eaa1f 100644 --- a/homeassistant/components/myq/const.py +++ b/homeassistant/components/myq/const.py @@ -1,9 +1,9 @@ """The MyQ integration.""" -from pymyq.device import ( - STATE_CLOSED as MYQ_STATE_CLOSED, - STATE_CLOSING as MYQ_STATE_CLOSING, - STATE_OPEN as MYQ_STATE_OPEN, - STATE_OPENING as MYQ_STATE_OPENING, +from pymyq.garagedoor import ( + STATE_CLOSED as MYQ_COVER_STATE_CLOSED, + STATE_CLOSING as MYQ_COVER_STATE_CLOSING, + STATE_OPEN as MYQ_COVER_STATE_OPEN, + STATE_OPENING as MYQ_COVER_STATE_OPENING, ) from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING @@ -13,10 +13,10 @@ PLATFORMS = ["cover", "binary_sensor"] MYQ_TO_HASS = { - MYQ_STATE_CLOSED: STATE_CLOSED, - MYQ_STATE_CLOSING: STATE_CLOSING, - MYQ_STATE_OPEN: STATE_OPEN, - MYQ_STATE_OPENING: STATE_OPENING, + MYQ_COVER_STATE_CLOSED: STATE_CLOSED, + MYQ_COVER_STATE_CLOSING: STATE_CLOSING, + MYQ_COVER_STATE_OPEN: STATE_OPEN, + MYQ_COVER_STATE_OPENING: STATE_OPENING, } MYQ_GATEWAY = "myq_gateway" @@ -24,7 +24,7 @@ # myq has some ratelimits in place # and 61 seemed to be work every time -UPDATE_INTERVAL = 61 +UPDATE_INTERVAL = 15 # Estimated time it takes myq to start transition from one # state to the next. diff --git a/homeassistant/components/myq/cover.py b/homeassistant/components/myq/cover.py index 6fef6b25bab22c..e26a969e724bde 100644 --- a/homeassistant/components/myq/cover.py +++ b/homeassistant/components/myq/cover.py @@ -1,14 +1,14 @@ """Support for MyQ-Enabled Garage Doors.""" -import time +import logging from pymyq.const import ( DEVICE_STATE as MYQ_DEVICE_STATE, DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE, - DEVICE_TYPE as MYQ_DEVICE_TYPE, DEVICE_TYPE_GATE as MYQ_DEVICE_TYPE_GATE, KNOWN_MODELS, MANUFACTURER, ) +from pymyq.errors import MyQError from homeassistant.components.cover import ( DEVICE_CLASS_GARAGE, @@ -17,19 +17,12 @@ SUPPORT_OPEN, CoverEntity, ) -from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING -from homeassistant.core import callback -from homeassistant.helpers.event import async_call_later +from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ( - DOMAIN, - MYQ_COORDINATOR, - MYQ_GATEWAY, - MYQ_TO_HASS, - TRANSITION_COMPLETE_DURATION, - TRANSITION_START_DURATION, -) +from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, MYQ_TO_HASS + +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): @@ -50,13 +43,11 @@ def __init__(self, coordinator, device): """Initialize with API object, device id.""" super().__init__(coordinator) self._device = device - self._last_action_timestamp = 0 - self._scheduled_transition_update = None @property def device_class(self): """Define this cover as a garage door.""" - device_type = self._device.device_json.get(MYQ_DEVICE_TYPE) + device_type = self._device.device_type if device_type is not None and device_type == MYQ_DEVICE_TYPE_GATE: return DEVICE_CLASS_GATE return DEVICE_CLASS_GARAGE @@ -87,6 +78,11 @@ def is_closing(self): """Return if the cover is closing or not.""" return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSING + @property + def is_open(self): + """Return if the cover is opening or not.""" + return MYQ_TO_HASS.get(self._device.state) == STATE_OPEN + @property def is_opening(self): """Return if the cover is opening or not.""" @@ -104,37 +100,48 @@ def unique_id(self): async def async_close_cover(self, **kwargs): """Issue close command to cover.""" - self._last_action_timestamp = time.time() - await self._device.close() - self._async_schedule_update_for_transition() + if self.is_closing or self.is_closed: + return + + try: + wait_task = await self._device.close(wait_for_state=False) + except MyQError as err: + _LOGGER.error( + "Closing of cover %s failed with error: %s", self._device.name, str(err) + ) + + return + + # Write closing state to HASS + self.async_write_ha_state() + + if not await wait_task: + _LOGGER.error("Closing of cover %s failed", self._device.name) + + # Write final state to HASS + self.async_write_ha_state() async def async_open_cover(self, **kwargs): """Issue open command to cover.""" - self._last_action_timestamp = time.time() - await self._device.open() - self._async_schedule_update_for_transition() + if self.is_opening or self.is_open: + return - @callback - def _async_schedule_update_for_transition(self): + try: + wait_task = await self._device.open(wait_for_state=False) + except MyQError as err: + _LOGGER.error( + "Opening of cover %s failed with error: %s", self._device.name, str(err) + ) + return + + # Write opening state to HASS self.async_write_ha_state() - # Cancel any previous updates - if self._scheduled_transition_update: - self._scheduled_transition_update() - - # Schedule an update for when we expect the transition - # to be completed so the garage door or gate does not - # seem like its closing or opening for a long time - self._scheduled_transition_update = async_call_later( - self.hass, - TRANSITION_COMPLETE_DURATION, - self._async_complete_schedule_update, - ) + if not await wait_task: + _LOGGER.error("Opening of cover %s failed", self._device.name) - async def _async_complete_schedule_update(self, _): - """Update status of the cover via coordinator.""" - self._scheduled_transition_update = None - await self.coordinator.async_request_refresh() + # Write final state to HASS + self.async_write_ha_state() @property def device_info(self): @@ -152,22 +159,8 @@ def device_info(self): device_info["via_device"] = (DOMAIN, self._device.parent_device_id) return device_info - @callback - def _async_consume_update(self): - if time.time() - self._last_action_timestamp <= TRANSITION_START_DURATION: - # If we just started a transition we need - # to prevent a bouncy state - return - - self.async_write_ha_state() - async def async_added_to_hass(self): """Subscribe to updates.""" self.async_on_remove( - self.coordinator.async_add_listener(self._async_consume_update) + self.coordinator.async_add_listener(self.async_write_ha_state) ) - - async def async_will_remove_from_hass(self): - """Undo subscription.""" - if self._scheduled_transition_update: - self._scheduled_transition_update() diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index aba2f24b5bd1ec..9dc8719ed4e87a 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.14"], + "requirements": ["pymyq==3.0.1"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 684c8f0f50aed5..4aca655d2e1b20 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1545,7 +1545,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.14 +pymyq==3.0.1 # homeassistant.components.mysensors pymysensors==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8f65280eefa5d9..bd6aeec6894dc3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -802,7 +802,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.14 +pymyq==3.0.1 # homeassistant.components.nut pynut2==2.1.2 diff --git a/tests/components/myq/util.py b/tests/components/myq/util.py index 84e85723918bf2..8cb0d17f592f9f 100644 --- a/tests/components/myq/util.py +++ b/tests/components/myq/util.py @@ -1,14 +1,18 @@ """Tests for the myq integration.""" - import json +import logging from unittest.mock import patch +from pymyq.const import ACCOUNTS_ENDPOINT, DEVICES_ENDPOINT + from homeassistant.components.myq.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture +_LOGGER = logging.getLogger(__name__) + async def async_init_integration( hass: HomeAssistant, @@ -20,16 +24,24 @@ async def async_init_integration( devices_json = load_fixture(devices_fixture) devices_dict = json.loads(devices_json) - def _handle_mock_api_request(method, endpoint, **kwargs): - if endpoint == "Login": - return {"SecurityToken": 1234} - if endpoint == "My": - return {"Account": {"Id": 1}} - if endpoint == "Accounts/1/Devices": - return devices_dict - return {} - - with patch("pymyq.api.API.request", side_effect=_handle_mock_api_request): + def _handle_mock_api_oauth_authenticate(): + return 1234, 1800 + + def _handle_mock_api_request(method, returns, url, **kwargs): + _LOGGER.debug("URL: %s", url) + if url == ACCOUNTS_ENDPOINT: + _LOGGER.debug("Accounts") + return None, {"accounts": [{"id": 1, "name": "mock"}]} + if url == DEVICES_ENDPOINT.format(account_id=1): + _LOGGER.debug("Devices") + return None, devices_dict + _LOGGER.debug("Something else") + return None, {} + + with patch( + "pymyq.api.API._oauth_authenticate", + side_effect=_handle_mock_api_oauth_authenticate, + ), patch("pymyq.api.API.request", side_effect=_handle_mock_api_request): entry = MockConfigEntry( domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"} ) From 30ddfd837a9f28f8826625d654b425acd8c27f55 Mon Sep 17 00:00:00 2001 From: "J.P. Hutchins" <34154542+JPHutchins@users.noreply.github.com> Date: Wed, 10 Feb 2021 11:53:31 -0800 Subject: [PATCH 0392/1818] Revert transmission to check torrent lists by name rather than object (#46190) --- .../components/transmission/__init__.py | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index d020bfe97450f9..76d9aedd8d53d9 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -397,42 +397,49 @@ def init_torrent_list(self): def check_completed_torrent(self): """Get completed torrent functionality.""" + old_completed_torrent_names = { + torrent.name for torrent in self._completed_torrents + } + current_completed_torrents = [ torrent for torrent in self._torrents if torrent.status == "seeding" ] - freshly_completed_torrents = set(current_completed_torrents).difference( - self._completed_torrents - ) - self._completed_torrents = current_completed_torrents - for torrent in freshly_completed_torrents: - self.hass.bus.fire( - EVENT_DOWNLOADED_TORRENT, {"name": torrent.name, "id": torrent.id} - ) + for torrent in current_completed_torrents: + if torrent.name not in old_completed_torrent_names: + self.hass.bus.fire( + EVENT_DOWNLOADED_TORRENT, {"name": torrent.name, "id": torrent.id} + ) + + self._completed_torrents = current_completed_torrents def check_started_torrent(self): """Get started torrent functionality.""" + old_started_torrent_names = {torrent.name for torrent in self._started_torrents} + current_started_torrents = [ torrent for torrent in self._torrents if torrent.status == "downloading" ] - freshly_started_torrents = set(current_started_torrents).difference( - self._started_torrents - ) - self._started_torrents = current_started_torrents - for torrent in freshly_started_torrents: - self.hass.bus.fire( - EVENT_STARTED_TORRENT, {"name": torrent.name, "id": torrent.id} - ) + for torrent in current_started_torrents: + if torrent.name not in old_started_torrent_names: + self.hass.bus.fire( + EVENT_STARTED_TORRENT, {"name": torrent.name, "id": torrent.id} + ) + + self._started_torrents = current_started_torrents def check_removed_torrent(self): """Get removed torrent functionality.""" - freshly_removed_torrents = set(self._all_torrents).difference(self._torrents) - self._all_torrents = self._torrents - for torrent in freshly_removed_torrents: - self.hass.bus.fire( - EVENT_REMOVED_TORRENT, {"name": torrent.name, "id": torrent.id} - ) + current_torrent_names = {torrent.name for torrent in self._torrents} + + for torrent in self._all_torrents: + if torrent.name not in current_torrent_names: + self.hass.bus.fire( + EVENT_REMOVED_TORRENT, {"name": torrent.name, "id": torrent.id} + ) + + self._all_torrents = self._torrents.copy() def start_torrents(self): """Start all torrents.""" From b5bdd7f2cffec0eebcea151aec0175fed32c68ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 09:50:38 -1000 Subject: [PATCH 0393/1818] Update powerwall for new authentication requirements (#46254) Co-authored-by: badguy99 <61918526+badguy99@users.noreply.github.com> --- .../components/powerwall/__init__.py | 82 ++++++++++++---- .../components/powerwall/config_flow.py | 58 ++++++++---- .../components/powerwall/manifest.json | 2 +- .../components/powerwall/strings.json | 10 +- .../components/powerwall/translations/en.json | 10 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/powerwall/test_config_flow.py | 94 ++++++++++++++++--- 8 files changed, 201 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 54b7310b7adb32..b392b71374142d 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -4,10 +4,15 @@ import logging import requests -from tesla_powerwall import MissingAttributeError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import ( + AccessDeniedError, + MissingAttributeError, + Powerwall, + PowerwallUnreachableError, +) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry @@ -93,11 +98,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN].setdefault(entry_id, {}) http_session = requests.Session() + + password = entry.data.get(CONF_PASSWORD) power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session) try: - await hass.async_add_executor_job(power_wall.detect_and_pin_version) - await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) - powerwall_data = await hass.async_add_executor_job(call_base_info, power_wall) + powerwall_data = await hass.async_add_executor_job( + _login_and_fetch_base_info, power_wall, password + ) except PowerwallUnreachableError as err: http_session.close() raise ConfigEntryNotReady from err @@ -105,6 +112,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): http_session.close() await _async_handle_api_changed_error(hass, err) return False + except AccessDeniedError as err: + _LOGGER.debug("Authentication failed", exc_info=err) + http_session.close() + _async_start_reauth(hass, entry) + return False await _migrate_old_unique_ids(hass, entry_id, powerwall_data) @@ -112,22 +124,20 @@ async def async_update_data(): """Fetch data from API endpoint.""" # Check if we had an error before _LOGGER.debug("Checking if update failed") - if not hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: - _LOGGER.debug("Updating 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 - else: + if hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data + _LOGGER.debug("Updating data") + try: + return await _async_update_powerwall_data(hass, entry, power_wall) + except AccessDeniedError: + if password is None: + raise + + # If the session expired, relogin, and try again + await hass.async_add_executor_job(power_wall.login, "", password) + return await _async_update_powerwall_data(hass, entry, power_wall) + coordinator = DataUpdateCoordinator( hass, _LOGGER, @@ -156,6 +166,40 @@ async def async_update_data(): 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 _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "reauth"}, + data=entry.data, + ) + ) + _LOGGER.error("Password is no longer valid. Please reauthenticate") + + +def _login_and_fetch_base_info(power_wall: Powerwall, password: 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 call_base_info(power_wall) + + def call_base_info(power_wall): """Wrap powerwall properties to be a callable.""" serial_numbers = power_wall.get_serial_numbers() diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 37ee2730bb49d1..b649b16008535f 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,12 +1,17 @@ """Config flow for Tesla Powerwall integration.""" import logging -from tesla_powerwall import MissingAttributeError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import ( + AccessDeniedError, + MissingAttributeError, + Powerwall, + PowerwallUnreachableError, +) import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.components.dhcp import IP_ADDRESS -from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.core import callback from .const import DOMAIN # pylint:disable=unused-import @@ -14,6 +19,14 @@ _LOGGER = logging.getLogger(__name__) +def _login_and_fetch_site_info(power_wall: Powerwall, password: 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() + + async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. @@ -21,12 +34,12 @@ async def validate_input(hass: core.HomeAssistant, data): """ power_wall = Powerwall(data[CONF_IP_ADDRESS]) + password = data[CONF_PASSWORD] try: - await hass.async_add_executor_job(power_wall.detect_and_pin_version) - site_info = await hass.async_add_executor_job(power_wall.get_site_info) - except PowerwallUnreachableError as err: - raise CannotConnect from err + site_info = await hass.async_add_executor_job( + _login_and_fetch_site_info, power_wall, password + ) except MissingAttributeError as err: # Only log the exception without the traceback _LOGGER.error(str(err)) @@ -62,27 +75,44 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: info = await validate_input(self.hass, user_input) - except CannotConnect: - errors["base"] = "cannot_connect" + 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" - if "base" not in errors: - await self.async_set_unique_id(user_input[CONF_IP_ADDRESS]) - self._abort_if_unique_id_configured() + 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 + ) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") return self.async_create_entry(title=info["title"], data=user_input) return self.async_show_form( step_id="user", data_schema=vol.Schema( - {vol.Required(CONF_IP_ADDRESS, default=self.ip_address): str} + { + vol.Required(CONF_IP_ADDRESS, default=self.ip_address): str, + 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() + @callback def _async_ip_address_already_configured(self, ip_address): """See if we already have an entry matching the ip_address.""" @@ -92,9 +122,5 @@ def _async_ip_address_already_configured(self, ip_address): return False -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" - - class WrongVersion(exceptions.HomeAssistantError): """Error to indicate the powerwall uses a software version we cannot interact with.""" diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 6b7b147d3c5d35..40d0a6c50fe117 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.3"], + "requirements": ["tesla-powerwall==0.3.5"], "codeowners": ["@bdraco", "@jrester"], "dhcp": [ {"hostname":"1118431-*","macaddress":"88DA1A*"}, diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index ac0d9568154166..c576d931756b98 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -4,18 +4,22 @@ "step": { "user": { "title": "Connect to the powerwall", + "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Telsa app; or the last 5 characters of the password found inside the door for Backup Gateway 2.", "data": { - "ip_address": "[%key:common::config_flow::data::ip%]" + "ip_address": "[%key:common::config_flow::data::ip%]", + "password": "[%key:common::config_flow::data::password%]" } } }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "[%key:common::config_flow::error::unknown%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "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/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 6eb0b77708da4c..4ebe1e9d5efaa7 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/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", + "invalid_auth": "Invalid authentication", "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." }, @@ -12,10 +14,12 @@ "step": { "user": { "data": { - "ip_address": "IP Address" + "ip_address": "IP Address", + "password": "Password" }, + "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Telsa app; or the last 5 characters of the password found inside the door for Backup Gateway 2.", "title": "Connect to the powerwall" } } } -} \ No newline at end of file +} diff --git a/requirements_all.txt b/requirements_all.txt index 4aca655d2e1b20..d014df91d46a58 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2178,7 +2178,7 @@ temperusb==1.5.3 # tensorflow==2.3.0 # homeassistant.components.powerwall -tesla-powerwall==0.3.3 +tesla-powerwall==0.3.5 # homeassistant.components.tesla teslajsonpy==0.11.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bd6aeec6894dc3..12836fea643757 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1087,7 +1087,7 @@ synologydsm-api==1.0.1 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.3.3 +tesla-powerwall==0.3.5 # homeassistant.components.tesla teslajsonpy==0.11.5 diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index 0955c16c9ec29f..be071b4594779a 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -2,17 +2,23 @@ from unittest.mock import patch -from tesla_powerwall import MissingAttributeError, PowerwallUnreachableError +from tesla_powerwall import ( + AccessDeniedError, + MissingAttributeError, + PowerwallUnreachableError, +) from homeassistant import config_entries, setup from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS, MAC_ADDRESS from homeassistant.components.powerwall.const import DOMAIN -from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from .mocks import _mock_powerwall_side_effect, _mock_powerwall_site_name from tests.common import MockConfigEntry +VALID_CONFIG = {CONF_IP_ADDRESS: "1.2.3.4", CONF_PASSWORD: "00GGX"} + async def test_form_source_user(hass): """Test we get config flow setup form as a user.""" @@ -36,13 +42,13 @@ async def test_form_source_user(hass): ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + VALID_CONFIG, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "My site" - assert result2["data"] == {CONF_IP_ADDRESS: "1.2.3.4"} + assert result2["data"] == VALID_CONFIG assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -61,11 +67,32 @@ async def test_form_cannot_connect(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + VALID_CONFIG, ) assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} + assert result2["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + +async def test_invalid_auth(hass): + """Test we handle invalid auth error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_powerwall = _mock_powerwall_side_effect(site_info=AccessDeniedError("any")) + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + VALID_CONFIG, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_PASSWORD: "invalid_auth"} async def test_form_unknown_exeption(hass): @@ -81,8 +108,7 @@ async def test_form_unknown_exeption(hass): return_value=mock_powerwall, ): result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + result["flow_id"], VALID_CONFIG ) assert result2["type"] == "form" @@ -105,7 +131,7 @@ async def test_form_wrong_version(hass): ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_IP_ADDRESS: "1.2.3.4"}, + VALID_CONFIG, ) assert result3["type"] == "form" @@ -178,16 +204,54 @@ async def test_dhcp_discovery(hass): ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - CONF_IP_ADDRESS: "1.1.1.1", - }, + VALID_CONFIG, ) await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Some site" - assert result2["data"] == { - CONF_IP_ADDRESS: "1.1.1.1", - } + assert result2["data"] == VALID_CONFIG + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_reauth(hass): + """Test reauthenticate.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data=VALID_CONFIG, + unique_id="1.2.3.4", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=entry.data + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_powerwall = await _mock_powerwall_site_name(hass, "My site") + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), patch( + "homeassistant.components.powerwall.async_setup", return_value=True + ) as mock_setup, 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"], + { + CONF_IP_ADDRESS: "1.2.3.4", + CONF_PASSWORD: "new-test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 From bf2a34600accbe6412eefe248b162a45fa62de1f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 09:48:15 -1000 Subject: [PATCH 0394/1818] Fix Lutron Integration Protocol reconnect logic (#46264) --- 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 34ab75dc0cd764..88c6eddd0bf294 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -3,7 +3,7 @@ "name": "Lutron Caséta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta", "requirements": [ - "pylutron-caseta==0.9.0", "aiolip==1.0.1" + "pylutron-caseta==0.9.0", "aiolip==1.1.4" ], "config_flow": true, "zeroconf": ["_leap._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index d014df91d46a58..b41919fe15ba8b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -197,7 +197,7 @@ aiolifx==0.6.9 aiolifx_effects==0.2.2 # homeassistant.components.lutron_caseta -aiolip==1.0.1 +aiolip==1.1.4 # homeassistant.components.keyboard_remote aionotify==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 12836fea643757..5a7331154b7ec8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -116,7 +116,7 @@ aiohue==2.1.0 aiokafka==0.6.0 # homeassistant.components.lutron_caseta -aiolip==1.0.1 +aiolip==1.1.4 # homeassistant.components.notion aionotion==1.1.0 From 1c2f72a453ccfd0f77701f353a029b1c0b394290 Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Wed, 10 Feb 2021 09:27:25 +0000 Subject: [PATCH 0395/1818] Bump roonapi to 0.0.32 (#46286) --- homeassistant/components/roon/manifest.json | 2 +- homeassistant/components/roon/server.py | 11 ----------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/roon/manifest.json b/homeassistant/components/roon/manifest.json index 0d5d0c131ae491..e4c4a25dcb540e 100644 --- a/homeassistant/components/roon/manifest.json +++ b/homeassistant/components/roon/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roon", "requirements": [ - "roonapi==0.0.31" + "roonapi==0.0.32" ], "codeowners": [ "@pavoni" diff --git a/homeassistant/components/roon/server.py b/homeassistant/components/roon/server.py index d5b8d81c2aa5f4..83b620e176ea81 100644 --- a/homeassistant/components/roon/server.py +++ b/homeassistant/components/roon/server.py @@ -141,17 +141,6 @@ async def async_update_players(self): async_dispatcher_send(self.hass, "roon_media_player", player_data) self.offline_devices.add(dev_id) - async def async_update_playlists(self): - """Store lists in memory with all playlists - could be used by a custom lovelace card.""" - all_playlists = [] - roon_playlists = self.roonapi.playlists() - if roon_playlists and "items" in roon_playlists: - all_playlists += [item["title"] for item in roon_playlists["items"]] - roon_playlists = self.roonapi.internet_radio() - if roon_playlists and "items" in roon_playlists: - all_playlists += [item["title"] for item in roon_playlists["items"]] - self.all_playlists = all_playlists - async def async_create_player_data(self, zone, output): """Create player object dict by combining zone with output.""" new_dict = zone.copy() diff --git a/requirements_all.txt b/requirements_all.txt index b41919fe15ba8b..83c93c0aa4db91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1961,7 +1961,7 @@ rokuecp==0.6.0 roombapy==1.6.2 # homeassistant.components.roon -roonapi==0.0.31 +roonapi==0.0.32 # homeassistant.components.rova rova==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a7331154b7ec8..dbc0e12fcb8ebf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -983,7 +983,7 @@ rokuecp==0.6.0 roombapy==1.6.2 # homeassistant.components.roon -roonapi==0.0.31 +roonapi==0.0.32 # homeassistant.components.rpi_power rpi-bad-power==0.1.0 From 70af3e4776d8955589e5ea7eddf49119006d0d80 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 10 Feb 2021 16:30:16 +0100 Subject: [PATCH 0396/1818] Add guards for missing value in binary_sensor platform of zwave_js integration (#46293) --- homeassistant/components/zwave_js/binary_sensor.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index bb2e4355f168f2..ae5444b707913b 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -258,8 +258,10 @@ class ZWaveBooleanBinarySensor(ZWaveBaseEntity, BinarySensorEntity): """Representation of a Z-Wave binary_sensor.""" @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: """Return if the sensor is on or off.""" + if self.info.primary_value.value is None: + return None return bool(self.info.primary_value.value) @property @@ -301,8 +303,10 @@ def __init__( self._mapping_info = self._get_sensor_mapping() @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: """Return if the sensor is on or off.""" + if self.info.primary_value.value is None: + return None return int(self.info.primary_value.value) == int(self.state_key) @property @@ -349,8 +353,10 @@ def __init__( self._mapping_info = self._get_sensor_mapping() @property - def is_on(self) -> bool: + def is_on(self) -> Optional[bool]: """Return if the sensor is on or off.""" + if self.info.primary_value.value is None: + return None return self.info.primary_value.value in self._mapping_info["on_states"] @property From 9f839b8c613cc2a50b5d4047c59281f13242b0da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Feb 2021 09:55:06 -1000 Subject: [PATCH 0397/1818] Add reauth support for tesla (#46307) --- homeassistant/components/tesla/__init__.py | 43 +++++-- homeassistant/components/tesla/config_flow.py | 105 ++++++++++-------- homeassistant/components/tesla/strings.json | 4 + .../components/tesla/translations/en.json | 4 + tests/components/tesla/test_config_flow.py | 39 ++++++- 5 files changed, 138 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index 8981b269a56d9e..b31f8ae6dd39df 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -5,10 +5,11 @@ import logging import async_timeout -from teslajsonpy import Controller as TeslaAPI, TeslaException +from teslajsonpy import Controller as TeslaAPI +from teslajsonpy.exceptions import IncompleteCredentials, TeslaException import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, @@ -17,8 +18,9 @@ CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_USERNAME, + HTTP_UNAUTHORIZED, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.update_coordinator import ( @@ -28,12 +30,7 @@ ) from homeassistant.util import slugify -from .config_flow import ( - CannotConnect, - InvalidAuth, - configured_instances, - validate_input, -) +from .config_flow import CannotConnect, InvalidAuth, validate_input from .const import ( CONF_WAKE_ON_START, DATA_LISTENER, @@ -75,6 +72,16 @@ def _async_save_tokens(hass, config_entry, access_token, refresh_token): ) +@callback +def _async_configured_emails(hass): + """Return a set of configured Tesla emails.""" + return { + entry.data[CONF_USERNAME] + for entry in hass.config_entries.async_entries(DOMAIN) + if CONF_USERNAME in entry.data + } + + async def async_setup(hass, base_config): """Set up of Tesla component.""" @@ -95,7 +102,7 @@ def _update_entry(email, data=None, options=None): email = config[CONF_USERNAME] password = config[CONF_PASSWORD] scan_interval = config[CONF_SCAN_INTERVAL] - if email in configured_instances(hass): + if email in _async_configured_emails(hass): try: info = await validate_input(hass, config) except (CannotConnect, InvalidAuth): @@ -151,7 +158,12 @@ async def async_setup_entry(hass, config_entry): CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START ) ) + except IncompleteCredentials: + _async_start_reauth(hass, config_entry) + return False except TeslaException as ex: + if ex.code == HTTP_UNAUTHORIZED: + _async_start_reauth(hass, config_entry) _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message) return False _async_save_tokens(hass, config_entry, access_token, refresh_token) @@ -206,6 +218,17 @@ async def async_unload_entry(hass, config_entry) -> bool: return False +def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "reauth"}, + data=entry.data, + ) + ) + _LOGGER.error("Credentials are no longer valid. Please reauthenticate") + + async def update_listener(hass, config_entry): """Update when config_entry options update.""" controller = hass.data[DOMAIN][config_entry.entry_id]["coordinator"].controller diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py index 683ef314a06426..194ea71a3b75a4 100644 --- a/homeassistant/components/tesla/config_flow.py +++ b/homeassistant/components/tesla/config_flow.py @@ -20,22 +20,12 @@ CONF_WAKE_ON_START, DEFAULT_SCAN_INTERVAL, DEFAULT_WAKE_ON_START, - DOMAIN, MIN_SCAN_INTERVAL, ) +from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema( - {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} -) - - -@callback -def configured_instances(hass): - """Return a set of configured Tesla instances.""" - return {entry.title for entry in hass.config_entries.async_entries(DOMAIN)} - class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Tesla.""" @@ -43,46 +33,56 @@ class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + def __init__(self): + """Initialize the tesla flow.""" + self.username = None + async def async_step_import(self, import_config): """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" + errors = {} + + if user_input is not None: + existing_entry = self._async_entry_for_username(user_input[CONF_USERNAME]) + if ( + existing_entry + and existing_entry.data[CONF_PASSWORD] == user_input[CONF_PASSWORD] + ): + return self.async_abort(reason="already_configured") + + try: + info = await validate_input(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + + if not errors: + if existing_entry: + self.hass.config_entries.async_update_entry( + existing_entry, data=info + ) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=info + ) + + return self.async_show_form( + step_id="user", + data_schema=self._async_schema(), + errors=errors, + description_placeholders={}, + ) - if not user_input: - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={}, - description_placeholders={}, - ) - - if user_input[CONF_USERNAME] in configured_instances(self.hass): - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={CONF_USERNAME: "already_configured"}, - description_placeholders={}, - ) - - try: - info = await validate_input(self.hass, user_input) - except CannotConnect: - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={"base": "cannot_connect"}, - description_placeholders={}, - ) - except InvalidAuth: - return self.async_show_form( - step_id="user", - data_schema=DATA_SCHEMA, - errors={"base": "invalid_auth"}, - description_placeholders={}, - ) - return self.async_create_entry(title=user_input[CONF_USERNAME], data=info) + async def async_step_reauth(self, data): + """Handle configuration by re-auth.""" + self.username = data[CONF_USERNAME] + return await self.async_step_user() @staticmethod @callback @@ -90,6 +90,23 @@ def async_get_options_flow(config_entry): """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) + @callback + def _async_schema(self): + """Fetch schema with defaults.""" + return vol.Schema( + { + vol.Required(CONF_USERNAME, default=self.username): str, + vol.Required(CONF_PASSWORD): str, + } + ) + + @callback + def _async_entry_for_username(self, username): + """Find an existing entry for a username.""" + for entry in self._async_current_entries(): + if entry.data.get(CONF_USERNAME) == username: + return entry + class OptionsFlowHandler(config_entries.OptionsFlow): """Handle a option flow for Tesla.""" diff --git a/homeassistant/components/tesla/strings.json b/homeassistant/components/tesla/strings.json index 503124eedd4e8f..c75562528defb1 100644 --- a/homeassistant/components/tesla/strings.json +++ b/homeassistant/components/tesla/strings.json @@ -5,6 +5,10 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, + "abort": { + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tesla/translations/en.json b/homeassistant/components/tesla/translations/en.json index f2b888552b9f1a..53b213ac19b254 100644 --- a/homeassistant/components/tesla/translations/en.json +++ b/homeassistant/components/tesla/translations/en.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" + }, "error": { "already_configured": "Account is already configured", "cannot_connect": "Failed to connect", diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index 136633c9a5cea0..b35ab0039d0f26 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -97,7 +97,12 @@ async def test_form_cannot_connect(hass): async def test_form_repeat_identifier(hass): """Test we handle repeat identifiers.""" - entry = MockConfigEntry(domain=DOMAIN, title="test-username", data={}, options=None) + entry = MockConfigEntry( + domain=DOMAIN, + title="test-username", + data={"username": "test-username", "password": "test-password"}, + options=None, + ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -112,8 +117,36 @@ async def test_form_repeat_identifier(hass): {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, ) - assert result2["type"] == "form" - assert result2["errors"] == {CONF_USERNAME: "already_configured"} + assert result2["type"] == "abort" + assert result2["reason"] == "already_configured" + + +async def test_form_reauth(hass): + """Test we handle reauth.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="test-username", + data={"username": "test-username", "password": "same"}, + options=None, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_REAUTH}, + data={"username": "test-username"}, + ) + with patch( + "homeassistant.components.tesla.config_flow.TeslaAPI.connect", + return_value=("test-refresh-token", "test-access-token"), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"}, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" async def test_import(hass): From 291746334ae9756b76c8d898d7e73b290cee190d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Feb 2021 15:25:24 +0100 Subject: [PATCH 0398/1818] Add `already_in_progress` string to roku config flow (#46333) --- homeassistant/components/roku/strings.json | 1 + homeassistant/components/roku/translations/en.json | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/roku/strings.json b/homeassistant/components/roku/strings.json index 55b533d4f1cfc6..3523615ff33a44 100644 --- a/homeassistant/components/roku/strings.json +++ b/homeassistant/components/roku/strings.json @@ -19,6 +19,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "unknown": "[%key:common::config_flow::error::unknown%]" } } diff --git a/homeassistant/components/roku/translations/en.json b/homeassistant/components/roku/translations/en.json index 08db89f367761a..2b54cafe8909a1 100644 --- a/homeassistant/components/roku/translations/en.json +++ b/homeassistant/components/roku/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", "unknown": "Unexpected error" }, "error": { From b5061d0232b61f31afbc2b33d9d1827f0940a2fd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Feb 2021 16:30:29 +0100 Subject: [PATCH 0399/1818] Restore Google/Alexa extra significant change checks (#46335) --- .../components/alexa/state_report.py | 38 ++++++-- .../google_assistant/report_state.py | 35 +++++-- homeassistant/helpers/significant_change.py | 92 +++++++++++++------ tests/components/alexa/test_state_report.py | 25 ++++- .../google_assistant/test_report_state.py | 18 ++++ tests/helpers/test_significant_change.py | 30 +++++- 6 files changed, 190 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index aa4110ea68698c..d66906810b2ef6 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -8,7 +8,7 @@ import async_timeout from homeassistant.const import HTTP_ACCEPTED, MATCH_ALL, STATE_ON -from homeassistant.core import State +from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers.significant_change import create_checker import homeassistant.util.dt as dt_util @@ -28,7 +28,20 @@ async def async_enable_proactive_mode(hass, smart_home_config): # Validate we can get access token. await smart_home_config.async_get_access_token() - checker = await create_checker(hass, DOMAIN) + @callback + def extra_significant_check( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + old_extra_arg: dict, + new_state: str, + new_attrs: dict, + new_extra_arg: dict, + ): + """Check if the serialized data has changed.""" + return old_extra_arg is not None and old_extra_arg != new_extra_arg + + checker = await create_checker(hass, DOMAIN, extra_significant_check) async def async_entity_state_listener( changed_entity: str, @@ -70,15 +83,22 @@ async def async_entity_state_listener( if not should_report and not should_doorbell: return - if not checker.async_is_significant_change(new_state): - return - if should_doorbell: should_report = False + if should_report: + alexa_properties = list(alexa_changed_entity.serialize_properties()) + else: + alexa_properties = None + + if not checker.async_is_significant_change( + new_state, extra_arg=alexa_properties + ): + return + if should_report: await async_send_changereport_message( - hass, smart_home_config, alexa_changed_entity + hass, smart_home_config, alexa_changed_entity, alexa_properties ) elif should_doorbell: @@ -92,7 +112,7 @@ async def async_entity_state_listener( async def async_send_changereport_message( - hass, config, alexa_entity, *, invalidate_access_token=True + hass, config, alexa_entity, alexa_properties, *, invalidate_access_token=True ): """Send a ChangeReport message for an Alexa entity. @@ -107,7 +127,7 @@ async def async_send_changereport_message( payload = { API_CHANGE: { "cause": {"type": Cause.APP_INTERACTION}, - "properties": list(alexa_entity.serialize_properties()), + "properties": alexa_properties, } } @@ -146,7 +166,7 @@ async def async_send_changereport_message( ): config.async_invalidate_access_token() return await async_send_changereport_message( - hass, config, alexa_entity, invalidate_access_token=False + hass, config, alexa_entity, alexa_properties, invalidate_access_token=False ) _LOGGER.error( diff --git a/homeassistant/components/google_assistant/report_state.py b/homeassistant/components/google_assistant/report_state.py index 8943d4d211e70e..cdfb06c5c39631 100644 --- a/homeassistant/components/google_assistant/report_state.py +++ b/homeassistant/components/google_assistant/report_state.py @@ -38,41 +38,58 @@ async def async_entity_state_listener(changed_entity, old_state, new_state): if not entity.is_supported(): return - if not checker.async_is_significant_change(new_state): - return - try: entity_data = entity.query_serialize() except SmartHomeError as err: _LOGGER.debug("Not reporting state for %s: %s", changed_entity, err.code) return + if not checker.async_is_significant_change(new_state, extra_arg=entity_data): + return + _LOGGER.debug("Reporting state for %s: %s", changed_entity, entity_data) await google_config.async_report_state_all( {"devices": {"states": {changed_entity: entity_data}}} ) + @callback + def extra_significant_check( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + old_extra_arg: dict, + new_state: str, + new_attrs: dict, + new_extra_arg: dict, + ): + """Check if the serialized data has changed.""" + return old_extra_arg != new_extra_arg + async def inital_report(_now): """Report initially all states.""" nonlocal unsub, checker entities = {} - checker = await create_checker(hass, DOMAIN) + checker = await create_checker(hass, DOMAIN, extra_significant_check) for entity in async_get_entities(hass, google_config): if not entity.should_expose(): continue + try: + entity_data = entity.query_serialize() + except SmartHomeError: + continue + # Tell our significant change checker that we're reporting # So it knows with subsequent changes what was already reported. - if not checker.async_is_significant_change(entity.state): + if not checker.async_is_significant_change( + entity.state, extra_arg=entity_data + ): continue - try: - entities[entity.entity_id] = entity.query_serialize() - except SmartHomeError: - continue + entities[entity.entity_id] = entity_data if not entities: return diff --git a/homeassistant/helpers/significant_change.py b/homeassistant/helpers/significant_change.py index 0a5b6aae10d24c..694acfcf2bd759 100644 --- a/homeassistant/helpers/significant_change.py +++ b/homeassistant/helpers/significant_change.py @@ -27,7 +27,7 @@ async def async_check_significant_change( - state adding/removing """ from types import MappingProxyType -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, Dict, Optional, Tuple, Union from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State, callback @@ -47,13 +47,28 @@ async def async_check_significant_change( Optional[bool], ] +ExtraCheckTypeFunc = Callable[ + [ + HomeAssistant, + str, + Union[dict, MappingProxyType], + Any, + str, + Union[dict, MappingProxyType], + Any, + ], + Optional[bool], +] + async def create_checker( - hass: HomeAssistant, _domain: str + hass: HomeAssistant, + _domain: str, + extra_significant_check: Optional[ExtraCheckTypeFunc] = None, ) -> "SignificantlyChangedChecker": """Create a significantly changed checker for a domain.""" await _initialize(hass) - return SignificantlyChangedChecker(hass) + return SignificantlyChangedChecker(hass, extra_significant_check) # Marked as singleton so multiple calls all wait for same output. @@ -105,34 +120,46 @@ class SignificantlyChangedChecker: Will always compare the entity to the last entity that was considered significant. """ - def __init__(self, hass: HomeAssistant) -> None: + def __init__( + self, + hass: HomeAssistant, + extra_significant_check: Optional[ExtraCheckTypeFunc] = None, + ) -> None: """Test if an entity has significantly changed.""" self.hass = hass - self.last_approved_entities: Dict[str, State] = {} + self.last_approved_entities: Dict[str, Tuple[State, Any]] = {} + self.extra_significant_check = extra_significant_check @callback - def async_is_significant_change(self, new_state: State) -> bool: - """Return if this was a significant change.""" - old_state: Optional[State] = self.last_approved_entities.get( + def async_is_significant_change( + self, new_state: State, *, extra_arg: Optional[Any] = None + ) -> bool: + """Return if this was a significant change. + + Extra kwargs are passed to the extra significant checker. + """ + old_data: Optional[Tuple[State, Any]] = self.last_approved_entities.get( new_state.entity_id ) # First state change is always ok to report - if old_state is None: - self.last_approved_entities[new_state.entity_id] = new_state + if old_data is None: + self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True + old_state, old_extra_arg = old_data + # Handle state unknown or unavailable if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): if new_state.state == old_state.state: return False - self.last_approved_entities[new_state.entity_id] = new_state + self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True # If last state was unknown/unavailable, also significant. if old_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): - self.last_approved_entities[new_state.entity_id] = new_state + self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True functions: Optional[Dict[str, CheckTypeFunc]] = self.hass.data.get( @@ -144,23 +171,36 @@ def async_is_significant_change(self, new_state: State) -> bool: check_significantly_changed = functions.get(new_state.domain) - # No platform available means always true. - if check_significantly_changed is None: - self.last_approved_entities[new_state.entity_id] = new_state - return True + if check_significantly_changed is not None: + result = check_significantly_changed( + self.hass, + old_state.state, + old_state.attributes, + new_state.state, + new_state.attributes, + ) - result = check_significantly_changed( - self.hass, - old_state.state, - old_state.attributes, - new_state.state, - new_state.attributes, - ) + if result is False: + return False - if result is False: - return False + if self.extra_significant_check is not None: + result = self.extra_significant_check( + self.hass, + old_state.state, + old_state.attributes, + old_extra_arg, + new_state.state, + new_state.attributes, + extra_arg, + ) + + if result is False: + return False # Result is either True or None. # None means the function doesn't know. For now assume it's True - self.last_approved_entities[new_state.entity_id] = new_state + self.last_approved_entities[new_state.entity_id] = ( + new_state, + extra_arg, + ) return True diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index 809bca5638b263..a057eada531e42 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -178,6 +178,7 @@ async def test_doorbell_event(hass, aioclient_mock): async def test_proactive_mode_filter_states(hass, aioclient_mock): """Test all the cases that filter states.""" + aioclient_mock.post(TEST_URL, text="", status=202) await state_report.async_enable_proactive_mode(hass, DEFAULT_CONFIG) # First state should report @@ -186,7 +187,8 @@ async def test_proactive_mode_filter_states(hass, aioclient_mock): "on", {"friendly_name": "Test Contact Sensor", "device_class": "door"}, ) - assert len(aioclient_mock.mock_calls) == 0 + await hass.async_block_till_done() + assert len(aioclient_mock.mock_calls) == 1 aioclient_mock.clear_requests() @@ -238,3 +240,24 @@ async def test_proactive_mode_filter_states(hass, aioclient_mock): await hass.async_block_till_done() await hass.async_block_till_done() assert len(aioclient_mock.mock_calls) == 0 + + # If serializes to same properties, it should not report + aioclient_mock.post(TEST_URL, text="", status=202) + with patch( + "homeassistant.components.alexa.entities.AlexaEntity.serialize_properties", + return_value=[{"same": "info"}], + ): + hass.states.async_set( + "binary_sensor.same_serialize", + "off", + {"friendly_name": "Test Contact Sensor", "device_class": "door"}, + ) + await hass.async_block_till_done() + hass.states.async_set( + "binary_sensor.same_serialize", + "off", + {"friendly_name": "Test Contact Sensor", "device_class": "door"}, + ) + + await hass.async_block_till_done() + assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index 72130dbfdb9848..f464be60bb93cd 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -46,6 +46,24 @@ async def test_report_state(hass, caplog, legacy_patchable_time): "devices": {"states": {"light.kitchen": {"on": True, "online": True}}} } + # Test that if serialize returns same value, we don't send + with patch( + "homeassistant.components.google_assistant.report_state.GoogleEntity.query_serialize", + return_value={"same": "info"}, + ), patch.object(BASIC_CONFIG, "async_report_state_all", AsyncMock()) as mock_report: + # New state, so reported + hass.states.async_set("light.double_report", "on") + await hass.async_block_till_done() + + # Changed, but serialize is same, so filtered out by extra check + hass.states.async_set("light.double_report", "off") + await hass.async_block_till_done() + + assert len(mock_report.mock_calls) == 1 + assert mock_report.mock_calls[0][1][0] == { + "devices": {"states": {"light.double_report": {"same": "info"}}} + } + # Test that only significant state changes are reported with patch.object( BASIC_CONFIG, "async_report_state_all", AsyncMock() diff --git a/tests/helpers/test_significant_change.py b/tests/helpers/test_significant_change.py index e72951d36dd2f9..79f3dd3fe3e4bd 100644 --- a/tests/helpers/test_significant_change.py +++ b/tests/helpers/test_significant_change.py @@ -5,7 +5,6 @@ from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import State from homeassistant.helpers import significant_change -from homeassistant.setup import async_setup_component @pytest.fixture(name="checker") @@ -26,8 +25,6 @@ def async_check_significant_change( async def test_signicant_change(hass, checker): """Test initialize helper works.""" - assert await async_setup_component(hass, "sensor", {}) - ent_id = "test_domain.test_entity" attrs = {ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY} @@ -48,3 +45,30 @@ async def test_signicant_change(hass, checker): # State turned unavailable assert checker.async_is_significant_change(State(ent_id, "100", attrs)) assert checker.async_is_significant_change(State(ent_id, STATE_UNAVAILABLE, attrs)) + + +async def test_significant_change_extra(hass, checker): + """Test extra significant checker works.""" + ent_id = "test_domain.test_entity" + attrs = {ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY} + + assert checker.async_is_significant_change(State(ent_id, "100", attrs), extra_arg=1) + assert checker.async_is_significant_change(State(ent_id, "200", attrs), extra_arg=1) + + # Reset the last significiant change to 100 to repeat test but with + # extra checker installed. + assert checker.async_is_significant_change(State(ent_id, "100", attrs), extra_arg=1) + + def extra_significant_check( + hass, old_state, old_attrs, old_extra_arg, new_state, new_attrs, new_extra_arg + ): + return old_extra_arg != new_extra_arg + + checker.extra_significant_check = extra_significant_check + + # This is normally a significant change (100 -> 200), but the extra arg check marks it + # as insignificant. + assert not checker.async_is_significant_change( + State(ent_id, "200", attrs), extra_arg=1 + ) + assert checker.async_is_significant_change(State(ent_id, "200", attrs), extra_arg=2) From 953491d509312b9167fcfa928ebcd6f6a5a66b06 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 11 Feb 2021 10:38:21 +0100 Subject: [PATCH 0400/1818] Bumped version to 2021.2.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5dba56f0247cbc..12772b1d2d1eb1 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 2 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 6015161dab78c0ac0d4707eb63656a547d4e6f9e Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 11 Feb 2021 11:40:03 +0200 Subject: [PATCH 0401/1818] Fix Shelly relay device set to light appliance type (#46181) --- homeassistant/components/shelly/light.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 0c91ddc1088c4f..5422f3fff05da0 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -187,6 +187,11 @@ def max_mireds(self) -> Optional[float]: async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" + if self.block.type == "relay": + self.control_result = await self.block.set_state(turn="on") + self.async_write_ha_state() + return + params = {"turn": "on"} if ATTR_BRIGHTNESS in kwargs: tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100) From 4034a274f702ee0bb1f69c593c4dca2e7edb0fd7 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 11 Feb 2021 11:40:03 +0200 Subject: [PATCH 0402/1818] Fix Shelly relay device set to light appliance type (#46181) --- homeassistant/components/shelly/light.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 0c91ddc1088c4f..5422f3fff05da0 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -187,6 +187,11 @@ def max_mireds(self) -> Optional[float]: async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" + if self.block.type == "relay": + self.control_result = await self.block.set_state(turn="on") + self.async_write_ha_state() + return + params = {"turn": "on"} if ATTR_BRIGHTNESS in kwargs: tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100) From 1b61b5c10b4436a673f27b6c5104ef38f24615e1 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 05:04:11 -0500 Subject: [PATCH 0403/1818] Clean up kira integration (#46292) --- homeassistant/components/kira/__init__.py | 2 +- homeassistant/components/kira/remote.py | 4 ++-- homeassistant/components/kira/sensor.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/kira/__init__.py b/homeassistant/components/kira/__init__.py index 8948fbd0b8f51c..732008e5780d97 100644 --- a/homeassistant/components/kira/__init__.py +++ b/homeassistant/components/kira/__init__.py @@ -13,6 +13,7 @@ CONF_HOST, CONF_NAME, CONF_PORT, + CONF_REPEAT, CONF_SENSORS, CONF_TYPE, EVENT_HOMEASSISTANT_STOP, @@ -28,7 +29,6 @@ DEFAULT_HOST = "0.0.0.0" DEFAULT_PORT = 65432 -CONF_REPEAT = "repeat" CONF_REMOTES = "remotes" CONF_SENSOR = "sensor" CONF_REMOTE = "remote" diff --git a/homeassistant/components/kira/remote.py b/homeassistant/components/kira/remote.py index c9b51fd7ab7a5d..526594266810ee 100644 --- a/homeassistant/components/kira/remote.py +++ b/homeassistant/components/kira/remote.py @@ -6,12 +6,12 @@ from homeassistant.const import CONF_DEVICE, CONF_NAME from homeassistant.helpers.entity import Entity +from . import CONF_REMOTE + DOMAIN = "kira" _LOGGER = logging.getLogger(__name__) -CONF_REMOTE = "remote" - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Kira platform.""" diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index 71aeec63232e7b..6656780d0e9cb1 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -4,14 +4,14 @@ from homeassistant.const import CONF_DEVICE, CONF_NAME, STATE_UNKNOWN from homeassistant.helpers.entity import Entity +from . import CONF_SENSOR + DOMAIN = "kira" _LOGGER = logging.getLogger(__name__) ICON = "mdi:remote" -CONF_SENSOR = "sensor" - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up a Kira sensor.""" From b85ecc0bd27c8378eca9581bb5a007fb4ab7ae7f Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 07:38:33 -0500 Subject: [PATCH 0404/1818] Use core constants for mqtt (#46389) --- homeassistant/components/mqtt/__init__.py | 4 +--- homeassistant/components/mqtt/alarm_control_panel.py | 1 - homeassistant/components/mqtt/binary_sensor.py | 2 -- homeassistant/components/mqtt/camera.py | 1 - homeassistant/components/mqtt/climate.py | 5 ++--- homeassistant/components/mqtt/config_flow.py | 2 +- homeassistant/components/mqtt/const.py | 1 - homeassistant/components/mqtt/cover.py | 1 - .../components/mqtt/device_tracker/schema_discovery.py | 1 - homeassistant/components/mqtt/fan.py | 1 - homeassistant/components/mqtt/light/__init__.py | 1 - homeassistant/components/mqtt/light/schema_json.py | 2 +- homeassistant/components/mqtt/light/schema_template.py | 2 +- homeassistant/components/mqtt/lock.py | 1 - homeassistant/components/mqtt/number.py | 3 --- homeassistant/components/mqtt/scene.py | 1 - homeassistant/components/mqtt/sensor.py | 1 - homeassistant/components/mqtt/switch.py | 1 - homeassistant/components/mqtt/tag.py | 1 - homeassistant/components/mqtt/vacuum/__init__.py | 1 - homeassistant/components/mqtt/vacuum/schema_legacy.py | 1 - 21 files changed, 6 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 098099f0a03f34..58c99aa7c007f6 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -19,6 +19,7 @@ from homeassistant.components import websocket_api from homeassistant.const import ( CONF_CLIENT_ID, + CONF_DISCOVERY, CONF_PASSWORD, CONF_PAYLOAD, CONF_PORT, @@ -28,7 +29,6 @@ EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.const import CONF_UNIQUE_ID # noqa: F401 from homeassistant.core import CoreState, Event, HassJob, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.helpers import config_validation as cv, event, template @@ -48,7 +48,6 @@ ATTR_TOPIC, CONF_BIRTH_MESSAGE, CONF_BROKER, - CONF_DISCOVERY, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, @@ -1053,7 +1052,6 @@ async def forward_messages(mqttmsg: Message): @callback def async_subscribe_connection_status(hass, connection_status_callback): """Subscribe to MQTT connection changes.""" - connection_status_callback_job = HassJob(connection_status_callback) async def connected(): diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 38fec57607e714..2f2ba06d6d79c6 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -110,7 +110,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT alarm control panel dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index d965401cef42e2..b9fb297cd5cd2d 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -78,7 +78,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT binary sensor dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) @@ -214,7 +213,6 @@ def state_message_received(msg): @callback def _value_is_expired(self, *_): """Triggered when value is expired.""" - self._expiration_trigger = None self._expired = True diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 21fcb9276dd40c..684e41214fe321 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -52,7 +52,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT camera dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 15c7c916eeb599..ede39103791097 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -38,6 +38,8 @@ ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, + CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, CONF_TEMPERATURE_UNIT, CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, @@ -98,8 +100,6 @@ CONF_MODE_LIST = "modes" CONF_MODE_STATE_TEMPLATE = "mode_state_template" CONF_MODE_STATE_TOPIC = "mode_state_topic" -CONF_PAYLOAD_OFF = "payload_off" -CONF_PAYLOAD_ON = "payload_on" CONF_POWER_COMMAND_TOPIC = "power_command_topic" CONF_POWER_STATE_TEMPLATE = "power_state_template" CONF_POWER_STATE_TOPIC = "power_state_topic" @@ -274,7 +274,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT climate device dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 5c4016437a62d2..c71541b58b07bb 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -7,6 +7,7 @@ from homeassistant import config_entries from homeassistant.const import ( + CONF_DISCOVERY, CONF_HOST, CONF_PASSWORD, CONF_PAYLOAD, @@ -22,7 +23,6 @@ ATTR_TOPIC, CONF_BIRTH_MESSAGE, CONF_BROKER, - CONF_DISCOVERY, CONF_WILL_MESSAGE, DATA_MQTT_CONFIG, DEFAULT_BIRTH, diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 3e56ab6caf93f7..6c334eca311a28 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -11,7 +11,6 @@ CONF_BROKER = "broker" CONF_BIRTH_MESSAGE = "birth_message" -CONF_DISCOVERY = "discovery" CONF_QOS = ATTR_QOS CONF_RETAIN = ATTR_RETAIN CONF_STATE_TOPIC = "state_topic" diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 4b428027a4d98f..54ef6cfb539277 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -207,7 +207,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT cover dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index 8b51b9fac0e0ca..296f66889b35e2 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -59,7 +59,6 @@ async def async_setup_entry_from_discovery(hass, config_entry, async_add_entities): """Set up MQTT device tracker dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index f96180b098240f..ef3ecffc0e0b17 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -122,7 +122,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT fan dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index e780332d093c43..72c438df81234d 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -43,7 +43,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT light dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 489b424f4eb254..8ec5c29db621c8 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -28,6 +28,7 @@ CONF_COLOR_TEMP, CONF_DEVICE, CONF_EFFECT, + CONF_HS, CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, @@ -75,7 +76,6 @@ CONF_FLASH_TIME_LONG = "flash_time_long" CONF_FLASH_TIME_SHORT = "flash_time_short" -CONF_HS = "hs" CONF_MAX_MIREDS = "max_mireds" CONF_MIN_MIREDS = "min_mireds" diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index e696e99552e862..1f4421205e00cc 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -24,6 +24,7 @@ CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, + CONF_STATE_TEMPLATE, CONF_UNIQUE_ID, STATE_OFF, STATE_ON, @@ -62,7 +63,6 @@ CONF_MAX_MIREDS = "max_mireds" CONF_MIN_MIREDS = "min_mireds" CONF_RED_TEMPLATE = "red_template" -CONF_STATE_TEMPLATE = "state_template" CONF_WHITE_VALUE_TEMPLATE = "white_value_template" PLATFORM_SCHEMA_TEMPLATE = ( diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index b08f8f8bb4397b..408bc07b9d9af3 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -84,7 +84,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT lock dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 159f466f7ae801..969eb254072389 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -67,7 +67,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT number dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, async_add_entities, config_entry=config_entry ) @@ -110,7 +109,6 @@ async def _subscribe_topics(self): @log_messages(self.hass, self.entity_id) def message_received(msg): """Handle new MQTT messages.""" - try: if msg.payload.decode("utf-8").isnumeric(): self._current_number = int(msg.payload) @@ -149,7 +147,6 @@ def value(self): async def async_set_value(self, value: float) -> None: """Update the current value.""" - current_number = value if value.is_integer(): diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index 908f4bafd30b86..061c7df3fdc626 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -47,7 +47,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT scene dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 3f79ab1bafe664..78a0517921044b 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -72,7 +72,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT sensors dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index d6d476b680d2da..73c7d55f6b027f 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -80,7 +80,6 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT switch dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index b691c5cf8ce3a3..4960ff50fb594d 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -45,7 +45,6 @@ async def async_setup_entry(hass, config_entry): """Set up MQTT tag scan dynamically through MQTT discovery.""" - setup = functools.partial(async_setup_tag, hass, config_entry=config_entry) await async_setup_entry_helper(hass, "tag", setup, PLATFORM_SCHEMA) diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index e580e874993366..4801c1ea994c6d 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -35,7 +35,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry(hass, config_entry, async_add_entities): """Set up MQTT vacuum dynamically through MQTT discovery.""" - setup = functools.partial( _async_setup_entity, async_add_entities, config_entry=config_entry ) diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index e7be64be6ae3cf..577e6a70ccd7b3 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -377,7 +377,6 @@ def battery_icon(self): No need to check SUPPORT_BATTERY, this won't be called if battery_level is None. """ - return icon_for_battery_level( battery_level=self.battery_level, charging=self._charging ) From c75e63dc95035fb1e00774a7f55fd1c48ddb10a6 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 07:58:16 -0500 Subject: [PATCH 0405/1818] Use core constants for modbus (#46388) --- homeassistant/components/modbus/__init__.py | 3 +-- homeassistant/components/modbus/binary_sensor.py | 3 +-- homeassistant/components/modbus/climate.py | 3 +-- homeassistant/components/modbus/const.py | 2 -- homeassistant/components/modbus/cover.py | 1 - homeassistant/components/modbus/switch.py | 2 -- tests/components/modbus/conftest.py | 2 -- tests/components/modbus/test_modbus_binary_sensor.py | 3 +-- tests/components/modbus/test_modbus_sensor.py | 3 +-- 9 files changed, 5 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 77e9b6f7ca9a31..c2a1a76840cd1d 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -17,6 +17,7 @@ CONF_HOST, CONF_METHOD, CONF_NAME, + CONF_OFFSET, CONF_PORT, CONF_SCAN_INTERVAL, CONF_SLAVE, @@ -46,7 +47,6 @@ CONF_INPUT_TYPE, CONF_MAX_TEMP, CONF_MIN_TEMP, - CONF_OFFSET, CONF_PARITY, CONF_PRECISION, CONF_REGISTER, @@ -257,7 +257,6 @@ class ModbusHub: def __init__(self, client_config): """Initialize the Modbus hub.""" - # generic configuration self._client = None self._lock = threading.Lock() diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index c9e9cc4196a5bf..8e91945d0732f8 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -10,13 +10,12 @@ PLATFORM_SCHEMA, BinarySensorEntity, ) -from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_SLAVE +from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, CONF_SLAVE from homeassistant.helpers import config_validation as cv from .const import ( CALL_TYPE_COIL, CALL_TYPE_DISCRETE, - CONF_ADDRESS, CONF_COILS, CONF_HUB, CONF_INPUT_TYPE, diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index b09a38f082eb05..c1b7b2e6bf4fd5 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -15,6 +15,7 @@ from homeassistant.const import ( ATTR_TEMPERATURE, CONF_NAME, + CONF_OFFSET, CONF_SCAN_INTERVAL, CONF_SLAVE, CONF_STRUCTURE, @@ -39,7 +40,6 @@ CONF_DATA_TYPE, CONF_MAX_TEMP, CONF_MIN_TEMP, - CONF_OFFSET, CONF_PRECISION, CONF_SCALE, CONF_STEP, @@ -146,7 +146,6 @@ def should_poll(self): False if entity pushes its state to HA. """ - # Handle polling directly in this entity return False diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index e79b69bbb87842..5e304165e42331 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -13,7 +13,6 @@ CONF_SCALE = "scale" CONF_COUNT = "count" CONF_PRECISION = "precision" -CONF_OFFSET = "offset" CONF_COILS = "coils" # integration names @@ -51,7 +50,6 @@ # binary_sensor.py CONF_INPUTS = "inputs" CONF_INPUT_TYPE = "input_type" -CONF_ADDRESS = "address" # sensor.py # CONF_DATA_TYPE = "data_type" diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index ab16e5306f112b..709c772564ab16 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -148,7 +148,6 @@ def should_poll(self): False if entity pushes its state to HA. """ - # Handle polling directly in this entity return False diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 8fe1f886c3e568..b1b07fb5a558d1 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -200,7 +200,6 @@ def __init__(self, hub: ModbusHub, config: Dict[str, Any]): def turn_on(self, **kwargs): """Set switch on.""" - # Only holding register is writable if self._register_type == CALL_TYPE_REGISTER_HOLDING: self._write_register(self._command_on) @@ -209,7 +208,6 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): """Set switch off.""" - # Only holding register is writable if self._register_type == CALL_TYPE_REGISTER_HOLDING: self._write_register(self._command_off) diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index e3a707b7fc9024..a3e6078ea096e3 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -46,7 +46,6 @@ async def setup_base_test( scan_interval, ): """Run setup device for given config.""" - # Full sensor configuration config = { entity_domain: { @@ -79,7 +78,6 @@ async def run_base_read_test( now, ): """Run test for given config.""" - # Setup inputs for the sensor read_result = ReadResult(register_words) if register_type == CALL_TYPE_COIL: diff --git a/tests/components/modbus/test_modbus_binary_sensor.py b/tests/components/modbus/test_modbus_binary_sensor.py index 91374cde22da4c..3bc7223c865c37 100644 --- a/tests/components/modbus/test_modbus_binary_sensor.py +++ b/tests/components/modbus/test_modbus_binary_sensor.py @@ -7,11 +7,10 @@ from homeassistant.components.modbus.const import ( CALL_TYPE_COIL, CALL_TYPE_DISCRETE, - CONF_ADDRESS, CONF_INPUT_TYPE, CONF_INPUTS, ) -from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON +from homeassistant.const import CONF_ADDRESS, CONF_NAME, STATE_OFF, STATE_ON from .conftest import run_base_read_test, setup_base_test diff --git a/tests/components/modbus/test_modbus_sensor.py b/tests/components/modbus/test_modbus_sensor.py index 68cdbffa462bc4..3f7c0fc60df52f 100644 --- a/tests/components/modbus/test_modbus_sensor.py +++ b/tests/components/modbus/test_modbus_sensor.py @@ -8,7 +8,6 @@ CALL_TYPE_REGISTER_INPUT, CONF_COUNT, CONF_DATA_TYPE, - CONF_OFFSET, CONF_PRECISION, CONF_REGISTER, CONF_REGISTER_TYPE, @@ -21,7 +20,7 @@ DATA_TYPE_UINT, ) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_OFFSET from .conftest import run_base_read_test, setup_base_test From b1a7bfee14a5d81ac9b92067c39571c705b1b5f2 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 07:59:09 -0500 Subject: [PATCH 0406/1818] Clean up kira integration constants (#46390) --- homeassistant/components/kira/remote.py | 4 +--- homeassistant/components/kira/sensor.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/kira/remote.py b/homeassistant/components/kira/remote.py index 526594266810ee..9c02a3199e40bc 100644 --- a/homeassistant/components/kira/remote.py +++ b/homeassistant/components/kira/remote.py @@ -6,9 +6,7 @@ from homeassistant.const import CONF_DEVICE, CONF_NAME from homeassistant.helpers.entity import Entity -from . import CONF_REMOTE - -DOMAIN = "kira" +from . import CONF_REMOTE, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index 6656780d0e9cb1..2d6322918c71f1 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -4,9 +4,7 @@ from homeassistant.const import CONF_DEVICE, CONF_NAME, STATE_UNKNOWN from homeassistant.helpers.entity import Entity -from . import CONF_SENSOR - -DOMAIN = "kira" +from . import CONF_SENSOR, DOMAIN _LOGGER = logging.getLogger(__name__) From 5ce49c62b143a4b0d9913687e9a37a8e9fdee35d Mon Sep 17 00:00:00 2001 From: Steve Dwyer <6332150+kangaroomadman@users.noreply.github.com> Date: Thu, 11 Feb 2021 13:57:27 +0000 Subject: [PATCH 0407/1818] Allow MQTT template light floating point transition (#46385) Allow to use floating point values for the transition time of the MQTT template light. --- homeassistant/components/mqtt/light/schema_template.py | 4 ++-- tests/components/mqtt/test_light_template.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 1f4421205e00cc..665e1a30d99a9a 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -383,7 +383,7 @@ async def async_turn_on(self, **kwargs): values["flash"] = kwargs.get(ATTR_FLASH) if ATTR_TRANSITION in kwargs: - values["transition"] = int(kwargs[ATTR_TRANSITION]) + values["transition"] = kwargs[ATTR_TRANSITION] mqtt.async_publish( self.hass, @@ -408,7 +408,7 @@ async def async_turn_off(self, **kwargs): self._state = False if ATTR_TRANSITION in kwargs: - values["transition"] = int(kwargs[ATTR_TRANSITION]) + values["transition"] = kwargs[ATTR_TRANSITION] mqtt.async_publish( self.hass, diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 733a39ce252e69..7b5d34edd69840 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -677,7 +677,7 @@ async def test_transition(hass, mqtt_mock): "name": "test", "command_topic": "test_light_rgb/set", "command_on_template": "on,{{ transition }}", - "command_off_template": "off,{{ transition|d }}", + "command_off_template": "off,{{ transition|int|d }}", "qos": 1, } }, @@ -689,15 +689,15 @@ async def test_transition(hass, mqtt_mock): assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 - await common.async_turn_on(hass, "light.test", transition=10) + await common.async_turn_on(hass, "light.test", transition=10.0) mqtt_mock.async_publish.assert_called_once_with( - "test_light_rgb/set", "on,10", 1, False + "test_light_rgb/set", "on,10.0", 1, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("light.test") assert state.state == STATE_ON - await common.async_turn_off(hass, "light.test", transition=20) + await common.async_turn_off(hass, "light.test", transition=20.0) mqtt_mock.async_publish.assert_called_once_with( "test_light_rgb/set", "off,20", 1, False ) From 888c9e120de997a4cb0419901b3a170f46286305 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Thu, 11 Feb 2021 17:29:17 +0100 Subject: [PATCH 0408/1818] Raise ConditionError for time errors (#46250) --- homeassistant/helpers/condition.py | 8 ++++++-- tests/helpers/test_condition.py | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index da4fdab07d79e7..2f63498c9cdb94 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -518,7 +518,9 @@ def time( elif isinstance(after, str): after_entity = hass.states.get(after) if not after_entity: - return False + raise ConditionError( + f"Error in 'time' condition: The 'after' entity {after} is not available" + ) after = dt_util.dt.time( after_entity.attributes.get("hour", 23), after_entity.attributes.get("minute", 59), @@ -530,7 +532,9 @@ def time( elif isinstance(before, str): before_entity = hass.states.get(before) if not before_entity: - return False + raise ConditionError( + f"Error in 'time' condition: The 'before' entity {before} is not available" + ) before = dt_util.dt.time( before_entity.attributes.get("hour", 23), before_entity.attributes.get("minute", 59), diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index dd1abdecb72110..1fc1e07da5dfaf 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -334,8 +334,11 @@ async def test_time_using_input_datetime(hass): hass, after="input_datetime.pm", before="input_datetime.am" ) - assert not condition.time(hass, after="input_datetime.not_existing") - assert not condition.time(hass, before="input_datetime.not_existing") + with pytest.raises(ConditionError): + condition.time(hass, after="input_datetime.not_existing") + + with pytest.raises(ConditionError): + condition.time(hass, before="input_datetime.not_existing") async def test_if_numeric_state_raises_on_unavailable(hass, caplog): From ed31cc363b3a457ed9249f7cf0386f2ec86e1850 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Feb 2021 17:36:19 +0100 Subject: [PATCH 0409/1818] Wait for registries to load at startup (#46265) * Wait for registries to load at startup * Don't decorate new functions with @bind_hass * Fix typing errors in zwave_js * Load registries in async_test_home_assistant * Tweak * Typo * Tweak * Explicitly silence mypy errors * Fix tests * Fix more tests * Fix test * Improve docstring * Wait for registries to load --- homeassistant/bootstrap.py | 11 ++++--- homeassistant/components/zwave_js/__init__.py | 8 ++--- homeassistant/helpers/area_registry.py | 33 +++++++++---------- homeassistant/helpers/device_registry.py | 28 ++++++++++++---- homeassistant/helpers/entity_registry.py | 27 +++++++++++---- tests/common.py | 11 ++++++- tests/components/discovery/test_init.py | 14 ++++---- tests/components/template/test_sensor.py | 3 ++ tests/conftest.py | 14 ++++++-- tests/helpers/test_area_registry.py | 21 ++---------- tests/helpers/test_device_registry.py | 31 +++++++---------- tests/helpers/test_entity_registry.py | 26 ++++----------- tests/test_bootstrap.py | 7 ++++ 13 files changed, 129 insertions(+), 105 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0f5bda7fbf24d7..fd2d580a879eaf 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -17,6 +17,7 @@ from homeassistant.components import http from homeassistant.const import REQUIRED_NEXT_PYTHON_DATE, REQUIRED_NEXT_PYTHON_VER from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import area_registry, device_registry, entity_registry from homeassistant.helpers.typing import ConfigType from homeassistant.setup import ( DATA_SETUP, @@ -510,10 +511,12 @@ async def _async_set_up_integrations( stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains - # Kick off loading the registries. They don't need to be awaited. - asyncio.create_task(hass.helpers.device_registry.async_get_registry()) - asyncio.create_task(hass.helpers.entity_registry.async_get_registry()) - asyncio.create_task(hass.helpers.area_registry.async_get_registry()) + # Load the registries + await asyncio.gather( + device_registry.async_load(hass), + entity_registry.async_load(hass), + area_registry.async_load(hass), + ) # Start setup if stage_1_domains: diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 01b8f4785c5d5b..d5624551b2797f 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -131,8 +131,8 @@ def async_on_node_removed(node: ZwaveNode) -> None: # grab device in device registry attached to this node dev_id = get_device_id(client, node) device = dev_reg.async_get_device({dev_id}) - # note: removal of entity registry is handled by core - dev_reg.async_remove_device(device.id) + # note: removal of entity registry entry is handled by core + dev_reg.async_remove_device(device.id) # type: ignore @callback def async_on_value_notification(notification: ValueNotification) -> None: @@ -149,7 +149,7 @@ def async_on_value_notification(notification: ValueNotification) -> None: ATTR_NODE_ID: notification.node.node_id, ATTR_HOME_ID: client.driver.controller.home_id, ATTR_ENDPOINT: notification.endpoint, - ATTR_DEVICE_ID: device.id, + ATTR_DEVICE_ID: device.id, # type: ignore ATTR_COMMAND_CLASS: notification.command_class, ATTR_COMMAND_CLASS_NAME: notification.command_class_name, ATTR_LABEL: notification.metadata.label, @@ -170,7 +170,7 @@ def async_on_notification(notification: Notification) -> None: ATTR_DOMAIN: DOMAIN, ATTR_NODE_ID: notification.node.node_id, ATTR_HOME_ID: client.driver.controller.home_id, - ATTR_DEVICE_ID: device.id, + ATTR_DEVICE_ID: device.id, # type: ignore ATTR_LABEL: notification.notification_label, ATTR_PARAMETERS: notification.parameters, }, diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index bdd231686e22ee..a41e748d1adb64 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,5 +1,5 @@ """Provide a way to connect devices to one physical location.""" -from asyncio import Event, gather +from asyncio import gather from collections import OrderedDict from typing import Container, Dict, Iterable, List, MutableMapping, Optional, cast @@ -154,24 +154,23 @@ def _data_to_save(self) -> Dict[str, List[Dict[str, Optional[str]]]]: return data -@bind_hass -async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: - """Return area registry instance.""" - reg_or_evt = hass.data.get(DATA_REGISTRY) +@callback +def async_get(hass: HomeAssistantType) -> AreaRegistry: + """Get area registry.""" + return cast(AreaRegistry, hass.data[DATA_REGISTRY]) - if not reg_or_evt: - evt = hass.data[DATA_REGISTRY] = Event() - reg = AreaRegistry(hass) - await reg.async_load() +async def async_load(hass: HomeAssistantType) -> None: + """Load area registry.""" + assert DATA_REGISTRY not in hass.data + hass.data[DATA_REGISTRY] = AreaRegistry(hass) + await hass.data[DATA_REGISTRY].async_load() - hass.data[DATA_REGISTRY] = reg - evt.set() - return reg - if isinstance(reg_or_evt, Event): - evt = reg_or_evt - await evt.wait() - return cast(AreaRegistry, hass.data.get(DATA_REGISTRY)) +@bind_hass +async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: + """Get area registry. - return cast(AreaRegistry, reg_or_evt) + This is deprecated and will be removed in the future. Use async_get instead. + """ + return async_get(hass) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index c449d2ed4d02e9..0d62b2cab47986 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -2,16 +2,16 @@ from collections import OrderedDict import logging import time -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, cast import attr from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import Event, callback +from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util from .debounce import Debouncer -from .singleton import singleton from .typing import UNDEFINED, HomeAssistantType, UndefinedType # mypy: disallow_any_generics @@ -593,12 +593,26 @@ def async_clear_area_id(self, area_id: str) -> None: self._async_update_device(dev_id, area_id=None) -@singleton(DATA_REGISTRY) +@callback +def async_get(hass: HomeAssistantType) -> DeviceRegistry: + """Get device registry.""" + return cast(DeviceRegistry, hass.data[DATA_REGISTRY]) + + +async def async_load(hass: HomeAssistantType) -> None: + """Load device registry.""" + assert DATA_REGISTRY not in hass.data + hass.data[DATA_REGISTRY] = DeviceRegistry(hass) + await hass.data[DATA_REGISTRY].async_load() + + +@bind_hass async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: - """Create entity registry.""" - reg = DeviceRegistry(hass) - await reg.async_load() - return reg + """Get device registry. + + This is deprecated and will be removed in the future. Use async_get instead. + """ + return async_get(hass) @callback diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 052e7398ba16a0..51985f7bae45aa 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -19,6 +19,7 @@ Optional, Tuple, Union, + cast, ) import attr @@ -35,10 +36,10 @@ ) from homeassistant.core import Event, callback, split_entity_id, valid_entity_id from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED +from homeassistant.loader import bind_hass from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml -from .singleton import singleton from .typing import UNDEFINED, HomeAssistantType, UndefinedType if TYPE_CHECKING: @@ -568,12 +569,26 @@ def _rebuild_index(self) -> None: self._add_index(entry) -@singleton(DATA_REGISTRY) +@callback +def async_get(hass: HomeAssistantType) -> EntityRegistry: + """Get entity registry.""" + return cast(EntityRegistry, hass.data[DATA_REGISTRY]) + + +async def async_load(hass: HomeAssistantType) -> None: + """Load entity registry.""" + assert DATA_REGISTRY not in hass.data + hass.data[DATA_REGISTRY] = EntityRegistry(hass) + await hass.data[DATA_REGISTRY].async_load() + + +@bind_hass async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: - """Create entity registry.""" - reg = EntityRegistry(hass) - await reg.async_load() - return reg + """Get entity registry. + + This is deprecated and will be removed in the future. Use async_get instead. + """ + return async_get(hass) @callback diff --git a/tests/common.py b/tests/common.py index ab5da25e38d363..c07716dbfc9ab4 100644 --- a/tests/common.py +++ b/tests/common.py @@ -146,7 +146,7 @@ def stop_hass(): # pylint: disable=protected-access -async def async_test_home_assistant(loop): +async def async_test_home_assistant(loop, load_registries=True): """Return a Home Assistant object pointing at test config dir.""" hass = ha.HomeAssistant() store = auth_store.AuthStore(hass) @@ -280,6 +280,15 @@ async def _await_count_and_log_pending( hass.config_entries._entries = [] hass.config_entries._store._async_ensure_stop_listener = lambda: None + # Load the registries + if load_registries: + await asyncio.gather( + device_registry.async_load(hass), + entity_registry.async_load(hass), + area_registry.async_load(hass), + ) + await hass.async_block_till_done() + hass.state = ha.CoreState.running # Mock async_start diff --git a/tests/components/discovery/test_init.py b/tests/components/discovery/test_init.py index fd66e59ef21210..2c1e41e8285156 100644 --- a/tests/components/discovery/test_init.py +++ b/tests/components/discovery/test_init.py @@ -38,19 +38,17 @@ async def mock_discovery(hass, discoveries, config=BASE_CONFIG): """Mock discoveries.""" with patch("homeassistant.components.zeroconf.async_get_instance"), patch( "homeassistant.components.zeroconf.async_setup", return_value=True - ): - assert await async_setup_component(hass, "discovery", config) - await hass.async_block_till_done() - await hass.async_start() - hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) - await hass.async_block_till_done() - - with patch.object(discovery, "_discover", discoveries), patch( + ), patch.object(discovery, "_discover", discoveries), patch( "homeassistant.components.discovery.async_discover", return_value=mock_coro() ) as mock_discover, patch( "homeassistant.components.discovery.async_load_platform", return_value=mock_coro(), ) as mock_platform: + assert await async_setup_component(hass, "discovery", config) + await hass.async_block_till_done() + await hass.async_start() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() async_fire_time_changed(hass, utcnow()) # Work around an issue where our loop.call_soon not get caught await hass.async_block_till_done() diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 7f560fa0abb190..9d014f86a36783 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -3,6 +3,8 @@ from datetime import timedelta from unittest.mock import patch +import pytest + from homeassistant.bootstrap import async_from_config_dict from homeassistant.components import sensor from homeassistant.const import ( @@ -403,6 +405,7 @@ async def test_setup_valid_device_class(hass): assert "device_class" not in state.attributes +@pytest.mark.parametrize("load_registries", [False]) async def test_creating_sensor_loads_group(hass): """Test setting up template sensor loads group component first.""" order = [] diff --git a/tests/conftest.py b/tests/conftest.py index 6e3edbd73e86aa..3fc2dc748cbd17 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -121,7 +121,17 @@ def hass_storage(): @pytest.fixture -def hass(loop, hass_storage, request): +def load_registries(): + """Fixture to control the loading of registries when setting up the hass fixture. + + To avoid loading the registries, tests can be marked with: + @pytest.mark.parametrize("load_registries", [False]) + """ + return True + + +@pytest.fixture +def hass(loop, load_registries, hass_storage, request): """Fixture to provide a test instance of Home Assistant.""" def exc_handle(loop, context): @@ -141,7 +151,7 @@ def exc_handle(loop, context): orig_exception_handler(loop, context) exceptions = [] - hass = loop.run_until_complete(async_test_home_assistant(loop)) + hass = loop.run_until_complete(async_test_home_assistant(loop, load_registries)) orig_exception_handler = loop.get_exception_handler() loop.set_exception_handler(exc_handle) diff --git a/tests/helpers/test_area_registry.py b/tests/helpers/test_area_registry.py index ec008dde7da1f8..2b06202c862171 100644 --- a/tests/helpers/test_area_registry.py +++ b/tests/helpers/test_area_registry.py @@ -1,7 +1,4 @@ """Tests for the Area Registry.""" -import asyncio -import unittest.mock - import pytest from homeassistant.core import callback @@ -164,6 +161,7 @@ async def test_load_area(hass, registry): assert list(registry.areas) == list(registry2.areas) +@pytest.mark.parametrize("load_registries", [False]) async def test_loading_area_from_storage(hass, hass_storage): """Test loading stored areas on start.""" hass_storage[area_registry.STORAGE_KEY] = { @@ -171,20 +169,7 @@ async def test_loading_area_from_storage(hass, hass_storage): "data": {"areas": [{"id": "12345A", "name": "mock"}]}, } - registry = await area_registry.async_get_registry(hass) + await area_registry.async_load(hass) + registry = area_registry.async_get(hass) assert len(registry.areas) == 1 - - -async def test_loading_race_condition(hass): - """Test only one storage load called when concurrent loading occurred .""" - with unittest.mock.patch( - "homeassistant.helpers.area_registry.AreaRegistry.async_load" - ) as mock_load: - results = await asyncio.gather( - area_registry.async_get_registry(hass), - area_registry.async_get_registry(hass), - ) - - mock_load.assert_called_once_with() - assert results[0] == results[1] diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 01959174335c0a..a128f8aa3903fd 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1,5 +1,4 @@ """Tests for the Device Registry.""" -import asyncio import time from unittest.mock import patch @@ -135,6 +134,7 @@ async def test_multiple_config_entries(registry): assert entry2.config_entries == {"123", "456"} +@pytest.mark.parametrize("load_registries", [False]) async def test_loading_from_storage(hass, hass_storage): """Test loading stored devices on start.""" hass_storage[device_registry.STORAGE_KEY] = { @@ -167,7 +167,8 @@ async def test_loading_from_storage(hass, hass_storage): }, } - registry = await device_registry.async_get_registry(hass) + await device_registry.async_load(hass) + registry = device_registry.async_get(hass) assert len(registry.devices) == 1 assert len(registry.deleted_devices) == 1 @@ -687,20 +688,6 @@ async def test_update_remove_config_entries(hass, registry, update_events): assert update_events[4]["device_id"] == entry3.id -async def test_loading_race_condition(hass): - """Test only one storage load called when concurrent loading occurred .""" - with patch( - "homeassistant.helpers.device_registry.DeviceRegistry.async_load" - ) as mock_load: - results = await asyncio.gather( - device_registry.async_get_registry(hass), - device_registry.async_get_registry(hass), - ) - - mock_load.assert_called_once_with() - assert results[0] == results[1] - - async def test_update_sw_version(registry): """Verify that we can update software version of a device.""" entry = registry.async_get_or_create( @@ -798,10 +785,16 @@ async def test_cleanup_startup(hass): assert len(mock_call.mock_calls) == 1 +@pytest.mark.parametrize("load_registries", [False]) async def test_cleanup_entity_registry_change(hass): - """Test we run a cleanup when entity registry changes.""" - await device_registry.async_get_registry(hass) - ent_reg = await entity_registry.async_get_registry(hass) + """Test we run a cleanup when entity registry changes. + + Don't pre-load the registries as the debouncer will then not be waiting for + EVENT_ENTITY_REGISTRY_UPDATED events. + """ + await device_registry.async_load(hass) + await entity_registry.async_load(hass) + ent_reg = entity_registry.async_get(hass) with patch( "homeassistant.helpers.device_registry.Debouncer.async_call" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index b176f7022d5357..71cfb33159188b 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -1,6 +1,4 @@ """Tests for the Entity Registry.""" -import asyncio -import unittest.mock from unittest.mock import patch import pytest @@ -219,6 +217,7 @@ def test_is_registered(registry): assert not registry.async_is_registered("light.non_existing") +@pytest.mark.parametrize("load_registries", [False]) async def test_loading_extra_values(hass, hass_storage): """Test we load extra data from the registry.""" hass_storage[entity_registry.STORAGE_KEY] = { @@ -258,7 +257,8 @@ async def test_loading_extra_values(hass, hass_storage): }, } - registry = await entity_registry.async_get_registry(hass) + await entity_registry.async_load(hass) + registry = entity_registry.async_get(hass) assert len(registry.entities) == 4 @@ -350,6 +350,7 @@ async def test_removing_area_id(registry): assert entry_w_area != entry_wo_area +@pytest.mark.parametrize("load_registries", [False]) async def test_migration(hass): """Test migration from old data to new.""" mock_config = MockConfigEntry(domain="test-platform", entry_id="test-config-id") @@ -366,7 +367,8 @@ async def test_migration(hass): with patch("os.path.isfile", return_value=True), patch("os.remove"), patch( "homeassistant.helpers.entity_registry.load_yaml", return_value=old_conf ): - registry = await entity_registry.async_get_registry(hass) + await entity_registry.async_load(hass) + registry = entity_registry.async_get(hass) assert registry.async_is_registered("light.kitchen") entry = registry.async_get_or_create( @@ -427,20 +429,6 @@ async def test_loading_invalid_entity_id(hass, hass_storage): assert valid_entity_id(entity_invalid_start.entity_id) -async def test_loading_race_condition(hass): - """Test only one storage load called when concurrent loading occurred .""" - with unittest.mock.patch( - "homeassistant.helpers.entity_registry.EntityRegistry.async_load" - ) as mock_load: - results = await asyncio.gather( - entity_registry.async_get_registry(hass), - entity_registry.async_get_registry(hass), - ) - - mock_load.assert_called_once_with() - assert results[0] == results[1] - - async def test_update_entity_unique_id(registry): """Test entity's unique_id is updated.""" mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1") @@ -794,7 +782,7 @@ async def test_disable_device_disables_entities(hass, registry): async def test_disabled_entities_excluded_from_entity_list(hass, registry): - """Test that disabled entities are exclduded from async_entries_for_device.""" + """Test that disabled entities are excluded from async_entries_for_device.""" device_registry = mock_device_registry(hass) config_entry = MockConfigEntry(domain="light") diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index fc653c25d0bae8..c035f6f1d1d791 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -71,6 +71,7 @@ async def test_load_hassio(hass): assert bootstrap._get_domains(hass, {}) == {"hassio"} +@pytest.mark.parametrize("load_registries", [False]) async def test_empty_setup(hass): """Test an empty set up loads the core.""" await bootstrap.async_from_config_dict({}, hass) @@ -91,6 +92,7 @@ async def test_core_failure_loads_safe_mode(hass, caplog): assert "group" not in hass.config.components +@pytest.mark.parametrize("load_registries", [False]) async def test_setting_up_config(hass): """Test we set up domains in config.""" await bootstrap._async_set_up_integrations( @@ -100,6 +102,7 @@ async def test_setting_up_config(hass): assert "group" in hass.config.components +@pytest.mark.parametrize("load_registries", [False]) async def test_setup_after_deps_all_present(hass): """Test after_dependencies when all present.""" order = [] @@ -144,6 +147,7 @@ async def async_setup(hass, config): assert order == ["logger", "root", "first_dep", "second_dep"] +@pytest.mark.parametrize("load_registries", [False]) async def test_setup_after_deps_in_stage_1_ignored(hass): """Test after_dependencies are ignored in stage 1.""" # This test relies on this @@ -190,6 +194,7 @@ async def async_setup(hass, config): assert order == ["cloud", "an_after_dep", "normal_integration"] +@pytest.mark.parametrize("load_registries", [False]) async def test_setup_after_deps_via_platform(hass): """Test after_dependencies set up via platform.""" order = [] @@ -239,6 +244,7 @@ def continue_loading(_): assert order == ["after_dep_of_platform_int", "platform_int"] +@pytest.mark.parametrize("load_registries", [False]) async def test_setup_after_deps_not_trigger_load(hass): """Test after_dependencies does not trigger loading it.""" order = [] @@ -277,6 +283,7 @@ async def async_setup(hass, config): assert "second_dep" in hass.config.components +@pytest.mark.parametrize("load_registries", [False]) async def test_setup_after_deps_not_present(hass): """Test after_dependencies when referenced integration doesn't exist.""" order = [] From c95f401e2e951f1d6b18181930c797b310d3cfa5 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 11:44:39 -0500 Subject: [PATCH 0410/1818] Use core constants for nissan_leaf (#46401) --- homeassistant/components/nissan_leaf/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 26689e5cb0a9f2..6417926702df83 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -7,7 +7,7 @@ from pycarwings2 import CarwingsError, Session import voluptuous as vol -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_OK +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME, HTTP_OK from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform @@ -34,7 +34,6 @@ CONF_INTERVAL = "update_interval" CONF_CHARGING_INTERVAL = "update_interval_charging" CONF_CLIMATE_INTERVAL = "update_interval_climate" -CONF_REGION = "region" CONF_VALID_REGIONS = ["NNA", "NE", "NCI", "NMA", "NML"] CONF_FORCE_MILES = "force_miles" @@ -272,7 +271,6 @@ def get_next_interval(self): async def async_refresh_data(self, now): """Refresh the leaf data and update the datastore.""" - if self.request_in_progress: _LOGGER.debug("Refresh currently in progress for %s", self.leaf.nickname) return @@ -336,7 +334,6 @@ def _extract_start_date(battery_info): async def async_get_battery(self): """Request battery update from Nissan servers.""" - try: # Request battery update from the car _LOGGER.debug("Requesting battery update, %s", self.leaf.vin) @@ -388,7 +385,6 @@ async def async_get_battery(self): async def async_get_climate(self): """Request climate data from Nissan servers.""" - try: return await self.hass.async_add_executor_job( self.leaf.get_latest_hvac_status From fd177441b3cfddb22b893b26e91862307bbcc437 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 11:45:26 -0500 Subject: [PATCH 0411/1818] Use core constants for nmap_tracker (#46402) --- homeassistant/components/nmap_tracker/device_tracker.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/nmap_tracker/device_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py index 0a8c177b08cf48..608f90d5421292 100644 --- a/homeassistant/components/nmap_tracker/device_tracker.py +++ b/homeassistant/components/nmap_tracker/device_tracker.py @@ -12,13 +12,12 @@ PLATFORM_SCHEMA, DeviceScanner, ) -from homeassistant.const import CONF_HOSTS +from homeassistant.const import CONF_EXCLUDE, CONF_HOSTS import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -CONF_EXCLUDE = "exclude" # Interval in minutes to exclude devices from a scan while they are home CONF_HOME_INTERVAL = "home_interval" CONF_OPTIONS = "scan_options" From eb0d1bb673230b8bbece6019aa639dc55477160c Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 11 Feb 2021 18:55:17 +0100 Subject: [PATCH 0412/1818] Improve knx fan implementation (#46404) --- homeassistant/components/knx/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index d5dfb25ccd4f97..de08b576eddd78 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -35,6 +35,8 @@ def __init__(self, device: XknxFan): if self._device.mode == FanSpeedMode.Step: self._step_range = (1, device.max_step) + else: + self._step_range = None async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" From 70e23402a90d4e5ac5cb306bbfc7f02a3a619bb1 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Thu, 11 Feb 2021 13:56:50 -0500 Subject: [PATCH 0413/1818] Use core constants for ohmconnect (#46413) --- homeassistant/components/ohmconnect/sensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index 56a3cc0655667f..7c7331990ea6c1 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -7,15 +7,13 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_ID = "id" - DEFAULT_NAME = "OhmConnect Status" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) From 26e79163677645943ff009b2dbe5922365681c25 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Feb 2021 20:18:03 +0100 Subject: [PATCH 0414/1818] Migrate mobile_app to RestoreEntity (#46391) --- .../components/mobile_app/__init__.py | 6 - .../components/mobile_app/binary_sensor.py | 52 ++-- .../components/mobile_app/config_flow.py | 6 +- homeassistant/components/mobile_app/const.py | 2 - homeassistant/components/mobile_app/entity.py | 55 ++-- .../components/mobile_app/helpers.py | 4 - homeassistant/components/mobile_app/sensor.py | 46 +-- .../components/mobile_app/webhook.py | 30 +- tests/components/mobile_app/conftest.py | 8 - .../mobile_app/test_binary_sensor.py | 271 ++++++++++++++++++ .../{test_entity.py => test_sensor.py} | 14 + tests/components/mobile_app/test_webhook.py | 2 +- 12 files changed, 395 insertions(+), 101 deletions(-) create mode 100644 tests/components/mobile_app/test_binary_sensor.py rename tests/components/mobile_app/{test_entity.py => test_sensor.py} (92%) diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 3bc95bf3e0520d..54fa3398ee2bcd 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -17,11 +17,9 @@ ATTR_MODEL, ATTR_OS_VERSION, CONF_CLOUDHOOK_URL, - DATA_BINARY_SENSOR, DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, DATA_DEVICES, - DATA_SENSOR, DATA_STORE, DOMAIN, STORAGE_KEY, @@ -40,18 +38,14 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): app_config = await store.async_load() if app_config is None: app_config = { - DATA_BINARY_SENSOR: {}, DATA_CONFIG_ENTRIES: {}, DATA_DELETED_IDS: [], - DATA_SENSOR: {}, } hass.data[DOMAIN] = { - DATA_BINARY_SENSOR: app_config.get(DATA_BINARY_SENSOR, {}), DATA_CONFIG_ENTRIES: {}, DATA_DELETED_IDS: app_config.get(DATA_DELETED_IDS, []), DATA_DEVICES: {}, - DATA_SENSOR: app_config.get(DATA_SENSOR, {}), DATA_STORE: store, } diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py index ae8efc0c11325e..36897dd9f693f5 100644 --- a/homeassistant/components/mobile_app/binary_sensor.py +++ b/homeassistant/components/mobile_app/binary_sensor.py @@ -2,18 +2,25 @@ from functools import partial from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID, STATE_ON from homeassistant.core import callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( + ATTR_DEVICE_NAME, + ATTR_SENSOR_ATTRIBUTES, + ATTR_SENSOR_DEVICE_CLASS, + ATTR_SENSOR_ICON, + ATTR_SENSOR_NAME, ATTR_SENSOR_STATE, + ATTR_SENSOR_TYPE, ATTR_SENSOR_TYPE_BINARY_SENSOR as ENTITY_TYPE, ATTR_SENSOR_UNIQUE_ID, DATA_DEVICES, DOMAIN, ) -from .entity import MobileAppEntity, sensor_id +from .entity import MobileAppEntity, unique_id async def async_setup_entry(hass, config_entry, async_add_entities): @@ -22,13 +29,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities): webhook_id = config_entry.data[CONF_WEBHOOK_ID] - for config in hass.data[DOMAIN][ENTITY_TYPE].values(): - if config[CONF_WEBHOOK_ID] != webhook_id: + entity_registry = await er.async_get_registry(hass) + entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + for entry in entries: + if entry.domain != ENTITY_TYPE or entry.disabled_by: continue - - device = hass.data[DOMAIN][DATA_DEVICES][webhook_id] - - entities.append(MobileAppBinarySensor(config, device, config_entry)) + config = { + ATTR_SENSOR_ATTRIBUTES: {}, + ATTR_SENSOR_DEVICE_CLASS: entry.device_class, + ATTR_SENSOR_ICON: entry.original_icon, + ATTR_SENSOR_NAME: entry.original_name, + ATTR_SENSOR_STATE: None, + ATTR_SENSOR_TYPE: entry.domain, + ATTR_SENSOR_UNIQUE_ID: entry.unique_id, + } + entities.append(MobileAppBinarySensor(config, entry.device_id, config_entry)) async_add_entities(entities) @@ -37,14 +52,12 @@ def handle_sensor_registration(webhook_id, data): if data[CONF_WEBHOOK_ID] != webhook_id: return - unique_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]) - - entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id] - - if "added" in entity: - return - - entity["added"] = True + data[CONF_UNIQUE_ID] = unique_id( + data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID] + ) + data[ + CONF_NAME + ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] @@ -64,3 +77,10 @@ class MobileAppBinarySensor(MobileAppEntity, BinarySensorEntity): def is_on(self): """Return the state of the binary sensor.""" return self._config[ATTR_SENSOR_STATE] + + @callback + def async_restore_last_state(self, last_state): + """Restore previous state.""" + + super().async_restore_last_state(last_state) + self._config[ATTR_SENSOR_STATE] = last_state.state == STATE_ON diff --git a/homeassistant/components/mobile_app/config_flow.py b/homeassistant/components/mobile_app/config_flow.py index 08fdecf364d16a..80b6c8db5e1487 100644 --- a/homeassistant/components/mobile_app/config_flow.py +++ b/homeassistant/components/mobile_app/config_flow.py @@ -3,7 +3,7 @@ from homeassistant import config_entries from homeassistant.components import person -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .const import ATTR_APP_ID, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN @@ -36,8 +36,8 @@ async def async_step_registration(self, user_input=None): user_input[ATTR_DEVICE_ID] = str(uuid.uuid4()).replace("-", "") # Register device tracker entity and add to person registering app - ent_reg = await entity_registry.async_get_registry(self.hass) - devt_entry = ent_reg.async_get_or_create( + entity_registry = await er.async_get_registry(self.hass) + devt_entry = entity_registry.async_get_or_create( "device_tracker", DOMAIN, user_input[ATTR_DEVICE_ID], diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index b35468a6fb3e8c..b603e117c4c9f6 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -9,11 +9,9 @@ CONF_SECRET = "secret" CONF_USER_ID = "user_id" -DATA_BINARY_SENSOR = "binary_sensor" DATA_CONFIG_ENTRIES = "config_entries" DATA_DELETED_IDS = "deleted_ids" DATA_DEVICES = "devices" -DATA_SENSOR = "sensor" DATA_STORE = "store" DATA_NOTIFY = "notify" diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 7a12f617740ec7..748f680da5e5b1 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -1,57 +1,70 @@ """A entity class for mobile_app.""" from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.const import ATTR_ICON, CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.restore_state import RestoreEntity from .const import ( - ATTR_DEVICE_NAME, ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_DEVICE_CLASS, ATTR_SENSOR_ICON, - ATTR_SENSOR_NAME, + ATTR_SENSOR_STATE, ATTR_SENSOR_TYPE, ATTR_SENSOR_UNIQUE_ID, - DOMAIN, SIGNAL_SENSOR_UPDATE, ) from .helpers import device_info -def sensor_id(webhook_id, unique_id): +def unique_id(webhook_id, sensor_unique_id): """Return a unique sensor ID.""" - return f"{webhook_id}_{unique_id}" + return f"{webhook_id}_{sensor_unique_id}" -class MobileAppEntity(Entity): +class MobileAppEntity(RestoreEntity): """Representation of an mobile app entity.""" def __init__(self, config: dict, device: DeviceEntry, entry: ConfigEntry): - """Initialize the sensor.""" + """Initialize the entity.""" self._config = config self._device = device self._entry = entry self._registration = entry.data - self._sensor_id = sensor_id( - self._registration[CONF_WEBHOOK_ID], config[ATTR_SENSOR_UNIQUE_ID] - ) + self._unique_id = config[CONF_UNIQUE_ID] self._entity_type = config[ATTR_SENSOR_TYPE] self.unsub_dispatcher = None - self._name = f"{entry.data[ATTR_DEVICE_NAME]} {config[ATTR_SENSOR_NAME]}" + self._name = config[CONF_NAME] async def async_added_to_hass(self): """Register callbacks.""" self.unsub_dispatcher = async_dispatcher_connect( self.hass, SIGNAL_SENSOR_UPDATE, self._handle_update ) + state = await self.async_get_last_state() + + if state is None: + return + + self.async_restore_last_state(state) async def async_will_remove_from_hass(self): """Disconnect dispatcher listener when removed.""" if self.unsub_dispatcher is not None: self.unsub_dispatcher() + @callback + def async_restore_last_state(self, last_state): + """Restore previous state.""" + self._config[ATTR_SENSOR_STATE] = last_state.state + self._config[ATTR_SENSOR_ATTRIBUTES] = { + **last_state.attributes, + **self._config[ATTR_SENSOR_ATTRIBUTES], + } + if ATTR_ICON in last_state.attributes: + self._config[ATTR_SENSOR_ICON] = last_state.attributes[ATTR_ICON] + @property def should_poll(self) -> bool: """Declare that this entity pushes its state to HA.""" @@ -80,27 +93,19 @@ def icon(self): @property def unique_id(self): """Return the unique ID of this sensor.""" - return self._sensor_id + return self._unique_id @property def device_info(self): """Return device registry information for this entity.""" return device_info(self._registration) - async def async_update(self): - """Get the latest state of the sensor.""" - data = self.hass.data[DOMAIN] - try: - self._config = data[self._entity_type][self._sensor_id] - except KeyError: - return - @callback def _handle_update(self, data): """Handle async event updates.""" - incoming_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]) - if incoming_id != self._sensor_id: + incoming_id = unique_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]) + if incoming_id != self._unique_id: return - self._config = data + self._config = {**self._config, **data} self.async_write_ha_state() diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index 7c5cbd135ed454..a9079be4f04834 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -25,9 +25,7 @@ ATTR_SUPPORTS_ENCRYPTION, CONF_SECRET, CONF_USER_ID, - DATA_BINARY_SENSOR, DATA_DELETED_IDS, - DATA_SENSOR, DOMAIN, ) @@ -138,9 +136,7 @@ def safe_registration(registration: Dict) -> Dict: def savable_state(hass: HomeAssistantType) -> Dict: """Return a clean object containing things that should be saved.""" return { - DATA_BINARY_SENSOR: hass.data[DOMAIN][DATA_BINARY_SENSOR], DATA_DELETED_IDS: hass.data[DOMAIN][DATA_DELETED_IDS], - DATA_SENSOR: hass.data[DOMAIN][DATA_SENSOR], } diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index 11e07ed5e7975b..b09ef86453b139 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -1,19 +1,26 @@ """Sensor platform for mobile_app.""" from functools import partial -from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID from homeassistant.core import callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( + ATTR_DEVICE_NAME, + ATTR_SENSOR_ATTRIBUTES, + ATTR_SENSOR_DEVICE_CLASS, + ATTR_SENSOR_ICON, + ATTR_SENSOR_NAME, ATTR_SENSOR_STATE, + ATTR_SENSOR_TYPE, ATTR_SENSOR_TYPE_SENSOR as ENTITY_TYPE, ATTR_SENSOR_UNIQUE_ID, ATTR_SENSOR_UOM, DATA_DEVICES, DOMAIN, ) -from .entity import MobileAppEntity, sensor_id +from .entity import MobileAppEntity, unique_id async def async_setup_entry(hass, config_entry, async_add_entities): @@ -22,13 +29,22 @@ async def async_setup_entry(hass, config_entry, async_add_entities): webhook_id = config_entry.data[CONF_WEBHOOK_ID] - for config in hass.data[DOMAIN][ENTITY_TYPE].values(): - if config[CONF_WEBHOOK_ID] != webhook_id: + entity_registry = await er.async_get_registry(hass) + entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) + for entry in entries: + if entry.domain != ENTITY_TYPE or entry.disabled_by: continue - - device = hass.data[DOMAIN][DATA_DEVICES][webhook_id] - - entities.append(MobileAppSensor(config, device, config_entry)) + config = { + ATTR_SENSOR_ATTRIBUTES: {}, + ATTR_SENSOR_DEVICE_CLASS: entry.device_class, + ATTR_SENSOR_ICON: entry.original_icon, + ATTR_SENSOR_NAME: entry.original_name, + ATTR_SENSOR_STATE: None, + ATTR_SENSOR_TYPE: entry.domain, + ATTR_SENSOR_UNIQUE_ID: entry.unique_id, + ATTR_SENSOR_UOM: entry.unit_of_measurement, + } + entities.append(MobileAppSensor(config, entry.device_id, config_entry)) async_add_entities(entities) @@ -37,14 +53,12 @@ def handle_sensor_registration(webhook_id, data): if data[CONF_WEBHOOK_ID] != webhook_id: return - unique_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]) - - entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id] - - if "added" in entity: - return - - entity["added"] = True + data[CONF_UNIQUE_ID] = unique_id( + data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID] + ) + data[ + CONF_NAME + ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 043a555b6b757c..3044f2df21225e 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -36,6 +36,7 @@ from homeassistant.helpers import ( config_validation as cv, device_registry as dr, + entity_registry as er, template, ) from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -79,7 +80,6 @@ CONF_SECRET, DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, - DATA_STORE, DOMAIN, ERR_ENCRYPTION_ALREADY_ENABLED, ERR_ENCRYPTION_NOT_AVAILABLE, @@ -95,7 +95,6 @@ error_response, registration_context, safe_registration, - savable_state, supports_encryption, webhook_response, ) @@ -415,7 +414,10 @@ async def webhook_register_sensor(hass, config_entry, data): device_name = config_entry.data[ATTR_DEVICE_NAME] unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" - existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type] + entity_registry = await er.async_get_registry(hass) + existing_sensor = entity_registry.async_get_entity_id( + entity_type, DOMAIN, unique_store_key + ) data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID] @@ -424,16 +426,7 @@ async def webhook_register_sensor(hass, config_entry, data): _LOGGER.debug( "Re-register for %s of existing sensor %s", device_name, unique_id ) - entry = hass.data[DOMAIN][entity_type][unique_store_key] - data = {**entry, **data} - - hass.data[DOMAIN][entity_type][unique_store_key] = data - - hass.data[DOMAIN][DATA_STORE].async_delay_save( - lambda: savable_state(hass), DELAY_SAVE - ) - if existing_sensor: async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data) else: register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" @@ -485,7 +478,10 @@ async def webhook_update_sensor_states(hass, config_entry, data): unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" - if unique_store_key not in hass.data[DOMAIN][entity_type]: + entity_registry = await er.async_get_registry(hass) + if not entity_registry.async_get_entity_id( + entity_type, DOMAIN, unique_store_key + ): _LOGGER.error( "Refusing to update %s non-registered sensor: %s", device_name, @@ -498,7 +494,7 @@ async def webhook_update_sensor_states(hass, config_entry, data): } continue - entry = hass.data[DOMAIN][entity_type][unique_store_key] + entry = {CONF_WEBHOOK_ID: config_entry.data[CONF_WEBHOOK_ID]} try: sensor = sensor_schema_full(sensor) @@ -518,16 +514,10 @@ async def webhook_update_sensor_states(hass, config_entry, data): new_state = {**entry, **sensor} - hass.data[DOMAIN][entity_type][unique_store_key] = new_state - async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {"success": True} - hass.data[DOMAIN][DATA_STORE].async_delay_save( - lambda: savable_state(hass), DELAY_SAVE - ) - return webhook_response(resp, registration=config_entry.data) diff --git a/tests/components/mobile_app/conftest.py b/tests/components/mobile_app/conftest.py index 7c611eb1010eec..db4843c126a99f 100644 --- a/tests/components/mobile_app/conftest.py +++ b/tests/components/mobile_app/conftest.py @@ -7,14 +7,6 @@ from .const import REGISTER, REGISTER_CLEARTEXT -from tests.common import mock_device_registry - - -@pytest.fixture -def registry(hass): - """Return a configured device registry.""" - return mock_device_registry(hass) - @pytest.fixture async def create_registrations(hass, authed_api_client): diff --git a/tests/components/mobile_app/test_binary_sensor.py b/tests/components/mobile_app/test_binary_sensor.py new file mode 100644 index 00000000000000..5ada948a5d66ff --- /dev/null +++ b/tests/components/mobile_app/test_binary_sensor.py @@ -0,0 +1,271 @@ +"""Entity tests for mobile_app.""" +from homeassistant.const import STATE_OFF +from homeassistant.helpers import device_registry + + +async def test_sensor(hass, create_registrations, webhook_client): + """Test that sensors can be registered and updated.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "attributes": {"foo": "bar"}, + "device_class": "plug", + "icon": "mdi:power-plug", + "name": "Is Charging", + "state": True, + "type": "binary_sensor", + "unique_id": "is_charging", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("binary_sensor.test_1_is_charging") + assert entity is not None + + assert entity.attributes["device_class"] == "plug" + assert entity.attributes["icon"] == "mdi:power-plug" + assert entity.attributes["foo"] == "bar" + assert entity.domain == "binary_sensor" + assert entity.name == "Test 1 Is Charging" + assert entity.state == "on" + + update_resp = await webhook_client.post( + webhook_url, + json={ + "type": "update_sensor_states", + "data": [ + { + "icon": "mdi:battery-unknown", + "state": False, + "type": "binary_sensor", + "unique_id": "is_charging", + }, + # This invalid data should not invalidate whole request + { + "type": "binary_sensor", + "unique_id": "invalid_state", + "invalid": "data", + }, + ], + }, + ) + + assert update_resp.status == 200 + + json = await update_resp.json() + assert json["invalid_state"]["success"] is False + + updated_entity = hass.states.get("binary_sensor.test_1_is_charging") + assert updated_entity.state == "off" + assert "foo" not in updated_entity.attributes + + dev_reg = await device_registry.async_get_registry(hass) + assert len(dev_reg.devices) == len(create_registrations) + + # Reload to verify state is restored + config_entry = hass.config_entries.async_entries("mobile_app")[1] + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + unloaded_entity = hass.states.get("binary_sensor.test_1_is_charging") + assert unloaded_entity.state == "unavailable" + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + restored_entity = hass.states.get("binary_sensor.test_1_is_charging") + assert restored_entity.state == updated_entity.state + assert restored_entity.attributes == updated_entity.attributes + + +async def test_sensor_must_register(hass, create_registrations, webhook_client): + """Test that sensors must be registered before updating.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + resp = await webhook_client.post( + webhook_url, + json={ + "type": "update_sensor_states", + "data": [ + {"state": True, "type": "binary_sensor", "unique_id": "battery_state"} + ], + }, + ) + + assert resp.status == 200 + + json = await resp.json() + assert json["battery_state"]["success"] is False + assert json["battery_state"]["error"]["code"] == "not_registered" + + +async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog): + """Test that a duplicate unique ID in registration updates the sensor.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + payload = { + "type": "register_sensor", + "data": { + "attributes": {"foo": "bar"}, + "device_class": "plug", + "icon": "mdi:power-plug", + "name": "Is Charging", + "state": True, + "type": "binary_sensor", + "unique_id": "is_charging", + }, + } + + reg_resp = await webhook_client.post(webhook_url, json=payload) + + assert reg_resp.status == 201 + + reg_json = await reg_resp.json() + assert reg_json == {"success": True} + await hass.async_block_till_done() + + assert "Re-register" not in caplog.text + + entity = hass.states.get("binary_sensor.test_1_is_charging") + assert entity is not None + + assert entity.attributes["device_class"] == "plug" + assert entity.attributes["icon"] == "mdi:power-plug" + assert entity.attributes["foo"] == "bar" + assert entity.domain == "binary_sensor" + assert entity.name == "Test 1 Is Charging" + assert entity.state == "on" + + payload["data"]["state"] = False + dupe_resp = await webhook_client.post(webhook_url, json=payload) + + assert dupe_resp.status == 201 + dupe_reg_json = await dupe_resp.json() + assert dupe_reg_json == {"success": True} + await hass.async_block_till_done() + + assert "Re-register" in caplog.text + + entity = hass.states.get("binary_sensor.test_1_is_charging") + assert entity is not None + + assert entity.attributes["device_class"] == "plug" + assert entity.attributes["icon"] == "mdi:power-plug" + assert entity.attributes["foo"] == "bar" + assert entity.domain == "binary_sensor" + assert entity.name == "Test 1 Is Charging" + assert entity.state == "off" + + +async def test_register_sensor_no_state(hass, create_registrations, webhook_client): + """Test that sensors can be registered, when there is no (unknown) state.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Is Charging", + "state": None, + "type": "binary_sensor", + "unique_id": "is_charging", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("binary_sensor.test_1_is_charging") + assert entity is not None + + assert entity.domain == "binary_sensor" + assert entity.name == "Test 1 Is Charging" + assert entity.state == STATE_OFF # Binary sensor defaults to off + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Backup Is Charging", + "type": "binary_sensor", + "unique_id": "backup_is_charging", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("binary_sensor.test_1_backup_is_charging") + assert entity + + assert entity.domain == "binary_sensor" + assert entity.name == "Test 1 Backup Is Charging" + assert entity.state == STATE_OFF # Binary sensor defaults to off + + +async def test_update_sensor_no_state(hass, create_registrations, webhook_client): + """Test that sensors can be updated, when there is no (unknown) state.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + reg_resp = await webhook_client.post( + webhook_url, + json={ + "type": "register_sensor", + "data": { + "name": "Is Charging", + "state": True, + "type": "binary_sensor", + "unique_id": "is_charging", + }, + }, + ) + + assert reg_resp.status == 201 + + json = await reg_resp.json() + assert json == {"success": True} + await hass.async_block_till_done() + + entity = hass.states.get("binary_sensor.test_1_is_charging") + assert entity is not None + assert entity.state == "on" + + update_resp = await webhook_client.post( + webhook_url, + json={ + "type": "update_sensor_states", + "data": [ + {"state": None, "type": "binary_sensor", "unique_id": "is_charging"} + ], + }, + ) + + assert update_resp.status == 200 + + json = await update_resp.json() + assert json == {"is_charging": {"success": True}} + + updated_entity = hass.states.get("binary_sensor.test_1_is_charging") + assert updated_entity.state == STATE_OFF # Binary sensor defaults to off diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_sensor.py similarity index 92% rename from tests/components/mobile_app/test_entity.py rename to tests/components/mobile_app/test_sensor.py index ba121d766acfbc..0ba1cf3096d519 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_sensor.py @@ -66,10 +66,24 @@ async def test_sensor(hass, create_registrations, webhook_client): updated_entity = hass.states.get("sensor.test_1_battery_state") assert updated_entity.state == "123" + assert "foo" not in updated_entity.attributes dev_reg = await device_registry.async_get_registry(hass) assert len(dev_reg.devices) == len(create_registrations) + # Reload to verify state is restored + config_entry = hass.config_entries.async_entries("mobile_app")[1] + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + unloaded_entity = hass.states.get("sensor.test_1_battery_state") + assert unloaded_entity.state == "unavailable" + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + restored_entity = hass.states.get("sensor.test_1_battery_state") + assert restored_entity.state == updated_entity.state + assert restored_entity.attributes == updated_entity.attributes + async def test_sensor_must_register(hass, create_registrations, webhook_client): """Test that sensors must be registered before updating.""" diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 831c8250d7ae5d..a7dc675b7b7ebb 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -109,7 +109,7 @@ async def test_webhook_handle_fire_event(hass, create_registrations, webhook_cli @callback def store_event(event): - """Helepr to store events.""" + """Help store events.""" events.append(event) hass.bus.async_listen("test_event", store_event) From 14a64ea970493eb792ca6e48697b751623bf8252 Mon Sep 17 00:00:00 2001 From: Czapla <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Thu, 11 Feb 2021 20:46:58 +0100 Subject: [PATCH 0415/1818] Add generic_thermostat unique ID parameter (#46399) * Add generic_thermostat unique ID parameter * Add tests for unique id * Fix flake8 --- .../components/generic_thermostat/climate.py | 11 ++++++++ .../generic_thermostat/test_climate.py | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 433e91104ad2de..5fbdf49914614d 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -23,6 +23,7 @@ ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_NAME, + CONF_UNIQUE_ID, EVENT_HOMEASSISTANT_START, PRECISION_HALVES, PRECISION_TENTHS, @@ -85,6 +86,7 @@ vol.Optional(CONF_PRECISION): vol.In( [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] ), + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -109,6 +111,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= away_temp = config.get(CONF_AWAY_TEMP) precision = config.get(CONF_PRECISION) unit = hass.config.units.temperature_unit + unique_id = config.get(CONF_UNIQUE_ID) async_add_entities( [ @@ -128,6 +131,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= away_temp, precision, unit, + unique_id, ) ] ) @@ -153,6 +157,7 @@ def __init__( away_temp, precision, unit, + unique_id, ): """Initialize the thermostat.""" self._name = name @@ -177,6 +182,7 @@ def __init__( self._max_temp = max_temp self._target_temp = target_temp self._unit = unit + self._unique_id = unique_id self._support_flags = SUPPORT_FLAGS if away_temp: self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE @@ -269,6 +275,11 @@ def name(self): """Return the name of the thermostat.""" return self._name + @property + def unique_id(self): + """Return the unique id of this thermostat.""" + return self._unique_id + @property def precision(self): """Return the precision of the system.""" diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index 201ed0130ffe84..e6cdf962d24e09 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -159,6 +159,33 @@ async def test_heater_switch(hass, setup_comp_1): assert STATE_ON == hass.states.get(heater_switch).state +async def test_unique_id(hass, setup_comp_1): + """Test heater switching input_boolean.""" + unique_id = "some_unique_id" + _setup_sensor(hass, 18) + _setup_switch(hass, True) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "unique_id": unique_id, + } + }, + ) + await hass.async_block_till_done() + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + entry = entity_registry.async_get(ENTITY) + assert entry + assert entry.unique_id == unique_id + + def _setup_sensor(hass, temp): """Set up the test sensor.""" hass.states.async_set(ENT_SENSOR, temp) From 8dc06e612fc3e1fe5ea2b6cd9dc493f917b6d6ff Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 11 Feb 2021 21:37:53 +0100 Subject: [PATCH 0416/1818] Add config flow to philips_js (#45784) * Add config flow to philips_js * Adjust name of entry to contain serial * Use device id in event rather than entity id * Adjust turn on text * Deprecate all fields * Be somewhat more explicit in typing * Switch to direct coordinator access * Refactor the pluggable action * Adjust tests a bit * Minor adjustment * More adjustments * Add missing await in update coordinator * Be more lenient to lack of system info * Use constant for trigger type and simplify * Apply suggestions from code review Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .coveragerc | 1 + .../components/philips_js/__init__.py | 132 +++++++++++- .../components/philips_js/config_flow.py | 90 +++++++++ homeassistant/components/philips_js/const.py | 4 + .../components/philips_js/device_trigger.py | 65 ++++++ .../components/philips_js/manifest.json | 11 +- .../components/philips_js/media_player.py | 189 ++++++++++-------- .../components/philips_js/strings.json | 24 +++ .../philips_js/translations/en.json | 24 +++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/philips_js/__init__.py | 25 +++ tests/components/philips_js/conftest.py | 62 ++++++ .../components/philips_js/test_config_flow.py | 105 ++++++++++ .../philips_js/test_device_trigger.py | 69 +++++++ 16 files changed, 721 insertions(+), 86 deletions(-) create mode 100644 homeassistant/components/philips_js/config_flow.py create mode 100644 homeassistant/components/philips_js/const.py create mode 100644 homeassistant/components/philips_js/device_trigger.py create mode 100644 homeassistant/components/philips_js/strings.json create mode 100644 homeassistant/components/philips_js/translations/en.json create mode 100644 tests/components/philips_js/__init__.py create mode 100644 tests/components/philips_js/conftest.py create mode 100644 tests/components/philips_js/test_config_flow.py create mode 100644 tests/components/philips_js/test_device_trigger.py diff --git a/.coveragerc b/.coveragerc index cd1d6a9f6d3fde..c17f3d1057d2d4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -704,6 +704,7 @@ omit = homeassistant/components/pandora/media_player.py homeassistant/components/pcal9535a/* homeassistant/components/pencom/switch.py + homeassistant/components/philips_js/__init__.py homeassistant/components/philips_js/media_player.py homeassistant/components/pi_hole/sensor.py homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 4b011c9f207b74..bfb99898ab2989 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -1 +1,131 @@ -"""The philips_js component.""" +"""The Philips TV integration.""" +import asyncio +from datetime import timedelta +import logging +from typing import Any, Callable, Dict, Optional + +from haphilipsjs import ConnectionFailure, PhilipsTV + +from homeassistant.components.automation import AutomationActionType +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_VERSION, CONF_HOST +from homeassistant.core import Context, HassJob, HomeAssistant, callback +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN + +PLATFORMS = ["media_player"] + +LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Philips TV component.""" + hass.data[DOMAIN] = {} + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Philips TV from a config entry.""" + + tvapi = PhilipsTV(entry.data[CONF_HOST], entry.data[CONF_API_VERSION]) + + coordinator = PhilipsTVDataUpdateCoordinator(hass, tvapi) + + await coordinator.async_refresh() + hass.data[DOMAIN][entry.entry_id] = coordinator + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +class PluggableAction: + """A pluggable action handler.""" + + _actions: Dict[Any, AutomationActionType] = {} + + def __init__(self, update: Callable[[], None]): + """Initialize.""" + self._update = update + + def __bool__(self): + """Return if we have something attached.""" + return bool(self._actions) + + @callback + def async_attach(self, action: AutomationActionType, variables: Dict[str, Any]): + """Attach a device trigger for turn on.""" + + @callback + def _remove(): + del self._actions[_remove] + self._update() + + job = HassJob(action) + + self._actions[_remove] = (job, variables) + self._update() + + return _remove + + async def async_run( + self, hass: HomeAssistantType, context: Optional[Context] = None + ): + """Run all turn on triggers.""" + for job, variables in self._actions.values(): + hass.async_run_hass_job(job, variables, context) + + +class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): + """Coordinator to update data.""" + + api: PhilipsTV + + def __init__(self, hass, api: PhilipsTV) -> None: + """Set up the coordinator.""" + self.api = api + + def _update_listeners(): + for update_callback in self._listeners: + update_callback() + + self.turn_on = PluggableAction(_update_listeners) + + async def _async_update(): + try: + await self.hass.async_add_executor_job(self.api.update) + except ConnectionFailure: + pass + + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_method=_async_update, + update_interval=timedelta(seconds=30), + request_refresh_debouncer=Debouncer( + hass, LOGGER, cooldown=2.0, immediate=False + ), + ) diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py new file mode 100644 index 00000000000000..71bc34688b4e14 --- /dev/null +++ b/homeassistant/components/philips_js/config_flow.py @@ -0,0 +1,90 @@ +"""Config flow for Philips TV integration.""" +import logging +from typing import Any, Dict, Optional, TypedDict + +from haphilipsjs import ConnectionFailure, PhilipsTV +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_API_VERSION, CONF_HOST + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class FlowUserDict(TypedDict): + """Data for user step.""" + + host: str + api_version: int + + +async def validate_input(hass: core.HomeAssistant, data: FlowUserDict): + """Validate the user input allows us to connect.""" + hub = PhilipsTV(data[CONF_HOST], data[CONF_API_VERSION]) + + await hass.async_add_executor_job(hub.getSystem) + + if hub.system is None: + raise ConnectionFailure + + return hub.system + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Philips TV.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + _default = {} + + async def async_step_import(self, conf: Dict[str, Any]): + """Import a configuration from config.yaml.""" + for entry in self._async_current_entries(): + if entry.data[CONF_HOST] == conf[CONF_HOST]: + return self.async_abort(reason="already_configured") + + return await self.async_step_user( + { + CONF_HOST: conf[CONF_HOST], + CONF_API_VERSION: conf[CONF_API_VERSION], + } + ) + + async def async_step_user(self, user_input: Optional[FlowUserDict] = None): + """Handle the initial step.""" + errors = {} + if user_input: + self._default = user_input + try: + system = await validate_input(self.hass, user_input) + except ConnectionFailure: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(system["serialnumber"]) + self._abort_if_unique_id_configured(updates=user_input) + + data = {**user_input, "system": system} + + return self.async_create_entry( + title=f"{system['name']} ({system['serialnumber']})", data=data + ) + + schema = vol.Schema( + { + vol.Required(CONF_HOST, default=self._default.get(CONF_HOST)): str, + vol.Required( + CONF_API_VERSION, default=self._default.get(CONF_API_VERSION) + ): vol.In([1, 6]), + } + ) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/philips_js/const.py b/homeassistant/components/philips_js/const.py new file mode 100644 index 00000000000000..893766b0083632 --- /dev/null +++ b/homeassistant/components/philips_js/const.py @@ -0,0 +1,4 @@ +"""The Philips TV constants.""" + +DOMAIN = "philips_js" +CONF_SYSTEM = "system" diff --git a/homeassistant/components/philips_js/device_trigger.py b/homeassistant/components/philips_js/device_trigger.py new file mode 100644 index 00000000000000..ec1da0635db4d4 --- /dev/null +++ b/homeassistant/components/philips_js/device_trigger.py @@ -0,0 +1,65 @@ +"""Provides device automations for control of device.""" +from typing import List + +import voluptuous as vol + +from homeassistant.components.automation import AutomationActionType +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.device_registry import DeviceRegistry, async_get_registry +from homeassistant.helpers.typing import ConfigType + +from . import PhilipsTVDataUpdateCoordinator +from .const import DOMAIN + +TRIGGER_TYPE_TURN_ON = "turn_on" + +TRIGGER_TYPES = {TRIGGER_TYPE_TURN_ON} +TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + } +) + + +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device triggers for device.""" + triggers = [] + triggers.append( + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: TRIGGER_TYPE_TURN_ON, + } + ) + + return triggers + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: dict, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + registry: DeviceRegistry = await async_get_registry(hass) + if config[CONF_TYPE] == TRIGGER_TYPE_TURN_ON: + variables = { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": config[CONF_DEVICE_ID], + "description": f"philips_js '{config[CONF_TYPE]}' event", + } + } + + device = registry.async_get(config[CONF_DEVICE_ID]) + for config_entry_id in device.config_entries: + coordinator: PhilipsTVDataUpdateCoordinator = hass.data[DOMAIN].get( + config_entry_id + ) + if coordinator: + return coordinator.turn_on.async_attach(action, variables) diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index 74473827424f3f..e41aa348732827 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -2,6 +2,11 @@ "domain": "philips_js", "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", - "requirements": ["ha-philipsjs==0.0.8"], - "codeowners": ["@elupus"] -} + "requirements": [ + "ha-philipsjs==0.1.0" + ], + "codeowners": [ + "@elupus" + ], + "config_flow": true +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 7ccec14406ae80..2e263ea2891ca2 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,11 +1,11 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" -from datetime import timedelta -import logging +from typing import Any, Dict -from haphilipsjs import PhilipsTV import voluptuous as vol +from homeassistant import config_entries from homeassistant.components.media_player import ( + DEVICE_CLASS_TV, PLATFORM_SCHEMA, BrowseMedia, MediaPlayerEntity, @@ -27,6 +27,7 @@ SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError +from homeassistant.components.philips_js import PhilipsTVDataUpdateCoordinator from homeassistant.const import ( CONF_API_VERSION, CONF_HOST, @@ -34,11 +35,13 @@ STATE_OFF, STATE_ON, ) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import call_later, track_time_interval -from homeassistant.helpers.script import Script +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import CoordinatorEntity -_LOGGER = logging.getLogger(__name__) +from . import LOGGER as _LOGGER +from .const import CONF_SYSTEM, DOMAIN SUPPORT_PHILIPS_JS = ( SUPPORT_TURN_OFF @@ -54,24 +57,25 @@ CONF_ON_ACTION = "turn_on_action" -DEFAULT_NAME = "Philips TV" DEFAULT_API_VERSION = "1" -DEFAULT_SCAN_INTERVAL = 30 - -DELAY_ACTION_DEFAULT = 2.0 -DELAY_ACTION_ON = 10.0 PREFIX_SEPARATOR = ": " PREFIX_SOURCE = "Input" PREFIX_CHANNEL = "Channel" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_API_VERSION, default=DEFAULT_API_VERSION): cv.string, - vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, - } +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_HOST), + cv.deprecated(CONF_NAME), + cv.deprecated(CONF_API_VERSION), + cv.deprecated(CONF_ON_ACTION), + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Remove(CONF_NAME): cv.string, + vol.Optional(CONF_API_VERSION, default=DEFAULT_API_VERSION): cv.string, + vol.Remove(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, + } + ), ) @@ -81,70 +85,69 @@ def _inverted(data): def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Philips TV platform.""" - name = config.get(CONF_NAME) - host = config.get(CONF_HOST) - api_version = config.get(CONF_API_VERSION) - turn_on_action = config.get(CONF_ON_ACTION) - - tvapi = PhilipsTV(host, api_version) - domain = __name__.split(".")[-2] - on_script = Script(hass, turn_on_action, name, domain) if turn_on_action else None - - add_entities([PhilipsTVMediaPlayer(tvapi, name, on_script)]) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config, + ) + ) + + +async def async_setup_entry( + hass: HomeAssistantType, + config_entry: config_entries.ConfigEntry, + async_add_entities, +): + """Set up the configuration entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + [ + PhilipsTVMediaPlayer( + coordinator, + config_entry.data[CONF_SYSTEM], + config_entry.unique_id or config_entry.entry_id, + ) + ] + ) -class PhilipsTVMediaPlayer(MediaPlayerEntity): +class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): """Representation of a Philips TV exposing the JointSpace API.""" - def __init__(self, tv: PhilipsTV, name: str, on_script: Script): + def __init__( + self, + coordinator: PhilipsTVDataUpdateCoordinator, + system: Dict[str, Any], + unique_id: str, + ): """Initialize the Philips TV.""" - self._tv = tv - self._name = name + self._tv = coordinator.api + self._coordinator = coordinator self._sources = {} self._channels = {} - self._on_script = on_script self._supports = SUPPORT_PHILIPS_JS - if self._on_script: - self._supports |= SUPPORT_TURN_ON - self._update_task = None + self._system = system + self._unique_id = unique_id + super().__init__(coordinator) + self._update_from_coordinator() - def _update_soon(self, delay): + def _update_soon(self): """Reschedule update task.""" - if self._update_task: - self._update_task() - self._update_task = None - - self.schedule_update_ha_state(force_refresh=False) - - def update_forced(event_time): - self.schedule_update_ha_state(force_refresh=True) - - def update_and_restart(event_time): - update_forced(event_time) - self._update_task = track_time_interval( - self.hass, update_forced, timedelta(seconds=DEFAULT_SCAN_INTERVAL) - ) - - call_later(self.hass, delay, update_and_restart) - - async def async_added_to_hass(self): - """Start running updates once we are added to hass.""" - await self.hass.async_add_executor_job(self._update_soon, 0) + self.hass.add_job(self.coordinator.async_request_refresh) @property def name(self): """Return the device name.""" - return self._name - - @property - def should_poll(self): - """Device should be polled.""" - return False + return self._system["name"] @property def supported_features(self): """Flag media player features that are supported.""" - return self._supports + supports = self._supports + if self._coordinator.turn_on: + supports |= SUPPORT_TURN_ON + return supports @property def state(self): @@ -178,7 +181,7 @@ def select_source(self, source): source_id = _inverted(self._sources).get(source) if source_id: self._tv.setSource(source_id) - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() @property def volume_level(self): @@ -190,47 +193,45 @@ def is_volume_muted(self): """Boolean if volume is currently muted.""" return self._tv.muted - def turn_on(self): + async def async_turn_on(self): """Turn on the device.""" - if self._on_script: - self._on_script.run(context=self._context) - self._update_soon(DELAY_ACTION_ON) + await self._coordinator.turn_on.async_run(self.hass, self._context) def turn_off(self): """Turn off the device.""" self._tv.sendKey("Standby") self._tv.on = False - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() def volume_up(self): """Send volume up command.""" self._tv.sendKey("VolumeUp") - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() def volume_down(self): """Send volume down command.""" self._tv.sendKey("VolumeDown") - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() def mute_volume(self, mute): """Send mute command.""" self._tv.setVolume(None, mute) - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() def set_volume_level(self, volume): """Set volume level, range 0..1.""" self._tv.setVolume(volume, self._tv.muted) - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() def media_previous_track(self): """Send rewind command.""" self._tv.sendKey("Previous") - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() def media_next_track(self): """Send fast forward command.""" self._tv.sendKey("Next") - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() @property def media_channel(self): @@ -267,6 +268,29 @@ def device_state_attributes(self): """Return the state attributes.""" return {"channel_list": list(self._channels.values())} + @property + def device_class(self): + """Return the device class.""" + return DEVICE_CLASS_TV + + @property + def unique_id(self): + """Return unique identifier if known.""" + return self._unique_id + + @property + def device_info(self): + """Return a device description for device registry.""" + return { + "name": self._system["name"], + "identifiers": { + (DOMAIN, self._unique_id), + }, + "model": self._system.get("model"), + "manufacturer": "Philips", + "sw_version": self._system.get("softwareversion"), + } + def play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) @@ -275,7 +299,7 @@ def play_media(self, media_type, media_id, **kwargs): channel_id = _inverted(self._channels).get(media_id) if channel_id: self._tv.setChannel(channel_id) - self._update_soon(DELAY_ACTION_DEFAULT) + self._update_soon() else: _LOGGER.error("Unable to find channel <%s>", media_id) else: @@ -308,10 +332,7 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non ], ) - def update(self): - """Get the latest data and update device state.""" - self._tv.update() - + def _update_from_coordinator(self): self._sources = { srcid: source.get("name") or f"Source {srcid}" for srcid, source in (self._tv.sources or {}).items() @@ -321,3 +342,9 @@ def update(self): chid: channel.get("name") or f"Channel {chid}" for chid, channel in (self._tv.channels or {}).items() } + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._update_from_coordinator() + super()._handle_coordinator_update() diff --git a/homeassistant/components/philips_js/strings.json b/homeassistant/components/philips_js/strings.json new file mode 100644 index 00000000000000..2267315501f771 --- /dev/null +++ b/homeassistant/components/philips_js/strings.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "api_version": "API Version" + } + } + }, + "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%]" + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Device is requested to turn on" + } + } +} diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json new file mode 100644 index 00000000000000..ca580159dab31f --- /dev/null +++ b/homeassistant/components/philips_js/translations/en.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "api_version": "API Version" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Device is requested to turn on" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 6366a3eb8879c6..06e2516633e5ab 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -159,6 +159,7 @@ "owntracks", "ozw", "panasonic_viera", + "philips_js", "pi_hole", "plaato", "plex", diff --git a/requirements_all.txt b/requirements_all.txt index 12ade1a446bcf2..4dbee8b0a65e3d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -720,7 +720,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.0.8 +ha-philipsjs==0.1.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f5a629a771fd4f..88e8dffff1cef9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -380,6 +380,9 @@ guppy3==3.1.0 # homeassistant.components.ffmpeg ha-ffmpeg==3.0.2 +# homeassistant.components.philips_js +ha-philipsjs==0.1.0 + # homeassistant.components.hangouts hangups==0.4.11 diff --git a/tests/components/philips_js/__init__.py b/tests/components/philips_js/__init__.py new file mode 100644 index 00000000000000..1c96a6d4e55d62 --- /dev/null +++ b/tests/components/philips_js/__init__.py @@ -0,0 +1,25 @@ +"""Tests for the Philips TV integration.""" + +MOCK_SERIAL_NO = "1234567890" +MOCK_NAME = "Philips TV" + +MOCK_SYSTEM = { + "menulanguage": "English", + "name": MOCK_NAME, + "country": "Sweden", + "serialnumber": MOCK_SERIAL_NO, + "softwareversion": "abcd", + "model": "modelname", +} + +MOCK_USERINPUT = { + "host": "1.1.1.1", + "api_version": 1, +} + +MOCK_CONFIG = { + **MOCK_USERINPUT, + "system": MOCK_SYSTEM, +} + +MOCK_ENTITY_ID = "media_player.philips_tv" diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py new file mode 100644 index 00000000000000..1f20cd821a55f3 --- /dev/null +++ b/tests/components/philips_js/conftest.py @@ -0,0 +1,62 @@ +"""Standard setup for tests.""" +from unittest.mock import Mock, patch + +from pytest import fixture + +from homeassistant import setup +from homeassistant.components.philips_js.const import DOMAIN + +from . import MOCK_CONFIG, MOCK_ENTITY_ID, MOCK_NAME, MOCK_SERIAL_NO, MOCK_SYSTEM + +from tests.common import MockConfigEntry, mock_device_registry + + +@fixture(autouse=True) +async def setup_notification(hass): + """Configure notification system.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + +@fixture(autouse=True) +def mock_tv(): + """Disable component actual use.""" + tv = Mock(autospec="philips_js.PhilipsTV") + tv.sources = {} + tv.channels = {} + tv.system = MOCK_SYSTEM + + with patch( + "homeassistant.components.philips_js.config_flow.PhilipsTV", return_value=tv + ), patch("homeassistant.components.philips_js.PhilipsTV", return_value=tv): + yield tv + + +@fixture +async def mock_config_entry(hass): + """Get standard player.""" + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, title=MOCK_NAME) + config_entry.add_to_hass(hass) + return config_entry + + +@fixture +def mock_device_reg(hass): + """Get standard device.""" + return mock_device_registry(hass) + + +@fixture +async def mock_entity(hass, mock_device_reg, mock_config_entry): + """Get standard player.""" + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + yield MOCK_ENTITY_ID + + +@fixture +def mock_device(hass, mock_device_reg, mock_entity, mock_config_entry): + """Get standard device.""" + return mock_device_reg.async_get_or_create( + config_entry_id=mock_config_entry.entry_id, + identifiers={(DOMAIN, MOCK_SERIAL_NO)}, + ) diff --git a/tests/components/philips_js/test_config_flow.py b/tests/components/philips_js/test_config_flow.py new file mode 100644 index 00000000000000..75caff788914d2 --- /dev/null +++ b/tests/components/philips_js/test_config_flow.py @@ -0,0 +1,105 @@ +"""Test the Philips TV config flow.""" +from unittest.mock import patch + +from pytest import fixture + +from homeassistant import config_entries +from homeassistant.components.philips_js.const import DOMAIN + +from . import MOCK_CONFIG, MOCK_USERINPUT + + +@fixture(autouse=True) +def mock_setup(): + """Disable component setup.""" + with patch( + "homeassistant.components.philips_js.async_setup", return_value=True + ) as mock_setup: + yield mock_setup + + +@fixture(autouse=True) +def mock_setup_entry(): + """Disable component setup.""" + with patch( + "homeassistant.components.philips_js.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +async def test_import(hass, mock_setup, mock_setup_entry): + """Test we get an item on import.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_USERINPUT, + ) + + assert result["type"] == "create_entry" + assert result["title"] == "Philips TV (1234567890)" + assert result["data"] == MOCK_CONFIG + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_exist(hass, mock_config_entry): + """Test we get an item on import.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_USERINPUT, + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + +async def test_form(hass, mock_setup, mock_setup_entry): + """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"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Philips TV (1234567890)" + assert result2["data"] == MOCK_CONFIG + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect(hass, mock_tv): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_tv.system = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], MOCK_USERINPUT + ) + + assert result["type"] == "form" + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_form_unexpected_error(hass, mock_tv): + """Test we handle unexpected exceptions.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_tv.getSystem.side_effect = Exception("Unexpected exception") + result = await hass.config_entries.flow.async_configure( + result["flow_id"], MOCK_USERINPUT + ) + + assert result["type"] == "form" + assert result["errors"] == {"base": "unknown"} diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py new file mode 100644 index 00000000000000..43c7c424cf9e8d --- /dev/null +++ b/tests/components/philips_js/test_device_trigger.py @@ -0,0 +1,69 @@ +"""The tests for Philips TV device triggers.""" +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.philips_js.const import DOMAIN +from homeassistant.setup import async_setup_component + +from tests.common import ( + assert_lists_same, + async_get_device_automations, + async_mock_service, +) +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_triggers(hass, mock_device): + """Test we get the expected triggers.""" + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": "turn_on", + "device_id": mock_device.id, + }, + ] + triggers = await async_get_device_automations(hass, "trigger", mock_device.id) + assert_lists_same(triggers, expected_triggers) + + +async def test_if_fires_on_turn_on_request(hass, calls, mock_entity, mock_device): + """Test for turn_on and turn_off triggers firing.""" + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": mock_device.id, + "type": "turn_on", + }, + "action": { + "service": "test.automation", + "data_template": {"some": "{{ trigger.device_id }}"}, + }, + } + ] + }, + ) + + await hass.services.async_call( + "media_player", + "turn_on", + {"entity_id": mock_entity}, + blocking=True, + ) + + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == mock_device.id From 34a491f826e4891f62eb368370c2ad1a26e77da2 Mon Sep 17 00:00:00 2001 From: chriss158 Date: Fri, 12 Feb 2021 00:17:49 +0100 Subject: [PATCH 0417/1818] Install libpcap-dev for devcontainer (#46106) --- Dockerfile.dev | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.dev b/Dockerfile.dev index 0d86c1a7eec18d..09f8f155930300 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -14,6 +14,7 @@ RUN \ libswscale-dev \ libswresample-dev \ libavfilter-dev \ + libpcap-dev \ git \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From ee04473e857431fc66424bc4e56d0cfe9851d4ff Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 12 Feb 2021 00:02:46 +0000 Subject: [PATCH 0418/1818] [ci skip] Translation update --- .../components/foscam/translations/pl.json | 2 + .../media_player/translations/pl.json | 7 ++ .../media_player/translations/tr.json | 4 + .../components/mysensors/translations/pl.json | 79 +++++++++++++++++++ .../philips_js/translations/en.json | 6 +- .../philips_js/translations/pl.json | 24 ++++++ .../components/powerwall/translations/ca.json | 8 +- .../components/powerwall/translations/et.json | 1 + .../components/powerwall/translations/no.json | 8 +- .../components/powerwall/translations/pl.json | 8 +- .../components/powerwall/translations/ru.json | 8 +- .../powerwall/translations/zh-Hant.json | 8 +- .../components/roku/translations/no.json | 1 + .../components/roku/translations/pl.json | 1 + .../components/shelly/translations/pl.json | 2 +- .../shelly/translations/zh-Hant.json | 2 +- .../components/tesla/translations/ca.json | 4 + .../components/tesla/translations/no.json | 4 + .../components/tesla/translations/pl.json | 4 + .../components/tesla/translations/ru.json | 4 + .../tesla/translations/zh-Hant.json | 4 + 21 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/mysensors/translations/pl.json create mode 100644 homeassistant/components/philips_js/translations/pl.json diff --git a/homeassistant/components/foscam/translations/pl.json b/homeassistant/components/foscam/translations/pl.json index ef0bcda2b3ac96..d7494e22063b3a 100644 --- a/homeassistant/components/foscam/translations/pl.json +++ b/homeassistant/components/foscam/translations/pl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_response": "Nieprawid\u0142owa odpowied\u017a z urz\u0105dzenia", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { @@ -14,6 +15,7 @@ "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port", + "rtsp_port": "Port RTSP", "stream": "Strumie\u0144", "username": "Nazwa u\u017cytkownika" } diff --git a/homeassistant/components/media_player/translations/pl.json b/homeassistant/components/media_player/translations/pl.json index 23ba46f93391f6..2a70661d788831 100644 --- a/homeassistant/components/media_player/translations/pl.json +++ b/homeassistant/components/media_player/translations/pl.json @@ -6,6 +6,13 @@ "is_on": "odtwarzacz {entity_name} jest w\u0142\u0105czony", "is_paused": "odtwarzanie medi\u00f3w na {entity_name} jest wstrzymane", "is_playing": "{entity_name} odtwarza media" + }, + "trigger_type": { + "idle": "odtwarzacz {entity_name} stanie si\u0119 bezczynny", + "paused": "odtwarzacz {entity_name} zostanie wstrzymany", + "playing": "odtwarzacz {entity_name} rozpocznie odtwarzanie", + "turned_off": "odtwarzacz {entity_name} zostanie wy\u0142\u0105czony", + "turned_on": "odtwarzacz {entity_name} zostanie w\u0142\u0105czony" } }, "state": { diff --git a/homeassistant/components/media_player/translations/tr.json b/homeassistant/components/media_player/translations/tr.json index 1f46c6a8bc7c54..f7b9be9da5361b 100644 --- a/homeassistant/components/media_player/translations/tr.json +++ b/homeassistant/components/media_player/translations/tr.json @@ -3,6 +3,10 @@ "condition_type": { "is_idle": "{entity_name} bo\u015fta", "is_off": "{entity_name} kapal\u0131" + }, + "trigger_type": { + "playing": "{entity_name} oynamaya ba\u015flar", + "turned_off": "{entity_name} kapat\u0131ld\u0131" } }, "state": { diff --git a/homeassistant/components/mysensors/translations/pl.json b/homeassistant/components/mysensors/translations/pl.json new file mode 100644 index 00000000000000..fa67ffe4030422 --- /dev/null +++ b/homeassistant/components/mysensors/translations/pl.json @@ -0,0 +1,79 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "duplicate_persistence_file": "Plik danych z sensora jest ju\u017c w u\u017cyciu", + "duplicate_topic": "Temat jest ju\u017c w u\u017cyciu", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_device": "Nieprawid\u0142owe urz\u0105dzenie", + "invalid_ip": "Nieprawid\u0142owy adres IP", + "invalid_persistence_file": "Nieprawid\u0142owy plik danych z sensora", + "invalid_port": "Nieprawid\u0142owy numer portu", + "invalid_publish_topic": "Nieprawid\u0142owy temat \"publish\"", + "invalid_serial": "Nieprawid\u0142owy port szeregowy", + "invalid_subscribe_topic": "Nieprawid\u0142owy temat \"subscribe\"", + "invalid_version": "Nieprawid\u0142owa wersja MySensors", + "not_a_number": "Prosz\u0119 wpisa\u0107 numer", + "port_out_of_range": "Numer portu musi by\u0107 pomi\u0119dzy 1 a 65535", + "same_topic": "Tematy \"subscribe\" i \"publish\" s\u0105 takie same", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "error": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "duplicate_persistence_file": "Plik danych z sensora jest ju\u017c w u\u017cyciu", + "duplicate_topic": "Temat jest ju\u017c w u\u017cyciu", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "invalid_device": "Nieprawid\u0142owe urz\u0105dzenie", + "invalid_ip": "Nieprawid\u0142owy adres IP", + "invalid_persistence_file": "Nieprawid\u0142owy plik danych z sensora", + "invalid_port": "Nieprawid\u0142owy numer portu", + "invalid_publish_topic": "Nieprawid\u0142owy temat \"publish\"", + "invalid_serial": "Nieprawid\u0142owy port szeregowy", + "invalid_subscribe_topic": "Nieprawid\u0142owy temat \"subscribe\"", + "invalid_version": "Nieprawid\u0142owa wersja MySensors", + "not_a_number": "Prosz\u0119 wpisa\u0107 numer", + "port_out_of_range": "Numer portu musi by\u0107 pomi\u0119dzy 1 a 65535", + "same_topic": "Tematy \"subscribe\" i \"publish\" s\u0105 takie same", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "plik danych z sensora (pozostaw puste. aby wygenerowa\u0107 automatycznie)", + "retain": "flaga \"retain\" dla mqtt", + "topic_in_prefix": "prefix tematu wej\u015bciowego (topic_in_prefix)", + "topic_out_prefix": "prefix tematu wyj\u015bciowego (topic_out_prefix)", + "version": "Wersja MySensors" + }, + "description": "Konfiguracja bramki MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "szybko\u015b\u0107 transmisji (baud rate)", + "device": "Port szeregowy", + "persistence_file": "plik danych z sensora (pozostaw puste. aby wygenerowa\u0107 automatycznie)", + "version": "Wersja MySensors" + }, + "description": "Konfiguracja bramki szeregowej" + }, + "gw_tcp": { + "data": { + "device": "Adres IP bramki", + "persistence_file": "plik danych z sensora (pozostaw puste. aby wygenerowa\u0107 automatycznie)", + "tcp_port": "port", + "version": "Wersja MySensors" + }, + "description": "Konfiguracja bramki LAN" + }, + "user": { + "data": { + "gateway_type": "Typ bramki" + }, + "description": "Wybierz metod\u0119 po\u0142\u0105czenia z bramk\u0105" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index ca580159dab31f..249fe5a892dbc8 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -10,8 +10,8 @@ "step": { "user": { "data": { - "host": "Host", - "api_version": "API Version" + "api_version": "API Version", + "host": "Host" } } } @@ -21,4 +21,4 @@ "turn_on": "Device is requested to turn on" } } -} +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/pl.json b/homeassistant/components/philips_js/translations/pl.json new file mode 100644 index 00000000000000..27c088350c4383 --- /dev/null +++ b/homeassistant/components/philips_js/translations/pl.json @@ -0,0 +1,24 @@ +{ + "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": { + "api_version": "Wersja API", + "host": "Nazwa hosta lub adres IP" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Urz\u0105dzenie zostanie poproszone o w\u0142\u0105czenie" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index 4b176fff686d07..38a86f05d110b8 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/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": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "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." }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "Adre\u00e7a IP" + "ip_address": "Adre\u00e7a IP", + "password": "Contrasenya" }, + "description": "La contrasenya normalment s\u00f3n els darrers cinc car\u00e0cters del n\u00famero de s\u00e8rie de la pasarel\u00b7la de control i es pot trobar a l'aplicaci\u00f3 de Tesla; tamb\u00e9 pot consistir en els darrers 5 car\u00e0cters de la contrasenya que es troba a l'interior de la tapa de la pasarel\u00b7la de control 2.", "title": "Connexi\u00f3 amb el Powerwall" } } diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index b10dca9b08b6f4..eaa70dc0a22511 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -14,6 +14,7 @@ "data": { "ip_address": "IP aadress" }, + "description": "Parool on tavaliselt Backup Gateway seerianumbri viimased 5 t\u00e4hem\u00e4rki ja selle leiad Tesla rakendusest v\u00f5i Backup Gateway 2 luugilt leitud parooli viimased 5 m\u00e4rki.", "title": "Powerwalliga \u00fchendamine" } } diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index cdc04a006ad5c9..13609f911a4b45 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/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", + "invalid_auth": "Ugyldig godkjenning", "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." }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "IP adresse" + "ip_address": "IP adresse", + "password": "Passord" }, + "description": "Passordet er vanligvis de siste 5 tegnene i serienummeret for Backup Gateway, og finnes i Telsa-appen. eller de siste 5 tegnene i passordet som er funnet inne i d\u00f8ren til Backup Gateway 2.", "title": "Koble til powerwall" } } diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index dfd4fa21a375ab..059aab2c014d7d 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/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", + "invalid_auth": "Niepoprawne uwierzytelnienie", "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." }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "Adres IP" + "ip_address": "Adres IP", + "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 Telsa; lub ostatnie 5 znak\u00f3w has\u0142a na wewn\u0119trznej stronie drzwiczek Backup Gateway 2.", "title": "Po\u0142\u0105czenie z Powerwall" } } diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index faabf2d0ede0af..2d8246cc14ff21 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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." }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441" + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", + "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": "Tesla Powerwall" } } diff --git a/homeassistant/components/powerwall/translations/zh-Hant.json b/homeassistant/components/powerwall/translations/zh-Hant.json index ec0d2e278b693c..44e79e935cdf1d 100644 --- a/homeassistant/components/powerwall/translations/zh-Hant.json +++ b/homeassistant/components/powerwall/translations/zh-Hant.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "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" }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "IP \u4f4d\u5740" + "ip_address": "IP \u4f4d\u5740", + "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": "\u9023\u7dda\u81f3 Powerwall" } } diff --git a/homeassistant/components/roku/translations/no.json b/homeassistant/components/roku/translations/no.json index dd4ce4181415fa..e7dc663b8f800e 100644 --- a/homeassistant/components/roku/translations/no.json +++ b/homeassistant/components/roku/translations/no.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "unknown": "Uventet feil" }, "error": { diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 1d193acc0ff15a..1a570c6434776c 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja jest ju\u017c w toku", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index cd8ffac71386e4..a6ca567d91ac26 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?\n\nPrzed skonfigurowaniem urz\u0105dzenia zasilane bateryjnie nale\u017cy, wybudzi\u0107 naciskaj\u0105c przycisk na urz\u0105dzeniu." + "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?\n\nUrz\u0105dzenia zasilane bateryjnie, z ustawionym has\u0142em, nale\u017cy wybudzi\u0107 przed konfiguracj\u0105.\nUrz\u0105dzenia zasilane bateryjnie, bez ustawionego has\u0142a, zostan\u0105 dodane gdy urz\u0105dzenie si\u0119 wybudzi. Mo\u017cesz r\u0119cznie wybudzi\u0107 urz\u0105dzenie jego przyciskiem lub poczeka\u0107 na aktualizacj\u0119 danych z urz\u0105dzenia." }, "credentials": { "data": { diff --git a/homeassistant/components/shelly/translations/zh-Hant.json b/homeassistant/components/shelly/translations/zh-Hant.json index 8f3152081354cc..abc0b627423e9b 100644 --- a/homeassistant/components/shelly/translations/zh-Hant.json +++ b/homeassistant/components/shelly/translations/zh-Hant.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u4f4d\u65bc {host} \u7684 {model}\uff1f\n\n\u958b\u59cb\u8a2d\u5b9a\u524d\uff0c\u5fc5\u9808\u6309\u4e0b\u88dd\u7f6e\u4e0a\u7684\u6309\u9215\u4ee5\u559a\u9192\u96fb\u6c60\u4f9b\u96fb\u88dd\u7f6e\u3002" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u4f4d\u65bc {host} \u7684 {model}\uff1f\n\n\u958b\u59cb\u8a2d\u5b9a\u524d\uff0c\u5fc5\u9808\u6309\u4e0b\u88dd\u7f6e\u4e0a\u7684\u6309\u9215\u4ee5\u559a\u9192\u96fb\u6c60\u4f9b\u96fb\u88dd\u7f6e\u3002\n\u4e0d\u5177\u5bc6\u78bc\u4fdd\u8b77\u7684\u96fb\u6c60\u4f9b\u96fb\u88dd\u7f6e\uff0c\u53ef\u4ee5\u65bc\u559a\u9192\u5f8c\u65b0\u589e\u3002\u53ef\u4ee5\u4f7f\u7528\u88dd\u7f6e\u4e0a\u7684\u6309\u9215\u6216\u7b49\u5f85\u88dd\u7f6e\u4e0b\u4e00\u6b21\u8cc7\u6599\u66f4\u65b0\u6642\u9032\u884c\u624b\u52d5\u559a\u9192\u3002" }, "credentials": { "data": { diff --git a/homeassistant/components/tesla/translations/ca.json b/homeassistant/components/tesla/translations/ca.json index 4d0583af408fbf..2a51c0297ae7ac 100644 --- a/homeassistant/components/tesla/translations/ca.json +++ b/homeassistant/components/tesla/translations/ca.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, "error": { "already_configured": "El compte ja ha estat configurat", "cannot_connect": "Ha fallat la connexi\u00f3", diff --git a/homeassistant/components/tesla/translations/no.json b/homeassistant/components/tesla/translations/no.json index 36cceb97f9f8d2..ce70664063636c 100644 --- a/homeassistant/components/tesla/translations/no.json +++ b/homeassistant/components/tesla/translations/no.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, "error": { "already_configured": "Kontoen er allerede konfigurert", "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/tesla/translations/pl.json b/homeassistant/components/tesla/translations/pl.json index dc4144d0f6a54c..7ec634cd56c033 100644 --- a/homeassistant/components/tesla/translations/pl.json +++ b/homeassistant/components/tesla/translations/pl.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, "error": { "already_configured": "Konto jest ju\u017c skonfigurowane", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", diff --git a/homeassistant/components/tesla/translations/ru.json b/homeassistant/components/tesla/translations/ru.json index 8fe167d8631b89..7429b8ffa535d8 100644 --- a/homeassistant/components/tesla/translations/ru.json +++ b/homeassistant/components/tesla/translations/ru.json @@ -1,5 +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.", + "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": { "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.", "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/tesla/translations/zh-Hant.json b/homeassistant/components/tesla/translations/zh-Hant.json index 235c9036637769..d9b7fd4ef79584 100644 --- a/homeassistant/components/tesla/translations/zh-Hant.json +++ b/homeassistant/components/tesla/translations/zh-Hant.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, "error": { "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "cannot_connect": "\u9023\u7dda\u5931\u6557", From a67b598971eb5142751dd6642697257b82aa31aa Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 12 Feb 2021 02:35:29 +0100 Subject: [PATCH 0419/1818] Correct errors found on post merge review in philips_js (#46428) * Correct missed review changes * Adjust return value for device trigger * Drop cannot connect * Always assume there is a unique id * No need to yield * Update homeassistant/components/philips_js/media_player.py Co-authored-by: Martin Hjelmare * Move typing to init * Adjust typing instead of returning lambda * Explicity return None * Coerce into int Co-authored-by: Martin Hjelmare --- .../components/philips_js/__init__.py | 19 ++++++++----------- .../components/philips_js/config_flow.py | 6 +----- .../components/philips_js/device_trigger.py | 6 ++++-- .../components/philips_js/media_player.py | 10 ++++++---- tests/components/philips_js/conftest.py | 2 +- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index bfb99898ab2989..11e84b6cd82990 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -64,11 +64,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): class PluggableAction: """A pluggable action handler.""" - _actions: Dict[Any, AutomationActionType] = {} - def __init__(self, update: Callable[[], None]): """Initialize.""" self._update = update + self._actions: Dict[Any, AutomationActionType] = {} def __bool__(self): """Return if we have something attached.""" @@ -101,8 +100,6 @@ async def async_run( class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): """Coordinator to update data.""" - api: PhilipsTV - def __init__(self, hass, api: PhilipsTV) -> None: """Set up the coordinator.""" self.api = api @@ -113,19 +110,19 @@ def _update_listeners(): self.turn_on = PluggableAction(_update_listeners) - async def _async_update(): - try: - await self.hass.async_add_executor_job(self.api.update) - except ConnectionFailure: - pass - super().__init__( hass, LOGGER, name=DOMAIN, - update_method=_async_update, update_interval=timedelta(seconds=30), request_refresh_debouncer=Debouncer( hass, LOGGER, cooldown=2.0, immediate=False ), ) + + async def _async_update_data(self): + """Fetch the latest data from the source.""" + try: + await self.hass.async_add_executor_job(self.api.update) + except ConnectionFailure: + pass diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 71bc34688b4e14..523918daa7c6e3 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -5,7 +5,7 @@ from haphilipsjs import ConnectionFailure, PhilipsTV import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries, core from homeassistant.const import CONF_API_VERSION, CONF_HOST from .const import DOMAIN # pylint:disable=unused-import @@ -84,7 +84,3 @@ async def async_step_user(self, user_input: Optional[FlowUserDict] = None): } ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/philips_js/device_trigger.py b/homeassistant/components/philips_js/device_trigger.py index ec1da0635db4d4..2a60a1664bcddf 100644 --- a/homeassistant/components/philips_js/device_trigger.py +++ b/homeassistant/components/philips_js/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for control of device.""" -from typing import List +from typing import List, Optional import voluptuous as vol @@ -43,7 +43,7 @@ async def async_attach_trigger( config: ConfigType, action: AutomationActionType, automation_info: dict, -) -> CALLBACK_TYPE: +) -> Optional[CALLBACK_TYPE]: """Attach a trigger.""" registry: DeviceRegistry = await async_get_registry(hass) if config[CONF_TYPE] == TRIGGER_TYPE_TURN_ON: @@ -63,3 +63,5 @@ async def async_attach_trigger( ) if coordinator: return coordinator.turn_on.async_attach(action, variables) + + return None diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 2e263ea2891ca2..20ef6ed9c0ffe2 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -57,7 +57,7 @@ CONF_ON_ACTION = "turn_on_action" -DEFAULT_API_VERSION = "1" +DEFAULT_API_VERSION = 1 PREFIX_SEPARATOR = ": " PREFIX_SOURCE = "Input" @@ -72,7 +72,9 @@ { vol.Required(CONF_HOST): cv.string, vol.Remove(CONF_NAME): cv.string, - vol.Optional(CONF_API_VERSION, default=DEFAULT_API_VERSION): cv.string, + vol.Optional(CONF_API_VERSION, default=DEFAULT_API_VERSION): vol.Coerce( + int + ), vol.Remove(CONF_ON_ACTION): cv.SCRIPT_SCHEMA, } ), @@ -83,7 +85,7 @@ def _inverted(data): return {v: k for k, v in data.items()} -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Philips TV platform.""" hass.async_create_task( hass.config_entries.flow.async_init( @@ -106,7 +108,7 @@ async def async_setup_entry( PhilipsTVMediaPlayer( coordinator, config_entry.data[CONF_SYSTEM], - config_entry.unique_id or config_entry.entry_id, + config_entry.unique_id, ) ] ) diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py index 1f20cd821a55f3..549ad77fb069f0 100644 --- a/tests/components/philips_js/conftest.py +++ b/tests/components/philips_js/conftest.py @@ -50,7 +50,7 @@ async def mock_entity(hass, mock_device_reg, mock_config_entry): """Get standard player.""" assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - yield MOCK_ENTITY_ID + return MOCK_ENTITY_ID @fixture From 910c034613a43a30f3796637cbae681a049c5d44 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 12 Feb 2021 03:28:11 -0500 Subject: [PATCH 0420/1818] Use core constants for recollect_waste (#46416) --- homeassistant/components/recollect_waste/sensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index d66c2aae0e4c81..66ced51b77f335 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -6,7 +6,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME +from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME, CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.update_coordinator import ( @@ -25,8 +25,6 @@ DEFAULT_NAME = "recollect_waste" DEFAULT_ICON = "mdi:trash-can-outline" -CONF_NAME = "name" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_PLACE_ID): cv.string, From 9b7c39d20ba8e22652695fc145f5a0a4cc041022 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 12 Feb 2021 10:58:20 +0100 Subject: [PATCH 0421/1818] Postponed evaluation of annotations in core (#46434) * Postponed evaluation of annotations in core * Remove unneeded future --- homeassistant/auth/__init__.py | 6 ++-- homeassistant/auth/mfa_modules/__init__.py | 4 ++- homeassistant/auth/providers/__init__.py | 4 ++- homeassistant/auth/providers/homeassistant.py | 4 ++- homeassistant/config_entries.py | 8 +++-- homeassistant/data_entry_flow.py | 4 ++- homeassistant/helpers/check_config.py | 4 ++- homeassistant/helpers/entity_platform.py | 12 ++++---- homeassistant/helpers/intent.py | 14 +++++---- homeassistant/helpers/restore_state.py | 8 +++-- homeassistant/helpers/service.py | 8 +++-- homeassistant/helpers/significant_change.py | 4 ++- homeassistant/helpers/sun.py | 4 ++- homeassistant/helpers/template.py | 8 +++-- homeassistant/loader.py | 30 ++++++++++--------- homeassistant/util/aiohttp.py | 4 +-- homeassistant/util/yaml/objects.py | 4 ++- script/translations/lokalise.py | 4 ++- 18 files changed, 84 insertions(+), 50 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 531e36ff0b309b..7d6f94dda85c0f 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -1,4 +1,6 @@ """Provide an authentication layer for Home Assistant.""" +from __future__ import annotations + import asyncio from collections import OrderedDict from datetime import timedelta @@ -36,7 +38,7 @@ async def auth_manager_from_config( hass: HomeAssistant, provider_configs: List[Dict[str, Any]], module_configs: List[Dict[str, Any]], -) -> "AuthManager": +) -> AuthManager: """Initialize an auth manager from config. CORE_CONFIG_SCHEMA will make sure do duplicated auth providers or @@ -76,7 +78,7 @@ async def auth_manager_from_config( class AuthManagerFlowManager(data_entry_flow.FlowManager): """Manage authentication flows.""" - def __init__(self, hass: HomeAssistant, auth_manager: "AuthManager"): + def __init__(self, hass: HomeAssistant, auth_manager: AuthManager): """Init auth manager flows.""" super().__init__(hass) self.auth_manager = auth_manager diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 6e4b189bf74338..f29f5f8fcc2063 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -1,4 +1,6 @@ """Pluggable auth modules for Home Assistant.""" +from __future__ import annotations + import importlib import logging import types @@ -66,7 +68,7 @@ def input_schema(self) -> vol.Schema: """Return a voluptuous schema to define mfa auth module's input.""" raise NotImplementedError - async def async_setup_flow(self, user_id: str) -> "SetupFlow": + async def async_setup_flow(self, user_id: str) -> SetupFlow: """Return a data entry flow handler for setup module. Mfa module should extend SetupFlow diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index e766083edc382c..2afe1333c6a345 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -1,4 +1,6 @@ """Auth providers for Home Assistant.""" +from __future__ import annotations + import importlib import logging import types @@ -92,7 +94,7 @@ def async_create_credentials(self, data: Dict[str, str]) -> Credentials: # Implement by extending class - async def async_login_flow(self, context: Optional[Dict]) -> "LoginFlow": + async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: """Return the data flow for logging in with auth provider. Auth provider should extend LoginFlow and return an instance. diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 70e2f5403cdefa..c66ffa7332e4a8 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -1,4 +1,6 @@ """Home Assistant auth provider.""" +from __future__ import annotations + import asyncio import base64 from collections import OrderedDict @@ -31,7 +33,7 @@ def _disallow_id(conf: Dict[str, Any]) -> Dict[str, Any]: @callback -def async_get_provider(hass: HomeAssistant) -> "HassAuthProvider": +def async_get_provider(hass: HomeAssistant) -> HassAuthProvider: """Get the provider.""" for prv in hass.auth.auth_providers: if prv.type == "homeassistant": diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 122b6f15e41421..e38136e33ca752 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1,4 +1,6 @@ """Manage config entries in Home Assistant.""" +from __future__ import annotations + import asyncio import functools import logging @@ -526,7 +528,7 @@ async def async_finish_flow( async def async_create_flow( self, handler_key: Any, *, context: Optional[Dict] = None, data: Any = None - ) -> "ConfigFlow": + ) -> ConfigFlow: """Create a flow for specified handler. Handler key is the domain of the component that we want to set up. @@ -890,7 +892,7 @@ def unique_id(self) -> Optional[str]: @staticmethod @callback - def async_get_options_flow(config_entry: ConfigEntry) -> "OptionsFlow": + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Get the options flow for this handler.""" raise data_entry_flow.UnknownHandler @@ -1074,7 +1076,7 @@ async def async_create_flow( *, context: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - ) -> "OptionsFlow": + ) -> OptionsFlow: """Create an options flow for a config entry. Entry_id and flow.handler is the same thing to map entry with flow. diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index c5b67ff16e8c94..85609556217bde 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,4 +1,6 @@ """Classes to help gather user submissions.""" +from __future__ import annotations + import abc import asyncio from typing import Any, Dict, List, Optional, cast @@ -75,7 +77,7 @@ async def async_create_flow( *, context: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - ) -> "FlowHandler": + ) -> FlowHandler: """Create a flow for specified handler. Handler key is the domain of the component that we want to set up. diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 97445b8cee2c0b..7b7b53d3c0fed3 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -1,4 +1,6 @@ """Helper to check the configuration file.""" +from __future__ import annotations + from collections import OrderedDict import logging import os @@ -49,7 +51,7 @@ def add_error( message: str, domain: Optional[str] = None, config: Optional[ConfigType] = None, - ) -> "HomeAssistantConfig": + ) -> HomeAssistantConfig: """Add a single error.""" self.errors.append(CheckConfigError(str(message), domain, config)) return self diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 26fec28c047e7e..509508405a4b4a 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -1,4 +1,6 @@ """Class to manage the entities for a single platform.""" +from __future__ import annotations + import asyncio from contextvars import ContextVar from datetime import datetime, timedelta @@ -245,7 +247,7 @@ async def setup_again(now): # type: ignore[no-untyped-def] warn_task.cancel() def _schedule_add_entities( - self, new_entities: Iterable["Entity"], update_before_add: bool = False + self, new_entities: Iterable[Entity], update_before_add: bool = False ) -> None: """Schedule adding entities for a single platform, synchronously.""" run_callback_threadsafe( @@ -257,7 +259,7 @@ def _schedule_add_entities( @callback def _async_schedule_add_entities( - self, new_entities: Iterable["Entity"], update_before_add: bool = False + self, new_entities: Iterable[Entity], update_before_add: bool = False ) -> None: """Schedule adding entities for a single platform async.""" task = self.hass.async_create_task( @@ -268,7 +270,7 @@ def _async_schedule_add_entities( self._tasks.append(task) def add_entities( - self, new_entities: Iterable["Entity"], update_before_add: bool = False + self, new_entities: Iterable[Entity], update_before_add: bool = False ) -> None: """Add entities for a single platform.""" # That avoid deadlocks @@ -284,7 +286,7 @@ def add_entities( ).result() async def async_add_entities( - self, new_entities: Iterable["Entity"], update_before_add: bool = False + self, new_entities: Iterable[Entity], update_before_add: bool = False ) -> None: """Add entities for a single platform async. @@ -547,7 +549,7 @@ async def async_remove_entity(self, entity_id: str) -> None: async def async_extract_from_service( self, service_call: ServiceCall, expand_group: bool = True - ) -> List["Entity"]: + ) -> List[Entity]: """Extract all known and available entities from a service call. Will return an empty list if entities specified but unknown. diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index f8c8b2c6d8c60b..1c5d56ccbd12b4 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -1,4 +1,6 @@ """Module to coordinate user intentions.""" +from __future__ import annotations + import logging import re from typing import Any, Callable, Dict, Iterable, Optional @@ -29,7 +31,7 @@ @callback @bind_hass -def async_register(hass: HomeAssistantType, handler: "IntentHandler") -> None: +def async_register(hass: HomeAssistantType, handler: IntentHandler) -> None: """Register an intent with Home Assistant.""" intents = hass.data.get(DATA_KEY) if intents is None: @@ -53,7 +55,7 @@ async def async_handle( slots: Optional[_SlotsType] = None, text_input: Optional[str] = None, context: Optional[Context] = None, -) -> "IntentResponse": +) -> IntentResponse: """Handle an intent.""" handler: IntentHandler = hass.data.get(DATA_KEY, {}).get(intent_type) @@ -131,7 +133,7 @@ class IntentHandler: platforms: Optional[Iterable[str]] = [] @callback - def async_can_handle(self, intent_obj: "Intent") -> bool: + def async_can_handle(self, intent_obj: Intent) -> bool: """Test if an intent can be handled.""" return self.platforms is None or intent_obj.platform in self.platforms @@ -152,7 +154,7 @@ def async_validate_slots(self, slots: _SlotsType) -> _SlotsType: return self._slot_schema(slots) # type: ignore - async def async_handle(self, intent_obj: "Intent") -> "IntentResponse": + async def async_handle(self, intent_obj: Intent) -> IntentResponse: """Handle the intent.""" raise NotImplementedError() @@ -195,7 +197,7 @@ def __init__( self.service = service self.speech = speech - async def async_handle(self, intent_obj: "Intent") -> "IntentResponse": + async def async_handle(self, intent_obj: Intent) -> IntentResponse: """Handle the hass intent.""" hass = intent_obj.hass slots = self.async_validate_slots(intent_obj.slots) @@ -236,7 +238,7 @@ def __init__( self.context = context @callback - def create_response(self) -> "IntentResponse": + def create_response(self) -> IntentResponse: """Create a response.""" return IntentResponse(self) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 97069913c80d87..4f738887ce376d 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -1,4 +1,6 @@ """Support for restoring entity states on startup.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging @@ -48,7 +50,7 @@ def as_dict(self) -> Dict[str, Any]: return {"state": self.state.as_dict(), "last_seen": self.last_seen} @classmethod - def from_dict(cls, json_dict: Dict) -> "StoredState": + def from_dict(cls, json_dict: Dict) -> StoredState: """Initialize a stored state from a dict.""" last_seen = json_dict["last_seen"] @@ -62,11 +64,11 @@ class RestoreStateData: """Helper class for managing the helper saved data.""" @classmethod - async def async_get_instance(cls, hass: HomeAssistant) -> "RestoreStateData": + async def async_get_instance(cls, hass: HomeAssistant) -> RestoreStateData: """Get the singleton instance of this data helper.""" @singleton(DATA_RESTORE_STATE_TASK) - async def load_instance(hass: HomeAssistant) -> "RestoreStateData": + async def load_instance(hass: HomeAssistant) -> RestoreStateData: """Get the singleton instance of this data helper.""" data = cls(hass) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index b2fa97d51cc3d0..afc354dae56174 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -1,4 +1,6 @@ """Service calling related helpers.""" +from __future__ import annotations + import asyncio import dataclasses from functools import partial, wraps @@ -230,10 +232,10 @@ def extract_entity_ids( @bind_hass async def async_extract_entities( hass: HomeAssistantType, - entities: Iterable["Entity"], + entities: Iterable[Entity], service_call: ha.ServiceCall, expand_group: bool = True, -) -> List["Entity"]: +) -> List[Entity]: """Extract a list of entity objects from a service call. Will convert group entity ids to the entity ids it represents. @@ -634,7 +636,7 @@ async def entity_service_call( async def _handle_entity_call( hass: HomeAssistantType, - entity: "Entity", + entity: Entity, func: Union[str, Callable[..., Any]], data: Union[Dict, ha.ServiceCall], context: ha.Context, diff --git a/homeassistant/helpers/significant_change.py b/homeassistant/helpers/significant_change.py index 694acfcf2bd759..a7be57693ba230 100644 --- a/homeassistant/helpers/significant_change.py +++ b/homeassistant/helpers/significant_change.py @@ -26,6 +26,8 @@ async def async_check_significant_change( - if either state is unknown/unavailable - state adding/removing """ +from __future__ import annotations + from types import MappingProxyType from typing import Any, Callable, Dict, Optional, Tuple, Union @@ -65,7 +67,7 @@ async def create_checker( hass: HomeAssistant, _domain: str, extra_significant_check: Optional[ExtraCheckTypeFunc] = None, -) -> "SignificantlyChangedChecker": +) -> SignificantlyChangedChecker: """Create a significantly changed checker for a domain.""" await _initialize(hass) return SignificantlyChangedChecker(hass, extra_significant_check) diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index a2385ba397c808..2b82e19b8ce5b3 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -1,4 +1,6 @@ """Helpers for sun events.""" +from __future__ import annotations + import datetime from typing import TYPE_CHECKING, Optional, Union @@ -17,7 +19,7 @@ @callback @bind_hass -def get_astral_location(hass: HomeAssistantType) -> "astral.Location": +def get_astral_location(hass: HomeAssistantType) -> astral.Location: """Get an astral location for the current Home Assistant configuration.""" from astral import Location # pylint: disable=import-outside-toplevel diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 9da0cbc09eb89a..200d678719ac74 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,4 +1,6 @@ """Template helper methods for rendering strings with Home Assistant data.""" +from __future__ import annotations + from ast import literal_eval import asyncio import base64 @@ -155,7 +157,7 @@ class TupleWrapper(tuple, ResultWrapper): def __new__( cls, value: tuple, *, render_result: Optional[str] = None - ) -> "TupleWrapper": + ) -> TupleWrapper: """Create a new tuple class.""" return super().__new__(cls, tuple(value)) @@ -297,7 +299,7 @@ def __init__(self, template, hass=None): self._limited = None @property - def _env(self) -> "TemplateEnvironment": + def _env(self) -> TemplateEnvironment: if self.hass is None or self._limited: return _NO_HASS_ENV ret: Optional[TemplateEnvironment] = self.hass.data.get(_ENVIRONMENT) @@ -530,7 +532,7 @@ def async_render_with_possible_json_value( ) return value if error_value is _SENTINEL else error_value - def _ensure_compiled(self, limited: bool = False) -> "Template": + def _ensure_compiled(self, limited: bool = False) -> Template: """Bind a template to a specific hass instance.""" self.ensure_valid() diff --git a/homeassistant/loader.py b/homeassistant/loader.py index de02db524a71ec..152a3d88b803da 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -4,6 +4,8 @@ This module has quite some complex parts. I have tried to add as much documentation as possible to keep it understandable. """ +from __future__ import annotations + import asyncio import functools as ft import importlib @@ -114,7 +116,7 @@ def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: async def _async_get_custom_components( hass: "HomeAssistant", -) -> Dict[str, "Integration"]: +) -> Dict[str, Integration]: """Return list of custom integrations.""" if hass.config.safe_mode: return {} @@ -155,7 +157,7 @@ def get_sub_directories(paths: List[str]) -> List[pathlib.Path]: async def async_get_custom_components( hass: "HomeAssistant", -) -> Dict[str, "Integration"]: +) -> Dict[str, Integration]: """Return cached list of custom integrations.""" reg_or_evt = hass.data.get(DATA_CUSTOM_COMPONENTS) @@ -175,7 +177,7 @@ async def async_get_custom_components( return cast(Dict[str, "Integration"], reg_or_evt) -async def async_get_config_flows(hass: "HomeAssistant") -> Set[str]: +async def async_get_config_flows(hass: HomeAssistant) -> Set[str]: """Return cached list of config flows.""" # pylint: disable=import-outside-toplevel from homeassistant.generated.config_flows import FLOWS @@ -195,7 +197,7 @@ async def async_get_config_flows(hass: "HomeAssistant") -> Set[str]: return flows -async def async_get_zeroconf(hass: "HomeAssistant") -> Dict[str, List[Dict[str, str]]]: +async def async_get_zeroconf(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]]: """Return cached list of zeroconf types.""" zeroconf: Dict[str, List[Dict[str, str]]] = ZEROCONF.copy() @@ -218,7 +220,7 @@ async def async_get_zeroconf(hass: "HomeAssistant") -> Dict[str, List[Dict[str, return zeroconf -async def async_get_dhcp(hass: "HomeAssistant") -> List[Dict[str, str]]: +async def async_get_dhcp(hass: HomeAssistant) -> List[Dict[str, str]]: """Return cached list of dhcp types.""" dhcp: List[Dict[str, str]] = DHCP.copy() @@ -232,7 +234,7 @@ async def async_get_dhcp(hass: "HomeAssistant") -> List[Dict[str, str]]: return dhcp -async def async_get_homekit(hass: "HomeAssistant") -> Dict[str, str]: +async def async_get_homekit(hass: HomeAssistant) -> Dict[str, str]: """Return cached list of homekit models.""" homekit: Dict[str, str] = HOMEKIT.copy() @@ -251,7 +253,7 @@ async def async_get_homekit(hass: "HomeAssistant") -> Dict[str, str]: return homekit -async def async_get_ssdp(hass: "HomeAssistant") -> Dict[str, List[Dict[str, str]]]: +async def async_get_ssdp(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]]: """Return cached list of ssdp mappings.""" ssdp: Dict[str, List[Dict[str, str]]] = SSDP.copy() @@ -266,7 +268,7 @@ async def async_get_ssdp(hass: "HomeAssistant") -> Dict[str, List[Dict[str, str] return ssdp -async def async_get_mqtt(hass: "HomeAssistant") -> Dict[str, List[str]]: +async def async_get_mqtt(hass: HomeAssistant) -> Dict[str, List[str]]: """Return cached list of MQTT mappings.""" mqtt: Dict[str, List[str]] = MQTT.copy() @@ -287,7 +289,7 @@ class Integration: @classmethod def resolve_from_root( cls, hass: "HomeAssistant", root_module: ModuleType, domain: str - ) -> "Optional[Integration]": + ) -> Optional[Integration]: """Resolve an integration from a root module.""" for base in root_module.__path__: # type: ignore manifest_path = pathlib.Path(base) / domain / "manifest.json" @@ -312,7 +314,7 @@ def resolve_from_root( @classmethod def resolve_legacy( cls, hass: "HomeAssistant", domain: str - ) -> "Optional[Integration]": + ) -> Optional[Integration]: """Resolve legacy component. Will create a stub manifest. @@ -671,7 +673,7 @@ def __getattr__(self, attr: str) -> Any: class Components: """Helper to load components.""" - def __init__(self, hass: "HomeAssistant") -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize the Components class.""" self._hass = hass @@ -697,7 +699,7 @@ def __getattr__(self, comp_name: str) -> ModuleWrapper: class Helpers: """Helper to load helpers.""" - def __init__(self, hass: "HomeAssistant") -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize the Helpers class.""" self._hass = hass @@ -758,7 +760,7 @@ async def _async_component_dependencies( return loaded -def _async_mount_config_dir(hass: "HomeAssistant") -> bool: +def _async_mount_config_dir(hass: HomeAssistant) -> bool: """Mount config dir in order to load custom_component. Async friendly but not a coroutine. @@ -771,7 +773,7 @@ def _async_mount_config_dir(hass: "HomeAssistant") -> bool: return True -def _lookup_path(hass: "HomeAssistant") -> List[str]: +def _lookup_path(hass: HomeAssistant) -> List[str]: """Return the lookup paths for legacy lookups.""" if hass.config.safe_mode: return [PACKAGE_BUILTIN] diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index 36cdc0f25e2fbf..f2c761282bc281 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -48,7 +48,7 @@ def __init__( self.mock_source = mock_source @property - def query(self) -> "MultiDict[str]": + def query(self) -> MultiDict[str]: """Return a dictionary with the query variables.""" return MultiDict(parse_qsl(self.query_string, keep_blank_values=True)) @@ -66,7 +66,7 @@ async def json(self) -> Any: """Return the body as JSON.""" return json.loads(self._text) - async def post(self) -> "MultiDict[str]": + async def post(self) -> MultiDict[str]: """Return POST parameters.""" return MultiDict(parse_qsl(self._text, keep_blank_values=True)) diff --git a/homeassistant/util/yaml/objects.py b/homeassistant/util/yaml/objects.py index 0e46820e0db120..2d318a9def0e24 100644 --- a/homeassistant/util/yaml/objects.py +++ b/homeassistant/util/yaml/objects.py @@ -1,4 +1,6 @@ """Custom yaml object types.""" +from __future__ import annotations + from dataclasses import dataclass import yaml @@ -19,6 +21,6 @@ class Input: name: str @classmethod - def from_node(cls, loader: yaml.Loader, node: yaml.nodes.Node) -> "Input": + def from_node(cls, loader: yaml.Loader, node: yaml.nodes.Node) -> Input: """Create a new placeholder from a node.""" return cls(node.value) diff --git a/script/translations/lokalise.py b/script/translations/lokalise.py index 69860b49e45c23..a23291169f41d8 100644 --- a/script/translations/lokalise.py +++ b/script/translations/lokalise.py @@ -1,4 +1,6 @@ """API for Lokalise.""" +from __future__ import annotations + from pprint import pprint import requests @@ -6,7 +8,7 @@ from .util import get_lokalise_token -def get_api(project_id, debug=False) -> "Lokalise": +def get_api(project_id, debug=False) -> Lokalise: """Get Lokalise API.""" return Lokalise(project_id, get_lokalise_token(), debug) From 0d2f5cf7ed8f5d636386bb0be930ed90320c5188 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 12 Feb 2021 05:42:34 -0500 Subject: [PATCH 0422/1818] Use core constants for plugwise (#46414) --- homeassistant/components/plugwise/config_flow.py | 8 +------- homeassistant/components/plugwise/const.py | 1 - homeassistant/components/plugwise/gateway.py | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index e0d2262773704d..247e0802eaeb8f 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -19,12 +19,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import DiscoveryInfoType -from .const import ( # pylint:disable=unused-import - DEFAULT_PORT, - DEFAULT_SCAN_INTERVAL, - DOMAIN, - ZEROCONF_MAP, -) +from .const import DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN, ZEROCONF_MAP _LOGGER = logging.getLogger(__name__) @@ -152,7 +147,6 @@ async def async_step_user_gateway(self, user_input=None): async def async_step_user(self, user_input=None): """Handle the initial step.""" - # PLACEHOLDER USB vs Gateway Logic return await self.async_step_user_gateway() diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index c6ef43af602667..fb8911d6fc7955 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -22,7 +22,6 @@ DEFAULT_TIMEOUT = 60 # Configuration directives -CONF_BASE = "base" CONF_GAS = "gas" CONF_MAX_TEMP = "max_temp" CONF_MIN_TEMP = "min_temp" diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index a0bf23986bdb6f..c14395319d41de 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -203,7 +203,6 @@ def name(self): @property def device_info(self) -> Dict[str, any]: """Return the device information.""" - device_information = { "identifiers": {(DOMAIN, self._dev_id)}, "name": self._entity_name, From 190a9f66cb1d46b384d1211d83e65e7626413fe7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Feb 2021 11:43:44 +0100 Subject: [PATCH 0423/1818] Improve MQTT timeout print (#46398) --- homeassistant/components/mqtt/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 58c99aa7c007f6..29f7e24da006f5 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -929,7 +929,9 @@ async def _wait_for_mid(self, mid): try: await asyncio.wait_for(self._pending_operations[mid].wait(), TIMEOUT_ACK) except asyncio.TimeoutError: - _LOGGER.error("Timed out waiting for mid %s", mid) + _LOGGER.warning( + "No ACK from MQTT server in %s seconds (mid: %s)", TIMEOUT_ACK, mid + ) finally: del self._pending_operations[mid] From 74f5f8976f289e5e40fbc70d7517ed61c186174d Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 12 Feb 2021 06:15:30 -0500 Subject: [PATCH 0424/1818] Use core constants for rpi_gpio (#46442) --- homeassistant/components/rpi_gpio/binary_sensor.py | 1 - homeassistant/components/rpi_gpio/cover.py | 4 +--- homeassistant/components/rpi_gpio/switch.py | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/rpi_gpio/binary_sensor.py b/homeassistant/components/rpi_gpio/binary_sensor.py index a2461f52db9e5a..36d7ae50f32559 100644 --- a/homeassistant/components/rpi_gpio/binary_sensor.py +++ b/homeassistant/components/rpi_gpio/binary_sensor.py @@ -32,7 +32,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Raspberry PI GPIO devices.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) pull_mode = config.get(CONF_PULL_MODE) diff --git a/homeassistant/components/rpi_gpio/cover.py b/homeassistant/components/rpi_gpio/cover.py index 032796fe55b603..15eae3b4b077ac 100644 --- a/homeassistant/components/rpi_gpio/cover.py +++ b/homeassistant/components/rpi_gpio/cover.py @@ -5,13 +5,12 @@ from homeassistant.components import rpi_gpio from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_COVERS, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import setup_reload_service from . import DOMAIN, PLATFORMS -CONF_COVERS = "covers" CONF_RELAY_PIN = "relay_pin" CONF_RELAY_TIME = "relay_time" CONF_STATE_PIN = "state_pin" @@ -49,7 +48,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the RPi cover platform.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) relay_time = config.get(CONF_RELAY_TIME) diff --git a/homeassistant/components/rpi_gpio/switch.py b/homeassistant/components/rpi_gpio/switch.py index d556d8f035419d..3fba7b4b2cb0a4 100644 --- a/homeassistant/components/rpi_gpio/switch.py +++ b/homeassistant/components/rpi_gpio/switch.py @@ -28,7 +28,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Raspberry PI GPIO devices.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) invert_logic = config.get(CONF_INVERT_LOGIC) From b7dd9bf58f465237cffe1c75355eb09ad2cbc951 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 12 Feb 2021 13:29:11 +0100 Subject: [PATCH 0425/1818] Enhance platform discovery for zwave_js (#46355) --- .../components/zwave_js/discovery.py | 372 +++++++++++++----- 1 file changed, 278 insertions(+), 94 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index be6d9b698d453e..62d45a5ae5e156 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -14,12 +14,14 @@ class ZwaveDiscoveryInfo: """Info discovered from (primary) ZWave Value to create entity.""" - node: ZwaveNode # node to which the value(s) belongs - primary_value: ZwaveValue # the value object itself for primary value - platform: str # the home assistant platform for which an entity should be created - platform_hint: Optional[ - str - ] = "" # hint for the platform about this discovered entity + # node to which the value(s) belongs + node: ZwaveNode + # the value object itself for primary value + primary_value: ZwaveValue + # the home assistant platform for which an entity should be created + platform: str + # hint for the platform about this discovered entity + platform_hint: Optional[str] = "" @property def value_id(self) -> str: @@ -27,38 +29,140 @@ def value_id(self) -> str: return f"{self.node.node_id}.{self.primary_value.value_id}" +@dataclass +class ZWaveValueDiscoverySchema: + """Z-Wave Value discovery schema. + + The Z-Wave Value must match these conditions. + Use the Z-Wave specifications to find out the values for these parameters: + https://github.com/zwave-js/node-zwave-js/tree/master/specs + """ + + # [optional] the value's command class must match ANY of these values + command_class: Optional[Set[int]] = None + # [optional] the value's endpoint must match ANY of these values + endpoint: Optional[Set[int]] = None + # [optional] the value's property must match ANY of these values + property: Optional[Set[Union[str, int]]] = None + # [optional] the value's metadata_type must match ANY of these values + type: Optional[Set[str]] = None + + @dataclass class ZWaveDiscoverySchema: """Z-Wave discovery schema. - The (primary) value for an entity must match these conditions. + The Z-Wave node and it's (primary) value for an entity must match these conditions. Use the Z-Wave specifications to find out the values for these parameters: https://github.com/zwave-js/node-zwave-js/tree/master/specs """ # specify the hass platform for which this scheme applies (e.g. light, sensor) platform: str + # primary value belonging to this discovery scheme + primary_value: ZWaveValueDiscoverySchema # [optional] hint for platform hint: Optional[str] = None + # [optional] the node's manufacturer_id must match ANY of these values + manufacturer_id: Optional[Set[int]] = None + # [optional] the node's product_id must match ANY of these values + product_id: Optional[Set[int]] = None + # [optional] the node's product_type must match ANY of these values + product_type: Optional[Set[int]] = None + # [optional] the node's firmware_version must match ANY of these values + firmware_version: Optional[Set[str]] = None # [optional] the node's basic device class must match ANY of these values device_class_basic: Optional[Set[str]] = None # [optional] the node's generic device class must match ANY of these values device_class_generic: Optional[Set[str]] = None # [optional] the node's specific device class must match ANY of these values device_class_specific: Optional[Set[str]] = None - # [optional] the value's command class must match ANY of these values - command_class: Optional[Set[int]] = None - # [optional] the value's endpoint must match ANY of these values - endpoint: Optional[Set[int]] = None - # [optional] the value's property must match ANY of these values - property: Optional[Set[Union[str, int]]] = None - # [optional] the value's metadata_type must match ANY of these values - type: Optional[Set[str]] = None + # [optional] additional values that ALL need to be present on the node for this scheme to pass + required_values: Optional[Set[ZWaveValueDiscoverySchema]] = None + # [optional] bool to specify if this primary value may be discovered by multiple platforms + allow_multi: bool = False # For device class mapping see: # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json DISCOVERY_SCHEMAS = [ + # ====== START OF DEVICE SPECIFIC MAPPING SCHEMAS ======= + # Honeywell 39358 In-Wall Fan Control using switch multilevel CC + ZWaveDiscoverySchema( + platform="fan", + manufacturer_id={0x0039}, + product_id={0x3131}, + product_type={0x4944}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), + ), + # GE/Jasco fan controllers using switch multilevel CC + ZWaveDiscoverySchema( + platform="fan", + manufacturer_id={0x0063}, + product_id={0x3034, 0x3131, 0x3138}, + product_type={0x4944}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), + ), + # Leviton ZW4SF fan controllers using switch multilevel CC + ZWaveDiscoverySchema( + platform="fan", + manufacturer_id={0x001D}, + product_id={0x0002}, + product_type={0x0038}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), + ), + # Fibaro Shutter Fibaro FGS222 + ZWaveDiscoverySchema( + platform="cover", + hint="fibaro_fgs222", + manufacturer_id={0x010F}, + product_id={0x1000}, + product_type={0x0302}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), + ), + # Qubino flush shutter + ZWaveDiscoverySchema( + platform="cover", + hint="fibaro_fgs222", + manufacturer_id={0x0159}, + product_id={0x0052}, + product_type={0x0003}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), + ), + # Graber/Bali/Spring Fashion Covers + ZWaveDiscoverySchema( + platform="cover", + hint="fibaro_fgs222", + manufacturer_id={0x026E}, + product_id={0x5A31}, + product_type={0x4353}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), + ), + # ====== START OF GENERIC MAPPING SCHEMAS ======= # locks ZWaveDiscoverySchema( platform="lock", @@ -69,12 +173,14 @@ class ZWaveDiscoverySchema: "Secure Keypad Door Lock", "Secure Lockbox", }, - command_class={ - CommandClass.LOCK, - CommandClass.DOOR_LOCK, - }, - property={"currentMode", "locked"}, - type={"number", "boolean"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.LOCK, + CommandClass.DOOR_LOCK, + }, + property={"currentMode", "locked"}, + type={"number", "boolean"}, + ), ), # door lock door status ZWaveDiscoverySchema( @@ -87,12 +193,14 @@ class ZWaveDiscoverySchema: "Secure Keypad Door Lock", "Secure Lockbox", }, - command_class={ - CommandClass.LOCK, - CommandClass.DOOR_LOCK, - }, - property={"doorStatus"}, - type={"any"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.LOCK, + CommandClass.DOOR_LOCK, + }, + property={"doorStatus"}, + type={"any"}, + ), ), # climate ZWaveDiscoverySchema( @@ -102,10 +210,14 @@ class ZWaveDiscoverySchema: "Setback Thermostat", "Thermostat General", "Thermostat General V2", + "General Thermostat", + "General Thermostat V2", }, - command_class={CommandClass.THERMOSTAT_MODE}, - property={"mode"}, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.THERMOSTAT_MODE}, + property={"mode"}, + type={"number"}, + ), ), # climate # setpoint thermostats @@ -115,9 +227,11 @@ class ZWaveDiscoverySchema: device_class_specific={ "Setpoint Thermostat", }, - command_class={CommandClass.THERMOSTAT_SETPOINT}, - property={"setpoint"}, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.THERMOSTAT_SETPOINT}, + property={"setpoint"}, + type={"number"}, + ), ), # lights # primary value is the currentValue (brightness) @@ -132,85 +246,104 @@ class ZWaveDiscoverySchema: "Multilevel Scene Switch", "Unused", }, - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), ), # binary sensors ZWaveDiscoverySchema( platform="binary_sensor", hint="boolean", - command_class={ - CommandClass.SENSOR_BINARY, - CommandClass.BATTERY, - CommandClass.SENSOR_ALARM, - }, - type={"boolean"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.SENSOR_BINARY, + CommandClass.BATTERY, + CommandClass.SENSOR_ALARM, + }, + type={"boolean"}, + ), ), ZWaveDiscoverySchema( platform="binary_sensor", hint="notification", - command_class={ - CommandClass.NOTIFICATION, - }, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.NOTIFICATION, + }, + type={"number"}, + ), + allow_multi=True, ), # generic text sensors ZWaveDiscoverySchema( platform="sensor", hint="string_sensor", - command_class={ - CommandClass.SENSOR_ALARM, - CommandClass.INDICATOR, - }, - type={"string"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.SENSOR_ALARM, + CommandClass.INDICATOR, + }, + type={"string"}, + ), ), # generic numeric sensors ZWaveDiscoverySchema( platform="sensor", hint="numeric_sensor", - command_class={ - CommandClass.SENSOR_MULTILEVEL, - CommandClass.SENSOR_ALARM, - CommandClass.INDICATOR, - CommandClass.BATTERY, - }, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.SENSOR_MULTILEVEL, + CommandClass.SENSOR_ALARM, + CommandClass.INDICATOR, + CommandClass.BATTERY, + }, + type={"number"}, + ), ), # numeric sensors for Meter CC ZWaveDiscoverySchema( platform="sensor", hint="numeric_sensor", - command_class={ - CommandClass.METER, - }, - type={"number"}, - property={"value"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.METER, + }, + type={"number"}, + property={"value"}, + ), ), # special list sensors (Notification CC) ZWaveDiscoverySchema( platform="sensor", hint="list_sensor", - command_class={ - CommandClass.NOTIFICATION, - }, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.NOTIFICATION, + }, + type={"number"}, + ), + allow_multi=True, ), # sensor for basic CC ZWaveDiscoverySchema( platform="sensor", hint="numeric_sensor", - command_class={ - CommandClass.BASIC, - }, - type={"number"}, - property={"currentValue"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={ + CommandClass.BASIC, + }, + type={"number"}, + property={"currentValue"}, + ), ), # binary switches ZWaveDiscoverySchema( platform="switch", - command_class={CommandClass.SWITCH_BINARY}, - property={"currentValue"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_BINARY}, property={"currentValue"} + ), ), # cover ZWaveDiscoverySchema( @@ -223,9 +356,11 @@ class ZWaveDiscoverySchema: "Motor Control Class C", "Multiposition Motor", }, - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), ), # fan ZWaveDiscoverySchema( @@ -233,9 +368,11 @@ class ZWaveDiscoverySchema: hint="fan", device_class_generic={"Multilevel Switch"}, device_class_specific={"Fan Switch"}, - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, + ), ), ] @@ -243,8 +380,33 @@ class ZWaveDiscoverySchema: @callback def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]: """Run discovery on ZWave node and return matching (primary) values.""" + # pylint: disable=too-many-nested-blocks for value in node.values.values(): for schema in DISCOVERY_SCHEMAS: + # check manufacturer_id + if ( + schema.manufacturer_id is not None + and value.node.manufacturer_id not in schema.manufacturer_id + ): + continue + # check product_id + if ( + schema.product_id is not None + and value.node.product_id not in schema.product_id + ): + continue + # check product_type + if ( + schema.product_type is not None + and value.node.product_type not in schema.product_type + ): + continue + # check firmware_version + if ( + schema.firmware_version is not None + and value.node.firmware_version not in schema.firmware_version + ): + continue # check device_class_basic if ( schema.device_class_basic is not None @@ -263,21 +425,19 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None and value.node.device_class.specific not in schema.device_class_specific ): continue - # check command_class - if ( - schema.command_class is not None - and value.command_class not in schema.command_class - ): - continue - # check endpoint - if schema.endpoint is not None and value.endpoint not in schema.endpoint: - continue - # check property - if schema.property is not None and value.property_ not in schema.property: - continue - # check metadata_type - if schema.type is not None and value.metadata.type not in schema.type: + # check primary value + if not check_value(value, schema.primary_value): continue + # check additional required values + if schema.required_values is not None: + required_values_present = True + for val_scheme in schema.required_values: + for val in node.values.values(): + if not check_value(val, val_scheme): + required_values_present = False + break + if not required_values_present: + continue # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( node=value.node, @@ -285,3 +445,27 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None platform=schema.platform, platform_hint=schema.hint, ) + if not schema.allow_multi: + # break out of loop, this value may not be discovered by other schemas/platforms + break + + +@callback +def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool: + """Check if value matches scheme.""" + # check command_class + if ( + schema.command_class is not None + and value.command_class not in schema.command_class + ): + return False + # check endpoint + if schema.endpoint is not None and value.endpoint not in schema.endpoint: + return False + # check property + if schema.property is not None and value.property_ not in schema.property: + return False + # check metadata_type + if schema.type is not None and value.metadata.type not in schema.type: + return False + return True From 479ff92acbc9ec4db09b7a300d2beb67f00ab746 Mon Sep 17 00:00:00 2001 From: Robert Kingston Date: Fri, 12 Feb 2021 23:31:36 +1100 Subject: [PATCH 0426/1818] Fix cmus remote disconnections (#40284) Co-authored-by: Martin Hjelmare --- homeassistant/components/cmus/media_player.py | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index 73a55fda8e3e82..49c10ab92a54fc 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -64,37 +64,66 @@ def setup_platform(hass, config, add_entities, discover_info=None): port = config[CONF_PORT] name = config[CONF_NAME] - try: - cmus_remote = CmusDevice(host, password, port, name) - except exceptions.InvalidPassword: - _LOGGER.error("The provided password was rejected by cmus") - return False - add_entities([cmus_remote], True) + cmus_remote = CmusRemote(server=host, port=port, password=password) + cmus_remote.connect() + + if cmus_remote.cmus is None: + return + + add_entities([CmusDevice(device=cmus_remote, name=name, server=host)], True) + + +class CmusRemote: + """Representation of a cmus connection.""" + + def __init__(self, server, port, password): + """Initialize the cmus remote.""" + + self._server = server + self._port = port + self._password = password + self.cmus = None + + def connect(self): + """Connect to the cmus server.""" + + try: + self.cmus = remote.PyCmus( + server=self._server, port=self._port, password=self._password + ) + except exceptions.InvalidPassword: + _LOGGER.error("The provided password was rejected by cmus") class CmusDevice(MediaPlayerEntity): """Representation of a running cmus.""" # pylint: disable=no-member - def __init__(self, server, password, port, name): + def __init__(self, device, name, server): """Initialize the CMUS device.""" + self._remote = device if server: - self.cmus = remote.PyCmus(server=server, password=password, port=port) auto_name = f"cmus-{server}" else: - self.cmus = remote.PyCmus() auto_name = "cmus-local" self._name = name or auto_name self.status = {} def update(self): """Get the latest data and update the state.""" - status = self.cmus.get_status_dict() - if not status: - _LOGGER.warning("Received no status from cmus") + try: + status = self._remote.cmus.get_status_dict() + except BrokenPipeError: + self._remote.connect() + except exceptions.ConfigurationError: + _LOGGER.warning("A configuration error occurred") + self._remote.connect() else: self.status = status + return + + _LOGGER.warning("Received no status from cmus") @property def name(self): @@ -168,15 +197,15 @@ def supported_features(self): def turn_off(self): """Service to send the CMUS the command to stop playing.""" - self.cmus.player_stop() + self._remote.cmus.player_stop() def turn_on(self): """Service to send the CMUS the command to start playing.""" - self.cmus.player_play() + self._remote.cmus.player_play() def set_volume_level(self, volume): """Set volume level, range 0..1.""" - self.cmus.set_volume(int(volume * 100)) + self._remote.cmus.set_volume(int(volume * 100)) def volume_up(self): """Set the volume up.""" @@ -188,7 +217,7 @@ def volume_up(self): current_volume = left if current_volume <= 100: - self.cmus.set_volume(int(current_volume) + 5) + self._remote.cmus.set_volume(int(current_volume) + 5) def volume_down(self): """Set the volume down.""" @@ -200,12 +229,12 @@ def volume_down(self): current_volume = left if current_volume <= 100: - self.cmus.set_volume(int(current_volume) - 5) + self._remote.cmus.set_volume(int(current_volume) - 5) def play_media(self, media_type, media_id, **kwargs): """Send the play command.""" if media_type in [MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST]: - self.cmus.player_play_file(media_id) + self._remote.cmus.player_play_file(media_id) else: _LOGGER.error( "Invalid media type %s. Only %s and %s are supported", @@ -216,24 +245,24 @@ def play_media(self, media_type, media_id, **kwargs): def media_pause(self): """Send the pause command.""" - self.cmus.player_pause() + self._remote.cmus.player_pause() def media_next_track(self): """Send next track command.""" - self.cmus.player_next() + self._remote.cmus.player_next() def media_previous_track(self): """Send next track command.""" - self.cmus.player_prev() + self._remote.cmus.player_prev() def media_seek(self, position): """Send seek command.""" - self.cmus.seek(position) + self._remote.cmus.seek(position) def media_play(self): """Send the play command.""" - self.cmus.player_play() + self._remote.cmus.player_play() def media_stop(self): """Send the stop command.""" - self.cmus.stop() + self._remote.cmus.stop() From a8beae3c517210823d2ef901196a885d321d5e6e Mon Sep 17 00:00:00 2001 From: David Dix Date: Fri, 12 Feb 2021 13:58:01 +0000 Subject: [PATCH 0427/1818] Add apple tv remote delay command (#46301) Co-authored-by: Martin Hjelmare --- homeassistant/components/apple_tv/remote.py | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py index a76c4c6a20882c..3d88bddcbc9546 100644 --- a/homeassistant/components/apple_tv/remote.py +++ b/homeassistant/components/apple_tv/remote.py @@ -1,8 +1,14 @@ """Remote control support for Apple TV.""" +import asyncio import logging -from homeassistant.components.remote import RemoteEntity +from homeassistant.components.remote import ( + ATTR_DELAY_SECS, + ATTR_NUM_REPEATS, + DEFAULT_DELAY_SECS, + RemoteEntity, +) from homeassistant.const import CONF_NAME from . import AppleTVEntity @@ -43,12 +49,19 @@ async def async_turn_off(self, **kwargs): async def async_send_command(self, command, **kwargs): """Send a command to one device.""" + num_repeats = kwargs[ATTR_NUM_REPEATS] + delay = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS) + if not self.is_on: _LOGGER.error("Unable to send commands, not connected to %s", self._name) return - for single_command in command: - if not hasattr(self.atv.remote_control, single_command): - continue + for _ in range(num_repeats): + for single_command in command: + attr_value = getattr(self.atv.remote_control, single_command, None) + if not attr_value: + raise ValueError("Command not found. Exiting sequence") - await getattr(self.atv.remote_control, single_command)() + _LOGGER.info("Sending command %s", single_command) + await attr_value() + await asyncio.sleep(delay) From c3b460920e76a45ae50f2883545e5c8359d15217 Mon Sep 17 00:00:00 2001 From: Christophe Painchaud Date: Fri, 12 Feb 2021 15:58:59 +0100 Subject: [PATCH 0428/1818] Enable TCP KEEPALIVE to RFLink for dead connection detection (#46438) RFLink compoment when used over TCP protocol suffers a major issue : it doesn't know when connection is timeout or lost because there is no keepalive mechanism so it can stay disconnected forever. I wrote a small patch for the underlying 'python-rflink' library which will enable TCP KEEPPAlive. On HASSIO side it will just add an optional argument in yml file which will propagate to python-rflink caller. --- homeassistant/components/rflink/__init__.py | 26 +++++++++++++++++++ homeassistant/components/rflink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 116b24642134be..6bedea3ecd924b 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -44,12 +44,14 @@ CONF_RECONNECT_INTERVAL = "reconnect_interval" CONF_SIGNAL_REPETITIONS = "signal_repetitions" CONF_WAIT_FOR_ACK = "wait_for_ack" +CONF_KEEPALIVE_IDLE = "tcp_keepalive_idle_timer" DATA_DEVICE_REGISTER = "rflink_device_register" DATA_ENTITY_LOOKUP = "rflink_entity_lookup" DATA_ENTITY_GROUP_LOOKUP = "rflink_entity_group_only_lookup" DEFAULT_RECONNECT_INTERVAL = 10 DEFAULT_SIGNAL_REPETITIONS = 1 +DEFAULT_TCP_KEEPALIVE_IDLE_TIMER = 3600 CONNECTION_TIMEOUT = 10 EVENT_BUTTON_PRESSED = "button_pressed" @@ -85,6 +87,9 @@ vol.Required(CONF_PORT): vol.Any(cv.port, cv.string), vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_WAIT_FOR_ACK, default=True): cv.boolean, + vol.Optional( + CONF_KEEPALIVE_IDLE, default=DEFAULT_TCP_KEEPALIVE_IDLE_TIMER + ): int, vol.Optional( CONF_RECONNECT_INTERVAL, default=DEFAULT_RECONNECT_INTERVAL ): int, @@ -199,6 +204,26 @@ def event_callback(event): # TCP port when host configured, otherwise serial port port = config[DOMAIN][CONF_PORT] + # TCP KEEPALIVE will be enabled if value > 0 + keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] + if keepalive_idle_timer < 0: + _LOGGER.error( + "A bogus TCP Keepalive IDLE timer was provided (%d secs), " + "default value will be used. " + "Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) + keepalive_idle_timer = DEFAULT_TCP_KEEPALIVE_IDLE_TIMER + elif keepalive_idle_timer == 0: + keepalive_idle_timer = None + elif keepalive_idle_timer <= 30: + _LOGGER.warning( + "A very short TCP Keepalive IDLE timer was provided (%d secs), " + "and may produce unexpected disconnections from RFlink device." + " Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) + @callback def reconnect(exc=None): """Schedule reconnect after connection has been unexpectedly lost.""" @@ -223,6 +248,7 @@ async def connect(): connection = create_rflink_connection( port=port, host=host, + keepalive=keepalive_idle_timer, event_callback=event_callback, disconnect_callback=reconnect, loop=hass.loop, diff --git a/homeassistant/components/rflink/manifest.json b/homeassistant/components/rflink/manifest.json index cdcfe97c21918d..f3854a139f2d46 100644 --- a/homeassistant/components/rflink/manifest.json +++ b/homeassistant/components/rflink/manifest.json @@ -2,6 +2,6 @@ "domain": "rflink", "name": "RFLink", "documentation": "https://www.home-assistant.io/integrations/rflink", - "requirements": ["rflink==0.0.55"], + "requirements": ["rflink==0.0.58"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 4dbee8b0a65e3d..f13cd23f0c7dda 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1943,7 +1943,7 @@ restrictedpython==5.1 rfk101py==0.0.1 # homeassistant.components.rflink -rflink==0.0.55 +rflink==0.0.58 # homeassistant.components.ring ring_doorbell==0.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 88e8dffff1cef9..95642b36891b5b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -989,7 +989,7 @@ regenmaschine==3.0.0 restrictedpython==5.1 # homeassistant.components.rflink -rflink==0.0.55 +rflink==0.0.58 # homeassistant.components.ring ring_doorbell==0.6.2 From f929aa222fb513ebb75d830361661d4cb104801c Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 12 Feb 2021 10:09:36 -0500 Subject: [PATCH 0429/1818] Use core constants for roomba (#46441) --- homeassistant/components/roomba/__init__.py | 15 +++------------ homeassistant/components/roomba/config_flow.py | 4 +--- homeassistant/components/roomba/const.py | 2 -- tests/components/roomba/test_config_flow.py | 9 ++------- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 63deead73071c0..658c230c3a77a4 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -6,18 +6,9 @@ from roombapy import Roomba, RoombaConnectionError from homeassistant import exceptions -from homeassistant.const import CONF_HOST, CONF_PASSWORD - -from .const import ( - BLID, - COMPONENTS, - CONF_BLID, - CONF_CONTINUOUS, - CONF_DELAY, - CONF_NAME, - DOMAIN, - ROOMBA_SESSION, -) +from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD + +from .const import BLID, COMPONENTS, CONF_BLID, CONF_CONTINUOUS, DOMAIN, ROOMBA_SESSION _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 4b0b76b44c9129..b1f2b290a5485c 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -9,15 +9,13 @@ from homeassistant import config_entries, core from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS -from homeassistant.const import CONF_HOST, CONF_PASSWORD +from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD from homeassistant.core import callback from . import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout from .const import ( CONF_BLID, CONF_CONTINUOUS, - CONF_DELAY, - CONF_NAME, DEFAULT_CONTINUOUS, DEFAULT_DELAY, ROOMBA_SESSION, diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 06684e63bdc238..2ffb34eb7c8773 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -3,8 +3,6 @@ COMPONENTS = ["sensor", "binary_sensor", "vacuum"] CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" -CONF_DELAY = "delay" -CONF_NAME = "name" CONF_BLID = "blid" DEFAULT_CERT = "/etc/ssl/certs/ca-certificates.crt" DEFAULT_CONTINUOUS = True diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index bf8e674950f68f..b597717e4a8ac8 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -6,13 +6,8 @@ from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS, MAC_ADDRESS -from homeassistant.components.roomba.const import ( - CONF_BLID, - CONF_CONTINUOUS, - CONF_DELAY, - DOMAIN, -) -from homeassistant.const import CONF_HOST, CONF_PASSWORD +from homeassistant.components.roomba.const import CONF_BLID, CONF_CONTINUOUS, DOMAIN +from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_PASSWORD from tests.common import MockConfigEntry From 8418489345b5053e53b59635c594b8e17a4d25df Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 12 Feb 2021 16:33:18 +0100 Subject: [PATCH 0430/1818] Allow Modbus "old" config or discovery_info as configuration (#46445) --- .coveragerc | 1 + homeassistant/components/modbus/__init__.py | 192 +--------------- homeassistant/components/modbus/climate.py | 2 +- homeassistant/components/modbus/const.py | 4 +- homeassistant/components/modbus/cover.py | 2 +- homeassistant/components/modbus/modbus.py | 229 ++++++++++++++++++++ homeassistant/components/modbus/switch.py | 2 +- 7 files changed, 239 insertions(+), 193 deletions(-) create mode 100644 homeassistant/components/modbus/modbus.py diff --git a/.coveragerc b/.coveragerc index c17f3d1057d2d4..6043d3d45f054c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -563,6 +563,7 @@ omit = homeassistant/components/mochad/* homeassistant/components/modbus/climate.py homeassistant/components/modbus/cover.py + homeassistant/components/modbus/modbus.py homeassistant/components/modbus/switch.py homeassistant/components/modbus/sensor.py homeassistant/components/modem_callerid/sensor.py diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index c2a1a76840cd1d..fe6811a35d9697 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -1,9 +1,6 @@ """Support for Modbus.""" import logging -import threading -from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient -from pymodbus.transaction import ModbusRtuFramer import voluptuous as vol from homeassistant.components.cover import ( @@ -24,10 +21,8 @@ CONF_STRUCTURE, CONF_TIMEOUT, CONF_TYPE, - EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import load_platform from .const import ( ATTR_ADDRESS, @@ -71,9 +66,8 @@ DEFAULT_STRUCTURE_PREFIX, DEFAULT_TEMP_UNIT, MODBUS_DOMAIN as DOMAIN, - SERVICE_WRITE_COIL, - SERVICE_WRITE_REGISTER, ) +from .modbus import modbus_setup _LOGGER = logging.getLogger(__name__) @@ -193,186 +187,6 @@ def setup(hass, config): """Set up Modbus component.""" - hass.data[DOMAIN] = hub_collect = {} - - for conf_hub in config[DOMAIN]: - hub_collect[conf_hub[CONF_NAME]] = ModbusHub(conf_hub) - - # load platforms - for component, conf_key in ( - ("climate", CONF_CLIMATES), - ("cover", CONF_COVERS), - ): - if conf_key in conf_hub: - load_platform(hass, component, DOMAIN, conf_hub, config) - - def stop_modbus(event): - """Stop Modbus service.""" - for client in hub_collect.values(): - client.close() - - def write_register(service): - """Write Modbus registers.""" - unit = int(float(service.data[ATTR_UNIT])) - address = int(float(service.data[ATTR_ADDRESS])) - value = service.data[ATTR_VALUE] - client_name = service.data[ATTR_HUB] - if isinstance(value, list): - hub_collect[client_name].write_registers( - unit, address, [int(float(i)) for i in value] - ) - else: - hub_collect[client_name].write_register(unit, address, int(float(value))) - - def write_coil(service): - """Write Modbus coil.""" - unit = service.data[ATTR_UNIT] - address = service.data[ATTR_ADDRESS] - state = service.data[ATTR_STATE] - client_name = service.data[ATTR_HUB] - hub_collect[client_name].write_coil(unit, address, state) - - # do not wait for EVENT_HOMEASSISTANT_START, activate pymodbus now - for client in hub_collect.values(): - client.setup() - - # register function to gracefully stop modbus - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) - - # Register services for modbus - hass.services.register( - DOMAIN, - SERVICE_WRITE_REGISTER, - write_register, - schema=SERVICE_WRITE_REGISTER_SCHEMA, - ) - hass.services.register( - DOMAIN, SERVICE_WRITE_COIL, write_coil, schema=SERVICE_WRITE_COIL_SCHEMA + return modbus_setup( + hass, config, SERVICE_WRITE_REGISTER_SCHEMA, SERVICE_WRITE_COIL_SCHEMA ) - return True - - -class ModbusHub: - """Thread safe wrapper class for pymodbus.""" - - def __init__(self, client_config): - """Initialize the Modbus hub.""" - # generic configuration - self._client = None - self._lock = threading.Lock() - self._config_name = client_config[CONF_NAME] - self._config_type = client_config[CONF_TYPE] - self._config_port = client_config[CONF_PORT] - self._config_timeout = client_config[CONF_TIMEOUT] - self._config_delay = 0 - - if self._config_type == "serial": - # serial configuration - self._config_method = client_config[CONF_METHOD] - self._config_baudrate = client_config[CONF_BAUDRATE] - self._config_stopbits = client_config[CONF_STOPBITS] - self._config_bytesize = client_config[CONF_BYTESIZE] - self._config_parity = client_config[CONF_PARITY] - else: - # network configuration - self._config_host = client_config[CONF_HOST] - self._config_delay = client_config[CONF_DELAY] - if self._config_delay > 0: - _LOGGER.warning( - "Parameter delay is accepted but not used in this version" - ) - - @property - def name(self): - """Return the name of this hub.""" - return self._config_name - - def setup(self): - """Set up pymodbus client.""" - if self._config_type == "serial": - self._client = ModbusSerialClient( - method=self._config_method, - port=self._config_port, - baudrate=self._config_baudrate, - stopbits=self._config_stopbits, - bytesize=self._config_bytesize, - parity=self._config_parity, - timeout=self._config_timeout, - retry_on_empty=True, - ) - elif self._config_type == "rtuovertcp": - self._client = ModbusTcpClient( - host=self._config_host, - port=self._config_port, - framer=ModbusRtuFramer, - timeout=self._config_timeout, - ) - elif self._config_type == "tcp": - self._client = ModbusTcpClient( - host=self._config_host, - port=self._config_port, - timeout=self._config_timeout, - ) - elif self._config_type == "udp": - self._client = ModbusUdpClient( - host=self._config_host, - port=self._config_port, - timeout=self._config_timeout, - ) - else: - assert False - - # Connect device - self.connect() - - def close(self): - """Disconnect client.""" - with self._lock: - self._client.close() - - def connect(self): - """Connect client.""" - with self._lock: - self._client.connect() - - def read_coils(self, unit, address, count): - """Read coils.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_coils(address, count, **kwargs) - - def read_discrete_inputs(self, unit, address, count): - """Read discrete inputs.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_discrete_inputs(address, count, **kwargs) - - def read_input_registers(self, unit, address, count): - """Read input registers.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_input_registers(address, count, **kwargs) - - def read_holding_registers(self, unit, address, count): - """Read holding registers.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - return self._client.read_holding_registers(address, count, **kwargs) - - def write_coil(self, unit, address, value): - """Write coil.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - self._client.write_coil(address, value, **kwargs) - - def write_register(self, unit, address, value): - """Write register.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - self._client.write_register(address, value, **kwargs) - - def write_registers(self, unit, address, values): - """Write registers.""" - with self._lock: - kwargs = {"unit": unit} if unit else {} - self._client.write_registers(address, values, **kwargs) diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index c1b7b2e6bf4fd5..45cfbf5eb57134 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -29,7 +29,6 @@ HomeAssistantType, ) -from . import ModbusHub from .const import ( CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT, @@ -49,6 +48,7 @@ DEFAULT_STRUCT_FORMAT, MODBUS_DOMAIN, ) +from .modbus import ModbusHub _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index 5e304165e42331..d3193cc004c706 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -16,7 +16,7 @@ CONF_COILS = "coils" # integration names -DEFAULT_HUB = "default" +DEFAULT_HUB = "modbus_hub" MODBUS_DOMAIN = "modbus" # data types @@ -67,6 +67,7 @@ # climate.py CONF_CLIMATES = "climates" +CONF_CLIMATE = "climate" CONF_TARGET_TEMP = "target_temp_register" CONF_CURRENT_TEMP = "current_temp_register" CONF_CURRENT_TEMP_REGISTER_TYPE = "current_temp_register_type" @@ -80,6 +81,7 @@ DEFAULT_TEMP_UNIT = "C" # cover.py +CONF_COVER = "cover" CONF_STATE_OPEN = "state_open" CONF_STATE_CLOSED = "state_closed" CONF_STATE_OPENING = "state_opening" diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index 709c772564ab16..09a465a2cdd7c1 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -21,7 +21,6 @@ HomeAssistantType, ) -from . import ModbusHub from .const import ( CALL_TYPE_COIL, CALL_TYPE_REGISTER_HOLDING, @@ -35,6 +34,7 @@ CONF_STATUS_REGISTER_TYPE, MODBUS_DOMAIN, ) +from .modbus import ModbusHub async def async_setup_platform( diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py new file mode 100644 index 00000000000000..21c6caa6fcccd4 --- /dev/null +++ b/homeassistant/components/modbus/modbus.py @@ -0,0 +1,229 @@ +"""Support for Modbus.""" +import logging +import threading + +from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient +from pymodbus.transaction import ModbusRtuFramer + +from homeassistant.const import ( + ATTR_STATE, + CONF_COVERS, + CONF_DELAY, + CONF_HOST, + CONF_METHOD, + CONF_NAME, + CONF_PORT, + CONF_TIMEOUT, + CONF_TYPE, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.helpers.discovery import load_platform + +from .const import ( + ATTR_ADDRESS, + ATTR_HUB, + ATTR_UNIT, + ATTR_VALUE, + CONF_BAUDRATE, + CONF_BYTESIZE, + CONF_CLIMATE, + CONF_CLIMATES, + CONF_COVER, + CONF_PARITY, + CONF_STOPBITS, + MODBUS_DOMAIN as DOMAIN, + SERVICE_WRITE_COIL, + SERVICE_WRITE_REGISTER, +) + +_LOGGER = logging.getLogger(__name__) + + +def modbus_setup( + hass, config, service_write_register_schema, service_write_coil_schema +): + """Set up Modbus component.""" + hass.data[DOMAIN] = hub_collect = {} + + for conf_hub in config[DOMAIN]: + hub_collect[conf_hub[CONF_NAME]] = ModbusHub(conf_hub) + + # modbus needs to be activated before components are loaded + # to avoid a racing problem + hub_collect[conf_hub[CONF_NAME]].setup() + + # load platforms + for component, conf_key in ( + (CONF_CLIMATE, CONF_CLIMATES), + (CONF_COVER, CONF_COVERS), + ): + if conf_key in conf_hub: + load_platform(hass, component, DOMAIN, conf_hub, config) + + def stop_modbus(event): + """Stop Modbus service.""" + for client in hub_collect.values(): + client.close() + + def write_register(service): + """Write Modbus registers.""" + unit = int(float(service.data[ATTR_UNIT])) + address = int(float(service.data[ATTR_ADDRESS])) + value = service.data[ATTR_VALUE] + client_name = service.data[ATTR_HUB] + if isinstance(value, list): + hub_collect[client_name].write_registers( + unit, address, [int(float(i)) for i in value] + ) + else: + hub_collect[client_name].write_register(unit, address, int(float(value))) + + def write_coil(service): + """Write Modbus coil.""" + unit = service.data[ATTR_UNIT] + address = service.data[ATTR_ADDRESS] + state = service.data[ATTR_STATE] + client_name = service.data[ATTR_HUB] + hub_collect[client_name].write_coil(unit, address, state) + + # register function to gracefully stop modbus + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) + + # Register services for modbus + hass.services.register( + DOMAIN, + SERVICE_WRITE_REGISTER, + write_register, + schema=service_write_register_schema, + ) + hass.services.register( + DOMAIN, SERVICE_WRITE_COIL, write_coil, schema=service_write_coil_schema + ) + return True + + +class ModbusHub: + """Thread safe wrapper class for pymodbus.""" + + def __init__(self, client_config): + """Initialize the Modbus hub.""" + + # generic configuration + self._client = None + self._lock = threading.Lock() + self._config_name = client_config[CONF_NAME] + self._config_type = client_config[CONF_TYPE] + self._config_port = client_config[CONF_PORT] + self._config_timeout = client_config[CONF_TIMEOUT] + self._config_delay = 0 + + if self._config_type == "serial": + # serial configuration + self._config_method = client_config[CONF_METHOD] + self._config_baudrate = client_config[CONF_BAUDRATE] + self._config_stopbits = client_config[CONF_STOPBITS] + self._config_bytesize = client_config[CONF_BYTESIZE] + self._config_parity = client_config[CONF_PARITY] + else: + # network configuration + self._config_host = client_config[CONF_HOST] + self._config_delay = client_config[CONF_DELAY] + if self._config_delay > 0: + _LOGGER.warning( + "Parameter delay is accepted but not used in this version" + ) + + @property + def name(self): + """Return the name of this hub.""" + return self._config_name + + def setup(self): + """Set up pymodbus client.""" + if self._config_type == "serial": + self._client = ModbusSerialClient( + method=self._config_method, + port=self._config_port, + baudrate=self._config_baudrate, + stopbits=self._config_stopbits, + bytesize=self._config_bytesize, + parity=self._config_parity, + timeout=self._config_timeout, + retry_on_empty=True, + ) + elif self._config_type == "rtuovertcp": + self._client = ModbusTcpClient( + host=self._config_host, + port=self._config_port, + framer=ModbusRtuFramer, + timeout=self._config_timeout, + ) + elif self._config_type == "tcp": + self._client = ModbusTcpClient( + host=self._config_host, + port=self._config_port, + timeout=self._config_timeout, + ) + elif self._config_type == "udp": + self._client = ModbusUdpClient( + host=self._config_host, + port=self._config_port, + timeout=self._config_timeout, + ) + else: + assert False + + # Connect device + self.connect() + + def close(self): + """Disconnect client.""" + with self._lock: + self._client.close() + + def connect(self): + """Connect client.""" + with self._lock: + self._client.connect() + + def read_coils(self, unit, address, count): + """Read coils.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + return self._client.read_coils(address, count, **kwargs) + + def read_discrete_inputs(self, unit, address, count): + """Read discrete inputs.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + return self._client.read_discrete_inputs(address, count, **kwargs) + + def read_input_registers(self, unit, address, count): + """Read input registers.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + return self._client.read_input_registers(address, count, **kwargs) + + def read_holding_registers(self, unit, address, count): + """Read holding registers.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + return self._client.read_holding_registers(address, count, **kwargs) + + def write_coil(self, unit, address, value): + """Write coil.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + self._client.write_coil(address, value, **kwargs) + + def write_register(self, unit, address, value): + """Write register.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + self._client.write_register(address, value, **kwargs) + + def write_registers(self, unit, address, values): + """Write registers.""" + with self._lock: + kwargs = {"unit": unit} if unit else {} + self._client.write_registers(address, values, **kwargs) diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index b1b07fb5a558d1..36fbef084281f6 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -20,7 +20,6 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from . import ModbusHub from .const import ( CALL_TYPE_COIL, CALL_TYPE_REGISTER_HOLDING, @@ -37,6 +36,7 @@ DEFAULT_HUB, MODBUS_DOMAIN, ) +from .modbus import ModbusHub _LOGGER = logging.getLogger(__name__) From f1714dd541c863a0a42844582ba00e80b2ed40fb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Feb 2021 17:00:35 +0100 Subject: [PATCH 0431/1818] Make some Area and EntityRegistry member functions callbacks (#46433) --- homeassistant/components/config/area_registry.py | 2 +- homeassistant/helpers/area_registry.py | 11 +++++------ homeassistant/helpers/entity_registry.py | 6 ++++-- tests/helpers/test_area_registry.py | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index 81daf35339e881..f40ed7834e3d6f 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -90,7 +90,7 @@ async def websocket_delete_area(hass, connection, msg): registry = await async_get_registry(hass) try: - await registry.async_delete(msg["area_id"]) + registry.async_delete(msg["area_id"]) except KeyError: connection.send_message( websocket_api.error_message( diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index a41e748d1adb64..562e832cc199cc 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,11 +1,11 @@ """Provide a way to connect devices to one physical location.""" -from asyncio import gather from collections import OrderedDict from typing import Container, Dict, Iterable, List, MutableMapping, Optional, cast import attr from homeassistant.core import callback +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.loader import bind_hass from homeassistant.util import slugify @@ -72,12 +72,11 @@ def async_create(self, name: str) -> AreaEntry: ) return area - async def async_delete(self, area_id: str) -> None: + @callback + def async_delete(self, area_id: str) -> None: """Delete area.""" - device_registry, entity_registry = await gather( - self.hass.helpers.device_registry.async_get_registry(), - self.hass.helpers.entity_registry.async_get_registry(), - ) + device_registry = dr.async_get(self.hass) + entity_registry = er.async_get(self.hass) device_registry.async_clear_area_id(area_id) entity_registry.async_clear_area_id(area_id) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 51985f7bae45aa..418c3f903047b5 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -35,6 +35,7 @@ STATE_UNAVAILABLE, ) from homeassistant.core import Event, callback, split_entity_id, valid_entity_id +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.loader import bind_hass from homeassistant.util import slugify @@ -313,7 +314,8 @@ def async_remove(self, entity_id: str) -> None: ) self.async_schedule_save() - async def async_device_modified(self, event: Event) -> None: + @callback + def async_device_modified(self, event: Event) -> None: """Handle the removal or update of a device. Remove entities from the registry that are associated to a device when @@ -333,7 +335,7 @@ async def async_device_modified(self, event: Event) -> None: if event.data["action"] != "update": return - device_registry = await self.hass.helpers.device_registry.async_get_registry() + 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 diff --git a/tests/helpers/test_area_registry.py b/tests/helpers/test_area_registry.py index 2b06202c862171..0bfa5e597d276a 100644 --- a/tests/helpers/test_area_registry.py +++ b/tests/helpers/test_area_registry.py @@ -81,7 +81,7 @@ async def test_delete_area(hass, registry, update_events): """Make sure that we can delete an area.""" area = registry.async_create("mock") - await registry.async_delete(area.id) + registry.async_delete(area.id) assert not registry.areas From 362a1cd9bd5d17bb8e3caec84f3272e425fc9b49 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 12 Feb 2021 17:59:08 +0100 Subject: [PATCH 0432/1818] Upgrade sentry-sdk to 0.20.1 (#46456) --- 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 da5294b9258ae9..090d19eb2fc49b 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,6 +3,6 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==0.19.5"], + "requirements": ["sentry-sdk==0.20.1"], "codeowners": ["@dcramer", "@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index f13cd23f0c7dda..f6716f52511e67 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2013,7 +2013,7 @@ sense-hat==2.2.0 sense_energy==0.8.1 # homeassistant.components.sentry -sentry-sdk==0.19.5 +sentry-sdk==0.20.1 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 95642b36891b5b..748fd69513722d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1023,7 +1023,7 @@ scapy==2.4.4 sense_energy==0.8.1 # homeassistant.components.sentry -sentry-sdk==0.19.5 +sentry-sdk==0.20.1 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From 061d9c5293d1e049ee3d407aafa718891822f2dc Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 12 Feb 2021 18:11:35 +0100 Subject: [PATCH 0433/1818] Bump brother library to version 0.2.1 (#46421) --- homeassistant/components/brother/__init__.py | 42 ++++++++----------- .../components/brother/config_flow.py | 10 +++-- homeassistant/components/brother/const.py | 4 ++ .../components/brother/manifest.json | 2 +- homeassistant/components/brother/sensor.py | 3 +- homeassistant/components/brother/utils.py | 30 +++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 homeassistant/components/brother/utils.py diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index 8c5cdb2d7edd35..d7cf906a87ca88 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -6,12 +6,13 @@ from brother import Brother, SnmpError, UnsupportedModel from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_TYPE, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.core import Config, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import DATA_CONFIG_ENTRY, DOMAIN, SNMP +from .utils import get_snmp_engine PLATFORMS = ["sensor"] @@ -30,15 +31,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): host = entry.data[CONF_HOST] kind = entry.data[CONF_TYPE] - coordinator = BrotherDataUpdateCoordinator(hass, host=host, kind=kind) + snmp_engine = get_snmp_engine(hass) + + coordinator = BrotherDataUpdateCoordinator( + hass, host=host, kind=kind, snmp_engine=snmp_engine + ) await coordinator.async_refresh() if not coordinator.last_update_success: - coordinator.shutdown() raise ConfigEntryNotReady hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = coordinator + hass.data[DOMAIN].setdefault(DATA_CONFIG_ENTRY, {}) + hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = coordinator + hass.data[DOMAIN][SNMP] = snmp_engine for component in PLATFORMS: hass.async_create_task( @@ -59,7 +65,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id).shutdown() + hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id) + if not hass.data[DOMAIN][DATA_CONFIG_ENTRY]: + hass.data[DOMAIN].pop(SNMP) + hass.data[DOMAIN].pop(DATA_CONFIG_ENTRY) return unload_ok @@ -67,12 +76,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): class BrotherDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching Brother data from the printer.""" - def __init__(self, hass, host, kind): + def __init__(self, hass, host, kind, snmp_engine): """Initialize.""" - self.brother = Brother(host, kind=kind) - self._unsub_stop = hass.bus.async_listen( - EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop - ) + self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine) super().__init__( hass, @@ -83,22 +89,8 @@ def __init__(self, hass, host, kind): async def _async_update_data(self): """Update data via library.""" - # Race condition on shutdown. Stop all the fetches. - if self._unsub_stop is None: - return None - try: await self.brother.async_update() except (ConnectionError, SnmpError, UnsupportedModel) as error: raise UpdateFailed(error) from error return self.brother.data - - def shutdown(self): - """Shutdown the Brother coordinator.""" - self._unsub_stop() - self._unsub_stop = None - self.brother.shutdown() - - def _handle_ha_stop(self, _): - """Handle Home Assistant stopping.""" - self.shutdown() diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index aa9d7ce53a3e40..6a9d2ca6746440 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -9,6 +9,7 @@ from homeassistant.const import CONF_HOST, CONF_TYPE from .const import DOMAIN, PRINTER_TYPES # pylint:disable=unused-import +from .utils import get_snmp_engine DATA_SCHEMA = vol.Schema( { @@ -48,9 +49,10 @@ async def async_step_user(self, user_input=None): if not host_valid(user_input[CONF_HOST]): raise InvalidHost() - brother = Brother(user_input[CONF_HOST]) + snmp_engine = get_snmp_engine(self.hass) + + brother = Brother(user_input[CONF_HOST], snmp_engine=snmp_engine) await brother.async_update() - brother.shutdown() await self.async_set_unique_id(brother.serial.lower()) self._abort_if_unique_id_configured() @@ -83,7 +85,9 @@ async def async_step_zeroconf(self, discovery_info): # Hostname is format: brother.local. self.host = discovery_info["hostname"].rstrip(".") - self.brother = Brother(self.host) + snmp_engine = get_snmp_engine(self.hass) + + self.brother = Brother(self.host, snmp_engine=snmp_engine) try: await self.brother.async_update() except (ConnectionError, SnmpError, UnsupportedModel): diff --git a/homeassistant/components/brother/const.py b/homeassistant/components/brother/const.py index 5aecde16327d53..5ae459c79aa144 100644 --- a/homeassistant/components/brother/const.py +++ b/homeassistant/components/brother/const.py @@ -41,12 +41,16 @@ ATTR_YELLOW_INK_REMAINING = "yellow_ink_remaining" ATTR_YELLOW_TONER_REMAINING = "yellow_toner_remaining" +DATA_CONFIG_ENTRY = "config_entry" + DOMAIN = "brother" UNIT_PAGES = "p" PRINTER_TYPES = ["laser", "ink"] +SNMP = "snmp" + SENSOR_TYPES = { ATTR_STATUS: { ATTR_ICON: "mdi:printer", diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 52286cd2c687af..15828e5f05a35f 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -3,7 +3,7 @@ "name": "Brother Printer", "documentation": "https://www.home-assistant.io/integrations/brother", "codeowners": ["@bieniu"], - "requirements": ["brother==0.2.0"], + "requirements": ["brother==0.2.1"], "zeroconf": [{ "type": "_printer._tcp.local.", "name": "brother*" }], "config_flow": true, "quality_scale": "platinum" diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 40e2deae67dc71..a379d9b415461b 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -24,6 +24,7 @@ ATTR_YELLOW_DRUM_COUNTER, ATTR_YELLOW_DRUM_REMAINING_LIFE, ATTR_YELLOW_DRUM_REMAINING_PAGES, + DATA_CONFIG_ENTRY, DOMAIN, SENSOR_TYPES, ) @@ -37,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Add Brother entities from a config_entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] sensors = [] diff --git a/homeassistant/components/brother/utils.py b/homeassistant/components/brother/utils.py new file mode 100644 index 00000000000000..3a53f4c04a2d60 --- /dev/null +++ b/homeassistant/components/brother/utils.py @@ -0,0 +1,30 @@ +"""Brother helpers functions.""" +import logging + +import pysnmp.hlapi.asyncio as hlapi +from pysnmp.hlapi.asyncio.cmdgen import lcd + +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import callback +from homeassistant.helpers import singleton + +from .const import DOMAIN, SNMP + +_LOGGER = logging.getLogger(__name__) + + +@singleton.singleton("snmp_engine") +def get_snmp_engine(hass): + """Get SNMP engine.""" + _LOGGER.debug("Creating SNMP engine") + snmp_engine = hlapi.SnmpEngine() + + @callback + def shutdown_listener(ev): + if hass.data.get(DOMAIN): + _LOGGER.debug("Unconfiguring SNMP engine") + lcd.unconfigure(hass.data[DOMAIN][SNMP], None) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener) + + return snmp_engine diff --git a/requirements_all.txt b/requirements_all.txt index f6716f52511e67..763d35a8cae1ac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -380,7 +380,7 @@ bravia-tv==1.0.8 broadlink==0.16.0 # homeassistant.components.brother -brother==0.2.0 +brother==0.2.1 # homeassistant.components.brottsplatskartan brottsplatskartan==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 748fd69513722d..3d57d3d23023d8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -210,7 +210,7 @@ bravia-tv==1.0.8 broadlink==0.16.0 # homeassistant.components.brother -brother==0.2.0 +brother==0.2.1 # homeassistant.components.bsblan bsblan==0.4.0 From dd8d4471ec5ca42de70f2035a2d2f23c7a757459 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 12 Feb 2021 18:54:00 +0100 Subject: [PATCH 0434/1818] Postponed evaluation of annotations for integrations (#46455) --- homeassistant/components/bmw_connected_drive/__init__.py | 4 +++- homeassistant/components/cast/helpers.py | 4 +++- homeassistant/components/counter/__init__.py | 4 +++- homeassistant/components/esphome/sensor.py | 4 ++-- homeassistant/components/huawei_lte/config_flow.py | 3 ++- homeassistant/components/huawei_lte/notify.py | 3 ++- homeassistant/components/input_boolean/__init__.py | 4 +++- homeassistant/components/input_datetime/__init__.py | 4 +++- homeassistant/components/input_number/__init__.py | 4 +++- homeassistant/components/input_select/__init__.py | 4 +++- homeassistant/components/input_text/__init__.py | 4 +++- homeassistant/components/light/__init__.py | 4 +++- homeassistant/components/media_player/__init__.py | 4 +++- homeassistant/components/media_source/models.py | 6 ++++-- homeassistant/components/timer/__init__.py | 4 +++- homeassistant/components/transmission/__init__.py | 4 +++- homeassistant/components/zha/core/channels/__init__.py | 8 +++++--- homeassistant/components/zone/__init__.py | 4 +++- 18 files changed, 54 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index ac25636a06660e..9d794ace5be144 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -1,4 +1,6 @@ """Reads vehicle status from BMW connected drive portal.""" +from __future__ import annotations + import asyncio import logging @@ -195,7 +197,7 @@ async def update_listener(hass, config_entry): await hass.config_entries.async_reload(config_entry.entry_id) -def setup_account(entry: ConfigEntry, hass, name: str) -> "BMWConnectedDriveAccount": +def setup_account(entry: ConfigEntry, hass, name: str) -> BMWConnectedDriveAccount: """Set up a new BMWConnectedDriveAccount based on the config.""" username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index e7db380406b0e4..b8742ec2b5eb3e 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -1,4 +1,6 @@ """Helpers to deal with Cast devices.""" +from __future__ import annotations + from typing import Optional import attr @@ -57,7 +59,7 @@ def manufacturer(self) -> str: return None return CAST_MANUFACTURERS.get(self.model_name.lower(), "Google Inc.") - def fill_out_missing_chromecast_info(self) -> "ChromecastInfo": + def fill_out_missing_chromecast_info(self) -> ChromecastInfo: """Return a new ChromecastInfo object with missing attributes filled in. Uses blocking HTTP / HTTPS. diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index d23c90bcb937bd..868a74cc7b76ab 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -1,4 +1,6 @@ """Component to count within automations.""" +from __future__ import annotations + import logging from typing import Dict, Optional @@ -179,7 +181,7 @@ def __init__(self, config: Dict): self.editable: bool = True @classmethod - def from_yaml(cls, config: Dict) -> "Counter": + def from_yaml(cls, config: Dict) -> Counter: """Create counter instance from yaml config.""" counter = cls(config) counter.editable = False diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index fbf3925953b01f..a5cc321cb08229 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -78,11 +78,11 @@ class EsphomeTextSensor(EsphomeEntity): """A text sensor implementation for ESPHome.""" @property - def _static_info(self) -> "TextSensorInfo": + def _static_info(self) -> TextSensorInfo: return super()._static_info @property - def _state(self) -> Optional["TextSensorState"]: + def _state(self) -> Optional[TextSensorState]: return super()._state @property diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index ba8baedcaf7a58..350ad5bca0d5ed 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Huawei LTE platform.""" +from __future__ import annotations from collections import OrderedDict import logging @@ -48,7 +49,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: config_entries.ConfigEntry, - ) -> "OptionsFlowHandler": + ) -> OptionsFlowHandler: """Get options flow.""" return OptionsFlowHandler(config_entry) diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 5659e66ea98dfb..ef354fefaf37de 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -1,4 +1,5 @@ """Support for Huawei LTE router notifications.""" +from __future__ import annotations import logging import time @@ -21,7 +22,7 @@ async def async_get_service( hass: HomeAssistantType, config: Dict[str, Any], discovery_info: Optional[Dict[str, Any]] = None, -) -> Optional["HuaweiLteSmsNotificationService"]: +) -> Optional[HuaweiLteSmsNotificationService]: """Get the notification service.""" if discovery_info is None: return None diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index 1b996722c01867..fbfe4cd0454a1b 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -1,4 +1,6 @@ """Support to keep track of user controlled booleans for within automation.""" +from __future__ import annotations + import logging import typing @@ -150,7 +152,7 @@ def __init__(self, config: typing.Optional[dict]): self._state = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> "InputBoolean": + def from_yaml(cls, config: typing.Dict) -> InputBoolean: """Return entity instance initialized from yaml storage.""" input_bool = cls(config) input_bool.entity_id = f"{DOMAIN}.{config[CONF_ID]}" diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 9589fe9a7ea08a..adefa36639a719 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -1,4 +1,6 @@ """Support to select a date and/or a time.""" +from __future__ import annotations + import datetime as py_datetime import logging import typing @@ -228,7 +230,7 @@ def __init__(self, config: typing.Dict) -> None: ) @classmethod - def from_yaml(cls, config: typing.Dict) -> "InputDatetime": + def from_yaml(cls, config: typing.Dict) -> InputDatetime: """Return entity instance initialized from yaml storage.""" input_dt = cls(config) input_dt.entity_id = f"{DOMAIN}.{config[CONF_ID]}" diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 5cad0f49c887c9..b68e6fff45da9b 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -1,4 +1,6 @@ """Support to set a numeric value from a slider or text box.""" +from __future__ import annotations + import logging import typing @@ -202,7 +204,7 @@ def __init__(self, config: typing.Dict): self._current_value = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> "InputNumber": + def from_yaml(cls, config: typing.Dict) -> InputNumber: """Return entity instance initialized from yaml storage.""" input_num = cls(config) input_num.entity_id = f"{DOMAIN}.{config[CONF_ID]}" diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index a390d8e190150f..f6831dc3e887ba 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -1,4 +1,6 @@ """Support to select an option from a list.""" +from __future__ import annotations + import logging import typing @@ -207,7 +209,7 @@ def __init__(self, config: typing.Dict): self._current_option = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> "InputSelect": + def from_yaml(cls, config: typing.Dict) -> InputSelect: """Return entity instance initialized from yaml storage.""" input_select = cls(config) input_select.entity_id = f"{DOMAIN}.{config[CONF_ID]}" diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 76eb51eedd5575..3f8c1d6a13e5a3 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -1,4 +1,6 @@ """Support to enter a value into a text box.""" +from __future__ import annotations + import logging import typing @@ -196,7 +198,7 @@ def __init__(self, config: typing.Dict): self._current_value = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> "InputText": + def from_yaml(cls, config: typing.Dict) -> InputText: """Return entity instance initialized from yaml storage.""" input_text = cls(config) input_text.entity_id = f"{DOMAIN}.{config[CONF_ID]}" diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c46b7568b59e13..55476c754f20ac 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1,4 +1,6 @@ """Provides functionality to interact with lights.""" +from __future__ import annotations + import csv import dataclasses from datetime import timedelta @@ -327,7 +329,7 @@ def __post_init__(self) -> None: ) @classmethod - def from_csv_row(cls, csv_row: List[str]) -> "Profile": + def from_csv_row(cls, csv_row: List[str]) -> Profile: """Create profile from a CSV row tuple.""" return cls(*cls.SCHEMA(csv_row)) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index d670acb7af98b5..87ecff7a54cd4b 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -1,4 +1,6 @@ """Component to interface with various media players.""" +from __future__ import annotations + import asyncio import base64 import collections @@ -851,7 +853,7 @@ async def async_browse_media( self, media_content_type: Optional[str] = None, media_content_id: Optional[str] = None, - ) -> "BrowseMedia": + ) -> BrowseMedia: """Return a BrowseMedia instance. The BrowseMedia instance will be used by the diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index e16ecbe578ecf1..98b817344d9caf 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -1,4 +1,6 @@ """Media Source models.""" +from __future__ import annotations + from abc import ABC from dataclasses import dataclass from typing import List, Optional, Tuple @@ -82,12 +84,12 @@ async def async_resolve(self) -> PlayMedia: return await self.async_media_source().async_resolve_media(self) @callback - def async_media_source(self) -> "MediaSource": + def async_media_source(self) -> MediaSource: """Return media source that owns this item.""" return self.hass.data[DOMAIN][self.domain] @classmethod - def from_uri(cls, hass: HomeAssistant, uri: str) -> "MediaSourceItem": + def from_uri(cls, hass: HomeAssistant, uri: str) -> MediaSourceItem: """Create an item from a uri.""" match = URI_SCHEME_REGEX.match(uri) diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index b123bbadf7dd6f..b0ff60bbcaef29 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -1,4 +1,6 @@ """Support for Timers.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging from typing import Dict, Optional @@ -198,7 +200,7 @@ def __init__(self, config: Dict): self._listener = None @classmethod - def from_yaml(cls, config: Dict) -> "Timer": + def from_yaml(cls, config: Dict) -> Timer: """Return entity instance initialized from yaml storage.""" timer = cls(config) timer.entity_id = ENTITY_ID_FORMAT.format(config[CONF_ID]) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 76d9aedd8d53d9..5a37cc4d771719 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -1,4 +1,6 @@ """Support for the Transmission BitTorrent client API.""" +from __future__ import annotations + from datetime import timedelta import logging from typing import List @@ -176,7 +178,7 @@ def __init__(self, hass, config_entry): self.unsub_timer = None @property - def api(self) -> "TransmissionData": + def api(self) -> TransmissionData: """Return the TransmissionData object.""" return self._tm_data diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 1bd8a52b6e6b08..852d576c03593a 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -1,4 +1,6 @@ """Channels module for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio from typing import Any, Dict, List, Optional, Tuple, Union @@ -47,7 +49,7 @@ def __init__(self, zha_device: zha_typing.ZhaDeviceType) -> None: self._zha_device = zha_device @property - def pools(self) -> List["ChannelPool"]: + def pools(self) -> List[ChannelPool]: """Return channel pools list.""" return self._pools @@ -102,7 +104,7 @@ def zigbee_signature(self) -> Dict[int, Dict[str, Any]]: } @classmethod - def new(cls, zha_device: zha_typing.ZhaDeviceType) -> "Channels": + def new(cls, zha_device: zha_typing.ZhaDeviceType) -> Channels: """Create new instance.""" channels = cls(zha_device) for ep_id in sorted(zha_device.device.endpoints): @@ -263,7 +265,7 @@ def zigbee_signature(self) -> Tuple[int, Dict[str, Any]]: ) @classmethod - def new(cls, channels: Channels, ep_id: int) -> "ChannelPool": + def new(cls, channels: Channels, ep_id: int) -> ChannelPool: """Create new channels for an endpoint.""" pool = cls(channels, ep_id) pool.add_all_channels() diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 1eef9636e36318..e1d48cbe1ff5ce 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -1,4 +1,6 @@ """Support for the definition of zones.""" +from __future__ import annotations + import logging from typing import Any, Dict, Optional, cast @@ -285,7 +287,7 @@ def __init__(self, config: Dict): self._generate_attrs() @classmethod - def from_yaml(cls, config: Dict) -> "Zone": + def from_yaml(cls, config: Dict) -> Zone: """Return entity instance initialized from yaml storage.""" zone = cls(config) zone.editable = False From bc8a52038b77fc3629a046699aac4a8bf5bf24cd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 12 Feb 2021 08:45:19 -1000 Subject: [PATCH 0435/1818] Fix homekit migration not being awaited (#46460) --- homeassistant/components/homekit/__init__.py | 2 +- tests/components/homekit/test_homekit.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 568804ef081ca5..34044742703a60 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -240,7 +240,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if CONF_ENTRY_INDEX in conf and conf[CONF_ENTRY_INDEX] == 0: _LOGGER.debug("Migrating legacy HomeKit data for %s", name) - hass.async_add_executor_job( + await hass.async_add_executor_job( migrate_filesystem_state_data_for_primary_imported_entry_id, hass, entry.entry_id, diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index f0d6a8b365fa17..1fff55db195bd1 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -51,7 +51,7 @@ CONF_PORT, DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, PERCENTAGE, SERVICE_RELOAD, @@ -118,7 +118,7 @@ async def test_setup_min(hass, mock_zeroconf): # Test auto start enabled mock_homekit.reset_mock() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() mock_homekit().async_start.assert_called() @@ -155,7 +155,7 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf): # Test auto_start disabled homekit.reset_mock() homekit.async_start.reset_mock() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert homekit.async_start.called is False @@ -951,7 +951,7 @@ async def test_setup_imported(hass, mock_zeroconf): # Test auto start enabled mock_homekit.reset_mock() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() mock_homekit().async_start.assert_called() @@ -1005,7 +1005,7 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): # Test auto start enabled mock_homekit.reset_mock() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() mock_homekit().async_start.assert_called() From da4cb6d2946f884a3899975159563fda7f85f55c Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 12 Feb 2021 17:25:15 -0500 Subject: [PATCH 0436/1818] Use core constants for somfy (#46466) --- homeassistant/components/somfy/__init__.py | 4 ++-- homeassistant/components/somfy/climate.py | 1 - homeassistant/components/somfy/const.py | 1 - homeassistant/components/somfy/cover.py | 5 ++--- homeassistant/components/somfy/sensor.py | 1 - homeassistant/components/somfy/switch.py | 1 - 6 files changed, 4 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 75475e52f0658c..ac32b9d53791d9 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -9,7 +9,7 @@ from homeassistant.components.somfy import config_flow from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_OPTIMISTIC from homeassistant.core import callback from homeassistant.helpers import ( config_entry_oauth2_flow, @@ -25,7 +25,7 @@ ) from . import api -from .const import API, CONF_OPTIMISTIC, COORDINATOR, DOMAIN +from .const import API, COORDINATOR, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/somfy/climate.py b/homeassistant/components/somfy/climate.py index 00a2738f4fefe1..99d6dca06eec74 100644 --- a/homeassistant/components/somfy/climate.py +++ b/homeassistant/components/somfy/climate.py @@ -48,7 +48,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Somfy climate platform.""" - domain_data = hass.data[DOMAIN] coordinator = domain_data[COORDINATOR] api = domain_data[API] diff --git a/homeassistant/components/somfy/const.py b/homeassistant/components/somfy/const.py index aca93be66cb1ae..128d6eb76bb9f1 100644 --- a/homeassistant/components/somfy/const.py +++ b/homeassistant/components/somfy/const.py @@ -3,4 +3,3 @@ DOMAIN = "somfy" COORDINATOR = "coordinator" API = "api" -CONF_OPTIMISTIC = "optimistic" diff --git a/homeassistant/components/somfy/cover.py b/homeassistant/components/somfy/cover.py index e7308558127032..d227bc31227e8a 100644 --- a/homeassistant/components/somfy/cover.py +++ b/homeassistant/components/somfy/cover.py @@ -18,11 +18,11 @@ SUPPORT_STOP_TILT, CoverEntity, ) -from homeassistant.const import STATE_CLOSED, STATE_OPEN +from homeassistant.const import CONF_OPTIMISTIC, STATE_CLOSED, STATE_OPEN from homeassistant.helpers.restore_state import RestoreEntity from . import SomfyEntity -from .const import API, CONF_OPTIMISTIC, COORDINATOR, DOMAIN +from .const import API, COORDINATOR, DOMAIN BLIND_DEVICE_CATEGORIES = {Category.INTERIOR_BLIND.value, Category.EXTERIOR_BLIND.value} SHUTTER_DEVICE_CATEGORIES = {Category.EXTERIOR_BLIND.value} @@ -35,7 +35,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Somfy cover platform.""" - domain_data = hass.data[DOMAIN] coordinator = domain_data[COORDINATOR] api = domain_data[API] diff --git a/homeassistant/components/somfy/sensor.py b/homeassistant/components/somfy/sensor.py index 1becc929adc533..996a95348a4fe0 100644 --- a/homeassistant/components/somfy/sensor.py +++ b/homeassistant/components/somfy/sensor.py @@ -13,7 +13,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Somfy sensor platform.""" - domain_data = hass.data[DOMAIN] coordinator = domain_data[COORDINATOR] api = domain_data[API] diff --git a/homeassistant/components/somfy/switch.py b/homeassistant/components/somfy/switch.py index 14328953367dcd..66eef99d6b5abb 100644 --- a/homeassistant/components/somfy/switch.py +++ b/homeassistant/components/somfy/switch.py @@ -10,7 +10,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Somfy switch platform.""" - domain_data = hass.data[DOMAIN] coordinator = domain_data[COORDINATOR] api = domain_data[API] From ae45d7dade7667f51873f8e09eff80896f5e6b23 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 12 Feb 2021 17:32:56 -0500 Subject: [PATCH 0437/1818] Use core constants for rflink (#46440) --- homeassistant/components/rflink/__init__.py | 5 ++--- homeassistant/components/rflink/binary_sensor.py | 9 +++++++-- homeassistant/components/rflink/cover.py | 3 +-- homeassistant/components/rflink/light.py | 3 +-- homeassistant/components/rflink/sensor.py | 5 ++--- homeassistant/components/rflink/switch.py | 3 +-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 6bedea3ecd924b..3cff3beed3ce72 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -10,7 +10,9 @@ from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_STATE, CONF_COMMAND, + CONF_DEVICE_ID, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, @@ -29,15 +31,12 @@ _LOGGER = logging.getLogger(__name__) ATTR_EVENT = "event" -ATTR_STATE = "state" CONF_ALIASES = "aliases" CONF_GROUP_ALIASES = "group_aliases" CONF_GROUP = "group" CONF_NOGROUP_ALIASES = "nogroup_aliases" CONF_DEVICE_DEFAULTS = "device_defaults" -CONF_DEVICE_ID = "device_id" -CONF_DEVICES = "devices" CONF_AUTOMATIC_ADD = "automatic_add" CONF_FIRE_EVENT = "fire_event" CONF_IGNORE_DEVICES = "ignore_devices" diff --git a/homeassistant/components/rflink/binary_sensor.py b/homeassistant/components/rflink/binary_sensor.py index dd16343898d8bb..77a8a522f6594b 100644 --- a/homeassistant/components/rflink/binary_sensor.py +++ b/homeassistant/components/rflink/binary_sensor.py @@ -6,11 +6,16 @@ PLATFORM_SCHEMA, BinarySensorEntity, ) -from homeassistant.const import CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_NAME +from homeassistant.const import ( + CONF_DEVICE_CLASS, + CONF_DEVICES, + CONF_FORCE_UPDATE, + CONF_NAME, +) import homeassistant.helpers.config_validation as cv import homeassistant.helpers.event as evt -from . import CONF_ALIASES, CONF_DEVICES, RflinkDevice +from . import CONF_ALIASES, RflinkDevice CONF_OFF_DELAY = "off_delay" DEFAULT_FORCE_UPDATE = False diff --git a/homeassistant/components/rflink/cover.py b/homeassistant/components/rflink/cover.py index 5eacce3afa83c4..2e6837d21ea6e2 100644 --- a/homeassistant/components/rflink/cover.py +++ b/homeassistant/components/rflink/cover.py @@ -4,14 +4,13 @@ import voluptuous as vol from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity -from homeassistant.const import CONF_NAME, CONF_TYPE, STATE_OPEN +from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_TYPE, STATE_OPEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity from . import ( CONF_ALIASES, CONF_DEVICE_DEFAULTS, - CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 6d63e12378d2db..fe74c979396934 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -9,14 +9,13 @@ SUPPORT_BRIGHTNESS, LightEntity, ) -from homeassistant.const import CONF_NAME, CONF_TYPE +from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_TYPE import homeassistant.helpers.config_validation as cv from . import ( CONF_ALIASES, CONF_AUTOMATIC_ADD, CONF_DEVICE_DEFAULTS, - CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, diff --git a/homeassistant/components/rflink/sensor.py b/homeassistant/components/rflink/sensor.py index 2c27477e6c6440..1a616c2ed907c2 100644 --- a/homeassistant/components/rflink/sensor.py +++ b/homeassistant/components/rflink/sensor.py @@ -5,7 +5,9 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, + CONF_DEVICES, CONF_NAME, + CONF_SENSOR_TYPE, CONF_UNIT_OF_MEASUREMENT, ) import homeassistant.helpers.config_validation as cv @@ -14,7 +16,6 @@ from . import ( CONF_ALIASES, CONF_AUTOMATIC_ADD, - CONF_DEVICES, DATA_DEVICE_REGISTER, DATA_ENTITY_LOOKUP, EVENT_KEY_ID, @@ -32,8 +33,6 @@ "temperature": "mdi:thermometer", } -CONF_SENSOR_TYPE = "sensor_type" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_AUTOMATIC_ADD, default=True): cv.boolean, diff --git a/homeassistant/components/rflink/switch.py b/homeassistant/components/rflink/switch.py index 77e1f821bad087..8f84286a616caf 100644 --- a/homeassistant/components/rflink/switch.py +++ b/homeassistant/components/rflink/switch.py @@ -2,13 +2,12 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv from . import ( CONF_ALIASES, CONF_DEVICE_DEFAULTS, - CONF_DEVICES, CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASES, From 8bacfcec50cf523a788010e519c6f28d7c770e3e Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 13 Feb 2021 00:03:13 +0000 Subject: [PATCH 0438/1818] [ci skip] Translation update --- .../components/foscam/translations/es.json | 2 + .../components/foscam/translations/nl.json | 26 ++++++++ .../huisbaasje/translations/nl.json | 21 ++++++ .../components/mazda/translations/nl.json | 14 ++++ .../media_player/translations/nl.json | 7 ++ .../components/mysensors/translations/nl.json | 66 +++++++++++++++++++ .../components/number/translations/nl.json | 8 +++ .../philips_js/translations/es.json | 16 +++++ .../philips_js/translations/et.json | 16 +++++ .../philips_js/translations/no.json | 24 +++++++ .../philips_js/translations/ru.json | 24 +++++++ .../philips_js/translations/zh-Hant.json | 24 +++++++ .../components/pi_hole/translations/nl.json | 6 ++ .../components/powerwall/translations/es.json | 1 + .../components/powerwall/translations/nl.json | 1 + .../components/roku/translations/nl.json | 8 +++ .../components/roomba/translations/nl.json | 24 +++++++ .../somfy_mylink/translations/nl.json | 5 ++ .../components/unifi/translations/nl.json | 4 +- .../components/zwave_js/translations/nl.json | 56 ++++++++++++++++ 20 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/foscam/translations/nl.json create mode 100644 homeassistant/components/huisbaasje/translations/nl.json create mode 100644 homeassistant/components/mazda/translations/nl.json create mode 100644 homeassistant/components/mysensors/translations/nl.json create mode 100644 homeassistant/components/number/translations/nl.json create mode 100644 homeassistant/components/philips_js/translations/es.json create mode 100644 homeassistant/components/philips_js/translations/et.json create mode 100644 homeassistant/components/philips_js/translations/no.json create mode 100644 homeassistant/components/philips_js/translations/ru.json create mode 100644 homeassistant/components/philips_js/translations/zh-Hant.json create mode 100644 homeassistant/components/somfy_mylink/translations/nl.json create mode 100644 homeassistant/components/zwave_js/translations/nl.json diff --git a/homeassistant/components/foscam/translations/es.json b/homeassistant/components/foscam/translations/es.json index 27f7ac36489b36..7e8b7c1427dc71 100644 --- a/homeassistant/components/foscam/translations/es.json +++ b/homeassistant/components/foscam/translations/es.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "No se pudo conectar", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "invalid_response": "Respuesta no v\u00e1lida del dispositivo", "unknown": "Error inesperado" }, "step": { @@ -14,6 +15,7 @@ "host": "Host", "password": "Contrase\u00f1a", "port": "Puerto", + "rtsp_port": "Puerto RTSP", "stream": "Stream", "username": "Usuario" } diff --git a/homeassistant/components/foscam/translations/nl.json b/homeassistant/components/foscam/translations/nl.json new file mode 100644 index 00000000000000..9bea23ad702c60 --- /dev/null +++ b/homeassistant/components/foscam/translations/nl.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "invalid_response": "Ongeldig antwoord van het apparaat", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "port": "Poort", + "rtsp_port": "RTSP-poort", + "stream": "Stream", + "username": "Gebruikersnaam" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/nl.json b/homeassistant/components/huisbaasje/translations/nl.json new file mode 100644 index 00000000000000..8cb09793af81be --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "connection_exception": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unauthenticated_exception": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json new file mode 100644 index 00000000000000..c820f481b9d189 --- /dev/null +++ b/homeassistant/components/mazda/translations/nl.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "account_locked": "Account vergrendeld. Probeer het later nog eens.", + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + } + } +} \ 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 5e690f35f8a119..37c1d6b4d9ef9b 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} is ingeschakeld", "is_paused": "{entity_name} is gepauzeerd", "is_playing": "{entity_name} wordt afgespeeld" + }, + "trigger_type": { + "idle": "{entity_name} wordt inactief", + "paused": "{entity_name} is gepauzeerd", + "playing": "{entity_name} begint te spelen", + "turned_off": "{entity_name} uitgeschakeld", + "turned_on": "{entity_name} ingeschakeld" } }, "state": { diff --git a/homeassistant/components/mysensors/translations/nl.json b/homeassistant/components/mysensors/translations/nl.json new file mode 100644 index 00000000000000..ebbcbf9a36e404 --- /dev/null +++ b/homeassistant/components/mysensors/translations/nl.json @@ -0,0 +1,66 @@ +{ + "config": { + "abort": { + "not_a_number": "Voer een nummer in", + "port_out_of_range": "Poortnummer moet minimaal 1 en maximaal 65535 zijn", + "same_topic": "De topics abonneren en publiceren zijn hetzelfde", + "unknown": "Onverwachte fout" + }, + "error": { + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", + "duplicate_persistence_file": "Persistentiebestand al in gebruik", + "duplicate_topic": "Topic is al in gebruik", + "invalid_auth": "Ongeldige authenticatie", + "invalid_device": "Ongeldig apparaat", + "invalid_ip": "Ongeldig IP-adres", + "invalid_persistence_file": "Ongeldig persistentiebestand", + "invalid_port": "Ongeldig poortnummer", + "invalid_publish_topic": "ongeldig publiceer topic", + "invalid_serial": "Ongeldige seri\u00eble poort", + "invalid_subscribe_topic": "Ongeldig abonneer topic", + "invalid_version": "Ongeldige MySensors-versie", + "not_a_number": "Voer een nummer in", + "port_out_of_range": "Poortnummer moet minimaal 1 en maximaal 65535 zijn", + "same_topic": "De topics abonneren en publiceren zijn hetzelfde", + "unknown": "Onverwachte fout" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "persistentiebestand (leeg laten om automatisch te genereren)", + "retain": "mqtt behouden", + "topic_in_prefix": "prefix voor inkomende topics (topic_in_prefix)", + "topic_out_prefix": "prefix voor uitgaande topics (topic_out_prefix)", + "version": "MySensors-versie" + }, + "description": "MQTT-gateway instellen" + }, + "gw_serial": { + "data": { + "baud_rate": "baudrate", + "device": "Seri\u00eble poort", + "persistence_file": "persistentiebestand (leeg laten om automatisch te genereren)", + "version": "MySensors-versie" + }, + "description": "Seri\u00eble gateway setup" + }, + "gw_tcp": { + "data": { + "device": "IP-adres van de gateway", + "persistence_file": "persistentiebestand (leeg laten om automatisch te genereren)", + "tcp_port": "Poort", + "version": "MySensors-versie" + }, + "description": "Ethernet gateway instellen" + }, + "user": { + "data": { + "gateway_type": "Gateway type" + }, + "description": "Kies de verbindingsmethode met de gateway" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/nl.json b/homeassistant/components/number/translations/nl.json new file mode 100644 index 00000000000000..f9a1c6b60a9374 --- /dev/null +++ b/homeassistant/components/number/translations/nl.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Stel waarde in voor {entity_name}" + } + }, + "title": "Nummer" +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/es.json b/homeassistant/components/philips_js/translations/es.json new file mode 100644 index 00000000000000..3d4beaa875214d --- /dev/null +++ b/homeassistant/components/philips_js/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_version": "Versi\u00f3n del API" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Se solicita al dispositivo que se encienda" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json new file mode 100644 index 00000000000000..ef5e3e0ffce249 --- /dev/null +++ b/homeassistant/components/philips_js/translations/et.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_version": "API versioon" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Seadmel palutakse sisse l\u00fclituda" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/no.json b/homeassistant/components/philips_js/translations/no.json new file mode 100644 index 00000000000000..dadf15fb67adfb --- /dev/null +++ b/homeassistant/components/philips_js/translations/no.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_version": "API-versjon", + "host": "Vert" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Enheten blir bedt om \u00e5 sl\u00e5 p\u00e5" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/ru.json b/homeassistant/components/philips_js/translations/ru.json new file mode 100644 index 00000000000000..9306ecf7a2958c --- /dev/null +++ b/homeassistant/components/philips_js/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." + }, + "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": { + "api_version": "\u0412\u0435\u0440\u0441\u0438\u044f API", + "host": "\u0425\u043e\u0441\u0442" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/zh-Hant.json b/homeassistant/components/philips_js/translations/zh-Hant.json new file mode 100644 index 00000000000000..af161b6b16b5f5 --- /dev/null +++ b/homeassistant/components/philips_js/translations/zh-Hant.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_version": "API \u7248\u672c", + "host": "\u4e3b\u6a5f\u7aef" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\u88dd\u7f6e\u5fc5\u9808\u70ba\u958b\u555f\u72c0\u614b" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/nl.json b/homeassistant/components/pi_hole/translations/nl.json index 24da024acaefa3..156a248a80bd53 100644 --- a/homeassistant/components/pi_hole/translations/nl.json +++ b/homeassistant/components/pi_hole/translations/nl.json @@ -7,6 +7,11 @@ "cannot_connect": "Kon niet verbinden" }, "step": { + "api_key": { + "data": { + "api_key": "API-sleutel" + } + }, "user": { "data": { "api_key": "API-sleutel", @@ -15,6 +20,7 @@ "name": "Naam", "port": "Poort", "ssl": "Maakt gebruik van een SSL-certificaat", + "statistics_only": "Alleen statistieken", "verify_ssl": "SSL-certificaat verifi\u00ebren" } } diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 373bf29f8bace7..81e3edab38772d 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -14,6 +14,7 @@ "data": { "ip_address": "Direcci\u00f3n IP" }, + "description": "La contrase\u00f1a suele ser los \u00faltimos 5 caracteres del n\u00famero de serie del Backup Gateway y se puede encontrar en la aplicaci\u00f3n Telsa; o los \u00faltimos 5 caracteres de la contrase\u00f1a que se encuentran dentro de la puerta del Backup Gateway 2.", "title": "Conectarse al powerwall" } } diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index f77cc864813d07..779da2086ebccc 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -8,6 +8,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": "Tesla Powerwall ({ip_adres})", "step": { "user": { "data": { diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json index 6b0927178fe5d5..529b01b64c2d94 100644 --- a/homeassistant/components/roku/translations/nl.json +++ b/homeassistant/components/roku/translations/nl.json @@ -9,6 +9,14 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "data": { + "one": "Een", + "other": "Ander" + }, + "description": "Wilt u {naam} instellen?", + "title": "Roku" + }, "ssdp_confirm": { "data": { "one": "Leeg", diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json index f5268bdf799471..754ff2e51a8753 100644 --- a/homeassistant/components/roomba/translations/nl.json +++ b/homeassistant/components/roomba/translations/nl.json @@ -1,9 +1,33 @@ { "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", + "not_irobot_device": "Het gevonden apparaat is geen iRobot-apparaat" + }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw" }, + "flow_title": "iRobot {naam} ({host})", "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Kies een Roomba of Braava.", + "title": "Automatisch verbinding maken met het apparaat" + }, + "link": { + "description": "Houd de Home-knop op {naam} ingedrukt totdat het apparaat een geluid genereert (ongeveer twee seconden).", + "title": "Wachtwoord opvragen" + }, + "link_manual": { + "data": { + "password": "Wachtwoord" + }, + "description": "Het wachtwoord kon niet automatisch van het apparaat worden opgehaald. Volg de stappen zoals beschreven in de documentatie op: {auth_help_url}", + "title": "Voer wachtwoord in" + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json new file mode 100644 index 00000000000000..208c032227aa17 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -0,0 +1,5 @@ +{ + "config": { + "flow_title": "Somfy MyLink {mac} ( {ip} )" + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 4e9aa16a245259..96ffc0c0acede7 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -1,13 +1,15 @@ { "config": { "abort": { - "already_configured": "Controller site is al geconfigureerd" + "already_configured": "Controller site is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "faulty_credentials": "Foutieve gebruikersgegevens", "service_unavailable": "Geen service beschikbaar", "unknown_client_mac": "Geen client beschikbaar op dat MAC-adres" }, + "flow_title": "UniFi Netwerk {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json new file mode 100644 index 00000000000000..74b4db46de1ab3 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Ophalen van ontdekkingsinformatie voor Z-Wave JS-add-on is mislukt.", + "addon_info_failed": "Ophalen van Z-Wave JS add-on-info is mislukt.", + "addon_install_failed": "Kan de Z-Wave JS add-on niet installeren.", + "addon_missing_discovery_info": "De Z-Wave JS addon mist ontdekkings informatie", + "addon_set_config_failed": "Instellen van de Z-Wave JS-configuratie is mislukt.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "cannot_connect": "Kan geen verbinding maken" + }, + "error": { + "addon_start_failed": "Het is niet gelukt om de Z-Wave JS add-on te starten. Controleer de configuratie.", + "cannot_connect": "Kan geen verbinding maken", + "invalid_ws_url": "Ongeldige websocket URL", + "unknown": "Onverwachte fout" + }, + "progress": { + "install_addon": "Een ogenblik geduld terwijl de installatie van de Z-Wave JS add-on is voltooid. Dit kan enkele minuten duren." + }, + "step": { + "hassio_confirm": { + "title": "Z-Wave JS integratie instellen met de Z-Wave JS add-on" + }, + "install_addon": { + "title": "De Z-Wave JS add-on installatie is gestart" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Gebruik de Z-Wave JS Supervisor add-on" + }, + "description": "Wilt u de Z-Wave JS Supervisor add-on gebruiken?", + "title": "Selecteer verbindingsmethode" + }, + "start_addon": { + "data": { + "network_key": "Netwerksleutel", + "usb_path": "USB-apparaatpad" + }, + "title": "Voer de Z-Wave JS add-on configuratie in" + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file From 1a8cdba9af1680c0456735142479c3c7fef43bff Mon Sep 17 00:00:00 2001 From: On Freund Date: Sat, 13 Feb 2021 13:03:49 +0200 Subject: [PATCH 0439/1818] Gracefully handle missing A/V info in Onkyo integration (#46228) * Gracefully handle missing A/V info * Do not attempt to query A/V info if unsupported * Rename _parse_onkyo_tuple --- .../components/onkyo/media_player.py | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index ac8cfa5e4b6956..7ac9b5fdfc6f25 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -118,15 +118,20 @@ SERVICE_SELECT_HDMI_OUTPUT = "onkyo_select_hdmi_output" -def _parse_onkyo_tuple(tup): - """Parse a tuple returned from the eiscp library.""" - if len(tup) < 2: +def _parse_onkyo_payload(payload): + """Parse a payload returned from the eiscp library.""" + if isinstance(payload, bool): + # command not supported by the device + return False + + if len(payload) < 2: + # no value return None - if isinstance(tup[1], str): - return tup[1].split(",") + if isinstance(payload[1], str): + return payload[1].split(",") - return tup[1] + return payload[1] def _tuple_get(tup, index, default=None): @@ -267,6 +272,8 @@ def __init__( self._reverse_mapping = {value: key for key, value in sources.items()} self._attributes = {} self._hdmi_out_supported = True + self._audio_info_supported = True + self._video_info_supported = True def command(self, command): """Run an eiscp command and catch connection errors.""" @@ -309,12 +316,14 @@ def update(self): else: hdmi_out_raw = [] preset_raw = self.command("preset query") - audio_information_raw = self.command("audio-information query") - video_information_raw = self.command("video-information query") + if self._audio_info_supported: + audio_information_raw = self.command("audio-information query") + if self._video_info_supported: + video_information_raw = self.command("video-information query") if not (volume_raw and mute_raw and current_source_raw): return - sources = _parse_onkyo_tuple(current_source_raw) + sources = _parse_onkyo_payload(current_source_raw) for source in sources: if source in self._source_mapping: @@ -441,7 +450,11 @@ def select_output(self, output): self.command(f"hdmi-output-selector={output}") def _parse_audio_information(self, audio_information_raw): - values = _parse_onkyo_tuple(audio_information_raw) + values = _parse_onkyo_payload(audio_information_raw) + if values is False: + self._audio_info_supported = False + return + if values: info = { "format": _tuple_get(values, 1), @@ -456,7 +469,11 @@ def _parse_audio_information(self, audio_information_raw): self._attributes.pop(ATTR_AUDIO_INFORMATION, None) def _parse_video_information(self, video_information_raw): - values = _parse_onkyo_tuple(video_information_raw) + values = _parse_onkyo_payload(video_information_raw) + if values is False: + self._video_info_supported = False + return + if values: info = { "input_resolution": _tuple_get(values, 1), From 2ecac6550f6ea7c6769a021501190fc15e74c638 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 06:06:20 -0500 Subject: [PATCH 0440/1818] Use core constants for dynalite (#46044) --- homeassistant/components/dynalite/__init__.py | 4 +--- homeassistant/components/dynalite/const.py | 1 - homeassistant/components/dynalite/convert_config.py | 10 ++++++++-- homeassistant/components/dynalite/light.py | 1 - homeassistant/components/dynalite/switch.py | 1 - tests/components/dynalite/test_init.py | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index c131ebec3dadd4..e52ec5946b6cd7 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -8,7 +8,7 @@ from homeassistant import config_entries from homeassistant.components.cover import DEVICE_CLASSES_SCHEMA from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE +from homeassistant.const import CONF_DEFAULT, CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv @@ -29,7 +29,6 @@ CONF_CHANNEL, CONF_CHANNEL_COVER, CONF_CLOSE_PRESET, - CONF_DEFAULT, CONF_DEVICE_CLASS, CONF_DURATION, CONF_FADE, @@ -181,7 +180,6 @@ def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool: """Set up the Dynalite platform.""" - conf = config.get(DOMAIN) LOGGER.debug("Setting up dynalite component config = %s", conf) diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 4159c98f073283..3f1e201f3fd5ce 100644 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -19,7 +19,6 @@ CONF_CHANNEL = "channel" CONF_CHANNEL_COVER = "channel_cover" CONF_CLOSE_PRESET = "close" -CONF_DEFAULT = "default" CONF_DEVICE_CLASS = "class" CONF_DURATION = "duration" CONF_FADE = "fade" diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index b84450c807dd9c..6a85147a2e0ad2 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -4,7 +4,14 @@ from dynalite_devices_lib import const as dyn_const -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_ROOM, CONF_TYPE +from homeassistant.const import ( + CONF_DEFAULT, + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_ROOM, + CONF_TYPE, +) from .const import ( ACTIVE_INIT, @@ -16,7 +23,6 @@ CONF_CHANNEL, CONF_CHANNEL_COVER, CONF_CLOSE_PRESET, - CONF_DEFAULT, CONF_DEVICE_CLASS, CONF_DURATION, CONF_FADE, diff --git a/homeassistant/components/dynalite/light.py b/homeassistant/components/dynalite/light.py index 5e7069ab50b7e8..bb9569358be42e 100644 --- a/homeassistant/components/dynalite/light.py +++ b/homeassistant/components/dynalite/light.py @@ -12,7 +12,6 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable ) -> None: """Record the async_add_entities function to add them later when received from Dynalite.""" - async_setup_entry_base( hass, config_entry, async_add_entities, "light", DynaliteLight ) diff --git a/homeassistant/components/dynalite/switch.py b/homeassistant/components/dynalite/switch.py index d106d976d689d9..a482228183ccb8 100644 --- a/homeassistant/components/dynalite/switch.py +++ b/homeassistant/components/dynalite/switch.py @@ -12,7 +12,6 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable ) -> None: """Record the async_add_entities function to add them later when received from Dynalite.""" - async_setup_entry_base( hass, config_entry, async_add_entities, "switch", DynaliteSwitch ) diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index d231f82d2f851a..eab88fb18ca4d6 100644 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -7,7 +7,7 @@ from voluptuous import MultipleInvalid import homeassistant.components.dynalite.const as dynalite -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_ROOM +from homeassistant.const import CONF_DEFAULT, CONF_HOST, CONF_NAME, CONF_PORT, CONF_ROOM from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -54,7 +54,7 @@ async def test_async_setup(hass): dynalite.CONF_TEMPLATE: dynalite.CONF_TIME_COVER, }, }, - dynalite.CONF_DEFAULT: {dynalite.CONF_FADE: 2.3}, + CONF_DEFAULT: {dynalite.CONF_FADE: 2.3}, dynalite.CONF_ACTIVE: dynalite.ACTIVE_INIT, dynalite.CONF_PRESET: { "5": {CONF_NAME: "pres5", dynalite.CONF_FADE: 4.5} From 820a260252bba4fd8df80d0ea688d510700079f7 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 06:07:42 -0500 Subject: [PATCH 0441/1818] Use core constants for homeassistant triggers (#46472) --- homeassistant/components/homeassistant/triggers/event.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 7665ee1b4d7151..2bc42c3d06354d 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -1,14 +1,13 @@ """Offer event listening automation rules.""" import voluptuous as vol -from homeassistant.const import CONF_PLATFORM +from homeassistant.const import CONF_EVENT_DATA, CONF_PLATFORM from homeassistant.core import HassJob, callback from homeassistant.helpers import config_validation as cv, template # mypy: allow-untyped-defs CONF_EVENT_TYPE = "event_type" -CONF_EVENT_DATA = "event_data" CONF_EVENT_CONTEXT = "context" TRIGGER_SCHEMA = vol.Schema( From b8584cab5d65042a357e9dcc478e183545112a85 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sat, 13 Feb 2021 12:27:54 +0100 Subject: [PATCH 0442/1818] Remove unnecessary gethostbyname() from Shelly integration (#46483) --- homeassistant/components/shelly/__init__.py | 5 +---- homeassistant/components/shelly/config_flow.py | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 537caf9707f478..d4423dc3a88968 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -2,7 +2,6 @@ import asyncio from datetime import timedelta import logging -from socket import gethostbyname import aioshelly import async_timeout @@ -57,10 +56,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): temperature_unit = "C" if hass.config.units.is_metric else "F" - ip_address = await hass.async_add_executor_job(gethostbyname, entry.data[CONF_HOST]) - options = aioshelly.ConnectionOptions( - ip_address, + entry.data[CONF_HOST], entry.data.get(CONF_USERNAME), entry.data.get(CONF_PASSWORD), temperature_unit, diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 026021a992ffa4..b3a9b068ac4e00 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -1,7 +1,6 @@ """Config flow for Shelly integration.""" import asyncio import logging -from socket import gethostbyname import aiohttp import aioshelly @@ -33,10 +32,9 @@ async def validate_input(hass: core.HomeAssistant, host, data): Data has the keys from DATA_SCHEMA with values provided by the user. """ - ip_address = await hass.async_add_executor_job(gethostbyname, host) options = aioshelly.ConnectionOptions( - ip_address, data.get(CONF_USERNAME), data.get(CONF_PASSWORD) + host, data.get(CONF_USERNAME), data.get(CONF_PASSWORD) ) coap_context = await get_coap_context(hass) From 621c8e700bcdbe1cd831e7959fc51334de57cf10 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 06:33:13 -0500 Subject: [PATCH 0443/1818] Use core constants for starline (#46471) --- homeassistant/components/starline/__init__.py | 2 +- homeassistant/components/starline/const.py | 1 - homeassistant/components/starline/lock.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/starline/__init__.py b/homeassistant/components/starline/__init__.py index 392dbff9e03db6..3025a7b4c119c0 100644 --- a/homeassistant/components/starline/__init__.py +++ b/homeassistant/components/starline/__init__.py @@ -2,12 +2,12 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.core import Config, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from .account import StarlineAccount from .const import ( - CONF_SCAN_INTERVAL, CONF_SCAN_OBD_INTERVAL, DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_OBD_INTERVAL, diff --git a/homeassistant/components/starline/const.py b/homeassistant/components/starline/const.py index 89ea0873aa1a6d..488a5cb9e0f447 100644 --- a/homeassistant/components/starline/const.py +++ b/homeassistant/components/starline/const.py @@ -11,7 +11,6 @@ CONF_MFA_CODE = "mfa_code" CONF_CAPTCHA_CODE = "captcha_code" -CONF_SCAN_INTERVAL = "scan_interval" DEFAULT_SCAN_INTERVAL = 180 # in seconds CONF_SCAN_OBD_INTERVAL = "scan_obd_interval" DEFAULT_SCAN_OBD_INTERVAL = 10800 # 3 hours in seconds diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index 56cd8686186a59..0b158451fb3dc0 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -8,7 +8,6 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up the StarLine lock.""" - account: StarlineAccount = hass.data[DOMAIN][entry.entry_id] entities = [] for device in account.api.devices.values(): From 13b881acfca9725f76a9be81827c5b0d61576c80 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 07:07:11 -0500 Subject: [PATCH 0444/1818] Use core constants for simplepush (#46465) --- homeassistant/components/simplepush/notify.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/simplepush/notify.py b/homeassistant/components/simplepush/notify.py index 1d10153415741d..5a83dec69f0673 100644 --- a/homeassistant/components/simplepush/notify.py +++ b/homeassistant/components/simplepush/notify.py @@ -8,13 +8,12 @@ PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import CONF_PASSWORD +from homeassistant.const import CONF_EVENT, CONF_PASSWORD import homeassistant.helpers.config_validation as cv ATTR_ENCRYPTED = "encrypted" CONF_DEVICE_KEY = "device_key" -CONF_EVENT = "event" CONF_SALT = "salt" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -44,7 +43,6 @@ def __init__(self, config): def send_message(self, message="", **kwargs): """Send a message to a Simplepush user.""" - title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) if self._password: From 1244fb4152680560b38f565b548c40e3db6b9f91 Mon Sep 17 00:00:00 2001 From: Rob Bierbooms Date: Sat, 13 Feb 2021 13:19:38 +0100 Subject: [PATCH 0445/1818] Bump dsmr_parser to 0.28, configure keep_alive_interval (#46464) --- homeassistant/components/dsmr/manifest.json | 2 +- homeassistant/components/dsmr/sensor.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dsmr/manifest.json b/homeassistant/components/dsmr/manifest.json index c3f6aa4dea33ad..c442130bb9fa81 100644 --- a/homeassistant/components/dsmr/manifest.json +++ b/homeassistant/components/dsmr/manifest.json @@ -2,7 +2,7 @@ "domain": "dsmr", "name": "DSMR Slimme Meter", "documentation": "https://www.home-assistant.io/integrations/dsmr", - "requirements": ["dsmr_parser==0.25"], + "requirements": ["dsmr_parser==0.28"], "codeowners": ["@Robbie1221"], "config_flow": false } diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 897fcd4e77b175..aea12a863f068a 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -215,6 +215,7 @@ def update_entities_telegram(telegram): config[CONF_DSMR_VERSION], update_entities_telegram, loop=hass.loop, + keep_alive_interval=60, ) else: reader_factory = partial( diff --git a/requirements_all.txt b/requirements_all.txt index 763d35a8cae1ac..401009e9fef40a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -502,7 +502,7 @@ doorbirdpy==2.1.0 dovado==0.4.1 # homeassistant.components.dsmr -dsmr_parser==0.25 +dsmr_parser==0.28 # homeassistant.components.dwd_weather_warnings dwdwfsapi==1.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3d57d3d23023d8..3fa94bd3117acb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -272,7 +272,7 @@ distro==1.5.0 doorbirdpy==2.1.0 # homeassistant.components.dsmr -dsmr_parser==0.25 +dsmr_parser==0.28 # homeassistant.components.dynalite dynalite_devices==0.1.46 From bc1daf1802d2ef3ec6afe64882eb21ecf28baa49 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 13 Feb 2021 13:21:37 +0100 Subject: [PATCH 0446/1818] None optional hass typing in FlowHandler (#46462) Co-authored-by: Martin Hjelmare --- homeassistant/auth/mfa_modules/totp.py | 2 +- homeassistant/components/apple_tv/config_flow.py | 4 ---- .../components/arcam_fmj/config_flow.py | 4 ++-- homeassistant/components/axis/config_flow.py | 2 -- .../components/azure_devops/config_flow.py | 1 - homeassistant/components/bond/config_flow.py | 1 - .../components/broadlink/config_flow.py | 1 - homeassistant/components/brother/config_flow.py | 2 -- .../components/cert_expiry/config_flow.py | 4 +--- .../components/cloudflare/config_flow.py | 1 - homeassistant/components/deconz/config_flow.py | 2 -- homeassistant/components/denonavr/config_flow.py | 1 - homeassistant/components/directv/config_flow.py | 1 - homeassistant/components/doorbird/config_flow.py | 1 - homeassistant/components/elgato/config_flow.py | 3 --- homeassistant/components/esphome/config_flow.py | 2 -- .../components/forked_daapd/config_flow.py | 1 - homeassistant/components/fritzbox/config_flow.py | 2 -- .../fritzbox_callmonitor/config_flow.py | 4 +--- homeassistant/components/guardian/config_flow.py | 1 - homeassistant/components/harmony/config_flow.py | 1 - .../components/homekit_controller/config_flow.py | 2 -- .../components/huawei_lte/config_flow.py | 11 ++--------- homeassistant/components/hyperion/config_flow.py | 5 ----- homeassistant/components/ipp/config_flow.py | 1 - homeassistant/components/isy994/config_flow.py | 1 - homeassistant/components/kodi/config_flow.py | 1 - .../components/konnected/config_flow.py | 2 -- .../components/lutron_caseta/config_flow.py | 3 --- homeassistant/components/nut/config_flow.py | 1 - .../components/ovo_energy/config_flow.py | 1 - homeassistant/components/plex/config_flow.py | 7 ++----- homeassistant/components/plugwise/config_flow.py | 1 - .../components/powerwall/config_flow.py | 1 - homeassistant/components/roku/config_flow.py | 3 --- homeassistant/components/roomba/config_flow.py | 2 -- .../components/samsungtv/config_flow.py | 2 -- homeassistant/components/shelly/config_flow.py | 1 - homeassistant/components/smappee/config_flow.py | 3 --- homeassistant/components/sms/config_flow.py | 2 +- .../components/somfy_mylink/config_flow.py | 1 - homeassistant/components/songpal/config_flow.py | 1 - homeassistant/components/spotify/config_flow.py | 2 -- .../components/squeezebox/config_flow.py | 1 - homeassistant/components/syncthru/config_flow.py | 4 +--- .../components/synology_dsm/config_flow.py | 1 - homeassistant/components/toon/config_flow.py | 6 +----- homeassistant/components/unifi/config_flow.py | 2 -- homeassistant/components/upnp/config_flow.py | 1 - homeassistant/components/vizio/config_flow.py | 7 ------- homeassistant/components/wilight/config_flow.py | 1 - homeassistant/components/withings/config_flow.py | 3 --- homeassistant/components/wled/config_flow.py | 5 ----- .../components/xiaomi_aqara/config_flow.py | 1 - .../components/xiaomi_miio/config_flow.py | 1 - homeassistant/components/zwave_js/config_flow.py | 11 ----------- homeassistant/config_entries.py | 8 -------- homeassistant/data_entry_flow.py | 16 ++++++++++------ 58 files changed, 22 insertions(+), 141 deletions(-) diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 359f79ce6ce1b3..4a6faef96c009f 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -198,7 +198,7 @@ async def async_step_init( errors: Dict[str, str] = {} if user_input: - verified = await self.hass.async_add_executor_job( # type: ignore + verified = await self.hass.async_add_executor_job( pyotp.TOTP(self._ota_secret).verify, user_input["code"] ) if verified: diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index 9c2f25b6d5343f..ef0a0cfe59e2d5 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -101,10 +101,7 @@ async def async_step_reauth(self, info): await self.async_set_unique_id(info[CONF_IDENTIFIER]) self.target_device = info[CONF_IDENTIFIER] - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {"name": info[CONF_NAME]} - - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["identifier"] = self.unique_id return await self.async_step_reconfigure() @@ -170,7 +167,6 @@ async def async_step_zeroconf(self, discovery_info): await self.async_set_unique_id(identifier) self._abort_if_unique_id_configured() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["identifier"] = self.unique_id self.context["title_placeholders"] = {"name": name} self.target_device = identifier diff --git a/homeassistant/components/arcam_fmj/config_flow.py b/homeassistant/components/arcam_fmj/config_flow.py index d270af9295b43c..31735a0a037a8e 100644 --- a/homeassistant/components/arcam_fmj/config_flow.py +++ b/homeassistant/components/arcam_fmj/config_flow.py @@ -71,7 +71,7 @@ async def async_step_user(self, user_input=None): async def async_step_confirm(self, user_input=None): """Handle user-confirmation of discovered node.""" - context = self.context # pylint: disable=no-member + context = self.context placeholders = { "host": context[CONF_HOST], } @@ -94,7 +94,7 @@ async def async_step_ssdp(self, discovery_info): await self._async_set_unique_id_and_update(host, port, uuid) - context = self.context # pylint: disable=no-member + context = self.context context[CONF_HOST] = host context[CONF_PORT] = DEFAULT_PORT return await self.async_step_confirm() diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index d99c5329e3230b..c65f663f2b9915 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -138,7 +138,6 @@ async def _create_entry(self): async def async_step_reauth(self, device_config: dict): """Trigger a reauthentication flow.""" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_NAME: device_config[CONF_NAME], CONF_HOST: device_config[CONF_HOST], @@ -204,7 +203,6 @@ async def _process_discovered_device(self, device: dict): } ) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_NAME: device[CONF_NAME], CONF_HOST: device[CONF_HOST], diff --git a/homeassistant/components/azure_devops/config_flow.py b/homeassistant/components/azure_devops/config_flow.py index e1e7d8339264df..d7d6f2868c3183 100644 --- a/homeassistant/components/azure_devops/config_flow.py +++ b/homeassistant/components/azure_devops/config_flow.py @@ -95,7 +95,6 @@ async def async_step_reauth(self, user_input): self._project = user_input[CONF_PROJECT] self._pat = user_input[CONF_PAT] - # pylint: disable=no-member self.context["title_placeholders"] = { "project_url": f"{self._organization}/{self._project}", } diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 2004da0c81e8cd..9298961269ee8d 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -73,7 +73,6 @@ async def async_step_zeroconf( CONF_HOST: host, CONF_BOND_ID: bond_id, } - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": self._discovered}) return await self.async_step_confirm() diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index a2e770d6c4fed3..a309e4eb6034e3 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -57,7 +57,6 @@ async def async_set_device(self, device, raise_on_progress=True): ) self.device = device - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { "name": device.name, "model": device.model, diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 6a9d2ca6746440..49f1c0ed1a353a 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -97,7 +97,6 @@ async def async_step_zeroconf(self, discovery_info): await self.async_set_unique_id(self.brother.serial.lower()) self._abort_if_unique_id_configured() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update( { "title_placeholders": { @@ -112,7 +111,6 @@ async def async_step_zeroconf_confirm(self, user_input=None): """Handle a flow initiated by zeroconf.""" if user_input is not None: title = f"{self.brother.model} {self.brother.serial}" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 return self.async_create_entry( title=title, data={CONF_HOST: self.host, CONF_TYPE: user_input[CONF_TYPE]}, diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 7953a7bb8cfc7e..282c87b25c51fa 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -63,9 +63,7 @@ async def async_step_user(self, user_input=None): title=title, data={CONF_HOST: host, CONF_PORT: port}, ) - if ( # pylint: disable=no-member - self.context["source"] == config_entries.SOURCE_IMPORT - ): + if self.context["source"] == config_entries.SOURCE_IMPORT: _LOGGER.error("Config import failed for %s", user_input[CONF_HOST]) return self.async_abort(reason="import_failed") else: diff --git a/homeassistant/components/cloudflare/config_flow.py b/homeassistant/components/cloudflare/config_flow.py index c96e6455ce9da8..066fff9f704848 100644 --- a/homeassistant/components/cloudflare/config_flow.py +++ b/homeassistant/components/cloudflare/config_flow.py @@ -97,7 +97,6 @@ async def async_step_user(self, user_input: Optional[Dict] = None): if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") - assert self.hass persistent_notification.async_dismiss(self.hass, "cloudflare_setup") errors = {} diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index bc14af9ff11015..d1ea3826e2fa6f 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -176,7 +176,6 @@ async def _create_entry(self): async def async_step_reauth(self, config: dict): """Trigger a reauthentication flow.""" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {CONF_HOST: config[CONF_HOST]} self.deconz_config = { @@ -207,7 +206,6 @@ async def async_step_ssdp(self, discovery_info): updates={CONF_HOST: parsed_url.hostname, CONF_PORT: parsed_url.port} ) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {"host": parsed_url.hostname} self.deconz_config = { diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index 21c8da01783588..ec0ab4fb1772f8 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -225,7 +225,6 @@ async def async_step_ssdp(self, discovery_info): await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update( { "title_placeholders": { diff --git a/homeassistant/components/directv/config_flow.py b/homeassistant/components/directv/config_flow.py index cae0e62b1bed88..fed13c63dc8f43 100644 --- a/homeassistant/components/directv/config_flow.py +++ b/homeassistant/components/directv/config_flow.py @@ -79,7 +79,6 @@ async def async_step_ssdp( if discovery_info.get(ATTR_UPNP_SERIAL): receiver_id = discovery_info[ATTR_UPNP_SERIAL][4:] # strips off RID- - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"name": host}}) self.discovery_info.update( diff --git a/homeassistant/components/doorbird/config_flow.py b/homeassistant/components/doorbird/config_flow.py index 8e3f661254df15..4bbd7f8dc86049 100644 --- a/homeassistant/components/doorbird/config_flow.py +++ b/homeassistant/components/doorbird/config_flow.py @@ -103,7 +103,6 @@ async def async_step_zeroconf(self, discovery_info): if friendly_hostname.endswith(chop_ending): friendly_hostname = friendly_hostname[: -len(chop_ending)] - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_NAME: friendly_hostname, CONF_HOST: discovery_info[CONF_HOST], diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index a6f25b8827c24d..60cc08dc9b3e32 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -63,7 +63,6 @@ async def async_step_zeroconf( await self.async_set_unique_id(info.serial_number) self._abort_if_unique_id_configured(updates={CONF_HOST: user_input[CONF_HOST]}) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update( { CONF_HOST: user_input[CONF_HOST], @@ -76,7 +75,6 @@ async def async_step_zeroconf( # Prepare configuration flow return self._show_confirm_dialog() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 async def async_step_zeroconf_confirm( self, user_input: ConfigType = None ) -> Dict[str, Any]: @@ -119,7 +117,6 @@ def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: def _show_confirm_dialog(self) -> Dict[str, Any]: """Show the confirm dialog to the user.""" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 serial_number = self.context.get(CONF_SERIAL_NUMBER) return self.async_show_form( step_id="zeroconf_confirm", diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 34b168cdd8cdeb..a84aa2959eac8c 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -48,12 +48,10 @@ async def async_step_user( @property def _name(self): - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 return self.context.get(CONF_NAME) @_name.setter def _name(self, value): - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context[CONF_NAME] = value self.context["title_placeholders"] = {"name": self._name} diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index 285f138264407a..adc6e9b7b35d4b 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -188,6 +188,5 @@ async def async_step_zeroconf(self, discovery_info): CONF_NAME: discovery_info["properties"]["Machine Name"], } self.discovery_schema = vol.Schema(fill_in_schema_dict(zeroconf_data)) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": zeroconf_data}) return await self.async_step_user() diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index f54211aa8a2a09..904081ef99fa12 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -43,8 +43,6 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - def __init__(self): """Initialize flow.""" self._entry = None diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index ab296c84121ebf..a08450e20a1832 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -165,9 +165,7 @@ async def async_step_user(self, user_input=None): if result != RESULT_SUCCESS: return self.async_abort(reason=result) - if ( # pylint: disable=no-member - self.context["source"] == config_entries.SOURCE_IMPORT - ): + if self.context["source"] == config_entries.SOURCE_IMPORT: self._phonebook_id = user_input[CONF_PHONEBOOK] self._phonebook_name = user_input[CONF_NAME] diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index 760cf960e4389a..a9286467afcf43 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -88,7 +88,6 @@ async def async_step_zeroconf(self, discovery_info): pin = async_get_pin_from_discovery_hostname(discovery_info["hostname"]) await self._async_set_unique_id(pin) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context[CONF_IP_ADDRESS] = discovery_info["host"] if any( diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index e01febbef43b61..899edeb8a912f1 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -89,7 +89,6 @@ async def async_step_ssdp(self, discovery_info): if self._host_already_configured(parsed_url.hostname): return self.async_abort(reason="already_configured") - # pylint: disable=no-member self.context["title_placeholders"] = {"name": friendly_name} self.harmony_config = { diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index e046a131a6bfb5..38a41617c6a0bc 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -253,7 +253,6 @@ async def async_step_zeroconf(self, discovery_info): await self.async_set_unique_id(normalize_hkid(hkid)) self._abort_if_unique_id_configured() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["hkid"] = hkid if paired: @@ -392,7 +391,6 @@ async def async_step_protocol_error(self, user_input=None): @callback def _async_step_pair_show_form(self, errors=None): - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 placeholders = {"name": self.name} self.context["title_placeholders"] = {"name": self.name} diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index 350ad5bca0d5ed..e38b873a5bb3c1 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -70,10 +70,7 @@ async def _async_show_user_form( CONF_URL, default=user_input.get( CONF_URL, - # https://github.com/PyCQA/pylint/issues/3167 - self.context.get( # pylint: disable=no-member - CONF_URL, "" - ), + self.context.get(CONF_URL, ""), ), ), str, @@ -192,7 +189,6 @@ def get_router_title(conn: Connection) -> str: title = info.get("DeviceName") return title or DEFAULT_DEVICE_NAME - assert self.hass is not None try: conn = await self.hass.async_add_executor_job(try_connect, user_input) except LoginErrorUsernameWrongException: @@ -218,7 +214,6 @@ def get_router_title(conn: Connection) -> str: user_input=user_input, errors=errors ) - # pylint: disable=no-member title = self.context.get("title_placeholders", {}).get( CONF_NAME ) or await self.hass.async_add_executor_job(get_router_title, conn) @@ -238,8 +233,7 @@ async def async_step_ssdp( # type: ignore # mypy says signature incompatible w if "mobile" not in discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "").lower(): return self.async_abort(reason="not_huawei_lte") - # https://github.com/PyCQA/pylint/issues/3167 - url = self.context[CONF_URL] = url_normalize( # pylint: disable=no-member + url = self.context[CONF_URL] = url_normalize( discovery_info.get( ssdp.ATTR_UPNP_PRESENTATION_URL, f"http://{urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname}/", @@ -255,7 +249,6 @@ async def async_step_ssdp( # type: ignore # mypy says signature incompatible w if self._already_configured(user_input): return self.async_abort(reason="already_configured") - # pylint: disable=no-member self.context["title_placeholders"] = { CONF_NAME: discovery_info.get(ssdp.ATTR_UPNP_FRIENDLY_NAME) } diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index f4528b0efbe68d..642bc0e93fd0ee 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -270,7 +270,6 @@ async def _request_token_task_func(self, auth_id: str) -> None: auth_resp = await hyperion_client.async_request_token( comment=DEFAULT_ORIGIN, id=auth_id ) - assert self.hass await self.hass.config_entries.flow.async_configure( flow_id=self.flow_id, user_input=auth_resp ) @@ -344,7 +343,6 @@ async def async_step_create_token( # Start a task in the background requesting a new token. The next step will # wait on the response (which includes the user needing to visit the Hyperion # UI to approve the request for a new token). - assert self.hass assert self._auth_id is not None self._request_token_task = self.hass.async_create_task( self._request_token_task_func(self._auth_id) @@ -414,9 +412,7 @@ async def async_step_confirm( entry = await self.async_set_unique_id(hyperion_id, raise_on_progress=False) - # pylint: disable=no-member if self.context.get(CONF_SOURCE) == SOURCE_REAUTH and entry is not None: - assert self.hass self.hass.config_entries.async_update_entry(entry, data=self._data) # Need to manually reload, as the listener won't have been installed because # the initial load did not succeed (the reauth flow will not be initiated if @@ -426,7 +422,6 @@ async def async_step_confirm( self._abort_if_unique_id_configured() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 return self.async_create_entry( title=f"{self._data[CONF_HOST]}:{self._data[CONF_PORT]}", data=self._data ) diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index feed7e7b5289f1..3815dcf8f69061 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -106,7 +106,6 @@ async def async_step_zeroconf(self, discovery_info: ConfigType) -> Dict[str, Any tls = zctype == "_ipps._tcp.local." base_path = discovery_info["properties"].get("rp", "ipp/print") - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"name": name}}) self.discovery_info.update( diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 6049b8c6ec323c..3d52687bced673 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -168,7 +168,6 @@ async def async_step_ssdp(self, discovery_info): CONF_HOST: url, } - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = self.discovered_conf return await self.async_step_user() diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index c48e4564f926bd..69460a575707f8 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -119,7 +119,6 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): } ) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {CONF_NAME: self._name}}) try: diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index dc15e7a86c411d..219148e37cfce8 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -169,8 +169,6 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # class variable to store/share discovered host information discovered_hosts = {} - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - def __init__(self): """Initialize the Konnected flow.""" self.data = {} diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index ab9865f999a0b9..6cd30a78f0c865 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -77,7 +77,6 @@ async def async_step_zeroconf(self, discovery_info): self._abort_if_unique_id_configured({CONF_HOST: host}) self.data[CONF_HOST] = host - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_NAME: self.bridge_id, CONF_HOST: host, @@ -201,8 +200,6 @@ async def async_step_import(self, import_info): async def async_step_import_failed(self, user_input=None): """Make failed import surfaced to user.""" - - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {CONF_NAME: self.data[CONF_HOST]} if user_input is None: diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 8a868d7bb39bbd..7407958cdc00cf 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -129,7 +129,6 @@ async def async_step_zeroconf(self, discovery_info): """Prepare configuration for a discovered nut device.""" self.discovery_info = discovery_info await self._async_handle_discovery_without_unique_id() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_PORT: discovery_info.get(CONF_PORT, DEFAULT_PORT), CONF_HOST: discovery_info[CONF_HOST], diff --git a/homeassistant/components/ovo_energy/config_flow.py b/homeassistant/components/ovo_energy/config_flow.py index f395415d89e104..0b2f7aac2d0dd3 100644 --- a/homeassistant/components/ovo_energy/config_flow.py +++ b/homeassistant/components/ovo_energy/config_flow.py @@ -62,7 +62,6 @@ async def async_step_reauth(self, user_input): if user_input and user_input.get(CONF_USERNAME): self.username = user_input[CONF_USERNAME] - # pylint: disable=no-member self.context["title_placeholders"] = {CONF_USERNAME: self.username} if user_input is not None and user_input.get(CONF_PASSWORD) is not None: diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index f177412e7ec26f..e52e4597bf925f 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -230,10 +230,7 @@ async def async_step_server_validate(self, server_config): } entry = await self.async_set_unique_id(server_id) - if ( - self.context[CONF_SOURCE] # pylint: disable=no-member - == config_entries.SOURCE_REAUTH - ): + if self.context[CONF_SOURCE] == config_entries.SOURCE_REAUTH: self.hass.config_entries.async_update_entry(entry, data=data) _LOGGER.debug("Updated config entry for %s", plex_server.friendly_name) await self.hass.config_entries.async_reload(entry.entry_id) @@ -280,7 +277,7 @@ async def async_step_integration_discovery(self, discovery_info): self._abort_if_unique_id_configured() host = f"{discovery_info['from'][0]}:{discovery_info['data']['Port']}" name = discovery_info["data"]["Name"] - self.context["title_placeholders"] = { # pylint: disable=no-member + self.context["title_placeholders"] = { "host": host, "name": name, } diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 247e0802eaeb8f..e17c85a79788d5 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -97,7 +97,6 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): _version = _properties.get("version", "n/a") _name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_HOST: discovery_info[CONF_HOST], CONF_PORT: discovery_info.get(CONF_PORT, DEFAULT_PORT), diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index b649b16008535f..eb804df3420f39 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -65,7 +65,6 @@ async def async_step_dhcp(self, dhcp_discovery): return self.async_abort(reason="already_configured") self.ip_address = dhcp_discovery[IP_ADDRESS] - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {CONF_IP_ADDRESS: self.ip_address} return await self.async_step_user() diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index f8e9034292c1d6..b086d7a931130c 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -109,7 +109,6 @@ async def async_step_homekit(self, discovery_info): updates={CONF_HOST: discovery_info[CONF_HOST]}, ) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"name": info["title"]}}) self.discovery_info.update({CONF_NAME: info["title"]}) @@ -126,7 +125,6 @@ async def async_step_ssdp( await self.async_set_unique_id(serial_number) self._abort_if_unique_id_configured(updates={CONF_HOST: host}) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"name": name}}) self.discovery_info.update({CONF_HOST: host, CONF_NAME: name}) @@ -146,7 +144,6 @@ async def async_step_discovery_confirm( self, user_input: Optional[Dict] = None ) -> Dict[str, Any]: """Handle user-confirmation of discovered device.""" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 if user_input is None: return self.async_show_form( step_id="discovery_confirm", diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index b1f2b290a5485c..787382ed8b5f2d 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -91,7 +91,6 @@ async def async_step_dhcp(self, dhcp_discovery): self.host = dhcp_discovery[IP_ADDRESS] self.blid = blid - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {"host": self.host, "name": self.blid} return await self.async_step_user() @@ -133,7 +132,6 @@ async def async_step_user(self, user_input=None): } if self.host and self.host in self.discovered_robots: # From discovery - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { "host": self.host, "name": self.discovered_robots[self.host].robot_name, diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index 7a7fa26f922584..73d0d0b5e93c76 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -51,8 +51,6 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - def __init__(self): """Initialize flow.""" self._host = None diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index b3a9b068ac4e00..cd74b83a62aefe 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -166,7 +166,6 @@ async def async_step_zeroconf(self, zeroconf_info): except HTTP_CONNECT_ERRORS: return self.async_abort(reason="cannot_connect") - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { "name": zeroconf_info.get("name", "").split(".")[0] } diff --git a/homeassistant/components/smappee/config_flow.py b/homeassistant/components/smappee/config_flow.py index c6d208626e4c3d..450874b3f35ae0 100644 --- a/homeassistant/components/smappee/config_flow.py +++ b/homeassistant/components/smappee/config_flow.py @@ -56,7 +56,6 @@ async def async_step_zeroconf(self, discovery_info): if self.is_cloud_device_already_added(): return self.async_abort(reason="already_configured_device") - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update( { CONF_IP_ADDRESS: discovery_info["host"], @@ -76,7 +75,6 @@ async def async_step_zeroconf_confirm(self, user_input=None): return self.async_abort(reason="already_configured_device") if user_input is None: - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 serialnumber = self.context.get(CONF_SERIALNUMBER) return self.async_show_form( step_id="zeroconf_confirm", @@ -84,7 +82,6 @@ async def async_step_zeroconf_confirm(self, user_input=None): errors=errors, ) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 ip_address = self.context.get(CONF_IP_ADDRESS) serial_number = self.context.get(CONF_SERIALNUMBER) diff --git a/homeassistant/components/sms/config_flow.py b/homeassistant/components/sms/config_flow.py index 52f3a403ed1481..01c1d182c93949 100644 --- a/homeassistant/components/sms/config_flow.py +++ b/homeassistant/components/sms/config_flow.py @@ -27,7 +27,7 @@ async def get_imei_from_config(hass: core.HomeAssistant, data): raise CannotConnect try: imei = await gateway.get_imei_async() - except gammu.GSMError as err: # pylint: disable=no-member + except gammu.GSMError as err: raise CannotConnect from err finally: await gateway.terminate_async() diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index ce69d265b55c61..b6d647b9a3b58a 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -72,7 +72,6 @@ async def async_step_dhcp(self, dhcp_discovery): self.host = dhcp_discovery[HOSTNAME] self.mac = formatted_mac self.ip_address = dhcp_discovery[IP_ADDRESS] - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {"ip": self.ip_address, "mac": self.mac} return await self.async_step_user() diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index 9acbedd11c7806..aaa9302cac25e2 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -114,7 +114,6 @@ async def async_step_ssdp(self, discovery_info): if "videoScreen" in service_types: return self.async_abort(reason="not_songpal_device") - # pylint: disable=no-member self.context["title_placeholders"] = { CONF_NAME: friendly_name, CONF_HOST: parsed_url.hostname, diff --git a/homeassistant/components/spotify/config_flow.py b/homeassistant/components/spotify/config_flow.py index ac6e101d4fe0c1..afad75f0f39b39 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -63,7 +63,6 @@ async def async_step_reauth(self, entry: Dict[str, Any]) -> Dict[str, Any]: if entry: self.entry = entry - assert self.hass persistent_notification.async_create( self.hass, f"Spotify integration for account {entry['id']} needs to be re-authenticated. Please go to the integrations page to re-configure it.", @@ -85,7 +84,6 @@ async def async_step_reauth_confirm( errors={}, ) - assert self.hass persistent_notification.async_dismiss(self.hass, "spotify_reauth") return await self.async_step_pick_implementation( diff --git a/homeassistant/components/squeezebox/config_flow.py b/homeassistant/components/squeezebox/config_flow.py index f5ed6073104a56..9edff5f9a2abda 100644 --- a/homeassistant/components/squeezebox/config_flow.py +++ b/homeassistant/components/squeezebox/config_flow.py @@ -182,7 +182,6 @@ async def async_step_discovery(self, discovery_info): # update schema with suggested values from discovery self.data_schema = _base_schema(discovery_info) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"host": discovery_info[CONF_HOST]}}) return await self.async_step_edit() diff --git a/homeassistant/components/syncthru/config_flow.py b/homeassistant/components/syncthru/config_flow.py index cbdd46b4a6ae64..83f044d8ebcb3f 100644 --- a/homeassistant/components/syncthru/config_flow.py +++ b/homeassistant/components/syncthru/config_flow.py @@ -63,9 +63,7 @@ async def async_step_ssdp(self, discovery_info): self.name = re.sub(r"\s+\([\d.]+\)\s*$", "", self.name) # https://github.com/PyCQA/pylint/issues/3167 - self.context["title_placeholders"] = { # pylint: disable=no-member - CONF_NAME: self.name - } + self.context["title_placeholders"] = {CONF_NAME: self.name} return await self.async_step_confirm() async def async_step_confirm(self, user_input=None): diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 5a1ab53b3f765f..f4638a5ec735ac 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -208,7 +208,6 @@ async def async_step_ssdp(self, discovery_info): CONF_NAME: friendly_name, CONF_HOST: parsed_url.hostname, } - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = self.discovered_conf return await self.async_step_user() diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index d1de68ef0b8bf9..1e1739e85dfb52 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -56,7 +56,6 @@ async def async_step_import( """ if config is not None and CONF_MIGRATE in config: - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({CONF_MIGRATE: config[CONF_MIGRATE]}) else: await self._async_handle_discovery_without_unique_id() @@ -87,10 +86,7 @@ async def async_step_agreement( return await self._create_entry(self.agreements[agreement_index]) async def _create_entry(self, agreement: Agreement) -> Dict[str, Any]: - if ( # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - CONF_MIGRATE in self.context - ): - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + if CONF_MIGRATE in self.context: await self.hass.config_entries.async_remove(self.context[CONF_MIGRATE]) await self.async_set_unique_id(agreement.agreement_id) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 8e83f53d198630..5a0a4969f09f27 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -195,7 +195,6 @@ async def async_step_reauth(self, config_entry: dict): """Trigger a reauthentication flow.""" self.reauth_config_entry = config_entry - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_HOST: config_entry.data[CONF_HOST], CONF_SITE_ID: config_entry.title, @@ -229,7 +228,6 @@ async def async_step_ssdp(self, discovery_info): await self.async_set_unique_id(mac_address) self._abort_if_unique_id_configured(updates=self.config) - # pylint: disable=no-member self.context["title_placeholders"] = { CONF_HOST: self.config[CONF_HOST], CONF_SITE_ID: DEFAULT_SITE_ID, diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 41c56dddb2921f..d3811b7e18b82d 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -183,7 +183,6 @@ async def async_step_ssdp(self, discovery_info: Mapping) -> Mapping[str, Any]: self._discoveries = [discovery] # Ensure user recognizable. - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { "name": discovery[DISCOVERY_NAME], } diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 40f71adda12718..3f57cdb81facee 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -206,7 +206,6 @@ async def async_step_user( self, user_input: Dict[str, Any] = None ) -> Dict[str, Any]: """Handle a flow initialized by the user.""" - assert self.hass errors = {} if user_input is not None: @@ -232,7 +231,6 @@ async def async_step_user( errors[CONF_HOST] = "existing_config_entry_found" if not errors: - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 if self._must_show_form and self.context["source"] == SOURCE_ZEROCONF: # Discovery should always display the config form before trying to # create entry so that user can update default config options @@ -251,7 +249,6 @@ async def async_step_user( if not errors: return await self._create_entry(user_input) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 elif self._must_show_form and self.context["source"] == SOURCE_IMPORT: # Import should always display the config form if CONF_ACCESS_TOKEN # wasn't included but is needed so that the user can choose to update @@ -271,7 +268,6 @@ async def async_step_user( schema = self._user_schema or _get_config_schema() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 if errors and self.context["source"] == SOURCE_IMPORT: # Log an error message if import config flow fails since otherwise failure is silent _LOGGER.error( @@ -346,8 +342,6 @@ async def async_step_zeroconf( self, discovery_info: Optional[DiscoveryInfoType] = None ) -> Dict[str, Any]: """Handle zeroconf discovery.""" - assert self.hass - # If host already has port, no need to add it again if ":" not in discovery_info[CONF_HOST]: discovery_info[ @@ -432,7 +426,6 @@ async def async_step_pair_tv( self._data[CONF_ACCESS_TOKEN] = pair_data.auth_token self._must_show_form = True - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 if self.context["source"] == SOURCE_IMPORT: # If user is pairing via config import, show different message return await self.async_step_pairing_complete_import() diff --git a/homeassistant/components/wilight/config_flow.py b/homeassistant/components/wilight/config_flow.py index 40643b4372fdfb..32c66df65a7ba9 100644 --- a/homeassistant/components/wilight/config_flow.py +++ b/homeassistant/components/wilight/config_flow.py @@ -84,7 +84,6 @@ async def async_step_ssdp(self, discovery_info): await self.async_set_unique_id(self._serial_number) self._abort_if_unique_id_configured(updates={CONF_HOST: self._host}) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {"name": self._title} return await self.async_step_confirm() diff --git a/homeassistant/components/withings/config_flow.py b/homeassistant/components/withings/config_flow.py index d04327808de0fc..ddf51741c622d3 100644 --- a/homeassistant/components/withings/config_flow.py +++ b/homeassistant/components/withings/config_flow.py @@ -48,7 +48,6 @@ async def async_oauth_create_entry(self, data: dict) -> dict: async def async_step_profile(self, data: dict) -> dict: """Prompt the user to select a user profile.""" errors = {} - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 reauth_profile = ( self.context.get(const.PROFILE) if self.context.get("source") == "reauth" @@ -81,14 +80,12 @@ async def async_step_reauth(self, data: dict = None) -> dict: if data is not None: return await self.async_step_user() - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 placeholders = {const.PROFILE: self.context["profile"]} self.context.update({"title_placeholders": placeholders}) return self.async_show_form( step_id="reauth", - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 description_placeholders=placeholders, ) diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index 4d0f6bf1606fd3..5915447f2f54f1 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -39,7 +39,6 @@ async def async_step_zeroconf( host = user_input["hostname"].rstrip(".") name, _ = host.rsplit(".") - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update( { CONF_HOST: user_input["host"], @@ -62,7 +61,6 @@ async def _handle_config_flow( self, user_input: Optional[ConfigType] = None, prepare: bool = False ) -> Dict[str, Any]: """Config flow handler for WLED.""" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 source = self.context.get("source") # Request user input, unless we are preparing discovery flow @@ -72,7 +70,6 @@ async def _handle_config_flow( return self._show_setup_form() if source == SOURCE_ZEROCONF: - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 user_input[CONF_HOST] = self.context.get(CONF_HOST) user_input[CONF_MAC] = self.context.get(CONF_MAC) @@ -93,7 +90,6 @@ async def _handle_config_flow( title = user_input[CONF_HOST] if source == SOURCE_ZEROCONF: - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 title = self.context.get(CONF_NAME) if prepare: @@ -114,7 +110,6 @@ def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: def _show_confirm_dialog(self, errors: Optional[Dict] = None) -> Dict[str, Any]: """Show the confirm dialog to the user.""" - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 name = self.context.get(CONF_NAME) return self.async_show_form( step_id="zeroconf_confirm", diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index 6bf1aa4f4ee5e3..46d4852abae24f 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -181,7 +181,6 @@ async def async_step_zeroconf(self, discovery_info): {CONF_HOST: self.host, CONF_MAC: mac_address} ) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update({"title_placeholders": {"name": self.host}}) return await self.async_step_user() diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 6ebb50cd7ce34d..6ed5f422f7cecd 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -67,7 +67,6 @@ async def async_step_zeroconf(self, discovery_info): await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context.update( {"title_placeholders": {"name": f"Gateway {self.host}"}} ) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 5faaa02d03db57..b18e28419ddff8 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -84,7 +84,6 @@ async def async_step_user( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle the initial step.""" - assert self.hass # typing if self.hass.components.hassio.is_hassio(): return await self.async_step_on_supervisor() @@ -101,7 +100,6 @@ async def async_step_manual( errors = {} - assert self.hass # typing try: version_info = await validate_input(self.hass, user_input) except InvalidInput as err: @@ -128,7 +126,6 @@ async def async_step_hassio( # type: ignore This flow is triggered by the Z-Wave JS add-on. """ - assert self.hass self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" try: version_info = await async_get_version_info(self.hass, self.ws_address) @@ -182,7 +179,6 @@ async def async_step_on_supervisor( self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" if not self.unique_id: - assert self.hass try: version_info = await async_get_version_info( self.hass, self.ws_address @@ -208,14 +204,12 @@ async def async_step_install_addon( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Install Z-Wave JS add-on.""" - assert self.hass if not self.install_task: self.install_task = self.hass.async_create_task(self._async_install_addon()) return self.async_show_progress( step_id="install_addon", progress_action="install_addon" ) - assert self.hass try: await self.install_task except self.hass.components.hassio.HassioAPIError as err: @@ -253,7 +247,6 @@ async def async_step_start_addon( if new_addon_config != self.addon_config: await self._async_set_addon_config(new_addon_config) - assert self.hass try: await self.hass.components.hassio.async_start_addon("core_zwave_js") except self.hass.components.hassio.HassioAPIError as err: @@ -299,7 +292,6 @@ async def async_step_start_addon( async def _async_get_addon_info(self) -> dict: """Return and cache Z-Wave JS add-on info.""" - assert self.hass try: addon_info: dict = await self.hass.components.hassio.async_get_addon_info( "core_zwave_js" @@ -327,7 +319,6 @@ async def _async_get_addon_config(self) -> dict: async def _async_set_addon_config(self, config: dict) -> None: """Set Z-Wave JS add-on config.""" - assert self.hass options = {"options": config} try: await self.hass.components.hassio.async_set_addon_options( @@ -339,7 +330,6 @@ async def _async_set_addon_config(self, config: dict) -> None: async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" - assert self.hass try: await self.hass.components.hassio.async_install_addon("core_zwave_js") finally: @@ -350,7 +340,6 @@ async def _async_install_addon(self) -> None: async def _async_get_addon_discovery_info(self) -> dict: """Return add-on discovery info.""" - assert self.hass try: discovery_info: dict = ( await self.hass.components.hassio.async_get_addon_discovery_info( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index e38136e33ca752..bbc1479524ab71 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -903,7 +903,6 @@ def _abort_if_unique_id_configured( reload_on_update: bool = True, ) -> None: """Abort if the unique ID is already configured.""" - assert self.hass if self.unique_id is None: return @@ -945,7 +944,6 @@ async def async_set_unique_id( self.context["unique_id"] = unique_id # pylint: disable=no-member # Abort discoveries done using the default discovery unique id - assert self.hass is not None if unique_id != DEFAULT_DISCOVERY_UNIQUE_ID: for progress in self._async_in_progress(): if progress["context"].get("unique_id") == DEFAULT_DISCOVERY_UNIQUE_ID: @@ -963,7 +961,6 @@ def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEnt If the flow is user initiated, filter out ignored entries unless include_ignore is True. """ - assert self.hass is not None config_entries = self.hass.config_entries.async_entries(self.handler) if include_ignore or self.source != SOURCE_USER: @@ -974,7 +971,6 @@ def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEnt @callback def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: """Return current unique IDs.""" - assert self.hass is not None return { entry.unique_id for entry in self.hass.config_entries.async_entries(self.handler) @@ -984,7 +980,6 @@ def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: @callback def _async_in_progress(self) -> List[Dict]: """Return other in progress flows for current domain.""" - assert self.hass is not None return [ flw for flw in self.hass.config_entries.flow.async_progress() @@ -1027,7 +1022,6 @@ async def _async_handle_discovery_without_unique_id(self) -> None: self._abort_if_unique_id_configured() # Abort if any other flow for this handler is already in progress - assert self.hass is not None if self._async_in_progress(): raise data_entry_flow.AbortFlow("already_in_progress") @@ -1043,8 +1037,6 @@ def async_abort( self, *, reason: str, description_placeholders: Optional[Dict] = None ) -> Dict[str, Any]: """Abort the config flow.""" - assert self.hass - # Remove reauth notification if no reauth flows are in progress if self.source == SOURCE_REAUTH and not any( ent["context"]["source"] == SOURCE_REAUTH diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 85609556217bde..e8235c9a23cc0e 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -3,7 +3,8 @@ import abc import asyncio -from typing import Any, Dict, List, Optional, cast +from types import MappingProxyType +from typing import Any, Dict, List, Optional import uuid import voluptuous as vol @@ -264,11 +265,14 @@ class FlowHandler: """Handle the configuration flow of a component.""" # Set by flow manager - flow_id: str = None # type: ignore - hass: Optional[HomeAssistant] = None - handler: Optional[str] = None cur_step: Optional[Dict[str, str]] = None - context: Dict + # Ignore types, pylint workaround: https://github.com/PyCQA/pylint/issues/3167 + flow_id: str = None # type: ignore + hass: HomeAssistant = None # type: ignore + handler: str = None # type: ignore + # Pylint workaround: https://github.com/PyCQA/pylint/issues/3167 + # Ensure the attribute has a subscriptable, but immutable, default value. + context: Dict = MappingProxyType({}) # type: ignore # Set by _async_create_flow callback init_step = "init" @@ -339,7 +343,7 @@ def async_abort( ) -> Dict[str, Any]: """Abort the config flow.""" return _create_abort_data( - self.flow_id, cast(str, self.handler), reason, description_placeholders + self.flow_id, self.handler, reason, description_placeholders ) @callback From 52c5bc0a9977c6a2b9b2b33b3461aaf8af464c51 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 13 Feb 2021 14:23:40 +0200 Subject: [PATCH 0447/1818] Remove deprecated Synology integration (#46482) --- .coveragerc | 1 - homeassistant/components/synology/__init__.py | 1 - homeassistant/components/synology/camera.py | 143 ------------------ .../components/synology/manifest.json | 7 - requirements_all.txt | 3 - 5 files changed, 155 deletions(-) delete mode 100644 homeassistant/components/synology/__init__.py delete mode 100644 homeassistant/components/synology/camera.py delete mode 100644 homeassistant/components/synology/manifest.json diff --git a/.coveragerc b/.coveragerc index 6043d3d45f054c..6339e09a034a2b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -899,7 +899,6 @@ omit = homeassistant/components/switcher_kis/switch.py homeassistant/components/switchmate/switch.py homeassistant/components/syncthru/sensor.py - homeassistant/components/synology/camera.py homeassistant/components/synology_chat/notify.py homeassistant/components/synology_dsm/__init__.py homeassistant/components/synology_dsm/binary_sensor.py diff --git a/homeassistant/components/synology/__init__.py b/homeassistant/components/synology/__init__.py deleted file mode 100644 index 0ab4b45e298646..00000000000000 --- a/homeassistant/components/synology/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The synology component.""" diff --git a/homeassistant/components/synology/camera.py b/homeassistant/components/synology/camera.py deleted file mode 100644 index 4417f72918d0dc..00000000000000 --- a/homeassistant/components/synology/camera.py +++ /dev/null @@ -1,143 +0,0 @@ -"""Support for Synology Surveillance Station Cameras.""" -from functools import partial -import logging - -import requests -from synology.surveillance_station import SurveillanceStation -import voluptuous as vol - -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.const import ( - CONF_NAME, - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_URL, - CONF_USERNAME, - CONF_VERIFY_SSL, - CONF_WHITELIST, -) -from homeassistant.helpers.aiohttp_client import ( - async_aiohttp_proxy_web, - async_get_clientsession, -) -import homeassistant.helpers.config_validation as cv - -_LOGGER = logging.getLogger(__name__) - -DEFAULT_NAME = "Synology Camera" -DEFAULT_TIMEOUT = 5 - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_URL): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list, - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, - } -) - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up a Synology IP Camera.""" - _LOGGER.warning( - "The Synology integration is deprecated." - " Please use the Synology DSM integration" - " (https://www.home-assistant.io/integrations/synology_dsm/) instead." - " This integration will be removed in version 0.118.0." - ) - - verify_ssl = config.get(CONF_VERIFY_SSL) - timeout = config.get(CONF_TIMEOUT) - - try: - surveillance = await hass.async_add_executor_job( - partial( - SurveillanceStation, - config.get(CONF_URL), - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - verify_ssl=verify_ssl, - timeout=timeout, - ) - ) - except (requests.exceptions.RequestException, ValueError): - _LOGGER.exception("Error when initializing SurveillanceStation") - return False - - cameras = surveillance.get_all_cameras() - - # add cameras - devices = [] - for camera in cameras: - if not config[CONF_WHITELIST] or camera.name in config[CONF_WHITELIST]: - device = SynologyCamera(surveillance, camera.camera_id, verify_ssl) - devices.append(device) - - async_add_entities(devices) - - -class SynologyCamera(Camera): - """An implementation of a Synology NAS based IP camera.""" - - def __init__(self, surveillance, camera_id, verify_ssl): - """Initialize a Synology Surveillance Station camera.""" - super().__init__() - self._surveillance = surveillance - self._camera_id = camera_id - self._verify_ssl = verify_ssl - self._camera = self._surveillance.get_camera(camera_id) - self._motion_setting = self._surveillance.get_motion_setting(camera_id) - self.is_streaming = self._camera.is_enabled - - def camera_image(self): - """Return bytes of camera image.""" - return self._surveillance.get_camera_image(self._camera_id) - - async def handle_async_mjpeg_stream(self, request): - """Return a MJPEG stream image response directly from the camera.""" - streaming_url = self._camera.video_stream_url - - websession = async_get_clientsession(self.hass, self._verify_ssl) - stream_coro = websession.get(streaming_url) - - return await async_aiohttp_proxy_web(self.hass, request, stream_coro) - - @property - def name(self): - """Return the name of this device.""" - return self._camera.name - - @property - def is_recording(self): - """Return true if the device is recording.""" - return self._camera.is_recording - - @property - def should_poll(self): - """Update the recording state periodically.""" - return True - - def update(self): - """Update the status of the camera.""" - self._surveillance.update() - self._camera = self._surveillance.get_camera(self._camera.camera_id) - self._motion_setting = self._surveillance.get_motion_setting( - self._camera.camera_id - ) - self.is_streaming = self._camera.is_enabled - - @property - def motion_detection_enabled(self): - """Return the camera motion detection status.""" - return self._motion_setting.is_enabled - - def enable_motion_detection(self): - """Enable motion detection in the camera.""" - self._surveillance.enable_motion_detection(self._camera_id) - - def disable_motion_detection(self): - """Disable motion detection in camera.""" - self._surveillance.disable_motion_detection(self._camera_id) diff --git a/homeassistant/components/synology/manifest.json b/homeassistant/components/synology/manifest.json deleted file mode 100644 index a29dccc2a78229..00000000000000 --- a/homeassistant/components/synology/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "synology", - "name": "Synology", - "documentation": "https://www.home-assistant.io/integrations/synology", - "requirements": ["py-synology==0.2.0"], - "codeowners": [] -} diff --git a/requirements_all.txt b/requirements_all.txt index 401009e9fef40a..9e7a75e8905aa6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1212,9 +1212,6 @@ py-nightscout==1.2.2 # homeassistant.components.schluter py-schluter==0.1.7 -# homeassistant.components.synology -py-synology==0.2.0 - # homeassistant.components.zabbix py-zabbix==1.1.7 From 6f261a09b04687f7d52a54a3a9a2c7ddc7595317 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 13 Feb 2021 15:07:55 +0200 Subject: [PATCH 0448/1818] Remove deprecated xfinity integration (#46484) --- .coveragerc | 1 - CODEOWNERS | 1 - homeassistant/components/xfinity/__init__.py | 1 - .../components/xfinity/device_tracker.py | 64 ------------------- .../components/xfinity/manifest.json | 7 -- requirements_all.txt | 3 - 6 files changed, 77 deletions(-) delete mode 100644 homeassistant/components/xfinity/__init__.py delete mode 100644 homeassistant/components/xfinity/device_tracker.py delete mode 100644 homeassistant/components/xfinity/manifest.json diff --git a/.coveragerc b/.coveragerc index 6339e09a034a2b..b5427435636577 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1065,7 +1065,6 @@ omit = homeassistant/components/xbox/sensor.py homeassistant/components/xbox_live/sensor.py homeassistant/components/xeoma/camera.py - homeassistant/components/xfinity/device_tracker.py homeassistant/components/xiaomi/camera.py homeassistant/components/xiaomi_aqara/__init__.py homeassistant/components/xiaomi_aqara/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 0e33076105f0ef..3d7ff5f79a502f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -525,7 +525,6 @@ homeassistant/components/workday/* @fabaff homeassistant/components/worldclock/* @fabaff homeassistant/components/xbox/* @hunterjm homeassistant/components/xbox_live/* @MartinHjelmare -homeassistant/components/xfinity/* @cisasteelersfan homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG homeassistant/components/xiaomi_tv/* @simse diff --git a/homeassistant/components/xfinity/__init__.py b/homeassistant/components/xfinity/__init__.py deleted file mode 100644 index 22e37eccde97b2..00000000000000 --- a/homeassistant/components/xfinity/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The xfinity component.""" diff --git a/homeassistant/components/xfinity/device_tracker.py b/homeassistant/components/xfinity/device_tracker.py deleted file mode 100644 index 832c8bb1d5dc86..00000000000000 --- a/homeassistant/components/xfinity/device_tracker.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Support for device tracking via Xfinity Gateways.""" -import logging - -from requests.exceptions import RequestException -import voluptuous as vol -from xfinity_gateway import XfinityGateway - -from homeassistant.components.device_tracker import ( - DOMAIN, - PLATFORM_SCHEMA, - DeviceScanner, -) -from homeassistant.const import CONF_HOST -import homeassistant.helpers.config_validation as cv - -_LOGGER = logging.getLogger(__name__) - -DEFAULT_HOST = "10.0.0.1" - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string} -) - - -def get_scanner(hass, config): - """Validate the configuration and return an Xfinity Gateway scanner.""" - _LOGGER.warning( - "The Xfinity Gateway has been deprecated and will be removed from " - "Home Assistant in version 0.109. Please remove it from your " - "configuration. " - ) - - gateway = XfinityGateway(config[DOMAIN][CONF_HOST]) - scanner = None - try: - gateway.scan_devices() - scanner = XfinityDeviceScanner(gateway) - except (RequestException, ValueError): - _LOGGER.error( - "Error communicating with Xfinity Gateway. Check host: %s", gateway.host - ) - - return scanner - - -class XfinityDeviceScanner(DeviceScanner): - """This class queries an Xfinity Gateway.""" - - def __init__(self, gateway): - """Initialize the scanner.""" - self.gateway = gateway - - def scan_devices(self): - """Scan for new devices and return a list of found MACs.""" - connected_devices = [] - try: - connected_devices = self.gateway.scan_devices() - except (RequestException, ValueError): - _LOGGER.error("Unable to scan devices. Check connection to gateway") - return connected_devices - - def get_device_name(self, device): - """Return the name of the given device or None if we don't know.""" - return self.gateway.get_device_name(device) diff --git a/homeassistant/components/xfinity/manifest.json b/homeassistant/components/xfinity/manifest.json deleted file mode 100644 index 999b77dfb59799..00000000000000 --- a/homeassistant/components/xfinity/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "xfinity", - "name": "Xfinity Gateway", - "documentation": "https://www.home-assistant.io/integrations/xfinity", - "requirements": ["xfinity-gateway==0.0.4"], - "codeowners": ["@cisasteelersfan"] -} diff --git a/requirements_all.txt b/requirements_all.txt index 9e7a75e8905aa6..dbad80603d440a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2313,9 +2313,6 @@ xbox-webapi==2.0.8 # homeassistant.components.xbox_live xboxapi==2.0.1 -# homeassistant.components.xfinity -xfinity-gateway==0.0.4 - # homeassistant.components.knx xknx==0.16.3 From f38b06ed6d18842e2921ae3bbec56ccad5f16972 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Sat, 13 Feb 2021 19:17:06 +0100 Subject: [PATCH 0449/1818] Add Asuswrt Config Flow and Scanner Entities (#46468) * Add Asuswrt config flow (#43948) * Add AsusWrt Scanner Entity (#44759) * Add Scanner Entity - device tracker entity changed from "DeviceScanner" to "ScannerEntity" - sensors recoded to use "router" class - config entry review to allow multiple entity (for future use) * Force checks * Removed new option and change sensors * Update test_sensor.py * Requested changes * Removed router unique-id * Update last_activity attr only when available * Add Options for AsusWRT Scanner Entity (#44808) * Add Asuswrt config flow (#43948) * Add AsusWrt Scanner Entity (#44759) * Add Scanner Entity - device tracker entity changed from "DeviceScanner" to "ScannerEntity" - sensors recoded to use "router" class - config entry review to allow multiple entity (for future use) * Force checks * Removed new option and change sensors * Update test_sensor.py * Requested changes * Removed router unique-id * Update last_activity attr only when available * Add Options for Scanner Entity * Fix isort * Removed "Track New" option * Add Options for Scanner Entity * Fix isort * Removed "Track New" option * Add test for all the options in the config flow --- .coveragerc | 2 + homeassistant/components/asuswrt/__init__.py | 222 ++++++++----- .../components/asuswrt/config_flow.py | 238 ++++++++++++++ homeassistant/components/asuswrt/const.py | 24 ++ .../components/asuswrt/device_tracker.py | 191 +++++++---- .../components/asuswrt/manifest.json | 1 + homeassistant/components/asuswrt/router.py | 274 ++++++++++++++++ homeassistant/components/asuswrt/sensor.py | 96 +++++- homeassistant/components/asuswrt/strings.json | 45 +++ .../components/asuswrt/translations/en.json | 45 +++ homeassistant/generated/config_flows.py | 1 + tests/components/asuswrt/test_config_flow.py | 296 ++++++++++++++++++ .../components/asuswrt/test_device_tracker.py | 119 ------- tests/components/asuswrt/test_sensor.py | 177 ++++++++--- 14 files changed, 1411 insertions(+), 320 deletions(-) create mode 100644 homeassistant/components/asuswrt/config_flow.py create mode 100644 homeassistant/components/asuswrt/const.py create mode 100644 homeassistant/components/asuswrt/router.py create mode 100644 homeassistant/components/asuswrt/strings.json create mode 100644 homeassistant/components/asuswrt/translations/en.json create mode 100644 tests/components/asuswrt/test_config_flow.py delete mode 100644 tests/components/asuswrt/test_device_tracker.py diff --git a/.coveragerc b/.coveragerc index b5427435636577..3bf3fa10947f78 100644 --- a/.coveragerc +++ b/.coveragerc @@ -67,6 +67,8 @@ omit = homeassistant/components/arwn/sensor.py homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_mbox/* + homeassistant/components/asuswrt/__init__.py + homeassistant/components/asuswrt/router.py homeassistant/components/aten_pe/* homeassistant/components/atome/* homeassistant/components/aurora/__init__.py diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 9cd47d803dede3..d2eb47fa2d2977 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -1,9 +1,10 @@ """Support for ASUSWRT devices.""" +import asyncio import logging -from aioasuswrt.asuswrt import AsusWrt import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_MODE, @@ -12,108 +13,165 @@ CONF_PROTOCOL, CONF_SENSORS, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, ) from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.discovery import async_load_platform -from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.typing import HomeAssistantType + +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, + SENSOR_TYPES, +) +from .router import AsusWrtRouter -_LOGGER = logging.getLogger(__name__) +PLATFORMS = ["device_tracker", "sensor"] -CONF_DNSMASQ = "dnsmasq" -CONF_INTERFACE = "interface" CONF_PUB_KEY = "pub_key" -CONF_REQUIRE_IP = "require_ip" -CONF_SSH_KEY = "ssh_key" - -DOMAIN = "asuswrt" -DATA_ASUSWRT = DOMAIN - -DEFAULT_SSH_PORT = 22 -DEFAULT_INTERFACE = "eth0" -DEFAULT_DNSMASQ = "/var/lib/misc" - -FIRST_RETRY_TIME = 60 -MAX_RETRY_TIME = 900 - SECRET_GROUP = "Password or SSH Key" -SENSOR_TYPES = ["devices", "upload_speed", "download_speed", "download", "upload"] + +_LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PROTOCOL, default="ssh"): vol.In(["ssh", "telnet"]), - vol.Optional(CONF_MODE, default="router"): vol.In(["router", "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, - } - ) - }, + 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, config, retry_delay=FIRST_RETRY_TIME): - """Set up the asuswrt component.""" - conf = config[DOMAIN] - - api = AsusWrt( - conf[CONF_HOST], - conf[CONF_PORT], - conf[CONF_PROTOCOL] == "telnet", - conf[CONF_USERNAME], - conf.get(CONF_PASSWORD, ""), - conf.get("ssh_key", conf.get("pub_key", "")), - conf[CONF_MODE], - conf[CONF_REQUIRE_IP], - interface=conf[CONF_INTERFACE], - dnsmasq=conf[CONF_DNSMASQ], - ) +async def async_setup(hass, config): + """Set up the AsusWrt integration.""" + conf = config.get(DOMAIN) + if conf 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 + pub_key = conf.pop(CONF_PUB_KEY, "") + if pub_key: + conf[CONF_SSH_KEY] = pub_key - try: - await api.connection.async_connect() - except OSError as ex: - _LOGGER.warning( - "Error [%s] connecting %s to %s. Will retry in %s seconds...", - str(ex), - DOMAIN, - conf[CONF_HOST], - retry_delay, + 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 ) + ) - async def retry_setup(now): - """Retry setup if a error happens on asuswrt API.""" - await async_setup( - hass, config, retry_delay=min(2 * retry_delay, MAX_RETRY_TIME) - ) + return True - async_call_later(hass, retry_delay, retry_setup) - return True +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): + """Set up AsusWrt platform.""" - if not api.is_connected: - _LOGGER.error("Error connecting %s to %s", DOMAIN, conf[CONF_HOST]) - return False + # 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) - hass.data[DATA_ASUSWRT] = api + router = AsusWrtRouter(hass, entry) + await router.setup() - hass.async_create_task( - async_load_platform( - hass, "sensor", DOMAIN, config[DOMAIN].get(CONF_SENSORS), config + router.async_on_close(entry.add_update_listener(update_listener)) + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) ) + + 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 ) - hass.async_create_task( - async_load_platform(hass, "device_tracker", DOMAIN, {}, config) - ) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + DATA_ASUSWRT: router, + "stop_listener": stop_listener, + } return True + + +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN][entry.entry_id]["stop_listener"]() + router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + await router.close() + + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def update_listener(hass: HomeAssistantType, entry: ConfigEntry): + """Update when config_entry options update.""" + router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + + if router.update_options(entry.options): + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py new file mode 100644 index 00000000000000..303b3cc3822ce9 --- /dev/null +++ b/homeassistant/components/asuswrt/config_flow.py @@ -0,0 +1,238 @@ +"""Config flow to configure the AsusWrt integration.""" +import logging +import os +import socket + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.device_tracker.const import ( + CONF_CONSIDER_HOME, + DEFAULT_CONSIDER_HOME, +) +from homeassistant.const import ( + CONF_HOST, + CONF_MODE, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_USERNAME, +) +from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv + +# pylint:disable=unused-import +from .const import ( + CONF_DNSMASQ, + CONF_INTERFACE, + CONF_REQUIRE_IP, + CONF_SSH_KEY, + CONF_TRACK_UNKNOWN, + DEFAULT_DNSMASQ, + DEFAULT_INTERFACE, + DEFAULT_SSH_PORT, + DEFAULT_TRACK_UNKNOWN, + DOMAIN, + MODE_AP, + MODE_ROUTER, + PROTOCOL_SSH, + PROTOCOL_TELNET, +) +from .router import get_api + +RESULT_CONN_ERROR = "cannot_connect" +RESULT_UNKNOWN = "unknown" +RESULT_SUCCESS = "success" + +_LOGGER = logging.getLogger(__name__) + + +def _is_file(value) -> bool: + """Validate that the value is an existing file.""" + file_in = os.path.expanduser(str(value)) + + if not os.path.isfile(file_in): + return False + if not os.access(file_in, os.R_OK): + return False + return True + + +def _get_ip(host): + """Get the ip address from the host name.""" + try: + return socket.gethostbyname(host) + except socket.gaierror: + return None + + +class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize AsusWrt config flow.""" + self._host = None + + @callback + def _show_setup_form(self, user_input=None, errors=None): + """Show the setup form to the user.""" + + if user_input is None: + user_input = {} + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, "")): str, + vol.Required( + CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") + ): str, + vol.Optional(CONF_PASSWORD): str, + vol.Optional(CONF_SSH_KEY): str, + vol.Required(CONF_PROTOCOL, default=PROTOCOL_SSH): vol.In( + {PROTOCOL_SSH: "SSH", PROTOCOL_TELNET: "Telnet"} + ), + vol.Required(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, + vol.Required(CONF_MODE, default=MODE_ROUTER): vol.In( + {MODE_ROUTER: "Router", MODE_AP: "Access Point"} + ), + } + ), + errors=errors or {}, + ) + + async def _async_check_connection(self, user_input): + """Attempt to connect the AsusWrt router.""" + + api = get_api(user_input) + try: + await api.connection.async_connect() + + except OSError: + _LOGGER.error("Error connecting to the AsusWrt router at %s", self._host) + return RESULT_CONN_ERROR + + except Exception: # pylint: disable=broad-except + _LOGGER.exception( + "Unknown error connecting with AsusWrt router at %s", self._host + ) + return RESULT_UNKNOWN + + if not api.is_connected: + _LOGGER.error("Error connecting to the AsusWrt router at %s", self._host) + return RESULT_CONN_ERROR + + conf_protocol = user_input[CONF_PROTOCOL] + if conf_protocol == PROTOCOL_TELNET: + await api.connection.disconnect() + return RESULT_SUCCESS + + async def async_step_user(self, user_input=None): + """Handle a flow initiated by the user.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + if user_input is None: + return self._show_setup_form(user_input) + + errors = {} + self._host = user_input[CONF_HOST] + pwd = user_input.get(CONF_PASSWORD) + ssh = user_input.get(CONF_SSH_KEY) + + if not (pwd or ssh): + errors["base"] = "pwd_or_ssh" + elif ssh: + if pwd: + errors["base"] = "pwd_and_ssh" + else: + isfile = await self.hass.async_add_executor_job(_is_file, ssh) + if not isfile: + errors["base"] = "ssh_not_file" + + if not errors: + ip_address = await self.hass.async_add_executor_job(_get_ip, self._host) + if not ip_address: + errors["base"] = "invalid_host" + + if not errors: + result = await self._async_check_connection(user_input) + if result != RESULT_SUCCESS: + errors["base"] = result + + if errors: + return self._show_setup_form(user_input, errors) + + return self.async_create_entry( + title=self._host, + 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): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle a option flow for AsusWrt.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Handle options flow.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + data_schema = vol.Schema( + { + vol.Optional( + CONF_CONSIDER_HOME, + default=self.config_entry.options.get( + CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds() + ), + ): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900)), + vol.Optional( + CONF_TRACK_UNKNOWN, + default=self.config_entry.options.get( + CONF_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN + ), + ): bool, + vol.Required( + CONF_INTERFACE, + default=self.config_entry.options.get( + CONF_INTERFACE, DEFAULT_INTERFACE + ), + ): str, + vol.Required( + CONF_DNSMASQ, + default=self.config_entry.options.get( + CONF_DNSMASQ, DEFAULT_DNSMASQ + ), + ): str, + } + ) + + conf_mode = self.config_entry.data[CONF_MODE] + if conf_mode == MODE_AP: + data_schema = data_schema.extend( + { + vol.Optional( + CONF_REQUIRE_IP, + default=self.config_entry.options.get(CONF_REQUIRE_IP, True), + ): bool, + } + ) + + return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/homeassistant/components/asuswrt/const.py b/homeassistant/components/asuswrt/const.py new file mode 100644 index 00000000000000..40752e81a08fd5 --- /dev/null +++ b/homeassistant/components/asuswrt/const.py @@ -0,0 +1,24 @@ +"""AsusWrt component constants.""" +DOMAIN = "asuswrt" + +CONF_DNSMASQ = "dnsmasq" +CONF_INTERFACE = "interface" +CONF_REQUIRE_IP = "require_ip" +CONF_SSH_KEY = "ssh_key" +CONF_TRACK_UNKNOWN = "track_unknown" + +DATA_ASUSWRT = DOMAIN + +DEFAULT_DNSMASQ = "/var/lib/misc" +DEFAULT_INTERFACE = "eth0" +DEFAULT_SSH_PORT = 22 +DEFAULT_TRACK_UNKNOWN = False + +MODE_AP = "ap" +MODE_ROUTER = "router" + +PROTOCOL_SSH = "ssh" +PROTOCOL_TELNET = "telnet" + +# Sensor +SENSOR_TYPES = ["devices", "upload_speed", "download_speed", "download", "upload"] diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index a3545183d2e597..85553674dbaf35 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -1,64 +1,143 @@ """Support for ASUSWRT routers.""" import logging +from typing import Dict -from homeassistant.components.device_tracker import DeviceScanner +from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER +from homeassistant.components.device_tracker.config_entry import ScannerEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType -from . import DATA_ASUSWRT +from .const import DATA_ASUSWRT, DOMAIN +from .router import AsusWrtRouter + +DEFAULT_DEVICE_NAME = "Unknown device" _LOGGER = logging.getLogger(__name__) -async def async_get_scanner(hass, config): - """Validate the configuration and return an ASUS-WRT scanner.""" - scanner = AsusWrtDeviceScanner(hass.data[DATA_ASUSWRT]) - await scanner.async_connect() - return scanner if scanner.success_init else None - - -class AsusWrtDeviceScanner(DeviceScanner): - """This class queries a router running ASUSWRT firmware.""" - - # Eighth attribute needed for mode (AP mode vs router mode) - def __init__(self, api): - """Initialize the scanner.""" - self.last_results = {} - self.success_init = False - self.connection = api - self._connect_error = False - - async def async_connect(self): - """Initialize connection to the router.""" - # Test the router is accessible. - data = await self.connection.async_get_connected_devices() - self.success_init = data is not None - - async def async_scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" - await self.async_update_info() - return list(self.last_results) - - async def async_get_device_name(self, device): - """Return the name of the given device or None if we don't know.""" - if device not in self.last_results: - return None - return self.last_results[device].name - - async def async_update_info(self): - """Ensure the information from the ASUSWRT router is up to date. - - Return boolean if scanning successful. - """ - _LOGGER.debug("Checking Devices") - - try: - self.last_results = await self.connection.async_get_connected_devices() - if self._connect_error: - self._connect_error = False - _LOGGER.info("Reconnected to ASUS router for device update") - - except OSError as err: - if not self._connect_error: - self._connect_error = True - _LOGGER.error( - "Error connecting to ASUS router for device update: %s", err - ) +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up device tracker for AsusWrt component.""" + router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + tracked = set() + + @callback + def update_router(): + """Update the values of the router.""" + add_entities(router, async_add_entities, tracked) + + router.async_on_close( + async_dispatcher_connect(hass, router.signal_device_new, update_router) + ) + + update_router() + + +@callback +def add_entities(router, async_add_entities, tracked): + """Add new tracker entities from the router.""" + new_tracked = [] + + for mac, device in router.devices.items(): + if mac in tracked: + continue + + new_tracked.append(AsusWrtDevice(router, device)) + tracked.add(mac) + + if new_tracked: + async_add_entities(new_tracked) + + +class AsusWrtDevice(ScannerEntity): + """Representation of a AsusWrt device.""" + + def __init__(self, router: AsusWrtRouter, device) -> None: + """Initialize a AsusWrt device.""" + self._router = router + self._mac = device.mac + self._name = device.name or DEFAULT_DEVICE_NAME + self._active = False + self._icon = None + self._attrs = {} + + @callback + def async_update_state(self) -> None: + """Update the AsusWrt device.""" + device = self._router.devices[self._mac] + self._active = device.is_connected + + self._attrs = { + "mac": device.mac, + "ip_address": device.ip_address, + } + if device.last_activity: + self._attrs["last_time_reachable"] = device.last_activity.isoformat( + timespec="seconds" + ) + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._mac + + @property + def name(self) -> str: + """Return the name.""" + return self._name + + @property + def is_connected(self): + """Return true if the device is connected to the network.""" + return self._active + + @property + def source_type(self) -> str: + """Return the source type.""" + return SOURCE_TYPE_ROUTER + + @property + def icon(self) -> str: + """Return the icon.""" + return self._icon + + @property + def device_state_attributes(self) -> Dict[str, any]: + """Return the attributes.""" + return self._attrs + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "manufacturer": "AsusWRT Tracked device", + } + + @property + def should_poll(self) -> bool: + """No polling needed.""" + return False + + @callback + def async_on_demand_update(self): + """Update state.""" + self.async_update_state() + self.async_write_ha_state() + + async def async_added_to_hass(self): + """Register state update callback.""" + self.async_update_state() + self.async_on_remove( + async_dispatcher_connect( + self.hass, + self._router.signal_device_update, + self.async_on_demand_update, + ) + ) diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json index 9afb7849f8cebd..744a05b9728ec4 100644 --- a/homeassistant/components/asuswrt/manifest.json +++ b/homeassistant/components/asuswrt/manifest.json @@ -1,6 +1,7 @@ { "domain": "asuswrt", "name": "ASUSWRT", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/asuswrt", "requirements": ["aioasuswrt==1.3.1"], "codeowners": ["@kennedyshead"] diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py new file mode 100644 index 00000000000000..11545919b43abb --- /dev/null +++ b/homeassistant/components/asuswrt/router.py @@ -0,0 +1,274 @@ +"""Represent the AsusWrt router.""" +from datetime import datetime, timedelta +import logging +from typing import Any, Dict, Optional + +from aioasuswrt.asuswrt import AsusWrt + +from homeassistant.components.device_tracker.const import ( + CONF_CONSIDER_HOME, + DEFAULT_CONSIDER_HOME, + DOMAIN as TRACKER_DOMAIN, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_MODE, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_USERNAME, +) +from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import dt as dt_util + +from .const import ( + CONF_DNSMASQ, + CONF_INTERFACE, + CONF_REQUIRE_IP, + CONF_SSH_KEY, + CONF_TRACK_UNKNOWN, + DEFAULT_DNSMASQ, + DEFAULT_INTERFACE, + DEFAULT_TRACK_UNKNOWN, + DOMAIN, + PROTOCOL_TELNET, +) + +CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] +SCAN_INTERVAL = timedelta(seconds=30) + +_LOGGER = logging.getLogger(__name__) + + +class AsusWrtDevInfo: + """Representation of a AsusWrt device info.""" + + def __init__(self, mac, name=None): + """Initialize a AsusWrt device info.""" + self._mac = mac + self._name = name + self._ip_address = None + self._last_activity = None + self._connected = False + + def update(self, dev_info=None, consider_home=0): + """Update AsusWrt device info.""" + utc_point_in_time = dt_util.utcnow() + if dev_info: + if not self._name: + self._name = dev_info.name or self._mac.replace(":", "_") + self._ip_address = dev_info.ip + self._last_activity = utc_point_in_time + self._connected = True + + elif self._connected: + self._connected = ( + utc_point_in_time - self._last_activity + ).total_seconds() < consider_home + self._ip_address = None + + @property + def is_connected(self): + """Return connected status.""" + return self._connected + + @property + def mac(self): + """Return device mac address.""" + return self._mac + + @property + def name(self): + """Return device name.""" + return self._name + + @property + def ip_address(self): + """Return device ip address.""" + return self._ip_address + + @property + def last_activity(self): + """Return device last activity.""" + return self._last_activity + + +class AsusWrtRouter: + """Representation of a AsusWrt router.""" + + def __init__(self, hass: HomeAssistantType, entry: ConfigEntry) -> None: + """Initialize a AsusWrt router.""" + self.hass = hass + self._entry = entry + + self._api: AsusWrt = None + self._protocol = entry.data[CONF_PROTOCOL] + self._host = entry.data[CONF_HOST] + + self._devices: Dict[str, Any] = {} + self._connect_error = False + + self._on_close = [] + + self._options = { + CONF_DNSMASQ: DEFAULT_DNSMASQ, + CONF_INTERFACE: DEFAULT_INTERFACE, + CONF_REQUIRE_IP: True, + } + self._options.update(entry.options) + + async def setup(self) -> None: + """Set up a AsusWrt router.""" + self._api = get_api(self._entry.data, self._options) + + try: + await self._api.connection.async_connect() + except OSError as exp: + raise ConfigEntryNotReady from exp + + if not self._api.is_connected: + raise ConfigEntryNotReady + + # Load tracked entities from registry + entity_registry = await self.hass.helpers.entity_registry.async_get_registry() + track_entries = ( + self.hass.helpers.entity_registry.async_entries_for_config_entry( + entity_registry, self._entry.entry_id + ) + ) + for entry in track_entries: + if entry.domain == TRACKER_DOMAIN: + self._devices[entry.unique_id] = AsusWrtDevInfo( + entry.unique_id, entry.original_name + ) + + # Update devices + await self.update_devices() + + self.async_on_close( + async_track_time_interval(self.hass, self.update_all, SCAN_INTERVAL) + ) + + async def update_all(self, now: Optional[datetime] = None) -> None: + """Update all AsusWrt platforms.""" + await self.update_devices() + + async def update_devices(self) -> None: + """Update AsusWrt devices tracker.""" + new_device = False + _LOGGER.debug("Checking devices for ASUS router %s", self._host) + try: + wrt_devices = await self._api.async_get_connected_devices() + except OSError as exc: + if not self._connect_error: + self._connect_error = True + _LOGGER.error( + "Error connecting to ASUS router %s for device update: %s", + self._host, + exc, + ) + return + + if self._connect_error: + self._connect_error = False + _LOGGER.info("Reconnected to ASUS router %s", self._host) + + consider_home = self._options.get( + CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds() + ) + track_unknown = self._options.get(CONF_TRACK_UNKNOWN, DEFAULT_TRACK_UNKNOWN) + + for device_mac in self._devices: + dev_info = wrt_devices.get(device_mac) + self._devices[device_mac].update(dev_info, consider_home) + + for device_mac, dev_info in wrt_devices.items(): + if device_mac in self._devices: + continue + if not track_unknown and not dev_info.name: + continue + new_device = True + device = AsusWrtDevInfo(device_mac) + device.update(dev_info) + self._devices[device_mac] = device + + async_dispatcher_send(self.hass, self.signal_device_update) + if new_device: + async_dispatcher_send(self.hass, self.signal_device_new) + + async def close(self) -> None: + """Close the connection.""" + if self._api is not None: + if self._protocol == PROTOCOL_TELNET: + await self._api.connection.disconnect() + self._api = None + + for func in self._on_close: + func() + self._on_close.clear() + + @callback + def async_on_close(self, func: CALLBACK_TYPE) -> None: + """Add a function to call when router is closed.""" + self._on_close.append(func) + + def update_options(self, new_options: Dict) -> bool: + """Update router options.""" + req_reload = False + for name, new_opt in new_options.items(): + if name in (CONF_REQ_RELOAD): + old_opt = self._options.get(name) + if not old_opt or old_opt != new_opt: + req_reload = True + break + + self._options.update(new_options) + return req_reload + + @property + def signal_device_new(self) -> str: + """Event specific per AsusWrt entry to signal new device.""" + return f"{DOMAIN}-device-new" + + @property + def signal_device_update(self) -> str: + """Event specific per AsusWrt entry to signal updates in devices.""" + return f"{DOMAIN}-device-update" + + @property + def host(self) -> str: + """Return router hostname.""" + return self._host + + @property + def devices(self) -> Dict[str, Any]: + """Return devices.""" + return self._devices + + @property + def api(self) -> AsusWrt: + """Return router API.""" + return self._api + + +def get_api(conf: Dict, options: Optional[Dict] = None) -> AsusWrt: + """Get the AsusWrt API.""" + opt = options or {} + + return AsusWrt( + conf[CONF_HOST], + conf[CONF_PORT], + conf[CONF_PROTOCOL] == PROTOCOL_TELNET, + conf[CONF_USERNAME], + conf.get(CONF_PASSWORD, ""), + conf.get(CONF_SSH_KEY, ""), + conf[CONF_MODE], + opt.get(CONF_REQUIRE_IP, True), + interface=opt.get(CONF_INTERFACE, DEFAULT_INTERFACE), + dnsmasq=opt.get(CONF_DNSMASQ, DEFAULT_DNSMASQ), + ) diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index aa13bee81d0946..2a39d339f06901 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -6,13 +6,15 @@ from aioasuswrt.asuswrt import AsusWrt -from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) -from . import DATA_ASUSWRT +from .const import DATA_ASUSWRT, DOMAIN, SENSOR_TYPES UPLOAD_ICON = "mdi:upload-network" DOWNLOAD_ICON = "mdi:download-network" @@ -35,6 +37,8 @@ def unit_of_measurement(self) -> Optional[str]: return DATA_GIGABYTES if self in (_SensorTypes.UPLOAD_SPEED, _SensorTypes.DOWNLOAD_SPEED): return DATA_RATE_MEGABITS_PER_SECOND + if self == _SensorTypes.DEVICES: + return "devices" return None @property @@ -72,15 +76,26 @@ def is_size(self) -> bool: return self in (_SensorTypes.UPLOAD, _SensorTypes.DOWNLOAD) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +class _SensorInfo: + """Class handling sensor information.""" + + def __init__(self, sensor_type: _SensorTypes): + """Initialize the handler class.""" + self.type = sensor_type + self.enabled = False + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: """Set up the asuswrt sensors.""" - if discovery_info is None: - return - api: AsusWrt = hass.data[DATA_ASUSWRT] + router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + api: AsusWrt = router.api + device_name = entry.data.get(CONF_NAME, "AsusWRT") # Let's discover the valid sensor types. - sensors = [_SensorTypes(x) for x in discovery_info] + sensors = [_SensorInfo(_SensorTypes(x)) for x in SENSOR_TYPES] data_handler = AsuswrtDataHandler(sensors, api) coordinator = DataUpdateCoordinator( @@ -93,34 +108,50 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) await coordinator.async_refresh() - async_add_entities([AsuswrtSensor(coordinator, x) for x in sensors]) + async_add_entities( + [AsuswrtSensor(coordinator, data_handler, device_name, x.type) for x in sensors] + ) class AsuswrtDataHandler: """Class handling the API updates.""" - def __init__(self, sensors: List[_SensorTypes], api: AsusWrt): + def __init__(self, sensors: List[_SensorInfo], api: AsusWrt): """Initialize the handler class.""" self._api = api self._sensors = sensors self._connected = True + def enable_sensor(self, sensor_type: _SensorTypes): + """Enable a specific sensor type.""" + for index, sensor in enumerate(self._sensors): + if sensor.type == sensor_type: + self._sensors[index].enabled = True + return + + def disable_sensor(self, sensor_type: _SensorTypes): + """Disable a specific sensor type.""" + for index, sensor in enumerate(self._sensors): + if sensor.type == sensor_type: + self._sensors[index].enabled = False + return + async def update_data(self) -> Dict[_SensorTypes, Any]: """Fetch the relevant data from the router.""" ret_dict: Dict[_SensorTypes, Any] = {} try: - if _SensorTypes.DEVICES in self._sensors: + if _SensorTypes.DEVICES in [x.type for x in self._sensors if x.enabled]: # Let's check the nr of devices. devices = await self._api.async_get_connected_devices() ret_dict[_SensorTypes.DEVICES] = len(devices) - if any(x.is_speed for x in self._sensors): + if any(x.type.is_speed for x in self._sensors if x.enabled): # Let's check the upload and download speed speed = await self._api.async_get_current_transfer_rates() ret_dict[_SensorTypes.DOWNLOAD_SPEED] = round(speed[0] / 125000, 2) ret_dict[_SensorTypes.UPLOAD_SPEED] = round(speed[1] / 125000, 2) - if any(x.is_size for x in self._sensors): + if any(x.type.is_size for x in self._sensors if x.enabled): rates = await self._api.async_get_bytes_total() ret_dict[_SensorTypes.DOWNLOAD] = round(rates[0] / 1000000000, 1) ret_dict[_SensorTypes.UPLOAD] = round(rates[1] / 1000000000, 1) @@ -142,9 +173,17 @@ async def update_data(self) -> Dict[_SensorTypes, Any]: class AsuswrtSensor(CoordinatorEntity): """The asuswrt specific sensor class.""" - def __init__(self, coordinator: DataUpdateCoordinator, sensor_type: _SensorTypes): + def __init__( + self, + coordinator: DataUpdateCoordinator, + data_handler: AsuswrtDataHandler, + device_name: str, + sensor_type: _SensorTypes, + ): """Initialize the sensor class.""" super().__init__(coordinator) + self._handler = data_handler + self._device_name = device_name self._type = sensor_type @property @@ -164,5 +203,34 @@ def icon(self) -> Optional[str]: @property def unit_of_measurement(self) -> Optional[str]: - """Return the unit of measurement of this entity, if any.""" + """Return the unit.""" return self._type.unit_of_measurement + + @property + def unique_id(self) -> str: + """Return the unique_id of the sensor.""" + return f"{DOMAIN} {self._type.sensor_name}" + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, "AsusWRT")}, + "name": self._device_name, + "model": "Asus Router", + "manufacturer": "Asus", + } + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return False + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + self._handler.enable_sensor(self._type) + await super().async_added_to_hass() + + async def async_will_remove_from_hass(self): + """Call when entity is removed from hass.""" + self._handler.disable_sensor(self._type) diff --git a/homeassistant/components/asuswrt/strings.json b/homeassistant/components/asuswrt/strings.json new file mode 100644 index 00000000000000..079ee35bf95a98 --- /dev/null +++ b/homeassistant/components/asuswrt/strings.json @@ -0,0 +1,45 @@ +{ + "config": { + "step": { + "user": { + "title": "AsusWRT", + "description": "Set required parameter to connect to your router", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "name": "[%key:common::config_flow::data::name%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "ssh_key": "Path to your SSH key file (instead of password)", + "protocol": "Communication protocol to use", + "port": "[%key:common::config_flow::data::port%]", + "mode": "[%key:common::config_flow::data::mode%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_host": "[%key:common::config_flow::error::invalid_host%]", + "pwd_and_ssh": "Only provide password or SSH key file", + "pwd_or_ssh": "Please provide password or SSH key file", + "ssh_not_file": "SSH key file not found", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + }, + "options": { + "step": { + "init": { + "title": "AsusWRT Options", + "data": { + "consider_home": "Seconds to wait before considering a device away", + "track_unknown": "Track unknown / unamed devices", + "interface": "The interface that you want statistics from (e.g. eth0,eth1 etc)", + "dnsmasq": "The location in the router of the dnsmasq.leases files", + "require_ip": "Devices must have IP (for access point mode)" + } + } + } + } +} diff --git a/homeassistant/components/asuswrt/translations/en.json b/homeassistant/components/asuswrt/translations/en.json new file mode 100644 index 00000000000000..5ac87e277f4e66 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/en.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_host": "Invalid hostname or IP address", + "pwd_and_ssh": "Only provide password or SSH key file", + "pwd_or_ssh": "Please provide password or SSH key file", + "ssh_not_file": "SSH key file not found", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Mode", + "name": "Name", + "password": "Password", + "port": "Port", + "protocol": "Communication protocol to use", + "ssh_key": "Path to your SSH key file (instead of password)", + "username": "Username" + }, + "description": "Set required parameter to connect to your router", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Seconds to wait before considering a device away", + "dnsmasq": "The location in the router of the dnsmasq.leases files", + "interface": "The interface that you want statistics from (e.g. eth0,eth1 etc)", + "require_ip": "Devices must have IP (for access point mode)", + "track_unknown": "Track unknown / unamed devices" + }, + "title": "AsusWRT Options" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 06e2516633e5ab..f5f550b3073e38 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -21,6 +21,7 @@ "ambient_station", "apple_tv", "arcam_fmj", + "asuswrt", "atag", "august", "aurora", diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py new file mode 100644 index 00000000000000..7faec5d336cce9 --- /dev/null +++ b/tests/components/asuswrt/test_config_flow.py @@ -0,0 +1,296 @@ +"""Tests for the AsusWrt config flow.""" +from socket import gaierror +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant import data_entry_flow +from homeassistant.components.asuswrt.const import ( + CONF_DNSMASQ, + CONF_INTERFACE, + CONF_REQUIRE_IP, + CONF_SSH_KEY, + CONF_TRACK_UNKNOWN, + DOMAIN, +) +from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import ( + CONF_HOST, + CONF_MODE, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_USERNAME, +) + +from tests.common import MockConfigEntry + +HOST = "myrouter.asuswrt.com" +IP_ADDRESS = "192.168.1.1" +SSH_KEY = "1234" + +CONFIG_DATA = { + CONF_HOST: HOST, + CONF_PORT: 22, + CONF_PROTOCOL: "telnet", + CONF_USERNAME: "user", + CONF_PASSWORD: "pwd", + CONF_MODE: "ap", +} + + +@pytest.fixture(name="connect") +def mock_controller_connect(): + """Mock a successful connection.""" + with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: + service_mock.return_value.connection.async_connect = AsyncMock() + service_mock.return_value.is_connected = True + service_mock.return_value.connection.disconnect = AsyncMock() + yield service_mock + + +async def test_user(hass, connect): + """Test user config.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + # test with all provided + 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_USER}, + 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(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() + config_data.pop(CONF_PASSWORD) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=config_data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "pwd_or_ssh"} + + +async def test_error_both_password_ssh(hass): + """Test we abort if component is already setup.""" + config_data = CONFIG_DATA.copy() + config_data[CONF_SSH_KEY] = SSH_KEY + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=config_data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "pwd_and_ssh"} + + +async def test_error_invalid_ssh(hass): + """Test we abort if component is already setup.""" + config_data = CONFIG_DATA.copy() + config_data.pop(CONF_PASSWORD) + config_data[CONF_SSH_KEY] = SSH_KEY + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=config_data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "ssh_not_file"} + + +async def test_error_invalid_host(hass): + """Test we abort if host name is invalid.""" + with patch( + "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", + side_effect=gaierror, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=CONFIG_DATA, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_host"} + + +async def test_abort_if_already_setup(hass): + """Test we abort if component is already setup.""" + MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + ).add_to_hass(hass) + + with patch( + "homeassistant.components.asuswrt.config_flow.socket.gethostbyname", + return_value=IP_ADDRESS, + ): + # Should fail, same HOST (flow) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=CONFIG_DATA, + ) + 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.""" + flow_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + + with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: + asus_wrt.return_value.connection.async_connect = AsyncMock() + asus_wrt.return_value.is_connected = False + result = await hass.config_entries.flow.async_configure( + flow_result["flow_id"], user_input=CONFIG_DATA + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} + + with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: + asus_wrt.return_value.connection.async_connect = AsyncMock(side_effect=OSError) + result = await hass.config_entries.flow.async_configure( + flow_result["flow_id"], user_input=CONFIG_DATA + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} + + with patch("homeassistant.components.asuswrt.router.AsusWrt") as asus_wrt: + asus_wrt.return_value.connection.async_connect = AsyncMock( + side_effect=TypeError + ) + result = await hass.config_entries.flow.async_configure( + flow_result["flow_id"], user_input=CONFIG_DATA + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "unknown"} + + +async def test_options_flow(hass): + """Test config flow options.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + options={CONF_REQUIRE_IP: True}, + ) + config_entry.add_to_hass(hass) + + with patch("homeassistant.components.asuswrt.async_setup_entry", return_value=True): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + 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_CONSIDER_HOME: 20, + CONF_TRACK_UNKNOWN: True, + CONF_INTERFACE: "aaa", + CONF_DNSMASQ: "bbb", + CONF_REQUIRE_IP: False, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options[CONF_CONSIDER_HOME] == 20 + assert config_entry.options[CONF_TRACK_UNKNOWN] is True + assert config_entry.options[CONF_INTERFACE] == "aaa" + assert config_entry.options[CONF_DNSMASQ] == "bbb" + assert config_entry.options[CONF_REQUIRE_IP] is False diff --git a/tests/components/asuswrt/test_device_tracker.py b/tests/components/asuswrt/test_device_tracker.py deleted file mode 100644 index 941b0c340d6177..00000000000000 --- a/tests/components/asuswrt/test_device_tracker.py +++ /dev/null @@ -1,119 +0,0 @@ -"""The tests for the ASUSWRT device tracker platform.""" - -from unittest.mock import AsyncMock, patch - -from homeassistant.components.asuswrt import ( - CONF_DNSMASQ, - CONF_INTERFACE, - DATA_ASUSWRT, - DOMAIN, -) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.setup import async_setup_component - - -async def test_password_or_pub_key_required(hass): - """Test creating an AsusWRT scanner without a pass or pubkey.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock() - AsusWrt().is_connected = False - result = await async_setup_component( - hass, DOMAIN, {DOMAIN: {CONF_HOST: "fake_host", CONF_USERNAME: "fake_user"}} - ) - assert not result - - -async def test_network_unreachable(hass): - """Test creating an AsusWRT scanner without a pass or pubkey.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock(side_effect=OSError) - AsusWrt().is_connected = False - result = await async_setup_component( - hass, DOMAIN, {DOMAIN: {CONF_HOST: "fake_host", CONF_USERNAME: "fake_user"}} - ) - assert result - assert hass.data.get(DATA_ASUSWRT) is None - - -async def test_get_scanner_with_password_no_pubkey(hass): - """Test creating an AsusWRT scanner with a password and no pubkey.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock() - AsusWrt().connection.async_get_connected_devices = AsyncMock(return_value={}) - result = await async_setup_component( - hass, - DOMAIN, - { - DOMAIN: { - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "4321", - CONF_DNSMASQ: "/", - } - }, - ) - assert result - assert hass.data[DATA_ASUSWRT] is not None - - -async def test_specify_non_directory_path_for_dnsmasq(hass): - """Test creating an AsusWRT scanner with a dnsmasq location which is not a valid directory.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock() - AsusWrt().is_connected = False - result = await async_setup_component( - hass, - DOMAIN, - { - DOMAIN: { - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "4321", - CONF_DNSMASQ: 1234, - } - }, - ) - assert not result - - -async def test_interface(hass): - """Test creating an AsusWRT scanner using interface eth1.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock() - AsusWrt().connection.async_get_connected_devices = AsyncMock(return_value={}) - result = await async_setup_component( - hass, - DOMAIN, - { - DOMAIN: { - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "4321", - CONF_DNSMASQ: "/", - CONF_INTERFACE: "eth1", - } - }, - ) - assert result - assert hass.data[DATA_ASUSWRT] is not None - - -async def test_no_interface(hass): - """Test creating an AsusWRT scanner using no interface.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock() - AsusWrt().is_connected = False - result = await async_setup_component( - hass, - DOMAIN, - { - DOMAIN: { - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "4321", - CONF_DNSMASQ: "/", - CONF_INTERFACE: None, - } - }, - ) - assert not result diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 69c70c409d5d41..994111370fda88 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -1,71 +1,150 @@ -"""The tests for the AsusWrt sensor platform.""" - +"""Tests for the AsusWrt sensor.""" +from datetime import timedelta from unittest.mock import AsyncMock, patch from aioasuswrt.asuswrt import Device +import pytest -from homeassistant.components import sensor -from homeassistant.components.asuswrt import ( - CONF_DNSMASQ, - CONF_INTERFACE, +from homeassistant.components import device_tracker, sensor +from homeassistant.components.asuswrt.const import DOMAIN +from homeassistant.components.asuswrt.sensor import _SensorTypes +from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME +from homeassistant.const import ( + CONF_HOST, CONF_MODE, + CONF_PASSWORD, CONF_PORT, CONF_PROTOCOL, - CONF_SENSORS, - DOMAIN, + CONF_USERNAME, + STATE_HOME, + STATE_NOT_HOME, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component - -VALID_CONFIG_ROUTER_SSH = { - DOMAIN: { - CONF_DNSMASQ: "/", - CONF_HOST: "fake_host", - CONF_INTERFACE: "eth0", - CONF_MODE: "router", - CONF_PORT: "22", - CONF_PROTOCOL: "ssh", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "fake_pass", - CONF_SENSORS: [ - "devices", - "download_speed", - "download", - "upload_speed", - "upload", - ], - } +from homeassistant.util.dt import utcnow + +from tests.common import MockConfigEntry, async_fire_time_changed + +HOST = "myrouter.asuswrt.com" +IP_ADDRESS = "192.168.1.1" + +CONFIG_DATA = { + CONF_HOST: HOST, + CONF_PORT: 22, + CONF_PROTOCOL: "ssh", + CONF_USERNAME: "user", + CONF_PASSWORD: "pwd", + CONF_MODE: "router", } MOCK_DEVICES = { "a1:b1:c1:d1:e1:f1": Device("a1:b1:c1:d1:e1:f1", "192.168.1.2", "Test"), "a2:b2:c2:d2:e2:f2": Device("a2:b2:c2:d2:e2:f2", "192.168.1.3", "TestTwo"), - "a3:b3:c3:d3:e3:f3": Device("a3:b3:c3:d3:e3:f3", "192.168.1.4", "TestThree"), } MOCK_BYTES_TOTAL = [60000000000, 50000000000] MOCK_CURRENT_TRANSFER_RATES = [20000000, 10000000] -async def test_sensors(hass: HomeAssistant, mock_device_tracker_conf): - """Test creating an AsusWRT sensor.""" - with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = AsyncMock() - AsusWrt().async_get_connected_devices = AsyncMock(return_value=MOCK_DEVICES) - AsusWrt().async_get_bytes_total = AsyncMock(return_value=MOCK_BYTES_TOTAL) - AsusWrt().async_get_current_transfer_rates = AsyncMock( +@pytest.fixture(name="connect") +def mock_controller_connect(): + """Mock a successful connection.""" + with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: + service_mock.return_value.connection.async_connect = AsyncMock() + service_mock.return_value.is_connected = True + service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.async_get_connected_devices = AsyncMock( + return_value=MOCK_DEVICES + ) + service_mock.return_value.async_get_bytes_total = AsyncMock( + return_value=MOCK_BYTES_TOTAL + ) + service_mock.return_value.async_get_current_transfer_rates = AsyncMock( return_value=MOCK_CURRENT_TRANSFER_RATES ) + yield service_mock - assert await async_setup_component(hass, DOMAIN, VALID_CONFIG_ROUTER_SSH) - await hass.async_block_till_done() - assert ( - hass.states.get(f"{sensor.DOMAIN}.asuswrt_devices_connected").state == "3" - ) - assert ( - hass.states.get(f"{sensor.DOMAIN}.asuswrt_download_speed").state == "160.0" - ) - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download").state == "60.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload_speed").state == "80.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload").state == "50.0" +async def test_sensors(hass, connect): + """Test creating an AsusWRT sensor.""" + entity_reg = await hass.helpers.entity_registry.async_get_registry() + + # Pre-enable the status sensor + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{DOMAIN} {_SensorTypes(_SensorTypes.DEVICES).sensor_name}", + suggested_object_id="asuswrt_connected_devices", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{DOMAIN} {_SensorTypes(_SensorTypes.DOWNLOAD_SPEED).sensor_name}", + suggested_object_id="asuswrt_download_speed", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{DOMAIN} {_SensorTypes(_SensorTypes.DOWNLOAD).sensor_name}", + suggested_object_id="asuswrt_download", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{DOMAIN} {_SensorTypes(_SensorTypes.UPLOAD_SPEED).sensor_name}", + suggested_object_id="asuswrt_upload_speed", + disabled_by=None, + ) + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{DOMAIN} {_SensorTypes(_SensorTypes.UPLOAD).sensor_name}", + suggested_object_id="asuswrt_upload", + disabled_by=None, + ) + + # init config entry + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + options={CONF_CONSIDER_HOME: 60}, + ) + config_entry.add_to_hass(hass) + + # initial devices setup + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME + assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME + assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_connected_devices").state == "2" + assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download_speed").state == "160.0" + assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download").state == "60.0" + assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload_speed").state == "80.0" + assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload").state == "50.0" + + # add one device and remove another + MOCK_DEVICES.pop("a1:b1:c1:d1:e1:f1") + MOCK_DEVICES["a3:b3:c3:d3:e3:f3"] = Device( + "a3:b3:c3:d3:e3:f3", "192.168.1.4", "TestThree" + ) + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + # consider home option set, all devices still home + assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME + assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME + assert hass.states.get(f"{device_tracker.DOMAIN}.testthree").state == STATE_HOME + assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_connected_devices").state == "2" + + hass.config_entries.async_update_entry( + config_entry, options={CONF_CONSIDER_HOME: 0} + ) + await hass.async_block_till_done() + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + # consider home option not set, device "test" not home + assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_NOT_HOME From 2f40f44670b603caa63abe07e27126ca18a5aa3b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 13 Feb 2021 09:30:55 -1000 Subject: [PATCH 0450/1818] Update HAP-python to 3.3.0 for homekit (#46497) Changes: https://github.com/ikalchev/HAP-python/compare/v3.2.0...v3.3.0 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index 23b43958848b2a..acc61408a4855c 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.2.0", + "HAP-python==3.3.0", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index dbad80603d440a..9ee4663bd17b78 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -14,7 +14,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.2.0 +HAP-python==3.3.0 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3fa94bd3117acb..737b3e4a989d91 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -4,7 +4,7 @@ -r requirements_test.txt # homeassistant.components.homekit -HAP-python==3.2.0 +HAP-python==3.3.0 # homeassistant.components.flick_electric PyFlick==0.0.2 From eecf07d7dfe8bc07ae9cdad1715b9b80d2678932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Sat, 13 Feb 2021 21:53:28 +0100 Subject: [PATCH 0451/1818] Add AEMET OpenData integration (#45074) Co-authored-by: Martin Hjelmare --- .coveragerc | 2 + CODEOWNERS | 1 + homeassistant/components/aemet/__init__.py | 61 + .../components/aemet/abstract_aemet_sensor.py | 57 + homeassistant/components/aemet/config_flow.py | 58 + homeassistant/components/aemet/const.py | 326 ++++ homeassistant/components/aemet/manifest.json | 8 + homeassistant/components/aemet/sensor.py | 114 ++ homeassistant/components/aemet/strings.json | 22 + .../components/aemet/translations/en.json | 22 + homeassistant/components/aemet/weather.py | 113 ++ .../aemet/weather_update_coordinator.py | 637 ++++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/aemet/__init__.py | 1 + tests/components/aemet/test_config_flow.py | 100 ++ tests/components/aemet/test_init.py | 44 + tests/components/aemet/test_sensor.py | 137 ++ tests/components/aemet/test_weather.py | 61 + tests/components/aemet/util.py | 93 ++ tests/fixtures/aemet/station-3195-data.json | 369 +++++ tests/fixtures/aemet/station-3195.json | 6 + tests/fixtures/aemet/station-list-data.json | 42 + tests/fixtures/aemet/station-list.json | 6 + .../aemet/town-28065-forecast-daily-data.json | 625 ++++++++ .../aemet/town-28065-forecast-daily.json | 6 + .../town-28065-forecast-hourly-data.json | 1416 +++++++++++++++++ .../aemet/town-28065-forecast-hourly.json | 6 + tests/fixtures/aemet/town-id28065.json | 15 + tests/fixtures/aemet/town-list.json | 43 + 31 files changed, 4398 insertions(+) create mode 100644 homeassistant/components/aemet/__init__.py create mode 100644 homeassistant/components/aemet/abstract_aemet_sensor.py create mode 100644 homeassistant/components/aemet/config_flow.py create mode 100644 homeassistant/components/aemet/const.py create mode 100644 homeassistant/components/aemet/manifest.json create mode 100644 homeassistant/components/aemet/sensor.py create mode 100644 homeassistant/components/aemet/strings.json create mode 100644 homeassistant/components/aemet/translations/en.json create mode 100644 homeassistant/components/aemet/weather.py create mode 100644 homeassistant/components/aemet/weather_update_coordinator.py create mode 100644 tests/components/aemet/__init__.py create mode 100644 tests/components/aemet/test_config_flow.py create mode 100644 tests/components/aemet/test_init.py create mode 100644 tests/components/aemet/test_sensor.py create mode 100644 tests/components/aemet/test_weather.py create mode 100644 tests/components/aemet/util.py create mode 100644 tests/fixtures/aemet/station-3195-data.json create mode 100644 tests/fixtures/aemet/station-3195.json create mode 100644 tests/fixtures/aemet/station-list-data.json create mode 100644 tests/fixtures/aemet/station-list.json create mode 100644 tests/fixtures/aemet/town-28065-forecast-daily-data.json create mode 100644 tests/fixtures/aemet/town-28065-forecast-daily.json create mode 100644 tests/fixtures/aemet/town-28065-forecast-hourly-data.json create mode 100644 tests/fixtures/aemet/town-28065-forecast-hourly.json create mode 100644 tests/fixtures/aemet/town-id28065.json create mode 100644 tests/fixtures/aemet/town-list.json diff --git a/.coveragerc b/.coveragerc index 3bf3fa10947f78..8c7d4b3393de06 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,6 +23,8 @@ omit = homeassistant/components/adguard/sensor.py homeassistant/components/adguard/switch.py homeassistant/components/ads/* + homeassistant/components/aemet/abstract_aemet_sensor.py + homeassistant/components/aemet/weather_update_coordinator.py homeassistant/components/aftership/sensor.py homeassistant/components/agent_dvr/__init__.py homeassistant/components/agent_dvr/alarm_control_panel.py diff --git a/CODEOWNERS b/CODEOWNERS index 3d7ff5f79a502f..6e3bc1feb87df7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -24,6 +24,7 @@ homeassistant/components/accuweather/* @bieniu homeassistant/components/acmeda/* @atmurray homeassistant/components/adguard/* @frenck homeassistant/components/advantage_air/* @Bre77 +homeassistant/components/aemet/* @noltari homeassistant/components/agent_dvr/* @ispysoftware homeassistant/components/airly/* @bieniu homeassistant/components/airnow/* @asymworks diff --git a/homeassistant/components/aemet/__init__.py b/homeassistant/components/aemet/__init__.py new file mode 100644 index 00000000000000..58b1a3b10f0399 --- /dev/null +++ b/homeassistant/components/aemet/__init__.py @@ -0,0 +1,61 @@ +"""The AEMET OpenData component.""" +import asyncio +import logging + +from aemet_opendata.interface import AEMET + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.core import HomeAssistant + +from .const import COMPONENTS, DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR +from .weather_update_coordinator import WeatherUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: dict) -> bool: + """Set up the AEMET OpenData component.""" + hass.data.setdefault(DOMAIN, {}) + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): + """Set up AEMET OpenData as config entry.""" + name = config_entry.data[CONF_NAME] + api_key = config_entry.data[CONF_API_KEY] + latitude = config_entry.data[CONF_LATITUDE] + longitude = config_entry.data[CONF_LONGITUDE] + + aemet = AEMET(api_key) + weather_coordinator = WeatherUpdateCoordinator(hass, aemet, latitude, longitude) + + await weather_coordinator.async_refresh() + + hass.data[DOMAIN][config_entry.entry_id] = { + ENTRY_NAME: name, + ENTRY_WEATHER_COORDINATOR: weather_coordinator, + } + + for component in COMPONENTS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in COMPONENTS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/aemet/abstract_aemet_sensor.py b/homeassistant/components/aemet/abstract_aemet_sensor.py new file mode 100644 index 00000000000000..6b7c3c69fee30a --- /dev/null +++ b/homeassistant/components/aemet/abstract_aemet_sensor.py @@ -0,0 +1,57 @@ +"""Abstraction form AEMET OpenData sensors.""" +from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import ATTRIBUTION, SENSOR_DEVICE_CLASS, SENSOR_NAME, SENSOR_UNIT +from .weather_update_coordinator import WeatherUpdateCoordinator + + +class AbstractAemetSensor(CoordinatorEntity): + """Abstract class for an AEMET OpenData sensor.""" + + def __init__( + self, + name, + unique_id, + sensor_type, + sensor_configuration, + coordinator: WeatherUpdateCoordinator, + ): + """Initialize the sensor.""" + super().__init__(coordinator) + self._name = name + self._unique_id = unique_id + self._sensor_type = sensor_type + self._sensor_name = sensor_configuration[SENSOR_NAME] + self._unit_of_measurement = sensor_configuration.get(SENSOR_UNIT) + self._device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS) + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._name} {self._sensor_name}" + + @property + def unique_id(self): + """Return a unique_id for this entity.""" + return self._unique_id + + @property + def attribution(self): + """Return the attribution.""" + return ATTRIBUTION + + @property + def device_class(self): + """Return the device_class.""" + return self._device_class + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/aemet/config_flow.py b/homeassistant/components/aemet/config_flow.py new file mode 100644 index 00000000000000..27f389660a8f9c --- /dev/null +++ b/homeassistant/components/aemet/config_flow.py @@ -0,0 +1,58 @@ +"""Config flow for AEMET OpenData.""" +from aemet_opendata import AEMET +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +import homeassistant.helpers.config_validation as cv + +from .const import DEFAULT_NAME +from .const import DOMAIN # pylint:disable=unused-import + + +class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow for AEMET OpenData.""" + + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + + if user_input is not None: + latitude = user_input[CONF_LATITUDE] + longitude = user_input[CONF_LONGITUDE] + + await self.async_set_unique_id(f"{latitude}-{longitude}") + self._abort_if_unique_id_configured() + + api_online = await _is_aemet_api_online(self.hass, user_input[CONF_API_KEY]) + if not api_online: + errors["base"] = "invalid_api_key" + + if not errors: + return self.async_create_entry( + title=user_input[CONF_NAME], data=user_input + ) + + schema = vol.Schema( + { + vol.Required(CONF_API_KEY): str, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, + vol.Optional( + CONF_LATITUDE, default=self.hass.config.latitude + ): cv.latitude, + vol.Optional( + CONF_LONGITUDE, default=self.hass.config.longitude + ): cv.longitude, + } + ) + + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + +async def _is_aemet_api_online(hass, api_key): + aemet = AEMET(api_key) + return await hass.async_add_executor_job( + aemet.get_conventional_observation_stations, False + ) diff --git a/homeassistant/components/aemet/const.py b/homeassistant/components/aemet/const.py new file mode 100644 index 00000000000000..13b9d944bf07dc --- /dev/null +++ b/homeassistant/components/aemet/const.py @@ -0,0 +1,326 @@ +"""Constant values for the AEMET OpenData component.""" + +from homeassistant.components.weather import ( + ATTR_CONDITION_CLEAR_NIGHT, + ATTR_CONDITION_CLOUDY, + ATTR_CONDITION_FOG, + ATTR_CONDITION_LIGHTNING, + ATTR_CONDITION_LIGHTNING_RAINY, + ATTR_CONDITION_PARTLYCLOUDY, + ATTR_CONDITION_POURING, + ATTR_CONDITION_RAINY, + ATTR_CONDITION_SNOWY, + ATTR_CONDITION_SUNNY, + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, +) +from homeassistant.const import ( + DEGREE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, + PERCENTAGE, + PRECIPITATION_MILLIMETERS_PER_HOUR, + PRESSURE_HPA, + SPEED_KILOMETERS_PER_HOUR, + TEMP_CELSIUS, +) + +ATTRIBUTION = "Powered by AEMET OpenData" +COMPONENTS = ["sensor", "weather"] +DEFAULT_NAME = "AEMET" +DOMAIN = "aemet" +ENTRY_NAME = "name" +ENTRY_WEATHER_COORDINATOR = "weather_coordinator" +UPDATE_LISTENER = "update_listener" +SENSOR_NAME = "sensor_name" +SENSOR_UNIT = "sensor_unit" +SENSOR_DEVICE_CLASS = "sensor_device_class" + +ATTR_API_CONDITION = "condition" +ATTR_API_FORECAST_DAILY = "forecast-daily" +ATTR_API_FORECAST_HOURLY = "forecast-hourly" +ATTR_API_HUMIDITY = "humidity" +ATTR_API_PRESSURE = "pressure" +ATTR_API_RAIN = "rain" +ATTR_API_RAIN_PROB = "rain-probability" +ATTR_API_SNOW = "snow" +ATTR_API_SNOW_PROB = "snow-probability" +ATTR_API_STATION_ID = "station-id" +ATTR_API_STATION_NAME = "station-name" +ATTR_API_STATION_TIMESTAMP = "station-timestamp" +ATTR_API_STORM_PROB = "storm-probability" +ATTR_API_TEMPERATURE = "temperature" +ATTR_API_TEMPERATURE_FEELING = "temperature-feeling" +ATTR_API_TOWN_ID = "town-id" +ATTR_API_TOWN_NAME = "town-name" +ATTR_API_TOWN_TIMESTAMP = "town-timestamp" +ATTR_API_WIND_BEARING = "wind-bearing" +ATTR_API_WIND_MAX_SPEED = "wind-max-speed" +ATTR_API_WIND_SPEED = "wind-speed" + +CONDITIONS_MAP = { + ATTR_CONDITION_CLEAR_NIGHT: { + "11n", # Despejado (de noche) + }, + ATTR_CONDITION_CLOUDY: { + "14", # Nuboso + "14n", # Nuboso (de noche) + "15", # Muy nuboso + "15n", # Muy nuboso (de noche) + "16", # Cubierto + "16n", # Cubierto (de noche) + "17", # Nubes altas + "17n", # Nubes altas (de noche) + }, + ATTR_CONDITION_FOG: { + "81", # Niebla + "81n", # Niebla (de noche) + "82", # Bruma - Neblina + "82n", # Bruma - Neblina (de noche) + }, + ATTR_CONDITION_LIGHTNING: { + "51", # Intervalos nubosos con tormenta + "51n", # Intervalos nubosos con tormenta (de noche) + "52", # Nuboso con tormenta + "52n", # Nuboso con tormenta (de noche) + "53", # Muy nuboso con tormenta + "53n", # Muy nuboso con tormenta (de noche) + "54", # Cubierto con tormenta + "54n", # Cubierto con tormenta (de noche) + }, + ATTR_CONDITION_LIGHTNING_RAINY: { + "61", # Intervalos nubosos con tormenta y lluvia escasa + "61n", # Intervalos nubosos con tormenta y lluvia escasa (de noche) + "62", # Nuboso con tormenta y lluvia escasa + "62n", # Nuboso con tormenta y lluvia escasa (de noche) + "63", # Muy nuboso con tormenta y lluvia escasa + "63n", # Muy nuboso con tormenta y lluvia escasa (de noche) + "64", # Cubierto con tormenta y lluvia escasa + "64n", # Cubierto con tormenta y lluvia escasa (de noche) + }, + ATTR_CONDITION_PARTLYCLOUDY: { + "12", # Poco nuboso + "12n", # Poco nuboso (de noche) + "13", # Intervalos nubosos + "13n", # Intervalos nubosos (de noche) + }, + ATTR_CONDITION_POURING: { + "27", # Chubascos + "27n", # Chubascos (de noche) + }, + ATTR_CONDITION_RAINY: { + "23", # Intervalos nubosos con lluvia + "23n", # Intervalos nubosos con lluvia (de noche) + "24", # Nuboso con lluvia + "24n", # Nuboso con lluvia (de noche) + "25", # Muy nuboso con lluvia + "25n", # Muy nuboso con lluvia (de noche) + "26", # Cubierto con lluvia + "26n", # Cubierto con lluvia (de noche) + "43", # Intervalos nubosos con lluvia escasa + "43n", # Intervalos nubosos con lluvia escasa (de noche) + "44", # Nuboso con lluvia escasa + "44n", # Nuboso con lluvia escasa (de noche) + "45", # Muy nuboso con lluvia escasa + "45n", # Muy nuboso con lluvia escasa (de noche) + "46", # Cubierto con lluvia escasa + "46n", # Cubierto con lluvia escasa (de noche) + }, + ATTR_CONDITION_SNOWY: { + "33", # Intervalos nubosos con nieve + "33n", # Intervalos nubosos con nieve (de noche) + "34", # Nuboso con nieve + "34n", # Nuboso con nieve (de noche) + "35", # Muy nuboso con nieve + "35n", # Muy nuboso con nieve (de noche) + "36", # Cubierto con nieve + "36n", # Cubierto con nieve (de noche) + "71", # Intervalos nubosos con nieve escasa + "71n", # Intervalos nubosos con nieve escasa (de noche) + "72", # Nuboso con nieve escasa + "72n", # Nuboso con nieve escasa (de noche) + "73", # Muy nuboso con nieve escasa + "73n", # Muy nuboso con nieve escasa (de noche) + "74", # Cubierto con nieve escasa + "74n", # Cubierto con nieve escasa (de noche) + }, + ATTR_CONDITION_SUNNY: { + "11", # Despejado + }, +} + +FORECAST_MONITORED_CONDITIONS = [ + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, +] +MONITORED_CONDITIONS = [ + ATTR_API_CONDITION, + ATTR_API_HUMIDITY, + ATTR_API_PRESSURE, + ATTR_API_RAIN, + ATTR_API_RAIN_PROB, + ATTR_API_SNOW, + ATTR_API_SNOW_PROB, + ATTR_API_STATION_ID, + ATTR_API_STATION_NAME, + ATTR_API_STATION_TIMESTAMP, + ATTR_API_STORM_PROB, + ATTR_API_TEMPERATURE, + ATTR_API_TEMPERATURE_FEELING, + ATTR_API_TOWN_ID, + ATTR_API_TOWN_NAME, + ATTR_API_TOWN_TIMESTAMP, + ATTR_API_WIND_BEARING, + ATTR_API_WIND_MAX_SPEED, + ATTR_API_WIND_SPEED, +] + +FORECAST_MODE_DAILY = "daily" +FORECAST_MODE_HOURLY = "hourly" +FORECAST_MODES = [ + FORECAST_MODE_DAILY, + FORECAST_MODE_HOURLY, +] +FORECAST_MODE_ATTR_API = { + FORECAST_MODE_DAILY: ATTR_API_FORECAST_DAILY, + FORECAST_MODE_HOURLY: ATTR_API_FORECAST_HOURLY, +} + +FORECAST_SENSOR_TYPES = { + ATTR_FORECAST_CONDITION: { + SENSOR_NAME: "Condition", + }, + ATTR_FORECAST_PRECIPITATION: { + SENSOR_NAME: "Precipitation", + SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR, + }, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: { + SENSOR_NAME: "Precipitation probability", + SENSOR_UNIT: PERCENTAGE, + }, + ATTR_FORECAST_TEMP: { + SENSOR_NAME: "Temperature", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, + ATTR_FORECAST_TEMP_LOW: { + SENSOR_NAME: "Temperature Low", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, + ATTR_FORECAST_TIME: { + SENSOR_NAME: "Time", + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, + ATTR_FORECAST_WIND_BEARING: { + SENSOR_NAME: "Wind bearing", + SENSOR_UNIT: DEGREE, + }, + ATTR_FORECAST_WIND_SPEED: { + SENSOR_NAME: "Wind speed", + SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR, + }, +} +WEATHER_SENSOR_TYPES = { + ATTR_API_CONDITION: { + SENSOR_NAME: "Condition", + }, + ATTR_API_HUMIDITY: { + SENSOR_NAME: "Humidity", + SENSOR_UNIT: PERCENTAGE, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY, + }, + ATTR_API_PRESSURE: { + SENSOR_NAME: "Pressure", + SENSOR_UNIT: PRESSURE_HPA, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, + }, + ATTR_API_RAIN: { + SENSOR_NAME: "Rain", + SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR, + }, + ATTR_API_RAIN_PROB: { + SENSOR_NAME: "Rain probability", + SENSOR_UNIT: PERCENTAGE, + }, + ATTR_API_SNOW: { + SENSOR_NAME: "Snow", + SENSOR_UNIT: PRECIPITATION_MILLIMETERS_PER_HOUR, + }, + ATTR_API_SNOW_PROB: { + SENSOR_NAME: "Snow probability", + SENSOR_UNIT: PERCENTAGE, + }, + ATTR_API_STATION_ID: { + SENSOR_NAME: "Station ID", + }, + ATTR_API_STATION_NAME: { + SENSOR_NAME: "Station name", + }, + ATTR_API_STATION_TIMESTAMP: { + SENSOR_NAME: "Station timestamp", + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, + ATTR_API_STORM_PROB: { + SENSOR_NAME: "Storm probability", + SENSOR_UNIT: PERCENTAGE, + }, + ATTR_API_TEMPERATURE: { + SENSOR_NAME: "Temperature", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, + ATTR_API_TEMPERATURE_FEELING: { + SENSOR_NAME: "Temperature feeling", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, + ATTR_API_TOWN_ID: { + SENSOR_NAME: "Town ID", + }, + ATTR_API_TOWN_NAME: { + SENSOR_NAME: "Town name", + }, + ATTR_API_TOWN_TIMESTAMP: { + SENSOR_NAME: "Town timestamp", + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, + ATTR_API_WIND_BEARING: { + SENSOR_NAME: "Wind bearing", + SENSOR_UNIT: DEGREE, + }, + ATTR_API_WIND_MAX_SPEED: { + SENSOR_NAME: "Wind max speed", + SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR, + }, + ATTR_API_WIND_SPEED: { + SENSOR_NAME: "Wind speed", + SENSOR_UNIT: SPEED_KILOMETERS_PER_HOUR, + }, +} + +WIND_BEARING_MAP = { + "C": None, + "N": 0.0, + "NE": 45.0, + "E": 90.0, + "SE": 135.0, + "S": 180.0, + "SO": 225.0, + "O": 270.0, + "NO": 315.0, +} diff --git a/homeassistant/components/aemet/manifest.json b/homeassistant/components/aemet/manifest.json new file mode 100644 index 00000000000000..eb5dc295f297ed --- /dev/null +++ b/homeassistant/components/aemet/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "aemet", + "name": "AEMET OpenData", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/aemet", + "requirements": ["AEMET-OpenData==0.1.8"], + "codeowners": ["@noltari"] +} diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py new file mode 100644 index 00000000000000..b57de1ce89029e --- /dev/null +++ b/homeassistant/components/aemet/sensor.py @@ -0,0 +1,114 @@ +"""Support for the AEMET OpenData service.""" +from .abstract_aemet_sensor import AbstractAemetSensor +from .const import ( + DOMAIN, + ENTRY_NAME, + ENTRY_WEATHER_COORDINATOR, + FORECAST_MODE_ATTR_API, + FORECAST_MODE_DAILY, + FORECAST_MODES, + FORECAST_MONITORED_CONDITIONS, + FORECAST_SENSOR_TYPES, + MONITORED_CONDITIONS, + WEATHER_SENSOR_TYPES, +) +from .weather_update_coordinator import WeatherUpdateCoordinator + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up AEMET OpenData sensor entities based on a config entry.""" + domain_data = hass.data[DOMAIN][config_entry.entry_id] + name = domain_data[ENTRY_NAME] + weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR] + + weather_sensor_types = WEATHER_SENSOR_TYPES + forecast_sensor_types = FORECAST_SENSOR_TYPES + + entities = [] + for sensor_type in MONITORED_CONDITIONS: + unique_id = f"{config_entry.unique_id}-{sensor_type}" + entities.append( + AemetSensor( + name, + unique_id, + sensor_type, + weather_sensor_types[sensor_type], + weather_coordinator, + ) + ) + + for mode in FORECAST_MODES: + name = f"{domain_data[ENTRY_NAME]} {mode}" + + for sensor_type in FORECAST_MONITORED_CONDITIONS: + unique_id = f"{config_entry.unique_id}-forecast-{mode}-{sensor_type}" + entities.append( + AemetForecastSensor( + f"{name} Forecast", + unique_id, + sensor_type, + forecast_sensor_types[sensor_type], + weather_coordinator, + mode, + ) + ) + + async_add_entities(entities) + + +class AemetSensor(AbstractAemetSensor): + """Implementation of an AEMET OpenData sensor.""" + + def __init__( + self, + name, + unique_id, + sensor_type, + sensor_configuration, + weather_coordinator: WeatherUpdateCoordinator, + ): + """Initialize the sensor.""" + super().__init__( + name, unique_id, sensor_type, sensor_configuration, weather_coordinator + ) + self._weather_coordinator = weather_coordinator + + @property + def state(self): + """Return the state of the device.""" + return self._weather_coordinator.data.get(self._sensor_type) + + +class AemetForecastSensor(AbstractAemetSensor): + """Implementation of an AEMET OpenData forecast sensor.""" + + def __init__( + self, + name, + unique_id, + sensor_type, + sensor_configuration, + weather_coordinator: WeatherUpdateCoordinator, + forecast_mode, + ): + """Initialize the sensor.""" + super().__init__( + name, unique_id, sensor_type, sensor_configuration, weather_coordinator + ) + self._weather_coordinator = weather_coordinator + self._forecast_mode = forecast_mode + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._forecast_mode == FORECAST_MODE_DAILY + + @property + def state(self): + """Return the state of the device.""" + forecasts = self._weather_coordinator.data.get( + FORECAST_MODE_ATTR_API[self._forecast_mode] + ) + if forecasts: + return forecasts[0].get(self._sensor_type) + return None diff --git a/homeassistant/components/aemet/strings.json b/homeassistant/components/aemet/strings.json new file mode 100644 index 00000000000000..a25a503bade514 --- /dev/null +++ b/homeassistant/components/aemet/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" + }, + "error": { + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]" + }, + "step": { + "user": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]", + "name": "Name of the integration" + }, + "description": "Set up AEMET OpenData integration. To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} diff --git a/homeassistant/components/aemet/translations/en.json b/homeassistant/components/aemet/translations/en.json new file mode 100644 index 00000000000000..60e7f5f2ec22cb --- /dev/null +++ b/homeassistant/components/aemet/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Location is already configured" + }, + "error": { + "invalid_api_key": "Invalid API key" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name of the integration" + }, + "description": "Set up AEMET OpenData integration. To generate API key go to https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/weather.py b/homeassistant/components/aemet/weather.py new file mode 100644 index 00000000000000..e54a297cc091e3 --- /dev/null +++ b/homeassistant/components/aemet/weather.py @@ -0,0 +1,113 @@ +"""Support for the AEMET OpenData service.""" +from homeassistant.components.weather import WeatherEntity +from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import ( + ATTR_API_CONDITION, + ATTR_API_HUMIDITY, + ATTR_API_PRESSURE, + ATTR_API_TEMPERATURE, + ATTR_API_WIND_BEARING, + ATTR_API_WIND_SPEED, + ATTRIBUTION, + DOMAIN, + ENTRY_NAME, + ENTRY_WEATHER_COORDINATOR, + FORECAST_MODE_ATTR_API, + FORECAST_MODE_DAILY, + FORECAST_MODES, +) +from .weather_update_coordinator import WeatherUpdateCoordinator + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up AEMET OpenData weather entity based on a config entry.""" + domain_data = hass.data[DOMAIN][config_entry.entry_id] + weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR] + + entities = [] + for mode in FORECAST_MODES: + name = f"{domain_data[ENTRY_NAME]} {mode}" + unique_id = f"{config_entry.unique_id} {mode}" + entities.append(AemetWeather(name, unique_id, weather_coordinator, mode)) + + if entities: + async_add_entities(entities, False) + + +class AemetWeather(CoordinatorEntity, WeatherEntity): + """Implementation of an AEMET OpenData sensor.""" + + def __init__( + self, + name, + unique_id, + coordinator: WeatherUpdateCoordinator, + forecast_mode, + ): + """Initialize the sensor.""" + super().__init__(coordinator) + self._name = name + self._unique_id = unique_id + self._forecast_mode = forecast_mode + + @property + def attribution(self): + """Return the attribution.""" + return ATTRIBUTION + + @property + def condition(self): + """Return the current condition.""" + return self.coordinator.data[ATTR_API_CONDITION] + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._forecast_mode == FORECAST_MODE_DAILY + + @property + def forecast(self): + """Return the forecast array.""" + return self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]] + + @property + def humidity(self): + """Return the humidity.""" + return self.coordinator.data[ATTR_API_HUMIDITY] + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def pressure(self): + """Return the pressure.""" + return self.coordinator.data[ATTR_API_PRESSURE] + + @property + def temperature(self): + """Return the temperature.""" + return self.coordinator.data[ATTR_API_TEMPERATURE] + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_CELSIUS + + @property + def unique_id(self): + """Return a unique_id for this entity.""" + return self._unique_id + + @property + def wind_bearing(self): + """Return the temperature.""" + return self.coordinator.data[ATTR_API_WIND_BEARING] + + @property + def wind_speed(self): + """Return the temperature.""" + return self.coordinator.data[ATTR_API_WIND_SPEED] diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py new file mode 100644 index 00000000000000..6a06b1dd39147a --- /dev/null +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -0,0 +1,637 @@ +"""Weather data coordinator for the AEMET OpenData service.""" +from dataclasses import dataclass, field +from datetime import timedelta +import logging + +from aemet_opendata.const import ( + AEMET_ATTR_DATE, + AEMET_ATTR_DAY, + AEMET_ATTR_DIRECTION, + AEMET_ATTR_ELABORATED, + AEMET_ATTR_FORECAST, + AEMET_ATTR_HUMIDITY, + AEMET_ATTR_ID, + AEMET_ATTR_IDEMA, + AEMET_ATTR_MAX, + AEMET_ATTR_MIN, + AEMET_ATTR_NAME, + AEMET_ATTR_PRECIPITATION, + AEMET_ATTR_PRECIPITATION_PROBABILITY, + AEMET_ATTR_SKY_STATE, + AEMET_ATTR_SNOW, + AEMET_ATTR_SNOW_PROBABILITY, + AEMET_ATTR_SPEED, + AEMET_ATTR_STATION_DATE, + AEMET_ATTR_STATION_HUMIDITY, + AEMET_ATTR_STATION_LOCATION, + AEMET_ATTR_STATION_PRESSURE_SEA, + AEMET_ATTR_STATION_TEMPERATURE, + AEMET_ATTR_STORM_PROBABILITY, + AEMET_ATTR_TEMPERATURE, + AEMET_ATTR_TEMPERATURE_FEELING, + AEMET_ATTR_WIND, + AEMET_ATTR_WIND_GUST, + ATTR_DATA, +) +from aemet_opendata.helpers import ( + get_forecast_day_value, + get_forecast_hour_value, + get_forecast_interval_value, +) +import async_timeout + +from homeassistant.components.weather import ( + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, +) +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.util import dt as dt_util + +from .const import ( + ATTR_API_CONDITION, + ATTR_API_FORECAST_DAILY, + ATTR_API_FORECAST_HOURLY, + ATTR_API_HUMIDITY, + ATTR_API_PRESSURE, + ATTR_API_RAIN, + ATTR_API_RAIN_PROB, + ATTR_API_SNOW, + ATTR_API_SNOW_PROB, + ATTR_API_STATION_ID, + ATTR_API_STATION_NAME, + ATTR_API_STATION_TIMESTAMP, + ATTR_API_STORM_PROB, + ATTR_API_TEMPERATURE, + ATTR_API_TEMPERATURE_FEELING, + ATTR_API_TOWN_ID, + ATTR_API_TOWN_NAME, + ATTR_API_TOWN_TIMESTAMP, + ATTR_API_WIND_BEARING, + ATTR_API_WIND_MAX_SPEED, + ATTR_API_WIND_SPEED, + CONDITIONS_MAP, + DOMAIN, + WIND_BEARING_MAP, +) + +_LOGGER = logging.getLogger(__name__) + +WEATHER_UPDATE_INTERVAL = timedelta(minutes=10) + + +def format_condition(condition: str) -> str: + """Return condition from dict CONDITIONS_MAP.""" + for key, value in CONDITIONS_MAP.items(): + if condition in value: + return key + _LOGGER.error('condition "%s" not found in CONDITIONS_MAP', condition) + return condition + + +def format_float(value) -> float: + """Try converting string to float.""" + try: + return float(value) + except ValueError: + return None + + +def format_int(value) -> int: + """Try converting string to int.""" + try: + return int(value) + except ValueError: + return None + + +class TownNotFound(UpdateFailed): + """Raised when town is not found.""" + + +class WeatherUpdateCoordinator(DataUpdateCoordinator): + """Weather data update coordinator.""" + + def __init__(self, hass, aemet, latitude, longitude): + """Initialize coordinator.""" + super().__init__( + hass, _LOGGER, name=DOMAIN, update_interval=WEATHER_UPDATE_INTERVAL + ) + + self._aemet = aemet + self._station = None + self._town = None + self._latitude = latitude + self._longitude = longitude + self._data = { + "daily": None, + "hourly": None, + "station": None, + } + + async def _async_update_data(self): + data = {} + with async_timeout.timeout(120): + weather_response = await self._get_aemet_weather() + data = self._convert_weather_response(weather_response) + return data + + async def _get_aemet_weather(self): + """Poll weather data from AEMET OpenData.""" + weather = await self.hass.async_add_executor_job(self._get_weather_and_forecast) + return weather + + def _get_weather_station(self): + if not self._station: + self._station = ( + self._aemet.get_conventional_observation_station_by_coordinates( + self._latitude, self._longitude + ) + ) + if self._station: + _LOGGER.debug( + "station found for coordinates [%s, %s]: %s", + self._latitude, + self._longitude, + self._station, + ) + if not self._station: + _LOGGER.debug( + "station not found for coordinates [%s, %s]", + self._latitude, + self._longitude, + ) + return self._station + + def _get_weather_town(self): + if not self._town: + self._town = self._aemet.get_town_by_coordinates( + self._latitude, self._longitude + ) + if self._town: + _LOGGER.debug( + "town found for coordinates [%s, %s]: %s", + self._latitude, + self._longitude, + self._town, + ) + if not self._town: + _LOGGER.error( + "town not found for coordinates [%s, %s]", + self._latitude, + self._longitude, + ) + raise TownNotFound + return self._town + + def _get_weather_and_forecast(self): + """Get weather and forecast data from AEMET OpenData.""" + + self._get_weather_town() + + daily = self._aemet.get_specific_forecast_town_daily(self._town[AEMET_ATTR_ID]) + if not daily: + _LOGGER.error( + 'error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID] + ) + + hourly = self._aemet.get_specific_forecast_town_hourly( + self._town[AEMET_ATTR_ID] + ) + if not hourly: + _LOGGER.error( + 'error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID] + ) + + station = None + if self._get_weather_station(): + station = self._aemet.get_conventional_observation_station_data( + self._station[AEMET_ATTR_IDEMA] + ) + if not station: + _LOGGER.error( + 'error fetching data for station "%s"', + self._station[AEMET_ATTR_IDEMA], + ) + + if daily: + self._data["daily"] = daily + if hourly: + self._data["hourly"] = hourly + if station: + self._data["station"] = station + + return AemetWeather( + self._data["daily"], + self._data["hourly"], + self._data["station"], + ) + + def _convert_weather_response(self, weather_response): + """Format the weather response correctly.""" + if not weather_response or not weather_response.hourly: + return None + + elaborated = dt_util.parse_datetime( + weather_response.hourly[ATTR_DATA][0][AEMET_ATTR_ELABORATED] + ) + now = dt_util.now() + hour = now.hour + + # Get current day + day = None + for cur_day in weather_response.hourly[ATTR_DATA][0][AEMET_ATTR_FORECAST][ + AEMET_ATTR_DAY + ]: + cur_day_date = dt_util.parse_datetime(cur_day[AEMET_ATTR_DATE]) + if now.date() == cur_day_date.date(): + day = cur_day + break + + # Get station data + station_data = None + if weather_response.station: + station_data = weather_response.station[ATTR_DATA][-1] + + condition = None + humidity = None + pressure = None + rain = None + rain_prob = None + snow = None + snow_prob = None + station_id = None + station_name = None + station_timestamp = None + storm_prob = None + temperature = None + temperature_feeling = None + town_id = None + town_name = None + town_timestamp = dt_util.as_utc(elaborated) + wind_bearing = None + wind_max_speed = None + wind_speed = None + + # Get weather values + if day: + condition = self._get_condition(day, hour) + humidity = self._get_humidity(day, hour) + rain = self._get_rain(day, hour) + rain_prob = self._get_rain_prob(day, hour) + snow = self._get_snow(day, hour) + snow_prob = self._get_snow_prob(day, hour) + station_id = self._get_station_id() + station_name = self._get_station_name() + storm_prob = self._get_storm_prob(day, hour) + temperature = self._get_temperature(day, hour) + temperature_feeling = self._get_temperature_feeling(day, hour) + town_id = self._get_town_id() + town_name = self._get_town_name() + wind_bearing = self._get_wind_bearing(day, hour) + wind_max_speed = self._get_wind_max_speed(day, hour) + wind_speed = self._get_wind_speed(day, hour) + + # Overwrite weather values with closest station data (if present) + if station_data: + if AEMET_ATTR_STATION_DATE in station_data: + station_dt = dt_util.parse_datetime( + station_data[AEMET_ATTR_STATION_DATE] + "Z" + ) + station_timestamp = dt_util.as_utc(station_dt).isoformat() + if AEMET_ATTR_STATION_HUMIDITY in station_data: + humidity = format_float(station_data[AEMET_ATTR_STATION_HUMIDITY]) + if AEMET_ATTR_STATION_PRESSURE_SEA in station_data: + pressure = format_float(station_data[AEMET_ATTR_STATION_PRESSURE_SEA]) + if AEMET_ATTR_STATION_TEMPERATURE in station_data: + temperature = format_float(station_data[AEMET_ATTR_STATION_TEMPERATURE]) + + # Get forecast from weather data + forecast_daily = self._get_daily_forecast_from_weather_response( + weather_response, now + ) + forecast_hourly = self._get_hourly_forecast_from_weather_response( + weather_response, now + ) + + return { + ATTR_API_CONDITION: condition, + ATTR_API_FORECAST_DAILY: forecast_daily, + ATTR_API_FORECAST_HOURLY: forecast_hourly, + ATTR_API_HUMIDITY: humidity, + ATTR_API_TEMPERATURE: temperature, + ATTR_API_TEMPERATURE_FEELING: temperature_feeling, + ATTR_API_PRESSURE: pressure, + ATTR_API_RAIN: rain, + ATTR_API_RAIN_PROB: rain_prob, + ATTR_API_SNOW: snow, + ATTR_API_SNOW_PROB: snow_prob, + ATTR_API_STATION_ID: station_id, + ATTR_API_STATION_NAME: station_name, + ATTR_API_STATION_TIMESTAMP: station_timestamp, + ATTR_API_STORM_PROB: storm_prob, + ATTR_API_TOWN_ID: town_id, + ATTR_API_TOWN_NAME: town_name, + ATTR_API_TOWN_TIMESTAMP: town_timestamp, + ATTR_API_WIND_BEARING: wind_bearing, + ATTR_API_WIND_MAX_SPEED: wind_max_speed, + ATTR_API_WIND_SPEED: wind_speed, + } + + def _get_daily_forecast_from_weather_response(self, weather_response, now): + if weather_response.daily: + parse = False + forecast = [] + for day in weather_response.daily[ATTR_DATA][0][AEMET_ATTR_FORECAST][ + AEMET_ATTR_DAY + ]: + day_date = dt_util.parse_datetime(day[AEMET_ATTR_DATE]) + if now.date() == day_date.date(): + parse = True + if parse: + cur_forecast = self._convert_forecast_day(day_date, day) + if cur_forecast: + forecast.append(cur_forecast) + return forecast + return None + + def _get_hourly_forecast_from_weather_response(self, weather_response, now): + if weather_response.hourly: + parse = False + hour = now.hour + forecast = [] + for day in weather_response.hourly[ATTR_DATA][0][AEMET_ATTR_FORECAST][ + AEMET_ATTR_DAY + ]: + day_date = dt_util.parse_datetime(day[AEMET_ATTR_DATE]) + hour_start = 0 + if now.date() == day_date.date(): + parse = True + hour_start = now.hour + if parse: + for hour in range(hour_start, 24): + cur_forecast = self._convert_forecast_hour(day_date, day, hour) + if cur_forecast: + forecast.append(cur_forecast) + return forecast + return None + + def _convert_forecast_day(self, date, day): + condition = self._get_condition_day(day) + if not condition: + return None + + return { + ATTR_FORECAST_CONDITION: condition, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._get_precipitation_prob_day( + day + ), + ATTR_FORECAST_TEMP: self._get_temperature_day(day), + ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day), + ATTR_FORECAST_TIME: dt_util.as_utc(date), + ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day), + ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day), + } + + def _convert_forecast_hour(self, date, day, hour): + condition = self._get_condition(day, hour) + if not condition: + return None + + forecast_dt = date.replace(hour=hour, minute=0, second=0) + + return { + ATTR_FORECAST_CONDITION: condition, + ATTR_FORECAST_PRECIPITATION: self._calc_precipitation(day, hour), + ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._calc_precipitation_prob( + day, hour + ), + ATTR_FORECAST_TEMP: self._get_temperature(day, hour), + ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt), + ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour), + ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour), + } + + def _calc_precipitation(self, day, hour): + """Calculate the precipitation.""" + rain_value = self._get_rain(day, hour) + if not rain_value: + rain_value = 0 + + snow_value = self._get_snow(day, hour) + if not snow_value: + snow_value = 0 + + if round(rain_value + snow_value, 1) == 0: + return None + return round(rain_value + snow_value, 1) + + def _calc_precipitation_prob(self, day, hour): + """Calculate the precipitation probability (hour).""" + rain_value = self._get_rain_prob(day, hour) + if not rain_value: + rain_value = 0 + + snow_value = self._get_snow_prob(day, hour) + if not snow_value: + snow_value = 0 + + if rain_value == 0 and snow_value == 0: + return None + return max(rain_value, snow_value) + + @staticmethod + def _get_condition(day_data, hour): + """Get weather condition (hour) from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_SKY_STATE], hour) + if val: + return format_condition(val) + return None + + @staticmethod + def _get_condition_day(day_data): + """Get weather condition (day) from weather data.""" + val = get_forecast_day_value(day_data[AEMET_ATTR_SKY_STATE]) + if val: + return format_condition(val) + return None + + @staticmethod + def _get_humidity(day_data, hour): + """Get humidity from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_HUMIDITY], hour) + if val: + return format_int(val) + return None + + @staticmethod + def _get_precipitation_prob_day(day_data): + """Get humidity from weather data.""" + val = get_forecast_day_value(day_data[AEMET_ATTR_PRECIPITATION_PROBABILITY]) + if val: + return format_int(val) + return None + + @staticmethod + def _get_rain(day_data, hour): + """Get rain from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_PRECIPITATION], hour) + if val: + return format_float(val) + return None + + @staticmethod + def _get_rain_prob(day_data, hour): + """Get rain probability from weather data.""" + val = get_forecast_interval_value( + day_data[AEMET_ATTR_PRECIPITATION_PROBABILITY], hour + ) + if val: + return format_int(val) + return None + + @staticmethod + def _get_snow(day_data, hour): + """Get snow from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_SNOW], hour) + if val: + return format_float(val) + return None + + @staticmethod + def _get_snow_prob(day_data, hour): + """Get snow probability from weather data.""" + val = get_forecast_interval_value(day_data[AEMET_ATTR_SNOW_PROBABILITY], hour) + if val: + return format_int(val) + return None + + def _get_station_id(self): + """Get station ID from weather data.""" + if self._station: + return self._station[AEMET_ATTR_IDEMA] + return None + + def _get_station_name(self): + """Get station name from weather data.""" + if self._station: + return self._station[AEMET_ATTR_STATION_LOCATION] + return None + + @staticmethod + def _get_storm_prob(day_data, hour): + """Get storm probability from weather data.""" + val = get_forecast_interval_value(day_data[AEMET_ATTR_STORM_PROBABILITY], hour) + if val: + return format_int(val) + return None + + @staticmethod + def _get_temperature(day_data, hour): + """Get temperature (hour) from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE], hour) + if val: + return format_int(val) + return None + + @staticmethod + def _get_temperature_day(day_data): + """Get temperature (day) from weather data.""" + val = get_forecast_day_value( + day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MAX + ) + if val: + return format_int(val) + return None + + @staticmethod + def _get_temperature_low_day(day_data): + """Get temperature (day) from weather data.""" + val = get_forecast_day_value( + day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MIN + ) + if val: + return format_int(val) + return None + + @staticmethod + def _get_temperature_feeling(day_data, hour): + """Get temperature from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE_FEELING], hour) + if val: + return format_int(val) + return None + + def _get_town_id(self): + """Get town ID from weather data.""" + if self._town: + return self._town[AEMET_ATTR_ID] + return None + + def _get_town_name(self): + """Get town name from weather data.""" + if self._town: + return self._town[AEMET_ATTR_NAME] + return None + + @staticmethod + def _get_wind_bearing(day_data, hour): + """Get wind bearing (hour) from weather data.""" + val = get_forecast_hour_value( + day_data[AEMET_ATTR_WIND_GUST], hour, key=AEMET_ATTR_DIRECTION + )[0] + if val in WIND_BEARING_MAP: + return WIND_BEARING_MAP[val] + _LOGGER.error("%s not found in Wind Bearing map", val) + return None + + @staticmethod + def _get_wind_bearing_day(day_data): + """Get wind bearing (day) from weather data.""" + val = get_forecast_day_value( + day_data[AEMET_ATTR_WIND], key=AEMET_ATTR_DIRECTION + ) + if val in WIND_BEARING_MAP: + return WIND_BEARING_MAP[val] + _LOGGER.error("%s not found in Wind Bearing map", val) + return None + + @staticmethod + def _get_wind_max_speed(day_data, hour): + """Get wind max speed from weather data.""" + val = get_forecast_hour_value(day_data[AEMET_ATTR_WIND_GUST], hour) + if val: + return format_int(val) + return None + + @staticmethod + def _get_wind_speed(day_data, hour): + """Get wind speed (hour) from weather data.""" + val = get_forecast_hour_value( + day_data[AEMET_ATTR_WIND_GUST], hour, key=AEMET_ATTR_SPEED + )[0] + if val: + return format_int(val) + return None + + @staticmethod + def _get_wind_speed_day(day_data): + """Get wind speed (day) from weather data.""" + val = get_forecast_day_value(day_data[AEMET_ATTR_WIND], key=AEMET_ATTR_SPEED) + if val: + return format_int(val) + return None + + +@dataclass +class AemetWeather: + """Class to harmonize weather data model.""" + + daily: dict = field(default_factory=dict) + hourly: dict = field(default_factory=dict) + station: dict = field(default_factory=dict) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index f5f550b3073e38..41d85c4b20b93b 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -11,6 +11,7 @@ "acmeda", "adguard", "advantage_air", + "aemet", "agent_dvr", "airly", "airnow", diff --git a/requirements_all.txt b/requirements_all.txt index 9ee4663bd17b78..d8ec67e1d30a04 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1,6 +1,9 @@ # Home Assistant Core, full dependency set -r requirements.txt +# homeassistant.components.aemet +AEMET-OpenData==0.1.8 + # homeassistant.components.dht # Adafruit-DHT==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 737b3e4a989d91..3a0ce6dd428b16 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -3,6 +3,9 @@ -r requirements_test.txt +# homeassistant.components.aemet +AEMET-OpenData==0.1.8 + # homeassistant.components.homekit HAP-python==3.3.0 diff --git a/tests/components/aemet/__init__.py b/tests/components/aemet/__init__.py new file mode 100644 index 00000000000000..a92ff2764b1b5c --- /dev/null +++ b/tests/components/aemet/__init__.py @@ -0,0 +1 @@ +"""Tests for the AEMET OpenData integration.""" diff --git a/tests/components/aemet/test_config_flow.py b/tests/components/aemet/test_config_flow.py new file mode 100644 index 00000000000000..3c93a9d63213b6 --- /dev/null +++ b/tests/components/aemet/test_config_flow.py @@ -0,0 +1,100 @@ +"""Define tests for the AEMET OpenData config flow.""" + +from unittest.mock import MagicMock, patch + +import requests_mock + +from homeassistant import data_entry_flow +from homeassistant.components.aemet.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, SOURCE_USER +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +import homeassistant.util.dt as dt_util + +from .util import aemet_requests_mock + +from tests.common import MockConfigEntry + +CONFIG = { + CONF_NAME: "aemet", + CONF_API_KEY: "foo", + CONF_LATITUDE: 40.30403754, + CONF_LONGITUDE: -3.72935236, +} + + +async def test_form(hass): + """Test that the form is served with valid input.""" + + with patch( + "homeassistant.components.aemet.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.aemet.async_setup_entry", + return_value=True, + ) as mock_setup_entry, requests_mock.mock() as _m: + aemet_requests_mock(_m) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == SOURCE_USER + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG + ) + + await hass.async_block_till_done() + + conf_entries = hass.config_entries.async_entries(DOMAIN) + entry = conf_entries[0] + assert entry.state == ENTRY_STATE_LOADED + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == CONFIG[CONF_NAME] + assert result["data"][CONF_LATITUDE] == CONFIG[CONF_LATITUDE] + assert result["data"][CONF_LONGITUDE] == CONFIG[CONF_LONGITUDE] + assert result["data"][CONF_API_KEY] == CONFIG[CONF_API_KEY] + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_duplicated_id(hass): + """Test that the options form.""" + + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ), requests_mock.mock() as _m: + aemet_requests_mock(_m) + + entry = MockConfigEntry( + domain=DOMAIN, unique_id="40.30403754--3.72935236", data=CONFIG + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=CONFIG + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + +async def test_form_api_offline(hass): + """Test setting up with api call error.""" + mocked_aemet = MagicMock() + + mocked_aemet.get_conventional_observation_stations.return_value = None + + with patch( + "homeassistant.components.aemet.config_flow.AEMET", + return_value=mocked_aemet, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=CONFIG + ) + + assert result["errors"] == {"base": "invalid_api_key"} diff --git a/tests/components/aemet/test_init.py b/tests/components/aemet/test_init.py new file mode 100644 index 00000000000000..f1c6c48f3f3323 --- /dev/null +++ b/tests/components/aemet/test_init.py @@ -0,0 +1,44 @@ +"""Define tests for the AEMET OpenData init.""" + +from unittest.mock import patch + +import requests_mock + +from homeassistant.components.aemet.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +import homeassistant.util.dt as dt_util + +from .util import aemet_requests_mock + +from tests.common import MockConfigEntry + +CONFIG = { + CONF_NAME: "aemet", + CONF_API_KEY: "foo", + CONF_LATITUDE: 40.30403754, + CONF_LONGITUDE: -3.72935236, +} + + +async def test_unload_entry(hass): + """Test that the options form.""" + + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ), requests_mock.mock() as _m: + aemet_requests_mock(_m) + + config_entry = MockConfigEntry( + domain=DOMAIN, unique_id="aemet_unique_id", data=CONFIG + ) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/aemet/test_sensor.py b/tests/components/aemet/test_sensor.py new file mode 100644 index 00000000000000..05f2d8d0b500d0 --- /dev/null +++ b/tests/components/aemet/test_sensor.py @@ -0,0 +1,137 @@ +"""The sensor tests for the AEMET OpenData platform.""" + +from unittest.mock import patch + +from homeassistant.components.weather import ( + ATTR_CONDITION_PARTLYCLOUDY, + ATTR_CONDITION_SNOWY, +) +from homeassistant.const import STATE_UNKNOWN +import homeassistant.util.dt as dt_util + +from .util import async_init_integration + + +async def test_aemet_forecast_create_sensors(hass): + """Test creation of forecast sensors.""" + + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ): + await async_init_integration(hass) + + state = hass.states.get("sensor.aemet_daily_forecast_condition") + assert state.state == ATTR_CONDITION_PARTLYCLOUDY + + state = hass.states.get("sensor.aemet_daily_forecast_precipitation") + assert state.state == STATE_UNKNOWN + + state = hass.states.get("sensor.aemet_daily_forecast_precipitation_probability") + assert state.state == "30" + + state = hass.states.get("sensor.aemet_daily_forecast_temperature") + assert state.state == "4" + + state = hass.states.get("sensor.aemet_daily_forecast_temperature_low") + assert state.state == "-4" + + state = hass.states.get("sensor.aemet_daily_forecast_time") + assert state.state == "2021-01-10 00:00:00+00:00" + + state = hass.states.get("sensor.aemet_daily_forecast_wind_bearing") + assert state.state == "45.0" + + state = hass.states.get("sensor.aemet_daily_forecast_wind_speed") + assert state.state == "20" + + state = hass.states.get("sensor.aemet_hourly_forecast_condition") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_precipitation") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_precipitation_probability") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_temperature") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_temperature_low") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_time") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_wind_bearing") + assert state is None + + state = hass.states.get("sensor.aemet_hourly_forecast_wind_speed") + assert state is None + + +async def test_aemet_weather_create_sensors(hass): + """Test creation of weather sensors.""" + + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ): + await async_init_integration(hass) + + state = hass.states.get("sensor.aemet_condition") + assert state.state == ATTR_CONDITION_SNOWY + + state = hass.states.get("sensor.aemet_humidity") + assert state.state == "99.0" + + state = hass.states.get("sensor.aemet_pressure") + assert state.state == "1004.4" + + state = hass.states.get("sensor.aemet_rain") + assert state.state == "1.8" + + state = hass.states.get("sensor.aemet_rain_probability") + assert state.state == "100" + + state = hass.states.get("sensor.aemet_snow") + assert state.state == "1.8" + + state = hass.states.get("sensor.aemet_snow_probability") + assert state.state == "100" + + state = hass.states.get("sensor.aemet_station_id") + assert state.state == "3195" + + state = hass.states.get("sensor.aemet_station_name") + assert state.state == "MADRID RETIRO" + + state = hass.states.get("sensor.aemet_station_timestamp") + assert state.state == "2021-01-09T12:00:00+00:00" + + state = hass.states.get("sensor.aemet_storm_probability") + assert state.state == "0" + + state = hass.states.get("sensor.aemet_temperature") + assert state.state == "-0.7" + + state = hass.states.get("sensor.aemet_temperature_feeling") + assert state.state == "-4" + + state = hass.states.get("sensor.aemet_town_id") + assert state.state == "id28065" + + state = hass.states.get("sensor.aemet_town_name") + assert state.state == "Getafe" + + state = hass.states.get("sensor.aemet_town_timestamp") + assert state.state == "2021-01-09 11:47:45+00:00" + + state = hass.states.get("sensor.aemet_wind_bearing") + assert state.state == "90.0" + + state = hass.states.get("sensor.aemet_wind_max_speed") + assert state.state == "24" + + state = hass.states.get("sensor.aemet_wind_speed") + assert state.state == "15" diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py new file mode 100644 index 00000000000000..eef6107d54322c --- /dev/null +++ b/tests/components/aemet/test_weather.py @@ -0,0 +1,61 @@ +"""The sensor tests for the AEMET OpenData platform.""" + +from unittest.mock import patch + +from homeassistant.components.aemet.const import ATTRIBUTION +from homeassistant.components.weather import ( + ATTR_CONDITION_PARTLYCLOUDY, + ATTR_CONDITION_SNOWY, + ATTR_FORECAST, + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, + ATTR_WEATHER_HUMIDITY, + ATTR_WEATHER_PRESSURE, + ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_WIND_BEARING, + ATTR_WEATHER_WIND_SPEED, +) +from homeassistant.const import ATTR_ATTRIBUTION +import homeassistant.util.dt as dt_util + +from .util import async_init_integration + + +async def test_aemet_weather(hass): + """Test states of the weather.""" + + now = dt_util.parse_datetime("2021-01-09 12:00:00+00:00") + with patch("homeassistant.util.dt.now", return_value=now), patch( + "homeassistant.util.dt.utcnow", return_value=now + ): + await async_init_integration(hass) + + state = hass.states.get("weather.aemet_daily") + assert state + assert state.state == ATTR_CONDITION_SNOWY + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 99.0 + assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1004.4 + assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == -0.7 + assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 90.0 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 15 + forecast = state.attributes.get(ATTR_FORECAST)[0] + assert forecast.get(ATTR_FORECAST_CONDITION) == ATTR_CONDITION_PARTLYCLOUDY + assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None + assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 30 + assert forecast.get(ATTR_FORECAST_TEMP) == 4 + assert forecast.get(ATTR_FORECAST_TEMP_LOW) == -4 + assert forecast.get(ATTR_FORECAST_TIME) == dt_util.parse_datetime( + "2021-01-10 00:00:00+00:00" + ) + assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0 + assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20 + + state = hass.states.get("weather.aemet_hourly") + assert state is None diff --git a/tests/components/aemet/util.py b/tests/components/aemet/util.py new file mode 100644 index 00000000000000..991e7459bf6d95 --- /dev/null +++ b/tests/components/aemet/util.py @@ -0,0 +1,93 @@ +"""Tests for the AEMET OpenData integration.""" + +import requests_mock + +from homeassistant.components.aemet import DOMAIN +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_fixture + + +def aemet_requests_mock(mock): + """Mock requests performed to AEMET OpenData API.""" + + station_3195_fixture = "aemet/station-3195.json" + station_3195_data_fixture = "aemet/station-3195-data.json" + station_list_fixture = "aemet/station-list.json" + station_list_data_fixture = "aemet/station-list-data.json" + + town_28065_forecast_daily_fixture = "aemet/town-28065-forecast-daily.json" + town_28065_forecast_daily_data_fixture = "aemet/town-28065-forecast-daily-data.json" + town_28065_forecast_hourly_fixture = "aemet/town-28065-forecast-hourly.json" + town_28065_forecast_hourly_data_fixture = ( + "aemet/town-28065-forecast-hourly-data.json" + ) + town_id28065_fixture = "aemet/town-id28065.json" + town_list_fixture = "aemet/town-list.json" + + mock.get( + "https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/3195", + text=load_fixture(station_3195_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/sh/208c3ca3", + text=load_fixture(station_3195_data_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/api/observacion/convencional/todas", + text=load_fixture(station_list_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/sh/2c55192f", + text=load_fixture(station_list_data_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/api/prediccion/especifica/municipio/diaria/28065", + text=load_fixture(town_28065_forecast_daily_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/sh/64e29abb", + text=load_fixture(town_28065_forecast_daily_data_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/api/prediccion/especifica/municipio/horaria/28065", + text=load_fixture(town_28065_forecast_hourly_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/sh/18ca1886", + text=load_fixture(town_28065_forecast_hourly_data_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/api/maestro/municipio/id28065", + text=load_fixture(town_id28065_fixture), + ) + mock.get( + "https://opendata.aemet.es/opendata/api/maestro/municipios", + text=load_fixture(town_list_fixture), + ) + + +async def async_init_integration( + hass: HomeAssistant, + skip_setup: bool = False, +): + """Set up the AEMET OpenData integration in Home Assistant.""" + + with requests_mock.mock() as _m: + aemet_requests_mock(_m) + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_API_KEY: "mock", + CONF_LATITUDE: "40.30403754", + CONF_LONGITUDE: "-3.72935236", + CONF_NAME: "AEMET", + }, + ) + entry.add_to_hass(hass) + + if not skip_setup: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/fixtures/aemet/station-3195-data.json b/tests/fixtures/aemet/station-3195-data.json new file mode 100644 index 00000000000000..1784a5fb3a42e8 --- /dev/null +++ b/tests/fixtures/aemet/station-3195-data.json @@ -0,0 +1,369 @@ +[ { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T14:00:00", + "prec" : 1.2, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 929.9, + "hr" : 97.0, + "pres_nmar" : 1009.9, + "tamin" : -0.1, + "ta" : 0.1, + "tamax" : 0.2, + "tpr" : -0.3, + "rviento" : 132.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T15:00:00", + "prec" : 1.5, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 929.0, + "hr" : 98.0, + "pres_nmar" : 1008.9, + "tamin" : 0.1, + "ta" : 0.2, + "tamax" : 0.3, + "tpr" : 0.0, + "rviento" : 154.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T16:00:00", + "prec" : 0.7, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 928.8, + "hr" : 98.0, + "pres_nmar" : 1008.6, + "tamin" : 0.2, + "ta" : 0.3, + "tamax" : 0.3, + "tpr" : 0.0, + "rviento" : 177.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T17:00:00", + "prec" : 1.7, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 928.6, + "hr" : 99.0, + "pres_nmar" : 1008.5, + "tamin" : 0.1, + "ta" : 0.1, + "tamax" : 0.3, + "tpr" : 0.0, + "rviento" : 174.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T18:00:00", + "prec" : 1.9, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 928.2, + "hr" : 99.0, + "pres_nmar" : 1008.1, + "tamin" : -0.1, + "ta" : -0.1, + "tamax" : 0.1, + "tpr" : -0.3, + "rviento" : 163.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T19:00:00", + "prec" : 3.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 928.4, + "hr" : 99.0, + "pres_nmar" : 1008.4, + "tamin" : -0.3, + "ta" : -0.3, + "tamax" : 0.0, + "tpr" : -0.5, + "rviento" : 79.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T20:00:00", + "prec" : 3.5, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 928.4, + "hr" : 99.0, + "pres_nmar" : 1008.5, + "tamin" : -0.6, + "ta" : -0.6, + "tamax" : -0.3, + "tpr" : -0.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T21:00:00", + "prec" : 2.6, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 928.1, + "hr" : 99.0, + "pres_nmar" : 1008.2, + "tamin" : -0.7, + "ta" : -0.7, + "tamax" : -0.5, + "tpr" : -0.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T22:00:00", + "prec" : 3.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 927.6, + "hr" : 99.0, + "pres_nmar" : 1007.7, + "tamin" : -0.8, + "ta" : -0.8, + "tamax" : -0.7, + "tpr" : -1.0, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T23:00:00", + "prec" : 2.9, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 926.9, + "hr" : 99.0, + "pres_nmar" : 1007.0, + "tamin" : -0.9, + "ta" : -0.9, + "tamax" : -0.7, + "tpr" : -1.0, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T00:00:00", + "prec" : 1.4, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 926.5, + "hr" : 99.0, + "pres_nmar" : 1006.6, + "tamin" : -1.0, + "ta" : -1.0, + "tamax" : -0.8, + "tpr" : -1.2, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T01:00:00", + "prec" : 2.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 925.9, + "hr" : 99.0, + "pres_nmar" : 1006.0, + "tamin" : -1.3, + "ta" : -1.3, + "tamax" : -1.0, + "tpr" : -1.4, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T02:00:00", + "prec" : 1.5, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 925.7, + "hr" : 99.0, + "pres_nmar" : 1005.8, + "tamin" : -1.5, + "ta" : -1.4, + "tamax" : -1.3, + "tpr" : -1.4, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T03:00:00", + "prec" : 1.2, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 925.6, + "hr" : 99.0, + "pres_nmar" : 1005.7, + "tamin" : -1.5, + "ta" : -1.4, + "tamax" : -1.4, + "tpr" : -1.4, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T04:00:00", + "prec" : 1.1, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 924.9, + "hr" : 99.0, + "pres_nmar" : 1005.0, + "tamin" : -1.5, + "ta" : -1.5, + "tamax" : -1.4, + "tpr" : -1.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T05:00:00", + "prec" : 0.7, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 924.6, + "hr" : 99.0, + "pres_nmar" : 1004.7, + "tamin" : -1.5, + "ta" : -1.5, + "tamax" : -1.4, + "tpr" : -1.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T06:00:00", + "prec" : 0.2, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 924.4, + "hr" : 99.0, + "pres_nmar" : 1004.5, + "tamin" : -1.6, + "ta" : -1.6, + "tamax" : -1.5, + "tpr" : -1.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T07:00:00", + "prec" : 0.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 924.4, + "hr" : 99.0, + "pres_nmar" : 1004.5, + "tamin" : -1.6, + "ta" : -1.6, + "tamax" : -1.6, + "tpr" : -1.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T08:00:00", + "prec" : 0.1, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 924.8, + "hr" : 99.0, + "pres_nmar" : 1004.9, + "tamin" : -1.6, + "ta" : -1.6, + "tamax" : -1.5, + "tpr" : -1.7, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T09:00:00", + "prec" : 0.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 925.0, + "hr" : 99.0, + "pres_nmar" : 1005.0, + "tamin" : -1.6, + "ta" : -1.3, + "tamax" : -1.3, + "tpr" : -1.4, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T10:00:00", + "prec" : 0.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 925.3, + "hr" : 99.0, + "pres_nmar" : 1005.3, + "tamin" : -1.3, + "ta" : -1.2, + "tamax" : -1.1, + "tpr" : -1.4, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T11:00:00", + "prec" : 4.4, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 925.4, + "hr" : 99.0, + "pres_nmar" : 1005.4, + "tamin" : -1.2, + "ta" : -1.0, + "tamax" : -1.0, + "tpr" : -1.2, + "rviento" : 0.0 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-09T12:00:00", + "prec" : 7.0, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 924.6, + "hr" : 99.0, + "pres_nmar" : 1004.4, + "tamin" : -1.0, + "ta" : -0.7, + "tamax" : -0.6, + "tpr" : -0.7, + "rviento" : 0.0 +} ] diff --git a/tests/fixtures/aemet/station-3195.json b/tests/fixtures/aemet/station-3195.json new file mode 100644 index 00000000000000..f97df3bea63447 --- /dev/null +++ b/tests/fixtures/aemet/station-3195.json @@ -0,0 +1,6 @@ +{ + "descripcion" : "exito", + "estado" : 200, + "datos" : "https://opendata.aemet.es/opendata/sh/208c3ca3", + "metadatos" : "https://opendata.aemet.es/opendata/sh/55c2971b" +} diff --git a/tests/fixtures/aemet/station-list-data.json b/tests/fixtures/aemet/station-list-data.json new file mode 100644 index 00000000000000..8b35bff6e4aefb --- /dev/null +++ b/tests/fixtures/aemet/station-list-data.json @@ -0,0 +1,42 @@ +[ { + "idema" : "3194U", + "lon" : -3.724167, + "fint" : "2021-01-08T14:00:00", + "prec" : 1.3, + "alt" : 664.0, + "lat" : 40.45167, + "ubi" : "MADRID C. UNIVERSITARIA", + "hr" : 98.0, + "tamin" : 0.6, + "ta" : 0.9, + "tamax" : 1.0, + "tpr" : 0.6 +}, { + "idema" : "3194Y", + "lon" : -3.813369, + "fint" : "2021-01-08T14:00:00", + "prec" : 0.2, + "alt" : 665.0, + "lat" : 40.448437, + "ubi" : "POZUELO DE ALARCON (AUTOM�TICA)", + "hr" : 93.0, + "tamin" : 0.5, + "ta" : 0.6, + "tamax" : 0.6 +}, { + "idema" : "3195", + "lon" : -3.678095, + "fint" : "2021-01-08T14:00:00", + "prec" : 1.2, + "alt" : 667.0, + "lat" : 40.411804, + "ubi" : "MADRID RETIRO", + "pres" : 929.9, + "hr" : 97.0, + "pres_nmar" : 1009.9, + "tamin" : -0.1, + "ta" : 0.1, + "tamax" : 0.2, + "tpr" : -0.3, + "rviento" : 132.0 +} ] diff --git a/tests/fixtures/aemet/station-list.json b/tests/fixtures/aemet/station-list.json new file mode 100644 index 00000000000000..6e0dbc97d6de98 --- /dev/null +++ b/tests/fixtures/aemet/station-list.json @@ -0,0 +1,6 @@ +{ + "descripcion" : "exito", + "estado" : 200, + "datos" : "https://opendata.aemet.es/opendata/sh/2c55192f", + "metadatos" : "https://opendata.aemet.es/opendata/sh/55c2971b" +} diff --git a/tests/fixtures/aemet/town-28065-forecast-daily-data.json b/tests/fixtures/aemet/town-28065-forecast-daily-data.json new file mode 100644 index 00000000000000..77877c72f3a2e5 --- /dev/null +++ b/tests/fixtures/aemet/town-28065-forecast-daily-data.json @@ -0,0 +1,625 @@ +[ { + "origen" : { + "productor" : "Agencia Estatal de Meteorolog�a - AEMET. Gobierno de Espa�a", + "web" : "http://www.aemet.es", + "enlace" : "http://www.aemet.es/es/eltiempo/prediccion/municipios/getafe-id28065", + "language" : "es", + "copyright" : "� AEMET. Autorizado el uso de la informaci�n y su reproducci�n citando a AEMET como autora de la misma.", + "notaLegal" : "http://www.aemet.es/es/nota_legal" + }, + "elaborado" : "2021-01-09T11:54:00", + "nombre" : "Getafe", + "provincia" : "Madrid", + "prediccion" : { + "dia" : [ { + "probPrecipitacion" : [ { + "value" : 0, + "periodo" : "00-24" + }, { + "value" : 0, + "periodo" : "00-12" + }, { + "value" : 100, + "periodo" : "12-24" + }, { + "value" : 0, + "periodo" : "00-06" + }, { + "value" : 100, + "periodo" : "06-12" + }, { + "value" : 100, + "periodo" : "12-18" + }, { + "value" : 100, + "periodo" : "18-24" + } ], + "cotaNieveProv" : [ { + "value" : "", + "periodo" : "00-24" + }, { + "value" : "", + "periodo" : "00-12" + }, { + "value" : "500", + "periodo" : "12-24" + }, { + "value" : "", + "periodo" : "00-06" + }, { + "value" : "400", + "periodo" : "06-12" + }, { + "value" : "500", + "periodo" : "12-18" + }, { + "value" : "600", + "periodo" : "18-24" + } ], + "estadoCielo" : [ { + "value" : "", + "periodo" : "00-24", + "descripcion" : "" + }, { + "value" : "", + "periodo" : "00-12", + "descripcion" : "" + }, { + "value" : "36", + "periodo" : "12-24", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "", + "periodo" : "00-06", + "descripcion" : "" + }, { + "value" : "36", + "periodo" : "06-12", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "12-18", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "34n", + "periodo" : "18-24", + "descripcion" : "Nuboso con nieve" + } ], + "viento" : [ { + "direccion" : "", + "velocidad" : 0, + "periodo" : "00-24" + }, { + "direccion" : "", + "velocidad" : 0, + "periodo" : "00-12" + }, { + "direccion" : "E", + "velocidad" : 15, + "periodo" : "12-24" + }, { + "direccion" : "NE", + "velocidad" : 30, + "periodo" : "00-06" + }, { + "direccion" : "E", + "velocidad" : 15, + "periodo" : "06-12" + }, { + "direccion" : "E", + "velocidad" : 5, + "periodo" : "12-18" + }, { + "direccion" : "NE", + "velocidad" : 5, + "periodo" : "18-24" + } ], + "rachaMax" : [ { + "value" : "", + "periodo" : "00-24" + }, { + "value" : "", + "periodo" : "00-12" + }, { + "value" : "", + "periodo" : "12-24" + }, { + "value" : "40", + "periodo" : "00-06" + }, { + "value" : "", + "periodo" : "06-12" + }, { + "value" : "", + "periodo" : "12-18" + }, { + "value" : "", + "periodo" : "18-24" + } ], + "temperatura" : { + "maxima" : 2, + "minima" : -1, + "dato" : [ { + "value" : -1, + "hora" : 6 + }, { + "value" : 0, + "hora" : 12 + }, { + "value" : 1, + "hora" : 18 + }, { + "value" : 1, + "hora" : 24 + } ] + }, + "sensTermica" : { + "maxima" : 1, + "minima" : -9, + "dato" : [ { + "value" : -1, + "hora" : 6 + }, { + "value" : -4, + "hora" : 12 + }, { + "value" : 1, + "hora" : 18 + }, { + "value" : 1, + "hora" : 24 + } ] + }, + "humedadRelativa" : { + "maxima" : 100, + "minima" : 75, + "dato" : [ { + "value" : 100, + "hora" : 6 + }, { + "value" : 100, + "hora" : 12 + }, { + "value" : 95, + "hora" : 18 + }, { + "value" : 75, + "hora" : 24 + } ] + }, + "uvMax" : 1, + "fecha" : "2021-01-09T00:00:00" + }, { + "probPrecipitacion" : [ { + "value" : 30, + "periodo" : "00-24" + }, { + "value" : 25, + "periodo" : "00-12" + }, { + "value" : 5, + "periodo" : "12-24" + }, { + "value" : 5, + "periodo" : "00-06" + }, { + "value" : 15, + "periodo" : "06-12" + }, { + "value" : 5, + "periodo" : "12-18" + }, { + "value" : 0, + "periodo" : "18-24" + } ], + "cotaNieveProv" : [ { + "value" : "600", + "periodo" : "00-24" + }, { + "value" : "600", + "periodo" : "00-12" + }, { + "value" : "", + "periodo" : "12-24" + }, { + "value" : "", + "periodo" : "00-06" + }, { + "value" : "600", + "periodo" : "06-12" + }, { + "value" : "", + "periodo" : "12-18" + }, { + "value" : "", + "periodo" : "18-24" + } ], + "estadoCielo" : [ { + "value" : "13", + "periodo" : "00-24", + "descripcion" : "Intervalos nubosos" + }, { + "value" : "15", + "periodo" : "00-12", + "descripcion" : "Muy nuboso" + }, { + "value" : "12", + "periodo" : "12-24", + "descripcion" : "Poco nuboso" + }, { + "value" : "14n", + "periodo" : "00-06", + "descripcion" : "Nuboso" + }, { + "value" : "15", + "periodo" : "06-12", + "descripcion" : "Muy nuboso" + }, { + "value" : "12", + "periodo" : "12-18", + "descripcion" : "Poco nuboso" + }, { + "value" : "12n", + "periodo" : "18-24", + "descripcion" : "Poco nuboso" + } ], + "viento" : [ { + "direccion" : "NE", + "velocidad" : 20, + "periodo" : "00-24" + }, { + "direccion" : "NE", + "velocidad" : 20, + "periodo" : "00-12" + }, { + "direccion" : "NE", + "velocidad" : 20, + "periodo" : "12-24" + }, { + "direccion" : "N", + "velocidad" : 10, + "periodo" : "00-06" + }, { + "direccion" : "NE", + "velocidad" : 20, + "periodo" : "06-12" + }, { + "direccion" : "NE", + "velocidad" : 15, + "periodo" : "12-18" + }, { + "direccion" : "NE", + "velocidad" : 20, + "periodo" : "18-24" + } ], + "rachaMax" : [ { + "value" : "30", + "periodo" : "00-24" + }, { + "value" : "30", + "periodo" : "00-12" + }, { + "value" : "30", + "periodo" : "12-24" + }, { + "value" : "", + "periodo" : "00-06" + }, { + "value" : "30", + "periodo" : "06-12" + }, { + "value" : "", + "periodo" : "12-18" + }, { + "value" : "", + "periodo" : "18-24" + } ], + "temperatura" : { + "maxima" : 4, + "minima" : -4, + "dato" : [ { + "value" : -1, + "hora" : 6 + }, { + "value" : 3, + "hora" : 12 + }, { + "value" : 1, + "hora" : 18 + }, { + "value" : -1, + "hora" : 24 + } ] + }, + "sensTermica" : { + "maxima" : 1, + "minima" : -7, + "dato" : [ { + "value" : -4, + "hora" : 6 + }, { + "value" : -2, + "hora" : 12 + }, { + "value" : -4, + "hora" : 18 + }, { + "value" : -6, + "hora" : 24 + } ] + }, + "humedadRelativa" : { + "maxima" : 100, + "minima" : 70, + "dato" : [ { + "value" : 90, + "hora" : 6 + }, { + "value" : 75, + "hora" : 12 + }, { + "value" : 80, + "hora" : 18 + }, { + "value" : 80, + "hora" : 24 + } ] + }, + "uvMax" : 1, + "fecha" : "2021-01-10T00:00:00" + }, { + "probPrecipitacion" : [ { + "value" : 0, + "periodo" : "00-24" + }, { + "value" : 0, + "periodo" : "00-12" + }, { + "value" : 0, + "periodo" : "12-24" + } ], + "cotaNieveProv" : [ { + "value" : "", + "periodo" : "00-24" + }, { + "value" : "", + "periodo" : "00-12" + }, { + "value" : "", + "periodo" : "12-24" + } ], + "estadoCielo" : [ { + "value" : "12", + "periodo" : "00-24", + "descripcion" : "Poco nuboso" + }, { + "value" : "12", + "periodo" : "00-12", + "descripcion" : "Poco nuboso" + }, { + "value" : "12", + "periodo" : "12-24", + "descripcion" : "Poco nuboso" + } ], + "viento" : [ { + "direccion" : "N", + "velocidad" : 5, + "periodo" : "00-24" + }, { + "direccion" : "NE", + "velocidad" : 20, + "periodo" : "00-12" + }, { + "direccion" : "NO", + "velocidad" : 10, + "periodo" : "12-24" + } ], + "rachaMax" : [ { + "value" : "", + "periodo" : "00-24" + }, { + "value" : "", + "periodo" : "00-12" + }, { + "value" : "", + "periodo" : "12-24" + } ], + "temperatura" : { + "maxima" : 3, + "minima" : -7, + "dato" : [ ] + }, + "sensTermica" : { + "maxima" : 3, + "minima" : -8, + "dato" : [ ] + }, + "humedadRelativa" : { + "maxima" : 85, + "minima" : 60, + "dato" : [ ] + }, + "uvMax" : 1, + "fecha" : "2021-01-11T00:00:00" + }, { + "probPrecipitacion" : [ { + "value" : 0, + "periodo" : "00-24" + }, { + "value" : 0, + "periodo" : "00-12" + }, { + "value" : 0, + "periodo" : "12-24" + } ], + "cotaNieveProv" : [ { + "value" : "", + "periodo" : "00-24" + }, { + "value" : "", + "periodo" : "00-12" + }, { + "value" : "", + "periodo" : "12-24" + } ], + "estadoCielo" : [ { + "value" : "12", + "periodo" : "00-24", + "descripcion" : "Poco nuboso" + }, { + "value" : "12", + "periodo" : "00-12", + "descripcion" : "Poco nuboso" + }, { + "value" : "12", + "periodo" : "12-24", + "descripcion" : "Poco nuboso" + } ], + "viento" : [ { + "direccion" : "C", + "velocidad" : 0, + "periodo" : "00-24" + }, { + "direccion" : "E", + "velocidad" : 5, + "periodo" : "00-12" + }, { + "direccion" : "C", + "velocidad" : 0, + "periodo" : "12-24" + } ], + "rachaMax" : [ { + "value" : "", + "periodo" : "00-24" + }, { + "value" : "", + "periodo" : "00-12" + }, { + "value" : "", + "periodo" : "12-24" + } ], + "temperatura" : { + "maxima" : -1, + "minima" : -13, + "dato" : [ ] + }, + "sensTermica" : { + "maxima" : -1, + "minima" : -13, + "dato" : [ ] + }, + "humedadRelativa" : { + "maxima" : 100, + "minima" : 65, + "dato" : [ ] + }, + "uvMax" : 2, + "fecha" : "2021-01-12T00:00:00" + }, { + "probPrecipitacion" : [ { + "value" : 0 + } ], + "cotaNieveProv" : [ { + "value" : "" + } ], + "estadoCielo" : [ { + "value" : "11", + "descripcion" : "Despejado" + } ], + "viento" : [ { + "direccion" : "C", + "velocidad" : 0 + } ], + "rachaMax" : [ { + "value" : "" + } ], + "temperatura" : { + "maxima" : 6, + "minima" : -11, + "dato" : [ ] + }, + "sensTermica" : { + "maxima" : 6, + "minima" : -11, + "dato" : [ ] + }, + "humedadRelativa" : { + "maxima" : 100, + "minima" : 65, + "dato" : [ ] + }, + "uvMax" : 2, + "fecha" : "2021-01-13T00:00:00" + }, { + "probPrecipitacion" : [ { + "value" : 0 + } ], + "cotaNieveProv" : [ { + "value" : "" + } ], + "estadoCielo" : [ { + "value" : "12", + "descripcion" : "Poco nuboso" + } ], + "viento" : [ { + "direccion" : "C", + "velocidad" : 0 + } ], + "rachaMax" : [ { + "value" : "" + } ], + "temperatura" : { + "maxima" : 6, + "minima" : -7, + "dato" : [ ] + }, + "sensTermica" : { + "maxima" : 6, + "minima" : -7, + "dato" : [ ] + }, + "humedadRelativa" : { + "maxima" : 100, + "minima" : 80, + "dato" : [ ] + }, + "fecha" : "2021-01-14T00:00:00" + }, { + "probPrecipitacion" : [ { + "value" : 0 + } ], + "cotaNieveProv" : [ { + "value" : "" + } ], + "estadoCielo" : [ { + "value" : "14", + "descripcion" : "Nuboso" + } ], + "viento" : [ { + "direccion" : "C", + "velocidad" : 0 + } ], + "rachaMax" : [ { + "value" : "" + } ], + "temperatura" : { + "maxima" : 5, + "minima" : -4, + "dato" : [ ] + }, + "sensTermica" : { + "maxima" : 5, + "minima" : -4, + "dato" : [ ] + }, + "humedadRelativa" : { + "maxima" : 100, + "minima" : 55, + "dato" : [ ] + }, + "fecha" : "2021-01-15T00:00:00" + } ] + }, + "id" : 28065, + "version" : 1.0 +} ] diff --git a/tests/fixtures/aemet/town-28065-forecast-daily.json b/tests/fixtures/aemet/town-28065-forecast-daily.json new file mode 100644 index 00000000000000..35935658c506db --- /dev/null +++ b/tests/fixtures/aemet/town-28065-forecast-daily.json @@ -0,0 +1,6 @@ +{ + "descripcion" : "exito", + "estado" : 200, + "datos" : "https://opendata.aemet.es/opendata/sh/64e29abb", + "metadatos" : "https://opendata.aemet.es/opendata/sh/dfd88b22" +} diff --git a/tests/fixtures/aemet/town-28065-forecast-hourly-data.json b/tests/fixtures/aemet/town-28065-forecast-hourly-data.json new file mode 100644 index 00000000000000..2bd3a22235a1f2 --- /dev/null +++ b/tests/fixtures/aemet/town-28065-forecast-hourly-data.json @@ -0,0 +1,1416 @@ +[ { + "origen" : { + "productor" : "Agencia Estatal de Meteorolog�a - AEMET. Gobierno de Espa�a", + "web" : "http://www.aemet.es", + "enlace" : "http://www.aemet.es/es/eltiempo/prediccion/municipios/horas/getafe-id28065", + "language" : "es", + "copyright" : "� AEMET. Autorizado el uso de la informaci�n y su reproducci�n citando a AEMET como autora de la misma.", + "notaLegal" : "http://www.aemet.es/es/nota_legal" + }, + "elaborado" : "2021-01-09T11:47:45", + "nombre" : "Getafe", + "provincia" : "Madrid", + "prediccion" : { + "dia" : [ { + "estadoCielo" : [ { + "value" : "36n", + "periodo" : "07", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36n", + "periodo" : "08", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "09", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "10", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "11", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "12", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "13", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "46", + "periodo" : "14", + "descripcion" : "Cubierto con lluvia escasa" + }, { + "value" : "46", + "periodo" : "15", + "descripcion" : "Cubierto con lluvia escasa" + }, { + "value" : "36", + "periodo" : "16", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "36", + "periodo" : "17", + "descripcion" : "Cubierto con nieve" + }, { + "value" : "74n", + "periodo" : "18", + "descripcion" : "Cubierto con nieve escasa" + }, { + "value" : "46n", + "periodo" : "19", + "descripcion" : "Cubierto con lluvia escasa" + }, { + "value" : "46n", + "periodo" : "20", + "descripcion" : "Cubierto con lluvia escasa" + }, { + "value" : "16n", + "periodo" : "21", + "descripcion" : "Cubierto" + }, { + "value" : "16n", + "periodo" : "22", + "descripcion" : "Cubierto" + }, { + "value" : "12n", + "periodo" : "23", + "descripcion" : "Poco nuboso" + } ], + "precipitacion" : [ { + "value" : "1.4", + "periodo" : "07" + }, { + "value" : "2.1", + "periodo" : "08" + }, { + "value" : "1.9", + "periodo" : "09" + }, { + "value" : "2", + "periodo" : "10" + }, { + "value" : "1.9", + "periodo" : "11" + }, { + "value" : "1.8", + "periodo" : "12" + }, { + "value" : "1.5", + "periodo" : "13" + }, { + "value" : "0.5", + "periodo" : "14" + }, { + "value" : "0.6", + "periodo" : "15" + }, { + "value" : "0.8", + "periodo" : "16" + }, { + "value" : "0.6", + "periodo" : "17" + }, { + "value" : "0.2", + "periodo" : "18" + }, { + "value" : "0.2", + "periodo" : "19" + }, { + "value" : "0.1", + "periodo" : "20" + }, { + "value" : "0", + "periodo" : "21" + }, { + "value" : "0", + "periodo" : "22" + }, { + "value" : "0", + "periodo" : "23" + } ], + "probPrecipitacion" : [ { + "value" : "", + "periodo" : "0107" + }, { + "value" : "100", + "periodo" : "0713" + }, { + "value" : "100", + "periodo" : "1319" + }, { + "value" : "100", + "periodo" : "1901" + } ], + "probTormenta" : [ { + "value" : "", + "periodo" : "0107" + }, { + "value" : "0", + "periodo" : "0713" + }, { + "value" : "0", + "periodo" : "1319" + }, { + "value" : "0", + "periodo" : "1901" + } ], + "nieve" : [ { + "value" : "1.4", + "periodo" : "07" + }, { + "value" : "2.1", + "periodo" : "08" + }, { + "value" : "1.9", + "periodo" : "09" + }, { + "value" : "2", + "periodo" : "10" + }, { + "value" : "1.9", + "periodo" : "11" + }, { + "value" : "1.8", + "periodo" : "12" + }, { + "value" : "1.2", + "periodo" : "13" + }, { + "value" : "0.1", + "periodo" : "14" + }, { + "value" : "0.2", + "periodo" : "15" + }, { + "value" : "0.6", + "periodo" : "16" + }, { + "value" : "0.6", + "periodo" : "17" + }, { + "value" : "0.2", + "periodo" : "18" + }, { + "value" : "0.1", + "periodo" : "19" + }, { + "value" : "0", + "periodo" : "20" + }, { + "value" : "0", + "periodo" : "21" + }, { + "value" : "0", + "periodo" : "22" + }, { + "value" : "0", + "periodo" : "23" + } ], + "probNieve" : [ { + "value" : "", + "periodo" : "0107" + }, { + "value" : "100", + "periodo" : "0713" + }, { + "value" : "100", + "periodo" : "1319" + }, { + "value" : "80", + "periodo" : "1901" + } ], + "temperatura" : [ { + "value" : "-1", + "periodo" : "07" + }, { + "value" : "-1", + "periodo" : "08" + }, { + "value" : "-1", + "periodo" : "09" + }, { + "value" : "-1", + "periodo" : "10" + }, { + "value" : "-1", + "periodo" : "11" + }, { + "value" : "-0", + "periodo" : "12" + }, { + "value" : "-0", + "periodo" : "13" + }, { + "value" : "0", + "periodo" : "14" + }, { + "value" : "1", + "periodo" : "15" + }, { + "value" : "1", + "periodo" : "16" + }, { + "value" : "1", + "periodo" : "17" + }, { + "value" : "1", + "periodo" : "18" + }, { + "value" : "1", + "periodo" : "19" + }, { + "value" : "1", + "periodo" : "20" + }, { + "value" : "1", + "periodo" : "21" + }, { + "value" : "1", + "periodo" : "22" + }, { + "value" : "1", + "periodo" : "23" + } ], + "sensTermica" : [ { + "value" : "-8", + "periodo" : "07" + }, { + "value" : "-7", + "periodo" : "08" + }, { + "value" : "-7", + "periodo" : "09" + }, { + "value" : "-6", + "periodo" : "10" + }, { + "value" : "-6", + "periodo" : "11" + }, { + "value" : "-4", + "periodo" : "12" + }, { + "value" : "-4", + "periodo" : "13" + }, { + "value" : "-4", + "periodo" : "14" + }, { + "value" : "-2", + "periodo" : "15" + }, { + "value" : "-2", + "periodo" : "16" + }, { + "value" : "-2", + "periodo" : "17" + }, { + "value" : "1", + "periodo" : "18" + }, { + "value" : "-2", + "periodo" : "19" + }, { + "value" : "1", + "periodo" : "20" + }, { + "value" : "1", + "periodo" : "21" + }, { + "value" : "1", + "periodo" : "22" + }, { + "value" : "-2", + "periodo" : "23" + } ], + "humedadRelativa" : [ { + "value" : "96", + "periodo" : "07" + }, { + "value" : "96", + "periodo" : "08" + }, { + "value" : "99", + "periodo" : "09" + }, { + "value" : "100", + "periodo" : "10" + }, { + "value" : "100", + "periodo" : "11" + }, { + "value" : "100", + "periodo" : "12" + }, { + "value" : "100", + "periodo" : "13" + }, { + "value" : "100", + "periodo" : "14" + }, { + "value" : "100", + "periodo" : "15" + }, { + "value" : "97", + "periodo" : "16" + }, { + "value" : "94", + "periodo" : "17" + }, { + "value" : "93", + "periodo" : "18" + }, { + "value" : "93", + "periodo" : "19" + }, { + "value" : "92", + "periodo" : "20" + }, { + "value" : "89", + "periodo" : "21" + }, { + "value" : "89", + "periodo" : "22" + }, { + "value" : "85", + "periodo" : "23" + } ], + "vientoAndRachaMax" : [ { + "direccion" : [ "NE" ], + "velocidad" : [ "28" ], + "periodo" : "07" + }, { + "value" : "41", + "periodo" : "07" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "27" ], + "periodo" : "08" + }, { + "value" : "41", + "periodo" : "08" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "25" ], + "periodo" : "09" + }, { + "value" : "39", + "periodo" : "09" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "20" ], + "periodo" : "10" + }, { + "value" : "36", + "periodo" : "10" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "17" ], + "periodo" : "11" + }, { + "value" : "29", + "periodo" : "11" + }, { + "direccion" : [ "E" ], + "velocidad" : [ "15" ], + "periodo" : "12" + }, { + "value" : "24", + "periodo" : "12" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "15" ], + "periodo" : "13" + }, { + "value" : "22", + "periodo" : "13" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "14" ], + "periodo" : "14" + }, { + "value" : "24", + "periodo" : "14" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "10" ], + "periodo" : "15" + }, { + "value" : "20", + "periodo" : "15" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "8" ], + "periodo" : "16" + }, { + "value" : "14", + "periodo" : "16" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "9" ], + "periodo" : "17" + }, { + "value" : "13", + "periodo" : "17" + }, { + "direccion" : [ "E" ], + "velocidad" : [ "7" ], + "periodo" : "18" + }, { + "value" : "13", + "periodo" : "18" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "8" ], + "periodo" : "19" + }, { + "value" : "12", + "periodo" : "19" + }, { + "direccion" : [ "SE" ], + "velocidad" : [ "6" ], + "periodo" : "20" + }, { + "value" : "12", + "periodo" : "20" + }, { + "direccion" : [ "E" ], + "velocidad" : [ "6" ], + "periodo" : "21" + }, { + "value" : "8", + "periodo" : "21" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "6" ], + "periodo" : "22" + }, { + "value" : "9", + "periodo" : "22" + }, { + "direccion" : [ "E" ], + "velocidad" : [ "8" ], + "periodo" : "23" + }, { + "value" : "11", + "periodo" : "23" + } ], + "fecha" : "2021-01-09T00:00:00", + "orto" : "08:37", + "ocaso" : "18:07" + }, { + "estadoCielo" : [ { + "value" : "12n", + "periodo" : "00", + "descripcion" : "Poco nuboso" + }, { + "value" : "81n", + "periodo" : "01", + "descripcion" : "Niebla" + }, { + "value" : "81n", + "periodo" : "02", + "descripcion" : "Niebla" + }, { + "value" : "81n", + "periodo" : "03", + "descripcion" : "Niebla" + }, { + "value" : "17n", + "periodo" : "04", + "descripcion" : "Nubes altas" + }, { + "value" : "16n", + "periodo" : "05", + "descripcion" : "Cubierto" + }, { + "value" : "16n", + "periodo" : "06", + "descripcion" : "Cubierto" + }, { + "value" : "16n", + "periodo" : "07", + "descripcion" : "Cubierto" + }, { + "value" : "16n", + "periodo" : "08", + "descripcion" : "Cubierto" + }, { + "value" : "14", + "periodo" : "09", + "descripcion" : "Nuboso" + }, { + "value" : "12", + "periodo" : "10", + "descripcion" : "Poco nuboso" + }, { + "value" : "12", + "periodo" : "11", + "descripcion" : "Poco nuboso" + }, { + "value" : "17", + "periodo" : "12", + "descripcion" : "Nubes altas" + }, { + "value" : "17", + "periodo" : "13", + "descripcion" : "Nubes altas" + }, { + "value" : "17", + "periodo" : "14", + "descripcion" : "Nubes altas" + }, { + "value" : "17", + "periodo" : "15", + "descripcion" : "Nubes altas" + }, { + "value" : "17", + "periodo" : "16", + "descripcion" : "Nubes altas" + }, { + "value" : "17", + "periodo" : "17", + "descripcion" : "Nubes altas" + }, { + "value" : "12n", + "periodo" : "18", + "descripcion" : "Poco nuboso" + }, { + "value" : "12n", + "periodo" : "19", + "descripcion" : "Poco nuboso" + }, { + "value" : "14n", + "periodo" : "20", + "descripcion" : "Nuboso" + }, { + "value" : "16n", + "periodo" : "21", + "descripcion" : "Cubierto" + }, { + "value" : "16n", + "periodo" : "22", + "descripcion" : "Cubierto" + }, { + "value" : "15n", + "periodo" : "23", + "descripcion" : "Muy nuboso" + } ], + "precipitacion" : [ { + "value" : "0", + "periodo" : "00" + }, { + "value" : "0", + "periodo" : "01" + }, { + "value" : "0", + "periodo" : "02" + }, { + "value" : "0", + "periodo" : "03" + }, { + "value" : "0", + "periodo" : "04" + }, { + "value" : "0", + "periodo" : "05" + }, { + "value" : "0", + "periodo" : "06" + }, { + "value" : "0", + "periodo" : "07" + }, { + "value" : "0", + "periodo" : "08" + }, { + "value" : "Ip", + "periodo" : "09" + }, { + "value" : "0", + "periodo" : "10" + }, { + "value" : "0", + "periodo" : "11" + }, { + "value" : "0", + "periodo" : "12" + }, { + "value" : "0", + "periodo" : "13" + }, { + "value" : "0", + "periodo" : "14" + }, { + "value" : "0", + "periodo" : "15" + }, { + "value" : "0", + "periodo" : "16" + }, { + "value" : "0", + "periodo" : "17" + }, { + "value" : "0", + "periodo" : "18" + }, { + "value" : "0", + "periodo" : "19" + }, { + "value" : "0", + "periodo" : "20" + }, { + "value" : "0", + "periodo" : "21" + }, { + "value" : "0", + "periodo" : "22" + }, { + "value" : "0", + "periodo" : "23" + } ], + "probPrecipitacion" : [ { + "value" : "10", + "periodo" : "0107" + }, { + "value" : "15", + "periodo" : "0713" + }, { + "value" : "5", + "periodo" : "1319" + }, { + "value" : "0", + "periodo" : "1901" + } ], + "probTormenta" : [ { + "value" : "0", + "periodo" : "0107" + }, { + "value" : "0", + "periodo" : "0713" + }, { + "value" : "0", + "periodo" : "1319" + }, { + "value" : "0", + "periodo" : "1901" + } ], + "nieve" : [ { + "value" : "0", + "periodo" : "00" + }, { + "value" : "0", + "periodo" : "01" + }, { + "value" : "0", + "periodo" : "02" + }, { + "value" : "0", + "periodo" : "03" + }, { + "value" : "0", + "periodo" : "04" + }, { + "value" : "0", + "periodo" : "05" + }, { + "value" : "0", + "periodo" : "06" + }, { + "value" : "0", + "periodo" : "07" + }, { + "value" : "0", + "periodo" : "08" + }, { + "value" : "Ip", + "periodo" : "09" + }, { + "value" : "0", + "periodo" : "10" + }, { + "value" : "0", + "periodo" : "11" + }, { + "value" : "0", + "periodo" : "12" + }, { + "value" : "0", + "periodo" : "13" + }, { + "value" : "0", + "periodo" : "14" + }, { + "value" : "0", + "periodo" : "15" + }, { + "value" : "0", + "periodo" : "16" + }, { + "value" : "0", + "periodo" : "17" + }, { + "value" : "0", + "periodo" : "18" + }, { + "value" : "0", + "periodo" : "19" + }, { + "value" : "0", + "periodo" : "20" + }, { + "value" : "0", + "periodo" : "21" + }, { + "value" : "0", + "periodo" : "22" + }, { + "value" : "0", + "periodo" : "23" + } ], + "probNieve" : [ { + "value" : "10", + "periodo" : "0107" + }, { + "value" : "10", + "periodo" : "0713" + }, { + "value" : "0", + "periodo" : "1319" + }, { + "value" : "0", + "periodo" : "1901" + } ], + "temperatura" : [ { + "value" : "1", + "periodo" : "00" + }, { + "value" : "0", + "periodo" : "01" + }, { + "value" : "-0", + "periodo" : "02" + }, { + "value" : "-0", + "periodo" : "03" + }, { + "value" : "-1", + "periodo" : "04" + }, { + "value" : "-1", + "periodo" : "05" + }, { + "value" : "-1", + "periodo" : "06" + }, { + "value" : "-2", + "periodo" : "07" + }, { + "value" : "-1", + "periodo" : "08" + }, { + "value" : "-1", + "periodo" : "09" + }, { + "value" : "0", + "periodo" : "10" + }, { + "value" : "2", + "periodo" : "11" + }, { + "value" : "3", + "periodo" : "12" + }, { + "value" : "3", + "periodo" : "13" + }, { + "value" : "3", + "periodo" : "14" + }, { + "value" : "4", + "periodo" : "15" + }, { + "value" : "3", + "periodo" : "16" + }, { + "value" : "2", + "periodo" : "17" + }, { + "value" : "1", + "periodo" : "18" + }, { + "value" : "1", + "periodo" : "19" + }, { + "value" : "1", + "periodo" : "20" + }, { + "value" : "1", + "periodo" : "21" + }, { + "value" : "0", + "periodo" : "22" + }, { + "value" : "-0", + "periodo" : "23" + } ], + "sensTermica" : [ { + "value" : "1", + "periodo" : "00" + }, { + "value" : "0", + "periodo" : "01" + }, { + "value" : "-0", + "periodo" : "02" + }, { + "value" : "-0", + "periodo" : "03" + }, { + "value" : "-4", + "periodo" : "04" + }, { + "value" : "-1", + "periodo" : "05" + }, { + "value" : "-4", + "periodo" : "06" + }, { + "value" : "-6", + "periodo" : "07" + }, { + "value" : "-6", + "periodo" : "08" + }, { + "value" : "-7", + "periodo" : "09" + }, { + "value" : "-5", + "periodo" : "10" + }, { + "value" : "-3", + "periodo" : "11" + }, { + "value" : "-2", + "periodo" : "12" + }, { + "value" : "-1", + "periodo" : "13" + }, { + "value" : "-1", + "periodo" : "14" + }, { + "value" : "0", + "periodo" : "15" + }, { + "value" : "-1", + "periodo" : "16" + }, { + "value" : "-2", + "periodo" : "17" + }, { + "value" : "-4", + "periodo" : "18" + }, { + "value" : "-4", + "periodo" : "19" + }, { + "value" : "-3", + "periodo" : "20" + }, { + "value" : "-4", + "periodo" : "21" + }, { + "value" : "-5", + "periodo" : "22" + }, { + "value" : "-5", + "periodo" : "23" + } ], + "humedadRelativa" : [ { + "value" : "74", + "periodo" : "00" + }, { + "value" : "71", + "periodo" : "01" + }, { + "value" : "80", + "periodo" : "02" + }, { + "value" : "84", + "periodo" : "03" + }, { + "value" : "81", + "periodo" : "04" + }, { + "value" : "78", + "periodo" : "05" + }, { + "value" : "90", + "periodo" : "06" + }, { + "value" : "100", + "periodo" : "07" + }, { + "value" : "100", + "periodo" : "08" + }, { + "value" : "93", + "periodo" : "09" + }, { + "value" : "84", + "periodo" : "10" + }, { + "value" : "78", + "periodo" : "11" + }, { + "value" : "73", + "periodo" : "12" + }, { + "value" : "74", + "periodo" : "13" + }, { + "value" : "74", + "periodo" : "14" + }, { + "value" : "73", + "periodo" : "15" + }, { + "value" : "78", + "periodo" : "16" + }, { + "value" : "79", + "periodo" : "17" + }, { + "value" : "79", + "periodo" : "18" + }, { + "value" : "77", + "periodo" : "19" + }, { + "value" : "75", + "periodo" : "20" + }, { + "value" : "77", + "periodo" : "21" + }, { + "value" : "80", + "periodo" : "22" + }, { + "value" : "80", + "periodo" : "23" + } ], + "vientoAndRachaMax" : [ { + "direccion" : [ "NE" ], + "velocidad" : [ "6" ], + "periodo" : "00" + }, { + "value" : "12", + "periodo" : "00" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "5" ], + "periodo" : "01" + }, { + "value" : "10", + "periodo" : "01" + }, { + "direccion" : [ "N" ], + "velocidad" : [ "6" ], + "periodo" : "02" + }, { + "value" : "11", + "periodo" : "02" + }, { + "direccion" : [ "N" ], + "velocidad" : [ "6" ], + "periodo" : "03" + }, { + "value" : "9", + "periodo" : "03" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "8" ], + "periodo" : "04" + }, { + "value" : "12", + "periodo" : "04" + }, { + "direccion" : [ "N" ], + "velocidad" : [ "5" ], + "periodo" : "05" + }, { + "value" : "11", + "periodo" : "05" + }, { + "direccion" : [ "N" ], + "velocidad" : [ "9" ], + "periodo" : "06" + }, { + "value" : "13", + "periodo" : "06" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "13" ], + "periodo" : "07" + }, { + "value" : "18", + "periodo" : "07" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "17" ], + "periodo" : "08" + }, { + "value" : "25", + "periodo" : "08" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "21" ], + "periodo" : "09" + }, { + "value" : "31", + "periodo" : "09" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "21" ], + "periodo" : "10" + }, { + "value" : "32", + "periodo" : "10" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "21" ], + "periodo" : "11" + }, { + "value" : "30", + "periodo" : "11" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "22" ], + "periodo" : "12" + }, { + "value" : "32", + "periodo" : "12" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "20" ], + "periodo" : "13" + }, { + "value" : "32", + "periodo" : "13" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "19" ], + "periodo" : "14" + }, { + "value" : "30", + "periodo" : "14" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "17" ], + "periodo" : "15" + }, { + "value" : "28", + "periodo" : "15" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "16" ], + "periodo" : "16" + }, { + "value" : "25", + "periodo" : "16" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "16" ], + "periodo" : "17" + }, { + "value" : "24", + "periodo" : "17" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "17" ], + "periodo" : "18" + }, { + "value" : "24", + "periodo" : "18" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "17" ], + "periodo" : "19" + }, { + "value" : "25", + "periodo" : "19" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "16" ], + "periodo" : "20" + }, { + "value" : "25", + "periodo" : "20" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "17" ], + "periodo" : "21" + }, { + "value" : "24", + "periodo" : "21" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "19" ], + "periodo" : "22" + }, { + "value" : "27", + "periodo" : "22" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "21" ], + "periodo" : "23" + }, { + "value" : "30", + "periodo" : "23" + } ], + "fecha" : "2021-01-10T00:00:00", + "orto" : "08:36", + "ocaso" : "18:08" + }, { + "estadoCielo" : [ { + "value" : "14n", + "periodo" : "00", + "descripcion" : "Nuboso" + }, { + "value" : "12n", + "periodo" : "01", + "descripcion" : "Poco nuboso" + }, { + "value" : "11n", + "periodo" : "02", + "descripcion" : "Despejado" + }, { + "value" : "11n", + "periodo" : "03", + "descripcion" : "Despejado" + }, { + "value" : "11n", + "periodo" : "04", + "descripcion" : "Despejado" + }, { + "value" : "11n", + "periodo" : "05", + "descripcion" : "Despejado" + }, { + "value" : "11n", + "periodo" : "06", + "descripcion" : "Despejado" + } ], + "precipitacion" : [ { + "value" : "0", + "periodo" : "00" + }, { + "value" : "0", + "periodo" : "01" + }, { + "value" : "0", + "periodo" : "02" + }, { + "value" : "0", + "periodo" : "03" + }, { + "value" : "0", + "periodo" : "04" + }, { + "value" : "0", + "periodo" : "05" + }, { + "value" : "0", + "periodo" : "06" + } ], + "probPrecipitacion" : [ { + "value" : "0", + "periodo" : "0107" + }, { + "value" : "", + "periodo" : "0713" + }, { + "value" : "", + "periodo" : "1319" + }, { + "value" : "", + "periodo" : "1901" + } ], + "probTormenta" : [ { + "value" : "0", + "periodo" : "0107" + }, { + "value" : "", + "periodo" : "0713" + }, { + "value" : "", + "periodo" : "1319" + }, { + "value" : "", + "periodo" : "1901" + } ], + "nieve" : [ { + "value" : "0", + "periodo" : "00" + }, { + "value" : "0", + "periodo" : "01" + }, { + "value" : "0", + "periodo" : "02" + }, { + "value" : "0", + "periodo" : "03" + }, { + "value" : "0", + "periodo" : "04" + }, { + "value" : "0", + "periodo" : "05" + }, { + "value" : "0", + "periodo" : "06" + } ], + "probNieve" : [ { + "value" : "0", + "periodo" : "0107" + }, { + "value" : "", + "periodo" : "0713" + }, { + "value" : "", + "periodo" : "1319" + }, { + "value" : "", + "periodo" : "1901" + } ], + "temperatura" : [ { + "value" : "-1", + "periodo" : "00" + }, { + "value" : "-1", + "periodo" : "01" + }, { + "value" : "-2", + "periodo" : "02" + }, { + "value" : "-2", + "periodo" : "03" + }, { + "value" : "-3", + "periodo" : "04" + }, { + "value" : "-4", + "periodo" : "05" + }, { + "value" : "-4", + "periodo" : "06" + } ], + "sensTermica" : [ { + "value" : "-6", + "periodo" : "00" + }, { + "value" : "-6", + "periodo" : "01" + }, { + "value" : "-6", + "periodo" : "02" + }, { + "value" : "-6", + "periodo" : "03" + }, { + "value" : "-7", + "periodo" : "04" + }, { + "value" : "-8", + "periodo" : "05" + }, { + "value" : "-8", + "periodo" : "06" + } ], + "humedadRelativa" : [ { + "value" : "81", + "periodo" : "00" + }, { + "value" : "79", + "periodo" : "01" + }, { + "value" : "77", + "periodo" : "02" + }, { + "value" : "76", + "periodo" : "03" + }, { + "value" : "76", + "periodo" : "04" + }, { + "value" : "76", + "periodo" : "05" + }, { + "value" : "78", + "periodo" : "06" + } ], + "vientoAndRachaMax" : [ { + "direccion" : [ "NE" ], + "velocidad" : [ "19" ], + "periodo" : "00" + }, { + "value" : "30", + "periodo" : "00" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "16" ], + "periodo" : "01" + }, { + "value" : "27", + "periodo" : "01" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "12" ], + "periodo" : "02" + }, { + "value" : "22", + "periodo" : "02" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "10" ], + "periodo" : "03" + }, { + "value" : "17", + "periodo" : "03" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "11" ], + "periodo" : "04" + }, { + "value" : "15", + "periodo" : "04" + }, { + "direccion" : [ "NE" ], + "velocidad" : [ "10" ], + "periodo" : "05" + }, { + "value" : "15", + "periodo" : "05" + }, { + "direccion" : [ "N" ], + "velocidad" : [ "10" ], + "periodo" : "06" + }, { + "value" : "15", + "periodo" : "06" + } ], + "fecha" : "2021-01-11T00:00:00", + "orto" : "08:36", + "ocaso" : "18:09" + } ] + }, + "id" : "28065", + "version" : "1.0" +} ] diff --git a/tests/fixtures/aemet/town-28065-forecast-hourly.json b/tests/fixtures/aemet/town-28065-forecast-hourly.json new file mode 100644 index 00000000000000..2fbcaaeb33e48a --- /dev/null +++ b/tests/fixtures/aemet/town-28065-forecast-hourly.json @@ -0,0 +1,6 @@ +{ + "descripcion" : "exito", + "estado" : 200, + "datos" : "https://opendata.aemet.es/opendata/sh/18ca1886", + "metadatos" : "https://opendata.aemet.es/opendata/sh/93a7c63d" +} diff --git a/tests/fixtures/aemet/town-id28065.json b/tests/fixtures/aemet/town-id28065.json new file mode 100644 index 00000000000000..342b163062c539 --- /dev/null +++ b/tests/fixtures/aemet/town-id28065.json @@ -0,0 +1,15 @@ +[ { + "latitud" : "40�18'14.535144\"", + "id_old" : "28325", + "url" : "getafe-id28065", + "latitud_dec" : "40.30403754", + "altitud" : "622", + "capital" : "Getafe", + "num_hab" : "173057", + "zona_comarcal" : "722802", + "destacada" : "1", + "nombre" : "Getafe", + "longitud_dec" : "-3.72935236", + "id" : "id28065", + "longitud" : "-3�43'45.668496\"" +} ] diff --git a/tests/fixtures/aemet/town-list.json b/tests/fixtures/aemet/town-list.json new file mode 100644 index 00000000000000..d5ed23ef9350c5 --- /dev/null +++ b/tests/fixtures/aemet/town-list.json @@ -0,0 +1,43 @@ +[ { + "latitud" : "40�18'14.535144\"", + "id_old" : "28325", + "url" : "getafe-id28065", + "latitud_dec" : "40.30403754", + "altitud" : "622", + "capital" : "Getafe", + "num_hab" : "173057", + "zona_comarcal" : "722802", + "destacada" : "1", + "nombre" : "Getafe", + "longitud_dec" : "-3.72935236", + "id" : "id28065", + "longitud" : "-3�43'45.668496\"" +}, { + "latitud" : "40�19'54.277752\"", + "id_old" : "28370", + "url" : "leganes-id28074", + "latitud_dec" : "40.33174382", + "altitud" : "667", + "capital" : "Legan�s", + "num_hab" : "186696", + "zona_comarcal" : "722802", + "destacada" : "1", + "nombre" : "Legan�s", + "longitud_dec" : "-3.76655557", + "id" : "id28074", + "longitud" : "-3�45'59.600052\"" +}, { + "latitud" : "40�24'30.282876\"", + "id_old" : "28001", + "url" : "madrid-id28079", + "latitud_dec" : "40.40841191", + "altitud" : "657", + "capital" : "Madrid", + "num_hab" : "3165235", + "zona_comarcal" : "722802", + "destacada" : "1", + "nombre" : "Madrid", + "longitud_dec" : "-3.68760088", + "id" : "id28079", + "longitud" : "-3�41'15.363168\"" +} ] From 7148071be89b0ecbb5cfb33140b3f4702db7035f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 13 Feb 2021 23:50:25 +0100 Subject: [PATCH 0452/1818] Improve Elgato code quality (#46505) --- .../components/elgato/config_flow.py | 128 +++++----- homeassistant/components/elgato/light.py | 24 +- tests/components/elgato/__init__.py | 11 +- tests/components/elgato/test_config_flow.py | 219 +++++++----------- tests/components/elgato/test_init.py | 2 +- tests/components/elgato/test_light.py | 3 +- 6 files changed, 153 insertions(+), 234 deletions(-) diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index 60cc08dc9b3e32..e9138afd86c7c9 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -1,13 +1,15 @@ """Config flow to configure the Elgato Key Light integration.""" -from typing import Any, Dict, Optional +from __future__ import annotations -from elgato import Elgato, ElgatoError, Info +from typing import Any, Dict + +from elgato import Elgato, ElgatoError import voluptuous as vol from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.typing import ConfigType from .const import CONF_SERIAL_NUMBER, DOMAIN # pylint: disable=unused-import @@ -18,91 +20,54 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL + host: str + port: int + serial_number: str + async def async_step_user( - self, user_input: Optional[ConfigType] = None + self, user_input: Dict[str, Any] | None = None ) -> Dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: - return self._show_setup_form() + return self._async_show_setup_form() + + self.host = user_input[CONF_HOST] + self.port = user_input[CONF_PORT] try: - info = await self._get_elgato_info( - user_input[CONF_HOST], user_input[CONF_PORT] - ) + await self._get_elgato_serial_number(raise_on_progress=False) except ElgatoError: - return self._show_setup_form({"base": "cannot_connect"}) - - # Check if already configured - await self.async_set_unique_id(info.serial_number) - self._abort_if_unique_id_configured() + return self._async_show_setup_form({"base": "cannot_connect"}) - return self.async_create_entry( - title=info.serial_number, - data={ - CONF_HOST: user_input[CONF_HOST], - CONF_PORT: user_input[CONF_PORT], - CONF_SERIAL_NUMBER: info.serial_number, - }, - ) + return self._async_create_entry() async def async_step_zeroconf( - self, user_input: Optional[ConfigType] = None + self, discovery_info: Dict[str, Any] ) -> Dict[str, Any]: """Handle zeroconf discovery.""" - if user_input is None: - return self.async_abort(reason="cannot_connect") + self.host = discovery_info[CONF_HOST] + self.port = discovery_info[CONF_PORT] try: - info = await self._get_elgato_info( - user_input[CONF_HOST], user_input[CONF_PORT] - ) + await self._get_elgato_serial_number() except ElgatoError: return self.async_abort(reason="cannot_connect") - # Check if already configured - await self.async_set_unique_id(info.serial_number) - self._abort_if_unique_id_configured(updates={CONF_HOST: user_input[CONF_HOST]}) - - self.context.update( - { - CONF_HOST: user_input[CONF_HOST], - CONF_PORT: user_input[CONF_PORT], - CONF_SERIAL_NUMBER: info.serial_number, - "title_placeholders": {"serial_number": info.serial_number}, - } + return self.async_show_form( + step_id="zeroconf_confirm", + description_placeholders={"serial_number": self.serial_number}, ) - # Prepare configuration flow - return self._show_confirm_dialog() - async def async_step_zeroconf_confirm( - self, user_input: ConfigType = None + self, _: Dict[str, Any] | None = None ) -> Dict[str, Any]: """Handle a flow initiated by zeroconf.""" - if user_input is None: - return self._show_confirm_dialog() - - try: - info = await self._get_elgato_info( - self.context.get(CONF_HOST), self.context.get(CONF_PORT) - ) - except ElgatoError: - return self.async_abort(reason="cannot_connect") - - # Check if already configured - await self.async_set_unique_id(info.serial_number) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=self.context.get(CONF_SERIAL_NUMBER), - data={ - CONF_HOST: self.context.get(CONF_HOST), - CONF_PORT: self.context.get(CONF_PORT), - CONF_SERIAL_NUMBER: self.context.get(CONF_SERIAL_NUMBER), - }, - ) + return self._async_create_entry() - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + @callback + def _async_show_setup_form( + self, errors: Dict[str, str] | None = None + ) -> Dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -115,20 +80,33 @@ def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: errors=errors or {}, ) - def _show_confirm_dialog(self) -> Dict[str, Any]: - """Show the confirm dialog to the user.""" - serial_number = self.context.get(CONF_SERIAL_NUMBER) - return self.async_show_form( - step_id="zeroconf_confirm", - description_placeholders={"serial_number": serial_number}, + @callback + def _async_create_entry(self) -> Dict[str, Any]: + return self.async_create_entry( + title=self.serial_number, + data={ + CONF_HOST: self.host, + CONF_PORT: self.port, + CONF_SERIAL_NUMBER: self.serial_number, + }, ) - async def _get_elgato_info(self, host: str, port: int) -> Info: + async def _get_elgato_serial_number(self, raise_on_progress: bool = True) -> None: """Get device information from an Elgato Key Light device.""" session = async_get_clientsession(self.hass) elgato = Elgato( - host, - port=port, + host=self.host, + port=self.port, session=session, ) - return await elgato.info() + info = await elgato.info() + + # Check if already configured + await self.async_set_unique_id( + info.serial_number, raise_on_progress=raise_on_progress + ) + self._abort_if_unique_id_configured( + updates={CONF_HOST: self.host, CONF_PORT: self.port} + ) + + self.serial_number = info.serial_number diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 313b5600248a07..eea80e60b15432 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -1,7 +1,9 @@ """Support for LED lights.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, List from elgato import Elgato, ElgatoError, Info, State @@ -42,7 +44,7 @@ async def async_setup_entry( """Set up Elgato Key Light based on a config entry.""" elgato: Elgato = hass.data[DOMAIN][entry.entry_id][DATA_ELGATO_CLIENT] info = await elgato.info() - async_add_entities([ElgatoLight(entry.entry_id, elgato, info)], True) + async_add_entities([ElgatoLight(elgato, info)], True) class ElgatoLight(LightEntity): @@ -50,15 +52,14 @@ class ElgatoLight(LightEntity): def __init__( self, - entry_id: str, elgato: Elgato, info: Info, ): """Initialize Elgato Key Light.""" - self._brightness: Optional[int] = None + self._brightness: int | None = None self._info: Info = info - self._state: Optional[bool] = None - self._temperature: Optional[int] = None + self._state: bool | None = None + self._temperature: int | None = None self._available = True self.elgato = elgato @@ -81,22 +82,22 @@ def unique_id(self) -> str: return self._info.serial_number @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" return self._brightness @property - def color_temp(self): + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" return self._temperature @property - def min_mireds(self): + def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" return 143 @property - def max_mireds(self): + def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" return 344 @@ -116,9 +117,8 @@ async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - data = {} + data: Dict[str, bool | int] = {ATTR_ON: True} - data[ATTR_ON] = True if ATTR_ON in kwargs: data[ATTR_ON] = kwargs[ATTR_ON] diff --git a/tests/components/elgato/__init__.py b/tests/components/elgato/__init__.py index 3b1942aee14f6f..ea63bc0c4d0c65 100644 --- a/tests/components/elgato/__init__.py +++ b/tests/components/elgato/__init__.py @@ -14,27 +14,26 @@ async def init_integration( skip_setup: bool = False, ) -> MockConfigEntry: """Set up the Elgato Key Light integration in Home Assistant.""" - aioclient_mock.get( - "http://1.2.3.4:9123/elgato/accessory-info", + "http://127.0.0.1:9123/elgato/accessory-info", text=load_fixture("elgato/info.json"), headers={"Content-Type": CONTENT_TYPE_JSON}, ) aioclient_mock.put( - "http://1.2.3.4:9123/elgato/lights", + "http://127.0.0.1:9123/elgato/lights", text=load_fixture("elgato/state.json"), headers={"Content-Type": CONTENT_TYPE_JSON}, ) aioclient_mock.get( - "http://1.2.3.4:9123/elgato/lights", + "http://127.0.0.1:9123/elgato/lights", text=load_fixture("elgato/state.json"), headers={"Content-Type": CONTENT_TYPE_JSON}, ) aioclient_mock.get( - "http://5.6.7.8:9123/elgato/accessory-info", + "http://127.0.0.2:9123/elgato/accessory-info", text=load_fixture("elgato/info.json"), headers={"Content-Type": CONTENT_TYPE_JSON}, ) @@ -43,7 +42,7 @@ async def init_integration( domain=DOMAIN, unique_id="CN11A1A00001", data={ - CONF_HOST: "1.2.3.4", + CONF_HOST: "127.0.0.1", CONF_PORT: 9123, CONF_SERIAL_NUMBER: "CN11A1A00001", }, diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py index c1dfa697041a6b..0f3ff032722a91 100644 --- a/tests/components/elgato/test_config_flow.py +++ b/tests/components/elgato/test_config_flow.py @@ -2,10 +2,9 @@ import aiohttp from homeassistant import data_entry_flow -from homeassistant.components.elgato import config_flow -from homeassistant.components.elgato.const import CONF_SERIAL_NUMBER +from homeassistant.components.elgato.const import CONF_SERIAL_NUMBER, DOMAIN from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF -from homeassistant.const import CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SOURCE, CONTENT_TYPE_JSON from homeassistant.core import HomeAssistant from . import init_integration @@ -14,62 +13,97 @@ from tests.test_util.aiohttp import AiohttpClientMocker -async def test_show_user_form(hass: HomeAssistant) -> None: - """Test that the user set up form is served.""" +async def test_full_user_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the full manual user flow from start to finish.""" + aioclient_mock.get( + "http://127.0.0.1:9123/elgato/accessory-info", + text=load_fixture("elgato/info.json"), + headers={"Content-Type": CONTENT_TYPE_JSON}, + ) + + # Start a discovered configuration flow, to guarantee a user flow doesn't abort + await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data={ + "host": "127.0.0.1", + "hostname": "example.local.", + "port": 9123, + "properties": {}, + }, + ) + result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - context={"source": SOURCE_USER}, + DOMAIN, + context={CONF_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_HOST: "127.0.0.1", CONF_PORT: 9123} + ) -async def test_show_zeroconf_confirm_form(hass: HomeAssistant) -> None: - """Test that the zeroconf confirmation form is served.""" - flow = config_flow.ElgatoFlowHandler() - flow.hass = hass - flow.context = {"source": SOURCE_ZEROCONF, CONF_SERIAL_NUMBER: "12345"} - result = await flow.async_step_zeroconf_confirm() + assert result["data"][CONF_HOST] == "127.0.0.1" + assert result["data"][CONF_PORT] == 9123 + assert result["data"][CONF_SERIAL_NUMBER] == "CN11A1A00001" + assert result["title"] == "CN11A1A00001" + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["description_placeholders"] == {CONF_SERIAL_NUMBER: "12345"} - assert result["step_id"] == "zeroconf_confirm" - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + entries = hass.config_entries.async_entries(DOMAIN) + assert entries[0].unique_id == "CN11A1A00001" -async def test_show_zerconf_form( +async def test_full_zeroconf_flow_implementation( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: - """Test that the zeroconf confirmation form is served.""" + """Test the zeroconf flow from start to finish.""" aioclient_mock.get( - "http://1.2.3.4:9123/elgato/accessory-info", + "http://127.0.0.1:9123/elgato/accessory-info", text=load_fixture("elgato/info.json"), headers={"Content-Type": CONTENT_TYPE_JSON}, ) - flow = config_flow.ElgatoFlowHandler() - flow.hass = hass - flow.context = {"source": SOURCE_ZEROCONF} - result = await flow.async_step_zeroconf({"host": "1.2.3.4", "port": 9123}) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data={ + "host": "127.0.0.1", + "hostname": "example.local.", + "port": 9123, + "properties": {}, + }, + ) - assert flow.context[CONF_HOST] == "1.2.3.4" - assert flow.context[CONF_PORT] == 9123 - assert flow.context[CONF_SERIAL_NUMBER] == "CN11A1A00001" assert result["description_placeholders"] == {CONF_SERIAL_NUMBER: "CN11A1A00001"} assert result["step_id"] == "zeroconf_confirm" assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["data"][CONF_HOST] == "127.0.0.1" + assert result["data"][CONF_PORT] == 9123 + assert result["data"][CONF_SERIAL_NUMBER] == "CN11A1A00001" + assert result["title"] == "CN11A1A00001" + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + async def test_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we show user form on Elgato Key Light connection error.""" - aioclient_mock.get("http://1.2.3.4/elgato/accessory-info", exc=aiohttp.ClientError) + aioclient_mock.get( + "http://127.0.0.1/elgato/accessory-info", exc=aiohttp.ClientError + ) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: "1.2.3.4", CONF_PORT: 9123}, + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 9123}, ) assert result["errors"] == {"base": "cannot_connect"} @@ -81,51 +115,20 @@ async def test_zeroconf_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on Elgato Key Light connection error.""" - aioclient_mock.get("http://1.2.3.4/elgato/accessory-info", exc=aiohttp.ClientError) + aioclient_mock.get( + "http://127.0.0.1/elgato/accessory-info", exc=aiohttp.ClientError + ) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + DOMAIN, context={"source": SOURCE_ZEROCONF}, - data={"host": "1.2.3.4", "port": 9123}, - ) - - assert result["reason"] == "cannot_connect" - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - - -async def test_zeroconf_confirm_connection_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test we abort zeroconf flow on Elgato Key Light connection error.""" - aioclient_mock.get("http://1.2.3.4/elgato/accessory-info", exc=aiohttp.ClientError) - - flow = config_flow.ElgatoFlowHandler() - flow.hass = hass - flow.context = { - "source": SOURCE_ZEROCONF, - CONF_HOST: "1.2.3.4", - CONF_PORT: 9123, - } - result = await flow.async_step_zeroconf_confirm( - user_input={CONF_HOST: "1.2.3.4", CONF_PORT: 9123} + data={"host": "127.0.0.1", "port": 9123}, ) assert result["reason"] == "cannot_connect" assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT -async def test_zeroconf_no_data( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test we abort if zeroconf provides no data.""" - flow = config_flow.ElgatoFlowHandler() - flow.hass = hass - result = await flow.async_step_zeroconf() - - assert result["reason"] == "cannot_connect" - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - - async def test_user_device_exists_abort( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: @@ -133,9 +136,9 @@ async def test_user_device_exists_abort( await init_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: "1.2.3.4", CONF_PORT: 9123}, + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 9123}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -148,84 +151,22 @@ async def test_zeroconf_device_exists_abort( await init_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - context={"source": SOURCE_ZEROCONF}, - data={"host": "1.2.3.4", "port": 9123}, + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data={"host": "127.0.0.1", "port": 9123}, ) assert result["reason"] == "already_configured" assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - context={"source": SOURCE_ZEROCONF, CONF_HOST: "1.2.3.4", "port": 9123}, - data={"host": "5.6.7.8", "port": 9123}, + DOMAIN, + context={CONF_SOURCE: SOURCE_ZEROCONF}, + data={"host": "127.0.0.2", "port": 9123}, ) assert result["reason"] == "already_configured" assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - entries = hass.config_entries.async_entries(config_flow.DOMAIN) - assert entries[0].data[CONF_HOST] == "5.6.7.8" - - -async def test_full_user_flow_implementation( - hass: HomeAssistant, aioclient_mock -) -> None: - """Test the full manual user flow from start to finish.""" - aioclient_mock.get( - "http://1.2.3.4:9123/elgato/accessory-info", - text=load_fixture("elgato/info.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - result = await hass.config_entries.flow.async_init( - config_flow.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_HOST: "1.2.3.4", CONF_PORT: 9123} - ) - - assert result["data"][CONF_HOST] == "1.2.3.4" - assert result["data"][CONF_PORT] == 9123 - assert result["data"][CONF_SERIAL_NUMBER] == "CN11A1A00001" - assert result["title"] == "CN11A1A00001" - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - - entries = hass.config_entries.async_entries(config_flow.DOMAIN) - assert entries[0].unique_id == "CN11A1A00001" - - -async def test_full_zeroconf_flow_implementation( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test the full manual user flow from start to finish.""" - aioclient_mock.get( - "http://1.2.3.4:9123/elgato/accessory-info", - text=load_fixture("elgato/info.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - - flow = config_flow.ElgatoFlowHandler() - flow.hass = hass - flow.context = {"source": SOURCE_ZEROCONF} - result = await flow.async_step_zeroconf({"host": "1.2.3.4", "port": 9123}) - - assert flow.context[CONF_HOST] == "1.2.3.4" - assert flow.context[CONF_PORT] == 9123 - assert flow.context[CONF_SERIAL_NUMBER] == "CN11A1A00001" - assert result["description_placeholders"] == {CONF_SERIAL_NUMBER: "CN11A1A00001"} - assert result["step_id"] == "zeroconf_confirm" - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - - result = await flow.async_step_zeroconf_confirm(user_input={CONF_HOST: "1.2.3.4"}) - assert result["data"][CONF_HOST] == "1.2.3.4" - assert result["data"][CONF_PORT] == 9123 - assert result["data"][CONF_SERIAL_NUMBER] == "CN11A1A00001" - assert result["title"] == "CN11A1A00001" - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + entries = hass.config_entries.async_entries(DOMAIN) + assert entries[0].data[CONF_HOST] == "127.0.0.2" diff --git a/tests/components/elgato/test_init.py b/tests/components/elgato/test_init.py index 2f0e39e05a8fef..069e533c423dc6 100644 --- a/tests/components/elgato/test_init.py +++ b/tests/components/elgato/test_init.py @@ -14,7 +14,7 @@ async def test_config_entry_not_ready( ) -> None: """Test the Elgato Key Light configuration entry not ready.""" aioclient_mock.get( - "http://1.2.3.4:9123/elgato/accessory-info", exc=aiohttp.ClientError + "http://127.0.0.1:9123/elgato/accessory-info", exc=aiohttp.ClientError ) entry = await init_integration(hass, aioclient_mock) diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py index 838608c0aac3ff..aed569c18fe884 100644 --- a/tests/components/elgato/test_light.py +++ b/tests/components/elgato/test_light.py @@ -1,7 +1,8 @@ """Tests for the Elgato Key Light light platform.""" from unittest.mock import patch -from homeassistant.components.elgato.light import ElgatoError +from elgato import ElgatoError + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, From 84488b9c281226017e0704a62695190e2c104790 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 18:21:15 -0500 Subject: [PATCH 0453/1818] Use core constants for sma (#46501) --- homeassistant/components/sma/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index 119d9a366d6ef9..94bab40a3b7d27 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -11,6 +11,7 @@ CONF_PASSWORD, CONF_PATH, CONF_SCAN_INTERVAL, + CONF_SENSORS, CONF_SSL, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP, @@ -27,7 +28,6 @@ CONF_FACTOR = "factor" CONF_GROUP = "group" CONF_KEY = "key" -CONF_SENSORS = "sensors" CONF_UNIT = "unit" GROUPS = ["user", "installer"] @@ -86,7 +86,6 @@ def _check_sensor_schema(conf): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up SMA WebConnect sensor.""" - # Check config again during load - dependency available config = _check_sensor_schema(config) From 5db4d78dc75f93c48647cb13a7aef3811261e1e7 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 18:21:42 -0500 Subject: [PATCH 0454/1818] Use core constants for rpi_rf (#46500) --- homeassistant/components/rpi_rf/switch.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rpi_rf/switch.py b/homeassistant/components/rpi_rf/switch.py index 78c2153a7b3902..4ac7283b1942d0 100644 --- a/homeassistant/components/rpi_rf/switch.py +++ b/homeassistant/components/rpi_rf/switch.py @@ -6,7 +6,12 @@ import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity -from homeassistant.const import CONF_NAME, CONF_SWITCHES, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_NAME, + CONF_PROTOCOL, + CONF_SWITCHES, + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -14,7 +19,6 @@ CONF_CODE_OFF = "code_off" CONF_CODE_ON = "code_on" CONF_GPIO = "gpio" -CONF_PROTOCOL = "protocol" CONF_PULSELENGTH = "pulselength" CONF_SIGNAL_REPETITIONS = "signal_repetitions" From dfe173d6196c0a13195cb5f0bcf33c0e84714459 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sat, 13 Feb 2021 18:22:28 -0500 Subject: [PATCH 0455/1818] Use core constants for rmvtransport (#46502) --- homeassistant/components/rmvtransport/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index eb053de8950257..da42c0cc9273fe 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES +from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_TIMEOUT, TIME_MINUTES from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -25,7 +25,6 @@ CONF_PRODUCTS = "products" CONF_TIME_OFFSET = "time_offset" CONF_MAX_JOURNEYS = "max_journeys" -CONF_TIMEOUT = "timeout" DEFAULT_NAME = "RMV Journey" From 17a4678906269b07627d4b79a76234c4a9a980d9 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 14 Feb 2021 00:06:28 +0000 Subject: [PATCH 0456/1818] [ci skip] Translation update --- .../components/philips_js/translations/cs.json | 15 +++++++++++++++ .../components/philips_js/translations/et.json | 10 +++++++++- .../components/powerwall/translations/et.json | 7 +++++-- .../components/powerwall/translations/pl.json | 2 +- .../components/roku/translations/et.json | 1 + .../components/shelly/translations/pl.json | 6 +++--- .../components/tesla/translations/et.json | 4 ++++ 7 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/philips_js/translations/cs.json diff --git a/homeassistant/components/philips_js/translations/cs.json b/homeassistant/components/philips_js/translations/cs.json new file mode 100644 index 00000000000000..8a5866b5959ebc --- /dev/null +++ b/homeassistant/components/philips_js/translations/cs.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_version": "Verze API", + "host": "Hostitel" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json index ef5e3e0ffce249..c77ef726411ce6 100644 --- a/homeassistant/components/philips_js/translations/et.json +++ b/homeassistant/components/philips_js/translations/et.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "unknown": "Ootamatu t\u00f5rge" + }, "step": { "user": { "data": { - "api_version": "API versioon" + "api_version": "API versioon", + "host": "Host" } } } diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index eaa70dc0a22511..4a937029296427 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/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": "\u00dchenduse loomine nurjus. Proovi uuesti", + "invalid_auth": "Vigane autentimine", "unknown": "Ootamatu t\u00f5rge", "wrong_version": "Teie Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, @@ -12,7 +14,8 @@ "step": { "user": { "data": { - "ip_address": "IP aadress" + "ip_address": "IP aadress", + "password": "Salas\u00f5na" }, "description": "Parool on tavaliselt Backup Gateway seerianumbri viimased 5 t\u00e4hem\u00e4rki ja selle leiad Tesla rakendusest v\u00f5i Backup Gateway 2 luugilt leitud parooli viimased 5 m\u00e4rki.", "title": "Powerwalliga \u00fchendamine" diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index 059aab2c014d7d..272f28df3b99b3 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -17,7 +17,7 @@ "ip_address": "Adres IP", "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 Telsa; lub ostatnie 5 znak\u00f3w has\u0142a na wewn\u0119trznej stronie drzwiczek Backup Gateway 2.", + "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": "Po\u0142\u0105czenie z Powerwall" } } diff --git a/homeassistant/components/roku/translations/et.json b/homeassistant/components/roku/translations/et.json index 6727f539f57248..17bce39f5dfca8 100644 --- a/homeassistant/components/roku/translations/et.json +++ b/homeassistant/components/roku/translations/et.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", "unknown": "Tundmatu viga" }, "error": { diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index a6ca567d91ac26..b0c4dd11b1b12b 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?\n\nUrz\u0105dzenia zasilane bateryjnie, z ustawionym has\u0142em, nale\u017cy wybudzi\u0107 przed konfiguracj\u0105.\nUrz\u0105dzenia zasilane bateryjnie, bez ustawionego has\u0142a, zostan\u0105 dodane gdy urz\u0105dzenie si\u0119 wybudzi. Mo\u017cesz r\u0119cznie wybudzi\u0107 urz\u0105dzenie jego przyciskiem lub poczeka\u0107 na aktualizacj\u0119 danych z urz\u0105dzenia." + "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?\n\nUrz\u0105dzenia zasilane bateryjnie, z ustawionym has\u0142em, nale\u017cy wybudzi\u0107 przed konfiguracj\u0105.\nUrz\u0105dzenia zasilane bateryjnie, bez ustawionego has\u0142a, zostan\u0105 dodane, gdy si\u0119 wybudz\u0105. Mo\u017cesz r\u0119cznie wybudzi\u0107 urz\u0105dzenie przyciskiem na obudowie lub poczeka\u0107 na aktualizacj\u0119 danych z urz\u0105dzenia." }, "credentials": { "data": { @@ -24,13 +24,13 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Przed skonfigurowaniem urz\u0105dzenia zasilane bateryjnie nale\u017cy, wybudzi\u0107 naciskaj\u0105c przycisk na urz\u0105dzeniu." + "description": "Przed skonfigurowaniem urz\u0105dzenia zasilane bateryjnie nale\u017cy, wybudzi\u0107 naciskaj\u0105c przycisk na obudowie." } } }, "device_automation": { "trigger_subtype": { - "button": "Przycisk", + "button": "przycisk", "button1": "pierwszy", "button2": "drugi", "button3": "trzeci" diff --git a/homeassistant/components/tesla/translations/et.json b/homeassistant/components/tesla/translations/et.json index ae427f5d1e7553..c7ceae36990e53 100644 --- a/homeassistant/components/tesla/translations/et.json +++ b/homeassistant/components/tesla/translations/et.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, "error": { "already_configured": "Konto on juba h\u00e4\u00e4lestatud", "cannot_connect": "\u00dchendamine nurjus", From 1845f69729f362fac26b7529e5de386f78b7ba99 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 13 Feb 2021 16:23:19 -1000 Subject: [PATCH 0457/1818] Update tuya for new fan entity model (#45870) --- homeassistant/components/tuya/fan.py | 43 ++++++++++++---------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index a66e1ff92a443a..b13b7c3602c87c 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -10,6 +10,10 @@ ) from homeassistant.const import CONF_PLATFORM, STATE_OFF from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, +) from . import TuyaDevice from .const import DOMAIN, TUYA_DATA, TUYA_DISCOVERY_NEW @@ -61,27 +65,21 @@ def __init__(self, tuya, platform): """Init Tuya fan device.""" super().__init__(tuya, platform) self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) - self.speeds = [STATE_OFF] + self.speeds = [] async def async_added_to_hass(self): """Create fan list when add to hass.""" await super().async_added_to_hass() self.speeds.extend(self._tuya.speed_list()) - def set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - if speed == STATE_OFF: + def set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + if percentage == 0: self.turn_off() else: - self._tuya.set_speed(speed) - - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # + tuya_speed = percentage_to_ordered_list_item(self.speeds, percentage) + self._tuya.set_speed(tuya_speed) + def turn_on( self, speed: str = None, @@ -90,8 +88,8 @@ def turn_on( **kwargs, ) -> None: """Turn on the fan.""" - if speed is not None: - self.set_speed(speed) + if percentage is not None: + self.set_percentage(percentage) else: self._tuya.turn_on() @@ -118,16 +116,13 @@ def is_on(self): return self._tuya.state() @property - def speed(self) -> str: + def percentage(self) -> str: """Return the current speed.""" - if self.is_on: - return self._tuya.speed() - return STATE_OFF - - @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return self.speeds + if not self.is_on: + return 0 + if self.speeds is None: + return None + return ordered_list_item_to_percentage(self.speeds, self._tuya.speed()) @property def supported_features(self) -> int: From 7a401d3d5d9aa7bf76048187e1aedb3a4235a718 Mon Sep 17 00:00:00 2001 From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Date: Sat, 13 Feb 2021 22:35:57 -0500 Subject: [PATCH 0458/1818] Fix missing condition in nws (#46513) --- homeassistant/components/nws/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/nws/const.py b/homeassistant/components/nws/const.py index 574ad6925ac906..f055bab0203465 100644 --- a/homeassistant/components/nws/const.py +++ b/homeassistant/components/nws/const.py @@ -35,7 +35,7 @@ "Hot", "Cold", ], - ATTR_CONDITION_SNOWY: ["Snow", "Sleet", "Blizzard"], + ATTR_CONDITION_SNOWY: ["Snow", "Sleet", "Snow/sleet", "Blizzard"], ATTR_CONDITION_SNOWY_RAINY: [ "Rain/snow", "Rain/sleet", From c76758f77537f29dfd2241572d877d81b9229f5e Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 04:21:02 -0500 Subject: [PATCH 0459/1818] Use core constants for temper (#46508) --- homeassistant/components/temper/sensor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py index fd26b1702dce98..c47aa1878fc1bf 100644 --- a/homeassistant/components/temper/sensor.py +++ b/homeassistant/components/temper/sensor.py @@ -5,13 +5,17 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME, TEMP_FAHRENHEIT +from homeassistant.const import ( + CONF_NAME, + CONF_OFFSET, + DEVICE_DEFAULT_NAME, + TEMP_FAHRENHEIT, +) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) CONF_SCALE = "scale" -CONF_OFFSET = "offset" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -26,7 +30,6 @@ def get_temper_devices(): """Scan the Temper devices from temperusb.""" - return TemperHandler().get_devices() From 854504cccc8de1aeb524d5536e1ba7627f4fda8a Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 04:21:53 -0500 Subject: [PATCH 0460/1818] Use core constants for switcher_kis (#46507) --- homeassistant/components/switcher_kis/__init__.py | 4 +--- homeassistant/components/switcher_kis/switch.py | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/homeassistant/components/switcher_kis/__init__.py b/homeassistant/components/switcher_kis/__init__.py index 244ed708cc7bc9..d081b3331c7244 100644 --- a/homeassistant/components/switcher_kis/__init__.py +++ b/homeassistant/components/switcher_kis/__init__.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_DEVICE_ID, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform @@ -20,7 +20,6 @@ DOMAIN = "switcher_kis" -CONF_DEVICE_ID = "device_id" CONF_DEVICE_PASSWORD = "device_password" CONF_PHONE_ID = "phone_id" @@ -48,7 +47,6 @@ async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: """Set up the switcher component.""" - phone_id = config[DOMAIN][CONF_PHONE_ID] device_id = config[DOMAIN][CONF_DEVICE_ID] device_password = config[DOMAIN][CONF_DEVICE_PASSWORD] diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index 5e75a0e6090f8c..6b4b5026c2f00e 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -62,7 +62,6 @@ async def async_setup_platform( async def async_set_auto_off_service(entity, service_call: ServiceCallType) -> None: """Use for handling setting device auto-off service calls.""" - async with SwitcherV2Api( hass.loop, device_data.ip_addr, @@ -76,7 +75,6 @@ async def async_turn_on_with_timer_service( entity, service_call: ServiceCallType ) -> None: """Use for handling turning device on with a timer service calls.""" - async with SwitcherV2Api( hass.loop, device_data.ip_addr, @@ -133,7 +131,6 @@ def unique_id(self) -> str: @property def is_on(self) -> bool: """Return True if entity is on.""" - return self._state == SWITCHER_STATE_ON @property @@ -144,7 +141,6 @@ def current_power_w(self) -> int: @property def device_state_attributes(self) -> Dict: """Return the optional state attributes.""" - attribs = {} for prop, attr in DEVICE_PROPERTIES_TO_HA_ATTRIBUTES.items(): @@ -157,7 +153,6 @@ def device_state_attributes(self) -> Dict: @property def available(self) -> bool: """Return True if entity is available.""" - return self._state in [SWITCHER_STATE_ON, SWITCHER_STATE_OFF] async def async_added_to_hass(self) -> None: @@ -188,7 +183,6 @@ async def async_turn_off(self, **kwargs: Dict) -> None: async def _control_device(self, send_on: bool) -> None: """Turn the entity on or off.""" - response: SwitcherV2ControlResponseMSG = None async with SwitcherV2Api( self.hass.loop, From 294d3c6529363b7548a81687cc4f607d7a88c5bd Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 05:28:55 -0500 Subject: [PATCH 0461/1818] Use core constants for thethingsnetwork (#46520) --- homeassistant/components/thethingsnetwork/sensor.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index eab843069f4e65..2e7b7f9499b703 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -8,7 +8,14 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED +from homeassistant.const import ( + ATTR_DEVICE_ID, + ATTR_TIME, + CONF_DEVICE_ID, + CONTENT_TYPE_JSON, + HTTP_NOT_FOUND, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -17,12 +24,9 @@ _LOGGER = logging.getLogger(__name__) -ATTR_DEVICE_ID = "device_id" ATTR_RAW = "raw" -ATTR_TIME = "time" DEFAULT_TIMEOUT = 10 -CONF_DEVICE_ID = "device_id" CONF_VALUES = "values" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( From 3ea02e646d94d61e391c8294d23c9544891cdb6b Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 05:30:40 -0500 Subject: [PATCH 0462/1818] Use core constants for trend (#46521) --- homeassistant/components/trend/binary_sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index 4b4bd48bfe34c9..b7079a3311aaa4 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -15,6 +15,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, + CONF_ATTRIBUTE, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FRIENDLY_NAME, @@ -40,7 +41,6 @@ ATTR_SAMPLE_DURATION = "sample_duration" ATTR_SAMPLE_COUNT = "sample_count" -CONF_ATTRIBUTE = "attribute" CONF_INVERT = "invert" CONF_MAX_SAMPLES = "max_samples" CONF_MIN_GRADIENT = "min_gradient" @@ -66,7 +66,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the trend sensors.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) sensors = [] From 811e1cc3e67a8276ab09f9dddf434fe316a3e1b5 Mon Sep 17 00:00:00 2001 From: Khole Date: Sun, 14 Feb 2021 10:39:31 +0000 Subject: [PATCH 0463/1818] Add hive hub 360 sensors (#46320) --- homeassistant/components/hive/binary_sensor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 30e5ae049f096c..41f1dacc8f3dc2 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -5,6 +5,8 @@ DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_MOTION, DEVICE_CLASS_OPENING, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_SOUND, BinarySensorEntity, ) @@ -14,6 +16,9 @@ "contactsensor": DEVICE_CLASS_OPENING, "motionsensor": DEVICE_CLASS_MOTION, "Connectivity": DEVICE_CLASS_CONNECTIVITY, + "SMOKE_CO": DEVICE_CLASS_SMOKE, + "DOG_BARK": DEVICE_CLASS_SOUND, + "GLASS_BREAK": DEVICE_CLASS_SOUND, } PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) From 2c3a2bd35e4590888f031275d1f7b5e1c1b56eac Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 06:16:30 -0500 Subject: [PATCH 0464/1818] Clean up template (#46509) --- homeassistant/components/template/__init__.py | 2 -- homeassistant/components/template/alarm_control_panel.py | 2 -- homeassistant/components/template/binary_sensor.py | 2 -- homeassistant/components/template/cover.py | 5 +---- homeassistant/components/template/fan.py | 1 - homeassistant/components/template/light.py | 4 ---- homeassistant/components/template/lock.py | 2 -- homeassistant/components/template/sensor.py | 3 --- homeassistant/components/template/switch.py | 2 -- homeassistant/components/template/template_entity.py | 1 - homeassistant/components/template/vacuum.py | 2 -- 11 files changed, 1 insertion(+), 25 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 079569a932462e..cc8862afcf4cae 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -7,13 +7,11 @@ async def async_setup_reload_service(hass): """Create the reload service for the template domain.""" - if hass.services.has_service(DOMAIN, SERVICE_RELOAD): return async def _reload_config(call): """Reload the template platform config.""" - await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS) hass.bus.async_fire(EVENT_TEMPLATE_RELOADED, context=call.context) diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index ccefab767be560..f56c5b2757250d 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -81,7 +81,6 @@ async def _async_create_entities(hass, config): """Create Template Alarm Control Panels.""" - alarm_control_panels = [] for device, device_config in config[CONF_ALARM_CONTROL_PANELS].items(): @@ -114,7 +113,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template Alarm Control Panels.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index f996b91a61eb32..b810c7faee16af 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -97,7 +97,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template binary sensors.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -141,7 +140,6 @@ def __init__( async def async_added_to_hass(self): """Register callbacks.""" - self.add_template_attribute("_state", self._template, None, self._update_state) if self._delay_on_raw is not None: diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 93ffd2fd988f3f..278cd1c80bbc48 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -20,6 +20,7 @@ CoverEntity, ) from homeassistant.const import ( + CONF_COVERS, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_ENTITY_PICTURE_TEMPLATE, @@ -53,8 +54,6 @@ "false", ] -CONF_COVERS = "covers" - CONF_POSITION_TEMPLATE = "position_template" CONF_TILT_TEMPLATE = "tilt_template" OPEN_ACTION = "open_cover" @@ -161,7 +160,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template cover.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -228,7 +226,6 @@ def __init__( async def async_added_to_hass(self): """Register callbacks.""" - if self._template: self.add_template_attribute( "_position", self._template, None, self._update_state diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 5d01790f21af86..ac77b3dc33347a 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -159,7 +159,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template fans.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 42493136b48e90..0edaacbb5ca853 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -137,7 +137,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template lights.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -259,7 +258,6 @@ def is_on(self): async def async_added_to_hass(self): """Register callbacks.""" - if self._template: self.add_template_attribute( "_state", self._template, None, self._update_state @@ -404,7 +402,6 @@ def _update_white_value(self, white_value): @callback def _update_state(self, result): """Update the state from the template.""" - if isinstance(result, TemplateError): # This behavior is legacy self._state = False @@ -431,7 +428,6 @@ def _update_state(self, result): @callback def _update_temperature(self, render): """Update the temperature from the template.""" - try: if render in ("None", ""): self._temperature = None diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 2cd8bc00266190..692f06e28fe712 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -60,7 +60,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template lock.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -129,7 +128,6 @@ def _update_state(self, result): async def async_added_to_hass(self): """Register callbacks.""" - self.add_template_attribute( "_state", self._state_template, None, self._update_state ) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index aea14884812f5c..c67e3a275a3ce0 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -59,7 +59,6 @@ async def _async_create_entities(hass, config): """Create the template sensors.""" - sensors = [] for device, device_config in config[CONF_SENSORS].items(): @@ -96,7 +95,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template sensors.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -140,7 +138,6 @@ def __init__( async def async_added_to_hass(self): """Register callbacks.""" - self.add_template_attribute("_state", self._template, None, self._update_state) if self._friendly_name_template is not None: self.add_template_attribute("_name", self._friendly_name_template) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 511338b5aa1171..412c4507d1f84a 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -90,7 +90,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template switches.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -147,7 +146,6 @@ def _update_state(self, result): async def async_added_to_hass(self): """Register callbacks.""" - if self._template is None: # restore state after startup diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 49b0edfab0230f..f350eb87d61686 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -223,7 +223,6 @@ def _handle_results( updates: List[TrackTemplateResult], ) -> None: """Call back the results to the attributes.""" - if event: self.async_set_context(event.context) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 5bf8148b96e75a..171aeb7af9256e 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -147,7 +147,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template vacuums.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) @@ -337,7 +336,6 @@ async def async_set_fan_speed(self, fan_speed, **kwargs): async def async_added_to_hass(self): """Register callbacks.""" - if self._template is not None: self.add_template_attribute( "_state", self._template, None, self._update_state From accba85e35d0e7e19f5bcb6855f429c6ef52b197 Mon Sep 17 00:00:00 2001 From: Andrey Kupreychik Date: Sun, 14 Feb 2021 19:09:19 +0700 Subject: [PATCH 0465/1818] Add keenetic_ndms2 config flow (#38353) --- .coveragerc | 4 + .../components/keenetic_ndms2/__init__.py | 91 ++++++ .../keenetic_ndms2/binary_sensor.py | 72 +++++ .../components/keenetic_ndms2/config_flow.py | 159 +++++++++ .../components/keenetic_ndms2/const.py | 21 ++ .../keenetic_ndms2/device_tracker.py | 301 +++++++++++++----- .../components/keenetic_ndms2/manifest.json | 5 +- .../components/keenetic_ndms2/router.py | 187 +++++++++++ .../components/keenetic_ndms2/strings.json | 36 +++ .../keenetic_ndms2/translations/en.json | 36 +++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/keenetic_ndms2/__init__.py | 27 ++ .../keenetic_ndms2/test_config_flow.py | 169 ++++++++++ 15 files changed, 1036 insertions(+), 78 deletions(-) create mode 100644 homeassistant/components/keenetic_ndms2/binary_sensor.py create mode 100644 homeassistant/components/keenetic_ndms2/config_flow.py create mode 100644 homeassistant/components/keenetic_ndms2/const.py create mode 100644 homeassistant/components/keenetic_ndms2/router.py create mode 100644 homeassistant/components/keenetic_ndms2/strings.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/en.json create mode 100644 tests/components/keenetic_ndms2/__init__.py create mode 100644 tests/components/keenetic_ndms2/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 8c7d4b3393de06..17dd078d15b952 100644 --- a/.coveragerc +++ b/.coveragerc @@ -466,7 +466,11 @@ omit = homeassistant/components/kaiterra/* homeassistant/components/kankun/switch.py homeassistant/components/keba/* + homeassistant/components/keenetic_ndms2/__init__.py + homeassistant/components/keenetic_ndms2/binary_sensor.py + homeassistant/components/keenetic_ndms2/const.py homeassistant/components/keenetic_ndms2/device_tracker.py + homeassistant/components/keenetic_ndms2/router.py homeassistant/components/kef/* homeassistant/components/keyboard/* homeassistant/components/keyboard_remote/* diff --git a/homeassistant/components/keenetic_ndms2/__init__.py b/homeassistant/components/keenetic_ndms2/__init__.py index cb0a718d71688c..42d747b5238de6 100644 --- a/homeassistant/components/keenetic_ndms2/__init__.py +++ b/homeassistant/components/keenetic_ndms2/__init__.py @@ -1 +1,92 @@ """The keenetic_ndms2 component.""" + +from homeassistant.components import binary_sensor, device_tracker +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_SCAN_INTERVAL +from homeassistant.core import Config, HomeAssistant + +from .const import ( + CONF_CONSIDER_HOME, + CONF_INCLUDE_ARP, + CONF_INCLUDE_ASSOCIATED, + CONF_INTERFACES, + CONF_TRY_HOTSPOT, + DEFAULT_CONSIDER_HOME, + DEFAULT_INTERFACE, + DEFAULT_SCAN_INTERVAL, + DOMAIN, + ROUTER, + UNDO_UPDATE_LISTENER, +) +from .router import KeeneticRouter + +PLATFORMS = [device_tracker.DOMAIN, binary_sensor.DOMAIN] + + +async def async_setup(hass: HomeAssistant, _config: Config) -> bool: + """Set up configured entries.""" + hass.data.setdefault(DOMAIN, {}) + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up the component.""" + + async_add_defaults(hass, config_entry) + + router = KeeneticRouter(hass, config_entry) + await router.async_setup() + + undo_listener = config_entry.add_update_listener(update_listener) + + hass.data[DOMAIN][config_entry.entry_id] = { + ROUTER: router, + UNDO_UPDATE_LISTENER: undo_listener, + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload a config entry.""" + hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() + + for component in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(config_entry, component) + + router: KeeneticRouter = hass.data[DOMAIN][config_entry.entry_id][ROUTER] + + await router.async_teardown() + + hass.data[DOMAIN].pop(config_entry.entry_id) + + return True + + +async def update_listener(hass, config_entry): + """Handle options update.""" + await hass.config_entries.async_reload(config_entry.entry_id) + + +def async_add_defaults(hass: HomeAssistant, config_entry: ConfigEntry): + """Populate default options.""" + host: str = config_entry.data[CONF_HOST] + imported_options: dict = hass.data[DOMAIN].get(f"imported_options_{host}", {}) + options = { + CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, + CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME, + CONF_INTERFACES: [DEFAULT_INTERFACE], + CONF_TRY_HOTSPOT: True, + CONF_INCLUDE_ARP: True, + CONF_INCLUDE_ASSOCIATED: True, + **imported_options, + **config_entry.options, + } + + if options.keys() - config_entry.options.keys(): + hass.config_entries.async_update_entry(config_entry, options=options) diff --git a/homeassistant/components/keenetic_ndms2/binary_sensor.py b/homeassistant/components/keenetic_ndms2/binary_sensor.py new file mode 100644 index 00000000000000..5da52eff00d119 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/binary_sensor.py @@ -0,0 +1,72 @@ +"""The Keenetic Client class.""" +import logging + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import KeeneticRouter +from .const import DOMAIN, ROUTER + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +): + """Set up device tracker for Keenetic NDMS2 component.""" + router: KeeneticRouter = hass.data[DOMAIN][config_entry.entry_id][ROUTER] + + async_add_entities([RouterOnlineBinarySensor(router)]) + + +class RouterOnlineBinarySensor(BinarySensorEntity): + """Representation router connection status.""" + + def __init__(self, router: KeeneticRouter): + """Initialize the APCUPSd binary device.""" + self._router = router + + @property + def name(self): + """Return the name of the online status sensor.""" + return f"{self._router.name} Online" + + @property + def unique_id(self) -> str: + """Return a unique identifier for this device.""" + return f"online_{self._router.config_entry.entry_id}" + + @property + def is_on(self): + """Return true if the UPS is online, else false.""" + return self._router.available + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_CONNECTIVITY + + @property + def should_poll(self) -> bool: + """Return False since entity pushes its state to HA.""" + return False + + @property + def device_info(self): + """Return a client description for device registry.""" + return self._router.device_info + + async def async_added_to_hass(self): + """Client entity created.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + self._router.signal_update, + self.async_write_ha_state, + ) + ) diff --git a/homeassistant/components/keenetic_ndms2/config_flow.py b/homeassistant/components/keenetic_ndms2/config_flow.py new file mode 100644 index 00000000000000..9338cb05935e90 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/config_flow.py @@ -0,0 +1,159 @@ +"""Config flow for Keenetic NDMS2.""" +from typing import List + +from ndms2_client import Client, ConnectionException, InterfaceInfo, TelnetConnection +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv + +from .const import ( + CONF_CONSIDER_HOME, + CONF_INCLUDE_ARP, + CONF_INCLUDE_ASSOCIATED, + CONF_INTERFACES, + CONF_TRY_HOTSPOT, + DEFAULT_CONSIDER_HOME, + DEFAULT_INTERFACE, + DEFAULT_SCAN_INTERVAL, + DEFAULT_TELNET_PORT, + DOMAIN, + ROUTER, +) +from .router import KeeneticRouter + + +class KeeneticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return KeeneticOptionsFlowHandler(config_entry) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + if user_input is not None: + for entry in self.hass.config_entries.async_entries(DOMAIN): + if entry.data[CONF_HOST] == user_input[CONF_HOST]: + return self.async_abort(reason="already_configured") + + _client = Client( + TelnetConnection( + user_input[CONF_HOST], + user_input[CONF_PORT], + user_input[CONF_USERNAME], + user_input[CONF_PASSWORD], + timeout=10, + ) + ) + + try: + router_info = await self.hass.async_add_executor_job( + _client.get_router_info + ) + except ConnectionException: + errors["base"] = "cannot_connect" + else: + return self.async_create_entry(title=router_info.name, data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_PORT, default=DEFAULT_TELNET_PORT): int, + } + ), + errors=errors, + ) + + async def async_step_import(self, user_input=None): + """Import a config entry.""" + return await self.async_step_user(user_input) + + +class KeeneticOptionsFlowHandler(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry: ConfigEntry): + """Initialize options flow.""" + self.config_entry = config_entry + self._interface_options = {} + + async def async_step_init(self, _user_input=None): + """Manage the options.""" + router: KeeneticRouter = self.hass.data[DOMAIN][self.config_entry.entry_id][ + ROUTER + ] + + interfaces: List[InterfaceInfo] = await self.hass.async_add_executor_job( + router.client.get_interfaces + ) + + self._interface_options = { + interface.name: (interface.description or interface.name) + for interface in interfaces + if interface.type.lower() == "bridge" + } + return await self.async_step_user() + + async def async_step_user(self, user_input=None): + """Manage the device tracker options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + options = vol.Schema( + { + vol.Required( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ), + ): int, + vol.Required( + CONF_CONSIDER_HOME, + default=self.config_entry.options.get( + CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME + ), + ): int, + vol.Required( + CONF_INTERFACES, + default=self.config_entry.options.get( + CONF_INTERFACES, [DEFAULT_INTERFACE] + ), + ): cv.multi_select(self._interface_options), + vol.Optional( + CONF_TRY_HOTSPOT, + default=self.config_entry.options.get(CONF_TRY_HOTSPOT, True), + ): bool, + vol.Optional( + CONF_INCLUDE_ARP, + default=self.config_entry.options.get(CONF_INCLUDE_ARP, True), + ): bool, + vol.Optional( + CONF_INCLUDE_ASSOCIATED, + default=self.config_entry.options.get( + CONF_INCLUDE_ASSOCIATED, True + ), + ): bool, + } + ) + + return self.async_show_form(step_id="user", data_schema=options) diff --git a/homeassistant/components/keenetic_ndms2/const.py b/homeassistant/components/keenetic_ndms2/const.py new file mode 100644 index 00000000000000..1818cfab6a661a --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/const.py @@ -0,0 +1,21 @@ +"""Constants used in the Keenetic NDMS2 components.""" + +from homeassistant.components.device_tracker.const import ( + DEFAULT_CONSIDER_HOME as _DEFAULT_CONSIDER_HOME, +) + +DOMAIN = "keenetic_ndms2" +ROUTER = "router" +UNDO_UPDATE_LISTENER = "undo_update_listener" +DEFAULT_TELNET_PORT = 23 +DEFAULT_SCAN_INTERVAL = 120 +DEFAULT_CONSIDER_HOME = _DEFAULT_CONSIDER_HOME.seconds +DEFAULT_INTERFACE = "Home" + +CONF_CONSIDER_HOME = "consider_home" +CONF_INTERFACES = "interfaces" +CONF_TRY_HOTSPOT = "try_hotspot" +CONF_INCLUDE_ARP = "include_arp" +CONF_INCLUDE_ASSOCIATED = "include_associated" + +CONF_LEGACY_INTERFACE = "interface" diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index d98806dfc05a17..9df222a326c719 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -1,102 +1,253 @@ -"""Support for Zyxel Keenetic NDMS2 based routers.""" +"""Support for Keenetic routers as device tracker.""" +from datetime import timedelta import logging +from typing import List, Optional, Set -from ndms2_client import Client, ConnectionException, TelnetConnection +from ndms2_client import Device import voluptuous as vol from homeassistant.components.device_tracker import ( - DOMAIN, - PLATFORM_SCHEMA, - DeviceScanner, + DOMAIN as DEVICE_TRACKER_DOMAIN, + PLATFORM_SCHEMA as DEVICE_TRACKER_SCHEMA, + SOURCE_TYPE_ROUTER, +) +from homeassistant.components.device_tracker.config_entry import ScannerEntity +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_USERNAME, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.dispatcher import async_dispatcher_connect +import homeassistant.util.dt as dt_util + +from .const import ( + CONF_CONSIDER_HOME, + CONF_INTERFACES, + CONF_LEGACY_INTERFACE, + DEFAULT_CONSIDER_HOME, + DEFAULT_INTERFACE, + DEFAULT_SCAN_INTERVAL, + DEFAULT_TELNET_PORT, + DOMAIN, + ROUTER, +) +from .router import KeeneticRouter _LOGGER = logging.getLogger(__name__) -# Interface name to track devices for. Most likely one will not need to -# change it from default 'Home'. This is needed not to track Guest WI-FI- -# clients and router itself -CONF_INTERFACE = "interface" - -DEFAULT_INTERFACE = "Home" -DEFAULT_PORT = 23 - - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( +PLATFORM_SCHEMA = DEVICE_TRACKER_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Required(CONF_PORT, default=DEFAULT_TELNET_PORT): cv.port, vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_INTERFACE, default=DEFAULT_INTERFACE): cv.string, + vol.Required(CONF_LEGACY_INTERFACE, default=DEFAULT_INTERFACE): cv.string, } ) -def get_scanner(_hass, config): - """Validate the configuration and return a Keenetic NDMS2 scanner.""" - scanner = KeeneticNDMS2DeviceScanner(config[DOMAIN]) - - return scanner if scanner.success_init else None - - -class KeeneticNDMS2DeviceScanner(DeviceScanner): - """This class scans for devices using keenetic NDMS2 web interface.""" +async def async_get_scanner(hass: HomeAssistant, config): + """Import legacy configuration from YAML.""" - def __init__(self, config): - """Initialize the scanner.""" + scanner_config = config[DEVICE_TRACKER_DOMAIN] + scan_interval: Optional[timedelta] = scanner_config.get(CONF_SCAN_INTERVAL) + consider_home: Optional[timedelta] = scanner_config.get(CONF_CONSIDER_HOME) - self.last_results = [] - - self._interface = config[CONF_INTERFACE] + host: str = scanner_config[CONF_HOST] + hass.data[DOMAIN][f"imported_options_{host}"] = { + CONF_INTERFACES: [scanner_config[CONF_LEGACY_INTERFACE]], + CONF_SCAN_INTERVAL: int(scan_interval.total_seconds()) + if scan_interval + else DEFAULT_SCAN_INTERVAL, + CONF_CONSIDER_HOME: int(consider_home.total_seconds()) + if consider_home + else DEFAULT_CONSIDER_HOME, + } - self._client = Client( - TelnetConnection( - config.get(CONF_HOST), - config.get(CONF_PORT), - config.get(CONF_USERNAME), - config.get(CONF_PASSWORD), - ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_HOST: scanner_config[CONF_HOST], + CONF_PORT: scanner_config[CONF_PORT], + CONF_USERNAME: scanner_config[CONF_USERNAME], + CONF_PASSWORD: scanner_config[CONF_PASSWORD], + }, + ) + ) + + _LOGGER.warning( + "Your Keenetic NDMS2 configuration has been imported into the UI, " + "please remove it from configuration.yaml. " + "Loading Keenetic NDMS2 via scanner setup is now deprecated" + ) + + return None + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +): + """Set up device tracker for Keenetic NDMS2 component.""" + router: KeeneticRouter = hass.data[DOMAIN][config_entry.entry_id][ROUTER] + + tracked = set() + + @callback + def update_from_router(): + """Update the status of devices.""" + update_items(router, async_add_entities, tracked) + + update_from_router() + + registry = await entity_registry.async_get_registry(hass) + # Restore devices that are not a part of active clients list. + restored = [] + for entity_entry in registry.entities.values(): + if ( + entity_entry.config_entry_id == config_entry.entry_id + and entity_entry.domain == DEVICE_TRACKER_DOMAIN + ): + mac = entity_entry.unique_id.partition("_")[0] + if mac not in tracked: + tracked.add(mac) + restored.append( + KeeneticTracker( + Device( + mac=mac, + # restore the original name as set by the router before + name=entity_entry.original_name, + ip=None, + interface=None, + ), + router, + ) + ) + + if restored: + async_add_entities(restored) + + async_dispatcher_connect(hass, router.signal_update, update_from_router) + + +@callback +def update_items(router: KeeneticRouter, async_add_entities, tracked: Set[str]): + """Update tracked device state from the hub.""" + new_tracked: List[KeeneticTracker] = [] + for mac, device in router.last_devices.items(): + if mac not in tracked: + tracked.add(mac) + new_tracked.append(KeeneticTracker(device, router)) + + if new_tracked: + async_add_entities(new_tracked) + + +class KeeneticTracker(ScannerEntity): + """Representation of network device.""" + + def __init__(self, device: Device, router: KeeneticRouter): + """Initialize the tracked device.""" + self._device = device + self._router = router + self._last_seen = ( + dt_util.utcnow() if device.mac in router.last_devices else None ) - self.success_init = self._update_info() - _LOGGER.info("Scanner initialized") - - def scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" - self._update_info() + @property + def should_poll(self) -> bool: + """Return False since entity pushes its state to HA.""" + return False + + @property + def is_connected(self): + """Return true if the device is connected to the network.""" + return ( + self._last_seen + and (dt_util.utcnow() - self._last_seen) + < self._router.consider_home_interval + ) - return [device.mac for device in self.last_results] + @property + def source_type(self): + """Return the source type of the client.""" + return SOURCE_TYPE_ROUTER + + @property + def name(self) -> str: + """Return the name of the device.""" + return self._device.name or self._device.mac + + @property + def unique_id(self) -> str: + """Return a unique identifier for this device.""" + return f"{self._device.mac}_{self._router.config_entry.entry_id}" + + @property + def ip_address(self) -> str: + """Return the primary ip address of the device.""" + return self._device.ip if self.is_connected else None + + @property + def mac_address(self) -> str: + """Return the mac address of the device.""" + return self._device.mac + + @property + def available(self) -> bool: + """Return if controller is available.""" + return self._router.available + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + if self.is_connected: + return { + "interface": self._device.interface, + } + return None + + @property + def device_info(self): + """Return a client description for device registry.""" + info = { + "connections": {(CONNECTION_NETWORK_MAC, self._device.mac)}, + "identifiers": {(DOMAIN, self._device.mac)}, + } + + if self._device.name: + info["name"] = self._device.name + + return info + + async def async_added_to_hass(self): + """Client entity created.""" + _LOGGER.debug("New network device tracker %s (%s)", self.name, self.unique_id) + + @callback + def update_device(): + _LOGGER.debug( + "Updating Keenetic tracked device %s (%s)", + self.entity_id, + self.unique_id, + ) + new_device = self._router.last_devices.get(self._device.mac) + if new_device: + self._device = new_device + self._last_seen = dt_util.utcnow() - def get_device_name(self, device): - """Return the name of the given device or None if we don't know.""" - name = next( - (result.name for result in self.last_results if result.mac == device), None - ) - return name + self.async_write_ha_state() - def get_extra_attributes(self, device): - """Return the IP of the given device.""" - attributes = next( - ({"ip": result.ip} for result in self.last_results if result.mac == device), - {}, + self.async_on_remove( + async_dispatcher_connect( + self.hass, self._router.signal_update, update_device + ) ) - return attributes - - def _update_info(self): - """Get ARP from keenetic router.""" - _LOGGER.debug("Fetching devices from router...") - - try: - self.last_results = [ - dev - for dev in self._client.get_devices() - if dev.interface == self._interface - ] - _LOGGER.debug("Successfully fetched data from router") - return True - - except ConnectionException: - _LOGGER.error("Error fetching data from router") - return False diff --git a/homeassistant/components/keenetic_ndms2/manifest.json b/homeassistant/components/keenetic_ndms2/manifest.json index 9d4c9f35716272..da8321a8bdc568 100644 --- a/homeassistant/components/keenetic_ndms2/manifest.json +++ b/homeassistant/components/keenetic_ndms2/manifest.json @@ -1,7 +1,8 @@ { "domain": "keenetic_ndms2", - "name": "Keenetic NDMS2 Routers", + "name": "Keenetic NDMS2 Router", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/keenetic_ndms2", - "requirements": ["ndms2_client==0.0.11"], + "requirements": ["ndms2_client==0.1.1"], "codeowners": ["@foxel"] } diff --git a/homeassistant/components/keenetic_ndms2/router.py b/homeassistant/components/keenetic_ndms2/router.py new file mode 100644 index 00000000000000..340b25ff725530 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/router.py @@ -0,0 +1,187 @@ +"""The Keenetic Client class.""" +from datetime import timedelta +import logging +from typing import Callable, Dict, Optional + +from ndms2_client import Client, ConnectionException, Device, TelnetConnection +from ndms2_client.client import RouterInfo + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_call_later +import homeassistant.util.dt as dt_util + +from .const import ( + CONF_CONSIDER_HOME, + CONF_INCLUDE_ARP, + CONF_INCLUDE_ASSOCIATED, + CONF_INTERFACES, + CONF_TRY_HOTSPOT, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +class KeeneticRouter: + """Keenetic client Object.""" + + def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry): + """Initialize the Client.""" + self.hass = hass + self.config_entry = config_entry + self._last_devices: Dict[str, Device] = {} + self._router_info: Optional[RouterInfo] = None + self._connection: Optional[TelnetConnection] = None + self._client: Optional[Client] = None + self._cancel_periodic_update: Optional[Callable] = None + self._available = False + self._progress = None + + @property + def client(self): + """Read-only accessor for the client connection.""" + return self._client + + @property + def last_devices(self): + """Read-only accessor for last_devices.""" + return self._last_devices + + @property + def host(self): + """Return the host of this hub.""" + return self.config_entry.data[CONF_HOST] + + @property + def device_info(self): + """Return the host of this hub.""" + return { + "identifiers": {(DOMAIN, f"router-{self.config_entry.entry_id}")}, + "manufacturer": self.manufacturer, + "model": self.model, + "name": self.name, + "sw_version": self.firmware, + } + + @property + def name(self): + """Return the name of the hub.""" + return self._router_info.name if self._router_info else self.host + + @property + def model(self): + """Return the model of the hub.""" + return self._router_info.model if self._router_info else None + + @property + def firmware(self): + """Return the firmware of the hub.""" + return self._router_info.fw_version if self._router_info else None + + @property + def manufacturer(self): + """Return the firmware of the hub.""" + return self._router_info.manufacturer if self._router_info else None + + @property + def available(self): + """Return if the hub is connected.""" + return self._available + + @property + def consider_home_interval(self): + """Config entry option defining number of seconds from last seen to away.""" + return timedelta(seconds=self.config_entry.options[CONF_CONSIDER_HOME]) + + @property + def signal_update(self): + """Event specific per router entry to signal updates.""" + return f"keenetic-update-{self.config_entry.entry_id}" + + async def request_update(self): + """Request an update.""" + if self._progress is not None: + await self._progress + return + + self._progress = self.hass.async_create_task(self.async_update()) + await self._progress + + self._progress = None + + async def async_update(self): + """Update devices information.""" + await self.hass.async_add_executor_job(self._update_devices) + async_dispatcher_send(self.hass, self.signal_update) + + async def async_setup(self): + """Set up the connection.""" + self._connection = TelnetConnection( + self.config_entry.data[CONF_HOST], + self.config_entry.data[CONF_PORT], + self.config_entry.data[CONF_USERNAME], + self.config_entry.data[CONF_PASSWORD], + ) + self._client = Client(self._connection) + + try: + await self.hass.async_add_executor_job(self._update_router_info) + except ConnectionException as error: + raise ConfigEntryNotReady from error + + async def async_update_data(_now): + await self.request_update() + self._cancel_periodic_update = async_call_later( + self.hass, + self.config_entry.options[CONF_SCAN_INTERVAL], + async_update_data, + ) + + await async_update_data(dt_util.utcnow()) + + async def async_teardown(self): + """Teardown up the connection.""" + if self._cancel_periodic_update: + self._cancel_periodic_update() + self._connection.disconnect() + + def _update_router_info(self): + try: + self._router_info = self._client.get_router_info() + self._available = True + except Exception: + self._available = False + raise + + def _update_devices(self): + """Get ARP from keenetic router.""" + _LOGGER.debug("Fetching devices from router...") + + try: + _response = self._client.get_devices( + try_hotspot=self.config_entry.options[CONF_TRY_HOTSPOT], + include_arp=self.config_entry.options[CONF_INCLUDE_ARP], + include_associated=self.config_entry.options[CONF_INCLUDE_ASSOCIATED], + ) + self._last_devices = { + dev.mac: dev + for dev in _response + if dev.interface in self.config_entry.options[CONF_INTERFACES] + } + _LOGGER.debug("Successfully fetched data from router: %s", str(_response)) + self._router_info = self._client.get_router_info() + self._available = True + + except ConnectionException: + _LOGGER.error("Error fetching data from router") + self._available = False diff --git a/homeassistant/components/keenetic_ndms2/strings.json b/homeassistant/components/keenetic_ndms2/strings.json new file mode 100644 index 00000000000000..15629ba0f2f1f1 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/strings.json @@ -0,0 +1,36 @@ +{ + "config": { + "step": { + "user": { + "title": "Set up Keenetic NDMS2 Router", + "data": { + "name": "Name", + "host": "[%key:common::config_flow::data::host%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "port": "[%key:common::config_flow::data::port%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scan interval", + "consider_home": "Consider home interval", + "interfaces": "Choose interfaces to scan", + "try_hotspot": "Use 'ip hotspot' data (most accurate)", + "include_arp": "Use ARP data (ignored if hotspot data used)", + "include_associated": "Use WiFi AP associations data (ignored if hotspot data used)" + } + } + } + } +} diff --git a/homeassistant/components/keenetic_ndms2/translations/en.json b/homeassistant/components/keenetic_ndms2/translations/en.json new file mode 100644 index 00000000000000..1849d68c651f63 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/en.json @@ -0,0 +1,36 @@ +{ + "config": { + "step": { + "user": { + "title": "Set up Keenetic NDMS2 Router", + "data": { + "name": "Name", + "host": "Host", + "username": "Username", + "password": "Password", + "port": "Port" + } + } + }, + "error": { + "cannot_connect": "Connection Unsuccessful" + }, + "abort": { + "already_configured": "This router is already configured" + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scan interval", + "consider_home": "Consider home interval", + "interfaces": "Choose interfaces to scan", + "try_hotspot": "Use 'ip hotspot' data (most accurate)", + "include_arp": "Use ARP data (if hotspot disabled/unavailable)", + "include_associated": "Use WiFi AP associations data (if hotspot disabled/unavailable)" + } + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 41d85c4b20b93b..6ff72cf5572527 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -113,6 +113,7 @@ "isy994", "izone", "juicenet", + "keenetic_ndms2", "kodi", "konnected", "kulersky", diff --git a/requirements_all.txt b/requirements_all.txt index d8ec67e1d30a04..de1a0b221c24f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -973,7 +973,7 @@ n26==0.2.7 nad_receiver==0.0.12 # homeassistant.components.keenetic_ndms2 -ndms2_client==0.0.11 +ndms2_client==0.1.1 # homeassistant.components.ness_alarm nessclient==0.9.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3a0ce6dd428b16..93c66806ade04a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -502,6 +502,9 @@ motionblinds==0.4.8 # homeassistant.components.tts mutagen==1.45.1 +# homeassistant.components.keenetic_ndms2 +ndms2_client==0.1.1 + # homeassistant.components.ness_alarm nessclient==0.9.15 diff --git a/tests/components/keenetic_ndms2/__init__.py b/tests/components/keenetic_ndms2/__init__.py new file mode 100644 index 00000000000000..1fce0dbe2a6fc5 --- /dev/null +++ b/tests/components/keenetic_ndms2/__init__.py @@ -0,0 +1,27 @@ +"""Tests for the Keenetic NDMS2 component.""" +from homeassistant.components.keenetic_ndms2 import const +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) + +MOCK_NAME = "Keenetic Ultra 2030" + +MOCK_DATA = { + CONF_HOST: "0.0.0.0", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 23, +} + +MOCK_OPTIONS = { + CONF_SCAN_INTERVAL: 15, + const.CONF_CONSIDER_HOME: 150, + const.CONF_TRY_HOTSPOT: False, + const.CONF_INCLUDE_ARP: True, + const.CONF_INCLUDE_ASSOCIATED: True, + const.CONF_INTERFACES: ["Home", "VPS0"], +} diff --git a/tests/components/keenetic_ndms2/test_config_flow.py b/tests/components/keenetic_ndms2/test_config_flow.py new file mode 100644 index 00000000000000..aa5369fdc0a366 --- /dev/null +++ b/tests/components/keenetic_ndms2/test_config_flow.py @@ -0,0 +1,169 @@ +"""Test Keenetic NDMS2 setup process.""" + +from unittest.mock import Mock, patch + +from ndms2_client import ConnectionException +from ndms2_client.client import InterfaceInfo, RouterInfo +import pytest + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components import keenetic_ndms2 as keenetic +from homeassistant.components.keenetic_ndms2 import const +from homeassistant.helpers.typing import HomeAssistantType + +from . import MOCK_DATA, MOCK_NAME, MOCK_OPTIONS + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="connect") +def mock_keenetic_connect(): + """Mock connection routine.""" + with patch("ndms2_client.client.Client.get_router_info") as mock_get_router_info: + mock_get_router_info.return_value = RouterInfo( + name=MOCK_NAME, + fw_version="3.0.4", + fw_channel="stable", + model="mock", + hw_version="0000", + manufacturer="pytest", + vendor="foxel", + region="RU", + ) + yield + + +@pytest.fixture(name="connect_error") +def mock_keenetic_connect_failed(): + """Mock connection routine.""" + with patch( + "ndms2_client.client.Client.get_router_info", + side_effect=ConnectionException("Mocked failure"), + ): + yield + + +async def test_flow_works(hass: HomeAssistantType, connect): + """Test config flow.""" + + result = await hass.config_entries.flow.async_init( + keenetic.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + with patch( + "homeassistant.components.keenetic_ndms2.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.keenetic_ndms2.async_setup_entry", return_value=True + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=MOCK_DATA, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == MOCK_NAME + assert result2["data"] == MOCK_DATA + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_works(hass: HomeAssistantType, connect): + """Test config flow.""" + + with patch( + "homeassistant.components.keenetic_ndms2.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.keenetic_ndms2.async_setup_entry", return_value=True + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + keenetic.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_DATA, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == MOCK_NAME + assert result["data"] == MOCK_DATA + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_options(hass): + """Test updating options.""" + entry = MockConfigEntry(domain=keenetic.DOMAIN, data=MOCK_DATA) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.keenetic_ndms2.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.keenetic_ndms2.async_setup_entry", return_value=True + ) as mock_setup_entry: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + # fake router + hass.data.setdefault(keenetic.DOMAIN, {}) + hass.data[keenetic.DOMAIN][entry.entry_id] = { + keenetic.ROUTER: Mock( + client=Mock( + get_interfaces=Mock( + return_value=[ + InterfaceInfo.from_dict({"id": name, "type": "bridge"}) + for name in MOCK_OPTIONS[const.CONF_INTERFACES] + ] + ) + ) + ) + } + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input=MOCK_OPTIONS, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["data"] == MOCK_OPTIONS + + +async def test_host_already_configured(hass, connect): + """Test host already configured.""" + + entry = MockConfigEntry( + domain=keenetic.DOMAIN, data=MOCK_DATA, options=MOCK_OPTIONS + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + keenetic.DOMAIN, context={"source": "user"} + ) + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_DATA + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "already_configured" + + +async def test_connection_error(hass, connect_error): + """Test error when connection is unsuccessful.""" + + result = await hass.config_entries.flow.async_init( + keenetic.DOMAIN, context={"source": "user"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_DATA + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} From dfa973f9ef6d03651cd7084f63e2c5017d006436 Mon Sep 17 00:00:00 2001 From: kpine Date: Sun, 14 Feb 2021 04:24:29 -0800 Subject: [PATCH 0466/1818] Add barrier covers to zwave_js integration (#46379) --- homeassistant/components/zwave_js/cover.py | 79 +- .../components/zwave_js/discovery.py | 47 +- homeassistant/components/zwave_js/switch.py | 67 +- tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_cover.py | 215 +++- tests/components/zwave_js/test_switch.py | 141 +++ .../fixtures/zwave_js/cover_zw062_state.json | 936 ++++++++++++++++++ 7 files changed, 1483 insertions(+), 16 deletions(-) create mode 100644 tests/fixtures/zwave_js/cover_zw062_state.json diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 38c891f7376310..ff77bdb408dbc1 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -3,9 +3,11 @@ from typing import Any, Callable, List, Optional from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.cover import ( ATTR_POSITION, + DEVICE_CLASS_GARAGE, DOMAIN as COVER_DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, @@ -20,7 +22,15 @@ from .entity import ZWaveBaseEntity LOGGER = logging.getLogger(__name__) -SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE + +BARRIER_TARGET_CLOSE = 0 +BARRIER_TARGET_OPEN = 255 + +BARRIER_STATE_CLOSED = 0 +BARRIER_STATE_CLOSING = 252 +BARRIER_STATE_STOPPED = 253 +BARRIER_STATE_OPENING = 254 +BARRIER_STATE_OPEN = 255 async def async_setup_entry( @@ -33,7 +43,10 @@ async def async_setup_entry( def async_add_cover(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave cover.""" entities: List[ZWaveBaseEntity] = [] - entities.append(ZWaveCover(config_entry, client, info)) + if info.platform_hint == "motorized_barrier": + entities.append(ZwaveMotorizedBarrier(config_entry, client, info)) + else: + entities.append(ZWaveCover(config_entry, client, info)) async_add_entities(entities) hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( @@ -99,3 +112,65 @@ async def async_stop_cover(self, **kwargs: Any) -> None: target_value = self.get_zwave_value("Close") or self.get_zwave_value("Down") if target_value: await self.info.node.async_set_value(target_value, False) + + +class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity): + """Representation of a Z-Wave motorized barrier device.""" + + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZwaveMotorizedBarrier entity.""" + super().__init__(config_entry, client, info) + self._target_state: ZwaveValue = self.get_zwave_value( + "targetState", add_to_watched_value_ids=False + ) + + @property + def supported_features(self) -> Optional[int]: + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE + + @property + def device_class(self) -> Optional[str]: + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_GARAGE + + @property + def is_opening(self) -> Optional[bool]: + """Return if the cover is opening or not.""" + if self.info.primary_value.value is None: + return None + return bool(self.info.primary_value.value == BARRIER_STATE_OPENING) + + @property + def is_closing(self) -> Optional[bool]: + """Return if the cover is closing or not.""" + if self.info.primary_value.value is None: + return None + return bool(self.info.primary_value.value == BARRIER_STATE_CLOSING) + + @property + def is_closed(self) -> Optional[bool]: + """Return if the cover is closed or not.""" + if self.info.primary_value.value is None: + return None + # If a barrier is in the stopped state, the only way to proceed is by + # issuing an open cover command. Return None in this case which + # produces an unknown state and allows it to be resolved with an open + # command. + if self.info.primary_value.value == BARRIER_STATE_STOPPED: + return None + + return bool(self.info.primary_value.value == BARRIER_STATE_CLOSED) + + async def async_open_cover(self, **kwargs: Any) -> None: + """Open the garage door.""" + await self.info.node.async_set_value(self._target_state, BARRIER_TARGET_OPEN) + + async def async_close_cover(self, **kwargs: Any) -> None: + """Close the garage door.""" + await self.info.node.async_set_value(self._target_state, BARRIER_TARGET_CLOSE) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 62d45a5ae5e156..1aa70ea2fa0977 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -1,7 +1,7 @@ """Map Z-Wave nodes and values to Home Assistant entities.""" from dataclasses import dataclass -from typing import Generator, Optional, Set, Union +from typing import Generator, List, Optional, Set, Union from zwave_js_server.const import CommandClass from zwave_js_server.model.node import Node as ZwaveNode @@ -78,7 +78,7 @@ class ZWaveDiscoverySchema: # [optional] the node's specific device class must match ANY of these values device_class_specific: Optional[Set[str]] = None # [optional] additional values that ALL need to be present on the node for this scheme to pass - required_values: Optional[Set[ZWaveValueDiscoverySchema]] = None + required_values: Optional[List[ZWaveValueDiscoverySchema]] = None # [optional] bool to specify if this primary value may be discovered by multiple platforms allow_multi: bool = False @@ -345,10 +345,22 @@ class ZWaveDiscoverySchema: command_class={CommandClass.SWITCH_BINARY}, property={"currentValue"} ), ), + # binary switch + # barrier operator signaling states + ZWaveDiscoverySchema( + platform="switch", + hint="barrier_event_signaling_state", + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.BARRIER_OPERATOR}, + property={"signalingState"}, + type={"number"}, + ), + ), # cover + # window coverings ZWaveDiscoverySchema( platform="cover", - hint="cover", + hint="window_cover", device_class_generic={"Multilevel Switch"}, device_class_specific={ "Motor Control Class A", @@ -362,6 +374,24 @@ class ZWaveDiscoverySchema: type={"number"}, ), ), + # cover + # motorized barriers + ZWaveDiscoverySchema( + platform="cover", + hint="motorized_barrier", + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.BARRIER_OPERATOR}, + property={"currentState"}, + type={"number"}, + ), + required_values=[ + ZWaveValueDiscoverySchema( + command_class={CommandClass.BARRIER_OPERATOR}, + property={"targetState"}, + type={"number"}, + ), + ], + ), # fan ZWaveDiscoverySchema( platform="fan", @@ -430,13 +460,10 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None continue # check additional required values if schema.required_values is not None: - required_values_present = True - for val_scheme in schema.required_values: - for val in node.values.values(): - if not check_value(val, val_scheme): - required_values_present = False - break - if not required_values_present: + if not all( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.required_values + ): continue # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index 8feba5911f8d82..a325e9821f7374 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -17,6 +17,10 @@ LOGGER = logging.getLogger(__name__) +BARRIER_EVENT_SIGNALING_OFF = 0 +BARRIER_EVENT_SIGNALING_ON = 255 + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable ) -> None: @@ -27,7 +31,12 @@ async def async_setup_entry( def async_add_switch(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Switch.""" entities: List[ZWaveBaseEntity] = [] - entities.append(ZWaveSwitch(config_entry, client, info)) + if info.platform_hint == "barrier_event_signaling_state": + entities.append( + ZWaveBarrierEventSignalingSwitch(config_entry, client, info) + ) + else: + entities.append(ZWaveSwitch(config_entry, client, info)) async_add_entities(entities) @@ -62,3 +71,59 @@ async def async_turn_off(self, **kwargs: Any) -> None: target_value = self.get_zwave_value("targetValue") if target_value is not None: await self.info.node.async_set_value(target_value, False) + + +class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity): + """This switch is used to turn on or off a barrier device's event signaling subsystem.""" + + def __init__( + self, + config_entry: ConfigEntry, + client: ZwaveClient, + info: ZwaveDiscoveryInfo, + ) -> None: + """Initialize a ZWaveBarrierEventSignalingSwitch entity.""" + super().__init__(config_entry, client, info) + self._name = self.generate_name(include_value_name=True) + self._state: Optional[bool] = None + + self._update_state() + + @callback + def on_value_update(self) -> None: + """Call when a watched value is added or updated.""" + self._update_state() + + @property + def name(self) -> str: + """Return default name from device name and value name combination.""" + return self._name + + @property + def is_on(self) -> Optional[bool]: # type: ignore + """Return a boolean for the state of the switch.""" + return self._state + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the switch on.""" + await self.info.node.async_set_value( + self.info.primary_value, BARRIER_EVENT_SIGNALING_ON + ) + # this value is not refreshed, so assume success + self._state = True + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the switch off.""" + await self.info.node.async_set_value( + self.info.primary_value, BARRIER_EVENT_SIGNALING_OFF + ) + # this value is not refreshed, so assume success + self._state = False + self.async_write_ha_state() + + @callback + def _update_state(self) -> None: + self._state = None + if self.info.primary_value.value is not None: + self._state = self.info.primary_value.value == BARRIER_EVENT_SIGNALING_ON diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 9cb950ba6e706c..31b7d795a605e6 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -158,6 +158,12 @@ def in_wall_smart_fan_control_state_fixture(): return json.loads(load_fixture("zwave_js/in_wall_smart_fan_control_state.json")) +@pytest.fixture(name="gdc_zw062_state", scope="session") +def motorized_barrier_cover_state_fixture(): + """Load the motorized barrier cover node state fixture data.""" + return json.loads(load_fixture("zwave_js/cover_zw062_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -345,3 +351,11 @@ def multiple_devices_fixture( node = Node(client, 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.""" + node = Node(client, gdc_zw062_state) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index 5e50ffb226e831..2378453e31a265 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -1,13 +1,28 @@ """Test the Z-Wave JS cover platform.""" from zwave_js_server.event import Event -from homeassistant.components.cover import ATTR_CURRENT_POSITION +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, + DEVICE_CLASS_GARAGE, + DOMAIN, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, +) +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, + STATE_UNKNOWN, +) WINDOW_COVER_ENTITY = "cover.zws_12" +GDC_COVER_ENTITY = "cover.aeon_labs_garage_door_controller_gen5" -async def test_cover(hass, client, chain_actuator_zws12, integration): - """Test the light entity.""" +async def test_window_cover(hass, client, chain_actuator_zws12, integration): + """Test the cover entity.""" node = chain_actuator_zws12 state = hass.states.get(WINDOW_COVER_ENTITY) @@ -282,3 +297,197 @@ async def test_cover(hass, client, chain_actuator_zws12, integration): state = hass.states.get(WINDOW_COVER_ENTITY) assert state.state == "closed" + + +async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): + """Test the cover entity.""" + node = gdc_zw062 + + state = hass.states.get(GDC_COVER_ENTITY) + assert state + assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_GARAGE + + assert state.state == STATE_CLOSED + + # Test open + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 12 + assert args["value"] == 255 + assert args["valueId"] == { + "ccVersion": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "endpoint": 0, + "metadata": { + "label": "Target Barrier State", + "max": 255, + "min": 0, + "readable": True, + "states": {"0": "Closed", "255": "Open"}, + "type": "number", + "writeable": True, + }, + "property": "targetState", + "propertyName": "targetState", + } + + # state doesn't change until currentState value update is received + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_CLOSED + + client.async_send_command.reset_mock() + + # Test close + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 12 + assert args["value"] == 0 + assert args["valueId"] == { + "ccVersion": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "endpoint": 0, + "metadata": { + "label": "Target Barrier State", + "max": 255, + "min": 0, + "readable": True, + "states": {"0": "Closed", "255": "Open"}, + "type": "number", + "writeable": True, + }, + "property": "targetState", + "propertyName": "targetState", + } + + # state doesn't change until currentState value update is received + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_CLOSED + + client.async_send_command.reset_mock() + + # Barrier sends an opening state + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "currentState", + "newValue": 254, + "prevValue": 0, + "propertyName": "currentState", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_OPENING + + # Barrier sends an opened state + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "currentState", + "newValue": 255, + "prevValue": 254, + "propertyName": "currentState", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_OPEN + + # Barrier sends a closing state + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "currentState", + "newValue": 252, + "prevValue": 255, + "propertyName": "currentState", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_CLOSING + + # Barrier sends a closed state + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "currentState", + "newValue": 0, + "prevValue": 252, + "propertyName": "currentState", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_CLOSED + + # Barrier sends a stopped state + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "currentState", + "newValue": 253, + "prevValue": 252, + "propertyName": "currentState", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(GDC_COVER_ENTITY) + assert state.state == STATE_UNKNOWN diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index a1d177cc5d8750..ea6e27d9b725f4 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -2,6 +2,9 @@ from zwave_js_server.event import Event +from homeassistant.components.switch import DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.const import STATE_OFF, STATE_ON + from .common import SWITCH_ENTITY @@ -83,3 +86,141 @@ async def test_switch(hass, hank_binary_switch, integration, client): "value": False, } assert args["value"] is False + + +async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): + """Test barrier signaling state switch.""" + node = gdc_zw062 + entity = "switch.aeon_labs_garage_door_controller_gen5_signaling_state_visual" + + state = hass.states.get(entity) + assert state + assert state.state == "on" + + # Test turning off + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {"entity_id": entity}, blocking=True + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 12 + assert args["value"] == 0 + assert args["valueId"] == { + "ccVersion": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "endpoint": 0, + "metadata": { + "label": "Signaling State (Visual)", + "max": 255, + "min": 0, + "readable": True, + "states": {"0": "Off", "255": "On"}, + "type": "number", + "writeable": True, + }, + "property": "signalingState", + "propertyKey": 2, + "propertyKeyName": "2", + "propertyName": "signalingState", + "value": 255, + } + + # state change is optimistic and writes state + await hass.async_block_till_done() + + state = hass.states.get(entity) + assert state.state == STATE_OFF + + client.async_send_command.reset_mock() + + # Test turning on + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {"entity_id": entity}, blocking=True + ) + + # Note: the valueId's value is still 255 because we never + # received an updated value + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 12 + assert args["value"] == 255 + assert args["valueId"] == { + "ccVersion": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "endpoint": 0, + "metadata": { + "label": "Signaling State (Visual)", + "max": 255, + "min": 0, + "readable": True, + "states": {"0": "Off", "255": "On"}, + "type": "number", + "writeable": True, + }, + "property": "signalingState", + "propertyKey": 2, + "propertyKeyName": "2", + "propertyName": "signalingState", + "value": 255, + } + + # state change is optimistic and writes state + await hass.async_block_till_done() + + state = hass.states.get(entity) + assert state.state == STATE_ON + + # Received a refresh off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "signalingState", + "propertyKey": 2, + "newValue": 0, + "prevValue": 0, + "propertyName": "signalingState", + "propertyKeyName": "2", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(entity) + assert state.state == STATE_OFF + + # Received a refresh off + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 12, + "args": { + "commandClassName": "Barrier Operator", + "commandClass": 102, + "endpoint": 0, + "property": "signalingState", + "propertyKey": 2, + "newValue": 255, + "prevValue": 255, + "propertyName": "signalingState", + "propertyKeyName": "2", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(entity) + assert state.state == STATE_ON diff --git a/tests/fixtures/zwave_js/cover_zw062_state.json b/tests/fixtures/zwave_js/cover_zw062_state.json new file mode 100644 index 00000000000000..107225e0dcc724 --- /dev/null +++ b/tests/fixtures/zwave_js/cover_zw062_state.json @@ -0,0 +1,936 @@ +{ + "nodeId": 12, + "index": 0, + "installerIcon": 7680, + "userIcon": 7680, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Entry Control", + "specific": "Secure Barrier Add-on", + "mandatorySupportedCCs": [ + "Application Status", + "Association", + "Association Group Information", + "Barrier Operator", + "Battery", + "Device Reset Locally", + "Manufacturer Specific", + "Notification", + "Powerlevel", + "Security", + "Security 2", + "Supervision", + "Transport Service", + "Version", + "Z-Wave Plus Info" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": true, + "version": 4, + "isBeaming": true, + "manufacturerId": 134, + "productId": 62, + "productType": 259, + "firmwareVersion": "1.12", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 134, + "manufacturer": "AEON Labs", + "label": "ZW062", + "description": "Aeon Labs Garage Door Controller Gen5", + "devices": [ + { + "productType": "0x0003", + "productId": "0x003e" + }, + { + "productType": "0x0103", + "productId": "0x003e" + }, + { + "productType": "0x0203", + "productId": "0x003e" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "paramInformation": { + "_map": {} + } + }, + "label": "ZW062", + "neighbors": [ + 1, + 8, + 11, + 15, + 19, + 21, + 22, + 24, + 25, + 26, + 27, + 29 + ], + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 12, + "index": 0, + "installerIcon": 7680, + "userIcon": 7680 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + } + }, + { + "endpoint": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "property": "currentState", + "propertyName": "currentState", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Current Barrier State", + "states": { + "0": "Closed", + "252": "Closing", + "253": "Stopped", + "254": "Opening", + "255": "Open" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "property": "position", + "propertyName": "position", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "label": "Barrier Position", + "unit": "%" + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "property": "signalingState", + "propertyKey": 1, + "propertyName": "signalingState", + "propertyKeyName": "1", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Signaling State (Audible)", + "states": { + "0": "Off", + "255": "On" + } + }, + "value": 255 + }, + { + "endpoint": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "property": "signalingState", + "propertyKey": 2, + "propertyName": "signalingState", + "propertyKeyName": "2", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Signaling State (Visual)", + "states": { + "0": "Off", + "255": "On" + } + }, + "value": 255 + }, + { + "endpoint": 0, + "commandClass": 102, + "commandClassName": "Barrier Operator", + "property": "targetState", + "propertyName": "targetState", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Target Barrier State", + "states": { + "0": "Closed", + "255": "Open" + } + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 32, + "propertyName": "Startup ringtone", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 100, + "default": 1, + "format": 0, + "allowManualEntry": true, + "label": "Startup ringtone", + "description": "Configure the default startup ringtone", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 35, + "propertyName": "Calibration timout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 1, + "max": 255, + "default": 60, + "format": 0, + "allowManualEntry": true, + "label": "Calibration timout", + "description": "Set the timeout of all calibration steps for the Sensor.", + "isFromConfig": true + }, + "value": 13 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 36, + "propertyName": "Number of alarm musics", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 1, + "min": 1, + "max": 100, + "default": 1, + "format": 0, + "allowManualEntry": true, + "label": "Number of alarm musics", + "description": "Get the number of alarm musics", + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 39, + "propertyName": "Unknown state alarm mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Unknown state alarm mode", + "description": "Configuration alarm mode when the garage door is in \"unknown\" state", + "isFromConfig": true + }, + "value": 100927488 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 40, + "propertyName": "Closed alarm mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Closed alarm mode", + "description": "Configure the alarm mode when the garage door is in closed position.", + "isFromConfig": true + }, + "value": 33883392 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 41, + "propertyName": "Tamper switch configuration", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Tamper switch configuration", + "description": "Configuration report for the tamper switch State", + "isFromConfig": true + }, + "value": 15 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 42, + "propertyName": "Battery state", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 1, + "min": 0, + "max": 16, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Battery state", + "description": "Configuration report for the battery state of Sensor", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 45, + "propertyName": "Temperature", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "valueSize": 2, + "min": 0, + "max": 500, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Temperature", + "description": "Get the environment temperature", + "isFromConfig": true + }, + "value": 550 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 47, + "propertyName": "Button definition", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Mode 0", + "1": "Mode 1" + }, + "label": "Button definition", + "description": "Define the function of Button- or Button+.", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 80, + "propertyName": "Door state change report type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 2, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "1": "Send hail CC", + "2": "Send barrier operator report CC" + }, + "label": "Door state change report type", + "description": "Configure the door state change report type", + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 241, + "propertyName": "Pair the Sensor", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 1431655681, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Stop sensor pairing", + "1431655681": "Start sensor pairing" + }, + "label": "Pair the Sensor", + "description": "Pair the Sensor with Garage Door Controller", + "isFromConfig": true + }, + "value": 33554943 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 252, + "propertyName": "Lock Configuration", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Configuration enabled", + "1": "Configuration disabled (locked)" + }, + "label": "Lock Configuration", + "description": "Enable/disable configuration", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 37, + "propertyKey": 255, + "propertyName": "Disable opening alarm", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disable alarm prompt", + "1": "Enable alarm prompt" + }, + "label": "Disable opening alarm", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 37, + "propertyKey": 65280, + "propertyName": "Opening alarm volume", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 10, + "default": 8, + "format": 0, + "allowManualEntry": true, + "label": "Opening alarm volume", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 37, + "propertyKey": 16711680, + "propertyName": "Opening alarm choice", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 4, + "default": 1, + "format": 0, + "allowManualEntry": true, + "label": "Opening alarm choice", + "description": "Alarm mode when the garage door is opening", + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 37, + "propertyKey": 251658240, + "propertyName": "Opening alarm LED mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 10, + "default": 10, + "format": 0, + "allowManualEntry": true, + "label": "Opening alarm LED mode", + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 255, + "propertyName": "Disable closing alarm", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disable alarm prompt", + "1": "Enable alarm prompt" + }, + "label": "Disable closing alarm", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 65280, + "propertyName": "Closing alarm volume", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 10, + "default": 8, + "format": 0, + "allowManualEntry": true, + "label": "Closing alarm volume", + "isFromConfig": true + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 16711680, + "propertyName": "Closing alarm choice", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 4, + "default": 2, + "format": 0, + "allowManualEntry": true, + "label": "Closing alarm choice", + "description": "Alarm mode when the garage door is closing", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 38, + "propertyKey": 251658240, + "propertyName": "Closing alarm LED mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 10, + "default": 6, + "format": 0, + "allowManualEntry": true, + "label": "Closing alarm LED mode", + "isFromConfig": true + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 34, + "propertyName": "Sensor Calibration", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Calibration not active", + "1": "Begin calibration" + }, + "label": "Sensor Calibration", + "description": "Perform Sensor Calibration", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 43, + "propertyName": "Play or Pause ringtone", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 255, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Play or Pause ringtone", + "description": "Start playing or Stop playing the ringtone", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 44, + "propertyName": "Ringtone test volume", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 10, + "default": 1, + "format": 0, + "allowManualEntry": true, + "label": "Ringtone test volume", + "description": "Set volume for test of ringtone", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "alarmType", + "propertyName": "alarmType", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Alarm Type" + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "alarmLevel", + "propertyName": "alarmLevel", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Alarm Level" + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 134 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 259 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 62 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "3.99" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.12" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + } + ] +} From 10e88cd23d39cbd46454f293763b1b53392ef8e5 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 14 Feb 2021 04:36:05 -0800 Subject: [PATCH 0467/1818] Improve nest defense against broken event loop on shutdown (#46494) --- homeassistant/components/nest/__init__.py | 2 +- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 85e591707ade38..b0abd24012a023 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -211,7 +211,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): if DATA_SDM not in entry.data: # Legacy API return True - + _LOGGER.debug("Stopping nest subscriber") subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] subscriber.stop_async() unload_ok = all( diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index f9bc135693d4d4..c68dbe6ee2f42e 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.9"], + "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.10"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [{"macaddress":"18B430*"}] diff --git a/requirements_all.txt b/requirements_all.txt index de1a0b221c24f5..110f573bc90249 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -684,7 +684,7 @@ google-cloud-pubsub==2.1.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==0.2.9 +google-nest-sdm==0.2.10 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 93c66806ade04a..3b8e9858cad670 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -369,7 +369,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.1.0 # homeassistant.components.nest -google-nest-sdm==0.2.9 +google-nest-sdm==0.2.10 # homeassistant.components.gree greeclimate==0.10.3 From a5a45f29e2953bc61a63f72bda1fd8a643efd7b4 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 07:46:58 -0500 Subject: [PATCH 0468/1818] Cleanup unused loggers (#46510) --- homeassistant/components/agent_dvr/config_flow.py | 3 --- homeassistant/components/airvisual/sensor.py | 4 ---- homeassistant/components/asuswrt/__init__.py | 3 --- homeassistant/components/asuswrt/device_tracker.py | 3 --- homeassistant/components/aurora/binary_sensor.py | 4 ---- homeassistant/components/aurora/sensor.py | 4 ---- homeassistant/components/blueprint/websocket_api.py | 3 --- homeassistant/components/bmw_connected_drive/config_flow.py | 5 ----- homeassistant/components/dyson/air_quality.py | 4 ---- homeassistant/components/dyson/sensor.py | 4 ---- homeassistant/components/econet/binary_sensor.py | 4 ---- homeassistant/components/econet/sensor.py | 5 ----- homeassistant/components/fireservicerota/binary_sensor.py | 4 ---- homeassistant/components/gree/switch.py | 3 --- homeassistant/components/hangouts/const.py | 5 ----- homeassistant/components/homekit/config_flow.py | 3 --- homeassistant/components/homematicip_cloud/__init__.py | 4 ---- homeassistant/components/ipma/const.py | 4 ---- homeassistant/components/izone/discovery.py | 5 ----- homeassistant/components/lutron_caseta/device_trigger.py | 4 ---- homeassistant/components/met/const.py | 4 ---- homeassistant/components/modbus/__init__.py | 4 ---- homeassistant/components/motion_blinds/config_flow.py | 5 ----- homeassistant/components/motion_blinds/sensor.py | 4 ---- homeassistant/components/mqtt/camera.py | 3 --- homeassistant/components/mqtt/config_flow.py | 3 --- homeassistant/components/mqtt/debug_info.py | 3 --- homeassistant/components/mqtt/device_automation.py | 3 --- .../components/mqtt/device_tracker/schema_discovery.py | 3 --- homeassistant/components/mqtt/fan.py | 3 --- homeassistant/components/mqtt/light/__init__.py | 3 --- homeassistant/components/mqtt/lock.py | 3 --- homeassistant/components/mqtt/scene.py | 3 --- homeassistant/components/mqtt/sensor.py | 3 --- homeassistant/components/mqtt/subscription.py | 3 --- homeassistant/components/mqtt/switch.py | 3 --- homeassistant/components/mqtt/vacuum/__init__.py | 3 --- homeassistant/components/mqtt/vacuum/schema_legacy.py | 3 --- homeassistant/components/mqtt/vacuum/schema_state.py | 3 --- homeassistant/components/neato/api.py | 3 --- homeassistant/components/neato/config_flow.py | 2 -- homeassistant/components/nest/device_trigger.py | 3 --- homeassistant/components/nws/weather.py | 3 --- homeassistant/components/ondilo_ico/config_flow.py | 2 -- homeassistant/components/ozw/fan.py | 3 --- homeassistant/components/plaato/sensor.py | 4 ---- homeassistant/components/proxmoxve/binary_sensor.py | 4 ---- homeassistant/components/rachio/webhooks.py | 6 ------ .../components/totalconnect/alarm_control_panel.py | 2 -- homeassistant/components/totalconnect/binary_sensor.py | 4 ---- homeassistant/components/tuya/climate.py | 3 --- homeassistant/components/zha/binary_sensor.py | 3 --- homeassistant/components/zwave_js/api.py | 3 --- homeassistant/components/zwave_js/climate.py | 3 --- homeassistant/components/zwave_js/fan.py | 3 --- tests/components/feedreader/test_init.py | 3 --- tests/components/harmony/conftest.py | 3 --- tests/components/harmony/test_activity_changes.py | 5 ----- tests/components/hyperion/__init__.py | 3 --- tests/components/hyperion/test_config_flow.py | 4 ---- tests/components/hyperion/test_light.py | 3 --- tests/components/hyperion/test_switch.py | 2 -- tests/components/litejet/test_init.py | 3 --- tests/components/litejet/test_scene.py | 3 --- tests/scripts/test_check_config.py | 3 --- 65 files changed, 223 deletions(-) diff --git a/homeassistant/components/agent_dvr/config_flow.py b/homeassistant/components/agent_dvr/config_flow.py index 9448b8d3123564..15ef58ced7ee77 100644 --- a/homeassistant/components/agent_dvr/config_flow.py +++ b/homeassistant/components/agent_dvr/config_flow.py @@ -1,6 +1,4 @@ """Config flow to configure Agent devices.""" -import logging - from agent import AgentConnectionError, AgentError from agent.a import Agent import voluptuous as vol @@ -13,7 +11,6 @@ from .helpers import generate_url DEFAULT_PORT = 8090 -_LOGGER = logging.getLogger(__name__) class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 680059af41126c..3c1aef128abcdc 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -1,6 +1,4 @@ """Support for AirVisual air quality sensors.""" -from logging import getLogger - from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -31,8 +29,6 @@ INTEGRATION_TYPE_GEOGRAPHY_NAME, ) -_LOGGER = getLogger(__name__) - ATTR_CITY = "city" ATTR_COUNTRY = "country" ATTR_POLLUTANT_SYMBOL = "pollutant_symbol" diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index d2eb47fa2d2977..28e8fe76684b3b 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -1,6 +1,5 @@ """Support for ASUSWRT devices.""" import asyncio -import logging import voluptuous as vol @@ -41,8 +40,6 @@ CONF_PUB_KEY = "pub_key" SECRET_GROUP = "Password or SSH Key" -_LOGGER = logging.getLogger(__name__) - CONFIG_SCHEMA = vol.Schema( vol.All( cv.deprecated(DOMAIN), diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 85553674dbaf35..385b25755b08f9 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -1,5 +1,4 @@ """Support for ASUSWRT routers.""" -import logging from typing import Dict from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER @@ -15,8 +14,6 @@ DEFAULT_DEVICE_NAME = "Unknown device" -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index 3ea1faa7949b74..a6d5a1817b293b 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -1,13 +1,9 @@ """Support for Aurora Forecast binary sensor.""" -import logging - from homeassistant.components.binary_sensor import BinarySensorEntity from . import AuroraEntity from .const import COORDINATOR, DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, entry, async_add_entries): """Set up the binary_sensor platform.""" diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py index 51ccb3dc4dd4b2..731c6d08afd499 100644 --- a/homeassistant/components/aurora/sensor.py +++ b/homeassistant/components/aurora/sensor.py @@ -1,13 +1,9 @@ """Support for Aurora Forecast sensor.""" -import logging - from homeassistant.const import PERCENTAGE from . import AuroraEntity from .const import COORDINATOR, DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, entry, async_add_entries): """Set up the sensor platform.""" diff --git a/homeassistant/components/blueprint/websocket_api.py b/homeassistant/components/blueprint/websocket_api.py index 6968d4530cdf35..05ae28166962af 100644 --- a/homeassistant/components/blueprint/websocket_api.py +++ b/homeassistant/components/blueprint/websocket_api.py @@ -1,5 +1,4 @@ """Websocket API for blueprint.""" -import logging from typing import Dict, Optional import async_timeout @@ -15,8 +14,6 @@ from .const import DOMAIN from .errors import FileAlreadyExists -_LOGGER = logging.getLogger(__package__) - @callback def async_setup(hass: HomeAssistant): diff --git a/homeassistant/components/bmw_connected_drive/config_flow.py b/homeassistant/components/bmw_connected_drive/config_flow.py index 8315a1322e20f2..fbfa20aff1a8ce 100644 --- a/homeassistant/components/bmw_connected_drive/config_flow.py +++ b/homeassistant/components/bmw_connected_drive/config_flow.py @@ -1,6 +1,4 @@ """Config flow for BMW ConnectedDrive integration.""" -import logging - from bimmer_connected.account import ConnectedDriveAccount from bimmer_connected.country_selector import get_region_from_name import voluptuous as vol @@ -12,9 +10,6 @@ from . import DOMAIN # pylint: disable=unused-import from .const import CONF_ALLOWED_REGIONS, CONF_READ_ONLY, CONF_USE_LOCATION -_LOGGER = logging.getLogger(__name__) - - DATA_SCHEMA = vol.Schema( { vol.Required(CONF_USERNAME): str, diff --git a/homeassistant/components/dyson/air_quality.py b/homeassistant/components/dyson/air_quality.py index d23b2b1ef883c9..3bf4f2bb34c9c4 100644 --- a/homeassistant/components/dyson/air_quality.py +++ b/homeassistant/components/dyson/air_quality.py @@ -1,6 +1,4 @@ """Support for Dyson Pure Cool Air Quality Sensors.""" -import logging - from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State @@ -10,8 +8,6 @@ ATTRIBUTION = "Dyson purifier air quality sensor" -_LOGGER = logging.getLogger(__name__) - DYSON_AIQ_DEVICES = "dyson_aiq_devices" ATTR_VOC = "volatile_organic_compounds" diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index f1198188b5ccd6..80a64e787f0d21 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -1,6 +1,4 @@ """Support for Dyson Pure Cool Link Sensors.""" -import logging - from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool_link import DysonPureCoolLink @@ -58,8 +56,6 @@ DYSON_SENSOR_DEVICES = "dyson_sensor_devices" -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Dyson Sensors.""" diff --git a/homeassistant/components/econet/binary_sensor.py b/homeassistant/components/econet/binary_sensor.py index ec8131c510560f..b87e6bb0cd0446 100644 --- a/homeassistant/components/econet/binary_sensor.py +++ b/homeassistant/components/econet/binary_sensor.py @@ -1,6 +1,4 @@ """Support for Rheem EcoNet water heaters.""" -import logging - from pyeconet.equipment import EquipmentType from homeassistant.components.binary_sensor import ( @@ -12,8 +10,6 @@ from . import EcoNetEntity from .const import DOMAIN, EQUIPMENT -_LOGGER = logging.getLogger(__name__) - SENSOR_NAME_RUNNING = "running" SENSOR_NAME_SHUTOFF_VALVE = "shutoff_valve" SENSOR_NAME_VACATION = "vacation" diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index 6ae14d18aa1243..e0ef7dc6ce9724 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -1,6 +1,4 @@ """Support for Rheem EcoNet water heaters.""" -import logging - from pyeconet.equipment import EquipmentType from homeassistant.const import ( @@ -25,9 +23,6 @@ RUNNING_STATE = "running_state" -_LOGGER = logging.getLogger(__name__) - - async def async_setup_entry(hass, entry, async_add_entities): """Set up EcoNet sensor based on a config entry.""" equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] diff --git a/homeassistant/components/fireservicerota/binary_sensor.py b/homeassistant/components/fireservicerota/binary_sensor.py index fc06e605cbd755..3f04adc2f7254b 100644 --- a/homeassistant/components/fireservicerota/binary_sensor.py +++ b/homeassistant/components/fireservicerota/binary_sensor.py @@ -1,6 +1,4 @@ """Binary Sensor platform for FireServiceRota integration.""" -import logging - from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -11,8 +9,6 @@ from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN as FIRESERVICEROTA_DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities diff --git a/homeassistant/components/gree/switch.py b/homeassistant/components/gree/switch.py index f4e9792a5897df..fa1f1550e83d81 100644 --- a/homeassistant/components/gree/switch.py +++ b/homeassistant/components/gree/switch.py @@ -1,5 +1,4 @@ """Support for interface with a Gree climate systems.""" -import logging from typing import Optional from homeassistant.components.switch import DEVICE_CLASS_SWITCH, SwitchEntity @@ -8,8 +7,6 @@ from .const import COORDINATOR, DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Gree HVAC device from a config entry.""" diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py index 0508bf4870347b..3a78e9bbe80509 100644 --- a/homeassistant/components/hangouts/const.py +++ b/homeassistant/components/hangouts/const.py @@ -1,14 +1,9 @@ """Constants for Google Hangouts Component.""" -import logging - import voluptuous as vol from homeassistant.components.notify import ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger(".") - - DOMAIN = "hangouts" CONF_2FA = "2fa" diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index f16d14e9330e98..d6278c0ca94988 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -1,5 +1,4 @@ """Config flow for HomeKit integration.""" -import logging import random import string @@ -101,8 +100,6 @@ CONF_EXCLUDE_ENTITIES: [], } -_LOGGER = logging.getLogger(__name__) - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for HomeKit.""" diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py index 7ea6a4fe0b41b0..ca1af8266c6295 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -1,6 +1,4 @@ """Support for HomematicIP Cloud devices.""" -import logging - import voluptuous as vol from homeassistant import config_entries @@ -23,8 +21,6 @@ from .hap import HomematicipAuth, HomematicipHAP # noqa: F401 from .services import async_setup_services, async_unload_services -_LOGGER = logging.getLogger(__name__) - CONFIG_SCHEMA = vol.Schema( { vol.Optional(DOMAIN, default=[]): vol.All( diff --git a/homeassistant/components/ipma/const.py b/homeassistant/components/ipma/const.py index 04064db2b88059..47434d7f76bf7f 100644 --- a/homeassistant/components/ipma/const.py +++ b/homeassistant/components/ipma/const.py @@ -1,6 +1,4 @@ """Constants for IPMA component.""" -import logging - from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN DOMAIN = "ipma" @@ -8,5 +6,3 @@ HOME_LOCATION_NAME = "Home" ENTITY_ID_SENSOR_FORMAT_HOME = f"{WEATHER_DOMAIN}.ipma_{HOME_LOCATION_NAME}" - -_LOGGER = logging.getLogger(".") diff --git a/homeassistant/components/izone/discovery.py b/homeassistant/components/izone/discovery.py index 7690600786e59f..2a4ad516af1463 100644 --- a/homeassistant/components/izone/discovery.py +++ b/homeassistant/components/izone/discovery.py @@ -1,7 +1,4 @@ """Internal discovery service for iZone AC.""" - -import logging - import pizone from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -18,8 +15,6 @@ DISPATCH_ZONE_UPDATE, ) -_LOGGER = logging.getLogger(__name__) - class DiscoveryService(pizone.Listener): """Discovery data and interfacing with pizone library.""" diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index 80d147191e6cc8..86ee5e46b51235 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -1,5 +1,4 @@ """Provides device triggers for lutron caseta.""" -import logging from typing import List import voluptuous as vol @@ -32,9 +31,6 @@ LUTRON_CASETA_BUTTON_EVENT, ) -_LOGGER = logging.getLogger(__name__) - - SUPPORTED_INPUTS_EVENTS_TYPES = [ACTION_PRESS, ACTION_RELEASE] LUTRON_BUTTON_TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( diff --git a/homeassistant/components/met/const.py b/homeassistant/components/met/const.py index 8c507eb0b8d285..b78c412393d3fb 100644 --- a/homeassistant/components/met/const.py +++ b/homeassistant/components/met/const.py @@ -1,6 +1,4 @@ """Constants for Met component.""" -import logging - from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, @@ -191,5 +189,3 @@ ATTR_WEATHER_WIND_BEARING: "wind_bearing", ATTR_WEATHER_WIND_SPEED: "wind_speed", } - -_LOGGER = logging.getLogger(".") diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index fe6811a35d9697..428ddfadb14893 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -1,6 +1,4 @@ """Support for Modbus.""" -import logging - import voluptuous as vol from homeassistant.components.cover import ( @@ -69,8 +67,6 @@ ) from .modbus import modbus_setup -_LOGGER = logging.getLogger(__name__) - BASE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string}) CLIMATE_SCHEMA = vol.Schema( diff --git a/homeassistant/components/motion_blinds/config_flow.py b/homeassistant/components/motion_blinds/config_flow.py index cb85b45e0e0a64..d2bf216e26a7b5 100644 --- a/homeassistant/components/motion_blinds/config_flow.py +++ b/homeassistant/components/motion_blinds/config_flow.py @@ -1,6 +1,4 @@ """Config flow to configure Motion Blinds using their WLAN API.""" -import logging - from motionblinds import MotionDiscovery import voluptuous as vol @@ -11,9 +9,6 @@ from .const import DEFAULT_GATEWAY_NAME, DOMAIN from .gateway import ConnectMotionGateway -_LOGGER = logging.getLogger(__name__) - - CONFIG_SCHEMA = vol.Schema( { vol.Optional(CONF_HOST): str, diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index dd637696e775d7..f8a673b3079816 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -1,6 +1,4 @@ """Support for Motion Blinds sensors.""" -import logging - from motionblinds import BlindType from homeassistant.const import ( @@ -14,8 +12,6 @@ from .const import DOMAIN, KEY_COORDINATOR, KEY_GATEWAY -_LOGGER = logging.getLogger(__name__) - ATTR_BATTERY_VOLTAGE = "battery_voltage" TYPE_BLIND = "blind" TYPE_GATEWAY = "gateway" diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 684e41214fe321..cc58741a92315c 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -1,6 +1,5 @@ """Camera that loads a picture from an MQTT topic.""" import functools -import logging import voluptuous as vol @@ -23,8 +22,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - CONF_TOPIC = "topic" DEFAULT_NAME = "MQTT Camera" diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index c71541b58b07bb..e19aaecc3db51b 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -1,6 +1,5 @@ """Config flow for MQTT.""" from collections import OrderedDict -import logging import queue import voluptuous as vol @@ -31,8 +30,6 @@ ) from .util import MQTT_WILL_BIRTH_SCHEMA -_LOGGER = logging.getLogger(__name__) - @config_entries.HANDLERS.register("mqtt") class FlowHandler(config_entries.ConfigFlow): diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index a3c56652253696..52aeb20e3aa4bc 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -1,7 +1,6 @@ """Helper to handle a set of topics to subscribe to.""" from collections import deque from functools import wraps -import logging from typing import Any from homeassistant.helpers.typing import HomeAssistantType @@ -9,8 +8,6 @@ from .const import ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC from .models import MessageCallbackType -_LOGGER = logging.getLogger(__name__) - DATA_MQTT_DEBUG_INFO = "mqtt_debug_info" STORED_MESSAGES = 10 diff --git a/homeassistant/components/mqtt/device_automation.py b/homeassistant/components/mqtt/device_automation.py index d3e1f33421df30..50d6a6e4d19587 100644 --- a/homeassistant/components/mqtt/device_automation.py +++ b/homeassistant/components/mqtt/device_automation.py @@ -1,6 +1,5 @@ """Provides device automations for MQTT.""" import functools -import logging import voluptuous as vol @@ -10,8 +9,6 @@ from .. import mqtt from .mixins import async_setup_entry_helper -_LOGGER = logging.getLogger(__name__) - AUTOMATION_TYPE_TRIGGER = "trigger" AUTOMATION_TYPES = [AUTOMATION_TYPE_TRIGGER] AUTOMATION_TYPES_SCHEMA = vol.In(AUTOMATION_TYPES) diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index 296f66889b35e2..bd5d9a1e60ef3f 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -1,6 +1,5 @@ """Support for tracking MQTT enabled devices identified through discovery.""" import functools -import logging import voluptuous as vol @@ -34,8 +33,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - CONF_PAYLOAD_HOME = "payload_home" CONF_PAYLOAD_NOT_HOME = "payload_not_home" CONF_SOURCE_TYPE = "source_type" diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index ef3ecffc0e0b17..24d652062ae10d 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -1,6 +1,5 @@ """Support for MQTT fans.""" import functools -import logging import voluptuous as vol @@ -48,8 +47,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index 72c438df81234d..412302273ac193 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -1,6 +1,5 @@ """Support for MQTT lights.""" import functools -import logging import voluptuous as vol @@ -15,8 +14,6 @@ from .schema_json import PLATFORM_SCHEMA_JSON, async_setup_entity_json from .schema_template import PLATFORM_SCHEMA_TEMPLATE, async_setup_entity_template -_LOGGER = logging.getLogger(__name__) - def validate_mqtt_light(value): """Validate MQTT light schema.""" diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 408bc07b9d9af3..e66b93f51c0d6b 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -1,6 +1,5 @@ """Support for MQTT locks.""" import functools -import logging import voluptuous as vol @@ -37,8 +36,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - CONF_PAYLOAD_LOCK = "payload_lock" CONF_PAYLOAD_UNLOCK = "payload_unlock" diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index 061c7df3fdc626..c6d9140af616a0 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -1,6 +1,5 @@ """Support for MQTT scenes.""" import functools -import logging import voluptuous as vol @@ -20,8 +19,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - DEFAULT_NAME = "MQTT Scene" DEFAULT_RETAIN = False diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 78a0517921044b..d017cb2ce85460 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -1,7 +1,6 @@ """Support for MQTT sensors.""" from datetime import timedelta import functools -import logging from typing import Optional import voluptuous as vol @@ -38,8 +37,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - CONF_EXPIRE_AFTER = "expire_after" DEFAULT_NAME = "MQTT Sensor" diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py index c61c30c922e998..5c2efabc2669a7 100644 --- a/homeassistant/components/mqtt/subscription.py +++ b/homeassistant/components/mqtt/subscription.py @@ -1,5 +1,4 @@ """Helper to handle a set of topics to subscribe to.""" -import logging from typing import Any, Callable, Dict, Optional import attr @@ -12,8 +11,6 @@ from .const import DEFAULT_QOS from .models import MessageCallbackType -_LOGGER = logging.getLogger(__name__) - @attr.s(slots=True) class EntitySubscription: diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 73c7d55f6b027f..939d6bb98b128c 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -1,6 +1,5 @@ """Support for MQTT switches.""" import functools -import logging import voluptuous as vol @@ -42,8 +41,6 @@ async_setup_entry_helper, ) -_LOGGER = logging.getLogger(__name__) - DEFAULT_NAME = "MQTT Switch" DEFAULT_PAYLOAD_ON = "ON" DEFAULT_PAYLOAD_OFF = "OFF" diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index 4801c1ea994c6d..85fd1247381ecd 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -1,6 +1,5 @@ """Support for MQTT vacuums.""" import functools -import logging import voluptuous as vol @@ -13,8 +12,6 @@ from .schema_legacy import PLATFORM_SCHEMA_LEGACY, async_setup_entity_legacy from .schema_state import PLATFORM_SCHEMA_STATE, async_setup_entity_state -_LOGGER = logging.getLogger(__name__) - def validate_mqtt_vacuum(value): """Validate MQTT vacuum schema.""" diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 577e6a70ccd7b3..ca91b8948d44b8 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -1,6 +1,5 @@ """Support for Legacy MQTT vacuum.""" import json -import logging import voluptuous as vol @@ -39,8 +38,6 @@ ) from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services -_LOGGER = logging.getLogger(__name__) - SERVICE_TO_STRING = { SUPPORT_TURN_ON: "turn_on", SUPPORT_TURN_OFF: "turn_off", diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index c754ba1604a368..3e43736ab2e386 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -1,6 +1,5 @@ """Support for a State MQTT vacuum.""" import json -import logging import voluptuous as vol @@ -43,8 +42,6 @@ ) from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services -_LOGGER = logging.getLogger(__name__) - SERVICE_TO_STRING = { SUPPORT_START: "start", SUPPORT_PAUSE: "pause", diff --git a/homeassistant/components/neato/api.py b/homeassistant/components/neato/api.py index 931d7cdb712be1..31988fc175e73f 100644 --- a/homeassistant/components/neato/api.py +++ b/homeassistant/components/neato/api.py @@ -1,14 +1,11 @@ """API for Neato Botvac bound to Home Assistant OAuth.""" from asyncio import run_coroutine_threadsafe -import logging import pybotvac from homeassistant import config_entries, core from homeassistant.helpers import config_entry_oauth2_flow -_LOGGER = logging.getLogger(__name__) - class ConfigEntryAuth(pybotvac.OAuthSession): """Provide Neato Botvac authentication tied to an OAuth2 based config entry.""" diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 449de72b158908..1f2f575ae505a9 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -11,8 +11,6 @@ # pylint: disable=unused-import from .const import NEATO_DOMAIN -_LOGGER = logging.getLogger(__name__) - class OAuth2FlowHandler( config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=NEATO_DOMAIN diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index e5bd7ea1ca8a60..ee16ca1166f874 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -1,5 +1,4 @@ """Provides device automations for Nest.""" -import logging from typing import List import voluptuous as vol @@ -17,8 +16,6 @@ from .const import DATA_SUBSCRIBER, DOMAIN from .events import DEVICE_TRAIT_TRIGGER_MAP, NEST_EVENT -_LOGGER = logging.getLogger(__name__) - DEVICE = "device" TRIGGER_TYPES = set(DEVICE_TRAIT_TRIGGER_MAP.values()) diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 34c2909188f1a2..9f4e69bdb8c061 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -1,6 +1,5 @@ """Support for NWS weather service.""" from datetime import timedelta -import logging from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, @@ -47,8 +46,6 @@ NWS_DATA, ) -_LOGGER = logging.getLogger(__name__) - PARALLEL_UPDATES = 0 OBSERVATION_VALID_TIME = timedelta(minutes=20) diff --git a/homeassistant/components/ondilo_ico/config_flow.py b/homeassistant/components/ondilo_ico/config_flow.py index c6a164e913b120..74c668a3d2c8e9 100644 --- a/homeassistant/components/ondilo_ico/config_flow.py +++ b/homeassistant/components/ondilo_ico/config_flow.py @@ -7,8 +7,6 @@ from .const import DOMAIN from .oauth_impl import OndiloOauth2Implementation -_LOGGER = logging.getLogger(__name__) - class OAuth2FlowHandler( config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN diff --git a/homeassistant/components/ozw/fan.py b/homeassistant/components/ozw/fan.py index b4054207d0f170..be0bd372b6583f 100644 --- a/homeassistant/components/ozw/fan.py +++ b/homeassistant/components/ozw/fan.py @@ -1,5 +1,4 @@ """Support for Z-Wave fans.""" -import logging import math from homeassistant.components.fan import ( @@ -17,8 +16,6 @@ from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity -_LOGGER = logging.getLogger(__name__) - SUPPORTED_FEATURES = SUPPORT_SET_SPEED SPEED_RANGE = (1, 99) # off is not included diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index 1dd347305e10f7..ae767add18b9d2 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -1,6 +1,4 @@ """Support for Plaato Airlock sensors.""" - -import logging from typing import Optional from pyplaato.models.device import PlaatoDevice @@ -25,8 +23,6 @@ ) from .entity import PlaatoEntity -_LOGGER = logging.getLogger(__name__) - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Plaato sensor.""" diff --git a/homeassistant/components/proxmoxve/binary_sensor.py b/homeassistant/components/proxmoxve/binary_sensor.py index 014766b532e95c..1151c2ec33299f 100644 --- a/homeassistant/components/proxmoxve/binary_sensor.py +++ b/homeassistant/components/proxmoxve/binary_sensor.py @@ -1,13 +1,9 @@ """Binary sensor to read Proxmox VE data.""" -import logging - from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from . import COORDINATOR, DOMAIN, ProxmoxEntity -_LOGGER = logging.getLogger(__name__) - async def async_setup_platform(hass, config, add_entities, discovery_info=None): """Set up binary sensors.""" diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index c175117efcb344..94c79a1504f062 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -1,7 +1,4 @@ """Webhooks used by rachio.""" - -import logging - from aiohttp import web from homeassistant.const import URL_API @@ -80,9 +77,6 @@ } -_LOGGER = logging.getLogger(__name__) - - @callback def async_register_webhook(hass, webhook_id, entry_id): """Register a webhook.""" diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index d7c17a1ccff2af..affff3823659b8 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -21,8 +21,6 @@ from .const import DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, entry, async_add_entities) -> None: """Set up TotalConnect alarm panels based on a config entry.""" diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py index e296b12fa59d4d..6bee603d1b1f4b 100644 --- a/homeassistant/components/totalconnect/binary_sensor.py +++ b/homeassistant/components/totalconnect/binary_sensor.py @@ -1,6 +1,4 @@ """Interfaces with TotalConnect sensors.""" -import logging - from homeassistant.components.binary_sensor import ( DEVICE_CLASS_DOOR, DEVICE_CLASS_GAS, @@ -10,8 +8,6 @@ from .const import DOMAIN -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, entry, async_add_entities) -> None: """Set up TotalConnect device sensors based on a config entry.""" diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index da851d4a7768d1..0502273818c299 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,6 +1,5 @@ """Support for the Tuya climate devices.""" from datetime import timedelta -import logging from homeassistant.components.climate import ( DOMAIN as SENSOR_DOMAIN, @@ -56,8 +55,6 @@ FAN_MODES = {FAN_LOW, FAN_MEDIUM, FAN_HIGH} -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up tuya sensors dynamically through tuya discovery.""" diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 48f35e035f0c18..a0d8abc12330b1 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -1,6 +1,5 @@ """Binary sensors on Zigbee Home Automation networks.""" import functools -import logging from homeassistant.components.binary_sensor import ( DEVICE_CLASS_GAS, @@ -32,8 +31,6 @@ from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity -_LOGGER = logging.getLogger(__name__) - # Zigbee Cluster Library Zone Type to Home Assistant device class CLASS_MAPPING = { 0x000D: DEVICE_CLASS_MOTION, diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 03a917217a9909..91c316d307e582 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -1,6 +1,5 @@ """Websocket API for Z-Wave JS.""" import json -import logging from aiohttp import hdrs, web, web_exceptions import voluptuous as vol @@ -17,8 +16,6 @@ from .const import DATA_CLIENT, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY -_LOGGER = logging.getLogger(__name__) - ID = "id" ENTRY_ID = "entry_id" NODE_ID = "node_id" diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index a0b0648932cfa9..689187f0b34526 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -1,5 +1,4 @@ """Representation of Z-Wave thermostats.""" -import logging from typing import Any, Callable, Dict, List, Optional from zwave_js_server.client import Client as ZwaveClient @@ -47,8 +46,6 @@ from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity -_LOGGER = logging.getLogger(__name__) - # Map Z-Wave HVAC Mode to Home Assistant value # Note: We treat "auto" as "heat_cool" as most Z-Wave devices # report auto_changeover as auto without schedule support. diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index 58b85eabef13a4..e957d774e56e51 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -1,5 +1,4 @@ """Support for Z-Wave fans.""" -import logging import math from typing import Any, Callable, List, Optional @@ -22,8 +21,6 @@ from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity -_LOGGER = logging.getLogger(__name__) - SUPPORTED_FEATURES = SUPPORT_SET_SPEED SPEED_RANGE = (1, 99) # off is not included diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index 307ba577de3c71..a433bbe9935c97 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,6 +1,5 @@ """The tests for the feedreader component.""" from datetime import timedelta -from logging import getLogger from os import remove from os.path import exists import time @@ -24,8 +23,6 @@ from tests.common import get_test_home_assistant, load_fixture -_LOGGER = getLogger(__name__) - URL = "http://some.rss.local/rss_feed.xml" VALID_CONFIG_1 = {feedreader.DOMAIN: {CONF_URLS: [URL]}} VALID_CONFIG_2 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_SCAN_INTERVAL: 60}} diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index 6ca483b2588500..29e897916b9bbe 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -1,5 +1,4 @@ """Fixtures for harmony tests.""" -import logging from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from aioharmony.const import ClientCallbackType @@ -9,8 +8,6 @@ from .const import NILE_TV_ACTIVITY_ID, PLAY_MUSIC_ACTIVITY_ID, WATCH_TV_ACTIVITY_ID -_LOGGER = logging.getLogger(__name__) - ACTIVITIES_TO_IDS = { ACTIVITY_POWER_OFF: -1, "Watch TV": WATCH_TV_ACTIVITY_ID, diff --git a/tests/components/harmony/test_activity_changes.py b/tests/components/harmony/test_activity_changes.py index ff76c3ce998bac..dbbc6beef5bdad 100644 --- a/tests/components/harmony/test_activity_changes.py +++ b/tests/components/harmony/test_activity_changes.py @@ -1,7 +1,4 @@ """Test the Logitech Harmony Hub activity switches.""" - -import logging - from homeassistant.components.harmony.const import DOMAIN from homeassistant.components.remote import ATTR_ACTIVITY, DOMAIN as REMOTE_DOMAIN from homeassistant.components.switch import ( @@ -22,8 +19,6 @@ from tests.common import MockConfigEntry -_LOGGER = logging.getLogger(__name__) - async def test_switch_toggles(mock_hc, hass, mock_write_config): """Ensure calls to the switch modify the harmony state.""" diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index e50de207d00ca5..954d9abc129d75 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -1,7 +1,6 @@ """Tests for the Hyperion component.""" from __future__ import annotations -import logging from types import TracebackType from typing import Any, Dict, Optional, Type from unittest.mock import AsyncMock, Mock, patch @@ -63,8 +62,6 @@ "info": {"required": False}, } -_LOGGER = logging.getLogger(__name__) - class AsyncContextManagerMock(Mock): """An async context manager mock for Hyperion.""" diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index ef7046660d644e..beb642792c9866 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the Hyperion config flow.""" - -import logging from typing import Any, Dict, Optional from unittest.mock import AsyncMock, patch @@ -41,8 +39,6 @@ from tests.common import MockConfigEntry -_LOGGER = logging.getLogger(__name__) - TEST_IP_ADDRESS = "192.168.0.1" TEST_HOST_PORT: Dict[str, Any] = { CONF_HOST: TEST_HOST, diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index c3226cdd389ab5..fe4279ed731c71 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -1,5 +1,4 @@ """Tests for the Hyperion integration.""" -import logging from typing import Optional from unittest.mock import AsyncMock, Mock, call, patch @@ -52,8 +51,6 @@ setup_test_config_entry, ) -_LOGGER = logging.getLogger(__name__) - COLOR_BLACK = color_util.COLORS["black"] diff --git a/tests/components/hyperion/test_switch.py b/tests/components/hyperion/test_switch.py index dcfba9662bfbe4..34030787e20b35 100644 --- a/tests/components/hyperion/test_switch.py +++ b/tests/components/hyperion/test_switch.py @@ -1,5 +1,4 @@ """Tests for the Hyperion integration.""" -import logging from unittest.mock import AsyncMock, call, patch from hyperion.const import ( @@ -35,7 +34,6 @@ {"enabled": True, "name": "LEDDEVICE"}, ] -_LOGGER = logging.getLogger(__name__) TEST_SWITCH_COMPONENT_BASE_ENTITY_ID = "switch.test_instance_1_component" TEST_SWITCH_COMPONENT_ALL_ENTITY_ID = f"{TEST_SWITCH_COMPONENT_BASE_ENTITY_ID}_all" diff --git a/tests/components/litejet/test_init.py b/tests/components/litejet/test_init.py index 3861e7a058ebb5..4aee0086cbdfe0 100644 --- a/tests/components/litejet/test_init.py +++ b/tests/components/litejet/test_init.py @@ -1,13 +1,10 @@ """The tests for the litejet component.""" -import logging import unittest from homeassistant.components import litejet from tests.common import get_test_home_assistant -_LOGGER = logging.getLogger(__name__) - class TestLiteJet(unittest.TestCase): """Test the litejet component.""" diff --git a/tests/components/litejet/test_scene.py b/tests/components/litejet/test_scene.py index c2297af6d3f509..fe9298cf1877f5 100644 --- a/tests/components/litejet/test_scene.py +++ b/tests/components/litejet/test_scene.py @@ -1,5 +1,4 @@ """The tests for the litejet component.""" -import logging import unittest from unittest import mock @@ -9,8 +8,6 @@ from tests.common import get_test_home_assistant from tests.components.scene import common -_LOGGER = logging.getLogger(__name__) - ENTITY_SCENE = "scene.mock_scene_1" ENTITY_SCENE_NUMBER = 1 ENTITY_OTHER_SCENE = "scene.mock_scene_2" diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 6eaaee87af0ecb..ea6048dfc9e702 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -1,5 +1,4 @@ """Test check_config script.""" -import logging from unittest.mock import patch import pytest @@ -9,8 +8,6 @@ from tests.common import get_test_config_dir, patch_yaml_files -_LOGGER = logging.getLogger(__name__) - BASE_CONFIG = ( "homeassistant:\n" " name: Home\n" From 27d16af36b642f739ba5efef68d65a24bc5dc247 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Feb 2021 13:23:31 +0000 Subject: [PATCH 0469/1818] Don't allow recursive secrets loading (#41812) Co-authored-by: Martin Hjelmare --- homeassistant/util/yaml/loader.py | 5 +++++ tests/util/yaml/test_init.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 746806f527def0..294cd0ac570331 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -275,6 +275,11 @@ def _load_secret_yaml(secret_path: str) -> JSON_TYPE: def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: """Load secrets and embed it into the configuration YAML.""" + if os.path.basename(loader.name) == SECRET_YAML: + _LOGGER.error("secrets.yaml: attempt to load secret from within secrets file") + raise HomeAssistantError( + "secrets.yaml: attempt to load secret from within secrets file" + ) secret_path = os.path.dirname(loader.name) while True: secrets = _load_secret_yaml(secret_path) diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index 34097287bc3734..e28a12acf71ab1 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -463,6 +463,17 @@ def test_duplicate_key(caplog): assert "contains duplicate key" in caplog.text +def test_no_recursive_secrets(caplog): + """Test that loading of secrets from the secrets file fails correctly.""" + files = {YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"} + with patch_yaml_files(files), pytest.raises(HomeAssistantError) as e: + load_yaml_config_file(YAML_CONFIG_FILE) + assert e.value.args == ( + "secrets.yaml: attempt to load secret from within secrets file", + ) + assert "attempt to load secret from within secrets file" in caplog.text + + def test_input_class(): """Test input class.""" input = yaml_loader.Input("hello") From 97776088618e279df65c768690718ee547ba091f Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sun, 14 Feb 2021 16:24:00 +0100 Subject: [PATCH 0470/1818] Add myself to RFLink codeowners (#46511) --- CODEOWNERS | 1 + homeassistant/components/rflink/manifest.json | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6e3bc1feb87df7..c6794a605d93aa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -378,6 +378,7 @@ homeassistant/components/random/* @fabaff homeassistant/components/recollect_waste/* @bachya homeassistant/components/rejseplanen/* @DarkFox homeassistant/components/repetier/* @MTrab +homeassistant/components/rflink/* @javicalle homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221 homeassistant/components/ring/* @balloob homeassistant/components/risco/* @OnFreund diff --git a/homeassistant/components/rflink/manifest.json b/homeassistant/components/rflink/manifest.json index f3854a139f2d46..ebd1fb5afdca52 100644 --- a/homeassistant/components/rflink/manifest.json +++ b/homeassistant/components/rflink/manifest.json @@ -3,5 +3,7 @@ "name": "RFLink", "documentation": "https://www.home-assistant.io/integrations/rflink", "requirements": ["rflink==0.0.58"], - "codeowners": [] + "codeowners": [ + "@javicalle" + ] } From 855bd653b4de85a4fd995c724af88999be203fb3 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sun, 14 Feb 2021 17:40:30 +0100 Subject: [PATCH 0471/1818] Update modbus test harness (#44892) * Update test harness to allow discovery_info testing. This is a needed update, before the whole config is converted to the new structure, because it allows test of both the old and new configuration style. The following changes have been made: - Combine test functions into a single base_test. - Prepare for new mock by combining the 2 common test functions into one. - Change mock to be only modbusHub - Do not replace whole modbus class, but only the class that connects to pymodbus (modbusHub). This allows test of modbus configurations and not only component configurations, and is needed when testing discovery activated platform. - Add test of climate. Warning this is merely test of discovery, the real cover tests still needs to be added. - Add test of cover. Warning this is merely test of discovery, the real cover tests still needs to be added. * Update comment for old config * Do not use hass.data, but instead patch pymodbus library. * Add test of configuration (old/new way as available). * Move assert to test function. Make assert more understandable by removing it from the helper. add base_config_test (check just calls base_test) to make it clear if test is wanted or just controlling the config. * use setup_component to load Modbus since it is an integration. * Optimized flow in test helper. --- tests/components/modbus/conftest.py | 190 +++++++++++------- .../modbus/test_modbus_binary_sensor.py | 87 ++++---- .../components/modbus/test_modbus_climate.py | 75 +++++++ tests/components/modbus/test_modbus_cover.py | 136 +++++++++++++ tests/components/modbus/test_modbus_sensor.py | 65 ++++-- tests/components/modbus/test_modbus_switch.py | 141 +++++++------ 6 files changed, 487 insertions(+), 207 deletions(-) create mode 100644 tests/components/modbus/test_modbus_climate.py create mode 100644 tests/components/modbus/test_modbus_cover.py diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index a3e6078ea096e3..403607b110fc98 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -1,31 +1,25 @@ """The tests for the Modbus sensor component.""" +from datetime import timedelta +import logging from unittest import mock -from unittest.mock import patch import pytest -from homeassistant.components.modbus.const import ( - CALL_TYPE_COIL, - CALL_TYPE_DISCRETE, - CALL_TYPE_REGISTER_INPUT, - DEFAULT_HUB, - MODBUS_DOMAIN as DOMAIN, +from homeassistant.components.modbus.const import DEFAULT_HUB, MODBUS_DOMAIN as DOMAIN +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PLATFORM, + CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_TYPE, ) -from homeassistant.const import CONF_PLATFORM, CONF_SCAN_INTERVAL from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed - -@pytest.fixture() -def mock_hub(hass): - """Mock hub.""" - with patch("homeassistant.components.modbus.setup", return_value=True): - hub = mock.MagicMock() - hub.name = "hub" - hass.data[DOMAIN] = {DEFAULT_HUB: hub} - yield hub +_LOGGER = logging.getLogger(__name__) class ReadResult: @@ -37,63 +31,119 @@ def __init__(self, register_words): self.bits = register_words -async def setup_base_test( - sensor_name, +async def base_test( hass, - use_mock_hub, - data_array, + config_device, + device_name, entity_domain, - scan_interval, + array_name_discovery, + array_name_old_config, + register_words, + expected, + method_discovery=False, + check_config_only=False, + config_modbus=None, + scan_interval=None, ): - """Run setup device for given config.""" - # Full sensor configuration - config = { - entity_domain: { - CONF_PLATFORM: "modbus", - CONF_SCAN_INTERVAL: scan_interval, - **data_array, + """Run test on device for given config.""" + + if config_modbus is None: + config_modbus = { + DOMAIN: { + CONF_NAME: DEFAULT_HUB, + CONF_TYPE: "tcp", + CONF_HOST: "modbusTest", + CONF_PORT: 5001, + }, } - } - - # Initialize sensor - now = dt_util.utcnow() - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - assert await async_setup_component(hass, entity_domain, config) - await hass.async_block_till_done() - - entity_id = f"{entity_domain}.{sensor_name}" - device = hass.states.get(entity_id) - if device is None: - pytest.fail("CONFIG failed, see output") - return entity_id, now, device - -async def run_base_read_test( - entity_id, + mock_sync = mock.MagicMock() + with mock.patch( + "homeassistant.components.modbus.modbus.ModbusTcpClient", return_value=mock_sync + ), mock.patch( + "homeassistant.components.modbus.modbus.ModbusSerialClient", + return_value=mock_sync, + ), mock.patch( + "homeassistant.components.modbus.modbus.ModbusUdpClient", return_value=mock_sync + ): + + # Setup inputs for the sensor + read_result = ReadResult(register_words) + mock_sync.read_coils.return_value = read_result + mock_sync.read_discrete_inputs.return_value = read_result + mock_sync.read_input_registers.return_value = read_result + mock_sync.read_holding_registers.return_value = read_result + + # mock timer and add old/new config + now = dt_util.utcnow() + with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): + if method_discovery and config_device is not None: + # setup modbus which in turn does setup for the devices + config_modbus[DOMAIN].update( + {array_name_discovery: [{**config_device}]} + ) + config_device = None + assert await async_setup_component(hass, DOMAIN, config_modbus) + await hass.async_block_till_done() + + # setup platform old style + if config_device is not None: + config_device = { + entity_domain: { + CONF_PLATFORM: DOMAIN, + array_name_old_config: [ + { + **config_device, + } + ], + } + } + if scan_interval is not None: + config_device[entity_domain][CONF_SCAN_INTERVAL] = scan_interval + assert await async_setup_component(hass, entity_domain, config_device) + await hass.async_block_till_done() + + assert DOMAIN in hass.data + if config_device is not None: + entity_id = f"{entity_domain}.{device_name}" + device = hass.states.get(entity_id) + if device is None: + pytest.fail("CONFIG failed, see output") + if check_config_only: + return + + # Trigger update call with time_changed event + now = now + timedelta(seconds=scan_interval + 60) + with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): + async_fire_time_changed(hass, now) + await hass.async_block_till_done() + + # Check state + entity_id = f"{entity_domain}.{device_name}" + return hass.states.get(entity_id).state + + +async def base_config_test( hass, - use_mock_hub, - register_type, - register_words, - expected, - now, + config_device, + device_name, + entity_domain, + array_name_discovery, + array_name_old_config, + method_discovery=False, + config_modbus=None, ): - """Run test for given config.""" - # Setup inputs for the sensor - read_result = ReadResult(register_words) - if register_type == CALL_TYPE_COIL: - use_mock_hub.read_coils.return_value = read_result - elif register_type == CALL_TYPE_DISCRETE: - use_mock_hub.read_discrete_inputs.return_value = read_result - elif register_type == CALL_TYPE_REGISTER_INPUT: - use_mock_hub.read_input_registers.return_value = read_result - else: # CALL_TYPE_REGISTER_HOLDING - use_mock_hub.read_holding_registers.return_value = read_result - - # Trigger update call with time_changed event - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - async_fire_time_changed(hass, now) - await hass.async_block_till_done() - - # Check state - state = hass.states.get(entity_id).state - assert state == expected + """Check config of device for given config.""" + + await base_test( + hass, + config_device, + device_name, + entity_domain, + array_name_discovery, + array_name_old_config, + None, + None, + method_discovery=method_discovery, + check_config_only=True, + ) diff --git a/tests/components/modbus/test_modbus_binary_sensor.py b/tests/components/modbus/test_modbus_binary_sensor.py index 3bc7223c865c37..4cd586f390f637 100644 --- a/tests/components/modbus/test_modbus_binary_sensor.py +++ b/tests/components/modbus/test_modbus_binary_sensor.py @@ -1,6 +1,4 @@ """The tests for the Modbus sensor component.""" -from datetime import timedelta - import pytest from homeassistant.components.binary_sensor import DOMAIN as SENSOR_DOMAIN @@ -10,83 +8,76 @@ CONF_INPUT_TYPE, CONF_INPUTS, ) -from homeassistant.const import CONF_ADDRESS, CONF_NAME, STATE_OFF, STATE_ON +from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_SLAVE, STATE_OFF, STATE_ON + +from .conftest import base_config_test, base_test -from .conftest import run_base_read_test, setup_base_test +@pytest.mark.parametrize("do_options", [False, True]) +async def test_config_binary_sensor(hass, do_options): + """Run test for binary sensor.""" + sensor_name = "test_sensor" + config_sensor = { + CONF_NAME: sensor_name, + CONF_ADDRESS: 51, + } + if do_options: + config_sensor.update( + { + CONF_SLAVE: 10, + CONF_INPUT_TYPE: CALL_TYPE_DISCRETE, + } + ) + await base_config_test( + hass, + config_sensor, + sensor_name, + SENSOR_DOMAIN, + None, + CONF_INPUTS, + method_discovery=False, + ) + +@pytest.mark.parametrize("do_type", [CALL_TYPE_COIL, CALL_TYPE_DISCRETE]) @pytest.mark.parametrize( - "cfg,regs,expected", + "regs,expected", [ ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0xFF], STATE_ON, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0x01], STATE_ON, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0x00], STATE_OFF, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0x80], STATE_OFF, ), ( - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - }, [0xFE], STATE_OFF, ), - ( - { - CONF_INPUT_TYPE: CALL_TYPE_DISCRETE, - }, - [0xFF], - STATE_ON, - ), - ( - { - CONF_INPUT_TYPE: CALL_TYPE_DISCRETE, - }, - [0x00], - STATE_OFF, - ), ], ) -async def test_coil_true(hass, mock_hub, cfg, regs, expected): +async def test_all_binary_sensor(hass, do_type, regs, expected): """Run test for given config.""" sensor_name = "modbus_test_binary_sensor" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - sensor_name, + state = await base_test( hass, - mock_hub, - {CONF_INPUTS: [dict(**{CONF_NAME: sensor_name, CONF_ADDRESS: 1234}, **cfg)]}, + {CONF_NAME: sensor_name, CONF_ADDRESS: 1234, CONF_INPUT_TYPE: do_type}, + sensor_name, SENSOR_DOMAIN, - scan_interval, - ) - await run_base_read_test( - entity_id, - hass, - mock_hub, - cfg.get(CONF_INPUT_TYPE), + None, + CONF_INPUTS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_climate.py b/tests/components/modbus/test_modbus_climate.py new file mode 100644 index 00000000000000..bbdaed63995b3d --- /dev/null +++ b/tests/components/modbus/test_modbus_climate.py @@ -0,0 +1,75 @@ +"""The tests for the Modbus climate component.""" +import pytest + +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN +from homeassistant.components.modbus.const import ( + CONF_CLIMATES, + CONF_CURRENT_TEMP, + CONF_DATA_COUNT, + CONF_TARGET_TEMP, +) +from homeassistant.const import CONF_NAME, CONF_SCAN_INTERVAL, CONF_SLAVE + +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +async def test_config_climate(hass, do_options): + """Run test for climate.""" + device_name = "test_climate" + device_config = { + CONF_NAME: device_name, + CONF_TARGET_TEMP: 117, + CONF_CURRENT_TEMP: 117, + CONF_SLAVE: 10, + } + if do_options: + device_config.update( + { + CONF_SCAN_INTERVAL: 20, + CONF_DATA_COUNT: 2, + } + ) + await base_config_test( + hass, + device_config, + device_name, + CLIMATE_DOMAIN, + CONF_CLIMATES, + None, + method_discovery=True, + ) + + +@pytest.mark.parametrize( + "regs,expected", + [ + ( + [0x00], + "auto", + ), + ], +) +async def test_temperature_climate(hass, regs, expected): + """Run test for given config.""" + climate_name = "modbus_test_climate" + return + state = await base_test( + hass, + { + CONF_NAME: climate_name, + CONF_SLAVE: 1, + CONF_TARGET_TEMP: 117, + CONF_CURRENT_TEMP: 117, + CONF_DATA_COUNT: 2, + }, + climate_name, + CLIMATE_DOMAIN, + CONF_CLIMATES, + None, + regs, + expected, + method_discovery=True, + scan_interval=5, + ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_cover.py b/tests/components/modbus/test_modbus_cover.py new file mode 100644 index 00000000000000..ff765314745994 --- /dev/null +++ b/tests/components/modbus/test_modbus_cover.py @@ -0,0 +1,136 @@ +"""The tests for the Modbus cover component.""" +import pytest + +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.components.modbus.const import CALL_TYPE_COIL, CONF_REGISTER +from homeassistant.const import ( + CONF_COVERS, + CONF_NAME, + CONF_SCAN_INTERVAL, + CONF_SLAVE, + STATE_OPEN, + STATE_OPENING, +) + +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +@pytest.mark.parametrize("read_type", [CALL_TYPE_COIL, CONF_REGISTER]) +async def test_config_cover(hass, do_options, read_type): + """Run test for cover.""" + device_name = "test_cover" + device_config = { + CONF_NAME: device_name, + read_type: 1234, + } + if do_options: + device_config.update( + { + CONF_SLAVE: 10, + CONF_SCAN_INTERVAL: 20, + } + ) + await base_config_test( + hass, + device_config, + device_name, + COVER_DOMAIN, + CONF_COVERS, + None, + method_discovery=True, + ) + + +@pytest.mark.parametrize( + "regs,expected", + [ + ( + [0x00], + STATE_OPENING, + ), + ( + [0x80], + STATE_OPENING, + ), + ( + [0xFE], + STATE_OPENING, + ), + ( + [0xFF], + STATE_OPENING, + ), + ( + [0x01], + STATE_OPENING, + ), + ], +) +async def test_coil_cover(hass, regs, expected): + """Run test for given config.""" + cover_name = "modbus_test_cover" + state = await base_test( + hass, + { + CONF_NAME: cover_name, + CALL_TYPE_COIL: 1234, + CONF_SLAVE: 1, + }, + cover_name, + COVER_DOMAIN, + CONF_COVERS, + None, + regs, + expected, + method_discovery=True, + scan_interval=5, + ) + assert state == expected + + +@pytest.mark.parametrize( + "regs,expected", + [ + ( + [0x00], + STATE_OPEN, + ), + ( + [0x80], + STATE_OPEN, + ), + ( + [0xFE], + STATE_OPEN, + ), + ( + [0xFF], + STATE_OPEN, + ), + ( + [0x01], + STATE_OPEN, + ), + ], +) +async def test_register_COVER(hass, regs, expected): + """Run test for given config.""" + cover_name = "modbus_test_cover" + state = await base_test( + hass, + { + CONF_NAME: cover_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + }, + cover_name, + COVER_DOMAIN, + CONF_COVERS, + None, + regs, + expected, + method_discovery=True, + scan_interval=5, + ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_sensor.py b/tests/components/modbus/test_modbus_sensor.py index 3f7c0fc60df52f..71a5213db9e07b 100644 --- a/tests/components/modbus/test_modbus_sensor.py +++ b/tests/components/modbus/test_modbus_sensor.py @@ -1,6 +1,4 @@ """The tests for the Modbus sensor component.""" -from datetime import timedelta - import pytest from homeassistant.components.modbus.const import ( @@ -20,9 +18,41 @@ DATA_TYPE_UINT, ) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import CONF_NAME, CONF_OFFSET +from homeassistant.const import CONF_NAME, CONF_OFFSET, CONF_SLAVE + +from .conftest import base_config_test, base_test + -from .conftest import run_base_read_test, setup_base_test +@pytest.mark.parametrize("do_options", [False, True]) +async def test_config_sensor(hass, do_options): + """Run test for sensor.""" + sensor_name = "test_sensor" + config_sensor = { + CONF_NAME: sensor_name, + CONF_REGISTER: 51, + } + if do_options: + config_sensor.update( + { + CONF_SLAVE: 10, + CONF_COUNT: 1, + CONF_DATA_TYPE: "int", + CONF_PRECISION: 0, + CONF_SCALE: 1, + CONF_REVERSE_ORDER: False, + CONF_OFFSET: 0, + CONF_REGISTER_TYPE: CALL_TYPE_REGISTER_HOLDING, + } + ) + await base_config_test( + hass, + config_sensor, + sensor_name, + SENSOR_DOMAIN, + None, + CONF_REGISTERS, + method_discovery=False, + ) @pytest.mark.parametrize( @@ -235,28 +265,19 @@ ), ], ) -async def test_all_sensor(hass, mock_hub, cfg, regs, expected): +async def test_all_sensor(hass, cfg, regs, expected): """Run test for sensor.""" sensor_name = "modbus_test_sensor" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - sensor_name, + state = await base_test( hass, - mock_hub, - { - CONF_REGISTERS: [ - dict(**{CONF_NAME: sensor_name, CONF_REGISTER: 1234}, **cfg) - ] - }, + {CONF_NAME: sensor_name, CONF_REGISTER: 1234, **cfg}, + sensor_name, SENSOR_DOMAIN, - scan_interval, - ) - await run_base_read_test( - entity_id, - hass, - mock_hub, - cfg.get(CONF_REGISTER_TYPE), + None, + CONF_REGISTERS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected diff --git a/tests/components/modbus/test_modbus_switch.py b/tests/components/modbus/test_modbus_switch.py index da2ff953660123..5c4717c9cf8858 100644 --- a/tests/components/modbus/test_modbus_switch.py +++ b/tests/components/modbus/test_modbus_switch.py @@ -1,11 +1,8 @@ """The tests for the Modbus switch component.""" -from datetime import timedelta - import pytest from homeassistant.components.modbus.const import ( CALL_TYPE_COIL, - CALL_TYPE_REGISTER_HOLDING, CONF_COILS, CONF_REGISTER, CONF_REGISTERS, @@ -20,7 +17,43 @@ STATE_ON, ) -from .conftest import run_base_read_test, setup_base_test +from .conftest import base_config_test, base_test + + +@pytest.mark.parametrize("do_options", [False, True]) +@pytest.mark.parametrize("read_type", [CALL_TYPE_COIL, CONF_REGISTER]) +async def test_config_switch(hass, do_options, read_type): + """Run test for switch.""" + device_name = "test_switch" + + if read_type == CONF_REGISTER: + device_config = { + CONF_NAME: device_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + CONF_COMMAND_OFF: 0x00, + CONF_COMMAND_ON: 0x01, + } + array_type = CONF_REGISTERS + else: + device_config = { + CONF_NAME: device_name, + read_type: 1234, + CONF_SLAVE: 10, + } + array_type = CONF_COILS + if do_options: + device_config.update({}) + + await base_config_test( + hass, + device_config, + device_name, + SWITCH_DOMAIN, + None, + array_type, + method_discovery=False, + ) @pytest.mark.parametrize( @@ -48,32 +81,26 @@ ), ], ) -async def test_coil_switch(hass, mock_hub, regs, expected): +async def test_coil_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - switch_name, + state = await base_test( hass, - mock_hub, { - CONF_COILS: [ - {CONF_NAME: switch_name, CALL_TYPE_COIL: 1234, CONF_SLAVE: 1}, - ] + CONF_NAME: switch_name, + CALL_TYPE_COIL: 1234, + CONF_SLAVE: 1, }, + switch_name, SWITCH_DOMAIN, - scan_interval, - ) - - await run_base_read_test( - entity_id, - hass, - mock_hub, - CALL_TYPE_COIL, + None, + CONF_COILS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected @pytest.mark.parametrize( @@ -101,38 +128,28 @@ async def test_coil_switch(hass, mock_hub, regs, expected): ), ], ) -async def test_register_switch(hass, mock_hub, regs, expected): +async def test_register_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - switch_name, + state = await base_test( hass, - mock_hub, { - CONF_REGISTERS: [ - { - CONF_NAME: switch_name, - CONF_REGISTER: 1234, - CONF_SLAVE: 1, - CONF_COMMAND_OFF: 0x00, - CONF_COMMAND_ON: 0x01, - }, - ] + CONF_NAME: switch_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + CONF_COMMAND_OFF: 0x00, + CONF_COMMAND_ON: 0x01, }, + switch_name, SWITCH_DOMAIN, - scan_interval, - ) - - await run_base_read_test( - entity_id, - hass, - mock_hub, - CALL_TYPE_REGISTER_HOLDING, + None, + CONF_REGISTERS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected @pytest.mark.parametrize( @@ -152,35 +169,25 @@ async def test_register_switch(hass, mock_hub, regs, expected): ), ], ) -async def test_register_state_switch(hass, mock_hub, regs, expected): +async def test_register_state_switch(hass, regs, expected): """Run test for given config.""" switch_name = "modbus_test_switch" - scan_interval = 5 - entity_id, now, device = await setup_base_test( - switch_name, + state = await base_test( hass, - mock_hub, { - CONF_REGISTERS: [ - { - CONF_NAME: switch_name, - CONF_REGISTER: 1234, - CONF_SLAVE: 1, - CONF_COMMAND_OFF: 0x04, - CONF_COMMAND_ON: 0x40, - }, - ] + CONF_NAME: switch_name, + CONF_REGISTER: 1234, + CONF_SLAVE: 1, + CONF_COMMAND_OFF: 0x04, + CONF_COMMAND_ON: 0x40, }, + switch_name, SWITCH_DOMAIN, - scan_interval, - ) - - await run_base_read_test( - entity_id, - hass, - mock_hub, - CALL_TYPE_REGISTER_HOLDING, + None, + CONF_REGISTERS, regs, expected, - now + timedelta(seconds=scan_interval + 1), + method_discovery=False, + scan_interval=5, ) + assert state == expected From f8f86fbe48cea849f04ac52238e004d72ec7eef6 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sun, 14 Feb 2021 19:54:11 +0100 Subject: [PATCH 0472/1818] Do not trigger when template is true at startup (#46423) --- homeassistant/components/template/trigger.py | 30 ++++++++- tests/components/template/test_trigger.py | 70 ++++++++++++++++++-- 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 80ad585486b294..9e6ee086c734c5 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -37,21 +37,49 @@ async def async_attach_trigger( template.attach(hass, time_delta) delay_cancel = None job = HassJob(action) + armed = False + + # Arm at setup if the template is already false. + try: + if not result_as_boolean(value_template.async_render()): + armed = True + except exceptions.TemplateError as ex: + _LOGGER.warning( + "Error initializing 'template' trigger for '%s': %s", + automation_info["name"], + ex, + ) @callback def template_listener(event, updates): """Listen for state changes and calls action.""" - nonlocal delay_cancel + nonlocal delay_cancel, armed result = updates.pop().result + if isinstance(result, exceptions.TemplateError): + _LOGGER.warning( + "Error evaluating 'template' trigger for '%s': %s", + automation_info["name"], + result, + ) + return + if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): + armed = True return + # Only fire when previously armed. + if not armed: + return + + # Fire! + armed = False + entity_id = event and event.data.get("entity_id") from_s = event and event.data.get("old_state") to_s = event and event.data.get("new_state") diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index de4974cb1b643b..3ba79e85bf2e31 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -43,13 +43,15 @@ async def test_if_fires_on_change_bool(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": "{{ states.test.entity.state and true }}", + "value_template": '{{ states.test.entity.state == "world" and true }}', }, "action": {"service": "test.automation"}, } }, ) + assert len(calls) == 0 + hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert len(calls) == 1 @@ -75,13 +77,15 @@ async def test_if_fires_on_change_str(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": '{{ states.test.entity.state and "true" }}', + "value_template": '{{ states.test.entity.state == "world" and "true" }}', }, "action": {"service": "test.automation"}, } }, ) + assert len(calls) == 0 + hass.states.async_set("test.entity", "world") await hass.async_block_till_done() assert len(calls) == 1 @@ -96,7 +100,7 @@ async def test_if_fires_on_change_str_crazy(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": '{{ states.test.entity.state and "TrUE" }}', + "value_template": '{{ states.test.entity.state == "world" and "TrUE" }}', }, "action": {"service": "test.automation"}, } @@ -108,6 +112,62 @@ async def test_if_fires_on_change_str_crazy(hass, calls): assert len(calls) == 1 +async def test_if_not_fires_when_true_at_setup(hass, calls): + """Test for not firing during startup.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "template", + "value_template": '{{ states.test.entity.state == "hello" }}', + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + +async def test_if_not_fires_because_fail(hass, calls): + """Test for not firing after TemplateError.""" + hass.states.async_set("test.number", "1") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "template", + "value_template": "{{ 84 / states.test.number.state|int == 42 }}", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + hass.states.async_set("test.number", "2") + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set("test.number", "0") + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set("test.number", "2") + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_not_fires_on_change_bool(hass, calls): """Test for not firing on boolean change.""" assert await async_setup_component( @@ -117,7 +177,7 @@ async def test_if_not_fires_on_change_bool(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": "{{ states.test.entity.state and false }}", + "value_template": '{{ states.test.entity.state == "world" and false }}', }, "action": {"service": "test.automation"}, } @@ -198,7 +258,7 @@ async def test_if_fires_on_two_change(hass, calls): automation.DOMAIN: { "trigger": { "platform": "template", - "value_template": "{{ states.test.entity.state and true }}", + "value_template": "{{ states.test.entity.state == 'world' }}", }, "action": {"service": "test.automation"}, } From c9df42b69a0e9e07dc9bac70ffb63386619f01a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Feb 2021 09:42:55 -1000 Subject: [PATCH 0473/1818] Add support for pre-filtering events to the event bus (#46371) --- homeassistant/components/recorder/__init__.py | 24 +++-- homeassistant/config_entries.py | 18 ++-- homeassistant/core.py | 59 ++++++++---- homeassistant/helpers/device_registry.py | 19 ++-- homeassistant/helpers/entity_registry.py | 14 ++- homeassistant/helpers/event.py | 61 +++++++++++-- homeassistant/scripts/benchmark/__init__.py | 89 ++++++++++++++++--- .../mqtt/test_device_tracker_discovery.py | 1 + tests/components/mqtt/test_discovery.py | 1 + tests/components/unifi/test_device_tracker.py | 1 + tests/test_core.py | 29 ++++++ 11 files changed, 256 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 0f8a5ae7f8f915..16232bcaa165c7 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -252,7 +252,22 @@ def __init__( @callback def async_initialize(self): """Initialize the recorder.""" - self.hass.bus.async_listen(MATCH_ALL, self.event_listener) + self.hass.bus.async_listen( + MATCH_ALL, self.event_listener, event_filter=self._async_event_filter + ) + + @callback + def _async_event_filter(self, event): + """Filter events.""" + if event.event_type in self.exclude_t: + return False + + entity_id = event.data.get(ATTR_ENTITY_ID) + if entity_id is not None: + if not self.entity_filter(entity_id): + return False + + return True def do_adhoc_purge(self, **kwargs): """Trigger an adhoc purge retaining keep_days worth of data.""" @@ -378,13 +393,6 @@ def async_purge(now): self._timechanges_seen = 0 self._commit_event_session_or_retry() continue - if event.event_type in self.exclude_t: - continue - - entity_id = event.data.get(ATTR_ENTITY_ID) - if entity_id is not None: - if not self.entity_filter(entity_id): - continue try: if event.event_type == EVENT_STATE_CHANGED: diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index bbc1479524ab71..7225b7c375dd24 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1139,17 +1139,13 @@ def __init__(self, hass: HomeAssistant) -> None: def async_setup(self) -> None: """Set up the disable handler.""" self.hass.bus.async_listen( - entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, self._handle_entry_updated + entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, + self._handle_entry_updated, + event_filter=_handle_entry_updated_filter, ) async def _handle_entry_updated(self, event: Event) -> None: """Handle entity registry entry update.""" - if ( - event.data["action"] != "update" - or "disabled_by" not in event.data["changes"] - ): - return - if self.registry is None: self.registry = await entity_registry.async_get_registry(self.hass) @@ -1203,6 +1199,14 @@ async def _handle_reload(self, _now: Any) -> None: ) +@callback +def _handle_entry_updated_filter(event: Event) -> bool: + """Handle entity registry entry update filter.""" + if event.data["action"] != "update" or "disabled_by" not in event.data["changes"]: + return False + return True + + async def support_entry_unload(hass: HomeAssistant, domain: str) -> bool: """Test if a domain supports entry unloading.""" integration = await loader.async_get_integration(hass, domain) diff --git a/homeassistant/core.py b/homeassistant/core.py index fff16cdd31f915..b62dd1ee7d5763 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -28,6 +28,7 @@ Mapping, Optional, Set, + Tuple, TypeVar, Union, cast, @@ -661,7 +662,7 @@ class EventBus: def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" - self._listeners: Dict[str, List[HassJob]] = {} + self._listeners: Dict[str, List[Tuple[HassJob, Optional[Callable]]]] = {} self._hass = hass @callback @@ -717,7 +718,14 @@ def async_fire( if not listeners: return - for job in listeners: + for job, event_filter in listeners: + if event_filter is not None: + try: + if not event_filter(event): + continue + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Error in event filter") + continue self._hass.async_add_hass_job(job, event) def listen(self, event_type: str, listener: Callable) -> CALLBACK_TYPE: @@ -737,23 +745,38 @@ def remove_listener() -> None: return remove_listener @callback - def async_listen(self, event_type: str, listener: Callable) -> CALLBACK_TYPE: + def async_listen( + self, + event_type: str, + listener: Callable, + event_filter: Optional[Callable] = None, + ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. To listen to all events specify the constant ``MATCH_ALL`` as event_type. + An optional event_filter, which must be a callable decorated with + @callback that returns a boolean value, determines if the + listener callable should run. + This method must be run in the event loop. """ - return self._async_listen_job(event_type, HassJob(listener)) + if event_filter is not None and not is_callback(event_filter): + raise HomeAssistantError(f"Event filter {event_filter} is not a callback") + return self._async_listen_filterable_job( + event_type, (HassJob(listener), event_filter) + ) @callback - def _async_listen_job(self, event_type: str, hassjob: HassJob) -> CALLBACK_TYPE: - self._listeners.setdefault(event_type, []).append(hassjob) + def _async_listen_filterable_job( + self, event_type: str, filterable_job: Tuple[HassJob, Optional[Callable]] + ) -> CALLBACK_TYPE: + self._listeners.setdefault(event_type, []).append(filterable_job) def remove_listener() -> None: """Remove the listener.""" - self._async_remove_listener(event_type, hassjob) + self._async_remove_listener(event_type, filterable_job) return remove_listener @@ -786,12 +809,12 @@ def async_listen_once(self, event_type: str, listener: Callable) -> CALLBACK_TYP This method must be run in the event loop. """ - job: Optional[HassJob] = None + filterable_job: Optional[Tuple[HassJob, Optional[Callable]]] = None @callback def _onetime_listener(event: Event) -> None: """Remove listener from event bus and then fire listener.""" - nonlocal job + nonlocal filterable_job if hasattr(_onetime_listener, "run"): return # Set variable so that we will never run twice. @@ -800,22 +823,24 @@ def _onetime_listener(event: Event) -> None: # multiple times as well. # This will make sure the second time it does nothing. setattr(_onetime_listener, "run", True) - assert job is not None - self._async_remove_listener(event_type, job) + assert filterable_job is not None + self._async_remove_listener(event_type, filterable_job) self._hass.async_run_job(listener, event) - job = HassJob(_onetime_listener) + filterable_job = (HassJob(_onetime_listener), None) - return self._async_listen_job(event_type, job) + return self._async_listen_filterable_job(event_type, filterable_job) @callback - def _async_remove_listener(self, event_type: str, hassjob: HassJob) -> None: + def _async_remove_listener( + self, event_type: str, filterable_job: Tuple[HassJob, Optional[Callable]] + ) -> None: """Remove a listener of a specific event_type. This method must be run in the event loop. """ try: - self._listeners[event_type].remove(hassjob) + self._listeners[event_type].remove(filterable_job) # delete event_type list if empty if not self._listeners[event_type]: @@ -823,7 +848,9 @@ def _async_remove_listener(self, event_type: str, hassjob: HassJob) -> None: except (KeyError, ValueError): # KeyError is key event_type listener did not exist # ValueError if listener did not exist within event_type - _LOGGER.exception("Unable to remove unknown job listener %s", hassjob) + _LOGGER.exception( + "Unable to remove unknown job listener %s", filterable_job + ) class State: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 0d62b2cab47986..77dc2cdf609937 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -686,25 +686,34 @@ async def cleanup() -> None: ) async def entity_registry_changed(event: Event) -> None: - """Handle entity updated or removed.""" + """Handle entity updated or removed dispatch.""" + await debounced_cleanup.async_call() + + @callback + def entity_registry_changed_filter(event: Event) -> bool: + """Handle entity updated or removed filter.""" if ( event.data["action"] == "update" and "device_id" not in event.data["changes"] ) or event.data["action"] == "create": - return + return False - await debounced_cleanup.async_call() + return True if hass.is_running: hass.bus.async_listen( - entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, entity_registry_changed + entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, + entity_registry_changed, + event_filter=entity_registry_changed_filter, ) return async def startup_clean(event: Event) -> None: """Clean up on startup.""" hass.bus.async_listen( - entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, entity_registry_changed + entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, + entity_registry_changed, + event_filter=entity_registry_changed_filter, ) await debounced_cleanup.async_call() diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 418c3f903047b5..0938ea9165fe56 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -641,12 +641,14 @@ def async_setup_entity_restore( ) -> None: """Set up the entity restore mechanism.""" + @callback + def cleanup_restored_states_filter(event: Event) -> bool: + """Clean up restored states filter.""" + return bool(event.data["action"] == "remove") + @callback def cleanup_restored_states(event: Event) -> None: """Clean up restored states.""" - if event.data["action"] != "remove": - return - state = hass.states.get(event.data["entity_id"]) if state is None or not state.attributes.get(ATTR_RESTORED): @@ -654,7 +656,11 @@ def cleanup_restored_states(event: Event) -> None: hass.states.async_remove(event.data["entity_id"], context=event.context) - hass.bus.async_listen(EVENT_ENTITY_REGISTRY_UPDATED, cleanup_restored_states) + hass.bus.async_listen( + EVENT_ENTITY_REGISTRY_UPDATED, + cleanup_restored_states, + event_filter=cleanup_restored_states_filter, + ) if hass.is_running: return diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 102a84863bd767..f496c7088a40da 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -180,7 +180,7 @@ def async_track_state_change( job = HassJob(action) @callback - def state_change_listener(event: Event) -> None: + def state_change_filter(event: Event) -> bool: """Handle specific state changes.""" if from_state is not None: old_state = event.data.get("old_state") @@ -188,15 +188,21 @@ def state_change_listener(event: Event) -> None: old_state = old_state.state if not match_from_state(old_state): - return + return False + if to_state is not None: new_state = event.data.get("new_state") if new_state is not None: new_state = new_state.state if not match_to_state(new_state): - return + return False + return True + + @callback + def state_change_dispatcher(event: Event) -> None: + """Handle specific state changes.""" hass.async_run_hass_job( job, event.data.get("entity_id"), @@ -204,6 +210,14 @@ def state_change_listener(event: Event) -> None: event.data.get("new_state"), ) + @callback + def state_change_listener(event: Event) -> None: + """Handle specific state changes.""" + if not state_change_filter(event): + return + + state_change_dispatcher(event) + if entity_ids != MATCH_ALL: # If we have a list of entity ids we use # async_track_state_change_event to route @@ -215,7 +229,9 @@ def state_change_listener(event: Event) -> None: # entity_id. return async_track_state_change_event(hass, entity_ids, state_change_listener) - return hass.bus.async_listen(EVENT_STATE_CHANGED, state_change_listener) + return hass.bus.async_listen( + EVENT_STATE_CHANGED, state_change_dispatcher, event_filter=state_change_filter + ) track_state_change = threaded_listener_factory(async_track_state_change) @@ -246,6 +262,11 @@ def async_track_state_change_event( if TRACK_STATE_CHANGE_LISTENER not in hass.data: + @callback + def _async_state_change_filter(event: Event) -> bool: + """Filter state changes by entity_id.""" + return event.data.get("entity_id") in entity_callbacks + @callback def _async_state_change_dispatcher(event: Event) -> None: """Dispatch state changes by entity_id.""" @@ -263,7 +284,9 @@ def _async_state_change_dispatcher(event: Event) -> None: ) hass.data[TRACK_STATE_CHANGE_LISTENER] = hass.bus.async_listen( - EVENT_STATE_CHANGED, _async_state_change_dispatcher + EVENT_STATE_CHANGED, + _async_state_change_dispatcher, + event_filter=_async_state_change_filter, ) job = HassJob(action) @@ -329,6 +352,12 @@ def async_track_entity_registry_updated_event( if TRACK_ENTITY_REGISTRY_UPDATED_LISTENER not in hass.data: + @callback + def _async_entity_registry_updated_filter(event: Event) -> bool: + """Filter entity registry updates by entity_id.""" + entity_id = event.data.get("old_entity_id", event.data["entity_id"]) + return entity_id in entity_callbacks + @callback def _async_entity_registry_updated_dispatcher(event: Event) -> None: """Dispatch entity registry updates by entity_id.""" @@ -347,7 +376,9 @@ def _async_entity_registry_updated_dispatcher(event: Event) -> None: ) hass.data[TRACK_ENTITY_REGISTRY_UPDATED_LISTENER] = hass.bus.async_listen( - EVENT_ENTITY_REGISTRY_UPDATED, _async_entity_registry_updated_dispatcher + EVENT_ENTITY_REGISTRY_UPDATED, + _async_entity_registry_updated_dispatcher, + event_filter=_async_entity_registry_updated_filter, ) job = HassJob(action) @@ -404,6 +435,11 @@ def async_track_state_added_domain( if TRACK_STATE_ADDED_DOMAIN_LISTENER not in hass.data: + @callback + def _async_state_change_filter(event: Event) -> bool: + """Filter state changes by entity_id.""" + return event.data.get("old_state") is None + @callback def _async_state_change_dispatcher(event: Event) -> None: """Dispatch state changes by entity_id.""" @@ -413,7 +449,9 @@ def _async_state_change_dispatcher(event: Event) -> None: _async_dispatch_domain_event(hass, event, domain_callbacks) hass.data[TRACK_STATE_ADDED_DOMAIN_LISTENER] = hass.bus.async_listen( - EVENT_STATE_CHANGED, _async_state_change_dispatcher + EVENT_STATE_CHANGED, + _async_state_change_dispatcher, + event_filter=_async_state_change_filter, ) job = HassJob(action) @@ -450,6 +488,11 @@ def async_track_state_removed_domain( if TRACK_STATE_REMOVED_DOMAIN_LISTENER not in hass.data: + @callback + def _async_state_change_filter(event: Event) -> bool: + """Filter state changes by entity_id.""" + return event.data.get("new_state") is None + @callback def _async_state_change_dispatcher(event: Event) -> None: """Dispatch state changes by entity_id.""" @@ -459,7 +502,9 @@ def _async_state_change_dispatcher(event: Event) -> None: _async_dispatch_domain_event(hass, event, domain_callbacks) hass.data[TRACK_STATE_REMOVED_DOMAIN_LISTENER] = hass.bus.async_listen( - EVENT_STATE_CHANGED, _async_state_change_dispatcher + EVENT_STATE_CHANGED, + _async_state_change_dispatcher, + event_filter=_async_state_change_filter, ) job = HassJob(action) diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 48e6d7d530210a..3f590362504953 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -62,7 +62,7 @@ async def fire_events(hass): """Fire a million events.""" count = 0 event_name = "benchmark_event" - event = asyncio.Event() + events_to_fire = 10 ** 6 @core.callback def listener(_): @@ -70,17 +70,48 @@ def listener(_): nonlocal count count += 1 - if count == 10 ** 6: - event.set() - hass.bus.async_listen(event_name, listener) - for _ in range(10 ** 6): + for _ in range(events_to_fire): hass.bus.async_fire(event_name) start = timer() - await event.wait() + await hass.async_block_till_done() + + assert count == events_to_fire + + return timer() - start + + +@benchmark +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 + + @core.callback + def event_filter(event): + """Filter event.""" + return False + + @core.callback + def listener(_): + """Handle event.""" + nonlocal count + count += 1 + + hass.bus.async_listen(event_name, listener, event_filter=event_filter) + + for _ in range(events_to_fire): + hass.bus.async_fire(event_name) + + start = timer() + + await hass.async_block_till_done() + + assert count == 0 return timer() - start @@ -154,7 +185,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" - event = asyncio.Event() + events_to_fire = 10 ** 6 @core.callback def listener(*args): @@ -162,9 +193,6 @@ def listener(*args): nonlocal count count += 1 - if count == 10 ** 6: - event.set() - hass.helpers.event.async_track_state_change_event( [f"{entity_id}{idx}" for idx in range(1000)], listener ) @@ -175,12 +203,49 @@ def listener(*args): "new_state": core.State(entity_id, "on"), } - for _ in range(10 ** 6): + for _ in range(events_to_fire): hass.bus.async_fire(EVENT_STATE_CHANGED, event_data) start = timer() - await event.wait() + await hass.async_block_till_done() + + assert count == events_to_fire + + return timer() - start + + +@benchmark +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 + + @core.callback + def listener(*args): + """Handle event.""" + nonlocal count + count += 1 + + hass.helpers.event.async_track_state_change_event( + [f"{entity_id}{idx}" for idx in range(1000)], listener + ) + + event_data = { + "entity_id": "switch.no_listeners", + "old_state": core.State(entity_id, "off"), + "new_state": core.State(entity_id, "on"), + } + + for _ in range(events_to_fire): + hass.bus.async_fire(EVENT_STATE_CHANGED, event_data) + + start = timer() + + await hass.async_block_till_done() + + assert count == 0 return timer() - start diff --git a/tests/components/mqtt/test_device_tracker_discovery.py b/tests/components/mqtt/test_device_tracker_discovery.py index 2c445ee0fa5189..f158a878fcdf90 100644 --- a/tests/components/mqtt/test_device_tracker_discovery.py +++ b/tests/components/mqtt/test_device_tracker_discovery.py @@ -194,6 +194,7 @@ async def test_cleanup_device_tracker(hass, device_reg, entity_reg, mqtt_mock): device_reg.async_remove_device(device_entry.id) 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")}) diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index c9b0879d490974..fed0dfa54d6f73 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -411,6 +411,7 @@ async def test_cleanup_device(hass, device_reg, entity_reg, mqtt_mock): device_reg.async_remove_device(device_entry.id) 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")}) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 39465a34aefbd9..e8081a831c2f91 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -353,6 +353,7 @@ async def test_remove_clients(hass, aioclient_mock): } controller.api.session_handler(SIGNAL_DATA) await hass.async_block_till_done() + await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 diff --git a/tests/test_core.py b/tests/test_core.py index dfd5b925e1c6a8..88b4e1d58f6c6b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -379,6 +379,35 @@ def listener(_): unsub() +async def test_eventbus_filtered_listener(hass): + """Test we can prefilter events.""" + calls = [] + + @ha.callback + def listener(event): + """Mock listener.""" + calls.append(event) + + @ha.callback + def filter(event): + """Mock filter.""" + return not event.data["filtered"] + + unsub = hass.bus.async_listen("test", listener, event_filter=filter) + + hass.bus.async_fire("test", {"filtered": True}) + await hass.async_block_till_done() + + assert len(calls) == 0 + + hass.bus.async_fire("test", {"filtered": False}) + await hass.async_block_till_done() + + assert len(calls) == 1 + + unsub() + + async def test_eventbus_unsubscribe_listener(hass): """Test unsubscribe listener from returned function.""" calls = [] From 1444afbe5a0c2d023f4995ae366938e338233ee7 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 15:47:08 -0500 Subject: [PATCH 0474/1818] Use core constants for vasttrafik (#46539) --- homeassistant/components/vasttrafik/sensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index 8b1609be6ba24a..882274f8e84ad5 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME +from homeassistant.const import ATTR_ATTRIBUTION, CONF_DELAY, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -20,7 +20,6 @@ ATTR_TRACK = "track" ATTRIBUTION = "Data provided by Västtrafik" -CONF_DELAY = "delay" CONF_DEPARTURES = "departures" CONF_FROM = "from" CONF_HEADING = "heading" @@ -55,7 +54,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the departure sensor.""" - planner = vasttrafik.JournyPlanner(config.get(CONF_KEY), config.get(CONF_SECRET)) sensors = [] From aa061e5818ff01203eb91cc957b8e2d1d71afe38 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Feb 2021 10:52:18 -1000 Subject: [PATCH 0475/1818] Fix variable name from script refactoring (#46503) --- homeassistant/helpers/script.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 3cc8348961f150..69ba082e57380f 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -44,6 +44,7 @@ CONF_REPEAT, CONF_SCENE, CONF_SEQUENCE, + CONF_SERVICE, CONF_TARGET, CONF_TIMEOUT, CONF_UNTIL, @@ -438,9 +439,9 @@ async def _async_call_service_step(self): ) running_script = ( - params["domain"] == "automation" - and params["service_name"] == "trigger" - or params["domain"] in ("python_script", "script") + params[CONF_DOMAIN] == "automation" + and params[CONF_SERVICE] == "trigger" + or params[CONF_DOMAIN] in ("python_script", "script") ) # If this might start a script then disable the call timeout. # Otherwise use the normal service call limit. From f1c792b4c800b38c2cd420b41ce3b82470cdf66a Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 16:21:55 -0500 Subject: [PATCH 0476/1818] Use core constants for uvc (#46538) --- homeassistant/components/uvc/camera.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index ae10c7db48f0b7..a20b99d673a0e7 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera -from homeassistant.const import CONF_PORT, CONF_SSL +from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_SSL from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv @@ -17,7 +17,6 @@ CONF_NVR = "nvr" CONF_KEY = "key" -CONF_PASSWORD = "password" DEFAULT_PASSWORD = "ubnt" DEFAULT_PORT = 7080 @@ -197,7 +196,6 @@ def _login(self): def camera_image(self): """Return the image of this camera.""" - if not self._camera: if not self._login(): return From a5f372018c61e9186e60187df5762ff9f856446b Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 17:01:18 -0500 Subject: [PATCH 0477/1818] Use core constants for volvooncall (#46543) --- homeassistant/components/volvooncall/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 7b7dffbef18ef5..792fcc25eff684 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -8,6 +8,7 @@ from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, + CONF_REGION, CONF_RESOURCES, CONF_SCAN_INTERVAL, CONF_USERNAME, @@ -32,7 +33,6 @@ MIN_UPDATE_INTERVAL = timedelta(minutes=1) DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1) -CONF_REGION = "region" CONF_SERVICE_URL = "service_url" CONF_SCANDINAVIAN_MILES = "scandinavian_miles" CONF_MUTABLE = "mutable" From 7e88487800bab311b2cd630d6941a45e08066623 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 17:01:53 -0500 Subject: [PATCH 0478/1818] Use core constants for wemo (#46544) --- homeassistant/components/wemo/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index 75ca322b9a3b12..0075b5dc8518c3 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -12,7 +12,7 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_DISCOVERY, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -57,7 +57,6 @@ def coerce_host_port(value): CONF_STATIC = "static" -CONF_DISCOVERY = "discovery" DEFAULT_DISCOVERY = True From 12abe5707d549cc07934271be29d381c0babdee1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Feb 2021 12:05:31 -1000 Subject: [PATCH 0479/1818] Fix typing on tuya fan percentage (#46541) --- homeassistant/components/tuya/fan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index b13b7c3602c87c..12e963f05d3065 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,5 +1,6 @@ """Support for Tuya fans.""" from datetime import timedelta +from typing import Optional from homeassistant.components.fan import ( DOMAIN as SENSOR_DOMAIN, @@ -116,7 +117,7 @@ def is_on(self): return self._tuya.state() @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed.""" if not self.is_on: return 0 From 0af634a9f8810fd87d7fe91cd778668f9983e17b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 15 Feb 2021 00:14:36 +0000 Subject: [PATCH 0480/1818] [ci skip] Translation update --- .../components/aemet/translations/ca.json | 22 +++++++ .../components/aemet/translations/cs.json | 19 ++++++ .../components/aemet/translations/et.json | 22 +++++++ .../components/aemet/translations/pl.json | 22 +++++++ .../components/aemet/translations/ru.json | 22 +++++++ .../aemet/translations/zh-Hant.json | 22 +++++++ .../components/airvisual/translations/cs.json | 12 ++++ .../components/asuswrt/translations/ca.json | 45 ++++++++++++++ .../components/asuswrt/translations/cs.json | 25 ++++++++ .../components/asuswrt/translations/et.json | 45 ++++++++++++++ .../components/asuswrt/translations/pl.json | 45 ++++++++++++++ .../components/asuswrt/translations/ru.json | 45 ++++++++++++++ .../asuswrt/translations/zh-Hant.json | 45 ++++++++++++++ .../components/econet/translations/cs.json | 21 +++++++ .../keenetic_ndms2/translations/ca.json | 36 +++++++++++ .../keenetic_ndms2/translations/en.json | 62 +++++++++---------- .../keenetic_ndms2/translations/et.json | 36 +++++++++++ .../components/mazda/translations/cs.json | 27 ++++++++ .../components/mysensors/translations/cs.json | 14 +++++ .../philips_js/translations/ca.json | 24 +++++++ .../philips_js/translations/cs.json | 4 ++ .../components/plaato/translations/cs.json | 1 + .../components/powerwall/translations/cs.json | 7 ++- .../components/roku/translations/cs.json | 1 + .../components/tesla/translations/cs.json | 4 ++ 25 files changed, 595 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/aemet/translations/ca.json create mode 100644 homeassistant/components/aemet/translations/cs.json create mode 100644 homeassistant/components/aemet/translations/et.json create mode 100644 homeassistant/components/aemet/translations/pl.json create mode 100644 homeassistant/components/aemet/translations/ru.json create mode 100644 homeassistant/components/aemet/translations/zh-Hant.json create mode 100644 homeassistant/components/asuswrt/translations/ca.json create mode 100644 homeassistant/components/asuswrt/translations/cs.json create mode 100644 homeassistant/components/asuswrt/translations/et.json create mode 100644 homeassistant/components/asuswrt/translations/pl.json create mode 100644 homeassistant/components/asuswrt/translations/ru.json create mode 100644 homeassistant/components/asuswrt/translations/zh-Hant.json create mode 100644 homeassistant/components/econet/translations/cs.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/ca.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/et.json create mode 100644 homeassistant/components/mazda/translations/cs.json create mode 100644 homeassistant/components/mysensors/translations/cs.json create mode 100644 homeassistant/components/philips_js/translations/ca.json diff --git a/homeassistant/components/aemet/translations/ca.json b/homeassistant/components/aemet/translations/ca.json new file mode 100644 index 00000000000000..85b22e72d76f9c --- /dev/null +++ b/homeassistant/components/aemet/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada" + }, + "error": { + "invalid_api_key": "Clau API inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nom de la integraci\u00f3" + }, + "description": "Configura la integraci\u00f3 d'AEMET OpenData. Per generar la clau API, v\u00e9s a https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/cs.json b/homeassistant/components/aemet/translations/cs.json new file mode 100644 index 00000000000000..d31920a87459f1 --- /dev/null +++ b/homeassistant/components/aemet/translations/cs.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Um\u00edst\u011bn\u00ed je ji\u017e nastaveno" + }, + "error": { + "invalid_api_key": "Neplatn\u00fd kl\u00ed\u010d API" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/et.json b/homeassistant/components/aemet/translations/et.json new file mode 100644 index 00000000000000..bc0a26179d56a9 --- /dev/null +++ b/homeassistant/components/aemet/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Asukoht on juba m\u00e4\u00e4ratud" + }, + "error": { + "invalid_api_key": "Vale API v\u00f5ti" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "name": "Sidumise nimi" + }, + "description": "Seadista AEMET OpenData sidumine. API v\u00f5tme loomiseks mine aadressile https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/pl.json b/homeassistant/components/aemet/translations/pl.json new file mode 100644 index 00000000000000..2c5c24fae2aa6e --- /dev/null +++ b/homeassistant/components/aemet/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Lokalizacja jest ju\u017c skonfigurowana" + }, + "error": { + "invalid_api_key": "Nieprawid\u0142owy klucz API" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "name": "Nazwa integracji" + }, + "description": "Skonfiguruj integracj\u0119 AEMET OpenData. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/ru.json b/homeassistant/components/aemet/translations/ru.json new file mode 100644 index 00000000000000..4da9a032d2b56b --- /dev/null +++ b/homeassistant/components/aemet/translations/ru.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "error": { + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AEMET OpenData. \u0427\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u044e\u0447 API, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 https://opendata.aemet.es/centrodedescargas/altaUsuario.", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/zh-Hant.json b/homeassistant/components/aemet/translations/zh-Hant.json new file mode 100644 index 00000000000000..75b251ae2ff5f8 --- /dev/null +++ b/homeassistant/components/aemet/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u5ea7\u6a19\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "name": "\u6574\u5408\u540d\u7a31" + }, + "description": "\u6b32\u8a2d\u5b9a AEMET OpenData \u6574\u5408\u3002\u8acb\u81f3 https://opendata.aemet.es/centrodedescargas/altaUsuario \u7522\u751f API \u5bc6\u9470", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/cs.json b/homeassistant/components/airvisual/translations/cs.json index 5c26dcf98efe21..0ff97bbacfeff5 100644 --- a/homeassistant/components/airvisual/translations/cs.json +++ b/homeassistant/components/airvisual/translations/cs.json @@ -17,6 +17,18 @@ "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" } }, + "geography_by_coords": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" + } + }, + "geography_by_name": { + "data": { + "api_key": "Kl\u00ed\u010d API" + } + }, "node_pro": { "data": { "ip_address": "Hostitel", diff --git a/homeassistant/components/asuswrt/translations/ca.json b/homeassistant/components/asuswrt/translations/ca.json new file mode 100644 index 00000000000000..2b15199a092b7e --- /dev/null +++ b/homeassistant/components/asuswrt/translations/ca.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_host": "Nom de l'amfitri\u00f3 o l'adre\u00e7a IP inv\u00e0lids", + "pwd_and_ssh": "Proporciona, nom\u00e9s, la contrasenya o el fitxer de claus SSH", + "pwd_or_ssh": "Proporciona la contrasenya o el fitxer de claus SSH", + "ssh_not_file": "No s'ha trobat el fitxer de claus SSH", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "mode": "Mode", + "name": "Nom", + "password": "Contrasenya", + "port": "Port", + "protocol": "Protocol de comunicacions a utilitzar", + "ssh_key": "Ruta al fitxer de claus SSH (en lloc de la contrasenya)", + "username": "Nom d'usuari" + }, + "description": "Introdueix el par\u00e0metre necessari per connectar-te al router", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Segons d'espera abans de considerar un dispositiu a fora", + "dnsmasq": "La ubicaci\u00f3 dins el router dels fitxers dnsmasq.leases", + "interface": "La interf\u00edcie de la qual obtenir les estad\u00edstiques (per exemple, eth0, eth1, etc.)", + "require_ip": "Els dispositius han de tenir una IP (per al mode de punt d'acc\u00e9s)", + "track_unknown": "Segueix dispositius desconeguts/sense nom" + }, + "title": "Opcions d'AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/cs.json b/homeassistant/components/asuswrt/translations/cs.json new file mode 100644 index 00000000000000..d9766e9a6d0b1e --- /dev/null +++ b/homeassistant/components/asuswrt/translations/cs.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace." + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_host": "Neplatn\u00fd hostitel nebo IP adresa", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "mode": "Re\u017eim", + "name": "Jm\u00e9no", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + }, + "title": "AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/et.json b/homeassistant/components/asuswrt/translations/et.json new file mode 100644 index 00000000000000..8cc14b7353b66b --- /dev/null +++ b/homeassistant/components/asuswrt/translations/et.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_host": "Sobimatu hostinimi v\u00f5i IP-aadress", + "pwd_and_ssh": "Sisesta ainult parooli v\u00f5i SSH v\u00f5tmefail", + "pwd_or_ssh": "Sisesta parool v\u00f5i SSH v\u00f5tmefail", + "ssh_not_file": "SSH v\u00f5tmefaili ei leitud", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Re\u017eiim", + "name": "Nimi", + "password": "Salas\u00f5na", + "port": "Port", + "protocol": "Kasutatav sideprotokoll", + "ssh_key": "Rada SSH v\u00f5tmefailini (parooli asemel)", + "username": "Kasutajanimi" + }, + "description": "M\u00e4\u00e4ra ruuteriga \u00fchenduse loomiseks vajalik parameeter", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Mitu sekundit oodata, enne kui lugeda seade eemal olevaks", + "dnsmasq": "Dnsmasq.leases failide asukoht ruuteris", + "interface": "Liides kust soovite statistikat (n\u00e4iteks eth0, eth1 jne.)", + "require_ip": "Seadmetel peab olema IP (p\u00e4\u00e4supunkti re\u017eiimi jaoks)", + "track_unknown": "J\u00e4lgi tundmatuid / nimetamata seadmeid" + }, + "title": "AsusWRT valikud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/pl.json b/homeassistant/components/asuswrt/translations/pl.json new file mode 100644 index 00000000000000..b646c8e45038f5 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/pl.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_host": "Nieprawid\u0142owa nazwa hosta lub adres IP", + "pwd_and_ssh": "Podaj tylko has\u0142o lub plik z kluczem SSH", + "pwd_or_ssh": "Podaj has\u0142o lub plik z kluczem SSH", + "ssh_not_file": "Nie znaleziono pliku z kluczem SSH", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "mode": "Tryb", + "name": "Nazwa", + "password": "Has\u0142o", + "port": "Port", + "protocol": "Wybierz protok\u00f3\u0142 komunikacyjny", + "ssh_key": "\u015acie\u017cka do pliku z kluczem SSH (zamiast has\u0142a)", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Ustaw wymagany parametr, aby po\u0142\u0105czy\u0107 si\u0119 z routerem", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Czas w sekundach, zanim urz\u0105dzenie zostanie uznane za \"nieobecne\"", + "dnsmasq": "Lokalizacja w routerze plik\u00f3w dnsmasq.leases", + "interface": "Interfejs, z kt\u00f3rego chcesz uzyska\u0107 statystyki (np. Eth0, eth1 itp.)", + "require_ip": "Urz\u0105dzenia musz\u0105 mie\u0107 adres IP (w trybie punktu dost\u0119pu)", + "track_unknown": "\u015aled\u017a nieznane / nienazwane urz\u0105dzenia" + }, + "title": "Opcje AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/ru.json b/homeassistant/components/asuswrt/translations/ru.json new file mode 100644 index 00000000000000..236f7642c12a64 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/ru.json @@ -0,0 +1,45 @@ +{ + "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." + }, + "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_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441.", + "pwd_and_ssh": "\u041d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0430\u0440\u043e\u043b\u044c \u0438\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u0439\u043b \u043a\u043b\u044e\u0447\u0430 SSH.", + "pwd_or_ssh": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c \u0438\u043b\u0438 \u0444\u0430\u0439\u043b \u043a\u043b\u044e\u0447\u0430 SSH.", + "ssh_not_file": "\u0424\u0430\u0439\u043b \u043a\u043b\u044e\u0447\u0430 SSH \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d.", + "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", + "mode": "\u0420\u0435\u0436\u0438\u043c", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441\u0432\u044f\u0437\u0438", + "ssh_key": "\u041f\u0443\u0442\u044c \u0444\u0430\u0439\u043b\u0443 \u043a\u043b\u044e\u0447\u0435\u0439 SSH (\u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0430\u0440\u043e\u043b\u044f)", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0440\u043e\u0443\u0442\u0435\u0440\u0443.", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "\u0412\u0440\u0435\u043c\u044f \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445), \u043f\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0434\u043e\u043c\u0430\"", + "dnsmasq": "\u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 dnsmasq.leases", + "interface": "\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, eth0, eth1 \u0438 \u0442. \u0434.)", + "require_ip": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0441 IP-\u0430\u0434\u0440\u0435\u0441\u043e\u043c (\u0434\u043b\u044f \u0440\u0435\u0436\u0438\u043c\u0430 \u0442\u043e\u0447\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430)", + "track_unknown": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435/\u0431\u0435\u0437\u044b\u043c\u044f\u043d\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/zh-Hant.json b/homeassistant/components/asuswrt/translations/zh-Hant.json new file mode 100644 index 00000000000000..8caddacd23e159 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/zh-Hant.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_host": "\u7121\u6548\u4e3b\u6a5f\u540d\u7a31\u6216 IP \u4f4d\u5740", + "pwd_and_ssh": "\u50c5\u63d0\u4f9b\u5bc6\u78bc\u6216 SSH \u5bc6\u9470\u6a94\u6848", + "pwd_or_ssh": "\u8acb\u8f38\u5165\u5bc6\u78bc\u6216 SSH \u5bc6\u9470\u6a94\u6848", + "ssh_not_file": "\u627e\u4e0d\u5230 SSH \u5bc6\u9470\u6a94\u6848", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "mode": "\u6a21\u5f0f", + "name": "\u540d\u7a31", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "protocol": "\u4f7f\u7528\u901a\u8a0a\u606f\u5354\u5b9a", + "ssh_key": "SSH \u5bc6\u9470\u6a94\u6848\u8def\u5f91\uff08\u975e\u5bc6\u78bc\uff09", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8a2d\u5b9a\u6240\u9700\u53c3\u6578\u4ee5\u9023\u7dda\u81f3\u8def\u7531\u5668", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "\u8996\u70ba\u96e2\u958b\u7684\u7b49\u5019\u79d2\u6578", + "dnsmasq": "dnsmasq.leases \u6a94\u6848\u65bc\u8def\u7531\u5668\u4e2d\u6240\u5728\u4f4d\u7f6e", + "interface": "\u6240\u8981\u9032\u884c\u7d71\u8a08\u7684\u4ecb\u9762\u53e3\uff08\u4f8b\u5982 eth0\u3001eth1 \u7b49\uff09", + "require_ip": "\u88dd\u7f6e\u5fc5\u9808\u5177\u6709 IP\uff08\u7528\u65bc AP \u6a21\u5f0f\uff09", + "track_unknown": "\u8ffd\u8e64\u672a\u77e5 / \u672a\u547d\u540d\u88dd\u7f6e" + }, + "title": "AsusWRT \u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/cs.json b/homeassistant/components/econet/translations/cs.json new file mode 100644 index 00000000000000..bd8b079962851c --- /dev/null +++ b/homeassistant/components/econet/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Heslo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ca.json b/homeassistant/components/keenetic_ndms2/translations/ca.json new file mode 100644 index 00000000000000..f15b11b3eb42fc --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/ca.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "name": "Nom", + "password": "Contrasenya", + "port": "Port", + "username": "Nom d'usuari" + }, + "title": "Configuraci\u00f3 del router Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Interval per considerar a casa", + "include_arp": "Utilitza dades d'ARP (s'ignorar\u00e0 si s'utilitzen dades de 'punt d'acc\u00e9s')", + "include_associated": "Utilitza dades d'associacions d'AP WiFi (s'ignorar\u00e0 si s'utilitzen dades de 'punt d'acc\u00e9s')", + "interfaces": "Escull les interf\u00edcies a escanejar", + "scan_interval": "Interval d'escaneig", + "try_hotspot": "Utilitza dades de 'punt d'acc\u00e9s IP' (m\u00e9s precisi\u00f3)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/en.json b/homeassistant/components/keenetic_ndms2/translations/en.json index 1849d68c651f63..e95f2f740efd6c 100644 --- a/homeassistant/components/keenetic_ndms2/translations/en.json +++ b/homeassistant/components/keenetic_ndms2/translations/en.json @@ -1,36 +1,36 @@ { - "config": { - "step": { - "user": { - "title": "Set up Keenetic NDMS2 Router", - "data": { - "name": "Name", - "host": "Host", - "username": "Username", - "password": "Password", - "port": "Port" + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "password": "Password", + "port": "Port", + "username": "Username" + }, + "title": "Set up Keenetic NDMS2 Router" + } } - } }, - "error": { - "cannot_connect": "Connection Unsuccessful" - }, - "abort": { - "already_configured": "This router is already configured" - } - }, - "options": { - "step": { - "user": { - "data": { - "scan_interval": "Scan interval", - "consider_home": "Consider home interval", - "interfaces": "Choose interfaces to scan", - "try_hotspot": "Use 'ip hotspot' data (most accurate)", - "include_arp": "Use ARP data (if hotspot disabled/unavailable)", - "include_associated": "Use WiFi AP associations data (if hotspot disabled/unavailable)" + "options": { + "step": { + "user": { + "data": { + "consider_home": "Consider home interval", + "include_arp": "Use ARP data (ignored if hotspot data used)", + "include_associated": "Use WiFi AP associations data (ignored if hotspot data used)", + "interfaces": "Choose interfaces to scan", + "scan_interval": "Scan interval", + "try_hotspot": "Use 'ip hotspot' data (most accurate)" + } + } } - } } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/et.json b/homeassistant/components/keenetic_ndms2/translations/et.json new file mode 100644 index 00000000000000..dc500be7e1a115 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/et.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nimi", + "password": "Salas\u00f5na", + "port": "Port", + "username": "Kasutajanimi" + }, + "title": "Seadista Keenetic NDMS2 ruuter" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "M\u00e4\u00e4ra n\u00e4htavuse aeg", + "include_arp": "Kasuta ARP andmeid (ignoreeritakse, kui kasutatakse kuumkoha andmeid)", + "include_associated": "Kasuta WiFi AP seoste andmeid (ignoreeritakse kui kasutatakse kuumkoha andmeid)", + "interfaces": "Sk\u00e4nnitavate liideste valimine", + "scan_interval": "P\u00e4ringute intervall", + "try_hotspot": "Kasuta 'ip hotspot' andmeid (k\u00f5ige t\u00e4psem)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/cs.json b/homeassistant/components/mazda/translations/cs.json new file mode 100644 index 00000000000000..8a929fb58d7de5 --- /dev/null +++ b/homeassistant/components/mazda/translations/cs.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "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", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "reauth": { + "data": { + "email": "E-mail", + "password": "Heslo" + } + }, + "user": { + "data": { + "email": "E-mail", + "password": "Heslo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/cs.json b/homeassistant/components/mysensors/translations/cs.json new file mode 100644 index 00000000000000..9d3cfbd25089ea --- /dev/null +++ b/homeassistant/components/mysensors/translations/cs.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "error": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json new file mode 100644 index 00000000000000..505a6472ea823a --- /dev/null +++ b/homeassistant/components/philips_js/translations/ca.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_version": "Versi\u00f3 de l'API", + "host": "Amfitri\u00f3" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Es demani que el dispositiu s'engegui" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/cs.json b/homeassistant/components/philips_js/translations/cs.json index 8a5866b5959ebc..a39944a8dbacae 100644 --- a/homeassistant/components/philips_js/translations/cs.json +++ b/homeassistant/components/philips_js/translations/cs.json @@ -1,6 +1,10 @@ { "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": { diff --git a/homeassistant/components/plaato/translations/cs.json b/homeassistant/components/plaato/translations/cs.json index 582a3e3a18086b..4d736d1c695a4a 100644 --- a/homeassistant/components/plaato/translations/cs.json +++ b/homeassistant/components/plaato/translations/cs.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven", "single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace.", "webhook_not_internet_accessible": "V\u00e1\u0161 Home Assistant mus\u00ed b\u00fdt p\u0159\u00edstupn\u00fd z internetu, aby mohl p\u0159ij\u00edmat zpr\u00e1vy webhook." }, diff --git a/homeassistant/components/powerwall/translations/cs.json b/homeassistant/components/powerwall/translations/cs.json index 698934ad10e615..d6e5cd5904b004 100644 --- a/homeassistant/components/powerwall/translations/cs.json +++ b/homeassistant/components/powerwall/translations/cs.json @@ -1,10 +1,12 @@ { "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", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "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." }, @@ -12,7 +14,8 @@ "step": { "user": { "data": { - "ip_address": "IP adresa" + "ip_address": "IP adresa", + "password": "Heslo" }, "title": "P\u0159ipojen\u00ed k powerwall" } diff --git a/homeassistant/components/roku/translations/cs.json b/homeassistant/components/roku/translations/cs.json index 89ca523af470dd..6914a519285fe4 100644 --- a/homeassistant/components/roku/translations/cs.json +++ b/homeassistant/components/roku/translations/cs.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "already_in_progress": "Konfigurace ji\u017e prob\u00edh\u00e1", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "error": { diff --git a/homeassistant/components/tesla/translations/cs.json b/homeassistant/components/tesla/translations/cs.json index c611b85d7348ad..9c117223d40896 100644 --- a/homeassistant/components/tesla/translations/cs.json +++ b/homeassistant/components/tesla/translations/cs.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "reauth_successful": "Op\u011btovn\u00e9 ov\u011b\u0159en\u00ed bylo \u00fasp\u011b\u0161n\u00e9" + }, "error": { "already_configured": "\u00da\u010det je ji\u017e nastaven", "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", From a3b733f1ec99c9802a4564edca7ce85ebf13dcd0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 14 Feb 2021 19:35:14 -0500 Subject: [PATCH 0481/1818] Add additional supported feature support to universal media player (#44711) * add additional supported feature support to universal media player * add missing services --- .../components/universal/media_player.py | 74 ++++++++++- .../components/universal/test_media_player.py | 116 +++++++++++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 8fff0e80dfb872..2a5fcee34dc332 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -20,6 +20,7 @@ ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT, + ATTR_MEDIA_REPEAT, ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SERIES_TITLE, @@ -28,13 +29,23 @@ ATTR_MEDIA_TRACK, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, + ATTR_SOUND_MODE, + ATTR_SOUND_MODE_LIST, DOMAIN, SERVICE_CLEAR_PLAYLIST, SERVICE_PLAY_MEDIA, + SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_REPEAT_SET, + SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, @@ -55,7 +66,9 @@ SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP, + SERVICE_REPEAT_SET, SERVICE_SHUFFLE_SET, + SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN, @@ -382,6 +395,16 @@ def app_name(self): """Name of the current running app.""" return self._child_attr(ATTR_APP_NAME) + @property + def sound_mode(self): + """Return the current sound mode of the device.""" + return self._override_or_child_attr(ATTR_SOUND_MODE) + + @property + def sound_mode_list(self): + """List of available sound modes.""" + return self._override_or_child_attr(ATTR_SOUND_MODE_LIST) + @property def source(self): """Return the current input source of the device.""" @@ -392,6 +415,11 @@ def source_list(self): """List of available input sources.""" return self._override_or_child_attr(ATTR_INPUT_SOURCE_LIST) + @property + def repeat(self): + """Boolean if repeating is enabled.""" + return self._override_or_child_attr(ATTR_MEDIA_REPEAT) + @property def shuffle(self): """Boolean if shuffling is enabled.""" @@ -407,6 +435,22 @@ def supported_features(self): if SERVICE_TURN_OFF in self._cmds: flags |= SUPPORT_TURN_OFF + if SERVICE_MEDIA_PLAY_PAUSE in self._cmds: + flags |= SUPPORT_PLAY | SUPPORT_PAUSE + else: + if SERVICE_MEDIA_PLAY in self._cmds: + flags |= SUPPORT_PLAY + if SERVICE_MEDIA_PAUSE in self._cmds: + flags |= SUPPORT_PAUSE + + if SERVICE_MEDIA_STOP in self._cmds: + flags |= SUPPORT_STOP + + if SERVICE_MEDIA_NEXT_TRACK in self._cmds: + flags |= SUPPORT_NEXT_TRACK + if SERVICE_MEDIA_PREVIOUS_TRACK in self._cmds: + flags |= SUPPORT_PREVIOUS_TRACK + if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]]): flags |= SUPPORT_VOLUME_STEP if SERVICE_VOLUME_SET in self._cmds: @@ -415,7 +459,10 @@ def supported_features(self): if SERVICE_VOLUME_MUTE in self._cmds and ATTR_MEDIA_VOLUME_MUTED in self._attrs: flags |= SUPPORT_VOLUME_MUTE - if SERVICE_SELECT_SOURCE in self._cmds: + if ( + SERVICE_SELECT_SOURCE in self._cmds + and ATTR_INPUT_SOURCE_LIST in self._attrs + ): flags |= SUPPORT_SELECT_SOURCE if SERVICE_CLEAR_PLAYLIST in self._cmds: @@ -424,6 +471,15 @@ def supported_features(self): if SERVICE_SHUFFLE_SET in self._cmds and ATTR_MEDIA_SHUFFLE in self._attrs: flags |= SUPPORT_SHUFFLE_SET + if SERVICE_REPEAT_SET in self._cmds and ATTR_MEDIA_REPEAT in self._attrs: + flags |= SUPPORT_REPEAT_SET + + if ( + SERVICE_SELECT_SOUND_MODE in self._cmds + and ATTR_SOUND_MODE_LIST in self._attrs + ): + flags |= SUPPORT_SELECT_SOUND_MODE + return flags @property @@ -502,6 +558,13 @@ async def async_media_play_pause(self): """Play or pause the media player.""" await self._async_call_service(SERVICE_MEDIA_PLAY_PAUSE) + async def async_select_sound_mode(self, sound_mode): + """Select sound mode.""" + data = {ATTR_SOUND_MODE: sound_mode} + await self._async_call_service( + SERVICE_SELECT_SOUND_MODE, data, allow_override=True + ) + async def async_select_source(self, source): """Set the input source.""" data = {ATTR_INPUT_SOURCE: source} @@ -516,6 +579,15 @@ async def async_set_shuffle(self, shuffle): data = {ATTR_MEDIA_SHUFFLE: shuffle} await self._async_call_service(SERVICE_SHUFFLE_SET, data, allow_override=True) + async def async_set_repeat(self, repeat): + """Set repeat mode.""" + data = {ATTR_MEDIA_REPEAT: repeat} + await self._async_call_service(SERVICE_REPEAT_SET, data, allow_override=True) + + async def async_toggle(self): + """Toggle the power on the media player.""" + await self._async_call_service(SERVICE_TOGGLE) + async def async_update(self): """Update state in HA.""" for child_name in self._children: diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index fd75620f3188b9..75cf029af40d89 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -51,6 +51,7 @@ def __init__(self, hass, name): self._tracks = 12 self._media_image_url = None self._shuffle = False + self._sound_mode = None self.service_calls = { "turn_on": mock_service( @@ -71,6 +72,9 @@ def __init__(self, hass, name): "media_pause": mock_service( hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE ), + "media_stop": mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_STOP + ), "media_previous_track": mock_service( hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PREVIOUS_TRACK ), @@ -92,12 +96,21 @@ def __init__(self, hass, name): "media_play_pause": mock_service( hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY_PAUSE ), + "select_sound_mode": mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOUND_MODE + ), "select_source": mock_service( hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE ), + "toggle": mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_TOGGLE + ), "clear_playlist": mock_service( hass, media_player.DOMAIN, media_player.SERVICE_CLEAR_PLAYLIST ), + "repeat_set": mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_REPEAT_SET + ), "shuffle_set": mock_service( hass, media_player.DOMAIN, media_player.SERVICE_SHUFFLE_SET ), @@ -162,18 +175,30 @@ def media_pause(self): """Mock pause.""" self._state = STATE_PAUSED + def select_sound_mode(self, sound_mode): + """Set the sound mode.""" + self._sound_mode = sound_mode + def select_source(self, source): """Set the input source.""" self._source = source + def async_toggle(self): + """Toggle the power on the media player.""" + self._state = STATE_OFF if self._state == STATE_ON else STATE_ON + def clear_playlist(self): """Clear players playlist.""" self._tracks = 0 def set_shuffle(self, shuffle): - """Clear players playlist.""" + """Enable/disable shuffle mode.""" self._shuffle = shuffle + def set_repeat(self, repeat): + """Enable/disable repeat mode.""" + self._repeat = repeat + class TestMediaPlayer(unittest.TestCase): """Test the media_player module.""" @@ -205,9 +230,18 @@ def setUp(self): # pylint: disable=invalid-name self.mock_source_id = f"{input_select.DOMAIN}.source" self.hass.states.set(self.mock_source_id, "dvd") + self.mock_sound_mode_list_id = f"{input_select.DOMAIN}.sound_mode_list" + self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie"]) + + self.mock_sound_mode_id = f"{input_select.DOMAIN}.sound_mode" + self.hass.states.set(self.mock_sound_mode_id, "music") + self.mock_shuffle_switch_id = switch.ENTITY_ID_FORMAT.format("shuffle") self.hass.states.set(self.mock_shuffle_switch_id, STATE_OFF) + self.mock_repeat_switch_id = switch.ENTITY_ID_FORMAT.format("repeat") + self.hass.states.set(self.mock_repeat_switch_id, STATE_OFF) + self.config_children_only = { "name": "test", "platform": "universal", @@ -230,6 +264,9 @@ def setUp(self): # pylint: disable=invalid-name "source_list": self.mock_source_list_id, "state": self.mock_state_switch_id, "shuffle": self.mock_shuffle_switch_id, + "repeat": self.mock_repeat_switch_id, + "sound_mode_list": self.mock_sound_mode_list_id, + "sound_mode": self.mock_sound_mode_id, }, } self.addCleanup(self.tear_down_cleanup) @@ -507,6 +544,17 @@ def test_is_volume_muted_children_only(self): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() assert ump.is_volume_muted + def test_sound_mode_list_children_and_attr(self): + """Test sound mode list property w/ children and attrs.""" + config = validate_config(self.config_children_and_attr) + + ump = universal.UniversalMediaPlayer(self.hass, **config) + + assert "['music', 'movie']" == ump.sound_mode_list + + self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie", "game"]) + assert "['music', 'movie', 'game']" == ump.sound_mode_list + def test_source_list_children_and_attr(self): """Test source list property w/ children and attrs.""" config = validate_config(self.config_children_and_attr) @@ -518,6 +566,17 @@ def test_source_list_children_and_attr(self): self.hass.states.set(self.mock_source_list_id, ["dvd", "htpc", "game"]) assert "['dvd', 'htpc', 'game']" == ump.source_list + def test_sound_mode_children_and_attr(self): + """Test sound modeproperty w/ children and attrs.""" + config = validate_config(self.config_children_and_attr) + + ump = universal.UniversalMediaPlayer(self.hass, **config) + + assert "music" == ump.sound_mode + + self.hass.states.set(self.mock_sound_mode_id, "movie") + assert "movie" == ump.sound_mode + def test_source_children_and_attr(self): """Test source property w/ children and attrs.""" config = validate_config(self.config_children_and_attr) @@ -579,8 +638,17 @@ def test_supported_features_children_and_cmds(self): "volume_down": excmd, "volume_mute": excmd, "volume_set": excmd, + "select_sound_mode": excmd, "select_source": excmd, + "repeat_set": excmd, "shuffle_set": excmd, + "media_play": excmd, + "media_pause": excmd, + "media_stop": excmd, + "media_next_track": excmd, + "media_previous_track": excmd, + "toggle": excmd, + "clear_playlist": excmd, } config = validate_config(config) @@ -598,13 +666,41 @@ def test_supported_features_children_and_cmds(self): | universal.SUPPORT_TURN_OFF | universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE + | universal.SUPPORT_SELECT_SOUND_MODE | universal.SUPPORT_SELECT_SOURCE + | universal.SUPPORT_REPEAT_SET | universal.SUPPORT_SHUFFLE_SET | universal.SUPPORT_VOLUME_SET + | universal.SUPPORT_PLAY + | universal.SUPPORT_PAUSE + | universal.SUPPORT_STOP + | universal.SUPPORT_NEXT_TRACK + | universal.SUPPORT_PREVIOUS_TRACK + | universal.SUPPORT_CLEAR_PLAYLIST ) assert check_flags == ump.supported_features + def test_supported_features_play_pause(self): + """Test supported media commands with play_pause function.""" + config = copy(self.config_children_and_attr) + excmd = {"service": "media_player.test", "data": {"entity_id": "test"}} + config["commands"] = {"media_play_pause": excmd} + config = validate_config(config) + + ump = universal.UniversalMediaPlayer(self.hass, **config) + ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) + asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() + + self.mock_mp_1._state = STATE_PLAYING + self.mock_mp_1.schedule_update_ha_state() + self.hass.block_till_done() + asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() + + check_flags = universal.SUPPORT_PLAY | universal.SUPPORT_PAUSE + + assert check_flags == ump.supported_features + def test_service_call_no_active_child(self): """Test a service call to children with no active child.""" config = validate_config(self.config_children_and_attr) @@ -663,6 +759,11 @@ def test_service_call_to_child(self): ).result() assert 1 == len(self.mock_mp_2.service_calls["media_pause"]) + asyncio.run_coroutine_threadsafe( + ump.async_media_stop(), self.hass.loop + ).result() + assert 1 == len(self.mock_mp_2.service_calls["media_stop"]) + asyncio.run_coroutine_threadsafe( ump.async_media_previous_track(), self.hass.loop ).result() @@ -696,6 +797,11 @@ def test_service_call_to_child(self): ).result() assert 1 == len(self.mock_mp_2.service_calls["media_play_pause"]) + asyncio.run_coroutine_threadsafe( + ump.async_select_sound_mode("music"), self.hass.loop + ).result() + assert 1 == len(self.mock_mp_2.service_calls["select_sound_mode"]) + asyncio.run_coroutine_threadsafe( ump.async_select_source("dvd"), self.hass.loop ).result() @@ -706,11 +812,19 @@ def test_service_call_to_child(self): ).result() assert 1 == len(self.mock_mp_2.service_calls["clear_playlist"]) + asyncio.run_coroutine_threadsafe( + ump.async_set_repeat(True), self.hass.loop + ).result() + assert 1 == len(self.mock_mp_2.service_calls["repeat_set"]) + asyncio.run_coroutine_threadsafe( ump.async_set_shuffle(True), self.hass.loop ).result() assert 1 == len(self.mock_mp_2.service_calls["shuffle_set"]) + asyncio.run_coroutine_threadsafe(ump.async_toggle(), self.hass.loop).result() + assert 1 == len(self.mock_mp_2.service_calls["toggle"]) + def test_service_call_to_command(self): """Test service call to command.""" config = copy(self.config_children_only) From 06c8fc6ef1f4895b23b3b83f8ea9d58cbae6b4d7 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 14 Feb 2021 20:14:48 -0500 Subject: [PATCH 0482/1818] Use core constants for wemo and whois (#46548) --- homeassistant/components/whois/sensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 7ec5c3dac5e436..0e3c0c6e0da365 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -6,14 +6,12 @@ import whois from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, TIME_DAYS +from homeassistant.const import CONF_DOMAIN, CONF_NAME, TIME_DAYS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_DOMAIN = "domain" - DEFAULT_NAME = "Whois" ATTR_EXPIRES = "expires" From cfdaadf5d962f740e409b93b541c6e01fff331ea Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 14 Feb 2021 22:05:23 -0500 Subject: [PATCH 0483/1818] Allow users to set device class for universal media player (#46550) --- .../components/universal/media_player.py | 27 +++++++++++++++++-- .../components/universal/test_media_player.py | 22 +++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 2a5fcee34dc332..e4891dca68af7f 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -1,9 +1,14 @@ """Combination of multiple media players for a universal controller.""" from copy import copy +from typing import Optional import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity +from homeassistant.components.media_player import ( + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + MediaPlayerEntity, +) from homeassistant.components.media_player.const import ( ATTR_APP_ID, ATTR_APP_NAME, @@ -56,6 +61,7 @@ ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, + CONF_DEVICE_CLASS, CONF_NAME, CONF_STATE, CONF_STATE_TEMPLATE, @@ -109,6 +115,7 @@ vol.Optional(CONF_ATTRS, default={}): vol.Or( cv.ensure_list(ATTRS_SCHEMA), ATTRS_SCHEMA ), + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_STATE_TEMPLATE): cv.template, }, extra=vol.REMOVE_EXTRA, @@ -126,6 +133,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= config.get(CONF_CHILDREN), config.get(CONF_COMMANDS), config.get(CONF_ATTRS), + config.get(CONF_DEVICE_CLASS), config.get(CONF_STATE_TEMPLATE), ) @@ -135,7 +143,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class UniversalMediaPlayer(MediaPlayerEntity): """Representation of an universal media player.""" - def __init__(self, hass, name, children, commands, attributes, state_template=None): + def __init__( + self, + hass, + name, + children, + commands, + attributes, + device_class=None, + state_template=None, + ): """Initialize the Universal media device.""" self.hass = hass self._name = name @@ -150,6 +167,7 @@ def __init__(self, hass, name, children, commands, attributes, state_template=No self._child_state = None self._state_template_result = None self._state_template = state_template + self._device_class = device_class async def async_added_to_hass(self): """Subscribe to children and template state changes.""" @@ -255,6 +273,11 @@ def should_poll(self): """No polling needed.""" return False + @property + def device_class(self) -> Optional[str]: + """Return the class of this device.""" + return self._device_class + @property def master_state(self): """Return the master state for entity or None.""" diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 75cf029af40d89..8d8bc80234e2c8 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -872,6 +872,25 @@ async def test_state_template(hass): assert hass.states.get("media_player.tv").state == STATE_OFF +async def test_device_class(hass): + """Test device_class property.""" + hass.states.async_set("sensor.test_sensor", "on") + + await async_setup_component( + hass, + "media_player", + { + "media_player": { + "platform": "universal", + "name": "tv", + "device_class": "tv", + } + }, + ) + await hass.async_block_till_done() + assert hass.states.get("media_player.tv").attributes["device_class"] == "tv" + + async def test_invalid_state_template(hass): """Test invalid state template sets state to None.""" hass.states.async_set("sensor.test_sensor", "on") @@ -1001,6 +1020,9 @@ async def test_reload(hass): assert hass.states.get("media_player.tv") is None assert hass.states.get("media_player.master_bed_tv").state == "on" assert hass.states.get("media_player.master_bed_tv").attributes["source"] == "act2" + assert ( + "device_class" not in hass.states.get("media_player.master_bed_tv").attributes + ) def _get_fixtures_base_path(): From 3f4828f5e1d1146cd7baeb783ea1ddd5f7e097d6 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 14 Feb 2021 20:32:37 -0800 Subject: [PATCH 0484/1818] Add additional stream HLS payload tests (#46517) * Add tests for HLS playlist view details * Add tests for hls playlist payloads * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Update tests/components/stream/test_hls.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> --- tests/components/stream/test_hls.py | 160 +++++++++++++++++++++++++--- 1 file changed, 147 insertions(+), 13 deletions(-) diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index b575b3877fa539..7811cac2a2af75 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -1,11 +1,15 @@ """The tests for hls streams.""" from datetime import timedelta +import io from unittest.mock import patch from urllib.parse import urlparse import av +import pytest from homeassistant.components.stream import create_stream +from homeassistant.components.stream.const import MAX_SEGMENTS, NUM_PLAYLIST_SEGMENTS +from homeassistant.components.stream.core import Segment from homeassistant.const import HTTP_NOT_FOUND from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -13,8 +17,61 @@ from tests.common import async_fire_time_changed from tests.components.stream.common import generate_h264_video - -async def test_hls_stream(hass, hass_client, stream_worker_sync): +STREAM_SOURCE = "some-stream-source" +SEQUENCE_BYTES = io.BytesIO(b"some-bytes") +DURATION = 10 + + +class HlsClient: + """Test fixture for fetching the hls stream.""" + + def __init__(self, http_client, parsed_url): + """Initialize HlsClient.""" + self.http_client = http_client + self.parsed_url = parsed_url + + async def get(self, path=None): + """Fetch the hls stream for the specified path.""" + url = self.parsed_url.path + if path: + # Strip off the master playlist suffix and replace with path + url = "/".join(self.parsed_url.path.split("/")[:-1]) + path + return await self.http_client.get(url) + + +@pytest.fixture +def hls_stream(hass, hass_client): + """Create test fixture for creating an HLS client for a stream.""" + + async def create_client_for_stream(stream): + http_client = await hass_client() + parsed_url = urlparse(stream.endpoint_url("hls")) + return HlsClient(http_client, parsed_url) + + return create_client_for_stream + + +def playlist_response(sequence, segments): + """Create a an hls playlist response for tests to assert on.""" + response = [ + "#EXTM3U", + "#EXT-X-VERSION:7", + "#EXT-X-TARGETDURATION:10", + '#EXT-X-MAP:URI="init.mp4"', + f"#EXT-X-MEDIA-SEQUENCE:{sequence}", + ] + for segment in segments: + response.extend( + [ + "#EXTINF:10.0000,", + f"./segment/{segment}.m4s", + ] + ) + response.append("") + return "\n".join(response) + + +async def test_hls_stream(hass, hls_stream, stream_worker_sync): """ Test hls stream. @@ -32,27 +89,22 @@ async def test_hls_stream(hass, hass_client, stream_worker_sync): # Request stream stream.add_provider("hls") stream.start() - url = stream.endpoint_url("hls") - http_client = await hass_client() + hls_client = await hls_stream(stream) # Fetch playlist - parsed_url = urlparse(url) - playlist_response = await http_client.get(parsed_url.path) + playlist_response = await hls_client.get() assert playlist_response.status == 200 # Fetch init playlist = await playlist_response.text() - playlist_url = "/".join(parsed_url.path.split("/")[:-1]) - init_url = playlist_url + "/init.mp4" - init_response = await http_client.get(init_url) + init_response = await hls_client.get("/init.mp4") assert init_response.status == 200 # Fetch segment playlist = await playlist_response.text() - playlist_url = "/".join(parsed_url.path.split("/")[:-1]) - segment_url = playlist_url + "/" + playlist.splitlines()[-1] - segment_response = await http_client.get(segment_url) + segment_url = "/" + playlist.splitlines()[-1] + segment_response = await hls_client.get(segment_url) assert segment_response.status == 200 stream_worker_sync.resume() @@ -61,7 +113,7 @@ async def test_hls_stream(hass, hass_client, stream_worker_sync): stream.stop() # Ensure playlist not accessible after stream ends - fail_response = await http_client.get(parsed_url.path) + fail_response = await hls_client.get() assert fail_response.status == HTTP_NOT_FOUND @@ -176,3 +228,85 @@ def time_side_effect(): # Stop stream, if it hasn't quit already stream.stop() + + +async def test_hls_playlist_view_no_output(hass, hass_client, hls_stream): + """Test rendering the hls playlist with no output segments.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream = create_stream(hass, STREAM_SOURCE) + stream.add_provider("hls") + + hls_client = await hls_stream(stream) + + # Fetch playlist + resp = await hls_client.get("/playlist.m3u8") + assert resp.status == 404 + + +async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync): + """Test rendering the hls playlist with 1 and 2 output segments.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream = create_stream(hass, STREAM_SOURCE) + stream_worker_sync.pause() + hls = stream.add_provider("hls") + + hls.put(Segment(1, SEQUENCE_BYTES, DURATION)) + await hass.async_block_till_done() + + hls_client = await hls_stream(stream) + + resp = await hls_client.get("/playlist.m3u8") + assert resp.status == 200 + assert await resp.text() == playlist_response(sequence=1, segments=[1]) + + hls.put(Segment(2, SEQUENCE_BYTES, DURATION)) + await hass.async_block_till_done() + resp = await hls_client.get("/playlist.m3u8") + assert resp.status == 200 + assert await resp.text() == playlist_response(sequence=1, segments=[1, 2]) + + stream_worker_sync.resume() + stream.stop() + + +async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): + """Test rendering the hls playlist with more segments than the segment deque can hold.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream = create_stream(hass, STREAM_SOURCE) + stream_worker_sync.pause() + hls = stream.add_provider("hls") + + hls_client = await hls_stream(stream) + + # Produce enough segments to overfill the output buffer by one + for sequence in range(1, MAX_SEGMENTS + 2): + hls.put(Segment(sequence, SEQUENCE_BYTES, DURATION)) + await hass.async_block_till_done() + + resp = await hls_client.get("/playlist.m3u8") + assert resp.status == 200 + + # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist. + start = MAX_SEGMENTS + 2 - NUM_PLAYLIST_SEGMENTS + assert await resp.text() == playlist_response( + sequence=start, segments=range(start, MAX_SEGMENTS + 2) + ) + + # Fetch the actual segments with a fake byte payload + with patch( + "homeassistant.components.stream.hls.get_m4s", return_value=b"fake-payload" + ): + # The segment that fell off the buffer is not accessible + segment_response = await hls_client.get("/segment/1.m4s") + assert segment_response.status == 404 + + # However all segments in the buffer are accessible, even those that were not in the playlist. + for sequence in range(2, MAX_SEGMENTS + 2): + segment_response = await hls_client.get(f"/segment/{sequence}.m4s") + assert segment_response.status == 200 + + stream_worker_sync.resume() + stream.stop() From 5a907ebafc41db485d03421ef487aa1b52196a43 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 15 Feb 2021 11:51:56 +0100 Subject: [PATCH 0485/1818] Remove @home-assistant/core from MQTT codeowners (#46562) --- CODEOWNERS | 2 +- homeassistant/components/mqtt/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c6794a605d93aa..ba727de5694f5c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -286,7 +286,7 @@ homeassistant/components/monoprice/* @etsinko @OnFreund homeassistant/components/moon/* @fabaff homeassistant/components/motion_blinds/* @starkillerOG homeassistant/components/mpd/* @fabaff -homeassistant/components/mqtt/* @home-assistant/core @emontnemery +homeassistant/components/mqtt/* @emontnemery homeassistant/components/msteams/* @peroyvind homeassistant/components/my/* @home-assistant/core homeassistant/components/myq/* @bdraco diff --git a/homeassistant/components/mqtt/manifest.json b/homeassistant/components/mqtt/manifest.json index 4d44090a4e3559..9de3b07184487c 100644 --- a/homeassistant/components/mqtt/manifest.json +++ b/homeassistant/components/mqtt/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/mqtt", "requirements": ["paho-mqtt==1.5.1"], "dependencies": ["http"], - "codeowners": ["@home-assistant/core", "@emontnemery"] + "codeowners": ["@emontnemery"] } From c8e04ee9607da48a4860603907175aa772db4c5d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 15 Feb 2021 12:16:28 +0100 Subject: [PATCH 0486/1818] Bump hatasmota to 0.2.9 (#46561) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_light.py | 30 +++++++++---------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 12604d3ed81a02..17e72a57ce65d7 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.2.8"], + "requirements": ["hatasmota==0.2.9"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 110f573bc90249..342849b291c7d6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hass-nabucasa==0.41.0 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.2.8 +hatasmota==0.2.9 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b8e9858cad670..7b49c63f3c5ad0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -393,7 +393,7 @@ hangups==0.4.11 hass-nabucasa==0.41.0 # homeassistant.components.tasmota -hatasmota==0.2.8 +hatasmota==0.2.9 # homeassistant.components.jewish_calendar hdate==0.9.12 diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index d64e39aacf0d00..a60f167c38f602 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -924,7 +924,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=255, transition=4) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 8;NoDelay;Dimmer 100", + "NoDelay;Fade2 1;NoDelay;Speed2 8;NoDelay;Dimmer 100", 0, False, ) @@ -934,7 +934,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=255, transition=100) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 40;NoDelay;Dimmer 100", + "NoDelay;Fade2 1;NoDelay;Speed2 40;NoDelay;Dimmer 100", 0, False, ) @@ -944,7 +944,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=0, transition=100) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 1;NoDelay;Power1 OFF", + "NoDelay;Fade2 1;NoDelay;Speed2 1;NoDelay;Power1 OFF", 0, False, ) @@ -954,7 +954,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=128, transition=4) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 16;NoDelay;Dimmer 50", + "NoDelay;Fade2 1;NoDelay;Speed2 16;NoDelay;Dimmer 50", 0, False, ) @@ -972,7 +972,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_off(hass, "light.test", transition=6) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 24;NoDelay;Power1 OFF", + "NoDelay;Fade2 1;NoDelay;Speed2 24;NoDelay;Power1 OFF", 0, False, ) @@ -990,7 +990,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_off(hass, "light.test", transition=0) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Power1 OFF", + "NoDelay;Fade2 0;NoDelay;Power1 OFF", 0, False, ) @@ -1011,7 +1011,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", rgb_color=[255, 0, 0], transition=6) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 24;NoDelay;Power1 ON;NoDelay;Color2 255,0,0", + "NoDelay;Fade2 1;NoDelay;Speed2 24;NoDelay;Power1 ON;NoDelay;Color2 255,0,0", 0, False, ) @@ -1032,7 +1032,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", rgb_color=[255, 0, 0], transition=6) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 12;NoDelay;Power1 ON;NoDelay;Color2 255,0,0", + "NoDelay;Fade2 1;NoDelay;Speed2 12;NoDelay;Power1 ON;NoDelay;Color2 255,0,0", 0, False, ) @@ -1051,7 +1051,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", color_temp=500, transition=6) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 24;NoDelay;Power1 ON;NoDelay;CT 500", + "NoDelay;Fade2 1;NoDelay;Speed2 24;NoDelay;Power1 ON;NoDelay;CT 500", 0, False, ) @@ -1070,7 +1070,7 @@ async def test_transition(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", color_temp=326, transition=6) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 40;NoDelay;Power1 ON;NoDelay;CT 326", + "NoDelay;Fade2 1;NoDelay;Speed2 40;NoDelay;Power1 ON;NoDelay;CT 326", 0, False, ) @@ -1103,7 +1103,7 @@ async def test_transition_fixed(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=255, transition=4) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 8;NoDelay;Dimmer 100", + "NoDelay;Fade2 1;NoDelay;Speed2 8;NoDelay;Dimmer 100", 0, False, ) @@ -1113,7 +1113,7 @@ async def test_transition_fixed(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=255, transition=100) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 40;NoDelay;Dimmer 100", + "NoDelay;Fade2 1;NoDelay;Speed2 40;NoDelay;Dimmer 100", 0, False, ) @@ -1123,7 +1123,7 @@ async def test_transition_fixed(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=0, transition=4) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 8;NoDelay;Power1 OFF", + "NoDelay;Fade2 1;NoDelay;Speed2 8;NoDelay;Power1 OFF", 0, False, ) @@ -1133,7 +1133,7 @@ async def test_transition_fixed(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=128, transition=4) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 1;NoDelay;Speed 8;NoDelay;Dimmer 50", + "NoDelay;Fade2 1;NoDelay;Speed2 8;NoDelay;Dimmer 50", 0, False, ) @@ -1143,7 +1143,7 @@ async def test_transition_fixed(hass, mqtt_mock, setup_tasmota): await common.async_turn_on(hass, "light.test", brightness=128, transition=0) mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/Backlog", - "NoDelay;Fade 0;NoDelay;Dimmer 50", + "NoDelay;Fade2 0;NoDelay;Dimmer 50", 0, False, ) From bed29fd4b1c77f53029f251430b559b9d3e5600e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Pol=C3=ADvka?= Date: Mon, 15 Feb 2021 12:33:13 +0100 Subject: [PATCH 0487/1818] Convert better from byte value to percentage in futurenow (#45042) Co-authored-by: Franck Nijhof --- homeassistant/components/futurenow/light.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/futurenow/light.py b/homeassistant/components/futurenow/light.py index 6b05c96416f180..04a731a7272385 100644 --- a/homeassistant/components/futurenow/light.py +++ b/homeassistant/components/futurenow/light.py @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def to_futurenow_level(level): """Convert the given Home Assistant light level (0-255) to FutureNow (0-100).""" - return int((level * 100) / 255) + return round((level * 100) / 255) def to_hass_level(level): From c5b9ad83c2a8f363a3f912f542ce966034ee1274 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 15 Feb 2021 01:39:51 -1000 Subject: [PATCH 0488/1818] Log ffmpeg errors for homekit cameras (#46545) --- .../components/homekit/type_cameras.py | 27 +++++++++++++++++-- tests/components/homekit/test_type_cameras.py | 9 +++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index 22bf37aa0c3b2b..0a499bf5d24468 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -1,8 +1,9 @@ """Class to hold all camera accessories.""" +import asyncio from datetime import timedelta import logging -from haffmpeg.core import HAFFmpeg +from haffmpeg.core import FFMPEG_STDERR, HAFFmpeg from pyhap.camera import ( VIDEO_CODEC_PARAM_LEVEL_TYPES, VIDEO_CODEC_PARAM_PROFILE_ID_TYPES, @@ -114,6 +115,7 @@ VIDEO_PROFILE_NAMES = ["baseline", "main", "high"] FFMPEG_WATCH_INTERVAL = timedelta(seconds=5) +FFMPEG_LOGGER = "ffmpeg_logger" FFMPEG_WATCHER = "ffmpeg_watcher" FFMPEG_PID = "ffmpeg_pid" SESSION_ID = "session_id" @@ -371,7 +373,12 @@ async def start_stream(self, session_info, stream_config): _LOGGER.debug("FFmpeg output settings: %s", output) stream = HAFFmpeg(self._ffmpeg.binary) opened = await stream.open( - cmd=[], input_source=input_source, output=output, stdout_pipe=False + cmd=[], + input_source=input_source, + output=output, + extra_cmd="-hide_banner -nostats", + stderr_pipe=True, + stdout_pipe=False, ) if not opened: _LOGGER.error("Failed to open ffmpeg stream") @@ -386,9 +393,14 @@ async def start_stream(self, session_info, stream_config): session_info["stream"] = stream session_info[FFMPEG_PID] = stream.process.pid + stderr_reader = await stream.get_reader(source=FFMPEG_STDERR) + async def watch_session(_): await self._async_ffmpeg_watch(session_info["id"]) + session_info[FFMPEG_LOGGER] = asyncio.create_task( + self._async_log_stderr_stream(stderr_reader) + ) session_info[FFMPEG_WATCHER] = async_track_time_interval( self.hass, watch_session, @@ -397,6 +409,16 @@ async def watch_session(_): return await self._async_ffmpeg_watch(session_info["id"]) + async def _async_log_stderr_stream(self, stderr_reader): + """Log output from ffmpeg.""" + _LOGGER.debug("%s: ffmpeg: started", self.display_name) + while True: + line = await stderr_reader.readline() + if line == b"": + return + + _LOGGER.debug("%s: ffmpeg: %s", self.display_name, line.rstrip()) + async def _async_ffmpeg_watch(self, session_id): """Check to make sure ffmpeg is still running and cleanup if not.""" ffmpeg_pid = self.sessions[session_id][FFMPEG_PID] @@ -414,6 +436,7 @@ def _async_stop_ffmpeg_watch(self, session_id): if FFMPEG_WATCHER not in self.sessions[session_id]: return self.sessions[session_id].pop(FFMPEG_WATCHER)() + self.sessions[session_id].pop(FFMPEG_LOGGER).cancel() async def stop_stream(self, session_info): """Stop the stream for the given ``session_id``.""" diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index f4c7169310f3a1..c9b2ebc422cd4b 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -99,6 +99,7 @@ def _get_exits_after_startup_mock_ffmpeg(): ffmpeg.open = AsyncMock(return_value=True) ffmpeg.close = AsyncMock(return_value=True) ffmpeg.kill = AsyncMock(return_value=True) + ffmpeg.get_reader = AsyncMock() return ffmpeg @@ -108,6 +109,7 @@ def _get_working_mock_ffmpeg(): ffmpeg.open = AsyncMock(return_value=True) ffmpeg.close = AsyncMock(return_value=True) ffmpeg.kill = AsyncMock(return_value=True) + ffmpeg.get_reader = AsyncMock() return ffmpeg @@ -118,6 +120,7 @@ def _get_failing_mock_ffmpeg(): ffmpeg.open = AsyncMock(return_value=False) ffmpeg.close = AsyncMock(side_effect=OSError) ffmpeg.kill = AsyncMock(side_effect=OSError) + ffmpeg.get_reader = AsyncMock() return ffmpeg @@ -189,6 +192,8 @@ async def test_camera_stream_source_configured(hass, run_driver, events): input_source="-i /dev/null", output=expected_output.format(**session_info), stdout_pipe=False, + extra_cmd="-hide_banner -nostats", + stderr_pipe=True, ) await _async_setup_endpoints(hass, acc) @@ -472,6 +477,8 @@ async def test_camera_stream_source_configured_and_copy_codec(hass, run_driver, input_source="-i /dev/null", output=expected_output.format(**session_info), stdout_pipe=False, + extra_cmd="-hide_banner -nostats", + stderr_pipe=True, ) @@ -542,6 +549,8 @@ async def test_camera_streaming_fails_after_starting_ffmpeg(hass, run_driver, ev input_source="-i /dev/null", output=expected_output.format(**session_info), stdout_pipe=False, + extra_cmd="-hide_banner -nostats", + stderr_pipe=True, ) From 9917bb76fbaed50d57910a167f11ffe1b465cee6 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Mon, 15 Feb 2021 23:37:53 +0800 Subject: [PATCH 0489/1818] Use httpx in generic camera (#46576) * Use httpx in generic camera * Remove commented out code --- homeassistant/components/generic/camera.py | 59 +++++++--------------- tests/components/generic/test_camera.py | 50 ++++++++++-------- 2 files changed, 46 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 2e798b8cc4b5e0..28db66b4f3eae9 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -2,10 +2,7 @@ import asyncio import logging -import aiohttp -import async_timeout -import requests -from requests.auth import HTTPDigestAuth +import httpx import voluptuous as vol from homeassistant.components.camera import ( @@ -25,7 +22,7 @@ ) from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.reload import async_setup_reload_service from . import DOMAIN, PLATFORMS @@ -39,6 +36,7 @@ CONF_FRAMERATE = "framerate" DEFAULT_NAME = "Generic Camera" +GET_IMAGE_TIMEOUT = 10 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -93,9 +91,9 @@ def __init__(self, hass, device_info): if username and password: if self._authentication == HTTP_DIGEST_AUTHENTICATION: - self._auth = HTTPDigestAuth(username, password) + self._auth = httpx.DigestAuth(username, password) else: - self._auth = aiohttp.BasicAuth(username, password=password) + self._auth = httpx.BasicAuth(username, password=password) else: self._auth = None @@ -129,40 +127,19 @@ async def async_camera_image(self): if url == self._last_url and self._limit_refetch: return self._last_image - # aiohttp don't support DigestAuth yet - if self._authentication == HTTP_DIGEST_AUTHENTICATION: - - def fetch(): - """Read image from a URL.""" - try: - response = requests.get( - url, timeout=10, auth=self._auth, verify=self.verify_ssl - ) - return response.content - except requests.exceptions.RequestException as error: - _LOGGER.error( - "Error getting new camera image from %s: %s", self._name, error - ) - return self._last_image - - self._last_image = await self.hass.async_add_executor_job(fetch) - # async - else: - try: - websession = async_get_clientsession( - self.hass, verify_ssl=self.verify_ssl - ) - with async_timeout.timeout(10): - response = await websession.get(url, auth=self._auth) - self._last_image = await response.read() - except asyncio.TimeoutError: - _LOGGER.error("Timeout getting camera image from %s", self._name) - return self._last_image - except aiohttp.ClientError as err: - _LOGGER.error( - "Error getting new camera image from %s: %s", self._name, err - ) - return self._last_image + try: + async_client = get_async_client(self.hass, verify_ssl=self.verify_ssl) + response = await async_client.get( + url, auth=self._auth, timeout=GET_IMAGE_TIMEOUT + ) + response.raise_for_status() + self._last_image = response.content + except httpx.TimeoutException: + _LOGGER.error("Timeout getting camera image from %s", self._name) + return self._last_image + except (httpx.RequestError, httpx.HTTPStatusError) as err: + _LOGGER.error("Error getting new camera image from %s: %s", self._name, err) + return self._last_image self._last_url = url return self._last_image diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 65f5306c4d8fbc..7f5b3bb3b53912 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -3,6 +3,8 @@ from os import path from unittest.mock import patch +import respx + from homeassistant import config as hass_config from homeassistant.components.generic import DOMAIN from homeassistant.components.websocket_api.const import TYPE_RESULT @@ -14,9 +16,10 @@ from homeassistant.setup import async_setup_component -async def test_fetching_url(aioclient_mock, hass, hass_client): +@respx.mock +async def test_fetching_url(hass, hass_client): """Test that it fetches the given url.""" - aioclient_mock.get("http://example.com", text="hello world") + respx.get("http://example.com").respond(text="hello world") await async_setup_component( hass, @@ -38,12 +41,12 @@ async def test_fetching_url(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 body = await resp.text() assert body == "hello world" resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 async def test_fetching_without_verify_ssl(aioclient_mock, hass, hass_client): @@ -100,12 +103,13 @@ async def test_fetching_url_with_verify_ssl(aioclient_mock, hass, hass_client): assert resp.status == 200 -async def test_limit_refetch(aioclient_mock, hass, hass_client): +@respx.mock +async def test_limit_refetch(hass, hass_client): """Test that it fetches the given url.""" - aioclient_mock.get("http://example.com/5a", text="hello world") - aioclient_mock.get("http://example.com/10a", text="hello world") - aioclient_mock.get("http://example.com/15a", text="hello planet") - aioclient_mock.get("http://example.com/20a", status=HTTP_NOT_FOUND) + respx.get("http://example.com/5a").respond(text="hello world") + respx.get("http://example.com/10a").respond(text="hello world") + respx.get("http://example.com/15a").respond(text="hello planet") + respx.get("http://example.com/20a").respond(status_code=HTTP_NOT_FOUND) await async_setup_component( hass, @@ -129,19 +133,19 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): with patch("async_timeout.timeout", side_effect=asyncio.TimeoutError()): resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 0 + assert respx.calls.call_count == 0 assert resp.status == HTTP_INTERNAL_SERVER_ERROR hass.states.async_set("sensor.temp", "10") resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 assert resp.status == 200 body = await resp.text() assert body == "hello world" resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 assert resp.status == 200 body = await resp.text() assert body == "hello world" @@ -150,7 +154,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): # Url change = fetch new image resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 assert resp.status == 200 body = await resp.text() assert body == "hello planet" @@ -158,7 +162,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): # Cause a template render error hass.states.async_remove("sensor.temp") resp = await client.get("/api/camera_proxy/camera.config_test") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 assert resp.status == 200 body = await resp.text() assert body == "hello planet" @@ -285,11 +289,12 @@ async def test_no_stream_source(aioclient_mock, hass, hass_client, hass_ws_clien } -async def test_camera_content_type(aioclient_mock, hass, hass_client): +@respx.mock +async def test_camera_content_type(hass, hass_client): """Test generic camera with custom content_type.""" svg_image = "" urlsvg = "https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg" - aioclient_mock.get(urlsvg, text=svg_image) + respx.get(urlsvg).respond(text=svg_image) cam_config_svg = { "name": "config_test_svg", @@ -309,23 +314,24 @@ async def test_camera_content_type(aioclient_mock, hass, hass_client): client = await hass_client() resp_1 = await client.get("/api/camera_proxy/camera.config_test_svg") - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 assert resp_1.status == 200 assert resp_1.content_type == "image/svg+xml" body = await resp_1.text() assert body == svg_image resp_2 = await client.get("/api/camera_proxy/camera.config_test_jpg") - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 assert resp_2.status == 200 assert resp_2.content_type == "image/jpeg" body = await resp_2.text() assert body == svg_image -async def test_reloading(aioclient_mock, hass, hass_client): +@respx.mock +async def test_reloading(hass, hass_client): """Test we can cleanly reload.""" - aioclient_mock.get("http://example.com", text="hello world") + respx.get("http://example.com").respond(text="hello world") await async_setup_component( hass, @@ -347,7 +353,7 @@ async def test_reloading(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.config_test") assert resp.status == 200 - assert aioclient_mock.call_count == 1 + assert respx.calls.call_count == 1 body = await resp.text() assert body == "hello world" @@ -374,7 +380,7 @@ async def test_reloading(aioclient_mock, hass, hass_client): resp = await client.get("/api/camera_proxy/camera.reload") assert resp.status == 200 - assert aioclient_mock.call_count == 2 + assert respx.calls.call_count == 2 body = await resp.text() assert body == "hello world" From a5d943b5f15c20b4c4f01bfdb412f18c297032be Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 15 Feb 2021 18:33:42 +0200 Subject: [PATCH 0490/1818] MQTT cover Bugfixes (#46479) * MQTT cover Bugfixes * Remove period --- homeassistant/components/mqtt/cover.py | 44 +++++++------ tests/components/mqtt/test_cover.py | 89 +++++++++++++------------- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 54ef6cfb539277..7b7f983b1e49a4 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -129,14 +129,14 @@ def validate_options(value): ): _LOGGER.warning( "using 'value_template' for 'position_topic' is deprecated " - "and will be removed from Home Assistant in version 2021.6" + "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) if CONF_TILT_INVERT_STATE in value: _LOGGER.warning( "'tilt_invert_state' is deprecated " - "and will be removed from Home Assistant in version 2021.6" + "and will be removed from Home Assistant in version 2021.6, " "please invert tilt using 'tilt_min' & 'tilt_max'" ) @@ -172,9 +172,7 @@ def validate_options(value): CONF_TILT_CLOSED_POSITION, default=DEFAULT_TILT_CLOSED_POSITION ): int, vol.Optional(CONF_TILT_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_TILT_INVERT_STATE, default=DEFAULT_TILT_INVERT_STATE - ): cv.boolean, + vol.Optional(CONF_TILT_INVERT_STATE): cv.boolean, vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int, vol.Optional(CONF_TILT_MIN, default=DEFAULT_TILT_MIN): int, vol.Optional( @@ -247,15 +245,22 @@ def _setup_from_config(self, config): ) self._tilt_optimistic = config[CONF_TILT_STATE_OPTIMISTIC] - template = self._config.get(CONF_VALUE_TEMPLATE) - if template is not None: - template.hass = self.hass + value_template = self._config.get(CONF_VALUE_TEMPLATE) + if value_template is not None: + value_template.hass = self.hass + set_position_template = self._config.get(CONF_SET_POSITION_TEMPLATE) if set_position_template is not None: set_position_template.hass = self.hass + + get_position_template = self._config.get(CONF_GET_POSITION_TEMPLATE) + if get_position_template is not None: + get_position_template.hass = self.hass + set_tilt_template = self._config.get(CONF_TILT_COMMAND_TEMPLATE) if set_tilt_template is not None: set_tilt_template.hass = self.hass + tilt_status_template = self._config.get(CONF_TILT_STATUS_TEMPLATE) if tilt_status_template is not None: tilt_status_template.hass = self.hass @@ -290,24 +295,21 @@ def tilt_message_received(msg): def state_message_received(msg): """Handle new MQTT state messages.""" payload = msg.payload - template = self._config.get(CONF_VALUE_TEMPLATE) - if template is not None: - payload = template.async_render_with_possible_json_value(payload) + value_template = self._config.get(CONF_VALUE_TEMPLATE) + if value_template is not None: + payload = value_template.async_render_with_possible_json_value(payload) if payload == self._config[CONF_STATE_STOPPED]: - if ( - self._optimistic - or self._config.get(CONF_GET_POSITION_TOPIC) is None - ): - self._state = ( - STATE_CLOSED if self._state == STATE_CLOSING else STATE_OPEN - ) - else: + if self._config.get(CONF_GET_POSITION_TOPIC) is not None: self._state = ( STATE_CLOSED if self._position == DEFAULT_POSITION_CLOSED else STATE_OPEN ) + else: + self._state = ( + STATE_CLOSED if self._state == STATE_CLOSING else STATE_OPEN + ) elif payload == self._config[CONF_STATE_OPENING]: self._state = STATE_OPENING elif payload == self._config[CONF_STATE_CLOSING]: @@ -616,7 +618,7 @@ def find_percentage_in_range(self, position, range_type=TILT_PAYLOAD): max_percent = 100 min_percent = 0 position_percentage = min(max(position_percentage, min_percent), max_percent) - if range_type == TILT_PAYLOAD and self._config[CONF_TILT_INVERT_STATE]: + if range_type == TILT_PAYLOAD and self._config.get(CONF_TILT_INVERT_STATE): return 100 - position_percentage return position_percentage @@ -640,6 +642,6 @@ def find_in_range_from_percent(self, percentage, range_type=TILT_PAYLOAD): position = round(current_range * (percentage / 100.0)) position += offset - if range_type == TILT_PAYLOAD and self._config[CONF_TILT_INVERT_STATE]: + if range_type == TILT_PAYLOAD and self._config.get(CONF_TILT_INVERT_STATE): position = max_range - position + offset return position diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 87b016e2d59d19..44144642f40959 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -2032,10 +2032,10 @@ async def test_entity_debug_info_message(hass, mqtt_mock): ) -async def test_deprecated_value_template_for_position_topic_warnning( +async def test_deprecated_value_template_for_position_topic_warning( hass, caplog, mqtt_mock ): - """Test warnning when value_template is used for position_topic.""" + """Test warning when value_template is used for position_topic.""" assert await async_setup_component( hass, cover.DOMAIN, @@ -2054,13 +2054,13 @@ async def test_deprecated_value_template_for_position_topic_warnning( assert ( "using 'value_template' for 'position_topic' is deprecated " - "and will be removed from Home Assistant in version 2021.6" + "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) in caplog.text -async def test_deprecated_tilt_invert_state_warnning(hass, caplog, mqtt_mock): - """Test warnning when tilt_invert_state is used.""" +async def test_deprecated_tilt_invert_state_warning(hass, caplog, mqtt_mock): + """Test warning when tilt_invert_state is used.""" assert await async_setup_component( hass, cover.DOMAIN, @@ -2077,11 +2077,33 @@ async def test_deprecated_tilt_invert_state_warnning(hass, caplog, mqtt_mock): assert ( "'tilt_invert_state' is deprecated " - "and will be removed from Home Assistant in version 2021.6" + "and will be removed from Home Assistant in version 2021.6, " "please invert tilt using 'tilt_min' & 'tilt_max'" ) in caplog.text +async def test_no_deprecated_tilt_invert_state_warning(hass, caplog, mqtt_mock): + """Test warning when tilt_invert_state is used.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + } + }, + ) + await hass.async_block_till_done() + + assert ( + "'tilt_invert_state' is deprecated " + "and will be removed from Home Assistant in version 2021.6, " + "please invert tilt using 'tilt_min' & 'tilt_max'" + ) not in caplog.text + + async def test_no_deprecated_warning_for_position_topic_using_position_template( hass, caplog, mqtt_mock ): @@ -2104,7 +2126,7 @@ async def test_no_deprecated_warning_for_position_topic_using_position_template( assert ( "using 'value_template' for 'position_topic' is deprecated " - "and will be removed from Home Assistant in version 2021.6" + "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) not in caplog.text @@ -2221,8 +2243,8 @@ async def test_set_state_via_position_using_stopped_state(hass, mqtt_mock): assert state.state == STATE_OPEN -async def test_set_state_via_stopped_state_optimistic(hass, mqtt_mock): - """Test the controlling state via stopped state in optimistic mode.""" +async def test_position_via_position_topic_template(hass, mqtt_mock): + """Test position by updating status via position template.""" assert await async_setup_component( hass, cover.DOMAIN, @@ -2231,51 +2253,28 @@ async def test_set_state_via_stopped_state_optimistic(hass, mqtt_mock): "platform": "mqtt", "name": "test", "state_topic": "state-topic", - "position_topic": "get-position-topic", - "position_open": 100, - "position_closed": 0, - "state_open": "OPEN", - "state_closed": "CLOSE", - "state_stopped": "STOPPED", - "state_opening": "OPENING", - "state_closing": "CLOSING", "command_topic": "command-topic", - "qos": 0, - "optimistic": True, + "set_position_topic": "set-position-topic", + "position_topic": "get-position-topic", + "position_template": "{{ (value | multiply(0.01)) | int }}", } }, ) await hass.async_block_till_done() - async_fire_mqtt_message(hass, "state-topic", "OPEN") - - state = hass.states.get("cover.test") - assert state.state == STATE_OPEN - - async_fire_mqtt_message(hass, "get-position-topic", "50") - - state = hass.states.get("cover.test") - assert state.state == STATE_OPEN - - async_fire_mqtt_message(hass, "state-topic", "OPENING") - - state = hass.states.get("cover.test") - assert state.state == STATE_OPENING - - async_fire_mqtt_message(hass, "state-topic", "STOPPED") - - state = hass.states.get("cover.test") - assert state.state == STATE_OPEN - - async_fire_mqtt_message(hass, "state-topic", "CLOSING") + async_fire_mqtt_message(hass, "get-position-topic", "99") - state = hass.states.get("cover.test") - assert state.state == STATE_CLOSING + current_cover_position_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_POSITION + ] + assert current_cover_position_position == 0 - async_fire_mqtt_message(hass, "state-topic", "STOPPED") + async_fire_mqtt_message(hass, "get-position-topic", "5000") - state = hass.states.get("cover.test") - assert state.state == STATE_CLOSED + current_cover_position_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_POSITION + ] + assert current_cover_position_position == 50 async def test_set_state_via_stopped_state_no_position_topic(hass, mqtt_mock): From 886067a32713e18fa445342c16175b28e2d38cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 15 Feb 2021 18:18:45 +0100 Subject: [PATCH 0491/1818] Add websocket handlers to hassio (#46571) --- homeassistant/components/hassio/__init__.py | 27 +++--- homeassistant/components/hassio/const.py | 39 +++++++-- .../components/hassio/websocket_api.py | 84 +++++++++++++++++++ tests/components/hassio/test_init.py | 68 +++++++++++++++ 4 files changed, 199 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/hassio/websocket_api.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index e8b874b23345e8..1c246ae753b74c 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -10,7 +10,6 @@ from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util from homeassistant.const import ( - ATTR_NAME, EVENT_CORE_CONFIG_UPDATE, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, @@ -24,15 +23,27 @@ from .addon_panel import async_setup_addon_panel from .auth import async_setup_auth_view -from .const import ATTR_DISCOVERY +from .const import ( + ATTR_ADDON, + ATTR_ADDONS, + ATTR_DISCOVERY, + ATTR_FOLDERS, + ATTR_HOMEASSISTANT, + ATTR_INPUT, + ATTR_NAME, + ATTR_PASSWORD, + ATTR_SNAPSHOT, + DOMAIN, +) from .discovery import async_setup_discovery_view from .handler import HassIO, HassioAPIError, api_data from .http import HassIOView from .ingress import async_setup_ingress_view +from .websocket_api import async_load_websocket_api _LOGGER = logging.getLogger(__name__) -DOMAIN = "hassio" + STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 @@ -62,13 +73,6 @@ SERVICE_RESTORE_FULL = "restore_full" SERVICE_RESTORE_PARTIAL = "restore_partial" -ATTR_ADDON = "addon" -ATTR_INPUT = "input" -ATTR_SNAPSHOT = "snapshot" -ATTR_ADDONS = "addons" -ATTR_FOLDERS = "folders" -ATTR_HOMEASSISTANT = "homeassistant" -ATTR_PASSWORD = "password" SCHEMA_NO_DATA = vol.Schema({}) @@ -101,6 +105,7 @@ } ) + MAP_SERVICE_API = { SERVICE_ADDON_START: ("/addons/{addon}/start", SCHEMA_ADDON, 60, False), SERVICE_ADDON_STOP: ("/addons/{addon}/stop", SCHEMA_ADDON, 60, False), @@ -290,6 +295,8 @@ async def async_setup(hass, config): _LOGGER.error("Missing %s environment variable", env) return False + async_load_websocket_api(hass) + host = os.environ["HASSIO"] websession = hass.helpers.aiohttp_client.async_get_clientsession() hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host) diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index ffccb32539563f..00893f8340197e 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -1,21 +1,42 @@ """Hass.io const variables.""" +DOMAIN = "hassio" + +ATTR_ADDON = "addon" ATTR_ADDONS = "addons" +ATTR_ADMIN = "admin" +ATTR_CONFIG = "config" +ATTR_DATA = "data" ATTR_DISCOVERY = "discovery" -ATTR_ADDON = "addon" +ATTR_ENABLE = "enable" +ATTR_FOLDERS = "folders" +ATTR_HOMEASSISTANT = "homeassistant" +ATTR_ICON = "icon" +ATTR_INPUT = "input" ATTR_NAME = "name" -ATTR_SERVICE = "service" -ATTR_CONFIG = "config" -ATTR_UUID = "uuid" -ATTR_USERNAME = "username" -ATTR_PASSWORD = "password" ATTR_PANELS = "panels" -ATTR_ENABLE = "enable" +ATTR_PASSWORD = "password" +ATTR_SERVICE = "service" +ATTR_SNAPSHOT = "snapshot" ATTR_TITLE = "title" -ATTR_ICON = "icon" -ATTR_ADMIN = "admin" +ATTR_USERNAME = "username" +ATTR_UUID = "uuid" +ATTR_WS_EVENT = "event" +ATTR_ENDPOINT = "endpoint" +ATTR_METHOD = "method" +ATTR_TIMEOUT = "timeout" + X_HASSIO = "X-Hassio-Key" X_INGRESS_PATH = "X-Ingress-Path" X_HASS_USER_ID = "X-Hass-User-ID" X_HASS_IS_ADMIN = "X-Hass-Is-Admin" + + +WS_TYPE = "type" +WS_ID = "id" + +WS_TYPE_EVENT = "supervisor/event" +WS_TYPE_API = "supervisor/api" + +EVENT_SUPERVISOR_EVENT = "supervisor_event" diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py new file mode 100644 index 00000000000000..851404b4b0ee6f --- /dev/null +++ b/homeassistant/components/hassio/websocket_api.py @@ -0,0 +1,84 @@ +"""Websocekt API handlers for the hassio integration.""" +import logging + +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.core import HomeAssistant, callback +import homeassistant.helpers.config_validation as cv + +from .const import ( + ATTR_DATA, + ATTR_ENDPOINT, + ATTR_METHOD, + ATTR_TIMEOUT, + ATTR_WS_EVENT, + DOMAIN, + EVENT_SUPERVISOR_EVENT, + WS_ID, + WS_TYPE, + WS_TYPE_API, + WS_TYPE_EVENT, +) +from .handler import HassIO + +SCHEMA_WEBSOCKET_EVENT = vol.Schema( + {vol.Required(ATTR_WS_EVENT): cv.string}, + extra=vol.ALLOW_EXTRA, +) + +_LOGGER: logging.Logger = logging.getLogger(__package__) + + +@callback +def async_load_websocket_api(hass: HomeAssistant): + """Set up the websocket API.""" + websocket_api.async_register_command(hass, websocket_supervisor_event) + websocket_api.async_register_command(hass, websocket_supervisor_api) + + +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(WS_TYPE): WS_TYPE_EVENT, + vol.Required(ATTR_DATA): SCHEMA_WEBSOCKET_EVENT, + } +) +async def websocket_supervisor_event( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +): + """Publish events from the Supervisor.""" + hass.bus.async_fire(EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) + connection.send_result(msg[WS_ID]) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(WS_TYPE): WS_TYPE_API, + vol.Required(ATTR_ENDPOINT): cv.string, + vol.Required(ATTR_METHOD): cv.string, + vol.Optional(ATTR_DATA): dict, + vol.Optional(ATTR_TIMEOUT): cv.string, + } +) +async def websocket_supervisor_api( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +): + """Websocket handler to call Supervisor API.""" + supervisor: HassIO = hass.data[DOMAIN] + result = False + try: + result = await supervisor.send_command( + msg[ATTR_ENDPOINT], + method=msg[ATTR_METHOD], + timeout=msg.get(ATTR_TIMEOUT, 10), + payload=msg.get(ATTR_DATA, {}), + ) + except hass.components.hassio.HassioAPIError as err: + _LOGGER.error("Failed to to call %s - %s", msg[ATTR_ENDPOINT], err) + connection.send_error(msg[WS_ID], err) + else: + connection.send_result(msg[WS_ID], result[ATTR_DATA]) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 7ed24dca457036..eaeed74fbf7c43 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -7,8 +7,21 @@ from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend from homeassistant.components.hassio import STORAGE_KEY +from homeassistant.components.hassio.const import ( + ATTR_DATA, + ATTR_ENDPOINT, + ATTR_METHOD, + EVENT_SUPERVISOR_EVENT, + WS_ID, + WS_TYPE, + WS_TYPE_API, + WS_TYPE_EVENT, +) +from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import async_capture_events + MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} @@ -346,3 +359,58 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert mock_check_config.called assert aioclient_mock.call_count == 5 + + +async def test_websocket_supervisor_event( + hassio_env, hass: HomeAssistant, hass_ws_client +): + """Test Supervisor websocket event.""" + assert await async_setup_component(hass, "hassio", {}) + websocket_client = await hass_ws_client(hass) + + test_event = async_capture_events(hass, EVENT_SUPERVISOR_EVENT) + + await websocket_client.send_json( + {WS_ID: 1, WS_TYPE: WS_TYPE_EVENT, ATTR_DATA: {"event": "test"}} + ) + + assert await websocket_client.receive_json() + await hass.async_block_till_done() + + assert test_event[0].data == {"event": "test"} + + +async def test_websocket_supervisor_api( + hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock +): + """Test Supervisor websocket api.""" + assert await async_setup_component(hass, "hassio", {}) + websocket_client = await hass_ws_client(hass) + aioclient_mock.post( + "http://127.0.0.1/snapshots/new/partial", + json={"result": "ok", "data": {"slug": "sn_slug"}}, + ) + + await websocket_client.send_json( + { + WS_ID: 1, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/snapshots/new/partial", + ATTR_METHOD: "post", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["slug"] == "sn_slug" + + await websocket_client.send_json( + { + WS_ID: 2, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/supervisor/info", + ATTR_METHOD: "get", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["version_latest"] == "1.0.0" From f2ca4acff0e19357a703dfe982ab0e72b5d12c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20M=C3=BCndler?= Date: Mon, 15 Feb 2021 18:28:28 +0100 Subject: [PATCH 0492/1818] Limit fronius error messages on failed connection (#45824) * Do not print several error messages on failed connection * Change wrapping of exception, implement available * Simplify exception flow * Remove unnecessary init * Add available property to actual entities * Rebase and formatting --- homeassistant/components/fronius/sensor.py | 28 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 130a8d55072c6c..02ff760e574f3b 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -2,6 +2,7 @@ import copy from datetime import timedelta import logging +from typing import Dict from pyfronius import Fronius import voluptuous as vol @@ -130,6 +131,7 @@ def __init__(self, bridge, name, device, add_entities): self._name = name self._device = device self._fetched = {} + self._available = True self.sensors = set() self._registered_sensors = set() @@ -145,21 +147,32 @@ def data(self): """Return the state attributes.""" return self._fetched + @property + def available(self): + """Whether the fronius device is active.""" + return self._available + async def async_update(self): """Retrieve and update latest state.""" - values = {} try: values = await self._update() except ConnectionError: - _LOGGER.error("Failed to update: connection error") + # fronius devices are often powered by self-produced solar energy + # and henced turned off at night. + # Therefore we will not print multiple errors when connection fails + if self._available: + self._available = False + _LOGGER.error("Failed to update: connection error") + return except ValueError: _LOGGER.error( "Failed to update: invalid response returned." "Maybe the configured device is not supported" ) - - if not values: return + + self._available = True # reset connection failure + attributes = self._fetched # Copy data of current fronius device for key, entry in values.items(): @@ -182,7 +195,7 @@ async def async_update(self): for sensor in self._registered_sensors: sensor.async_schedule_update_ha_state(True) - async def _update(self): + async def _update(self) -> Dict: """Return values of interest.""" async def register(self, sensor): @@ -268,6 +281,11 @@ def should_poll(self): """Device should not be polled, returns False.""" return False + @property + def available(self): + """Whether the fronius device is active.""" + return self.parent.available + async def async_update(self): """Update the internal state.""" state = self.parent.data.get(self._name) From 89aaeb3c351a5f9c3fe28bbd62066b4ab5cd665e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 15 Feb 2021 09:52:37 -0800 Subject: [PATCH 0493/1818] Refactor stream worker responsibilities for segmenting into a separate class (#46563) * Remove stream_worker dependencies on Stream Removee stream_worker dependencies on Stream and split out the logic for writing segments to a stream buffer. * Stop calling internal stream methods * Update homeassistant/components/stream/worker.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Reuse self._outputs when creating new streams Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> --- homeassistant/components/stream/__init__.py | 9 +- homeassistant/components/stream/worker.py | 143 ++++++++++---------- tests/components/stream/test_worker.py | 4 +- 3 files changed, 81 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index e871963d2baec9..cdaa0faeb9502e 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -124,7 +124,6 @@ def endpoint_url(self, fmt): self.access_token = secrets.token_hex() return self.hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(self.access_token) - @property def outputs(self): """Return a copy of the stream outputs.""" # A copy is returned so the caller can iterate through the outputs @@ -192,7 +191,7 @@ def _run_worker(self): wait_timeout = 0 while not self._thread_quit.wait(timeout=wait_timeout): start_time = time.time() - stream_worker(self.hass, self, self._thread_quit) + stream_worker(self.source, self.options, self.outputs, self._thread_quit) if not self.keepalive or self._thread_quit.is_set(): if self._fast_restart_once: # The stream source is updated, restart without any delay. @@ -219,7 +218,7 @@ def _worker_finished(self): @callback def remove_outputs(): - for provider in self.outputs.values(): + for provider in self.outputs().values(): self.remove_provider(provider) self.hass.loop.call_soon_threadsafe(remove_outputs) @@ -248,7 +247,7 @@ async def async_record(self, video_path, duration=30, lookback=5): raise HomeAssistantError(f"Can't write {video_path}, no access to path!") # Add recorder - recorder = self.outputs.get("recorder") + recorder = self.outputs().get("recorder") if recorder: raise HomeAssistantError( f"Stream already recording to {recorder.video_path}!" @@ -259,7 +258,7 @@ async def async_record(self, video_path, duration=30, lookback=5): self.start() # Take advantage of lookback - hls = self.outputs.get("hls") + hls = self.outputs().get("hls") if lookback > 0 and hls: num_segments = min(int(lookback // hls.target_duration), MAX_SEGMENTS) # Wait for latest segment, then add the lookback diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 510d0ebd460c34..2050787a71400f 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -43,15 +43,78 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): return StreamBuffer(segment, output, vstream, astream) -def stream_worker(hass, stream, quit_event): +class SegmentBuffer: + """Buffer for writing a sequence of packets to the output as a segment.""" + + def __init__(self, video_stream, audio_stream, outputs_callback) -> None: + """Initialize SegmentBuffer.""" + self._video_stream = video_stream + self._audio_stream = audio_stream + self._outputs_callback = outputs_callback + # tuple of StreamOutput, StreamBuffer + self._outputs = [] + self._sequence = 0 + self._segment_start_pts = None + + def reset(self, video_pts): + """Initialize a new stream segment.""" + # Keep track of the number of segments we've processed + self._sequence += 1 + self._segment_start_pts = video_pts + + # Fetch the latest StreamOutputs, which may have changed since the + # worker started. + self._outputs = [] + for stream_output in self._outputs_callback().values(): + if self._video_stream.name not in stream_output.video_codecs: + continue + buffer = create_stream_buffer( + stream_output, self._video_stream, self._audio_stream, self._sequence + ) + self._outputs.append((buffer, stream_output)) + + def mux_packet(self, packet): + """Mux a packet to the appropriate StreamBuffers.""" + + # Check for end of segment + if packet.stream == self._video_stream and packet.is_keyframe: + duration = (packet.pts - self._segment_start_pts) * packet.time_base + if duration >= MIN_SEGMENT_DURATION: + # Save segment to outputs + self.flush(duration) + + # Reinitialize + self.reset(packet.pts) + + # Mux the packet + for (buffer, _) in self._outputs: + if packet.stream == self._video_stream: + packet.stream = buffer.vstream + elif packet.stream == self._audio_stream: + packet.stream = buffer.astream + else: + continue + buffer.output.mux(packet) + + def flush(self, duration): + """Create a segment from the buffered packets and write to output.""" + for (buffer, stream_output) in self._outputs: + buffer.output.close() + stream_output.put(Segment(self._sequence, buffer.segment, duration)) + + def close(self): + """Close all StreamBuffers.""" + for (buffer, _) in self._outputs: + buffer.output.close() + + +def stream_worker(source, options, outputs_callback, quit_event): """Handle consuming streams.""" try: - container = av.open( - stream.source, options=stream.options, timeout=STREAM_TIMEOUT - ) + container = av.open(source, options=options, timeout=STREAM_TIMEOUT) except av.AVError: - _LOGGER.error("Error opening stream %s", stream.source) + _LOGGER.error("Error opening stream %s", source) return try: video_stream = container.streams.video[0] @@ -78,9 +141,7 @@ def stream_worker(hass, stream, quit_event): # Keep track of consecutive packets without a dts to detect end of stream. missing_dts = 0 # Holds the buffers for each stream provider - outputs = None - # Keep track of the number of segments we've processed - sequence = 0 + segment_buffer = SegmentBuffer(video_stream, audio_stream, outputs_callback) # The video pts at the beginning of the segment segment_start_pts = None # Because of problems 1 and 2 below, we need to store the first few packets and replay them @@ -157,44 +218,11 @@ def peek_first_pts(): return False return True - def initialize_segment(video_pts): - """Reset some variables and initialize outputs for each segment.""" - nonlocal outputs, sequence, segment_start_pts - # Clear outputs and increment sequence - outputs = {} - sequence += 1 - segment_start_pts = video_pts - for stream_output in stream.outputs.values(): - if video_stream.name not in stream_output.video_codecs: - continue - buffer = create_stream_buffer( - stream_output, video_stream, audio_stream, sequence - ) - outputs[stream_output.name] = ( - buffer, - {video_stream: buffer.vstream, audio_stream: buffer.astream}, - ) - - def mux_video_packet(packet): - # mux packets to each buffer - for buffer, output_streams in outputs.values(): - # Assign the packet to the new stream & mux - packet.stream = output_streams[video_stream] - buffer.output.mux(packet) - - def mux_audio_packet(packet): - # almost the same as muxing video but add extra check - for buffer, output_streams in outputs.values(): - # Assign the packet to the new stream & mux - if output_streams.get(audio_stream): - packet.stream = output_streams[audio_stream] - buffer.output.mux(packet) - if not peek_first_pts(): container.close() return - initialize_segment(segment_start_pts) + segment_buffer.reset(segment_start_pts) while not quit_event.is_set(): try: @@ -229,34 +257,13 @@ def mux_audio_packet(packet): break continue - # Check for end of segment - if packet.stream == video_stream and packet.is_keyframe: - segment_duration = (packet.pts - segment_start_pts) * packet.time_base - if segment_duration >= MIN_SEGMENT_DURATION: - # Save segment to outputs - for fmt, (buffer, _) in outputs.items(): - buffer.output.close() - if stream.outputs.get(fmt): - stream.outputs[fmt].put( - Segment( - sequence, - buffer.segment, - segment_duration, - ), - ) - - # Reinitialize - initialize_segment(packet.pts) - # Update last_dts processed last_dts[packet.stream] = packet.dts - # mux packets - if packet.stream == video_stream: - mux_video_packet(packet) # mutates packet timestamps - else: - mux_audio_packet(packet) # mutates packet timestamps + + # Mux packets, and possibly write a segment to the output stream. + # This mutates packet timestamps and stream + segment_buffer.mux_packet(packet) # Close stream - for buffer, _ in outputs.values(): - buffer.output.close() + segment_buffer.close() container.close() diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 0d5be68d93c203..b348d68fc86a7f 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -198,7 +198,7 @@ async def async_decode_stream(hass, packets, py_av=None): "homeassistant.components.stream.core.StreamOutput.put", side_effect=py_av.capture_buffer.capture_output_segment, ): - stream_worker(hass, stream, threading.Event()) + stream_worker(STREAM_SOURCE, {}, stream.outputs, threading.Event()) await hass.async_block_till_done() return py_av.capture_buffer @@ -210,7 +210,7 @@ async def test_stream_open_fails(hass): stream.add_provider(STREAM_OUTPUT_FORMAT) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") - stream_worker(hass, stream, threading.Event()) + stream_worker(STREAM_SOURCE, {}, stream.outputs, threading.Event()) await hass.async_block_till_done() av_open.assert_called_once() From 68809e9f43699b2ae414954b6cab0456dc8e40a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 15 Feb 2021 20:08:08 +0100 Subject: [PATCH 0494/1818] Fix issue with timeout and error response (#46584) --- homeassistant/components/hassio/websocket_api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index 851404b4b0ee6f..d2c0bc9ed104cf 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -61,7 +61,7 @@ async def websocket_supervisor_event( vol.Required(ATTR_ENDPOINT): cv.string, vol.Required(ATTR_METHOD): cv.string, vol.Optional(ATTR_DATA): dict, - vol.Optional(ATTR_TIMEOUT): cv.string, + vol.Optional(ATTR_TIMEOUT): vol.Any(cv.Number, None), } ) async def websocket_supervisor_api( @@ -79,6 +79,8 @@ async def websocket_supervisor_api( ) except hass.components.hassio.HassioAPIError as err: _LOGGER.error("Failed to to call %s - %s", msg[ATTR_ENDPOINT], err) - connection.send_error(msg[WS_ID], err) + connection.send_error( + msg[WS_ID], code=websocket_api.ERR_UNKNOWN_ERROR, message=str(err) + ) else: - connection.send_result(msg[WS_ID], result[ATTR_DATA]) + connection.send_result(msg[WS_ID], result.get(ATTR_DATA, {})) From 2f9fda73f4e0f843c7a3b48ce7c33beac6cbcab8 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 15 Feb 2021 20:11:27 +0100 Subject: [PATCH 0495/1818] Add config flow to Xiaomi Miio switch (#46179) --- .coveragerc | 1 + .../components/xiaomi_miio/__init__.py | 36 ++- .../components/xiaomi_miio/config_flow.py | 148 +++++---- homeassistant/components/xiaomi_miio/const.py | 21 ++ .../components/xiaomi_miio/device.py | 87 ++++++ homeassistant/components/xiaomi_miio/light.py | 13 +- .../components/xiaomi_miio/sensor.py | 3 +- .../components/xiaomi_miio/strings.json | 15 +- .../components/xiaomi_miio/switch.py | 281 +++++++++--------- .../xiaomi_miio/translations/en.json | 19 +- .../xiaomi_miio/test_config_flow.py | 252 ++++++++++++---- 11 files changed, 572 insertions(+), 304 deletions(-) create mode 100644 homeassistant/components/xiaomi_miio/device.py diff --git a/.coveragerc b/.coveragerc index 17dd078d15b952..e64bfab280a46f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1085,6 +1085,7 @@ omit = homeassistant/components/xiaomi_miio/__init__.py homeassistant/components/xiaomi_miio/air_quality.py homeassistant/components/xiaomi_miio/alarm_control_panel.py + homeassistant/components/xiaomi_miio/device.py homeassistant/components/xiaomi_miio/device_tracker.py homeassistant/components/xiaomi_miio/fan.py homeassistant/components/xiaomi_miio/gateway.py diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 7ff1ed999c46c0..e81c35d39e4bf2 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -3,11 +3,18 @@ from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.helpers import device_registry as dr -from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY -from .const import DOMAIN +from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + CONF_GATEWAY, + CONF_MODEL, + DOMAIN, + MODELS_SWITCH, +) from .gateway import ConnectXiaomiGateway GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] +SWITCH_PLATFORMS = ["switch"] async def async_setup(hass: core.HomeAssistant, config: dict): @@ -19,10 +26,13 @@ async def async_setup_entry( hass: core.HomeAssistant, entry: config_entries.ConfigEntry ): """Set up the Xiaomi Miio components from a config entry.""" - hass.data[DOMAIN] = {} + hass.data.setdefault(DOMAIN, {}) if entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: if not await async_setup_gateway_entry(hass, entry): return False + if entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + if not await async_setup_device_entry(hass, entry): + return False return True @@ -67,3 +77,23 @@ async def async_setup_gateway_entry( ) return True + + +async def async_setup_device_entry( + hass: core.HomeAssistant, entry: config_entries.ConfigEntry +): + """Set up the Xiaomi Miio device component from a config entry.""" + model = entry.data[CONF_MODEL] + + # Identify platforms to setup + if model in MODELS_SWITCH: + platforms = SWITCH_PLATFORMS + else: + return False + + for component in platforms: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 6ed5f422f7cecd..2a1532eaf9b813 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -8,24 +8,27 @@ from homeassistant.helpers.device_registry import format_mac # pylint: disable=unused-import -from .const import DOMAIN -from .gateway import ConnectXiaomiGateway +from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + CONF_GATEWAY, + CONF_MAC, + CONF_MODEL, + DOMAIN, + MODELS_GATEWAY, + MODELS_SWITCH, +) +from .device import ConnectXiaomiDevice _LOGGER = logging.getLogger(__name__) -CONF_FLOW_TYPE = "config_flow_device" -CONF_GATEWAY = "gateway" DEFAULT_GATEWAY_NAME = "Xiaomi Gateway" -ZEROCONF_GATEWAY = "lumi-gateway" -ZEROCONF_ACPARTNER = "lumi-acpartner" +DEFAULT_DEVICE_NAME = "Xiaomi Device" -GATEWAY_SETTINGS = { +DEVICE_SETTINGS = { vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), - vol.Optional(CONF_NAME, default=DEFAULT_GATEWAY_NAME): str, } -GATEWAY_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(GATEWAY_SETTINGS) - -CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_GATEWAY, default=False): bool}) +DEVICE_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(DEVICE_SETTINGS) class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -38,19 +41,13 @@ def __init__(self): """Initialize.""" self.host = None + async def async_step_import(self, conf: dict): + """Import a configuration from config.yaml.""" + return await self.async_step_device(user_input=conf) + async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" - errors = {} - if user_input is not None: - # Check which device needs to be connected. - if user_input[CONF_GATEWAY]: - return await self.async_step_gateway() - - errors["base"] = "no_device_selected" - - return self.async_show_form( - step_id="user", data_schema=CONFIG_SCHEMA, errors=errors - ) + return await self.async_step_device() async def async_step_zeroconf(self, discovery_info): """Handle zeroconf discovery.""" @@ -62,16 +59,28 @@ async def async_step_zeroconf(self, discovery_info): return self.async_abort(reason="not_xiaomi_miio") # Check which device is discovered. - if name.startswith(ZEROCONF_GATEWAY) or name.startswith(ZEROCONF_ACPARTNER): - unique_id = format_mac(mac_address) - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured({CONF_HOST: self.host}) + for gateway_model in MODELS_GATEWAY: + if name.startswith(gateway_model.replace(".", "-")): + unique_id = format_mac(mac_address) + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured({CONF_HOST: self.host}) + + self.context.update( + {"title_placeholders": {"name": f"Gateway {self.host}"}} + ) - self.context.update( - {"title_placeholders": {"name": f"Gateway {self.host}"}} - ) + return await self.async_step_device() + for switch_model in MODELS_SWITCH: + if name.startswith(switch_model.replace(".", "-")): + unique_id = format_mac(mac_address) + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured({CONF_HOST: self.host}) - return await self.async_step_gateway() + self.context.update( + {"title_placeholders": {"name": f"Miio Device {self.host}"}} + ) + + return await self.async_step_device() # Discovered device is not yet supported _LOGGER.debug( @@ -81,42 +90,63 @@ async def async_step_zeroconf(self, discovery_info): ) return self.async_abort(reason="not_xiaomi_miio") - async def async_step_gateway(self, user_input=None): - """Handle a flow initialized by the user to configure a gateway.""" + async def async_step_device(self, user_input=None): + """Handle a flow initialized by the user to configure a xiaomi miio device.""" errors = {} if user_input is not None: token = user_input[CONF_TOKEN] if user_input.get(CONF_HOST): self.host = user_input[CONF_HOST] - # Try to connect to a Xiaomi Gateway. - connect_gateway_class = ConnectXiaomiGateway(self.hass) - await connect_gateway_class.async_connect_gateway(self.host, token) - gateway_info = connect_gateway_class.gateway_info - - if gateway_info is not None: - mac = format_mac(gateway_info.mac_address) - unique_id = mac - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() - return self.async_create_entry( - title=user_input[CONF_NAME], - data={ - CONF_FLOW_TYPE: CONF_GATEWAY, - CONF_HOST: self.host, - CONF_TOKEN: token, - "model": gateway_info.model, - "mac": mac, - }, - ) - - errors["base"] = "cannot_connect" + # Try to connect to a Xiaomi Device. + connect_device_class = ConnectXiaomiDevice(self.hass) + await connect_device_class.async_connect_device(self.host, token) + device_info = connect_device_class.device_info + + if device_info is not None: + # Setup Gateways + for gateway_model in MODELS_GATEWAY: + if device_info.model.startswith(gateway_model): + mac = format_mac(device_info.mac_address) + unique_id = mac + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=DEFAULT_GATEWAY_NAME, + data={ + CONF_FLOW_TYPE: CONF_GATEWAY, + CONF_HOST: self.host, + CONF_TOKEN: token, + CONF_MODEL: device_info.model, + CONF_MAC: mac, + }, + ) + + # Setup all other Miio Devices + name = user_input.get(CONF_NAME, DEFAULT_DEVICE_NAME) + + if device_info.model in MODELS_SWITCH: + mac = format_mac(device_info.mac_address) + unique_id = mac + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name, + data={ + CONF_FLOW_TYPE: CONF_DEVICE, + CONF_HOST: self.host, + CONF_TOKEN: token, + CONF_MODEL: device_info.model, + CONF_MAC: mac, + }, + ) + errors["base"] = "unknown_device" + else: + errors["base"] = "cannot_connect" if self.host: - schema = vol.Schema(GATEWAY_SETTINGS) + schema = vol.Schema(DEVICE_SETTINGS) else: - schema = GATEWAY_CONFIG + schema = DEVICE_CONFIG - return self.async_show_form( - step_id="gateway", data_schema=schema, errors=errors - ) + return self.async_show_form(step_id="device", data_schema=schema, errors=errors) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 8de68cda97f602..3726f7f709dbca 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -1,6 +1,27 @@ """Constants for the Xiaomi Miio component.""" DOMAIN = "xiaomi_miio" +CONF_FLOW_TYPE = "config_flow_device" +CONF_GATEWAY = "gateway" +CONF_DEVICE = "device" +CONF_MODEL = "model" +CONF_MAC = "mac" + +MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] +MODELS_SWITCH = [ + "chuangmi.plug.v1", + "chuangmi.plug.v3", + "chuangmi.plug.hmi208", + "qmi.powerstrip.v1", + "zimi.powerstrip.v2", + "chuangmi.plug.m1", + "chuangmi.plug.m3", + "chuangmi.plug.v2", + "chuangmi.plug.hmi205", + "chuangmi.plug.hmi206", + "lumi.acpartner.v3", +] + # Fan Services SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on" SERVICE_SET_BUZZER_OFF = "fan_set_buzzer_off" diff --git a/homeassistant/components/xiaomi_miio/device.py b/homeassistant/components/xiaomi_miio/device.py new file mode 100644 index 00000000000000..48bedbf0cc8ff3 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/device.py @@ -0,0 +1,87 @@ +"""Code to handle a Xiaomi Device.""" +import logging + +from miio import Device, DeviceException + +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.entity import Entity + +from .const import CONF_MAC, CONF_MODEL, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class ConnectXiaomiDevice: + """Class to async connect to a Xiaomi Device.""" + + def __init__(self, hass): + """Initialize the entity.""" + self._hass = hass + self._device = None + self._device_info = None + + @property + def device(self): + """Return the class containing all connections to the device.""" + return self._device + + @property + def device_info(self): + """Return the class containing device info.""" + return self._device_info + + async def async_connect_device(self, host, token): + """Connect to the Xiaomi Device.""" + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + try: + self._device = Device(host, token) + # get the device info + self._device_info = await self._hass.async_add_executor_job( + self._device.info + ) + except DeviceException: + _LOGGER.error( + "DeviceException during setup of xiaomi device with host %s", host + ) + return False + _LOGGER.debug( + "%s %s %s detected", + self._device_info.model, + self._device_info.firmware_version, + self._device_info.hardware_version, + ) + return True + + +class XiaomiMiioEntity(Entity): + """Representation of a base Xiaomi Miio Entity.""" + + def __init__(self, name, device, entry, unique_id): + """Initialize the Xiaomi Miio Device.""" + self._device = device + self._model = entry.data[CONF_MODEL] + self._mac = entry.data[CONF_MAC] + self._device_id = entry.unique_id + self._unique_id = unique_id + self._name = name + + @property + def unique_id(self): + """Return an unique ID.""" + return self._unique_id + + @property + def name(self): + """Return the name of this entity, if any.""" + return self._name + + @property + def device_info(self): + """Return the device info.""" + return { + "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac)}, + "identifiers": {(DOMAIN, self._device_id)}, + "manufacturer": "Xiaomi", + "name": self._name, + "model": self._model, + } diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index d1746fcd889be9..efe67a370c4a61 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -6,14 +6,8 @@ import logging from math import ceil -from miio import ( # pylint: disable=import-error - Ceil, - Device, - DeviceException, - PhilipsBulb, - PhilipsEyecare, - PhilipsMoonlight, -) +from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight +from miio import Device # pylint: disable=import-error from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, @@ -37,8 +31,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.util import color, dt -from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY from .const import ( + CONF_FLOW_TYPE, + CONF_GATEWAY, DOMAIN, SERVICE_EYECARE_MODE_OFF, SERVICE_EYECARE_MODE_ON, diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index d20c2dfac1e2ac..ab4df8cd98225f 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -31,8 +31,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY -from .const import DOMAIN +from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 68536de76e51a5..1ab0c6f51c635d 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -2,26 +2,19 @@ "config": { "flow_title": "Xiaomi Miio: {name}", "step": { - "user": { - "title": "Xiaomi Miio", - "description": "Select to which device you want to connect.", - "data": { - "gateway": "Connect to a Xiaomi Gateway" - } - }, - "gateway": { - "title": "Connect to a Xiaomi Gateway", + "device": { + "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway", "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", "data": { "host": "[%key:common::config_flow::data::ip%]", "token": "[%key:common::config_flow::data::api_token%]", - "name": "Name of the Gateway" + "name": "Name of the device" } } }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "no_device_selected": "No device selected, please select one device." + "unknown_device": "The device model is not known, not able to setup the device using config flow." }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index b9e90cc5c23b84..3cc95572e6cc86 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -3,17 +3,13 @@ from functools import partial import logging -from miio import ( # pylint: disable=import-error - AirConditioningCompanionV3, - ChuangmiPlug, - Device, - DeviceException, - PowerStrip, -) +from miio import AirConditioningCompanionV3 # pylint: disable=import-error +from miio import ChuangmiPlug, DeviceException, PowerStrip from miio.powerstrip import PowerMode # pylint: disable=import-error import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, @@ -21,23 +17,25 @@ CONF_NAME, CONF_TOKEN, ) -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + CONF_MODEL, DOMAIN, SERVICE_SET_POWER_MODE, SERVICE_SET_POWER_PRICE, SERVICE_SET_WIFI_LED_OFF, SERVICE_SET_WIFI_LED_ON, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Switch" DATA_KEY = "switch.xiaomi_miio" -CONF_MODEL = "model" MODEL_POWER_STRIP_V2 = "zimi.powerstrip.v2" MODEL_PLUG_V3 = "chuangmi.plug.v3" @@ -114,119 +112,124 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the switch from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - model = config.get(CONF_MODEL) - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - devices = [] - unique_id = None - - if model is None: - try: - miio_device = Device(host, token) - device_info = await hass.async_add_executor_job(miio_device.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Switch via platform setup is deprecated. Please remove it from your configuration." + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the switch from a config entry.""" + entities = [] + + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + if DATA_KEY not in hass.data: + hass.data[DATA_KEY] = {} + + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]: + plug = ChuangmiPlug(host, token, model=model) + + # The device has two switchable channels (mains and a USB port). + # A switch device per channel will be created. + for channel_usb in [True, False]: + if channel_usb: + unique_id_ch = f"{unique_id}-USB" + else: + unique_id_ch = f"{unique_id}-mains" + device = ChuangMiPlugSwitch( + name, plug, config_entry, unique_id_ch, channel_usb + ) + entities.append(device) + hass.data[DATA_KEY][host] = device + elif model in ["qmi.powerstrip.v1", "zimi.powerstrip.v2"]: + plug = PowerStrip(host, token, model=model) + device = XiaomiPowerStripSwitch(name, plug, config_entry, unique_id) + entities.append(device) + hass.data[DATA_KEY][host] = device + elif model in [ + "chuangmi.plug.m1", + "chuangmi.plug.m3", + "chuangmi.plug.v2", + "chuangmi.plug.hmi205", + "chuangmi.plug.hmi206", + ]: + plug = ChuangmiPlug(host, token, model=model) + device = XiaomiPlugGenericSwitch(name, plug, config_entry, unique_id) + entities.append(device) + hass.data[DATA_KEY][host] = device + elif model in ["lumi.acpartner.v3"]: + plug = AirConditioningCompanionV3(host, token) + device = XiaomiAirConditioningCompanionSwitch( + name, plug, config_entry, unique_id + ) + entities.append(device) + hass.data[DATA_KEY][host] = device + else: + _LOGGER.error( + "Unsupported device found! Please create an issue at " + "https://github.com/rytilahti/python-miio/issues " + "and provide the following data: %s", model, - device_info.firmware_version, - device_info.hardware_version, ) - except DeviceException as ex: - raise PlatformNotReady from ex - - if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]: - plug = ChuangmiPlug(host, token, model=model) - # The device has two switchable channels (mains and a USB port). - # A switch device per channel will be created. - for channel_usb in [True, False]: - device = ChuangMiPlugSwitch(name, plug, model, unique_id, channel_usb) - devices.append(device) - hass.data[DATA_KEY][host] = device + async def async_service_handler(service): + """Map services to methods on XiaomiPlugGenericSwitch.""" + method = SERVICE_TO_METHOD.get(service.service) + params = { + key: value + for key, value in service.data.items() + if key != ATTR_ENTITY_ID + } + entity_ids = service.data.get(ATTR_ENTITY_ID) + if entity_ids: + devices = [ + device + for device in hass.data[DATA_KEY].values() + if device.entity_id in entity_ids + ] + else: + devices = hass.data[DATA_KEY].values() + + update_tasks = [] + for device in devices: + if not hasattr(device, method["method"]): + continue + await getattr(device, method["method"])(**params) + update_tasks.append(device.async_update_ha_state(True)) + + if update_tasks: + await asyncio.wait(update_tasks) + + for plug_service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[plug_service].get("schema", SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, plug_service, async_service_handler, schema=schema + ) - elif model in ["qmi.powerstrip.v1", "zimi.powerstrip.v2"]: - plug = PowerStrip(host, token, model=model) - device = XiaomiPowerStripSwitch(name, plug, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model in [ - "chuangmi.plug.m1", - "chuangmi.plug.m3", - "chuangmi.plug.v2", - "chuangmi.plug.hmi205", - "chuangmi.plug.hmi206", - ]: - plug = ChuangmiPlug(host, token, model=model) - device = XiaomiPlugGenericSwitch(name, plug, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model in ["lumi.acpartner.v3"]: - plug = AirConditioningCompanionV3(host, token) - device = XiaomiAirConditioningCompanionSwitch(name, plug, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - else: - _LOGGER.error( - "Unsupported device found! Please create an issue at " - "https://github.com/rytilahti/python-miio/issues " - "and provide the following data: %s", - model, - ) - return False - - async_add_entities(devices, update_before_add=True) - - async def async_service_handler(service): - """Map services to methods on XiaomiPlugGenericSwitch.""" - method = SERVICE_TO_METHOD.get(service.service) - params = { - key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID - } - entity_ids = service.data.get(ATTR_ENTITY_ID) - if entity_ids: - devices = [ - device - for device in hass.data[DATA_KEY].values() - if device.entity_id in entity_ids - ] - else: - devices = hass.data[DATA_KEY].values() - - update_tasks = [] - for device in devices: - if not hasattr(device, method["method"]): - continue - await getattr(device, method["method"])(**params) - update_tasks.append(device.async_update_ha_state(True)) - - if update_tasks: - await asyncio.wait(update_tasks) - - for plug_service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[plug_service].get("schema", SERVICE_SCHEMA) - hass.services.async_register( - DOMAIN, plug_service, async_service_handler, schema=schema - ) + async_add_entities(entities, update_before_add=True) -class XiaomiPlugGenericSwitch(SwitchEntity): +class XiaomiPlugGenericSwitch(XiaomiMiioEntity, SwitchEntity): """Representation of a Xiaomi Plug Generic.""" - def __init__(self, name, plug, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the plug switch.""" - self._name = name - self._plug = plug - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._icon = "mdi:power-socket" self._available = False @@ -235,16 +238,6 @@ def __init__(self, name, plug, model, unique_id): self._device_features = FEATURE_FLAGS_GENERIC self._skip_update = False - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the device if any.""" - return self._name - @property def icon(self): """Return the icon to use for device if any.""" @@ -288,7 +281,7 @@ async def _try_command(self, mask_error, func, *args, **kwargs): async def async_turn_on(self, **kwargs): """Turn the plug on.""" - result = await self._try_command("Turning the plug on failed.", self._plug.on) + result = await self._try_command("Turning the plug on failed", self._device.on) if result: self._state = True @@ -296,7 +289,9 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): """Turn the plug off.""" - result = await self._try_command("Turning the plug off failed.", self._plug.off) + result = await self._try_command( + "Turning the plug off failed", self._device.off + ) if result: self._state = False @@ -310,7 +305,7 @@ async def async_update(self): return try: - state = await self.hass.async_add_executor_job(self._plug.status) + state = await self.hass.async_add_executor_job(self._device.status) _LOGGER.debug("Got new state: %s", state) self._available = True @@ -328,7 +323,7 @@ async def async_set_wifi_led_on(self): return await self._try_command( - "Turning the wifi led on failed.", self._plug.set_wifi_led, True + "Turning the wifi led on failed", self._device.set_wifi_led, True ) async def async_set_wifi_led_off(self): @@ -337,7 +332,7 @@ async def async_set_wifi_led_off(self): return await self._try_command( - "Turning the wifi led off failed.", self._plug.set_wifi_led, False + "Turning the wifi led off failed", self._device.set_wifi_led, False ) async def async_set_power_price(self, price: int): @@ -346,8 +341,8 @@ async def async_set_power_price(self, price: int): return await self._try_command( - "Setting the power price of the power strip failed.", - self._plug.set_power_price, + "Setting the power price of the power strip failed", + self._device.set_power_price, price, ) @@ -383,7 +378,7 @@ async def async_update(self): return try: - state = await self.hass.async_add_executor_job(self._plug.status) + state = await self.hass.async_add_executor_job(self._device.status) _LOGGER.debug("Got new state: %s", state) self._available = True @@ -415,8 +410,8 @@ async def async_set_power_mode(self, mode: str): return await self._try_command( - "Setting the power mode of the power strip failed.", - self._plug.set_power_mode, + "Setting the power mode of the power strip failed", + self._device.set_power_mode, PowerMode(mode), ) @@ -424,14 +419,14 @@ async def async_set_power_mode(self, mode: str): class ChuangMiPlugSwitch(XiaomiPlugGenericSwitch): """Representation of a Chuang Mi Plug V1 and V3.""" - def __init__(self, name, plug, model, unique_id, channel_usb): + def __init__(self, name, plug, entry, unique_id, channel_usb): """Initialize the plug switch.""" name = f"{name} USB" if channel_usb else name if unique_id is not None and channel_usb: unique_id = f"{unique_id}-usb" - super().__init__(name, plug, model, unique_id) + super().__init__(name, plug, entry, unique_id) self._channel_usb = channel_usb if self._model == MODEL_PLUG_V3: @@ -444,11 +439,11 @@ async def async_turn_on(self, **kwargs): """Turn a channel on.""" if self._channel_usb: result = await self._try_command( - "Turning the plug on failed.", self._plug.usb_on + "Turning the plug on failed", self._device.usb_on ) else: result = await self._try_command( - "Turning the plug on failed.", self._plug.on + "Turning the plug on failed", self._device.on ) if result: @@ -459,11 +454,11 @@ async def async_turn_off(self, **kwargs): """Turn a channel off.""" if self._channel_usb: result = await self._try_command( - "Turning the plug on failed.", self._plug.usb_off + "Turning the plug off failed", self._device.usb_off ) else: result = await self._try_command( - "Turning the plug on failed.", self._plug.off + "Turning the plug off failed", self._device.off ) if result: @@ -478,7 +473,7 @@ async def async_update(self): return try: - state = await self.hass.async_add_executor_job(self._plug.status) + state = await self.hass.async_add_executor_job(self._device.status) _LOGGER.debug("Got new state: %s", state) self._available = True @@ -513,7 +508,7 @@ def __init__(self, name, plug, model, unique_id): async def async_turn_on(self, **kwargs): """Turn the socket on.""" result = await self._try_command( - "Turning the socket on failed.", self._plug.socket_on + "Turning the socket on failed", self._device.socket_on ) if result: @@ -523,7 +518,7 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): """Turn the socket off.""" result = await self._try_command( - "Turning the socket off failed.", self._plug.socket_off + "Turning the socket off failed", self._device.socket_off ) if result: @@ -538,7 +533,7 @@ async def async_update(self): return try: - state = await self.hass.async_add_executor_job(self._plug.status) + state = await self.hass.async_add_executor_job(self._device.status) _LOGGER.debug("Got new state: %s", state) self._available = True diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 4d39a6d113781d..c8ac63d1ea725d 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -6,25 +6,18 @@ }, "error": { "cannot_connect": "Failed to connect", - "no_device_selected": "No device selected, please select one device." + "unknown_device": "The device model is not known, not able to setup the device using config flow." }, "flow_title": "Xiaomi Miio: {name}", "step": { - "gateway": { + "device": { "data": { - "host": "IP Address", - "name": "Name of the Gateway", - "token": "API Token" + "host": "IP Address", + "name": "Name of the device", + "token": "API Token" }, "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", - "title": "Connect to a Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Connect to a Xiaomi Gateway" - }, - "description": "Select to which device you want to connect.", - "title": "Xiaomi Miio" + "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" } } } diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index dbe78957586ae5..220c51034f1766 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -5,7 +5,11 @@ from homeassistant import config_entries from homeassistant.components import zeroconf -from homeassistant.components.xiaomi_miio import config_flow, const +from homeassistant.components.xiaomi_miio import const +from homeassistant.components.xiaomi_miio.config_flow import ( + DEFAULT_DEVICE_NAME, + DEFAULT_GATEWAY_NAME, +) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN ZEROCONF_NAME = "name" @@ -15,7 +19,7 @@ TEST_HOST = "1.2.3.4" TEST_TOKEN = "12345678901234567890123456789012" TEST_NAME = "Test_Gateway" -TEST_MODEL = "model5" +TEST_MODEL = const.MODELS_GATEWAY[0] TEST_MAC = "ab:cd:ef:gh:ij:kl" TEST_GATEWAY_ID = TEST_MAC TEST_HARDWARE_VERSION = "AB123" @@ -40,26 +44,6 @@ def get_mock_info( return gateway_info -async def test_config_flow_step_user_no_device(hass): - """Test config flow, user step with no device selected.""" - result = await hass.config_entries.flow.async_init( - const.DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {"base": "no_device_selected"} - - async def test_config_flow_step_gateway_connect_error(hass): """Test config flow, gateway connection error.""" result = await hass.config_entries.flow.async_init( @@ -67,29 +51,20 @@ async def test_config_flow_step_gateway_connect_error(hass): ) assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {config_flow.CONF_GATEWAY: True}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "gateway" + assert result["step_id"] == "device" assert result["errors"] == {} with patch( - "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info", + "homeassistant.components.xiaomi_miio.device.Device.info", side_effect=DeviceException({}), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: TEST_HOST, CONF_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN}, + {CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, ) assert result["type"] == "form" - assert result["step_id"] == "gateway" + assert result["step_id"] == "device" assert result["errors"] == {"base": "cannot_connect"} @@ -100,42 +75,30 @@ async def test_config_flow_gateway_success(hass): ) assert result["type"] == "form" - assert result["step_id"] == "user" - assert result["errors"] == {} - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {config_flow.CONF_GATEWAY: True}, - ) - - assert result["type"] == "form" - assert result["step_id"] == "gateway" + assert result["step_id"] == "device" assert result["errors"] == {} mock_info = get_mock_info() with patch( - "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info", + "homeassistant.components.xiaomi_miio.device.Device.info", return_value=mock_info, - ), patch( - "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.discover_devices", - return_value=TEST_SUB_DEVICE_LIST, ), patch( "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: TEST_HOST, CONF_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN}, + {CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, ) assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME + assert result["title"] == DEFAULT_GATEWAY_NAME assert result["data"] == { - config_flow.CONF_FLOW_TYPE: config_flow.CONF_GATEWAY, + const.CONF_FLOW_TYPE: const.CONF_GATEWAY, CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN, - "model": TEST_MODEL, - "mac": TEST_MAC, + const.CONF_MODEL: TEST_MODEL, + const.CONF_MAC: TEST_MAC, } @@ -152,33 +115,30 @@ async def test_zeroconf_gateway_success(hass): ) assert result["type"] == "form" - assert result["step_id"] == "gateway" + assert result["step_id"] == "device" assert result["errors"] == {} mock_info = get_mock_info() with patch( - "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info", + "homeassistant.components.xiaomi_miio.device.Device.info", return_value=mock_info, - ), patch( - "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.discover_devices", - return_value=TEST_SUB_DEVICE_LIST, ), patch( "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN}, + {CONF_TOKEN: TEST_TOKEN}, ) assert result["type"] == "create_entry" - assert result["title"] == TEST_NAME + assert result["title"] == DEFAULT_GATEWAY_NAME assert result["data"] == { - config_flow.CONF_FLOW_TYPE: config_flow.CONF_GATEWAY, + const.CONF_FLOW_TYPE: const.CONF_GATEWAY, CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN, - "model": TEST_MODEL, - "mac": TEST_MAC, + const.CONF_MODEL: TEST_MODEL, + const.CONF_MAC: TEST_MAC, } @@ -218,3 +178,167 @@ async def test_zeroconf_missing_data(hass): assert result["type"] == "abort" assert result["reason"] == "not_xiaomi_miio" + + +async def test_config_flow_step_device_connect_error(hass): + """Test config flow, device connection error.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {} + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + side_effect=DeviceException({}), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_config_flow_step_unknown_device(hass): + """Test config flow, unknown device error.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {} + + mock_info = get_mock_info(model="UNKNOWN") + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + return_value=mock_info, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {"base": "unknown_device"} + + +async def test_import_flow_success(hass): + """Test a successful import form yaml for a device.""" + mock_info = get_mock_info(model=const.MODELS_SWITCH[0]) + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + return_value=mock_info, + ), patch( + "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_NAME: TEST_NAME, CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + const.CONF_FLOW_TYPE: const.CONF_DEVICE, + CONF_HOST: TEST_HOST, + CONF_TOKEN: TEST_TOKEN, + const.CONF_MODEL: const.MODELS_SWITCH[0], + const.CONF_MAC: TEST_MAC, + } + + +async def config_flow_device_success(hass, model_to_test): + """Test a successful config flow for a device (base class).""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {} + + mock_info = get_mock_info(model=model_to_test) + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + return_value=mock_info, + ), patch( + "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == DEFAULT_DEVICE_NAME + assert result["data"] == { + const.CONF_FLOW_TYPE: const.CONF_DEVICE, + CONF_HOST: TEST_HOST, + CONF_TOKEN: TEST_TOKEN, + const.CONF_MODEL: model_to_test, + const.CONF_MAC: TEST_MAC, + } + + +async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): + """Test a successful zeroconf discovery of a device (base class).""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={ + zeroconf.ATTR_HOST: TEST_HOST, + ZEROCONF_NAME: zeroconf_name_to_test, + ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {} + + mock_info = get_mock_info(model=model_to_test) + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + return_value=mock_info, + ), patch( + "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == DEFAULT_DEVICE_NAME + assert result["data"] == { + const.CONF_FLOW_TYPE: const.CONF_DEVICE, + CONF_HOST: TEST_HOST, + CONF_TOKEN: TEST_TOKEN, + const.CONF_MODEL: model_to_test, + const.CONF_MAC: TEST_MAC, + } + + +async def test_config_flow_plug_success(hass): + """Test a successful config flow for a plug.""" + test_plug_model = const.MODELS_SWITCH[0] + await config_flow_device_success(hass, test_plug_model) + + +async def test_zeroconf_plug_success(hass): + """Test a successful zeroconf discovery of a plug.""" + test_plug_model = const.MODELS_SWITCH[0] + test_zeroconf_name = const.MODELS_SWITCH[0].replace(".", "-") + await zeroconf_device_success(hass, test_zeroconf_name, test_plug_model) From e3ae3cfb83afa94ae06155c20a41c0802e679def Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Mon, 15 Feb 2021 14:24:42 -0500 Subject: [PATCH 0496/1818] Upgrade blinkpy to 0.17.0 (#46581) --- homeassistant/components/blink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index 17d737bcaf3418..1c91f1a2295a26 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -2,7 +2,7 @@ "domain": "blink", "name": "Blink", "documentation": "https://www.home-assistant.io/integrations/blink", - "requirements": ["blinkpy==0.16.4"], + "requirements": ["blinkpy==0.17.0"], "codeowners": ["@fronzbot"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 342849b291c7d6..86a15b1288f993 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -351,7 +351,7 @@ bizkaibus==0.1.1 blebox_uniapi==1.3.2 # homeassistant.components.blink -blinkpy==0.16.4 +blinkpy==0.17.0 # homeassistant.components.blinksticklight blinkstick==1.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7b49c63f3c5ad0..9d057cc414a796 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -201,7 +201,7 @@ bimmer_connected==0.7.14 blebox_uniapi==1.3.2 # homeassistant.components.blink -blinkpy==0.16.4 +blinkpy==0.17.0 # homeassistant.components.bond bond-api==0.1.9 From 6f4df7e52e18159ab412df4d16deaad54dd84599 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 15 Feb 2021 10:35:58 -1000 Subject: [PATCH 0497/1818] Use shared clientsession for sense (#46419) --- homeassistant/components/emulated_kasa/manifest.json | 2 +- homeassistant/components/sense/__init__.py | 7 ++++++- homeassistant/components/sense/config_flow.py | 6 +++++- homeassistant/components/sense/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index c3458093943c63..bb292b2e7b5886 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.8.1"], + "requirements": ["sense_energy==0.9.0"], "codeowners": ["@kbickar"], "quality_scale": "internal" } diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 114c64c390b938..2d6c0c41e5bd19 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -14,6 +14,7 @@ from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT 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.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval @@ -96,7 +97,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): password = entry_data[CONF_PASSWORD] timeout = entry_data[CONF_TIMEOUT] - gateway = ASyncSenseable(api_timeout=timeout, wss_timeout=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 try: diff --git a/homeassistant/components/sense/config_flow.py b/homeassistant/components/sense/config_flow.py index f8b8ede6a4c5fc..866c1683b1e1e6 100644 --- a/homeassistant/components/sense/config_flow.py +++ b/homeassistant/components/sense/config_flow.py @@ -6,6 +6,7 @@ from homeassistant import config_entries, core from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ACTIVE_UPDATE_RATE, DEFAULT_TIMEOUT, SENSE_TIMEOUT_EXCEPTIONS from .const import DOMAIN # pylint:disable=unused-import; pylint:disable=unused-import @@ -27,8 +28,11 @@ async def validate_input(hass: core.HomeAssistant, data): 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) + 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]) diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index bd132f1f983021..57028ccb395a69 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.8.1"], + "requirements": ["sense_energy==0.9.0"], "codeowners": ["@kbickar"], "config_flow": true, "dhcp": [{"hostname":"sense-*","macaddress":"009D6B*"}, {"hostname":"sense-*","macaddress":"DCEFCA*"}] diff --git a/requirements_all.txt b/requirements_all.txt index 86a15b1288f993..a56447ee251096 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2010,7 +2010,7 @@ sense-hat==2.2.0 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense_energy==0.8.1 +sense_energy==0.9.0 # homeassistant.components.sentry sentry-sdk==0.20.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9d057cc414a796..1cec8c9a3c2707 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1026,7 +1026,7 @@ scapy==2.4.4 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense_energy==0.8.1 +sense_energy==0.9.0 # homeassistant.components.sentry sentry-sdk==0.20.1 From ea47e5d8af49de4cdaf2908b010207a2536e1238 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Feb 2021 22:02:58 +0100 Subject: [PATCH 0498/1818] Upgrade sentry-sdk to 0.20.2 (#46590) --- 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 090d19eb2fc49b..4c823362aee78b 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,6 +3,6 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==0.20.1"], + "requirements": ["sentry-sdk==0.20.2"], "codeowners": ["@dcramer", "@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index a56447ee251096..c31abaa6c1f5f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2013,7 +2013,7 @@ sense-hat==2.2.0 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.1 +sentry-sdk==0.20.2 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1cec8c9a3c2707..66faad75ed7435 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1029,7 +1029,7 @@ scapy==2.4.4 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.1 +sentry-sdk==0.20.2 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From 2a7d2868bea58a2571815ce0096474c10459bc1a Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 15 Feb 2021 18:14:27 -0500 Subject: [PATCH 0499/1818] Use core constants for xiaomi_aqara (#46551) --- homeassistant/components/xiaomi_aqara/__init__.py | 2 +- homeassistant/components/xiaomi_aqara/config_flow.py | 3 +-- homeassistant/components/xiaomi_aqara/const.py | 1 - tests/components/xiaomi_aqara/test_config_flow.py | 12 ++++++------ 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index c5b74e68af5839..f54c262abba493 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -13,6 +13,7 @@ CONF_HOST, CONF_MAC, CONF_PORT, + CONF_PROTOCOL, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import callback @@ -26,7 +27,6 @@ from .const import ( CONF_INTERFACE, CONF_KEY, - CONF_PROTOCOL, CONF_SID, DEFAULT_DISCOVERY_RETRY, DOMAIN, diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index 46d4852abae24f..8028d16f86a2ac 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -6,7 +6,7 @@ from xiaomi_gateway import MULTICAST_PORT, XiaomiGateway, XiaomiGatewayDiscovery from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL from homeassistant.core import callback from homeassistant.helpers.device_registry import format_mac @@ -14,7 +14,6 @@ from .const import ( CONF_INTERFACE, CONF_KEY, - CONF_PROTOCOL, CONF_SID, DEFAULT_DISCOVERY_RETRY, DOMAIN, diff --git a/homeassistant/components/xiaomi_aqara/const.py b/homeassistant/components/xiaomi_aqara/const.py index 1cc3b2d4633e95..11706cdb6fb165 100644 --- a/homeassistant/components/xiaomi_aqara/const.py +++ b/homeassistant/components/xiaomi_aqara/const.py @@ -9,7 +9,6 @@ ZEROCONF_ACPARTNER = "lumi-acpartner" CONF_INTERFACE = "interface" -CONF_PROTOCOL = "protocol" CONF_KEY = "key" CONF_SID = "sid" diff --git a/tests/components/xiaomi_aqara/test_config_flow.py b/tests/components/xiaomi_aqara/test_config_flow.py index 280775a7130952..f52f9b8e64d7f4 100644 --- a/tests/components/xiaomi_aqara/test_config_flow.py +++ b/tests/components/xiaomi_aqara/test_config_flow.py @@ -7,7 +7,7 @@ from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.components.xiaomi_aqara import config_flow, const -from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL ZEROCONF_NAME = "name" ZEROCONF_PROP = "properties" @@ -107,7 +107,7 @@ async def test_config_flow_user_success(hass): CONF_PORT: TEST_PORT, CONF_MAC: TEST_MAC, const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, - const.CONF_PROTOCOL: TEST_PROTOCOL, + CONF_PROTOCOL: TEST_PROTOCOL, const.CONF_KEY: TEST_KEY, const.CONF_SID: TEST_SID, } @@ -159,7 +159,7 @@ async def test_config_flow_user_multiple_success(hass): CONF_PORT: TEST_PORT, CONF_MAC: TEST_MAC, const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, - const.CONF_PROTOCOL: TEST_PROTOCOL, + CONF_PROTOCOL: TEST_PROTOCOL, const.CONF_KEY: TEST_KEY, const.CONF_SID: TEST_SID, } @@ -196,7 +196,7 @@ async def test_config_flow_user_no_key_success(hass): CONF_PORT: TEST_PORT, CONF_MAC: TEST_MAC, const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, - const.CONF_PROTOCOL: TEST_PROTOCOL, + CONF_PROTOCOL: TEST_PROTOCOL, const.CONF_KEY: None, const.CONF_SID: TEST_SID, } @@ -243,7 +243,7 @@ async def test_config_flow_user_host_mac_success(hass): CONF_PORT: TEST_PORT, CONF_MAC: TEST_MAC, const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, - const.CONF_PROTOCOL: TEST_PROTOCOL, + CONF_PROTOCOL: TEST_PROTOCOL, const.CONF_KEY: None, const.CONF_SID: TEST_SID, } @@ -433,7 +433,7 @@ async def test_zeroconf_success(hass): CONF_PORT: TEST_PORT, CONF_MAC: TEST_MAC, const.CONF_INTERFACE: config_flow.DEFAULT_INTERFACE, - const.CONF_PROTOCOL: TEST_PROTOCOL, + CONF_PROTOCOL: TEST_PROTOCOL, const.CONF_KEY: TEST_KEY, const.CONF_SID: TEST_SID, } From 1bb535aa6784fcb1af69749ec8eea2a1207456b9 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 16 Feb 2021 00:03:57 +0000 Subject: [PATCH 0500/1818] [ci skip] Translation update --- .../components/abode/translations/it.json | 2 +- .../components/aemet/translations/es.json | 22 +++++++++ .../components/aemet/translations/it.json | 22 +++++++++ .../components/aemet/translations/no.json | 22 +++++++++ .../components/airvisual/translations/de.json | 26 ++++++++++- .../components/airvisual/translations/it.json | 2 +- .../ambiclimate/translations/ru.json | 2 +- .../components/asuswrt/translations/es.json | 17 +++++++ .../components/asuswrt/translations/it.json | 45 +++++++++++++++++++ .../components/asuswrt/translations/no.json | 45 +++++++++++++++++++ .../components/august/translations/it.json | 2 +- .../components/awair/translations/it.json | 2 +- .../azure_devops/translations/it.json | 2 +- .../cloudflare/translations/de.json | 7 ++- .../components/deconz/translations/de.json | 1 + .../fireservicerota/translations/it.json | 2 +- .../components/fritzbox/translations/de.json | 10 ++++- .../components/fritzbox/translations/it.json | 2 +- .../fritzbox_callmonitor/translations/de.json | 41 +++++++++++++++++ .../components/homekit/translations/de.json | 21 ++++++++- .../homekit_controller/translations/de.json | 9 ++++ .../components/hyperion/translations/it.json | 2 +- .../components/icloud/translations/it.json | 4 +- .../components/icloud/translations/ru.json | 2 +- .../keenetic_ndms2/translations/es.json | 20 +++++++++ .../keenetic_ndms2/translations/it.json | 36 +++++++++++++++ .../keenetic_ndms2/translations/no.json | 36 +++++++++++++++ .../keenetic_ndms2/translations/ru.json | 36 +++++++++++++++ .../components/konnected/translations/ru.json | 2 +- .../lutron_caseta/translations/ru.json | 19 ++++++++ .../components/mazda/translations/it.json | 2 +- .../components/neato/translations/it.json | 2 +- .../components/nest/translations/it.json | 4 +- .../philips_js/translations/it.json | 24 ++++++++++ .../components/pi_hole/translations/de.json | 6 +++ .../components/plaato/translations/ru.json | 4 +- .../components/plex/translations/it.json | 2 +- .../components/powerwall/translations/it.json | 8 +++- .../components/sharkiq/translations/it.json | 2 +- .../components/shelly/translations/ru.json | 2 +- .../simplisafe/translations/it.json | 4 +- .../components/sonarr/translations/it.json | 4 +- .../components/spotify/translations/it.json | 2 +- .../components/tesla/translations/it.json | 4 ++ .../components/traccar/translations/ru.json | 2 +- .../components/unifi/translations/it.json | 2 +- .../components/withings/translations/it.json | 2 +- .../xiaomi_aqara/translations/ru.json | 2 +- .../xiaomi_miio/translations/en.json | 23 ++++++++-- .../components/zwave/translations/ru.json | 2 +- 50 files changed, 520 insertions(+), 44 deletions(-) create mode 100644 homeassistant/components/aemet/translations/es.json create mode 100644 homeassistant/components/aemet/translations/it.json create mode 100644 homeassistant/components/aemet/translations/no.json create mode 100644 homeassistant/components/asuswrt/translations/es.json create mode 100644 homeassistant/components/asuswrt/translations/it.json create mode 100644 homeassistant/components/asuswrt/translations/no.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/de.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/es.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/it.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/no.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/ru.json create mode 100644 homeassistant/components/philips_js/translations/it.json diff --git a/homeassistant/components/abode/translations/it.json b/homeassistant/components/abode/translations/it.json index a3e5aa4d7a81b0..6cb571df8e5e9d 100644 --- a/homeassistant/components/abode/translations/it.json +++ b/homeassistant/components/abode/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "reauth_successful": "La riautenticazione ha avuto successo", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { diff --git a/homeassistant/components/aemet/translations/es.json b/homeassistant/components/aemet/translations/es.json new file mode 100644 index 00000000000000..ffe4d524754c05 --- /dev/null +++ b/homeassistant/components/aemet/translations/es.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada" + }, + "error": { + "invalid_api_key": "Clave API no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API", + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nombre de la integraci\u00f3n" + }, + "description": "Configurar la integraci\u00f3n de AEMET OpenData. Para generar la clave API, ve a https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/it.json b/homeassistant/components/aemet/translations/it.json new file mode 100644 index 00000000000000..112630028b9687 --- /dev/null +++ b/homeassistant/components/aemet/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "La posizione \u00e8 gi\u00e0 configurata" + }, + "error": { + "invalid_api_key": "Chiave API non valida" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine", + "name": "Nome dell'integrazione" + }, + "description": "Imposta l'integrazione di AEMET OpenData. Per generare la chiave API, vai su https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/no.json b/homeassistant/components/aemet/translations/no.json new file mode 100644 index 00000000000000..48cbc9916caed8 --- /dev/null +++ b/homeassistant/components/aemet/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Plasseringen er allerede konfigurert" + }, + "error": { + "invalid_api_key": "Ugyldig API-n\u00f8kkel" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "name": "Navnet p\u00e5 integrasjonen" + }, + "description": "Sett opp AEMET OpenData-integrasjon. For \u00e5 generere API-n\u00f8kkel, g\u00e5 til https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/de.json b/homeassistant/components/airvisual/translations/de.json index a16b02915ee2f5..6e2a5f60c6feda 100644 --- a/homeassistant/components/airvisual/translations/de.json +++ b/homeassistant/components/airvisual/translations/de.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "general_error": "Unerwarteter Fehler", - "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "location_not_found": "Standort nicht gefunden" }, "step": { "geography": { @@ -16,8 +17,28 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad" }, + "description": "Verwende die AirVisual Cloud API, um einen geografischen Standort zu \u00fcberwachen.", "title": "Konfigurieren Sie eine Geografie" }, + "geography_by_coords": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + }, + "description": "Verwende die AirVisual Cloud API, um einen L\u00e4ngengrad/Breitengrad zu \u00fcberwachen.", + "title": "Konfiguriere einen Standort" + }, + "geography_by_name": { + "data": { + "api_key": "API-Schl\u00fcssel", + "city": "Stadt", + "country": "Land", + "state": "Bundesland" + }, + "description": "Verwende die AirVisual Cloud API, um ein(e) Stadt/Bundesland/Land zu \u00fcberwachen.", + "title": "Konfiguriere einen Standort" + }, "node_pro": { "data": { "ip_address": "Host", @@ -29,7 +50,8 @@ "reauth_confirm": { "data": { "api_key": "API-Key" - } + }, + "title": "AirVisual erneut authentifizieren" }, "user": { "data": { diff --git a/homeassistant/components/airvisual/translations/it.json b/homeassistant/components/airvisual/translations/it.json index be493669a641fb..3ce45ff1342d0b 100644 --- a/homeassistant/components/airvisual/translations/it.json +++ b/homeassistant/components/airvisual/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "La posizione \u00e8 gi\u00e0 configurata o Node/Pro ID sono gi\u00e0 registrati.", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/ambiclimate/translations/ru.json b/homeassistant/components/ambiclimate/translations/ru.json index 8c8863c0eec34f..a1948c45d0f677 100644 --- a/homeassistant/components/ambiclimate/translations/ru.json +++ b/homeassistant/components/ambiclimate/translations/ru.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 **\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435** \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambi Climate, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**. \n(\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 URL \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 {cb_url})", + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 **\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435** \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambi Climate, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**. \n(\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 {cb_url})", "title": "Ambi Climate" } } diff --git a/homeassistant/components/asuswrt/translations/es.json b/homeassistant/components/asuswrt/translations/es.json new file mode 100644 index 00000000000000..3531e0a3ccaaad --- /dev/null +++ b/homeassistant/components/asuswrt/translations/es.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "mode": "Modo", + "name": "Nombre", + "password": "Contrase\u00f1a", + "port": "Puerto" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/it.json b/homeassistant/components/asuswrt/translations/it.json new file mode 100644 index 00000000000000..d266cabbed4f14 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/it.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_host": "Nome host o indirizzo IP non valido", + "pwd_and_ssh": "Fornire solo la password o il file della chiave SSH", + "pwd_or_ssh": "Si prega di fornire la password o il file della chiave SSH", + "ssh_not_file": "File chiave SSH non trovato", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Modalit\u00e0", + "name": "Nome", + "password": "Password", + "port": "Porta", + "protocol": "Protocollo di comunicazione da utilizzare", + "ssh_key": "Percorso del file della chiave SSH (invece della password)", + "username": "Nome utente" + }, + "description": "Imposta il parametro richiesto per collegarti al tuo router", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Secondi di attesa prima di considerare un dispositivo lontano", + "dnsmasq": "La posizione nel router dei file dnsmasq.leases", + "interface": "L'interfaccia da cui si desidera ottenere statistiche (ad esempio eth0, eth1, ecc.)", + "require_ip": "I dispositivi devono avere un IP (per la modalit\u00e0 punto di accesso)", + "track_unknown": "Tieni traccia dei dispositivi sconosciuti / non denominati" + }, + "title": "Opzioni AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/no.json b/homeassistant/components/asuswrt/translations/no.json new file mode 100644 index 00000000000000..42c9798d49578c --- /dev/null +++ b/homeassistant/components/asuswrt/translations/no.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_host": "Ugyldig vertsnavn eller IP-adresse", + "pwd_and_ssh": "Oppgi bare passord eller SSH-n\u00f8kkelfil", + "pwd_or_ssh": "Oppgi passord eller SSH-n\u00f8kkelfil", + "ssh_not_file": "Finner ikke SSH-n\u00f8kkelfilen", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "mode": "Modus", + "name": "Navn", + "password": "Passord", + "port": "Port", + "protocol": "Kommunikasjonsprotokoll som skal brukes", + "ssh_key": "Bane til SSH-n\u00f8kkelfilen (i stedet for passord)", + "username": "Brukernavn" + }, + "description": "Sett \u00f8nsket parameter for \u00e5 koble til ruteren", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Sekunder \u00e5 vente f\u00f8r du vurderer en enhet borte", + "dnsmasq": "Plasseringen i ruteren til dnsmasq.leases-filene", + "interface": "Grensesnittet du vil ha statistikk fra (f.eks. Eth0, eth1 osv.)", + "require_ip": "Enheter m\u00e5 ha IP (for tilgangspunktmodus)", + "track_unknown": "Spor ukjente / ikke-navngitte enheter" + }, + "title": "AsusWRT-alternativer" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/it.json b/homeassistant/components/august/translations/it.json index 08332c29d7ecdf..adc9017a275fe1 100644 --- a/homeassistant/components/august/translations/it.json +++ b/homeassistant/components/august/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/awair/translations/it.json b/homeassistant/components/awair/translations/it.json index 085796f9263ec1..cad2b8555a8f04 100644 --- a/homeassistant/components/awair/translations/it.json +++ b/homeassistant/components/awair/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", "no_devices_found": "Nessun dispositivo trovato sulla rete", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "invalid_access_token": "Token di accesso non valido", diff --git a/homeassistant/components/azure_devops/translations/it.json b/homeassistant/components/azure_devops/translations/it.json index 849e65b933fa9f..4b2f5e0efae8d8 100644 --- a/homeassistant/components/azure_devops/translations/it.json +++ b/homeassistant/components/azure_devops/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/cloudflare/translations/de.json b/homeassistant/components/cloudflare/translations/de.json index d9858b36f55aff..21118e106bf22e 100644 --- a/homeassistant/components/cloudflare/translations/de.json +++ b/homeassistant/components/cloudflare/translations/de.json @@ -14,18 +14,21 @@ "records": { "data": { "records": "Datens\u00e4tze" - } + }, + "title": "W\u00e4hle die Records, die aktualisiert werden sollen" }, "user": { "data": { "api_token": "API Token" }, + "description": "F\u00fcr diese Integration ist ein API-Token erforderlich, der mit Zone: Zone: Lesen und Zone: DNS: Bearbeiten f\u00fcr alle Zonen in deinem Konto erstellt wurde.", "title": "Mit Cloudflare verbinden" }, "zone": { "data": { "zone": "Zone" - } + }, + "title": "W\u00e4hle die Zone, die aktualisiert werden soll" } } } diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index d7553652412a09..75b807b88483e1 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -66,6 +66,7 @@ "remote_button_quadruple_press": "\"{subtype}\" Taste vierfach geklickt", "remote_button_quintuple_press": "\"{subtype}\" Taste f\u00fcnffach geklickt", "remote_button_rotated": "Button gedreht \"{subtype}\".", + "remote_button_rotated_fast": "Button schnell gedreht \"{subtype}\"", "remote_button_rotation_stopped": "Die Tastendrehung \"{subtype}\" wurde gestoppt", "remote_button_short_press": "\"{subtype}\" Taste gedr\u00fcckt", "remote_button_short_release": "\"{subtype}\" Taste losgelassen", diff --git a/homeassistant/components/fireservicerota/translations/it.json b/homeassistant/components/fireservicerota/translations/it.json index 8fc43f294ec5aa..6960b68b2a20ed 100644 --- a/homeassistant/components/fireservicerota/translations/it.json +++ b/homeassistant/components/fireservicerota/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "create_entry": { "default": "Autenticazione riuscita" diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 9b76ad19ff4fe2..8e79076bda6adc 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -4,7 +4,8 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden", - "not_supported": "Verbunden mit AVM FRITZ! Box, kann jedoch keine Smart Home-Ger\u00e4te steuern." + "not_supported": "Verbunden mit AVM FRITZ! Box, kann jedoch keine Smart Home-Ger\u00e4te steuern.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "invalid_auth": "Ung\u00fcltige Zugangsdaten" @@ -18,6 +19,13 @@ }, "description": "M\u00f6chtest du {name} einrichten?" }, + "reauth_confirm": { + "data": { + "password": "Passwort", + "username": "Benutzername" + }, + "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name} ." + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index a420b3f6de767a..6aba6a007d7205 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -5,7 +5,7 @@ "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "no_devices_found": "Nessun dispositivo trovato sulla rete", "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home.", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "invalid_auth": "Autenticazione non valida" diff --git a/homeassistant/components/fritzbox_callmonitor/translations/de.json b/homeassistant/components/fritzbox_callmonitor/translations/de.json new file mode 100644 index 00000000000000..a26f301a9bdb5c --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/de.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "insufficient_permissions": "Der Benutzer verf\u00fcgt nicht \u00fcber ausreichende Berechtigungen, um auf die Einstellungen der AVM FRITZ!Box und ihre Telefonb\u00fccher zuzugreifen.", + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "flow_title": "AVM FRITZ! Box-Anrufmonitor: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Telefonbuch" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Die Pr\u00e4fixe sind fehlerhaft, bitte das Format \u00fcberpr\u00fcfen." + }, + "step": { + "init": { + "data": { + "prefixes": "Pr\u00e4fixe (kommagetrennte Liste)" + }, + "title": "Pr\u00e4fixe konfigurieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/de.json b/homeassistant/components/homekit/translations/de.json index 6d69c498bacacc..88583d9ca804a1 100644 --- a/homeassistant/components/homekit/translations/de.json +++ b/homeassistant/components/homekit/translations/de.json @@ -4,12 +4,27 @@ "port_name_in_use": "Eine HomeKit Bridge mit demselben Namen oder Port ist bereits vorhanden." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entit\u00e4t" + }, + "description": "W\u00e4hle die Entit\u00e4t aus, die aufgenommen werden soll. Im Zubeh\u00f6rmodus ist nur eine einzelne Entit\u00e4t enthalten.", + "title": "W\u00e4hle die Entit\u00e4t aus, die aufgenommen werden soll" + }, + "bridge_mode": { + "data": { + "include_domains": "Einzubeziehende Domains" + }, + "description": "W\u00e4hle die Domains aus, die aufgenommen werden sollen. Alle unterst\u00fctzten Ger\u00e4te innerhalb der Domain werden aufgenommen.", + "title": "W\u00e4hle die Domains aus, die aufgenommen werden sollen" + }, "pairing": { "title": "HomeKit verbinden" }, "user": { "data": { - "include_domains": "Einzubeziehende Domains" + "include_domains": "Einzubeziehende Domains", + "mode": "Modus" }, "title": "HomeKit aktivieren" } @@ -21,6 +36,7 @@ "data": { "safe_mode": "Abgesicherter Modus (nur aktivieren, wenn das Pairing fehlschl\u00e4gt)" }, + "description": "Diese Einstellungen m\u00fcssen nur angepasst werden, wenn HomeKit nicht funktioniert.", "title": "Erweiterte Konfiguration" }, "cameras": { @@ -33,7 +49,8 @@ "data": { "entities": "Entit\u00e4ten", "mode": "Modus" - } + }, + "title": "W\u00e4hle die Entit\u00e4ten aus, die aufgenommen werden sollen" }, "init": { "data": { diff --git a/homeassistant/components/homekit_controller/translations/de.json b/homeassistant/components/homekit_controller/translations/de.json index 7bab8f30574730..49586a23634b05 100644 --- a/homeassistant/components/homekit_controller/translations/de.json +++ b/homeassistant/components/homekit_controller/translations/de.json @@ -18,6 +18,12 @@ }, "flow_title": "HomeKit-Zubeh\u00f6r: {name}", "step": { + "busy_error": { + "title": "Das Ger\u00e4t wird bereits mit einem anderen Controller gekoppelt" + }, + "max_tries_error": { + "title": "Maximale Authentifizierungsversuche \u00fcberschritten" + }, "pair": { "data": { "pairing_code": "Kopplungscode" @@ -25,6 +31,9 @@ "description": "Gib deinen HomeKit-Kopplungscode ein, um dieses Zubeh\u00f6r zu verwenden", "title": "Mit HomeKit Zubeh\u00f6r koppeln" }, + "protocol_error": { + "title": "Fehler bei der Kommunikation mit dem Zubeh\u00f6r" + }, "user": { "data": { "device": "Ger\u00e4t" diff --git a/homeassistant/components/hyperion/translations/it.json b/homeassistant/components/hyperion/translations/it.json index 6fee49ebe14706..b03b368d0390eb 100644 --- a/homeassistant/components/hyperion/translations/it.json +++ b/homeassistant/components/hyperion/translations/it.json @@ -8,7 +8,7 @@ "auth_required_error": "Impossibile determinare se \u00e8 necessaria l'autorizzazione", "cannot_connect": "Impossibile connettersi", "no_id": "L'istanza Hyperion Ambilight non ha segnalato il suo ID", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/icloud/translations/it.json b/homeassistant/components/icloud/translations/it.json index 32931d96a32649..cfb18caee1e477 100644 --- a/homeassistant/components/icloud/translations/it.json +++ b/homeassistant/components/icloud/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", "no_device": "Nessuno dei tuoi dispositivi ha attivato \"Trova il mio iPhone\"", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "invalid_auth": "Autenticazione non valida", @@ -16,7 +16,7 @@ "password": "Password" }, "description": "La password inserita in precedenza per {username} non funziona pi\u00f9. Aggiorna la tua password per continuare a utilizzare questa integrazione.", - "title": "Reautenticare l'integrazione" + "title": "Autenticare nuovamente l'integrazione" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/ru.json b/homeassistant/components/icloud/translations/ru.json index d977899d9021ca..797637e1010a78 100644 --- a/homeassistant/components/icloud/translations/ru.json +++ b/homeassistant/components/icloud/translations/ru.json @@ -8,7 +8,7 @@ "error": { "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "send_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.", - "validate_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438 \u043d\u0430\u0447\u043d\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0441\u043d\u043e\u0432\u0430." + "validate_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437." }, "step": { "reauth": { diff --git a/homeassistant/components/keenetic_ndms2/translations/es.json b/homeassistant/components/keenetic_ndms2/translations/es.json new file mode 100644 index 00000000000000..6b8af4f98afddb --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Fallo de conexi\u00f3n" + }, + "step": { + "user": { + "data": { + "name": "Nombre", + "password": "Contrase\u00f1a", + "port": "Puerto", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/it.json b/homeassistant/components/keenetic_ndms2/translations/it.json new file mode 100644 index 00000000000000..e5a705d14b8c6f --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/it.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nome", + "password": "Password", + "port": "Porta", + "username": "Nome utente" + }, + "title": "Configurare il router Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Considerare in casa nell'intervallo di", + "include_arp": "Usa i dati ARP (ignorati se vengono utilizzati i dati dell'hotspot)", + "include_associated": "Usa i dati delle associazioni WiFi AP (ignorati se si usano i dati dell'hotspot)", + "interfaces": "Scegli le interfacce da scansionare", + "scan_interval": "Intervallo di scansione", + "try_hotspot": "Utilizza i dati \"ip hotspot\" (pi\u00f9 accurato)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/no.json b/homeassistant/components/keenetic_ndms2/translations/no.json new file mode 100644 index 00000000000000..6ad2805eb3ddb4 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/no.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "name": "Navn", + "password": "Passord", + "port": "Port", + "username": "Brukernavn" + }, + "title": "Sett opp Keenetic NDMS2 Router" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Vurder hjemmeintervall", + "include_arp": "Bruk ARP-data (ignorert hvis hotspot-data brukes)", + "include_associated": "Bruk WiFi AP-tilknytningsdata (ignoreres hvis hotspot-data brukes)", + "interfaces": "Velg grensesnitt for \u00e5 skanne", + "scan_interval": "Skanneintervall", + "try_hotspot": "Bruk 'ip hotspot'-data (mest n\u00f8yaktig)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ru.json b/homeassistant/components/keenetic_ndms2/translations/ru.json new file mode 100644 index 00000000000000..bfd7f6407e75b0 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/ru.json @@ -0,0 +1,36 @@ +{ + "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." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "port": "\u041f\u043e\u0440\u0442", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "\u0412\u0440\u0435\u043c\u044f \u043e\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0430 \u0441\u0432\u044f\u0437\u0438 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c (\u0441\u0435\u043a.), \u043f\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0434\u043e\u043c\u0430\"", + "include_arp": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 ARP (\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430)", + "include_associated": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u0435\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u0430 WiFi (\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 hotspot)", + "interfaces": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u0434\u043b\u044f \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f", + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f", + "try_hotspot": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 'ip hotspot' (\u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0442\u043e\u0447\u043d\u044b\u0435)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/ru.json b/homeassistant/components/konnected/translations/ru.json index 931f0802dc0586..4357c924572e8e 100644 --- a/homeassistant/components/konnected/translations/ru.json +++ b/homeassistant/components/konnected/translations/ru.json @@ -32,7 +32,7 @@ "not_konn_panel": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Konnected.io \u043d\u0435 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u043e." }, "error": { - "bad_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0445\u043e\u0441\u0442\u0430 API." + "bad_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430 Override API." }, "step": { "options_binary": { diff --git a/homeassistant/components/lutron_caseta/translations/ru.json b/homeassistant/components/lutron_caseta/translations/ru.json index edda7af8e9a96a..f54057f464e7f6 100644 --- a/homeassistant/components/lutron_caseta/translations/ru.json +++ b/homeassistant/components/lutron_caseta/translations/ru.json @@ -42,6 +42,25 @@ "group_1_button_2": "\u041f\u0435\u0440\u0432\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u0432\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", "group_2_button_1": "\u0412\u0442\u043e\u0440\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u043f\u0435\u0440\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", "group_2_button_2": "\u0412\u0442\u043e\u0440\u0430\u044f \u0433\u0440\u0443\u043f\u043f\u0430 \u0432\u0442\u043e\u0440\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430", + "lower": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c", + "lower_1": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c 1", + "lower_2": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c 2", + "lower_3": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c 3", + "lower_4": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c 4", + "lower_all": "\u041e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0432\u0441\u0435", + "off": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "on": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "open_1": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c 1", + "open_2": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c 2", + "open_3": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c 3", + "open_4": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c 4", + "open_all": "\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0432\u0441\u0435", + "raise": "\u041f\u043e\u0434\u043d\u044f\u0442\u044c", + "raise_1": "\u041f\u043e\u0434\u043d\u044f\u0442\u044c 1", + "raise_2": "\u041f\u043e\u0434\u043d\u044f\u0442\u044c 2", + "raise_3": "\u041f\u043e\u0434\u043d\u044f\u0442\u044c 3", + "raise_4": "\u041f\u043e\u0434\u043d\u044f\u0442\u044c 4", + "raise_all": "\u041f\u043e\u0434\u043d\u044f\u0442\u044c \u0432\u0441\u0435", "stop": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c (\u043b\u044e\u0431\u0438\u043c\u0430\u044f)", "stop_1": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 1", "stop_2": "\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c 2", diff --git a/homeassistant/components/mazda/translations/it.json b/homeassistant/components/mazda/translations/it.json index 5eb995a4dfb277..d5a2796ed1845c 100644 --- a/homeassistant/components/mazda/translations/it.json +++ b/homeassistant/components/mazda/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "account_locked": "Account bloccato. Per favore riprova pi\u00f9 tardi.", diff --git a/homeassistant/components/neato/translations/it.json b/homeassistant/components/neato/translations/it.json index 95866e918c6a0a..b559c23bb1a827 100644 --- a/homeassistant/components/neato/translations/it.json +++ b/homeassistant/components/neato/translations/it.json @@ -6,7 +6,7 @@ "invalid_auth": "Autenticazione non valida", "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "create_entry": { "default": "Autenticazione riuscita" diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 376437d20f0e9b..84c040499468c9 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -5,7 +5,7 @@ "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", - "reauth_successful": "La riautenticazione ha avuto successo", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "unknown_authorize_url_generation": "Errore sconosciuto durante la generazione di un URL di autorizzazione." }, @@ -38,7 +38,7 @@ }, "reauth_confirm": { "description": "L'integrazione di Nest deve autenticare nuovamente il tuo account", - "title": "Reautenticare l'integrazione" + "title": "Autenticare nuovamente l'integrazione" } } }, diff --git a/homeassistant/components/philips_js/translations/it.json b/homeassistant/components/philips_js/translations/it.json new file mode 100644 index 00000000000000..216248d8eac4ce --- /dev/null +++ b/homeassistant/components/philips_js/translations/it.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_version": "Versione API", + "host": "Host" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Si richiede l'accensione del dispositivo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/de.json b/homeassistant/components/pi_hole/translations/de.json index 34198fcfebeaff..6d9518490d55ea 100644 --- a/homeassistant/components/pi_hole/translations/de.json +++ b/homeassistant/components/pi_hole/translations/de.json @@ -7,6 +7,11 @@ "cannot_connect": "Verbindung fehlgeschlagen" }, "step": { + "api_key": { + "data": { + "api_key": "API-Schl\u00fcssel" + } + }, "user": { "data": { "api_key": "API-Schl\u00fcssel", @@ -15,6 +20,7 @@ "name": "Name", "port": "Port", "ssl": "Nutzt ein SSL-Zertifikat", + "statistics_only": "Nur Statistiken", "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" } } diff --git a/homeassistant/components/plaato/translations/ru.json b/homeassistant/components/plaato/translations/ru.json index befce3d7e84c75..99e1bf94e0d9de 100644 --- a/homeassistant/components/plaato/translations/ru.json +++ b/homeassistant/components/plaato/translations/ru.json @@ -6,7 +6,7 @@ "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." }, "create_entry": { - "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Plaato Airlock.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + "default": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Plaato {device_type} **{device_name}** \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { "invalid_webhook_device": "\u0412\u044b \u0432\u044b\u0431\u0440\u0430\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 Webhook. \u042d\u0442\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0430.", @@ -28,7 +28,7 @@ "device_type": "\u0422\u0438\u043f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Plaato" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?", - "title": "Plaato Airlock" + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 Plaato" }, "webhook": { "description": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Plaato Airlock.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.", diff --git a/homeassistant/components/plex/translations/it.json b/homeassistant/components/plex/translations/it.json index f470c1bc1639cc..f1ec23e2736bca 100644 --- a/homeassistant/components/plex/translations/it.json +++ b/homeassistant/components/plex/translations/it.json @@ -4,7 +4,7 @@ "all_configured": "Tutti i server collegati sono gi\u00e0 configurati", "already_configured": "Questo server Plex \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", - "reauth_successful": "La riautenticazione ha avuto successo", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "token_request_timeout": "Timeout per l'ottenimento del token", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index 376168f86167ee..d136c385acacd1 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/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", + "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto", "wrong_version": "Il tuo powerwall utilizza una versione del software non supportata. Si prega di considerare l'aggiornamento o la segnalazione di questo problema in modo che possa essere risolto." }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "Indirizzo IP" + "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 Telsa; oppure 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/sharkiq/translations/it.json b/homeassistant/components/sharkiq/translations/it.json index 4f7940cb5fc070..cfba2066bfca6c 100644 --- a/homeassistant/components/sharkiq/translations/it.json +++ b/homeassistant/components/sharkiq/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", "cannot_connect": "Impossibile connettersi", - "reauth_successful": "La riautenticazione ha avuto successo", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "unknown": "Errore imprevisto" }, "error": { diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index 5a3a40ac9f8cfb..ad5a288cf9138d 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "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 {model} ({host}) ?\n\n\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." + "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 {model} ({host})?\n\n\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438 \u0438 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u044b \u043f\u0430\u0440\u043e\u043b\u0435\u043c, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044c, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443.\n\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438 \u0438 \u043d\u0435 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u044b \u043f\u0430\u0440\u043e\u043b\u0435\u043c, \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b, \u043a\u043e\u0433\u0434\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0432\u044b\u0439\u0434\u0435\u0442 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435, \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0432 \u0435\u0433\u043e, \u043b\u0438\u0431\u043e \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430." }, "credentials": { "data": { diff --git a/homeassistant/components/simplisafe/translations/it.json b/homeassistant/components/simplisafe/translations/it.json index fdd69b39efc0a1..b5ce2a2670204f 100644 --- a/homeassistant/components/simplisafe/translations/it.json +++ b/homeassistant/components/simplisafe/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Questo account SimpliSafe \u00e8 gi\u00e0 in uso.", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "identifier_exists": "Account gi\u00e0 registrato", @@ -20,7 +20,7 @@ "password": "Password" }, "description": "Il token di accesso \u00e8 scaduto o \u00e8 stato revocato. Inserisci la tua password per ricollegare il tuo account.", - "title": "Reautenticare l'integrazione" + "title": "Autenticare nuovamente l'integrazione" }, "user": { "data": { diff --git a/homeassistant/components/sonarr/translations/it.json b/homeassistant/components/sonarr/translations/it.json index 1a383201ab1c31..71a4cca729e1ce 100644 --- a/homeassistant/components/sonarr/translations/it.json +++ b/homeassistant/components/sonarr/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", - "reauth_successful": "La riautenticazione ha avuto successo", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", "unknown": "Errore imprevisto" }, "error": { @@ -13,7 +13,7 @@ "step": { "reauth_confirm": { "description": "L'integrazione di Sonarr deve essere nuovamente autenticata manualmente con l'API Sonarr ospitata su: {host}", - "title": "Reautenticare l'integrazione" + "title": "Autenticare nuovamente l'integrazione" }, "user": { "data": { diff --git a/homeassistant/components/spotify/translations/it.json b/homeassistant/components/spotify/translations/it.json index 595e13865e1296..28d821c81f144c 100644 --- a/homeassistant/components/spotify/translations/it.json +++ b/homeassistant/components/spotify/translations/it.json @@ -15,7 +15,7 @@ }, "reauth_confirm": { "description": "L'integrazione di Spotify deve essere nuovamente autenticata con Spotify per l'account: {account}", - "title": "Reautenticare l'integrazione" + "title": "Autenticare nuovamente l'integrazione" } } }, diff --git a/homeassistant/components/tesla/translations/it.json b/homeassistant/components/tesla/translations/it.json index a316b41c29c8ee..3a137da78f1484 100644 --- a/homeassistant/components/tesla/translations/it.json +++ b/homeassistant/components/tesla/translations/it.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, "error": { "already_configured": "L'account \u00e8 gi\u00e0 configurato", "cannot_connect": "Impossibile connettersi", diff --git a/homeassistant/components/traccar/translations/ru.json b/homeassistant/components/traccar/translations/ru.json index b35b1e74e1e7e1..dc4cd2cde112e4 100644 --- a/homeassistant/components/traccar/translations/ru.json +++ b/homeassistant/components/traccar/translations/ru.json @@ -5,7 +5,7 @@ "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." }, "create_entry": { - "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Traccar.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Traccar.\n\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 URL-\u0430\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438: `{webhook_url}`\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." }, "step": { "user": { diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index d50018227c5d0a..f5311f538c1428 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato", "configuration_updated": "Configurazione aggiornata.", - "reauth_successful": "La riautenticazione ha avuto successo" + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" }, "error": { "faulty_credentials": "Autenticazione non valida", diff --git a/homeassistant/components/withings/translations/it.json b/homeassistant/components/withings/translations/it.json index 85baeb1f0e07cc..8fb4dee991866a 100644 --- a/homeassistant/components/withings/translations/it.json +++ b/homeassistant/components/withings/translations/it.json @@ -26,7 +26,7 @@ }, "reauth": { "description": "Il profilo \"{profile}\" deve essere autenticato nuovamente per continuare a ricevere i dati Withings.", - "title": "Reautenticare l'integrazione" + "title": "Autenticare nuovamente l'integrazione" } } } diff --git a/homeassistant/components/xiaomi_aqara/translations/ru.json b/homeassistant/components/xiaomi_aqara/translations/ru.json index 96da0a24074962..4ede8019a4f003 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ru.json +++ b/homeassistant/components/xiaomi_aqara/translations/ru.json @@ -18,7 +18,7 @@ "data": { "select_ip": "IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0435\u0441\u043b\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0448\u043b\u044e\u0437", + "description": "\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0435\u0441\u043b\u0438 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0448\u043b\u044e\u0437.", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara" }, "settings": { diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index c8ac63d1ea725d..fe95af5e06cb0a 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -6,18 +6,35 @@ }, "error": { "cannot_connect": "Failed to connect", + "no_device_selected": "No device selected, please select one device.", "unknown_device": "The device model is not known, not able to setup the device using config flow." }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { - "host": "IP Address", - "name": "Name of the device", - "token": "API Token" + "host": "IP Address", + "name": "Name of the device", + "token": "API Token" }, "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" + }, + "gateway": { + "data": { + "host": "IP Address", + "name": "Name of the Gateway", + "token": "API Token" + }, + "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", + "title": "Connect to a Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Connect to a Xiaomi Gateway" + }, + "description": "Select to which device you want to connect.", + "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/zwave/translations/ru.json b/homeassistant/components/zwave/translations/ru.json index 515a47d87a62ce..5188bb8330e0d2 100644 --- a/homeassistant/components/zwave/translations/ru.json +++ b/homeassistant/components/zwave/translations/ru.json @@ -13,7 +13,7 @@ "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438)", "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" }, - "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/docs/z-wave/installation/) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", + "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u043e \u043d\u0435\u0451 Z-Wave JS.\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/docs/z-wave/installation/) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", "title": "Z-Wave" } } From 3c26235e784177d25452f90310d16b114863545d Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Tue, 16 Feb 2021 03:20:45 +0100 Subject: [PATCH 0501/1818] Bump tuyaha to 0.0.10 and fix set temperature issues (#45732) --- .coveragerc | 9 +- homeassistant/components/tuya/climate.py | 10 +- homeassistant/components/tuya/config_flow.py | 31 ++- homeassistant/components/tuya/const.py | 2 + homeassistant/components/tuya/manifest.json | 2 +- homeassistant/components/tuya/strings.json | 2 + .../components/tuya/translations/en.json | 2 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tuya/common.py | 75 ++++++ tests/components/tuya/test_config_flow.py | 233 +++++++++++++++--- 11 files changed, 318 insertions(+), 52 deletions(-) create mode 100644 tests/components/tuya/common.py diff --git a/.coveragerc b/.coveragerc index e64bfab280a46f..df9fbac4c588bb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -993,7 +993,14 @@ omit = homeassistant/components/transmission/const.py homeassistant/components/transmission/errors.py homeassistant/components/travisci/sensor.py - homeassistant/components/tuya/* + homeassistant/components/tuya/__init__.py + homeassistant/components/tuya/climate.py + homeassistant/components/tuya/const.py + homeassistant/components/tuya/cover.py + homeassistant/components/tuya/fan.py + homeassistant/components/tuya/light.py + homeassistant/components/tuya/scene.py + homeassistant/components/tuya/switch.py homeassistant/components/twentemilieu/const.py homeassistant/components/twentemilieu/sensor.py homeassistant/components/twilio_call/notify.py diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 0502273818c299..73ba69da79786b 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -33,7 +33,9 @@ CONF_CURR_TEMP_DIVIDER, CONF_MAX_TEMP, CONF_MIN_TEMP, + CONF_SET_TEMP_DIVIDED, CONF_TEMP_DIVIDER, + CONF_TEMP_STEP_OVERRIDE, DOMAIN, SIGNAL_CONFIG_ENTITY, TUYA_DATA, @@ -103,6 +105,8 @@ def __init__(self, tuya, platform): self.operations = [HVAC_MODE_OFF] self._has_operation = False self._def_hvac_mode = HVAC_MODE_AUTO + self._set_temp_divided = True + self._temp_step_override = None self._min_temp = None self._max_temp = None @@ -117,6 +121,8 @@ def _process_config(self): self._tuya.set_unit("FAHRENHEIT" if unit == TEMP_FAHRENHEIT else "CELSIUS") self._tuya.temp_divider = config.get(CONF_TEMP_DIVIDER, 0) self._tuya.curr_temp_divider = config.get(CONF_CURR_TEMP_DIVIDER, 0) + self._set_temp_divided = config.get(CONF_SET_TEMP_DIVIDED, True) + self._temp_step_override = config.get(CONF_TEMP_STEP_OVERRIDE) min_temp = config.get(CONF_MIN_TEMP, 0) max_temp = config.get(CONF_MAX_TEMP, 0) if min_temp >= max_temp: @@ -189,6 +195,8 @@ def target_temperature(self): @property def target_temperature_step(self): """Return the supported step of target temperature.""" + if self._temp_step_override: + return self._temp_step_override return self._tuya.target_temperature_step() @property @@ -204,7 +212,7 @@ def fan_modes(self): def set_temperature(self, **kwargs): """Set new target temperature.""" if ATTR_TEMPERATURE in kwargs: - self._tuya.set_temperature(kwargs[ATTR_TEMPERATURE]) + self._tuya.set_temperature(kwargs[ATTR_TEMPERATURE], self._set_temp_divided) def set_fan_mode(self, fan_mode): """Set new target fan mode.""" diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index 5d22a83e03ef08..b705c2c7c36ef1 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -23,7 +23,6 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -# pylint:disable=unused-import from .const import ( CONF_BRIGHTNESS_RANGE_MODE, CONF_COUNTRYCODE, @@ -35,17 +34,19 @@ CONF_MIN_TEMP, CONF_QUERY_DEVICE, CONF_QUERY_INTERVAL, + CONF_SET_TEMP_DIVIDED, CONF_SUPPORT_COLOR, CONF_TEMP_DIVIDER, + CONF_TEMP_STEP_OVERRIDE, CONF_TUYA_MAX_COLTEMP, DEFAULT_DISCOVERY_INTERVAL, DEFAULT_QUERY_INTERVAL, DEFAULT_TUYA_MAX_COLTEMP, - DOMAIN, TUYA_DATA, TUYA_PLATFORMS, TUYA_TYPE_NOT_QUERY, ) +from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) @@ -66,6 +67,7 @@ RESULT_AUTH_FAILED = "invalid_auth" RESULT_CONN_ERROR = "cannot_connect" +RESULT_SINGLE_INSTANCE = "single_instance_allowed" RESULT_SUCCESS = "success" RESULT_LOG_MESSAGE = { @@ -123,7 +125,7 @@ async def async_step_import(self, user_input=None): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") + return self.async_abort(reason=RESULT_SINGLE_INSTANCE) errors = {} @@ -257,7 +259,7 @@ async def async_step_init(self, user_input=None): if self.config_entry.state != config_entries.ENTRY_STATE_LOADED: _LOGGER.error("Tuya integration not yet loaded") - return self.async_abort(reason="cannot_connect") + return self.async_abort(reason=RESULT_CONN_ERROR) if user_input is not None: dev_ids = user_input.get(CONF_LIST_DEVICES) @@ -323,11 +325,14 @@ async def async_step_device(self, user_input=None, dev_ids=None): def _get_device_schema(self, device_type, curr_conf, device): """Return option schema for device.""" + if device_type != device.device_type(): + return None + schema = None if device_type == "light": - return self._get_light_schema(curr_conf, device) - if device_type == "climate": - return self._get_climate_schema(curr_conf, device) - return None + schema = self._get_light_schema(curr_conf, device) + elif device_type == "climate": + schema = self._get_climate_schema(curr_conf, device) + return schema @staticmethod def _get_light_schema(curr_conf, device): @@ -374,6 +379,8 @@ def _get_climate_schema(curr_conf, device): """Create option schema for climate device.""" unit = device.temperature_unit() def_unit = TEMP_FAHRENHEIT if unit == "FAHRENHEIT" else TEMP_CELSIUS + supported_steps = device.supported_temperature_steps() + default_step = device.target_temperature_step() config_schema = vol.Schema( { @@ -389,6 +396,14 @@ def _get_climate_schema(curr_conf, device): CONF_CURR_TEMP_DIVIDER, default=curr_conf.get(CONF_CURR_TEMP_DIVIDER, 0), ): vol.All(vol.Coerce(int), vol.Clamp(min=0)), + vol.Optional( + CONF_SET_TEMP_DIVIDED, + default=curr_conf.get(CONF_SET_TEMP_DIVIDED, True), + ): bool, + vol.Optional( + CONF_TEMP_STEP_OVERRIDE, + default=curr_conf.get(CONF_TEMP_STEP_OVERRIDE, default_step), + ): vol.In(supported_steps), vol.Optional( CONF_MIN_TEMP, default=curr_conf.get(CONF_MIN_TEMP, 0), diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 4f4ec342b152a2..646bcc077cfa42 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -10,8 +10,10 @@ CONF_MIN_TEMP = "min_temp" CONF_QUERY_DEVICE = "query_device" CONF_QUERY_INTERVAL = "query_interval" +CONF_SET_TEMP_DIVIDED = "set_temp_divided" CONF_SUPPORT_COLOR = "support_color" CONF_TEMP_DIVIDER = "temp_divider" +CONF_TEMP_STEP_OVERRIDE = "temp_step_override" CONF_TUYA_MAX_COLTEMP = "tuya_max_coltemp" DEFAULT_DISCOVERY_INTERVAL = 605 diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index 7481e56f00af73..e72c7c63112cce 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -2,7 +2,7 @@ "domain": "tuya", "name": "Tuya", "documentation": "https://www.home-assistant.io/integrations/tuya", - "requirements": ["tuyaha==0.0.9"], + "requirements": ["tuyaha==0.0.10"], "codeowners": ["@ollo69"], "config_flow": true } diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 444ff0b5c21180..23958349b66cc9 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -49,6 +49,8 @@ "unit_of_measurement": "Temperature unit used by device", "temp_divider": "Temperature values divider (0 = use default)", "curr_temp_divider": "Current Temperature value divider (0 = use default)", + "set_temp_divided": "Use divided Temperature value for set temperature command", + "temp_step_override": "Target Temperature step", "min_temp": "Min target temperature (use min and max = 0 for default)", "max_temp": "Max target temperature (use min and max = 0 for default)" } diff --git a/homeassistant/components/tuya/translations/en.json b/homeassistant/components/tuya/translations/en.json index 46756b18cb855b..7204d6072a96d0 100644 --- a/homeassistant/components/tuya/translations/en.json +++ b/homeassistant/components/tuya/translations/en.json @@ -40,8 +40,10 @@ "max_temp": "Max target temperature (use min and max = 0 for default)", "min_kelvin": "Min color temperature supported in kelvin", "min_temp": "Min target temperature (use min and max = 0 for default)", + "set_temp_divided": "Use divided Temperature value for set temperature command", "support_color": "Force color support", "temp_divider": "Temperature values divider (0 = use default)", + "temp_step_override": "Target Temperature step", "tuya_max_coltemp": "Max color temperature reported by device", "unit_of_measurement": "Temperature unit used by device" }, diff --git a/requirements_all.txt b/requirements_all.txt index c31abaa6c1f5f5..b1a18147b25cb6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2217,7 +2217,7 @@ tp-connected==0.0.4 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.9 +tuyaha==0.0.10 # homeassistant.components.twentemilieu twentemilieu==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 66faad75ed7435..bc1e86fc61f477 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1126,7 +1126,7 @@ total_connect_client==0.55 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.9 +tuyaha==0.0.10 # homeassistant.components.twentemilieu twentemilieu==0.3.0 diff --git a/tests/components/tuya/common.py b/tests/components/tuya/common.py new file mode 100644 index 00000000000000..8dcef136b7f4c3 --- /dev/null +++ b/tests/components/tuya/common.py @@ -0,0 +1,75 @@ +"""Test code shared between test files.""" + +from tuyaha.devices import climate, light, switch + +CLIMATE_ID = "1" +CLIMATE_DATA = { + "data": {"state": "true", "temp_unit": climate.UNIT_CELSIUS}, + "id": CLIMATE_ID, + "ha_type": "climate", + "name": "TestClimate", + "dev_type": "climate", +} + +LIGHT_ID = "2" +LIGHT_DATA = { + "data": {"state": "true"}, + "id": LIGHT_ID, + "ha_type": "light", + "name": "TestLight", + "dev_type": "light", +} + +SWITCH_ID = "3" +SWITCH_DATA = { + "data": {"state": True}, + "id": SWITCH_ID, + "ha_type": "switch", + "name": "TestSwitch", + "dev_type": "switch", +} + +LIGHT_ID_FAKE1 = "9998" +LIGHT_DATA_FAKE1 = { + "data": {"state": "true"}, + "id": LIGHT_ID_FAKE1, + "ha_type": "light", + "name": "TestLightFake1", + "dev_type": "light", +} + +LIGHT_ID_FAKE2 = "9999" +LIGHT_DATA_FAKE2 = { + "data": {"state": "true"}, + "id": LIGHT_ID_FAKE2, + "ha_type": "light", + "name": "TestLightFake2", + "dev_type": "light", +} + +TUYA_DEVICES = [ + climate.TuyaClimate(CLIMATE_DATA, None), + light.TuyaLight(LIGHT_DATA, None), + switch.TuyaSwitch(SWITCH_DATA, None), + light.TuyaLight(LIGHT_DATA_FAKE1, None), + light.TuyaLight(LIGHT_DATA_FAKE2, None), +] + + +class MockTuya: + """Mock for Tuya devices.""" + + def get_all_devices(self): + """Return all configured devices.""" + return TUYA_DEVICES + + def get_device_by_id(self, dev_id): + """Return configured device with dev id.""" + if dev_id == LIGHT_ID_FAKE1: + return None + if dev_id == LIGHT_ID_FAKE2: + return switch.TuyaSwitch(SWITCH_DATA, None) + for device in TUYA_DEVICES: + if device.object_id() == dev_id: + return device + return None diff --git a/tests/components/tuya/test_config_flow.py b/tests/components/tuya/test_config_flow.py index 0055b451e1a7a3..012886fe3b8b36 100644 --- a/tests/components/tuya/test_config_flow.py +++ b/tests/components/tuya/test_config_flow.py @@ -2,11 +2,47 @@ from unittest.mock import Mock, patch import pytest +from tuyaha.devices.climate import STEP_HALVES from tuyaha.tuyaapi import TuyaAPIException, TuyaNetException -from homeassistant import config_entries, data_entry_flow, setup -from homeassistant.components.tuya.const import CONF_COUNTRYCODE, DOMAIN -from homeassistant.const import CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.tuya.config_flow import ( + CONF_LIST_DEVICES, + ERROR_DEV_MULTI_TYPE, + ERROR_DEV_NOT_CONFIG, + ERROR_DEV_NOT_FOUND, + RESULT_AUTH_FAILED, + RESULT_CONN_ERROR, + RESULT_SINGLE_INSTANCE, +) +from homeassistant.components.tuya.const import ( + CONF_BRIGHTNESS_RANGE_MODE, + CONF_COUNTRYCODE, + CONF_CURR_TEMP_DIVIDER, + CONF_DISCOVERY_INTERVAL, + CONF_MAX_KELVIN, + CONF_MAX_TEMP, + CONF_MIN_KELVIN, + CONF_MIN_TEMP, + CONF_QUERY_DEVICE, + CONF_QUERY_INTERVAL, + CONF_SET_TEMP_DIVIDED, + CONF_SUPPORT_COLOR, + CONF_TEMP_DIVIDER, + CONF_TEMP_STEP_OVERRIDE, + CONF_TUYA_MAX_COLTEMP, + DOMAIN, + TUYA_DATA, +) +from homeassistant.const import ( + CONF_PASSWORD, + CONF_PLATFORM, + CONF_UNIT_OF_MEASUREMENT, + CONF_USERNAME, + TEMP_CELSIUS, +) + +from .common import CLIMATE_ID, LIGHT_ID, LIGHT_ID_FAKE1, LIGHT_ID_FAKE2, MockTuya from tests.common import MockConfigEntry @@ -30,9 +66,15 @@ def tuya_fixture() -> Mock: yield tuya +@pytest.fixture(name="tuya_setup", autouse=True) +def tuya_setup_fixture(): + """Mock tuya entry setup.""" + with patch("homeassistant.components.tuya.async_setup_entry", return_value=True): + yield + + async def test_user(hass, tuya): """Test user config.""" - await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -40,15 +82,10 @@ async def test_user(hass, tuya): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.tuya.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.tuya.async_setup_entry", return_value=True - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=TUYA_USER_DATA - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=TUYA_USER_DATA + ) + await hass.async_block_till_done() assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == USERNAME @@ -58,26 +95,15 @@ async def test_user(hass, tuya): assert result["data"][CONF_PLATFORM] == TUYA_PLATFORM assert not result["result"].unique_id - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - async def test_import(hass, tuya): """Test import step.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - with patch( - "homeassistant.components.tuya.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.tuya.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=TUYA_USER_DATA, - ) - await hass.async_block_till_done() + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=TUYA_USER_DATA, + ) + await hass.async_block_till_done() assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == USERNAME @@ -87,9 +113,6 @@ async def test_import(hass, tuya): assert result["data"][CONF_PLATFORM] == TUYA_PLATFORM assert not result["result"].unique_id - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - async def test_abort_if_already_setup(hass, tuya): """Test we abort if Tuya is already setup.""" @@ -101,7 +124,7 @@ async def test_abort_if_already_setup(hass, tuya): ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "single_instance_allowed" + assert result["reason"] == RESULT_SINGLE_INSTANCE # Should fail, config exist (flow) result = await hass.config_entries.flow.async_init( @@ -109,7 +132,7 @@ async def test_abort_if_already_setup(hass, tuya): ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "single_instance_allowed" + assert result["reason"] == RESULT_SINGLE_INSTANCE async def test_abort_on_invalid_credentials(hass, tuya): @@ -121,14 +144,14 @@ async def test_abort_on_invalid_credentials(hass, tuya): ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_auth"} + assert result["errors"] == {"base": RESULT_AUTH_FAILED} result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TUYA_USER_DATA ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "invalid_auth" + assert result["reason"] == RESULT_AUTH_FAILED async def test_abort_on_connection_error(hass, tuya): @@ -140,11 +163,143 @@ async def test_abort_on_connection_error(hass, tuya): ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "cannot_connect" + assert result["reason"] == RESULT_CONN_ERROR result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=TUYA_USER_DATA ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "cannot_connect" + assert result["reason"] == RESULT_CONN_ERROR + + +async def test_options_flow(hass): + """Test config flow options.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=TUYA_USER_DATA, + ) + config_entry.add_to_hass(hass) + + # Test check for integration not loaded + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == RESULT_CONN_ERROR + + # Load integration and enter options + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + hass.data[DOMAIN] = {TUYA_DATA: MockTuya()} + 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" + + # Test dev not found error + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID_FAKE1}"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {"base": ERROR_DEV_NOT_FOUND} + + # Test dev type error + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID_FAKE2}"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {"base": ERROR_DEV_NOT_CONFIG} + + # Test multi dev error + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_LIST_DEVICES: [f"climate-{CLIMATE_ID}", f"light-{LIGHT_ID}"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {"base": ERROR_DEV_MULTI_TYPE} + + # Test climate options form + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_LIST_DEVICES: [f"climate-{CLIMATE_ID}"]} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "device" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, + CONF_TEMP_DIVIDER: 10, + CONF_CURR_TEMP_DIVIDER: 5, + CONF_SET_TEMP_DIVIDED: False, + CONF_TEMP_STEP_OVERRIDE: STEP_HALVES, + CONF_MIN_TEMP: 12, + CONF_MAX_TEMP: 22, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + # Test light options form + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_LIST_DEVICES: [f"light-{LIGHT_ID}"]} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "device" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_SUPPORT_COLOR: True, + CONF_BRIGHTNESS_RANGE_MODE: 1, + CONF_MIN_KELVIN: 4000, + CONF_MAX_KELVIN: 5000, + CONF_TUYA_MAX_COLTEMP: 12000, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + # Test common options + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_DISCOVERY_INTERVAL: 100, + CONF_QUERY_INTERVAL: 50, + CONF_QUERY_DEVICE: LIGHT_ID, + }, + ) + + # Verify results + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + climate_options = config_entry.options[CLIMATE_ID] + assert climate_options[CONF_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS + assert climate_options[CONF_TEMP_DIVIDER] == 10 + assert climate_options[CONF_CURR_TEMP_DIVIDER] == 5 + assert climate_options[CONF_SET_TEMP_DIVIDED] is False + assert climate_options[CONF_TEMP_STEP_OVERRIDE] == STEP_HALVES + assert climate_options[CONF_MIN_TEMP] == 12 + assert climate_options[CONF_MAX_TEMP] == 22 + + light_options = config_entry.options[LIGHT_ID] + assert light_options[CONF_SUPPORT_COLOR] is True + assert light_options[CONF_BRIGHTNESS_RANGE_MODE] == 1 + assert light_options[CONF_MIN_KELVIN] == 4000 + assert light_options[CONF_MAX_KELVIN] == 5000 + assert light_options[CONF_TUYA_MAX_COLTEMP] == 12000 + + assert config_entry.options[CONF_DISCOVERY_INTERVAL] == 100 + assert config_entry.options[CONF_QUERY_INTERVAL] == 50 + assert config_entry.options[CONF_QUERY_DEVICE] == LIGHT_ID From 1e172dedf6fd431b20723a097b802da6cea0bce4 Mon Sep 17 00:00:00 2001 From: marecabo <23156476+marecabo@users.noreply.github.com> Date: Tue, 16 Feb 2021 07:19:31 +0100 Subject: [PATCH 0502/1818] Add device_class attribute to ESPHome sensor entities (#46595) --- homeassistant/components/esphome/manifest.json | 2 +- homeassistant/components/esphome/sensor.py | 11 +++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index c69f4f4d8c62c4..17ff2ca96ba30e 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==2.6.4"], + "requirements": ["aioesphomeapi==2.6.5"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter"], "after_dependencies": ["zeroconf", "tag"] diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index a5cc321cb08229..68a8c00caed8b9 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -52,6 +52,8 @@ def _state(self) -> Optional[SensorState]: @property def icon(self) -> str: """Return the icon.""" + if self._static_info.icon == "": + return None return self._static_info.icon @property @@ -71,8 +73,17 @@ def state(self) -> Optional[str]: @property def unit_of_measurement(self) -> str: """Return the unit the value is expressed in.""" + if self._static_info.unit_of_measurement == "": + return None return self._static_info.unit_of_measurement + @property + def device_class(self) -> str: + """Return the class of this device, from component DEVICE_CLASSES.""" + if self._static_info.device_class == "": + return None + return self._static_info.device_class + class EsphomeTextSensor(EsphomeEntity): """A text sensor implementation for ESPHome.""" diff --git a/requirements_all.txt b/requirements_all.txt index b1a18147b25cb6..640c2ace4dff8e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -154,7 +154,7 @@ aiodns==2.0.0 aioeafm==0.1.2 # homeassistant.components.esphome -aioesphomeapi==2.6.4 +aioesphomeapi==2.6.5 # homeassistant.components.flo aioflo==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc1e86fc61f477..6b77eaafb9dc83 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -91,7 +91,7 @@ aiodns==2.0.0 aioeafm==0.1.2 # homeassistant.components.esphome -aioesphomeapi==2.6.4 +aioesphomeapi==2.6.5 # homeassistant.components.flo aioflo==0.4.1 From 20d93b4b298085547258831a76a2c4a0fac2ed1d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 15 Feb 2021 21:37:43 -1000 Subject: [PATCH 0503/1818] Remove support for migrating pre-config entry homekit (#46616) HomeKit pairings and accessory ids from versions 0.109 and earlier are no longer migrated on upgrade. Users upgrading directly to 2021.3 from 0.109 and older should upgrade to 2021.2 first if they wish to preserve HomeKit configuration and avoid re-pairing the bridge. This change does not affect upgrades from 0.110 and later. --- homeassistant/components/homekit/__init__.py | 9 --- homeassistant/components/homekit/util.py | 19 ------ tests/components/homekit/test_homekit.py | 71 +------------------- 3 files changed, 1 insertion(+), 98 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 34044742703a60..396f36f7c030f6 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -99,7 +99,6 @@ from .util import ( dismiss_setup_message, get_persist_fullpath_for_entry_id, - migrate_filesystem_state_data_for_primary_imported_entry_id, port_is_available, remove_state_files_for_entry_id, show_setup_message, @@ -238,14 +237,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): port = conf[CONF_PORT] _LOGGER.debug("Begin setup HomeKit for %s", name) - if CONF_ENTRY_INDEX in conf and conf[CONF_ENTRY_INDEX] == 0: - _LOGGER.debug("Migrating legacy HomeKit data for %s", name) - await hass.async_add_executor_job( - migrate_filesystem_state_data_for_primary_imported_entry_id, - hass, - entry.entry_id, - ) - aid_storage = AccessoryAidStorage(hass, entry.entry_id) await aid_storage.async_initialize() diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 83a9a0a0353bbc..c23b8c1baaf0e7 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -66,7 +66,6 @@ FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, - HOMEKIT_FILE, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, TYPE_FAUCET, @@ -410,24 +409,6 @@ def format_sw_version(version): return None -def migrate_filesystem_state_data_for_primary_imported_entry_id( - hass: HomeAssistant, entry_id: str -): - """Migrate the old paths to the storage directory.""" - legacy_persist_file_path = hass.config.path(HOMEKIT_FILE) - if os.path.exists(legacy_persist_file_path): - os.rename( - legacy_persist_file_path, get_persist_fullpath_for_entry_id(hass, entry_id) - ) - - legacy_aid_storage_path = hass.config.path(STORAGE_DIR, "homekit.aids") - if os.path.exists(legacy_aid_storage_path): - os.rename( - legacy_aid_storage_path, - get_aid_storage_fullpath_for_entry_id(hass, entry_id), - ) - - def remove_state_files_for_entry_id(hass: HomeAssistant, entry_id: str): """Remove the state files from disk.""" persist_file_path = get_persist_fullpath_for_entry_id(hass, entry_id) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 1fff55db195bd1..b0213ee7e8bcde 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -27,20 +27,15 @@ BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CONF_AUTO_START, - CONF_ENTRY_INDEX, DEFAULT_PORT, DOMAIN, HOMEKIT, - HOMEKIT_FILE, HOMEKIT_MODE_ACCESSORY, HOMEKIT_MODE_BRIDGE, SERVICE_HOMEKIT_RESET_ACCESSORY, SERVICE_HOMEKIT_START, ) -from homeassistant.components.homekit.util import ( - get_aid_storage_fullpath_for_entry_id, - get_persist_fullpath_for_entry_id, -) +from homeassistant.components.homekit.util import get_persist_fullpath_for_entry_id from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -60,7 +55,6 @@ from homeassistant.core import State from homeassistant.helpers import device_registry from homeassistant.helpers.entityfilter import generate_filter -from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.setup import async_setup_component from homeassistant.util import json as json_util @@ -909,69 +903,6 @@ def _mock_get_accessory(*args, **kwargs): ) -async def test_setup_imported(hass, mock_zeroconf): - """Test async_setup with imported config options.""" - legacy_persist_file_path = hass.config.path(HOMEKIT_FILE) - legacy_aid_storage_path = hass.config.path(STORAGE_DIR, "homekit.aids") - legacy_homekit_state_contents = {"homekit.state": 1} - legacy_homekit_aids_contents = {"homekit.aids": 1} - await hass.async_add_executor_job( - _write_data, legacy_persist_file_path, legacy_homekit_state_contents - ) - await hass.async_add_executor_job( - _write_data, legacy_aid_storage_path, legacy_homekit_aids_contents - ) - - entry = MockConfigEntry( - domain=DOMAIN, - source=SOURCE_IMPORT, - data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT, CONF_ENTRY_INDEX: 0}, - options={}, - ) - entry.add_to_hass(hass) - - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: - mock_homekit.return_value = homekit = Mock() - type(homekit).async_start = AsyncMock() - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - mock_homekit.assert_any_call( - hass, - BRIDGE_NAME, - DEFAULT_PORT, - None, - ANY, - {}, - HOMEKIT_MODE_BRIDGE, - None, - entry.entry_id, - ) - assert mock_homekit().setup.called is True - - # Test auto start enabled - mock_homekit.reset_mock() - hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) - await hass.async_block_till_done() - - mock_homekit().async_start.assert_called() - - migrated_persist_file_path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) - assert ( - await hass.async_add_executor_job( - json_util.load_json, migrated_persist_file_path - ) - == legacy_homekit_state_contents - ) - os.unlink(migrated_persist_file_path) - migrated_aid_file_path = get_aid_storage_fullpath_for_entry_id(hass, entry.entry_id) - assert ( - await hass.async_add_executor_job(json_util.load_json, migrated_aid_file_path) - == legacy_homekit_aids_contents - ) - os.unlink(migrated_aid_file_path) - - async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): """Test async_setup with imported config.""" entry = MockConfigEntry( From 6986fa4eb674c4a9d49af2e4f393dac8be4e6ba5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 16 Feb 2021 09:35:27 +0100 Subject: [PATCH 0504/1818] Add target to services.yaml (#46410) Co-authored-by: Franck Nijhof --- homeassistant/components/light/services.yaml | 20 +++++++++++--- homeassistant/components/sonos/services.yaml | 28 +++++++++++++++++++- homeassistant/helpers/service.py | 8 ++++-- script/hassfest/services.py | 8 +++++- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index a2b71f5632bb25..dda7396e11bbb0 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -2,13 +2,19 @@ turn_on: description: Turn a light on. + target: fields: - entity_id: - description: Name(s) of entities to turn on - example: "light.kitchen" transition: + name: Transition description: Duration in seconds it takes to get to next state example: 60 + selector: + number: + min: 0 + max: 300 + step: 1 + unit_of_measurement: seconds + mode: slider rgb_color: description: Color for the light in RGB-format. example: "[255, 100, 100]" @@ -34,8 +40,16 @@ turn_on: description: Number between 0..255 indicating brightness, where 0 turns the light off, 1 is the minimum brightness and 255 is the maximum brightness supported by the light. example: 120 brightness_pct: + name: Brightness description: Number between 0..100 indicating percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness and 100 is the maximum brightness supported by the light. example: 47 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider brightness_step: description: Change brightness by an amount. Should be between -255..255. example: -25.5 diff --git a/homeassistant/components/sonos/services.yaml b/homeassistant/components/sonos/services.yaml index 8a35e9a7790667..04a46940d6a1ae 100644 --- a/homeassistant/components/sonos/services.yaml +++ b/homeassistant/components/sonos/services.yaml @@ -4,6 +4,10 @@ join: master: description: Entity ID of the player that should become the coordinator of the group. example: "media_player.living_room_sonos" + selector: + entity: + integration: sonos + domain: media_player entity_id: description: Name(s) of entities that will join the master. example: "media_player.living_room_sonos" @@ -64,7 +68,13 @@ set_sleep_timer: sleep_time: description: Number of seconds to set the timer. example: "900" - + selector: + number: + min: 0 + max: 3600 + step: 1 + unit_of_measurement: seconds + mode: slider clear_sleep_timer: description: Clear a Sonos timer. fields: @@ -89,12 +99,18 @@ set_option: night_sound: description: Enable Night Sound mode example: "true" + selector: + boolean: speech_enhance: description: Enable Speech Enhancement mode example: "true" + selector: + boolean: status_light: description: Enable Status (LED) Light example: "true" + selector: + boolean: play_queue: description: Starts playing the queue from the first item. @@ -109,6 +125,11 @@ play_queue: queue_position: description: Position of the song in the queue to start playing from. example: "0" + selector: + number: + min: 0 + max: 100000000 + mode: box remove_from_queue: description: Removes an item from the queue. @@ -123,6 +144,11 @@ remove_from_queue: queue_position: description: Position in the queue to remove. example: "0" + selector: + number: + min: 0 + max: 100000000 + mode: box update_alarm: description: Updates an alarm with new time and volume settings. diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index afc354dae56174..d4eacbc0503c9e 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -448,12 +448,16 @@ async def async_get_all_descriptions( # Don't warn for missing services, because it triggers false # positives for things like scripts, that register as a service - description = descriptions_cache[cache_key] = { + description = { "description": yaml_description.get("description", ""), - "target": yaml_description.get("target"), "fields": yaml_description.get("fields", {}), } + if "target" in yaml_description: + description["target"] = yaml_description["target"] + + descriptions_cache[cache_key] = description + descriptions[domain][service] = description return descriptions diff --git a/script/hassfest/services.py b/script/hassfest/services.py index c07d3bbc6efd9a..c0823b672e82a5 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -24,6 +24,7 @@ def exists(value): FIELD_SCHEMA = vol.Schema( { vol.Required("description"): str, + vol.Optional("name"): str, vol.Optional("example"): exists, vol.Optional("default"): exists, vol.Optional("values"): exists, @@ -35,6 +36,9 @@ def exists(value): SERVICE_SCHEMA = vol.Schema( { vol.Required("description"): str, + vol.Optional("target"): vol.Any( + selector.TargetSelector.CONFIG_SCHEMA, None # pylint: disable=no-member + ), vol.Optional("fields"): vol.Schema({str: FIELD_SCHEMA}), } ) @@ -60,7 +64,9 @@ def validate_services(integration: Integration): """Validate services.""" # Find if integration uses services has_services = grep_dir( - integration.path, "**/*.py", r"hass\.services\.(register|async_register)" + integration.path, + "**/*.py", + r"(hass\.services\.(register|async_register))|async_register_entity_service", ) if not has_services: From 0bfcd5e1ee44254292c775a7a82f6f1a02c57461 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 16 Feb 2021 10:26:38 +0100 Subject: [PATCH 0505/1818] Use explicit open/close for covers (#46602) --- .../components/google_assistant/trait.py | 12 ++++++------ tests/components/google_assistant/test_trait.py | 17 +++++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 9e0da39b58a266..8b0bde09010e7a 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1685,17 +1685,17 @@ async def execute(self, command, data, params, challenge): else: position = params["openPercent"] - if features & cover.SUPPORT_SET_POSITION: - service = cover.SERVICE_SET_COVER_POSITION - if position > 0: - should_verify = True - svc_params[cover.ATTR_POSITION] = position - elif position == 0: + if position == 0: service = cover.SERVICE_CLOSE_COVER should_verify = False elif position == 100: service = cover.SERVICE_OPEN_COVER should_verify = True + elif features & cover.SUPPORT_SET_POSITION: + service = cover.SERVICE_SET_COVER_POSITION + if position > 0: + should_verify = True + svc_params[cover.ATTR_POSITION] = position else: raise SmartHomeError( ERR_NOT_SUPPORTED, "No support for partial open close" diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 74e8ab21eb0c23..a9b1e9a97fb452 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1933,14 +1933,18 @@ async def test_openclose_cover(hass): assert trt.sync_attributes() == {} assert trt.query_attributes() == {"openPercent": 75} - calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) + calls_set = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) + calls_open = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER) + await trt.execute(trait.COMMAND_OPENCLOSE, BASIC_DATA, {"openPercent": 50}, {}) await trt.execute( trait.COMMAND_OPENCLOSE_RELATIVE, BASIC_DATA, {"openRelativePercent": 50}, {} ) - assert len(calls) == 2 - assert calls[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50} - assert calls[1].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 100} + assert len(calls_set) == 1 + assert calls_set[0].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 50} + + assert len(calls_open) == 1 + assert calls_open[0].data == {ATTR_ENTITY_ID: "cover.bla"} async def test_openclose_cover_unknown_state(hass): @@ -2111,6 +2115,7 @@ async def test_openclose_cover_secure(hass, device_class): assert trt.query_attributes() == {"openPercent": 75} calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) + calls_close = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_CLOSE_COVER) # No challenge data with pytest.raises(error.ChallengeNeeded) as err: @@ -2136,8 +2141,8 @@ async def test_openclose_cover_secure(hass, device_class): # no challenge on close await trt.execute(trait.COMMAND_OPENCLOSE, PIN_DATA, {"openPercent": 0}, {}) - assert len(calls) == 2 - assert calls[1].data == {ATTR_ENTITY_ID: "cover.bla", cover.ATTR_POSITION: 0} + assert len(calls_close) == 1 + assert calls_close[0].data == {ATTR_ENTITY_ID: "cover.bla"} @pytest.mark.parametrize( From a6912277eb014571fee3318ec96e8c4a7add831f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 16 Feb 2021 11:00:08 +0100 Subject: [PATCH 0506/1818] Remove defunct CoinMarketCap integration (#46615) --- .../components/coinmarketcap/__init__.py | 1 - .../components/coinmarketcap/manifest.json | 7 - .../components/coinmarketcap/sensor.py | 164 ------------------ requirements_all.txt | 3 - requirements_test_all.txt | 3 - tests/components/coinmarketcap/__init__.py | 1 - tests/components/coinmarketcap/test_sensor.py | 42 ----- 7 files changed, 221 deletions(-) delete mode 100644 homeassistant/components/coinmarketcap/__init__.py delete mode 100644 homeassistant/components/coinmarketcap/manifest.json delete mode 100644 homeassistant/components/coinmarketcap/sensor.py delete mode 100644 tests/components/coinmarketcap/__init__.py delete mode 100644 tests/components/coinmarketcap/test_sensor.py diff --git a/homeassistant/components/coinmarketcap/__init__.py b/homeassistant/components/coinmarketcap/__init__.py deleted file mode 100644 index 0cdb5a16a4aec5..00000000000000 --- a/homeassistant/components/coinmarketcap/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The coinmarketcap component.""" diff --git a/homeassistant/components/coinmarketcap/manifest.json b/homeassistant/components/coinmarketcap/manifest.json deleted file mode 100644 index e3f827f2718ba5..00000000000000 --- a/homeassistant/components/coinmarketcap/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "coinmarketcap", - "name": "CoinMarketCap", - "documentation": "https://www.home-assistant.io/integrations/coinmarketcap", - "requirements": ["coinmarketcap==5.0.3"], - "codeowners": [] -} diff --git a/homeassistant/components/coinmarketcap/sensor.py b/homeassistant/components/coinmarketcap/sensor.py deleted file mode 100644 index f3fe240c0bca37..00000000000000 --- a/homeassistant/components/coinmarketcap/sensor.py +++ /dev/null @@ -1,164 +0,0 @@ -"""Details about crypto currencies from CoinMarketCap.""" -from datetime import timedelta -import logging -from urllib.error import HTTPError - -from coinmarketcap import Market -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION, CONF_DISPLAY_CURRENCY -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity - -_LOGGER = logging.getLogger(__name__) - -ATTR_VOLUME_24H = "volume_24h" -ATTR_AVAILABLE_SUPPLY = "available_supply" -ATTR_CIRCULATING_SUPPLY = "circulating_supply" -ATTR_MARKET_CAP = "market_cap" -ATTR_PERCENT_CHANGE_24H = "percent_change_24h" -ATTR_PERCENT_CHANGE_7D = "percent_change_7d" -ATTR_PERCENT_CHANGE_1H = "percent_change_1h" -ATTR_PRICE = "price" -ATTR_RANK = "rank" -ATTR_SYMBOL = "symbol" -ATTR_TOTAL_SUPPLY = "total_supply" - -ATTRIBUTION = "Data provided by CoinMarketCap" - -CONF_CURRENCY_ID = "currency_id" -CONF_DISPLAY_CURRENCY_DECIMALS = "display_currency_decimals" - -DEFAULT_CURRENCY_ID = 1 -DEFAULT_DISPLAY_CURRENCY = "USD" -DEFAULT_DISPLAY_CURRENCY_DECIMALS = 2 - -ICON = "mdi:currency-usd" - -SCAN_INTERVAL = timedelta(minutes=15) - - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_CURRENCY_ID, default=DEFAULT_CURRENCY_ID): cv.positive_int, - vol.Optional(CONF_DISPLAY_CURRENCY, default=DEFAULT_DISPLAY_CURRENCY): vol.All( - cv.string, vol.Upper - ), - vol.Optional( - CONF_DISPLAY_CURRENCY_DECIMALS, default=DEFAULT_DISPLAY_CURRENCY_DECIMALS - ): vol.All(vol.Coerce(int), vol.Range(min=1)), - } -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the CoinMarketCap sensor.""" - currency_id = config[CONF_CURRENCY_ID] - display_currency = config[CONF_DISPLAY_CURRENCY] - display_currency_decimals = config[CONF_DISPLAY_CURRENCY_DECIMALS] - - try: - CoinMarketCapData(currency_id, display_currency).update() - except HTTPError: - _LOGGER.warning( - "Currency ID %s or display currency %s " - "is not available. Using 1 (bitcoin) " - "and USD", - currency_id, - display_currency, - ) - currency_id = DEFAULT_CURRENCY_ID - display_currency = DEFAULT_DISPLAY_CURRENCY - - add_entities( - [ - CoinMarketCapSensor( - CoinMarketCapData(currency_id, display_currency), - display_currency_decimals, - ) - ], - True, - ) - - -class CoinMarketCapSensor(Entity): - """Representation of a CoinMarketCap sensor.""" - - def __init__(self, data, display_currency_decimals): - """Initialize the sensor.""" - self.data = data - self.display_currency_decimals = display_currency_decimals - self._ticker = None - self._unit_of_measurement = self.data.display_currency - - @property - def name(self): - """Return the name of the sensor.""" - return self._ticker.get("name") - - @property - def state(self): - """Return the state of the sensor.""" - return round( - float( - self._ticker.get("quotes").get(self.data.display_currency).get("price") - ), - self.display_currency_decimals, - ) - - @property - def 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 device_state_attributes(self): - """Return the state attributes of the sensor.""" - return { - ATTR_VOLUME_24H: self._ticker.get("quotes") - .get(self.data.display_currency) - .get("volume_24h"), - ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_CIRCULATING_SUPPLY: self._ticker.get("circulating_supply"), - ATTR_MARKET_CAP: self._ticker.get("quotes") - .get(self.data.display_currency) - .get("market_cap"), - ATTR_PERCENT_CHANGE_24H: self._ticker.get("quotes") - .get(self.data.display_currency) - .get("percent_change_24h"), - ATTR_PERCENT_CHANGE_7D: self._ticker.get("quotes") - .get(self.data.display_currency) - .get("percent_change_7d"), - ATTR_PERCENT_CHANGE_1H: self._ticker.get("quotes") - .get(self.data.display_currency) - .get("percent_change_1h"), - ATTR_RANK: self._ticker.get("rank"), - ATTR_SYMBOL: self._ticker.get("symbol"), - ATTR_TOTAL_SUPPLY: self._ticker.get("total_supply"), - } - - def update(self): - """Get the latest data and updates the states.""" - self.data.update() - self._ticker = self.data.ticker.get("data") - - -class CoinMarketCapData: - """Get the latest data and update the states.""" - - def __init__(self, currency_id, display_currency): - """Initialize the data object.""" - self.currency_id = currency_id - self.display_currency = display_currency - self.ticker = None - - def update(self): - """Get the latest data from coinmarketcap.com.""" - - self.ticker = Market().ticker(self.currency_id, convert=self.display_currency) diff --git a/requirements_all.txt b/requirements_all.txt index 640c2ace4dff8e..6eb19726cb393b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -427,9 +427,6 @@ co2signal==0.4.2 # homeassistant.components.coinbase coinbase==2.1.0 -# homeassistant.components.coinmarketcap -coinmarketcap==5.0.3 - # homeassistant.scripts.check_config colorlog==4.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b77eaafb9dc83..11064b63db8a57 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -224,9 +224,6 @@ buienradar==1.0.4 # homeassistant.components.caldav caldav==0.7.1 -# homeassistant.components.coinmarketcap -coinmarketcap==5.0.3 - # homeassistant.scripts.check_config colorlog==4.7.2 diff --git a/tests/components/coinmarketcap/__init__.py b/tests/components/coinmarketcap/__init__.py deleted file mode 100644 index 9e9b871bbe2cac..00000000000000 --- a/tests/components/coinmarketcap/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the coinmarketcap component.""" diff --git a/tests/components/coinmarketcap/test_sensor.py b/tests/components/coinmarketcap/test_sensor.py deleted file mode 100644 index 369a006f568368..00000000000000 --- a/tests/components/coinmarketcap/test_sensor.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Tests for the CoinMarketCap sensor platform.""" -import json -from unittest.mock import patch - -import pytest - -from homeassistant.components.sensor import DOMAIN -from homeassistant.setup import async_setup_component - -from tests.common import assert_setup_component, load_fixture - -VALID_CONFIG = { - DOMAIN: { - "platform": "coinmarketcap", - "currency_id": 1027, - "display_currency": "EUR", - "display_currency_decimals": 3, - } -} - - -@pytest.fixture -async def setup_sensor(hass): - """Set up demo sensor component.""" - with assert_setup_component(1, DOMAIN): - with patch( - "coinmarketcap.Market.ticker", - return_value=json.loads(load_fixture("coinmarketcap.json")), - ): - await async_setup_component(hass, DOMAIN, VALID_CONFIG) - await hass.async_block_till_done() - - -async def test_setup(hass, setup_sensor): - """Test the setup with custom settings.""" - state = hass.states.get("sensor.ethereum") - assert state is not None - - assert state.name == "Ethereum" - assert state.state == "493.455" - assert state.attributes.get("symbol") == "ETH" - assert state.attributes.get("unit_of_measurement") == "EUR" From 713544e5ebd4727f28b524981092bf61aa889976 Mon Sep 17 00:00:00 2001 From: David McClosky Date: Tue, 16 Feb 2021 06:32:53 -0500 Subject: [PATCH 0507/1818] Bump python-vlc-telnet to 2.0.1 (#46608) Includes corresponding Home Assistant changes to avoid introducing regressions. --- CODEOWNERS | 2 +- homeassistant/components/vlc_telnet/manifest.json | 4 ++-- homeassistant/components/vlc_telnet/media_player.py | 9 ++++----- requirements_all.txt | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index ba727de5694f5c..d1bda212d2d449 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -510,7 +510,7 @@ homeassistant/components/vicare/* @oischinger homeassistant/components/vilfo/* @ManneW homeassistant/components/vivotek/* @HarlemSquirrel homeassistant/components/vizio/* @raman325 -homeassistant/components/vlc_telnet/* @rodripf +homeassistant/components/vlc_telnet/* @rodripf @dmcc homeassistant/components/volkszaehler/* @fabaff homeassistant/components/volumio/* @OnFreund homeassistant/components/waqi/* @andrey-git diff --git a/homeassistant/components/vlc_telnet/manifest.json b/homeassistant/components/vlc_telnet/manifest.json index f6e4aa04521467..37941e15458570 100644 --- a/homeassistant/components/vlc_telnet/manifest.json +++ b/homeassistant/components/vlc_telnet/manifest.json @@ -2,6 +2,6 @@ "domain": "vlc_telnet", "name": "VLC media player Telnet", "documentation": "https://www.home-assistant.io/integrations/vlc-telnet", - "requirements": ["python-telnet-vlc==1.0.4"], - "codeowners": ["@rodripf"] + "requirements": ["python-telnet-vlc==2.0.1"], + "codeowners": ["@rodripf", "@dmcc"] } diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 1f0d62b6ee830a..96690955fcc454 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -37,6 +37,7 @@ DEFAULT_NAME = "VLC-TELNET" DEFAULT_PORT = 4212 +MAX_VOLUME = 500 SUPPORT_VLC = ( SUPPORT_PAUSE @@ -210,17 +211,15 @@ def mute_volume(self, mute): """Mute the volume.""" if mute: self._volume_bkp = self._volume - self._volume = 0 - self._vlc.set_volume("0") + self.set_volume_level(0) else: - self._vlc.set_volume(str(self._volume_bkp)) - self._volume = self._volume_bkp + self.set_volume_level(self._volume_bkp) self._muted = mute def set_volume_level(self, volume): """Set volume level, range 0..1.""" - self._vlc.set_volume(str(volume * 500)) + self._vlc.set_volume(volume * MAX_VOLUME) self._volume = volume def media_play(self): diff --git a/requirements_all.txt b/requirements_all.txt index 6eb19726cb393b..1b2409a8e6fd93 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1822,7 +1822,7 @@ python-tado==0.10.0 python-telegram-bot==13.1 # homeassistant.components.vlc_telnet -python-telnet-vlc==1.0.4 +python-telnet-vlc==2.0.1 # homeassistant.components.twitch python-twitch-client==0.6.0 From add0d9d3eb8243eeca1de8b2ab0e9d6756005386 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 16 Feb 2021 09:00:09 -0500 Subject: [PATCH 0508/1818] Use core constants for yeelight (#46552) --- homeassistant/components/yeelight/__init__.py | 2 -- homeassistant/components/yeelight/config_flow.py | 3 +-- homeassistant/components/yeelight/light.py | 2 -- tests/components/yeelight/test_config_flow.py | 3 +-- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index b61df3f810b1be..f24847a2d54667 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -42,7 +42,6 @@ CONF_CUSTOM_EFFECTS = "custom_effects" CONF_NIGHTLIGHT_SWITCH_TYPE = "nightlight_switch_type" CONF_NIGHTLIGHT_SWITCH = "nightlight_switch" -CONF_DEVICE = "device" DATA_CONFIG_ENTRIES = "config_entries" DATA_CUSTOM_EFFECTS = "custom_effects" @@ -423,7 +422,6 @@ def is_nightlight_supported(self) -> bool: Uses brightness as it appears to be supported in both ceiling and other lights. """ - return self._nightlight_brightness is not None @property diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index 52f27932403056..f186c897a212b0 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -5,12 +5,11 @@ import yeelight from homeassistant import config_entries, exceptions -from homeassistant.const import CONF_HOST, CONF_ID, CONF_NAME +from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_ID, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from . import ( - CONF_DEVICE, CONF_MODE_MUSIC, CONF_MODEL, CONF_NIGHTLIGHT_SWITCH, diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index c256cfb23e04c6..e4044303ef0232 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -261,7 +261,6 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities ) -> None: """Set up Yeelight from a config entry.""" - custom_effects = _parse_custom_effects(hass.data[DOMAIN][DATA_CUSTOM_EFFECTS]) device = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][DATA_DEVICE] @@ -563,7 +562,6 @@ def _predefined_effects(self): @property def device_state_attributes(self): """Return the device specific state attributes.""" - attributes = { "flowing": self.device.is_color_flow_enabled, "music_mode": self._bulb.music_mode, diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index 8fa1ba5c9880b4..6a1508d78965fb 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -3,7 +3,6 @@ from homeassistant import config_entries from homeassistant.components.yeelight import ( - CONF_DEVICE, CONF_MODE_MUSIC, CONF_MODEL, CONF_NIGHTLIGHT_SWITCH, @@ -18,7 +17,7 @@ DOMAIN, NIGHTLIGHT_SWITCH_TYPE_LIGHT, ) -from homeassistant.const import CONF_HOST, CONF_ID, CONF_NAME +from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_ID, CONF_NAME from homeassistant.core import HomeAssistant from . import ( From 9d8ba6af967f86ed3097e83413754ceedd4ce52f Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 16 Feb 2021 15:28:25 +0100 Subject: [PATCH 0509/1818] Use update coordinator for Xioami Miio subdevices (#46251) Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 35 +++++++++++++++++-- .../xiaomi_miio/alarm_control_panel.py | 4 +-- homeassistant/components/xiaomi_miio/const.py | 2 ++ .../components/xiaomi_miio/gateway.py | 22 +++--------- homeassistant/components/xiaomi_miio/light.py | 2 +- .../components/xiaomi_miio/sensor.py | 29 +++++++-------- 6 files changed, 55 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index e81c35d39e4bf2..273fc53da5ae6f 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -1,7 +1,13 @@ """Support for Xiaomi Miio.""" +from datetime import timedelta +import logging + +from miio.gateway import GatewayException + from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( CONF_DEVICE, @@ -9,10 +15,13 @@ CONF_GATEWAY, CONF_MODEL, DOMAIN, + KEY_COORDINATOR, MODELS_SWITCH, ) from .gateway import ConnectXiaomiGateway +_LOGGER = logging.getLogger(__name__) + GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] SWITCH_PLATFORMS = ["switch"] @@ -56,8 +65,6 @@ async def async_setup_gateway_entry( return False gateway_info = gateway.gateway_info - hass.data[DOMAIN][entry.entry_id] = gateway.gateway_device - gateway_model = f"{gateway_info.model}-{gateway_info.hardware_version}" device_registry = await dr.async_get_registry(hass) @@ -71,6 +78,30 @@ async def async_setup_gateway_entry( sw_version=gateway_info.firmware_version, ) + async def async_update_data(): + """Fetch data from the subdevice.""" + try: + for sub_device in gateway.gateway_device.devices.values(): + await hass.async_add_executor_job(sub_device.update) + except GatewayException as ex: + raise UpdateFailed("Got exception while fetching the state") from ex + + # Create update coordinator + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name=name, + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=10), + ) + + hass.data[DOMAIN][entry.entry_id] = { + CONF_GATEWAY: gateway.gateway_device, + KEY_COORDINATOR: coordinator, + } + for component in GATEWAY_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) diff --git a/homeassistant/components/xiaomi_miio/alarm_control_panel.py b/homeassistant/components/xiaomi_miio/alarm_control_panel.py index 6880202cbd62e0..26421770771f3d 100644 --- a/homeassistant/components/xiaomi_miio/alarm_control_panel.py +++ b/homeassistant/components/xiaomi_miio/alarm_control_panel.py @@ -15,7 +15,7 @@ STATE_ALARM_DISARMED, ) -from .const import DOMAIN +from .const import CONF_GATEWAY, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi Gateway Alarm from a config entry.""" entities = [] - gateway = hass.data[DOMAIN][config_entry.entry_id] + gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY] entity = XiaomiGatewayAlarm( gateway, f"{config_entry.title} Alarm", diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 3726f7f709dbca..c0ddb6983409b3 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -7,6 +7,8 @@ CONF_MODEL = "model" CONF_MAC = "mac" +KEY_COORDINATOR = "coordinator" + MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ "chuangmi.plug.v1", diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index eb2f4cdf2eb267..356b19dc89a1ee 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -4,6 +4,7 @@ from miio import DeviceException, gateway from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN @@ -56,16 +57,16 @@ async def async_connect_gateway(self, host, token): return True -class XiaomiGatewayDevice(Entity): +class XiaomiGatewayDevice(CoordinatorEntity, Entity): """Representation of a base Xiaomi Gateway Device.""" - def __init__(self, sub_device, entry): + def __init__(self, coordinator, sub_device, entry): """Initialize the Xiaomi Gateway Device.""" + super().__init__(coordinator) self._sub_device = sub_device self._entry = entry self._unique_id = sub_device.sid self._name = f"{sub_device.name} ({sub_device.sid})" - self._available = False @property def unique_id(self): @@ -88,18 +89,3 @@ def device_info(self): "model": self._sub_device.model, "sw_version": self._sub_device.firmware_version, } - - @property - def available(self): - """Return true when state is known.""" - return self._available - - async def async_update(self): - """Fetch state from the sub device.""" - try: - await self.hass.async_add_executor_job(self._sub_device.update) - self._available = True - except gateway.GatewayException as ex: - if self._available: - self._available = False - _LOGGER.error("Got exception while fetching the state: %s", ex) diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index efe67a370c4a61..7f168cf0e3ee72 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -130,7 +130,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: - gateway = hass.data[DOMAIN][config_entry.entry_id] + gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY] # Gateway light if gateway.model not in [ GATEWAY_MODEL_AC_V1, diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index ab4df8cd98225f..821fe164ea97f5 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -2,13 +2,13 @@ from dataclasses import dataclass import logging -from miio import AirQualityMonitor, DeviceException # pylint: disable=import-error +from miio import AirQualityMonitor # pylint: disable=import-error +from miio import DeviceException from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, GATEWAY_MODEL_AC_V3, GATEWAY_MODEL_EU, - DeviceType, GatewayException, ) import voluptuous as vol @@ -31,7 +31,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN +from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) @@ -87,7 +87,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: - gateway = hass.data[DOMAIN][config_entry.entry_id] + gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY] # Gateway illuminance sensor if gateway.model not in [ GATEWAY_MODEL_AC_V1, @@ -102,16 +102,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) # Gateway sub devices sub_devices = gateway.devices + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] for sub_device in sub_devices.values(): - sensor_variables = None - if sub_device.type == DeviceType.SensorHT: - sensor_variables = ["temperature", "humidity"] - if sub_device.type == DeviceType.AqaraHT: - sensor_variables = ["temperature", "humidity", "pressure"] - if sensor_variables is not None: + sensor_variables = set(sub_device.status) & set(GATEWAY_SENSOR_TYPES) + if sensor_variables: entities.extend( [ - XiaomiGatewaySensor(sub_device, config_entry, variable) + XiaomiGatewaySensor( + coordinator, sub_device, config_entry, variable + ) for variable in sensor_variables ] ) @@ -240,9 +239,9 @@ async def async_update(self): class XiaomiGatewaySensor(XiaomiGatewayDevice): """Representation of a XiaomiGatewaySensor.""" - def __init__(self, sub_device, entry, data_key): + def __init__(self, coordinator, sub_device, entry, data_key): """Initialize the XiaomiSensor.""" - super().__init__(sub_device, entry) + super().__init__(coordinator, sub_device, entry) self._data_key = data_key self._unique_id = f"{sub_device.sid}-{data_key}" self._name = f"{data_key} ({sub_device.sid})".capitalize() @@ -288,9 +287,7 @@ def unique_id(self): @property def device_info(self): """Return the device info of the gateway.""" - return { - "identifiers": {(DOMAIN, self._gateway_device_id)}, - } + return {"identifiers": {(DOMAIN, self._gateway_device_id)}} @property def name(self): From f0e9ef421c264f882b73a9a62897e3982ec5f03d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 16 Feb 2021 15:48:06 +0100 Subject: [PATCH 0510/1818] Fix vlc_telnet state update (#46628) * Clean up * Add debug logs * Fix info lookup * Handle more errors * Guard get length and time --- .../components/vlc_telnet/media_player.py | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 96690955fcc454..a7d5ee0a211eb5 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -1,7 +1,13 @@ """Provide functionality to interact with the vlc telnet interface.""" import logging -from python_telnet_vlc import ConnectionError as ConnErr, VLCTelnet +from python_telnet_vlc import ( + CommandError, + ConnectionError as ConnErr, + LuaError, + ParseError, + VLCTelnet, +) import voluptuous as vol from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity @@ -82,7 +88,6 @@ class VlcDevice(MediaPlayerEntity): def __init__(self, name, host, port, passwd): """Initialize the vlc device.""" - self._instance = None self._name = name self._volume = None self._muted = None @@ -94,7 +99,7 @@ def __init__(self, name, host, port, passwd): self._port = port self._password = passwd self._vlc = None - self._available = False + self._available = True self._volume_bkp = 0 self._media_artist = "" self._media_title = "" @@ -104,43 +109,54 @@ def update(self): if self._vlc is None: try: self._vlc = VLCTelnet(self._host, self._password, self._port) - self._state = STATE_IDLE - self._available = True - except (ConnErr, EOFError): - self._available = False + except (ConnErr, EOFError) as err: + if self._available: + _LOGGER.error("Connection error: %s", err) + self._available = False self._vlc = None - else: - try: - status = self._vlc.status() - if status: - if "volume" in status: - self._volume = int(status["volume"]) / 500.0 - else: - self._volume = None - if "state" in status: - state = status["state"] - if state == "playing": - self._state = STATE_PLAYING - elif state == "paused": - self._state = STATE_PAUSED - else: - self._state = STATE_IDLE + return + + self._state = STATE_IDLE + self._available = True + + try: + status = self._vlc.status() + _LOGGER.debug("Status: %s", status) + + if status: + if "volume" in status: + self._volume = int(status["volume"]) / 500.0 + else: + self._volume = None + if "state" in status: + state = status["state"] + if state == "playing": + self._state = STATE_PLAYING + elif state == "paused": + self._state = STATE_PAUSED else: self._state = STATE_IDLE + else: + self._state = STATE_IDLE + if self._state != STATE_IDLE: self._media_duration = self._vlc.get_length() self._media_position = self._vlc.get_time() - info = self._vlc.info() - if info: - self._media_artist = info[0].get("artist") - self._media_title = info[0].get("title") + info = self._vlc.info() + _LOGGER.debug("Info: %s", info) - except (ConnErr, EOFError): - self._available = False - self._vlc = None + if info: + self._media_artist = info.get(0, {}).get("artist") + self._media_title = info.get(0, {}).get("title") - return True + except (CommandError, LuaError, ParseError) as err: + _LOGGER.error("Command error: %s", err) + except (ConnErr, EOFError) as err: + if self._available: + _LOGGER.error("Connection error: %s", err) + self._available = False + self._vlc = None @property def name(self): From 68e78a2ddce52a6c6be8c43712d0e4a0280902c0 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 16 Feb 2021 09:49:31 -0500 Subject: [PATCH 0511/1818] Remove unnecessary constants from universal (#46537) --- homeassistant/components/universal/media_player.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index e4891dca68af7f..1834d22855c389 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -94,13 +94,10 @@ from homeassistant.helpers.service import async_call_from_config ATTR_ACTIVE_CHILD = "active_child" -ATTR_DATA = "data" CONF_ATTRS = "attributes" CONF_CHILDREN = "children" CONF_COMMANDS = "commands" -CONF_SERVICE = "service" -CONF_SERVICE_DATA = "service_data" OFF_STATES = [STATE_IDLE, STATE_OFF, STATE_UNAVAILABLE] @@ -124,7 +121,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the universal media players.""" - await async_setup_reload_service(hass, "universal", ["media_player"]) player = UniversalMediaPlayer( From 08201d146b372fbfd40d80a7d37fc853461ed6c4 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 16 Feb 2021 06:59:43 -0800 Subject: [PATCH 0512/1818] Separate HLS logic out of core StreamOutput to prepare for discontinuity (#46610) Separate the HLS stream view logic out of StreamOutput since the hls stream view is about to get more complex to track discontinuities. This makes the idle timeout, shutdown, and coupling between hls and record more explicit. --- homeassistant/components/camera/__init__.py | 14 +- homeassistant/components/stream/__init__.py | 151 ++++++++++---------- homeassistant/components/stream/const.py | 8 +- homeassistant/components/stream/core.py | 93 +----------- homeassistant/components/stream/hls.py | 92 ++++++++---- homeassistant/components/stream/recorder.py | 43 ++---- homeassistant/components/stream/worker.py | 11 +- tests/components/stream/test_hls.py | 22 ++- tests/components/stream/test_recorder.py | 78 +++++----- tests/components/stream/test_worker.py | 9 +- 10 files changed, 226 insertions(+), 295 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index ba61f15547360f..d0bee4e249f360 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -24,7 +24,11 @@ SERVICE_PLAY_MEDIA, ) from homeassistant.components.stream import Stream, create_stream -from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, OUTPUT_FORMATS +from homeassistant.components.stream.const import ( + FORMAT_CONTENT_TYPE, + HLS_OUTPUT, + OUTPUT_FORMATS, +) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_FILENAME, @@ -254,7 +258,7 @@ async def preload_stream(_): stream = await camera.create_stream() if not stream: continue - stream.add_provider("hls") + stream.hls_output() stream.start() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream) @@ -702,6 +706,8 @@ async def async_handle_play_stream_service(camera, service_call): async def _async_stream_endpoint_url(hass, camera, fmt): + if fmt != HLS_OUTPUT: + raise ValueError("Only format {HLS_OUTPUT} is supported") stream = await camera.create_stream() if not stream: raise HomeAssistantError( @@ -712,9 +718,9 @@ async def _async_stream_endpoint_url(hass, camera, fmt): camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) stream.keepalive = camera_prefs.preload_stream - stream.add_provider(fmt) + stream.hls_output() stream.start() - return stream.endpoint_url(fmt) + return stream.endpoint_url() async def async_handle_record_service(camera, call): diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index cdaa0faeb9502e..677d01e5006599 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -7,25 +7,25 @@ - Home Assistant URLs for viewing a stream - Access tokens for URLs for viewing a stream -A Stream consists of a background worker, and one or more output formats each -with their own idle timeout managed by the stream component. When an output -format is no longer in use, the stream component will expire it. When there -are no active output formats, the background worker is shut down and access -tokens are expired. Alternatively, a Stream can be configured with keepalive -to always keep workers active. +A Stream consists of a background worker and multiple output streams (e.g. hls +and recorder). The worker has a callback to retrieve the current active output +streams where it writes the decoded output packets. The HLS stream has an +inactivity idle timeout that expires the access token. When all output streams +are inactive, the background worker is shut down. Alternatively, a Stream +can be configured with keepalive to always keep workers active. """ import logging import secrets import threading import time -from types import MappingProxyType +from typing import List from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from .const import ( - ATTR_ENDPOINTS, + ATTR_HLS_ENDPOINT, ATTR_STREAMS, DOMAIN, MAX_SEGMENTS, @@ -33,8 +33,8 @@ STREAM_RESTART_INCREMENT, STREAM_RESTART_RESET_TIME, ) -from .core import PROVIDERS, IdleTimer -from .hls import async_setup_hls +from .core import IdleTimer, StreamOutput +from .hls import HlsStreamOutput, async_setup_hls _LOGGER = logging.getLogger(__name__) @@ -75,12 +75,10 @@ async def async_setup(hass, config): from .recorder import async_setup_recorder hass.data[DOMAIN] = {} - hass.data[DOMAIN][ATTR_ENDPOINTS] = {} hass.data[DOMAIN][ATTR_STREAMS] = [] # Setup HLS - hls_endpoint = async_setup_hls(hass) - hass.data[DOMAIN][ATTR_ENDPOINTS]["hls"] = hls_endpoint + hass.data[DOMAIN][ATTR_HLS_ENDPOINT] = async_setup_hls(hass) # Setup Recorder async_setup_recorder(hass) @@ -89,7 +87,6 @@ async def async_setup(hass, config): def shutdown(event): """Stop all stream workers.""" for stream in hass.data[DOMAIN][ATTR_STREAMS]: - stream.keepalive = False stream.stop() _LOGGER.info("Stopped stream workers") @@ -110,58 +107,53 @@ def __init__(self, hass, source, options=None): self.access_token = None self._thread = None self._thread_quit = threading.Event() - self._outputs = {} + self._hls = None + self._hls_timer = None + self._recorder = None self._fast_restart_once = False if self.options is None: self.options = {} - def endpoint_url(self, fmt): - """Start the stream and returns a url for the output format.""" - if fmt not in self._outputs: - raise ValueError(f"Stream is not configured for format '{fmt}'") + def endpoint_url(self) -> str: + """Start the stream and returns a url for the hls endpoint.""" + if not self._hls: + raise ValueError("Stream is not configured for hls") if not self.access_token: self.access_token = secrets.token_hex() - return self.hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(self.access_token) - - def outputs(self): - """Return a copy of the stream outputs.""" - # A copy is returned so the caller can iterate through the outputs - # without concern about self._outputs being modified from another thread. - return MappingProxyType(self._outputs.copy()) - - def add_provider(self, fmt, timeout=OUTPUT_IDLE_TIMEOUT): - """Add provider output stream.""" - if not self._outputs.get(fmt): - - @callback - def idle_callback(): - if not self.keepalive and fmt in self._outputs: - self.remove_provider(self._outputs[fmt]) - self.check_idle() - - provider = PROVIDERS[fmt]( - self.hass, IdleTimer(self.hass, timeout, idle_callback) - ) - self._outputs[fmt] = provider - return self._outputs[fmt] + return self.hass.data[DOMAIN][ATTR_HLS_ENDPOINT].format(self.access_token) + + def outputs(self) -> List[StreamOutput]: + """Return the active stream outputs.""" + return [output for output in [self._hls, self._recorder] if output] - def remove_provider(self, provider): - """Remove provider output stream.""" - if provider.name in self._outputs: - self._outputs[provider.name].cleanup() - del self._outputs[provider.name] + def hls_output(self) -> StreamOutput: + """Return the hls output stream, creating if not already active.""" + if not self._hls: + self._hls = HlsStreamOutput(self.hass) + self._hls_timer = IdleTimer(self.hass, OUTPUT_IDLE_TIMEOUT, self._hls_idle) + self._hls_timer.start() + self._hls_timer.awake() + return self._hls - if not self._outputs: - self.stop() + @callback + def _hls_idle(self): + """Reset access token and cleanup stream due to inactivity.""" + self.access_token = None + if not self.keepalive: + self._hls.cleanup() + self._hls = None + self._hls_timer = None + self._check_idle() - def check_idle(self): - """Reset access token if all providers are idle.""" - if all([p.idle for p in self._outputs.values()]): - self.access_token = None + def _check_idle(self): + """Check if all outputs are idle and shut down worker.""" + if self.keepalive or self.outputs(): + return + self.stop() def start(self): - """Start a stream.""" + """Start stream decode worker.""" if self._thread is None or not self._thread.is_alive(): if self._thread is not None: # The thread must have crashed/exited. Join to clean up the @@ -215,21 +207,18 @@ def _run_worker(self): def _worker_finished(self): """Schedule cleanup of all outputs.""" - - @callback - def remove_outputs(): - for provider in self.outputs().values(): - self.remove_provider(provider) - - self.hass.loop.call_soon_threadsafe(remove_outputs) + self.hass.loop.call_soon_threadsafe(self.stop) def stop(self): """Remove outputs and access token.""" - self._outputs = {} self.access_token = None - - if not self.keepalive: - self._stop() + if self._hls: + self._hls.cleanup() + self._hls = None + if self._recorder: + self._recorder.save() + self._recorder = None + self._stop() def _stop(self): """Stop worker thread.""" @@ -242,25 +231,35 @@ def _stop(self): async def async_record(self, video_path, duration=30, lookback=5): """Make a .mp4 recording from a provided stream.""" + # Keep import here so that we can import stream integration without installing reqs + # pylint: disable=import-outside-toplevel + from .recorder import RecorderOutput + # Check for file access if not self.hass.config.is_allowed_path(video_path): raise HomeAssistantError(f"Can't write {video_path}, no access to path!") # Add recorder - recorder = self.outputs().get("recorder") - if recorder: + if self._recorder: raise HomeAssistantError( - f"Stream already recording to {recorder.video_path}!" + f"Stream already recording to {self._recorder.video_path}!" ) - recorder = self.add_provider("recorder", timeout=duration) - recorder.video_path = video_path - + self._recorder = RecorderOutput(self.hass) + self._recorder.video_path = video_path self.start() # Take advantage of lookback - hls = self.outputs().get("hls") - if lookback > 0 and hls: - num_segments = min(int(lookback // hls.target_duration), MAX_SEGMENTS) + if lookback > 0 and self._hls: + num_segments = min(int(lookback // self._hls.target_duration), MAX_SEGMENTS) # Wait for latest segment, then add the lookback - await hls.recv() - recorder.prepend(list(hls.get_segment())[-num_segments:]) + await self._hls.recv() + self._recorder.prepend(list(self._hls.get_segment())[-num_segments:]) + + @callback + def save_recording(): + if self._recorder: + self._recorder.save() + self._recorder = None + self._check_idle() + + IdleTimer(self.hass, duration, save_recording).start() diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 41df806d020c4f..55f447a9a69e44 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -1,10 +1,14 @@ """Constants for Stream component.""" DOMAIN = "stream" -ATTR_ENDPOINTS = "endpoints" +ATTR_HLS_ENDPOINT = "hls_endpoint" ATTR_STREAMS = "streams" -OUTPUT_FORMATS = ["hls"] +HLS_OUTPUT = "hls" +OUTPUT_FORMATS = [HLS_OUTPUT] +OUTPUT_CONTAINER_FORMAT = "mp4" +OUTPUT_VIDEO_CODECS = {"hevc", "h264"} +OUTPUT_AUDIO_CODECS = {"aac", "mp3"} FORMAT_CONTENT_TYPE = {"hls": "application/vnd.apple.mpegurl"} diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 31c7940b8e1372..4fc70eb856fd8f 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -1,8 +1,7 @@ """Provides core stream functionality.""" -import asyncio -from collections import deque +import abc import io -from typing import Any, Callable, List +from typing import Callable from aiohttp import web import attr @@ -10,11 +9,8 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import async_call_later -from homeassistant.util.decorator import Registry -from .const import ATTR_STREAMS, DOMAIN, MAX_SEGMENTS - -PROVIDERS = Registry() +from .const import ATTR_STREAMS, DOMAIN @attr.s @@ -78,86 +74,18 @@ def fire(self, _now=None): self._callback() -class StreamOutput: +class StreamOutput(abc.ABC): """Represents a stream output.""" - def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: + def __init__(self, hass: HomeAssistant): """Initialize a stream output.""" self._hass = hass - self._idle_timer = idle_timer - self._cursor = None - self._event = asyncio.Event() - self._segments = deque(maxlen=MAX_SEGMENTS) - - @property - def name(self) -> str: - """Return provider name.""" - return None - - @property - def idle(self) -> bool: - """Return True if the output is idle.""" - return self._idle_timer.idle - - @property - def format(self) -> str: - """Return container format.""" - return None - - @property - def audio_codecs(self) -> str: - """Return desired audio codecs.""" - return None - - @property - def video_codecs(self) -> tuple: - """Return desired video codecs.""" - return None @property def container_options(self) -> Callable[[int], dict]: """Return Callable which takes a sequence number and returns container options.""" return None - @property - def segments(self) -> List[int]: - """Return current sequence from segments.""" - return [s.sequence for s in self._segments] - - @property - def target_duration(self) -> int: - """Return the max duration of any given segment in seconds.""" - segment_length = len(self._segments) - if not segment_length: - return 1 - durations = [s.duration for s in self._segments] - return round(max(durations)) or 1 - - def get_segment(self, sequence: int = None) -> Any: - """Retrieve a specific segment, or the whole list.""" - self._idle_timer.awake() - - if not sequence: - return self._segments - - for segment in self._segments: - if segment.sequence == sequence: - return segment - return None - - async def recv(self) -> Segment: - """Wait for and retrieve the latest segment.""" - last_segment = max(self.segments, default=0) - if self._cursor is None or self._cursor <= last_segment: - await self._event.wait() - - if not self._segments: - return None - - segment = self.get_segment()[-1] - self._cursor = segment.sequence - return segment - def put(self, segment: Segment) -> None: """Store output.""" self._hass.loop.call_soon_threadsafe(self._async_put, segment) @@ -165,17 +93,6 @@ def put(self, segment: Segment) -> None: @callback def _async_put(self, segment: Segment) -> None: """Store output from event loop.""" - # Start idle timeout when we start receiving data - self._idle_timer.start() - self._segments.append(segment) - self._event.set() - self._event.clear() - - def cleanup(self): - """Handle cleanup.""" - self._event.set() - self._idle_timer.clear() - self._segments = deque(maxlen=MAX_SEGMENTS) class StreamView(HomeAssistantView): diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index bd5fbd5e9aebb1..57894d177113be 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -1,13 +1,15 @@ """Provide functionality to stream HLS.""" +import asyncio +from collections import deque import io -from typing import Callable +from typing import Any, Callable, List from aiohttp import web from homeassistant.core import callback -from .const import FORMAT_CONTENT_TYPE, NUM_PLAYLIST_SEGMENTS -from .core import PROVIDERS, StreamOutput, StreamView +from .const import FORMAT_CONTENT_TYPE, MAX_SEGMENTS, NUM_PLAYLIST_SEGMENTS +from .core import Segment, StreamOutput, StreamView from .fmp4utils import get_codec_string, get_init, get_m4s @@ -48,8 +50,7 @@ def render(track): async def handle(self, request, stream, sequence): """Return m3u8 playlist.""" - track = stream.add_provider("hls") - stream.start() + track = stream.hls_output() # Wait for a segment to be ready if not track.segments: if not await track.recv(): @@ -102,8 +103,7 @@ def render(self, track): async def handle(self, request, stream, sequence): """Return m3u8 playlist.""" - track = stream.add_provider("hls") - stream.start() + track = stream.hls_output() # Wait for a segment to be ready if not track.segments: if not await track.recv(): @@ -121,7 +121,7 @@ class HlsInitView(StreamView): async def handle(self, request, stream, sequence): """Return init.mp4.""" - track = stream.add_provider("hls") + track = stream.hls_output() segments = track.get_segment() if not segments: return web.HTTPNotFound() @@ -138,7 +138,7 @@ class HlsSegmentView(StreamView): async def handle(self, request, stream, sequence): """Return fmp4 segment.""" - track = stream.add_provider("hls") + track = stream.hls_output() segment = track.get_segment(int(sequence)) if not segment: return web.HTTPNotFound() @@ -149,29 +149,15 @@ async def handle(self, request, stream, sequence): ) -@PROVIDERS.register("hls") class HlsStreamOutput(StreamOutput): """Represents HLS Output formats.""" - @property - def name(self) -> str: - """Return provider name.""" - return "hls" - - @property - def format(self) -> str: - """Return container format.""" - return "mp4" - - @property - def audio_codecs(self) -> str: - """Return desired audio codecs.""" - return {"aac", "mp3"} - - @property - def video_codecs(self) -> tuple: - """Return desired video codecs.""" - return {"hevc", "h264"} + def __init__(self, hass) -> None: + """Initialize HlsStreamOutput.""" + super().__init__(hass) + self._cursor = None + self._event = asyncio.Event() + self._segments = deque(maxlen=MAX_SEGMENTS) @property def container_options(self) -> Callable[[int], dict]: @@ -182,3 +168,51 @@ def container_options(self) -> Callable[[int], dict]: "avoid_negative_ts": "make_non_negative", "fragment_index": str(sequence), } + + @property + def segments(self) -> List[int]: + """Return current sequence from segments.""" + return [s.sequence for s in self._segments] + + @property + def target_duration(self) -> int: + """Return the max duration of any given segment in seconds.""" + segment_length = len(self._segments) + if not segment_length: + return 1 + durations = [s.duration for s in self._segments] + return round(max(durations)) or 1 + + def get_segment(self, sequence: int = None) -> Any: + """Retrieve a specific segment, or the whole list.""" + if not sequence: + return self._segments + + for segment in self._segments: + if segment.sequence == sequence: + return segment + return None + + async def recv(self) -> Segment: + """Wait for and retrieve the latest segment.""" + last_segment = max(self.segments, default=0) + if self._cursor is None or self._cursor <= last_segment: + await self._event.wait() + + if not self._segments: + return None + + segment = self.get_segment()[-1] + self._cursor = segment.sequence + return segment + + def _async_put(self, segment: Segment) -> None: + """Store output from event loop.""" + self._segments.append(segment) + self._event.set() + self._event.clear() + + def cleanup(self): + """Handle cleanup.""" + self._event.set() + self._segments = deque(maxlen=MAX_SEGMENTS) diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 7db9997f870688..0fc3d84b1b9471 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -6,9 +6,10 @@ import av -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import callback -from .core import PROVIDERS, IdleTimer, Segment, StreamOutput +from .const import OUTPUT_CONTAINER_FORMAT +from .core import Segment, StreamOutput _LOGGER = logging.getLogger(__name__) @@ -18,7 +19,7 @@ def async_setup_recorder(hass): """Only here so Provider Registry works.""" -def recorder_save_worker(file_out: str, segments: List[Segment], container_format: str): +def recorder_save_worker(file_out: str, segments: List[Segment], container_format): """Handle saving stream.""" if not os.path.exists(os.path.dirname(file_out)): os.makedirs(os.path.dirname(file_out), exist_ok=True) @@ -68,51 +69,31 @@ def recorder_save_worker(file_out: str, segments: List[Segment], container_forma output.close() -@PROVIDERS.register("recorder") class RecorderOutput(StreamOutput): """Represents HLS Output formats.""" - def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: + def __init__(self, hass) -> None: """Initialize recorder output.""" - super().__init__(hass, idle_timer) + super().__init__(hass) self.video_path = None self._segments = [] - @property - def name(self) -> str: - """Return provider name.""" - return "recorder" - - @property - def format(self) -> str: - """Return container format.""" - return "mp4" - - @property - def audio_codecs(self) -> str: - """Return desired audio codec.""" - return {"aac", "mp3"} - - @property - def video_codecs(self) -> tuple: - """Return desired video codecs.""" - return {"hevc", "h264"} + def _async_put(self, segment: Segment) -> None: + """Store output.""" + self._segments.append(segment) def prepend(self, segments: List[Segment]) -> None: """Prepend segments to existing list.""" - own_segments = self.segments - segments = [s for s in segments if s.sequence not in own_segments] + segments = [s for s in segments if s.sequence not in self._segments] self._segments = segments + self._segments - def cleanup(self): + def save(self): """Write recording and clean up.""" _LOGGER.debug("Starting recorder worker thread") thread = threading.Thread( name="recorder_save_worker", target=recorder_save_worker, - args=(self.video_path, self._segments, self.format), + args=(self.video_path, self._segments, OUTPUT_CONTAINER_FORMAT), ) thread.start() - - super().cleanup() self._segments = [] diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 2050787a71400f..41cb4bafd90a45 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -9,6 +9,9 @@ MAX_MISSING_DTS, MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, + OUTPUT_AUDIO_CODECS, + OUTPUT_CONTAINER_FORMAT, + OUTPUT_VIDEO_CODECS, PACKETS_TO_WAIT_FOR_AUDIO, STREAM_TIMEOUT, ) @@ -29,7 +32,7 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): output = av.open( segment, mode="w", - format=stream_output.format, + format=OUTPUT_CONTAINER_FORMAT, container_options={ "video_track_timescale": str(int(1 / video_stream.time_base)), **container_options, @@ -38,7 +41,7 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): vstream = output.add_stream(template=video_stream) # Check if audio is requested astream = None - if audio_stream and audio_stream.name in stream_output.audio_codecs: + if audio_stream and audio_stream.name in OUTPUT_AUDIO_CODECS: astream = output.add_stream(template=audio_stream) return StreamBuffer(segment, output, vstream, astream) @@ -65,8 +68,8 @@ def reset(self, video_pts): # Fetch the latest StreamOutputs, which may have changed since the # worker started. self._outputs = [] - for stream_output in self._outputs_callback().values(): - if self._video_stream.name not in stream_output.video_codecs: + for stream_output in self._outputs_callback(): + if self._video_stream.name not in OUTPUT_VIDEO_CODECS: continue buffer = create_stream_buffer( stream_output, self._video_stream, self._audio_stream, self._sequence diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 7811cac2a2af75..2a53e2c5169650 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -45,7 +45,7 @@ def hls_stream(hass, hass_client): async def create_client_for_stream(stream): http_client = await hass_client() - parsed_url = urlparse(stream.endpoint_url("hls")) + parsed_url = urlparse(stream.endpoint_url()) return HlsClient(http_client, parsed_url) return create_client_for_stream @@ -87,7 +87,7 @@ async def test_hls_stream(hass, hls_stream, stream_worker_sync): stream = create_stream(hass, source) # Request stream - stream.add_provider("hls") + stream.hls_output() stream.start() hls_client = await hls_stream(stream) @@ -128,9 +128,9 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): stream = create_stream(hass, source) # Request stream - stream.add_provider("hls") + stream.hls_output() stream.start() - url = stream.endpoint_url("hls") + url = stream.endpoint_url() http_client = await hass_client() @@ -168,12 +168,10 @@ async def test_stream_ended(hass, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() stream = create_stream(hass, source) - track = stream.add_provider("hls") # Request stream - stream.add_provider("hls") + track = stream.hls_output() stream.start() - stream.endpoint_url("hls") # Run it dead while True: @@ -199,7 +197,7 @@ async def test_stream_keepalive(hass): # Setup demo HLS track source = "test_stream_keepalive_source" stream = create_stream(hass, source) - track = stream.add_provider("hls") + track = stream.hls_output() track.num_segments = 2 stream.start() @@ -230,12 +228,12 @@ def time_side_effect(): stream.stop() -async def test_hls_playlist_view_no_output(hass, hass_client, hls_stream): +async def test_hls_playlist_view_no_output(hass, hls_stream): """Test rendering the hls playlist with no output segments.""" await async_setup_component(hass, "stream", {"stream": {}}) stream = create_stream(hass, STREAM_SOURCE) - stream.add_provider("hls") + stream.hls_output() hls_client = await hls_stream(stream) @@ -250,7 +248,7 @@ async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync): stream = create_stream(hass, STREAM_SOURCE) stream_worker_sync.pause() - hls = stream.add_provider("hls") + hls = stream.hls_output() hls.put(Segment(1, SEQUENCE_BYTES, DURATION)) await hass.async_block_till_done() @@ -277,7 +275,7 @@ async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): stream = create_stream(hass, STREAM_SOURCE) stream_worker_sync.pause() - hls = stream.add_provider("hls") + hls = stream.hls_output() hls_client = await hls_stream(stream) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 9d418c360b1f8f..3930a5e237d64d 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -1,10 +1,12 @@ """The tests for hls streams.""" +import asyncio from datetime import timedelta import logging import os import threading from unittest.mock import patch +import async_timeout import av import pytest @@ -32,23 +34,30 @@ class SaveRecordWorkerSync: def __init__(self): """Initialize SaveRecordWorkerSync.""" self.reset() + self._segments = None - def recorder_save_worker(self, *args, **kwargs): + def recorder_save_worker(self, file_out, segments, container_format): """Mock method for patch.""" logging.debug("recorder_save_worker thread started") + self._segments = segments assert self._save_thread is None self._save_thread = threading.current_thread() self._save_event.set() + async def get_segments(self): + """Verify save worker thread was invoked and return saved segments.""" + with async_timeout.timeout(TEST_TIMEOUT): + assert await self._save_event.wait() + return self._segments + def join(self): - """Verify save worker was invoked and block on shutdown.""" - assert self._save_event.wait(timeout=TEST_TIMEOUT) + """Block until the record worker thread exist to ensure cleanup.""" self._save_thread.join() def reset(self): """Reset callback state for reuse in tests.""" self._save_thread = None - self._save_event = threading.Event() + self._save_event = asyncio.Event() @pytest.fixture() @@ -63,7 +72,7 @@ def record_worker_sync(hass): yield sync -async def test_record_stream(hass, hass_client, stream_worker_sync, record_worker_sync): +async def test_record_stream(hass, hass_client, record_worker_sync): """ Test record stream. @@ -73,28 +82,14 @@ async def test_record_stream(hass, hass_client, stream_worker_sync, record_worke """ await async_setup_component(hass, "stream", {"stream": {}}) - stream_worker_sync.pause() - # Setup demo track source = generate_h264_video() stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") - recorder = stream.add_provider("recorder") - while True: - segment = await recorder.recv() - if not segment: - break - segments = segment.sequence - if segments > 1: - stream_worker_sync.resume() - - stream.stop() - assert segments > 1 - - # Verify that the save worker was invoked, then block until its - # thread completes and is shutdown completely to avoid thread leaks. + segments = await record_worker_sync.get_segments() + assert len(segments) > 1 record_worker_sync.join() @@ -107,19 +102,24 @@ async def test_record_lookback( source = generate_h264_video() stream = create_stream(hass, source) + # Don't let the stream finish (and clean itself up) until the test has had + # a chance to perform lookback + stream_worker_sync.pause() + # Start an HLS feed to enable lookback - stream.add_provider("hls") - stream.start() + stream.hls_output() with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path", lookback=4) # This test does not need recorder cleanup since it is not fully exercised - + stream_worker_sync.resume() stream.stop() -async def test_recorder_timeout(hass, hass_client, stream_worker_sync): +async def test_recorder_timeout( + hass, hass_client, stream_worker_sync, record_worker_sync +): """ Test recorder timeout. @@ -137,9 +137,8 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") - recorder = stream.add_provider("recorder") - await recorder.recv() + assert not mock_timeout.called # Wait a minute future = dt_util.utcnow() + timedelta(minutes=1) @@ -149,9 +148,11 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): assert mock_timeout.called stream_worker_sync.resume() + # Verify worker is invoked, and do clean shutdown of worker thread + await record_worker_sync.get_segments() + record_worker_sync.join() + stream.stop() - await hass.async_block_till_done() - await hass.async_block_till_done() async def test_record_path_not_allowed(hass, hass_client): @@ -180,9 +181,7 @@ async def test_recorder_save(tmpdir): assert os.path.exists(filename) -async def test_record_stream_audio( - hass, hass_client, stream_worker_sync, record_worker_sync -): +async def test_record_stream_audio(hass, hass_client, record_worker_sync): """ Test treatment of different audio inputs. @@ -198,7 +197,6 @@ async def test_record_stream_audio( (None, 0), # no audio stream ): record_worker_sync.reset() - stream_worker_sync.pause() # Setup demo track source = generate_h264_video( @@ -207,22 +205,14 @@ async def test_record_stream_audio( stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") - recorder = stream.add_provider("recorder") - while True: - segment = await recorder.recv() - if not segment: - break - last_segment = segment - stream_worker_sync.resume() + segments = await record_worker_sync.get_segments() + last_segment = segments[-1] result = av.open(last_segment.segment, "r", format="mp4") assert len(result.streams.audio) == expected_audio_streams result.close() - stream.stop() - await hass.async_block_till_done() - # Verify that the save worker was invoked, then block until its - # thread completes and is shutdown completely to avoid thread leaks. + stream.stop() record_worker_sync.join() diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index b348d68fc86a7f..d9006c81ad5624 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -31,7 +31,6 @@ STREAM_SOURCE = "some-stream-source" # Formats here are arbitrary, not exercised by tests -STREAM_OUTPUT_FORMAT = "hls" AUDIO_STREAM_FORMAT = "mp3" VIDEO_STREAM_FORMAT = "h264" VIDEO_FRAME_RATE = 12 @@ -188,7 +187,7 @@ def open(self, stream_source, *args, **kwargs): async def async_decode_stream(hass, packets, py_av=None): """Start a stream worker that decodes incoming stream packets into output segments.""" stream = Stream(hass, STREAM_SOURCE) - stream.add_provider(STREAM_OUTPUT_FORMAT) + stream.hls_output() if not py_av: py_av = MockPyAv() @@ -207,7 +206,7 @@ async def async_decode_stream(hass, packets, py_av=None): async def test_stream_open_fails(hass): """Test failure on stream open.""" stream = Stream(hass, STREAM_SOURCE) - stream.add_provider(STREAM_OUTPUT_FORMAT) + stream.hls_output() with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") stream_worker(STREAM_SOURCE, {}, stream.outputs, threading.Event()) @@ -483,7 +482,7 @@ async def test_stream_stopped_while_decoding(hass): worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE) - stream.add_provider(STREAM_OUTPUT_FORMAT) + stream.hls_output() py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) @@ -510,7 +509,7 @@ async def test_update_stream_source(hass): worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE) - stream.add_provider(STREAM_OUTPUT_FORMAT) + stream.hls_output() # Note that keepalive is not set here. The stream is "restarted" even though # it is not stopping due to failure. From 960b5b7d86dcb32a720b8e87e5403df9e5ae466b Mon Sep 17 00:00:00 2001 From: Mark Coombes Date: Tue, 16 Feb 2021 12:06:20 -0500 Subject: [PATCH 0513/1818] Fix climate hold bug in ecobee (#46613) --- homeassistant/components/ecobee/climate.py | 15 +++++++++++---- homeassistant/components/ecobee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/ecobee/test_climate.py | 10 +++++----- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index c61428cbc78cc9..089f0950854749 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -559,7 +559,7 @@ def set_preset_mode(self, preset_mode): if preset_mode == PRESET_AWAY: self.data.ecobee.set_climate_hold( - self.thermostat_index, "away", "indefinite" + self.thermostat_index, "away", "indefinite", self.hold_hours() ) elif preset_mode == PRESET_TEMPERATURE: @@ -570,6 +570,7 @@ def set_preset_mode(self, preset_mode): self.thermostat_index, PRESET_TO_ECOBEE_HOLD[preset_mode], self.hold_preference(), + self.hold_hours(), ) elif preset_mode == PRESET_NONE: @@ -585,14 +586,20 @@ def set_preset_mode(self, preset_mode): if climate_ref is not None: self.data.ecobee.set_climate_hold( - self.thermostat_index, climate_ref, self.hold_preference() + self.thermostat_index, + climate_ref, + self.hold_preference(), + self.hold_hours(), ) else: _LOGGER.warning("Received unknown preset mode: %s", preset_mode) else: self.data.ecobee.set_climate_hold( - self.thermostat_index, preset_mode, self.hold_preference() + self.thermostat_index, + preset_mode, + self.hold_preference(), + self.hold_hours(), ) @property @@ -743,7 +750,7 @@ def hold_hours(self): "useEndTime2hour": 2, "useEndTime4hour": 4, } - return hold_hours_map.get(device_preference, 0) + return hold_hours_map.get(device_preference) def create_vacation(self, service_data): """Create a vacation with user-specified parameters.""" diff --git a/homeassistant/components/ecobee/manifest.json b/homeassistant/components/ecobee/manifest.json index 040744b27aa939..de7a7d325b3d2d 100644 --- a/homeassistant/components/ecobee/manifest.json +++ b/homeassistant/components/ecobee/manifest.json @@ -3,6 +3,6 @@ "name": "ecobee", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ecobee", - "requirements": ["python-ecobee-api==0.2.8"], + "requirements": ["python-ecobee-api==0.2.10"], "codeowners": ["@marthoc"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1b2409a8e6fd93..24bb508c713de6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1747,7 +1747,7 @@ python-clementine-remote==1.0.1 python-digitalocean==1.13.2 # homeassistant.components.ecobee -python-ecobee-api==0.2.8 +python-ecobee-api==0.2.10 # homeassistant.components.eq3btsmart # python-eq3bt==0.1.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 11064b63db8a57..e22ab3fa0e2dfd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -914,7 +914,7 @@ pysqueezebox==0.5.5 pysyncthru==0.7.0 # homeassistant.components.ecobee -python-ecobee-api==0.2.8 +python-ecobee-api==0.2.10 # homeassistant.components.darksky python-forecastio==1.4.0 diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index a9b9165d7134f9..975fabcf9ab330 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -209,14 +209,14 @@ async def test_set_temperature(ecobee_fixture, thermostat, data): data.reset_mock() thermostat.set_temperature(target_temp_low=20, target_temp_high=30) data.ecobee.set_hold_temp.assert_has_calls( - [mock.call(1, 30, 20, "nextTransition", 0)] + [mock.call(1, 30, 20, "nextTransition", None)] ) # Auto -> Hold data.reset_mock() thermostat.set_temperature(temperature=20) data.ecobee.set_hold_temp.assert_has_calls( - [mock.call(1, 25, 15, "nextTransition", 0)] + [mock.call(1, 25, 15, "nextTransition", None)] ) # Cool -> Hold @@ -224,7 +224,7 @@ async def test_set_temperature(ecobee_fixture, thermostat, data): ecobee_fixture["settings"]["hvacMode"] = "cool" thermostat.set_temperature(temperature=20.5) data.ecobee.set_hold_temp.assert_has_calls( - [mock.call(1, 20.5, 20.5, "nextTransition", 0)] + [mock.call(1, 20.5, 20.5, "nextTransition", None)] ) # Heat -> Hold @@ -232,7 +232,7 @@ async def test_set_temperature(ecobee_fixture, thermostat, data): ecobee_fixture["settings"]["hvacMode"] = "heat" thermostat.set_temperature(temperature=20) data.ecobee.set_hold_temp.assert_has_calls( - [mock.call(1, 20, 20, "nextTransition", 0)] + [mock.call(1, 20, 20, "nextTransition", None)] ) # Heat -> Auto @@ -311,7 +311,7 @@ def test_hold_hours(ecobee_fixture, thermostat): "askMe", ]: ecobee_fixture["settings"]["holdAction"] = action - assert thermostat.hold_hours() == 0 + assert thermostat.hold_hours() is None async def test_set_fan_mode_on(thermostat, data): From c45ce86e530e5fcda4f96c035ab1bc98f4d98a58 Mon Sep 17 00:00:00 2001 From: marecabo <23156476+marecabo@users.noreply.github.com> Date: Tue, 16 Feb 2021 20:36:32 +0100 Subject: [PATCH 0514/1818] Do not provide icon if device class is set in ESPHome config (#46650) --- homeassistant/components/esphome/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 68a8c00caed8b9..ad23afd47449f8 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -52,7 +52,7 @@ def _state(self) -> Optional[SensorState]: @property def icon(self) -> str: """Return the icon.""" - if self._static_info.icon == "": + if not self._static_info.icon or self._static_info.device_class: return None return self._static_info.icon @@ -73,14 +73,14 @@ def state(self) -> Optional[str]: @property def unit_of_measurement(self) -> str: """Return the unit the value is expressed in.""" - if self._static_info.unit_of_measurement == "": + if not self._static_info.unit_of_measurement: return None return self._static_info.unit_of_measurement @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" - if self._static_info.device_class == "": + if not self._static_info.device_class: return None return self._static_info.device_class From aaecd91407b631dc67bbb3e827b01802bb1a7e19 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 16 Feb 2021 12:10:26 -0800 Subject: [PATCH 0515/1818] Fix exception in stream idle callback (#46642) * Fix exception in stream idle callback Fix bug where idle timeout callback fails if the stream previously exited. * Add a test for stream idle timeout after stream is stopped. * Add clarifying comment to idle timer clear method * Clear hls timer only on stop --- homeassistant/components/stream/__init__.py | 3 ++ homeassistant/components/stream/core.py | 2 +- tests/components/stream/test_hls.py | 32 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 677d01e5006599..9b5afb38ed0559 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -212,6 +212,9 @@ def _worker_finished(self): def stop(self): """Remove outputs and access token.""" self.access_token = None + if self._hls_timer: + self._hls_timer.clear() + self._hls_timer = None if self._hls: self._hls.cleanup() self._hls = None diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 4fc70eb856fd8f..f7beb3aa75423d 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -63,7 +63,7 @@ def awake(self): self._unsub = async_call_later(self._hass, self._timeout, self.fire) def clear(self): - """Clear and disable the timer.""" + """Clear and disable the timer if it has not already fired.""" if self._unsub is not None: self._unsub() diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 2a53e2c5169650..55b79684b7bba3 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -159,6 +159,38 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): assert fail_response.status == HTTP_NOT_FOUND +async def test_stream_timeout_after_stop(hass, hass_client, stream_worker_sync): + """Test hls stream timeout after the stream has been stopped already.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream_worker_sync.pause() + + # Setup demo HLS track + source = generate_h264_video() + stream = create_stream(hass, source) + + # Request stream + stream.hls_output() + stream.start() + url = stream.endpoint_url() + + http_client = await hass_client() + + # Fetch playlist + parsed_url = urlparse(url) + playlist_response = await http_client.get(parsed_url.path) + assert playlist_response.status == 200 + + stream_worker_sync.resume() + stream.stop() + + # Wait 5 minutes and fire callback. Stream should already have been + # stopped so this is a no-op. + future = dt_util.utcnow() + timedelta(minutes=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + async def test_stream_ended(hass, stream_worker_sync): """Test hls stream packets ended.""" await async_setup_component(hass, "stream", {"stream": {}}) From e4496ed1e344ce9f560e54e096ceb6b508b58763 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Tue, 16 Feb 2021 13:07:18 -0800 Subject: [PATCH 0516/1818] Fix KeyError in comfoconnect percentage (#46654) --- homeassistant/components/comfoconnect/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index 1d7b9c1c0af791..7457d0ffad2817 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -96,7 +96,7 @@ def supported_features(self) -> int: @property def percentage(self) -> str: """Return the current speed percentage.""" - speed = self._ccb.data[SENSOR_FAN_SPEED_MODE] + speed = self._ccb.data.get(SENSOR_FAN_SPEED_MODE) if speed is None: return None return ranged_value_to_percentage(SPEED_RANGE, speed) From b38af0821b99f5b47a6c5ac944c30f79777ffe82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 17 Feb 2021 00:26:41 +0200 Subject: [PATCH 0517/1818] Fix version of pip in tox (#46656) --- script/bootstrap | 2 +- tox.ini | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/script/bootstrap b/script/bootstrap index 3ffac11852b89a..b641ec7e8c0384 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 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 diff --git a/tox.ini b/tox.ini index 9c9963c28eed03..a82c5afda79efb 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,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 commands = pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar {posargs} {toxinidir}/script/check_dirty From 318cbf29136f71f569e0be95482d2e86ea9cdfdb Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 17 Feb 2021 00:08:46 +0000 Subject: [PATCH 0518/1818] [ci skip] Translation update --- .../components/asuswrt/translations/pl.json | 4 +-- .../binary_sensor/translations/zh-Hans.json | 4 +-- .../keenetic_ndms2/translations/pl.json | 36 +++++++++++++++++++ .../keenetic_ndms2/translations/zh-Hant.json | 36 +++++++++++++++++++ .../components/plaato/translations/et.json | 2 +- .../components/tuya/translations/ca.json | 2 ++ .../components/tuya/translations/et.json | 2 ++ .../components/tuya/translations/no.json | 2 ++ .../components/tuya/translations/pl.json | 1 + .../components/tuya/translations/ru.json | 2 ++ .../components/tuya/translations/zh-Hant.json | 2 ++ .../xiaomi_miio/translations/ca.json | 12 ++++++- .../xiaomi_miio/translations/et.json | 12 ++++++- .../xiaomi_miio/translations/no.json | 12 ++++++- .../xiaomi_miio/translations/pl.json | 12 ++++++- .../xiaomi_miio/translations/ru.json | 12 ++++++- .../xiaomi_miio/translations/zh-Hant.json | 12 ++++++- 17 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/keenetic_ndms2/translations/pl.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/zh-Hant.json diff --git a/homeassistant/components/asuswrt/translations/pl.json b/homeassistant/components/asuswrt/translations/pl.json index b646c8e45038f5..9fd5d00b1c4545 100644 --- a/homeassistant/components/asuswrt/translations/pl.json +++ b/homeassistant/components/asuswrt/translations/pl.json @@ -32,9 +32,9 @@ "step": { "init": { "data": { - "consider_home": "Czas w sekundach, zanim urz\u0105dzenie zostanie uznane za \"nieobecne\"", + "consider_home": "Czas w sekundach, zanim urz\u0105dzenie utrzyma stan \"poza domem\"", "dnsmasq": "Lokalizacja w routerze plik\u00f3w dnsmasq.leases", - "interface": "Interfejs, z kt\u00f3rego chcesz uzyska\u0107 statystyki (np. Eth0, eth1 itp.)", + "interface": "Interfejs, z kt\u00f3rego chcesz uzyska\u0107 statystyki (np. eth0, eth1 itp.)", "require_ip": "Urz\u0105dzenia musz\u0105 mie\u0107 adres IP (w trybie punktu dost\u0119pu)", "track_unknown": "\u015aled\u017a nieznane / nienazwane urz\u0105dzenia" }, diff --git a/homeassistant/components/binary_sensor/translations/zh-Hans.json b/homeassistant/components/binary_sensor/translations/zh-Hans.json index 9254f667a48d92..a44e16d78e2250 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hans.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hans.json @@ -100,8 +100,8 @@ "on": "\u8fc7\u70ed" }, "light": { - "off": "\u6ca1\u6709\u5149\u7ebf", - "on": "\u68c0\u6d4b\u5230\u5149\u7ebf" + "off": "\u65e0\u5149", + "on": "\u6709\u5149" }, "lock": { "off": "\u4e0a\u9501", diff --git a/homeassistant/components/keenetic_ndms2/translations/pl.json b/homeassistant/components/keenetic_ndms2/translations/pl.json new file mode 100644 index 00000000000000..30e7b9a4fae76d --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/pl.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "name": "Nazwa", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Konfiguracja routera Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Czas przed oznaczeniem \"poza domem\"", + "include_arp": "U\u017cyj danych ARP (ignorowane, je\u015bli u\u017cywane s\u0105 dane hotspotu)", + "include_associated": "U\u017cyj danych skojarze\u0144 WiFi AP (ignorowane, je\u015bli u\u017cywane s\u0105 dane hotspotu)", + "interfaces": "Wybierz interfejsy do skanowania", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania", + "try_hotspot": "U\u017cyj danych \u201eip hotspot\u201d (najdok\u0142adniejsze)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/zh-Hant.json b/homeassistant/components/keenetic_ndms2/translations/zh-Hant.json new file mode 100644 index 00000000000000..7900f3a885430b --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/zh-Hant.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "name": "\u540d\u7a31", + "password": "\u5bc6\u78bc", + "port": "\u901a\u8a0a\u57e0", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "title": "\u8a2d\u5b9a Keenetic NDMS2 \u8def\u7531\u5668" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "\u5224\u5b9a\u5728\u5bb6\u9593\u9694", + "include_arp": "\u4f7f\u7528 ARP \u8cc7\u6599\uff08\u5047\u5982\u5df2\u4f7f\u7528 hotspot \u8cc7\u6599\u5247\u5ffd\u7565\uff09", + "include_associated": "\u4f7f\u7528 WiFi AP \u95dc\u806f\u8cc7\u6599\uff08\u5047\u5982\u5df2\u4f7f\u7528 hotspot \u8cc7\u6599\u5247\u5ffd\u7565\uff09", + "interfaces": "\u9078\u64c7\u6383\u63cf\u4ecb\u9762", + "scan_interval": "\u6383\u63cf\u9593\u8ddd", + "try_hotspot": "\u4f7f\u7528 'ip hotspot' \u8cc7\u6599\uff08\u6700\u7cbe\u6e96\uff09" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json index ec7b7e4b1a409c..66a7b252a87c64 100644 --- a/homeassistant/components/plaato/translations/et.json +++ b/homeassistant/components/plaato/translations/et.json @@ -24,7 +24,7 @@ }, "user": { "data": { - "device_name": "Pang oma seadmele nimi", + "device_name": "Pane oma seadmele nimi", "device_type": "Plaato seadme t\u00fc\u00fcp" }, "description": "Kas alustan seadistamist?", diff --git a/homeassistant/components/tuya/translations/ca.json b/homeassistant/components/tuya/translations/ca.json index 908cf287eebe83..a00d9683141f1f 100644 --- a/homeassistant/components/tuya/translations/ca.json +++ b/homeassistant/components/tuya/translations/ca.json @@ -40,8 +40,10 @@ "max_temp": "Temperatura desitjada m\u00e0xima (utilitza min i max = 0 per defecte)", "min_kelvin": "Temperatura del color m\u00ednima suportada, en Kelvin", "min_temp": "Temperatura desitjada m\u00ednima (utilitza min i max = 0 per defecte)", + "set_temp_divided": "Utilitza el valor de temperatura dividit per a ordres de configuraci\u00f3 de temperatura", "support_color": "For\u00e7a el suport de color", "temp_divider": "Divisor del valor de temperatura (0 = predeterminat)", + "temp_step_override": "Pas de temperatura objectiu", "tuya_max_coltemp": "Temperatura de color m\u00e0xima enviada pel dispositiu", "unit_of_measurement": "Unitat de temperatura utilitzada pel dispositiu" }, diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json index 967b38cdb82330..0fc1297ce7c461 100644 --- a/homeassistant/components/tuya/translations/et.json +++ b/homeassistant/components/tuya/translations/et.json @@ -40,8 +40,10 @@ "max_temp": "Maksimaalne sihttemperatuur (vaikimisi kasuta min ja max = 0)", "min_kelvin": "Minimaalne v\u00f5imalik v\u00e4rvitemperatuur (Kelvinites)", "min_temp": "Minimaalne sihttemperatuur (vaikimisi kasuta min ja max = 0)", + "set_temp_divided": "M\u00e4\u00e4ratud temperatuuri k\u00e4su jaoks kasuta jagatud temperatuuri v\u00e4\u00e4rtust", "support_color": "Luba v\u00e4rvuse juhtimine", "temp_divider": "Temperatuuri v\u00e4\u00e4rtuse eraldaja (0 = kasuta vaikev\u00e4\u00e4rtust)", + "temp_step_override": "Sihttemperatuuri samm", "tuya_max_coltemp": "Seadme teatatud maksimaalne v\u00e4rvitemperatuur", "unit_of_measurement": "Seadme temperatuuri\u00fchik" }, diff --git a/homeassistant/components/tuya/translations/no.json b/homeassistant/components/tuya/translations/no.json index d0c1a3ca1882a1..d02a88f4097c1d 100644 --- a/homeassistant/components/tuya/translations/no.json +++ b/homeassistant/components/tuya/translations/no.json @@ -40,8 +40,10 @@ "max_temp": "Maks m\u00e5ltemperatur (bruk min og maks = 0 for standard)", "min_kelvin": "Min fargetemperatur st\u00f8ttet i kelvin", "min_temp": "Min m\u00e5ltemperatur (bruk min og maks = 0 for standard)", + "set_temp_divided": "Bruk delt temperaturverdi for innstilt temperaturkommando", "support_color": "Tving fargest\u00f8tte", "temp_divider": "Deler temperaturverdier (0 = bruk standard)", + "temp_step_override": "Trinn for m\u00e5ltemperatur", "tuya_max_coltemp": "Maks fargetemperatur rapportert av enheten", "unit_of_measurement": "Temperaturenhet som brukes av enheten" }, diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index dc57f064d57a71..742ac600c62d9c 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -40,6 +40,7 @@ "max_temp": "Maksymalna temperatura docelowa (u\u017cyj min i max = 0 dla warto\u015bci domy\u015blnej)", "min_kelvin": "Minimalna obs\u0142ugiwana temperatura barwy w kelwinach", "min_temp": "Minimalna temperatura docelowa (u\u017cyj min i max = 0 dla warto\u015bci domy\u015blnej)", + "set_temp_divided": "U\u017cyj podzielonej warto\u015bci temperatury dla polecenia ustawienia temperatury", "support_color": "Wymu\u015b obs\u0142ug\u0119 kolor\u00f3w", "temp_divider": "Dzielnik warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", "tuya_max_coltemp": "Maksymalna temperatura barwy raportowana przez urz\u0105dzenie", diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index b98c6c8e9cd0c8..5d887710230e6a 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -40,8 +40,10 @@ "max_temp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 min \u0438 max = 0)", "min_kelvin": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u0432 \u043a\u0435\u043b\u044c\u0432\u0438\u043d\u0430\u0445)", "min_temp": "\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 min \u0438 max = 0)", + "set_temp_divided": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", "support_color": "\u041f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0446\u0432\u0435\u0442\u0430", "temp_divider": "\u0414\u0435\u043b\u0438\u0442\u0435\u043b\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b (0 = \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e)", + "temp_step_override": "\u0428\u0430\u0433 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u044b", "tuya_max_coltemp": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430, \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c\u0430\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c", "unit_of_measurement": "\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, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c" }, diff --git a/homeassistant/components/tuya/translations/zh-Hant.json b/homeassistant/components/tuya/translations/zh-Hant.json index 08871c3108e333..7221c86eb6370f 100644 --- a/homeassistant/components/tuya/translations/zh-Hant.json +++ b/homeassistant/components/tuya/translations/zh-Hant.json @@ -40,8 +40,10 @@ "max_temp": "\u6700\u9ad8\u76ee\u6a19\u8272\u6eab\uff08\u4f7f\u7528\u6700\u4f4e\u8207\u6700\u9ad8 = 0 \u4f7f\u7528\u9810\u8a2d\uff09", "min_kelvin": "Kelvin \u652f\u63f4\u6700\u4f4e\u8272\u6eab", "min_temp": "\u6700\u4f4e\u76ee\u6a19\u8272\u6eab\uff08\u4f7f\u7528\u6700\u4f4e\u8207\u6700\u9ad8 = 0 \u4f7f\u7528\u9810\u8a2d\uff09", + "set_temp_divided": "\u4f7f\u7528\u5206\u9694\u865f\u6eab\u5ea6\u503c\u4ee5\u57f7\u884c\u8a2d\u5b9a\u6eab\u5ea6\u6307\u4ee4", "support_color": "\u5f37\u5236\u8272\u6eab\u652f\u63f4", "temp_divider": "\u8272\u6eab\u503c\u5206\u914d\u5668\uff080 = \u4f7f\u7528\u9810\u8a2d\uff09", + "temp_step_override": "\u76ee\u6a19\u6eab\u5ea6\u8a2d\u5b9a", "tuya_max_coltemp": "\u88dd\u7f6e\u56de\u5831\u6700\u9ad8\u8272\u6eab", "unit_of_measurement": "\u88dd\u7f6e\u6240\u4f7f\u7528\u4e4b\u6eab\u5ea6\u55ae\u4f4d" }, diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index 5183157371beeb..3c666fe4ef1c2a 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", - "no_device_selected": "No hi ha cap dispositiu seleccionat, selecciona'n un." + "no_device_selected": "No hi ha cap dispositiu seleccionat, selecciona'n un.", + "unknown_device": "No es reconeix el model del dispositiu, no es pot configurar el dispositiu mitjan\u00e7ant el flux de configuraci\u00f3." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "Adre\u00e7a IP", + "name": "Nom del dispositiu", + "token": "Token d'API" + }, + "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", + "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la de Xiaomi" + }, "gateway": { "data": { "host": "Adre\u00e7a IP", diff --git a/homeassistant/components/xiaomi_miio/translations/et.json b/homeassistant/components/xiaomi_miio/translations/et.json index f6bd3218e14b85..a5975ab05dc9db 100644 --- a/homeassistant/components/xiaomi_miio/translations/et.json +++ b/homeassistant/components/xiaomi_miio/translations/et.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "\u00dchendus nurjus", - "no_device_selected": "Seadmeid pole valitud, vali \u00fcks seade." + "no_device_selected": "Seadmeid pole valitud, vali \u00fcks seade.", + "unknown_device": "Seadme mudel pole teada, seadet ei saa seadistamisvoo abil seadistada." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP-aadress", + "name": "Seadme nimi", + "token": "API v\u00f5ti" + }, + "description": "Vaja on 32 t\u00e4hem\u00e4rgilist v\u00f5tit API v\u00f5ti , juhiste saamiseks vaata https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Pane t\u00e4hele, et see v\u00f5ti API v\u00f5ti erineb Xiaomi Aqara sidumises kasutatavast v\u00f5tmest.", + "title": "\u00dchenda Xiaomi Miio seade v\u00f5i Xiaomi Gateway" + }, "gateway": { "data": { "host": "IP aadress", diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index c7128900fc7c30..3375cac31d510b 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes", - "no_device_selected": "Ingen enhet valgt, vennligst velg en enhet." + "no_device_selected": "Ingen enhet valgt, vennligst velg en enhet.", + "unknown_device": "Enhetsmodellen er ikke kjent, kan ikke konfigurere enheten ved hjelp av konfigurasjonsflyt." }, "flow_title": "", "step": { + "device": { + "data": { + "host": "IP adresse", + "name": "Navnet p\u00e5 enheten", + "token": "API-token" + }, + "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", + "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" + }, "gateway": { "data": { "host": "IP adresse", diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 82f7f9589057c3..50eb4d16887816 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "no_device_selected": "Nie wybrano \u017cadnego urz\u0105dzenia, wybierz jedno urz\u0105dzenie" + "no_device_selected": "Nie wybrano \u017cadnego urz\u0105dzenia, wybierz jedno urz\u0105dzenie", + "unknown_device": "Model urz\u0105dzenia nie jest znany, nie mo\u017cna skonfigurowa\u0107 urz\u0105dzenia przy u\u017cyciu interfejsu u\u017cytkownika." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "Adres IP", + "name": "Nazwa urz\u0105dzenia", + "token": "Token API" + }, + "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara.", + "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi b\u0105d\u017a innym urz\u0105dzeniem Xiaomi Miio" + }, "gateway": { "data": { "host": "Adres IP", diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index be02fd14f3ed87..5113128cac55e3 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", - "no_device_selected": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u043e \u0438\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432." + "no_device_selected": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u043e \u0438\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.", + "unknown_device": "\u041c\u043e\u0434\u0435\u043b\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0430\u0441\u0442\u0435\u0440\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "token": "\u0422\u043e\u043a\u0435\u043d API" + }, + "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" + }, "gateway": { "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 95499fb7b827de..43e3e10df202b2 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "no_device_selected": "\u672a\u9078\u64c7\u88dd\u7f6e\uff0c\u8acb\u9078\u64c7\u4e00\u9805\u88dd\u7f6e\u3002" + "no_device_selected": "\u672a\u9078\u64c7\u88dd\u7f6e\uff0c\u8acb\u9078\u64c7\u4e00\u9805\u88dd\u7f6e\u3002", + "unknown_device": "\u88dd\u7f6e\u578b\u865f\u672a\u77e5\uff0c\u7121\u6cd5\u4f7f\u7528\u8a2d\u5b9a\u6d41\u7a0b\u3002" }, "flow_title": "Xiaomi Miio\uff1a{name}", "step": { + "device": { + "data": { + "host": "IP \u4f4d\u5740", + "name": "\u88dd\u7f6e\u540d\u7a31", + "token": "API \u5bc6\u9470" + }, + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" + }, "gateway": { "data": { "host": "IP \u4f4d\u5740", From 4236f6e5d499e38e21f2d54a9da6ffdb6061b538 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 16 Feb 2021 20:46:44 -0800 Subject: [PATCH 0519/1818] Fix stream keepalive on startup (#46640) This was accidentally dropped in a previous PR that refactored the interaction between stream and camera. This is difficult to test from the existing preload stream tests, so leaving untested for now. --- homeassistant/components/camera/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index d0bee4e249f360..4c5d89030b4c68 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -258,6 +258,7 @@ async def preload_stream(_): stream = await camera.create_stream() if not stream: continue + stream.keepalive = True stream.hls_output() stream.start() From 58499946edf050218f57bde729883cf269f17533 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 16 Feb 2021 21:37:56 -0800 Subject: [PATCH 0520/1818] Add SmartTub integration (#37775) Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + homeassistant/components/smarttub/__init__.py | 54 ++++++++ homeassistant/components/smarttub/climate.py | 116 ++++++++++++++++++ .../components/smarttub/config_flow.py | 53 ++++++++ homeassistant/components/smarttub/const.py | 14 +++ .../components/smarttub/controller.py | 110 +++++++++++++++++ homeassistant/components/smarttub/entity.py | 64 ++++++++++ homeassistant/components/smarttub/helpers.py | 8 ++ .../components/smarttub/manifest.json | 12 ++ .../components/smarttub/strings.json | 22 ++++ .../components/smarttub/translations/en.json | 22 ++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/smarttub/__init__.py | 1 + tests/components/smarttub/conftest.py | 86 +++++++++++++ tests/components/smarttub/test_climate.py | 74 +++++++++++ tests/components/smarttub/test_config_flow.py | 64 ++++++++++ tests/components/smarttub/test_controller.py | 37 ++++++ tests/components/smarttub/test_entity.py | 18 +++ tests/components/smarttub/test_init.py | 60 +++++++++ 21 files changed, 823 insertions(+) create mode 100644 homeassistant/components/smarttub/__init__.py create mode 100644 homeassistant/components/smarttub/climate.py create mode 100644 homeassistant/components/smarttub/config_flow.py create mode 100644 homeassistant/components/smarttub/const.py create mode 100644 homeassistant/components/smarttub/controller.py create mode 100644 homeassistant/components/smarttub/entity.py create mode 100644 homeassistant/components/smarttub/helpers.py create mode 100644 homeassistant/components/smarttub/manifest.json create mode 100644 homeassistant/components/smarttub/strings.json create mode 100644 homeassistant/components/smarttub/translations/en.json create mode 100644 tests/components/smarttub/__init__.py create mode 100644 tests/components/smarttub/conftest.py create mode 100644 tests/components/smarttub/test_climate.py create mode 100644 tests/components/smarttub/test_config_flow.py create mode 100644 tests/components/smarttub/test_controller.py create mode 100644 tests/components/smarttub/test_entity.py create mode 100644 tests/components/smarttub/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index d1bda212d2d449..8a2ea23ded9436 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -421,6 +421,7 @@ homeassistant/components/smappee/* @bsmappee homeassistant/components/smart_meter_texas/* @grahamwetzler homeassistant/components/smarthab/* @outadoc homeassistant/components/smartthings/* @andrewsayre +homeassistant/components/smarttub/* @mdz homeassistant/components/smarty/* @z0mbieprocess homeassistant/components/sms/* @ocalvo homeassistant/components/smtp/* @fabaff diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py new file mode 100644 index 00000000000000..85298a75f65258 --- /dev/null +++ b/homeassistant/components/smarttub/__init__.py @@ -0,0 +1,54 @@ +"""SmartTub integration.""" +import asyncio +import logging + +from .const import DOMAIN, SMARTTUB_CONTROLLER +from .controller import SmartTubController + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["climate"] + + +async def async_setup(hass, _config): + """Set up smarttub component.""" + + hass.data.setdefault(DOMAIN, {}) + + return True + + +async def async_setup_entry(hass, entry): + """Set up a smarttub config entry.""" + + controller = SmartTubController(hass) + hass.data[DOMAIN][entry.entry_id] = { + SMARTTUB_CONTROLLER: controller, + } + + if not await controller.async_setup_entry(entry): + return False + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) + + return True + + +async def async_unload_entry(hass, entry): + """Remove a smarttub config entry.""" + if not all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + ] + ) + ): + return False + + hass.data[DOMAIN].pop(entry.entry_id) + + return True diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py new file mode 100644 index 00000000000000..5f16bea8cf70bd --- /dev/null +++ b/homeassistant/components/smarttub/climate.py @@ -0,0 +1,116 @@ +"""Platform for climate integration.""" +import logging + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + HVAC_MODE_HEAT, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.util.temperature import convert as convert_temperature + +from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up climate entity for the thermostat in the tub.""" + + controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] + + entities = [ + SmartTubThermostat(controller.coordinator, spa) for spa in controller.spas + ] + + async_add_entities(entities) + + +class SmartTubThermostat(SmartTubEntity, ClimateEntity): + """The target water temperature for the spa.""" + + def __init__(self, coordinator, spa): + """Initialize the entity.""" + super().__init__(coordinator, spa, "thermostat") + + @property + def unique_id(self) -> str: + """Return a unique id for the entity.""" + return f"{self.spa.id}-{self._entity_type}" + + @property + def temperature_unit(self): + """Return the unit of measurement used by the platform.""" + return TEMP_CELSIUS + + @property + def hvac_action(self): + """Return the current running hvac operation.""" + heater_status = self.get_spa_status("heater") + if heater_status == "ON": + return CURRENT_HVAC_HEAT + if heater_status == "OFF": + return CURRENT_HVAC_IDLE + return None + + @property + def hvac_modes(self): + """Return the list of available hvac operation modes.""" + return [HVAC_MODE_HEAT] + + @property + def hvac_mode(self): + """Return the current hvac mode. + + SmartTub devices don't seem to have the option of disabling the heater, + so this is always HVAC_MODE_HEAT. + """ + return HVAC_MODE_HEAT + + async def async_set_hvac_mode(self, hvac_mode: str): + """Set new target hvac mode. + + As with hvac_mode, we don't really have an option here. + """ + if hvac_mode == HVAC_MODE_HEAT: + return + raise NotImplementedError(hvac_mode) + + @property + def min_temp(self): + """Return the minimum temperature.""" + min_temp = DEFAULT_MIN_TEMP + return convert_temperature(min_temp, TEMP_CELSIUS, self.temperature_unit) + + @property + def max_temp(self): + """Return the maximum temperature.""" + max_temp = DEFAULT_MAX_TEMP + return convert_temperature(max_temp, TEMP_CELSIUS, self.temperature_unit) + + @property + def supported_features(self): + """Return the set of supported features. + + Only target temperature is supported. + """ + return SUPPORT_TARGET_TEMPERATURE + + @property + def current_temperature(self): + """Return the current water temperature.""" + return self.get_spa_status("water.temperature") + + @property + def target_temperature(self): + """Return the target water temperature.""" + return self.get_spa_status("setTemperature") + + async def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + temperature = kwargs[ATTR_TEMPERATURE] + await self.spa.set_temperature(temperature) + await self.coordinator.async_refresh() diff --git a/homeassistant/components/smarttub/config_flow.py b/homeassistant/components/smarttub/config_flow.py new file mode 100644 index 00000000000000..8f3ed17f93ac8e --- /dev/null +++ b/homeassistant/components/smarttub/config_flow.py @@ -0,0 +1,53 @@ +"""Config flow to configure the SmartTub integration.""" +import logging + +from smarttub import LoginFailed +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD + +from .const import DOMAIN # pylint: disable=unused-import +from .controller import SmartTubController + +DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str} +) + + +_LOGGER = logging.getLogger(__name__) + + +class SmartTubConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """SmartTub configuration flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle a flow initiated by the user.""" + errors = {} + + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + controller = SmartTubController(self.hass) + try: + account = await controller.login( + user_input[CONF_EMAIL], user_input[CONF_PASSWORD] + ) + except LoginFailed: + errors["base"] = "invalid_auth" + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + existing_entry = await self.async_set_unique_id(account.id) + if existing_entry: + self.hass.config_entries.async_update_entry(existing_entry, data=user_input) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + return self.async_create_entry(title=user_input[CONF_EMAIL], data=user_input) diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py new file mode 100644 index 00000000000000..99e7f21f86df8e --- /dev/null +++ b/homeassistant/components/smarttub/const.py @@ -0,0 +1,14 @@ +"""smarttub constants.""" + +DOMAIN = "smarttub" + +EVENT_SMARTTUB = "smarttub" + +SMARTTUB_CONTROLLER = "smarttub_controller" + +SCAN_INTERVAL = 60 + +POLLING_TIMEOUT = 10 + +DEFAULT_MIN_TEMP = 18.5 +DEFAULT_MAX_TEMP = 40 diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py new file mode 100644 index 00000000000000..31fe71d14b6052 --- /dev/null +++ b/homeassistant/components/smarttub/controller.py @@ -0,0 +1,110 @@ +"""Interface to the SmartTub API.""" + +import asyncio +from datetime import timedelta +import logging + +from aiohttp import client_exceptions +import async_timeout +from smarttub import APIError, LoginFailed, SmartTub +from smarttub.api import Account + +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +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 DOMAIN, POLLING_TIMEOUT, SCAN_INTERVAL +from .helpers import get_spa_name + +_LOGGER = logging.getLogger(__name__) + + +class SmartTubController: + """Interface between Home Assistant and the SmartTub API.""" + + def __init__(self, hass): + """Initialize an interface to SmartTub.""" + self._hass = hass + self._account = None + self.spas = set() + self._spa_devices = {} + + self.coordinator = None + + async def async_setup_entry(self, entry): + """Perform initial setup. + + Authenticate, query static state, set up polling, and otherwise make + ready for normal operations . + """ + + try: + self._account = await self.login( + entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD] + ) + except LoginFailed: + # credentials were changed or invalidated, we need new ones + + return False + except ( + asyncio.TimeoutError, + client_exceptions.ClientOSError, + client_exceptions.ServerDisconnectedError, + client_exceptions.ContentTypeError, + ) as err: + raise ConfigEntryNotReady from err + + self.spas = await self._account.get_spas() + + self.coordinator = DataUpdateCoordinator( + self._hass, + _LOGGER, + name=DOMAIN, + update_method=self.async_update_data, + update_interval=timedelta(seconds=SCAN_INTERVAL), + ) + + await self.coordinator.async_refresh() + + await self.async_register_devices(entry) + + return True + + async def async_update_data(self): + """Query the API and return the new state.""" + + data = {} + try: + async with async_timeout.timeout(POLLING_TIMEOUT): + for spa in self.spas: + data[spa.id] = {"status": await spa.get_status()} + except APIError as err: + raise UpdateFailed(err) from err + + return data + + async def async_register_devices(self, entry): + """Register devices with the device registry for all spas.""" + device_registry = await dr.async_get_registry(self._hass) + for spa in self.spas: + device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, spa.id)}, + manufacturer=spa.brand, + name=get_spa_name(spa), + model=spa.model, + ) + self._spa_devices[spa.id] = device + + async def login(self, email, password) -> Account: + """Retrieve the account corresponding to the specified email and password. + + Returns None if the credentials are invalid. + """ + + api = SmartTub(async_get_clientsession(self._hass)) + + await api.login(email, password) + return await api.get_account() diff --git a/homeassistant/components/smarttub/entity.py b/homeassistant/components/smarttub/entity.py new file mode 100644 index 00000000000000..95d89971cb723c --- /dev/null +++ b/homeassistant/components/smarttub/entity.py @@ -0,0 +1,64 @@ +"""SmartTub integration.""" +import logging + +import smarttub + +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +from .const import DOMAIN +from .helpers import get_spa_name + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["climate"] + + +class SmartTubEntity(CoordinatorEntity): + """Base class for SmartTub entities.""" + + def __init__( + self, coordinator: DataUpdateCoordinator, spa: smarttub.Spa, entity_type + ): + """Initialize the entity. + + Given a spa id and a short name for the entity, we provide basic device + info, name, unique id, etc. for all derived entities. + """ + + super().__init__(coordinator) + self.spa = spa + self._entity_type = entity_type + + @property + def device_info(self) -> str: + """Return device info.""" + return { + "identifiers": {(DOMAIN, self.spa.id)}, + "manufacturer": self.spa.brand, + "model": self.spa.model, + } + + @property + def name(self) -> str: + """Return the name of the entity.""" + spa_name = get_spa_name(self.spa) + return f"{spa_name} {self._entity_type}" + + def get_spa_status(self, path): + """Retrieve a value from the data returned by Spa.get_status(). + + Nested keys can be specified by a dotted path, e.g. + status['foo']['bar'] is 'foo.bar'. + """ + + status = self.coordinator.data[self.spa.id].get("status") + if status is None: + return None + + for key in path.split("."): + status = status[key] + + return status diff --git a/homeassistant/components/smarttub/helpers.py b/homeassistant/components/smarttub/helpers.py new file mode 100644 index 00000000000000..a6f2d09c38f18b --- /dev/null +++ b/homeassistant/components/smarttub/helpers.py @@ -0,0 +1,8 @@ +"""Helper functions for SmartTub integration.""" + +import smarttub + + +def get_spa_name(spa: smarttub.Spa) -> str: + """Return the name of the specified spa.""" + return f"{spa.brand} {spa.model}" diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json new file mode 100644 index 00000000000000..9735a3753b4f33 --- /dev/null +++ b/homeassistant/components/smarttub/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "smarttub", + "name": "SmartTub", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/smarttub", + "dependencies": [], + "codeowners": ["@mdz"], + "requirements": [ + "python-smarttub==0.0.6" + ], + "quality_scale": "platinum" +} diff --git a/homeassistant/components/smarttub/strings.json b/homeassistant/components/smarttub/strings.json new file mode 100644 index 00000000000000..0d52673a469cb5 --- /dev/null +++ b/homeassistant/components/smarttub/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "title": "Login", + "description": "Enter your SmartTub email address and password to login", + "data": { + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + } + } +} diff --git a/homeassistant/components/smarttub/translations/en.json b/homeassistant/components/smarttub/translations/en.json new file mode 100644 index 00000000000000..4cf930918875b4 --- /dev/null +++ b/homeassistant/components/smarttub/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured", + "reauth_successful": "Re-authentication was successful" + }, + "error": { + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Password" + }, + "description": "Enter your SmartTub email address and password to login", + "title": "Login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 6ff72cf5572527..400f2f2352bb61 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -197,6 +197,7 @@ "smart_meter_texas", "smarthab", "smartthings", + "smarttub", "smhi", "sms", "solaredge", diff --git a/requirements_all.txt b/requirements_all.txt index 24bb508c713de6..de6c34f4febd16 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1809,6 +1809,9 @@ python-qbittorrent==0.4.2 # homeassistant.components.ripple python-ripple-api==0.0.3 +# homeassistant.components.smarttub +python-smarttub==0.0.6 + # homeassistant.components.sochain python-sochain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e22ab3fa0e2dfd..4f15becca1a428 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -934,6 +934,9 @@ python-nest==4.1.0 # homeassistant.components.ozw python-openzwave-mqtt[mqtt-client]==1.4.0 +# homeassistant.components.smarttub +python-smarttub==0.0.6 + # homeassistant.components.songpal python-songpal==0.12 diff --git a/tests/components/smarttub/__init__.py b/tests/components/smarttub/__init__.py new file mode 100644 index 00000000000000..afbf271eb63301 --- /dev/null +++ b/tests/components/smarttub/__init__.py @@ -0,0 +1 @@ +"""Tests for the smarttub integration.""" diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py new file mode 100644 index 00000000000000..fec74a2b30add3 --- /dev/null +++ b/tests/components/smarttub/conftest.py @@ -0,0 +1,86 @@ +"""Common fixtures for smarttub tests.""" + +from unittest.mock import create_autospec, patch + +import pytest +import smarttub + +from homeassistant.components.smarttub.const import DOMAIN +from homeassistant.components.smarttub.controller import SmartTubController +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD + +from tests.common import MockConfigEntry + + +@pytest.fixture +def config_data(): + """Provide configuration data for tests.""" + return {CONF_EMAIL: "test-email", CONF_PASSWORD: "test-password"} + + +@pytest.fixture +def config_entry(config_data): + """Create a mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data=config_data, + options={}, + ) + + +@pytest.fixture(name="spa") +def mock_spa(): + """Mock a SmartTub.Spa.""" + + mock_spa = create_autospec(smarttub.Spa, instance=True) + mock_spa.id = "mockspa1" + mock_spa.brand = "mockbrand1" + mock_spa.model = "mockmodel1" + mock_spa.get_status.return_value = { + "setTemperature": 39, + "water": {"temperature": 38}, + "heater": "ON", + } + return mock_spa + + +@pytest.fixture(name="account") +def mock_account(spa): + """Mock a SmartTub.Account.""" + + mock_account = create_autospec(smarttub.Account, instance=True) + mock_account.id = "mockaccount1" + mock_account.get_spas.return_value = [spa] + return mock_account + + +@pytest.fixture(name="smarttub_api") +def mock_api(account, spa): + """Mock the SmartTub API.""" + + with patch( + "homeassistant.components.smarttub.controller.SmartTub", + autospec=True, + ) as api_class_mock: + api_mock = api_class_mock.return_value + api_mock.get_account.return_value = account + yield api_mock + + +@pytest.fixture +async def controller(smarttub_api, hass, config_entry): + """Instantiate controller for testing.""" + + controller = SmartTubController(hass) + assert len(controller.spas) == 0 + assert await controller.async_setup_entry(config_entry) + + assert len(controller.spas) > 0 + + return controller + + +@pytest.fixture +async def coordinator(controller): + """Provide convenient access to the coordinator via the controller.""" + return controller.coordinator diff --git a/tests/components/smarttub/test_climate.py b/tests/components/smarttub/test_climate.py new file mode 100644 index 00000000000000..f0e6ced4abd2a8 --- /dev/null +++ b/tests/components/smarttub/test_climate.py @@ -0,0 +1,74 @@ +"""Test the SmartTub climate platform.""" + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_MAX_TEMP, + ATTR_MIN_TEMP, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + DOMAIN as CLIMATE_DOMAIN, + HVAC_MODE_HEAT, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.components.smarttub.const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) + + +async def test_thermostat(coordinator, spa, hass, config_entry): + """Test the thermostat entity.""" + + spa.get_status.return_value = { + "heater": "ON", + "water": { + "temperature": 38, + }, + "setTemperature": 39, + } + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + entity_id = f"climate.{spa.brand}_{spa.model}_thermostat" + state = hass.states.get(entity_id) + assert state + + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + + spa.get_status.return_value["heater"] = "OFF" + await hass.helpers.entity_component.async_update_entity(entity_id) + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + + assert set(state.attributes[ATTR_HVAC_MODES]) == {HVAC_MODE_HEAT} + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TARGET_TEMPERATURE + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 38 + assert state.attributes[ATTR_TEMPERATURE] == 39 + assert state.attributes[ATTR_MAX_TEMP] == DEFAULT_MAX_TEMP + assert state.attributes[ATTR_MIN_TEMP] == DEFAULT_MIN_TEMP + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 37}, + blocking=True, + ) + spa.set_temperature.assert_called_with(37) + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + # does nothing diff --git a/tests/components/smarttub/test_config_flow.py b/tests/components/smarttub/test_config_flow.py new file mode 100644 index 00000000000000..a57eb43eef7f6a --- /dev/null +++ b/tests/components/smarttub/test_config_flow.py @@ -0,0 +1,64 @@ +"""Test the smarttub config flow.""" +from unittest.mock import patch + +from smarttub import LoginFailed + +from homeassistant import config_entries +from homeassistant.components.smarttub.const import DOMAIN + + +async def test_form(hass, smarttub_api): + """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"] == {} + + with patch( + "homeassistant.components.smarttub.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.smarttub.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"email": "test-email", "password": "test-password"}, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "test-email" + assert result2["data"] == { + "email": "test-email", + "password": "test-password", + } + await hass.async_block_till_done() + mock_setup.assert_called_once() + mock_setup_entry.assert_called_once() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"email": "test-email2", "password": "test-password2"} + ) + assert result2["type"] == "abort" + assert result2["reason"] == "reauth_successful" + + +async def test_form_invalid_auth(hass, smarttub_api): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + smarttub_api.login.side_effect = LoginFailed + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"email": "test-email", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} diff --git a/tests/components/smarttub/test_controller.py b/tests/components/smarttub/test_controller.py new file mode 100644 index 00000000000000..e59ad86c09e130 --- /dev/null +++ b/tests/components/smarttub/test_controller.py @@ -0,0 +1,37 @@ +"""Test the SmartTub controller.""" + +import pytest +import smarttub + +from homeassistant.components.smarttub.controller import SmartTubController +from homeassistant.helpers.update_coordinator import UpdateFailed + + +async def test_invalid_credentials(hass, controller, smarttub_api, config_entry): + """Check that we return False if the configured credentials are invalid. + + This should mean that the user changed their SmartTub password. + """ + + smarttub_api.login.side_effect = smarttub.LoginFailed + controller = SmartTubController(hass) + ret = await controller.async_setup_entry(config_entry) + assert ret is False + + +async def test_update(controller, spa): + """Test data updates from API.""" + data = await controller.async_update_data() + assert data[spa.id] == {"status": spa.get_status.return_value} + + spa.get_status.side_effect = smarttub.APIError + with pytest.raises(UpdateFailed): + data = await controller.async_update_data() + + +async def test_login(controller, smarttub_api, account): + """Test SmartTubController.login.""" + smarttub_api.get_account.return_value.id = "account-id1" + account = await controller.login("test-email1", "test-password1") + smarttub_api.login.assert_called() + assert account == account diff --git a/tests/components/smarttub/test_entity.py b/tests/components/smarttub/test_entity.py new file mode 100644 index 00000000000000..4a19b2650909dc --- /dev/null +++ b/tests/components/smarttub/test_entity.py @@ -0,0 +1,18 @@ +"""Test SmartTubEntity.""" + +from homeassistant.components.smarttub.entity import SmartTubEntity + + +async def test_entity(coordinator, spa): + """Test SmartTubEntity.""" + + entity = SmartTubEntity(coordinator, spa, "entity1") + + assert entity.device_info + assert entity.name + + coordinator.data[spa.id] = {} + assert entity.get_spa_status("foo") is None + coordinator.data[spa.id]["status"] = {"foo": "foo1", "bar": {"baz": "barbaz1"}} + assert entity.get_spa_status("foo") == "foo1" + assert entity.get_spa_status("bar.baz") == "barbaz1" diff --git a/tests/components/smarttub/test_init.py b/tests/components/smarttub/test_init.py new file mode 100644 index 00000000000000..aa22780e9b8c02 --- /dev/null +++ b/tests/components/smarttub/test_init.py @@ -0,0 +1,60 @@ +"""Test smarttub setup process.""" + +import asyncio +from unittest.mock import patch + +import pytest +from smarttub import LoginFailed + +from homeassistant.components import smarttub +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.setup import async_setup_component + + +async def test_setup_with_no_config(hass): + """Test that we do not discover anything.""" + assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True + + # No flows started + assert len(hass.config_entries.flow.async_progress()) == 0 + + assert smarttub.const.SMARTTUB_CONTROLLER not in hass.data[smarttub.DOMAIN] + + +async def test_setup_entry_not_ready(hass, config_entry, smarttub_api): + """Test setup when the entry is not ready.""" + assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True + smarttub_api.login.side_effect = asyncio.TimeoutError + + with pytest.raises(ConfigEntryNotReady): + await smarttub.async_setup_entry(hass, config_entry) + + +async def test_setup_auth_failed(hass, config_entry, smarttub_api): + """Test setup when the credentials are invalid.""" + assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True + smarttub_api.login.side_effect = LoginFailed + + assert await smarttub.async_setup_entry(hass, config_entry) is False + + +async def test_config_passed_to_config_entry(hass, config_entry, config_data): + """Test that configured options are loaded via config entry.""" + config_entry.add_to_hass(hass) + ret = await async_setup_component(hass, smarttub.DOMAIN, config_data) + assert ret is True + + +async def test_unload_entry(hass, config_entry, smarttub_api): + """Test being able to unload an entry.""" + config_entry.add_to_hass(hass) + + assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True + + assert await smarttub.async_unload_entry(hass, config_entry) + + # test failure of platform unload + assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True + with patch.object(hass.config_entries, "async_forward_entry_unload") as mock: + mock.return_value = False + assert await smarttub.async_unload_entry(hass, config_entry) is False From 58f6db0127c8436fe5bc0ca3acb0dae13e1c1e05 Mon Sep 17 00:00:00 2001 From: David McClosky Date: Wed, 17 Feb 2021 00:39:46 -0500 Subject: [PATCH 0521/1818] Fix media_pause in vlc_telnet (#46675) The underlying python-telnet-vlc pause() function toggles play/pause, so we need to check our state before calling it. --- homeassistant/components/vlc_telnet/media_player.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index a7d5ee0a211eb5..68b3c373c7add3 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -245,7 +245,11 @@ def media_play(self): def media_pause(self): """Send pause command.""" - self._vlc.pause() + current_state = self._vlc.status().get("state") + if current_state != "paused": + # Make sure we're not already paused since VLCTelnet.pause() toggles + # pause. + self._vlc.pause() self._state = STATE_PAUSED def media_stop(self): From b956a571f4a136d0dac6ec46326cf640f7a8fc02 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Feb 2021 21:49:53 -0800 Subject: [PATCH 0522/1818] Fix Cloud Google/Alexa check (#46681) --- homeassistant/components/cloud/alexa_config.py | 6 +++++- homeassistant/components/cloud/google_config.py | 12 +++++++++--- tests/components/cloud/conftest.py | 15 ++++++++++++++- tests/components/cloud/test_alexa_config.py | 13 +++++++++++++ tests/components/cloud/test_google_config.py | 13 +++++++++++++ 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 7abbefe85ff5bb..2d4714b4c81855 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -62,7 +62,11 @@ def __init__( @property def enabled(self): """Return if Alexa is enabled.""" - return self._prefs.alexa_enabled + return ( + self._cloud.is_logged_in + and not self._cloud.subscription_expired + and self._prefs.alexa_enabled + ) @property def supports_auth(self): diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 2ac0bc40252978..dffa1e2f306c92 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -2,7 +2,7 @@ import asyncio import logging -from hass_nabucasa import cloud_api +from hass_nabucasa import Cloud, cloud_api from hass_nabucasa.google_report_state import ErrorResponse from homeassistant.components.google_assistant.helpers import AbstractConfig @@ -28,7 +28,9 @@ class CloudGoogleConfig(AbstractConfig): """HA Cloud Configuration for Google Assistant.""" - def __init__(self, hass, config, cloud_user: str, prefs: CloudPreferences, cloud): + def __init__( + self, hass, config, cloud_user: str, prefs: CloudPreferences, cloud: Cloud + ): """Initialize the Google config.""" super().__init__(hass) self._config = config @@ -43,7 +45,11 @@ def __init__(self, hass, config, cloud_user: str, prefs: CloudPreferences, cloud @property def enabled(self): """Return if Google is enabled.""" - return self._cloud.is_logged_in and self._prefs.google_enabled + return ( + self._cloud.is_logged_in + and not self._cloud.subscription_expired + and self._prefs.google_enabled + ) @property def entity_config(self): diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index 4755d470418fad..75276a9f2e21ca 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -43,7 +43,20 @@ def mock_cloud_login(hass, mock_cloud_setup): hass.data[const.DOMAIN].id_token = jwt.encode( { "email": "hello@home-assistant.io", - "custom:sub-exp": "2018-01-03", + "custom:sub-exp": "2300-01-03", + "cognito:username": "abcdefghjkl", + }, + "test", + ) + + +@pytest.fixture +def mock_expired_cloud_login(hass, mock_cloud_setup): + """Mock cloud is logged in.""" + hass.data[const.DOMAIN].id_token = jwt.encode( + { + "email": "hello@home-assistant.io", + "custom:sub-exp": "2018-01-01", "cognito:username": "abcdefghjkl", }, "test", diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 966ef4b0af312a..8e104f641b2a7f 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -215,3 +215,16 @@ async def test_alexa_update_report_state(hass, cloud_prefs): await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 1 + + +def test_enabled_requires_valid_sub(hass, mock_expired_cloud_login, cloud_prefs): + """Test that alexa config enabled requires a valid Cloud sub.""" + assert cloud_prefs.alexa_enabled + assert hass.data["cloud"].is_logged_in + assert hass.data["cloud"].subscription_expired + + config = alexa_config.AlexaConfig( + hass, ALEXA_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + + assert not config.enabled diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index f58ea1a415b07c..e1da6bbe0a8ebf 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -192,3 +192,16 @@ async def test_google_config_expose_entity_prefs(mock_conf, cloud_prefs): google_default_expose=["sensor"], ) assert not mock_conf.should_expose(state) + + +def test_enabled_requires_valid_sub(hass, mock_expired_cloud_login, cloud_prefs): + """Test that google config enabled requires a valid Cloud sub.""" + assert cloud_prefs.google_enabled + assert hass.data["cloud"].is_logged_in + assert hass.data["cloud"].subscription_expired + + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + + assert not config.enabled From 56f32196bdc3ae65da29a0a282fc5135ae8f0c0c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 16 Feb 2021 22:05:20 -0800 Subject: [PATCH 0523/1818] Add back block_until_done calls removed in PR 46610 (#46680) --- tests/components/stream/test_recorder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 3930a5e237d64d..e8ff540ba415b0 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -153,6 +153,8 @@ async def test_recorder_timeout( record_worker_sync.join() stream.stop() + await hass.async_block_till_done() + await hass.async_block_till_done() async def test_record_path_not_allowed(hass, hass_client): From 8c72cb616394cafdea881024c18ee101851328a9 Mon Sep 17 00:00:00 2001 From: Ilja Leiko Date: Wed, 17 Feb 2021 09:04:11 +0100 Subject: [PATCH 0524/1818] Add sensors to fetch Habitica tasks (#38910) * Adding sensors to fetch habitica tasks * PR changes and rebase * Fixing pylint * Fixing failed test dependancy * Generating requirements * Apply suggestions from code review Co-authored-by: Martin Hjelmare * PR changes * Update homeassistant/components/habitica/config_flow.py Thank you, @MartinHjelmare Co-authored-by: Martin Hjelmare * PR Changes * Fix failing test * Update tests/components/habitica/test_config_flow.py Co-authored-by: Martin Hjelmare * Fixing linting and imports Co-authored-by: Martin Hjelmare --- .coveragerc | 4 +- CODEOWNERS | 1 + homeassistant/components/habitica/__init__.py | 173 +++++++++-------- .../components/habitica/config_flow.py | 85 +++++++++ homeassistant/components/habitica/const.py | 14 ++ .../components/habitica/manifest.json | 11 +- homeassistant/components/habitica/sensor.py | 179 ++++++++++++++++-- .../components/habitica/strings.json | 20 ++ .../components/habitica/translations/en.json | 20 ++ homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/habitica/__init__.py | 1 + tests/components/habitica/test_config_flow.py | 135 +++++++++++++ 13 files changed, 550 insertions(+), 97 deletions(-) create mode 100644 homeassistant/components/habitica/config_flow.py create mode 100644 homeassistant/components/habitica/const.py create mode 100644 homeassistant/components/habitica/strings.json create mode 100644 homeassistant/components/habitica/translations/en.json create mode 100644 tests/components/habitica/__init__.py create mode 100644 tests/components/habitica/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index df9fbac4c588bb..51a539b4abab7c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -361,7 +361,9 @@ omit = homeassistant/components/guardian/sensor.py homeassistant/components/guardian/switch.py homeassistant/components/guardian/util.py - homeassistant/components/habitica/* + 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/const.py diff --git a/CODEOWNERS b/CODEOWNERS index 8a2ea23ded9436..81f43da58e7943 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -182,6 +182,7 @@ homeassistant/components/griddy/* @bdraco homeassistant/components/group/* @home-assistant/core homeassistant/components/growatt_server/* @indykoning homeassistant/components/guardian/* @bachya +homeassistant/components/habitica/* @ASMfreaK @leikoilja homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey homeassistant/components/hassio/* @home-assistant/supervisor homeassistant/components/hdmi_cec/* @newAM diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index b2c3fb168316ab..36e50db6c20654 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -1,55 +1,47 @@ -"""Support for Habitica devices.""" -from collections import namedtuple +"""The habitica integration.""" +import asyncio import logging from habitipy.aio import HabitipyAsync import voluptuous as vol -from homeassistant.const import ( - CONF_API_KEY, - CONF_NAME, - CONF_PATH, - CONF_SENSORS, - CONF_URL, -) -from homeassistant.helpers import config_validation as cv, discovery +from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_SENSORS, CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -_LOGGER = logging.getLogger(__name__) - -CONF_API_USER = "api_user" - -DEFAULT_URL = "https://habitica.com" -DOMAIN = "habitica" - -ST = SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"]) +from .const import ( + ATTR_ARGS, + ATTR_NAME, + ATTR_PATH, + CONF_API_USER, + DEFAULT_URL, + DOMAIN, + EVENT_API_CALL_SUCCESS, + SERVICE_API_CALL, +) +from .sensor import SENSORS_TYPES -SENSORS_TYPES = { - "name": ST("Name", None, "", ["profile", "name"]), - "hp": ST("HP", "mdi:heart", "HP", ["stats", "hp"]), - "maxHealth": ST("max HP", "mdi:heart", "HP", ["stats", "maxHealth"]), - "mp": ST("Mana", "mdi:auto-fix", "MP", ["stats", "mp"]), - "maxMP": ST("max Mana", "mdi:auto-fix", "MP", ["stats", "maxMP"]), - "exp": ST("EXP", "mdi:star", "EXP", ["stats", "exp"]), - "toNextLevel": ST("Next Lvl", "mdi:star", "EXP", ["stats", "toNextLevel"]), - "lvl": ST("Lvl", "mdi:arrow-up-bold-circle-outline", "Lvl", ["stats", "lvl"]), - "gp": ST("Gold", "mdi:currency-usd-circle", "Gold", ["stats", "gp"]), - "class": ST("Class", "mdi:sword", "", ["stats", "class"]), -} +_LOGGER = logging.getLogger(__name__) -INSTANCE_SCHEMA = vol.Schema( - { - vol.Optional(CONF_URL, default=DEFAULT_URL): cv.url, - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_API_USER): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)): vol.All( - cv.ensure_list, vol.Unique(), [vol.In(list(SENSORS_TYPES))] - ), - } +INSTANCE_SCHEMA = vol.All( + cv.deprecated(CONF_SENSORS), + vol.Schema( + { + vol.Optional(CONF_URL, default=DEFAULT_URL): cv.url, + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_API_USER): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_SENSORS, default=list(SENSORS_TYPES)): vol.All( + cv.ensure_list, vol.Unique(), [vol.In(list(SENSORS_TYPES))] + ), + } + ), ) -has_unique_values = vol.Schema(vol.Unique()) +has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name # because we want a handy alias @@ -73,14 +65,9 @@ def has_all_unique_users_names(value): INSTANCE_LIST_SCHEMA = vol.All( cv.ensure_list, has_all_unique_users, has_all_unique_users_names, [INSTANCE_SCHEMA] ) - CONFIG_SCHEMA = vol.Schema({DOMAIN: INSTANCE_LIST_SCHEMA}, extra=vol.ALLOW_EXTRA) -SERVICE_API_CALL = "api_call" -ATTR_NAME = CONF_NAME -ATTR_PATH = CONF_PATH -ATTR_ARGS = "args" -EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success" +PLATFORMS = ["sensor"] SERVICE_API_CALL_SCHEMA = vol.Schema( { @@ -91,12 +78,25 @@ def has_all_unique_users_names(value): ) -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the Habitica service.""" + configs = config.get(DOMAIN, []) + + for conf in configs: + if conf.get(CONF_URL) is None: + conf[CONF_URL] = DEFAULT_URL + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf + ) + ) + + return True - conf = config[DOMAIN] - data = hass.data[DOMAIN] = {} - websession = async_get_clientsession(hass) + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up habitica from a config entry.""" class HAHabitipyAsync(HabitipyAsync): """Closure API class to hold session.""" @@ -104,28 +104,6 @@ class HAHabitipyAsync(HabitipyAsync): def __call__(self, **kwargs): return super().__call__(websession, **kwargs) - for instance in conf: - url = instance[CONF_URL] - username = instance[CONF_API_USER] - password = instance[CONF_API_KEY] - name = instance.get(CONF_NAME) - config_dict = {"url": url, "login": username, "password": password} - api = HAHabitipyAsync(config_dict) - user = await api.user.get() - if name is None: - name = user["profile"]["name"] - data[name] = api - if CONF_SENSORS in instance: - hass.async_create_task( - discovery.async_load_platform( - hass, - "sensor", - DOMAIN, - {"name": name, "sensors": instance[CONF_SENSORS]}, - config, - ) - ) - async def handle_api_call(call): name = call.data[ATTR_NAME] path = call.data[ATTR_PATH] @@ -147,7 +125,50 @@ async def handle_api_call(call): EVENT_API_CALL_SUCCESS, {"name": name, "path": path, "data": data} ) - hass.services.async_register( - DOMAIN, SERVICE_API_CALL, handle_api_call, schema=SERVICE_API_CALL_SCHEMA - ) + data = hass.data.setdefault(DOMAIN, {}) + config = config_entry.data + websession = async_get_clientsession(hass) + url = config[CONF_URL] + username = config[CONF_API_USER] + password = config[CONF_API_KEY] + name = config.get(CONF_NAME) + config_dict = {"url": url, "login": username, "password": password} + api = HAHabitipyAsync(config_dict) + user = await api.user.get() + if name is None: + name = user["profile"]["name"] + hass.config_entries.async_update_entry( + config_entry, + data={**config_entry.data, CONF_NAME: name}, + ) + data[config_entry.entry_id] = api + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + if not hass.services.has_service(DOMAIN, SERVICE_API_CALL): + hass.services.async_register( + DOMAIN, SERVICE_API_CALL, handle_api_call, schema=SERVICE_API_CALL_SCHEMA + ) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + if len(hass.config_entries.async_entries) == 1: + hass.components.webhook.async_unregister(SERVICE_API_CALL) + return unload_ok diff --git a/homeassistant/components/habitica/config_flow.py b/homeassistant/components/habitica/config_flow.py new file mode 100644 index 00000000000000..6e3311ea9b5b59 --- /dev/null +++ b/homeassistant/components/habitica/config_flow.py @@ -0,0 +1,85 @@ +"""Config flow for habitica integration.""" +import logging +from typing import Dict + +from aiohttp import ClientResponseError +from habitipy.aio import HabitipyAsync +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_URL +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import CONF_API_USER, DEFAULT_URL, DOMAIN # pylint: disable=unused-import + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_USER): str, + vol.Required(CONF_API_KEY): str, + vol.Optional(CONF_NAME): str, + vol.Optional(CONF_URL, default=DEFAULT_URL): str, + } +) + +_LOGGER = logging.getLogger(__name__) + + +async def validate_input( + hass: core.HomeAssistant, data: Dict[str, str] +) -> Dict[str, str]: + """Validate the user input allows us to connect.""" + + websession = async_get_clientsession(hass) + api = HabitipyAsync( + conf={ + "login": data[CONF_API_USER], + "password": data[CONF_API_KEY], + "url": data[CONF_URL] or DEFAULT_URL, + } + ) + try: + await api.user.get(session=websession) + return { + "title": f"{data.get('name', 'Default username')}", + CONF_API_USER: data[CONF_API_USER], + } + except ClientResponseError as ex: + raise InvalidAuth() from ex + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for habitica.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + 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 InvalidAuth: + errors = {"base": "invalid_credentials"} + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors = {"base": "unknown"} + else: + await self.async_set_unique_id(info[CONF_API_USER]) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=info["title"], data=user_input) + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors=errors, + description_placeholders={}, + ) + + async def async_step_import(self, import_data): + """Import habitica config from configuration.yaml.""" + return await self.async_step_user(import_data) + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/habitica/const.py b/homeassistant/components/habitica/const.py new file mode 100644 index 00000000000000..438bcec9d94e90 --- /dev/null +++ b/homeassistant/components/habitica/const.py @@ -0,0 +1,14 @@ +"""Constants for the habitica integration.""" + +from homeassistant.const import CONF_NAME, CONF_PATH + +CONF_API_USER = "api_user" + +DEFAULT_URL = "https://habitica.com" +DOMAIN = "habitica" + +SERVICE_API_CALL = "api_call" +ATTR_NAME = CONF_NAME +ATTR_PATH = CONF_PATH +ATTR_ARGS = "args" +EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success" diff --git a/homeassistant/components/habitica/manifest.json b/homeassistant/components/habitica/manifest.json index 50664d862ada6f..0779a2d3248640 100644 --- a/homeassistant/components/habitica/manifest.json +++ b/homeassistant/components/habitica/manifest.json @@ -1,7 +1,8 @@ { - "domain": "habitica", - "name": "Habitica", - "documentation": "https://www.home-assistant.io/integrations/habitica", - "requirements": ["habitipy==0.2.0"], - "codeowners": [] + "domain": "habitica", + "name": "Habitica", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/habitica", + "requirements": ["habitipy==0.2.0"], + "codeowners": ["@ASMfreaK", "@leikoilja"] } diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index f885aa832c78f0..29e494d89ee313 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -1,25 +1,82 @@ """Support for Habitica sensors.""" +from collections import namedtuple from datetime import timedelta +import logging -from homeassistant.components import habitica +from aiohttp import ClientResponseError + +from homeassistant.const import CONF_NAME, HTTP_TOO_MANY_REQUESTS from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) +from .const import DOMAIN +_LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up the habitica platform.""" - if discovery_info is None: - return +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) - name = discovery_info[habitica.CONF_NAME] - sensors = discovery_info[habitica.CONF_SENSORS] - sensor_data = HabitipyData(hass.data[habitica.DOMAIN][name]) +ST = SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"]) + +SENSORS_TYPES = { + "name": ST("Name", None, "", ["profile", "name"]), + "hp": ST("HP", "mdi:heart", "HP", ["stats", "hp"]), + "maxHealth": ST("max HP", "mdi:heart", "HP", ["stats", "maxHealth"]), + "mp": ST("Mana", "mdi:auto-fix", "MP", ["stats", "mp"]), + "maxMP": ST("max Mana", "mdi:auto-fix", "MP", ["stats", "maxMP"]), + "exp": ST("EXP", "mdi:star", "EXP", ["stats", "exp"]), + "toNextLevel": ST("Next Lvl", "mdi:star", "EXP", ["stats", "toNextLevel"]), + "lvl": ST("Lvl", "mdi:arrow-up-bold-circle-outline", "Lvl", ["stats", "lvl"]), + "gp": ST("Gold", "mdi:currency-usd-circle", "Gold", ["stats", "gp"]), + "class": ST("Class", "mdi:sword", "", ["stats", "class"]), +} + +TASKS_TYPES = { + "habits": ST("Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habits"]), + "dailys": ST("Dailys", "mdi:clipboard-list-outline", "n_of_tasks", ["dailys"]), + "todos": ST("TODOs", "mdi:clipboard-list-outline", "n_of_tasks", ["todos"]), + "rewards": ST("Rewards", "mdi:clipboard-list-outline", "n_of_tasks", ["rewards"]), +} + +TASKS_MAP_ID = "id" +TASKS_MAP = { + "repeat": "repeat", + "challenge": "challenge", + "group": "group", + "frequency": "frequency", + "every_x": "everyX", + "streak": "streak", + "counter_up": "counterUp", + "counter_down": "counterDown", + "next_due": "nextDue", + "yester_daily": "yesterDaily", + "completed": "completed", + "collapse_checklist": "collapseChecklist", + "type": "type", + "notes": "notes", + "tags": "tags", + "value": "value", + "priority": "priority", + "start_date": "startDate", + "days_of_month": "daysOfMonth", + "weeks_of_month": "weeksOfMonth", + "created_at": "createdAt", + "text": "text", + "is_due": "isDue", +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the habitica sensors.""" + + entities = [] + name = config_entry.data[CONF_NAME] + sensor_data = HabitipyData(hass.data[DOMAIN][config_entry.entry_id]) await sensor_data.update() - async_add_devices( - [HabitipySensor(name, sensor, sensor_data) for sensor in sensors], True - ) + for sensor_type in SENSORS_TYPES: + entities.append(HabitipySensor(name, sensor_type, sensor_data)) + for task_type in TASKS_TYPES: + entities.append(HabitipyTaskSensor(name, task_type, sensor_data)) + async_add_entities(entities, True) class HabitipyData: @@ -29,11 +86,43 @@ def __init__(self, api): """Habitica API user data cache.""" self.api = api self.data = None + self.tasks = {} @Throttle(MIN_TIME_BETWEEN_UPDATES) async def update(self): """Get a new fix from Habitica servers.""" - self.data = await self.api.user.get() + try: + self.data = await self.api.user.get() + except ClientResponseError as error: + if error.status == HTTP_TOO_MANY_REQUESTS: + _LOGGER.warning( + "Sensor data update for %s has too many API requests." + " Skipping the update.", + DOMAIN, + ) + else: + _LOGGER.error( + "Count not update sensor data for %s (%s)", + DOMAIN, + error, + ) + + for task_type in TASKS_TYPES: + try: + self.tasks[task_type] = await self.api.tasks.user.get(type=task_type) + except ClientResponseError as error: + if error.status == HTTP_TOO_MANY_REQUESTS: + _LOGGER.warning( + "Sensor data update for %s has too many API requests." + " Skipping the update.", + DOMAIN, + ) + else: + _LOGGER.error( + "Count not update sensor data for %s (%s)", + DOMAIN, + error, + ) class HabitipySensor(Entity): @@ -43,7 +132,7 @@ def __init__(self, name, sensor_name, updater): """Initialize a generic Habitica sensor.""" self._name = name self._sensor_name = sensor_name - self._sensor_type = habitica.SENSORS_TYPES[sensor_name] + self._sensor_type = SENSORS_TYPES[sensor_name] self._state = None self._updater = updater @@ -63,7 +152,7 @@ def icon(self): @property def name(self): """Return the name of the sensor.""" - return f"{habitica.DOMAIN}_{self._name}_{self._sensor_name}" + return f"{DOMAIN}_{self._name}_{self._sensor_name}" @property def state(self): @@ -74,3 +163,63 @@ def state(self): def unit_of_measurement(self): """Return the unit the value is expressed in.""" return self._sensor_type.unit + + +class HabitipyTaskSensor(Entity): + """A Habitica task sensor.""" + + def __init__(self, name, task_name, updater): + """Initialize a generic Habitica task.""" + self._name = name + self._task_name = task_name + self._task_type = TASKS_TYPES[task_name] + self._state = None + self._updater = updater + + async def async_update(self): + """Update Condition and Forecast.""" + await self._updater.update() + all_tasks = self._updater.tasks + for element in self._task_type.path: + tasks_length = len(all_tasks[element]) + self._state = tasks_length + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return self._task_type.icon + + @property + def name(self): + """Return the name of the task.""" + return f"{DOMAIN}_{self._name}_{self._task_name}" + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes of all user tasks.""" + if self._updater.tasks is not None: + all_received_tasks = self._updater.tasks + for element in self._task_type.path: + received_tasks = all_received_tasks[element] + attrs = {} + + # Map tasks to TASKS_MAP + for received_task in received_tasks: + task_id = received_task[TASKS_MAP_ID] + task = {} + for map_key, map_value in TASKS_MAP.items(): + value = received_task.get(map_value) + if value: + task[map_key] = value + attrs[task_id] = task + return attrs + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._task_type.unit diff --git a/homeassistant/components/habitica/strings.json b/homeassistant/components/habitica/strings.json new file mode 100644 index 00000000000000..868d024b02efa9 --- /dev/null +++ b/homeassistant/components/habitica/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "step": { + "user": { + "data": { + "url": "[%key:common::config_flow::data::url%]", + "name": "Override for Habitica’s username. Will be used for service calls", + "api_user": "Habitica’s API user ID", + "api_key": "[%key:common::config_flow::data::api_key%]" + }, + "description": "Connect your Habitica profile to allow monitoring of your user's profile and tasks. Note that api_id and api_key must be gotten from https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} diff --git a/homeassistant/components/habitica/translations/en.json b/homeassistant/components/habitica/translations/en.json new file mode 100644 index 00000000000000..fa571d0d72a4b4 --- /dev/null +++ b/homeassistant/components/habitica/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Invalid credentials", + "unknown": "Unknown error" + }, + "step": { + "user": { + "data": { + "url": "Habitica URL", + "name": "Override for Habitica’s username. Will be used for service calls", + "api_user": "Habitica’s API user ID", + "api_key": "Habitica's API user KEY" + }, + "description": "Connect your Habitica profile to allow monitoring of your user's profile and tasks. Note that api_id and api_key must be grabed from https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 400f2f2352bb61..53d3c8294d206f 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -86,6 +86,7 @@ "gree", "griddy", "guardian", + "habitica", "hangouts", "harmony", "heos", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f15becca1a428..0e863e23bb1ab2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -383,6 +383,9 @@ ha-ffmpeg==3.0.2 # homeassistant.components.philips_js ha-philipsjs==0.1.0 +# homeassistant.components.habitica +habitipy==0.2.0 + # homeassistant.components.hangouts hangups==0.4.11 diff --git a/tests/components/habitica/__init__.py b/tests/components/habitica/__init__.py new file mode 100644 index 00000000000000..a7f62afff8f2bf --- /dev/null +++ b/tests/components/habitica/__init__.py @@ -0,0 +1 @@ +"""Tests for the habitica integration.""" diff --git a/tests/components/habitica/test_config_flow.py b/tests/components/habitica/test_config_flow.py new file mode 100644 index 00000000000000..8ae92bcc0e2b72 --- /dev/null +++ b/tests/components/habitica/test_config_flow.py @@ -0,0 +1,135 @@ +"""Test the habitica config flow.""" +from asyncio import Future +from unittest.mock import AsyncMock, MagicMock, patch + +from homeassistant import config_entries, setup +from homeassistant.components.habitica.config_flow import InvalidAuth +from homeassistant.components.habitica.const import DEFAULT_URL, DOMAIN +from homeassistant.const import HTTP_OK + +from tests.common import MockConfigEntry + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + request_mock = MagicMock() + type(request_mock).status_code = HTTP_OK + + mock_obj = MagicMock() + mock_obj.user.get.return_value = Future() + mock_obj.user.get.return_value.set_result(None) + + with patch( + "homeassistant.components.habitica.config_flow.HabitipyAsync", + return_value=mock_obj, + ), patch( + "homeassistant.components.habitica.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.habitica.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"api_user": "test-api-user", "api_key": "test-api-key"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Default username" + assert result2["data"] == { + "url": DEFAULT_URL, + "api_user": "test-api-user", + "api_key": "test-api-key", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_credentials(hass): + """Test we handle invalid credentials error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "habitipy.aio.HabitipyAsync", + side_effect=InvalidAuth, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": DEFAULT_URL, + "api_user": "test-api-user", + "api_key": "test-api-key", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_credentials"} + + +async def test_form_unexpected_exception(hass): + """Test we handle unexpected exception error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.habitica.config_flow.HabitipyAsync", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": DEFAULT_URL, + "api_user": "test-api-user", + "api_key": "test-api-key", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +async def test_manual_flow_config_exist(hass, aioclient_mock): + """Test config flow discovers only already configured config.""" + MockConfigEntry( + domain=DOMAIN, + unique_id="test-api-user", + data={"api_user": "test-api-user", "api_key": "test-api-key"}, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + mock_obj = MagicMock() + mock_obj.user.get.side_effect = AsyncMock( + return_value={"api_user": "test-api-user"} + ) + + with patch( + "homeassistant.components.habitica.config_flow.HabitipyAsync", + return_value=mock_obj, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": DEFAULT_URL, + "api_user": "test-api-user", + "api_key": "test-api-key", + }, + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" From 94131df5e01ae9be572da534ab2f8bda000c4eab Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 17 Feb 2021 00:07:22 -0800 Subject: [PATCH 0525/1818] Remove exception handling for AttributeError in wemo (#46674) --- homeassistant/components/wemo/binary_sensor.py | 2 +- homeassistant/components/wemo/fan.py | 2 +- homeassistant/components/wemo/light.py | 4 ++-- homeassistant/components/wemo/switch.py | 2 +- tests/components/wemo/entity_test_helpers.py | 4 +++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index b6690ed6d28b23..2ea5f2d0b07f55 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -41,7 +41,7 @@ def _update(self, force_update=True): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except (AttributeError, ActionException) as err: + except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False self.wemo.reconnect_with_device() diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index cdbcc89fae6591..faf1b50f1cda3c 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -155,7 +155,7 @@ def _update(self, force_update=True): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except (AttributeError, ActionException) as err: + except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False self.wemo.reconnect_with_device() diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 1362c7d483cc34..9aa7c945671eae 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -191,7 +191,7 @@ def _update(self, force_update=True): try: self._update_lights(no_throttle=force_update) self._state = self.wemo.state - except (AttributeError, ActionException) as err: + except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False self.wemo.bridge.reconnect_with_device() @@ -238,7 +238,7 @@ def _update(self, force_update=True): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except (AttributeError, ActionException) as err: + except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False self.wemo.reconnect_with_device() diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 50926e07a11368..4d2b9c007d4792 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -177,7 +177,7 @@ def _update(self, force_update=True): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except (AttributeError, ActionException) as err: + except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False self.wemo.reconnect_with_device() diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index 0ecfc46d526487..87bc6fd7f40869 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -6,6 +6,8 @@ import threading from unittest.mock import patch +from pywemo.ouimeaux_device.api.service import ActionException + from homeassistant.components.homeassistant import ( DOMAIN as HA_DOMAIN, SERVICE_UPDATE_ENTITY, @@ -127,7 +129,7 @@ async def test_async_locked_update_with_exception( assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF await async_setup_component(hass, HA_DOMAIN, {}) update_polling_method = update_polling_method or pywemo_device.get_state - update_polling_method.side_effect = AttributeError + update_polling_method.side_effect = ActionException await hass.services.async_call( HA_DOMAIN, From ddf1f88b650b7a96293ab322be51657b98c444ce Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 17 Feb 2021 09:25:00 +0100 Subject: [PATCH 0526/1818] Fix multiple motion blinds gateways (#46622) local variable multicast was undefined for a second or more gateway that was setup. --- homeassistant/components/motion_blinds/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index e10f1655d2f186..5d02d5a14a8577 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -54,6 +54,7 @@ def stop_motion_multicast(event): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_motion_multicast) # Connect to motion gateway + multicast = hass.data[DOMAIN][KEY_MULTICAST_LISTENER] connect_gateway_class = ConnectMotionGateway(hass, multicast) if not await connect_gateway_class.async_connect_gateway(host, key): raise ConfigEntryNotReady From 971e27dd80dbbd78ddd5ff2f504994a75ceceef2 Mon Sep 17 00:00:00 2001 From: badguy99 <61918526+badguy99@users.noreply.github.com> Date: Wed, 17 Feb 2021 08:44:37 +0000 Subject: [PATCH 0527/1818] Home connect use consts (#46659) * Use more consts * black * re-black with black, version 20.8b1 --- homeassistant/components/home_connect/api.py | 82 ++++++++++++------- .../components/home_connect/binary_sensor.py | 18 ++-- .../components/home_connect/const.py | 12 +++ .../components/home_connect/light.py | 28 +++---- .../components/home_connect/sensor.py | 12 +-- .../components/home_connect/switch.py | 20 ++--- 6 files changed, 102 insertions(+), 70 deletions(-) diff --git a/homeassistant/components/home_connect/api.py b/homeassistant/components/home_connect/api.py index 44669ed200fbac..da5f1df20c6664 100644 --- a/homeassistant/components/home_connect/api.py +++ b/homeassistant/components/home_connect/api.py @@ -7,11 +7,27 @@ from homeconnect.api import HomeConnectError from homeassistant import config_entries, core -from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE, TIME_SECONDS +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ICON, + CONF_DEVICE, + CONF_ENTITIES, + DEVICE_CLASS_TIMESTAMP, + PERCENTAGE, + TIME_SECONDS, +) from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.dispatcher import dispatcher_send from .const import ( + ATTR_AMBIENT, + ATTR_DESC, + ATTR_DEVICE, + ATTR_KEY, + ATTR_SENSOR_TYPE, + ATTR_SIGN, + ATTR_UNIT, + ATTR_VALUE, BSH_ACTIVE_PROGRAM, BSH_OPERATION_STATE, BSH_POWER_OFF, @@ -72,7 +88,9 @@ def get_devices(self): else: _LOGGER.warning("Appliance type %s not implemented", app.type) continue - devices.append({"device": device, "entities": device.get_entity_info()}) + devices.append( + {CONF_DEVICE: device, CONF_ENTITIES: device.get_entity_info()} + ) self.devices = devices return devices @@ -104,8 +122,10 @@ def initialize(self): except (HomeConnectError, ValueError): _LOGGER.debug("Unable to fetch active programs. Probably offline") program_active = None - if program_active and "key" in program_active: - self.appliance.status[BSH_ACTIVE_PROGRAM] = {"value": program_active["key"]} + if program_active and ATTR_KEY in program_active: + self.appliance.status[BSH_ACTIVE_PROGRAM] = { + ATTR_VALUE: program_active[ATTR_KEY] + } self.appliance.listen_events(callback=self.event_callback) def event_callback(self, appliance): @@ -130,7 +150,7 @@ def get_program_switches(self): There will be one switch for each program. """ programs = self.get_programs_available() - return [{"device": self, "program_name": p["name"]} for p in programs] + return [{ATTR_DEVICE: self, "program_name": p["name"]} for p in programs] def get_program_sensors(self): """Get a dictionary with info about program sensors. @@ -145,13 +165,13 @@ def get_program_sensors(self): } return [ { - "device": self, - "desc": k, - "unit": unit, - "key": "BSH.Common.Option.{}".format(k.replace(" ", "")), - "icon": icon, - "device_class": device_class, - "sign": sign, + ATTR_DEVICE: self, + ATTR_DESC: k, + ATTR_UNIT: unit, + ATTR_KEY: "BSH.Common.Option.{}".format(k.replace(" ", "")), + ATTR_ICON: icon, + ATTR_DEVICE_CLASS: device_class, + ATTR_SIGN: sign, } for k, (unit, icon, device_class, sign) in sensors.items() ] @@ -165,13 +185,13 @@ def get_opstate_sensor(self): return [ { - "device": self, - "desc": "Operation State", - "unit": None, - "key": BSH_OPERATION_STATE, - "icon": "mdi:state-machine", - "device_class": None, - "sign": 1, + ATTR_DEVICE: self, + ATTR_DESC: "Operation State", + ATTR_UNIT: None, + ATTR_KEY: BSH_OPERATION_STATE, + ATTR_ICON: "mdi:state-machine", + ATTR_DEVICE_CLASS: None, + ATTR_SIGN: 1, } ] @@ -182,10 +202,10 @@ class DeviceWithDoor(HomeConnectDevice): def get_door_entity(self): """Get a dictionary with info about the door binary sensor.""" return { - "device": self, - "desc": "Door", - "sensor_type": "door", - "device_class": "door", + ATTR_DEVICE: self, + ATTR_DESC: "Door", + ATTR_SENSOR_TYPE: "door", + ATTR_DEVICE_CLASS: "door", } @@ -194,7 +214,7 @@ class DeviceWithLight(HomeConnectDevice): def get_light_entity(self): """Get a dictionary with info about the lighting.""" - return {"device": self, "desc": "Light", "ambient": None} + return {ATTR_DEVICE: self, ATTR_DESC: "Light", ATTR_AMBIENT: None} class DeviceWithAmbientLight(HomeConnectDevice): @@ -202,7 +222,7 @@ class DeviceWithAmbientLight(HomeConnectDevice): def get_ambientlight_entity(self): """Get a dictionary with info about the ambient lighting.""" - return {"device": self, "desc": "AmbientLight", "ambient": True} + return {ATTR_DEVICE: self, ATTR_DESC: "AmbientLight", ATTR_AMBIENT: True} class DeviceWithRemoteControl(HomeConnectDevice): @@ -211,9 +231,9 @@ class DeviceWithRemoteControl(HomeConnectDevice): def get_remote_control(self): """Get a dictionary with info about the remote control sensor.""" return { - "device": self, - "desc": "Remote Control", - "sensor_type": "remote_control", + ATTR_DEVICE: self, + ATTR_DESC: "Remote Control", + ATTR_SENSOR_TYPE: "remote_control", } @@ -222,7 +242,11 @@ class DeviceWithRemoteStart(HomeConnectDevice): def get_remote_start(self): """Get a dictionary with info about the remote start sensor.""" - return {"device": self, "desc": "Remote Start", "sensor_type": "remote_start"} + return { + ATTR_DEVICE: self, + ATTR_DESC: "Remote Start", + ATTR_SENSOR_TYPE: "remote_start", + } class Dryer( diff --git a/homeassistant/components/home_connect/binary_sensor.py b/homeassistant/components/home_connect/binary_sensor.py index 1713d34809ae2a..4dc21f2fd582c8 100644 --- a/homeassistant/components/home_connect/binary_sensor.py +++ b/homeassistant/components/home_connect/binary_sensor.py @@ -2,9 +2,14 @@ import logging from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.const import CONF_ENTITIES from .const import ( + ATTR_VALUE, BSH_DOOR_STATE, + BSH_DOOR_STATE_CLOSED, + BSH_DOOR_STATE_LOCKED, + BSH_DOOR_STATE_OPEN, BSH_REMOTE_CONTROL_ACTIVATION_STATE, BSH_REMOTE_START_ALLOWANCE_STATE, DOMAIN, @@ -21,7 +26,7 @@ def get_entities(): entities = [] hc_api = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: - entity_dicts = device_dict.get("entities", {}).get("binary_sensor", []) + entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("binary_sensor", []) entities += [HomeConnectBinarySensor(**d) for d in entity_dicts] return entities @@ -39,11 +44,8 @@ def __init__(self, device, desc, sensor_type, device_class=None): self._type = sensor_type if self._type == "door": self._update_key = BSH_DOOR_STATE - self._false_value_list = ( - "BSH.Common.EnumType.DoorState.Closed", - "BSH.Common.EnumType.DoorState.Locked", - ) - self._true_value_list = ["BSH.Common.EnumType.DoorState.Open"] + self._false_value_list = (BSH_DOOR_STATE_CLOSED, BSH_DOOR_STATE_LOCKED) + self._true_value_list = [BSH_DOOR_STATE_OPEN] elif self._type == "remote_control": self._update_key = BSH_REMOTE_CONTROL_ACTIVATION_STATE self._false_value_list = [False] @@ -68,9 +70,9 @@ async def async_update(self): state = self.device.appliance.status.get(self._update_key, {}) if not state: self._state = None - elif state.get("value") in self._false_value_list: + elif state.get(ATTR_VALUE) in self._false_value_list: self._state = False - elif state.get("value") in self._true_value_list: + elif state.get(ATTR_VALUE) in self._true_value_list: self._state = True else: _LOGGER.warning( diff --git a/homeassistant/components/home_connect/const.py b/homeassistant/components/home_connect/const.py index 98dd8d383bd0b7..438ee5ace164ab 100644 --- a/homeassistant/components/home_connect/const.py +++ b/homeassistant/components/home_connect/const.py @@ -26,5 +26,17 @@ BSH_AMBIENT_LIGHT_CUSTOM_COLOR = "BSH.Common.Setting.AmbientLightCustomColor" BSH_DOOR_STATE = "BSH.Common.Status.DoorState" +BSH_DOOR_STATE_CLOSED = "BSH.Common.EnumType.DoorState.Closed" +BSH_DOOR_STATE_LOCKED = "BSH.Common.EnumType.DoorState.Locked" +BSH_DOOR_STATE_OPEN = "BSH.Common.EnumType.DoorState.Open" SIGNAL_UPDATE_ENTITIES = "home_connect.update_entities" + +ATTR_AMBIENT = "ambient" +ATTR_DESC = "desc" +ATTR_DEVICE = "device" +ATTR_KEY = "key" +ATTR_SENSOR_TYPE = "sensor_type" +ATTR_SIGN = "sign" +ATTR_UNIT = "unit" +ATTR_VALUE = "value" diff --git a/homeassistant/components/home_connect/light.py b/homeassistant/components/home_connect/light.py index 814e3b0ed03a17..dc176ba90f2234 100644 --- a/homeassistant/components/home_connect/light.py +++ b/homeassistant/components/home_connect/light.py @@ -11,9 +11,11 @@ SUPPORT_COLOR, LightEntity, ) +from homeassistant.const import CONF_ENTITIES import homeassistant.util.color as color_util from .const import ( + ATTR_VALUE, BSH_AMBIENT_LIGHT_BRIGHTNESS, BSH_AMBIENT_LIGHT_COLOR, BSH_AMBIENT_LIGHT_COLOR_CUSTOM_COLOR, @@ -36,7 +38,7 @@ def get_entities(): entities = [] hc_api = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: - entity_dicts = device_dict.get("entities", {}).get("light", []) + entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("light", []) entity_list = [HomeConnectLight(**d) for d in entity_dicts] entities += entity_list return entities @@ -93,9 +95,7 @@ async def async_turn_on(self, **kwargs): _LOGGER.debug("Switching ambient light on for: %s", self.name) try: await self.hass.async_add_executor_job( - self.device.appliance.set_setting, - self._key, - True, + self.device.appliance.set_setting, self._key, True ) except HomeConnectError as err: _LOGGER.error("Error while trying to turn on ambient light: %s", err) @@ -135,9 +135,7 @@ async def async_turn_on(self, **kwargs): brightness = 10 + ceil(kwargs[ATTR_BRIGHTNESS] / 255 * 90) try: await self.hass.async_add_executor_job( - self.device.appliance.set_setting, - self._brightness_key, - brightness, + self.device.appliance.set_setting, self._brightness_key, brightness ) except HomeConnectError as err: _LOGGER.error("Error while trying set the brightness: %s", err) @@ -145,9 +143,7 @@ async def async_turn_on(self, **kwargs): _LOGGER.debug("Switching light on for: %s", self.name) try: await self.hass.async_add_executor_job( - self.device.appliance.set_setting, - self._key, - True, + self.device.appliance.set_setting, self._key, True ) except HomeConnectError as err: _LOGGER.error("Error while trying to turn on light: %s", err) @@ -159,9 +155,7 @@ async def async_turn_off(self, **kwargs): _LOGGER.debug("Switching light off for: %s", self.name) try: await self.hass.async_add_executor_job( - self.device.appliance.set_setting, - self._key, - False, + self.device.appliance.set_setting, self._key, False ) except HomeConnectError as err: _LOGGER.error("Error while trying to turn off light: %s", err) @@ -169,9 +163,9 @@ async def async_turn_off(self, **kwargs): async def async_update(self): """Update the light's status.""" - if self.device.appliance.status.get(self._key, {}).get("value") is True: + if self.device.appliance.status.get(self._key, {}).get(ATTR_VALUE) is True: self._state = True - elif self.device.appliance.status.get(self._key, {}).get("value") is False: + elif self.device.appliance.status.get(self._key, {}).get(ATTR_VALUE) is False: self._state = False else: self._state = None @@ -185,7 +179,7 @@ async def async_update(self): self._hs_color = None self._brightness = None else: - colorvalue = color.get("value")[1:] + colorvalue = color.get(ATTR_VALUE)[1:] rgb = color_util.rgb_hex_to_rgb_list(colorvalue) hsv = color_util.color_RGB_to_hsv(rgb[0], rgb[1], rgb[2]) self._hs_color = [hsv[0], hsv[1]] @@ -197,5 +191,5 @@ async def async_update(self): if brightness is None: self._brightness = None else: - self._brightness = ceil((brightness.get("value") - 10) * 255 / 90) + self._brightness = ceil((brightness.get(ATTR_VALUE) - 10) * 255 / 90) _LOGGER.debug("Updated, new brightness: %s", self._brightness) diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index e51efe06057e91..064ae033fb0d56 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -3,10 +3,10 @@ from datetime import timedelta import logging -from homeassistant.const import DEVICE_CLASS_TIMESTAMP +from homeassistant.const import CONF_ENTITIES, DEVICE_CLASS_TIMESTAMP import homeassistant.util.dt as dt_util -from .const import BSH_OPERATION_STATE, DOMAIN +from .const import ATTR_VALUE, BSH_OPERATION_STATE, DOMAIN from .entity import HomeConnectEntity _LOGGER = logging.getLogger(__name__) @@ -20,7 +20,7 @@ def get_entities(): entities = [] hc_api = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: - entity_dicts = device_dict.get("entities", {}).get("sensor", []) + entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("sensor", []) entities += [HomeConnectSensor(**d) for d in entity_dicts] return entities @@ -57,7 +57,7 @@ async def async_update(self): self._state = None else: if self.device_class == DEVICE_CLASS_TIMESTAMP: - if "value" not in status[self._key]: + if ATTR_VALUE not in status[self._key]: self._state = None elif ( self._state is not None @@ -68,12 +68,12 @@ async def async_update(self): # already past it, set state to None. self._state = None else: - seconds = self._sign * float(status[self._key]["value"]) + seconds = self._sign * float(status[self._key][ATTR_VALUE]) self._state = ( dt_util.utcnow() + timedelta(seconds=seconds) ).isoformat() else: - self._state = status[self._key].get("value") + self._state = status[self._key].get(ATTR_VALUE) if self._key == BSH_OPERATION_STATE: # Value comes back as an enum, we only really care about the # last part, so split it off diff --git a/homeassistant/components/home_connect/switch.py b/homeassistant/components/home_connect/switch.py index 346e739e5ff562..5e12d724a5e3ce 100644 --- a/homeassistant/components/home_connect/switch.py +++ b/homeassistant/components/home_connect/switch.py @@ -4,8 +4,10 @@ from homeconnect.api import HomeConnectError from homeassistant.components.switch import SwitchEntity +from homeassistant.const import CONF_DEVICE, CONF_ENTITIES from .const import ( + ATTR_VALUE, BSH_ACTIVE_PROGRAM, BSH_OPERATION_STATE, BSH_POWER_ON, @@ -25,9 +27,9 @@ def get_entities(): entities = [] hc_api = hass.data[DOMAIN][config_entry.entry_id] for device_dict in hc_api.devices: - entity_dicts = device_dict.get("entities", {}).get("switch", []) + entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("switch", []) entity_list = [HomeConnectProgramSwitch(**d) for d in entity_dicts] - entity_list += [HomeConnectPowerSwitch(device_dict["device"])] + entity_list += [HomeConnectPowerSwitch(device_dict[CONF_DEVICE])] entities += entity_list return entities @@ -78,7 +80,7 @@ async def async_turn_off(self, **kwargs): async def async_update(self): """Update the switch's status.""" state = self.device.appliance.status.get(BSH_ACTIVE_PROGRAM, {}) - if state.get("value") == self.program_name: + if state.get(ATTR_VALUE) == self.program_name: self._state = True else: self._state = False @@ -103,9 +105,7 @@ async def async_turn_on(self, **kwargs): _LOGGER.debug("Tried to switch on %s", self.name) try: await self.hass.async_add_executor_job( - self.device.appliance.set_setting, - BSH_POWER_STATE, - BSH_POWER_ON, + self.device.appliance.set_setting, BSH_POWER_STATE, BSH_POWER_ON ) except HomeConnectError as err: _LOGGER.error("Error while trying to turn on device: %s", err) @@ -129,17 +129,17 @@ async def async_turn_off(self, **kwargs): async def async_update(self): """Update the switch's status.""" if ( - self.device.appliance.status.get(BSH_POWER_STATE, {}).get("value") + self.device.appliance.status.get(BSH_POWER_STATE, {}).get(ATTR_VALUE) == BSH_POWER_ON ): self._state = True elif ( - self.device.appliance.status.get(BSH_POWER_STATE, {}).get("value") + self.device.appliance.status.get(BSH_POWER_STATE, {}).get(ATTR_VALUE) == self.device.power_off_state ): self._state = False elif self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get( - "value", None + ATTR_VALUE, None ) in [ "BSH.Common.EnumType.OperationState.Ready", "BSH.Common.EnumType.OperationState.DelayedStart", @@ -151,7 +151,7 @@ async def async_update(self): ]: self._state = True elif ( - self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get("value") + self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get(ATTR_VALUE) == "BSH.Common.EnumType.OperationState.Inactive" ): self._state = False From efb172cedd8a54a2a508e27b4110e3b7f6dc7bbd Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 17 Feb 2021 01:05:50 -0800 Subject: [PATCH 0528/1818] Fix flaky stream tests due to race in idle timeout callback (#46687) --- homeassistant/components/stream/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 9b5afb38ed0559..7f88885ac0be1e 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -141,8 +141,9 @@ def _hls_idle(self): """Reset access token and cleanup stream due to inactivity.""" self.access_token = None if not self.keepalive: - self._hls.cleanup() - self._hls = None + if self._hls: + self._hls.cleanup() + self._hls = None self._hls_timer = None self._check_idle() From fb73768164a0d885204838a8ec14a181d81c9431 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Wed, 17 Feb 2021 10:43:12 +0100 Subject: [PATCH 0529/1818] Fix Tuya Option Flow tests (#46651) --- tests/components/tuya/test_config_flow.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/components/tuya/test_config_flow.py b/tests/components/tuya/test_config_flow.py index 012886fe3b8b36..ede6e5ac1db36c 100644 --- a/tests/components/tuya/test_config_flow.py +++ b/tests/components/tuya/test_config_flow.py @@ -181,6 +181,15 @@ async def test_options_flow(hass): ) config_entry.add_to_hass(hass) + # Set up the integration to make sure the config flow module is loaded. + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + # Unload the integration to prepare for the test. + with patch("homeassistant.components.tuya.async_unload_entry", return_value=True): + assert await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + # Test check for integration not loaded result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT From facbd73130e1e19026de344ea8d7cd568f02d1c6 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 17 Feb 2021 07:15:13 -0500 Subject: [PATCH 0530/1818] Clean up xbee (#46549) --- homeassistant/components/xbee/__init__.py | 5 ++--- homeassistant/components/xbee/binary_sensor.py | 8 ++------ homeassistant/components/xbee/const.py | 5 +++++ homeassistant/components/xbee/light.py | 8 ++------ homeassistant/components/xbee/sensor.py | 3 +-- homeassistant/components/xbee/switch.py | 9 ++------- 6 files changed, 14 insertions(+), 24 deletions(-) create mode 100644 homeassistant/components/xbee/const.py diff --git a/homeassistant/components/xbee/__init__.py b/homeassistant/components/xbee/__init__.py index e6175a4dccfe93..6373cfa7535564 100644 --- a/homeassistant/components/xbee/__init__.py +++ b/homeassistant/components/xbee/__init__.py @@ -21,9 +21,9 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN -DOMAIN = "xbee" +_LOGGER = logging.getLogger(__name__) SIGNAL_XBEE_FRAME_RECEIVED = "xbee_frame_received" @@ -59,7 +59,6 @@ def setup(hass, config): """Set up the connection to the XBee Zigbee device.""" - usb_device = config[DOMAIN].get(CONF_DEVICE, DEFAULT_DEVICE) baud = int(config[DOMAIN].get(CONF_BAUD, DEFAULT_BAUD)) try: diff --git a/homeassistant/components/xbee/binary_sensor.py b/homeassistant/components/xbee/binary_sensor.py index 47c7515ddc7770..01095822d1f57c 100644 --- a/homeassistant/components/xbee/binary_sensor.py +++ b/homeassistant/components/xbee/binary_sensor.py @@ -3,12 +3,8 @@ from homeassistant.components.binary_sensor import BinarySensorEntity -from . import DOMAIN, PLATFORM_SCHEMA, XBeeDigitalIn, XBeeDigitalInConfig - -CONF_ON_STATE = "on_state" - -DEFAULT_ON_STATE = "high" -STATES = ["high", "low"] +from . import PLATFORM_SCHEMA, XBeeDigitalIn, XBeeDigitalInConfig +from .const import CONF_ON_STATE, DOMAIN, STATES PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_ON_STATE): vol.In(STATES)}) diff --git a/homeassistant/components/xbee/const.py b/homeassistant/components/xbee/const.py new file mode 100644 index 00000000000000..a77e71e92f5b62 --- /dev/null +++ b/homeassistant/components/xbee/const.py @@ -0,0 +1,5 @@ +"""Constants for the xbee integration.""" +CONF_ON_STATE = "on_state" +DEFAULT_ON_STATE = "high" +DOMAIN = "xbee" +STATES = ["high", "low"] diff --git a/homeassistant/components/xbee/light.py b/homeassistant/components/xbee/light.py index 76ed8120166aee..859feee495bf52 100644 --- a/homeassistant/components/xbee/light.py +++ b/homeassistant/components/xbee/light.py @@ -3,12 +3,8 @@ from homeassistant.components.light import LightEntity -from . import DOMAIN, PLATFORM_SCHEMA, XBeeDigitalOut, XBeeDigitalOutConfig - -CONF_ON_STATE = "on_state" - -DEFAULT_ON_STATE = "high" -STATES = ["high", "low"] +from . import PLATFORM_SCHEMA, XBeeDigitalOut, XBeeDigitalOutConfig +from .const import CONF_ON_STATE, DEFAULT_ON_STATE, DOMAIN, STATES PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Optional(CONF_ON_STATE, default=DEFAULT_ON_STATE): vol.In(STATES)} diff --git a/homeassistant/components/xbee/sensor.py b/homeassistant/components/xbee/sensor.py index 4a392691032020..4d9f9ca518b6b0 100644 --- a/homeassistant/components/xbee/sensor.py +++ b/homeassistant/components/xbee/sensor.py @@ -5,14 +5,13 @@ import voluptuous as vol from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import CONF_TYPE, TEMP_CELSIUS from homeassistant.helpers.entity import Entity from . import DOMAIN, PLATFORM_SCHEMA, XBeeAnalogIn, XBeeAnalogInConfig, XBeeConfig _LOGGER = logging.getLogger(__name__) -CONF_TYPE = "type" CONF_MAX_VOLTS = "max_volts" DEFAULT_VOLTS = 1.2 diff --git a/homeassistant/components/xbee/switch.py b/homeassistant/components/xbee/switch.py index cdb0d2677c541c..b97d9f315d572b 100644 --- a/homeassistant/components/xbee/switch.py +++ b/homeassistant/components/xbee/switch.py @@ -3,13 +3,8 @@ from homeassistant.components.switch import SwitchEntity -from . import DOMAIN, PLATFORM_SCHEMA, XBeeDigitalOut, XBeeDigitalOutConfig - -CONF_ON_STATE = "on_state" - -DEFAULT_ON_STATE = "high" - -STATES = ["high", "low"] +from . import PLATFORM_SCHEMA, XBeeDigitalOut, XBeeDigitalOutConfig +from .const import CONF_ON_STATE, DOMAIN, STATES PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_ON_STATE): vol.In(STATES)}) From f7c0fc55530adc4f0dd0cc3efb23ffd4a6477804 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 17 Feb 2021 04:17:16 -0800 Subject: [PATCH 0531/1818] Increase async_timeout for wemo update polling (#46649) --- homeassistant/components/wemo/entity.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index e7c0712272ccb2..2278cc854b21a8 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -49,16 +49,16 @@ async def async_update(self) -> None: """Update WeMo state. Wemo has an aggressive retry logic that sometimes can take over a - minute to return. If we don't get a state after 5 seconds, assume the - Wemo switch is unreachable. If update goes through, it will be made - available again. + minute to return. If we don't get a state within the scan interval, + assume the Wemo switch is unreachable. If update goes through, it will + be made available again. """ # If an update is in progress, we don't do anything if self._update_lock.locked(): return try: - with async_timeout.timeout(5): + with async_timeout.timeout(self.platform.scan_interval.seconds - 0.1): await asyncio.shield(self._async_locked_update(True)) except asyncio.TimeoutError: _LOGGER.warning("Lost connection to %s", self.name) From eb3e5cb67f1a0d64cf65c22c5f668448168e4009 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 17 Feb 2021 04:17:31 -0800 Subject: [PATCH 0532/1818] Remove calls to wemo.reconnect_with_device (#46646) --- homeassistant/components/wemo/binary_sensor.py | 1 - homeassistant/components/wemo/fan.py | 1 - homeassistant/components/wemo/light.py | 2 -- homeassistant/components/wemo/switch.py | 1 - tests/components/wemo/entity_test_helpers.py | 1 - 5 files changed, 6 deletions(-) diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index 2ea5f2d0b07f55..0d7f253205774c 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -44,4 +44,3 @@ def _update(self, force_update=True): except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False - self.wemo.reconnect_with_device() diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index faf1b50f1cda3c..678fd93fe05216 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -158,7 +158,6 @@ def _update(self, force_update=True): except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False - self.wemo.reconnect_with_device() def turn_on( self, diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 9aa7c945671eae..169534bf0c5f1d 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -194,7 +194,6 @@ def _update(self, force_update=True): except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False - self.wemo.bridge.reconnect_with_device() else: self._is_on = self._state.get("onoff") != WEMO_OFF self._brightness = self._state.get("level", 255) @@ -241,7 +240,6 @@ def _update(self, force_update=True): except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False - self.wemo.reconnect_with_device() def turn_on(self, **kwargs): """Turn the dimmer on.""" diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 4d2b9c007d4792..f925aad3f72f97 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -180,4 +180,3 @@ def _update(self, force_update=True): except ActionException as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False - self.wemo.reconnect_with_device() diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index 87bc6fd7f40869..4c92640572bf57 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -139,7 +139,6 @@ async def test_async_locked_update_with_exception( ) assert hass.states.get(wemo_entity.entity_id).state == STATE_UNAVAILABLE - pywemo_device.reconnect_with_device.assert_called_with() async def test_async_update_with_timeout_and_recovery(hass, wemo_entity, pywemo_device): From 1e9483a0e90e14a7a6d0fcb21f37fd3be031de51 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 17 Feb 2021 13:23:15 +0100 Subject: [PATCH 0533/1818] Bump RMVtransport to v0.3.0 (#46691) --- homeassistant/components/rmvtransport/manifest.json | 2 +- homeassistant/components/rmvtransport/sensor.py | 7 +++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/rmvtransport/manifest.json b/homeassistant/components/rmvtransport/manifest.json index 595e2d4834ae3f..30aef1a0616330 100644 --- a/homeassistant/components/rmvtransport/manifest.json +++ b/homeassistant/components/rmvtransport/manifest.json @@ -3,7 +3,7 @@ "name": "RMV", "documentation": "https://www.home-assistant.io/integrations/rmvtransport", "requirements": [ - "PyRMVtransport==0.2.10" + "PyRMVtransport==0.3.0" ], "codeowners": [ "@cgtobi" diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index da42c0cc9273fe..ad1ceea3d8619a 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -4,7 +4,10 @@ import logging from RMVtransport import RMVtransport -from RMVtransport.rmvtransport import RMVtransportApiConnectionError +from RMVtransport.rmvtransport import ( + RMVtransportApiConnectionError, + RMVtransportDataError, +) import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -229,7 +232,7 @@ async def async_update(self): max_journeys=50, ) - except RMVtransportApiConnectionError: + except (RMVtransportApiConnectionError, RMVtransportDataError): self.departures = [] _LOGGER.warning("Could not retrieve data from rmv.de") return diff --git a/requirements_all.txt b/requirements_all.txt index de6c34f4febd16..f02ef7b2bb17b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -49,7 +49,7 @@ PyNaCl==1.3.0 PyQRCode==1.2.1 # homeassistant.components.rmvtransport -PyRMVtransport==0.2.10 +PyRMVtransport==0.3.0 # homeassistant.components.telegram_bot PySocks==1.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0e863e23bb1ab2..efba3a67c911ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -21,7 +21,7 @@ PyNaCl==1.3.0 PyQRCode==1.2.1 # homeassistant.components.rmvtransport -PyRMVtransport==0.2.10 +PyRMVtransport==0.3.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From 4ab0151fb1cbefb31def23ba850e197da0a5027f Mon Sep 17 00:00:00 2001 From: Gaetan Semet Date: Wed, 17 Feb 2021 15:06:16 +0100 Subject: [PATCH 0534/1818] Discover HRT4-ZW / SRT321 SetPoint in zwave_js (#46625) Missing a specific class to allow discovery of the setpoint command. Fix #46570 Signed-off-by: Gaetan Semet --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 1aa70ea2fa0977..2022258e6ea0e6 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -226,6 +226,7 @@ class ZWaveDiscoverySchema: device_class_generic={"Thermostat"}, device_class_specific={ "Setpoint Thermostat", + "Unused", }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_SETPOINT}, From b2df9aaaf1060916e8484260d0914ec3872581e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Feb 2021 08:03:11 -1000 Subject: [PATCH 0535/1818] Update zha to use new fan entity model (#45758) * Update zha to use new fan entity model * fixes * tweaks for zha * pylint * augment test cover --- homeassistant/components/zha/fan.py | 173 +++++++++++++++++----------- tests/components/zha/test_fan.py | 123 +++++++++++++++++++- 2 files changed, 222 insertions(+), 74 deletions(-) diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index bc5714ef08c327..1cd66f94686fa0 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -1,22 +1,26 @@ """Fans on Zigbee Home Automation networks.""" +from abc import abstractmethod import functools +import math from typing import List, Optional from zigpy.exceptions import ZigbeeException import zigpy.zcl.clusters.hvac as hvac from homeassistant.components.fan import ( + ATTR_PERCENTAGE, + ATTR_PRESET_MODE, DOMAIN, - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_OFF, SUPPORT_SET_SPEED, FanEntity, ) from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import State, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + percentage_to_ranged_value, + ranged_value_to_percentage, +) from .core import discovery from .core.const import ( @@ -32,24 +36,20 @@ # Additional speeds in zigbee's ZCL # Spec is unclear as to what this value means. On King Of Fans HBUniversal # receiver, this means Very High. -SPEED_ON = "on" +PRESET_MODE_ON = "on" # The fan speed is self-regulated -SPEED_AUTO = "auto" +PRESET_MODE_AUTO = "auto" # When the heated/cooled space is occupied, the fan is always on -SPEED_SMART = "smart" - -SPEED_LIST = [ - SPEED_OFF, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_HIGH, - SPEED_ON, - SPEED_AUTO, - SPEED_SMART, -] - -VALUE_TO_SPEED = dict(enumerate(SPEED_LIST)) -SPEED_TO_VALUE = {speed: i for i, speed in enumerate(SPEED_LIST)} +PRESET_MODE_SMART = "smart" + +SPEED_RANGE = (1, 3) # off is not included +PRESET_MODES_TO_NAME = {4: PRESET_MODE_ON, 5: PRESET_MODE_AUTO, 6: PRESET_MODE_SMART} + +NAME_TO_PRESET_MODE = {v: k for k, v in PRESET_MODES_TO_NAME.items()} +PRESET_MODES = list(NAME_TO_PRESET_MODE) + +DEFAULT_ON_PERCENTAGE = 50 + STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) GROUP_MATCH = functools.partial(ZHA_ENTITIES.group_match, DOMAIN) @@ -74,51 +74,41 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BaseFan(FanEntity): """Base representation of a ZHA fan.""" - def __init__(self, *args, **kwargs): - """Initialize the fan.""" - super().__init__(*args, **kwargs) - self._state = None - self._fan_channel = None - - @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return SPEED_LIST - @property - def speed(self) -> str: - """Return the current speed.""" - return self._state + def preset_modes(self) -> str: + """Return the available preset modes.""" + return PRESET_MODES @property def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # async def async_turn_on( self, speed=None, percentage=None, preset_mode=None, **kwargs ) -> None: """Turn the entity on.""" - if speed is None: - speed = SPEED_MEDIUM - - await self.async_set_speed(speed) + await self.async_set_percentage(percentage) async def async_turn_off(self, **kwargs) -> None: """Turn the entity off.""" - await self.async_set_speed(SPEED_OFF) + await self.async_set_percentage(0) - async def async_set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - await self._fan_channel.async_set_speed(SPEED_TO_VALUE[speed]) - self.async_set_state(0, "fan_mode", speed) + async def async_set_percentage(self, percentage: Optional[int]) -> None: + """Set the speed percenage of the fan.""" + if percentage is None: + percentage = DEFAULT_ON_PERCENTAGE + fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + await self._async_set_fan_mode(fan_mode) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the speed percenage of the fan.""" + fan_mode = NAME_TO_PRESET_MODE.get(preset_mode) + await self._async_set_fan_mode(fan_mode) + + @abstractmethod + async def _async_set_fan_mode(self, fan_mode: int) -> None: + """Set the fan mode for the fan.""" @callback def async_set_state(self, attr_id, attr_name, value): @@ -142,15 +132,32 @@ async def async_added_to_hass(self): ) @property - def speed(self) -> Optional[str]: - """Return the current speed.""" - return VALUE_TO_SPEED.get(self._fan_channel.fan_mode) + def percentage(self) -> str: + """Return the current speed percentage.""" + if ( + self._fan_channel.fan_mode is None + or self._fan_channel.fan_mode > SPEED_RANGE[1] + ): + return None + if self._fan_channel.fan_mode == 0: + return 0 + return ranged_value_to_percentage(SPEED_RANGE, self._fan_channel.fan_mode) + + @property + def preset_mode(self) -> str: + """Return the current preset mode.""" + return PRESET_MODES_TO_NAME.get(self._fan_channel.fan_mode) @callback def async_set_state(self, attr_id, attr_name, value): """Handle state update from channel.""" self.async_write_ha_state() + async def _async_set_fan_mode(self, fan_mode: int) -> None: + """Set the fan mode for the fan.""" + await self._fan_channel.async_set_speed(fan_mode) + self.async_set_state(0, "fan_mode", fan_mode) + @GROUP_MATCH() class FanGroup(BaseFan, ZhaGroupEntity): @@ -164,30 +171,60 @@ def __init__( self._available: bool = False group = self.zha_device.gateway.get_group(self._group_id) self._fan_channel = group.endpoint[hvac.Fan.cluster_id] + self._percentage = None + self._preset_mode = None - # what should we do with this hack? - async def async_set_speed(value) -> None: - """Set the speed of the fan.""" - try: - await self._fan_channel.write_attributes({"fan_mode": value}) - except ZigbeeException as ex: - self.error("Could not set speed: %s", ex) - return + @property + def percentage(self) -> str: + """Return the current speed percentage.""" + return self._percentage - self._fan_channel.async_set_speed = async_set_speed + @property + def preset_mode(self) -> str: + """Return the current preset mode.""" + return self._preset_mode + + async def async_set_percentage(self, percentage: Optional[int]) -> None: + """Set the speed percenage of the fan.""" + if percentage is None: + percentage = DEFAULT_ON_PERCENTAGE + fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + await self._async_set_fan_mode(fan_mode) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the speed percenage of the fan.""" + fan_mode = NAME_TO_PRESET_MODE.get(preset_mode) + await self._async_set_fan_mode(fan_mode) + + async def _async_set_fan_mode(self, fan_mode: int) -> None: + """Set the fan mode for the group.""" + try: + await self._fan_channel.write_attributes({"fan_mode": fan_mode}) + except ZigbeeException as ex: + self.error("Could not set fan mode: %s", ex) + self.async_set_state(0, "fan_mode", fan_mode) async def async_update(self): """Attempt to retrieve on off state from the fan.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] states: List[State] = list(filter(None, all_states)) - on_states: List[State] = [state for state in states if state.state != SPEED_OFF] - + percentage_states: List[State] = [ + state for state in states if state.attributes.get(ATTR_PERCENTAGE) + ] + preset_mode_states: List[State] = [ + state for state in states if state.attributes.get(ATTR_PRESET_MODE) + ] self._available = any(state.state != STATE_UNAVAILABLE for state in states) - # for now just use first non off state since its kind of arbitrary - if not on_states: - self._state = SPEED_OFF + + if percentage_states: + self._percentage = percentage_states[0].attributes[ATTR_PERCENTAGE] + self._preset_mode = None + elif preset_mode_states: + self._preset_mode = preset_mode_states[0].attributes[ATTR_PRESET_MODE] + self._percentage = None else: - self._state = on_states[0].state + self._percentage = None + self._preset_mode = None async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 61828c135bc113..b6347ac6568a53 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock, call, patch import pytest +from zigpy.exceptions import ZigbeeException import zigpy.profiles.zha as zha import zigpy.zcl.clusters.general as general import zigpy.zcl.clusters.hvac as hvac @@ -9,8 +10,11 @@ from homeassistant.components import fan from homeassistant.components.fan import ( + ATTR_PERCENTAGE, + ATTR_PRESET_MODE, ATTR_SPEED, DOMAIN, + SERVICE_SET_PRESET_MODE, SERVICE_SET_SPEED, SPEED_HIGH, SPEED_LOW, @@ -20,6 +24,11 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.zha.core.discovery import GROUP_PROBE from homeassistant.components.zha.core.group import GroupMember +from homeassistant.components.zha.fan import ( + PRESET_MODE_AUTO, + PRESET_MODE_ON, + PRESET_MODE_SMART, +) from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -173,6 +182,12 @@ async def test_fan(hass, zha_device_joined_restored, zigpy_device): assert len(cluster.write_attributes.mock_calls) == 1 assert cluster.write_attributes.call_args == call({"fan_mode": 3}) + # change preset_mode from HA + cluster.write_attributes.reset_mock() + await async_set_preset_mode(hass, entity_id, preset_mode=PRESET_MODE_ON) + assert len(cluster.write_attributes.mock_calls) == 1 + assert cluster.write_attributes.call_args == call({"fan_mode": 4}) + # test adding new fan to the network and HA await async_test_rejoin(hass, zigpy_device, [cluster], (1,)) @@ -206,6 +221,17 @@ async def async_set_speed(hass, entity_id, speed=None): await hass.services.async_call(DOMAIN, SERVICE_SET_SPEED, data, blocking=True) +async def async_set_preset_mode(hass, entity_id, preset_mode=None): + """Set preset_mode for specified fan.""" + data = { + key: value + for key, value in [(ATTR_ENTITY_ID, entity_id), (ATTR_PRESET_MODE, preset_mode)] + if value is not None + } + + await hass.services.async_call(DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True) + + @patch( "zigpy.zcl.clusters.hvac.Fan.write_attributes", new=AsyncMock(return_value=zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]), @@ -276,6 +302,24 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato assert len(group_fan_cluster.write_attributes.mock_calls) == 1 assert group_fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 3} + # change preset mode from HA + group_fan_cluster.write_attributes.reset_mock() + await async_set_preset_mode(hass, entity_id, preset_mode=PRESET_MODE_ON) + assert len(group_fan_cluster.write_attributes.mock_calls) == 1 + assert group_fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 4} + + # change preset mode from HA + group_fan_cluster.write_attributes.reset_mock() + await async_set_preset_mode(hass, entity_id, preset_mode=PRESET_MODE_AUTO) + assert len(group_fan_cluster.write_attributes.mock_calls) == 1 + assert group_fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5} + + # change preset mode from HA + group_fan_cluster.write_attributes.reset_mock() + await async_set_preset_mode(hass, entity_id, preset_mode=PRESET_MODE_SMART) + assert len(group_fan_cluster.write_attributes.mock_calls) == 1 + assert group_fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 6} + # test some of the group logic to make sure we key off states correctly await send_attributes_report(hass, dev1_fan_cluster, {0: 0}) await send_attributes_report(hass, dev2_fan_cluster, {0: 0}) @@ -296,14 +340,74 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato assert hass.states.get(entity_id).state == STATE_OFF +@patch( + "zigpy.zcl.clusters.hvac.Fan.write_attributes", + new=AsyncMock(side_effect=ZigbeeException), +) +async def test_zha_group_fan_entity_failure_state( + hass, device_fan_1, device_fan_2, coordinator, caplog +): + """Test the fan entity for a ZHA group when writing attributes generates an exception.""" + zha_gateway = get_zha_gateway(hass) + assert zha_gateway is not None + zha_gateway.coordinator_zha_device = coordinator + coordinator._zha_gateway = zha_gateway + device_fan_1._zha_gateway = zha_gateway + device_fan_2._zha_gateway = zha_gateway + member_ieee_addresses = [device_fan_1.ieee, device_fan_2.ieee] + members = [GroupMember(device_fan_1.ieee, 1), GroupMember(device_fan_2.ieee, 1)] + + # test creating a group with 2 members + zha_group = await zha_gateway.async_create_zigpy_group("Test Group", members) + await hass.async_block_till_done() + + assert zha_group is not None + assert len(zha_group.members) == 2 + for member in zha_group.members: + assert member.device.ieee in member_ieee_addresses + assert member.group == zha_group + assert member.endpoint is not None + + entity_domains = GROUP_PROBE.determine_entity_domains(hass, zha_group) + assert len(entity_domains) == 2 + + assert LIGHT_DOMAIN in entity_domains + assert DOMAIN in entity_domains + + entity_id = async_find_group_entity_id(hass, DOMAIN, zha_group) + assert hass.states.get(entity_id) is not None + + group_fan_cluster = zha_group.endpoint[hvac.Fan.cluster_id] + + await async_enable_traffic(hass, [device_fan_1, device_fan_2], enabled=False) + await hass.async_block_till_done() + # test that the fans were created and that they are unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [device_fan_1, device_fan_2]) + + # test that the fan group entity was created and is off + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on from HA + group_fan_cluster.write_attributes.reset_mock() + await async_turn_on(hass, entity_id) + await hass.async_block_till_done() + assert len(group_fan_cluster.write_attributes.mock_calls) == 1 + assert group_fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 2} + + assert "Could not set fan mode" in caplog.text + + @pytest.mark.parametrize( - "plug_read, expected_state, expected_speed", + "plug_read, expected_state, expected_speed, expected_percentage", ( - (None, STATE_OFF, None), - ({"fan_mode": 0}, STATE_OFF, SPEED_OFF), - ({"fan_mode": 1}, STATE_ON, SPEED_LOW), - ({"fan_mode": 2}, STATE_ON, SPEED_MEDIUM), - ({"fan_mode": 3}, STATE_ON, SPEED_HIGH), + (None, STATE_OFF, None, None), + ({"fan_mode": 0}, STATE_OFF, SPEED_OFF, 0), + ({"fan_mode": 1}, STATE_ON, SPEED_LOW, 33), + ({"fan_mode": 2}, STATE_ON, SPEED_MEDIUM, 66), + ({"fan_mode": 3}, STATE_ON, SPEED_HIGH, 100), ), ) async def test_fan_init( @@ -313,6 +417,7 @@ async def test_fan_init( plug_read, expected_state, expected_speed, + expected_percentage, ): """Test zha fan platform.""" @@ -324,6 +429,8 @@ async def test_fan_init( assert entity_id is not None assert hass.states.get(entity_id).state == expected_state assert hass.states.get(entity_id).attributes[ATTR_SPEED] == expected_speed + assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE] == expected_percentage + assert hass.states.get(entity_id).attributes[ATTR_PRESET_MODE] is None async def test_fan_update_entity( @@ -341,6 +448,8 @@ async def test_fan_update_entity( assert entity_id is not None assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id).attributes[ATTR_SPEED] == SPEED_OFF + assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE] == 0 + assert hass.states.get(entity_id).attributes[ATTR_PRESET_MODE] is None assert cluster.read_attributes.await_count == 1 await async_setup_component(hass, "homeassistant", {}) @@ -358,5 +467,7 @@ async def test_fan_update_entity( "homeassistant", "update_entity", {"entity_id": entity_id}, blocking=True ) assert hass.states.get(entity_id).state == STATE_ON + assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE] == 33 assert hass.states.get(entity_id).attributes[ATTR_SPEED] == SPEED_LOW + assert hass.states.get(entity_id).attributes[ATTR_PRESET_MODE] is None assert cluster.read_attributes.await_count == 3 From 8bee3cda375b9a25d27c360364abadc4e393e9b0 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 17 Feb 2021 14:36:39 -0800 Subject: [PATCH 0536/1818] Centralize wemo exception handling (#46705) --- .../components/wemo/binary_sensor.py | 11 +----- homeassistant/components/wemo/entity.py | 33 +++++++++++++++- homeassistant/components/wemo/fan.py | 36 +++-------------- homeassistant/components/wemo/light.py | 39 ++++--------------- homeassistant/components/wemo/switch.py | 21 ++-------- 5 files changed, 48 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index 0d7f253205774c..94d5a587c17250 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -2,8 +2,6 @@ import asyncio import logging -from pywemo.ouimeaux_device.api.service import ActionException - from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -35,12 +33,5 @@ class WemoBinarySensor(WemoSubscriptionEntity, BinarySensorEntity): def _update(self, force_update=True): """Update the sensor state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) - - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 2278cc854b21a8..91470d0cd5cd45 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -1,10 +1,12 @@ """Classes shared among Wemo entities.""" import asyncio +import contextlib import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, Generator, Optional import async_timeout from pywemo import WeMoDevice +from pywemo.ouimeaux_device.api.service import ActionException from homeassistant.helpers.entity import Entity @@ -13,6 +15,18 @@ _LOGGER = logging.getLogger(__name__) +class ExceptionHandlerStatus: + """Exit status from the _wemo_exception_handler context manager.""" + + # An exception if one was raised in the _wemo_exception_handler. + exception: Optional[Exception] = None + + @property + def success(self) -> bool: + """Return True if the handler completed with no exception.""" + return self.exception is None + + class WemoEntity(Entity): """Common methods for Wemo entities. @@ -36,6 +50,23 @@ def available(self) -> bool: """Return true if switch is available.""" return self._available + @contextlib.contextmanager + def _wemo_exception_handler( + self, message: str + ) -> Generator[ExceptionHandlerStatus, None, None]: + """Wrap device calls to set `_available` when wemo exceptions happen.""" + status = ExceptionHandlerStatus() + try: + yield status + except ActionException as err: + status.exception = err + _LOGGER.warning("Could not %s for %s (%s)", message, self.name, err) + self._available = False + else: + if not self._available: + _LOGGER.info("Reconnected to %s", self.name) + self._available = True + def _update(self, force_update: Optional[bool] = True): """Update the device state.""" raise NotImplementedError() diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 678fd93fe05216..94dab468a6980c 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -4,7 +4,6 @@ import logging import math -from pywemo.ouimeaux_device.api.service import ActionException import voluptuous as vol from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity @@ -138,7 +137,7 @@ def supported_features(self) -> int: def _update(self, force_update=True): """Update the device state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) self._fan_mode = self.wemo.fan_mode @@ -152,13 +151,6 @@ def _update(self, force_update=True): if self.wemo.fan_mode != WEMO_FAN_OFF: self._last_fan_on_mode = self.wemo.fan_mode - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False - def turn_on( self, speed: str = None, @@ -171,11 +163,8 @@ def turn_on( def turn_off(self, **kwargs) -> None: """Turn the switch off.""" - try: + with self._wemo_exception_handler("turn off"): self.wemo.set_state(WEMO_FAN_OFF) - except ActionException as err: - _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() @@ -188,13 +177,8 @@ def set_percentage(self, percentage: int) -> None: else: named_speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) - try: + with self._wemo_exception_handler("set speed"): self.wemo.set_state(named_speed) - except ActionException as err: - _LOGGER.warning( - "Error while setting speed of device %s (%s)", self.name, err - ) - self._available = False self.schedule_update_ha_state() @@ -211,24 +195,14 @@ def set_humidity(self, target_humidity: float) -> None: elif target_humidity >= 100: pywemo_humidity = WEMO_HUMIDITY_100 - try: + with self._wemo_exception_handler("set humidity"): self.wemo.set_humidity(pywemo_humidity) - except ActionException as err: - _LOGGER.warning( - "Error while setting humidity of device: %s (%s)", self.name, err - ) - self._available = False self.schedule_update_ha_state() def reset_filter_life(self) -> None: """Reset the filter life to 100%.""" - try: + with self._wemo_exception_handler("reset filter life"): self.wemo.reset_filter_life() - except ActionException as err: - _LOGGER.warning( - "Error while resetting filter life on device: %s (%s)", self.name, err - ) - self._available = False self.schedule_update_ha_state() diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 169534bf0c5f1d..bbcdafaf3513a1 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -3,8 +3,6 @@ from datetime import timedelta import logging -from pywemo.ouimeaux_device.api.service import ActionException - from homeassistant import util from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -158,7 +156,7 @@ def turn_on(self, **kwargs): "force_update": False, } - try: + with self._wemo_exception_handler("turn on"): if xy_color is not None: self.wemo.set_color(xy_color, transition=transition_time) @@ -167,9 +165,6 @@ def turn_on(self, **kwargs): if self.wemo.turn_on(**turn_on_kwargs): self._state["onoff"] = WEMO_ON - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() @@ -177,28 +172,21 @@ def turn_off(self, **kwargs): """Turn the light off.""" transition_time = int(kwargs.get(ATTR_TRANSITION, 0)) - try: + with self._wemo_exception_handler("turn off"): if self.wemo.turn_off(transition=transition_time): self._state["onoff"] = WEMO_OFF - except ActionException as err: - _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def _update(self, force_update=True): """Synchronize state with bridge.""" - try: + with self._wemo_exception_handler("update status") as handler: self._update_lights(no_throttle=force_update) self._state = self.wemo.state - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False - else: + if handler.success: self._is_on = self._state.get("onoff") != WEMO_OFF self._brightness = self._state.get("level", 255) self._color_temp = self._state.get("temperature_mireds") - self._available = True xy_color = self._state.get("color_xy") @@ -228,19 +216,12 @@ def brightness(self): def _update(self, force_update=True): """Update the device state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) wemobrightness = int(self.wemo.get_brightness(force_update)) self._brightness = int((wemobrightness * 255) / 100) - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False - def turn_on(self, **kwargs): """Turn the dimmer on.""" # Wemo dimmer switches use a range of [0, 100] to control @@ -251,24 +232,18 @@ def turn_on(self, **kwargs): else: brightness = 255 - try: + with self._wemo_exception_handler("turn on"): if self.wemo.on(): self._state = WEMO_ON self.wemo.set_brightness(brightness) - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the dimmer off.""" - try: + with self._wemo_exception_handler("turn off"): if self.wemo.off(): self._state = WEMO_OFF - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index f925aad3f72f97..15b38550b938bc 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -3,8 +3,6 @@ from datetime import datetime, timedelta import logging -from pywemo.ouimeaux_device.api.service import ActionException - from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -140,29 +138,23 @@ def icon(self): def turn_on(self, **kwargs): """Turn the switch on.""" - try: + with self._wemo_exception_handler("turn on"): if self.wemo.on(): self._state = WEMO_ON - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the switch off.""" - try: + with self._wemo_exception_handler("turn off"): if self.wemo.off(): self._state = WEMO_OFF - except ActionException as err: - _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def _update(self, force_update=True): """Update the device state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) if self.wemo.model_name == "Insight": @@ -173,10 +165,3 @@ def _update(self, force_update=True): elif self.wemo.model_name == "CoffeeMaker": self.coffeemaker_mode = self.wemo.mode self._mode_string = self.wemo.mode_string - - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False From 2ac075bb3762e93e92be7b0bc7ab96185559a64b Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 17 Feb 2021 15:38:08 -0800 Subject: [PATCH 0537/1818] Perform wemo state update quickly after a timeout (#46702) * Perform state update quickly after a timeout * with async_timeout.timeout(...) -> async with async_timeout.timeout(...) --- homeassistant/components/wemo/entity.py | 19 ++++++++++++---- tests/components/wemo/entity_test_helpers.py | 24 +++++++++++++------- tests/components/wemo/test_light_bridge.py | 7 +++--- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 91470d0cd5cd45..d9d90c5508bde0 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -89,16 +89,28 @@ async def async_update(self) -> None: return try: - with async_timeout.timeout(self.platform.scan_interval.seconds - 0.1): - await asyncio.shield(self._async_locked_update(True)) + async with async_timeout.timeout( + self.platform.scan_interval.seconds - 0.1 + ) as timeout: + await asyncio.shield(self._async_locked_update(True, timeout)) except asyncio.TimeoutError: _LOGGER.warning("Lost connection to %s", self.name) self._available = False - async def _async_locked_update(self, force_update: bool) -> None: + async def _async_locked_update( + self, force_update: bool, timeout: Optional[async_timeout.timeout] = None + ) -> None: """Try updating within an async lock.""" async with self._update_lock: await self.hass.async_add_executor_job(self._update, force_update) + # When the timeout expires HomeAssistant is no longer waiting for an + # update from the device. Instead, the state needs to be updated + # asynchronously. This also handles the case where an update came + # directly from the device (device push). In that case no polling + # update was involved and the state also needs to be updated + # asynchronously. + if not timeout or timeout.expired: + self.async_write_ha_state() class WemoSubscriptionEntity(WemoEntity): @@ -152,4 +164,3 @@ async def _async_locked_subscription_callback(self, force_update: bool) -> None: return await self._async_locked_update(force_update) - self.async_write_ha_state() diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index 4c92640572bf57..e584cb5fb39d91 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -6,6 +6,7 @@ import threading from unittest.mock import patch +import async_timeout from pywemo.ouimeaux_device.api.service import ActionException from homeassistant.components.homeassistant import ( @@ -146,7 +147,19 @@ async def test_async_update_with_timeout_and_recovery(hass, wemo_entity, pywemo_ assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF await async_setup_component(hass, HA_DOMAIN, {}) - with patch("async_timeout.timeout", side_effect=asyncio.TimeoutError): + event = threading.Event() + + def get_state(*args): + event.wait() + return 0 + + if hasattr(pywemo_device, "bridge_update"): + pywemo_device.bridge_update.side_effect = get_state + else: + pywemo_device.get_state.side_effect = get_state + timeout = async_timeout.timeout(0) + + with patch("async_timeout.timeout", return_value=timeout): await hass.services.async_call( HA_DOMAIN, SERVICE_UPDATE_ENTITY, @@ -157,11 +170,6 @@ async def test_async_update_with_timeout_and_recovery(hass, wemo_entity, pywemo_ assert hass.states.get(wemo_entity.entity_id).state == STATE_UNAVAILABLE # Check that the entity recovers and is available after the update succeeds. - await hass.services.async_call( - HA_DOMAIN, - SERVICE_UPDATE_ENTITY, - {ATTR_ENTITY_ID: [wemo_entity.entity_id]}, - blocking=True, - ) - + event.set() + await hass.async_block_till_done() assert hass.states.get(wemo_entity.entity_id).state == STATE_OFF diff --git a/tests/components/wemo/test_light_bridge.py b/tests/components/wemo/test_light_bridge.py index 3e7f79200c6504..573f75a66d92de 100644 --- a/tests/components/wemo/test_light_bridge.py +++ b/tests/components/wemo/test_light_bridge.py @@ -69,9 +69,10 @@ async def test_async_update_with_timeout_and_recovery( hass, pywemo_bridge_light, wemo_entity, pywemo_device ): """Test that the entity becomes unavailable after a timeout, and that it recovers.""" - await entity_test_helpers.test_async_update_with_timeout_and_recovery( - hass, wemo_entity, pywemo_device - ) + with _bypass_throttling(): + await entity_test_helpers.test_async_update_with_timeout_and_recovery( + hass, wemo_entity, pywemo_device + ) async def test_async_locked_update_with_exception( From 28ffa97635cfa2e668430273e66f3f671bee6c16 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 18 Feb 2021 00:07:45 +0000 Subject: [PATCH 0538/1818] [ci skip] Translation update --- .../components/habitica/translations/ca.json | 20 +++++++++++++++++ .../components/habitica/translations/en.json | 16 +++++++------- .../components/habitica/translations/et.json | 20 +++++++++++++++++ .../components/habitica/translations/no.json | 20 +++++++++++++++++ .../habitica/translations/zh-Hant.json | 20 +++++++++++++++++ .../components/smarttub/translations/ca.json | 22 +++++++++++++++++++ .../components/smarttub/translations/et.json | 22 +++++++++++++++++++ .../components/smarttub/translations/no.json | 22 +++++++++++++++++++ .../smarttub/translations/zh-Hant.json | 22 +++++++++++++++++++ 9 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/habitica/translations/ca.json create mode 100644 homeassistant/components/habitica/translations/et.json create mode 100644 homeassistant/components/habitica/translations/no.json create mode 100644 homeassistant/components/habitica/translations/zh-Hant.json create mode 100644 homeassistant/components/smarttub/translations/ca.json create mode 100644 homeassistant/components/smarttub/translations/et.json create mode 100644 homeassistant/components/smarttub/translations/no.json create mode 100644 homeassistant/components/smarttub/translations/zh-Hant.json diff --git a/homeassistant/components/habitica/translations/ca.json b/homeassistant/components/habitica/translations/ca.json new file mode 100644 index 00000000000000..675fc33db8cafc --- /dev/null +++ b/homeassistant/components/habitica/translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "api_user": "ID d'usuari de l'API d'Habitica", + "name": "Substitueix el nom d'usuari d'Habitica. S'utilitzar\u00e0 per a crides de servei", + "url": "URL" + }, + "description": "Connecta el perfil d'Habitica per permetre el seguiment del teu perfil i tasques d'usuari. Tingues en compte que l'api_id i l'api_key els has d'obtenir des de https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/en.json b/homeassistant/components/habitica/translations/en.json index fa571d0d72a4b4..ffbbd2de8408b8 100644 --- a/homeassistant/components/habitica/translations/en.json +++ b/homeassistant/components/habitica/translations/en.json @@ -1,20 +1,20 @@ { "config": { "error": { - "invalid_credentials": "Invalid credentials", - "unknown": "Unknown error" + "invalid_credentials": "Invalid authentication", + "unknown": "Unexpected error" }, "step": { "user": { "data": { - "url": "Habitica URL", - "name": "Override for Habitica’s username. Will be used for service calls", - "api_user": "Habitica’s API user ID", - "api_key": "Habitica's API user KEY" + "api_key": "API Key", + "api_user": "Habitica\u2019s API user ID", + "name": "Override for Habitica\u2019s username. Will be used for service calls", + "url": "URL" }, - "description": "Connect your Habitica profile to allow monitoring of your user's profile and tasks. Note that api_id and api_key must be grabed from https://habitica.com/user/settings/api" + "description": "Connect your Habitica profile to allow monitoring of your user's profile and tasks. Note that api_id and api_key must be gotten from https://habitica.com/user/settings/api" } } }, "title": "Habitica" -} +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/et.json b/homeassistant/components/habitica/translations/et.json new file mode 100644 index 00000000000000..cfc2bcf898c383 --- /dev/null +++ b/homeassistant/components/habitica/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "api_user": "Habitica API kasutaja ID", + "name": "Habitica kasutajanime alistamine. Kasutatakse teenuste kutsumiseks", + "url": "URL" + }, + "description": "\u00dchenda oma Habitica profiil, et saaksid j\u00e4lgida oma kasutaja profiili ja \u00fclesandeid. Pane t\u00e4hele, et api_id ja api_key tuleb hankida aadressilt https://habitica.com/user/settings/api" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/no.json b/homeassistant/components/habitica/translations/no.json new file mode 100644 index 00000000000000..cdb72d3c3d674e --- /dev/null +++ b/homeassistant/components/habitica/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "api_user": "Habiticas API-bruker-ID", + "name": "Overstyr for Habiticas brukernavn. Blir brukt til serviceanrop", + "url": "URL" + }, + "description": "Koble til Habitica-profilen din for \u00e5 tillate overv\u00e5king av brukerens profil og oppgaver. Merk at api_id og api_key m\u00e5 hentes fra https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/zh-Hant.json b/homeassistant/components/habitica/translations/zh-Hant.json new file mode 100644 index 00000000000000..001682b5c88577 --- /dev/null +++ b/homeassistant/components/habitica/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "api_user": "Habitica \u4e4b API \u4f7f\u7528\u8005 ID", + "name": "\u8986\u5beb Habitica \u4f7f\u7528\u8005\u540d\u7a31\u3001\u7528\u4ee5\u670d\u52d9\u547c\u53eb", + "url": "\u7db2\u5740" + }, + "description": "\u9023\u7dda\u81f3 Habitica \u8a2d\u5b9a\u6a94\u4ee5\u4f9b\u76e3\u63a7\u500b\u4eba\u8a2d\u5b9a\u8207\u4efb\u52d9\u3002\u6ce8\u610f\uff1a\u5fc5\u9808\u7531 https://habitica.com/user/settings/api \u53d6\u5f97 api_id \u8207 api_key" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/ca.json b/homeassistant/components/smarttub/translations/ca.json new file mode 100644 index 00000000000000..6d882abeee6583 --- /dev/null +++ b/homeassistant/components/smarttub/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + }, + "description": "Introdueix el correu electr\u00f2nic i la contrasenya de SmartTub per iniciar sessi\u00f3", + "title": "Inici de sessi\u00f3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/et.json b/homeassistant/components/smarttub/translations/et.json new file mode 100644 index 00000000000000..676edee158421f --- /dev/null +++ b/homeassistant/components/smarttub/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" + }, + "error": { + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "email": "E-posti aadress", + "password": "Salas\u00f5na" + }, + "description": "Sisselogimiseks sisesta oma SmartTubi e-posti aadress ja salas\u00f5na", + "title": "Sisselogimine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/no.json b/homeassistant/components/smarttub/translations/no.json new file mode 100644 index 00000000000000..7f1c5982d28adc --- /dev/null +++ b/homeassistant/components/smarttub/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "email": "E-post", + "password": "Passord" + }, + "description": "Skriv inn din SmartTub e-postadresse og passord for \u00e5 logge p\u00e5", + "title": "P\u00e5logging" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/zh-Hant.json b/homeassistant/components/smarttub/translations/zh-Hant.json new file mode 100644 index 00000000000000..9491e7d2f2570f --- /dev/null +++ b/homeassistant/components/smarttub/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + }, + "description": "\u8acb\u8f38\u5165\u767b\u5165 SmartTub \u4e4b Email \u5730\u5740\u8207\u5bc6\u78bc\u3002", + "title": "\u767b\u5165" + } + } + } +} \ No newline at end of file From e9334347eb8354795cdb17f1401a80ef3abfb269 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Thu, 18 Feb 2021 14:54:10 +0800 Subject: [PATCH 0539/1818] Bump pylutron 0.2.7 (#46717) --- homeassistant/components/lutron/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json index 2dbeb51da58ede..fdd47d9005d622 100644 --- a/homeassistant/components/lutron/manifest.json +++ b/homeassistant/components/lutron/manifest.json @@ -2,6 +2,6 @@ "domain": "lutron", "name": "Lutron", "documentation": "https://www.home-assistant.io/integrations/lutron", - "requirements": ["pylutron==0.2.5"], + "requirements": ["pylutron==0.2.7"], "codeowners": ["@JonGilmore"] } diff --git a/requirements_all.txt b/requirements_all.txt index f02ef7b2bb17b1..aba43a17ee04ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1503,7 +1503,7 @@ pyloopenergy==0.2.1 pylutron-caseta==0.9.0 # homeassistant.components.lutron -pylutron==0.2.5 +pylutron==0.2.7 # homeassistant.components.mailgun pymailgunner==1.4 From 39785c5cef194bcaa0432f7fd0e17450e72e20a8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 18 Feb 2021 00:00:11 -1000 Subject: [PATCH 0540/1818] Switch ssdp to be async by using async_upnp_client for scanning (#46554) SSDP scans no longer runs in the executor This is an interim step that converts the async_upnp_client response to netdisco's object to ensure fully backwards compatibility --- homeassistant/components/ssdp/__init__.py | 31 ++++++---- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/package_constraints.txt | 1 + requirements_all.txt | 1 + requirements_test_all.txt | 1 + tests/components/ssdp/test_init.py | 65 ++++++++++++++++----- 6 files changed, 73 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index f07e88d811a547..8cad4a74bf89b8 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -1,14 +1,16 @@ """The SSDP integration.""" import asyncio from datetime import timedelta -import itertools import logging +from typing import Any, Mapping import aiohttp +from async_upnp_client.search import async_search from defusedxml import ElementTree from netdisco import ssdp, util from homeassistant.const import EVENT_HOMEASSISTANT_STARTED +from homeassistant.core import callback from homeassistant.helpers.event import async_track_time_interval from homeassistant.loader import async_get_ssdp @@ -51,12 +53,6 @@ async def initialize(_): return True -def _run_ssdp_scans(): - _LOGGER.debug("Scanning") - # Run 3 times as packets can get lost - return itertools.chain.from_iterable([ssdp.scan() for _ in range(3)]) - - class Scanner: """Class to manage SSDP scanning.""" @@ -64,25 +60,38 @@ def __init__(self, hass, integration_matchers): """Initialize class.""" self.hass = hass self.seen = set() + self._entries = [] self._integration_matchers = integration_matchers self._description_cache = {} + async def _on_ssdp_response(self, data: Mapping[str, Any]) -> None: + """Process an ssdp response.""" + self.async_store_entry( + ssdp.UPNPEntry({key.lower(): item for key, item in data.items()}) + ) + + @callback + def async_store_entry(self, entry): + """Save an entry for later processing.""" + self._entries.append(entry) + async def async_scan(self, _): """Scan for new entries.""" - entries = await self.hass.async_add_executor_job(_run_ssdp_scans) - await self._process_entries(entries) + await async_search(async_callback=self._on_ssdp_response) + await self._process_entries() # We clear the cache after each run. We track discovered entries # so will never need a description twice. self._description_cache.clear() + self._entries.clear() - async def _process_entries(self, entries): + async def _process_entries(self): """Process SSDP entries.""" entries_to_process = [] unseen_locations = set() - for entry in entries: + for entry in self._entries: key = (entry.st, entry.location) if key in self.seen: diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index ed20ae9ead6c9f..931119e239894b 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": ["defusedxml==0.6.0", "netdisco==2.8.2"], + "requirements": ["defusedxml==0.6.0", "netdisco==2.8.2", "async-upnp-client==0.14.13"], "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d926471660ea2f..1a1163c7a8829c 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -3,6 +3,7 @@ PyNaCl==1.3.0 aiohttp==3.7.3 aiohttp_cors==0.7.0 astral==1.10.1 +async-upnp-client==0.14.13 async_timeout==3.0.1 attrs==19.3.0 awesomeversion==21.2.2 diff --git a/requirements_all.txt b/requirements_all.txt index aba43a17ee04ca..0f96ab51235d96 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -284,6 +284,7 @@ asmog==0.0.6 asterisk_mbox==0.5.0 # homeassistant.components.dlna_dmr +# homeassistant.components.ssdp # homeassistant.components.upnp async-upnp-client==0.14.13 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index efba3a67c911ab..148b66e9ac1303 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -173,6 +173,7 @@ aprslib==0.6.46 arcam-fmj==0.5.3 # homeassistant.components.dlna_dmr +# homeassistant.components.ssdp # homeassistant.components.upnp async-upnp-client==0.14.13 diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index bba809aedbb4b4..8ca82e93bfc712 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -14,15 +14,18 @@ async def test_scan_match_st(hass, caplog): """Test matching based on ST.""" scanner = ssdp.Scanner(hass, {"mock-domain": [{"st": "mock-st"}]}) - with patch( - "netdisco.ssdp.scan", - return_value=[ + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( Mock( st="mock-st", location=None, values={"usn": "mock-usn", "server": "mock-server", "ext": ""}, ) - ], + ) + + with patch( + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: @@ -58,9 +61,14 @@ async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key): ) scanner = ssdp.Scanner(hass, {"mock-domain": [{key: "Paulus"}]}) + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( + Mock(st="mock-st", location="http://1.1.1.1", values={}) + ) + with patch( - "netdisco.ssdp.scan", - return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: @@ -95,9 +103,14 @@ async def test_scan_not_all_present(hass, aioclient_mock): }, ) + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( + Mock(st="mock-st", location="http://1.1.1.1", values={}) + ) + with patch( - "netdisco.ssdp.scan", - return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: @@ -131,9 +144,14 @@ async def test_scan_not_all_match(hass, aioclient_mock): }, ) + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( + Mock(st="mock-st", location="http://1.1.1.1", values={}) + ) + with patch( - "netdisco.ssdp.scan", - return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: @@ -148,9 +166,14 @@ async def test_scan_description_fetch_fail(hass, aioclient_mock, exc): aioclient_mock.get("http://1.1.1.1", exc=exc) scanner = ssdp.Scanner(hass, {}) + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( + Mock(st="mock-st", location="http://1.1.1.1", values={}) + ) + with patch( - "netdisco.ssdp.scan", - return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ): await scanner.async_scan(None) @@ -165,9 +188,14 @@ async def test_scan_description_parse_fail(hass, aioclient_mock): ) scanner = ssdp.Scanner(hass, {}) + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( + Mock(st="mock-st", location="http://1.1.1.1", values={}) + ) + with patch( - "netdisco.ssdp.scan", - return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ): await scanner.async_scan(None) @@ -196,9 +224,14 @@ async def test_invalid_characters(hass, aioclient_mock): }, ) + async def _inject_entry(*args, **kwargs): + scanner.async_store_entry( + Mock(st="mock-st", location="http://1.1.1.1", values={}) + ) + with patch( - "netdisco.ssdp.scan", - return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})], + "homeassistant.components.ssdp.async_search", + side_effect=_inject_entry, ), patch.object( hass.config_entries.flow, "async_init", return_value=mock_coro() ) as mock_init: From da662c48901e8443966d06880fb31da8fafa190b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:23:30 +0100 Subject: [PATCH 0541/1818] Add selectors to Input * service definitions (#46652) --- .../components/input_boolean/services.yaml | 26 +++----- .../components/input_datetime/services.yaml | 27 ++++++-- .../components/input_number/services.yaml | 30 +++++---- .../components/input_select/services.yaml | 64 +++++++++++-------- .../components/input_text/services.yaml | 11 ++-- 5 files changed, 90 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/input_boolean/services.yaml b/homeassistant/components/input_boolean/services.yaml index 5ab5e7a9b821d1..8cefe2b4974614 100644 --- a/homeassistant/components/input_boolean/services.yaml +++ b/homeassistant/components/input_boolean/services.yaml @@ -1,20 +1,14 @@ toggle: - description: Toggles an input boolean. - fields: - entity_id: - description: Entity id of the input boolean to toggle. - example: input_boolean.notify_alerts + description: Toggle an input boolean + target: + turn_off: - description: Turns off an input boolean - fields: - entity_id: - description: Entity id of the input boolean to turn off. - example: input_boolean.notify_alerts + description: Turn off an input boolean + target: + turn_on: - description: Turns on an input boolean. - fields: - entity_id: - description: Entity id of the input boolean to turn on. - example: input_boolean.notify_alerts + description: Turn on an input boolean + target: + reload: - description: Reload the input_boolean configuration. + description: Reload the input_boolean configuration diff --git a/homeassistant/components/input_datetime/services.yaml b/homeassistant/components/input_datetime/services.yaml index bcbadc45aad433..ef4cb9556c8e0c 100644 --- a/homeassistant/components/input_datetime/services.yaml +++ b/homeassistant/components/input_datetime/services.yaml @@ -1,21 +1,36 @@ set_datetime: - description: This can be used to dynamically set the date and/or time. Use date/time, datetime or timestamp. + description: This can be used to dynamically set the date and/or time + target: fields: - entity_id: - description: Entity id of the input datetime to set the new value. - example: input_datetime.test_date_time date: + name: Date description: The target date the entity should be set to. example: '"2019-04-20"' + selector: + text: time: + name: Time description: The target time the entity should be set to. example: '"05:04:20"' + selector: + time: datetime: + name: Date & Time description: The target date & time the entity should be set to. example: '"2019-04-20 05:04:20"' + selector: + text: timestamp: - description: The target date & time the entity should be set to as expressed by a UNIX timestamp. + name: Timestamp + description: + The target date & time the entity should be set to as expressed by a + UNIX timestamp. example: 1598027400 + selector: + number: + min: 0 + max: 9223372036854775807 + mode: box reload: - description: Reload the input_datetime configuration. + description: Reload the input_datetime configuration diff --git a/homeassistant/components/input_number/services.yaml b/homeassistant/components/input_number/services.yaml index 4d69bf72eda693..700a2c281441c7 100644 --- a/homeassistant/components/input_number/services.yaml +++ b/homeassistant/components/input_number/services.yaml @@ -1,23 +1,25 @@ decrement: - description: Decrement the value of an input number entity by its stepping. - fields: - entity_id: - description: Entity id of the input number that should be decremented. - example: input_number.threshold + description: Decrement the value of an input number entity by its stepping + target: + increment: - description: Increment the value of an input number entity by its stepping. - fields: - entity_id: - description: Entity id of the input number that should be incremented. - example: input_number.threshold + description: Increment the value of an input number entity by its stepping + target: + set_value: - description: Set the value of an input number entity. + description: Set the value of an input number entity + target: fields: - entity_id: - description: Entity id of the input number to set the new value. - example: input_number.threshold value: + name: Value description: The target value the entity should be set to. + required: true example: 42 + selector: + number: + min: 0 + max: 9223372036854775807 + step: 0.001 + mode: box reload: description: Reload the input_number configuration. diff --git a/homeassistant/components/input_select/services.yaml b/homeassistant/components/input_select/services.yaml index 0eddb158d34aa0..bf1b9e8103362d 100644 --- a/homeassistant/components/input_select/services.yaml +++ b/homeassistant/components/input_select/services.yaml @@ -1,50 +1,58 @@ select_next: - description: Select the next options of an input select entity. + description: Select the next options of an input select entity + target: fields: - entity_id: - description: Entity id of the input select to select the next value for. - example: input_select.my_select cycle: - description: If the option should cycle from the last to the first (defaults to true). + name: Cycle + description: If the option should cycle from the last to the first. + default: true example: true + selector: + boolean: + select_option: - description: Select an option of an input select entity. + description: Select an option of an input select entity + target: fields: - entity_id: - description: Entity id of the input select to select the value. - example: input_select.my_select option: + name: Option description: Option to be selected. + required: true example: '"Item A"' + selector: + text: + select_previous: - description: Select the previous options of an input select entity. + description: Select the previous options of an input select entity + target: fields: - entity_id: - description: Entity id of the input select to select the previous value for. - example: input_select.my_select cycle: - description: If the option should cycle from the first to the last (defaults to true). + name: Cycle + description: If the option should cycle from the first to the last. + default: true example: true + selector: + boolean: + select_first: - description: Select the first option of an input select entity. - fields: - entity_id: - description: Entity id of the input select to select the first value for. - example: input_select.my_select + description: Select the first option of an input select entity + target: + select_last: - description: Select the last option of an input select entity. - fields: - entity_id: - description: Entity id of the input select to select the last value for. - example: input_select.my_select + description: Select the last option of an input select entity + target: + set_options: - description: Set the options of an input select entity. + description: Set the options of an input select entity + target: fields: - entity_id: - description: Entity id of the input select to set the new options for. - example: input_select.my_select options: + name: Options description: Options for the input select entity. + required: true example: '["Item A", "Item B", "Item C"]' + selector: + object: + reload: description: Reload the input_select configuration. diff --git a/homeassistant/components/input_text/services.yaml b/homeassistant/components/input_text/services.yaml index 0f74cd8940ea9c..b5ac97f837a50a 100644 --- a/homeassistant/components/input_text/services.yaml +++ b/homeassistant/components/input_text/services.yaml @@ -1,11 +1,14 @@ set_value: - description: Set the value of an input text entity. + description: Set the value of an input text entity + target: fields: - entity_id: - description: Entity id of the input text to set the new value. - example: input_text.text1 value: + name: Value description: The target value the entity should be set to. + required: true example: This is an example text + selector: + text: + reload: description: Reload the input_text configuration. From 9f3fdb1b68de84a6d441f04bc2d251c0c504856e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:23:50 +0100 Subject: [PATCH 0542/1818] Add selectors to Alert service definitions (#46627) --- homeassistant/components/alert/services.yaml | 23 +++++++------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/alert/services.yaml b/homeassistant/components/alert/services.yaml index 995302005460e7..c8c1d5d814add8 100644 --- a/homeassistant/components/alert/services.yaml +++ b/homeassistant/components/alert/services.yaml @@ -1,18 +1,11 @@ toggle: - description: Toggle alert's notifications. - fields: - entity_id: - description: Name of the alert to toggle. - example: alert.garage_door_open + description: Toggle alert's notifications + target: + turn_off: - description: Silence alert's notifications. - fields: - entity_id: - description: Name of the alert to silence. - example: alert.garage_door_open + description: Silence alert's notifications + target: + turn_on: - description: Reset alert's notifications. - fields: - entity_id: - description: Name of the alert to reset. - example: alert.garage_door_open + description: Reset alert's notifications + target: From dec2eb36fd5070fd4a6a9a860eab14d08e3ee615 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:24:04 +0100 Subject: [PATCH 0543/1818] Add selectors to Camera service definitions (#46630) --- homeassistant/components/camera/services.yaml | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml index 70d33da884c047..3ae5b650304952 100644 --- a/homeassistant/components/camera/services.yaml +++ b/homeassistant/components/camera/services.yaml @@ -1,69 +1,89 @@ # Describes the format for available camera services turn_off: - description: Turn off camera. - fields: - entity_id: - description: Entity id. - example: "camera.living_room" + description: Turn off camera + target: turn_on: - description: Turn on camera. - fields: - entity_id: - description: Entity id. - example: "camera.living_room" + description: Turn on camera + target: enable_motion_detection: - description: Enable the motion detection in a camera. - fields: - entity_id: - description: Name(s) of entities to enable motion detection. - example: "camera.living_room_camera" + description: Enable the motion detection in a camera + target: disable_motion_detection: - description: Disable the motion detection in a camera. - fields: - entity_id: - description: Name(s) of entities to disable motion detection. - example: "camera.living_room_camera" + description: Disable the motion detection in a camera + target: snapshot: - description: Take a snapshot from a camera. + description: Take a snapshot from a camera + target: fields: - entity_id: - description: Name(s) of entities to create snapshots from. - example: "camera.living_room_camera" filename: + name: Filename description: Template of a Filename. Variable is entity_id. + required: true example: "/tmp/snapshot_{{ entity_id.name }}.jpg" + selector: + text: play_stream: - description: Play camera stream on supported media player. + description: Play camera stream on supported media player + target: fields: - entity_id: - description: Name(s) of entities to stream from. - example: "camera.living_room_camera" media_player: + name: Media Player description: Name(s) of media player to stream to. + required: true example: "media_player.living_room_tv" + selector: + entity: + domain: media_player format: - description: (Optional) Stream format supported by media player. + name: Format + description: Stream format supported by media player. + default: "hls" example: "hls" + selector: + select: + options: + - "hls" record: - description: Record live camera feed. + description: Record live camera feed + target: fields: - entity_id: - description: Name of entities to record. - example: "camera.living_room_camera" filename: - description: Template of a Filename. Variable is entity_id. Must be mp4. + name: Filename + description: Template of a Filename. Variable is entity_id. Must be mp4. + required: true example: "/tmp/snapshot_{{ entity_id.name }}.mp4" + selector: + text: duration: - description: (Optional) Target recording length (in seconds). + name: Duration + description: Target recording length. default: 30 example: 30 + selector: + number: + min: 1 + max: 3600 + step: 1 + unit_of_measurement: seconds + mode: slider lookback: - description: (Optional) Target lookback period (in seconds) to include in addition to duration. Only available if there is currently an active HLS stream. + name: Lookback + description: + Target lookback period to include in addition to duration. Only + available if there is currently an active HLS stream. + default: 0 example: 4 + selector: + number: + min: 0 + max: 300 + step: 1 + unit_of_measurement: seconds + mode: slider From c8c3ce4172323684691d110c359cd313a81dd8d6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:24:42 +0100 Subject: [PATCH 0544/1818] Add selectors to Switch service definitions (#46635) --- homeassistant/components/switch/services.yaml | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/switch/services.yaml b/homeassistant/components/switch/services.yaml index 74dda2ddf4f0e4..de45995797fe7c 100644 --- a/homeassistant/components/switch/services.yaml +++ b/homeassistant/components/switch/services.yaml @@ -1,22 +1,13 @@ # Describes the format for available switch services turn_on: - description: Turn a switch on. - fields: - entity_id: - description: Name(s) of entities to turn on - example: "switch.living_room" + description: Turn a switch on + target: turn_off: - description: Turn a switch off. - fields: - entity_id: - description: Name(s) of entities to turn off. - example: "switch.living_room" + description: Turn a switch off + target: toggle: - description: Toggles a switch state. - fields: - entity_id: - description: Name(s) of entities to toggle. - example: "switch.living_room" + description: Toggles a switch state + target: From 4e93a0c774793f9ecc7981b66fd5d5313af6d2cd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:24:50 +0100 Subject: [PATCH 0545/1818] Add selectors to Downloader service definitions (#46638) --- .../components/downloader/services.yaml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/downloader/services.yaml b/homeassistant/components/downloader/services.yaml index 6e16e00432fdb7..5ac383fc4f68b5 100644 --- a/homeassistant/components/downloader/services.yaml +++ b/homeassistant/components/downloader/services.yaml @@ -1,15 +1,28 @@ download_file: - description: Downloads a file to the download location. + description: Downloads a file to the download location fields: url: + name: URL description: The URL of the file to download. + required: true example: "http://example.org/myfile" + selector: + text: subdir: + name: Subdirectory description: Download into subdirectory. example: "download_dir" + selector: + text: filename: + name: Filename description: Determine the filename. example: "my_file_name" + selector: + text: overwrite: + name: Overwrite description: Whether to overwrite the file or not. - example: "false" + default: false + selector: + boolean: From 2dfbd4fbcf1069e18328bf63328ebe8f5942e11c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:24:58 +0100 Subject: [PATCH 0546/1818] Add selectors to Fan service definitions (#46639) --- homeassistant/components/fan/services.yaml | 106 +++++++++++++-------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml index 760aaabcf4ad21..2f5802b69f7df9 100644 --- a/homeassistant/components/fan/services.yaml +++ b/homeassistant/components/fan/services.yaml @@ -1,80 +1,102 @@ # Describes the format for available fan services set_speed: - description: Sets fan speed. + description: Set fan speed + target: fields: - entity_id: - description: Name(s) of the entities to set - example: "fan.living_room" speed: - description: Speed setting + name: Speed + description: Speed setting. + required: true example: "low" + selector: + text: set_preset_mode: - description: Set preset mode for a fan device. + description: Set preset mode for a fan device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "fan.kitchen" preset_mode: - description: New value of preset mode + name: Preset mode + description: New value of preset mode. + required: true example: "auto" + selector: + text: set_percentage: - description: Sets fan speed percentage. + description: Set fan speed percentage + target: fields: - entity_id: - description: Name(s) of the entities to set - example: "fan.living_room" percentage: - description: Percentage speed setting + name: Percentage + description: Percentage speed setting. + required: true example: 25 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider turn_on: - description: Turns fan on. + description: Turn fan on + target: fields: - entity_id: - description: Names(s) of the entities to turn on - example: "fan.living_room" speed: - description: Speed setting + name: Speed + description: Speed setting. example: "high" percentage: - description: Percentage speed setting + name: Percentage + description: Percentage speed setting. example: 75 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider preset_mode: - description: Preset mode setting + name: Preset mode + description: Preset mode setting. example: "auto" + selector: + text: turn_off: - description: Turns fan off. - fields: - entity_id: - description: Names(s) of the entities to turn off - example: "fan.living_room" + description: Turn fan off + target: oscillate: - description: Oscillates the fan. + description: Oscillate the fan + target: fields: - entity_id: - description: Name(s) of the entities to oscillate - example: "fan.desk_fan" oscillating: - description: Flag to turn on/off oscillation + name: Oscillating + description: Flag to turn on/off oscillation. + required: true example: true + selector: + boolean: toggle: - description: Toggle the fan on/off. - fields: - entity_id: - description: Name(s) of the entities to toggle - example: "fan.living_room" + description: Toggle the fan on/off + target: set_direction: - description: Set the fan rotation. + description: Set the fan rotation + target: fields: - entity_id: - description: Name(s) of the entities to set - example: "fan.living_room" direction: - description: The direction to rotate. Either 'forward' or 'reverse' + name: Direction + description: The direction to rotate. + required: true example: "forward" + selector: + select: + options: + - "forward" + - "reverse" From e45fc4562b80a68ede88643107e50abf42a4f380 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:25:16 +0100 Subject: [PATCH 0547/1818] Add selectors to Cover service definitions (#46634) Co-authored-by: Tobias Sauerwein --- homeassistant/components/cover/services.yaml | 90 +++++++++----------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/cover/services.yaml b/homeassistant/components/cover/services.yaml index 604955aa1992ed..173674193cb61d 100644 --- a/homeassistant/components/cover/services.yaml +++ b/homeassistant/components/cover/services.yaml @@ -1,77 +1,67 @@ # Describes the format for available cover services open_cover: - description: Open all or specified cover. - fields: - entity_id: - description: Name(s) of cover(s) to open. - example: "cover.living_room" + description: Open all or specified cover + target: close_cover: - description: Close all or specified cover. - fields: - entity_id: - description: Name(s) of cover(s) to close. - example: "cover.living_room" + description: Close all or specified cover + target: toggle: - description: Toggles a cover open/closed. - fields: - entity_id: - description: Name(s) of cover(s) to toggle. - example: "cover.garage_door" + description: Toggles a cover open/closed + target: set_cover_position: - description: Move to specific position all or specified cover. + description: Move to specific position all or specified cover + target: fields: - entity_id: - description: Name(s) of cover(s) to set cover position. - example: "cover.living_room" position: - description: Position of the cover (0 to 100). + name: Position + description: Position of the cover + required: true example: 30 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider stop_cover: - description: Stop all or specified cover. - fields: - entity_id: - description: Name(s) of cover(s) to stop. - example: "cover.living_room" + description: Stop all or specified cover + target: open_cover_tilt: - description: Open all or specified cover tilt. - fields: - entity_id: - description: Name(s) of cover(s) tilt to open. - example: "cover.living_room_blinds" + description: Open all or specified cover tilt + target: close_cover_tilt: - description: Close all or specified cover tilt. - fields: - entity_id: - description: Name(s) of cover(s) to close tilt. - example: "cover.living_room_blinds" + description: Close all or specified cover tilt + target: toggle_cover_tilt: - description: Toggles a cover tilt open/closed. - fields: - entity_id: - description: Name(s) of cover(s) to toggle tilt. - example: "cover.living_room_blinds" + description: Toggle a cover tilt open/closed + target: set_cover_tilt_position: - description: Move to specific position all or specified cover tilt. + description: Move to specific position all or specified cover tilt + target: fields: - entity_id: - description: Name(s) of cover(s) to set cover tilt position. - example: "cover.living_room_blinds" tilt_position: - description: Tilt position of the cover (0 to 100). + name: Tilt position + description: Tilt position of the cover. + required: true example: 30 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider stop_cover_tilt: - description: Stop all or specified cover. - fields: - entity_id: - description: Name(s) of cover(s) to stop. - example: "cover.living_room_blinds" + description: Stop all or specified cover + target: From bc1cb8f0a044c3cd9d33ea0e44963e4781375add Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:25:25 +0100 Subject: [PATCH 0548/1818] Add selectors to Automation service definitions (#46629) --- .../components/automation/services.yaml | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/automation/services.yaml b/homeassistant/components/automation/services.yaml index 2f5b0a231e4908..d8380914ce6171 100644 --- a/homeassistant/components/automation/services.yaml +++ b/homeassistant/components/automation/services.yaml @@ -1,37 +1,35 @@ # Describes the format for available automation services turn_on: - description: Enable an automation. - fields: - entity_id: - description: Name of the automation to turn on. - example: "automation.notify_home" + description: Enable an automation + target: turn_off: - description: Disable an automation. + description: Disable an automation + target: fields: - entity_id: - description: Name of the automation to turn off. - example: "automation.notify_home" stop_actions: - description: Stop currently running actions (defaults to true). - example: false + name: Stop actions + description: Stop currently running actions. + default: true + example: true + selector: + boolean: toggle: - description: Toggle an automation. - fields: - entity_id: - description: Name of the automation to toggle on/off. - example: "automation.notify_home" + description: Toggle an automation + target: trigger: - description: Trigger the action of an automation. + description: Trigger the action of an automation + target: fields: - entity_id: - description: Name of the automation to trigger. - example: "automation.notify_home" skip_condition: - description: Whether or not the condition will be skipped (defaults to true). + name: Skip conditions + description: Whether or not the condition will be skipped. + default: true example: true + selector: + boolean: reload: - description: Reload the automation configuration. + description: Reload the automation configuration From ae643bfaf3e6e155fcd29caf0a9ec2773c4aa5b1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:25:55 +0100 Subject: [PATCH 0549/1818] Add selectors to Climate service definitions (#46632) --- .../components/climate/services.yaml | 121 ++++++++++++------ 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index 999640812779d3..ea90cbf30f6ef1 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -1,93 +1,130 @@ # Describes the format for available climate services set_aux_heat: - description: Turn auxiliary heater on/off for climate device. + description: Turn auxiliary heater on/off for climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.kitchen" aux_heat: - description: New value of axillary heater. + name: Auxiliary heating + description: New value of auxiliary heater. + required: true example: true + selector: + boolean: set_preset_mode: - description: Set preset mode for climate device. + description: Set preset mode for climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.kitchen" preset_mode: - description: New value of preset mode + name: Preset mode + description: New value of preset mode. + required: true example: "away" + selector: + text: set_temperature: - description: Set target temperature of climate device. + description: Set target temperature of climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.kitchen" temperature: + name: Temperature description: New target temperature for HVAC. example: 25 + selector: + number: + min: 0 + max: 250 + step: 0.1 + mode: box target_temp_high: + name: Target temperature high description: New target high temperature for HVAC. example: 26 target_temp_low: + name: Target temperature low description: New target low temperature for HVAC. example: 20 hvac_mode: + name: HVAC mode description: HVAC operation mode to set temperature to. example: "heat" + selector: + select: + options: + - "off" + - "auto" + - "cool" + - "dry" + - "fan_only" + - "heat_cool" + - "heat" set_humidity: - description: Set target humidity of climate device. + description: Set target humidity of climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.kitchen" humidity: + name: Humidity description: New target humidity for climate device. + required: true example: 60 + selector: + number: + min: 30 + max: 99 + step: 1 + unit_of_measurement: "%" + mode: slider set_fan_mode: - description: Set fan operation for climate device. + description: Set fan operation for climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.nest" fan_mode: + name: Fan mode description: New value of fan mode. - example: On Low + required: true + example: "low" + selector: + text: set_hvac_mode: - description: Set HVAC operation mode for climate device. + description: Set HVAC operation mode for climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.nest" hvac_mode: + name: HVAC mode description: New value of operation mode. - example: heat + example: "heat" + selector: + select: + options: + - "off" + - "auto" + - "cool" + - "dry" + - "fan_only" + - "heat_cool" + - "heat" set_swing_mode: - description: Set swing operation for climate device. + description: Set swing operation for climate device + target: fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.nest" swing_mode: + name: Swing mode description: New value of swing mode. + required: true + example: "horizontal" + selector: + text: turn_on: - description: Turn climate device on. - fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.kitchen" + description: Turn climate device on + target: turn_off: - description: Turn climate device off. - fields: - entity_id: - description: Name(s) of entities to change. - example: "climate.kitchen" + description: Turn climate device off + target: From 399777cfa8ad9c157bff7d5c555b981b70a1a2fe Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:26:05 +0100 Subject: [PATCH 0550/1818] Add selectors to Alarm Control Panel service definitions (#46626) --- .../alarm_control_panel/services.yaml | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/services.yaml b/homeassistant/components/alarm_control_panel/services.yaml index ee1e8c1fcf6993..a6151c58db03d2 100644 --- a/homeassistant/components/alarm_control_panel/services.yaml +++ b/homeassistant/components/alarm_control_panel/services.yaml @@ -1,61 +1,68 @@ # Describes the format for available alarm control panel services alarm_disarm: - description: Send the alarm the command for disarm. + description: Send the alarm the command for disarm + target: fields: - entity_id: - description: Name of alarm control panel to disarm. - example: "alarm_control_panel.downstairs" code: + name: Code description: An optional code to disarm the alarm control panel with. example: "1234" + selector: + text: alarm_arm_custom_bypass: - description: Send arm custom bypass command. + description: Send arm custom bypass command + target: fields: - entity_id: - description: Name of alarm control panel to arm custom bypass. - example: "alarm_control_panel.downstairs" code: - description: An optional code to arm custom bypass the alarm control panel with. + name: Code + description: + An optional code to arm custom bypass the alarm control panel with. example: "1234" + selector: + text: alarm_arm_home: - description: Send the alarm the command for arm home. + description: Send the alarm the command for arm home + target: fields: - entity_id: - description: Name of alarm control panel to arm home. - example: "alarm_control_panel.downstairs" code: + name: Code description: An optional code to arm home the alarm control panel with. example: "1234" + selector: + text: alarm_arm_away: - description: Send the alarm the command for arm away. + description: Send the alarm the command for arm away + target: fields: - entity_id: - description: Name of alarm control panel to arm away. - example: "alarm_control_panel.downstairs" code: + name: Code description: An optional code to arm away the alarm control panel with. example: "1234" + selector: + text: alarm_arm_night: - description: Send the alarm the command for arm night. + description: Send the alarm the command for arm night + target: fields: - entity_id: - description: Name of alarm control panel to arm night. - example: "alarm_control_panel.downstairs" code: + name: Code description: An optional code to arm night the alarm control panel with. example: "1234" + selector: + text: alarm_trigger: - description: Send the alarm the command for trigger. + description: Send the alarm the command for trigger + target: fields: - entity_id: - description: Name of alarm control panel to trigger. - example: "alarm_control_panel.downstairs" code: + name: Code description: An optional code to trigger the alarm control panel with. example: "1234" + selector: + text: From 0181cbb3127475588c060a0dbfea50243cf92bc8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:31:07 +0100 Subject: [PATCH 0551/1818] Upgrade and constrain httplib2>=0.19.0 (#46725) --- homeassistant/components/google/manifest.json | 2 +- homeassistant/components/remember_the_milk/manifest.json | 2 +- homeassistant/package_constraints.txt | 5 +++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/gen_requirements_all.py | 5 +++-- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 6df116effa5fdf..859f1b332960b4 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/google", "requirements": [ "google-api-python-client==1.6.4", - "httplib2==0.18.1", + "httplib2==0.19.0", "oauth2client==4.0.0" ], "codeowners": [] diff --git a/homeassistant/components/remember_the_milk/manifest.json b/homeassistant/components/remember_the_milk/manifest.json index f03f88023ae335..8ce8cb98e5bfaa 100644 --- a/homeassistant/components/remember_the_milk/manifest.json +++ b/homeassistant/components/remember_the_milk/manifest.json @@ -2,7 +2,7 @@ "domain": "remember_the_milk", "name": "Remember The Milk", "documentation": "https://www.home-assistant.io/integrations/remember_the_milk", - "requirements": ["RtmAPI==0.7.2", "httplib2==0.18.1"], + "requirements": ["RtmAPI==0.7.2", "httplib2==0.19.0"], "dependencies": ["configurator"], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1a1163c7a8829c..7db311b56d5bc5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -46,8 +46,9 @@ h11>=0.12.0 # https://github.com/encode/httpcore/issues/239 httpcore>=0.12.3 -# Constrain httplib2 to protect against CVE-2020-11078 -httplib2>=0.18.0 +# Constrain httplib2 to protect against GHSA-93xj-8mrv-444m +# https://github.com/advisories/GHSA-93xj-8mrv-444m +httplib2>=0.19.0 # gRPC 1.32+ currently causes issues on ARMv7, see: # https://github.com/home-assistant/core/issues/40148 diff --git a/requirements_all.txt b/requirements_all.txt index 0f96ab51235d96..fdddefc44a6c41 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -779,7 +779,7 @@ horimote==0.4.1 # homeassistant.components.google # homeassistant.components.remember_the_milk -httplib2==0.18.1 +httplib2==0.19.0 # homeassistant.components.huawei_lte huawei-lte-api==1.4.17 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 148b66e9ac1303..cf1b25c93506f4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -425,7 +425,7 @@ homematicip==0.13.1 # homeassistant.components.google # homeassistant.components.remember_the_milk -httplib2==0.18.1 +httplib2==0.19.0 # homeassistant.components.huawei_lte huawei-lte-api==1.4.17 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 52820bfa5726d3..7dd4924dac81c2 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -71,8 +71,9 @@ # https://github.com/encode/httpcore/issues/239 httpcore>=0.12.3 -# Constrain httplib2 to protect against CVE-2020-11078 -httplib2>=0.18.0 +# Constrain httplib2 to protect against GHSA-93xj-8mrv-444m +# https://github.com/advisories/GHSA-93xj-8mrv-444m +httplib2>=0.19.0 # gRPC 1.32+ currently causes issues on ARMv7, see: # https://github.com/home-assistant/core/issues/40148 From 4083b90138386dace66a38ea2363bdf16daf9731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Thu, 18 Feb 2021 12:33:21 +0100 Subject: [PATCH 0552/1818] ubus: switch to pypi library (#46690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Fernández Rojas --- CODEOWNERS | 1 + .../components/ubus/device_tracker.py | 89 +++---------------- homeassistant/components/ubus/manifest.json | 3 +- requirements_all.txt | 3 + 4 files changed, 19 insertions(+), 77 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 81f43da58e7943..75faf90eb75fe9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -491,6 +491,7 @@ homeassistant/components/tts/* @pvizeli homeassistant/components/tuya/* @ollo69 homeassistant/components/twentemilieu/* @frenck homeassistant/components/twinkly/* @dr1rrb +homeassistant/components/ubus/* @noltari homeassistant/components/unifi/* @Kane610 homeassistant/components/unifiled/* @florisvdk homeassistant/components/upb/* @gwww diff --git a/homeassistant/components/ubus/device_tracker.py b/homeassistant/components/ubus/device_tracker.py index 4cefefc2f96b79..12c986d57bbba3 100644 --- a/homeassistant/components/ubus/device_tracker.py +++ b/homeassistant/components/ubus/device_tracker.py @@ -1,9 +1,9 @@ """Support for OpenWRT (ubus) routers.""" -import json + import logging import re -import requests +from openwrt.ubus import Ubus import voluptuous as vol from homeassistant.components.device_tracker import ( @@ -11,8 +11,7 @@ PLATFORM_SCHEMA, DeviceScanner, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, HTTP_OK -from homeassistant.exceptions import HomeAssistantError +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ def decorator(self, *args, **kwargs): "Invalid session detected." " Trying to refresh session_id and re-run RPC" ) - self.session_id = _get_session_id(self.url, self.username, self.password) + self.ubus.connect() return func(self, *args, **kwargs) @@ -82,10 +81,10 @@ def __init__(self, config): self.last_results = {} self.url = f"http://{host}/ubus" - self.session_id = _get_session_id(self.url, self.username, self.password) + self.ubus = Ubus(self.url, self.username, self.password) self.hostapd = [] self.mac2name = None - self.success_init = self.session_id is not None + self.success_init = self.ubus.connect() is not None def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -119,16 +118,14 @@ def _update_info(self): _LOGGER.info("Checking hostapd") if not self.hostapd: - hostapd = _req_json_rpc(self.url, self.session_id, "list", "hostapd.*", "") + hostapd = self.ubus.get_hostapd() self.hostapd.extend(hostapd.keys()) self.last_results = [] results = 0 # for each access point for hostapd in self.hostapd: - result = _req_json_rpc( - self.url, self.session_id, "call", hostapd, "get_clients" - ) + result = self.ubus.get_hostapd_clients(hostapd) if result: results = results + 1 @@ -151,31 +148,21 @@ def __init__(self, config): def _generate_mac2name(self): if self.leasefile is None: - result = _req_json_rpc( - self.url, - self.session_id, - "call", - "uci", - "get", - config="dhcp", - type="dnsmasq", - ) + result = self.ubus.get_uci_config("dhcp", "dnsmasq") if result: values = result["values"].values() self.leasefile = next(iter(values))["leasefile"] else: return - result = _req_json_rpc( - self.url, self.session_id, "call", "file", "read", path=self.leasefile - ) + result = self.ubus.file_read(self.leasefile) if result: self.mac2name = {} for line in result["data"].splitlines(): hosts = line.split(" ") self.mac2name[hosts[1].upper()] = hosts[3] else: - # Error, handled in the _req_json_rpc + # Error, handled in the ubus.file_read() return @@ -183,7 +170,7 @@ class OdhcpdUbusDeviceScanner(UbusDeviceScanner): """Implement the Ubus device scanning for the odhcp DHCP server.""" def _generate_mac2name(self): - result = _req_json_rpc(self.url, self.session_id, "call", "dhcp", "ipv4leases") + result = self.ubus.get_dhcp_method("ipv4leases") if result: self.mac2name = {} for device in result["device"].values(): @@ -193,55 +180,5 @@ def _generate_mac2name(self): mac = ":".join(mac[i : i + 2] for i in range(0, len(mac), 2)) self.mac2name[mac.upper()] = lease["hostname"] else: - # Error, handled in the _req_json_rpc + # Error, handled in the ubus.get_dhcp_method() return - - -def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params): - """Perform one JSON RPC operation.""" - data = json.dumps( - { - "jsonrpc": "2.0", - "id": 1, - "method": rpcmethod, - "params": [session_id, subsystem, method, params], - } - ) - - try: - res = requests.post(url, data=data, timeout=5) - - except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): - return - - if res.status_code == HTTP_OK: - response = res.json() - if "error" in response: - if ( - "message" in response["error"] - and response["error"]["message"] == "Access denied" - ): - raise PermissionError(response["error"]["message"]) - raise HomeAssistantError(response["error"]["message"]) - - if rpcmethod == "call": - try: - return response["result"][1] - except IndexError: - return - else: - return response["result"] - - -def _get_session_id(url, username, password): - """Get the authentication token for the given host+username+password.""" - res = _req_json_rpc( - url, - "00000000000000000000000000000000", - "call", - "session", - "login", - username=username, - password=password, - ) - return res["ubus_rpc_session"] diff --git a/homeassistant/components/ubus/manifest.json b/homeassistant/components/ubus/manifest.json index af7fb50b6c49ea..68452f98f7d720 100644 --- a/homeassistant/components/ubus/manifest.json +++ b/homeassistant/components/ubus/manifest.json @@ -2,5 +2,6 @@ "domain": "ubus", "name": "OpenWrt (ubus)", "documentation": "https://www.home-assistant.io/integrations/ubus", - "codeowners": [] + "requirements": ["openwrt-ubus-rpc==0.0.2"], + "codeowners": ["@noltari"] } diff --git a/requirements_all.txt b/requirements_all.txt index fdddefc44a6c41..815484aa70007d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1070,6 +1070,9 @@ openwebifpy==3.2.7 # homeassistant.components.luci openwrt-luci-rpc==1.1.6 +# homeassistant.components.ubus +openwrt-ubus-rpc==0.0.2 + # homeassistant.components.oru oru==0.1.11 From 82934b31f8219f1b44ffdb7fe65dc95b8a0ac940 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:59:29 +0100 Subject: [PATCH 0553/1818] Add selectors to Counter service definitions (#46633) --- .../components/counter/services.yaml | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/counter/services.yaml b/homeassistant/components/counter/services.yaml index 960424df0ca2c5..16010f6e2f41d5 100644 --- a/homeassistant/components/counter/services.yaml +++ b/homeassistant/components/counter/services.yaml @@ -1,41 +1,63 @@ # Describes the format for available counter services decrement: - description: Decrement a counter. - fields: - entity_id: - description: Entity id of the counter to decrement. - example: "counter.count0" + description: Decrement a counter + target: + increment: - description: Increment a counter. - fields: - entity_id: - description: Entity id of the counter to increment. - example: "counter.count0" + description: Increment a counter + target: + reset: - description: Reset a counter. - fields: - entity_id: - description: Entity id of the counter to reset. - example: "counter.count0" + description: Reset a counter + target: + configure: description: Change counter parameters + target: fields: - entity_id: - description: Entity id of the counter to change. - example: "counter.count0" minimum: - description: New minimum value for the counter or None to remove minimum + name: Minimum + description: New minimum value for the counter or None to remove minimum. example: 0 + selector: + number: + min: -9223372036854775807 + max: 9223372036854775807 + mode: box maximum: - description: New maximum value for the counter or None to remove maximum + name: Maximum + description: New maximum value for the counter or None to remove maximum. example: 100 + selector: + number: + min: -9223372036854775807 + max: 9223372036854775807 + mode: box step: - description: New value for step + name: Step + description: New value for step. example: 2 + selector: + number: + min: 1 + max: 9223372036854775807 + mode: box initial: - description: New value for initial + name: Initial + description: New value for initial. example: 6 + selector: + number: + min: 0 + max: 9223372036854775807 + mode: box value: - description: New state value + name: Value + description: New state value. example: 3 + selector: + number: + min: 0 + max: 9223372036854775807 + mode: box From 62cfe24ed431365d837821788e8405d68fed1468 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 12:59:46 +0100 Subject: [PATCH 0554/1818] Add advanced service parameter flag (#46727) --- homeassistant/components/light/services.yaml | 37 ++++++++++++++++---- script/hassfest/services.py | 1 + 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index dda7396e11bbb0..202899db7bfe9e 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -22,7 +22,8 @@ turn_on: description: A human readable color name. example: "red" hs_color: - description: Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100. + description: + Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100. example: "[300, 70]" xy_color: description: Color for the light in XY-format. @@ -37,11 +38,25 @@ turn_on: description: Number between 0..255 indicating level of white. example: "250" brightness: - description: Number between 0..255 indicating brightness, where 0 turns the light off, 1 is the minimum brightness and 255 is the maximum brightness supported by the light. + name: Brightness value + description: + Number between 0..255 indicating brightness, where 0 turns the light + off, 1 is the minimum brightness and 255 is the maximum brightness + supported by the light. + advanced: true example: 120 + selector: + number: + min: 0 + max: 255 + step: 1 + mode: slider brightness_pct: name: Brightness - description: Number between 0..100 indicating percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness and 100 is the maximum brightness supported by the light. + description: + Number between 0..100 indicating percentage of full brightness, where 0 + turns the light off, 1 is the minimum brightness and 100 is the maximum + brightness supported by the light. example: 47 selector: number: @@ -54,7 +69,8 @@ turn_on: description: Change brightness by an amount. Should be between -255..255. example: -25.5 brightness_step_pct: - description: Change brightness by a percentage. Should be between -100..100. + description: + Change brightness by a percentage. Should be between -100..100. example: -10 profile: description: Name of a light profile to use. @@ -104,7 +120,8 @@ toggle: description: A human readable color name. example: "red" hs_color: - description: Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100. + description: + Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100. example: "[300, 70]" xy_color: description: Color for the light in XY-format. @@ -119,10 +136,16 @@ toggle: description: Number between 0..255 indicating level of white. example: "250" brightness: - description: Number between 0..255 indicating brightness, where 0 turns the light off, 1 is the minimum brightness and 255 is the maximum brightness supported by the light. + description: + Number between 0..255 indicating brightness, where 0 turns the light + off, 1 is the minimum brightness and 255 is the maximum brightness + supported by the light. example: 120 brightness_pct: - description: Number between 0..100 indicating percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness and 100 is the maximum brightness supported by the light. + description: + Number between 0..100 indicating percentage of full brightness, where 0 + turns the light off, 1 is the minimum brightness and 100 is the maximum + brightness supported by the light. example: 47 profile: description: Name of a light profile to use. diff --git a/script/hassfest/services.py b/script/hassfest/services.py index c0823b672e82a5..9037b0bb45e55e 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -29,6 +29,7 @@ def exists(value): vol.Optional("default"): exists, vol.Optional("values"): exists, vol.Optional("required"): bool, + vol.Optional("advanced"): bool, vol.Optional(CONF_SELECTOR): selector.validate_selector, } ) From 88d143a644fc606cdea5ab512cf4396dd223bff2 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 18 Feb 2021 04:26:02 -0800 Subject: [PATCH 0555/1818] Add discontinuity support to HLS streams and fix nest expiring stream urls (#46683) * Support HLS stream discontinuity. * Clarify discontinuity comments * Signal a stream discontinuity on restart due to stream error * Apply suggestions from code review Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> * Simplify stream discontinuity logic --- homeassistant/components/stream/__init__.py | 8 +- homeassistant/components/stream/core.py | 2 + homeassistant/components/stream/hls.py | 14 ++- homeassistant/components/stream/worker.py | 28 ++++-- tests/components/stream/test_hls.py | 101 +++++++++++++++++--- tests/components/stream/test_worker.py | 8 +- 6 files changed, 132 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 7f88885ac0be1e..6c3f0104ad0ead 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -170,7 +170,7 @@ def start(self): def update_source(self, new_source): """Restart the stream with a new stream source.""" - _LOGGER.debug("Updating stream source %s", self.source) + _LOGGER.debug("Updating stream source %s", new_source) self.source = new_source self._fast_restart_once = True self._thread_quit.set() @@ -179,12 +179,14 @@ def _run_worker(self): """Handle consuming streams and restart keepalive streams.""" # Keep import here so that we can import stream integration without installing reqs # pylint: disable=import-outside-toplevel - from .worker import stream_worker + from .worker import SegmentBuffer, stream_worker + segment_buffer = SegmentBuffer(self.outputs) wait_timeout = 0 while not self._thread_quit.wait(timeout=wait_timeout): start_time = time.time() - stream_worker(self.source, self.options, self.outputs, self._thread_quit) + stream_worker(self.source, self.options, segment_buffer, self._thread_quit) + segment_buffer.discontinuity() if not self.keepalive or self._thread_quit.is_set(): if self._fast_restart_once: # The stream source is updated, restart without any delay. diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index f7beb3aa75423d..7a46de547d7748 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -30,6 +30,8 @@ class Segment: sequence: int = attr.ib() segment: io.BytesIO = attr.ib() duration: float = attr.ib() + # For detecting discontinuities across stream restarts + stream_id: int = attr.ib(default=0) class IdleTimer: diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 57894d177113be..85102d208e72b7 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -78,21 +78,27 @@ def render_preamble(track): @staticmethod def render_playlist(track): """Render playlist.""" - segments = track.segments[-NUM_PLAYLIST_SEGMENTS:] + segments = list(track.get_segment())[-NUM_PLAYLIST_SEGMENTS:] if not segments: return [] - playlist = ["#EXT-X-MEDIA-SEQUENCE:{}".format(segments[0])] + playlist = [ + "#EXT-X-MEDIA-SEQUENCE:{}".format(segments[0].sequence), + "#EXT-X-DISCONTINUITY-SEQUENCE:{}".format(segments[0].stream_id), + ] - for sequence in segments: - segment = track.get_segment(sequence) + last_stream_id = segments[0].stream_id + for segment in segments: + if last_stream_id != segment.stream_id: + playlist.append("#EXT-X-DISCONTINUITY") playlist.extend( [ "#EXTINF:{:.04f},".format(float(segment.duration)), f"./segment/{segment.sequence}.m4s", ] ) + last_stream_id = segment.stream_id return playlist diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 41cb4bafd90a45..2592a74584e45b 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -49,16 +49,22 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): class SegmentBuffer: """Buffer for writing a sequence of packets to the output as a segment.""" - def __init__(self, video_stream, audio_stream, outputs_callback) -> None: + def __init__(self, outputs_callback) -> None: """Initialize SegmentBuffer.""" - self._video_stream = video_stream - self._audio_stream = audio_stream + self._stream_id = 0 + self._video_stream = None + self._audio_stream = None self._outputs_callback = outputs_callback # tuple of StreamOutput, StreamBuffer self._outputs = [] self._sequence = 0 self._segment_start_pts = None + def set_streams(self, video_stream, audio_stream): + """Initialize output buffer with streams from container.""" + self._video_stream = video_stream + self._audio_stream = audio_stream + def reset(self, video_pts): """Initialize a new stream segment.""" # Keep track of the number of segments we've processed @@ -103,7 +109,16 @@ def flush(self, duration): """Create a segment from the buffered packets and write to output.""" for (buffer, stream_output) in self._outputs: buffer.output.close() - stream_output.put(Segment(self._sequence, buffer.segment, duration)) + stream_output.put( + Segment(self._sequence, buffer.segment, duration, self._stream_id) + ) + + def discontinuity(self): + """Mark the stream as having been restarted.""" + # Preserving sequence and stream_id here keep the HLS playlist logic + # simple to check for discontinuity at output time, and to determine + # the discontinuity sequence number. + self._stream_id += 1 def close(self): """Close all StreamBuffers.""" @@ -111,7 +126,7 @@ def close(self): buffer.output.close() -def stream_worker(source, options, outputs_callback, quit_event): +def stream_worker(source, options, segment_buffer, quit_event): """Handle consuming streams.""" try: @@ -143,8 +158,6 @@ def stream_worker(source, options, outputs_callback, quit_event): last_dts = {video_stream: float("-inf"), audio_stream: float("-inf")} # Keep track of consecutive packets without a dts to detect end of stream. missing_dts = 0 - # Holds the buffers for each stream provider - segment_buffer = SegmentBuffer(video_stream, audio_stream, outputs_callback) # The video pts at the beginning of the segment segment_start_pts = None # Because of problems 1 and 2 below, we need to store the first few packets and replay them @@ -225,6 +238,7 @@ def peek_first_pts(): container.close() return + segment_buffer.set_streams(video_stream, audio_stream) segment_buffer.reset(segment_start_pts) while not quit_event.is_set(): diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 55b79684b7bba3..ffe32d13c61153 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -51,7 +51,16 @@ async def create_client_for_stream(stream): return create_client_for_stream -def playlist_response(sequence, segments): +def make_segment(segment, discontinuity=False): + """Create a playlist response for a segment.""" + response = [] + if discontinuity: + response.append("#EXT-X-DISCONTINUITY") + response.extend(["#EXTINF:10.0000,", f"./segment/{segment}.m4s"]), + return "\n".join(response) + + +def make_playlist(sequence, discontinuity_sequence=0, segments=[]): """Create a an hls playlist response for tests to assert on.""" response = [ "#EXTM3U", @@ -59,14 +68,9 @@ def playlist_response(sequence, segments): "#EXT-X-TARGETDURATION:10", '#EXT-X-MAP:URI="init.mp4"', f"#EXT-X-MEDIA-SEQUENCE:{sequence}", + f"#EXT-X-DISCONTINUITY-SEQUENCE:{discontinuity_sequence}", ] - for segment in segments: - response.extend( - [ - "#EXTINF:10.0000,", - f"./segment/{segment}.m4s", - ] - ) + response.extend(segments) response.append("") return "\n".join(response) @@ -289,13 +293,15 @@ async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync): resp = await hls_client.get("/playlist.m3u8") assert resp.status == 200 - assert await resp.text() == playlist_response(sequence=1, segments=[1]) + assert await resp.text() == make_playlist(sequence=1, segments=[make_segment(1)]) hls.put(Segment(2, SEQUENCE_BYTES, DURATION)) await hass.async_block_till_done() resp = await hls_client.get("/playlist.m3u8") assert resp.status == 200 - assert await resp.text() == playlist_response(sequence=1, segments=[1, 2]) + assert await resp.text() == make_playlist( + sequence=1, segments=[make_segment(1), make_segment(2)] + ) stream_worker_sync.resume() stream.stop() @@ -321,8 +327,12 @@ async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist. start = MAX_SEGMENTS + 2 - NUM_PLAYLIST_SEGMENTS - assert await resp.text() == playlist_response( - sequence=start, segments=range(start, MAX_SEGMENTS + 2) + segments = [] + for sequence in range(start, MAX_SEGMENTS + 2): + segments.append(make_segment(sequence)) + assert await resp.text() == make_playlist( + sequence=start, + segments=segments, ) # Fetch the actual segments with a fake byte payload @@ -340,3 +350,70 @@ async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): stream_worker_sync.resume() stream.stop() + + +async def test_hls_playlist_view_discontinuity(hass, hls_stream, stream_worker_sync): + """Test a discontinuity across segments in the stream with 3 segments.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream = create_stream(hass, STREAM_SOURCE) + stream_worker_sync.pause() + hls = stream.hls_output() + + hls.put(Segment(1, SEQUENCE_BYTES, DURATION, stream_id=0)) + hls.put(Segment(2, SEQUENCE_BYTES, DURATION, stream_id=0)) + hls.put(Segment(3, SEQUENCE_BYTES, DURATION, stream_id=1)) + await hass.async_block_till_done() + + hls_client = await hls_stream(stream) + + resp = await hls_client.get("/playlist.m3u8") + assert resp.status == 200 + assert await resp.text() == make_playlist( + sequence=1, + segments=[ + make_segment(1), + make_segment(2), + make_segment(3, discontinuity=True), + ], + ) + + stream_worker_sync.resume() + stream.stop() + + +async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sync): + """Test a discontinuity with more segments than the segment deque can hold.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream = create_stream(hass, STREAM_SOURCE) + stream_worker_sync.pause() + hls = stream.hls_output() + + hls_client = await hls_stream(stream) + + hls.put(Segment(1, SEQUENCE_BYTES, DURATION, stream_id=0)) + + # Produce enough segments to overfill the output buffer by one + for sequence in range(1, MAX_SEGMENTS + 2): + hls.put(Segment(sequence, SEQUENCE_BYTES, DURATION, stream_id=1)) + await hass.async_block_till_done() + + resp = await hls_client.get("/playlist.m3u8") + assert resp.status == 200 + + # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist causing the + # EXT-X-DISCONTINUITY tag to be omitted and EXT-X-DISCONTINUITY-SEQUENCE + # returned instead. + start = MAX_SEGMENTS + 2 - NUM_PLAYLIST_SEGMENTS + segments = [] + for sequence in range(start, MAX_SEGMENTS + 2): + segments.append(make_segment(sequence)) + assert await resp.text() == make_playlist( + sequence=start, + discontinuity_sequence=1, + segments=segments, + ) + + stream_worker_sync.resume() + stream.stop() diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index d9006c81ad5624..f7952b7db4479e 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -27,7 +27,7 @@ MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO, ) -from homeassistant.components.stream.worker import stream_worker +from homeassistant.components.stream.worker import SegmentBuffer, stream_worker STREAM_SOURCE = "some-stream-source" # Formats here are arbitrary, not exercised by tests @@ -197,7 +197,8 @@ async def async_decode_stream(hass, packets, py_av=None): "homeassistant.components.stream.core.StreamOutput.put", side_effect=py_av.capture_buffer.capture_output_segment, ): - stream_worker(STREAM_SOURCE, {}, stream.outputs, threading.Event()) + segment_buffer = SegmentBuffer(stream.outputs) + stream_worker(STREAM_SOURCE, {}, segment_buffer, threading.Event()) await hass.async_block_till_done() return py_av.capture_buffer @@ -209,7 +210,8 @@ async def test_stream_open_fails(hass): stream.hls_output() with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") - stream_worker(STREAM_SOURCE, {}, stream.outputs, threading.Event()) + segment_buffer = SegmentBuffer(stream.outputs) + stream_worker(STREAM_SOURCE, {}, segment_buffer, threading.Event()) await hass.async_block_till_done() av_open.assert_called_once() From 616e8f6a65f88e60093c6c48bc27381d6adaa7f7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 13:56:15 +0100 Subject: [PATCH 0556/1818] Add selectors to Scene service definitions (#46729) --- homeassistant/components/scene/services.yaml | 52 +++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/scene/services.yaml b/homeassistant/components/scene/services.yaml index 29fa11e9367197..dc9e9e07ab4cd3 100644 --- a/homeassistant/components/scene/services.yaml +++ b/homeassistant/components/scene/services.yaml @@ -1,53 +1,79 @@ # Describes the format for available scene services turn_on: - description: Activate a scene. + description: Activate a scene + target: fields: transition: + name: Transition description: Transition duration in seconds it takes to bring devices to the state defined in the scene. example: 2.5 - entity_id: - description: Name(s) of scenes to turn on - example: "scene.romantic" + selector: + number: + min: 0 + max: 300 + step: 1 + unit_of_measurement: seconds + mode: slider reload: description: Reload the scene configuration apply: - description: - Activate a scene. Takes same data as the entities field from a single scene - in the config. + description: Activate a scene with configuration fields: - transition: - description: - Transition duration in seconds it takes to bring devices to the state - defined in the scene. - example: 2.5 entities: + name: Entities state description: The entities and the state that they need to be. + required: true example: light.kitchen: "on" light.ceiling: state: "on" brightness: 80 + selector: + object: + transition: + name: Transition + description: + Transition duration in seconds it takes to bring devices to the state + defined in the scene. + example: 2.5 + selector: + number: + min: 0 + max: 300 + step: 1 + unit_of_measurement: seconds + mode: slider create: - description: Creates a new scene. + description: Creates a new scene fields: scene_id: + name: Scene entity ID description: The entity_id of the new scene. + required: true example: all_lights + selector: + text: entities: + name: Entities state description: The entities to control with the scene. example: light.tv_back_light: "on" light.ceiling: state: "on" brightness: 200 + selector: + object: snapshot_entities: + name: Snapshot entities description: The entities of which a snapshot is to be taken example: - light.ceiling - light.kitchen + selector: + object: From 12477c5e4686d5d17d12fe65ca3013da9e084a61 Mon Sep 17 00:00:00 2001 From: Jesse Campbell Date: Thu, 18 Feb 2021 07:58:43 -0500 Subject: [PATCH 0557/1818] Fix missing color switch specific device class for Z-Wave JS driver >6.3 (#46718) --- homeassistant/components/zwave_js/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 2022258e6ea0e6..7231d18e1868e3 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -242,6 +242,7 @@ class ZWaveDiscoverySchema: device_class_specific={ "Tunable Color Light", "Binary Tunable Color Light", + "Tunable Color Switch", "Multilevel Remote Switch", "Multilevel Power Switch", "Multilevel Scene Switch", From bfa171f80291dcfa9345cda5356258dfa4b0f88a Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 18 Feb 2021 15:01:54 +0100 Subject: [PATCH 0558/1818] Add selectors to Netatmo services (#46574) * Add selectors * Fix schedul selector * Update homeassistant/components/netatmo/services.yaml Co-authored-by: Bram Kragten * Update homeassistant/components/netatmo/services.yaml Co-authored-by: Bram Kragten * Update services.yaml * Added required field Co-authored-by: Bram Kragten --- .../components/netatmo/services.yaml | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/netatmo/services.yaml b/homeassistant/components/netatmo/services.yaml index 459ef23b0e0dbe..11f83830dff881 100644 --- a/homeassistant/components/netatmo/services.yaml +++ b/homeassistant/components/netatmo/services.yaml @@ -1,43 +1,63 @@ # Describes the format for available Netatmo services set_camera_light: - description: Set the camera light mode. + description: Set the camera light mode + target: + entity: + integration: netatmo + domain: light fields: camera_light_mode: + name: Camera light mode description: Outdoor camera light mode (on/off/auto) example: auto - entity_id: - description: Entity id of the camera. - example: camera.netatmo_entrance + required: true + selector: + select: + options: + - "on" + - "off" + - "auto" set_schedule: - description: Set the heating schedule. + description: Set the heating schedule + target: + entity: + integration: netatmo + domain: climate fields: schedule_name: description: Schedule name example: Standard - entity_id: - description: Entity id of the climate device. - example: climate.netatmo_livingroom + required: true + selector: + text: set_persons_home: - description: Set a list of persons as at home. Person's name must match a name known by the Welcome Camera. + description: Set a list of persons as at home. Person's name must match a name known by the Welcome Camera + target: + entity: + integration: netatmo + domain: camera fields: persons: description: List of names example: Bob - entity_id: - description: Entity id of the camera. - example: camera.netatmo_entrance + required: true + selector: + text: set_person_away: - description: Set a person away. If no person is set the home will be marked as empty. Person's name must match a name known by the Welcome Camera. + description: Set a person away. If no person is set the home will be marked as empty. Person's name must match a name known by the Welcome Camera + target: + entity: + integration: netatmo + domain: camera fields: person: description: Person's name (optional) example: Bob - entity_id: - description: Entity id of the camera. - example: camera.netatmo_entrance + selector: + text: register_webhook: description: Register webhook From 74720d4afd1b7affda1f040fbcb91eec3e4fd5c4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:13:52 +0100 Subject: [PATCH 0559/1818] Add selectors to Vacuum service definitions (#46728) --- homeassistant/components/vacuum/services.yaml | 91 ++++++++----------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/vacuum/services.yaml b/homeassistant/components/vacuum/services.yaml index 3287eafe7f2852..a60ce9ee658469 100644 --- a/homeassistant/components/vacuum/services.yaml +++ b/homeassistant/components/vacuum/services.yaml @@ -1,87 +1,70 @@ # Describes the format for available vacuum services turn_on: - description: Start a new cleaning task. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Start a new cleaning task + target: turn_off: - description: Stop the current cleaning task and return to home. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Stop the current cleaning task and return to home + target: stop: - description: Stop the current cleaning task. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Stop the current cleaning task + target: locate: - description: Locate the vacuum cleaner robot. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Locate the vacuum cleaner robot + target: start_pause: - description: Start, pause, or resume the cleaning task. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Start, pause, or resume the cleaning task + target: start: - description: Start or resume the cleaning task. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Start or resume the cleaning task + target: pause: - description: Pause the cleaning task. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Pause the cleaning task + target: return_to_base: - description: Tell the vacuum cleaner to return to its dock. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Tell the vacuum cleaner to return to its dock + target: clean_spot: - description: Tell the vacuum cleaner to do a spot clean-up. - fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" + description: Tell the vacuum cleaner to do a spot clean-up + target: send_command: - description: Send a raw command to the vacuum cleaner. + description: Send a raw command to the vacuum cleaner + target: fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" command: + name: Command description: Command to execute. + required: true example: "set_dnd_timer" + selector: + text: + params: + name: Parameters description: Parameters for the command. example: '{ "key": "value" }' + selector: + object: set_fan_speed: - description: Set the fan speed of the vacuum cleaner. + description: Set the fan speed of the vacuum cleaner + target: fields: - entity_id: - description: Name of the vacuum entity. - example: "vacuum.xiaomi_vacuum_cleaner" fan_speed: - description: Platform dependent vacuum cleaner fan speed, with speed steps, like 'medium' or by percentage, between 0 and 100. + name: Fan speed + description: + Platform dependent vacuum cleaner fan speed, with speed steps, like + 'medium' or by percentage, between 0 and 100. + required: true example: "low" + selector: + text: From 1d62bf88750ca1d2513d894c70f876d99f376ac0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:14:16 +0100 Subject: [PATCH 0560/1818] Add selectors to Script service definitions (#46730) --- homeassistant/components/script/services.yaml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/script/services.yaml b/homeassistant/components/script/services.yaml index 1347f760b546cf..5af81734a9e2f2 100644 --- a/homeassistant/components/script/services.yaml +++ b/homeassistant/components/script/services.yaml @@ -5,21 +5,12 @@ reload: turn_on: description: Turn on script - fields: - entity_id: - description: Name(s) of script to be turned on. - example: "script.arrive_home" + target: turn_off: description: Turn off script - fields: - entity_id: - description: Name(s) of script to be turned off. - example: "script.arrive_home" + target: toggle: description: Toggle script - fields: - entity_id: - description: Name(s) of script to be toggled. - example: "script.arrive_home" + target: From c0cdc0fe795640904f4e704c95e48bf71a52eb7c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:15:16 +0100 Subject: [PATCH 0561/1818] Add advanced selectors to Light service definitions (#46732) --- homeassistant/components/light/services.yaml | 483 ++++++++++++++++++- 1 file changed, 472 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index 202899db7bfe9e..f777cd3d3488b4 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -1,12 +1,12 @@ # Describes the format for available light services turn_on: - description: Turn a light on. + description: Turn a light on target: fields: transition: name: Transition - description: Duration in seconds it takes to get to next state + description: Duration in seconds it takes to get to next state. example: 60 selector: number: @@ -16,27 +16,218 @@ turn_on: unit_of_measurement: seconds mode: slider rgb_color: + name: RGB-color description: Color for the light in RGB-format. + advanced: true example: "[255, 100, 100]" + selector: + object: color_name: + name: Color name description: A human readable color name. + advanced: true example: "red" + selector: + select: + options: + - "aliceblue" + - "antiquewhite" + - "aqua" + - "aquamarine" + - "azure" + - "beige" + - "bisque" + - "black" + - "blanchedalmond" + - "blue" + - "blueviolet" + - "brown" + - "burlywood" + - "cadetblue" + - "chartreuse" + - "chocolate" + - "coral" + - "cornflowerblue" + - "cornsilk" + - "crimson" + - "cyan" + - "darkblue" + - "darkcyan" + - "darkgoldenrod" + - "darkgray" + - "darkgreen" + - "darkgrey" + - "darkkhaki" + - "darkmagenta" + - "darkolivegreen" + - "darkorange" + - "darkorchid" + - "darkred" + - "darksalmon" + - "darkseagreen" + - "darkslateblue" + - "darkslategray" + - "darkslategrey" + - "darkturquoise" + - "darkviolet" + - "deeppink" + - "deepskyblue" + - "dimgray" + - "dimgrey" + - "dodgerblue" + - "firebrick" + - "floralwhite" + - "forestgreen" + - "fuchsia" + - "gainsboro" + - "ghostwhite" + - "gold" + - "goldenrod" + - "gray" + - "green" + - "greenyellow" + - "grey" + - "honeydew" + - "hotpink" + - "indianred" + - "indigo" + - "ivory" + - "khaki" + - "lavender" + - "lavenderblush" + - "lawngreen" + - "lemonchiffon" + - "lightblue" + - "lightcoral" + - "lightcyan" + - "lightgoldenrodyellow" + - "lightgray" + - "lightgreen" + - "lightgrey" + - "lightpink" + - "lightsalmon" + - "lightseagreen" + - "lightskyblue" + - "lightslategray" + - "lightslategrey" + - "lightsteelblue" + - "lightyellow" + - "lime" + - "limegreen" + - "linen" + - "magenta" + - "maroon" + - "mediumaquamarine" + - "mediumblue" + - "mediumorchid" + - "mediumpurple" + - "mediumseagreen" + - "mediumslateblue" + - "mediumspringgreen" + - "mediumturquoise" + - "mediumvioletred" + - "midnightblue" + - "mintcream" + - "mistyrose" + - "moccasin" + - "navajowhite" + - "navy" + - "navyblue" + - "oldlace" + - "olive" + - "olivedrab" + - "orange" + - "orangered" + - "orchid" + - "palegoldenrod" + - "palegreen" + - "paleturquoise" + - "palevioletred" + - "papayawhip" + - "peachpuff" + - "peru" + - "pink" + - "plum" + - "powderblue" + - "purple" + - "red" + - "rosybrown" + - "royalblue" + - "saddlebrown" + - "salmon" + - "sandybrown" + - "seagreen" + - "seashell" + - "sienna" + - "silver" + - "skyblue" + - "slateblue" + - "slategray" + - "slategrey" + - "snow" + - "springgreen" + - "steelblue" + - "tan" + - "teal" + - "thistle" + - "tomato" + - "turquoise" + - "violet" + - "wheat" + - "white" + - "whitesmoke" + - "yellow" + - "yellowgreen" hs_color: + name: Hue/Sat color description: Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100. + advanced: true example: "[300, 70]" + selector: + object: xy_color: + name: XY-color description: Color for the light in XY-format. + advanced: true example: "[0.52, 0.43]" + selector: + object: color_temp: + name: Color temperature (mireds) description: Color temperature for the light in mireds. + advanced: true example: 250 + selector: + number: + min: 153 + max: 500 + step: 1 + unit_of_measurement: mireds + mode: slider kelvin: + name: Color temperature (Kelvin) description: Color temperature for the light in Kelvin. + advanced: true example: 4000 + selector: + number: + min: 2000 + max: 6500 + step: 100 + unit_of_measurement: K + mode: slider white_value: + name: White level description: Number between 0..255 indicating level of white. + advanced: true example: "250" + selector: + number: + min: 0 + max: 255 + step: 1 + mode: slider brightness: name: Brightness value description: @@ -66,99 +257,369 @@ turn_on: unit_of_measurement: "%" mode: slider brightness_step: + name: Brightness step value description: Change brightness by an amount. Should be between -255..255. + advanced: true example: -25.5 + selector: + number: + min: -225 + max: 255 + step: 1 + mode: slider brightness_step_pct: + name: Brightness step description: Change brightness by a percentage. Should be between -100..100. example: -10 + selector: + number: + min: -100 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider profile: + name: Profile description: Name of a light profile to use. + advanced: true example: relax + selector: + text: flash: + name: Flash description: If the light should flash. Valid values are short and long. + advanced: true example: short values: - short - long + selector: + select: + options: + - long + - short effect: + name: Effect description: Light effect. example: random values: - colorloop - random + selector: + text: turn_off: - description: Turn a light off. + description: Turn a light off + target: fields: - entity_id: - description: Name(s) of entities to turn off. - example: "light.kitchen" transition: + name: Transition description: Duration in seconds it takes to get to next state. example: 60 + selector: + number: + min: 0 + max: 300 + step: 1 + unit_of_measurement: seconds + mode: slider flash: + name: Flash description: If the light should flash. Valid values are short and long. + advanced: true example: short values: - short - long + selector: + select: + options: + - long + - short toggle: - description: Toggles a light. + description: Toggles a light + target: fields: - entity_id: - description: Name(s) of entities to turn on - example: "light.kitchen" transition: - description: Duration in seconds it takes to get to next state + name: Transition + description: Duration in seconds it takes to get to next state. example: 60 + selector: + number: + min: 0 + max: 300 + step: 1 + unit_of_measurement: seconds + mode: slider rgb_color: + name: RGB-color description: Color for the light in RGB-format. + advanced: true example: "[255, 100, 100]" + selector: + object: color_name: + name: Color name description: A human readable color name. + advanced: true example: "red" + selector: + select: + options: + - "aliceblue" + - "antiquewhite" + - "aqua" + - "aquamarine" + - "azure" + - "beige" + - "bisque" + - "black" + - "blanchedalmond" + - "blue" + - "blueviolet" + - "brown" + - "burlywood" + - "cadetblue" + - "chartreuse" + - "chocolate" + - "coral" + - "cornflowerblue" + - "cornsilk" + - "crimson" + - "cyan" + - "darkblue" + - "darkcyan" + - "darkgoldenrod" + - "darkgray" + - "darkgreen" + - "darkgrey" + - "darkkhaki" + - "darkmagenta" + - "darkolivegreen" + - "darkorange" + - "darkorchid" + - "darkred" + - "darksalmon" + - "darkseagreen" + - "darkslateblue" + - "darkslategray" + - "darkslategrey" + - "darkturquoise" + - "darkviolet" + - "deeppink" + - "deepskyblue" + - "dimgray" + - "dimgrey" + - "dodgerblue" + - "firebrick" + - "floralwhite" + - "forestgreen" + - "fuchsia" + - "gainsboro" + - "ghostwhite" + - "gold" + - "goldenrod" + - "gray" + - "green" + - "greenyellow" + - "grey" + - "honeydew" + - "hotpink" + - "indianred" + - "indigo" + - "ivory" + - "khaki" + - "lavender" + - "lavenderblush" + - "lawngreen" + - "lemonchiffon" + - "lightblue" + - "lightcoral" + - "lightcyan" + - "lightgoldenrodyellow" + - "lightgray" + - "lightgreen" + - "lightgrey" + - "lightpink" + - "lightsalmon" + - "lightseagreen" + - "lightskyblue" + - "lightslategray" + - "lightslategrey" + - "lightsteelblue" + - "lightyellow" + - "lime" + - "limegreen" + - "linen" + - "magenta" + - "maroon" + - "mediumaquamarine" + - "mediumblue" + - "mediumorchid" + - "mediumpurple" + - "mediumseagreen" + - "mediumslateblue" + - "mediumspringgreen" + - "mediumturquoise" + - "mediumvioletred" + - "midnightblue" + - "mintcream" + - "mistyrose" + - "moccasin" + - "navajowhite" + - "navy" + - "navyblue" + - "oldlace" + - "olive" + - "olivedrab" + - "orange" + - "orangered" + - "orchid" + - "palegoldenrod" + - "palegreen" + - "paleturquoise" + - "palevioletred" + - "papayawhip" + - "peachpuff" + - "peru" + - "pink" + - "plum" + - "powderblue" + - "purple" + - "red" + - "rosybrown" + - "royalblue" + - "saddlebrown" + - "salmon" + - "sandybrown" + - "seagreen" + - "seashell" + - "sienna" + - "silver" + - "skyblue" + - "slateblue" + - "slategray" + - "slategrey" + - "snow" + - "springgreen" + - "steelblue" + - "tan" + - "teal" + - "thistle" + - "tomato" + - "turquoise" + - "violet" + - "wheat" + - "white" + - "whitesmoke" + - "yellow" + - "yellowgreen" hs_color: + name: Hue/Sat color description: Color for the light in hue/sat format. Hue is 0-360 and Sat is 0-100. + advanced: true example: "[300, 70]" + selector: + object: xy_color: + name: XY-color description: Color for the light in XY-format. + advanced: true example: "[0.52, 0.43]" + selector: + object: color_temp: + name: Color temperature (mireds) description: Color temperature for the light in mireds. + advanced: true example: 250 + selector: + number: + min: 153 + max: 500 + step: 1 + unit_of_measurement: mireds + mode: slider kelvin: + name: Color temperature (Kelvin) description: Color temperature for the light in Kelvin. + advanced: true example: 4000 + selector: + number: + min: 2000 + max: 6500 + step: 100 + unit_of_measurement: K + mode: slider white_value: + name: White level description: Number between 0..255 indicating level of white. + advanced: true example: "250" + selector: + number: + min: 0 + max: 255 + step: 1 + mode: slider brightness: + name: Brightness value description: Number between 0..255 indicating brightness, where 0 turns the light off, 1 is the minimum brightness and 255 is the maximum brightness supported by the light. + advanced: true example: 120 + selector: + number: + min: 0 + max: 255 + step: 1 + mode: slider brightness_pct: + name: Brightness description: Number between 0..100 indicating percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness and 100 is the maximum brightness supported by the light. example: 47 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider profile: + name: Profile description: Name of a light profile to use. + advanced: true example: relax + selector: + text: flash: + name: Flash description: If the light should flash. Valid values are short and long. + advanced: true example: short values: - short - long + selector: + select: + options: + - long + - short effect: + name: Effect description: Light effect. example: random values: - colorloop - random + selector: + text: From b4136c35853bdcc786d965bbe8a9e3ff7816deda Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:16:13 +0100 Subject: [PATCH 0562/1818] Add selectors to WLED service definitions (#46731) --- homeassistant/components/wled/services.yaml | 42 +++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wled/services.yaml b/homeassistant/components/wled/services.yaml index 1f1fa1b809d59e..827e8b5fb36a55 100644 --- a/homeassistant/components/wled/services.yaml +++ b/homeassistant/components/wled/services.yaml @@ -1,30 +1,58 @@ effect: description: Controls the effect settings of WLED + target: fields: - entity_id: - description: Name of the WLED light entity. - example: "light.wled" effect: + name: Effect description: Name or ID of the WLED light effect. example: "Rainbow" + selector: + text: intensity: + name: Effect intensity description: Intensity of the effect. Number between 0 and 255. example: 100 + selector: + number: + min: 0 + max: 255 + step: 1 + mode: slider palette: + name: Color palette description: Name or ID of the WLED light palette. example: "Tiamat" + selector: + text: speed: + name: Effect speed description: Speed of the effect. Number between 0 (slow) and 255 (fast). example: 150 + selector: + number: + min: 0 + max: 255 + step: 1 + mode: slider reverse: - description: Reverse the effect. Either true to reverse or false otherwise. + name: Reverse effect + description: + Reverse the effect. Either true to reverse or false otherwise. + default: false example: false + selector: + boolean: + preset: description: Calls a preset on the WLED device + target: fields: - entity_id: - description: Name of the WLED light entity. - example: "light.wled" preset: + name: Preset ID description: ID of the WLED preset example: 6 + selector: + number: + min: -1 + max: 65535 + mode: box From 0f4433ce1414a071d7a7f548d72726355fe55442 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:23:10 +0100 Subject: [PATCH 0563/1818] Add advanced selectors to Climate service definitions (#46736) --- homeassistant/components/climate/services.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index ea90cbf30f6ef1..d333260202f975 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -41,11 +41,25 @@ set_temperature: target_temp_high: name: Target temperature high description: New target high temperature for HVAC. + advanced: true example: 26 + selector: + number: + min: 0 + max: 250 + step: 0.1 + mode: box target_temp_low: name: Target temperature low description: New target low temperature for HVAC. + advanced: true example: 20 + selector: + number: + min: 0 + max: 250 + step: 0.1 + mode: box hvac_mode: name: HVAC mode description: HVAC operation mode to set temperature to. From 62e0949ea9c26501e72783dfd6c2b1c4abf03365 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:24:04 +0100 Subject: [PATCH 0564/1818] Add selectors to Z-Wave JS service definitions (#46737) --- .../components/zwave_js/services.yaml | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index cc81da7ed58ff5..edb1f8b1ba4ee2 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -1,24 +1,31 @@ # Describes the format for available Z-Wave services clear_lock_usercode: - description: Clear a usercode from lock. + description: Clear a usercode from lock + target: fields: - entity_id: - description: Lock entity_id. - example: lock.front_door_locked code_slot: - description: Code slot to clear code from. + name: Code slot + description: Code slot to clear code from + required: true example: 1 + selector: + text: set_lock_usercode: - description: Set a usercode to lock. + description: Set a usercode to lock + target: fields: - entity_id: - description: Lock entity_id. - example: lock.front_door_locked code_slot: + name: Code slot description: Code slot to set the code. example: 1 + selector: + text: usercode: + name: Code description: Code to set. + required: true example: 1234 + selector: + text: From 3f96ebeae53a935ba0d215e5d937e4d400be2049 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:33:29 +0100 Subject: [PATCH 0565/1818] Add selectors to Logger, System Log & Logbook service definitions (#46740) --- .../components/logbook/services.yaml | 16 ++++++++- homeassistant/components/logger/services.yaml | 35 +++++++++++++++---- .../components/system_log/services.yaml | 29 ++++++++++++--- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/logbook/services.yaml b/homeassistant/components/logbook/services.yaml index fb1736d7784b5c..252b0b6a39b3fa 100644 --- a/homeassistant/components/logbook/services.yaml +++ b/homeassistant/components/logbook/services.yaml @@ -1,15 +1,29 @@ log: - description: Create a custom entry in your logbook. + description: Create a custom entry in your logbook fields: name: + name: Name description: Custom name for an entity, can be referenced with entity_id + required: true example: "Kitchen" + selector: + text: message: + name: Message description: Message of the custom logbook entry + required: true example: "is being used" + selector: + text: entity_id: + name: Entity ID description: Entity to reference in custom logbook entry [Optional] example: "light.kitchen" + selector: + entity: domain: + name: Domain description: Icon of domain to display in custom logbook entry [Optional] example: "light" + selector: + text: diff --git a/homeassistant/components/logger/services.yaml b/homeassistant/components/logger/services.yaml index 514aac4c71cb0b..4bd46b4b01eaea 100644 --- a/homeassistant/components/logger/services.yaml +++ b/homeassistant/components/logger/services.yaml @@ -1,22 +1,43 @@ set_default_level: - description: Set the default log level for components. + description: Set the default log level for components fields: level: - description: "Default severity level. Possible values are debug, info, warn, warning, error, fatal, critical" + name: Level + description: + "Default severity level. Possible values are debug, info, warn, warning, + error, fatal, critical" example: debug + selector: + select: + options: + - debug + - info + - warning + - error + - fatal + - critical set_level: - description: Set log level for components. + description: Set log level for components fields: homeassistant.core: - description: "Example on how to change the logging level for a Home Assistant core components. Possible values are debug, info, warn, warning, error, fatal, critical" + description: + "Example on how to change the logging level for a Home Assistant core + components. Possible values are debug, info, warn, warning, error, + fatal, critical" example: debug homeassistant.components.mqtt: - description: "Example on how to change the logging level for an Integration. Possible values are debug, info, warn, warning, error, fatal, critical" + description: + "Example on how to change the logging level for an Integration. Possible + values are debug, info, warn, warning, error, fatal, critical" example: warning custom_components.my_integration: - description: "Example on how to change the logging level for a Custom Integration. Possible values are debug, info, warn, warning, error, fatal, critical" + description: + "Example on how to change the logging level for a Custom Integration. + Possible values are debug, info, warn, warning, error, fatal, critical" example: debug aiohttp: - description: "Example on how to change the logging level for a Python module. Possible values are debug, info, warn, warning, error, fatal, critical" + description: + "Example on how to change the logging level for a Python module. + Possible values are debug, info, warn, warning, error, fatal, critical" example: error diff --git a/homeassistant/components/system_log/services.yaml b/homeassistant/components/system_log/services.yaml index 2545d47c82532d..e07aea9c2a12f8 100644 --- a/homeassistant/components/system_log/services.yaml +++ b/homeassistant/components/system_log/services.yaml @@ -1,15 +1,34 @@ clear: - description: Clear all log entries. + description: Clear all log entries write: - description: Write log entry. + description: Write log entry fields: message: - description: Message to log. [Required] + name: Message + description: Message to log. + required: true example: Something went wrong + selector: + text: level: - description: "Log level: debug, info, warning, error, critical. Defaults to 'error'." + name: Level + description: "Log level: debug, info, warning, error, critical." + default: error example: debug + selector: + select: + options: + - "debug" + - "info" + - "warning" + - "error" + - "critical" logger: - description: Logger name under which to log the message. Defaults to 'system_log.external'. + name: Logger + description: + Logger name under which to log the message. Defaults to + 'system_log.external'. example: mycomponent.myplatform + selector: + text: From cd6a83f00901eba78fa59cca66f96f7db5c9df1d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:37:15 +0100 Subject: [PATCH 0566/1818] Add selectors to MQTT service definitions (#46738) --- homeassistant/components/mqtt/services.yaml | 46 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/services.yaml b/homeassistant/components/mqtt/services.yaml index 992dd1b3545112..c6c3014362f332 100644 --- a/homeassistant/components/mqtt/services.yaml +++ b/homeassistant/components/mqtt/services.yaml @@ -1,40 +1,76 @@ # Describes the format for available MQTT services publish: - description: Publish a message to an MQTT topic. + description: Publish a message to an MQTT topic fields: topic: + name: Topic description: Topic to publish payload. + required: true example: /homeassistant/hello + selector: + text: payload: + name: Payload description: Payload to publish. example: This is great + selector: + text: payload_template: - description: Template to render as payload value. Ignored if payload given. + name: Payload Template + description: + Template to render as payload value. Ignored if payload given. + advanced: true example: "{{ states('sensor.temperature') }}" + selector: + object: qos: + name: QoS description: Quality of Service to use. + advanced: true example: 2 values: - 0 - 1 - 2 default: 0 + selector: + select: + options: + - "0" + - "1" + - "2" retain: + name: Retain description: If message should have the retain flag set. - example: true default: false + example: true + selector: + boolean: dump: - description: Dump messages on a topic selector to the 'mqtt_dump.txt' file in your config folder. + description: + Dump messages on a topic selector to the 'mqtt_dump.txt' file in your config + folder fields: topic: + name: Topic description: topic to listen to example: "OpenZWave/#" + selector: + text: duration: + name: Duration description: how long we should listen for messages in seconds example: 5 default: 5 + selector: + number: + min: 1 + max: 300 + step: 1 + unit_of_measurement: "seconds" + mode: slider reload: - description: Reload all MQTT entities from YAML. + description: Reload all MQTT entities from YAML From e57bfe05d5eb353b05dcdd5012b36e5c5e3d78bb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:43:34 +0100 Subject: [PATCH 0567/1818] Add selectors to Color Extractor service definitions (#46742) --- .../components/color_extractor/services.yaml | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/color_extractor/services.yaml b/homeassistant/components/color_extractor/services.yaml index 33055fd41b9438..671a2a2ebb9360 100644 --- a/homeassistant/components/color_extractor/services.yaml +++ b/homeassistant/components/color_extractor/services.yaml @@ -1,12 +1,22 @@ turn_on: - description: Set the light RGB to the predominant color found in the image provided by URL or file path. + description: + Set the light RGB to the predominant color found in the image provided by + URL or file path + target: fields: color_extract_url: - description: The URL of the image we want to extract RGB values from. Must be allowed in allowlist_external_urls. + name: URL + description: + The URL of the image we want to extract RGB values from. Must be allowed + in allowlist_external_urls. example: https://www.example.com/images/logo.png + selector: + text: color_extract_path: - description: The full system path to the image we want to extract RGB values from. Must be allowed in allowlist_external_dirs. + name: Path + description: + The full system path to the image we want to extract RGB values from. + Must be allowed in allowlist_external_dirs. example: /opt/images/logo.png - entity_id: - description: The entity we want to set our RGB color on. - example: "light.living_room_shelves" + selector: + text: From 4b6b03e33e6fc6314a4d437f86ac747ac898d0e0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:51:13 +0100 Subject: [PATCH 0568/1818] Add selectors to Lock service definitions (#46743) --- homeassistant/components/lock/services.yaml | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/lock/services.yaml b/homeassistant/components/lock/services.yaml index d1456f1e68e464..22af0ab97cf87b 100644 --- a/homeassistant/components/lock/services.yaml +++ b/homeassistant/components/lock/services.yaml @@ -21,27 +21,29 @@ get_usercode: example: 1 lock: - description: Lock all or specified locks. + description: Lock all or specified locks + target: fields: - entity_id: - description: Name of lock to lock. - example: "lock.front_door" code: + name: Code description: An optional code to lock the lock with. example: 1234 + selector: + text: open: - description: Open all or specified locks. + description: Open all or specified locks + target: fields: - entity_id: - description: Name of lock to open. - example: "lock.front_door" code: + name: Code description: An optional code to open the lock with. example: 1234 + selector: + text: set_usercode: - description: Set a usercode to lock. + description: Set a usercode to lock fields: node_id: description: Node id of the lock. @@ -54,11 +56,12 @@ set_usercode: example: 1234 unlock: - description: Unlock all or specified locks. + description: Unlock all or specified locks + target: fields: - entity_id: - description: Name of lock to unlock. - example: "lock.front_door" code: + name: Code description: An optional code to unlock the lock with. example: 1234 + selector: + text: From a5ac338c7482285fdae57a33f0e51b54790c11e8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 16:54:20 +0100 Subject: [PATCH 0569/1818] Add selectors to Timer service definitions (#46744) --- homeassistant/components/timer/services.yaml | 34 +++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/timer/services.yaml b/homeassistant/components/timer/services.yaml index cd810c21de51fa..fcde11cd47f09d 100644 --- a/homeassistant/components/timer/services.yaml +++ b/homeassistant/components/timer/services.yaml @@ -1,36 +1,24 @@ # Describes the format for available timer services start: - description: Start a timer. - + description: Start a timer + target: fields: - entity_id: - description: Entity id of the timer to start. [optional] - example: "timer.timer0" duration: description: Duration the timer requires to finish. [optional] + default: 0 example: "00:01:00 or 60" + selector: + text: pause: - description: Pause a timer. - - fields: - entity_id: - description: Entity id of the timer to pause. [optional] - example: "timer.timer0" + description: Pause a timer + target: cancel: - description: Cancel a timer. - - fields: - entity_id: - description: Entity id of the timer to cancel. [optional] - example: "timer.timer0" + description: Cancel a timer + target: finish: - description: Finish a timer. - - fields: - entity_id: - description: Entity id of the timer to finish. [optional] - example: "timer.timer0" + description: Finish a timer + target: From 3c7db7bd5b1db889f7e0473c6e1ee726dacf8817 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 19 Feb 2021 00:00:26 +0800 Subject: [PATCH 0570/1818] Skip repeated segment in stream recorder (#46701) * Skip repeated segment in stream recorder * Allow for multiple overlap --- homeassistant/components/stream/recorder.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 0fc3d84b1b9471..9653123377133b 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -42,7 +42,14 @@ def recorder_save_worker(file_out: str, segments: List[Segment], container_forma ) source.close() + last_sequence = float("-inf") for segment in segments: + # Because the stream_worker is in a different thread from the record service, + # the lookback segments may still have some overlap with the recorder segments + if segment.sequence <= last_sequence: + continue + last_sequence = segment.sequence + # Open segment source = av.open(segment.segment, "r", format=container_format) source_v = source.streams.video[0] From cdc4f634d18ac750b9185d24451c21427a3a1eea Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 18 Feb 2021 17:01:22 +0100 Subject: [PATCH 0571/1818] Fix typo in Tesla Powerwall strings (#46752) --- homeassistant/components/powerwall/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index c576d931756b98..5deacd6a8f9d1c 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -4,7 +4,7 @@ "step": { "user": { "title": "Connect to the powerwall", - "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Telsa app; or the last 5 characters of the password found inside the door for Backup Gateway 2.", + "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.", "data": { "ip_address": "[%key:common::config_flow::data::ip%]", "password": "[%key:common::config_flow::data::password%]" From a164a6cf8020ba865f53988a32869c04a745f975 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:01:29 +0100 Subject: [PATCH 0572/1818] Add selectors to Hue service definitions (#46747) --- homeassistant/components/hue/services.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/services.yaml b/homeassistant/components/hue/services.yaml index 68eaf6ac377b2f..80ca25007bcf73 100644 --- a/homeassistant/components/hue/services.yaml +++ b/homeassistant/components/hue/services.yaml @@ -1,11 +1,17 @@ # Describes the format for available hue services hue_activate_scene: - description: Activate a hue scene stored in the hue hub. + description: Activate a hue scene stored in the hue hub fields: group_name: + name: Group description: Name of hue group/room from the hue app. example: "Living Room" + selector: + text: scene_name: + name: Scene description: Name of hue scene from the hue app. example: "Energize" + selector: + text: From 81c7b3b9c9949b87cc32808960e15ceee6389c45 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:02:18 +0100 Subject: [PATCH 0573/1818] Add selectors to Media Player service definitions (#46739) --- .../components/media_player/services.yaml | 184 +++++++++--------- 1 file changed, 91 insertions(+), 93 deletions(-) diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index 08637df0745f06..f85e0658426a1c 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -1,168 +1,166 @@ # Describes the format for available media player services turn_on: - description: Turn a media player power on. - fields: - entity_id: - description: Name(s) of entities to turn on. - example: "media_player.living_room_chromecast" + description: Turn a media player power on + target: turn_off: - description: Turn a media player power off. - fields: - entity_id: - description: Name(s) of entities to turn off. - example: "media_player.living_room_chromecast" + description: Turn a media player power off + target: toggle: - description: Toggles a media player power state. - fields: - entity_id: - description: Name(s) of entities to toggle. - example: "media_player.living_room_chromecast" + description: Toggles a media player power state + target: volume_up: - description: Turn a media player volume up. - fields: - entity_id: - description: Name(s) of entities to turn volume up on. - example: "media_player.living_room_sonos" + description: Turn a media player volume up + target: volume_down: - description: Turn a media player volume down. - fields: - entity_id: - description: Name(s) of entities to turn volume down on. - example: "media_player.living_room_sonos" + description: Turn a media player volume down + target: volume_mute: - description: Mute a media player's volume. + description: Mute a media player's volume + target: fields: - entity_id: - description: Name(s) of entities to mute. - example: "media_player.living_room_sonos" is_volume_muted: + name: Muted description: True/false for mute/unmute. + required: true example: true + selector: + boolean: volume_set: - description: Set a media player's volume level. + description: Set a media player's volume level + target: fields: - entity_id: - description: Name(s) of entities to set volume level on. - example: "media_player.living_room_sonos" volume_level: + name: Level description: Volume level to set as float. + required: true example: 0.6 + selector: + number: + min: 0 + max: 1 + step: 0.01 + mode: slider media_play_pause: - description: Toggle media player play/pause state. - fields: - entity_id: - description: Name(s) of entities to toggle play/pause state on. - example: "media_player.living_room_sonos" + description: Toggle media player play/pause state + target: media_play: - description: Send the media player the command for play. - fields: - entity_id: - description: Name(s) of entities to play on. - example: "media_player.living_room_sonos" + description: Send the media player the command for play + target: media_pause: - description: Send the media player the command for pause. - fields: - entity_id: - description: Name(s) of entities to pause on. - example: "media_player.living_room_sonos" + description: Send the media player the command for pause + target: media_stop: - description: Send the media player the stop command. - fields: - entity_id: - description: Name(s) of entities to stop on. - example: "media_player.living_room_sonos" + description: Send the media player the stop command + target: media_next_track: - description: Send the media player the command for next track. - fields: - entity_id: - description: Name(s) of entities to send next track command to. - example: "media_player.living_room_sonos" + description: Send the media player the command for next track + target: media_previous_track: - description: Send the media player the command for previous track. - fields: - entity_id: - description: Name(s) of entities to send previous track command to. - example: "media_player.living_room_sonos" + description: Send the media player the command for previous track + target: media_seek: - description: Send the media player the command to seek in current playing media. + description: + Send the media player the command to seek in current playing media fields: - entity_id: - description: Name(s) of entities to seek media on. - example: "media_player.living_room_chromecast" seek_position: + name: Position description: Position to seek to. The format is platform dependent. + required: true example: 100 + selector: + number: + min: 0 + max: 9223372036854775807 + step: 0.01 + mode: box play_media: - description: Send the media player the command for playing media. + description: Send the media player the command for playing media + target: fields: - entity_id: - description: Name(s) of entities to seek media on - example: "media_player.living_room_chromecast" media_content_id: + name: Content ID description: The ID of the content to play. Platform dependent. + required: true example: "https://home-assistant.io/images/cast/splash.png" + selector: + text: + media_content_type: - description: The type of the content to play. Must be one of image, music, tvshow, video, episode, channel or playlist + name: Content type + description: + The type of the content to play. Must be one of image, music, tvshow, + video, episode, channel or playlist. + required: true example: "music" + selector: + text: select_source: - description: Send the media player the command to change input source. + description: Send the media player the command to change input source + target: fields: - entity_id: - description: Name(s) of entities to change source on. - example: "media_player.txnr535_0009b0d81f82" source: + name: Source description: Name of the source to switch to. Platform dependent. + required: true example: "video1" + selector: + text: select_sound_mode: - description: Send the media player the command to change sound mode. + description: Send the media player the command to change sound mode + target: fields: - entity_id: - description: Name(s) of entities to change sound mode on. - example: "media_player.marantz" sound_mode: + name: Sound mode description: Name of the sound mode to switch to. example: "Music" + selector: + text: clear_playlist: - description: Send the media player the command to clear players playlist. - fields: - entity_id: - description: Name(s) of entities to change source on. - example: "media_player.living_room_chromecast" + description: Send the media player the command to clear players playlist + target: shuffle_set: - description: Set shuffling state. + description: Set shuffling state + target: fields: - entity_id: - description: Name(s) of entities to set. - example: "media_player.spotify" shuffle: + name: Shuffle description: True/false for enabling/disabling shuffle. + required: true example: true + selector: + boolean: repeat_set: - description: Set repeat mode. + description: Set repeat mode + target: fields: - entity_id: - description: Name(s) of entities to set. - example: "media_player.sonos" repeat: + name: Repeat mode description: Repeat mode to set (off, all, one). + required: true example: "off" + selector: + select: + options: + - "off" + - "all" + - "one" From 208af0367a2138d3fb7a3bea2270bb90af6612ef Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:02:45 +0100 Subject: [PATCH 0574/1818] Add selectors to Toon service definitions (#46750) --- homeassistant/components/toon/services.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/toon/services.yaml b/homeassistant/components/toon/services.yaml index 7afedeb4bf6696..3e06e6d3f9fdfa 100644 --- a/homeassistant/components/toon/services.yaml +++ b/homeassistant/components/toon/services.yaml @@ -2,5 +2,9 @@ update: description: Update all entities with fresh data from Toon fields: display: + name: Display description: Toon display to update (optional) + advanced: true example: eneco-001-123456 + selector: + text: From 8b69608242774a4dbeeb6024974acae0e1e8e415 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:06:25 +0100 Subject: [PATCH 0575/1818] Add selectors to Browser, Recorder, Shopping List service definitions (#46749) --- .../components/browser/services.yaml | 7 ++++++- .../components/recorder/services.yaml | 20 ++++++++++++++++--- .../components/shopping_list/services.yaml | 15 +++++++++++--- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/browser/services.yaml b/homeassistant/components/browser/services.yaml index 460def22dc1445..f6c5e7c90e1e4f 100644 --- a/homeassistant/components/browser/services.yaml +++ b/homeassistant/components/browser/services.yaml @@ -1,6 +1,11 @@ browse_url: - description: Open a URL in the default browser on the host machine of Home Assistant. + description: + Open a URL in the default browser on the host machine of Home Assistant fields: url: + name: URL description: The URL to open. + required: true example: "https://www.home-assistant.io" + selector: + text: diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index 512807c9f69427..cad1925080f85c 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -1,11 +1,25 @@ # Describes the format for available recorder services purge: - description: Start purge task - delete events and states older than x days, according to keep_days service data. + description: Start purge task - to clean up old data from your database fields: keep_days: - description: Number of history days to keep in database after purge. Value >= 0. + name: Days to keep + description: Number of history days to keep in database after purge. example: 2 + selector: + number: + min: 0 + max: 365 + step: 1 + unit_of_measurement: days + mode: slider + repack: - description: Attempt to save disk space by rewriting the entire database file. + name: Repack + description: + Attempt to save disk space by rewriting the entire database file. example: true + default: false + selector: + boolean: diff --git a/homeassistant/components/shopping_list/services.yaml b/homeassistant/components/shopping_list/services.yaml index 04457e2abec4e2..961fb867aa7147 100644 --- a/homeassistant/components/shopping_list/services.yaml +++ b/homeassistant/components/shopping_list/services.yaml @@ -1,12 +1,21 @@ add_item: - description: Adds an item to the shopping list. + description: Adds an item to the shopping list fields: name: + name: Name description: The name of the item to add. + required: true example: Beer + selector: + text: + complete_item: - description: Marks an item as completed in the shopping list. It does not remove the item. + description: Marks an item as completed in the shopping list. fields: name: - description: The name of the item to mark as completed. + name: Name + description: The name of the item to mark as completed (without removing). + required: true example: Beer + selector: + text: From b9d5b3c34e71adc8375eb88a56228ca42339d668 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:13:59 +0100 Subject: [PATCH 0576/1818] Add selectors to Conversation, Image Processing and Number service definitions (#46746) --- homeassistant/components/conversation/services.yaml | 5 ++++- homeassistant/components/image_processing/services.yaml | 7 ++----- homeassistant/components/number/services.yaml | 9 +++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/conversation/services.yaml b/homeassistant/components/conversation/services.yaml index 032edba8db18f9..5c85de7c187670 100644 --- a/homeassistant/components/conversation/services.yaml +++ b/homeassistant/components/conversation/services.yaml @@ -1,7 +1,10 @@ # Describes the format for available component services process: - description: Launch a conversation from a transcribed text. + description: Launch a conversation from a transcribed text fields: text: + name: Text description: Transcribed text example: Turn all lights on + selector: + text: diff --git a/homeassistant/components/image_processing/services.yaml b/homeassistant/components/image_processing/services.yaml index 69e455344b0ab2..cd074acd9f433b 100644 --- a/homeassistant/components/image_processing/services.yaml +++ b/homeassistant/components/image_processing/services.yaml @@ -1,8 +1,5 @@ # Describes the format for available image processing services scan: - description: Process an image immediately. - fields: - entity_id: - description: Name(s) of entities to scan immediately. - example: "image_processing.alpr_garage" + description: Process an image immediately + target: diff --git a/homeassistant/components/number/services.yaml b/homeassistant/components/number/services.yaml index d18416f9974275..4cb0cf098291a3 100644 --- a/homeassistant/components/number/services.yaml +++ b/homeassistant/components/number/services.yaml @@ -1,11 +1,12 @@ # Describes the format for available Number entity services set_value: - description: Set the value of a Number entity. + description: Set the value of a Number entity + target: fields: - entity_id: - description: Entity ID of the Number to set the new value. - example: number.volume value: + name: Value description: The target value the entity should be set to. example: 42 + selector: + text: From a80921ab65208cb7c704318b4c2fb415c040649b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:14:36 +0100 Subject: [PATCH 0577/1818] Minor service definition tweaks (#46741) --- .../components/bayesian/services.yaml | 2 +- homeassistant/components/cloud/services.yaml | 4 ++-- .../components/cloudflare/services.yaml | 2 +- .../components/command_line/services.yaml | 2 +- .../components/debugpy/services.yaml | 2 +- homeassistant/components/filter/services.yaml | 2 +- .../components/keyboard/services.yaml | 24 ++++++++++++++----- .../components/lovelace/services.yaml | 2 +- homeassistant/components/rest/services.yaml | 2 +- .../components/universal/services.yaml | 2 +- 10 files changed, 28 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/bayesian/services.yaml b/homeassistant/components/bayesian/services.yaml index ec7313a86301d3..2fe3a4f7c9b260 100644 --- a/homeassistant/components/bayesian/services.yaml +++ b/homeassistant/components/bayesian/services.yaml @@ -1,2 +1,2 @@ reload: - description: Reload all bayesian entities. + description: Reload all bayesian entities diff --git a/homeassistant/components/cloud/services.yaml b/homeassistant/components/cloud/services.yaml index 20c25225ce2450..a7fb6b2f21b56e 100644 --- a/homeassistant/components/cloud/services.yaml +++ b/homeassistant/components/cloud/services.yaml @@ -1,7 +1,7 @@ # Describes the format for available cloud services remote_connect: - description: Make instance UI available outside over NabuCasa cloud. + description: Make instance UI available outside over NabuCasa cloud remote_disconnect: - description: Disconnect UI from NabuCasa cloud. + description: Disconnect UI from NabuCasa cloud diff --git a/homeassistant/components/cloudflare/services.yaml b/homeassistant/components/cloudflare/services.yaml index 23ffdd14d5f03d..80165700dbb034 100644 --- a/homeassistant/components/cloudflare/services.yaml +++ b/homeassistant/components/cloudflare/services.yaml @@ -1,2 +1,2 @@ update_records: - description: Manually trigger update to Cloudflare records. + description: Manually trigger update to Cloudflare records diff --git a/homeassistant/components/command_line/services.yaml b/homeassistant/components/command_line/services.yaml index 8876e8dc925184..de010ba8b850d7 100644 --- a/homeassistant/components/command_line/services.yaml +++ b/homeassistant/components/command_line/services.yaml @@ -1,2 +1,2 @@ reload: - description: Reload all command_line entities. + description: Reload all command_line entities diff --git a/homeassistant/components/debugpy/services.yaml b/homeassistant/components/debugpy/services.yaml index 4e3c19dd0d7ea1..6bf9ad67288448 100644 --- a/homeassistant/components/debugpy/services.yaml +++ b/homeassistant/components/debugpy/services.yaml @@ -1,3 +1,3 @@ # Describes the format for available Remote Python Debugger services start: - description: Start the Remote Python Debugger. + description: Start the Remote Python Debugger diff --git a/homeassistant/components/filter/services.yaml b/homeassistant/components/filter/services.yaml index 6f6ea1b04d6aa1..7d64b34a4f7605 100644 --- a/homeassistant/components/filter/services.yaml +++ b/homeassistant/components/filter/services.yaml @@ -1,2 +1,2 @@ reload: - description: Reload all filter entities. + description: Reload all filter entities diff --git a/homeassistant/components/keyboard/services.yaml b/homeassistant/components/keyboard/services.yaml index 8e49cdd6a124c5..d0919d595143bf 100644 --- a/homeassistant/components/keyboard/services.yaml +++ b/homeassistant/components/keyboard/services.yaml @@ -1,17 +1,29 @@ volume_up: - description: Simulates a key press of the "Volume Up" button on Home Assistant's host machine. + description: + Simulates a key press of the "Volume Up" button on Home Assistant's host + machine volume_down: - description: Simulates a key press of the "Volume Down" button on Home Assistant's host machine. + description: + Simulates a key press of the "Volume Down" button on Home Assistant's host + machine volume_mute: - description: Simulates a key press of the "Volume Mute" button on Home Assistant's host machine. + description: + Simulates a key press of the "Volume Mute" button on Home Assistant's host + machine media_play_pause: - description: Simulates a key press of the "Media Play/Pause" button on Home Assistant's host machine. + description: + Simulates a key press of the "Media Play/Pause" button on Home Assistant's + host machine media_next_track: - description: Simulates a key press of the "Media Next Track" button on Home Assistant's host machine. + description: + Simulates a key press of the "Media Next Track" button on Home Assistant's + host machine media_prev_track: - description: Simulates a key press of the "Media Previous Track" button on Home Assistant's host machine. + description: + Simulates a key press of the "Media Previous Track" button on Home + Assistant's host machine diff --git a/homeassistant/components/lovelace/services.yaml b/homeassistant/components/lovelace/services.yaml index 20450942dce572..b324b551e942ce 100644 --- a/homeassistant/components/lovelace/services.yaml +++ b/homeassistant/components/lovelace/services.yaml @@ -1,4 +1,4 @@ # Describes the format for available lovelace services reload_resources: - description: Reload Lovelace resources from YAML configuration. + description: Reload Lovelace resources from YAML configuration diff --git a/homeassistant/components/rest/services.yaml b/homeassistant/components/rest/services.yaml index 06baa8734f2979..7e324670134a28 100644 --- a/homeassistant/components/rest/services.yaml +++ b/homeassistant/components/rest/services.yaml @@ -1,2 +1,2 @@ reload: - description: Reload all rest entities and notify services. + description: Reload all rest entities and notify services diff --git a/homeassistant/components/universal/services.yaml b/homeassistant/components/universal/services.yaml index ed8f550275e385..8b515151fd9bae 100644 --- a/homeassistant/components/universal/services.yaml +++ b/homeassistant/components/universal/services.yaml @@ -1,2 +1,2 @@ reload: - description: Reload all universal entities. + description: Reload all universal entities From e000b9c813c625e2ab9042c041fc8c03ac20547d Mon Sep 17 00:00:00 2001 From: AdmiralStipe <64564398+AdmiralStipe@users.noreply.github.com> Date: Thu, 18 Feb 2021 17:16:45 +0100 Subject: [PATCH 0578/1818] Added Slovenian language (sl-si) to Microsoft TTS (#46720) --- 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 bbfe9b0379e2e6..1e1c088b351d5b 100644 --- a/homeassistant/components/microsoft/tts.py +++ b/homeassistant/components/microsoft/tts.py @@ -54,6 +54,7 @@ "ro-ro", "ru-ru", "sk-sk", + "sl-si", "sv-se", "th-th", "tr-tr", From 113059c1c737d7d873c950be58c27a0517f88dc7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 17:32:48 +0100 Subject: [PATCH 0579/1818] Add selectors to HomeKit service definitions (#46745) --- homeassistant/components/homekit/services.yaml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/homekit/services.yaml b/homeassistant/components/homekit/services.yaml index 96971f7030033d..6f9c005ed641da 100644 --- a/homeassistant/components/homekit/services.yaml +++ b/homeassistant/components/homekit/services.yaml @@ -1,14 +1,11 @@ # Describes the format for available HomeKit services start: - description: Starts the HomeKit driver. + description: Starts the HomeKit driver reload: - description: Reload homekit and re-process YAML configuration. + description: Reload homekit and re-process YAML configuration reset_accessory: - description: Reset a HomeKit accessory. This can be useful when changing a media_player’s device class to tv, linking a battery, or whenever Home Assistant adds support for new HomeKit features to existing entities. - fields: - entity_id: - description: Name of the entity to reset. - example: "binary_sensor.grid_status" + description: Reset a HomeKit accessory + target: From b6f374b507679962344bea9952e6b2cb1bf8c9a1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 18:05:03 +0100 Subject: [PATCH 0580/1818] Add selectors to Twente Milieu service definitions (#46748) --- homeassistant/components/twentemilieu/services.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/twentemilieu/services.yaml b/homeassistant/components/twentemilieu/services.yaml index 7a5b1db301d79c..7a6a16f33ad0e4 100644 --- a/homeassistant/components/twentemilieu/services.yaml +++ b/homeassistant/components/twentemilieu/services.yaml @@ -2,5 +2,9 @@ update: description: Update all entities with fresh data from Twente Milieu fields: id: + name: ID description: Specific unique address ID to update + advanced: true example: 1300012345 + selector: + text: From 782863ca8712a045f34f78375c7c34ad3c6977b7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 18:17:09 +0100 Subject: [PATCH 0581/1818] Ensure pre-commit's hassfest triggers on service file changes (#46753) --- .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 efd4b86e8ac2c9..6b650ebac798f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -90,4 +90,4 @@ repos: pass_filenames: false language: script types: [text] - files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc)$ + files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|homeassistant/.+/services\.yaml)$ From 01ebb156a05d874c71c5596501a953fcf6278831 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 18:19:49 +0100 Subject: [PATCH 0582/1818] Add selectors to Home Assistant service definitions (#46733) Co-authored-by: Bram Kragten --- .../components/homeassistant/services.yaml | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/homeassistant/services.yaml b/homeassistant/components/homeassistant/services.yaml index cb3efb0d524a67..d23bdfdba72fed 100644 --- a/homeassistant/components/homeassistant/services.yaml +++ b/homeassistant/components/homeassistant/services.yaml @@ -1,49 +1,49 @@ check_config: - description: Check the Home Assistant configuration files for errors. Errors will be displayed in the Home Assistant log. + description: + Check the Home Assistant configuration files for errors. Errors will be + displayed in the Home Assistant log reload_core_config: - description: Reload the core configuration. + description: Reload the core configuration restart: - description: Restart the Home Assistant service. + description: Restart the Home Assistant service set_location: - description: Update the Home Assistant location. + description: Update the Home Assistant location fields: latitude: - description: Latitude of your location + name: Latitude + description: Latitude of your location. + required: true example: 32.87336 + selector: + text: longitude: - description: Longitude of your location + name: Longitude + description: Longitude of your location. + required: true example: 117.22743 + selector: + text: stop: description: Stop the Home Assistant service. toggle: - description: Generic service to toggle devices on/off under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services. - fields: - entity_id: - description: The entity_id of the device to toggle on/off. - example: light.living_room + description: Generic service to toggle devices on/off under any domain + target: + entity: {} turn_on: - description: Generic service to turn devices on under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services. - fields: - entity_id: - description: The entity_id of the device to turn on. - example: light.living_room + description: Generic service to turn devices on under any domain. + target: + entity: {} turn_off: - description: Generic service to turn devices off under any domain. Same usage as the light.turn_on, switch.turn_on, etc. services. - fields: - entity_id: - description: The entity_id of the device to turn off. - example: light.living_room + description: Generic service to turn devices off under any domain. update_entity: description: Force one or more entities to update its data - fields: - entity_id: - description: One or multiple entity_ids to update. Can be a list. - example: light.living_room + target: + entity: {} From 3353c63f8f9502a6a0d81741f1f8677a47f2f826 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 18:25:15 +0100 Subject: [PATCH 0583/1818] Upgrade sentry-sdk to 0.20.3 (#46754) --- 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 4c823362aee78b..d0592493f15633 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,6 +3,6 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==0.20.2"], + "requirements": ["sentry-sdk==0.20.3"], "codeowners": ["@dcramer", "@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index 815484aa70007d..1e9b7295946577 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2017,7 +2017,7 @@ sense-hat==2.2.0 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.2 +sentry-sdk==0.20.3 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cf1b25c93506f4..71bcca6fe2ff37 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1033,7 +1033,7 @@ scapy==2.4.4 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.2 +sentry-sdk==0.20.3 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From 8b97f62a8e0fd309ad3807fff58c13e45a762375 Mon Sep 17 00:00:00 2001 From: Ellis Michael Date: Thu, 18 Feb 2021 09:40:16 -0800 Subject: [PATCH 0584/1818] Allow LIFX bulbs to fade color even when off (#46596) LIFX bulbs have the capability to fade their color attributes even while the bulb is off. When the bulb is later turned on, the fade will continue as if the bulb was on all along. --- homeassistant/components/lifx/light.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index e775b5623d33c8..f06a7720bb2146 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -608,9 +608,13 @@ async def set_state(self, **kwargs): if not self.is_on: if power_off: await self.set_power(ack, False) - if hsbk: + # If fading on with color, set color immediately + if hsbk and power_on: await self.set_color(ack, hsbk, kwargs) - if power_on: + await self.set_power(ack, True, duration=fade) + elif hsbk: + await self.set_color(ack, hsbk, kwargs, duration=fade) + elif power_on: await self.set_power(ack, True, duration=fade) else: if power_on: From 76e5f86b76ffbe10ba22505270946b56933217f9 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 18 Feb 2021 10:08:48 -0800 Subject: [PATCH 0585/1818] Add @esev as codeowner for wemo (#46756) --- CODEOWNERS | 1 + homeassistant/components/wemo/manifest.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 75faf90eb75fe9..ab10b4dfd6040c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -521,6 +521,7 @@ homeassistant/components/watson_tts/* @rutkai homeassistant/components/weather/* @fabaff homeassistant/components/webostv/* @bendavid homeassistant/components/websocket_api/* @home-assistant/core +homeassistant/components/wemo/* @esev homeassistant/components/wiffi/* @mampfes homeassistant/components/wilight/* @leofig-rj homeassistant/components/withings/* @vangorra diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index fe5559b58d6cc3..94bc0fa72aaa03 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -12,5 +12,5 @@ "homekit": { "models": ["Socket", "Wemo"] }, - "codeowners": [] + "codeowners": ["@esev"] } From 52a9a66d0f499a8ff402392187d59ad3e6682d0d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Feb 2021 21:30:54 +0100 Subject: [PATCH 0586/1818] Upgrade cryptography to 3.3.2 (#46759) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7db311b56d5bc5..5f2aa859e26323 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ awesomeversion==21.2.2 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 -cryptography==3.3.1 +cryptography==3.3.2 defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 diff --git a/requirements.txt b/requirements.txt index 42be3cfdf49379..d0b894bcb589fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ ciso8601==2.1.3 httpx==0.16.1 jinja2>=2.11.3 PyJWT==1.7.1 -cryptography==3.3.1 +cryptography==3.3.2 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2021.1 diff --git a/setup.py b/setup.py index 1b4b52ff26d4e4..1dbc29f5537669 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ "jinja2>=2.11.3", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. - "cryptography==3.3.1", + "cryptography==3.3.2", "pip>=8.0.3,<20.3", "python-slugify==4.0.1", "pytz>=2021.1", From d9ce7db554d86790cf28b5b58a95b727c7864cf4 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 19 Feb 2021 00:03:06 +0000 Subject: [PATCH 0587/1818] [ci skip] Translation update --- .../components/aemet/translations/fr.json | 22 ++++++ .../components/asuswrt/translations/fr.json | 45 +++++++++++ .../fireservicerota/translations/fr.json | 3 +- .../components/foscam/translations/fr.json | 2 + .../components/fritzbox/translations/fr.json | 3 +- .../components/habitica/translations/fr.json | 20 +++++ .../components/habitica/translations/ru.json | 20 +++++ .../components/hyperion/translations/fr.json | 4 +- .../keenetic_ndms2/translations/fr.json | 36 +++++++++ .../components/kulersky/translations/fr.json | 5 ++ .../components/local_ip/translations/fr.json | 1 + .../lutron_caseta/translations/fr.json | 10 +++ .../components/lyric/translations/fr.json | 9 +++ .../components/mazda/translations/fr.json | 3 +- .../media_player/translations/fr.json | 7 ++ .../motion_blinds/translations/fr.json | 1 + .../components/mysensors/translations/fr.json | 79 +++++++++++++++++++ .../components/neato/translations/fr.json | 11 ++- .../components/nest/translations/fr.json | 4 +- .../components/nuki/translations/fr.json | 3 +- .../ondilo_ico/translations/fr.json | 14 ++++ .../components/ozw/translations/fr.json | 1 + .../philips_js/translations/fr.json | 24 ++++++ .../components/powerwall/translations/ca.json | 2 +- .../components/powerwall/translations/en.json | 2 +- .../components/powerwall/translations/fr.json | 8 +- .../components/roku/translations/fr.json | 1 + .../components/shelly/translations/fr.json | 5 +- .../components/smarttub/translations/fr.json | 22 ++++++ .../components/smarttub/translations/ru.json | 22 ++++++ .../components/tesla/translations/fr.json | 4 + .../components/tuya/translations/fr.json | 2 + .../components/unifi/translations/fr.json | 3 +- .../xiaomi_miio/translations/fr.json | 12 ++- .../components/zwave_js/translations/fr.json | 4 +- 35 files changed, 398 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/aemet/translations/fr.json create mode 100644 homeassistant/components/asuswrt/translations/fr.json create mode 100644 homeassistant/components/habitica/translations/fr.json create mode 100644 homeassistant/components/habitica/translations/ru.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/fr.json create mode 100644 homeassistant/components/mysensors/translations/fr.json create mode 100644 homeassistant/components/philips_js/translations/fr.json create mode 100644 homeassistant/components/smarttub/translations/fr.json create mode 100644 homeassistant/components/smarttub/translations/ru.json diff --git a/homeassistant/components/aemet/translations/fr.json b/homeassistant/components/aemet/translations/fr.json new file mode 100644 index 00000000000000..bb1e792aa5e504 --- /dev/null +++ b/homeassistant/components/aemet/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'emplacement est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "invalid_api_key": "Cl\u00e9 API invalide" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom de l'int\u00e9gration" + }, + "description": "Configurez l'int\u00e9gration AEMET OpenData. Pour g\u00e9n\u00e9rer la cl\u00e9 API, acc\u00e9dez \u00e0 https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/fr.json b/homeassistant/components/asuswrt/translations/fr.json new file mode 100644 index 00000000000000..0d53f3f24cfc79 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/fr.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_host": "Nom d'h\u00f4te ou adresse IP non valide", + "pwd_and_ssh": "Fournissez uniquement le mot de passe ou le fichier de cl\u00e9 SSH", + "pwd_or_ssh": "Veuillez fournir un mot de passe ou un fichier de cl\u00e9 SSH", + "ssh_not_file": "Fichier cl\u00e9 SSH non trouv\u00e9", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "mode": "Mode", + "name": "Nom", + "password": "Mot de passe", + "port": "Port", + "protocol": "Protocole de communication \u00e0 utiliser", + "ssh_key": "Chemin d'acc\u00e8s \u00e0 votre fichier de cl\u00e9s SSH (au lieu du mot de passe)", + "username": "Nom d'utilisateur" + }, + "description": "D\u00e9finissez les param\u00e8tres n\u00e9cessaires pour vous connecter \u00e0 votre routeur", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Quelques secondes d'attente avant d'envisager l'abandon d'un appareil", + "dnsmasq": "L\u2019emplacement dans le routeur des fichiers dnsmasq.leases", + "interface": "L'interface \u00e0 partir de laquelle vous souhaitez obtenir des statistiques (e.g. eth0,eth1 etc)", + "require_ip": "Les appareils doivent avoir une IP (pour le mode point d'acc\u00e8s)", + "track_unknown": "Traquer les appareils inconnus / non identifi\u00e9s" + }, + "title": "Options AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/fr.json b/homeassistant/components/fireservicerota/translations/fr.json index d0ce81458e329c..fdbf28e32e1f1a 100644 --- a/homeassistant/components/fireservicerota/translations/fr.json +++ b/homeassistant/components/fireservicerota/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Le compte \u00e0 d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + "already_configured": "Le compte \u00e0 d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "create_entry": { "default": "Autentification r\u00e9ussie" diff --git a/homeassistant/components/foscam/translations/fr.json b/homeassistant/components/foscam/translations/fr.json index 9af8115c305cc1..1424c22ad6121f 100644 --- a/homeassistant/components/foscam/translations/fr.json +++ b/homeassistant/components/foscam/translations/fr.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Echec de connection", "invalid_auth": "Authentification invalide", + "invalid_response": "R\u00e9ponse invalide de l\u2019appareil", "unknown": "Erreur inattendue" }, "step": { @@ -14,6 +15,7 @@ "host": "H\u00f4te", "password": "Mot de passe", "port": "Port", + "rtsp_port": "Port RTSP", "stream": "Flux", "username": "Nom d'utilisateur" } diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json index 0cd425410e6072..e6302964988cfd 100644 --- a/homeassistant/components/fritzbox/translations/fr.json +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -4,7 +4,8 @@ "already_configured": "Cette AVM FRITZ!Box est d\u00e9j\u00e0 configur\u00e9e.", "already_in_progress": "Une configuration d'AVM FRITZ!Box est d\u00e9j\u00e0 en cours.", "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau", - "not_supported": "Connect\u00e9 \u00e0 AVM FRITZ! Box mais impossible de contr\u00f4ler les appareils Smart Home." + "not_supported": "Connect\u00e9 \u00e0 AVM FRITZ! Box mais impossible de contr\u00f4ler les appareils Smart Home.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "invalid_auth": "Authentification invalide" diff --git a/homeassistant/components/habitica/translations/fr.json b/homeassistant/components/habitica/translations/fr.json new file mode 100644 index 00000000000000..00fcd36a50821f --- /dev/null +++ b/homeassistant/components/habitica/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 API", + "api_user": "ID utilisateur de l'API d'Habitica", + "name": "Remplacez le nom d\u2019utilisateur d\u2019Habitica. Sera utilis\u00e9 pour les appels de service", + "url": "URL" + }, + "description": "Connectez votre profil Habitica pour permettre la surveillance du profil et des t\u00e2ches de votre utilisateur. Notez que api_id et api_key doivent \u00eatre obtenus de https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ru.json b/homeassistant/components/habitica/translations/ru.json new file mode 100644 index 00000000000000..b3e81a349975a8 --- /dev/null +++ b/homeassistant/components/habitica/translations/ru.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "api_user": "ID \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f API Habitica", + "name": "\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Habitica. \u0411\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u043e\u0432 \u0441\u043b\u0443\u0436\u0431", + "url": "URL-\u0430\u0434\u0440\u0435\u0441" + }, + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c Habitica, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u0412\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e api_id \u0438 api_key \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b \u0441 https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/fr.json b/homeassistant/components/hyperion/translations/fr.json index 4b374f097a4ae6..f69fd6acdc63e1 100644 --- a/homeassistant/components/hyperion/translations/fr.json +++ b/homeassistant/components/hyperion/translations/fr.json @@ -2,11 +2,13 @@ "config": { "abort": { "already_configured": "Le service est d\u00e9ja configur\u00e9 ", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "auth_new_token_not_granted_error": "Le jeton nouvellement cr\u00e9\u00e9 n'a pas \u00e9t\u00e9 approuv\u00e9 sur l'interface utilisateur Hyperion", "auth_new_token_not_work_error": "\u00c9chec de l'authentification \u00e0 l'aide du jeton nouvellement cr\u00e9\u00e9", "auth_required_error": "Impossible de d\u00e9terminer si une autorisation est requise", "cannot_connect": "Echec de connection", - "no_id": "L'instance Hyperion Ambilight n'a pas signal\u00e9 son identifiant" + "no_id": "L'instance Hyperion Ambilight n'a pas signal\u00e9 son identifiant", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "cannot_connect": "Echec de la connexion ", diff --git a/homeassistant/components/keenetic_ndms2/translations/fr.json b/homeassistant/components/keenetic_ndms2/translations/fr.json new file mode 100644 index 00000000000000..2ac19dcdc64aca --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/fr.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "name": "Nom", + "password": "Mot de passe", + "port": "Port", + "username": "Nom d'utilisateur" + }, + "title": "Configurer le routeur Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Consid\u00e9rez l'intervalle de home assistant", + "include_arp": "Utiliser les donn\u00e9es ARP (ignor\u00e9es si les donn\u00e9es du hotspot sont utilis\u00e9es)", + "include_associated": "Utiliser les donn\u00e9es d'associations WiFi AP (ignor\u00e9es si les donn\u00e9es du hotspot sont utilis\u00e9es)", + "interfaces": "Choisissez les interfaces \u00e0 analyser", + "scan_interval": "Intervalle d\u2019analyse", + "try_hotspot": "Utiliser les donn\u00e9es 'ip hotspot' (plus pr\u00e9cis)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/fr.json b/homeassistant/components/kulersky/translations/fr.json index 649a3d387bd1f2..42f356ac365fdb 100644 --- a/homeassistant/components/kulersky/translations/fr.json +++ b/homeassistant/components/kulersky/translations/fr.json @@ -3,6 +3,11 @@ "abort": { "no_devices_found": "Aucun appareil n'a \u00e9t\u00e9 d\u00e9tect\u00e9 sur le r\u00e9seau", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Seulement une seule configuration est possible " + }, + "step": { + "confirm": { + "description": "Voulez-vous commencer la configuration ?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/fr.json b/homeassistant/components/local_ip/translations/fr.json index c1933032ed0a27..1c5a8fc963454f 100644 --- a/homeassistant/components/local_ip/translations/fr.json +++ b/homeassistant/components/local_ip/translations/fr.json @@ -8,6 +8,7 @@ "data": { "name": "Nom du capteur" }, + "description": "Voulez-vous commencer la configuration ?", "title": "Adresse IP locale" } } diff --git a/homeassistant/components/lutron_caseta/translations/fr.json b/homeassistant/components/lutron_caseta/translations/fr.json index 4d7dccd0acf018..ff561548b44deb 100644 --- a/homeassistant/components/lutron_caseta/translations/fr.json +++ b/homeassistant/components/lutron_caseta/translations/fr.json @@ -42,6 +42,11 @@ "group_1_button_2": "Premier groupe deuxi\u00e8me bouton", "group_2_button_1": "Premier bouton du deuxi\u00e8me groupe", "group_2_button_2": "Deuxi\u00e8me bouton du deuxi\u00e8me groupe", + "lower": "Bas", + "lower_1": "Bas 1", + "lower_2": "Bas 2", + "lower_3": "Bas 3", + "lower_4": "Bas 4", "lower_all": "Tout baisser", "off": "Eteint", "on": "Allumer", @@ -50,6 +55,11 @@ "open_3": "Ouvrir 3", "open_4": "Ouvrir 4", "open_all": "Ouvre tout", + "raise": "Haut", + "raise_1": "Haut 1", + "raise_2": "Haut 2", + "raise_3": "Haut 3", + "raise_4": "Haut 4", "raise_all": "Lever tout", "stop": "Stop (favori)", "stop_1": "Arr\u00eat 1", diff --git a/homeassistant/components/lyric/translations/fr.json b/homeassistant/components/lyric/translations/fr.json index 794e85b7fa6624..540d3e1e6c2956 100644 --- a/homeassistant/components/lyric/translations/fr.json +++ b/homeassistant/components/lyric/translations/fr.json @@ -1,7 +1,16 @@ { "config": { "abort": { + "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." + }, + "create_entry": { + "default": "Authentification r\u00e9ussie" + }, + "step": { + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + } } } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/fr.json b/homeassistant/components/mazda/translations/fr.json index e9ccb013b5eef2..aa1ea252c0cd4a 100644 --- a/homeassistant/components/mazda/translations/fr.json +++ b/homeassistant/components/mazda/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Le compte est d\u00e9ja configur\u00e9" + "already_configured": "Le compte est d\u00e9ja configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "account_locked": "Compte bloqu\u00e9. Veuillez r\u00e9essayer plus tard.", diff --git a/homeassistant/components/media_player/translations/fr.json b/homeassistant/components/media_player/translations/fr.json index f3992f7461614d..9ecdd19037f8ca 100644 --- a/homeassistant/components/media_player/translations/fr.json +++ b/homeassistant/components/media_player/translations/fr.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} est activ\u00e9", "is_paused": "{entity_name} est en pause", "is_playing": "{entity_name} joue" + }, + "trigger_type": { + "idle": "{entity_name} devient inactif", + "paused": "{entity_name} est mis en pause", + "playing": "{entity_name} commence \u00e0 jouer", + "turned_off": "{entity_name} d\u00e9sactiv\u00e9", + "turned_on": "{entity_name} activ\u00e9" } }, "state": { diff --git a/homeassistant/components/motion_blinds/translations/fr.json b/homeassistant/components/motion_blinds/translations/fr.json index da8abbcc5645cf..b6715970e40a90 100644 --- a/homeassistant/components/motion_blinds/translations/fr.json +++ b/homeassistant/components/motion_blinds/translations/fr.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9ja configur\u00e9 ", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "connection_error": "\u00c9chec de la connexion " }, "error": { diff --git a/homeassistant/components/mysensors/translations/fr.json b/homeassistant/components/mysensors/translations/fr.json new file mode 100644 index 00000000000000..00f9831c035b3f --- /dev/null +++ b/homeassistant/components/mysensors/translations/fr.json @@ -0,0 +1,79 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion", + "duplicate_persistence_file": "Fichier de persistance d\u00e9j\u00e0 utilis\u00e9", + "duplicate_topic": "Sujet d\u00e9j\u00e0 utilis\u00e9", + "invalid_auth": "Authentification invalide", + "invalid_device": "Appareil non valide", + "invalid_ip": "Adresse IP non valide", + "invalid_persistence_file": "Fichier de persistance non valide", + "invalid_port": "Num\u00e9ro de port non valide", + "invalid_publish_topic": "Sujet de publication non valide", + "invalid_serial": "Port s\u00e9rie non valide", + "invalid_subscribe_topic": "Sujet d'abonnement non valide", + "invalid_version": "Version de MySensors non valide", + "not_a_number": "Veuillez saisir un nombre", + "port_out_of_range": "Le num\u00e9ro de port doit \u00eatre au moins 1 et au plus 65535", + "same_topic": "Les sujets de souscription et de publication sont identiques", + "unknown": "Erreur inattendue" + }, + "error": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion", + "duplicate_persistence_file": "Fichier de persistance d\u00e9j\u00e0 utilis\u00e9", + "duplicate_topic": "Sujet d\u00e9j\u00e0 utilis\u00e9", + "invalid_auth": "Authentification invalide", + "invalid_device": "Appareil non valide", + "invalid_ip": "Adresse IP non valide", + "invalid_persistence_file": "Fichier de persistance non valide", + "invalid_port": "Num\u00e9ro de port non valide", + "invalid_publish_topic": "Sujet de publication non valide", + "invalid_serial": "Port s\u00e9rie non valide", + "invalid_subscribe_topic": "Sujet d'abonnement non valide", + "invalid_version": "Version de MySensors non valide", + "not_a_number": "Veuillez saisir un nombre", + "port_out_of_range": "Le num\u00e9ro de port doit \u00eatre au moins 1 et au plus 65535", + "same_topic": "Les sujets de souscription et de publication sont identiques", + "unknown": "Erreur inattendue" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "fichier de persistance (laissez vide pour g\u00e9n\u00e9rer automatiquement)", + "retain": "mqtt conserver", + "topic_in_prefix": "pr\u00e9fixe pour les sujets d\u2019entr\u00e9e (topic_in_prefix)", + "topic_out_prefix": "pr\u00e9fixe pour les sujets de sortie (topic_out_prefix)", + "version": "Version de MySensors" + }, + "description": "Configuration de la passerelle MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "d\u00e9bit en bauds", + "device": "Port s\u00e9rie", + "persistence_file": "fichier de persistance (laissez vide pour g\u00e9n\u00e9rer automatiquement)", + "version": "Version de MySensors" + }, + "description": "Configuration de la passerelle s\u00e9rie" + }, + "gw_tcp": { + "data": { + "device": "Adresse IP de la passerelle", + "persistence_file": "fichier de persistance (laisser vide pour g\u00e9n\u00e9rer automatiquement)", + "tcp_port": "port", + "version": "Version de MySensors" + }, + "description": "Configuration de la passerelle Ethernet" + }, + "user": { + "data": { + "gateway_type": "Type de passerelle" + }, + "description": "Choisissez la m\u00e9thode de connexion \u00e0 la passerelle" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/fr.json b/homeassistant/components/neato/translations/fr.json index 4b71a93a783903..26b97e83c0bf55 100644 --- a/homeassistant/components/neato/translations/fr.json +++ b/homeassistant/components/neato/translations/fr.json @@ -2,8 +2,11 @@ "config": { "abort": { "already_configured": "D\u00e9j\u00e0 configur\u00e9", + "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "invalid_auth": "Authentification invalide", - "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation " + "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation ", + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "create_entry": { "default": "Voir [Documentation Neato]({docs_url})." @@ -13,6 +16,12 @@ "unknown": "Erreur inattendue" }, "step": { + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + }, + "reauth_confirm": { + "title": "Voulez-vous commencer la configuration ?" + }, "user": { "data": { "password": "Mot de passe", diff --git a/homeassistant/components/nest/translations/fr.json b/homeassistant/components/nest/translations/fr.json index 0d1f5b761f569f..2830bf5da8771e 100644 --- a/homeassistant/components/nest/translations/fr.json +++ b/homeassistant/components/nest/translations/fr.json @@ -5,6 +5,7 @@ "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation.", "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "unknown_authorize_url_generation": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation." }, @@ -36,7 +37,8 @@ "title": "S\u00e9lectionner une m\u00e9thode d'authentification" }, "reauth_confirm": { - "description": "L'int\u00e9gration Nest doit r\u00e9-authentifier votre compte" + "description": "L'int\u00e9gration Nest doit r\u00e9-authentifier votre compte", + "title": "R\u00e9-authentifier l'int\u00e9gration" } } }, diff --git a/homeassistant/components/nuki/translations/fr.json b/homeassistant/components/nuki/translations/fr.json index 26a949038d5681..035c07325766cd 100644 --- a/homeassistant/components/nuki/translations/fr.json +++ b/homeassistant/components/nuki/translations/fr.json @@ -2,7 +2,8 @@ "config": { "error": { "cannot_connect": "\u00c9chec de la connexion ", - "invalid_auth": "Authentification invalide " + "invalid_auth": "Authentification invalide ", + "unknown": "Erreur inattendue" }, "step": { "user": { diff --git a/homeassistant/components/ondilo_ico/translations/fr.json b/homeassistant/components/ondilo_ico/translations/fr.json index 33271e594a3a67..c05fc0caaa6222 100644 --- a/homeassistant/components/ondilo_ico/translations/fr.json +++ b/homeassistant/components/ondilo_ico/translations/fr.json @@ -1,3 +1,17 @@ { + "config": { + "abort": { + "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", + "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." + }, + "create_entry": { + "default": "Authentification r\u00e9ussie" + }, + "step": { + "pick_implementation": { + "title": "S\u00e9lectionner une m\u00e9thode d'authentification" + } + } + }, "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/fr.json b/homeassistant/components/ozw/translations/fr.json index 5eed478549df05..bf4ba5c699541a 100644 --- a/homeassistant/components/ozw/translations/fr.json +++ b/homeassistant/components/ozw/translations/fr.json @@ -5,6 +5,7 @@ "addon_install_failed": "\u00c9chec de l\u2019installation de l'add-on OpenZWave.", "addon_set_config_failed": "\u00c9chec de la configuration OpenZWave.", "already_configured": "Cet appareil est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "mqtt_required": "L'int\u00e9gration MQTT n'est pas configur\u00e9e", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json new file mode 100644 index 00000000000000..9ae65c18fa408e --- /dev/null +++ b/homeassistant/components/philips_js/translations/fr.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_version": "Version de l'API", + "host": "H\u00f4te" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Il a \u00e9t\u00e9 demand\u00e9 \u00e0 l'appareil de s'allumer" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index 38a86f05d110b8..8016cd123710a2 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -17,7 +17,7 @@ "ip_address": "Adre\u00e7a IP", "password": "Contrasenya" }, - "description": "La contrasenya normalment s\u00f3n els darrers cinc car\u00e0cters del n\u00famero de s\u00e8rie de la pasarel\u00b7la de control i es pot trobar a l'aplicaci\u00f3 de Tesla; tamb\u00e9 pot consistir en els darrers 5 car\u00e0cters de la contrasenya que es troba a l'interior de la tapa de la pasarel\u00b7la de control 2.", + "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": "Connexi\u00f3 amb el Powerwall" } } diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index ae8122589bec27..06fc09804d92ce 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -17,7 +17,7 @@ "ip_address": "IP Address", "password": "Password" }, - "description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Telsa app; or the last 5 characters of the password found inside the door for Backup Gateway 2.", + "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": "Connect to the powerwall" } } diff --git a/homeassistant/components/powerwall/translations/fr.json b/homeassistant/components/powerwall/translations/fr.json index 2086393dfefdf8..3bfd70cd44c421 100644 --- a/homeassistant/components/powerwall/translations/fr.json +++ b/homeassistant/components/powerwall/translations/fr.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Le Powerwall est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Le Powerwall est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", + "invalid_auth": "Authentification invalide", "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." }, @@ -12,8 +14,10 @@ "step": { "user": { "data": { - "ip_address": "Adresse IP" + "ip_address": "Adresse IP", + "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": "Connectez-vous au Powerwall" } } diff --git a/homeassistant/components/roku/translations/fr.json b/homeassistant/components/roku/translations/fr.json index 6d2379925921eb..b3dc08a7dc86d9 100644 --- a/homeassistant/components/roku/translations/fr.json +++ b/homeassistant/components/roku/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", "unknown": "Erreur inattendue" }, "error": { diff --git a/homeassistant/components/shelly/translations/fr.json b/homeassistant/components/shelly/translations/fr.json index 0dea9111c628ec..e4bdc99db1ec32 100644 --- a/homeassistant/components/shelly/translations/fr.json +++ b/homeassistant/components/shelly/translations/fr.json @@ -12,7 +12,7 @@ "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "description": "Voulez-vous configurer le {model} \u00e0 {host}?" + "description": "Voulez-vous configurer le {model} \u00e0 {host}?\n\nLes appareils aliment\u00e9s par batterie prot\u00e9g\u00e9s par mot de passe doivent \u00eatre r\u00e9veill\u00e9s avant de continuer \u00e0 s\u2019installer.\nLes appareils aliment\u00e9s par batterie qui ne sont pas prot\u00e9g\u00e9s par mot de passe seront ajout\u00e9s lorsque l\u2019appareil se r\u00e9veillera, vous pouvez maintenant r\u00e9veiller manuellement l\u2019appareil \u00e0 l\u2019aide d\u2019un bouton dessus ou attendre la prochaine mise \u00e0 jour des donn\u00e9es de l\u2019appareil." }, "credentials": { "data": { @@ -24,7 +24,7 @@ "data": { "host": "H\u00f4te" }, - "description": "Avant la configuration, l'appareil aliment\u00e9 par batterie doit \u00eatre r\u00e9veill\u00e9 en appuyant sur le bouton de l'appareil." + "description": "Avant la configuration, les appareils aliment\u00e9s par batterie doivent \u00eatre r\u00e9veill\u00e9s, vous pouvez maintenant r\u00e9veiller l'appareil \u00e0 l'aide d'un bouton dessus." } } }, @@ -38,6 +38,7 @@ "trigger_type": { "double": "{subtype} double-cliqu\u00e9", "long": " {sous-type} long cliqu\u00e9", + "long_single": "{subtype} clic long et simple clic", "single": "{subtype} simple clic", "single_long": "{subtype} simple clic, puis un clic long", "triple": "{subtype} cliqu\u00e9 trois fois" diff --git a/homeassistant/components/smarttub/translations/fr.json b/homeassistant/components/smarttub/translations/fr.json new file mode 100644 index 00000000000000..15dfa04fc7876f --- /dev/null +++ b/homeassistant/components/smarttub/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a \u00e9t\u00e9 un succ\u00e8s" + }, + "error": { + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Mot de passe" + }, + "description": "Entrez votre adresse e-mail et votre mot de passe SmartTub pour vous connecter", + "title": "Connexion" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/ru.json b/homeassistant/components/smarttub/translations/ru.json new file mode 100644 index 00000000000000..67e055a32c5efe --- /dev/null +++ b/homeassistant/components/smarttub/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "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": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u0414\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c SmartTub.", + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/fr.json b/homeassistant/components/tesla/translations/fr.json index 6134ff25f6b526..889c32a7d911f3 100644 --- a/homeassistant/components/tesla/translations/fr.json +++ b/homeassistant/components/tesla/translations/fr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" + }, "error": { "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", "cannot_connect": "\u00c9chec de connexion", diff --git a/homeassistant/components/tuya/translations/fr.json b/homeassistant/components/tuya/translations/fr.json index 9ef1c325d1e7a2..1681343f3b756c 100644 --- a/homeassistant/components/tuya/translations/fr.json +++ b/homeassistant/components/tuya/translations/fr.json @@ -40,8 +40,10 @@ "max_temp": "Temp\u00e9rature cible maximale (utilisez min et max = 0 par d\u00e9faut)", "min_kelvin": "Temp\u00e9rature de couleur minimale prise en charge en kelvin", "min_temp": "Temp\u00e9rature cible minimale (utilisez min et max = 0 par d\u00e9faut)", + "set_temp_divided": "Utilisez la valeur de temp\u00e9rature divis\u00e9e pour la commande de temp\u00e9rature d\u00e9finie", "support_color": "Forcer la prise en charge des couleurs", "temp_divider": "Diviseur de valeurs de temp\u00e9rature (0 = utiliser la valeur par d\u00e9faut)", + "temp_step_override": "Pas de temp\u00e9rature cible", "tuya_max_coltemp": "Temp\u00e9rature de couleur maximale rapport\u00e9e par l'appareil", "unit_of_measurement": "Unit\u00e9 de temp\u00e9rature utilis\u00e9e par l'appareil" }, diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index 49d9c68c01c06c..d750fb0cdd94e6 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9", - "configuration_updated": "Configuration mise \u00e0 jour." + "configuration_updated": "Configuration mise \u00e0 jour.", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "faulty_credentials": "Authentification invalide", diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json index 84849041c8c64e..10ce9972818d1c 100644 --- a/homeassistant/components/xiaomi_miio/translations/fr.json +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", - "no_device_selected": "Aucun appareil s\u00e9lectionn\u00e9, veuillez s\u00e9lectionner un appareil." + "no_device_selected": "Aucun appareil s\u00e9lectionn\u00e9, veuillez s\u00e9lectionner un appareil.", + "unknown_device": "Le mod\u00e8le d'appareil n'est pas connu, impossible de configurer l'appareil \u00e0 l'aide du flux de configuration." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "Adresse IP", + "name": "Nom de l'appareil", + "token": "Jeton d'API" + }, + "description": "Vous aurez besoin des 32 caract\u00e8res Jeton d'API , voir https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token pour les instructions. Veuillez noter que cette Jeton d'API est diff\u00e9rente de la cl\u00e9 utilis\u00e9e par l'int\u00e9gration Xiaomi Aqara.", + "title": "Connectez-vous \u00e0 un appareil Xiaomi Miio ou \u00e0 une passerelle Xiaomi" + }, "gateway": { "data": { "host": "Adresse IP", diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index e52552fa9860cb..2196ed0259e05f 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -7,6 +7,7 @@ "addon_missing_discovery_info": "Informations manquantes sur la d\u00e9couverte du module compl\u00e9mentaire Z-Wave JS.", "addon_set_config_failed": "\u00c9chec de la d\u00e9finition de la configuration Z-Wave JS.", "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "\u00c9chec de la connexion " }, "error": { @@ -39,7 +40,8 @@ }, "start_addon": { "data": { - "network_key": "Cl\u00e9 r\u00e9seau" + "network_key": "Cl\u00e9 r\u00e9seau", + "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" }, "title": "Entrez la configuration du module compl\u00e9mentaire Z-Wave JS" }, From da51e2351405b02c36dcfeaca9056f775b870070 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Thu, 18 Feb 2021 16:18:02 -0800 Subject: [PATCH 0588/1818] Address late smarttub review (#46703) * _config -> config * remove unused string * remove entity tests * replace unit tests with integration tests using the core * refactor polling to use asyncio.gather * remove redundant component init * remove gather in favor of simple loop * use async_fire_time_changed instead of async_update_entity * use hass.config_entries.async_setup instead of calling smarttub.async_setup_entry directly * replace stray smarttub.async_setup_entry call * async_unload_entry -> hass.config_entries.async_unload * remove broken test --- homeassistant/components/smarttub/__init__.py | 2 +- .../components/smarttub/controller.py | 5 ++- .../components/smarttub/strings.json | 3 +- .../components/smarttub/translations/en.json | 3 +- tests/components/smarttub/conftest.py | 27 ++++-------- tests/components/smarttub/test_climate.py | 28 +++++++++++-- tests/components/smarttub/test_controller.py | 37 ---------------- tests/components/smarttub/test_entity.py | 18 -------- tests/components/smarttub/test_init.py | 42 +++++++++---------- 9 files changed, 58 insertions(+), 107 deletions(-) delete mode 100644 tests/components/smarttub/test_controller.py delete mode 100644 tests/components/smarttub/test_entity.py diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py index 85298a75f65258..4700b0df4de619 100644 --- a/homeassistant/components/smarttub/__init__.py +++ b/homeassistant/components/smarttub/__init__.py @@ -10,7 +10,7 @@ PLATFORMS = ["climate"] -async def async_setup(hass, _config): +async def async_setup(hass, config): """Set up smarttub component.""" hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index 31fe71d14b6052..ad40c94fbed312 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -79,12 +79,15 @@ async def async_update_data(self): try: async with async_timeout.timeout(POLLING_TIMEOUT): for spa in self.spas: - data[spa.id] = {"status": await spa.get_status()} + data[spa.id] = await self._get_spa_data(spa) except APIError as err: raise UpdateFailed(err) from err return data + async def _get_spa_data(self, spa): + return {"status": await spa.get_status()} + async def async_register_devices(self, entry): """Register devices with the device registry for all spas.""" device_registry = await dr.async_get_registry(self._hass) diff --git a/homeassistant/components/smarttub/strings.json b/homeassistant/components/smarttub/strings.json index 0d52673a469cb5..8ba888a9ffb3a1 100644 --- a/homeassistant/components/smarttub/strings.json +++ b/homeassistant/components/smarttub/strings.json @@ -11,8 +11,7 @@ } }, "error": { - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", diff --git a/homeassistant/components/smarttub/translations/en.json b/homeassistant/components/smarttub/translations/en.json index 4cf930918875b4..b013f816559dc2 100644 --- a/homeassistant/components/smarttub/translations/en.json +++ b/homeassistant/components/smarttub/translations/en.json @@ -5,8 +5,7 @@ "reauth_successful": "Re-authentication was successful" }, "error": { - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" + "invalid_auth": "Invalid authentication" }, "step": { "user": { diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index fec74a2b30add3..3519d4f85ce498 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -6,8 +6,8 @@ import smarttub from homeassistant.components.smarttub.const import DOMAIN -from homeassistant.components.smarttub.controller import SmartTubController from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -28,6 +28,12 @@ def config_entry(config_data): ) +@pytest.fixture +async def setup_component(hass): + """Set up the component.""" + assert await async_setup_component(hass, DOMAIN, {}) is True + + @pytest.fixture(name="spa") def mock_spa(): """Mock a SmartTub.Spa.""" @@ -65,22 +71,3 @@ def mock_api(account, spa): api_mock = api_class_mock.return_value api_mock.get_account.return_value = account yield api_mock - - -@pytest.fixture -async def controller(smarttub_api, hass, config_entry): - """Instantiate controller for testing.""" - - controller = SmartTubController(hass) - assert len(controller.spas) == 0 - assert await controller.async_setup_entry(config_entry) - - assert len(controller.spas) > 0 - - return controller - - -@pytest.fixture -async def coordinator(controller): - """Provide convenient access to the coordinator via the controller.""" - return controller.coordinator diff --git a/tests/components/smarttub/test_climate.py b/tests/components/smarttub/test_climate.py index f0e6ced4abd2a8..ad47e3ede04ecf 100644 --- a/tests/components/smarttub/test_climate.py +++ b/tests/components/smarttub/test_climate.py @@ -1,5 +1,9 @@ """Test the SmartTub climate platform.""" +from datetime import timedelta + +import smarttub + from homeassistant.components.climate.const import ( ATTR_CURRENT_TEMPERATURE, ATTR_HVAC_ACTION, @@ -15,15 +19,22 @@ SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.components.smarttub.const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP +from homeassistant.components.smarttub.const import ( + DEFAULT_MAX_TEMP, + DEFAULT_MIN_TEMP, + SCAN_INTERVAL, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, ) +from homeassistant.util import dt + +from tests.common import async_fire_time_changed -async def test_thermostat(coordinator, spa, hass, config_entry): +async def test_thermostat_update(spa, hass, config_entry, smarttub_api): """Test the thermostat entity.""" spa.get_status.return_value = { @@ -44,7 +55,7 @@ async def test_thermostat(coordinator, spa, hass, config_entry): assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT spa.get_status.return_value["heater"] = "OFF" - await hass.helpers.entity_component.async_update_entity(entity_id) + await trigger_update(hass) state = hass.states.get(entity_id) assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE @@ -72,3 +83,14 @@ async def test_thermostat(coordinator, spa, hass, config_entry): blocking=True, ) # does nothing + + spa.get_status.side_effect = smarttub.APIError + await trigger_update(hass) + # should not fail + + +async def trigger_update(hass): + """Trigger a polling update by moving time forward.""" + new_time = dt.utcnow() + timedelta(seconds=SCAN_INTERVAL + 1) + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() diff --git a/tests/components/smarttub/test_controller.py b/tests/components/smarttub/test_controller.py deleted file mode 100644 index e59ad86c09e130..00000000000000 --- a/tests/components/smarttub/test_controller.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Test the SmartTub controller.""" - -import pytest -import smarttub - -from homeassistant.components.smarttub.controller import SmartTubController -from homeassistant.helpers.update_coordinator import UpdateFailed - - -async def test_invalid_credentials(hass, controller, smarttub_api, config_entry): - """Check that we return False if the configured credentials are invalid. - - This should mean that the user changed their SmartTub password. - """ - - smarttub_api.login.side_effect = smarttub.LoginFailed - controller = SmartTubController(hass) - ret = await controller.async_setup_entry(config_entry) - assert ret is False - - -async def test_update(controller, spa): - """Test data updates from API.""" - data = await controller.async_update_data() - assert data[spa.id] == {"status": spa.get_status.return_value} - - spa.get_status.side_effect = smarttub.APIError - with pytest.raises(UpdateFailed): - data = await controller.async_update_data() - - -async def test_login(controller, smarttub_api, account): - """Test SmartTubController.login.""" - smarttub_api.get_account.return_value.id = "account-id1" - account = await controller.login("test-email1", "test-password1") - smarttub_api.login.assert_called() - assert account == account diff --git a/tests/components/smarttub/test_entity.py b/tests/components/smarttub/test_entity.py deleted file mode 100644 index 4a19b2650909dc..00000000000000 --- a/tests/components/smarttub/test_entity.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Test SmartTubEntity.""" - -from homeassistant.components.smarttub.entity import SmartTubEntity - - -async def test_entity(coordinator, spa): - """Test SmartTubEntity.""" - - entity = SmartTubEntity(coordinator, spa, "entity1") - - assert entity.device_info - assert entity.name - - coordinator.data[spa.id] = {} - assert entity.get_spa_status("foo") is None - coordinator.data[spa.id]["status"] = {"foo": "foo1", "bar": {"baz": "barbaz1"}} - assert entity.get_spa_status("foo") == "foo1" - assert entity.get_spa_status("bar.baz") == "barbaz1" diff --git a/tests/components/smarttub/test_init.py b/tests/components/smarttub/test_init.py index aa22780e9b8c02..13a447529d4d81 100644 --- a/tests/components/smarttub/test_init.py +++ b/tests/components/smarttub/test_init.py @@ -1,48 +1,50 @@ """Test smarttub setup process.""" import asyncio -from unittest.mock import patch -import pytest from smarttub import LoginFailed from homeassistant.components import smarttub -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.config_entries import ( + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) from homeassistant.setup import async_setup_component -async def test_setup_with_no_config(hass): +async def test_setup_with_no_config(setup_component, hass, smarttub_api): """Test that we do not discover anything.""" - assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True # No flows started assert len(hass.config_entries.flow.async_progress()) == 0 - assert smarttub.const.SMARTTUB_CONTROLLER not in hass.data[smarttub.DOMAIN] + smarttub_api.login.assert_not_called() -async def test_setup_entry_not_ready(hass, config_entry, smarttub_api): +async def test_setup_entry_not_ready(setup_component, hass, config_entry, smarttub_api): """Test setup when the entry is not ready.""" - assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True smarttub_api.login.side_effect = asyncio.TimeoutError - with pytest.raises(ConfigEntryNotReady): - await smarttub.async_setup_entry(hass, config_entry) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + assert config_entry.state == ENTRY_STATE_SETUP_RETRY -async def test_setup_auth_failed(hass, config_entry, smarttub_api): +async def test_setup_auth_failed(setup_component, hass, config_entry, smarttub_api): """Test setup when the credentials are invalid.""" - assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True smarttub_api.login.side_effect = LoginFailed - assert await smarttub.async_setup_entry(hass, config_entry) is False + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + assert config_entry.state == ENTRY_STATE_SETUP_ERROR -async def test_config_passed_to_config_entry(hass, config_entry, config_data): +async def test_config_passed_to_config_entry( + hass, config_entry, config_data, smarttub_api +): """Test that configured options are loaded via config entry.""" config_entry.add_to_hass(hass) - ret = await async_setup_component(hass, smarttub.DOMAIN, config_data) - assert ret is True + assert await async_setup_component(hass, smarttub.DOMAIN, config_data) async def test_unload_entry(hass, config_entry, smarttub_api): @@ -51,10 +53,4 @@ async def test_unload_entry(hass, config_entry, smarttub_api): assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True - assert await smarttub.async_unload_entry(hass, config_entry) - - # test failure of platform unload - assert await async_setup_component(hass, smarttub.DOMAIN, {}) is True - with patch.object(hass.config_entries, "async_forward_entry_unload") as mock: - mock.return_value = False - assert await smarttub.async_unload_entry(hass, config_entry) is False + assert await hass.config_entries.async_unload(config_entry.entry_id) From 5df46b60e82160abd103c4d4cd27353de8472536 Mon Sep 17 00:00:00 2001 From: shbatm Date: Thu, 18 Feb 2021 20:00:14 -0600 Subject: [PATCH 0589/1818] Fix flip-flopped substitutions in Custom Version Type Warning message. (#46768) --- homeassistant/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 152a3d88b803da..2ae279da79ea27 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -806,7 +806,7 @@ def custom_integration_warning(integration: Integration) -> None: if not validate_custom_integration_version(integration.manifest["version"]): _LOGGER.warning( CUSTOM_WARNING_VERSION_TYPE, - integration.domain, integration.manifest["version"], integration.domain, + integration.domain, ) From f2b303d5099f20db5b62a7954087df94e8b0c6e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 18 Feb 2021 21:05:09 -1000 Subject: [PATCH 0590/1818] Implement percentage step sizes for fans (#46512) Co-authored-by: Paulus Schoutsen --- homeassistant/components/bond/fan.py | 6 + homeassistant/components/comfoconnect/fan.py | 6 + homeassistant/components/demo/fan.py | 10 ++ homeassistant/components/dyson/fan.py | 6 + homeassistant/components/esphome/fan.py | 5 + homeassistant/components/fan/__init__.py | 67 ++++++++++ homeassistant/components/fan/services.yaml | 38 ++++++ homeassistant/components/isy994/fan.py | 17 ++- homeassistant/components/knx/fan.py | 8 ++ homeassistant/components/lutron_caseta/fan.py | 5 + homeassistant/components/ozw/fan.py | 6 + homeassistant/components/smartthings/fan.py | 6 + homeassistant/components/template/fan.py | 13 ++ homeassistant/components/tuya/fan.py | 7 + homeassistant/components/vesync/fan.py | 6 + homeassistant/components/wemo/fan.py | 6 + homeassistant/components/wilight/fan.py | 5 + homeassistant/components/zwave/fan.py | 6 + homeassistant/components/zwave_js/fan.py | 6 + homeassistant/util/percentage.py | 14 +- tests/components/demo/test_fan.py | 123 ++++++++++++++++++ tests/components/fan/common.py | 35 +++++ tests/components/fan/test_init.py | 8 ++ tests/components/template/test_fan.py | 41 ++++++ 24 files changed, 447 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 9b70195db5d631..cef2efae690a58 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -16,6 +16,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -85,6 +86,11 @@ def percentage(self) -> Optional[str]: return 0 return ranged_value_to_percentage(self._speed_range, self._speed) + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return int_states_in_range(self._speed_range) + @property def current_direction(self) -> Optional[str]: """Return fan rotation direction.""" diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index 7457d0ffad2817..26abd85522a7b9 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -13,6 +13,7 @@ from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -101,6 +102,11 @@ def percentage(self) -> str: return None return ranged_value_to_percentage(SPEED_RANGE, speed) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + def turn_on( self, speed: str = None, percentage=None, preset_mode=None, **kwargs ) -> None: diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index cb6036a8938abb..6bbd8b81f6dd90 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -215,6 +215,11 @@ def percentage(self) -> str: """Return the current speed.""" return self._percentage + @property + def speed_count(self) -> Optional[float]: + """Return the number of speeds the fan supports.""" + return 3 + def set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" self._percentage = percentage @@ -270,6 +275,11 @@ def percentage(self) -> str: """Return the current speed.""" return self._percentage + @property + def speed_count(self) -> Optional[float]: + """Return the number of speeds the fan supports.""" + return 3 + async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" self._percentage = percentage diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 7a403902ee8c64..9e49badbc8e57e 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -13,6 +13,7 @@ from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -154,6 +155,11 @@ def percentage(self): return None return ranged_value_to_percentage(SPEED_RANGE, int(self._device.state.speed)) + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def preset_modes(self): """Return the available preset modes.""" diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 8da52b8d584da6..092c416acabbd9 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -119,6 +119,11 @@ def percentage(self) -> Optional[str]: ORDERED_NAMED_FAN_SPEEDS, self._state.speed ) + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return len(ORDERED_NAMED_FAN_SPEEDS) + @esphome_state_property def oscillating(self) -> None: """Return the oscillation state.""" diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 8d6fcbea2c9658..692588cff480e5 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta import functools as ft import logging +import math from typing import List, Optional import voluptuous as vol @@ -23,6 +24,8 @@ from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, ) _LOGGER = logging.getLogger(__name__) @@ -39,6 +42,8 @@ SUPPORT_PRESET_MODE = 8 SERVICE_SET_SPEED = "set_speed" +SERVICE_INCREASE_SPEED = "increase_speed" +SERVICE_DECREASE_SPEED = "decrease_speed" SERVICE_OSCILLATE = "oscillate" SERVICE_SET_DIRECTION = "set_direction" SERVICE_SET_PERCENTAGE = "set_percentage" @@ -54,6 +59,7 @@ ATTR_SPEED = "speed" ATTR_PERCENTAGE = "percentage" +ATTR_PERCENTAGE_STEP = "percentage_step" ATTR_SPEED_LIST = "speed_list" ATTR_OSCILLATING = "oscillating" ATTR_DIRECTION = "direction" @@ -142,6 +148,26 @@ async def async_setup(hass, config: dict): "async_set_speed_deprecated", [SUPPORT_SET_SPEED], ) + component.async_register_entity_service( + SERVICE_INCREASE_SPEED, + { + vol.Optional(ATTR_PERCENTAGE_STEP): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100) + ) + }, + "async_increase_speed", + [SUPPORT_SET_SPEED], + ) + component.async_register_entity_service( + SERVICE_DECREASE_SPEED, + { + vol.Optional(ATTR_PERCENTAGE_STEP): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100) + ) + }, + "async_decrease_speed", + [SUPPORT_SET_SPEED], + ) component.async_register_entity_service( SERVICE_OSCILLATE, {vol.Required(ATTR_OSCILLATING): cv.boolean}, @@ -246,6 +272,33 @@ async def async_set_percentage(self, percentage: int) -> None: else: await self.async_set_speed(self.percentage_to_speed(percentage)) + async def async_increase_speed(self, percentage_step=None) -> None: + """Increase the speed of the fan.""" + await self._async_adjust_speed(1, percentage_step) + + async def async_decrease_speed(self, percentage_step=None) -> None: + """Decrease the speed of the fan.""" + await self._async_adjust_speed(-1, percentage_step) + + async def _async_adjust_speed(self, modifier, percentage_step) -> None: + """Increase or decrease the speed of the fan.""" + current_percentage = self.percentage or 0 + + if percentage_step is not None: + new_percentage = current_percentage + (percentage_step * modifier) + else: + speed_range = (1, self.speed_count) + speed_index = math.ceil( + percentage_to_ranged_value(speed_range, current_percentage) + ) + new_percentage = ranged_value_to_percentage( + speed_range, speed_index + modifier + ) + + new_percentage = max(0, min(100, new_percentage)) + + await self.async_set_percentage(new_percentage) + @_fan_native def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" @@ -408,6 +461,19 @@ def percentage(self) -> Optional[int]: return self.speed_to_percentage(self.speed) return 0 + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + speed_list = speed_list_without_preset_modes(self.speed_list) + if speed_list: + return len(speed_list) + return 100 + + @property + def percentage_step(self) -> Optional[float]: + """Return the step size for percentage.""" + return 100 / self.speed_count + @property def speed_list(self) -> list: """Get the list of available speeds.""" @@ -531,6 +597,7 @@ def state_attributes(self) -> dict: if supported_features & SUPPORT_SET_SPEED: data[ATTR_SPEED] = self.speed data[ATTR_PERCENTAGE] = self.percentage + data[ATTR_PERCENTAGE_STEP] = self.percentage_step if ( supported_features & SUPPORT_PRESET_MODE diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml index 2f5802b69f7df9..ad513b84e8f0fe 100644 --- a/homeassistant/components/fan/services.yaml +++ b/homeassistant/components/fan/services.yaml @@ -100,3 +100,41 @@ set_direction: options: - "forward" - "reverse" + +increase_speed: + description: Increase the speed of the fan by one speed or a percentage_step. + fields: + entity_id: + description: Name(s) of the entities to increase speed + example: "fan.living_room" + percentage_step: + advanced: true + required: false + description: Increase speed by a percentage. Should be between 0..100. [optional] + example: 50 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider + +decrease_speed: + description: Decrease the speed of the fan by one speed or a percentage_step. + fields: + entity_id: + description: Name(s) of the entities to decrease speed + example: "fan.living_room" + percentage_step: + advanced: true + required: false + description: Decrease speed by a percentage. Should be between 0..100. [optional] + example: 50 + selector: + number: + min: 0 + max: 100 + step: 1 + unit_of_measurement: "%" + mode: slider diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index 74ed477d3a7cdb..f565383f0078a4 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -2,12 +2,13 @@ import math from typing import Callable -from pyisy.constants import ISY_VALUE_UNKNOWN +from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON from homeassistant.components.fan import DOMAIN as FAN, SUPPORT_SET_SPEED, FanEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -48,6 +49,13 @@ def percentage(self) -> str: return None return ranged_value_to_percentage(SPEED_RANGE, self._node.status) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + if self._node.protocol == PROTO_INSTEON: + return 3 + return int_states_in_range(SPEED_RANGE) + @property def is_on(self) -> bool: """Get if the fan is on.""" @@ -95,6 +103,13 @@ def percentage(self) -> str: return None return ranged_value_to_percentage(SPEED_RANGE, self._node.status) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + if self._node.protocol == PROTO_INSTEON: + return 3 + return int_states_in_range(SPEED_RANGE) + @property def is_on(self) -> bool: """Get if the fan is on.""" diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index de08b576eddd78..d0b7b4c55466ba 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -7,6 +7,7 @@ from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -68,6 +69,13 @@ def percentage(self) -> Optional[int]: ) return self._device.current_speed + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + if self._step_range is None: + return super().speed_count + return int_states_in_range(self._step_range) + async def async_turn_on( self, speed: Optional[str] = None, diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 935c8827c84c11..330ff81d1d2cff 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -48,6 +48,11 @@ def percentage(self) -> str: ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return len(ORDERED_NAMED_FAN_SPEEDS) + @property def supported_features(self) -> int: """Flag supported features. Speed Only.""" diff --git a/homeassistant/components/ozw/fan.py b/homeassistant/components/ozw/fan.py index be0bd372b6583f..505959dd3431c5 100644 --- a/homeassistant/components/ozw/fan.py +++ b/homeassistant/components/ozw/fan.py @@ -9,6 +9,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -72,6 +73,11 @@ def percentage(self): """ return ranged_value_to_percentage(SPEED_RANGE, self.values.primary.value) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def supported_features(self): """Flag supported features.""" diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index b09bfe0ad46c2b..12edac36dfe8ac 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -6,6 +6,7 @@ from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -79,6 +80,11 @@ def percentage(self) -> str: """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed) + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index ac77b3dc33347a..18a7d8262e005f 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -46,6 +46,7 @@ CONF_FANS = "fans" CONF_SPEED_LIST = "speeds" +CONF_SPEED_COUNT = "speed_count" CONF_PRESET_MODES = "preset_modes" CONF_SPEED_TEMPLATE = "speed_template" CONF_PERCENTAGE_TEMPLATE = "percentage_template" @@ -86,6 +87,7 @@ vol.Optional(CONF_SET_PRESET_MODE_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_SET_OSCILLATING_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int), vol.Optional( CONF_SPEED_LIST, default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], @@ -126,6 +128,7 @@ async def _async_create_entities(hass, config): set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION) speed_list = device_config[CONF_SPEED_LIST] + speed_count = device_config.get(CONF_SPEED_COUNT) preset_modes = device_config.get(CONF_PRESET_MODES) unique_id = device_config.get(CONF_UNIQUE_ID) @@ -148,6 +151,7 @@ async def _async_create_entities(hass, config): set_preset_mode_action, set_oscillating_action, set_direction_action, + speed_count, speed_list, preset_modes, unique_id, @@ -185,6 +189,7 @@ def __init__( set_preset_mode_action, set_oscillating_action, set_direction_action, + speed_count, speed_list, preset_modes, unique_id, @@ -260,6 +265,9 @@ def __init__( self._unique_id = unique_id + # Number of valid speeds + self._speed_count = speed_count + # List of valid speeds self._speed_list = speed_list @@ -281,6 +289,11 @@ def supported_features(self) -> int: """Flag supported features.""" return self._supported_features + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return self._speed_count or super().speed_count + @property def speed_list(self) -> list: """Get the list of available speeds.""" diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index 12e963f05d3065..4c555bb942a5fc 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -102,6 +102,13 @@ def oscillate(self, oscillating) -> None: """Oscillate the fan.""" self._tuya.oscillate(oscillating) + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + if self.speeds is None: + return super().speed_count + return len(self.speeds) + @property def oscillating(self): """Return current oscillating status.""" diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index 10754007ce6f52..e9f421215fb347 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -6,6 +6,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -77,6 +78,11 @@ def percentage(self): return ranged_value_to_percentage(SPEED_RANGE, current_level) return None + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def preset_modes(self): """Get the list of available preset modes.""" diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 94dab468a6980c..1f45194659d499 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -10,6 +10,7 @@ from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -130,6 +131,11 @@ def percentage(self) -> str: """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._fan_mode) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index ece79874ccf394..d663dc39dedb28 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -87,6 +87,11 @@ def percentage(self) -> str: return None return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, wl_speed) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return len(ORDERED_NAMED_FAN_SPEEDS) + @property def current_direction(self) -> str: """Return the current direction of the fan.""" diff --git a/homeassistant/components/zwave/fan.py b/homeassistant/components/zwave/fan.py index ea529ccd90bd10..7fb0fb8e8bee5f 100644 --- a/homeassistant/components/zwave/fan.py +++ b/homeassistant/components/zwave/fan.py @@ -5,6 +5,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -68,6 +69,11 @@ def percentage(self): """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._state) + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def supported_features(self): """Flag supported features.""" diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index e957d774e56e51..ae903d9efaf4e0 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -13,6 +13,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -96,6 +97,11 @@ def percentage(self) -> Optional[int]: return None return ranged_value_to_percentage(SPEED_RANGE, self.info.primary_value.value) + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index fa4c9dcc252c32..10a72a85dff438 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -67,7 +67,7 @@ def ranged_value_to_percentage( (1,255), 127: 50 (1,255), 10: 4 """ - return int((value * 100) // (low_high_range[1] - low_high_range[0] + 1)) + return int((value * 100) // states_in_range(low_high_range)) def percentage_to_ranged_value( @@ -84,4 +84,14 @@ def percentage_to_ranged_value( (1,255), 50: 127.5 (1,255), 4: 10.2 """ - return (low_high_range[1] - low_high_range[0] + 1) * percentage / 100 + return states_in_range(low_high_range) * percentage / 100 + + +def states_in_range(low_high_range: Tuple[float, float]) -> float: + """Given a range of low and high values return how many states exist.""" + return low_high_range[1] - low_high_range[0] + 1 + + +def int_states_in_range(low_high_range: Tuple[float, float]) -> int: + """Given a range of low and high values return how many integer states exist.""" + return int(states_in_range(low_high_range)) diff --git a/tests/components/demo/test_fan.py b/tests/components/demo/test_fan.py index 2439d49685c8ab..a788e69b0d347a 100644 --- a/tests/components/demo/test_fan.py +++ b/tests/components/demo/test_fan.py @@ -27,6 +27,7 @@ FANS_WITH_PRESET_MODES = FULL_FAN_ENTITY_IDS + [ "fan.percentage_limited_fan", ] +PERCENTAGE_MODEL_FANS = ["fan.percentage_full_fan", "fan.percentage_limited_fan"] @pytest.fixture(autouse=True) @@ -397,6 +398,128 @@ async def test_set_percentage(hass, fan_entity_id): assert state.attributes[fan.ATTR_PERCENTAGE] == 33 +@pytest.mark.parametrize("fan_entity_id", LIMITED_AND_FULL_FAN_ENTITY_IDS) +async def test_increase_decrease_speed(hass, fan_entity_id): + """Test increasing and decreasing the percentage speed of the device.""" + state = hass.states.get(fan_entity_id) + assert state.state == STATE_OFF + assert state.attributes[fan.ATTR_PERCENTAGE_STEP] == 100 / 3 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH + assert state.attributes[fan.ATTR_PERCENTAGE] == 100 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH + assert state.attributes[fan.ATTR_PERCENTAGE] == 100 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_DECREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_DECREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_DECREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF + assert state.attributes[fan.ATTR_PERCENTAGE] == 0 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_DECREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF + assert state.attributes[fan.ATTR_PERCENTAGE] == 0 + + +@pytest.mark.parametrize("fan_entity_id", PERCENTAGE_MODEL_FANS) +async def test_increase_decrease_speed_with_percentage_step(hass, fan_entity_id): + """Test increasing speed with a percentage step.""" + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE_STEP: 25}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW + assert state.attributes[fan.ATTR_PERCENTAGE] == 25 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE_STEP: 25}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM + assert state.attributes[fan.ATTR_PERCENTAGE] == 50 + + await hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_INCREASE_SPEED, + {ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE_STEP: 25}, + blocking=True, + ) + state = hass.states.get(fan_entity_id) + assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH + assert state.attributes[fan.ATTR_PERCENTAGE] == 75 + + @pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS) async def test_oscillate(hass, fan_entity_id): """Test oscillating the fan.""" diff --git a/tests/components/fan/common.py b/tests/components/fan/common.py index 215849e6aabe5c..c32686b9311dfd 100644 --- a/tests/components/fan/common.py +++ b/tests/components/fan/common.py @@ -7,9 +7,12 @@ ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_PERCENTAGE, + ATTR_PERCENTAGE_STEP, ATTR_PRESET_MODE, ATTR_SPEED, DOMAIN, + SERVICE_DECREASE_SPEED, + SERVICE_INCREASE_SPEED, SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, SERVICE_SET_PERCENTAGE, @@ -106,6 +109,38 @@ async def async_set_percentage( await hass.services.async_call(DOMAIN, SERVICE_SET_PERCENTAGE, data, blocking=True) +async def async_increase_speed( + hass, entity_id=ENTITY_MATCH_ALL, percentage_step: int = None +) -> None: + """Increase speed for all or specified fan.""" + data = { + key: value + for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_PERCENTAGE_STEP, percentage_step), + ] + if value is not None + } + + await hass.services.async_call(DOMAIN, SERVICE_INCREASE_SPEED, data, blocking=True) + + +async def async_decrease_speed( + hass, entity_id=ENTITY_MATCH_ALL, percentage_step: int = None +) -> None: + """Decrease speed for all or specified fan.""" + data = { + key: value + for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_PERCENTAGE_STEP, percentage_step), + ] + if value is not None + } + + await hass.services.async_call(DOMAIN, SERVICE_DECREASE_SPEED, data, blocking=True) + + async def async_set_direction( hass, entity_id=ENTITY_MATCH_ALL, direction: str = None ) -> None: diff --git a/tests/components/fan/test_init.py b/tests/components/fan/test_init.py index f5c303bd416a8b..05ced3b8be760d 100644 --- a/tests/components/fan/test_init.py +++ b/tests/components/fan/test_init.py @@ -19,6 +19,8 @@ def test_fanentity(): assert len(fan.speed_list) == 0 assert len(fan.preset_modes) == 0 assert fan.supported_features == 0 + assert fan.percentage_step == 1 + assert fan.speed_count == 100 assert fan.capability_attributes == {} # Test set_speed not required with pytest.raises(NotImplementedError): @@ -43,6 +45,8 @@ async def test_async_fanentity(hass): assert len(fan.speed_list) == 0 assert len(fan.preset_modes) == 0 assert fan.supported_features == 0 + assert fan.percentage_step == 1 + assert fan.speed_count == 100 assert fan.capability_attributes == {} # Test set_speed not required with pytest.raises(NotImplementedError): @@ -57,3 +61,7 @@ async def test_async_fanentity(hass): await fan.async_turn_on() with pytest.raises(NotImplementedError): await fan.async_turn_off() + with pytest.raises(NotImplementedError): + await fan.async_increase_speed() + with pytest.raises(NotImplementedError): + await fan.async_decrease_speed() diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index b3927ad3118d04..2b9059017c6d50 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -203,6 +203,7 @@ async def test_templates_with_entities(hass, calls): "preset_mode_template": "{{ states('input_select.preset_mode') }}", "oscillating_template": "{{ states('input_select.osc') }}", "direction_template": "{{ states('input_select.direction') }}", + "speed_count": "3", "set_percentage": { "service": "script.fans_set_speed", "data_template": {"percentage": "{{ percentage }}"}, @@ -648,6 +649,46 @@ async def test_set_percentage(hass, calls): _verify(hass, STATE_ON, SPEED_MEDIUM, 50, None, None, None) +async def test_increase_decrease_speed(hass, calls): + """Test set valid increase and derease speed.""" + await _register_components(hass) + + # Turn on fan + await common.async_turn_on(hass, _TEST_FAN) + + # Set fan's percentage speed to 100 + await common.async_set_percentage(hass, _TEST_FAN, 100) + + # verify + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 100 + + _verify(hass, STATE_ON, SPEED_HIGH, 100, None, None, None) + + # Set fan's percentage speed to 66 + await common.async_decrease_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 66 + + _verify(hass, STATE_ON, SPEED_MEDIUM, 66, None, None, None) + + # Set fan's percentage speed to 33 + await common.async_decrease_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 33 + + _verify(hass, STATE_ON, SPEED_LOW, 33, None, None, None) + + # Set fan's percentage speed to 0 + await common.async_decrease_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 0 + + _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + + # Set fan's percentage speed to 33 + await common.async_increase_speed(hass, _TEST_FAN) + assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == 33 + + _verify(hass, STATE_ON, SPEED_LOW, 33, None, None, None) + + async def test_set_invalid_speed_from_initial_stage(hass, calls): """Test set invalid speed when fan is in initial state.""" await _register_components(hass) From 0ed0c7c02603e4917f1dcf52901cfcb022ac9e7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 18 Feb 2021 21:54:40 -1000 Subject: [PATCH 0591/1818] Fix backwards compatibility with vesync fans (#45950) --- homeassistant/components/vesync/fan.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index e9f421215fb347..1d1320d8d7832c 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -91,8 +91,8 @@ def preset_modes(self): @property def preset_mode(self): """Get the current preset mode.""" - if self.smartfan.mode == FAN_MODE_AUTO: - return FAN_MODE_AUTO + if self.smartfan.mode in (FAN_MODE_AUTO, FAN_MODE_SLEEP): + return self.smartfan.mode return None @property @@ -136,7 +136,11 @@ def set_preset_mode(self, preset_mode): if not self.smartfan.is_on: self.smartfan.turn_on() - self.smartfan.auto_mode() + if preset_mode == FAN_MODE_AUTO: + self.smartfan.auto_mode() + elif preset_mode == FAN_MODE_SLEEP: + self.smartfan.sleep_mode() + self.schedule_update_ha_state() def turn_on( @@ -150,4 +154,6 @@ def turn_on( if preset_mode: self.set_preset_mode(preset_mode) return + if percentage is None: + percentage = 50 self.set_percentage(percentage) From 40068c2f1bae65dd48e4be212ec03c0cfd3e1204 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Feb 2021 08:57:28 +0100 Subject: [PATCH 0592/1818] Bump actions/stale from v3.0.16 to v3.0.17 (#46777) --- .github/workflows/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index fd8ca4eb477771..a280d0ee89a8e4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: # - No PRs marked as no-stale # - No issues marked as no-stale or help-wanted - name: 90 days stale issues & PRs policy - uses: actions/stale@v3.0.16 + uses: actions/stale@v3.0.17 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 90 @@ -53,7 +53,7 @@ jobs: # - No PRs marked as no-stale or new-integrations # - No issues (-1) - name: 30 days stale PRs policy - uses: actions/stale@v3.0.16 + uses: actions/stale@v3.0.17 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -78,7 +78,7 @@ jobs: # - No Issues marked as no-stale or help-wanted # - No PRs (-1) - name: Needs more information stale issues policy - uses: actions/stale@v3.0.16 + uses: actions/stale@v3.0.17 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "needs-more-information" From e7e3e09063678e6154a1dc7cd83baae6c08bb576 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 19 Feb 2021 13:14:47 +0100 Subject: [PATCH 0593/1818] Raise ConditionError for zone errors (#46253) * Raise ConditionError for zone errors * Do not test missing state * Handle multiple entities/zones --- .../components/geo_location/trigger.py | 7 +- homeassistant/components/zone/trigger.py | 2 +- homeassistant/helpers/condition.py | 52 +++++++++--- tests/helpers/test_condition.py | 80 +++++++++++++++++++ 4 files changed, 125 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/geo_location/trigger.py b/homeassistant/components/geo_location/trigger.py index aad281da117355..9ca8c86e150f1f 100644 --- a/homeassistant/components/geo_location/trigger.py +++ b/homeassistant/components/geo_location/trigger.py @@ -48,8 +48,11 @@ def state_change_listener(event): return zone_state = hass.states.get(zone_entity_id) - from_match = condition.zone(hass, zone_state, from_state) - to_match = condition.zone(hass, zone_state, to_state) + + from_match = ( + condition.zone(hass, zone_state, from_state) if from_state else False + ) + to_match = condition.zone(hass, zone_state, to_state) if to_state else False if ( trigger_event == EVENT_ENTER diff --git a/homeassistant/components/zone/trigger.py b/homeassistant/components/zone/trigger.py index bc827c2ba0d70a..a5f89a7515d2c0 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -58,7 +58,7 @@ def zone_automation_listener(zone_event): zone_state = hass.states.get(zone_entity_id) from_match = condition.zone(hass, zone_state, from_s) if from_s else False - to_match = condition.zone(hass, zone_state, to_s) + to_match = condition.zone(hass, zone_state, to_s) if to_s else False if ( event == EVENT_ENTER diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 2f63498c9cdb94..e09176dc098d26 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -588,23 +588,36 @@ def zone( Async friendly. """ + if zone_ent is None: + raise ConditionError("No zone specified") + if isinstance(zone_ent, str): + zone_ent_id = zone_ent zone_ent = hass.states.get(zone_ent) - if zone_ent is None: - return False + if zone_ent is None: + raise ConditionError(f"Unknown zone {zone_ent_id}") + + if entity is None: + raise ConditionError("No entity specified") if isinstance(entity, str): + entity_id = entity entity = hass.states.get(entity) - if entity is None: - return False + if entity is None: + raise ConditionError(f"Unknown entity {entity_id}") + else: + entity_id = entity.entity_id latitude = entity.attributes.get(ATTR_LATITUDE) longitude = entity.attributes.get(ATTR_LONGITUDE) - if latitude is None or longitude is None: - return False + if latitude is None: + raise ConditionError(f"Entity {entity_id} has no 'latitude' attribute") + + if longitude is None: + raise ConditionError(f"Entity {entity_id} has no 'longitude' attribute") return zone_cmp.in_zone( zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0) @@ -622,13 +635,26 @@ def zone_from_config( def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" - return all( - any( - zone(hass, zone_entity_id, entity_id) - for zone_entity_id in zone_entity_ids - ) - for entity_id in entity_ids - ) + errors = [] + + all_ok = True + for entity_id in entity_ids: + entity_ok = False + for zone_entity_id in zone_entity_ids: + try: + if zone(hass, zone_entity_id, entity_id): + entity_ok = True + except ConditionError as ex: + errors.append(str(ex)) + + if not entity_ok: + all_ok = False + + # Raise the errors only if no definitive result was found + if errors and not all_ok: + raise ConditionError("Error in 'zone' condition: " + ", ".join(errors)) + + return all_ok return if_in_zone diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 1fc1e07da5dfaf..0db8220bc5056b 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -811,6 +811,86 @@ async def test_numeric_state_using_input_number(hass): ) +async def test_zone_raises(hass): + """Test that zone raises ConditionError on errors.""" + test = await condition.async_from_config( + hass, + { + "condition": "zone", + "entity_id": "device_tracker.cat", + "zone": "zone.home", + }, + ) + + with pytest.raises(ConditionError, match="Unknown zone"): + test(hass) + + hass.states.async_set( + "zone.home", + "zoning", + {"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10}, + ) + + with pytest.raises(ConditionError, match="Unknown entity"): + test(hass) + + hass.states.async_set( + "device_tracker.cat", + "home", + {"friendly_name": "cat"}, + ) + + with pytest.raises(ConditionError, match="latitude"): + test(hass) + + hass.states.async_set( + "device_tracker.cat", + "home", + {"friendly_name": "cat", "latitude": 2.1}, + ) + + with pytest.raises(ConditionError, match="longitude"): + test(hass) + + hass.states.async_set( + "device_tracker.cat", + "home", + {"friendly_name": "cat", "latitude": 2.1, "longitude": 1.1}, + ) + + # All okay, now test multiple failed conditions + assert test(hass) + + test = await condition.async_from_config( + hass, + { + "condition": "zone", + "entity_id": ["device_tracker.cat", "device_tracker.dog"], + "zone": ["zone.home", "zone.work"], + }, + ) + + with pytest.raises(ConditionError, match="dog"): + test(hass) + + with pytest.raises(ConditionError, match="work"): + test(hass) + + hass.states.async_set( + "zone.work", + "zoning", + {"name": "work", "latitude": 20, "longitude": 10, "radius": 25000}, + ) + + hass.states.async_set( + "device_tracker.dog", + "work", + {"friendly_name": "dog", "latitude": 20.1, "longitude": 10.1}, + ) + + assert test(hass) + + async def test_zone_multiple_entities(hass): """Test with multiple entities in condition.""" test = await condition.async_from_config( From bfea7d0baa0c94e9f048972cf4645d11ff8c7380 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 19 Feb 2021 13:15:30 +0100 Subject: [PATCH 0594/1818] Raise ConditionError for and/or/not errors (#46767) --- .../components/automation/__init__.py | 17 +++++-- homeassistant/helpers/condition.py | 48 +++++++++++++------ tests/helpers/test_condition.py | 20 ++++++-- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index ae8c71b4fb8d08..3a48b3e3cc2e09 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -615,12 +615,21 @@ async def _async_process_if(hass, config, p_config): def if_action(variables=None): """AND all conditions.""" - try: - return all(check(hass, variables) for check in checks) - except ConditionError as ex: - LOGGER.warning("Error in 'condition' evaluation: %s", ex) + errors = [] + for check in checks: + try: + if not check(hass, variables): + return False + except ConditionError as ex: + errors.append(f"Error in 'condition' evaluation: {ex}") + + if errors: + for error in errors: + LOGGER.warning("%s", error) return False + return True + if_action.config = if_configs return if_action diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index e09176dc098d26..b66ee6c797610d 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -108,13 +108,19 @@ def if_and_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: """Test and condition.""" - try: - for check in checks: + errors = [] + for check in checks: + try: if not check(hass, variables): return False - except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning("Error during and-condition: %s", ex) - return False + except ConditionError as ex: + errors.append(str(ex)) + except Exception as ex: # pylint: disable=broad-except + errors.append(str(ex)) + + # Raise the errors if no check was false + if errors: + raise ConditionError("Error in 'and' condition: " + ", ".join(errors)) return True @@ -134,13 +140,20 @@ async def async_or_from_config( def if_or_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: - """Test and condition.""" - try: - for check in checks: + """Test or condition.""" + errors = [] + for check in checks: + try: if check(hass, variables): return True - except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning("Error during or-condition: %s", ex) + except ConditionError as ex: + errors.append(str(ex)) + except Exception as ex: # pylint: disable=broad-except + errors.append(str(ex)) + + # Raise the errors if no check was true + if errors: + raise ConditionError("Error in 'or' condition: " + ", ".join(errors)) return False @@ -161,12 +174,19 @@ def if_not_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: """Test not condition.""" - try: - for check in checks: + errors = [] + for check in checks: + try: if check(hass, variables): return False - except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning("Error during not-condition: %s", ex) + except ConditionError as ex: + errors.append(str(ex)) + except Exception as ex: # pylint: disable=broad-except + errors.append(str(ex)) + + # Raise the errors if no check was true + if errors: + raise ConditionError("Error in 'not' condition: " + ", ".join(errors)) return True diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 0db8220bc5056b..3e7833b24dd0f3 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -50,6 +50,9 @@ async def test_and_condition(hass): }, ) + with pytest.raises(ConditionError): + test(hass) + hass.states.async_set("sensor.temperature", 120) assert not test(hass) @@ -111,6 +114,9 @@ async def test_or_condition(hass): }, ) + with pytest.raises(ConditionError): + test(hass) + hass.states.async_set("sensor.temperature", 120) assert not test(hass) @@ -169,6 +175,9 @@ async def test_not_condition(hass): }, ) + with pytest.raises(ConditionError): + test(hass) + hass.states.async_set("sensor.temperature", 101) assert test(hass) @@ -466,7 +475,8 @@ async def test_state_attribute(hass): ) hass.states.async_set("sensor.temperature", 100, {"unkown_attr": 200}) - assert not test(hass) + with pytest.raises(ConditionError): + test(hass) hass.states.async_set("sensor.temperature", 100, {"attribute1": 200}) assert test(hass) @@ -720,7 +730,7 @@ async def test_numeric_state_multiple_entities(hass): assert not test(hass) -async def test_numberic_state_attribute(hass): +async def test_numeric_state_attribute(hass): """Test with numeric state attribute in condition.""" test = await condition.async_from_config( hass, @@ -738,7 +748,8 @@ async def test_numberic_state_attribute(hass): ) hass.states.async_set("sensor.temperature", 100, {"unkown_attr": 10}) - assert not test(hass) + with pytest.raises(ConditionError): + assert test(hass) hass.states.async_set("sensor.temperature", 100, {"attribute1": 49}) assert test(hass) @@ -750,7 +761,8 @@ async def test_numberic_state_attribute(hass): assert not test(hass) hass.states.async_set("sensor.temperature", 100, {"attribute1": None}) - assert not test(hass) + with pytest.raises(ConditionError): + assert test(hass) async def test_numeric_state_using_input_number(hass): From d2a187a57bc52763e466b82a737e7dd584dcb381 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Feb 2021 13:28:00 +0100 Subject: [PATCH 0595/1818] Bump RMVtransport to 0.3.1 (#46780) --- homeassistant/components/rmvtransport/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rmvtransport/manifest.json b/homeassistant/components/rmvtransport/manifest.json index 30aef1a0616330..68f895cb2b8855 100644 --- a/homeassistant/components/rmvtransport/manifest.json +++ b/homeassistant/components/rmvtransport/manifest.json @@ -3,7 +3,7 @@ "name": "RMV", "documentation": "https://www.home-assistant.io/integrations/rmvtransport", "requirements": [ - "PyRMVtransport==0.3.0" + "PyRMVtransport==0.3.1" ], "codeowners": [ "@cgtobi" diff --git a/requirements_all.txt b/requirements_all.txt index 1e9b7295946577..082b6db5dd2e30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -49,7 +49,7 @@ PyNaCl==1.3.0 PyQRCode==1.2.1 # homeassistant.components.rmvtransport -PyRMVtransport==0.3.0 +PyRMVtransport==0.3.1 # homeassistant.components.telegram_bot PySocks==1.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 71bcca6fe2ff37..74080a3945e3b4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -21,7 +21,7 @@ PyNaCl==1.3.0 PyQRCode==1.2.1 # homeassistant.components.rmvtransport -PyRMVtransport==0.3.0 +PyRMVtransport==0.3.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From 6ad7020f99f82e86698e4ae1d457d4d2a7ac8dd8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Feb 2021 13:30:27 +0100 Subject: [PATCH 0596/1818] Add Home Assistant color (#46751) --- homeassistant/components/light/services.yaml | 2 ++ homeassistant/util/color.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index f777cd3d3488b4..2161ed3f81da37 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -30,6 +30,7 @@ turn_on: selector: select: options: + - "homeassistant" - "aliceblue" - "antiquewhite" - "aqua" @@ -368,6 +369,7 @@ toggle: selector: select: options: + - "homeassistant" - "aliceblue" - "antiquewhite" - "aqua" diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 1e782f0c0f9ee2..9a5fbdb180faf2 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -160,6 +160,8 @@ "whitesmoke": (245, 245, 245), "yellow": (255, 255, 0), "yellowgreen": (154, 205, 50), + # And... + "homeassistant": (3, 169, 244), } From 3e82509263098fd8940ba086444e04e16404163e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 19 Feb 2021 15:23:59 +0100 Subject: [PATCH 0597/1818] Use string instead of slug for add-on slug validation (#46784) --- homeassistant/components/hassio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 1c246ae753b74c..4f6f0ed8348d8b 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -76,7 +76,7 @@ SCHEMA_NO_DATA = vol.Schema({}) -SCHEMA_ADDON = vol.Schema({vol.Required(ATTR_ADDON): cv.slug}) +SCHEMA_ADDON = vol.Schema({vol.Required(ATTR_ADDON): cv.string}) SCHEMA_ADDON_STDIN = SCHEMA_ADDON.extend( {vol.Required(ATTR_INPUT): vol.Any(dict, cv.string)} From 250327fac45e5bfb177fe7d5d6d60d674d16e02a Mon Sep 17 00:00:00 2001 From: Andreas Oetken Date: Fri, 19 Feb 2021 15:56:20 +0100 Subject: [PATCH 0598/1818] Allow multiple recipients for XMPP (#45328) * recipient is a string list instead of a single string now * this does NOT break existing automations/etc using this component --- homeassistant/components/xmpp/notify.py | 41 +++++++++++++------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index 78dc3c4303269a..68a041a28877c3 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -55,7 +55,7 @@ { vol.Required(CONF_SENDER): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_RECIPIENT): cv.string, + vol.Required(CONF_RECIPIENT): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_RESOURCE, default=DEFAULT_RESOURCE): cv.string, vol.Optional(CONF_ROOM, default=""): cv.string, vol.Optional(CONF_TLS, default=True): cv.boolean, @@ -87,7 +87,7 @@ def __init__(self, sender, resource, password, recipient, tls, verify, room, has self._sender = sender self._resource = resource self._password = password - self._recipient = recipient + self._recipients = recipient self._tls = tls self._verify = verify self._room = room @@ -102,7 +102,7 @@ async def async_send_message(self, message="", **kwargs): await async_send_message( f"{self._sender}/{self._resource}", self._password, - self._recipient, + self._recipients, self._tls, self._verify, self._room, @@ -116,7 +116,7 @@ async def async_send_message(self, message="", **kwargs): async def async_send_message( sender, password, - recipient, + recipients, use_tls, verify_certificate, room, @@ -182,19 +182,21 @@ async def send_file(self, timeout=None): url = await self.upload_file(timeout=timeout) _LOGGER.info("Upload success") - if room: - _LOGGER.info("Sending file to %s", room) - message = self.Message(sto=room, stype="groupchat") - else: - _LOGGER.info("Sending file to %s", recipient) - message = self.Message(sto=recipient, stype="chat") - - message["body"] = url - message["oob"]["url"] = url - try: - message.send() - except (IqError, IqTimeout, XMPPError) as ex: - _LOGGER.error("Could not send image message %s", ex) + for recipient in recipients: + if room: + _LOGGER.info("Sending file to %s", room) + message = self.Message(sto=room, stype="groupchat") + else: + _LOGGER.info("Sending file to %s", recipient) + message = self.Message(sto=recipient, stype="chat") + message["body"] = url + message["oob"]["url"] = url + try: + message.send() + except (IqError, IqTimeout, XMPPError) as ex: + _LOGGER.error("Could not send image message %s", ex) + if room: + break except (IqError, IqTimeout, XMPPError) as ex: _LOGGER.error("Upload error, could not send message %s", ex) except NotConnectedError as ex: @@ -336,8 +338,9 @@ def send_text_message(self): self.plugin["xep_0045"].join_muc(room, sender, wait=True) self.send_message(mto=room, mbody=message, mtype="groupchat") else: - _LOGGER.debug("Sending message to %s", recipient) - self.send_message(mto=recipient, mbody=message, mtype="chat") + for recipient in recipients: + _LOGGER.debug("Sending message to %s", recipient) + self.send_message(mto=recipient, mbody=message, mtype="chat") except (IqError, IqTimeout, XMPPError) as ex: _LOGGER.error("Could not send text message %s", ex) except NotConnectedError as ex: From 32bec5ea6320e0950e55fff457d1dc1b2e0cea3a Mon Sep 17 00:00:00 2001 From: Denise Yu Date: Fri, 19 Feb 2021 15:23:34 -0500 Subject: [PATCH 0599/1818] Update GitHub Issue Form template (#46791) This PR fixes the bug report template! Details: https://gh-community.github.io/issue-template-feedback/changes/#what-do-i-need-to-do --- .github/ISSUE_TEMPLATE/bug_report.yml | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 88e8afed67de95..9a46dd82215f73 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,8 +2,8 @@ name: Report an issue with Home Assistant Core about: Report an issue with Home Assistant Core. title: "" issue_body: true -inputs: - - type: description +body: + - type: markdown attributes: value: | This issue form is for reporting bugs only! @@ -12,39 +12,41 @@ inputs: [fr]: https://community.home-assistant.io/c/feature-requests - type: textarea + validations: + required: true attributes: label: The problem - required: true description: >- Describe the issue you are experiencing here to communicate to the maintainers. Tell us what you were trying to do and what happened. Provide a clear and concise description of what the problem is. - - type: description + - type: markdown attributes: value: | ## Environment - type: input + validations: + required: true attributes: label: What is version of Home Assistant Core has the issue? - required: true placeholder: core- description: > Can be found in the Configuration panel -> Info. - type: input attributes: label: What was the last working version of Home Assistant Core? - required: false placeholder: core- description: > If known, otherwise leave blank. - type: dropdown + validations: + required: true attributes: label: What type of installation are you running? - required: true description: > If you don't know, you can find it in: Configuration panel -> Info. - choices: + options: - Home Assistant OS - Home Assistant Container - Home Assistant Supervised @@ -52,13 +54,11 @@ inputs: - type: input attributes: label: Integration causing the issue - required: false description: > The name of the integration, for example, Automation or Philips Hue. - type: input attributes: label: Link to integration documentation on our website - required: false placeholder: "https://www.home-assistant.io/integrations/..." description: | Providing a link [to the documentation][docs] help us categorizing the @@ -66,14 +66,13 @@ inputs: [docs]: https://www.home-assistant.io/integrations - - type: description + - type: markdown attributes: value: | # Details - type: textarea attributes: label: Example YAML snippet - required: false description: | If this issue has an example piece of YAML that can help reproducing this problem, please provide. This can be an piece of YAML from, e.g., an automation, script, scene or configuration. @@ -86,17 +85,16 @@ inputs: attributes: label: Anything in the logs that might be useful for us? description: For example, error message, or stack traces. - required: false value: | ```txt # Put your logs below this line ``` - - type: description + - type: markdown attributes: value: | ## Additional information - - type: description + - type: markdown attributes: value: > If you have any additional information for us, use the field below. From 4d23ffacd1a2b1e1841500715ada00a872b68961 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 19 Feb 2021 18:20:10 -0500 Subject: [PATCH 0600/1818] Add zwave_js thermostat fan mode and fan state support (#46793) * add thermostat fan mode and fan state support * return when fan mode is not supported * use get just in case * validate state key is in states so we dont have to use get * pylint --- homeassistant/components/zwave_js/climate.py | 69 ++++++++++++++++++- tests/components/zwave_js/test_climate.py | 56 +++++++++++++++ ...ate_radio_thermostat_ct100_plus_state.json | 46 +++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 689187f0b34526..341b8f99fd63db 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -1,5 +1,5 @@ """Representation of Z-Wave thermostats.""" -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional, cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( @@ -33,6 +33,7 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_NONE, + SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, @@ -81,6 +82,8 @@ ThermostatOperatingState.THIRD_STAGE_AUX_HEAT: CURRENT_HVAC_HEAT, } +ATTR_FAN_STATE = "fan_state" + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable @@ -148,6 +151,16 @@ def __init__( add_to_watched_value_ids=True, check_all_endpoints=True, ) + self._fan_mode = self.get_zwave_value( + THERMOSTAT_MODE_PROPERTY, + CommandClass.THERMOSTAT_FAN_MODE, + add_to_watched_value_ids=True, + ) + self._fan_state = self.get_zwave_value( + THERMOSTAT_OPERATING_STATE_PROPERTY, + CommandClass.THERMOSTAT_FAN_STATE, + add_to_watched_value_ids=True, + ) self._set_modes_and_presets() def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: @@ -275,6 +288,40 @@ def preset_modes(self) -> Optional[List[str]]: """Return a list of available preset modes.""" return list(self._hvac_presets) + @property + def fan_mode(self) -> Optional[str]: + """Return the fan setting.""" + if ( + self._fan_mode + and self._fan_mode.value is not None + and str(self._fan_mode.value) in self._fan_mode.metadata.states + ): + return cast(str, self._fan_mode.metadata.states[str(self._fan_mode.value)]) + return None + + @property + def fan_modes(self) -> Optional[List[str]]: + """Return the list of available fan modes.""" + if self._fan_mode and self._fan_mode.metadata.states: + return list(self._fan_mode.metadata.states.values()) + return None + + @property + def device_state_attributes(self) -> Optional[Dict[str, str]]: + """Return the optional state attributes.""" + if ( + self._fan_state + and self._fan_state.value is not None + and str(self._fan_state.value) in self._fan_state.metadata.states + ): + return { + ATTR_FAN_STATE: self._fan_state.metadata.states[ + str(self._fan_state.value) + ] + } + + return None + @property def supported_features(self) -> int: """Return the list of supported features.""" @@ -283,8 +330,28 @@ def supported_features(self) -> int: support |= SUPPORT_TARGET_TEMPERATURE if len(self._current_mode_setpoint_enums) > 1: support |= SUPPORT_TARGET_TEMPERATURE_RANGE + if self._fan_mode: + support |= SUPPORT_FAN_MODE return support + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set new target fan mode.""" + if not self._fan_mode: + return + + try: + new_state = int( + next( + state + for state, label in self._fan_mode.metadata.states.items() + if label == fan_mode + ) + ) + except StopIteration: + raise ValueError(f"Received an invalid fan mode: {fan_mode}") from None + + await self.info.node.async_set_value(self._fan_mode, new_state) + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" assert self.hass diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index e11d3b75c47637..7336acd82eb45b 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -5,6 +5,7 @@ from homeassistant.components.climate.const import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, @@ -19,10 +20,12 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_NONE, + SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, ) +from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" @@ -50,6 +53,8 @@ async def test_thermostat_v2( assert state.attributes[ATTR_TEMPERATURE] == 22.2 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert state.attributes[ATTR_FAN_MODE] == "Auto low" + assert state.attributes[ATTR_FAN_STATE] == "Idle / off" # Test setting preset mode await hass.services.async_call( @@ -329,6 +334,57 @@ async def test_thermostat_v2( blocking=True, ) + client.async_send_command.reset_mock() + + # Test setting fan mode + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, + ATTR_FAN_MODE: "Low", + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 13 + assert args["valueId"] == { + "endpoint": 1, + "commandClass": 68, + "commandClassName": "Thermostat Fan Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 255, + "states": {"0": "Auto low", "1": "Low"}, + "label": "Thermostat fan mode", + }, + "value": 0, + } + assert args["value"] == 1 + + client.async_send_command.reset_mock() + + # Test setting invalid fan mode + with pytest.raises(ValueError): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, + ATTR_FAN_MODE: "fake value", + }, + blocking=True, + ) + async def test_thermostat_different_endpoints( hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration diff --git a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json index 77a68aafde1bcb..caad22aac36cf3 100644 --- a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json +++ b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json @@ -594,6 +594,52 @@ "propertyName": "manufacturerData", "metadata": { "type": "any", "readable": true, "writeable": true } }, + { + "endpoint": 1, + "commandClass": 68, + "commandClassName": "Thermostat Fan Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "states": { "0": "Auto low", "1": "Low" }, + "label": "Thermostat fan mode" + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 69, + "commandClassName": "Thermostat Fan State", + "property": "state", + "propertyName": "state", + "ccVersion": 0, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "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" + }, + "label": "Thermostat fan state" + }, + "value": 0 + }, { "commandClassName": "Thermostat Operating State", "commandClass": 66, From 47dcd2bf32ad36cd4734b439856fb6e0d417ad3e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 19 Feb 2021 18:44:15 -0500 Subject: [PATCH 0601/1818] Format zwave_js dump as json (#46792) --- homeassistant/components/zwave_js/api.py | 6 +++--- tests/components/zwave_js/test_api.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 91c316d307e582..bed2166a4da5cd 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -281,9 +281,9 @@ async def get(self, request: web.Request, config_entry_id: str) -> web.Response: msgs = await dump.dump_msgs(entry.data[CONF_URL], async_get_clientsession(hass)) return web.Response( - body="\n".join(json.dumps(msg) for msg in msgs) + "\n", + body=json.dumps(msgs, indent=2) + "\n", headers={ - hdrs.CONTENT_TYPE: "application/jsonl", - hdrs.CONTENT_DISPOSITION: 'attachment; filename="zwave_js_dump.jsonl"', + hdrs.CONTENT_TYPE: "application/json", + hdrs.CONTENT_DISPOSITION: 'attachment; filename="zwave_js_dump.json"', }, ) diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 88e8acc577156f..a36743421c95d3 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS Websocket API.""" +import json from unittest.mock import patch from zwave_js_server.event import Event @@ -164,7 +165,7 @@ async def test_dump_view(integration, hass_client): ): resp = await client.get(f"/api/zwave_js/dump/{integration.entry_id}") assert resp.status == 200 - assert await resp.text() == '{"hello": "world"}\n{"second": "msg"}\n' + assert json.loads(await resp.text()) == [{"hello": "world"}, {"second": "msg"}] async def test_dump_view_invalid_entry_id(integration, hass_client): From fd60d4273b554a1b94391e1fc7de70ccd89ec0c1 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 20 Feb 2021 00:08:25 +0000 Subject: [PATCH 0602/1818] [ci skip] Translation update --- .../components/airvisual/translations/es.json | 8 +++- .../components/asuswrt/translations/es.json | 32 ++++++++++++- .../components/econet/translations/es.json | 3 +- .../components/fritzbox/translations/es.json | 3 +- .../fritzbox_callmonitor/translations/es.json | 12 +++++ .../components/habitica/translations/es.json | 15 ++++++ .../components/habitica/translations/tr.json | 3 ++ .../components/homekit/translations/es.json | 5 +- .../keenetic_ndms2/translations/es.json | 16 +++++++ .../lutron_caseta/translations/es.json | 19 ++++++++ .../components/mazda/translations/es.json | 26 ++++++++++ .../media_player/translations/es.json | 7 +++ .../components/mysensors/translations/es.json | 47 ++++++++++++++++++- .../components/number/translations/es.json | 5 ++ .../philips_js/translations/es.json | 3 +- .../components/plaato/translations/es.json | 10 ++++ .../components/powerwall/translations/no.json | 2 +- .../components/smarttub/translations/en.json | 3 +- .../components/smarttub/translations/es.json | 10 ++++ .../components/tuya/translations/es.json | 2 + .../components/unifi/translations/es.json | 1 + .../xiaomi_miio/translations/es.json | 10 +++- .../xiaomi_miio/translations/tr.json | 5 ++ .../components/zwave_js/translations/es.json | 5 +- 24 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 homeassistant/components/habitica/translations/es.json create mode 100644 homeassistant/components/habitica/translations/tr.json create mode 100644 homeassistant/components/mazda/translations/es.json create mode 100644 homeassistant/components/smarttub/translations/es.json diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index 9b3ac467b84075..53768f679be687 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -25,7 +25,9 @@ "api_key": "Clave API", "latitude": "Latitud", "longitude": "Longitud" - } + }, + "description": "Utilice la API de la nube de AirVisual para supervisar una latitud/longitud.", + "title": "Configurar una geograf\u00eda" }, "geography_by_name": { "data": { @@ -33,7 +35,9 @@ "city": "Ciudad", "country": "Pa\u00eds", "state": "estado" - } + }, + "description": "Utilice la API de la nube de AirVisual para supervisar una ciudad/estado/pa\u00eds.", + "title": "Configurar una geograf\u00eda" }, "node_pro": { "data": { diff --git a/homeassistant/components/asuswrt/translations/es.json b/homeassistant/components/asuswrt/translations/es.json index 3531e0a3ccaaad..c5792babf004d2 100644 --- a/homeassistant/components/asuswrt/translations/es.json +++ b/homeassistant/components/asuswrt/translations/es.json @@ -1,16 +1,44 @@ { "config": { + "abort": { + "single_instance_allowed": "Ya configurado. Solo es posible una \u00fanica configuraci\u00f3n." + }, "error": { + "cannot_connect": "No se pudo conectar", + "invalid_host": "Nombre de host o direcci\u00f3n IP no v\u00e1lidos", + "pwd_and_ssh": "S\u00f3lo proporcionar la contrase\u00f1a o el archivo de clave SSH", + "pwd_or_ssh": "Por favor, proporcione la contrase\u00f1a o el archivo de clave SSH", + "ssh_not_file": "Archivo de clave SSH no encontrado", "unknown": "Error inesperado" }, "step": { "user": { "data": { + "host": "Host", "mode": "Modo", "name": "Nombre", "password": "Contrase\u00f1a", - "port": "Puerto" - } + "port": "Puerto", + "protocol": "Protocolo de comunicaci\u00f3n a utilizar", + "ssh_key": "Ruta de acceso a su archivo de clave SSH (en lugar de la contrase\u00f1a)", + "username": "Nombre de usuario" + }, + "description": "Establezca los par\u00e1metros necesarios para conectarse a su router", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Segundos de espera antes de considerar un dispositivo ausente", + "dnsmasq": "La ubicaci\u00f3n en el router de los archivos dnsmasq.leases", + "interface": "La interfaz de la que desea obtener estad\u00edsticas (por ejemplo, eth0, eth1, etc.)", + "require_ip": "Los dispositivos deben tener IP (para el modo de punto de acceso)", + "track_unknown": "Seguimiento de dispositivos desconocidos / sin nombre" + }, + "title": "Opciones de AsusWRT" } } } diff --git a/homeassistant/components/econet/translations/es.json b/homeassistant/components/econet/translations/es.json index ac69f8f7be1f8d..8634be9413fcc2 100644 --- a/homeassistant/components/econet/translations/es.json +++ b/homeassistant/components/econet/translations/es.json @@ -14,7 +14,8 @@ "data": { "email": "Correo electr\u00f3nico", "password": "Contrase\u00f1a" - } + }, + "title": "Configurar la cuenta Rheem EcoNet" } } } diff --git a/homeassistant/components/fritzbox/translations/es.json b/homeassistant/components/fritzbox/translations/es.json index c9b67ca59f1c54..fcb240deb77792 100644 --- a/homeassistant/components/fritzbox/translations/es.json +++ b/homeassistant/components/fritzbox/translations/es.json @@ -23,7 +23,8 @@ "data": { "password": "Contrase\u00f1a", "username": "Usuario" - } + }, + "description": "Actualice la informaci\u00f3n de inicio de sesi\u00f3n para {name}." }, "user": { "data": { diff --git a/homeassistant/components/fritzbox_callmonitor/translations/es.json b/homeassistant/components/fritzbox_callmonitor/translations/es.json index 899a50507552f7..4d4aa4cd86b84b 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/es.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/es.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "insufficient_permissions": "El usuario no tiene permisos suficientes para acceder a la configuraci\u00f3n de AVM FRITZ! Box y sus agendas telef\u00f3nicas." + }, "error": { "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, + "flow_title": "Monitor de llamadas de AVM FRITZ! Box: {name}", "step": { + "phonebook": { + "data": { + "phonebook": "Directorio telef\u00f3nico" + } + }, "user": { "data": { "host": "Host", @@ -20,6 +29,9 @@ }, "step": { "init": { + "data": { + "prefixes": "Prefijos (lista separada por comas)" + }, "title": "Configurar prefijos" } } diff --git a/homeassistant/components/habitica/translations/es.json b/homeassistant/components/habitica/translations/es.json new file mode 100644 index 00000000000000..afdbb6666ad141 --- /dev/null +++ b/homeassistant/components/habitica/translations/es.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_user": "ID de usuario de la API de Habitica", + "name": "Anular el nombre de usuario de Habitica. Se utilizar\u00e1 para llamadas de servicio.", + "url": "URL" + }, + "description": "Conecta tu perfil de Habitica para permitir la supervisi\u00f3n del perfil y las tareas de tu usuario. Ten en cuenta que api_id y api_key deben obtenerse de https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/tr.json b/homeassistant/components/habitica/translations/tr.json new file mode 100644 index 00000000000000..f77cc77798c5e9 --- /dev/null +++ b/homeassistant/components/habitica/translations/tr.json @@ -0,0 +1,3 @@ +{ + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/es.json b/homeassistant/components/homekit/translations/es.json index 8ceee3f3016e24..694b7dcdb6cf90 100644 --- a/homeassistant/components/homekit/translations/es.json +++ b/homeassistant/components/homekit/translations/es.json @@ -7,12 +7,15 @@ "accessory_mode": { "data": { "entity_id": "Entidad" - } + }, + "description": "Elija la entidad que desea incluir. En el modo accesorio, s\u00f3lo se incluye una \u00fanica entidad.", + "title": "Seleccione la entidad a incluir" }, "bridge_mode": { "data": { "include_domains": "Dominios a incluir" }, + "description": "Elija los dominios que se van a incluir. Se incluir\u00e1n todas las entidades admitidas en el dominio.", "title": "Selecciona los dominios a incluir" }, "pairing": { diff --git a/homeassistant/components/keenetic_ndms2/translations/es.json b/homeassistant/components/keenetic_ndms2/translations/es.json index 6b8af4f98afddb..6846cfbef4203a 100644 --- a/homeassistant/components/keenetic_ndms2/translations/es.json +++ b/homeassistant/components/keenetic_ndms2/translations/es.json @@ -9,10 +9,26 @@ "step": { "user": { "data": { + "host": "Host", "name": "Nombre", "password": "Contrase\u00f1a", "port": "Puerto", "username": "Usuario" + }, + "title": "Configurar el router Keenetic NDMS2" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Considerar el intervalo en casa", + "include_arp": "Usar datos ARP (ignorado si se usan datos de hotspot)", + "include_associated": "Utilizar los datos de las asociaciones WiFi AP (se ignora si se utilizan los datos del hotspot)", + "interfaces": "Elija las interfaces para escanear", + "scan_interval": "Intervalo de escaneo", + "try_hotspot": "Utilizar datos de 'punto de acceso ip' (m\u00e1s precisos)" } } } diff --git a/homeassistant/components/lutron_caseta/translations/es.json b/homeassistant/components/lutron_caseta/translations/es.json index 460d4fc5e69f2e..9dbedba145722c 100644 --- a/homeassistant/components/lutron_caseta/translations/es.json +++ b/homeassistant/components/lutron_caseta/translations/es.json @@ -30,9 +30,24 @@ "device_automation": { "trigger_subtype": { "button_1": "Primer bot\u00f3n", + "button_2": "Segundo bot\u00f3n", "button_3": "Tercer bot\u00f3n", + "button_4": "Cuarto bot\u00f3n", "close_1": "Cerrar 1", "close_2": "Cerrar 2", + "close_3": "Cerrar 3", + "close_4": "Cerrar 4", + "close_all": "Cerrar todo", + "group_1_button_1": "Primer bot\u00f3n de primer grupo", + "group_1_button_2": "Segundo bot\u00f3n del primer grupo", + "group_2_button_1": "Primer bot\u00f3n del segundo grupo", + "group_2_button_2": "Segundo bot\u00f3n del segundo grupo", + "lower": "Inferior", + "lower_1": "Inferior 1", + "lower_2": "Inferior 2", + "lower_3": "Inferior 3", + "lower_4": "Inferior 4", + "lower_all": "Bajar todo", "off": "Apagado", "on": "Encendido", "open_1": "Abrir 1", @@ -52,6 +67,10 @@ "stop_3": "Detener 3", "stop_4": "Detener 4", "stop_all": "Detener todo" + }, + "trigger_type": { + "press": "\"{subtipo}\" presionado", + "release": "\"{subtipo}\" liberado" } } } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/es.json b/homeassistant/components/mazda/translations/es.json new file mode 100644 index 00000000000000..72fc9ce7389103 --- /dev/null +++ b/homeassistant/components/mazda/translations/es.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "account_locked": "Cuenta bloqueada. Por favor, int\u00e9ntelo de nuevo m\u00e1s tarde." + }, + "step": { + "reauth": { + "data": { + "region": "Regi\u00f3n" + }, + "description": "Ha fallado la autenticaci\u00f3n para los Servicios Conectados de Mazda. Por favor, introduce tus credenciales actuales.", + "title": "Servicios Conectados de Mazda - Fallo de autenticaci\u00f3n" + }, + "user": { + "data": { + "email": "Correo electronico", + "password": "Contrase\u00f1a", + "region": "Regi\u00f3n" + }, + "description": "Introduce la direcci\u00f3n de correo electr\u00f3nico y la contrase\u00f1a que utilizas para iniciar sesi\u00f3n en la aplicaci\u00f3n m\u00f3vil MyMazda.", + "title": "Servicios Conectados de Mazda - A\u00f1adir cuenta" + } + } + }, + "title": "Servicios Conectados de Mazda" +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/es.json b/homeassistant/components/media_player/translations/es.json index fffaedc1d972bc..f1ffc44957eefb 100644 --- a/homeassistant/components/media_player/translations/es.json +++ b/homeassistant/components/media_player/translations/es.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} est\u00e1 activado", "is_paused": "{entity_name} est\u00e1 en pausa", "is_playing": "{entity_name} est\u00e1 reproduciendo" + }, + "trigger_type": { + "idle": "{entity_name} est\u00e1 inactivo", + "paused": "{entity_name} est\u00e1 en pausa", + "playing": "{entity_name} comienza a reproducirse", + "turned_off": "{entity_name} desactivado", + "turned_on": "{entity_name} activado" } }, "state": { diff --git a/homeassistant/components/mysensors/translations/es.json b/homeassistant/components/mysensors/translations/es.json index f43762b9701286..2a4b30910d17de 100644 --- a/homeassistant/components/mysensors/translations/es.json +++ b/homeassistant/components/mysensors/translations/es.json @@ -1,21 +1,46 @@ { "config": { + "abort": { + "duplicate_persistence_file": "Archivo de persistencia ya en uso", + "duplicate_topic": "Tema ya en uso", + "invalid_device": "Dispositivo no v\u00e1lido", + "invalid_ip": "Direcci\u00f3n IP no v\u00e1lida", + "invalid_persistence_file": "Archivo de persistencia no v\u00e1lido", + "invalid_port": "N\u00famero de puerto no v\u00e1lido", + "invalid_publish_topic": "Tema de publicaci\u00f3n no v\u00e1lido", + "invalid_serial": "Puerto serie no v\u00e1lido", + "invalid_subscribe_topic": "Tema de suscripci\u00f3n no v\u00e1lido", + "invalid_version": "Versi\u00f3n inv\u00e1lida de MySensors", + "not_a_number": "Por favor, introduzca un n\u00famero", + "port_out_of_range": "El n\u00famero de puerto debe ser como m\u00ednimo 1 y como m\u00e1ximo 65535", + "same_topic": "Los temas de suscripci\u00f3n y publicaci\u00f3n son los mismos" + }, "error": { "already_configured": "El dispositivo ya est\u00e1 configurado", "cannot_connect": "No se pudo conectar", + "duplicate_persistence_file": "Archivo de persistencia ya en uso", + "duplicate_topic": "Tema ya en uso", "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_device": "Dispositivo no v\u00e1lido", "invalid_ip": "Direcci\u00f3n IP no v\u00e1lida", + "invalid_persistence_file": "Archivo de persistencia no v\u00e1lido", "invalid_port": "N\u00famero de puerto no v\u00e1lido", + "invalid_publish_topic": "Tema de publicaci\u00f3n no v\u00e1lido", "invalid_serial": "Puerto serie no v\u00e1lido", + "invalid_subscribe_topic": "Tema de suscripci\u00f3n no v\u00e1lido", "invalid_version": "Versi\u00f3n no v\u00e1lida de MySensors", "not_a_number": "Por favor, introduce un n\u00famero", "port_out_of_range": "El n\u00famero de puerto debe ser como m\u00ednimo 1 y como m\u00e1ximo 65535", + "same_topic": "Los temas de suscripci\u00f3n y publicaci\u00f3n son los mismos", "unknown": "Error inesperado" }, "step": { "gw_mqtt": { "data": { + "persistence_file": "archivo de persistencia (d\u00e9jelo vac\u00edo para que se genere autom\u00e1ticamente)", + "retain": "retener 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" @@ -24,9 +49,27 @@ "data": { "baud_rate": "tasa de baudios", "device": "Puerto serie", + "persistence_file": "archivo de persistencia (d\u00e9jelo vac\u00edo para que se genere autom\u00e1ticamente)", "version": "Versi\u00f3n de MySensors" - } + }, + "description": "Configuraci\u00f3n de la pasarela en serie" + }, + "gw_tcp": { + "data": { + "device": "Direcci\u00f3n IP de la pasarela", + "persistence_file": "archivo de persistencia (d\u00e9jelo vac\u00edo para que se genere autom\u00e1ticamente)", + "tcp_port": "Puerto", + "version": "Versi\u00f3n de MySensores" + }, + "description": "Configuraci\u00f3n de la pasarela Ethernet" + }, + "user": { + "data": { + "gateway_type": "Tipo de pasarela" + }, + "description": "Elija el m\u00e9todo de conexi\u00f3n con la pasarela" } } - } + }, + "title": "MySensors" } \ No newline at end of file diff --git a/homeassistant/components/number/translations/es.json b/homeassistant/components/number/translations/es.json index e77258e777d0ee..e709346849e403 100644 --- a/homeassistant/components/number/translations/es.json +++ b/homeassistant/components/number/translations/es.json @@ -1,3 +1,8 @@ { + "device_automation": { + "action_type": { + "set_value": "Establecer valor para {entity_name}" + } + }, "title": "N\u00famero" } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/es.json b/homeassistant/components/philips_js/translations/es.json index 3d4beaa875214d..d4476f29981a13 100644 --- a/homeassistant/components/philips_js/translations/es.json +++ b/homeassistant/components/philips_js/translations/es.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "api_version": "Versi\u00f3n del API" + "api_version": "Versi\u00f3n del API", + "host": "Host" } } } diff --git a/homeassistant/components/plaato/translations/es.json b/homeassistant/components/plaato/translations/es.json index 6ff49fc707c401..e0b6c767043acf 100644 --- a/homeassistant/components/plaato/translations/es.json +++ b/homeassistant/components/plaato/translations/es.json @@ -19,13 +19,19 @@ "token": "Pega el token de autenticaci\u00f3n aqu\u00ed", "use_webhook": "Usar webhook" }, + "description": "Para poder consultar la API se necesita un `auth_token` que puede obtenerse siguiendo [estas](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrucciones\n\n Dispositivo seleccionado: **{device_type}** \n\nSi prefiere utilizar el m\u00e9todo de webhook incorporado (s\u00f3lo Airlock), marque la casilla siguiente y deje en blanco el Auth Token", "title": "Selecciona el m\u00e9todo API" }, "user": { + "data": { + "device_name": "Nombre de su dispositivo", + "device_type": "Tipo de dispositivo Plaato" + }, "description": "\u00bfEst\u00e1s seguro de que quieres configurar el Airlock de Plaato?", "title": "Configurar el webhook de Plaato" }, "webhook": { + "description": "Para enviar eventos a Home Assistant, deber\u00e1 configurar la funci\u00f3n de webhook en Plaato Airlock. \n\n Complete la siguiente informaci\u00f3n: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n\n Consulte [la documentaci\u00f3n]({docs_url}) para obtener m\u00e1s detalles.", "title": "Webhook a utilizar" } } @@ -38,6 +44,10 @@ }, "description": "Intervalo de actualizaci\u00f3n (minutos)", "title": "Opciones de Plaato" + }, + "webhook": { + "description": "Informaci\u00f3n de webhook: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n\n", + "title": "Opciones para Plaato Airlock" } } } diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index 13609f911a4b45..00b77e1456611b 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/translations/no.json @@ -17,7 +17,7 @@ "ip_address": "IP adresse", "password": "Passord" }, - "description": "Passordet er vanligvis de siste 5 tegnene i serienummeret for Backup Gateway, og finnes i Telsa-appen. eller de siste 5 tegnene i passordet som er funnet inne i d\u00f8ren til Backup Gateway 2.", + "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": "Koble til powerwall" } } diff --git a/homeassistant/components/smarttub/translations/en.json b/homeassistant/components/smarttub/translations/en.json index b013f816559dc2..4cf930918875b4 100644 --- a/homeassistant/components/smarttub/translations/en.json +++ b/homeassistant/components/smarttub/translations/en.json @@ -5,7 +5,8 @@ "reauth_successful": "Re-authentication was successful" }, "error": { - "invalid_auth": "Invalid authentication" + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/smarttub/translations/es.json b/homeassistant/components/smarttub/translations/es.json new file mode 100644 index 00000000000000..df5b4122bc4449 --- /dev/null +++ b/homeassistant/components/smarttub/translations/es.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "Introduzca su direcci\u00f3n de correo electr\u00f3nico y contrase\u00f1a de SmartTub para iniciar sesi\u00f3n", + "title": "Inicio de sesi\u00f3n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/es.json b/homeassistant/components/tuya/translations/es.json index cd8da781870557..9c57a2168880b2 100644 --- a/homeassistant/components/tuya/translations/es.json +++ b/homeassistant/components/tuya/translations/es.json @@ -40,8 +40,10 @@ "max_temp": "Temperatura objetivo m\u00e1xima (usa m\u00edn. y m\u00e1x. = 0 por defecto)", "min_kelvin": "Temperatura de color m\u00ednima soportada en kelvin", "min_temp": "Temperatura objetivo m\u00ednima (usa m\u00edn. y m\u00e1x. = 0 por defecto)", + "set_temp_divided": "Use el valor de temperatura dividido para el comando de temperatura establecida", "support_color": "Forzar soporte de color", "temp_divider": "Divisor de los valores de temperatura (0 = usar valor por defecto)", + "temp_step_override": "Temperatura deseada", "tuya_max_coltemp": "Temperatura de color m\u00e1xima notificada por dispositivo", "unit_of_measurement": "Unidad de temperatura utilizada por el dispositivo" }, diff --git a/homeassistant/components/unifi/translations/es.json b/homeassistant/components/unifi/translations/es.json index a676d70e88c484..a5963d7019ef2e 100644 --- a/homeassistant/components/unifi/translations/es.json +++ b/homeassistant/components/unifi/translations/es.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El sitio del controlador ya est\u00e1 configurado", + "configuration_updated": "Configuraci\u00f3n actualizada.", "reauth_successful": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json index 46fb93012adb98..fd4b8c36a8b228 100644 --- a/homeassistant/components/xiaomi_miio/translations/es.json +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -6,10 +6,18 @@ }, "error": { "cannot_connect": "No se pudo conectar", - "no_device_selected": "No se ha seleccionado ning\u00fan dispositivo, por favor, seleccione un dispositivo." + "no_device_selected": "No se ha seleccionado ning\u00fan dispositivo, por favor, seleccione un dispositivo.", + "unknown_device": "No se conoce el modelo del dispositivo, no se puede configurar el dispositivo mediante el flujo de configuraci\u00f3n." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "name": "Nombre del dispositivo" + }, + "description": "Necesitar\u00e1 la clave de 32 caracteres Token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones. Tenga en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", + "title": "Con\u00e9ctese a un dispositivo Xiaomi Miio o Xiaomi Gateway" + }, "gateway": { "data": { "host": "Direcci\u00f3n IP", diff --git a/homeassistant/components/xiaomi_miio/translations/tr.json b/homeassistant/components/xiaomi_miio/translations/tr.json index 46a6493ab3a8d2..3dbf08bd6f1963 100644 --- a/homeassistant/components/xiaomi_miio/translations/tr.json +++ b/homeassistant/components/xiaomi_miio/translations/tr.json @@ -9,6 +9,11 @@ "no_device_selected": "Cihaz se\u00e7ilmedi, l\u00fctfen bir cihaz se\u00e7in." }, "step": { + "device": { + "data": { + "name": "Cihaz\u0131n ad\u0131" + } + }, "gateway": { "data": { "host": "\u0130p Adresi", diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index 257d26eefd6ce6..69a638e1f40983 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -1,8 +1,10 @@ { "config": { "abort": { + "addon_get_discovery_info_failed": "Fallo en la obtenci\u00f3n de la informaci\u00f3n de descubrimiento del complemento Z-Wave JS.", "addon_info_failed": "No se pudo obtener la informaci\u00f3n del complemento Z-Wave JS.", "addon_install_failed": "No se ha podido instalar el complemento Z-Wave JS.", + "addon_missing_discovery_info": "Falta informaci\u00f3n de descubrimiento del complemento Z-Wave JS.", "addon_set_config_failed": "Fallo en la configuraci\u00f3n de Z-Wave JS.", "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", @@ -40,7 +42,8 @@ "data": { "network_key": "Clave de red", "usb_path": "Ruta del dispositivo USB" - } + }, + "title": "Introduzca la configuraci\u00f3n del complemento Z-Wave JS" }, "user": { "data": { From e6125a1e4ea4ecbd2578dfaa84733746835190c1 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Fri, 19 Feb 2021 18:49:39 -0800 Subject: [PATCH 0603/1818] Bump pywemo to 0.6.2 (#46797) --- homeassistant/components/wemo/__init__.py | 7 +++---- homeassistant/components/wemo/entity.py | 2 +- homeassistant/components/wemo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index 0075b5dc8518c3..db380ae11cac2f 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -3,7 +3,6 @@ import logging import pywemo -import requests import voluptuous as vol from homeassistant import config_entries @@ -229,10 +228,10 @@ def validate_static_config(host, port): return None try: - device = pywemo.discovery.device_from_description(url, None) + device = pywemo.discovery.device_from_description(url) except ( - requests.exceptions.ConnectionError, - requests.exceptions.Timeout, + pywemo.exceptions.ActionException, + pywemo.exceptions.HTTPException, ) as err: _LOGGER.error("Unable to access WeMo at %s (%s)", url, err) return None diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index d9d90c5508bde0..65183a6f7a41fe 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -6,7 +6,7 @@ import async_timeout from pywemo import WeMoDevice -from pywemo.ouimeaux_device.api.service import ActionException +from pywemo.exceptions import ActionException from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index 94bc0fa72aaa03..23911b31be275b 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -3,7 +3,7 @@ "name": "Belkin WeMo", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wemo", - "requirements": ["pywemo==0.6.1"], + "requirements": ["pywemo==0.6.2"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/requirements_all.txt b/requirements_all.txt index 082b6db5dd2e30..0e3c6117780a8a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1902,7 +1902,7 @@ pyvolumio==0.1.3 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.6.1 +pywemo==0.6.2 # homeassistant.components.wilight pywilight==0.0.68 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 74080a3945e3b4..317063ffffcc88 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -981,7 +981,7 @@ pyvolumio==0.1.3 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.6.1 +pywemo==0.6.2 # homeassistant.components.wilight pywilight==0.0.68 From bb7e4d7daa9f369fc39f043f9fdb761f7f510436 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 19:34:33 -1000 Subject: [PATCH 0604/1818] Implement suggested_area in the device registry (#45940) Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/area_registry.py | 70 ++++++++--- homeassistant/helpers/device_registry.py | 17 +++ homeassistant/helpers/entity_platform.py | 1 + tests/components/config/test_area_registry.py | 4 +- tests/helpers/test_area_registry.py | 77 +++++++++++- tests/helpers/test_device_registry.py | 113 +++++++++++++++++- tests/helpers/test_entity_platform.py | 2 + 7 files changed, 258 insertions(+), 26 deletions(-) diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 562e832cc199cc..164207a8b2ad91 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -25,6 +25,7 @@ class AreaEntry: """Area Registry Entry.""" name: str = attr.ib() + normalized_name: str = attr.ib() id: Optional[str] = attr.ib(default=None) def generate_id(self, existing_ids: Container[str]) -> None: @@ -45,27 +46,47 @@ def __init__(self, hass: HomeAssistantType) -> None: self.hass = hass self.areas: MutableMapping[str, AreaEntry] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + self._normalized_name_area_idx: Dict[str, str] = {} @callback def async_get_area(self, area_id: str) -> Optional[AreaEntry]: - """Get all areas.""" + """Get area by id.""" return self.areas.get(area_id) + @callback + def async_get_area_by_name(self, name: str) -> Optional[AreaEntry]: + """Get area by name.""" + normalized_name = normalize_area_name(name) + if normalized_name not in self._normalized_name_area_idx: + return None + return self.areas[self._normalized_name_area_idx[normalized_name]] + @callback def async_list_areas(self) -> Iterable[AreaEntry]: """Get all areas.""" return self.areas.values() + @callback + def async_get_or_create(self, name: str) -> AreaEntry: + """Get or create an area.""" + area = self.async_get_area_by_name(name) + if area: + return area + return self.async_create(name) + @callback def async_create(self, name: str) -> AreaEntry: """Create a new area.""" - if self._async_is_registered(name): - raise ValueError("Name is already in use") + normalized_name = normalize_area_name(name) + + if self.async_get_area_by_name(name): + raise ValueError(f"The name {name} ({normalized_name}) is already in use") - area = AreaEntry(name=name) + area = AreaEntry(name=name, normalized_name=normalized_name) area.generate_id(self.areas) assert area.id is not None self.areas[area.id] = area + self._normalized_name_area_idx[normalized_name] = area.id self.async_schedule_save() self.hass.bus.async_fire( EVENT_AREA_REGISTRY_UPDATED, {"action": "create", "area_id": area.id} @@ -75,12 +96,14 @@ def async_create(self, name: str) -> AreaEntry: @callback def async_delete(self, area_id: str) -> None: """Delete area.""" + area = self.areas[area_id] device_registry = dr.async_get(self.hass) entity_registry = er.async_get(self.hass) device_registry.async_clear_area_id(area_id) entity_registry.async_clear_area_id(area_id) del self.areas[area_id] + del self._normalized_name_area_idx[area.normalized_name] self.hass.bus.async_fire( EVENT_AREA_REGISTRY_UPDATED, {"action": "remove", "area_id": area_id} @@ -107,23 +130,25 @@ def _async_update(self, area_id: str, name: str) -> AreaEntry: if name == old.name: return old - if self._async_is_registered(name): - raise ValueError("Name is already in use") + normalized_name = normalize_area_name(name) + + if normalized_name != old.normalized_name: + if self.async_get_area_by_name(name): + raise ValueError( + f"The name {name} ({normalized_name}) is already in use" + ) changes["name"] = name + changes["normalized_name"] = normalized_name new = self.areas[area_id] = attr.evolve(old, **changes) + self._normalized_name_area_idx[ + normalized_name + ] = self._normalized_name_area_idx.pop(old.normalized_name) + self.async_schedule_save() return new - @callback - def _async_is_registered(self, name: str) -> Optional[AreaEntry]: - """Check if a name is currently registered.""" - for area in self.areas.values(): - if name == area.name: - return area - return None - async def async_load(self) -> None: """Load the area registry.""" data = await self._store.async_load() @@ -132,7 +157,11 @@ async def async_load(self) -> None: if data is not None: for area in data["areas"]: - areas[area["id"]] = AreaEntry(name=area["name"], id=area["id"]) + normalized_name = normalize_area_name(area["name"]) + areas[area["id"]] = AreaEntry( + name=area["name"], id=area["id"], normalized_name=normalized_name + ) + self._normalized_name_area_idx[normalized_name] = area["id"] self.areas = areas @@ -147,7 +176,11 @@ def _data_to_save(self) -> Dict[str, List[Dict[str, Optional[str]]]]: data = {} data["areas"] = [ - {"name": entry.name, "id": entry.id} for entry in self.areas.values() + { + "name": entry.name, + "id": entry.id, + } + for entry in self.areas.values() ] return data @@ -173,3 +206,8 @@ async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: This is deprecated and will be removed in the future. Use async_get instead. """ return async_get(hass) + + +def normalize_area_name(area_name: str) -> str: + """Normalize an area name by removing whitespace and case folding.""" + return area_name.casefold().replace(" ", "") diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 77dc2cdf609937..3dd44364604103 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -71,6 +71,7 @@ class DeviceEntry: ) ), ) + suggested_area: Optional[str] = attr.ib(default=None) @property def disabled(self) -> bool: @@ -251,6 +252,7 @@ def async_get_or_create( via_device: Optional[Tuple[str, str]] = None, # To disable a device if it gets created disabled_by: Union[str, None, UndefinedType] = UNDEFINED, + suggested_area: Union[str, None, UndefinedType] = UNDEFINED, ) -> Optional[DeviceEntry]: """Get device. Create if it doesn't exist.""" if not identifiers and not connections: @@ -304,6 +306,7 @@ def async_get_or_create( sw_version=sw_version, entry_type=entry_type, disabled_by=disabled_by, + suggested_area=suggested_area, ) @callback @@ -321,6 +324,7 @@ def async_update_device( via_device_id: Union[str, None, UndefinedType] = UNDEFINED, remove_config_entry_id: Union[str, UndefinedType] = UNDEFINED, disabled_by: Union[str, None, UndefinedType] = UNDEFINED, + suggested_area: Union[str, None, UndefinedType] = UNDEFINED, ) -> Optional[DeviceEntry]: """Update properties of a device.""" return self._async_update_device( @@ -335,6 +339,7 @@ def async_update_device( via_device_id=via_device_id, remove_config_entry_id=remove_config_entry_id, disabled_by=disabled_by, + suggested_area=suggested_area, ) @callback @@ -356,6 +361,7 @@ def _async_update_device( area_id: Union[str, None, UndefinedType] = UNDEFINED, name_by_user: Union[str, None, UndefinedType] = UNDEFINED, disabled_by: Union[str, None, UndefinedType] = UNDEFINED, + suggested_area: Union[str, None, UndefinedType] = UNDEFINED, ) -> Optional[DeviceEntry]: """Update device attributes.""" old = self.devices[device_id] @@ -364,6 +370,16 @@ def _async_update_device( config_entries = old.config_entries + if ( + suggested_area not in (UNDEFINED, None, "") + and area_id is UNDEFINED + and old.area_id is None + ): + area = self.hass.helpers.area_registry.async_get( + self.hass + ).async_get_or_create(suggested_area) + area_id = area.id + if ( add_config_entry_id is not UNDEFINED and add_config_entry_id not in old.config_entries @@ -403,6 +419,7 @@ def _async_update_device( ("entry_type", entry_type), ("via_device_id", via_device_id), ("disabled_by", disabled_by), + ("suggested_area", suggested_area), ): if value is not UNDEFINED and value != getattr(old, attr_name): changes[attr_name] = value diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 509508405a4b4a..2caf7fe46ab0ed 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -399,6 +399,7 @@ async def _async_add_entity( # type: ignore[no-untyped-def] "sw_version", "entry_type", "via_device", + "suggested_area", ): if key in device_info: processed_dev_info[key] = device_info[key] diff --git a/tests/components/config/test_area_registry.py b/tests/components/config/test_area_registry.py index f66e16e606fc2e..35176cc79f90fd 100644 --- a/tests/components/config/test_area_registry.py +++ b/tests/components/config/test_area_registry.py @@ -55,7 +55,7 @@ async def test_create_area_with_name_already_in_use(hass, client, registry): assert not msg["success"] assert msg["error"]["code"] == "invalid_info" - assert msg["error"]["message"] == "Name is already in use" + assert msg["error"]["message"] == "The name mock (mock) is already in use" assert len(registry.areas) == 1 @@ -147,5 +147,5 @@ async def test_update_area_with_name_already_in_use(hass, client, registry): assert not msg["success"] assert msg["error"]["code"] == "invalid_info" - assert msg["error"]["message"] == "Name is already in use" + assert msg["error"]["message"] == "The name mock 2 (mock2) is already in use" assert len(registry.areas) == 2 diff --git a/tests/helpers/test_area_registry.py b/tests/helpers/test_area_registry.py index 0bfa5e597d276a..7dca029987e85c 100644 --- a/tests/helpers/test_area_registry.py +++ b/tests/helpers/test_area_registry.py @@ -58,7 +58,7 @@ async def test_create_area_with_name_already_in_use(hass, registry, update_event with pytest.raises(ValueError) as e_info: area2 = registry.async_create("mock") assert area1 != area2 - assert e_info == "Name is already in use" + assert e_info == "The name mock 2 (mock2) is already in use" await hass.async_block_till_done() @@ -133,6 +133,18 @@ async def test_update_area_with_same_name(registry): assert len(registry.areas) == 1 +async def test_update_area_with_same_name_change_case(registry): + """Make sure that we can reapply the same name with a different case to the area.""" + area = registry.async_create("mock") + + updated_area = registry.async_update(area.id, name="Mock") + + assert updated_area.name == "Mock" + assert updated_area.id == area.id + assert updated_area.normalized_name == area.normalized_name + assert len(registry.areas) == 1 + + async def test_update_area_with_name_already_in_use(registry): """Make sure that we can't update an area with a name already in use.""" area1 = registry.async_create("mock1") @@ -140,17 +152,31 @@ async def test_update_area_with_name_already_in_use(registry): with pytest.raises(ValueError) as e_info: registry.async_update(area1.id, name="mock2") - assert e_info == "Name is already in use" + assert e_info == "The name mock 2 (mock2) is already in use" assert area1.name == "mock1" assert area2.name == "mock2" assert len(registry.areas) == 2 +async def test_update_area_with_normalized_name_already_in_use(registry): + """Make sure that we can't update an area with a normalized name already in use.""" + area1 = registry.async_create("mock1") + area2 = registry.async_create("Moc k2") + + with pytest.raises(ValueError) as e_info: + registry.async_update(area1.id, name="mock2") + assert e_info == "The name mock 2 (mock2) is already in use" + + assert area1.name == "mock1" + assert area2.name == "Moc k2" + assert len(registry.areas) == 2 + + async def test_load_area(hass, registry): """Make sure that we can load/save data correctly.""" - registry.async_create("mock1") - registry.async_create("mock2") + area1 = registry.async_create("mock1") + area2 = registry.async_create("mock2") assert len(registry.areas) == 2 @@ -160,6 +186,11 @@ async def test_load_area(hass, registry): assert list(registry.areas) == list(registry2.areas) + area1_registry2 = registry2.async_get_or_create("mock1") + assert area1_registry2.id == area1.id + area2_registry2 = registry2.async_get_or_create("mock2") + assert area2_registry2.id == area2.id + @pytest.mark.parametrize("load_registries", [False]) async def test_loading_area_from_storage(hass, hass_storage): @@ -173,3 +204,41 @@ async def test_loading_area_from_storage(hass, hass_storage): registry = area_registry.async_get(hass) assert len(registry.areas) == 1 + + +async def test_async_get_or_create(hass, registry): + """Make sure we can get the area by name.""" + area = registry.async_get_or_create("Mock1") + area2 = registry.async_get_or_create("mock1") + area3 = registry.async_get_or_create("mock 1") + + assert area == area2 + assert area == area3 + assert area2 == area3 + + +async def test_async_get_area_by_name(hass, registry): + """Make sure we can get the area by name.""" + registry.async_create("Mock1") + + assert len(registry.areas) == 1 + + assert registry.async_get_area_by_name("M o c k 1").normalized_name == "mock1" + + +async def test_async_get_area_by_name_not_found(hass, registry): + """Make sure we return None for non-existent areas.""" + registry.async_create("Mock1") + + assert len(registry.areas) == 1 + + assert registry.async_get_area_by_name("non_exist") is None + + +async def test_async_get_area(hass, registry): + """Make sure we can get the area by id.""" + area = registry.async_create("Mock1") + + assert len(registry.areas) == 1 + + assert registry.async_get_area(area.id).normalized_name == "mock1" diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index a128f8aa3903fd..bc0e1c3bec95d8 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -8,7 +8,12 @@ from homeassistant.core import CoreState, callback from homeassistant.helpers import device_registry, entity_registry -from tests.common import MockConfigEntry, flush_store, mock_device_registry +from tests.common import ( + MockConfigEntry, + flush_store, + mock_area_registry, + mock_device_registry, +) @pytest.fixture @@ -17,6 +22,12 @@ def registry(hass): return mock_device_registry(hass) +@pytest.fixture +def area_registry(hass): + """Return an empty, loaded, registry.""" + return mock_area_registry(hass) + + @pytest.fixture def update_events(hass): """Capture update events.""" @@ -31,7 +42,9 @@ def async_capture(event): return events -async def test_get_or_create_returns_same_entry(hass, registry, update_events): +async def test_get_or_create_returns_same_entry( + hass, registry, area_registry, update_events +): """Make sure we do not duplicate entries.""" entry = registry.async_get_or_create( config_entry_id="1234", @@ -41,6 +54,7 @@ async def test_get_or_create_returns_same_entry(hass, registry, update_events): name="name", manufacturer="manufacturer", model="model", + suggested_area="Game Room", ) entry2 = registry.async_get_or_create( config_entry_id="1234", @@ -48,21 +62,31 @@ async def test_get_or_create_returns_same_entry(hass, registry, update_events): identifiers={("bridgeid", "0123")}, manufacturer="manufacturer", model="model", + suggested_area="Game Room", ) entry3 = registry.async_get_or_create( config_entry_id="1234", connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) + game_room_area = area_registry.async_get_area_by_name("Game Room") + assert game_room_area is not None + assert len(area_registry.areas) == 1 + assert len(registry.devices) == 1 + assert entry.area_id == game_room_area.id assert entry.id == entry2.id assert entry.id == entry3.id assert entry.identifiers == {("bridgeid", "0123")} + assert entry2.area_id == game_room_area.id + assert entry3.manufacturer == "manufacturer" assert entry3.model == "model" assert entry3.name == "name" assert entry3.sw_version == "sw-version" + assert entry3.suggested_area == "Game Room" + assert entry3.area_id == game_room_area.id await hass.async_block_till_done() @@ -154,6 +178,7 @@ async def test_loading_from_storage(hass, hass_storage): "area_id": "12345A", "name_by_user": "Test Friendly Name", "disabled_by": "user", + "suggested_area": "Kitchen", } ], "deleted_devices": [ @@ -444,7 +469,7 @@ async def test_specifying_via_device_update(registry): assert light.via_device_id == via.id -async def test_loading_saving_data(hass, registry): +async def test_loading_saving_data(hass, registry, area_registry): """Test that we load/save data correctly.""" orig_via = registry.async_get_or_create( config_entry_id="123", @@ -506,7 +531,18 @@ async def test_loading_saving_data(hass, registry): assert orig_light4.id == orig_light3.id - assert len(registry.devices) == 3 + orig_kitchen_light = registry.async_get_or_create( + config_entry_id="999", + connections=set(), + identifiers={("hue", "999")}, + manufacturer="manufacturer", + model="light", + via_device=("hue", "0123"), + disabled_by="user", + suggested_area="Kitchen", + ) + + assert len(registry.devices) == 4 assert len(registry.deleted_devices) == 1 orig_via = registry.async_update_device( @@ -530,6 +566,16 @@ async def test_loading_saving_data(hass, registry): assert orig_light == new_light assert orig_light4 == new_light4 + # Ensure a save/load cycle does not keep suggested area + new_kitchen_light = registry2.async_get_device({("hue", "999")}) + assert orig_kitchen_light.suggested_area == "Kitchen" + + orig_kitchen_light_witout_suggested_area = registry.async_update_device( + orig_kitchen_light.id, suggested_area=None + ) + orig_kitchen_light_witout_suggested_area.suggested_area is None + assert orig_kitchen_light_witout_suggested_area == new_kitchen_light + async def test_no_unnecessary_changes(registry): """Make sure we do not consider devices changes.""" @@ -706,6 +752,33 @@ async def test_update_sw_version(registry): assert updated_entry.sw_version == sw_version +async def test_update_suggested_area(registry, area_registry): + """Verify that we can update the suggested area version of a device.""" + entry = registry.async_get_or_create( + config_entry_id="1234", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bla", "123")}, + ) + assert not entry.suggested_area + assert entry.area_id is None + + suggested_area = "Pool" + + with patch.object(registry, "async_schedule_save") as mock_save: + updated_entry = registry.async_update_device( + entry.id, suggested_area=suggested_area + ) + + assert mock_save.call_count == 1 + assert updated_entry != entry + assert updated_entry.suggested_area == suggested_area + + pool_area = area_registry.async_get_area_by_name("Pool") + assert pool_area is not None + assert updated_entry.area_id == pool_area.id + assert len(area_registry.areas) == 1 + + async def test_cleanup_device_registry(hass, registry): """Test cleanup works.""" config_entry = MockConfigEntry(domain="hue") @@ -1104,3 +1177,35 @@ async def test_get_or_create_sets_default_values(hass, registry): assert entry.name == "default name 1" assert entry.model == "default model 1" assert entry.manufacturer == "default manufacturer 1" + + +async def test_verify_suggested_area_does_not_overwrite_area_id( + hass, registry, area_registry +): + """Make sure suggested area does not override a set area id.""" + game_room_area = area_registry.async_create("Game Room") + + original_entry = registry.async_get_or_create( + config_entry_id="1234", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + sw_version="sw-version", + name="name", + manufacturer="manufacturer", + model="model", + ) + entry = registry.async_update_device(original_entry.id, area_id=game_room_area.id) + + assert entry.area_id == game_room_area.id + + entry2 = registry.async_get_or_create( + config_entry_id="1234", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + sw_version="sw-version", + name="name", + manufacturer="manufacturer", + model="model", + suggested_area="New Game Room", + ) + assert entry2.area_id == game_room_area.id diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 0a939ba2825089..ab3e04843f939a 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -728,6 +728,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): "model": "test-model", "name": "test-name", "sw_version": "test-sw", + "suggested_area": "Heliport", "entry_type": "service", "via_device": ("hue", "via-id"), }, @@ -755,6 +756,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): assert device.model == "test-model" assert device.name == "test-name" assert device.sw_version == "test-sw" + assert device.suggested_area == "Heliport" assert device.entry_type == "service" assert device.via_device_id == via.id From 71586b766138f1440ce3e15a897870986e409a99 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 19:42:14 -1000 Subject: [PATCH 0605/1818] Cleanup inconsistencies in zha fan and make it a bit more dry (#46714) Co-authored-by: Martin Hjelmare --- homeassistant/components/zha/fan.py | 42 ++++++++++++++--------------- tests/components/zha/test_fan.py | 12 +++++++++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index 1cd66f94686fa0..ed041f8c6c01f6 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -13,11 +13,13 @@ DOMAIN, SUPPORT_SET_SPEED, FanEntity, + NotValidPresetModeError, ) from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import State, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.percentage import ( + int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -75,7 +77,7 @@ class BaseFan(FanEntity): """Base representation of a ZHA fan.""" @property - def preset_modes(self) -> str: + def preset_modes(self) -> List[str]: """Return the available preset modes.""" return PRESET_MODES @@ -84,10 +86,17 @@ def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) + async def async_turn_on( self, speed=None, percentage=None, preset_mode=None, **kwargs ) -> None: """Turn the entity on.""" + if percentage is None: + percentage = DEFAULT_ON_PERCENTAGE await self.async_set_percentage(percentage) async def async_turn_off(self, **kwargs) -> None: @@ -96,15 +105,16 @@ async def async_turn_off(self, **kwargs) -> None: async def async_set_percentage(self, percentage: Optional[int]) -> None: """Set the speed percenage of the fan.""" - if percentage is None: - percentage = DEFAULT_ON_PERCENTAGE fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) await self._async_set_fan_mode(fan_mode) async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set the speed percenage of the fan.""" - fan_mode = NAME_TO_PRESET_MODE.get(preset_mode) - await self._async_set_fan_mode(fan_mode) + """Set the preset mode for the fan.""" + if preset_mode not in self.preset_modes: + raise NotValidPresetModeError( + f"The preset_mode {preset_mode} is not a valid preset_mode: {self.preset_modes}" + ) + await self._async_set_fan_mode(NAME_TO_PRESET_MODE[preset_mode]) @abstractmethod async def _async_set_fan_mode(self, fan_mode: int) -> None: @@ -132,7 +142,7 @@ async def async_added_to_hass(self): ) @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if ( self._fan_channel.fan_mode is None @@ -144,7 +154,7 @@ def percentage(self) -> str: return ranged_value_to_percentage(SPEED_RANGE, self._fan_channel.fan_mode) @property - def preset_mode(self) -> str: + def preset_mode(self) -> Optional[str]: """Return the current preset mode.""" return PRESET_MODES_TO_NAME.get(self._fan_channel.fan_mode) @@ -175,27 +185,15 @@ def __init__( self._preset_mode = None @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" return self._percentage @property - def preset_mode(self) -> str: + def preset_mode(self) -> Optional[str]: """Return the current preset mode.""" return self._preset_mode - async def async_set_percentage(self, percentage: Optional[int]) -> None: - """Set the speed percenage of the fan.""" - if percentage is None: - percentage = DEFAULT_ON_PERCENTAGE - fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) - await self._async_set_fan_mode(fan_mode) - - async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set the speed percenage of the fan.""" - fan_mode = NAME_TO_PRESET_MODE.get(preset_mode) - await self._async_set_fan_mode(fan_mode) - async def _async_set_fan_mode(self, fan_mode: int) -> None: """Set the fan mode for the group.""" try: diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index b6347ac6568a53..81a441a4101dcc 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -11,6 +11,7 @@ from homeassistant.components import fan from homeassistant.components.fan import ( ATTR_PERCENTAGE, + ATTR_PERCENTAGE_STEP, ATTR_PRESET_MODE, ATTR_SPEED, DOMAIN, @@ -20,6 +21,7 @@ SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, + NotValidPresetModeError, ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.zha.core.discovery import GROUP_PROBE @@ -188,6 +190,14 @@ async def test_fan(hass, zha_device_joined_restored, zigpy_device): assert len(cluster.write_attributes.mock_calls) == 1 assert cluster.write_attributes.call_args == call({"fan_mode": 4}) + # set invalid preset_mode from HA + cluster.write_attributes.reset_mock() + with pytest.raises(NotValidPresetModeError): + await async_set_preset_mode( + hass, entity_id, preset_mode="invalid does not exist" + ) + assert len(cluster.write_attributes.mock_calls) == 0 + # test adding new fan to the network and HA await async_test_rejoin(hass, zigpy_device, [cluster], (1,)) @@ -450,6 +460,7 @@ async def test_fan_update_entity( assert hass.states.get(entity_id).attributes[ATTR_SPEED] == SPEED_OFF assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE] == 0 assert hass.states.get(entity_id).attributes[ATTR_PRESET_MODE] is None + assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE_STEP] == 100 / 3 assert cluster.read_attributes.await_count == 1 await async_setup_component(hass, "homeassistant", {}) @@ -470,4 +481,5 @@ async def test_fan_update_entity( assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE] == 33 assert hass.states.get(entity_id).attributes[ATTR_SPEED] == SPEED_LOW assert hass.states.get(entity_id).attributes[ATTR_PRESET_MODE] is None + assert hass.states.get(entity_id).attributes[ATTR_PERCENTAGE_STEP] == 100 / 3 assert cluster.read_attributes.await_count == 3 From 2f3c2f5f4d713ba954237e426340f41eb3e782c0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 19:44:15 -1000 Subject: [PATCH 0606/1818] Add support for using a single endpoint for rest data (#46711) --- homeassistant/components/rest/__init__.py | 172 ++++++++- .../components/rest/binary_sensor.py | 159 +++----- homeassistant/components/rest/const.py | 20 ++ homeassistant/components/rest/entity.py | 89 +++++ homeassistant/components/rest/notify.py | 5 - homeassistant/components/rest/schema.py | 99 +++++ homeassistant/components/rest/sensor.py | 172 ++------- homeassistant/components/rest/switch.py | 7 - tests/components/rest/test_init.py | 340 ++++++++++++++++++ tests/components/rest/test_notify.py | 4 + tests/components/rest/test_sensor.py | 32 ++ tests/components/rest/test_switch.py | 22 +- tests/fixtures/rest/configuration_empty.yaml | 0 .../rest/configuration_invalid.notyaml | 2 + .../rest/configuration_top_level.yaml | 12 + 15 files changed, 860 insertions(+), 275 deletions(-) create mode 100644 homeassistant/components/rest/const.py create mode 100644 homeassistant/components/rest/entity.py create mode 100644 homeassistant/components/rest/schema.py create mode 100644 tests/components/rest/test_init.py create mode 100644 tests/fixtures/rest/configuration_empty.yaml create mode 100644 tests/fixtures/rest/configuration_invalid.notyaml create mode 100644 tests/fixtures/rest/configuration_top_level.yaml diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index 69bc61723418f6..ebeddcfd7c7c22 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -1,4 +1,174 @@ """The rest component.""" -DOMAIN = "rest" +import asyncio +import logging + +import httpx +import voluptuous as vol + +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_HEADERS, + CONF_METHOD, + CONF_PARAMS, + CONF_PASSWORD, + CONF_PAYLOAD, + CONF_RESOURCE, + CONF_RESOURCE_TEMPLATE, + CONF_SCAN_INTERVAL, + CONF_TIMEOUT, + CONF_USERNAME, + CONF_VERIFY_SSL, + HTTP_DIGEST_AUTHENTICATION, + SERVICE_RELOAD, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import discovery +from homeassistant.helpers.entity_component import ( + DEFAULT_SCAN_INTERVAL, + EntityComponent, +) +from homeassistant.helpers.reload import async_reload_integration_platforms +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_IDX +from .data import RestData +from .schema import CONFIG_SCHEMA # noqa:F401 pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + PLATFORMS = ["binary_sensor", "notify", "sensor", "switch"] +COORDINATOR_AWARE_PLATFORMS = [SENSOR_DOMAIN, BINARY_SENSOR_DOMAIN] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the rest platforms.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + _async_setup_shared_data(hass) + + async def reload_service_handler(service): + """Remove all user-defined groups and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS) + _async_setup_shared_data(hass) + await _async_process_config(hass, conf) + + hass.services.async_register( + DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) + ) + + return await _async_process_config(hass, config) + + +@callback +def _async_setup_shared_data(hass: HomeAssistant): + """Create shared data for platform config and rest coordinators.""" + hass.data[DOMAIN] = {platform: {} for platform in COORDINATOR_AWARE_PLATFORMS} + + +async def _async_process_config(hass, config) -> bool: + """Process rest configuration.""" + if DOMAIN not in config: + return True + + refresh_tasks = [] + load_tasks = [] + for rest_idx, conf in enumerate(config[DOMAIN]): + scan_interval = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + resource_template = conf.get(CONF_RESOURCE_TEMPLATE) + rest = create_rest_data_from_config(hass, conf) + coordinator = _wrap_rest_in_coordinator( + hass, rest, resource_template, scan_interval + ) + refresh_tasks.append(coordinator.async_refresh()) + hass.data[DOMAIN][rest_idx] = {REST: rest, COORDINATOR: coordinator} + + for platform_domain in COORDINATOR_AWARE_PLATFORMS: + if platform_domain not in conf: + continue + + for platform_idx, platform_conf in enumerate(conf[platform_domain]): + hass.data[DOMAIN][platform_domain][platform_idx] = platform_conf + + load = discovery.async_load_platform( + hass, + platform_domain, + DOMAIN, + {REST_IDX: rest_idx, PLATFORM_IDX: platform_idx}, + config, + ) + load_tasks.append(load) + + if refresh_tasks: + await asyncio.gather(*refresh_tasks) + + if load_tasks: + await asyncio.gather(*load_tasks) + + return True + + +async def async_get_config_and_coordinator(hass, platform_domain, discovery_info): + """Get the config and coordinator for the platform from discovery.""" + shared_data = hass.data[DOMAIN][discovery_info[REST_IDX]] + conf = hass.data[DOMAIN][platform_domain][discovery_info[PLATFORM_IDX]] + coordinator = shared_data[COORDINATOR] + rest = shared_data[REST] + if rest.data is None: + await coordinator.async_request_refresh() + return conf, coordinator, rest + + +def _wrap_rest_in_coordinator(hass, rest, resource_template, update_interval): + """Wrap a DataUpdateCoordinator around the rest object.""" + if resource_template: + + async def _async_refresh_with_resource_template(): + rest.set_url(resource_template.async_render(parse_result=False)) + await rest.async_update() + + update_method = _async_refresh_with_resource_template + else: + update_method = rest.async_update + + return DataUpdateCoordinator( + hass, + _LOGGER, + name="rest data", + update_method=update_method, + update_interval=update_interval, + ) + + +def create_rest_data_from_config(hass, config): + """Create RestData from config.""" + resource = config.get(CONF_RESOURCE) + resource_template = config.get(CONF_RESOURCE_TEMPLATE) + method = config.get(CONF_METHOD) + payload = config.get(CONF_PAYLOAD) + verify_ssl = config.get(CONF_VERIFY_SSL) + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + headers = config.get(CONF_HEADERS) + params = config.get(CONF_PARAMS) + timeout = config.get(CONF_TIMEOUT) + + if resource_template is not None: + resource_template.hass = hass + resource = resource_template.async_render(parse_result=False) + + if username and password: + if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: + auth = httpx.DigestAuth(username, password) + else: + auth = (username, password) + else: + auth = None + + return RestData( + hass, method, resource, auth, headers, params, payload, verify_ssl, timeout + ) diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py index 49c10354c5138b..9692f5b9339a88 100644 --- a/homeassistant/components/rest/binary_sensor.py +++ b/homeassistant/components/rest/binary_sensor.py @@ -1,64 +1,27 @@ """Support for RESTful binary sensors.""" -import httpx import voluptuous as vol from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, + DOMAIN as BINARY_SENSOR_DOMAIN, PLATFORM_SCHEMA, BinarySensorEntity, ) from homeassistant.const import ( - CONF_AUTHENTICATION, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, - CONF_HEADERS, - CONF_METHOD, CONF_NAME, - CONF_PARAMS, - CONF_PASSWORD, - CONF_PAYLOAD, CONF_RESOURCE, CONF_RESOURCE_TEMPLATE, - CONF_TIMEOUT, - CONF_USERNAME, CONF_VALUE_TEMPLATE, - CONF_VERIFY_SSL, - HTTP_BASIC_AUTHENTICATION, - HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service - -from . import DOMAIN, PLATFORMS -from .data import DEFAULT_TIMEOUT, RestData - -DEFAULT_METHOD = "GET" -DEFAULT_NAME = "REST Binary Sensor" -DEFAULT_VERIFY_SSL = True -DEFAULT_FORCE_UPDATE = False - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Exclusive(CONF_RESOURCE, CONF_RESOURCE): cv.url, - vol.Exclusive(CONF_RESOURCE_TEMPLATE, CONF_RESOURCE): cv.template, - vol.Optional(CONF_AUTHENTICATION): vol.In( - [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] - ), - vol.Optional(CONF_HEADERS): {cv.string: cv.string}, - vol.Optional(CONF_PARAMS): {cv.string: cv.string}, - vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(["POST", "GET"]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PAYLOAD): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - } -) + +from . import async_get_config_and_coordinator, create_rest_data_from_config +from .entity import RestEntity +from .schema import BINARY_SENSOR_SCHEMA, RESOURCE_SCHEMA + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({**RESOURCE_SCHEMA, **BINARY_SENSOR_SCHEMA}) PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_RESOURCE, CONF_RESOURCE_TEMPLATE), PLATFORM_SCHEMA @@ -67,51 +30,34 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the REST binary sensor.""" - - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - - name = config.get(CONF_NAME) - resource = config.get(CONF_RESOURCE) - resource_template = config.get(CONF_RESOURCE_TEMPLATE) - method = config.get(CONF_METHOD) - payload = config.get(CONF_PAYLOAD) - verify_ssl = config.get(CONF_VERIFY_SSL) - timeout = config.get(CONF_TIMEOUT) - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - headers = config.get(CONF_HEADERS) - params = config.get(CONF_PARAMS) - device_class = config.get(CONF_DEVICE_CLASS) - value_template = config.get(CONF_VALUE_TEMPLATE) - force_update = config.get(CONF_FORCE_UPDATE) - - if resource_template is not None: - resource_template.hass = hass - resource = resource_template.async_render(parse_result=False) - - if value_template is not None: - value_template.hass = hass - - if username and password: - if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: - auth = httpx.DigestAuth(username, password) - else: - auth = (username, password) + # Must update the sensor now (including fetching the rest resource) to + # ensure it's updating its state. + if discovery_info is not None: + conf, coordinator, rest = await async_get_config_and_coordinator( + hass, BINARY_SENSOR_DOMAIN, discovery_info + ) else: - auth = None - - rest = RestData( - hass, method, resource, auth, headers, params, payload, verify_ssl, timeout - ) - await rest.async_update() + conf = config + coordinator = None + rest = create_rest_data_from_config(hass, conf) + await rest.async_update() if rest.data is None: raise PlatformNotReady + name = conf.get(CONF_NAME) + device_class = conf.get(CONF_DEVICE_CLASS) + value_template = conf.get(CONF_VALUE_TEMPLATE) + force_update = conf.get(CONF_FORCE_UPDATE) + resource_template = conf.get(CONF_RESOURCE_TEMPLATE) + + if value_template is not None: + value_template.hass = hass + async_add_entities( [ RestBinarySensor( - hass, + coordinator, rest, name, device_class, @@ -123,12 +69,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class RestBinarySensor(BinarySensorEntity): +class RestBinarySensor(RestEntity, BinarySensorEntity): """Representation of a REST binary sensor.""" def __init__( self, - hass, + coordinator, rest, name, device_class, @@ -137,36 +83,23 @@ def __init__( resource_template, ): """Initialize a REST binary sensor.""" - self._hass = hass - self.rest = rest - self._name = name - self._device_class = device_class + super().__init__( + coordinator, rest, name, device_class, resource_template, force_update + ) self._state = False self._previous_data = None self._value_template = value_template - self._force_update = force_update - self._resource_template = resource_template - - @property - def name(self): - """Return the name of the binary sensor.""" - return self._name - - @property - def device_class(self): - """Return the class of this sensor.""" - return self._device_class - - @property - def available(self): - """Return the availability of this sensor.""" - return self.rest.data is not None + self._is_on = None @property def is_on(self): """Return true if the binary sensor is on.""" + return self._is_on + + def _update_from_rest_data(self): + """Update state from the rest data.""" if self.rest.data is None: - return False + self._is_on = False response = self.rest.data @@ -176,20 +109,8 @@ def is_on(self): ) try: - return bool(int(response)) + self._is_on = bool(int(response)) except ValueError: - return {"true": True, "on": True, "open": True, "yes": True}.get( + self._is_on = {"true": True, "on": True, "open": True, "yes": True}.get( response.lower(), False ) - - @property - def force_update(self): - """Force update.""" - return self._force_update - - async def async_update(self): - """Get the latest data from REST API and updates the state.""" - if self._resource_template is not None: - self.rest.set_url(self._resource_template.async_render(parse_result=False)) - - await self.rest.async_update() diff --git a/homeassistant/components/rest/const.py b/homeassistant/components/rest/const.py new file mode 100644 index 00000000000000..31216b65968b71 --- /dev/null +++ b/homeassistant/components/rest/const.py @@ -0,0 +1,20 @@ +"""The rest component constants.""" + +DOMAIN = "rest" + +DEFAULT_METHOD = "GET" +DEFAULT_VERIFY_SSL = True +DEFAULT_FORCE_UPDATE = False + +DEFAULT_BINARY_SENSOR_NAME = "REST Binary Sensor" +DEFAULT_SENSOR_NAME = "REST Sensor" +CONF_JSON_ATTRS = "json_attributes" +CONF_JSON_ATTRS_PATH = "json_attributes_path" + +REST_IDX = "rest_idx" +PLATFORM_IDX = "platform_idx" + +COORDINATOR = "coordinator" +REST = "rest" + +METHODS = ["POST", "GET"] diff --git a/homeassistant/components/rest/entity.py b/homeassistant/components/rest/entity.py new file mode 100644 index 00000000000000..acfe5a2dfc5c1e --- /dev/null +++ b/homeassistant/components/rest/entity.py @@ -0,0 +1,89 @@ +"""The base entity for the rest component.""" + +from abc import abstractmethod +from typing import Any + +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .data import RestData + + +class RestEntity(Entity): + """A class for entities using DataUpdateCoordinator or rest data directly.""" + + def __init__( + self, + coordinator: DataUpdateCoordinator[Any], + rest: RestData, + name, + device_class, + resource_template, + force_update, + ) -> None: + """Create the entity that may have a coordinator.""" + self.coordinator = coordinator + self.rest = rest + self._name = name + self._device_class = device_class + self._resource_template = resource_template + self._force_update = force_update + super().__init__() + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def device_class(self): + """Return the class of this sensor.""" + return self._device_class + + @property + def force_update(self): + """Force update.""" + return self._force_update + + @property + def should_poll(self) -> bool: + """Poll only if we do noty have a coordinator.""" + return not self.coordinator + + @property + def available(self): + """Return the availability of this sensor.""" + if self.coordinator and not self.coordinator.last_update_success: + return False + return self.rest.data is not None + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self._update_from_rest_data() + if self.coordinator: + self.async_on_remove( + self.coordinator.async_add_listener(self._handle_coordinator_update) + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._update_from_rest_data() + self.async_write_ha_state() + + async def async_update(self): + """Get the latest data from REST API and update the state.""" + if self.coordinator: + await self.coordinator.async_request_refresh() + return + + if self._resource_template is not None: + self.rest.set_url(self._resource_template.async_render(parse_result=False)) + await self.rest.async_update() + self._update_from_rest_data() + + @abstractmethod + def _update_from_rest_data(self): + """Update state from the rest data.""" diff --git a/homeassistant/components/rest/notify.py b/homeassistant/components/rest/notify.py index f15df428640328..198e5b06c52865 100644 --- a/homeassistant/components/rest/notify.py +++ b/homeassistant/components/rest/notify.py @@ -29,11 +29,8 @@ HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import setup_reload_service from homeassistant.helpers.template import Template -from . import DOMAIN, PLATFORMS - CONF_DATA = "data" CONF_DATA_TEMPLATE = "data_template" CONF_MESSAGE_PARAMETER_NAME = "message_param_name" @@ -73,8 +70,6 @@ def get_service(hass, config, discovery_info=None): """Get the RESTful notification service.""" - setup_reload_service(hass, DOMAIN, PLATFORMS) - resource = config.get(CONF_RESOURCE) method = config.get(CONF_METHOD) headers = config.get(CONF_HEADERS) diff --git a/homeassistant/components/rest/schema.py b/homeassistant/components/rest/schema.py new file mode 100644 index 00000000000000..bedd02d272a438 --- /dev/null +++ b/homeassistant/components/rest/schema.py @@ -0,0 +1,99 @@ +"""The rest component schemas.""" + +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, + DOMAIN as BINARY_SENSOR_DOMAIN, +) +from homeassistant.components.sensor import ( + DEVICE_CLASSES_SCHEMA as SENSOR_DEVICE_CLASSES_SCHEMA, + DOMAIN as SENSOR_DOMAIN, +) +from homeassistant.const import ( + CONF_AUTHENTICATION, + CONF_DEVICE_CLASS, + CONF_FORCE_UPDATE, + CONF_HEADERS, + CONF_METHOD, + CONF_NAME, + CONF_PARAMS, + CONF_PASSWORD, + CONF_PAYLOAD, + CONF_RESOURCE, + CONF_RESOURCE_TEMPLATE, + CONF_SCAN_INTERVAL, + CONF_TIMEOUT, + CONF_UNIT_OF_MEASUREMENT, + CONF_USERNAME, + CONF_VALUE_TEMPLATE, + CONF_VERIFY_SSL, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, +) +import homeassistant.helpers.config_validation as cv + +from .const import ( + CONF_JSON_ATTRS, + CONF_JSON_ATTRS_PATH, + DEFAULT_BINARY_SENSOR_NAME, + DEFAULT_FORCE_UPDATE, + DEFAULT_METHOD, + DEFAULT_SENSOR_NAME, + DEFAULT_VERIFY_SSL, + DOMAIN, + METHODS, +) +from .data import DEFAULT_TIMEOUT + +RESOURCE_SCHEMA = { + vol.Exclusive(CONF_RESOURCE, CONF_RESOURCE): cv.url, + vol.Exclusive(CONF_RESOURCE_TEMPLATE, CONF_RESOURCE): cv.template, + vol.Optional(CONF_AUTHENTICATION): vol.In( + [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] + ), + vol.Optional(CONF_HEADERS): vol.Schema({cv.string: cv.string}), + vol.Optional(CONF_PARAMS): vol.Schema({cv.string: cv.string}), + vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(METHODS), + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PAYLOAD): cv.string, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, +} + +SENSOR_SCHEMA = { + vol.Optional(CONF_NAME, default=DEFAULT_SENSOR_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv, + vol.Optional(CONF_JSON_ATTRS_PATH): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, +} + +BINARY_SENSOR_SCHEMA = { + vol.Optional(CONF_NAME, default=DEFAULT_BINARY_SENSOR_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, +} + + +COMBINED_SCHEMA = vol.Schema( + { + vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, + **RESOURCE_SCHEMA, + vol.Optional(SENSOR_DOMAIN): vol.All( + cv.ensure_list, [vol.Schema(SENSOR_SCHEMA)] + ), + vol.Optional(BINARY_SENSOR_DOMAIN): vol.All( + cv.ensure_list, [vol.Schema(BINARY_SENSOR_SCHEMA)] + ), + } +) + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.All(cv.ensure_list, [COMBINED_SCHEMA])}, + extra=vol.ALLOW_EXTRA, +) diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 85d79b6b33172a..0699d9dc07c5a6 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -3,76 +3,31 @@ import logging from xml.parsers.expat import ExpatError -import httpx from jsonpath import jsonpath import voluptuous as vol import xmltodict -from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_AUTHENTICATION, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, - CONF_HEADERS, - CONF_METHOD, CONF_NAME, - CONF_PARAMS, - CONF_PASSWORD, - CONF_PAYLOAD, CONF_RESOURCE, CONF_RESOURCE_TEMPLATE, - CONF_TIMEOUT, CONF_UNIT_OF_MEASUREMENT, - CONF_USERNAME, CONF_VALUE_TEMPLATE, - CONF_VERIFY_SSL, - HTTP_BASIC_AUTHENTICATION, - HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.reload import async_setup_reload_service -from . import DOMAIN, PLATFORMS -from .data import DEFAULT_TIMEOUT, RestData +from . import async_get_config_and_coordinator, create_rest_data_from_config +from .const import CONF_JSON_ATTRS, CONF_JSON_ATTRS_PATH +from .entity import RestEntity +from .schema import RESOURCE_SCHEMA, SENSOR_SCHEMA _LOGGER = logging.getLogger(__name__) -DEFAULT_METHOD = "GET" -DEFAULT_NAME = "REST Sensor" -DEFAULT_VERIFY_SSL = True -DEFAULT_FORCE_UPDATE = False - - -CONF_JSON_ATTRS = "json_attributes" -CONF_JSON_ATTRS_PATH = "json_attributes_path" -METHODS = ["POST", "GET"] - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Exclusive(CONF_RESOURCE, CONF_RESOURCE): cv.url, - vol.Exclusive(CONF_RESOURCE_TEMPLATE, CONF_RESOURCE): cv.template, - vol.Optional(CONF_AUTHENTICATION): vol.In( - [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] - ), - vol.Optional(CONF_HEADERS): vol.Schema({cv.string: cv.string}), - vol.Optional(CONF_PARAMS): vol.Schema({cv.string: cv.string}), - vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv, - vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(METHODS), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PAYLOAD): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_JSON_ATTRS_PATH): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - } -) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({**RESOURCE_SCHEMA, **SENSOR_SCHEMA}) PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_RESOURCE, CONF_RESOURCE_TEMPLATE), PLATFORM_SCHEMA @@ -81,55 +36,37 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the RESTful sensor.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - - name = config.get(CONF_NAME) - resource = config.get(CONF_RESOURCE) - resource_template = config.get(CONF_RESOURCE_TEMPLATE) - method = config.get(CONF_METHOD) - payload = config.get(CONF_PAYLOAD) - verify_ssl = config.get(CONF_VERIFY_SSL) - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - headers = config.get(CONF_HEADERS) - params = config.get(CONF_PARAMS) - unit = config.get(CONF_UNIT_OF_MEASUREMENT) - device_class = config.get(CONF_DEVICE_CLASS) - value_template = config.get(CONF_VALUE_TEMPLATE) - json_attrs = config.get(CONF_JSON_ATTRS) - json_attrs_path = config.get(CONF_JSON_ATTRS_PATH) - force_update = config.get(CONF_FORCE_UPDATE) - timeout = config.get(CONF_TIMEOUT) - - if value_template is not None: - value_template.hass = hass - - if resource_template is not None: - resource_template.hass = hass - resource = resource_template.async_render(parse_result=False) - - if username and password: - if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION: - auth = httpx.DigestAuth(username, password) - else: - auth = (username, password) + # Must update the sensor now (including fetching the rest resource) to + # ensure it's updating its state. + if discovery_info is not None: + conf, coordinator, rest = await async_get_config_and_coordinator( + hass, SENSOR_DOMAIN, discovery_info + ) else: - auth = None - rest = RestData( - hass, method, resource, auth, headers, params, payload, verify_ssl, timeout - ) - - await rest.async_update() + conf = config + coordinator = None + rest = create_rest_data_from_config(hass, conf) + await rest.async_update() if rest.data is None: raise PlatformNotReady - # Must update the sensor now (including fetching the rest resource) to - # ensure it's updating its state. + name = conf.get(CONF_NAME) + unit = conf.get(CONF_UNIT_OF_MEASUREMENT) + device_class = conf.get(CONF_DEVICE_CLASS) + json_attrs = conf.get(CONF_JSON_ATTRS) + json_attrs_path = conf.get(CONF_JSON_ATTRS_PATH) + value_template = conf.get(CONF_VALUE_TEMPLATE) + force_update = conf.get(CONF_FORCE_UPDATE) + resource_template = conf.get(CONF_RESOURCE_TEMPLATE) + + if value_template is not None: + value_template.hass = hass + async_add_entities( [ RestSensor( - hass, + coordinator, rest, name, unit, @@ -144,12 +81,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class RestSensor(Entity): +class RestSensor(RestEntity): """Implementation of a REST sensor.""" def __init__( self, - hass, + coordinator, rest, name, unit_of_measurement, @@ -161,60 +98,30 @@ def __init__( json_attrs_path, ): """Initialize the REST sensor.""" - self._hass = hass - self.rest = rest - self._name = name + super().__init__( + coordinator, rest, name, device_class, resource_template, force_update + ) self._state = None self._unit_of_measurement = unit_of_measurement - self._device_class = device_class self._value_template = value_template self._json_attrs = json_attrs self._attributes = None - self._force_update = force_update - self._resource_template = resource_template self._json_attrs_path = json_attrs_path - @property - def name(self): - """Return the name of the sensor.""" - return self._name - @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" return self._unit_of_measurement - @property - def device_class(self): - """Return the class of this sensor.""" - return self._device_class - - @property - def available(self): - """Return if the sensor data are available.""" - return self.rest.data is not None - @property def state(self): """Return the state of the device.""" return self._state @property - def force_update(self): - """Force update.""" - return self._force_update - - async def async_update(self): - """Get the latest data from REST API and update the state.""" - if self._resource_template is not None: - self.rest.set_url(self._resource_template.async_render(parse_result=False)) - - await self.rest.async_update() - self._update_from_rest_data() - - async def async_added_to_hass(self): - """Ensure the data from the initial update is reflected in the state.""" - self._update_from_rest_data() + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes def _update_from_rest_data(self): """Update state from the rest data.""" @@ -273,8 +180,3 @@ def _update_from_rest_data(self): ) self._state = value - - @property - def device_state_attributes(self): - """Return the state attributes.""" - return self._attributes diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py index ea480d549f3ac2..e8ae1dee0158b6 100644 --- a/homeassistant/components/rest/switch.py +++ b/homeassistant/components/rest/switch.py @@ -22,12 +22,8 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service - -from . import DOMAIN, PLATFORMS _LOGGER = logging.getLogger(__name__) - CONF_BODY_OFF = "body_off" CONF_BODY_ON = "body_on" CONF_IS_ON_TEMPLATE = "is_on_template" @@ -65,9 +61,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the RESTful switch.""" - - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - body_off = config.get(CONF_BODY_OFF) body_on = config.get(CONF_BODY_ON) is_on_template = config.get(CONF_IS_ON_TEMPLATE) diff --git a/tests/components/rest/test_init.py b/tests/components/rest/test_init.py new file mode 100644 index 00000000000000..19a5651e9898f5 --- /dev/null +++ b/tests/components/rest/test_init.py @@ -0,0 +1,340 @@ +"""Tests for rest component.""" + +import asyncio +from datetime import timedelta +from os import path +from unittest.mock import patch + +import respx + +from homeassistant import config as hass_config +from homeassistant.components.rest.const import DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + DATA_MEGABYTES, + SERVICE_RELOAD, + STATE_UNAVAILABLE, +) +from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed + + +@respx.mock +async def test_setup_with_endpoint_timeout_with_recovery(hass): + """Test setup with an endpoint that times out that recovers.""" + await async_setup_component(hass, "homeassistant", {}) + + respx.get("http://localhost").mock(side_effect=asyncio.TimeoutError()) + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://localhost", + "method": "GET", + "verify_ssl": "false", + "timeout": 30, + "sensor": [ + { + "unit_of_measurement": DATA_MEGABYTES, + "name": "sensor1", + "value_template": "{{ value_json.sensor1 }}", + }, + { + "unit_of_measurement": DATA_MEGABYTES, + "name": "sensor2", + "value_template": "{{ value_json.sensor2 }}", + }, + ], + "binary_sensor": [ + { + "name": "binary_sensor1", + "value_template": "{{ value_json.binary_sensor1 }}", + }, + { + "name": "binary_sensor2", + "value_template": "{{ value_json.binary_sensor2 }}", + }, + ], + } + ] + }, + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + respx.get("http://localhost").respond( + status_code=200, + json={ + "sensor1": "1", + "sensor2": "2", + "binary_sensor1": "on", + "binary_sensor2": "off", + }, + ) + + # Refresh the coordinator + async_fire_time_changed(hass, utcnow() + timedelta(seconds=31)) + await hass.async_block_till_done() + + # Wait for platform setup retry + async_fire_time_changed(hass, utcnow() + timedelta(seconds=61)) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 4 + + assert hass.states.get("sensor.sensor1").state == "1" + assert hass.states.get("sensor.sensor2").state == "2" + assert hass.states.get("binary_sensor.binary_sensor1").state == "on" + assert hass.states.get("binary_sensor.binary_sensor2").state == "off" + + # Now the end point flakes out again + respx.get("http://localhost").mock(side_effect=asyncio.TimeoutError()) + + # Refresh the coordinator + async_fire_time_changed(hass, utcnow() + timedelta(seconds=31)) + await hass.async_block_till_done() + + assert hass.states.get("sensor.sensor1").state == STATE_UNAVAILABLE + assert hass.states.get("sensor.sensor2").state == STATE_UNAVAILABLE + assert hass.states.get("binary_sensor.binary_sensor1").state == STATE_UNAVAILABLE + assert hass.states.get("binary_sensor.binary_sensor2").state == STATE_UNAVAILABLE + + # We request a manual refresh when the + # endpoint is working again + + respx.get("http://localhost").respond( + status_code=200, + json={ + "sensor1": "1", + "sensor2": "2", + "binary_sensor1": "on", + "binary_sensor2": "off", + }, + ) + + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["sensor.sensor1"]}, + blocking=True, + ) + assert hass.states.get("sensor.sensor1").state == "1" + assert hass.states.get("sensor.sensor2").state == "2" + assert hass.states.get("binary_sensor.binary_sensor1").state == "on" + assert hass.states.get("binary_sensor.binary_sensor2").state == "off" + + +@respx.mock +async def test_setup_minimum_resource_template(hass): + """Test setup with minimum configuration (resource_template).""" + + respx.get("http://localhost").respond( + status_code=200, + json={ + "sensor1": "1", + "sensor2": "2", + "binary_sensor1": "on", + "binary_sensor2": "off", + }, + ) + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource_template": "{% set url = 'http://localhost' %}{{ url }}", + "method": "GET", + "verify_ssl": "false", + "timeout": 30, + "sensor": [ + { + "unit_of_measurement": DATA_MEGABYTES, + "name": "sensor1", + "value_template": "{{ value_json.sensor1 }}", + }, + { + "unit_of_measurement": DATA_MEGABYTES, + "name": "sensor2", + "value_template": "{{ value_json.sensor2 }}", + }, + ], + "binary_sensor": [ + { + "name": "binary_sensor1", + "value_template": "{{ value_json.binary_sensor1 }}", + }, + { + "name": "binary_sensor2", + "value_template": "{{ value_json.binary_sensor2 }}", + }, + ], + } + ] + }, + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 4 + + assert hass.states.get("sensor.sensor1").state == "1" + assert hass.states.get("sensor.sensor2").state == "2" + assert hass.states.get("binary_sensor.binary_sensor1").state == "on" + assert hass.states.get("binary_sensor.binary_sensor2").state == "off" + + +@respx.mock +async def test_reload(hass): + """Verify we can reload.""" + + respx.get("http://localhost") % 200 + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://localhost", + "method": "GET", + "verify_ssl": "false", + "timeout": 30, + "sensor": [ + { + "name": "mockrest", + }, + ], + } + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + assert hass.states.get("sensor.mockrest") + + yaml_path = path.join( + _get_fixtures_base_path(), + "fixtures", + "rest/configuration_top_level.yaml", + ) + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + "rest", + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.mockreset") is None + assert hass.states.get("sensor.rollout") + assert hass.states.get("sensor.fallover") + + +@respx.mock +async def test_reload_and_remove_all(hass): + """Verify we can reload and remove all.""" + + respx.get("http://localhost") % 200 + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://localhost", + "method": "GET", + "verify_ssl": "false", + "timeout": 30, + "sensor": [ + { + "name": "mockrest", + }, + ], + } + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + assert hass.states.get("sensor.mockrest") + + yaml_path = path.join( + _get_fixtures_base_path(), + "fixtures", + "rest/configuration_empty.yaml", + ) + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + "rest", + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.mockreset") is None + + +@respx.mock +async def test_reload_fails_to_read_configuration(hass): + """Verify reload when configuration is missing or broken.""" + + respx.get("http://localhost") % 200 + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://localhost", + "method": "GET", + "verify_ssl": "false", + "timeout": 30, + "sensor": [ + { + "name": "mockrest", + }, + ], + } + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + yaml_path = path.join( + _get_fixtures_base_path(), + "fixtures", + "rest/configuration_invalid.notyaml", + ) + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + "rest", + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + +def _get_fixtures_base_path(): + return path.dirname(path.dirname(path.dirname(__file__))) diff --git a/tests/components/rest/test_notify.py b/tests/components/rest/test_notify.py index aa3e40c2dd4388..fb7b8a31238608 100644 --- a/tests/components/rest/test_notify.py +++ b/tests/components/rest/test_notify.py @@ -2,6 +2,8 @@ from os import path from unittest.mock import patch +import respx + from homeassistant import config as hass_config import homeassistant.components.notify as notify from homeassistant.components.rest import DOMAIN @@ -9,8 +11,10 @@ from homeassistant.setup import async_setup_component +@respx.mock async def test_reload_notify(hass): """Verify we can reload the notify service.""" + respx.get("http://localhost") % 200 assert await async_setup_component( hass, diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 58309cd7532e7c..2e308f69384d07 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -91,6 +91,38 @@ async def test_setup_minimum(hass): assert len(hass.states.async_all()) == 1 +@respx.mock +async def test_manual_update(hass): + """Test setup with minimum configuration.""" + await async_setup_component(hass, "homeassistant", {}) + respx.get("http://localhost").respond(status_code=200, json={"data": "first"}) + assert await async_setup_component( + hass, + sensor.DOMAIN, + { + "sensor": { + "name": "mysensor", + "value_template": "{{ value_json.data }}", + "platform": "rest", + "resource_template": "{% set url = 'http://localhost' %}{{ url }}", + "method": "GET", + } + }, + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 1 + assert hass.states.get("sensor.mysensor").state == "first" + + respx.get("http://localhost").respond(status_code=200, json={"data": "second"}) + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["sensor.mysensor"]}, + blocking=True, + ) + assert hass.states.get("sensor.mysensor").state == "second" + + @respx.mock async def test_setup_minimum_resource_template(hass): """Test setup with minimum configuration (resource_template).""" diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index 5e0c9fbeab32dd..7141a34203a00c 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -3,6 +3,7 @@ import aiohttp +from homeassistant.components.rest import DOMAIN import homeassistant.components.rest.switch as rest from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( @@ -34,14 +35,14 @@ async def test_setup_missing_config(hass): """Test setup with configuration missing required entries.""" - assert not await rest.async_setup_platform(hass, {CONF_PLATFORM: rest.DOMAIN}, None) + assert not await rest.async_setup_platform(hass, {CONF_PLATFORM: DOMAIN}, None) async def test_setup_missing_schema(hass): """Test setup with resource missing schema.""" assert not await rest.async_setup_platform( hass, - {CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "localhost"}, + {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: "localhost"}, None, ) @@ -51,7 +52,7 @@ async def test_setup_failed_connect(hass, aioclient_mock): aioclient_mock.get("http://localhost", exc=aiohttp.ClientError) assert not await rest.async_setup_platform( hass, - {CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "http://localhost"}, + {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: "http://localhost"}, None, ) @@ -61,7 +62,7 @@ async def test_setup_timeout(hass, aioclient_mock): aioclient_mock.get("http://localhost", exc=asyncio.TimeoutError()) assert not await rest.async_setup_platform( hass, - {CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "http://localhost"}, + {CONF_PLATFORM: DOMAIN, CONF_RESOURCE: "http://localhost"}, None, ) @@ -75,11 +76,12 @@ async def test_setup_minimum(hass, aioclient_mock): SWITCH_DOMAIN, { SWITCH_DOMAIN: { - CONF_PLATFORM: rest.DOMAIN, + CONF_PLATFORM: DOMAIN, CONF_RESOURCE: "http://localhost", } }, ) + await hass.async_block_till_done() assert aioclient_mock.call_count == 1 @@ -92,12 +94,14 @@ async def test_setup_query_params(hass, aioclient_mock): SWITCH_DOMAIN, { SWITCH_DOMAIN: { - CONF_PLATFORM: rest.DOMAIN, + CONF_PLATFORM: DOMAIN, CONF_RESOURCE: "http://localhost", CONF_PARAMS: {"search": "something"}, } }, ) + await hass.async_block_till_done() + print(aioclient_mock) assert aioclient_mock.call_count == 1 @@ -110,7 +114,7 @@ async def test_setup(hass, aioclient_mock): SWITCH_DOMAIN, { SWITCH_DOMAIN: { - CONF_PLATFORM: rest.DOMAIN, + CONF_PLATFORM: DOMAIN, CONF_NAME: "foo", CONF_RESOURCE: "http://localhost", CONF_HEADERS: {"Content-type": CONTENT_TYPE_JSON}, @@ -119,6 +123,7 @@ async def test_setup(hass, aioclient_mock): } }, ) + await hass.async_block_till_done() assert aioclient_mock.call_count == 1 assert_setup_component(1, SWITCH_DOMAIN) @@ -132,7 +137,7 @@ async def test_setup_with_state_resource(hass, aioclient_mock): SWITCH_DOMAIN, { SWITCH_DOMAIN: { - CONF_PLATFORM: rest.DOMAIN, + CONF_PLATFORM: DOMAIN, CONF_NAME: "foo", CONF_RESOURCE: "http://localhost", rest.CONF_STATE_RESOURCE: "http://localhost/state", @@ -142,6 +147,7 @@ async def test_setup_with_state_resource(hass, aioclient_mock): } }, ) + await hass.async_block_till_done() assert aioclient_mock.call_count == 1 assert_setup_component(1, SWITCH_DOMAIN) diff --git a/tests/fixtures/rest/configuration_empty.yaml b/tests/fixtures/rest/configuration_empty.yaml new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/tests/fixtures/rest/configuration_invalid.notyaml b/tests/fixtures/rest/configuration_invalid.notyaml new file mode 100644 index 00000000000000..548d8bcf5a0f42 --- /dev/null +++ b/tests/fixtures/rest/configuration_invalid.notyaml @@ -0,0 +1,2 @@ +*!* NOT YAML + diff --git a/tests/fixtures/rest/configuration_top_level.yaml b/tests/fixtures/rest/configuration_top_level.yaml new file mode 100644 index 00000000000000..df27e1601176f5 --- /dev/null +++ b/tests/fixtures/rest/configuration_top_level.yaml @@ -0,0 +1,12 @@ +rest: + - method: GET + resource: "http://localhost" + sensor: + name: fallover + +sensor: + - platform: rest + resource: "http://localhost" + method: GET + name: rollout + From 54cf9543531619d4d3963f635c3c20963ece4831 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 20 Feb 2021 06:50:59 +0100 Subject: [PATCH 0607/1818] Add device_entities template function/filter (#46406) --- homeassistant/helpers/template.py | 52 +++++++++++++-------- tests/helpers/test_template.py | 75 +++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 200d678719ac74..7377120af40a20 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -33,7 +33,7 @@ ) from homeassistant.core import State, callback, split_entity_id, valid_entity_id from homeassistant.exceptions import TemplateError -from homeassistant.helpers import location as loc_helper +from homeassistant.helpers import entity_registry, location as loc_helper from homeassistant.helpers.typing import HomeAssistantType, TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util import convert, dt as dt_util, location as loc_util @@ -48,6 +48,7 @@ _RENDER_INFO = "template.render_info" _ENVIRONMENT = "template.environment" +_ENVIRONMENT_LIMITED = "template.environment_limited" _RE_JINJA_DELIMITERS = re.compile(r"\{%|\{\{|\{#") # Match "simple" ints and floats. -1.0, 1, +5, 5.0 @@ -300,11 +301,12 @@ def __init__(self, template, hass=None): @property def _env(self) -> TemplateEnvironment: - if self.hass is None or self._limited: + if self.hass is None: return _NO_HASS_ENV - ret: Optional[TemplateEnvironment] = self.hass.data.get(_ENVIRONMENT) + wanted_env = _ENVIRONMENT_LIMITED if self._limited else _ENVIRONMENT + ret: Optional[TemplateEnvironment] = self.hass.data.get(wanted_env) if ret is None: - ret = self.hass.data[_ENVIRONMENT] = TemplateEnvironment(self.hass) # type: ignore[no-untyped-call] + ret = self.hass.data[wanted_env] = TemplateEnvironment(self.hass, self._limited) # type: ignore[no-untyped-call] return ret def ensure_valid(self) -> None: @@ -867,6 +869,13 @@ def expand(hass: HomeAssistantType, *args: Any) -> Iterable[State]: return sorted(found.values(), key=lambda a: a.entity_id) +def device_entities(hass: HomeAssistantType, device_id: str) -> Iterable[str]: + """Get entity ids for entities tied to a device.""" + entity_reg = entity_registry.async_get(hass) + entries = entity_registry.async_entries_for_device(entity_reg, device_id) + return [entry.entity_id for entry in entries] + + def closest(hass, *args): """Find closest entity. @@ -1311,7 +1320,7 @@ def urlencode(value): class TemplateEnvironment(ImmutableSandboxedEnvironment): """The Home Assistant template environment.""" - def __init__(self, hass): + def __init__(self, hass, limited=False): """Initialise template environment.""" super().__init__() self.hass = hass @@ -1368,7 +1377,27 @@ def __init__(self, hass): self.globals["strptime"] = strptime self.globals["urlencode"] = urlencode if hass is None: + return + + # We mark these as a context functions to ensure they get + # evaluated fresh with every execution, rather than executed + # at compile time and the value stored. The context itself + # can be discarded, we only need to get at the hass object. + def hassfunction(func): + """Wrap function that depend on hass.""" + + @wraps(func) + def wrapper(*args, **kwargs): + return func(hass, *args[1:], **kwargs) + + return contextfunction(wrapper) + + self.globals["device_entities"] = hassfunction(device_entities) + self.filters["device_entities"] = contextfilter(self.globals["device_entities"]) + if limited: + # Only device_entities is available to limited templates, mark other + # functions and filters as unsupported. def unsupported(name): def warn_unsupported(*args, **kwargs): raise TemplateError( @@ -1395,19 +1424,6 @@ def warn_unsupported(*args, **kwargs): self.filters[filt] = unsupported(filt) return - # We mark these as a context functions to ensure they get - # evaluated fresh with every execution, rather than executed - # at compile time and the value stored. The context itself - # can be discarded, we only need to get at the hass object. - def hassfunction(func): - """Wrap function that depend on hass.""" - - @wraps(func) - def wrapper(*args, **kwargs): - return func(hass, *args[1:], **kwargs) - - return contextfunction(wrapper) - self.globals["expand"] = hassfunction(expand) self.filters["expand"] = contextfilter(self.globals["expand"]) self.globals["closest"] = hassfunction(closest) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 174d61ea47049c..4259e7302ed9fc 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -24,6 +24,8 @@ import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import UnitSystem +from tests.common import MockConfigEntry, mock_device_registry, mock_registry + def _set_up_units(hass): """Set up the tests.""" @@ -1470,6 +1472,79 @@ async def test_expand(hass): assert info.rate_limit is None +async def test_device_entities(hass): + """Test expand function.""" + config_entry = MockConfigEntry(domain="light") + device_registry = mock_device_registry(hass) + entity_registry = mock_registry(hass) + + # Test non existing device ids + info = render_to_info(hass, "{{ device_entities('abc123') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ device_entities(56) }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test device without entities + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + info = render_to_info(hass, f"{{{{ device_entities('{device_entry.id}') }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test device with single entity, which has no state + entity_registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + device_id=device_entry.id, + ) + info = render_to_info(hass, f"{{{{ device_entities('{device_entry.id}') }}}}") + assert_result_info(info, ["light.hue_5678"], []) + assert info.rate_limit is None + info = render_to_info( + hass, + f"{{{{ device_entities('{device_entry.id}') | expand | map(attribute='entity_id') | join(', ') }}}}", + ) + assert_result_info(info, "", ["light.hue_5678"]) + assert info.rate_limit is None + + # Test device with single entity, with state + hass.states.async_set("light.hue_5678", "happy") + info = render_to_info( + hass, + f"{{{{ device_entities('{device_entry.id}') | expand | map(attribute='entity_id') | join(', ') }}}}", + ) + assert_result_info(info, "light.hue_5678", ["light.hue_5678"]) + assert info.rate_limit is None + + # Test device with multiple entities, which have a state + entity_registry.async_get_or_create( + "light", + "hue", + "ABCD", + config_entry=config_entry, + device_id=device_entry.id, + ) + hass.states.async_set("light.hue_abcd", "camper") + info = render_to_info(hass, f"{{{{ device_entities('{device_entry.id}') }}}}") + assert_result_info(info, ["light.hue_5678", "light.hue_abcd"], []) + assert info.rate_limit is None + info = render_to_info( + hass, + f"{{{{ device_entities('{device_entry.id}') | expand | map(attribute='entity_id') | join(', ') }}}}", + ) + assert_result_info( + info, "light.hue_5678, light.hue_abcd", ["light.hue_5678", "light.hue_abcd"] + ) + assert info.rate_limit is None + + def test_closest_function_to_coord(hass): """Test closest function to coord.""" hass.states.async_set( From 0e9148e239c01f2ca29e1ef2e0d69058aae24d51 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 19:57:02 -1000 Subject: [PATCH 0608/1818] Add suggested area support to nuheat (#46801) --- homeassistant/components/nuheat/climate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/nuheat/climate.py b/homeassistant/components/nuheat/climate.py index e8f21fc89c2c87..35000dd21fac79 100644 --- a/homeassistant/components/nuheat/climate.py +++ b/homeassistant/components/nuheat/climate.py @@ -291,4 +291,5 @@ def device_info(self): "name": self._thermostat.room, "model": "nVent Signature", "manufacturer": MANUFACTURER, + "suggested_area": self._thermostat.room, } From 3e334a4950ae8620167a2c470b42ebdb8dd88dfb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 19:57:21 -1000 Subject: [PATCH 0609/1818] Fix typing of fan speed count and steps (#46790) --- homeassistant/components/bond/fan.py | 2 +- homeassistant/components/demo/fan.py | 4 ++-- homeassistant/components/dyson/fan.py | 2 +- homeassistant/components/esphome/fan.py | 2 +- homeassistant/components/fan/__init__.py | 12 +++++++----- homeassistant/components/knx/fan.py | 2 +- homeassistant/components/smartthings/fan.py | 2 +- homeassistant/components/tuya/fan.py | 2 +- homeassistant/components/zwave_js/fan.py | 2 +- 9 files changed, 16 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index cef2efae690a58..5ff7e0c7065377 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -87,7 +87,7 @@ def percentage(self) -> Optional[str]: return ranged_value_to_percentage(self._speed_range, self._speed) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return int_states_in_range(self._speed_range) diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 6bbd8b81f6dd90..c79b53c0918e09 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -216,7 +216,7 @@ def percentage(self) -> str: return self._percentage @property - def speed_count(self) -> Optional[float]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return 3 @@ -276,7 +276,7 @@ def percentage(self) -> str: return self._percentage @property - def speed_count(self) -> Optional[float]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return 3 diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 9e49badbc8e57e..a8e737bb48b7e9 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -156,7 +156,7 @@ def percentage(self): return ranged_value_to_percentage(SPEED_RANGE, int(self._device.state.speed)) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return int_states_in_range(SPEED_RANGE) diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 092c416acabbd9..df23f37cb63eb9 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -120,7 +120,7 @@ def percentage(self) -> Optional[str]: ) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return len(ORDERED_NAMED_FAN_SPEEDS) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 692588cff480e5..18f46b3d6190ff 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -272,15 +272,17 @@ async def async_set_percentage(self, percentage: int) -> None: else: await self.async_set_speed(self.percentage_to_speed(percentage)) - async def async_increase_speed(self, percentage_step=None) -> None: + async def async_increase_speed(self, percentage_step: Optional[int] = None) -> None: """Increase the speed of the fan.""" await self._async_adjust_speed(1, percentage_step) - async def async_decrease_speed(self, percentage_step=None) -> None: + async def async_decrease_speed(self, percentage_step: Optional[int] = None) -> None: """Decrease the speed of the fan.""" await self._async_adjust_speed(-1, percentage_step) - async def _async_adjust_speed(self, modifier, percentage_step) -> None: + async def _async_adjust_speed( + self, modifier: int, percentage_step: Optional[int] + ) -> None: """Increase or decrease the speed of the fan.""" current_percentage = self.percentage or 0 @@ -462,7 +464,7 @@ def percentage(self) -> Optional[int]: return 0 @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" speed_list = speed_list_without_preset_modes(self.speed_list) if speed_list: @@ -470,7 +472,7 @@ def speed_count(self) -> Optional[int]: return 100 @property - def percentage_step(self) -> Optional[float]: + def percentage_step(self) -> float: """Return the step size for percentage.""" return 100 / self.speed_count diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index d0b7b4c55466ba..6aa4f72289281f 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -70,7 +70,7 @@ def percentage(self) -> Optional[int]: return self._device.current_speed @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" if self._step_range is None: return super().speed_count diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 12edac36dfe8ac..4cd451e2416b9a 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -81,7 +81,7 @@ def percentage(self) -> str: return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return int_states_in_range(SPEED_RANGE) diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index 4c555bb942a5fc..cb6f96358c9a26 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -103,7 +103,7 @@ def oscillate(self, oscillating) -> None: self._tuya.oscillate(oscillating) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" if self.speeds is None: return super().speed_count diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index ae903d9efaf4e0..ea17fbe4cff843 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -98,7 +98,7 @@ def percentage(self) -> Optional[int]: return ranged_value_to_percentage(SPEED_RANGE, self.info.primary_value.value) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return int_states_in_range(SPEED_RANGE) From 2807cb1de783939251f29611534c532c8f6807d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 19:57:37 -1000 Subject: [PATCH 0610/1818] Implement suggested area for netatmo (#46802) --- homeassistant/components/netatmo/climate.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index dee8d3b668df3b..34a94df008a643 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -567,6 +567,11 @@ def _service_set_schedule(self, **kwargs): schedule_id, ) + @property + def device_info(self): + """Return the device info for the thermostat.""" + return {**super().device_info, "suggested_area": self._room_data["name"]} + def interpolate(batterylevel, module_type): """Interpolate battery level depending on device type.""" From 11277faa93da7bc18447e1ac5973a4c8c92c8de0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:01:22 -1000 Subject: [PATCH 0611/1818] Add suggested area to nexia (#46776) --- homeassistant/components/nexia/entity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nexia/entity.py b/homeassistant/components/nexia/entity.py index 7820ebb6216539..62f6e8275c4053 100644 --- a/homeassistant/components/nexia/entity.py +++ b/homeassistant/components/nexia/entity.py @@ -83,10 +83,12 @@ def __init__(self, coordinator, zone, name, unique_id): def device_info(self): """Return the device_info of the device.""" data = super().device_info + zone_name = self._zone.get_name() data.update( { "identifiers": {(DOMAIN, self._zone.zone_id)}, - "name": self._zone.get_name(), + "name": zone_name, + "suggested_area": zone_name, "via_device": (DOMAIN, self._zone.thermostat.thermostat_id), } ) From 4078a8782edcf4ec60a5e193fdd499dae5e05efe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:17:00 -1000 Subject: [PATCH 0612/1818] Add suggested area to hunterdouglas_powerview (#46774) --- .../components/hunterdouglas_powerview/cover.py | 11 +++++------ .../components/hunterdouglas_powerview/entity.py | 9 ++++++--- .../components/hunterdouglas_powerview/scene.py | 16 +++++++--------- .../components/hunterdouglas_powerview/sensor.py | 8 +++++++- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index d96beec53aee1f..e90b315fd168ab 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -77,9 +77,11 @@ async def async_setup_entry(hass, entry, async_add_entities): name_before_refresh, ) continue + room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) + room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") entities.append( PowerViewShade( - shade, name_before_refresh, room_data, coordinator, device_info + coordinator, device_info, room_name, shade, name_before_refresh ) ) async_add_entities(entities) @@ -98,17 +100,14 @@ def hass_position_to_hd(hass_positon): class PowerViewShade(ShadeEntity, CoverEntity): """Representation of a powerview shade.""" - def __init__(self, shade, name, room_data, coordinator, device_info): + def __init__(self, coordinator, device_info, room_name, shade, name): """Initialize the shade.""" - room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) - super().__init__(coordinator, device_info, shade, name) + super().__init__(coordinator, device_info, room_name, shade, name) self._shade = shade - self._device_info = device_info self._is_opening = False self._is_closing = False self._last_action_timestamp = 0 self._scheduled_transition_update = None - self._room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") self._current_cover_position = MIN_POSITION @property diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py index 4ed68fc3557fd2..679e55e806c797 100644 --- a/homeassistant/components/hunterdouglas_powerview/entity.py +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -23,9 +23,10 @@ class HDEntity(CoordinatorEntity): """Base class for hunter douglas entities.""" - def __init__(self, coordinator, device_info, unique_id): + def __init__(self, coordinator, device_info, room_name, unique_id): """Initialize the entity.""" super().__init__(coordinator) + self._room_name = room_name self._unique_id = unique_id self._device_info = device_info @@ -45,6 +46,7 @@ def device_info(self): (dr.CONNECTION_NETWORK_MAC, self._device_info[DEVICE_MAC_ADDRESS]) }, "name": self._device_info[DEVICE_NAME], + "suggested_area": self._room_name, "model": self._device_info[DEVICE_MODEL], "sw_version": sw_version, "manufacturer": MANUFACTURER, @@ -54,9 +56,9 @@ def device_info(self): class ShadeEntity(HDEntity): """Base class for hunter douglas shade entities.""" - def __init__(self, coordinator, device_info, shade, shade_name): + def __init__(self, coordinator, device_info, room_name, shade, shade_name): """Initialize the shade.""" - super().__init__(coordinator, device_info, shade.id) + super().__init__(coordinator, device_info, room_name, shade.id) self._shade_name = shade_name self._shade = shade @@ -67,6 +69,7 @@ def device_info(self): device_info = { "identifiers": {(DOMAIN, self._shade.id)}, "name": self._shade_name, + "suggested_area": self._room_name, "manufacturer": MANUFACTURER, "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), } diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index 61c93078aa1238..33c7e7129fc2de 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -49,23 +49,21 @@ async def async_setup_entry(hass, entry, async_add_entities): coordinator = pv_data[COORDINATOR] device_info = pv_data[DEVICE_INFO] - pvscenes = ( - PowerViewScene( - PvScene(raw_scene, pv_request), room_data, coordinator, device_info - ) - for scene_id, raw_scene in scene_data.items() - ) + pvscenes = [] + for raw_scene in scene_data.values(): + scene = PvScene(raw_scene, pv_request) + room_name = room_data.get(scene.room_id, {}).get(ROOM_NAME_UNICODE, "") + pvscenes.append(PowerViewScene(coordinator, device_info, room_name, scene)) async_add_entities(pvscenes) class PowerViewScene(HDEntity, Scene): """Representation of a Powerview scene.""" - def __init__(self, scene, room_data, coordinator, device_info): + def __init__(self, coordinator, device_info, room_name, scene): """Initialize the scene.""" - super().__init__(coordinator, device_info, scene.id) + super().__init__(coordinator, device_info, room_name, scene.id) self._scene = scene - self._room_name = room_data.get(scene.room_id, {}).get(ROOM_NAME_UNICODE, "") @property def name(self): diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py index 6241ddd4d62dd0..130e8dd507a624 100644 --- a/homeassistant/components/hunterdouglas_powerview/sensor.py +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -9,7 +9,10 @@ DEVICE_INFO, DOMAIN, PV_API, + PV_ROOM_DATA, PV_SHADE_DATA, + ROOM_ID_IN_SHADE, + ROOM_NAME_UNICODE, SHADE_BATTERY_LEVEL, SHADE_BATTERY_LEVEL_MAX, ) @@ -20,6 +23,7 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up the hunter douglas shades sensors.""" pv_data = hass.data[DOMAIN][entry.entry_id] + room_data = pv_data[PV_ROOM_DATA] shade_data = pv_data[PV_SHADE_DATA] pv_request = pv_data[PV_API] coordinator = pv_data[COORDINATOR] @@ -31,9 +35,11 @@ async def async_setup_entry(hass, entry, async_add_entities): if SHADE_BATTERY_LEVEL not in shade.raw_data: continue name_before_refresh = shade.name + room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) + room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") entities.append( PowerViewShadeBatterySensor( - coordinator, device_info, shade, name_before_refresh + coordinator, device_info, room_name, shade, name_before_refresh ) ) async_add_entities(entities) From 22dbac259bc0761d99558a7daf6c6aa487ed6fc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:18:21 -1000 Subject: [PATCH 0613/1818] Ensure recorder shuts down cleanly on restart before startup is finished (#46604) --- homeassistant/components/recorder/__init__.py | 9 ++- homeassistant/components/recorder/util.py | 19 ++++++- tests/components/recorder/test_init.py | 37 +++++++++++- tests/components/recorder/test_util.py | 56 +++++++++++++++---- 4 files changed, 104 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 16232bcaa165c7..ceec7ee9eed787 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -346,8 +346,15 @@ def notify_hass_started(event): self.hass.add_job(register) result = hass_started.result() + self.event_session = self.get_session() + self.event_session.expire_on_commit = False + # If shutdown happened before Home Assistant finished starting if result is shutdown_task: + # Make sure we cleanly close the run if + # we restart before startup finishes + self._close_run() + self._close_connection() return # Start periodic purge @@ -363,8 +370,6 @@ def async_purge(now): async_purge, hour=4, minute=12, second=0 ) - self.event_session = self.get_session() - self.event_session.expire_on_commit = False # Use a session for the event read loop # with a commit every time the event time # has changed. This reduces the disk io. diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index abf14268687357..41bca335a562d2 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -171,7 +171,10 @@ def validate_sqlite_database(dbpath: str, db_integrity_check: bool) -> bool: def run_checks_on_open_db(dbpath, cursor, db_integrity_check): """Run checks that will generate a sqlite3 exception if there is corruption.""" - if basic_sanity_check(cursor) and last_run_was_recently_clean(cursor): + sanity_check_passed = basic_sanity_check(cursor) + last_run_was_clean = last_run_was_recently_clean(cursor) + + if sanity_check_passed and last_run_was_clean: _LOGGER.debug( "The quick_check will be skipped as the system was restarted cleanly and passed the basic sanity check" ) @@ -187,7 +190,19 @@ def run_checks_on_open_db(dbpath, cursor, db_integrity_check): ) return - _LOGGER.debug( + if not sanity_check_passed: + _LOGGER.warning( + "The database sanity check failed to validate the sqlite3 database at %s", + dbpath, + ) + + if not last_run_was_clean: + _LOGGER.warning( + "The system could not validate that the sqlite3 database at %s was shutdown cleanly.", + dbpath, + ) + + _LOGGER.info( "A quick_check is being performed on the sqlite3 database at %s", dbpath ) cursor.execute("PRAGMA QUICK_CHECK") diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index d4092d709c0afa..3b71648166e750 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -16,14 +16,45 @@ from homeassistant.components.recorder.const import DATA_INSTANCE from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.util import session_scope -from homeassistant.const import MATCH_ALL, STATE_LOCKED, STATE_UNLOCKED -from homeassistant.core import Context, callback +from homeassistant.const import ( + EVENT_HOMEASSISTANT_STOP, + MATCH_ALL, + STATE_LOCKED, + STATE_UNLOCKED, +) +from homeassistant.core import Context, CoreState, callback from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from .common import wait_recording_done -from tests.common import fire_time_changed, get_test_home_assistant +from tests.common import ( + async_init_recorder_component, + fire_time_changed, + get_test_home_assistant, +) + + +async def test_shutdown_before_startup_finishes(hass): + """Test shutdown before recorder starts is clean.""" + + hass.state = CoreState.not_running + + await async_init_recorder_component(hass) + await hass.async_block_till_done() + + session = await hass.async_add_executor_job(hass.data[DATA_INSTANCE].get_session) + + with patch.object(hass.data[DATA_INSTANCE], "engine"): + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + hass.stop() + + run_info = await hass.async_add_executor_job(run_information_with_session, session) + + assert run_info.run_id == 1 + assert run_info.start is not None + assert run_info.end is not None def test_saving_state(hass, hass_recorder): diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index a4109648d2f558..38df1285008aec 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -178,36 +178,72 @@ def test_basic_sanity_check(hass_recorder): util.basic_sanity_check(cursor) -def test_combined_checks(hass_recorder): +def test_combined_checks(hass_recorder, caplog): """Run Checks on the open database.""" hass = hass_recorder() - db_integrity_check = False - cursor = hass.data[DATA_INSTANCE].engine.raw_connection().cursor() - assert ( - util.run_checks_on_open_db("fake_db_path", cursor, db_integrity_check) is None - ) + assert util.run_checks_on_open_db("fake_db_path", cursor, False) is None + assert "skipped because db_integrity_check was disabled" in caplog.text + + caplog.clear() + assert util.run_checks_on_open_db("fake_db_path", cursor, True) is None + assert "could not validate that the sqlite3 database" in caplog.text + + # We are patching recorder.util here in order + # to avoid creating the full database on disk + with patch( + "homeassistant.components.recorder.util.basic_sanity_check", return_value=False + ): + caplog.clear() + assert util.run_checks_on_open_db("fake_db_path", cursor, False) is None + assert "skipped because db_integrity_check was disabled" in caplog.text + + caplog.clear() + assert util.run_checks_on_open_db("fake_db_path", cursor, True) is None + assert "could not validate that the sqlite3 database" in caplog.text # We are patching recorder.util here in order # to avoid creating the full database on disk with patch("homeassistant.components.recorder.util.last_run_was_recently_clean"): + caplog.clear() + assert util.run_checks_on_open_db("fake_db_path", cursor, False) is None assert ( - util.run_checks_on_open_db("fake_db_path", cursor, db_integrity_check) - is None + "system was restarted cleanly and passed the basic sanity check" + in caplog.text ) + caplog.clear() + assert util.run_checks_on_open_db("fake_db_path", cursor, True) is None + assert ( + "system was restarted cleanly and passed the basic sanity check" + in caplog.text + ) + + caplog.clear() + with patch( + "homeassistant.components.recorder.util.last_run_was_recently_clean", + side_effect=sqlite3.DatabaseError, + ), pytest.raises(sqlite3.DatabaseError): + util.run_checks_on_open_db("fake_db_path", cursor, False) + + caplog.clear() with patch( "homeassistant.components.recorder.util.last_run_was_recently_clean", side_effect=sqlite3.DatabaseError, ), pytest.raises(sqlite3.DatabaseError): - util.run_checks_on_open_db("fake_db_path", cursor, db_integrity_check) + util.run_checks_on_open_db("fake_db_path", cursor, True) cursor.execute("DROP TABLE events;") + caplog.clear() + with pytest.raises(sqlite3.DatabaseError): + util.run_checks_on_open_db("fake_db_path", cursor, False) + + caplog.clear() with pytest.raises(sqlite3.DatabaseError): - util.run_checks_on_open_db("fake_db_path", cursor, db_integrity_check) + util.run_checks_on_open_db("fake_db_path", cursor, True) def _corrupt_db_file(test_db_file): From 500cb172981b743ea8463183758337427efb94ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:22:48 -1000 Subject: [PATCH 0614/1818] Ensure HomeAssistant can still restart when a library file is missing (#46664) --- homeassistant/config.py | 13 ++++++++++--- tests/test_config.py | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 73d0273d1c041b..90df365c349304 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -76,6 +76,13 @@ SCRIPT_CONFIG_PATH = "scripts.yaml" SCENE_CONFIG_PATH = "scenes.yaml" +LOAD_EXCEPTIONS = (ImportError, FileNotFoundError) +INTEGRATION_LOAD_EXCEPTIONS = ( + IntegrationNotFound, + RequirementsNotFound, + *LOAD_EXCEPTIONS, +) + DEFAULT_CONFIG = f""" # Configure a default setup of Home Assistant (frontend, api, etc) default_config: @@ -689,7 +696,7 @@ async def merge_packages_config( hass, domain ) component = integration.get_component() - except (IntegrationNotFound, RequirementsNotFound, ImportError) as ex: + except INTEGRATION_LOAD_EXCEPTIONS as ex: _log_pkg_error(pack_name, comp_name, config, str(ex)) continue @@ -746,7 +753,7 @@ async def async_process_component_config( domain = integration.domain try: component = integration.get_component() - except ImportError as ex: + except LOAD_EXCEPTIONS as ex: _LOGGER.error("Unable to import %s: %s", domain, ex) return None @@ -825,7 +832,7 @@ async def async_process_component_config( try: platform = p_integration.get_platform(domain) - except ImportError: + except LOAD_EXCEPTIONS: _LOGGER.exception("Platform error: %s", domain) continue diff --git a/tests/test_config.py b/tests/test_config.py index 7dd7d61e8efab6..299cf9caa7386e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1088,6 +1088,26 @@ async def test_component_config_exceptions(hass, caplog): in caplog.text ) + # get_component raising + caplog.clear() + assert ( + await config_util.async_process_component_config( + hass, + {"test_domain": {}}, + integration=Mock( + pkg_path="homeassistant.components.test_domain", + domain="test_domain", + get_component=Mock( + side_effect=FileNotFoundError( + "No such file or directory: b'liblibc.a'" + ) + ), + ), + ) + is None + ) + assert "Unable to import test_domain: No such file or directory" in caplog.text + @pytest.mark.parametrize( "domain, schema, expected", From 749883dc62278f1e1de33c2c9a03ff20d87f9f73 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:24:49 -1000 Subject: [PATCH 0615/1818] Implement suggested area in lutron_caseta (#45941) --- homeassistant/components/lutron_caseta/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 220096fe0bfed6..56cc7a78c96619 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -229,6 +229,7 @@ async def _async_register_button_devices( dr_device = device_registry.async_get_or_create( name=device["leap_name"], + suggested_area=device["leap_name"].split("_")[0], manufacturer=MANUFACTURER, config_entry_id=config_entry_id, identifiers={(DOMAIN, device["serial"])}, @@ -344,6 +345,7 @@ def device_info(self): return { "identifiers": {(DOMAIN, self.serial)}, "name": self.name, + "suggested_area": self._device["name"].split("_")[0], "manufacturer": MANUFACTURER, "model": f"{self._device['model']} ({self._device['type']})", "via_device": (DOMAIN, self._bridge_device["serial"]), From 773a2027771d72d62fee7484d247f08d1f3032f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:37:12 -1000 Subject: [PATCH 0616/1818] Implement percentage step sizes in HomeKit (#46722) --- homeassistant/components/homekit/type_fans.py | 9 ++++++++- tests/components/homekit/test_type_fans.py | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 7ed7256d48c59f..d7215be9508760 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -7,6 +7,7 @@ ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_PERCENTAGE, + ATTR_PERCENTAGE_STEP, DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, @@ -33,6 +34,7 @@ CHAR_ROTATION_DIRECTION, CHAR_ROTATION_SPEED, CHAR_SWING_MODE, + PROP_MIN_STEP, SERV_FANV2, ) @@ -53,6 +55,7 @@ def __init__(self, *args): 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) if features & SUPPORT_DIRECTION: chars.append(CHAR_ROTATION_DIRECTION) @@ -77,7 +80,11 @@ def __init__(self, *args): # 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. - self.char_speed = serv_fan.configure_char(CHAR_ROTATION_SPEED, value=100) + self.char_speed = serv_fan.configure_char( + CHAR_ROTATION_SPEED, + value=100, + properties={PROP_MIN_STEP: percentage_step}, + ) if CHAR_SWING_MODE in chars: self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0) diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 8111d256594aff..e99f9d0b95afd1 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -6,6 +6,7 @@ ATTR_DIRECTION, ATTR_OSCILLATING, ATTR_PERCENTAGE, + ATTR_PERCENTAGE_STEP, DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, @@ -13,7 +14,7 @@ SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, ) -from homeassistant.components.homekit.const import ATTR_VALUE +from homeassistant.components.homekit.const import ATTR_VALUE, PROP_MIN_STEP from homeassistant.components.homekit.type_fans import Fan from homeassistant.const import ( ATTR_ENTITY_ID, @@ -254,6 +255,7 @@ async def test_fan_speed(hass, hk_driver, events): { ATTR_SUPPORTED_FEATURES: SUPPORT_SET_SPEED, ATTR_PERCENTAGE: 0, + ATTR_PERCENTAGE_STEP: 25, }, ) await hass.async_block_till_done() @@ -263,6 +265,7 @@ async def test_fan_speed(hass, hk_driver, events): # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the # speed to 100 when turning on a fan on a freshly booted up server. assert acc.char_speed.value != 0 + assert acc.char_speed.properties[PROP_MIN_STEP] == 25 await acc.run_handler() await hass.async_block_till_done() From 5b95f61fd36cca19bfcc24e3df1cdb098c0d109b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:38:45 -1000 Subject: [PATCH 0617/1818] Update smarty to use new fan entity model (#45879) --- homeassistant/components/smarty/fan.py | 114 +++++++++++-------------- 1 file changed, 50 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/smarty/fan.py b/homeassistant/components/smarty/fan.py index 40c244944cefc7..20376e1d44edf3 100644 --- a/homeassistant/components/smarty/fan.py +++ b/homeassistant/components/smarty/fan.py @@ -1,26 +1,25 @@ """Platform to control a Salda Smarty XP/XV ventilation unit.""" import logging +import math +from typing import Optional -from homeassistant.components.fan import ( - SPEED_HIGH, - SPEED_LOW, - SPEED_MEDIUM, - SPEED_OFF, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + int_states_in_range, + percentage_to_ranged_value, + ranged_value_to_percentage, +) from . import DOMAIN, SIGNAL_UPDATE_SMARTY _LOGGER = logging.getLogger(__name__) -SPEED_LIST = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] - -SPEED_MAPPING = {1: SPEED_LOW, 2: SPEED_MEDIUM, 3: SPEED_HIGH} -SPEED_TO_MODE = {v: k for k, v in SPEED_MAPPING.items()} +DEFAULT_ON_PERCENTAGE = 66 +SPEED_RANGE = (1, 3) # off is not included async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -37,8 +36,7 @@ class SmartyFan(FanEntity): def __init__(self, name, smarty): """Initialize the entity.""" self._name = name - self._speed = SPEED_OFF - self._state = None + self._smarty_fan_speed = 0 self._smarty = smarty @property @@ -61,76 +59,64 @@ def supported_features(self): """Return the list of supported features.""" return SUPPORT_SET_SPEED - @property - def speed_list(self): - """List of available fan modes.""" - return SPEED_LIST - @property def is_on(self): """Return state of the fan.""" - return self._state + return bool(self._smarty_fan_speed) + + @property + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return int_states_in_range(SPEED_RANGE) @property - def speed(self) -> str: - """Return speed of the fan.""" - return self._speed - - def set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - _LOGGER.debug("Set the fan speed to %s", speed) - if speed == SPEED_OFF: + def percentage(self) -> str: + """Return speed percentage of the fan.""" + if self._smarty_fan_speed == 0: + return 0 + return ranged_value_to_percentage(SPEED_RANGE, self._smarty_fan_speed) + + def set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + _LOGGER.debug("Set the fan percentage to %s", percentage) + if percentage == 0: self.turn_off() - else: - self._smarty.set_fan_speed(SPEED_TO_MODE.get(speed)) - self._speed = speed - self._state = True - - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # + return + + fan_speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + if not self._smarty.set_fan_speed(fan_speed): + raise HomeAssistantError( + f"Failed to set the fan speed percentage to {percentage}" + ) + + self._smarty_fan_speed = fan_speed + self.schedule_update_ha_state() + def turn_on(self, speed=None, percentage=None, preset_mode=None, **kwargs): """Turn on the fan.""" _LOGGER.debug("Turning on fan. Speed is %s", speed) - if speed is None: - if self._smarty.turn_on(SPEED_TO_MODE.get(self._speed)): - self._state = True - self._speed = SPEED_MEDIUM - else: - if self._smarty.set_fan_speed(SPEED_TO_MODE.get(speed)): - self._speed = speed - self._state = True - - self.schedule_update_ha_state() + self.set_percentage(percentage or DEFAULT_ON_PERCENTAGE) def turn_off(self, **kwargs): """Turn off the fan.""" _LOGGER.debug("Turning off fan") - if self._smarty.turn_off(): - self._state = False + if not self._smarty.turn_off(): + raise HomeAssistantError("Failed to turn off the fan") + self._smarty_fan_speed = 0 self.schedule_update_ha_state() async def async_added_to_hass(self): """Call to update fan.""" - async_dispatcher_connect(self.hass, SIGNAL_UPDATE_SMARTY, self._update_callback) + self.async_on_remove( + async_dispatcher_connect( + self.hass, SIGNAL_UPDATE_SMARTY, self._update_callback + ) + ) @callback def _update_callback(self): """Call update method.""" - self.async_schedule_update_ha_state(True) - - def update(self): - """Update state.""" _LOGGER.debug("Updating state") - result = self._smarty.fan_speed - if result: - self._speed = SPEED_MAPPING[result] - _LOGGER.debug("Speed is %s, Mode is %s", self._speed, result) - self._state = True - else: - self._state = False + self._smarty_fan_speed = self._smarty.fan_speed + self.async_write_ha_state() From 6707496c5d73f04d0e5ae4929663bd73a12ab7ed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 20:45:14 -1000 Subject: [PATCH 0618/1818] Update alexa for new fan model (#45836) --- .../components/alexa/capabilities.py | 9 +-- homeassistant/components/alexa/const.py | 8 --- homeassistant/components/alexa/handlers.py | 62 ++++--------------- tests/components/alexa/test_capabilities.py | 4 ++ tests/components/alexa/test_smart_home.py | 37 +++++------ 5 files changed, 37 insertions(+), 83 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 008870c8dd93b0..acfba91a933aeb 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -46,7 +46,6 @@ API_THERMOSTAT_MODES, API_THERMOSTAT_PRESETS, DATE_FORMAT, - PERCENTAGE_FAN_MAP, Inputs, ) from .errors import UnsupportedProperty @@ -668,9 +667,7 @@ def get_property(self, name): raise UnsupportedProperty(name) if self.entity.domain == fan.DOMAIN: - speed = self.entity.attributes.get(fan.ATTR_SPEED) - - return PERCENTAGE_FAN_MAP.get(speed, 0) + return self.entity.attributes.get(fan.ATTR_PERCENTAGE) or 0 if self.entity.domain == cover.DOMAIN: return self.entity.attributes.get(cover.ATTR_CURRENT_POSITION, 0) @@ -1155,9 +1152,7 @@ def get_property(self, name): raise UnsupportedProperty(name) if self.entity.domain == fan.DOMAIN: - speed = self.entity.attributes.get(fan.ATTR_SPEED) - - return PERCENTAGE_FAN_MAP.get(speed) + return self.entity.attributes.get(fan.ATTR_PERCENTAGE) or 0 return None diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index ca0d8435e02450..a076fdcad9edcb 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -1,7 +1,6 @@ """Constants for the Alexa integration.""" from collections import OrderedDict -from homeassistant.components import fan from homeassistant.components.climate import const as climate from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -80,13 +79,6 @@ API_THERMOSTAT_MODES_CUSTOM = {climate.HVAC_MODE_DRY: "DEHUMIDIFY"} API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"} -PERCENTAGE_FAN_MAP = { - fan.SPEED_OFF: 0, - fan.SPEED_LOW: 33, - fan.SPEED_MEDIUM: 66, - fan.SPEED_HIGH: 100, -} - class Cause: """Possible causes for property changes. diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 8837210b6ad726..dce4f9f2210cab 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -54,7 +54,6 @@ API_THERMOSTAT_MODES, API_THERMOSTAT_MODES_CUSTOM, API_THERMOSTAT_PRESETS, - PERCENTAGE_FAN_MAP, Cause, Inputs, ) @@ -360,17 +359,9 @@ async def async_api_set_percentage(hass, config, directive, context): data = {ATTR_ENTITY_ID: entity.entity_id} if entity.domain == fan.DOMAIN: - service = fan.SERVICE_SET_SPEED - speed = "off" - + service = fan.SERVICE_SET_PERCENTAGE percentage = int(directive.payload["percentage"]) - if percentage <= 33: - speed = "low" - elif percentage <= 66: - speed = "medium" - elif percentage <= 100: - speed = "high" - data[fan.ATTR_SPEED] = speed + data[fan.ATTR_PERCENTAGE] = percentage await hass.services.async_call( entity.domain, service, data, blocking=False, context=context @@ -388,22 +379,12 @@ async def async_api_adjust_percentage(hass, config, directive, context): data = {ATTR_ENTITY_ID: entity.entity_id} if entity.domain == fan.DOMAIN: - service = fan.SERVICE_SET_SPEED - speed = entity.attributes.get(fan.ATTR_SPEED) - current = PERCENTAGE_FAN_MAP.get(speed, 100) + service = fan.SERVICE_SET_PERCENTAGE + current = entity.attributes.get(fan.ATTR_PERCENTAGE) or 0 # set percentage - percentage = max(0, percentage_delta + current) - speed = "off" - - if percentage <= 33: - speed = "low" - elif percentage <= 66: - speed = "medium" - elif percentage <= 100: - speed = "high" - - data[fan.ATTR_SPEED] = speed + percentage = min(100, max(0, percentage_delta + current)) + data[fan.ATTR_PERCENTAGE] = percentage await hass.services.async_call( entity.domain, service, data, blocking=False, context=context @@ -854,18 +835,9 @@ async def async_api_set_power_level(hass, config, directive, context): data = {ATTR_ENTITY_ID: entity.entity_id} if entity.domain == fan.DOMAIN: - service = fan.SERVICE_SET_SPEED - speed = "off" - + service = fan.SERVICE_SET_PERCENTAGE percentage = int(directive.payload["powerLevel"]) - if percentage <= 33: - speed = "low" - elif percentage <= 66: - speed = "medium" - else: - speed = "high" - - data[fan.ATTR_SPEED] = speed + data[fan.ATTR_PERCENTAGE] = percentage await hass.services.async_call( entity.domain, service, data, blocking=False, context=context @@ -883,22 +855,12 @@ async def async_api_adjust_power_level(hass, config, directive, context): data = {ATTR_ENTITY_ID: entity.entity_id} if entity.domain == fan.DOMAIN: - service = fan.SERVICE_SET_SPEED - speed = entity.attributes.get(fan.ATTR_SPEED) - current = PERCENTAGE_FAN_MAP.get(speed, 100) + service = fan.SERVICE_SET_PERCENTAGE + current = entity.attributes.get(fan.ATTR_PERCENTAGE) or 0 # set percentage - percentage = max(0, percentage_delta + current) - speed = "off" - - if percentage <= 33: - speed = "low" - elif percentage <= 66: - speed = "medium" - else: - speed = "high" - - data[fan.ATTR_SPEED] = speed + percentage = min(100, max(0, percentage_delta + current)) + data[fan.ATTR_PERCENTAGE] = percentage await hass.services.async_call( entity.domain, service, data, blocking=False, context=context diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 0bdbac70d7db6d..cd013ca70d9b0e 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -323,6 +323,7 @@ async def test_report_fan_speed_state(hass): "friendly_name": "Off fan", "speed": "off", "supported_features": 1, + "percentage": 0, "speed_list": ["off", "low", "medium", "high"], }, ) @@ -333,6 +334,7 @@ async def test_report_fan_speed_state(hass): "friendly_name": "Low speed fan", "speed": "low", "supported_features": 1, + "percentage": 33, "speed_list": ["off", "low", "medium", "high"], }, ) @@ -343,6 +345,7 @@ async def test_report_fan_speed_state(hass): "friendly_name": "Medium speed fan", "speed": "medium", "supported_features": 1, + "percentage": 66, "speed_list": ["off", "low", "medium", "high"], }, ) @@ -353,6 +356,7 @@ async def test_report_fan_speed_state(hass): "friendly_name": "High speed fan", "speed": "high", "supported_features": 1, + "percentage": 100, "speed_list": ["off", "low", "medium", "high"], }, ) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 05a60c86ae06c5..657bc407fb0b96 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -383,6 +383,7 @@ async def test_variable_fan(hass): "supported_features": 1, "speed_list": ["low", "medium", "high"], "speed": "high", + "percentage": 100, }, ) appliance = await discovery_test(device, hass) @@ -423,82 +424,82 @@ async def test_variable_fan(hass): "Alexa.PercentageController", "SetPercentage", "fan#test_2", - "fan.set_speed", + "fan.set_percentage", hass, payload={"percentage": "50"}, ) - assert call.data["speed"] == "medium" + assert call.data["percentage"] == 50 call, _ = await assert_request_calls_service( "Alexa.PercentageController", "SetPercentage", "fan#test_2", - "fan.set_speed", + "fan.set_percentage", hass, payload={"percentage": "33"}, ) - assert call.data["speed"] == "low" + assert call.data["percentage"] == 33 call, _ = await assert_request_calls_service( "Alexa.PercentageController", "SetPercentage", "fan#test_2", - "fan.set_speed", + "fan.set_percentage", hass, payload={"percentage": "100"}, ) - assert call.data["speed"] == "high" + assert call.data["percentage"] == 100 await assert_percentage_changes( hass, - [("high", "-5"), ("off", "5"), ("low", "-80"), ("medium", "-34")], + [(95, "-5"), (100, "5"), (20, "-80"), (66, "-34")], "Alexa.PercentageController", "AdjustPercentage", "fan#test_2", "percentageDelta", - "fan.set_speed", - "speed", + "fan.set_percentage", + "percentage", ) call, _ = await assert_request_calls_service( "Alexa.PowerLevelController", "SetPowerLevel", "fan#test_2", - "fan.set_speed", + "fan.set_percentage", hass, payload={"powerLevel": "20"}, ) - assert call.data["speed"] == "low" + assert call.data["percentage"] == 20 call, _ = await assert_request_calls_service( "Alexa.PowerLevelController", "SetPowerLevel", "fan#test_2", - "fan.set_speed", + "fan.set_percentage", hass, payload={"powerLevel": "50"}, ) - assert call.data["speed"] == "medium" + assert call.data["percentage"] == 50 call, _ = await assert_request_calls_service( "Alexa.PowerLevelController", "SetPowerLevel", "fan#test_2", - "fan.set_speed", + "fan.set_percentage", hass, payload={"powerLevel": "99"}, ) - assert call.data["speed"] == "high" + assert call.data["percentage"] == 99 await assert_percentage_changes( hass, - [("high", "-5"), ("medium", "-50"), ("low", "-80")], + [(95, "-5"), (50, "-50"), (20, "-80")], "Alexa.PowerLevelController", "AdjustPowerLevel", "fan#test_2", "powerLevelDelta", - "fan.set_speed", - "speed", + "fan.set_percentage", + "percentage", ) From 5b0b01d727f8364229dafc443b7744abe7e2f32e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 21:06:43 -1000 Subject: [PATCH 0619/1818] Implement suggested areas in bond (#45942) Co-authored-by: Paulus Schoutsen --- homeassistant/components/bond/__init__.py | 4 +- homeassistant/components/bond/config_flow.py | 22 ++-- homeassistant/components/bond/entity.py | 3 +- homeassistant/components/bond/manifest.json | 2 +- homeassistant/components/bond/utils.py | 52 +++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bond/common.py | 35 +++++- tests/components/bond/test_config_flow.py | 62 ++++++++-- tests/components/bond/test_init.py | 124 ++++++++++++++++--- tests/components/bond/test_light.py | 2 +- 11 files changed, 249 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 4e6705cbe09e9e..9d0a613000ad46 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -50,14 +50,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id) + hub_name = hub.name or hub.bond_id device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( config_entry_id=config_entry_id, identifiers={(DOMAIN, hub.bond_id)}, manufacturer=BRIDGE_MAKE, - name=hub.bond_id, + name=hub_name, model=hub.target, sw_version=hub.fw_ver, + suggested_area=hub.location, ) _async_remove_old_device_identifiers(config_entry_id, device_registry, hub) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 9298961269ee8d..0132df486d3ab8 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -1,6 +1,6 @@ """Config flow for Bond integration.""" import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Tuple from aiohttp import ClientConnectionError, ClientResponseError from bond_api import Bond @@ -16,6 +16,7 @@ from .const import CONF_BOND_ID from .const import DOMAIN # pylint:disable=unused-import +from .utils import BondHub _LOGGER = logging.getLogger(__name__) @@ -25,14 +26,13 @@ DATA_SCHEMA_DISCOVERY = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) -async def _validate_input(data: Dict[str, Any]) -> str: +async def _validate_input(data: Dict[str, Any]) -> Tuple[str, Optional[str]]: """Validate the user input allows us to connect.""" + bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) try: - bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) - version = await bond.version() - # call to non-version API is needed to validate authentication - await bond.devices() + hub = BondHub(bond) + await hub.setup(max_devices=1) except ClientConnectionError as error: raise InputValidationError("cannot_connect") from error except ClientResponseError as error: @@ -44,11 +44,10 @@ async def _validate_input(data: Dict[str, Any]) -> str: raise InputValidationError("unknown") from error # Return unique ID from the hub to be stored in the config entry. - bond_id = version.get("bondid") - if not bond_id: + if not hub.bond_id: raise InputValidationError("old_firmware") - return bond_id + return hub.bond_id, hub.name class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -113,10 +112,11 @@ async def async_step_user( ) async def _try_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]: - bond_id = await _validate_input(data) + bond_id, name = await _validate_input(data) await self.async_set_unique_id(bond_id) self._abort_if_unique_id_configured() - return self.async_create_entry(title=bond_id, data=data) + hub_name = name or bond_id + return self.async_create_entry(title=hub_name, data=data) class InputValidationError(exceptions.HomeAssistantError): diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 769794a31e8b1a..f6165eb7890543 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -65,7 +65,8 @@ def device_info(self) -> Optional[Dict[str, Any]]: device_info = { ATTR_NAME: self.name, "manufacturer": self._hub.make, - "identifiers": {(DOMAIN, self._hub.bond_id, self._device_id)}, + "identifiers": {(DOMAIN, self._hub.bond_id, self._device.device_id)}, + "suggested_area": self._device.location, "via_device": (DOMAIN, self._hub.bond_id), } if not self._hub.is_bridge: diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index e1ec5e5dd46e84..cf009c11caa607 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -3,7 +3,7 @@ "name": "Bond", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/bond", - "requirements": ["bond-api==0.1.9"], + "requirements": ["bond-api==0.1.10"], "zeroconf": ["_bond._tcp.local."], "codeowners": ["@prystupa"], "quality_scale": "platinum" diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index df3373ed7a1aaa..6d3aacf5e42d9d 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,7 +1,9 @@ """Reusable utilities for the Bond component.""" +import asyncio import logging from typing import List, Optional +from aiohttp import ClientResponseError from bond_api import Action, Bond from .const import BRIDGE_MAKE @@ -39,7 +41,7 @@ def type(self) -> str: @property def location(self) -> str: """Get the location of this device.""" - return self._attrs["location"] + return self._attrs.get("location") @property def template(self) -> str: @@ -89,31 +91,40 @@ class BondHub: def __init__(self, bond: Bond): """Initialize Bond Hub.""" self.bond: Bond = bond + self._bridge: Optional[dict] = None self._version: Optional[dict] = None self._devices: Optional[List[BondDevice]] = None - async def setup(self): + async def setup(self, max_devices=None): """Read hub version information.""" self._version = await self.bond.version() _LOGGER.debug("Bond reported the following version info: %s", self._version) - # Fetch all available devices using Bond API. device_ids = await self.bond.devices() - self._devices = [ - BondDevice( - device_id, - await self.bond.device(device_id), - await self.bond.device_properties(device_id), + self._devices = [] + for idx, device_id in enumerate(device_ids): + if max_devices is not None and idx >= max_devices: + break + + device, props = await asyncio.gather( + self.bond.device(device_id), self.bond.device_properties(device_id) ) - for device_id in device_ids - ] + + self._devices.append(BondDevice(device_id, device, props)) _LOGGER.debug("Discovered Bond devices: %s", self._devices) + try: + # Smart by bond devices do not have a bridge api call + self._bridge = await self.bond.bridge() + except ClientResponseError: + self._bridge = {} + _LOGGER.debug("Bond reported the following bridge info: %s", self._bridge) @property - def bond_id(self) -> str: + def bond_id(self) -> Optional[str]: """Return unique Bond ID for this hub.""" - return self._version["bondid"] + # Old firmwares are missing the bondid + return self._version.get("bondid") @property def target(self) -> str: @@ -130,6 +141,20 @@ def make(self) -> str: """Return this hub make.""" return self._version.get("make", BRIDGE_MAKE) + @property + def name(self) -> Optional[str]: + """Get the name of this bridge.""" + if not self.is_bridge and self._devices: + return self._devices[0].name + return self._bridge.get("name") + + @property + def location(self) -> Optional[str]: + """Get the location of this bridge.""" + if not self.is_bridge and self._devices: + return self._devices[0].location + return self._bridge.get("location") + @property def fw_ver(self) -> str: """Return this hub firmware version.""" @@ -143,5 +168,4 @@ def devices(self) -> List[BondDevice]: @property def is_bridge(self) -> bool: """Return if the Bond is a Bond Bridge.""" - # If False, it means that it is a Smart by Bond product. Assumes that it is if the model is not available. - return self._version.get("model", "BD-").startswith("BD-") + return bool(self._bridge) diff --git a/requirements_all.txt b/requirements_all.txt index 0e3c6117780a8a..971b8ac9c92835 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -371,7 +371,7 @@ blockchain==1.4.4 # bme680==1.0.5 # homeassistant.components.bond -bond-api==0.1.9 +bond-api==0.1.10 # homeassistant.components.amazon_polly # homeassistant.components.route53 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 317063ffffcc88..c552f8452c01de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -205,7 +205,7 @@ blebox_uniapi==1.3.2 blinkpy==0.17.0 # homeassistant.components.bond -bond-api==0.1.9 +bond-api==0.1.10 # homeassistant.components.braviatv bravia-tv==1.0.8 diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index ba4d10c8892f35..54d127832b546e 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -29,13 +29,16 @@ async def setup_bond_entity( patch_version=False, patch_device_ids=False, patch_platforms=False, + patch_bridge=False, ): """Set up Bond entity.""" config_entry.add_to_hass(hass) - with patch_start_bpup(), patch_bond_version( - enabled=patch_version - ), patch_bond_device_ids(enabled=patch_device_ids), patch_setup_entry( + with patch_start_bpup(), patch_bond_bridge( + enabled=patch_bridge + ), patch_bond_version(enabled=patch_version), patch_bond_device_ids( + enabled=patch_device_ids + ), patch_setup_entry( "cover", enabled=patch_platforms ), patch_setup_entry( "fan", enabled=patch_platforms @@ -56,6 +59,7 @@ async def setup_platform( bond_version: Dict[str, Any] = None, props: Dict[str, Any] = None, state: Dict[str, Any] = None, + bridge: Dict[str, Any] = None, ): """Set up the specified Bond platform.""" mock_entry = MockConfigEntry( @@ -65,7 +69,9 @@ async def setup_platform( mock_entry.add_to_hass(hass) with patch("homeassistant.components.bond.PLATFORMS", [platform]): - with patch_bond_version(return_value=bond_version), patch_bond_device_ids( + with patch_bond_version(return_value=bond_version), patch_bond_bridge( + return_value=bridge + ), patch_bond_device_ids( return_value=[bond_device_id] ), patch_start_bpup(), patch_bond_device( return_value=discovered_device @@ -97,6 +103,27 @@ def patch_bond_version( ) +def patch_bond_bridge( + enabled: bool = True, return_value: Optional[dict] = None, side_effect=None +): + """Patch Bond API bridge endpoint.""" + if not enabled: + return nullcontext() + + if return_value is None: + return_value = { + "name": "bond-name", + "location": "bond-location", + "bluelight": 127, + } + + return patch( + "homeassistant.components.bond.Bond.bridge", + return_value=return_value, + side_effect=side_effect, + ) + + def patch_bond_device_ids(enabled: bool = True, return_value=None, side_effect=None): """Patch Bond API devices endpoint.""" if not enabled: diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index dba6c5906412ed..2a76e1fa6a03ba 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -8,7 +8,13 @@ from homeassistant.components.bond.const import DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST -from .common import patch_bond_device_ids, patch_bond_version +from .common import ( + patch_bond_bridge, + patch_bond_device, + patch_bond_device_ids, + patch_bond_device_properties, + patch_bond_version, +) from tests.common import MockConfigEntry @@ -24,7 +30,9 @@ async def test_user_form(hass: core.HomeAssistant): with patch_bond_version( return_value={"bondid": "test-bond-id"} - ), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: + ), patch_bond_device_ids( + return_value=["f6776c11", "f6776c12"] + ), patch_bond_bridge(), patch_bond_device_properties(), patch_bond_device(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -32,7 +40,43 @@ async def test_user_form(hass: core.HomeAssistant): await hass.async_block_till_done() assert result2["type"] == "create_entry" - assert result2["title"] == "test-bond-id" + assert result2["title"] == "bond-name" + assert result2["data"] == { + CONF_HOST: "some host", + CONF_ACCESS_TOKEN: "test-token", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_user_form_with_non_bridge(hass: core.HomeAssistant): + """Test setup a smart by bond fan.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch_bond_version( + return_value={"bondid": "test-bond-id"} + ), patch_bond_device_ids( + return_value=["f6776c11"] + ), patch_bond_device_properties(), patch_bond_device( + return_value={ + "name": "New Fan", + } + ), patch_bond_bridge( + return_value={} + ), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "New Fan" assert result2["data"] == { CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token", @@ -49,7 +93,7 @@ async def test_user_form_invalid_auth(hass: core.HomeAssistant): with patch_bond_version( return_value={"bond_id": "test-bond-id"} - ), patch_bond_device_ids( + ), patch_bond_bridge(), patch_bond_device_ids( side_effect=ClientResponseError(Mock(), Mock(), status=401), ): result2 = await hass.config_entries.flow.async_configure( @@ -69,7 +113,7 @@ async def test_user_form_cannot_connect(hass: core.HomeAssistant): with patch_bond_version( side_effect=ClientConnectionError() - ), patch_bond_device_ids(): + ), patch_bond_bridge(), patch_bond_device_ids(): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -87,7 +131,7 @@ async def test_user_form_old_firmware(hass: core.HomeAssistant): with patch_bond_version( return_value={"no_bond_id": "present"} - ), patch_bond_device_ids(): + ), patch_bond_bridge(), patch_bond_device_ids(): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -133,7 +177,7 @@ async def test_user_form_one_entry_per_device_allowed(hass: core.HomeAssistant): with patch_bond_version( return_value={"bondid": "already-registered-bond-id"} - ), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: + ), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -160,7 +204,7 @@ async def test_zeroconf_form(hass: core.HomeAssistant): with patch_bond_version( return_value={"bondid": "test-bond-id"} - ), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: + ), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_ACCESS_TOKEN: "test-token"}, @@ -168,7 +212,7 @@ async def test_zeroconf_form(hass: core.HomeAssistant): await hass.async_block_till_done() assert result2["type"] == "create_entry" - assert result2["title"] == "test-bond-id" + assert result2["title"] == "bond-name" assert result2["data"] == { CONF_HOST: "test-host", CONF_ACCESS_TOKEN: "test-token", diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index 4dc7ae5c8d4fcf..7346acc52761e4 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -1,5 +1,7 @@ """Tests for the Bond module.""" -from aiohttp import ClientConnectionError +from unittest.mock import Mock + +from aiohttp import ClientConnectionError, ClientResponseError from bond_api import DeviceType from homeassistant.components.bond.const import DOMAIN @@ -14,6 +16,7 @@ from homeassistant.setup import async_setup_component from .common import ( + patch_bond_bridge, patch_bond_device, patch_bond_device_ids, patch_bond_device_properties, @@ -54,25 +57,22 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(hass: HomeAss data={CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, ) - with patch_bond_version( + with patch_bond_bridge(), patch_bond_version( return_value={ "bondid": "test-bond-id", "target": "test-model", "fw_ver": "test-version", } - ): - with patch_setup_entry( - "cover" - ) as mock_cover_async_setup_entry, patch_setup_entry( - "fan" - ) as mock_fan_async_setup_entry, patch_setup_entry( - "light" - ) as mock_light_async_setup_entry, patch_setup_entry( - "switch" - ) as mock_switch_async_setup_entry: - result = await setup_bond_entity(hass, config_entry, patch_device_ids=True) - assert result is True - await hass.async_block_till_done() + ), patch_setup_entry("cover") as mock_cover_async_setup_entry, patch_setup_entry( + "fan" + ) as mock_fan_async_setup_entry, patch_setup_entry( + "light" + ) as mock_light_async_setup_entry, patch_setup_entry( + "switch" + ) as mock_switch_async_setup_entry: + result = await setup_bond_entity(hass, config_entry, patch_device_ids=True) + assert result is True + await hass.async_block_till_done() assert config_entry.entry_id in hass.data[DOMAIN] assert config_entry.state == ENTRY_STATE_LOADED @@ -81,7 +81,7 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(hass: HomeAss # verify hub device is registered correctly device_registry = await dr.async_get_registry(hass) hub = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) - assert hub.name == "test-bond-id" + assert hub.name == "bond-name" assert hub.manufacturer == "Olibra" assert hub.model == "test-model" assert hub.sw_version == "test-version" @@ -106,6 +106,7 @@ async def test_unload_config_entry(hass: HomeAssistant): patch_version=True, patch_device_ids=True, patch_platforms=True, + patch_bridge=True, ) assert result is True await hass.async_block_till_done() @@ -136,7 +137,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant): config_entry.add_to_hass(hass) - with patch_bond_version( + with patch_bond_bridge(), patch_bond_version( return_value={ "bondid": "test-bond-id", "target": "test-model", @@ -164,3 +165,92 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant): # verify the device info is cleaned up assert device_registry.async_get_device(identifiers={old_identifers}) is None assert device_registry.async_get_device(identifiers={new_identifiers}) is not None + + +async def test_smart_by_bond_device_suggested_area(hass: HomeAssistant): + """Test we can setup a smart by bond device and get the suggested area.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, + ) + + config_entry.add_to_hass(hass) + + with patch_bond_bridge( + side_effect=ClientResponseError(Mock(), Mock(), status=404) + ), patch_bond_version( + return_value={ + "bondid": "test-bond-id", + "target": "test-model", + "fw_ver": "test-version", + } + ), patch_start_bpup(), patch_bond_device_ids( + return_value=["bond-device-id", "device_id"] + ), patch_bond_device( + return_value={ + "name": "test1", + "type": DeviceType.GENERIC_DEVICE, + "location": "Den", + } + ), patch_bond_device_properties( + return_value={} + ), patch_bond_device_state( + return_value={} + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) is True + await hass.async_block_till_done() + + assert config_entry.entry_id in hass.data[DOMAIN] + assert config_entry.state == ENTRY_STATE_LOADED + assert config_entry.unique_id == "test-bond-id" + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) + assert device is not None + assert device.suggested_area == "Den" + + +async def test_bridge_device_suggested_area(hass: HomeAssistant): + """Test we can setup a bridge bond device and get the suggested area.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, + ) + + config_entry.add_to_hass(hass) + + with patch_bond_bridge( + return_value={ + "name": "Office Bridge", + "location": "Office", + } + ), patch_bond_version( + return_value={ + "bondid": "test-bond-id", + "target": "test-model", + "fw_ver": "test-version", + } + ), patch_start_bpup(), patch_bond_device_ids( + return_value=["bond-device-id", "device_id"] + ), patch_bond_device( + return_value={ + "name": "test1", + "type": DeviceType.GENERIC_DEVICE, + "location": "Bathroom", + } + ), patch_bond_device_properties( + return_value={} + ), patch_bond_device_state( + return_value={} + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) is True + await hass.async_block_till_done() + + assert config_entry.entry_id in hass.data[DOMAIN] + assert config_entry.state == ENTRY_STATE_LOADED + assert config_entry.unique_id == "test-bond-id" + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) + assert device is not None + assert device.suggested_area == "Office" diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index 3f7a3ef62f9847..e4cd4e4e3e90f6 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -148,7 +148,7 @@ async def test_sbb_trust_state(hass: core.HomeAssistant): "bondid": "test-bond-id", } await setup_platform( - hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_version=version + hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_version=version, bridge={} ) device = hass.states.get("light.name_1") From 9b69549f734d225227e9f1d3829f387ab7f724e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 21:26:24 -1000 Subject: [PATCH 0620/1818] Recover and restart the recorder if the sqlite database encounters corruption while running (#46612) --- homeassistant/components/recorder/__init__.py | 375 ++++++++++-------- homeassistant/components/recorder/util.py | 11 +- tests/components/recorder/common.py | 13 + tests/components/recorder/test_init.py | 54 ++- tests/components/recorder/test_util.py | 15 +- 5 files changed, 279 insertions(+), 189 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index ceec7ee9eed787..915e6b4518136b 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -5,6 +5,7 @@ from datetime import datetime import logging import queue +import sqlite3 import threading import time from typing import Any, Callable, List, Optional @@ -37,7 +38,12 @@ from . import migration, purge from .const import CONF_DB_INTEGRITY_CHECK, DATA_INSTANCE, DOMAIN, SQLITE_URL_PREFIX from .models import Base, Events, RecorderRuns, States -from .util import session_scope, validate_or_move_away_sqlite_database +from .util import ( + dburl_to_path, + move_away_broken_database, + session_scope, + validate_or_move_away_sqlite_database, +) _LOGGER = logging.getLogger(__name__) @@ -247,7 +253,7 @@ def __init__( self._pending_expunge = [] self.event_session = None self.get_session = None - self._completed_database_setup = False + self._completed_database_setup = None @callback def async_initialize(self): @@ -278,39 +284,8 @@ def do_adhoc_purge(self, **kwargs): def run(self): """Start processing events to save.""" - tries = 1 - connected = False - - while not connected and tries <= self.db_max_retries: - if tries != 1: - time.sleep(self.db_retry_wait) - try: - self._setup_connection() - migration.migrate_schema(self) - self._setup_run() - connected = True - _LOGGER.debug("Connected to recorder database") - except Exception as err: # pylint: disable=broad-except - _LOGGER.error( - "Error during connection setup: %s (retrying in %s seconds)", - err, - self.db_retry_wait, - ) - tries += 1 - - if not connected: - @callback - def connection_failed(): - """Connect failed tasks.""" - self.async_db_ready.set_result(False) - persistent_notification.async_create( - self.hass, - "The recorder could not start, please check the log", - "Recorder", - ) - - self.hass.add_job(connection_failed) + if not self._setup_recorder(): return shutdown_task = object() @@ -346,15 +321,11 @@ def notify_hass_started(event): self.hass.add_job(register) result = hass_started.result() - self.event_session = self.get_session() - self.event_session.expire_on_commit = False - # If shutdown happened before Home Assistant finished starting if result is shutdown_task: # Make sure we cleanly close the run if # we restart before startup finishes - self._close_run() - self._close_connection() + self._shutdown() return # Start periodic purge @@ -370,182 +341,228 @@ def async_purge(now): async_purge, hour=4, minute=12, second=0 ) + _LOGGER.debug("Recorder processing the queue") # Use a session for the event read loop # with a commit every time the event time # has changed. This reduces the disk io. while True: event = self.queue.get() + if event is None: - self._close_run() - self._close_connection() + self._shutdown() return - if isinstance(event, PurgeTask): - # Schedule a new purge task if this one didn't finish - if not purge.purge_old_data(self, event.keep_days, event.repack): - self.queue.put(PurgeTask(event.keep_days, event.repack)) - continue - if isinstance(event, WaitTask): - self._queue_watch.set() - continue - if event.event_type == EVENT_TIME_CHANGED: - self._keepalive_count += 1 - if self._keepalive_count >= KEEPALIVE_TIME: - self._keepalive_count = 0 - self._send_keep_alive() - if self.commit_interval: - self._timechanges_seen += 1 - if self._timechanges_seen >= self.commit_interval: - self._timechanges_seen = 0 - self._commit_event_session_or_retry() - continue + self._process_one_event(event) + + def _setup_recorder(self) -> bool: + """Create schema and connect to the database.""" + tries = 1 + + while tries <= self.db_max_retries: try: - if event.event_type == EVENT_STATE_CHANGED: - dbevent = Events.from_event(event, event_data="{}") - else: - dbevent = Events.from_event(event) - dbevent.created = event.time_fired - self.event_session.add(dbevent) + self._setup_connection() + migration.migrate_schema(self) + self._setup_run() + except Exception as err: # pylint: disable=broad-except + _LOGGER.error( + "Error during connection setup to %s: %s (retrying in %s seconds)", + self.db_url, + err, + self.db_retry_wait, + ) + else: + _LOGGER.debug("Connected to recorder database") + self._open_event_session() + return True + + tries += 1 + time.sleep(self.db_retry_wait) + + @callback + def connection_failed(): + """Connect failed tasks.""" + self.async_db_ready.set_result(False) + persistent_notification.async_create( + self.hass, + "The recorder could not start, please check the log", + "Recorder", + ) + + self.hass.add_job(connection_failed) + return False + + def _process_one_event(self, event): + """Process one event.""" + if isinstance(event, PurgeTask): + # Schedule a new purge task if this one didn't finish + if not purge.purge_old_data(self, event.keep_days, event.repack): + self.queue.put(PurgeTask(event.keep_days, event.repack)) + return + if isinstance(event, WaitTask): + self._queue_watch.set() + return + if event.event_type == EVENT_TIME_CHANGED: + self._keepalive_count += 1 + if self._keepalive_count >= KEEPALIVE_TIME: + self._keepalive_count = 0 + self._send_keep_alive() + if self.commit_interval: + self._timechanges_seen += 1 + if self._timechanges_seen >= self.commit_interval: + self._timechanges_seen = 0 + self._commit_event_session_or_recover() + return + + try: + if event.event_type == EVENT_STATE_CHANGED: + dbevent = Events.from_event(event, event_data="{}") + else: + dbevent = Events.from_event(event) + dbevent.created = event.time_fired + self.event_session.add(dbevent) + except (TypeError, ValueError): + _LOGGER.warning("Event is not JSON serializable: %s", event) + return + except Exception as err: # pylint: disable=broad-except + # Must catch the exception to prevent the loop from collapsing + _LOGGER.exception("Error adding event: %s", err) + return + + if event.event_type == EVENT_STATE_CHANGED: + try: + dbstate = States.from_event(event) + has_new_state = event.data.get("new_state") + if dbstate.entity_id in self._old_states: + old_state = self._old_states.pop(dbstate.entity_id) + if old_state.state_id: + dbstate.old_state_id = old_state.state_id + else: + dbstate.old_state = old_state + if not has_new_state: + dbstate.state = None + dbstate.event = dbevent + dbstate.created = event.time_fired + self.event_session.add(dbstate) + if has_new_state: + self._old_states[dbstate.entity_id] = dbstate + self._pending_expunge.append(dbstate) except (TypeError, ValueError): - _LOGGER.warning("Event is not JSON serializable: %s", event) + _LOGGER.warning( + "State is not JSON serializable: %s", + event.data.get("new_state"), + ) except Exception as err: # pylint: disable=broad-except # Must catch the exception to prevent the loop from collapsing - _LOGGER.exception("Error adding event: %s", err) - - if dbevent and event.event_type == EVENT_STATE_CHANGED: - try: - dbstate = States.from_event(event) - has_new_state = event.data.get("new_state") - if dbstate.entity_id in self._old_states: - old_state = self._old_states.pop(dbstate.entity_id) - if old_state.state_id: - dbstate.old_state_id = old_state.state_id - else: - dbstate.old_state = old_state - if not has_new_state: - dbstate.state = None - dbstate.event = dbevent - dbstate.created = event.time_fired - self.event_session.add(dbstate) - if has_new_state: - self._old_states[dbstate.entity_id] = dbstate - self._pending_expunge.append(dbstate) - except (TypeError, ValueError): - _LOGGER.warning( - "State is not JSON serializable: %s", - event.data.get("new_state"), - ) - except Exception as err: # pylint: disable=broad-except - # Must catch the exception to prevent the loop from collapsing - _LOGGER.exception("Error adding state change: %s", err) - - # If they do not have a commit interval - # than we commit right away - if not self.commit_interval: - self._commit_event_session_or_retry() + _LOGGER.exception("Error adding state change: %s", err) - def _send_keep_alive(self): + # If they do not have a commit interval + # than we commit right away + if not self.commit_interval: + self._commit_event_session_or_recover() + + def _commit_event_session_or_recover(self): + """Commit changes to the database and recover if the database fails when possible.""" try: - _LOGGER.debug("Sending keepalive") - self.event_session.connection().scalar(select([1])) + self._commit_event_session_or_retry() return + except exc.DatabaseError as err: + if isinstance(err.__cause__, sqlite3.DatabaseError): + _LOGGER.exception( + "Unrecoverable sqlite3 database corruption detected: %s", err + ) + self._handle_sqlite_corruption() + return + _LOGGER.exception("Unexpected error saving events: %s", err) except Exception as err: # pylint: disable=broad-except # Must catch the exception to prevent the loop from collapsing - _LOGGER.error( - "Error in database connectivity during keepalive: %s", - err, - ) - self._reopen_event_session() + _LOGGER.exception("Unexpected error saving events: %s", err) + + self._reopen_event_session() + return def _commit_event_session_or_retry(self): tries = 1 while tries <= self.db_max_retries: - if tries != 1: - time.sleep(self.db_retry_wait) - try: self._commit_event_session() return except (exc.InternalError, exc.OperationalError) as err: if err.connection_invalidated: - _LOGGER.error( - "Database connection invalidated: %s. " - "(retrying in %s seconds)", - err, - self.db_retry_wait, - ) + message = "Database connection invalidated" else: - _LOGGER.error( - "Error in database connectivity during commit: %s. " - "(retrying in %s seconds)", - err, - self.db_retry_wait, - ) + message = "Error in database connectivity during commit" + _LOGGER.error( + "%s: Error executing query: %s. (retrying in %s seconds)", + message, + err, + self.db_retry_wait, + ) + if tries == self.db_max_retries: + raise + tries += 1 + time.sleep(self.db_retry_wait) - except Exception as err: # pylint: disable=broad-except - # Must catch the exception to prevent the loop from collapsing - _LOGGER.exception("Error saving events: %s", err) - return + def _commit_event_session(self): + self._commits_without_expire += 1 - _LOGGER.error( - "Error in database update. Could not save " "after %d tries. Giving up", - tries, - ) - self._reopen_event_session() + if self._pending_expunge: + self.event_session.flush() + for dbstate in self._pending_expunge: + # Expunge the state so its not expired + # until we use it later for dbstate.old_state + if dbstate in self.event_session: + self.event_session.expunge(dbstate) + self._pending_expunge = [] + self.event_session.commit() + + # Expire is an expensive operation (frequently more expensive + # than the flush and commit itself) so we only + # do it after EXPIRE_AFTER_COMMITS commits + if self._commits_without_expire == EXPIRE_AFTER_COMMITS: + self._commits_without_expire = 0 + self.event_session.expire_all() + + def _handle_sqlite_corruption(self): + """Handle the sqlite3 database being corrupt.""" + self._close_connection() + move_away_broken_database(dburl_to_path(self.db_url)) + self._setup_recorder() def _reopen_event_session(self): - try: - self.event_session.rollback() - except Exception as err: # pylint: disable=broad-except - # Must catch the exception to prevent the loop from collapsing - _LOGGER.exception("Error while rolling back event session: %s", err) + """Rollback the event session and reopen it after a failure.""" + self._old_states = {} try: + self.event_session.rollback() self.event_session.close() except Exception as err: # pylint: disable=broad-except # Must catch the exception to prevent the loop from collapsing - _LOGGER.exception("Error while closing event session: %s", err) + _LOGGER.exception( + "Error while rolling back and closing the event session: %s", err + ) + + self._open_event_session() + def _open_event_session(self): + """Open the event session.""" try: self.event_session = self.get_session() self.event_session.expire_on_commit = False except Exception as err: # pylint: disable=broad-except - # Must catch the exception to prevent the loop from collapsing _LOGGER.exception("Error while creating new event session: %s", err) - def _commit_event_session(self): - self._commits_without_expire += 1 - + def _send_keep_alive(self): try: - if self._pending_expunge: - self.event_session.flush() - for dbstate in self._pending_expunge: - # Expunge the state so its not expired - # until we use it later for dbstate.old_state - if dbstate in self.event_session: - self.event_session.expunge(dbstate) - self._pending_expunge = [] - self.event_session.commit() - except exc.IntegrityError as err: + _LOGGER.debug("Sending keepalive") + self.event_session.connection().scalar(select([1])) + return + except Exception as err: # pylint: disable=broad-except _LOGGER.error( - "Integrity error executing query (database likely deleted out from under us): %s", + "Error in database connectivity during keepalive: %s", err, ) - self.event_session.rollback() - self._old_states = {} - raise - except Exception as err: - _LOGGER.error("Error executing query: %s", err) - self.event_session.rollback() - raise - - # Expire is an expensive operation (frequently more expensive - # than the flush and commit itself) so we only - # do it after EXPIRE_AFTER_COMMITS commits - if self._commits_without_expire == EXPIRE_AFTER_COMMITS: - self._commits_without_expire = 0 - self.event_session.expire_all() + self._reopen_event_session() @callback def event_listener(self, event): @@ -571,6 +588,7 @@ def block_till_done(self): def _setup_connection(self): """Ensure database is ready to fly.""" kwargs = {} + self._completed_database_setup = False def setup_recorder_connection(dbapi_connection, connection_record): """Dbapi specific connection settings.""" @@ -603,9 +621,7 @@ def setup_recorder_connection(dbapi_connection, connection_record): else: kwargs["echo"] = False - if self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith( - SQLITE_URL_PREFIX - ): + if self._using_file_sqlite: with self.hass.timeout.freeze(DOMAIN): # # Here we run an sqlite3 quick_check. In the majority @@ -628,6 +644,13 @@ def setup_recorder_connection(dbapi_connection, connection_record): Base.metadata.create_all(self.engine) self.get_session = scoped_session(sessionmaker(bind=self.engine)) + @property + def _using_file_sqlite(self): + """Short version to check if we are using sqlite3 as a file.""" + return self.db_url != SQLITE_URL_PREFIX and self.db_url.startswith( + SQLITE_URL_PREFIX + ) + def _close_connection(self): """Close the connection.""" self.engine.dispose() @@ -652,12 +675,18 @@ def _setup_run(self): session.flush() session.expunge(self.run_info) - def _close_run(self): + def _shutdown(self): """Save end time for current run.""" if self.event_session is not None: self.run_info.end = dt_util.utcnow() self.event_session.add(self.run_info) - self._commit_event_session_or_retry() - self.event_session.close() + try: + self._commit_event_session_or_retry() + self.event_session.close() + except Exception as err: # pylint: disable=broad-except + _LOGGER.exception( + "Error saving the event session during shutdown: %s", err + ) self.run_info = None + self._close_connection() diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index 41bca335a562d2..b945386de82b49 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -112,19 +112,24 @@ def execute(qry, to_native=False, validate_entity_ids=True): def validate_or_move_away_sqlite_database(dburl: str, db_integrity_check: bool) -> bool: """Ensure that the database is valid or move it away.""" - dbpath = dburl[len(SQLITE_URL_PREFIX) :] + dbpath = dburl_to_path(dburl) if not os.path.exists(dbpath): # Database does not exist yet, this is OK return True if not validate_sqlite_database(dbpath, db_integrity_check): - _move_away_broken_database(dbpath) + move_away_broken_database(dbpath) return False return True +def dburl_to_path(dburl): + """Convert the db url into a filesystem path.""" + return dburl[len(SQLITE_URL_PREFIX) :] + + def last_run_was_recently_clean(cursor): """Verify the last recorder run was recently clean.""" @@ -208,7 +213,7 @@ def run_checks_on_open_db(dbpath, cursor, db_integrity_check): cursor.execute("PRAGMA QUICK_CHECK") -def _move_away_broken_database(dbfile: str) -> None: +def move_away_broken_database(dbfile: str) -> None: """Move away a broken sqlite3 database.""" isotime = dt_util.utcnow().isoformat() diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index 1d0e6dbbfa00e5..d2b731777e2912 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -10,14 +10,27 @@ def wait_recording_done(hass): """Block till recording is done.""" + hass.block_till_done() trigger_db_commit(hass) hass.block_till_done() hass.data[recorder.DATA_INSTANCE].block_till_done() hass.block_till_done() +async def async_wait_recording_done(hass): + """Block till recording is done.""" + await hass.loop.run_in_executor(None, wait_recording_done, hass) + + def trigger_db_commit(hass): """Force the recorder to commit.""" for _ in range(recorder.DEFAULT_COMMIT_INTERVAL): # We only commit on time change fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1)) + + +def corrupt_db_file(test_db_file): + """Corrupt an sqlite3 database file.""" + with open(test_db_file, "w+") as fhandle: + fhandle.seek(200) + fhandle.write("I am a corrupt db" * 100) diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 3b71648166e750..ca25fe102840c2 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -6,6 +6,7 @@ from sqlalchemy.exc import OperationalError from homeassistant.components.recorder import ( + CONF_DB_URL, CONFIG_SCHEMA, DOMAIN, Recorder, @@ -13,7 +14,7 @@ run_information_from_instance, run_information_with_session, ) -from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.util import session_scope from homeassistant.const import ( @@ -26,7 +27,7 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util -from .common import wait_recording_done +from .common import async_wait_recording_done, corrupt_db_file, wait_recording_done from tests.common import ( async_init_recorder_component, @@ -519,3 +520,52 @@ def test_run_information(hass_recorder): class CannotSerializeMe: """A class that the JSONEncoder cannot serialize.""" + + +async def test_database_corruption_while_running(hass, tmpdir, caplog): + """Test we can recover from sqlite3 db corruption.""" + + def _create_tmpdir_for_test_db(): + return tmpdir.mkdir("sqlite").join("test.db") + + test_db_file = await hass.async_add_executor_job(_create_tmpdir_for_test_db) + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: dburl}}) + await hass.async_block_till_done() + caplog.clear() + + hass.states.async_set("test.lost", "on", {}) + + await async_wait_recording_done(hass) + await hass.async_add_executor_job(corrupt_db_file, test_db_file) + await async_wait_recording_done(hass) + + # This state will not be recorded because + # the database corruption will be discovered + # and we will have to rollback to recover + hass.states.async_set("test.one", "off", {}) + await async_wait_recording_done(hass) + + assert "Unrecoverable sqlite3 database corruption detected" in caplog.text + assert "The system will rename the corrupt database file" in caplog.text + assert "Connected to recorder database" in caplog.text + + # This state should go into the new database + hass.states.async_set("test.two", "on", {}) + await async_wait_recording_done(hass) + + def _get_last_state(): + with session_scope(hass=hass) as session: + db_states = list(session.query(States)) + assert len(db_states) == 1 + assert db_states[0].event_id > 0 + return db_states[0].to_native() + + state = await hass.async_add_executor_job(_get_last_state) + assert state.entity_id == "test.two" + assert state.state == "on" + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + hass.stop() diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 38df1285008aec..f1d55999ae4261 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -10,7 +10,7 @@ from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX from homeassistant.util import dt as dt_util -from .common import wait_recording_done +from .common import corrupt_db_file, wait_recording_done from tests.common import get_test_home_assistant, init_recorder_component @@ -90,7 +90,7 @@ def test_validate_or_move_away_sqlite_database_with_integrity_check( util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is False ) - _corrupt_db_file(test_db_file) + corrupt_db_file(test_db_file) assert util.validate_sqlite_database(dburl, db_integrity_check) is False @@ -127,7 +127,7 @@ def test_validate_or_move_away_sqlite_database_without_integrity_check( util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is False ) - _corrupt_db_file(test_db_file) + corrupt_db_file(test_db_file) assert util.validate_sqlite_database(dburl, db_integrity_check) is False @@ -150,7 +150,7 @@ def test_last_run_was_recently_clean(hass_recorder): assert util.last_run_was_recently_clean(cursor) is False - hass.data[DATA_INSTANCE]._close_run() + hass.data[DATA_INSTANCE]._shutdown() wait_recording_done(hass) assert util.last_run_was_recently_clean(cursor) is True @@ -244,10 +244,3 @@ def test_combined_checks(hass_recorder, caplog): caplog.clear() with pytest.raises(sqlite3.DatabaseError): util.run_checks_on_open_db("fake_db_path", cursor, True) - - -def _corrupt_db_file(test_db_file): - """Corrupt an sqlite3 database file.""" - f = open(test_db_file, "a") - f.write("I am a corrupt db") - f.close() From 41332493b52fd77ba1580309e4a697910b61a681 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 21:28:52 -1000 Subject: [PATCH 0621/1818] Add suggested area support to Sonos (#46794) --- homeassistant/components/sonos/media_player.py | 1 + tests/components/sonos/test_media_player.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 2c69730211b2b8..363e499292ea5e 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -589,6 +589,7 @@ def device_info(self): "sw_version": self._sw_version, "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac_address)}, "manufacturer": "Sonos", + "suggested_area": self._name, } @property diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index 6a401ee0c16846..ba9ba1c6db6285 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -56,4 +56,5 @@ async def test_device_registry(hass, config_entry, config, soco): assert reg_device.sw_version == "49.2-64250" assert reg_device.connections == {("mac", "00:11:22:33:44:55")} assert reg_device.manufacturer == "Sonos" + assert reg_device.suggested_area == "Zone A" assert reg_device.name == "Zone A" From b775a0d796974608b2e5edfe3e772d7689470f0d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Feb 2021 21:34:52 -1000 Subject: [PATCH 0622/1818] Run homekit service calls in async since the server is now async (#45859) * Simplify homekit runs and service calls Now that the homekit server is async, call_service and run are running in the Home Assistant event loop * remove comment * remove another comment --- .../components/homekit/accessories.py | 28 ++++--------- .../components/homekit/type_cameras.py | 4 +- .../components/homekit/type_covers.py | 18 +++++---- homeassistant/components/homekit/type_fans.py | 8 ++-- .../components/homekit/type_humidifiers.py | 8 ++-- .../components/homekit/type_lights.py | 2 +- .../components/homekit/type_locks.py | 2 +- .../components/homekit/type_media_players.py | 20 +++++----- .../homekit/type_security_systems.py | 2 +- .../components/homekit/type_switches.py | 10 +++-- .../components/homekit/type_thermostats.py | 6 +-- tests/components/homekit/test_accessories.py | 34 ++++++++-------- tests/components/homekit/test_type_cameras.py | 36 ++++++++--------- tests/components/homekit/test_type_covers.py | 16 ++++---- tests/components/homekit/test_type_fans.py | 10 ++--- .../homekit/test_type_humidifiers.py | 14 +++---- tests/components/homekit/test_type_lights.py | 16 ++++---- tests/components/homekit/test_type_locks.py | 2 +- .../homekit/test_type_media_players.py | 8 ++-- .../homekit/test_type_security_systems.py | 4 +- tests/components/homekit/test_type_sensors.py | 16 ++++---- .../components/homekit/test_type_switches.py | 20 +++++----- .../homekit/test_type_thermostats.py | 40 +++++++++---------- 23 files changed, 157 insertions(+), 167 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 6a33b63e89aa66..e31b9ec842ebef 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -300,17 +300,7 @@ def available(self): return state is not None and state.state != STATE_UNAVAILABLE async def run(self): - """Handle accessory driver started event. - - Run inside the HAP-python event loop. - """ - self.hass.add_job(self.run_handler) - - async def run_handler(self): - """Handle accessory driver started event. - - Run inside the Home Assistant event loop. - """ + """Handle accessory driver started event.""" state = self.hass.states.get(self.entity_id) self.async_update_state_callback(state) self._subscriptions.append( @@ -441,15 +431,9 @@ def async_update_state(self, new_state): """ raise NotImplementedError() - def call_service(self, domain, service, service_data, value=None): + @ha_callback + def async_call_service(self, domain, service, service_data, value=None): """Fire event and call service for changes from HomeKit.""" - self.hass.add_job(self.async_call_service, domain, service, service_data, value) - - async def async_call_service(self, domain, service, service_data, value=None): - """Fire event and call service for changes from HomeKit. - - This method must be run in the event loop. - """ event_data = { ATTR_ENTITY_ID: self.entity_id, ATTR_DISPLAY_NAME: self.display_name, @@ -459,8 +443,10 @@ async def async_call_service(self, domain, service, service_data, value=None): context = Context() self.hass.bus.async_fire(EVENT_HOMEKIT_CHANGED, event_data, context=context) - await self.hass.services.async_call( - domain, service, service_data, context=context + self.hass.async_create_task( + self.hass.services.async_call( + domain, service, service_data, context=context + ) ) @ha_callback diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index 0a499bf5d24468..48f7ad9b064792 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -240,7 +240,7 @@ def __init__(self, hass, driver, name, entity_id, aid, config): self._async_update_doorbell_state(state) - async def run_handler(self): + async def run(self): """Handle accessory driver started event. Run inside the Home Assistant event loop. @@ -259,7 +259,7 @@ async def run_handler(self): self._async_update_doorbell_state_event, ) - await super().run_handler() + await super().run() @callback def _async_update_motion_state_event(self, event): diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index daa782b8d676b5..ca375bb6f37c88 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -113,7 +113,7 @@ def __init__(self, *args): self.async_update_state(state) - async def run_handler(self): + async def run(self): """Handle accessory driver started event. Run inside the Home Assistant event loop. @@ -125,7 +125,7 @@ async def run_handler(self): self._async_update_obstruction_event, ) - await super().run_handler() + await super().run() @callback def _async_update_obstruction_event(self, event): @@ -158,11 +158,11 @@ def set_state(self, value): if value == HK_DOOR_OPEN: if self.char_current_state.value != value: self.char_current_state.set_value(HK_DOOR_OPENING) - self.call_service(DOMAIN, SERVICE_OPEN_COVER, params) + self.async_call_service(DOMAIN, SERVICE_OPEN_COVER, params) elif value == HK_DOOR_CLOSED: if self.char_current_state.value != value: self.char_current_state.set_value(HK_DOOR_CLOSING) - self.call_service(DOMAIN, SERVICE_CLOSE_COVER, params) + self.async_call_service(DOMAIN, SERVICE_CLOSE_COVER, params) @callback def async_update_state(self, new_state): @@ -231,7 +231,9 @@ def set_stop(self, value): """Stop the cover motion from HomeKit.""" if value != 1: return - self.call_service(DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: self.entity_id}) + self.async_call_service( + DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: self.entity_id} + ) def set_tilt(self, value): """Set tilt to value if call came from HomeKit.""" @@ -243,7 +245,7 @@ def set_tilt(self, value): params = {ATTR_ENTITY_ID: self.entity_id, ATTR_TILT_POSITION: value} - self.call_service(DOMAIN, SERVICE_SET_COVER_TILT_POSITION, params, value) + self.async_call_service(DOMAIN, SERVICE_SET_COVER_TILT_POSITION, params, value) @callback def async_update_state(self, new_state): @@ -287,7 +289,7 @@ def move_cover(self, value): """Move cover to value if call came from HomeKit.""" _LOGGER.debug("%s: Set position to %d", self.entity_id, value) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_POSITION: value} - self.call_service(DOMAIN, SERVICE_SET_COVER_POSITION, params, value) + self.async_call_service(DOMAIN, SERVICE_SET_COVER_POSITION, params, value) @callback def async_update_state(self, new_state): @@ -376,7 +378,7 @@ def move_cover(self, value): service, position = (SERVICE_CLOSE_COVER, 0) params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) # Snap the current/target position to the expected final position. self.char_current_position.set_value(position) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index d7215be9508760..306beb89c48c05 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -125,27 +125,27 @@ def set_state(self, value): _LOGGER.debug("%s: Set state to %d", self.entity_id, value) service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) def set_direction(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set direction to %d", self.entity_id, value) direction = DIRECTION_REVERSE if value == 1 else DIRECTION_FORWARD params = {ATTR_ENTITY_ID: self.entity_id, ATTR_DIRECTION: direction} - self.call_service(DOMAIN, SERVICE_SET_DIRECTION, params, direction) + self.async_call_service(DOMAIN, SERVICE_SET_DIRECTION, params, direction) def set_oscillating(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set oscillating to %d", self.entity_id, value) oscillating = value == 1 params = {ATTR_ENTITY_ID: self.entity_id, ATTR_OSCILLATING: oscillating} - self.call_service(DOMAIN, SERVICE_OSCILLATE, params, oscillating) + self.async_call_service(DOMAIN, SERVICE_OSCILLATE, params, oscillating) def set_percentage(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set speed to %d", self.entity_id, value) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_PERCENTAGE: value} - self.call_service(DOMAIN, SERVICE_SET_PERCENTAGE, params, value) + self.async_call_service(DOMAIN, SERVICE_SET_PERCENTAGE, params, value) @callback def async_update_state(self, new_state): diff --git a/homeassistant/components/homekit/type_humidifiers.py b/homeassistant/components/homekit/type_humidifiers.py index dd829206b0cf7b..6e1978d9499ef4 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -143,7 +143,7 @@ def __init__(self, *args): if humidity_state: self._async_update_current_humidity(humidity_state) - async def run_handler(self): + async def run(self): """Handle accessory driver started event. Run inside the Home Assistant event loop. @@ -155,7 +155,7 @@ async def run_handler(self): self.async_update_current_humidity_event, ) - await super().run_handler() + await super().run() @callback def async_update_current_humidity_event(self, event): @@ -201,7 +201,7 @@ def _set_chars(self, char_values): ) if CHAR_ACTIVE in char_values: - self.call_service( + self.async_call_service( DOMAIN, SERVICE_TURN_ON if char_values[CHAR_ACTIVE] else SERVICE_TURN_OFF, {ATTR_ENTITY_ID: self.entity_id}, @@ -210,7 +210,7 @@ def _set_chars(self, char_values): if self._target_humidity_char_name in char_values: humidity = round(char_values[self._target_humidity_char_name]) - self.call_service( + self.async_call_service( DOMAIN, SERVICE_SET_HUMIDITY, {ATTR_ENTITY_ID: self.entity_id, ATTR_HUMIDITY: humidity}, diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 086934ea6f756f..8be1580537dd45 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -139,7 +139,7 @@ def _set_chars(self, char_values): params[ATTR_HS_COLOR] = color events.append(f"set color at {color}") - self.call_service(DOMAIN, service, params, ", ".join(events)) + self.async_call_service(DOMAIN, service, params, ", ".join(events)) @callback def async_update_state(self, new_state): diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index af5b24c50e17d2..140940dda47b81 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -61,7 +61,7 @@ def set_state(self, value): params = {ATTR_ENTITY_ID: self.entity_id} if self._code: params[ATTR_CODE] = self._code - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) @callback def async_update_state(self, new_state): diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 901ac2173f4a0a..b54b62372f9d9e 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -177,7 +177,7 @@ def set_on_off(self, value): _LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value) service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) def set_play_pause(self, value): """Move switch state to value if call came from HomeKit.""" @@ -186,7 +186,7 @@ def set_play_pause(self, value): ) service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) def set_play_stop(self, value): """Move switch state to value if call came from HomeKit.""" @@ -195,7 +195,7 @@ def set_play_stop(self, value): ) service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_STOP params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) def set_toggle_mute(self, value): """Move switch state to value if call came from HomeKit.""" @@ -203,7 +203,7 @@ def set_toggle_mute(self, value): '%s: Set switch state for "toggle_mute" to %s', self.entity_id, value ) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_MUTED: value} - self.call_service(DOMAIN, SERVICE_VOLUME_MUTE, params) + self.async_call_service(DOMAIN, SERVICE_VOLUME_MUTE, params) @callback def async_update_state(self, new_state): @@ -344,7 +344,7 @@ def set_on_off(self, value): _LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value) service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) def set_mute(self, value): """Move switch state to value if call came from HomeKit.""" @@ -352,27 +352,27 @@ def set_mute(self, value): '%s: Set switch state for "toggle_mute" to %s', self.entity_id, value ) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_MUTED: value} - self.call_service(DOMAIN, SERVICE_VOLUME_MUTE, params) + self.async_call_service(DOMAIN, SERVICE_VOLUME_MUTE, params) def set_volume(self, value): """Send volume step value if call came from HomeKit.""" _LOGGER.debug("%s: Set volume to %s", self.entity_id, value) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_MEDIA_VOLUME_LEVEL: value} - self.call_service(DOMAIN, SERVICE_VOLUME_SET, params) + self.async_call_service(DOMAIN, SERVICE_VOLUME_SET, params) def set_volume_step(self, value): """Send volume step value if call came from HomeKit.""" _LOGGER.debug("%s: Step volume by %s", self.entity_id, value) service = SERVICE_VOLUME_DOWN if value else SERVICE_VOLUME_UP params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) def set_input_source(self, value): """Send input set value if call came from HomeKit.""" _LOGGER.debug("%s: Set current input to %s", self.entity_id, value) source = self.sources[value] params = {ATTR_ENTITY_ID: self.entity_id, ATTR_INPUT_SOURCE: source} - self.call_service(DOMAIN, SERVICE_SELECT_SOURCE, params) + self.async_call_service(DOMAIN, SERVICE_SELECT_SOURCE, params) def set_remote_key(self, value): """Send remote key value if call came from HomeKit.""" @@ -392,7 +392,7 @@ def set_remote_key(self, value): else: service = SERVICE_MEDIA_PLAY_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) else: # Unhandled keys can be handled by listening to the event bus self.hass.bus.fire( diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index feae1b5cd0676b..acbf636c1c3b30 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -150,7 +150,7 @@ def set_security_state(self, value): params = {ATTR_ENTITY_ID: self.entity_id} if self._alarm_code: params[ATTR_CODE] = self._alarm_code - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) @callback def async_update_state(self, new_state): diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index b3ee8a06497497..1ce6c364896ef5 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -80,7 +80,7 @@ def set_state(self, value): _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) @callback def async_update_state(self, new_state): @@ -131,7 +131,7 @@ def set_state(self, value): return params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF - self.call_service(self._domain, service, params) + self.async_call_service(self._domain, service, params) if self.activate_only: call_later(self.hass, 1, self.reset_switch) @@ -169,7 +169,9 @@ def set_state(self, value): sup_return_home = features & SUPPORT_RETURN_HOME service = SERVICE_RETURN_TO_BASE if sup_return_home else SERVICE_TURN_OFF - self.call_service(VACUUM_DOMAIN, service, {ATTR_ENTITY_ID: self.entity_id}) + self.async_call_service( + VACUUM_DOMAIN, service, {ATTR_ENTITY_ID: self.entity_id} + ) @callback def async_update_state(self, new_state): @@ -209,7 +211,7 @@ def set_state(self, value): self.char_in_use.set_value(value) params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF - self.call_service(DOMAIN, service, params) + self.async_call_service(DOMAIN, service, params) @callback def async_update_state(self, new_state): diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index a1c1343261470c..2eb63f4c840fb4 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -356,7 +356,7 @@ def _set_chars(self, char_values): if service: params[ATTR_ENTITY_ID] = self.entity_id - self.call_service( + self.async_call_service( DOMAIN_CLIMATE, service, params, @@ -407,7 +407,7 @@ def set_target_humidity(self, value): """Set target humidity to value if call came from HomeKit.""" _LOGGER.debug("%s: Set target humidity to %d", self.entity_id, value) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HUMIDITY: value} - self.call_service( + self.async_call_service( DOMAIN_CLIMATE, SERVICE_SET_HUMIDITY, params, f"{value}{PERCENTAGE}" ) @@ -584,7 +584,7 @@ def set_target_temperature(self, value): _LOGGER.debug("%s: Set target temperature to %.1f°C", self.entity_id, value) temperature = temperature_to_states(value, self._unit) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_TEMPERATURE: temperature} - self.call_service( + self.async_call_service( DOMAIN_WATER_HEATER, SERVICE_SET_TEMPERATURE_WATER_HEATER, params, diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 8ba2d9fb0b2455..e308ed2153751c 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -57,7 +57,7 @@ async def test_accessory_cancels_track_state_change_on_stop(hass, hk_driver): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ): - await acc.run_handler() + await acc.run() assert len(hass.data[TRACK_STATE_CHANGE_CALLBACKS][entity_id]) == 1 acc.async_stop() assert entity_id not in hass.data[TRACK_STATE_CHANGE_CALLBACKS] @@ -121,7 +121,7 @@ async def test_home_accessory(hass, hk_driver): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -156,7 +156,7 @@ async def test_battery_service(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -212,7 +212,7 @@ async def test_battery_service(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -253,7 +253,7 @@ async def test_linked_battery_sensor(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -298,7 +298,7 @@ async def test_linked_battery_sensor(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -340,7 +340,7 @@ async def test_linked_battery_charging_sensor(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -352,7 +352,7 @@ async def test_linked_battery_charging_sensor(hass, hk_driver, caplog): "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: hass.states.async_set(linked_battery_charging_sensor, STATE_OFF, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -362,7 +362,7 @@ async def test_linked_battery_charging_sensor(hass, hk_driver, caplog): "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: hass.states.async_set(linked_battery_charging_sensor, STATE_ON, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -372,7 +372,7 @@ async def test_linked_battery_charging_sensor(hass, hk_driver, caplog): "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: hass.states.async_remove(linked_battery_charging_sensor) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc._char_charging.value == 1 @@ -405,7 +405,7 @@ async def test_linked_battery_sensor_and_linked_battery_charging_sensor( with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -449,7 +449,7 @@ async def test_missing_linked_battery_charging_sensor(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ): - await acc.run_handler() + await acc.run() await hass.async_block_till_done() # Make sure we don't throw if the entity_id @@ -458,7 +458,7 @@ async def test_missing_linked_battery_charging_sensor(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ): - await acc.run_handler() + await acc.run() await hass.async_block_till_done() @@ -482,7 +482,7 @@ async def test_missing_linked_battery_sensor(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -496,7 +496,7 @@ async def test_missing_linked_battery_sensor(hass, hk_driver, caplog): "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: hass.states.async_remove(entity_id) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert not acc.linked_battery_sensor @@ -517,7 +517,7 @@ async def test_battery_appears_after_startup(hass, hk_driver, caplog): with patch( "homeassistant.components.homekit.accessories.HomeAccessory.async_update_state" ) as mock_async_update_state: - await acc.run_handler() + await acc.run() await hass.async_block_till_done() state = hass.states.get(entity_id) mock_async_update_state.assert_called_with(state) @@ -551,7 +551,7 @@ async def test_call_service(hass, hk_driver, events): test_service = "open_cover" test_value = "value" - await acc.async_call_service( + acc.async_call_service( test_domain, test_service, {ATTR_ENTITY_ID: entity_id}, test_value ) await hass.async_block_till_done() diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index c9b2ebc422cd4b..ba08ea3caafc5a 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -45,35 +45,35 @@ async def _async_start_streaming(hass, acc): """Start streaming a camera.""" acc.set_selected_stream_configuration(MOCK_START_STREAM_TLV) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() async def _async_setup_endpoints(hass, acc): """Set camera endpoints.""" acc.set_endpoints(MOCK_END_POINTS_TLV) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() async def _async_reconfigure_stream(hass, acc, session_info, stream_config): """Reconfigure the stream.""" await acc.reconfigure_stream(session_info, stream_config) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() async def _async_stop_all_streams(hass, acc): """Stop all camera streams.""" await acc.stop() - await acc.run_handler() + await acc.run() await hass.async_block_till_done() async def _async_stop_stream(hass, acc, session_info): """Stop a camera stream.""" await acc.stop_stream(session_info) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() @@ -156,7 +156,7 @@ async def test_camera_stream_source_configured(hass, run_driver, events): bridge.add_accessory(acc) bridge.add_accessory(not_camera_acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -271,7 +271,7 @@ async def test_camera_stream_source_configured_with_failing_ffmpeg( bridge.add_accessory(acc) bridge.add_accessory(not_camera_acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -311,7 +311,7 @@ async def test_camera_stream_source_found(hass, run_driver, events): 2, {}, ) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -361,7 +361,7 @@ async def test_camera_stream_source_fails(hass, run_driver, events): 2, {}, ) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -396,7 +396,7 @@ async def test_camera_with_no_stream(hass, run_driver, events): 2, {}, ) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -439,7 +439,7 @@ async def test_camera_stream_source_configured_and_copy_codec(hass, run_driver, bridge = HomeBridge("hass", run_driver, "Test Bridge") bridge.add_accessory(acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -510,7 +510,7 @@ async def test_camera_streaming_fails_after_starting_ffmpeg(hass, run_driver, ev bridge = HomeBridge("hass", run_driver, "Test Bridge") bridge.add_accessory(acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -588,7 +588,7 @@ async def test_camera_with_linked_motion_sensor(hass, run_driver, events): bridge = HomeBridge("hass", run_driver, "Test Bridge") bridge.add_accessory(acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -617,7 +617,7 @@ async def test_camera_with_linked_motion_sensor(hass, run_driver, events): # motion sensor is removed hass.states.async_remove(motion_entity_id) await hass.async_block_till_done() - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert char.value is True @@ -644,7 +644,7 @@ async def test_camera_with_a_missing_linked_motion_sensor(hass, run_driver, even bridge = HomeBridge("hass", run_driver, "Test Bridge") bridge.add_accessory(acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -686,7 +686,7 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events): bridge = HomeBridge("hass", run_driver, "Test Bridge") bridge.add_accessory(acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera @@ -725,7 +725,7 @@ async def test_camera_with_linked_doorbell_sensor(hass, run_driver, events): # doorbell sensor is removed hass.states.async_remove(doorbell_entity_id) await hass.async_block_till_done() - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert char.value == 0 assert char2.value == 0 @@ -753,7 +753,7 @@ async def test_camera_with_a_missing_linked_doorbell_sensor(hass, run_driver, ev bridge = HomeBridge("hass", run_driver, "Test Bridge") bridge.add_accessory(acc) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 17 # Camera diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index d39e9cda7d07b4..1c0de6c3af253b 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -52,7 +52,7 @@ async def test_garage_door_open_close(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = GarageDoorOpener(hass, hk_driver, "Garage Door", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -136,7 +136,7 @@ async def test_windowcovering_set_cover_position(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -206,7 +206,7 @@ async def test_window_instantiate(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = Window(hass, hk_driver, "Window", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -225,7 +225,7 @@ async def test_windowcovering_cover_set_tilt(hass, hk_driver, events): ) await hass.async_block_till_done() acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -289,7 +289,7 @@ async def test_windowcovering_open_close(hass, hk_driver, events): hass.states.async_set(entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: 0}) acc = WindowCoveringBasic(hass, hk_driver, "Cover", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -372,7 +372,7 @@ async def test_windowcovering_open_close_stop(hass, hk_driver, events): entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP} ) acc = WindowCoveringBasic(hass, hk_driver, "Cover", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() # Set from HomeKit @@ -423,7 +423,7 @@ async def test_windowcovering_open_close_with_position_and_stop( {ATTR_SUPPORTED_FEATURES: SUPPORT_STOP | SUPPORT_SET_POSITION}, ) acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() # Set from HomeKit @@ -534,7 +534,7 @@ async def test_garage_door_with_linked_obstruction_sensor(hass, hk_driver, event 2, {CONF_LINKED_OBSTRUCTION_SENSOR: linked_obstruction_sensor_entity_id}, ) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index e99f9d0b95afd1..baa47462cdc4cf 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -46,7 +46,7 @@ async def test_fan_basic(hass, hk_driver, events): # If there are no speed_list values, then HomeKit speed is unsupported assert acc.char_speed is None - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_active.value == 1 @@ -123,7 +123,7 @@ async def test_fan_direction(hass, hk_driver, events): assert acc.char_direction.value == 0 - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_direction.value == 0 @@ -191,7 +191,7 @@ async def test_fan_oscillate(hass, hk_driver, events): assert acc.char_swing.value == 0 - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_swing.value == 0 @@ -267,7 +267,7 @@ async def test_fan_speed(hass, hk_driver, events): assert acc.char_speed.value != 0 assert acc.char_speed.properties[PROP_MIN_STEP] == 25 - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hass.states.async_set(entity_id, STATE_ON, {ATTR_PERCENTAGE: 100}) @@ -349,7 +349,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, events): # Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the # speed to 100 when turning on a fan on a freshly booted up server. assert acc.char_speed.value != 0 - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hass.states.async_set( diff --git a/tests/components/homekit/test_type_humidifiers.py b/tests/components/homekit/test_type_humidifiers.py index 51f9621d15afd2..1a301e340b3e8a 100644 --- a/tests/components/homekit/test_type_humidifiers.py +++ b/tests/components/homekit/test_type_humidifiers.py @@ -54,7 +54,7 @@ async def test_humidifier(hass, hk_driver, events): ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 1 @@ -135,7 +135,7 @@ async def test_dehumidifier(hass, hk_driver, events): ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 1 @@ -220,7 +220,7 @@ async def test_hygrostat_power_state(hass, hk_driver, events): ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_current_humidifier_dehumidifier.value == 2 @@ -298,7 +298,7 @@ async def test_hygrostat_get_humidity_range(hass, hk_driver): ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_target_humidity.properties[PROP_MAX_VALUE] == 45 @@ -332,7 +332,7 @@ async def test_humidifier_with_linked_humidity_sensor(hass, hk_driver): ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_current_humidity.value == 42.0 @@ -384,7 +384,7 @@ async def test_humidifier_with_a_missing_linked_humidity_sensor(hass, hk_driver) ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_current_humidity.value == 0 @@ -401,7 +401,7 @@ async def test_humidifier_as_dehumidifier(hass, hk_driver, events, caplog): ) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_target_humidifier_dehumidifier.value == 1 diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 42ef18f3505b33..0ab3ef8e45dd8d 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -42,7 +42,7 @@ async def test_light_basic(hass, hk_driver, events): assert acc.category == 5 # Lightbulb assert acc.char_on.value - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_on.value == 1 @@ -117,7 +117,7 @@ async def test_light_brightness(hass, hk_driver, events): char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID] char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_brightness.value == 100 @@ -231,7 +231,7 @@ async def test_light_color_temperature(hass, hk_driver, events): assert acc.char_color_temperature.value == 190 - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_color_temperature.value == 190 @@ -285,14 +285,14 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, events): hass.states.async_set(entity_id, STATE_ON, {ATTR_COLOR_TEMP: 224}) await hass.async_block_till_done() - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_hue.value == 27 assert acc.char_saturation.value == 27 hass.states.async_set(entity_id, STATE_ON, {ATTR_COLOR_TEMP: 352}) await hass.async_block_till_done() - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_hue.value == 28 assert acc.char_saturation.value == 61 @@ -314,7 +314,7 @@ async def test_light_rgb_color(hass, hk_driver, events): assert acc.char_hue.value == 260 assert acc.char_saturation.value == 90 - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_hue.value == 260 assert acc.char_saturation.value == 90 @@ -407,7 +407,7 @@ async def test_light_set_brightness_and_color(hass, hk_driver, events): char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_brightness.value == 100 @@ -482,7 +482,7 @@ async def test_light_set_brightness_and_color_temp(hass, hk_driver, events): char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] char_color_temperature_iid = acc.char_color_temperature.to_HAP()[HAP_REPR_IID] - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_brightness.value == 100 diff --git a/tests/components/homekit/test_type_locks.py b/tests/components/homekit/test_type_locks.py index 7899af36995be4..b2bb9b4736ea94 100644 --- a/tests/components/homekit/test_type_locks.py +++ b/tests/components/homekit/test_type_locks.py @@ -24,7 +24,7 @@ async def test_lock_unlock(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = Lock(hass, hk_driver, "Lock", entity_id, 2, config) - await acc.run_handler() + await acc.run() assert acc.aid == 2 assert acc.category == 6 # DoorLock diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 9516963a982327..0b9d25ce3ecb28 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -61,7 +61,7 @@ async def test_media_player_set_state(hass, hk_driver, events): ) await hass.async_block_till_done() acc = MediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, config) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -203,7 +203,7 @@ async def test_media_player_television(hass, hk_driver, events, caplog): ) await hass.async_block_till_done() acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -375,7 +375,7 @@ async def test_media_player_television_basic(hass, hk_driver, events, caplog): ) await hass.async_block_till_done() acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.chars_tv == [CHAR_REMOTE_KEY] @@ -411,7 +411,7 @@ async def test_media_player_television_supports_source_select_no_sources( ) await hass.async_block_till_done() acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.support_select_source is False diff --git a/tests/components/homekit/test_type_security_systems.py b/tests/components/homekit/test_type_security_systems.py index d6bf74bb7cfa17..19b8b5720e2110 100644 --- a/tests/components/homekit/test_type_security_systems.py +++ b/tests/components/homekit/test_type_security_systems.py @@ -34,7 +34,7 @@ async def test_switch_set_state(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = SecuritySystem(hass, hk_driver, "SecuritySystem", entity_id, 2, config) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -238,7 +238,7 @@ async def test_supported_states(hass, hk_driver, events): await hass.async_block_till_done() acc = SecuritySystem(hass, hk_driver, "SecuritySystem", entity_id, 2, config) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() valid_current_values = acc.char_current_state.properties.get("ValidValues") diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index 7ee79352d7b5c9..fe2ae7566d5142 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -40,7 +40,7 @@ async def test_temperature(hass, hk_driver): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = TemperatureSensor(hass, hk_driver, "Temperature", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -74,7 +74,7 @@ async def test_humidity(hass, hk_driver): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = HumiditySensor(hass, hk_driver, "Humidity", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -98,7 +98,7 @@ async def test_air_quality(hass, hk_driver): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = AirQualitySensor(hass, hk_driver, "Air Quality", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -130,7 +130,7 @@ async def test_co(hass, hk_driver): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = CarbonMonoxideSensor(hass, hk_driver, "CO", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -170,7 +170,7 @@ async def test_co2(hass, hk_driver): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = CarbonDioxideSensor(hass, hk_driver, "CO2", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -210,7 +210,7 @@ async def test_light(hass, hk_driver): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = LightSensor(hass, hk_driver, "Light", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -235,7 +235,7 @@ async def test_binary(hass, hk_driver): await hass.async_block_till_done() acc = BinarySensor(hass, hk_driver, "Window Opening", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -274,7 +274,7 @@ async def test_motion_uses_bool(hass, hk_driver): await hass.async_block_till_done() acc = BinarySensor(hass, hk_driver, "Motion Sensor", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 diff --git a/tests/components/homekit/test_type_switches.py b/tests/components/homekit/test_type_switches.py index 5d218a6ef8a5b2..2ce0acfc8bcd32 100644 --- a/tests/components/homekit/test_type_switches.py +++ b/tests/components/homekit/test_type_switches.py @@ -42,7 +42,7 @@ async def test_outlet_set_state(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = Outlet(hass, hk_driver, "Outlet", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -95,7 +95,7 @@ async def test_switch_set_state(hass, hk_driver, entity_id, attrs, events): hass.states.async_set(entity_id, None, attrs) await hass.async_block_till_done() acc = Switch(hass, hk_driver, "Switch", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -139,25 +139,25 @@ async def test_valve_set_state(hass, hk_driver, events): await hass.async_block_till_done() acc = Valve(hass, hk_driver, "Valve", entity_id, 2, {CONF_TYPE: TYPE_FAUCET}) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.category == 29 # Faucet assert acc.char_valve_type.value == 3 # Water faucet acc = Valve(hass, hk_driver, "Valve", entity_id, 2, {CONF_TYPE: TYPE_SHOWER}) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.category == 30 # Shower assert acc.char_valve_type.value == 2 # Shower head acc = Valve(hass, hk_driver, "Valve", entity_id, 2, {CONF_TYPE: TYPE_SPRINKLER}) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.category == 28 # Sprinkler assert acc.char_valve_type.value == 1 # Irrigation acc = Valve(hass, hk_driver, "Valve", entity_id, 2, {CONF_TYPE: TYPE_VALVE}) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -210,7 +210,7 @@ async def test_vacuum_set_state_with_returnhome_and_start_support( await hass.async_block_till_done() acc = Vacuum(hass, hk_driver, "Vacuum", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 assert acc.category == 8 # Switch @@ -266,7 +266,7 @@ async def test_vacuum_set_state_without_returnhome_and_start_support( await hass.async_block_till_done() acc = Vacuum(hass, hk_driver, "Vacuum", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 assert acc.category == 8 # Switch @@ -310,7 +310,7 @@ async def test_reset_switch(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = Switch(hass, hk_driver, "Switch", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.activate_only is True @@ -347,7 +347,7 @@ async def test_reset_switch_reload(hass, hk_driver, events): hass.states.async_set(entity_id, None) await hass.async_block_till_done() acc = Switch(hass, hk_driver, "Switch", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.activate_only is False diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 79b5ca2109752f..7d3d0d14c2fab6 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -89,7 +89,7 @@ async def test_thermostat(hass, hk_driver, events): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 1 @@ -431,7 +431,7 @@ async def test_thermostat_auto(hass, hk_driver, events): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_cooling_thresh_temp.value == 23.0 @@ -570,7 +570,7 @@ async def test_thermostat_humidity(hass, hk_driver, events): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_target_humidity.value == 50 @@ -645,7 +645,7 @@ async def test_thermostat_power_state(hass, hk_driver, events): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_current_heat_cool.value == 1 @@ -756,7 +756,7 @@ async def test_thermostat_fahrenheit(hass, hk_driver, events): with patch.object(hass.config.units, CONF_TEMPERATURE_UNIT, new=TEMP_FAHRENHEIT): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hass.states.async_set( @@ -879,7 +879,7 @@ async def test_thermostat_temperature_step_whole(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_target_temp.properties[PROP_MIN_STEP] == 0.1 @@ -942,7 +942,7 @@ async def test_thermostat_hvac_modes(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [0, 1] @@ -985,7 +985,7 @@ async def test_thermostat_hvac_modes_with_auto_heat_cool(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [0, 1, 3] @@ -1041,7 +1041,7 @@ async def test_thermostat_hvac_modes_with_auto_no_heat_cool(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [0, 1, 3] @@ -1095,7 +1095,7 @@ async def test_thermostat_hvac_modes_with_auto_only(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [0, 3] @@ -1149,7 +1149,7 @@ async def test_thermostat_hvac_modes_with_heat_only(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [HC_HEAT_COOL_OFF, HC_HEAT_COOL_HEAT] @@ -1209,7 +1209,7 @@ async def test_thermostat_hvac_modes_with_cool_only(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [HC_HEAT_COOL_OFF, HC_HEAT_COOL_COOL] @@ -1273,7 +1273,7 @@ async def test_thermostat_hvac_modes_with_heat_cool_only(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [ @@ -1362,7 +1362,7 @@ async def test_thermostat_hvac_modes_without_off(hass, hk_driver): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hap = acc.char_target_heat_cool.to_HAP() assert hap["valid-values"] == [1, 3] @@ -1401,7 +1401,7 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, events acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_cooling_thresh_temp.value == 23.0 @@ -1576,7 +1576,7 @@ async def test_water_heater(hass, hk_driver, events): hass.states.async_set(entity_id, HVAC_MODE_HEAT) await hass.async_block_till_done() acc = WaterHeater(hass, hk_driver, "WaterHeater", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.aid == 2 @@ -1655,7 +1655,7 @@ async def test_water_heater_fahrenheit(hass, hk_driver, events): await hass.async_block_till_done() with patch.object(hass.config.units, CONF_TEMPERATURE_UNIT, new=TEMP_FAHRENHEIT): acc = WaterHeater(hass, hk_driver, "WaterHeater", entity_id, 2, None) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() hass.states.async_set(entity_id, HVAC_MODE_HEAT, {ATTR_TEMPERATURE: 131}) @@ -1762,7 +1762,7 @@ async def test_thermostat_with_no_modes_when_we_first_see(hass, hk_driver, event acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_cooling_thresh_temp.value == 23.0 @@ -1815,7 +1815,7 @@ async def test_thermostat_with_no_off_after_recheck(hass, hk_driver, events): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_cooling_thresh_temp.value == 23.0 @@ -1869,7 +1869,7 @@ async def test_thermostat_with_temp_clamps(hass, hk_driver, events): acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None) hk_driver.add_accessory(acc) - await acc.run_handler() + await acc.run() await hass.async_block_till_done() assert acc.char_cooling_thresh_temp.value == 100 From 26ce316c18f3f7e753589bdac835aa5a278491df Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 20 Feb 2021 09:11:36 +0100 Subject: [PATCH 0623/1818] Do not trigger when numeric_state is true at startup (#46424) --- .../homeassistant/triggers/numeric_state.py | 54 +++++--- tests/components/cover/test_device_trigger.py | 22 ++- .../triggers/test_numeric_state.py | 129 +++++++++++++++++- .../components/sensor/test_device_trigger.py | 3 +- 4 files changed, 175 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 4f406de23caacb..16b3fb97475be0 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -73,7 +73,7 @@ async def async_attach_trigger( template.attach(hass, time_delta) value_template = config.get(CONF_VALUE_TEMPLATE) unsub_track_same = {} - entities_triggered = set() + armed_entities = set() period: dict = {} attribute = config.get(CONF_ATTRIBUTE) job = HassJob(action) @@ -100,20 +100,22 @@ def variables(entity_id): @callback def check_numeric_state(entity_id, from_s, to_s): - """Return True if criteria are now met.""" + """Return whether the criteria are met, raise ConditionError if unknown.""" + return condition.async_numeric_state( + hass, to_s, below, above, value_template, variables(entity_id), attribute + ) + + # Each entity that starts outside the range is already armed (ready to fire). + for entity_id in entity_ids: try: - return condition.async_numeric_state( - hass, - to_s, - below, - above, - value_template, - variables(entity_id), - attribute, + if not check_numeric_state(entity_id, None, entity_id): + armed_entities.add(entity_id) + except exceptions.ConditionError as ex: + _LOGGER.warning( + "Error initializing 'numeric_state' trigger for '%s': %s", + automation_info["name"], + ex, ) - except exceptions.ConditionError as err: - _LOGGER.warning("%s", err) - return False @callback def state_automation_listener(event): @@ -142,12 +144,27 @@ def call_action(): to_s.context, ) - matching = check_numeric_state(entity_id, from_s, to_s) + @callback + def check_numeric_state_no_raise(entity_id, from_s, to_s): + """Return True if the criteria are now met, False otherwise.""" + try: + return check_numeric_state(entity_id, from_s, to_s) + except exceptions.ConditionError: + # This is an internal same-state listener so we just drop the + # error. The same error will be reached and logged by the + # primary async_track_state_change_event() listener. + return False + + try: + matching = check_numeric_state(entity_id, from_s, to_s) + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in '%s' trigger: %s", automation_info["name"], ex) + return if not matching: - entities_triggered.discard(entity_id) - elif entity_id not in entities_triggered: - entities_triggered.add(entity_id) + armed_entities.add(entity_id) + elif entity_id in armed_entities: + armed_entities.discard(entity_id) if time_delta: try: @@ -160,7 +177,6 @@ def call_action(): automation_info["name"], ex, ) - entities_triggered.discard(entity_id) return unsub_track_same[entity_id] = async_track_same_state( @@ -168,7 +184,7 @@ def call_action(): period[entity_id], call_action, entity_ids=entity_id, - async_check_same_func=check_numeric_state, + async_check_same_func=check_numeric_state_no_raise, ) else: call_action() diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index ab054ad8223bff..e8bb3cdc8df762 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -542,8 +542,12 @@ async def test_if_fires_on_position(hass, calls): ] }, ) + hass.states.async_set(ent.entity_id, STATE_OPEN, attributes={"current_position": 1}) hass.states.async_set( - ent.entity_id, STATE_CLOSED, attributes={"current_position": 50} + ent.entity_id, STATE_CLOSED, attributes={"current_position": 95} + ) + hass.states.async_set( + ent.entity_id, STATE_OPEN, attributes={"current_position": 50} ) await hass.async_block_till_done() assert len(calls) == 3 @@ -551,8 +555,8 @@ async def test_if_fires_on_position(hass, calls): [calls[0].data["some"], calls[1].data["some"], calls[2].data["some"]] ) == sorted( [ - "is_pos_gt_45_lt_90 - device - cover.set_position_cover - open - closed - None", - "is_pos_lt_90 - device - cover.set_position_cover - open - closed - None", + "is_pos_gt_45_lt_90 - device - cover.set_position_cover - closed - open - None", + "is_pos_lt_90 - device - cover.set_position_cover - closed - open - None", "is_pos_gt_45 - device - cover.set_position_cover - open - closed - None", ] ) @@ -666,7 +670,13 @@ async def test_if_fires_on_tilt_position(hass, calls): }, ) hass.states.async_set( - ent.entity_id, STATE_CLOSED, attributes={"current_tilt_position": 50} + ent.entity_id, STATE_OPEN, attributes={"current_tilt_position": 1} + ) + hass.states.async_set( + ent.entity_id, STATE_CLOSED, attributes={"current_tilt_position": 95} + ) + hass.states.async_set( + ent.entity_id, STATE_OPEN, attributes={"current_tilt_position": 50} ) await hass.async_block_till_done() assert len(calls) == 3 @@ -674,8 +684,8 @@ async def test_if_fires_on_tilt_position(hass, calls): [calls[0].data["some"], calls[1].data["some"], calls[2].data["some"]] ) == sorted( [ - "is_pos_gt_45_lt_90 - device - cover.set_position_cover - open - closed - None", - "is_pos_lt_90 - device - cover.set_position_cover - open - closed - None", + "is_pos_gt_45_lt_90 - device - cover.set_position_cover - closed - open - None", + "is_pos_lt_90 - device - cover.set_position_cover - closed - open - None", "is_pos_gt_45 - device - cover.set_position_cover - open - closed - None", ] ) diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 85dc68c770da61..831e20b78a1dbc 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -10,7 +10,12 @@ from homeassistant.components.homeassistant.triggers import ( numeric_state as numeric_state_trigger, ) -from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF +from homeassistant.const import ( + ATTR_ENTITY_ID, + ENTITY_MATCH_ALL, + SERVICE_TURN_OFF, + STATE_UNAVAILABLE, +) from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -241,7 +246,7 @@ async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls, below): @pytest.mark.parametrize("below", (10, "input_number.value_10")) -async def test_if_fires_on_initial_entity_below(hass, calls, below): +async def test_if_not_fires_on_initial_entity_below(hass, calls, below): """Test the firing when starting with a match.""" hass.states.async_set("test.entity", 9) await hass.async_block_till_done() @@ -261,14 +266,14 @@ async def test_if_fires_on_initial_entity_below(hass, calls, below): }, ) - # Fire on first update even if initial state was already below + # Do not fire on first update when initial state was already below hass.states.async_set("test.entity", 8) await hass.async_block_till_done() - assert len(calls) == 1 + assert len(calls) == 0 @pytest.mark.parametrize("above", (10, "input_number.value_10")) -async def test_if_fires_on_initial_entity_above(hass, calls, above): +async def test_if_not_fires_on_initial_entity_above(hass, calls, above): """Test the firing when starting with a match.""" hass.states.async_set("test.entity", 11) await hass.async_block_till_done() @@ -288,10 +293,10 @@ async def test_if_fires_on_initial_entity_above(hass, calls, above): }, ) - # Fire on first update even if initial state was already above + # Do not fire on first update when initial state was already above hass.states.async_set("test.entity", 12) await hass.async_block_till_done() - assert len(calls) == 1 + assert len(calls) == 0 @pytest.mark.parametrize("above", (10, "input_number.value_10")) @@ -320,6 +325,74 @@ async def test_if_fires_on_entity_change_above(hass, calls, above): assert len(calls) == 1 +async def test_if_fires_on_entity_unavailable_at_startup(hass, calls): + """Test the firing with changed entity at startup.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "numeric_state", + "entity_id": "test.entity", + "above": 10, + }, + "action": {"service": "test.automation"}, + } + }, + ) + # 11 is above 10 + hass.states.async_set("test.entity", 11) + await hass.async_block_till_done() + assert len(calls) == 0 + + +async def test_if_not_fires_on_entity_unavailable(hass, calls): + """Test the firing with entity changing to unavailable.""" + # set initial state + hass.states.async_set("test.entity", 9) + await hass.async_block_till_done() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "numeric_state", + "entity_id": "test.entity", + "above": 10, + }, + "action": {"service": "test.automation"}, + } + }, + ) + + # 11 is above 10 + hass.states.async_set("test.entity", 11) + await hass.async_block_till_done() + assert len(calls) == 1 + + # Going to unavailable and back should not fire + hass.states.async_set("test.entity", STATE_UNAVAILABLE) + await hass.async_block_till_done() + assert len(calls) == 1 + hass.states.async_set("test.entity", 11) + await hass.async_block_till_done() + assert len(calls) == 1 + + # Crossing threshold via unavailable should fire + hass.states.async_set("test.entity", 9) + await hass.async_block_till_done() + assert len(calls) == 1 + hass.states.async_set("test.entity", STATE_UNAVAILABLE) + await hass.async_block_till_done() + assert len(calls) == 1 + hass.states.async_set("test.entity", 11) + await hass.async_block_till_done() + assert len(calls) == 2 + + @pytest.mark.parametrize("above", (10, "input_number.value_10")) async def test_if_fires_on_entity_change_below_to_above(hass, calls, above): """Test the firing with changed entity.""" @@ -1449,6 +1522,48 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls, above, below) assert len(calls) == 1 +async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): + """Test for not firing on error with for template.""" + hass.states.async_set("test.entity", 0) + await hass.async_block_till_done() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "numeric_state", + "entity_id": "test.entity", + "above": 100, + "for": "00:00:05", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.states.async_set("test.entity", 101) + await hass.async_block_till_done() + assert len(calls) == 0 + + caplog.clear() + caplog.set_level(logging.WARNING) + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) + hass.states.async_set("test.entity", "unavailable") + await hass.async_block_till_done() + assert len(calls) == 0 + + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][1] == logging.WARNING + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) + hass.states.async_set("test.entity", 101) + await hass.async_block_till_done() + assert len(calls) == 0 + + @pytest.mark.parametrize( "above, below", ( diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index c39b4597632b71..d5755ac3288c3a 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -428,6 +428,7 @@ async def test_if_fires_on_state_change_with_for(hass, calls): assert hass.states.get(sensor1.entity_id).state == STATE_UNKNOWN assert len(calls) == 0 + hass.states.async_set(sensor1.entity_id, 10) hass.states.async_set(sensor1.entity_id, 11) await hass.async_block_till_done() assert len(calls) == 0 @@ -437,5 +438,5 @@ async def test_if_fires_on_state_change_with_for(hass, calls): await hass.async_block_till_done() assert ( calls[0].data["some"] - == f"turn_off device - {sensor1.entity_id} - unknown - 11 - 0:00:05" + == f"turn_off device - {sensor1.entity_id} - 10 - 11 - 0:00:05" ) From 788134cbc408cec45e1ff2014d112a8cdaf1683c Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 20 Feb 2021 03:50:00 -0500 Subject: [PATCH 0624/1818] Bump zwave-js-server-python to 0.18.0 (#46787) * updates to support changes in zwave-js-server-python * bump lib version * use named arguments for optional args * re-add lost commits --- homeassistant/components/zwave_js/climate.py | 10 +++++- homeassistant/components/zwave_js/entity.py | 21 ++++++----- homeassistant/components/zwave_js/light.py | 36 ++++++++++++++----- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_climate.py | 1 - tests/components/zwave_js/test_light.py | 16 ++++++--- 8 files changed, 63 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 341b8f99fd63db..f864efe91ff5c3 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -125,10 +125,18 @@ def __init__( ) self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: + # Some devices don't include a property key so we need to check for value + # ID's, both with and without the property key self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, command_class=CommandClass.THERMOSTAT_SETPOINT, - value_property_key_name=enum.value, + value_property_key=enum.value.key, + value_property_key_name=enum.value.name, + add_to_watched_value_ids=True, + ) or self.get_zwave_value( + THERMOSTAT_SETPOINT_PROPERTY, + command_class=CommandClass.THERMOSTAT_SETPOINT, + value_property_key_name=enum.value.name, add_to_watched_value_ids=True, ) # Use the first found setpoint value to always determine the temperature unit diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 911b0b7b2f8a1f..3e81bfaeadf878 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -134,6 +134,7 @@ def get_zwave_value( value_property: Union[str, int], command_class: Optional[int] = None, endpoint: Optional[int] = None, + value_property_key: Optional[int] = None, value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, @@ -146,16 +147,14 @@ def get_zwave_value( if endpoint is None: endpoint = self.info.primary_value.endpoint - # Build partial event data dictionary so we can change the endpoint later - partial_evt_data = { - "commandClass": command_class, - "property": value_property, - "propertyKeyName": value_property_key_name, - } - # lookup value by value_id value_id = get_value_id( - self.info.node, {**partial_evt_data, "endpoint": endpoint} + self.info.node, + command_class, + value_property, + endpoint=endpoint, + property_key=value_property_key, + property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) @@ -166,7 +165,11 @@ def get_zwave_value( if endpoint_.index != self.info.primary_value.endpoint: value_id = get_value_id( self.info.node, - {**partial_evt_data, "endpoint": endpoint_.index}, + command_class, + value_property, + endpoint=endpoint_.index, + property_key=value_property_key, + property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) if return_value: diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index dd444fdb40d30e..6ed0286e184465 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -3,7 +3,7 @@ from typing import Any, Callable, Optional, Tuple from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.const import CommandClass +from zwave_js_server.const import ColorComponent, CommandClass from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -200,10 +200,18 @@ async def async_turn_off(self, **kwargs: Any) -> None: async def _async_set_color(self, color_name: str, new_value: int) -> None: """Set defined color to given value.""" + try: + property_key = ColorComponent[color_name.upper().replace(" ", "_")].value + except KeyError: + raise ValueError( + "Illegal color name specified, color must be one of " + f"{','.join([color.name for color in ColorComponent])}" + ) from None cur_zwave_value = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, - value_property_key_name=color_name, + value_property_key=property_key.key, + value_property_key_name=property_key.name, ) # guard for unsupported command if cur_zwave_value is None: @@ -212,7 +220,8 @@ async def _async_set_color(self, color_name: str, new_value: int) -> None: target_zwave_value = self.get_zwave_value( "targetColor", CommandClass.SWITCH_COLOR, - value_property_key_name=color_name, + value_property_key=property_key.key, + value_property_key_name=property_key.name, ) if target_zwave_value is None: return @@ -276,13 +285,22 @@ def _calculate_color_values(self) -> None: # RGB support red_val = self.get_zwave_value( - "currentColor", CommandClass.SWITCH_COLOR, value_property_key_name="Red" + "currentColor", + CommandClass.SWITCH_COLOR, + value_property_key=ColorComponent.RED.value.key, + value_property_key_name=ColorComponent.RED.value.name, ) green_val = self.get_zwave_value( - "currentColor", CommandClass.SWITCH_COLOR, value_property_key_name="Green" + "currentColor", + CommandClass.SWITCH_COLOR, + value_property_key=ColorComponent.GREEN.value.key, + value_property_key_name=ColorComponent.GREEN.value.name, ) blue_val = self.get_zwave_value( - "currentColor", CommandClass.SWITCH_COLOR, value_property_key_name="Blue" + "currentColor", + CommandClass.SWITCH_COLOR, + value_property_key=ColorComponent.BLUE.value.key, + value_property_key_name=ColorComponent.BLUE.value.name, ) if red_val and green_val and blue_val: self._supports_color = True @@ -300,12 +318,14 @@ def _calculate_color_values(self) -> None: ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, - value_property_key_name="Warm White", + value_property_key=ColorComponent.WARM_WHITE.value.key, + value_property_key_name=ColorComponent.WARM_WHITE.value.name, ) cw_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, - value_property_key_name="Cold White", + value_property_key=ColorComponent.COLD_WHITE.value.key, + value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) if ww_val and cw_val: # Color temperature (CW + WW) Support diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 4bd12baa685198..56e60fc322cbfe 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.17.2"], + "requirements": ["zwave-js-server-python==0.18.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 971b8ac9c92835..d8d91f2213cea3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2385,4 +2385,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.2 +zwave-js-server-python==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c552f8452c01de..65c9a788f31362 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1222,4 +1222,4 @@ zigpy-znp==0.3.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.17.2 +zwave-js-server-python==0.18.0 diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 7336acd82eb45b..17f4dd38144a9b 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -456,7 +456,6 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClass": 67, "endpoint": 0, "property": "setpoint", - "propertyKey": 1, "propertyKeyName": "Heating", "propertyName": "setpoint", "newValue": 23, diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index bcd9a2edd9f92d..40950362ad76a1 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -203,17 +203,21 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "property": "currentColor", "newValue": 255, "prevValue": 0, + "propertyKey": 2, "propertyKeyName": "Red", }, }, ) green_event = deepcopy(red_event) - green_event.data["args"].update({"newValue": 76, "propertyKeyName": "Green"}) + green_event.data["args"].update( + {"newValue": 76, "propertyKey": 3, "propertyKeyName": "Green"} + ) blue_event = deepcopy(red_event) + blue_event.data["args"]["propertyKey"] = 4 blue_event.data["args"]["propertyKeyName"] = "Blue" warm_white_event = deepcopy(red_event) warm_white_event.data["args"].update( - {"newValue": 0, "propertyKeyName": "Warm White"} + {"newValue": 0, "propertyKey": 0, "propertyKeyName": "Warm White"} ) node.receive_event(warm_white_event) node.receive_event(red_event) @@ -316,23 +320,25 @@ async def test_light(hass, client, bulb_6_multi_color, integration): "property": "currentColor", "newValue": 0, "prevValue": 255, + "propertyKey": 2, "propertyKeyName": "Red", }, }, ) green_event = deepcopy(red_event) green_event.data["args"].update( - {"newValue": 0, "prevValue": 76, "propertyKeyName": "Green"} + {"newValue": 0, "prevValue": 76, "propertyKey": 3, "propertyKeyName": "Green"} ) blue_event = deepcopy(red_event) + blue_event.data["args"]["propertyKey"] = 4 blue_event.data["args"]["propertyKeyName"] = "Blue" warm_white_event = deepcopy(red_event) warm_white_event.data["args"].update( - {"newValue": 20, "propertyKeyName": "Warm White"} + {"newValue": 20, "propertyKey": 0, "propertyKeyName": "Warm White"} ) cold_white_event = deepcopy(red_event) cold_white_event.data["args"].update( - {"newValue": 235, "propertyKeyName": "Cold White"} + {"newValue": 235, "propertyKey": 1, "propertyKeyName": "Cold White"} ) node.receive_event(red_event) node.receive_event(green_event) From 4aa4f7e28525d79d8b12d79ff74ba6a22bd8d5a8 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 20 Feb 2021 06:49:39 -0800 Subject: [PATCH 0625/1818] Rollback stream StreamOutput refactoring in PR#46610 (#46684) * Rollback PR#46610 * Update stream tests post-merge --- homeassistant/components/camera/__init__.py | 14 +- homeassistant/components/stream/__init__.py | 155 ++++++++++---------- homeassistant/components/stream/const.py | 8 +- homeassistant/components/stream/core.py | 93 +++++++++++- homeassistant/components/stream/hls.py | 92 ++++-------- homeassistant/components/stream/recorder.py | 43 ++++-- homeassistant/components/stream/worker.py | 11 +- tests/components/stream/test_hls.py | 36 ++--- tests/components/stream/test_recorder.py | 76 +++++----- tests/components/stream/test_worker.py | 9 +- 10 files changed, 296 insertions(+), 241 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 4c5d89030b4c68..99b5cebc2a390d 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -24,11 +24,7 @@ SERVICE_PLAY_MEDIA, ) from homeassistant.components.stream import Stream, create_stream -from homeassistant.components.stream.const import ( - FORMAT_CONTENT_TYPE, - HLS_OUTPUT, - OUTPUT_FORMATS, -) +from homeassistant.components.stream.const import FORMAT_CONTENT_TYPE, OUTPUT_FORMATS from homeassistant.const import ( ATTR_ENTITY_ID, CONF_FILENAME, @@ -259,7 +255,7 @@ async def preload_stream(_): if not stream: continue stream.keepalive = True - stream.hls_output() + stream.add_provider("hls") stream.start() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream) @@ -707,8 +703,6 @@ async def async_handle_play_stream_service(camera, service_call): async def _async_stream_endpoint_url(hass, camera, fmt): - if fmt != HLS_OUTPUT: - raise ValueError("Only format {HLS_OUTPUT} is supported") stream = await camera.create_stream() if not stream: raise HomeAssistantError( @@ -719,9 +713,9 @@ async def _async_stream_endpoint_url(hass, camera, fmt): camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) stream.keepalive = camera_prefs.preload_stream - stream.hls_output() + stream.add_provider(fmt) stream.start() - return stream.endpoint_url() + return stream.endpoint_url(fmt) async def async_handle_record_service(camera, call): diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 6c3f0104ad0ead..0027152dbd6d04 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -7,25 +7,25 @@ - Home Assistant URLs for viewing a stream - Access tokens for URLs for viewing a stream -A Stream consists of a background worker and multiple output streams (e.g. hls -and recorder). The worker has a callback to retrieve the current active output -streams where it writes the decoded output packets. The HLS stream has an -inactivity idle timeout that expires the access token. When all output streams -are inactive, the background worker is shut down. Alternatively, a Stream -can be configured with keepalive to always keep workers active. +A Stream consists of a background worker, and one or more output formats each +with their own idle timeout managed by the stream component. When an output +format is no longer in use, the stream component will expire it. When there +are no active output formats, the background worker is shut down and access +tokens are expired. Alternatively, a Stream can be configured with keepalive +to always keep workers active. """ import logging import secrets import threading import time -from typing import List +from types import MappingProxyType from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from .const import ( - ATTR_HLS_ENDPOINT, + ATTR_ENDPOINTS, ATTR_STREAMS, DOMAIN, MAX_SEGMENTS, @@ -33,8 +33,8 @@ STREAM_RESTART_INCREMENT, STREAM_RESTART_RESET_TIME, ) -from .core import IdleTimer, StreamOutput -from .hls import HlsStreamOutput, async_setup_hls +from .core import PROVIDERS, IdleTimer +from .hls import async_setup_hls _LOGGER = logging.getLogger(__name__) @@ -75,10 +75,12 @@ async def async_setup(hass, config): from .recorder import async_setup_recorder hass.data[DOMAIN] = {} + hass.data[DOMAIN][ATTR_ENDPOINTS] = {} hass.data[DOMAIN][ATTR_STREAMS] = [] # Setup HLS - hass.data[DOMAIN][ATTR_HLS_ENDPOINT] = async_setup_hls(hass) + hls_endpoint = async_setup_hls(hass) + hass.data[DOMAIN][ATTR_ENDPOINTS]["hls"] = hls_endpoint # Setup Recorder async_setup_recorder(hass) @@ -87,6 +89,7 @@ async def async_setup(hass, config): def shutdown(event): """Stop all stream workers.""" for stream in hass.data[DOMAIN][ATTR_STREAMS]: + stream.keepalive = False stream.stop() _LOGGER.info("Stopped stream workers") @@ -107,54 +110,58 @@ def __init__(self, hass, source, options=None): self.access_token = None self._thread = None self._thread_quit = threading.Event() - self._hls = None - self._hls_timer = None - self._recorder = None + self._outputs = {} self._fast_restart_once = False if self.options is None: self.options = {} - def endpoint_url(self) -> str: - """Start the stream and returns a url for the hls endpoint.""" - if not self._hls: - raise ValueError("Stream is not configured for hls") + def endpoint_url(self, fmt): + """Start the stream and returns a url for the output format.""" + if fmt not in self._outputs: + raise ValueError(f"Stream is not configured for format '{fmt}'") if not self.access_token: self.access_token = secrets.token_hex() - return self.hass.data[DOMAIN][ATTR_HLS_ENDPOINT].format(self.access_token) + return self.hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(self.access_token) + + def outputs(self): + """Return a copy of the stream outputs.""" + # A copy is returned so the caller can iterate through the outputs + # without concern about self._outputs being modified from another thread. + return MappingProxyType(self._outputs.copy()) + + def add_provider(self, fmt, timeout=OUTPUT_IDLE_TIMEOUT): + """Add provider output stream.""" + if not self._outputs.get(fmt): + + @callback + def idle_callback(): + if not self.keepalive and fmt in self._outputs: + self.remove_provider(self._outputs[fmt]) + self.check_idle() + + provider = PROVIDERS[fmt]( + self.hass, IdleTimer(self.hass, timeout, idle_callback) + ) + self._outputs[fmt] = provider + return self._outputs[fmt] - def outputs(self) -> List[StreamOutput]: - """Return the active stream outputs.""" - return [output for output in [self._hls, self._recorder] if output] + def remove_provider(self, provider): + """Remove provider output stream.""" + if provider.name in self._outputs: + self._outputs[provider.name].cleanup() + del self._outputs[provider.name] - def hls_output(self) -> StreamOutput: - """Return the hls output stream, creating if not already active.""" - if not self._hls: - self._hls = HlsStreamOutput(self.hass) - self._hls_timer = IdleTimer(self.hass, OUTPUT_IDLE_TIMEOUT, self._hls_idle) - self._hls_timer.start() - self._hls_timer.awake() - return self._hls + if not self._outputs: + self.stop() - @callback - def _hls_idle(self): - """Reset access token and cleanup stream due to inactivity.""" - self.access_token = None - if not self.keepalive: - if self._hls: - self._hls.cleanup() - self._hls = None - self._hls_timer = None - self._check_idle() - - def _check_idle(self): - """Check if all outputs are idle and shut down worker.""" - if self.keepalive or self.outputs(): - return - self.stop() + def check_idle(self): + """Reset access token if all providers are idle.""" + if all([p.idle for p in self._outputs.values()]): + self.access_token = None def start(self): - """Start stream decode worker.""" + """Start a stream.""" if self._thread is None or not self._thread.is_alive(): if self._thread is not None: # The thread must have crashed/exited. Join to clean up the @@ -210,21 +217,21 @@ def _run_worker(self): def _worker_finished(self): """Schedule cleanup of all outputs.""" - self.hass.loop.call_soon_threadsafe(self.stop) + + @callback + def remove_outputs(): + for provider in self.outputs().values(): + self.remove_provider(provider) + + self.hass.loop.call_soon_threadsafe(remove_outputs) def stop(self): """Remove outputs and access token.""" + self._outputs = {} self.access_token = None - if self._hls_timer: - self._hls_timer.clear() - self._hls_timer = None - if self._hls: - self._hls.cleanup() - self._hls = None - if self._recorder: - self._recorder.save() - self._recorder = None - self._stop() + + if not self.keepalive: + self._stop() def _stop(self): """Stop worker thread.""" @@ -237,35 +244,25 @@ def _stop(self): async def async_record(self, video_path, duration=30, lookback=5): """Make a .mp4 recording from a provided stream.""" - # Keep import here so that we can import stream integration without installing reqs - # pylint: disable=import-outside-toplevel - from .recorder import RecorderOutput - # Check for file access if not self.hass.config.is_allowed_path(video_path): raise HomeAssistantError(f"Can't write {video_path}, no access to path!") # Add recorder - if self._recorder: + recorder = self.outputs().get("recorder") + if recorder: raise HomeAssistantError( - f"Stream already recording to {self._recorder.video_path}!" + f"Stream already recording to {recorder.video_path}!" ) - self._recorder = RecorderOutput(self.hass) - self._recorder.video_path = video_path + recorder = self.add_provider("recorder", timeout=duration) + recorder.video_path = video_path + self.start() # Take advantage of lookback - if lookback > 0 and self._hls: - num_segments = min(int(lookback // self._hls.target_duration), MAX_SEGMENTS) + hls = self.outputs().get("hls") + if lookback > 0 and hls: + num_segments = min(int(lookback // hls.target_duration), MAX_SEGMENTS) # Wait for latest segment, then add the lookback - await self._hls.recv() - self._recorder.prepend(list(self._hls.get_segment())[-num_segments:]) - - @callback - def save_recording(): - if self._recorder: - self._recorder.save() - self._recorder = None - self._check_idle() - - IdleTimer(self.hass, duration, save_recording).start() + await hls.recv() + recorder.prepend(list(hls.get_segment())[-num_segments:]) diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 55f447a9a69e44..41df806d020c4f 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -1,14 +1,10 @@ """Constants for Stream component.""" DOMAIN = "stream" -ATTR_HLS_ENDPOINT = "hls_endpoint" +ATTR_ENDPOINTS = "endpoints" ATTR_STREAMS = "streams" -HLS_OUTPUT = "hls" -OUTPUT_FORMATS = [HLS_OUTPUT] -OUTPUT_CONTAINER_FORMAT = "mp4" -OUTPUT_VIDEO_CODECS = {"hevc", "h264"} -OUTPUT_AUDIO_CODECS = {"aac", "mp3"} +OUTPUT_FORMATS = ["hls"] FORMAT_CONTENT_TYPE = {"hls": "application/vnd.apple.mpegurl"} diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 7a46de547d7748..eba6a0696981bb 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -1,7 +1,8 @@ """Provides core stream functionality.""" -import abc +import asyncio +from collections import deque import io -from typing import Callable +from typing import Any, Callable, List from aiohttp import web import attr @@ -9,8 +10,11 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.event import async_call_later +from homeassistant.util.decorator import Registry -from .const import ATTR_STREAMS, DOMAIN +from .const import ATTR_STREAMS, DOMAIN, MAX_SEGMENTS + +PROVIDERS = Registry() @attr.s @@ -76,18 +80,86 @@ def fire(self, _now=None): self._callback() -class StreamOutput(abc.ABC): +class StreamOutput: """Represents a stream output.""" - def __init__(self, hass: HomeAssistant): + def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: """Initialize a stream output.""" self._hass = hass + self._idle_timer = idle_timer + self._cursor = None + self._event = asyncio.Event() + self._segments = deque(maxlen=MAX_SEGMENTS) + + @property + def name(self) -> str: + """Return provider name.""" + return None + + @property + def idle(self) -> bool: + """Return True if the output is idle.""" + return self._idle_timer.idle + + @property + def format(self) -> str: + """Return container format.""" + return None + + @property + def audio_codecs(self) -> str: + """Return desired audio codecs.""" + return None + + @property + def video_codecs(self) -> tuple: + """Return desired video codecs.""" + return None @property def container_options(self) -> Callable[[int], dict]: """Return Callable which takes a sequence number and returns container options.""" return None + @property + def segments(self) -> List[int]: + """Return current sequence from segments.""" + return [s.sequence for s in self._segments] + + @property + def target_duration(self) -> int: + """Return the max duration of any given segment in seconds.""" + segment_length = len(self._segments) + if not segment_length: + return 1 + durations = [s.duration for s in self._segments] + return round(max(durations)) or 1 + + def get_segment(self, sequence: int = None) -> Any: + """Retrieve a specific segment, or the whole list.""" + self._idle_timer.awake() + + if not sequence: + return self._segments + + for segment in self._segments: + if segment.sequence == sequence: + return segment + return None + + async def recv(self) -> Segment: + """Wait for and retrieve the latest segment.""" + last_segment = max(self.segments, default=0) + if self._cursor is None or self._cursor <= last_segment: + await self._event.wait() + + if not self._segments: + return None + + segment = self.get_segment()[-1] + self._cursor = segment.sequence + return segment + def put(self, segment: Segment) -> None: """Store output.""" self._hass.loop.call_soon_threadsafe(self._async_put, segment) @@ -95,6 +167,17 @@ def put(self, segment: Segment) -> None: @callback def _async_put(self, segment: Segment) -> None: """Store output from event loop.""" + # Start idle timeout when we start receiving data + self._idle_timer.start() + self._segments.append(segment) + self._event.set() + self._event.clear() + + def cleanup(self): + """Handle cleanup.""" + self._event.set() + self._idle_timer.clear() + self._segments = deque(maxlen=MAX_SEGMENTS) class StreamView(HomeAssistantView): diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 85102d208e72b7..28d6a300ae7362 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -1,15 +1,13 @@ """Provide functionality to stream HLS.""" -import asyncio -from collections import deque import io -from typing import Any, Callable, List +from typing import Callable from aiohttp import web from homeassistant.core import callback -from .const import FORMAT_CONTENT_TYPE, MAX_SEGMENTS, NUM_PLAYLIST_SEGMENTS -from .core import Segment, StreamOutput, StreamView +from .const import FORMAT_CONTENT_TYPE, NUM_PLAYLIST_SEGMENTS +from .core import PROVIDERS, StreamOutput, StreamView from .fmp4utils import get_codec_string, get_init, get_m4s @@ -50,7 +48,8 @@ def render(track): async def handle(self, request, stream, sequence): """Return m3u8 playlist.""" - track = stream.hls_output() + track = stream.add_provider("hls") + stream.start() # Wait for a segment to be ready if not track.segments: if not await track.recv(): @@ -109,7 +108,8 @@ def render(self, track): async def handle(self, request, stream, sequence): """Return m3u8 playlist.""" - track = stream.hls_output() + track = stream.add_provider("hls") + stream.start() # Wait for a segment to be ready if not track.segments: if not await track.recv(): @@ -127,7 +127,7 @@ class HlsInitView(StreamView): async def handle(self, request, stream, sequence): """Return init.mp4.""" - track = stream.hls_output() + track = stream.add_provider("hls") segments = track.get_segment() if not segments: return web.HTTPNotFound() @@ -144,7 +144,7 @@ class HlsSegmentView(StreamView): async def handle(self, request, stream, sequence): """Return fmp4 segment.""" - track = stream.hls_output() + track = stream.add_provider("hls") segment = track.get_segment(int(sequence)) if not segment: return web.HTTPNotFound() @@ -155,15 +155,29 @@ async def handle(self, request, stream, sequence): ) +@PROVIDERS.register("hls") class HlsStreamOutput(StreamOutput): """Represents HLS Output formats.""" - def __init__(self, hass) -> None: - """Initialize HlsStreamOutput.""" - super().__init__(hass) - self._cursor = None - self._event = asyncio.Event() - self._segments = deque(maxlen=MAX_SEGMENTS) + @property + def name(self) -> str: + """Return provider name.""" + return "hls" + + @property + def format(self) -> str: + """Return container format.""" + return "mp4" + + @property + def audio_codecs(self) -> str: + """Return desired audio codecs.""" + return {"aac", "mp3"} + + @property + def video_codecs(self) -> tuple: + """Return desired video codecs.""" + return {"hevc", "h264"} @property def container_options(self) -> Callable[[int], dict]: @@ -174,51 +188,3 @@ def container_options(self) -> Callable[[int], dict]: "avoid_negative_ts": "make_non_negative", "fragment_index": str(sequence), } - - @property - def segments(self) -> List[int]: - """Return current sequence from segments.""" - return [s.sequence for s in self._segments] - - @property - def target_duration(self) -> int: - """Return the max duration of any given segment in seconds.""" - segment_length = len(self._segments) - if not segment_length: - return 1 - durations = [s.duration for s in self._segments] - return round(max(durations)) or 1 - - def get_segment(self, sequence: int = None) -> Any: - """Retrieve a specific segment, or the whole list.""" - if not sequence: - return self._segments - - for segment in self._segments: - if segment.sequence == sequence: - return segment - return None - - async def recv(self) -> Segment: - """Wait for and retrieve the latest segment.""" - last_segment = max(self.segments, default=0) - if self._cursor is None or self._cursor <= last_segment: - await self._event.wait() - - if not self._segments: - return None - - segment = self.get_segment()[-1] - self._cursor = segment.sequence - return segment - - def _async_put(self, segment: Segment) -> None: - """Store output from event loop.""" - self._segments.append(segment) - self._event.set() - self._event.clear() - - def cleanup(self): - """Handle cleanup.""" - self._event.set() - self._segments = deque(maxlen=MAX_SEGMENTS) diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 9653123377133b..0b77d0ba6302fa 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -6,10 +6,9 @@ import av -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback -from .const import OUTPUT_CONTAINER_FORMAT -from .core import Segment, StreamOutput +from .core import PROVIDERS, IdleTimer, Segment, StreamOutput _LOGGER = logging.getLogger(__name__) @@ -19,7 +18,7 @@ def async_setup_recorder(hass): """Only here so Provider Registry works.""" -def recorder_save_worker(file_out: str, segments: List[Segment], container_format): +def recorder_save_worker(file_out: str, segments: List[Segment], container_format: str): """Handle saving stream.""" if not os.path.exists(os.path.dirname(file_out)): os.makedirs(os.path.dirname(file_out), exist_ok=True) @@ -76,31 +75,51 @@ def recorder_save_worker(file_out: str, segments: List[Segment], container_forma output.close() +@PROVIDERS.register("recorder") class RecorderOutput(StreamOutput): """Represents HLS Output formats.""" - def __init__(self, hass) -> None: + def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: """Initialize recorder output.""" - super().__init__(hass) + super().__init__(hass, idle_timer) self.video_path = None self._segments = [] - def _async_put(self, segment: Segment) -> None: - """Store output.""" - self._segments.append(segment) + @property + def name(self) -> str: + """Return provider name.""" + return "recorder" + + @property + def format(self) -> str: + """Return container format.""" + return "mp4" + + @property + def audio_codecs(self) -> str: + """Return desired audio codec.""" + return {"aac", "mp3"} + + @property + def video_codecs(self) -> tuple: + """Return desired video codecs.""" + return {"hevc", "h264"} def prepend(self, segments: List[Segment]) -> None: """Prepend segments to existing list.""" - segments = [s for s in segments if s.sequence not in self._segments] + own_segments = self.segments + segments = [s for s in segments if s.sequence not in own_segments] self._segments = segments + self._segments - def save(self): + def cleanup(self): """Write recording and clean up.""" _LOGGER.debug("Starting recorder worker thread") thread = threading.Thread( name="recorder_save_worker", target=recorder_save_worker, - args=(self.video_path, self._segments, OUTPUT_CONTAINER_FORMAT), + args=(self.video_path, self._segments, self.format), ) thread.start() + + super().cleanup() self._segments = [] diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 2592a74584e45b..61d4f5db17ab28 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -9,9 +9,6 @@ MAX_MISSING_DTS, MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, - OUTPUT_AUDIO_CODECS, - OUTPUT_CONTAINER_FORMAT, - OUTPUT_VIDEO_CODECS, PACKETS_TO_WAIT_FOR_AUDIO, STREAM_TIMEOUT, ) @@ -32,7 +29,7 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): output = av.open( segment, mode="w", - format=OUTPUT_CONTAINER_FORMAT, + format=stream_output.format, container_options={ "video_track_timescale": str(int(1 / video_stream.time_base)), **container_options, @@ -41,7 +38,7 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): vstream = output.add_stream(template=video_stream) # Check if audio is requested astream = None - if audio_stream and audio_stream.name in OUTPUT_AUDIO_CODECS: + if audio_stream and audio_stream.name in stream_output.audio_codecs: astream = output.add_stream(template=audio_stream) return StreamBuffer(segment, output, vstream, astream) @@ -74,8 +71,8 @@ def reset(self, video_pts): # Fetch the latest StreamOutputs, which may have changed since the # worker started. self._outputs = [] - for stream_output in self._outputs_callback(): - if self._video_stream.name not in OUTPUT_VIDEO_CODECS: + for stream_output in self._outputs_callback().values(): + if self._video_stream.name not in stream_output.video_codecs: continue buffer = create_stream_buffer( stream_output, self._video_stream, self._audio_stream, self._sequence diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index ffe32d13c61153..c11576d257047b 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -45,7 +45,7 @@ def hls_stream(hass, hass_client): async def create_client_for_stream(stream): http_client = await hass_client() - parsed_url = urlparse(stream.endpoint_url()) + parsed_url = urlparse(stream.endpoint_url("hls")) return HlsClient(http_client, parsed_url) return create_client_for_stream @@ -91,7 +91,7 @@ async def test_hls_stream(hass, hls_stream, stream_worker_sync): stream = create_stream(hass, source) # Request stream - stream.hls_output() + stream.add_provider("hls") stream.start() hls_client = await hls_stream(stream) @@ -132,9 +132,9 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): stream = create_stream(hass, source) # Request stream - stream.hls_output() + stream.add_provider("hls") stream.start() - url = stream.endpoint_url() + url = stream.endpoint_url("hls") http_client = await hass_client() @@ -174,16 +174,8 @@ async def test_stream_timeout_after_stop(hass, hass_client, stream_worker_sync): stream = create_stream(hass, source) # Request stream - stream.hls_output() + stream.add_provider("hls") stream.start() - url = stream.endpoint_url() - - http_client = await hass_client() - - # Fetch playlist - parsed_url = urlparse(url) - playlist_response = await http_client.get(parsed_url.path) - assert playlist_response.status == 200 stream_worker_sync.resume() stream.stop() @@ -204,10 +196,12 @@ async def test_stream_ended(hass, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() stream = create_stream(hass, source) + track = stream.add_provider("hls") # Request stream - track = stream.hls_output() + stream.add_provider("hls") stream.start() + stream.endpoint_url("hls") # Run it dead while True: @@ -233,7 +227,7 @@ async def test_stream_keepalive(hass): # Setup demo HLS track source = "test_stream_keepalive_source" stream = create_stream(hass, source) - track = stream.hls_output() + track = stream.add_provider("hls") track.num_segments = 2 stream.start() @@ -264,12 +258,12 @@ def time_side_effect(): stream.stop() -async def test_hls_playlist_view_no_output(hass, hls_stream): +async def test_hls_playlist_view_no_output(hass, hass_client, hls_stream): """Test rendering the hls playlist with no output segments.""" await async_setup_component(hass, "stream", {"stream": {}}) stream = create_stream(hass, STREAM_SOURCE) - stream.hls_output() + stream.add_provider("hls") hls_client = await hls_stream(stream) @@ -284,7 +278,7 @@ async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync): stream = create_stream(hass, STREAM_SOURCE) stream_worker_sync.pause() - hls = stream.hls_output() + hls = stream.add_provider("hls") hls.put(Segment(1, SEQUENCE_BYTES, DURATION)) await hass.async_block_till_done() @@ -313,7 +307,7 @@ async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): stream = create_stream(hass, STREAM_SOURCE) stream_worker_sync.pause() - hls = stream.hls_output() + hls = stream.add_provider("hls") hls_client = await hls_stream(stream) @@ -358,7 +352,7 @@ async def test_hls_playlist_view_discontinuity(hass, hls_stream, stream_worker_s stream = create_stream(hass, STREAM_SOURCE) stream_worker_sync.pause() - hls = stream.hls_output() + hls = stream.add_provider("hls") hls.put(Segment(1, SEQUENCE_BYTES, DURATION, stream_id=0)) hls.put(Segment(2, SEQUENCE_BYTES, DURATION, stream_id=0)) @@ -388,7 +382,7 @@ async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sy stream = create_stream(hass, STREAM_SOURCE) stream_worker_sync.pause() - hls = stream.hls_output() + hls = stream.add_provider("hls") hls_client = await hls_stream(stream) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index e8ff540ba415b0..9d418c360b1f8f 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -1,12 +1,10 @@ """The tests for hls streams.""" -import asyncio from datetime import timedelta import logging import os import threading from unittest.mock import patch -import async_timeout import av import pytest @@ -34,30 +32,23 @@ class SaveRecordWorkerSync: def __init__(self): """Initialize SaveRecordWorkerSync.""" self.reset() - self._segments = None - def recorder_save_worker(self, file_out, segments, container_format): + def recorder_save_worker(self, *args, **kwargs): """Mock method for patch.""" logging.debug("recorder_save_worker thread started") - self._segments = segments assert self._save_thread is None self._save_thread = threading.current_thread() self._save_event.set() - async def get_segments(self): - """Verify save worker thread was invoked and return saved segments.""" - with async_timeout.timeout(TEST_TIMEOUT): - assert await self._save_event.wait() - return self._segments - def join(self): - """Block until the record worker thread exist to ensure cleanup.""" + """Verify save worker was invoked and block on shutdown.""" + assert self._save_event.wait(timeout=TEST_TIMEOUT) self._save_thread.join() def reset(self): """Reset callback state for reuse in tests.""" self._save_thread = None - self._save_event = asyncio.Event() + self._save_event = threading.Event() @pytest.fixture() @@ -72,7 +63,7 @@ def record_worker_sync(hass): yield sync -async def test_record_stream(hass, hass_client, record_worker_sync): +async def test_record_stream(hass, hass_client, stream_worker_sync, record_worker_sync): """ Test record stream. @@ -82,14 +73,28 @@ async def test_record_stream(hass, hass_client, record_worker_sync): """ await async_setup_component(hass, "stream", {"stream": {}}) + stream_worker_sync.pause() + # Setup demo track source = generate_h264_video() stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") - segments = await record_worker_sync.get_segments() - assert len(segments) > 1 + recorder = stream.add_provider("recorder") + while True: + segment = await recorder.recv() + if not segment: + break + segments = segment.sequence + if segments > 1: + stream_worker_sync.resume() + + stream.stop() + assert segments > 1 + + # Verify that the save worker was invoked, then block until its + # thread completes and is shutdown completely to avoid thread leaks. record_worker_sync.join() @@ -102,24 +107,19 @@ async def test_record_lookback( source = generate_h264_video() stream = create_stream(hass, source) - # Don't let the stream finish (and clean itself up) until the test has had - # a chance to perform lookback - stream_worker_sync.pause() - # Start an HLS feed to enable lookback - stream.hls_output() + stream.add_provider("hls") + stream.start() with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path", lookback=4) # This test does not need recorder cleanup since it is not fully exercised - stream_worker_sync.resume() + stream.stop() -async def test_recorder_timeout( - hass, hass_client, stream_worker_sync, record_worker_sync -): +async def test_recorder_timeout(hass, hass_client, stream_worker_sync): """ Test recorder timeout. @@ -137,8 +137,9 @@ async def test_recorder_timeout( stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") + recorder = stream.add_provider("recorder") - assert not mock_timeout.called + await recorder.recv() # Wait a minute future = dt_util.utcnow() + timedelta(minutes=1) @@ -148,10 +149,6 @@ async def test_recorder_timeout( assert mock_timeout.called stream_worker_sync.resume() - # Verify worker is invoked, and do clean shutdown of worker thread - await record_worker_sync.get_segments() - record_worker_sync.join() - stream.stop() await hass.async_block_till_done() await hass.async_block_till_done() @@ -183,7 +180,9 @@ async def test_recorder_save(tmpdir): assert os.path.exists(filename) -async def test_record_stream_audio(hass, hass_client, record_worker_sync): +async def test_record_stream_audio( + hass, hass_client, stream_worker_sync, record_worker_sync +): """ Test treatment of different audio inputs. @@ -199,6 +198,7 @@ async def test_record_stream_audio(hass, hass_client, record_worker_sync): (None, 0), # no audio stream ): record_worker_sync.reset() + stream_worker_sync.pause() # Setup demo track source = generate_h264_video( @@ -207,14 +207,22 @@ async def test_record_stream_audio(hass, hass_client, record_worker_sync): stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") + recorder = stream.add_provider("recorder") - segments = await record_worker_sync.get_segments() - last_segment = segments[-1] + while True: + segment = await recorder.recv() + if not segment: + break + last_segment = segment + stream_worker_sync.resume() result = av.open(last_segment.segment, "r", format="mp4") assert len(result.streams.audio) == expected_audio_streams result.close() - stream.stop() + await hass.async_block_till_done() + + # Verify that the save worker was invoked, then block until its + # thread completes and is shutdown completely to avoid thread leaks. record_worker_sync.join() diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index f7952b7db4479e..2c202a290ce470 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -31,6 +31,7 @@ STREAM_SOURCE = "some-stream-source" # Formats here are arbitrary, not exercised by tests +STREAM_OUTPUT_FORMAT = "hls" AUDIO_STREAM_FORMAT = "mp3" VIDEO_STREAM_FORMAT = "h264" VIDEO_FRAME_RATE = 12 @@ -187,7 +188,7 @@ def open(self, stream_source, *args, **kwargs): async def async_decode_stream(hass, packets, py_av=None): """Start a stream worker that decodes incoming stream packets into output segments.""" stream = Stream(hass, STREAM_SOURCE) - stream.hls_output() + stream.add_provider(STREAM_OUTPUT_FORMAT) if not py_av: py_av = MockPyAv() @@ -207,7 +208,7 @@ async def async_decode_stream(hass, packets, py_av=None): async def test_stream_open_fails(hass): """Test failure on stream open.""" stream = Stream(hass, STREAM_SOURCE) - stream.hls_output() + stream.add_provider(STREAM_OUTPUT_FORMAT) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") segment_buffer = SegmentBuffer(stream.outputs) @@ -484,7 +485,7 @@ async def test_stream_stopped_while_decoding(hass): worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE) - stream.hls_output() + stream.add_provider(STREAM_OUTPUT_FORMAT) py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) @@ -511,7 +512,7 @@ async def test_update_stream_source(hass): worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE) - stream.hls_output() + stream.add_provider(STREAM_OUTPUT_FORMAT) # Note that keepalive is not set here. The stream is "restarted" even though # it is not stopping due to failure. From b5c7493b60efce7f1bd275a474c7ffa1fc4dbd47 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 20 Feb 2021 17:26:21 +0100 Subject: [PATCH 0626/1818] Upgrade TwitterAPI to 2.6.6 (#46812) --- homeassistant/components/twitter/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json index cd3a12255a2161..873c9e12ab116f 100644 --- a/homeassistant/components/twitter/manifest.json +++ b/homeassistant/components/twitter/manifest.json @@ -2,6 +2,6 @@ "domain": "twitter", "name": "Twitter", "documentation": "https://www.home-assistant.io/integrations/twitter", - "requirements": ["TwitterAPI==2.6.5"], + "requirements": ["TwitterAPI==2.6.6"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index d8d91f2213cea3..6dbcf46ea511f6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -81,7 +81,7 @@ RtmAPI==0.7.2 TravisPy==0.3.5 # homeassistant.components.twitter -TwitterAPI==2.6.5 +TwitterAPI==2.6.6 # homeassistant.components.tof # VL53L1X2==0.1.5 From 65e8835f2825a1583c92d8d750870def2264941d Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 20 Feb 2021 10:59:22 -0600 Subject: [PATCH 0627/1818] Update rokuecp to 0.8.0 (#46799) * update rokuecp to 0.8.0 * Update requirements_test_all.txt * Update requirements_all.txt --- 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 f1509edb6fbbc5..10cef13cda02a1 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.6.0"], + "requirements": ["rokuecp==0.8.0"], "homekit": { "models": [ "3810X", diff --git a/requirements_all.txt b/requirements_all.txt index 6dbcf46ea511f6..3440eb9557e9b8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1962,7 +1962,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.6.0 +rokuecp==0.8.0 # homeassistant.components.roomba roombapy==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 65c9a788f31362..f843874b5bc0c8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1005,7 +1005,7 @@ rflink==0.0.58 ring_doorbell==0.6.2 # homeassistant.components.roku -rokuecp==0.6.0 +rokuecp==0.8.0 # homeassistant.components.roomba roombapy==1.6.2 From adf480025d0ee380e5a514d90dfc16fec7467920 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Feb 2021 08:03:40 -1000 Subject: [PATCH 0628/1818] Add support for bond up and down lights (#46233) --- homeassistant/components/bond/entity.py | 3 + homeassistant/components/bond/light.py | 94 ++++++++++-- homeassistant/components/bond/utils.py | 36 +++-- tests/components/bond/test_light.py | 186 +++++++++++++++++++++++- 4 files changed, 294 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index f6165eb7890543..5b2e27b94cc5b0 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -52,6 +52,9 @@ def unique_id(self) -> Optional[str]: @property def name(self) -> Optional[str]: """Get entity name.""" + if self._sub_device: + sub_device_name = self._sub_device.replace("_", " ").title() + return f"{self._device.name} {sub_device_name}" return self._device.name @property diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index 8d0dfe85246397..194a009a857896 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -34,7 +34,21 @@ async def async_setup_entry( fan_lights: List[Entity] = [ BondLight(hub, device, bpup_subs) for device in hub.devices - if DeviceType.is_fan(device.type) and device.supports_light() + if DeviceType.is_fan(device.type) + and device.supports_light() + and not (device.supports_up_light() and device.supports_down_light()) + ] + + fan_up_lights: List[Entity] = [ + BondUpLight(hub, device, bpup_subs, "up_light") + for device in hub.devices + if DeviceType.is_fan(device.type) and device.supports_up_light() + ] + + fan_down_lights: List[Entity] = [ + BondDownLight(hub, device, bpup_subs, "down_light") + for device in hub.devices + if DeviceType.is_fan(device.type) and device.supports_down_light() ] fireplaces: List[Entity] = [ @@ -55,10 +69,13 @@ async def async_setup_entry( if DeviceType.is_light(device.type) ] - async_add_entities(fan_lights + fireplaces + fp_lights + lights, True) + async_add_entities( + fan_lights + fan_up_lights + fan_down_lights + fireplaces + fp_lights + lights, + True, + ) -class BondLight(BondEntity, LightEntity): +class BondBaseLight(BondEntity, LightEntity): """Representation of a Bond light.""" def __init__( @@ -68,11 +85,35 @@ def __init__( bpup_subs: BPUPSubscriptions, sub_device: Optional[str] = None, ): - """Create HA entity representing Bond fan.""" + """Create HA entity representing Bond light.""" super().__init__(hub, device, bpup_subs, sub_device) - self._brightness: Optional[int] = None self._light: Optional[int] = None + @property + def is_on(self) -> bool: + """Return if light is currently on.""" + return self._light == 1 + + @property + def supported_features(self) -> Optional[int]: + """Flag supported features.""" + return 0 + + +class BondLight(BondBaseLight, BondEntity, LightEntity): + """Representation of a Bond light.""" + + def __init__( + self, + hub: BondHub, + device: BondDevice, + bpup_subs: BPUPSubscriptions, + sub_device: Optional[str] = None, + ): + """Create HA entity representing Bond light.""" + super().__init__(hub, device, bpup_subs, sub_device) + self._brightness: Optional[int] = None + def _apply_state(self, state: dict): self._light = state.get("light") self._brightness = state.get("brightness") @@ -84,11 +125,6 @@ def supported_features(self) -> Optional[int]: return SUPPORT_BRIGHTNESS return 0 - @property - def is_on(self) -> bool: - """Return if light is currently on.""" - return self._light == 1 - @property def brightness(self) -> int: """Return the brightness of this light between 1..255.""" @@ -113,6 +149,44 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self._hub.bond.action(self._device.device_id, Action.turn_light_off()) +class BondDownLight(BondBaseLight, BondEntity, LightEntity): + """Representation of a Bond light.""" + + def _apply_state(self, state: dict): + self._light = state.get("down_light") and state.get("light") + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + await self._hub.bond.action( + self._device.device_id, Action(Action.TURN_DOWN_LIGHT_ON) + ) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the light.""" + await self._hub.bond.action( + self._device.device_id, Action(Action.TURN_DOWN_LIGHT_OFF) + ) + + +class BondUpLight(BondBaseLight, BondEntity, LightEntity): + """Representation of a Bond light.""" + + def _apply_state(self, state: dict): + self._light = state.get("up_light") and state.get("light") + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + await self._hub.bond.action( + self._device.device_id, Action(Action.TURN_UP_LIGHT_ON) + ) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the light.""" + await self._hub.bond.action( + self._device.device_id, Action(Action.TURN_UP_LIGHT_OFF) + ) + + class BondFireplace(BondEntity, LightEntity): """Representation of a Bond-controlled fireplace.""" diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 6d3aacf5e42d9d..55ef81778f02a8 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,7 +1,7 @@ """Reusable utilities for the Bond component.""" import asyncio import logging -from typing import List, Optional +from typing import List, Optional, Set from aiohttp import ClientResponseError from bond_api import Action, Bond @@ -58,31 +58,39 @@ def trust_state(self) -> bool: """Check if Trust State is turned on.""" return self.props.get("trust_state", False) + def _has_any_action(self, actions: Set[str]): + """Check to see if the device supports any of the actions.""" + supported_actions: List[str] = self._attrs["actions"] + for action in supported_actions: + if action in actions: + return True + return False + def supports_speed(self) -> bool: """Return True if this device supports any of the speed related commands.""" - actions: List[str] = self._attrs["actions"] - return bool([action for action in actions if action in [Action.SET_SPEED]]) + return self._has_any_action({Action.SET_SPEED}) def supports_direction(self) -> bool: """Return True if this device supports any of the direction related commands.""" - actions: List[str] = self._attrs["actions"] - return bool([action for action in actions if action in [Action.SET_DIRECTION]]) + return self._has_any_action({Action.SET_DIRECTION}) def supports_light(self) -> bool: """Return True if this device supports any of the light related commands.""" - actions: List[str] = self._attrs["actions"] - return bool( - [ - action - for action in actions - if action in [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF] - ] + return self._has_any_action({Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF}) + + def supports_up_light(self) -> bool: + """Return true if the device has an up light.""" + return self._has_any_action({Action.TURN_UP_LIGHT_ON, Action.TURN_UP_LIGHT_OFF}) + + def supports_down_light(self) -> bool: + """Return true if the device has a down light.""" + return self._has_any_action( + {Action.TURN_DOWN_LIGHT_ON, Action.TURN_DOWN_LIGHT_OFF} ) def supports_set_brightness(self) -> bool: """Return True if this device supports setting a light brightness.""" - actions: List[str] = self._attrs["actions"] - return bool([action for action in actions if action in [Action.SET_BRIGHTNESS]]) + return self._has_any_action({Action.SET_BRIGHTNESS}) class BondHub: diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index e4cd4e4e3e90f6..59d051fbe8671c 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -56,6 +56,24 @@ def dimmable_ceiling_fan(name: str): } +def down_light_ceiling_fan(name: str): + """Create a ceiling fan (that has built-in down light) with given name.""" + return { + "name": name, + "type": DeviceType.CEILING_FAN, + "actions": [Action.TURN_DOWN_LIGHT_ON, Action.TURN_DOWN_LIGHT_OFF], + } + + +def up_light_ceiling_fan(name: str): + """Create a ceiling fan (that has built-in down light) with given name.""" + return { + "name": name, + "type": DeviceType.CEILING_FAN, + "actions": [Action.TURN_UP_LIGHT_ON, Action.TURN_UP_LIGHT_OFF], + } + + def fireplace(name: str): """Create a fireplace with given name.""" return { @@ -94,6 +112,36 @@ async def test_fan_entity_registry(hass: core.HomeAssistant): assert entity.unique_id == "test-hub-id_test-device-id" +async def test_fan_up_light_entity_registry(hass: core.HomeAssistant): + """Tests that fan with up light devices are registered in the entity registry.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + up_light_ceiling_fan("fan-name"), + bond_version={"bondid": "test-hub-id"}, + bond_device_id="test-device-id", + ) + + registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + entity = registry.entities["light.fan_name_up_light"] + assert entity.unique_id == "test-hub-id_test-device-id_up_light" + + +async def test_fan_down_light_entity_registry(hass: core.HomeAssistant): + """Tests that fan with down light devices are registered in the entity registry.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + down_light_ceiling_fan("fan-name"), + bond_version={"bondid": "test-hub-id"}, + bond_device_id="test-device-id", + ) + + registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + entity = registry.entities["light.fan_name_down_light"] + assert entity.unique_id == "test-hub-id_test-device-id_down_light" + + async def test_fireplace_entity_registry(hass: core.HomeAssistant): """Tests that flame fireplace devices are registered in the entity registry.""" await setup_platform( @@ -122,7 +170,7 @@ async def test_fireplace_with_light_entity_registry(hass: core.HomeAssistant): registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() entity_flame = registry.entities["light.fireplace_name"] assert entity_flame.unique_id == "test-hub-id_test-device-id" - entity_light = registry.entities["light.fireplace_name_2"] + entity_light = registry.entities["light.fireplace_name_light"] assert entity_light.unique_id == "test-hub-id_test-device-id_light" @@ -269,6 +317,98 @@ async def test_turn_on_light_with_brightness(hass: core.HomeAssistant): ) +async def test_turn_on_up_light(hass: core.HomeAssistant): + """Tests that turn on command, on an up light, delegates to API.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + up_light_ceiling_fan("name-1"), + bond_device_id="test-device-id", + ) + + with patch_bond_action() as mock_turn_on, patch_bond_device_state(): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.name_1_up_light"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_turn_on.assert_called_once_with( + "test-device-id", Action(Action.TURN_UP_LIGHT_ON) + ) + + +async def test_turn_off_up_light(hass: core.HomeAssistant): + """Tests that turn off command, on an up light, delegates to API.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + up_light_ceiling_fan("name-1"), + bond_device_id="test-device-id", + ) + + with patch_bond_action() as mock_turn_off, patch_bond_device_state(): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.name_1_up_light"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_turn_off.assert_called_once_with( + "test-device-id", Action(Action.TURN_UP_LIGHT_OFF) + ) + + +async def test_turn_on_down_light(hass: core.HomeAssistant): + """Tests that turn on command, on a down light, delegates to API.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + down_light_ceiling_fan("name-1"), + bond_device_id="test-device-id", + ) + + with patch_bond_action() as mock_turn_on, patch_bond_device_state(): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.name_1_down_light"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_turn_on.assert_called_once_with( + "test-device-id", Action(Action.TURN_DOWN_LIGHT_ON) + ) + + +async def test_turn_off_down_light(hass: core.HomeAssistant): + """Tests that turn off command, on a down light, delegates to API.""" + await setup_platform( + hass, + LIGHT_DOMAIN, + down_light_ceiling_fan("name-1"), + bond_device_id="test-device-id", + ) + + with patch_bond_action() as mock_turn_off, patch_bond_device_state(): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.name_1_down_light"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_turn_off.assert_called_once_with( + "test-device-id", Action(Action.TURN_DOWN_LIGHT_OFF) + ) + + async def test_update_reports_light_is_on(hass: core.HomeAssistant): """Tests that update command sets correct state when Bond API reports the light is on.""" await setup_platform(hass, LIGHT_DOMAIN, ceiling_fan("name-1")) @@ -291,6 +431,50 @@ async def test_update_reports_light_is_off(hass: core.HomeAssistant): assert hass.states.get("light.name_1").state == "off" +async def test_update_reports_up_light_is_on(hass: core.HomeAssistant): + """Tests that update command sets correct state when Bond API reports the up light is on.""" + await setup_platform(hass, LIGHT_DOMAIN, up_light_ceiling_fan("name-1")) + + with patch_bond_device_state(return_value={"up_light": 1, "light": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert hass.states.get("light.name_1_up_light").state == "on" + + +async def test_update_reports_up_light_is_off(hass: core.HomeAssistant): + """Tests that update command sets correct state when Bond API reports the up light is off.""" + await setup_platform(hass, LIGHT_DOMAIN, up_light_ceiling_fan("name-1")) + + with patch_bond_device_state(return_value={"up_light": 0, "light": 0}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert hass.states.get("light.name_1_up_light").state == "off" + + +async def test_update_reports_down_light_is_on(hass: core.HomeAssistant): + """Tests that update command sets correct state when Bond API reports the down light is on.""" + await setup_platform(hass, LIGHT_DOMAIN, down_light_ceiling_fan("name-1")) + + with patch_bond_device_state(return_value={"down_light": 1, "light": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert hass.states.get("light.name_1_down_light").state == "on" + + +async def test_update_reports_down_light_is_off(hass: core.HomeAssistant): + """Tests that update command sets correct state when Bond API reports the down light is off.""" + await setup_platform(hass, LIGHT_DOMAIN, down_light_ceiling_fan("name-1")) + + with patch_bond_device_state(return_value={"down_light": 0, "light": 0}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) + await hass.async_block_till_done() + + assert hass.states.get("light.name_1_down_light").state == "off" + + async def test_turn_on_fireplace_with_brightness(hass: core.HomeAssistant): """Tests that turn on command delegates to set flame API.""" await setup_platform( From 39da0dc409347d6e84cec75b7b30d3c9be4b68b1 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sun, 21 Feb 2021 02:11:50 +0800 Subject: [PATCH 0629/1818] Add rtsp transport options to generic camera (#46623) * Add rtsp transport options to generic camera --- homeassistant/components/generic/camera.py | 12 ++++++++++-- tests/components/generic/test_camera.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 28db66b4f3eae9..56b490e165a058 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -34,6 +34,9 @@ CONF_STILL_IMAGE_URL = "still_image_url" CONF_STREAM_SOURCE = "stream_source" CONF_FRAMERATE = "framerate" +CONF_RTSP_TRANSPORT = "rtsp_transport" +FFMPEG_OPTION_MAP = {CONF_RTSP_TRANSPORT: "rtsp_transport"} +ALLOWED_RTSP_TRANSPORT_PROTOCOLS = {"tcp", "udp", "udp_multicast", "http"} DEFAULT_NAME = "Generic Camera" GET_IMAGE_TIMEOUT = 10 @@ -54,6 +57,7 @@ cv.small_float, cv.positive_int ), vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + vol.Optional(CONF_RTSP_TRANSPORT): vol.In(ALLOWED_RTSP_TRANSPORT_PROTOCOLS), } ) @@ -85,15 +89,19 @@ def __init__(self, hass, device_info): 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] + if device_info.get(CONF_RTSP_TRANSPORT): + self.stream_options[FFMPEG_OPTION_MAP[CONF_RTSP_TRANSPORT]] = device_info[ + CONF_RTSP_TRANSPORT + ] username = device_info.get(CONF_USERNAME) password = device_info.get(CONF_PASSWORD) if username and password: if self._authentication == HTTP_DIGEST_AUTHENTICATION: - self._auth = httpx.DigestAuth(username, password) + self._auth = httpx.DigestAuth(username=username, password=password) else: - self._auth = httpx.BasicAuth(username, password=password) + self._auth = httpx.BasicAuth(username=username, password=password) else: self._auth = None diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 7f5b3bb3b53912..3e2f07b446b684 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -250,6 +250,28 @@ async def test_stream_source_error(aioclient_mock, hass, hass_client, hass_ws_cl } +async def test_setup_alternative_options(hass, hass_ws_client): + """Test that the stream source is setup with different config options.""" + assert await async_setup_component( + hass, + "camera", + { + "camera": { + "name": "config_test", + "platform": "generic", + "still_image_url": "https://example.com", + "authentication": "digest", + "username": "user", + "password": "pass", + "stream_source": "rtsp://example.com:554/rtsp/", + "rtsp_transport": "udp", + }, + }, + ) + await hass.async_block_till_done() + assert hass.data["camera"].get_entity("camera.config_test") + + async def test_no_stream_source(aioclient_mock, hass, hass_client, hass_ws_client): """Test a stream request without stream source option set.""" assert await async_setup_component( From da1808226ee0133512f2cd5cbe0bd63fb08c3e10 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 20 Feb 2021 19:42:37 +0100 Subject: [PATCH 0630/1818] change log level to info (#46823) --- homeassistant/components/nut/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index f4fbbdef932001..174405e22e2347 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) ) else: - _LOGGER.warning( + _LOGGER.info( "Sensor type: %s does not appear in the NUT status " "output, cannot add", sensor_type, From 2940df09e9e302c2941b91e70f4535f64a896ef4 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sat, 20 Feb 2021 19:45:04 +0100 Subject: [PATCH 0631/1818] Update xknx to 0.17.0 (#46809) --- homeassistant/components/knx/__init__.py | 1 + homeassistant/components/knx/binary_sensor.py | 4 +++- homeassistant/components/knx/factory.py | 8 +++++++- homeassistant/components/knx/manifest.json | 2 +- homeassistant/components/knx/schema.py | 10 ++++++++-- homeassistant/components/knx/sensor.py | 2 +- homeassistant/components/knx/weather.py | 7 ++++++- requirements_all.txt | 2 +- 8 files changed, 28 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 1879d9a6415e46..5a8b0d1c351df3 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -376,6 +376,7 @@ def register_callback(self) -> TelegramQueue.Callback: self.telegram_received_cb, address_filters=address_filters, group_addresses=[], + match_for_outgoing=True, ) async def service_event_register_modify(self, call): diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index 35feb09dc1d9b2..f7ec3e80fa12d6 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -40,7 +40,9 @@ def is_on(self): @property def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Return device specific state attributes.""" - return {ATTR_COUNTER: self._device.counter} + if self._device.counter is not None: + return {ATTR_COUNTER: self._device.counter} + return None @property def force_update(self) -> bool: diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 20a887b628d10f..8d3464b25ad267 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -264,6 +264,9 @@ def _create_climate(knx_module: XKNX, config: ConfigType) -> XknxClimate: max_temp=config.get(ClimateSchema.CONF_MAX_TEMP), mode=climate_mode, on_off_invert=config[ClimateSchema.CONF_ON_OFF_INVERT], + create_temperature_sensors=config.get( + ClimateSchema.CONF_CREATE_TEMPERATURE_SENSORS + ), ) @@ -332,7 +335,7 @@ def _create_weather(knx_module: XKNX, config: ConfigType) -> XknxWeather: knx_module, name=config[CONF_NAME], sync_state=config[WeatherSchema.CONF_SYNC_STATE], - expose_sensors=config[WeatherSchema.CONF_KNX_EXPOSE_SENSORS], + create_sensors=config[WeatherSchema.CONF_KNX_CREATE_SENSORS], group_address_temperature=config[WeatherSchema.CONF_KNX_TEMPERATURE_ADDRESS], group_address_brightness_south=config.get( WeatherSchema.CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS @@ -347,6 +350,9 @@ def _create_weather(knx_module: XKNX, config: ConfigType) -> XknxWeather: WeatherSchema.CONF_KNX_BRIGHTNESS_NORTH_ADDRESS ), group_address_wind_speed=config.get(WeatherSchema.CONF_KNX_WIND_SPEED_ADDRESS), + group_address_wind_bearing=config.get( + WeatherSchema.CONF_KNX_WIND_BEARING_ADDRESS + ), group_address_rain_alarm=config.get(WeatherSchema.CONF_KNX_RAIN_ALARM_ADDRESS), group_address_frost_alarm=config.get( WeatherSchema.CONF_KNX_FROST_ALARM_ADDRESS diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 60fb097128c4ae..834e1604e9ec4d 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.16.3"], + "requirements": ["xknx==0.17.0"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index a9b65b853523e5..54134365b950ad 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -240,6 +240,7 @@ class ClimateSchema: CONF_ON_OFF_INVERT = "on_off_invert" CONF_MIN_TEMP = "min_temp" CONF_MAX_TEMP = "max_temp" + CONF_CREATE_TEMPERATURE_SENSORS = "create_temperature_sensors" DEFAULT_NAME = "KNX Climate" DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010" @@ -295,6 +296,9 @@ class ClimateSchema: ), vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), + vol.Optional( + CONF_CREATE_TEMPERATURE_SENSORS, default=False + ): cv.boolean, } ), ) @@ -397,13 +401,14 @@ class WeatherSchema: CONF_KNX_BRIGHTNESS_WEST_ADDRESS = "address_brightness_west" CONF_KNX_BRIGHTNESS_NORTH_ADDRESS = "address_brightness_north" CONF_KNX_WIND_SPEED_ADDRESS = "address_wind_speed" + CONF_KNX_WIND_BEARING_ADDRESS = "address_wind_bearing" CONF_KNX_RAIN_ALARM_ADDRESS = "address_rain_alarm" CONF_KNX_FROST_ALARM_ADDRESS = "address_frost_alarm" CONF_KNX_WIND_ALARM_ADDRESS = "address_wind_alarm" CONF_KNX_DAY_NIGHT_ADDRESS = "address_day_night" CONF_KNX_AIR_PRESSURE_ADDRESS = "address_air_pressure" CONF_KNX_HUMIDITY_ADDRESS = "address_humidity" - CONF_KNX_EXPOSE_SENSORS = "expose_sensors" + CONF_KNX_CREATE_SENSORS = "create_sensors" DEFAULT_NAME = "KNX Weather Station" @@ -415,13 +420,14 @@ class WeatherSchema: cv.boolean, cv.string, ), - vol.Optional(CONF_KNX_EXPOSE_SENSORS, default=False): cv.boolean, + vol.Optional(CONF_KNX_CREATE_SENSORS, default=False): cv.boolean, vol.Required(CONF_KNX_TEMPERATURE_ADDRESS): cv.string, vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): cv.string, vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): cv.string, vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): cv.string, vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): cv.string, vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): cv.string, + vol.Optional(CONF_KNX_WIND_BEARING_ADDRESS): cv.string, vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): cv.string, vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): cv.string, vol.Optional(CONF_KNX_WIND_ALARM_ADDRESS): cv.string, diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index dc9ffcb61b74f7..2409d7a6425eef 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -30,7 +30,7 @@ def state(self): return self._device.resolve_state() @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" return self._device.unit_of_measurement() diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py index 097fa661f4ad0a..031af9f5af014f 100644 --- a/homeassistant/components/knx/weather.py +++ b/homeassistant/components/knx/weather.py @@ -53,7 +53,12 @@ def condition(self): @property def humidity(self): """Return current humidity.""" - return self._device.humidity if self._device.humidity is not None else None + return self._device.humidity + + @property + def wind_bearing(self): + """Return current wind bearing in degrees.""" + return self._device.wind_bearing @property def wind_speed(self): diff --git a/requirements_all.txt b/requirements_all.txt index 3440eb9557e9b8..d4ed3e54fb4a1d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2321,7 +2321,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.16.3 +xknx==0.17.0 # homeassistant.components.bluesound # homeassistant.components.rest From 2ae790283c0c3c1a26ef6ebf103b7a9c768b24f0 Mon Sep 17 00:00:00 2001 From: kpine Date: Sat, 20 Feb 2021 10:52:23 -0800 Subject: [PATCH 0632/1818] Detect iBlinds v2.0 switch value as a cover not light (#46807) * Remove unused "fibaro_fgs222" discovery hint * Simplify multilevel switch current value discovery schema * Force iBlinds v2.0 devices to be discovered as cover entities * Rename discovery test file --- .../components/zwave_js/discovery.py | 71 ++-- tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_discovery.py | 14 + .../zwave_js/cover_iblinds_v2_state.json | 359 ++++++++++++++++++ 4 files changed, 410 insertions(+), 48 deletions(-) create mode 100644 tests/components/zwave_js/test_discovery.py create mode 100644 tests/fixtures/zwave_js/cover_iblinds_v2_state.json diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 7231d18e1868e3..9379ab69b3444b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -83,6 +83,12 @@ class ZWaveDiscoverySchema: allow_multi: bool = False +SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={"currentValue"}, + type={"number"}, +) + # For device class mapping see: # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json DISCOVERY_SCHEMAS = [ @@ -93,11 +99,7 @@ class ZWaveDiscoverySchema: manufacturer_id={0x0039}, product_id={0x3131}, product_type={0x4944}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # GE/Jasco fan controllers using switch multilevel CC ZWaveDiscoverySchema( @@ -105,11 +107,7 @@ class ZWaveDiscoverySchema: manufacturer_id={0x0063}, product_id={0x3034, 0x3131, 0x3138}, product_type={0x4944}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # Leviton ZW4SF fan controllers using switch multilevel CC ZWaveDiscoverySchema( @@ -117,50 +115,39 @@ class ZWaveDiscoverySchema: manufacturer_id={0x001D}, product_id={0x0002}, product_type={0x0038}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # Fibaro Shutter Fibaro FGS222 ZWaveDiscoverySchema( platform="cover", - hint="fibaro_fgs222", manufacturer_id={0x010F}, product_id={0x1000}, product_type={0x0302}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # Qubino flush shutter ZWaveDiscoverySchema( platform="cover", - hint="fibaro_fgs222", manufacturer_id={0x0159}, product_id={0x0052}, product_type={0x0003}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # Graber/Bali/Spring Fashion Covers ZWaveDiscoverySchema( platform="cover", - hint="fibaro_fgs222", manufacturer_id={0x026E}, product_id={0x5A31}, product_type={0x4353}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, + ), + # iBlinds v2 window blind motor + ZWaveDiscoverySchema( + platform="cover", + manufacturer_id={0x0287}, + product_id={0x000D}, + product_type={0x0003}, + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # ====== START OF GENERIC MAPPING SCHEMAS ======= # locks @@ -248,11 +235,7 @@ class ZWaveDiscoverySchema: "Multilevel Scene Switch", "Unused", }, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # binary sensors ZWaveDiscoverySchema( @@ -370,11 +353,7 @@ class ZWaveDiscoverySchema: "Motor Control Class C", "Multiposition Motor", }, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), # cover # motorized barriers @@ -400,11 +379,7 @@ class ZWaveDiscoverySchema: hint="fan", device_class_generic={"Multilevel Switch"}, device_class_specific={"Fan Switch"}, - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.SWITCH_MULTILEVEL}, - property={"currentValue"}, - type={"number"}, - ), + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), ] diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 31b7d795a605e6..02b11970376c83 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -164,6 +164,12 @@ def motorized_barrier_cover_state_fixture(): return json.loads(load_fixture("zwave_js/cover_zw062_state.json")) +@pytest.fixture(name="iblinds_v2_state", scope="session") +def iblinds_v2_state_fixture(): + """Load the iBlinds v2 node state fixture data.""" + return json.loads(load_fixture("zwave_js/cover_iblinds_v2_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -359,3 +365,11 @@ def motorized_barrier_cover_fixture(client, gdc_zw062_state): node = Node(client, gdc_zw062_state) client.driver.controller.nodes[node.node_id] = node return node + + +@pytest.fixture(name="iblinds_v2") +def iblinds_cover_fixture(client, iblinds_v2_state): + """Mock an iBlinds v2.0 window cover node.""" + node = Node(client, iblinds_v2_state) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py new file mode 100644 index 00000000000000..f7d26f07d216ca --- /dev/null +++ b/tests/components/zwave_js/test_discovery.py @@ -0,0 +1,14 @@ +"""Test discovery of entities for device-specific schemas for the Z-Wave JS integration.""" + + +async def test_iblinds_v2(hass, client, iblinds_v2, integration): + """Test that an iBlinds v2.0 multilevel switch value is discovered as a cover.""" + + node = iblinds_v2 + assert node.device_class.specific == "Unused" + + state = hass.states.get("light.window_blind_controller") + assert not state + + state = hass.states.get("cover.window_blind_controller") + assert state diff --git a/tests/fixtures/zwave_js/cover_iblinds_v2_state.json b/tests/fixtures/zwave_js/cover_iblinds_v2_state.json new file mode 100644 index 00000000000000..7cb6f94a6f0b95 --- /dev/null +++ b/tests/fixtures/zwave_js/cover_iblinds_v2_state.json @@ -0,0 +1,359 @@ +{ + "nodeId": 54, + "index": 0, + "installerIcon": 6400, + "userIcon": 6400, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Multilevel Switch", + "specific": "Unused", + "mandatorySupportedCCs": [ + "Basic", + "Multilevel Switch" + ], + "mandatoryControlCCs": [] + }, + "isListening": false, + "isFrequentListening": true, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 647, + "productId": 13, + "productType": 3, + "firmwareVersion": "1.65", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 7, + "deviceConfig": { + "manufacturerId": 647, + "manufacturer": "HAB Home Intelligence, LLC", + "label": "IB2.0", + "description": "Window Blind Controller", + "devices": [ + { + "productType": "0x0003", + "productId": "0x000d" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + } + }, + "label": "IB2.0", + "neighbors": [ + 1, + 2, + 3, + 7, + 8, + 11, + 15, + 18, + 19, + 22, + 26, + 27, + 44, + 52 + ], + "interviewAttempts": 1, + "interviewStage": 7, + "endpoints": [ + { + "nodeId": 54, + "index": 0, + "installerIcon": 6400, + "userIcon": 6400 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 2, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": true + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 2, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": true + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 2, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + }, + "value": { + "value": 0, + "unit": "seconds" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 4, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + }, + "value": { + "value": 0, + "unit": "seconds" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 30 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 647 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 13 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "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": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.33" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.65" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + } + ] +} From cf694152720308fa9e61110c9cccab7dc5eb13be Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 20 Feb 2021 12:57:24 -0600 Subject: [PATCH 0633/1818] Implement suggested area in roku (#46819) * implement suggested area in roku * Update const.py * Update test_media_player.py * Update test_media_player.py * Update test_media_player.py * Update test_media_player.py --- homeassistant/components/roku/__init__.py | 2 ++ homeassistant/components/roku/const.py | 1 + tests/components/roku/test_media_player.py | 29 +++++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index af2e0ee946fe2b..a75fc813fb917f 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -27,6 +27,7 @@ ATTR_MANUFACTURER, ATTR_MODEL, ATTR_SOFTWARE_VERSION, + ATTR_SUGGESTED_AREA, DOMAIN, ) @@ -161,4 +162,5 @@ def device_info(self) -> Dict[str, Any]: ATTR_MANUFACTURER: self.coordinator.data.info.brand, ATTR_MODEL: self.coordinator.data.info.model_name, ATTR_SOFTWARE_VERSION: self.coordinator.data.info.version, + ATTR_SUGGESTED_AREA: self.coordinator.data.info.device_location, } diff --git a/homeassistant/components/roku/const.py b/homeassistant/components/roku/const.py index 4abbd9e109aca6..dc458c88cd0f97 100644 --- a/homeassistant/components/roku/const.py +++ b/homeassistant/components/roku/const.py @@ -7,6 +7,7 @@ ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" ATTR_SOFTWARE_VERSION = "sw_version" +ATTR_SUGGESTED_AREA = "suggested_area" # Default Values DEFAULT_PORT = 8060 diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 1a1b46117bdc28..09124ecdf37325 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -65,14 +65,18 @@ from homeassistant.util import dt as dt_util from tests.common import async_fire_time_changed -from tests.components.roku import UPNP_SERIAL, setup_integration +from tests.components.roku import NAME_ROKUTV, UPNP_SERIAL, setup_integration from tests.test_util.aiohttp import AiohttpClientMocker MAIN_ENTITY_ID = f"{MP_DOMAIN}.my_roku_3" TV_ENTITY_ID = f"{MP_DOMAIN}.58_onn_roku_tv" TV_HOST = "192.168.1.161" +TV_LOCATION = "Living room" +TV_MANUFACTURER = "Onn" +TV_MODEL = "100005844" TV_SERIAL = "YN00H5555555" +TV_SW_VERSION = "9.2.0" async def test_setup( @@ -304,6 +308,29 @@ async def test_tv_attributes( assert state.attributes.get(ATTR_MEDIA_TITLE) == "Airwolf" +async def test_tv_device_registry( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test device registered for Roku TV in the device registry.""" + await setup_integration( + hass, + aioclient_mock, + device="rokutv", + app="tvinput-dtv", + host=TV_HOST, + unique_id=TV_SERIAL, + ) + + device_registry = await hass.helpers.device_registry.async_get_registry() + reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TV_SERIAL)}) + + assert reg_device.model == TV_MODEL + assert reg_device.sw_version == TV_SW_VERSION + assert reg_device.manufacturer == TV_MANUFACTURER + assert reg_device.suggested_area == TV_LOCATION + assert reg_device.name == NAME_ROKUTV + + async def test_services( hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker ) -> None: From 9f4874bb81c615c90edf0312b4b862052518c5a2 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 20 Feb 2021 19:57:46 +0100 Subject: [PATCH 0634/1818] Explicitly create_task for asyncio.wait (#46325) --- homeassistant/components/script/__init__.py | 5 ++++- homeassistant/helpers/service.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index eab30e01ee2cd7..5de3cb8264f893 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -181,7 +181,10 @@ async def turn_off_service(service): return await asyncio.wait( - [script_entity.async_turn_off() for script_entity in script_entities] + [ + asyncio.create_task(script_entity.async_turn_off()) + for script_entity in script_entities + ] ) async def toggle_service(service): diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index d4eacbc0503c9e..01fb76ec1bc579 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -629,7 +629,7 @@ async def entity_service_call( # Context expires if the turn on commands took a long time. # Set context again so it's there when we update entity.async_set_context(call.context) - tasks.append(entity.async_update_ha_state(True)) + tasks.append(asyncio.create_task(entity.async_update_ha_state(True))) if tasks: done, pending = await asyncio.wait(tasks) From c6c0e2416c257234ded5674ff242481c2def6aac Mon Sep 17 00:00:00 2001 From: marecabo <23156476+marecabo@users.noreply.github.com> Date: Sat, 20 Feb 2021 20:05:35 +0100 Subject: [PATCH 0635/1818] Validate icon and device_class of ESPHome sensor entities (#46709) --- homeassistant/components/esphome/sensor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index ad23afd47449f8..fe9922cf0edcb1 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -3,8 +3,11 @@ from typing import Optional from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState +import voluptuous as vol +from homeassistant.components.sensor import DEVICE_CLASSES from homeassistant.config_entries import ConfigEntry +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry @@ -54,7 +57,7 @@ def icon(self) -> str: """Return the icon.""" if not self._static_info.icon or self._static_info.device_class: return None - return self._static_info.icon + return vol.Schema(cv.icon)(self._static_info.icon) @property def force_update(self) -> bool: @@ -80,7 +83,7 @@ def unit_of_measurement(self) -> str: @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" - if not self._static_info.device_class: + if self._static_info.device_class not in DEVICE_CLASSES: return None return self._static_info.device_class From 2cf46330b1ec3fde09599af5aaf51149e847af42 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sat, 20 Feb 2021 20:08:59 +0100 Subject: [PATCH 0636/1818] Enable KNX routing optional local_ip (#46133) --- homeassistant/components/knx/__init__.py | 9 ++++++--- homeassistant/components/knx/schema.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 5a8b0d1c351df3..c957dbe51956c5 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -298,9 +298,12 @@ def connection_config(self): def connection_config_routing(self): """Return the connection_config if routing is configured.""" - local_ip = self.config[DOMAIN][CONF_KNX_ROUTING].get( - ConnectionSchema.CONF_KNX_LOCAL_IP - ) + local_ip = None + # all configuration values are optional + if self.config[DOMAIN][CONF_KNX_ROUTING] is not None: + local_ip = self.config[DOMAIN][CONF_KNX_ROUTING].get( + ConnectionSchema.CONF_KNX_LOCAL_IP + ) return ConnectionConfig( connection_type=ConnectionType.ROUTING, local_ip=local_ip ) diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 54134365b950ad..619090137396b5 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -38,7 +38,7 @@ class ConnectionSchema: } ) - ROUTING_SCHEMA = vol.Schema({vol.Optional(CONF_KNX_LOCAL_IP): cv.string}) + ROUTING_SCHEMA = vol.Maybe(vol.Schema({vol.Optional(CONF_KNX_LOCAL_IP): cv.string})) class CoverSchema: From 194c0d2b089b650ca7a41257df53b17669a12c45 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sat, 20 Feb 2021 20:09:23 +0100 Subject: [PATCH 0637/1818] knx-read-service (#46670) --- homeassistant/components/knx/__init__.py | 28 +++++++++++++++++++++- homeassistant/components/knx/services.yaml | 6 +++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index c957dbe51956c5..e4280e6bddc1e9 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -15,7 +15,7 @@ ConnectionType, ) from xknx.telegram import AddressFilter, GroupAddress, Telegram -from xknx.telegram.apci import GroupValueResponse, GroupValueWrite +from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite from homeassistant.const import ( CONF_ENTITY_ID, @@ -75,6 +75,7 @@ SERVICE_KNX_ATTR_TYPE = "type" SERVICE_KNX_ATTR_REMOVE = "remove" SERVICE_KNX_EVENT_REGISTER = "event_register" +SERVICE_KNX_READ = "read" CONFIG_SCHEMA = vol.Schema( { @@ -166,6 +167,15 @@ ), ) +SERVICE_KNX_READ_SCHEMA = vol.Schema( + { + vol.Required(SERVICE_KNX_ATTR_ADDRESS): vol.All( + cv.ensure_list, + [cv.string], + ) + } +) + SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, @@ -210,6 +220,13 @@ async def async_setup(hass, config): schema=SERVICE_KNX_SEND_SCHEMA, ) + hass.services.async_register( + DOMAIN, + SERVICE_KNX_READ, + hass.data[DOMAIN].service_read_to_knx_bus, + schema=SERVICE_KNX_READ_SCHEMA, + ) + async_register_admin_service( hass, DOMAIN, @@ -423,6 +440,15 @@ def calculate_payload(attr_payload): ) await self.xknx.telegrams.put(telegram) + async def service_read_to_knx_bus(self, call): + """Service for sending a GroupValueRead telegram to the KNX bus.""" + for address in call.data.get(SERVICE_KNX_ATTR_ADDRESS): + telegram = Telegram( + destination_address=GroupAddress(address), + payload=GroupValueRead(), + ) + await self.xknx.telegrams.put(telegram) + class KNXExposeTime: """Object to Expose Time/Date object to KNX bus.""" diff --git a/homeassistant/components/knx/services.yaml b/homeassistant/components/knx/services.yaml index cab8c100b01fd1..aa946459cfd261 100644 --- a/homeassistant/components/knx/services.yaml +++ b/homeassistant/components/knx/services.yaml @@ -10,6 +10,12 @@ send: type: description: "Optional. If set, the payload will not be sent as raw bytes, but encoded as given DPT. Knx sensor types are valid values (see https://www.home-assistant.io/integrations/sensor.knx)." example: "temperature" +read: + description: "Send GroupValueRead requests to the KNX bus. Response can be used from `knx_event` and will be processed in KNX entities." + fields: + address: + description: "Group address(es) to send read request to. Lists will read multiple group addresses." + example: "1/1/0" event_register: description: "Add or remove single group address to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." fields: From 6bb848455fdb59b0da6e9164b661cfc3b4d3db55 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 20 Feb 2021 20:09:43 +0100 Subject: [PATCH 0638/1818] Add open/close tilt support to KNX cover (#46583) --- homeassistant/components/knx/cover.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 33da600976e6de..86afd467be1d43 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -7,7 +7,9 @@ DEVICE_CLASS_BLIND, DEVICE_CLASSES, SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, SUPPORT_OPEN, + SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, @@ -61,7 +63,9 @@ def supported_features(self): if self._device.supports_stop: supported_features |= SUPPORT_STOP if self._device.supports_angle: - supported_features |= SUPPORT_SET_TILT_POSITION + supported_features |= ( + SUPPORT_SET_TILT_POSITION | SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT + ) return supported_features @property @@ -127,6 +131,14 @@ async def async_set_cover_tilt_position(self, **kwargs): knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION] await self._device.set_angle(knx_tilt_position) + async def async_open_cover_tilt(self, **kwargs): + """Open the cover tilt.""" + await self._device.set_short_up() + + async def async_close_cover_tilt(self, **kwargs): + """Close the cover tilt.""" + await self._device.set_short_down() + def start_auto_updater(self): """Start the autoupdater to update Home Assistant while cover is moving.""" if self._unsubscribe_auto_updater is None: From 806369ddc1328b23a2eaa62ff91d0d662331a6b1 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Sat, 20 Feb 2021 11:21:01 -0800 Subject: [PATCH 0639/1818] Bump pywemo to 0.6.3 (#46825) --- homeassistant/components/wemo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index 23911b31be275b..9d91ab7ef967db 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -3,7 +3,7 @@ "name": "Belkin WeMo", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wemo", - "requirements": ["pywemo==0.6.2"], + "requirements": ["pywemo==0.6.3"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/requirements_all.txt b/requirements_all.txt index d4ed3e54fb4a1d..45fd1d9850bfca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1902,7 +1902,7 @@ pyvolumio==0.1.3 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.6.2 +pywemo==0.6.3 # homeassistant.components.wilight pywilight==0.0.68 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f843874b5bc0c8..cec8c469d4b27d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -981,7 +981,7 @@ pyvolumio==0.1.3 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.6.2 +pywemo==0.6.3 # homeassistant.components.wilight pywilight==0.0.68 From fe4cf611f7afb9ae38243f2ce101f4b44b0b02a4 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Sat, 20 Feb 2021 11:37:48 -0800 Subject: [PATCH 0640/1818] Disable update polling for Wemo when devices can push updates (#46806) --- homeassistant/components/wemo/entity.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 65183a6f7a41fe..4fac786af9a69e 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -39,6 +39,7 @@ def __init__(self, device: WeMoDevice) -> None: self._state = None self._available = True self._update_lock = None + self._has_polled = False @property def name(self) -> str: @@ -103,6 +104,7 @@ async def _async_locked_update( """Try updating within an async lock.""" async with self._update_lock: await self.hass.async_add_executor_job(self._update, force_update) + self._has_polled = True # When the timeout expires HomeAssistant is no longer waiting for an # update from the device. Instead, the state needs to be updated # asynchronously. This also handles the case where an update came @@ -136,6 +138,24 @@ def is_on(self) -> bool: """Return true if the state is on. Standby is on.""" return self._state + @property + def should_poll(self) -> bool: + """Return True if the the device requires local polling, False otherwise. + + Polling can be disabled if three conditions are met: + 1. The device has polled to get the initial state (self._has_polled). + 2. The polling was successful and the device is in a healthy state + (self.available). + 3. The pywemo subscription registry reports that there is an active + subscription and the subscription has been confirmed by receiving an + initial event. This confirms that device push notifications are + working correctly (registry.is_subscribed - this method is async safe). + """ + registry = self.hass.data[WEMO_DOMAIN]["registry"] + return not ( + self.available and self._has_polled and registry.is_subscribed(self.wemo) + ) + async def async_added_to_hass(self) -> None: """Wemo device added to Home Assistant.""" await super().async_added_to_hass() From 43a5852561b6c464177fa016ab2a3216137fe418 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 20 Feb 2021 20:59:59 +0100 Subject: [PATCH 0641/1818] Fix habitica entry unload clean up (#46798) * Fix habitica entry unload clean up * Fix service remove * Add entry setup and unload test * Fix config flow tests * Assert service --- homeassistant/components/habitica/__init__.py | 4 +-- tests/components/habitica/test_config_flow.py | 29 ++++++++------- tests/components/habitica/test_init.py | 36 +++++++++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 tests/components/habitica/test_init.py diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index 36e50db6c20654..ca3837ef8ca5cc 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -169,6 +169,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) - if len(hass.config_entries.async_entries) == 1: - hass.components.webhook.async_unregister(SERVICE_API_CALL) + if len(hass.config_entries.async_entries(DOMAIN)) == 1: + hass.services.async_remove(DOMAIN, SERVICE_API_CALL) return unload_ok diff --git a/tests/components/habitica/test_config_flow.py b/tests/components/habitica/test_config_flow.py index 8ae92bcc0e2b72..d02a9031d631a7 100644 --- a/tests/components/habitica/test_config_flow.py +++ b/tests/components/habitica/test_config_flow.py @@ -1,11 +1,10 @@ """Test the habitica config flow.""" -from asyncio import Future from unittest.mock import AsyncMock, MagicMock, patch +from aiohttp import ClientResponseError + from homeassistant import config_entries, setup -from homeassistant.components.habitica.config_flow import InvalidAuth from homeassistant.components.habitica.const import DEFAULT_URL, DOMAIN -from homeassistant.const import HTTP_OK from tests.common import MockConfigEntry @@ -19,12 +18,8 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} - request_mock = MagicMock() - type(request_mock).status_code = HTTP_OK - mock_obj = MagicMock() - mock_obj.user.get.return_value = Future() - mock_obj.user.get.return_value.set_result(None) + mock_obj.user.get = AsyncMock() with patch( "homeassistant.components.habitica.config_flow.HabitipyAsync", @@ -58,9 +53,12 @@ async def test_form_invalid_credentials(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) + mock_obj = MagicMock() + mock_obj.user.get = AsyncMock(side_effect=ClientResponseError(MagicMock(), ())) + with patch( - "habitipy.aio.HabitipyAsync", - side_effect=InvalidAuth, + "homeassistant.components.habitica.config_flow.HabitipyAsync", + return_value=mock_obj, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -81,9 +79,12 @@ async def test_form_unexpected_exception(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) + mock_obj = MagicMock() + mock_obj.user.get = AsyncMock(side_effect=Exception) + with patch( "homeassistant.components.habitica.config_flow.HabitipyAsync", - side_effect=Exception, + return_value=mock_obj, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -98,7 +99,7 @@ async def test_form_unexpected_exception(hass): assert result2["errors"] == {"base": "unknown"} -async def test_manual_flow_config_exist(hass, aioclient_mock): +async def test_manual_flow_config_exist(hass): """Test config flow discovers only already configured config.""" MockConfigEntry( domain=DOMAIN, @@ -114,9 +115,7 @@ async def test_manual_flow_config_exist(hass, aioclient_mock): assert result["step_id"] == "user" mock_obj = MagicMock() - mock_obj.user.get.side_effect = AsyncMock( - return_value={"api_user": "test-api-user"} - ) + mock_obj.user.get = AsyncMock(return_value={"api_user": "test-api-user"}) with patch( "homeassistant.components.habitica.config_flow.HabitipyAsync", diff --git a/tests/components/habitica/test_init.py b/tests/components/habitica/test_init.py new file mode 100644 index 00000000000000..5f7e4b7fbf509d --- /dev/null +++ b/tests/components/habitica/test_init.py @@ -0,0 +1,36 @@ +"""Test the habitica init module.""" +from homeassistant.components.habitica.const import ( + DEFAULT_URL, + DOMAIN, + SERVICE_API_CALL, +) + +from tests.common import MockConfigEntry + + +async def test_entry_setup_unload(hass, aioclient_mock): + """Test integration setup and unload.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="test-api-user", + data={ + "api_user": "test-api-user", + "api_key": "test-api-key", + "url": DEFAULT_URL, + }, + ) + entry.add_to_hass(hass) + + aioclient_mock.get( + "https://habitica.com/api/v3/user", + json={"data": {"api_user": "test-api-user", "profile": {"name": "test_user"}}}, + ) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.services.has_service(DOMAIN, SERVICE_API_CALL) + + assert await hass.config_entries.async_unload(entry.entry_id) + + assert not hass.services.has_service(DOMAIN, SERVICE_API_CALL) From 6e52b26c06098052d379065b00f570c2a44653e1 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Sat, 20 Feb 2021 13:16:50 -0800 Subject: [PATCH 0642/1818] Speed-up wemo discovery (#46821) * Speed-up wemo discovery * Use gather_with_concurrency to limit concurrent executor usage * Comment fixup: asyncio executors -> executor threads --- homeassistant/components/wemo/__init__.py | 48 +++++++++++++++++++---- tests/components/wemo/test_init.py | 33 +++++++++++----- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index db380ae11cac2f..df737f101baaa6 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,5 +1,4 @@ """Support for WeMo device discovery.""" -import asyncio import logging import pywemo @@ -16,9 +15,14 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later +from homeassistant.util.async_ import gather_with_concurrency from .const import DOMAIN +# Max number of devices to initialize at once. This limit is in place to +# avoid tying up too many executor threads with WeMo device setup. +MAX_CONCURRENCY = 3 + # Mapping from Wemo model_name to domain. WEMO_MODEL_DISPATCH = { "Bridge": LIGHT_DOMAIN, @@ -114,11 +118,12 @@ async def async_stop_wemo(event): static_conf = config.get(CONF_STATIC, []) if static_conf: _LOGGER.debug("Adding statically configured WeMo devices...") - for device in await asyncio.gather( + for device in await gather_with_concurrency( + MAX_CONCURRENCY, *[ hass.async_add_executor_job(validate_static_config, host, port) for host, port in static_conf - ] + ], ): if device: wemo_dispatcher.async_add_unique_device(hass, device) @@ -187,15 +192,44 @@ def __init__(self, hass: HomeAssistant, wemo_dispatcher: WemoDispatcher) -> None self._wemo_dispatcher = wemo_dispatcher self._stop = None self._scan_delay = 0 + self._upnp_entries = set() + + async def async_add_from_upnp_entry(self, entry: pywemo.ssdp.UPNPEntry) -> None: + """Create a WeMoDevice from an UPNPEntry and add it to the dispatcher. + + Uses the self._upnp_entries set to avoid interrogating the same device + multiple times. + """ + if entry in self._upnp_entries: + return + try: + device = await self._hass.async_add_executor_job( + pywemo.discovery.device_from_uuid_and_location, + entry.udn, + entry.location, + ) + except pywemo.PyWeMoException as err: + _LOGGER.error("Unable to setup WeMo %r (%s)", entry, err) + else: + self._wemo_dispatcher.async_add_unique_device(self._hass, device) + self._upnp_entries.add(entry) async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" _LOGGER.debug("Scanning network for WeMo devices...") try: - for device in await self._hass.async_add_executor_job( - pywemo.discover_devices - ): - self._wemo_dispatcher.async_add_unique_device(self._hass, device) + # pywemo.ssdp.scan is a light-weight UDP UPnP scan for WeMo devices. + entries = await self._hass.async_add_executor_job(pywemo.ssdp.scan) + + # async_add_from_upnp_entry causes multiple HTTP requests to be sent + # to the WeMo device for the initial setup of the WeMoDevice + # instance. This may take some time to complete. The per-device + # setup work is done in parallel to speed up initial setup for the + # component. + await gather_with_concurrency( + MAX_CONCURRENCY, + *[self.async_add_from_upnp_entry(entry) for entry in entries], + ) finally: # Run discovery more frequently after hass has just started. self._scan_delay = min( diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 374222d86888da..7c2b43dfd8cfd2 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -100,28 +100,41 @@ async def test_static_config_with_invalid_host(hass): async def test_discovery(hass, pywemo_registry): """Verify that discovery dispatches devices to the platform for setup.""" - def create_device(counter): + def create_device(uuid, location): """Create a unique mock Motion detector device for each counter value.""" device = create_autospec(pywemo.Motion, instance=True) - device.host = f"{MOCK_HOST}_{counter}" - device.port = MOCK_PORT + counter - device.name = f"{MOCK_NAME}_{counter}" - device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{counter}" + device.host = location + device.port = MOCK_PORT + device.name = f"{MOCK_NAME}_{uuid}" + device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{uuid}" device.model_name = "Motion" device.get_state.return_value = 0 # Default to Off return device - pywemo_devices = [create_device(0), create_device(1)] + def create_upnp_entry(counter): + return pywemo.ssdp.UPNPEntry.from_response( + "\r\n".join( + [ + "", + f"LOCATION: http://192.168.1.100:{counter}/setup.xml", + f"USN: uuid:Socket-1_0-SERIAL{counter}::upnp:rootdevice", + "", + ] + ) + ) + + upnp_entries = [create_upnp_entry(0), create_upnp_entry(1)] # Setup the component and start discovery. with patch( - "pywemo.discover_devices", return_value=pywemo_devices - ) as mock_discovery: + "pywemo.discovery.device_from_uuid_and_location", side_effect=create_device + ), patch("pywemo.ssdp.scan", return_value=upnp_entries) as mock_scan: assert await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_DISCOVERY: True}} ) await pywemo_registry.semaphore.acquire() # Returns after platform setup. - mock_discovery.assert_called() - pywemo_devices.append(create_device(2)) + mock_scan.assert_called() + # Add two of the same entries to test deduplication. + upnp_entries.extend([create_upnp_entry(2), create_upnp_entry(2)]) # Test that discovery runs periodically and the async_dispatcher_send code works. async_fire_time_changed( From 4af619d3838406cfda66b1e6a5e4b100f00b1b80 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Sat, 20 Feb 2021 22:55:23 +0100 Subject: [PATCH 0643/1818] Add Rituals Perfume Genie integration (#46218) Co-authored-by: J. Nick Koston --- .coveragerc | 2 + CODEOWNERS | 1 + .../rituals_perfume_genie/__init__.py | 61 ++++++++++ .../rituals_perfume_genie/config_flow.py | 60 ++++++++++ .../components/rituals_perfume_genie/const.py | 5 + .../rituals_perfume_genie/manifest.json | 12 ++ .../rituals_perfume_genie/strings.json | 21 ++++ .../rituals_perfume_genie/switch.py | 92 +++++++++++++++ .../translations/en.json | 21 ++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + .../rituals_perfume_genie/__init__.py | 1 + .../rituals_perfume_genie/test_config_flow.py | 109 ++++++++++++++++++ 14 files changed, 392 insertions(+) create mode 100644 homeassistant/components/rituals_perfume_genie/__init__.py create mode 100644 homeassistant/components/rituals_perfume_genie/config_flow.py create mode 100644 homeassistant/components/rituals_perfume_genie/const.py create mode 100644 homeassistant/components/rituals_perfume_genie/manifest.json create mode 100644 homeassistant/components/rituals_perfume_genie/strings.json create mode 100644 homeassistant/components/rituals_perfume_genie/switch.py create mode 100644 homeassistant/components/rituals_perfume_genie/translations/en.json create mode 100644 tests/components/rituals_perfume_genie/__init__.py create mode 100644 tests/components/rituals_perfume_genie/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 51a539b4abab7c..2b71ba546cc75d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -787,6 +787,8 @@ omit = homeassistant/components/rest/switch.py homeassistant/components/ring/camera.py homeassistant/components/ripple/sensor.py + homeassistant/components/rituals_perfume_genie/switch.py + homeassistant/components/rituals_perfume_genie/__init__.py homeassistant/components/rocketchat/notify.py homeassistant/components/roomba/binary_sensor.py homeassistant/components/roomba/braava.py diff --git a/CODEOWNERS b/CODEOWNERS index ab10b4dfd6040c..f3c7487a520506 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -383,6 +383,7 @@ homeassistant/components/rflink/* @javicalle homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221 homeassistant/components/ring/* @balloob homeassistant/components/risco/* @OnFreund +homeassistant/components/rituals_perfume_genie/* @milanmeu homeassistant/components/rmvtransport/* @cgtobi homeassistant/components/roku/* @ctalkington homeassistant/components/roomba/* @pschmitt @cyr-ius @shenxn diff --git a/homeassistant/components/rituals_perfume_genie/__init__.py b/homeassistant/components/rituals_perfume_genie/__init__.py new file mode 100644 index 00000000000000..ba11206d49650d --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/__init__.py @@ -0,0 +1,61 @@ +"""The Rituals Perfume Genie integration.""" +import asyncio +import logging + +from aiohttp.client_exceptions import ClientConnectorError +from pyrituals import Account + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import ACCOUNT_HASH, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +EMPTY_CREDENTIALS = "" + +PLATFORMS = ["switch"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Rituals Perfume Genie component.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Rituals Perfume Genie from a config entry.""" + session = async_get_clientsession(hass) + account = Account(EMPTY_CREDENTIALS, EMPTY_CREDENTIALS, session) + account.data = {ACCOUNT_HASH: entry.data.get(ACCOUNT_HASH)} + + try: + await account.get_devices() + except ClientConnectorError as ex: + raise ConfigEntryNotReady from ex + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = account + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/rituals_perfume_genie/config_flow.py b/homeassistant/components/rituals_perfume_genie/config_flow.py new file mode 100644 index 00000000000000..59e442df538b14 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/config_flow.py @@ -0,0 +1,60 @@ +"""Config flow for Rituals Perfume Genie integration.""" +import logging + +from aiohttp import ClientResponseError +from pyrituals import Account, AuthenticationException +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import ACCOUNT_HASH, DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + } +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Rituals Perfume Genie.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + if user_input is None: + return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA) + + errors = {} + + session = async_get_clientsession(self.hass) + account = Account(user_input[CONF_EMAIL], user_input[CONF_PASSWORD], session) + + try: + await account.authenticate() + except ClientResponseError: + errors["base"] = "cannot_connect" + except AuthenticationException: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(account.data[CONF_EMAIL]) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=account.data[CONF_EMAIL], + data={ACCOUNT_HASH: account.data[ACCOUNT_HASH]}, + ) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/rituals_perfume_genie/const.py b/homeassistant/components/rituals_perfume_genie/const.py new file mode 100644 index 00000000000000..075d79ec8de9e8 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/const.py @@ -0,0 +1,5 @@ +"""Constants for the Rituals Perfume Genie integration.""" + +DOMAIN = "rituals_perfume_genie" + +ACCOUNT_HASH = "account_hash" diff --git a/homeassistant/components/rituals_perfume_genie/manifest.json b/homeassistant/components/rituals_perfume_genie/manifest.json new file mode 100644 index 00000000000000..8be7e98b939895 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "rituals_perfume_genie", + "name": "Rituals Perfume Genie", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/rituals_perfume_genie", + "requirements": [ + "pyrituals==0.0.2" + ], + "codeowners": [ + "@milanmeu" + ] +} diff --git a/homeassistant/components/rituals_perfume_genie/strings.json b/homeassistant/components/rituals_perfume_genie/strings.json new file mode 100644 index 00000000000000..8824923c3138bf --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "user": { + "title": "Connect to your Rituals account", + "data": { + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "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%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py new file mode 100644 index 00000000000000..7041d22f4b82ea --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -0,0 +1,92 @@ +"""Support for Rituals Perfume Genie switches.""" +from datetime import timedelta + +from homeassistant.components.switch import SwitchEntity + +from .const import DOMAIN + +SCAN_INTERVAL = timedelta(seconds=30) + +ON_STATE = "1" +AVAILABLE_STATE = 1 + +MANUFACTURER = "Rituals Cosmetics" +MODEL = "Diffuser" +ICON = "mdi:fan" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the diffuser switch.""" + account = hass.data[DOMAIN][config_entry.entry_id] + diffusers = await account.get_devices() + + entities = [] + for diffuser in diffusers: + entities.append(DiffuserSwitch(diffuser)) + + async_add_entities(entities, True) + + +class DiffuserSwitch(SwitchEntity): + """Representation of a diffuser switch.""" + + def __init__(self, diffuser): + """Initialize the switch.""" + self._diffuser = diffuser + + @property + def device_info(self): + """Return information about the device.""" + return { + "name": self._diffuser.data["hub"]["attributes"]["roomnamec"], + "identifiers": {(DOMAIN, self._diffuser.data["hub"]["hublot"])}, + "manufacturer": MANUFACTURER, + "model": MODEL, + "sw_version": self._diffuser.data["hub"]["sensors"]["versionc"], + } + + @property + def unique_id(self): + """Return the unique ID of the device.""" + return self._diffuser.data["hub"]["hublot"] + + @property + def available(self): + """Return if the device is available.""" + return self._diffuser.data["hub"]["status"] == AVAILABLE_STATE + + @property + def name(self): + """Return the name of the device.""" + return self._diffuser.data["hub"]["attributes"]["roomnamec"] + + @property + def icon(self): + """Return the icon of the device.""" + return ICON + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attributes = { + "fan_speed": self._diffuser.data["hub"]["attributes"]["speedc"], + "room_size": self._diffuser.data["hub"]["attributes"]["roomc"], + } + return attributes + + @property + def is_on(self): + """If the device is currently on or off.""" + return self._diffuser.data["hub"]["attributes"]["fanc"] == ON_STATE + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + await self._diffuser.turn_on() + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + await self._diffuser.turn_off() + + async def async_update(self): + """Update the data of the device.""" + await self._diffuser.update_data() diff --git a/homeassistant/components/rituals_perfume_genie/translations/en.json b/homeassistant/components/rituals_perfume_genie/translations/en.json new file mode 100644 index 00000000000000..21207b1e7ed34a --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Password" + }, + "title": "Connect to your Rituals account" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 53d3c8294d206f..dfb2f56b29e24d 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -182,6 +182,7 @@ "rfxtrx", "ring", "risco", + "rituals_perfume_genie", "roku", "roomba", "roon", diff --git a/requirements_all.txt b/requirements_all.txt index 45fd1d9850bfca..35a4d925225ecd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1652,6 +1652,9 @@ pyrepetier==3.0.5 # homeassistant.components.risco pyrisco==0.3.1 +# homeassistant.components.rituals_perfume_genie +pyrituals==0.0.2 + # homeassistant.components.ruckus_unleashed pyruckus==0.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cec8c469d4b27d..7f920b735e7aa9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,6 +876,9 @@ pyqwikswitch==0.93 # homeassistant.components.risco pyrisco==0.3.1 +# homeassistant.components.rituals_perfume_genie +pyrituals==0.0.2 + # homeassistant.components.ruckus_unleashed pyruckus==0.12 diff --git a/tests/components/rituals_perfume_genie/__init__.py b/tests/components/rituals_perfume_genie/__init__.py new file mode 100644 index 00000000000000..bd90242f14c210 --- /dev/null +++ b/tests/components/rituals_perfume_genie/__init__.py @@ -0,0 +1 @@ +"""Tests for the Rituals Perfume Genie integration.""" diff --git a/tests/components/rituals_perfume_genie/test_config_flow.py b/tests/components/rituals_perfume_genie/test_config_flow.py new file mode 100644 index 00000000000000..60ec389a3715bb --- /dev/null +++ b/tests/components/rituals_perfume_genie/test_config_flow.py @@ -0,0 +1,109 @@ +"""Test the Rituals Perfume Genie config flow.""" +from unittest.mock import patch + +from aiohttp import ClientResponseError +from pyrituals import AuthenticationException + +from homeassistant import config_entries +from homeassistant.components.rituals_perfume_genie.const import ACCOUNT_HASH, DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD + +TEST_EMAIL = "rituals@example.com" +VALID_PASSWORD = "passw0rd" +WRONG_PASSWORD = "wrong-passw0rd" + + +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 + + with patch( + "homeassistant.components.rituals_perfume_genie.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.rituals_perfume_genie.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: TEST_EMAIL, + CONF_PASSWORD: VALID_PASSWORD, + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == TEST_EMAIL + assert isinstance(result2["data"][ACCOUNT_HASH], str) + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.rituals_perfume_genie.config_flow.Account.authenticate", + side_effect=AuthenticationException, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: TEST_EMAIL, + CONF_PASSWORD: WRONG_PASSWORD, + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_auth_exception(hass): + """Test we handle auth exception.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.rituals_perfume_genie.config_flow.Account.authenticate", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: TEST_EMAIL, + CONF_PASSWORD: VALID_PASSWORD, + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +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( + "homeassistant.components.rituals_perfume_genie.config_flow.Account.authenticate", + side_effect=ClientResponseError(None, None, status=500), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: TEST_EMAIL, + CONF_PASSWORD: VALID_PASSWORD, + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} From 2b6619f815adfd391a78ad86d7cafc752095f7a7 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Sat, 20 Feb 2021 16:57:16 -0500 Subject: [PATCH 0644/1818] Bump zigpy-znp from 0.3.0 to 0.4.0 (#46828) --- 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 a24c20872f2cf5..ad2bf5f17c5488 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -13,7 +13,7 @@ "zigpy==0.32.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.7.3", - "zigpy-znp==0.3.0" + "zigpy-znp==0.4.0" ], "codeowners": ["@dmulcahey", "@adminiuga"] } diff --git a/requirements_all.txt b/requirements_all.txt index 35a4d925225ecd..4d3d7d5bebf184 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2379,7 +2379,7 @@ zigpy-xbee==0.13.0 zigpy-zigate==0.7.3 # homeassistant.components.zha -zigpy-znp==0.3.0 +zigpy-znp==0.4.0 # homeassistant.components.zha zigpy==0.32.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7f920b735e7aa9..80d325100c86aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1219,7 +1219,7 @@ zigpy-xbee==0.13.0 zigpy-zigate==0.7.3 # homeassistant.components.zha -zigpy-znp==0.3.0 +zigpy-znp==0.4.0 # homeassistant.components.zha zigpy==0.32.0 From 115fe266425654d042d633a20c5119918ffbcbcc Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sat, 20 Feb 2021 14:25:02 -0800 Subject: [PATCH 0645/1818] Add smarttub sensor platform and state sensor (#46775) --- homeassistant/components/smarttub/__init__.py | 2 +- homeassistant/components/smarttub/climate.py | 5 --- homeassistant/components/smarttub/entity.py | 5 +++ homeassistant/components/smarttub/sensor.py | 30 ++++++++++++++++++ tests/components/smarttub/__init__.py | 14 +++++++++ tests/components/smarttub/conftest.py | 11 ++++++- tests/components/smarttub/test_climate.py | 31 ++----------------- tests/components/smarttub/test_config_flow.py | 2 +- tests/components/smarttub/test_init.py | 6 ++-- tests/components/smarttub/test_sensor.py | 18 +++++++++++ 10 files changed, 84 insertions(+), 40 deletions(-) create mode 100644 homeassistant/components/smarttub/sensor.py create mode 100644 tests/components/smarttub/test_sensor.py diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py index 4700b0df4de619..2b80c92510f8eb 100644 --- a/homeassistant/components/smarttub/__init__.py +++ b/homeassistant/components/smarttub/__init__.py @@ -7,7 +7,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["climate"] +PLATFORMS = ["climate", "sensor"] async def async_setup(hass, config): diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 5f16bea8cf70bd..02d627d383eb6a 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -36,11 +36,6 @@ def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__(coordinator, spa, "thermostat") - @property - def unique_id(self) -> str: - """Return a unique id for the entity.""" - return f"{self.spa.id}-{self._entity_type}" - @property def temperature_unit(self): """Return the unit of measurement used by the platform.""" diff --git a/homeassistant/components/smarttub/entity.py b/homeassistant/components/smarttub/entity.py index 95d89971cb723c..0e84c92e3e16e9 100644 --- a/homeassistant/components/smarttub/entity.py +++ b/homeassistant/components/smarttub/entity.py @@ -32,6 +32,11 @@ def __init__( self.spa = spa self._entity_type = entity_type + @property + def unique_id(self) -> str: + """Return a unique id for the entity.""" + return f"{self.spa.id}-{self._entity_type}" + @property def device_info(self) -> str: """Return device info.""" diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py new file mode 100644 index 00000000000000..320f288f36a68b --- /dev/null +++ b/homeassistant/components/smarttub/sensor.py @@ -0,0 +1,30 @@ +"""Platform for sensor integration.""" +import logging + +from .const import DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up climate entity for the thermostat in the tub.""" + + controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] + + entities = [SmartTubState(controller.coordinator, spa) for spa in controller.spas] + + async_add_entities(entities) + + +class SmartTubState(SmartTubEntity): + """The state of the spa.""" + + def __init__(self, coordinator, spa): + """Initialize the entity.""" + super().__init__(coordinator, spa, "state") + + @property + def state(self) -> str: + """Return the current state of the sensor.""" + return self.get_spa_status("state").lower() diff --git a/tests/components/smarttub/__init__.py b/tests/components/smarttub/__init__.py index afbf271eb63301..b19af1ee59a91b 100644 --- a/tests/components/smarttub/__init__.py +++ b/tests/components/smarttub/__init__.py @@ -1 +1,15 @@ """Tests for the smarttub integration.""" + +from datetime import timedelta + +from homeassistant.components.smarttub.const import SCAN_INTERVAL +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def trigger_update(hass): + """Trigger a polling update by moving time forward.""" + new_time = dt.utcnow() + timedelta(seconds=SCAN_INTERVAL + 1) + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index 3519d4f85ce498..d1bd7c377e36f8 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -46,6 +46,7 @@ def mock_spa(): "setTemperature": 39, "water": {"temperature": 38}, "heater": "ON", + "state": "NORMAL", } return mock_spa @@ -60,7 +61,7 @@ def mock_account(spa): return mock_account -@pytest.fixture(name="smarttub_api") +@pytest.fixture(name="smarttub_api", autouse=True) def mock_api(account, spa): """Mock the SmartTub API.""" @@ -71,3 +72,11 @@ def mock_api(account, spa): api_mock = api_class_mock.return_value api_mock.get_account.return_value = account yield api_mock + + +@pytest.fixture +async def setup_entry(hass, config_entry): + """Initialize the config entry.""" + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/smarttub/test_climate.py b/tests/components/smarttub/test_climate.py index ad47e3ede04ecf..69fb642aab4a98 100644 --- a/tests/components/smarttub/test_climate.py +++ b/tests/components/smarttub/test_climate.py @@ -1,7 +1,5 @@ """Test the SmartTub climate platform.""" -from datetime import timedelta - import smarttub from homeassistant.components.climate.const import ( @@ -19,35 +17,19 @@ SERVICE_SET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.components.smarttub.const import ( - DEFAULT_MAX_TEMP, - DEFAULT_MIN_TEMP, - SCAN_INTERVAL, -) +from homeassistant.components.smarttub.const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, ) -from homeassistant.util import dt -from tests.common import async_fire_time_changed +from . import trigger_update -async def test_thermostat_update(spa, hass, config_entry, smarttub_api): +async def test_thermostat_update(spa, setup_entry, hass): """Test the thermostat entity.""" - spa.get_status.return_value = { - "heater": "ON", - "water": { - "temperature": 38, - }, - "setTemperature": 39, - } - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - entity_id = f"climate.{spa.brand}_{spa.model}_thermostat" state = hass.states.get(entity_id) assert state @@ -87,10 +69,3 @@ async def test_thermostat_update(spa, hass, config_entry, smarttub_api): spa.get_status.side_effect = smarttub.APIError await trigger_update(hass) # should not fail - - -async def trigger_update(hass): - """Trigger a polling update by moving time forward.""" - new_time = dt.utcnow() + timedelta(seconds=SCAN_INTERVAL + 1) - async_fire_time_changed(hass, new_time) - await hass.async_block_till_done() diff --git a/tests/components/smarttub/test_config_flow.py b/tests/components/smarttub/test_config_flow.py index a57eb43eef7f6a..2608d867c0d6c7 100644 --- a/tests/components/smarttub/test_config_flow.py +++ b/tests/components/smarttub/test_config_flow.py @@ -7,7 +7,7 @@ from homeassistant.components.smarttub.const import DOMAIN -async def test_form(hass, smarttub_api): +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} diff --git a/tests/components/smarttub/test_init.py b/tests/components/smarttub/test_init.py index 13a447529d4d81..01989818d3be67 100644 --- a/tests/components/smarttub/test_init.py +++ b/tests/components/smarttub/test_init.py @@ -39,15 +39,13 @@ async def test_setup_auth_failed(setup_component, hass, config_entry, smarttub_a assert config_entry.state == ENTRY_STATE_SETUP_ERROR -async def test_config_passed_to_config_entry( - hass, config_entry, config_data, smarttub_api -): +async def test_config_passed_to_config_entry(hass, config_entry, config_data): """Test that configured options are loaded via config entry.""" config_entry.add_to_hass(hass) assert await async_setup_component(hass, smarttub.DOMAIN, config_data) -async def test_unload_entry(hass, config_entry, smarttub_api): +async def test_unload_entry(hass, config_entry): """Test being able to unload an entry.""" config_entry.add_to_hass(hass) diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py new file mode 100644 index 00000000000000..7d62440295e6f0 --- /dev/null +++ b/tests/components/smarttub/test_sensor.py @@ -0,0 +1,18 @@ +"""Test the SmartTub sensor platform.""" + +from . import trigger_update + + +async def test_state_update(spa, setup_entry, hass, smarttub_api): + """Test the state entity.""" + + entity_id = f"sensor.{spa.brand}_{spa.model}_state" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "normal" + + spa.get_status.return_value["state"] = "BAD" + await trigger_update(hass) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "bad" From f045c0512b063569a18d7d40fa7c36ac757b3beb Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Sat, 20 Feb 2021 18:00:18 -0500 Subject: [PATCH 0646/1818] Fix Insteon config flow with add X10 and device override (#45854) --- homeassistant/components/insteon/schemas.py | 46 ++++++++++---------- tests/components/insteon/test_config_flow.py | 8 +++- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index adc7e945eba2f5..8698a358b21abe 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -187,47 +187,47 @@ def add_device_override(config_data, new_override): except ValueError as err: raise ValueError("Incorrect values") from err - overrides = config_data.get(CONF_OVERRIDE, []) - curr_override = {} + overrides = [] - # If this address has an override defined, remove it - for override in overrides: - if override[CONF_ADDRESS] == address: - curr_override = override - break - if curr_override: - overrides.remove(curr_override) + for override in config_data.get(CONF_OVERRIDE, []): + if override[CONF_ADDRESS] != address: + overrides.append(override) + curr_override = {} curr_override[CONF_ADDRESS] = address curr_override[CONF_CAT] = cat curr_override[CONF_SUBCAT] = subcat overrides.append(curr_override) - config_data[CONF_OVERRIDE] = overrides - return config_data + + new_config = {} + if config_data.get(CONF_X10): + new_config[CONF_X10] = config_data[CONF_X10] + new_config[CONF_OVERRIDE] = overrides + return new_config def add_x10_device(config_data, new_x10): """Add a new X10 device to X10 device list.""" - curr_device = {} - x10_devices = config_data.get(CONF_X10, []) - for x10_device in x10_devices: + x10_devices = [] + for x10_device in config_data.get(CONF_X10, []): if ( - x10_device[CONF_HOUSECODE] == new_x10[CONF_HOUSECODE] - and x10_device[CONF_UNITCODE] == new_x10[CONF_UNITCODE] + x10_device[CONF_HOUSECODE] != new_x10[CONF_HOUSECODE] + or x10_device[CONF_UNITCODE] != new_x10[CONF_UNITCODE] ): - curr_device = x10_device - break - - if curr_device: - x10_devices.remove(curr_device) + x10_devices.append(x10_device) + curr_device = {} curr_device[CONF_HOUSECODE] = new_x10[CONF_HOUSECODE] curr_device[CONF_UNITCODE] = new_x10[CONF_UNITCODE] curr_device[CONF_PLATFORM] = new_x10[CONF_PLATFORM] curr_device[CONF_DIM_STEPS] = new_x10[CONF_DIM_STEPS] x10_devices.append(curr_device) - config_data[CONF_X10] = x10_devices - return config_data + + new_config = {} + if config_data.get(CONF_OVERRIDE): + new_config[CONF_OVERRIDE] = config_data[CONF_OVERRIDE] + new_config[CONF_X10] = x10_devices + return new_config def build_device_override_schema( diff --git a/tests/components/insteon/test_config_flow.py b/tests/components/insteon/test_config_flow.py index f1940b1eb3927f..1b08317ca303ca 100644 --- a/tests/components/insteon/test_config_flow.py +++ b/tests/components/insteon/test_config_flow.py @@ -369,13 +369,16 @@ async def test_options_add_device_override(hass: HomeAssistantType): CONF_CAT: "05", CONF_SUBCAT: "bb", } - await _options_form(hass, result2["flow_id"], user_input) + result3, _ = await _options_form(hass, result2["flow_id"], user_input) assert len(config_entry.options[CONF_OVERRIDE]) == 2 assert config_entry.options[CONF_OVERRIDE][1][CONF_ADDRESS] == "4D.5E.6F" assert config_entry.options[CONF_OVERRIDE][1][CONF_CAT] == 5 assert config_entry.options[CONF_OVERRIDE][1][CONF_SUBCAT] == 187 + # If result1 eq result2 the changes will not save + assert result["data"] != result3["data"] + async def test_options_remove_device_override(hass: HomeAssistantType): """Test removing a device override.""" @@ -477,6 +480,9 @@ async def test_options_add_x10_device(hass: HomeAssistantType): assert config_entry.options[CONF_X10][1][CONF_PLATFORM] == "binary_sensor" assert config_entry.options[CONF_X10][1][CONF_DIM_STEPS] == 15 + # If result2 eq result3 the changes will not save + assert result2["data"] != result3["data"] + async def test_options_remove_x10_device(hass: HomeAssistantType): """Test removing an X10 device.""" From 0cb1f61deb3393dc1f3701e88cc00be5376ef813 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 21 Feb 2021 00:07:04 +0000 Subject: [PATCH 0647/1818] [ci skip] Translation update --- .../components/abode/translations/ko.json | 13 ++++++- .../accuweather/translations/ko.json | 20 +++++++++++ .../components/acmeda/translations/ko.json | 3 ++ .../components/adguard/translations/ko.json | 9 +++-- .../advantage_air/translations/ko.json | 18 ++++++++++ .../components/aemet/translations/cs.json | 3 +- .../components/aemet/translations/ko.json | 21 ++++++++++++ .../components/agent_dvr/translations/ko.json | 3 +- .../components/airly/translations/ko.json | 5 +-- .../components/airnow/translations/ko.json | 21 ++++++++++++ .../components/airvisual/translations/cs.json | 4 ++- .../components/airvisual/translations/ko.json | 27 ++++++++++++--- .../alarmdecoder/translations/ko.json | 5 ++- .../components/almond/translations/ko.json | 7 ++-- .../ambiclimate/translations/ko.json | 8 +++-- .../ambient_station/translations/ko.json | 4 +-- .../components/apple_tv/translations/ko.json | 23 +++++++++++++ .../components/arcam_fmj/translations/ko.json | 3 +- .../components/asuswrt/translations/ko.json | 24 +++++++++++++ .../components/atag/translations/ko.json | 5 +-- .../components/august/translations/ko.json | 5 +-- .../components/aurora/translations/ko.json | 16 +++++++++ .../components/awair/translations/ko.json | 8 +++-- .../components/axis/translations/ko.json | 6 ++-- .../azure_devops/translations/ko.json | 12 +++++++ .../components/blebox/translations/ko.json | 6 ++-- .../components/blink/translations/ko.json | 4 +-- .../bmw_connected_drive/translations/ko.json | 19 +++++++++++ .../components/bond/translations/ko.json | 6 ++++ .../components/braviatv/translations/ko.json | 9 +++-- .../components/broadlink/translations/ko.json | 21 +++++++----- .../components/brother/translations/ko.json | 3 +- .../components/bsblan/translations/ko.json | 7 +++- .../components/canary/translations/ko.json | 10 +++--- .../components/cast/translations/ko.json | 6 ++-- .../cert_expiry/translations/ko.json | 2 +- .../cloudflare/translations/ko.json | 19 +++++++++++ .../coolmaster/translations/ko.json | 1 + .../coronavirus/translations/ko.json | 2 +- .../components/daikin/translations/ko.json | 8 ++++- .../components/deconz/translations/ko.json | 2 +- .../components/denonavr/translations/ko.json | 4 +-- .../devolo_home_control/translations/ko.json | 11 +++--- .../components/dexcom/translations/ko.json | 5 +++ .../dialogflow/translations/ko.json | 4 +++ .../components/doorbird/translations/ko.json | 2 +- .../components/dsmr/translations/ko.json | 2 +- .../components/dunehd/translations/ko.json | 2 +- .../components/eafm/translations/ko.json | 1 + .../components/ecobee/translations/ko.json | 3 ++ .../components/econet/translations/ko.json | 21 ++++++++++++ .../components/elgato/translations/ko.json | 6 +++- .../components/elkm1/translations/ko.json | 2 +- .../emulated_roku/translations/ko.json | 7 ++-- .../components/epson/translations/ko.json | 16 +++++++++ .../components/esphome/translations/ko.json | 5 +-- .../fireservicerota/translations/ko.json | 27 +++++++++++++++ .../components/firmata/translations/ko.json | 2 +- .../flick_electric/translations/ko.json | 4 +-- .../components/flo/translations/ko.json | 12 ++++--- .../components/flume/translations/ko.json | 4 +-- .../flunearyou/translations/ko.json | 5 ++- .../forked_daapd/translations/ko.json | 2 +- .../components/foscam/translations/ko.json | 22 ++++++++++++ .../components/freebox/translations/ko.json | 6 ++-- .../components/fritzbox/translations/ko.json | 17 ++++++++-- .../fritzbox_callmonitor/translations/ko.json | 21 ++++++++++++ .../garmin_connect/translations/ko.json | 4 +-- .../components/gdacs/translations/ko.json | 2 +- .../components/geofency/translations/ko.json | 4 +++ .../geonetnz_quakes/translations/ko.json | 2 +- .../geonetnz_volcano/translations/ko.json | 3 ++ .../components/gios/translations/ko.json | 6 ++-- .../components/glances/translations/ko.json | 8 ++--- .../components/goalzero/translations/ko.json | 20 +++++++++++ .../components/gogogate2/translations/ko.json | 2 +- .../components/gpslogger/translations/ko.json | 4 +++ .../components/gree/translations/ko.json | 13 +++++++ .../components/griddy/translations/ko.json | 4 +-- .../components/guardian/translations/ko.json | 5 +-- .../components/habitica/translations/cs.json | 16 +++++++++ .../components/habitica/translations/it.json | 20 +++++++++++ .../components/habitica/translations/ko.json | 17 ++++++++++ .../components/hangouts/translations/ko.json | 4 +-- .../components/harmony/translations/ko.json | 2 +- .../components/heos/translations/ko.json | 6 ++++ .../hisense_aehw4a1/translations/ko.json | 4 +-- .../components/hlk_sw16/translations/ko.json | 21 ++++++++++++ .../home_connect/translations/ko.json | 6 ++-- .../components/homekit/translations/ko.json | 34 +++++++++++++------ .../homekit_controller/translations/ko.json | 12 +++---- .../homematicip_cloud/translations/ko.json | 10 +++--- .../huawei_lte/translations/ko.json | 7 ++-- .../components/hue/translations/ko.json | 10 +++--- .../huisbaasje/translations/ko.json | 21 ++++++++++++ .../translations/ko.json | 2 +- .../hvv_departures/translations/ko.json | 2 +- .../components/hyperion/translations/ko.json | 22 ++++++++++++ .../components/iaqualink/translations/ko.json | 6 ++++ .../components/icloud/translations/ko.json | 14 ++++++-- .../components/ifttt/translations/ko.json | 4 +++ .../components/insteon/translations/ko.json | 20 +++++++---- .../components/ios/translations/ko.json | 4 +-- .../components/ipp/translations/ko.json | 6 ++-- .../components/iqvia/translations/ko.json | 2 +- .../islamic_prayer_times/translations/ko.json | 3 ++ .../components/izone/translations/ko.json | 4 +-- .../components/juicenet/translations/ko.json | 6 ++-- .../keenetic_ndms2/translations/cs.json | 21 ++++++++++++ .../keenetic_ndms2/translations/ko.json | 30 ++++++++++++++++ .../components/kodi/translations/ko.json | 26 ++++++++++---- .../components/konnected/translations/ko.json | 12 +++---- .../components/kulersky/translations/ko.json | 13 +++++++ .../components/life360/translations/ko.json | 9 ++++- .../components/lifx/translations/ko.json | 4 +-- .../components/local_ip/translations/ko.json | 3 +- .../components/locative/translations/ko.json | 6 +++- .../logi_circle/translations/ko.json | 8 +++-- .../components/luftdaten/translations/ko.json | 2 ++ .../lutron_caseta/translations/ko.json | 11 ++++-- .../components/lyric/translations/ko.json | 16 +++++++++ .../components/mailgun/translations/ko.json | 4 +++ .../components/mazda/translations/cs.json | 6 ++-- .../components/mazda/translations/ko.json | 27 +++++++++++++++ .../components/melcloud/translations/ko.json | 2 +- .../components/met/translations/ko.json | 3 ++ .../meteo_france/translations/ko.json | 4 +-- .../components/metoffice/translations/ko.json | 4 +-- .../components/mikrotik/translations/ko.json | 5 +-- .../components/mill/translations/ko.json | 5 ++- .../minecraft_server/translations/ko.json | 2 +- .../components/monoprice/translations/ko.json | 2 +- .../motion_blinds/translations/ko.json | 27 +++++++++++++++ .../components/mqtt/translations/ko.json | 7 ++-- .../components/myq/translations/ko.json | 4 +-- .../components/mysensors/translations/cs.json | 5 ++- .../components/mysensors/translations/ko.json | 16 +++++++++ .../components/neato/translations/ko.json | 19 +++++++++-- .../components/nest/translations/ko.json | 23 ++++++++++--- .../components/netatmo/translations/ko.json | 3 +- .../components/nexia/translations/ko.json | 4 +-- .../nightscout/translations/ko.json | 12 ++++++- .../components/notion/translations/ko.json | 3 +- .../components/nuheat/translations/ko.json | 4 +-- .../components/nuki/translations/ko.json | 18 ++++++++++ .../components/nut/translations/ko.json | 2 +- .../components/nws/translations/ko.json | 6 ++-- .../components/nzbget/translations/ko.json | 14 ++++---- .../components/omnilogic/translations/ko.json | 12 +++---- .../ondilo_ico/translations/ko.json | 16 +++++++++ .../components/onewire/translations/ko.json | 18 ++++++++++ .../components/onvif/translations/ko.json | 7 ++-- .../opentherm_gw/translations/ko.json | 3 +- .../components/openuv/translations/ko.json | 2 +- .../openweathermap/translations/ko.json | 10 ++++-- .../ovo_energy/translations/ko.json | 13 ++++++- .../components/owntracks/translations/ko.json | 3 ++ .../components/ozw/translations/ko.json | 12 ++++++- .../panasonic_viera/translations/ko.json | 10 +++--- .../philips_js/translations/ko.json | 18 ++++++++++ .../components/pi_hole/translations/ko.json | 9 +++-- .../components/plaato/translations/ko.json | 11 ++++-- .../components/plex/translations/ko.json | 9 ++--- .../components/plugwise/translations/ko.json | 16 ++++++--- .../plum_lightpad/translations/ko.json | 2 +- .../components/point/translations/ko.json | 5 +-- .../components/poolsense/translations/ko.json | 2 +- .../components/powerwall/translations/it.json | 2 +- .../components/powerwall/translations/ko.json | 9 +++-- .../components/profiler/translations/ko.json | 12 +++++++ .../progettihwsw/translations/ko.json | 6 ++-- .../components/ps4/translations/ko.json | 14 ++++---- .../pvpc_hourly_pricing/translations/ko.json | 2 +- .../components/rachio/translations/ko.json | 6 ++-- .../rainmachine/translations/ko.json | 5 ++- .../recollect_waste/translations/ko.json | 7 ++++ .../components/rfxtrx/translations/ko.json | 25 +++++++++++++- .../components/risco/translations/ko.json | 19 ++++++++--- .../translations/ca.json | 21 ++++++++++++ .../translations/it.json | 21 ++++++++++++ .../components/roku/translations/ko.json | 1 + .../components/roomba/translations/ko.json | 21 +++++++++++- .../components/roon/translations/ko.json | 2 +- .../components/rpi_power/translations/ko.json | 2 +- .../ruckus_unleashed/translations/ko.json | 21 ++++++++++++ .../components/samsungtv/translations/ko.json | 5 +-- .../components/sense/translations/ko.json | 2 +- .../components/sentry/translations/ko.json | 3 ++ .../components/sharkiq/translations/ko.json | 21 ++++++------ .../components/shelly/translations/ko.json | 14 ++++++-- .../shopping_list/translations/ko.json | 8 ++--- .../simplisafe/translations/ko.json | 13 +++++-- .../components/smappee/translations/ko.json | 7 ++-- .../components/smarthab/translations/ko.json | 4 ++- .../components/smarttub/translations/cs.json | 21 ++++++++++++ .../components/smarttub/translations/it.json | 22 ++++++++++++ .../components/smarttub/translations/ko.json | 20 +++++++++++ .../components/solaredge/translations/ko.json | 3 ++ .../components/solarlog/translations/ko.json | 2 +- .../components/soma/translations/ko.json | 2 +- .../components/somfy/translations/ko.json | 12 +++---- .../somfy_mylink/translations/ko.json | 25 ++++++++++++++ .../components/sonarr/translations/ko.json | 8 +++-- .../components/sonos/translations/ko.json | 4 +-- .../speedtestdotnet/translations/ko.json | 3 +- .../components/spider/translations/ko.json | 15 ++++++-- .../components/spotify/translations/ko.json | 6 ++-- .../srp_energy/translations/ko.json | 20 +++++++++++ .../synology_dsm/translations/ko.json | 16 +++++---- .../components/tado/translations/ko.json | 2 +- .../components/tasmota/translations/ko.json | 7 ++++ .../tellduslive/translations/ko.json | 12 ++++--- .../components/tesla/translations/ko.json | 9 +++++ .../components/tibber/translations/ko.json | 3 +- .../components/tile/translations/ko.json | 5 ++- .../components/toon/translations/ko.json | 5 +-- .../totalconnect/translations/ko.json | 5 ++- .../components/tplink/translations/ko.json | 4 +-- .../components/traccar/translations/ko.json | 6 +++- .../components/tradfri/translations/ko.json | 6 ++-- .../transmission/translations/ko.json | 5 +-- .../components/tuya/translations/it.json | 2 ++ .../components/tuya/translations/ko.json | 10 ++++++ .../twentemilieu/translations/ko.json | 4 +++ .../components/twilio/translations/ko.json | 6 +++- .../components/twinkly/translations/ko.json | 10 ++++++ .../components/unifi/translations/ko.json | 5 +-- .../components/upb/translations/ko.json | 7 ++-- .../components/upcloud/translations/ko.json | 16 +++++++++ .../components/upnp/translations/ko.json | 4 +-- .../components/velbus/translations/ko.json | 7 ++++ .../components/vera/translations/ko.json | 4 +-- .../components/vesync/translations/ko.json | 6 ++++ .../components/vilfo/translations/ko.json | 8 ++--- .../components/vizio/translations/ko.json | 3 +- .../components/volumio/translations/ko.json | 19 +++++++++++ .../components/wemo/translations/ko.json | 4 +-- .../components/wiffi/translations/ko.json | 2 +- .../components/wilight/translations/ko.json | 1 + .../components/withings/translations/ko.json | 11 +++--- .../components/wled/translations/ko.json | 6 +++- .../wolflink/translations/sensor.ko.json | 3 +- .../components/xbox/translations/ko.json | 17 ++++++++++ .../xiaomi_aqara/translations/ko.json | 9 ++--- .../xiaomi_miio/translations/cs.json | 6 ++++ .../xiaomi_miio/translations/it.json | 12 ++++++- .../xiaomi_miio/translations/ko.json | 11 ++++-- .../components/yeelight/translations/ko.json | 6 ++-- .../components/zha/translations/ko.json | 4 +-- .../zoneminder/translations/ko.json | 14 +++++--- .../components/zwave/translations/ko.json | 5 +-- .../components/zwave_js/translations/ko.json | 30 ++++++++++++++++ 252 files changed, 1910 insertions(+), 406 deletions(-) create mode 100644 homeassistant/components/advantage_air/translations/ko.json create mode 100644 homeassistant/components/aemet/translations/ko.json create mode 100644 homeassistant/components/airnow/translations/ko.json create mode 100644 homeassistant/components/apple_tv/translations/ko.json create mode 100644 homeassistant/components/asuswrt/translations/ko.json create mode 100644 homeassistant/components/aurora/translations/ko.json create mode 100644 homeassistant/components/azure_devops/translations/ko.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/ko.json create mode 100644 homeassistant/components/cloudflare/translations/ko.json create mode 100644 homeassistant/components/econet/translations/ko.json create mode 100644 homeassistant/components/epson/translations/ko.json create mode 100644 homeassistant/components/fireservicerota/translations/ko.json create mode 100644 homeassistant/components/foscam/translations/ko.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/ko.json create mode 100644 homeassistant/components/goalzero/translations/ko.json create mode 100644 homeassistant/components/gree/translations/ko.json create mode 100644 homeassistant/components/habitica/translations/cs.json create mode 100644 homeassistant/components/habitica/translations/it.json create mode 100644 homeassistant/components/habitica/translations/ko.json create mode 100644 homeassistant/components/hlk_sw16/translations/ko.json create mode 100644 homeassistant/components/huisbaasje/translations/ko.json create mode 100644 homeassistant/components/hyperion/translations/ko.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/cs.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/ko.json create mode 100644 homeassistant/components/kulersky/translations/ko.json create mode 100644 homeassistant/components/lyric/translations/ko.json create mode 100644 homeassistant/components/mazda/translations/ko.json create mode 100644 homeassistant/components/motion_blinds/translations/ko.json create mode 100644 homeassistant/components/mysensors/translations/ko.json create mode 100644 homeassistant/components/nuki/translations/ko.json create mode 100644 homeassistant/components/ondilo_ico/translations/ko.json create mode 100644 homeassistant/components/onewire/translations/ko.json create mode 100644 homeassistant/components/philips_js/translations/ko.json create mode 100644 homeassistant/components/profiler/translations/ko.json create mode 100644 homeassistant/components/recollect_waste/translations/ko.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/ca.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/it.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/ko.json create mode 100644 homeassistant/components/smarttub/translations/cs.json create mode 100644 homeassistant/components/smarttub/translations/it.json create mode 100644 homeassistant/components/smarttub/translations/ko.json create mode 100644 homeassistant/components/somfy_mylink/translations/ko.json create mode 100644 homeassistant/components/srp_energy/translations/ko.json create mode 100644 homeassistant/components/tasmota/translations/ko.json create mode 100644 homeassistant/components/twinkly/translations/ko.json create mode 100644 homeassistant/components/upcloud/translations/ko.json create mode 100644 homeassistant/components/volumio/translations/ko.json create mode 100644 homeassistant/components/xbox/translations/ko.json create mode 100644 homeassistant/components/zwave_js/translations/ko.json diff --git a/homeassistant/components/abode/translations/ko.json b/homeassistant/components/abode/translations/ko.json index 06c301550b4fea..a9756447adf22f 100644 --- a/homeassistant/components/abode/translations/ko.json +++ b/homeassistant/components/abode/translations/ko.json @@ -1,9 +1,20 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 Abode \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc774\uba54\uc77c" + } + }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", diff --git a/homeassistant/components/accuweather/translations/ko.json b/homeassistant/components/accuweather/translations/ko.json index b04778c8cb2aa9..2f0a01e094b062 100644 --- a/homeassistant/components/accuweather/translations/ko.json +++ b/homeassistant/components/accuweather/translations/ko.json @@ -1,9 +1,29 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\uc774\ub984" + }, "description": "\uad6c\uc131\uc5d0 \ub300\ud55c \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 \ub2e4\uc74c\uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694:\nhttps://www.home-assistant.io/integrations/accuweather/\n\n\uc77c\ubd80 \uc13c\uc11c\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc5f0\ub3d9 \uad6c\uc131 \ud6c4 \uad6c\uc131\uc694\uc18c \ub808\uc9c0\uc2a4\ud2b8\ub9ac\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\uc77c\uae30\uc608\ubcf4\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc5f0\ub3d9 \uc635\uc158\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." } } + }, + "options": { + "step": { + "user": { + "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/ko.json b/homeassistant/components/acmeda/translations/ko.json index 345628eef02a62..098d3a952f5516 100644 --- a/homeassistant/components/acmeda/translations/ko.json +++ b/homeassistant/components/acmeda/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index e17bca1f0a2509..b14e627ca56aab 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -2,7 +2,10 @@ "config": { "abort": { "existing_instance_updated": "\uae30\uc874 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 AdGuard Home \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "hassio_confirm": { @@ -14,9 +17,9 @@ "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", - "ssl": "AdGuard Home \uc740 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4", + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", - "verify_ssl": "AdGuard Home \uc740 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4" + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "description": "\ubaa8\ub2c8\ud130\ub9c1 \ubc0f \uc81c\uc5b4\uac00 \uac00\ub2a5\ud558\ub3c4\ub85d AdGuard Home \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694." } diff --git a/homeassistant/components/advantage_air/translations/ko.json b/homeassistant/components/advantage_air/translations/ko.json new file mode 100644 index 00000000000000..444d8d3828550e --- /dev/null +++ b/homeassistant/components/advantage_air/translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/cs.json b/homeassistant/components/aemet/translations/cs.json index d31920a87459f1..d892d4c6dc37ad 100644 --- a/homeassistant/components/aemet/translations/cs.json +++ b/homeassistant/components/aemet/translations/cs.json @@ -11,7 +11,8 @@ "data": { "api_key": "Kl\u00ed\u010d API", "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", - "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka" + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", + "name": "N\u00e1zev integrace" } } } diff --git a/homeassistant/components/aemet/translations/ko.json b/homeassistant/components/aemet/translations/ko.json new file mode 100644 index 00000000000000..edfb023a88b060 --- /dev/null +++ b/homeassistant/components/aemet/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" + }, + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/ko.json b/homeassistant/components/agent_dvr/translations/ko.json index 30b96c00b636b9..add0b91710089b 100644 --- a/homeassistant/components/agent_dvr/translations/ko.json +++ b/homeassistant/components/agent_dvr/translations/ko.json @@ -4,7 +4,8 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/airly/translations/ko.json b/homeassistant/components/airly/translations/ko.json index a0b20ed8c443b2..95981ea5eb1f76 100644 --- a/homeassistant/components/airly/translations/ko.json +++ b/homeassistant/components/airly/translations/ko.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 \uc88c\ud45c\uc5d0 \ub300\ud55c Airly \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "wrong_location": "\uc774 \uc9c0\uc5ed\uc5d0\ub294 Airly \uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc774 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { @@ -12,7 +13,7 @@ "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4", - "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984" + "name": "\uc774\ub984" }, "description": "Airly \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://developer.airly.eu/register \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", "title": "Airly" diff --git a/homeassistant/components/airnow/translations/ko.json b/homeassistant/components/airnow/translations/ko.json new file mode 100644 index 00000000000000..6da62dffa2ca5f --- /dev/null +++ b/homeassistant/components/airnow/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/cs.json b/homeassistant/components/airvisual/translations/cs.json index 0ff97bbacfeff5..75720b0f30b7dd 100644 --- a/homeassistant/components/airvisual/translations/cs.json +++ b/homeassistant/components/airvisual/translations/cs.json @@ -26,7 +26,9 @@ }, "geography_by_name": { "data": { - "api_key": "Kl\u00ed\u010d API" + "api_key": "Kl\u00ed\u010d API", + "city": "M\u011bsto", + "country": "Zem\u011b" } }, "node_pro": { diff --git a/homeassistant/components/airvisual/translations/ko.json b/homeassistant/components/airvisual/translations/ko.json index d25df4c213f4ec..8cf450e597b29c 100644 --- a/homeassistant/components/airvisual/translations/ko.json +++ b/homeassistant/components/airvisual/translations/ko.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "\uc88c\ud45c\uac12 \ub610\ub294 Node/Pro ID \uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "Node/Pro ID \uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uac70\ub098 \uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "general_error": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "general_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "geography": { @@ -17,14 +19,31 @@ "description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API \ub97c \uc0ac\uc6a9\ud558\uc5ec \uc9c0\ub9ac\uc801 \uc704\uce58\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", "title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30" }, + "geography_by_coords": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4" + } + }, + "geography_by_name": { + "data": { + "api_key": "API \ud0a4" + } + }, "node_pro": { "data": { - "ip_address": "\uae30\uae30 IP \uc8fc\uc18c/\ud638\uc2a4\ud2b8 \uc774\ub984", + "ip_address": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638" }, "description": "\uc0ac\uc6a9\uc790\uc758 AirVisual \uae30\uae30\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4. \uae30\uae30\uc758 UI \uc5d0\uc11c \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "AirVisual Node/Pro \uad6c\uc131\ud558\uae30" }, + "reauth_confirm": { + "data": { + "api_key": "API \ud0a4" + } + }, "user": { "data": { "cloud_api": "\uc9c0\ub9ac\uc801 \uc704\uce58", diff --git a/homeassistant/components/alarmdecoder/translations/ko.json b/homeassistant/components/alarmdecoder/translations/ko.json index ed29a3260efd22..08383d37151b71 100644 --- a/homeassistant/components/alarmdecoder/translations/ko.json +++ b/homeassistant/components/alarmdecoder/translations/ko.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "create_entry": { "default": "AlarmDecoder\uc5d0 \uc131\uacf5\uc801\uc73c\ub85c \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "protocol": { "data": { diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index eff796699e3578..062ef885c7050e 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/ambiclimate/translations/ko.json b/homeassistant/components/ambiclimate/translations/ko.json index 2a5e9280aa722c..c28affbebb3d52 100644 --- a/homeassistant/components/ambiclimate/translations/ko.json +++ b/homeassistant/components/ambiclimate/translations/ko.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070 \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070 \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." }, "create_entry": { - "default": "Ambi Climate \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", @@ -12,7 +14,7 @@ }, "step": { "auth": { - "description": "[\ub9c1\ud06c]({authorization_url}) \ub97c \ud074\ub9ad\ud558\uc5ec Ambi Climate \uacc4\uc815\uc5d0 \ub300\ud574 **\ud5c8\uc6a9**\ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694. \n(\ucf5c\ubc31 url \uc744 {cb_url} \ub85c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", + "description": "[\ub9c1\ud06c]({authorization_url}) \ub97c \ud074\ub9ad\ud558\uc5ec Ambiclimate \uacc4\uc815\uc5d0 \ub300\ud574 **\ud5c8\uc6a9**\ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n(\ucf5c\ubc31 URL \uc774 {cb_url} \ub85c \uc9c0\uc815\ub418\uc5c8\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", "title": "Ambi Climate \uc778\uc99d\ud558\uae30" } } diff --git a/homeassistant/components/ambient_station/translations/ko.json b/homeassistant/components/ambient_station/translations/ko.json index d4e227656c296b..6fc8f4b17fc72d 100644 --- a/homeassistant/components/ambient_station/translations/ko.json +++ b/homeassistant/components/ambient_station/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 \uc571 \ud0a4\ub294 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_key": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_devices": "\uacc4\uc815\uc5d0 \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/apple_tv/translations/ko.json b/homeassistant/components/apple_tv/translations/ko.json new file mode 100644 index 00000000000000..c7e664b0638a0e --- /dev/null +++ b/homeassistant/components/apple_tv/translations/ko.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pair_with_pin": { + "data": { + "pin": "PIN \ucf54\ub4dc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ko.json b/homeassistant/components/arcam_fmj/translations/ko.json index 62b5a54928ef35..532e5ef4c5f51c 100644 --- a/homeassistant/components/arcam_fmj/translations/ko.json +++ b/homeassistant/components/arcam_fmj/translations/ko.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Arcam FMJ: {host}", "step": { diff --git a/homeassistant/components/asuswrt/translations/ko.json b/homeassistant/components/asuswrt/translations/ko.json new file mode 100644 index 00000000000000..de3de06e6b1bf7 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/ko.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "mode": "\ubaa8\ub4dc", + "name": "\uc774\ub984", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/ko.json b/homeassistant/components/atag/translations/ko.json index c09b4f7b249432..9b0c1ea1b3665e 100644 --- a/homeassistant/components/atag/translations/ko.json +++ b/homeassistant/components/atag/translations/ko.json @@ -1,15 +1,16 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 HomeAssistant \uc5d0 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unauthorized": "\ud398\uc5b4\ub9c1\uc774 \uac70\ubd80\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc778\uc99d \uc694\uccad \uae30\uae30\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" }, "step": { "user": { "data": { - "email": "\uc774\uba54\uc77c (\uc120\ud0dd \uc0ac\ud56d)", + "email": "\uc774\uba54\uc77c", "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, diff --git a/homeassistant/components/august/translations/ko.json b/homeassistant/components/august/translations/ko.json index 52f939c45a0a76..e7aed3d4c2c24d 100644 --- a/homeassistant/components/august/translations/ko.json +++ b/homeassistant/components/august/translations/ko.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/aurora/translations/ko.json b/homeassistant/components/aurora/translations/ko.json new file mode 100644 index 00000000000000..ea10c059f03d32 --- /dev/null +++ b/homeassistant/components/aurora/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/ko.json b/homeassistant/components/awair/translations/ko.json index 977532de45defa..22677f8ab45fbe 100644 --- a/homeassistant/components/awair/translations/ko.json +++ b/homeassistant/components/awair/translations/ko.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "reauth_successful": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc131\uacf5\uc801\uc73c\ub85c \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "unknown": "\uc54c \uc218 \uc5c6\ub294 Awair API \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + "invalid_access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "reauth": { diff --git a/homeassistant/components/axis/translations/ko.json b/homeassistant/components/axis/translations/ko.json index f73d467fbf774f..d9e0114a97a9aa 100644 --- a/homeassistant/components/axis/translations/ko.json +++ b/homeassistant/components/axis/translations/ko.json @@ -7,9 +7,11 @@ }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, - "flow_title": "Axis \uae30\uae30: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/azure_devops/translations/ko.json b/homeassistant/components/azure_devops/translations/ko.json new file mode 100644 index 00000000000000..555c548a14277e --- /dev/null +++ b/homeassistant/components/azure_devops/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/ko.json b/homeassistant/components/blebox/translations/ko.json index ff3fa740092a9f..81c7bf3af481eb 100644 --- a/homeassistant/components/blebox/translations/ko.json +++ b/homeassistant/components/blebox/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "BleBox \uae30\uae30\uac00 {address} \ub85c \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_configured": "BleBox \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "BleBox \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. (\ub85c\uadf8\uc5d0\uc11c \uc624\ub958 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\ubcf4\uc138\uc694.)", - "unknown": "BleBox \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. (\ub85c\uadf8\uc5d0\uc11c \uc624\ub958 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\ubcf4\uc138\uc694.)", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "unsupported_version": "BleBox \uae30\uae30 \ud38c\uc6e8\uc5b4\uac00 \uc624\ub798\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\uc8fc\uc138\uc694." }, "flow_title": "BleBox \uae30\uae30: {name} ({host})", diff --git a/homeassistant/components/blink/translations/ko.json b/homeassistant/components/blink/translations/ko.json index ac8c96e4f2d7a5..35ef0cefdef4e9 100644 --- a/homeassistant/components/blink/translations/ko.json +++ b/homeassistant/components/blink/translations/ko.json @@ -4,8 +4,8 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_access_token": "\uc798\ubabb\ub41c \uc778\uc99d", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/bmw_connected_drive/translations/ko.json b/homeassistant/components/bmw_connected_drive/translations/ko.json new file mode 100644 index 00000000000000..9cc079cf1cdfa2 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/ko.json b/homeassistant/components/bond/translations/ko.json index 61576d7043166b..b44db53f7c8d5c 100644 --- a/homeassistant/components/bond/translations/ko.json +++ b/homeassistant/components/bond/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -8,6 +11,9 @@ "flow_title": "\ubcf8\ub4dc : {bond_id} ( {host} )", "step": { "confirm": { + "data": { + "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" + }, "description": "{bond_id} \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index 3a82c38f90448d..0bfb6b3f1b2a50 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -1,16 +1,19 @@ { "config": { "abort": { - "already_configured": "\uc774 TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_ip_control": "TV \uc5d0\uc11c IP \uc81c\uc5b4\uac00 \ube44\ud65c\uc131\ud654\ub418\uc5c8\uac70\ub098 TV \uac00 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \ub610\ub294 PIN \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unsupported_model": "\uc774 TV \ubaa8\ub378\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "step": { "authorize": { + "data": { + "pin": "PIN \ucf54\ub4dc" + }, "description": "Sony Bravia TV \uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nPIN \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc9c0 \uc54a\uc73c\uba74 TV \uc5d0\uc11c Home Assistant \ub97c \ub4f1\ub85d \ud574\uc81c\ud558\uc5ec\uc57c \ud569\ub2c8\ub2e4. Settings -> Network -> Remote device settings -> Unregister remote device \ub85c \uc774\ub3d9\ud558\uc5ec \ub4f1\ub85d\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", "title": "Sony Bravia TV \uc2b9\uc778\ud558\uae30" }, diff --git a/homeassistant/components/broadlink/translations/ko.json b/homeassistant/components/broadlink/translations/ko.json index b27391e11001bf..13cd17a8475de2 100644 --- a/homeassistant/components/broadlink/translations/ko.json +++ b/homeassistant/components/broadlink/translations/ko.json @@ -1,16 +1,17 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "\uc774 \uae30\uae30\uc5d0 \ub300\ud574 \uc774\ubbf8 \uc9c4\ud589\uc911\uc778 \uad6c\uc131\uc774 \uc788\uc2b5\ub2c8\ub2e4.", - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_host": "\uc798\ubabb\ub41c \ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "not_supported": "\uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uc7a5\uce58", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "{name} ({host} \uc758 {model})", "step": { @@ -18,6 +19,9 @@ "title": "\uc7a5\uce58\uc5d0 \uc778\uc99d" }, "finish": { + "data": { + "name": "\uc774\ub984" + }, "title": "\uc7a5\uce58 \uc774\ub984\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624" }, "reset": { @@ -27,11 +31,12 @@ "data": { "unlock": "\uc608" }, - "description": "\uc7a5\uce58\uac00 \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant\uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "{name} ({host} \uc758 {model}) \uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant \uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c (\uc635\uc158)" }, "user": { "data": { + "host": "\ud638\uc2a4\ud2b8", "timeout": "\uc81c\ud55c \uc2dc\uac04" }, "title": "\uc7a5\uce58\uc5d0 \uc5f0\uacb0" diff --git a/homeassistant/components/brother/translations/ko.json b/homeassistant/components/brother/translations/ko.json index a54aea7f108e21..47722afdae5df6 100644 --- a/homeassistant/components/brother/translations/ko.json +++ b/homeassistant/components/brother/translations/ko.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\uc774 \ud504\ub9b0\ud130\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unsupported_model": "\uc774 \ud504\ub9b0\ud130 \ubaa8\ub378\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "snmp_error": "SNMP \uc11c\ubc84\uac00 \uaebc\uc838 \uc788\uac70\ub098 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud504\ub9b0\ud130\uc785\ub2c8\ub2e4.", "wrong_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, diff --git a/homeassistant/components/bsblan/translations/ko.json b/homeassistant/components/bsblan/translations/ko.json index 41b421ff8174cb..85703c7eeb72c1 100644 --- a/homeassistant/components/bsblan/translations/ko.json +++ b/homeassistant/components/bsblan/translations/ko.json @@ -3,13 +3,18 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "flow_title": "BSB-Lan: {name}", "step": { "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "passkey": "\ud328\uc2a4\ud0a4 \ubb38\uc790\uc5f4", - "port": "\ud3ec\ud2b8" + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "Home Assistant \uc5d0 BSB-Lan \uae30\uae30 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", "title": "BSB-Lan \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" diff --git a/homeassistant/components/canary/translations/ko.json b/homeassistant/components/canary/translations/ko.json index 0b1d82bb20a418..d02344a90272c2 100644 --- a/homeassistant/components/canary/translations/ko.json +++ b/homeassistant/components/canary/translations/ko.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568.", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec \ubc1c\uc0dd" + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Canary: {name}", "step": { "user": { "data": { - "password": "\uc554\ud638", - "username": "\uc0ac\uc6a9\uc790\uba85" + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "title": "Canary\uc5d0 \uc5f0\uacb0" } diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index e57fceb77052ca..7011a61f7573ab 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "Google \uce90\uc2a4\ud2b8 \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Google \uce90\uc2a4\ud2b8\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "Google \uce90\uc2a4\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/cert_expiry/translations/ko.json b/homeassistant/components/cert_expiry/translations/ko.json index ee912a3369532b..8782776977195a 100644 --- a/homeassistant/components/cert_expiry/translations/ko.json +++ b/homeassistant/components/cert_expiry/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "import_failed": "\uad6c\uc131\uc5d0\uc11c \uac00\uc838\uc624\uae30 \uc2e4\ud328" }, "error": { diff --git a/homeassistant/components/cloudflare/translations/ko.json b/homeassistant/components/cloudflare/translations/ko.json new file mode 100644 index 00000000000000..d4f4eee49a8435 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_token": "API \ud1a0\ud070" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/ko.json b/homeassistant/components/coolmaster/translations/ko.json index 5d0636bddcd245..82b88394431c97 100644 --- a/homeassistant/components/coolmaster/translations/ko.json +++ b/homeassistant/components/coolmaster/translations/ko.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "no_units": "CoolMasterNet \ud638\uc2a4\ud2b8\uc5d0\uc11c HVAC \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { diff --git a/homeassistant/components/coronavirus/translations/ko.json b/homeassistant/components/coronavirus/translations/ko.json index 65eec9e8bb757f..873aca88e30af9 100644 --- a/homeassistant/components/coronavirus/translations/ko.json +++ b/homeassistant/components/coronavirus/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc774 \uad6d\uac00\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/ko.json b/homeassistant/components/daikin/translations/ko.json index 9c4a6c8d50c191..e87db9f29d34ee 100644 --- a/homeassistant/components/daikin/translations/ko.json +++ b/homeassistant/components/daikin/translations/ko.json @@ -4,13 +4,19 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { + "api_key": "API \ud0a4", "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nAPI \ud0a4 \ubc0f \ube44\ubc00\ubc88\ud638\ub294 BRP072Cxx \uc640 SKYFi \uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ub41c\ub2e4\ub294 \uc810\uc5d0 \uc720\uc758\ud558\uc138\uc694.", + "description": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nAPI \ud0a4 \ubc0f \ube44\ubc00\ubc88\ud638\ub294 BRP072Cxx \uc640 SKYFi \uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ub41c\ub2e4\ub294 \uc810\uc5d0 \uc720\uc758\ud574\uc8fc\uc138\uc694.", "title": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8 \uad6c\uc131\ud558\uae30" } } diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index 6c7dde04e31488..bd8aef75dd6cdc 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\ube0c\ub9ac\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "not_deconz_bridge": "deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc544\ub2d9\ub2c8\ub2e4", "updated_instance": "deCONZ \uc778\uc2a4\ud134\uc2a4\ub97c \uc0c8\ub85c\uc6b4 \ud638\uc2a4\ud2b8 \uc8fc\uc18c\ub85c \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/denonavr/translations/ko.json b/homeassistant/components/denonavr/translations/ko.json index f995b852a57fe9..71562ac53a4ec3 100644 --- a/homeassistant/components/denonavr/translations/ko.json +++ b/homeassistant/components/denonavr/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "Denon AVR \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "not_denonavr_manufacturer": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4. \ubc1c\uacac\ub41c \uc81c\uc870\uc0ac\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "not_denonavr_missing": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4. \uac80\uc0c9 \uc815\ubcf4\uac00 \uc644\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, @@ -17,7 +17,7 @@ }, "select": { "data": { - "select_host": "\ub9ac\uc2dc\ubc84 IP" + "select_host": "\ub9ac\uc2dc\ubc84 IP \uc8fc\uc18c" }, "description": "\ub9ac\uc2dc\ubc84 \uc5f0\uacb0\uc744 \ucd94\uac00\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694", "title": "\uc5f0\uacb0\ud560 \ub9ac\uc2dc\ubc84\ub97c \uc120\ud0dd\ud558\uae30" diff --git a/homeassistant/components/devolo_home_control/translations/ko.json b/homeassistant/components/devolo_home_control/translations/ko.json index 17d4fe28a569f0..f21122bff70d82 100644 --- a/homeassistant/components/devolo_home_control/translations/ko.json +++ b/homeassistant/components/devolo_home_control/translations/ko.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "\uc774 Home Control Central \uc720\ub2db\uc740 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { - "home_control_url": "Home Control URL", - "mydevolo_url": "mydevolo URL", + "home_control_url": "Home Control URL \uc8fc\uc18c", + "mydevolo_url": "mydevolo URL \uc8fc\uc18c", "password": "\ube44\ubc00\ubc88\ud638", - "username": "\uc774\uba54\uc77c \uc8fc\uc18c / devolo ID" + "username": "\uc774\uba54\uc77c / devolo ID" } } } diff --git a/homeassistant/components/dexcom/translations/ko.json b/homeassistant/components/dexcom/translations/ko.json index 35129cbfbdee9f..c3daac033564ee 100644 --- a/homeassistant/components/dexcom/translations/ko.json +++ b/homeassistant/components/dexcom/translations/ko.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/dialogflow/translations/ko.json b/homeassistant/components/dialogflow/translations/ko.json index 7afeb6da74c63d..2b1be9657b403e 100644 --- a/homeassistant/components/dialogflow/translations/ko.json +++ b/homeassistant/components/dialogflow/translations/ko.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/doorbird/translations/ko.json b/homeassistant/components/doorbird/translations/ko.json index 74057a94d26784..819b3b51d1010a 100644 --- a/homeassistant/components/doorbird/translations/ko.json +++ b/homeassistant/components/doorbird/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc774 DoorBird \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "link_local_address": "\ub85c\uceec \uc8fc\uc18c \uc5f0\uacb0\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "not_doorbird_device": "\uc774 \uae30\uae30\ub294 DoorBird \uac00 \uc544\ub2d9\ub2c8\ub2e4" }, diff --git a/homeassistant/components/dsmr/translations/ko.json b/homeassistant/components/dsmr/translations/ko.json index 9c8fbbe80a99cb..17dee71d640a46 100644 --- a/homeassistant/components/dsmr/translations/ko.json +++ b/homeassistant/components/dsmr/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc7a5\uce58\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" } } } \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/ko.json b/homeassistant/components/dunehd/translations/ko.json index 1ddcadf8350341..45a59b4d75b068 100644 --- a/homeassistant/components/dunehd/translations/ko.json +++ b/homeassistant/components/dunehd/translations/ko.json @@ -6,7 +6,7 @@ "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/eafm/translations/ko.json b/homeassistant/components/eafm/translations/ko.json index 4e7bfc9dc9363d..36af97756ee705 100644 --- a/homeassistant/components/eafm/translations/ko.json +++ b/homeassistant/components/eafm/translations/ko.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_stations": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158\uc774 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { diff --git a/homeassistant/components/ecobee/translations/ko.json b/homeassistant/components/ecobee/translations/ko.json index 8be4c28bfbbdc2..674b087620aaed 100644 --- a/homeassistant/components/ecobee/translations/ko.json +++ b/homeassistant/components/ecobee/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, "error": { "pin_request_failed": "ecobee \ub85c\ubd80\ud130 PIN \uc694\uccad\uc5d0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4; API \ud0a4\uac00 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "token_request_failed": "ecobee \ub85c\ubd80\ud130 \ud1a0\ud070 \uc694\uccad\uc5d0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4; \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/econet/translations/ko.json b/homeassistant/components/econet/translations/ko.json new file mode 100644 index 00000000000000..f5c1381b8b164a --- /dev/null +++ b/homeassistant/components/econet/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/ko.json b/homeassistant/components/elgato/translations/ko.json index d11b106e28b4a2..f2deb818431160 100644 --- a/homeassistant/components/elgato/translations/ko.json +++ b/homeassistant/components/elgato/translations/ko.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "Elgato Key Light \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Elgato Key Light: {serial_number}", "step": { diff --git a/homeassistant/components/elkm1/translations/ko.json b/homeassistant/components/elkm1/translations/ko.json index d074946b7ad067..fb8c22ba5b2283 100644 --- a/homeassistant/components/elkm1/translations/ko.json +++ b/homeassistant/components/elkm1/translations/ko.json @@ -5,7 +5,7 @@ "already_configured": "\uc774 \uc811\ub450\uc0ac\ub85c ElkM1 \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/emulated_roku/translations/ko.json b/homeassistant/components/emulated_roku/translations/ko.json index e9d1d134af2b0c..32b4202bc78d05 100644 --- a/homeassistant/components/emulated_roku/translations/ko.json +++ b/homeassistant/components/emulated_roku/translations/ko.json @@ -1,11 +1,14 @@ { "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { - "advertise_ip": "\uad11\uace0 IP", + "advertise_ip": "\uad11\uace0 IP \uc8fc\uc18c", "advertise_port": "\uad11\uace0 \ud3ec\ud2b8", - "host_ip": "\ud638\uc2a4\ud2b8 IP", + "host_ip": "\ud638\uc2a4\ud2b8 IP \uc8fc\uc18c", "listen_port": "\uc218\uc2e0 \ud3ec\ud2b8", "name": "\uc774\ub984", "upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ud560\ub2f9 (\ucc38/\uac70\uc9d3)" diff --git a/homeassistant/components/epson/translations/ko.json b/homeassistant/components/epson/translations/ko.json new file mode 100644 index 00000000000000..1ee9afdcf75ce3 --- /dev/null +++ b/homeassistant/components/epson/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984", + "port": "\ud3ec\ud2b8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/ko.json b/homeassistant/components/esphome/translations/ko.json index 82b9490757b7b8..18827f690248bb 100644 --- a/homeassistant/components/esphome/translations/ko.json +++ b/homeassistant/components/esphome/translations/ko.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "ESP \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "ESP \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4" }, "error": { "connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", diff --git a/homeassistant/components/fireservicerota/translations/ko.json b/homeassistant/components/fireservicerota/translations/ko.json new file mode 100644 index 00000000000000..f705fd9873c23a --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "reauth": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + } + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/ko.json b/homeassistant/components/firmata/translations/ko.json index 753a5851811001..b5b8d46f329cfe 100644 --- a/homeassistant/components/firmata/translations/ko.json +++ b/homeassistant/components/firmata/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "\uc124\uce58\ud558\ub294 \ub3d9\uc548 Firmata \ubcf4\ub4dc\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" } } } \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/ko.json b/homeassistant/components/flick_electric/translations/ko.json index 82d095f275512d..e5b69253fa7186 100644 --- a/homeassistant/components/flick_electric/translations/ko.json +++ b/homeassistant/components/flick_electric/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\ud574\ub2f9 \uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/flo/translations/ko.json b/homeassistant/components/flo/translations/ko.json index ab85b70afa76b7..9ba063c37ddf46 100644 --- a/homeassistant/components/flo/translations/ko.json +++ b/homeassistant/components/flo/translations/ko.json @@ -1,17 +1,19 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { - "host": "\ud638\uc2a4\ud2b8" + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } diff --git a/homeassistant/components/flume/translations/ko.json b/homeassistant/components/flume/translations/ko.json index faac5e9c579d32..b700854ab5708c 100644 --- a/homeassistant/components/flume/translations/ko.json +++ b/homeassistant/components/flume/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 \uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/flunearyou/translations/ko.json b/homeassistant/components/flunearyou/translations/ko.json index 68e65d3c349ae5..f6528e85ccac27 100644 --- a/homeassistant/components/flunearyou/translations/ko.json +++ b/homeassistant/components/flunearyou/translations/ko.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc88c\ud45c\uac12\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/forked_daapd/translations/ko.json b/homeassistant/components/forked_daapd/translations/ko.json index 5522eda3a766b0..5ae487a4096552 100644 --- a/homeassistant/components/forked_daapd/translations/ko.json +++ b/homeassistant/components/forked_daapd/translations/ko.json @@ -5,7 +5,7 @@ "not_forked_daapd": "\uae30\uae30\uac00 forked-daapd \uc11c\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4." }, "error": { - "unknown_error": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958", + "unknown_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "websocket_not_enabled": "forked-daapd \uc11c\ubc84 \uc6f9\uc18c\ucf13\uc774 \ube44\ud65c\uc131\ud654 \ub418\uc5b4\uc788\uc2b5\ub2c8\ub2e4.", "wrong_host_or_port": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", "wrong_password": "\ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/foscam/translations/ko.json b/homeassistant/components/foscam/translations/ko.json new file mode 100644 index 00000000000000..bfd8e952671238 --- /dev/null +++ b/homeassistant/components/foscam/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index 986f345b3ecc29..a8b9a1edc7aa49 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "register_failed": "\ub4f1\ub85d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "link": { diff --git a/homeassistant/components/fritzbox/translations/ko.json b/homeassistant/components/fritzbox/translations/ko.json index b04b6905284a16..dfdcc0ad4eb6f1 100644 --- a/homeassistant/components/fritzbox/translations/ko.json +++ b/homeassistant/components/fritzbox/translations/ko.json @@ -1,9 +1,14 @@ { "config": { "abort": { - "already_configured": "\uc774 AVM FRITZ!Box \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "AVM FRITZ!Box \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", - "not_supported": "AVM FRITZ!Box \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc2a4\ub9c8\ud2b8 \ud648 \uae30\uae30\ub97c \uc81c\uc5b4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "not_supported": "AVM FRITZ!Box \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc2a4\ub9c8\ud2b8 \ud648 \uae30\uae30\ub97c \uc81c\uc5b4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "flow_title": "AVM FRITZ!Box: {name}", "step": { @@ -14,6 +19,12 @@ }, "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ko.json b/homeassistant/components/fritzbox_callmonitor/translations/ko.json new file mode 100644 index 00000000000000..b8fd442cd03c43 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/ko.json b/homeassistant/components/garmin_connect/translations/ko.json index fee07e579fe974..4d5330a824f135 100644 --- a/homeassistant/components/garmin_connect/translations/ko.json +++ b/homeassistant/components/garmin_connect/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "too_many_requests": "\uc694\uccad\uc774 \ub108\ubb34 \ub9ce\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/gdacs/translations/ko.json b/homeassistant/components/gdacs/translations/ko.json index 1aeaf2192889e9..b91d512039ad66 100644 --- a/homeassistant/components/gdacs/translations/ko.json +++ b/homeassistant/components/gdacs/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/geofency/translations/ko.json b/homeassistant/components/geofency/translations/ko.json index 8bab38a8a34591..fd49c57e249df4 100644 --- a/homeassistant/components/geofency/translations/ko.json +++ b/homeassistant/components/geofency/translations/ko.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/geonetnz_quakes/translations/ko.json b/homeassistant/components/geonetnz_quakes/translations/ko.json index b231629e8567a7..277aa945792bcb 100644 --- a/homeassistant/components/geonetnz_quakes/translations/ko.json +++ b/homeassistant/components/geonetnz_quakes/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_volcano/translations/ko.json b/homeassistant/components/geonetnz_volcano/translations/ko.json index 26e83789e8f9a8..1aeaf2192889e9 100644 --- a/homeassistant/components/geonetnz_volcano/translations/ko.json +++ b/homeassistant/components/geonetnz_volcano/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/gios/translations/ko.json b/homeassistant/components/gios/translations/ko.json index 2ad64efadc1de6..7895dafe8ce8d0 100644 --- a/homeassistant/components/gios/translations/ko.json +++ b/homeassistant/components/gios/translations/ko.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "\uc774 \uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc5d0 \ub300\ud55c GIO\u015a \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "cannot_connect": "GIO\u015a \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_sensors_data": "\uc774 \uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc5d0 \ub300\ud55c \uc13c\uc11c \ub370\uc774\ud130\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "wrong_station_id": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc758 ID \uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "step": { "user": { "data": { - "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984", + "name": "\uc774\ub984", "station_id": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158\uc758 ID" }, "description": "\ud3f4\ub780\ub4dc \ud658\uacbd\uccad (GIO\u015a) \ub300\uae30\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 https://www.home-assistant.io/integrations/gios \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", diff --git a/homeassistant/components/glances/translations/ko.json b/homeassistant/components/glances/translations/ko.json index 336c9f3b3e5a1b..47f24d2edf12bf 100644 --- a/homeassistant/components/glances/translations/ko.json +++ b/homeassistant/components/glances/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\ud638\uc2a4\ud2b8\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "wrong_version": "\ud574\ub2f9 \ubc84\uc804\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4 (2 \ub610\ub294 3\ub9cc \uc9c0\uc6d0)" }, "step": { @@ -14,9 +14,9 @@ "name": "\uc774\ub984", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", - "ssl": "SSL/TLS \ub97c \uc0ac\uc6a9\ud558\uc5ec Glances \uc2dc\uc2a4\ud15c\uc5d0 \uc5f0\uacb0", + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", - "verify_ssl": "\uc2dc\uc2a4\ud15c \uc778\uc99d \ud655\uc778", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778", "version": "Glances API \ubc84\uc804 (2 \ub610\ub294 3)" }, "title": "Glances \uc124\uce58\ud558\uae30" diff --git a/homeassistant/components/goalzero/translations/ko.json b/homeassistant/components/goalzero/translations/ko.json new file mode 100644 index 00000000000000..f15f5827448670 --- /dev/null +++ b/homeassistant/components/goalzero/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/ko.json b/homeassistant/components/gogogate2/translations/ko.json index 55b32812bfacb5..dc37928db76fe2 100644 --- a/homeassistant/components/gogogate2/translations/ko.json +++ b/homeassistant/components/gogogate2/translations/ko.json @@ -15,7 +15,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "\uc544\ub798\uc5d0 \ud544\uc218 \uc815\ubcf4\ub97c \uc81c\uacf5\ud574\uc8fc\uc138\uc694.", - "title": "GogoGate2 \uc124\uce58\ud558\uae30" + "title": "GogoGate2 \ub610\ub294 iSmartGate \uc124\uce58\ud558\uae30" } } } diff --git a/homeassistant/components/gpslogger/translations/ko.json b/homeassistant/components/gpslogger/translations/ko.json index a6d95a0e51b200..e73d72c06b7a07 100644 --- a/homeassistant/components/gpslogger/translations/ko.json +++ b/homeassistant/components/gpslogger/translations/ko.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/gree/translations/ko.json b/homeassistant/components/gree/translations/ko.json new file mode 100644 index 00000000000000..7011a61f7573ab --- /dev/null +++ b/homeassistant/components/gree/translations/ko.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/ko.json b/homeassistant/components/griddy/translations/ko.json index a17db380aa01b1..df9178fab93c41 100644 --- a/homeassistant/components/griddy/translations/ko.json +++ b/homeassistant/components/griddy/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 \uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/guardian/translations/ko.json b/homeassistant/components/guardian/translations/ko.json index da40b674009bab..d9f70ad2d33ecb 100644 --- a/homeassistant/components/guardian/translations/ko.json +++ b/homeassistant/components/guardian/translations/ko.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "\uc774 Guardian \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "Guardian \uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/habitica/translations/cs.json b/homeassistant/components/habitica/translations/cs.json new file mode 100644 index 00000000000000..5ebfec2cf122ba --- /dev/null +++ b/homeassistant/components/habitica/translations/cs.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_credentials": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/it.json b/homeassistant/components/habitica/translations/it.json new file mode 100644 index 00000000000000..2bef21519b6581 --- /dev/null +++ b/homeassistant/components/habitica/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "api_user": "ID utente API di Habitica", + "name": "Sostituisci il nome utente di Habitica. Verr\u00e0 utilizzato per le chiamate di servizio", + "url": "URL" + }, + "description": "Collega il tuo profilo Habitica per consentire il monitoraggio del profilo e delle attivit\u00e0 dell'utente. Nota che api_id e api_key devono essere ottenuti da https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ko.json b/homeassistant/components/habitica/translations/ko.json new file mode 100644 index 00000000000000..3fd04a4477b08f --- /dev/null +++ b/homeassistant/components/habitica/translations/ko.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_credentials": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "url": "URL \uc8fc\uc18c" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/ko.json b/homeassistant/components/hangouts/translations/ko.json index 51bd857e3581cb..3c23effaf4fa93 100644 --- a/homeassistant/components/hangouts/translations/ko.json +++ b/homeassistant/components/hangouts/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google \ud589\uc544\uc6c3\uc740 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_2fa": "2\ub2e8\uacc4 \uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/harmony/translations/ko.json b/homeassistant/components/harmony/translations/ko.json index 528f5e9cc7e10b..026e751b788f45 100644 --- a/homeassistant/components/harmony/translations/ko.json +++ b/homeassistant/components/harmony/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Logitech Harmony Hub: {name}", diff --git a/homeassistant/components/heos/translations/ko.json b/homeassistant/components/heos/translations/ko.json index fc20a77d7b88f8..d17cbd0e4b71b5 100644 --- a/homeassistant/components/heos/translations/ko.json +++ b/homeassistant/components/heos/translations/ko.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/ko.json b/homeassistant/components/hisense_aehw4a1/translations/ko.json index 27d0ff88f6a029..491887280c03cd 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ko.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Hisense AEH-W4A1 \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Hisense AEH-W4A1 \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/hlk_sw16/translations/ko.json b/homeassistant/components/hlk_sw16/translations/ko.json new file mode 100644 index 00000000000000..9ba063c37ddf46 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/ko.json b/homeassistant/components/home_connect/translations/ko.json index 8d1f5554e7f880..425968d1460cd3 100644 --- a/homeassistant/components/home_connect/translations/ko.json +++ b/homeassistant/components/home_connect/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "missing_configuration": "Home Connect \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "create_entry": { - "default": "Home Connect \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/homekit/translations/ko.json b/homeassistant/components/homekit/translations/ko.json index 1b7276d41712f5..bc8e138fbaf3b6 100644 --- a/homeassistant/components/homekit/translations/ko.json +++ b/homeassistant/components/homekit/translations/ko.json @@ -4,17 +4,23 @@ "port_name_in_use": "\uc774\ub984\uc774\ub098 \ud3ec\ud2b8\uac00 \uac19\uc740 \ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { + "bridge_mode": { + "data": { + "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" + } + }, "pairing": { - "description": "{name} \ube0c\ub9ac\uc9c0\uac00 \uc900\ube44\ub418\uba74 \"\uc54c\ub9bc\"\uc5d0\uc11c \"HomeKit \ube0c\ub9ac\uc9c0 \uc124\uc815\"\uc73c\ub85c \ud398\uc5b4\ub9c1\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "HomeKit \ube0c\ub9ac\uc9c0 \ud398\uc5b4\ub9c1\ud558\uae30" + "description": "{name} \uc774(\uac00) \uc900\ube44\ub418\uba74 \"\uc54c\ub9bc\"\uc5d0\uc11c \"HomeKit \ube0c\ub9ac\uc9c0 \uc124\uc815\"\uc73c\ub85c \ud398\uc5b4\ub9c1\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "HomeKit \ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { "data": { "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (Z-Wave \ub610\ub294 \uae30\ud0c0 \uc9c0\uc5f0\ub41c \uc2dc\uc791 \uc2dc\uc2a4\ud15c\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", - "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" + "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", + "mode": "\ubaa8\ub4dc" }, - "description": "HomeKit \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\uba74 HomeKit \uc5d0\uc11c Home Assistant \uad6c\uc131\uc694\uc18c\uc5d0 \uc561\uc138\uc2a4\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. HomeKit \ube0c\ub9ac\uc9c0\ub294 \ube0c\ub9ac\uc9c0 \uc790\uccb4\ub97c \ud3ec\ud568\ud558\uc5ec \uc778\uc2a4\ud134\uc2a4\ub2f9 150\uac1c\uc758 \uc561\uc138\uc11c\ub9ac\ub85c \uc81c\ud55c\ub429\ub2c8\ub2e4. \ucd5c\ub300 \uc561\uc138\uc11c\ub9ac \uc218\ub97c \ucd08\uacfc\ud558\uc5ec \ube0c\ub9ac\uc9d5\ud558\ub824\uba74 \uc5ec\ub7ec \ub3c4\uba54\uc778\uc5d0 \ub300\ud574 \uc5ec\ub7ec \uac1c\uc758 \ud648\ud0b7 \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 \uae30\ubcf8 \ube0c\ub9ac\uc9c0\uc758 YAML \uc744 \ud1b5\ud574\uc11c\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "HomeKit \ube0c\ub9ac\uc9c0 \ud65c\uc131\ud654\ud558\uae30" + "description": "HomeKit \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \ud1b5\ud574 HomeKit \uc758 Home Assistant \uad6c\uc131\uc694\uc18c\uc5d0 \uc561\uc138\uc2a4\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \ubaa8\ub4dc\uc5d0\uc11c HomeKit \ube0c\ub9ac\uc9c0\ub294 \ube0c\ub9ac\uc9c0 \uc790\uccb4\ub97c \ud3ec\ud568\ud558\uc5ec \uc778\uc2a4\ud134\uc2a4\ub2f9 150 \uac1c\uc758 \uc561\uc138\uc11c\ub9ac\ub85c \uc81c\ud55c\ub429\ub2c8\ub2e4. \ucd5c\ub300 \uc561\uc138\uc11c\ub9ac \uac1c\uc218\ubcf4\ub2e4 \ub9ce\uc740 \uc218\uc758 \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub824\ub294 \uacbd\uc6b0, \uc11c\ub85c \ub2e4\ub978 \ub3c4\uba54\uc778\uc5d0 \ub300\ud574 \uc5ec\ub7ec\uac1c\uc758 HomeKit \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc758 \uc790\uc138\ud55c \uad6c\uc131\uc740 YAML \uc744 \ud1b5\ud574\uc11c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ucd5c\uc0c1\uc758 \uc131\ub2a5\uacfc \uc608\uae30\uce58 \uc54a\uc740 \uc0ac\uc6a9 \ubd88\uac00\ub2a5\ud55c \uc0c1\ud0dc\ub97c \ubc29\uc9c0\ud558\ub824\uba74 \uac01\uac01\uc758 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\uc5d0 \ub300\ud574 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c \ubcc4\ub3c4\uc758 HomeKit \uc778\uc2a4\ud134\uc2a4\ub97c \uc0dd\uc131\ud558\uace0 \ud398\uc5b4\ub9c1\ud574\uc8fc\uc138\uc694.", + "title": "HomeKit \ud65c\uc131\ud654\ud558\uae30" } } }, @@ -22,10 +28,10 @@ "step": { "advanced": { "data": { - "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (Z-Wave \ub610\ub294 \uae30\ud0c0 \uc9c0\uc5f0\ub41c \uc2dc\uc791 \uc2dc\uc2a4\ud15c\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", + "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (homekit.start \uc11c\ube44\uc2a4\ub97c \uc218\ub3d9\uc73c\ub85c \ud638\ucd9c\ud558\ub824\uba74 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", "safe_mode": "\uc548\uc804 \ubaa8\ub4dc (\ud398\uc5b4\ub9c1\uc774 \uc2e4\ud328\ud55c \uacbd\uc6b0\uc5d0\ub9cc \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)" }, - "description": "\uc774 \uc124\uc815\uc740 HomeKit \ube0c\ub9ac\uc9c0\uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "description": "\uc774 \uc124\uc815\uc740 HomeKit \uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "\uace0\uae09 \uad6c\uc131\ud558\uae30" }, "cameras": { @@ -35,16 +41,22 @@ "description": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \ubaa8\ub4e0 \uce74\uba54\ub77c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \uce74\uba54\ub77c\uac00 H.264 \uc2a4\ud2b8\ub9bc\uc744 \ucd9c\ub825\ud558\uc9c0 \uc54a\uc73c\uba74 \uc2dc\uc2a4\ud15c\uc740 \ube44\ub514\uc624\ub97c HomeKit \uc6a9 H.264 \ud3ec\ub9f7\uc73c\ub85c \ubcc0\ud658\uc2dc\ud0b5\ub2c8\ub2e4. \ud2b8\ub79c\uc2a4\ucf54\ub529 \ubcc0\ud658\uc5d0\ub294 \ub192\uc740 CPU \uc131\ub2a5\uc774 \ud544\uc694\ud558\uba70 Raspberry Pi \uc640 \uac19\uc740 \ub2e8\uc77c \ubcf4\ub4dc \ucef4\ud4e8\ud130\uc5d0\uc11c\ub294 \uc791\ub3d9\ud558\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\uce74\uba54\ub77c \ube44\ub514\uc624 \ucf54\ub371 \uc120\ud0dd\ud558\uae30" }, + "include_exclude": { + "data": { + "mode": "\ubaa8\ub4dc" + } + }, "init": { "data": { - "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" + "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", + "mode": "\ubaa8\ub4dc" }, - "description": "\"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\ub294 HomeKit \uc5d0 \uc5f0\uacb0\ub429\ub2c8\ub2e4. \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc758 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "\ube0c\ub9ac\uc9c0 \ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" + "description": "HomeKit \ub294 \ube0c\ub9ac\uc9c0 \ub610\ub294 \uc561\uc138\uc11c\ub9ac\ub97c \ub178\ucd9c\ud558\ub3c4\ub85d \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. TV \uae30\uae30 \ud074\ub798\uc2a4\uac00 \uc788\ub294 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uac00 \uc81c\ub300\ub85c \uc791\ub3d9\ud558\ub824\uba74 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\ub294 HomeKit \uc5d0 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc5d0 \ud3ec\ud568\ud558\uac70\ub098 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "yaml": { "description": "\uc774 \ud56d\ubaa9\uc740 YAML \uc744 \ud1b5\ud574 \uc81c\uc5b4\ub429\ub2c8\ub2e4", - "title": "HomeKit \ube0c\ub9ac\uc9c0 \uc635\uc158 \uc870\uc815\ud558\uae30" + "title": "HomeKit \uc635\uc158 \uc870\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/homekit_controller/translations/ko.json b/homeassistant/components/homekit_controller/translations/ko.json index 2c41447b5a0436..7314f43545ed93 100644 --- a/homeassistant/components/homekit_controller/translations/ko.json +++ b/homeassistant/components/homekit_controller/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "\uae30\uae30\ub97c \ub354 \uc774\uc0c1 \ucc3e\uc744 \uc218 \uc5c6\uc73c\ubbc0\ub85c \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "already_configured": "\uc561\uc138\uc11c\ub9ac\uac00 \ucee8\ud2b8\ub864\ub7ec\uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "already_paired": "\uc774 \uc561\uc138\uc11c\ub9ac\ub294 \uc774\ubbf8 \ub2e4\ub978 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac\ub97c \uc7ac\uc124\uc815\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "ignored_model": "\uc774 \ubaa8\ub378\uc5d0 \ub300\ud55c HomeKit \uc9c0\uc6d0\uc740 \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc81c\uacf5\ud558\ub294 \uae30\ubcf8 \uad6c\uc131\uc694\uc18c\ub85c \uc778\ud574 \ucc28\ub2e8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant \uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.", @@ -17,7 +17,7 @@ "unable_to_pair": "\ud398\uc5b4\ub9c1 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "unknown_error": "\uae30\uae30\uc5d0\uc11c \uc54c \uc218\uc5c6\ub294 \uc624\ub958\ub97c \ubcf4\uace0\ud588\uc2b5\ub2c8\ub2e4. \ud398\uc5b4\ub9c1\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4." }, - "flow_title": "HomeKit \uc561\uc138\uc11c\ub9ac: {name}", + "flow_title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud504\ub85c\ud1a0\ucf5c\uc744 \ud1b5\ud55c {name}", "step": { "busy_error": { "description": "\ubaa8\ub4e0 \ucee8\ud2b8\ub864\ub7ec\uc5d0\uc11c \ud398\uc5b4\ub9c1\uc744 \uc911\ub2e8\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", @@ -31,8 +31,8 @@ "data": { "pairing_code": "\ud398\uc5b4\ub9c1 \ucf54\ub4dc" }, - "description": "\uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc (XXX-XX-XXX \ud615\uc2dd) \ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1\ud558\uae30" + "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c {name} \uacfc(\uc640) \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc (XX-XX-XXX \ud615\uc2dd) \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774 \ucf54\ub4dc\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uae30\uae30\ub098 \ud3ec\uc7a5 \ubc15\uc2a4\uc5d0 \ud45c\uc2dc\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud504\ub85c\ud1a0\ucf5c\uc744 \ud1b5\ud574 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1 \ud558\uae30" }, "protocol_error": { "description": "\uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\uc9c0 \uc54a\uc744 \uc218 \uc788\uc73c\uba70 \ubb3c\ub9ac\uc801 \ub610\ub294 \uac00\uc0c1 \uc758 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc57c \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", @@ -42,8 +42,8 @@ "data": { "device": "\uae30\uae30" }, - "description": "\ud398\uc5b4\ub9c1 \ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1\ud558\uae30" + "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c \uae30\uae30\uc640 \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \ud398\uc5b4\ub9c1 \ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694:", + "title": "\uae30\uae30 \uc120\ud0dd\ud558\uae30" } } }, diff --git a/homeassistant/components/homematicip_cloud/translations/ko.json b/homeassistant/components/homematicip_cloud/translations/ko.json index b85b8ac00b1153..6a15b21de84c98 100644 --- a/homeassistant/components/homematicip_cloud/translations/ko.json +++ b/homeassistant/components/homematicip_cloud/translations/ko.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "connection_aborted": "HMIP \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "connection_aborted": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_sgtin_or_pin": "PIN\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "invalid_sgtin_or_pin": "\uc798\ubabb\ub41c SGTIN \uc774\uac70\ub098 PIN \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "press_the_button": "\ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", "register_failed": "\ub4f1\ub85d\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "timeout_button": "\uc815\ud574\uc9c4 \uc2dc\uac04\ub0b4\uc5d0 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub974\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." @@ -16,7 +16,7 @@ "data": { "hapid": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 ID (SGTIN)", "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc5b4\ub85c \uc0ac\uc6a9)", - "pin": "PIN \ucf54\ub4dc (\uc120\ud0dd\uc0ac\ud56d)" + "pin": "PIN \ucf54\ub4dc" }, "title": "HomematicIP \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc120\ud0dd\ud558\uae30" }, diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index 6469bf6a696a21..73274d15bfb90a 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -2,22 +2,25 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "not_huawei_lte": "\ud654\uc6e8\uc774 LTE \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { "connection_timeout": "\uc811\uc18d \uc2dc\uac04 \ucd08\uacfc", "incorrect_password": "\ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "incorrect_username": "\uc0ac\uc6a9\uc790 \uc774\ub984\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_url": "URL \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "login_attempts_exceeded": "\ucd5c\ub300 \ub85c\uadf8\uc778 \uc2dc\ub3c4 \ud69f\uc218\ub97c \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694", - "response_error": "\uae30\uae30\uc5d0\uc11c \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "response_error": "\uae30\uae30\uc5d0\uc11c \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Huawei LTE: {name}", "step": { "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", + "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant \uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/hue/translations/ko.json b/homeassistant/components/hue/translations/ko.json index 050b5c51c978e1..846ea937515105 100644 --- a/homeassistant/components/hue/translations/ko.json +++ b/homeassistant/components/hue/translations/ko.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "\ubaa8\ub4e0 \ud544\ub9bd\uc2a4 Hue \ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_configured": "\ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\ube0c\ub9ac\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", - "cannot_connect": "\ube0c\ub9ac\uc9c0\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "discover_timeout": "Hue \ube0c\ub9ac\uc9c0\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "no_bridges": "\ubc1c\uacac\ub41c \ud544\ub9bd\uc2a4 Hue \ube0c\ub9ac\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "not_hue_bridge": "Hue \ube0c\ub9ac\uc9c0\uac00 \uc544\ub2d9\ub2c8\ub2e4", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "linking": "\uc54c \uc218 \uc5c6\ub294 \uc5f0\uacb0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "linking": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "register_failed": "\ub4f1\ub85d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" }, "step": { diff --git a/homeassistant/components/huisbaasje/translations/ko.json b/homeassistant/components/huisbaasje/translations/ko.json new file mode 100644 index 00000000000000..bd25569d7c7236 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "connection_exception": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unauthenticated_exception": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/ko.json b/homeassistant/components/hunterdouglas_powerview/translations/ko.json index cba1b76168286d..d16945084d00f7 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/ko.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/hvv_departures/translations/ko.json b/homeassistant/components/hvv_departures/translations/ko.json index 41c7f44be7ff0d..ea6ef8bc23a885 100644 --- a/homeassistant/components/hvv_departures/translations/ko.json +++ b/homeassistant/components/hvv_departures/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_results": "\uacb0\uacfc\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\ub978 \uc2a4\ud14c\uc774\uc158\uc774\ub098 \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694" }, diff --git a/homeassistant/components/hyperion/translations/ko.json b/homeassistant/components/hyperion/translations/ko.json new file mode 100644 index 00000000000000..295d418da12e28 --- /dev/null +++ b/homeassistant/components/hyperion/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/ko.json b/homeassistant/components/iaqualink/translations/ko.json index 6396d5d250e954..1386480fca4d61 100644 --- a/homeassistant/components/iaqualink/translations/ko.json +++ b/homeassistant/components/iaqualink/translations/ko.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/icloud/translations/ko.json b/homeassistant/components/icloud/translations/ko.json index 045042362b7727..5e02fb02993702 100644 --- a/homeassistant/components/icloud/translations/ko.json +++ b/homeassistant/components/icloud/translations/ko.json @@ -1,14 +1,22 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "no_device": "\"\ub098\uc758 iPhone \ucc3e\uae30\"\uac00 \ud65c\uc131\ud654\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_device": "\"\ub098\uc758 iPhone \ucc3e\uae30\"\uac00 \ud65c\uc131\ud654\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "send_verification_code": "\uc778\uc99d \ucf54\ub4dc\ub97c \ubcf4\ub0b4\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "validate_verification_code": "\uc778\uc99d \ucf54\ub4dc\ub97c \ud655\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc2e0\ub8b0\ud560 \uc218 \uc788\ub294 \uae30\uae30\ub97c \uc120\ud0dd\ud558\uace0 \uc778\uc99d\uc744 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" + "validate_verification_code": "\uc778\uc99d \ucf54\ub4dc \ud655\uc778\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" }, "step": { + "reauth": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + }, "trusted_device": { "data": { "trusted_device": "\uc2e0\ub8b0\ud560 \uc218 \uc788\ub294 \uae30\uae30" diff --git a/homeassistant/components/ifttt/translations/ko.json b/homeassistant/components/ifttt/translations/ko.json index 93daad9e18267d..bc561027fc3451 100644 --- a/homeassistant/components/ifttt/translations/ko.json +++ b/homeassistant/components/ifttt/translations/ko.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/insteon/translations/ko.json b/homeassistant/components/insteon/translations/ko.json index 76ef956672537f..1cd65afc9e9c38 100644 --- a/homeassistant/components/insteon/translations/ko.json +++ b/homeassistant/components/insteon/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub428. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." }, "step": { @@ -20,9 +20,9 @@ "hubv2": { "data": { "host": "IP \uc8fc\uc18c", - "password": "\uc554\ud638", + "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", - "username": "\uc0ac\uc6a9\uc790\uba85" + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "Insteon Hub \ubc84\uc804 2\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", "title": "Insteon Hub \ubc84\uc804 2" @@ -43,7 +43,7 @@ }, "options": { "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "select_single": "\uc635\uc158 \uc120\ud0dd" }, "step": { @@ -59,6 +59,14 @@ }, "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4." }, + "change_hub_config": { + "data": { + "host": "IP \uc8fc\uc18c", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + }, "init": { "data": { "add_override": "\uc7a5\uce58 Override \ucd94\uac00", diff --git a/homeassistant/components/ios/translations/ko.json b/homeassistant/components/ios/translations/ko.json index 6abe9380473470..f5da462c1ab564 100644 --- a/homeassistant/components/ios/translations/ko.json +++ b/homeassistant/components/ios/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 Home Assistant iOS \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "Home Assistant iOS \ucef4\ud3ec\ub10c\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/ipp/translations/ko.json b/homeassistant/components/ipp/translations/ko.json index bc2e18cb5c2f3d..28e79ffe281806 100644 --- a/homeassistant/components/ipp/translations/ko.json +++ b/homeassistant/components/ipp/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud558\ub824\uba74 \uc5f0\uacb0\uc744 \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4.", "ipp_error": "IPP \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "ipp_version_error": "\ud504\ub9b0\ud130\uc5d0\uc11c IPP \ubc84\uc804\uc744 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", @@ -9,6 +10,7 @@ "unique_id_required": "\uae30\uae30 \uac80\uc0c9\uc5d0 \ud544\uc694\ud55c \uace0\uc720\ud55c ID \uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. SSL/TLS \uc635\uc158\uc744 \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "flow_title": "\ud504\ub9b0\ud130: {name}", @@ -18,8 +20,8 @@ "base_path": "\ud504\ub9b0\ud130\uc758 \uc0c1\ub300 \uacbd\ub85c", "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8", - "ssl": "\ud504\ub9b0\ud130\ub294 SSL/TLS \ub97c \ud1b5\ud55c \ud1b5\uc2e0\uc744 \uc9c0\uc6d0\ud569\ub2c8\ub2e4", - "verify_ssl": "\ud504\ub9b0\ud130\ub294 \uc62c\ubc14\ub978 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4" + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "description": "\uc778\ud130\ub137 \uc778\uc1c4 \ud504\ub85c\ud1a0\ucf5c (IPP) \ub97c \ud1b5\ud574 \ud504\ub9b0\ud130\ub97c \uc124\uc815\ud558\uc5ec Home Assistant \uc640 \uc5f0\ub3d9\ud569\ub2c8\ub2e4.", "title": "\ud504\ub9b0\ud130 \uc5f0\uacb0\ud558\uae30" diff --git a/homeassistant/components/iqvia/translations/ko.json b/homeassistant/components/iqvia/translations/ko.json index f6a914bd07d9f0..1b0dfd980ec176 100644 --- a/homeassistant/components/iqvia/translations/ko.json +++ b/homeassistant/components/iqvia/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc774 \uc6b0\ud3b8 \ubc88\ud638\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_zip_code": "\uc6b0\ud3b8\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/islamic_prayer_times/translations/ko.json b/homeassistant/components/islamic_prayer_times/translations/ko.json index 52ac68698559f9..240ad6f57dca0f 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ko.json +++ b/homeassistant/components/islamic_prayer_times/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, "step": { "user": { "description": "\uc774\uc2ac\ub78c \uae30\ub3c4 \uc2dc\uac04\uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", diff --git a/homeassistant/components/izone/translations/ko.json b/homeassistant/components/izone/translations/ko.json index 85aec276562787..b6eae170becdf6 100644 --- a/homeassistant/components/izone/translations/ko.json +++ b/homeassistant/components/izone/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "iZone \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 iZone \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/juicenet/translations/ko.json b/homeassistant/components/juicenet/translations/ko.json index 50b824ec82f085..1e1ae6aaa88022 100644 --- a/homeassistant/components/juicenet/translations/ko.json +++ b/homeassistant/components/juicenet/translations/ko.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "\uc774 JuiceNet \uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { - "api_token": "JuiceNet API \ud1a0\ud070" + "api_token": "API \ud1a0\ud070" }, "description": "https://home.juice.net/Manage \uc758 API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", "title": "JuiceNet \uc5d0 \uc5f0\uacb0\ud558\uae30" diff --git a/homeassistant/components/keenetic_ndms2/translations/cs.json b/homeassistant/components/keenetic_ndms2/translations/cs.json new file mode 100644 index 00000000000000..f34807f3fee64c --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "name": "N\u00e1zev", + "password": "Heslo", + "port": "Port", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ko.json b/homeassistant/components/keenetic_ndms2/translations/ko.json new file mode 100644 index 00000000000000..3281ddbe3d4485 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\uc2a4\uce94 \uac04\uaca9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/ko.json b/homeassistant/components/kodi/translations/ko.json index 64b08475b688b3..233cd068a1ee2a 100644 --- a/homeassistant/components/kodi/translations/ko.json +++ b/homeassistant/components/kodi/translations/ko.json @@ -1,17 +1,23 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Kodi: {name}", "step": { "credentials": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, "description": "Kodi \uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624. \uc774\ub7ec\ud55c \ub0b4\uc6a9\uc740 \uc2dc\uc2a4\ud15c/\uc124\uc815/\ub124\ud2b8\uc6cc\ud06c/\uc11c\ube44\uc2a4\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "discovery_confirm": { @@ -19,9 +25,17 @@ "title": "Kodi \ubc1c\uacac" }, "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8", + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9" + }, "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4. \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c Kodi \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624." }, "ws_port": { + "data": { + "ws_port": "\ud3ec\ud2b8" + }, "description": "WebSocket \ud3ec\ud2b8 (Kodi\uc5d0\uc11c TCP \ud3ec\ud2b8\ub77c\uace0\ub3c4 \ud568). WebSocket\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub824\uba74 \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"\ud504\ub85c\uadf8\ub7a8\uc774 Kodi\ub97c \uc81c\uc5b4\ud558\ub3c4\ub85d \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud574\uc57c\ud569\ub2c8\ub2e4. WebSocket\uc774 \ud65c\uc131\ud654\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc81c\uac70\ud558\uace0 \ube44\uc6cc \ub461\ub2c8\ub2e4." } } diff --git a/homeassistant/components/konnected/translations/ko.json b/homeassistant/components/konnected/translations/ko.json index d8d2b70d909c09..fe5b9a0347aaed 100644 --- a/homeassistant/components/konnected/translations/ko.json +++ b/homeassistant/components/konnected/translations/ko.json @@ -2,12 +2,12 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "not_konn_panel": "\uc778\uc2dd\ub41c Konnected.io \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "{host}:{port} \uc758 Konnected \ud328\ub110\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "confirm": { @@ -38,7 +38,7 @@ "options_binary": { "data": { "inverse": "\uc5f4\ub9bc / \ub2eb\ud798 \uc0c1\ud0dc \ubc18\uc804", - "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d)", + "name": "\uc774\ub984 (\uc120\ud0dd\uc0ac\ud56d)", "type": "\uc774\uc9c4 \uc13c\uc11c \uc720\ud615" }, "description": "{zone} \uc635\uc158", @@ -46,7 +46,7 @@ }, "options_digital": { "data": { - "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d)", + "name": "\uc774\ub984 (\uc120\ud0dd\uc0ac\ud56d)", "poll_interval": "\ud3f4\ub9c1 \uac04\uaca9 (\ubd84) (\uc120\ud0dd \uc0ac\ud56d)", "type": "\uc13c\uc11c \uc720\ud615" }, @@ -96,7 +96,7 @@ "activation": "\uc2a4\uc704\uce58\uac00 \ucf1c\uc9c8 \ub54c \ucd9c\ub825", "momentary": "\ud384\uc2a4 \uc9c0\uc18d\uc2dc\uac04 (ms) (\uc120\ud0dd \uc0ac\ud56d)", "more_states": "\uc774 \uad6c\uc5ed\uc5d0 \ub300\ud55c \ucd94\uac00 \uc0c1\ud0dc \uad6c\uc131", - "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d)", + "name": "\uc774\ub984 (\uc120\ud0dd\uc0ac\ud56d)", "pause": "\ud384\uc2a4 \uac04 \uc77c\uc2dc\uc815\uc9c0 \uc2dc\uac04 (ms) (\uc120\ud0dd \uc0ac\ud56d)", "repeat": "\ubc18\ubcf5 \uc2dc\uac04 (-1 = \ubb34\ud55c) (\uc120\ud0dd \uc0ac\ud56d)" }, diff --git a/homeassistant/components/kulersky/translations/ko.json b/homeassistant/components/kulersky/translations/ko.json new file mode 100644 index 00000000000000..7011a61f7573ab --- /dev/null +++ b/homeassistant/components/kulersky/translations/ko.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/ko.json b/homeassistant/components/life360/translations/ko.json index d419c5fdc020eb..d2ebd7c674ff3c 100644 --- a/homeassistant/components/life360/translations/ko.json +++ b/homeassistant/components/life360/translations/ko.json @@ -1,10 +1,17 @@ { "config": { + "abort": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, "create_entry": { "default": "\uace0\uae09 \uc635\uc158\uc744 \uc124\uc815\ud558\ub824\uba74 [Life360 \uc124\uba85\uc11c]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { - "invalid_username": "\uc0ac\uc6a9\uc790 \uc774\ub984\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_username": "\uc0ac\uc6a9\uc790 \uc774\ub984\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/lifx/translations/ko.json b/homeassistant/components/lifx/translations/ko.json index 040ac405e2d1b9..34bec9c3aee4fe 100644 --- a/homeassistant/components/lifx/translations/ko.json +++ b/homeassistant/components/lifx/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "LIFX \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 LIFX \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/local_ip/translations/ko.json b/homeassistant/components/local_ip/translations/ko.json index 050229dbf087f6..3b543f87f79ead 100644 --- a/homeassistant/components/local_ip/translations/ko.json +++ b/homeassistant/components/local_ip/translations/ko.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 \ub85c\uceec IP \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "user": { "data": { "name": "\uc13c\uc11c \uc774\ub984" }, + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ub85c\uceec IP \uc8fc\uc18c" } } diff --git a/homeassistant/components/locative/translations/ko.json b/homeassistant/components/locative/translations/ko.json index eb10a8ca1671dc..5930e7edf1ba33 100644 --- a/homeassistant/components/locative/translations/ko.json +++ b/homeassistant/components/locative/translations/ko.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "Locative \uc6f9 \ud6c5\uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Locative \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/logi_circle/translations/ko.json b/homeassistant/components/logi_circle/translations/ko.json index 3fe8ce4824e097..2300bbb27c6623 100644 --- a/homeassistant/components/logi_circle/translations/ko.json +++ b/homeassistant/components/logi_circle/translations/ko.json @@ -1,11 +1,15 @@ { "config": { "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "external_error": "\ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc608\uc678\uc0ac\ud56d\uc774 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "external_setup": "Logi Circle \uc774 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "external_setup": "Logi Circle \uc774 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." }, "error": { - "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694" + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "auth": { diff --git a/homeassistant/components/luftdaten/translations/ko.json b/homeassistant/components/luftdaten/translations/ko.json index eb69dfb64a2687..fbb5a26e7ee4ec 100644 --- a/homeassistant/components/luftdaten/translations/ko.json +++ b/homeassistant/components/luftdaten/translations/ko.json @@ -1,6 +1,8 @@ { "config": { "error": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_sensor": "\uc13c\uc11c\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uac70\ub098 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/lutron_caseta/translations/ko.json b/homeassistant/components/lutron_caseta/translations/ko.json index 8c5caec998e6e9..af7fed5829c6be 100644 --- a/homeassistant/components/lutron_caseta/translations/ko.json +++ b/homeassistant/components/lutron_caseta/translations/ko.json @@ -1,16 +1,21 @@ { "config": { "abort": { - "already_configured": "Cas\u00e9ta \ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "cannot_connect": "Cas\u00e9ta \ube0c\ub9ac\uc9c0 \uc5f0\uacb0 \uc2e4\ud328\ub85c \uc124\uc815\uc774 \ucde8\uc18c\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "Cas\u00e9ta \ube0c\ub9ac\uc9c0\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \ubc0f \uc778\uc99d\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "import_failed": { "description": "configuration.yaml \uc5d0\uc11c \uac00\uc838\uc628 \ube0c\ub9ac\uc9c0 (\ud638\uc2a4\ud2b8:{host}) \ub97c \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "title": "Cas\u00e9ta \ube0c\ub9ac\uc9c0 \uad6c\uc131\uc744 \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + } } } } diff --git a/homeassistant/components/lyric/translations/ko.json b/homeassistant/components/lyric/translations/ko.json new file mode 100644 index 00000000000000..fa000ea1c06d1b --- /dev/null +++ b/homeassistant/components/lyric/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/ko.json b/homeassistant/components/mailgun/translations/ko.json index 43b6586b14ff43..b757a27f4a0608 100644 --- a/homeassistant/components/mailgun/translations/ko.json +++ b/homeassistant/components/mailgun/translations/ko.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/mazda/translations/cs.json b/homeassistant/components/mazda/translations/cs.json index 8a929fb58d7de5..89fde600735578 100644 --- a/homeassistant/components/mazda/translations/cs.json +++ b/homeassistant/components/mazda/translations/cs.json @@ -13,13 +13,15 @@ "reauth": { "data": { "email": "E-mail", - "password": "Heslo" + "password": "Heslo", + "region": "Region" } }, "user": { "data": { "email": "E-mail", - "password": "Heslo" + "password": "Heslo", + "region": "Region" } } } diff --git a/homeassistant/components/mazda/translations/ko.json b/homeassistant/components/mazda/translations/ko.json new file mode 100644 index 00000000000000..31495b0d8e324a --- /dev/null +++ b/homeassistant/components/mazda/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "reauth": { + "data": { + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + }, + "user": { + "data": { + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/ko.json b/homeassistant/components/melcloud/translations/ko.json index a43d4cfbcb3062..2e1f1b535e1f38 100644 --- a/homeassistant/components/melcloud/translations/ko.json +++ b/homeassistant/components/melcloud/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uc774 \uc774\uba54\uc77c\uc5d0 \ub300\ud55c MELCloud \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uac31\uc2e0\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/met/translations/ko.json b/homeassistant/components/met/translations/ko.json index e7263aba3d252a..17175c196c0af0 100644 --- a/homeassistant/components/met/translations/ko.json +++ b/homeassistant/components/met/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/meteo_france/translations/ko.json b/homeassistant/components/meteo_france/translations/ko.json index 4b8dc3204dd57a..ec48103bbff8bc 100644 --- a/homeassistant/components/meteo_france/translations/ko.json +++ b/homeassistant/components/meteo_france/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "\ub3c4\uc2dc\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "empty": "\ub3c4\uc2dc \uac80\uc0c9 \uacb0\uacfc \uc5c6\uc74c: \ub3c4\uc2dc \ud544\ub4dc\ub97c \ud655\uc778\ud558\uc2ed\uc2dc\uc624." diff --git a/homeassistant/components/metoffice/translations/ko.json b/homeassistant/components/metoffice/translations/ko.json index b1af2afaf30ccf..b2f09a4a9e5a0f 100644 --- a/homeassistant/components/metoffice/translations/ko.json +++ b/homeassistant/components/metoffice/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "\uc601\uad6d \uae30\uc0c1\uccad DataPoint API \ud0a4", + "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4" }, diff --git a/homeassistant/components/mikrotik/translations/ko.json b/homeassistant/components/mikrotik/translations/ko.json index f32e2260501c2a..05a8f50066c5d6 100644 --- a/homeassistant/components/mikrotik/translations/ko.json +++ b/homeassistant/components/mikrotik/translations/ko.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Mikrotik \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/mill/translations/ko.json b/homeassistant/components/mill/translations/ko.json index d2c6fd74284d5e..48c8cdc6eaa515 100644 --- a/homeassistant/components/mill/translations/ko.json +++ b/homeassistant/components/mill/translations/ko.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/minecraft_server/translations/ko.json b/homeassistant/components/minecraft_server/translations/ko.json index 30605d729369bb..98ab72e94fccdb 100644 --- a/homeassistant/components/minecraft_server/translations/ko.json +++ b/homeassistant/components/minecraft_server/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\ub97c \ud655\uc778\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \ub610\ud55c \uc11c\ubc84\uc5d0\uc11c Minecraft \ubc84\uc804 1.7 \uc774\uc0c1\uc744 \uc2e4\ud589 \uc911\uc778\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/monoprice/translations/ko.json b/homeassistant/components/monoprice/translations/ko.json index 23e191735359b4..6afed3aa6d6178 100644 --- a/homeassistant/components/monoprice/translations/ko.json +++ b/homeassistant/components/monoprice/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/motion_blinds/translations/ko.json b/homeassistant/components/motion_blinds/translations/ko.json new file mode 100644 index 00000000000000..9d2f0eead3d390 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "connect": { + "data": { + "api_key": "API \ud0a4" + } + }, + "select": { + "data": { + "select_ip": "IP \uc8fc\uc18c" + } + }, + "user": { + "data": { + "api_key": "API \ud0a4", + "host": "IP \uc8fc\uc18c" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index f713d564438695..fd79863fadf288 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 MQTT \ube0c\ub85c\ucee4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "error": { - "cannot_connect": "MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "Birth \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "bad_will": "Will \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "cannot_connect": "MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "broker": { @@ -71,6 +71,7 @@ "birth_retain": "Birth \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", "birth_topic": "Birth \uba54\uc2dc\uc9c0 \ud1a0\ud53d", "discovery": "\uc7a5\uce58 \uac80\uc0c9 \ud65c\uc131\ud654", + "will_enable": "Will \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654", "will_payload": "Will \uba54\uc2dc\uc9c0 \ud398\uc774\ub85c\ub4dc", "will_qos": "Will \uba54\uc2dc\uc9c0 QoS", "will_retain": "Will \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", diff --git a/homeassistant/components/myq/translations/ko.json b/homeassistant/components/myq/translations/ko.json index 31e3f8646e629f..23ba2eecea7022 100644 --- a/homeassistant/components/myq/translations/ko.json +++ b/homeassistant/components/myq/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/mysensors/translations/cs.json b/homeassistant/components/mysensors/translations/cs.json index 9d3cfbd25089ea..abe47f046ff64b 100644 --- a/homeassistant/components/mysensors/translations/cs.json +++ b/homeassistant/components/mysensors/translations/cs.json @@ -1,13 +1,16 @@ { "config": { "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "error": { "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno", "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" } }, "title": "MySensors" diff --git a/homeassistant/components/mysensors/translations/ko.json b/homeassistant/components/mysensors/translations/ko.json new file mode 100644 index 00000000000000..bb38f94bc9204c --- /dev/null +++ b/homeassistant/components/mysensors/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ko.json b/homeassistant/components/neato/translations/ko.json index 00d1ae3b4677c0..359aeefcc78552 100644 --- a/homeassistant/components/neato/translations/ko.json +++ b/homeassistant/components/neato/translations/ko.json @@ -1,12 +1,27 @@ { "config": { "abort": { - "already_configured": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "create_entry": { - "default": "[Neato \uc124\uba85\uc11c]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "reauth_confirm": { + "title": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", diff --git a/homeassistant/components/nest/translations/ko.json b/homeassistant/components/nest/translations/ko.json index 798f191e34adf2..f5a0fcf39d1c77 100644 --- a/homeassistant/components/nest/translations/ko.json +++ b/homeassistant/components/nest/translations/ko.json @@ -1,20 +1,29 @@ { "config": { "abort": { - "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "internal_error": "\ucf54\ub4dc \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \ub0b4\ubd80 \uc624\ub958 \ubc1c\uc0dd", + "invalid_pin": "PIN \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "timeout": "\ucf54\ub4dc \uc720\ud6a8\uc131 \uac80\uc0ac \uc2dc\uac04 \ucd08\uacfc", - "unknown": "\ucf54\ub4dc \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958 \ubc1c\uc0dd" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "init": { "data": { "flow_impl": "\uacf5\uae09\uc790" }, - "description": "Nest \ub97c \uc778\uc99d\ud558\uae30 \uc704\ud55c \uc778\uc99d \uacf5\uae09\uc790\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "description": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30", "title": "\uc778\uc99d \uacf5\uae09\uc790" }, "link": { @@ -23,6 +32,12 @@ }, "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74, [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url}) \uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4, \uc544\ub798\uc758 PIN \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec\ub123\uc73c\uc138\uc694.", "title": "Nest \uacc4\uc815 \uc5f0\uacb0\ud558\uae30" + }, + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "reauth_confirm": { + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" } } } diff --git a/homeassistant/components/netatmo/translations/ko.json b/homeassistant/components/netatmo/translations/ko.json index 8165941f0d8fa1..320df4665156a4 100644 --- a/homeassistant/components/netatmo/translations/ko.json +++ b/homeassistant/components/netatmo/translations/ko.json @@ -3,7 +3,8 @@ "abort": { "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/nexia/translations/ko.json b/homeassistant/components/nexia/translations/ko.json index a918de60fe63d5..170411f5f7390f 100644 --- a/homeassistant/components/nexia/translations/ko.json +++ b/homeassistant/components/nexia/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "nexia home \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/nightscout/translations/ko.json b/homeassistant/components/nightscout/translations/ko.json index 0235c446e75454..0408a1f61abfb6 100644 --- a/homeassistant/components/nightscout/translations/ko.json +++ b/homeassistant/components/nightscout/translations/ko.json @@ -4,7 +4,17 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "url": "URL \uc8fc\uc18c" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/ko.json b/homeassistant/components/notion/translations/ko.json index 323ea126445223..b5c7cadbe9b3a5 100644 --- a/homeassistant/components/notion/translations/ko.json +++ b/homeassistant/components/notion/translations/ko.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_devices": "\uacc4\uc815\uc5d0 \ub4f1\ub85d\ub41c \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/nuheat/translations/ko.json b/homeassistant/components/nuheat/translations/ko.json index 1476e8beb0dada..a533cd69093f80 100644 --- a/homeassistant/components/nuheat/translations/ko.json +++ b/homeassistant/components/nuheat/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc628\ub3c4 \uc870\uc808\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_thermostat": "\uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/nuki/translations/ko.json b/homeassistant/components/nuki/translations/ko.json new file mode 100644 index 00000000000000..68f43847d6c788 --- /dev/null +++ b/homeassistant/components/nuki/translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8", + "token": "\uc561\uc138\uc2a4 \ud1a0\ud070" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ko.json b/homeassistant/components/nut/translations/ko.json index 81fe8d88a90e61..0fb8339ddfce3c 100644 --- a/homeassistant/components/nut/translations/ko.json +++ b/homeassistant/components/nut/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/nws/translations/ko.json b/homeassistant/components/nws/translations/ko.json index 552099c7193756..9fbdf026558856 100644 --- a/homeassistant/components/nws/translations/ko.json +++ b/homeassistant/components/nws/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -15,7 +15,7 @@ "longitude": "\uacbd\ub3c4", "station": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc" }, - "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\ub97c \uc9c0\uc815\ud558\uc9c0 \uc54a\uc73c\uba74 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294\ub370 \uc704\ub3c4\uc640 \uacbd\ub3c4\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.", + "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\uac00 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uac00\uc7a5 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294 \ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \ud604\uc7ac API Key \ub294 \uc544\ubb34 \ud0a4\ub098 \ub123\uc5b4\ub3c4 \uc0c1\uad00 \uc5c6\uc2b5\ub2c8\ub2e4\ub9cc, \uc62c\ubc14\ub978 \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uad8c\uc7a5\ud569\ub2c8\ub2e4.", "title": "\ubbf8\uad6d \uae30\uc0c1\uccad\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/nzbget/translations/ko.json b/homeassistant/components/nzbget/translations/ko.json index dd53d52a236fc1..58d38b66361e08 100644 --- a/homeassistant/components/nzbget/translations/ko.json +++ b/homeassistant/components/nzbget/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub428. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "NZBGet : {name}", "step": { @@ -13,11 +13,11 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984", - "password": "\uc554\ud638", + "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", - "ssl": "NZBGet\uc740 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.", - "username": "\uc0ac\uc6a9\uc790\uba85", - "verify_ssl": "NZBGet\uc740 \uc801\uc808\ud55c \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4." + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "title": "NZBGet\uc5d0 \uc5f0\uacb0" } diff --git a/homeassistant/components/omnilogic/translations/ko.json b/homeassistant/components/omnilogic/translations/ko.json index 5389207cdda909..74786104624fe1 100644 --- a/homeassistant/components/omnilogic/translations/ko.json +++ b/homeassistant/components/omnilogic/translations/ko.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { - "password": "\uc554\ud638", - "username": "\uc0ac\uc6a9\uc790\uba85" + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } diff --git a/homeassistant/components/ondilo_ico/translations/ko.json b/homeassistant/components/ondilo_ico/translations/ko.json new file mode 100644 index 00000000000000..fa000ea1c06d1b --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/ko.json b/homeassistant/components/onewire/translations/ko.json new file mode 100644 index 00000000000000..871482b766b53b --- /dev/null +++ b/homeassistant/components/onewire/translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "owserver": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/ko.json b/homeassistant/components/onvif/translations/ko.json index 3b992e8d35fa9a..173cdb88512df5 100644 --- a/homeassistant/components/onvif/translations/ko.json +++ b/homeassistant/components/onvif/translations/ko.json @@ -1,12 +1,15 @@ { "config": { "abort": { - "already_configured": "ONVIF \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "ONVIF \uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_h264": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c H264 \uc2a4\ud2b8\ub9bc\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\uc5d0\uc11c \ud504\ub85c\ud544 \uad6c\uc131\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "no_mac": "ONVIF \uae30\uae30\uc758 \uace0\uc720 ID \ub97c \uad6c\uc131\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "onvif_error": "ONVIF \uae30\uae30 \uc124\uc815 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 \ub85c\uadf8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "auth": { "data": { diff --git a/homeassistant/components/opentherm_gw/translations/ko.json b/homeassistant/components/opentherm_gw/translations/ko.json index eece1492002b56..6f3ac939ad1e11 100644 --- a/homeassistant/components/opentherm_gw/translations/ko.json +++ b/homeassistant/components/opentherm_gw/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "error": { - "already_configured": "OpenTherm Gateway \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "id_exists": "OpenTherm Gateway id \uac00 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/openuv/translations/ko.json b/homeassistant/components/openuv/translations/ko.json index 480b745fe36031..ee211d3cbd53e6 100644 --- a/homeassistant/components/openuv/translations/ko.json +++ b/homeassistant/components/openuv/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc88c\ud45c\uac12\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/openweathermap/translations/ko.json b/homeassistant/components/openweathermap/translations/ko.json index 9560f447250a15..1514ada24ec40f 100644 --- a/homeassistant/components/openweathermap/translations/ko.json +++ b/homeassistant/components/openweathermap/translations/ko.json @@ -1,17 +1,21 @@ { "config": { "abort": { - "already_configured": "\uc774\ub7ec\ud55c \uc88c\ud45c\uc5d0 \ub300\ud55c OpenWeatherMap \ud1b5\ud569\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { - "api_key": "OpenWeatherMap API \ud0a4", + "api_key": "API \ud0a4", "language": "\uc5b8\uc5b4", "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4", "mode": "\ubaa8\ub4dc", - "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uba85" + "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, "description": "OpenWeatherMap \ud1b5\ud569\uc744 \uc124\uc815\ud558\uc138\uc694. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid\ub85c \uc774\ub3d9\ud558\uc2ed\uc2dc\uc624.", "title": "OpenWeatherMap" diff --git a/homeassistant/components/ovo_energy/translations/ko.json b/homeassistant/components/ovo_energy/translations/ko.json index 26372afc28eb85..07ef8d8e166a11 100644 --- a/homeassistant/components/ovo_energy/translations/ko.json +++ b/homeassistant/components/ovo_energy/translations/ko.json @@ -1,10 +1,21 @@ { "config": { "error": { - "already_configured": "\uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + } + }, "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, "title": "OVO Energy \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/owntracks/translations/ko.json b/homeassistant/components/owntracks/translations/ko.json index 3cde37528c24fe..6e558e54627473 100644 --- a/homeassistant/components/owntracks/translations/ko.json +++ b/homeassistant/components/owntracks/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "\n\nAndroid \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url}) \uc744 \uc5f4\uace0 preferences -> connection \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url}) \uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/ozw/translations/ko.json b/homeassistant/components/ozw/translations/ko.json index 98b965d5dd23a5..ba37dccdd68050 100644 --- a/homeassistant/components/ozw/translations/ko.json +++ b/homeassistant/components/ozw/translations/ko.json @@ -1,7 +1,17 @@ { "config": { "abort": { - "mqtt_required": "MQTT \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "mqtt_required": "MQTT \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "step": { + "start_addon": { + "data": { + "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/ko.json b/homeassistant/components/panasonic_viera/translations/ko.json index fc2fd7827ab50c..0f3252a4ab13dd 100644 --- a/homeassistant/components/panasonic_viera/translations/ko.json +++ b/homeassistant/components/panasonic_viera/translations/ko.json @@ -1,18 +1,20 @@ { "config": { "abort": { - "already_configured": "\uc774 Panasonic Viera TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 \ub85c\uadf8\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_pin_code": "\uc785\ub825\ud55c PIN \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "pairing": { "data": { - "pin": "PIN" + "pin": "PIN \ucf54\ub4dc" }, - "description": "TV \uc5d0 \ud45c\uc2dc\ub41c PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", + "description": "TV \uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "\ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json new file mode 100644 index 00000000000000..85281856809b4a --- /dev/null +++ b/homeassistant/components/philips_js/translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/ko.json b/homeassistant/components/pi_hole/translations/ko.json index 4653cc8564dec1..7261742b2a64ec 100644 --- a/homeassistant/components/pi_hole/translations/ko.json +++ b/homeassistant/components/pi_hole/translations/ko.json @@ -7,6 +7,11 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "api_key": { + "data": { + "api_key": "API \ud0a4" + } + }, "user": { "data": { "api_key": "API \ud0a4", @@ -14,8 +19,8 @@ "location": "\uc704\uce58", "name": "\uc774\ub984", "port": "\ud3ec\ud2b8", - "ssl": "SSL \uc0ac\uc6a9", - "verify_ssl": "SSL \uc778\uc99d\uc11c \uac80\uc99d" + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" } } } diff --git a/homeassistant/components/plaato/translations/ko.json b/homeassistant/components/plaato/translations/ko.json index 6eeb6a9c06150d..753653c88b2f9d 100644 --- a/homeassistant/components/plaato/translations/ko.json +++ b/homeassistant/components/plaato/translations/ko.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Plaato Airlock \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4.\n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "**{device_name}** \uc758 Plaato {device_type} \uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" }, "step": { "user": { - "description": "Plaato Airlock \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Plaato \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Plaato \uae30\uae30 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index 7c461fe1673211..df7285334678d0 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "\uc774\ubbf8 \uad6c\uc131\ub41c \ubaa8\ub4e0 \uc5f0\uacb0\ub41c \uc11c\ubc84", "already_configured": "\uc774 Plex \uc11c\ubc84\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "Plex \ub97c \uad6c\uc131 \uc911\uc785\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", "token_request_timeout": "\ud1a0\ud070 \ud68d\ub4dd \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc774\uc720\ub85c \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "faulty_credentials": "\uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud1a0\ud070\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694", @@ -20,9 +21,9 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8", - "ssl": "SSL \uc0ac\uc6a9", + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "token": "\ud1a0\ud070 (\uc120\ud0dd \uc0ac\ud56d)", - "verify_ssl": "SSL \uc778\uc99d\uc11c \uac80\uc99d" + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "title": "Plex \uc9c1\uc811 \uad6c\uc131\ud558\uae30" }, diff --git a/homeassistant/components/plugwise/translations/ko.json b/homeassistant/components/plugwise/translations/ko.json index 4af12098b7f33b..7af503f0a660d2 100644 --- a/homeassistant/components/plugwise/translations/ko.json +++ b/homeassistant/components/plugwise/translations/ko.json @@ -1,18 +1,24 @@ { "config": { "abort": { - "already_configured": "\uc774 Smile \uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. 8\uc790\uc758 Smile ID \ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Smile: {name}", "step": { "user": { - "description": "\uc138\ubd80 \uc815\ubcf4", - "title": "Smile \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "\uc81c\ud488:", + "title": "Plugwise \uc720\ud615" + }, + "user_gateway": { + "data": { + "host": "IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8" + } } } }, diff --git a/homeassistant/components/plum_lightpad/translations/ko.json b/homeassistant/components/plum_lightpad/translations/ko.json index 008177f1cec336..be71285cbb7354 100644 --- a/homeassistant/components/plum_lightpad/translations/ko.json +++ b/homeassistant/components/plum_lightpad/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/point/translations/ko.json b/homeassistant/components/point/translations/ko.json index 6ea58e95834a24..f4ca6002036819 100644 --- a/homeassistant/components/point/translations/ko.json +++ b/homeassistant/components/point/translations/ko.json @@ -2,10 +2,11 @@ "config": { "abort": { "already_setup": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", - "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "external_setup": "Point \uac00 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "no_flows": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + "no_flows": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/poolsense/translations/ko.json b/homeassistant/components/poolsense/translations/ko.json index 42a6654592f00f..ec8c7dfc90f60e 100644 --- a/homeassistant/components/poolsense/translations/ko.json +++ b/homeassistant/components/poolsense/translations/ko.json @@ -12,7 +12,7 @@ "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "[%key:common::config_flow::description%]", + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "PoolSense" } } diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index d136c385acacd1..48cd7c04743df6 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -17,7 +17,7 @@ "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 Telsa; oppure dagli ultimi 5 caratteri della password trovata all'interno della porta per il Backup Gateway 2.", + "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" } } diff --git a/homeassistant/components/powerwall/translations/ko.json b/homeassistant/components/powerwall/translations/ko.json index 9ba7004899f7ca..11c638fa9bd50d 100644 --- a/homeassistant/components/powerwall/translations/ko.json +++ b/homeassistant/components/powerwall/translations/ko.json @@ -1,17 +1,20 @@ { "config": { "abort": { - "already_configured": "powerwall \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "wrong_version": "Powerwall \uc774 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ubc84\uc804\uc758 \uc18c\ud504\ud2b8\uc6e8\uc5b4\ub97c \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4. \uc774 \ubb38\uc81c\ub97c \ud574\uacb0\ud558\ub824\uba74 \uc5c5\uadf8\ub808\uc774\ub4dc\ud558\uac70\ub098 \uc774 \ub0b4\uc6a9\uc744 \uc54c\ub824\uc8fc\uc138\uc694." }, "step": { "user": { "data": { - "ip_address": "IP \uc8fc\uc18c" + "ip_address": "IP \uc8fc\uc18c", + "password": "\ube44\ubc00\ubc88\ud638" }, "title": "powerwall \uc5d0 \uc5f0\uacb0\ud558\uae30" } diff --git a/homeassistant/components/profiler/translations/ko.json b/homeassistant/components/profiler/translations/ko.json new file mode 100644 index 00000000000000..0c38052b826296 --- /dev/null +++ b/homeassistant/components/profiler/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "step": { + "user": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/ko.json b/homeassistant/components/progettihwsw/translations/ko.json index 02fca81481141b..ab21d8427bd50f 100644 --- a/homeassistant/components/progettihwsw/translations/ko.json +++ b/homeassistant/components/progettihwsw/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\uc7a5\uce58\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "relay_modes": { diff --git a/homeassistant/components/ps4/translations/ko.json b/homeassistant/components/ps4/translations/ko.json index 0e62a64d1c62ae..76762a0bec6b4d 100644 --- a/homeassistant/components/ps4/translations/ko.json +++ b/homeassistant/components/ps4/translations/ko.json @@ -1,15 +1,17 @@ { "config": { "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "credential_error": "\uc790\uaca9 \uc99d\uba85\uc744 \uac00\uc838\uc624\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "no_devices_found": "PlayStation 4 \uae30\uae30\ub97c \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "port_987_bind_error": "\ud3ec\ud2b8 987 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "credential_timeout": "\uc790\uaca9 \uc99d\uba85 \uc11c\ube44\uc2a4 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud655\uc778\uc744 \ud074\ub9ad\ud558\uc5ec \ub2e4\uc2dc \uc2dc\uc791\ud574\uc8fc\uc138\uc694.", - "login_failed": "PlayStation 4 \uc640 \ud398\uc5b4\ub9c1\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. PIN \uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "no_ipaddress": "\uad6c\uc131\ud558\uace0\uc790 \ud558\ub294 PlayStation 4 \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." + "login_failed": "PlayStation 4 \uc640 \ud398\uc5b4\ub9c1\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. PIN \ucf54\ub4dc\uac00 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "no_ipaddress": "\uad6c\uc131\ud560 PlayStation 4\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." }, "step": { "creds": { @@ -18,12 +20,12 @@ }, "link": { "data": { - "code": "PIN", + "code": "PIN \ucf54\ub4dc", "ip_address": "IP \uc8fc\uc18c", "name": "\uc774\ub984", "region": "\uc9c0\uc5ed" }, - "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. 'PIN' \uc744 \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30' \ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c 8\uc790\ub9ac \uc22b\uc790\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. PIN \ucf54\ub4dc\ub97c \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30' \ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "title": "PlayStation 4" }, "mode": { @@ -31,7 +33,7 @@ "ip_address": "IP \uc8fc\uc18c (\uc790\ub3d9 \uac80\uc0c9\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\uc6cc\ub450\uc138\uc694)", "mode": "\uad6c\uc131 \ubaa8\ub4dc" }, - "description": "\uad6c\uc131 \ubaa8\ub4dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc790\ub3d9 \uac80\uc0c9\uc744 \uc120\ud0dd\ud558\uba74 \uae30\uae30\uac00 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub418\ubbc0\ub85c IP \uc8fc\uc18c \ud544\ub4dc\ub294 \ube44\uc6cc\ub450\uc154\ub3c4 \ub429\ub2c8\ub2e4.", + "description": "\uad6c\uc131\ud560 \ubaa8\ub4dc\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4. \uc790\ub3d9 \uac80\uc0c9\uc744 \uc120\ud0dd\ud558\uba74 \uae30\uae30\uac00 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub418\ubbc0\ub85c IP \uc8fc\uc18c \ud544\ub4dc\ub294 \ube44\uc6cc\ub450\uc154\ub3c4 \ub429\ub2c8\ub2e4.", "title": "PlayStation 4" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json index 35ac17a8bb8ff4..f1f225ae525771 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uc774\ubbf8 \ud574\ub2f9 \uc694\uae08\uc81c \uc13c\uc11c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/rachio/translations/ko.json b/homeassistant/components/rachio/translations/ko.json index 2f5724c7af1b6b..298ef476745461 100644 --- a/homeassistant/components/rachio/translations/ko.json +++ b/homeassistant/components/rachio/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, @@ -13,7 +13,7 @@ "data": { "api_key": "API \ud0a4" }, - "description": "https://app.rach.io/ \uc758 API \ud0a4\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uacc4\uc815 \uc124\uc815\uc744 \uc120\ud0dd\ud55c \ub2e4\uc74c 'GET API KEY ' \ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", + "description": "https://app.rach.io/ \uc758 API \ud0a4\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. Settings \ub85c \uc774\ub3d9\ud55c \ub2e4\uc74c 'GET API KEY ' \ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", "title": "Rachio \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "\uc2a4\uc704\uce58\uac00 \ud65c\uc131\ud654\ub41c \uacbd\uc6b0 \uc2a4\ud14c\uc774\uc158\uc744 \ucf1c\ub294 \uc2dc\uac04(\ubd84) \uc785\ub2c8\ub2e4." + "manual_run_mins": "\uad6c\uc5ed \uc2a4\uc704\uce58\ub97c \ud65c\uc131\ud654\ud560 \ub54c \uc2e4\ud589\ud560 \uc2dc\uac04(\ubd84)" } } } diff --git a/homeassistant/components/rainmachine/translations/ko.json b/homeassistant/components/rainmachine/translations/ko.json index e1c78ae82479a5..08ccfd1f5b9a84 100644 --- a/homeassistant/components/rainmachine/translations/ko.json +++ b/homeassistant/components/rainmachine/translations/ko.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 RainMachine \ucee8\ud2b8\ub864\ub7ec\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/recollect_waste/translations/ko.json b/homeassistant/components/recollect_waste/translations/ko.json new file mode 100644 index 00000000000000..17dee71d640a46 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/ko.json b/homeassistant/components/rfxtrx/translations/ko.json index aa8512da2850af..e8c83a7bfd70e3 100644 --- a/homeassistant/components/rfxtrx/translations/ko.json +++ b/homeassistant/components/rfxtrx/translations/ko.json @@ -1,7 +1,30 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "setup_network": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + } + }, + "setup_serial_manual_path": { + "data": { + "device": "USB \uc7a5\uce58 \uacbd\ub85c" + } + } + } + }, + "options": { + "error": { + "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" } } } \ No newline at end of file diff --git a/homeassistant/components/risco/translations/ko.json b/homeassistant/components/risco/translations/ko.json index 37d9a61307b062..f3065256e7fa39 100644 --- a/homeassistant/components/risco/translations/ko.json +++ b/homeassistant/components/risco/translations/ko.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "pin": "PIN \ucf54\ub4dc", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } } }, "options": { @@ -23,6 +32,8 @@ }, "init": { "data": { + "code_arm_required": "\uc124\uc815\ud558\ub824\uba74 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4", + "code_disarm_required": "\ud574\uc81c\ud558\ub824\uba74 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4", "scan_interval": "Risco\ub97c \ud3f4\ub9c1\ud558\ub294 \ube48\ub3c4 (\ucd08)" } }, diff --git a/homeassistant/components/rituals_perfume_genie/translations/ca.json b/homeassistant/components/rituals_perfume_genie/translations/ca.json new file mode 100644 index 00000000000000..d7abccc2c25bc0 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + }, + "title": "Connexi\u00f3 amb el teu compte Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/it.json b/homeassistant/components/rituals_perfume_genie/translations/it.json new file mode 100644 index 00000000000000..6dfb2230285cbf --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Password" + }, + "title": "Collegati al tuo account Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ko.json b/homeassistant/components/roku/translations/ko.json index 054c1674884f8a..19f4c16785fb08 100644 --- a/homeassistant/components/roku/translations/ko.json +++ b/homeassistant/components/roku/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { diff --git a/homeassistant/components/roomba/translations/ko.json b/homeassistant/components/roomba/translations/ko.json index ebf9056c0379ec..d9c661a20dd2a9 100644 --- a/homeassistant/components/roomba/translations/ko.json +++ b/homeassistant/components/roomba/translations/ko.json @@ -1,9 +1,28 @@ { "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "init": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + } + }, + "link_manual": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + } + }, + "manual": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + } + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/roon/translations/ko.json b/homeassistant/components/roon/translations/ko.json index ae483b0b09880c..7c051d49dc7046 100644 --- a/homeassistant/components/roon/translations/ko.json +++ b/homeassistant/components/roon/translations/ko.json @@ -17,7 +17,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Roon \uc11c\ubc84 Hostname \ub610\ub294 IP\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624." + "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uba85\uc774\ub098 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/rpi_power/translations/ko.json b/homeassistant/components/rpi_power/translations/ko.json index b9a9a1be643c50..445c0c34e6811a 100644 --- a/homeassistant/components/rpi_power/translations/ko.json +++ b/homeassistant/components/rpi_power/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\uc774 \uad6c\uc131 \uc694\uc18c\uc5d0 \ud544\uc694\ud55c \uc2dc\uc2a4\ud15c \ud074\ub798\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee4\ub110\uc774 \ucd5c\uc2e0\uc774\uace0 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc9c0\uc6d0\ub418\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.", - "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/ruckus_unleashed/translations/ko.json b/homeassistant/components/ruckus_unleashed/translations/ko.json new file mode 100644 index 00000000000000..9ba063c37ddf46 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 1c7e0b29808b71..14e35a7ff2e850 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 \uc0bc\uc131 TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "\uc0bc\uc131 TV \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "auth_missing": "Home Assistant \uac00 \ud574\ub2f9 \uc0bc\uc131 TV \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant \ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "not_supported": "\uc774 \uc0bc\uc131 TV \ubaa8\ub378\uc740 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "flow_title": "\uc0bc\uc131 TV: {model}", diff --git a/homeassistant/components/sense/translations/ko.json b/homeassistant/components/sense/translations/ko.json index 26545db739a54d..517ad7af17d2cb 100644 --- a/homeassistant/components/sense/translations/ko.json +++ b/homeassistant/components/sense/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/sentry/translations/ko.json b/homeassistant/components/sentry/translations/ko.json index 963695dabfd782..6c00ffea2eff2e 100644 --- a/homeassistant/components/sentry/translations/ko.json +++ b/homeassistant/components/sentry/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, "error": { "bad_dsn": "DSN \uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/sharkiq/translations/ko.json b/homeassistant/components/sharkiq/translations/ko.json index 92649031534ab6..04f400212f19fe 100644 --- a/homeassistant/components/sharkiq/translations/ko.json +++ b/homeassistant/components/sharkiq/translations/ko.json @@ -1,26 +1,27 @@ { "config": { "abort": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "reauth_successful": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc131\uacf5\uc801\uc73c\ub85c \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "reauth": { "data": { - "password": "\uc554\ud638", - "username": "\uc0ac\uc6a9\uc790\uba85" + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } }, "user": { "data": { - "password": "\uc554\ud638", - "username": "\uc0ac\uc6a9\uc790\uba85" + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } diff --git a/homeassistant/components/shelly/translations/ko.json b/homeassistant/components/shelly/translations/ko.json index 5fb84e0ac90486..914c9a46bd8030 100644 --- a/homeassistant/components/shelly/translations/ko.json +++ b/homeassistant/components/shelly/translations/ko.json @@ -1,16 +1,24 @@ { "config": { "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unsupported_firmware": "\uc774 \uc7a5\uce58\ub294 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud38c\uc6e8\uc5b4 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "credentials": { "data": { - "password": "\uc554\ud638", - "username": "\uc0ac\uc6a9\uc790\uba85" + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" } } } diff --git a/homeassistant/components/shopping_list/translations/ko.json b/homeassistant/components/shopping_list/translations/ko.json index 247fa8d9f4d42e..a576567a87f3b4 100644 --- a/homeassistant/components/shopping_list/translations/ko.json +++ b/homeassistant/components/shopping_list/translations/ko.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_configured": "\uc7a5\ubcf4\uae30\ubaa9\ub85d\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { - "description": "\uc7a5\ubcf4\uae30\ubaa9\ub85d\uc744 \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "\uc7a5\ubcf4\uae30\ubaa9\ub85d" + "description": "\uc7a5\ubcf4\uae30 \ubaa9\ub85d\uc744 \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc7a5\ubcf4\uae30 \ubaa9\ub85d" } } }, - "title": "\uc7a5\ubcf4\uae30\ubaa9\ub85d" + "title": "\uc7a5\ubcf4\uae30 \ubaa9\ub85d" } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/ko.json b/homeassistant/components/simplisafe/translations/ko.json index 57ba4a88fc16cc..c5c1b057ea87c0 100644 --- a/homeassistant/components/simplisafe/translations/ko.json +++ b/homeassistant/components/simplisafe/translations/ko.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "\uc774 SimpliSafe \uacc4\uc815\uc740 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uc774 SimpliSafe \uacc4\uc815\uc740 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4.", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "identifier_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "identifier_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + }, "user": { "data": { "code": "\ucf54\ub4dc (Home Assistant UI \uc5d0\uc11c \uc0ac\uc6a9\ub428)", diff --git a/homeassistant/components/smappee/translations/ko.json b/homeassistant/components/smappee/translations/ko.json index b3e37ee6d01ed0..8509b65ca09fa5 100644 --- a/homeassistant/components/smappee/translations/ko.json +++ b/homeassistant/components/smappee/translations/ko.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "local": { diff --git a/homeassistant/components/smarthab/translations/ko.json b/homeassistant/components/smarthab/translations/ko.json index 4b15acd2f3837c..d39931cbc034e9 100644 --- a/homeassistant/components/smarthab/translations/ko.json +++ b/homeassistant/components/smarthab/translations/ko.json @@ -1,7 +1,9 @@ { "config": { "error": { - "service": "SmartHab \uc5d0 \uc811\uc18d\ud558\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc11c\ube44\uc2a4\uac00 \ub2e4\uc6b4\ub418\uc5c8\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc5f0\uacb0\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "service": "SmartHab \uc5d0 \uc811\uc18d\ud558\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc11c\ube44\uc2a4\uac00 \ub2e4\uc6b4\ub418\uc5c8\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc5f0\uacb0\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/smarttub/translations/cs.json b/homeassistant/components/smarttub/translations/cs.json new file mode 100644 index 00000000000000..6be2df92286601 --- /dev/null +++ b/homeassistant/components/smarttub/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "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": { + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Heslo" + }, + "title": "P\u0159ihl\u00e1\u0161en\u00ed" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/it.json b/homeassistant/components/smarttub/translations/it.json new file mode 100644 index 00000000000000..64aed0996f3fe2 --- /dev/null +++ b/homeassistant/components/smarttub/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Password" + }, + "description": "Inserisci il tuo indirizzo e-mail e la password SmartTub per accedere", + "title": "Accesso" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/ko.json b/homeassistant/components/smarttub/translations/ko.json new file mode 100644 index 00000000000000..fab7e511034b46 --- /dev/null +++ b/homeassistant/components/smarttub/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/ko.json b/homeassistant/components/solaredge/translations/ko.json index eb2d8c42a14cd7..8544cdd143dfff 100644 --- a/homeassistant/components/solaredge/translations/ko.json +++ b/homeassistant/components/solaredge/translations/ko.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "site_exists": "\uc774 site_id \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "site_exists": "\uc774 site_id \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/solarlog/translations/ko.json b/homeassistant/components/solarlog/translations/ko.json index 66c6a2177d4eff..22002c52cef36e 100644 --- a/homeassistant/components/solarlog/translations/ko.json +++ b/homeassistant/components/solarlog/translations/ko.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \uc8fc\uc18c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/soma/translations/ko.json b/homeassistant/components/soma/translations/ko.json index b987c7b2b73e7a..83c2f01ff8bbb9 100644 --- a/homeassistant/components/soma/translations/ko.json +++ b/homeassistant/components/soma/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "\ud558\ub098\uc758 Soma \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "connection_error": "SOMA Connect \uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Soma \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "result_error": "SOMA Connect \uac00 \uc624\ub958 \uc0c1\ud0dc\ub85c \uc751\ub2f5\ud588\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/somfy/translations/ko.json b/homeassistant/components/somfy/translations/ko.json index 5119670f766d83..8b4f4ff752f67d 100644 --- a/homeassistant/components/somfy/translations/ko.json +++ b/homeassistant/components/somfy/translations/ko.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Somfy \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})", - "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568." + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Somfy \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "pick_implementation": { - "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/somfy_mylink/translations/ko.json b/homeassistant/components/somfy_mylink/translations/ko.json new file mode 100644 index 00000000000000..4d4a78ee1f01c3 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/ko.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/ko.json b/homeassistant/components/sonarr/translations/ko.json index fe650991778f8d..17e3592d509260 100644 --- a/homeassistant/components/sonarr/translations/ko.json +++ b/homeassistant/components/sonarr/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -10,14 +11,17 @@ }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + }, "user": { "data": { "api_key": "API \ud0a4", "base_path": "API \uacbd\ub85c", "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8", - "ssl": "Sonarr \ub294 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4", - "verify_ssl": "Sonarr \ub294 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4" + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" } } } diff --git a/homeassistant/components/sonos/translations/ko.json b/homeassistant/components/sonos/translations/ko.json index c92b50a0f83933..ba85f8df170222 100644 --- a/homeassistant/components/sonos/translations/ko.json +++ b/homeassistant/components/sonos/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Sonos \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Sonos \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/speedtestdotnet/translations/ko.json b/homeassistant/components/speedtestdotnet/translations/ko.json index ede64fa053119d..2951d72d2018a4 100644 --- a/homeassistant/components/speedtestdotnet/translations/ko.json +++ b/homeassistant/components/speedtestdotnet/translations/ko.json @@ -1,11 +1,12 @@ { "config": { "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", "wrong_server_id": "\uc11c\ubc84 ID \uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { - "description": "SpeedTest \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } }, diff --git a/homeassistant/components/spider/translations/ko.json b/homeassistant/components/spider/translations/ko.json index 1f08b96ee10939..9e9ed5b0f3026f 100644 --- a/homeassistant/components/spider/translations/ko.json +++ b/homeassistant/components/spider/translations/ko.json @@ -1,8 +1,19 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, "error": { - "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/ko.json b/homeassistant/components/spotify/translations/ko.json index a12162bb4fb629..22a338a8d7cc95 100644 --- a/homeassistant/components/spotify/translations/ko.json +++ b/homeassistant/components/spotify/translations/ko.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Spotify \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "reauth_account_mismatch": "\uc778\uc99d\ub41c Spotify \uacc4\uc815\uc740 \uc7ac\uc778\uc99d\uc774 \ud544\uc694\ud55c \uacc4\uc815\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "create_entry": { @@ -15,7 +15,7 @@ }, "reauth_confirm": { "description": "Spotify \ud1b5\ud569\uc740 \uacc4\uc815 {account} \ub300\ud574 Spotify\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4.", - "title": "Spotify\ub85c \uc7ac \uc778\uc99d" + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" } } } diff --git a/homeassistant/components/srp_energy/translations/ko.json b/homeassistant/components/srp_energy/translations/ko.json new file mode 100644 index 00000000000000..4b6af62638af11 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index 6989f6515a1e4e..efc20dbe03f570 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -1,12 +1,14 @@ { "config": { "abort": { - "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "missing_data": "\ub204\ub77d\ub41c \ub370\uc774\ud130: \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uac70\ub098 \ub2e4\ub978 \uad6c\uc131\uc744 \uc2dc\ub3c4\ud574\ubcf4\uc138\uc694", "otp_failed": "2\ub2e8\uacc4 \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \uc0c8\ub85c\uc6b4 \ud328\uc2a4 \ucf54\ub4dc\ub85c \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694", - "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 \ub85c\uadf8\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694" + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Synology DSM: {name} ({host})", "step": { @@ -20,8 +22,9 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", - "ssl": "SSL/TLS \ub97c \uc0ac\uc6a9\ud558\uc5ec NAS \uc5d0 \uc5f0\uacb0", - "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Synology DSM" @@ -31,8 +34,9 @@ "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", - "ssl": "SSL/TLS \ub97c \uc0ac\uc6a9\ud558\uc5ec NAS \uc5d0 \uc5f0\uacb0", - "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "title": "Synology DSM" } diff --git a/homeassistant/components/tado/translations/ko.json b/homeassistant/components/tado/translations/ko.json index 8982b68829baf0..8290e32c4c868a 100644 --- a/homeassistant/components/tado/translations/ko.json +++ b/homeassistant/components/tado/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_homes": "\uc774 Tado \uacc4\uc815\uc5d0 \uc5f0\uacb0\ub41c \uc9d1\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/tasmota/translations/ko.json b/homeassistant/components/tasmota/translations/ko.json new file mode 100644 index 00000000000000..c6e52d209e7bfc --- /dev/null +++ b/homeassistant/components/tasmota/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/ko.json b/homeassistant/components/tellduslive/translations/ko.json index 645b7233bd75a1..d29dd50484414c 100644 --- a/homeassistant/components/tellduslive/translations/ko.json +++ b/homeassistant/components/tellduslive/translations/ko.json @@ -1,10 +1,14 @@ { "config": { "abort": { - "already_configured": "TelldusLive \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", + "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "auth": { diff --git a/homeassistant/components/tesla/translations/ko.json b/homeassistant/components/tesla/translations/ko.json index 27a96518ca728d..3e3893e0b7531f 100644 --- a/homeassistant/components/tesla/translations/ko.json +++ b/homeassistant/components/tesla/translations/ko.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tibber/translations/ko.json b/homeassistant/components/tibber/translations/ko.json index 1f99aa440b495c..5ba1f62e4ed137 100644 --- a/homeassistant/components/tibber/translations/ko.json +++ b/homeassistant/components/tibber/translations/ko.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Tibber \uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "timeout": "Tibber \uc5f0\uacb0 \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4." }, diff --git a/homeassistant/components/tile/translations/ko.json b/homeassistant/components/tile/translations/ko.json index 50ba5000a1a5c5..d592fef112cb9e 100644 --- a/homeassistant/components/tile/translations/ko.json +++ b/homeassistant/components/tile/translations/ko.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "\uc774 Tile \uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/toon/translations/ko.json b/homeassistant/components/toon/translations/ko.json index 379058f68d1109..faed1fe74d77d7 100644 --- a/homeassistant/components/toon/translations/ko.json +++ b/homeassistant/components/toon/translations/ko.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "\uc120\ud0dd\ub41c \uc57d\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." }, "step": { "agreement": { diff --git a/homeassistant/components/totalconnect/translations/ko.json b/homeassistant/components/totalconnect/translations/ko.json index 99513a64508c9a..c074472b8f4c6d 100644 --- a/homeassistant/components/totalconnect/translations/ko.json +++ b/homeassistant/components/totalconnect/translations/ko.json @@ -1,7 +1,10 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/tplink/translations/ko.json b/homeassistant/components/tplink/translations/ko.json index dc8a6a5a8fc9ab..e1ff7eff3722ca 100644 --- a/homeassistant/components/tplink/translations/ko.json +++ b/homeassistant/components/tplink/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "TP-Link \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 TP-Link \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/traccar/translations/ko.json b/homeassistant/components/traccar/translations/ko.json index 910281d4b38009..04e13a9aa6f91a 100644 --- a/homeassistant/components/traccar/translations/ko.json +++ b/homeassistant/components/traccar/translations/ko.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c URL \uc815\ubcf4\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/tradfri/translations/ko.json b/homeassistant/components/tradfri/translations/ko.json index caa94fa8b10e26..067a10c6490b33 100644 --- a/homeassistant/components/tradfri/translations/ko.json +++ b/homeassistant/components/tradfri/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\ube0c\ub9ac\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_key": "\uc81c\uacf5\ub41c \ud0a4\ub85c \ub4f1\ub85d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774 \ubb38\uc81c\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\ubcf4\uc138\uc694.", "timeout": "\ucf54\ub4dc \uc720\ud6a8\uc131 \uac80\uc0ac \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, diff --git a/homeassistant/components/transmission/translations/ko.json b/homeassistant/components/transmission/translations/ko.json index 7f5d67114a1cd2..002e374e54d179 100644 --- a/homeassistant/components/transmission/translations/ko.json +++ b/homeassistant/components/transmission/translations/ko.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\ud638\uc2a4\ud2b8\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4" }, "step": { diff --git a/homeassistant/components/tuya/translations/it.json b/homeassistant/components/tuya/translations/it.json index 639f58349226a0..729514d35416a1 100644 --- a/homeassistant/components/tuya/translations/it.json +++ b/homeassistant/components/tuya/translations/it.json @@ -40,8 +40,10 @@ "max_temp": "Temperatura di destinazione massima (utilizzare min e max = 0 per impostazione predefinita)", "min_kelvin": "Temperatura colore minima supportata in kelvin", "min_temp": "Temperatura di destinazione minima (utilizzare min e max = 0 per impostazione predefinita)", + "set_temp_divided": "Utilizzare il valore temperatura diviso per impostare il comando temperatura", "support_color": "Forza il supporto del colore", "temp_divider": "Divisore dei valori di temperatura (0 = utilizzare il valore predefinito)", + "temp_step_override": "Passo della temperatura da raggiungere", "tuya_max_coltemp": "Temperatura di colore massima riportata dal dispositivo", "unit_of_measurement": "Unit\u00e0 di temperatura utilizzata dal dispositivo" }, diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index e123bc2b6f9921..81dd2689b0c08f 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -1,8 +1,13 @@ { "config": { "abort": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "flow_title": "Tuya \uad6c\uc131\ud558\uae30", "step": { "user": { @@ -16,5 +21,10 @@ "title": "Tuya" } } + }, + "options": { + "abort": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + } } } \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/ko.json b/homeassistant/components/twentemilieu/translations/ko.json index 3efc227abf7203..e6c19d40d0622c 100644 --- a/homeassistant/components/twentemilieu/translations/ko.json +++ b/homeassistant/components/twentemilieu/translations/ko.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_address": "Twente Milieu \uc11c\ube44\uc2a4 \uc9c0\uc5ed\uc5d0\uc11c \uc8fc\uc18c\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { diff --git a/homeassistant/components/twilio/translations/ko.json b/homeassistant/components/twilio/translations/ko.json index b6be32e1de4190..72165dfb798842 100644 --- a/homeassistant/components/twilio/translations/ko.json +++ b/homeassistant/components/twilio/translations/ko.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, "create_entry": { "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "Twilio \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Twilio \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/twinkly/translations/ko.json b/homeassistant/components/twinkly/translations/ko.json new file mode 100644 index 00000000000000..207037cba606dc --- /dev/null +++ b/homeassistant/components/twinkly/translations/ko.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "device_exists": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index 94160829bad4c6..454feec0922794 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\ucee8\ud2b8\ub864\ub7ec \uc0ac\uc774\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\ucee8\ud2b8\ub864\ub7ec \uc0ac\uc774\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "faulty_credentials": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -16,7 +17,7 @@ "port": "\ud3ec\ud2b8", "site": "\uc0ac\uc774\ud2b8 ID", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", - "verify_ssl": "\uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\ub294 \ucee8\ud2b8\ub864\ub7ec" + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "title": "UniFi \ucee8\ud2b8\ub864\ub7ec \uc124\uc815\ud558\uae30" } diff --git a/homeassistant/components/upb/translations/ko.json b/homeassistant/components/upb/translations/ko.json index 48cc545d87b4ae..da357f7a136816 100644 --- a/homeassistant/components/upb/translations/ko.json +++ b/homeassistant/components/upb/translations/ko.json @@ -1,9 +1,12 @@ { "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "error": { - "cannot_connect": "UPB PIM \uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_upb_file": "UPB UPStart \ub0b4\ubcf4\ub0b4\uae30 \ud30c\uc77c\uc774 \uc5c6\uac70\ub098 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud30c\uc77c \uc774\ub984\uacfc \uacbd\ub85c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/ko.json b/homeassistant/components/upcloud/translations/ko.json new file mode 100644 index 00000000000000..04360a9a8f79d8 --- /dev/null +++ b/homeassistant/components/upcloud/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index 1a7b52049309a2..7dd2e5c685c6e6 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "UPnP/IGD \uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4", + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "incomplete_discovery": "\uae30\uae30 \uac80\uc0c9\uc774 \uc644\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", - "no_devices_found": "UPnP/IGD \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "flow_title": "UPnP/IGD: {name}", "step": { diff --git a/homeassistant/components/velbus/translations/ko.json b/homeassistant/components/velbus/translations/ko.json index 3d23ff3727dc41..fa9c4f7496d4a3 100644 --- a/homeassistant/components/velbus/translations/ko.json +++ b/homeassistant/components/velbus/translations/ko.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vera/translations/ko.json b/homeassistant/components/vera/translations/ko.json index 1556b9fe0d2f52..e8658528488d63 100644 --- a/homeassistant/components/vera/translations/ko.json +++ b/homeassistant/components/vera/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "URL {base_url} \uc5d0 \ucee8\ud2b8\ub864\ub7ec\ub97c \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + "cannot_connect": "{base_url} URL \uc8fc\uc18c\uc758 \ucee8\ud2b8\ub864\ub7ec\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "step": { "user": { @@ -10,7 +10,7 @@ "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant \uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4.", "vera_controller_url": "\ucee8\ud2b8\ub864\ub7ec URL" }, - "description": "\uc544\ub798\uc5d0 Vera \ucee8\ud2b8\ub864\ub7ec URL \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. http://192.168.1.161:3480 \uacfc \uac19\uc740 \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.", + "description": "\uc544\ub798\uc5d0 Vera \ucee8\ud2b8\ub864\ub7ec URL \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. http://192.168.1.161:3480 \uacfc \uac19\uc740 \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.", "title": "Vera \ucee8\ud2b8\ub864\ub7ec \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/vesync/translations/ko.json b/homeassistant/components/vesync/translations/ko.json index 888bcd66231dd0..d11e9f9459d698 100644 --- a/homeassistant/components/vesync/translations/ko.json +++ b/homeassistant/components/vesync/translations/ko.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vilfo/translations/ko.json b/homeassistant/components/vilfo/translations/ko.json index 70a315ae703095..4b79130c750290 100644 --- a/homeassistant/components/vilfo/translations/ko.json +++ b/homeassistant/components/vilfo/translations/ko.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "\uc774 Vilfo \ub77c\uc6b0\ud130\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc785\ub825\ud558\uc2e0 \ub0b4\uc6a9\uc744 \ud655\uc778\ud558\uc2e0 \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \ud655\uc778\ud558\uc2e0 \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "unknown": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud558\ub294 \uc911 \uc608\uae30\uce58 \uc54a\uc740 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index ef10cb1f4fc857..037c85d7c4eab0 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "updated_entry": "\uc774 \ud56d\ubaa9\uc740 \uc774\ubbf8 \uc124\uc815\ub418\uc5c8\uc9c0\ub9cc \uad6c\uc131\uc5d0 \uc815\uc758\ub41c \uc774\ub984, \uc571 \ud639\uc740 \uc635\uc158\uc774 \uc774\uc804\uc5d0 \uac00\uc838\uc628 \uad6c\uc131 \ub0b4\uc6a9\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc73c\ubbc0\ub85c \uad6c\uc131 \ud56d\ubaa9\uc774 \uadf8\uc5d0 \ub530\ub77c \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { @@ -12,7 +13,7 @@ "step": { "pair_tv": { "data": { - "pin": "PIN" + "pin": "PIN \ucf54\ub4dc" }, "description": "TV \uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc785\ub825\ub780\uc5d0 \uc785\ub825\ud55c \ud6c4 \ub2e4\uc74c \ub2e8\uacc4\ub97c \uacc4\uc18d\ud558\uc5ec \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "\ud398\uc5b4\ub9c1 \uacfc\uc815 \ub05d\ub0b4\uae30" diff --git a/homeassistant/components/volumio/translations/ko.json b/homeassistant/components/volumio/translations/ko.json new file mode 100644 index 00000000000000..2c630e533ff590 --- /dev/null +++ b/homeassistant/components/volumio/translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/ko.json b/homeassistant/components/wemo/translations/ko.json index a262f7ebd3e297..5673c049422e5d 100644 --- a/homeassistant/components/wemo/translations/ko.json +++ b/homeassistant/components/wemo/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Wemo \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Wemo \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/wiffi/translations/ko.json b/homeassistant/components/wiffi/translations/ko.json index c332d3e5f26e5f..74c6568feef550 100644 --- a/homeassistant/components/wiffi/translations/ko.json +++ b/homeassistant/components/wiffi/translations/ko.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "\uc11c\ubc84 \ud3ec\ud2b8" + "port": "\ud3ec\ud2b8" }, "title": "WIFFI \uae30\uae30\uc6a9 TCP \uc11c\ubc84 \uc124\uc815\ud558\uae30" } diff --git a/homeassistant/components/wilight/translations/ko.json b/homeassistant/components/wilight/translations/ko.json index 677b104c065634..e18250811fcc05 100644 --- a/homeassistant/components/wilight/translations/ko.json +++ b/homeassistant/components/wilight/translations/ko.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "not_wilight_device": "\uc774 \uc7a5\uce58\ub294 WiLight\uac00 \uc544\ub2d9\ub2c8\ub2e4." }, "step": { diff --git a/homeassistant/components/withings/translations/ko.json b/homeassistant/components/withings/translations/ko.json index 902f3c77e686df..38ed96dca67941 100644 --- a/homeassistant/components/withings/translations/ko.json +++ b/homeassistant/components/withings/translations/ko.json @@ -2,13 +2,16 @@ "config": { "abort": { "already_configured": "\ud504\ub85c\ud544\uc5d0 \ub300\ud55c \uad6c\uc131\uc774 \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Withings \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "create_entry": { "default": "Withings \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, + "error": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "flow_title": "Withings: {profile}", "step": { "pick_implementation": { @@ -23,7 +26,7 @@ }, "reauth": { "description": "Withings \ub370\uc774\ud130\ub97c \uacc4\uc18d \uc218\uc2e0\ud558\ub824\uba74 \"{profile}\" \ud504\ub85c\ud544\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", - "title": "\ud504\ub85c\ud544 \uc7ac\uc778\uc99d\ud558\uae30" + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" } } } diff --git a/homeassistant/components/wled/translations/ko.json b/homeassistant/components/wled/translations/ko.json index 2adb7985fd3c5c..d1945707b6d0a0 100644 --- a/homeassistant/components/wled/translations/ko.json +++ b/homeassistant/components/wled/translations/ko.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "\uc774 WLED \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "WLED: {name}", "step": { diff --git a/homeassistant/components/wolflink/translations/sensor.ko.json b/homeassistant/components/wolflink/translations/sensor.ko.json index 5b6c33d7231004..71fde05a07a0ad 100644 --- a/homeassistant/components/wolflink/translations/sensor.ko.json +++ b/homeassistant/components/wolflink/translations/sensor.ko.json @@ -14,7 +14,8 @@ "auto_off_cool": "\ub0c9\ubc29 \uc790\ub3d9 \uaebc\uc9d0", "auto_on_cool": "\ub0c9\ubc29 \uc790\ub3d9 \ucf1c\uc9d0", "automatik_aus": "\uc790\ub3d9 \uaebc\uc9d0", - "automatik_ein": "\uc790\ub3d9 \ucf1c\uc9d0" + "automatik_ein": "\uc790\ub3d9 \ucf1c\uc9d0", + "permanent": "\uc601\uad6c\uc801" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/ko.json b/homeassistant/components/xbox/translations/ko.json new file mode 100644 index 00000000000000..7314d9e3c5c4ea --- /dev/null +++ b/homeassistant/components/xbox/translations/ko.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index 1b4e11c6ea3ff0..7c15bc572e56e5 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "not_xiaomi_aqara": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uac00 \uc544\ub2d9\ub2c8\ub2e4. \ubc1c\uacac\ub41c \uae30\uae30\uac00 \uc54c\ub824\uc9c4 \uac8c\uc774\ud2b8\uc6e8\uc774\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, "error": { "discovery_error": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ubc1c\uacac\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. HomeAssistant \ub97c \uc778\ud130\ud398\uc774\uc2a4\ub85c \uc0ac\uc6a9\ud558\ub294 \uae30\uae30\uc758 IP \ub85c \uc2dc\ub3c4\ud574\ubcf4\uc138\uc694.", + "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694", "invalid_interface": "\ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, @@ -14,9 +15,9 @@ "step": { "select": { "data": { - "select_ip": "\uac8c\uc774\ud2b8\uc6e8\uc774 IP" + "select_ip": "IP \uc8fc\uc18c" }, - "description": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc5f0\uacb0\uc744 \ucd94\uac00\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694", + "description": "\uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucd94\uac00 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694", "title": "\uc5f0\uacb0\ud560 Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774 \uc120\ud0dd\ud558\uae30" }, "settings": { @@ -33,7 +34,7 @@ "interface": "\uc0ac\uc6a9\ud560 \ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4", "mac": "Mac \uc8fc\uc18c(\uc120\ud0dd \uc0ac\ud56d)" }, - "description": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \ubc0f Mac \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", + "description": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c \ubc0f MAC \uc8fc\uc18c\ub97c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774" } } diff --git a/homeassistant/components/xiaomi_miio/translations/cs.json b/homeassistant/components/xiaomi_miio/translations/cs.json index 91c30a69e54054..ec275b9333036b 100644 --- a/homeassistant/components/xiaomi_miio/translations/cs.json +++ b/homeassistant/components/xiaomi_miio/translations/cs.json @@ -10,6 +10,12 @@ }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP adresa", + "token": "API token" + } + }, "gateway": { "data": { "host": "IP adresa", diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index cbfc2d60621a5c..68202e1631e5fe 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -6,10 +6,20 @@ }, "error": { "cannot_connect": "Impossibile connettersi", - "no_device_selected": "Nessun dispositivo selezionato, selezionare un dispositivo." + "no_device_selected": "Nessun dispositivo selezionato, selezionare un dispositivo.", + "unknown_device": "Il modello del dispositivo non \u00e8 noto, non \u00e8 possibile configurare il dispositivo utilizzando il flusso di configurazione." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "Indirizzo IP", + "name": "Nome del dispositivo", + "token": "Token API" + }, + "description": "Avrai bisogno dei 32 caratteri Token API , vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni. Tieni presente che questa Token API \u00e8 diversa dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", + "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" + }, "gateway": { "data": { "host": "Indirizzo IP", diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json index 52f1ed960d2d9a..7e594fde247af6 100644 --- a/homeassistant/components/xiaomi_miio/translations/ko.json +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -2,20 +2,27 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "Xiaomi Miio \uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "no_device_selected": "\uc120\ud0dd\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP \uc8fc\uc18c", + "token": "API \ud1a0\ud070" + } + }, "gateway": { "data": { "host": "IP \uc8fc\uc18c", "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984", "token": "API \ud1a0\ud070" }, - "description": "32 \ubb38\uc790\uc758 API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694. \uc774 \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", + "description": "32 \uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", "title": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/yeelight/translations/ko.json b/homeassistant/components/yeelight/translations/ko.json index 7164e56b595c90..1d6974aaa61e20 100644 --- a/homeassistant/components/yeelight/translations/ko.json +++ b/homeassistant/components/yeelight/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", - "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0 \ubc1c\uacac\ub41c \uc7a5\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "pick_device": { diff --git a/homeassistant/components/zha/translations/ko.json b/homeassistant/components/zha/translations/ko.json index 93582cc9202e9d..639cc84d86f1e3 100644 --- a/homeassistant/components/zha/translations/ko.json +++ b/homeassistant/components/zha/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 ZHA \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "error": { - "cannot_connect": "ZHA \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zoneminder/translations/ko.json b/homeassistant/components/zoneminder/translations/ko.json index 3625d6e402ead4..e03da9ed8fa9f8 100644 --- a/homeassistant/components/zoneminder/translations/ko.json +++ b/homeassistant/components/zoneminder/translations/ko.json @@ -2,25 +2,29 @@ "config": { "abort": { "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", - "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "create_entry": { "default": "ZoneMinder \uc11c\ubc84\uac00 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", - "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "flow_title": "ZoneMinder", "step": { "user": { "data": { "host": "\ud638\uc2a4\ud2b8 \ubc0f \ud3ec\ud2b8(\uc608: 10.10.0.4:8010)", - "password": "\uc554\ud638", + "password": "\ube44\ubc00\ubc88\ud638", "path": "ZMS \uacbd\ub85c", "path_zms": "ZMS \uacbd\ub85c", - "ssl": "ZoneMinder \uc5f0\uacb0\uc5d0 SSL \uc0ac\uc6a9", - "username": "\uc0ac\uc6a9\uc790\uba85", + "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, "title": "ZoneMinder \uc11c\ubc84\ub97c \ucd94\uac00\ud558\uc138\uc694." diff --git a/homeassistant/components/zwave/translations/ko.json b/homeassistant/components/zwave/translations/ko.json index 1357fd492c5bd6..84c7b4ee4e3ece 100644 --- a/homeassistant/components/zwave/translations/ko.json +++ b/homeassistant/components/zwave/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Z-Wave \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." }, "error": { "option_error": "Z-Wave \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. USB \uc2a4\ud2f1\uc758 \uacbd\ub85c\uac00 \uc815\ud655\ud569\ub2c8\uae4c?" @@ -12,7 +13,7 @@ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4 (\uacf5\ub780\uc73c\ub85c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uc0dd\uc131\ud569\ub2c8\ub2e4)", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" }, - "description": "\uad6c\uc131 \ubcc0\uc218\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/docs/z-wave/installation/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "description": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \ub354 \uc774\uc0c1 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc0c8\ub85c\uc6b4 \uc124\uce58\uc758 \uacbd\uc6b0 Z-Wave JS \ub97c \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694.\n\n\uad6c\uc131 \ubcc0\uc218\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/docs/z-wave/installation/ \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694", "title": "Z-Wave \uc124\uc815" } } diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json new file mode 100644 index 00000000000000..283b0aa17b6ac8 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "manual": { + "data": { + "url": "URL \uc8fc\uc18c" + } + }, + "start_addon": { + "data": { + "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" + } + }, + "user": { + "data": { + "url": "URL \uc8fc\uc18c" + } + } + } + } +} \ No newline at end of file From efa339ca543c62a191e8c93ee27d283656be3b9e Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Sun, 21 Feb 2021 03:26:17 +0100 Subject: [PATCH 0648/1818] Allow upnp ignore SSDP-discoveries (#46592) --- homeassistant/components/upnp/__init__.py | 16 ++++- homeassistant/components/upnp/config_flow.py | 13 ++++ homeassistant/components/upnp/const.py | 2 + homeassistant/components/upnp/device.py | 15 ++++- homeassistant/components/upnp/sensor.py | 6 +- tests/components/upnp/mock_device.py | 9 ++- tests/components/upnp/test_config_flow.py | 65 +++++++++++++++++++- tests/components/upnp/test_init.py | 4 ++ 8 files changed, 119 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 7b46037d99df16..5d251ce7dd823a 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -13,6 +13,7 @@ from .const import ( CONF_LOCAL_IP, + CONFIG_ENTRY_HOSTNAME, CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, DISCOVERY_LOCATION, @@ -31,7 +32,13 @@ NOTIFICATION_TITLE = "UPnP/IGD Setup" CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema({vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string)})}, + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string), + }, + ) + }, extra=vol.ALLOW_EXTRA, ) @@ -115,6 +122,13 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) unique_id=device.unique_id, ) + # Ensure entry has a hostname, for older entries. + if CONFIG_ENTRY_HOSTNAME not in config_entry.data: + hass.config_entries.async_update_entry( + entry=config_entry, + data={CONFIG_ENTRY_HOSTNAME: device.hostname, **config_entry.data}, + ) + # Create device registry entry. device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index d3811b7e18b82d..1d212441bfa532 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -10,10 +10,12 @@ from homeassistant.core import callback from .const import ( + CONFIG_ENTRY_HOSTNAME, CONFIG_ENTRY_SCAN_INTERVAL, CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, DEFAULT_SCAN_INTERVAL, + DISCOVERY_HOSTNAME, DISCOVERY_LOCATION, DISCOVERY_NAME, DISCOVERY_ST, @@ -179,6 +181,16 @@ async def async_step_ssdp(self, discovery_info: Mapping) -> Mapping[str, Any]: await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured() + # Handle devices changing their UDN, only allow a single + existing_entries = self.hass.config_entries.async_entries(DOMAIN) + for config_entry in existing_entries: + entry_hostname = config_entry.data.get(CONFIG_ENTRY_HOSTNAME) + if entry_hostname == discovery[DISCOVERY_HOSTNAME]: + _LOGGER.debug( + "Found existing config_entry with same hostname, discovery ignored" + ) + return self.async_abort(reason="discovery_ignored") + # Store discovery. self._discoveries = [discovery] @@ -222,6 +234,7 @@ async def _async_create_entry_from_discovery( data = { CONFIG_ENTRY_UDN: discovery[DISCOVERY_UDN], CONFIG_ENTRY_ST: discovery[DISCOVERY_ST], + CONFIG_ENTRY_HOSTNAME: discovery[DISCOVERY_HOSTNAME], } return self.async_create_entry(title=title, data=data) diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py index 4ccf6d3d7ea4bc..6575139c4a4e0c 100644 --- a/homeassistant/components/upnp/const.py +++ b/homeassistant/components/upnp/const.py @@ -21,6 +21,7 @@ DATA_RATE_PACKETS_PER_SECOND = f"{DATA_PACKETS}/{TIME_SECONDS}" KIBIBYTE = 1024 UPDATE_INTERVAL = timedelta(seconds=30) +DISCOVERY_HOSTNAME = "hostname" DISCOVERY_LOCATION = "location" DISCOVERY_NAME = "name" DISCOVERY_ST = "st" @@ -30,4 +31,5 @@ CONFIG_ENTRY_SCAN_INTERVAL = "scan_interval" CONFIG_ENTRY_ST = "st" CONFIG_ENTRY_UDN = "udn" +CONFIG_ENTRY_HOSTNAME = "hostname" DEFAULT_SCAN_INTERVAL = timedelta(seconds=30).seconds diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 39fd09089b414b..a06ca254c87914 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -4,6 +4,7 @@ import asyncio from ipaddress import IPv4Address from typing import List, Mapping +from urllib.parse import urlparse from async_upnp_client import UpnpFactory from async_upnp_client.aiohttp import AiohttpSessionRequester @@ -17,6 +18,7 @@ BYTES_RECEIVED, BYTES_SENT, CONF_LOCAL_IP, + DISCOVERY_HOSTNAME, DISCOVERY_LOCATION, DISCOVERY_NAME, DISCOVERY_ST, @@ -66,10 +68,10 @@ async def async_supplement_discovery( cls, hass: HomeAssistantType, discovery: Mapping ) -> Mapping: """Get additional data from device and supplement discovery.""" - device = await Device.async_create_device(hass, discovery[DISCOVERY_LOCATION]) + location = discovery[DISCOVERY_LOCATION] + device = await Device.async_create_device(hass, location) discovery[DISCOVERY_NAME] = device.name - - # Set unique_id. + discovery[DISCOVERY_HOSTNAME] = device.hostname discovery[DISCOVERY_UNIQUE_ID] = discovery[DISCOVERY_USN] return discovery @@ -126,6 +128,13 @@ def unique_id(self) -> str: """Get the unique id.""" return self.usn + @property + def hostname(self) -> str: + """Get the hostname.""" + url = self._igd_device.device.device_url + parsed = urlparse(url) + return parsed.hostname + def __str__(self) -> str: """Get string representation.""" return f"IGD Device: {self.name}/{self.udn}::{self.device_type}" diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 59205f49667ca8..97d3c1a702c178 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -1,6 +1,6 @@ """Support for UPnP/IGD Sensors.""" from datetime import timedelta -from typing import Any, Mapping +from typing import Any, Mapping, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_BYTES, DATA_RATE_KIBIBYTES_PER_SECOND @@ -176,7 +176,7 @@ class RawUpnpSensor(UpnpSensor): """Representation of a UPnP/IGD sensor.""" @property - def state(self) -> str: + def state(self) -> Optional[str]: """Return the state of the device.""" device_value_key = self._sensor_type["device_value_key"] value = self.coordinator.data[device_value_key] @@ -214,7 +214,7 @@ def _has_overflowed(self, current_value) -> bool: return current_value < self._last_value @property - def state(self) -> str: + def state(self) -> Optional[str]: """Return the state of the device.""" # Can't calculate any derivative if we have only one value. device_value_key = self._sensor_type["device_value_key"] diff --git a/tests/components/upnp/mock_device.py b/tests/components/upnp/mock_device.py index a70b3fa02370b2..d6027608137355 100644 --- a/tests/components/upnp/mock_device.py +++ b/tests/components/upnp/mock_device.py @@ -16,14 +16,14 @@ class MockDevice(Device): """Mock device for Device.""" - def __init__(self, udn): + def __init__(self, udn: str) -> None: """Initialize mock device.""" igd_device = object() super().__init__(igd_device) self._udn = udn @classmethod - async def async_create_device(cls, hass, ssdp_location): + async def async_create_device(cls, hass, ssdp_location) -> "MockDevice": """Return self.""" return cls("UDN") @@ -52,6 +52,11 @@ def device_type(self) -> str: """Get the device type.""" return "urn:schemas-upnp-org:device:InternetGatewayDevice:1" + @property + def hostname(self) -> str: + """Get the hostname.""" + return "mock-hostname" + async def async_get_traffic_data(self) -> Mapping[str, any]: """Get traffic data.""" return { diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index f702d770ee6291..77d04381a12c9b 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -6,10 +6,12 @@ from homeassistant import config_entries, data_entry_flow from homeassistant.components import ssdp from homeassistant.components.upnp.const import ( + CONFIG_ENTRY_HOSTNAME, CONFIG_ENTRY_SCAN_INTERVAL, CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, DEFAULT_SCAN_INTERVAL, + DISCOVERY_HOSTNAME, DISCOVERY_LOCATION, DISCOVERY_NAME, DISCOVERY_ST, @@ -41,6 +43,7 @@ async def test_flow_ssdp_discovery(hass: HomeAssistantType): DISCOVERY_UDN: mock_device.udn, DISCOVERY_UNIQUE_ID: mock_device.unique_id, DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, } ] with patch.object( @@ -75,10 +78,11 @@ async def test_flow_ssdp_discovery(hass: HomeAssistantType): assert result["data"] == { CONFIG_ENTRY_ST: mock_device.device_type, CONFIG_ENTRY_UDN: mock_device.udn, + CONFIG_ENTRY_HOSTNAME: mock_device.hostname, } -async def test_flow_ssdp_discovery_incomplete(hass: HomeAssistantType): +async def test_flow_ssdp_incomplete_discovery(hass: HomeAssistantType): """Test config flow: incomplete discovery through ssdp.""" udn = "uuid:device_1" location = "dummy" @@ -89,15 +93,64 @@ async def test_flow_ssdp_discovery_incomplete(hass: HomeAssistantType): DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data={ + ssdp.ATTR_SSDP_LOCATION: location, ssdp.ATTR_SSDP_ST: mock_device.device_type, + ssdp.ATTR_SSDP_USN: mock_device.usn, # ssdp.ATTR_UPNP_UDN: mock_device.udn, # Not provided. - ssdp.ATTR_SSDP_LOCATION: location, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "incomplete_discovery" +async def test_flow_ssdp_discovery_ignored(hass: HomeAssistantType): + """Test config flow: discovery through ssdp, but ignored.""" + udn = "uuid:device_random_1" + location = "dummy" + mock_device = MockDevice(udn) + + # Existing entry. + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONFIG_ENTRY_UDN: "uuid:device_random_2", + CONFIG_ENTRY_ST: mock_device.device_type, + CONFIG_ENTRY_HOSTNAME: mock_device.hostname, + }, + options={CONFIG_ENTRY_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL}, + ) + config_entry.add_to_hass(hass) + + discoveries = [ + { + DISCOVERY_LOCATION: location, + DISCOVERY_NAME: mock_device.name, + DISCOVERY_ST: mock_device.device_type, + DISCOVERY_UDN: mock_device.udn, + DISCOVERY_UNIQUE_ID: mock_device.unique_id, + DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, + } + ] + + with patch.object( + Device, "async_supplement_discovery", AsyncMock(return_value=discoveries[0]) + ): + # Discovered via step ssdp, but ignored. + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data={ + ssdp.ATTR_SSDP_LOCATION: location, + ssdp.ATTR_SSDP_ST: mock_device.device_type, + ssdp.ATTR_SSDP_USN: mock_device.usn, + ssdp.ATTR_UPNP_UDN: mock_device.udn, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "discovery_ignored" + + async def test_flow_user(hass: HomeAssistantType): """Test config flow: discovered + configured through user.""" udn = "uuid:device_1" @@ -111,6 +164,7 @@ async def test_flow_user(hass: HomeAssistantType): DISCOVERY_UDN: mock_device.udn, DISCOVERY_UNIQUE_ID: mock_device.unique_id, DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, } ] @@ -139,6 +193,7 @@ async def test_flow_user(hass: HomeAssistantType): assert result["data"] == { CONFIG_ENTRY_ST: mock_device.device_type, CONFIG_ENTRY_UDN: mock_device.udn, + CONFIG_ENTRY_HOSTNAME: mock_device.hostname, } @@ -155,6 +210,7 @@ async def test_flow_import(hass: HomeAssistantType): DISCOVERY_UDN: mock_device.udn, DISCOVERY_UNIQUE_ID: mock_device.unique_id, DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, } ] @@ -175,6 +231,7 @@ async def test_flow_import(hass: HomeAssistantType): assert result["data"] == { CONFIG_ENTRY_ST: mock_device.device_type, CONFIG_ENTRY_UDN: mock_device.udn, + CONFIG_ENTRY_HOSTNAME: mock_device.hostname, } @@ -189,6 +246,7 @@ async def test_flow_import_already_configured(hass: HomeAssistantType): data={ CONFIG_ENTRY_UDN: mock_device.udn, CONFIG_ENTRY_ST: mock_device.device_type, + CONFIG_ENTRY_HOSTNAME: mock_device.hostname, }, options={CONFIG_ENTRY_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL}, ) @@ -216,6 +274,7 @@ async def test_flow_import_incomplete(hass: HomeAssistantType): DISCOVERY_UDN: mock_device.udn, DISCOVERY_UNIQUE_ID: mock_device.unique_id, DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, } ] @@ -243,6 +302,7 @@ async def test_options_flow(hass: HomeAssistantType): DISCOVERY_UDN: mock_device.udn, DISCOVERY_UNIQUE_ID: mock_device.unique_id, DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, } ] config_entry = MockConfigEntry( @@ -250,6 +310,7 @@ async def test_options_flow(hass: HomeAssistantType): data={ CONFIG_ENTRY_UDN: mock_device.udn, CONFIG_ENTRY_ST: mock_device.device_type, + CONFIG_ENTRY_HOSTNAME: mock_device.hostname, }, options={CONFIG_ENTRY_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL}, ) diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 26b2f970feda82..086fbd677abed9 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -5,6 +5,7 @@ from homeassistant.components.upnp.const import ( CONFIG_ENTRY_ST, CONFIG_ENTRY_UDN, + DISCOVERY_HOSTNAME, DISCOVERY_LOCATION, DISCOVERY_NAME, DISCOVERY_ST, @@ -35,6 +36,7 @@ async def test_async_setup_entry_default(hass: HomeAssistantType): DISCOVERY_UDN: mock_device.udn, DISCOVERY_UNIQUE_ID: mock_device.unique_id, DISCOVERY_USN: mock_device.usn, + DISCOVERY_HOSTNAME: mock_device.hostname, } ] entry = MockConfigEntry( @@ -83,6 +85,7 @@ async def test_sync_setup_entry_multiple_discoveries(hass: HomeAssistantType): DISCOVERY_UDN: mock_device_0.udn, DISCOVERY_UNIQUE_ID: mock_device_0.unique_id, DISCOVERY_USN: mock_device_0.usn, + DISCOVERY_HOSTNAME: mock_device_0.hostname, }, { DISCOVERY_LOCATION: location_1, @@ -91,6 +94,7 @@ async def test_sync_setup_entry_multiple_discoveries(hass: HomeAssistantType): DISCOVERY_UDN: mock_device_1.udn, DISCOVERY_UNIQUE_ID: mock_device_1.unique_id, DISCOVERY_USN: mock_device_1.usn, + DISCOVERY_HOSTNAME: mock_device_1.hostname, }, ] entry = MockConfigEntry( From 3ad207a499b96d71283b18c38155e6722ed51861 Mon Sep 17 00:00:00 2001 From: Garrett <7310260+G-Two@users.noreply.github.com> Date: Sat, 20 Feb 2021 21:52:44 -0500 Subject: [PATCH 0649/1818] Add new Subaru integration (#35760) Co-authored-by: On Freund Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + homeassistant/components/subaru/__init__.py | 173 +++++++++++ .../components/subaru/config_flow.py | 157 ++++++++++ homeassistant/components/subaru/const.py | 47 +++ homeassistant/components/subaru/entity.py | 39 +++ homeassistant/components/subaru/manifest.json | 8 + homeassistant/components/subaru/sensor.py | 265 ++++++++++++++++ homeassistant/components/subaru/strings.json | 45 +++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/subaru/__init__.py | 1 + tests/components/subaru/api_responses.py | 284 ++++++++++++++++++ tests/components/subaru/conftest.py | 139 +++++++++ tests/components/subaru/test_config_flow.py | 250 +++++++++++++++ tests/components/subaru/test_init.py | 153 ++++++++++ tests/components/subaru/test_sensor.py | 67 +++++ 17 files changed, 1636 insertions(+) create mode 100644 homeassistant/components/subaru/__init__.py create mode 100644 homeassistant/components/subaru/config_flow.py create mode 100644 homeassistant/components/subaru/const.py create mode 100644 homeassistant/components/subaru/entity.py create mode 100644 homeassistant/components/subaru/manifest.json create mode 100644 homeassistant/components/subaru/sensor.py create mode 100644 homeassistant/components/subaru/strings.json create mode 100644 tests/components/subaru/__init__.py create mode 100644 tests/components/subaru/api_responses.py create mode 100644 tests/components/subaru/conftest.py create mode 100644 tests/components/subaru/test_config_flow.py create mode 100644 tests/components/subaru/test_init.py create mode 100644 tests/components/subaru/test_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index f3c7487a520506..788f36361434bc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -450,6 +450,7 @@ homeassistant/components/stiebel_eltron/* @fucm homeassistant/components/stookalert/* @fwestenberg homeassistant/components/stream/* @hunterjm @uvjustin homeassistant/components/stt/* @pvizeli +homeassistant/components/subaru/* @G-Two homeassistant/components/suez_water/* @ooii homeassistant/components/sun/* @Swamp-Ig homeassistant/components/supla/* @mwegrzynek diff --git a/homeassistant/components/subaru/__init__.py b/homeassistant/components/subaru/__init__.py new file mode 100644 index 00000000000000..63bc644b50ab4a --- /dev/null +++ b/homeassistant/components/subaru/__init__.py @@ -0,0 +1,173 @@ +"""The Subaru integration.""" +import asyncio +from datetime import timedelta +import logging +import time + +from subarulink import Controller as SubaruAPI, InvalidCredentials, SubaruException + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import ( + CONF_COUNTRY, + CONF_UPDATE_ENABLED, + COORDINATOR_NAME, + DOMAIN, + ENTRY_CONTROLLER, + ENTRY_COORDINATOR, + ENTRY_VEHICLES, + FETCH_INTERVAL, + SUPPORTED_PLATFORMS, + UPDATE_INTERVAL, + VEHICLE_API_GEN, + VEHICLE_HAS_EV, + VEHICLE_HAS_REMOTE_SERVICE, + VEHICLE_HAS_REMOTE_START, + VEHICLE_HAS_SAFETY_SERVICE, + VEHICLE_LAST_UPDATE, + VEHICLE_NAME, + VEHICLE_VIN, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, base_config): + """Do nothing since this integration does not support configuration.yml setup.""" + hass.data.setdefault(DOMAIN, {}) + return True + + +async def async_setup_entry(hass, entry): + """Set up Subaru from a config entry.""" + config = entry.data + websession = aiohttp_client.async_get_clientsession(hass) + try: + controller = SubaruAPI( + websession, + config[CONF_USERNAME], + config[CONF_PASSWORD], + config[CONF_DEVICE_ID], + config[CONF_PIN], + None, + config[CONF_COUNTRY], + update_interval=UPDATE_INTERVAL, + fetch_interval=FETCH_INTERVAL, + ) + _LOGGER.debug("Using subarulink %s", controller.version) + await controller.connect() + except InvalidCredentials: + _LOGGER.error("Invalid account") + return False + except SubaruException as err: + raise ConfigEntryNotReady(err.message) from err + + vehicle_info = {} + for vin in controller.get_vehicles(): + vehicle_info[vin] = get_vehicle_info(controller, vin) + + async def async_update_data(): + """Fetch data from API endpoint.""" + try: + return await refresh_subaru_data(entry, vehicle_info, controller) + except SubaruException as err: + raise UpdateFailed(err.message) from err + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=COORDINATOR_NAME, + update_method=async_update_data, + update_interval=timedelta(seconds=FETCH_INTERVAL), + ) + + await coordinator.async_refresh() + + hass.data[DOMAIN][entry.entry_id] = { + ENTRY_CONTROLLER: controller, + ENTRY_COORDINATOR: coordinator, + ENTRY_VEHICLES: vehicle_info, + } + + for component in SUPPORTED_PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in SUPPORTED_PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + + +async def refresh_subaru_data(config_entry, vehicle_info, controller): + """ + Refresh local data with data fetched via Subaru API. + + Subaru API calls assume a server side vehicle context + Data fetch/update must be done for each vehicle + """ + data = {} + + for vehicle in vehicle_info.values(): + vin = vehicle[VEHICLE_VIN] + + # Active subscription required + if not vehicle[VEHICLE_HAS_SAFETY_SERVICE]: + continue + + # Optionally send an "update" remote command to vehicle (throttled with update_interval) + if config_entry.options.get(CONF_UPDATE_ENABLED, False): + await update_subaru(vehicle, controller) + + # Fetch data from Subaru servers + await controller.fetch(vin, force=True) + + # Update our local data that will go to entity states + received_data = await controller.get_data(vin) + if received_data: + data[vin] = received_data + + return data + + +async def update_subaru(vehicle, controller): + """Commands remote vehicle update (polls the vehicle to update subaru API cache).""" + cur_time = time.time() + last_update = vehicle[VEHICLE_LAST_UPDATE] + + if cur_time - last_update > controller.get_update_interval(): + await controller.update(vehicle[VEHICLE_VIN], force=True) + vehicle[VEHICLE_LAST_UPDATE] = cur_time + + +def get_vehicle_info(controller, vin): + """Obtain vehicle identifiers and capabilities.""" + info = { + VEHICLE_VIN: vin, + VEHICLE_NAME: controller.vin_to_name(vin), + VEHICLE_HAS_EV: controller.get_ev_status(vin), + VEHICLE_API_GEN: controller.get_api_gen(vin), + VEHICLE_HAS_REMOTE_START: controller.get_res_status(vin), + VEHICLE_HAS_REMOTE_SERVICE: controller.get_remote_status(vin), + VEHICLE_HAS_SAFETY_SERVICE: controller.get_safety_status(vin), + VEHICLE_LAST_UPDATE: 0, + } + return info diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py new file mode 100644 index 00000000000000..4c5c476a4025ec --- /dev/null +++ b/homeassistant/components/subaru/config_flow.py @@ -0,0 +1,157 @@ +"""Config flow for Subaru integration.""" +from datetime import datetime +import logging + +from subarulink import ( + Controller as SubaruAPI, + InvalidCredentials, + InvalidPIN, + SubaruException, +) +from subarulink.const import COUNTRY_CAN, COUNTRY_USA +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client, config_validation as cv + +# pylint: disable=unused-import +from .const import CONF_COUNTRY, CONF_UPDATE_ENABLED, DOMAIN + +_LOGGER = logging.getLogger(__name__) +PIN_SCHEMA = vol.Schema({vol.Required(CONF_PIN): str}) + + +class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Subaru.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + config_data = {CONF_PIN: None} + controller = None + + async def async_step_user(self, user_input=None): + """Handle the start of the config flow.""" + error = None + + if user_input: + if user_input[CONF_USERNAME] in [ + entry.data[CONF_USERNAME] for entry in self._async_current_entries() + ]: + return self.async_abort(reason="already_configured") + + try: + await self.validate_login_creds(user_input) + except InvalidCredentials: + error = {"base": "invalid_auth"} + except SubaruException as ex: + _LOGGER.error("Unable to communicate with Subaru API: %s", ex.message) + return self.async_abort(reason="cannot_connect") + else: + if self.controller.is_pin_required(): + return await self.async_step_pin() + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=self.config_data + ) + + 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 else "", + ): str, + vol.Required( + CONF_PASSWORD, + default=user_input.get(CONF_PASSWORD) if user_input else "", + ): str, + vol.Required( + CONF_COUNTRY, + default=user_input.get(CONF_COUNTRY) + if user_input + else COUNTRY_USA, + ): vol.In([COUNTRY_CAN, COUNTRY_USA]), + } + ), + errors=error, + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + async def validate_login_creds(self, data): + """Validate the user input allows us to connect. + + data: contains values provided by the user. + """ + websession = aiohttp_client.async_get_clientsession(self.hass) + now = datetime.now() + if not data.get(CONF_DEVICE_ID): + data[CONF_DEVICE_ID] = int(now.timestamp()) + date = now.strftime("%Y-%m-%d") + device_name = "Home Assistant: Added " + date + + self.controller = SubaruAPI( + websession, + username=data[CONF_USERNAME], + password=data[CONF_PASSWORD], + device_id=data[CONF_DEVICE_ID], + pin=None, + device_name=device_name, + country=data[CONF_COUNTRY], + ) + _LOGGER.debug("Using subarulink %s", self.controller.version) + _LOGGER.debug( + "Setting up first time connection to Subuaru API. This may take up to 20 seconds." + ) + if await self.controller.connect(): + _LOGGER.debug("Successfully authenticated and authorized with Subaru API") + self.config_data.update(data) + + async def async_step_pin(self, user_input=None): + """Handle second part of config flow, if required.""" + error = None + if user_input: + if self.controller.update_saved_pin(user_input[CONF_PIN]): + try: + vol.Match(r"[0-9]{4}")(user_input[CONF_PIN]) + await self.controller.test_pin() + except vol.Invalid: + error = {"base": "bad_pin_format"} + except InvalidPIN: + error = {"base": "incorrect_pin"} + else: + _LOGGER.debug("PIN successfully tested") + self.config_data.update(user_input) + return self.async_create_entry( + title=self.config_data[CONF_USERNAME], data=self.config_data + ) + return self.async_show_form(step_id="pin", data_schema=PIN_SCHEMA, errors=error) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle a option flow for Subaru.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Handle options flow.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + data_schema = vol.Schema( + { + vol.Required( + CONF_UPDATE_ENABLED, + default=self.config_entry.options.get(CONF_UPDATE_ENABLED, False), + ): cv.boolean, + } + ) + return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py new file mode 100644 index 00000000000000..7349f9c32d6d38 --- /dev/null +++ b/homeassistant/components/subaru/const.py @@ -0,0 +1,47 @@ +"""Constants for the Subaru integration.""" + +DOMAIN = "subaru" +FETCH_INTERVAL = 300 +UPDATE_INTERVAL = 7200 +CONF_UPDATE_ENABLED = "update_enabled" +CONF_COUNTRY = "country" + +# entry fields +ENTRY_CONTROLLER = "controller" +ENTRY_COORDINATOR = "coordinator" +ENTRY_VEHICLES = "vehicles" + +# update coordinator name +COORDINATOR_NAME = "subaru_data" + +# info fields +VEHICLE_VIN = "vin" +VEHICLE_NAME = "display_name" +VEHICLE_HAS_EV = "is_ev" +VEHICLE_API_GEN = "api_gen" +VEHICLE_HAS_REMOTE_START = "has_res" +VEHICLE_HAS_REMOTE_SERVICE = "has_remote" +VEHICLE_HAS_SAFETY_SERVICE = "has_safety" +VEHICLE_LAST_UPDATE = "last_update" +VEHICLE_STATUS = "status" + + +API_GEN_1 = "g1" +API_GEN_2 = "g2" +MANUFACTURER = "Subaru Corp." + +SUPPORTED_PLATFORMS = [ + "sensor", +] + +ICONS = { + "Avg Fuel Consumption": "mdi:leaf", + "EV Time to Full Charge": "mdi:car-electric", + "EV Range": "mdi:ev-station", + "Odometer": "mdi:road-variant", + "Range": "mdi:gas-station", + "Tire Pressure FL": "mdi:gauge", + "Tire Pressure FR": "mdi:gauge", + "Tire Pressure RL": "mdi:gauge", + "Tire Pressure RR": "mdi:gauge", +} diff --git a/homeassistant/components/subaru/entity.py b/homeassistant/components/subaru/entity.py new file mode 100644 index 00000000000000..4fdeca4e484743 --- /dev/null +++ b/homeassistant/components/subaru/entity.py @@ -0,0 +1,39 @@ +"""Base class for all Subaru Entities.""" +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, ICONS, MANUFACTURER, VEHICLE_NAME, VEHICLE_VIN + + +class SubaruEntity(CoordinatorEntity): + """Representation of a Subaru Entity.""" + + def __init__(self, vehicle_info, coordinator): + """Initialize the Subaru Entity.""" + super().__init__(coordinator) + self.car_name = vehicle_info[VEHICLE_NAME] + self.vin = vehicle_info[VEHICLE_VIN] + self.entity_type = "entity" + + @property + def name(self): + """Return name.""" + return f"{self.car_name} {self.entity_type}" + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return f"{self.vin}_{self.entity_type}" + + @property + def icon(self): + """Return the icon of the sensor.""" + return ICONS.get(self.entity_type) + + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, self.vin)}, + "name": self.car_name, + "manufacturer": MANUFACTURER, + } diff --git a/homeassistant/components/subaru/manifest.json b/homeassistant/components/subaru/manifest.json new file mode 100644 index 00000000000000..7a918c59f74721 --- /dev/null +++ b/homeassistant/components/subaru/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "subaru", + "name": "Subaru", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/subaru", + "requirements": ["subarulink==0.3.12"], + "codeowners": ["@G-Two"] +} diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py new file mode 100644 index 00000000000000..594d18028e6b4c --- /dev/null +++ b/homeassistant/components/subaru/sensor.py @@ -0,0 +1,265 @@ +"""Support for Subaru sensors.""" +import subarulink.const as sc + +from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + LENGTH_KILOMETERS, + LENGTH_MILES, + PERCENTAGE, + PRESSURE_HPA, + TEMP_CELSIUS, + TIME_MINUTES, + VOLT, + VOLUME_GALLONS, + VOLUME_LITERS, +) +from homeassistant.util.distance import convert as dist_convert +from homeassistant.util.unit_system import ( + IMPERIAL_SYSTEM, + LENGTH_UNITS, + PRESSURE_UNITS, + TEMPERATURE_UNITS, +) +from homeassistant.util.volume import convert as vol_convert + +from .const import ( + API_GEN_2, + DOMAIN, + ENTRY_COORDINATOR, + ENTRY_VEHICLES, + VEHICLE_API_GEN, + VEHICLE_HAS_EV, + VEHICLE_HAS_SAFETY_SERVICE, + VEHICLE_STATUS, +) +from .entity import SubaruEntity + +L_PER_GAL = vol_convert(1, VOLUME_GALLONS, VOLUME_LITERS) +KM_PER_MI = dist_convert(1, LENGTH_MILES, LENGTH_KILOMETERS) + +# Fuel Economy Constants +FUEL_CONSUMPTION_L_PER_100KM = "L/100km" +FUEL_CONSUMPTION_MPG = "mi/gal" +FUEL_CONSUMPTION_UNITS = [FUEL_CONSUMPTION_L_PER_100KM, FUEL_CONSUMPTION_MPG] + +SENSOR_TYPE = "type" +SENSOR_CLASS = "class" +SENSOR_FIELD = "field" +SENSOR_UNITS = "units" + +# Sensor data available to "Subaru Safety Plus" subscribers with Gen1 or Gen2 vehicles +SAFETY_SENSORS = [ + { + SENSOR_TYPE: "Odometer", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.ODOMETER, + SENSOR_UNITS: LENGTH_KILOMETERS, + }, +] + +# Sensor data available to "Subaru Safety Plus" subscribers with Gen2 vehicles +API_GEN_2_SENSORS = [ + { + SENSOR_TYPE: "Avg Fuel Consumption", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.AVG_FUEL_CONSUMPTION, + SENSOR_UNITS: FUEL_CONSUMPTION_L_PER_100KM, + }, + { + SENSOR_TYPE: "Range", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.DIST_TO_EMPTY, + SENSOR_UNITS: LENGTH_KILOMETERS, + }, + { + SENSOR_TYPE: "Tire Pressure FL", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.TIRE_PRESSURE_FL, + SENSOR_UNITS: PRESSURE_HPA, + }, + { + SENSOR_TYPE: "Tire Pressure FR", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.TIRE_PRESSURE_FR, + SENSOR_UNITS: PRESSURE_HPA, + }, + { + SENSOR_TYPE: "Tire Pressure RL", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.TIRE_PRESSURE_RL, + SENSOR_UNITS: PRESSURE_HPA, + }, + { + SENSOR_TYPE: "Tire Pressure RR", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.TIRE_PRESSURE_RR, + SENSOR_UNITS: PRESSURE_HPA, + }, + { + SENSOR_TYPE: "External Temp", + SENSOR_CLASS: DEVICE_CLASS_TEMPERATURE, + SENSOR_FIELD: sc.EXTERNAL_TEMP, + SENSOR_UNITS: TEMP_CELSIUS, + }, + { + SENSOR_TYPE: "12V Battery Voltage", + SENSOR_CLASS: DEVICE_CLASS_VOLTAGE, + SENSOR_FIELD: sc.BATTERY_VOLTAGE, + SENSOR_UNITS: VOLT, + }, +] + +# Sensor data available to "Subaru Safety Plus" subscribers with PHEV vehicles +EV_SENSORS = [ + { + SENSOR_TYPE: "EV Range", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.EV_DISTANCE_TO_EMPTY, + SENSOR_UNITS: LENGTH_MILES, + }, + { + SENSOR_TYPE: "EV Battery Level", + SENSOR_CLASS: DEVICE_CLASS_BATTERY, + SENSOR_FIELD: sc.EV_STATE_OF_CHARGE_PERCENT, + SENSOR_UNITS: PERCENTAGE, + }, + { + SENSOR_TYPE: "EV Time to Full Charge", + SENSOR_CLASS: None, + SENSOR_FIELD: sc.EV_TIME_TO_FULLY_CHARGED, + SENSOR_UNITS: TIME_MINUTES, + }, +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Subaru sensors by config_entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id][ENTRY_COORDINATOR] + vehicle_info = hass.data[DOMAIN][config_entry.entry_id][ENTRY_VEHICLES] + entities = [] + for vin in vehicle_info.keys(): + entities.extend(create_vehicle_sensors(vehicle_info[vin], coordinator)) + async_add_entities(entities, True) + + +def create_vehicle_sensors(vehicle_info, coordinator): + """Instantiate all available sensors for the vehicle.""" + sensors_to_add = [] + if vehicle_info[VEHICLE_HAS_SAFETY_SERVICE]: + sensors_to_add.extend(SAFETY_SENSORS) + + if vehicle_info[VEHICLE_API_GEN] == API_GEN_2: + sensors_to_add.extend(API_GEN_2_SENSORS) + + if vehicle_info[VEHICLE_HAS_EV]: + sensors_to_add.extend(EV_SENSORS) + + return [ + SubaruSensor( + vehicle_info, + coordinator, + s[SENSOR_TYPE], + s[SENSOR_CLASS], + s[SENSOR_FIELD], + s[SENSOR_UNITS], + ) + for s in sensors_to_add + ] + + +class SubaruSensor(SubaruEntity): + """Class for Subaru sensors.""" + + def __init__( + self, vehicle_info, coordinator, entity_type, sensor_class, data_field, api_unit + ): + """Initialize the sensor.""" + super().__init__(vehicle_info, coordinator) + self.hass_type = "sensor" + self.current_value = None + self.entity_type = entity_type + self.sensor_class = sensor_class + self.data_field = data_field + self.api_unit = api_unit + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + if self.sensor_class in DEVICE_CLASSES: + return self.sensor_class + return super().device_class + + @property + def state(self): + """Return the state of the sensor.""" + self.current_value = self.get_current_value() + + if self.current_value is None: + return None + + if self.api_unit in TEMPERATURE_UNITS: + return round( + self.hass.config.units.temperature(self.current_value, self.api_unit), 1 + ) + + if self.api_unit in LENGTH_UNITS: + return round( + self.hass.config.units.length(self.current_value, self.api_unit), 1 + ) + + if self.api_unit in PRESSURE_UNITS: + if self.hass.config.units == IMPERIAL_SYSTEM: + return round( + self.hass.config.units.pressure(self.current_value, self.api_unit), + 1, + ) + + if self.api_unit in FUEL_CONSUMPTION_UNITS: + if self.hass.config.units == IMPERIAL_SYSTEM: + return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1) + + return self.current_value + + @property + def unit_of_measurement(self): + """Return the unit_of_measurement of the device.""" + if self.api_unit in TEMPERATURE_UNITS: + return self.hass.config.units.temperature_unit + + if self.api_unit in LENGTH_UNITS: + return self.hass.config.units.length_unit + + if self.api_unit in PRESSURE_UNITS: + if self.hass.config.units == IMPERIAL_SYSTEM: + return self.hass.config.units.pressure_unit + return PRESSURE_HPA + + if self.api_unit in FUEL_CONSUMPTION_UNITS: + if self.hass.config.units == IMPERIAL_SYSTEM: + return FUEL_CONSUMPTION_MPG + return FUEL_CONSUMPTION_L_PER_100KM + + return self.api_unit + + @property + def available(self): + """Return if entity is available.""" + last_update_success = super().available + if last_update_success and self.vin not in self.coordinator.data: + return False + return last_update_success + + def get_current_value(self): + """Get raw value from the coordinator.""" + value = self.coordinator.data[self.vin][VEHICLE_STATUS].get(self.data_field) + if value in sc.BAD_SENSOR_VALUES: + value = None + if isinstance(value, str): + if "." in value: + value = float(value) + else: + value = int(value) + return value diff --git a/homeassistant/components/subaru/strings.json b/homeassistant/components/subaru/strings.json new file mode 100644 index 00000000000000..064245e0732317 --- /dev/null +++ b/homeassistant/components/subaru/strings.json @@ -0,0 +1,45 @@ +{ + "config": { + "step": { + "user": { + "title": "Subaru Starlink Configuration", + "description": "Please enter your MySubaru credentials\nNOTE: Initial setup may take up to 30 seconds", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "country": "Select country" + } + }, + "pin": { + "title": "Subaru Starlink Configuration", + "description": "Please enter your MySubaru PIN\nNOTE: All vehicles in account must have the same PIN", + "data": { + "pin": "PIN" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "incorrect_pin": "Incorrect PIN", + "bad_pin_format": "PIN should be 4 digits", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + } + }, + + "options": { + "step": { + "init": { + "title": "Subaru Starlink Options", + "description": "When enabled, vehicle polling will send a remote command to your vehicle every 2 hours to obtain new sensor data. Without vehicle polling, new sensor data is only received when the vehicle automatically pushes data (normally after engine shutdown).", + "data": { + "update_enabled": "Enable vehicle polling" + } + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index dfb2f56b29e24d..7e17a83906878a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -216,6 +216,7 @@ "squeezebox", "srp_energy", "starline", + "subaru", "syncthru", "synology_dsm", "tado", diff --git a/requirements_all.txt b/requirements_all.txt index 4d3d7d5bebf184..9ceb668bc584db 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2142,6 +2142,9 @@ streamlabswater==1.0.1 # homeassistant.components.traccar stringcase==1.2.0 +# homeassistant.components.subaru +subarulink==0.3.12 + # homeassistant.components.ecovacs sucks==0.9.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 80d325100c86aa..d37ee25cbbd936 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1105,6 +1105,9 @@ statsd==3.2.1 # homeassistant.components.traccar stringcase==1.2.0 +# homeassistant.components.subaru +subarulink==0.3.12 + # homeassistant.components.solarlog sunwatcher==0.2.1 diff --git a/tests/components/subaru/__init__.py b/tests/components/subaru/__init__.py new file mode 100644 index 00000000000000..26b81c84a1ea62 --- /dev/null +++ b/tests/components/subaru/__init__.py @@ -0,0 +1 @@ +"""Tests for the Subaru integration.""" diff --git a/tests/components/subaru/api_responses.py b/tests/components/subaru/api_responses.py new file mode 100644 index 00000000000000..b6a79ab8829500 --- /dev/null +++ b/tests/components/subaru/api_responses.py @@ -0,0 +1,284 @@ +"""Sample API response data for tests.""" + +from homeassistant.components.subaru.const import ( + API_GEN_1, + API_GEN_2, + VEHICLE_API_GEN, + VEHICLE_HAS_EV, + VEHICLE_HAS_REMOTE_SERVICE, + VEHICLE_HAS_REMOTE_START, + VEHICLE_HAS_SAFETY_SERVICE, + VEHICLE_NAME, + VEHICLE_VIN, +) + +TEST_VIN_1_G1 = "JF2ABCDE6L0000001" +TEST_VIN_2_EV = "JF2ABCDE6L0000002" +TEST_VIN_3_G2 = "JF2ABCDE6L0000003" + +VEHICLE_DATA = { + TEST_VIN_1_G1: { + VEHICLE_VIN: TEST_VIN_1_G1, + VEHICLE_NAME: "test_vehicle_1", + VEHICLE_HAS_EV: False, + VEHICLE_API_GEN: API_GEN_1, + VEHICLE_HAS_REMOTE_START: True, + VEHICLE_HAS_REMOTE_SERVICE: True, + VEHICLE_HAS_SAFETY_SERVICE: False, + }, + TEST_VIN_2_EV: { + VEHICLE_VIN: TEST_VIN_2_EV, + VEHICLE_NAME: "test_vehicle_2", + VEHICLE_HAS_EV: True, + VEHICLE_API_GEN: API_GEN_2, + VEHICLE_HAS_REMOTE_START: True, + VEHICLE_HAS_REMOTE_SERVICE: True, + VEHICLE_HAS_SAFETY_SERVICE: True, + }, + TEST_VIN_3_G2: { + VEHICLE_VIN: TEST_VIN_3_G2, + VEHICLE_NAME: "test_vehicle_3", + VEHICLE_HAS_EV: False, + VEHICLE_API_GEN: API_GEN_2, + VEHICLE_HAS_REMOTE_START: True, + VEHICLE_HAS_REMOTE_SERVICE: True, + VEHICLE_HAS_SAFETY_SERVICE: True, + }, +} + +VEHICLE_STATUS_EV = { + "status": { + "AVG_FUEL_CONSUMPTION": 2.3, + "BATTERY_VOLTAGE": "12.0", + "DISTANCE_TO_EMPTY_FUEL": 707, + "DOOR_BOOT_LOCK_STATUS": "UNKNOWN", + "DOOR_BOOT_POSITION": "CLOSED", + "DOOR_ENGINE_HOOD_LOCK_STATUS": "UNKNOWN", + "DOOR_ENGINE_HOOD_POSITION": "CLOSED", + "DOOR_FRONT_LEFT_LOCK_STATUS": "UNKNOWN", + "DOOR_FRONT_LEFT_POSITION": "CLOSED", + "DOOR_FRONT_RIGHT_LOCK_STATUS": "UNKNOWN", + "DOOR_FRONT_RIGHT_POSITION": "CLOSED", + "DOOR_REAR_LEFT_LOCK_STATUS": "UNKNOWN", + "DOOR_REAR_LEFT_POSITION": "CLOSED", + "DOOR_REAR_RIGHT_LOCK_STATUS": "UNKNOWN", + "DOOR_REAR_RIGHT_POSITION": "CLOSED", + "EV_CHARGER_STATE_TYPE": "CHARGING_STOPPED", + "EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM", + "EV_CHARGE_VOLT_TYPE": "CHARGE_LEVEL_1", + "EV_DISTANCE_TO_EMPTY": 17, + "EV_IS_PLUGGED_IN": "UNLOCKED_CONNECTED", + "EV_STATE_OF_CHARGE_MODE": "EV_MODE", + "EV_STATE_OF_CHARGE_PERCENT": "100", + "EV_TIME_TO_FULLY_CHARGED": "65535", + "EV_VEHICLE_TIME_DAYOFWEEK": "6", + "EV_VEHICLE_TIME_HOUR": "14", + "EV_VEHICLE_TIME_MINUTE": "20", + "EV_VEHICLE_TIME_SECOND": "39", + "EXT_EXTERNAL_TEMP": "21.5", + "ODOMETER": 1234, + "POSITION_HEADING_DEGREE": "150", + "POSITION_SPEED_KMPH": "0", + "POSITION_TIMESTAMP": 1595560000.0, + "SEAT_BELT_STATUS_FRONT_LEFT": "BELTED", + "SEAT_BELT_STATUS_FRONT_MIDDLE": "NOT_EQUIPPED", + "SEAT_BELT_STATUS_FRONT_RIGHT": "BELTED", + "SEAT_BELT_STATUS_SECOND_LEFT": "UNKNOWN", + "SEAT_BELT_STATUS_SECOND_MIDDLE": "UNKNOWN", + "SEAT_BELT_STATUS_SECOND_RIGHT": "UNKNOWN", + "SEAT_BELT_STATUS_THIRD_LEFT": "UNKNOWN", + "SEAT_BELT_STATUS_THIRD_MIDDLE": "UNKNOWN", + "SEAT_BELT_STATUS_THIRD_RIGHT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_FRONT_LEFT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_FRONT_MIDDLE": "NOT_EQUIPPED", + "SEAT_OCCUPATION_STATUS_FRONT_RIGHT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_SECOND_LEFT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_SECOND_MIDDLE": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_SECOND_RIGHT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_THIRD_LEFT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_THIRD_MIDDLE": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_THIRD_RIGHT": "UNKNOWN", + "TIMESTAMP": 1595560000.0, + "TRANSMISSION_MODE": "UNKNOWN", + "TYRE_PRESSURE_FRONT_LEFT": 2550, + "TYRE_PRESSURE_FRONT_RIGHT": 2550, + "TYRE_PRESSURE_REAR_LEFT": 2450, + "TYRE_PRESSURE_REAR_RIGHT": 2350, + "TYRE_STATUS_FRONT_LEFT": "UNKNOWN", + "TYRE_STATUS_FRONT_RIGHT": "UNKNOWN", + "TYRE_STATUS_REAR_LEFT": "UNKNOWN", + "TYRE_STATUS_REAR_RIGHT": "UNKNOWN", + "VEHICLE_STATE_TYPE": "IGNITION_OFF", + "WINDOW_BACK_STATUS": "UNKNOWN", + "WINDOW_FRONT_LEFT_STATUS": "VENTED", + "WINDOW_FRONT_RIGHT_STATUS": "VENTED", + "WINDOW_REAR_LEFT_STATUS": "UNKNOWN", + "WINDOW_REAR_RIGHT_STATUS": "UNKNOWN", + "WINDOW_SUNROOF_STATUS": "UNKNOWN", + "heading": 170, + "latitude": 40.0, + "longitude": -100.0, + } +} + +VEHICLE_STATUS_G2 = { + "status": { + "AVG_FUEL_CONSUMPTION": 2.3, + "BATTERY_VOLTAGE": "12.0", + "DISTANCE_TO_EMPTY_FUEL": 707, + "DOOR_BOOT_LOCK_STATUS": "UNKNOWN", + "DOOR_BOOT_POSITION": "CLOSED", + "DOOR_ENGINE_HOOD_LOCK_STATUS": "UNKNOWN", + "DOOR_ENGINE_HOOD_POSITION": "CLOSED", + "DOOR_FRONT_LEFT_LOCK_STATUS": "UNKNOWN", + "DOOR_FRONT_LEFT_POSITION": "CLOSED", + "DOOR_FRONT_RIGHT_LOCK_STATUS": "UNKNOWN", + "DOOR_FRONT_RIGHT_POSITION": "CLOSED", + "DOOR_REAR_LEFT_LOCK_STATUS": "UNKNOWN", + "DOOR_REAR_LEFT_POSITION": "CLOSED", + "DOOR_REAR_RIGHT_LOCK_STATUS": "UNKNOWN", + "DOOR_REAR_RIGHT_POSITION": "CLOSED", + "EXT_EXTERNAL_TEMP": "21.5", + "ODOMETER": 1234, + "POSITION_HEADING_DEGREE": "150", + "POSITION_SPEED_KMPH": "0", + "POSITION_TIMESTAMP": 1595560000.0, + "SEAT_BELT_STATUS_FRONT_LEFT": "BELTED", + "SEAT_BELT_STATUS_FRONT_MIDDLE": "NOT_EQUIPPED", + "SEAT_BELT_STATUS_FRONT_RIGHT": "BELTED", + "SEAT_BELT_STATUS_SECOND_LEFT": "UNKNOWN", + "SEAT_BELT_STATUS_SECOND_MIDDLE": "UNKNOWN", + "SEAT_BELT_STATUS_SECOND_RIGHT": "UNKNOWN", + "SEAT_BELT_STATUS_THIRD_LEFT": "UNKNOWN", + "SEAT_BELT_STATUS_THIRD_MIDDLE": "UNKNOWN", + "SEAT_BELT_STATUS_THIRD_RIGHT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_FRONT_LEFT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_FRONT_MIDDLE": "NOT_EQUIPPED", + "SEAT_OCCUPATION_STATUS_FRONT_RIGHT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_SECOND_LEFT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_SECOND_MIDDLE": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_SECOND_RIGHT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_THIRD_LEFT": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_THIRD_MIDDLE": "UNKNOWN", + "SEAT_OCCUPATION_STATUS_THIRD_RIGHT": "UNKNOWN", + "TIMESTAMP": 1595560000.0, + "TRANSMISSION_MODE": "UNKNOWN", + "TYRE_PRESSURE_FRONT_LEFT": 2550, + "TYRE_PRESSURE_FRONT_RIGHT": 2550, + "TYRE_PRESSURE_REAR_LEFT": 2450, + "TYRE_PRESSURE_REAR_RIGHT": 2350, + "TYRE_STATUS_FRONT_LEFT": "UNKNOWN", + "TYRE_STATUS_FRONT_RIGHT": "UNKNOWN", + "TYRE_STATUS_REAR_LEFT": "UNKNOWN", + "TYRE_STATUS_REAR_RIGHT": "UNKNOWN", + "VEHICLE_STATE_TYPE": "IGNITION_OFF", + "WINDOW_BACK_STATUS": "UNKNOWN", + "WINDOW_FRONT_LEFT_STATUS": "VENTED", + "WINDOW_FRONT_RIGHT_STATUS": "VENTED", + "WINDOW_REAR_LEFT_STATUS": "UNKNOWN", + "WINDOW_REAR_RIGHT_STATUS": "UNKNOWN", + "WINDOW_SUNROOF_STATUS": "UNKNOWN", + "heading": 170, + "latitude": 40.0, + "longitude": -100.0, + } +} + +EXPECTED_STATE_EV_IMPERIAL = { + "AVG_FUEL_CONSUMPTION": "102.3", + "BATTERY_VOLTAGE": "12.0", + "DISTANCE_TO_EMPTY_FUEL": "439.3", + "EV_CHARGER_STATE_TYPE": "CHARGING_STOPPED", + "EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM", + "EV_CHARGE_VOLT_TYPE": "CHARGE_LEVEL_1", + "EV_DISTANCE_TO_EMPTY": "17", + "EV_IS_PLUGGED_IN": "UNLOCKED_CONNECTED", + "EV_STATE_OF_CHARGE_MODE": "EV_MODE", + "EV_STATE_OF_CHARGE_PERCENT": "100", + "EV_TIME_TO_FULLY_CHARGED": "unknown", + "EV_VEHICLE_TIME_DAYOFWEEK": "6", + "EV_VEHICLE_TIME_HOUR": "14", + "EV_VEHICLE_TIME_MINUTE": "20", + "EV_VEHICLE_TIME_SECOND": "39", + "EXT_EXTERNAL_TEMP": "70.7", + "ODOMETER": "766.8", + "POSITION_HEADING_DEGREE": "150", + "POSITION_SPEED_KMPH": "0", + "POSITION_TIMESTAMP": 1595560000.0, + "TIMESTAMP": 1595560000.0, + "TRANSMISSION_MODE": "UNKNOWN", + "TYRE_PRESSURE_FRONT_LEFT": "37.0", + "TYRE_PRESSURE_FRONT_RIGHT": "37.0", + "TYRE_PRESSURE_REAR_LEFT": "35.5", + "TYRE_PRESSURE_REAR_RIGHT": "34.1", + "VEHICLE_STATE_TYPE": "IGNITION_OFF", + "heading": 170, + "latitude": 40.0, + "longitude": -100.0, +} + +EXPECTED_STATE_EV_METRIC = { + "AVG_FUEL_CONSUMPTION": "2.3", + "BATTERY_VOLTAGE": "12.0", + "DISTANCE_TO_EMPTY_FUEL": "707", + "EV_CHARGER_STATE_TYPE": "CHARGING_STOPPED", + "EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM", + "EV_CHARGE_VOLT_TYPE": "CHARGE_LEVEL_1", + "EV_DISTANCE_TO_EMPTY": "27.4", + "EV_IS_PLUGGED_IN": "UNLOCKED_CONNECTED", + "EV_STATE_OF_CHARGE_MODE": "EV_MODE", + "EV_STATE_OF_CHARGE_PERCENT": "100", + "EV_TIME_TO_FULLY_CHARGED": "unknown", + "EV_VEHICLE_TIME_DAYOFWEEK": "6", + "EV_VEHICLE_TIME_HOUR": "14", + "EV_VEHICLE_TIME_MINUTE": "20", + "EV_VEHICLE_TIME_SECOND": "39", + "EXT_EXTERNAL_TEMP": "21.5", + "ODOMETER": "1234", + "POSITION_HEADING_DEGREE": "150", + "POSITION_SPEED_KMPH": "0", + "POSITION_TIMESTAMP": 1595560000.0, + "TIMESTAMP": 1595560000.0, + "TRANSMISSION_MODE": "UNKNOWN", + "TYRE_PRESSURE_FRONT_LEFT": "2550", + "TYRE_PRESSURE_FRONT_RIGHT": "2550", + "TYRE_PRESSURE_REAR_LEFT": "2450", + "TYRE_PRESSURE_REAR_RIGHT": "2350", + "VEHICLE_STATE_TYPE": "IGNITION_OFF", + "heading": 170, + "latitude": 40.0, + "longitude": -100.0, +} + +EXPECTED_STATE_EV_UNAVAILABLE = { + "AVG_FUEL_CONSUMPTION": "unavailable", + "BATTERY_VOLTAGE": "unavailable", + "DISTANCE_TO_EMPTY_FUEL": "unavailable", + "EV_CHARGER_STATE_TYPE": "unavailable", + "EV_CHARGE_SETTING_AMPERE_TYPE": "unavailable", + "EV_CHARGE_VOLT_TYPE": "unavailable", + "EV_DISTANCE_TO_EMPTY": "unavailable", + "EV_IS_PLUGGED_IN": "unavailable", + "EV_STATE_OF_CHARGE_MODE": "unavailable", + "EV_STATE_OF_CHARGE_PERCENT": "unavailable", + "EV_TIME_TO_FULLY_CHARGED": "unavailable", + "EV_VEHICLE_TIME_DAYOFWEEK": "unavailable", + "EV_VEHICLE_TIME_HOUR": "unavailable", + "EV_VEHICLE_TIME_MINUTE": "unavailable", + "EV_VEHICLE_TIME_SECOND": "unavailable", + "EXT_EXTERNAL_TEMP": "unavailable", + "ODOMETER": "unavailable", + "POSITION_HEADING_DEGREE": "unavailable", + "POSITION_SPEED_KMPH": "unavailable", + "POSITION_TIMESTAMP": "unavailable", + "TIMESTAMP": "unavailable", + "TRANSMISSION_MODE": "unavailable", + "TYRE_PRESSURE_FRONT_LEFT": "unavailable", + "TYRE_PRESSURE_FRONT_RIGHT": "unavailable", + "TYRE_PRESSURE_REAR_LEFT": "unavailable", + "TYRE_PRESSURE_REAR_RIGHT": "unavailable", + "VEHICLE_STATE_TYPE": "unavailable", + "heading": "unavailable", + "latitude": "unavailable", + "longitude": "unavailable", +} diff --git a/tests/components/subaru/conftest.py b/tests/components/subaru/conftest.py new file mode 100644 index 00000000000000..8216ca2d2c2bd0 --- /dev/null +++ b/tests/components/subaru/conftest.py @@ -0,0 +1,139 @@ +"""Common functions needed to setup tests for Subaru component.""" +from unittest.mock import patch + +import pytest +from subarulink.const import COUNTRY_USA + +from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN +from homeassistant.components.subaru.const import ( + CONF_COUNTRY, + CONF_UPDATE_ENABLED, + DOMAIN, + VEHICLE_API_GEN, + VEHICLE_HAS_EV, + VEHICLE_HAS_REMOTE_SERVICE, + VEHICLE_HAS_REMOTE_START, + VEHICLE_HAS_SAFETY_SERVICE, + VEHICLE_NAME, +) +from homeassistant.config_entries import ENTRY_STATE_LOADED +from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME +from homeassistant.setup import async_setup_component + +from .api_responses import TEST_VIN_2_EV, VEHICLE_DATA, VEHICLE_STATUS_EV + +from tests.common import MockConfigEntry + +MOCK_API = "homeassistant.components.subaru.SubaruAPI." +MOCK_API_CONNECT = f"{MOCK_API}connect" +MOCK_API_IS_PIN_REQUIRED = f"{MOCK_API}is_pin_required" +MOCK_API_TEST_PIN = f"{MOCK_API}test_pin" +MOCK_API_UPDATE_SAVED_PIN = f"{MOCK_API}update_saved_pin" +MOCK_API_GET_VEHICLES = f"{MOCK_API}get_vehicles" +MOCK_API_VIN_TO_NAME = f"{MOCK_API}vin_to_name" +MOCK_API_GET_API_GEN = f"{MOCK_API}get_api_gen" +MOCK_API_GET_EV_STATUS = f"{MOCK_API}get_ev_status" +MOCK_API_GET_RES_STATUS = f"{MOCK_API}get_res_status" +MOCK_API_GET_REMOTE_STATUS = f"{MOCK_API}get_remote_status" +MOCK_API_GET_SAFETY_STATUS = f"{MOCK_API}get_safety_status" +MOCK_API_GET_GET_DATA = f"{MOCK_API}get_data" +MOCK_API_UPDATE = f"{MOCK_API}update" +MOCK_API_FETCH = f"{MOCK_API}fetch" + +TEST_USERNAME = "user@email.com" +TEST_PASSWORD = "password" +TEST_PIN = "1234" +TEST_DEVICE_ID = 1613183362 +TEST_COUNTRY = COUNTRY_USA + +TEST_CREDS = { + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: TEST_PASSWORD, + CONF_COUNTRY: TEST_COUNTRY, +} + +TEST_CONFIG = { + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: TEST_PASSWORD, + CONF_COUNTRY: TEST_COUNTRY, + CONF_PIN: TEST_PIN, + CONF_DEVICE_ID: TEST_DEVICE_ID, +} + +TEST_OPTIONS = { + CONF_UPDATE_ENABLED: True, +} + +TEST_ENTITY_ID = "sensor.test_vehicle_2_odometer" + + +async def setup_subaru_integration( + hass, + vehicle_list=None, + vehicle_data=None, + vehicle_status=None, + connect_effect=None, + fetch_effect=None, +): + """Create Subaru entry.""" + assert await async_setup_component(hass, HA_DOMAIN, {}) + assert await async_setup_component(hass, DOMAIN, {}) + + config_entry = MockConfigEntry( + domain=DOMAIN, + data=TEST_CONFIG, + options=TEST_OPTIONS, + entry_id=1, + ) + config_entry.add_to_hass(hass) + + with patch( + MOCK_API_CONNECT, + return_value=connect_effect is None, + side_effect=connect_effect, + ), patch(MOCK_API_GET_VEHICLES, return_value=vehicle_list,), patch( + MOCK_API_VIN_TO_NAME, + return_value=vehicle_data[VEHICLE_NAME], + ), patch( + MOCK_API_GET_API_GEN, + return_value=vehicle_data[VEHICLE_API_GEN], + ), patch( + MOCK_API_GET_EV_STATUS, + return_value=vehicle_data[VEHICLE_HAS_EV], + ), patch( + MOCK_API_GET_RES_STATUS, + return_value=vehicle_data[VEHICLE_HAS_REMOTE_START], + ), patch( + MOCK_API_GET_REMOTE_STATUS, + return_value=vehicle_data[VEHICLE_HAS_REMOTE_SERVICE], + ), patch( + MOCK_API_GET_SAFETY_STATUS, + return_value=vehicle_data[VEHICLE_HAS_SAFETY_SERVICE], + ), patch( + MOCK_API_GET_GET_DATA, + return_value=vehicle_status, + ), patch( + MOCK_API_UPDATE, + ), patch( + MOCK_API_FETCH, side_effect=fetch_effect + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return config_entry + + +@pytest.fixture +async def ev_entry(hass): + """Create a Subaru entry representing an EV vehicle with full STARLINK subscription.""" + entry = await setup_subaru_integration( + hass, + vehicle_list=[TEST_VIN_2_EV], + vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], + vehicle_status=VEHICLE_STATUS_EV, + ) + assert DOMAIN in hass.config_entries.async_domains() + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert hass.config_entries.async_get_entry(entry.entry_id) + assert entry.state == ENTRY_STATE_LOADED + return entry diff --git a/tests/components/subaru/test_config_flow.py b/tests/components/subaru/test_config_flow.py new file mode 100644 index 00000000000000..676b876652b7a7 --- /dev/null +++ b/tests/components/subaru/test_config_flow.py @@ -0,0 +1,250 @@ +"""Tests for the Subaru component config flow.""" +# pylint: disable=redefined-outer-name +from copy import deepcopy +from unittest import mock +from unittest.mock import patch + +import pytest +from subarulink.exceptions import InvalidCredentials, InvalidPIN, SubaruException + +from homeassistant import config_entries +from homeassistant.components.subaru import config_flow +from homeassistant.components.subaru.const import CONF_UPDATE_ENABLED, DOMAIN +from homeassistant.const import CONF_DEVICE_ID, CONF_PIN + +from .conftest import ( + MOCK_API_CONNECT, + MOCK_API_IS_PIN_REQUIRED, + MOCK_API_TEST_PIN, + MOCK_API_UPDATE_SAVED_PIN, + TEST_CONFIG, + TEST_CREDS, + TEST_DEVICE_ID, + TEST_PIN, + TEST_USERNAME, +) + +from tests.common import MockConfigEntry + + +async def test_user_form_init(user_form): + """Test the initial user form for first step of the config flow.""" + expected = { + "data_schema": mock.ANY, + "description_placeholders": None, + "errors": None, + "flow_id": mock.ANY, + "handler": DOMAIN, + "step_id": "user", + "type": "form", + } + assert expected == user_form + + +async def test_user_form_repeat_identifier(hass, user_form): + """Test we handle repeat identifiers.""" + entry = MockConfigEntry( + domain=DOMAIN, title=TEST_USERNAME, data=TEST_CREDS, options=None + ) + entry.add_to_hass(hass) + + with patch( + MOCK_API_CONNECT, + return_value=True, + ) as mock_connect: + result = await hass.config_entries.flow.async_configure( + user_form["flow_id"], + TEST_CREDS, + ) + assert len(mock_connect.mock_calls) == 0 + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + +async def test_user_form_cannot_connect(hass, user_form): + """Test we handle cannot connect error.""" + with patch( + MOCK_API_CONNECT, + side_effect=SubaruException(None), + ) as mock_connect: + result = await hass.config_entries.flow.async_configure( + user_form["flow_id"], + TEST_CREDS, + ) + assert len(mock_connect.mock_calls) == 1 + assert result["type"] == "abort" + assert result["reason"] == "cannot_connect" + + +async def test_user_form_invalid_auth(hass, user_form): + """Test we handle invalid auth.""" + with patch( + MOCK_API_CONNECT, + side_effect=InvalidCredentials("invalidAccount"), + ) as mock_connect: + result = await hass.config_entries.flow.async_configure( + user_form["flow_id"], + TEST_CREDS, + ) + assert len(mock_connect.mock_calls) == 1 + assert result["type"] == "form" + assert result["errors"] == {"base": "invalid_auth"} + + +async def test_user_form_pin_not_required(hass, user_form): + """Test successful login when no PIN is required.""" + with patch(MOCK_API_CONNECT, return_value=True,) as mock_connect, patch( + MOCK_API_IS_PIN_REQUIRED, + return_value=False, + ) as mock_is_pin_required: + result = await hass.config_entries.flow.async_configure( + user_form["flow_id"], + TEST_CREDS, + ) + assert len(mock_connect.mock_calls) == 2 + assert len(mock_is_pin_required.mock_calls) == 1 + + expected = { + "title": TEST_USERNAME, + "description": None, + "description_placeholders": None, + "flow_id": mock.ANY, + "result": mock.ANY, + "handler": DOMAIN, + "type": "create_entry", + "version": 1, + "data": deepcopy(TEST_CONFIG), + } + expected["data"][CONF_PIN] = None + result["data"][CONF_DEVICE_ID] = TEST_DEVICE_ID + assert expected == result + + +async def test_pin_form_init(pin_form): + """Test the pin entry form for second step of the config flow.""" + expected = { + "data_schema": config_flow.PIN_SCHEMA, + "description_placeholders": None, + "errors": None, + "flow_id": mock.ANY, + "handler": DOMAIN, + "step_id": "pin", + "type": "form", + } + assert expected == pin_form + + +async def test_pin_form_bad_pin_format(hass, pin_form): + """Test we handle invalid pin.""" + with patch(MOCK_API_TEST_PIN,) as mock_test_pin, patch( + MOCK_API_UPDATE_SAVED_PIN, + return_value=True, + ) as mock_update_saved_pin: + result = await hass.config_entries.flow.async_configure( + pin_form["flow_id"], user_input={CONF_PIN: "abcd"} + ) + assert len(mock_test_pin.mock_calls) == 0 + assert len(mock_update_saved_pin.mock_calls) == 1 + assert result["type"] == "form" + assert result["errors"] == {"base": "bad_pin_format"} + + +async def test_pin_form_success(hass, pin_form): + """Test successful PIN entry.""" + with patch(MOCK_API_TEST_PIN, return_value=True,) as mock_test_pin, patch( + MOCK_API_UPDATE_SAVED_PIN, + return_value=True, + ) as mock_update_saved_pin: + result = await hass.config_entries.flow.async_configure( + pin_form["flow_id"], user_input={CONF_PIN: TEST_PIN} + ) + + assert len(mock_test_pin.mock_calls) == 1 + assert len(mock_update_saved_pin.mock_calls) == 1 + expected = { + "title": TEST_USERNAME, + "description": None, + "description_placeholders": None, + "flow_id": mock.ANY, + "result": mock.ANY, + "handler": DOMAIN, + "type": "create_entry", + "version": 1, + "data": TEST_CONFIG, + } + result["data"][CONF_DEVICE_ID] = TEST_DEVICE_ID + assert result == expected + + +async def test_pin_form_incorrect_pin(hass, pin_form): + """Test we handle invalid pin.""" + with patch( + MOCK_API_TEST_PIN, + side_effect=InvalidPIN("invalidPin"), + ) as mock_test_pin, patch( + MOCK_API_UPDATE_SAVED_PIN, + return_value=True, + ) as mock_update_saved_pin: + result = await hass.config_entries.flow.async_configure( + pin_form["flow_id"], user_input={CONF_PIN: TEST_PIN} + ) + assert len(mock_test_pin.mock_calls) == 1 + assert len(mock_update_saved_pin.mock_calls) == 1 + assert result["type"] == "form" + assert result["errors"] == {"base": "incorrect_pin"} + + +async def test_option_flow_form(options_form): + """Test config flow options form.""" + expected = { + "data_schema": mock.ANY, + "description_placeholders": None, + "errors": None, + "flow_id": mock.ANY, + "handler": mock.ANY, + "step_id": "init", + "type": "form", + } + assert expected == options_form + + +async def test_option_flow(hass, options_form): + """Test config flow options.""" + result = await hass.config_entries.options.async_configure( + options_form["flow_id"], + user_input={ + CONF_UPDATE_ENABLED: False, + }, + ) + assert result["type"] == "create_entry" + assert result["data"] == { + CONF_UPDATE_ENABLED: False, + } + + +@pytest.fixture +async def user_form(hass): + """Return initial form for Subaru config flow.""" + return await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + +@pytest.fixture +async def pin_form(hass, user_form): + """Return second form (PIN input) for Subaru config flow.""" + with patch(MOCK_API_CONNECT, return_value=True,), patch( + MOCK_API_IS_PIN_REQUIRED, + return_value=True, + ): + return await hass.config_entries.flow.async_configure( + user_form["flow_id"], user_input=TEST_CREDS + ) + + +@pytest.fixture +async def options_form(hass): + """Return options form for Subaru config flow.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + return await hass.config_entries.options.async_init(entry.entry_id) diff --git a/tests/components/subaru/test_init.py b/tests/components/subaru/test_init.py new file mode 100644 index 00000000000000..13b510e8c40c98 --- /dev/null +++ b/tests/components/subaru/test_init.py @@ -0,0 +1,153 @@ +"""Test Subaru component setup and updates.""" +from unittest.mock import patch + +from subarulink import InvalidCredentials, SubaruException + +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.components.subaru.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.setup import async_setup_component + +from .api_responses import ( + TEST_VIN_1_G1, + TEST_VIN_2_EV, + TEST_VIN_3_G2, + VEHICLE_DATA, + VEHICLE_STATUS_EV, + VEHICLE_STATUS_G2, +) +from .conftest import ( + MOCK_API_FETCH, + MOCK_API_UPDATE, + TEST_ENTITY_ID, + setup_subaru_integration, +) + + +async def test_setup_with_no_config(hass): + """Test DOMAIN is empty if there is no config.""" + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert DOMAIN not in hass.config_entries.async_domains() + + +async def test_setup_ev(hass, ev_entry): + """Test setup with an EV vehicle.""" + check_entry = hass.config_entries.async_get_entry(ev_entry.entry_id) + assert check_entry + assert check_entry.state == ENTRY_STATE_LOADED + + +async def test_setup_g2(hass): + """Test setup with a G2 vehcile .""" + entry = await setup_subaru_integration( + hass, + vehicle_list=[TEST_VIN_3_G2], + vehicle_data=VEHICLE_DATA[TEST_VIN_3_G2], + vehicle_status=VEHICLE_STATUS_G2, + ) + check_entry = hass.config_entries.async_get_entry(entry.entry_id) + assert check_entry + assert check_entry.state == ENTRY_STATE_LOADED + + +async def test_setup_g1(hass): + """Test setup with a G1 vehicle.""" + entry = await setup_subaru_integration( + hass, vehicle_list=[TEST_VIN_1_G1], vehicle_data=VEHICLE_DATA[TEST_VIN_1_G1] + ) + check_entry = hass.config_entries.async_get_entry(entry.entry_id) + assert check_entry + assert check_entry.state == ENTRY_STATE_LOADED + + +async def test_unsuccessful_connect(hass): + """Test unsuccessful connect due to connectivity.""" + entry = await setup_subaru_integration( + hass, + connect_effect=SubaruException("Service Unavailable"), + vehicle_list=[TEST_VIN_2_EV], + vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], + vehicle_status=VEHICLE_STATUS_EV, + ) + check_entry = hass.config_entries.async_get_entry(entry.entry_id) + assert check_entry + assert check_entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_invalid_credentials(hass): + """Test invalid credentials.""" + entry = await setup_subaru_integration( + hass, + connect_effect=InvalidCredentials("Invalid Credentials"), + vehicle_list=[TEST_VIN_2_EV], + vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], + vehicle_status=VEHICLE_STATUS_EV, + ) + check_entry = hass.config_entries.async_get_entry(entry.entry_id) + assert check_entry + assert check_entry.state == ENTRY_STATE_SETUP_ERROR + + +async def test_update_skip_unsubscribed(hass): + """Test update function skips vehicles without subscription.""" + await setup_subaru_integration( + hass, vehicle_list=[TEST_VIN_1_G1], vehicle_data=VEHICLE_DATA[TEST_VIN_1_G1] + ) + + with patch(MOCK_API_FETCH) as mock_fetch: + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: TEST_ENTITY_ID}, + blocking=True, + ) + + await hass.async_block_till_done() + mock_fetch.assert_not_called() + + +async def test_update_disabled(hass, ev_entry): + """Test update function disable option.""" + with patch(MOCK_API_FETCH, side_effect=SubaruException("403 Error"),), patch( + MOCK_API_UPDATE, + ) as mock_update: + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: TEST_ENTITY_ID}, + blocking=True, + ) + await hass.async_block_till_done() + mock_update.assert_not_called() + + +async def test_fetch_failed(hass): + """Tests when fetch fails.""" + await setup_subaru_integration( + hass, + vehicle_list=[TEST_VIN_2_EV], + vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], + vehicle_status=VEHICLE_STATUS_EV, + fetch_effect=SubaruException("403 Error"), + ) + + test_entity = hass.states.get(TEST_ENTITY_ID) + assert test_entity.state == "unavailable" + + +async def test_unload_entry(hass, ev_entry): + """Test that entry is unloaded.""" + assert ev_entry.state == ENTRY_STATE_LOADED + assert await hass.config_entries.async_unload(ev_entry.entry_id) + await hass.async_block_till_done() + assert ev_entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py new file mode 100644 index 00000000000000..4344c147f2266b --- /dev/null +++ b/tests/components/subaru/test_sensor.py @@ -0,0 +1,67 @@ +"""Test Subaru sensors.""" +from homeassistant.components.subaru.const import VEHICLE_NAME +from homeassistant.components.subaru.sensor import ( + API_GEN_2_SENSORS, + EV_SENSORS, + SAFETY_SENSORS, + SENSOR_FIELD, + SENSOR_TYPE, +) +from homeassistant.util import slugify +from homeassistant.util.unit_system import IMPERIAL_SYSTEM + +from .api_responses import ( + EXPECTED_STATE_EV_IMPERIAL, + EXPECTED_STATE_EV_METRIC, + EXPECTED_STATE_EV_UNAVAILABLE, + TEST_VIN_2_EV, + VEHICLE_DATA, + VEHICLE_STATUS_EV, +) + +from tests.components.subaru.conftest import setup_subaru_integration + +VEHICLE_NAME = VEHICLE_DATA[TEST_VIN_2_EV][VEHICLE_NAME] + + +async def test_sensors_ev_imperial(hass): + """Test sensors supporting imperial units.""" + hass.config.units = IMPERIAL_SYSTEM + await setup_subaru_integration( + hass, + vehicle_list=[TEST_VIN_2_EV], + vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], + vehicle_status=VEHICLE_STATUS_EV, + ) + _assert_data(hass, EXPECTED_STATE_EV_IMPERIAL) + + +async def test_sensors_ev_metric(hass, ev_entry): + """Test sensors supporting metric units.""" + _assert_data(hass, EXPECTED_STATE_EV_METRIC) + + +async def test_sensors_missing_vin_data(hass): + """Test for missing VIN dataset.""" + await setup_subaru_integration( + hass, + vehicle_list=[TEST_VIN_2_EV], + vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], + vehicle_status=None, + ) + _assert_data(hass, EXPECTED_STATE_EV_UNAVAILABLE) + + +def _assert_data(hass, expected_state): + sensor_list = EV_SENSORS + sensor_list.extend(API_GEN_2_SENSORS) + sensor_list.extend(SAFETY_SENSORS) + expected_states = {} + for item in sensor_list: + expected_states[ + f"sensor.{slugify(f'{VEHICLE_NAME} {item[SENSOR_TYPE]}')}" + ] = expected_state[item[SENSOR_FIELD]] + + for sensor in expected_states: + actual = hass.states.get(sensor) + assert actual.state == expected_states[sensor] From 2d70806035693c77e8340936defcd95d014ed918 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 21 Feb 2021 04:21:09 +0100 Subject: [PATCH 0650/1818] Add support for "alias" in script steps device, device_condition, and conditions (#46647) Co-authored-by: Donnie --- homeassistant/helpers/config_validation.py | 41 +++++++++---- homeassistant/helpers/script.py | 68 +++++++++------------- tests/helpers/test_condition.py | 36 ++++++++---- tests/helpers/test_config_validation.py | 5 ++ tests/helpers/test_script.py | 68 +++++++++++++++++----- 5 files changed, 146 insertions(+), 72 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 6f1c6f465997da..6b0737ae346989 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -888,9 +888,11 @@ def script_action(value: Any) -> dict: SCRIPT_SCHEMA = vol.All(ensure_list, [script_action]) +SCRIPT_ACTION_BASE_SCHEMA = {vol.Optional(CONF_ALIAS): string} + EVENT_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_EVENT): string, vol.Optional(CONF_EVENT_DATA): vol.All(dict, template_complex), vol.Optional(CONF_EVENT_DATA_TEMPLATE): vol.All(dict, template_complex), @@ -900,7 +902,7 @@ def script_action(value: Any) -> dict: SERVICE_SCHEMA = vol.All( vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Exclusive(CONF_SERVICE, "service name"): vol.Any( service, dynamic_template ), @@ -920,9 +922,12 @@ def script_action(value: Any) -> dict: vol.Coerce(float), vol.All(str, entity_domain("input_number")) ) +CONDITION_BASE_SCHEMA = {vol.Optional(CONF_ALIAS): string} + NUMERIC_STATE_CONDITION_SCHEMA = vol.All( vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "numeric_state", vol.Required(CONF_ENTITY_ID): entity_ids, vol.Optional(CONF_ATTRIBUTE): str, @@ -935,6 +940,7 @@ def script_action(value: Any) -> dict: ) STATE_CONDITION_BASE_SCHEMA = { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "state", vol.Required(CONF_ENTITY_ID): entity_ids, vol.Optional(CONF_ATTRIBUTE): str, @@ -975,6 +981,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name SUN_CONDITION_SCHEMA = vol.All( vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "sun", vol.Optional("before"): sun_event, vol.Optional("before_offset"): time_period, @@ -989,6 +996,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name TEMPLATE_CONDITION_SCHEMA = vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "template", vol.Required(CONF_VALUE_TEMPLATE): template, } @@ -997,6 +1005,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name TIME_CONDITION_SCHEMA = vol.All( vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "time", "before": vol.Any(time, vol.All(str, entity_domain("input_datetime"))), "after": vol.Any(time, vol.All(str, entity_domain("input_datetime"))), @@ -1008,6 +1017,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name ZONE_CONDITION_SCHEMA = vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "zone", vol.Required(CONF_ENTITY_ID): entity_ids, "zone": entity_ids, @@ -1019,6 +1029,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name AND_CONDITION_SCHEMA = vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "and", vol.Required(CONF_CONDITIONS): vol.All( ensure_list, @@ -1030,6 +1041,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name OR_CONDITION_SCHEMA = vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "or", vol.Required(CONF_CONDITIONS): vol.All( ensure_list, @@ -1041,6 +1053,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name NOT_CONDITION_SCHEMA = vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "not", vol.Required(CONF_CONDITIONS): vol.All( ensure_list, @@ -1052,6 +1065,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name DEVICE_CONDITION_BASE_SCHEMA = vol.Schema( { + **CONDITION_BASE_SCHEMA, vol.Required(CONF_CONDITION): "device", vol.Required(CONF_DEVICE_ID): str, vol.Required(CONF_DOMAIN): str, @@ -1087,14 +1101,14 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name _SCRIPT_DELAY_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_DELAY): positive_time_period_template, } ) _SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_WAIT_TEMPLATE): template, vol.Optional(CONF_TIMEOUT): positive_time_period_template, vol.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean, @@ -1102,16 +1116,22 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name ) DEVICE_ACTION_BASE_SCHEMA = vol.Schema( - {vol.Required(CONF_DEVICE_ID): string, vol.Required(CONF_DOMAIN): str} + { + **SCRIPT_ACTION_BASE_SCHEMA, + vol.Required(CONF_DEVICE_ID): string, + vol.Required(CONF_DOMAIN): str, + } ) DEVICE_ACTION_SCHEMA = DEVICE_ACTION_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) -_SCRIPT_SCENE_SCHEMA = vol.Schema({vol.Required(CONF_SCENE): entity_domain("scene")}) +_SCRIPT_SCENE_SCHEMA = vol.Schema( + {**SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_SCENE): entity_domain("scene")} +) _SCRIPT_REPEAT_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_REPEAT): vol.All( { vol.Exclusive(CONF_COUNT, "repeat"): vol.Any(vol.Coerce(int), template), @@ -1130,11 +1150,12 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name _SCRIPT_CHOOSE_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_CHOOSE): vol.All( ensure_list, [ { + vol.Optional(CONF_ALIAS): string, vol.Required(CONF_CONDITIONS): vol.All( ensure_list, [CONDITION_SCHEMA] ), @@ -1148,7 +1169,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name _SCRIPT_WAIT_FOR_TRIGGER_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_WAIT_FOR_TRIGGER): TRIGGER_SCHEMA, vol.Optional(CONF_TIMEOUT): positive_time_period_template, vol.Optional(CONF_CONTINUE_ON_TIMEOUT): boolean, @@ -1157,7 +1178,7 @@ def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name _SCRIPT_SET_SCHEMA = vol.Schema( { - vol.Optional(CONF_ALIAS): string, + **SCRIPT_ACTION_BASE_SCHEMA, vol.Required(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, } ) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 69ba082e57380f..2e8348bcaf8473 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -18,7 +18,7 @@ cast, ) -from async_timeout import timeout +import async_timeout import voluptuous as vol from homeassistant import exceptions @@ -235,6 +235,13 @@ def _log( msg, *args, level=level, **kwargs ) + def _step_log(self, default_message, timeout=None): + self._script.last_action = self._action.get(CONF_ALIAS, default_message) + _timeout = ( + "" if timeout is None else f" (timeout: {timedelta(seconds=timeout)})" + ) + self._log("Executing step %s%s", self._script.last_action, _timeout) + async def async_run(self) -> None: """Run script.""" try: @@ -327,13 +334,12 @@ async def _async_delay_step(self): """Handle delay.""" delay = self._get_pos_time_period_template(CONF_DELAY) - self._script.last_action = self._action.get(CONF_ALIAS, f"delay {delay}") - self._log("Executing step %s", self._script.last_action) + self._step_log(f"delay {delay}") delay = delay.total_seconds() self._changed() try: - async with timeout(delay): + async with async_timeout.timeout(delay): await self._stop.wait() except asyncio.TimeoutError: pass @@ -341,18 +347,13 @@ async def _async_delay_step(self): async def _async_wait_template_step(self): """Handle a wait template.""" if CONF_TIMEOUT in self._action: - delay = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() + timeout = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() else: - delay = None + timeout = None - self._script.last_action = self._action.get(CONF_ALIAS, "wait template") - self._log( - "Executing step %s%s", - self._script.last_action, - "" if delay is None else f" (timeout: {timedelta(seconds=delay)})", - ) + self._step_log("wait template", timeout) - self._variables["wait"] = {"remaining": delay, "completed": False} + self._variables["wait"] = {"remaining": timeout, "completed": False} wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass @@ -366,7 +367,7 @@ async def _async_wait_template_step(self): def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._variables["wait"] = { - "remaining": to_context.remaining if to_context else delay, + "remaining": to_context.remaining if to_context else timeout, "completed": True, } done.set() @@ -382,7 +383,7 @@ def async_script_wait(entity_id, from_s, to_s): self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] try: - async with timeout(delay) as to_context: + async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): @@ -431,8 +432,7 @@ async def async_cancel_long_task() -> None: async def _async_call_service_step(self): """Call the service specified in the action.""" - self._script.last_action = self._action.get(CONF_ALIAS, "call service") - self._log("Executing step %s", self._script.last_action) + self._step_log("call service") params = service.async_prepare_call_from_config( self._hass, self._action, self._variables @@ -467,8 +467,7 @@ async def _async_call_service_step(self): async def _async_device_step(self): """Perform the device automation specified in the action.""" - self._script.last_action = self._action.get(CONF_ALIAS, "device automation") - self._log("Executing step %s", self._script.last_action) + self._step_log("device automation") platform = await device_automation.async_get_device_automation_platform( self._hass, self._action[CONF_DOMAIN], "action" ) @@ -478,8 +477,7 @@ async def _async_device_step(self): async def _async_scene_step(self): """Activate the scene specified in the action.""" - self._script.last_action = self._action.get(CONF_ALIAS, "activate scene") - self._log("Executing step %s", self._script.last_action) + self._step_log("activate scene") await self._hass.services.async_call( scene.DOMAIN, SERVICE_TURN_ON, @@ -490,10 +488,7 @@ async def _async_scene_step(self): async def _async_event_step(self): """Fire an event.""" - self._script.last_action = self._action.get( - CONF_ALIAS, self._action[CONF_EVENT] - ) - self._log("Executing step %s", self._script.last_action) + self._step_log(self._action.get(CONF_ALIAS, self._action[CONF_EVENT])) event_data = {} for conf in [CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE]: if conf not in self._action: @@ -627,25 +622,20 @@ async def _async_choose_step(self) -> None: async def _async_wait_for_trigger_step(self): """Wait for a trigger event.""" if CONF_TIMEOUT in self._action: - delay = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() + timeout = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() else: - delay = None + timeout = None - self._script.last_action = self._action.get(CONF_ALIAS, "wait for trigger") - self._log( - "Executing step %s%s", - self._script.last_action, - "" if delay is None else f" (timeout: {timedelta(seconds=delay)})", - ) + self._step_log("wait for trigger", timeout) variables = {**self._variables} - self._variables["wait"] = {"remaining": delay, "trigger": None} + self._variables["wait"] = {"remaining": timeout, "trigger": None} done = asyncio.Event() async def async_done(variables, context=None): self._variables["wait"] = { - "remaining": to_context.remaining if to_context else delay, + "remaining": to_context.remaining if to_context else timeout, "trigger": variables["trigger"], } done.set() @@ -671,7 +661,7 @@ def log_cb(level, msg, **kwargs): self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) ] try: - async with timeout(delay) as to_context: + async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): @@ -685,8 +675,7 @@ def log_cb(level, msg, **kwargs): async def _async_variables_step(self): """Set a variable value.""" - self._script.last_action = self._action.get(CONF_ALIAS, "setting variables") - self._log("Executing step %s", self._script.last_action) + self._step_log("setting variables") self._variables = self._action[CONF_VARIABLES].async_render( self._hass, self._variables, render_as_defaults=False ) @@ -1111,10 +1100,11 @@ async def _async_prep_choose_data(self, step): await self._async_get_condition(config) for config in choice.get(CONF_CONDITIONS, []) ] + choice_name = choice.get(CONF_ALIAS, f"choice {idx}") sub_script = Script( self._hass, choice[CONF_SEQUENCE], - f"{self.name}: {step_name}: choice {idx}", + f"{self.name}: {step_name}: {choice_name}", self.domain, running_description=self.running_description, script_mode=SCRIPT_MODE_PARALLEL, diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 3e7833b24dd0f3..63ef9ba56d8abf 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -34,6 +34,7 @@ async def test_and_condition(hass): test = await condition.async_from_config( hass, { + "alias": "And Condition", "condition": "and", "conditions": [ { @@ -71,6 +72,7 @@ async def test_and_condition_with_template(hass): "condition": "and", "conditions": [ { + "alias": "Template Condition", "condition": "template", "value_template": '{{ states.sensor.temperature.state == "100" }}', }, @@ -98,6 +100,7 @@ async def test_or_condition(hass): test = await condition.async_from_config( hass, { + "alias": "Or Condition", "condition": "or", "conditions": [ { @@ -159,6 +162,7 @@ async def test_not_condition(hass): test = await condition.async_from_config( hass, { + "alias": "Not Condition", "condition": "not", "conditions": [ { @@ -226,36 +230,45 @@ async def test_not_condition_with_template(hass): async def test_time_window(hass): """Test time condition windows.""" - sixam = dt.parse_time("06:00:00") - sixpm = dt.parse_time("18:00:00") + sixam = "06:00:00" + sixpm = "18:00:00" + + test1 = await condition.async_from_config( + hass, + {"alias": "Time Cond", "condition": "time", "after": sixam, "before": sixpm}, + ) + test2 = await condition.async_from_config( + hass, + {"alias": "Time Cond", "condition": "time", "after": sixpm, "before": sixam}, + ) with patch( "homeassistant.helpers.condition.dt_util.now", return_value=dt.now().replace(hour=3), ): - assert not condition.time(hass, after=sixam, before=sixpm) - assert condition.time(hass, after=sixpm, before=sixam) + assert not test1(hass) + assert test2(hass) with patch( "homeassistant.helpers.condition.dt_util.now", return_value=dt.now().replace(hour=9), ): - assert condition.time(hass, after=sixam, before=sixpm) - assert not condition.time(hass, after=sixpm, before=sixam) + assert test1(hass) + assert not test2(hass) with patch( "homeassistant.helpers.condition.dt_util.now", return_value=dt.now().replace(hour=15), ): - assert condition.time(hass, after=sixam, before=sixpm) - assert not condition.time(hass, after=sixpm, before=sixam) + assert test1(hass) + assert not test2(hass) with patch( "homeassistant.helpers.condition.dt_util.now", return_value=dt.now().replace(hour=21), ): - assert not condition.time(hass, after=sixam, before=sixpm) - assert condition.time(hass, after=sixpm, before=sixam) + assert not test1(hass) + assert test2(hass) async def test_time_using_input_datetime(hass): @@ -439,6 +452,7 @@ async def test_multiple_states(hass): "condition": "and", "conditions": [ { + "alias": "State Condition", "condition": "state", "entity_id": "sensor.temperature", "state": ["100", "200"], @@ -709,6 +723,7 @@ async def test_numeric_state_multiple_entities(hass): "condition": "and", "conditions": [ { + "alias": "Numeric State Condition", "condition": "numeric_state", "entity_id": ["sensor.temperature_1", "sensor.temperature_2"], "below": 50, @@ -911,6 +926,7 @@ async def test_zone_multiple_entities(hass): "condition": "and", "conditions": [ { + "alias": "Zone Condition", "condition": "zone", "entity_id": ["device_tracker.person_1", "device_tracker.person_2"], "zone": "zone.home", diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 1397e499c7ed5b..d0ae86f8f7edb8 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -358,6 +358,11 @@ def test_service_schema(): "service": "homeassistant.turn_on", "entity_id": ["light.kitchen", "light.ceiling"], }, + { + "service": "light.turn_on", + "entity_id": "all", + "alias": "turn on kitchen lights", + }, ) for value in options: cv.SERVICE_SCHEMA(value) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index a22cf27acdc802..d2946fcd4947eb 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -50,7 +50,10 @@ async def test_firing_event_basic(hass, caplog): context = Context() events = async_capture_events(hass, event) - sequence = cv.SCRIPT_SCHEMA({"event": event, "event_data": {"hello": "world"}}) + alias = "event step" + sequence = cv.SCRIPT_SCHEMA( + {"alias": alias, "event": event, "event_data": {"hello": "world"}} + ) script_obj = script.Script( hass, sequence, "Test Name", "test_domain", running_description="test script" ) @@ -63,6 +66,7 @@ async def test_firing_event_basic(hass, caplog): assert events[0].data.get("hello") == "world" assert ".test_name:" in caplog.text assert "Test Name: Running test script" in caplog.text + assert f"Executing step {alias}" in caplog.text async def test_firing_event_template(hass): @@ -107,12 +111,15 @@ async def test_firing_event_template(hass): } -async def test_calling_service_basic(hass): +async def test_calling_service_basic(hass, caplog): """Test the calling of a service.""" context = Context() calls = async_mock_service(hass, "test", "script") - sequence = cv.SCRIPT_SCHEMA({"service": "test.script", "data": {"hello": "world"}}) + alias = "service step" + sequence = cv.SCRIPT_SCHEMA( + {"alias": alias, "service": "test.script", "data": {"hello": "world"}} + ) script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=context) @@ -121,6 +128,7 @@ async def test_calling_service_basic(hass): assert len(calls) == 1 assert calls[0].context is context assert calls[0].data.get("hello") == "world" + assert f"Executing step {alias}" in caplog.text async def test_calling_service_template(hass): @@ -250,12 +258,13 @@ def heard_event_cb(event): assert len(calls) == 4 -async def test_activating_scene(hass): +async def test_activating_scene(hass, caplog): """Test the activation of a scene.""" context = Context() calls = async_mock_service(hass, scene.DOMAIN, SERVICE_TURN_ON) - sequence = cv.SCRIPT_SCHEMA({"scene": "scene.hello"}) + alias = "scene step" + sequence = cv.SCRIPT_SCHEMA({"alias": alias, "scene": "scene.hello"}) script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=context) @@ -264,6 +273,7 @@ async def test_activating_scene(hass): assert len(calls) == 1 assert calls[0].context is context assert calls[0].data.get(ATTR_ENTITY_ID) == "scene.hello" + assert f"Executing step {alias}" in caplog.text @pytest.mark.parametrize("count", [1, 3]) @@ -1063,14 +1073,16 @@ async def test_condition_warning(hass, caplog): assert len(events) == 1 -async def test_condition_basic(hass): +async def test_condition_basic(hass, caplog): """Test if we can use conditions in a script.""" event = "test_event" events = async_capture_events(hass, event) + alias = "condition step" sequence = cv.SCRIPT_SCHEMA( [ {"event": event}, { + "alias": alias, "condition": "template", "value_template": "{{ states.test.entity.state == 'hello' }}", }, @@ -1083,6 +1095,8 @@ async def test_condition_basic(hass): await script_obj.async_run(context=Context()) await hass.async_block_till_done() + assert f"Test condition {alias}: True" in caplog.text + caplog.clear() assert len(events) == 2 hass.states.async_set("test.entity", "goodbye") @@ -1090,6 +1104,7 @@ async def test_condition_basic(hass): await script_obj.async_run(context=Context()) await hass.async_block_till_done() + assert f"Test condition {alias}: False" in caplog.text assert len(events) == 3 @@ -1140,14 +1155,16 @@ async def test_condition_all_cached(hass): assert len(script_obj._config_cache) == 2 -async def test_repeat_count(hass): +async def test_repeat_count(hass, caplog): """Test repeat action w/ count option.""" event = "test_event" events = async_capture_events(hass, event) count = 3 + alias = "condition step" sequence = cv.SCRIPT_SCHEMA( { + "alias": alias, "repeat": { "count": count, "sequence": { @@ -1158,7 +1175,7 @@ async def test_repeat_count(hass): "last": "{{ repeat.last }}", }, }, - } + }, } ) script_obj = script.Script(hass, sequence, "Test Name", "test_domain") @@ -1171,6 +1188,7 @@ async def test_repeat_count(hass): assert event.data.get("first") == (index == 0) assert event.data.get("index") == index + 1 assert event.data.get("last") == (index == count - 1) + assert caplog.text.count(f"Repeating {alias}") == count @pytest.mark.parametrize("condition", ["while", "until"]) @@ -1470,26 +1488,44 @@ async def test_choose_warning(hass, caplog): @pytest.mark.parametrize("var,result", [(1, "first"), (2, "second"), (3, "default")]) -async def test_choose(hass, var, result): +async def test_choose(hass, caplog, var, result): """Test choose action.""" event = "test_event" events = async_capture_events(hass, event) + alias = "choose step" + choice = {1: "choice one", 2: "choice two", 3: None} + aliases = {1: "sequence one", 2: "sequence two", 3: "default sequence"} sequence = cv.SCRIPT_SCHEMA( { + "alias": alias, "choose": [ { + "alias": choice[1], "conditions": { "condition": "template", "value_template": "{{ var == 1 }}", }, - "sequence": {"event": event, "event_data": {"choice": "first"}}, + "sequence": { + "alias": aliases[1], + "event": event, + "event_data": {"choice": "first"}, + }, }, { + "alias": choice[2], "conditions": "{{ var == 2 }}", - "sequence": {"event": event, "event_data": {"choice": "second"}}, + "sequence": { + "alias": aliases[2], + "event": event, + "event_data": {"choice": "second"}, + }, }, ], - "default": {"event": event, "event_data": {"choice": "default"}}, + "default": { + "alias": aliases[3], + "event": event, + "event_data": {"choice": "default"}, + }, } ) script_obj = script.Script(hass, sequence, "Test Name", "test_domain") @@ -1499,6 +1535,10 @@ async def test_choose(hass, var, result): assert len(events) == 1 assert events[0].data["choice"] == result + expected_choice = choice[var] + if var == 3: + expected_choice = "default" + assert f"{alias}: {expected_choice}: Executing step {aliases[var]}" in caplog.text @pytest.mark.parametrize( @@ -2115,9 +2155,10 @@ def started_action(): async def test_set_variable(hass, caplog): """Test setting variables in scripts.""" + alias = "variables step" sequence = cv.SCRIPT_SCHEMA( [ - {"variables": {"variable": "value"}}, + {"alias": alias, "variables": {"variable": "value"}}, {"service": "test.script", "data": {"value": "{{ variable }}"}}, ] ) @@ -2129,6 +2170,7 @@ async def test_set_variable(hass, caplog): await hass.async_block_till_done() assert mock_calls[0].data["value"] == "value" + assert f"Executing step {alias}" in caplog.text async def test_set_redefines_variable(hass, caplog): From 5e26bda52d4b4e61a0e7df415a8b0b5bd90343cf Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 21 Feb 2021 04:21:39 +0100 Subject: [PATCH 0651/1818] Add support for disabling config entries (#46779) --- .../components/config/config_entries.py | 89 +++++++----- homeassistant/config_entries.py | 46 +++++- homeassistant/const.py | 1 + homeassistant/helpers/device_registry.py | 43 +++++- homeassistant/helpers/entity_registry.py | 44 ++++++ tests/common.py | 2 + .../components/config/test_config_entries.py | 133 ++++++++++++++++++ tests/helpers/test_device_registry.py | 38 +++++ tests/helpers/test_entity_registry.py | 80 +++++++++++ tests/test_config_entries.py | 104 ++++++++++++++ 10 files changed, 542 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index b8d9944d7af34e..b45b6abe468062 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -1,7 +1,6 @@ """Http views to control the config manager.""" import aiohttp.web_exceptions import voluptuous as vol -import voluptuous_serialize from homeassistant import config_entries, data_entry_flow from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT @@ -10,7 +9,6 @@ from homeassistant.const import HTTP_FORBIDDEN, HTTP_NOT_FOUND from homeassistant.core import callback from homeassistant.exceptions import Unauthorized -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.data_entry_flow import ( FlowManagerIndexView, FlowManagerResourceView, @@ -30,6 +28,7 @@ async def async_setup(hass): hass.http.register_view(OptionManagerFlowIndexView(hass.config_entries.options)) hass.http.register_view(OptionManagerFlowResourceView(hass.config_entries.options)) + hass.components.websocket_api.async_register_command(config_entry_disable) hass.components.websocket_api.async_register_command(config_entry_update) hass.components.websocket_api.async_register_command(config_entries_progress) hass.components.websocket_api.async_register_command(system_options_list) @@ -39,24 +38,6 @@ async def async_setup(hass): return True -def _prepare_json(result): - """Convert result for JSON.""" - if result["type"] != data_entry_flow.RESULT_TYPE_FORM: - return result - - data = result.copy() - - schema = data["data_schema"] - if schema is None: - data["data_schema"] = [] - else: - data["data_schema"] = voluptuous_serialize.convert( - schema, custom_serializer=cv.custom_serializer - ) - - return data - - class ConfigManagerEntryIndexView(HomeAssistantView): """View to get available config entries.""" @@ -265,6 +246,21 @@ async def system_options_list(hass, connection, msg): connection.send_result(msg["id"], entry.system_options.as_dict()) +def send_entry_not_found(connection, msg_id): + """Send Config entry not found error.""" + connection.send_error( + msg_id, websocket_api.const.ERR_NOT_FOUND, "Config entry not found" + ) + + +def get_entry(hass, connection, entry_id, msg_id): + """Get entry, send error message if it doesn't exist.""" + entry = hass.config_entries.async_get_entry(entry_id) + if entry is None: + send_entry_not_found(connection, msg_id) + return entry + + @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( @@ -279,13 +275,10 @@ async def system_options_update(hass, connection, msg): changes = dict(msg) changes.pop("id") changes.pop("type") - entry_id = changes.pop("entry_id") - entry = hass.config_entries.async_get_entry(entry_id) + changes.pop("entry_id") + entry = get_entry(hass, connection, msg["entry_id"], msg["id"]) if entry is None: - connection.send_error( - msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found" - ) return hass.config_entries.async_update_entry(entry, system_options=changes) @@ -302,20 +295,47 @@ async def config_entry_update(hass, connection, msg): changes = dict(msg) changes.pop("id") changes.pop("type") - entry_id = changes.pop("entry_id") - - entry = hass.config_entries.async_get_entry(entry_id) + changes.pop("entry_id") + entry = get_entry(hass, connection, msg["entry_id"], msg["id"]) if entry is None: - connection.send_error( - msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found" - ) return hass.config_entries.async_update_entry(entry, **changes) connection.send_result(msg["id"], entry_json(entry)) +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command( + { + "type": "config_entries/disable", + "entry_id": str, + # We only allow setting disabled_by user via API. + "disabled_by": vol.Any("user", None), + } +) +async def config_entry_disable(hass, connection, msg): + """Disable config entry.""" + disabled_by = msg["disabled_by"] + + result = False + try: + result = await hass.config_entries.async_set_disabled_by( + msg["entry_id"], disabled_by + ) + except config_entries.OperationNotAllowed: + # Failed to unload the config entry + pass + except config_entries.UnknownEntry: + send_entry_not_found(connection, msg["id"]) + return + + result = {"require_restart": not result} + + connection.send_result(msg["id"], result) + + @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( @@ -333,9 +353,7 @@ async def ignore_config_flow(hass, connection, msg): ) if flow is None: - connection.send_error( - msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found" - ) + send_entry_not_found(connection, msg["id"]) return if "unique_id" not in flow["context"]: @@ -357,7 +375,7 @@ def entry_json(entry: config_entries.ConfigEntry) -> dict: """Return JSON value of a config entry.""" handler = config_entries.HANDLERS.get(entry.domain) supports_options = ( - # Guard in case handler is no longer registered (custom compnoent etc) + # Guard in case handler is no longer registered (custom component etc) handler is not None # pylint: disable=comparison-with-callable and handler.async_get_options_flow @@ -372,4 +390,5 @@ def entry_json(entry: config_entries.ConfigEntry) -> dict: "connection_class": entry.connection_class, "supports_options": supports_options, "supports_unload": entry.supports_unload, + "disabled_by": entry.disabled_by, } diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 7225b7c375dd24..dbc0dd01454364 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -11,6 +11,7 @@ import attr from homeassistant import data_entry_flow, loader +from homeassistant.const import EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import entity_registry @@ -68,6 +69,8 @@ ENTRY_STATE_NOT_LOADED = "not_loaded" # An error occurred when trying to unload the entry ENTRY_STATE_FAILED_UNLOAD = "failed_unload" +# The config entry is disabled +ENTRY_STATE_DISABLED = "disabled" UNRECOVERABLE_STATES = (ENTRY_STATE_MIGRATION_ERROR, ENTRY_STATE_FAILED_UNLOAD) @@ -92,6 +95,8 @@ CONN_CLASS_ASSUMED = "assumed" CONN_CLASS_UNKNOWN = "unknown" +DISABLED_USER = "user" + RELOAD_AFTER_UPDATE_DELAY = 30 @@ -126,6 +131,7 @@ class ConfigEntry: "source", "connection_class", "state", + "disabled_by", "_setup_lock", "update_listeners", "_async_cancel_retry_setup", @@ -144,6 +150,7 @@ def __init__( unique_id: Optional[str] = None, entry_id: Optional[str] = None, state: str = ENTRY_STATE_NOT_LOADED, + disabled_by: Optional[str] = None, ) -> None: """Initialize a config entry.""" # Unique id of the config entry @@ -179,6 +186,9 @@ def __init__( # Unique ID of this entry. self.unique_id = unique_id + # Config entry is disabled + self.disabled_by = disabled_by + # Supports unload self.supports_unload = False @@ -198,7 +208,7 @@ async def async_setup( tries: int = 0, ) -> None: """Set up an entry.""" - if self.source == SOURCE_IGNORE: + if self.source == SOURCE_IGNORE or self.disabled_by: return if integration is None: @@ -441,6 +451,7 @@ def as_dict(self) -> Dict[str, Any]: "source": self.source, "connection_class": self.connection_class, "unique_id": self.unique_id, + "disabled_by": self.disabled_by, } @@ -711,6 +722,8 @@ async def async_initialize(self) -> None: system_options=entry.get("system_options", {}), # New in 0.104 unique_id=entry.get("unique_id"), + # New in 2021.3 + disabled_by=entry.get("disabled_by"), ) for entry in config["entries"] ] @@ -759,13 +772,42 @@ async def async_reload(self, entry_id: str) -> bool: If an entry was not loaded, will just load. """ + entry = self.async_get_entry(entry_id) + + if entry is None: + raise UnknownEntry + unload_result = await self.async_unload(entry_id) - if not unload_result: + if not unload_result or entry.disabled_by: return unload_result return await self.async_setup(entry_id) + async def async_set_disabled_by( + self, entry_id: str, disabled_by: Optional[str] + ) -> bool: + """Disable an entry. + + If disabled_by is changed, the config entry will be reloaded. + """ + entry = self.async_get_entry(entry_id) + + if entry is None: + raise UnknownEntry + + if entry.disabled_by == disabled_by: + return True + + entry.disabled_by = disabled_by + self._async_schedule_save() + + self.hass.bus.async_fire( + EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} + ) + + return await self.async_reload(entry_id) + @callback def async_update_entry( self, diff --git a/homeassistant/const.py b/homeassistant/const.py index 4406c8bdfc3e2c..a0aafaad3cedb2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -202,6 +202,7 @@ # #### EVENTS #### EVENT_CALL_SERVICE = "call_service" EVENT_COMPONENT_LOADED = "component_loaded" +EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED = "config_entry_disabled_by_updated" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close" EVENT_HOMEASSISTANT_START = "homeassistant_start" diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 3dd44364604103..705f6cdd89a5a5 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -6,7 +6,10 @@ import attr -from homeassistant.const import EVENT_HOMEASSISTANT_STARTED +from homeassistant.const import ( + EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, + EVENT_HOMEASSISTANT_STARTED, +) from homeassistant.core import Event, callback from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util @@ -37,6 +40,7 @@ REGISTERED_DEVICE = "registered" DELETED_DEVICE = "deleted" +DISABLED_CONFIG_ENTRY = "config_entry" DISABLED_INTEGRATION = "integration" DISABLED_USER = "user" @@ -65,6 +69,7 @@ class DeviceEntry: default=None, validator=attr.validators.in_( ( + DISABLED_CONFIG_ENTRY, DISABLED_INTEGRATION, DISABLED_USER, None, @@ -138,6 +143,10 @@ def __init__(self, hass: HomeAssistantType) -> None: self.hass = hass self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._clear_index() + self.hass.bus.async_listen( + EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, + self.async_config_entry_disabled_by_changed, + ) @callback def async_get(self, device_id: str) -> Optional[DeviceEntry]: @@ -609,6 +618,38 @@ def async_clear_area_id(self, area_id: str) -> None: if area_id == device.area_id: self._async_update_device(dev_id, area_id=None) + @callback + def async_config_entry_disabled_by_changed(self, event: Event) -> None: + """Handle a config entry being disabled or enabled. + + Disable devices in the registry that are associated to a config entry when + the config entry is disabled. + """ + config_entry = self.hass.config_entries.async_get_entry( + event.data["config_entry_id"] + ) + + # The config entry may be deleted already if the event handling is late + if not config_entry: + return + + if not config_entry.disabled_by: + devices = async_entries_for_config_entry( + self, event.data["config_entry_id"] + ) + for device in devices: + if device.disabled_by != DISABLED_CONFIG_ENTRY: + continue + self.async_update_device(device.id, disabled_by=None) + return + + devices = async_entries_for_config_entry(self, event.data["config_entry_id"]) + for device in devices: + if device.disabled: + # Entity already disabled, do not overwrite + continue + self.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) + @callback def async_get(hass: HomeAssistantType) -> DeviceRegistry: diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 0938ea9165fe56..c86bd64d73e311 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -31,6 +31,7 @@ ATTR_RESTORED, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, + EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) @@ -157,6 +158,10 @@ def __init__(self, hass: HomeAssistantType): self.hass.bus.async_listen( EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified ) + self.hass.bus.async_listen( + EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, + self.async_config_entry_disabled_by_changed, + ) @callback def async_get_device_class_lookup(self, domain_device_classes: set) -> dict: @@ -349,10 +354,49 @@ def async_device_modified(self, event: Event) -> None: self.async_update_entity(entity.entity_id, disabled_by=None) return + if device.disabled_by == dr.DISABLED_CONFIG_ENTRY: + # Handled by async_config_entry_disabled + return + + # Fetch entities which are not already disabled entities = async_entries_for_device(self, event.data["device_id"]) for entity in entities: self.async_update_entity(entity.entity_id, disabled_by=DISABLED_DEVICE) + @callback + def async_config_entry_disabled_by_changed(self, event: Event) -> None: + """Handle a config entry being disabled or enabled. + + Disable entities in the registry that are associated to a config entry when + the config entry is disabled. + """ + config_entry = self.hass.config_entries.async_get_entry( + event.data["config_entry_id"] + ) + + # The config entry may be deleted already if the event handling is late + if not config_entry: + return + + if not config_entry.disabled_by: + entities = async_entries_for_config_entry( + self, event.data["config_entry_id"] + ) + for entity in entities: + if entity.disabled_by != DISABLED_CONFIG_ENTRY: + continue + self.async_update_entity(entity.entity_id, disabled_by=None) + return + + entities = async_entries_for_config_entry(self, event.data["config_entry_id"]) + for entity in entities: + if entity.disabled: + # Entity already disabled, do not overwrite + continue + self.async_update_entity( + entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY + ) + @callback def async_update_entity( self, diff --git a/tests/common.py b/tests/common.py index c07716dbfc9ab4..52d368853b306b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -749,6 +749,7 @@ def __init__( system_options={}, connection_class=config_entries.CONN_CLASS_UNKNOWN, unique_id=None, + disabled_by=None, ): """Initialize a mock config entry.""" kwargs = { @@ -761,6 +762,7 @@ def __init__( "title": title, "connection_class": connection_class, "unique_id": unique_id, + "disabled_by": disabled_by, } if source is not None: kwargs["source"] = source diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 87b1559a21b126..6bb1f1885eb8b5 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -68,6 +68,12 @@ def async_get_options_flow(config, options): state=core_ce.ENTRY_STATE_LOADED, connection_class=core_ce.CONN_CLASS_ASSUMED, ).add_to_hass(hass) + MockConfigEntry( + domain="comp3", + title="Test 3", + source="bla3", + disabled_by="user", + ).add_to_hass(hass) resp = await client.get("/api/config/config_entries/entry") assert resp.status == 200 @@ -83,6 +89,7 @@ def async_get_options_flow(config, options): "connection_class": "local_poll", "supports_options": True, "supports_unload": True, + "disabled_by": None, }, { "domain": "comp2", @@ -92,6 +99,17 @@ def async_get_options_flow(config, options): "connection_class": "assumed", "supports_options": False, "supports_unload": False, + "disabled_by": None, + }, + { + "domain": "comp3", + "title": "Test 3", + "source": "bla3", + "state": "not_loaded", + "connection_class": "unknown", + "supports_options": False, + "supports_unload": False, + "disabled_by": "user", }, ] @@ -680,6 +698,25 @@ async def test_update_system_options(hass, hass_ws_client): assert entry.system_options.disable_new_entities +async def test_update_system_options_nonexisting(hass, hass_ws_client): + """Test that we can update entry.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + "id": 5, + "type": "config_entries/system_options/update", + "entry_id": "non_existing", + "disable_new_entities": True, + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "not_found" + + async def test_update_entry(hass, hass_ws_client): """Test that we can update entry.""" assert await async_setup_component(hass, "config", {}) @@ -722,6 +759,83 @@ async def test_update_entry_nonexisting(hass, hass_ws_client): assert response["error"]["code"] == "not_found" +async def test_disable_entry(hass, hass_ws_client): + """Test that we can disable entry.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + entry = MockConfigEntry(domain="demo", state="loaded") + entry.add_to_hass(hass) + assert entry.disabled_by is None + + # Disable + await ws_client.send_json( + { + "id": 5, + "type": "config_entries/disable", + "entry_id": entry.entry_id, + "disabled_by": "user", + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"] == {"require_restart": True} + assert entry.disabled_by == "user" + assert entry.state == "failed_unload" + + # Enable + await ws_client.send_json( + { + "id": 6, + "type": "config_entries/disable", + "entry_id": entry.entry_id, + "disabled_by": None, + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"] == {"require_restart": True} + assert entry.disabled_by is None + assert entry.state == "failed_unload" + + # Enable again -> no op + await ws_client.send_json( + { + "id": 7, + "type": "config_entries/disable", + "entry_id": entry.entry_id, + "disabled_by": None, + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"] == {"require_restart": False} + assert entry.disabled_by is None + assert entry.state == "failed_unload" + + +async def test_disable_entry_nonexisting(hass, hass_ws_client): + """Test that we can disable entry.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + "id": 5, + "type": "config_entries/disable", + "entry_id": "non_existing", + "disabled_by": "user", + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "not_found" + + async def test_ignore_flow(hass, hass_ws_client): """Test we can ignore a flow.""" assert await async_setup_component(hass, "config", {}) @@ -763,3 +877,22 @@ async def async_step_user(self, user_input=None): assert entry.source == "ignore" assert entry.unique_id == "mock-unique-id" assert entry.title == "Test Integration" + + +async def test_ignore_flow_nonexisting(hass, hass_ws_client): + """Test we can ignore a flow.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + "id": 5, + "type": "config_entries/ignore_flow", + "flow_id": "non_existing", + "title": "Test Integration", + } + ) + response = await ws_client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == "not_found" diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index bc0e1c3bec95d8..965ebcd3e234b7 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1209,3 +1209,41 @@ async def test_verify_suggested_area_does_not_overwrite_area_id( suggested_area="New Game Room", ) assert entry2.area_id == game_room_area.id + + +async def test_disable_config_entry_disables_devices(hass, registry): + """Test that we disable entities tied to a config entry.""" + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + + entry1 = registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + entry2 = registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "34:56:AB:CD:EF:12")}, + disabled_by="user", + ) + + assert not entry1.disabled + assert entry2.disabled + + await hass.config_entries.async_set_disabled_by(config_entry.entry_id, "user") + await hass.async_block_till_done() + + entry1 = registry.async_get(entry1.id) + assert entry1.disabled + assert entry1.disabled_by == "config_entry" + entry2 = registry.async_get(entry2.id) + assert entry2.disabled + assert entry2.disabled_by == "user" + + await hass.config_entries.async_set_disabled_by(config_entry.entry_id, None) + await hass.async_block_till_done() + + entry1 = registry.async_get(entry1.id) + assert not entry1.disabled + entry2 = registry.async_get(entry2.id) + assert entry2.disabled + assert entry2.disabled_by == "user" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 71cfb33159188b..86cdab822382c3 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -757,9 +757,18 @@ async def test_disable_device_disables_entities(hass, registry): device_id=device_entry.id, disabled_by="user", ) + entry3 = registry.async_get_or_create( + "light", + "hue", + "EFGH", + config_entry=config_entry, + device_id=device_entry.id, + disabled_by="config_entry", + ) assert not entry1.disabled assert entry2.disabled + assert entry3.disabled device_registry.async_update_device(device_entry.id, disabled_by="user") await hass.async_block_till_done() @@ -770,6 +779,9 @@ async def test_disable_device_disables_entities(hass, registry): entry2 = registry.async_get(entry2.entity_id) assert entry2.disabled assert entry2.disabled_by == "user" + entry3 = registry.async_get(entry3.entity_id) + assert entry3.disabled + assert entry3.disabled_by == "config_entry" device_registry.async_update_device(device_entry.id, disabled_by=None) await hass.async_block_till_done() @@ -779,6 +791,74 @@ async def test_disable_device_disables_entities(hass, registry): entry2 = registry.async_get(entry2.entity_id) assert entry2.disabled assert entry2.disabled_by == "user" + entry3 = registry.async_get(entry3.entity_id) + assert entry3.disabled + assert entry3.disabled_by == "config_entry" + + +async def test_disable_config_entry_disables_entities(hass, registry): + """Test that we disable entities tied to a config entry.""" + device_registry = mock_device_registry(hass) + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + ) + + entry1 = registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + device_id=device_entry.id, + ) + entry2 = registry.async_get_or_create( + "light", + "hue", + "ABCD", + config_entry=config_entry, + device_id=device_entry.id, + disabled_by="user", + ) + entry3 = registry.async_get_or_create( + "light", + "hue", + "EFGH", + config_entry=config_entry, + device_id=device_entry.id, + disabled_by="device", + ) + + assert not entry1.disabled + assert entry2.disabled + assert entry3.disabled + + await hass.config_entries.async_set_disabled_by(config_entry.entry_id, "user") + await hass.async_block_till_done() + + entry1 = registry.async_get(entry1.entity_id) + assert entry1.disabled + assert entry1.disabled_by == "config_entry" + entry2 = registry.async_get(entry2.entity_id) + assert entry2.disabled + assert entry2.disabled_by == "user" + entry3 = registry.async_get(entry3.entity_id) + assert entry3.disabled + assert entry3.disabled_by == "device" + + await hass.config_entries.async_set_disabled_by(config_entry.entry_id, None) + await hass.async_block_till_done() + + entry1 = registry.async_get(entry1.entity_id) + assert not entry1.disabled + entry2 = registry.async_get(entry2.entity_id) + assert entry2.disabled + assert entry2.disabled_by == "user" + # The device was re-enabled, so entity disabled by the device will be re-enabled too + entry3 = registry.async_get(entry3.entity_id) + assert not entry3.disabled_by async def test_disabled_entities_excluded_from_entity_list(hass, registry): diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 435f2a11cc2a9e..8a479a802e45fa 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1108,6 +1108,110 @@ async def test_entry_reload_error(hass, manager, state): assert entry.state == state +async def test_entry_disable_succeed(hass, manager): + """Test that we can disable an entry.""" + entry = MockConfigEntry(domain="comp", state=config_entries.ENTRY_STATE_LOADED) + entry.add_to_hass(hass) + + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + async_unload_entry = AsyncMock(return_value=True) + + mock_integration( + hass, + MockModule( + "comp", + async_setup=async_setup, + async_setup_entry=async_setup_entry, + async_unload_entry=async_unload_entry, + ), + ) + mock_entity_platform(hass, "config_flow.comp", None) + + # Disable + assert await manager.async_set_disabled_by( + entry.entry_id, config_entries.DISABLED_USER + ) + assert len(async_unload_entry.mock_calls) == 1 + assert len(async_setup.mock_calls) == 0 + assert len(async_setup_entry.mock_calls) == 0 + assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED + + # Enable + assert await manager.async_set_disabled_by(entry.entry_id, None) + assert len(async_unload_entry.mock_calls) == 1 + assert len(async_setup.mock_calls) == 1 + assert len(async_setup_entry.mock_calls) == 1 + assert entry.state == config_entries.ENTRY_STATE_LOADED + + +async def test_entry_disable_without_reload_support(hass, manager): + """Test that we can disable an entry without reload support.""" + entry = MockConfigEntry(domain="comp", state=config_entries.ENTRY_STATE_LOADED) + entry.add_to_hass(hass) + + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + + mock_integration( + hass, + MockModule( + "comp", + async_setup=async_setup, + async_setup_entry=async_setup_entry, + ), + ) + mock_entity_platform(hass, "config_flow.comp", None) + + # Disable + assert not await manager.async_set_disabled_by( + entry.entry_id, config_entries.DISABLED_USER + ) + assert len(async_setup.mock_calls) == 0 + assert len(async_setup_entry.mock_calls) == 0 + assert entry.state == config_entries.ENTRY_STATE_FAILED_UNLOAD + + # Enable + with pytest.raises(config_entries.OperationNotAllowed): + await manager.async_set_disabled_by(entry.entry_id, None) + assert len(async_setup.mock_calls) == 0 + assert len(async_setup_entry.mock_calls) == 0 + assert entry.state == config_entries.ENTRY_STATE_FAILED_UNLOAD + + +async def test_entry_enable_without_reload_support(hass, manager): + """Test that we can disable an entry without reload support.""" + entry = MockConfigEntry(domain="comp", disabled_by=config_entries.DISABLED_USER) + entry.add_to_hass(hass) + + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + + mock_integration( + hass, + MockModule( + "comp", + async_setup=async_setup, + async_setup_entry=async_setup_entry, + ), + ) + mock_entity_platform(hass, "config_flow.comp", None) + + # Enable + assert await manager.async_set_disabled_by(entry.entry_id, None) + assert len(async_setup.mock_calls) == 1 + assert len(async_setup_entry.mock_calls) == 1 + assert entry.state == config_entries.ENTRY_STATE_LOADED + + # Disable + assert not await manager.async_set_disabled_by( + entry.entry_id, config_entries.DISABLED_USER + ) + assert len(async_setup.mock_calls) == 1 + assert len(async_setup_entry.mock_calls) == 1 + assert entry.state == config_entries.ENTRY_STATE_FAILED_UNLOAD + + async def test_init_custom_integration(hass): """Test initializing flow for custom integration.""" integration = loader.Integration( From d9ab1482bc5e4e0e2eea675036aa352e8e38b8d5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Feb 2021 17:24:14 -1000 Subject: [PATCH 0652/1818] Add support for preset modes in homekit fans (#45962) --- homeassistant/components/homekit/type_fans.py | 43 ++++++++++ tests/components/homekit/test_type_fans.py | 83 +++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 306beb89c48c05..1efb3b6c8bed85 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -8,12 +8,15 @@ ATTR_OSCILLATING, ATTR_PERCENTAGE, ATTR_PERCENTAGE_STEP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, SERVICE_SET_PERCENTAGE, + SERVICE_SET_PRESET_MODE, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, @@ -31,11 +34,14 @@ from .accessories import TYPES, HomeAccessory from .const import ( CHAR_ACTIVE, + CHAR_NAME, + CHAR_ON, CHAR_ROTATION_DIRECTION, CHAR_ROTATION_SPEED, CHAR_SWING_MODE, PROP_MIN_STEP, SERV_FANV2, + SERV_SWITCH, ) _LOGGER = logging.getLogger(__name__) @@ -56,6 +62,7 @@ def __init__(self, *args): 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) if features & SUPPORT_DIRECTION: chars.append(CHAR_ROTATION_DIRECTION) @@ -65,11 +72,13 @@ def __init__(self, *args): chars.append(CHAR_ROTATION_SPEED) serv_fan = self.add_preload_service(SERV_FANV2, 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.preset_mode_chars = {} if CHAR_ROTATION_DIRECTION in chars: self.char_direction = serv_fan.configure_char( @@ -86,6 +95,22 @@ def __init__(self, *args): properties={PROP_MIN_STEP: percentage_step}, ) + if preset_modes: + for preset_mode in preset_modes: + preset_serv = self.add_preload_service(SERV_SWITCH, CHAR_NAME) + serv_fan.add_linked_service(preset_serv) + preset_serv.configure_char( + CHAR_NAME, value=f"{self.display_name} {preset_mode}" + ) + + self.preset_mode_chars[preset_mode] = preset_serv.configure_char( + CHAR_ON, + value=False, + setter_callback=lambda value, preset_mode=preset_mode: self.set_preset_mode( + value, preset_mode + ), + ) + if CHAR_SWING_MODE in chars: self.char_swing = serv_fan.configure_char(CHAR_SWING_MODE, value=0) self.async_update_state(state) @@ -120,6 +145,18 @@ def _set_chars(self, char_values): if CHAR_ROTATION_SPEED in char_values: self.set_percentage(char_values[CHAR_ROTATION_SPEED]) + def set_preset_mode(self, value, preset_mode): + """Set preset_mode if call came from HomeKit.""" + _LOGGER.debug( + "%s: Set preset_mode %s to %d", self.entity_id, preset_mode, value + ) + params = {ATTR_ENTITY_ID: self.entity_id} + if value: + params[ATTR_PRESET_MODE] = preset_mode + self.async_call_service(DOMAIN, SERVICE_SET_PRESET_MODE, params) + else: + self.async_call_service(DOMAIN, SERVICE_TURN_ON, params) + def set_state(self, value): """Set state if call came from HomeKit.""" _LOGGER.debug("%s: Set state to %d", self.entity_id, value) @@ -193,3 +230,9 @@ def async_update_state(self, new_state): hk_oscillating = 1 if oscillating else 0 if self.char_swing.value != hk_oscillating: self.char_swing.set_value(hk_oscillating) + + current_preset_mode = new_state.attributes.get(ATTR_PRESET_MODE) + for preset_mode, char in self.preset_mode_chars.items(): + hk_value = 1 if preset_mode == current_preset_mode else 0 + if char.value != hk_value: + char.set_value(hk_value) diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index baa47462cdc4cf..ba660f2f12dce1 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -7,11 +7,14 @@ ATTR_OSCILLATING, ATTR_PERCENTAGE, ATTR_PERCENTAGE_STEP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, DIRECTION_FORWARD, DIRECTION_REVERSE, DOMAIN, SUPPORT_DIRECTION, SUPPORT_OSCILLATE, + SUPPORT_PRESET_MODE, SUPPORT_SET_SPEED, ) from homeassistant.components.homekit.const import ATTR_VALUE, PROP_MIN_STEP @@ -557,3 +560,83 @@ async def test_fan_restore(hass, hk_driver, events): assert acc.char_direction is not None assert acc.char_speed is not None assert acc.char_swing is not None + + +async def test_fan_preset_modes(hass, hk_driver, events): + """Test fan with direction.""" + entity_id = "fan.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_PRESET_MODE, + ATTR_PRESET_MODE: "auto", + ATTR_PRESET_MODES: ["auto", "smart"], + }, + ) + await hass.async_block_till_done() + acc = Fan(hass, hk_driver, "Fan", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.preset_mode_chars["auto"].value == 1 + assert acc.preset_mode_chars["smart"].value == 0 + + await acc.run() + await hass.async_block_till_done() + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_PRESET_MODE, + ATTR_PRESET_MODE: "smart", + ATTR_PRESET_MODES: ["auto", "smart"], + }, + ) + await hass.async_block_till_done() + + assert acc.preset_mode_chars["auto"].value == 0 + assert acc.preset_mode_chars["smart"].value == 1 + # 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_auto_iid = acc.preset_mode_chars["auto"].to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_auto_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] == "auto" + assert len(events) == 1 + assert events[-1].data["service"] == "set_preset_mode" + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_auto_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 events[-1].data["service"] == "turn_on" + assert len(events) == 2 From e2fd255a96500e4a7de88b79b60d4f53c6d9903a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 20 Feb 2021 22:52:41 -1000 Subject: [PATCH 0653/1818] Cleanup recorder tests (#46836) --- tests/components/recorder/test_init.py | 2 +- tests/components/recorder/test_util.py | 33 +++++++++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index ca25fe102840c2..dfa659448115b0 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -49,7 +49,7 @@ async def test_shutdown_before_startup_finishes(hass): with patch.object(hass.data[DATA_INSTANCE], "engine"): hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() - hass.stop() + await hass.async_stop() run_info = await hass.async_add_executor_job(run_information_with_session, session) diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index f1d55999ae4261..4aba6569a413f5 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -8,11 +8,16 @@ from homeassistant.components.recorder import util from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX +from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.util import dt as dt_util -from .common import corrupt_db_file, wait_recording_done +from .common import corrupt_db_file -from tests.common import get_test_home_assistant, init_recorder_component +from tests.common import ( + async_init_recorder_component, + get_test_home_assistant, + init_recorder_component, +) @pytest.fixture @@ -142,18 +147,25 @@ def test_validate_or_move_away_sqlite_database_without_integrity_check( assert util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is True -def test_last_run_was_recently_clean(hass_recorder): +async def test_last_run_was_recently_clean(hass): """Test we can check if the last recorder run was recently clean.""" - hass = hass_recorder() + await async_init_recorder_component(hass) + await hass.async_block_till_done() cursor = hass.data[DATA_INSTANCE].engine.raw_connection().cursor() - assert util.last_run_was_recently_clean(cursor) is False + assert ( + await hass.async_add_executor_job(util.last_run_was_recently_clean, cursor) + is False + ) - hass.data[DATA_INSTANCE]._shutdown() - wait_recording_done(hass) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() - assert util.last_run_was_recently_clean(cursor) is True + assert ( + await hass.async_add_executor_job(util.last_run_was_recently_clean, cursor) + is True + ) thirty_min_future_time = dt_util.utcnow() + timedelta(minutes=30) @@ -161,7 +173,10 @@ def test_last_run_was_recently_clean(hass_recorder): "homeassistant.components.recorder.dt_util.utcnow", return_value=thirty_min_future_time, ): - assert util.last_run_was_recently_clean(cursor) is False + assert ( + await hass.async_add_executor_job(util.last_run_was_recently_clean, cursor) + is False + ) def test_basic_sanity_check(hass_recorder): From d33a1a5ff85569d94b521c75a4ad1bb8378a63d0 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sun, 21 Feb 2021 14:54:36 +0100 Subject: [PATCH 0654/1818] Refine printing of ConditionError (#46838) * Refine printing of ConditionError * Improve coverage * name -> type --- .../components/automation/__init__.py | 21 ++- .../homeassistant/triggers/numeric_state.py | 2 +- homeassistant/exceptions.py | 69 ++++++++- homeassistant/helpers/condition.py | 140 +++++++++++------- homeassistant/helpers/script.py | 8 +- tests/helpers/test_condition.py | 59 ++++++-- tests/test_exceptions.py | 46 ++++++ 7 files changed, 272 insertions(+), 73 deletions(-) create mode 100644 tests/test_exceptions.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3a48b3e3cc2e09..7e07f35be45dbc 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -32,7 +32,12 @@ callback, split_entity_id, ) -from homeassistant.exceptions import ConditionError, HomeAssistantError +from homeassistant.exceptions import ( + ConditionError, + ConditionErrorContainer, + ConditionErrorIndex, + HomeAssistantError, +) from homeassistant.helpers import condition, extract_domain_configs, template import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity @@ -616,16 +621,22 @@ async def _async_process_if(hass, config, p_config): def if_action(variables=None): """AND all conditions.""" errors = [] - for check in checks: + for index, check in enumerate(checks): try: if not check(hass, variables): return False except ConditionError as ex: - errors.append(f"Error in 'condition' evaluation: {ex}") + errors.append( + ConditionErrorIndex( + "condition", index=index, total=len(checks), error=ex + ) + ) if errors: - for error in errors: - LOGGER.warning("%s", error) + LOGGER.warning( + "Error evaluating condition:\n%s", + ConditionErrorContainer("condition", errors=errors), + ) return False return True diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 16b3fb97475be0..59f16c41a36e3a 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -112,7 +112,7 @@ def check_numeric_state(entity_id, from_s, to_s): armed_entities.add(entity_id) except exceptions.ConditionError as ex: _LOGGER.warning( - "Error initializing 'numeric_state' trigger for '%s': %s", + "Error initializing '%s' trigger: %s", automation_info["name"], ex, ) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 852795ebb4ac88..0ac231fd314b9a 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,5 +1,7 @@ """The exceptions used by Home Assistant.""" -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Generator, Optional, Sequence + +import attr if TYPE_CHECKING: from .core import Context # noqa: F401 pylint: disable=unused-import @@ -25,9 +27,74 @@ def __init__(self, exception: Exception) -> None: super().__init__(f"{exception.__class__.__name__}: {exception}") +@attr.s class ConditionError(HomeAssistantError): """Error during condition evaluation.""" + # The type of the failed condition, such as 'and' or 'numeric_state' + type: str = attr.ib() + + @staticmethod + def _indent(indent: int, message: str) -> str: + """Return indentation.""" + return " " * indent + message + + def output(self, indent: int) -> Generator: + """Yield an indented representation.""" + raise NotImplementedError() + + def __str__(self) -> str: + """Return string representation.""" + return "\n".join(list(self.output(indent=0))) + + +@attr.s +class ConditionErrorMessage(ConditionError): + """Condition error message.""" + + # A message describing this error + message: str = attr.ib() + + def output(self, indent: int) -> Generator: + """Yield an indented representation.""" + yield self._indent(indent, f"In '{self.type}' condition: {self.message}") + + +@attr.s +class ConditionErrorIndex(ConditionError): + """Condition error with index.""" + + # The zero-based index of the failed condition, for conditions with multiple parts + index: int = attr.ib() + # The total number of parts in this condition, including non-failed parts + total: int = attr.ib() + # The error that this error wraps + error: ConditionError = attr.ib() + + def output(self, indent: int) -> Generator: + """Yield an indented representation.""" + if self.total > 1: + yield self._indent( + indent, f"In '{self.type}' (item {self.index+1} of {self.total}):" + ) + else: + yield self._indent(indent, f"In '{self.type}':") + + yield from self.error.output(indent + 1) + + +@attr.s +class ConditionErrorContainer(ConditionError): + """Condition error with index.""" + + # List of ConditionErrors that this error wraps + errors: Sequence[ConditionError] = attr.ib() + + def output(self, indent: int) -> Generator: + """Yield an indented representation.""" + for item in self.errors: + yield from item.output(indent) + class PlatformNotReady(HomeAssistantError): """Error to indicate that platform is not ready.""" diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index b66ee6c797610d..c20755a1780fe0 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -36,7 +36,14 @@ WEEKDAYS, ) from homeassistant.core import HomeAssistant, State, callback -from homeassistant.exceptions import ConditionError, HomeAssistantError, TemplateError +from homeassistant.exceptions import ( + ConditionError, + ConditionErrorContainer, + ConditionErrorIndex, + ConditionErrorMessage, + HomeAssistantError, + TemplateError, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.sun import get_astral_event_date from homeassistant.helpers.template import Template @@ -109,18 +116,18 @@ def if_and_condition( ) -> bool: """Test and condition.""" errors = [] - for check in checks: + for index, check in enumerate(checks): try: if not check(hass, variables): return False except ConditionError as ex: - errors.append(str(ex)) - except Exception as ex: # pylint: disable=broad-except - errors.append(str(ex)) + errors.append( + ConditionErrorIndex("and", index=index, total=len(checks), error=ex) + ) # Raise the errors if no check was false if errors: - raise ConditionError("Error in 'and' condition: " + ", ".join(errors)) + raise ConditionErrorContainer("and", errors=errors) return True @@ -142,18 +149,18 @@ def if_or_condition( ) -> bool: """Test or condition.""" errors = [] - for check in checks: + for index, check in enumerate(checks): try: if check(hass, variables): return True except ConditionError as ex: - errors.append(str(ex)) - except Exception as ex: # pylint: disable=broad-except - errors.append(str(ex)) + errors.append( + ConditionErrorIndex("or", index=index, total=len(checks), error=ex) + ) # Raise the errors if no check was true if errors: - raise ConditionError("Error in 'or' condition: " + ", ".join(errors)) + raise ConditionErrorContainer("or", errors=errors) return False @@ -175,18 +182,18 @@ def if_not_condition( ) -> bool: """Test not condition.""" errors = [] - for check in checks: + for index, check in enumerate(checks): try: if check(hass, variables): return False except ConditionError as ex: - errors.append(str(ex)) - except Exception as ex: # pylint: disable=broad-except - errors.append(str(ex)) + errors.append( + ConditionErrorIndex("not", index=index, total=len(checks), error=ex) + ) # Raise the errors if no check was true if errors: - raise ConditionError("Error in 'not' condition: " + ", ".join(errors)) + raise ConditionErrorContainer("not", errors=errors) return True @@ -225,20 +232,21 @@ def async_numeric_state( ) -> bool: """Test a numeric state condition.""" if entity is None: - raise ConditionError("No entity specified") + raise ConditionErrorMessage("numeric_state", "no entity specified") if isinstance(entity, str): entity_id = entity entity = hass.states.get(entity) if entity is None: - raise ConditionError(f"Unknown entity {entity_id}") + raise ConditionErrorMessage("numeric_state", f"unknown entity {entity_id}") else: entity_id = entity.entity_id if attribute is not None and attribute not in entity.attributes: - raise ConditionError( - f"Attribute '{attribute}' (of entity {entity_id}) does not exist" + raise ConditionErrorMessage( + "numeric_state", + f"attribute '{attribute}' (of entity {entity_id}) does not exist", ) value: Any = None @@ -253,16 +261,21 @@ def async_numeric_state( try: value = value_template.async_render(variables) except TemplateError as ex: - raise ConditionError(f"Template error: {ex}") from ex + raise ConditionErrorMessage( + "numeric_state", f"template error: {ex}" + ) from ex if value in (STATE_UNAVAILABLE, STATE_UNKNOWN): - raise ConditionError("State is not available") + raise ConditionErrorMessage( + "numeric_state", f"state of {entity_id} is unavailable" + ) try: fvalue = float(value) - except ValueError as ex: - raise ConditionError( - f"Entity {entity_id} state '{value}' cannot be processed as a number" + except (ValueError, TypeError) as ex: + raise ConditionErrorMessage( + "numeric_state", + f"entity {entity_id} state '{value}' cannot be processed as a number", ) from ex if below is not None: @@ -272,9 +285,17 @@ def async_numeric_state( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionError(f"The below entity {below} is not available") - if fvalue >= float(below_entity.state): - return False + raise ConditionErrorMessage( + "numeric_state", f"the 'below' entity {below} is unavailable" + ) + try: + if fvalue >= float(below_entity.state): + return False + except (ValueError, TypeError) as ex: + raise ConditionErrorMessage( + "numeric_state", + f"the 'below' entity {below} state '{below_entity.state}' cannot be processed as a number", + ) from ex elif fvalue >= below: return False @@ -285,9 +306,17 @@ def async_numeric_state( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionError(f"The above entity {above} is not available") - if fvalue <= float(above_entity.state): - return False + raise ConditionErrorMessage( + "numeric_state", f"the 'above' entity {above} is unavailable" + ) + try: + if fvalue <= float(above_entity.state): + return False + except (ValueError, TypeError) as ex: + raise ConditionErrorMessage( + "numeric_state", + f"the 'above' entity {above} state '{above_entity.state}' cannot be processed as a number", + ) from ex elif fvalue <= above: return False @@ -335,20 +364,20 @@ def state( Async friendly. """ if entity is None: - raise ConditionError("No entity specified") + raise ConditionErrorMessage("state", "no entity specified") if isinstance(entity, str): entity_id = entity entity = hass.states.get(entity) if entity is None: - raise ConditionError(f"Unknown entity {entity_id}") + raise ConditionErrorMessage("state", f"unknown entity {entity_id}") else: entity_id = entity.entity_id if attribute is not None and attribute not in entity.attributes: - raise ConditionError( - f"Attribute '{attribute}' (of entity {entity_id}) does not exist" + raise ConditionErrorMessage( + "state", f"attribute '{attribute}' (of entity {entity_id}) does not exist" ) assert isinstance(entity, State) @@ -370,7 +399,9 @@ def state( ): state_entity = hass.states.get(req_state_value) if not state_entity: - continue + raise ConditionErrorMessage( + "state", f"the 'state' entity {req_state_value} is unavailable" + ) state_value = state_entity.state is_state = value == state_value if is_state: @@ -495,7 +526,7 @@ def async_template( try: value: str = value_template.async_render(variables, parse_result=False) except TemplateError as ex: - raise ConditionError(f"Error in 'template' condition: {ex}") from ex + raise ConditionErrorMessage("template", str(ex)) from ex return value.lower() == "true" @@ -538,9 +569,7 @@ def time( elif isinstance(after, str): after_entity = hass.states.get(after) if not after_entity: - raise ConditionError( - f"Error in 'time' condition: The 'after' entity {after} is not available" - ) + raise ConditionErrorMessage("time", f"unknown 'after' entity {after}") after = dt_util.dt.time( after_entity.attributes.get("hour", 23), after_entity.attributes.get("minute", 59), @@ -552,9 +581,7 @@ def time( elif isinstance(before, str): before_entity = hass.states.get(before) if not before_entity: - raise ConditionError( - f"Error in 'time' condition: The 'before' entity {before} is not available" - ) + raise ConditionErrorMessage("time", f"unknown 'before' entity {before}") before = dt_util.dt.time( before_entity.attributes.get("hour", 23), before_entity.attributes.get("minute", 59), @@ -609,24 +636,24 @@ def zone( Async friendly. """ if zone_ent is None: - raise ConditionError("No zone specified") + raise ConditionErrorMessage("zone", "no zone specified") if isinstance(zone_ent, str): zone_ent_id = zone_ent zone_ent = hass.states.get(zone_ent) if zone_ent is None: - raise ConditionError(f"Unknown zone {zone_ent_id}") + raise ConditionErrorMessage("zone", f"unknown zone {zone_ent_id}") if entity is None: - raise ConditionError("No entity specified") + raise ConditionErrorMessage("zone", "no entity specified") if isinstance(entity, str): entity_id = entity entity = hass.states.get(entity) if entity is None: - raise ConditionError(f"Unknown entity {entity_id}") + raise ConditionErrorMessage("zone", f"unknown entity {entity_id}") else: entity_id = entity.entity_id @@ -634,10 +661,14 @@ def zone( longitude = entity.attributes.get(ATTR_LONGITUDE) if latitude is None: - raise ConditionError(f"Entity {entity_id} has no 'latitude' attribute") + raise ConditionErrorMessage( + "zone", f"entity {entity_id} has no 'latitude' attribute" + ) if longitude is None: - raise ConditionError(f"Entity {entity_id} has no 'longitude' attribute") + raise ConditionErrorMessage( + "zone", f"entity {entity_id} has no 'longitude' attribute" + ) return zone_cmp.in_zone( zone_ent, latitude, longitude, entity.attributes.get(ATTR_GPS_ACCURACY, 0) @@ -664,15 +695,20 @@ def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: try: if zone(hass, zone_entity_id, entity_id): entity_ok = True - except ConditionError as ex: - errors.append(str(ex)) + except ConditionErrorMessage as ex: + errors.append( + ConditionErrorMessage( + "zone", + f"error matching {entity_id} with {zone_entity_id}: {ex.message}", + ) + ) if not entity_ok: all_ok = False # Raise the errors only if no definitive result was found if errors and not all_ok: - raise ConditionError("Error in 'zone' condition: " + ", ".join(errors)) + raise ConditionErrorContainer("zone", errors=errors) return all_ok diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 2e8348bcaf8473..e4eb0d4a901788 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -516,7 +516,7 @@ async def _async_condition_step(self): try: check = cond(self._hass, self._variables) except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'condition' evaluation: %s", ex) + _LOGGER.warning("Error in 'condition' evaluation:\n%s", ex) check = False self._log("Test condition %s: %s", self._script.last_action, check) @@ -575,7 +575,7 @@ async def async_run_sequence(iteration, extra_msg=""): ): break except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'while' evaluation: %s", ex) + _LOGGER.warning("Error in 'while' evaluation:\n%s", ex) break await async_run_sequence(iteration) @@ -593,7 +593,7 @@ async def async_run_sequence(iteration, extra_msg=""): ): break except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'until' evaluation: %s", ex) + _LOGGER.warning("Error in 'until' evaluation:\n%s", ex) break if saved_repeat_vars: @@ -614,7 +614,7 @@ async def _async_choose_step(self) -> None: await self._async_run_script(script) return except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'choose' evaluation: %s", ex) + _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: await self._async_run_script(choose_data["default"]) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 63ef9ba56d8abf..2c35a3c8b155ba 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -386,8 +386,12 @@ async def test_if_numeric_state_raises_on_unavailable(hass, caplog): async def test_state_raises(hass): """Test that state raises ConditionError on errors.""" + # No entity + with pytest.raises(ConditionError, match="no entity"): + condition.state(hass, entity=None, req_state="missing") + # Unknown entity_id - with pytest.raises(ConditionError, match="Unknown entity"): + with pytest.raises(ConditionError, match="unknown entity"): test = await condition.async_from_config( hass, { @@ -400,7 +404,7 @@ async def test_state_raises(hass): test(hass) # Unknown attribute - with pytest.raises(ConditionError, match=r"Attribute .* does not exist"): + with pytest.raises(ConditionError, match=r"attribute .* does not exist"): test = await condition.async_from_config( hass, { @@ -414,6 +418,20 @@ async def test_state_raises(hass): hass.states.async_set("sensor.door", "open") test(hass) + # Unknown state entity + with pytest.raises(ConditionError, match="input_text.missing"): + test = await condition.async_from_config( + hass, + { + "condition": "state", + "entity_id": "sensor.door", + "state": "input_text.missing", + }, + ) + + hass.states.async_set("sensor.door", "open") + test(hass) + async def test_state_multiple_entities(hass): """Test with multiple entities in condition.""" @@ -564,7 +582,6 @@ async def test_state_using_input_entities(hass): "state": [ "input_text.hello", "input_select.hello", - "input_number.not_exist", "salut", ], }, @@ -616,7 +633,7 @@ async def test_state_using_input_entities(hass): async def test_numeric_state_raises(hass): """Test that numeric_state raises ConditionError on errors.""" # Unknown entity_id - with pytest.raises(ConditionError, match="Unknown entity"): + with pytest.raises(ConditionError, match="unknown entity"): test = await condition.async_from_config( hass, { @@ -629,7 +646,7 @@ async def test_numeric_state_raises(hass): test(hass) # Unknown attribute - with pytest.raises(ConditionError, match=r"Attribute .* does not exist"): + with pytest.raises(ConditionError, match=r"attribute .* does not exist"): test = await condition.async_from_config( hass, { @@ -659,7 +676,7 @@ async def test_numeric_state_raises(hass): test(hass) # Unavailable state - with pytest.raises(ConditionError, match="State is not available"): + with pytest.raises(ConditionError, match="state of .* is unavailable"): test = await condition.async_from_config( hass, { @@ -687,7 +704,7 @@ async def test_numeric_state_raises(hass): test(hass) # Below entity missing - with pytest.raises(ConditionError, match="below entity"): + with pytest.raises(ConditionError, match="'below' entity"): test = await condition.async_from_config( hass, { @@ -700,8 +717,16 @@ async def test_numeric_state_raises(hass): hass.states.async_set("sensor.temperature", 50) test(hass) + # Below entity not a number + with pytest.raises( + ConditionError, + match="'below'.*input_number.missing.*cannot be processed as a number", + ): + hass.states.async_set("input_number.missing", "number") + test(hass) + # Above entity missing - with pytest.raises(ConditionError, match="above entity"): + with pytest.raises(ConditionError, match="'above' entity"): test = await condition.async_from_config( hass, { @@ -714,6 +739,14 @@ async def test_numeric_state_raises(hass): hass.states.async_set("sensor.temperature", 50) test(hass) + # Above entity not a number + with pytest.raises( + ConditionError, + match="'above'.*input_number.missing.*cannot be processed as a number", + ): + hass.states.async_set("input_number.missing", "number") + test(hass) + async def test_numeric_state_multiple_entities(hass): """Test with multiple entities in condition.""" @@ -849,7 +882,10 @@ async def test_zone_raises(hass): }, ) - with pytest.raises(ConditionError, match="Unknown zone"): + with pytest.raises(ConditionError, match="no zone"): + condition.zone(hass, zone_ent=None, entity="sensor.any") + + with pytest.raises(ConditionError, match="unknown zone"): test(hass) hass.states.async_set( @@ -858,7 +894,10 @@ async def test_zone_raises(hass): {"name": "home", "latitude": 2.1, "longitude": 1.1, "radius": 10}, ) - with pytest.raises(ConditionError, match="Unknown entity"): + with pytest.raises(ConditionError, match="no entity"): + condition.zone(hass, zone_ent="zone.home", entity=None) + + with pytest.raises(ConditionError, match="unknown entity"): test(hass) hass.states.async_set( diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 00000000000000..959f0846cae17e --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,46 @@ +"""Test to verify that Home Assistant exceptions work.""" +from homeassistant.exceptions import ( + ConditionErrorContainer, + ConditionErrorIndex, + ConditionErrorMessage, +) + + +def test_conditionerror_format(): + """Test ConditionError stringifiers.""" + error1 = ConditionErrorMessage("test", "A test error") + assert str(error1) == "In 'test' condition: A test error" + + error2 = ConditionErrorMessage("test", "Another error") + assert str(error2) == "In 'test' condition: Another error" + + error_pos1 = ConditionErrorIndex("box", index=0, total=2, error=error1) + assert ( + str(error_pos1) + == """In 'box' (item 1 of 2): + In 'test' condition: A test error""" + ) + + error_pos2 = ConditionErrorIndex("box", index=1, total=2, error=error2) + assert ( + str(error_pos2) + == """In 'box' (item 2 of 2): + In 'test' condition: Another error""" + ) + + error_container1 = ConditionErrorContainer("box", errors=[error_pos1, error_pos2]) + print(error_container1) + assert ( + str(error_container1) + == """In 'box' (item 1 of 2): + In 'test' condition: A test error +In 'box' (item 2 of 2): + In 'test' condition: Another error""" + ) + + error_pos3 = ConditionErrorIndex("box", index=0, total=1, error=error1) + assert ( + str(error_pos3) + == """In 'box': + In 'test' condition: A test error""" + ) From c1ee9f7e4a872b3b5e2dcab21b220329afce07a5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Feb 2021 09:47:38 -1000 Subject: [PATCH 0655/1818] Fix unmocked I/O in rituals_perfume_genie config flow test (#46862) --- .../rituals_perfume_genie/test_config_flow.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/components/rituals_perfume_genie/test_config_flow.py b/tests/components/rituals_perfume_genie/test_config_flow.py index 60ec389a3715bb..92c3e15c2478cc 100644 --- a/tests/components/rituals_perfume_genie/test_config_flow.py +++ b/tests/components/rituals_perfume_genie/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Rituals Perfume Genie config flow.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, MagicMock, patch from aiohttp import ClientResponseError from pyrituals import AuthenticationException @@ -13,6 +13,13 @@ WRONG_PASSWORD = "wrong-passw0rd" +def _mock_account(*_): + account = MagicMock() + account.authenticate = AsyncMock() + account.data = {CONF_EMAIL: TEST_EMAIL, ACCOUNT_HASH: "any"} + return account + + async def test_form(hass): """Test we get the form.""" result = await hass.config_entries.flow.async_init( @@ -22,6 +29,9 @@ async def test_form(hass): assert result["errors"] is None with patch( + "homeassistant.components.rituals_perfume_genie.config_flow.Account", + side_effect=_mock_account, + ), patch( "homeassistant.components.rituals_perfume_genie.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.rituals_perfume_genie.async_setup_entry", From 50a07f6d25ab40a1e3d435a885029839e56364a7 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Sun, 21 Feb 2021 16:42:06 -0500 Subject: [PATCH 0656/1818] Log zwave_js connection errors (#46867) --- homeassistant/components/zwave_js/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index d5624551b2797f..9c8c245e910413 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -181,6 +181,7 @@ def async_on_notification(notification: Notification) -> None: async with timeout(CONNECT_TIMEOUT): await client.connect() except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: + LOGGER.error("Failed to connect: %s", err) raise ConfigEntryNotReady from err else: LOGGER.info("Connected to Zwave JS Server") @@ -268,8 +269,8 @@ async def client_listen( await client.listen(driver_ready) except asyncio.CancelledError: should_reload = False - except BaseZwaveJSServerError: - pass + except BaseZwaveJSServerError as err: + LOGGER.error("Failed to listen: %s", err) # The entry needs to be reloaded since a new driver state # will be acquired on reconnect. From 871427f5f1a6fd6cc339118fbb6b760fe8dcd4be Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 22 Feb 2021 00:06:36 +0000 Subject: [PATCH 0657/1818] [ci skip] Translation update --- .../components/habitica/translations/pl.json | 20 +++++++++ .../keenetic_ndms2/translations/pl.json | 2 +- .../translations/et.json | 21 +++++++++ .../translations/nl.json | 21 +++++++++ .../translations/pl.json | 21 +++++++++ .../components/smarttub/translations/pl.json | 22 ++++++++++ .../components/subaru/translations/ca.json | 44 +++++++++++++++++++ .../components/subaru/translations/en.json | 44 +++++++++++++++++++ .../components/subaru/translations/et.json | 44 +++++++++++++++++++ .../components/subaru/translations/pl.json | 44 +++++++++++++++++++ 10 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/habitica/translations/pl.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/et.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/nl.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/pl.json create mode 100644 homeassistant/components/smarttub/translations/pl.json create mode 100644 homeassistant/components/subaru/translations/ca.json create mode 100644 homeassistant/components/subaru/translations/en.json create mode 100644 homeassistant/components/subaru/translations/et.json create mode 100644 homeassistant/components/subaru/translations/pl.json diff --git a/homeassistant/components/habitica/translations/pl.json b/homeassistant/components/habitica/translations/pl.json new file mode 100644 index 00000000000000..f06f1a0e1aaa8b --- /dev/null +++ b/homeassistant/components/habitica/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "api_user": "Identyfikator API u\u017cytkownika Habitica", + "name": "Nadpisanie nazwy u\u017cytkownika Habitica. B\u0119dzie u\u017cywany do wywo\u0142a\u0144 serwisowych.", + "url": "URL" + }, + "description": "Po\u0142\u0105cz sw\u00f3j profil Habitica, aby umo\u017cliwi\u0107 monitorowanie profilu i zada\u0144 u\u017cytkownika. Pami\u0119taj, \u017ce api_id i api_key musz\u0105 zosta\u0107 pobrane z https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/pl.json b/homeassistant/components/keenetic_ndms2/translations/pl.json index 30e7b9a4fae76d..13bcbfb91baf23 100644 --- a/homeassistant/components/keenetic_ndms2/translations/pl.json +++ b/homeassistant/components/keenetic_ndms2/translations/pl.json @@ -28,7 +28,7 @@ "include_associated": "U\u017cyj danych skojarze\u0144 WiFi AP (ignorowane, je\u015bli u\u017cywane s\u0105 dane hotspotu)", "interfaces": "Wybierz interfejsy do skanowania", "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania", - "try_hotspot": "U\u017cyj danych \u201eip hotspot\u201d (najdok\u0142adniejsze)" + "try_hotspot": "U\u017cyj danych \u201eIP hotspot\u201d (najdok\u0142adniejsze)" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/et.json b/homeassistant/components/rituals_perfume_genie/translations/et.json new file mode 100644 index 00000000000000..17720a597924eb --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "email": "E-posti aadress", + "password": "Salas\u00f5na" + }, + "title": "Loo \u00fchendus oma Ritualsi kontoga" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/nl.json b/homeassistant/components/rituals_perfume_genie/translations/nl.json new file mode 100644 index 00000000000000..432079cac257fc --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Wachtwoord" + }, + "title": "Verbind met uw Rituals account" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/pl.json b/homeassistant/components/rituals_perfume_genie/translations/pl.json new file mode 100644 index 00000000000000..9e8c9839cbe962 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/pl.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "email": "Adres e-mail", + "password": "Has\u0142o" + }, + "title": "Po\u0142\u0105czenie z kontem Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/pl.json b/homeassistant/components/smarttub/translations/pl.json new file mode 100644 index 00000000000000..2c3f097d6d003a --- /dev/null +++ b/homeassistant/components/smarttub/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "email": "Adres e-mail", + "password": "Has\u0142o" + }, + "description": "Wprowad\u017a sw\u00f3j adres e-mail SmartTub oraz has\u0142o, aby si\u0119 zalogowa\u0107", + "title": "Logowanie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/ca.json b/homeassistant/components/subaru/translations/ca.json new file mode 100644 index 00000000000000..51cd44b4ce2b1b --- /dev/null +++ b/homeassistant/components/subaru/translations/ca.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "error": { + "bad_pin_format": "El PIN ha de tenir 4 d\u00edgits", + "cannot_connect": "Ha fallat la connexi\u00f3", + "incorrect_pin": "PIN incorrecte", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Introdueix el teu PIN de MySubaru\nNOTA: tots els vehicles associats a un compte han de tenir el mateix PIN", + "title": "Configuraci\u00f3 de Subaru Starlink" + }, + "user": { + "data": { + "country": "Selecciona un pa\u00eds", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Introdueix les teves credencials de MySubaru\nNOTA: la primera configuraci\u00f3 pot tardar fins a 30 segons", + "title": "Configuraci\u00f3 de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Activa el sondeig de vehicle" + }, + "description": "Quan estigui activat, el sondeig de vehicle enviar\u00e0 una ordre al vehicle cada 2 hores per tal d'obtenir noves dades. Sense el sondeig de vehicle, les noves dades nom\u00e9s es rebr\u00e0n quan el vehicle envia autom\u00e0ticament les dades (normalment despr\u00e9s de l'aturada del motor).", + "title": "Opcions de Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/en.json b/homeassistant/components/subaru/translations/en.json new file mode 100644 index 00000000000000..ea15ff0055206c --- /dev/null +++ b/homeassistant/components/subaru/translations/en.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured", + "cannot_connect": "Failed to connect" + }, + "error": { + "bad_pin_format": "PIN should be 4 digits", + "cannot_connect": "Failed to connect", + "incorrect_pin": "Incorrect PIN", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Please enter your MySubaru PIN\nNOTE: All vehicles in account must have the same PIN", + "title": "Subaru Starlink Configuration" + }, + "user": { + "data": { + "country": "Select country", + "password": "Password", + "username": "Username" + }, + "description": "Please enter your MySubaru credentials\nNOTE: Initial setup may take up to 30 seconds", + "title": "Subaru Starlink Configuration" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Enable vehicle polling" + }, + "description": "When enabled, vehicle polling will send a remote command to your vehicle every 2 hours to obtain new sensor data. Without vehicle polling, new sensor data is only received when the vehicle automatically pushes data (normally after engine shutdown).", + "title": "Subaru Starlink Options" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/et.json b/homeassistant/components/subaru/translations/et.json new file mode 100644 index 00000000000000..30dd690f84939f --- /dev/null +++ b/homeassistant/components/subaru/translations/et.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "cannot_connect": "\u00dchendamine nurjus" + }, + "error": { + "bad_pin_format": "PIN-kood peaks olema 4-kohaline", + "cannot_connect": "\u00dchendamine nurjus", + "incorrect_pin": "Vale PIN-kood", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Sisesta oma MySubaru PIN-kood\n M\u00c4RKUS. K\u00f5igil kontol olevatel s\u00f5idukitel peab olema sama PIN-kood", + "title": "Subaru Starlinki konfiguratsioon" + }, + "user": { + "data": { + "country": "Vali riik", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Sisesta oma MySubaru mandaat\n M\u00c4RKUS. Esmane seadistamine v\u00f5ib v\u00f5tta kuni 30 sekundit", + "title": "Subaru Starlinki konfiguratsioon" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Luba s\u00f5iduki k\u00fcsitlus" + }, + "description": "Kui see on lubatud, saadetakse k\u00fcsitlus ts\u00f5idukile iga kahe tunni j\u00e4rel, et saada uusi anduriandmeid. Ilma s\u00f5iduki valimiseta saadakse uusi anduriandmeid ainult siis, kui s\u00f5iduk automaatselt andmeid edastab (tavaliselt p\u00e4rast mootori seiskamist).", + "title": "Subaru Starlinki valikud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/pl.json b/homeassistant/components/subaru/translations/pl.json new file mode 100644 index 00000000000000..99415cdeea710f --- /dev/null +++ b/homeassistant/components/subaru/translations/pl.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "error": { + "bad_pin_format": "PIN powinien sk\u0142ada\u0107 si\u0119 z 4 cyfr", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "incorrect_pin": "Nieprawid\u0142owy PIN", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Wprowad\u017a sw\u00f3j PIN dla MySubaru\nUWAGA: Wszystkie pojazdy na koncie musz\u0105 mie\u0107 ten sam kod PIN", + "title": "Konfiguracja Subaru Starlink" + }, + "user": { + "data": { + "country": "Wybierz kraj", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce MySubaru\nUWAGA: Pocz\u0105tkowa konfiguracja mo\u017ce zaj\u0105\u0107 do 30 sekund", + "title": "Konfiguracja Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "W\u0142\u0105cz odpytywanie pojazdu" + }, + "description": "Po w\u0142\u0105czeniu, odpytywanie pojazdu b\u0119dzie co 2 godziny wysy\u0142a\u0107 zdalne polecenie do pojazdu w celu uzyskania nowych danych z czujnika. Bez odpytywania pojazdu, nowe dane z czujnika s\u0105 odbierane tylko wtedy, gdy pojazd automatycznie przesy\u0142a dane (zwykle po wy\u0142\u0105czeniu silnika).", + "title": "Opcje Subaru Starlink" + } + } + } +} \ No newline at end of file From 29c06965375037e445d79f92dfa7d41063020f77 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 21 Feb 2021 20:30:23 -0500 Subject: [PATCH 0658/1818] Clean up denonavr constants (#46883) --- homeassistant/components/denonavr/__init__.py | 3 +-- homeassistant/components/denonavr/config_flow.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py index 89c6413d1462a5..3946a0d6171ede 100644 --- a/homeassistant/components/denonavr/__init__.py +++ b/homeassistant/components/denonavr/__init__.py @@ -4,7 +4,7 @@ import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST +from homeassistant.const import ATTR_COMMAND, ATTR_ENTITY_ID, CONF_HOST from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.dispatcher import dispatcher_send @@ -24,7 +24,6 @@ CONF_RECEIVER = "receiver" UNDO_UPDATE_LISTENER = "undo_update_listener" SERVICE_GET_COMMAND = "get_command" -ATTR_COMMAND = "command" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index ec0ab4fb1772f8..0b7c0b718471df 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -9,7 +9,7 @@ from homeassistant import config_entries from homeassistant.components import ssdp -from homeassistant.const import CONF_HOST, CONF_MAC +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_TYPE from homeassistant.core import callback from homeassistant.helpers.device_registry import format_mac @@ -25,7 +25,6 @@ CONF_SHOW_ALL_SOURCES = "show_all_sources" CONF_ZONE2 = "zone2" CONF_ZONE3 = "zone3" -CONF_TYPE = "type" CONF_MODEL = "model" CONF_MANUFACTURER = "manufacturer" CONF_SERIAL_NUMBER = "serial_number" From 8330940996ee150b85cb90947fc980043176f856 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 21 Feb 2021 20:31:09 -0500 Subject: [PATCH 0659/1818] Clean up acer_projector constants (#46880) --- homeassistant/components/acer_projector/switch.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index f947f3fe0c080e..101f7cbd6155b0 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -9,6 +9,7 @@ from homeassistant.const import ( CONF_FILENAME, CONF_NAME, + CONF_TIMEOUT, STATE_OFF, STATE_ON, STATE_UNKNOWN, @@ -17,7 +18,6 @@ _LOGGER = logging.getLogger(__name__) -CONF_TIMEOUT = "timeout" CONF_WRITE_TIMEOUT = "write_timeout" DEFAULT_NAME = "Acer Projector" @@ -74,7 +74,6 @@ class AcerSwitch(SwitchEntity): def __init__(self, serial_port, name, timeout, write_timeout, **kwargs): """Init of the Acer projector.""" - self.ser = serial.Serial( port=serial_port, timeout=timeout, write_timeout=write_timeout, **kwargs ) @@ -90,7 +89,6 @@ def __init__(self, serial_port, name, timeout, write_timeout, **kwargs): def _write_read(self, msg): """Write to the projector and read the return.""" - ret = "" # Sometimes the projector won't answer for no reason or the projector # was disconnected during runtime. From 12c4db076c50f424549400a9690f4d3b429e20eb Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 21 Feb 2021 19:40:23 -0800 Subject: [PATCH 0660/1818] Add more sensors to SmartTub integration (#46839) --- homeassistant/components/smarttub/sensor.py | 36 +++++++++++++++++---- tests/components/smarttub/conftest.py | 18 +++++++++++ tests/components/smarttub/test_sensor.py | 24 ++++++++++++-- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 320f288f36a68b..402fb87373f7fa 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -8,23 +8,45 @@ async def async_setup_entry(hass, entry, async_add_entities): - """Set up climate entity for the thermostat in the tub.""" + """Set up sensor entities for the sensors in the tub.""" controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] - entities = [SmartTubState(controller.coordinator, spa) for spa in controller.spas] + entities = [] + for spa in controller.spas: + entities.extend( + [ + SmartTubSensor(controller.coordinator, spa, "State", "state"), + SmartTubSensor( + controller.coordinator, spa, "Flow Switch", "flowSwitch" + ), + SmartTubSensor(controller.coordinator, spa, "Ozone", "ozone"), + SmartTubSensor( + controller.coordinator, spa, "Blowout Cycle", "blowoutCycle" + ), + SmartTubSensor( + controller.coordinator, spa, "Cleanup Cycle", "cleanupCycle" + ), + ] + ) async_add_entities(entities) -class SmartTubState(SmartTubEntity): - """The state of the spa.""" +class SmartTubSensor(SmartTubEntity): + """Generic and base class for SmartTub sensors.""" - def __init__(self, coordinator, spa): + def __init__(self, coordinator, spa, sensor_name, spa_status_key): """Initialize the entity.""" - super().__init__(coordinator, spa, "state") + super().__init__(coordinator, spa, sensor_name) + self._spa_status_key = spa_status_key + + @property + def _state(self): + """Retrieve the underlying state from the spa.""" + return self.get_spa_status(self._spa_status_key) @property def state(self) -> str: """Return the current state of the sensor.""" - return self.get_spa_status("state").lower() + return self._state.lower() diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index d1bd7c377e36f8..efba121822d17d 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -47,6 +47,24 @@ def mock_spa(): "water": {"temperature": 38}, "heater": "ON", "state": "NORMAL", + "primaryFiltration": { + "cycle": 1, + "duration": 4, + "lastUpdated": "2021-01-20T11:38:57.014Z", + "mode": "NORMAL", + "startHour": 2, + "status": "INACTIVE", + }, + "secondaryFiltration": { + "lastUpdated": "2020-07-09T19:39:52.961Z", + "mode": "AWAY", + "status": "INACTIVE", + }, + "flowSwitch": "OPEN", + "ozone": "OFF", + "uv": "OFF", + "blowoutCycle": "INACTIVE", + "cleanupCycle": "INACTIVE", } return mock_spa diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 7d62440295e6f0..8551ac3cccb21e 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -3,8 +3,8 @@ from . import trigger_update -async def test_state_update(spa, setup_entry, hass, smarttub_api): - """Test the state entity.""" +async def test_sensors(spa, setup_entry, hass, smarttub_api): + """Test the sensors.""" entity_id = f"sensor.{spa.brand}_{spa.model}_state" state = hass.states.get(entity_id) @@ -16,3 +16,23 @@ async def test_state_update(spa, setup_entry, hass, smarttub_api): state = hass.states.get(entity_id) assert state is not None assert state.state == "bad" + + entity_id = f"sensor.{spa.brand}_{spa.model}_flow_switch" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "open" + + entity_id = f"sensor.{spa.brand}_{spa.model}_ozone" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "off" + + entity_id = f"sensor.{spa.brand}_{spa.model}_blowout_cycle" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "inactive" + + entity_id = f"sensor.{spa.brand}_{spa.model}_cleanup_cycle" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "inactive" From 0e44d612253b2b4178fedb88c8c3e38d61176392 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 22 Feb 2021 04:08:00 +0000 Subject: [PATCH 0661/1818] Add weather platform to template domain (#45031) Co-authored-by: J. Nick Koston --- homeassistant/components/template/const.py | 1 + homeassistant/components/template/weather.py | 220 +++++++++++++++++++ tests/components/template/test_weather.py | 56 +++++ 3 files changed, 277 insertions(+) create mode 100644 homeassistant/components/template/weather.py create mode 100644 tests/components/template/test_weather.py diff --git a/homeassistant/components/template/const.py b/homeassistant/components/template/const.py index cf1ec8bc1c3aca..5b38f19eaeb4f6 100644 --- a/homeassistant/components/template/const.py +++ b/homeassistant/components/template/const.py @@ -18,4 +18,5 @@ "sensor", "switch", "vacuum", + "weather", ] diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py new file mode 100644 index 00000000000000..560bd5639ba199 --- /dev/null +++ b/homeassistant/components/template/weather.py @@ -0,0 +1,220 @@ +"""Template platform that aggregates meteorological data.""" +import voluptuous as vol + +from homeassistant.components.weather import ( + ATTR_CONDITION_CLOUDY, + ATTR_CONDITION_EXCEPTIONAL, + ATTR_CONDITION_FOG, + ATTR_CONDITION_HAIL, + ATTR_CONDITION_LIGHTNING, + ATTR_CONDITION_LIGHTNING_RAINY, + ATTR_CONDITION_PARTLYCLOUDY, + ATTR_CONDITION_POURING, + ATTR_CONDITION_RAINY, + ATTR_CONDITION_SNOWY, + ATTR_CONDITION_SNOWY_RAINY, + ATTR_CONDITION_SUNNY, + ATTR_CONDITION_WINDY, + ATTR_CONDITION_WINDY_VARIANT, + ENTITY_ID_FORMAT, + WeatherEntity, +) +from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.reload import async_setup_reload_service + +from .const import DOMAIN, PLATFORMS +from .template_entity import TemplateEntity + +CONDITION_CLASSES = { + ATTR_CONDITION_CLOUDY, + ATTR_CONDITION_FOG, + ATTR_CONDITION_HAIL, + ATTR_CONDITION_LIGHTNING, + ATTR_CONDITION_LIGHTNING_RAINY, + ATTR_CONDITION_PARTLYCLOUDY, + ATTR_CONDITION_POURING, + ATTR_CONDITION_RAINY, + ATTR_CONDITION_SNOWY, + ATTR_CONDITION_SNOWY_RAINY, + ATTR_CONDITION_SUNNY, + ATTR_CONDITION_WINDY, + ATTR_CONDITION_WINDY_VARIANT, + ATTR_CONDITION_EXCEPTIONAL, +} + +CONF_WEATHER = "weather" +CONF_TEMPERATURE_TEMPLATE = "temperature_template" +CONF_HUMIDITY_TEMPLATE = "humidity_template" +CONF_CONDITION_TEMPLATE = "condition_template" +CONF_PRESSURE_TEMPLATE = "pressure_template" +CONF_WIND_SPEED_TEMPLATE = "wind_speed_template" +CONF_FORECAST_TEMPLATE = "forecast_template" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_CONDITION_TEMPLATE): cv.template, + vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template, + vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template, + vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template, + vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template, + vol.Optional(CONF_FORECAST_TEMPLATE): cv.template, + vol.Optional(CONF_UNIQUE_ID): cv.string, + } +) + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Template weather.""" + await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + + name = config[CONF_NAME] + condition_template = config[CONF_CONDITION_TEMPLATE] + temperature_template = config[CONF_TEMPERATURE_TEMPLATE] + humidity_template = config[CONF_HUMIDITY_TEMPLATE] + pressure_template = config.get(CONF_PRESSURE_TEMPLATE) + wind_speed_template = config.get(CONF_WIND_SPEED_TEMPLATE) + forecast_template = config.get(CONF_FORECAST_TEMPLATE) + unique_id = config.get(CONF_UNIQUE_ID) + + async_add_entities( + [ + WeatherTemplate( + hass, + name, + condition_template, + temperature_template, + humidity_template, + pressure_template, + wind_speed_template, + forecast_template, + unique_id, + ) + ] + ) + + +class WeatherTemplate(TemplateEntity, WeatherEntity): + """Representation of a weather condition.""" + + def __init__( + self, + hass, + name, + condition_template, + temperature_template, + humidity_template, + pressure_template, + wind_speed_template, + forecast_template, + unique_id, + ): + """Initialize the Demo weather.""" + super().__init__() + + self._name = name + self._condition_template = condition_template + self._temperature_template = temperature_template + self._humidity_template = humidity_template + self._pressure_template = pressure_template + self._wind_speed_template = wind_speed_template + self._forecast_template = forecast_template + self._unique_id = unique_id + + self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass) + + self._condition = None + self._temperature = None + self._humidity = None + self._pressure = None + self._wind_speed = None + self._forecast = [] + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def condition(self): + """Return the current condition.""" + return self._condition + + @property + def temperature(self): + """Return the temperature.""" + return self._temperature + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return self.hass.config.units.temperature_unit + + @property + def humidity(self): + """Return the humidity.""" + return self._humidity + + @property + def wind_speed(self): + """Return the wind speed.""" + return self._wind_speed + + @property + def pressure(self): + """Return the pressure.""" + return self._pressure + + @property + def forecast(self): + """Return the forecast.""" + return self._forecast + + @property + def attribution(self): + """Return the attribution.""" + return "Powered by Home Assistant" + + @property + def unique_id(self): + """Return the unique id of this light.""" + return self._unique_id + + async def async_added_to_hass(self): + """Register callbacks.""" + + if self._condition_template: + self.add_template_attribute( + "_condition", + self._condition_template, + lambda condition: condition if condition in CONDITION_CLASSES else None, + ) + if self._temperature_template: + self.add_template_attribute( + "_temperature", + self._temperature_template, + ) + if self._humidity_template: + self.add_template_attribute( + "_humidity", + self._humidity_template, + ) + if self._pressure_template: + self.add_template_attribute( + "_pressure", + self._pressure_template, + ) + if self._wind_speed_template: + self.add_template_attribute( + "_wind_speed", + self._wind_speed_template, + ) + if self._forecast_template: + self.add_template_attribute( + "_forecast", + self._forecast_template, + ) + await super().async_added_to_hass() diff --git a/tests/components/template/test_weather.py b/tests/components/template/test_weather.py new file mode 100644 index 00000000000000..155c7f33a9f828 --- /dev/null +++ b/tests/components/template/test_weather.py @@ -0,0 +1,56 @@ +"""The tests for the Template Weather platform.""" +from homeassistant.components.weather import ( + ATTR_WEATHER_HUMIDITY, + ATTR_WEATHER_PRESSURE, + ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_WIND_SPEED, + DOMAIN, +) +from homeassistant.setup import async_setup_component + + +async def test_template_state_text(hass): + """Test the state text of a template.""" + await async_setup_component( + hass, + DOMAIN, + { + "weather": [ + {"weather": {"platform": "demo"}}, + { + "platform": "template", + "name": "test", + "condition_template": "sunny", + "forecast_template": "{{ states.weather.demo.attributes.forecast }}", + "temperature_template": "{{ states('sensor.temperature') | float }}", + "humidity_template": "{{ states('sensor.humidity') | int }}", + "pressure_template": "{{ states('sensor.pressure') }}", + "wind_speed_template": "{{ states('sensor.windspeed') }}", + }, + ] + }, + ) + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + hass.states.async_set("sensor.temperature", 22.3) + await hass.async_block_till_done() + hass.states.async_set("sensor.humidity", 60) + await hass.async_block_till_done() + hass.states.async_set("sensor.pressure", 1000) + await hass.async_block_till_done() + hass.states.async_set("sensor.windspeed", 20) + await hass.async_block_till_done() + + state = hass.states.get("weather.test") + assert state is not None + + assert state.state == "sunny" + + data = state.attributes + assert data.get(ATTR_WEATHER_TEMPERATURE) == 22.3 + assert data.get(ATTR_WEATHER_HUMIDITY) == 60 + assert data.get(ATTR_WEATHER_PRESSURE) == 1000 + assert data.get(ATTR_WEATHER_WIND_SPEED) == 20 From d32dbc4cdd6090af3aead56470d6db6c26672b05 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 21 Feb 2021 20:46:54 -0800 Subject: [PATCH 0662/1818] Add support for SmartTub heat modes (#46876) --- homeassistant/components/smarttub/climate.py | 43 ++++++++++++++++---- tests/components/smarttub/conftest.py | 1 + tests/components/smarttub/test_climate.py | 26 +++++++++++- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 02d627d383eb6a..ee6afc80fb1fdc 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -6,6 +6,9 @@ CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, HVAC_MODE_HEAT, + PRESET_ECO, + PRESET_NONE, + SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -16,6 +19,8 @@ _LOGGER = logging.getLogger(__name__) +PRESET_DAY = "day" + async def async_setup_entry(hass, entry, async_add_entities): """Set up climate entity for the thermostat in the tub.""" @@ -32,6 +37,19 @@ async def async_setup_entry(hass, entry, async_add_entities): class SmartTubThermostat(SmartTubEntity, ClimateEntity): """The target water temperature for the spa.""" + PRESET_MODES = { + "AUTO": PRESET_NONE, + "ECO": PRESET_ECO, + "DAY": PRESET_DAY, + } + + HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} + + HVAC_ACTIONS = { + "OFF": CURRENT_HVAC_IDLE, + "ON": CURRENT_HVAC_HEAT, + } + def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__(coordinator, spa, "thermostat") @@ -44,12 +62,7 @@ def temperature_unit(self): @property def hvac_action(self): """Return the current running hvac operation.""" - heater_status = self.get_spa_status("heater") - if heater_status == "ON": - return CURRENT_HVAC_HEAT - if heater_status == "OFF": - return CURRENT_HVAC_IDLE - return None + return self.HVAC_ACTIONS.get(self.get_spa_status("heater")) @property def hvac_modes(self): @@ -92,7 +105,17 @@ def supported_features(self): Only target temperature is supported. """ - return SUPPORT_TARGET_TEMPERATURE + return SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + + @property + def preset_mode(self): + """Return the current preset mode.""" + return self.PRESET_MODES[self.get_spa_status("heatMode")] + + @property + def preset_modes(self): + """Return the available preset modes.""" + return list(self.PRESET_MODES.values()) @property def current_temperature(self): @@ -109,3 +132,9 @@ async def async_set_temperature(self, **kwargs): temperature = kwargs[ATTR_TEMPERATURE] await self.spa.set_temperature(temperature) await self.coordinator.async_refresh() + + async def async_set_preset_mode(self, preset_mode: str): + """Activate the specified preset mode.""" + heat_mode = self.HEAT_MODES[preset_mode] + await self.spa.set_heat_mode(heat_mode) + await self.coordinator.async_refresh() diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index efba121822d17d..265afcfc24c5ce 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -46,6 +46,7 @@ def mock_spa(): "setTemperature": 39, "water": {"temperature": 38}, "heater": "ON", + "heatMode": "AUTO", "state": "NORMAL", "primaryFiltration": { "cycle": 1, diff --git a/tests/components/smarttub/test_climate.py b/tests/components/smarttub/test_climate.py index 69fb642aab4a98..118264183e8279 100644 --- a/tests/components/smarttub/test_climate.py +++ b/tests/components/smarttub/test_climate.py @@ -9,12 +9,18 @@ ATTR_HVAC_MODES, ATTR_MAX_TEMP, ATTR_MIN_TEMP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, DOMAIN as CLIMATE_DOMAIN, HVAC_MODE_HEAT, + PRESET_ECO, + PRESET_NONE, SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, + SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.components.smarttub.const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP @@ -44,11 +50,15 @@ async def test_thermostat_update(spa, setup_entry, hass): assert set(state.attributes[ATTR_HVAC_MODES]) == {HVAC_MODE_HEAT} assert state.state == HVAC_MODE_HEAT - assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TARGET_TEMPERATURE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 38 assert state.attributes[ATTR_TEMPERATURE] == 39 assert state.attributes[ATTR_MAX_TEMP] == DEFAULT_MAX_TEMP assert state.attributes[ATTR_MIN_TEMP] == DEFAULT_MIN_TEMP + assert state.attributes[ATTR_PRESET_MODES] == ["none", "eco", "day"] await hass.services.async_call( CLIMATE_DOMAIN, @@ -66,6 +76,20 @@ async def test_thermostat_update(spa, setup_entry, hass): ) # does nothing + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO}, + blocking=True, + ) + spa.set_heat_mode.assert_called_with("ECO") + + spa.get_status.return_value["heatMode"] = "ECO" + await trigger_update(hass) + state = hass.states.get(entity_id) + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO + spa.get_status.side_effect = smarttub.APIError await trigger_update(hass) # should not fail From a8be5be37666d5a7b7cee27247c36aadc14dcfb2 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 21 Feb 2021 20:48:27 -0800 Subject: [PATCH 0663/1818] Add switch platform and pump entity to SmartTub (#46842) --- homeassistant/components/smarttub/__init__.py | 2 +- homeassistant/components/smarttub/const.py | 1 + .../components/smarttub/controller.py | 9 +- .../components/smarttub/manifest.json | 2 +- homeassistant/components/smarttub/switch.py | 82 +++++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/smarttub/conftest.py | 23 +++++- tests/components/smarttub/test_sensor.py | 2 +- tests/components/smarttub/test_switch.py | 38 +++++++++ 10 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/smarttub/switch.py create mode 100644 tests/components/smarttub/test_switch.py diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py index 2b80c92510f8eb..e8fc9989d38529 100644 --- a/homeassistant/components/smarttub/__init__.py +++ b/homeassistant/components/smarttub/__init__.py @@ -7,7 +7,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["climate", "sensor"] +PLATFORMS = ["climate", "sensor", "switch"] async def async_setup(hass, config): diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 99e7f21f86df8e..8292c5ef82662d 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -9,6 +9,7 @@ SCAN_INTERVAL = 60 POLLING_TIMEOUT = 10 +API_TIMEOUT = 5 DEFAULT_MIN_TEMP = 18.5 DEFAULT_MAX_TEMP = 40 diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index ad40c94fbed312..feb13066b4442f 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -86,7 +86,14 @@ async def async_update_data(self): return data async def _get_spa_data(self, spa): - return {"status": await spa.get_status()} + status, pumps = await asyncio.gather( + spa.get_status(), + spa.get_pumps(), + ) + return { + "status": status, + "pumps": {pump.id: pump for pump in pumps}, + } async def async_register_devices(self, entry): """Register devices with the device registry for all spas.""" diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json index 9735a3753b4f33..9360c59da8bacc 100644 --- a/homeassistant/components/smarttub/manifest.json +++ b/homeassistant/components/smarttub/manifest.json @@ -6,7 +6,7 @@ "dependencies": [], "codeowners": ["@mdz"], "requirements": [ - "python-smarttub==0.0.6" + "python-smarttub==0.0.12" ], "quality_scale": "platinum" } diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py new file mode 100644 index 00000000000000..7e4c83f6feb9d4 --- /dev/null +++ b/homeassistant/components/smarttub/switch.py @@ -0,0 +1,82 @@ +"""Platform for switch integration.""" +import logging + +import async_timeout +from smarttub import SpaPump + +from homeassistant.components.switch import SwitchEntity + +from .const import API_TIMEOUT, DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubEntity +from .helpers import get_spa_name + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up switch entities for the pumps on the tub.""" + + controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] + + entities = [ + SmartTubPump(controller.coordinator, pump) + for spa in controller.spas + for pump in await spa.get_pumps() + ] + + async_add_entities(entities) + + +class SmartTubPump(SmartTubEntity, SwitchEntity): + """A pump on a spa.""" + + def __init__(self, coordinator, pump: SpaPump): + """Initialize the entity.""" + super().__init__(coordinator, pump.spa, "pump") + self.pump_id = pump.id + self.pump_type = pump.type + + @property + def pump(self) -> SpaPump: + """Return the underlying SpaPump object for this entity.""" + return self.coordinator.data[self.spa.id]["pumps"][self.pump_id] + + @property + def unique_id(self) -> str: + """Return a unique ID for this pump entity.""" + return f"{super().unique_id}-{self.pump_id}" + + @property + def name(self) -> str: + """Return a name for this pump entity.""" + spa_name = get_spa_name(self.spa) + if self.pump_type == SpaPump.PumpType.CIRCULATION: + return f"{spa_name} Circulation Pump" + if self.pump_type == SpaPump.PumpType.JET: + return f"{spa_name} Jet {self.pump_id}" + return f"{spa_name} pump {self.pump_id}" + + @property + def is_on(self) -> bool: + """Return True if the pump is on.""" + return self.pump.state != SpaPump.PumpState.OFF + + async def async_turn_on(self, **kwargs) -> None: + """Turn the pump on.""" + + # the API only supports toggling + if not self.is_on: + await self.async_toggle() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the pump off.""" + + # the API only supports toggling + if self.is_on: + await self.async_toggle() + + async def async_toggle(self, **kwargs) -> None: + """Toggle the pump on or off.""" + async with async_timeout.timeout(API_TIMEOUT): + await self.pump.toggle() + await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index 9ceb668bc584db..d8738946c19949 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1817,7 +1817,7 @@ python-qbittorrent==0.4.2 python-ripple-api==0.0.3 # homeassistant.components.smarttub -python-smarttub==0.0.6 +python-smarttub==0.0.12 # homeassistant.components.sochain python-sochain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d37ee25cbbd936..27f7475328e431 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -942,7 +942,7 @@ python-nest==4.1.0 python-openzwave-mqtt[mqtt-client]==1.4.0 # homeassistant.components.smarttub -python-smarttub==0.0.6 +python-smarttub==0.0.12 # homeassistant.components.songpal python-songpal==0.12 diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index 265afcfc24c5ce..fe1ca465f07cee 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -36,7 +36,7 @@ async def setup_component(hass): @pytest.fixture(name="spa") def mock_spa(): - """Mock a SmartTub.Spa.""" + """Mock a smarttub.Spa.""" mock_spa = create_autospec(smarttub.Spa, instance=True) mock_spa.id = "mockspa1" @@ -67,6 +67,27 @@ def mock_spa(): "blowoutCycle": "INACTIVE", "cleanupCycle": "INACTIVE", } + + mock_circulation_pump = create_autospec(smarttub.SpaPump, instance=True) + mock_circulation_pump.id = "CP" + mock_circulation_pump.spa = mock_spa + mock_circulation_pump.state = smarttub.SpaPump.PumpState.OFF + mock_circulation_pump.type = smarttub.SpaPump.PumpType.CIRCULATION + + mock_jet_off = create_autospec(smarttub.SpaPump, instance=True) + mock_jet_off.id = "P1" + mock_jet_off.spa = mock_spa + mock_jet_off.state = smarttub.SpaPump.PumpState.OFF + mock_jet_off.type = smarttub.SpaPump.PumpType.JET + + mock_jet_on = create_autospec(smarttub.SpaPump, instance=True) + mock_jet_on.id = "P2" + mock_jet_on.spa = mock_spa + mock_jet_on.state = smarttub.SpaPump.PumpState.HIGH + mock_jet_on.type = smarttub.SpaPump.PumpType.JET + + mock_spa.get_pumps.return_value = [mock_circulation_pump, mock_jet_off, mock_jet_on] + return mock_spa diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 8551ac3cccb21e..8e0cbf64abc7b5 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -3,7 +3,7 @@ from . import trigger_update -async def test_sensors(spa, setup_entry, hass, smarttub_api): +async def test_sensors(spa, setup_entry, hass): """Test the sensors.""" entity_id = f"sensor.{spa.brand}_{spa.model}_state" diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py new file mode 100644 index 00000000000000..8750bf79747fe3 --- /dev/null +++ b/tests/components/smarttub/test_switch.py @@ -0,0 +1,38 @@ +"""Test the SmartTub switch platform.""" + +from smarttub import SpaPump + + +async def test_pumps(spa, setup_entry, hass): + """Test pump entities.""" + + for pump in spa.get_pumps.return_value: + if pump.type == SpaPump.PumpType.CIRCULATION: + entity_id = f"switch.{spa.brand}_{spa.model}_circulation_pump" + elif pump.type == SpaPump.PumpType.JET: + entity_id = f"switch.{spa.brand}_{spa.model}_jet_{pump.id.lower()}" + else: + raise NotImplementedError("Unknown pump type") + + state = hass.states.get(entity_id) + assert state is not None + if pump.state == SpaPump.PumpState.OFF: + assert state.state == "off" + + await hass.services.async_call( + "switch", + "turn_on", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() + else: + assert state.state == "on" + + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From b6b1e725c75e641d80d20dd6f02f34e8a7906e06 Mon Sep 17 00:00:00 2001 From: Jonathan Keslin Date: Sun, 21 Feb 2021 21:16:13 -0800 Subject: [PATCH 0664/1818] Add support for VeSync dimmer switches (#44713) Co-authored-by: J. Nick Koston --- .coveragerc | 1 + homeassistant/components/vesync/__init__.py | 20 ++++- homeassistant/components/vesync/common.py | 7 +- homeassistant/components/vesync/const.py | 1 + homeassistant/components/vesync/light.py | 85 +++++++++++++++++++++ 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/vesync/light.py diff --git a/.coveragerc b/.coveragerc index 2b71ba546cc75d..d66f4032f74755 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1042,6 +1042,7 @@ omit = homeassistant/components/vesync/common.py homeassistant/components/vesync/const.py homeassistant/components/vesync/fan.py + homeassistant/components/vesync/light.py homeassistant/components/vesync/switch.py homeassistant/components/viaggiatreno/sensor.py homeassistant/components/vicare/* diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py index 94a0d5c2f25635..24bd0f000df338 100644 --- a/homeassistant/components/vesync/__init__.py +++ b/homeassistant/components/vesync/__init__.py @@ -18,11 +18,12 @@ VS_DISCOVERY, VS_DISPATCHERS, VS_FANS, + VS_LIGHTS, VS_MANAGER, VS_SWITCHES, ) -PLATFORMS = ["switch", "fan"] +PLATFORMS = ["switch", "fan", "light"] _LOGGER = logging.getLogger(__name__) @@ -85,6 +86,7 @@ async def async_setup_entry(hass, config_entry): switches = hass.data[DOMAIN][VS_SWITCHES] = [] fans = hass.data[DOMAIN][VS_FANS] = [] + lights = hass.data[DOMAIN][VS_LIGHTS] = [] hass.data[DOMAIN][VS_DISPATCHERS] = [] @@ -96,15 +98,21 @@ async def async_setup_entry(hass, config_entry): fans.extend(device_dict[VS_FANS]) hass.async_create_task(forward_setup(config_entry, "fan")) + if device_dict[VS_LIGHTS]: + lights.extend(device_dict[VS_LIGHTS]) + hass.async_create_task(forward_setup(config_entry, "light")) + async def async_new_device_discovery(service): """Discover if new devices should be added.""" manager = hass.data[DOMAIN][VS_MANAGER] switches = hass.data[DOMAIN][VS_SWITCHES] fans = hass.data[DOMAIN][VS_FANS] + lights = hass.data[DOMAIN][VS_LIGHTS] dev_dict = await async_process_devices(hass, manager) switch_devs = dev_dict.get(VS_SWITCHES, []) fan_devs = dev_dict.get(VS_FANS, []) + light_devs = dev_dict.get(VS_LIGHTS, []) switch_set = set(switch_devs) new_switches = list(switch_set.difference(switches)) @@ -126,6 +134,16 @@ async def async_new_device_discovery(service): fans.extend(new_fans) hass.async_create_task(forward_setup(config_entry, "fan")) + light_set = set(light_devs) + new_lights = list(light_set.difference(lights)) + if new_lights and lights: + lights.extend(new_lights) + async_dispatcher_send(hass, VS_DISCOVERY.format(VS_LIGHTS), new_lights) + return + if new_lights and not lights: + lights.extend(new_lights) + hass.async_create_task(forward_setup(config_entry, "light")) + hass.services.async_register( DOMAIN, SERVICE_UPDATE_DEVS, async_new_device_discovery ) diff --git a/homeassistant/components/vesync/common.py b/homeassistant/components/vesync/common.py index 42e3516f085c85..240a5e48287372 100644 --- a/homeassistant/components/vesync/common.py +++ b/homeassistant/components/vesync/common.py @@ -3,7 +3,7 @@ from homeassistant.helpers.entity import ToggleEntity -from .const import VS_FANS, VS_SWITCHES +from .const import VS_FANS, VS_LIGHTS, VS_SWITCHES _LOGGER = logging.getLogger(__name__) @@ -13,6 +13,7 @@ async def async_process_devices(hass, manager): devices = {} devices[VS_SWITCHES] = [] devices[VS_FANS] = [] + devices[VS_LIGHTS] = [] await hass.async_add_executor_job(manager.update) @@ -28,7 +29,9 @@ async def async_process_devices(hass, manager): for switch in manager.switches: if not switch.is_dimmable(): devices[VS_SWITCHES].append(switch) - _LOGGER.info("%d VeSync standard switches found", len(manager.switches)) + else: + devices[VS_LIGHTS].append(switch) + _LOGGER.info("%d VeSync switches found", len(manager.switches)) return devices diff --git a/homeassistant/components/vesync/const.py b/homeassistant/components/vesync/const.py index 9923ab94ecffc6..5d9dfc8aa5d1c5 100644 --- a/homeassistant/components/vesync/const.py +++ b/homeassistant/components/vesync/const.py @@ -7,4 +7,5 @@ VS_SWITCHES = "switches" VS_FANS = "fans" +VS_LIGHTS = "lights" VS_MANAGER = "manager" diff --git a/homeassistant/components/vesync/light.py b/homeassistant/components/vesync/light.py new file mode 100644 index 00000000000000..53dfdc5f0a96c5 --- /dev/null +++ b/homeassistant/components/vesync/light.py @@ -0,0 +1,85 @@ +"""Support for VeSync dimmers.""" +import logging + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .common import VeSyncDevice +from .const import DOMAIN, VS_DISCOVERY, VS_DISPATCHERS, VS_LIGHTS + +_LOGGER = logging.getLogger(__name__) + +DEV_TYPE_TO_HA = { + "ESD16": "light", + "ESWD16": "light", +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up lights.""" + + async def async_discover(devices): + """Add new devices to platform.""" + _async_setup_entities(devices, async_add_entities) + + disp = async_dispatcher_connect( + hass, VS_DISCOVERY.format(VS_LIGHTS), async_discover + ) + hass.data[DOMAIN][VS_DISPATCHERS].append(disp) + + _async_setup_entities(hass.data[DOMAIN][VS_LIGHTS], async_add_entities) + return True + + +@callback +def _async_setup_entities(devices, async_add_entities): + """Check if device is online and add entity.""" + dev_list = [] + for dev in devices: + if DEV_TYPE_TO_HA.get(dev.device_type) == "light": + dev_list.append(VeSyncDimmerHA(dev)) + else: + _LOGGER.debug( + "%s - Unknown device type - %s", dev.device_name, dev.device_type + ) + continue + + async_add_entities(dev_list, update_before_add=True) + + +class VeSyncDimmerHA(VeSyncDevice, LightEntity): + """Representation of a VeSync dimmer.""" + + def __init__(self, dimmer): + """Initialize the VeSync dimmer device.""" + super().__init__(dimmer) + self.dimmer = dimmer + + def turn_on(self, **kwargs): + """Turn the device on.""" + if ATTR_BRIGHTNESS in kwargs: + # get brightness from HA data + brightness = int(kwargs[ATTR_BRIGHTNESS]) + # convert to percent that vesync api expects + brightness = round((brightness / 255) * 100) + # clamp to 1-100 + brightness = max(1, min(brightness, 100)) + self.dimmer.set_brightness(brightness) + # Avoid turning device back on if this is just a brightness adjustment + if not self.is_on: + self.device.turn_on() + + @property + def supported_features(self): + """Get supported features for this entity.""" + return SUPPORT_BRIGHTNESS + + @property + def brightness(self): + """Get dimmer brightness.""" + return round((int(self.dimmer.brightness) / 100) * 255) From b1a24c8bbbd93e28a5beb33da1784516166158e2 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 22 Feb 2021 06:34:45 +0100 Subject: [PATCH 0665/1818] Log the name of automations with condition errors (#46854) --- homeassistant/components/automation/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7e07f35be45dbc..7f006d929b15aa 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -568,7 +568,7 @@ async def _async_process_config( ) if CONF_CONDITION in config_block: - cond_func = await _async_process_if(hass, config, config_block) + cond_func = await _async_process_if(hass, name, config, config_block) if cond_func is None: continue @@ -606,7 +606,7 @@ async def _async_process_config( return blueprints_used -async def _async_process_if(hass, config, p_config): +async def _async_process_if(hass, name, config, p_config): """Process if checks.""" if_configs = p_config[CONF_CONDITION] @@ -634,7 +634,8 @@ def if_action(variables=None): if errors: LOGGER.warning( - "Error evaluating condition:\n%s", + "Error evaluating condition in '%s':\n%s", + name, ConditionErrorContainer("condition", errors=errors), ) return False From 5d8390fd9b6a33441f99104cda60d6b2efbb1427 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 21 Feb 2021 21:36:50 -0800 Subject: [PATCH 0666/1818] Add support for SmartTub filtration cycles (#46868) --- homeassistant/components/smarttub/climate.py | 16 +++-- homeassistant/components/smarttub/entity.py | 18 ++--- .../components/smarttub/manifest.json | 2 +- homeassistant/components/smarttub/sensor.py | 72 +++++++++++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/smarttub/conftest.py | 52 +++++++------- tests/components/smarttub/test_climate.py | 6 +- tests/components/smarttub/test_sensor.py | 18 ++++- 9 files changed, 129 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index ee6afc80fb1fdc..66c03a22e1f77c 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -1,6 +1,8 @@ """Platform for climate integration.""" import logging +from smarttub import Spa + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, @@ -38,9 +40,9 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): """The target water temperature for the spa.""" PRESET_MODES = { - "AUTO": PRESET_NONE, - "ECO": PRESET_ECO, - "DAY": PRESET_DAY, + Spa.HeatMode.AUTO: PRESET_NONE, + Spa.HeatMode.ECONOMY: PRESET_ECO, + Spa.HeatMode.DAY: PRESET_DAY, } HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} @@ -62,7 +64,7 @@ def temperature_unit(self): @property def hvac_action(self): """Return the current running hvac operation.""" - return self.HVAC_ACTIONS.get(self.get_spa_status("heater")) + return self.HVAC_ACTIONS.get(self.spa_status.heater) @property def hvac_modes(self): @@ -110,7 +112,7 @@ def supported_features(self): @property def preset_mode(self): """Return the current preset mode.""" - return self.PRESET_MODES[self.get_spa_status("heatMode")] + return self.PRESET_MODES[self.spa_status.heat_mode] @property def preset_modes(self): @@ -120,12 +122,12 @@ def preset_modes(self): @property def current_temperature(self): """Return the current water temperature.""" - return self.get_spa_status("water.temperature") + return self.spa_status.water.temperature @property def target_temperature(self): """Return the target water temperature.""" - return self.get_spa_status("setTemperature") + return self.spa_status.set_temperature async def async_set_temperature(self, **kwargs): """Set new target temperature.""" diff --git a/homeassistant/components/smarttub/entity.py b/homeassistant/components/smarttub/entity.py index 0e84c92e3e16e9..eab60b4162c263 100644 --- a/homeassistant/components/smarttub/entity.py +++ b/homeassistant/components/smarttub/entity.py @@ -52,18 +52,8 @@ def name(self) -> str: spa_name = get_spa_name(self.spa) return f"{spa_name} {self._entity_type}" - def get_spa_status(self, path): - """Retrieve a value from the data returned by Spa.get_status(). - - Nested keys can be specified by a dotted path, e.g. - status['foo']['bar'] is 'foo.bar'. - """ - - status = self.coordinator.data[self.spa.id].get("status") - if status is None: - return None - - for key in path.split("."): - status = status[key] + @property + def spa_status(self) -> smarttub.SpaState: + """Retrieve the result of Spa.get_status().""" - return status + return self.coordinator.data[self.spa.id].get("status") diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json index 9360c59da8bacc..292ce81b4fb431 100644 --- a/homeassistant/components/smarttub/manifest.json +++ b/homeassistant/components/smarttub/manifest.json @@ -6,7 +6,7 @@ "dependencies": [], "codeowners": ["@mdz"], "requirements": [ - "python-smarttub==0.0.12" + "python-smarttub==0.0.17" ], "quality_scale": "platinum" } diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 402fb87373f7fa..54921596fb2174 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" +from enum import Enum import logging from .const import DOMAIN, SMARTTUB_CONTROLLER @@ -6,6 +7,11 @@ _LOGGER = logging.getLogger(__name__) +ATTR_DURATION = "duration" +ATTR_LAST_UPDATED = "last_updated" +ATTR_MODE = "mode" +ATTR_START_HOUR = "start_hour" + async def async_setup_entry(hass, entry, async_add_entities): """Set up sensor entities for the sensors in the tub.""" @@ -18,15 +24,17 @@ async def async_setup_entry(hass, entry, async_add_entities): [ SmartTubSensor(controller.coordinator, spa, "State", "state"), SmartTubSensor( - controller.coordinator, spa, "Flow Switch", "flowSwitch" + controller.coordinator, spa, "Flow Switch", "flow_switch" ), SmartTubSensor(controller.coordinator, spa, "Ozone", "ozone"), SmartTubSensor( - controller.coordinator, spa, "Blowout Cycle", "blowoutCycle" + controller.coordinator, spa, "Blowout Cycle", "blowout_cycle" ), SmartTubSensor( - controller.coordinator, spa, "Cleanup Cycle", "cleanupCycle" + controller.coordinator, spa, "Cleanup Cycle", "cleanup_cycle" ), + SmartTubPrimaryFiltrationCycle(controller.coordinator, spa), + SmartTubSecondaryFiltrationCycle(controller.coordinator, spa), ] ) @@ -36,17 +44,69 @@ async def async_setup_entry(hass, entry, async_add_entities): class SmartTubSensor(SmartTubEntity): """Generic and base class for SmartTub sensors.""" - def __init__(self, coordinator, spa, sensor_name, spa_status_key): + def __init__(self, coordinator, spa, sensor_name, attr_name): """Initialize the entity.""" super().__init__(coordinator, spa, sensor_name) - self._spa_status_key = spa_status_key + self._attr_name = attr_name @property def _state(self): """Retrieve the underlying state from the spa.""" - return self.get_spa_status(self._spa_status_key) + return getattr(self.spa_status, self._attr_name) @property def state(self) -> str: """Return the current state of the sensor.""" + if isinstance(self._state, Enum): + return self._state.name.lower() return self._state.lower() + + +class SmartTubPrimaryFiltrationCycle(SmartTubSensor): + """The primary filtration cycle.""" + + def __init__(self, coordinator, spa): + """Initialize the entity.""" + super().__init__( + coordinator, spa, "primary filtration cycle", "primary_filtration" + ) + + @property + def state(self) -> str: + """Return the current state of the sensor.""" + return self._state.status.name.lower() + + @property + def device_state_attributes(self): + """Return the state attributes.""" + state = self._state + return { + ATTR_DURATION: state.duration, + ATTR_LAST_UPDATED: state.last_updated.isoformat(), + ATTR_MODE: state.mode.name.lower(), + ATTR_START_HOUR: state.start_hour, + } + + +class SmartTubSecondaryFiltrationCycle(SmartTubSensor): + """The secondary filtration cycle.""" + + def __init__(self, coordinator, spa): + """Initialize the entity.""" + super().__init__( + coordinator, spa, "Secondary Filtration Cycle", "secondary_filtration" + ) + + @property + def state(self) -> str: + """Return the current state of the sensor.""" + return self._state.status.name.lower() + + @property + def device_state_attributes(self): + """Return the state attributes.""" + state = self._state + return { + ATTR_LAST_UPDATED: state.last_updated.isoformat(), + ATTR_MODE: state.mode.name.lower(), + } diff --git a/requirements_all.txt b/requirements_all.txt index d8738946c19949..0d8203279b5d77 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1817,7 +1817,7 @@ python-qbittorrent==0.4.2 python-ripple-api==0.0.3 # homeassistant.components.smarttub -python-smarttub==0.0.12 +python-smarttub==0.0.17 # homeassistant.components.sochain python-sochain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27f7475328e431..99a0351ee371ea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -942,7 +942,7 @@ python-nest==4.1.0 python-openzwave-mqtt[mqtt-client]==1.4.0 # homeassistant.components.smarttub -python-smarttub==0.0.12 +python-smarttub==0.0.17 # homeassistant.components.songpal python-songpal==0.12 diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index fe1ca465f07cee..ad962ba0474b41 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -42,32 +42,34 @@ def mock_spa(): mock_spa.id = "mockspa1" mock_spa.brand = "mockbrand1" mock_spa.model = "mockmodel1" - mock_spa.get_status.return_value = { - "setTemperature": 39, - "water": {"temperature": 38}, - "heater": "ON", - "heatMode": "AUTO", - "state": "NORMAL", - "primaryFiltration": { - "cycle": 1, - "duration": 4, - "lastUpdated": "2021-01-20T11:38:57.014Z", - "mode": "NORMAL", - "startHour": 2, - "status": "INACTIVE", + mock_spa.get_status.return_value = smarttub.SpaState( + mock_spa, + **{ + "setTemperature": 39, + "water": {"temperature": 38}, + "heater": "ON", + "heatMode": "AUTO", + "state": "NORMAL", + "primaryFiltration": { + "cycle": 1, + "duration": 4, + "lastUpdated": "2021-01-20T11:38:57.014Z", + "mode": "NORMAL", + "startHour": 2, + "status": "INACTIVE", + }, + "secondaryFiltration": { + "lastUpdated": "2020-07-09T19:39:52.961Z", + "mode": "AWAY", + "status": "INACTIVE", + }, + "flowSwitch": "OPEN", + "ozone": "OFF", + "uv": "OFF", + "blowoutCycle": "INACTIVE", + "cleanupCycle": "INACTIVE", }, - "secondaryFiltration": { - "lastUpdated": "2020-07-09T19:39:52.961Z", - "mode": "AWAY", - "status": "INACTIVE", - }, - "flowSwitch": "OPEN", - "ozone": "OFF", - "uv": "OFF", - "blowoutCycle": "INACTIVE", - "cleanupCycle": "INACTIVE", - } - + ) mock_circulation_pump = create_autospec(smarttub.SpaPump, instance=True) mock_circulation_pump.id = "CP" mock_circulation_pump.spa = mock_spa diff --git a/tests/components/smarttub/test_climate.py b/tests/components/smarttub/test_climate.py index 118264183e8279..a034a4ce17edef 100644 --- a/tests/components/smarttub/test_climate.py +++ b/tests/components/smarttub/test_climate.py @@ -42,7 +42,7 @@ async def test_thermostat_update(spa, setup_entry, hass): assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT - spa.get_status.return_value["heater"] = "OFF" + spa.get_status.return_value.heater = "OFF" await trigger_update(hass) state = hass.states.get(entity_id) @@ -83,9 +83,9 @@ async def test_thermostat_update(spa, setup_entry, hass): {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO}, blocking=True, ) - spa.set_heat_mode.assert_called_with("ECO") + spa.set_heat_mode.assert_called_with(smarttub.Spa.HeatMode.ECONOMY) - spa.get_status.return_value["heatMode"] = "ECO" + spa.get_status.return_value.heat_mode = smarttub.Spa.HeatMode.ECONOMY await trigger_update(hass) state = hass.states.get(entity_id) assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 8e0cbf64abc7b5..7ef3062894a654 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -11,7 +11,7 @@ async def test_sensors(spa, setup_entry, hass): assert state is not None assert state.state == "normal" - spa.get_status.return_value["state"] = "BAD" + spa.get_status.return_value.state = "BAD" await trigger_update(hass) state = hass.states.get(entity_id) assert state is not None @@ -36,3 +36,19 @@ async def test_sensors(spa, setup_entry, hass): state = hass.states.get(entity_id) assert state is not None assert state.state == "inactive" + + entity_id = f"sensor.{spa.brand}_{spa.model}_primary_filtration_cycle" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "inactive" + assert state.attributes["duration"] == 4 + assert state.attributes["last_updated"] is not None + assert state.attributes["mode"] == "normal" + assert state.attributes["start_hour"] == 2 + + entity_id = f"sensor.{spa.brand}_{spa.model}_secondary_filtration_cycle" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "inactive" + assert state.attributes["last_updated"] is not None + assert state.attributes["mode"] == "away" From 1a27af43cc48ece781dc1be9a0abf9e92d0c0782 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 22 Feb 2021 06:38:17 +0100 Subject: [PATCH 0667/1818] Add KNX service exposure_register (#45257) --- homeassistant/components/knx/__init__.py | 192 +++++++-------------- homeassistant/components/knx/expose.py | 152 ++++++++++++++++ homeassistant/components/knx/services.yaml | 66 ++++++- 3 files changed, 282 insertions(+), 128 deletions(-) create mode 100644 homeassistant/components/knx/expose.py diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index e4280e6bddc1e9..8716f03838cd91 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -5,7 +5,6 @@ import voluptuous as vol from xknx import XKNX from xknx.core.telegram_queue import TelegramQueue -from xknx.devices import DateTime, ExposeSensor from xknx.dpt import DPTArray, DPTBase, DPTBinary from xknx.exceptions import XKNXException from xknx.io import ( @@ -18,26 +17,21 @@ from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite from homeassistant.const import ( - CONF_ENTITY_ID, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, SERVICE_RELOAD, - STATE_OFF, - STATE_ON, - STATE_UNAVAILABLE, - STATE_UNKNOWN, ) -from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import async_get_platforms -from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ServiceCallType from .const import DOMAIN, SupportedPlatforms +from .expose import create_knx_exposure from .factory import create_knx_device from .schema import ( BinarySensorSchema, @@ -75,6 +69,7 @@ SERVICE_KNX_ATTR_TYPE = "type" SERVICE_KNX_ATTR_REMOVE = "remove" SERVICE_KNX_EVENT_REGISTER = "event_register" +SERVICE_KNX_EXPOSURE_REGISTER = "exposure_register" SERVICE_KNX_READ = "read" CONFIG_SCHEMA = vol.Schema( @@ -183,12 +178,27 @@ } ) +SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( + ExposeSchema.SCHEMA.extend( + { + vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, + } + ), + vol.Schema( + # for removing only `address` is required + { + vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, + vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True), + }, + extra=vol.ALLOW_EXTRA, + ), +) + async def async_setup(hass, config): """Set up the KNX component.""" try: hass.data[DOMAIN] = KNXModule(hass, config) - hass.data[DOMAIN].async_create_exposures() await hass.data[DOMAIN].start() except XKNXException as ex: _LOGGER.warning("Could not connect to KNX interface: %s", ex) @@ -196,6 +206,12 @@ async def async_setup(hass, config): f"Could not connect to KNX interface:
{ex}", title="KNX" ) + if CONF_KNX_EXPOSE in config[DOMAIN]: + for expose_config in config[DOMAIN][CONF_KNX_EXPOSE]: + hass.data[DOMAIN].exposures.append( + create_knx_exposure(hass, hass.data[DOMAIN].xknx, expose_config) + ) + for platform in SupportedPlatforms: if platform.value in config[DOMAIN]: for device_config in config[DOMAIN][platform.value]: @@ -235,6 +251,14 @@ async def async_setup(hass, config): schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA, ) + async_register_admin_service( + hass, + DOMAIN, + SERVICE_KNX_EXPOSURE_REGISTER, + hass.data[DOMAIN].service_exposure_register_modify, + schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, + ) + async def reload_service_handler(service_call: ServiceCallType) -> None: """Remove all KNX components and load new ones from config.""" @@ -269,6 +293,7 @@ def __init__(self, hass, config): self.config = config self.connected = False self.exposures = [] + self.service_exposures = {} self.init_xknx() self._knx_event_callback: TelegramQueue.Callback = self.register_callback() @@ -340,34 +365,6 @@ def connection_config_tunneling(self): auto_reconnect=True, ) - @callback - def async_create_exposures(self): - """Create exposures.""" - if CONF_KNX_EXPOSE not in self.config[DOMAIN]: - return - for to_expose in self.config[DOMAIN][CONF_KNX_EXPOSE]: - expose_type = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) - entity_id = to_expose.get(CONF_ENTITY_ID) - attribute = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) - default = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) - address = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS) - if expose_type.lower() in ["time", "date", "datetime"]: - exposure = KNXExposeTime(self.xknx, expose_type, address) - exposure.async_register() - self.exposures.append(exposure) - else: - exposure = KNXExposeSensor( - self.hass, - self.xknx, - expose_type, - entity_id, - attribute, - default, - address, - ) - exposure.async_register() - self.exposures.append(exposure) - async def telegram_received_cb(self, telegram): """Call invoked after a KNX telegram was received.""" data = None @@ -417,6 +414,37 @@ async def service_event_register_modify(self, call): group_address, ) + async def service_exposure_register_modify(self, call): + """Service for adding or removing an exposure to KNX bus.""" + group_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS) + + if call.data.get(SERVICE_KNX_ATTR_REMOVE): + try: + removed_exposure = self.service_exposures.pop(group_address) + except KeyError as err: + raise HomeAssistantError( + f"Could not find exposure for '{group_address}' to remove." + ) from err + else: + removed_exposure.shutdown() + return + + if group_address in self.service_exposures: + replaced_exposure = self.service_exposures.pop(group_address) + _LOGGER.warning( + "Service exposure_register replacing already registered exposure for '%s' - %s", + group_address, + replaced_exposure.device.name, + ) + replaced_exposure.shutdown() + 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", + group_address, + exposure.device.name, + ) + async def service_send_to_knx_bus(self, call): """Service for sending an arbitrary KNX message to the KNX bus.""" attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) @@ -448,93 +476,3 @@ async def service_read_to_knx_bus(self, call): payload=GroupValueRead(), ) await self.xknx.telegrams.put(telegram) - - -class KNXExposeTime: - """Object to Expose Time/Date object to KNX bus.""" - - def __init__(self, xknx: XKNX, expose_type: str, address: str): - """Initialize of Expose class.""" - self.xknx = xknx - self.expose_type = expose_type - self.address = address - self.device = None - - @callback - def async_register(self): - """Register listener.""" - self.device = DateTime( - self.xknx, - name=self.expose_type.capitalize(), - broadcast_type=self.expose_type.upper(), - localtime=True, - group_address=self.address, - ) - - -class KNXExposeSensor: - """Object to Expose Home Assistant entity to KNX bus.""" - - def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address): - """Initialize of Expose class.""" - self.hass = hass - self.xknx = xknx - self.type = expose_type - self.entity_id = entity_id - self.expose_attribute = attribute - self.expose_default = default - self.address = address - self.device = None - - @callback - def async_register(self): - """Register listener.""" - if self.expose_attribute is not None: - _name = self.entity_id + "__" + self.expose_attribute - else: - _name = self.entity_id - self.device = ExposeSensor( - self.xknx, - name=_name, - group_address=self.address, - value_type=self.type, - ) - async_track_state_change_event( - self.hass, [self.entity_id], self._async_entity_changed - ) - - async def _async_entity_changed(self, event): - """Handle entity change.""" - new_state = event.data.get("new_state") - if new_state is None: - return - if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): - return - - if self.expose_attribute is not None: - new_attribute = new_state.attributes.get(self.expose_attribute) - old_state = event.data.get("old_state") - - if old_state is not None: - old_attribute = old_state.attributes.get(self.expose_attribute) - if old_attribute == new_attribute: - # don't send same value sequentially - return - await self._async_set_knx_value(new_attribute) - else: - await self._async_set_knx_value(new_state.state) - - async def _async_set_knx_value(self, value): - """Set new value on xknx ExposeSensor.""" - if value is None: - if self.expose_default is None: - return - value = self.expose_default - - if self.type == "binary": - if value == STATE_ON: - value = True - elif value == STATE_OFF: - value = False - - await self.device.set(value) diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py new file mode 100644 index 00000000000000..93abd7d7b431ca --- /dev/null +++ b/homeassistant/components/knx/expose.py @@ -0,0 +1,152 @@ +"""Exposures to KNX bus.""" +from typing import Union + +from xknx import XKNX +from xknx.devices import DateTime, ExposeSensor + +from homeassistant.const import ( + CONF_ENTITY_ID, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.helpers.typing import ConfigType + +from .schema import ExposeSchema + + +@callback +def create_knx_exposure( + hass: HomeAssistant, xknx: XKNX, config: ConfigType +) -> Union["KNXExposeSensor", "KNXExposeTime"]: + """Create exposures from config.""" + expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) + entity_id = config.get(CONF_ENTITY_ID) + attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) + default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) + address = config.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS) + + exposure: Union["KNXExposeSensor", "KNXExposeTime"] + if expose_type.lower() in ["time", "date", "datetime"]: + exposure = KNXExposeTime(xknx, expose_type, address) + else: + exposure = KNXExposeSensor( + hass, + xknx, + expose_type, + entity_id, + attribute, + default, + address, + ) + exposure.async_register() + return exposure + + +class KNXExposeSensor: + """Object to Expose Home Assistant entity to KNX bus.""" + + def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address): + """Initialize of Expose class.""" + self.hass = hass + self.xknx = xknx + self.type = expose_type + self.entity_id = entity_id + self.expose_attribute = attribute + self.expose_default = default + self.address = address + self.device = None + self._remove_listener = None + + @callback + def async_register(self): + """Register listener.""" + if self.expose_attribute is not None: + _name = self.entity_id + "__" + self.expose_attribute + else: + _name = self.entity_id + self.device = ExposeSensor( + self.xknx, + name=_name, + group_address=self.address, + value_type=self.type, + ) + self._remove_listener = async_track_state_change_event( + self.hass, [self.entity_id], self._async_entity_changed + ) + + @callback + def shutdown(self) -> None: + """Prepare for deletion.""" + if self._remove_listener is not None: + self._remove_listener() + if self.device is not None: + self.device.shutdown() + + async def _async_entity_changed(self, event): + """Handle entity change.""" + new_state = event.data.get("new_state") + if new_state is None: + return + if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): + return + + if self.expose_attribute is None: + await self._async_set_knx_value(new_state.state) + return + + new_attribute = new_state.attributes.get(self.expose_attribute) + old_state = event.data.get("old_state") + + if old_state is not None: + old_attribute = old_state.attributes.get(self.expose_attribute) + if old_attribute == new_attribute: + # don't send same value sequentially + return + await self._async_set_knx_value(new_attribute) + + async def _async_set_knx_value(self, value): + """Set new value on xknx ExposeSensor.""" + if value is None: + if self.expose_default is None: + return + value = self.expose_default + + if self.type == "binary": + if value == STATE_ON: + value = True + elif value == STATE_OFF: + value = False + + await self.device.set(value) + + +class KNXExposeTime: + """Object to Expose Time/Date object to KNX bus.""" + + def __init__(self, xknx: XKNX, expose_type: str, address: str): + """Initialize of Expose class.""" + self.xknx = xknx + self.expose_type = expose_type + self.address = address + self.device = None + + @callback + def async_register(self): + """Register listener.""" + self.device = DateTime( + self.xknx, + name=self.expose_type.capitalize(), + broadcast_type=self.expose_type.upper(), + localtime=True, + group_address=self.address, + ) + + @callback + def shutdown(self): + """Prepare for deletion.""" + if self.device is not None: + self.device.shutdown() diff --git a/homeassistant/components/knx/services.yaml b/homeassistant/components/knx/services.yaml index aa946459cfd261..ef74acc49b14cf 100644 --- a/homeassistant/components/knx/services.yaml +++ b/homeassistant/components/knx/services.yaml @@ -2,25 +2,89 @@ send: description: "Send arbitrary data directly to the KNX bus." fields: address: + name: "Group address" description: "Group address(es) to write to." + required: true example: "1/1/0" + selector: + text: payload: + name: "Payload" description: "Payload to send to the bus. Integers are treated as DPT 1/2/3 payloads. For DPTs > 6 bits send a list. Each value represents 1 octet (0-255). Pad with 0 to DPT byte length." + required: true example: "[0, 4]" type: + name: "Value type" description: "Optional. If set, the payload will not be sent as raw bytes, but encoded as given DPT. Knx sensor types are valid values (see https://www.home-assistant.io/integrations/sensor.knx)." + required: false example: "temperature" + selector: + text: read: description: "Send GroupValueRead requests to the KNX bus. Response can be used from `knx_event` and will be processed in KNX entities." fields: address: + name: "Group address" description: "Group address(es) to send read request to. Lists will read multiple group addresses." + required: true example: "1/1/0" + selector: + text: event_register: description: "Add or remove single group address to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." fields: address: + name: "Group address" description: "Group address that shall be added or removed." + required: true example: "1/1/0" remove: - description: "Optional. If `True` the group address will be removed. Defaults to `False`." + name: "Remove event registration" + description: "Optional. If `True` the group address will be removed." + required: false + default: false + selector: + boolean: +exposure_register: + description: "Add or remove exposures to KNX bus. Only exposures added with this service can be removed." + fields: + address: + name: "Group address" + description: "Group address state or attribute updates will be sent to. GroupValueRead requests will be answered. Per address only one exposure can be registered." + required: true + example: "1/1/0" + selector: + text: + type: + name: "Value type" + description: "Telegrams will be encoded as given DPT. 'binary' and all Knx sensor types are valid values (see https://www.home-assistant.io/integrations/sensor.knx)" + required: true + example: "percentU8" + selector: + text: + entity_id: + name: "Entity" + description: "Entity id whose state or attribute shall be exposed." + required: true + example: "light.kitchen" + selector: + entity: + attribute: + name: "Entity attribute" + description: "Optional. Attribute of the entity that shall be sent to the KNX bus. If not set the state will be sent. Eg. for a light the state is eigther “on” or “off” - with attribute you can expose its “brightness”." + required: false + example: "brightness" + default: + name: "Default value" + description: "Optional. Default value to send to the bus if the state or attribute value is None. Eg. a light with state “off” has no brightness attribute so a default value of 0 could be used. If not set (or None) no value would be sent to the bus and a GroupReadRequest to the address would return the last known value." + required: false + example: "0" + remove: + name: "Remove exposure" + description: "Optional. If `True` the exposure will be removed. Only `address` is required for removal." + required: false + default: false + selector: + boolean: +reload: + description: "Reload KNX configuration." From 5cd022a683bb3b081a17b14d483fa1cd355df2e7 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 21 Feb 2021 22:09:21 -0800 Subject: [PATCH 0668/1818] Add light platform to SmartTub (#46886) Co-authored-by: J. Nick Koston --- homeassistant/components/smarttub/__init__.py | 2 +- homeassistant/components/smarttub/const.py | 7 + .../components/smarttub/controller.py | 4 +- homeassistant/components/smarttub/light.py | 141 ++++++++++++++++++ tests/components/smarttub/conftest.py | 14 ++ tests/components/smarttub/test_light.py | 38 +++++ 6 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/smarttub/light.py create mode 100644 tests/components/smarttub/test_light.py diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py index e8fc9989d38529..8467c20807677f 100644 --- a/homeassistant/components/smarttub/__init__.py +++ b/homeassistant/components/smarttub/__init__.py @@ -7,7 +7,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["climate", "sensor", "switch"] +PLATFORMS = ["climate", "light", "sensor", "switch"] async def async_setup(hass, config): diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 8292c5ef82662d..0b97926cc4390a 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -13,3 +13,10 @@ DEFAULT_MIN_TEMP = 18.5 DEFAULT_MAX_TEMP = 40 + +# the device doesn't remember any state for the light, so we have to choose a +# mode (smarttub.SpaLight.LightMode) when turning it on. There is no white +# mode. +DEFAULT_LIGHT_EFFECT = "purple" +# default to 50% brightness +DEFAULT_LIGHT_BRIGHTNESS = 128 diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index feb13066b4442f..bf8de2f4e2e381 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -86,13 +86,15 @@ async def async_update_data(self): return data async def _get_spa_data(self, spa): - status, pumps = await asyncio.gather( + status, pumps, lights = await asyncio.gather( spa.get_status(), spa.get_pumps(), + spa.get_lights(), ) return { "status": status, "pumps": {pump.id: pump for pump in pumps}, + "lights": {light.zone: light for light in lights}, } async def async_register_devices(self, entry): diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py new file mode 100644 index 00000000000000..a4ada7c3024cdf --- /dev/null +++ b/homeassistant/components/smarttub/light.py @@ -0,0 +1,141 @@ +"""Platform for light integration.""" +import logging + +from smarttub import SpaLight + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_EFFECT, + EFFECT_COLORLOOP, + SUPPORT_BRIGHTNESS, + SUPPORT_EFFECT, + LightEntity, +) + +from .const import ( + DEFAULT_LIGHT_BRIGHTNESS, + DEFAULT_LIGHT_EFFECT, + DOMAIN, + SMARTTUB_CONTROLLER, +) +from .entity import SmartTubEntity +from .helpers import get_spa_name + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up entities for any lights in the tub.""" + + controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] + + entities = [ + SmartTubLight(controller.coordinator, light) + for spa in controller.spas + for light in await spa.get_lights() + ] + + async_add_entities(entities) + + +class SmartTubLight(SmartTubEntity, LightEntity): + """A light on a spa.""" + + def __init__(self, coordinator, light): + """Initialize the entity.""" + super().__init__(coordinator, light.spa, "light") + self.light_zone = light.zone + + @property + def light(self) -> SpaLight: + """Return the underlying SpaLight object for this entity.""" + return self.coordinator.data[self.spa.id]["lights"][self.light_zone] + + @property + def unique_id(self) -> str: + """Return a unique ID for this light entity.""" + return f"{super().unique_id}-{self.light_zone}" + + @property + def name(self) -> str: + """Return a name for this light entity.""" + spa_name = get_spa_name(self.spa) + return f"{spa_name} Light {self.light_zone}" + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + + # SmartTub intensity is 0..100 + return self._smarttub_to_hass_brightness(self.light.intensity) + + @staticmethod + def _smarttub_to_hass_brightness(intensity): + if intensity in (0, 1): + return 0 + return round(intensity * 255 / 100) + + @staticmethod + def _hass_to_smarttub_brightness(brightness): + return round(brightness * 100 / 255) + + @property + def is_on(self): + """Return true if the light is on.""" + return self.light.mode != SpaLight.LightMode.OFF + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_EFFECT + + @property + def effect(self): + """Return the current effect.""" + mode = self.light.mode.name.lower() + if mode in self.effect_list: + return mode + return None + + @property + def effect_list(self): + """Return the list of supported effects.""" + effects = [ + effect + for effect in map(self._light_mode_to_effect, SpaLight.LightMode) + if effect is not None + ] + + return effects + + @staticmethod + def _light_mode_to_effect(light_mode: SpaLight.LightMode): + if light_mode == SpaLight.LightMode.OFF: + return None + if light_mode == SpaLight.LightMode.HIGH_SPEED_COLOR_WHEEL: + return EFFECT_COLORLOOP + + return light_mode.name.lower() + + @staticmethod + def _effect_to_light_mode(effect): + if effect == EFFECT_COLORLOOP: + return SpaLight.LightMode.HIGH_SPEED_COLOR_WHEEL + + return SpaLight.LightMode[effect.upper()] + + async def async_turn_on(self, **kwargs): + """Turn the light on.""" + + mode = self._effect_to_light_mode(kwargs.get(ATTR_EFFECT, DEFAULT_LIGHT_EFFECT)) + intensity = self._hass_to_smarttub_brightness( + kwargs.get(ATTR_BRIGHTNESS, DEFAULT_LIGHT_BRIGHTNESS) + ) + + await self.light.set_mode(mode, intensity) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs): + """Turn the light off.""" + await self.light.set_mode(self.light.LightMode.OFF, 0) + await self.coordinator.async_request_refresh() diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index ad962ba0474b41..79e5d06d3b382d 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -90,6 +90,20 @@ def mock_spa(): mock_spa.get_pumps.return_value = [mock_circulation_pump, mock_jet_off, mock_jet_on] + mock_light_off = create_autospec(smarttub.SpaLight, instance=True) + mock_light_off.spa = mock_spa + mock_light_off.zone = 1 + mock_light_off.intensity = 0 + mock_light_off.mode = smarttub.SpaLight.LightMode.OFF + + mock_light_on = create_autospec(smarttub.SpaLight, instance=True) + mock_light_on.spa = mock_spa + mock_light_on.zone = 2 + mock_light_on.intensity = 50 + mock_light_on.mode = smarttub.SpaLight.LightMode.PURPLE + + mock_spa.get_lights.return_value = [mock_light_off, mock_light_on] + return mock_spa diff --git a/tests/components/smarttub/test_light.py b/tests/components/smarttub/test_light.py new file mode 100644 index 00000000000000..5e9d9459eabfbe --- /dev/null +++ b/tests/components/smarttub/test_light.py @@ -0,0 +1,38 @@ +"""Test the SmartTub light platform.""" + +from smarttub import SpaLight + + +async def test_light(spa, setup_entry, hass): + """Test light entity.""" + + for light in spa.get_lights.return_value: + entity_id = f"light.{spa.brand}_{spa.model}_light_{light.zone}" + state = hass.states.get(entity_id) + assert state is not None + if light.mode == SpaLight.LightMode.OFF: + assert state.state == "off" + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": entity_id}, + blocking=True, + ) + light.set_mode.assert_called() + + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": entity_id, "brightness": 255}, + blocking=True, + ) + light.set_mode.assert_called_with(SpaLight.LightMode.PURPLE, 100) + + else: + assert state.state == "on" + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": entity_id}, + blocking=True, + ) From 5c29adea3de3d74abc4166f45dc4cec904152135 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 22 Feb 2021 06:12:50 +0000 Subject: [PATCH 0669/1818] Add KMTronic Integration (#41682) Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + homeassistant/components/kmtronic/__init__.py | 104 ++++++++++++ .../components/kmtronic/config_flow.py | 74 +++++++++ homeassistant/components/kmtronic/const.py | 16 ++ .../components/kmtronic/manifest.json | 8 + .../components/kmtronic/strings.json | 21 +++ homeassistant/components/kmtronic/switch.py | 67 ++++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/kmtronic/__init__.py | 1 + tests/components/kmtronic/test_config_flow.py | 145 +++++++++++++++++ tests/components/kmtronic/test_switch.py | 150 ++++++++++++++++++ 13 files changed, 594 insertions(+) create mode 100644 homeassistant/components/kmtronic/__init__.py create mode 100644 homeassistant/components/kmtronic/config_flow.py create mode 100644 homeassistant/components/kmtronic/const.py create mode 100644 homeassistant/components/kmtronic/manifest.json create mode 100644 homeassistant/components/kmtronic/strings.json create mode 100644 homeassistant/components/kmtronic/switch.py create mode 100644 tests/components/kmtronic/__init__.py create mode 100644 tests/components/kmtronic/test_config_flow.py create mode 100644 tests/components/kmtronic/test_switch.py diff --git a/CODEOWNERS b/CODEOWNERS index 788f36361434bc..a9d4ce63209bce 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -243,6 +243,7 @@ homeassistant/components/keba/* @dannerph homeassistant/components/keenetic_ndms2/* @foxel homeassistant/components/kef/* @basnijholt homeassistant/components/keyboard_remote/* @bendavid +homeassistant/components/kmtronic/* @dgomes homeassistant/components/knx/* @Julius2342 @farmio @marvin-w homeassistant/components/kodi/* @OnFreund @cgtobi homeassistant/components/konnected/* @heythisisnate @kit-klein diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py new file mode 100644 index 00000000000000..b55ab9e1c9c3d8 --- /dev/null +++ b/homeassistant/components/kmtronic/__init__.py @@ -0,0 +1,104 @@ +"""The kmtronic integration.""" +import asyncio +from datetime import timedelta +import logging + +import aiohttp +import async_timeout +from pykmtronic.auth import Auth +from pykmtronic.hub import KMTronicHubAPI +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady +from homeassistant.core import HomeAssistant +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import ( + CONF_HOSTNAME, + CONF_PASSWORD, + CONF_USERNAME, + DATA_COORDINATOR, + DATA_HOST, + DATA_HUB, + DOMAIN, + MANUFACTURER, +) + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) + +PLATFORMS = ["switch"] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the kmtronic component.""" + hass.data[DOMAIN] = {} + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up kmtronic from a config entry.""" + + session = aiohttp_client.async_get_clientsession(hass) + auth = Auth( + session, + f"http://{entry.data[CONF_HOSTNAME]}", + entry.data[CONF_USERNAME], + entry.data[CONF_PASSWORD], + ) + hub = KMTronicHubAPI(auth) + + async def async_update_data(): + try: + async with async_timeout.timeout(10): + await hub.async_update_relays() + except aiohttp.client_exceptions.ClientResponseError as err: + raise UpdateFailed(f"Wrong credentials: {err}") from err + except ( + asyncio.TimeoutError, + aiohttp.client_exceptions.ClientConnectorError, + ) as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{MANUFACTURER} {hub.name}", + update_method=async_update_data, + update_interval=timedelta(seconds=30), + ) + await coordinator.async_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][entry.entry_id] = { + DATA_HUB: hub, + DATA_HOST: entry.data[DATA_HOST], + DATA_COORDINATOR: coordinator, + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py new file mode 100644 index 00000000000000..376bb64c34c72e --- /dev/null +++ b/homeassistant/components/kmtronic/config_flow.py @@ -0,0 +1,74 @@ +"""Config flow for kmtronic integration.""" +import logging + +import aiohttp +from pykmtronic.auth import Auth +from pykmtronic.hub import KMTronicHubAPI +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.helpers import aiohttp_client + +from .const import CONF_HOSTNAME, CONF_PASSWORD, CONF_USERNAME +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({CONF_HOSTNAME: str, CONF_USERNAME: str, CONF_PASSWORD: str}) + + +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect.""" + + session = aiohttp_client.async_get_clientsession(hass) + auth = Auth( + session, + f"http://{data[CONF_HOSTNAME]}", + data[CONF_USERNAME], + data[CONF_PASSWORD], + ) + hub = KMTronicHubAPI(auth) + + try: + await hub.async_get_status() + except aiohttp.client_exceptions.ClientResponseError as err: + raise InvalidAuth from err + except aiohttp.client_exceptions.ClientConnectorError as err: + raise CannotConnect from err + + return data + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for kmtronic.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + 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) + + return self.async_create_entry(title=info["host"], data=user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + return self.async_show_form( + step_id="user", data_schema=DATA_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/kmtronic/const.py b/homeassistant/components/kmtronic/const.py new file mode 100644 index 00000000000000..58553217799bb1 --- /dev/null +++ b/homeassistant/components/kmtronic/const.py @@ -0,0 +1,16 @@ +"""Constants for the kmtronic integration.""" + +DOMAIN = "kmtronic" + +CONF_HOSTNAME = "host" +CONF_USERNAME = "username" +CONF_PASSWORD = "password" + +DATA_HUB = "hub" +DATA_HOST = "host" +DATA_COORDINATOR = "coordinator" + +MANUFACTURER = "KMtronic" +ATTR_MANUFACTURER = "manufacturer" +ATTR_IDENTIFIERS = "identifiers" +ATTR_NAME = "name" diff --git a/homeassistant/components/kmtronic/manifest.json b/homeassistant/components/kmtronic/manifest.json new file mode 100644 index 00000000000000..27e9f953eb780e --- /dev/null +++ b/homeassistant/components/kmtronic/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "kmtronic", + "name": "KMtronic", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/kmtronic", + "requirements": ["pykmtronic==0.0.3"], + "codeowners": ["@dgomes"] +} diff --git a/homeassistant/components/kmtronic/strings.json b/homeassistant/components/kmtronic/strings.json new file mode 100644 index 00000000000000..7becb830d91dba --- /dev/null +++ b/homeassistant/components/kmtronic/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "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%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/kmtronic/switch.py b/homeassistant/components/kmtronic/switch.py new file mode 100644 index 00000000000000..5970ec20cb8f3c --- /dev/null +++ b/homeassistant/components/kmtronic/switch.py @@ -0,0 +1,67 @@ +"""KMtronic Switch integration.""" + +from homeassistant.components.switch import SwitchEntity +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN + + +async def async_setup_entry(hass, entry, async_add_entities): + """Config entry example.""" + coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] + hub = hass.data[DOMAIN][entry.entry_id][DATA_HUB] + host = hass.data[DOMAIN][entry.entry_id][DATA_HOST] + await hub.async_get_relays() + + async_add_entities( + [ + KMtronicSwitch(coordinator, host, relay, entry.unique_id) + for relay in hub.relays + ] + ) + + +class KMtronicSwitch(CoordinatorEntity, SwitchEntity): + """KMtronic Switch Entity.""" + + def __init__(self, coordinator, host, relay, config_entry_id): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(coordinator) + self._host = host + self._relay = relay + self._config_entry_id = config_entry_id + + @property + def available(self) -> bool: + """Return whether the entity is available.""" + return self.coordinator.last_update_success + + @property + def name(self) -> str: + """Return the name of the entity.""" + return f"Relay{self._relay.id}" + + @property + def unique_id(self) -> str: + """Return the unique ID of the entity.""" + return f"{self._config_entry_id}_relay{self._relay.id}" + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return True + + @property + def is_on(self): + """Return entity state.""" + return self._relay.is_on + + async def async_turn_on(self, **kwargs) -> None: + """Turn the switch on.""" + await self._relay.turn_on() + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the switch off.""" + await self._relay.turn_off() + self.async_write_ha_state() diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 7e17a83906878a..8e8949e5788c47 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -115,6 +115,7 @@ "izone", "juicenet", "keenetic_ndms2", + "kmtronic", "kodi", "konnected", "kulersky", diff --git a/requirements_all.txt b/requirements_all.txt index 0d8203279b5d77..05ed5e7aa92be2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1473,6 +1473,9 @@ pyitachip2ir==0.0.7 # homeassistant.components.kira pykira==0.1.1 +# homeassistant.components.kmtronic +pykmtronic==0.0.3 + # homeassistant.components.kodi pykodi==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 99a0351ee371ea..46c91feeb16992 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -775,6 +775,9 @@ pyisy==2.1.0 # homeassistant.components.kira pykira==0.1.1 +# homeassistant.components.kmtronic +pykmtronic==0.0.3 + # homeassistant.components.kodi pykodi==0.2.1 diff --git a/tests/components/kmtronic/__init__.py b/tests/components/kmtronic/__init__.py new file mode 100644 index 00000000000000..2f089d6495f049 --- /dev/null +++ b/tests/components/kmtronic/__init__.py @@ -0,0 +1 @@ +"""Tests for the kmtronic integration.""" diff --git a/tests/components/kmtronic/test_config_flow.py b/tests/components/kmtronic/test_config_flow.py new file mode 100644 index 00000000000000..ebbbf626451fc7 --- /dev/null +++ b/tests/components/kmtronic/test_config_flow.py @@ -0,0 +1,145 @@ +"""Test the kmtronic config flow.""" +from unittest.mock import Mock, patch + +from aiohttp import ClientConnectorError, ClientResponseError + +from homeassistant import config_entries, setup +from homeassistant.components.kmtronic.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED + +from tests.common import MockConfigEntry + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.kmtronic.config_flow.KMTronicHubAPI.async_get_status", + return_value=[Mock()], + ), patch( + "homeassistant.components.kmtronic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.kmtronic.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "1.1.1.1" + assert result2["data"] == { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.kmtronic.config_flow.KMTronicHubAPI.async_get_status", + side_effect=ClientResponseError(None, None, status=401), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +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( + "homeassistant.components.kmtronic.config_flow.KMTronicHubAPI.async_get_status", + side_effect=ClientConnectorError(None, Mock()), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unknown_error(hass): + """Test we handle unknown errors.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.kmtronic.config_flow.KMTronicHubAPI.async_get_status", + side_effect=Exception(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "test-username", + "password": "test-password", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +async def test_unload_config_entry(hass, aioclient_mock): + """Test entry unloading.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.1.1.1", "username": "admin", "password": "admin"}, + ) + config_entry.add_to_hass(hass) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + config_entries = hass.config_entries.async_entries(DOMAIN) + assert len(config_entries) == 1 + assert config_entries[0] is config_entry + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/kmtronic/test_switch.py b/tests/components/kmtronic/test_switch.py new file mode 100644 index 00000000000000..5eec3537176756 --- /dev/null +++ b/tests/components/kmtronic/test_switch.py @@ -0,0 +1,150 @@ +"""The tests for the KMtronic switch platform.""" +import asyncio +from datetime import timedelta + +from homeassistant.components.kmtronic.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY +from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_relay_on_off(hass, aioclient_mock): + """Tests the relay turns on correctly.""" + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + + MockConfigEntry( + domain=DOMAIN, data={"host": "1.1.1.1", "username": "foo", "password": "bar"} + ).add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + # Mocks the response for turning a relay1 on + aioclient_mock.get( + "http://1.1.1.1/FF0101", + text="", + ) + + state = hass.states.get("switch.relay1") + assert state.state == "off" + + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.relay1"}, blocking=True + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "on" + + # Mocks the response for turning a relay1 off + aioclient_mock.get( + "http://1.1.1.1/FF0100", + text="", + ) + + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.relay1"}, blocking=True + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "off" + + +async def test_update(hass, aioclient_mock): + """Tests switch refreshes status periodically.""" + now = dt_util.utcnow() + future = now + timedelta(minutes=10) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + + MockConfigEntry( + domain=DOMAIN, data={"host": "1.1.1.1", "username": "foo", "password": "bar"} + ).add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "off" + + aioclient_mock.clear_requests() + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="11", + ) + async_fire_time_changed(hass, future) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "on" + + +async def test_config_entry_not_ready(hass, aioclient_mock): + """Tests configuration entry not ready.""" + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + exc=asyncio.TimeoutError(), + ) + + config_entry = MockConfigEntry( + domain=DOMAIN, data={"host": "1.1.1.1", "username": "foo", "password": "bar"} + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_failed_update(hass, aioclient_mock): + """Tests coordinator update fails.""" + now = dt_util.utcnow() + future = now + timedelta(minutes=10) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + + MockConfigEntry( + domain=DOMAIN, data={"host": "1.1.1.1", "username": "foo", "password": "bar"} + ).add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "off" + + aioclient_mock.clear_requests() + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="401 Unauthorized: Password required", + status=401, + ) + async_fire_time_changed(hass, future) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == STATE_UNAVAILABLE + + future += timedelta(minutes=10) + aioclient_mock.clear_requests() + aioclient_mock.get( + "http://1.1.1.1/status.xml", + exc=asyncio.TimeoutError(), + ) + async_fire_time_changed(hass, future) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == STATE_UNAVAILABLE From b2b476596b673911b8ecb840ec40e4967dff78b8 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 21 Feb 2021 22:25:01 -0800 Subject: [PATCH 0670/1818] Add UV sensor to SmartTub (#46888) --- homeassistant/components/smarttub/sensor.py | 1 + tests/components/smarttub/test_sensor.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 54921596fb2174..4619d07dbe05c3 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -27,6 +27,7 @@ async def async_setup_entry(hass, entry, async_add_entities): controller.coordinator, spa, "Flow Switch", "flow_switch" ), SmartTubSensor(controller.coordinator, spa, "Ozone", "ozone"), + SmartTubSensor(controller.coordinator, spa, "UV", "uv"), SmartTubSensor( controller.coordinator, spa, "Blowout Cycle", "blowout_cycle" ), diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 7ef3062894a654..5b0163daf262ce 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -27,6 +27,11 @@ async def test_sensors(spa, setup_entry, hass): assert state is not None assert state.state == "off" + entity_id = f"sensor.{spa.brand}_{spa.model}_uv" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "off" + entity_id = f"sensor.{spa.brand}_{spa.model}_blowout_cycle" state = hass.states.get(entity_id) assert state is not None From 75b37b4c2a0bdf68076ecc9eb556c9066e472e9f Mon Sep 17 00:00:00 2001 From: Jan-Willem Mulder Date: Mon, 22 Feb 2021 07:53:58 +0100 Subject: [PATCH 0671/1818] Expose locked attribute in deCONZ climate platform (#46814) --- homeassistant/components/deconz/climate.py | 5 ++++- homeassistant/components/deconz/const.py | 1 + tests/components/deconz/test_climate.py | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 98e3864e191b9c..44111fbbb1e4bb 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -26,7 +26,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR +from .const import ATTR_LOCKED, ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry @@ -254,4 +254,7 @@ def device_state_attributes(self): if self._device.valve is not None: attr[ATTR_VALVE] = self._device.valve + if self._device.locked is not None: + attr[ATTR_LOCKED] = self._device.locked + return attr diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index cbad37b1b87eca..67effa4e81b0ec 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -46,6 +46,7 @@ NEW_SENSOR = "sensors" ATTR_DARK = "dark" +ATTR_LOCKED = "locked" ATTR_OFFSET = "offset" ATTR_ON = "on" ATTR_VALVE = "valve" diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 0a37debade3c6c..5577a2d0414542 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -92,7 +92,7 @@ async def test_simple_climate_device(hass, aioclient_mock): "battery": 59, "displayflipped": None, "heatsetpoint": 2100, - "locked": None, + "locked": True, "mountingmode": None, "offset": 0, "on": True, @@ -132,6 +132,7 @@ async def test_simple_climate_device(hass, aioclient_mock): ] 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" # Event signals thermostat configured off From d61d39de08737a0bb0c7935c5cfa286cf8b694e8 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 22 Feb 2021 08:11:59 +0100 Subject: [PATCH 0672/1818] Handle ConditionError with multiple entity_id for state/numeric_state (#46855) --- homeassistant/exceptions.py | 2 +- homeassistant/helpers/condition.py | 46 +++++++++++++++++++++++------- tests/helpers/test_condition.py | 46 ++++++++++++++++-------------- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 0ac231fd314b9a..84ba2cfa3485da 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -85,7 +85,7 @@ def output(self, indent: int) -> Generator: @attr.s class ConditionErrorContainer(ConditionError): - """Condition error with index.""" + """Condition error with subconditions.""" # List of ConditionErrors that this error wraps errors: Sequence[ConditionError] = attr.ib() diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index c20755a1780fe0..40087650141419 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -342,12 +342,25 @@ def if_numeric_state( if value_template is not None: value_template.hass = hass - return all( - async_numeric_state( - hass, entity_id, below, above, value_template, variables, attribute - ) - for entity_id in entity_ids - ) + errors = [] + for index, entity_id in enumerate(entity_ids): + try: + if not async_numeric_state( + hass, entity_id, below, above, value_template, variables, attribute + ): + return False + except ConditionError as ex: + errors.append( + ConditionErrorIndex( + "numeric_state", index=index, total=len(entity_ids), error=ex + ) + ) + + # Raise the errors if no check was false + if errors: + raise ConditionErrorContainer("numeric_state", errors=errors) + + return True return if_numeric_state @@ -429,10 +442,23 @@ def state_from_config( def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" - return all( - state(hass, entity_id, req_states, for_period, attribute) - for entity_id in entity_ids - ) + errors = [] + for index, entity_id in enumerate(entity_ids): + try: + if not state(hass, entity_id, req_states, for_period, attribute): + return False + except ConditionError as ex: + errors.append( + ConditionErrorIndex( + "state", index=index, total=len(entity_ids), error=ex + ) + ) + + # Raise the errors if no check was false + if errors: + raise ConditionErrorContainer("state", errors=errors) + + return True return if_state diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 2c35a3c8b155ba..5074b6e70c462d 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -390,17 +390,18 @@ async def test_state_raises(hass): with pytest.raises(ConditionError, match="no entity"): condition.state(hass, entity=None, req_state="missing") - # Unknown entity_id - with pytest.raises(ConditionError, match="unknown entity"): - test = await condition.async_from_config( - hass, - { - "condition": "state", - "entity_id": "sensor.door_unknown", - "state": "open", - }, - ) - + # Unknown entities + test = await condition.async_from_config( + hass, + { + "condition": "state", + "entity_id": ["sensor.door_unknown", "sensor.window_unknown"], + "state": "open", + }, + ) + with pytest.raises(ConditionError, match="unknown entity.*door"): + test(hass) + with pytest.raises(ConditionError, match="unknown entity.*window"): test(hass) # Unknown attribute @@ -632,17 +633,18 @@ async def test_state_using_input_entities(hass): async def test_numeric_state_raises(hass): """Test that numeric_state raises ConditionError on errors.""" - # Unknown entity_id - with pytest.raises(ConditionError, match="unknown entity"): - test = await condition.async_from_config( - hass, - { - "condition": "numeric_state", - "entity_id": "sensor.temperature_unknown", - "above": 0, - }, - ) - + # Unknown entities + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": ["sensor.temperature_unknown", "sensor.humidity_unknown"], + "above": 0, + }, + ) + with pytest.raises(ConditionError, match="unknown entity.*temperature"): + test(hass) + with pytest.raises(ConditionError, match="unknown entity.*humidity"): test(hass) # Unknown attribute From e5aef45bd74dcf46142fef8fe870b32e96e993e6 Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Mon, 22 Feb 2021 01:39:10 -0800 Subject: [PATCH 0673/1818] Add usercode support to totalconnect (#39199) * Add test for invalid usercode * Add usercodes to totalconnect. * Update existing tests for usercodes * Fix tests * Add test for invalid usercode * Add usercodes to totalconnect. * Update existing tests for usercodes * Fix tests * Remove YAML support * Fix conflict * Bump to total_connect_client 0.56 * Change Exception to HomeAssistantError * Fix config_flow.py * Simplify async_setup since no yaml * Remove import from config flow and tests * Add reauth and test for it. Various other fixes. * Fix pylint in __init__ * Show config yaml as deprecated * separate config_flow and init tests * Assert ENTRY_STATE_SETUP_ERROR in init test * Add test for reauth flow * Fix reauth and tests * Fix strings * Restore username and usercode with new passord * Correct the integration name * Update tests/components/totalconnect/test_config_flow.py Co-authored-by: Martin Hjelmare * Update tests/components/totalconnect/test_init.py Co-authored-by: Martin Hjelmare * Update .coveragerc * Add test for invalid auth during reauth * Bump total-connect-client to 0.57 * Fix .coveragerc * More tests for usercodes * Fix usercode test * Reload config entry on reauth Co-authored-by: Martin Hjelmare --- .coveragerc | 5 +- .../components/totalconnect/__init__.py | 65 +++++--- .../components/totalconnect/config_flow.py | 131 +++++++++++++-- .../components/totalconnect/const.py | 5 + .../components/totalconnect/manifest.json | 5 +- .../components/totalconnect/strings.json | 17 +- .../totalconnect/translations/en.json | 17 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/totalconnect/common.py | 38 ++++- .../totalconnect/test_alarm_control_panel.py | 52 +++++- .../totalconnect/test_config_flow.py | 156 ++++++++++++------ tests/components/totalconnect/test_init.py | 29 ++++ 13 files changed, 421 insertions(+), 103 deletions(-) create mode 100644 tests/components/totalconnect/test_init.py diff --git a/.coveragerc b/.coveragerc index d66f4032f74755..899577f2acfef2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -978,7 +978,10 @@ omit = homeassistant/components/toon/sensor.py homeassistant/components/toon/switch.py homeassistant/components/torque/sensor.py - homeassistant/components/totalconnect/* + homeassistant/components/totalconnect/__init__.py + homeassistant/components/totalconnect/alarm_control_panel.py + homeassistant/components/totalconnect/binary_sensor.py + homeassistant/components/totalconnect/const.py homeassistant/components/touchline/climate.py homeassistant/components/tplink/common.py homeassistant/components/tplink/switch.py diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index cf3f059cfb9ce6..179d60b794ac94 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -5,60 +5,79 @@ from total_connect_client import TotalConnectClient import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from .const import DOMAIN +from .const import CONF_USERCODES, DOMAIN _LOGGER = logging.getLogger(__name__) PLATFORMS = ["alarm_control_panel", "binary_sensor"] CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) async def async_setup(hass: HomeAssistant, config: dict): """Set up by configuration file.""" - if DOMAIN not in config: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=config[DOMAIN], - ) - ) + hass.data.setdefault(DOMAIN, {}) return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up upon config entry in user interface.""" - hass.data.setdefault(DOMAIN, {}) - conf = entry.data username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] + if CONF_USERCODES not in conf: + _LOGGER.warning("No usercodes in TotalConnect configuration") + # should only happen for those who used UI before we added usercodes + await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": SOURCE_REAUTH, + }, + data=conf, + ) + return False + + temp_codes = conf[CONF_USERCODES] + usercodes = {} + for code in temp_codes: + usercodes[int(code)] = temp_codes[code] + client = await hass.async_add_executor_job( - TotalConnectClient.TotalConnectClient, username, password + TotalConnectClient.TotalConnectClient, username, password, usercodes ) if not client.is_valid_credentials(): _LOGGER.error("TotalConnect authentication failed") + await hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": SOURCE_REAUTH, + }, + data=conf, + ) + ) + return False hass.data[DOMAIN][entry.entry_id] = client diff --git a/homeassistant/components/totalconnect/config_flow.py b/homeassistant/components/totalconnect/config_flow.py index 2608a3c812cdbd..27fa4203a42215 100644 --- a/homeassistant/components/totalconnect/config_flow.py +++ b/homeassistant/components/totalconnect/config_flow.py @@ -5,7 +5,11 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from .const import DOMAIN # pylint: disable=unused-import +from .const import CONF_USERCODES, DOMAIN # pylint: disable=unused-import + +CONF_LOCATION = "location" + +PASSWORD_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -13,6 +17,13 @@ class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 + def __init__(self): + """Initialize the config flow.""" + self.username = None + self.password = None + self.usercodes = {} + self.client = None + async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" errors = {} @@ -25,14 +36,16 @@ async def async_step_user(self, user_input=None): await self.async_set_unique_id(username) self._abort_if_unique_id_configured() - valid = await self.is_valid(username, password) + client = await self.hass.async_add_executor_job( + TotalConnectClient.TotalConnectClient, username, password, None + ) - if valid: - # authentication success / valid - return self.async_create_entry( - title="Total Connect", - data={CONF_USERNAME: username, CONF_PASSWORD: password}, - ) + if client.is_valid_credentials(): + # username/password valid so show user locations + self.username = username + self.password = password + self.client = client + return await self.async_step_locations() # authentication failed / invalid errors["base"] = "invalid_auth" @@ -44,13 +57,101 @@ async def async_step_user(self, user_input=None): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_import(self, user_input): - """Import a config entry.""" - return await self.async_step_user(user_input) + async def async_step_locations(self, user_entry=None): + """Handle the user locations and associated usercodes.""" + errors = {} + if user_entry is not None: + for location_id in self.usercodes: + if self.usercodes[location_id] is None: + valid = await self.hass.async_add_executor_job( + self.client.locations[location_id].set_usercode, + user_entry[CONF_LOCATION], + ) + if valid: + self.usercodes[location_id] = user_entry[CONF_LOCATION] + else: + errors[CONF_LOCATION] = "usercode" + break + + complete = True + for location_id in self.usercodes: + if self.usercodes[location_id] is None: + complete = False + + if not errors and complete: + return self.async_create_entry( + title="Total Connect", + data={ + CONF_USERNAME: self.username, + CONF_PASSWORD: self.password, + CONF_USERCODES: self.usercodes, + }, + ) + else: + for location_id in self.client.locations: + self.usercodes[location_id] = None + + # show the next location that needs a usercode + location_codes = {} + for location_id in self.usercodes: + if self.usercodes[location_id] is None: + location_codes[ + vol.Required( + CONF_LOCATION, + default=location_id, + ) + ] = str + break + + data_schema = vol.Schema(location_codes) + return self.async_show_form( + step_id="locations", + data_schema=data_schema, + errors=errors, + description_placeholders={"base": "description"}, + ) + + async def async_step_reauth(self, config): + """Perform reauth upon an authentication error or no usercode.""" + self.username = config[CONF_USERNAME] + self.usercodes = config[CONF_USERCODES] + + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm(self, user_input=None): + """Dialog that informs the user that reauth is required.""" + errors = {} + if user_input is None: + return self.async_show_form( + step_id="reauth_confirm", + data_schema=PASSWORD_DATA_SCHEMA, + ) - async def is_valid(self, username="", password=""): - """Return true if the given username and password are valid.""" client = await self.hass.async_add_executor_job( - TotalConnectClient.TotalConnectClient, username, password + TotalConnectClient.TotalConnectClient, + self.username, + user_input[CONF_PASSWORD], + self.usercodes, ) - return client.is_valid_credentials() + + if not client.is_valid_credentials(): + errors["base"] = "invalid_auth" + return self.async_show_form( + step_id="reauth_confirm", + errors=errors, + data_schema=PASSWORD_DATA_SCHEMA, + ) + + existing_entry = await self.async_set_unique_id(self.username) + new_entry = { + CONF_USERNAME: self.username, + CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_USERCODES: self.usercodes, + } + self.hass.config_entries.async_update_entry(existing_entry, data=new_entry) + + self.hass.async_create_task( + self.hass.config_entries.async_reload(existing_entry.entry_id) + ) + + return self.async_abort(reason="reauth_successful") diff --git a/homeassistant/components/totalconnect/const.py b/homeassistant/components/totalconnect/const.py index 6c19bf0a217a22..22ecd14281f3fd 100644 --- a/homeassistant/components/totalconnect/const.py +++ b/homeassistant/components/totalconnect/const.py @@ -1,3 +1,8 @@ """TotalConnect constants.""" DOMAIN = "totalconnect" +CONF_USERCODES = "usercodes" +CONF_LOCATION = "location" + +# Most TotalConnect alarms will work passing '-1' as usercode +DEFAULT_USERCODE = "-1" diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json index 4ec632f45779e9..8a42ca99f035df 100644 --- a/homeassistant/components/totalconnect/manifest.json +++ b/homeassistant/components/totalconnect/manifest.json @@ -1,8 +1,9 @@ { "domain": "totalconnect", - "name": "Honeywell Total Connect Alarm", + "name": "Total Connect", "documentation": "https://www.home-assistant.io/integrations/totalconnect", - "requirements": ["total_connect_client==0.55"], + "requirements": ["total_connect_client==0.57"], + "dependencies": [], "codeowners": ["@austinmroczek"], "config_flow": true } diff --git a/homeassistant/components/totalconnect/strings.json b/homeassistant/components/totalconnect/strings.json index 7b306554b7be6f..41b0bf4648bae2 100644 --- a/homeassistant/components/totalconnect/strings.json +++ b/homeassistant/components/totalconnect/strings.json @@ -7,13 +7,26 @@ "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" } + }, + "locations": { + "title": "Location Usercodes", + "description": "Enter the usercode for this user at this location", + "data": { + "location": "Location" + } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "Total Connect needs to re-authenticate your account" } }, "error": { - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "usercode": "Usercode not valid for this user at this location" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } diff --git a/homeassistant/components/totalconnect/translations/en.json b/homeassistant/components/totalconnect/translations/en.json index f02a3eadf9ca9b..5071e623701353 100644 --- a/homeassistant/components/totalconnect/translations/en.json +++ b/homeassistant/components/totalconnect/translations/en.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Account is already configured" + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { - "invalid_auth": "Invalid authentication" + "invalid_auth": "Invalid authentication", + "usercode": "Usercode not valid for this user at this location" }, "step": { + "locations": { + "data": { + "location": "Location" + }, + "description": "Enter the usercode for this user at this location", + "title": "Location Usercodes" + }, + "reauth_confirm": { + "description": "Total Connect needs to re-authenticate your account", + "title": "Reauthenticate Integration" + }, "user": { "data": { "password": "Password", diff --git a/requirements_all.txt b/requirements_all.txt index 05ed5e7aa92be2..5d704b7522aeec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2221,7 +2221,7 @@ todoist-python==8.0.0 toonapi==0.2.0 # homeassistant.components.totalconnect -total_connect_client==0.55 +total_connect_client==0.57 # homeassistant.components.tplink_lte tp-connected==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 46c91feeb16992..957161dca12943 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1133,7 +1133,7 @@ teslajsonpy==0.11.5 toonapi==0.2.0 # homeassistant.components.totalconnect -total_connect_client==0.55 +total_connect_client==0.57 # homeassistant.components.transmission transmissionrpc==0.11 diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py index 17fa244f9b2839..d4285c07425002 100644 --- a/tests/components/totalconnect/common.py +++ b/tests/components/totalconnect/common.py @@ -3,7 +3,7 @@ from total_connect_client import TotalConnectClient -from homeassistant.components.totalconnect import DOMAIN +from homeassistant.components.totalconnect.const import CONF_USERCODES, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component @@ -29,13 +29,19 @@ } RESPONSE_AUTHENTICATE = { - "ResultCode": 0, + "ResultCode": TotalConnectClient.TotalConnectClient.SUCCESS, "SessionID": 1, "Locations": LOCATIONS, "ModuleFlags": MODULE_FLAGS, "UserInfo": USER, } +RESPONSE_AUTHENTICATE_FAILED = { + "ResultCode": TotalConnectClient.TotalConnectClient.BAD_USER_OR_PASSWORD, + "ResultData": "test bad authentication", +} + + PARTITION_DISARMED = { "PartitionID": "1", "ArmingState": TotalConnectClient.TotalConnectLocation.DISARMED, @@ -101,6 +107,32 @@ "ResultCode": TotalConnectClient.TotalConnectClient.COMMAND_FAILED, "ResultData": "Command Failed", } +RESPONSE_USER_CODE_INVALID = { + "ResultCode": TotalConnectClient.TotalConnectClient.USER_CODE_INVALID, + "ResultData": "testing user code invalid", +} +RESPONSE_SUCCESS = {"ResultCode": TotalConnectClient.TotalConnectClient.SUCCESS} + +USERNAME = "username@me.com" +PASSWORD = "password" +USERCODES = {123456: "7890"} +CONFIG_DATA = { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_USERCODES: USERCODES, +} +CONFIG_DATA_NO_USERCODES = {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + + +USERNAME = "username@me.com" +PASSWORD = "password" +USERCODES = {123456: "7890"} +CONFIG_DATA = { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_USERCODES: USERCODES, +} +CONFIG_DATA_NO_USERCODES = {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} async def setup_platform(hass, platform): @@ -108,7 +140,7 @@ async def setup_platform(hass, platform): # first set up a config entry and add it to hass mock_entry = MockConfigEntry( domain=DOMAIN, - data={CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"}, + data=CONFIG_DATA, ) mock_entry.add_to_hass(hass) diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index bc90c1aae2a4f1..ba929c0bc54dbb 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -14,6 +14,7 @@ STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, ) +from homeassistant.exceptions import HomeAssistantError from .common import ( RESPONSE_ARM_FAILURE, @@ -23,6 +24,7 @@ RESPONSE_DISARM_FAILURE, RESPONSE_DISARM_SUCCESS, RESPONSE_DISARMED, + RESPONSE_USER_CODE_INVALID, setup_platform, ) @@ -72,12 +74,31 @@ async def test_arm_home_failure(hass): await setup_platform(hass, ALARM_DOMAIN) assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state - with pytest.raises(Exception) as e: + with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True ) await hass.async_block_till_done() - assert f"{e.value}" == "TotalConnect failed to arm home test." + assert f"{err.value}" == "TotalConnect failed to arm home test." + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + +async def test_arm_home_invalid_usercode(hass): + """Test arm home method with invalid usercode.""" + responses = [RESPONSE_DISARMED, RESPONSE_USER_CODE_INVALID, RESPONSE_DISARMED] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{err.value}" == "TotalConnect failed to arm home test." assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state @@ -108,12 +129,12 @@ async def test_arm_away_failure(hass): await setup_platform(hass, ALARM_DOMAIN) assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state - with pytest.raises(Exception) as e: + with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True ) await hass.async_block_till_done() - assert f"{e.value}" == "TotalConnect failed to arm away test." + assert f"{err.value}" == "TotalConnect failed to arm away test." assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state @@ -144,10 +165,29 @@ async def test_disarm_failure(hass): await setup_platform(hass, ALARM_DOMAIN) assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state - with pytest.raises(Exception) as e: + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{err.value}" == "TotalConnect failed to disarm test." + assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + + +async def test_disarm_invalid_usercode(hass): + """Test disarm method failure.""" + responses = [RESPONSE_ARMED_AWAY, RESPONSE_USER_CODE_INVALID, RESPONSE_ARMED_AWAY] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + + with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True ) await hass.async_block_till_done() - assert f"{e.value}" == "TotalConnect failed to disarm test." + assert f"{err.value}" == "TotalConnect failed to disarm test." assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state diff --git a/tests/components/totalconnect/test_config_flow.py b/tests/components/totalconnect/test_config_flow.py index a1aa8780cfb64c..5d1723a835e3ab 100644 --- a/tests/components/totalconnect/test_config_flow.py +++ b/tests/components/totalconnect/test_config_flow.py @@ -1,78 +1,97 @@ -"""Tests for the iCloud config flow.""" +"""Tests for the TotalConnect config flow.""" from unittest.mock import patch from homeassistant import data_entry_flow -from homeassistant.components.totalconnect.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.components.totalconnect.const import CONF_LOCATION, DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_PASSWORD + +from .common import ( + CONFIG_DATA, + CONFIG_DATA_NO_USERCODES, + RESPONSE_AUTHENTICATE, + RESPONSE_DISARMED, + RESPONSE_SUCCESS, + RESPONSE_USER_CODE_INVALID, + USERNAME, +) from tests.common import MockConfigEntry -USERNAME = "username@me.com" -PASSWORD = "password" - async def test_user(hass): - """Test user config.""" - # no data provided so show the form + """Test user step.""" + # user starts with no data entered, so show the user form result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} + DOMAIN, + context={"source": SOURCE_USER}, + data=None, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" - # now data is provided, so check if login is correct and create the entry - with patch( - "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient" - ) as client_mock: - client_mock.return_value.is_valid_credentials.return_value = True + +async def test_user_show_locations(hass): + """Test user locations form.""" + # user/pass provided, so check if valid then ask for usercodes on locations form + responses = [ + RESPONSE_AUTHENTICATE, + RESPONSE_DISARMED, + RESPONSE_USER_CODE_INVALID, + RESPONSE_SUCCESS, + ] + + with patch("zeep.Client", autospec=True), patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ) as mock_request, patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.get_zone_details", + return_value=True, + ), patch( + "homeassistant.components.totalconnect.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + data=CONFIG_DATA_NO_USERCODES, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + # first it should show the locations form + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "locations" + # client should have sent two requests, authenticate and get status + assert mock_request.call_count == 2 - -async def test_import(hass): - """Test import step with good username and password.""" - with patch( - "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient" - ) as client_mock: - client_mock.return_value.is_valid_credentials.return_value = True - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + # user enters an invalid usercode + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_LOCATION: "bad"}, ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "locations" + # client should have sent 3rd request to validate usercode + assert mock_request.call_count == 3 + + # user enters a valid usercode + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + user_input={CONF_LOCATION: "7890"}, + ) + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + # client should have sent another request to validate usercode + assert mock_request.call_count == 4 async def test_abort_if_already_setup(hass): """Test abort if the account is already setup.""" MockConfigEntry( domain=DOMAIN, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + data=CONFIG_DATA, unique_id=USERNAME, ).add_to_hass(hass) - # Should fail, same USERNAME (import) - with patch( - "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient" - ) as client_mock: - client_mock.return_value.is_valid_credentials.return_value = True - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - # Should fail, same USERNAME (flow) with patch( "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient" @@ -81,7 +100,7 @@ async def test_abort_if_already_setup(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + data=CONFIG_DATA, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -97,8 +116,51 @@ async def test_login_failed(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + data=CONFIG_DATA, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {"base": "invalid_auth"} + + +async def test_reauth(hass): + """Test reauth.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + unique_id=USERNAME, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=entry.data + ) + assert result["step_id"] == "reauth_confirm" + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth_confirm" + + with patch( + "homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient" + ) as client_mock: + # first test with an invalid password + client_mock.return_value.is_valid_credentials.return_value = False + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_PASSWORD: "password"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "reauth_confirm" + assert result["errors"] == {"base": "invalid_auth"} + + # now test with the password valid + client_mock.return_value.is_valid_credentials.return_value = True + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_PASSWORD: "password"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "reauth_successful" + + assert len(hass.config_entries.async_entries()) == 1 diff --git a/tests/components/totalconnect/test_init.py b/tests/components/totalconnect/test_init.py new file mode 100644 index 00000000000000..b8024dbe70d606 --- /dev/null +++ b/tests/components/totalconnect/test_init.py @@ -0,0 +1,29 @@ +"""Tests for the TotalConnect init process.""" +from unittest.mock import patch + +from homeassistant.components.totalconnect.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_SETUP_ERROR +from homeassistant.setup import async_setup_component + +from .common import CONFIG_DATA + +from tests.common import MockConfigEntry + + +async def test_reauth_started(hass): + """Test that reauth is started when we have login errors.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + ) + mock_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient", + autospec=True, + ) as mock_client: + mock_client.return_value.is_valid_credentials.return_value = False + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + assert mock_entry.state == ENTRY_STATE_SETUP_ERROR From 23c2bd4e6943ee813973dee753e2dfe3aa118e52 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Feb 2021 12:44:40 +0100 Subject: [PATCH 0674/1818] Upgrade mypy to 0.812 (#46898) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 4683c927085d70..12f215f177a46b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,7 +8,7 @@ codecov==2.1.10 coverage==5.4 jsonpickle==1.4.1 mock-open==1.4.0 -mypy==0.800 +mypy==0.812 pre-commit==2.10.1 pylint==2.6.0 astroid==2.4.2 From 338c07a56b3a9064b2a44a4834d969e2b1865209 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 22 Feb 2021 13:01:02 +0100 Subject: [PATCH 0675/1818] Add Xiaomi Miio vacuum config flow (#46669) --- .../components/xiaomi_miio/__init__.py | 9 +- .../components/xiaomi_miio/config_flow.py | 68 +++-- homeassistant/components/xiaomi_miio/const.py | 4 + .../components/xiaomi_miio/device.py | 8 +- .../components/xiaomi_miio/strings.json | 2 +- .../xiaomi_miio/translations/en.json | 21 +- .../components/xiaomi_miio/vacuum.py | 245 +++++++++--------- .../xiaomi_miio/test_config_flow.py | 60 +++++ tests/components/xiaomi_miio/test_vacuum.py | 32 ++- 9 files changed, 269 insertions(+), 180 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 273fc53da5ae6f..a8b32a31576ee5 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -17,6 +17,7 @@ DOMAIN, KEY_COORDINATOR, MODELS_SWITCH, + MODELS_VACUUM, ) from .gateway import ConnectXiaomiGateway @@ -24,6 +25,7 @@ GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] SWITCH_PLATFORMS = ["switch"] +VACUUM_PLATFORMS = ["vacuum"] async def async_setup(hass: core.HomeAssistant, config: dict): @@ -117,9 +119,14 @@ async def async_setup_device_entry( model = entry.data[CONF_MODEL] # Identify platforms to setup + platforms = [] if model in MODELS_SWITCH: platforms = SWITCH_PLATFORMS - else: + for vacuum_model in MODELS_VACUUM: + if model.startswith(vacuum_model): + platforms = VACUUM_PLATFORMS + + if not platforms: return False for component in platforms: diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 2a1532eaf9b813..d7e2198f72f024 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -15,8 +15,9 @@ CONF_MAC, CONF_MODEL, DOMAIN, + MODELS_ALL, + MODELS_ALL_DEVICES, MODELS_GATEWAY, - MODELS_SWITCH, ) from .device import ConnectXiaomiDevice @@ -29,6 +30,7 @@ vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), } DEVICE_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(DEVICE_SETTINGS) +DEVICE_MODEL_CONFIG = {vol.Optional(CONF_MODEL): vol.In(MODELS_ALL)} class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -40,6 +42,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize.""" self.host = None + self.mac = None async def async_step_import(self, conf: dict): """Import a configuration from config.yaml.""" @@ -53,15 +56,15 @@ async def async_step_zeroconf(self, discovery_info): """Handle zeroconf discovery.""" name = discovery_info.get("name") self.host = discovery_info.get("host") - mac_address = discovery_info.get("properties", {}).get("mac") + self.mac = discovery_info.get("properties", {}).get("mac") - if not name or not self.host or not mac_address: + if not name or not self.host or not self.mac: return self.async_abort(reason="not_xiaomi_miio") # Check which device is discovered. for gateway_model in MODELS_GATEWAY: if name.startswith(gateway_model.replace(".", "-")): - unique_id = format_mac(mac_address) + unique_id = format_mac(self.mac) await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) @@ -70,9 +73,9 @@ async def async_step_zeroconf(self, discovery_info): ) return await self.async_step_device() - for switch_model in MODELS_SWITCH: - if name.startswith(switch_model.replace(".", "-")): - unique_id = format_mac(mac_address) + for device_model in MODELS_ALL_DEVICES: + if name.startswith(device_model.replace(".", "-")): + unique_id = format_mac(self.mac) await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) @@ -95,6 +98,7 @@ async def async_step_device(self, user_input=None): errors = {} if user_input is not None: token = user_input[CONF_TOKEN] + model = user_input.get(CONF_MODEL) if user_input.get(CONF_HOST): self.host = user_input[CONF_HOST] @@ -103,12 +107,17 @@ async def async_step_device(self, user_input=None): await connect_device_class.async_connect_device(self.host, token) device_info = connect_device_class.device_info - if device_info is not None: + if model is None and device_info is not None: + model = device_info.model + + if model is not None: + if self.mac is None and device_info is not None: + self.mac = format_mac(device_info.mac_address) + # Setup Gateways for gateway_model in MODELS_GATEWAY: - if device_info.model.startswith(gateway_model): - mac = format_mac(device_info.mac_address) - unique_id = mac + if model.startswith(gateway_model): + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured() return self.async_create_entry( @@ -117,29 +126,29 @@ async def async_step_device(self, user_input=None): CONF_FLOW_TYPE: CONF_GATEWAY, CONF_HOST: self.host, CONF_TOKEN: token, - CONF_MODEL: device_info.model, - CONF_MAC: mac, + CONF_MODEL: model, + CONF_MAC: self.mac, }, ) # Setup all other Miio Devices name = user_input.get(CONF_NAME, DEFAULT_DEVICE_NAME) - if device_info.model in MODELS_SWITCH: - mac = format_mac(device_info.mac_address) - unique_id = mac - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() - return self.async_create_entry( - title=name, - data={ - CONF_FLOW_TYPE: CONF_DEVICE, - CONF_HOST: self.host, - CONF_TOKEN: token, - CONF_MODEL: device_info.model, - CONF_MAC: mac, - }, - ) + for device_model in MODELS_ALL_DEVICES: + if model.startswith(device_model): + unique_id = self.mac + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name, + data={ + CONF_FLOW_TYPE: CONF_DEVICE, + CONF_HOST: self.host, + CONF_TOKEN: token, + CONF_MODEL: model, + CONF_MAC: self.mac, + }, + ) errors["base"] = "unknown_device" else: errors["base"] = "cannot_connect" @@ -149,4 +158,7 @@ async def async_step_device(self, user_input=None): else: schema = DEVICE_CONFIG + if errors: + schema = schema.extend(DEVICE_MODEL_CONFIG) + return self.async_show_form(step_id="device", data_schema=schema, errors=errors) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index c0ddb6983409b3..d6c39146f6ad0f 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -23,6 +23,10 @@ "chuangmi.plug.hmi206", "lumi.acpartner.v3", ] +MODELS_VACUUM = ["roborock.vacuum"] + +MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM +MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services SERVICE_SET_BUZZER_ON = "fan_set_buzzer_on" diff --git a/homeassistant/components/xiaomi_miio/device.py b/homeassistant/components/xiaomi_miio/device.py index 48bedbf0cc8ff3..cb91726ecadd3a 100644 --- a/homeassistant/components/xiaomi_miio/device.py +++ b/homeassistant/components/xiaomi_miio/device.py @@ -78,10 +78,14 @@ def name(self): @property def device_info(self): """Return the device info.""" - return { - "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac)}, + device_info = { "identifiers": {(DOMAIN, self._device_id)}, "manufacturer": "Xiaomi", "name": self._name, "model": self._model, } + + if self._mac is not None: + device_info["connections"] = {(dr.CONNECTION_NETWORK_MAC, self._mac)} + + return device_info diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 1ab0c6f51c635d..90710baebca73d 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -8,7 +8,7 @@ "data": { "host": "[%key:common::config_flow::data::ip%]", "token": "[%key:common::config_flow::data::api_token%]", - "name": "Name of the device" + "model": "Device model (Optional)" } } }, diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index fe95af5e06cb0a..37a8ce06eba918 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -6,7 +6,6 @@ }, "error": { "cannot_connect": "Failed to connect", - "no_device_selected": "No device selected, please select one device.", "unknown_device": "The device model is not known, not able to setup the device using config flow." }, "flow_title": "Xiaomi Miio: {name}", @@ -14,27 +13,11 @@ "device": { "data": { "host": "IP Address", - "name": "Name of the device", - "token": "API Token" + "token": "API Token", + "model": "Device model (Optional)" }, "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" - }, - "gateway": { - "data": { - "host": "IP Address", - "name": "Name of the Gateway", - "token": "API Token" - }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", - "title": "Connect to a Xiaomi Gateway" - }, - "user": { - "data": { - "gateway": "Connect to a Xiaomi Gateway" - }, - "description": "Select to which device you want to connect.", - "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index ab76d14a69ae44..7bdbfca7bc9190 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -26,11 +26,15 @@ SUPPORT_STOP, StateVacuumEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.util.dt import as_utc from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + DOMAIN, SERVICE_CLEAN_SEGMENT, SERVICE_CLEAN_ZONE, SERVICE_GOTO, @@ -39,11 +43,11 @@ SERVICE_START_REMOTE_CONTROL, SERVICE_STOP_REMOTE_CONTROL, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Vacuum cleaner" -DATA_KEY = "vacuum.xiaomi_miio" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -116,110 +120,124 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Xiaomi vacuum cleaner robot platform.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Vacuum via platform setup is deprecated. Please remove it from your configuration." + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - # Create handler - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - vacuum = Vacuum(host, token) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Xiaomi vacuum cleaner robot from a config entry.""" + entities = [] - mirobo = MiroboVacuum(name, vacuum) - hass.data[DATA_KEY][host] = mirobo + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + unique_id = config_entry.unique_id - async_add_entities([mirobo], update_before_add=True) + # Create handler + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + vacuum = Vacuum(host, token) - platform = entity_platform.current_platform.get() + mirobo = MiroboVacuum(name, vacuum, config_entry, unique_id) + entities.append(mirobo) - platform.async_register_entity_service( - SERVICE_START_REMOTE_CONTROL, - {}, - MiroboVacuum.async_remote_control_start.__name__, - ) + platform = entity_platform.current_platform.get() - platform.async_register_entity_service( - SERVICE_STOP_REMOTE_CONTROL, - {}, - MiroboVacuum.async_remote_control_stop.__name__, - ) + platform.async_register_entity_service( + SERVICE_START_REMOTE_CONTROL, + {}, + MiroboVacuum.async_remote_control_start.__name__, + ) - platform.async_register_entity_service( - SERVICE_MOVE_REMOTE_CONTROL, - { - vol.Optional(ATTR_RC_VELOCITY): vol.All( - vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29) - ), - vol.Optional(ATTR_RC_ROTATION): vol.All( - vol.Coerce(int), vol.Clamp(min=-179, max=179) - ), - vol.Optional(ATTR_RC_DURATION): cv.positive_int, - }, - MiroboVacuum.async_remote_control_move.__name__, - ) + platform.async_register_entity_service( + SERVICE_STOP_REMOTE_CONTROL, + {}, + MiroboVacuum.async_remote_control_stop.__name__, + ) - platform.async_register_entity_service( - SERVICE_MOVE_REMOTE_CONTROL_STEP, - { - vol.Optional(ATTR_RC_VELOCITY): vol.All( - vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29) - ), - vol.Optional(ATTR_RC_ROTATION): vol.All( - vol.Coerce(int), vol.Clamp(min=-179, max=179) - ), - vol.Optional(ATTR_RC_DURATION): cv.positive_int, - }, - MiroboVacuum.async_remote_control_move_step.__name__, - ) + platform.async_register_entity_service( + SERVICE_MOVE_REMOTE_CONTROL, + { + vol.Optional(ATTR_RC_VELOCITY): vol.All( + vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29) + ), + vol.Optional(ATTR_RC_ROTATION): vol.All( + vol.Coerce(int), vol.Clamp(min=-179, max=179) + ), + vol.Optional(ATTR_RC_DURATION): cv.positive_int, + }, + MiroboVacuum.async_remote_control_move.__name__, + ) - platform.async_register_entity_service( - SERVICE_CLEAN_ZONE, - { - vol.Required(ATTR_ZONE_ARRAY): vol.All( - list, - [ - vol.ExactSequence( - [ - vol.Coerce(int), - vol.Coerce(int), - vol.Coerce(int), - vol.Coerce(int), - ] - ) - ], - ), - vol.Required(ATTR_ZONE_REPEATER): vol.All( - vol.Coerce(int), vol.Clamp(min=1, max=3) - ), - }, - MiroboVacuum.async_clean_zone.__name__, - ) + platform.async_register_entity_service( + SERVICE_MOVE_REMOTE_CONTROL_STEP, + { + vol.Optional(ATTR_RC_VELOCITY): vol.All( + vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29) + ), + vol.Optional(ATTR_RC_ROTATION): vol.All( + vol.Coerce(int), vol.Clamp(min=-179, max=179) + ), + vol.Optional(ATTR_RC_DURATION): cv.positive_int, + }, + MiroboVacuum.async_remote_control_move_step.__name__, + ) - platform.async_register_entity_service( - SERVICE_GOTO, - { - vol.Required("x_coord"): vol.Coerce(int), - vol.Required("y_coord"): vol.Coerce(int), - }, - MiroboVacuum.async_goto.__name__, - ) - platform.async_register_entity_service( - SERVICE_CLEAN_SEGMENT, - {vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)])}, - MiroboVacuum.async_clean_segment.__name__, - ) + platform.async_register_entity_service( + SERVICE_CLEAN_ZONE, + { + vol.Required(ATTR_ZONE_ARRAY): vol.All( + list, + [ + vol.ExactSequence( + [ + vol.Coerce(int), + vol.Coerce(int), + vol.Coerce(int), + vol.Coerce(int), + ] + ) + ], + ), + vol.Required(ATTR_ZONE_REPEATER): vol.All( + vol.Coerce(int), vol.Clamp(min=1, max=3) + ), + }, + MiroboVacuum.async_clean_zone.__name__, + ) + + platform.async_register_entity_service( + SERVICE_GOTO, + { + vol.Required("x_coord"): vol.Coerce(int), + vol.Required("y_coord"): vol.Coerce(int), + }, + MiroboVacuum.async_goto.__name__, + ) + platform.async_register_entity_service( + SERVICE_CLEAN_SEGMENT, + {vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)])}, + MiroboVacuum.async_clean_segment.__name__, + ) + async_add_entities(entities, update_before_add=True) -class MiroboVacuum(StateVacuumEntity): + +class MiroboVacuum(XiaomiMiioEntity, StateVacuumEntity): """Representation of a Xiaomi Vacuum cleaner robot.""" - def __init__(self, name, vacuum): + def __init__(self, name, device, entry, unique_id): """Initialize the Xiaomi vacuum cleaner robot handler.""" - self._name = name - self._vacuum = vacuum + super().__init__(name, device, entry, unique_id) self.vacuum_state = None self._available = False @@ -233,11 +251,6 @@ def __init__(self, name, vacuum): self._timers = None - @property - def name(self): - """Return the name of the device.""" - return self._name - @property def state(self): """Return the status of the vacuum cleaner.""" @@ -364,16 +377,16 @@ async def _try_command(self, mask_error, func, *args, **kwargs): async def async_start(self): """Start or resume the cleaning task.""" await self._try_command( - "Unable to start the vacuum: %s", self._vacuum.resume_or_start + "Unable to start the vacuum: %s", self._device.resume_or_start ) async def async_pause(self): """Pause the cleaning task.""" - await self._try_command("Unable to set start/pause: %s", self._vacuum.pause) + await self._try_command("Unable to set start/pause: %s", self._device.pause) async def async_stop(self, **kwargs): """Stop the vacuum cleaner.""" - await self._try_command("Unable to stop: %s", self._vacuum.stop) + await self._try_command("Unable to stop: %s", self._device.stop) async def async_set_fan_speed(self, fan_speed, **kwargs): """Set fan speed.""" @@ -390,28 +403,28 @@ async def async_set_fan_speed(self, fan_speed, **kwargs): ) return await self._try_command( - "Unable to set fan speed: %s", self._vacuum.set_fan_speed, fan_speed + "Unable to set fan speed: %s", self._device.set_fan_speed, fan_speed ) async def async_return_to_base(self, **kwargs): """Set the vacuum cleaner to return to the dock.""" - await self._try_command("Unable to return home: %s", self._vacuum.home) + await self._try_command("Unable to return home: %s", self._device.home) async def async_clean_spot(self, **kwargs): """Perform a spot clean-up.""" await self._try_command( - "Unable to start the vacuum for a spot clean-up: %s", self._vacuum.spot + "Unable to start the vacuum for a spot clean-up: %s", self._device.spot ) async def async_locate(self, **kwargs): """Locate the vacuum cleaner.""" - await self._try_command("Unable to locate the botvac: %s", self._vacuum.find) + await self._try_command("Unable to locate the botvac: %s", self._device.find) async def async_send_command(self, command, params=None, **kwargs): """Send raw command.""" await self._try_command( "Unable to send command to the vacuum: %s", - self._vacuum.raw_command, + self._device.raw_command, command, params, ) @@ -419,13 +432,13 @@ async def async_send_command(self, command, params=None, **kwargs): async def async_remote_control_start(self): """Start remote control mode.""" await self._try_command( - "Unable to start remote control the vacuum: %s", self._vacuum.manual_start + "Unable to start remote control the vacuum: %s", self._device.manual_start ) async def async_remote_control_stop(self): """Stop remote control mode.""" await self._try_command( - "Unable to stop remote control the vacuum: %s", self._vacuum.manual_stop + "Unable to stop remote control the vacuum: %s", self._device.manual_stop ) async def async_remote_control_move( @@ -434,7 +447,7 @@ async def async_remote_control_move( """Move vacuum with remote control mode.""" await self._try_command( "Unable to move with remote control the vacuum: %s", - self._vacuum.manual_control, + self._device.manual_control, velocity=velocity, rotation=rotation, duration=duration, @@ -446,7 +459,7 @@ async def async_remote_control_move_step( """Move vacuum one step with remote control mode.""" await self._try_command( "Unable to remote control the vacuum: %s", - self._vacuum.manual_control_once, + self._device.manual_control_once, velocity=velocity, rotation=rotation, duration=duration, @@ -456,7 +469,7 @@ async def async_goto(self, x_coord: int, y_coord: int): """Goto the specified coordinates.""" await self._try_command( "Unable to send the vacuum cleaner to the specified coordinates: %s", - self._vacuum.goto, + self._device.goto, x_coord=x_coord, y_coord=y_coord, ) @@ -468,23 +481,23 @@ async def async_clean_segment(self, segments): await self._try_command( "Unable to start cleaning of the specified segments: %s", - self._vacuum.segment_clean, + self._device.segment_clean, segments=segments, ) def update(self): """Fetch state from the device.""" try: - state = self._vacuum.status() + state = self._device.status() self.vacuum_state = state - self._fan_speeds = self._vacuum.fan_speed_presets() + self._fan_speeds = self._device.fan_speed_presets() self._fan_speeds_reverse = {v: k for k, v in self._fan_speeds.items()} - self.consumable_state = self._vacuum.consumable_status() - self.clean_history = self._vacuum.clean_history() - self.last_clean = self._vacuum.last_clean_details() - self.dnd_state = self._vacuum.dnd_status() + self.consumable_state = self._device.consumable_status() + self.clean_history = self._device.clean_history() + self.last_clean = self._device.last_clean_details() + self.dnd_state = self._device.dnd_status() self._available = True except (OSError, DeviceException) as exc: @@ -494,7 +507,7 @@ def update(self): # Fetch timers separately, see #38285 try: - self._timers = self._vacuum.timer() + self._timers = self._device.timer() except DeviceException as exc: _LOGGER.debug( "Unable to fetch timers, this may happen on some devices: %s", exc @@ -507,6 +520,6 @@ async def async_clean_zone(self, zone, repeats=1): _zone.append(repeats) _LOGGER.debug("Zone with repeats: %s", zone) try: - await self.hass.async_add_executor_job(self._vacuum.zoned_clean, zone) + await self.hass.async_add_executor_job(self._device.zoned_clean, zone) except (OSError, DeviceException) as exc: _LOGGER.error("Unable to send zoned_clean command to the vacuum: %s", exc) diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index 220c51034f1766..f4f7b5e2b46fe5 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -257,6 +257,53 @@ async def test_import_flow_success(hass): } +async def test_config_flow_step_device_manual_model_succes(hass): + """Test config flow, device connection error, manual model.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {} + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + side_effect=DeviceException({}), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: TEST_HOST, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "device" + assert result["errors"] == {"base": "cannot_connect"} + + overwrite_model = const.MODELS_VACUUM[0] + + with patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + side_effect=DeviceException({}), + ), patch( + "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_TOKEN: TEST_TOKEN, const.CONF_MODEL: overwrite_model}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == DEFAULT_DEVICE_NAME + assert result["data"] == { + const.CONF_FLOW_TYPE: const.CONF_DEVICE, + CONF_HOST: TEST_HOST, + CONF_TOKEN: TEST_TOKEN, + const.CONF_MODEL: overwrite_model, + const.CONF_MAC: None, + } + + async def config_flow_device_success(hass, model_to_test): """Test a successful config flow for a device (base class).""" result = await hass.config_entries.flow.async_init( @@ -342,3 +389,16 @@ async def test_zeroconf_plug_success(hass): test_plug_model = const.MODELS_SWITCH[0] test_zeroconf_name = const.MODELS_SWITCH[0].replace(".", "-") await zeroconf_device_success(hass, test_zeroconf_name, test_plug_model) + + +async def test_config_flow_vacuum_success(hass): + """Test a successful config flow for a vacuum.""" + test_vacuum_model = const.MODELS_VACUUM[0] + await config_flow_device_success(hass, test_vacuum_model) + + +async def test_zeroconf_vacuum_success(hass): + """Test a successful zeroconf discovery of a vacuum.""" + test_vacuum_model = const.MODELS_VACUUM[0] + test_zeroconf_name = const.MODELS_VACUUM[0].replace(".", "-") + await zeroconf_device_success(hass, test_zeroconf_name, test_vacuum_model) diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index b1a3c08b84b690..23e5d8884b34e0 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -22,6 +22,7 @@ STATE_CLEANING, STATE_ERROR, ) +from homeassistant.components.xiaomi_miio import const from homeassistant.components.xiaomi_miio.const import DOMAIN as XIAOMI_DOMAIN from homeassistant.components.xiaomi_miio.vacuum import ( ATTR_CLEANED_AREA, @@ -38,7 +39,6 @@ ATTR_SIDE_BRUSH_LEFT, ATTR_TIMERS, CONF_HOST, - CONF_NAME, CONF_TOKEN, SERVICE_CLEAN_SEGMENT, SERVICE_CLEAN_ZONE, @@ -51,12 +51,14 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - CONF_PLATFORM, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.setup import async_setup_component + +from .test_config_flow import TEST_MAC + +from tests.common import MockConfigEntry PLATFORM = "xiaomi_miio" @@ -521,17 +523,21 @@ async def setup_component(hass, entity_name): """Set up vacuum component.""" entity_id = f"{DOMAIN}.{entity_name}" - await async_setup_component( - hass, - DOMAIN, - { - DOMAIN: { - CONF_PLATFORM: PLATFORM, - CONF_HOST: "192.168.1.100", - CONF_NAME: entity_name, - CONF_TOKEN: "12345678901234567890123456789012", - } + config_entry = MockConfigEntry( + domain=XIAOMI_DOMAIN, + unique_id="123456", + title=entity_name, + data={ + const.CONF_FLOW_TYPE: const.CONF_DEVICE, + CONF_HOST: "192.168.1.100", + CONF_TOKEN: "12345678901234567890123456789012", + const.CONF_MODEL: const.MODELS_VACUUM[0], + const.CONF_MAC: TEST_MAC, }, ) + + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() + return entity_id From 36b56586def1e2ae0e63b68f58966adea3989fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Mon, 22 Feb 2021 13:58:32 +0100 Subject: [PATCH 0676/1818] Bump samsungtvws from 1.4.0 to 1.6.0 (#46878) --- homeassistant/components/samsungtv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 5584d2dd452bdc..08dc4d0c04974a 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/samsungtv", "requirements": [ "samsungctl[websocket]==0.7.1", - "samsungtvws==1.4.0" + "samsungtvws==1.6.0" ], "ssdp": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 5d704b7522aeec..b98cbde46bca68 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1998,7 +1998,7 @@ rxv==0.6.0 samsungctl[websocket]==0.7.1 # homeassistant.components.samsungtv -samsungtvws==1.4.0 +samsungtvws==1.6.0 # homeassistant.components.satel_integra satel_integra==0.3.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 957161dca12943..b1beff34323421 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1029,7 +1029,7 @@ rxv==0.6.0 samsungctl[websocket]==0.7.1 # homeassistant.components.samsungtv -samsungtvws==1.4.0 +samsungtvws==1.6.0 # homeassistant.components.dhcp scapy==2.4.4 From 82a9dc620cc20692e5b5c84381be38084f89ad75 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 22 Feb 2021 14:31:22 +0100 Subject: [PATCH 0677/1818] Add device_class to Shelly cover domain (#46894) Fix author --- homeassistant/components/shelly/cover.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/shelly/cover.py b/homeassistant/components/shelly/cover.py index 6caa7d5132c320..0438e5fe6b7e9d 100644 --- a/homeassistant/components/shelly/cover.py +++ b/homeassistant/components/shelly/cover.py @@ -3,6 +3,7 @@ from homeassistant.components.cover import ( ATTR_POSITION, + DEVICE_CLASS_SHUTTER, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, @@ -75,6 +76,11 @@ def supported_features(self): """Flag supported features.""" return self._supported_features + @property + def device_class(self) -> str: + """Return the class of the device.""" + return DEVICE_CLASS_SHUTTER + async def async_close_cover(self, **kwargs): """Close cover.""" self.control_result = await self.block.set_state(go="close") From 603191702fcfcd51c257567565d6990f3a6930ed Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Feb 2021 14:43:29 +0100 Subject: [PATCH 0678/1818] Cleanup of possibily confusing comment in esphome (#46903) --- homeassistant/components/esphome/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 23b4044fc9e940..6ce411b5169927 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -52,10 +52,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: - """Stub to allow setting up this component. - - Configuration through YAML is not supported at this time. - """ + """Stub to allow setting up this component.""" return True From 81d011efc533cc516ad57dfb873f654bd8f1f31b Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Mon, 22 Feb 2021 06:10:00 -0800 Subject: [PATCH 0679/1818] Add binary sensor to SmartTub for online status (#46889) --- homeassistant/components/smarttub/__init__.py | 2 +- .../components/smarttub/binary_sensor.py | 40 +++++++++++++++++++ homeassistant/components/smarttub/entity.py | 16 +++++++- homeassistant/components/smarttub/sensor.py | 16 ++------ tests/components/smarttub/conftest.py | 1 + .../components/smarttub/test_binary_sensor.py | 13 ++++++ 6 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/smarttub/binary_sensor.py create mode 100644 tests/components/smarttub/test_binary_sensor.py diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py index 8467c20807677f..457af4b7bc0d9e 100644 --- a/homeassistant/components/smarttub/__init__.py +++ b/homeassistant/components/smarttub/__init__.py @@ -7,7 +7,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["climate", "light", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "climate", "light", "sensor", "switch"] async def async_setup(hass, config): diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py new file mode 100644 index 00000000000000..52dbfd71a37b1b --- /dev/null +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -0,0 +1,40 @@ +"""Platform for binary sensor integration.""" +import logging + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + BinarySensorEntity, +) + +from .const import DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubSensorBase + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up binary sensor entities for the binary sensors in the tub.""" + + controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] + + entities = [SmartTubOnline(controller.coordinator, spa) for spa in controller.spas] + + async_add_entities(entities) + + +class SmartTubOnline(SmartTubSensorBase, BinarySensorEntity): + """A binary sensor indicating whether the spa is currently online (connected to the cloud).""" + + def __init__(self, coordinator, spa): + """Initialize the entity.""" + super().__init__(coordinator, spa, "Online", "online") + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self._state is True + + @property + def device_class(self) -> str: + """Return the device class for this entity.""" + return DEVICE_CLASS_CONNECTIVITY diff --git a/homeassistant/components/smarttub/entity.py b/homeassistant/components/smarttub/entity.py index eab60b4162c263..8be956a2b70e9d 100644 --- a/homeassistant/components/smarttub/entity.py +++ b/homeassistant/components/smarttub/entity.py @@ -13,8 +13,6 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["climate"] - class SmartTubEntity(CoordinatorEntity): """Base class for SmartTub entities.""" @@ -57,3 +55,17 @@ def spa_status(self) -> smarttub.SpaState: """Retrieve the result of Spa.get_status().""" return self.coordinator.data[self.spa.id].get("status") + + +class SmartTubSensorBase(SmartTubEntity): + """Base class for SmartTub sensors.""" + + def __init__(self, coordinator, spa, sensor_name, attr_name): + """Initialize the entity.""" + super().__init__(coordinator, spa, sensor_name) + self._attr_name = attr_name + + @property + def _state(self): + """Retrieve the underlying state from the spa.""" + return getattr(self.spa_status, self._attr_name) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 4619d07dbe05c3..be3d60c02419ad 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -3,7 +3,7 @@ import logging from .const import DOMAIN, SMARTTUB_CONTROLLER -from .entity import SmartTubEntity +from .entity import SmartTubSensorBase _LOGGER = logging.getLogger(__name__) @@ -42,18 +42,8 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class SmartTubSensor(SmartTubEntity): - """Generic and base class for SmartTub sensors.""" - - def __init__(self, coordinator, spa, sensor_name, attr_name): - """Initialize the entity.""" - super().__init__(coordinator, spa, sensor_name) - self._attr_name = attr_name - - @property - def _state(self): - """Retrieve the underlying state from the spa.""" - return getattr(self.spa_status, self._attr_name) +class SmartTubSensor(SmartTubSensorBase): + """Generic class for SmartTub status sensors.""" @property def state(self) -> str: diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index 79e5d06d3b382d..b7c90b5ad3e2a7 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -48,6 +48,7 @@ def mock_spa(): "setTemperature": 39, "water": {"temperature": 38}, "heater": "ON", + "online": True, "heatMode": "AUTO", "state": "NORMAL", "primaryFiltration": { diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py new file mode 100644 index 00000000000000..b2624369e96884 --- /dev/null +++ b/tests/components/smarttub/test_binary_sensor.py @@ -0,0 +1,13 @@ +"""Test the SmartTub binary sensor platform.""" + +from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY, STATE_ON + + +async def test_binary_sensors(spa, setup_entry, hass): + """Test the binary sensors.""" + + entity_id = f"binary_sensor.{spa.brand}_{spa.model}_online" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_ON + assert state.attributes.get("device_class") == DEVICE_CLASS_CONNECTIVITY From 75e04f3a713b6fab3ff4839cd1006280302b7bdf Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 22 Feb 2021 09:28:08 -0500 Subject: [PATCH 0680/1818] Clean up constants (#46885) --- homeassistant/components/air_quality/__init__.py | 6 ++++-- homeassistant/components/channels/media_player.py | 5 +---- homeassistant/components/comfoconnect/sensor.py | 2 +- homeassistant/components/facebox/image_processing.py | 2 +- homeassistant/components/hassio/const.py | 1 - homeassistant/components/hassio/discovery.py | 11 ++--------- .../components/homematicip_cloud/generic_entity.py | 2 +- homeassistant/components/netatmo/const.py | 1 - homeassistant/components/netatmo/webhook.py | 2 +- homeassistant/components/nightscout/const.py | 1 - homeassistant/components/nightscout/sensor.py | 3 ++- homeassistant/components/rachio/switch.py | 3 +-- homeassistant/components/reddit/sensor.py | 2 +- homeassistant/components/traccar/__init__.py | 8 ++++++-- homeassistant/components/traccar/const.py | 1 - .../components/usgs_earthquakes_feed/geo_location.py | 2 +- homeassistant/components/watson_tts/tts.py | 1 - homeassistant/components/zwave_js/__init__.py | 3 +-- homeassistant/components/zwave_js/const.py | 1 - tests/components/dynalite/common.py | 2 +- tests/components/nightscout/test_sensor.py | 3 +-- 21 files changed, 25 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index 48423d08e69f68..52c9208854a91e 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -2,7 +2,10 @@ from datetime import timedelta import logging -from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER +from homeassistant.const import ( + ATTR_ATTRIBUTION, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, +) from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, @@ -13,7 +16,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_AQI = "air_quality_index" -ATTR_ATTRIBUTION = "attribution" ATTR_CO2 = "carbon_dioxide" ATTR_CO = "carbon_monoxide" ATTR_N2O = "nitrogen_oxide" diff --git a/homeassistant/components/channels/media_player.py b/homeassistant/components/channels/media_player.py index 481cdd7ecad3d5..5376dc3fe97848 100644 --- a/homeassistant/components/channels/media_player.py +++ b/homeassistant/components/channels/media_player.py @@ -18,6 +18,7 @@ SUPPORT_VOLUME_MUTE, ) from homeassistant.const import ( + ATTR_SECONDS, CONF_HOST, CONF_NAME, CONF_PORT, @@ -53,10 +54,6 @@ ) -# Service call validation schemas -ATTR_SECONDS = "seconds" - - async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Channels platform.""" device = ChannelsPlayer(config[CONF_NAME], config[CONF_HOST], config[CONF_PORT]) diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 53075beecaf643..660228b0b8d5cd 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -29,6 +29,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_DEVICE_CLASS, + ATTR_ID, CONF_RESOURCES, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -72,7 +73,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_ICON = "icon" -ATTR_ID = "id" ATTR_LABEL = "label" ATTR_MULTIPLIER = "multiplier" ATTR_UNIT = "unit" diff --git a/homeassistant/components/facebox/image_processing.py b/homeassistant/components/facebox/image_processing.py index ee6e4d8a6fa50e..6a460ac305be82 100644 --- a/homeassistant/components/facebox/image_processing.py +++ b/homeassistant/components/facebox/image_processing.py @@ -15,6 +15,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_ID, ATTR_NAME, CONF_IP_ADDRESS, CONF_PASSWORD, @@ -34,7 +35,6 @@ ATTR_BOUNDING_BOX = "bounding_box" ATTR_CLASSIFIER = "classifier" ATTR_IMAGE_ID = "image_id" -ATTR_ID = "id" ATTR_MATCHED = "matched" FACEBOX_NAME = "name" CLASSIFIER = "facebox" diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 00893f8340197e..0cb1649dfc55af 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -16,7 +16,6 @@ ATTR_NAME = "name" ATTR_PANELS = "panels" ATTR_PASSWORD = "password" -ATTR_SERVICE = "service" ATTR_SNAPSHOT = "snapshot" ATTR_TITLE = "title" ATTR_USERNAME = "username" diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index f3337254f1a9b4..cda05eccbecd56 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -6,17 +6,10 @@ from aiohttp.web_exceptions import HTTPServiceUnavailable from homeassistant.components.http import HomeAssistantView -from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.const import ATTR_SERVICE, EVENT_HOMEASSISTANT_START from homeassistant.core import callback -from .const import ( - ATTR_ADDON, - ATTR_CONFIG, - ATTR_DISCOVERY, - ATTR_NAME, - ATTR_SERVICE, - ATTR_UUID, -) +from .const import ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_NAME, ATTR_UUID from .handler import HassioAPIError _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index 65e5ade7d1df27..a1e13658d20113 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -5,6 +5,7 @@ from homematicip.aio.device import AsyncDevice from homematicip.aio.group import AsyncGroup +from homeassistant.const import ATTR_ID from homeassistant.core import callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import Entity @@ -19,7 +20,6 @@ ATTR_CONFIG_PENDING = "config_pending" ATTR_CONNECTION_TYPE = "connection_type" ATTR_DUTY_CYCLE_REACHED = "duty_cycle_reached" -ATTR_ID = "id" ATTR_IS_GROUP = "is_group" # RSSI HAP -> Device ATTR_RSSI_DEVICE = "rssi_device" diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index ed1c5f0a8801dc..4c3650ef121dd9 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -56,7 +56,6 @@ DEFAULT_DISCOVERY = True DEFAULT_WEBHOOKS = False -ATTR_ID = "id" ATTR_PSEUDO = "pseudo" ATTR_NAME = "name" ATTR_EVENT_TYPE = "event_type" diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 582fce8985c608..309451fd982d17 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -1,13 +1,13 @@ """The Netatmo integration.""" import logging +from homeassistant.const import ATTR_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( ATTR_EVENT_TYPE, ATTR_FACE_URL, - ATTR_ID, ATTR_IS_KNOWN, ATTR_NAME, ATTR_PERSONS, diff --git a/homeassistant/components/nightscout/const.py b/homeassistant/components/nightscout/const.py index 4bb96a94c2971f..7e47f7ff49d4a2 100644 --- a/homeassistant/components/nightscout/const.py +++ b/homeassistant/components/nightscout/const.py @@ -3,6 +3,5 @@ DOMAIN = "nightscout" ATTR_DEVICE = "device" -ATTR_DATE = "date" ATTR_DELTA = "delta" ATTR_DIRECTION = "direction" diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index f4ff14d7b2a7ea..efa625577d9353 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -8,10 +8,11 @@ from py_nightscout import Api as NightscoutAPI from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_DATE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity -from .const import ATTR_DATE, ATTR_DELTA, ATTR_DEVICE, ATTR_DIRECTION, DOMAIN +from .const import ATTR_DELTA, ATTR_DEVICE, ATTR_DIRECTION, DOMAIN SCAN_INTERVAL = timedelta(minutes=1) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 8009d79b2240cc..44a17acaecfb35 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.switch import SwitchEntity -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, ATTR_ID from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_platform @@ -67,7 +67,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_DURATION = "duration" -ATTR_ID = "id" ATTR_PERCENT = "percent" ATTR_SCHEDULE_SUMMARY = "Summary" ATTR_SCHEDULE_ENABLED = "Enabled" diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 0fe4e87f863429..7a04fb6a8ae8d4 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + ATTR_ID, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_MAXIMUM, @@ -21,7 +22,6 @@ CONF_SORT_BY = "sort_by" CONF_SUBREDDITS = "subreddits" -ATTR_ID = "id" ATTR_BODY = "body" ATTR_COMMENTS_NUMBER = "comms_num" ATTR_CREATED = "created" diff --git a/homeassistant/components/traccar/__init__.py b/homeassistant/components/traccar/__init__.py index c19a9cdd27e895..cc598a9851b132 100644 --- a/homeassistant/components/traccar/__init__.py +++ b/homeassistant/components/traccar/__init__.py @@ -3,7 +3,12 @@ import voluptuous as vol from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER -from homeassistant.const import CONF_WEBHOOK_ID, HTTP_OK, HTTP_UNPROCESSABLE_ENTITY +from homeassistant.const import ( + ATTR_ID, + CONF_WEBHOOK_ID, + HTTP_OK, + HTTP_UNPROCESSABLE_ENTITY, +) from homeassistant.helpers import config_entry_flow import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -13,7 +18,6 @@ ATTR_ALTITUDE, ATTR_BATTERY, ATTR_BEARING, - ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_SPEED, diff --git a/homeassistant/components/traccar/const.py b/homeassistant/components/traccar/const.py index 56c0ab5ba1d5d6..06dd368b6a3e25 100644 --- a/homeassistant/components/traccar/const.py +++ b/homeassistant/components/traccar/const.py @@ -12,7 +12,6 @@ ATTR_BEARING = "bearing" ATTR_CATEGORY = "category" ATTR_GEOFENCE = "geofence" -ATTR_ID = "id" ATTR_LATITUDE = "lat" ATTR_LONGITUDE = "lon" ATTR_MOTION = "motion" diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 2b149fcac26630..9fd98de42dfb76 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -11,6 +11,7 @@ from homeassistant.components.geo_location import PLATFORM_SCHEMA, GeolocationEvent from homeassistant.const import ( ATTR_ATTRIBUTION, + ATTR_TIME, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, @@ -30,7 +31,6 @@ ATTR_MAGNITUDE = "magnitude" ATTR_PLACE = "place" ATTR_STATUS = "status" -ATTR_TIME = "time" ATTR_TYPE = "type" ATTR_UPDATED = "updated" diff --git a/homeassistant/components/watson_tts/tts.py b/homeassistant/components/watson_tts/tts.py index 9b2af2ea7fe5ac..ad989ec39fcbfb 100644 --- a/homeassistant/components/watson_tts/tts.py +++ b/homeassistant/components/watson_tts/tts.py @@ -8,7 +8,6 @@ CONF_URL = "watson_url" CONF_APIKEY = "watson_apikey" -ATTR_CREDENTIALS = "credentials" DEFAULT_URL = "https://stream.watsonplatform.net/text-to-speech/api" diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 9c8c245e910413..f6edb2f4596e44 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -12,7 +12,7 @@ from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry @@ -24,7 +24,6 @@ ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DEVICE_ID, - ATTR_DOMAIN, ATTR_ENDPOINT, ATTR_HOME_ID, ATTR_LABEL, diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index dc2ffaeaa20471..9905eba06937ba 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -28,7 +28,6 @@ ATTR_COMMAND_CLASS = "command_class" ATTR_COMMAND_CLASS_NAME = "command_class_name" ATTR_TYPE = "type" -ATTR_DOMAIN = "domain" ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index 48ec378689ed13..072e222c19430b 100644 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -2,11 +2,11 @@ from unittest.mock import AsyncMock, Mock, call, patch from homeassistant.components import dynalite +from homeassistant.const import ATTR_SERVICE from homeassistant.helpers import entity_registry from tests.common import MockConfigEntry -ATTR_SERVICE = "service" ATTR_METHOD = "method" ATTR_ARGS = "args" diff --git a/tests/components/nightscout/test_sensor.py b/tests/components/nightscout/test_sensor.py index 3df98a2595a742..5e73c75d93c619 100644 --- a/tests/components/nightscout/test_sensor.py +++ b/tests/components/nightscout/test_sensor.py @@ -1,12 +1,11 @@ """The sensor tests for the Nightscout platform.""" from homeassistant.components.nightscout.const import ( - ATTR_DATE, ATTR_DELTA, ATTR_DEVICE, ATTR_DIRECTION, ) -from homeassistant.const import ATTR_ICON, STATE_UNAVAILABLE +from homeassistant.const import ATTR_DATE, ATTR_ICON, STATE_UNAVAILABLE from tests.components.nightscout import ( GLUCOSE_READINGS, From c8ffac20b9b717b704c3c8a9d80e6fdc6b088a22 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Feb 2021 16:26:46 +0100 Subject: [PATCH 0681/1818] Add name to services (#46905) --- homeassistant/components/light/services.yaml | 13 ++++++++++--- homeassistant/helpers/service.py | 6 ++++-- script/hassfest/services.py | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index 2161ed3f81da37..4f5d74cbbbb6ec 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -1,7 +1,10 @@ # Describes the format for available light services turn_on: - description: Turn a light on + name: Turn on lights + description: > + Turn on one or more lights and adjust properties of the light, even when + they are turned on already. target: fields: transition: @@ -311,7 +314,8 @@ turn_on: text: turn_off: - description: Turn a light off + name: Turn off lights + description: Turns off one or more lights. target: fields: transition: @@ -340,7 +344,10 @@ turn_off: - short toggle: - description: Toggles a light + name: Toggle lights + description: > + Toggles one or more lights, from on to off, or, off to on, based on their + current state. target: fields: transition: diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 01fb76ec1bc579..a55ba8a84af54e 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -449,6 +449,7 @@ async def async_get_all_descriptions( # positives for things like scripts, that register as a service description = { + "name": yaml_description.get("name", ""), "description": yaml_description.get("description", ""), "fields": yaml_description.get("fields", {}), } @@ -472,8 +473,9 @@ def async_set_service_schema( hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) description = { - "description": schema.get("description") or "", - "fields": schema.get("fields") or {}, + "name": schema.get("name", ""), + "description": schema.get("description", ""), + "fields": schema.get("fields", {}), } hass.data[SERVICE_DESCRIPTION_CACHE][f"{domain}.{service}"] = description diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 9037b0bb45e55e..62e9b2f88a1ef1 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -37,6 +37,7 @@ def exists(value): SERVICE_SCHEMA = vol.Schema( { vol.Required("description"): str, + vol.Optional("name"): str, vol.Optional("target"): vol.Any( selector.TargetSelector.CONFIG_SCHEMA, None # pylint: disable=no-member ), From 692942b39999c9cf071ecb4447f1609c270884b4 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 22 Feb 2021 18:50:01 +0100 Subject: [PATCH 0682/1818] Add service names to Netatmo services (#46909) --- homeassistant/components/netatmo/services.yaml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/netatmo/services.yaml b/homeassistant/components/netatmo/services.yaml index 11f83830dff881..7005d26c326ba6 100644 --- a/homeassistant/components/netatmo/services.yaml +++ b/homeassistant/components/netatmo/services.yaml @@ -1,6 +1,7 @@ # Describes the format for available Netatmo services set_camera_light: - description: Set the camera light mode + name: Set camera light mode + description: Sets the light mode for a Netatmo Outdoor camera light. target: entity: integration: netatmo @@ -19,7 +20,8 @@ set_camera_light: - "auto" set_schedule: - description: Set the heating schedule + name: Set heating schedule + description: Set the heating schedule for Netatmo climate device. The schedule name must match a schedule configured at Netatmo. target: entity: integration: netatmo @@ -33,7 +35,8 @@ set_schedule: text: set_persons_home: - description: Set a list of persons as at home. Person's name must match a name known by the Welcome Camera + name: Set persons at home + description: Set a list of persons as at home. Person's name must match a name known by the Netatmo Indoor (Welcome) Camera. target: entity: integration: netatmo @@ -47,7 +50,8 @@ set_persons_home: text: set_person_away: - description: Set a person away. If no person is set the home will be marked as empty. Person's name must match a name known by the Welcome Camera + name: Set person away + description: Set a person as away. If no person is set the home will be marked as empty. Person's name must match a name known by the Netatmo Indoor (Welcome) Camera. target: entity: integration: netatmo @@ -60,7 +64,9 @@ set_person_away: text: register_webhook: - description: Register webhook + name: Register webhook + description: Register the webhook to the Netatmo backend. unregister_webhook: - description: Unregister webhook + name: Unregister webhook + description: Unregister the webhook from the Netatmo backend. From 6e10b39d67e36e45d43846f9d1246476f99a22d0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 22 Feb 2021 12:54:06 -0500 Subject: [PATCH 0683/1818] add name and target filter to zwave_js lock services.yaml (#46914) --- homeassistant/components/zwave_js/services.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index edb1f8b1ba4ee2..dd66a0c7f29431 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -1,8 +1,12 @@ # Describes the format for available Z-Wave services clear_lock_usercode: - description: Clear a usercode from lock + name: Clear a usercode from a lock + description: Clear a usercode from a lock target: + entity: + domain: lock + integration: zwave_js fields: code_slot: name: Code slot @@ -13,8 +17,12 @@ clear_lock_usercode: text: set_lock_usercode: - description: Set a usercode to lock + name: Set a usercode on a lock + description: Set a usercode on a lock target: + entity: + domain: lock + integration: zwave_js fields: code_slot: name: Code slot From 5907129b25a3193d157994fdde3d45525e076588 Mon Sep 17 00:00:00 2001 From: Michal Knizek Date: Mon, 22 Feb 2021 19:30:23 +0100 Subject: [PATCH 0684/1818] Increase tado API polling interval to 5 minutes (#46915) Polling interval of 15 seconds causes high load on tado servers and does not provide enough value to warrant it. tado plans to introduce a rate limit to prevent such misuse of the API, therefore the polling interval needs to be increased to make sure the integration works well in the future. --- homeassistant/components/tado/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index e88fb4c60b8267..c7fb180e6d88ee 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -33,8 +33,8 @@ TADO_COMPONENTS = ["binary_sensor", "sensor", "climate", "water_heater"] -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) -SCAN_INTERVAL = timedelta(seconds=15) +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=4) +SCAN_INTERVAL = timedelta(minutes=5) CONFIG_SCHEMA = cv.deprecated(DOMAIN) From 668574c48f99041ec56d626cc856b167f39dedb1 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 22 Feb 2021 13:31:18 -0500 Subject: [PATCH 0685/1818] Add name and target filter to vizio entity service (#46916) * Add name and target filter to vizio entity service * Update homeassistant/components/vizio/services.yaml Co-authored-by: Franck Nijhof * add selectors * Update homeassistant/components/vizio/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/vizio/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/vizio/services.yaml Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- homeassistant/components/vizio/services.yaml | 28 +++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/vizio/services.yaml b/homeassistant/components/vizio/services.yaml index 50bde6cab788dd..a2981fa32ad1cb 100644 --- a/homeassistant/components/vizio/services.yaml +++ b/homeassistant/components/vizio/services.yaml @@ -1,15 +1,29 @@ update_setting: - description: Update the value of a setting on a particular Vizio media player device. + name: Update a Vizio media player setting + description: Update the value of a setting on a Vizio media player device. + target: + entity: + integration: vizio + domain: media_player fields: - entity_id: - description: Name of an entity to send command to. - example: "media_player.vizio_smartcast" setting_type: - description: The type of setting to be changed. Available types are listed in the `setting_types` property. + name: Setting type + description: The type of setting to be changed. Available types are listed in the 'setting_types' property. + required: true example: "audio" + selector: + text: setting_name: - description: The name of the setting to be changed. Available settings for a given setting_type are listed in the `_settings` property. + name: Setting name + description: The name of the setting to be changed. Available settings for a given setting_type are listed in the '_settings' property. + required: true example: "eq" + selector: + text: new_value: - description: The new value for the setting + name: New value + description: The new value for the setting. + required: true example: "Music" + selector: + text: From e70d896e1bfe56cfd6aa90cb85200e022ec92474 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Mon, 22 Feb 2021 11:53:57 -0700 Subject: [PATCH 0686/1818] Add litterrobot integration (#45886) --- CODEOWNERS | 1 + .../components/litterrobot/__init__.py | 54 ++++++++ .../components/litterrobot/config_flow.py | 51 +++++++ homeassistant/components/litterrobot/const.py | 2 + homeassistant/components/litterrobot/hub.py | 122 +++++++++++++++++ .../components/litterrobot/manifest.json | 8 ++ .../components/litterrobot/strings.json | 20 +++ .../litterrobot/translations/en.json | 20 +++ .../components/litterrobot/vacuum.py | 127 ++++++++++++++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/litterrobot/__init__.py | 1 + tests/components/litterrobot/common.py | 24 ++++ tests/components/litterrobot/conftest.py | 35 +++++ .../litterrobot/test_config_flow.py | 92 +++++++++++++ tests/components/litterrobot/test_init.py | 20 +++ tests/components/litterrobot/test_vacuum.py | 92 +++++++++++++ 18 files changed, 676 insertions(+) create mode 100644 homeassistant/components/litterrobot/__init__.py create mode 100644 homeassistant/components/litterrobot/config_flow.py create mode 100644 homeassistant/components/litterrobot/const.py create mode 100644 homeassistant/components/litterrobot/hub.py create mode 100644 homeassistant/components/litterrobot/manifest.json create mode 100644 homeassistant/components/litterrobot/strings.json create mode 100644 homeassistant/components/litterrobot/translations/en.json create mode 100644 homeassistant/components/litterrobot/vacuum.py create mode 100644 tests/components/litterrobot/__init__.py create mode 100644 tests/components/litterrobot/common.py create mode 100644 tests/components/litterrobot/conftest.py create mode 100644 tests/components/litterrobot/test_config_flow.py create mode 100644 tests/components/litterrobot/test_init.py create mode 100644 tests/components/litterrobot/test_vacuum.py diff --git a/CODEOWNERS b/CODEOWNERS index a9d4ce63209bce..b20b489ac6dc5a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -253,6 +253,7 @@ homeassistant/components/launch_library/* @ludeeus homeassistant/components/lcn/* @alengwenus homeassistant/components/life360/* @pnbruckner homeassistant/components/linux_battery/* @fabaff +homeassistant/components/litterrobot/* @natekspencer homeassistant/components/local_ip/* @issacg homeassistant/components/logger/* @home-assistant/core homeassistant/components/logi_circle/* @evanjd diff --git a/homeassistant/components/litterrobot/__init__.py b/homeassistant/components/litterrobot/__init__.py new file mode 100644 index 00000000000000..bf43d5c465eeda --- /dev/null +++ b/homeassistant/components/litterrobot/__init__.py @@ -0,0 +1,54 @@ +"""The Litter-Robot integration.""" +import asyncio + +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import DOMAIN +from .hub import LitterRobotHub + +PLATFORMS = ["vacuum"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Litter-Robot component.""" + hass.data.setdefault(DOMAIN, {}) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Litter-Robot from a config entry.""" + hub = hass.data[DOMAIN][entry.entry_id] = LitterRobotHub(hass, entry.data) + try: + await hub.login(load_robots=True) + except LitterRobotLoginException: + return False + except LitterRobotException as ex: + raise ConfigEntryNotReady from ex + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py new file mode 100644 index 00000000000000..d6c92d8dad64b3 --- /dev/null +++ b/homeassistant/components/litterrobot/config_flow.py @@ -0,0 +1,51 @@ +"""Config flow for Litter-Robot integration.""" +import logging + +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +from .const import DOMAIN # pylint:disable=unused-import +from .hub import LitterRobotHub + +_LOGGER = logging.getLogger(__name__) + +STEP_USER_DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Litter-Robot.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + + if user_input is not None: + for entry in self._async_current_entries(): + if entry.data[CONF_USERNAME] == user_input[CONF_USERNAME]: + return self.async_abort(reason="already_configured") + + hub = LitterRobotHub(self.hass, user_input) + try: + await hub.login() + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + except LitterRobotLoginException: + errors["base"] = "invalid_auth" + except LitterRobotException: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/litterrobot/const.py b/homeassistant/components/litterrobot/const.py new file mode 100644 index 00000000000000..5ac889d9b738a9 --- /dev/null +++ b/homeassistant/components/litterrobot/const.py @@ -0,0 +1,2 @@ +"""Constants for the Litter-Robot integration.""" +DOMAIN = "litterrobot" diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py new file mode 100644 index 00000000000000..0d0559140c7431 --- /dev/null +++ b/homeassistant/components/litterrobot/hub.py @@ -0,0 +1,122 @@ +"""A wrapper 'hub' for the Litter-Robot API and base entity for common attributes.""" +from datetime import time, timedelta +import logging +from types import MethodType +from typing import Any, Optional + +from pylitterbot import Account, Robot +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException + +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) +import homeassistant.util.dt as dt_util + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +REFRESH_WAIT_TIME = 12 +UPDATE_INTERVAL = 10 + + +class LitterRobotHub: + """A Litter-Robot hub wrapper class.""" + + def __init__(self, hass: HomeAssistant, data: dict): + """Initialize the Litter-Robot hub.""" + self._data = data + self.account = None + self.logged_in = False + + async def _async_update_data(): + """Update all device states from the Litter-Robot API.""" + await self.account.refresh_robots() + return True + + self.coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=DOMAIN, + update_method=_async_update_data, + update_interval=timedelta(seconds=UPDATE_INTERVAL), + ) + + async def login(self, load_robots: bool = False): + """Login to Litter-Robot.""" + self.logged_in = False + self.account = Account() + try: + await self.account.connect( + username=self._data[CONF_USERNAME], + password=self._data[CONF_PASSWORD], + load_robots=load_robots, + ) + self.logged_in = True + return self.logged_in + except LitterRobotLoginException as ex: + _LOGGER.error("Invalid credentials") + raise ex + except LitterRobotException as ex: + _LOGGER.error("Unable to connect to Litter-Robot API") + raise ex + + +class LitterRobotEntity(CoordinatorEntity): + """Generic Litter-Robot entity representing common data and methods.""" + + def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(hub.coordinator) + self.robot = robot + self.entity_type = entity_type if entity_type else "" + self.hub = hub + + @property + def name(self): + """Return the name of this entity.""" + return f"{self.robot.name} {self.entity_type}" + + @property + def unique_id(self): + """Return a unique ID.""" + return f"{self.robot.serial}-{self.entity_type}" + + @property + def device_info(self): + """Return the device information for a Litter-Robot.""" + model = "Litter-Robot 3 Connect" + if not self.robot.serial.startswith("LR3C"): + model = "Other Litter-Robot Connected Device" + return { + "identifiers": {(DOMAIN, self.robot.serial)}, + "name": self.robot.name, + "manufacturer": "Litter-Robot", + "model": model, + } + + async def perform_action_and_refresh(self, action: MethodType, *args: Any): + """Perform an action and initiates a refresh of the robot data after a few seconds.""" + await action(*args) + async_call_later( + self.hass, REFRESH_WAIT_TIME, self.hub.coordinator.async_request_refresh + ) + + @staticmethod + def parse_time_at_default_timezone(time_str: str) -> Optional[time]: + """Parse a time string and add default timezone.""" + parsed_time = dt_util.parse_time(time_str) + + if parsed_time is None: + return None + + return time( + hour=parsed_time.hour, + minute=parsed_time.minute, + second=parsed_time.second, + tzinfo=dt_util.DEFAULT_TIME_ZONE, + ) diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json new file mode 100644 index 00000000000000..1c6ac7274bf657 --- /dev/null +++ b/homeassistant/components/litterrobot/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "litterrobot", + "name": "Litter-Robot", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/litterrobot", + "requirements": ["pylitterbot==2021.2.5"], + "codeowners": ["@natekspencer"] +} diff --git a/homeassistant/components/litterrobot/strings.json b/homeassistant/components/litterrobot/strings.json new file mode 100644 index 00000000000000..96dc8b371d143c --- /dev/null +++ b/homeassistant/components/litterrobot/strings.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "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%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/litterrobot/translations/en.json b/homeassistant/components/litterrobot/translations/en.json new file mode 100644 index 00000000000000..b3fc76ae458f25 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py new file mode 100644 index 00000000000000..a57c1ffead513e --- /dev/null +++ b/homeassistant/components/litterrobot/vacuum.py @@ -0,0 +1,127 @@ +"""Support for Litter-Robot "Vacuum".""" +from pylitterbot import Robot + +from homeassistant.components.vacuum import ( + STATE_CLEANING, + STATE_DOCKED, + STATE_ERROR, + SUPPORT_SEND_COMMAND, + SUPPORT_START, + SUPPORT_STATE, + SUPPORT_STATUS, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + VacuumEntity, +) +from homeassistant.const import STATE_OFF + +from .const import DOMAIN +from .hub import LitterRobotEntity + +SUPPORT_LITTERROBOT = ( + SUPPORT_SEND_COMMAND + | SUPPORT_START + | SUPPORT_STATE + | SUPPORT_STATUS + | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON +) +TYPE_LITTER_BOX = "Litter Box" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Litter-Robot cleaner using config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + entities = [] + for robot in hub.account.robots: + entities.append(LitterRobotCleaner(robot, TYPE_LITTER_BOX, hub)) + + if entities: + async_add_entities(entities, True) + + +class LitterRobotCleaner(LitterRobotEntity, VacuumEntity): + """Litter-Robot "Vacuum" Cleaner.""" + + @property + def supported_features(self): + """Flag cleaner robot features that are supported.""" + return SUPPORT_LITTERROBOT + + @property + def state(self): + """Return the state of the cleaner.""" + switcher = { + Robot.UnitStatus.CCP: STATE_CLEANING, + Robot.UnitStatus.EC: STATE_CLEANING, + Robot.UnitStatus.CCC: STATE_DOCKED, + Robot.UnitStatus.CST: STATE_DOCKED, + Robot.UnitStatus.DF1: STATE_DOCKED, + Robot.UnitStatus.DF2: STATE_DOCKED, + Robot.UnitStatus.RDY: STATE_DOCKED, + Robot.UnitStatus.OFF: STATE_OFF, + } + + return switcher.get(self.robot.unit_status, STATE_ERROR) + + @property + def error(self): + """Return the error associated with the current state, if any.""" + return self.robot.unit_status.value + + @property + def status(self): + """Return the status of the cleaner.""" + return f"{self.robot.unit_status.value}{' (Sleeping)' if self.robot.is_sleeping else ''}" + + async def async_turn_on(self, **kwargs): + """Turn the cleaner on, starting a clean cycle.""" + await self.perform_action_and_refresh(self.robot.set_power_status, True) + + async def async_turn_off(self, **kwargs): + """Turn the unit off, stopping any cleaning in progress as is.""" + await self.perform_action_and_refresh(self.robot.set_power_status, False) + + async def async_start(self): + """Start a clean cycle.""" + await self.perform_action_and_refresh(self.robot.start_cleaning) + + async def async_send_command(self, command, params=None, **kwargs): + """Send command. + + Available commands: + - reset_waste_drawer + * params: none + - set_sleep_mode + * params: + - enabled: bool + - sleep_time: str (optional) + + """ + if command == "reset_waste_drawer": + # Normally we need to request a refresh of data after a command is sent. + # However, the API for resetting the waste drawer returns a refreshed + # data set for the robot. Thus, we only need to tell hass to update the + # state of devices associated with this robot. + await self.robot.reset_waste_drawer() + self.hub.coordinator.async_set_updated_data(True) + elif command == "set_sleep_mode": + await self.perform_action_and_refresh( + self.robot.set_sleep_mode, + params.get("enabled"), + self.parse_time_at_default_timezone(params.get("sleep_time")), + ) + else: + raise NotImplementedError() + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return { + "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, + "is_sleeping": self.robot.is_sleeping, + "power_status": self.robot.power_status, + "unit_status_code": self.robot.unit_status.name, + "last_seen": self.robot.last_seen, + } diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 8e8949e5788c47..36c22262ef4d86 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -121,6 +121,7 @@ "kulersky", "life360", "lifx", + "litterrobot", "local_ip", "locative", "logi_circle", diff --git a/requirements_all.txt b/requirements_all.txt index b98cbde46bca68..55ea1510b59830 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1503,6 +1503,9 @@ pylibrespot-java==0.1.0 # homeassistant.components.litejet pylitejet==0.1 +# homeassistant.components.litterrobot +pylitterbot==2021.2.5 + # homeassistant.components.loopenergy pyloopenergy==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b1beff34323421..b590a8c50b3b5b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -793,6 +793,9 @@ pylibrespot-java==0.1.0 # homeassistant.components.litejet pylitejet==0.1 +# homeassistant.components.litterrobot +pylitterbot==2021.2.5 + # homeassistant.components.lutron_caseta pylutron-caseta==0.9.0 diff --git a/tests/components/litterrobot/__init__.py b/tests/components/litterrobot/__init__.py new file mode 100644 index 00000000000000..a726736510035e --- /dev/null +++ b/tests/components/litterrobot/__init__.py @@ -0,0 +1 @@ +"""Tests for the Litter-Robot Component.""" diff --git a/tests/components/litterrobot/common.py b/tests/components/litterrobot/common.py new file mode 100644 index 00000000000000..ed893a3a756865 --- /dev/null +++ b/tests/components/litterrobot/common.py @@ -0,0 +1,24 @@ +"""Common utils for Litter-Robot tests.""" +from homeassistant.components.litterrobot import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +BASE_PATH = "homeassistant.components.litterrobot" +CONFIG = {DOMAIN: {CONF_USERNAME: "user@example.com", CONF_PASSWORD: "password"}} + +ROBOT_NAME = "Test" +ROBOT_SERIAL = "LR3C012345" +ROBOT_DATA = { + "powerStatus": "AC", + "lastSeen": "2021-02-01T15:30:00.000000", + "cleanCycleWaitTimeMinutes": "7", + "unitStatus": "RDY", + "litterRobotNickname": ROBOT_NAME, + "cycleCount": "15", + "panelLockActive": "0", + "cyclesAfterDrawerFull": "0", + "litterRobotSerial": ROBOT_SERIAL, + "cycleCapacity": "30", + "litterRobotId": "a0123b4567cd8e", + "nightLightActive": "1", + "sleepModeActive": "112:50:19", +} diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py new file mode 100644 index 00000000000000..2f967d266bc24d --- /dev/null +++ b/tests/components/litterrobot/conftest.py @@ -0,0 +1,35 @@ +"""Configure pytest for Litter-Robot tests.""" +from unittest.mock import AsyncMock, MagicMock + +from pylitterbot import Robot +import pytest + +from homeassistant.components import litterrobot +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .common import ROBOT_DATA + + +def create_mock_robot(hass): + """Create a mock Litter-Robot device.""" + robot = Robot(data=ROBOT_DATA) + robot.start_cleaning = AsyncMock() + robot.set_power_status = AsyncMock() + robot.reset_waste_drawer = AsyncMock() + robot.set_sleep_mode = AsyncMock() + return robot + + +@pytest.fixture() +def mock_hub(hass): + """Mock a Litter-Robot hub.""" + hub = MagicMock( + hass=hass, + account=MagicMock(), + logged_in=True, + coordinator=MagicMock(spec=DataUpdateCoordinator), + spec=litterrobot.LitterRobotHub, + ) + hub.coordinator.last_update_success = True + hub.account.robots = [create_mock_robot(hass)] + return hub diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py new file mode 100644 index 00000000000000..fd88595d37e19e --- /dev/null +++ b/tests/components/litterrobot/test_config_flow.py @@ -0,0 +1,92 @@ +"""Test the Litter-Robot config flow.""" +from unittest.mock import patch + +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException + +from homeassistant import config_entries, setup + +from .common import CONF_USERNAME, CONFIG, DOMAIN + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + return_value=True, + ), patch( + "homeassistant.components.litterrobot.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.litterrobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG[DOMAIN] + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == CONFIG[DOMAIN][CONF_USERNAME] + assert result2["data"] == CONFIG[DOMAIN] + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + side_effect=LitterRobotLoginException, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG[DOMAIN] + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +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( + "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + side_effect=LitterRobotException, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG[DOMAIN] + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unknown_error(hass): + """Test we handle unknown error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG[DOMAIN] + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py new file mode 100644 index 00000000000000..1d0ed075cc77e7 --- /dev/null +++ b/tests/components/litterrobot/test_init.py @@ -0,0 +1,20 @@ +"""Test Litter-Robot setup process.""" +from homeassistant.components import litterrobot +from homeassistant.setup import async_setup_component + +from .common import CONFIG + +from tests.common import MockConfigEntry + + +async def test_unload_entry(hass): + """Test being able to unload an entry.""" + entry = MockConfigEntry( + domain=litterrobot.DOMAIN, + data=CONFIG[litterrobot.DOMAIN], + ) + entry.add_to_hass(hass) + + assert await async_setup_component(hass, litterrobot.DOMAIN, {}) is True + assert await litterrobot.async_unload_entry(hass, entry) + assert hass.data[litterrobot.DOMAIN] == {} diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py new file mode 100644 index 00000000000000..b47eff64e136e6 --- /dev/null +++ b/tests/components/litterrobot/test_vacuum.py @@ -0,0 +1,92 @@ +"""Test the Litter-Robot vacuum entity.""" +from datetime import timedelta +from unittest.mock import patch + +import pytest + +from homeassistant.components import litterrobot +from homeassistant.components.litterrobot.hub import REFRESH_WAIT_TIME +from homeassistant.components.vacuum import ( + ATTR_PARAMS, + DOMAIN as PLATFORM_DOMAIN, + SERVICE_SEND_COMMAND, + SERVICE_START, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_DOCKED, +) +from homeassistant.const import ATTR_COMMAND, ATTR_ENTITY_ID +from homeassistant.util.dt import utcnow + +from .common import CONFIG + +from tests.common import MockConfigEntry, async_fire_time_changed + +ENTITY_ID = "vacuum.test_litter_box" + + +async def setup_hub(hass, mock_hub): + """Load the Litter-Robot vacuum platform with the provided hub.""" + hass.config.components.add(litterrobot.DOMAIN) + entry = MockConfigEntry( + domain=litterrobot.DOMAIN, + data=CONFIG[litterrobot.DOMAIN], + ) + + with patch.dict(hass.data, {litterrobot.DOMAIN: {entry.entry_id: mock_hub}}): + await hass.config_entries.async_forward_entry_setup(entry, PLATFORM_DOMAIN) + await hass.async_block_till_done() + + +async def test_vacuum(hass, mock_hub): + """Tests the vacuum entity was set up.""" + await setup_hub(hass, mock_hub) + + vacuum = hass.states.get(ENTITY_ID) + assert vacuum is not None + assert vacuum.state == STATE_DOCKED + assert vacuum.attributes["is_sleeping"] is False + + +@pytest.mark.parametrize( + "service,command,extra", + [ + (SERVICE_START, "start_cleaning", None), + (SERVICE_TURN_OFF, "set_power_status", None), + (SERVICE_TURN_ON, "set_power_status", None), + ( + SERVICE_SEND_COMMAND, + "reset_waste_drawer", + {ATTR_COMMAND: "reset_waste_drawer"}, + ), + ( + SERVICE_SEND_COMMAND, + "set_sleep_mode", + { + ATTR_COMMAND: "set_sleep_mode", + ATTR_PARAMS: {"enabled": True, "sleep_time": "22:30"}, + }, + ), + ], +) +async def test_commands(hass, mock_hub, service, command, extra): + """Test sending commands to the vacuum.""" + await setup_hub(hass, mock_hub) + + vacuum = hass.states.get(ENTITY_ID) + assert vacuum is not None + assert vacuum.state == STATE_DOCKED + + data = {ATTR_ENTITY_ID: ENTITY_ID} + if extra: + data.update(extra) + + await hass.services.async_call( + PLATFORM_DOMAIN, + service, + data, + blocking=True, + ) + future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) + async_fire_time_changed(hass, future) + getattr(mock_hub.account.robots[0], command).assert_called_once() From fb32c2e3a8a7ea245916545573f1d15b86898f3a Mon Sep 17 00:00:00 2001 From: kpine Date: Mon, 22 Feb 2021 10:56:23 -0800 Subject: [PATCH 0687/1818] Test zwave_js GE 12730 fan controller device-specific discovery (#46840) * Add test for GE 12730 fan controller device-specific discovery * Adjust Co-authored-by: Martin Hjelmare --- tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_discovery.py | 13 +- .../fixtures/zwave_js/fan_ge_12730_state.json | 434 ++++++++++++++++++ 3 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/zwave_js/fan_ge_12730_state.json diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 02b11970376c83..2e856bde362037 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -170,6 +170,12 @@ def iblinds_v2_state_fixture(): return json.loads(load_fixture("zwave_js/cover_iblinds_v2_state.json")) +@pytest.fixture(name="ge_12730_state", scope="session") +def ge_12730_state_fixture(): + """Load the GE 12730 node state fixture data.""" + return json.loads(load_fixture("zwave_js/fan_ge_12730_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -373,3 +379,11 @@ def iblinds_cover_fixture(client, iblinds_v2_state): node = Node(client, iblinds_v2_state) client.driver.controller.nodes[node.node_id] = node return node + + +@pytest.fixture(name="ge_12730") +def ge_12730_fixture(client, ge_12730_state): + """Mock a GE 12730 fan controller node.""" + node = Node(client, ge_12730_state) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index f7d26f07d216ca..8f3dbce8dcaa83 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -3,7 +3,6 @@ async def test_iblinds_v2(hass, client, iblinds_v2, integration): """Test that an iBlinds v2.0 multilevel switch value is discovered as a cover.""" - node = iblinds_v2 assert node.device_class.specific == "Unused" @@ -12,3 +11,15 @@ async def test_iblinds_v2(hass, client, iblinds_v2, integration): state = hass.states.get("cover.window_blind_controller") assert state + + +async def test_ge_12730(hass, client, ge_12730, integration): + """Test GE 12730 Fan Controller v2.0 multilevel switch is discovered as a fan.""" + node = ge_12730 + assert node.device_class.specific == "Multilevel Power Switch" + + state = hass.states.get("light.in_wall_smart_fan_control") + assert not state + + state = hass.states.get("fan.in_wall_smart_fan_control") + assert state diff --git a/tests/fixtures/zwave_js/fan_ge_12730_state.json b/tests/fixtures/zwave_js/fan_ge_12730_state.json new file mode 100644 index 00000000000000..692cc75fe99585 --- /dev/null +++ b/tests/fixtures/zwave_js/fan_ge_12730_state.json @@ -0,0 +1,434 @@ +{ + "nodeId": 24, + "index": 0, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Multilevel Switch", + "specific": "Multilevel Power Switch", + "mandatorySupportedCCs": [ + "Basic", + "Multilevel Switch", + "All Switch" + ], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 99, + "productId": 12340, + "productType": 18756, + "firmwareVersion": "3.10", + "deviceConfig": { + "manufacturerId": 99, + "manufacturer": "GE/Jasco", + "label": "12730 / ZW4002", + "description": "In-Wall Smart Fan Control", + "devices": [ + { + "productType": "0x4944", + "productId": "0x3034" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + } + }, + "label": "12730 / ZW4002", + "neighbors": [ + 1, + 12 + ], + "interviewAttempts": 1, + "interviewStage": 7, + "endpoints": [ + { + "nodeId": 24, + "index": 0 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "LED Light", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 2, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "LED on when light off", + "1": "LED on when light on", + "2": "LED always off" + }, + "label": "LED Light", + "description": "Sets when the LED on the switch is lit.", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Invert Switch", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "No", + "1": "Yes" + }, + "label": "Invert Switch", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Dim Rate Steps (Z-Wave Command)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Dim Rate Steps (Z-Wave Command)", + "description": "Number of steps or levels", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Dim Rate Timing (Z-Wave)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 1, + "max": 255, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Dim Rate Timing (Z-Wave)", + "description": "Timing of steps or levels", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 9, + "propertyName": "Dim Rate Steps (Manual)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 99, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Dim Rate Steps (Manual)", + "description": "Number of steps or levels", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 10, + "propertyName": "Dim Rate Timing (Manual)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 1, + "max": 255, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Dim Rate Timing (Manual)", + "description": "Timing of steps", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 11, + "propertyName": "Dim Rate Steps (All-On/All-Off)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 99, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Dim Rate Steps (All-On/All-Off)", + "description": "Number of steps or levels", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 12, + "propertyName": "Dim Rate Timing (All-On/All-Off)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 1, + "max": 255, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Dim Rate Timing (All-On/All-Off)", + "description": "Timing of steps or levels", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 18756 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 12340 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "3.67" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "3.10" + ] + } + ] +} From f0c7aff2483f68112d625ca4ee627733261cd5f3 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Mon, 22 Feb 2021 15:12:00 -0500 Subject: [PATCH 0688/1818] Clean up Mitemp_bt constants (#46881) * Use core constants for acer_projector * Use core constants for mitemp_bt * remove acer changes --- homeassistant/components/mitemp_bt/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mitemp_bt/sensor.py b/homeassistant/components/mitemp_bt/sensor.py index 6b64c88c1cea14..244a0c410d57c5 100644 --- a/homeassistant/components/mitemp_bt/sensor.py +++ b/homeassistant/components/mitemp_bt/sensor.py @@ -12,6 +12,7 @@ CONF_MAC, CONF_MONITORED_CONDITIONS, CONF_NAME, + CONF_TIMEOUT, DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -34,7 +35,6 @@ CONF_CACHE = "cache_value" CONF_MEDIAN = "median" CONF_RETRIES = "retries" -CONF_TIMEOUT = "timeout" DEFAULT_ADAPTER = "hci0" DEFAULT_UPDATE_INTERVAL = 300 From 8ac9faef3b7959fa44beae109c4758029260f9f0 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 22 Feb 2021 21:36:38 +0100 Subject: [PATCH 0689/1818] Description tweaks for automation services (#46926) --- homeassistant/components/automation/services.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/automation/services.yaml b/homeassistant/components/automation/services.yaml index d8380914ce6171..95f2057f5ee0f1 100644 --- a/homeassistant/components/automation/services.yaml +++ b/homeassistant/components/automation/services.yaml @@ -16,16 +16,16 @@ turn_off: boolean: toggle: - description: Toggle an automation + description: Toggle (enable / disable) an automation target: trigger: - description: Trigger the action of an automation + description: Trigger the actions of an automation target: fields: skip_condition: name: Skip conditions - description: Whether or not the condition will be skipped. + description: Whether or not the conditions will be skipped. default: true example: true selector: From be33336d96887455d9bcef768cf9d9aea1f1558f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 22 Feb 2021 22:48:47 +0100 Subject: [PATCH 0690/1818] Update frontend to 20210222.0 (#46928) --- 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 7faac6c99cb346..ea46e0b2e07101 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==20210208.0" + "home-assistant-frontend==20210222.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5f2aa859e26323..fa5862b5c28ff2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210208.0 +home-assistant-frontend==20210222.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 55ea1510b59830..081881b250507e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210208.0 +home-assistant-frontend==20210222.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b590a8c50b3b5b..376eef402e43ad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210208.0 +home-assistant-frontend==20210222.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 04e07d8b2c3f2d65351d5606c7d7f770f59338a6 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Mon, 22 Feb 2021 17:03:38 -0500 Subject: [PATCH 0691/1818] Add get_config_parameters websocket command to zwave_js (#46463) * Add get_configuration_values websocket command to zwave_js * Tweak return value * Review comments and cleanup returned values * Update test * Rename to get_config_parameters * Add get_configuration_values websocket command to zwave_js * Rename to get_config_parameters * fix test * fix tests #2 * Add readable to metadata Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- homeassistant/components/zwave_js/api.py | 43 ++++++++++++++++++++++++ tests/components/zwave_js/test_api.py | 18 ++++++++++ 2 files changed, 61 insertions(+) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index bed2166a4da5cd..fb9282b4763efe 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -31,6 +31,7 @@ def async_register_api(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_stop_inclusion) websocket_api.async_register_command(hass, websocket_remove_node) websocket_api.async_register_command(hass, websocket_stop_exclusion) + websocket_api.async_register_command(hass, websocket_get_config_parameters) hass.http.register_view(DumpView) # type: ignore @@ -263,6 +264,48 @@ def node_removed(event: dict) -> None: ) +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zwave_js/get_config_parameters", + vol.Required(ENTRY_ID): str, + vol.Required(NODE_ID): int, + } +) +@callback +def websocket_get_config_parameters( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +) -> None: + """Get a list of configuration parameterss for a Z-Wave node.""" + entry_id = msg[ENTRY_ID] + node_id = msg[NODE_ID] + client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + node = client.driver.controller.nodes[node_id] + values = node.get_configuration_values() + result = {} + for value_id, zwave_value in values.items(): + metadata = zwave_value.metadata + result[value_id] = { + "property": zwave_value.property_, + "configuration_value_type": zwave_value.configuration_value_type.value, + "metadata": { + "description": metadata.description, + "label": metadata.label, + "type": metadata.type, + "min": metadata.min, + "max": metadata.max, + "unit": metadata.unit, + "writeable": metadata.writeable, + "readable": metadata.readable, + }, + "value": zwave_value.value, + } + connection.send_result( + msg[ID], + result, + ) + + class DumpView(HomeAssistantView): """View to dump the state of the Z-Wave JS server.""" diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index a36743421c95d3..7689f7140f4c56 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -41,6 +41,24 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): assert not result["is_secure"] assert result["status"] == 1 + # Test getting configuration parameter values + await ws_client.send_json( + { + ID: 4, + TYPE: "zwave_js/get_config_parameters", + ENTRY_ID: entry.entry_id, + NODE_ID: node.node_id, + } + ) + msg = await ws_client.receive_json() + result = msg["result"] + + assert len(result) == 61 + key = "52-112-0-2-00-00" + assert result[key]["property"] == 2 + assert result[key]["metadata"]["type"] == "number" + assert result[key]["configuration_value_type"] == "enumerated" + async def test_add_node( hass, integration, client, hass_ws_client, nortek_thermostat_added_event From 9d7c64ec1ae56df9dde005f87a4a620dbcccff7d Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 22 Feb 2021 17:42:12 -0500 Subject: [PATCH 0692/1818] Add missing required=true to code slot field in zwave_js.set_lock_usercode service (#46931) --- homeassistant/components/zwave_js/services.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index dd66a0c7f29431..d2f1c75b64e1af 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -27,6 +27,7 @@ set_lock_usercode: code_slot: name: Code slot description: Code slot to set the code. + required: true example: 1 selector: text: From 20ccec9aab7f7a42bfe0a10697adab52e34166ea Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 22 Feb 2021 18:35:19 -0500 Subject: [PATCH 0693/1818] Add zwave_js/get_log_config and zwave_js/update_log_config WS API commands (#46601) * Add zwave_js.update_log_config service * fix comment * reduce lines * move update_log_config from service to ws API call * fix docstring * Add zwave_js/get_log_config WS API command * resolve stale comments * remove transports since it will be removed from upstream PR * add support to update all log config parameters since they could be useful outside of the UI for advanced users * fix comment * switch to lambda instead of single line validator * fix rebase * re-add ATTR_DOMAIN --- homeassistant/components/zwave_js/api.py | 90 ++++++++++++ tests/components/zwave_js/test_api.py | 172 ++++++++++++++++++++++- 2 files changed, 261 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index fb9282b4763efe..a6cd8c50a7664a 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -1,26 +1,40 @@ """Websocket API for Z-Wave JS.""" +import dataclasses import json +from typing import Dict from aiohttp import hdrs, web, web_exceptions import voluptuous as vol from zwave_js_server import dump +from zwave_js_server.const import LogLevel +from zwave_js_server.model.log_config import LogConfig from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DATA_CLIENT, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY +# general API constants ID = "id" ENTRY_ID = "entry_id" NODE_ID = "node_id" TYPE = "type" +# constants for log config commands +CONFIG = "config" +LEVEL = "level" +LOG_TO_FILE = "log_to_file" +FILENAME = "filename" +ENABLED = "enabled" +FORCE_CONSOLE = "force_console" + @callback def async_register_api(hass: HomeAssistant) -> None: @@ -32,6 +46,8 @@ def async_register_api(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_remove_node) websocket_api.async_register_command(hass, websocket_stop_exclusion) websocket_api.async_register_command(hass, websocket_get_config_parameters) + websocket_api.async_register_command(hass, websocket_update_log_config) + websocket_api.async_register_command(hass, websocket_get_log_config) hass.http.register_view(DumpView) # type: ignore @@ -306,6 +322,80 @@ def websocket_get_config_parameters( ) +def convert_log_level_to_enum(value: str) -> LogLevel: + """Convert log level string to LogLevel enum.""" + return LogLevel[value.upper()] + + +def filename_is_present_if_logging_to_file(obj: Dict) -> Dict: + """Validate that filename is provided if log_to_file is True.""" + if obj.get(LOG_TO_FILE, False) and FILENAME not in obj: + raise vol.Invalid("`filename` must be provided if logging to file") + return obj + + +@websocket_api.require_admin # type: ignore +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zwave_js/update_log_config", + vol.Required(ENTRY_ID): str, + vol.Required(CONFIG): vol.All( + vol.Schema( + { + vol.Optional(ENABLED): cv.boolean, + vol.Optional(LEVEL): vol.All( + cv.string, + vol.Lower, + vol.In([log_level.name.lower() for log_level in LogLevel]), + lambda val: LogLevel[val.upper()], + ), + vol.Optional(LOG_TO_FILE): cv.boolean, + vol.Optional(FILENAME): cv.string, + vol.Optional(FORCE_CONSOLE): cv.boolean, + } + ), + cv.has_at_least_one_key( + ENABLED, FILENAME, FORCE_CONSOLE, LEVEL, LOG_TO_FILE + ), + filename_is_present_if_logging_to_file, + ), + }, +) +async def websocket_update_log_config( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +) -> None: + """Update the driver log config.""" + entry_id = msg[ENTRY_ID] + client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + result = await client.driver.async_update_log_config(LogConfig(**msg[CONFIG])) + connection.send_result( + msg[ID], + result, + ) + + +@websocket_api.require_admin # type: ignore +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zwave_js/get_log_config", + vol.Required(ENTRY_ID): str, + }, +) +async def websocket_get_log_config( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +) -> None: + """Cancel removing a node from the Z-Wave network.""" + entry_id = msg[ENTRY_ID] + client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + result = await client.driver.async_get_log_config() + connection.send_result( + msg[ID], + dataclasses.asdict(result), + ) + + class DumpView(HomeAssistantView): """View to dump the state of the Z-Wave JS server.""" diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 7689f7140f4c56..29a86d63c83002 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -2,9 +2,21 @@ import json from unittest.mock import patch +from zwave_js_server.const import LogLevel from zwave_js_server.event import Event -from homeassistant.components.zwave_js.api import ENTRY_ID, ID, NODE_ID, TYPE +from homeassistant.components.zwave_js.api import ( + CONFIG, + ENABLED, + ENTRY_ID, + FILENAME, + FORCE_CONSOLE, + ID, + LEVEL, + LOG_TO_FILE, + NODE_ID, + TYPE, +) from homeassistant.components.zwave_js.const import DOMAIN from homeassistant.helpers.device_registry import async_get_registry @@ -191,3 +203,161 @@ async def test_dump_view_invalid_entry_id(integration, hass_client): client = await hass_client() resp = await client.get("/api/zwave_js/dump/INVALID") assert resp.status == 400 + + +async def test_update_log_config(hass, client, integration, hass_ws_client): + """Test that the update_log_config WS API call works and that schema validation works.""" + entry = integration + ws_client = await hass_ws_client(hass) + + # Test we can set log level + client.async_send_command.return_value = {"success": True} + await ws_client.send_json( + { + ID: 1, + TYPE: "zwave_js/update_log_config", + ENTRY_ID: entry.entry_id, + CONFIG: {LEVEL: "Error"}, + } + ) + msg = await ws_client.receive_json() + assert msg["result"] + assert msg["success"] + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "update_log_config" + assert args["config"] == {"level": 0} + + client.async_send_command.reset_mock() + + # Test we can set logToFile to True + client.async_send_command.return_value = {"success": True} + await ws_client.send_json( + { + ID: 2, + TYPE: "zwave_js/update_log_config", + ENTRY_ID: entry.entry_id, + CONFIG: {LOG_TO_FILE: True, FILENAME: "/test"}, + } + ) + msg = await ws_client.receive_json() + assert msg["result"] + assert msg["success"] + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "update_log_config" + assert args["config"] == {"logToFile": True, "filename": "/test"} + + client.async_send_command.reset_mock() + + # Test all parameters + client.async_send_command.return_value = {"success": True} + await ws_client.send_json( + { + ID: 3, + TYPE: "zwave_js/update_log_config", + ENTRY_ID: entry.entry_id, + CONFIG: { + LEVEL: "Error", + LOG_TO_FILE: True, + FILENAME: "/test", + FORCE_CONSOLE: True, + ENABLED: True, + }, + } + ) + msg = await ws_client.receive_json() + assert msg["result"] + assert msg["success"] + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "update_log_config" + assert args["config"] == { + "level": 0, + "logToFile": True, + "filename": "/test", + "forceConsole": True, + "enabled": True, + } + + client.async_send_command.reset_mock() + + # Test error when setting unrecognized log level + await ws_client.send_json( + { + ID: 4, + TYPE: "zwave_js/update_log_config", + ENTRY_ID: entry.entry_id, + CONFIG: {LEVEL: "bad_log_level"}, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert "error" in msg and "value must be one of" in msg["error"]["message"] + + # Test error without service data + await ws_client.send_json( + { + ID: 5, + TYPE: "zwave_js/update_log_config", + ENTRY_ID: entry.entry_id, + CONFIG: {}, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert "error" in msg and "must contain at least one of" in msg["error"]["message"] + + # Test error if we set logToFile to True without providing filename + await ws_client.send_json( + { + ID: 6, + TYPE: "zwave_js/update_log_config", + ENTRY_ID: entry.entry_id, + CONFIG: {LOG_TO_FILE: True}, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert ( + "error" in msg + and "must be provided if logging to file" in msg["error"]["message"] + ) + + +async def test_get_log_config(hass, client, integration, hass_ws_client): + """Test that the get_log_config WS API call works.""" + entry = integration + ws_client = await hass_ws_client(hass) + + # Test we can get log configuration + client.async_send_command.return_value = { + "success": True, + "config": { + "enabled": True, + "level": 0, + "logToFile": False, + "filename": "/test.txt", + "forceConsole": False, + }, + } + await ws_client.send_json( + { + ID: 1, + TYPE: "zwave_js/get_log_config", + ENTRY_ID: entry.entry_id, + } + ) + msg = await ws_client.receive_json() + assert msg["result"] + assert msg["success"] + + log_config = msg["result"] + assert log_config["enabled"] + assert log_config["level"] == LogLevel.ERROR + assert log_config["log_to_file"] is False + assert log_config["filename"] == "/test.txt" + assert log_config["force_console"] is False From 580d25c622accb47a7387974c8579e3e9ee53cf0 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 23 Feb 2021 00:05:06 +0000 Subject: [PATCH 0694/1818] [ci skip] Translation update --- .../components/abode/translations/ru.json | 2 +- .../components/airnow/translations/ru.json | 2 +- .../components/apple_tv/translations/ru.json | 2 +- .../components/august/translations/ru.json | 2 +- .../components/axis/translations/ru.json | 2 +- .../azure_devops/translations/ru.json | 2 +- .../components/blink/translations/ru.json | 2 +- .../bmw_connected_drive/translations/ru.json | 2 +- .../components/bond/translations/ru.json | 2 +- .../cloudflare/translations/ru.json | 2 +- .../components/control4/translations/ru.json | 2 +- .../components/daikin/translations/ru.json | 2 +- .../devolo_home_control/translations/ru.json | 2 +- .../components/dexcom/translations/ru.json | 2 +- .../components/doorbird/translations/ru.json | 2 +- .../components/econet/translations/ru.json | 4 +- .../components/elkm1/translations/ru.json | 2 +- .../components/esphome/translations/ru.json | 2 +- .../fireservicerota/translations/ru.json | 2 +- .../flick_electric/translations/ru.json | 2 +- .../components/flo/translations/ru.json | 2 +- .../components/flume/translations/ru.json | 2 +- .../components/foscam/translations/ru.json | 2 +- .../components/fritzbox/translations/ru.json | 2 +- .../fritzbox_callmonitor/translations/ru.json | 2 +- .../garmin_connect/translations/ru.json | 2 +- .../components/gogogate2/translations/ru.json | 2 +- .../components/habitica/translations/ru.json | 2 +- .../components/hlk_sw16/translations/ru.json | 2 +- .../huawei_lte/translations/ru.json | 2 +- .../huisbaasje/translations/ru.json | 4 +- .../hvv_departures/translations/ru.json | 2 +- .../components/icloud/translations/ru.json | 2 +- .../components/isy994/translations/ru.json | 2 +- .../components/juicenet/translations/ru.json | 2 +- .../components/kmtronic/translations/ca.json | 21 +++++++++ .../components/kmtronic/translations/en.json | 21 +++++++++ .../components/kmtronic/translations/et.json | 21 +++++++++ .../components/kmtronic/translations/no.json | 21 +++++++++ .../components/kodi/translations/ru.json | 4 +- .../components/life360/translations/ru.json | 4 +- .../litterrobot/translations/ca.json | 20 +++++++++ .../litterrobot/translations/en.json | 2 +- .../litterrobot/translations/et.json | 20 +++++++++ .../logi_circle/translations/ru.json | 2 +- .../components/mazda/translations/ru.json | 2 +- .../components/melcloud/translations/ru.json | 2 +- .../components/mikrotik/translations/ru.json | 2 +- .../components/myq/translations/ru.json | 2 +- .../components/mysensors/translations/ru.json | 4 +- .../components/neato/translations/ru.json | 4 +- .../components/nexia/translations/ru.json | 2 +- .../nightscout/translations/ru.json | 2 +- .../components/notion/translations/ru.json | 2 +- .../components/nuheat/translations/ru.json | 2 +- .../components/nuki/translations/ru.json | 2 +- .../components/omnilogic/translations/ru.json | 2 +- .../ovo_energy/translations/ru.json | 2 +- .../components/plugwise/translations/ru.json | 2 +- .../components/poolsense/translations/ru.json | 2 +- .../components/powerwall/translations/ru.json | 2 +- .../components/rachio/translations/ru.json | 2 +- .../rainmachine/translations/ru.json | 2 +- .../components/ring/translations/ru.json | 2 +- .../components/risco/translations/ru.json | 2 +- .../translations/no.json | 21 +++++++++ .../translations/ru.json | 21 +++++++++ .../components/roon/translations/ru.json | 2 +- .../ruckus_unleashed/translations/ru.json | 2 +- .../components/sense/translations/ru.json | 2 +- .../components/sharkiq/translations/ru.json | 2 +- .../components/shelly/translations/ru.json | 2 +- .../simplisafe/translations/ru.json | 2 +- .../smart_meter_texas/translations/ru.json | 2 +- .../components/smarthab/translations/ru.json | 2 +- .../components/smarttub/translations/ru.json | 2 +- .../somfy_mylink/translations/ru.json | 2 +- .../components/sonarr/translations/ru.json | 2 +- .../components/spider/translations/ru.json | 2 +- .../squeezebox/translations/ru.json | 2 +- .../srp_energy/translations/ru.json | 2 +- .../components/subaru/translations/no.json | 44 +++++++++++++++++++ .../components/subaru/translations/ru.json | 44 +++++++++++++++++++ .../synology_dsm/translations/ru.json | 2 +- .../components/tado/translations/ru.json | 2 +- .../tellduslive/translations/ru.json | 2 +- .../components/tesla/translations/ru.json | 2 +- .../components/tile/translations/ru.json | 2 +- .../totalconnect/translations/ca.json | 17 ++++++- .../totalconnect/translations/et.json | 17 ++++++- .../totalconnect/translations/ru.json | 2 +- .../transmission/translations/ru.json | 2 +- .../components/tuya/translations/ru.json | 4 +- .../components/unifi/translations/ru.json | 2 +- .../components/upcloud/translations/ru.json | 2 +- .../components/vesync/translations/ru.json | 2 +- .../components/vilfo/translations/ru.json | 2 +- .../components/wolflink/translations/ru.json | 2 +- .../xiaomi_miio/translations/ca.json | 1 + .../xiaomi_miio/translations/en.json | 22 +++++++++- .../xiaomi_miio/translations/et.json | 1 + .../zoneminder/translations/ru.json | 4 +- 102 files changed, 401 insertions(+), 101 deletions(-) create mode 100644 homeassistant/components/kmtronic/translations/ca.json create mode 100644 homeassistant/components/kmtronic/translations/en.json create mode 100644 homeassistant/components/kmtronic/translations/et.json create mode 100644 homeassistant/components/kmtronic/translations/no.json create mode 100644 homeassistant/components/litterrobot/translations/ca.json create mode 100644 homeassistant/components/litterrobot/translations/et.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/no.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/ru.json create mode 100644 homeassistant/components/subaru/translations/no.json create mode 100644 homeassistant/components/subaru/translations/ru.json diff --git a/homeassistant/components/abode/translations/ru.json b/homeassistant/components/abode/translations/ru.json index 04efaa6e5194a5..f3804a840ab2e3 100644 --- a/homeassistant/components/abode/translations/ru.json +++ b/homeassistant/components/abode/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_mfa_code": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434 MFA." }, "step": { diff --git a/homeassistant/components/airnow/translations/ru.json b/homeassistant/components/airnow/translations/ru.json index 650633cc8167c1..9667accb7c4c61 100644 --- a/homeassistant/components/airnow/translations/ru.json +++ b/homeassistant/components/airnow/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_location": "\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.", "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/apple_tv/translations/ru.json b/homeassistant/components/apple_tv/translations/ru.json index e3f5804cebef5c..4ad9b9f52c762d 100644 --- a/homeassistant/components/apple_tv/translations/ru.json +++ b/homeassistant/components/apple_tv/translations/ru.json @@ -11,7 +11,7 @@ }, "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "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.", "no_usable_service": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u043c\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u0415\u0441\u043b\u0438 \u0412\u044b \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043b\u0438 \u044d\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0435\u0433\u043e.", "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/august/translations/ru.json b/homeassistant/components/august/translations/ru.json index 9ea0b531bf8b0c..97dba8fc758e55 100644 --- a/homeassistant/components/august/translations/ru.json +++ b/homeassistant/components/august/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index 6d979dc9de086d..1bf3e369b65a3d 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -9,7 +9,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.", "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "{name} ({host})", "step": { diff --git a/homeassistant/components/azure_devops/translations/ru.json b/homeassistant/components/azure_devops/translations/ru.json index 84e0fc93b466aa..4e59af2dd11a73 100644 --- a/homeassistant/components/azure_devops/translations/ru.json +++ b/homeassistant/components/azure_devops/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "project_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0435." }, "flow_title": "Azure DevOps: {project_url}", diff --git a/homeassistant/components/blink/translations/ru.json b/homeassistant/components/blink/translations/ru.json index 0e55fa716b92a7..0835ab5ac0af2e 100644 --- a/homeassistant/components/blink/translations/ru.json +++ b/homeassistant/components/blink/translations/ru.json @@ -6,7 +6,7 @@ "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_access_token": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/bmw_connected_drive/translations/ru.json b/homeassistant/components/bmw_connected_drive/translations/ru.json index 0840affcef4200..9ac76bbea9e35d 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ru.json +++ b/homeassistant/components/bmw_connected_drive/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/bond/translations/ru.json b/homeassistant/components/bond/translations/ru.json index 493b8e141ce01e..e6c4067d8ac585 100644 --- a/homeassistant/components/bond/translations/ru.json +++ b/homeassistant/components/bond/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "old_firmware": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430 \u0438 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439.", "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/cloudflare/translations/ru.json b/homeassistant/components/cloudflare/translations/ru.json index fa4819d8c836b2..7c397faa37e7f8 100644 --- a/homeassistant/components/cloudflare/translations/ru.json +++ b/homeassistant/components/cloudflare/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_zone": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0437\u043e\u043d\u0430" }, "flow_title": "Cloudflare: {name}", diff --git a/homeassistant/components/control4/translations/ru.json b/homeassistant/components/control4/translations/ru.json index 4f51641992b102..3882f03cb32c8c 100644 --- a/homeassistant/components/control4/translations/ru.json +++ b/homeassistant/components/control4/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/daikin/translations/ru.json b/homeassistant/components/daikin/translations/ru.json index df7d9fb07dc8d0..7365bb0e7bb37f 100644 --- a/homeassistant/components/daikin/translations/ru.json +++ b/homeassistant/components/daikin/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/devolo_home_control/translations/ru.json b/homeassistant/components/devolo_home_control/translations/ru.json index d4cf639ffd5882..b2e82f1355b750 100644 --- a/homeassistant/components/devolo_home_control/translations/ru.json +++ b/homeassistant/components/devolo_home_control/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/dexcom/translations/ru.json b/homeassistant/components/dexcom/translations/ru.json index 5b6b3ab24b11a0..aa90d6d998dac6 100644 --- a/homeassistant/components/dexcom/translations/ru.json +++ b/homeassistant/components/dexcom/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/doorbird/translations/ru.json b/homeassistant/components/doorbird/translations/ru.json index 274b88a8b473f1..5e376ee56d3086 100644 --- a/homeassistant/components/doorbird/translations/ru.json +++ b/homeassistant/components/doorbird/translations/ru.json @@ -7,7 +7,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "DoorBird {name} ({host})", diff --git a/homeassistant/components/econet/translations/ru.json b/homeassistant/components/econet/translations/ru.json index 109ded8db998eb..1b0d79ac396e13 100644 --- a/homeassistant/components/econet/translations/ru.json +++ b/homeassistant/components/econet/translations/ru.json @@ -3,11 +3,11 @@ "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/elkm1/translations/ru.json b/homeassistant/components/elkm1/translations/ru.json index 3c84b98b0cade9..48a950f1cca17e 100644 --- a/homeassistant/components/elkm1/translations/ru.json +++ b/homeassistant/components/elkm1/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/esphome/translations/ru.json b/homeassistant/components/esphome/translations/ru.json index bcbe914885495d..4277a057a86d32 100644 --- a/homeassistant/components/esphome/translations/ru.json +++ b/homeassistant/components/esphome/translations/ru.json @@ -6,7 +6,7 @@ }, "error": { "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a ESP. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u0430\u0448 YAML-\u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 'api:'.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "resolve_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0430\u0434\u0440\u0435\u0441 ESP. \u0415\u0441\u043b\u0438 \u044d\u0442\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442\u0441\u044f, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 IP-\u0430\u0434\u0440\u0435\u0441: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips." }, "flow_title": "ESPHome: {name}", diff --git a/homeassistant/components/fireservicerota/translations/ru.json b/homeassistant/components/fireservicerota/translations/ru.json index 2c90bd53ca95e5..3955172e02da95 100644 --- a/homeassistant/components/fireservicerota/translations/ru.json +++ b/homeassistant/components/fireservicerota/translations/ru.json @@ -8,7 +8,7 @@ "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "step": { "reauth": { diff --git a/homeassistant/components/flick_electric/translations/ru.json b/homeassistant/components/flick_electric/translations/ru.json index c97bb9133cc562..bcabe2f2157353 100644 --- a/homeassistant/components/flick_electric/translations/ru.json +++ b/homeassistant/components/flick_electric/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/flo/translations/ru.json b/homeassistant/components/flo/translations/ru.json index 6f71ee41376162..9e0db9fcf94dd8 100644 --- a/homeassistant/components/flo/translations/ru.json +++ b/homeassistant/components/flo/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/flume/translations/ru.json b/homeassistant/components/flume/translations/ru.json index f35579c2dee5b7..e4be913abcdfcd 100644 --- a/homeassistant/components/flume/translations/ru.json +++ b/homeassistant/components/flume/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json index 01e0494a07ecd4..f78f64af69aa6d 100644 --- a/homeassistant/components/foscam/translations/ru.json +++ b/homeassistant/components/foscam/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_response": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", "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/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 50146b490ba818..8cd77671bd8d97 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -8,7 +8,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "AVM FRITZ!Box: {name}", "step": { diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ru.json b/homeassistant/components/fritzbox_callmonitor/translations/ru.json index 3eb432532c45b9..f1bcb18a2f6818 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ru.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ru.json @@ -6,7 +6,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "AVM FRITZ!Box call monitor: {name}", "step": { diff --git a/homeassistant/components/garmin_connect/translations/ru.json b/homeassistant/components/garmin_connect/translations/ru.json index 69fa96c2a5e9af..49dd5c5b3bc148 100644 --- a/homeassistant/components/garmin_connect/translations/ru.json +++ b/homeassistant/components/garmin_connect/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "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." }, diff --git a/homeassistant/components/gogogate2/translations/ru.json b/homeassistant/components/gogogate2/translations/ru.json index 0c8f14f65f4e75..43e9f7a1b2ffe3 100644 --- a/homeassistant/components/gogogate2/translations/ru.json +++ b/homeassistant/components/gogogate2/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/habitica/translations/ru.json b/homeassistant/components/habitica/translations/ru.json index b3e81a349975a8..4899cd1e43b212 100644 --- a/homeassistant/components/habitica/translations/ru.json +++ b/homeassistant/components/habitica/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "error": { - "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_credentials": "\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." }, "step": { diff --git a/homeassistant/components/hlk_sw16/translations/ru.json b/homeassistant/components/hlk_sw16/translations/ru.json index 6f71ee41376162..9e0db9fcf94dd8 100644 --- a/homeassistant/components/hlk_sw16/translations/ru.json +++ b/homeassistant/components/hlk_sw16/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/huawei_lte/translations/ru.json b/homeassistant/components/huawei_lte/translations/ru.json index 884574577965dd..c2ec20fb2598da 100644 --- a/homeassistant/components/huawei_lte/translations/ru.json +++ b/homeassistant/components/huawei_lte/translations/ru.json @@ -9,7 +9,7 @@ "connection_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", "incorrect_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", "incorrect_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_url": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441.", "login_attempts_exceeded": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0432\u0445\u043e\u0434\u0430, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "response_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json index ada9aed539afd8..a598320115dbcc 100644 --- a/homeassistant/components/huisbaasje/translations/ru.json +++ b/homeassistant/components/huisbaasje/translations/ru.json @@ -5,8 +5,8 @@ }, "error": { "connection_exception": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", - "unauthenticated_exception": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "unauthenticated_exception": "\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." }, "step": { diff --git a/homeassistant/components/hvv_departures/translations/ru.json b/homeassistant/components/hvv_departures/translations/ru.json index ff5819a562d7c2..6ae277150334b1 100644 --- a/homeassistant/components/hvv_departures/translations/ru.json +++ b/homeassistant/components/hvv_departures/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "no_results": "\u041d\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0435\u0439 / \u0430\u0434\u0440\u0435\u0441\u043e\u043c." }, "step": { diff --git a/homeassistant/components/icloud/translations/ru.json b/homeassistant/components/icloud/translations/ru.json index 797637e1010a78..bdd6fe776ad421 100644 --- a/homeassistant/components/icloud/translations/ru.json +++ b/homeassistant/components/icloud/translations/ru.json @@ -6,7 +6,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "send_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.", "validate_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437." }, diff --git a/homeassistant/components/isy994/translations/ru.json b/homeassistant/components/isy994/translations/ru.json index c0a658423c6729..cbf88574e1e066 100644 --- a/homeassistant/components/isy994/translations/ru.json +++ b/homeassistant/components/isy994/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_host": "URL-\u0430\u0434\u0440\u0435\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 'http://192.168.10.100:80').", "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/juicenet/translations/ru.json b/homeassistant/components/juicenet/translations/ru.json index 2fec7d485c477c..d582e6f17038d3 100644 --- a/homeassistant/components/juicenet/translations/ru.json +++ b/homeassistant/components/juicenet/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/kmtronic/translations/ca.json b/homeassistant/components/kmtronic/translations/ca.json new file mode 100644 index 00000000000000..df8218bab3e9b3 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/en.json b/homeassistant/components/kmtronic/translations/en.json new file mode 100644 index 00000000000000..f15fe84c3ed6e9 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/et.json b/homeassistant/components/kmtronic/translations/et.json new file mode 100644 index 00000000000000..0c1715b49320e3 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/no.json b/homeassistant/components/kmtronic/translations/no.json new file mode 100644 index 00000000000000..249711bb9120f9 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/ru.json b/homeassistant/components/kodi/translations/ru.json index 312008c9b629cd..50742417f28f99 100644 --- a/homeassistant/components/kodi/translations/ru.json +++ b/homeassistant/components/kodi/translations/ru.json @@ -3,13 +3,13 @@ "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "no_uuid": "\u0423 \u044d\u0442\u043e\u0433\u043e \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 Kodi \u043d\u0435\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430. \u0421\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e, \u044d\u0442\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0442\u0430\u0440\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439 Kodi (17.x \u0438\u043b\u0438 \u043d\u0438\u0436\u0435). \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Kodi.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "Kodi: {name}", diff --git a/homeassistant/components/life360/translations/ru.json b/homeassistant/components/life360/translations/ru.json index 2de2f63dbd64d3..5b5934fbb427da 100644 --- a/homeassistant/components/life360/translations/ru.json +++ b/homeassistant/components/life360/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "create_entry": { @@ -9,7 +9,7 @@ }, "error": { "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", "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/litterrobot/translations/ca.json b/homeassistant/components/litterrobot/translations/ca.json new file mode 100644 index 00000000000000..9677f944330e80 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/en.json b/homeassistant/components/litterrobot/translations/en.json index b3fc76ae458f25..cb0e7bed7ea2bf 100644 --- a/homeassistant/components/litterrobot/translations/en.json +++ b/homeassistant/components/litterrobot/translations/en.json @@ -17,4 +17,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/et.json b/homeassistant/components/litterrobot/translations/et.json new file mode 100644 index 00000000000000..ce02ca14929ad2 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/logi_circle/translations/ru.json b/homeassistant/components/logi_circle/translations/ru.json index 2a7ccc4f3741bc..8da20b60c394c8 100644 --- a/homeassistant/components/logi_circle/translations/ru.json +++ b/homeassistant/components/logi_circle/translations/ru.json @@ -9,7 +9,7 @@ "error": { "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", "follow_link": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0438 \u043f\u0440\u043e\u0439\u0434\u0438\u0442\u0435 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u044c \"\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c\".", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "step": { "auth": { diff --git a/homeassistant/components/mazda/translations/ru.json b/homeassistant/components/mazda/translations/ru.json index e41babd499dd61..be3f861d4066a5 100644 --- a/homeassistant/components/mazda/translations/ru.json +++ b/homeassistant/components/mazda/translations/ru.json @@ -7,7 +7,7 @@ "error": { "account_locked": "\u0410\u043a\u043a\u0430\u0443\u043d\u0442 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/melcloud/translations/ru.json b/homeassistant/components/melcloud/translations/ru.json index e904ea4e8b7206..5c5081cb0c6fd2 100644 --- a/homeassistant/components/melcloud/translations/ru.json +++ b/homeassistant/components/melcloud/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/mikrotik/translations/ru.json b/homeassistant/components/mikrotik/translations/ru.json index 868ed49b5c4ba1..21391f12b1c05d 100644 --- a/homeassistant/components/mikrotik/translations/ru.json +++ b/homeassistant/components/mikrotik/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "name_exists": "\u042d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f." }, "step": { diff --git a/homeassistant/components/myq/translations/ru.json b/homeassistant/components/myq/translations/ru.json index daa3148beef481..c3b113f148f562 100644 --- a/homeassistant/components/myq/translations/ru.json +++ b/homeassistant/components/myq/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/mysensors/translations/ru.json b/homeassistant/components/mysensors/translations/ru.json index e78685e3f6bdde..6267970901761a 100644 --- a/homeassistant/components/mysensors/translations/ru.json +++ b/homeassistant/components/mysensors/translations/ru.json @@ -5,7 +5,7 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "duplicate_persistence_file": "\u042d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", "duplicate_topic": "\u042d\u0442\u043e\u0442 \u0442\u043e\u043f\u0438\u043a \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_device": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", "invalid_ip": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441.", "invalid_persistence_file": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439.", @@ -24,7 +24,7 @@ "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "duplicate_persistence_file": "\u042d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", "duplicate_topic": "\u042d\u0442\u043e\u0442 \u0442\u043e\u043f\u0438\u043a \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_device": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", "invalid_ip": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441.", "invalid_persistence_file": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439.", diff --git a/homeassistant/components/neato/translations/ru.json b/homeassistant/components/neato/translations/ru.json index 30ea15c60c37bc..ea1be16d7ac1c1 100644 --- a/homeassistant/components/neato/translations/ru.json +++ b/homeassistant/components/neato/translations/ru.json @@ -3,7 +3,7 @@ "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.", "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438.", "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435.", "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." @@ -12,7 +12,7 @@ "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/nexia/translations/ru.json b/homeassistant/components/nexia/translations/ru.json index a19d16e3a7e696..f1c7b5b8ced785 100644 --- a/homeassistant/components/nexia/translations/ru.json +++ b/homeassistant/components/nexia/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/nightscout/translations/ru.json b/homeassistant/components/nightscout/translations/ru.json index 738c4dfa9a33d0..c7688973c1bc77 100644 --- a/homeassistant/components/nightscout/translations/ru.json +++ b/homeassistant/components/nightscout/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "Nightscout", diff --git a/homeassistant/components/notion/translations/ru.json b/homeassistant/components/notion/translations/ru.json index 678eff742b513d..737539424b02b8 100644 --- a/homeassistant/components/notion/translations/ru.json +++ b/homeassistant/components/notion/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "no_devices": "\u041d\u0435\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e." }, "step": { diff --git a/homeassistant/components/nuheat/translations/ru.json b/homeassistant/components/nuheat/translations/ru.json index 09e74c0e4cbf8a..099f6c3f1fccaa 100644 --- a/homeassistant/components/nuheat/translations/ru.json +++ b/homeassistant/components/nuheat/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_thermostat": "\u0421\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d.", "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/nuki/translations/ru.json b/homeassistant/components/nuki/translations/ru.json index bad9f35c076bbf..a7fe1c61f5b62d 100644 --- a/homeassistant/components/nuki/translations/ru.json +++ b/homeassistant/components/nuki/translations/ru.json @@ -2,7 +2,7 @@ "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/omnilogic/translations/ru.json b/homeassistant/components/omnilogic/translations/ru.json index 9040654e58cebb..828f053083096a 100644 --- a/homeassistant/components/omnilogic/translations/ru.json +++ b/homeassistant/components/omnilogic/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/ovo_energy/translations/ru.json b/homeassistant/components/ovo_energy/translations/ru.json index dd422bac01f6cb..47a94f6a24aad7 100644 --- a/homeassistant/components/ovo_energy/translations/ru.json +++ b/homeassistant/components/ovo_energy/translations/ru.json @@ -3,7 +3,7 @@ "error": { "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.", "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "OVO Energy: {username}", "step": { diff --git a/homeassistant/components/plugwise/translations/ru.json b/homeassistant/components/plugwise/translations/ru.json index 8a59d492e66ac3..9df460e8919634 100644 --- a/homeassistant/components/plugwise/translations/ru.json +++ b/homeassistant/components/plugwise/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "Smile: {name}", diff --git a/homeassistant/components/poolsense/translations/ru.json b/homeassistant/components/poolsense/translations/ru.json index 3687b75a6f7373..09c94368cdabb9 100644 --- a/homeassistant/components/poolsense/translations/ru.json +++ b/homeassistant/components/poolsense/translations/ru.json @@ -4,7 +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." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index 2d8246cc14ff21..f79b62c2c78dc6 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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.", "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." }, diff --git a/homeassistant/components/rachio/translations/ru.json b/homeassistant/components/rachio/translations/ru.json index 53cd98387facf9..52248b8d6860b1 100644 --- a/homeassistant/components/rachio/translations/ru.json +++ b/homeassistant/components/rachio/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/rainmachine/translations/ru.json b/homeassistant/components/rainmachine/translations/ru.json index 08ce690d22f327..8502b66aff7e5f 100644 --- a/homeassistant/components/rainmachine/translations/ru.json +++ b/homeassistant/components/rainmachine/translations/ru.json @@ -4,7 +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." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/ring/translations/ru.json b/homeassistant/components/ring/translations/ru.json index fb8c22c39af644..636d83f2e02701 100644 --- a/homeassistant/components/ring/translations/ru.json +++ b/homeassistant/components/ring/translations/ru.json @@ -4,7 +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." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/risco/translations/ru.json b/homeassistant/components/risco/translations/ru.json index 3fd1fd567f9a89..a507bb84e530d4 100644 --- a/homeassistant/components/risco/translations/ru.json +++ b/homeassistant/components/risco/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/no.json b/homeassistant/components/rituals_perfume_genie/translations/no.json new file mode 100644 index 00000000000000..2ffc9bc91af8bd --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "email": "E-post", + "password": "Passord" + }, + "title": "Koble til Rituals-kontoen din" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/ru.json b/homeassistant/components/rituals_perfume_genie/translations/ru.json new file mode 100644 index 00000000000000..afbf1da0e4693c --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/ru.json @@ -0,0 +1,21 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/ru.json b/homeassistant/components/roon/translations/ru.json index 187151affe2f40..c01006d6269663 100644 --- a/homeassistant/components/roon/translations/ru.json +++ b/homeassistant/components/roon/translations/ru.json @@ -5,7 +5,7 @@ }, "error": { "duplicate_entry": "\u042d\u0442\u043e\u0442 \u0445\u043e\u0441\u0442 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/ruckus_unleashed/translations/ru.json b/homeassistant/components/ruckus_unleashed/translations/ru.json index 6f71ee41376162..9e0db9fcf94dd8 100644 --- a/homeassistant/components/ruckus_unleashed/translations/ru.json +++ b/homeassistant/components/ruckus_unleashed/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/sense/translations/ru.json b/homeassistant/components/sense/translations/ru.json index 74be3049a750be..0bb299e22084f3 100644 --- a/homeassistant/components/sense/translations/ru.json +++ b/homeassistant/components/sense/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/sharkiq/translations/ru.json b/homeassistant/components/sharkiq/translations/ru.json index 80af08a8958837..60ce7d454d655f 100644 --- a/homeassistant/components/sharkiq/translations/ru.json +++ b/homeassistant/components/sharkiq/translations/ru.json @@ -8,7 +8,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index ad5a288cf9138d..a570cb7f9fb3ef 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "{name}", diff --git a/homeassistant/components/simplisafe/translations/ru.json b/homeassistant/components/simplisafe/translations/ru.json index 94b0e6a0975d73..abe0542c926b14 100644 --- a/homeassistant/components/simplisafe/translations/ru.json +++ b/homeassistant/components/simplisafe/translations/ru.json @@ -6,7 +6,7 @@ }, "error": { "identifier_exists": "\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "still_awaiting_mfa": "\u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043e \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u0435.", "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/smart_meter_texas/translations/ru.json b/homeassistant/components/smart_meter_texas/translations/ru.json index 9fe75df9c3f8ac..3f4677a050e6df 100644 --- a/homeassistant/components/smart_meter_texas/translations/ru.json +++ b/homeassistant/components/smart_meter_texas/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/smarthab/translations/ru.json b/homeassistant/components/smarthab/translations/ru.json index cea090f51d2a27..45e3698034f683 100644 --- a/homeassistant/components/smarthab/translations/ru.json +++ b/homeassistant/components/smarthab/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "service": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a SmartHab. \u0421\u0435\u0440\u0432\u0438\u0441 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435.", "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/smarttub/translations/ru.json b/homeassistant/components/smarttub/translations/ru.json index 67e055a32c5efe..44f27877d93642 100644 --- a/homeassistant/components/smarttub/translations/ru.json +++ b/homeassistant/components/smarttub/translations/ru.json @@ -5,7 +5,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/somfy_mylink/translations/ru.json b/homeassistant/components/somfy_mylink/translations/ru.json index e4cc7b717128bf..7c98166433571c 100644 --- a/homeassistant/components/somfy_mylink/translations/ru.json +++ b/homeassistant/components/somfy_mylink/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": "Somfy MyLink {mac} ({ip})", diff --git a/homeassistant/components/sonarr/translations/ru.json b/homeassistant/components/sonarr/translations/ru.json index 1b6345d4563694..75d23cd3ec0ed0 100644 --- a/homeassistant/components/sonarr/translations/ru.json +++ b/homeassistant/components/sonarr/translations/ru.json @@ -7,7 +7,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "Sonarr: {name}", "step": { diff --git a/homeassistant/components/spider/translations/ru.json b/homeassistant/components/spider/translations/ru.json index 983f2b94361b41..1b1a175cce526f 100644 --- a/homeassistant/components/spider/translations/ru.json +++ b/homeassistant/components/spider/translations/ru.json @@ -4,7 +4,7 @@ "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." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/squeezebox/translations/ru.json b/homeassistant/components/squeezebox/translations/ru.json index 789fff313d884c..fb07471d116e10 100644 --- a/homeassistant/components/squeezebox/translations/ru.json +++ b/homeassistant/components/squeezebox/translations/ru.json @@ -6,7 +6,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "no_server_found": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440.", "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/srp_energy/translations/ru.json b/homeassistant/components/srp_energy/translations/ru.json index 3fcbace37dfe90..125f3a5addcff4 100644 --- a/homeassistant/components/srp_energy/translations/ru.json +++ b/homeassistant/components/srp_energy/translations/ru.json @@ -6,7 +6,7 @@ "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_account": "ID \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c 9-\u0437\u043d\u0430\u0447\u043d\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/subaru/translations/no.json b/homeassistant/components/subaru/translations/no.json new file mode 100644 index 00000000000000..f1a263d5cb4bd8 --- /dev/null +++ b/homeassistant/components/subaru/translations/no.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes" + }, + "error": { + "bad_pin_format": "PIN-koden skal best\u00e5 av fire sifre", + "cannot_connect": "Tilkobling mislyktes", + "incorrect_pin": "Feil PIN", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Vennligst skriv inn MySubaru PIN-koden\n MERKNAD: Alle kj\u00f8ret\u00f8yer som er kontoen m\u00e5 ha samme PIN-kode", + "title": "Subaru Starlink-konfigurasjon" + }, + "user": { + "data": { + "country": "Velg land", + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Vennligst skriv inn MySubaru-legitimasjonen din\n MERK: F\u00f8rste oppsett kan ta opptil 30 sekunder", + "title": "Subaru Starlink-konfigurasjon" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Aktiver polling av kj\u00f8ret\u00f8y" + }, + "description": "N\u00e5r dette er aktivert, sender polling av kj\u00f8ret\u00f8y en fjernkommando til kj\u00f8ret\u00f8yet annenhver time for \u00e5 skaffe nye sensordata. Uten kj\u00f8ret\u00f8yoppm\u00e5ling mottas nye sensordata bare n\u00e5r kj\u00f8ret\u00f8yet automatisk skyver data (normalt etter motorstans).", + "title": "Subaru Starlink Alternativer" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/ru.json b/homeassistant/components/subaru/translations/ru.json new file mode 100644 index 00000000000000..7e3fbce6e38cd3 --- /dev/null +++ b/homeassistant/components/subaru/translations/ru.json @@ -0,0 +1,44 @@ +{ + "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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "error": { + "bad_pin_format": "PIN-\u043a\u043e\u0434 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 4 \u0446\u0438\u0444\u0440.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "incorrect_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434.", + "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." + }, + "step": { + "pin": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434 MySubaru.\n\u0412\u0441\u0435 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0438 \u0432 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0439 PIN-\u043a\u043e\u0434.", + "title": "Subaru Starlink" + }, + "user": { + "data": { + "country": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0443", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 MySubaru.\n\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u0434\u043e 30 \u0441\u0435\u043a\u0443\u043d\u0434.", + "title": "Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u043f\u0440\u043e\u0441 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u0435\u0439" + }, + "description": "\u0415\u0441\u043b\u0438 \u044d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d, Home Assistant \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u043d\u0430 \u0412\u0430\u0448 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u044c \u043a\u0430\u0436\u0434\u044b\u0435 2 \u0447\u0430\u0441\u0430 \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043d\u043e\u0432\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445. \u0411\u0435\u0437 \u043e\u043f\u0440\u043e\u0441\u0430 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u043e\u0433\u043e \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 (\u043e\u0431\u044b\u0447\u043d\u043e \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043b\u044f).", + "title": "Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index ed3c2eea0a8ab0..8c48b8c3fc7418 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "missing_data": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435: \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "otp_failed": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0441 \u043d\u043e\u0432\u044b\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c.", "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/tado/translations/ru.json b/homeassistant/components/tado/translations/ru.json index 8ffb14edc0efc2..75c83e8582bfcf 100644 --- a/homeassistant/components/tado/translations/ru.json +++ b/homeassistant/components/tado/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "no_homes": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0434\u043e\u043c\u043e\u0432, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e.", "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/tellduslive/translations/ru.json b/homeassistant/components/tellduslive/translations/ru.json index 0fc0c2f449f0e4..95a16fa205f215 100644 --- a/homeassistant/components/tellduslive/translations/ru.json +++ b/homeassistant/components/tellduslive/translations/ru.json @@ -8,7 +8,7 @@ "unknown_authorize_url_generation": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "step": { "auth": { diff --git a/homeassistant/components/tesla/translations/ru.json b/homeassistant/components/tesla/translations/ru.json index 7429b8ffa535d8..d62a2e1f16848b 100644 --- a/homeassistant/components/tesla/translations/ru.json +++ b/homeassistant/components/tesla/translations/ru.json @@ -7,7 +7,7 @@ "error": { "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.", "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/tile/translations/ru.json b/homeassistant/components/tile/translations/ru.json index 62d0b10857c64f..f42a4d631b04e8 100644 --- a/homeassistant/components/tile/translations/ru.json +++ b/homeassistant/components/tile/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/totalconnect/translations/ca.json b/homeassistant/components/totalconnect/translations/ca.json index 25dafcf7d21dbb..ce055082a2171e 100644 --- a/homeassistant/components/totalconnect/translations/ca.json +++ b/homeassistant/components/totalconnect/translations/ca.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "El compte ja ha estat configurat" + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "usercode": "El codi d'usuari no \u00e9s v\u00e0lid per a aquest usuari en aquesta ubicaci\u00f3" }, "step": { + "locations": { + "data": { + "location": "Ubicaci\u00f3" + }, + "description": "Introdueix el codi d'usuari de l'usuari en aquesta ubicaci\u00f3", + "title": "Codis d'usuari d'ubicaci\u00f3" + }, + "reauth_confirm": { + "description": "Total Connect ha de tornar a autenticar-se amb el teu compte", + "title": "Reautenticaci\u00f3 de la integraci\u00f3" + }, "user": { "data": { "password": "Contrasenya", diff --git a/homeassistant/components/totalconnect/translations/et.json b/homeassistant/components/totalconnect/translations/et.json index 2940b7a9e65b82..3f1a15fe139925 100644 --- a/homeassistant/components/totalconnect/translations/et.json +++ b/homeassistant/components/totalconnect/translations/et.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Konto on juba seadistatud" + "already_configured": "Konto on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { - "invalid_auth": "Tuvastamise viga" + "invalid_auth": "Tuvastamise viga", + "usercode": "Kasutajakood ei sobi selle kasutaja jaoks selles asukohas" }, "step": { + "locations": { + "data": { + "location": "Asukoht" + }, + "description": "Sisesta selle kasutaja kood selles asukohas", + "title": "Asukoha kasutajakoodid" + }, + "reauth_confirm": { + "description": "Total Connect peab konto uuesti autentima", + "title": "Taastuvasta sidumine" + }, "user": { "data": { "password": "Salas\u00f5na", diff --git a/homeassistant/components/totalconnect/translations/ru.json b/homeassistant/components/totalconnect/translations/ru.json index c5221b5e4ca43a..054f207b2b0473 100644 --- a/homeassistant/components/totalconnect/translations/ru.json +++ b/homeassistant/components/totalconnect/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/transmission/translations/ru.json b/homeassistant/components/transmission/translations/ru.json index d1fbd592f0f4db..6b326bc123c6a5 100644 --- a/homeassistant/components/transmission/translations/ru.json +++ b/homeassistant/components/transmission/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "name_exists": "\u042d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f." }, "step": { diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index 5d887710230e6a..4babc23f2ec8aa 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -2,11 +2,11 @@ "config": { "abort": { "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "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." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Tuya", "step": { diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index df2150c98ecea8..5810204db41f07 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -6,7 +6,7 @@ "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": { - "faulty_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "faulty_credentials": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "service_unavailable": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "unknown_client_mac": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043d\u0430 \u044d\u0442\u043e\u043c MAC-\u0430\u0434\u0440\u0435\u0441\u0435." }, diff --git a/homeassistant/components/upcloud/translations/ru.json b/homeassistant/components/upcloud/translations/ru.json index ced4097a7e2719..c64a69965b22d1 100644 --- a/homeassistant/components/upcloud/translations/ru.json +++ b/homeassistant/components/upcloud/translations/ru.json @@ -2,7 +2,7 @@ "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.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/vesync/translations/ru.json b/homeassistant/components/vesync/translations/ru.json index fd6132565f60a8..b3ac09685be13a 100644 --- a/homeassistant/components/vesync/translations/ru.json +++ b/homeassistant/components/vesync/translations/ru.json @@ -4,7 +4,7 @@ "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." }, "error": { - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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": { diff --git a/homeassistant/components/vilfo/translations/ru.json b/homeassistant/components/vilfo/translations/ru.json index 8e61be904004e8..62ec2fe5daeff9 100644 --- a/homeassistant/components/vilfo/translations/ru.json +++ b/homeassistant/components/vilfo/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/wolflink/translations/ru.json b/homeassistant/components/wolflink/translations/ru.json index 841f7b26030b9e..0b105ad922afee 100644 --- a/homeassistant/components/wolflink/translations/ru.json +++ b/homeassistant/components/wolflink/translations/ru.json @@ -5,7 +5,7 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\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." }, "step": { diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index 3c666fe4ef1c2a..170d14fc6dcaba 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adre\u00e7a IP", + "model": "Model del dispositiu (opcional)", "name": "Nom del dispositiu", "token": "Token d'API" }, diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 37a8ce06eba918..951ae546b5618c 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -6,6 +6,7 @@ }, "error": { "cannot_connect": "Failed to connect", + "no_device_selected": "No device selected, please select one device.", "unknown_device": "The device model is not known, not able to setup the device using config flow." }, "flow_title": "Xiaomi Miio: {name}", @@ -13,11 +14,28 @@ "device": { "data": { "host": "IP Address", - "token": "API Token", - "model": "Device model (Optional)" + "model": "Device model (Optional)", + "name": "Name of the device", + "token": "API Token" }, "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" + }, + "gateway": { + "data": { + "host": "IP Address", + "name": "Name of the Gateway", + "token": "API Token" + }, + "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", + "title": "Connect to a Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Connect to a Xiaomi Gateway" + }, + "description": "Select to which device you want to connect.", + "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/xiaomi_miio/translations/et.json b/homeassistant/components/xiaomi_miio/translations/et.json index a5975ab05dc9db..a290f80ad311b9 100644 --- a/homeassistant/components/xiaomi_miio/translations/et.json +++ b/homeassistant/components/xiaomi_miio/translations/et.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "IP-aadress", + "model": "Seadme mudel (valikuline)", "name": "Seadme nimi", "token": "API v\u00f5ti" }, diff --git a/homeassistant/components/zoneminder/translations/ru.json b/homeassistant/components/zoneminder/translations/ru.json index d599e767f64a49..bee720ee09a4da 100644 --- a/homeassistant/components/zoneminder/translations/ru.json +++ b/homeassistant/components/zoneminder/translations/ru.json @@ -4,7 +4,7 @@ "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "create_entry": { "default": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440 ZoneMinder." @@ -13,7 +13,7 @@ "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." }, "flow_title": "ZoneMinder", "step": { From 1cecf229b94f282b39e046bb07900736179b8b2e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 22 Feb 2021 20:34:47 -0500 Subject: [PATCH 0695/1818] Add zwave_js set_config_parameter WS API command (#46910) * add WS API command * handle error scenario better * fixes and remove duplicate catch * make elif statement more compact * fix conflict * switch to str(err) --- homeassistant/components/zwave_js/api.py | 60 ++++++++++- tests/components/zwave_js/test_api.py | 123 +++++++++++++++++++++++ 2 files changed, 182 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index a6cd8c50a7664a..71994f8b00b36a 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -7,11 +7,18 @@ import voluptuous as vol from zwave_js_server import dump from zwave_js_server.const import LogLevel +from zwave_js_server.exceptions import InvalidNewValue, NotFoundError, SetValueFailed from zwave_js_server.model.log_config import LogConfig +from zwave_js_server.util.node import async_set_config_parameter from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.components.websocket_api.const import ( + ERR_NOT_FOUND, + ERR_NOT_SUPPORTED, + ERR_UNKNOWN_ERROR, +) from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv @@ -26,6 +33,9 @@ ENTRY_ID = "entry_id" NODE_ID = "node_id" TYPE = "type" +PROPERTY = "property" +PROPERTY_KEY = "property_key" +VALUE = "value" # constants for log config commands CONFIG = "config" @@ -45,9 +55,10 @@ def async_register_api(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_stop_inclusion) websocket_api.async_register_command(hass, websocket_remove_node) websocket_api.async_register_command(hass, websocket_stop_exclusion) - websocket_api.async_register_command(hass, websocket_get_config_parameters) websocket_api.async_register_command(hass, websocket_update_log_config) websocket_api.async_register_command(hass, websocket_get_log_config) + websocket_api.async_register_command(hass, websocket_get_config_parameters) + websocket_api.async_register_command(hass, websocket_set_config_parameter) hass.http.register_view(DumpView) # type: ignore @@ -280,6 +291,53 @@ def node_removed(event: dict) -> None: ) +@websocket_api.require_admin # type:ignore +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required(TYPE): "zwave_js/set_config_parameter", + vol.Required(ENTRY_ID): str, + vol.Required(NODE_ID): int, + vol.Required(PROPERTY): int, + vol.Optional(PROPERTY_KEY): int, + vol.Required(VALUE): int, + } +) +async def websocket_set_config_parameter( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +) -> None: + """Set a config parameter value for a Z-Wave node.""" + entry_id = msg[ENTRY_ID] + node_id = msg[NODE_ID] + property_ = msg[PROPERTY] + property_key = msg.get(PROPERTY_KEY) + value = msg[VALUE] + client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + node = client.driver.controller.nodes[node_id] + try: + result = await async_set_config_parameter( + node, value, property_, property_key=property_key + ) + except (InvalidNewValue, NotFoundError, NotImplementedError, SetValueFailed) as err: + code = ERR_UNKNOWN_ERROR + if isinstance(err, NotFoundError): + code = ERR_NOT_FOUND + elif isinstance(err, (InvalidNewValue, NotImplementedError)): + code = ERR_NOT_SUPPORTED + + connection.send_error( + msg[ID], + code, + str(err), + ) + return + + connection.send_result( + msg[ID], + str(result), + ) + + @websocket_api.require_admin @websocket_api.websocket_command( { diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 29a86d63c83002..403a73a6767334 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -4,6 +4,7 @@ from zwave_js_server.const import LogLevel from zwave_js_server.event import Event +from zwave_js_server.exceptions import InvalidNewValue, NotFoundError, SetValueFailed from homeassistant.components.zwave_js.api import ( CONFIG, @@ -15,7 +16,10 @@ LEVEL, LOG_TO_FILE, NODE_ID, + PROPERTY, + PROPERTY_KEY, TYPE, + VALUE, ) from homeassistant.components.zwave_js.const import DOMAIN from homeassistant.helpers.device_registry import async_get_registry @@ -186,6 +190,125 @@ async def test_remove_node( assert device is None +async def test_set_config_parameter( + hass, client, hass_ws_client, multisensor_6, integration +): + """Test the set_config_parameter service.""" + entry = integration + ws_client = await hass_ws_client(hass) + + client.async_send_command.return_value = {"success": True} + + await ws_client.send_json( + { + ID: 1, + TYPE: "zwave_js/set_config_parameter", + ENTRY_ID: entry.entry_id, + NODE_ID: 52, + PROPERTY: 102, + PROPERTY_KEY: 1, + VALUE: 1, + } + ) + + msg = await ws_client.receive_json() + assert msg["result"] + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 102, + "propertyName": "Group 2: Send battery reports", + "propertyKey": 1, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": True, + "label": "Group 2: Send battery reports", + "description": "Include battery information in periodic reports to Group 2", + "isFromConfig": True, + }, + "value": 0, + } + assert args["value"] == 1 + + client.async_send_command.reset_mock() + + with patch( + "homeassistant.components.zwave_js.api.async_set_config_parameter", + ) as set_param_mock: + set_param_mock.side_effect = InvalidNewValue("test") + await ws_client.send_json( + { + ID: 2, + TYPE: "zwave_js/set_config_parameter", + ENTRY_ID: entry.entry_id, + NODE_ID: 52, + PROPERTY: 102, + PROPERTY_KEY: 1, + VALUE: 1, + } + ) + + msg = await ws_client.receive_json() + + assert len(client.async_send_command.call_args_list) == 0 + assert not msg["success"] + assert msg["error"]["code"] == "not_supported" + assert msg["error"]["message"] == "test" + + set_param_mock.side_effect = NotFoundError("test") + await ws_client.send_json( + { + ID: 3, + TYPE: "zwave_js/set_config_parameter", + ENTRY_ID: entry.entry_id, + NODE_ID: 52, + PROPERTY: 102, + PROPERTY_KEY: 1, + VALUE: 1, + } + ) + + msg = await ws_client.receive_json() + + assert len(client.async_send_command.call_args_list) == 0 + assert not msg["success"] + assert msg["error"]["code"] == "not_found" + assert msg["error"]["message"] == "test" + + set_param_mock.side_effect = SetValueFailed("test") + await ws_client.send_json( + { + ID: 4, + TYPE: "zwave_js/set_config_parameter", + ENTRY_ID: entry.entry_id, + NODE_ID: 52, + PROPERTY: 102, + PROPERTY_KEY: 1, + VALUE: 1, + } + ) + + msg = await ws_client.receive_json() + + assert len(client.async_send_command.call_args_list) == 0 + assert not msg["success"] + assert msg["error"]["code"] == "unknown_error" + assert msg["error"]["message"] == "test" + + async def test_dump_view(integration, hass_client): """Test the HTTP dump view.""" client = await hass_client() From f005c686301560411d854c1803097a70aeb63084 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 23 Feb 2021 10:37:19 +0800 Subject: [PATCH 0696/1818] Restore stream recorder functionality and add discontinuity support (#46772) * Add discontinuity support to stream recorder * Use same container options for both StreamOutputs * Fix pts adjuster * Remove redundant/incorrect duplicate hls segment check * Use same StreamBuffer across outputs * Remove keepalive check for recorder * Set output video timescale explicitly * Disable avoid_negative_ts --- homeassistant/components/stream/__init__.py | 2 +- homeassistant/components/stream/const.py | 4 + homeassistant/components/stream/core.py | 30 ++----- homeassistant/components/stream/hls.py | 34 ++------ homeassistant/components/stream/recorder.py | 88 +++++++++++---------- homeassistant/components/stream/worker.py | 64 ++++++++------- tests/components/stream/test_recorder.py | 15 +++- 7 files changed, 107 insertions(+), 130 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 0027152dbd6d04..2d115c6978d78e 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -136,7 +136,7 @@ def add_provider(self, fmt, timeout=OUTPUT_IDLE_TIMEOUT): @callback def idle_callback(): - if not self.keepalive and fmt in self._outputs: + if (not self.keepalive or fmt == "recorder") and fmt in self._outputs: self.remove_provider(self._outputs[fmt]) self.check_idle() diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 41df806d020c4f..a2557286cf1753 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -6,6 +6,10 @@ OUTPUT_FORMATS = ["hls"] +SEGMENT_CONTAINER_FORMAT = "mp4" # format for segments +RECORDER_CONTAINER_FORMAT = "mp4" # format for recorder output +AUDIO_CODECS = {"aac", "mp3"} + FORMAT_CONTENT_TYPE = {"hls": "application/vnd.apple.mpegurl"} OUTPUT_IDLE_TIMEOUT = 300 # Idle timeout due to inactivity diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index eba6a0696981bb..17d4516344a4f0 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -12,7 +12,7 @@ from homeassistant.helpers.event import async_call_later from homeassistant.util.decorator import Registry -from .const import ATTR_STREAMS, DOMAIN, MAX_SEGMENTS +from .const import ATTR_STREAMS, DOMAIN PROVIDERS = Registry() @@ -83,13 +83,15 @@ def fire(self, _now=None): class StreamOutput: """Represents a stream output.""" - def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: + def __init__( + self, hass: HomeAssistant, idle_timer: IdleTimer, deque_maxlen: int = None + ) -> None: """Initialize a stream output.""" self._hass = hass self._idle_timer = idle_timer self._cursor = None self._event = asyncio.Event() - self._segments = deque(maxlen=MAX_SEGMENTS) + self._segments = deque(maxlen=deque_maxlen) @property def name(self) -> str: @@ -101,26 +103,6 @@ def idle(self) -> bool: """Return True if the output is idle.""" return self._idle_timer.idle - @property - def format(self) -> str: - """Return container format.""" - return None - - @property - def audio_codecs(self) -> str: - """Return desired audio codecs.""" - return None - - @property - def video_codecs(self) -> tuple: - """Return desired video codecs.""" - return None - - @property - def container_options(self) -> Callable[[int], dict]: - """Return Callable which takes a sequence number and returns container options.""" - return None - @property def segments(self) -> List[int]: """Return current sequence from segments.""" @@ -177,7 +159,7 @@ def cleanup(self): """Handle cleanup.""" self._event.set() self._idle_timer.clear() - self._segments = deque(maxlen=MAX_SEGMENTS) + self._segments = deque(maxlen=self._segments.maxlen) class StreamView(HomeAssistantView): diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 28d6a300ae7362..b260097797159e 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -1,13 +1,12 @@ """Provide functionality to stream HLS.""" import io -from typing import Callable from aiohttp import web from homeassistant.core import callback -from .const import FORMAT_CONTENT_TYPE, NUM_PLAYLIST_SEGMENTS -from .core import PROVIDERS, StreamOutput, StreamView +from .const import FORMAT_CONTENT_TYPE, MAX_SEGMENTS, NUM_PLAYLIST_SEGMENTS +from .core import PROVIDERS, HomeAssistant, IdleTimer, StreamOutput, StreamView from .fmp4utils import get_codec_string, get_init, get_m4s @@ -159,32 +158,11 @@ async def handle(self, request, stream, sequence): class HlsStreamOutput(StreamOutput): """Represents HLS Output formats.""" + def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: + """Initialize recorder output.""" + super().__init__(hass, idle_timer, deque_maxlen=MAX_SEGMENTS) + @property def name(self) -> str: """Return provider name.""" return "hls" - - @property - def format(self) -> str: - """Return container format.""" - return "mp4" - - @property - def audio_codecs(self) -> str: - """Return desired audio codecs.""" - return {"aac", "mp3"} - - @property - def video_codecs(self) -> tuple: - """Return desired video codecs.""" - return {"hevc", "h264"} - - @property - def container_options(self) -> Callable[[int], dict]: - """Return Callable which takes a sequence number and returns container options.""" - return lambda sequence: { - # Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970 - "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont", - "avoid_negative_ts": "make_non_negative", - "fragment_index": str(sequence), - } diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 0b77d0ba6302fa..0344e220647855 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -2,12 +2,13 @@ import logging import os import threading -from typing import List +from typing import Deque, List import av from homeassistant.core import HomeAssistant, callback +from .const import RECORDER_CONTAINER_FORMAT, SEGMENT_CONTAINER_FORMAT from .core import PROVIDERS, IdleTimer, Segment, StreamOutput _LOGGER = logging.getLogger(__name__) @@ -18,28 +19,20 @@ def async_setup_recorder(hass): """Only here so Provider Registry works.""" -def recorder_save_worker(file_out: str, segments: List[Segment], container_format: str): +def recorder_save_worker(file_out: str, segments: Deque[Segment]): """Handle saving stream.""" if not os.path.exists(os.path.dirname(file_out)): os.makedirs(os.path.dirname(file_out), exist_ok=True) - first_pts = {"video": None, "audio": None} - output = av.open(file_out, "w", format=container_format) + pts_adjuster = {"video": None, "audio": None} + output = None output_v = None output_a = None - # Get first_pts values from first segment - if len(segments) > 0: - segment = segments[0] - source = av.open(segment.segment, "r", format=container_format) - source_v = source.streams.video[0] - first_pts["video"] = source_v.start_time - if len(source.streams.audio) > 0: - source_a = source.streams.audio[0] - first_pts["audio"] = int( - source_v.start_time * source_v.time_base / source_a.time_base - ) - source.close() + last_stream_id = None + # The running duration of processed segments. Note that this is in av.time_base + # units which seem to be defined inversely to how stream time_bases are defined + running_duration = 0 last_sequence = float("-inf") for segment in segments: @@ -50,26 +43,54 @@ def recorder_save_worker(file_out: str, segments: List[Segment], container_forma last_sequence = segment.sequence # Open segment - source = av.open(segment.segment, "r", format=container_format) + source = av.open(segment.segment, "r", format=SEGMENT_CONTAINER_FORMAT) source_v = source.streams.video[0] - # Add output streams + source_a = source.streams.audio[0] if len(source.streams.audio) > 0 else None + + # Create output on first segment + if not output: + output = av.open( + file_out, + "w", + format=RECORDER_CONTAINER_FORMAT, + container_options={ + "video_track_timescale": str(int(1 / source_v.time_base)) + }, + ) + + # Add output streams if necessary if not output_v: output_v = output.add_stream(template=source_v) context = output_v.codec_context context.flags |= "GLOBAL_HEADER" - if not output_a and len(source.streams.audio) > 0: - source_a = source.streams.audio[0] + if source_a and not output_a: output_a = output.add_stream(template=source_a) + # Recalculate pts adjustments on first segment and on any discontinuity + # We are assuming time base is the same across all discontinuities + if last_stream_id != segment.stream_id: + last_stream_id = segment.stream_id + pts_adjuster["video"] = int( + (running_duration - source.start_time) + / (av.time_base * source_v.time_base) + ) + if source_a: + pts_adjuster["audio"] = int( + (running_duration - source.start_time) + / (av.time_base * source_a.time_base) + ) + # Remux video for packet in source.demux(): if packet.dts is None: continue - packet.pts -= first_pts[packet.stream.type] - packet.dts -= first_pts[packet.stream.type] + packet.pts += pts_adjuster[packet.stream.type] + packet.dts += pts_adjuster[packet.stream.type] packet.stream = output_v if packet.stream.type == "video" else output_a output.mux(packet) + running_duration += source.duration - source.start_time + source.close() output.close() @@ -83,33 +104,15 @@ def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: """Initialize recorder output.""" super().__init__(hass, idle_timer) self.video_path = None - self._segments = [] @property def name(self) -> str: """Return provider name.""" return "recorder" - @property - def format(self) -> str: - """Return container format.""" - return "mp4" - - @property - def audio_codecs(self) -> str: - """Return desired audio codec.""" - return {"aac", "mp3"} - - @property - def video_codecs(self) -> tuple: - """Return desired video codecs.""" - return {"hevc", "h264"} - def prepend(self, segments: List[Segment]) -> None: """Prepend segments to existing list.""" - own_segments = self.segments - segments = [s for s in segments if s.sequence not in own_segments] - self._segments = segments + self._segments + self._segments.extendleft(reversed(segments)) def cleanup(self): """Write recording and clean up.""" @@ -117,9 +120,8 @@ def cleanup(self): thread = threading.Thread( name="recorder_save_worker", target=recorder_save_worker, - args=(self.video_path, self._segments, self.format), + args=(self.video_path, self._segments), ) thread.start() super().cleanup() - self._segments = [] diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 61d4f5db17ab28..d5760877c43da5 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -6,10 +6,12 @@ import av from .const import ( + AUDIO_CODECS, MAX_MISSING_DTS, MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO, + SEGMENT_CONTAINER_FORMAT, STREAM_TIMEOUT, ) from .core import Segment, StreamBuffer @@ -17,19 +19,20 @@ _LOGGER = logging.getLogger(__name__) -def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): +def create_stream_buffer(video_stream, audio_stream, sequence): """Create a new StreamBuffer.""" segment = io.BytesIO() - container_options = ( - stream_output.container_options(sequence) - if stream_output.container_options - else {} - ) + container_options = { + # Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970 + "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont", + "avoid_negative_ts": "disabled", + "fragment_index": str(sequence), + } output = av.open( segment, mode="w", - format=stream_output.format, + format=SEGMENT_CONTAINER_FORMAT, container_options={ "video_track_timescale": str(int(1 / video_stream.time_base)), **container_options, @@ -38,7 +41,7 @@ def create_stream_buffer(stream_output, video_stream, audio_stream, sequence): vstream = output.add_stream(template=video_stream) # Check if audio is requested astream = None - if audio_stream and audio_stream.name in stream_output.audio_codecs: + if audio_stream and audio_stream.name in AUDIO_CODECS: astream = output.add_stream(template=audio_stream) return StreamBuffer(segment, output, vstream, astream) @@ -52,10 +55,11 @@ def __init__(self, outputs_callback) -> None: self._video_stream = None self._audio_stream = None self._outputs_callback = outputs_callback - # tuple of StreamOutput, StreamBuffer + # Each element is a StreamOutput self._outputs = [] self._sequence = 0 self._segment_start_pts = None + self._stream_buffer = None def set_streams(self, video_stream, audio_stream): """Initialize output buffer with streams from container.""" @@ -70,14 +74,10 @@ def reset(self, video_pts): # Fetch the latest StreamOutputs, which may have changed since the # worker started. - self._outputs = [] - for stream_output in self._outputs_callback().values(): - if self._video_stream.name not in stream_output.video_codecs: - continue - buffer = create_stream_buffer( - stream_output, self._video_stream, self._audio_stream, self._sequence - ) - self._outputs.append((buffer, stream_output)) + self._outputs = self._outputs_callback().values() + self._stream_buffer = create_stream_buffer( + self._video_stream, self._audio_stream, self._sequence + ) def mux_packet(self, packet): """Mux a packet to the appropriate StreamBuffers.""" @@ -93,22 +93,21 @@ def mux_packet(self, packet): self.reset(packet.pts) # Mux the packet - for (buffer, _) in self._outputs: - if packet.stream == self._video_stream: - packet.stream = buffer.vstream - elif packet.stream == self._audio_stream: - packet.stream = buffer.astream - else: - continue - buffer.output.mux(packet) + if packet.stream == self._video_stream: + packet.stream = self._stream_buffer.vstream + self._stream_buffer.output.mux(packet) + elif packet.stream == self._audio_stream: + packet.stream = self._stream_buffer.astream + self._stream_buffer.output.mux(packet) def flush(self, duration): """Create a segment from the buffered packets and write to output.""" - for (buffer, stream_output) in self._outputs: - buffer.output.close() - stream_output.put( - Segment(self._sequence, buffer.segment, duration, self._stream_id) - ) + self._stream_buffer.output.close() + segment = Segment( + self._sequence, self._stream_buffer.segment, duration, self._stream_id + ) + for stream_output in self._outputs: + stream_output.put(segment) def discontinuity(self): """Mark the stream as having been restarted.""" @@ -118,9 +117,8 @@ def discontinuity(self): self._stream_id += 1 def close(self): - """Close all StreamBuffers.""" - for (buffer, _) in self._outputs: - buffer.output.close() + """Close stream buffer.""" + self._stream_buffer.output.close() def stream_worker(source, options, segment_buffer, quit_event): diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 9d418c360b1f8f..199020097bda50 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -174,7 +174,20 @@ async def test_recorder_save(tmpdir): filename = f"{tmpdir}/test.mp4" # Run - recorder_save_worker(filename, [Segment(1, source, 4)], "mp4") + recorder_save_worker(filename, [Segment(1, source, 4)]) + + # Assert + assert os.path.exists(filename) + + +async def test_recorder_discontinuity(tmpdir): + """Test recorder save across a discontinuity.""" + # Setup + source = generate_h264_video() + filename = f"{tmpdir}/test.mp4" + + # Run + recorder_save_worker(filename, [Segment(1, source, 4, 0), Segment(2, source, 4, 1)]) # Assert assert os.path.exists(filename) From fb2a100f5e366dc154ccf1285085a684972b5047 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Feb 2021 17:27:09 -1000 Subject: [PATCH 0697/1818] Add suggested area to tado (#46932) I realized tado shows what we internally call `zone_name` as the room name in the app. --- homeassistant/components/tado/entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index e9fefe2848bb4a..34473a45c98151 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -50,6 +50,7 @@ def device_info(self): "name": self.zone_name, "manufacturer": DEFAULT_NAME, "model": TADO_ZONE, + "suggested_area": self.zone_name, } @property From bd87047ff25600bea1309e0bbea1e7411afaf679 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Feb 2021 17:27:21 -1000 Subject: [PATCH 0698/1818] Update tasmota to use new fan entity model (#45877) --- homeassistant/components/tasmota/fan.py | 62 ++++++++++++++----------- tests/components/tasmota/test_fan.py | 37 ++++++++++++++- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/tasmota/fan.py b/homeassistant/components/tasmota/fan.py index 1d7aa9f38cb813..77b4532c001ff1 100644 --- a/homeassistant/components/tasmota/fan.py +++ b/homeassistant/components/tasmota/fan.py @@ -1,24 +1,27 @@ """Support for Tasmota fans.""" +from typing import Optional + from hatasmota import const as tasmota_const from homeassistant.components import fan from homeassistant.components.fan import FanEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, +) from .const import DATA_REMOVE_DISCOVER_COMPONENT from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate -HA_TO_TASMOTA_SPEED_MAP = { - fan.SPEED_OFF: tasmota_const.FAN_SPEED_OFF, - fan.SPEED_LOW: tasmota_const.FAN_SPEED_LOW, - fan.SPEED_MEDIUM: tasmota_const.FAN_SPEED_MEDIUM, - fan.SPEED_HIGH: tasmota_const.FAN_SPEED_HIGH, -} - -TASMOTA_TO_HA_SPEED_MAP = {v: k for k, v in HA_TO_TASMOTA_SPEED_MAP.items()} +ORDERED_NAMED_FAN_SPEEDS = [ + tasmota_const.FAN_SPEED_LOW, + tasmota_const.FAN_SPEED_MEDIUM, + tasmota_const.FAN_SPEED_HIGH, +] # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): @@ -56,42 +59,45 @@ def __init__(self, **kwds): ) @property - def speed(self): - """Return the current speed.""" - return TASMOTA_TO_HA_SPEED_MAP.get(self._state) + def speed_count(self) -> Optional[int]: + """Return the number of speeds the fan supports.""" + return len(ORDERED_NAMED_FAN_SPEEDS) @property - def speed_list(self): - """Get the list of available speeds.""" - return list(HA_TO_TASMOTA_SPEED_MAP) + def percentage(self): + """Return the current speed percentage.""" + if self._state is None: + return None + if self._state == 0: + return 0 + return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, self._state) @property def supported_features(self): """Flag supported features.""" return fan.SUPPORT_SET_SPEED - async def async_set_speed(self, speed): + async def async_set_percentage(self, percentage): """Set the speed of the fan.""" - if speed not in HA_TO_TASMOTA_SPEED_MAP: - raise ValueError(f"Unsupported speed {speed}") - if speed == fan.SPEED_OFF: + if percentage == 0: await self.async_turn_off() else: - self._tasmota_entity.set_speed(HA_TO_TASMOTA_SPEED_MAP[speed]) - - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # + tasmota_speed = percentage_to_ordered_list_item( + ORDERED_NAMED_FAN_SPEEDS, percentage + ) + self._tasmota_entity.set_speed(tasmota_speed) + async def async_turn_on( self, speed=None, percentage=None, preset_mode=None, **kwargs ): """Turn the fan on.""" # Tasmota does not support turning a fan on with implicit speed - await self.async_set_speed(speed or fan.SPEED_MEDIUM) + await self.async_set_percentage( + percentage + or ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, tasmota_const.FAN_SPEED_MEDIUM + ) + ) async def async_turn_off(self, **kwargs): """Turn the fan off.""" diff --git a/tests/components/tasmota/test_fan.py b/tests/components/tasmota/test_fan.py index 4035c877bb87c5..a64c5e9c5e4136 100644 --- a/tests/components/tasmota/test_fan.py +++ b/tests/components/tasmota/test_fan.py @@ -52,6 +52,7 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("fan.tasmota") assert state.state == STATE_OFF assert state.attributes["speed"] is None + assert state.attributes["percentage"] is None assert state.attributes["speed_list"] == ["off", "low", "medium", "high"] assert state.attributes["supported_features"] == fan.SUPPORT_SET_SPEED assert not state.attributes.get(ATTR_ASSUMED_STATE) @@ -60,31 +61,37 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("fan.tasmota") assert state.state == STATE_ON assert state.attributes["speed"] == "low" + assert state.attributes["percentage"] == 33 async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":2}') state = hass.states.get("fan.tasmota") assert state.state == STATE_ON assert state.attributes["speed"] == "medium" + assert state.attributes["percentage"] == 66 async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":3}') state = hass.states.get("fan.tasmota") assert state.state == STATE_ON assert state.attributes["speed"] == "high" + assert state.attributes["percentage"] == 100 async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":0}') state = hass.states.get("fan.tasmota") assert state.state == STATE_OFF assert state.attributes["speed"] == "off" + assert state.attributes["percentage"] == 0 async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"FanSpeed":1}') state = hass.states.get("fan.tasmota") assert state.state == STATE_ON assert state.attributes["speed"] == "low" + assert state.attributes["percentage"] == 33 async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"FanSpeed":0}') state = hass.states.get("fan.tasmota") assert state.state == STATE_OFF assert state.attributes["speed"] == "off" + assert state.attributes["percentage"] == 0 async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): @@ -151,6 +158,34 @@ async def test_sending_mqtt_commands(hass, mqtt_mock, setup_tasmota): mqtt_mock.async_publish.assert_called_once_with( "tasmota_49A3BC/cmnd/FanSpeed", "3", 0, False ) + mqtt_mock.async_publish.reset_mock() + + # Set speed percentage and verify MQTT message is sent + await common.async_set_percentage(hass, "fan.tasmota", 0) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/FanSpeed", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Set speed percentage and verify MQTT message is sent + await common.async_set_percentage(hass, "fan.tasmota", 15) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/FanSpeed", "1", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Set speed percentage and verify MQTT message is sent + await common.async_set_percentage(hass, "fan.tasmota", 50) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/FanSpeed", "2", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Set speed percentage and verify MQTT message is sent + await common.async_set_percentage(hass, "fan.tasmota", 90) + mqtt_mock.async_publish.assert_called_once_with( + "tasmota_49A3BC/cmnd/FanSpeed", "3", 0, False + ) async def test_invalid_fan_speed(hass, mqtt_mock, setup_tasmota): @@ -176,7 +211,7 @@ async def test_invalid_fan_speed(hass, mqtt_mock, setup_tasmota): # Set an unsupported speed and verify MQTT message is not sent with pytest.raises(ValueError) as excinfo: await common.async_set_speed(hass, "fan.tasmota", "no_such_speed") - assert "Unsupported speed no_such_speed" in str(excinfo.value) + assert "no_such_speed" in str(excinfo.value) mqtt_mock.async_publish.assert_not_called() From f33618d33d1337e38f58d53c5ed527b58856e9d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Feb 2021 17:27:33 -1000 Subject: [PATCH 0699/1818] Add suggested area support to isy994 (#46927) --- homeassistant/components/isy994/entity.py | 2 ++ homeassistant/components/isy994/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index 95bd43facde0b6..a484b56b145b5e 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -100,6 +100,8 @@ def device_info(self): f"ProductID:{node.zwave_props.product_id}" ) # Note: sw_version is not exposed by the ISY for the individual devices. + if hasattr(node, "folder") and node.folder is not None: + device_info["suggested_area"] = node.folder return device_info diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 9e22b3533d7a5b..3769cc328db36f 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -2,7 +2,7 @@ "domain": "isy994", "name": "Universal Devices ISY994", "documentation": "https://www.home-assistant.io/integrations/isy994", - "requirements": ["pyisy==2.1.0"], + "requirements": ["pyisy==2.1.1"], "codeowners": ["@bdraco", "@shbatm"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 081881b250507e..7966859d341b94 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1465,7 +1465,7 @@ pyirishrail==0.0.2 pyiss==1.0.1 # homeassistant.components.isy994 -pyisy==2.1.0 +pyisy==2.1.1 # homeassistant.components.itach pyitachip2ir==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 376eef402e43ad..489e365fad8d19 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -770,7 +770,7 @@ pyipp==0.11.0 pyiqvia==0.3.1 # homeassistant.components.isy994 -pyisy==2.1.0 +pyisy==2.1.1 # homeassistant.components.kira pykira==0.1.1 From 08889a9819295bb711b48d4e8721ab66d393dd5f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Feb 2021 20:14:39 -1000 Subject: [PATCH 0700/1818] Fix smaty fan typing (#46941) --- homeassistant/components/smarty/fan.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smarty/fan.py b/homeassistant/components/smarty/fan.py index 20376e1d44edf3..481d2e56d3d597 100644 --- a/homeassistant/components/smarty/fan.py +++ b/homeassistant/components/smarty/fan.py @@ -2,7 +2,6 @@ import logging import math -from typing import Optional from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity from homeassistant.core import callback @@ -65,12 +64,12 @@ def is_on(self): return bool(self._smarty_fan_speed) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return int_states_in_range(SPEED_RANGE) @property - def percentage(self) -> str: + def percentage(self) -> int: """Return speed percentage of the fan.""" if self._smarty_fan_speed == 0: return 0 From 4fdb617e22de89dcb385b7dccb918e6572f313e2 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 23 Feb 2021 03:56:44 -0500 Subject: [PATCH 0701/1818] Clean up constants (#46924) * Clean up constants * fix imports --- homeassistant/components/abode/__init__.py | 2 +- homeassistant/components/accuweather/const.py | 2 +- homeassistant/components/airnow/sensor.py | 2 +- homeassistant/components/awair/const.py | 2 +- homeassistant/components/brother/const.py | 3 +-- homeassistant/components/comfoconnect/sensor.py | 2 +- .../components/dlib_face_detect/image_processing.py | 3 +-- .../components/dlib_face_identify/image_processing.py | 2 +- homeassistant/components/elgato/const.py | 1 - homeassistant/components/elgato/light.py | 3 +-- .../components/fritzbox_callmonitor/config_flow.py | 4 ++-- homeassistant/components/fritzbox_callmonitor/const.py | 1 - homeassistant/components/gios/const.py | 1 - homeassistant/components/habitica/__init__.py | 9 +++++++-- homeassistant/components/habitica/const.py | 3 +-- homeassistant/components/hassio/__init__.py | 2 +- homeassistant/components/hassio/addon_panel.py | 4 ++-- homeassistant/components/hassio/const.py | 2 -- homeassistant/components/hassio/discovery.py | 4 ++-- homeassistant/components/hive/__init__.py | 1 - homeassistant/components/homematicip_cloud/services.py | 3 +-- homeassistant/components/html5/notify.py | 2 +- homeassistant/components/humidifier/__init__.py | 2 +- homeassistant/components/humidifier/const.py | 2 +- .../components/fritzbox_callmonitor/test_config_flow.py | 6 +++--- 25 files changed, 31 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 529e3ff7189b01..20c0624742c228 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -13,6 +13,7 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DATE, + ATTR_DEVICE_ID, ATTR_ENTITY_ID, ATTR_TIME, CONF_PASSWORD, @@ -32,7 +33,6 @@ SERVICE_CAPTURE_IMAGE = "capture_image" SERVICE_TRIGGER_AUTOMATION = "trigger_automation" -ATTR_DEVICE_ID = "device_id" ATTR_DEVICE_NAME = "device_name" ATTR_DEVICE_TYPE = "device_type" ATTR_EVENT_CODE = "event_code" diff --git a/homeassistant/components/accuweather/const.py b/homeassistant/components/accuweather/const.py index e8dbe921d7707f..60fdd48c8f4c02 100644 --- a/homeassistant/components/accuweather/const.py +++ b/homeassistant/components/accuweather/const.py @@ -17,6 +17,7 @@ ) from homeassistant.const import ( ATTR_DEVICE_CLASS, + ATTR_ICON, CONCENTRATION_PARTS_PER_CUBIC_METER, DEVICE_CLASS_TEMPERATURE, LENGTH_FEET, @@ -33,7 +34,6 @@ ) ATTRIBUTION = "Data provided by AccuWeather" -ATTR_ICON = "icon" ATTR_FORECAST = CONF_FORECAST = "forecast" ATTR_LABEL = "label" ATTR_UNIT_IMPERIAL = "Imperial" diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index fed6def2b36e62..4488098701f135 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -2,6 +2,7 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, + ATTR_ICON, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_MILLION, ) @@ -20,7 +21,6 @@ ATTRIBUTION = "Data provided by AirNow" -ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_UNIT = "unit" diff --git a/homeassistant/components/awair/const.py b/homeassistant/components/awair/const.py index b262fdec572086..44490b8401f4dc 100644 --- a/homeassistant/components/awair/const.py +++ b/homeassistant/components/awair/const.py @@ -8,6 +8,7 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, + ATTR_ICON, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, @@ -33,7 +34,6 @@ ATTRIBUTION = "Awair air quality sensor" -ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_UNIT = "unit" ATTR_UNIQUE_ID = "unique_id" diff --git a/homeassistant/components/brother/const.py b/homeassistant/components/brother/const.py index 5ae459c79aa144..07843b0f3d0b1c 100644 --- a/homeassistant/components/brother/const.py +++ b/homeassistant/components/brother/const.py @@ -1,5 +1,5 @@ """Constants for Brother integration.""" -from homeassistant.const import PERCENTAGE +from homeassistant.const import ATTR_ICON, PERCENTAGE ATTR_BELT_UNIT_REMAINING_LIFE = "belt_unit_remaining_life" ATTR_BLACK_DRUM_COUNTER = "black_drum_counter" @@ -20,7 +20,6 @@ ATTR_DUPLEX_COUNTER = "duplex_unit_pages_counter" ATTR_ENABLED = "enabled" ATTR_FUSER_REMAINING_LIFE = "fuser_remaining_life" -ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_LASER_REMAINING_LIFE = "laser_remaining_life" ATTR_MAGENTA_DRUM_COUNTER = "magenta_drum_counter" diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 660228b0b8d5cd..87fa8f4a1a6edb 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -29,6 +29,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_DEVICE_CLASS, + ATTR_ICON, ATTR_ID, CONF_RESOURCES, DEVICE_CLASS_ENERGY, @@ -72,7 +73,6 @@ _LOGGER = logging.getLogger(__name__) -ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_MULTIPLIER = "multiplier" ATTR_UNIT = "unit" diff --git a/homeassistant/components/dlib_face_detect/image_processing.py b/homeassistant/components/dlib_face_detect/image_processing.py index c2bec855b9b965..2a5e7662d454a0 100644 --- a/homeassistant/components/dlib_face_detect/image_processing.py +++ b/homeassistant/components/dlib_face_detect/image_processing.py @@ -9,6 +9,7 @@ CONF_SOURCE, ImageProcessingFaceEntity, ) +from homeassistant.const import ATTR_LOCATION from homeassistant.core import split_entity_id # pylint: disable=unused-import @@ -16,8 +17,6 @@ PLATFORM_SCHEMA, ) -ATTR_LOCATION = "location" - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Dlib Face detection platform.""" diff --git a/homeassistant/components/dlib_face_identify/image_processing.py b/homeassistant/components/dlib_face_identify/image_processing.py index 32c2aa5868c009..f9db607c2986e8 100644 --- a/homeassistant/components/dlib_face_identify/image_processing.py +++ b/homeassistant/components/dlib_face_identify/image_processing.py @@ -14,12 +14,12 @@ PLATFORM_SCHEMA, ImageProcessingFaceEntity, ) +from homeassistant.const import ATTR_NAME from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_NAME = "name" CONF_FACES = "faces" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/elgato/const.py b/homeassistant/components/elgato/const.py index 2b6caa37a8f9c8..b2535ce0e4f986 100644 --- a/homeassistant/components/elgato/const.py +++ b/homeassistant/components/elgato/const.py @@ -12,6 +12,5 @@ ATTR_MODEL = "model" ATTR_ON = "on" ATTR_SOFTWARE_VERSION = "sw_version" -ATTR_TEMPERATURE = "temperature" CONF_SERIAL_NUMBER = "serial_number" diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index eea80e60b15432..0648a4817bc3dd 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -15,7 +15,7 @@ LightEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_NAME +from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType @@ -25,7 +25,6 @@ ATTR_MODEL, ATTR_ON, ATTR_SOFTWARE_VERSION, - ATTR_TEMPERATURE, DATA_ELGATO_CLIENT, DOMAIN, ) diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index a08450e20a1832..01a43f7c7ef405 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -7,6 +7,7 @@ from homeassistant import config_entries from homeassistant.const import ( + ATTR_NAME, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -27,7 +28,6 @@ DEFAULT_USERNAME, DOMAIN, FRITZ_ACTION_GET_INFO, - FRITZ_ATTR_NAME, FRITZ_ATTR_SERIAL_NUMBER, FRITZ_SERVICE_DEVICE_INFO, SERIAL_NUMBER, @@ -119,7 +119,7 @@ async def _get_name_of_phonebook(self, phonebook_id): phonebook_info = await self.hass.async_add_executor_job( self._fritzbox_phonebook.fph.phonebook_info, phonebook_id ) - return phonebook_info[FRITZ_ATTR_NAME] + return phonebook_info[ATTR_NAME] async def _get_list_of_phonebook_names(self): """Return list of names for all available phonebooks.""" diff --git a/homeassistant/components/fritzbox_callmonitor/const.py b/homeassistant/components/fritzbox_callmonitor/const.py index a71f14401b3cf2..6f0c87f5273cd1 100644 --- a/homeassistant/components/fritzbox_callmonitor/const.py +++ b/homeassistant/components/fritzbox_callmonitor/const.py @@ -15,7 +15,6 @@ ATTR_PREFIXES = "prefixes" FRITZ_ACTION_GET_INFO = "GetInfo" -FRITZ_ATTR_NAME = "name" FRITZ_ATTR_SERIAL_NUMBER = "NewSerialNumber" FRITZ_SERVICE_DEVICE_INFO = "DeviceInfo" diff --git a/homeassistant/components/gios/const.py b/homeassistant/components/gios/const.py index 117eada036b2a6..ab354e319a8232 100644 --- a/homeassistant/components/gios/const.py +++ b/homeassistant/components/gios/const.py @@ -1,7 +1,6 @@ """Constants for GIOS integration.""" from datetime import timedelta -ATTR_NAME = "name" ATTR_STATION = "station" CONF_STATION_ID = "station_id" DEFAULT_NAME = "GIOŚ" diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index ca3837ef8ca5cc..64680a56bb31bc 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -7,14 +7,19 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_SENSORS, CONF_URL +from homeassistant.const import ( + ATTR_NAME, + CONF_API_KEY, + CONF_NAME, + CONF_SENSORS, + CONF_URL, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( ATTR_ARGS, - ATTR_NAME, ATTR_PATH, CONF_API_USER, DEFAULT_URL, diff --git a/homeassistant/components/habitica/const.py b/homeassistant/components/habitica/const.py index 438bcec9d94e90..02a46334c7a056 100644 --- a/homeassistant/components/habitica/const.py +++ b/homeassistant/components/habitica/const.py @@ -1,6 +1,6 @@ """Constants for the habitica integration.""" -from homeassistant.const import CONF_NAME, CONF_PATH +from homeassistant.const import CONF_PATH CONF_API_USER = "api_user" @@ -8,7 +8,6 @@ DOMAIN = "habitica" SERVICE_API_CALL = "api_call" -ATTR_NAME = CONF_NAME ATTR_PATH = CONF_PATH ATTR_ARGS = "args" EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success" diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 4f6f0ed8348d8b..fdeb10bcafe35b 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -10,6 +10,7 @@ from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util from homeassistant.const import ( + ATTR_NAME, EVENT_CORE_CONFIG_UPDATE, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, @@ -30,7 +31,6 @@ ATTR_FOLDERS, ATTR_HOMEASSISTANT, ATTR_INPUT, - ATTR_NAME, ATTR_PASSWORD, ATTR_SNAPSHOT, DOMAIN, diff --git a/homeassistant/components/hassio/addon_panel.py b/homeassistant/components/hassio/addon_panel.py index 9e44b961a1c534..a48c8b4d05b14e 100644 --- a/homeassistant/components/hassio/addon_panel.py +++ b/homeassistant/components/hassio/addon_panel.py @@ -5,10 +5,10 @@ from aiohttp import web from homeassistant.components.http import HomeAssistantView -from homeassistant.const import HTTP_BAD_REQUEST +from homeassistant.const import ATTR_ICON, HTTP_BAD_REQUEST from homeassistant.helpers.typing import HomeAssistantType -from .const import ATTR_ADMIN, ATTR_ENABLE, ATTR_ICON, ATTR_PANELS, ATTR_TITLE +from .const import ATTR_ADMIN, ATTR_ENABLE, ATTR_PANELS, ATTR_TITLE from .handler import HassioAPIError _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 0cb1649dfc55af..a3e4451312a3da 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -11,9 +11,7 @@ ATTR_ENABLE = "enable" ATTR_FOLDERS = "folders" ATTR_HOMEASSISTANT = "homeassistant" -ATTR_ICON = "icon" ATTR_INPUT = "input" -ATTR_NAME = "name" ATTR_PANELS = "panels" ATTR_PASSWORD = "password" ATTR_SNAPSHOT = "snapshot" diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index cda05eccbecd56..c682e34c301063 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -6,10 +6,10 @@ from aiohttp.web_exceptions import HTTPServiceUnavailable from homeassistant.components.http import HomeAssistantView -from homeassistant.const import ATTR_SERVICE, EVENT_HOMEASSISTANT_START +from homeassistant.const import ATTR_NAME, ATTR_SERVICE, EVENT_HOMEASSISTANT_START from homeassistant.core import callback -from .const import ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_NAME, ATTR_UUID +from .const import ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_UUID from .handler import HassioAPIError _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 6245db5ea7efde..331ab37224f37e 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -23,7 +23,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_AVAILABLE = "available" -ATTR_MODE = "mode" DOMAIN = "hive" DATA_HIVE = "data_hive" SERVICES = ["Heating", "HotWater", "TRV"] diff --git a/homeassistant/components/homematicip_cloud/services.py b/homeassistant/components/homematicip_cloud/services.py index d8535edda50e97..7c92ac5e7211c1 100644 --- a/homeassistant/components/homematicip_cloud/services.py +++ b/homeassistant/components/homematicip_cloud/services.py @@ -9,7 +9,7 @@ from homematicip.base.helpers import handle_config import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import comp_entity_ids from homeassistant.helpers.service import ( @@ -29,7 +29,6 @@ ATTR_CONFIG_OUTPUT_PATH = "config_output_path" ATTR_DURATION = "duration" ATTR_ENDTIME = "endtime" -ATTR_TEMPERATURE = "temperature" DEFAULT_CONFIG_FILE_PREFIX = "hmip-config" diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index c07cddb7a9cc56..33dd8118ee49cc 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -26,6 +26,7 @@ BaseNotificationService, ) from homeassistant.const import ( + ATTR_NAME, HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR, HTTP_UNAUTHORIZED, @@ -73,7 +74,6 @@ def gcm_api_deprecated(value): ATTR_SUBSCRIPTION = "subscription" ATTR_BROWSER = "browser" -ATTR_NAME = "name" ATTR_ENDPOINT = "endpoint" ATTR_KEYS = "keys" diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index fc455feb477a6c..1763e169d50bc6 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -7,6 +7,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_MODE, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -27,7 +28,6 @@ ATTR_HUMIDITY, ATTR_MAX_HUMIDITY, ATTR_MIN_HUMIDITY, - ATTR_MODE, DEFAULT_MAX_HUMIDITY, DEFAULT_MIN_HUMIDITY, DEVICE_CLASS_DEHUMIDIFIER, diff --git a/homeassistant/components/humidifier/const.py b/homeassistant/components/humidifier/const.py index 82e87ae5c31d59..7e70c51df28020 100644 --- a/homeassistant/components/humidifier/const.py +++ b/homeassistant/components/humidifier/const.py @@ -1,4 +1,5 @@ """Provides the constants needed for component.""" +from homeassistant.const import ATTR_MODE # noqa: F401 pylint: disable=unused-import MODE_NORMAL = "normal" MODE_ECO = "eco" @@ -10,7 +11,6 @@ MODE_AUTO = "auto" MODE_BABY = "baby" -ATTR_MODE = "mode" ATTR_AVAILABLE_MODES = "available_modes" ATTR_HUMIDITY = "humidity" ATTR_MAX_HUMIDITY = "max_humidity" diff --git a/tests/components/fritzbox_callmonitor/test_config_flow.py b/tests/components/fritzbox_callmonitor/test_config_flow.py index 00bc1e18679b4c..cde30b615eb2c5 100644 --- a/tests/components/fritzbox_callmonitor/test_config_flow.py +++ b/tests/components/fritzbox_callmonitor/test_config_flow.py @@ -14,12 +14,12 @@ CONF_PHONEBOOK, CONF_PREFIXES, DOMAIN, - FRITZ_ATTR_NAME, FRITZ_ATTR_SERIAL_NUMBER, SERIAL_NUMBER, ) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import ( + ATTR_NAME, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -69,8 +69,8 @@ CONF_NAME: MOCK_NAME, } MOCK_DEVICE_INFO = {FRITZ_ATTR_SERIAL_NUMBER: MOCK_SERIAL_NUMBER} -MOCK_PHONEBOOK_INFO_1 = {FRITZ_ATTR_NAME: MOCK_PHONEBOOK_NAME_1} -MOCK_PHONEBOOK_INFO_2 = {FRITZ_ATTR_NAME: MOCK_PHONEBOOK_NAME_2} +MOCK_PHONEBOOK_INFO_1 = {ATTR_NAME: MOCK_PHONEBOOK_NAME_1} +MOCK_PHONEBOOK_INFO_2 = {ATTR_NAME: MOCK_PHONEBOOK_NAME_2} MOCK_UNIQUE_ID = f"{MOCK_SERIAL_NUMBER}-{MOCK_PHONEBOOK_ID}" From 6fe72b04eb507a111324ae636b8285790fbc1e96 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 23 Feb 2021 13:24:07 +0100 Subject: [PATCH 0702/1818] Add zwave_js constant for add-on slug (#46950) --- homeassistant/components/zwave_js/__init__.py | 5 +++-- homeassistant/components/zwave_js/config_flow.py | 11 ++++++----- homeassistant/components/zwave_js/const.py | 2 ++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index f6edb2f4596e44..530a8022233042 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -21,6 +21,7 @@ from .api import async_register_api from .const import ( + ADDON_SLUG, ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DEVICE_ID, @@ -333,11 +334,11 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: return try: - await hass.components.hassio.async_stop_addon("core_zwave_js") + await hass.components.hassio.async_stop_addon(ADDON_SLUG) except HassioAPIError as err: LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) return try: - await hass.components.hassio.async_uninstall_addon("core_zwave_js") + await hass.components.hassio.async_uninstall_addon(ADDON_SLUG) except HassioAPIError as err: LOGGER.error("Failed to uninstall the Z-Wave JS add-on: %s", err) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index b18e28419ddff8..ec74acf988641c 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -14,6 +14,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( # pylint:disable=unused-import + ADDON_SLUG, CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN, @@ -248,7 +249,7 @@ async def async_step_start_addon( await self._async_set_addon_config(new_addon_config) try: - await self.hass.components.hassio.async_start_addon("core_zwave_js") + await self.hass.components.hassio.async_start_addon(ADDON_SLUG) except self.hass.components.hassio.HassioAPIError as err: _LOGGER.error("Failed to start Z-Wave JS add-on: %s", err) errors["base"] = "addon_start_failed" @@ -294,7 +295,7 @@ async def _async_get_addon_info(self) -> dict: """Return and cache Z-Wave JS add-on info.""" try: addon_info: dict = await self.hass.components.hassio.async_get_addon_info( - "core_zwave_js" + ADDON_SLUG ) except self.hass.components.hassio.HassioAPIError as err: _LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err) @@ -322,7 +323,7 @@ async def _async_set_addon_config(self, config: dict) -> None: options = {"options": config} try: await self.hass.components.hassio.async_set_addon_options( - "core_zwave_js", options + ADDON_SLUG, options ) except self.hass.components.hassio.HassioAPIError as err: _LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err) @@ -331,7 +332,7 @@ async def _async_set_addon_config(self, config: dict) -> None: async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" try: - await self.hass.components.hassio.async_install_addon("core_zwave_js") + await self.hass.components.hassio.async_install_addon(ADDON_SLUG) finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( @@ -343,7 +344,7 @@ async def _async_get_addon_discovery_info(self) -> dict: try: discovery_info: dict = ( await self.hass.components.hassio.async_get_addon_discovery_info( - "core_zwave_js" + ADDON_SLUG ) ) except self.hass.components.hassio.HassioAPIError as err: diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 9905eba06937ba..5eb537c0d4ee19 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -32,3 +32,5 @@ ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" ATTR_PARAMETERS = "parameters" + +ADDON_SLUG = "core_zwave_js" From 593e7aea5a06cc724f5b12fbca70d595bcb252d4 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 23 Feb 2021 14:00:21 +0100 Subject: [PATCH 0703/1818] Bump accuweather to 0.1.0 (#46951) --- homeassistant/components/accuweather/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index 6ccd6a4f10b1f5..b03c0e510180c6 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -2,7 +2,7 @@ "domain": "accuweather", "name": "AccuWeather", "documentation": "https://www.home-assistant.io/integrations/accuweather/", - "requirements": ["accuweather==0.0.11"], + "requirements": ["accuweather==0.1.0"], "codeowners": ["@bieniu"], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 7966859d341b94..00bd7af0d74d52 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -96,7 +96,7 @@ WazeRouteCalculator==0.12 abodepy==1.2.0 # homeassistant.components.accuweather -accuweather==0.0.11 +accuweather==0.1.0 # homeassistant.components.bmp280 adafruit-circuitpython-bmp280==3.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 489e365fad8d19..3a0954f41ad425 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -42,7 +42,7 @@ WSDiscovery==2.0.0 abodepy==1.2.0 # homeassistant.components.accuweather -accuweather==0.0.11 +accuweather==0.1.0 # homeassistant.components.androidtv adb-shell[async]==0.2.1 From ea4bbd771f165f7a55014c6fe620600601a20315 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Feb 2021 14:10:13 +0100 Subject: [PATCH 0704/1818] Add service names to previously enriched services (#46929) Co-authored-by: Tobias Sauerwein --- .../alarm_control_panel/services.yaml | 18 ++++-- homeassistant/components/alert/services.yaml | 9 ++- .../components/automation/services.yaml | 15 +++-- .../components/browser/services.yaml | 3 +- homeassistant/components/camera/services.yaml | 21 ++++--- .../components/climate/services.yaml | 27 ++++++--- .../components/color_extractor/services.yaml | 3 +- .../components/conversation/services.yaml | 3 +- .../components/counter/services.yaml | 12 ++-- homeassistant/components/cover/services.yaml | 30 ++++++---- .../components/downloader/services.yaml | 3 +- homeassistant/components/fan/services.yaml | 30 ++++++---- .../components/homeassistant/services.yaml | 12 ++-- homeassistant/components/hue/services.yaml | 3 +- .../components/input_datetime/services.yaml | 6 +- .../components/input_number/services.yaml | 11 +++- .../components/input_select/services.yaml | 19 ++++-- .../components/input_text/services.yaml | 4 +- homeassistant/components/light/services.yaml | 6 +- homeassistant/components/lock/services.yaml | 12 ++-- homeassistant/components/logger/services.yaml | 22 +++---- .../components/media_player/services.yaml | 58 +++++++++++++------ homeassistant/components/mqtt/services.yaml | 11 ++-- .../components/netatmo/services.yaml | 13 ++++- homeassistant/components/number/services.yaml | 3 +- .../components/recorder/services.yaml | 3 +- homeassistant/components/scene/services.yaml | 12 ++-- .../components/shopping_list/services.yaml | 6 +- homeassistant/components/sonos/services.yaml | 16 ++++- .../components/system_log/services.yaml | 6 +- homeassistant/components/timer/services.yaml | 10 +++- homeassistant/components/toon/services.yaml | 1 + .../components/twentemilieu/services.yaml | 1 + homeassistant/components/vacuum/services.yaml | 34 +++++++---- homeassistant/components/vizio/services.yaml | 10 +++- homeassistant/components/wled/services.yaml | 6 +- 36 files changed, 308 insertions(+), 151 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/services.yaml b/homeassistant/components/alarm_control_panel/services.yaml index a6151c58db03d2..b18f1cfb78270a 100644 --- a/homeassistant/components/alarm_control_panel/services.yaml +++ b/homeassistant/components/alarm_control_panel/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available alarm control panel services alarm_disarm: - description: Send the alarm the command for disarm + name: Disarm + description: Send the alarm the command for disarm. target: fields: code: @@ -12,7 +13,8 @@ alarm_disarm: text: alarm_arm_custom_bypass: - description: Send arm custom bypass command + name: Arm with custom bypass + description: Send arm custom bypass command. target: fields: code: @@ -24,7 +26,8 @@ alarm_arm_custom_bypass: text: alarm_arm_home: - description: Send the alarm the command for arm home + name: Arm home + description: Send the alarm the command for arm home. target: fields: code: @@ -35,7 +38,8 @@ alarm_arm_home: text: alarm_arm_away: - description: Send the alarm the command for arm away + name: Arm away + description: Send the alarm the command for arm away. target: fields: code: @@ -46,7 +50,8 @@ alarm_arm_away: text: alarm_arm_night: - description: Send the alarm the command for arm night + name: Arm night + description: Send the alarm the command for arm night. target: fields: code: @@ -57,7 +62,8 @@ alarm_arm_night: text: alarm_trigger: - description: Send the alarm the command for trigger + name: Trigger + description: Send the alarm the command for trigger. target: fields: code: diff --git a/homeassistant/components/alert/services.yaml b/homeassistant/components/alert/services.yaml index c8c1d5d814add8..5800d642b93116 100644 --- a/homeassistant/components/alert/services.yaml +++ b/homeassistant/components/alert/services.yaml @@ -1,11 +1,14 @@ toggle: - description: Toggle alert's notifications + name: Toggle + description: Toggle alert's notifications. target: turn_off: - description: Silence alert's notifications + name: Turn off + description: Silence alert's notifications. target: turn_on: - description: Reset alert's notifications + name: Turn on + description: Reset alert's notifications. target: diff --git a/homeassistant/components/automation/services.yaml b/homeassistant/components/automation/services.yaml index 95f2057f5ee0f1..5d399fb253efc6 100644 --- a/homeassistant/components/automation/services.yaml +++ b/homeassistant/components/automation/services.yaml @@ -1,10 +1,12 @@ # Describes the format for available automation services turn_on: - description: Enable an automation + name: Turn on + description: Enable an automation. target: turn_off: - description: Disable an automation + name: Turn off + description: Disable an automation. target: fields: stop_actions: @@ -16,11 +18,13 @@ turn_off: boolean: toggle: - description: Toggle (enable / disable) an automation + name: Toggle + description: Toggle (enable / disable) an automation. target: trigger: - description: Trigger the actions of an automation + name: Trigger + description: Trigger the actions of an automation. target: fields: skip_condition: @@ -32,4 +36,5 @@ trigger: boolean: reload: - description: Reload the automation configuration + name: Reload + description: Reload the automation configuration. diff --git a/homeassistant/components/browser/services.yaml b/homeassistant/components/browser/services.yaml index f6c5e7c90e1e4f..1014e50db21204 100644 --- a/homeassistant/components/browser/services.yaml +++ b/homeassistant/components/browser/services.yaml @@ -1,6 +1,7 @@ browse_url: + name: Browse description: - Open a URL in the default browser on the host machine of Home Assistant + Open a URL in the default browser on the host machine of Home Assistant. fields: url: name: URL diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml index 3ae5b650304952..3c8e99f001ba26 100644 --- a/homeassistant/components/camera/services.yaml +++ b/homeassistant/components/camera/services.yaml @@ -1,23 +1,28 @@ # Describes the format for available camera services turn_off: - description: Turn off camera + name: Turn off + description: Turn off camera. target: turn_on: - description: Turn on camera + name: Turn on + description: Turn on camera. target: enable_motion_detection: - description: Enable the motion detection in a camera + name: Enable motion detection + description: Enable the motion detection in a camera. target: disable_motion_detection: - description: Disable the motion detection in a camera + name: Disable motion detection + description: Disable the motion detection in a camera. target: snapshot: - description: Take a snapshot from a camera + name: Take snapshot + description: Take a snapshot from a camera. target: fields: filename: @@ -29,7 +34,8 @@ snapshot: text: play_stream: - description: Play camera stream on supported media player + name: Play stream + description: Play camera stream on supported media player. target: fields: media_player: @@ -51,7 +57,8 @@ play_stream: - "hls" record: - description: Record live camera feed + name: Record + description: Record live camera feed. target: fields: filename: diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index d333260202f975..ca88896c6c251b 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available climate services set_aux_heat: - description: Turn auxiliary heater on/off for climate device + name: Turn on/off auxiliary heater + description: Turn auxiliary heater on/off for climate device. target: fields: aux_heat: @@ -13,7 +14,8 @@ set_aux_heat: boolean: set_preset_mode: - description: Set preset mode for climate device + name: Set preset mode + description: Set preset mode for climate device. target: fields: preset_mode: @@ -25,7 +27,8 @@ set_preset_mode: text: set_temperature: - description: Set target temperature of climate device + name: Set temperature + description: Set target temperature of climate device. target: fields: temperature: @@ -76,7 +79,8 @@ set_temperature: - "heat" set_humidity: - description: Set target humidity of climate device + name: Set target humidity + description: Set target humidity of climate device. target: fields: humidity: @@ -93,7 +97,8 @@ set_humidity: mode: slider set_fan_mode: - description: Set fan operation for climate device + name: Set fan mode + description: Set fan operation for climate device. target: fields: fan_mode: @@ -105,7 +110,8 @@ set_fan_mode: text: set_hvac_mode: - description: Set HVAC operation mode for climate device + name: Set HVAC mode + description: Set HVAC operation mode for climate device. target: fields: hvac_mode: @@ -124,7 +130,8 @@ set_hvac_mode: - "heat" set_swing_mode: - description: Set swing operation for climate device + name: Set swing mode + description: Set swing operation for climate device. target: fields: swing_mode: @@ -136,9 +143,11 @@ set_swing_mode: text: turn_on: - description: Turn climate device on + name: Turn on + description: Turn climate device on. target: turn_off: - description: Turn climate device off + name: Turn off + description: Turn climate device off. target: diff --git a/homeassistant/components/color_extractor/services.yaml b/homeassistant/components/color_extractor/services.yaml index 671a2a2ebb9360..00438dc9aa17f0 100644 --- a/homeassistant/components/color_extractor/services.yaml +++ b/homeassistant/components/color_extractor/services.yaml @@ -1,7 +1,8 @@ turn_on: + name: Turn on description: Set the light RGB to the predominant color found in the image provided by - URL or file path + URL or file path. target: fields: color_extract_url: diff --git a/homeassistant/components/conversation/services.yaml b/homeassistant/components/conversation/services.yaml index 5c85de7c187670..edba9ffb0b9fee 100644 --- a/homeassistant/components/conversation/services.yaml +++ b/homeassistant/components/conversation/services.yaml @@ -1,6 +1,7 @@ # Describes the format for available component services process: - description: Launch a conversation from a transcribed text + name: Process + description: Launch a conversation from a transcribed text. fields: text: name: Text diff --git a/homeassistant/components/counter/services.yaml b/homeassistant/components/counter/services.yaml index 16010f6e2f41d5..4dd427c1fa11bd 100644 --- a/homeassistant/components/counter/services.yaml +++ b/homeassistant/components/counter/services.yaml @@ -1,19 +1,23 @@ # Describes the format for available counter services decrement: - description: Decrement a counter + name: Decrement + description: Decrement a counter. target: increment: - description: Increment a counter + name: Increment + description: Increment a counter. target: reset: - description: Reset a counter + name: Reset + description: Reset a counter. target: configure: - description: Change counter parameters + name: Configure + description: Change counter parameters. target: fields: minimum: diff --git a/homeassistant/components/cover/services.yaml b/homeassistant/components/cover/services.yaml index 173674193cb61d..1419a5f48edea9 100644 --- a/homeassistant/components/cover/services.yaml +++ b/homeassistant/components/cover/services.yaml @@ -1,19 +1,23 @@ # Describes the format for available cover services open_cover: - description: Open all or specified cover + name: Open + description: Open all or specified cover. target: close_cover: - description: Close all or specified cover + name: Close + description: Close all or specified cover. target: toggle: - description: Toggles a cover open/closed + name: Toggle + description: Toggle a cover open/closed. target: set_cover_position: - description: Move to specific position all or specified cover + name: Set position + description: Move to specific position all or specified cover. target: fields: position: @@ -30,23 +34,28 @@ set_cover_position: mode: slider stop_cover: - description: Stop all or specified cover + name: Stop + description: Stop all or specified cover. target: open_cover_tilt: - description: Open all or specified cover tilt + name: Open tilt + description: Open all or specified cover tilt. target: close_cover_tilt: - description: Close all or specified cover tilt + name: Close tilt + description: Close all or specified cover tilt. target: toggle_cover_tilt: - description: Toggle a cover tilt open/closed + name: Toggle tilt + description: Toggle a cover tilt open/closed. target: set_cover_tilt_position: - description: Move to specific position all or specified cover tilt + name: Set tilt position + description: Move to specific position all or specified cover tilt. target: fields: tilt_position: @@ -63,5 +72,6 @@ set_cover_tilt_position: mode: slider stop_cover_tilt: - description: Stop all or specified cover + name: Stop tilt + description: Stop all or specified cover. target: diff --git a/homeassistant/components/downloader/services.yaml b/homeassistant/components/downloader/services.yaml index 5ac383fc4f68b5..cecb3804227a77 100644 --- a/homeassistant/components/downloader/services.yaml +++ b/homeassistant/components/downloader/services.yaml @@ -1,5 +1,6 @@ download_file: - description: Downloads a file to the download location + name: Download file + description: Download a file to the download location. fields: url: name: URL diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml index ad513b84e8f0fe..3c8eb2d0761630 100644 --- a/homeassistant/components/fan/services.yaml +++ b/homeassistant/components/fan/services.yaml @@ -1,6 +1,7 @@ # Describes the format for available fan services set_speed: - description: Set fan speed + name: Set speed + description: Set fan speed. target: fields: speed: @@ -12,7 +13,8 @@ set_speed: text: set_preset_mode: - description: Set preset mode for a fan device + name: Set preset mode + description: Set preset mode for a fan device. target: fields: preset_mode: @@ -24,7 +26,8 @@ set_preset_mode: text: set_percentage: - description: Set fan speed percentage + name: Set speed percentage + description: Set fan speed percentage. target: fields: percentage: @@ -41,7 +44,8 @@ set_percentage: mode: slider turn_on: - description: Turn fan on + name: Turn on + description: Turn fan on. target: fields: speed: @@ -67,11 +71,13 @@ turn_on: text: turn_off: - description: Turn fan off + name: Turn off + description: Turn fan off. target: oscillate: - description: Oscillate the fan + name: Oscillate + description: Oscillate the fan. target: fields: oscillating: @@ -83,11 +89,13 @@ oscillate: boolean: toggle: - description: Toggle the fan on/off + name: Toggle + description: Toggle the fan on/off. target: set_direction: - description: Set the fan rotation + name: Set direction + description: Set the fan rotation. target: fields: direction: @@ -102,6 +110,7 @@ set_direction: - "reverse" increase_speed: + name: Increase speed description: Increase the speed of the fan by one speed or a percentage_step. fields: entity_id: @@ -110,7 +119,7 @@ increase_speed: percentage_step: advanced: true required: false - description: Increase speed by a percentage. Should be between 0..100. [optional] + description: Increase speed by a percentage. example: 50 selector: number: @@ -121,6 +130,7 @@ increase_speed: mode: slider decrease_speed: + name: Decrease speed description: Decrease the speed of the fan by one speed or a percentage_step. fields: entity_id: @@ -129,7 +139,7 @@ decrease_speed: percentage_step: advanced: true required: false - description: Decrease speed by a percentage. Should be between 0..100. [optional] + description: Decrease speed by a percentage. example: 50 selector: number: diff --git a/homeassistant/components/homeassistant/services.yaml b/homeassistant/components/homeassistant/services.yaml index d23bdfdba72fed..6bd0a0852eda03 100644 --- a/homeassistant/components/homeassistant/services.yaml +++ b/homeassistant/components/homeassistant/services.yaml @@ -1,16 +1,20 @@ check_config: + name: Check configuration description: Check the Home Assistant configuration files for errors. Errors will be - displayed in the Home Assistant log + displayed in the Home Assistant log. reload_core_config: - description: Reload the core configuration + name: Reload core configuration + description: Reload the core configuration. restart: - description: Restart the Home Assistant service + name: Restart + description: Restart the Home Assistant service. set_location: - description: Update the Home Assistant location + name: Set location + description: Update the Home Assistant location. fields: latitude: name: Latitude diff --git a/homeassistant/components/hue/services.yaml b/homeassistant/components/hue/services.yaml index 80ca25007bcf73..07eeca6fa0fb5b 100644 --- a/homeassistant/components/hue/services.yaml +++ b/homeassistant/components/hue/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available hue services hue_activate_scene: - description: Activate a hue scene stored in the hue hub + name: Activate scene + description: Activate a hue scene stored in the hue hub. fields: group_name: name: Group diff --git a/homeassistant/components/input_datetime/services.yaml b/homeassistant/components/input_datetime/services.yaml index ef4cb9556c8e0c..0243ca9f67dfe8 100644 --- a/homeassistant/components/input_datetime/services.yaml +++ b/homeassistant/components/input_datetime/services.yaml @@ -1,5 +1,6 @@ set_datetime: - description: This can be used to dynamically set the date and/or time + name: Set + description: This can be used to dynamically set the date and/or time. target: fields: date: @@ -33,4 +34,5 @@ set_datetime: mode: box reload: - description: Reload the input_datetime configuration + name: Reload + description: Reload the input_datetime configuration. diff --git a/homeassistant/components/input_number/services.yaml b/homeassistant/components/input_number/services.yaml index 700a2c281441c7..7d388238022582 100644 --- a/homeassistant/components/input_number/services.yaml +++ b/homeassistant/components/input_number/services.yaml @@ -1,13 +1,16 @@ decrement: - description: Decrement the value of an input number entity by its stepping + name: Decrement + description: Decrement the value of an input number entity by its stepping. target: increment: - description: Increment the value of an input number entity by its stepping + name: Increment + description: Increment the value of an input number entity by its stepping. target: set_value: - description: Set the value of an input number entity + name: Set + description: Set the value of an input number entity. target: fields: value: @@ -21,5 +24,7 @@ set_value: max: 9223372036854775807 step: 0.001 mode: box + reload: + name: Reload description: Reload the input_number configuration. diff --git a/homeassistant/components/input_select/services.yaml b/homeassistant/components/input_select/services.yaml index bf1b9e8103362d..f8fbe158aab5d4 100644 --- a/homeassistant/components/input_select/services.yaml +++ b/homeassistant/components/input_select/services.yaml @@ -1,5 +1,6 @@ select_next: - description: Select the next options of an input select entity + name: Next + description: Select the next options of an input select entity. target: fields: cycle: @@ -11,7 +12,8 @@ select_next: boolean: select_option: - description: Select an option of an input select entity + name: Select + description: Select an option of an input select entity. target: fields: option: @@ -23,7 +25,8 @@ select_option: text: select_previous: - description: Select the previous options of an input select entity + name: Previous + description: Select the previous options of an input select entity. target: fields: cycle: @@ -35,15 +38,18 @@ select_previous: boolean: select_first: - description: Select the first option of an input select entity + name: First + description: Select the first option of an input select entity. target: select_last: - description: Select the last option of an input select entity + name: Last + description: Select the last option of an input select entity. target: set_options: - description: Set the options of an input select entity + name: Set options + description: Set the options of an input select entity. target: fields: options: @@ -55,4 +61,5 @@ set_options: object: reload: + name: Reload description: Reload the input_select configuration. diff --git a/homeassistant/components/input_text/services.yaml b/homeassistant/components/input_text/services.yaml index b5ac97f837a50a..5983683ec6dc0c 100644 --- a/homeassistant/components/input_text/services.yaml +++ b/homeassistant/components/input_text/services.yaml @@ -1,5 +1,6 @@ set_value: - description: Set the value of an input text entity + name: Set + description: Set the value of an input text entity. target: fields: value: @@ -11,4 +12,5 @@ set_value: text: reload: + name: Reload description: Reload the input_text configuration. diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index 4f5d74cbbbb6ec..fe96f3a6777765 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -1,7 +1,7 @@ # Describes the format for available light services turn_on: - name: Turn on lights + name: Turn on description: > Turn on one or more lights and adjust properties of the light, even when they are turned on already. @@ -314,7 +314,7 @@ turn_on: text: turn_off: - name: Turn off lights + name: Turn off description: Turns off one or more lights. target: fields: @@ -344,7 +344,7 @@ turn_off: - short toggle: - name: Toggle lights + name: Toggle description: > Toggles one or more lights, from on to off, or, off to on, based on their current state. diff --git a/homeassistant/components/lock/services.yaml b/homeassistant/components/lock/services.yaml index 22af0ab97cf87b..f5f6077ddc10c5 100644 --- a/homeassistant/components/lock/services.yaml +++ b/homeassistant/components/lock/services.yaml @@ -21,7 +21,8 @@ get_usercode: example: 1 lock: - description: Lock all or specified locks + name: Lock + description: Lock all or specified locks. target: fields: code: @@ -32,7 +33,8 @@ lock: text: open: - description: Open all or specified locks + name: Open + description: Open all or specified locks. target: fields: code: @@ -43,7 +45,8 @@ open: text: set_usercode: - description: Set a usercode to lock + name: Set usercode + description: Set a usercode to lock. fields: node_id: description: Node id of the lock. @@ -56,7 +59,8 @@ set_usercode: example: 1234 unlock: - description: Unlock all or specified locks + name: Unlock + description: Unlock all or specified locks. target: fields: code: diff --git a/homeassistant/components/logger/services.yaml b/homeassistant/components/logger/services.yaml index 4bd46b4b01eaea..39c0bcfdfe19ff 100644 --- a/homeassistant/components/logger/services.yaml +++ b/homeassistant/components/logger/services.yaml @@ -1,11 +1,10 @@ set_default_level: - description: Set the default log level for components + name: Set default level + description: Set the default log level for integrations. fields: level: name: Level - description: - "Default severity level. Possible values are debug, info, warn, warning, - error, fatal, critical" + description: Default severity level for all integrations. example: debug selector: select: @@ -18,26 +17,27 @@ set_default_level: - critical set_level: - description: Set log level for components + name: Set level + description: Set log level for integrations. fields: homeassistant.core: description: - "Example on how to change the logging level for a Home Assistant core - components. Possible values are debug, info, warn, warning, error, - fatal, critical" + "Example on how to change the logging level for a Home Assistant Core + integrations. Possible values are debug, info, warn, warning, error, + fatal, critical." example: debug homeassistant.components.mqtt: description: "Example on how to change the logging level for an Integration. Possible - values are debug, info, warn, warning, error, fatal, critical" + values are debug, info, warn, warning, error, fatal, critical." example: warning custom_components.my_integration: description: "Example on how to change the logging level for a Custom Integration. - Possible values are debug, info, warn, warning, error, fatal, critical" + Possible values are debug, info, warn, warning, error, fatal, critical." example: debug aiohttp: description: "Example on how to change the logging level for a Python module. - Possible values are debug, info, warn, warning, error, fatal, critical" + Possible values are debug, info, warn, warning, error, fatal, critical." example: error diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index f85e0658426a1c..03084f0f1a10e0 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -1,27 +1,33 @@ # Describes the format for available media player services turn_on: - description: Turn a media player power on + name: Turn on + description: Turn a media player power on. target: turn_off: - description: Turn a media player power off + name: Turn off + description: Turn a media player power off. target: toggle: - description: Toggles a media player power state + name: Toggle + description: Toggles a media player power state. target: volume_up: - description: Turn a media player volume up + name: Turn up volume + description: Turn a media player volume up. target: volume_down: - description: Turn a media player volume down + name: Turn down volume + description: Turn a media player volume down. target: volume_mute: - description: Mute a media player's volume + name: Mute volume + description: Mute a media player's volume. target: fields: is_volume_muted: @@ -33,7 +39,8 @@ volume_mute: boolean: volume_set: - description: Set a media player's volume level + name: Set volume + description: Set a media player's volume level. target: fields: volume_level: @@ -49,32 +56,39 @@ volume_set: mode: slider media_play_pause: - description: Toggle media player play/pause state + name: Play/Pause + description: Toggle media player play/pause state. target: media_play: - description: Send the media player the command for play + name: Play + description: Send the media player the command for play. target: media_pause: - description: Send the media player the command for pause + name: Pause + description: Send the media player the command for pause. target: media_stop: - description: Send the media player the stop command + name: Stop + description: Send the media player the stop command. target: media_next_track: - description: Send the media player the command for next track + name: Next + description: Send the media player the command for next track. target: media_previous_track: - description: Send the media player the command for previous track + name: Previous + description: Send the media player the command for previous track. target: media_seek: + name: Seek description: - Send the media player the command to seek in current playing media + Send the media player the command to seek in current playing media. fields: seek_position: name: Position @@ -89,7 +103,8 @@ media_seek: mode: box play_media: - description: Send the media player the command for playing media + name: Play media + description: Send the media player the command for playing media. target: fields: media_content_id: @@ -111,7 +126,8 @@ play_media: text: select_source: - description: Send the media player the command to change input source + name: Select source + description: Send the media player the command to change input source. target: fields: source: @@ -123,7 +139,8 @@ select_source: text: select_sound_mode: - description: Send the media player the command to change sound mode + name: Select sound mode + description: Send the media player the command to change sound mode. target: fields: sound_mode: @@ -134,11 +151,13 @@ select_sound_mode: text: clear_playlist: - description: Send the media player the command to clear players playlist + name: Clear playlist + description: Send the media player the command to clear players playlist. target: shuffle_set: - description: Set shuffling state + name: Shuffle + description: Set shuffling state. target: fields: shuffle: @@ -150,6 +169,7 @@ shuffle_set: boolean: repeat_set: + name: Repeat description: Set repeat mode target: fields: diff --git a/homeassistant/components/mqtt/services.yaml b/homeassistant/components/mqtt/services.yaml index c6c3014362f332..21d3915628a153 100644 --- a/homeassistant/components/mqtt/services.yaml +++ b/homeassistant/components/mqtt/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available MQTT services publish: - description: Publish a message to an MQTT topic + name: Publish + description: Publish a message to an MQTT topic. fields: topic: name: Topic @@ -49,9 +50,10 @@ publish: boolean: dump: + name: Dump description: - Dump messages on a topic selector to the 'mqtt_dump.txt' file in your config - folder + Dump messages on a topic selector to the 'mqtt_dump.txt' file in your + configuration folder. fields: topic: name: Topic @@ -73,4 +75,5 @@ dump: mode: slider reload: - description: Reload all MQTT entities from YAML + name: Reload + description: Reload all MQTT entities from YAML. diff --git a/homeassistant/components/netatmo/services.yaml b/homeassistant/components/netatmo/services.yaml index 7005d26c326ba6..06f56d084c640b 100644 --- a/homeassistant/components/netatmo/services.yaml +++ b/homeassistant/components/netatmo/services.yaml @@ -21,7 +21,9 @@ set_camera_light: set_schedule: name: Set heating schedule - description: Set the heating schedule for Netatmo climate device. The schedule name must match a schedule configured at Netatmo. + description: + Set the heating schedule for Netatmo climate device. The schedule name must + match a schedule configured at Netatmo. target: entity: integration: netatmo @@ -36,7 +38,9 @@ set_schedule: set_persons_home: name: Set persons at home - description: Set a list of persons as at home. Person's name must match a name known by the Netatmo Indoor (Welcome) Camera. + description: + Set a list of persons as at home. Person's name must match a name known by + the Netatmo Indoor (Welcome) Camera. target: entity: integration: netatmo @@ -51,7 +55,10 @@ set_persons_home: set_person_away: name: Set person away - description: Set a person as away. If no person is set the home will be marked as empty. Person's name must match a name known by the Netatmo Indoor (Welcome) Camera. + description: + Set a person as away. If no person is set the home will be marked as empty. + Person's name must match a name known by the Netatmo Indoor (Welcome) + Camera. target: entity: integration: netatmo diff --git a/homeassistant/components/number/services.yaml b/homeassistant/components/number/services.yaml index 4cb0cf098291a3..a684fef7d5ddd2 100644 --- a/homeassistant/components/number/services.yaml +++ b/homeassistant/components/number/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available Number entity services set_value: - description: Set the value of a Number entity + name: Set + description: Set the value of a Number entity. target: fields: value: diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index cad1925080f85c..e3dea47f4f876b 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available recorder services purge: - description: Start purge task - to clean up old data from your database + name: Purge + description: Start purge task - to clean up old data from your database. fields: keep_days: name: Days to keep diff --git a/homeassistant/components/scene/services.yaml b/homeassistant/components/scene/services.yaml index dc9e9e07ab4cd3..9d07460379c930 100644 --- a/homeassistant/components/scene/services.yaml +++ b/homeassistant/components/scene/services.yaml @@ -1,7 +1,8 @@ # Describes the format for available scene services turn_on: - description: Activate a scene + name: Activate + description: Activate a scene. target: fields: transition: @@ -19,10 +20,12 @@ turn_on: mode: slider reload: - description: Reload the scene configuration + name: Reload + description: Reload the scene configuration. apply: - description: Activate a scene with configuration + name: Apply + description: Activate a scene with configuration. fields: entities: name: Entities state @@ -50,7 +53,8 @@ apply: mode: slider create: - description: Creates a new scene + name: Create + description: Creates a new scene. fields: scene_id: name: Scene entity ID diff --git a/homeassistant/components/shopping_list/services.yaml b/homeassistant/components/shopping_list/services.yaml index 961fb867aa7147..2a1e89b9786047 100644 --- a/homeassistant/components/shopping_list/services.yaml +++ b/homeassistant/components/shopping_list/services.yaml @@ -1,5 +1,6 @@ add_item: - description: Adds an item to the shopping list + name: Add item + description: Add an item to the shopping list. fields: name: name: Name @@ -10,7 +11,8 @@ add_item: text: complete_item: - description: Marks an item as completed in the shopping list. + name: Complete item + description: Mark an item as completed in the shopping list. fields: name: name: Name diff --git a/homeassistant/components/sonos/services.yaml b/homeassistant/components/sonos/services.yaml index 04a46940d6a1ae..99b430e46806a6 100644 --- a/homeassistant/components/sonos/services.yaml +++ b/homeassistant/components/sonos/services.yaml @@ -1,8 +1,10 @@ join: + name: Join group description: Group player together. fields: master: - description: Entity ID of the player that should become the coordinator of the group. + description: + Entity ID of the player that should become the coordinator of the group. example: "media_player.living_room_sonos" selector: entity: @@ -17,6 +19,7 @@ join: domain: media_player unjoin: + name: Unjoin group description: Unjoin the player from a group. fields: entity_id: @@ -28,6 +31,7 @@ unjoin: domain: media_player snapshot: + name: Snapshot description: Take a snapshot of the media player. fields: entity_id: @@ -42,6 +46,7 @@ snapshot: example: "true" restore: + name: Restore description: Restore a snapshot of the media player. fields: entity_id: @@ -56,6 +61,7 @@ restore: example: "true" set_sleep_timer: + name: Set timer description: Set a Sonos timer. fields: entity_id: @@ -75,7 +81,9 @@ set_sleep_timer: step: 1 unit_of_measurement: seconds mode: slider + clear_sleep_timer: + name: Clear timer description: Clear a Sonos timer. fields: entity_id: @@ -87,6 +95,7 @@ clear_sleep_timer: domain: media_player set_option: + name: Set option description: Set Sonos sound options. fields: entity_id: @@ -113,7 +122,8 @@ set_option: boolean: play_queue: - description: Starts playing the queue from the first item. + name: Play queue + description: Start playing the queue from the first item. fields: entity_id: description: Name(s) of entities that will start playing. @@ -132,6 +142,7 @@ play_queue: mode: box remove_from_queue: + name: Remove from queue description: Removes an item from the queue. fields: entity_id: @@ -151,6 +162,7 @@ remove_from_queue: mode: box update_alarm: + name: Update alarm description: Updates an alarm with new time and volume settings. fields: alarm_id: diff --git a/homeassistant/components/system_log/services.yaml b/homeassistant/components/system_log/services.yaml index e07aea9c2a12f8..a762c31f2054d5 100644 --- a/homeassistant/components/system_log/services.yaml +++ b/homeassistant/components/system_log/services.yaml @@ -1,8 +1,10 @@ clear: - description: Clear all log entries + name: Clear all + description: Clear all log entries. write: - description: Write log entry + name: Write + description: Write log entry. fields: message: name: Message diff --git a/homeassistant/components/timer/services.yaml b/homeassistant/components/timer/services.yaml index fcde11cd47f09d..54175de3cf7fef 100644 --- a/homeassistant/components/timer/services.yaml +++ b/homeassistant/components/timer/services.yaml @@ -1,6 +1,7 @@ # Describes the format for available timer services start: + name: Start description: Start a timer target: fields: @@ -12,13 +13,16 @@ start: text: pause: - description: Pause a timer + name: Pause + description: Pause a timer. target: cancel: - description: Cancel a timer + name: Cancel + description: Cancel a timer. target: finish: - description: Finish a timer + name: Finish + description: Finish a timer. target: diff --git a/homeassistant/components/toon/services.yaml b/homeassistant/components/toon/services.yaml index 3e06e6d3f9fdfa..909018f820bea7 100644 --- a/homeassistant/components/toon/services.yaml +++ b/homeassistant/components/toon/services.yaml @@ -1,4 +1,5 @@ update: + name: Update description: Update all entities with fresh data from Toon fields: display: diff --git a/homeassistant/components/twentemilieu/services.yaml b/homeassistant/components/twentemilieu/services.yaml index 7a6a16f33ad0e4..6227bad1b6de5d 100644 --- a/homeassistant/components/twentemilieu/services.yaml +++ b/homeassistant/components/twentemilieu/services.yaml @@ -1,4 +1,5 @@ update: + name: Update description: Update all entities with fresh data from Twente Milieu fields: id: diff --git a/homeassistant/components/vacuum/services.yaml b/homeassistant/components/vacuum/services.yaml index a60ce9ee658469..e0064bc475bba4 100644 --- a/homeassistant/components/vacuum/services.yaml +++ b/homeassistant/components/vacuum/services.yaml @@ -1,43 +1,53 @@ # Describes the format for available vacuum services turn_on: - description: Start a new cleaning task + name: Turn on + description: Start a new cleaning task. target: turn_off: - description: Stop the current cleaning task and return to home + name: Turn off + description: Stop the current cleaning task and return to home. target: stop: - description: Stop the current cleaning task + name: Stop + description: Stop the current cleaning task. target: locate: - description: Locate the vacuum cleaner robot + name: Locate + description: Locate the vacuum cleaner robot. target: start_pause: - description: Start, pause, or resume the cleaning task + name: Start/Pause + description: Start, pause, or resume the cleaning task. target: start: - description: Start or resume the cleaning task + name: Start + description: Start or resume the cleaning task. target: pause: - description: Pause the cleaning task + name: Pause + description: Pause the cleaning task. target: return_to_base: - description: Tell the vacuum cleaner to return to its dock + name: Return to base + description: Tell the vacuum cleaner to return to its dock. target: clean_spot: - description: Tell the vacuum cleaner to do a spot clean-up + name: Clean spot + description: Tell the vacuum cleaner to do a spot clean-up. target: send_command: - description: Send a raw command to the vacuum cleaner + name: Send command + description: Send a raw command to the vacuum cleaner. target: fields: command: @@ -47,7 +57,6 @@ send_command: example: "set_dnd_timer" selector: text: - params: name: Parameters description: Parameters for the command. @@ -56,7 +65,8 @@ send_command: object: set_fan_speed: - description: Set the fan speed of the vacuum cleaner + name: Set fan speed + description: Set the fan speed of the vacuum cleaner. target: fields: fan_speed: diff --git a/homeassistant/components/vizio/services.yaml b/homeassistant/components/vizio/services.yaml index a2981fa32ad1cb..7a2ea859b7dab5 100644 --- a/homeassistant/components/vizio/services.yaml +++ b/homeassistant/components/vizio/services.yaml @@ -1,5 +1,5 @@ update_setting: - name: Update a Vizio media player setting + name: Update setting description: Update the value of a setting on a Vizio media player device. target: entity: @@ -8,14 +8,18 @@ update_setting: fields: setting_type: name: Setting type - description: The type of setting to be changed. Available types are listed in the 'setting_types' property. + description: + The type of setting to be changed. Available types are listed in the + 'setting_types' property. required: true example: "audio" selector: text: setting_name: name: Setting name - description: The name of the setting to be changed. Available settings for a given setting_type are listed in the '_settings' property. + description: + The name of the setting to be changed. Available settings for a given + setting_type are listed in the '_settings' property. required: true example: "eq" selector: diff --git a/homeassistant/components/wled/services.yaml b/homeassistant/components/wled/services.yaml index 827e8b5fb36a55..d6927610a47889 100644 --- a/homeassistant/components/wled/services.yaml +++ b/homeassistant/components/wled/services.yaml @@ -1,5 +1,6 @@ effect: - description: Controls the effect settings of WLED + name: Set effect + description: Control the effect settings of WLED. target: fields: effect: @@ -44,7 +45,8 @@ effect: boolean: preset: - description: Calls a preset on the WLED device + name: Set preset + description: Set a preset for the WLED device. target: fields: preset: From afa91e886b93d35f61ec8a838ef74a2b38ec48a0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 23 Feb 2021 14:29:57 +0100 Subject: [PATCH 0705/1818] Add description to tts and notify services (#46764) Co-authored-by: Paulus Schoutsen Co-authored-by: Franck Nijhof --- .../components/media_player/services.yaml | 2 +- homeassistant/components/notify/__init__.py | 31 +++++++++++++++++-- homeassistant/components/notify/services.yaml | 31 ++++++++++++++++--- homeassistant/components/tts/__init__.py | 24 +++++++++++++- homeassistant/components/tts/services.yaml | 26 ++++++++++++++-- homeassistant/helpers/service.py | 3 ++ 6 files changed, 105 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index 03084f0f1a10e0..eaca8483be188e 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -118,7 +118,7 @@ play_media: media_content_type: name: Content type description: - The type of the content to play. Must be one of image, music, tvshow, + The type of the content to play. Like image, music, tvshow, video, episode, channel or playlist. required: true example: "music" diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 1e9c7d8595a9d9..7be66dc3c59ce5 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -2,7 +2,7 @@ import asyncio from functools import partial import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, cast import voluptuous as vol @@ -12,10 +12,12 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.loader import bind_hass +from homeassistant.loader import async_get_integration, bind_hass from homeassistant.setup import async_prepare_setup_platform from homeassistant.util import slugify +from homeassistant.util.yaml import load_yaml # mypy: allow-untyped-defs, no-check-untyped-defs @@ -41,6 +43,9 @@ NOTIFY_SERVICES = "notify_services" +CONF_DESCRIPTION = "description" +CONF_FIELDS = "fields" + PLATFORM_SCHEMA = vol.Schema( {vol.Required(CONF_PLATFORM): cv.string, vol.Optional(CONF_NAME): cv.string}, extra=vol.ALLOW_EXTRA, @@ -161,6 +166,13 @@ async def async_setup( self._target_service_name_prefix = target_service_name_prefix self.registered_targets = {} + # Load service descriptions from notify/services.yaml + integration = await async_get_integration(hass, DOMAIN) + services_yaml = integration.file_path / "services.yaml" + self.services_dict = cast( + dict, await hass.async_add_executor_job(load_yaml, str(services_yaml)) + ) + async def async_register_services(self) -> None: """Create or update the notify services.""" assert self.hass @@ -185,6 +197,13 @@ async def async_register_services(self) -> None: self._async_notify_message_service, schema=NOTIFY_SERVICE_SCHEMA, ) + # Register the service description + service_desc = { + CONF_NAME: f"Send a notification via {target_name}", + CONF_DESCRIPTION: f"Sends a notification message using the {target_name} integration.", + CONF_FIELDS: self.services_dict[SERVICE_NOTIFY][CONF_FIELDS], + } + async_set_service_schema(self.hass, DOMAIN, target_name, service_desc) for stale_target_name in stale_targets: del self.registered_targets[stale_target_name] @@ -203,6 +222,14 @@ async def async_register_services(self) -> None: schema=NOTIFY_SERVICE_SCHEMA, ) + # Register the service description + service_desc = { + CONF_NAME: f"Send a notification with {self._service_name}", + CONF_DESCRIPTION: f"Sends a notification message using the {self._service_name} service.", + CONF_FIELDS: self.services_dict[SERVICE_NOTIFY][CONF_FIELDS], + } + async_set_service_schema(self.hass, DOMAIN, self._service_name, service_desc) + async def async_unregister_services(self) -> None: """Unregister the notify services.""" assert self.hass diff --git a/homeassistant/components/notify/services.yaml b/homeassistant/components/notify/services.yaml index 8c75c94e34ab37..f6918b6c09c6b9 100644 --- a/homeassistant/components/notify/services.yaml +++ b/homeassistant/components/notify/services.yaml @@ -1,22 +1,37 @@ # Describes the format for available notification services notify: - description: Send a notification. + name: Send a notification + description: Sends a notification message to selected notify platforms. fields: message: + name: Message description: Message body of the notification. example: The garage door has been open for 10 minutes. + selector: + text: title: + name: Title description: Optional title for your notification. example: "Your Garage Door Friend" + selector: + text: target: - description: An array of targets to send the notification to. Optional depending on the platform. + description: + An array of targets to send the notification to. Optional depending on + the platform. example: platform specific data: - description: Extended information for notification. Optional depending on the platform. + name: Data + description: + Extended information for notification. Optional depending on the + platform. example: platform specific + selector: + object: persistent_notification: + name: Send a persistent notification description: Sends a notification to the visible in the front-end. fields: message: @@ -27,10 +42,16 @@ persistent_notification: example: "Your Garage Door Friend" apns_register: - description: Registers a device to receive push notifications. + name: Register APNS device + description: + Registers a device to receive push notifications via APNS (Apple Push + Notification Service). fields: push_id: - description: The device token, a 64 character hex string (256 bits). The device token is provided to you by your client app, which receives the token after registering itself with the remote notification service. + description: + The device token, a 64 character hex string (256 bits). The device token + is provided to you by your client app, which receives the token after + registering itself with the remote notification service. example: "72f2a8633655c5ce574fdc9b2b34ff8abdfc3b739b6ceb7a9ff06c1cbbf99f62" name: description: A friendly name for the device (optional). diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index ff1bf946e830a8..67bc933a530de7 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -7,7 +7,7 @@ import mimetypes import os import re -from typing import Dict, Optional +from typing import Dict, Optional, cast from aiohttp import web import mutagen @@ -24,6 +24,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + CONF_NAME, CONF_PLATFORM, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, @@ -33,8 +34,11 @@ from homeassistant.helpers import config_per_platform, discovery 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 HomeAssistantType +from homeassistant.loader import async_get_integration from homeassistant.setup import async_prepare_setup_platform +from homeassistant.util.yaml import load_yaml # mypy: allow-untyped-defs, no-check-untyped-defs @@ -55,6 +59,9 @@ CONF_SERVICE_NAME = "service_name" CONF_TIME_MEMORY = "time_memory" +CONF_DESCRIPTION = "description" +CONF_FIELDS = "fields" + DEFAULT_CACHE = True DEFAULT_CACHE_DIR = "tts" DEFAULT_TIME_MEMORY = 300 @@ -127,6 +134,13 @@ async def async_setup(hass, config): 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_dict = cast( + dict, await hass.async_add_executor_job(load_yaml, str(services_yaml)) + ) + async def async_setup_platform(p_type, p_config=None, discovery_info=None): """Set up a TTS platform.""" if p_config is None: @@ -193,6 +207,14 @@ async def async_say_handle(service): DOMAIN, service_name, async_say_handle, schema=SCHEMA_SERVICE_SAY ) + # Register the service description + service_desc = { + CONF_NAME: "Say an TTS message with {p_type}", + CONF_DESCRIPTION: f"Say something using text-to-speech on a media player with {p_type}.", + CONF_FIELDS: services_dict[SERVICE_SAY][CONF_FIELDS], + } + async_set_service_schema(hass, DOMAIN, service_name, service_desc) + setup_tasks = [ asyncio.create_task(async_setup_platform(p_type, p_config)) for p_type, p_config in config_per_platform(config, DOMAIN) diff --git a/homeassistant/components/tts/services.yaml b/homeassistant/components/tts/services.yaml index 7d1bf95572b31f..2b48dd39dee375 100644 --- a/homeassistant/components/tts/services.yaml +++ b/homeassistant/components/tts/services.yaml @@ -1,23 +1,43 @@ # Describes the format for available TTS services say: - description: Say some things on a media player. + name: Say an TTS message + description: Say something using text-to-speech on a media player. fields: entity_id: + name: Entity description: Name(s) of media player entities. example: "media_player.floor" + required: true + selector: + entity: + domain: media_player message: + name: Message description: Text to speak on devices. example: "My name is hanna" + required: true + selector: + text: cache: + name: Cache description: Control file cache of this message. example: "true" + default: false + selector: + boolean: language: + name: Language description: Language to use for speech generation. example: "ru" + selector: + text: options: - description: A dictionary containing platform-specific options. Optional depending on the platform. + description: + A dictionary containing platform-specific options. Optional depending on + the platform. example: platform specific clear_cache: - description: Remove cache files and RAM cache. + name: Clear TTS cache + description: Remove all text-to-speech cache files and RAM cache. diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index a55ba8a84af54e..7983190dbe836f 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -478,6 +478,9 @@ def async_set_service_schema( "fields": schema.get("fields", {}), } + if "target" in schema: + description["target"] = schema["target"] + hass.data[SERVICE_DESCRIPTION_CACHE][f"{domain}.{service}"] = description From f0f752936ba776078dce39a189319e3e658c230f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 23 Feb 2021 14:49:04 +0100 Subject: [PATCH 0706/1818] Update homeassistant services.yaml (#46952) Add names and missing target to turn off service --- homeassistant/components/homeassistant/services.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/homeassistant/services.yaml b/homeassistant/components/homeassistant/services.yaml index 6bd0a0852eda03..38814d9f902062 100644 --- a/homeassistant/components/homeassistant/services.yaml +++ b/homeassistant/components/homeassistant/services.yaml @@ -32,22 +32,29 @@ set_location: text: stop: + name: Stop description: Stop the Home Assistant service. toggle: + name: Generic toggle description: Generic service to toggle devices on/off under any domain target: entity: {} turn_on: + name: Generic turn on description: Generic service to turn devices on under any domain. target: entity: {} turn_off: + name: Generic turn off description: Generic service to turn devices off under any domain. + target: + entity: {} update_entity: + name: Update entity description: Force one or more entities to update its data target: entity: {} From 3c35b6558bf83d4ed704453d7dbd34168f441cab Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Tue, 23 Feb 2021 09:31:47 -0500 Subject: [PATCH 0707/1818] Return states list from zwave_js get_config_parameters websocket if available (#46954) --- homeassistant/components/zwave_js/api.py | 3 +++ tests/components/zwave_js/test_api.py | 1 + 2 files changed, 4 insertions(+) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 71994f8b00b36a..8358b93aae5611 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -374,6 +374,9 @@ def websocket_get_config_parameters( }, "value": zwave_value.value, } + if zwave_value.metadata.states: + result[value_id]["metadata"]["states"] = zwave_value.metadata.states + connection.send_result( msg[ID], result, diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 403a73a6767334..d2a6215575f412 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -74,6 +74,7 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): assert result[key]["property"] == 2 assert result[key]["metadata"]["type"] == "number" assert result[key]["configuration_value_type"] == "enumerated" + assert result[key]["metadata"]["states"] async def test_add_node( From 7a7147edcf6bf3922c546bdf0f917f3fbc1dcc4c Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Tue, 23 Feb 2021 16:34:02 +0100 Subject: [PATCH 0708/1818] Add stop tilt support to KNX (#46947) --- homeassistant/components/knx/cover.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 86afd467be1d43..4c08612926b04e 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -13,6 +13,7 @@ SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, + SUPPORT_STOP_TILT, CoverEntity, ) from homeassistant.core import callback @@ -64,7 +65,10 @@ def supported_features(self): supported_features |= SUPPORT_STOP if self._device.supports_angle: supported_features |= ( - SUPPORT_SET_TILT_POSITION | SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT + SUPPORT_SET_TILT_POSITION + | SUPPORT_OPEN_TILT + | SUPPORT_CLOSE_TILT + | SUPPORT_STOP_TILT ) return supported_features @@ -139,6 +143,11 @@ async def async_close_cover_tilt(self, **kwargs): """Close the cover tilt.""" await self._device.set_short_down() + async def async_stop_cover_tilt(self, **kwargs): + """Stop the cover tilt.""" + await self._device.stop() + self.stop_auto_updater() + def start_auto_updater(self): """Start the autoupdater to update Home Assistant while cover is moving.""" if self._unsubscribe_auto_updater is None: From c94968d8114c12f1c01e97a928ab5bb4b5defcf3 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 23 Feb 2021 16:36:53 +0100 Subject: [PATCH 0709/1818] Catch more zwave_js errors (#46957) --- homeassistant/components/zwave_js/__init__.py | 9 +++++++- homeassistant/components/zwave_js/climate.py | 10 +++++++-- tests/components/zwave_js/test_init.py | 21 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 530a8022233042..836bd77192399c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -219,7 +219,11 @@ async def handle_ha_shutdown(event: Event) -> None: hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) ) - await driver_ready.wait() + try: + await driver_ready.wait() + except asyncio.CancelledError: + LOGGER.debug("Cancelling start platforms") + return LOGGER.info("Connection to Zwave JS Server initialized") @@ -271,6 +275,9 @@ async def client_listen( should_reload = False except BaseZwaveJSServerError as err: LOGGER.error("Failed to listen: %s", err) + except Exception as err: # pylint: disable=broad-except + # We need to guard against unknown exceptions to not crash this task. + LOGGER.exception("Unexpected exception: %s", err) # The entry needs to be reloaded since a new driver state # will be acquired on reconnect. diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index f864efe91ff5c3..54966538aaee0f 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -261,7 +261,10 @@ def target_temperature(self) -> Optional[float]: if self._current_mode and self._current_mode.value is None: # guard missing value return None - temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) + try: + temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) + except ValueError: + return None return temp.value if temp else None @property @@ -270,7 +273,10 @@ def target_temperature_high(self) -> Optional[float]: if self._current_mode and self._current_mode.value is None: # guard missing value return None - temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) + try: + temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) + except ValueError: + return None return temp.value if temp else None @property diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 1aad07400ad87d..6e41da42c8f9ac 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest +from zwave_js_server.exceptions import BaseZwaveJSServerError from zwave_js_server.model.node import Node from homeassistant.components.hassio.handler import HassioAPIError @@ -76,6 +77,26 @@ async def test_initialized_timeout(hass, client, connect_timeout): assert entry.state == ENTRY_STATE_SETUP_RETRY +@pytest.mark.parametrize("error", [BaseZwaveJSServerError("Boom"), Exception("Boom")]) +async def test_listen_failure(hass, client, error): + """Test we handle errors during client listen.""" + + async def listen(driver_ready): + """Mock the client listen method.""" + # Set the connect side effect to stop an endless loop on reload. + client.connect.side_effect = BaseZwaveJSServerError("Boom") + raise error + + client.listen.side_effect = listen + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + + async def test_on_node_added_ready( hass, multisensor_6_state, client, integration, device_registry ): From 5a3bd30e01681be1102410c37418f04475ee9fd4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Feb 2021 11:35:11 -0500 Subject: [PATCH 0710/1818] Add zwave_js.set_config_parameter service (#46673) * create zwave_js.set_config_value service * update docstring * PR comments * make proposed changes * handle providing a label for the new value * fix docstring * use new library function * config param endpoint is always 0 * corresponding changes from upstream PR * bug fixes and add tests * create zwave_js.set_config_value service * update docstring * PR comments * make proposed changes * handle providing a label for the new value * fix docstring * use new library function * config param endpoint is always 0 * corresponding changes from upstream PR * bug fixes and add tests * use lambda to avoid extra function * add services description file * bring back the missing selector * move helper functions to helper file for reuse * allow target selector for automation editor * formatting * fix service schema * update docstrings * raise error in service if call to set value is unsuccessful * Update homeassistant/components/zwave_js/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/zwave_js/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/zwave_js/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/zwave_js/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/zwave_js/services.yaml Co-authored-by: Franck Nijhof * Update homeassistant/components/zwave_js/services.yaml Co-authored-by: Franck Nijhof * remove extra param to vol.Optional * switch to set over list for nodes * switch to set over list for nodes Co-authored-by: Marcel van der Veldt Co-authored-by: Franck Nijhof --- homeassistant/components/zwave_js/__init__.py | 6 +- homeassistant/components/zwave_js/const.py | 7 + homeassistant/components/zwave_js/entity.py | 11 +- homeassistant/components/zwave_js/helpers.py | 100 ++++++ homeassistant/components/zwave_js/services.py | 110 +++++++ .../components/zwave_js/services.yaml | 28 ++ tests/components/zwave_js/test_init.py | 2 +- tests/components/zwave_js/test_services.py | 295 ++++++++++++++++++ 8 files changed, 548 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/zwave_js/helpers.py create mode 100644 homeassistant/components/zwave_js/services.py create mode 100644 tests/components/zwave_js/test_services.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 836bd77192399c..a70716ad421825 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -43,7 +43,8 @@ ZWAVE_JS_EVENT, ) from .discovery import async_discover_values -from .entity import get_device_id +from .helpers import get_device_id +from .services import ZWaveServices LOGGER = logging.getLogger(__package__) CONNECT_TIMEOUT = 10 @@ -192,6 +193,9 @@ def async_on_notification(notification: Notification) -> None: DATA_UNSUBSCRIBE: unsubscribe_callbacks, } + services = ZWaveServices(hass) + services.async_register() + # Set up websocket API async_register_api(hass) diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 5eb537c0d4ee19..1031a51719ac86 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -33,4 +33,11 @@ ATTR_PROPERTY_KEY_NAME = "property_key_name" ATTR_PARAMETERS = "parameters" +# service constants +SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" + +ATTR_CONFIG_PARAMETER = "parameter" +ATTR_CONFIG_PARAMETER_BITMASK = "bitmask" +ATTR_CONFIG_VALUE = "value" + ADDON_SLUG = "core_zwave_js" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 3e81bfaeadf878..3141dd0caea4c2 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,30 +1,23 @@ """Generic Z-Wave Entity Class.""" import logging -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Union from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback from homeassistant.helpers.entity import Entity -from .const import DOMAIN from .discovery import ZwaveDiscoveryInfo +from .helpers import get_device_id LOGGER = logging.getLogger(__name__) EVENT_VALUE_UPDATED = "value updated" -@callback -def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: - """Get device registry identifier for Z-Wave node.""" - return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") - - class ZWaveBaseEntity(Entity): """Generic Entity Class for a Z-Wave Device.""" diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py new file mode 100644 index 00000000000000..cc00c39b747ac0 --- /dev/null +++ b/homeassistant/components/zwave_js/helpers.py @@ -0,0 +1,100 @@ +"""Helper functions for Z-Wave JS integration.""" +from typing import List, Tuple, cast + +from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.node import Node as ZwaveNode + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import async_get as async_get_dev_reg +from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg + +from .const import DATA_CLIENT, DOMAIN + + +@callback +def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: + """Get device registry identifier for Z-Wave node.""" + return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") + + +@callback +def get_home_and_node_id_from_device_id(device_id: Tuple[str, str]) -> List[str]: + """ + Get home ID and node ID for Z-Wave device registry entry. + + Returns [home_id, node_id] + """ + return device_id[1].split("-") + + +@callback +def async_get_node_from_device_id(hass: HomeAssistant, device_id: str) -> ZwaveNode: + """ + Get node from a device ID. + + Raises ValueError if device is invalid or node can't be found. + """ + device_entry = async_get_dev_reg(hass).async_get(device_id) + + if not device_entry: + raise ValueError("Device ID is not valid") + + # Use device config entry ID's to validate that this is a valid zwave_js device + # and to get the client + config_entry_ids = device_entry.config_entries + config_entry_id = next( + ( + config_entry_id + for config_entry_id in config_entry_ids + if cast( + ConfigEntry, + hass.config_entries.async_get_entry(config_entry_id), + ).domain + == DOMAIN + ), + None, + ) + if config_entry_id is None or config_entry_id not in hass.data[DOMAIN]: + raise ValueError("Device is not from an existing zwave_js config entry") + + client = hass.data[DOMAIN][config_entry_id][DATA_CLIENT] + + # Get node ID from device identifier, perform some validation, and then get the + # node + identifier = next( + ( + get_home_and_node_id_from_device_id(identifier) + for identifier in device_entry.identifiers + if identifier[0] == DOMAIN + ), + None, + ) + + node_id = int(identifier[1]) if identifier is not None else None + + if node_id is None or node_id not in client.driver.controller.nodes: + raise ValueError("Device node can't be found") + + return client.driver.controller.nodes[node_id] + + +@callback +def async_get_node_from_entity_id(hass: HomeAssistant, entity_id: str) -> ZwaveNode: + """ + Get node from an entity ID. + + Raises ValueError if entity is invalid. + """ + entity_entry = async_get_ent_reg(hass).async_get(entity_id) + + if not entity_entry: + raise ValueError("Entity ID is not valid") + + if entity_entry.platform != DOMAIN: + raise ValueError("Entity is not from zwave_js integration") + + # Assert for mypy, safe because we know that zwave_js entities are always + # tied to a device + assert entity_entry.device_id + return async_get_node_from_device_id(hass, entity_entry.device_id) diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py new file mode 100644 index 00000000000000..da60ddab6662dd --- /dev/null +++ b/homeassistant/components/zwave_js/services.py @@ -0,0 +1,110 @@ +"""Methods and classes related to executing Z-Wave commands and publishing these to hass.""" + +import logging +from typing import Dict, Set, Union + +import voluptuous as vol +from zwave_js_server.model.node import Node as ZwaveNode +from zwave_js_server.util.node import async_set_config_parameter + +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant, ServiceCall, callback +import homeassistant.helpers.config_validation as cv + +from . import const +from .helpers import async_get_node_from_device_id, async_get_node_from_entity_id + +_LOGGER = logging.getLogger(__name__) + + +def parameter_name_does_not_need_bitmask( + val: Dict[str, Union[int, str]] +) -> Dict[str, Union[int, str]]: + """Validate that if a parameter name is provided, bitmask is not as well.""" + if isinstance(val[const.ATTR_CONFIG_PARAMETER], str) and ( + val.get(const.ATTR_CONFIG_PARAMETER_BITMASK) + ): + raise vol.Invalid( + "Don't include a bitmask when a parameter name is specified", + path=[const.ATTR_CONFIG_PARAMETER, const.ATTR_CONFIG_PARAMETER_BITMASK], + ) + return val + + +# Validates that a bitmask is provided in hex form and converts it to decimal +# int equivalent since that's what the library uses +BITMASK_SCHEMA = vol.All( + cv.string, vol.Lower, vol.Match(r"^(0x)?[0-9a-f]+$"), lambda value: int(value, 16) +) + + +class ZWaveServices: + """Class that holds our services (Zwave Commands) that should be published to hass.""" + + def __init__(self, hass: HomeAssistant): + """Initialize with hass object.""" + self._hass = hass + + @callback + def async_register(self) -> None: + """Register all our services.""" + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_SET_CONFIG_PARAMETER, + self.async_set_config_parameter, + schema=vol.All( + { + vol.Optional(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Any( + vol.Coerce(int), cv.string + ), + vol.Optional(const.ATTR_CONFIG_PARAMETER_BITMASK): vol.Any( + vol.Coerce(int), BITMASK_SCHEMA + ), + vol.Required(const.ATTR_CONFIG_VALUE): vol.Any( + vol.Coerce(int), cv.string + ), + }, + cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID), + parameter_name_does_not_need_bitmask, + ), + ) + + async def async_set_config_parameter(self, service: ServiceCall) -> None: + """Set a config value on a node.""" + nodes: Set[ZwaveNode] = set() + if ATTR_ENTITY_ID in service.data: + nodes |= { + async_get_node_from_entity_id(self._hass, entity_id) + for entity_id in service.data[ATTR_ENTITY_ID] + } + if ATTR_DEVICE_ID in service.data: + nodes |= { + async_get_node_from_device_id(self._hass, device_id) + for device_id in service.data[ATTR_DEVICE_ID] + } + property_or_property_name = service.data[const.ATTR_CONFIG_PARAMETER] + property_key = service.data.get(const.ATTR_CONFIG_PARAMETER_BITMASK) + new_value = service.data[const.ATTR_CONFIG_VALUE] + + for node in nodes: + zwave_value = await async_set_config_parameter( + node, + new_value, + property_or_property_name, + property_key=property_key, + ) + + if zwave_value: + _LOGGER.info( + "Set configuration parameter %s on Node %s with value %s", + zwave_value, + node, + new_value, + ) + else: + raise ValueError( + f"Unable to set configuration parameter on Node {node} with " + f"value {new_value}" + ) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index d2f1c75b64e1af..a5e9efd721635f 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -38,3 +38,31 @@ set_lock_usercode: example: 1234 selector: text: + +set_config_parameter: + name: Set a Z-Wave device configuration parameter + description: Allow for changing configuration parameters of your Z-Wave devices. + target: + entity: + integration: zwave_js + fields: + parameter: + name: Parameter + description: The (name or id of the) configuration parameter you want to configure. + example: Minimum brightness level + required: true + selector: + text: + value: + name: Value + description: The new value to set for this configuration parameter. + example: 5 + required: true + selector: + object: + bitmask: + name: Bitmask + description: Target a specific bitmask (see the documentation for more information). + advanced: true + selector: + object: diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 6e41da42c8f9ac..6a255becf2dbad 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -8,7 +8,7 @@ from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.zwave_js.const import DOMAIN -from homeassistant.components.zwave_js.entity import get_device_id +from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.config_entries import ( CONN_CLASS_LOCAL_PUSH, ENTRY_STATE_LOADED, diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py new file mode 100644 index 00000000000000..b085d9e32fb641 --- /dev/null +++ b/tests/components/zwave_js/test_services.py @@ -0,0 +1,295 @@ +"""Test the Z-Wave JS services.""" +import pytest +import voluptuous as vol + +from homeassistant.components.zwave_js.const import ( + ATTR_CONFIG_PARAMETER, + ATTR_CONFIG_PARAMETER_BITMASK, + ATTR_CONFIG_VALUE, + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, +) +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID +from homeassistant.helpers.device_registry import async_get as async_get_dev_reg +from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg + +from .common import AIR_TEMPERATURE_SENSOR + +from tests.common import MockConfigEntry + + +async def test_set_config_parameter(hass, client, multisensor_6, integration): + """Test the set_config_parameter service.""" + dev_reg = async_get_dev_reg(hass) + ent_reg = async_get_ent_reg(hass) + entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) + + # Test setting config parameter by property and property_key + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, + ATTR_CONFIG_PARAMETER: 102, + ATTR_CONFIG_PARAMETER_BITMASK: 1, + ATTR_CONFIG_VALUE: 1, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 102, + "propertyName": "Group 2: Send battery reports", + "propertyKey": 1, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": True, + "label": "Group 2: Send battery reports", + "description": "Include battery information in periodic reports to Group 2", + "isFromConfig": True, + }, + "value": 0, + } + assert args["value"] == 1 + + client.async_send_command.reset_mock() + + # Test setting parameter by property name + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, + ATTR_CONFIG_PARAMETER: "Group 2: Send battery reports", + ATTR_CONFIG_VALUE: 1, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 102, + "propertyName": "Group 2: Send battery reports", + "propertyKey": 1, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": True, + "label": "Group 2: Send battery reports", + "description": "Include battery information in periodic reports to Group 2", + "isFromConfig": True, + }, + "value": 0, + } + assert args["value"] == 1 + + client.async_send_command.reset_mock() + + # Test setting parameter by property name and state label + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_DEVICE_ID: entity_entry.device_id, + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 41, + "propertyName": "Temperature Threshold (Unit)", + "propertyKey": 15, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "valueSize": 3, + "min": 1, + "max": 2, + "default": 1, + "format": 0, + "allowManualEntry": False, + "states": {"1": "Celsius", "2": "Fahrenheit"}, + "label": "Temperature Threshold (Unit)", + "isFromConfig": True, + }, + "value": 0, + } + assert args["value"] == 2 + + client.async_send_command.reset_mock() + + # Test setting parameter by property and bitmask + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, + ATTR_CONFIG_PARAMETER: 102, + ATTR_CONFIG_PARAMETER_BITMASK: "0x01", + ATTR_CONFIG_VALUE: 1, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 52 + assert args["valueId"] == { + "commandClassName": "Configuration", + "commandClass": 112, + "endpoint": 0, + "property": 102, + "propertyName": "Group 2: Send battery reports", + "propertyKey": 1, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "valueSize": 4, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": True, + "label": "Group 2: Send battery reports", + "description": "Include battery information in periodic reports to Group 2", + "isFromConfig": True, + }, + "value": 0, + } + assert args["value"] == 1 + + # Test that an invalid entity ID raises a ValueError + with pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_ENTITY_ID: "sensor.fake_entity", + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) + + # Test that an invalid device ID raises a ValueError + with pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_DEVICE_ID: "fake_device_id", + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) + + # Test that we can't include a bitmask value if parameter is a string + with pytest.raises(vol.Invalid): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_DEVICE_ID: entity_entry.device_id, + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_PARAMETER_BITMASK: 1, + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) + + non_zwave_js_config_entry = MockConfigEntry(entry_id="fake_entry_id") + non_zwave_js_config_entry.add_to_hass(hass) + non_zwave_js_device = dev_reg.async_get_or_create( + config_entry_id=non_zwave_js_config_entry.entry_id, + identifiers={("test", "test")}, + ) + + # Test that a non Z-Wave JS device raises a ValueError + with pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_DEVICE_ID: non_zwave_js_device.id, + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) + + zwave_js_device_with_invalid_node_id = dev_reg.async_get_or_create( + config_entry_id=integration.entry_id, identifiers={(DOMAIN, "500-500")} + ) + + # Test that a Z-Wave JS device with an invalid node ID raises a ValueError + with pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_DEVICE_ID: zwave_js_device_with_invalid_node_id.id, + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) + + non_zwave_js_entity = ent_reg.async_get_or_create( + "test", + "sensor", + "test_sensor", + suggested_object_id="test_sensor", + config_entry=non_zwave_js_config_entry, + ) + + # Test that a non Z-Wave JS entity raises a ValueError + with pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG_PARAMETER, + { + ATTR_ENTITY_ID: non_zwave_js_entity.entity_id, + ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", + ATTR_CONFIG_VALUE: "Fahrenheit", + }, + blocking=True, + ) From ffe42e150a8f061bef86e438efd120eafb884a43 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Feb 2021 12:03:30 -0600 Subject: [PATCH 0711/1818] Add missing target to increase_speed/decrease_speed service (#46939) --- homeassistant/components/fan/services.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml index 3c8eb2d0761630..f86a32823dcf82 100644 --- a/homeassistant/components/fan/services.yaml +++ b/homeassistant/components/fan/services.yaml @@ -112,10 +112,8 @@ set_direction: increase_speed: name: Increase speed description: Increase the speed of the fan by one speed or a percentage_step. + target: fields: - entity_id: - description: Name(s) of the entities to increase speed - example: "fan.living_room" percentage_step: advanced: true required: false @@ -132,10 +130,8 @@ increase_speed: decrease_speed: name: Decrease speed description: Decrease the speed of the fan by one speed or a percentage_step. + target: fields: - entity_id: - description: Name(s) of the entities to decrease speed - example: "fan.living_room" percentage_step: advanced: true required: false From 6a8b5ee51b15fa74c1f237a96811b6eea4826552 Mon Sep 17 00:00:00 2001 From: Jon Caruana Date: Tue, 23 Feb 2021 12:20:58 -0800 Subject: [PATCH 0712/1818] LiteJet is now configured using config_flow (#44409) Co-authored-by: J. Nick Koston --- CODEOWNERS | 1 + homeassistant/components/litejet/__init__.py | 91 +++++--- .../components/litejet/config_flow.py | 53 +++++ homeassistant/components/litejet/const.py | 8 + homeassistant/components/litejet/light.py | 43 ++-- .../components/litejet/manifest.json | 5 +- homeassistant/components/litejet/scene.py | 40 +++- homeassistant/components/litejet/strings.json | 19 ++ homeassistant/components/litejet/switch.py | 49 +++-- .../components/litejet/translations/en.json | 19 ++ homeassistant/components/litejet/trigger.py | 15 +- homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/litejet/__init__.py | 50 +++++ tests/components/litejet/conftest.py | 64 +++++- tests/components/litejet/test_config_flow.py | 77 +++++++ tests/components/litejet/test_init.py | 65 +++--- tests/components/litejet/test_light.py | 204 ++++++++---------- tests/components/litejet/test_scene.py | 81 +++---- tests/components/litejet/test_switch.py | 184 ++++++---------- tests/components/litejet/test_trigger.py | 168 ++++++++------- 22 files changed, 757 insertions(+), 484 deletions(-) create mode 100644 homeassistant/components/litejet/config_flow.py create mode 100644 homeassistant/components/litejet/const.py create mode 100644 homeassistant/components/litejet/strings.json create mode 100644 homeassistant/components/litejet/translations/en.json create mode 100644 tests/components/litejet/test_config_flow.py diff --git a/CODEOWNERS b/CODEOWNERS index b20b489ac6dc5a..e4e2ab59615a13 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -253,6 +253,7 @@ homeassistant/components/launch_library/* @ludeeus homeassistant/components/lcn/* @alengwenus homeassistant/components/life360/* @pnbruckner homeassistant/components/linux_battery/* @fabaff +homeassistant/components/litejet/* @joncar homeassistant/components/litterrobot/* @natekspencer homeassistant/components/local_ip/* @issacg homeassistant/components/logger/* @home-assistant/core diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py index 9977bb9bdb4b50..0c8f59c412743e 100644 --- a/homeassistant/components/litejet/__init__.py +++ b/homeassistant/components/litejet/__init__.py @@ -1,49 +1,86 @@ """Support for the LiteJet lighting system.""" -from pylitejet import LiteJet +import asyncio +import logging + +import pylitejet +from serial import SerialException import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PORT -from homeassistant.helpers import discovery +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv -CONF_EXCLUDE_NAMES = "exclude_names" -CONF_INCLUDE_SWITCHES = "include_switches" +from .const import CONF_EXCLUDE_NAMES, CONF_INCLUDE_SWITCHES, DOMAIN, PLATFORMS -DOMAIN = "litejet" +_LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_PORT): cv.string, - vol.Optional(CONF_EXCLUDE_NAMES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_INCLUDE_SWITCHES, default=False): cv.boolean, - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_PORT): cv.string, + vol.Optional(CONF_EXCLUDE_NAMES): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_INCLUDE_SWITCHES, default=False): cv.boolean, + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) def setup(hass, config): """Set up the LiteJet component.""" + if DOMAIN in config and not hass.config_entries.async_entries(DOMAIN): + # No config entry exists and configuration.yaml config exists, trigger the import flow. + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) + return True + - url = config[DOMAIN].get(CONF_PORT) +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up LiteJet via a config entry.""" + port = entry.data[CONF_PORT] - hass.data["litejet_system"] = LiteJet(url) - hass.data["litejet_config"] = config[DOMAIN] + try: + system = pylitejet.LiteJet(port) + except SerialException as ex: + _LOGGER.error("Error connecting to the LiteJet MCP at %s", port, exc_info=ex) + raise ConfigEntryNotReady from ex - discovery.load_platform(hass, "light", DOMAIN, {}, config) - if config[DOMAIN].get(CONF_INCLUDE_SWITCHES): - discovery.load_platform(hass, "switch", DOMAIN, {}, config) - discovery.load_platform(hass, "scene", DOMAIN, {}, config) + hass.data[DOMAIN] = system + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) return True -def is_ignored(hass, name): - """Determine if a load, switch, or scene should be ignored.""" - for prefix in hass.data["litejet_config"].get(CONF_EXCLUDE_NAMES, []): - if name.startswith(prefix): - return True - return False +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a LiteJet config entry.""" + + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + + if unload_ok: + hass.data[DOMAIN].close() + hass.data.pop(DOMAIN) + + return unload_ok diff --git a/homeassistant/components/litejet/config_flow.py b/homeassistant/components/litejet/config_flow.py new file mode 100644 index 00000000000000..e1c7d8ab7b93bb --- /dev/null +++ b/homeassistant/components/litejet/config_flow.py @@ -0,0 +1,53 @@ +"""Config flow for the LiteJet lighting system.""" +import logging +from typing import Any, Dict, Optional + +import pylitejet +from serial import SerialException +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PORT + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class LiteJetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """LiteJet config flow.""" + + async def async_step_user( + self, user_input: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Create a LiteJet config entry based upon user input.""" + if self.hass.config_entries.async_entries(DOMAIN): + return self.async_abort(reason="single_instance_allowed") + + errors = {} + if user_input is not None: + port = user_input[CONF_PORT] + + await self.async_set_unique_id(port) + self._abort_if_unique_id_configured() + + try: + system = pylitejet.LiteJet(port) + system.close() + except SerialException: + errors[CONF_PORT] = "open_failed" + else: + return self.async_create_entry( + title=port, + data={CONF_PORT: port}, + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_PORT): str}), + errors=errors, + ) + + async def async_step_import(self, import_data): + """Import litejet config from configuration.yaml.""" + return self.async_create_entry(title=import_data[CONF_PORT], data=import_data) diff --git a/homeassistant/components/litejet/const.py b/homeassistant/components/litejet/const.py new file mode 100644 index 00000000000000..8e27aa3a0a7341 --- /dev/null +++ b/homeassistant/components/litejet/const.py @@ -0,0 +1,8 @@ +"""LiteJet constants.""" + +DOMAIN = "litejet" + +CONF_EXCLUDE_NAMES = "exclude_names" +CONF_INCLUDE_SWITCHES = "include_switches" + +PLATFORMS = ["light", "switch", "scene"] diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index efc6830d775149..27ce904cc2c681 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -1,43 +1,53 @@ """Support for LiteJet lights.""" import logging -from homeassistant.components import litejet from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, LightEntity, ) +from .const import DOMAIN + _LOGGER = logging.getLogger(__name__) ATTR_NUMBER = "number" -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up lights for the LiteJet platform.""" - litejet_ = hass.data["litejet_system"] +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + + system = hass.data[DOMAIN] + + def get_entities(system): + entities = [] + for i in system.loads(): + name = system.get_load_name(i) + entities.append(LiteJetLight(config_entry.entry_id, system, i, name)) + return entities - devices = [] - for i in litejet_.loads(): - name = litejet_.get_load_name(i) - if not litejet.is_ignored(hass, name): - devices.append(LiteJetLight(hass, litejet_, i, name)) - add_entities(devices, True) + async_add_entities(await hass.async_add_executor_job(get_entities, system), True) class LiteJetLight(LightEntity): """Representation of a single LiteJet light.""" - def __init__(self, hass, lj, i, name): + def __init__(self, entry_id, lj, i, name): """Initialize a LiteJet light.""" - self._hass = hass + self._entry_id = entry_id self._lj = lj self._index = i self._brightness = 0 self._name = name - lj.on_load_activated(i, self._on_load_changed) - lj.on_load_deactivated(i, self._on_load_changed) + async def async_added_to_hass(self): + """Run when this Entity has been added to HA.""" + self._lj.on_load_activated(self._index, self._on_load_changed) + self._lj.on_load_deactivated(self._index, self._on_load_changed) + + async def async_will_remove_from_hass(self): + """Entity being removed from hass.""" + self._lj.unsubscribe(self._on_load_changed) def _on_load_changed(self): """Handle state changes.""" @@ -54,6 +64,11 @@ def name(self): """Return the light's name.""" return self._name + @property + def unique_id(self): + """Return a unique identifier for this light.""" + return f"{self._entry_id}_{self._index}" + @property def brightness(self): """Return the light's brightness.""" diff --git a/homeassistant/components/litejet/manifest.json b/homeassistant/components/litejet/manifest.json index 1e469370b4338d..e23e5ac2964cca 100644 --- a/homeassistant/components/litejet/manifest.json +++ b/homeassistant/components/litejet/manifest.json @@ -2,6 +2,7 @@ "domain": "litejet", "name": "LiteJet", "documentation": "https://www.home-assistant.io/integrations/litejet", - "requirements": ["pylitejet==0.1"], - "codeowners": [] + "requirements": ["pylitejet==0.3.0"], + "codeowners": ["@joncar"], + "config_flow": true } diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index 3311b8d86a0616..daadfce90dcc2d 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -1,29 +1,37 @@ """Support for LiteJet scenes.""" +import logging from typing import Any -from homeassistant.components import litejet from homeassistant.components.scene import Scene +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + ATTR_NUMBER = "number" -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up scenes for the LiteJet platform.""" - litejet_ = hass.data["litejet_system"] +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + + system = hass.data[DOMAIN] + + def get_entities(system): + entities = [] + for i in system.scenes(): + name = system.get_scene_name(i) + entities.append(LiteJetScene(config_entry.entry_id, system, i, name)) + return entities - devices = [] - for i in litejet_.scenes(): - name = litejet_.get_scene_name(i) - if not litejet.is_ignored(hass, name): - devices.append(LiteJetScene(litejet_, i, name)) - add_entities(devices) + async_add_entities(await hass.async_add_executor_job(get_entities, system), True) class LiteJetScene(Scene): """Representation of a single LiteJet scene.""" - def __init__(self, lj, i, name): + def __init__(self, entry_id, lj, i, name): """Initialize the scene.""" + self._entry_id = entry_id self._lj = lj self._index = i self._name = name @@ -33,6 +41,11 @@ def name(self): """Return the name of the scene.""" return self._name + @property + def unique_id(self): + """Return a unique identifier for this scene.""" + return f"{self._entry_id}_{self._index}" + @property def device_state_attributes(self): """Return the device-specific state attributes.""" @@ -41,3 +54,8 @@ def device_state_attributes(self): def activate(self, **kwargs: Any) -> None: """Activate the scene.""" self._lj.activate_scene(self._index) + + @property + def entity_registry_enabled_default(self) -> bool: + """Scenes are only enabled by explicit user choice.""" + return False diff --git a/homeassistant/components/litejet/strings.json b/homeassistant/components/litejet/strings.json new file mode 100644 index 00000000000000..79c4ed5f329b3a --- /dev/null +++ b/homeassistant/components/litejet/strings.json @@ -0,0 +1,19 @@ +{ + "config": { + "step": { + "user": { + "title": "Connect To LiteJet", + "description": "Connect the LiteJet's RS232-2 port to your computer and enter the path to the serial port device.\n\nThe LiteJet MCP must be configured for 19.2 K baud, 8 data bits, 1 stop bit, no parity, and to transmit a 'CR' after each response.", + "data": { + "port": "[%key:common::config_flow::data::port%]" + } + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, + "error": { + "open_failed": "Cannot open the specified serial port." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index a734dc46d3e6b9..b782a4a9d98421 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -1,39 +1,50 @@ """Support for LiteJet switch.""" import logging -from homeassistant.components import litejet from homeassistant.components.switch import SwitchEntity +from .const import DOMAIN + ATTR_NUMBER = "number" _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the LiteJet switch platform.""" - litejet_ = hass.data["litejet_system"] +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + + system = hass.data[DOMAIN] + + def get_entities(system): + entities = [] + for i in system.button_switches(): + name = system.get_switch_name(i) + entities.append(LiteJetSwitch(config_entry.entry_id, system, i, name)) + return entities - devices = [] - for i in litejet_.button_switches(): - name = litejet_.get_switch_name(i) - if not litejet.is_ignored(hass, name): - devices.append(LiteJetSwitch(hass, litejet_, i, name)) - add_entities(devices, True) + async_add_entities(await hass.async_add_executor_job(get_entities, system), True) class LiteJetSwitch(SwitchEntity): """Representation of a single LiteJet switch.""" - def __init__(self, hass, lj, i, name): + def __init__(self, entry_id, lj, i, name): """Initialize a LiteJet switch.""" - self._hass = hass + self._entry_id = entry_id self._lj = lj self._index = i self._state = False self._name = name - lj.on_switch_pressed(i, self._on_switch_pressed) - lj.on_switch_released(i, self._on_switch_released) + async def async_added_to_hass(self): + """Run when this Entity has been added to HA.""" + self._lj.on_switch_pressed(self._index, self._on_switch_pressed) + self._lj.on_switch_released(self._index, self._on_switch_released) + + async def async_will_remove_from_hass(self): + """Entity being removed from hass.""" + self._lj.unsubscribe(self._on_switch_pressed) + self._lj.unsubscribe(self._on_switch_released) def _on_switch_pressed(self): _LOGGER.debug("Updating pressed for %s", self._name) @@ -50,6 +61,11 @@ def name(self): """Return the name of the switch.""" return self._name + @property + def unique_id(self): + """Return a unique identifier for this switch.""" + return f"{self._entry_id}_{self._index}" + @property def is_on(self): """Return if the switch is pressed.""" @@ -72,3 +88,8 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): """Release the switch.""" self._lj.release_switch(self._index) + + @property + def entity_registry_enabled_default(self) -> bool: + """Switches are only enabled by explicit user choice.""" + return False diff --git a/homeassistant/components/litejet/translations/en.json b/homeassistant/components/litejet/translations/en.json new file mode 100644 index 00000000000000..e09b20dc9f2527 --- /dev/null +++ b/homeassistant/components/litejet/translations/en.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "error": { + "open_failed": "Cannot open the specified serial port." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connect the LiteJet's RS232-2 port to your computer and enter the path to the serial port device.\n\nThe LiteJet MCP must be configured for 19.2 K baud, 8 data bits, 1 stop bit, no parity, and to transmit a 'CR' after each response.", + "title": "Connect To LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/trigger.py b/homeassistant/components/litejet/trigger.py index 0b0117465df472..71841d9c4fd8c2 100644 --- a/homeassistant/components/litejet/trigger.py +++ b/homeassistant/components/litejet/trigger.py @@ -1,4 +1,6 @@ """Trigger an automation when a LiteJet switch is released.""" +from typing import Callable + import voluptuous as vol from homeassistant.const import CONF_PLATFORM @@ -7,7 +9,7 @@ from homeassistant.helpers.event import track_point_in_utc_time import homeassistant.util.dt as dt_util -# mypy: allow-untyped-defs, no-check-untyped-defs +from .const import DOMAIN CONF_NUMBER = "number" CONF_HELD_MORE_THAN = "held_more_than" @@ -33,7 +35,7 @@ async def async_attach_trigger(hass, config, action, automation_info): held_more_than = config.get(CONF_HELD_MORE_THAN) held_less_than = config.get(CONF_HELD_LESS_THAN) pressed_time = None - cancel_pressed_more_than = None + cancel_pressed_more_than: Callable = None job = HassJob(action) @callback @@ -91,12 +93,15 @@ def released(): ): hass.add_job(call_action) - hass.data["litejet_system"].on_switch_pressed(number, pressed) - hass.data["litejet_system"].on_switch_released(number, released) + system = hass.data[DOMAIN] + + system.on_switch_pressed(number, pressed) + system.on_switch_released(number, released) @callback def async_remove(): """Remove all subscriptions used for this trigger.""" - return + system.unsubscribe(pressed) + system.unsubscribe(released) return async_remove diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 36c22262ef4d86..e8e06ed7a1591b 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -121,6 +121,7 @@ "kulersky", "life360", "lifx", + "litejet", "litterrobot", "local_ip", "locative", diff --git a/requirements_all.txt b/requirements_all.txt index 00bd7af0d74d52..5f971b4ece78ea 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1501,7 +1501,7 @@ pylgnetcast-homeassistant==0.2.0.dev0 pylibrespot-java==0.1.0 # homeassistant.components.litejet -pylitejet==0.1 +pylitejet==0.3.0 # homeassistant.components.litterrobot pylitterbot==2021.2.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3a0954f41ad425..c78f2fbb96a925 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -791,7 +791,7 @@ pylast==4.1.0 pylibrespot-java==0.1.0 # homeassistant.components.litejet -pylitejet==0.1 +pylitejet==0.3.0 # homeassistant.components.litterrobot pylitterbot==2021.2.5 diff --git a/tests/components/litejet/__init__.py b/tests/components/litejet/__init__.py index 9a01fbe5114fd1..13e2b547cd84b2 100644 --- a/tests/components/litejet/__init__.py +++ b/tests/components/litejet/__init__.py @@ -1 +1,51 @@ """Tests for the litejet component.""" +from homeassistant.components import scene, switch +from homeassistant.components.litejet import DOMAIN +from homeassistant.const import CONF_PORT + +from tests.common import MockConfigEntry + + +async def async_init_integration( + hass, use_switch=False, use_scene=False +) -> MockConfigEntry: + """Set up the LiteJet integration in Home Assistant.""" + + registry = await hass.helpers.entity_registry.async_get_registry() + + entry_data = {CONF_PORT: "/dev/mock"} + + entry = MockConfigEntry( + domain=DOMAIN, unique_id=entry_data[CONF_PORT], data=entry_data + ) + + if use_switch: + registry.async_get_or_create( + switch.DOMAIN, + DOMAIN, + f"{entry.entry_id}_1", + suggested_object_id="mock_switch_1", + disabled_by=None, + ) + registry.async_get_or_create( + switch.DOMAIN, + DOMAIN, + f"{entry.entry_id}_2", + suggested_object_id="mock_switch_2", + disabled_by=None, + ) + + if use_scene: + registry.async_get_or_create( + scene.DOMAIN, + DOMAIN, + f"{entry.entry_id}_1", + suggested_object_id="mock_scene_1", + disabled_by=None, + ) + + 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/litejet/conftest.py b/tests/components/litejet/conftest.py index 68797f96ccff14..00b1eb921901e5 100644 --- a/tests/components/litejet/conftest.py +++ b/tests/components/litejet/conftest.py @@ -1,2 +1,62 @@ -"""litejet conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +"""Fixtures for LiteJet testing.""" +from datetime import timedelta +from unittest.mock import patch + +import pytest + +import homeassistant.util.dt as dt_util + + +@pytest.fixture +def mock_litejet(): + """Mock LiteJet system.""" + with patch("pylitejet.LiteJet") as mock_pylitejet: + + def get_load_name(number): + return f"Mock Load #{number}" + + def get_scene_name(number): + return f"Mock Scene #{number}" + + def get_switch_name(number): + return f"Mock Switch #{number}" + + mock_lj = mock_pylitejet.return_value + + mock_lj.switch_pressed_callbacks = {} + mock_lj.switch_released_callbacks = {} + mock_lj.load_activated_callbacks = {} + mock_lj.load_deactivated_callbacks = {} + + def on_switch_pressed(number, callback): + mock_lj.switch_pressed_callbacks[number] = callback + + def on_switch_released(number, callback): + mock_lj.switch_released_callbacks[number] = callback + + def on_load_activated(number, callback): + mock_lj.load_activated_callbacks[number] = callback + + def on_load_deactivated(number, callback): + mock_lj.load_deactivated_callbacks[number] = callback + + mock_lj.on_switch_pressed.side_effect = on_switch_pressed + mock_lj.on_switch_released.side_effect = on_switch_released + mock_lj.on_load_activated.side_effect = on_load_activated + mock_lj.on_load_deactivated.side_effect = on_load_deactivated + + mock_lj.loads.return_value = range(1, 3) + mock_lj.get_load_name.side_effect = get_load_name + mock_lj.get_load_level.return_value = 0 + + mock_lj.button_switches.return_value = range(1, 3) + mock_lj.all_switches.return_value = range(1, 6) + mock_lj.get_switch_name.side_effect = get_switch_name + + mock_lj.scenes.return_value = range(1, 3) + mock_lj.get_scene_name.side_effect = get_scene_name + + mock_lj.start_time = dt_util.utcnow() + mock_lj.last_delta = timedelta(0) + + yield mock_lj diff --git a/tests/components/litejet/test_config_flow.py b/tests/components/litejet/test_config_flow.py new file mode 100644 index 00000000000000..015ba1c64946a8 --- /dev/null +++ b/tests/components/litejet/test_config_flow.py @@ -0,0 +1,77 @@ +"""The tests for the litejet component.""" +from unittest.mock import patch + +from serial import SerialException + +from homeassistant.components.litejet.const import DOMAIN +from homeassistant.const import CONF_PORT + +from tests.common import MockConfigEntry + + +async def test_show_config_form(hass): + """Test show configuration form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + +async def test_create_entry(hass, mock_litejet): + """Test create entry from user input.""" + test_data = {CONF_PORT: "/dev/test"} + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"}, data=test_data + ) + + assert result["type"] == "create_entry" + assert result["title"] == "/dev/test" + assert result["data"] == test_data + + +async def test_flow_entry_already_exists(hass): + """Test user input when a config entry already exists.""" + first_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_PORT: "/dev/first"}, + ) + first_entry.add_to_hass(hass) + + test_data = {CONF_PORT: "/dev/test"} + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"}, data=test_data + ) + + assert result["type"] == "abort" + assert result["reason"] == "single_instance_allowed" + + +async def test_flow_open_failed(hass): + """Test user input when serial port open fails.""" + test_data = {CONF_PORT: "/dev/test"} + + with patch("pylitejet.LiteJet") as mock_pylitejet: + mock_pylitejet.side_effect = SerialException + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"}, data=test_data + ) + + assert result["type"] == "form" + assert result["errors"][CONF_PORT] == "open_failed" + + +async def test_import_step(hass): + """Test initializing via import step.""" + test_data = {CONF_PORT: "/dev/imported"} + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "import"}, data=test_data + ) + + assert result["type"] == "create_entry" + assert result["title"] == test_data[CONF_PORT] + assert result["data"] == test_data diff --git a/tests/components/litejet/test_init.py b/tests/components/litejet/test_init.py index 4aee0086cbdfe0..636864526217d5 100644 --- a/tests/components/litejet/test_init.py +++ b/tests/components/litejet/test_init.py @@ -1,41 +1,30 @@ """The tests for the litejet component.""" -import unittest - from homeassistant.components import litejet +from homeassistant.components.litejet.const import DOMAIN +from homeassistant.const import CONF_PORT +from homeassistant.setup import async_setup_component + +from . import async_init_integration + + +async def test_setup_with_no_config(hass): + """Test that nothing happens.""" + assert await async_setup_component(hass, DOMAIN, {}) is True + assert DOMAIN not in hass.data + + +async def test_setup_with_config_to_import(hass, mock_litejet): + """Test that import happens.""" + assert ( + await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PORT: "/dev/hello"}}) + is True + ) + assert DOMAIN in hass.data + + +async def test_unload_entry(hass, mock_litejet): + """Test being able to unload an entry.""" + entry = await async_init_integration(hass, use_switch=True, use_scene=True) -from tests.common import get_test_home_assistant - - -class TestLiteJet(unittest.TestCase): - """Test the litejet component.""" - - def setup_method(self, method): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.start() - self.hass.block_till_done() - - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - - def test_is_ignored_unspecified(self): - """Ensure it is ignored when unspecified.""" - self.hass.data["litejet_config"] = {} - assert not litejet.is_ignored(self.hass, "Test") - - def test_is_ignored_empty(self): - """Ensure it is ignored when empty.""" - self.hass.data["litejet_config"] = {litejet.CONF_EXCLUDE_NAMES: []} - assert not litejet.is_ignored(self.hass, "Test") - - def test_is_ignored_normal(self): - """Test if usually ignored.""" - self.hass.data["litejet_config"] = { - litejet.CONF_EXCLUDE_NAMES: ["Test", "Other One"] - } - assert litejet.is_ignored(self.hass, "Test") - assert not litejet.is_ignored(self.hass, "Other one") - assert not litejet.is_ignored(self.hass, "Other 0ne") - assert litejet.is_ignored(self.hass, "Other One There") - assert litejet.is_ignored(self.hass, "Other One") + assert await litejet.async_unload_entry(hass, entry) + assert DOMAIN not in hass.data diff --git a/tests/components/litejet/test_light.py b/tests/components/litejet/test_light.py index e08bd5c27ac679..c455d3a960e652 100644 --- a/tests/components/litejet/test_light.py +++ b/tests/components/litejet/test_light.py @@ -1,14 +1,11 @@ """The tests for the litejet component.""" import logging -import unittest -from unittest import mock -from homeassistant import setup -from homeassistant.components import litejet -import homeassistant.components.light as light +from homeassistant.components import light +from homeassistant.components.light import ATTR_BRIGHTNESS +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON -from tests.common import get_test_home_assistant -from tests.components.light import common +from . import async_init_integration _LOGGER = logging.getLogger(__name__) @@ -18,144 +15,113 @@ ENTITY_OTHER_LIGHT_NUMBER = 2 -class TestLiteJetLight(unittest.TestCase): - """Test the litejet component.""" +async def test_on_brightness(hass, mock_litejet): + """Test turning the light on with brightness.""" + await async_init_integration(hass) - @mock.patch("homeassistant.components.litejet.LiteJet") - def setup_method(self, method, mock_pylitejet): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.start() + assert hass.states.get(ENTITY_LIGHT).state == "off" + assert hass.states.get(ENTITY_OTHER_LIGHT).state == "off" - self.load_activated_callbacks = {} - self.load_deactivated_callbacks = {} + assert not light.is_on(hass, ENTITY_LIGHT) - def get_load_name(number): - return f"Mock Load #{number}" + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_BRIGHTNESS: 102}, + blocking=True, + ) + mock_litejet.activate_load_at.assert_called_with(ENTITY_LIGHT_NUMBER, 39, 0) - def on_load_activated(number, callback): - self.load_activated_callbacks[number] = callback - def on_load_deactivated(number, callback): - self.load_deactivated_callbacks[number] = callback +async def test_on_off(hass, mock_litejet): + """Test turning the light on and off.""" + await async_init_integration(hass) - self.mock_lj = mock_pylitejet.return_value - self.mock_lj.loads.return_value = range(1, 3) - self.mock_lj.button_switches.return_value = range(0) - self.mock_lj.all_switches.return_value = range(0) - self.mock_lj.scenes.return_value = range(0) - self.mock_lj.get_load_level.return_value = 0 - self.mock_lj.get_load_name.side_effect = get_load_name - self.mock_lj.on_load_activated.side_effect = on_load_activated - self.mock_lj.on_load_deactivated.side_effect = on_load_deactivated + assert hass.states.get(ENTITY_LIGHT).state == "off" + assert hass.states.get(ENTITY_OTHER_LIGHT).state == "off" - assert setup.setup_component( - self.hass, - litejet.DOMAIN, - {"litejet": {"port": "/dev/serial/by-id/mock-litejet"}}, - ) - self.hass.block_till_done() + assert not light.is_on(hass, ENTITY_LIGHT) - self.mock_lj.get_load_level.reset_mock() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_LIGHT}, + blocking=True, + ) + mock_litejet.activate_load.assert_called_with(ENTITY_LIGHT_NUMBER) - def light(self): - """Test for main light entity.""" - return self.hass.states.get(ENTITY_LIGHT) + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_LIGHT}, + blocking=True, + ) + mock_litejet.deactivate_load.assert_called_with(ENTITY_LIGHT_NUMBER) - def other_light(self): - """Test the other light.""" - return self.hass.states.get(ENTITY_OTHER_LIGHT) - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() +async def test_activated_event(hass, mock_litejet): + """Test handling an event from LiteJet.""" - def test_on_brightness(self): - """Test turning the light on with brightness.""" - assert self.light().state == "off" - assert self.other_light().state == "off" + await async_init_integration(hass) - assert not light.is_on(self.hass, ENTITY_LIGHT) + # Light 1 + mock_litejet.get_load_level.return_value = 99 + mock_litejet.get_load_level.reset_mock() + mock_litejet.load_activated_callbacks[ENTITY_LIGHT_NUMBER]() + await hass.async_block_till_done() - common.turn_on(self.hass, ENTITY_LIGHT, brightness=102) - self.hass.block_till_done() - self.mock_lj.activate_load_at.assert_called_with(ENTITY_LIGHT_NUMBER, 39, 0) + mock_litejet.get_load_level.assert_called_once_with(ENTITY_LIGHT_NUMBER) - def test_on_off(self): - """Test turning the light on and off.""" - assert self.light().state == "off" - assert self.other_light().state == "off" + assert light.is_on(hass, ENTITY_LIGHT) + assert not light.is_on(hass, ENTITY_OTHER_LIGHT) + assert hass.states.get(ENTITY_LIGHT).state == "on" + assert hass.states.get(ENTITY_OTHER_LIGHT).state == "off" + assert hass.states.get(ENTITY_LIGHT).attributes.get(ATTR_BRIGHTNESS) == 255 - assert not light.is_on(self.hass, ENTITY_LIGHT) + # Light 2 - common.turn_on(self.hass, ENTITY_LIGHT) - self.hass.block_till_done() - self.mock_lj.activate_load.assert_called_with(ENTITY_LIGHT_NUMBER) + mock_litejet.get_load_level.return_value = 40 + mock_litejet.get_load_level.reset_mock() + mock_litejet.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() + await hass.async_block_till_done() - common.turn_off(self.hass, ENTITY_LIGHT) - self.hass.block_till_done() - self.mock_lj.deactivate_load.assert_called_with(ENTITY_LIGHT_NUMBER) + mock_litejet.get_load_level.assert_called_once_with(ENTITY_OTHER_LIGHT_NUMBER) - def test_activated_event(self): - """Test handling an event from LiteJet.""" - self.mock_lj.get_load_level.return_value = 99 + assert light.is_on(hass, ENTITY_LIGHT) + assert light.is_on(hass, ENTITY_OTHER_LIGHT) + assert hass.states.get(ENTITY_LIGHT).state == "on" + assert hass.states.get(ENTITY_OTHER_LIGHT).state == "on" + assert ( + int(hass.states.get(ENTITY_OTHER_LIGHT).attributes.get(ATTR_BRIGHTNESS)) == 103 + ) - # Light 1 - _LOGGER.info(self.load_activated_callbacks[ENTITY_LIGHT_NUMBER]) - self.load_activated_callbacks[ENTITY_LIGHT_NUMBER]() - self.hass.block_till_done() +async def test_deactivated_event(hass, mock_litejet): + """Test handling an event from LiteJet.""" + await async_init_integration(hass) - self.mock_lj.get_load_level.assert_called_once_with(ENTITY_LIGHT_NUMBER) + # Initial state is on. + mock_litejet.get_load_level.return_value = 99 - assert light.is_on(self.hass, ENTITY_LIGHT) - assert not light.is_on(self.hass, ENTITY_OTHER_LIGHT) - assert self.light().state == "on" - assert self.other_light().state == "off" - assert self.light().attributes.get(light.ATTR_BRIGHTNESS) == 255 + mock_litejet.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() + await hass.async_block_till_done() - # Light 2 + assert light.is_on(hass, ENTITY_OTHER_LIGHT) - self.mock_lj.get_load_level.return_value = 40 + # Event indicates it is off now. - self.mock_lj.get_load_level.reset_mock() + mock_litejet.get_load_level.reset_mock() + mock_litejet.get_load_level.return_value = 0 - self.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() - self.hass.block_till_done() + mock_litejet.load_deactivated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() + await hass.async_block_till_done() - self.mock_lj.get_load_level.assert_called_once_with(ENTITY_OTHER_LIGHT_NUMBER) + # (Requesting the level is not strictly needed with a deactivated + # event but the implementation happens to do it. This could be + # changed to an assert_not_called in the future.) + mock_litejet.get_load_level.assert_called_with(ENTITY_OTHER_LIGHT_NUMBER) - assert light.is_on(self.hass, ENTITY_OTHER_LIGHT) - assert light.is_on(self.hass, ENTITY_LIGHT) - assert self.light().state == "on" - assert self.other_light().state == "on" - assert int(self.other_light().attributes[light.ATTR_BRIGHTNESS]) == 103 - - def test_deactivated_event(self): - """Test handling an event from LiteJet.""" - # Initial state is on. - - self.mock_lj.get_load_level.return_value = 99 - - self.load_activated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() - self.hass.block_till_done() - - assert light.is_on(self.hass, ENTITY_OTHER_LIGHT) - - # Event indicates it is off now. - - self.mock_lj.get_load_level.reset_mock() - self.mock_lj.get_load_level.return_value = 0 - - self.load_deactivated_callbacks[ENTITY_OTHER_LIGHT_NUMBER]() - self.hass.block_till_done() - - # (Requesting the level is not strictly needed with a deactivated - # event but the implementation happens to do it. This could be - # changed to an assert_not_called in the future.) - self.mock_lj.get_load_level.assert_called_with(ENTITY_OTHER_LIGHT_NUMBER) - - assert not light.is_on(self.hass, ENTITY_OTHER_LIGHT) - assert not light.is_on(self.hass, ENTITY_LIGHT) - assert self.light().state == "off" - assert self.other_light().state == "off" + assert not light.is_on(hass, ENTITY_OTHER_LIGHT) + assert not light.is_on(hass, ENTITY_LIGHT) + assert hass.states.get(ENTITY_LIGHT).state == "off" + assert hass.states.get(ENTITY_OTHER_LIGHT).state == "off" diff --git a/tests/components/litejet/test_scene.py b/tests/components/litejet/test_scene.py index fe9298cf1877f5..5df26f8c68022e 100644 --- a/tests/components/litejet/test_scene.py +++ b/tests/components/litejet/test_scene.py @@ -1,12 +1,8 @@ """The tests for the litejet component.""" -import unittest -from unittest import mock +from homeassistant.components import scene +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON -from homeassistant import setup -from homeassistant.components import litejet - -from tests.common import get_test_home_assistant -from tests.components.scene import common +from . import async_init_integration ENTITY_SCENE = "scene.mock_scene_1" ENTITY_SCENE_NUMBER = 1 @@ -14,46 +10,31 @@ ENTITY_OTHER_SCENE_NUMBER = 2 -class TestLiteJetScene(unittest.TestCase): - """Test the litejet component.""" - - @mock.patch("homeassistant.components.litejet.LiteJet") - def setup_method(self, method, mock_pylitejet): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.start() - - def get_scene_name(number): - return f"Mock Scene #{number}" - - self.mock_lj = mock_pylitejet.return_value - self.mock_lj.loads.return_value = range(0) - self.mock_lj.button_switches.return_value = range(0) - self.mock_lj.all_switches.return_value = range(0) - self.mock_lj.scenes.return_value = range(1, 3) - self.mock_lj.get_scene_name.side_effect = get_scene_name - - assert setup.setup_component( - self.hass, - litejet.DOMAIN, - {"litejet": {"port": "/dev/serial/by-id/mock-litejet"}}, - ) - self.hass.block_till_done() - - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - - def scene(self): - """Get the current scene.""" - return self.hass.states.get(ENTITY_SCENE) - - def other_scene(self): - """Get the other scene.""" - return self.hass.states.get(ENTITY_OTHER_SCENE) - - def test_activate(self): - """Test activating the scene.""" - common.activate(self.hass, ENTITY_SCENE) - self.hass.block_till_done() - self.mock_lj.activate_scene.assert_called_once_with(ENTITY_SCENE_NUMBER) +async def test_disabled_by_default(hass, mock_litejet): + """Test the scene is disabled by default.""" + await async_init_integration(hass) + + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get(ENTITY_SCENE) + assert state is None + + entry = registry.async_get(ENTITY_SCENE) + assert entry + assert entry.disabled + assert entry.disabled_by == "integration" + + +async def test_activate(hass, mock_litejet): + """Test activating the scene.""" + + await async_init_integration(hass, use_scene=True) + + state = hass.states.get(ENTITY_SCENE) + assert state is not None + + await hass.services.async_call( + scene.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_SCENE}, blocking=True + ) + + mock_litejet.activate_scene.assert_called_once_with(ENTITY_SCENE_NUMBER) diff --git a/tests/components/litejet/test_switch.py b/tests/components/litejet/test_switch.py index 2f897045c924a9..dfcb980109328a 100644 --- a/tests/components/litejet/test_switch.py +++ b/tests/components/litejet/test_switch.py @@ -1,14 +1,10 @@ """The tests for the litejet component.""" import logging -import unittest -from unittest import mock -from homeassistant import setup -from homeassistant.components import litejet -import homeassistant.components.switch as switch +from homeassistant.components import switch +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON -from tests.common import get_test_home_assistant -from tests.components.switch import common +from . import async_init_integration _LOGGER = logging.getLogger(__name__) @@ -18,117 +14,67 @@ ENTITY_OTHER_SWITCH_NUMBER = 2 -class TestLiteJetSwitch(unittest.TestCase): - """Test the litejet component.""" - - @mock.patch("homeassistant.components.litejet.LiteJet") - def setup_method(self, method, mock_pylitejet): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.start() +async def test_on_off(hass, mock_litejet): + """Test turning the switch on and off.""" - self.switch_pressed_callbacks = {} - self.switch_released_callbacks = {} + await async_init_integration(hass, use_switch=True) - def get_switch_name(number): - return f"Mock Switch #{number}" - - def on_switch_pressed(number, callback): - self.switch_pressed_callbacks[number] = callback - - def on_switch_released(number, callback): - self.switch_released_callbacks[number] = callback - - self.mock_lj = mock_pylitejet.return_value - self.mock_lj.loads.return_value = range(0) - self.mock_lj.button_switches.return_value = range(1, 3) - self.mock_lj.all_switches.return_value = range(1, 6) - self.mock_lj.scenes.return_value = range(0) - self.mock_lj.get_switch_name.side_effect = get_switch_name - self.mock_lj.on_switch_pressed.side_effect = on_switch_pressed - self.mock_lj.on_switch_released.side_effect = on_switch_released - - config = {"litejet": {"port": "/dev/serial/by-id/mock-litejet"}} - if method == self.test_include_switches_False: - config["litejet"]["include_switches"] = False - elif method != self.test_include_switches_unspecified: - config["litejet"]["include_switches"] = True - - assert setup.setup_component(self.hass, litejet.DOMAIN, config) - self.hass.block_till_done() - - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - - def switch(self): - """Return the switch state.""" - return self.hass.states.get(ENTITY_SWITCH) - - def other_switch(self): - """Return the other switch state.""" - return self.hass.states.get(ENTITY_OTHER_SWITCH) - - def test_include_switches_unspecified(self): - """Test that switches are ignored by default.""" - self.mock_lj.button_switches.assert_not_called() - self.mock_lj.all_switches.assert_not_called() - - def test_include_switches_False(self): - """Test that switches can be explicitly ignored.""" - self.mock_lj.button_switches.assert_not_called() - self.mock_lj.all_switches.assert_not_called() - - def test_on_off(self): - """Test turning the switch on and off.""" - assert self.switch().state == "off" - assert self.other_switch().state == "off" - - assert not switch.is_on(self.hass, ENTITY_SWITCH) - - common.turn_on(self.hass, ENTITY_SWITCH) - self.hass.block_till_done() - self.mock_lj.press_switch.assert_called_with(ENTITY_SWITCH_NUMBER) - - common.turn_off(self.hass, ENTITY_SWITCH) - self.hass.block_till_done() - self.mock_lj.release_switch.assert_called_with(ENTITY_SWITCH_NUMBER) - - def test_pressed_event(self): - """Test handling an event from LiteJet.""" - # Switch 1 - _LOGGER.info(self.switch_pressed_callbacks[ENTITY_SWITCH_NUMBER]) - self.switch_pressed_callbacks[ENTITY_SWITCH_NUMBER]() - self.hass.block_till_done() - - assert switch.is_on(self.hass, ENTITY_SWITCH) - assert not switch.is_on(self.hass, ENTITY_OTHER_SWITCH) - assert self.switch().state == "on" - assert self.other_switch().state == "off" - - # Switch 2 - self.switch_pressed_callbacks[ENTITY_OTHER_SWITCH_NUMBER]() - self.hass.block_till_done() - - assert switch.is_on(self.hass, ENTITY_OTHER_SWITCH) - assert switch.is_on(self.hass, ENTITY_SWITCH) - assert self.other_switch().state == "on" - assert self.switch().state == "on" - - def test_released_event(self): - """Test handling an event from LiteJet.""" - # Initial state is on. - self.switch_pressed_callbacks[ENTITY_OTHER_SWITCH_NUMBER]() - self.hass.block_till_done() - - assert switch.is_on(self.hass, ENTITY_OTHER_SWITCH) - - # Event indicates it is off now. - - self.switch_released_callbacks[ENTITY_OTHER_SWITCH_NUMBER]() - self.hass.block_till_done() - - assert not switch.is_on(self.hass, ENTITY_OTHER_SWITCH) - assert not switch.is_on(self.hass, ENTITY_SWITCH) - assert self.other_switch().state == "off" - assert self.switch().state == "off" + assert hass.states.get(ENTITY_SWITCH).state == "off" + assert hass.states.get(ENTITY_OTHER_SWITCH).state == "off" + + assert not switch.is_on(hass, ENTITY_SWITCH) + + await hass.services.async_call( + switch.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_SWITCH}, blocking=True + ) + mock_litejet.press_switch.assert_called_with(ENTITY_SWITCH_NUMBER) + + await hass.services.async_call( + switch.DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_SWITCH}, blocking=True + ) + mock_litejet.release_switch.assert_called_with(ENTITY_SWITCH_NUMBER) + + +async def test_pressed_event(hass, mock_litejet): + """Test handling an event from LiteJet.""" + + await async_init_integration(hass, use_switch=True) + + # Switch 1 + mock_litejet.switch_pressed_callbacks[ENTITY_SWITCH_NUMBER]() + await hass.async_block_till_done() + + assert switch.is_on(hass, ENTITY_SWITCH) + assert not switch.is_on(hass, ENTITY_OTHER_SWITCH) + assert hass.states.get(ENTITY_SWITCH).state == "on" + assert hass.states.get(ENTITY_OTHER_SWITCH).state == "off" + + # Switch 2 + mock_litejet.switch_pressed_callbacks[ENTITY_OTHER_SWITCH_NUMBER]() + await hass.async_block_till_done() + + assert switch.is_on(hass, ENTITY_OTHER_SWITCH) + assert switch.is_on(hass, ENTITY_SWITCH) + assert hass.states.get(ENTITY_SWITCH).state == "on" + assert hass.states.get(ENTITY_OTHER_SWITCH).state == "on" + + +async def test_released_event(hass, mock_litejet): + """Test handling an event from LiteJet.""" + + await async_init_integration(hass, use_switch=True) + + # Initial state is on. + mock_litejet.switch_pressed_callbacks[ENTITY_OTHER_SWITCH_NUMBER]() + await hass.async_block_till_done() + + assert switch.is_on(hass, ENTITY_OTHER_SWITCH) + + # Event indicates it is off now. + mock_litejet.switch_released_callbacks[ENTITY_OTHER_SWITCH_NUMBER]() + await hass.async_block_till_done() + + assert not switch.is_on(hass, ENTITY_OTHER_SWITCH) + assert not switch.is_on(hass, ENTITY_SWITCH) + assert hass.states.get(ENTITY_SWITCH).state == "off" + assert hass.states.get(ENTITY_OTHER_SWITCH).state == "off" diff --git a/tests/components/litejet/test_trigger.py b/tests/components/litejet/test_trigger.py index 3cbbd474b88a2b..216da9b54efd31 100644 --- a/tests/components/litejet/test_trigger.py +++ b/tests/components/litejet/test_trigger.py @@ -2,14 +2,16 @@ from datetime import timedelta import logging from unittest import mock +from unittest.mock import patch import pytest from homeassistant import setup -from homeassistant.components import litejet import homeassistant.components.automation as automation import homeassistant.util.dt as dt_util +from . import async_init_integration + from tests.common import async_fire_time_changed, async_mock_service from tests.components.blueprint.conftest import stub_blueprint_populate # noqa @@ -27,88 +29,51 @@ def calls(hass): return async_mock_service(hass, "test", "automation") -def get_switch_name(number): - """Get a mock switch name.""" - return f"Mock Switch #{number}" - - -@pytest.fixture -def mock_lj(hass): - """Initialize components.""" - with mock.patch("homeassistant.components.litejet.LiteJet") as mock_pylitejet: - mock_lj = mock_pylitejet.return_value - - mock_lj.switch_pressed_callbacks = {} - mock_lj.switch_released_callbacks = {} - - def on_switch_pressed(number, callback): - mock_lj.switch_pressed_callbacks[number] = callback - - def on_switch_released(number, callback): - mock_lj.switch_released_callbacks[number] = callback - - mock_lj.loads.return_value = range(0) - mock_lj.button_switches.return_value = range(1, 3) - mock_lj.all_switches.return_value = range(1, 6) - mock_lj.scenes.return_value = range(0) - mock_lj.get_switch_name.side_effect = get_switch_name - mock_lj.on_switch_pressed.side_effect = on_switch_pressed - mock_lj.on_switch_released.side_effect = on_switch_released - - config = {"litejet": {"port": "/dev/serial/by-id/mock-litejet"}} - assert hass.loop.run_until_complete( - setup.async_setup_component(hass, litejet.DOMAIN, config) - ) - - mock_lj.start_time = dt_util.utcnow() - mock_lj.last_delta = timedelta(0) - return mock_lj - - -async def simulate_press(hass, mock_lj, number): +async def simulate_press(hass, mock_litejet, number): """Test to simulate a press.""" _LOGGER.info("*** simulate press of %d", number) - callback = mock_lj.switch_pressed_callbacks.get(number) + callback = mock_litejet.switch_pressed_callbacks.get(number) with mock.patch( "homeassistant.helpers.condition.dt_util.utcnow", - return_value=mock_lj.start_time + mock_lj.last_delta, + return_value=mock_litejet.start_time + mock_litejet.last_delta, ): if callback is not None: await hass.async_add_executor_job(callback) await hass.async_block_till_done() -async def simulate_release(hass, mock_lj, number): +async def simulate_release(hass, mock_litejet, number): """Test to simulate releasing.""" _LOGGER.info("*** simulate release of %d", number) - callback = mock_lj.switch_released_callbacks.get(number) + callback = mock_litejet.switch_released_callbacks.get(number) with mock.patch( "homeassistant.helpers.condition.dt_util.utcnow", - return_value=mock_lj.start_time + mock_lj.last_delta, + return_value=mock_litejet.start_time + mock_litejet.last_delta, ): if callback is not None: await hass.async_add_executor_job(callback) await hass.async_block_till_done() -async def simulate_time(hass, mock_lj, delta): +async def simulate_time(hass, mock_litejet, delta): """Test to simulate time.""" _LOGGER.info( - "*** simulate time change by %s: %s", delta, mock_lj.start_time + delta + "*** simulate time change by %s: %s", delta, mock_litejet.start_time + delta ) - mock_lj.last_delta = delta + mock_litejet.last_delta = delta with mock.patch( "homeassistant.helpers.condition.dt_util.utcnow", - return_value=mock_lj.start_time + delta, + return_value=mock_litejet.start_time + delta, ): _LOGGER.info("now=%s", dt_util.utcnow()) - async_fire_time_changed(hass, mock_lj.start_time + delta) + async_fire_time_changed(hass, mock_litejet.start_time + delta) await hass.async_block_till_done() _LOGGER.info("done with now=%s", dt_util.utcnow()) async def setup_automation(hass, trigger): """Test setting up the automation.""" + await async_init_integration(hass, use_switch=True) assert await setup.async_setup_component( hass, automation.DOMAIN, @@ -125,19 +90,19 @@ async def setup_automation(hass, trigger): await hass.async_block_till_done() -async def test_simple(hass, calls, mock_lj): +async def test_simple(hass, calls, mock_litejet): """Test the simplest form of a LiteJet trigger.""" await setup_automation( hass, {"platform": "litejet", "number": ENTITY_OTHER_SWITCH_NUMBER} ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 1 -async def test_held_more_than_short(hass, calls, mock_lj): +async def test_held_more_than_short(hass, calls, mock_litejet): """Test a too short hold.""" await setup_automation( hass, @@ -148,13 +113,13 @@ async def test_held_more_than_short(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) - await simulate_time(hass, mock_lj, timedelta(seconds=0.1)) - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.1)) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 -async def test_held_more_than_long(hass, calls, mock_lj): +async def test_held_more_than_long(hass, calls, mock_litejet): """Test a hold that is long enough.""" await setup_automation( hass, @@ -165,15 +130,15 @@ async def test_held_more_than_long(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 - await simulate_time(hass, mock_lj, timedelta(seconds=0.3)) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.3)) assert len(calls) == 1 - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 1 -async def test_held_less_than_short(hass, calls, mock_lj): +async def test_held_less_than_short(hass, calls, mock_litejet): """Test a hold that is short enough.""" await setup_automation( hass, @@ -184,14 +149,14 @@ async def test_held_less_than_short(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) - await simulate_time(hass, mock_lj, timedelta(seconds=0.1)) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.1)) assert len(calls) == 0 - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 1 -async def test_held_less_than_long(hass, calls, mock_lj): +async def test_held_less_than_long(hass, calls, mock_litejet): """Test a hold that is too long.""" await setup_automation( hass, @@ -202,15 +167,15 @@ async def test_held_less_than_long(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 - await simulate_time(hass, mock_lj, timedelta(seconds=0.3)) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.3)) assert len(calls) == 0 - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 -async def test_held_in_range_short(hass, calls, mock_lj): +async def test_held_in_range_short(hass, calls, mock_litejet): """Test an in-range trigger with a too short hold.""" await setup_automation( hass, @@ -222,13 +187,13 @@ async def test_held_in_range_short(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) - await simulate_time(hass, mock_lj, timedelta(seconds=0.05)) - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.05)) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 -async def test_held_in_range_just_right(hass, calls, mock_lj): +async def test_held_in_range_just_right(hass, calls, mock_litejet): """Test an in-range trigger with a just right hold.""" await setup_automation( hass, @@ -240,15 +205,15 @@ async def test_held_in_range_just_right(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 - await simulate_time(hass, mock_lj, timedelta(seconds=0.2)) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.2)) assert len(calls) == 0 - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 1 -async def test_held_in_range_long(hass, calls, mock_lj): +async def test_held_in_range_long(hass, calls, mock_litejet): """Test an in-range trigger with a too long hold.""" await setup_automation( hass, @@ -260,9 +225,50 @@ async def test_held_in_range_long(hass, calls, mock_lj): }, ) - await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) + assert len(calls) == 0 + await simulate_time(hass, mock_litejet, timedelta(seconds=0.4)) assert len(calls) == 0 - await simulate_time(hass, mock_lj, timedelta(seconds=0.4)) + await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) + assert len(calls) == 0 + + +async def test_reload(hass, calls, mock_litejet): + """Test reloading automation.""" + await setup_automation( + hass, + { + "platform": "litejet", + "number": ENTITY_OTHER_SWITCH_NUMBER, + "held_more_than": {"milliseconds": "100"}, + "held_less_than": {"milliseconds": "300"}, + }, + ) + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + "automation": { + "trigger": { + "platform": "litejet", + "number": ENTITY_OTHER_SWITCH_NUMBER, + "held_more_than": {"milliseconds": "1000"}, + }, + "action": {"service": "test.automation"}, + } + }, + ): + await hass.services.async_call( + "automation", + "reload", + blocking=True, + ) + await hass.async_block_till_done() + + await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER) assert len(calls) == 0 - await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER) + await simulate_time(hass, mock_litejet, timedelta(seconds=0.5)) assert len(calls) == 0 + await simulate_time(hass, mock_litejet, timedelta(seconds=1.25)) + assert len(calls) == 1 From eb8d723689ec714c9945cf00ae8f19e3d83d9cd7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Feb 2021 14:50:06 -0600 Subject: [PATCH 0713/1818] Bump pymyq to fix myq in core (#46962) --- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index 9dc8719ed4e87a..2098480af523be 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==3.0.1"], + "requirements": ["pymyq==3.0.4"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 5f971b4ece78ea..638989b46c94c4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1552,7 +1552,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==3.0.1 +pymyq==3.0.4 # homeassistant.components.mysensors pymysensors==0.20.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c78f2fbb96a925..254f8cdf86a592 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -824,7 +824,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==3.0.1 +pymyq==3.0.4 # homeassistant.components.mysensors pymysensors==0.20.1 From 272e975a52deee7635beb33a5a728dd3d207ab2f Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 23 Feb 2021 15:38:24 -0600 Subject: [PATCH 0714/1818] Fix Plex handling of clips (#46667) --- homeassistant/components/plex/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plex/models.py b/homeassistant/components/plex/models.py index 7633c5deaa86b8..731d5bbc7dbca2 100644 --- a/homeassistant/components/plex/models.py +++ b/homeassistant/components/plex/models.py @@ -70,7 +70,7 @@ def update_media(self, media): self.media_library_title = "Live TV" else: self.media_library_title = ( - media.section().title if media.section() is not None else "" + media.section().title if media.librarySectionID is not None else "" ) if media.type == "episode": From 089effbe3fae331932d68faf5a138ca4200a61a7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 23 Feb 2021 22:41:19 +0100 Subject: [PATCH 0715/1818] Improve zwave_js config flow (#46906) --- .../components/zwave_js/config_flow.py | 205 +++++++++------- .../components/zwave_js/strings.json | 7 +- .../components/zwave_js/translations/en.json | 24 +- tests/components/zwave_js/conftest.py | 25 +- tests/components/zwave_js/test_config_flow.py | 224 +++++++++++++++--- 5 files changed, 329 insertions(+), 156 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index ec74acf988641c..fe79796edf17bf 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -8,8 +8,18 @@ import voluptuous as vol from zwave_js_server.version import VersionInfo, get_server_version -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries, exceptions +from homeassistant.components.hassio import ( + async_get_addon_discovery_info, + async_get_addon_info, + async_install_addon, + async_set_addon_options, + async_start_addon, + is_hassio, +) +from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.const import CONF_URL +from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -29,13 +39,14 @@ DEFAULT_URL = "ws://localhost:3000" TITLE = "Z-Wave JS" -ADDON_SETUP_TIME = 10 +ADDON_SETUP_TIMEOUT = 5 +ADDON_SETUP_TIMEOUT_ROUNDS = 4 ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool}) STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_URL, default=DEFAULT_URL): str}) -async def validate_input(hass: core.HomeAssistant, user_input: dict) -> VersionInfo: +async def validate_input(hass: HomeAssistant, user_input: dict) -> VersionInfo: """Validate if the user input allows us to connect.""" ws_address = user_input[CONF_URL] @@ -48,9 +59,7 @@ async def validate_input(hass: core.HomeAssistant, user_input: dict) -> VersionI raise InvalidInput("cannot_connect") from err -async def async_get_version_info( - hass: core.HomeAssistant, ws_address: str -) -> VersionInfo: +async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo: """Return Z-Wave JS version info.""" async with timeout(10): try: @@ -58,7 +67,9 @@ async def async_get_version_info( ws_address, async_get_clientsession(hass) ) except (asyncio.TimeoutError, aiohttp.ClientError) as err: - _LOGGER.error("Failed to connect to Z-Wave JS server: %s", err) + # We don't want to spam the log if the add-on isn't started + # or takes a long time to start. + _LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err) raise CannotConnect from err return version_info @@ -72,7 +83,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Set up flow instance.""" - self.addon_config: Optional[dict] = None self.network_key: Optional[str] = None self.usb_path: Optional[str] = None self.use_addon = False @@ -80,12 +90,14 @@ def __init__(self) -> None: # If we install the add-on we should uninstall it on entry remove. self.integration_created_addon = False self.install_task: Optional[asyncio.Task] = None + self.start_task: Optional[asyncio.Task] = None async def async_step_user( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle the initial step.""" - if self.hass.components.hassio.is_hassio(): + assert self.hass # typing + if is_hassio(self.hass): # type: ignore # no-untyped-call return await self.async_step_on_supervisor() return await self.async_step_manual() @@ -120,7 +132,7 @@ async def async_step_manual( step_id="manual", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_hassio( # type: ignore + async def async_step_hassio( # type: ignore # override self, discovery_info: Dict[str, Any] ) -> Dict[str, Any]: """Receive configuration from add-on discovery info. @@ -149,6 +161,7 @@ async def async_step_hassio_confirm( return self.async_show_form(step_id="hassio_confirm") + @callback def _async_create_entry_from_vars(self) -> Dict[str, Any]: """Return a config entry for the flow.""" return self.async_create_entry( @@ -176,28 +189,13 @@ async def async_step_on_supervisor( self.use_addon = True if await self._async_is_addon_running(): - discovery_info = await self._async_get_addon_discovery_info() - self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" - - if not self.unique_id: - try: - version_info = await async_get_version_info( - self.hass, self.ws_address - ) - except CannotConnect: - return self.async_abort(reason="cannot_connect") - await self.async_set_unique_id( - version_info.home_id, raise_on_progress=False - ) - - self._abort_if_unique_id_configured() addon_config = await self._async_get_addon_config() self.usb_path = addon_config[CONF_ADDON_DEVICE] self.network_key = addon_config.get(CONF_ADDON_NETWORK_KEY, "") - return self._async_create_entry_from_vars() + return await self.async_step_finish_addon_setup() if await self._async_is_addon_installed(): - return await self.async_step_start_addon() + return await self.async_step_configure_addon() return await self.async_step_install_addon() @@ -213,13 +211,13 @@ async def async_step_install_addon( try: await self.install_task - except self.hass.components.hassio.HassioAPIError as err: + except HassioAPIError as err: _LOGGER.error("Failed to install Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="install_failed") self.integration_created_addon = True - return self.async_show_progress_done(next_step_id="start_addon") + return self.async_show_progress_done(next_step_id="configure_addon") async def async_step_install_failed( self, user_input: Optional[Dict[str, Any]] = None @@ -227,14 +225,13 @@ async def async_step_install_failed( """Add-on installation failed.""" return self.async_abort(reason="addon_install_failed") - async def async_step_start_addon( + async def async_step_configure_addon( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: - """Ask for config and start Z-Wave JS add-on.""" - if self.addon_config is None: - self.addon_config = await self._async_get_addon_config() + """Ask for config for Z-Wave JS add-on.""" + addon_config = await self._async_get_addon_config() - errors = {} + errors: Dict[str, str] = {} if user_input is not None: self.network_key = user_input[CONF_NETWORK_KEY] @@ -245,40 +242,13 @@ async def async_step_start_addon( CONF_ADDON_NETWORK_KEY: self.network_key, } - if new_addon_config != self.addon_config: + if new_addon_config != addon_config: await self._async_set_addon_config(new_addon_config) - try: - await self.hass.components.hassio.async_start_addon(ADDON_SLUG) - except self.hass.components.hassio.HassioAPIError as err: - _LOGGER.error("Failed to start Z-Wave JS add-on: %s", err) - errors["base"] = "addon_start_failed" - else: - # Sleep some seconds to let the add-on start properly before connecting. - await asyncio.sleep(ADDON_SETUP_TIME) - discovery_info = await self._async_get_addon_discovery_info() - self.ws_address = ( - f"ws://{discovery_info['host']}:{discovery_info['port']}" - ) - - if not self.unique_id: - try: - version_info = await async_get_version_info( - self.hass, self.ws_address - ) - except CannotConnect: - return self.async_abort(reason="cannot_connect") - await self.async_set_unique_id( - version_info.home_id, raise_on_progress=False - ) - - self._abort_if_unique_id_configured() - return self._async_create_entry_from_vars() + return await self.async_step_start_addon() - usb_path = self.addon_config.get(CONF_ADDON_DEVICE, self.usb_path or "") - network_key = self.addon_config.get( - CONF_ADDON_NETWORK_KEY, self.network_key or "" - ) + usb_path = addon_config.get(CONF_ADDON_DEVICE, self.usb_path or "") + network_key = addon_config.get(CONF_ADDON_NETWORK_KEY, self.network_key or "") data_schema = vol.Schema( { @@ -288,16 +258,95 @@ async def async_step_start_addon( ) return self.async_show_form( - step_id="start_addon", data_schema=data_schema, errors=errors + step_id="configure_addon", data_schema=data_schema, errors=errors ) + async def async_step_start_addon( + self, user_input: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Start Z-Wave JS add-on.""" + assert self.hass + if not self.start_task: + self.start_task = self.hass.async_create_task(self._async_start_addon()) + return self.async_show_progress( + step_id="start_addon", progress_action="start_addon" + ) + + try: + await self.start_task + except (CannotConnect, HassioAPIError) as err: + _LOGGER.error("Failed to start Z-Wave JS add-on: %s", err) + return self.async_show_progress_done(next_step_id="start_failed") + + return self.async_show_progress_done(next_step_id="finish_addon_setup") + + async def async_step_start_failed( + self, user_input: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Add-on start failed.""" + return self.async_abort(reason="addon_start_failed") + + async def _async_start_addon(self) -> None: + """Start the Z-Wave JS add-on.""" + assert self.hass + try: + await async_start_addon(self.hass, ADDON_SLUG) + # Sleep some seconds to let the add-on start properly before connecting. + for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): + await asyncio.sleep(ADDON_SETUP_TIMEOUT) + try: + if not self.ws_address: + discovery_info = await self._async_get_addon_discovery_info() + self.ws_address = ( + f"ws://{discovery_info['host']}:{discovery_info['port']}" + ) + await async_get_version_info(self.hass, self.ws_address) + except (AbortFlow, CannotConnect) as err: + _LOGGER.debug( + "Add-on not ready yet, waiting %s seconds: %s", + ADDON_SETUP_TIMEOUT, + err, + ) + else: + break + else: + raise CannotConnect("Failed to start add-on: timeout") + finally: + # Continue the flow after show progress when the task is done. + self.hass.async_create_task( + self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) + ) + + async def async_step_finish_addon_setup( + self, user_input: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Prepare info needed to complete the config entry. + + Get add-on discovery info and server version info. + Set unique id and abort if already configured. + """ + assert self.hass + if not self.ws_address: + discovery_info = await self._async_get_addon_discovery_info() + self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" + + if not self.unique_id: + try: + version_info = await async_get_version_info(self.hass, self.ws_address) + except CannotConnect as err: + raise AbortFlow("cannot_connect") from err + await self.async_set_unique_id( + version_info.home_id, raise_on_progress=False + ) + + self._abort_if_unique_id_configured() + return self._async_create_entry_from_vars() + async def _async_get_addon_info(self) -> dict: """Return and cache Z-Wave JS add-on info.""" try: - addon_info: dict = await self.hass.components.hassio.async_get_addon_info( - ADDON_SLUG - ) - except self.hass.components.hassio.HassioAPIError as err: + addon_info: dict = await async_get_addon_info(self.hass, ADDON_SLUG) + except HassioAPIError as err: _LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err) raise AbortFlow("addon_info_failed") from err @@ -322,17 +371,15 @@ async def _async_set_addon_config(self, config: dict) -> None: """Set Z-Wave JS add-on config.""" options = {"options": config} try: - await self.hass.components.hassio.async_set_addon_options( - ADDON_SLUG, options - ) - except self.hass.components.hassio.HassioAPIError as err: + await async_set_addon_options(self.hass, ADDON_SLUG, options) + except HassioAPIError as err: _LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err) raise AbortFlow("addon_set_config_failed") from err async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" try: - await self.hass.components.hassio.async_install_addon(ADDON_SLUG) + await async_install_addon(self.hass, ADDON_SLUG) finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( @@ -342,12 +389,8 @@ async def _async_install_addon(self) -> None: async def _async_get_addon_discovery_info(self) -> dict: """Return add-on discovery info.""" try: - discovery_info: dict = ( - await self.hass.components.hassio.async_get_addon_discovery_info( - ADDON_SLUG - ) - ) - except self.hass.components.hassio.HassioAPIError as err: + discovery_info = await async_get_addon_discovery_info(self.hass, ADDON_SLUG) + except HassioAPIError as err: _LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err) raise AbortFlow("addon_get_discovery_info_failed") from err diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 212bef708891ba..5d3aa730a7c29d 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -15,13 +15,14 @@ "install_addon": { "title": "The Z-Wave JS add-on installation has started" }, - "start_addon": { + "configure_addon": { "title": "Enter the Z-Wave JS add-on configuration", "data": { "usb_path": "[%key:common::config_flow::data::usb_path%]", "network_key": "Network Key" } }, + "start_addon": { "title": "The Z-Wave JS add-on is starting." }, "hassio_confirm": { "title": "Set up Z-Wave JS integration with the Z-Wave JS add-on" } @@ -38,12 +39,14 @@ "addon_info_failed": "Failed to get Z-Wave JS add-on info.", "addon_install_failed": "Failed to install the Z-Wave JS add-on.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", + "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "progress": { - "install_addon": "Please wait while the Z-Wave JS add-on installation finishes. This can take several minutes." + "install_addon": "Please wait while the Z-Wave JS add-on installation finishes. This can take several minutes.", + "start_addon": "Please wait while the Z-Wave JS add-on start completes. This may take some seconds." } } } diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 977651a576bc9b..d8bdafcefee1b5 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -6,6 +6,7 @@ "addon_install_failed": "Failed to install the Z-Wave JS add-on.", "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", + "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", "cannot_connect": "Failed to connect" @@ -17,9 +18,17 @@ "unknown": "Unexpected error" }, "progress": { - "install_addon": "Please wait while the Z-Wave JS add-on installation finishes. This can take several minutes." + "install_addon": "Please wait while the Z-Wave JS add-on installation finishes. This can take several minutes.", + "start_addon": "Please wait while the Z-Wave JS add-on start completes. This may take some seconds." }, "step": { + "configure_addon": { + "data": { + "network_key": "Network Key", + "usb_path": "USB Device Path" + }, + "title": "Enter the Z-Wave JS add-on configuration" + }, "hassio_confirm": { "title": "Set up Z-Wave JS integration with the Z-Wave JS add-on" }, @@ -39,18 +48,9 @@ "title": "Select connection method" }, "start_addon": { - "data": { - "network_key": "Network Key", - "usb_path": "USB Device Path" - }, - "title": "Enter the Z-Wave JS add-on configuration" - }, - "user": { - "data": { - "url": "URL" - } + "title": "The Z-Wave JS add-on is starting." } } }, "title": "Z-Wave JS" -} \ No newline at end of file +} diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 2e856bde362037..b4b89fb14a2d69 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,7 +1,7 @@ """Provide common Z-Wave JS fixtures.""" import asyncio import json -from unittest.mock import DEFAULT, AsyncMock, patch +from unittest.mock import AsyncMock, patch import pytest from zwave_js_server.event import Event @@ -22,29 +22,6 @@ async def device_registry_fixture(hass): return await async_get_device_registry(hass) -@pytest.fixture(name="discovery_info") -def discovery_info_fixture(): - """Return the discovery info from the supervisor.""" - return DEFAULT - - -@pytest.fixture(name="discovery_info_side_effect") -def discovery_info_side_effect_fixture(): - """Return the discovery info from the supervisor.""" - return None - - -@pytest.fixture(name="get_addon_discovery_info") -def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect): - """Mock get add-on discovery info.""" - with patch( - "homeassistant.components.hassio.async_get_addon_discovery_info", - side_effect=discovery_info_side_effect, - return_value=discovery_info, - ) as get_addon_discovery_info: - yield get_addon_discovery_info - - @pytest.fixture(name="controller_state", scope="session") def controller_state_fixture(): """Load the controller state fixture data.""" diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 0270383174ee7c..3c956d42a27ab9 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -1,6 +1,6 @@ """Test the Z-Wave JS config flow.""" import asyncio -from unittest.mock import patch +from unittest.mock import DEFAULT, patch import pytest from zwave_js_server.version import VersionInfo @@ -22,10 +22,35 @@ @pytest.fixture(name="supervisor") def mock_supervisor_fixture(): """Mock Supervisor.""" - with patch("homeassistant.components.hassio.is_hassio", return_value=True): + with patch( + "homeassistant.components.zwave_js.config_flow.is_hassio", return_value=True + ): yield +@pytest.fixture(name="discovery_info") +def discovery_info_fixture(): + """Return the discovery info from the supervisor.""" + return DEFAULT + + +@pytest.fixture(name="discovery_info_side_effect") +def discovery_info_side_effect_fixture(): + """Return the discovery info from the supervisor.""" + return None + + +@pytest.fixture(name="get_addon_discovery_info") +def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect): + """Mock get add-on discovery info.""" + with patch( + "homeassistant.components.zwave_js.config_flow.async_get_addon_discovery_info", + side_effect=discovery_info_side_effect, + return_value=discovery_info, + ) as get_addon_discovery_info: + yield get_addon_discovery_info + + @pytest.fixture(name="addon_info_side_effect") def addon_info_side_effect_fixture(): """Return the add-on info side effect.""" @@ -36,7 +61,7 @@ def addon_info_side_effect_fixture(): def mock_addon_info(addon_info_side_effect): """Mock Supervisor add-on info.""" with patch( - "homeassistant.components.hassio.async_get_addon_info", + "homeassistant.components.zwave_js.config_flow.async_get_addon_info", side_effect=addon_info_side_effect, ) as addon_info: addon_info.return_value = {} @@ -75,7 +100,7 @@ def set_addon_options_side_effect_fixture(): def mock_set_addon_options(set_addon_options_side_effect): """Mock set add-on options.""" with patch( - "homeassistant.components.hassio.async_set_addon_options", + "homeassistant.components.zwave_js.config_flow.async_set_addon_options", side_effect=set_addon_options_side_effect, ) as set_options: yield set_options @@ -84,7 +109,9 @@ def mock_set_addon_options(set_addon_options_side_effect): @pytest.fixture(name="install_addon") def mock_install_addon(): """Mock install add-on.""" - with patch("homeassistant.components.hassio.async_install_addon") as install_addon: + with patch( + "homeassistant.components.zwave_js.config_flow.async_install_addon" + ) as install_addon: yield install_addon @@ -98,7 +125,7 @@ def start_addon_side_effect_fixture(): def mock_start_addon(start_addon_side_effect): """Mock start add-on.""" with patch( - "homeassistant.components.hassio.async_start_addon", + "homeassistant.components.zwave_js.config_flow.async_start_addon", side_effect=start_addon_side_effect, ) as start_addon: yield start_addon @@ -130,7 +157,7 @@ def mock_get_server_version(server_version_side_effect): def mock_addon_setup_time(): """Mock add-on setup sleep time.""" with patch( - "homeassistant.components.zwave_js.config_flow.ADDON_SETUP_TIME", new=0 + "homeassistant.components.zwave_js.config_flow.ADDON_SETUP_TIMEOUT", new=0 ) as addon_setup_time: yield addon_setup_time @@ -399,12 +426,47 @@ async def test_discovery_addon_not_running( result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["step_id"] == "start_addon" assert result["type"] == "form" + assert result["step_id"] == "configure_addon" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + ) + + assert result["type"] == "progress" + assert result["step_id"] == "start_addon" + + with patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == TITLE + assert result["data"] == { + "url": "ws://host1:3001", + "usb_path": "/test", + "network_key": "abc123", + "use_addon": True, + "integration_created_addon": False, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 async def test_discovery_addon_not_installed( - hass, supervisor, addon_installed, install_addon, addon_options + hass, + supervisor, + addon_installed, + install_addon, + addon_options, + set_addon_options, + start_addon, ): """Test discovery with add-on not installed.""" addon_installed.return_value["version"] = None @@ -429,8 +491,37 @@ async def test_discovery_addon_not_installed( result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "form" + assert result["step_id"] == "configure_addon" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + ) + + assert result["type"] == "progress" assert result["step_id"] == "start_addon" + with patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == TITLE + assert result["data"] == { + "url": "ws://host1:3001", + "usb_path": "/test", + "network_key": "abc123", + "use_addon": True, + "integration_created_addon": True, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + async def test_not_addon(hass, supervisor): """Test opting out of add-on on Supervisor.""" @@ -559,10 +650,13 @@ async def test_addon_running_failures( hass, supervisor, addon_running, + addon_options, get_addon_discovery_info, abort_reason, ): """Test all failures when add-on is running.""" + addon_options["device"] = "/test" + addon_options["network_key"] = "abc123" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -582,9 +676,11 @@ async def test_addon_running_failures( @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running_already_configured( - hass, supervisor, addon_running, get_addon_discovery_info + hass, supervisor, addon_running, addon_options, get_addon_discovery_info ): """Test that only one unique instance is allowed when add-on is running.""" + addon_options["device"] = "/test" + addon_options["network_key"] = "abc123" entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) entry.add_to_hass(hass) @@ -629,6 +725,13 @@ async def test_addon_installed( ) assert result["type"] == "form" + assert result["step_id"] == "configure_addon" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + ) + + assert result["type"] == "progress" assert result["step_id"] == "start_addon" with patch( @@ -637,9 +740,8 @@ async def test_addon_installed( "homeassistant.components.zwave_js.async_setup_entry", return_value=True, ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} - ) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() assert result["type"] == "create_entry" @@ -683,40 +785,32 @@ async def test_addon_installed_start_failure( ) assert result["type"] == "form" - assert result["step_id"] == "start_addon" + assert result["step_id"] == "configure_addon" result = await hass.config_entries.flow.async_configure( result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} ) - assert result["type"] == "form" - assert result["errors"] == {"base": "addon_start_failed"} + assert result["type"] == "progress" + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] == "abort" + assert result["reason"] == "addon_start_failed" @pytest.mark.parametrize( - "set_addon_options_side_effect, start_addon_side_effect, discovery_info, " - "server_version_side_effect, abort_reason", + "discovery_info, server_version_side_effect", [ ( - HassioAPIError(), - None, - {"config": ADDON_DISCOVERY_INFO}, - None, - "addon_set_config_failed", - ), - ( - None, - None, {"config": ADDON_DISCOVERY_INFO}, asyncio.TimeoutError, - "cannot_connect", ), ( None, None, - None, - None, - "addon_missing_discovery_info", ), ], ) @@ -728,7 +822,6 @@ async def test_addon_installed_failures( set_addon_options, start_addon, get_addon_discovery_info, - abort_reason, ): """Test all failures when add-on is installed.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -745,14 +838,58 @@ async def test_addon_installed_failures( ) assert result["type"] == "form" + assert result["step_id"] == "configure_addon" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + ) + + assert result["type"] == "progress" assert result["step_id"] == "start_addon" + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] == "abort" + assert result["reason"] == "addon_start_failed" + + +@pytest.mark.parametrize( + "set_addon_options_side_effect, discovery_info", + [(HassioAPIError(), {"config": ADDON_DISCOVERY_INFO})], +) +async def test_addon_installed_set_options_failure( + hass, + supervisor, + addon_installed, + addon_options, + set_addon_options, + start_addon, + get_addon_discovery_info, +): + """Test all failures when add-on is installed.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert result["type"] == "form" + assert result["step_id"] == "configure_addon" + result = await hass.config_entries.flow.async_configure( result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} ) assert result["type"] == "abort" - assert result["reason"] == abort_reason + assert result["reason"] == "addon_set_config_failed" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) @@ -782,12 +919,18 @@ async def test_addon_installed_already_configured( ) assert result["type"] == "form" - assert result["step_id"] == "start_addon" + assert result["step_id"] == "configure_addon" result = await hass.config_entries.flow.async_configure( result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} ) + assert result["type"] == "progress" + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == "abort" assert result["reason"] == "already_configured" @@ -819,6 +962,7 @@ async def test_addon_not_installed( ) assert result["type"] == "progress" + assert result["step_id"] == "install_addon" # Make sure the flow continues when the progress task is done. await hass.async_block_till_done() @@ -826,6 +970,13 @@ async def test_addon_not_installed( result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == "form" + assert result["step_id"] == "configure_addon" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + ) + + assert result["type"] == "progress" assert result["step_id"] == "start_addon" with patch( @@ -834,9 +985,8 @@ async def test_addon_not_installed( "homeassistant.components.zwave_js.async_setup_entry", return_value=True, ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} - ) + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() assert result["type"] == "create_entry" From 00dd557cce82014848cb02ce8c74e1b9a0713e40 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 23 Feb 2021 22:42:33 +0100 Subject: [PATCH 0716/1818] Fix Shelly mireds and color_temp return type (#46112) --- homeassistant/components/shelly/light.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 5422f3fff05da0..848ef9903404b0 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -118,7 +118,7 @@ def mode(self) -> Optional[str]: return "white" @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int: """Brightness of light.""" if self.mode == "color": if self.control_result: @@ -133,7 +133,7 @@ def brightness(self) -> Optional[int]: return int(brightness / 100 * 255) @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int: """White value of light.""" if self.control_result: white = self.control_result["white"] @@ -142,7 +142,7 @@ def white_value(self) -> Optional[int]: return int(white) @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> Tuple[float, float]: """Return the hue and saturation color value of light.""" if self.mode == "white": return color_RGB_to_hs(255, 255, 255) @@ -158,7 +158,7 @@ def hs_color(self) -> Optional[Tuple[float, float]]: return color_RGB_to_hs(red, green, blue) @property - def color_temp(self) -> Optional[float]: + def color_temp(self) -> Optional[int]: """Return the CT color value in mireds.""" if self.mode == "color": return None @@ -176,14 +176,14 @@ def color_temp(self) -> Optional[float]: return int(color_temperature_kelvin_to_mired(color_temp)) @property - def min_mireds(self) -> Optional[float]: + def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" - return color_temperature_kelvin_to_mired(KELVIN_MAX_VALUE) + return int(color_temperature_kelvin_to_mired(KELVIN_MAX_VALUE)) @property - def max_mireds(self) -> Optional[float]: + def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" - return color_temperature_kelvin_to_mired(min_kelvin(self.wrapper.model)) + return int(color_temperature_kelvin_to_mired(min_kelvin(self.wrapper.model))) async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" From d68a51ddcec7887474eab3869a968f7ada90ac11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Feb 2021 15:42:56 -0600 Subject: [PATCH 0717/1818] Avoid having to ask for the bond token when possible during config (#46845) --- homeassistant/components/bond/config_flow.py | 92 ++++++++++++++----- homeassistant/components/bond/manifest.json | 2 +- homeassistant/components/bond/strings.json | 4 +- .../components/bond/translations/en.json | 4 +- homeassistant/components/bond/utils.py | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bond/common.py | 25 ++++- tests/components/bond/test_config_flow.py | 65 +++++++++++++ 9 files changed, 166 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 0132df486d3ab8..f81e3a0be5c1ca 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -14,16 +14,17 @@ HTTP_UNAUTHORIZED, ) -from .const import CONF_BOND_ID from .const import DOMAIN # pylint:disable=unused-import from .utils import BondHub _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA_USER = vol.Schema( + +USER_SCHEMA = vol.Schema( {vol.Required(CONF_HOST): str, vol.Required(CONF_ACCESS_TOKEN): str} ) -DATA_SCHEMA_DISCOVERY = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) +DISCOVERY_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) +TOKEN_SCHEMA = vol.Schema({}) async def _validate_input(data: Dict[str, Any]) -> Tuple[str, Optional[str]]: @@ -56,7 +57,30 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH - _discovered: dict = None + def __init__(self): + """Initialize config flow.""" + self._discovered: dict = None + + async def _async_try_automatic_configure(self): + """Try to auto configure the device. + + Failure is acceptable here since the device may have been + online longer then the allowed setup period, and we will + instead ask them to manually enter the token. + """ + bond = Bond(self._discovered[CONF_HOST], "") + try: + response = await bond.token() + except ClientConnectionError: + return + + token = response.get("token") + if token is None: + return + + self._discovered[CONF_ACCESS_TOKEN] = token + _, hub_name = await _validate_input(self._discovered) + self._discovered[CONF_NAME] = hub_name async def async_step_zeroconf( self, discovery_info: Optional[Dict[str, Any]] = None @@ -68,11 +92,17 @@ async def async_step_zeroconf( await self.async_set_unique_id(bond_id) self._abort_if_unique_id_configured({CONF_HOST: host}) - self._discovered = { - CONF_HOST: host, - CONF_BOND_ID: bond_id, - } - self.context.update({"title_placeholders": self._discovered}) + self._discovered = {CONF_HOST: host, CONF_NAME: bond_id} + await self._async_try_automatic_configure() + + self.context.update( + { + "title_placeholders": { + CONF_HOST: self._discovered[CONF_HOST], + CONF_NAME: self._discovered[CONF_NAME], + } + } + ) return await self.async_step_confirm() @@ -82,16 +112,37 @@ async def async_step_confirm( """Handle confirmation flow for discovered bond hub.""" errors = {} if user_input is not None: - data = user_input.copy() - data[CONF_HOST] = self._discovered[CONF_HOST] + if CONF_ACCESS_TOKEN in self._discovered: + return self.async_create_entry( + title=self._discovered[CONF_NAME], + data={ + CONF_ACCESS_TOKEN: self._discovered[CONF_ACCESS_TOKEN], + CONF_HOST: self._discovered[CONF_HOST], + }, + ) + + data = { + CONF_ACCESS_TOKEN: user_input[CONF_ACCESS_TOKEN], + CONF_HOST: self._discovered[CONF_HOST], + } try: - return await self._try_create_entry(data) + _, hub_name = await _validate_input(data) except InputValidationError as error: errors["base"] = error.base + else: + return self.async_create_entry( + title=hub_name, + data=data, + ) + + if CONF_ACCESS_TOKEN in self._discovered: + data_schema = TOKEN_SCHEMA + else: + data_schema = DISCOVERY_SCHEMA return self.async_show_form( step_id="confirm", - data_schema=DATA_SCHEMA_DISCOVERY, + data_schema=data_schema, errors=errors, description_placeholders=self._discovered, ) @@ -103,21 +154,18 @@ async def async_step_user( errors = {} if user_input is not None: try: - return await self._try_create_entry(user_input) + bond_id, hub_name = await _validate_input(user_input) except InputValidationError as error: errors["base"] = error.base + else: + await self.async_set_unique_id(bond_id) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=hub_name, data=user_input) return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors + step_id="user", data_schema=USER_SCHEMA, errors=errors ) - async def _try_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]: - bond_id, name = await _validate_input(data) - await self.async_set_unique_id(bond_id) - self._abort_if_unique_id_configured() - hub_name = name or bond_id - return self.async_create_entry(title=hub_name, data=data) - class InputValidationError(exceptions.HomeAssistantError): """Error to indicate we cannot proceed due to invalid input.""" diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index cf009c11caa607..65cb6a83bb2133 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -3,7 +3,7 @@ "name": "Bond", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/bond", - "requirements": ["bond-api==0.1.10"], + "requirements": ["bond-api==0.1.11"], "zeroconf": ["_bond._tcp.local."], "codeowners": ["@prystupa"], "quality_scale": "platinum" diff --git a/homeassistant/components/bond/strings.json b/homeassistant/components/bond/strings.json index 5ca2278a3e59f8..f8eff6ddd9e335 100644 --- a/homeassistant/components/bond/strings.json +++ b/homeassistant/components/bond/strings.json @@ -1,9 +1,9 @@ { "config": { - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { - "description": "Do you want to set up {bond_id}?", + "description": "Do you want to set up {name}?", "data": { "access_token": "[%key:common::config_flow::data::access_token%]" } diff --git a/homeassistant/components/bond/translations/en.json b/homeassistant/components/bond/translations/en.json index 945b09b8186cb3..d9ce8ab0fe4b92 100644 --- a/homeassistant/components/bond/translations/en.json +++ b/homeassistant/components/bond/translations/en.json @@ -9,13 +9,13 @@ "old_firmware": "Unsupported old firmware on the Bond device - please upgrade before continuing", "unknown": "Unexpected error" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Access Token" }, - "description": "Do you want to set up {bond_id}?" + "description": "Do you want to set up {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 55ef81778f02a8..225eec87d98a99 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -150,11 +150,11 @@ def make(self) -> str: return self._version.get("make", BRIDGE_MAKE) @property - def name(self) -> Optional[str]: + def name(self) -> str: """Get the name of this bridge.""" if not self.is_bridge and self._devices: return self._devices[0].name - return self._bridge.get("name") + return self._bridge["name"] @property def location(self) -> Optional[str]: diff --git a/requirements_all.txt b/requirements_all.txt index 638989b46c94c4..7079c94a00ba80 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -371,7 +371,7 @@ blockchain==1.4.4 # bme680==1.0.5 # homeassistant.components.bond -bond-api==0.1.10 +bond-api==0.1.11 # homeassistant.components.amazon_polly # homeassistant.components.route53 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 254f8cdf86a592..a434739be14203 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -205,7 +205,7 @@ blebox_uniapi==1.3.2 blinkpy==0.17.0 # homeassistant.components.bond -bond-api==0.1.10 +bond-api==0.1.11 # homeassistant.components.braviatv bravia-tv==1.0.8 diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 54d127832b546e..061dc23797e603 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -30,12 +30,13 @@ async def setup_bond_entity( patch_device_ids=False, patch_platforms=False, patch_bridge=False, + patch_token=False, ): """Set up Bond entity.""" config_entry.add_to_hass(hass) - with patch_start_bpup(), patch_bond_bridge( - enabled=patch_bridge + with patch_start_bpup(), patch_bond_bridge(enabled=patch_bridge), patch_bond_token( + enabled=patch_token ), patch_bond_version(enabled=patch_version), patch_bond_device_ids( enabled=patch_device_ids ), patch_setup_entry( @@ -60,6 +61,7 @@ async def setup_platform( props: Dict[str, Any] = None, state: Dict[str, Any] = None, bridge: Dict[str, Any] = None, + token: Dict[str, Any] = None, ): """Set up the specified Bond platform.""" mock_entry = MockConfigEntry( @@ -71,7 +73,7 @@ async def setup_platform( with patch("homeassistant.components.bond.PLATFORMS", [platform]): with patch_bond_version(return_value=bond_version), patch_bond_bridge( return_value=bridge - ), patch_bond_device_ids( + ), patch_bond_token(return_value=token), patch_bond_device_ids( return_value=[bond_device_id] ), patch_start_bpup(), patch_bond_device( return_value=discovered_device @@ -124,6 +126,23 @@ def patch_bond_bridge( ) +def patch_bond_token( + enabled: bool = True, return_value: Optional[dict] = None, side_effect=None +): + """Patch Bond API token endpoint.""" + if not enabled: + return nullcontext() + + if return_value is None: + return_value = {"locked": 1} + + return patch( + "homeassistant.components.bond.Bond.token", + return_value=return_value, + side_effect=side_effect, + ) + + def patch_bond_device_ids(enabled: bool = True, return_value=None, side_effect=None): """Patch Bond API devices endpoint.""" if not enabled: diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 2a76e1fa6a03ba..39fd1a2db5dcb0 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -13,6 +13,7 @@ patch_bond_device, patch_bond_device_ids, patch_bond_device_properties, + patch_bond_token, patch_bond_version, ) @@ -221,6 +222,70 @@ async def test_zeroconf_form(hass: core.HomeAssistant): assert len(mock_setup_entry.mock_calls) == 1 +async def test_zeroconf_form_token_unavailable(hass: core.HomeAssistant): + """Test we get the discovery form and we handle the token being unavailable.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch_bond_version(), patch_bond_token(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={"name": "test-bond-id.some-other-tail-info", "host": "test-host"}, + ) + await hass.async_block_till_done() + assert result["type"] == "form" + assert result["errors"] == {} + + with patch_bond_version(), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_ACCESS_TOKEN: "test-token"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "bond-name" + assert result2["data"] == { + CONF_HOST: "test-host", + CONF_ACCESS_TOKEN: "test-token", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_zeroconf_form_with_token_available(hass: core.HomeAssistant): + """Test we get the discovery form when we can get the token.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch_bond_version(return_value={"bondid": "test-bond-id"}), patch_bond_token( + return_value={"token": "discovered-token"} + ), patch_bond_bridge( + return_value={"name": "discovered-name"} + ), patch_bond_device_ids(): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={"name": "test-bond-id.some-other-tail-info", "host": "test-host"}, + ) + await hass.async_block_till_done() + assert result["type"] == "form" + assert result["errors"] == {} + + with _patch_async_setup() as mock_setup, _patch_async_setup_entry() 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"] == "discovered-name" + assert result2["data"] == { + CONF_HOST: "test-host", + CONF_ACCESS_TOKEN: "discovered-token", + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_zeroconf_already_configured(hass: core.HomeAssistant): """Test starting a flow from discovery when already configured.""" await setup.async_setup_component(hass, "persistent_notification", {}) From b583ded8b5c33aabc596bc5b7bfd8d1e25a512d6 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 23 Feb 2021 22:59:16 +0100 Subject: [PATCH 0718/1818] Fix KNX services.yaml (#46897) --- homeassistant/components/knx/__init__.py | 43 +++++++++++----------- homeassistant/components/knx/expose.py | 8 ++-- homeassistant/components/knx/schema.py | 3 +- homeassistant/components/knx/services.yaml | 19 +++++++--- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 8716f03838cd91..f092bafe404949 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -17,6 +17,7 @@ from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite from homeassistant.const import ( + CONF_ADDRESS, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, @@ -64,7 +65,6 @@ CONF_KNX_EXPOSE = "expose" SERVICE_KNX_SEND = "send" -SERVICE_KNX_ATTR_ADDRESS = "address" SERVICE_KNX_ATTR_PAYLOAD = "payload" SERVICE_KNX_ATTR_TYPE = "type" SERVICE_KNX_ATTR_REMOVE = "remove" @@ -146,7 +146,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( { - vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): cv.string, vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), } @@ -154,7 +154,7 @@ vol.Schema( # without type given payload is treated as raw bytes { - vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): cv.string, vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any( cv.positive_int, [cv.positive_int] ), @@ -164,7 +164,7 @@ SERVICE_KNX_READ_SCHEMA = vol.Schema( { - vol.Required(SERVICE_KNX_ATTR_ADDRESS): vol.All( + vol.Required(CONF_ADDRESS): vol.All( cv.ensure_list, [cv.string], ) @@ -173,7 +173,7 @@ SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { - vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): cv.string, vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } ) @@ -187,7 +187,7 @@ vol.Schema( # for removing only `address` is required { - vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): cv.string, vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True), }, extra=vol.ALLOW_EXTRA, @@ -198,8 +198,9 @@ async def async_setup(hass, config): """Set up the KNX component.""" try: - hass.data[DOMAIN] = KNXModule(hass, config) - await hass.data[DOMAIN].start() + knx_module = KNXModule(hass, config) + hass.data[DOMAIN] = knx_module + await knx_module.start() except XKNXException as ex: _LOGGER.warning("Could not connect to KNX interface: %s", ex) hass.components.persistent_notification.async_create( @@ -208,14 +209,14 @@ async def async_setup(hass, config): if CONF_KNX_EXPOSE in config[DOMAIN]: for expose_config in config[DOMAIN][CONF_KNX_EXPOSE]: - hass.data[DOMAIN].exposures.append( - create_knx_exposure(hass, hass.data[DOMAIN].xknx, expose_config) + knx_module.exposures.append( + create_knx_exposure(hass, knx_module.xknx, expose_config) ) for platform in SupportedPlatforms: if platform.value in config[DOMAIN]: for device_config in config[DOMAIN][platform.value]: - create_knx_device(platform, hass.data[DOMAIN].xknx, device_config) + create_knx_device(platform, knx_module.xknx, device_config) # We need to wait until all entities are loaded into the device list since they could also be created from other platforms for platform in SupportedPlatforms: @@ -223,7 +224,7 @@ async def async_setup(hass, config): discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) ) - if not hass.data[DOMAIN].xknx.devices: + if not knx_module.xknx.devices: _LOGGER.warning( "No KNX devices are configured. Please read " "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" @@ -232,14 +233,14 @@ async def async_setup(hass, config): hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, - hass.data[DOMAIN].service_send_to_knx_bus, + knx_module.service_send_to_knx_bus, schema=SERVICE_KNX_SEND_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_KNX_READ, - hass.data[DOMAIN].service_read_to_knx_bus, + knx_module.service_read_to_knx_bus, schema=SERVICE_KNX_READ_SCHEMA, ) @@ -247,7 +248,7 @@ async def async_setup(hass, config): hass, DOMAIN, SERVICE_KNX_EVENT_REGISTER, - hass.data[DOMAIN].service_event_register_modify, + knx_module.service_event_register_modify, schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA, ) @@ -255,7 +256,7 @@ async def async_setup(hass, config): hass, DOMAIN, SERVICE_KNX_EXPOSURE_REGISTER, - hass.data[DOMAIN].service_exposure_register_modify, + knx_module.service_exposure_register_modify, schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, ) @@ -269,7 +270,7 @@ async def reload_service_handler(service_call: ServiceCallType) -> None: if not config or DOMAIN not in config: return - await hass.data[DOMAIN].xknx.stop() + await knx_module.xknx.stop() await asyncio.gather( *[platform.async_reset() for platform in async_get_platforms(hass, DOMAIN)] @@ -398,7 +399,7 @@ def register_callback(self) -> TelegramQueue.Callback: async def service_event_register_modify(self, call): """Service for adding or removing a GroupAddress to the knx_event filter.""" - group_address = GroupAddress(call.data.get(SERVICE_KNX_ATTR_ADDRESS)) + group_address = GroupAddress(call.data[CONF_ADDRESS]) if call.data.get(SERVICE_KNX_ATTR_REMOVE): try: self._knx_event_callback.group_addresses.remove(group_address) @@ -416,7 +417,7 @@ async def service_event_register_modify(self, call): async def service_exposure_register_modify(self, call): """Service for adding or removing an exposure to KNX bus.""" - group_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS) + group_address = call.data.get(CONF_ADDRESS) if call.data.get(SERVICE_KNX_ATTR_REMOVE): try: @@ -448,7 +449,7 @@ async def service_exposure_register_modify(self, call): async def service_send_to_knx_bus(self, call): """Service for sending an arbitrary KNX message to the KNX bus.""" attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) - attr_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS) + attr_address = call.data.get(CONF_ADDRESS) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) def calculate_payload(attr_payload): @@ -470,7 +471,7 @@ def calculate_payload(attr_payload): async def service_read_to_knx_bus(self, call): """Service for sending a GroupValueRead telegram to the KNX bus.""" - for address in call.data.get(SERVICE_KNX_ATTR_ADDRESS): + for address in call.data.get(CONF_ADDRESS): telegram = Telegram( destination_address=GroupAddress(address), payload=GroupValueRead(), diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 93abd7d7b431ca..5abc58f82cca8a 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -5,6 +5,7 @@ from xknx.devices import DateTime, ExposeSensor from homeassistant.const import ( + CONF_ADDRESS, CONF_ENTITY_ID, STATE_OFF, STATE_ON, @@ -23,11 +24,11 @@ def create_knx_exposure( hass: HomeAssistant, xknx: XKNX, config: ConfigType ) -> Union["KNXExposeSensor", "KNXExposeTime"]: """Create exposures from config.""" - expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) - entity_id = config.get(CONF_ENTITY_ID) + address = config[CONF_ADDRESS] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) + entity_id = config.get(CONF_ENTITY_ID) + expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) - address = config.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS) exposure: Union["KNXExposeSensor", "KNXExposeTime"] if expose_type.lower() in ["time", "date", "datetime"]: @@ -83,6 +84,7 @@ def shutdown(self) -> None: """Prepare for deletion.""" if self._remove_listener is not None: self._remove_listener() + self._remove_listener = None if self.device is not None: self.device.shutdown() diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 619090137396b5..125115f28b27c2 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -327,7 +327,6 @@ class ExposeSchema: CONF_KNX_EXPOSE_TYPE = CONF_TYPE CONF_KNX_EXPOSE_ATTRIBUTE = "attribute" CONF_KNX_EXPOSE_DEFAULT = "default" - CONF_KNX_EXPOSE_ADDRESS = CONF_ADDRESS SCHEMA = vol.Schema( { @@ -335,7 +334,7 @@ class ExposeSchema: vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, - vol.Required(CONF_KNX_EXPOSE_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): cv.string, } ) diff --git a/homeassistant/components/knx/services.yaml b/homeassistant/components/knx/services.yaml index ef74acc49b14cf..3fae7dfce0e799 100644 --- a/homeassistant/components/knx/services.yaml +++ b/homeassistant/components/knx/services.yaml @@ -1,4 +1,5 @@ send: + name: "Send to KNX bus" description: "Send arbitrary data directly to the KNX bus." fields: address: @@ -13,6 +14,8 @@ send: description: "Payload to send to the bus. Integers are treated as DPT 1/2/3 payloads. For DPTs > 6 bits send a list. Each value represents 1 octet (0-255). Pad with 0 to DPT byte length." required: true example: "[0, 4]" + selector: + object: type: name: "Value type" description: "Optional. If set, the payload will not be sent as raw bytes, but encoded as given DPT. Knx sensor types are valid values (see https://www.home-assistant.io/integrations/sensor.knx)." @@ -21,6 +24,7 @@ send: selector: text: read: + name: "Read from KNX bus" description: "Send GroupValueRead requests to the KNX bus. Response can be used from `knx_event` and will be processed in KNX entities." fields: address: @@ -31,6 +35,7 @@ read: selector: text: event_register: + name: "Register knx_event" description: "Add or remove single group address to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." fields: address: @@ -38,14 +43,16 @@ event_register: description: "Group address that shall be added or removed." required: true example: "1/1/0" + selector: + text: remove: name: "Remove event registration" description: "Optional. If `True` the group address will be removed." - required: false default: false selector: boolean: exposure_register: + name: "Expose to KNX bus" description: "Add or remove exposures to KNX bus. Only exposures added with this service can be removed." fields: address: @@ -72,19 +79,21 @@ exposure_register: attribute: name: "Entity attribute" description: "Optional. Attribute of the entity that shall be sent to the KNX bus. If not set the state will be sent. Eg. for a light the state is eigther “on” or “off” - with attribute you can expose its “brightness”." - required: false example: "brightness" + selector: + text: default: name: "Default value" description: "Optional. Default value to send to the bus if the state or attribute value is None. Eg. a light with state “off” has no brightness attribute so a default value of 0 could be used. If not set (or None) no value would be sent to the bus and a GroupReadRequest to the address would return the last known value." - required: false example: "0" + selector: + object: remove: name: "Remove exposure" description: "Optional. If `True` the exposure will be removed. Only `address` is required for removal." - required: false default: false selector: boolean: reload: - description: "Reload KNX configuration." + name: "Reload KNX configuration" + description: "Reload the KNX configuration from YAML." From d96249e39c8dbfa6568bb05688bd49110abfcb9b Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 23 Feb 2021 23:23:50 +0100 Subject: [PATCH 0719/1818] Implement additional DataUpdateCoordinator to harmonize the data update handling of Synology DSM (#46113) --- .../components/synology_dsm/__init__.py | 237 +++++++++--------- .../components/synology_dsm/binary_sensor.py | 21 +- .../components/synology_dsm/camera.py | 19 +- .../components/synology_dsm/config_flow.py | 3 +- .../components/synology_dsm/const.py | 6 +- .../components/synology_dsm/sensor.py | 42 +++- .../components/synology_dsm/switch.py | 58 ++--- 7 files changed, 210 insertions(+), 176 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index b0d78ca67163f3..6f0476b403c950 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -39,12 +39,6 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, @@ -53,9 +47,12 @@ ) from .const import ( + CONF_DEVICE_TOKEN, CONF_SERIAL, CONF_VOLUMES, - COORDINATOR_SURVEILLANCE, + COORDINATOR_CAMERAS, + COORDINATOR_CENTRAL, + COORDINATOR_SWITCHES, DEFAULT_SCAN_INTERVAL, DEFAULT_USE_SSL, DEFAULT_VERIFY_SSL, @@ -73,6 +70,7 @@ STORAGE_DISK_SENSORS, STORAGE_VOL_SENSORS, SYNO_API, + SYSTEM_LOADED, TEMP_SENSORS_KEYS, UNDO_UPDATE_LISTENER, UTILISATION_SENSORS, @@ -196,12 +194,11 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): _LOGGER.debug("async_setup_entry() - Unable to connect to DSM: %s", err) raise ConfigEntryNotReady from err - undo_listener = entry.add_update_listener(_async_update_listener) - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = { + UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), SYNO_API: api, - UNDO_UPDATE_LISTENER: undo_listener, + SYSTEM_LOADED: True, } # Services @@ -214,32 +211,82 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): entry, data={**entry.data, CONF_MAC: network.macs} ) - # setup DataUpdateCoordinator - async def async_coordinator_update_data_surveillance_station(): - """Fetch all surveillance station data from api.""" + async def async_coordinator_update_data_cameras(): + """Fetch all camera data from api.""" + if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]: + raise UpdateFailed("System not fully loaded") + + if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: + return None + surveillance_station = api.surveillance_station + try: async with async_timeout.timeout(10): await hass.async_add_executor_job(surveillance_station.update) except SynologyDSMAPIErrorException as err: + _LOGGER.debug( + "async_coordinator_update_data_cameras() - exception: %s", err + ) raise UpdateFailed(f"Error communicating with API: {err}") from err - if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: - return - return { "cameras": { camera.id: camera for camera in surveillance_station.get_all_cameras() } } - hass.data[DOMAIN][entry.unique_id][ - COORDINATOR_SURVEILLANCE - ] = DataUpdateCoordinator( + async def async_coordinator_update_data_central(): + """Fetch all device and sensor data from api.""" + try: + await api.async_update() + except Exception as err: + _LOGGER.debug( + "async_coordinator_update_data_central() - exception: %s", err + ) + raise UpdateFailed(f"Error communicating with API: {err}") from err + return None + + async def async_coordinator_update_data_switches(): + """Fetch all switch data from api.""" + if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]: + raise UpdateFailed("System not fully loaded") + if SynoSurveillanceStation.HOME_MODE_API_KEY not in api.dsm.apis: + return None + + surveillance_station = api.surveillance_station + + return { + "switches": { + "home_mode": await hass.async_add_executor_job( + surveillance_station.get_home_mode_status + ) + } + } + + hass.data[DOMAIN][entry.unique_id][COORDINATOR_CAMERAS] = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{entry.unique_id}_cameras", + update_method=async_coordinator_update_data_cameras, + update_interval=timedelta(seconds=30), + ) + + hass.data[DOMAIN][entry.unique_id][COORDINATOR_CENTRAL] = DataUpdateCoordinator( + hass, + _LOGGER, + name=f"{entry.unique_id}_central", + update_method=async_coordinator_update_data_central, + update_interval=timedelta( + minutes=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + ), + ) + + hass.data[DOMAIN][entry.unique_id][COORDINATOR_SWITCHES] = DataUpdateCoordinator( hass, _LOGGER, - name=f"{entry.unique_id}_surveillance_station", - update_method=async_coordinator_update_data_surveillance_station, + name=f"{entry.unique_id}_switches", + update_method=async_coordinator_update_data_switches, update_interval=timedelta(seconds=30), ) @@ -304,10 +351,11 @@ async def service_handler(call: ServiceCall): _LOGGER.debug("%s DSM with serial %s", call.service, serial) dsm_api = dsm_device[SYNO_API] + dsm_device[SYSTEM_LOADED] = False if call.service == SERVICE_REBOOT: await dsm_api.async_reboot() elif call.service == SERVICE_SHUTDOWN: - await dsm_api.system.shutdown() + await dsm_api.async_shutdown() for service in SERVICES: hass.services.async_register(DOMAIN, service, service_handler) @@ -342,16 +390,8 @@ def __init__(self, hass: HomeAssistantType, entry: ConfigEntry): self._with_upgrade = True self._with_utilisation = True - self._unsub_dispatcher = None - - @property - def signal_sensor_update(self) -> str: - """Event specific per Synology DSM entry to signal updates in sensors.""" - return f"{DOMAIN}-{self.information.serial}-sensor-update" - async def async_setup(self): """Start interacting with the NAS.""" - # init SynologyDSM object and login self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], @@ -360,7 +400,7 @@ async def async_setup(self): self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), - device_token=self._entry.data.get("device_token"), + device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) await self._hass.async_add_executor_job(self.dsm.login) @@ -373,24 +413,14 @@ async def async_setup(self): self._with_surveillance_station, ) - self._async_setup_api_requests() + self._setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() - self._unsub_dispatcher = async_track_time_interval( - self._hass, - self.async_update, - timedelta( - minutes=self._entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ) - ), - ) - @callback def subscribe(self, api_key, unique_id): - """Subscribe an entity from API fetches.""" + """Subscribe an entity to API fetches.""" _LOGGER.debug( "SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id ) @@ -401,31 +431,35 @@ def subscribe(self, api_key, unique_id): @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" + _LOGGER.debug( + "SynoAPI.unsubscribe() - api_key:%s, unique_id:%s", api_key, unique_id + ) self._fetching_entities[api_key].remove(unique_id) + if len(self._fetching_entities[api_key]) == 0: + self._fetching_entities.pop(api_key) return unsubscribe @callback - def _async_setup_api_requests(self): + def _setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" - _LOGGER.debug( - "SynoAPI._async_setup_api_requests() - self._fetching_entities:%s", - self._fetching_entities, - ) - # Entities not added yet, fetch all if not self._fetching_entities: _LOGGER.debug( - "SynoAPI._async_setup_api_requests() - Entities not added yet, fetch all" + "SynoAPI._setup_api_requests() - Entities not added yet, fetch all" ) return # Determine if we should fetch an API + self._with_system = bool(self.dsm.apis.get(SynoCoreSystem.API_KEY)) + self._with_surveillance_station = bool( + self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) + ) or bool(self.dsm.apis.get(SynoSurveillanceStation.HOME_MODE_API_KEY)) + self._with_security = bool( self._fetching_entities.get(SynoCoreSecurity.API_KEY) ) self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY)) - self._with_system = bool(self._fetching_entities.get(SynoCoreSystem.API_KEY)) self._with_upgrade = bool(self._fetching_entities.get(SynoCoreUpgrade.API_KEY)) self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY) @@ -433,39 +467,36 @@ def _async_setup_api_requests(self): self._with_information = bool( self._fetching_entities.get(SynoDSMInformation.API_KEY) ) - self._with_surveillance_station = bool( - self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) - ) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable security") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable security") self.dsm.reset(self.security) self.security = None if not self._with_storage: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable storage") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable storage") self.dsm.reset(self.storage) self.storage = None if not self._with_system: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable system") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable system") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable upgrade") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable upgrade") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable utilisation") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable utilisation") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: _LOGGER.debug( - "SynoAPI._async_setup_api_requests() - disable surveillance_station" + "SynoAPI._setup_api_requests() - disable surveillance_station" ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -504,26 +535,31 @@ def _fetch_device_configuration(self): async def async_reboot(self): """Reboot NAS.""" - if not self.system: - _LOGGER.debug("SynoAPI.async_reboot() - System API not ready: %s", self) - return - await self._hass.async_add_executor_job(self.system.reboot) + try: + await self._hass.async_add_executor_job(self.system.reboot) + except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: + _LOGGER.error("Reboot not possible, please try again later") + _LOGGER.debug("Exception:%s", err) async def async_shutdown(self): """Shutdown NAS.""" - if not self.system: - _LOGGER.debug("SynoAPI.async_shutdown() - System API not ready: %s", self) - return - await self._hass.async_add_executor_job(self.system.shutdown) + try: + await self._hass.async_add_executor_job(self.system.shutdown) + except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: + _LOGGER.error("Shutdown not possible, please try again later") + _LOGGER.debug("Exception:%s", err) async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" - self._unsub_dispatcher() + try: + await self._hass.async_add_executor_job(self.dsm.logout) + except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: + _LOGGER.debug("Logout not possible:%s", err) async def async_update(self, now=None): """Update function for updating API information.""" _LOGGER.debug("SynoAPI.async_update()") - self._async_setup_api_requests() + self._setup_api_requests() try: await self._hass.async_add_executor_job( self.dsm.update, self._with_information @@ -535,10 +571,9 @@ async def async_update(self, now=None): _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return - async_dispatcher_send(self._hass, self.signal_sensor_update) -class SynologyDSMBaseEntity(Entity): +class SynologyDSMBaseEntity(CoordinatorEntity): """Representation of a Synology NAS entry.""" def __init__( @@ -546,8 +581,11 @@ def __init__( api: SynoApi, entity_type: str, entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, ): """Initialize the Synology DSM entity.""" + super().__init__(coordinator) + self._api = api self._api_key = entity_type.split(":")[0] self.entity_type = entity_type.split(":")[-1] @@ -606,59 +644,13 @@ def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" return self._enable_default - -class SynologyDSMDispatcherEntity(SynologyDSMBaseEntity, Entity): - """Representation of a Synology NAS entry.""" - - def __init__( - self, - api: SynoApi, - entity_type: str, - entity_info: Dict[str, str], - ): - """Initialize the Synology DSM entity.""" - super().__init__(api, entity_type, entity_info) - Entity.__init__(self) - - @property - def should_poll(self) -> bool: - """No polling needed.""" - return False - - async def async_update(self): - """Only used by the generic entity update service.""" - if not self.enabled: - return - - await self._api.async_update() - async def async_added_to_hass(self): - """Register state update callback.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, self._api.signal_sensor_update, self.async_write_ha_state - ) - ) - + """Register entity for updates from API.""" self.async_on_remove(self._api.subscribe(self._api_key, self.unique_id)) + await super().async_added_to_hass() -class SynologyDSMCoordinatorEntity(SynologyDSMBaseEntity, CoordinatorEntity): - """Representation of a Synology NAS entry.""" - - def __init__( - self, - api: SynoApi, - entity_type: str, - entity_info: Dict[str, str], - coordinator: DataUpdateCoordinator, - ): - """Initialize the Synology DSM entity.""" - super().__init__(api, entity_type, entity_info) - CoordinatorEntity.__init__(self, coordinator) - - -class SynologyDSMDeviceEntity(SynologyDSMDispatcherEntity): +class SynologyDSMDeviceEntity(SynologyDSMBaseEntity): """Representation of a Synology NAS disk or volume entry.""" def __init__( @@ -666,10 +658,11 @@ def __init__( api: SynoApi, entity_type: str, entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, device_id: str = None, ): """Initialize the Synology DSM disk or volume entity.""" - super().__init__(api, entity_type, entity_info) + super().__init__(api, entity_type, entity_info, coordinator) self._device_id = device_id self._device_name = None self._device_manufacturer = None diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 2bbfb8f464107b..6e89f3d7a84f48 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -6,8 +6,9 @@ from homeassistant.const import CONF_DISKS from homeassistant.helpers.typing import HomeAssistantType -from . import SynologyDSMDeviceEntity, SynologyDSMDispatcherEntity +from . import SynologyDSMBaseEntity, SynologyDSMDeviceEntity from .const import ( + COORDINATOR_CENTRAL, DOMAIN, SECURITY_BINARY_SENSORS, STORAGE_DISK_BINARY_SENSORS, @@ -21,18 +22,20 @@ async def async_setup_entry( ) -> None: """Set up the Synology NAS binary sensor.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = data[SYNO_API] + coordinator = data[COORDINATOR_CENTRAL] entities = [ SynoDSMSecurityBinarySensor( - api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type] + api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type], coordinator ) for sensor_type in SECURITY_BINARY_SENSORS ] entities += [ SynoDSMUpgradeBinarySensor( - api, sensor_type, UPGRADE_BINARY_SENSORS[sensor_type] + api, sensor_type, UPGRADE_BINARY_SENSORS[sensor_type], coordinator ) for sensor_type in UPGRADE_BINARY_SENSORS ] @@ -42,7 +45,11 @@ async def async_setup_entry( for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids): entities += [ SynoDSMStorageBinarySensor( - api, sensor_type, STORAGE_DISK_BINARY_SENSORS[sensor_type], disk + api, + sensor_type, + STORAGE_DISK_BINARY_SENSORS[sensor_type], + coordinator, + disk, ) for sensor_type in STORAGE_DISK_BINARY_SENSORS ] @@ -50,7 +57,7 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMSecurityBinarySensor(SynologyDSMDispatcherEntity, BinarySensorEntity): +class SynoDSMSecurityBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity): """Representation a Synology Security binary sensor.""" @property @@ -78,7 +85,7 @@ def is_on(self) -> bool: return getattr(self._api.storage, self.entity_type)(self._device_id) -class SynoDSMUpgradeBinarySensor(SynologyDSMDispatcherEntity, BinarySensorEntity): +class SynoDSMUpgradeBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity): """Representation a Synology Upgrade binary sensor.""" @property diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index f24615bd28e9bd..c0e0ded72ed57b 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -3,16 +3,19 @@ from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation -from synology_dsm.exceptions import SynologyDSMAPIErrorException +from synology_dsm.exceptions import ( + SynologyDSMAPIErrorException, + SynologyDSMRequestException, +) from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMCoordinatorEntity +from . import SynoApi, SynologyDSMBaseEntity from .const import ( - COORDINATOR_SURVEILLANCE, + COORDINATOR_CAMERAS, DOMAIN, ENTITY_CLASS, ENTITY_ENABLE, @@ -37,7 +40,7 @@ async def async_setup_entry( return # initial data fetch - coordinator = data[COORDINATOR_SURVEILLANCE] + coordinator = data[COORDINATOR_CAMERAS] await coordinator.async_refresh() async_add_entities( @@ -46,7 +49,7 @@ async def async_setup_entry( ) -class SynoDSMCamera(SynologyDSMCoordinatorEntity, Camera): +class SynoDSMCamera(SynologyDSMBaseEntity, Camera): """Representation a Synology camera.""" def __init__( @@ -125,7 +128,11 @@ def camera_image(self) -> bytes: return None try: return self._api.surveillance_station.get_camera_image(self._camera_id) - except (SynologyDSMAPIErrorException) as err: + except ( + SynologyDSMAPIErrorException, + SynologyDSMRequestException, + ConnectionRefusedError, + ) as err: _LOGGER.debug( "SynoDSMCamera.camera_image(%s) - Exception:%s", self.camera_data.name, diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index f4638a5ec735ac..e7b510bb399377 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -31,6 +31,7 @@ import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE_TOKEN, CONF_VOLUMES, DEFAULT_PORT, DEFAULT_PORT_SSL, @@ -180,7 +181,7 @@ async def async_step_user(self, user_input=None): CONF_MAC: api.network.macs, } if otp_code: - config_data["device_token"] = api.device_token + config_data[CONF_DEVICE_TOKEN] = api.device_token if user_input.get(CONF_DISKS): config_data[CONF_DISKS] = user_input[CONF_DISKS] if user_input.get(CONF_VOLUMES): diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 1c4e004f7490bb..97f378c8e766af 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -19,7 +19,10 @@ DOMAIN = "synology_dsm" PLATFORMS = ["binary_sensor", "camera", "sensor", "switch"] -COORDINATOR_SURVEILLANCE = "coordinator_surveillance_station" +COORDINATOR_CAMERAS = "coordinator_cameras" +COORDINATOR_CENTRAL = "coordinator_central" +COORDINATOR_SWITCHES = "coordinator_switches" +SYSTEM_LOADED = "system_loaded" # Entry keys SYNO_API = "syno_api" @@ -28,6 +31,7 @@ # Configuration CONF_SERIAL = "serial" CONF_VOLUMES = "volumes" +CONF_DEVICE_TOKEN = "device_token" DEFAULT_USE_SSL = True DEFAULT_VERIFY_SSL = False diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 7dd4e5e9870ad2..79350ce89d3578 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -13,11 +13,13 @@ ) from homeassistant.helpers.temperature import display_temp from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.dt import utcnow -from . import SynoApi, SynologyDSMDeviceEntity, SynologyDSMDispatcherEntity +from . import SynoApi, SynologyDSMBaseEntity, SynologyDSMDeviceEntity from .const import ( CONF_VOLUMES, + COORDINATOR_CENTRAL, DOMAIN, ENTITY_UNIT_LOAD, INFORMATION_SENSORS, @@ -34,10 +36,14 @@ async def async_setup_entry( ) -> None: """Set up the Synology NAS Sensor.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = data[SYNO_API] + coordinator = data[COORDINATOR_CENTRAL] entities = [ - SynoDSMUtilSensor(api, sensor_type, UTILISATION_SENSORS[sensor_type]) + SynoDSMUtilSensor( + api, sensor_type, UTILISATION_SENSORS[sensor_type], coordinator + ) for sensor_type in UTILISATION_SENSORS ] @@ -46,7 +52,11 @@ async def async_setup_entry( for volume in entry.data.get(CONF_VOLUMES, api.storage.volumes_ids): entities += [ SynoDSMStorageSensor( - api, sensor_type, STORAGE_VOL_SENSORS[sensor_type], volume + api, + sensor_type, + STORAGE_VOL_SENSORS[sensor_type], + coordinator, + volume, ) for sensor_type in STORAGE_VOL_SENSORS ] @@ -56,20 +66,26 @@ async def async_setup_entry( for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids): entities += [ SynoDSMStorageSensor( - api, sensor_type, STORAGE_DISK_SENSORS[sensor_type], disk + api, + sensor_type, + STORAGE_DISK_SENSORS[sensor_type], + coordinator, + disk, ) for sensor_type in STORAGE_DISK_SENSORS ] entities += [ - SynoDSMInfoSensor(api, sensor_type, INFORMATION_SENSORS[sensor_type]) + SynoDSMInfoSensor( + api, sensor_type, INFORMATION_SENSORS[sensor_type], coordinator + ) for sensor_type in INFORMATION_SENSORS ] async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMDispatcherEntity): +class SynoDSMUtilSensor(SynologyDSMBaseEntity): """Representation a Synology Utilisation sensor.""" @property @@ -122,12 +138,18 @@ def state(self): return attr -class SynoDSMInfoSensor(SynologyDSMDispatcherEntity): +class SynoDSMInfoSensor(SynologyDSMBaseEntity): """Representation a Synology information sensor.""" - def __init__(self, api: SynoApi, entity_type: str, entity_info: Dict[str, str]): + def __init__( + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, + ): """Initialize the Synology SynoDSMInfoSensor entity.""" - super().__init__(api, entity_type, entity_info) + super().__init__(api, entity_type, entity_info, coordinator) self._previous_uptime = None self._last_boot = None diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 21511757cf315b..998f74adf2a4a8 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -7,9 +7,10 @@ from homeassistant.components.switch import ToggleEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMDispatcherEntity -from .const import DOMAIN, SURVEILLANCE_SWITCH, SYNO_API +from . import SynoApi, SynologyDSMBaseEntity +from .const import COORDINATOR_SWITCHES, DOMAIN, SURVEILLANCE_SWITCH, SYNO_API _LOGGER = logging.getLogger(__name__) @@ -19,16 +20,21 @@ async def async_setup_entry( ) -> None: """Set up the Synology NAS switch.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = data[SYNO_API] entities = [] if SynoSurveillanceStation.INFO_API_KEY in api.dsm.apis: info = await hass.async_add_executor_job(api.dsm.surveillance_station.get_info) version = info["data"]["CMSMinVersion"] + + # initial data fetch + coordinator = data[COORDINATOR_SWITCHES] + await coordinator.async_refresh() entities += [ SynoDSMSurveillanceHomeModeToggle( - api, sensor_type, SURVEILLANCE_SWITCH[sensor_type], version + api, sensor_type, SURVEILLANCE_SWITCH[sensor_type], version, coordinator ) for sensor_type in SURVEILLANCE_SWITCH ] @@ -36,58 +42,52 @@ async def async_setup_entry( async_add_entities(entities, True) -class SynoDSMSurveillanceHomeModeToggle(SynologyDSMDispatcherEntity, ToggleEntity): +class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity): """Representation a Synology Surveillance Station Home Mode toggle.""" def __init__( - self, api: SynoApi, entity_type: str, entity_info: Dict[str, str], version: str + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + version: str, + coordinator: DataUpdateCoordinator, ): """Initialize a Synology Surveillance Station Home Mode.""" super().__init__( api, entity_type, entity_info, + coordinator, ) self._version = version - self._state = None @property def is_on(self) -> bool: """Return the state.""" - if self.entity_type == "home_mode": - return self._state - return None - - @property - def should_poll(self) -> bool: - """No polling needed.""" - return True - - async def async_update(self): - """Update the toggle state.""" - _LOGGER.debug( - "SynoDSMSurveillanceHomeModeToggle.async_update(%s)", - self._api.information.serial, - ) - self._state = await self.hass.async_add_executor_job( - self._api.surveillance_station.get_home_mode_status - ) + return self.coordinator.data["switches"][self.entity_type] - def turn_on(self, **kwargs) -> None: + async def async_turn_on(self, **kwargs) -> None: """Turn on Home mode.""" _LOGGER.debug( "SynoDSMSurveillanceHomeModeToggle.turn_on(%s)", self._api.information.serial, ) - self._api.surveillance_station.set_home_mode(True) + await self.hass.async_add_executor_job( + self._api.dsm.surveillance_station.set_home_mode, True + ) + await self.coordinator.async_request_refresh() - def turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs) -> None: """Turn off Home mode.""" _LOGGER.debug( "SynoDSMSurveillanceHomeModeToggle.turn_off(%s)", self._api.information.serial, ) - self._api.surveillance_station.set_home_mode(False) + await self.hass.async_add_executor_job( + self._api.dsm.surveillance_station.set_home_mode, False + ) + await self.coordinator.async_request_refresh() @property def available(self) -> bool: From 23b2953773152e6563a2ff1e1c8f817a746fed65 Mon Sep 17 00:00:00 2001 From: MHV33 <41445347+MHV33@users.noreply.github.com> Date: Tue, 23 Feb 2021 16:25:50 -0600 Subject: [PATCH 0720/1818] Add more shopping list services (#45591) Co-authored-by: Franck Nijhof --- .../components/shopping_list/__init__.py | 54 ++++++++++++++++++- .../components/shopping_list/services.yaml | 13 +++++ tests/components/shopping_list/test_init.py | 34 ++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 1831f894cecab3..e438bf3b8f4b8a 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -15,17 +15,21 @@ from .const import DOMAIN ATTR_NAME = "name" +ATTR_COMPLETE = "complete" _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA) EVENT = "shopping_list_updated" -ITEM_UPDATE_SCHEMA = vol.Schema({"complete": bool, ATTR_NAME: str}) +ITEM_UPDATE_SCHEMA = vol.Schema({ATTR_COMPLETE: bool, ATTR_NAME: str}) PERSISTENCE = ".shopping_list.json" SERVICE_ADD_ITEM = "add_item" SERVICE_COMPLETE_ITEM = "complete_item" - +SERVICE_INCOMPLETE_ITEM = "incomplete_item" +SERVICE_COMPLETE_ALL = "complete_all" +SERVICE_INCOMPLETE_ALL = "incomplete_all" SERVICE_ITEM_SCHEMA = vol.Schema({vol.Required(ATTR_NAME): vol.Any(None, cv.string)}) +SERVICE_LIST_SCHEMA = vol.Schema({}) WS_TYPE_SHOPPING_LIST_ITEMS = "shopping_list/items" WS_TYPE_SHOPPING_LIST_ADD_ITEM = "shopping_list/items/add" @@ -92,6 +96,27 @@ async def complete_item_service(call): else: await data.async_update(item["id"], {"name": name, "complete": True}) + async def incomplete_item_service(call): + """Mark the item provided via `name` as incomplete.""" + data = hass.data[DOMAIN] + name = call.data.get(ATTR_NAME) + if name is None: + return + try: + item = [item for item in data.items if item["name"] == name][0] + except IndexError: + _LOGGER.error("Restoring of item failed: %s cannot be found", name) + else: + await data.async_update(item["id"], {"name": name, "complete": False}) + + async def complete_all_service(call): + """Mark all items in the list as complete.""" + await data.async_update_list({"complete": True}) + + async def incomplete_all_service(call): + """Mark all items in the list as incomplete.""" + await data.async_update_list({"complete": False}) + data = hass.data[DOMAIN] = ShoppingData(hass) await data.async_load() @@ -101,6 +126,24 @@ async def complete_item_service(call): hass.services.async_register( DOMAIN, SERVICE_COMPLETE_ITEM, complete_item_service, schema=SERVICE_ITEM_SCHEMA ) + hass.services.async_register( + DOMAIN, + SERVICE_INCOMPLETE_ITEM, + incomplete_item_service, + schema=SERVICE_ITEM_SCHEMA, + ) + hass.services.async_register( + DOMAIN, + SERVICE_COMPLETE_ALL, + complete_all_service, + schema=SERVICE_LIST_SCHEMA, + ) + hass.services.async_register( + DOMAIN, + SERVICE_INCOMPLETE_ALL, + incomplete_all_service, + schema=SERVICE_LIST_SCHEMA, + ) hass.http.register_view(ShoppingListView) hass.http.register_view(CreateShoppingListItemView) @@ -165,6 +208,13 @@ async def async_clear_completed(self): self.items = [itm for itm in self.items if not itm["complete"]] await self.hass.async_add_executor_job(self.save) + async def async_update_list(self, info): + """Update all items in the list.""" + for item in self.items: + item.update(info) + await self.hass.async_add_executor_job(self.save) + return self.items + @callback def async_reorder(self, item_ids): """Reorder items.""" diff --git a/homeassistant/components/shopping_list/services.yaml b/homeassistant/components/shopping_list/services.yaml index 2a1e89b9786047..73540210232b8a 100644 --- a/homeassistant/components/shopping_list/services.yaml +++ b/homeassistant/components/shopping_list/services.yaml @@ -21,3 +21,16 @@ complete_item: example: Beer selector: text: + +incomplete_item: + description: Marks an item as incomplete in the shopping list. + fields: + name: + description: The name of the item to mark as incomplete. + example: Beer + +complete_all: + description: Marks all items as completed in the shopping list. It does not remove the items. + +incomplete_all: + description: Marks all items as incomplete in the shopping list. diff --git a/tests/components/shopping_list/test_init.py b/tests/components/shopping_list/test_init.py index 0be4c70ef18f0e..48482787f4d58c 100644 --- a/tests/components/shopping_list/test_init.py +++ b/tests/components/shopping_list/test_init.py @@ -1,5 +1,6 @@ """Test shopping list component.""" +from homeassistant.components.shopping_list.const import DOMAIN from homeassistant.components.websocket_api.const import ( ERR_INVALID_FORMAT, ERR_NOT_FOUND, @@ -19,6 +20,39 @@ async def test_add_item(hass, sl_setup): assert response.speech["plain"]["speech"] == "I've added beer to your shopping list" +async def test_update_list(hass, sl_setup): + """Test updating all list items.""" + await intent.async_handle( + hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} + ) + + await intent.async_handle( + hass, "test", "HassShoppingListAddItem", {"item": {"value": "cheese"}} + ) + + # Update a single attribute, other attributes shouldn't change + await hass.data[DOMAIN].async_update_list({"complete": True}) + + beer = hass.data[DOMAIN].items[0] + assert beer["name"] == "beer" + assert beer["complete"] is True + + cheese = hass.data[DOMAIN].items[1] + assert cheese["name"] == "cheese" + assert cheese["complete"] is True + + # Update multiple attributes + await hass.data[DOMAIN].async_update_list({"name": "dupe", "complete": False}) + + beer = hass.data[DOMAIN].items[0] + assert beer["name"] == "dupe" + assert beer["complete"] is False + + cheese = hass.data[DOMAIN].items[1] + assert cheese["name"] == "dupe" + assert cheese["complete"] is False + + async def test_recent_items_intent(hass, sl_setup): """Test recent items.""" From c9847920affcdbfd4ad4b0e377691b73a16ded58 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 23 Feb 2021 17:32:39 -0500 Subject: [PATCH 0721/1818] Use core constants for dht (#46029) --- homeassistant/components/dht/sensor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index 57e12d03ffecbe..0bddd5a187eeab 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -9,6 +9,7 @@ from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, + CONF_PIN, PERCENTAGE, TEMP_FAHRENHEIT, ) @@ -19,7 +20,6 @@ _LOGGER = logging.getLogger(__name__) -CONF_PIN = "pin" CONF_SENSOR = "sensor" CONF_HUMIDITY_OFFSET = "humidity_offset" CONF_TEMPERATURE_OFFSET = "temperature_offset" @@ -56,7 +56,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the DHT sensor.""" - SENSOR_TYPES[SENSOR_TEMPERATURE][1] = hass.config.units.temperature_unit available_sensors = { "AM2302": Adafruit_DHT.AM2302, From 425d56d024d82dc593703803520184307558f224 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 23 Feb 2021 16:36:46 -0600 Subject: [PATCH 0722/1818] Fix Plex showing removed shared users (#46971) --- homeassistant/components/plex/server.py | 20 +++++++++++++------- tests/components/plex/conftest.py | 10 ++++++++++ tests/components/plex/test_init.py | 4 ++++ tests/fixtures/plex/plextv_shared_users.xml | 9 +++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/plex/plextv_shared_users.xml diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 1baceb78ff1e45..8f9d4d1cc515f0 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -213,21 +213,27 @@ def _update_plexdirect_hostname(): try: system_accounts = self._plex_server.systemAccounts() + shared_users = self.account.users() if self.account else [] except Unauthorized: _LOGGER.warning( "Plex account has limited permissions, shared account filtering will not be available" ) else: - self._accounts = [ - account.name for account in system_accounts if account.name - ] + self._accounts = [] + for user in shared_users: + for shared_server in user.servers: + if shared_server.machineIdentifier == self.machine_identifier: + self._accounts.append(user.title) + _LOGGER.debug("Linked accounts: %s", self.accounts) - owner_account = [ - account.name for account in system_accounts if account.accountID == 1 - ] + owner_account = next( + (account.name for account in system_accounts if account.accountID == 1), + None, + ) if owner_account: - self._owner_username = owner_account[0] + self._owner_username = owner_account + self._accounts.append(owner_account) _LOGGER.debug("Server owner found: '%s'", self._owner_username) self._version = self._plex_server.version diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index d3e66cc4989d37..372a06f15b62f9 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -218,6 +218,12 @@ def plextv_resources_fixture(plextv_resources_base): return plextv_resources_base.format(second_server_enabled=0) +@pytest.fixture(name="plextv_shared_users", scope="session") +def plextv_shared_users_fixture(plextv_resources_base): + """Load payload for plex.tv shared users and return it.""" + return load_fixture("plex/plextv_shared_users.xml") + + @pytest.fixture(name="session_base", scope="session") def session_base_fixture(): """Load the base session payload and return it.""" @@ -293,6 +299,7 @@ def mock_plex_calls( children_200, children_300, empty_library, + empty_payload, grandchildren_300, library, library_sections, @@ -310,12 +317,15 @@ def mock_plex_calls( playlist_500, plextv_account, plextv_resources, + plextv_shared_users, plex_server_accounts, plex_server_clients, plex_server_default, security_token, ): """Mock Plex API calls.""" + requests_mock.get("https://plex.tv/api/users/", text=plextv_shared_users) + requests_mock.get("https://plex.tv/api/invites/requested", text=empty_payload) requests_mock.get("https://plex.tv/users/account", text=plextv_account) requests_mock.get("https://plex.tv/api/resources", text=plextv_resources) diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index 95d2ef9bddb027..2e5a30ce11a4d1 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -116,6 +116,7 @@ async def test_setup_when_certificate_changed( plex_server_default, plextv_account, plextv_resources, + plextv_shared_users, ): """Test setup component when the Plex certificate has changed.""" await async_setup_component(hass, "persistent_notification", {}) @@ -141,6 +142,9 @@ def __init__(self): unique_id=DEFAULT_DATA["server_id"], ) + requests_mock.get("https://plex.tv/api/users/", text=plextv_shared_users) + requests_mock.get("https://plex.tv/api/invites/requested", text=empty_payload) + requests_mock.get("https://plex.tv/users/account", text=plextv_account) requests_mock.get("https://plex.tv/api/resources", text=plextv_resources) requests_mock.get(old_url, exc=WrongCertHostnameException) diff --git a/tests/fixtures/plex/plextv_shared_users.xml b/tests/fixtures/plex/plextv_shared_users.xml new file mode 100644 index 00000000000000..9421bdfa17a983 --- /dev/null +++ b/tests/fixtures/plex/plextv_shared_users.xml @@ -0,0 +1,9 @@ + + + + + + + + + From 228096847bddc56d095459597e0681daf49fdeb5 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Tue, 23 Feb 2021 23:42:24 +0100 Subject: [PATCH 0723/1818] Fix typo in fireservicerota strings (#46973) --- homeassistant/components/fireservicerota/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fireservicerota/strings.json b/homeassistant/components/fireservicerota/strings.json index c44673d6c2c361..aef6f1b684918b 100644 --- a/homeassistant/components/fireservicerota/strings.json +++ b/homeassistant/components/fireservicerota/strings.json @@ -9,7 +9,7 @@ } }, "reauth": { - "description": "Authentication tokens baceame invalid, login to recreate them.", + "description": "Authentication tokens became invalid, login to recreate them.", "data": { "password": "[%key:common::config_flow::data::password%]" } From 1a99562e91a06a4523d9a4071db4d4d8d8aa7fdd Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Feb 2021 18:58:04 -0500 Subject: [PATCH 0724/1818] Add zwave_js.refresh_value service (#46944) * add poll_value service * switch vol.All to vol.Schema * more relevant log message * switch service name to refresh_value, add parameter to refresh all watched values, fix tests * rename parameter and create task for polling command so we don't wait for a response * raise ValueError for unknown entity * better error message * fix test --- homeassistant/components/zwave_js/__init__.py | 4 +- homeassistant/components/zwave_js/const.py | 4 ++ homeassistant/components/zwave_js/entity.py | 38 ++++++++++ homeassistant/components/zwave_js/services.py | 31 +++++++- .../components/zwave_js/services.yaml | 16 +++++ tests/components/zwave_js/common.py | 3 + tests/components/zwave_js/test_climate.py | 8 ++- tests/components/zwave_js/test_services.py | 71 ++++++++++++++++++- 8 files changed, 168 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index a70716ad421825..062b28cf6a92b4 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -15,7 +15,7 @@ from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -193,7 +193,7 @@ def async_on_notification(notification: Notification) -> None: DATA_UNSUBSCRIBE: unsubscribe_callbacks, } - services = ZWaveServices(hass) + services = ZWaveServices(hass, entity_registry.async_get(hass)) services.async_register() # Set up websocket API diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 1031a51719ac86..dba4e6d33a3722 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -40,4 +40,8 @@ ATTR_CONFIG_PARAMETER_BITMASK = "bitmask" ATTR_CONFIG_VALUE = "value" +SERVICE_REFRESH_VALUE = "refresh_value" + +ATTR_REFRESH_ALL_VALUES = "refresh_all_values" + ADDON_SLUG = "core_zwave_js" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 3141dd0caea4c2..cb898e861e9f44 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -8,8 +8,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity +from .const import DOMAIN from .discovery import ZwaveDiscoveryInfo from .helpers import get_device_id @@ -39,6 +41,35 @@ def on_value_update(self) -> None: To be overridden by platforms needing this event. """ + async def async_poll_value(self, refresh_all_values: bool) -> None: + """Poll a value.""" + assert self.hass + if not refresh_all_values: + self.hass.async_create_task( + self.info.node.async_poll_value(self.info.primary_value) + ) + LOGGER.info( + ( + "Refreshing primary value %s for %s, " + "state update may be delayed for devices on battery" + ), + self.info.primary_value, + self.entity_id, + ) + return + + for value_id in self.watched_value_ids: + self.hass.async_create_task(self.info.node.async_poll_value(value_id)) + + LOGGER.info( + ( + "Refreshing values %s for %s, state update may be delayed for " + "devices on battery" + ), + ", ".join(self.watched_value_ids), + self.entity_id, + ) + async def async_added_to_hass(self) -> None: """Call when entity is added.""" assert self.hass # typing @@ -46,6 +77,13 @@ async def async_added_to_hass(self) -> None: self.async_on_remove( self.info.node.on(EVENT_VALUE_UPDATED, self._value_changed) ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{DOMAIN}_{self.unique_id}_poll_value", + self.async_poll_value, + ) + ) @property def device_info(self) -> dict: diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index da60ddab6662dd..c971891b35bedc 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -10,6 +10,8 @@ from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity_registry import EntityRegistry from . import const from .helpers import async_get_node_from_device_id, async_get_node_from_entity_id @@ -41,9 +43,10 @@ def parameter_name_does_not_need_bitmask( class ZWaveServices: """Class that holds our services (Zwave Commands) that should be published to hass.""" - def __init__(self, hass: HomeAssistant): + def __init__(self, hass: HomeAssistant, ent_reg: EntityRegistry): """Initialize with hass object.""" self._hass = hass + self._ent_reg = ent_reg @callback def async_register(self) -> None: @@ -71,6 +74,18 @@ def async_register(self) -> None: ), ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_REFRESH_VALUE, + self.async_poll_value, + schema=vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(const.ATTR_REFRESH_ALL_VALUES, default=False): bool, + } + ), + ) + async def async_set_config_parameter(self, service: ServiceCall) -> None: """Set a config value on a node.""" nodes: Set[ZwaveNode] = set() @@ -108,3 +123,17 @@ async def async_set_config_parameter(self, service: ServiceCall) -> None: f"Unable to set configuration parameter on Node {node} with " f"value {new_value}" ) + + async def async_poll_value(self, service: ServiceCall) -> None: + """Poll value on a node.""" + for entity_id in service.data[ATTR_ENTITY_ID]: + entry = self._ent_reg.async_get(entity_id) + if entry is None or entry.platform != const.DOMAIN: + raise ValueError( + f"Entity {entity_id} is not a valid {const.DOMAIN} entity." + ) + async_dispatcher_send( + self._hass, + f"{const.DOMAIN}_{entry.unique_id}_poll_value", + service.data[const.ATTR_REFRESH_ALL_VALUES], + ) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index a5e9efd721635f..8e6d907fc96dc7 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -66,3 +66,19 @@ set_config_parameter: advanced: true selector: object: + +refresh_value: + name: Refresh value(s) of a Z-Wave entity + description: Force update value(s) for a Z-Wave entity + target: + entity: + integration: zwave_js + fields: + refresh_all_values: + name: Refresh all values? + description: Whether to refresh all values (true) or just the primary value (false) + required: false + example: true + default: false + selector: + boolean: diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index 9c6adb100fa721..ebba16136a0334 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -13,3 +13,6 @@ PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( "binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door" ) +CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" +CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" +CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 17f4dd38144a9b..1ccf6f82017234 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -28,9 +28,11 @@ from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE -CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" -CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" -CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +from .common import ( + CLIMATE_DANFOSS_LC13_ENTITY, + CLIMATE_FLOOR_THERMOSTAT_ENTITY, + CLIMATE_RADIO_THERMOSTAT_ENTITY, +) async def test_thermostat_v2( diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index b085d9e32fb641..8e882b9547c8d5 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -6,14 +6,16 @@ ATTR_CONFIG_PARAMETER, ATTR_CONFIG_PARAMETER_BITMASK, ATTR_CONFIG_VALUE, + ATTR_REFRESH_ALL_VALUES, DOMAIN, + SERVICE_REFRESH_VALUE, SERVICE_SET_CONFIG_PARAMETER, ) from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID from homeassistant.helpers.device_registry import async_get as async_get_dev_reg from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg -from .common import AIR_TEMPERATURE_SENSOR +from .common import AIR_TEMPERATURE_SENSOR, CLIMATE_RADIO_THERMOSTAT_ENTITY from tests.common import MockConfigEntry @@ -293,3 +295,70 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): }, blocking=True, ) + + +async def test_poll_value( + hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration +): + """Test the poll_value service.""" + # Test polling the primary value + client.async_send_command.return_value = {"result": 2} + await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_VALUE, + {ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY}, + blocking=True, + ) + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.poll_value" + assert args["nodeId"] == 26 + assert args["valueId"] == { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "2": "Cool", + "3": "Auto", + "11": "Energy heat", + "12": "Energy cool", + }, + }, + "value": 1, + "ccVersion": 2, + } + + client.async_send_command.reset_mock() + + # Test polling all watched values + client.async_send_command.return_value = {"result": 2} + await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_VALUE, + { + ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, + ATTR_REFRESH_ALL_VALUES: True, + }, + blocking=True, + ) + assert len(client.async_send_command.call_args_list) == 8 + + # Test polling against an invalid entity raises ValueError + with pytest.raises(ValueError): + await hass.services.async_call( + DOMAIN, + SERVICE_REFRESH_VALUE, + {ATTR_ENTITY_ID: "sensor.fake_entity_id"}, + blocking=True, + ) From b657fd02cdb3a839adee909323ce9b69504b83b4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Feb 2021 18:58:25 -0500 Subject: [PATCH 0725/1818] deep copy zwave_js state in test fixtures so tests are more isolated (#46976) --- tests/components/zwave_js/conftest.py | 42 +++++++++++++++------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b4b89fb14a2d69..2f082621c45dd6 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,5 +1,6 @@ """Provide common Z-Wave JS fixtures.""" import asyncio +import copy import json from unittest.mock import AsyncMock, patch @@ -188,7 +189,7 @@ async def disconnect(): @pytest.fixture(name="multisensor_6") def multisensor_6_fixture(client, multisensor_6_state): """Mock a multisensor 6 node.""" - node = Node(client, multisensor_6_state) + node = Node(client, copy.deepcopy(multisensor_6_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -196,7 +197,7 @@ def multisensor_6_fixture(client, multisensor_6_state): @pytest.fixture(name="ecolink_door_sensor") def legacy_binary_sensor_fixture(client, ecolink_door_sensor_state): """Mock a legacy_binary_sensor node.""" - node = Node(client, ecolink_door_sensor_state) + node = Node(client, copy.deepcopy(ecolink_door_sensor_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -204,7 +205,7 @@ def legacy_binary_sensor_fixture(client, ecolink_door_sensor_state): @pytest.fixture(name="hank_binary_switch") def hank_binary_switch_fixture(client, hank_binary_switch_state): """Mock a binary switch node.""" - node = Node(client, hank_binary_switch_state) + node = Node(client, copy.deepcopy(hank_binary_switch_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -212,7 +213,7 @@ def hank_binary_switch_fixture(client, hank_binary_switch_state): @pytest.fixture(name="bulb_6_multi_color") def bulb_6_multi_color_fixture(client, bulb_6_multi_color_state): """Mock a bulb 6 multi-color node.""" - node = Node(client, bulb_6_multi_color_state) + node = Node(client, copy.deepcopy(bulb_6_multi_color_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -220,7 +221,7 @@ def bulb_6_multi_color_fixture(client, bulb_6_multi_color_state): @pytest.fixture(name="eaton_rf9640_dimmer") def eaton_rf9640_dimmer_fixture(client, eaton_rf9640_dimmer_state): """Mock a Eaton RF9640 (V4 compatible) dimmer node.""" - node = Node(client, eaton_rf9640_dimmer_state) + node = Node(client, copy.deepcopy(eaton_rf9640_dimmer_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -228,7 +229,7 @@ def eaton_rf9640_dimmer_fixture(client, eaton_rf9640_dimmer_state): @pytest.fixture(name="lock_schlage_be469") def lock_schlage_be469_fixture(client, lock_schlage_be469_state): """Mock a schlage lock node.""" - node = Node(client, lock_schlage_be469_state) + node = Node(client, copy.deepcopy(lock_schlage_be469_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -236,7 +237,7 @@ def lock_schlage_be469_fixture(client, lock_schlage_be469_state): @pytest.fixture(name="lock_august_pro") def lock_august_asl03_fixture(client, lock_august_asl03_state): """Mock a August Pro lock node.""" - node = Node(client, lock_august_asl03_state) + node = Node(client, copy.deepcopy(lock_august_asl03_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -246,7 +247,7 @@ def climate_radio_thermostat_ct100_plus_fixture( client, climate_radio_thermostat_ct100_plus_state ): """Mock a climate radio thermostat ct100 plus node.""" - node = Node(client, climate_radio_thermostat_ct100_plus_state) + node = Node(client, copy.deepcopy(climate_radio_thermostat_ct100_plus_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -256,7 +257,10 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( client, climate_radio_thermostat_ct100_plus_different_endpoints_state ): """Mock a climate radio thermostat ct100 plus node with values on different endpoints.""" - node = Node(client, climate_radio_thermostat_ct100_plus_different_endpoints_state) + node = Node( + client, + copy.deepcopy(climate_radio_thermostat_ct100_plus_different_endpoints_state), + ) client.driver.controller.nodes[node.node_id] = node return node @@ -264,7 +268,7 @@ def climate_radio_thermostat_ct100_plus_different_endpoints_fixture( @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.""" - node = Node(client, climate_danfoss_lc_13_state) + node = Node(client, copy.deepcopy(climate_danfoss_lc_13_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -272,7 +276,7 @@ def climate_danfoss_lc_13_fixture(client, climate_danfoss_lc_13_state): @pytest.fixture(name="climate_heatit_z_trm3") def climate_heatit_z_trm3_fixture(client, climate_heatit_z_trm3_state): """Mock a climate radio HEATIT Z-TRM3 node.""" - node = Node(client, climate_heatit_z_trm3_state) + node = Node(client, copy.deepcopy(climate_heatit_z_trm3_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -280,7 +284,7 @@ def climate_heatit_z_trm3_fixture(client, climate_heatit_z_trm3_state): @pytest.fixture(name="nortek_thermostat") def nortek_thermostat_fixture(client, nortek_thermostat_state): """Mock a nortek thermostat node.""" - node = Node(client, nortek_thermostat_state) + node = Node(client, copy.deepcopy(nortek_thermostat_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -317,7 +321,7 @@ async def integration_fixture(hass, client): @pytest.fixture(name="chain_actuator_zws12") def window_cover_fixture(client, chain_actuator_zws12_state): """Mock a window cover node.""" - node = Node(client, chain_actuator_zws12_state) + node = Node(client, copy.deepcopy(chain_actuator_zws12_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -325,7 +329,7 @@ def window_cover_fixture(client, chain_actuator_zws12_state): @pytest.fixture(name="in_wall_smart_fan_control") def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state): """Mock a fan node.""" - node = Node(client, in_wall_smart_fan_control_state) + node = Node(client, copy.deepcopy(in_wall_smart_fan_control_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -335,9 +339,9 @@ def multiple_devices_fixture( client, climate_radio_thermostat_ct100_plus_state, lock_schlage_be469_state ): """Mock a client with multiple devices.""" - node = Node(client, climate_radio_thermostat_ct100_plus_state) + node = Node(client, copy.deepcopy(climate_radio_thermostat_ct100_plus_state)) client.driver.controller.nodes[node.node_id] = node - node = Node(client, lock_schlage_be469_state) + node = Node(client, copy.deepcopy(lock_schlage_be469_state)) client.driver.controller.nodes[node.node_id] = node return client.driver.controller.nodes @@ -345,7 +349,7 @@ def multiple_devices_fixture( @pytest.fixture(name="gdc_zw062") def motorized_barrier_cover_fixture(client, gdc_zw062_state): """Mock a motorized barrier node.""" - node = Node(client, gdc_zw062_state) + node = Node(client, copy.deepcopy(gdc_zw062_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -353,7 +357,7 @@ def motorized_barrier_cover_fixture(client, gdc_zw062_state): @pytest.fixture(name="iblinds_v2") def iblinds_cover_fixture(client, iblinds_v2_state): """Mock an iBlinds v2.0 window cover node.""" - node = Node(client, iblinds_v2_state) + node = Node(client, copy.deepcopy(iblinds_v2_state)) client.driver.controller.nodes[node.node_id] = node return node @@ -361,6 +365,6 @@ def iblinds_cover_fixture(client, iblinds_v2_state): @pytest.fixture(name="ge_12730") def ge_12730_fixture(client, ge_12730_state): """Mock a GE 12730 fan controller node.""" - node = Node(client, ge_12730_state) + node = Node(client, copy.deepcopy(ge_12730_state)) client.driver.controller.nodes[node.node_id] = node return node From 9159f5490084b7f19bfd713a5ff6476095d7002a Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 24 Feb 2021 00:04:14 +0000 Subject: [PATCH 0726/1818] [ci skip] Translation update --- .../components/abode/translations/nl.json | 8 +++- .../components/acmeda/translations/nl.json | 3 ++ .../advantage_air/translations/nl.json | 4 ++ .../components/aemet/translations/nl.json | 21 +++++++++ .../components/agent_dvr/translations/nl.json | 1 + .../components/airnow/translations/nl.json | 24 ++++++++++ .../components/airvisual/translations/nl.json | 21 ++++++++- .../ambiclimate/translations/nl.json | 3 +- .../components/apple_tv/translations/nl.json | 36 ++++++++++++++- .../components/asuswrt/translations/nl.json | 35 +++++++++++++++ .../components/aurora/translations/nl.json | 26 +++++++++++ .../components/awair/translations/nl.json | 14 +++++- .../components/axis/translations/nl.json | 3 +- .../azure_devops/translations/nl.json | 1 + .../components/blink/translations/nl.json | 8 ++++ .../bmw_connected_drive/translations/nl.json | 20 +++++++++ .../components/broadlink/translations/nl.json | 2 + .../components/bsblan/translations/nl.json | 4 +- .../components/cloud/translations/nl.json | 15 +++++++ .../cloudflare/translations/nl.json | 28 +++++++++++- .../components/daikin/translations/nl.json | 3 ++ .../components/denonavr/translations/nl.json | 3 +- .../devolo_home_control/translations/nl.json | 5 +++ .../dialogflow/translations/nl.json | 3 +- .../components/ecobee/translations/nl.json | 3 ++ .../components/econet/translations/nl.json | 22 ++++++++++ .../components/epson/translations/nl.json | 16 +++++++ .../fireservicerota/translations/en.json | 2 +- .../fireservicerota/translations/nl.json | 28 ++++++++++++ .../components/fritzbox/translations/nl.json | 12 ++++- .../fritzbox_callmonitor/translations/nl.json | 21 +++++++++ .../components/geofency/translations/nl.json | 3 +- .../components/gpslogger/translations/nl.json | 3 +- .../components/gree/translations/nl.json | 13 ++++++ .../components/habitica/translations/nl.json | 17 +++++++ .../homeassistant/translations/nl.json | 3 ++ .../components/homekit/translations/nl.json | 11 ++++- .../components/hue/translations/nl.json | 2 +- .../components/hyperion/translations/nl.json | 22 ++++++++++ .../components/icloud/translations/nl.json | 2 + .../keenetic_ndms2/translations/nl.json | 29 ++++++++++++ .../components/kmtronic/translations/nl.json | 21 +++++++++ .../components/kmtronic/translations/ru.json | 21 +++++++++ .../kmtronic/translations/zh-Hant.json | 21 +++++++++ .../components/kulersky/translations/nl.json | 13 ++++++ .../components/life360/translations/nl.json | 6 ++- .../components/litejet/translations/et.json | 19 ++++++++ .../components/litejet/translations/nl.json | 18 ++++++++ .../litterrobot/translations/nl.json | 20 +++++++++ .../litterrobot/translations/ru.json | 20 +++++++++ .../litterrobot/translations/zh-Hant.json | 20 +++++++++ .../components/local_ip/translations/nl.json | 1 + .../logi_circle/translations/nl.json | 4 +- .../components/luftdaten/translations/nl.json | 1 + .../lutron_caseta/translations/nl.json | 39 ++++++++++++++++ .../components/lyric/translations/nl.json | 16 +++++++ .../components/mazda/translations/nl.json | 15 +++++++ .../meteo_france/translations/nl.json | 3 ++ .../components/mill/translations/nl.json | 5 ++- .../motion_blinds/translations/nl.json | 32 ++++++++++++++ .../components/mysensors/translations/nl.json | 10 +++++ .../components/neato/translations/nl.json | 15 ++++++- .../components/nest/translations/nl.json | 18 +++++++- .../components/nuki/translations/nl.json | 18 ++++++++ .../ondilo_ico/translations/nl.json | 17 +++++++ .../components/onewire/translations/nl.json | 5 +++ .../opentherm_gw/translations/nl.json | 1 + .../ovo_energy/translations/nl.json | 6 +++ .../components/ozw/translations/nl.json | 15 +++++++ .../philips_js/translations/nl.json | 19 ++++++++ .../components/plaato/translations/nl.json | 28 +++++++++++- .../components/plex/translations/nl.json | 1 + .../components/point/translations/nl.json | 3 +- .../components/poolsense/translations/nl.json | 1 + .../components/powerwall/translations/nl.json | 7 ++- .../components/profiler/translations/nl.json | 5 +++ .../progettihwsw/translations/nl.json | 18 ++++++++ .../components/ps4/translations/nl.json | 1 + .../rainmachine/translations/nl.json | 10 +++++ .../recollect_waste/translations/nl.json | 25 +++++++++++ .../components/rfxtrx/translations/nl.json | 25 +++++++++++ .../translations/zh-Hant.json | 21 +++++++++ .../components/roku/translations/nl.json | 1 + .../components/roomba/translations/nl.json | 6 +++ .../ruckus_unleashed/translations/nl.json | 7 ++- .../components/shelly/translations/nl.json | 13 ++++++ .../components/smappee/translations/nl.json | 8 +++- .../components/smarthab/translations/nl.json | 2 + .../components/smarttub/translations/nl.json | 21 +++++++++ .../components/solaredge/translations/nl.json | 7 ++- .../somfy_mylink/translations/nl.json | 39 +++++++++++++++- .../components/sonarr/translations/nl.json | 5 +++ .../srp_energy/translations/nl.json | 22 ++++++++++ .../components/subaru/translations/nl.json | 38 ++++++++++++++++ .../subaru/translations/zh-Hant.json | 44 +++++++++++++++++++ .../tellduslive/translations/nl.json | 6 ++- .../components/tesla/translations/nl.json | 7 ++- .../components/tibber/translations/nl.json | 1 + .../components/tile/translations/nl.json | 3 ++ .../components/toon/translations/nl.json | 3 +- .../totalconnect/translations/nl.json | 6 ++- .../totalconnect/translations/ru.json | 17 ++++++- .../totalconnect/translations/zh-Hant.json | 17 ++++++- .../components/traccar/translations/nl.json | 3 +- .../transmission/translations/nl.json | 1 + .../components/tuya/translations/nl.json | 13 ++++++ .../twentemilieu/translations/nl.json | 1 + .../components/twilio/translations/nl.json | 3 +- .../components/twinkly/translations/nl.json | 6 +++ .../components/unifi/translations/nl.json | 1 + .../components/upcloud/translations/nl.json | 4 +- .../components/vesync/translations/nl.json | 3 ++ .../components/vizio/translations/nl.json | 1 + .../components/weather/translations/et.json | 2 +- .../components/xbox/translations/nl.json | 17 +++++++ .../xiaomi_aqara/translations/nl.json | 10 ++++- .../xiaomi_miio/translations/nl.json | 9 ++++ .../xiaomi_miio/translations/ru.json | 1 + .../xiaomi_miio/translations/zh-Hant.json | 1 + .../components/zerproc/translations/nl.json | 13 ++++++ .../zoneminder/translations/nl.json | 3 +- .../components/zwave_js/translations/ca.json | 14 +++--- .../components/zwave_js/translations/cs.json | 8 ++-- .../components/zwave_js/translations/en.json | 7 ++- .../components/zwave_js/translations/es.json | 14 +++--- .../components/zwave_js/translations/et.json | 14 +++--- .../components/zwave_js/translations/fr.json | 14 +++--- .../components/zwave_js/translations/it.json | 14 +++--- .../components/zwave_js/translations/ko.json | 8 ++-- .../components/zwave_js/translations/nl.json | 14 +++--- .../components/zwave_js/translations/no.json | 14 +++--- .../components/zwave_js/translations/pl.json | 14 +++--- .../components/zwave_js/translations/ru.json | 14 +++--- .../components/zwave_js/translations/tr.json | 14 +++--- .../zwave_js/translations/zh-Hant.json | 14 +++--- 135 files changed, 1470 insertions(+), 136 deletions(-) create mode 100644 homeassistant/components/aemet/translations/nl.json create mode 100644 homeassistant/components/airnow/translations/nl.json create mode 100644 homeassistant/components/asuswrt/translations/nl.json create mode 100644 homeassistant/components/aurora/translations/nl.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/nl.json create mode 100644 homeassistant/components/cloud/translations/nl.json create mode 100644 homeassistant/components/econet/translations/nl.json create mode 100644 homeassistant/components/epson/translations/nl.json create mode 100644 homeassistant/components/fireservicerota/translations/nl.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/nl.json create mode 100644 homeassistant/components/gree/translations/nl.json create mode 100644 homeassistant/components/habitica/translations/nl.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/nl.json create mode 100644 homeassistant/components/kmtronic/translations/nl.json create mode 100644 homeassistant/components/kmtronic/translations/ru.json create mode 100644 homeassistant/components/kmtronic/translations/zh-Hant.json create mode 100644 homeassistant/components/kulersky/translations/nl.json create mode 100644 homeassistant/components/litejet/translations/et.json create mode 100644 homeassistant/components/litejet/translations/nl.json create mode 100644 homeassistant/components/litterrobot/translations/nl.json create mode 100644 homeassistant/components/litterrobot/translations/ru.json create mode 100644 homeassistant/components/litterrobot/translations/zh-Hant.json create mode 100644 homeassistant/components/lyric/translations/nl.json create mode 100644 homeassistant/components/motion_blinds/translations/nl.json create mode 100644 homeassistant/components/nuki/translations/nl.json create mode 100644 homeassistant/components/ondilo_ico/translations/nl.json create mode 100644 homeassistant/components/philips_js/translations/nl.json create mode 100644 homeassistant/components/recollect_waste/translations/nl.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/zh-Hant.json create mode 100644 homeassistant/components/smarttub/translations/nl.json create mode 100644 homeassistant/components/srp_energy/translations/nl.json create mode 100644 homeassistant/components/subaru/translations/nl.json create mode 100644 homeassistant/components/subaru/translations/zh-Hant.json create mode 100644 homeassistant/components/xbox/translations/nl.json create mode 100644 homeassistant/components/zerproc/translations/nl.json diff --git a/homeassistant/components/abode/translations/nl.json b/homeassistant/components/abode/translations/nl.json index 9177b1deb7c346..7b6a8b5aace727 100644 --- a/homeassistant/components/abode/translations/nl.json +++ b/homeassistant/components/abode/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "reauth_successful": "Herauthenticatie was succesvol", "single_instance_allowed": "Slechts een enkele configuratie van Abode is toegestaan." }, "error": { @@ -12,9 +13,14 @@ "mfa": { "data": { "mfa_code": "MFA-code (6-cijfers)" - } + }, + "title": "Voer uw MFA-code voor Abode in" }, "reauth_confirm": { + "data": { + "password": "Wachtwoord", + "username": "E-mail" + }, "title": "Vul uw Abode-inloggegevens in" }, "user": { diff --git a/homeassistant/components/acmeda/translations/nl.json b/homeassistant/components/acmeda/translations/nl.json index 470e0f8f6982f2..aac926ec0481ce 100644 --- a/homeassistant/components/acmeda/translations/nl.json +++ b/homeassistant/components/acmeda/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_devices_found": "Geen apparaten gevonden op het netwerk" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/advantage_air/translations/nl.json b/homeassistant/components/advantage_air/translations/nl.json index 95395d24bcaf60..3206c7a3165560 100644 --- a/homeassistant/components/advantage_air/translations/nl.json +++ b/homeassistant/components/advantage_air/translations/nl.json @@ -3,9 +3,13 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd" }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, "step": { "user": { "data": { + "ip_address": "IP-adres", "port": "Poort" }, "description": "Maak verbinding met de API van uw Advantage Air-tablet voor wandmontage.", diff --git a/homeassistant/components/aemet/translations/nl.json b/homeassistant/components/aemet/translations/nl.json new file mode 100644 index 00000000000000..02415dde1e6423 --- /dev/null +++ b/homeassistant/components/aemet/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Locatie is al geconfigureerd." + }, + "error": { + "invalid_api_key": "Ongeldige API-sleutel" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "name": "Naam van de integratie" + }, + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/nl.json b/homeassistant/components/agent_dvr/translations/nl.json index ad625c169c8372..7c679f66c11936 100644 --- a/homeassistant/components/agent_dvr/translations/nl.json +++ b/homeassistant/components/agent_dvr/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" }, "step": { diff --git a/homeassistant/components/airnow/translations/nl.json b/homeassistant/components/airnow/translations/nl.json new file mode 100644 index 00000000000000..011498269f89a2 --- /dev/null +++ b/homeassistant/components/airnow/translations/nl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "invalid_location": "Geen resultaten gevonden voor die locatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json index 85f8be5f8e00cb..ecf2322c801350 100644 --- a/homeassistant/components/airvisual/translations/nl.json +++ b/homeassistant/components/airvisual/translations/nl.json @@ -1,12 +1,14 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten of Node / Pro ID zijn al geregistreerd." + "already_configured": "Deze co\u00f6rdinaten of Node / Pro ID zijn al geregistreerd.", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Kan geen verbinding maken", "general_error": "Er is een onbekende fout opgetreden.", - "invalid_api_key": "Ongeldige API-sleutel" + "invalid_api_key": "Ongeldige API-sleutel", + "location_not_found": "Locatie niet gevonden" }, "step": { "geography": { @@ -18,6 +20,21 @@ "description": "Gebruik de AirVisual cloud API om een geografische locatie te bewaken.", "title": "Configureer een geografie" }, + "geography_by_coords": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad" + } + }, + "geography_by_name": { + "data": { + "api_key": "API-sleutel", + "city": "Stad", + "country": "Land" + }, + "description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken." + }, "node_pro": { "data": { "ip_address": "IP adres/hostname van component", diff --git a/homeassistant/components/ambiclimate/translations/nl.json b/homeassistant/components/ambiclimate/translations/nl.json index 52f8cfc40d37e4..1d7652a370ecb7 100644 --- a/homeassistant/components/ambiclimate/translations/nl.json +++ b/homeassistant/components/ambiclimate/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "access_token": "Onbekende fout bij het genereren van een toegangstoken.", - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." }, "create_entry": { "default": "Succesvol geverifieerd met Ambiclimate" diff --git a/homeassistant/components/apple_tv/translations/nl.json b/homeassistant/components/apple_tv/translations/nl.json index a11488ebca979e..d809ac749b762b 100644 --- a/homeassistant/components/apple_tv/translations/nl.json +++ b/homeassistant/components/apple_tv/translations/nl.json @@ -1,9 +1,41 @@ { "config": { "abort": { + "already_configured_device": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "backoff": "Het apparaat accepteert op dit moment geen koppelingsverzoeken (u heeft mogelijk te vaak een ongeldige pincode ingevoerd), probeer het later opnieuw.", "device_did_not_pair": "Er is geen poging gedaan om het koppelingsproces te voltooien vanaf het apparaat.", - "invalid_config": "De configuratie voor dit apparaat is onvolledig. Probeer het opnieuw toe te voegen." + "invalid_config": "De configuratie voor dit apparaat is onvolledig. Probeer het opnieuw toe te voegen.", + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "unknown": "Onverwachte fout" + }, + "error": { + "already_configured": "Apparaat is al geconfigureerd", + "invalid_auth": "Ongeldige authenticatie", + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "unknown": "Onverwachte fout" + }, + "flow_title": "Apple TV: {name}", + "step": { + "confirm": { + "title": "Bevestig het toevoegen van Apple TV" + }, + "pair_no_pin": { + "title": "Koppelen" + }, + "pair_with_pin": { + "data": { + "pin": "PIN-code" + }, + "title": "Koppelen" + }, + "user": { + "data": { + "device_input": "Apparaat" + }, + "title": "Stel een nieuwe Apple TV in" + } } - } + }, + "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json new file mode 100644 index 00000000000000..1128a820cd577c --- /dev/null +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_host": "Ongeldige hostnaam of IP-adres", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Mode", + "name": "Naam", + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam" + }, + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "track_unknown": "Volg onbekende / naamloze apparaten" + }, + "title": "AsusWRT-opties" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/nl.json b/homeassistant/components/aurora/translations/nl.json new file mode 100644 index 00000000000000..fe7b4809f1320c --- /dev/null +++ b/homeassistant/components/aurora/translations/nl.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, + "step": { + "user": { + "data": { + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "name": "Naam" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "Drempel (%)" + } + } + } + }, + "title": "NOAA Aurora Sensor" +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/nl.json b/homeassistant/components/awair/translations/nl.json index 08a30a52250ac7..5d20aed2fdb878 100644 --- a/homeassistant/components/awair/translations/nl.json +++ b/homeassistant/components/awair/translations/nl.json @@ -2,14 +2,26 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", - "no_devices_found": "Geen apparaten op het netwerk gevonden" + "no_devices_found": "Geen apparaten op het netwerk gevonden", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { + "invalid_access_token": "Ongeldig toegangstoken", "unknown": "Onverwachte fout" }, "step": { "reauth": { + "data": { + "access_token": "Toegangstoken", + "email": "E-mail" + }, "description": "Voer uw Awair-ontwikkelaarstoegangstoken opnieuw in." + }, + "user": { + "data": { + "access_token": "Toegangstoken", + "email": "E-mail" + } } } } diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 483acefec15e66..345e6622e9384a 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -8,7 +8,8 @@ "error": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", - "cannot_connect": "Kan geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" }, "flow_title": "Axis apparaat: {name} ({host})", "step": { diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index 9abecd187fef4d..aef6a7178954e4 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Account is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" } } diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json index c1ab971dbf04f8..4067bf75f834e0 100644 --- a/homeassistant/components/blink/translations/nl.json +++ b/homeassistant/components/blink/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "invalid_access_token": "Ongeldig toegangstoken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -23,5 +24,12 @@ "title": "Aanmelden met Blink account" } } + }, + "options": { + "step": { + "simple_options": { + "title": "Blink opties" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/nl.json b/homeassistant/components/bmw_connected_drive/translations/nl.json new file mode 100644 index 00000000000000..83ae0b9ff7d012 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/nl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "region": "ConnectedDrive-regio", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 7205512d3681dc..2f3a7313f75c78 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -3,11 +3,13 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kon niet verbinden", + "invalid_host": "Ongeldige hostnaam of IP-adres", "not_supported": "Apparaat wordt niet ondersteund", "unknown": "Onverwachte fout" }, "error": { "cannot_connect": "Kon niet verbinden", + "invalid_host": "Ongeldige hostnaam of IP-adres", "unknown": "Onverwachte fout" }, "flow_title": "{name} ({model} bij {host})", diff --git a/homeassistant/components/bsblan/translations/nl.json b/homeassistant/components/bsblan/translations/nl.json index 850f942df2e7e6..415cd759a8a886 100644 --- a/homeassistant/components/bsblan/translations/nl.json +++ b/homeassistant/components/bsblan/translations/nl.json @@ -12,7 +12,9 @@ "data": { "host": "Host", "passkey": "Passkey-tekenreeks", - "port": "Poort" + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam" }, "description": "Stel uw BSB-Lan-apparaat in om te integreren met Home Assistant.", "title": "Maak verbinding met het BSB-Lan-apparaat" diff --git a/homeassistant/components/cloud/translations/nl.json b/homeassistant/components/cloud/translations/nl.json new file mode 100644 index 00000000000000..d9aa78afecb7e2 --- /dev/null +++ b/homeassistant/components/cloud/translations/nl.json @@ -0,0 +1,15 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa ingeschakeld", + "can_reach_cloud": "Bereik Home Assistant Cloud", + "can_reach_cloud_auth": "Bereik authenticatieserver", + "google_enabled": "Google ingeschakeld", + "logged_in": "Ingelogd", + "relayer_connected": "Relayer verbonden", + "remote_connected": "Op afstand verbonden", + "remote_enabled": "Op afstand ingeschakeld", + "subscription_expiration": "Afloop abonnement" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/nl.json b/homeassistant/components/cloudflare/translations/nl.json index 37162761d86f3e..35c765d5da7f2e 100644 --- a/homeassistant/components/cloudflare/translations/nl.json +++ b/homeassistant/components/cloudflare/translations/nl.json @@ -1,7 +1,33 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "unknown": "Onverwachte fout" + }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "invalid_zone": "Ongeldige zone" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Records" + }, + "title": "Kies de records die u wilt bijwerken" + }, + "user": { + "data": { + "api_token": "API-token" + }, + "title": "Verbinden met Cloudflare" + }, + "zone": { + "data": { + "zone": "Zone" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index 2d1e1edbdbbbc2..69d52436beb986 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -4,6 +4,9 @@ "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kon niet verbinden" }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index 9f79aebeb600fc..6a00e03765fb3d 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang" }, "flow_title": "Denon AVR Network Receiver: {name}", "step": { diff --git a/homeassistant/components/devolo_home_control/translations/nl.json b/homeassistant/components/devolo_home_control/translations/nl.json index d61f9183cc5d30..5d79d2ec9e9c31 100644 --- a/homeassistant/components/devolo_home_control/translations/nl.json +++ b/homeassistant/components/devolo_home_control/translations/nl.json @@ -3,9 +3,14 @@ "abort": { "already_configured": "Account is al geconfigureerd" }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, "step": { "user": { "data": { + "home_control_url": "Home Control URL", + "mydevolo_url": "mydevolo URL", "password": "Wachtwoord", "username": "E-mail adres / devolo ID" } diff --git a/homeassistant/components/dialogflow/translations/nl.json b/homeassistant/components/dialogflow/translations/nl.json index 7cccf8ecb9b2bd..82fe7daea00150 100644 --- a/homeassistant/components/dialogflow/translations/nl.json +++ b/homeassistant/components/dialogflow/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u [webhookintegratie van Dialogflow]({dialogflow_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [de documentatie]({docs_url}) voor verdere informatie." diff --git a/homeassistant/components/ecobee/translations/nl.json b/homeassistant/components/ecobee/translations/nl.json index 62405b05ff1959..957d2f8244d29c 100644 --- a/homeassistant/components/ecobee/translations/nl.json +++ b/homeassistant/components/ecobee/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { "pin_request_failed": "Fout bij het aanvragen van pincode bij ecobee; Controleer of de API-sleutel correct is.", "token_request_failed": "Fout bij het aanvragen van tokens bij ecobee; probeer het opnieuw." diff --git a/homeassistant/components/econet/translations/nl.json b/homeassistant/components/econet/translations/nl.json new file mode 100644 index 00000000000000..226c1611e2b71d --- /dev/null +++ b/homeassistant/components/econet/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Wachtwoord" + }, + "title": "Stel Rheem EcoNet-account in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/epson/translations/nl.json b/homeassistant/components/epson/translations/nl.json new file mode 100644 index 00000000000000..d5ae90c0e38274 --- /dev/null +++ b/homeassistant/components/epson/translations/nl.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Naam", + "port": "Poort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/en.json b/homeassistant/components/fireservicerota/translations/en.json index 288b89c31b8276..a059081760dec3 100644 --- a/homeassistant/components/fireservicerota/translations/en.json +++ b/homeassistant/components/fireservicerota/translations/en.json @@ -15,7 +15,7 @@ "data": { "password": "Password" }, - "description": "Authentication tokens baceame invalid, login to recreate them." + "description": "Authentication tokens became invalid, login to recreate them." }, "user": { "data": { diff --git a/homeassistant/components/fireservicerota/translations/nl.json b/homeassistant/components/fireservicerota/translations/nl.json new file mode 100644 index 00000000000000..7289d53e71fb0f --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/nl.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "reauth": { + "data": { + "password": "Wachtwoord" + } + }, + "user": { + "data": { + "password": "Wachtwoord", + "url": "Website", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index b72374547bc157..71a80dbd577c46 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -3,7 +3,11 @@ "abort": { "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", - "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen." + "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie" }, "flow_title": "AVM FRITZ!Box: {name}", "step": { @@ -14,6 +18,12 @@ }, "description": "Wilt u {name} instellen?" }, + "reauth_confirm": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + }, "user": { "data": { "host": "Host of IP-adres", diff --git a/homeassistant/components/fritzbox_callmonitor/translations/nl.json b/homeassistant/components/fritzbox_callmonitor/translations/nl.json new file mode 100644 index 00000000000000..3381ed0d9b2eda --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "no_devices_found": "Geen apparaten gevonden op het netwerk" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/nl.json b/homeassistant/components/geofency/translations/nl.json index 763d903a8ba5f1..59ed1cf6b5bf1b 100644 --- a/homeassistant/components/geofency/translations/nl.json +++ b/homeassistant/components/geofency/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in Geofency.\n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." diff --git a/homeassistant/components/gpslogger/translations/nl.json b/homeassistant/components/gpslogger/translations/nl.json index dbf7f47a2e9cbc..d90b648760db9d 100644 --- a/homeassistant/components/gpslogger/translations/nl.json +++ b/homeassistant/components/gpslogger/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar Home Assistant te verzenden, moet u de webhook-functie instellen in GPSLogger. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." diff --git a/homeassistant/components/gree/translations/nl.json b/homeassistant/components/gree/translations/nl.json new file mode 100644 index 00000000000000..d11896014fd2c6 --- /dev/null +++ b/homeassistant/components/gree/translations/nl.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "step": { + "confirm": { + "description": "Wil je beginnen met instellen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/nl.json b/homeassistant/components/habitica/translations/nl.json new file mode 100644 index 00000000000000..13a4fd6c7299a9 --- /dev/null +++ b/homeassistant/components/habitica/translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_credentials": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "url": "URL" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/nl.json b/homeassistant/components/homeassistant/translations/nl.json index 338a019019feac..47b69068ea34ac 100644 --- a/homeassistant/components/homeassistant/translations/nl.json +++ b/homeassistant/components/homeassistant/translations/nl.json @@ -1,12 +1,15 @@ { "system_health": { "info": { + "arch": "CPU-architectuur", + "chassis": "Chassis", "dev": "Ontwikkeling", "docker": "Docker", "docker_version": "Docker", "hassio": "Supervisor", "host_os": "Home Assistant OS", "installation_type": "Type installatie", + "os_name": "Besturingssysteemfamilie", "os_version": "Versie van het besturingssysteem", "python_version": "Python versie", "supervisor": "Supervisor", diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 2733d6bd12d367..bcf61fe9868faa 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -4,6 +4,11 @@ "port_name_in_use": "Er is al een bridge of apparaat met dezelfde naam of poort geconfigureerd." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entiteit" + } + }, "pairing": { "description": "Zodra de {name} klaar is, is het koppelen beschikbaar in \"Meldingen\" als \"HomeKit Bridge Setup\".", "title": "Koppel HomeKit Bridge" @@ -11,7 +16,8 @@ "user": { "data": { "auto_start": "Automatisch starten (uitschakelen als u Z-Wave of een ander vertraagd startsysteem gebruikt)", - "include_domains": "Domeinen om op te nemen" + "include_domains": "Domeinen om op te nemen", + "mode": "Mode" }, "description": "De HomeKit-integratie geeft u toegang tot uw Home Assistant-entiteiten in HomeKit. In bridge-modus zijn HomeKit-bruggen beperkt tot 150 accessoires per exemplaar, inclusief de brug zelf. Als u meer dan het maximale aantal accessoires wilt overbruggen, is het aan te raden om meerdere HomeKit-bridges voor verschillende domeinen te gebruiken. Gedetailleerde entiteitsconfiguratie is alleen beschikbaar via YAML voor de primaire bridge.", "title": "Activeer HomeKit Bridge" @@ -37,7 +43,8 @@ }, "include_exclude": { "data": { - "entities": "Entiteiten" + "entities": "Entiteiten", + "mode": "Mode" } }, "init": { diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index f04d372bf6ad59..cead9dd21c64db 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -8,7 +8,7 @@ "discover_timeout": "Hue bridges kunnen niet worden gevonden", "no_bridges": "Geen Philips Hue bridges ontdekt", "not_hue_bridge": "Dit is geen Hue bridge", - "unknown": "Onbekende fout opgetreden" + "unknown": "Onverwachte fout" }, "error": { "linking": "Er is een onbekende verbindingsfout opgetreden.", diff --git a/homeassistant/components/hyperion/translations/nl.json b/homeassistant/components/hyperion/translations/nl.json index d93018f8a3cb5c..0898272e4a2bab 100644 --- a/homeassistant/components/hyperion/translations/nl.json +++ b/homeassistant/components/hyperion/translations/nl.json @@ -1,10 +1,32 @@ { "config": { + "abort": { + "already_configured": "Service is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "auth_new_token_not_granted_error": "Nieuw aangemaakte token is niet goedgekeurd in Hyperion UI", + "auth_new_token_not_work_error": "Verificatie met nieuw aangemaakt token mislukt", + "auth_required_error": "Kan niet bepalen of autorisatie vereist is", + "cannot_connect": "Kan geen verbinding maken", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_access_token": "Ongeldig toegangstoken" + }, "step": { "auth": { "data": { "create_token": "Maak automatisch een nieuw token aan" } + }, + "create_token_external": { + "title": "Accepteer nieuwe token in Hyperion UI" + }, + "user": { + "data": { + "host": "Host", + "port": "Poort" + } } } } diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index 537d310b0a7861..97673069054f1d 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -5,6 +5,7 @@ "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd" }, "error": { + "invalid_auth": "Ongeldige authenticatie", "send_verification_code": "Kan verificatiecode niet verzenden", "validate_verification_code": "Kan uw verificatiecode niet verifi\u00ebren, kies een vertrouwensapparaat en start de verificatie opnieuw" }, @@ -25,6 +26,7 @@ "user": { "data": { "password": "Wachtwoord", + "username": "E-mail", "with_family": "Met gezin" }, "description": "Voer uw gegevens in", diff --git a/homeassistant/components/keenetic_ndms2/translations/nl.json b/homeassistant/components/keenetic_ndms2/translations/nl.json new file mode 100644 index 00000000000000..f422e2641f6cbf --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/nl.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam" + } + } + } + }, + "options": { + "step": { + "user": { + "data": { + "interfaces": "Kies interfaces om te scannen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/nl.json b/homeassistant/components/kmtronic/translations/nl.json new file mode 100644 index 00000000000000..8ad15260b0de5c --- /dev/null +++ b/homeassistant/components/kmtronic/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/ru.json b/homeassistant/components/kmtronic/translations/ru.json new file mode 100644 index 00000000000000..9e0db9fcf94dd8 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/ru.json @@ -0,0 +1,21 @@ +{ + "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.", + "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", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/zh-Hant.json b/homeassistant/components/kmtronic/translations/zh-Hant.json new file mode 100644 index 00000000000000..cad7d736a9d26c --- /dev/null +++ b/homeassistant/components/kmtronic/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/nl.json b/homeassistant/components/kulersky/translations/nl.json new file mode 100644 index 00000000000000..d11896014fd2c6 --- /dev/null +++ b/homeassistant/components/kulersky/translations/nl.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "step": { + "confirm": { + "description": "Wil je beginnen met instellen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/nl.json b/homeassistant/components/life360/translations/nl.json index c3b667722d0dc3..612b0d5c4f707b 100644 --- a/homeassistant/components/life360/translations/nl.json +++ b/homeassistant/components/life360/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "create_entry": { "default": "Om geavanceerde opties in te stellen, zie [Life360 documentatie]({docs_url})." @@ -9,7 +10,8 @@ "error": { "already_configured": "Account is al geconfigureerd", "invalid_auth": "Ongeldige authenticatie", - "invalid_username": "Ongeldige gebruikersnaam" + "invalid_username": "Ongeldige gebruikersnaam", + "unknown": "Onverwachte fout" }, "step": { "user": { diff --git a/homeassistant/components/litejet/translations/et.json b/homeassistant/components/litejet/translations/et.json new file mode 100644 index 00000000000000..6e50b5dcdf36be --- /dev/null +++ b/homeassistant/components/litejet/translations/et.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "error": { + "open_failed": "valitud jadaporti ei saa avada." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "\u00dchenda LiteJeti RS232-2 port arvutiga ja sisesta jadapordi seadme tee.\n\nLiteJet MCP peab olema konfigureeritud: 19200 boodi, 8 andmebitti, 1 stopp bitt, paarsus puudub ja edastada \"CR\" p\u00e4rast iga vastust.", + "title": "Loo \u00fchendus LiteJetiga" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/nl.json b/homeassistant/components/litejet/translations/nl.json new file mode 100644 index 00000000000000..f16f25a3987326 --- /dev/null +++ b/homeassistant/components/litejet/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "open_failed": "Kan de opgegeven seri\u00eble poort niet openen." + }, + "step": { + "user": { + "data": { + "port": "Poort" + }, + "title": "Maak verbinding met LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/nl.json b/homeassistant/components/litterrobot/translations/nl.json new file mode 100644 index 00000000000000..50b4c3f2fe6e24 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/nl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/ru.json b/homeassistant/components/litterrobot/translations/ru.json new file mode 100644 index 00000000000000..3f4677a050e6df --- /dev/null +++ b/homeassistant/components/litterrobot/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." + }, + "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." + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/zh-Hant.json b/homeassistant/components/litterrobot/translations/zh-Hant.json new file mode 100644 index 00000000000000..d232b491b68e52 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/nl.json b/homeassistant/components/local_ip/translations/nl.json index ba75a9b2a4db8c..57547adedd85c9 100644 --- a/homeassistant/components/local_ip/translations/nl.json +++ b/homeassistant/components/local_ip/translations/nl.json @@ -8,6 +8,7 @@ "data": { "name": "Sensor Naam" }, + "description": "Wil je beginnen met instellen?", "title": "Lokaal IP-adres" } } diff --git a/homeassistant/components/logi_circle/translations/nl.json b/homeassistant/components/logi_circle/translations/nl.json index b521af1f969a2a..36970feb48b560 100644 --- a/homeassistant/components/logi_circle/translations/nl.json +++ b/homeassistant/components/logi_circle/translations/nl.json @@ -3,9 +3,11 @@ "abort": { "already_configured": "Account is al geconfigureerd", "external_error": "Uitzondering opgetreden uit een andere stroom.", - "external_setup": "Logi Circle is met succes geconfigureerd vanuit een andere stroom." + "external_setup": "Logi Circle is met succes geconfigureerd vanuit een andere stroom.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." }, "error": { + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "follow_link": "Volg de link en authenticeer voordat u op Verzenden drukt.", "invalid_auth": "Ongeldige authenticatie" }, diff --git a/homeassistant/components/luftdaten/translations/nl.json b/homeassistant/components/luftdaten/translations/nl.json index b3bdf2442b857d..dc913232e8cc4f 100644 --- a/homeassistant/components/luftdaten/translations/nl.json +++ b/homeassistant/components/luftdaten/translations/nl.json @@ -2,6 +2,7 @@ "config": { "error": { "already_configured": "Service is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "invalid_sensor": "Sensor niet beschikbaar of ongeldig" }, "step": { diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index 8e48dea075d5d4..17e6fc47fd8966 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -7,10 +7,49 @@ "error": { "cannot_connect": "Kon niet verbinden" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { "description": "Kan bridge (host: {host} ) niet instellen, ge\u00efmporteerd uit configuration.yaml." + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Voer het IP-adres van het apparaat in." } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Eerste knop", + "button_2": "Tweede knop", + "button_3": "Derde knop", + "button_4": "Vierde knop", + "close_1": "Sluit 1", + "close_2": "Sluit 2", + "close_3": "Sluit 3", + "close_4": "Sluit 4", + "close_all": "Sluit alles", + "group_1_button_1": "Eerste Groep eerste knop", + "group_1_button_2": "Eerste Groep tweede knop", + "group_2_button_1": "Tweede Groep eerste knop", + "group_2_button_2": "Tweede Groep tweede knop", + "off": "Uit", + "on": "Aan", + "open_1": "Open 1", + "open_2": "Open 2", + "open_3": "Open 3", + "open_4": "Open 4", + "stop": "Stop (favoriet)", + "stop_1": "Stop 1", + "stop_2": "Stop 2", + "stop_3": "Stop 3", + "stop_4": "Stop 4" + }, + "trigger_type": { + "press": "\" {subtype} \" ingedrukt", + "release": "\"{subtype}\" losgelaten" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/nl.json b/homeassistant/components/lyric/translations/nl.json new file mode 100644 index 00000000000000..d490acb1b599fd --- /dev/null +++ b/homeassistant/components/lyric/translations/nl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index c820f481b9d189..86f1e656e519dd 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -9,6 +9,21 @@ "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" + }, + "step": { + "reauth": { + "data": { + "email": "E-mail", + "password": "Wachtwoord" + } + }, + "user": { + "data": { + "email": "E-mail", + "password": "Wachtwoord", + "region": "Regio" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index 27dfb56f8d78d3..61925da4cd3df4 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -5,6 +5,9 @@ "unknown": "Onbekende fout: probeer het later nog eens" }, "step": { + "cities": { + "title": "M\u00e9t\u00e9o-France" + }, "user": { "data": { "city": "Stad" diff --git a/homeassistant/components/mill/translations/nl.json b/homeassistant/components/mill/translations/nl.json index 4699b6fb733552..fff0a8232e4017 100644 --- a/homeassistant/components/mill/translations/nl.json +++ b/homeassistant/components/mill/translations/nl.json @@ -3,10 +3,13 @@ "abort": { "already_configured": "Account is al geconfigureerd" }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, "step": { "user": { "data": { - "password": "Password", + "password": "Wachtwoord", "username": "Gebruikersnaam" } } diff --git a/homeassistant/components/motion_blinds/translations/nl.json b/homeassistant/components/motion_blinds/translations/nl.json new file mode 100644 index 00000000000000..01cb117bb5b84e --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "connection_error": "Kan geen verbinding maken" + }, + "error": { + "discovery_error": "Kan geen Motion Gateway vinden" + }, + "step": { + "connect": { + "data": { + "api_key": "API-sleutel" + }, + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "IP-adres" + }, + "title": "Selecteer de Motion Gateway waarmee u verbinding wilt maken" + }, + "user": { + "data": { + "api_key": "API-sleutel", + "host": "IP-adres" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/nl.json b/homeassistant/components/mysensors/translations/nl.json index ebbcbf9a36e404..e41f67c7730e11 100644 --- a/homeassistant/components/mysensors/translations/nl.json +++ b/homeassistant/components/mysensors/translations/nl.json @@ -1,6 +1,16 @@ { "config": { "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", + "duplicate_topic": "Topic is al in gebruik", + "invalid_auth": "Ongeldige authenticatie", + "invalid_device": "Ongeldig apparaat", + "invalid_ip": "Ongeldig IP-adres", + "invalid_port": "Ongeldig poortnummer", + "invalid_publish_topic": "Ongeldig publiceer topic", + "invalid_serial": "Ongeldige seri\u00eble poort", + "invalid_version": "Ongeldige MySensors-versie", "not_a_number": "Voer een nummer in", "port_out_of_range": "Poortnummer moet minimaal 1 en maximaal 65535 zijn", "same_topic": "De topics abonneren en publiceren zijn hetzelfde", diff --git a/homeassistant/components/neato/translations/nl.json b/homeassistant/components/neato/translations/nl.json index 26e5a647b1d8c5..563e6500c16a7f 100644 --- a/homeassistant/components/neato/translations/nl.json +++ b/homeassistant/components/neato/translations/nl.json @@ -2,7 +2,11 @@ "config": { "abort": { "already_configured": "Al geconfigureerd", - "invalid_auth": "Ongeldige authenticatie" + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "invalid_auth": "Ongeldige authenticatie", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "reauth_successful": "Herauthenticatie was succesvol" }, "create_entry": { "default": "Zie [Neato-documentatie] ({docs_url})." @@ -12,6 +16,12 @@ "unknown": "Onverwachte fout" }, "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + }, + "reauth_confirm": { + "title": "Wil je beginnen met instellen?" + }, "user": { "data": { "password": "Wachtwoord", @@ -22,5 +32,6 @@ "title": "Neato-account info" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/nl.json b/homeassistant/components/nest/translations/nl.json index 931b8aa770e74f..387b1effcb0014 100644 --- a/homeassistant/components/nest/translations/nl.json +++ b/homeassistant/components/nest/translations/nl.json @@ -2,10 +2,19 @@ "config": { "abort": { "authorize_url_fail": "Onbekende fout bij het genereren van een autoriseer-URL.", - "authorize_url_timeout": "Toestemming voor het genereren van autoriseer-url." + "authorize_url_timeout": "Toestemming voor het genereren van autoriseer-url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "reauth_successful": "Herauthenticatie was succesvol", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", + "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" }, "error": { "internal_error": "Interne foutvalidatiecode", + "invalid_pin": "Ongeldige PIN-code", "timeout": "Time-out validatie van code", "unknown": "Onbekende foutvalidatiecode" }, @@ -23,6 +32,13 @@ }, "description": "Als je je Nest-account wilt koppelen, [autoriseer je account] ( {url} ). \n\nNa autorisatie, kopieer en plak de voorziene pincode hieronder.", "title": "Koppel Nest-account" + }, + "pick_implementation": { + "title": "Kies een authenticatie methode" + }, + "reauth_confirm": { + "description": "De Nest-integratie moet je account opnieuw verifi\u00ebren", + "title": "Verifieer de integratie opnieuw" } } }, diff --git a/homeassistant/components/nuki/translations/nl.json b/homeassistant/components/nuki/translations/nl.json new file mode 100644 index 00000000000000..4e220dbe78d791 --- /dev/null +++ b/homeassistant/components/nuki/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Poort", + "token": "Toegangstoken" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/nl.json b/homeassistant/components/ondilo_ico/translations/nl.json new file mode 100644 index 00000000000000..8a91dff086f642 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/nl.json b/homeassistant/components/onewire/translations/nl.json index ae155ccf2c23d2..77ac79c15975bf 100644 --- a/homeassistant/components/onewire/translations/nl.json +++ b/homeassistant/components/onewire/translations/nl.json @@ -4,10 +4,15 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_path": "Directory niet gevonden." }, "step": { "owserver": { + "data": { + "host": "Host", + "port": "Poort" + }, "title": "Owserver-details instellen" }, "user": { diff --git a/homeassistant/components/opentherm_gw/translations/nl.json b/homeassistant/components/opentherm_gw/translations/nl.json index 7c9c89381e8e6e..e832e790c1ec51 100644 --- a/homeassistant/components/opentherm_gw/translations/nl.json +++ b/homeassistant/components/opentherm_gw/translations/nl.json @@ -2,6 +2,7 @@ "config": { "error": { "already_configured": "Gateway al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "id_exists": "Gateway id bestaat al" }, "step": { diff --git a/homeassistant/components/ovo_energy/translations/nl.json b/homeassistant/components/ovo_energy/translations/nl.json index daa12f9e569a39..7a2b5b757bbc37 100644 --- a/homeassistant/components/ovo_energy/translations/nl.json +++ b/homeassistant/components/ovo_energy/translations/nl.json @@ -2,10 +2,16 @@ "config": { "error": { "already_configured": "Account is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { + "data": { + "password": "Wachtwoord" + }, + "description": "Authenticatie mislukt voor OVO Energy. Voer uw huidige inloggegevens in.", "title": "Opnieuw verifi\u00ebren" }, "user": { diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index 4497654e7f37be..80ef72a061e2f4 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -1,8 +1,23 @@ { "config": { "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "mqtt_required": "De [%%] integratie is niet ingesteld", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + }, + "step": { + "hassio_confirm": { + "title": "OpenZWave integratie instellen met de OpenZWave add-on" + }, + "install_addon": { + "title": "De OpenZWave add-on installatie is gestart" + }, + "start_addon": { + "data": { + "usb_path": "USB-apparaatpad" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/nl.json b/homeassistant/components/philips_js/translations/nl.json new file mode 100644 index 00000000000000..23cd7e47043f32 --- /dev/null +++ b/homeassistant/components/philips_js/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": { + "api_version": "API Versie", + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 6545a659427805..c0a9d1e04fba45 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -1,16 +1,42 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "already_configured": "Account is al geconfigureerd", + "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." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." }, + "error": { + "no_auth_token": "U moet een verificatie token toevoegen" + }, "step": { + "api_method": { + "data": { + "token": "Plak hier de verificatie-token", + "use_webhook": "Webhook gebruiken" + }, + "title": "Selecteer API-methode" + }, "user": { + "data": { + "device_name": "Geef uw apparaat een naam", + "device_type": "Type Plaato-apparaat" + }, "description": "Weet u zeker dat u de Plaato-airlock wilt instellen?", "title": "Stel de Plaato Webhook in" } } + }, + "options": { + "step": { + "user": { + "title": "Opties voor Plaato" + }, + "webhook": { + "title": "Opties voor Plaato Airlock" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index 00c2b30c490ee7..6c89b0b8d5fe5f 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -4,6 +4,7 @@ "all_configured": "Alle gekoppelde servers zijn al geconfigureerd", "already_configured": "Deze Plex-server is al geconfigureerd", "already_in_progress": "Plex wordt geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol", "token_request_timeout": "Time-out verkrijgen van token", "unknown": "Mislukt om onbekende reden" }, diff --git a/homeassistant/components/point/translations/nl.json b/homeassistant/components/point/translations/nl.json index a257ba3e1115db..94763a412a0e72 100644 --- a/homeassistant/components/point/translations/nl.json +++ b/homeassistant/components/point/translations/nl.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Onbekende fout bij het genereren van een autoriseer-URL.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "external_setup": "Punt succesvol geconfigureerd vanuit een andere stroom.", - "no_flows": "U moet Point configureren voordat u zich ermee kunt verifi\u00ebren. [Gelieve de instructies te lezen](https://www.home-assistant.io/components/nest/)." + "no_flows": "U moet Point configureren voordat u zich ermee kunt verifi\u00ebren. [Gelieve de instructies te lezen](https://www.home-assistant.io/components/nest/).", + "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "create_entry": { "default": "Succesvol geverifieerd met Minut voor uw Point appara(a)t(en)" diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 7482a0bbe7c64d..46fc915d7bd4a6 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "email": "E-mail", "password": "Wachtwoord" } } diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index 779da2086ebccc..c4ae2616f46e5f 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "De powerwall is al geconfigureerd" + "already_configured": "De powerwall is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", "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." }, @@ -12,7 +14,8 @@ "step": { "user": { "data": { - "ip_address": "IP-adres" + "ip_address": "IP-adres", + "password": "Wachtwoord" }, "title": "Maak verbinding met de powerwall" } diff --git a/homeassistant/components/profiler/translations/nl.json b/homeassistant/components/profiler/translations/nl.json index 703ac8614c49d4..8690611b1c90ff 100644 --- a/homeassistant/components/profiler/translations/nl.json +++ b/homeassistant/components/profiler/translations/nl.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + }, + "step": { + "user": { + "description": "Wil je beginnen met instellen?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/nl.json b/homeassistant/components/progettihwsw/translations/nl.json index ba10aee5ea20cc..7810a8018a42ed 100644 --- a/homeassistant/components/progettihwsw/translations/nl.json +++ b/homeassistant/components/progettihwsw/translations/nl.json @@ -9,6 +9,24 @@ }, "step": { "relay_modes": { + "data": { + "relay_1": "Relais 1", + "relay_10": "Relais 10", + "relay_11": "Relais 11", + "relay_12": "Relais 12", + "relay_13": "Relais 13", + "relay_14": "Relais 14", + "relay_15": "Relais 15", + "relay_16": "Relais 16", + "relay_2": "Relais 2", + "relay_3": "Relais 3", + "relay_4": "Relais 4", + "relay_5": "Relais 5", + "relay_6": "Relais 6", + "relay_7": "Relais 7", + "relay_8": "Relais 8", + "relay_9": "Relais 9" + }, "title": "Stel relais in" }, "user": { diff --git a/homeassistant/components/ps4/translations/nl.json b/homeassistant/components/ps4/translations/nl.json index d86240b2c0a273..326917e49608b5 100644 --- a/homeassistant/components/ps4/translations/nl.json +++ b/homeassistant/components/ps4/translations/nl.json @@ -8,6 +8,7 @@ "port_997_bind_error": "Kon niet binden aan poort 997. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor aanvullende informatie." }, "error": { + "cannot_connect": "Kan geen verbinding maken", "credential_timeout": "Time-out van inlog service. Druk op Submit om opnieuw te starten.", "login_failed": "Kan niet koppelen met PlayStation 4. Controleer of de pincode juist is.", "no_ipaddress": "Voer het IP-adres in van de PlayStation 4 die je wilt configureren." diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index adaa8cb5f308b4..02411ea999f0c3 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Deze RainMachine controller is al geconfigureerd." }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, "step": { "user": { "data": { @@ -13,5 +16,12 @@ "title": "Vul uw gegevens in" } } + }, + "options": { + "step": { + "init": { + "title": "Configureer RainMachine" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/nl.json b/homeassistant/components/recollect_waste/translations/nl.json new file mode 100644 index 00000000000000..6ce4a8f8a9f342 --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "invalid_place_or_service_id": "Ongeldige plaats of service-ID" + }, + "step": { + "user": { + "data": { + "place_id": "Plaats-ID", + "service_id": "Service-ID" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Configureer Recollect Waste" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 0dc56206f668f8..0b6e8997b18299 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -10,10 +10,23 @@ "step": { "setup_network": { "data": { + "host": "Host", "port": "Poort" }, "title": "Selecteer verbindingsadres" }, + "setup_serial": { + "data": { + "device": "Selecteer apparaat" + }, + "title": "Apparaat" + }, + "setup_serial_manual_path": { + "data": { + "device": "USB-apparaatpad" + }, + "title": "Pad" + }, "user": { "data": { "type": "Verbindingstype" @@ -25,7 +38,19 @@ "options": { "error": { "already_configured_device": "Apparaat is al geconfigureerd", + "invalid_event_code": "Ongeldige gebeurteniscode", "unknown": "Onverwachte fout" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Schakel automatisch toevoegen in", + "debug": "Foutopsporing inschakelen" + } + }, + "set_device_options": { + "title": "Configureer apparaatopties" + } } } } \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/zh-Hant.json b/homeassistant/components/rituals_perfume_genie/translations/zh-Hant.json new file mode 100644 index 00000000000000..c91a500edd81fb --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + }, + "title": "\u9023\u7dda\u81f3 Rituals \u5e33\u865f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json index 529b01b64c2d94..d892d2c78d2f2e 100644 --- a/homeassistant/components/roku/translations/nl.json +++ b/homeassistant/components/roku/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Roku-apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json index 754ff2e51a8753..0177adaea1ffe0 100644 --- a/homeassistant/components/roomba/translations/nl.json +++ b/homeassistant/components/roomba/translations/nl.json @@ -28,6 +28,12 @@ "description": "Het wachtwoord kon niet automatisch van het apparaat worden opgehaald. Volg de stappen zoals beschreven in de documentatie op: {auth_help_url}", "title": "Voer wachtwoord in" }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + } + }, "user": { "data": { "blid": "BLID", diff --git a/homeassistant/components/ruckus_unleashed/translations/nl.json b/homeassistant/components/ruckus_unleashed/translations/nl.json index 7482a0bbe7c64d..0569c39321a24b 100644 --- a/homeassistant/components/ruckus_unleashed/translations/nl.json +++ b/homeassistant/components/ruckus_unleashed/translations/nl.json @@ -4,12 +4,15 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "password": "Wachtwoord" + "host": "Host", + "password": "Wachtwoord", + "username": "Gebruikersnaam" } } } diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json index 75a2d2771d668b..7084a972e291d5 100644 --- a/homeassistant/components/shelly/translations/nl.json +++ b/homeassistant/components/shelly/translations/nl.json @@ -25,5 +25,18 @@ } } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Knop", + "button1": "Eerste knop", + "button2": "Tweede knop", + "button3": "Derde knop" + }, + "trigger_type": { + "double": "{subtype} dubbel geklikt", + "single_long": "{subtype} een keer geklikt en daarna lang geklikt", + "triple": "{subtype} driemaal geklikt" + } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index 86f4a40c6f9765..ebcc16dafacfbc 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -3,7 +3,10 @@ "abort": { "already_configured_device": "Apparaat is al geconfigureerd", "already_configured_local_device": "Lokale apparaten zijn al geconfigureerd. Verwijder deze eerst voordat u een cloudapparaat configureert.", - "cannot_connect": "Kan geen verbinding maken" + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "cannot_connect": "Kan geen verbinding maken", + "invalid_mdns": "Niet-ondersteund apparaat voor de Smappee-integratie.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." }, "step": { "local": { @@ -11,6 +14,9 @@ "host": "Host" }, "description": "Voer de host in om de lokale Smappee-integratie te starten" + }, + "pick_implementation": { + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/smarthab/translations/nl.json b/homeassistant/components/smarthab/translations/nl.json index 9dabac8aa55d9a..7f5fc7fe27c64d 100644 --- a/homeassistant/components/smarthab/translations/nl.json +++ b/homeassistant/components/smarthab/translations/nl.json @@ -1,12 +1,14 @@ { "config": { "error": { + "invalid_auth": "Ongeldige authenticatie", "service": "Fout bij het bereiken van SmartHab. De service is mogelijk uitgevallen. Controleer uw verbinding.", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { + "email": "E-mail", "password": "Wachtwoord" } } diff --git a/homeassistant/components/smarttub/translations/nl.json b/homeassistant/components/smarttub/translations/nl.json new file mode 100644 index 00000000000000..a5f20db8a32a99 --- /dev/null +++ b/homeassistant/components/smarttub/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Wachtwoord" + }, + "title": "Inloggen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/nl.json b/homeassistant/components/solaredge/translations/nl.json index 4b468218410628..3fe28971f29e81 100644 --- a/homeassistant/components/solaredge/translations/nl.json +++ b/homeassistant/components/solaredge/translations/nl.json @@ -1,10 +1,15 @@ { "config": { "abort": { + "already_configured": "Apparaat is al geconfigureerd", "site_exists": "Deze site_id is al geconfigureerd" }, "error": { - "site_exists": "Deze site_id is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "could_not_connect": "Kon geen verbinding maken met de solaredge API", + "invalid_api_key": "Ongeldige API-sleutel", + "site_exists": "Deze site_id is al geconfigureerd", + "site_not_active": "De site is niet actief" }, "step": { "user": { diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index 208c032227aa17..a63320919c6dcf 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -1,5 +1,40 @@ { "config": { - "flow_title": "Somfy MyLink {mac} ( {ip} )" - } + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "flow_title": "Somfy MyLink {mac} ( {ip} )", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Poort", + "system_id": "Systeem-ID" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Kan geen verbinding maken" + }, + "step": { + "entity_config": { + "description": "Configureer opties voor `{entity_id}`", + "title": "Entiteit configureren" + }, + "init": { + "data": { + "entity_id": "Configureer een specifieke entiteit." + }, + "title": "Configureer MyLink-opties" + } + } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/nl.json b/homeassistant/components/sonarr/translations/nl.json index 58db7f57dd4f28..08ef9bb2ecea61 100644 --- a/homeassistant/components/sonarr/translations/nl.json +++ b/homeassistant/components/sonarr/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Service is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol", "unknown": "Onverwachte fout" }, "error": { @@ -9,6 +10,10 @@ "invalid_auth": "Ongeldige authenticatie" }, "step": { + "reauth_confirm": { + "description": "De Sonarr-integratie moet handmatig opnieuw worden geverifieerd met de Sonarr-API die wordt gehost op: {host}", + "title": "Verifieer de integratie opnieuw" + }, "user": { "data": { "api_key": "API-sleutel", diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json new file mode 100644 index 00000000000000..cd06c36b66158c --- /dev/null +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_account": "Account-ID moet een 9-cijferig nummer zijn", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + }, + "title": "SRP Energy" +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/nl.json b/homeassistant/components/subaru/translations/nl.json new file mode 100644 index 00000000000000..5a9bd4119ff09a --- /dev/null +++ b/homeassistant/components/subaru/translations/nl.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken" + }, + "error": { + "bad_pin_format": "De pincode moet uit 4 cijfers bestaan", + "cannot_connect": "Kan geen verbinding maken", + "incorrect_pin": "Onjuiste PIN", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "title": "Subaru Starlink Configuratie" + }, + "user": { + "data": { + "country": "Selecteer land", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "title": "Subaru Starlink-configuratie" + } + } + }, + "options": { + "step": { + "init": { + "title": "Subaru Starlink-opties" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/zh-Hant.json b/homeassistant/components/subaru/translations/zh-Hant.json new file mode 100644 index 00000000000000..22eaa589fe2e79 --- /dev/null +++ b/homeassistant/components/subaru/translations/zh-Hant.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "error": { + "bad_pin_format": "PIN \u78bc\u61c9\u8a72\u70ba 4 \u4f4d\u6578\u5b57", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "incorrect_pin": "PIN \u78bc\u932f\u8aa4", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "\u8acb\u8f38\u5165 MySubaru PIN \u78bc\n\u6ce8\u610f\uff1a\u6240\u4ee5\u5e33\u865f\u5167\u8eca\u8f1b\u90fd\u5fc5\u9808\u4f7f\u7528\u76f8\u540c PIN \u78bc", + "title": "Subaru Starlink \u8a2d\u5b9a" + }, + "user": { + "data": { + "country": "\u9078\u64c7\u570b\u5bb6", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8acb\u8f38\u5165 MySubaru \u8a8d\u8b49\n\u6ce8\u610f\uff1a\u555f\u59cb\u8a2d\u5b9a\u5927\u7d04\u9700\u8981 30 \u79d2", + "title": "Subaru Starlink \u8a2d\u5b9a" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "\u958b\u555f\u8eca\u8f1b\u8cc7\u6599\u4e0b\u8f09" + }, + "description": "\u958b\u555f\u5f8c\uff0c\u5c07\u6703\u6bcf 2 \u5c0f\u6642\u50b3\u9001\u9060\u7aef\u547d\u4ee4\u81f3\u8eca\u8f1b\u4ee5\u7372\u5f97\u6700\u65b0\u50b3\u611f\u5668\u8cc7\u6599\u3002\u5982\u679c\u6c92\u6709\u958b\u555f\uff0c\u50b3\u611f\u5668\u65b0\u8cc7\u6599\u50c5\u6703\u65bc\u8eca\u8f1b\u81ea\u52d5\u63a8\u9001\u8cc7\u6599\u6642\u63a5\u6536\uff08\u901a\u5e38\u70ba\u5f15\u64ce\u7184\u706b\u4e4b\u5f8c\uff09\u3002", + "title": "Subaru Starlink \u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/nl.json b/homeassistant/components/tellduslive/translations/nl.json index b3874dac77ec40..4eb6d40a14267f 100644 --- a/homeassistant/components/tellduslive/translations/nl.json +++ b/homeassistant/components/tellduslive/translations/nl.json @@ -4,7 +4,11 @@ "already_configured": "Service is al geconfigureerd", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie url.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "unknown": "Onbekende fout opgetreden" + "unknown": "Onbekende fout opgetreden", + "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." + }, + "error": { + "invalid_auth": "Ongeldige authenticatie" }, "step": { "auth": { diff --git a/homeassistant/components/tesla/translations/nl.json b/homeassistant/components/tesla/translations/nl.json index 9e79b35165dcd6..f6289de6d9d6d9 100644 --- a/homeassistant/components/tesla/translations/nl.json +++ b/homeassistant/components/tesla/translations/nl.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" + }, "error": { "already_configured": "Account is al geconfigureerd", - "cannot_connect": "Kan geen verbinding maken" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie" }, "step": { "user": { diff --git a/homeassistant/components/tibber/translations/nl.json b/homeassistant/components/tibber/translations/nl.json index 4a89639cf5070a..4a5e518f306625 100644 --- a/homeassistant/components/tibber/translations/nl.json +++ b/homeassistant/components/tibber/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Service is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_access_token": "Ongeldig toegangstoken", "timeout": "Time-out om verbinding te maken met Tibber" }, diff --git a/homeassistant/components/tile/translations/nl.json b/homeassistant/components/tile/translations/nl.json index 26c5726868938a..c160ac631ee0dd 100644 --- a/homeassistant/components/tile/translations/nl.json +++ b/homeassistant/components/tile/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd" }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index 4f63d7d09dacbe..cf77b94d025e88 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -4,7 +4,8 @@ "already_configured": "De geselecteerde overeenkomst is al geconfigureerd.", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie-URL.", "no_agreements": "Dit account heeft geen Toon schermen.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )" + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", + "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index c72b7e368ac0ff..1f4fb5490d1408 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -1,12 +1,16 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd" + "already_configured": "Account al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "invalid_auth": "Ongeldige authenticatie" }, "step": { + "reauth_confirm": { + "title": "Verifieer de integratie opnieuw" + }, "user": { "data": { "password": "Wachtwoord", diff --git a/homeassistant/components/totalconnect/translations/ru.json b/homeassistant/components/totalconnect/translations/ru.json index 054f207b2b0473..0f067541dec673 100644 --- a/homeassistant/components/totalconnect/translations/ru.json +++ b/homeassistant/components/totalconnect/translations/ru.json @@ -1,12 +1,25 @@ { "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." }, "error": { - "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\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.", + "usercode": "\u041a\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438." }, "step": { + "locations": { + "data": { + "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438.", + "title": "\u041a\u043e\u0434\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u043b\u044f \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f" + }, + "reauth_confirm": { + "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 Total Connect", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u044f" + }, "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", diff --git a/homeassistant/components/totalconnect/translations/zh-Hant.json b/homeassistant/components/totalconnect/translations/zh-Hant.json index c20dd4065b6de7..96921baf007dbe 100644 --- a/homeassistant/components/totalconnect/translations/zh-Hant.json +++ b/homeassistant/components/totalconnect/translations/zh-Hant.json @@ -1,12 +1,25 @@ { "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" }, "error": { - "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "usercode": "\u4f7f\u7528\u8005\u4ee3\u78bc\u4e0d\u652f\u63f4\u6b64\u5ea7\u6a19" }, "step": { + "locations": { + "data": { + "location": "\u5ea7\u6a19" + }, + "description": "\u8f38\u5165\u4f7f\u7528\u8005\u65bc\u6b64\u5ea7\u6a19\u4e4b\u4f7f\u7528\u8005\u4ee3\u78bc", + "title": "\u5ea7\u6a19\u4f7f\u7528\u8005\u4ee3\u78bc" + }, + "reauth_confirm": { + "description": "Total Connect \u9700\u8981\u91cd\u65b0\u8a8d\u8b49\u5e33\u865f", + "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" + }, "user": { "data": { "password": "\u5bc6\u78bc", diff --git a/homeassistant/components/traccar/translations/nl.json b/homeassistant/components/traccar/translations/nl.json index 251e16d07638c2..0b4563d69fcfa4 100644 --- a/homeassistant/components/traccar/translations/nl.json +++ b/homeassistant/components/traccar/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Voor het verzenden van gebeurtenissen naar Home Assistant, moet u de webhook-functie in Traccar instellen.\n\nGebruik de volgende URL: ' {webhook_url} '\n\nZie [de documentatie] ({docs_url}) voor meer informatie." diff --git a/homeassistant/components/transmission/translations/nl.json b/homeassistant/components/transmission/translations/nl.json index 8cfa9333ba486c..df9a4590e663b0 100644 --- a/homeassistant/components/transmission/translations/nl.json +++ b/homeassistant/components/transmission/translations/nl.json @@ -5,6 +5,7 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken met host", + "invalid_auth": "Ongeldige authenticatie", "name_exists": "Naam bestaat al" }, "step": { diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 5a0e3691d5b247..46a0228843bdb6 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -2,8 +2,12 @@ "config": { "abort": { "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", "single_instance_allowed": "Al geconfigureerd. Er is maar een configuratie mogelijk." }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, "flow_title": "Tuya-configuratie", "step": { "user": { @@ -19,7 +23,16 @@ } }, "options": { + "abort": { + "cannot_connect": "Kan geen verbinding maken" + }, + "error": { + "dev_not_found": "Apparaat niet gevonden" + }, "step": { + "device": { + "title": "Configureer Tuya Apparaat" + }, "init": { "title": "Configureer Tuya opties" } diff --git a/homeassistant/components/twentemilieu/translations/nl.json b/homeassistant/components/twentemilieu/translations/nl.json index ca5abd7e37ca59..54611aa9ab89f7 100644 --- a/homeassistant/components/twentemilieu/translations/nl.json +++ b/homeassistant/components/twentemilieu/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Locatie is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_address": "Adres niet gevonden in servicegebied Twente Milieu." }, "step": { diff --git a/homeassistant/components/twilio/translations/nl.json b/homeassistant/components/twilio/translations/nl.json index ee97ef4f6cd71a..55db4ef5e48dce 100644 --- a/homeassistant/components/twilio/translations/nl.json +++ b/homeassistant/components/twilio/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u [Webhooks with Twilio] ( {twilio_url} ) instellen. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhoudstype: application / x-www-form-urlencoded \n\n Zie [de documentatie] ( {docs_url} ) voor informatie over het configureren van automatiseringen om binnenkomende gegevens te verwerken." diff --git a/homeassistant/components/twinkly/translations/nl.json b/homeassistant/components/twinkly/translations/nl.json index 861ee57283cfab..97a55150447321 100644 --- a/homeassistant/components/twinkly/translations/nl.json +++ b/homeassistant/components/twinkly/translations/nl.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "device_exists": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 96ffc0c0acede7..7f0baf4a3be8ea 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Controller site is al geconfigureerd", + "configuration_updated": "Configuratie bijgewerkt.", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { diff --git a/homeassistant/components/upcloud/translations/nl.json b/homeassistant/components/upcloud/translations/nl.json index 783032a1da0be5..312b117208c1cd 100644 --- a/homeassistant/components/upcloud/translations/nl.json +++ b/homeassistant/components/upcloud/translations/nl.json @@ -1,12 +1,14 @@ { "config": { "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "Gebruikersnaam" } } } diff --git a/homeassistant/components/vesync/translations/nl.json b/homeassistant/components/vesync/translations/nl.json index 0dc21373c14d8e..36c7f315bcc3a0 100644 --- a/homeassistant/components/vesync/translations/nl.json +++ b/homeassistant/components/vesync/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Al geconfigureerd. Slecht \u00e9\u00e9n configuratie mogelijk." }, + "error": { + "invalid_auth": "Ongeldige authenticatie" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 9841eaa7f508fd..48fd831d61c045 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured_device": "Dit apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken", "updated_entry": "Dit item is al ingesteld, maar de naam en/of opties die zijn gedefinieerd in de configuratie komen niet overeen met de eerder ge\u00efmporteerde configuratie, dus het configuratie-item is dienovereenkomstig bijgewerkt." }, "error": { diff --git a/homeassistant/components/weather/translations/et.json b/homeassistant/components/weather/translations/et.json index f035d37d62ea68..2de9158c085165 100644 --- a/homeassistant/components/weather/translations/et.json +++ b/homeassistant/components/weather/translations/et.json @@ -3,7 +3,7 @@ "_": { "clear-night": "Selge \u00f6\u00f6", "cloudy": "Pilves", - "exceptional": "Erakordne", + "exceptional": "Ohtlikud olud", "fog": "Udu", "hail": "Rahe", "lightning": "\u00c4ikeseline", diff --git a/homeassistant/components/xbox/translations/nl.json b/homeassistant/components/xbox/translations/nl.json new file mode 100644 index 00000000000000..858fd264eaf992 --- /dev/null +++ b/homeassistant/components/xbox/translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index e17b3b572d1988..81f984a5a0583d 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -1,11 +1,19 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang" + }, + "error": { + "invalid_host": "Ongeldige hostnaam of IP-adres, zie https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_mac": "Ongeldig MAC-adres" }, "flow_title": "Xiaomi Aqara Gateway: {name}", "step": { "select": { + "data": { + "select_ip": "IP-adres" + }, "description": "Voer de installatie opnieuw uit als u extra gateways wilt aansluiten", "title": "Selecteer de Xiaomi Aqara Gateway waarmee u verbinding wilt maken" }, diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index eea72c1c4c0206..3ea12e3a465785 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -5,9 +5,18 @@ "already_in_progress": "De configuratiestroom voor dit Xiaomi Miio-apparaat is al bezig." }, "error": { + "cannot_connect": "Kan geen verbinding maken", "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" }, "step": { + "device": { + "data": { + "host": "IP-adres", + "model": "Apparaatmodel (Optioneel)", + "name": "Naam van het apparaat", + "token": "API-token" + } + }, "gateway": { "data": { "host": "IP-adres", diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index 5113128cac55e3..5c5064ac347398 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", + "model": "\u041c\u043e\u0434\u0435\u043b\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "token": "\u0422\u043e\u043a\u0435\u043d API" }, diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 43e3e10df202b2..dce2002faa957b 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "IP \u4f4d\u5740", + "model": "\u88dd\u7f6e\u578b\u865f\uff08\u9078\u9805\uff09", "name": "\u88dd\u7f6e\u540d\u7a31", "token": "API \u5bc6\u9470" }, diff --git a/homeassistant/components/zerproc/translations/nl.json b/homeassistant/components/zerproc/translations/nl.json new file mode 100644 index 00000000000000..d11896014fd2c6 --- /dev/null +++ b/homeassistant/components/zerproc/translations/nl.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "step": { + "confirm": { + "description": "Wil je beginnen met instellen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/nl.json b/homeassistant/components/zoneminder/translations/nl.json index ebfd26329dcdb2..f4f071d9097fc6 100644 --- a/homeassistant/components/zoneminder/translations/nl.json +++ b/homeassistant/components/zoneminder/translations/nl.json @@ -12,7 +12,8 @@ "error": { "auth_fail": "Gebruikersnaam of wachtwoord is onjuist.", "cannot_connect": "Kon niet verbinden", - "connection_error": "Kan geen verbinding maken met een ZoneMinder-server." + "connection_error": "Kan geen verbinding maken met een ZoneMinder-server.", + "invalid_auth": "Ongeldige authenticatie" }, "flow_title": "ZoneMinder", "step": { diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json index 93ec53a644e0fa..6806b5072c1aaf 100644 --- a/homeassistant/components/zwave_js/translations/ca.json +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -20,6 +20,13 @@ "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts." }, "step": { + "configure_addon": { + "data": { + "network_key": "Clau de xarxa", + "usb_path": "Ruta del port USB del dispositiu" + }, + "title": "Introdueix la configuraci\u00f3 del complement Z-Wave JS" + }, "hassio_confirm": { "title": "Configura la integraci\u00f3 Z-Wave JS mitjan\u00e7ant el complement Z-Wave JS" }, @@ -38,13 +45,6 @@ "description": "Vols utilitzar el complement Supervisor de Z-Wave JS?", "title": "Selecciona el m\u00e8tode de connexi\u00f3" }, - "start_addon": { - "data": { - "network_key": "Clau de xarxa", - "usb_path": "Ruta del port USB del dispositiu" - }, - "title": "Introdueix la configuraci\u00f3 del complement Z-Wave JS" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/cs.json b/homeassistant/components/zwave_js/translations/cs.json index 96073b579ed688..57e7a6b74db5dc 100644 --- a/homeassistant/components/zwave_js/translations/cs.json +++ b/homeassistant/components/zwave_js/translations/cs.json @@ -10,14 +10,14 @@ "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" }, "step": { - "manual": { + "configure_addon": { "data": { - "url": "URL" + "usb_path": "Cesta k USB za\u0159\u00edzen\u00ed" } }, - "start_addon": { + "manual": { "data": { - "usb_path": "Cesta k USB za\u0159\u00edzen\u00ed" + "url": "URL" } }, "user": { diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index d8bdafcefee1b5..5be980d52cb07a 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -49,8 +49,13 @@ }, "start_addon": { "title": "The Z-Wave JS add-on is starting." + }, + "user": { + "data": { + "url": "URL" + } } } }, "title": "Z-Wave JS" -} +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index 69a638e1f40983..32d7a6d2e6d808 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -20,6 +20,13 @@ "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos." }, "step": { + "configure_addon": { + "data": { + "network_key": "Clave de red", + "usb_path": "Ruta del dispositivo USB" + }, + "title": "Introduzca la configuraci\u00f3n del complemento Z-Wave JS" + }, "hassio_confirm": { "title": "Configurar la integraci\u00f3n de Z-Wave JS con el complemento Z-Wave JS" }, @@ -38,13 +45,6 @@ "description": "\u00bfQuieres utilizar el complemento Z-Wave JS Supervisor?", "title": "Selecciona el m\u00e9todo de conexi\u00f3n" }, - "start_addon": { - "data": { - "network_key": "Clave de red", - "usb_path": "Ruta del dispositivo USB" - }, - "title": "Introduzca la configuraci\u00f3n del complemento Z-Wave JS" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json index 7a7aadfb84152e..d51507b616f0b7 100644 --- a/homeassistant/components/zwave_js/translations/et.json +++ b/homeassistant/components/zwave_js/translations/et.json @@ -20,6 +20,13 @@ "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit." }, "step": { + "configure_addon": { + "data": { + "network_key": "V\u00f5rgu v\u00f5ti", + "usb_path": "USB-seadme asukoha rada" + }, + "title": "Sisesta Z-Wave JS lisandmooduli seaded" + }, "hassio_confirm": { "title": "Seadista Z-Wave JS-i sidumine Z-Wave JS-i lisandmooduliga" }, @@ -38,13 +45,6 @@ "description": "Kas soovid kasutada Z-Wave JSi halduri lisandmoodulit?", "title": "Vali \u00fchendusviis" }, - "start_addon": { - "data": { - "network_key": "V\u00f5rgu v\u00f5ti", - "usb_path": "USB-seadme asukoha rada" - }, - "title": "Sisesta Z-Wave JS lisandmooduli seaded" - }, "user": { "data": { "url": "" diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 2196ed0259e05f..040e3997ac17ea 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -20,6 +20,13 @@ "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes." }, "step": { + "configure_addon": { + "data": { + "network_key": "Cl\u00e9 r\u00e9seau", + "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" + }, + "title": "Entrez la configuration du module compl\u00e9mentaire Z-Wave JS" + }, "hassio_confirm": { "title": "Configurer l'int\u00e9gration Z-Wave JS avec le module compl\u00e9mentaire Z-Wave JS" }, @@ -38,13 +45,6 @@ "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, - "start_addon": { - "data": { - "network_key": "Cl\u00e9 r\u00e9seau", - "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" - }, - "title": "Entrez la configuration du module compl\u00e9mentaire Z-Wave JS" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index fc76b309a34ebc..5f0868a3f743b4 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -20,6 +20,13 @@ "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti." }, "step": { + "configure_addon": { + "data": { + "network_key": "Chiave di rete", + "usb_path": "Percorso del dispositivo USB" + }, + "title": "Accedi alla configurazione del componente aggiuntivo Z-Wave JS" + }, "hassio_confirm": { "title": "Configura l'integrazione di Z-Wave JS con il componente aggiuntivo Z-Wave JS" }, @@ -38,13 +45,6 @@ "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS Supervisor?", "title": "Seleziona il metodo di connessione" }, - "start_addon": { - "data": { - "network_key": "Chiave di rete", - "usb_path": "Percorso del dispositivo USB" - }, - "title": "Accedi alla configurazione del componente aggiuntivo Z-Wave JS" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json index 283b0aa17b6ac8..9c86a064151093 100644 --- a/homeassistant/components/zwave_js/translations/ko.json +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -10,14 +10,14 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { - "manual": { + "configure_addon": { "data": { - "url": "URL \uc8fc\uc18c" + "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" } }, - "start_addon": { + "manual": { "data": { - "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" + "url": "URL \uc8fc\uc18c" } }, "user": { diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index 74b4db46de1ab3..7f46c02ece5ecc 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -20,6 +20,13 @@ "install_addon": "Een ogenblik geduld terwijl de installatie van de Z-Wave JS add-on is voltooid. Dit kan enkele minuten duren." }, "step": { + "configure_addon": { + "data": { + "network_key": "Netwerksleutel", + "usb_path": "USB-apparaatpad" + }, + "title": "Voer de Z-Wave JS add-on configuratie in" + }, "hassio_confirm": { "title": "Z-Wave JS integratie instellen met de Z-Wave JS add-on" }, @@ -38,13 +45,6 @@ "description": "Wilt u de Z-Wave JS Supervisor add-on gebruiken?", "title": "Selecteer verbindingsmethode" }, - "start_addon": { - "data": { - "network_key": "Netwerksleutel", - "usb_path": "USB-apparaatpad" - }, - "title": "Voer de Z-Wave JS add-on configuratie in" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index e16425b59ec898..b724fb34e48385 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -20,6 +20,13 @@ "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter." }, "step": { + "configure_addon": { + "data": { + "network_key": "Nettverksn\u00f8kkel", + "usb_path": "USB enhetsbane" + }, + "title": "Angi konfigurasjon for Z-Wave JS-tillegg" + }, "hassio_confirm": { "title": "Sett opp Z-Wave JS-integrasjon med Z-Wave JS-tillegg" }, @@ -38,13 +45,6 @@ "description": "Vil du bruke Z-Wave JS Supervisor-tillegg?", "title": "Velg tilkoblingsmetode" }, - "start_addon": { - "data": { - "network_key": "Nettverksn\u00f8kkel", - "usb_path": "USB enhetsbane" - }, - "title": "Angi konfigurasjon for Z-Wave JS-tillegg" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json index 47e263c610192f..b139b0dacc6657 100644 --- a/homeassistant/components/zwave_js/translations/pl.json +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -20,6 +20,13 @@ "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut." }, "step": { + "configure_addon": { + "data": { + "network_key": "Klucz sieci", + "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" + }, + "title": "Wprowad\u017a konfiguracj\u0119 dodatku Z-Wave JS" + }, "hassio_confirm": { "title": "Skonfiguruj integracj\u0119 Z-Wave JS z dodatkiem Z-Wave JS" }, @@ -38,13 +45,6 @@ "description": "Czy chcesz skorzysta\u0107 z dodatku Z-Wave JS Supervisor?", "title": "Wybierz metod\u0119 po\u0142\u0105czenia" }, - "start_addon": { - "data": { - "network_key": "Klucz sieci", - "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" - }, - "title": "Wprowad\u017a konfiguracj\u0119 dodatku Z-Wave JS" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json index 2d9609e9d00437..5b7e5f470175f7 100644 --- a/homeassistant/components/zwave_js/translations/ru.json +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -20,6 +20,13 @@ "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442." }, "step": { + "configure_addon": { + "data": { + "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438", + "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + }, + "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS" + }, "hassio_confirm": { "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Z-Wave JS (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Home Assistant Z-Wave JS)" }, @@ -38,13 +45,6 @@ "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS?", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" }, - "start_addon": { - "data": { - "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438", - "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" - }, - "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS" - }, "user": { "data": { "url": "URL-\u0430\u0434\u0440\u0435\u0441" diff --git a/homeassistant/components/zwave_js/translations/tr.json b/homeassistant/components/zwave_js/translations/tr.json index 2faa8ba43075b5..04ddcc5252ca44 100644 --- a/homeassistant/components/zwave_js/translations/tr.json +++ b/homeassistant/components/zwave_js/translations/tr.json @@ -20,6 +20,13 @@ "install_addon": "L\u00fctfen Z-Wave JS eklenti kurulumu bitene kadar bekleyin. Bu birka\u00e7 dakika s\u00fcrebilir." }, "step": { + "configure_addon": { + "data": { + "network_key": "A\u011f Anahtar\u0131", + "usb_path": "USB Ayg\u0131t Yolu" + }, + "title": "Z-Wave JS eklenti yap\u0131land\u0131rmas\u0131na girin" + }, "hassio_confirm": { "title": "Z-Wave JS eklentisiyle Z-Wave JS entegrasyonunu ayarlay\u0131n" }, @@ -38,13 +45,6 @@ "description": "Z-Wave JS Supervisor eklentisini kullanmak istiyor musunuz?", "title": "Ba\u011flant\u0131 y\u00f6ntemini se\u00e7in" }, - "start_addon": { - "data": { - "network_key": "A\u011f Anahtar\u0131", - "usb_path": "USB Ayg\u0131t Yolu" - }, - "title": "Z-Wave JS eklenti yap\u0131land\u0131rmas\u0131na girin" - }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index 1cbde8f886ba84..b9ff1b419202f1 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -20,6 +20,13 @@ "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { + "configure_addon": { + "data": { + "network_key": "\u7db2\u8def\u5bc6\u9470", + "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" + }, + "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u8a2d\u5b9a" + }, "hassio_confirm": { "title": "\u4ee5 Z-Wave JS add-on \u8a2d\u5b9a Z-Wave JS \u6574\u5408" }, @@ -38,13 +45,6 @@ "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, - "start_addon": { - "data": { - "network_key": "\u7db2\u8def\u5bc6\u9470", - "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" - }, - "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u8a2d\u5b9a" - }, "user": { "data": { "url": "\u7db2\u5740" From 87cbbcb014f1ef5c5f83f7ab4331df9895588daa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Feb 2021 18:22:23 -0600 Subject: [PATCH 0727/1818] Automatically create HomeKit accessory mode entries (#46473) When we set up HomeKit, we asked users if they wanted to create an entry in bridge or accessory mode. This approach required the user to understand how HomeKit works and choose which type to create. When the user includes the media player or camera domains, we exclude them from the bridge and create the additional entries for each entity in accessory mode. --- homeassistant/components/homekit/__init__.py | 57 ++-- .../components/homekit/accessories.py | 10 +- .../components/homekit/config_flow.py | 203 +++++++----- homeassistant/components/homekit/const.py | 2 + homeassistant/components/homekit/strings.json | 22 +- .../components/homekit/translations/en.json | 29 +- homeassistant/components/homekit/util.py | 37 ++- tests/components/homekit/test_accessories.py | 14 +- tests/components/homekit/test_config_flow.py | 191 +++++++---- tests/components/homekit/test_homekit.py | 313 ++++++------------ tests/components/homekit/test_util.py | 12 + 11 files changed, 467 insertions(+), 423 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 396f36f7c030f6..534ea3c6f95270 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -5,7 +5,7 @@ import os from aiohttp import web -from pyhap.const import CATEGORY_CAMERA, CATEGORY_TELEVISION, STANDALONE_AID +from pyhap.const import STANDALONE_AID import voluptuous as vol from homeassistant.components import zeroconf @@ -70,6 +70,7 @@ CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_ENTRY_INDEX, + CONF_EXCLUDE_ACCESSORY_MODE, CONF_FILTER, CONF_HOMEKIT_MODE, CONF_LINKED_BATTERY_CHARGING_SENSOR, @@ -81,6 +82,7 @@ CONF_ZEROCONF_DEFAULT_INTERFACE, CONFIG_OPTIONS, DEFAULT_AUTO_START, + DEFAULT_EXCLUDE_ACCESSORY_MODE, DEFAULT_HOMEKIT_MODE, DEFAULT_PORT, DEFAULT_SAFE_MODE, @@ -97,11 +99,13 @@ UNDO_UPDATE_LISTENER, ) from .util import ( + accessory_friendly_name, dismiss_setup_message, get_persist_fullpath_for_entry_id, port_is_available, remove_state_files_for_entry_id, show_setup_message, + state_needs_accessory_mode, validate_entity_config, ) @@ -243,6 +247,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # ip_address and advertise_ip are yaml only ip_address = conf.get(CONF_IP_ADDRESS) advertise_ip = conf.get(CONF_ADVERTISE_IP) + # exclude_accessory_mode is only used for config flow + # to indicate that the config entry was setup after + # we started creating config entries for entities that + # to run in accessory mode and that we should never include + # these entities on the bridge. For backwards compatibility + # with users who have not migrated yet we do not do exclude + # these entities by default as we cannot migrate automatically + # since it requires a re-pairing. + exclude_accessory_mode = conf.get( + CONF_EXCLUDE_ACCESSORY_MODE, DEFAULT_EXCLUDE_ACCESSORY_MODE + ) homekit_mode = options.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE) entity_config = options.get(CONF_ENTITY_CONFIG, {}).copy() auto_start = options.get(CONF_AUTO_START, DEFAULT_AUTO_START) @@ -254,10 +269,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): port, ip_address, entity_filter, + exclude_accessory_mode, entity_config, homekit_mode, advertise_ip, entry.entry_id, + entry.title, ) zeroconf_instance = await zeroconf.async_get_instance(hass) @@ -427,10 +444,12 @@ def __init__( port, ip_address, entity_filter, + exclude_accessory_mode, entity_config, homekit_mode, advertise_ip=None, entry_id=None, + entry_title=None, ): """Initialize a HomeKit object.""" self.hass = hass @@ -439,8 +458,10 @@ def __init__( self._ip_address = ip_address self._filter = entity_filter self._config = entity_config + self._exclude_accessory_mode = exclude_accessory_mode self._advertise_ip = advertise_ip self._entry_id = entry_id + self._entry_title = entry_title self._homekit_mode = homekit_mode self.status = STATUS_READY @@ -457,6 +478,7 @@ def setup(self, zeroconf_instance): self.hass, self._entry_id, self._name, + self._entry_title, loop=self.hass.loop, address=ip_addr, port=self._port, @@ -518,6 +540,18 @@ def add_bridge_accessory(self, state): ) return + if state_needs_accessory_mode(state): + if self._exclude_accessory_mode: + return + _LOGGER.warning( + "The bridge %s has entity %s. For best performance, " + "and to prevent unexpected unavailability, create and " + "pair a separate HomeKit instance in accessory mode for " + "this entity.", + self._name, + state.entity_id, + ) + aid = self.hass.data[DOMAIN][self._entry_id][ AID_STORAGE ].get_or_allocate_aid_for_entity_id(state.entity_id) @@ -528,24 +562,6 @@ def add_bridge_accessory(self, state): try: acc = get_accessory(self.hass, self.driver, state, aid, conf) if acc is not None: - if acc.category == CATEGORY_CAMERA: - _LOGGER.warning( - "The bridge %s has camera %s. For best performance, " - "and to prevent unexpected unavailability, create and " - "pair a separate HomeKit instance in accessory mode for " - "each camera.", - self._name, - acc.entity_id, - ) - elif acc.category == CATEGORY_TELEVISION: - _LOGGER.warning( - "The bridge %s has tv %s. For best performance, " - "and to prevent unexpected unavailability, create and " - "pair a separate HomeKit instance in accessory mode for " - "each tv media player.", - self._name, - acc.entity_id, - ) self.bridge.add_accessory(acc) except Exception: # pylint: disable=broad-except _LOGGER.exception( @@ -650,6 +666,7 @@ async def _async_start(self, entity_states): state = entity_states[0] conf = self._config.pop(state.entity_id, {}) acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf) + self.driver.add_accessory(acc) else: self.bridge = HomeBridge(self.hass, self.driver, self._name) @@ -663,7 +680,7 @@ async def _async_start(self, entity_states): show_setup_message( self.hass, self._entry_id, - self._name, + accessory_friendly_name(self._entry_title, self.driver.accessory), self.driver.state.pincode, self.driver.accessory.xhm_uri(), ) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index e31b9ec842ebef..7e68daf4b62a14 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -71,6 +71,7 @@ TYPE_VALVE, ) from .util import ( + accessory_friendly_name, convert_to_float, dismiss_setup_message, format_sw_version, @@ -489,12 +490,13 @@ async def async_get_snapshot(self, info): class HomeDriver(AccessoryDriver): """Adapter class for AccessoryDriver.""" - def __init__(self, hass, entry_id, bridge_name, **kwargs): + def __init__(self, hass, entry_id, bridge_name, entry_title, **kwargs): """Initialize a AccessoryDriver object.""" super().__init__(**kwargs) self.hass = hass self._entry_id = entry_id self._bridge_name = bridge_name + self._entry_title = entry_title def pair(self, client_uuid, client_public): """Override super function to dismiss setup message if paired.""" @@ -506,10 +508,14 @@ def pair(self, client_uuid, client_public): def unpair(self, client_uuid): """Override super function to show setup message if unpaired.""" super().unpair(client_uuid) + + if self.state.paired: + return + show_setup_message( self.hass, self._entry_id, - self._bridge_name, + accessory_friendly_name(self._entry_title, self.accessory), self.state.pincode, self.accessory.xhm_uri(), ) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index d6278c0ca94988..c21c27fba83862 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -1,10 +1,13 @@ """Config flow for HomeKit integration.""" import random +import re import string import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_FRIENDLY_NAME, @@ -26,6 +29,7 @@ from .const import ( CONF_AUTO_START, CONF_ENTITY_CONFIG, + CONF_EXCLUDE_ACCESSORY_MODE, CONF_FILTER, CONF_HOMEKIT_MODE, CONF_VIDEO_CODEC, @@ -33,13 +37,13 @@ DEFAULT_CONFIG_FLOW_PORT, DEFAULT_HOMEKIT_MODE, HOMEKIT_MODE_ACCESSORY, + HOMEKIT_MODE_BRIDGE, HOMEKIT_MODES, - SHORT_ACCESSORY_NAME, SHORT_BRIDGE_NAME, VIDEO_CODEC_COPY, ) from .const import DOMAIN # pylint:disable=unused-import -from .util import async_find_next_available_port +from .util import async_find_next_available_port, state_needs_accessory_mode CONF_CAMERA_COPY = "camera_copy" CONF_INCLUDE_EXCLUDE_MODE = "include_exclude_mode" @@ -49,11 +53,16 @@ INCLUDE_EXCLUDE_MODES = [MODE_EXCLUDE, MODE_INCLUDE] +DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN] +NEVER_BRIDGED_DOMAINS = [CAMERA_DOMAIN] + +CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}." + SUPPORTED_DOMAINS = [ "alarm_control_panel", "automation", "binary_sensor", - "camera", + CAMERA_DOMAIN, "climate", "cover", "demo", @@ -63,7 +72,7 @@ "input_boolean", "light", "lock", - "media_player", + MEDIA_PLAYER_DOMAIN, "person", "remote", "scene", @@ -77,22 +86,18 @@ DEFAULT_DOMAINS = [ "alarm_control_panel", "climate", + CAMERA_DOMAIN, "cover", "humidifier", "fan", "light", "lock", - "media_player", + MEDIA_PLAYER_DOMAIN, "switch", "vacuum", "water_heater", ] -DOMAINS_PREFER_ACCESSORY_MODE = ["camera", "media_player"] - -CAMERA_DOMAIN = "camera" -CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}." - _EMPTY_ENTITY_FILTER = { CONF_INCLUDE_DOMAINS: [], CONF_EXCLUDE_DOMAINS: [], @@ -110,32 +115,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize config flow.""" self.hk_data = {} - self.entry_title = None - - async def async_step_accessory_mode(self, user_input=None): - """Choose specific entity in accessory mode.""" - if user_input is not None: - entity_id = user_input[CONF_ENTITY_ID] - entity_filter = _EMPTY_ENTITY_FILTER.copy() - entity_filter[CONF_INCLUDE_ENTITIES] = [entity_id] - self.hk_data[CONF_FILTER] = entity_filter - if entity_id.startswith(CAMERA_ENTITY_PREFIX): - self.hk_data[CONF_ENTITY_CONFIG] = { - entity_id: {CONF_VIDEO_CODEC: VIDEO_CODEC_COPY} - } - return await self.async_step_pairing() - - all_supported_entities = _async_get_matching_entities( - self.hass, domains=DOMAINS_PREFER_ACCESSORY_MODE - ) - return self.async_show_form( - step_id="accessory_mode", - data_schema=vol.Schema( - {vol.Required(CONF_ENTITY_ID): vol.In(all_supported_entities)} - ), - ) - async def async_step_bridge_mode(self, user_input=None): + async def async_step_user(self, user_input=None): """Choose specific domains in bridge mode.""" if user_input is not None: entity_filter = _EMPTY_ENTITY_FILTER.copy() @@ -143,9 +124,10 @@ async def async_step_bridge_mode(self, user_input=None): self.hk_data[CONF_FILTER] = entity_filter return await self.async_step_pairing() + self.hk_data[CONF_HOMEKIT_MODE] = HOMEKIT_MODE_BRIDGE default_domains = [] if self._async_current_names() else DEFAULT_DOMAINS return self.async_show_form( - step_id="bridge_mode", + step_id="user", data_schema=vol.Schema( { vol.Required( @@ -158,43 +140,72 @@ async def async_step_bridge_mode(self, user_input=None): async def async_step_pairing(self, user_input=None): """Pairing instructions.""" if user_input is not None: - return self.async_create_entry(title=self.entry_title, data=self.hk_data) + port = await async_find_next_available_port( + self.hass, DEFAULT_CONFIG_FLOW_PORT + ) + await self._async_add_entries_for_accessory_mode_entities(port) + self.hk_data[CONF_PORT] = port + include_domains_filter = self.hk_data[CONF_FILTER][CONF_INCLUDE_DOMAINS] + for domain in NEVER_BRIDGED_DOMAINS: + if domain in include_domains_filter: + include_domains_filter.remove(domain) + return self.async_create_entry( + title=f"{self.hk_data[CONF_NAME]}:{self.hk_data[CONF_PORT]}", + data=self.hk_data, + ) - self.hk_data[CONF_PORT] = await async_find_next_available_port( - self.hass, DEFAULT_CONFIG_FLOW_PORT - ) - self.hk_data[CONF_NAME] = self._async_available_name( - self.hk_data[CONF_HOMEKIT_MODE] - ) - self.entry_title = f"{self.hk_data[CONF_NAME]}:{self.hk_data[CONF_PORT]}" + self.hk_data[CONF_NAME] = self._async_available_name(SHORT_BRIDGE_NAME) + self.hk_data[CONF_EXCLUDE_ACCESSORY_MODE] = True return self.async_show_form( step_id="pairing", description_placeholders={CONF_NAME: self.hk_data[CONF_NAME]}, ) - async def async_step_user(self, user_input=None): - """Handle the initial step.""" - errors = {} + async def _async_add_entries_for_accessory_mode_entities(self, last_assigned_port): + """Generate new flows for entities that need their own instances.""" + accessory_mode_entity_ids = _async_get_entity_ids_for_accessory_mode( + self.hass, self.hk_data[CONF_FILTER][CONF_INCLUDE_DOMAINS] + ) + exiting_entity_ids_accessory_mode = _async_entity_ids_with_accessory_mode( + self.hass + ) + next_port_to_check = last_assigned_port + 1 + for entity_id in accessory_mode_entity_ids: + if entity_id in exiting_entity_ids_accessory_mode: + continue + port = await async_find_next_available_port(self.hass, next_port_to_check) + next_port_to_check = port + 1 + self.hass.async_create_task( + self.hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "accessory"}, + data={CONF_ENTITY_ID: entity_id, CONF_PORT: port}, + ) + ) - if user_input is not None: - self.hk_data = { - CONF_HOMEKIT_MODE: user_input[CONF_HOMEKIT_MODE], + async def async_step_accessory(self, accessory_input): + """Handle creation a single accessory in accessory mode.""" + entity_id = accessory_input[CONF_ENTITY_ID] + port = accessory_input[CONF_PORT] + + state = self.hass.states.get(entity_id) + name = state.attributes.get(ATTR_FRIENDLY_NAME) or state.entity_id + entity_filter = _EMPTY_ENTITY_FILTER.copy() + entity_filter[CONF_INCLUDE_ENTITIES] = [entity_id] + + entry_data = { + CONF_PORT: port, + CONF_NAME: self._async_available_name(name), + CONF_HOMEKIT_MODE: HOMEKIT_MODE_ACCESSORY, + CONF_FILTER: entity_filter, + } + if entity_id.startswith(CAMERA_ENTITY_PREFIX): + entry_data[CONF_ENTITY_CONFIG] = { + entity_id: {CONF_VIDEO_CODEC: VIDEO_CODEC_COPY} } - if user_input[CONF_HOMEKIT_MODE] == HOMEKIT_MODE_ACCESSORY: - return await self.async_step_accessory_mode() - return await self.async_step_bridge_mode() - homekit_mode = self.hk_data.get(CONF_HOMEKIT_MODE, DEFAULT_HOMEKIT_MODE) - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_HOMEKIT_MODE, default=homekit_mode): vol.In( - HOMEKIT_MODES - ) - } - ), - errors=errors, + return self.async_create_entry( + title=f"{name}:{entry_data[CONF_PORT]}", data=entry_data ) async def async_step_import(self, user_input=None): @@ -215,21 +226,19 @@ def _async_current_names(self): } @callback - def _async_available_name(self, homekit_mode): + def _async_available_name(self, requested_name): """Return an available for the bridge.""" + current_names = self._async_current_names() + valid_mdns_name = re.sub("[^A-Za-z0-9 ]+", " ", requested_name) - base_name = SHORT_BRIDGE_NAME - if homekit_mode == HOMEKIT_MODE_ACCESSORY: - base_name = SHORT_ACCESSORY_NAME + if valid_mdns_name not in current_names: + return valid_mdns_name - # We always pick a RANDOM name to avoid Zeroconf - # name collisions. If the name has been seen before - # pairing will probably fail. - acceptable_chars = string.ascii_uppercase + string.digits + acceptable_mdns_chars = string.ascii_uppercase + string.digits suggested_name = None - while not suggested_name or suggested_name in self._async_current_names(): - trailer = "".join(random.choices(acceptable_chars, k=4)) - suggested_name = f"{base_name} {trailer}" + while not suggested_name or suggested_name in current_names: + trailer = "".join(random.choices(acceptable_mdns_chars, k=2)) + suggested_name = f"{valid_mdns_name} {trailer}" return suggested_name @@ -447,7 +456,7 @@ async def async_step_init(self, user_input=None): def _async_get_matching_entities(hass, domains=None): """Fetch all entities or entities in the given domains.""" return { - state.entity_id: f"{state.entity_id} ({state.attributes.get(ATTR_FRIENDLY_NAME, state.entity_id)})" + state.entity_id: f"{state.attributes.get(ATTR_FRIENDLY_NAME, state.entity_id)} ({state.entity_id})" for state in sorted( hass.states.async_all(domains and set(domains)), key=lambda item: item.entity_id, @@ -457,7 +466,41 @@ def _async_get_matching_entities(hass, domains=None): def _domains_set_from_entities(entity_ids): """Build a set of domains for the given entity ids.""" - domains = set() - for entity_id in entity_ids: - domains.add(split_entity_id(entity_id)[0]) - return domains + return {split_entity_id(entity_id)[0] for entity_id in entity_ids} + + +@callback +def _async_get_entity_ids_for_accessory_mode(hass, include_domains): + """Build a list of entities that should be paired in accessory mode.""" + accessory_mode_domains = { + domain for domain in include_domains if domain in DOMAINS_NEED_ACCESSORY_MODE + } + + if not accessory_mode_domains: + return [] + + return [ + state.entity_id + for state in hass.states.async_all(accessory_mode_domains) + if state_needs_accessory_mode(state) + ] + + +@callback +def _async_entity_ids_with_accessory_mode(hass): + """Return a set of entity ids that have config entries in accessory mode.""" + + entity_ids = set() + + current_entries = hass.config_entries.async_entries(DOMAIN) + for entry in current_entries: + # We have to handle the case where the data has not yet + # been migrated to options because the data was just + # imported and the entry was never started + target = entry.options if CONF_HOMEKIT_MODE in entry.options else entry.data + if target.get(CONF_HOMEKIT_MODE) != HOMEKIT_MODE_ACCESSORY: + continue + + entity_ids.add(target[CONF_FILTER][CONF_INCLUDE_ENTITIES][0]) + + return entity_ids diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index fac4168a79b309..67312903b50de4 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -42,6 +42,7 @@ CONF_FEATURE = "feature" CONF_FEATURE_LIST = "feature_list" CONF_FILTER = "filter" +CONF_EXCLUDE_ACCESSORY_MODE = "exclude_accessory_mode" CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor" CONF_LINKED_BATTERY_CHARGING_SENSOR = "linked_battery_charging_sensor" CONF_LINKED_DOORBELL_SENSOR = "linked_doorbell_sensor" @@ -68,6 +69,7 @@ DEFAULT_AUDIO_MAP = "0:a:0" DEFAULT_AUDIO_PACKET_SIZE = 188 DEFAULT_AUTO_START = True +DEFAULT_EXCLUDE_ACCESSORY_MODE = False DEFAULT_LOW_BATTERY_THRESHOLD = 20 DEFAULT_MAX_FPS = 30 DEFAULT_MAX_HEIGHT = 1080 diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index ed825ada23ce7b..a9b7c1c6cc1efa 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -8,7 +8,7 @@ "init": { "data": { "mode": "[%key:common::config_flow::data::mode%]", - "include_domains": "[%key:component::homekit::config::step::bridge_mode::data::include_domains%]" + "include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]" }, "description": "HomeKit can be configured expose a bridge or a single accessory. In accessory mode, only a single entity can be used. Accessory mode is required for media players with the TV device class to function properly. Entities in the \u201cDomains to include\u201d will be included to HomeKit. You will be able to select which entities to include or exclude from this list on the next screen.", "title": "Select domains to be included." @@ -18,7 +18,7 @@ "mode": "[%key:common::config_flow::data::mode%]", "entities": "Entities" }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", + "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a seperate HomeKit accessory will beeach tv media player and camera.", "title": "Select entities to be included" }, "cameras": { @@ -40,29 +40,15 @@ "config": { "step": { "user": { - "data": { - "mode": "[%key:common::config_flow::data::mode%]" - }, - "description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", - "title": "Activate HomeKit" - }, - "accessory_mode": { - "data": { - "entity_id": "Entity" - }, - "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", - "title": "Select entity to be included" - }, - "bridge_mode": { "data": { "include_domains": "Domains to include" }, - "description": "Choose the domains to be included. All supported entities in the domain will be included.", + "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" }, "pairing": { "title": "Pair HomeKit", - "description": "As soon as the {name} is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d." + "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d." } }, "abort": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index db0656c0450524..e9aaeb60df8730 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,32 +4,16 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { - "accessory_mode": { - "data": { - "entity_id": "Entity" - }, - "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", - "title": "Select entity to be included" - }, - "bridge_mode": { - "data": { - "include_domains": "Domains to include" - }, - "description": "Choose the domains to be included. All supported entities in the domain will be included.", - "title": "Select domains to be included" - }, "pairing": { - "description": "As soon as the {name} is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d.", + "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains": "Domains to include", - "mode": "Mode" + "include_domains": "Domains to include" }, - "description": "The HomeKit integration will allow you to access your Home Assistant entities in HomeKit. In bridge mode, HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", - "title": "Activate HomeKit" + "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", + "title": "Select domains to be included" } } }, @@ -37,8 +21,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", - "safe_mode": "Safe Mode (enable only if pairing fails)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" @@ -55,7 +38,7 @@ "entities": "Entities", "mode": "Mode" }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, and to prevent unexpected unavailability, create and pair a separate HomeKit instance in accessory mode for each tv media player and camera.", + "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a seperate HomeKit accessory will beeach tv media player and camera.", "title": "Select entities to be included" }, "init": { diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index c23b8c1baaf0e7..46b893bb96d3b5 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -11,8 +11,14 @@ import voluptuous as vol from homeassistant.components import binary_sensor, media_player, sensor +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.media_player import ( + DEVICE_CLASS_TV, + DOMAIN as MEDIA_PLAYER_DOMAIN, +) from homeassistant.const import ( ATTR_CODE, + ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_PORT, @@ -328,9 +334,7 @@ def show_setup_message(hass, entry_id, bridge_name, pincode, uri): f"### {pin}\n" f"![image](/api/homekit/pairingqr?{entry_id}-{pairing_secret})" ) - hass.components.persistent_notification.create( - message, "HomeKit Bridge Setup", entry_id - ) + hass.components.persistent_notification.create(message, "HomeKit Pairing", entry_id) def dismiss_setup_message(hass, entry_id): @@ -473,3 +477,30 @@ def pid_is_alive(pid) -> bool: except OSError: pass return False + + +def accessory_friendly_name(hass_name, accessory): + """Return the combined name for the accessory. + + The mDNS name and the Home Assistant config entry + name are usually different which means they need to + see both to identify the accessory. + """ + accessory_mdns_name = accessory.display_name + if hass_name.startswith(accessory_mdns_name): + return hass_name + return f"{hass_name} ({accessory_mdns_name})" + + +def state_needs_accessory_mode(state): + """Return if the entity represented by the state must be paired in accessory mode.""" + if state.domain == CAMERA_DOMAIN: + return True + + if ( + state.domain == MEDIA_PLAYER_DOMAIN + and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV + ): + return True + + return False diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index e308ed2153751c..afaa9ea0892fe3 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -603,13 +603,19 @@ def test_home_driver(): with patch("pyhap.accessory_driver.AccessoryDriver.__init__") as mock_driver: driver = HomeDriver( - "hass", "entry_id", "name", address=ip_address, port=port, persist_file=path + "hass", + "entry_id", + "name", + "title", + address=ip_address, + port=port, + persist_file=path, ) mock_driver.assert_called_with(address=ip_address, port=port, persist_file=path) - driver.state = Mock(pincode=pin) + driver.state = Mock(pincode=pin, paired=False) xhm_uri_mock = Mock(return_value="X-HM://0") - driver.accessory = Mock(xhm_uri=xhm_uri_mock) + driver.accessory = Mock(display_name="any", xhm_uri=xhm_uri_mock) # pair with patch("pyhap.accessory_driver.AccessoryDriver.pair") as mock_pair, patch( @@ -627,4 +633,4 @@ def test_home_driver(): driver.unpair("client_uuid") mock_unpair.assert_called_with("client_uuid") - mock_show_msg.assert_called_with("hass", "entry_id", "name", pin, "X-HM://0") + mock_show_msg.assert_called_with("hass", "entry_id", "title (any)", pin, "X-HM://0") diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index 34c7ab2ecc0440..3d94672cd8ac9e 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -4,7 +4,7 @@ import pytest from homeassistant import config_entries, data_entry_flow, setup -from homeassistant.components.homekit.const import DOMAIN +from homeassistant.components.homekit.const import DOMAIN, SHORT_BRIDGE_NAME from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_NAME, CONF_PORT @@ -39,48 +39,41 @@ async def test_setup_in_bridge_mode(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" - assert result["errors"] == {} + assert result["errors"] is None result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"mode": "bridge"}, + {"include_domains": ["light"]}, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["step_id"] == "bridge_mode" + assert result2["step_id"] == "pairing" with patch( "homeassistant.components.homekit.config_flow.async_find_next_available_port", return_value=12345, - ): - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], - {"include_domains": ["light"]}, - ) - assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result3["step_id"] == "pairing" - - with patch( + ), patch( "homeassistant.components.homekit.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.homekit.async_setup_entry", return_value=True, ) as mock_setup_entry: - result4 = await hass.config_entries.flow.async_configure( - result3["flow_id"], + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {}, ) await hass.async_block_till_done() - assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result4["title"][:11] == "HASS Bridge" - bridge_name = (result4["title"].split(":"))[0] - assert result4["data"] == { + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + bridge_name = (result3["title"].split(":"))[0] + assert bridge_name == SHORT_BRIDGE_NAME + assert result3["data"] == { "filter": { "exclude_domains": [], "exclude_entities": [], "include_domains": ["light"], "include_entities": [], }, + "exclude_accessory_mode": True, "mode": "bridge", "name": bridge_name, "port": 12345, @@ -89,64 +82,147 @@ async def test_setup_in_bridge_mode(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_setup_in_accessory_mode(hass): - """Test we can setup a new instance in accessory.""" +async def test_setup_in_bridge_mode_name_taken(hass): + """Test we can setup a new instance in bridge mode when the name is taken.""" await setup.async_setup_component(hass, "persistent_notification", {}) - hass.states.async_set("camera.mine", "off") + + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: SHORT_BRIDGE_NAME, CONF_PORT: 8000}, + ) + entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" - assert result["errors"] == {} + assert result["errors"] is None result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"mode": "accessory"}, + {"include_domains": ["light"]}, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["step_id"] == "accessory_mode" + assert result2["step_id"] == "pairing" with patch( "homeassistant.components.homekit.config_flow.async_find_next_available_port", return_value=12345, - ): + ), patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.homekit.async_setup_entry", + return_value=True, + ) as mock_setup_entry: result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], - {"entity_id": "camera.mine"}, + {}, ) - assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result3["step_id"] == "pairing" + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"] != SHORT_BRIDGE_NAME + assert result3["title"].startswith(SHORT_BRIDGE_NAME) + bridge_name = (result3["title"].split(":"))[0] + assert result3["data"] == { + "filter": { + "exclude_domains": [], + "exclude_entities": [], + "include_domains": ["light"], + "include_entities": [], + }, + "exclude_accessory_mode": True, + "mode": "bridge", + "name": bridge_name, + "port": 12345, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 2 + + +async def test_setup_creates_entries_for_accessory_mode_devices(hass): + """Test we can setup a new instance and we create entries for accessory mode devices.""" + hass.states.async_set("camera.one", "on") + hass.states.async_set("camera.existing", "on") + hass.states.async_set("media_player.two", "on", {"device_class": "tv"}) + + bridge_mode_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "bridge", CONF_PORT: 8001}, + options={ + "mode": "bridge", + "filter": { + "include_entities": ["camera.existing"], + }, + }, + ) + bridge_mode_entry.add_to_hass(hass) + accessory_mode_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "accessory", CONF_PORT: 8000}, + options={ + "mode": "accessory", + "filter": { + "include_entities": ["camera.existing"], + }, + }, + ) + accessory_mode_entry.add_to_hass(hass) + + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] is None + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"include_domains": ["camera", "media_player", "light"]}, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "pairing" with patch( + "homeassistant.components.homekit.config_flow.async_find_next_available_port", + return_value=12345, + ), patch( "homeassistant.components.homekit.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.homekit.async_setup_entry", return_value=True, ) as mock_setup_entry: - result4 = await hass.config_entries.flow.async_configure( - result3["flow_id"], + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {}, ) await hass.async_block_till_done() - assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result4["title"][:14] == "HASS Accessory" - bridge_name = (result4["title"].split(":"))[0] - assert result4["data"] == { + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"][:11] == "HASS Bridge" + bridge_name = (result3["title"].split(":"))[0] + assert result3["data"] == { "filter": { "exclude_domains": [], "exclude_entities": [], - "include_domains": [], - "include_entities": ["camera.mine"], + "include_domains": ["media_player", "light"], + "include_entities": [], }, - "mode": "accessory", + "exclude_accessory_mode": True, + "mode": "bridge", "name": bridge_name, - "entity_config": {"camera.mine": {"video_codec": "copy"}}, "port": 12345, } assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 + # + # Existing accessory mode entries should get setup but not duplicated + # + # 1 - existing accessory for camera.existing + # 2 - existing bridge for camera.one + # 3 - new bridge + # 4 - camera.one in accessory mode + # 5 - media_player.two in accessory mode + assert len(mock_setup_entry.mock_calls) == 5 async def test_import(hass): @@ -656,55 +732,48 @@ async def test_converting_bridge_to_accessory_mode(hass, hk_driver): DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == "form" - assert result["errors"] == {} + assert result["errors"] is None result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"mode": "bridge"}, + {"include_domains": ["light"]}, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["step_id"] == "bridge_mode" - - with patch( - "homeassistant.components.homekit.config_flow.async_find_next_available_port", - return_value=12345, - ): - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], - {"include_domains": ["light"]}, - ) - assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result3["step_id"] == "pairing" + assert result2["step_id"] == "pairing" # We need to actually setup the config entry or the data # will not get migrated to options with patch( + "homeassistant.components.homekit.config_flow.async_find_next_available_port", + return_value=12345, + ), patch( "homeassistant.components.homekit.HomeKit.async_start", return_value=True, ) as mock_async_start: - result4 = await hass.config_entries.flow.async_configure( - result3["flow_id"], + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {}, ) await hass.async_block_till_done() - assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result4["title"][:11] == "HASS Bridge" - bridge_name = (result4["title"].split(":"))[0] - assert result4["data"] == { + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"][:11] == "HASS Bridge" + bridge_name = (result3["title"].split(":"))[0] + assert result3["data"] == { "filter": { "exclude_domains": [], "exclude_entities": [], "include_domains": ["light"], "include_entities": [], }, + "exclude_accessory_mode": True, "mode": "bridge", "name": bridge_name, "port": 12345, } assert len(mock_async_start.mock_calls) == 1 - config_entry = result4["result"] + config_entry = result3["result"] hass.states.async_set("camera.tv", "off") hass.states.async_set("camera.sonos", "off") diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index b0213ee7e8bcde..ec32460268488d 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -82,6 +82,22 @@ def entity_reg_fixture(hass): return mock_registry(hass) +def _mock_homekit(hass, entry, homekit_mode, entity_filter=None): + return HomeKit( + hass=hass, + name=BRIDGE_NAME, + port=DEFAULT_PORT, + ip_address=None, + entity_filter=entity_filter or generate_filter([], [], [], []), + exclude_accessory_mode=False, + entity_config={}, + homekit_mode=homekit_mode, + advertise_ip=None, + entry_id=entry.entry_id, + entry_title=entry.title, + ) + + async def test_setup_min(hass, mock_zeroconf): """Test async_setup with min config options.""" entry = MockConfigEntry( @@ -103,10 +119,12 @@ async def test_setup_min(hass, mock_zeroconf): DEFAULT_PORT, None, ANY, + ANY, {}, HOMEKIT_MODE_BRIDGE, None, entry.entry_id, + entry.title, ) assert mock_homekit().setup.called is True @@ -139,10 +157,12 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf): 11111, "172.0.0.0", ANY, + ANY, {}, HOMEKIT_MODE_BRIDGE, None, entry.entry_id, + entry.title, ) assert mock_homekit().setup.called is True @@ -184,11 +204,13 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf): BRIDGE_NAME, DEFAULT_PORT, None, + True, {}, {}, HOMEKIT_MODE_BRIDGE, advertise_ip=None, entry_id=entry.entry_id, + entry_title=entry.title, ) hass.states.async_set("light.demo", "on") @@ -205,6 +227,7 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf): hass, entry.entry_id, BRIDGE_NAME, + entry.title, loop=hass.loop, address=IP_ADDRESS, port=DEFAULT_PORT, @@ -230,11 +253,13 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf): BRIDGE_NAME, DEFAULT_PORT, "172.0.0.0", + True, {}, {}, HOMEKIT_MODE_BRIDGE, None, entry_id=entry.entry_id, + entry_title=entry.title, ) mock_zeroconf = MagicMock() @@ -245,6 +270,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf): hass, entry.entry_id, BRIDGE_NAME, + entry.title, loop=hass.loop, address="172.0.0.0", port=DEFAULT_PORT, @@ -266,11 +292,13 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf): BRIDGE_NAME, DEFAULT_PORT, "0.0.0.0", + True, {}, {}, HOMEKIT_MODE_BRIDGE, "192.168.1.100", entry_id=entry.entry_id, + entry_title=entry.title, ) zeroconf_instance = MagicMock() @@ -281,6 +309,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf): hass, entry.entry_id, BRIDGE_NAME, + entry.title, loop=hass.loop, address="0.0.0.0", port=DEFAULT_PORT, @@ -292,40 +321,40 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf): async def test_homekit_add_accessory(hass, mock_zeroconf): """Add accessory if config exists and get_acc returns an accessory.""" - entry = await async_init_integration(hass) - homekit = HomeKit( - hass, - None, - None, - None, - lambda entity_id: True, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) + entry.add_to_hass(hass) + + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() homekit.bridge.accessories = range(10) + homekit.async_start = AsyncMock() - mock_acc = Mock(category="any") + with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() - await async_init_integration(hass) + mock_acc = Mock(category="any") with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, mock_acc, None] - homekit.add_bridge_accessory(State("light.demo", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 1403373688, {}) + state = State("light.demo", "on") + homekit.add_bridge_accessory(state) + mock_get_acc.assert_called_with(hass, ANY, ANY, 1403373688, {}) assert not mock_bridge.add_accessory.called - homekit.add_bridge_accessory(State("demo.test", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 600325356, {}) + state = State("demo.test", "on") + homekit.add_bridge_accessory(state) + mock_get_acc.assert_called_with(hass, ANY, ANY, 600325356, {}) assert mock_bridge.add_accessory.called - homekit.add_bridge_accessory(State("demo.test_2", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 1467253281, {}) - mock_bridge.add_accessory.assert_called_with(mock_acc) + state = State("demo.test_2", "on") + homekit.add_bridge_accessory(state) + mock_get_acc.assert_called_with(hass, ANY, ANY, 1467253281, {}) + assert mock_bridge.add_accessory.called @pytest.mark.parametrize("acc_category", [CATEGORY_TELEVISION, CATEGORY_CAMERA]) @@ -333,37 +362,30 @@ async def test_homekit_warn_add_accessory_bridge( hass, acc_category, mock_zeroconf, caplog ): """Test we warn when adding cameras or tvs to a bridge.""" - entry = await async_init_integration(hass) - - homekit = HomeKit( - hass, - None, - None, - None, - lambda entity_id: True, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) + entry.add_to_hass(hass) + + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() homekit.bridge.accessories = range(10) + homekit.async_start = AsyncMock() - mock_camera_acc = Mock(category=acc_category) + with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() - await async_init_integration(hass) + mock_camera_acc = Mock(category=acc_category) with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, mock_camera_acc, None] - homekit.add_bridge_accessory(State("light.demo", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 1403373688, {}) + state = State("camera.test", "on") + homekit.add_bridge_accessory(state) + mock_get_acc.assert_called_with(hass, ANY, ANY, 1508819236, {}) assert not mock_bridge.add_accessory.called - homekit.add_bridge_accessory(State("camera.test", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 1508819236, {}) - assert mock_bridge.add_accessory.called - assert "accessory mode" in caplog.text @@ -371,17 +393,8 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf): """Remove accessory from bridge.""" entry = await async_init_integration(hass) - homekit = HomeKit( - hass, - None, - None, - None, - lambda entity_id: True, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() mock_bridge.accessories = {"light.demo": "acc"} @@ -396,17 +409,8 @@ async def test_homekit_entity_filter(hass, mock_zeroconf): entry = await async_init_integration(hass) entity_filter = generate_filter(["cover"], ["demo.test"], [], []) - homekit = HomeKit( - hass, - None, - None, - None, - entity_filter, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter) + homekit.bridge = Mock() homekit.bridge.accessories = {} @@ -432,17 +436,8 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf): entity_filter = generate_filter( ["cover"], ["demo.test"], [], [], ["*.included_*"], ["*.excluded_*"] ) - homekit = HomeKit( - hass, - None, - None, - None, - entity_filter, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter) + homekit.bridge = Mock() homekit.bridge.accessories = {} @@ -471,17 +466,8 @@ async def test_homekit_start(hass, hk_driver, device_reg): entry = await async_init_integration(hass) pin = b"123-45-678" - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver @@ -513,7 +499,9 @@ async def test_homekit_start(hass, hk_driver, device_reg): await hass.async_block_till_done() mock_add_acc.assert_any_call(state) - mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY) + mock_setup_msg.assert_called_with( + hass, entry.entry_id, "Mock Title (any)", pin, ANY + ) hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -563,17 +551,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc entity_filter = generate_filter(["cover", "light"], ["demo.test"], [], []) await async_init_entry(hass, entry) - homekit = HomeKit( - hass, - None, - None, - None, - entity_filter, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter) homekit.bridge = Mock() homekit.bridge.accessories = [] @@ -593,7 +571,9 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc await homekit.async_start() await hass.async_block_till_done() - mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY) + mock_setup_msg.assert_called_with( + hass, entry.entry_id, "Mock Title (any)", pin, ANY + ) hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -608,18 +588,8 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc async def test_homekit_stop(hass): """Test HomeKit stop method.""" entry = await async_init_integration(hass) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) homekit.driver = Mock() homekit.driver.async_stop = AsyncMock() homekit.bridge = Mock() @@ -649,17 +619,8 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf): domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entity_id = "light.demo" - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {entity_id: {}}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.bridge = Mock() homekit.bridge.accessories = {} @@ -697,17 +658,7 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco entity_filter = generate_filter(["cover", "light"], ["demo.test"], [], []) - homekit = HomeKit( - hass, - None, - None, - None, - entity_filter, - {}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter) def _mock_bridge(*_): mock_bridge = HomeBridge(hass, hk_driver, "mock_bridge") @@ -738,17 +689,8 @@ async def test_homekit_finds_linked_batteries( """Test HomeKit start method.""" entry = await async_init_integration(hass) - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {"light.demo": {}}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.driver = hk_driver # pylint: disable=protected-access homekit._filter = Mock(return_value=True) @@ -792,9 +734,6 @@ async def test_homekit_finds_linked_batteries( ) hass.states.async_set(light.entity_id, STATE_ON) - def _mock_get_accessory(*args, **kwargs): - return [None, "acc", None] - with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( @@ -823,18 +762,8 @@ async def test_homekit_async_get_integration_fails( ): """Test that we continue if async_get_integration fails.""" entry = await async_init_integration(hass) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {"light.demo": {}}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) homekit.driver = hk_driver # pylint: disable=protected-access homekit._filter = Mock(return_value=True) @@ -877,9 +806,6 @@ async def test_homekit_async_get_integration_fails( ) hass.states.async_set(light.entity_id, STATE_ON) - def _mock_get_accessory(*args, **kwargs): - return [None, "acc", None] - with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( @@ -927,10 +853,12 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): 12345, None, ANY, + ANY, {}, HOMEKIT_MODE_BRIDGE, None, entry.entry_id, + entry.title, ) assert mock_homekit().setup.called is True @@ -989,18 +917,8 @@ async def test_homekit_ignored_missing_devices( ): """Test HomeKit handles a device in the entity registry but missing from the device registry.""" entry = await async_init_integration(hass) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {"light.demo": {}}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) homekit.driver = hk_driver # pylint: disable=protected-access homekit._filter = Mock(return_value=True) @@ -1041,9 +959,6 @@ async def test_homekit_ignored_missing_devices( hass.states.async_set(light.entity_id, STATE_ON) hass.states.async_set("light.two", STATE_ON) - def _mock_get_accessory(*args, **kwargs): - return [None, "acc", None] - with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( @@ -1071,17 +986,8 @@ async def test_homekit_finds_linked_motion_sensors( """Test HomeKit start method.""" entry = await async_init_integration(hass) - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {"camera.camera_demo": {}}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.driver = hk_driver # pylint: disable=protected-access homekit._filter = Mock(return_value=True) @@ -1115,9 +1021,6 @@ async def test_homekit_finds_linked_motion_sensors( ) hass.states.async_set(camera.entity_id, STATE_ON) - def _mock_get_accessory(*args, **kwargs): - return [None, "acc", None] - with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( @@ -1146,17 +1049,8 @@ async def test_homekit_finds_linked_humidity_sensors( """Test HomeKit start method.""" entry = await async_init_integration(hass) - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {"humidifier.humidifier": {}}, - HOMEKIT_MODE_BRIDGE, - advertise_ip=None, - entry_id=entry.entry_id, - ) + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.driver = hk_driver homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") @@ -1192,9 +1086,6 @@ async def test_homekit_finds_linked_humidity_sensors( ) hass.states.async_set(humidifier.entity_id, STATE_ON) - def _mock_get_accessory(*args, **kwargs): - return [None, "acc", None] - with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( @@ -1241,10 +1132,12 @@ async def test_reload(hass, mock_zeroconf): 12345, None, ANY, + False, {}, HOMEKIT_MODE_BRIDGE, None, entry.entry_id, + entry.title, ) assert mock_homekit().setup.called is True yaml_path = os.path.join( @@ -1277,10 +1170,12 @@ async def test_reload(hass, mock_zeroconf): 45678, None, ANY, + False, {}, HOMEKIT_MODE_BRIDGE, None, entry.entry_id, + entry.title, ) assert mock_homekit2().setup.called is True @@ -1294,17 +1189,9 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): entry = await async_init_integration(hass) pin = b"123-45-678" - homekit = HomeKit( - hass, - None, - None, - None, - {}, - {}, - HOMEKIT_MODE_ACCESSORY, - advertise_ip=None, - entry_id=entry.entry_id, - ) + + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) + homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver @@ -1323,6 +1210,8 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): await hass.async_block_till_done() mock_add_acc.assert_not_called() - mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY) + mock_setup_msg.assert_called_with( + hass, entry.entry_id, "Mock Title (any)", pin, ANY + ) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index afa1408a06bc91..9b03d6160027e8 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -1,4 +1,6 @@ """Test HomeKit util module.""" +from unittest.mock import Mock + import pytest import voluptuous as vol @@ -22,6 +24,7 @@ TYPE_VALVE, ) from homeassistant.components.homekit.util import ( + accessory_friendly_name, async_find_next_available_port, cleanup_name_for_homekit, convert_to_float, @@ -284,3 +287,12 @@ async def test_format_sw_version(): assert format_sw_version("56.0-76060") == "56.0.76060" assert format_sw_version(3.6) == "3.6" assert format_sw_version("unknown") is None + + +async def test_accessory_friendly_name(): + """Test we provide a helpful friendly name.""" + + accessory = Mock() + accessory.display_name = "same" + assert accessory_friendly_name("same", accessory) == "same" + assert accessory_friendly_name("hass title", accessory) == "hass title (same)" From d02b27a5d03370ccb2a06308969bee71bd2e69fc Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Wed, 24 Feb 2021 01:26:17 +0100 Subject: [PATCH 0728/1818] Update xknx to 0.17.1 (#46974) --- homeassistant/components/knx/__init__.py | 20 +++++++++--------- homeassistant/components/knx/const.py | 24 +++++++++++----------- homeassistant/components/knx/factory.py | 24 +++++++++++----------- homeassistant/components/knx/fan.py | 6 +++--- homeassistant/components/knx/manifest.json | 2 +- homeassistant/components/knx/schema.py | 4 ++-- requirements_all.txt | 2 +- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index f092bafe404949..8265ba42b7233e 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -106,34 +106,34 @@ vol.Optional(CONF_KNX_EXPOSE): vol.All( cv.ensure_list, [ExposeSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.cover.value): vol.All( + vol.Optional(SupportedPlatforms.COVER.value): vol.All( cv.ensure_list, [CoverSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.binary_sensor.value): vol.All( + vol.Optional(SupportedPlatforms.BINARY_SENSOR.value): vol.All( cv.ensure_list, [BinarySensorSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.light.value): vol.All( + vol.Optional(SupportedPlatforms.LIGHT.value): vol.All( cv.ensure_list, [LightSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.climate.value): vol.All( + vol.Optional(SupportedPlatforms.CLIMATE.value): vol.All( cv.ensure_list, [ClimateSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.notify.value): vol.All( + vol.Optional(SupportedPlatforms.NOTIFY.value): vol.All( cv.ensure_list, [NotifySchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.switch.value): vol.All( + vol.Optional(SupportedPlatforms.SWITCH.value): vol.All( cv.ensure_list, [SwitchSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.sensor.value): vol.All( + vol.Optional(SupportedPlatforms.SENSOR.value): vol.All( cv.ensure_list, [SensorSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.scene.value): vol.All( + vol.Optional(SupportedPlatforms.SCENE.value): vol.All( cv.ensure_list, [SceneSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.weather.value): vol.All( + vol.Optional(SupportedPlatforms.WEATHER.value): vol.All( cv.ensure_list, [WeatherSchema.SCHEMA] ), - vol.Optional(SupportedPlatforms.fan.value): vol.All( + vol.Optional(SupportedPlatforms.FAN.value): vol.All( cv.ensure_list, [FanSchema.SCHEMA] ), } diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 6a76de6a97f81c..83ffc2557c2fb4 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -26,23 +26,23 @@ class ColorTempModes(Enum): """Color temperature modes for config validation.""" - absolute = "DPT-7.600" - relative = "DPT-5.001" + ABSOLUTE = "DPT-7.600" + RELATIVE = "DPT-5.001" class SupportedPlatforms(Enum): """Supported platforms.""" - binary_sensor = "binary_sensor" - climate = "climate" - cover = "cover" - fan = "fan" - light = "light" - notify = "notify" - scene = "scene" - sensor = "sensor" - switch = "switch" - weather = "weather" + BINARY_SENSOR = "binary_sensor" + CLIMATE = "climate" + COVER = "cover" + FAN = "fan" + LIGHT = "light" + NOTIFY = "notify" + SCENE = "scene" + SENSOR = "sensor" + SWITCH = "switch" + WEATHER = "weather" # Map KNX controller modes to HA modes. This list might not be complete. diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 8d3464b25ad267..51a94bc06e3c00 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -40,34 +40,34 @@ def create_knx_device( config: ConfigType, ) -> XknxDevice: """Return the requested XKNX device.""" - if platform is SupportedPlatforms.light: + if platform is SupportedPlatforms.LIGHT: return _create_light(knx_module, config) - if platform is SupportedPlatforms.cover: + if platform is SupportedPlatforms.COVER: return _create_cover(knx_module, config) - if platform is SupportedPlatforms.climate: + if platform is SupportedPlatforms.CLIMATE: return _create_climate(knx_module, config) - if platform is SupportedPlatforms.switch: + if platform is SupportedPlatforms.SWITCH: return _create_switch(knx_module, config) - if platform is SupportedPlatforms.sensor: + if platform is SupportedPlatforms.SENSOR: return _create_sensor(knx_module, config) - if platform is SupportedPlatforms.notify: + if platform is SupportedPlatforms.NOTIFY: return _create_notify(knx_module, config) - if platform is SupportedPlatforms.scene: + if platform is SupportedPlatforms.SCENE: return _create_scene(knx_module, config) - if platform is SupportedPlatforms.binary_sensor: + if platform is SupportedPlatforms.BINARY_SENSOR: return _create_binary_sensor(knx_module, config) - if platform is SupportedPlatforms.weather: + if platform is SupportedPlatforms.WEATHER: return _create_weather(knx_module, config) - if platform is SupportedPlatforms.fan: + if platform is SupportedPlatforms.FAN: return _create_fan(knx_module, config) @@ -121,12 +121,12 @@ def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight: group_address_tunable_white_state = None group_address_color_temp = None group_address_color_temp_state = None - if config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.absolute: + if config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.ABSOLUTE: group_address_color_temp = config.get(LightSchema.CONF_COLOR_TEMP_ADDRESS) group_address_color_temp_state = config.get( LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS ) - elif config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.relative: + elif config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.RELATIVE: group_address_tunable_white = config.get(LightSchema.CONF_COLOR_TEMP_ADDRESS) group_address_tunable_white_state = config.get( LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index 6aa4f72289281f..43d1cd7d6f20d8 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -34,14 +34,14 @@ def __init__(self, device: XknxFan): """Initialize of KNX fan.""" super().__init__(device) - if self._device.mode == FanSpeedMode.Step: + if self._device.mode == FanSpeedMode.STEP: self._step_range = (1, device.max_step) else: self._step_range = None async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" - if self._device.mode == FanSpeedMode.Step: + if self._device.mode == FanSpeedMode.STEP: step = math.ceil(percentage_to_ranged_value(self._step_range, percentage)) await self._device.set_speed(step) else: @@ -63,7 +63,7 @@ def percentage(self) -> Optional[int]: if self._device.current_speed is None: return None - if self._device.mode == FanSpeedMode.Step: + if self._device.mode == FanSpeedMode.STEP: return ranged_value_to_percentage( self._step_range, self._device.current_speed ) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 834e1604e9ec4d..7ca7657d0ff535 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.0"], + "requirements": ["xknx==0.17.1"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 125115f28b27c2..1a48d7945bd2c8 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -174,7 +174,7 @@ class LightSchema: vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string, vol.Optional( CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE - ): cv.enum(ColorTempModes), + ): vol.All(vol.Upper, cv.enum(ColorTempModes)), vol.Exclusive(CONF_RGBW_ADDRESS, "color"): cv.string, vol.Optional(CONF_RGBW_STATE_ADDRESS): cv.string, vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All( @@ -256,7 +256,7 @@ class ClimateSchema: vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional( CONF_SETPOINT_SHIFT_MODE, default=DEFAULT_SETPOINT_SHIFT_MODE - ): cv.enum(SetpointShiftMode), + ): vol.All(vol.Upper, cv.enum(SetpointShiftMode)), vol.Optional( CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX ): vol.All(int, vol.Range(min=0, max=32)), diff --git a/requirements_all.txt b/requirements_all.txt index 7079c94a00ba80..3ac487c8fb966e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2333,7 +2333,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.0 +xknx==0.17.1 # homeassistant.components.bluesound # homeassistant.components.rest From d4d68ebc64b63936030003aadd2747afeaeed9bc Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 24 Feb 2021 01:31:24 +0100 Subject: [PATCH 0729/1818] Extend zwave_js discovery scheme for lights (#46907) --- .../components/zwave_js/discovery.py | 25 +- tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_light.py | 7 + .../zwave_js/aeon_smart_switch_6_state.json | 1247 +++++++++++++++++ 4 files changed, 1277 insertions(+), 16 deletions(-) create mode 100644 tests/fixtures/zwave_js/aeon_smart_switch_6_state.json diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 9379ab69b3444b..f4f5c359e2260a 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -221,22 +221,6 @@ class ZWaveDiscoverySchema: type={"number"}, ), ), - # lights - # primary value is the currentValue (brightness) - ZWaveDiscoverySchema( - platform="light", - device_class_generic={"Multilevel Switch", "Remote Switch"}, - device_class_specific={ - "Tunable Color Light", - "Binary Tunable Color Light", - "Tunable Color Switch", - "Multilevel Remote Switch", - "Multilevel Power Switch", - "Multilevel Scene Switch", - "Unused", - }, - primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, - ), # binary sensors ZWaveDiscoverySchema( platform="binary_sensor", @@ -381,6 +365,15 @@ class ZWaveDiscoverySchema: device_class_specific={"Fan Switch"}, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), + # lights + # primary value is the currentValue (brightness) + # catch any device with multilevel CC as light + # NOTE: keep this at the bottom of the discovery scheme, + # to handle all others that need the multilevel CC first + ZWaveDiscoverySchema( + platform="light", + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, + ), ] diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 2f082621c45dd6..a9618acc64d97b 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -148,6 +148,12 @@ def iblinds_v2_state_fixture(): return json.loads(load_fixture("zwave_js/cover_iblinds_v2_state.json")) +@pytest.fixture(name="aeon_smart_switch_6_state", scope="session") +def aeon_smart_switch_6_state_fixture(): + """Load the AEON Labs (ZW096) Smart Switch 6 node state fixture data.""" + return json.loads(load_fixture("zwave_js/aeon_smart_switch_6_state.json")) + + @pytest.fixture(name="ge_12730_state", scope="session") def ge_12730_state_fixture(): """Load the GE 12730 node state fixture data.""" @@ -362,6 +368,14 @@ def iblinds_cover_fixture(client, iblinds_v2_state): return node +@pytest.fixture(name="aeon_smart_switch_6") +def aeon_smart_switch_6_fixture(client, aeon_smart_switch_6_state): + """Mock an AEON Labs (ZW096) Smart Switch 6 node.""" + node = Node(client, aeon_smart_switch_6_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="ge_12730") def ge_12730_fixture(client, ge_12730_state): """Mock a GE 12730 fan controller node.""" diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index 40950362ad76a1..f48b02223d0795 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -14,6 +14,7 @@ BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" EATON_RF9640_ENTITY = "light.allloaddimmer" +AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" async def test_light(hass, client, bulb_6_multi_color, integration): @@ -403,3 +404,9 @@ async def test_v4_dimmer_light(hass, client, eaton_rf9640_dimmer, integration): assert state.state == STATE_ON # the light should pick currentvalue which has zwave value 22 assert state.attributes[ATTR_BRIGHTNESS] == 57 + + +async def test_optional_light(hass, client, aeon_smart_switch_6, integration): + """Test a device that has an additional light endpoint being identified as light.""" + state = hass.states.get(AEON_SMART_SWITCH_LIGHT_ENTITY) + assert state.state == STATE_ON diff --git a/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json b/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json new file mode 100644 index 00000000000000..2da1c203561c47 --- /dev/null +++ b/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json @@ -0,0 +1,1247 @@ +{ + "nodeId": 102, + "index": 0, + "installerIcon": 1792, + "userIcon": 1792, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Binary Switch", + "specific": "Binary Power Switch", + "mandatorySupportedCCs": ["Basic", "Binary Switch", "All Switch"], + "mandatoryControlCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": true, + "version": 4, + "isBeaming": true, + "manufacturerId": 134, + "productId": 96, + "productType": 3, + "firmwareVersion": "1.1", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 5, + "deviceConfig": { + "manufacturerId": 134, + "manufacturer": "AEON Labs", + "label": "ZW096", + "description": "Smart Switch 6", + "devices": [ + { "productType": "0x0003", "productId": "0x0060" }, + { "productType": "0x0103", "productId": "0x0060" }, + { "productType": "0x0203", "productId": "0x0060" }, + { "productType": "0x1d03", "productId": "0x0060" } + ], + "firmwareVersion": { "min": "0.0", "max": "255.255" }, + "associations": {}, + "paramInformation": { "_map": {} } + }, + "label": "ZW096", + "neighbors": [1, 63, 90, 117], + "interviewAttempts": 1, + "interviewStage": 7, + "endpoints": [ + { + "nodeId": 102, + "index": 0, + "installerIcon": 1792, + "userIcon": 1792 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": true + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 2, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 2, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { "switchType": 2 } + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 2, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { "switchType": 2 } + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 65537, + "propertyName": "value", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh]", + "unit": "kWh", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 0 } + }, + "value": 659.813 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 65537, + "propertyName": "previousValue", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh] (prev. value)", + "unit": "kWh", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 0 } + }, + "value": 659.813 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 65537, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh] (prev. time delta)", + "unit": "s", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 0 } + }, + "value": 1200 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 66049, + "propertyName": "value", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W]", + "unit": "W", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 2 } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 66049, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W] (prev. time delta)", + "unit": "s", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 2 } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 66561, + "propertyName": "value", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [V]", + "unit": "V", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 4 } + }, + "value": 229.935 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 66561, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [V] (prev. time delta)", + "unit": "s", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 4 } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 66817, + "propertyName": "value", + "propertyKeyName": "Electric_A_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [A]", + "unit": "A", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 5 } + }, + "value": 9.699 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 66817, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_A_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [A] (prev. time delta)", + "unit": "s", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 5 } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "reset", + "propertyName": "reset", + "ccVersion": 3, + "metadata": { + "type": "boolean", + "readable": false, + "writeable": true, + "label": "Reset accumulated values" + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 66049, + "propertyName": "previousValue", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W] (prev. value)", + "unit": "W", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 2 } + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 66561, + "propertyName": "previousValue", + "propertyKeyName": "Electric_V_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [V] (prev. value)", + "unit": "V", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 4 } + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 66817, + "propertyName": "previousValue", + "propertyKeyName": "Electric_A_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [A] (prev. value)", + "unit": "A", + "ccSpecific": { "meterType": 1, "rateType": 1, "scale": 5 } + } + }, + { + "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, + "min": 0, + "max": 255, + "label": "Current value (Red)", + "description": "The current value of the Red color." + }, + "value": 27 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "hexColor", + "propertyName": "hexColor", + "ccVersion": 1, + "metadata": { + "type": "color", + "readable": true, + "writeable": true, + "minLength": 6, + "maxLength": 7, + "label": "RGB Color" + }, + "value": "1b141b" + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "currentColor", + "propertyKey": 3, + "propertyName": "currentColor", + "propertyKeyName": "Green", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Current value (Green)", + "description": "The current value of the Green color." + }, + "value": 20 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "currentColor", + "propertyKey": 4, + "propertyName": "currentColor", + "propertyKeyName": "Blue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Current value (Blue)", + "description": "The current value of the Blue color." + }, + "value": 27 + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyKey": 2, + "propertyName": "targetColor", + "propertyKeyName": "Red", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Target value (Red)", + "description": "The target value of the Red color." + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyKey": 3, + "propertyName": "targetColor", + "propertyKeyName": "Green", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Target value (Green)", + "description": "The target value of the Green color." + } + }, + { + "endpoint": 0, + "commandClass": 51, + "commandClassName": "Color Switch", + "property": "targetColor", + "propertyKey": 4, + "propertyName": "targetColor", + "propertyKeyName": "Blue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "label": "Target value (Blue)", + "description": "The target value of the Blue color." + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Current overload protection enable", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { "0": "disabled", "1": "enabled" }, + "label": "Current overload protection enable", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 20, + "propertyName": "Output load after re-power", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 2, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "last status", + "1": "always on", + "2": "always off" + }, + "label": "Output load after re-power", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 80, + "propertyName": "Enable send to associated devices", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 2, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "nothing", + "1": "hail CC", + "2": "basic CC report" + }, + "label": "Enable send to associated devices", + "description": "Enable to send notifications to Group 1", + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 81, + "propertyName": "Configure LED state", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 2, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "LED follows load", + "1": "LED follows load for 5 seconds", + "2": "Night light mode" + }, + "label": "Configure LED state", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 90, + "propertyName": "Enable items 91 and 92", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { "0": "disabled", "1": "enabled" }, + "label": "Enable items 91 and 92", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 91, + "propertyName": "Wattage Threshold", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 60000, + "default": 25, + "format": 1, + "allowManualEntry": true, + "label": "Wattage Threshold", + "description": "minimum change in wattage to trigger", + "isFromConfig": true + }, + "value": 100 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 92, + "propertyName": "Wattage Percent Change", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 5, + "format": 0, + "allowManualEntry": true, + "label": "Wattage Percent Change", + "description": "minimum change in wattage percent", + "isFromConfig": true + }, + "value": 100 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 101, + "propertyName": "Values to send to group 1", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 15, + "default": 4, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Nothing", + "1": "Voltage", + "2": "Current", + "4": "Wattage", + "8": "kWh", + "15": "All Values" + }, + "label": "Values to send to group 1", + "description": "Which reports need to send in Report group 1", + "isFromConfig": true + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 102, + "propertyName": "Values to send to group 2", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 15, + "default": 8, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Nothing", + "1": "Voltage", + "2": "Current", + "4": "Wattage", + "8": "kWh", + "15": "All Values" + }, + "label": "Values to send to group 2", + "description": "Which reports need to send in Report group 2", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 103, + "propertyName": "Values to send to group 3", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 15, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Nothing", + "1": "Voltage", + "2": "Current", + "4": "Wattage", + "8": "kWh", + "15": "All Values" + }, + "label": "Values to send to group 3", + "description": "Which reports need to send in Report group 3", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 111, + "propertyName": "Time interval for sending to group 1", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 2147483647, + "default": 3, + "format": 0, + "allowManualEntry": true, + "label": "Time interval for sending to group 1", + "description": "Group 1 automatic update interval", + "isFromConfig": true + }, + "value": 1200 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 112, + "propertyName": "Time interval for sending to group 2", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 2147483647, + "default": 600, + "format": 0, + "allowManualEntry": true, + "label": "Time interval for sending to group 2", + "description": "Group 2 automatic update interval", + "isFromConfig": true + }, + "value": 120 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 113, + "propertyName": "Time interval for sending to group 3", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 1, + "max": 2147483647, + "default": 600, + "format": 0, + "allowManualEntry": true, + "label": "Time interval for sending to group 3", + "description": "Group 3 automatic update interval", + "isFromConfig": true + }, + "value": 65460 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 252, + "propertyName": "Configuration Locked", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { "0": "disabled", "1": "enabled" }, + "label": "Configuration Locked", + "description": "Enable/disable Configuration Locked (0 =disable, 1 = enable).", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 83, + "propertyKey": 255, + "propertyName": "Blue night light color value", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 3, + "min": 0, + "max": 255, + "default": 221, + "format": 0, + "allowManualEntry": true, + "label": "Blue night light color value", + "isFromConfig": true + }, + "value": 27 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 83, + "propertyKey": 65280, + "propertyName": "Green night light color value", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 3, + "min": 0, + "max": 255, + "default": 160, + "format": 0, + "allowManualEntry": true, + "label": "Green night light color value", + "isFromConfig": true + }, + "value": 20 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 83, + "propertyKey": 16711680, + "propertyName": "Red night light color value", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 3, + "min": 0, + "max": 255, + "default": 221, + "format": 0, + "allowManualEntry": true, + "label": "Red night light color value", + "description": "Configure the RGB value when it is in Night light mode", + "isFromConfig": true + }, + "value": 27 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 84, + "propertyKey": 255, + "propertyName": "Green brightness in energy mode (%)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 3, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Green brightness in energy mode (%)", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 84, + "propertyKey": 65280, + "propertyName": "Yellow brightness in energy mode (%)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 3, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Yellow brightness in energy mode (%)", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 84, + "propertyKey": 16711680, + "propertyName": "Red brightness in energy mode (%)", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 3, + "min": 0, + "max": 100, + "default": 50, + "format": 0, + "allowManualEntry": true, + "label": "Red brightness in energy mode (%)", + "isFromConfig": true + }, + "value": 50 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 33, + "propertyName": "RGB LED color testing", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 0, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "RGB LED color testing", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 100, + "propertyName": "Set 101\u2010103 to default.", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { "0": "False", "1": "True" }, + "label": "Set 101\u2010103 to default.", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 110, + "propertyName": "Set 111\u2010113 to default.", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { "0": "False", "1": "True" }, + "label": "Set 111\u2010113 to default.", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 255, + "propertyName": "RESET", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": false, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "RESET", + "description": "Reset the device to defaults", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 134 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 96 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.54" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["1.1"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + } + ] + } \ No newline at end of file From b8f7bc12ee9dad5b65a4b201b035aa25948058ca Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Tue, 23 Feb 2021 19:34:25 -0700 Subject: [PATCH 0730/1818] Add switches and sensors to Litter-Robot (#46942) --- .../components/litterrobot/__init__.py | 2 +- .../components/litterrobot/sensor.py | 54 +++++++++++++++ .../components/litterrobot/switch.py | 68 +++++++++++++++++++ .../components/litterrobot/vacuum.py | 13 ++++ tests/components/litterrobot/conftest.py | 24 ++++++- tests/components/litterrobot/test_sensor.py | 20 ++++++ tests/components/litterrobot/test_switch.py | 59 ++++++++++++++++ tests/components/litterrobot/test_vacuum.py | 25 ++----- 8 files changed, 242 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/litterrobot/sensor.py create mode 100644 homeassistant/components/litterrobot/switch.py create mode 100644 tests/components/litterrobot/test_sensor.py create mode 100644 tests/components/litterrobot/test_switch.py diff --git a/homeassistant/components/litterrobot/__init__.py b/homeassistant/components/litterrobot/__init__.py index bf43d5c465eeda..19e76b9bb193d4 100644 --- a/homeassistant/components/litterrobot/__init__.py +++ b/homeassistant/components/litterrobot/__init__.py @@ -10,7 +10,7 @@ from .const import DOMAIN from .hub import LitterRobotHub -PLATFORMS = ["vacuum"] +PLATFORMS = ["sensor", "switch", "vacuum"] async def async_setup(hass: HomeAssistant, config: dict): diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py new file mode 100644 index 00000000000000..2843660bceecb3 --- /dev/null +++ b/homeassistant/components/litterrobot/sensor.py @@ -0,0 +1,54 @@ +"""Support for Litter-Robot sensors.""" +from homeassistant.const import PERCENTAGE +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN +from .hub import LitterRobotEntity + +WASTE_DRAWER = "Waste Drawer" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Litter-Robot sensors using config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + entities = [] + for robot in hub.account.robots: + entities.append(LitterRobotSensor(robot, WASTE_DRAWER, hub)) + + if entities: + async_add_entities(entities, True) + + +class LitterRobotSensor(LitterRobotEntity, Entity): + """Litter-Robot sensors.""" + + @property + def state(self): + """Return the state.""" + return self.robot.waste_drawer_gauge + + @property + def unit_of_measurement(self): + """Return unit of measurement.""" + return PERCENTAGE + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + if self.robot.waste_drawer_gauge <= 10: + return "mdi:gauge-empty" + if self.robot.waste_drawer_gauge < 50: + return "mdi:gauge-low" + if self.robot.waste_drawer_gauge <= 90: + return "mdi:gauge" + return "mdi:gauge-full" + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return { + "cycle_count": self.robot.cycle_count, + "cycle_capacity": self.robot.cycle_capacity, + "cycles_after_drawer_full": self.robot.cycles_after_drawer_full, + } diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py new file mode 100644 index 00000000000000..b94b29a35e1f59 --- /dev/null +++ b/homeassistant/components/litterrobot/switch.py @@ -0,0 +1,68 @@ +"""Support for Litter-Robot switches.""" +from homeassistant.helpers.entity import ToggleEntity + +from .const import DOMAIN +from .hub import LitterRobotEntity + + +class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity): + """Litter-Robot Night Light Mode Switch.""" + + @property + def is_on(self): + """Return true if switch is on.""" + return self.robot.night_light_active + + @property + def icon(self): + """Return the icon.""" + return "mdi:lightbulb-on" if self.is_on else "mdi:lightbulb-off" + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self.perform_action_and_refresh(self.robot.set_night_light, True) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self.perform_action_and_refresh(self.robot.set_night_light, False) + + +class LitterRobotPanelLockoutSwitch(LitterRobotEntity, ToggleEntity): + """Litter-Robot Panel Lockout Switch.""" + + @property + def is_on(self): + """Return true if switch is on.""" + return self.robot.panel_lock_active + + @property + def icon(self): + """Return the icon.""" + return "mdi:lock" if self.is_on else "mdi:lock-open" + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self.perform_action_and_refresh(self.robot.set_panel_lockout, True) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self.perform_action_and_refresh(self.robot.set_panel_lockout, False) + + +ROBOT_SWITCHES = { + "Night Light Mode": LitterRobotNightLightModeSwitch, + "Panel Lockout": LitterRobotPanelLockoutSwitch, +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Litter-Robot switches using config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + entities = [] + for robot in hub.account.robots: + for switch_type, switch_class in ROBOT_SWITCHES.items(): + entities.append(switch_class(robot, switch_type, hub)) + + if entities: + async_add_entities(entities, True) diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index a57c1ffead513e..6ee92993869b69 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -14,6 +14,7 @@ VacuumEntity, ) from homeassistant.const import STATE_OFF +import homeassistant.util.dt as dt_util from .const import DOMAIN from .hub import LitterRobotEntity @@ -118,9 +119,21 @@ async def async_send_command(self, command, params=None, **kwargs): @property def device_state_attributes(self): """Return device specific state attributes.""" + [sleep_mode_start_time, sleep_mode_end_time] = [None, None] + + if self.robot.sleep_mode_active: + sleep_mode_start_time = dt_util.as_local( + self.robot.sleep_mode_start_time + ).strftime("%H:%M:00") + sleep_mode_end_time = dt_util.as_local( + self.robot.sleep_mode_end_time + ).strftime("%H:%M:00") + return { "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, "is_sleeping": self.robot.is_sleeping, + "sleep_mode_start_time": sleep_mode_start_time, + "sleep_mode_end_time": sleep_mode_end_time, "power_status": self.robot.power_status, "unit_status_code": self.robot.unit_status.name, "last_seen": self.robot.last_seen, diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index 2f967d266bc24d..dae183b4cf6f04 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,5 +1,5 @@ """Configure pytest for Litter-Robot tests.""" -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock, MagicMock, patch from pylitterbot import Robot import pytest @@ -7,7 +7,9 @@ from homeassistant.components import litterrobot from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .common import ROBOT_DATA +from .common import CONFIG, ROBOT_DATA + +from tests.common import MockConfigEntry def create_mock_robot(hass): @@ -17,6 +19,8 @@ def create_mock_robot(hass): robot.set_power_status = AsyncMock() robot.reset_waste_drawer = AsyncMock() robot.set_sleep_mode = AsyncMock() + robot.set_night_light = AsyncMock() + robot.set_panel_lockout = AsyncMock() return robot @@ -33,3 +37,19 @@ def mock_hub(hass): hub.coordinator.last_update_success = True hub.account.robots = [create_mock_robot(hass)] return hub + + +async def setup_hub(hass, mock_hub, platform_domain): + """Load a Litter-Robot platform with the provided hub.""" + entry = MockConfigEntry( + domain=litterrobot.DOMAIN, + data=CONFIG[litterrobot.DOMAIN], + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.litterrobot.LitterRobotHub", + return_value=mock_hub, + ): + await hass.config_entries.async_forward_entry_setup(entry, platform_domain) + await hass.async_block_till_done() diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py new file mode 100644 index 00000000000000..2421489e2376b5 --- /dev/null +++ b/tests/components/litterrobot/test_sensor.py @@ -0,0 +1,20 @@ +"""Test the Litter-Robot sensor entity.""" +from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN +from homeassistant.const import PERCENTAGE + +from .conftest import setup_hub + +ENTITY_ID = "sensor.test_waste_drawer" + + +async def test_sensor(hass, mock_hub): + """Tests the sensor entity was set up.""" + await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + + sensor = hass.states.get(ENTITY_ID) + assert sensor + assert sensor.state == "50" + assert sensor.attributes["cycle_count"] == 15 + assert sensor.attributes["cycle_capacity"] == 30 + assert sensor.attributes["cycles_after_drawer_full"] == 0 + assert sensor.attributes["unit_of_measurement"] == PERCENTAGE diff --git a/tests/components/litterrobot/test_switch.py b/tests/components/litterrobot/test_switch.py new file mode 100644 index 00000000000000..c7f85db74125b8 --- /dev/null +++ b/tests/components/litterrobot/test_switch.py @@ -0,0 +1,59 @@ +"""Test the Litter-Robot switch entity.""" +from datetime import timedelta + +import pytest + +from homeassistant.components.litterrobot.hub import REFRESH_WAIT_TIME +from homeassistant.components.switch import ( + DOMAIN as PLATFORM_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_ON +from homeassistant.util.dt import utcnow + +from .conftest import setup_hub + +from tests.common import async_fire_time_changed + +NIGHT_LIGHT_MODE_ENTITY_ID = "switch.test_night_light_mode" +PANEL_LOCKOUT_ENTITY_ID = "switch.test_panel_lockout" + + +async def test_switch(hass, mock_hub): + """Tests the switch entity was set up.""" + await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + + switch = hass.states.get(NIGHT_LIGHT_MODE_ENTITY_ID) + assert switch + assert switch.state == STATE_ON + + +@pytest.mark.parametrize( + "entity_id,robot_command", + [ + (NIGHT_LIGHT_MODE_ENTITY_ID, "set_night_light"), + (PANEL_LOCKOUT_ENTITY_ID, "set_panel_lockout"), + ], +) +async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): + """Test sending commands to the switch.""" + await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + + switch = hass.states.get(entity_id) + assert switch + + data = {ATTR_ENTITY_ID: entity_id} + + count = 0 + for service in [SERVICE_TURN_ON, SERVICE_TURN_OFF]: + count += 1 + await hass.services.async_call( + PLATFORM_DOMAIN, + service, + data, + blocking=True, + ) + future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) + async_fire_time_changed(hass, future) + assert getattr(mock_hub.account.robots[0], robot_command).call_count == count diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py index b47eff64e136e6..03e63b472b62f4 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -1,10 +1,8 @@ """Test the Litter-Robot vacuum entity.""" from datetime import timedelta -from unittest.mock import patch import pytest -from homeassistant.components import litterrobot from homeassistant.components.litterrobot.hub import REFRESH_WAIT_TIME from homeassistant.components.vacuum import ( ATTR_PARAMS, @@ -18,32 +16,19 @@ from homeassistant.const import ATTR_COMMAND, ATTR_ENTITY_ID from homeassistant.util.dt import utcnow -from .common import CONFIG +from .conftest import setup_hub -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import async_fire_time_changed ENTITY_ID = "vacuum.test_litter_box" -async def setup_hub(hass, mock_hub): - """Load the Litter-Robot vacuum platform with the provided hub.""" - hass.config.components.add(litterrobot.DOMAIN) - entry = MockConfigEntry( - domain=litterrobot.DOMAIN, - data=CONFIG[litterrobot.DOMAIN], - ) - - with patch.dict(hass.data, {litterrobot.DOMAIN: {entry.entry_id: mock_hub}}): - await hass.config_entries.async_forward_entry_setup(entry, PLATFORM_DOMAIN) - await hass.async_block_till_done() - - async def test_vacuum(hass, mock_hub): """Tests the vacuum entity was set up.""" - await setup_hub(hass, mock_hub) + await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) - assert vacuum is not None + assert vacuum assert vacuum.state == STATE_DOCKED assert vacuum.attributes["is_sleeping"] is False @@ -71,7 +56,7 @@ async def test_vacuum(hass, mock_hub): ) async def test_commands(hass, mock_hub, service, command, extra): """Test sending commands to the vacuum.""" - await setup_hub(hass, mock_hub) + await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) assert vacuum is not None From 19f5b467b72413dc63fb307659a8a7534afc1bff Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 23 Feb 2021 18:38:52 -0800 Subject: [PATCH 0731/1818] Make FAN_ON use the max duration rather than 15 min default (#46489) --- homeassistant/components/nest/climate_sdm.py | 7 ++++++- tests/components/nest/climate_sdm_test.py | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index 6413b2e0dfe089..b0c64329ffdf34 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -75,6 +75,8 @@ } FAN_INV_MODE_MAP = {v: k for k, v in FAN_MODE_MAP.items()} +MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API + async def async_setup_sdm_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities @@ -322,4 +324,7 @@ async def async_set_fan_mode(self, fan_mode): if fan_mode not in self.fan_modes: raise ValueError(f"Unsupported fan_mode '{fan_mode}'") trait = self._device.traits[FanTrait.NAME] - await trait.set_timer(FAN_INV_MODE_MAP[fan_mode]) + duration = None + if fan_mode != FAN_OFF: + duration = MAX_FAN_DURATION + await trait.set_timer(FAN_INV_MODE_MAP[fan_mode], duration=duration) diff --git a/tests/components/nest/climate_sdm_test.py b/tests/components/nest/climate_sdm_test.py index ef332d0e8480ba..888227b9cdeb64 100644 --- a/tests/components/nest/climate_sdm_test.py +++ b/tests/components/nest/climate_sdm_test.py @@ -819,6 +819,20 @@ async def test_thermostat_set_fan(hass, auth): "params": {"timerMode": "OFF"}, } + # Turn on fan mode + await common.async_set_fan_mode(hass, FAN_ON) + await hass.async_block_till_done() + + assert auth.method == "post" + assert auth.url == "some-device-id:executeCommand" + assert auth.json == { + "command": "sdm.devices.commands.Fan.SetTimer", + "params": { + "duration": "43200s", + "timerMode": "ON", + }, + } + async def test_thermostat_fan_empty(hass): """Test a fan trait with an empty response.""" @@ -938,7 +952,7 @@ async def test_thermostat_set_hvac_fan_only(hass, auth): assert url == "some-device-id:executeCommand" assert json == { "command": "sdm.devices.commands.Fan.SetTimer", - "params": {"timerMode": "ON"}, + "params": {"duration": "43200s", "timerMode": "ON"}, } (method, url, json, headers) = auth.captured_requests.pop(0) assert method == "post" From 3e26e2adad9fc077ddf63d7b254c324cff3c5cb2 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Wed, 24 Feb 2021 04:00:47 +0100 Subject: [PATCH 0732/1818] Handle device IP change in upnp (#46859) --- homeassistant/components/upnp/__init__.py | 5 ++++- homeassistant/components/upnp/config_flow.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 5d251ce7dd823a..d5be0757cf3c0b 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -123,7 +123,10 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) ) # Ensure entry has a hostname, for older entries. - if CONFIG_ENTRY_HOSTNAME not in config_entry.data: + if ( + CONFIG_ENTRY_HOSTNAME not in config_entry.data + or config_entry.data[CONFIG_ENTRY_HOSTNAME] != device.hostname + ): hass.config_entries.async_update_entry( entry=config_entry, data={CONFIG_ENTRY_HOSTNAME: device.hostname, **config_entry.data}, diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 1d212441bfa532..e1101c3713c21c 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -179,7 +179,9 @@ async def async_step_ssdp(self, discovery_info: Mapping) -> Mapping[str, Any]: discovery = await Device.async_supplement_discovery(self.hass, discovery) unique_id = discovery[DISCOVERY_UNIQUE_ID] await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured( + updates={CONFIG_ENTRY_HOSTNAME: discovery[DISCOVERY_HOSTNAME]} + ) # Handle devices changing their UDN, only allow a single existing_entries = self.hass.config_entries.async_entries(DOMAIN) From 42fd3be0e8ad51735d93aa67ecfd24d487cbd06e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Feb 2021 07:46:00 +0100 Subject: [PATCH 0733/1818] Add template support to service targets (#46977) Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/config_validation.py | 13 ++++++++++--- homeassistant/helpers/service.py | 18 +++++++++++++++++- tests/helpers/test_service.py | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 6b0737ae346989..422f940e98e09e 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -847,11 +847,18 @@ def custom_serializer(schema: Any) -> Any: PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) ENTITY_SERVICE_FIELDS = { - vol.Optional(ATTR_ENTITY_ID): comp_entity_ids, + # Either accept static entity IDs, a single dynamic template or a mixed list + # of static and dynamic templates. While this could be solved with a single + # complex template, handling it like this, keeps config validation useful. + vol.Optional(ATTR_ENTITY_ID): vol.Any( + comp_entity_ids, dynamic_template, vol.All(list, template_complex) + ), vol.Optional(ATTR_DEVICE_ID): vol.Any( - ENTITY_MATCH_NONE, vol.All(ensure_list, [str]) + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), + vol.Optional(ATTR_AREA_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), - vol.Optional(ATTR_AREA_ID): vol.Any(ENTITY_MATCH_NONE, vol.All(ensure_list, [str])), } diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 7983190dbe836f..932384493f3143 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -28,6 +28,7 @@ ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, + CONF_ENTITY_ID, CONF_SERVICE, CONF_SERVICE_DATA, CONF_SERVICE_TEMPLATE, @@ -189,7 +190,22 @@ def async_prepare_call_from_config( domain, service = domain_service.split(".", 1) - target = config.get(CONF_TARGET) + target = {} + if CONF_TARGET in config: + conf = config.get(CONF_TARGET) + try: + template.attach(hass, conf) + target.update(template.render_complex(conf, variables)) + if CONF_ENTITY_ID in target: + target[CONF_ENTITY_ID] = cv.comp_entity_ids(target[CONF_ENTITY_ID]) + except TemplateError as ex: + raise HomeAssistantError( + f"Error rendering service target template: {ex}" + ) from ex + except vol.Invalid as ex: + raise HomeAssistantError( + f"Template rendered invalid entity IDs: {target[CONF_ENTITY_ID]}" + ) from ex service_data = {} diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 95ccdc843951ba..92cbd5514e6cd3 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -195,6 +195,24 @@ def test_service_call(self): "area_id": ["test-area-id"], } + config = { + "service": "{{ 'test_domain.test_service' }}", + "target": { + "area_id": ["area-42", "{{ 'area-51' }}"], + "device_id": ["abcdef", "{{ 'fedcba' }}"], + "entity_id": ["light.static", "{{ 'light.dynamic' }}"], + }, + } + + service.call_from_config(self.hass, config) + self.hass.block_till_done() + + assert dict(self.calls[1].data) == { + "area_id": ["area-42", "area-51"], + "device_id": ["abcdef", "fedcba"], + "entity_id": ["light.static", "light.dynamic"], + } + def test_service_template_service_call(self): """Test legacy service_template call with templating.""" config = { From a6322155411575d5e1d00af59a793773ccc2f189 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Wed, 24 Feb 2021 08:37:41 +0100 Subject: [PATCH 0734/1818] Validate KNX addresses (#46933) --- homeassistant/components/knx/__init__.py | 14 +- homeassistant/components/knx/schema.py | 453 ++++++++++++----------- 2 files changed, 246 insertions(+), 221 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 8265ba42b7233e..b8feb010e29f45 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -47,6 +47,8 @@ SensorSchema, SwitchSchema, WeatherSchema, + ga_validator, + ia_validator, ) _LOGGER = logging.getLogger(__name__) @@ -92,7 +94,7 @@ ), vol.Optional( CONF_KNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS - ): cv.string, + ): ia_validator, vol.Optional( CONF_KNX_MCAST_GRP, default=DEFAULT_MCAST_GRP ): cv.string, @@ -146,7 +148,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( { - vol.Required(CONF_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), } @@ -154,7 +156,7 @@ vol.Schema( # without type given payload is treated as raw bytes { - vol.Required(CONF_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any( cv.positive_int, [cv.positive_int] ), @@ -166,14 +168,14 @@ { vol.Required(CONF_ADDRESS): vol.All( cv.ensure_list, - [cv.string], + [ga_validator], ) } ) SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } ) @@ -187,7 +189,7 @@ vol.Schema( # for removing only `address` is required { - vol.Required(CONF_ADDRESS): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True), }, extra=vol.ALLOW_EXTRA, diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 1a48d7945bd2c8..08a8c62adc421b 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -2,6 +2,7 @@ import voluptuous as vol from xknx.devices.climate import SetpointShiftMode from xknx.io import DEFAULT_MCAST_PORT +from xknx.telegram.address import GroupAddress, IndividualAddress from homeassistant.const import ( CONF_ADDRESS, @@ -24,6 +25,33 @@ ColorTempModes, ) +################## +# KNX VALIDATORS +################## + +ga_validator = vol.Any( + cv.matches_regex(GroupAddress.ADDRESS_RE), + vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + msg="value does not match pattern for KNX group address '
//', '
/' or '' (eg.'1/2/3', '9/234', '123')", +) + +ia_validator = vol.Any( + cv.matches_regex(IndividualAddress.ADDRESS_RE), + vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), + msg="value does not match pattern for KNX individual address '..' (eg.'1.1.100')", +) + +sync_state_validator = vol.Any( + vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), + cv.boolean, + cv.matches_regex(r"^(init|expire|every)( \d*)?$"), +) + + +############## +# CONNECTION +############## + class ConnectionSchema: """Voluptuous schema for KNX connection.""" @@ -41,45 +69,9 @@ class ConnectionSchema: ROUTING_SCHEMA = vol.Maybe(vol.Schema({vol.Optional(CONF_KNX_LOCAL_IP): cv.string})) -class CoverSchema: - """Voluptuous schema for KNX covers.""" - - CONF_MOVE_LONG_ADDRESS = "move_long_address" - CONF_MOVE_SHORT_ADDRESS = "move_short_address" - CONF_STOP_ADDRESS = "stop_address" - CONF_POSITION_ADDRESS = "position_address" - CONF_POSITION_STATE_ADDRESS = "position_state_address" - CONF_ANGLE_ADDRESS = "angle_address" - CONF_ANGLE_STATE_ADDRESS = "angle_state_address" - CONF_TRAVELLING_TIME_DOWN = "travelling_time_down" - CONF_TRAVELLING_TIME_UP = "travelling_time_up" - CONF_INVERT_POSITION = "invert_position" - CONF_INVERT_ANGLE = "invert_angle" - - DEFAULT_TRAVEL_TIME = 25 - DEFAULT_NAME = "KNX Cover" - - SCHEMA = vol.Schema( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string, - vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string, - vol.Optional(CONF_STOP_ADDRESS): cv.string, - vol.Optional(CONF_POSITION_ADDRESS): cv.string, - vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string, - vol.Optional(CONF_ANGLE_ADDRESS): cv.string, - vol.Optional(CONF_ANGLE_STATE_ADDRESS): cv.string, - vol.Optional( - CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME - ): cv.positive_int, - vol.Optional( - CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME - ): cv.positive_int, - vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, - vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, - vol.Optional(CONF_DEVICE_CLASS): cv.string, - } - ) +############# +# PLATFORMS +############# class BinarySensorSchema: @@ -100,13 +92,9 @@ class BinarySensorSchema: vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SYNC_STATE, default=True): vol.Any( - vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), - cv.boolean, - cv.string, - ), + vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean, - vol.Required(CONF_STATE_ADDRESS): cv.string, + vol.Required(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_CONTEXT_TIMEOUT): vol.All( vol.Coerce(float), vol.Range(min=0, max=10) ), @@ -118,95 +106,6 @@ class BinarySensorSchema: ) -class LightSchema: - """Voluptuous schema for KNX lights.""" - - CONF_STATE_ADDRESS = CONF_STATE_ADDRESS - CONF_BRIGHTNESS_ADDRESS = "brightness_address" - CONF_BRIGHTNESS_STATE_ADDRESS = "brightness_state_address" - CONF_COLOR_ADDRESS = "color_address" - CONF_COLOR_STATE_ADDRESS = "color_state_address" - CONF_COLOR_TEMP_ADDRESS = "color_temperature_address" - CONF_COLOR_TEMP_STATE_ADDRESS = "color_temperature_state_address" - CONF_COLOR_TEMP_MODE = "color_temperature_mode" - CONF_RGBW_ADDRESS = "rgbw_address" - CONF_RGBW_STATE_ADDRESS = "rgbw_state_address" - CONF_MIN_KELVIN = "min_kelvin" - CONF_MAX_KELVIN = "max_kelvin" - - DEFAULT_NAME = "KNX Light" - DEFAULT_COLOR_TEMP_MODE = "absolute" - DEFAULT_MIN_KELVIN = 2700 # 370 mireds - DEFAULT_MAX_KELVIN = 6000 # 166 mireds - - CONF_INDIVIDUAL_COLORS = "individual_colors" - CONF_RED = "red" - CONF_GREEN = "green" - CONF_BLUE = "blue" - CONF_WHITE = "white" - - COLOR_SCHEMA = vol.Schema( - { - vol.Optional(CONF_ADDRESS): cv.string, - vol.Optional(CONF_STATE_ADDRESS): cv.string, - vol.Required(CONF_BRIGHTNESS_ADDRESS): cv.string, - vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string, - } - ) - - SCHEMA = vol.All( - vol.Schema( - { - vol.Optional(CONF_ADDRESS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_STATE_ADDRESS): cv.string, - vol.Optional(CONF_BRIGHTNESS_ADDRESS): cv.string, - vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string, - vol.Exclusive(CONF_INDIVIDUAL_COLORS, "color"): { - vol.Inclusive(CONF_RED, "colors"): COLOR_SCHEMA, - vol.Inclusive(CONF_GREEN, "colors"): COLOR_SCHEMA, - vol.Inclusive(CONF_BLUE, "colors"): COLOR_SCHEMA, - vol.Optional(CONF_WHITE): COLOR_SCHEMA, - }, - vol.Exclusive(CONF_COLOR_ADDRESS, "color"): cv.string, - vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string, - vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string, - vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string, - vol.Optional( - CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE - ): vol.All(vol.Upper, cv.enum(ColorTempModes)), - vol.Exclusive(CONF_RGBW_ADDRESS, "color"): cv.string, - vol.Optional(CONF_RGBW_STATE_ADDRESS): cv.string, - vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All( - vol.Coerce(int), vol.Range(min=1) - ), - vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All( - vol.Coerce(int), vol.Range(min=1) - ), - } - ), - vol.Any( - # either global "address" or all addresses for individual colors are required - vol.Schema( - { - vol.Required(CONF_INDIVIDUAL_COLORS): { - vol.Required(CONF_RED): {vol.Required(CONF_ADDRESS): object}, - vol.Required(CONF_GREEN): {vol.Required(CONF_ADDRESS): object}, - vol.Required(CONF_BLUE): {vol.Required(CONF_ADDRESS): object}, - }, - }, - extra=vol.ALLOW_EXTRA, - ), - vol.Schema( - { - vol.Required(CONF_ADDRESS): object, - }, - extra=vol.ALLOW_EXTRA, - ), - ), - ) - - class ClimateSchema: """Voluptuous schema for KNX climate devices.""" @@ -266,25 +165,27 @@ class ClimateSchema: vol.Optional( CONF_TEMPERATURE_STEP, default=DEFAULT_TEMPERATURE_STEP ): vol.All(float, vol.Range(min=0, max=2)), - vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string, - vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): cv.string, - vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string, - vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string, - vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string, - vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string, - vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string, - vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string, - vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string, - vol.Optional(CONF_HEAT_COOL_ADDRESS): cv.string, - vol.Optional(CONF_HEAT_COOL_STATE_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): cv.string, - vol.Optional(CONF_ON_OFF_ADDRESS): cv.string, - vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string, + vol.Required(CONF_TEMPERATURE_ADDRESS): ga_validator, + vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): ga_validator, + vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): ga_validator, + vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_OPERATION_MODE_ADDRESS): ga_validator, + vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): ga_validator, + vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): ga_validator, + vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_HEAT_COOL_ADDRESS): ga_validator, + vol.Optional(CONF_HEAT_COOL_STATE_ADDRESS): ga_validator, + vol.Optional( + CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS + ): ga_validator, + vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): ga_validator, + vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): ga_validator, + vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): ga_validator, + vol.Optional(CONF_ON_OFF_ADDRESS): ga_validator, + vol.Optional(CONF_ON_OFF_STATE_ADDRESS): ga_validator, vol.Optional( CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT ): cv.boolean, @@ -304,19 +205,43 @@ class ClimateSchema: ) -class SwitchSchema: - """Voluptuous schema for KNX switches.""" +class CoverSchema: + """Voluptuous schema for KNX covers.""" - CONF_INVERT = CONF_INVERT - CONF_STATE_ADDRESS = CONF_STATE_ADDRESS + CONF_MOVE_LONG_ADDRESS = "move_long_address" + CONF_MOVE_SHORT_ADDRESS = "move_short_address" + CONF_STOP_ADDRESS = "stop_address" + CONF_POSITION_ADDRESS = "position_address" + CONF_POSITION_STATE_ADDRESS = "position_state_address" + CONF_ANGLE_ADDRESS = "angle_address" + CONF_ANGLE_STATE_ADDRESS = "angle_state_address" + CONF_TRAVELLING_TIME_DOWN = "travelling_time_down" + CONF_TRAVELLING_TIME_UP = "travelling_time_up" + CONF_INVERT_POSITION = "invert_position" + CONF_INVERT_ANGLE = "invert_angle" + + DEFAULT_TRAVEL_TIME = 25 + DEFAULT_NAME = "KNX Cover" - DEFAULT_NAME = "KNX Switch" SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_STATE_ADDRESS): cv.string, - vol.Optional(CONF_INVERT): cv.boolean, + vol.Optional(CONF_MOVE_LONG_ADDRESS): ga_validator, + vol.Optional(CONF_MOVE_SHORT_ADDRESS): ga_validator, + vol.Optional(CONF_STOP_ADDRESS): ga_validator, + vol.Optional(CONF_POSITION_ADDRESS): ga_validator, + vol.Optional(CONF_POSITION_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_ANGLE_ADDRESS): ga_validator, + vol.Optional(CONF_ANGLE_STATE_ADDRESS): ga_validator, + vol.Optional( + CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME + ): cv.positive_float, + vol.Optional( + CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME + ): cv.positive_float, + vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, + vol.Optional(CONF_DEVICE_CLASS): cv.string, } ) @@ -331,14 +256,125 @@ class ExposeSchema: SCHEMA = vol.Schema( { vol.Required(CONF_KNX_EXPOSE_TYPE): vol.Any(int, float, str), + vol.Required(CONF_ADDRESS): ga_validator, vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, - vol.Required(CONF_ADDRESS): cv.string, } ) +class FanSchema: + """Voluptuous schema for KNX fans.""" + + CONF_STATE_ADDRESS = CONF_STATE_ADDRESS + CONF_OSCILLATION_ADDRESS = "oscillation_address" + CONF_OSCILLATION_STATE_ADDRESS = "oscillation_state_address" + CONF_MAX_STEP = "max_step" + + DEFAULT_NAME = "KNX Fan" + + SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, + vol.Optional(CONF_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_OSCILLATION_ADDRESS): ga_validator, + vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_MAX_STEP): cv.byte, + } + ) + + +class LightSchema: + """Voluptuous schema for KNX lights.""" + + CONF_STATE_ADDRESS = CONF_STATE_ADDRESS + CONF_BRIGHTNESS_ADDRESS = "brightness_address" + CONF_BRIGHTNESS_STATE_ADDRESS = "brightness_state_address" + CONF_COLOR_ADDRESS = "color_address" + CONF_COLOR_STATE_ADDRESS = "color_state_address" + CONF_COLOR_TEMP_ADDRESS = "color_temperature_address" + CONF_COLOR_TEMP_STATE_ADDRESS = "color_temperature_state_address" + CONF_COLOR_TEMP_MODE = "color_temperature_mode" + CONF_RGBW_ADDRESS = "rgbw_address" + CONF_RGBW_STATE_ADDRESS = "rgbw_state_address" + CONF_MIN_KELVIN = "min_kelvin" + CONF_MAX_KELVIN = "max_kelvin" + + DEFAULT_NAME = "KNX Light" + DEFAULT_COLOR_TEMP_MODE = "absolute" + DEFAULT_MIN_KELVIN = 2700 # 370 mireds + DEFAULT_MAX_KELVIN = 6000 # 166 mireds + + CONF_INDIVIDUAL_COLORS = "individual_colors" + CONF_RED = "red" + CONF_GREEN = "green" + CONF_BLUE = "blue" + CONF_WHITE = "white" + + COLOR_SCHEMA = vol.Schema( + { + vol.Optional(CONF_ADDRESS): ga_validator, + vol.Optional(CONF_STATE_ADDRESS): ga_validator, + vol.Required(CONF_BRIGHTNESS_ADDRESS): ga_validator, + vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator, + } + ) + + SCHEMA = vol.All( + vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_ADDRESS): ga_validator, + vol.Optional(CONF_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_BRIGHTNESS_ADDRESS): ga_validator, + vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator, + vol.Exclusive(CONF_INDIVIDUAL_COLORS, "color"): { + vol.Inclusive(CONF_RED, "colors"): COLOR_SCHEMA, + vol.Inclusive(CONF_GREEN, "colors"): COLOR_SCHEMA, + vol.Inclusive(CONF_BLUE, "colors"): COLOR_SCHEMA, + vol.Optional(CONF_WHITE): COLOR_SCHEMA, + }, + vol.Exclusive(CONF_COLOR_ADDRESS, "color"): ga_validator, + vol.Optional(CONF_COLOR_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_COLOR_TEMP_ADDRESS): ga_validator, + vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): ga_validator, + vol.Optional( + CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE + ): vol.All(vol.Upper, cv.enum(ColorTempModes)), + vol.Exclusive(CONF_RGBW_ADDRESS, "color"): ga_validator, + vol.Optional(CONF_RGBW_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } + ), + vol.Any( + # either global "address" or all addresses for individual colors are required + vol.Schema( + { + vol.Required(CONF_INDIVIDUAL_COLORS): { + vol.Required(CONF_RED): {vol.Required(CONF_ADDRESS): object}, + vol.Required(CONF_GREEN): {vol.Required(CONF_ADDRESS): object}, + vol.Required(CONF_BLUE): {vol.Required(CONF_ADDRESS): object}, + }, + }, + extra=vol.ALLOW_EXTRA, + ), + vol.Schema( + { + vol.Required(CONF_ADDRESS): object, + }, + extra=vol.ALLOW_EXTRA, + ), + ), + ) + + class NotifySchema: """Voluptuous schema for KNX notifications.""" @@ -346,8 +382,23 @@ class NotifySchema: SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, + } + ) + + +class SceneSchema: + """Voluptuous schema for KNX scenes.""" + + CONF_SCENE_NUMBER = "scene_number" + + DEFAULT_NAME = "KNX SCENE" + SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_SCENE_NUMBER): cv.positive_int, } ) @@ -363,29 +414,27 @@ class SensorSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SYNC_STATE, default=True): vol.Any( - vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), - cv.boolean, - cv.string, - ), + vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_ALWAYS_CALLBACK, default=False): cv.boolean, - vol.Required(CONF_STATE_ADDRESS): cv.string, + vol.Required(CONF_STATE_ADDRESS): ga_validator, vol.Required(CONF_TYPE): vol.Any(int, float, str), } ) -class SceneSchema: - """Voluptuous schema for KNX scenes.""" +class SwitchSchema: + """Voluptuous schema for KNX switches.""" - CONF_SCENE_NUMBER = "scene_number" + CONF_INVERT = CONF_INVERT + CONF_STATE_ADDRESS = CONF_STATE_ADDRESS - DEFAULT_NAME = "KNX SCENE" + DEFAULT_NAME = "KNX Switch" SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): cv.string, - vol.Required(CONF_SCENE_NUMBER): cv.positive_int, + vol.Required(CONF_ADDRESS): ga_validator, + vol.Optional(CONF_STATE_ADDRESS): ga_validator, + vol.Optional(CONF_INVERT): cv.boolean, } ) @@ -414,46 +463,20 @@ class WeatherSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SYNC_STATE, default=True): vol.Any( - vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)), - cv.boolean, - cv.string, - ), + vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_KNX_CREATE_SENSORS, default=False): cv.boolean, - vol.Required(CONF_KNX_TEMPERATURE_ADDRESS): cv.string, - vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): cv.string, - vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): cv.string, - vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): cv.string, - vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): cv.string, - vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): cv.string, - vol.Optional(CONF_KNX_WIND_BEARING_ADDRESS): cv.string, - vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): cv.string, - vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): cv.string, - vol.Optional(CONF_KNX_WIND_ALARM_ADDRESS): cv.string, - vol.Optional(CONF_KNX_DAY_NIGHT_ADDRESS): cv.string, - vol.Optional(CONF_KNX_AIR_PRESSURE_ADDRESS): cv.string, - vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): cv.string, - } - ) - - -class FanSchema: - """Voluptuous schema for KNX fans.""" - - CONF_STATE_ADDRESS = CONF_STATE_ADDRESS - CONF_OSCILLATION_ADDRESS = "oscillation_address" - CONF_OSCILLATION_STATE_ADDRESS = "oscillation_state_address" - CONF_MAX_STEP = "max_step" - - DEFAULT_NAME = "KNX Fan" - - SCHEMA = vol.Schema( - { - vol.Required(CONF_ADDRESS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_STATE_ADDRESS): cv.string, - vol.Optional(CONF_OSCILLATION_ADDRESS): cv.string, - vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): cv.string, - vol.Optional(CONF_MAX_STEP): cv.byte, + vol.Required(CONF_KNX_TEMPERATURE_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_WIND_BEARING_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_WIND_ALARM_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_DAY_NIGHT_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_AIR_PRESSURE_ADDRESS): ga_validator, + vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): ga_validator, } ) From eccdae60bfb72d3af1c137e4629c8476caffb34c Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Feb 2021 03:34:27 -0500 Subject: [PATCH 0735/1818] Add ClimaCell weather integration (#36547) Co-authored-by: Martin Hjelmare --- .coveragerc | 1 + CODEOWNERS | 1 + .../components/climacell/__init__.py | 262 +++++++++++++++ .../components/climacell/config_flow.py | 146 +++++++++ homeassistant/components/climacell/const.py | 79 +++++ .../components/climacell/manifest.json | 8 + .../components/climacell/strings.json | 34 ++ homeassistant/components/climacell/weather.py | 305 ++++++++++++++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/climacell/__init__.py | 1 + tests/components/climacell/conftest.py | 42 +++ tests/components/climacell/const.py | 9 + .../components/climacell/test_config_flow.py | 167 ++++++++++ tests/components/climacell/test_init.py | 82 +++++ 16 files changed, 1144 insertions(+) create mode 100644 homeassistant/components/climacell/__init__.py create mode 100644 homeassistant/components/climacell/config_flow.py create mode 100644 homeassistant/components/climacell/const.py create mode 100644 homeassistant/components/climacell/manifest.json create mode 100644 homeassistant/components/climacell/strings.json create mode 100644 homeassistant/components/climacell/weather.py create mode 100644 tests/components/climacell/__init__.py create mode 100644 tests/components/climacell/conftest.py create mode 100644 tests/components/climacell/const.py create mode 100644 tests/components/climacell/test_config_flow.py create mode 100644 tests/components/climacell/test_init.py diff --git a/.coveragerc b/.coveragerc index 899577f2acfef2..c0a28f70a4fbd1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -145,6 +145,7 @@ omit = homeassistant/components/clickatell/notify.py homeassistant/components/clicksend/notify.py homeassistant/components/clicksend_tts/notify.py + homeassistant/components/climacell/weather.py homeassistant/components/cmus/media_player.py homeassistant/components/co2signal/* homeassistant/components/coinbase/* diff --git a/CODEOWNERS b/CODEOWNERS index e4e2ab59615a13..2db89f09948472 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -83,6 +83,7 @@ homeassistant/components/circuit/* @braam homeassistant/components/cisco_ios/* @fbradyirl homeassistant/components/cisco_mobility_express/* @fbradyirl homeassistant/components/cisco_webex_teams/* @fbradyirl +homeassistant/components/climacell/* @raman325 homeassistant/components/cloud/* @home-assistant/cloud homeassistant/components/cloudflare/* @ludeeus @ctalkington homeassistant/components/color_extractor/* @GenericStudent diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py new file mode 100644 index 00000000000000..d6bf0ec4e124d3 --- /dev/null +++ b/homeassistant/components/climacell/__init__.py @@ -0,0 +1,262 @@ +"""The ClimaCell integration.""" +import asyncio +from datetime import timedelta +import logging +from math import ceil +from typing import Any, Dict, Optional, Union + +from pyclimacell import ClimaCell +from pyclimacell.const import ( + FORECAST_DAILY, + FORECAST_HOURLY, + FORECAST_NOWCAST, + REALTIME, +) +from pyclimacell.pyclimacell import ( + CantConnectException, + InvalidAPIKeyException, + RateLimitedException, + UnknownException, +) + +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .const import ( + ATTRIBUTION, + CONF_TIMESTEP, + CURRENT, + DAILY, + DEFAULT_TIMESTEP, + DOMAIN, + FORECASTS, + HOURLY, + MAX_REQUESTS_PER_DAY, + NOWCAST, +) + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = [WEATHER_DOMAIN] + + +def _set_update_interval( + hass: HomeAssistantType, current_entry: ConfigEntry +) -> timedelta: + """Recalculate update_interval based on existing ClimaCell instances and update them.""" + # We check how many ClimaCell configured instances are using the same API key and + # calculate interval to not exceed allowed numbers of requests. Divide 90% of + # MAX_REQUESTS_PER_DAY by 4 because every update requires four API calls and we want + # a buffer in the number of API calls left at the end of the day. + other_instance_entry_ids = [ + entry.entry_id + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.entry_id != current_entry.entry_id + and entry.data[CONF_API_KEY] == current_entry.data[CONF_API_KEY] + ] + + interval = timedelta( + minutes=( + ceil( + (24 * 60 * (len(other_instance_entry_ids) + 1) * 4) + / (MAX_REQUESTS_PER_DAY * 0.9) + ) + ) + ) + + for entry_id in other_instance_entry_ids: + if entry_id in hass.data[DOMAIN]: + hass.data[DOMAIN][entry_id].update_interval = interval + + return interval + + +async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: + """Set up the ClimaCell API component.""" + return True + + +async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool: + """Set up ClimaCell API from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + + # If config entry options not set up, set them up + if not config_entry.options: + hass.config_entries.async_update_entry( + config_entry, + options={ + CONF_TIMESTEP: DEFAULT_TIMESTEP, + }, + ) + + coordinator = ClimaCellDataUpdateCoordinator( + hass, + config_entry, + ClimaCell( + config_entry.data[CONF_API_KEY], + config_entry.data.get(CONF_LATITUDE, hass.config.latitude), + config_entry.data.get(CONF_LONGITUDE, hass.config.longitude), + session=async_get_clientsession(hass), + ), + _set_update_interval(hass, config_entry), + ) + + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][config_entry.entry_id] = coordinator + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + return True + + +async def async_unload_entry( + hass: HomeAssistantType, config_entry: ConfigEntry +) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) + + hass.data[DOMAIN].pop(config_entry.entry_id) + if not hass.data[DOMAIN]: + hass.data.pop(DOMAIN) + + return unload_ok + + +class ClimaCellDataUpdateCoordinator(DataUpdateCoordinator): + """Define an object to hold ClimaCell data.""" + + def __init__( + self, + hass: HomeAssistantType, + config_entry: ConfigEntry, + api: ClimaCell, + update_interval: timedelta, + ) -> None: + """Initialize.""" + + self._config_entry = config_entry + self._api = api + self.name = config_entry.data[CONF_NAME] + self.data = {CURRENT: {}, FORECASTS: {}} + + super().__init__( + hass, + _LOGGER, + name=config_entry.data[CONF_NAME], + update_interval=update_interval, + ) + + async def _async_update_data(self) -> Dict[str, Any]: + """Update data via library.""" + data = {FORECASTS: {}} + try: + data[CURRENT] = await self._api.realtime( + self._api.available_fields(REALTIME) + ) + data[FORECASTS][HOURLY] = await self._api.forecast_hourly( + self._api.available_fields(FORECAST_HOURLY), + None, + timedelta(hours=24), + ) + + data[FORECASTS][DAILY] = await self._api.forecast_daily( + self._api.available_fields(FORECAST_DAILY), None, timedelta(days=14) + ) + + data[FORECASTS][NOWCAST] = await self._api.forecast_nowcast( + self._api.available_fields(FORECAST_NOWCAST), + None, + timedelta( + minutes=min(300, self._config_entry.options[CONF_TIMESTEP] * 30) + ), + self._config_entry.options[CONF_TIMESTEP], + ) + except ( + CantConnectException, + InvalidAPIKeyException, + RateLimitedException, + UnknownException, + ) as error: + raise UpdateFailed from error + + return data + + +class ClimaCellEntity(CoordinatorEntity): + """Base ClimaCell Entity.""" + + def __init__( + self, config_entry: ConfigEntry, coordinator: ClimaCellDataUpdateCoordinator + ) -> None: + """Initialize ClimaCell Entity.""" + super().__init__(coordinator) + self._config_entry = config_entry + + @staticmethod + def _get_cc_value( + weather_dict: Dict[str, Any], key: str + ) -> Optional[Union[int, float, str]]: + """Return property from weather_dict.""" + items = weather_dict.get(key, {}) + # Handle cases where value returned is a list. + # Optimistically find the best value to return. + if isinstance(items, list): + if len(items) == 1: + return items[0].get("value") + return next( + (item.get("value") for item in items if "max" in item), + next( + (item.get("value") for item in items if "min" in item), + items[0].get("value", None), + ), + ) + + return items.get("value") + + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._config_entry.data[CONF_NAME] + + @property + def unique_id(self) -> str: + """Return the unique id of the entity.""" + return self._config_entry.unique_id + + @property + def attribution(self): + """Return the attribution.""" + return ATTRIBUTION + + @property + def device_info(self) -> Dict[str, Any]: + """Return device registry information.""" + return { + "identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])}, + "name": self.name, + "manufacturer": "ClimaCell", + "entry_type": "service", + } diff --git a/homeassistant/components/climacell/config_flow.py b/homeassistant/components/climacell/config_flow.py new file mode 100644 index 00000000000000..09e02f3f559b7c --- /dev/null +++ b/homeassistant/components/climacell/config_flow.py @@ -0,0 +1,146 @@ +"""Config flow for ClimaCell integration.""" +import logging +from typing import Any, Dict + +from pyclimacell import ClimaCell +from pyclimacell.const import REALTIME +from pyclimacell.exceptions import ( + CantConnectException, + InvalidAPIKeyException, + RateLimitedException, +) +import voluptuous as vol + +from homeassistant import config_entries, core +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.core import callback +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType + +from .const import CONF_TIMESTEP, DEFAULT_NAME, DEFAULT_TIMESTEP +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +def _get_config_schema( + hass: core.HomeAssistant, input_dict: Dict[str, Any] = None +) -> vol.Schema: + """ + Return schema defaults for init step based on user input/config dict. + + Retain info already provided for future form views by setting them as + defaults in schema. + """ + if input_dict is None: + input_dict = {} + + return vol.Schema( + { + vol.Required( + CONF_NAME, default=input_dict.get(CONF_NAME, DEFAULT_NAME) + ): str, + vol.Required(CONF_API_KEY, default=input_dict.get(CONF_API_KEY)): str, + vol.Inclusive( + CONF_LATITUDE, + "location", + default=input_dict.get(CONF_LATITUDE, hass.config.latitude), + ): cv.latitude, + vol.Inclusive( + CONF_LONGITUDE, + "location", + default=input_dict.get(CONF_LONGITUDE, hass.config.longitude), + ): cv.longitude, + }, + extra=vol.REMOVE_EXTRA, + ) + + +def _get_unique_id(hass: HomeAssistantType, input_dict: Dict[str, Any]): + """Return unique ID from config data.""" + return ( + f"{input_dict[CONF_API_KEY]}" + f"_{input_dict.get(CONF_LATITUDE, hass.config.latitude)}" + f"_{input_dict.get(CONF_LONGITUDE, hass.config.longitude)}" + ) + + +class ClimaCellOptionsConfigFlow(config_entries.OptionsFlow): + """Handle ClimaCell options.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize ClimaCell options flow.""" + self._config_entry = config_entry + + async def async_step_init( + self, user_input: Dict[str, Any] = None + ) -> Dict[str, Any]: + """Manage the ClimaCell options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + options_schema = { + vol.Required( + CONF_TIMESTEP, + default=self._config_entry.options.get(CONF_TIMESTEP, DEFAULT_TIMESTEP), + ): vol.All(vol.Coerce(int), vol.Range(min=1, max=60)), + } + + return self.async_show_form( + step_id="init", data_schema=vol.Schema(options_schema) + ) + + +class ClimaCellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for ClimaCell Weather API.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> ClimaCellOptionsConfigFlow: + """Get the options flow for this handler.""" + return ClimaCellOptionsConfigFlow(config_entry) + + async def async_step_user( + self, user_input: Dict[str, Any] = None + ) -> Dict[str, Any]: + """Handle the initial step.""" + assert self.hass + errors = {} + if user_input is not None: + await self.async_set_unique_id( + unique_id=_get_unique_id(self.hass, user_input) + ) + self._abort_if_unique_id_configured() + + try: + await ClimaCell( + user_input[CONF_API_KEY], + str(user_input.get(CONF_LATITUDE, self.hass.config.latitude)), + str(user_input.get(CONF_LONGITUDE, self.hass.config.longitude)), + session=async_get_clientsession(self.hass), + ).realtime(ClimaCell.first_field(REALTIME)) + + return self.async_create_entry( + title=user_input[CONF_NAME], data=user_input + ) + except CantConnectException: + errors["base"] = "cannot_connect" + except InvalidAPIKeyException: + errors[CONF_API_KEY] = "invalid_api_key" + except RateLimitedException: + errors[CONF_API_KEY] = "rate_limited" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + return self.async_show_form( + step_id="user", + data_schema=_get_config_schema(self.hass, user_input), + errors=errors, + ) diff --git a/homeassistant/components/climacell/const.py b/homeassistant/components/climacell/const.py new file mode 100644 index 00000000000000..28117a164f8c07 --- /dev/null +++ b/homeassistant/components/climacell/const.py @@ -0,0 +1,79 @@ +"""Constants for the ClimaCell integration.""" + +from homeassistant.components.weather import ( + ATTR_CONDITION_CLEAR_NIGHT, + ATTR_CONDITION_CLOUDY, + ATTR_CONDITION_FOG, + ATTR_CONDITION_HAIL, + ATTR_CONDITION_LIGHTNING, + ATTR_CONDITION_PARTLYCLOUDY, + ATTR_CONDITION_POURING, + ATTR_CONDITION_RAINY, + ATTR_CONDITION_SNOWY, + ATTR_CONDITION_SNOWY_RAINY, + ATTR_CONDITION_SUNNY, +) + +CONF_TIMESTEP = "timestep" + +DAILY = "daily" +HOURLY = "hourly" +NOWCAST = "nowcast" +FORECAST_TYPES = [DAILY, HOURLY, NOWCAST] + +CURRENT = "current" +FORECASTS = "forecasts" + +DEFAULT_NAME = "ClimaCell" +DEFAULT_TIMESTEP = 15 +DEFAULT_FORECAST_TYPE = DAILY +DOMAIN = "climacell" +ATTRIBUTION = "Powered by ClimaCell" + +MAX_REQUESTS_PER_DAY = 1000 + +CONDITIONS = { + "freezing_rain_heavy": ATTR_CONDITION_SNOWY_RAINY, + "freezing_rain": ATTR_CONDITION_SNOWY_RAINY, + "freezing_rain_light": ATTR_CONDITION_SNOWY_RAINY, + "freezing_drizzle": ATTR_CONDITION_SNOWY_RAINY, + "ice_pellets_heavy": ATTR_CONDITION_HAIL, + "ice_pellets": ATTR_CONDITION_HAIL, + "ice_pellets_light": ATTR_CONDITION_HAIL, + "snow_heavy": ATTR_CONDITION_SNOWY, + "snow": ATTR_CONDITION_SNOWY, + "snow_light": ATTR_CONDITION_SNOWY, + "flurries": ATTR_CONDITION_SNOWY, + "tstorm": ATTR_CONDITION_LIGHTNING, + "rain_heavy": ATTR_CONDITION_POURING, + "rain": ATTR_CONDITION_RAINY, + "rain_light": ATTR_CONDITION_RAINY, + "drizzle": ATTR_CONDITION_RAINY, + "fog_light": ATTR_CONDITION_FOG, + "fog": ATTR_CONDITION_FOG, + "cloudy": ATTR_CONDITION_CLOUDY, + "mostly_cloudy": ATTR_CONDITION_CLOUDY, + "partly_cloudy": ATTR_CONDITION_PARTLYCLOUDY, +} + +CLEAR_CONDITIONS = {"night": ATTR_CONDITION_CLEAR_NIGHT, "day": ATTR_CONDITION_SUNNY} + +CC_ATTR_TIMESTAMP = "observation_time" +CC_ATTR_TEMPERATURE = "temp" +CC_ATTR_TEMPERATURE_HIGH = "max" +CC_ATTR_TEMPERATURE_LOW = "min" +CC_ATTR_PRESSURE = "baro_pressure" +CC_ATTR_HUMIDITY = "humidity" +CC_ATTR_WIND_SPEED = "wind_speed" +CC_ATTR_WIND_DIRECTION = "wind_direction" +CC_ATTR_OZONE = "o3" +CC_ATTR_CONDITION = "weather_code" +CC_ATTR_VISIBILITY = "visibility" +CC_ATTR_PRECIPITATION = "precipitation" +CC_ATTR_PRECIPITATION_DAILY = "precipitation_accumulation" +CC_ATTR_PRECIPITATION_PROBABILITY = "precipitation_probability" +CC_ATTR_PM_2_5 = "pm25" +CC_ATTR_PM_10 = "pm10" +CC_ATTR_CARBON_MONOXIDE = "co" +CC_ATTR_SULPHUR_DIOXIDE = "so2" +CC_ATTR_NITROGEN_DIOXIDE = "no2" diff --git a/homeassistant/components/climacell/manifest.json b/homeassistant/components/climacell/manifest.json new file mode 100644 index 00000000000000..f410c2275a9703 --- /dev/null +++ b/homeassistant/components/climacell/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "climacell", + "name": "ClimaCell", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/climacell", + "requirements": ["pyclimacell==0.14.0"], + "codeowners": ["@raman325"] +} diff --git a/homeassistant/components/climacell/strings.json b/homeassistant/components/climacell/strings.json new file mode 100644 index 00000000000000..45a1d5b7404bce --- /dev/null +++ b/homeassistant/components/climacell/strings.json @@ -0,0 +1,34 @@ +{ + "title": "ClimaCell", + "config": { + "step": { + "user": { + "description": "If [%key:component::climacell::config::step::user::data::latitude%] and [%key:component::climacell::config::step::user::data::longitude%] are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default.", + "data": { + "name": "Name", + "api_key": "[%key:common::config_flow::data::api_key%]", + "latitude": "Latitude", + "longitude": "Longitude" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]", + "unknown": "[%key:common::config_flow::error::unknown%]", + "rate_limited": "Currently rate limited, please try again later." + } + }, + "options": { + "step": { + "init": { + "title": "Update [%key:component::climacell::title%] Options", + "description": "If you choose to enable the `nowcast` forecast entity, you can configure the number of minutes between each forecast. The number of forecasts provided depends on the number of minutes chosen between forecasts.", + "data": { + "timestep": "Min. Between NowCast Forecasts", + "forecast_types": "Forecast Type(s)" + } + } + } + } +} diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py new file mode 100644 index 00000000000000..a4802586bf179b --- /dev/null +++ b/homeassistant/components/climacell/weather.py @@ -0,0 +1,305 @@ +"""Weather component that handles meteorological data for your location.""" +import logging +from typing import Any, Callable, Dict, List, Optional + +from homeassistant.components.weather import ( + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, + WeatherEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + LENGTH_FEET, + LENGTH_KILOMETERS, + LENGTH_METERS, + LENGTH_MILES, + PRESSURE_HPA, + PRESSURE_INHG, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.sun import is_up +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import dt as dt_util +from homeassistant.util.distance import convert as distance_convert +from homeassistant.util.pressure import convert as pressure_convert +from homeassistant.util.temperature import convert as temp_convert + +from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity +from .const import ( + CC_ATTR_CONDITION, + CC_ATTR_HUMIDITY, + CC_ATTR_OZONE, + CC_ATTR_PRECIPITATION, + CC_ATTR_PRECIPITATION_DAILY, + CC_ATTR_PRECIPITATION_PROBABILITY, + CC_ATTR_PRESSURE, + CC_ATTR_TEMPERATURE, + CC_ATTR_TEMPERATURE_HIGH, + CC_ATTR_TEMPERATURE_LOW, + CC_ATTR_TIMESTAMP, + CC_ATTR_VISIBILITY, + CC_ATTR_WIND_DIRECTION, + CC_ATTR_WIND_SPEED, + CLEAR_CONDITIONS, + CONDITIONS, + CONF_TIMESTEP, + CURRENT, + DAILY, + DEFAULT_FORECAST_TYPE, + DOMAIN, + FORECASTS, + HOURLY, + NOWCAST, +) + +# mypy: allow-untyped-defs, no-check-untyped-defs + +_LOGGER = logging.getLogger(__name__) + + +def _translate_condition( + condition: Optional[str], sun_is_up: bool = True +) -> Optional[str]: + """Translate ClimaCell condition into an HA condition.""" + if not condition: + return None + if "clear" in condition.lower(): + if sun_is_up: + return CLEAR_CONDITIONS["day"] + return CLEAR_CONDITIONS["night"] + return CONDITIONS[condition] + + +def _forecast_dict( + hass: HomeAssistantType, + time: str, + use_datetime: bool, + condition: str, + precipitation: Optional[float], + precipitation_probability: Optional[float], + temp: Optional[float], + temp_low: Optional[float], + wind_direction: Optional[float], + wind_speed: Optional[float], +) -> Dict[str, Any]: + """Return formatted Forecast dict from ClimaCell forecast data.""" + if use_datetime: + translated_condition = _translate_condition( + condition, + is_up(hass, dt_util.as_utc(dt_util.parse_datetime(time))), + ) + else: + translated_condition = _translate_condition(condition, True) + + if hass.config.units.is_metric: + if precipitation: + precipitation = ( + distance_convert(precipitation / 12, LENGTH_FEET, LENGTH_METERS) * 1000 + ) + if temp: + temp = temp_convert(temp, TEMP_FAHRENHEIT, TEMP_CELSIUS) + if temp_low: + temp_low = temp_convert(temp_low, TEMP_FAHRENHEIT, TEMP_CELSIUS) + if wind_speed: + wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) + + data = { + ATTR_FORECAST_TIME: time, + ATTR_FORECAST_CONDITION: translated_condition, + ATTR_FORECAST_PRECIPITATION: precipitation, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: precipitation_probability, + ATTR_FORECAST_TEMP: temp, + ATTR_FORECAST_TEMP_LOW: temp_low, + ATTR_FORECAST_WIND_BEARING: wind_direction, + ATTR_FORECAST_WIND_SPEED: wind_speed, + } + + return {k: v for k, v in data.items() if v is not None} + + +async def async_setup_entry( + hass: HomeAssistantType, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up a config entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + + entities = [ + ClimaCellWeatherEntity(config_entry, coordinator, forecast_type) + for forecast_type in [DAILY, HOURLY, NOWCAST] + ] + async_add_entities(entities) + + +class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): + """Entity that talks to ClimaCell API to retrieve weather data.""" + + def __init__( + self, + config_entry: ConfigEntry, + coordinator: ClimaCellDataUpdateCoordinator, + forecast_type: str, + ) -> None: + """Initialize ClimaCell weather entity.""" + super().__init__(config_entry, coordinator) + self.forecast_type = forecast_type + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + if self.forecast_type == DEFAULT_FORECAST_TYPE: + return True + + return False + + @property + def name(self) -> str: + """Return the name of the entity.""" + return f"{super().name} - {self.forecast_type.title()}" + + @property + def unique_id(self) -> str: + """Return the unique id of the entity.""" + return f"{super().unique_id}_{self.forecast_type}" + + @property + def temperature(self): + """Return the platform temperature.""" + return self._get_cc_value(self.coordinator.data[CURRENT], CC_ATTR_TEMPERATURE) + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + + @property + def pressure(self): + """Return the pressure.""" + pressure = self._get_cc_value(self.coordinator.data[CURRENT], CC_ATTR_PRESSURE) + if self.hass.config.units.is_metric and pressure: + return pressure_convert(pressure, PRESSURE_INHG, PRESSURE_HPA) + return pressure + + @property + def humidity(self): + """Return the humidity.""" + return self._get_cc_value(self.coordinator.data[CURRENT], CC_ATTR_HUMIDITY) + + @property + def wind_speed(self): + """Return the wind speed.""" + wind_speed = self._get_cc_value( + self.coordinator.data[CURRENT], CC_ATTR_WIND_SPEED + ) + if self.hass.config.units.is_metric and wind_speed: + return distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) + return wind_speed + + @property + def wind_bearing(self): + """Return the wind bearing.""" + return self._get_cc_value( + self.coordinator.data[CURRENT], CC_ATTR_WIND_DIRECTION + ) + + @property + def ozone(self): + """Return the O3 (ozone) level.""" + return self._get_cc_value(self.coordinator.data[CURRENT], CC_ATTR_OZONE) + + @property + def condition(self): + """Return the condition.""" + return _translate_condition( + self._get_cc_value(self.coordinator.data[CURRENT], CC_ATTR_CONDITION), + is_up(self.hass), + ) + + @property + def visibility(self): + """Return the visibility.""" + visibility = self._get_cc_value( + self.coordinator.data[CURRENT], CC_ATTR_VISIBILITY + ) + if self.hass.config.units.is_metric and visibility: + return distance_convert(visibility, LENGTH_MILES, LENGTH_KILOMETERS) + return visibility + + @property + def forecast(self): + """Return the forecast.""" + # Check if forecasts are available + if not self.coordinator.data[FORECASTS].get(self.forecast_type): + return None + + forecasts = [] + + # Set default values (in cases where keys don't exist), None will be + # returned. Override properties per forecast type as needed + for forecast in self.coordinator.data[FORECASTS][self.forecast_type]: + timestamp = self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + use_datetime = True + condition = self._get_cc_value(forecast, CC_ATTR_CONDITION) + precipitation = self._get_cc_value(forecast, CC_ATTR_PRECIPITATION) + precipitation_probability = self._get_cc_value( + forecast, CC_ATTR_PRECIPITATION_PROBABILITY + ) + temp = self._get_cc_value(forecast, CC_ATTR_TEMPERATURE) + temp_low = None + wind_direction = self._get_cc_value(forecast, CC_ATTR_WIND_DIRECTION) + wind_speed = self._get_cc_value(forecast, CC_ATTR_WIND_SPEED) + + if self.forecast_type == DAILY: + use_datetime = False + precipitation = self._get_cc_value( + forecast, CC_ATTR_PRECIPITATION_DAILY + ) + temp = next( + ( + self._get_cc_value(item, CC_ATTR_TEMPERATURE_HIGH) + for item in forecast[CC_ATTR_TEMPERATURE] + if "max" in item + ), + temp, + ) + temp_low = next( + ( + self._get_cc_value(item, CC_ATTR_TEMPERATURE_LOW) + for item in forecast[CC_ATTR_TEMPERATURE] + if "max" in item + ), + temp_low, + ) + elif self.forecast_type == NOWCAST: + # Precipitation is forecasted in CONF_TIMESTEP increments but in a + # per hour rate, so value needs to be converted to an amount. + if precipitation: + precipitation = ( + precipitation / 60 * self._config_entry.options[CONF_TIMESTEP] + ) + + forecasts.append( + _forecast_dict( + self.hass, + timestamp, + use_datetime, + condition, + precipitation, + precipitation_probability, + temp, + temp_low, + wind_direction, + wind_speed, + ) + ) + + return forecasts diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index e8e06ed7a1591b..e0a4fd8cd57d30 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -40,6 +40,7 @@ "canary", "cast", "cert_expiry", + "climacell", "cloudflare", "control4", "coolmaster", diff --git a/requirements_all.txt b/requirements_all.txt index 3ac487c8fb966e..60f7997a433b10 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1313,6 +1313,9 @@ pychromecast==8.1.0 # homeassistant.components.pocketcasts pycketcasts==1.0.0 +# homeassistant.components.climacell +pyclimacell==0.14.0 + # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a434739be14203..c21bec2a08fe24 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -693,6 +693,9 @@ pycfdns==1.2.1 # homeassistant.components.cast pychromecast==8.1.0 +# homeassistant.components.climacell +pyclimacell==0.14.0 + # homeassistant.components.comfoconnect pycomfoconnect==0.4 diff --git a/tests/components/climacell/__init__.py b/tests/components/climacell/__init__.py new file mode 100644 index 00000000000000..04ebc3c14c39c2 --- /dev/null +++ b/tests/components/climacell/__init__.py @@ -0,0 +1 @@ +"""Tests for the ClimaCell Weather API integration.""" diff --git a/tests/components/climacell/conftest.py b/tests/components/climacell/conftest.py new file mode 100644 index 00000000000000..3666243b4b4a4b --- /dev/null +++ b/tests/components/climacell/conftest.py @@ -0,0 +1,42 @@ +"""Configure py.test.""" +from unittest.mock import patch + +import pytest + + +@pytest.fixture(name="skip_notifications", autouse=True) +def skip_notifications_fixture(): + """Skip notification calls.""" + with patch("homeassistant.components.persistent_notification.async_create"), patch( + "homeassistant.components.persistent_notification.async_dismiss" + ): + yield + + +@pytest.fixture(name="climacell_config_flow_connect", autouse=True) +def climacell_config_flow_connect(): + """Mock valid climacell config flow setup.""" + with patch( + "homeassistant.components.climacell.config_flow.ClimaCell.realtime", + return_value={}, + ): + yield + + +@pytest.fixture(name="climacell_config_entry_update") +def climacell_config_entry_update_fixture(): + """Mock valid climacell config entry setup.""" + with patch( + "homeassistant.components.climacell.ClimaCell.realtime", + return_value={}, + ), patch( + "homeassistant.components.climacell.ClimaCell.forecast_hourly", + return_value=[], + ), patch( + "homeassistant.components.climacell.ClimaCell.forecast_daily", + return_value=[], + ), patch( + "homeassistant.components.climacell.ClimaCell.forecast_nowcast", + return_value=[], + ): + yield diff --git a/tests/components/climacell/const.py b/tests/components/climacell/const.py new file mode 100644 index 00000000000000..ada0ebd1eb5c3c --- /dev/null +++ b/tests/components/climacell/const.py @@ -0,0 +1,9 @@ +"""Constants for climacell tests.""" + +from homeassistant.const import CONF_API_KEY + +API_KEY = "aa" + +MIN_CONFIG = { + CONF_API_KEY: API_KEY, +} diff --git a/tests/components/climacell/test_config_flow.py b/tests/components/climacell/test_config_flow.py new file mode 100644 index 00000000000000..a34bf6fd0fde46 --- /dev/null +++ b/tests/components/climacell/test_config_flow.py @@ -0,0 +1,167 @@ +"""Test the ClimaCell config flow.""" +import logging +from unittest.mock import patch + +from pyclimacell.exceptions import ( + CantConnectException, + InvalidAPIKeyException, + RateLimitedException, + UnknownException, +) + +from homeassistant import data_entry_flow +from homeassistant.components.climacell.config_flow import ( + _get_config_schema, + _get_unique_id, +) +from homeassistant.components.climacell.const import ( + CONF_TIMESTEP, + DEFAULT_NAME, + DEFAULT_TIMESTEP, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.helpers.typing import HomeAssistantType + +from .const import API_KEY, MIN_CONFIG + +from tests.common import MockConfigEntry + +_LOGGER = logging.getLogger(__name__) + + +async def test_user_flow_minimum_fields(hass: HomeAssistantType) -> None: + """Test user config flow with minimum fields.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=_get_config_schema(hass, MIN_CONFIG)(MIN_CONFIG), + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == DEFAULT_NAME + assert result["data"][CONF_NAME] == DEFAULT_NAME + assert result["data"][CONF_API_KEY] == API_KEY + assert result["data"][CONF_LATITUDE] == hass.config.latitude + assert result["data"][CONF_LONGITUDE] == hass.config.longitude + + +async def test_user_flow_same_unique_ids(hass: HomeAssistantType) -> None: + """Test user config flow with the same unique ID as an existing entry.""" + user_input = _get_config_schema(hass, MIN_CONFIG)(MIN_CONFIG) + MockConfigEntry( + domain=DOMAIN, + data=user_input, + source=SOURCE_USER, + unique_id=_get_unique_id(hass, user_input), + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=user_input, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_user_flow_cannot_connect(hass: HomeAssistantType) -> None: + """Test user config flow when ClimaCell can't connect.""" + with patch( + "homeassistant.components.climacell.config_flow.ClimaCell.realtime", + side_effect=CantConnectException, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=_get_config_schema(hass, MIN_CONFIG)(MIN_CONFIG), + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_user_flow_invalid_api(hass: HomeAssistantType) -> None: + """Test user config flow when API key is invalid.""" + with patch( + "homeassistant.components.climacell.config_flow.ClimaCell.realtime", + side_effect=InvalidAPIKeyException, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=_get_config_schema(hass, MIN_CONFIG)(MIN_CONFIG), + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} + + +async def test_user_flow_rate_limited(hass: HomeAssistantType) -> None: + """Test user config flow when API key is rate limited.""" + with patch( + "homeassistant.components.climacell.config_flow.ClimaCell.realtime", + side_effect=RateLimitedException, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=_get_config_schema(hass, MIN_CONFIG)(MIN_CONFIG), + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_API_KEY: "rate_limited"} + + +async def test_user_flow_unknown_exception(hass: HomeAssistantType) -> None: + """Test user config flow when unknown error occurs.""" + with patch( + "homeassistant.components.climacell.config_flow.ClimaCell.realtime", + side_effect=UnknownException, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=_get_config_schema(hass, MIN_CONFIG)(MIN_CONFIG), + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "unknown"} + + +async def test_options_flow(hass: HomeAssistantType) -> None: + """Test options config flow for climacell.""" + user_config = _get_config_schema(hass)(MIN_CONFIG) + entry = MockConfigEntry( + domain=DOMAIN, + data=user_config, + source=SOURCE_USER, + unique_id=_get_unique_id(hass, user_config), + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + + assert entry.options[CONF_TIMESTEP] == DEFAULT_TIMESTEP + assert CONF_TIMESTEP not in entry.data + + 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" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_TIMESTEP: 1} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "" + assert result["data"][CONF_TIMESTEP] == 1 + assert entry.options[CONF_TIMESTEP] == 1 diff --git a/tests/components/climacell/test_init.py b/tests/components/climacell/test_init.py new file mode 100644 index 00000000000000..1456a068d77c8d --- /dev/null +++ b/tests/components/climacell/test_init.py @@ -0,0 +1,82 @@ +"""Tests for Climacell init.""" +from datetime import timedelta +import logging +from unittest.mock import patch + +import pytest + +from homeassistant.components.climacell.config_flow import ( + _get_config_schema, + _get_unique_id, +) +from homeassistant.components.climacell.const import DOMAIN +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import dt as dt_util + +from .const import MIN_CONFIG + +from tests.common import MockConfigEntry, async_fire_time_changed + +_LOGGER = logging.getLogger(__name__) + + +async def test_load_and_unload( + hass: HomeAssistantType, + climacell_config_entry_update: pytest.fixture, +) -> None: + """Test loading and unloading entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=_get_config_schema(hass)(MIN_CONFIG), + unique_id=_get_unique_id(hass, _get_config_schema(hass)(MIN_CONFIG)), + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 1 + + assert await hass.config_entries.async_remove(config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 0 + + +async def test_update_interval( + hass: HomeAssistantType, + climacell_config_entry_update: pytest.fixture, +) -> None: + """Test that update_interval changes based on number of entries.""" + now = dt_util.utcnow() + async_fire_time_changed(hass, now) + config = _get_config_schema(hass)(MIN_CONFIG) + for i in range(1, 3): + config_entry = MockConfigEntry( + domain=DOMAIN, data=config, unique_id=_get_unique_id(hass, config) + str(i) + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + with patch("homeassistant.components.climacell.ClimaCell.realtime") as mock_api: + # First entry refresh will happen in 7 minutes due to original update interval. + # Next refresh for this entry will happen at 20 minutes due to the update interval + # change. + mock_api.return_value = {} + async_fire_time_changed(hass, now + timedelta(minutes=7)) + await hass.async_block_till_done() + assert mock_api.call_count == 1 + + # Second entry refresh will happen in 13 minutes due to the update interval set + # when it was set up. Next refresh for this entry will happen at 26 minutes due to the + # update interval change. + mock_api.reset_mock() + async_fire_time_changed(hass, now + timedelta(minutes=13)) + await hass.async_block_till_done() + assert not mock_api.call_count == 1 + + # 19 minutes should be after the first update for each config entry and before the + # second update for the first config entry + mock_api.reset_mock() + async_fire_time_changed(hass, now + timedelta(minutes=19)) + await hass.async_block_till_done() + assert not mock_api.call_count == 0 From 1c7c6163dd6c45b9db7e937190a4deac5cb04a02 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 24 Feb 2021 11:31:31 +0100 Subject: [PATCH 0736/1818] Save mysensors gateway type in config entry (#46981) --- .../components/mysensors/config_flow.py | 30 ++++++++++++------- .../components/mysensors/test_config_flow.py | 3 ++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 058b782d208eef..06ead121706724 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -19,6 +19,7 @@ is_persistence_file, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from . import CONF_RETAIN, CONF_VERSION, DEFAULT_VERSION @@ -99,6 +100,10 @@ def _is_same_device( class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow.""" + def __init__(self) -> None: + """Set up config flow.""" + self._gw_type: Optional[str] = None + async def async_step_import(self, user_input: Optional[Dict[str, str]] = None): """Import a config entry. @@ -130,7 +135,7 @@ async def async_step_user(self, user_input: Optional[Dict[str, str]] = None): schema = vol.Schema(schema) if user_input is not None: - gw_type = user_input[CONF_GATEWAY_TYPE] + gw_type = self._gw_type = user_input[CONF_GATEWAY_TYPE] input_pass = user_input if CONF_DEVICE in user_input else None if gw_type == CONF_GATEWAY_TYPE_MQTT: return await self.async_step_gw_mqtt(input_pass) @@ -149,9 +154,7 @@ async def async_step_gw_serial(self, user_input: Optional[Dict[str, str]] = None await self.validate_common(CONF_GATEWAY_TYPE_SERIAL, errors, user_input) ) if not errors: - return self.async_create_entry( - title=f"{user_input[CONF_DEVICE]}", data=user_input - ) + return self._async_create_entry(user_input) schema = _get_schema_common() schema[ @@ -177,9 +180,7 @@ async def async_step_gw_tcp(self, user_input: Optional[Dict[str, str]] = None): await self.validate_common(CONF_GATEWAY_TYPE_TCP, errors, user_input) ) if not errors: - return self.async_create_entry( - title=f"{user_input[CONF_DEVICE]}", data=user_input - ) + return self._async_create_entry(user_input) schema = _get_schema_common() schema[vol.Required(CONF_DEVICE, default="127.0.0.1")] = str @@ -228,9 +229,8 @@ async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): await self.validate_common(CONF_GATEWAY_TYPE_MQTT, errors, user_input) ) if not errors: - return self.async_create_entry( - title=f"{user_input[CONF_DEVICE]}", data=user_input - ) + return self._async_create_entry(user_input) + schema = _get_schema_common() schema[vol.Required(CONF_RETAIN, default=True)] = bool schema[vol.Required(CONF_TOPIC_IN_PREFIX)] = str @@ -241,6 +241,16 @@ async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): step_id="gw_mqtt", data_schema=schema, errors=errors ) + @callback + def _async_create_entry( + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: + """Create the config entry.""" + return self.async_create_entry( + title=f"{user_input[CONF_DEVICE]}", + data={**user_input, CONF_GATEWAY_TYPE: self._gw_type}, + ) + def _normalize_persistence_file(self, path: str) -> str: return os.path.realpath(os.path.normcase(self.hass.config.path(path))) diff --git a/tests/components/mysensors/test_config_flow.py b/tests/components/mysensors/test_config_flow.py index 6bfec3b102e585..5fd9e3e7ea1f0b 100644 --- a/tests/components/mysensors/test_config_flow.py +++ b/tests/components/mysensors/test_config_flow.py @@ -81,6 +81,7 @@ async def test_config_mqtt(hass: HomeAssistantType): CONF_TOPIC_IN_PREFIX: "bla", CONF_TOPIC_OUT_PREFIX: "blub", CONF_VERSION: "2.4", + CONF_GATEWAY_TYPE: "MQTT", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -120,6 +121,7 @@ async def test_config_serial(hass: HomeAssistantType): CONF_DEVICE: "/dev/ttyACM0", CONF_BAUD_RATE: 115200, CONF_VERSION: "2.4", + CONF_GATEWAY_TYPE: "Serial", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 @@ -156,6 +158,7 @@ async def test_config_tcp(hass: HomeAssistantType): CONF_DEVICE: "127.0.0.1", CONF_TCP_PORT: 5003, CONF_VERSION: "2.4", + CONF_GATEWAY_TYPE: "TCP", } assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 From 1a73cb4791980a4d31fff16b696c32a478384591 Mon Sep 17 00:00:00 2001 From: MeIchthys <10717998+meichthys@users.noreply.github.com> Date: Wed, 24 Feb 2021 06:04:38 -0500 Subject: [PATCH 0737/1818] Mullvad VPN (#44189) Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- .coveragerc | 3 + CODEOWNERS | 1 + homeassistant/components/mullvad/__init__.py | 63 +++++++++++++++++++ .../components/mullvad/binary_sensor.py | 52 +++++++++++++++ .../components/mullvad/config_flow.py | 25 ++++++++ homeassistant/components/mullvad/const.py | 3 + .../components/mullvad/manifest.json | 12 ++++ homeassistant/components/mullvad/strings.json | 22 +++++++ .../components/mullvad/translations/en.json | 22 +++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/mullvad/__init__.py | 1 + tests/components/mullvad/test_config_flow.py | 46 ++++++++++++++ 14 files changed, 257 insertions(+) create mode 100644 homeassistant/components/mullvad/__init__.py create mode 100644 homeassistant/components/mullvad/binary_sensor.py create mode 100644 homeassistant/components/mullvad/config_flow.py create mode 100644 homeassistant/components/mullvad/const.py create mode 100644 homeassistant/components/mullvad/manifest.json create mode 100644 homeassistant/components/mullvad/strings.json create mode 100644 homeassistant/components/mullvad/translations/en.json create mode 100644 tests/components/mullvad/__init__.py create mode 100644 tests/components/mullvad/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index c0a28f70a4fbd1..dfa742b490c02f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -586,6 +586,9 @@ omit = homeassistant/components/mpd/media_player.py homeassistant/components/mqtt_room/sensor.py homeassistant/components/msteams/notify.py + homeassistant/components/mullvad/__init__.py + homeassistant/components/mullvad/binary_sensor.py + homeassistant/components/nest/const.py homeassistant/components/mvglive/sensor.py homeassistant/components/mychevy/* homeassistant/components/mycroft/* diff --git a/CODEOWNERS b/CODEOWNERS index 2db89f09948472..398e5b15f7ff8a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -293,6 +293,7 @@ homeassistant/components/motion_blinds/* @starkillerOG homeassistant/components/mpd/* @fabaff homeassistant/components/mqtt/* @emontnemery homeassistant/components/msteams/* @peroyvind +homeassistant/components/mullvad/* @meichthys homeassistant/components/my/* @home-assistant/core homeassistant/components/myq/* @bdraco homeassistant/components/mysensors/* @MartinHjelmare @functionpointer diff --git a/homeassistant/components/mullvad/__init__.py b/homeassistant/components/mullvad/__init__.py new file mode 100644 index 00000000000000..8d63ffd22214bc --- /dev/null +++ b/homeassistant/components/mullvad/__init__.py @@ -0,0 +1,63 @@ +"""The Mullvad VPN integration.""" +import asyncio +from datetime import timedelta +import logging + +import async_timeout +from mullvad_api import MullvadAPI + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import update_coordinator + +from .const import DOMAIN + +PLATFORMS = ["binary_sensor"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Mullvad VPN integration.""" + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: dict): + """Set up Mullvad VPN integration.""" + + async def async_get_mullvad_api_data(): + with async_timeout.timeout(10): + api = await hass.async_add_executor_job(MullvadAPI) + return api.data + + hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator( + hass, + logging.getLogger(__name__), + name=DOMAIN, + update_method=async_get_mullvad_api_data, + update_interval=timedelta(minutes=1), + ) + await hass.data[DOMAIN].async_refresh() + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + + if unload_ok: + del hass.data[DOMAIN] + + return unload_ok diff --git a/homeassistant/components/mullvad/binary_sensor.py b/homeassistant/components/mullvad/binary_sensor.py new file mode 100644 index 00000000000000..40b9ae5b1a898f --- /dev/null +++ b/homeassistant/components/mullvad/binary_sensor.py @@ -0,0 +1,52 @@ +"""Setup Mullvad VPN Binary Sensors.""" +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + BinarySensorEntity, +) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_ID, CONF_NAME +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN + +BINARY_SENSORS = ( + { + CONF_ID: "mullvad_exit_ip", + CONF_NAME: "Mullvad Exit IP", + CONF_DEVICE_CLASS: DEVICE_CLASS_CONNECTIVITY, + }, +) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Defer sensor setup to the shared sensor module.""" + coordinator = hass.data[DOMAIN] + + async_add_entities( + MullvadBinarySensor(coordinator, sensor) for sensor in BINARY_SENSORS + ) + + +class MullvadBinarySensor(CoordinatorEntity, BinarySensorEntity): + """Represents a Mullvad binary sensor.""" + + def __init__(self, coordinator, sensor): # pylint: disable=super-init-not-called + """Initialize the Mullvad binary sensor.""" + super().__init__(coordinator) + self.id = sensor[CONF_ID] + self._name = sensor[CONF_NAME] + self._device_class = sensor[CONF_DEVICE_CLASS] + + @property + def device_class(self): + """Return the device class for this binary sensor.""" + return self._device_class + + @property + def name(self): + """Return the name for this binary sensor.""" + return self._name + + @property + def state(self): + """Return the state for this binary sensor.""" + return self.coordinator.data[self.id] diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py new file mode 100644 index 00000000000000..d7b6f92c445c56 --- /dev/null +++ b/homeassistant/components/mullvad/config_flow.py @@ -0,0 +1,25 @@ +"""Config flow for Mullvad VPN integration.""" +import logging + +from homeassistant import config_entries + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Mullvad VPN.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + if self.hass.config_entries.async_entries(DOMAIN): + return self.async_abort(reason="already_configured") + + if user_input is not None: + return self.async_create_entry(title="Mullvad VPN", data=user_input) + + return self.async_show_form(step_id="user") diff --git a/homeassistant/components/mullvad/const.py b/homeassistant/components/mullvad/const.py new file mode 100644 index 00000000000000..4e3be28782ce63 --- /dev/null +++ b/homeassistant/components/mullvad/const.py @@ -0,0 +1,3 @@ +"""Constants for the Mullvad VPN integration.""" + +DOMAIN = "mullvad" diff --git a/homeassistant/components/mullvad/manifest.json b/homeassistant/components/mullvad/manifest.json new file mode 100644 index 00000000000000..1a440240d7e1cf --- /dev/null +++ b/homeassistant/components/mullvad/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "mullvad", + "name": "Mullvad VPN", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/mullvad", + "requirements": [ + "mullvad-api==1.0.0" + ], + "codeowners": [ + "@meichthys" + ] +} diff --git a/homeassistant/components/mullvad/strings.json b/homeassistant/components/mullvad/strings.json new file mode 100644 index 00000000000000..f522c12871f232 --- /dev/null +++ b/homeassistant/components/mullvad/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + }, + "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%]" + }, + "step": { + "user": { + "description": "Set up the Mullvad VPN integration?", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + } + } + } + } +} diff --git a/homeassistant/components/mullvad/translations/en.json b/homeassistant/components/mullvad/translations/en.json new file mode 100644 index 00000000000000..fcfa89ef0829ee --- /dev/null +++ b/homeassistant/components/mullvad/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + }, + "description": "Set up the Mullvad VPN integration?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index e0a4fd8cd57d30..da16d32d45b75a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -143,6 +143,7 @@ "monoprice", "motion_blinds", "mqtt", + "mullvad", "myq", "mysensors", "neato", diff --git a/requirements_all.txt b/requirements_all.txt index 60f7997a433b10..102b474f3bf243 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -955,6 +955,9 @@ mitemp_bt==0.0.3 # homeassistant.components.motion_blinds motionblinds==0.4.8 +# homeassistant.components.mullvad +mullvad-api==1.0.0 + # homeassistant.components.tts mutagen==1.45.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c21bec2a08fe24..7070b69162ed15 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -500,6 +500,9 @@ minio==4.0.9 # homeassistant.components.motion_blinds motionblinds==0.4.8 +# homeassistant.components.mullvad +mullvad-api==1.0.0 + # homeassistant.components.tts mutagen==1.45.1 diff --git a/tests/components/mullvad/__init__.py b/tests/components/mullvad/__init__.py new file mode 100644 index 00000000000000..dc940265eaccb7 --- /dev/null +++ b/tests/components/mullvad/__init__.py @@ -0,0 +1 @@ +"""Tests for the mullvad component.""" diff --git a/tests/components/mullvad/test_config_flow.py b/tests/components/mullvad/test_config_flow.py new file mode 100644 index 00000000000000..01485da60a01b2 --- /dev/null +++ b/tests/components/mullvad/test_config_flow.py @@ -0,0 +1,46 @@ +"""Test the Mullvad config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, setup +from homeassistant.components.mullvad.const import DOMAIN + +from tests.common import MockConfigEntry + + +async def test_form_user(hass): + """Test we can setup by the user.""" + await setup.async_setup_component(hass, DOMAIN, {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] is None + + with patch( + "homeassistant.components.mullvad.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.mullvad.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"] == "Mullvad VPN" + assert result2["data"] == {} + assert len(mock_setup.mock_calls) == 0 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_user_only_once(hass): + """Test we can setup by the user only once.""" + MockConfigEntry(domain=DOMAIN).add_to_hass(hass) + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "abort" + assert result["reason"] == "already_configured" From 17f4c9dd06815963cad19acf0d0deb82daf6f53a Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 24 Feb 2021 12:52:04 +0100 Subject: [PATCH 0738/1818] Improve mysensors config flow (#46984) --- .../components/mysensors/config_flow.py | 58 ++++++++++++++----- homeassistant/components/mysensors/gateway.py | 2 +- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 06ead121706724..d2cd7f3bccd03d 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -44,15 +44,17 @@ _LOGGER = logging.getLogger(__name__) -def _get_schema_common() -> dict: +def _get_schema_common(user_input: Dict[str, str]) -> dict: """Create a schema with options common to all gateway types.""" schema = { vol.Required( - CONF_VERSION, default="", description={"suggested_value": DEFAULT_VERSION} - ): str, - vol.Optional( - CONF_PERSISTENCE_FILE, + CONF_VERSION, + default="", + description={ + "suggested_value": user_input.get(CONF_VERSION, DEFAULT_VERSION) + }, ): str, + vol.Optional(CONF_PERSISTENCE_FILE): str, } return schema @@ -156,11 +158,19 @@ async def async_step_gw_serial(self, user_input: Optional[Dict[str, str]] = None if not errors: return self._async_create_entry(user_input) - schema = _get_schema_common() + user_input = user_input or {} + schema = _get_schema_common(user_input) schema[ - vol.Required(CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE) + vol.Required( + CONF_BAUD_RATE, + default=user_input.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE), + ) ] = cv.positive_int - schema[vol.Required(CONF_DEVICE, default="/dev/ttyACM0")] = str + schema[ + vol.Required( + CONF_DEVICE, default=user_input.get(CONF_DEVICE, "/dev/ttyACM0") + ) + ] = str schema = vol.Schema(schema) return self.async_show_form( @@ -182,10 +192,17 @@ async def async_step_gw_tcp(self, user_input: Optional[Dict[str, str]] = None): if not errors: return self._async_create_entry(user_input) - schema = _get_schema_common() - schema[vol.Required(CONF_DEVICE, default="127.0.0.1")] = str + user_input = user_input or {} + schema = _get_schema_common(user_input) + schema[ + vol.Required(CONF_DEVICE, default=user_input.get(CONF_DEVICE, "127.0.0.1")) + ] = str # Don't use cv.port as that would show a slider *facepalm* - schema[vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT)] = vol.Coerce(int) + schema[ + vol.Optional( + CONF_TCP_PORT, default=user_input.get(CONF_TCP_PORT, DEFAULT_TCP_PORT) + ) + ] = vol.Coerce(int) schema = vol.Schema(schema) return self.async_show_form(step_id="gw_tcp", data_schema=schema, errors=errors) @@ -231,10 +248,21 @@ async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): if not errors: return self._async_create_entry(user_input) - schema = _get_schema_common() - schema[vol.Required(CONF_RETAIN, default=True)] = bool - schema[vol.Required(CONF_TOPIC_IN_PREFIX)] = str - schema[vol.Required(CONF_TOPIC_OUT_PREFIX)] = str + user_input = user_input or {} + schema = _get_schema_common(user_input) + schema[ + vol.Required(CONF_RETAIN, default=user_input.get(CONF_RETAIN, True)) + ] = bool + schema[ + vol.Required( + CONF_TOPIC_IN_PREFIX, default=user_input.get(CONF_TOPIC_IN_PREFIX, "") + ) + ] = str + schema[ + vol.Required( + CONF_TOPIC_OUT_PREFIX, default=user_input.get(CONF_TOPIC_OUT_PREFIX, "") + ) + ] = str schema = vol.Schema(schema) return self.async_show_form( diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index b618004b62222d..4267ba5cbb3938 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -96,7 +96,7 @@ def gateway_ready_callback(msg): connect_task = None try: connect_task = asyncio.create_task(gateway.start()) - with async_timeout.timeout(5): + with async_timeout.timeout(20): await gateway_ready return True except asyncio.TimeoutError: From b0d56970a54907a367490d4dfd2506febef68baf Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 24 Feb 2021 13:43:24 +0100 Subject: [PATCH 0739/1818] Fix TTS services name (#46988) --- homeassistant/components/tts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 67bc933a530de7..f9b07a98595bc3 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -209,7 +209,7 @@ async def async_say_handle(service): # Register the service description service_desc = { - CONF_NAME: "Say an TTS message with {p_type}", + CONF_NAME: f"Say an TTS message with {p_type}", CONF_DESCRIPTION: f"Say something using text-to-speech on a media player with {p_type}.", CONF_FIELDS: services_dict[SERVICE_SAY][CONF_FIELDS], } From 45b50c53ad89d6093700fe3d05cdb1316e822256 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Feb 2021 13:43:44 +0100 Subject: [PATCH 0740/1818] Mullvad integration improvements (#46987) --- homeassistant/components/mullvad/__init__.py | 11 +++- .../components/mullvad/binary_sensor.py | 2 +- .../components/mullvad/config_flow.py | 16 +++++- homeassistant/components/mullvad/strings.json | 8 +-- .../components/mullvad/translations/en.json | 6 -- tests/components/mullvad/test_config_flow.py | 56 +++++++++++++++++-- 6 files changed, 75 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/mullvad/__init__.py b/homeassistant/components/mullvad/__init__.py index 8d63ffd22214bc..20a7092d58a513 100644 --- a/homeassistant/components/mullvad/__init__.py +++ b/homeassistant/components/mullvad/__init__.py @@ -8,6 +8,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import update_coordinator from .const import DOMAIN @@ -17,7 +18,6 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the Mullvad VPN integration.""" - return True @@ -29,14 +29,19 @@ async def async_get_mullvad_api_data(): api = await hass.async_add_executor_job(MullvadAPI) return api.data - hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator( + coordinator = update_coordinator.DataUpdateCoordinator( hass, logging.getLogger(__name__), name=DOMAIN, update_method=async_get_mullvad_api_data, update_interval=timedelta(minutes=1), ) - await hass.data[DOMAIN].async_refresh() + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN] = coordinator for component in PLATFORMS: hass.async_create_task( diff --git a/homeassistant/components/mullvad/binary_sensor.py b/homeassistant/components/mullvad/binary_sensor.py index 40b9ae5b1a898f..f85820cd7d0a76 100644 --- a/homeassistant/components/mullvad/binary_sensor.py +++ b/homeassistant/components/mullvad/binary_sensor.py @@ -47,6 +47,6 @@ def name(self): return self._name @property - def state(self): + def is_on(self): """Return the state for this binary sensor.""" return self.coordinator.data[self.id] diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py index d7b6f92c445c56..674308c1d1a246 100644 --- a/homeassistant/components/mullvad/config_flow.py +++ b/homeassistant/components/mullvad/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Mullvad VPN integration.""" import logging +from mullvad_api import MullvadAPI, MullvadAPIError + from homeassistant import config_entries from .const import DOMAIN # pylint:disable=unused-import @@ -19,7 +21,15 @@ async def async_step_user(self, user_input=None): if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="already_configured") + errors = {} if user_input is not None: - return self.async_create_entry(title="Mullvad VPN", data=user_input) - - return self.async_show_form(step_id="user") + try: + await self.hass.async_add_executor_job(MullvadAPI) + except MullvadAPIError: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + errors["base"] = "unknown" + else: + return self.async_create_entry(title="Mullvad VPN", data=user_input) + + return self.async_show_form(step_id="user", errors=errors) diff --git a/homeassistant/components/mullvad/strings.json b/homeassistant/components/mullvad/strings.json index f522c12871f232..7910a40ec35e4f 100644 --- a/homeassistant/components/mullvad/strings.json +++ b/homeassistant/components/mullvad/strings.json @@ -5,17 +5,11 @@ }, "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%]" }, "step": { "user": { - "description": "Set up the Mullvad VPN integration?", - "data": { - "host": "[%key:common::config_flow::data::host%]", - "password": "[%key:common::config_flow::data::password%]", - "username": "[%key:common::config_flow::data::username%]" - } + "description": "Set up the Mullvad VPN integration?" } } } diff --git a/homeassistant/components/mullvad/translations/en.json b/homeassistant/components/mullvad/translations/en.json index fcfa89ef0829ee..45664554aed74f 100644 --- a/homeassistant/components/mullvad/translations/en.json +++ b/homeassistant/components/mullvad/translations/en.json @@ -5,16 +5,10 @@ }, "error": { "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "step": { "user": { - "data": { - "host": "Host", - "password": "Password", - "username": "Username" - }, "description": "Set up the Mullvad VPN integration?" } } diff --git a/tests/components/mullvad/test_config_flow.py b/tests/components/mullvad/test_config_flow.py index 01485da60a01b2..c101e5a7246675 100644 --- a/tests/components/mullvad/test_config_flow.py +++ b/tests/components/mullvad/test_config_flow.py @@ -1,8 +1,11 @@ """Test the Mullvad config flow.""" from unittest.mock import patch +from mullvad_api import MullvadAPIError + from homeassistant import config_entries, setup from homeassistant.components.mullvad.const import DOMAIN +from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_FORM from tests.common import MockConfigEntry @@ -13,15 +16,17 @@ async def test_form_user(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "form" - assert result["errors"] is None + assert result["type"] == RESULT_TYPE_FORM + assert not result["errors"] with patch( "homeassistant.components.mullvad.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.mullvad.async_setup_entry", return_value=True, - ) as mock_setup_entry: + ) as mock_setup_entry, patch( + "homeassistant.components.mullvad.config_flow.MullvadAPI" + ) as mock_mullvad_api: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -33,6 +38,7 @@ async def test_form_user(hass): assert result2["data"] == {} assert len(mock_setup.mock_calls) == 0 assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_mullvad_api.mock_calls) == 1 async def test_form_user_only_once(hass): @@ -42,5 +48,47 @@ async def test_form_user_only_once(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "abort" + assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + +async def test_connection_error(hass): + """Test we show an error when we have trouble connecting.""" + await setup.async_setup_component(hass, DOMAIN, {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.mullvad.config_flow.MullvadAPI", + side_effect=MullvadAPIError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_unknown_error(hass): + """Test we show an error when an unknown error occurs.""" + await setup.async_setup_component(hass, DOMAIN, {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.mullvad.config_flow.MullvadAPI", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} From b645b151f987dda47df390870c35cae8f8b010ea Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 24 Feb 2021 14:13:32 +0100 Subject: [PATCH 0741/1818] Catch AuthRequired exception in confirm discovery step for Shelly config flow (#46135) --- .../components/shelly/config_flow.py | 2 ++ tests/components/shelly/test_config_flow.py | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index cd74b83a62aefe..dfb078ee9c7cf1 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -192,6 +192,8 @@ async def async_step_confirm_discovery(self, user_input=None): device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.AuthRequired: + return await self.async_step_credentials() except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 450bf8efb24ddf..9dfbc19255be0a 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -512,6 +512,36 @@ async def test_zeroconf_confirm_error(hass, error): assert result2["errors"] == {"base": base_error} +async def test_zeroconf_confirm_auth_error(hass): + """Test we get credentials form after an auth error when confirming discovery.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "aioshelly.get_info", + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=DISCOVERY_INFO, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "aioshelly.Device.create", + new=AsyncMock(side_effect=aioshelly.AuthRequired), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "credentials" + assert result2["errors"] == {} + + async def test_zeroconf_already_configured(hass): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) From 470121e5b07a98cc34b68dd13bd97b4a81be76c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Wed, 24 Feb 2021 14:17:01 +0100 Subject: [PATCH 0742/1818] Restore Tado binary sensor attributes (#46069) --- .../components/tado/binary_sensor.py | 11 +++++++++ tests/components/tado/util.py | 23 +++++++++++++++++++ tests/fixtures/tado/zone_default_overlay.json | 5 ++++ 3 files changed, 39 insertions(+) create mode 100644 tests/fixtures/tado/zone_default_overlay.json diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index 71b529310131bd..068c3a7ce93160 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -183,6 +183,7 @@ def __init__(self, tado, zone_name, zone_id, zone_variable): self._unique_id = f"{zone_variable} {zone_id} {tado.home_id}" self._state = None + self._state_attributes = None self._tado_zone_data = None async def async_added_to_hass(self): @@ -229,6 +230,11 @@ def device_class(self): return DEVICE_CLASS_POWER return None + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._state_attributes + @callback def _async_update_callback(self): """Update and write state.""" @@ -251,6 +257,10 @@ def _async_update_zone_data(self): elif self.zone_variable == "overlay": self._state = self._tado_zone_data.overlay_active + if self._tado_zone_data.overlay_active: + self._state_attributes = { + "termination": self._tado_zone_data.overlay_termination_type + } elif self.zone_variable == "early start": self._state = self._tado_zone_data.preparation @@ -260,3 +270,4 @@ def _async_update_zone_data(self): self._tado_zone_data.open_window or self._tado_zone_data.open_window_detected ) + self._state_attributes = self._tado_zone_data.open_window_attr diff --git a/tests/components/tado/util.py b/tests/components/tado/util.py index d27ede47a63303..c5bf8cf28a41bc 100644 --- a/tests/components/tado/util.py +++ b/tests/components/tado/util.py @@ -46,6 +46,9 @@ async def async_init_integration( # Device Temp Offset device_temp_offset = "tado/device_temp_offset.json" + # Zone Default Overlay + zone_def_overlay = "tado/zone_default_overlay.json" + with requests_mock.mock() as m: m.post("https://auth.tado.com/oauth/token", text=load_fixture(token_fixture)) m.get( @@ -92,6 +95,26 @@ async def async_init_integration( "https://my.tado.com/api/v2/homes/1/zones/1/capabilities", text=load_fixture(zone_1_capabilities_fixture), ) + m.get( + "https://my.tado.com/api/v2/homes/1/zones/1/defaultOverlay", + text=load_fixture(zone_def_overlay), + ) + m.get( + "https://my.tado.com/api/v2/homes/1/zones/2/defaultOverlay", + text=load_fixture(zone_def_overlay), + ) + m.get( + "https://my.tado.com/api/v2/homes/1/zones/3/defaultOverlay", + text=load_fixture(zone_def_overlay), + ) + m.get( + "https://my.tado.com/api/v2/homes/1/zones/4/defaultOverlay", + text=load_fixture(zone_def_overlay), + ) + m.get( + "https://my.tado.com/api/v2/homes/1/zones/5/defaultOverlay", + text=load_fixture(zone_def_overlay), + ) m.get( "https://my.tado.com/api/v2/homes/1/zones/5/state", text=load_fixture(zone_5_state_fixture), diff --git a/tests/fixtures/tado/zone_default_overlay.json b/tests/fixtures/tado/zone_default_overlay.json new file mode 100644 index 00000000000000..092b2b25d4dda1 --- /dev/null +++ b/tests/fixtures/tado/zone_default_overlay.json @@ -0,0 +1,5 @@ +{ + "terminationCondition": { + "type": "MANUAL" + } +} From 44293a37388aef33d1bb0c67b87a29c74b183a50 Mon Sep 17 00:00:00 2001 From: adrian-vlad Date: Wed, 24 Feb 2021 15:26:05 +0200 Subject: [PATCH 0743/1818] Add enable and disable services for recorder (#45778) --- homeassistant/components/recorder/__init__.py | 30 ++++ .../components/recorder/services.yaml | 6 + tests/components/recorder/test_init.py | 157 +++++++++++++++++- 3 files changed, 191 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 915e6b4518136b..3935aa97eb8753 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -48,6 +48,8 @@ _LOGGER = logging.getLogger(__name__) SERVICE_PURGE = "purge" +SERVICE_ENABLE = "enable" +SERVICE_DISABLE = "disable" ATTR_KEEP_DAYS = "keep_days" ATTR_REPACK = "repack" @@ -58,6 +60,8 @@ vol.Optional(ATTR_REPACK, default=False): cv.boolean, } ) +SERVICE_ENABLE_SCHEMA = vol.Schema({}) +SERVICE_DISABLE_SCHEMA = vol.Schema({}) DEFAULT_URL = "sqlite:///{hass_config_path}" DEFAULT_DB_FILE = "home-assistant_v2.db" @@ -199,6 +203,23 @@ async def async_handle_purge_service(service): DOMAIN, SERVICE_PURGE, async_handle_purge_service, schema=SERVICE_PURGE_SCHEMA ) + async def async_handle_enable_sevice(service): + instance.set_enable(True) + + hass.services.async_register( + DOMAIN, SERVICE_ENABLE, async_handle_enable_sevice, schema=SERVICE_ENABLE_SCHEMA + ) + + async def async_handle_disable_service(service): + instance.set_enable(False) + + hass.services.async_register( + DOMAIN, + SERVICE_DISABLE, + async_handle_disable_service, + schema=SERVICE_DISABLE_SCHEMA, + ) + return await instance.async_db_ready @@ -255,6 +276,12 @@ def __init__( self.get_session = None self._completed_database_setup = None + self.enabled = True + + def set_enable(self, enable): + """Enable or disable recording events and states.""" + self.enabled = enable + @callback def async_initialize(self): """Initialize the recorder.""" @@ -413,6 +440,9 @@ def _process_one_event(self, event): self._commit_event_session_or_recover() return + if not self.enabled: + return + try: if event.event_type == EVENT_STATE_CHANGED: dbevent = Events.from_event(event, event_data="{}") diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index e3dea47f4f876b..2be5b0e095e354 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -24,3 +24,9 @@ purge: default: false selector: boolean: + +disable: + description: Stop the recording of events and state changes + +enabled: + description: Start the recording of events and state changes diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index dfa659448115b0..63f4b9887c6c53 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -8,13 +8,17 @@ from homeassistant.components.recorder import ( CONF_DB_URL, CONFIG_SCHEMA, + DATA_INSTANCE, DOMAIN, + SERVICE_DISABLE, + SERVICE_ENABLE, + SERVICE_PURGE, + SQLITE_URL_PREFIX, Recorder, run_information, run_information_from_instance, run_information_with_session, ) -from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.util import session_scope from homeassistant.const import ( @@ -24,7 +28,7 @@ STATE_UNLOCKED, ) from homeassistant.core import Context, CoreState, callback -from homeassistant.setup import async_setup_component +from homeassistant.setup import async_setup_component, setup_component from homeassistant.util import dt as dt_util from .common import async_wait_recording_done, corrupt_db_file, wait_recording_done @@ -518,6 +522,155 @@ def test_run_information(hass_recorder): assert run_info.closed_incorrect is False +def test_has_services(hass_recorder): + """Test the services exist.""" + hass = hass_recorder() + + assert hass.services.has_service(DOMAIN, SERVICE_DISABLE) + assert hass.services.has_service(DOMAIN, SERVICE_ENABLE) + assert hass.services.has_service(DOMAIN, SERVICE_PURGE) + + +def test_service_disable_events_not_recording(hass, hass_recorder): + """Test that events are not recorded when recorder is disabled using service.""" + hass = hass_recorder() + + assert hass.services.call( + DOMAIN, + SERVICE_DISABLE, + {}, + blocking=True, + ) + + event_type = "EVENT_TEST" + + events = [] + + @callback + def event_listener(event): + """Record events from eventbus.""" + if event.event_type == event_type: + events.append(event) + + hass.bus.listen(MATCH_ALL, event_listener) + + event_data1 = {"test_attr": 5, "test_attr_10": "nice"} + hass.bus.fire(event_type, event_data1) + wait_recording_done(hass) + + assert len(events) == 1 + event = events[0] + + with session_scope(hass=hass) as session: + db_events = list(session.query(Events).filter_by(event_type=event_type)) + assert len(db_events) == 0 + + assert hass.services.call( + DOMAIN, + SERVICE_ENABLE, + {}, + blocking=True, + ) + + event_data2 = {"attr_one": 5, "attr_two": "nice"} + hass.bus.fire(event_type, event_data2) + wait_recording_done(hass) + + assert len(events) == 2 + assert events[0] != events[1] + assert events[0].data != events[1].data + + with session_scope(hass=hass) as session: + db_events = list(session.query(Events).filter_by(event_type=event_type)) + assert len(db_events) == 1 + db_event = db_events[0].to_native() + + event = events[1] + + assert event.event_type == db_event.event_type + assert event.data == db_event.data + assert event.origin == db_event.origin + assert event.time_fired.replace(microsecond=0) == db_event.time_fired.replace( + microsecond=0 + ) + + +def test_service_disable_states_not_recording(hass, hass_recorder): + """Test that state changes are not recorded when recorder is disabled using service.""" + hass = hass_recorder() + + assert hass.services.call( + DOMAIN, + SERVICE_DISABLE, + {}, + blocking=True, + ) + + hass.states.set("test.one", "on", {}) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + assert len(list(session.query(States))) == 0 + + assert hass.services.call( + DOMAIN, + SERVICE_ENABLE, + {}, + blocking=True, + ) + + hass.states.set("test.two", "off", {}) + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + db_states = list(session.query(States)) + assert len(db_states) == 1 + assert db_states[0].event_id > 0 + assert db_states[0].to_native() == _state_empty_context(hass, "test.two") + + +def test_service_disable_run_information_recorded(tmpdir): + """Test that runs are still recorded when recorder is disabled.""" + test_db_file = tmpdir.mkdir("sqlite").join("test_run_info.db") + dburl = f"{SQLITE_URL_PREFIX}//{test_db_file}" + + hass = get_test_home_assistant() + setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: dburl}}) + hass.start() + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + db_run_info = list(session.query(RecorderRuns)) + assert len(db_run_info) == 1 + assert db_run_info[0].start is not None + assert db_run_info[0].end is None + + assert hass.services.call( + DOMAIN, + SERVICE_DISABLE, + {}, + blocking=True, + ) + + wait_recording_done(hass) + hass.stop() + + hass = get_test_home_assistant() + setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: dburl}}) + hass.start() + wait_recording_done(hass) + + with session_scope(hass=hass) as session: + db_run_info = list(session.query(RecorderRuns)) + assert len(db_run_info) == 2 + assert db_run_info[0].start is not None + assert db_run_info[0].end is not None + assert db_run_info[1].start is not None + assert db_run_info[1].end is None + + hass.stop() + + class CannotSerializeMe: """A class that the JSONEncoder cannot serialize.""" From 424526db7eeec63a31cd162bf5983568236aee88 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Feb 2021 09:41:10 -0500 Subject: [PATCH 0744/1818] Migrate zwave_js entities to use new unique ID format (#46979) * migrate zwave_js entities to use new unique ID format * remove extra param from helper * add comment to remove migration logic in the future * update comment * use instance attribute instead of calling functino on every state update --- homeassistant/components/zwave_js/__init__.py | 27 ++++++++++++++-- homeassistant/components/zwave_js/entity.py | 7 +++-- homeassistant/components/zwave_js/helpers.py | 17 ++++++++++ tests/components/zwave_js/test_init.py | 31 +++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 062b28cf6a92b4..cc58e31066a00f 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -43,7 +43,7 @@ ZWAVE_JS_EVENT, ) from .discovery import async_discover_values -from .helpers import get_device_id +from .helpers import get_device_id, get_old_value_id, get_unique_id from .services import ZWaveServices LOGGER = logging.getLogger(__package__) @@ -83,6 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) dev_reg = await device_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) @callback def async_on_node_ready(node: ZwaveNode) -> None: @@ -95,6 +96,28 @@ def async_on_node_ready(node: ZwaveNode) -> None: # run discovery on all node values and create/update entities for disc_info in async_discover_values(node): LOGGER.debug("Discovered entity: %s", disc_info) + + # This migration logic was added in 2021.3 to handle breaking change to + # value_id format. Some time in the future, this code block + # (and get_old_value_id helper) can be removed. + old_value_id = get_old_value_id(disc_info.primary_value) + old_unique_id = get_unique_id( + client.driver.controller.home_id, old_value_id + ) + if entity_id := ent_reg.async_get_entity_id( + disc_info.platform, DOMAIN, old_unique_id + ): + LOGGER.debug( + "Entity %s is using old unique ID, migrating to new one", entity_id + ) + ent_reg.async_update_entity( + entity_id, + new_unique_id=get_unique_id( + client.driver.controller.home_id, + disc_info.primary_value.value_id, + ), + ) + async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) @@ -193,7 +216,7 @@ def async_on_notification(notification: Notification) -> None: DATA_UNSUBSCRIBE: unsubscribe_callbacks, } - services = ZWaveServices(hass, entity_registry.async_get(hass)) + services = ZWaveServices(hass, ent_reg) services.async_register() # Set up websocket API diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index cb898e861e9f44..685fe50c9b6891 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -13,7 +13,7 @@ from .const import DOMAIN from .discovery import ZwaveDiscoveryInfo -from .helpers import get_device_id +from .helpers import get_device_id, get_unique_id LOGGER = logging.getLogger(__name__) @@ -31,6 +31,9 @@ def __init__( self.client = client self.info = info self._name = self.generate_name() + self._unique_id = get_unique_id( + self.client.driver.controller.home_id, self.info.value_id + ) # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} @@ -128,7 +131,7 @@ def name(self) -> str: @property def unique_id(self) -> str: """Return the unique_id of the entity.""" - return f"{self.client.driver.controller.home_id}.{self.info.value_id}" + return self._unique_id @property def available(self) -> bool: diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index cc00c39b747ac0..9582b7ee054f01 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -3,6 +3,7 @@ from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode +from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -12,6 +13,22 @@ from .const import DATA_CLIENT, DOMAIN +@callback +def get_old_value_id(value: ZwaveValue) -> str: + """Get old value ID so we can migrate entity unique ID.""" + command_class = value.command_class + endpoint = value.endpoint or "00" + property_ = value.property_ + property_key_name = value.property_key_name or "00" + return f"{value.node.node_id}-{command_class}-{endpoint}-{property_}-{property_key_name}" + + +@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}" + + @callback def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: """Get device registry identifier for Z-Wave node.""" diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 6a255becf2dbad..3634454544fe4e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -124,6 +124,37 @@ async def test_on_node_added_ready( ) +async def test_unique_id_migration(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new.""" + ent_reg = entity_registry.async_get(hass) + entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52-49-00-Air temperature-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == AIR_TEMPERATURE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + assert entity_entry.unique_id == new_unique_id + + async def test_on_node_added_not_ready( hass, multisensor_6_state, client, integration, device_registry ): From db8f597f1052dda14f5415423bcea571141047ea Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 24 Feb 2021 16:22:58 +0100 Subject: [PATCH 0745/1818] Fix zwave_js config flow server version timeout (#46990) --- .../components/zwave_js/config_flow.py | 15 ++++++----- tests/components/zwave_js/test_config_flow.py | 27 +++++++++++++++---- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index fe79796edf17bf..cc19fb85d3a147 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -41,6 +41,7 @@ ADDON_SETUP_TIMEOUT = 5 ADDON_SETUP_TIMEOUT_ROUNDS = 4 +SERVER_VERSION_TIMEOUT = 10 ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool}) STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_URL, default=DEFAULT_URL): str}) @@ -61,16 +62,16 @@ async def validate_input(hass: HomeAssistant, user_input: dict) -> VersionInfo: async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo: """Return Z-Wave JS version info.""" - async with timeout(10): - try: + try: + async with timeout(SERVER_VERSION_TIMEOUT): version_info: VersionInfo = await get_server_version( ws_address, async_get_clientsession(hass) ) - except (asyncio.TimeoutError, aiohttp.ClientError) as err: - # We don't want to spam the log if the add-on isn't started - # or takes a long time to start. - _LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err) - raise CannotConnect from err + except (asyncio.TimeoutError, aiohttp.ClientError) as err: + # We don't want to spam the log if the add-on isn't started + # or takes a long time to start. + _LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err) + raise CannotConnect from err return version_info diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 3c956d42a27ab9..73057f3fe21b00 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -7,7 +7,7 @@ from homeassistant import config_entries, setup from homeassistant.components.hassio.handler import HassioAPIError -from homeassistant.components.zwave_js.config_flow import TITLE +from homeassistant.components.zwave_js.config_flow import SERVER_VERSION_TIMEOUT, TITLE from homeassistant.components.zwave_js.const import DOMAIN from tests.common import MockConfigEntry @@ -138,7 +138,7 @@ def server_version_side_effect_fixture(): @pytest.fixture(name="get_server_version", autouse=True) -def mock_get_server_version(server_version_side_effect): +def mock_get_server_version(server_version_side_effect, server_version_timeout): """Mock server version.""" version_info = VersionInfo( driver_version="mock-driver-version", @@ -149,10 +149,19 @@ def mock_get_server_version(server_version_side_effect): "homeassistant.components.zwave_js.config_flow.get_server_version", side_effect=server_version_side_effect, return_value=version_info, - ) as mock_version: + ) as mock_version, patch( + "homeassistant.components.zwave_js.config_flow.SERVER_VERSION_TIMEOUT", + new=server_version_timeout, + ): yield mock_version +@pytest.fixture(name="server_version_timeout") +def mock_server_version_timeout(): + """Patch the timeout for getting server version.""" + return SERVER_VERSION_TIMEOUT + + @pytest.fixture(name="addon_setup_time", autouse=True) def mock_addon_setup_time(): """Mock add-on setup sleep time.""" @@ -198,22 +207,30 @@ async def test_manual(hass): assert result2["result"].unique_id == 1234 +async def slow_server_version(*args): + """Simulate a slow server version.""" + await asyncio.sleep(0.1) + + @pytest.mark.parametrize( - "url, server_version_side_effect, error", + "url, server_version_side_effect, server_version_timeout, error", [ ( "not-ws-url", None, + SERVER_VERSION_TIMEOUT, "invalid_ws_url", ), ( "ws://localhost:3000", - asyncio.TimeoutError, + slow_server_version, + 0, "cannot_connect", ), ( "ws://localhost:3000", Exception("Boom"), + SERVER_VERSION_TIMEOUT, "unknown", ), ], From 0ef16dd563c14fa95074a14eca21ff027a4df24f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 24 Feb 2021 16:43:09 +0100 Subject: [PATCH 0746/1818] Set awesomeversion to 21.2.3 (#46989) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fa5862b5c28ff2..d867db5cef0e12 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,7 +6,7 @@ astral==1.10.1 async-upnp-client==0.14.13 async_timeout==3.0.1 attrs==19.3.0 -awesomeversion==21.2.2 +awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/requirements.txt b/requirements.txt index d0b894bcb589fb..14ebf2708ada8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ aiohttp==3.7.3 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 -awesomeversion==21.2.2 +awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 diff --git a/setup.py b/setup.py index 1dbc29f5537669..6dbe35760a6ddf 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", - "awesomeversion==21.2.2", + "awesomeversion==21.2.3", "bcrypt==3.1.7", "certifi>=2020.12.5", "ciso8601==2.1.3", From 11a89bc3ac54270b61993ce4588e16516664daf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 24 Feb 2021 17:02:48 +0100 Subject: [PATCH 0747/1818] Add addon selector (#46789) --- homeassistant/components/hassio/services.yaml | 101 ++++++++---------- homeassistant/helpers/selector.py | 7 ++ tests/helpers/test_selector.py | 9 ++ 3 files changed, 62 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/hassio/services.yaml b/homeassistant/components/hassio/services.yaml index 8afdcc633bff1f..3570a857c554e1 100644 --- a/homeassistant/components/hassio/services.yaml +++ b/homeassistant/components/hassio/services.yaml @@ -1,110 +1,101 @@ -addon_install: - description: Install a Hass.io docker add-on. - fields: - addon: - description: The add-on slug. - example: core_ssh - version: - description: Optional or it will be use the latest version. - example: "0.2" - addon_start: - description: Start a Hass.io docker add-on. + name: Start add-on + description: Start add-on. fields: addon: + name: Add-on + required: true description: The add-on slug. example: core_ssh + selector: + addon: addon_restart: - description: Restart a Hass.io docker add-on. + name: Restart add-on. + description: Restart add-on. fields: addon: + name: Add-on + required: true description: The add-on slug. example: core_ssh + selector: + addon: addon_stdin: - description: Write data to a Hass.io docker add-on stdin . + name: Write data to add-on stdin. + description: Write data to add-on stdin. fields: addon: + name: Add-on + required: true description: The add-on slug. example: core_ssh + selector: + addon: addon_stop: - description: Stop a Hass.io docker add-on. - fields: - addon: - description: The add-on slug. - example: core_ssh - -addon_uninstall: - description: Uninstall a Hass.io docker add-on. - fields: - addon: - description: The add-on slug. - example: core_ssh - -addon_update: - description: Update a Hass.io docker add-on. + name: Stop add-on. + description: Stop add-on. fields: addon: + name: Add-on + required: true description: The add-on slug. example: core_ssh - version: - description: Optional or it will be use the latest version. - example: "0.2" - -homeassistant_update: - description: Update the Home Assistant docker image. - fields: - version: - description: Optional or it will be use the latest version. - example: 0.40.1 + selector: + addon: host_reboot: + name: Reboot the host system. description: Reboot the host system. host_shutdown: + name: Poweroff the host system. description: Poweroff the host system. -host_update: - description: Update the host system. - fields: - version: - description: Optional or it will be use the latest version. - example: "0.3" - snapshot_full: + name: Create a full snapshot. description: Create a full snapshot. fields: name: + name: Name description: Optional or it will be the current date and time. example: "Snapshot 1" + selector: + text: password: + name: Password description: Optional password. example: "password" + selector: + text: snapshot_partial: + name: Create a partial snapshot. description: Create a partial snapshot. fields: addons: + name: Add-ons description: Optional list of addon slugs. example: ["core_ssh", "core_samba", "core_mosquitto"] + selector: + object: folders: + name: Folders description: Optional list of directories. example: ["homeassistant", "share"] + selector: + object: name: + name: Name description: Optional or it will be the current date and time. example: "Partial Snapshot 1" + selector: + text: password: + name: Password description: Optional password. example: "password" - -supervisor_reload: - description: Reload the Hass.io supervisor. - -supervisor_update: - description: Update the Hass.io supervisor. - fields: - version: - description: Optional or it will be use the latest version. - example: "0.3" + selector: + text: diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 68511a771d3ae7..34befe9c37b2eb 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -116,6 +116,13 @@ class NumberSelector(Selector): ) +@SELECTORS.register("addon") +class AddonSelector(Selector): + """Selector of a add-on.""" + + CONFIG_SCHEMA = vol.Schema({}) + + @SELECTORS.register("boolean") class BooleanSelector(Selector): """Selector of a boolean value.""" diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index c43ed4097e0856..23d8200be23c73 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -118,6 +118,15 @@ def test_number_selector_schema(schema): selector.validate_selector({"number": schema}) +@pytest.mark.parametrize( + "schema", + ({},), +) +def test_addon_selector_schema(schema): + """Test add-on selector.""" + selector.validate_selector({"addon": schema}) + + @pytest.mark.parametrize( "schema", ({},), From 106ae184322c21cf820647261bc9caa70119f529 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Feb 2021 11:15:01 -0500 Subject: [PATCH 0748/1818] Climacell fixes: Use common keys for strings, fix temp_low measurement, add windy condition (#46991) * use common keys for lat and long * additional fixes * Update homeassistant/components/climacell/strings.json Co-authored-by: Milan Meulemans Co-authored-by: Milan Meulemans --- homeassistant/components/climacell/const.py | 2 ++ homeassistant/components/climacell/strings.json | 8 ++++---- homeassistant/components/climacell/weather.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/climacell/const.py b/homeassistant/components/climacell/const.py index 28117a164f8c07..f2d0a596121908 100644 --- a/homeassistant/components/climacell/const.py +++ b/homeassistant/components/climacell/const.py @@ -12,6 +12,7 @@ ATTR_CONDITION_SNOWY, ATTR_CONDITION_SNOWY_RAINY, ATTR_CONDITION_SUNNY, + ATTR_CONDITION_WINDY, ) CONF_TIMESTEP = "timestep" @@ -33,6 +34,7 @@ MAX_REQUESTS_PER_DAY = 1000 CONDITIONS = { + "breezy": ATTR_CONDITION_WINDY, "freezing_rain_heavy": ATTR_CONDITION_SNOWY_RAINY, "freezing_rain": ATTR_CONDITION_SNOWY_RAINY, "freezing_rain_light": ATTR_CONDITION_SNOWY_RAINY, diff --git a/homeassistant/components/climacell/strings.json b/homeassistant/components/climacell/strings.json index 45a1d5b7404bce..be80ac4e506a9c 100644 --- a/homeassistant/components/climacell/strings.json +++ b/homeassistant/components/climacell/strings.json @@ -3,12 +3,12 @@ "config": { "step": { "user": { - "description": "If [%key:component::climacell::config::step::user::data::latitude%] and [%key:component::climacell::config::step::user::data::longitude%] are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default.", + "description": "If [%key:common::config_flow::data::latitude%] and [%key:common::config_flow::data::longitude%] are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default.", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "api_key": "[%key:common::config_flow::data::api_key%]", - "latitude": "Latitude", - "longitude": "Longitude" + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index a4802586bf179b..da3282108a5ce6 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -275,7 +275,7 @@ def forecast(self): ( self._get_cc_value(item, CC_ATTR_TEMPERATURE_LOW) for item in forecast[CC_ATTR_TEMPERATURE] - if "max" in item + if "min" in item ), temp_low, ) From db0d815f9d95d841465c21b01392edc2450093cb Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 24 Feb 2021 18:44:12 +0100 Subject: [PATCH 0749/1818] Use location common key reference in totalconnect (#46995) --- homeassistant/components/totalconnect/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/totalconnect/strings.json b/homeassistant/components/totalconnect/strings.json index 41b0bf4648bae2..f284e4b86da7f0 100644 --- a/homeassistant/components/totalconnect/strings.json +++ b/homeassistant/components/totalconnect/strings.json @@ -12,7 +12,7 @@ "title": "Location Usercodes", "description": "Enter the usercode for this user at this location", "data": { - "location": "Location" + "location": "[%key:common::config_flow::data::location%]" } }, "reauth_confirm": { From 722b1e8746176d800644f6fa3e9d373e5969cacb Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 24 Feb 2021 19:20:40 +0100 Subject: [PATCH 0750/1818] Add Netatmo device trigger (#45387) Co-authored-by: J. Nick Koston --- homeassistant/components/netatmo/climate.py | 6 + homeassistant/components/netatmo/const.py | 101 +++++- .../components/netatmo/device_trigger.py | 155 +++++++++ .../components/netatmo/netatmo_entity_base.py | 6 +- homeassistant/components/netatmo/strings.json | 24 +- .../components/netatmo/translations/en.json | 22 ++ homeassistant/components/netatmo/webhook.py | 40 ++- .../components/netatmo/test_device_trigger.py | 311 ++++++++++++++++++ 8 files changed, 632 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/netatmo/device_trigger.py create mode 100644 tests/components/netatmo/test_device_trigger.py diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 34a94df008a643..91026c40c2fa29 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -26,12 +26,14 @@ ) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( ATTR_HEATING_POWER_REQUEST, ATTR_SCHEDULE_NAME, ATTR_SELECTED_SCHEDULE, + DATA_DEVICE_IDS, DATA_HANDLER, DATA_HOMES, DATA_SCHEDULES, @@ -237,6 +239,10 @@ async def async_added_to_hass(self) -> None: ) ) + registry = await async_get_registry(self.hass) + device = registry.async_get_device({(DOMAIN, self._id)}, set()) + self.hass.data[DOMAIN][DATA_DEVICE_IDS][self._home_id] = device.id + async def handle_event(self, event): """Handle webhook events.""" data = event["data"] diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index 4c3650ef121dd9..baee3e4035c775 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -4,21 +4,36 @@ DOMAIN = "netatmo" MANUFACTURER = "Netatmo" +MODEL_NAPLUG = "Relay" +MODEL_NATHERM1 = "Smart Thermostat" +MODEL_NRV = "Smart Radiator Valves" +MODEL_NOC = "Smart Outdoor Camera" +MODEL_NACAMERA = "Smart Indoor Camera" +MODEL_NSD = "Smart Smoke Alarm" +MODEL_NACAMDOORTAG = "Smart Door and Window Sensors" +MODEL_NHC = "Smart Indoor Air Quality Monitor" +MODEL_NAMAIN = "Smart Home Weather station – indoor module" +MODEL_NAMODULE1 = "Smart Home Weather station – outdoor module" +MODEL_NAMODULE4 = "Smart Additional Indoor module" +MODEL_NAMODULE3 = "Smart Rain Gauge" +MODEL_NAMODULE2 = "Smart Anemometer" +MODEL_PUBLIC = "Public Weather stations" + MODELS = { - "NAPlug": "Relay", - "NATherm1": "Smart Thermostat", - "NRV": "Smart Radiator Valves", - "NACamera": "Smart Indoor Camera", - "NOC": "Smart Outdoor Camera", - "NSD": "Smart Smoke Alarm", - "NACamDoorTag": "Smart Door and Window Sensors", - "NHC": "Smart Indoor Air Quality Monitor", - "NAMain": "Smart Home Weather station – indoor module", - "NAModule1": "Smart Home Weather station – outdoor module", - "NAModule4": "Smart Additional Indoor module", - "NAModule3": "Smart Rain Gauge", - "NAModule2": "Smart Anemometer", - "public": "Public Weather stations", + "NAPlug": MODEL_NAPLUG, + "NATherm1": MODEL_NATHERM1, + "NRV": MODEL_NRV, + "NACamera": MODEL_NACAMERA, + "NOC": MODEL_NOC, + "NSD": MODEL_NSD, + "NACamDoorTag": MODEL_NACAMDOORTAG, + "NHC": MODEL_NHC, + "NAMain": MODEL_NAMAIN, + "NAModule1": MODEL_NAMODULE1, + "NAModule4": MODEL_NAMODULE4, + "NAModule3": MODEL_NAMODULE3, + "NAModule2": MODEL_NAMODULE2, + "public": MODEL_PUBLIC, } AUTH = "netatmo_auth" @@ -76,12 +91,66 @@ SERVICE_SET_PERSONS_HOME = "set_persons_home" SERVICE_SET_PERSON_AWAY = "set_person_away" +# Climate events +EVENT_TYPE_SET_POINT = "set_point" EVENT_TYPE_CANCEL_SET_POINT = "cancel_set_point" +EVENT_TYPE_THERM_MODE = "therm_mode" +# Camera events EVENT_TYPE_LIGHT_MODE = "light_mode" +EVENT_TYPE_CAMERA_OUTDOOR = "outdoor" +EVENT_TYPE_CAMERA_ANIMAL = "animal" +EVENT_TYPE_CAMERA_HUMAN = "human" +EVENT_TYPE_CAMERA_VEHICLE = "vehicle" +EVENT_TYPE_CAMERA_MOVEMENT = "movement" +EVENT_TYPE_CAMERA_PERSON = "person" +EVENT_TYPE_CAMERA_PERSON_AWAY = "person_away" +# Door tags +EVENT_TYPE_DOOR_TAG_SMALL_MOVE = "tag_small_move" +EVENT_TYPE_DOOR_TAG_BIG_MOVE = "tag_big_move" +EVENT_TYPE_DOOR_TAG_OPEN = "tag_open" EVENT_TYPE_OFF = "off" EVENT_TYPE_ON = "on" -EVENT_TYPE_SET_POINT = "set_point" -EVENT_TYPE_THERM_MODE = "therm_mode" +EVENT_TYPE_ALARM_STARTED = "alarm_started" + +OUTDOOR_CAMERA_TRIGGERS = [ + EVENT_TYPE_CAMERA_ANIMAL, + EVENT_TYPE_CAMERA_HUMAN, + EVENT_TYPE_CAMERA_OUTDOOR, + EVENT_TYPE_CAMERA_VEHICLE, +] +INDOOR_CAMERA_TRIGGERS = [ + EVENT_TYPE_CAMERA_MOVEMENT, + EVENT_TYPE_CAMERA_PERSON, + EVENT_TYPE_CAMERA_PERSON_AWAY, + EVENT_TYPE_ALARM_STARTED, +] +DOOR_TAG_TRIGGERS = [ + EVENT_TYPE_DOOR_TAG_SMALL_MOVE, + EVENT_TYPE_DOOR_TAG_BIG_MOVE, + EVENT_TYPE_DOOR_TAG_OPEN, +] +CLIMATE_TRIGGERS = [ + EVENT_TYPE_SET_POINT, + EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_THERM_MODE, +] +EVENT_ID_MAP = { + EVENT_TYPE_CAMERA_MOVEMENT: "device_id", + EVENT_TYPE_CAMERA_PERSON: "device_id", + EVENT_TYPE_CAMERA_PERSON_AWAY: "device_id", + EVENT_TYPE_CAMERA_ANIMAL: "device_id", + EVENT_TYPE_CAMERA_HUMAN: "device_id", + EVENT_TYPE_CAMERA_OUTDOOR: "device_id", + EVENT_TYPE_CAMERA_VEHICLE: "device_id", + EVENT_TYPE_DOOR_TAG_SMALL_MOVE: "device_id", + EVENT_TYPE_DOOR_TAG_BIG_MOVE: "device_id", + EVENT_TYPE_DOOR_TAG_OPEN: "device_id", + EVENT_TYPE_LIGHT_MODE: "device_id", + EVENT_TYPE_ALARM_STARTED: "device_id", + EVENT_TYPE_CANCEL_SET_POINT: "room_id", + EVENT_TYPE_SET_POINT: "room_id", + EVENT_TYPE_THERM_MODE: "home_id", +} MODE_LIGHT_ON = "on" MODE_LIGHT_OFF = "off" diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py new file mode 100644 index 00000000000000..38601e981db7cc --- /dev/null +++ b/homeassistant/components/netatmo/device_trigger.py @@ -0,0 +1,155 @@ +"""Provides device automations for Netatmo.""" +from typing import List + +import voluptuous as vol + +from homeassistant.components.automation import AutomationActionType +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.device_automation.exceptions import ( + InvalidDeviceAutomationConfig, +) +from homeassistant.components.homeassistant.triggers import event as event_trigger +from homeassistant.const import ( + ATTR_DEVICE_ID, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, +) +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.typing import ConfigType + +from . import DOMAIN +from .climate import STATE_NETATMO_AWAY, STATE_NETATMO_HG, STATE_NETATMO_SCHEDULE +from .const import ( + CLIMATE_TRIGGERS, + EVENT_TYPE_THERM_MODE, + INDOOR_CAMERA_TRIGGERS, + MODEL_NACAMERA, + MODEL_NATHERM1, + MODEL_NOC, + MODEL_NRV, + NETATMO_EVENT, + OUTDOOR_CAMERA_TRIGGERS, +) + +CONF_SUBTYPE = "subtype" + +DEVICES = { + MODEL_NACAMERA: INDOOR_CAMERA_TRIGGERS, + MODEL_NOC: OUTDOOR_CAMERA_TRIGGERS, + MODEL_NATHERM1: CLIMATE_TRIGGERS, + MODEL_NRV: CLIMATE_TRIGGERS, +} + +SUBTYPES = { + EVENT_TYPE_THERM_MODE: [ + STATE_NETATMO_SCHEDULE, + STATE_NETATMO_HG, + STATE_NETATMO_AWAY, + ] +} + +TRIGGER_TYPES = OUTDOOR_CAMERA_TRIGGERS + INDOOR_CAMERA_TRIGGERS + CLIMATE_TRIGGERS + +TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + vol.Optional(CONF_SUBTYPE): str, + } +) + + +async def async_validate_trigger_config(hass, config): + """Validate config.""" + config = TRIGGER_SCHEMA(config) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(config[CONF_DEVICE_ID]) + + trigger = config[CONF_TYPE] + + if ( + not device + or device.model not in DEVICES + or trigger not in DEVICES[device.model] + ): + raise InvalidDeviceAutomationConfig(f"Unsupported model {device.model}") + + return config + + +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device triggers for Netatmo devices.""" + registry = await entity_registry.async_get_registry(hass) + device_registry = await hass.helpers.device_registry.async_get_registry() + triggers = [] + + for entry in entity_registry.async_entries_for_device(registry, device_id): + device = device_registry.async_get(device_id) + + for trigger in DEVICES.get(device.model, []): + if trigger in SUBTYPES: + for subtype in SUBTYPES[trigger]: + triggers.append( + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: trigger, + CONF_SUBTYPE: subtype, + } + ) + else: + triggers.append( + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: trigger, + } + ) + + return triggers + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: dict, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + config = TRIGGER_SCHEMA(config) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(config[CONF_DEVICE_ID]) + + if not device: + return + + if device.model not in DEVICES: + return + + event_config = { + event_trigger.CONF_PLATFORM: "event", + event_trigger.CONF_EVENT_TYPE: NETATMO_EVENT, + event_trigger.CONF_EVENT_DATA: { + "type": config[CONF_TYPE], + ATTR_DEVICE_ID: config[ATTR_DEVICE_ID], + }, + } + if config[CONF_TYPE] in SUBTYPES: + event_config[event_trigger.CONF_EVENT_DATA]["data"] = { + "mode": config[CONF_SUBTYPE] + } + + event_config = event_trigger.TRIGGER_SCHEMA(event_config) + return await event_trigger.async_attach_trigger( + hass, event_config, action, automation_info, platform_type="device" + ) diff --git a/homeassistant/components/netatmo/netatmo_entity_base.py b/homeassistant/components/netatmo/netatmo_entity_base.py index d0753613555ed9..1845cbe76e9621 100644 --- a/homeassistant/components/netatmo/netatmo_entity_base.py +++ b/homeassistant/components/netatmo/netatmo_entity_base.py @@ -5,7 +5,7 @@ from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.entity import Entity -from .const import DOMAIN, MANUFACTURER, MODELS, SIGNAL_NAME +from .const import DATA_DEVICE_IDS, DOMAIN, MANUFACTURER, MODELS, SIGNAL_NAME from .data_handler import NetatmoDataHandler _LOGGER = logging.getLogger(__name__) @@ -58,6 +58,10 @@ async def async_added_to_hass(self) -> None: await self.data_handler.unregister_data_class(signal_name, None) + registry = await self.hass.helpers.device_registry.async_get_registry() + device = registry.async_get_device({(DOMAIN, self._id)}, set()) + self.hass.data[DOMAIN][DATA_DEVICE_IDS][self._id] = device.id + self.async_update_callback() async def async_will_remove_from_hass(self): diff --git a/homeassistant/components/netatmo/strings.json b/homeassistant/components/netatmo/strings.json index 60fdab5f22c5f7..c65001b2e8f90b 100644 --- a/homeassistant/components/netatmo/strings.json +++ b/homeassistant/components/netatmo/strings.json @@ -39,5 +39,27 @@ "title": "Netatmo public weather sensor" } } + }, + "device_automation": { + "trigger_subtype": { + "away": "away", + "schedule": "schedule", + "hg": "frost guard" + }, + "trigger_type": { + "turned_off": "{entity_name} turned off", + "turned_on": "{entity_name} turned on", + "human": "{entity_name} detected a human", + "movement": "{entity_name} detected movement", + "person": "{entity_name} detected a person", + "person_away": "{entity_name} detected a person has left", + "animal": "{entity_name} detected an animal", + "outdoor": "{entity_name} detected an outdoor event", + "vehicle": "{entity_name} detected a vehicle", + "alarm_started": "{entity_name} detected an alarm", + "set_point": "Target temperature {entity_name} set manually", + "cancel_set_point": "{entity_name} has resumed its schedule", + "therm_mode": "{entity_name} switched to \"{subtype}\"" + } } -} +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/en.json b/homeassistant/components/netatmo/translations/en.json index e31d801b7a0da2..7e230374720a09 100644 --- a/homeassistant/components/netatmo/translations/en.json +++ b/homeassistant/components/netatmo/translations/en.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "away", + "hg": "frost guard", + "schedule": "schedule" + }, + "trigger_type": { + "alarm_started": "{entity_name} detected an alarm", + "animal": "{entity_name} detected an animal", + "cancel_set_point": "{entity_name} has resumed its schedule", + "human": "{entity_name} detected a human", + "movement": "{entity_name} detected movement", + "outdoor": "{entity_name} detected an outdoor event", + "person": "{entity_name} detected a person", + "person_away": "{entity_name} detected a person has left", + "set_point": "Target temperature {entity_name} set manually", + "therm_mode": "{entity_name} switched to \"{subtype}\"", + "turned_off": "{entity_name} turned off", + "turned_on": "{entity_name} turned on", + "vehicle": "{entity_name} detected a vehicle" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 309451fd982d17..1fe7302038eae2 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -1,8 +1,7 @@ """The Netatmo integration.""" import logging -from homeassistant.const import ATTR_ID -from homeassistant.core import callback +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( @@ -11,9 +10,11 @@ ATTR_IS_KNOWN, ATTR_NAME, ATTR_PERSONS, + DATA_DEVICE_IDS, DATA_PERSONS, DEFAULT_PERSON, DOMAIN, + EVENT_ID_MAP, NETATMO_EVENT, ) @@ -38,17 +39,16 @@ async def handle_webhook(hass, webhook_id, request): event_type = data.get(ATTR_EVENT_TYPE) if event_type in EVENT_TYPE_MAP: - async_send_event(hass, event_type, data) + await async_send_event(hass, event_type, data) for event_data in data.get(EVENT_TYPE_MAP[event_type], []): - async_evaluate_event(hass, event_data) + await async_evaluate_event(hass, event_data) else: - async_evaluate_event(hass, data) + await async_evaluate_event(hass, data) -@callback -def async_evaluate_event(hass, event_data): +async def async_evaluate_event(hass, event_data): """Evaluate events from webhook.""" event_type = event_data.get(ATTR_EVENT_TYPE) @@ -62,21 +62,31 @@ def async_evaluate_event(hass, event_data): person_event_data[ATTR_IS_KNOWN] = person.get(ATTR_IS_KNOWN) person_event_data[ATTR_FACE_URL] = person.get(ATTR_FACE_URL) - async_send_event(hass, event_type, person_event_data) + await async_send_event(hass, event_type, person_event_data) else: - _LOGGER.debug("%s: %s", event_type, event_data) - async_send_event(hass, event_type, event_data) + await async_send_event(hass, event_type, event_data) -@callback -def async_send_event(hass, event_type, data): +async def async_send_event(hass, event_type, data): """Send events.""" - hass.bus.async_fire( - event_type=NETATMO_EVENT, event_data={"type": event_type, "data": data} - ) + _LOGGER.debug("%s: %s", event_type, data) async_dispatcher_send( hass, f"signal-{DOMAIN}-webhook-{event_type}", {"type": event_type, "data": data}, ) + + if event_type not in EVENT_ID_MAP: + return + + data_device_id = data[EVENT_ID_MAP[event_type]] + + hass.bus.async_fire( + event_type=NETATMO_EVENT, + event_data={ + "type": event_type, + "data": data, + ATTR_DEVICE_ID: hass.data[DOMAIN][DATA_DEVICE_IDS].get(data_device_id), + }, + ) diff --git a/tests/components/netatmo/test_device_trigger.py b/tests/components/netatmo/test_device_trigger.py new file mode 100644 index 00000000000000..7e014d2648f9f9 --- /dev/null +++ b/tests/components/netatmo/test_device_trigger.py @@ -0,0 +1,311 @@ +"""The tests for Netatmo device triggers.""" +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.netatmo import DOMAIN as NETATMO_DOMAIN +from homeassistant.components.netatmo.const import ( + CLIMATE_TRIGGERS, + INDOOR_CAMERA_TRIGGERS, + MODEL_NACAMERA, + MODEL_NAPLUG, + MODEL_NATHERM1, + MODEL_NOC, + MODEL_NRV, + NETATMO_EVENT, + OUTDOOR_CAMERA_TRIGGERS, +) +from homeassistant.components.netatmo.device_trigger import SUBTYPES +from homeassistant.const import ATTR_DEVICE_ID +from homeassistant.helpers import device_registry +from homeassistant.setup import async_setup_component + +from tests.common import ( + MockConfigEntry, + assert_lists_same, + async_capture_events, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass): + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +@pytest.mark.parametrize( + "platform,device_type,event_types", + [ + ("camera", MODEL_NOC, OUTDOOR_CAMERA_TRIGGERS), + ("camera", MODEL_NACAMERA, INDOOR_CAMERA_TRIGGERS), + ("climate", MODEL_NRV, CLIMATE_TRIGGERS), + ("climate", MODEL_NATHERM1, CLIMATE_TRIGGERS), + ], +) +async def test_get_triggers( + hass, device_reg, entity_reg, platform, device_type, event_types +): + """Test we get the expected triggers from a netatmo devices.""" + config_entry = MockConfigEntry(domain=NETATMO_DOMAIN, data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + model=device_type, + ) + entity_reg.async_get_or_create( + platform, NETATMO_DOMAIN, "5678", device_id=device_entry.id + ) + expected_triggers = [] + for event_type in event_types: + if event_type in SUBTYPES: + for subtype in SUBTYPES[event_type]: + expected_triggers.append( + { + "platform": "device", + "domain": NETATMO_DOMAIN, + "type": event_type, + "subtype": subtype, + "device_id": device_entry.id, + "entity_id": f"{platform}.{NETATMO_DOMAIN}_5678", + } + ) + else: + expected_triggers.append( + { + "platform": "device", + "domain": NETATMO_DOMAIN, + "type": event_type, + "device_id": device_entry.id, + "entity_id": f"{platform}.{NETATMO_DOMAIN}_5678", + } + ) + triggers = [ + trigger + for trigger in await async_get_device_automations( + hass, "trigger", device_entry.id + ) + if trigger["domain"] == NETATMO_DOMAIN + ] + assert_lists_same(triggers, expected_triggers) + + +@pytest.mark.parametrize( + "platform,camera_type,event_type", + [("camera", MODEL_NOC, trigger) for trigger in OUTDOOR_CAMERA_TRIGGERS] + + [("camera", MODEL_NACAMERA, trigger) for trigger in INDOOR_CAMERA_TRIGGERS] + + [ + ("climate", MODEL_NRV, trigger) + for trigger in CLIMATE_TRIGGERS + if trigger not in SUBTYPES + ] + + [ + ("climate", MODEL_NATHERM1, trigger) + for trigger in CLIMATE_TRIGGERS + if trigger not in SUBTYPES + ], +) +async def test_if_fires_on_event( + hass, calls, device_reg, entity_reg, platform, camera_type, event_type +): + """Test for event triggers firing.""" + mac_address = "12:34:56:AB:CD:EF" + connection = (device_registry.CONNECTION_NETWORK_MAC, mac_address) + config_entry = MockConfigEntry(domain=NETATMO_DOMAIN, data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={connection}, + identifiers={(NETATMO_DOMAIN, mac_address)}, + model=camera_type, + ) + entity_reg.async_get_or_create( + platform, NETATMO_DOMAIN, "5678", device_id=device_entry.id + ) + events = async_capture_events(hass, "netatmo_event") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": NETATMO_DOMAIN, + "device_id": device_entry.id, + "entity_id": f"{platform}.{NETATMO_DOMAIN}_5678", + "type": event_type, + }, + "action": { + "service": "test.automation", + "data_template": { + "some": ( + "{{trigger.event.data.type}} - {{trigger.platform}} - {{trigger.event.data.device_id}}" + ) + }, + }, + }, + ] + }, + ) + + device = device_reg.async_get_device(set(), {connection}) + assert device is not None + + # Fake that the entity is turning on. + hass.bus.async_fire( + event_type=NETATMO_EVENT, + event_data={ + "type": event_type, + ATTR_DEVICE_ID: device.id, + }, + ) + await hass.async_block_till_done() + assert len(events) == 1 + assert len(calls) == 1 + assert calls[0].data["some"] == f"{event_type} - device - {device.id}" + + +@pytest.mark.parametrize( + "platform,camera_type,event_type,sub_type", + [ + ("climate", MODEL_NRV, trigger, subtype) + for trigger in SUBTYPES + for subtype in SUBTYPES[trigger] + ] + + [ + ("climate", MODEL_NATHERM1, trigger, subtype) + for trigger in SUBTYPES + for subtype in SUBTYPES[trigger] + ], +) +async def test_if_fires_on_event_with_subtype( + hass, calls, device_reg, entity_reg, platform, camera_type, event_type, sub_type +): + """Test for event triggers firing.""" + mac_address = "12:34:56:AB:CD:EF" + connection = (device_registry.CONNECTION_NETWORK_MAC, mac_address) + config_entry = MockConfigEntry(domain=NETATMO_DOMAIN, data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={connection}, + identifiers={(NETATMO_DOMAIN, mac_address)}, + model=camera_type, + ) + entity_reg.async_get_or_create( + platform, NETATMO_DOMAIN, "5678", device_id=device_entry.id + ) + events = async_capture_events(hass, "netatmo_event") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": NETATMO_DOMAIN, + "device_id": device_entry.id, + "entity_id": f"{platform}.{NETATMO_DOMAIN}_5678", + "type": event_type, + "subtype": sub_type, + }, + "action": { + "service": "test.automation", + "data_template": { + "some": ( + "{{trigger.event.data.type}} - {{trigger.event.data.data.mode}} - " + "{{trigger.platform}} - {{trigger.event.data.device_id}}" + ) + }, + }, + }, + ] + }, + ) + + device = device_reg.async_get_device(set(), {connection}) + assert device is not None + + # Fake that the entity is turning on. + hass.bus.async_fire( + event_type=NETATMO_EVENT, + event_data={ + "type": event_type, + "data": { + "mode": sub_type, + }, + ATTR_DEVICE_ID: device.id, + }, + ) + await hass.async_block_till_done() + assert len(events) == 1 + assert len(calls) == 1 + assert calls[0].data["some"] == f"{event_type} - {sub_type} - device - {device.id}" + + +@pytest.mark.parametrize( + "platform,device_type,event_type", + [("climate", MODEL_NAPLUG, trigger) for trigger in CLIMATE_TRIGGERS], +) +async def test_if_invalid_device( + hass, device_reg, entity_reg, platform, device_type, event_type +): + """Test for event triggers firing.""" + mac_address = "12:34:56:AB:CD:EF" + connection = (device_registry.CONNECTION_NETWORK_MAC, mac_address) + config_entry = MockConfigEntry(domain=NETATMO_DOMAIN, data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={connection}, + identifiers={(NETATMO_DOMAIN, mac_address)}, + model=device_type, + ) + entity_reg.async_get_or_create( + platform, NETATMO_DOMAIN, "5678", device_id=device_entry.id + ) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": NETATMO_DOMAIN, + "device_id": device_entry.id, + "entity_id": f"{platform}.{NETATMO_DOMAIN}_5678", + "type": event_type, + }, + "action": { + "service": "test.automation", + "data_template": { + "some": ( + "{{trigger.event.data.type}} - {{trigger.platform}} - {{trigger.event.data.device_id}}" + ) + }, + }, + }, + ] + }, + ) From 0eb8951aed07f18f641a8061ccb5602292314be7 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 24 Feb 2021 19:29:53 +0100 Subject: [PATCH 0751/1818] Remove recursive key reference (#46999) --- homeassistant/components/syncthru/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/syncthru/strings.json b/homeassistant/components/syncthru/strings.json index 0164fdf6ddc2fe..67f50e84a983ed 100644 --- a/homeassistant/components/syncthru/strings.json +++ b/homeassistant/components/syncthru/strings.json @@ -12,7 +12,7 @@ "step": { "confirm": { "data": { - "name": "[%key:component::syncthru::config::step::user::data::name%]", + "name": "[%key:common::config_flow::data::name%]", "url": "[%key:component::syncthru::config::step::user::data::url%]" } }, From 868a536d8175d0a56fb8766d1fb38571dfc43b52 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 24 Feb 2021 19:39:35 +0100 Subject: [PATCH 0752/1818] Add number platform to Z-Wave JS (#46956) * add number platform to zwave_js integration * add discovery scheme for thermostat valve control, using number platform Co-authored-by: kpine --- homeassistant/components/zwave_js/const.py | 1 + .../components/zwave_js/discovery.py | 8 + homeassistant/components/zwave_js/number.py | 84 +++ tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_number.py | 69 ++ .../aeotec_radiator_thermostat_state.json | 626 ++++++++++++++++++ 6 files changed, 802 insertions(+) create mode 100644 homeassistant/components/zwave_js/number.py create mode 100644 tests/components/zwave_js/test_number.py create mode 100644 tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index dba4e6d33a3722..19e6fc3db14bd4 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -9,6 +9,7 @@ "fan", "light", "lock", + "number", "sensor", "switch", ] diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index f4f5c359e2260a..77709f84e58d5c 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -365,6 +365,14 @@ class ZWaveDiscoverySchema: device_class_specific={"Fan Switch"}, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), + # number platform + # valve control for thermostats + ZWaveDiscoverySchema( + platform="number", + hint="Valve control", + device_class_generic={"Thermostat"}, + primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, + ), # lights # primary value is the currentValue (brightness) # catch any device with multilevel CC as light diff --git a/homeassistant/components/zwave_js/number.py b/homeassistant/components/zwave_js/number.py new file mode 100644 index 00000000000000..8f8e894cda211d --- /dev/null +++ b/homeassistant/components/zwave_js/number.py @@ -0,0 +1,84 @@ +"""Support for Z-Wave controls using the number platform.""" +from typing import Callable, List, Optional + +from zwave_js_server.client import Client as ZwaveClient + +from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN, NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN +from .discovery import ZwaveDiscoveryInfo +from .entity import ZWaveBaseEntity + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable +) -> None: + """Set up Z-Wave Number entity from Config Entry.""" + client: ZwaveClient = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT] + + @callback + def async_add_number(info: ZwaveDiscoveryInfo) -> None: + """Add Z-Wave number entity.""" + entities: List[ZWaveBaseEntity] = [] + entities.append(ZwaveNumberEntity(config_entry, client, info)) + async_add_entities(entities) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{config_entry.entry_id}_add_{NUMBER_DOMAIN}", + async_add_number, + ) + ) + + +class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity): + """Representation of a Z-Wave number entity.""" + + def __init__( + self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo + ) -> None: + """Initialize a ZwaveNumberEntity entity.""" + super().__init__(config_entry, client, info) + self._name = self.generate_name( + include_value_name=True, alternate_value_name=info.platform_hint + ) + if self.info.primary_value.metadata.writeable: + self._target_value = self.info.primary_value + else: + self._target_value = self.get_zwave_value("targetValue") + + @property + def min_value(self) -> float: + """Return the minimum value.""" + if self.info.primary_value.metadata.min is None: + return 0 + return float(self.info.primary_value.metadata.min) + + @property + def max_value(self) -> float: + """Return the maximum value.""" + if self.info.primary_value.metadata.max is None: + return 255 + return float(self.info.primary_value.metadata.max) + + @property + def value(self) -> Optional[float]: # type: ignore + """Return the entity value.""" + if self.info.primary_value.value is None: + return None + return float(self.info.primary_value.value) + + @property + def unit_of_measurement(self) -> Optional[str]: + """Return the unit of measurement of this entity, if any.""" + if self.info.primary_value.metadata.unit is None: + return None + return str(self.info.primary_value.metadata.unit) + + async def async_set_value(self, value: float) -> None: + """Set new value.""" + await self.info.node.async_set_value(self._target_value, value) diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index a9618acc64d97b..e0bc588abf421d 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -160,6 +160,12 @@ def ge_12730_state_fixture(): return json.loads(load_fixture("zwave_js/fan_ge_12730_state.json")) +@pytest.fixture(name="aeotec_radiator_thermostat_state", scope="session") +def aeotec_radiator_thermostat_state_fixture(): + """Load the Aeotec Radiator Thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/aeotec_radiator_thermostat_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -295,6 +301,14 @@ def nortek_thermostat_fixture(client, nortek_thermostat_state): return node +@pytest.fixture(name="aeotec_radiator_thermostat") +def aeotec_radiator_thermostat_fixture(client, aeotec_radiator_thermostat_state): + """Mock a Aeotec thermostat node.""" + node = Node(client, aeotec_radiator_thermostat_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="nortek_thermostat_added_event") def nortek_thermostat_added_event_fixture(client): """Mock a Nortek thermostat node added event.""" diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py new file mode 100644 index 00000000000000..b7d83068bea220 --- /dev/null +++ b/tests/components/zwave_js/test_number.py @@ -0,0 +1,69 @@ +"""Test the Z-Wave JS number platform.""" +from zwave_js_server.event import Event + +NUMBER_ENTITY = "number.thermostat_hvac_valve_control" + + +async def test_number(hass, client, aeotec_radiator_thermostat, integration): + """Test the number entity.""" + node = aeotec_radiator_thermostat + state = hass.states.get(NUMBER_ENTITY) + + assert state + assert state.state == "75.0" + + # Test turn on setting value + await hass.services.async_call( + "number", + "set_value", + {"entity_id": NUMBER_ENTITY, "value": 30}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 4 + assert args["valueId"] == { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "ccVersion": 1, + "endpoint": 0, + "property": "targetValue", + "propertyName": "targetValue", + "metadata": { + "label": "Target value", + "max": 99, + "min": 0, + "type": "number", + "readable": True, + "writeable": True, + "label": "Target value", + }, + } + assert args["value"] == 30.0 + + client.async_send_command.reset_mock() + + # Test value update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 4, + "args": { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "currentValue", + "newValue": 99, + "prevValue": 0, + "propertyName": "currentValue", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(NUMBER_ENTITY) + assert state.state == "99.0" diff --git a/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json b/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json new file mode 100644 index 00000000000000..8cd6fe782016e0 --- /dev/null +++ b/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json @@ -0,0 +1,626 @@ +{ + "nodeId": 4, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "deviceClass": { + "basic": "Routing Slave", + "generic": "Thermostat", + "specific": "Thermostat General V2", + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": false, + "isFrequentListening": true, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 881, + "productId": 21, + "productType": 2, + "firmwareVersion": "0.16", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 7, + "deviceConfig": { + "manufacturerId": 881, + "manufacturer": "Aeotec Ltd.", + "label": "Radiator Thermostat", + "description": "Thermostat - HVAC", + "devices": [{ "productType": "0x0002", "productId": "0x0015" }], + "firmwareVersion": { "min": "0.0", "max": "255.255" }, + "paramInformation": { "_map": {} } + }, + "label": "Radiator Thermostat", + "neighbors": [6, 7, 45, 67], + "interviewAttempts": 1, + "endpoints": [ + { "nodeId": 4, "index": 0, "installerIcon": 4608, "userIcon": 4608 } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 75 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { "switchType": 2 } + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { "switchType": 2 } + } + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 5, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { "sensorType": 1, "scale": 0 } + }, + "value": 19.37 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "11": "Energy heat", + "15": "Full power" + } + }, + "value": 31 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 3, + "metadata": { "type": "any", "readable": true, "writeable": true } + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 8, + "max": 28, + "unit": "\u00b0C", + "ccSpecific": { "setpointType": 1 } + }, + "value": 24 + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 8, + "max": 28, + "unit": "\u00b0C", + "ccSpecific": { "setpointType": 11 } + }, + "value": 18 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "Invert LCD orientation", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Normal orientation", + "1": "LCD content inverted" + }, + "label": "Invert LCD orientation", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "LCD Timeout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 30, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "LCD Timeout", + "description": "LCD Timeout in seconds", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Backlight", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Backlight disabled", + "1": "Backlight enabled" + }, + "label": "Backlight", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Battery report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Battery reporting disabled", + "1": "Battery reporting enabled" + }, + "label": "Battery report", + "description": "Battery reporting", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Measured Temperature", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 50, + "default": 5, + "format": 0, + "allowManualEntry": true, + "label": "Measured Temperature", + "description": "Measured Temperature report. Reporting Delta in 1/10 Celsius. '0' to disable reporting.", + "isFromConfig": true + }, + "value": 5 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Valve position", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Valve position", + "description": "Valve position report. Reporting delta in percent. '0' to disable reporting.", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Window open detection", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 3, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Detection Disabled", + "1": "Sensitivity low", + "2": "Sensitivity medium", + "3": "Sensitivity high" + }, + "label": "Window open detection", + "description": "Control 'Window open detection' sensitivity", + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Temperature Offset", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -128, + "max": 50, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Temperature Offset", + "description": "Measured Temperature offset", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Power Management", + "propertyName": "Power Management", + "propertyKeyName": "Battery maintenance status", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Battery maintenance status", + "states": { + "0": "idle", + "10": "Replace battery soon", + "11": "Replace battery now" + }, + "ccSpecific": { "notificationType": 8 } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "System", + "propertyName": "System", + "propertyKeyName": "Hardware status", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Hardware status", + "states": { + "0": "idle", + "3": "System hardware failure (with failure code)" + }, + "ccSpecific": { "notificationType": 9 } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 881 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 21 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "1": "ProtectedBySequence", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "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": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.61" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["0.16"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + } + ] + } \ No newline at end of file From 783e0f9a143181aae27c67e4c67cec14d2f35352 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Feb 2021 14:15:47 -0500 Subject: [PATCH 0753/1818] Setup config entry even if vizio device is unreachable (#46864) --- .../components/vizio/media_player.py | 17 +++------ tests/components/vizio/conftest.py | 3 ++ tests/components/vizio/test_media_player.py | 37 +++++++++---------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 4c06c89692afab..53c8a2bba88b8f 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -25,7 +25,6 @@ STATE_ON, ) from homeassistant.core import callback -from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import entity_platform from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import ( @@ -115,10 +114,6 @@ async def async_setup_entry( timeout=DEFAULT_TIMEOUT, ) - if not await device.can_connect_with_auth_check(): - _LOGGER.warning("Failed to connect to %s", host) - raise PlatformNotReady - apps_coordinator = hass.data[DOMAIN].get(CONF_APPS) entity = VizioDevice(config_entry, device, name, device_class, apps_coordinator) @@ -183,12 +178,6 @@ def _apps_list(self, apps: List[str]) -> List[str]: async def async_update(self) -> None: """Retrieve latest state of the device.""" - if not self._model: - self._model = await self._device.get_model_name(log_api_exception=False) - - if not self._sw_version: - self._sw_version = await self._device.get_version(log_api_exception=False) - is_on = await self._device.get_power_state(log_api_exception=False) if is_on is None: @@ -205,6 +194,12 @@ async def async_update(self) -> None: ) self._available = True + if not self._model: + self._model = await self._device.get_model_name(log_api_exception=False) + + if not self._sw_version: + self._sw_version = await self._device.get_version(log_api_exception=False) + if not is_on: self._state = STATE_OFF self._volume_level = None diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index 917e6f7f291c13..8124827dbf0853 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -157,6 +157,9 @@ def vizio_cant_connect_fixture(): with patch( "homeassistant.components.vizio.config_flow.VizioAsync.validate_ha_config", AsyncMock(return_value=False), + ), patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", + return_value=None, ): yield diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 78976032b00f09..6d0ba2781e65b4 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -239,17 +239,6 @@ def _assert_source_list_with_apps( assert attr["source_list"] == list_to_test -async def _test_setup_failure(hass: HomeAssistantType, config: str) -> None: - """Test generic Vizio entity setup failure.""" - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", - return_value=False, - ): - config_entry = MockConfigEntry(domain=DOMAIN, data=config, unique_id=UNIQUE_ID) - await _add_config_entry_to_hass(hass, config_entry) - assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 0 - - async def _test_service( hass: HomeAssistantType, domain: str, @@ -334,18 +323,28 @@ async def test_init_tv_unavailable( await _test_setup_tv(hass, None) -async def test_setup_failure_speaker( - hass: HomeAssistantType, vizio_connect: pytest.fixture +async def test_setup_unavailable_speaker( + hass: HomeAssistantType, vizio_cant_connect: pytest.fixture ) -> None: - """Test speaker entity setup failure.""" - await _test_setup_failure(hass, MOCK_SPEAKER_CONFIG) + """Test speaker entity sets up as unavailable.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, unique_id=UNIQUE_ID + ) + await _add_config_entry_to_hass(hass, config_entry) + assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 1 + assert hass.states.get("media_player.vizio").state == STATE_UNAVAILABLE -async def test_setup_failure_tv( - hass: HomeAssistantType, vizio_connect: pytest.fixture +async def test_setup_unavailable_tv( + hass: HomeAssistantType, vizio_cant_connect: pytest.fixture ) -> None: - """Test TV entity setup failure.""" - await _test_setup_failure(hass, MOCK_USER_VALID_TV_CONFIG) + """Test TV entity sets up as unavailable.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID + ) + await _add_config_entry_to_hass(hass, config_entry) + assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 1 + assert hass.states.get("media_player.vizio").state == STATE_UNAVAILABLE async def test_services( From daf7595ca64405572488b3e184dbf5eafbe97317 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 24 Feb 2021 20:16:36 +0100 Subject: [PATCH 0754/1818] Support value_template in MQTT triggers (#46891) * Support value_template in MQTT triggers * Rename value_template to payload_template * Revert "Rename value_template to payload_template" This reverts commit 902094eefc6612e6b5c3bdb7440520af050c7f20. --- homeassistant/components/mqtt/trigger.py | 26 ++++++++++++++------ tests/components/mqtt/test_trigger.py | 31 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index a82ea355343e4d..459adabd418fa1 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -4,7 +4,7 @@ import voluptuous as vol -from homeassistant.const import CONF_PAYLOAD, CONF_PLATFORM +from homeassistant.const import CONF_PAYLOAD, CONF_PLATFORM, CONF_VALUE_TEMPLATE from homeassistant.core import HassJob, callback from homeassistant.helpers import config_validation as cv, template @@ -23,6 +23,7 @@ vol.Required(CONF_PLATFORM): mqtt.DOMAIN, vol.Required(CONF_TOPIC): mqtt.util.valid_subscribe_topic_template, vol.Optional(CONF_PAYLOAD): cv.template, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All( vol.Coerce(int), vol.In([0, 1, 2]) @@ -36,7 +37,8 @@ async def async_attach_trigger(hass, config, action, automation_info): """Listen for state changes based on configuration.""" topic = config[CONF_TOPIC] - payload = config.get(CONF_PAYLOAD) + wanted_payload = config.get(CONF_PAYLOAD) + value_template = config.get(CONF_VALUE_TEMPLATE) encoding = config[CONF_ENCODING] or None qos = config[CONF_QOS] job = HassJob(action) @@ -44,19 +46,29 @@ async def async_attach_trigger(hass, config, action, automation_info): if automation_info: variables = automation_info.get("variables") - template.attach(hass, payload) - if payload: - payload = payload.async_render(variables, limited=True) + template.attach(hass, wanted_payload) + if wanted_payload: + wanted_payload = wanted_payload.async_render(variables, limited=True) template.attach(hass, topic) if isinstance(topic, template.Template): topic = topic.async_render(variables, limited=True) topic = mqtt.util.valid_subscribe_topic(topic) + template.attach(hass, value_template) + @callback def mqtt_automation_listener(mqttmsg): """Listen for MQTT messages.""" - if payload is None or payload == mqttmsg.payload: + payload = mqttmsg.payload + + if value_template is not None: + payload = value_template.async_render_with_possible_json_value( + payload, + error_value=None, + ) + + if wanted_payload is None or wanted_payload == payload: data = { "platform": "mqtt", "topic": mqttmsg.topic, @@ -73,7 +85,7 @@ def mqtt_automation_listener(mqttmsg): hass.async_run_hass_job(job, {"trigger": data}) _LOGGER.debug( - "Attaching MQTT trigger for topic: '%s', payload: '%s'", topic, payload + "Attaching MQTT trigger for topic: '%s', payload: '%s'", topic, wanted_payload ) remove = await mqtt.async_subscribe( diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 537a4f8dc64caf..23078b9ba23df6 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -111,6 +111,37 @@ async def test_if_fires_on_templated_topic_and_payload_match(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_payload_template(hass, calls): + """Test if message is fired on templated topic and payload match.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "mqtt", + "topic": "test-topic", + "payload": "hello", + "value_template": "{{ value_json.wanted_key }}", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + async_fire_mqtt_message(hass, "test-topic", "hello") + await hass.async_block_till_done() + assert len(calls) == 0 + + async_fire_mqtt_message(hass, "test-topic", '{"unwanted_key":"hello"}') + await hass.async_block_till_done() + assert len(calls) == 0 + + async_fire_mqtt_message(hass, "test-topic", '{"wanted_key":"hello"}') + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_non_allowed_templates(hass, calls, caplog): """Test non allowed function in template.""" assert await async_setup_component( From 39baeb62f20e68a85a9d2963ccb4af26ff4e271e Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 24 Feb 2021 20:51:12 +0100 Subject: [PATCH 0755/1818] Add Sonos media browser image proxy (#46902) --- homeassistant/components/sonos/const.py | 111 ++++++ homeassistant/components/sonos/exception.py | 6 + .../components/sonos/media_browser.py | 218 +++++++++++ .../components/sonos/media_player.py | 352 +++--------------- 4 files changed, 382 insertions(+), 305 deletions(-) create mode 100644 homeassistant/components/sonos/exception.py create mode 100644 homeassistant/components/sonos/media_browser.py diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index da397b3e5e71ef..63d5745da21a75 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -1,4 +1,20 @@ """Const for Sonos.""" +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_ALBUM, + MEDIA_CLASS_ARTIST, + MEDIA_CLASS_COMPOSER, + MEDIA_CLASS_CONTRIBUTING_ARTIST, + MEDIA_CLASS_GENRE, + MEDIA_CLASS_PLAYLIST, + MEDIA_CLASS_TRACK, + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_COMPOSER, + MEDIA_TYPE_CONTRIBUTING_ARTIST, + MEDIA_TYPE_GENRE, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_TRACK, +) DOMAIN = "sonos" DATA_SONOS = "sonos_media_player" @@ -10,3 +26,98 @@ SONOS_ALBUM_ARTIST = "album_artists" SONOS_TRACKS = "tracks" SONOS_COMPOSER = "composers" + +EXPANDABLE_MEDIA_TYPES = [ + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_COMPOSER, + MEDIA_TYPE_GENRE, + MEDIA_TYPE_PLAYLIST, + SONOS_ALBUM, + SONOS_ALBUM_ARTIST, + SONOS_ARTIST, + SONOS_GENRE, + SONOS_COMPOSER, + SONOS_PLAYLISTS, +] + +SONOS_TO_MEDIA_CLASSES = { + SONOS_ALBUM: MEDIA_CLASS_ALBUM, + SONOS_ALBUM_ARTIST: MEDIA_CLASS_ARTIST, + SONOS_ARTIST: MEDIA_CLASS_CONTRIBUTING_ARTIST, + SONOS_COMPOSER: MEDIA_CLASS_COMPOSER, + SONOS_GENRE: MEDIA_CLASS_GENRE, + SONOS_PLAYLISTS: MEDIA_CLASS_PLAYLIST, + SONOS_TRACKS: MEDIA_CLASS_TRACK, + "object.container.album.musicAlbum": MEDIA_CLASS_ALBUM, + "object.container.genre.musicGenre": MEDIA_CLASS_PLAYLIST, + "object.container.person.composer": MEDIA_CLASS_PLAYLIST, + "object.container.person.musicArtist": MEDIA_CLASS_ARTIST, + "object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST, + "object.container.playlistContainer": MEDIA_CLASS_PLAYLIST, + "object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK, +} + +SONOS_TO_MEDIA_TYPES = { + SONOS_ALBUM: MEDIA_TYPE_ALBUM, + SONOS_ALBUM_ARTIST: MEDIA_TYPE_ARTIST, + SONOS_ARTIST: MEDIA_TYPE_CONTRIBUTING_ARTIST, + SONOS_COMPOSER: MEDIA_TYPE_COMPOSER, + SONOS_GENRE: MEDIA_TYPE_GENRE, + SONOS_PLAYLISTS: MEDIA_TYPE_PLAYLIST, + SONOS_TRACKS: MEDIA_TYPE_TRACK, + "object.container.album.musicAlbum": MEDIA_TYPE_ALBUM, + "object.container.genre.musicGenre": MEDIA_TYPE_PLAYLIST, + "object.container.person.composer": MEDIA_TYPE_PLAYLIST, + "object.container.person.musicArtist": MEDIA_TYPE_ARTIST, + "object.container.playlistContainer.sameArtist": MEDIA_TYPE_ARTIST, + "object.container.playlistContainer": MEDIA_TYPE_PLAYLIST, + "object.item.audioItem.musicTrack": MEDIA_TYPE_TRACK, +} + +MEDIA_TYPES_TO_SONOS = { + MEDIA_TYPE_ALBUM: SONOS_ALBUM, + MEDIA_TYPE_ARTIST: SONOS_ALBUM_ARTIST, + MEDIA_TYPE_CONTRIBUTING_ARTIST: SONOS_ARTIST, + MEDIA_TYPE_COMPOSER: SONOS_COMPOSER, + MEDIA_TYPE_GENRE: SONOS_GENRE, + MEDIA_TYPE_PLAYLIST: SONOS_PLAYLISTS, + MEDIA_TYPE_TRACK: SONOS_TRACKS, +} + +SONOS_TYPES_MAPPING = { + "A:ALBUM": SONOS_ALBUM, + "A:ALBUMARTIST": SONOS_ALBUM_ARTIST, + "A:ARTIST": SONOS_ARTIST, + "A:COMPOSER": SONOS_COMPOSER, + "A:GENRE": SONOS_GENRE, + "A:PLAYLISTS": SONOS_PLAYLISTS, + "A:TRACKS": SONOS_TRACKS, + "object.container.album.musicAlbum": SONOS_ALBUM, + "object.container.genre.musicGenre": SONOS_GENRE, + "object.container.person.composer": SONOS_COMPOSER, + "object.container.person.musicArtist": SONOS_ALBUM_ARTIST, + "object.container.playlistContainer.sameArtist": SONOS_ARTIST, + "object.container.playlistContainer": SONOS_PLAYLISTS, + "object.item.audioItem.musicTrack": SONOS_TRACKS, +} + +LIBRARY_TITLES_MAPPING = { + "A:ALBUM": "Albums", + "A:ALBUMARTIST": "Artists", + "A:ARTIST": "Contributing Artists", + "A:COMPOSER": "Composers", + "A:GENRE": "Genres", + "A:PLAYLISTS": "Playlists", + "A:TRACKS": "Tracks", +} + +PLAYABLE_MEDIA_TYPES = [ + MEDIA_TYPE_ALBUM, + MEDIA_TYPE_ARTIST, + MEDIA_TYPE_COMPOSER, + MEDIA_TYPE_CONTRIBUTING_ARTIST, + MEDIA_TYPE_GENRE, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_TRACK, +] diff --git a/homeassistant/components/sonos/exception.py b/homeassistant/components/sonos/exception.py new file mode 100644 index 00000000000000..3d5a1230bcbad3 --- /dev/null +++ b/homeassistant/components/sonos/exception.py @@ -0,0 +1,6 @@ +"""Sonos specific exceptions.""" +from homeassistant.components.media_player.errors import BrowseError + + +class UnknownMediaType(BrowseError): + """Unknown media type.""" diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py new file mode 100644 index 00000000000000..6b6c927ca1c2fc --- /dev/null +++ b/homeassistant/components/sonos/media_browser.py @@ -0,0 +1,218 @@ +"""Support for media browsing.""" +import logging +import urllib.parse + +from homeassistant.components.media_player import BrowseMedia +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_ALBUM, +) +from homeassistant.components.media_player.errors import BrowseError + +from .const import ( + EXPANDABLE_MEDIA_TYPES, + LIBRARY_TITLES_MAPPING, + MEDIA_TYPES_TO_SONOS, + PLAYABLE_MEDIA_TYPES, + SONOS_ALBUM, + SONOS_ALBUM_ARTIST, + SONOS_GENRE, + SONOS_TO_MEDIA_CLASSES, + SONOS_TO_MEDIA_TYPES, + SONOS_TRACKS, + SONOS_TYPES_MAPPING, +) +from .exception import UnknownMediaType + +_LOGGER = logging.getLogger(__name__) + + +def build_item_response(media_library, payload, get_thumbnail_url=None): + """Create response payload for the provided media query.""" + if payload["search_type"] == MEDIA_TYPE_ALBUM and payload["idstring"].startswith( + ("A:GENRE", "A:COMPOSER") + ): + payload["idstring"] = "A:ALBUMARTIST/" + "/".join( + payload["idstring"].split("/")[2:] + ) + + media = media_library.browse_by_idstring( + MEDIA_TYPES_TO_SONOS[payload["search_type"]], + payload["idstring"], + full_album_art_uri=True, + max_items=0, + ) + + if media is None: + return + + thumbnail = None + title = None + + # Fetch album info for titles and thumbnails + # Can't be extracted from track info + if ( + payload["search_type"] == MEDIA_TYPE_ALBUM + and media[0].item_class == "object.item.audioItem.musicTrack" + ): + item = get_media(media_library, payload["idstring"], SONOS_ALBUM_ARTIST) + title = getattr(item, "title", None) + thumbnail = get_thumbnail_url(SONOS_ALBUM_ARTIST, payload["idstring"]) + + if not title: + try: + title = urllib.parse.unquote(payload["idstring"].split("/")[1]) + except IndexError: + title = LIBRARY_TITLES_MAPPING[payload["idstring"]] + + try: + media_class = SONOS_TO_MEDIA_CLASSES[ + MEDIA_TYPES_TO_SONOS[payload["search_type"]] + ] + except KeyError: + _LOGGER.debug("Unknown media type received %s", payload["search_type"]) + return None + + children = [] + for item in media: + try: + children.append(item_payload(item, get_thumbnail_url)) + except UnknownMediaType: + pass + + return BrowseMedia( + title=title, + thumbnail=thumbnail, + media_class=media_class, + media_content_id=payload["idstring"], + media_content_type=payload["search_type"], + children=children, + can_play=can_play(payload["search_type"]), + can_expand=can_expand(payload["search_type"]), + ) + + +def item_payload(item, get_thumbnail_url=None): + """ + Create response payload for a single media item. + + Used by async_browse_media. + """ + media_type = get_media_type(item) + try: + media_class = SONOS_TO_MEDIA_CLASSES[media_type] + except KeyError as err: + _LOGGER.debug("Unknown media type received %s", media_type) + raise UnknownMediaType from err + + content_id = get_content_id(item) + thumbnail = None + if getattr(item, "album_art_uri", None): + thumbnail = get_thumbnail_url(media_class, content_id) + + return BrowseMedia( + title=item.title, + thumbnail=thumbnail, + media_class=media_class, + media_content_id=content_id, + media_content_type=SONOS_TO_MEDIA_TYPES[media_type], + can_play=can_play(item.item_class), + can_expand=can_expand(item), + ) + + +def library_payload(media_library, get_thumbnail_url=None): + """ + Create response payload to describe contents of a specific library. + + Used by async_browse_media. + """ + if not media_library.browse_by_idstring( + "tracks", + "", + max_items=1, + ): + raise BrowseError("Local library not found") + + children = [] + for item in media_library.browse(): + try: + children.append(item_payload(item, get_thumbnail_url)) + except UnknownMediaType: + pass + + return BrowseMedia( + title="Music Library", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="library", + media_content_type="library", + can_play=False, + can_expand=True, + children=children, + ) + + +def get_media_type(item): + """Extract media type of item.""" + if item.item_class == "object.item.audioItem.musicTrack": + return SONOS_TRACKS + + if ( + item.item_class == "object.container.album.musicAlbum" + and SONOS_TYPES_MAPPING.get(item.item_id.split("/")[0]) + in [ + SONOS_ALBUM_ARTIST, + SONOS_GENRE, + ] + ): + return SONOS_TYPES_MAPPING[item.item_class] + + return SONOS_TYPES_MAPPING.get(item.item_id.split("/")[0], item.item_class) + + +def can_play(item): + """ + Test if playable. + + Used by async_browse_media. + """ + return SONOS_TO_MEDIA_TYPES.get(item) in PLAYABLE_MEDIA_TYPES + + +def can_expand(item): + """ + Test if expandable. + + Used by async_browse_media. + """ + if isinstance(item, str): + return SONOS_TYPES_MAPPING.get(item) in EXPANDABLE_MEDIA_TYPES + + if SONOS_TO_MEDIA_TYPES.get(item.item_class) in EXPANDABLE_MEDIA_TYPES: + return True + + return SONOS_TYPES_MAPPING.get(item.item_id) in EXPANDABLE_MEDIA_TYPES + + +def get_content_id(item): + """Extract content id or uri.""" + if item.item_class == "object.item.audioItem.musicTrack": + return item.get_uri() + return item.item_id + + +def get_media(media_library, item_id, search_type): + """Fetch media/album.""" + search_type = MEDIA_TYPES_TO_SONOS.get(search_type, search_type) + + if not item_id.startswith("A:ALBUM") and search_type == SONOS_ALBUM: + item_id = "A:ALBUMARTIST/" + "/".join(item_id.split("/")[2:]) + + for item in media_library.browse_by_idstring( + search_type, + "/".join(item_id.split("/")[:-1]), + full_album_art_uri=True, + max_items=0, + ): + if item.item_id == item_id: + return item diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 363e499292ea5e..e6ee45e7a57b29 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -21,22 +21,11 @@ import pysonos.snapshot import voluptuous as vol -from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, - MEDIA_CLASS_ALBUM, - MEDIA_CLASS_ARTIST, - MEDIA_CLASS_COMPOSER, - MEDIA_CLASS_CONTRIBUTING_ARTIST, - MEDIA_CLASS_DIRECTORY, - MEDIA_CLASS_GENRE, - MEDIA_CLASS_PLAYLIST, - MEDIA_CLASS_TRACK, MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST, - MEDIA_TYPE_COMPOSER, - MEDIA_TYPE_CONTRIBUTING_ARTIST, - MEDIA_TYPE_GENRE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TRACK, @@ -71,20 +60,17 @@ from homeassistant.core import ServiceCall, callback from homeassistant.helpers import config_validation as cv, entity_platform, service import homeassistant.helpers.device_registry as dr +from homeassistant.helpers.network import is_internal_request from homeassistant.util.dt import utcnow from . import CONF_ADVERTISE_ADDR, CONF_HOSTS, CONF_INTERFACE_ADDR from .const import ( DATA_SONOS, DOMAIN as SONOS_DOMAIN, - SONOS_ALBUM, - SONOS_ALBUM_ARTIST, - SONOS_ARTIST, - SONOS_COMPOSER, - SONOS_GENRE, - SONOS_PLAYLISTS, - SONOS_TRACKS, + MEDIA_TYPES_TO_SONOS, + PLAYABLE_MEDIA_TYPES, ) +from .media_browser import build_item_response, get_media, library_payload _LOGGER = logging.getLogger(__name__) @@ -111,101 +97,6 @@ SOURCE_LINEIN = "Line-in" SOURCE_TV = "TV" -EXPANDABLE_MEDIA_TYPES = [ - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_COMPOSER, - MEDIA_TYPE_GENRE, - MEDIA_TYPE_PLAYLIST, - SONOS_ALBUM, - SONOS_ALBUM_ARTIST, - SONOS_ARTIST, - SONOS_GENRE, - SONOS_COMPOSER, - SONOS_PLAYLISTS, -] - -SONOS_TO_MEDIA_CLASSES = { - SONOS_ALBUM: MEDIA_CLASS_ALBUM, - SONOS_ALBUM_ARTIST: MEDIA_CLASS_ARTIST, - SONOS_ARTIST: MEDIA_CLASS_CONTRIBUTING_ARTIST, - SONOS_COMPOSER: MEDIA_CLASS_COMPOSER, - SONOS_GENRE: MEDIA_CLASS_GENRE, - SONOS_PLAYLISTS: MEDIA_CLASS_PLAYLIST, - SONOS_TRACKS: MEDIA_CLASS_TRACK, - "object.container.album.musicAlbum": MEDIA_CLASS_ALBUM, - "object.container.genre.musicGenre": MEDIA_CLASS_PLAYLIST, - "object.container.person.composer": MEDIA_CLASS_PLAYLIST, - "object.container.person.musicArtist": MEDIA_CLASS_ARTIST, - "object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST, - "object.container.playlistContainer": MEDIA_CLASS_PLAYLIST, - "object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK, -} - -SONOS_TO_MEDIA_TYPES = { - SONOS_ALBUM: MEDIA_TYPE_ALBUM, - SONOS_ALBUM_ARTIST: MEDIA_TYPE_ARTIST, - SONOS_ARTIST: MEDIA_TYPE_CONTRIBUTING_ARTIST, - SONOS_COMPOSER: MEDIA_TYPE_COMPOSER, - SONOS_GENRE: MEDIA_TYPE_GENRE, - SONOS_PLAYLISTS: MEDIA_TYPE_PLAYLIST, - SONOS_TRACKS: MEDIA_TYPE_TRACK, - "object.container.album.musicAlbum": MEDIA_TYPE_ALBUM, - "object.container.genre.musicGenre": MEDIA_TYPE_PLAYLIST, - "object.container.person.composer": MEDIA_TYPE_PLAYLIST, - "object.container.person.musicArtist": MEDIA_TYPE_ARTIST, - "object.container.playlistContainer.sameArtist": MEDIA_TYPE_ARTIST, - "object.container.playlistContainer": MEDIA_TYPE_PLAYLIST, - "object.item.audioItem.musicTrack": MEDIA_TYPE_TRACK, -} - -MEDIA_TYPES_TO_SONOS = { - MEDIA_TYPE_ALBUM: SONOS_ALBUM, - MEDIA_TYPE_ARTIST: SONOS_ALBUM_ARTIST, - MEDIA_TYPE_CONTRIBUTING_ARTIST: SONOS_ARTIST, - MEDIA_TYPE_COMPOSER: SONOS_COMPOSER, - MEDIA_TYPE_GENRE: SONOS_GENRE, - MEDIA_TYPE_PLAYLIST: SONOS_PLAYLISTS, - MEDIA_TYPE_TRACK: SONOS_TRACKS, -} - -SONOS_TYPES_MAPPING = { - "A:ALBUM": SONOS_ALBUM, - "A:ALBUMARTIST": SONOS_ALBUM_ARTIST, - "A:ARTIST": SONOS_ARTIST, - "A:COMPOSER": SONOS_COMPOSER, - "A:GENRE": SONOS_GENRE, - "A:PLAYLISTS": SONOS_PLAYLISTS, - "A:TRACKS": SONOS_TRACKS, - "object.container.album.musicAlbum": SONOS_ALBUM, - "object.container.genre.musicGenre": SONOS_GENRE, - "object.container.person.composer": SONOS_COMPOSER, - "object.container.person.musicArtist": SONOS_ALBUM_ARTIST, - "object.container.playlistContainer.sameArtist": SONOS_ARTIST, - "object.container.playlistContainer": SONOS_PLAYLISTS, - "object.item.audioItem.musicTrack": SONOS_TRACKS, -} - -LIBRARY_TITLES_MAPPING = { - "A:ALBUM": "Albums", - "A:ALBUMARTIST": "Artists", - "A:ARTIST": "Contributing Artists", - "A:COMPOSER": "Composers", - "A:GENRE": "Genres", - "A:PLAYLISTS": "Playlists", - "A:TRACKS": "Tracks", -} - -PLAYABLE_MEDIA_TYPES = [ - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_COMPOSER, - MEDIA_TYPE_CONTRIBUTING_ARTIST, - MEDIA_TYPE_GENRE, - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_TRACK, -] - REPEAT_TO_SONOS = { REPEAT_MODE_OFF: False, REPEAT_MODE_ALL: True, @@ -244,10 +135,6 @@ UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None} -class UnknownMediaType(BrowseError): - """Unknown media type.""" - - class SonosData: """Storage class for platform global data.""" @@ -1491,11 +1378,51 @@ def device_state_attributes(self): return attributes + async def async_get_browse_image( + self, media_content_type, media_content_id, media_image_id=None + ): + """Fetch media browser image to serve via proxy.""" + if ( + media_content_type in [MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST] + and media_content_id + ): + item = await self.hass.async_add_executor_job( + get_media, + self._media_library, + media_content_id, + MEDIA_TYPES_TO_SONOS[media_content_type], + ) + image_url = getattr(item, "album_art_uri", None) + if image_url: + result = await self._async_fetch_image(image_url) + return result + + return (None, None) + async def async_browse_media(self, media_content_type=None, media_content_id=None): """Implement the websocket media browsing helper.""" + is_internal = is_internal_request(self.hass) + + def _get_thumbnail_url( + media_content_type, media_content_id, media_image_id=None + ): + if is_internal: + item = get_media( + self._media_library, + media_content_id, + media_content_type, + ) + return getattr(item, "album_art_uri", None) + + return self.get_browse_image_url( + media_content_type, + urllib.parse.quote_plus(media_content_id), + media_image_id, + ) + if media_content_type in [None, "library"]: return await self.hass.async_add_executor_job( - library_payload, self._media_library + library_payload, self._media_library, _get_thumbnail_url ) payload = { @@ -1503,195 +1430,10 @@ async def async_browse_media(self, media_content_type=None, media_content_id=Non "idstring": media_content_id, } response = await self.hass.async_add_executor_job( - build_item_response, self._media_library, payload + build_item_response, self._media_library, payload, _get_thumbnail_url ) if response is None: raise BrowseError( f"Media not found: {media_content_type} / {media_content_id}" ) return response - - -def build_item_response(media_library, payload): - """Create response payload for the provided media query.""" - if payload["search_type"] == MEDIA_TYPE_ALBUM and payload["idstring"].startswith( - ("A:GENRE", "A:COMPOSER") - ): - payload["idstring"] = "A:ALBUMARTIST/" + "/".join( - payload["idstring"].split("/")[2:] - ) - - media = media_library.browse_by_idstring( - MEDIA_TYPES_TO_SONOS[payload["search_type"]], - payload["idstring"], - full_album_art_uri=True, - max_items=0, - ) - - if media is None: - return - - thumbnail = None - title = None - - # Fetch album info for titles and thumbnails - # Can't be extracted from track info - if ( - payload["search_type"] == MEDIA_TYPE_ALBUM - and media[0].item_class == "object.item.audioItem.musicTrack" - ): - item = get_media(media_library, payload["idstring"], SONOS_ALBUM_ARTIST) - title = getattr(item, "title", None) - thumbnail = getattr(item, "album_art_uri", media[0].album_art_uri) - - if not title: - try: - title = urllib.parse.unquote(payload["idstring"].split("/")[1]) - except IndexError: - title = LIBRARY_TITLES_MAPPING[payload["idstring"]] - - try: - media_class = SONOS_TO_MEDIA_CLASSES[ - MEDIA_TYPES_TO_SONOS[payload["search_type"]] - ] - except KeyError: - _LOGGER.debug("Unknown media type received %s", payload["search_type"]) - return None - - children = [] - for item in media: - try: - children.append(item_payload(item)) - except UnknownMediaType: - pass - - return BrowseMedia( - title=title, - thumbnail=thumbnail, - media_class=media_class, - media_content_id=payload["idstring"], - media_content_type=payload["search_type"], - children=children, - can_play=can_play(payload["search_type"]), - can_expand=can_expand(payload["search_type"]), - ) - - -def item_payload(item): - """ - Create response payload for a single media item. - - Used by async_browse_media. - """ - media_type = get_media_type(item) - try: - media_class = SONOS_TO_MEDIA_CLASSES[media_type] - except KeyError as err: - _LOGGER.debug("Unknown media type received %s", media_type) - raise UnknownMediaType from err - return BrowseMedia( - title=item.title, - thumbnail=getattr(item, "album_art_uri", None), - media_class=media_class, - media_content_id=get_content_id(item), - media_content_type=SONOS_TO_MEDIA_TYPES[media_type], - can_play=can_play(item.item_class), - can_expand=can_expand(item), - ) - - -def library_payload(media_library): - """ - Create response payload to describe contents of a specific library. - - Used by async_browse_media. - """ - if not media_library.browse_by_idstring( - "tracks", - "", - max_items=1, - ): - raise BrowseError("Local library not found") - - children = [] - for item in media_library.browse(): - try: - children.append(item_payload(item)) - except UnknownMediaType: - pass - - return BrowseMedia( - title="Music Library", - media_class=MEDIA_CLASS_DIRECTORY, - media_content_id="library", - media_content_type="library", - can_play=False, - can_expand=True, - children=children, - ) - - -def get_media_type(item): - """Extract media type of item.""" - if item.item_class == "object.item.audioItem.musicTrack": - return SONOS_TRACKS - - if ( - item.item_class == "object.container.album.musicAlbum" - and SONOS_TYPES_MAPPING.get(item.item_id.split("/")[0]) - in [ - SONOS_ALBUM_ARTIST, - SONOS_GENRE, - ] - ): - return SONOS_TYPES_MAPPING[item.item_class] - - return SONOS_TYPES_MAPPING.get(item.item_id.split("/")[0], item.item_class) - - -def can_play(item): - """ - Test if playable. - - Used by async_browse_media. - """ - return SONOS_TO_MEDIA_TYPES.get(item) in PLAYABLE_MEDIA_TYPES - - -def can_expand(item): - """ - Test if expandable. - - Used by async_browse_media. - """ - if isinstance(item, str): - return SONOS_TYPES_MAPPING.get(item) in EXPANDABLE_MEDIA_TYPES - - if SONOS_TO_MEDIA_TYPES.get(item.item_class) in EXPANDABLE_MEDIA_TYPES: - return True - - return SONOS_TYPES_MAPPING.get(item.item_id) in EXPANDABLE_MEDIA_TYPES - - -def get_content_id(item): - """Extract content id or uri.""" - if item.item_class == "object.item.audioItem.musicTrack": - return item.get_uri() - return item.item_id - - -def get_media(media_library, item_id, search_type): - """Fetch media/album.""" - search_type = MEDIA_TYPES_TO_SONOS.get(search_type, search_type) - - if not item_id.startswith("A:ALBUM") and search_type == SONOS_ALBUM: - item_id = "A:ALBUMARTIST/" + "/".join(item_id.split("/")[2:]) - - for item in media_library.browse_by_idstring( - search_type, - "/".join(item_id.split("/")[:-1]), - full_album_art_uri=True, - max_items=0, - ): - if item.item_id == item_id: - return item From 2f1dba74d1254e2e575359f33e938fe070c801cc Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 24 Feb 2021 13:57:02 -0600 Subject: [PATCH 0756/1818] Use Plex server URL as config entry title (#47010) --- homeassistant/components/plex/config_flow.py | 2 +- tests/components/plex/test_config_flow.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index e52e4597bf925f..d611c09c43e00f 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -240,7 +240,7 @@ async def async_step_server_validate(self, server_config): _LOGGER.debug("Valid config created for %s", plex_server.friendly_name) - return self.async_create_entry(title=plex_server.friendly_name, data=data) + return self.async_create_entry(title=url, data=data) async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index bc0e59e658f4f5..bdd78131800dfe 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -203,7 +203,7 @@ async def test_single_available_server(hass, mock_plex_calls): server_id = result["data"][CONF_SERVER_IDENTIFIER] mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] - assert result["title"] == mock_plex_server.friendly_name + assert result["title"] == mock_plex_server.url_in_use assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert ( result["data"][CONF_SERVER_IDENTIFIER] @@ -259,7 +259,7 @@ async def test_multiple_servers_with_selection( server_id = result["data"][CONF_SERVER_IDENTIFIER] mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] - assert result["title"] == mock_plex_server.friendly_name + assert result["title"] == mock_plex_server.url_in_use assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert ( result["data"][CONF_SERVER_IDENTIFIER] @@ -317,7 +317,7 @@ async def test_adding_last_unconfigured_server( server_id = result["data"][CONF_SERVER_IDENTIFIER] mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] - assert result["title"] == mock_plex_server.friendly_name + assert result["title"] == mock_plex_server.url_in_use assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert ( result["data"][CONF_SERVER_IDENTIFIER] @@ -656,7 +656,7 @@ def __init__(self): server_id = result["data"][CONF_SERVER_IDENTIFIER] mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] - assert result["title"] == mock_plex_server.friendly_name + assert result["title"] == mock_plex_server.url_in_use assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use @@ -692,7 +692,7 @@ async def test_manual_config_with_token(hass, mock_plex_calls): server_id = result["data"][CONF_SERVER_IDENTIFIER] mock_plex_server = hass.data[DOMAIN][SERVERS][server_id] - assert result["title"] == mock_plex_server.friendly_name + assert result["title"] == mock_plex_server.url_in_use assert result["data"][CONF_SERVER] == mock_plex_server.friendly_name assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machine_identifier assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server.url_in_use From ba51ada4944c247f853628fb70039c466b40a6e2 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 24 Feb 2021 14:00:58 -0600 Subject: [PATCH 0757/1818] Bump plexapi to 4.4.0 (#47007) --- 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 913f405cfcdc48..49388bdfdb656d 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.3.1", + "plexapi==4.4.0", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index 102b474f3bf243..bcef556ae380c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1141,7 +1141,7 @@ pillow==8.1.0 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.3.1 +plexapi==4.4.0 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7070b69162ed15..155aaa269e5e8a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -587,7 +587,7 @@ pilight==0.1.1 pillow==8.1.0 # homeassistant.components.plex -plexapi==4.3.1 +plexapi==4.4.0 # homeassistant.components.plex plexauth==0.0.6 From 8d2606134d3918d9210ccaf0a24cbbf3ef65c5a7 Mon Sep 17 00:00:00 2001 From: Nathan Tilley Date: Wed, 24 Feb 2021 15:11:20 -0500 Subject: [PATCH 0758/1818] Add FAA Delays Integration (#41347) Co-authored-by: Franck Nijhof Co-authored-by: Martin Hjelmare --- .coveragerc | 2 + CODEOWNERS | 1 + .../components/faa_delays/__init__.py | 84 ++++++++++++ .../components/faa_delays/binary_sensor.py | 93 ++++++++++++++ .../components/faa_delays/config_flow.py | 62 +++++++++ homeassistant/components/faa_delays/const.py | 28 ++++ .../components/faa_delays/manifest.json | 8 ++ .../components/faa_delays/strings.json | 21 +++ .../faa_delays/translations/en.json | 19 +++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/faa_delays/__init__.py | 1 + .../components/faa_delays/test_config_flow.py | 120 ++++++++++++++++++ 14 files changed, 446 insertions(+) create mode 100644 homeassistant/components/faa_delays/__init__.py create mode 100644 homeassistant/components/faa_delays/binary_sensor.py create mode 100644 homeassistant/components/faa_delays/config_flow.py create mode 100644 homeassistant/components/faa_delays/const.py create mode 100644 homeassistant/components/faa_delays/manifest.json create mode 100644 homeassistant/components/faa_delays/strings.json create mode 100644 homeassistant/components/faa_delays/translations/en.json create mode 100644 tests/components/faa_delays/__init__.py create mode 100644 tests/components/faa_delays/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index dfa742b490c02f..50fcf151821634 100644 --- a/.coveragerc +++ b/.coveragerc @@ -272,6 +272,8 @@ omit = homeassistant/components/evohome/* homeassistant/components/ezviz/* homeassistant/components/familyhub/camera.py + homeassistant/components/faa_delays/__init__.py + homeassistant/components/faa_delays/binary_sensor.py homeassistant/components/fastdotcom/* homeassistant/components/ffmpeg/camera.py homeassistant/components/fibaro/* diff --git a/CODEOWNERS b/CODEOWNERS index 398e5b15f7ff8a..b0a31203009799 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -146,6 +146,7 @@ homeassistant/components/esphome/* @OttoWinter homeassistant/components/essent/* @TheLastProject homeassistant/components/evohome/* @zxdavb homeassistant/components/ezviz/* @baqs +homeassistant/components/faa_delays/* @ntilley905 homeassistant/components/fastdotcom/* @rohankapoorcom homeassistant/components/file/* @fabaff homeassistant/components/filter/* @dgomes diff --git a/homeassistant/components/faa_delays/__init__.py b/homeassistant/components/faa_delays/__init__.py new file mode 100644 index 00000000000000..b9def765123122 --- /dev/null +++ b/homeassistant/components/faa_delays/__init__.py @@ -0,0 +1,84 @@ +"""The FAA Delays integration.""" +import asyncio +from datetime import timedelta +import logging + +from aiohttp import ClientConnectionError +from async_timeout import timeout +from faadelays import Airport + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["binary_sensor"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the FAA Delays component.""" + hass.data[DOMAIN] = {} + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up FAA Delays from a config entry.""" + code = entry.data[CONF_ID] + + coordinator = FAADataUpdateCoordinator(hass, code) + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][entry.entry_id] = coordinator + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +class FAADataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching FAA API data from a single endpoint.""" + + def __init__(self, hass, code): + """Initialize the coordinator.""" + super().__init__( + hass, _LOGGER, name=DOMAIN, update_interval=timedelta(minutes=1) + ) + self.session = aiohttp_client.async_get_clientsession(hass) + self.data = Airport(code, self.session) + self.code = code + + async def _async_update_data(self): + try: + with timeout(10): + await self.data.update() + except ClientConnectionError as err: + raise UpdateFailed(err) from err + return self.data diff --git a/homeassistant/components/faa_delays/binary_sensor.py b/homeassistant/components/faa_delays/binary_sensor.py new file mode 100644 index 00000000000000..6c5876b7017f49 --- /dev/null +++ b/homeassistant/components/faa_delays/binary_sensor.py @@ -0,0 +1,93 @@ +"""Platform for FAA Delays sensor component.""" +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.const import ATTR_ICON, ATTR_NAME +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, FAA_BINARY_SENSORS + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up a FAA sensor based on a config entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + + binary_sensors = [] + for kind, attrs in FAA_BINARY_SENSORS.items(): + name = attrs[ATTR_NAME] + icon = attrs[ATTR_ICON] + + binary_sensors.append( + FAABinarySensor(coordinator, kind, name, icon, entry.entry_id) + ) + + async_add_entities(binary_sensors) + + +class FAABinarySensor(CoordinatorEntity, BinarySensorEntity): + """Define a binary sensor for FAA Delays.""" + + def __init__(self, coordinator, sensor_type, name, icon, entry_id): + """Initialize the sensor.""" + super().__init__(coordinator) + + self.coordinator = coordinator + self._entry_id = entry_id + self._icon = icon + self._name = name + self._sensor_type = sensor_type + self._id = self.coordinator.data.iata + self._attrs = {} + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._id} {self._name}" + + @property + def icon(self): + """Return the icon.""" + return self._icon + + @property + def is_on(self): + """Return the status of the sensor.""" + if self._sensor_type == "GROUND_DELAY": + return self.coordinator.data.ground_delay.status + if self._sensor_type == "GROUND_STOP": + return self.coordinator.data.ground_stop.status + if self._sensor_type == "DEPART_DELAY": + return self.coordinator.data.depart_delay.status + if self._sensor_type == "ARRIVE_DELAY": + return self.coordinator.data.arrive_delay.status + if self._sensor_type == "CLOSURE": + return self.coordinator.data.closure.status + return None + + @property + def unique_id(self): + """Return a unique, Home Assistant friendly identifier for this entity.""" + return f"{self._id}_{self._sensor_type}" + + @property + def device_state_attributes(self): + """Return attributes for sensor.""" + if self._sensor_type == "GROUND_DELAY": + self._attrs["average"] = self.coordinator.data.ground_delay.average + self._attrs["reason"] = self.coordinator.data.ground_delay.reason + elif self._sensor_type == "GROUND_STOP": + self._attrs["endtime"] = self.coordinator.data.ground_stop.endtime + self._attrs["reason"] = self.coordinator.data.ground_stop.reason + elif self._sensor_type == "DEPART_DELAY": + self._attrs["minimum"] = self.coordinator.data.depart_delay.minimum + self._attrs["maximum"] = self.coordinator.data.depart_delay.maximum + self._attrs["trend"] = self.coordinator.data.depart_delay.trend + self._attrs["reason"] = self.coordinator.data.depart_delay.reason + elif self._sensor_type == "ARRIVE_DELAY": + self._attrs["minimum"] = self.coordinator.data.arrive_delay.minimum + self._attrs["maximum"] = self.coordinator.data.arrive_delay.maximum + self._attrs["trend"] = self.coordinator.data.arrive_delay.trend + self._attrs["reason"] = self.coordinator.data.arrive_delay.reason + elif self._sensor_type == "CLOSURE": + self._attrs["begin"] = self.coordinator.data.closure.begin + self._attrs["end"] = self.coordinator.data.closure.end + self._attrs["reason"] = self.coordinator.data.closure.reason + return self._attrs diff --git a/homeassistant/components/faa_delays/config_flow.py b/homeassistant/components/faa_delays/config_flow.py new file mode 100644 index 00000000000000..46d917cc92f667 --- /dev/null +++ b/homeassistant/components/faa_delays/config_flow.py @@ -0,0 +1,62 @@ +"""Config flow for FAA Delays integration.""" +import logging + +from aiohttp import ClientConnectionError +import faadelays +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_ID +from homeassistant.helpers import aiohttp_client + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({vol.Required(CONF_ID): str}) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for FAA Delays.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + + await self.async_set_unique_id(user_input[CONF_ID]) + self._abort_if_unique_id_configured() + + websession = aiohttp_client.async_get_clientsession(self.hass) + + data = faadelays.Airport(user_input[CONF_ID], websession) + + try: + await data.update() + + except faadelays.InvalidAirport: + _LOGGER.error("Airport code %s is invalid", user_input[CONF_ID]) + errors[CONF_ID] = "invalid_airport" + + except ClientConnectionError: + _LOGGER.error("Error connecting to FAA API") + errors["base"] = "cannot_connect" + + except Exception as error: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception: %s", error) + errors["base"] = "unknown" + + if not errors: + _LOGGER.debug( + "Creating entry with id: %s, name: %s", + user_input[CONF_ID], + data.name, + ) + return self.async_create_entry(title=data.name, data=user_input) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/faa_delays/const.py b/homeassistant/components/faa_delays/const.py new file mode 100644 index 00000000000000..c725be88106442 --- /dev/null +++ b/homeassistant/components/faa_delays/const.py @@ -0,0 +1,28 @@ +"""Constants for the FAA Delays integration.""" + +from homeassistant.const import ATTR_ICON, ATTR_NAME + +DOMAIN = "faa_delays" + +FAA_BINARY_SENSORS = { + "GROUND_DELAY": { + ATTR_NAME: "Ground Delay", + ATTR_ICON: "mdi:airport", + }, + "GROUND_STOP": { + ATTR_NAME: "Ground Stop", + ATTR_ICON: "mdi:airport", + }, + "DEPART_DELAY": { + ATTR_NAME: "Departure Delay", + ATTR_ICON: "mdi:airplane-takeoff", + }, + "ARRIVE_DELAY": { + ATTR_NAME: "Arrival Delay", + ATTR_ICON: "mdi:airplane-landing", + }, + "CLOSURE": { + ATTR_NAME: "Closure", + ATTR_ICON: "mdi:airplane:off", + }, +} diff --git a/homeassistant/components/faa_delays/manifest.json b/homeassistant/components/faa_delays/manifest.json new file mode 100644 index 00000000000000..4148e7b956f4ab --- /dev/null +++ b/homeassistant/components/faa_delays/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "faa_delays", + "name": "FAA Delays", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/faadelays", + "requirements": ["faadelays==0.0.6"], + "codeowners": ["@ntilley905"] +} diff --git a/homeassistant/components/faa_delays/strings.json b/homeassistant/components/faa_delays/strings.json new file mode 100644 index 00000000000000..92a9dafb4da987 --- /dev/null +++ b/homeassistant/components/faa_delays/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "user": { + "title": "FAA Delays", + "description": "Enter a US Airport Code in IATA Format", + "data": { + "id": "Airport" + } + } + }, + "error": { + "invalid_airport": "Airport code is not valid", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "This airport is already configured." + } + } +} diff --git a/homeassistant/components/faa_delays/translations/en.json b/homeassistant/components/faa_delays/translations/en.json new file mode 100644 index 00000000000000..48e9e1c8993cf2 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/en.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "This airport is already configured." + }, + "error": { + "invalid_airport": "Airport code is not valid" + }, + "step": { + "user": { + "title": "FAA Delays", + "description": "Enter a US Airport Code in IATA Format", + "data": { + "id": "Airport" + } + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index da16d32d45b75a..c3d629ebe29264 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -64,6 +64,7 @@ "enocean", "epson", "esphome", + "faa_delays", "fireservicerota", "flick_electric", "flo", diff --git a/requirements_all.txt b/requirements_all.txt index bcef556ae380c8..21bcd4d604aec3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -574,6 +574,9 @@ eternalegypt==0.0.12 # homeassistant.components.evohome evohome-async==0.3.5.post1 +# homeassistant.components.faa_delays +faadelays==0.0.6 + # homeassistant.components.dlib_face_detect # homeassistant.components.dlib_face_identify # face_recognition==1.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 155aaa269e5e8a..ae06051a774343 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -302,6 +302,9 @@ ephem==3.7.7.0 # homeassistant.components.epson epson-projector==0.2.3 +# homeassistant.components.faa_delays +faadelays==0.0.6 + # homeassistant.components.feedreader feedparser==6.0.2 diff --git a/tests/components/faa_delays/__init__.py b/tests/components/faa_delays/__init__.py new file mode 100644 index 00000000000000..2bb5194605dd15 --- /dev/null +++ b/tests/components/faa_delays/__init__.py @@ -0,0 +1 @@ +"""Tests for the FAA Delays integration.""" diff --git a/tests/components/faa_delays/test_config_flow.py b/tests/components/faa_delays/test_config_flow.py new file mode 100644 index 00000000000000..c289f1544153fb --- /dev/null +++ b/tests/components/faa_delays/test_config_flow.py @@ -0,0 +1,120 @@ +"""Test the FAA Delays config flow.""" +from unittest.mock import patch + +from aiohttp import ClientConnectionError +import faadelays + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.faa_delays.const import DOMAIN +from homeassistant.const import CONF_ID +from homeassistant.exceptions import HomeAssistantError + +from tests.common import MockConfigEntry + + +async def mock_valid_airport(self, *args, **kwargs): + """Return a valid airport.""" + self.name = "Test airport" + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch.object(faadelays.Airport, "update", new=mock_valid_airport), patch( + "homeassistant.components.faa_delays.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.faa_delays.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "id": "test", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "Test airport" + assert result2["data"] == { + "id": "test", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_duplicate_error(hass): + """Test that we handle a duplicate configuration.""" + conf = {CONF_ID: "test"} + + MockConfigEntry(domain=DOMAIN, unique_id="test", data=conf).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_form_invalid_airport(hass): + """Test we handle invalid airport.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "faadelays.Airport.update", + side_effect=faadelays.InvalidAirport, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "id": "test", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_ID: "invalid_airport"} + + +async def test_form_cannot_connect(hass): + """Test we handle a connection error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch("faadelays.Airport.update", side_effect=ClientConnectionError): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "id": "test", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unexpected_exception(hass): + """Test we handle an unexpected exception.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch("faadelays.Airport.update", side_effect=HomeAssistantError): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "id": "test", + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} From 4c294adfe88bc81384101e3db0da08820b1b12d0 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 24 Feb 2021 21:18:14 +0100 Subject: [PATCH 0759/1818] Add missing tilt icon to Shelly tilt sensor (#46993) --- homeassistant/components/shelly/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 36656740b92f8d..32fb33877d3e6d 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -149,7 +149,11 @@ unit=LIGHT_LUX, device_class=sensor.DEVICE_CLASS_ILLUMINANCE, ), - ("sensor", "tilt"): BlockAttributeDescription(name="Tilt", unit=DEGREE), + ("sensor", "tilt"): BlockAttributeDescription( + name="Tilt", + unit=DEGREE, + icon="mdi:angle-acute", + ), ("relay", "totalWorkTime"): BlockAttributeDescription( name="Lamp life", unit=PERCENTAGE, From afae2534328fcc9acfe24bee480c8b7c7b323fce Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 24 Feb 2021 21:18:24 +0100 Subject: [PATCH 0760/1818] Update frontend to 20210224.0 (#47013) --- 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 ea46e0b2e07101..623aaf42ca596f 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==20210222.0" + "home-assistant-frontend==20210224.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d867db5cef0e12..e4f84f0c8ef478 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210222.0 +home-assistant-frontend==20210224.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 21bcd4d604aec3..f406c042d527b3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -766,7 +766,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210222.0 +home-assistant-frontend==20210224.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ae06051a774343..5d9df811084176 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -415,7 +415,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210222.0 +home-assistant-frontend==20210224.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 23cbd2dda3076105167b42c509a47eb91c857f76 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 24 Feb 2021 21:59:06 +0100 Subject: [PATCH 0761/1818] Change Z-Wave JS discovery logic to adopt changes to DeviceClass (#46983) Co-authored-by: raman325 <7243222+raman325@users.noreply.github.com> --- homeassistant/components/zwave_js/api.py | 3 +- .../components/zwave_js/binary_sensor.py | 2 +- .../components/zwave_js/discovery.py | 37 +++++++---- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_api.py | 3 - tests/components/zwave_js/test_climate.py | 50 +++++++-------- tests/components/zwave_js/test_cover.py | 52 ++++++++-------- tests/components/zwave_js/test_discovery.py | 4 +- tests/components/zwave_js/test_fan.py | 22 +++---- tests/components/zwave_js/test_light.py | 62 ++++++++++--------- tests/components/zwave_js/test_lock.py | 24 +++---- tests/components/zwave_js/test_number.py | 6 +- tests/components/zwave_js/test_services.py | 12 ++-- tests/components/zwave_js/test_switch.py | 14 ++--- .../zwave_js/aeon_smart_switch_6_state.json | 9 +-- .../aeotec_radiator_thermostat_state.json | 14 ++--- .../zwave_js/bulb_6_multi_color_state.json | 13 ++-- .../zwave_js/chain_actuator_zws12_state.json | 15 ++--- .../zwave_js/climate_danfoss_lc_13_state.json | 14 ++--- .../zwave_js/climate_heatit_z_trm3_state.json | 15 ++--- ..._ct100_plus_different_endpoints_state.json | 15 ++--- ...ate_radio_thermostat_ct100_plus_state.json | 15 ++--- .../zwave_js/cover_iblinds_v2_state.json | 12 ++-- .../fixtures/zwave_js/cover_zw062_state.json | 25 ++------ .../zwave_js/eaton_rf9640_dimmer_state.json | 13 ++-- .../zwave_js/ecolink_door_sensor_state.json | 16 ++--- .../fixtures/zwave_js/fan_ge_12730_state.json | 13 ++-- .../zwave_js/hank_binary_switch_state.json | 13 ++-- .../in_wall_smart_fan_control_state.json | 12 ++-- .../zwave_js/lock_august_asl03_state.json | 16 ++--- .../zwave_js/lock_schlage_be469_state.json | 16 ++--- .../zwave_js/multisensor_6_state.json | 12 ++-- .../nortek_thermostat_added_event.json | 15 ++--- .../nortek_thermostat_removed_event.json | 15 ++--- .../zwave_js/nortek_thermostat_state.json | 15 ++--- 37 files changed, 261 insertions(+), 339 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 8358b93aae5611..a48eadfad1da86 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -429,10 +429,9 @@ async def websocket_update_log_config( """Update the driver log config.""" entry_id = msg[ENTRY_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] - result = await client.driver.async_update_log_config(LogConfig(**msg[CONFIG])) + await client.driver.async_update_log_config(LogConfig(**msg[CONFIG])) connection.send_result( msg[ID], - result, ) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 8c56869449adcc..8d266c83f2249a 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -287,7 +287,7 @@ def entity_registry_enabled_default(self) -> bool: if self.info.primary_value.command_class == CommandClass.SENSOR_BINARY: # Legacy binary sensors are phased out (replaced by notification sensors) # Disable by default to not confuse users - if self.info.node.device_class.generic != "Binary Sensor": + if self.info.node.device_class.generic.key != 0x20: return False return True diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 77709f84e58d5c..248a34547b5c5e 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -4,6 +4,7 @@ from typing import Generator, List, Optional, Set, Union from zwave_js_server.const import CommandClass +from zwave_js_server.model.device_class import DeviceClassItem from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import Value as ZwaveValue @@ -72,11 +73,11 @@ class ZWaveDiscoverySchema: # [optional] the node's firmware_version must match ANY of these values firmware_version: Optional[Set[str]] = None # [optional] the node's basic device class must match ANY of these values - device_class_basic: Optional[Set[str]] = None + device_class_basic: Optional[Set[Union[str, int]]] = None # [optional] the node's generic device class must match ANY of these values - device_class_generic: Optional[Set[str]] = None + device_class_generic: Optional[Set[Union[str, int]]] = None # [optional] the node's specific device class must match ANY of these values - device_class_specific: Optional[Set[str]] = None + device_class_specific: Optional[Set[Union[str, int]]] = None # [optional] additional values that ALL need to be present on the node for this scheme to pass required_values: Optional[List[ZWaveValueDiscoverySchema]] = None # [optional] bool to specify if this primary value may be discovered by multiple platforms @@ -416,21 +417,18 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None ): continue # check device_class_basic - if ( - schema.device_class_basic is not None - and value.node.device_class.basic not in schema.device_class_basic + if not check_device_class( + value.node.device_class.basic, schema.device_class_basic ): continue # check device_class_generic - if ( - schema.device_class_generic is not None - and value.node.device_class.generic not in schema.device_class_generic + if not check_device_class( + value.node.device_class.generic, schema.device_class_generic ): continue # check device_class_specific - if ( - schema.device_class_specific is not None - and value.node.device_class.specific not in schema.device_class_specific + if not check_device_class( + value.node.device_class.specific, schema.device_class_specific ): continue # check primary value @@ -474,3 +472,18 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool: if schema.type is not None and value.metadata.type not in schema.type: return False return True + + +@callback +def check_device_class( + device_class: DeviceClassItem, required_value: Optional[Set[Union[str, int]]] +) -> bool: + """Check if device class id or label matches.""" + if required_value is None: + return True + for val in required_value: + if isinstance(val, str) and device_class.label == val: + return True + if isinstance(val, int) and device_class.key == val: + return True + return False diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 56e60fc322cbfe..f5d9461e9e09c1 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.18.0"], + "requirements": ["zwave-js-server-python==0.19.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index f406c042d527b3..677ce0d0c2fd9b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2406,4 +2406,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.18.0 +zwave-js-server-python==0.19.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5d9df811084176..28478c4349aa2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1243,4 +1243,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.18.0 +zwave-js-server-python==0.19.0 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index d2a6215575f412..dcbd924c86e6ca 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -345,7 +345,6 @@ async def test_update_log_config(hass, client, integration, hass_ws_client): } ) msg = await ws_client.receive_json() - assert msg["result"] assert msg["success"] assert len(client.async_send_command.call_args_list) == 1 @@ -366,7 +365,6 @@ async def test_update_log_config(hass, client, integration, hass_ws_client): } ) msg = await ws_client.receive_json() - assert msg["result"] assert msg["success"] assert len(client.async_send_command.call_args_list) == 1 @@ -393,7 +391,6 @@ async def test_update_log_config(hass, client, integration, hass_ws_client): } ) msg = await ws_client.receive_json() - assert msg["result"] assert msg["success"] assert len(client.async_send_command.call_args_list) == 1 diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 1ccf6f82017234..44804825885f1b 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -69,8 +69,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -92,7 +92,7 @@ async def test_thermostat_v2( } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting hvac mode await hass.services.async_call( @@ -105,8 +105,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -128,7 +128,7 @@ async def test_thermostat_v2( } assert args["value"] == 2 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting temperature await hass.services.async_call( @@ -142,8 +142,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command.call_args_list) == 2 - args = client.async_send_command.call_args_list[0][0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 2 + args = client.async_send_command_no_wait.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -164,7 +164,7 @@ async def test_thermostat_v2( "value": 1, } assert args["value"] == 2 - args = client.async_send_command.call_args_list[1][0][0] + args = client.async_send_command_no_wait.call_args_list[1][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -186,7 +186,7 @@ async def test_thermostat_v2( } assert args["value"] == 77 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test cool mode update from value updated event event = Event( @@ -237,7 +237,7 @@ async def test_thermostat_v2( assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 22.8 assert state.attributes[ATTR_TARGET_TEMP_LOW] == 22.2 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting temperature with heat_cool await hass.services.async_call( @@ -251,8 +251,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command.call_args_list) == 2 - args = client.async_send_command.call_args_list[0][0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 2 + args = client.async_send_command_no_wait.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -274,7 +274,7 @@ async def test_thermostat_v2( } assert args["value"] == 77 - args = client.async_send_command.call_args_list[1][0][0] + args = client.async_send_command_no_wait.call_args_list[1][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -296,7 +296,7 @@ async def test_thermostat_v2( } assert args["value"] == 86 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() with pytest.raises(ValueError): # Test setting unknown preset mode @@ -310,7 +310,7 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 # Test setting invalid hvac mode with pytest.raises(ValueError): @@ -336,7 +336,7 @@ async def test_thermostat_v2( blocking=True, ) - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting fan mode await hass.services.async_call( @@ -349,8 +349,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -373,7 +373,7 @@ async def test_thermostat_v2( } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting invalid fan mode with pytest.raises(ValueError): @@ -408,7 +408,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting temperature await hass.services.async_call( @@ -421,8 +421,8 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args_list[0][0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 5 assert args["valueId"] == { @@ -444,7 +444,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat } assert args["value"] == 21.5 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setpoint mode update from value updated event event = Event( @@ -471,7 +471,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.state == HVAC_MODE_HEAT assert state.attributes[ATTR_TEMPERATURE] == 23 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integration): diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index 2378453e31a265..e6118f9b37d1ea 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -38,8 +38,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -60,7 +60,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 50 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting position await hass.services.async_call( @@ -70,8 +70,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -92,7 +92,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 0 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test opening await hass.services.async_call( @@ -102,8 +102,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -124,7 +124,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test stop after opening await hass.services.async_call( "cover", @@ -133,8 +133,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 2 - open_args = client.async_send_command.call_args_list[0][0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 2 + open_args = client.async_send_command_no_wait.call_args_list[0][0][0] assert open_args["command"] == "node.set_value" assert open_args["nodeId"] == 6 assert open_args["valueId"] == { @@ -153,7 +153,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not open_args["value"] - close_args = client.async_send_command.call_args_list[1][0][0] + close_args = client.async_send_command_no_wait.call_args_list[1][0][0] assert close_args["command"] == "node.set_value" assert close_args["nodeId"] == 6 assert close_args["valueId"] == { @@ -191,7 +191,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): }, ) node.receive_event(event) - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() state = hass.states.get(WINDOW_COVER_ENTITY) assert state.state == "open" @@ -203,8 +203,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): {"entity_id": WINDOW_COVER_ENTITY}, blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -225,7 +225,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 0 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test stop after closing await hass.services.async_call( @@ -235,8 +235,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 2 - open_args = client.async_send_command.call_args_list[0][0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 2 + open_args = client.async_send_command_no_wait.call_args_list[0][0][0] assert open_args["command"] == "node.set_value" assert open_args["nodeId"] == 6 assert open_args["valueId"] == { @@ -255,7 +255,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not open_args["value"] - close_args = client.async_send_command.call_args_list[1][0][0] + close_args = client.async_send_command_no_wait.call_args_list[1][0][0] assert close_args["command"] == "node.set_value" assert close_args["nodeId"] == 6 assert close_args["valueId"] == { @@ -274,7 +274,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not close_args["value"] - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() event = Event( type="value updated", @@ -314,8 +314,8 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): DOMAIN, SERVICE_OPEN_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 255 @@ -341,15 +341,15 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): state = hass.states.get(GDC_COVER_ENTITY) assert state.state == STATE_CLOSED - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test close await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 0 @@ -375,7 +375,7 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): state = hass.states.get(GDC_COVER_ENTITY) assert state.state == STATE_CLOSED - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Barrier sends an opening state event = Event( diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index 8f3dbce8dcaa83..e28c8ae15632fa 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -4,7 +4,7 @@ async def test_iblinds_v2(hass, client, iblinds_v2, integration): """Test that an iBlinds v2.0 multilevel switch value is discovered as a cover.""" node = iblinds_v2 - assert node.device_class.specific == "Unused" + assert node.device_class.specific.label == "Unused" state = hass.states.get("light.window_blind_controller") assert not state @@ -16,7 +16,7 @@ async def test_iblinds_v2(hass, client, iblinds_v2, integration): async def test_ge_12730(hass, client, ge_12730, integration): """Test GE 12730 Fan Controller v2.0 multilevel switch is discovered as a fan.""" node = ge_12730 - assert node.device_class.specific == "Multilevel Power Switch" + assert node.device_class.specific.label == "Multilevel Power Switch" state = hass.states.get("light.in_wall_smart_fan_control") assert not state diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index 5bd856c664a02c..0ee007aab35213 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -23,8 +23,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -45,7 +45,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 66 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting unknown speed with pytest.raises(ValueError): @@ -56,7 +56,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turn on no speed await hass.services.async_call( @@ -66,8 +66,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -88,7 +88,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 255 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning off await hass.services.async_call( @@ -98,8 +98,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -120,7 +120,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 0 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test speed update from value updated event event = Event( @@ -146,7 +146,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): assert state.state == "on" assert state.attributes[ATTR_SPEED] == "high" - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() event = Event( type="value updated", diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index f48b02223d0795..ca36ea35393ab8 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -36,8 +36,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { @@ -58,7 +58,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): } assert args["value"] == 255 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test brightness update from value updated event event = Event( @@ -93,9 +93,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 + assert len(client.async_send_command_no_wait.call_args_list) == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning on with brightness await hass.services.async_call( @@ -105,8 +105,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { @@ -127,7 +127,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): } assert args["value"] == 50 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning on with rgb color await hass.services.async_call( @@ -137,8 +137,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 5 - warm_args = client.async_send_command.call_args_list[0][0][0] # warm white 0 + assert len(client.async_send_command_no_wait.call_args_list) == 5 + warm_args = client.async_send_command_no_wait.call_args_list[0][0][ + 0 + ] # warm white 0 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" @@ -149,7 +151,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert warm_args["valueId"]["propertyName"] == "targetColor" assert warm_args["value"] == 0 - cold_args = client.async_send_command.call_args_list[1][0][0] # cold white 0 + cold_args = client.async_send_command_no_wait.call_args_list[1][0][ + 0 + ] # cold white 0 assert cold_args["command"] == "node.set_value" assert cold_args["nodeId"] == 39 assert cold_args["valueId"]["commandClassName"] == "Color Switch" @@ -159,7 +163,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert cold_args["valueId"]["property"] == "targetColor" assert cold_args["valueId"]["propertyName"] == "targetColor" assert cold_args["value"] == 0 - red_args = client.async_send_command.call_args_list[2][0][0] # red 255 + red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # red 255 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -169,7 +173,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 255 - green_args = client.async_send_command.call_args_list[3][0][0] # green 76 + green_args = client.async_send_command_no_wait.call_args_list[3][0][0] # green 76 assert green_args["command"] == "node.set_value" assert green_args["nodeId"] == 39 assert green_args["valueId"]["commandClassName"] == "Color Switch" @@ -179,7 +183,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert green_args["valueId"]["property"] == "targetColor" assert green_args["valueId"]["propertyName"] == "targetColor" assert green_args["value"] == 76 - blue_args = client.async_send_command.call_args_list[4][0][0] # blue 255 + blue_args = client.async_send_command_no_wait.call_args_list[4][0][0] # blue 255 assert blue_args["command"] == "node.set_value" assert blue_args["nodeId"] == 39 assert blue_args["valueId"]["commandClassName"] == "Color Switch" @@ -231,7 +235,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert state.attributes[ATTR_COLOR_TEMP] == 370 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning on with same rgb color await hass.services.async_call( @@ -241,9 +245,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 5 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning on with color temp await hass.services.async_call( @@ -253,8 +257,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 5 - red_args = client.async_send_command.call_args_list[0][0][0] # red 0 + assert len(client.async_send_command_no_wait.call_args_list) == 5 + red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -264,7 +268,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - red_args = client.async_send_command.call_args_list[1][0][0] # green 0 + red_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -274,7 +278,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - red_args = client.async_send_command.call_args_list[2][0][0] # blue 0 + red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -284,7 +288,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - warm_args = client.async_send_command.call_args_list[3][0][0] # warm white 0 + warm_args = client.async_send_command_no_wait.call_args_list[3][0][ + 0 + ] # warm white 0 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" @@ -294,7 +300,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert warm_args["valueId"]["property"] == "targetColor" assert warm_args["valueId"]["propertyName"] == "targetColor" assert warm_args["value"] == 20 - red_args = client.async_send_command.call_args_list[4][0][0] # cold white + red_args = client.async_send_command_no_wait.call_args_list[4][0][0] # cold white assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -305,7 +311,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 235 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test color temp update from value updated event red_event = Event( @@ -361,9 +367,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 5 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning off await hass.services.async_call( @@ -373,8 +379,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_lock.py b/tests/components/zwave_js/test_lock.py index 9ddc7abdd88eaa..e4032cf42ed038 100644 --- a/tests/components/zwave_js/test_lock.py +++ b/tests/components/zwave_js/test_lock.py @@ -33,8 +33,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -64,7 +64,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == 255 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test locked update from value updated event event = Event( @@ -88,7 +88,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): assert hass.states.get(SCHLAGE_BE469_LOCK_ENTITY).state == STATE_LOCKED - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test unlocking await hass.services.async_call( @@ -98,8 +98,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -129,7 +129,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == 0 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test set usercode service await hass.services.async_call( @@ -143,8 +143,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -167,7 +167,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == "1234" - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test clear usercode await hass.services.async_call( @@ -177,8 +177,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py index b7d83068bea220..136e62c540590f 100644 --- a/tests/components/zwave_js/test_number.py +++ b/tests/components/zwave_js/test_number.py @@ -20,8 +20,8 @@ async def test_number(hass, client, aeotec_radiator_thermostat, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 4 assert args["valueId"] == { @@ -43,7 +43,7 @@ async def test_number(hass, client, aeotec_radiator_thermostat, integration): } assert args["value"] == 30.0 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test value update from value updated event event = Event( diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 8e882b9547c8d5..d0bf08c1b7ab09 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -302,15 +302,15 @@ async def test_poll_value( ): """Test the poll_value service.""" # Test polling the primary value - client.async_send_command.return_value = {"result": 2} + client.async_send_command_no_wait.return_value = {"result": 2} await hass.services.async_call( DOMAIN, SERVICE_REFRESH_VALUE, {ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY}, blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.poll_value" assert args["nodeId"] == 26 assert args["valueId"] == { @@ -339,10 +339,10 @@ async def test_poll_value( "ccVersion": 2, } - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test polling all watched values - client.async_send_command.return_value = {"result": 2} + client.async_send_command_no_wait.return_value = {"result": 2} await hass.services.async_call( DOMAIN, SERVICE_REFRESH_VALUE, @@ -352,7 +352,7 @@ async def test_poll_value( }, blocking=True, ) - assert len(client.async_send_command.call_args_list) == 8 + assert len(client.async_send_command_no_wait.call_args_list) == 8 # Test polling against an invalid entity raises ValueError with pytest.raises(ValueError): diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index ea6e27d9b725f4..dceaa17a816c67 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -21,7 +21,7 @@ async def test_switch(hass, hank_binary_switch, integration, client): "switch", "turn_on", {"entity_id": SWITCH_ENTITY}, blocking=True ) - args = client.async_send_command.call_args[0][0] + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 32 assert args["valueId"] == { @@ -68,7 +68,7 @@ async def test_switch(hass, hank_binary_switch, integration, client): "switch", "turn_off", {"entity_id": SWITCH_ENTITY}, blocking=True ) - args = client.async_send_command.call_args[0][0] + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 32 assert args["valueId"] == { @@ -102,8 +102,8 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): DOMAIN, SERVICE_TURN_OFF, {"entity_id": entity}, blocking=True ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 0 @@ -134,7 +134,7 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): state = hass.states.get(entity) assert state.state == STATE_OFF - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test turning on await hass.services.async_call( @@ -143,8 +143,8 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): # Note: the valueId's value is still 255 because we never # received an updated value - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 255 diff --git a/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json b/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json index 2da1c203561c47..36db78faace357 100644 --- a/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json +++ b/tests/fixtures/zwave_js/aeon_smart_switch_6_state.json @@ -6,10 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Binary Switch", - "specific": "Binary Power Switch", - "mandatorySupportedCCs": ["Basic", "Binary Switch", "All Switch"], + "basic": {"key": 4, "label": "Routing Slave"}, + "generic": {"key": 16, "label":"Binary Switch"}, + "specific": {"key": 1, "label":"Binary Power Switch"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -53,6 +53,7 @@ "userIcon": 1792 } ], + "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json b/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json index 8cd6fe782016e0..27c3f991d33334 100644 --- a/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json +++ b/tests/fixtures/zwave_js/aeotec_radiator_thermostat_state.json @@ -6,16 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, diff --git a/tests/fixtures/zwave_js/bulb_6_multi_color_state.json b/tests/fixtures/zwave_js/bulb_6_multi_color_state.json index b7c422121c9c4b..64bfecfb20b4e2 100644 --- a/tests/fixtures/zwave_js/bulb_6_multi_color_state.json +++ b/tests/fixtures/zwave_js/bulb_6_multi_color_state.json @@ -6,14 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Multilevel Switch", - "specific": "Multilevel Power Switch", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Switch", - "All Switch" - ], + "basic": {"key": 2, "label": "Static Controller"}, + "generic": {"key": 17, "label":"Multilevel Switch"}, + "specific": {"key": 1, "label":"Multilevel Power Switch"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -67,6 +63,7 @@ "userIcon": 1536 } ], + "commandClasses": [], "values": [ { "commandClassName": "Multilevel Switch", diff --git a/tests/fixtures/zwave_js/chain_actuator_zws12_state.json b/tests/fixtures/zwave_js/chain_actuator_zws12_state.json index dbae35e04d0729..cf7adddc21e1cc 100644 --- a/tests/fixtures/zwave_js/chain_actuator_zws12_state.json +++ b/tests/fixtures/zwave_js/chain_actuator_zws12_state.json @@ -6,16 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Multilevel Switch", - "specific": "Motor Control Class C", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Switch", - "Binary Switch", - "Manufacturer Specific", - "Version" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 17, "label":"Multilevel Switch"}, + "specific": {"key": 7, "label":"Motor Control Class C"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -52,6 +46,7 @@ "endpoints": [ { "nodeId": 6, "index": 0, "installerIcon": 6656, "userIcon": 6656 } ], + "commandClasses": [], "values": [ { "commandClassName": "Multilevel Switch", diff --git a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json index e218d3b6a0e55d..90410998597146 100644 --- a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json +++ b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json @@ -4,15 +4,10 @@ "status": 1, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Thermostat", - "specific": "Setpoint Thermostat", - "mandatorySupportedCCs": [ - "Manufacturer Specific", - "Multi Command", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 4, "label":"Setpoint Thermostat"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, @@ -77,6 +72,7 @@ "index": 0 } ], + "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json index 066811c7374b75..0dc040c6cb24b9 100644 --- a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json +++ b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json @@ -6,16 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -116,6 +110,7 @@ "userIcon": 3329 } ], + "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json index ea38dfd9d6b0f6..fcdd57e981b7a7 100644 --- a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json +++ b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_different_endpoints_state.json @@ -6,16 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -63,6 +57,7 @@ "userIcon": 3333 } ], + "commandClasses": [], "values": [ { "commandClassName": "Manufacturer Specific", diff --git a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json index caad22aac36cf3..34df415301eb5a 100644 --- a/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json +++ b/tests/fixtures/zwave_js/climate_radio_thermostat_ct100_plus_state.json @@ -6,16 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -63,6 +57,7 @@ }, { "nodeId": 13, "index": 2 } ], + "commandClasses": [], "values": [ { "commandClassName": "Manufacturer Specific", diff --git a/tests/fixtures/zwave_js/cover_iblinds_v2_state.json b/tests/fixtures/zwave_js/cover_iblinds_v2_state.json index 7cb6f94a6f0b95..35ce70f617aa76 100644 --- a/tests/fixtures/zwave_js/cover_iblinds_v2_state.json +++ b/tests/fixtures/zwave_js/cover_iblinds_v2_state.json @@ -6,13 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Multilevel Switch", - "specific": "Unused", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Switch" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 17, "label":"Routing Slave"}, + "specific": {"key": 0, "label":"Unused"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, @@ -75,6 +72,7 @@ "userIcon": 6400 } ], + "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/fixtures/zwave_js/cover_zw062_state.json b/tests/fixtures/zwave_js/cover_zw062_state.json index 107225e0dcc724..9e7b05adc34bf9 100644 --- a/tests/fixtures/zwave_js/cover_zw062_state.json +++ b/tests/fixtures/zwave_js/cover_zw062_state.json @@ -6,26 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Entry Control", - "specific": "Secure Barrier Add-on", - "mandatorySupportedCCs": [ - "Application Status", - "Association", - "Association Group Information", - "Barrier Operator", - "Battery", - "Device Reset Locally", - "Manufacturer Specific", - "Notification", - "Powerlevel", - "Security", - "Security 2", - "Supervision", - "Transport Service", - "Version", - "Z-Wave Plus Info" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 64, "label":"Entry Control"}, + "specific": {"key": 7, "label":"Secure Barrier Add-on"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -94,6 +78,7 @@ "userIcon": 7680 } ], + "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json index 0f2f45d01e3906..db815506a6be1c 100644 --- a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json +++ b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json @@ -6,14 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Multilevel Switch", - "specific": "Multilevel Power Switch", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Switch", - "All Switch" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 17, "label":"Routing Slave"}, + "specific": {"key": 1, "label":"Multilevel Power Switch"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -74,6 +70,7 @@ "userIcon": 1536 } ], + "commandClasses": [], "values": [ { "commandClassName": "Multilevel Switch", diff --git a/tests/fixtures/zwave_js/ecolink_door_sensor_state.json b/tests/fixtures/zwave_js/ecolink_door_sensor_state.json index bd5f2c6b466371..9c2befdf5e8dfb 100644 --- a/tests/fixtures/zwave_js/ecolink_door_sensor_state.json +++ b/tests/fixtures/zwave_js/ecolink_door_sensor_state.json @@ -4,16 +4,11 @@ "status": 1, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Binary Sensor", - "specific": "Routing Binary Sensor", - "mandatorySupportedCCs": [ - "Basic", - "Binary Sensor" - ], - "mandatoryControlCCs": [ - - ] + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 32, "label":"Binary Sensor"}, + "specific": {"key": 1, "label":"Routing Binary Sensor"}, + "mandatorySupportedCCs": [], + "mandatoryControlCCs": [] }, "isListening": false, "isFrequentListening": false, @@ -61,6 +56,7 @@ "index": 0 } ], + "commandClasses": [], "values": [ { "commandClassName": "Basic", diff --git a/tests/fixtures/zwave_js/fan_ge_12730_state.json b/tests/fixtures/zwave_js/fan_ge_12730_state.json index 692cc75fe99585..b6cf59b4226050 100644 --- a/tests/fixtures/zwave_js/fan_ge_12730_state.json +++ b/tests/fixtures/zwave_js/fan_ge_12730_state.json @@ -4,14 +4,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Multilevel Switch", - "specific": "Multilevel Power Switch", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Switch", - "All Switch" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 17, "label":"Multilevel Switch"}, + "specific": {"key": 1, "label":"Multilevel Power Switch"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -57,6 +53,7 @@ "index": 0 } ], + "commandClasses": [], "values": [ { "endpoint": 0, diff --git a/tests/fixtures/zwave_js/hank_binary_switch_state.json b/tests/fixtures/zwave_js/hank_binary_switch_state.json index 0c629b3cf9973d..e5f739d63a5f16 100644 --- a/tests/fixtures/zwave_js/hank_binary_switch_state.json +++ b/tests/fixtures/zwave_js/hank_binary_switch_state.json @@ -6,14 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Binary Switch", - "specific": "Binary Power Switch", - "mandatorySupportedCCs": [ - "Basic", - "Binary Switch", - "All Switch" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 16, "label":"Binary Switch"}, + "specific": {"key": 1, "label":"Binary Power Switch"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -67,6 +63,7 @@ "userIcon": 1792 } ], + "commandClasses": [], "values": [ { "commandClassName": "Binary Switch", diff --git a/tests/fixtures/zwave_js/in_wall_smart_fan_control_state.json b/tests/fixtures/zwave_js/in_wall_smart_fan_control_state.json index fe5550a54242cb..74467664955af5 100644 --- a/tests/fixtures/zwave_js/in_wall_smart_fan_control_state.json +++ b/tests/fixtures/zwave_js/in_wall_smart_fan_control_state.json @@ -6,13 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Multilevel Switch", - "specific": "Fan Switch", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Switch" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 17, "label":"Multilevel Switch"}, + "specific": {"key": 8, "label":"Fan Switch"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -87,6 +84,7 @@ "userIcon": 1024 } ], + "commandClasses": [], "values": [ { "commandClassName": "Multilevel Switch", diff --git a/tests/fixtures/zwave_js/lock_august_asl03_state.json b/tests/fixtures/zwave_js/lock_august_asl03_state.json index b6d4434185347f..2b218cd915b277 100644 --- a/tests/fixtures/zwave_js/lock_august_asl03_state.json +++ b/tests/fixtures/zwave_js/lock_august_asl03_state.json @@ -6,17 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Routing Slave", - "generic": "Entry Control", - "specific": "Secure Keypad Door Lock", - "mandatorySupportedCCs": [ - "Basic", - "Door Lock", - "User Code", - "Manufacturer Specific", - "Security", - "Version" - ], + "basic": {"key": 4, "label":"Routing Slave"}, + "generic": {"key": 64, "label":"Entry Control"}, + "specific": {"key": 3, "label":"Secure Keypad Door Lock"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, @@ -73,6 +66,7 @@ "userIcon": 768 } ], + "commandClasses": [], "values": [ { "commandClassName": "Door Lock", diff --git a/tests/fixtures/zwave_js/lock_schlage_be469_state.json b/tests/fixtures/zwave_js/lock_schlage_be469_state.json index af1fc92a206572..be1ddb9c3f0786 100644 --- a/tests/fixtures/zwave_js/lock_schlage_be469_state.json +++ b/tests/fixtures/zwave_js/lock_schlage_be469_state.json @@ -4,17 +4,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Entry Control", - "specific": "Secure Keypad Door Lock", - "mandatorySupportedCCs": [ - "Basic", - "Door Lock", - "User Code", - "Manufacturer Specific", - "Security", - "Version" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 64, "label":"Entry Control"}, + "specific": {"key": 3, "label":"Secure Keypad Door Lock"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, @@ -57,6 +50,7 @@ "index": 0 } ], + "commandClasses": [], "values": [ { "commandClassName": "Door Lock", diff --git a/tests/fixtures/zwave_js/multisensor_6_state.json b/tests/fixtures/zwave_js/multisensor_6_state.json index 3c508ffd3ff018..131a5aa026f468 100644 --- a/tests/fixtures/zwave_js/multisensor_6_state.json +++ b/tests/fixtures/zwave_js/multisensor_6_state.json @@ -6,13 +6,10 @@ "status": 1, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Multilevel Sensor", - "specific": "Routing Multilevel Sensor", - "mandatorySupportedCCs": [ - "Basic", - "Multilevel Sensor" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 21, "label":"Multilevel Sensor"}, + "specific": {"key": 1, "label":"Routing Multilevel Sensor"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": true, @@ -70,6 +67,7 @@ "userIcon": 3079 } ], + "commandClasses": [], "values": [ { "commandClassName": "Basic", diff --git a/tests/fixtures/zwave_js/nortek_thermostat_added_event.json b/tests/fixtures/zwave_js/nortek_thermostat_added_event.json index d778f77ce2450f..60078100caf615 100644 --- a/tests/fixtures/zwave_js/nortek_thermostat_added_event.json +++ b/tests/fixtures/zwave_js/nortek_thermostat_added_event.json @@ -7,16 +7,10 @@ "status": 0, "ready": false, "deviceClass": { - "basic": "Static Controller", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "neighbors": [], @@ -27,6 +21,7 @@ "index": 0 } ], + "commandClasses": [], "values": [ { "commandClassName": "Basic", diff --git a/tests/fixtures/zwave_js/nortek_thermostat_removed_event.json b/tests/fixtures/zwave_js/nortek_thermostat_removed_event.json index ed25a65054350b..01bad6c4a8fb18 100644 --- a/tests/fixtures/zwave_js/nortek_thermostat_removed_event.json +++ b/tests/fixtures/zwave_js/nortek_thermostat_removed_event.json @@ -7,16 +7,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, @@ -67,6 +61,7 @@ "index": 0 } ], + "commandClasses": [], "values": [ { "commandClassName": "Manufacturer Specific", diff --git a/tests/fixtures/zwave_js/nortek_thermostat_state.json b/tests/fixtures/zwave_js/nortek_thermostat_state.json index 62a08999cda727..4e6ca17e01386f 100644 --- a/tests/fixtures/zwave_js/nortek_thermostat_state.json +++ b/tests/fixtures/zwave_js/nortek_thermostat_state.json @@ -6,16 +6,10 @@ "status": 4, "ready": true, "deviceClass": { - "basic": "Static Controller", - "generic": "Thermostat", - "specific": "Thermostat General V2", - "mandatorySupportedCCs": [ - "Basic", - "Manufacturer Specific", - "Thermostat Mode", - "Thermostat Setpoint", - "Version" - ], + "basic": {"key": 2, "label":"Static Controller"}, + "generic": {"key": 8, "label":"Thermostat"}, + "specific": {"key": 6, "label":"Thermostat General V2"}, + "mandatorySupportedCCs": [], "mandatoryControlCCs": [] }, "isListening": false, @@ -75,6 +69,7 @@ "userIcon": 4608 } ], + "commandClasses": [], "values": [ { "commandClassName": "Manufacturer Specific", From 567ec26c4813547cb2d09dfb13afb8bb186da07f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Feb 2021 22:01:28 +0100 Subject: [PATCH 0762/1818] Bumped version to 2021.3.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a0aafaad3cedb2..f9a8e3e99b3769 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 5ab11df551fee1792ff6b6d70a451b720ec01f6f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Feb 2021 22:28:02 +0100 Subject: [PATCH 0763/1818] Bump version to 2021.4.0dev0 (#47017) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a0aafaad3cedb2..f8d2507d0517e5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,6 +1,6 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 -MINOR_VERSION = 3 +MINOR_VERSION = 4 PATCH_VERSION = "0.dev0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 557ec374f176d219b53b12e999e334f0006e8aa8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Feb 2021 13:37:31 -0800 Subject: [PATCH 0764/1818] Convert discovery helper to use dispatcher (#47008) --- .../components/discovery/__init__.py | 9 +- .../components/octoprint/__init__.py | 8 -- homeassistant/const.py | 4 - homeassistant/helpers/discovery.py | 120 +++++++----------- tests/common.py | 18 --- tests/helpers/test_discovery.py | 57 ++++----- tests/test_setup.py | 12 +- 7 files changed, 79 insertions(+), 149 deletions(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index b97202033bde1a..2b2931798885bd 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -1,11 +1,4 @@ -""" -Starts a service to scan in intervals for new devices. - -Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered. - -Knows which components handle certain types, will make sure they are -loaded before the EVENT_PLATFORM_DISCOVERED is fired. -""" +"""Starts a service to scan in intervals for new devices.""" from datetime import timedelta import json import logging diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 6f178f2657844b..66b804927c7740 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -6,7 +6,6 @@ import requests import voluptuous as vol -from homeassistant.components.discovery import SERVICE_OCTOPRINT from homeassistant.const import ( CONF_API_KEY, CONF_BINARY_SENSORS, @@ -22,7 +21,6 @@ TEMP_CELSIUS, TIME_SECONDS, ) -from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.util import slugify as util_slugify @@ -132,12 +130,6 @@ def setup(hass, config): printers = hass.data[DOMAIN] = {} success = False - def device_discovered(service, info): - """Get called when an Octoprint server has been discovered.""" - _LOGGER.debug("Found an Octoprint server: %s", info) - - discovery.listen(hass, SERVICE_OCTOPRINT, device_discovered) - if DOMAIN not in config: # Skip the setup if there is no configuration present return True diff --git a/homeassistant/const.py b/homeassistant/const.py index f8d2507d0517e5..712f7ede0d3fbe 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -210,7 +210,6 @@ EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_HOMEASSISTANT_FINAL_WRITE = "homeassistant_final_write" EVENT_LOGBOOK_ENTRY = "logbook_entry" -EVENT_PLATFORM_DISCOVERED = "platform_discovered" EVENT_SERVICE_REGISTERED = "service_registered" EVENT_SERVICE_REMOVED = "service_removed" EVENT_STATE_CHANGED = "state_changed" @@ -313,9 +312,6 @@ # Electrical attributes ATTR_VOLTAGE = "voltage" -# Contains the information that is discovered -ATTR_DISCOVERED = "discovered" - # Location of the device/sensor ATTR_LOCATION = "location" diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 0770e6798f19c1..7ee72759d65dfd 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,62 +5,55 @@ - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ -from typing import Any, Callable, Collection, Dict, Optional, Union +from typing import Any, Callable, Dict, Optional, TypedDict from homeassistant import core, setup -from homeassistant.const import ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED from homeassistant.core import CALLBACK_TYPE -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import bind_hass -from homeassistant.util.async_ import run_callback_threadsafe +from .dispatcher import async_dispatcher_connect, async_dispatcher_send +from .typing import ConfigType, DiscoveryInfoType + +SIGNAL_PLATFORM_DISCOVERED = "discovery.platform_discovered_{}" EVENT_LOAD_PLATFORM = "load_platform.{}" ATTR_PLATFORM = "platform" +ATTR_DISCOVERED = "discovered" # mypy: disallow-any-generics -@bind_hass -def listen( - hass: core.HomeAssistant, - service: Union[str, Collection[str]], - callback: CALLBACK_TYPE, -) -> None: - """Set up listener for discovery of specific service. +class DiscoveryDict(TypedDict): + """Discovery data.""" - Service can be a string or a list/tuple. - """ - run_callback_threadsafe(hass.loop, async_listen, hass, service, callback).result() + service: str + platform: Optional[str] + discovered: Optional[DiscoveryInfoType] @core.callback @bind_hass def async_listen( hass: core.HomeAssistant, - service: Union[str, Collection[str]], + service: str, callback: CALLBACK_TYPE, ) -> None: """Set up listener for discovery of specific service. Service can be a string or a list/tuple. """ - if isinstance(service, str): - service = (service,) - else: - service = tuple(service) - job = core.HassJob(callback) - async def discovery_event_listener(event: core.Event) -> None: + async def discovery_event_listener(discovered: DiscoveryDict) -> None: """Listen for discovery events.""" - if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service: - task = hass.async_run_hass_job( - job, event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED) - ) - if task: - await task + task = hass.async_run_hass_job( + job, discovered["service"], discovered["discovered"] + ) + if task: + await task - hass.bus.async_listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener) + async_dispatcher_connect( + hass, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_event_listener + ) @bind_hass @@ -91,22 +84,13 @@ async def async_discover( if component is not None and component not in hass.config.components: await setup.async_setup_component(hass, component, hass_config) - data: Dict[str, Any] = {ATTR_SERVICE: service} - - if discovered is not None: - data[ATTR_DISCOVERED] = discovered - - hass.bus.async_fire(EVENT_PLATFORM_DISCOVERED, data) - + data: DiscoveryDict = { + "service": service, + "platform": None, + "discovered": discovered, + } -@bind_hass -def listen_platform( - hass: core.HomeAssistant, component: str, callback: CALLBACK_TYPE -) -> None: - """Register a platform loader listener.""" - run_callback_threadsafe( - hass.loop, async_listen_platform, hass, component, callback - ).result() + async_dispatcher_send(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data) @bind_hass @@ -122,21 +106,20 @@ def async_listen_platform( service = EVENT_LOAD_PLATFORM.format(component) job = core.HassJob(callback) - async def discovery_platform_listener(event: core.Event) -> None: + async def discovery_platform_listener(discovered: DiscoveryDict) -> None: """Listen for platform discovery events.""" - if event.data.get(ATTR_SERVICE) != service: - return - - platform = event.data.get(ATTR_PLATFORM) + platform = discovered["platform"] if not platform: return - task = hass.async_run_hass_job(job, platform, event.data.get(ATTR_DISCOVERED)) + task = hass.async_run_hass_job(job, platform, discovered.get("discovered")) if task: await task - hass.bus.async_listen(EVENT_PLATFORM_DISCOVERED, discovery_platform_listener) + async_dispatcher_connect( + hass, SIGNAL_PLATFORM_DISCOVERED.format(service), discovery_platform_listener + ) @bind_hass @@ -147,16 +130,7 @@ def load_platform( discovered: DiscoveryInfoType, hass_config: ConfigType, ) -> None: - """Load a component and platform dynamically. - - Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be - fired to load the platform. The event will contain: - { ATTR_SERVICE = EVENT_LOAD_PLATFORM + '.' + <> - ATTR_PLATFORM = <> - ATTR_DISCOVERED = <> } - - Use `listen_platform` to register a callback for these events. - """ + """Load a component and platform dynamically.""" hass.add_job( async_load_platform( # type: ignore hass, component, platform, discovered, hass_config @@ -174,18 +148,10 @@ async def async_load_platform( ) -> None: """Load a component and platform dynamically. - Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be - fired to load the platform. The event will contain: - { ATTR_SERVICE = EVENT_LOAD_PLATFORM + '.' + <> - ATTR_PLATFORM = <> - ATTR_DISCOVERED = <> } - - Use `listen_platform` to register a callback for these events. + Use `async_listen_platform` to register a callback for these events. Warning: Do not await this inside a setup method to avoid a dead lock. Use `hass.async_create_task(async_load_platform(..))` instead. - - This method is a coroutine. """ assert hass_config, "You need to pass in the real hass config" @@ -194,16 +160,16 @@ async def async_load_platform( if component not in hass.config.components: setup_success = await setup.async_setup_component(hass, component, hass_config) - # No need to fire event if we could not set up component + # No need to send signal if we could not set up component if not setup_success: return - data: Dict[str, Any] = { - ATTR_SERVICE: EVENT_LOAD_PLATFORM.format(component), - ATTR_PLATFORM: platform, - } + service = EVENT_LOAD_PLATFORM.format(component) - if discovered is not None: - data[ATTR_DISCOVERED] = discovered + data: DiscoveryDict = { + "service": service, + "platform": platform, + "discovered": discovered, + } - hass.bus.async_fire(EVENT_PLATFORM_DISCOVERED, data) + async_dispatcher_send(hass, SIGNAL_PLATFORM_DISCOVERED.format(service), data) diff --git a/tests/common.py b/tests/common.py index 52d368853b306b..0ae6f7ef5c7f84 100644 --- a/tests/common.py +++ b/tests/common.py @@ -36,11 +36,8 @@ from homeassistant.components.mqtt.models import Message from homeassistant.config import async_process_component_config from homeassistant.const import ( - ATTR_DISCOVERED, - ATTR_SERVICE, DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_CLOSE, - EVENT_PLATFORM_DISCOVERED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, STATE_OFF, @@ -387,21 +384,6 @@ def async_fire_time_changed(hass, datetime_, fire_all=False): fire_time_changed = threadsafe_callback_factory(async_fire_time_changed) -def fire_service_discovered(hass, service, info): - """Fire the MQTT message.""" - hass.bus.fire( - EVENT_PLATFORM_DISCOVERED, {ATTR_SERVICE: service, ATTR_DISCOVERED: info} - ) - - -@ha.callback -def async_fire_service_discovered(hass, service, info): - """Fire the MQTT message.""" - hass.bus.async_fire( - EVENT_PLATFORM_DISCOVERED, {ATTR_SERVICE: service, ATTR_DISCOVERED: info} - ) - - def load_fixture(filename): """Load a fixture.""" path = os.path.join(os.path.dirname(__file__), "fixtures", filename) diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index 64f39fb13bd28e..d0d8580d69de2d 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -4,6 +4,8 @@ from homeassistant import setup from homeassistant.core import callback from homeassistant.helpers import discovery +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.util.async_ import run_callback_threadsafe from tests.common import ( MockModule, @@ -31,23 +33,22 @@ def test_listen(self, mock_setup_component): """Test discovery listen/discover combo.""" helpers = self.hass.helpers calls_single = [] - calls_multi = [] @callback def callback_single(service, info): """Service discovered callback.""" calls_single.append((service, info)) - @callback - def callback_multi(service, info): - """Service discovered callback.""" - calls_multi.append((service, info)) - - helpers.discovery.listen("test service", callback_single) - helpers.discovery.listen(["test service", "another service"], callback_multi) + self.hass.add_job( + helpers.discovery.async_listen, "test service", callback_single + ) - helpers.discovery.discover( - "test service", "discovery info", "test_component", {} + self.hass.add_job( + helpers.discovery.async_discover, + "test service", + "discovery info", + "test_component", + {}, ) self.hass.block_till_done() @@ -56,15 +57,6 @@ def callback_multi(service, info): assert len(calls_single) == 1 assert calls_single[0] == ("test service", "discovery info") - helpers.discovery.discover( - "another service", "discovery info", "test_component", {} - ) - self.hass.block_till_done() - - assert len(calls_single) == 1 - assert len(calls_multi) == 2 - assert ["test service", "another service"] == [info[0] for info in calls_multi] - @patch("homeassistant.setup.async_setup_component", return_value=mock_coro(True)) def test_platform(self, mock_setup_component): """Test discover platform method.""" @@ -75,7 +67,13 @@ def platform_callback(platform, info): """Platform callback method.""" calls.append((platform, info)) - discovery.listen_platform(self.hass, "test_component", platform_callback) + run_callback_threadsafe( + self.hass.loop, + discovery.async_listen_platform, + self.hass, + "test_component", + platform_callback, + ).result() discovery.load_platform( self.hass, @@ -105,13 +103,10 @@ def platform_callback(platform, info): assert len(calls) == 1 assert calls[0] == ("test_platform", "discovery info") - self.hass.bus.fire( - discovery.EVENT_PLATFORM_DISCOVERED, - { - discovery.ATTR_SERVICE: discovery.EVENT_LOAD_PLATFORM.format( - "test_component" - ) - }, + dispatcher_send( + self.hass, + discovery.SIGNAL_PLATFORM_DISCOVERED, + {"service": discovery.EVENT_LOAD_PLATFORM.format("test_component")}, ) self.hass.block_till_done() @@ -179,10 +174,12 @@ def test_1st_discovers_2nd_component(self, mock_signal): """ component_calls = [] - def component1_setup(hass, config): + async def component1_setup(hass, config): """Set up mock component.""" print("component1 setup") - discovery.discover(hass, "test_component2", {}, "test_component2", {}) + await discovery.async_discover( + hass, "test_component2", {}, "test_component2", {} + ) return True def component2_setup(hass, config): @@ -191,7 +188,7 @@ def component2_setup(hass, config): return True mock_integration( - self.hass, MockModule("test_component1", setup=component1_setup) + self.hass, MockModule("test_component1", async_setup=component1_setup) ) mock_integration( diff --git a/tests/test_setup.py b/tests/test_setup.py index 539ed3f14428c3..abb8f7569897f4 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -441,10 +441,14 @@ def test_all_work_done_before_start(self): """Test all init work done till start.""" call_order = [] - def component1_setup(hass, config): + async def component1_setup(hass, config): """Set up mock component.""" - discovery.discover(hass, "test_component2", {}, "test_component2", {}) - discovery.discover(hass, "test_component3", {}, "test_component3", {}) + await discovery.async_discover( + hass, "test_component2", {}, "test_component2", {} + ) + await discovery.async_discover( + hass, "test_component3", {}, "test_component3", {} + ) return True def component_track_setup(hass, config): @@ -453,7 +457,7 @@ def component_track_setup(hass, config): return True mock_integration( - self.hass, MockModule("test_component1", setup=component1_setup) + self.hass, MockModule("test_component1", async_setup=component1_setup) ) mock_integration( From bb7f1b748f77a8801cd0833573a255b83978f939 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 25 Feb 2021 00:05:20 +0000 Subject: [PATCH 0765/1818] [ci skip] Translation update --- .../components/arcam_fmj/translations/nl.json | 1 + .../components/asuswrt/translations/nl.json | 1 + .../components/august/translations/nl.json | 3 +- .../components/blink/translations/nl.json | 1 + .../components/bond/translations/ca.json | 4 +-- .../components/bond/translations/et.json | 4 +-- .../components/bond/translations/fr.json | 4 +-- .../components/bond/translations/nl.json | 1 + .../components/bond/translations/no.json | 4 +-- .../components/bond/translations/pl.json | 4 +-- .../components/bond/translations/ru.json | 4 +-- .../components/bond/translations/zh-Hant.json | 4 +-- .../components/broadlink/translations/nl.json | 1 + .../components/climacell/translations/af.json | 10 ++++++ .../components/climacell/translations/ca.json | 34 +++++++++++++++++++ .../components/climacell/translations/en.json | 34 +++++++++++++++++++ .../components/climacell/translations/et.json | 34 +++++++++++++++++++ .../components/climacell/translations/fr.json | 34 +++++++++++++++++++ .../components/climacell/translations/nl.json | 32 +++++++++++++++++ .../components/climacell/translations/no.json | 34 +++++++++++++++++++ .../components/climacell/translations/ru.json | 34 +++++++++++++++++++ .../components/daikin/translations/nl.json | 4 ++- .../components/enocean/translations/nl.json | 7 ++++ .../faa_delays/translations/en.json | 12 ++++--- .../faa_delays/translations/fr.json | 21 ++++++++++++ .../faa_delays/translations/nl.json | 21 ++++++++++++ .../fireservicerota/translations/nl.json | 3 +- .../fireservicerota/translations/no.json | 2 +- .../components/firmata/translations/nl.json | 7 ++++ .../flunearyou/translations/nl.json | 3 ++ .../components/fritzbox/translations/nl.json | 1 + .../components/goalzero/translations/nl.json | 1 + .../home_connect/translations/nl.json | 3 +- .../components/homekit/translations/ca.json | 8 ++--- .../components/homekit/translations/en.json | 21 ++++++++++-- .../components/homekit/translations/et.json | 10 +++--- .../components/homekit/translations/fr.json | 8 ++--- .../components/homekit/translations/no.json | 8 ++--- .../components/homekit/translations/pl.json | 8 ++--- .../components/homekit/translations/ru.json | 8 ++--- .../homekit/translations/zh-Hant.json | 8 ++--- .../huawei_lte/translations/nl.json | 1 + .../components/icloud/translations/nl.json | 6 ++-- .../components/ifttt/translations/nl.json | 3 +- .../components/insteon/translations/nl.json | 3 ++ .../keenetic_ndms2/translations/nl.json | 3 +- .../components/kmtronic/translations/fr.json | 21 ++++++++++++ .../components/kmtronic/translations/pl.json | 21 ++++++++++++ .../components/litejet/translations/ca.json | 19 +++++++++++ .../components/litejet/translations/fr.json | 16 +++++++++ .../components/litejet/translations/no.json | 19 +++++++++++ .../components/litejet/translations/pl.json | 19 +++++++++++ .../components/litejet/translations/ru.json | 19 +++++++++++ .../components/litejet/translations/tr.json | 9 +++++ .../litejet/translations/zh-Hant.json | 19 +++++++++++ .../litterrobot/translations/fr.json | 20 +++++++++++ .../litterrobot/translations/no.json | 20 +++++++++++ .../litterrobot/translations/pl.json | 20 +++++++++++ .../components/locative/translations/nl.json | 3 +- .../components/mailgun/translations/nl.json | 3 +- .../components/mazda/translations/nl.json | 3 +- .../components/mullvad/translations/ca.json | 22 ++++++++++++ .../components/mullvad/translations/en.json | 6 ++++ .../components/mullvad/translations/et.json | 22 ++++++++++++ .../components/mullvad/translations/fr.json | 22 ++++++++++++ .../components/mullvad/translations/nl.json | 22 ++++++++++++ .../components/mullvad/translations/no.json | 22 ++++++++++++ .../components/mullvad/translations/ru.json | 22 ++++++++++++ .../components/mullvad/translations/tr.json | 12 +++++++ .../components/netatmo/translations/et.json | 22 ++++++++++++ .../components/netatmo/translations/fr.json | 22 ++++++++++++ .../components/netatmo/translations/nl.json | 25 +++++++++++++- .../components/netatmo/translations/ru.json | 22 ++++++++++++ .../components/netatmo/translations/tr.json | 16 +++++++++ .../nightscout/translations/nl.json | 1 + .../components/nzbget/translations/nl.json | 1 + .../plum_lightpad/translations/nl.json | 3 +- .../components/poolsense/translations/nl.json | 3 +- .../translations/fr.json | 21 ++++++++++++ .../components/rpi_power/translations/nl.json | 5 +++ .../ruckus_unleashed/translations/nl.json | 1 + .../components/sharkiq/translations/nl.json | 1 + .../components/smappee/translations/nl.json | 3 +- .../components/sms/translations/nl.json | 3 +- .../components/somfy/translations/nl.json | 1 + .../speedtestdotnet/translations/nl.json | 5 +++ .../components/spider/translations/nl.json | 3 ++ .../components/subaru/translations/fr.json | 31 +++++++++++++++++ .../components/syncthru/translations/nl.json | 3 +- .../components/tile/translations/nl.json | 3 +- .../components/toon/translations/nl.json | 2 ++ .../totalconnect/translations/fr.json | 11 +++++- .../totalconnect/translations/nl.json | 5 +++ .../totalconnect/translations/no.json | 17 ++++++++-- .../totalconnect/translations/pl.json | 17 ++++++++-- .../xiaomi_miio/translations/no.json | 1 + .../xiaomi_miio/translations/pl.json | 1 + .../components/zwave_js/translations/ca.json | 7 +++- .../components/zwave_js/translations/et.json | 7 +++- .../components/zwave_js/translations/fr.json | 7 +++- .../components/zwave_js/translations/no.json | 7 +++- .../components/zwave_js/translations/pl.json | 7 +++- .../components/zwave_js/translations/ru.json | 7 +++- .../zwave_js/translations/zh-Hant.json | 7 +++- 104 files changed, 1060 insertions(+), 81 deletions(-) create mode 100644 homeassistant/components/climacell/translations/af.json create mode 100644 homeassistant/components/climacell/translations/ca.json create mode 100644 homeassistant/components/climacell/translations/en.json create mode 100644 homeassistant/components/climacell/translations/et.json create mode 100644 homeassistant/components/climacell/translations/fr.json create mode 100644 homeassistant/components/climacell/translations/nl.json create mode 100644 homeassistant/components/climacell/translations/no.json create mode 100644 homeassistant/components/climacell/translations/ru.json create mode 100644 homeassistant/components/enocean/translations/nl.json create mode 100644 homeassistant/components/faa_delays/translations/fr.json create mode 100644 homeassistant/components/faa_delays/translations/nl.json create mode 100644 homeassistant/components/firmata/translations/nl.json create mode 100644 homeassistant/components/kmtronic/translations/fr.json create mode 100644 homeassistant/components/kmtronic/translations/pl.json create mode 100644 homeassistant/components/litejet/translations/ca.json create mode 100644 homeassistant/components/litejet/translations/fr.json create mode 100644 homeassistant/components/litejet/translations/no.json create mode 100644 homeassistant/components/litejet/translations/pl.json create mode 100644 homeassistant/components/litejet/translations/ru.json create mode 100644 homeassistant/components/litejet/translations/tr.json create mode 100644 homeassistant/components/litejet/translations/zh-Hant.json create mode 100644 homeassistant/components/litterrobot/translations/fr.json create mode 100644 homeassistant/components/litterrobot/translations/no.json create mode 100644 homeassistant/components/litterrobot/translations/pl.json create mode 100644 homeassistant/components/mullvad/translations/ca.json create mode 100644 homeassistant/components/mullvad/translations/et.json create mode 100644 homeassistant/components/mullvad/translations/fr.json create mode 100644 homeassistant/components/mullvad/translations/nl.json create mode 100644 homeassistant/components/mullvad/translations/no.json create mode 100644 homeassistant/components/mullvad/translations/ru.json create mode 100644 homeassistant/components/mullvad/translations/tr.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/fr.json create mode 100644 homeassistant/components/subaru/translations/fr.json diff --git a/homeassistant/components/arcam_fmj/translations/nl.json b/homeassistant/components/arcam_fmj/translations/nl.json index 5607b426cc988f..03465d5c53df4e 100644 --- a/homeassistant/components/arcam_fmj/translations/nl.json +++ b/homeassistant/components/arcam_fmj/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" }, "error": { diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index 1128a820cd577c..9d1e76aaf2b5c2 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", + "ssh_not_file": "SSH-sleutelbestand niet gevonden", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 1697f634d9a82e..e48d27801ccdc7 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd" + "already_configured": "Account al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json index 4067bf75f834e0..f1f1ce7888bd12 100644 --- a/homeassistant/components/blink/translations/nl.json +++ b/homeassistant/components/blink/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_access_token": "Ongeldig toegangstoken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/bond/translations/ca.json b/homeassistant/components/bond/translations/ca.json index 3903ea77c34166..1d1df91563057b 100644 --- a/homeassistant/components/bond/translations/ca.json +++ b/homeassistant/components/bond/translations/ca.json @@ -9,13 +9,13 @@ "old_firmware": "Hi ha un programari antic i no compatible al dispositiu Bond - actualitza'l abans de continuar", "unknown": "Error inesperat" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token d'acc\u00e9s" }, - "description": "Vols configurar {bond_id}?" + "description": "Vols configurar {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/et.json b/homeassistant/components/bond/translations/et.json index dc6a8414bce9ef..5e9a8e4493f44b 100644 --- a/homeassistant/components/bond/translations/et.json +++ b/homeassistant/components/bond/translations/et.json @@ -9,13 +9,13 @@ "old_firmware": "Bondi seadme ei toeta vana p\u00fcsivara - uuenda enne j\u00e4tkamist", "unknown": "Tundmatu viga" }, - "flow_title": "Bond: {bond_id} ( {host} )", + "flow_title": "Bond: {name} ( {host} )", "step": { "confirm": { "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Kas soovid seadistada teenuse {bond_id} ?" + "description": "Kas soovid seadistada teenust {name} ?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/fr.json b/homeassistant/components/bond/translations/fr.json index 496a21339cbf24..d9eb14b1a620cc 100644 --- a/homeassistant/components/bond/translations/fr.json +++ b/homeassistant/components/bond/translations/fr.json @@ -9,13 +9,13 @@ "old_firmware": "Ancien micrologiciel non pris en charge sur l'appareil Bond - veuillez mettre \u00e0 niveau avant de continuer", "unknown": "Erreur inattendue" }, - "flow_title": "Bond : {bond_id} ({h\u00f4te})", + "flow_title": "Lien : {name} ({host})", "step": { "confirm": { "data": { "access_token": "Jeton d'acc\u00e8s" }, - "description": "Voulez-vous configurer {bond_id} ?" + "description": "Voulez-vous configurer {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index 8010dfc2e78645..b5d8c593ea9e93 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "access_token": "Toegangstoken", "host": "Host" } } diff --git a/homeassistant/components/bond/translations/no.json b/homeassistant/components/bond/translations/no.json index 01ff745eed36c7..c09b7a1763533b 100644 --- a/homeassistant/components/bond/translations/no.json +++ b/homeassistant/components/bond/translations/no.json @@ -9,13 +9,13 @@ "old_firmware": "Gammel fastvare som ikke st\u00f8ttes p\u00e5 Bond-enheten \u2013 vennligst oppgrader f\u00f8r du fortsetter", "unknown": "Uventet feil" }, - "flow_title": "", + "flow_title": "Obligasjon: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Tilgangstoken" }, - "description": "Vil du konfigurere {bond_id}?" + "description": "Vil du konfigurere {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/pl.json b/homeassistant/components/bond/translations/pl.json index c50c270b74cd50..6f5f2d276ff391 100644 --- a/homeassistant/components/bond/translations/pl.json +++ b/homeassistant/components/bond/translations/pl.json @@ -9,13 +9,13 @@ "old_firmware": "Stare, nieobs\u0142ugiwane oprogramowanie na urz\u0105dzeniu Bond - zaktualizuj przed kontynuowaniem", "unknown": "Nieoczekiwany b\u0142\u0105d" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token dost\u0119pu" }, - "description": "Czy chcesz skonfigurowa\u0107 {bond_id}?" + "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/ru.json b/homeassistant/components/bond/translations/ru.json index e6c4067d8ac585..cdc37fc27f7021 100644 --- a/homeassistant/components/bond/translations/ru.json +++ b/homeassistant/components/bond/translations/ru.json @@ -9,13 +9,13 @@ "old_firmware": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430 \u0438 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, - "flow_title": "Bond {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {bond_id}?" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/zh-Hant.json b/homeassistant/components/bond/translations/zh-Hant.json index af652c54509efa..1c5327dc6627ed 100644 --- a/homeassistant/components/bond/translations/zh-Hant.json +++ b/homeassistant/components/bond/translations/zh-Hant.json @@ -9,13 +9,13 @@ "old_firmware": "Bond \u88dd\u7f6e\u4f7f\u7528\u4e0d\u652f\u63f4\u7684\u820a\u7248\u672c\u97cc\u9ad4 - \u8acb\u66f4\u65b0\u5f8c\u518d\u7e7c\u7e8c", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, - "flow_title": "Bond\uff1a{bond_id} ({host})", + "flow_title": "Bond\uff1a{name} ({host})", "step": { "confirm": { "data": { "access_token": "\u5b58\u53d6\u5bc6\u9470" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {bond_id}\uff1f" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 2f3a7313f75c78..7f85335d7bb375 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kon niet verbinden", "invalid_host": "Ongeldige hostnaam of IP-adres", "not_supported": "Apparaat wordt niet ondersteund", diff --git a/homeassistant/components/climacell/translations/af.json b/homeassistant/components/climacell/translations/af.json new file mode 100644 index 00000000000000..b62fc7023a47b1 --- /dev/null +++ b/homeassistant/components/climacell/translations/af.json @@ -0,0 +1,10 @@ +{ + "options": { + "step": { + "init": { + "title": "Update ClimaCell opties" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ca.json b/homeassistant/components/climacell/translations/ca.json new file mode 100644 index 00000000000000..23afb6a3d9057c --- /dev/null +++ b/homeassistant/components/climacell/translations/ca.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_api_key": "Clau API inv\u00e0lida", + "rate_limited": "Freq\u00fc\u00e8ncia limitada temporalment, torna-ho a provar m\u00e9s tard.", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nom" + }, + "description": "Si no es proporcionen la Latitud i Longitud, s'utilitzaran els valors per defecte de la configuraci\u00f3 de Home Assistant. Es crear\u00e0 una entitat per a cada tipus de previsi\u00f3, per\u00f2 nom\u00e9s s'habilitaran les que seleccionis." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipus de previsi\u00f3", + "timestep": "Minuts entre previsions NowCast" + }, + "description": "Si decideixes activar l'entitat de predicci\u00f3 \"nowcast\", podr\u00e0s configurar l'interval en minuts entre cada previsi\u00f3. El nombre de previsions proporcionades dep\u00e8n d'aquest interval de minuts.", + "title": "Actualitzaci\u00f3 de les opcions de ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/en.json b/homeassistant/components/climacell/translations/en.json new file mode 100644 index 00000000000000..ed3ead421e1e5b --- /dev/null +++ b/homeassistant/components/climacell/translations/en.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Failed to connect", + "invalid_api_key": "Invalid API key", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "description": "If Latitude and Longitude are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Forecast Type(s)", + "timestep": "Min. Between NowCast Forecasts" + }, + "description": "If you choose to enable the `nowcast` forecast entity, you can configure the number of minutes between each forecast. The number of forecasts provided depends on the number of minutes chosen between forecasts.", + "title": "Update ClimaCell Options" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/et.json b/homeassistant/components/climacell/translations/et.json new file mode 100644 index 00000000000000..3722c258afaa46 --- /dev/null +++ b/homeassistant/components/climacell/translations/et.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_api_key": "Vale API v\u00f5ti", + "rate_limited": "Hetkel on p\u00e4ringud piiratud, proovi hiljem uuesti.", + "unknown": "Tundmatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "name": "Nimi" + }, + "description": "Kui [%key:component::climacell::config::step::user::d ata::latitude%] ja [%key:component::climacell::config::step::user::d ata::longitude%] andmed pole sisestatud kasutatakse Home Assistanti vaikev\u00e4\u00e4rtusi. Olem luuakse iga prognoosit\u00fc\u00fcbi jaoks kuid vaikimisi lubatakse ainult need, mille valid." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Prognoosi t\u00fc\u00fcp (t\u00fc\u00fcbid)", + "timestep": "Minuteid NowCasti prognooside vahel" + }, + "description": "Kui otsustad lubada \"nowcast\" prognoosi\u00fcksuse, saad seadistada minutite arvu iga prognoosi vahel. Esitatavate prognooside arv s\u00f5ltub prognooside vahel valitud minutite arvust.", + "title": "V\u00e4rskenda ClimaCell suvandeid" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/fr.json b/homeassistant/components/climacell/translations/fr.json new file mode 100644 index 00000000000000..8fd3f7b71221a2 --- /dev/null +++ b/homeassistant/components/climacell/translations/fr.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_api_key": "Cl\u00e9 API invalide", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom" + }, + "description": "Si Latitude et Longitude ne sont pas fournis, les valeurs par d\u00e9faut de la configuration de Home Assistant seront utilis\u00e9es. Une entit\u00e9 sera cr\u00e9\u00e9e pour chaque type de pr\u00e9vision, mais seules celles que vous s\u00e9lectionnez seront activ\u00e9es par d\u00e9faut." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Type(s) de pr\u00e9vision", + "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": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/nl.json b/homeassistant/components/climacell/translations/nl.json new file mode 100644 index 00000000000000..488a43ae24ecd2 --- /dev/null +++ b/homeassistant/components/climacell/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_api_key": "Ongeldige API-sleutel", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "name": "Naam" + }, + "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype maar alleen degene die u selecteert worden standaard ingeschakeld." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Voorspellingstype(n)" + }, + "description": "Als u ervoor kiest om de `nowcast` voorspellingsentiteit in te schakelen, kan u het aantal minuten tussen elke voorspelling configureren. Het aantal voorspellingen hangt af van het aantal gekozen minuten tussen de voorspellingen.", + "title": "Update ClimaCell Opties" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json new file mode 100644 index 00000000000000..64845ff76978c0 --- /dev/null +++ b/homeassistant/components/climacell/translations/no.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "rate_limited": "Prisen er for \u00f8yeblikket begrenset. Pr\u00f8v igjen senere.", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "name": "Navn" + }, + "description": "Hvis Breddegrad and Lengdegrad er ikke gitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Prognosetype(r)", + "timestep": "Min. Mellom NowCast Prognoser" + }, + "description": "Hvis du velger \u00e5 aktivere \u00abnowcast\u00bb -varselenheten, kan du konfigurere antall minutter mellom hver prognose. Antall angitte prognoser avhenger av antall minutter som er valgt mellom prognosene.", + "title": "Oppdater ClimaCell Alternativer" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ru.json b/homeassistant/components/climacell/translations/ru.json new file mode 100644 index 00000000000000..2cce63d95ea03f --- /dev/null +++ b/homeassistant/components/climacell/translations/ru.json @@ -0,0 +1,34 @@ +{ + "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.", + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "rate_limited": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a, \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." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0415\u0441\u043b\u0438 \u0428\u0438\u0440\u043e\u0442\u0430 \u0438 \u0414\u043e\u043b\u0433\u043e\u0442\u0430 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 Home Assistant. \u041e\u0431\u044a\u0435\u043a\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u044b \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430, \u043d\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0412\u0430\u043c\u0438." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u0422\u0438\u043f(\u044b) \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430", + "timestep": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + }, + "description": "\u0415\u0441\u043b\u0438 \u0412\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 'nowcast', \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430.", + "title": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index 69d52436beb986..e4cf54eb365ff9 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -5,7 +5,9 @@ "cannot_connect": "Kon niet verbinden" }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "step": { "user": { diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json new file mode 100644 index 00000000000000..79aaec23123a61 --- /dev/null +++ b/homeassistant/components/enocean/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/en.json b/homeassistant/components/faa_delays/translations/en.json index 48e9e1c8993cf2..e78b15c68cb186 100644 --- a/homeassistant/components/faa_delays/translations/en.json +++ b/homeassistant/components/faa_delays/translations/en.json @@ -4,16 +4,18 @@ "already_configured": "This airport is already configured." }, "error": { - "invalid_airport": "Airport code is not valid" + "cannot_connect": "Failed to connect", + "invalid_airport": "Airport code is not valid", + "unknown": "Unexpected error" }, "step": { "user": { - "title": "FAA Delays", - "description": "Enter a US Airport Code in IATA Format", "data": { "id": "Airport" - } + }, + "description": "Enter a US Airport Code in IATA Format", + "title": "FAA Delays" } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/fr.json b/homeassistant/components/faa_delays/translations/fr.json new file mode 100644 index 00000000000000..996a22c842209c --- /dev/null +++ b/homeassistant/components/faa_delays/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cet a\u00e9roport est d\u00e9j\u00e0 configur\u00e9." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_airport": "Le code de l'a\u00e9roport n'est pas valide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "id": "A\u00e9roport" + }, + "description": "Entrez un code d'a\u00e9roport am\u00e9ricain au format IATA", + "title": "D\u00e9lais FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/nl.json b/homeassistant/components/faa_delays/translations/nl.json new file mode 100644 index 00000000000000..3dbc55f5b1b863 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Deze luchthaven is al geconfigureerd." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_airport": "Luchthavencode is ongeldig", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "id": "Luchthaven" + }, + "description": "Voer een Amerikaanse luchthavencode in IATA-indeling in", + "title": "FAA-vertragingen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/nl.json b/homeassistant/components/fireservicerota/translations/nl.json index 7289d53e71fb0f..3a6ba936dee1d1 100644 --- a/homeassistant/components/fireservicerota/translations/nl.json +++ b/homeassistant/components/fireservicerota/translations/nl.json @@ -14,7 +14,8 @@ "reauth": { "data": { "password": "Wachtwoord" - } + }, + "description": "Authenticatietokens zijn ongeldig geworden, log in om ze opnieuw te maken." }, "user": { "data": { diff --git a/homeassistant/components/fireservicerota/translations/no.json b/homeassistant/components/fireservicerota/translations/no.json index af1ceba2c97e90..be485577e65aea 100644 --- a/homeassistant/components/fireservicerota/translations/no.json +++ b/homeassistant/components/fireservicerota/translations/no.json @@ -15,7 +15,7 @@ "data": { "password": "Passord" }, - "description": "Godkjenningstokener ble ugyldige, logg inn for \u00e5 gjenopprette dem" + "description": "Autentiseringstokener ble ugyldige, logg inn for \u00e5 gjenskape dem." }, "user": { "data": { diff --git a/homeassistant/components/firmata/translations/nl.json b/homeassistant/components/firmata/translations/nl.json new file mode 100644 index 00000000000000..7cb0141826a8fb --- /dev/null +++ b/homeassistant/components/firmata/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Kan geen verbinding maken" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index c63a59e18e72bf..0ff044abc5e578 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." }, + "error": { + "unknown": "Onverwachte fout" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 71a80dbd577c46..9bfe2ef6be6ed3 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", + "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", "reauth_successful": "Herauthenticatie was succesvol" }, diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index 86958670d70af5..4d9b5a397ddd63 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "Host", "name": "Naam" }, "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router." diff --git a/homeassistant/components/home_connect/translations/nl.json b/homeassistant/components/home_connect/translations/nl.json index 41b27cc387fdca..25a812096074dd 100644 --- a/homeassistant/components/home_connect/translations/nl.json +++ b/homeassistant/components/home_connect/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie." + "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geverifieerd" diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index 0870b05a6d1665..dbd83622d8a7ab 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -19,7 +19,7 @@ "title": "Selecciona els dominis a incloure" }, "pairing": { - "description": "Tan aviat com {name} estigui llest, la vinculaci\u00f3 estar\u00e0 disponible a \"Notificacions\" com a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\".", + "description": "Per completar la vinculaci\u00f3, segueix les instruccions a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\" sota \"Notificacions\".", "title": "Vinculaci\u00f3 HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Dominis a incloure", "mode": "Mode" }, - "description": "La integraci\u00f3 HomeKit et permetr\u00e0 l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", - "title": "Activaci\u00f3 de HomeKit" + "description": "Selecciona els dominis a incloure. S'inclouran totes les entitats del domini compatibles. Es crear\u00e0 una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "title": "Selecciona els dominis a incloure" } } }, @@ -55,7 +55,7 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Selecciona les entitats a incloure" }, "init": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index e9aaeb60df8730..3b0129567c4ee3 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,13 +4,29 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entity" + }, + "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", + "title": "Select entity to be included" + }, + "bridge_mode": { + "data": { + "include_domains": "Domains to include" + }, + "description": "Choose the domains to be included. All supported entities in the domain will be included.", + "title": "Select domains to be included" + }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "include_domains": "Domains to include" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", + "mode": "Mode" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -21,7 +37,8 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 37bff5f9b70e2c..8c24d2d225180f 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -19,7 +19,7 @@ "title": "Vali kaasatavad domeenid" }, "pairing": { - "description": "Niipea kui {name} on valmis, on sidumine saadaval jaotises \"Notifications\" kui \"HomeKit Bridge Setup\".", + "description": "Sidumise l\u00f5puleviimiseks j\u00e4rgi jaotises \"HomeKiti sidumine\" toodud juhiseid alajaotises \"Teatised\".", "title": "HomeKiti sidumine" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Kaasatavad domeenid", "mode": "Re\u017eiim" }, - "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu. Parema tulemuse saavutamiseks ja ootamatute seadmete kadumise v\u00e4ltimiseks loo ja seo eraldi HomeKiti seade tarviku re\u017eiimis kga meediaesitaja ja kaamera jaoks.", - "title": "Aktiveeri HomeKit" + "description": "Vali kaasatavad domeenid. Kaasatakse k\u00f5ik domeenis toetatud olemid. Iga telemeedia pleieri ja kaamera jaoks luuakse eraldi HomeKiti eksemplar tarvikure\u017eiimis.", + "title": "Vali kaasatavad domeenid" } } }, @@ -55,12 +55,12 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", "title": "Vali kaasatavd olemid" }, "init": { "data": { - "include_domains": "Kaasatavad domeenid", + "include_domains": "Kaasatud domeenid", "mode": "Re\u017eiim" }, "description": "HomeKiti saab seadistada silla v\u00f5i \u00fche lisaseadme avaldamiseks. Lisare\u017eiimis saab kasutada ainult \u00fchte \u00fcksust. Teleriseadmete klassiga meediumipleierite n\u00f5uetekohaseks toimimiseks on vaja lisare\u017eiimi. \u201eKaasatavate domeenide\u201d \u00fcksused puutuvad kokku HomeKitiga. J\u00e4rgmisel ekraanil saad valida, millised \u00fcksused sellesse loendisse lisada v\u00f5i sellest v\u00e4lja j\u00e4tta.", diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index a0f10c9684a5a0..4721514e61520e 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -19,7 +19,7 @@ "title": "S\u00e9lectionnez les domaines \u00e0 inclure" }, "pairing": { - "description": "D\u00e8s que le pont {name} est pr\u00eat, l'appairage sera disponible dans \"Notifications\" sous \"Configuration de la Passerelle HomeKit\".", + "description": "Pour compl\u00e9ter l'appariement, suivez les instructions dans les \"Notifications\" sous \"Appariement HomeKit\".", "title": "Appairage de la Passerelle Homekit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domaines \u00e0 inclure", "mode": "Mode" }, - "description": "La passerelle HomeKit vous permettra d'acc\u00e9der \u00e0 vos entit\u00e9s Home Assistant dans HomeKit. Les passerelles HomeKit sont limit\u00e9es \u00e0 150 accessoires par instance, y compris la passerelle elle-m\u00eame. Si vous souhaitez connecter plus que le nombre maximum d'accessoires, il est recommand\u00e9 d'utiliser plusieurs passerelles HomeKit pour diff\u00e9rents domaines. La configuration d\u00e9taill\u00e9e des entit\u00e9s est uniquement disponible via YAML pour la passerelle principale.", - "title": "Activer la Passerelle HomeKit" + "description": "Choisissez les domaines \u00e0 inclure. Toutes les entit\u00e9s prises en charge dans le domaine seront incluses. Une instance HomeKit distincte en mode accessoire sera cr\u00e9\u00e9e pour chaque lecteur multim\u00e9dia TV et cam\u00e9ra.", + "title": "S\u00e9lectionnez les domaines \u00e0 inclure" } } }, @@ -60,7 +60,7 @@ }, "init": { "data": { - "include_domains": "Domaine \u00e0 inclure", + "include_domains": "Domaines \u00e0 inclure", "mode": "Mode" }, "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.", diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 9a64def41569d2..4748fe63af204e 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -19,7 +19,7 @@ "title": "Velg domener som skal inkluderes" }, "pairing": { - "description": "S\u00e5 snart {name} er klart, vil sammenkobling v\u00e6re tilgjengelig i \"Notifications\" som \"HomeKit Bridge Setup\".", + "description": "For \u00e5 fullf\u00f8re sammenkoblingen ved \u00e5 f\u00f8lge instruksjonene i \"Varsler\" under \"Sammenkobling av HomeKit\".", "title": "Koble sammen HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domener \u00e5 inkludere", "mode": "Modus" }, - "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-enhetene dine i HomeKit. I bromodus er HomeKit Bridges begrenset til 150 tilbeh\u00f8r per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 bygge bro over maksimalt antall tilbeh\u00f8r, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert enhetskonfigurasjon er bare tilgjengelig via YAML. For best ytelse og for \u00e5 forhindre uventet utilgjengelighet, opprett og par sammen en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", - "title": "Aktiver HomeKit" + "description": "Velg domenene som skal inkluderes. Alle st\u00f8ttede enheter i domenet vil bli inkludert. Det opprettes en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", + "title": "Velg domener som skal inkluderes" } } }, @@ -55,7 +55,7 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare \u00e9n enkelt enhet inkludert. I bridge include-modus inkluderes alle enheter i domenet med mindre bestemte enheter er valgt. I brounnlatingsmodus inkluderes alle enheter i domenet, med unntak av de utelatte enhetene. For best mulig ytelse, og for \u00e5 forhindre uventet utilgjengelighet, opprett og par en separat HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediespiller og kamera.", + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse vil et eget HomeKit-tilbeh\u00f8r v\u00e6re TV-mediaspiller og kamera.", "title": "Velg enheter som skal inkluderes" }, "init": { diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 2679a4de20afb1..ef35ff667c4107 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -19,7 +19,7 @@ "title": "Wybierz uwzgl\u0119dniane domeny" }, "pairing": { - "description": "Gdy tylko {name} b\u0119dzie gotowy, opcja parowania b\u0119dzie dost\u0119pna w \u201ePowiadomieniach\u201d jako \u201eKonfiguracja mostka HomeKit\u201d.", + "description": "Aby doko\u0144czy\u0107 parowanie, post\u0119puj wg instrukcji \u201eParowanie HomeKit\u201d w \u201ePowiadomieniach\u201d.", "title": "Parowanie z HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domeny do uwzgl\u0119dnienia", "mode": "Tryb" }, - "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", - "title": "Aktywacja HomeKit" + "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 oraz kamery.", + "title": "Wybierz uwzgl\u0119dniane domeny" } } }, @@ -55,7 +55,7 @@ "entities": "Encje", "mode": "Tryb" }, - "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", + "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci, zostanie utworzone oddzielne akcesorium HomeKit dla ka\u017cdego tv media playera oraz kamery.", "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione" }, "init": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 84346aed2ef66c..d00744e4cb4419 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -19,7 +19,7 @@ "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "pairing": { - "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e, \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u0445\" \u043a\u0430\u043a \"HomeKit Bridge Setup\".", + "description": "\u0414\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f \u0441\u043b\u0435\u0434\u0443\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0432 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0438 \"HomeKit Pairing\".", "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", - "title": "HomeKit" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u044b. \u0411\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438\u0437 \u0434\u043e\u043c\u0435\u043d\u0430. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430.", + "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" } } }, @@ -55,7 +55,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index 605263c4489728..95a0782cf1283f 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -19,7 +19,7 @@ "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" }, "pairing": { - "description": "\u65bc {name} \u5c31\u7dd2\u5f8c\u3001\u5c07\u6703\u65bc\u300c\u901a\u77e5\u300d\u4e2d\u986f\u793a\u300cHomeKit Bridge \u8a2d\u5b9a\u300d\u7684\u914d\u5c0d\u8cc7\u8a0a\u3002", + "description": "\u6b32\u5b8c\u6210\u914d\u5c0d\u3001\u8acb\u8ddf\u96a8\u300c\u901a\u77e5\u300d\u5167\u7684\u300cHomekit \u914d\u5c0d\u300d\u6307\u5f15\u3002", "title": "\u914d\u5c0d HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "\u5305\u542b\u7db2\u57df", "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c\u7db2\u57df\u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", - "title": "\u555f\u7528 HomeKit" + "description": "\u9078\u64c7\u6240\u8981\u5305\u542b\u7684\u7db2\u57df\uff0c\u6240\u6709\u8a72\u7db2\u57df\u5167\u652f\u63f4\u7684\u5be6\u9ad4\u90fd\u5c07\u6703\u88ab\u5305\u542b\u3002 \u5176\u4ed6 Homekit \u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\u5be6\u4f8b\uff0c\u5c07\u6703\u4ee5\u914d\u4ef6\u6a21\u5f0f\u65b0\u589e\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" } } }, @@ -55,7 +55,7 @@ "entities": "\u5be6\u9ad4", "mode": "\u6a21\u5f0f" }, - "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u5c07\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u9032\u884c\u3002", "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" }, "init": { diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index dd51fdc1bc5edb..d420093996c768 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -19,6 +19,7 @@ "user": { "data": { "password": "Wachtwoord", + "url": "URL", "username": "Gebruikersnaam" }, "description": "Voer de toegangsgegevens van het apparaat in. Opgeven van gebruikersnaam en wachtwoord is optioneel, maar biedt ondersteuning voor meer integratiefuncties. Aan de andere kant kan het gebruik van een geautoriseerde verbinding problemen veroorzaken bij het openen van het webinterface van het apparaat buiten de Home Assitant, terwijl de integratie actief is en andersom.", diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index 97673069054f1d..b150c8d5b16fac 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Account reeds geconfigureerd", - "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd" + "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -14,7 +15,8 @@ "data": { "password": "Wachtwoord" }, - "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken." + "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken.", + "title": "Verifieer de integratie opnieuw" }, "trusted_device": { "data": { diff --git a/homeassistant/components/ifttt/translations/nl.json b/homeassistant/components/ifttt/translations/nl.json index e7da47dd658b76..82006860db3adc 100644 --- a/homeassistant/components/ifttt/translations/nl.json +++ b/homeassistant/components/ifttt/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u de actie \"Een webverzoek doen\" gebruiken vanuit de [IFTTT Webhook-applet]({applet_url}). \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [the documentation]({docs_url}) voor informatie over het configureren van automatiseringen om inkomende gegevens te verwerken." diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index d2f73fca37bb74..e4f7d4a8102f70 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -28,6 +28,9 @@ "title": "Insteon Hub versie 2" }, "plm": { + "data": { + "device": "USB-apparaatpad" + }, "description": "Configureer de Insteon PowerLink Modem (PLM).", "title": "Insteon PLM" }, diff --git a/homeassistant/components/keenetic_ndms2/translations/nl.json b/homeassistant/components/keenetic_ndms2/translations/nl.json index f422e2641f6cbf..c3c08575052fe1 100644 --- a/homeassistant/components/keenetic_ndms2/translations/nl.json +++ b/homeassistant/components/keenetic_ndms2/translations/nl.json @@ -21,7 +21,8 @@ "step": { "user": { "data": { - "interfaces": "Kies interfaces om te scannen" + "interfaces": "Kies interfaces om te scannen", + "scan_interval": "Scaninterval" } } } diff --git a/homeassistant/components/kmtronic/translations/fr.json b/homeassistant/components/kmtronic/translations/fr.json new file mode 100644 index 00000000000000..45620fe7795250 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pl.json b/homeassistant/components/kmtronic/translations/pl.json new file mode 100644 index 00000000000000..25dab56796cc15 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/pl.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ca.json b/homeassistant/components/litejet/translations/ca.json new file mode 100644 index 00000000000000..39e2a56dc4dd18 --- /dev/null +++ b/homeassistant/components/litejet/translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "open_failed": "No s'ha pogut obrir el port s\u00e8rie especificat." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connecta el port RS232-2 LiteJet a l'ordinador i introdueix la ruta al port s\u00e8rie del dispositiu.\n\nEl LiteJet MCP ha d'estar configurat amb una velocitat de 19.2 k baudis, 8 bits de dades, 1 bit de parada, sense paritat i ha de transmetre un 'CR' despr\u00e9s de cada resposta.", + "title": "Connexi\u00f3 amb LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/fr.json b/homeassistant/components/litejet/translations/fr.json new file mode 100644 index 00000000000000..455ba7fdc0c569 --- /dev/null +++ b/homeassistant/components/litejet/translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connectez le port RS232-2 du LiteJet \u00e0 votre ordinateur et entrez le chemin d'acc\u00e8s au p\u00e9riph\u00e9rique de port s\u00e9rie. \n\n Le LiteJet MCP doit \u00eatre configur\u00e9 pour 19,2 K bauds, 8 bits de donn\u00e9es, 1 bit d'arr\u00eat, sans parit\u00e9 et pour transmettre un \u00abCR\u00bb apr\u00e8s chaque r\u00e9ponse.", + "title": "Connectez-vous \u00e0 LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/no.json b/homeassistant/components/litejet/translations/no.json new file mode 100644 index 00000000000000..26ccd3335468b5 --- /dev/null +++ b/homeassistant/components/litejet/translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "open_failed": "Kan ikke \u00e5pne den angitte serielle porten." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Koble LiteJets RS232-2-port til datamaskinen og skriv stien til den serielle portenheten. \n\n LiteJet MCP m\u00e5 konfigureres for 19,2 K baud, 8 databiter, 1 stoppbit, ingen paritet, og for \u00e5 overf\u00f8re en 'CR' etter hvert svar.", + "title": "Koble til LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/pl.json b/homeassistant/components/litejet/translations/pl.json new file mode 100644 index 00000000000000..20e5d68288d89a --- /dev/null +++ b/homeassistant/components/litejet/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "open_failed": "Nie mo\u017cna otworzy\u0107 okre\u015blonego portu szeregowego." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Pod\u0142\u0105cz port RS232-2 LiteJet do komputera i wprowad\u017a \u015bcie\u017ck\u0119 do urz\u0105dzenia portu szeregowego. \n\nLiteJet MCP musi by\u0107 skonfigurowany dla szybko\u015bci 19,2K, 8 bit\u00f3w danych, 1 bit stopu, brak parzysto\u015bci i przesy\u0142anie \u201eCR\u201d po ka\u017cdej odpowiedzi.", + "title": "Po\u0142\u0105czenie z LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ru.json b/homeassistant/components/litejet/translations/ru.json new file mode 100644 index 00000000000000..c90e6956301436 --- /dev/null +++ b/homeassistant/components/litejet/translations/ru.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "open_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442." + }, + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u043f\u043e\u0440\u0442 RS232-2 LiteJet \u043a \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0443, \u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443. \n\nLiteJet MCP \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c 19,2 \u041a\u0431\u043e\u0434, 8 \u0431\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0445, 1 \u0441\u0442\u043e\u043f\u043e\u0432\u044b\u0439 \u0431\u0438\u0442, \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438 \u0438 \u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0443 'CR' \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/tr.json b/homeassistant/components/litejet/translations/tr.json new file mode 100644 index 00000000000000..de4ea12cb6f360 --- /dev/null +++ b/homeassistant/components/litejet/translations/tr.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "LiteJet'e Ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/zh-Hant.json b/homeassistant/components/litejet/translations/zh-Hant.json new file mode 100644 index 00000000000000..8a268f3db494b5 --- /dev/null +++ b/homeassistant/components/litejet/translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "error": { + "open_failed": "\u7121\u6cd5\u958b\u555f\u6307\u5b9a\u7684\u5e8f\u5217\u57e0" + }, + "step": { + "user": { + "data": { + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u9023\u7dda\u81f3\u96fb\u8166\u4e0a\u7684 LiteJet RS232-2 \u57e0\uff0c\u4e26\u8f38\u5165\u5e8f\u5217\u57e0\u88dd\u7f6e\u7684\u8def\u5f91\u3002\n\nLiteJet MCP \u5fc5\u9808\u8a2d\u5b9a\u70ba\u901a\u8a0a\u901f\u7387 19.2 K baud\u30018 \u6578\u64da\u4f4d\u5143\u30011 \u505c\u6b62\u4f4d\u5143\u3001\u7121\u540c\u4f4d\u4f4d\u5143\u4e26\u65bc\u6bcf\u500b\u56de\u5fa9\u5f8c\u50b3\u9001 'CR'\u3002", + "title": "\u9023\u7dda\u81f3 LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/fr.json b/homeassistant/components/litterrobot/translations/fr.json new file mode 100644 index 00000000000000..aa84ec33d8cdad --- /dev/null +++ b/homeassistant/components/litterrobot/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/no.json b/homeassistant/components/litterrobot/translations/no.json new file mode 100644 index 00000000000000..4ea7b2401c30f5 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/pl.json b/homeassistant/components/litterrobot/translations/pl.json new file mode 100644 index 00000000000000..8a08a06c69904c --- /dev/null +++ b/homeassistant/components/litterrobot/translations/pl.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index e02378432abaa5..16cbbc77277af2 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in de Locative app. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." diff --git a/homeassistant/components/mailgun/translations/nl.json b/homeassistant/components/mailgun/translations/nl.json index 772a67c118e6df..dea33946af51f6 100644 --- a/homeassistant/components/mailgun/translations/nl.json +++ b/homeassistant/components/mailgun/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar Home Assistant te verzenden, moet u [Webhooks with Mailgun]({mailgun_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhoudstype: application/json \n\n Zie [de documentatie]({docs_url}) voor informatie over het configureren van automatiseringen om binnenkomende gegevens te verwerken." diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index 86f1e656e519dd..3198bfb4192e5f 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -25,5 +25,6 @@ } } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ca.json b/homeassistant/components/mullvad/translations/ca.json new file mode 100644 index 00000000000000..f81781cbc0fa43 --- /dev/null +++ b/homeassistant/components/mullvad/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Vols configurar la integraci\u00f3 Mullvad VPN?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/en.json b/homeassistant/components/mullvad/translations/en.json index 45664554aed74f..fcfa89ef0829ee 100644 --- a/homeassistant/components/mullvad/translations/en.json +++ b/homeassistant/components/mullvad/translations/en.json @@ -5,10 +5,16 @@ }, "error": { "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "step": { "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + }, "description": "Set up the Mullvad VPN integration?" } } diff --git a/homeassistant/components/mullvad/translations/et.json b/homeassistant/components/mullvad/translations/et.json new file mode 100644 index 00000000000000..671d18a2cd344c --- /dev/null +++ b/homeassistant/components/mullvad/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendumine nurjus", + "invalid_auth": "Tuvastamise viga", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Kas seadistada Mullvad VPN sidumine?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/fr.json b/homeassistant/components/mullvad/translations/fr.json new file mode 100644 index 00000000000000..1a8b10de809cfd --- /dev/null +++ b/homeassistant/components/mullvad/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Configurez l'int\u00e9gration VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/nl.json b/homeassistant/components/mullvad/translations/nl.json new file mode 100644 index 00000000000000..aa4d80ac71dd67 --- /dev/null +++ b/homeassistant/components/mullvad/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "De Mullvad VPN-integratie instellen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/no.json b/homeassistant/components/mullvad/translations/no.json new file mode 100644 index 00000000000000..d33f26404452be --- /dev/null +++ b/homeassistant/components/mullvad/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Sette opp Mullvad VPN-integrasjon?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ru.json b/homeassistant/components/mullvad/translations/ru.json new file mode 100644 index 00000000000000..ff34530e4a9b48 --- /dev/null +++ b/homeassistant/components/mullvad/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "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", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Mullvad VPN." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/tr.json b/homeassistant/components/mullvad/translations/tr.json new file mode 100644 index 00000000000000..0f3ddabfc4f44b --- /dev/null +++ b/homeassistant/components/mullvad/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/et.json b/homeassistant/components/netatmo/translations/et.json index 99e062b38422b6..8725eb48016a13 100644 --- a/homeassistant/components/netatmo/translations/et.json +++ b/homeassistant/components/netatmo/translations/et.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "eemal", + "hg": "k\u00fclmumiskaitse", + "schedule": "ajastus" + }, + "trigger_type": { + "alarm_started": "{entity_name} tuvastas h\u00e4ire", + "animal": "{entity_name} tuvastas looma", + "cancel_set_point": "{entity_name} on oma ajakava j\u00e4tkanud", + "human": "{entity_name} tuvastas inimese", + "movement": "{entity_name} tuvastas liikumise", + "outdoor": "{entity_name} tuvastas v\u00e4lise s\u00fcndmuse", + "person": "{entity_name} tuvastas isiku", + "person_away": "{entity_name} tuvastas inimese eemaldumise", + "set_point": "Sihttemperatuur {entity_name} on k\u00e4sitsi m\u00e4\u00e4ratud", + "therm_mode": "{entity_name} l\u00fclitus olekusse {subtype}.", + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse", + "vehicle": "{entity_name} tuvastas s\u00f5iduki" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/fr.json b/homeassistant/components/netatmo/translations/fr.json index fe8fc74d273473..6c294d467abb6c 100644 --- a/homeassistant/components/netatmo/translations/fr.json +++ b/homeassistant/components/netatmo/translations/fr.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "absent", + "hg": "garde-gel", + "schedule": "horaire" + }, + "trigger_type": { + "alarm_started": "{entity_name} a d\u00e9tect\u00e9 une alarme", + "animal": "{entity_name} a d\u00e9tect\u00e9 un animal", + "cancel_set_point": "{entity_name} a repris son programme", + "human": "{entity_name} a d\u00e9tect\u00e9 une personne", + "movement": "{entity_name} a d\u00e9tect\u00e9 un mouvement", + "outdoor": "{entity_name} a d\u00e9tect\u00e9 un \u00e9v\u00e9nement ext\u00e9rieur", + "person": "{entity_name} a d\u00e9tect\u00e9 une personne", + "person_away": "{entity_name} a d\u00e9tect\u00e9 qu\u2019une personne est partie", + "set_point": "Temp\u00e9rature cible {entity_name} d\u00e9finie manuellement", + "therm_mode": "{entity_name} est pass\u00e9 \u00e0 \"{subtype}\"", + "turned_off": "{entity_name} d\u00e9sactiv\u00e9", + "turned_on": "{entity_name} activ\u00e9", + "vehicle": "{entity_name} a d\u00e9tect\u00e9 un v\u00e9hicule" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index eab1d9741ad136..431f105df3d8c8 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "authorize_url_timeout": "Time-out genereren autorisatie-URL.", - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie." + "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geauthenticeerd met Netatmo." @@ -13,6 +14,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "afwezig", + "hg": "vorstbescherming", + "schedule": "schema" + }, + "trigger_type": { + "alarm_started": "{entity_name} heeft een alarm gedetecteerd", + "animal": "{entity_name} heeft een dier gedetecteerd", + "cancel_set_point": "{entity_name} heeft zijn schema hervat", + "human": "{entity_name} heeft een mens gedetecteerd", + "movement": "{entity_name} heeft beweging gedetecteerd", + "outdoor": "{entity_name} heeft een buitengebeurtenis gedetecteerd", + "person": "{entity_name} heeft een persoon gedetecteerd", + "person_away": "{entity_name} heeft gedetecteerd dat een persoon is vertrokken", + "set_point": "Doeltemperatuur {entity_name} handmatig ingesteld", + "therm_mode": "{entity_name} is overgeschakeld naar \"{subtype}\"", + "turned_off": "{entity_name} uitgeschakeld", + "turned_on": "{entity_name} ingeschakeld", + "vehicle": "{entity_name} heeft een voertuig gedetecteerd" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/ru.json b/homeassistant/components/netatmo/translations/ru.json index c9be7e60825004..b25e0843967487 100644 --- a/homeassistant/components/netatmo/translations/ru.json +++ b/homeassistant/components/netatmo/translations/ru.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\u043d\u0435 \u0434\u043e\u043c\u0430", + "hg": "\u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u0438\u044f", + "schedule": "\u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0440\u0435\u0432\u043e\u0433\u0443", + "animal": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0436\u0438\u0432\u043e\u0442\u043d\u043e\u0435", + "cancel_set_point": "{entity_name} \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0441\u0432\u043e\u0435 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435", + "human": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430", + "movement": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435", + "outdoor": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u043e\u0437\u0434\u0443\u0445\u0435", + "person": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0441\u043e\u043d\u0443", + "person_away": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430 \u0443\u0448\u043b\u0430", + "set_point": "\u0426\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 {entity_name} \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430 \u0432\u0440\u0443\u0447\u043d\u0443\u044e", + "therm_mode": "{entity_name} \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \"{subtype}\"", + "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "vehicle": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u043e\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/tr.json b/homeassistant/components/netatmo/translations/tr.json index 94dd5b3fb0f4c6..69646be2292b1b 100644 --- a/homeassistant/components/netatmo/translations/tr.json +++ b/homeassistant/components/netatmo/translations/tr.json @@ -4,6 +4,22 @@ "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." } }, + "device_automation": { + "trigger_subtype": { + "away": "uzakta", + "hg": "donma korumas\u0131", + "schedule": "Zamanlama" + }, + "trigger_type": { + "alarm_started": "{entity_name} bir alarm alg\u0131lad\u0131", + "animal": "{entity_name} bir hayvan tespit etti", + "cancel_set_point": "{entity_name} zamanlamas\u0131na devam etti", + "human": "{entity_name} bir insan alg\u0131lad\u0131", + "movement": "{entity_name} hareket alg\u0131lad\u0131", + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/nightscout/translations/nl.json b/homeassistant/components/nightscout/translations/nl.json index 208299fd4427ff..0146996dce586d 100644 --- a/homeassistant/components/nightscout/translations/nl.json +++ b/homeassistant/components/nightscout/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/nzbget/translations/nl.json b/homeassistant/components/nzbget/translations/nl.json index f5f1bfd39ed9f1..89d58d14292ae5 100644 --- a/homeassistant/components/nzbget/translations/nl.json +++ b/homeassistant/components/nzbget/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "Host", "name": "Naam", "password": "Wachtwoord", "port": "Poort", diff --git a/homeassistant/components/plum_lightpad/translations/nl.json b/homeassistant/components/plum_lightpad/translations/nl.json index 7f0f85b7326aa7..8410cabbbb92c1 100644 --- a/homeassistant/components/plum_lightpad/translations/nl.json +++ b/homeassistant/components/plum_lightpad/translations/nl.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "E-mail" } } } diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 46fc915d7bd4a6..38ef34d5afc9c8 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -11,7 +11,8 @@ "data": { "email": "E-mail", "password": "Wachtwoord" - } + }, + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/fr.json b/homeassistant/components/rituals_perfume_genie/translations/fr.json new file mode 100644 index 00000000000000..2a1fb9c8bb8a89 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Mot de passe" + }, + "title": "Connectez-vous \u00e0 votre compte Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json index 72f9ff82ba4c9f..8c15279dca8c8f 100644 --- a/homeassistant/components/rpi_power/translations/nl.json +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + }, + "step": { + "confirm": { + "description": "Wil je beginnen met instellen?" + } } }, "title": "Raspberry Pi Voeding Checker" diff --git a/homeassistant/components/ruckus_unleashed/translations/nl.json b/homeassistant/components/ruckus_unleashed/translations/nl.json index 0569c39321a24b..8ad15260b0de5c 100644 --- a/homeassistant/components/ruckus_unleashed/translations/nl.json +++ b/homeassistant/components/ruckus_unleashed/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/sharkiq/translations/nl.json b/homeassistant/components/sharkiq/translations/nl.json index 96c10f3e2f0812..3acfdbdf0747cd 100644 --- a/homeassistant/components/sharkiq/translations/nl.json +++ b/homeassistant/components/sharkiq/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", + "reauth_successful": "Herauthenticatie was succesvol", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index ebcc16dafacfbc..10a4fe2efab23e 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "cannot_connect": "Kan geen verbinding maken", "invalid_mdns": "Niet-ondersteund apparaat voor de Smappee-integratie.", - "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "step": { "local": { diff --git a/homeassistant/components/sms/translations/nl.json b/homeassistant/components/sms/translations/nl.json index 75dd593982a24e..ddcc54d239f50b 100644 --- a/homeassistant/components/sms/translations/nl.json +++ b/homeassistant/components/sms/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { "cannot_connect": "Kon niet verbinden", diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index 423dbb6a2bb8e9..b7f077f2c7342f 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "missing_configuration": "Het Somfy-component is niet geconfigureerd. Gelieve de documentatie te volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { diff --git a/homeassistant/components/speedtestdotnet/translations/nl.json b/homeassistant/components/speedtestdotnet/translations/nl.json index 0c0c184b5fe582..1fe99195f7a0a0 100644 --- a/homeassistant/components/speedtestdotnet/translations/nl.json +++ b/homeassistant/components/speedtestdotnet/translations/nl.json @@ -3,6 +3,11 @@ "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "wrong_server_id": "Server-ID is niet geldig" + }, + "step": { + "user": { + "description": "Wil je beginnen met instellen?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json index f0b4ddf59a9ea8..bc7683ac0a4165 100644 --- a/homeassistant/components/spider/translations/nl.json +++ b/homeassistant/components/spider/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json new file mode 100644 index 00000000000000..a6bf6902aabd11 --- /dev/null +++ b/homeassistant/components/subaru/translations/fr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion" + }, + "error": { + "bad_pin_format": "Le code PIN doit \u00eatre compos\u00e9 de 4 chiffres", + "cannot_connect": "\u00c9chec de connexion", + "incorrect_pin": "PIN incorrect", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Veuillez entrer votre NIP MySubaru\nREMARQUE : Tous les v\u00e9hicules en compte doivent avoir le m\u00eame NIP", + "title": "Configuration de Subaru Starlink" + }, + "user": { + "data": { + "country": "Choisissez le pays", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index 349b4b2818e69c..b1beb4058bc9d3 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -10,7 +10,8 @@ "step": { "user": { "data": { - "name": "Naam" + "name": "Naam", + "url": "Webinterface URL" } } } diff --git a/homeassistant/components/tile/translations/nl.json b/homeassistant/components/tile/translations/nl.json index c160ac631ee0dd..236d250122a689 100644 --- a/homeassistant/components/tile/translations/nl.json +++ b/homeassistant/components/tile/translations/nl.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "E-mail" }, "title": "Tegel configureren" } diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index cf77b94d025e88..a0cda915172de3 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -3,6 +3,8 @@ "abort": { "already_configured": "De geselecteerde overeenkomst is al geconfigureerd.", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie-URL.", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_agreements": "Dit account heeft geen Toon schermen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index 6526d0a98001c4..40ca767b4acbec 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "invalid_auth": "Authentification invalide" }, "step": { + "locations": { + "data": { + "location": "Emplacement" + } + }, + "reauth_confirm": { + "title": "R\u00e9-authentifier l'int\u00e9gration" + }, "user": { "data": { "password": "Mot de passe", diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 1f4fb5490d1408..94d8e3ac01ee9a 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -8,6 +8,11 @@ "invalid_auth": "Ongeldige authenticatie" }, "step": { + "locations": { + "data": { + "location": "Locatie" + } + }, "reauth_confirm": { "title": "Verifieer de integratie opnieuw" }, diff --git a/homeassistant/components/totalconnect/translations/no.json b/homeassistant/components/totalconnect/translations/no.json index e80f86696fcd3f..9c98d6ad1e7155 100644 --- a/homeassistant/components/totalconnect/translations/no.json +++ b/homeassistant/components/totalconnect/translations/no.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { - "invalid_auth": "Ugyldig godkjenning" + "invalid_auth": "Ugyldig godkjenning", + "usercode": "Brukerkode er ikke gyldig for denne brukeren p\u00e5 dette stedet" }, "step": { + "locations": { + "data": { + "location": "Plassering" + }, + "description": "Angi brukerkoden for denne brukeren p\u00e5 denne plasseringen", + "title": "Brukerkoder for plassering" + }, + "reauth_confirm": { + "description": "Total Connect m\u00e5 godkjenne kontoen din p\u00e5 nytt", + "title": "Godkjenne integrering p\u00e5 nytt" + }, "user": { "data": { "password": "Passord", diff --git a/homeassistant/components/totalconnect/translations/pl.json b/homeassistant/components/totalconnect/translations/pl.json index 530d632040ce35..ff2ca2351e6d25 100644 --- a/homeassistant/components/totalconnect/translations/pl.json +++ b/homeassistant/components/totalconnect/translations/pl.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane" + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie" + "invalid_auth": "Niepoprawne uwierzytelnienie", + "usercode": "Nieprawid\u0142owy kod u\u017cytkownika dla u\u017cytkownika w tej lokalizacji" }, "step": { + "locations": { + "data": { + "location": "Lokalizacja" + }, + "description": "Wprowad\u017a kod u\u017cytkownika dla u\u017cytkownika w tej lokalizacji", + "title": "Kody lokalizacji u\u017cytkownika" + }, + "reauth_confirm": { + "description": "Integracja Total Connect wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, "user": { "data": { "password": "Has\u0142o", diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 3375cac31d510b..0a6cf433d87740 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "IP adresse", + "model": "Enhetsmodell (valgfritt)", "name": "Navnet p\u00e5 enheten", "token": "API-token" }, diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 50eb4d16887816..80528b71370146 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adres IP", + "model": "Model urz\u0105dzenia (opcjonalnie)", "name": "Nazwa urz\u0105dzenia", "token": "Token API" }, diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json index 6806b5072c1aaf..731c0bbcea8535 100644 --- a/homeassistant/components/zwave_js/translations/ca.json +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -6,6 +6,7 @@ "addon_install_failed": "No s'ha pogut instal\u00b7lar el complement Z-Wave JS.", "addon_missing_discovery_info": "Falta la informaci\u00f3 de descobriment del complement Z-Wave JS.", "addon_set_config_failed": "No s'ha pogut establir la configuraci\u00f3 de Z-Wave JS.", + "addon_start_failed": "No s'ha pogut iniciar el complement Z-Wave JS.", "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "cannot_connect": "Ha fallat la connexi\u00f3" @@ -17,7 +18,8 @@ "unknown": "Error inesperat" }, "progress": { - "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts." + "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts.", + "start_addon": "Espera mentre es completa la inicialitzaci\u00f3 del complement Z-Wave JS. Pot tardar uns segons." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Vols utilitzar el complement Supervisor de Z-Wave JS?", "title": "Selecciona el m\u00e8tode de connexi\u00f3" }, + "start_addon": { + "title": "El complement Z-Wave JS s'est\u00e0 iniciant." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json index d51507b616f0b7..4c68e63530ff82 100644 --- a/homeassistant/components/zwave_js/translations/et.json +++ b/homeassistant/components/zwave_js/translations/et.json @@ -6,6 +6,7 @@ "addon_install_failed": "Z-Wave JS lisandmooduli paigaldamine nurjus.", "addon_missing_discovery_info": "Z-Wave JS lisandmooduli tuvastusteave puudub.", "addon_set_config_failed": "Z-Wave JS konfiguratsiooni m\u00e4\u00e4ramine nurjus.", + "addon_start_failed": "Z-Wave JS-i lisandmooduli k\u00e4ivitamine nurjus.", "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "cannot_connect": "\u00dchendamine nurjus" @@ -17,7 +18,8 @@ "unknown": "Ootamatu t\u00f5rge" }, "progress": { - "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit." + "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit.", + "start_addon": "Palun oota kuni Z-Wave JS lisandmooduli ak\u00e4ivitumine l\u00f5ppeb. See v\u00f5ib v\u00f5tta m\u00f5ned sekundid." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Kas soovid kasutada Z-Wave JSi halduri lisandmoodulit?", "title": "Vali \u00fchendusviis" }, + "start_addon": { + "title": "Z-Wave JS lisandmoodul k\u00e4ivitub." + }, "user": { "data": { "url": "" diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 040e3997ac17ea..9cc8bf822b87d4 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -6,6 +6,7 @@ "addon_install_failed": "\u00c9chec de l'installation du module compl\u00e9mentaire Z-Wave JS.", "addon_missing_discovery_info": "Informations manquantes sur la d\u00e9couverte du module compl\u00e9mentaire Z-Wave JS.", "addon_set_config_failed": "\u00c9chec de la d\u00e9finition de la configuration Z-Wave JS.", + "addon_start_failed": "\u00c9chec du d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS.", "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "\u00c9chec de la connexion " @@ -17,7 +18,8 @@ "unknown": "Erreur inattendue" }, "progress": { - "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes." + "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes.", + "start_addon": "Veuillez patienter pendant le d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre quelques secondes." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, + "start_addon": { + "title": "Le module compl\u00e9mentaire Z-Wave JS est d\u00e9marr\u00e9." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index b724fb34e48385..acd049fc5619a0 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -6,6 +6,7 @@ "addon_install_failed": "Kunne ikke installere Z-Wave JS-tillegg", "addon_missing_discovery_info": "Manglende oppdagelsesinformasjon for Z-Wave JS-tillegg", "addon_set_config_failed": "Kunne ikke angi Z-Wave JS-konfigurasjon", + "addon_start_failed": "Kunne ikke starte Z-Wave JS-tillegget.", "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Tilkobling mislyktes" @@ -17,7 +18,8 @@ "unknown": "Uventet feil" }, "progress": { - "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter." + "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter.", + "start_addon": "Vent mens Z-Wave JS-tilleggsstarten er fullf\u00f8rt. Dette kan ta noen sekunder." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Vil du bruke Z-Wave JS Supervisor-tillegg?", "title": "Velg tilkoblingsmetode" }, + "start_addon": { + "title": "Z-Wave JS-tillegget starter." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json index b139b0dacc6657..2bfd994132b7ab 100644 --- a/homeassistant/components/zwave_js/translations/pl.json +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -6,6 +6,7 @@ "addon_install_failed": "Nie uda\u0142o si\u0119 zainstalowa\u0107 dodatku Z-Wave JS", "addon_missing_discovery_info": "Brak informacji wykrywania dodatku Z-Wave JS", "addon_set_config_failed": "Nie uda\u0142o si\u0119 skonfigurowa\u0107 Z-Wave JS", + "addon_start_failed": "Nie uda\u0142o si\u0119 uruchomi\u0107 dodatku Z-Wave JS.", "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" @@ -17,7 +18,8 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "progress": { - "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut." + "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut.", + "start_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 uruchamianie dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 chwil\u0119." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Czy chcesz skorzysta\u0107 z dodatku Z-Wave JS Supervisor?", "title": "Wybierz metod\u0119 po\u0142\u0105czenia" }, + "start_addon": { + "title": "Dodatek Z-Wave JS uruchamia si\u0119." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json index 5b7e5f470175f7..1a65ce3ea71184 100644 --- a/homeassistant/components/zwave_js/translations/ru.json +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -6,6 +6,7 @@ "addon_install_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", "addon_missing_discovery_info": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", "addon_set_config_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e Z-Wave JS.", + "addon_start_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", "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.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." @@ -17,7 +18,8 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "progress": { - "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442." + "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442.", + "start_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u043a \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS?", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" }, + "start_addon": { + "title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f" + }, "user": { "data": { "url": "URL-\u0430\u0434\u0440\u0435\u0441" diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index b9ff1b419202f1..f1495b1aedadb9 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -6,6 +6,7 @@ "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "addon_start_failed": "Z-Wave JS add-on \u555f\u59cb\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557" @@ -17,7 +18,8 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", + "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, + "start_addon": { + "title": "Z-Wave JS add-on \u555f\u59cb\u4e2d\u3002" + }, "user": { "data": { "url": "\u7db2\u5740" From 7e35af5d4e96312237fc6ce6d4764f1d97d131f7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 01:09:33 +0100 Subject: [PATCH 0766/1818] Upgrade TwitterAPI to 2.6.8 (#47019) --- homeassistant/components/twitter/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twitter/manifest.json b/homeassistant/components/twitter/manifest.json index 873c9e12ab116f..297f990e9df2c6 100644 --- a/homeassistant/components/twitter/manifest.json +++ b/homeassistant/components/twitter/manifest.json @@ -2,6 +2,6 @@ "domain": "twitter", "name": "Twitter", "documentation": "https://www.home-assistant.io/integrations/twitter", - "requirements": ["TwitterAPI==2.6.6"], + "requirements": ["TwitterAPI==2.6.8"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 677ce0d0c2fd9b..b170a784cf3182 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -81,7 +81,7 @@ RtmAPI==0.7.2 TravisPy==0.3.5 # homeassistant.components.twitter -TwitterAPI==2.6.6 +TwitterAPI==2.6.8 # homeassistant.components.tof # VL53L1X2==0.1.5 From 7dc907177614a497aae715256294e241907d6e1c Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 25 Feb 2021 04:25:06 +0100 Subject: [PATCH 0767/1818] Add Xiaomi Miio fan config flow (#46866) * Miio fan config flow * fix styling and imports * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/fan.py Co-authored-by: Martin Hjelmare * rename device -> entity * fix indent Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 4 + .../components/xiaomi_miio/config_flow.py | 1 + homeassistant/components/xiaomi_miio/const.py | 50 +++- homeassistant/components/xiaomi_miio/fan.py | 280 ++++++++---------- 4 files changed, 177 insertions(+), 158 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index a8b32a31576ee5..42b491c9b556b2 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -16,6 +16,7 @@ CONF_MODEL, DOMAIN, KEY_COORDINATOR, + MODELS_FAN, MODELS_SWITCH, MODELS_VACUUM, ) @@ -25,6 +26,7 @@ GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] SWITCH_PLATFORMS = ["switch"] +FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] @@ -122,6 +124,8 @@ async def async_setup_device_entry( platforms = [] if model in MODELS_SWITCH: platforms = SWITCH_PLATFORMS + elif model in MODELS_FAN: + platforms = FAN_PLATFORMS for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index d7e2198f72f024..c9c363b61eb9f9 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -73,6 +73,7 @@ async def async_step_zeroconf(self, discovery_info): ) return await self.async_step_device() + for device_model in MODELS_ALL_DEVICES: if name.startswith(device_model.replace(".", "-")): unique_id = format_mac(self.mac) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index d6c39146f6ad0f..5dc381b17fb6b7 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -9,6 +9,53 @@ KEY_COORDINATOR = "coordinator" +# Fam Models +MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" +MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" +MODEL_AIRPURIFIER_V3 = "zhimi.airpurifier.v3" +MODEL_AIRPURIFIER_V5 = "zhimi.airpurifier.v5" +MODEL_AIRPURIFIER_PRO = "zhimi.airpurifier.v6" +MODEL_AIRPURIFIER_PRO_V7 = "zhimi.airpurifier.v7" +MODEL_AIRPURIFIER_M1 = "zhimi.airpurifier.m1" +MODEL_AIRPURIFIER_M2 = "zhimi.airpurifier.m2" +MODEL_AIRPURIFIER_MA1 = "zhimi.airpurifier.ma1" +MODEL_AIRPURIFIER_MA2 = "zhimi.airpurifier.ma2" +MODEL_AIRPURIFIER_SA1 = "zhimi.airpurifier.sa1" +MODEL_AIRPURIFIER_SA2 = "zhimi.airpurifier.sa2" +MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" +MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" +MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" + +MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" +MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" +MODEL_AIRHUMIDIFIER_CA4 = "zhimi.humidifier.ca4" +MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1" + +MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" + +MODELS_PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] +MODELS_HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4] +MODELS_FAN_MIIO = [ + MODEL_AIRPURIFIER_V1, + MODEL_AIRPURIFIER_V2, + MODEL_AIRPURIFIER_V3, + MODEL_AIRPURIFIER_V5, + MODEL_AIRPURIFIER_PRO, + MODEL_AIRPURIFIER_PRO_V7, + MODEL_AIRPURIFIER_M1, + MODEL_AIRPURIFIER_M2, + MODEL_AIRPURIFIER_MA1, + MODEL_AIRPURIFIER_MA2, + MODEL_AIRPURIFIER_SA1, + MODEL_AIRPURIFIER_SA2, + MODEL_AIRPURIFIER_2S, + MODEL_AIRHUMIDIFIER_V1, + MODEL_AIRHUMIDIFIER_CA1, + MODEL_AIRHUMIDIFIER_CB1, + MODEL_AIRFRESH_VA2, +] + +# Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ "chuangmi.plug.v1", @@ -23,9 +70,10 @@ "chuangmi.plug.hmi206", "lumi.acpartner.v3", ] +MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum"] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM +MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 0d07654e61b188..055690a4264437 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -10,7 +10,6 @@ AirHumidifierMiot, AirPurifier, AirPurifierMiot, - Device, DeviceException, ) from miio.airfresh import ( # pylint: disable=import-error, import-error @@ -44,6 +43,7 @@ SUPPORT_SET_SPEED, FanEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, @@ -51,11 +51,24 @@ CONF_NAME, CONF_TOKEN, ) -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, DOMAIN, + MODEL_AIRHUMIDIFIER_CA1, + MODEL_AIRHUMIDIFIER_CA4, + MODEL_AIRHUMIDIFIER_CB1, + MODEL_AIRPURIFIER_2S, + MODEL_AIRPURIFIER_3, + MODEL_AIRPURIFIER_3H, + MODEL_AIRPURIFIER_PRO, + MODEL_AIRPURIFIER_PRO_V7, + MODEL_AIRPURIFIER_V3, + MODELS_FAN, + MODELS_HUMIDIFIER_MIOT, + MODELS_PURIFIER_MIOT, SERVICE_RESET_FILTER, SERVICE_SET_AUTO_DETECT_OFF, SERVICE_SET_AUTO_DETECT_ON, @@ -77,6 +90,7 @@ SERVICE_SET_TARGET_HUMIDITY, SERVICE_SET_VOLUME, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) @@ -84,58 +98,14 @@ DATA_KEY = "fan.xiaomi_miio" CONF_MODEL = "model" -MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" -MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" -MODEL_AIRPURIFIER_V3 = "zhimi.airpurifier.v3" -MODEL_AIRPURIFIER_V5 = "zhimi.airpurifier.v5" -MODEL_AIRPURIFIER_PRO = "zhimi.airpurifier.v6" -MODEL_AIRPURIFIER_PRO_V7 = "zhimi.airpurifier.v7" -MODEL_AIRPURIFIER_M1 = "zhimi.airpurifier.m1" -MODEL_AIRPURIFIER_M2 = "zhimi.airpurifier.m2" -MODEL_AIRPURIFIER_MA1 = "zhimi.airpurifier.ma1" -MODEL_AIRPURIFIER_MA2 = "zhimi.airpurifier.ma2" -MODEL_AIRPURIFIER_SA1 = "zhimi.airpurifier.sa1" -MODEL_AIRPURIFIER_SA2 = "zhimi.airpurifier.sa2" -MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" -MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" -MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" - -MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" -MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" -MODEL_AIRHUMIDIFIER_CA4 = "zhimi.humidifier.ca4" -MODEL_AIRHUMIDIFIER_CB1 = "zhimi.humidifier.cb1" - -MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MODEL): vol.In( - [ - MODEL_AIRPURIFIER_V1, - MODEL_AIRPURIFIER_V2, - MODEL_AIRPURIFIER_V3, - MODEL_AIRPURIFIER_V5, - MODEL_AIRPURIFIER_PRO, - MODEL_AIRPURIFIER_PRO_V7, - MODEL_AIRPURIFIER_M1, - MODEL_AIRPURIFIER_M2, - MODEL_AIRPURIFIER_MA1, - MODEL_AIRPURIFIER_MA2, - MODEL_AIRPURIFIER_SA1, - MODEL_AIRPURIFIER_SA2, - MODEL_AIRPURIFIER_2S, - MODEL_AIRPURIFIER_3, - MODEL_AIRPURIFIER_3H, - MODEL_AIRHUMIDIFIER_V1, - MODEL_AIRHUMIDIFIER_CA1, - MODEL_AIRHUMIDIFIER_CA4, - MODEL_AIRHUMIDIFIER_CB1, - MODEL_AIRFRESH_VA2, - ] - ), + vol.Optional(CONF_MODEL): vol.In(MODELS_FAN), } ) @@ -193,9 +163,6 @@ # Air Fresh ATTR_CO2 = "co2" -PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] -HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4] - # Map attributes to properties of the state object AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = { ATTR_TEMPERATURE: "temperature", @@ -553,104 +520,114 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the miio fan device from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - model = config.get(CONF_MODEL) - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - unique_id = None - - if model is None: - try: - miio_device = Device(host, token) - device_info = await hass.async_add_executor_job(miio_device.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) - except DeviceException as ex: - raise PlatformNotReady from ex - - if model in PURIFIER_MIOT: - air_purifier = AirPurifierMiot(host, token) - device = XiaomiAirPurifierMiot(name, air_purifier, model, unique_id) - elif model.startswith("zhimi.airpurifier."): - air_purifier = AirPurifier(host, token) - device = XiaomiAirPurifier(name, air_purifier, model, unique_id) - elif model in HUMIDIFIER_MIOT: - air_humidifier = AirHumidifierMiot(host, token) - device = XiaomiAirHumidifierMiot(name, air_humidifier, model, unique_id) - elif model.startswith("zhimi.humidifier."): - air_humidifier = AirHumidifier(host, token, model=model) - device = XiaomiAirHumidifier(name, air_humidifier, model, unique_id) - elif model.startswith("zhimi.airfresh."): - air_fresh = AirFresh(host, token) - device = XiaomiAirFresh(name, air_fresh, model, unique_id) - else: - _LOGGER.error( - "Unsupported device found! Please create an issue at " - "https://github.com/syssi/xiaomi_airpurifier/issues " - "and provide the following data: %s", - model, + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Fan via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, ) - return False - - hass.data[DATA_KEY][host] = device - async_add_entities([device], update_before_add=True) - - async def async_service_handler(service): - """Map services to methods on XiaomiAirPurifier.""" - method = SERVICE_TO_METHOD.get(service.service) - params = { - key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID - } - entity_ids = service.data.get(ATTR_ENTITY_ID) - if entity_ids: - devices = [ - device - for device in hass.data[DATA_KEY].values() - if device.entity_id in entity_ids - ] + ) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Fan from a config entry.""" + entities = [] + + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + if DATA_KEY not in hass.data: + hass.data[DATA_KEY] = {} + + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + + if model in MODELS_PURIFIER_MIOT: + air_purifier = AirPurifierMiot(host, token) + entity = XiaomiAirPurifierMiot(name, air_purifier, config_entry, unique_id) + elif model.startswith("zhimi.airpurifier."): + air_purifier = AirPurifier(host, token) + entity = XiaomiAirPurifier(name, air_purifier, config_entry, unique_id) + elif model in MODELS_HUMIDIFIER_MIOT: + air_humidifier = AirHumidifierMiot(host, token) + entity = XiaomiAirHumidifierMiot( + name, air_humidifier, config_entry, unique_id + ) + elif model.startswith("zhimi.humidifier."): + air_humidifier = AirHumidifier(host, token, model=model) + entity = XiaomiAirHumidifier(name, air_humidifier, config_entry, unique_id) + elif model.startswith("zhimi.airfresh."): + air_fresh = AirFresh(host, token) + entity = XiaomiAirFresh(name, air_fresh, config_entry, unique_id) else: - devices = hass.data[DATA_KEY].values() - - update_tasks = [] - for device in devices: - if not hasattr(device, method["method"]): - continue - await getattr(device, method["method"])(**params) - update_tasks.append(device.async_update_ha_state(True)) + _LOGGER.error( + "Unsupported device found! Please create an issue at " + "https://github.com/syssi/xiaomi_airpurifier/issues " + "and provide the following data: %s", + model, + ) + return - if update_tasks: - await asyncio.wait(update_tasks) + hass.data[DATA_KEY][host] = entity + entities.append(entity) + + async def async_service_handler(service): + """Map services to methods on XiaomiAirPurifier.""" + method = SERVICE_TO_METHOD[service.service] + params = { + key: value + for key, value in service.data.items() + if key != ATTR_ENTITY_ID + } + entity_ids = service.data.get(ATTR_ENTITY_ID) + if entity_ids: + entities = [ + entity + for entity in hass.data[DATA_KEY].values() + if entity.entity_id in entity_ids + ] + else: + entities = hass.data[DATA_KEY].values() + + update_tasks = [] + + for entity in entities: + entity_method = getattr(entity, method["method"], None) + if not entity_method: + continue + await entity_method(**params) + update_tasks.append( + hass.async_create_task(entity.async_update_ha_state(True)) + ) + + if update_tasks: + await asyncio.wait(update_tasks) + + for air_purifier_service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[air_purifier_service].get( + "schema", AIRPURIFIER_SERVICE_SCHEMA + ) + hass.services.async_register( + DOMAIN, air_purifier_service, async_service_handler, schema=schema + ) - for air_purifier_service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[air_purifier_service].get( - "schema", AIRPURIFIER_SERVICE_SCHEMA - ) - hass.services.async_register( - DOMAIN, air_purifier_service, async_service_handler, schema=schema - ) + async_add_entities(entities, update_before_add=True) -class XiaomiGenericDevice(FanEntity): +class XiaomiGenericDevice(XiaomiMiioEntity, FanEntity): """Representation of a generic Xiaomi device.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the generic Xiaomi device.""" - self._name = name - self._device = device - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._available = False self._state = None @@ -668,16 +645,6 @@ def should_poll(self): """Poll the device.""" return True - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the device if any.""" - return self._name - @property def available(self): """Return true when state is known.""" @@ -803,9 +770,9 @@ async def async_set_child_lock_off(self): class XiaomiAirPurifier(XiaomiGenericDevice): """Representation of a Xiaomi Air Purifier.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the plug switch.""" - super().__init__(name, device, model, unique_id) + super().__init__(name, device, entry, unique_id) if self._model == MODEL_AIRPURIFIER_PRO: self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO @@ -1056,9 +1023,9 @@ async def async_set_led_brightness(self, brightness: int = 2): class XiaomiAirHumidifier(XiaomiGenericDevice): """Representation of a Xiaomi Air Humidifier.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the plug switch.""" - super().__init__(name, device, model, unique_id) + super().__init__(name, device, entry, unique_id) if self._model in [MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1]: self._device_features = FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB @@ -1214,7 +1181,6 @@ def button_pressed(self): async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - await self._try_command( "Setting operation mode of the miio device failed.", self._device.set_mode, @@ -1247,9 +1213,9 @@ async def async_set_motor_speed(self, motor_speed: int = 400): class XiaomiAirFresh(XiaomiGenericDevice): """Representation of a Xiaomi Air Fresh.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the miio device.""" - super().__init__(name, device, model, unique_id) + super().__init__(name, device, entry, unique_id) self._device_features = FEATURE_FLAGS_AIRFRESH self._available_attributes = AVAILABLE_ATTRIBUTES_AIRFRESH From a43f3c1a0c360e358e5de8cf2072fe5fe3a0b5c8 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 25 Feb 2021 02:14:32 -0500 Subject: [PATCH 0768/1818] Fix zwave_js unique ID migration logic (#47031) --- homeassistant/components/zwave_js/__init__.py | 74 +++++++++++++----- .../components/zwave_js/discovery.py | 5 -- homeassistant/components/zwave_js/entity.py | 2 +- tests/components/zwave_js/test_init.py | 77 ++++++++++++++++++- 4 files changed, 130 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index cc58e31066a00f..75bc95b7fe4026 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -85,6 +85,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) + @callback + def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> None: + """Check if entity with old unique ID exists, and if so migrate it to new ID.""" + if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): + LOGGER.debug( + "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", + entity_id, + old_unique_id, + new_unique_id, + ) + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -97,26 +112,49 @@ def async_on_node_ready(node: ZwaveNode) -> None: for disc_info in async_discover_values(node): LOGGER.debug("Discovered entity: %s", disc_info) - # This migration logic was added in 2021.3 to handle breaking change to - # value_id format. Some time in the future, this code block - # (and get_old_value_id helper) can be removed. - old_value_id = get_old_value_id(disc_info.primary_value) - old_unique_id = get_unique_id( - client.driver.controller.home_id, old_value_id + # This migration logic was added in 2021.3 to handle a breaking change to + # the value_id format. Some time in the future, this code block + # (as well as get_old_value_id helper and migrate_entity closure) can be + # removed. + value_ids = [ + # 2021.2.* format + get_old_value_id(disc_info.primary_value), + # 2021.3.0b0 format + disc_info.primary_value.value_id, + ] + + new_unique_id = get_unique_id( + client.driver.controller.home_id, + disc_info.primary_value.value_id, ) - if entity_id := ent_reg.async_get_entity_id( - disc_info.platform, DOMAIN, old_unique_id - ): - LOGGER.debug( - "Entity %s is using old unique ID, migrating to new one", entity_id - ) - ent_reg.async_update_entity( - entity_id, - new_unique_id=get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ), + + for value_id in value_ids: + old_unique_id = get_unique_id( + client.driver.controller.home_id, + f"{disc_info.primary_value.node.node_id}.{value_id}", ) + # Most entities have the same ID format, but notification binary sensors + # have a state key in their ID so we need to handle them differently + if ( + disc_info.platform == "binary_sensor" + and disc_info.platform_hint == "notification" + ): + for state_key in disc_info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + + migrate_entity( + disc_info.platform, + f"{old_unique_id}.{state_key}", + f"{new_unique_id}.{state_key}", + ) + + # Once we've iterated through all state keys, we can move on to the + # next item + continue + + migrate_entity(disc_info.platform, old_unique_id, new_unique_id) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 248a34547b5c5e..a40eb10de8b792 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -24,11 +24,6 @@ class ZwaveDiscoveryInfo: # hint for the platform about this discovered entity platform_hint: Optional[str] = "" - @property - def value_id(self) -> str: - """Return the unique value_id belonging to primary value.""" - return f"{self.node.node_id}.{self.primary_value.value_id}" - @dataclass class ZWaveValueDiscoverySchema: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 685fe50c9b6891..d0ed9eb5291309 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -32,7 +32,7 @@ def __init__( self.info = info self._name = self.generate_name() self._unique_id = get_unique_id( - self.client.driver.controller.home_id, self.info.value_id + self.client.driver.controller.home_id, self.info.primary_value.value_id ) # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 3634454544fe4e..f2815bec7f6250 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,7 @@ from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry, entity_registry -from .common import AIR_TEMPERATURE_SENSOR +from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR from tests.common import MockConfigEntry @@ -124,13 +124,15 @@ async def test_on_node_added_ready( ) -async def test_unique_id_migration(hass, multisensor_6_state, client, integration): - """Test unique ID is migrated from old format to new.""" +async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 1).""" ent_reg = entity_registry.async_get(hass) + + # Migrate version 1 entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] # Create entity RegistryEntry using old unique ID format - old_unique_id = f"{client.driver.controller.home_id}.52-49-00-Air temperature-00" + old_unique_id = f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -155,6 +157,73 @@ async def test_unique_id_migration(hass, multisensor_6_state, client, integratio assert entity_entry.unique_id == new_unique_id +async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 2).""" + ent_reg = entity_registry.async_get(hass) + # Migrate version 2 + ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" + entity_name = ILLUMINANCE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52.52-49-0-Illuminance-00-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == ILLUMINANCE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_notification_binary_sensor( + hass, multisensor_6_state, client, integration +): + """Test unique ID is migrated from old format to new for a notification binary sensor.""" + ent_reg = entity_registry.async_get(hass) + + entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52.52-113-00-Home Security-Motion sensor status.8" + entity_entry = ent_reg.async_get_or_create( + "binary_sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == NOTIFICATION_MOTION_BINARY_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8" + assert entity_entry.unique_id == new_unique_id + + async def test_on_node_added_not_ready( hass, multisensor_6_state, client, integration, device_registry ): From 72263abfa986b30c778158067601a7aeecce61ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 01:16:20 -0600 Subject: [PATCH 0769/1818] Ensure doorbird events are re-registered when changing options (#46860) - Fixed the update listener not being unsubscribed - DRY up some of the code - Fix sync code being called in async - Reduce executor jumps --- homeassistant/components/doorbird/__init__.py | 43 ++++++++++--------- homeassistant/components/doorbird/const.py | 2 + homeassistant/components/doorbird/switch.py | 5 ++- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 1dc5bf56c8638c..22db3c762736bf 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -34,6 +34,7 @@ DOOR_STATION_EVENT_ENTITY_IDS, DOOR_STATION_INFO, PLATFORMS, + UNDO_UPDATE_LISTENER, ) from .util import get_doorstation_by_token @@ -128,8 +129,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): device = DoorBird(device_ip, username, password) try: - status = await hass.async_add_executor_job(device.ready) - info = await hass.async_add_executor_job(device.info) + status, info = await hass.async_add_executor_job(_init_doorbird_device, device) except urllib.error.HTTPError as err: if err.code == HTTP_UNAUTHORIZED: _LOGGER.error( @@ -154,18 +154,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): custom_url = doorstation_config.get(CONF_CUSTOM_URL) name = doorstation_config.get(CONF_NAME) events = doorstation_options.get(CONF_EVENTS, []) - doorstation = ConfiguredDoorBird(device, name, events, custom_url, token) + doorstation = ConfiguredDoorBird(device, name, custom_url, token) + doorstation.update_events(events) # Subscribe to doorbell or motion events if not await _async_register_events(hass, doorstation): raise ConfigEntryNotReady + undo_listener = entry.add_update_listener(_update_listener) + hass.data[DOMAIN][config_entry_id] = { DOOR_STATION: doorstation, DOOR_STATION_INFO: info, + UNDO_UPDATE_LISTENER: undo_listener, } - entry.add_update_listener(_update_listener) - for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) @@ -174,9 +176,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True +def _init_doorbird_device(device): + return device.ready(), device.info() + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() + unload_ok = all( await asyncio.gather( *[ @@ -195,7 +203,7 @@ async def _async_register_events(hass, doorstation): try: await hass.async_add_executor_job(doorstation.register_events, hass) except HTTPError: - hass.components.persistent_notification.create( + hass.components.persistent_notification.async_create( "Doorbird configuration failed. Please verify that API " "Operator permission is enabled for the Doorbird user. " "A restart will be required once permissions have been " @@ -212,8 +220,7 @@ async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): """Handle options update.""" config_entry_id = entry.entry_id doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] - - doorstation.events = entry.options[CONF_EVENTS] + doorstation.update_events(entry.options[CONF_EVENTS]) # Subscribe to doorbell or motion events await _async_register_events(hass, doorstation) @@ -234,14 +241,19 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi class ConfiguredDoorBird: """Attach additional information to pass along with configured device.""" - def __init__(self, device, name, events, custom_url, token): + def __init__(self, device, name, custom_url, token): """Initialize configured device.""" self._name = name self._device = device self._custom_url = custom_url + self.events = None + self.doorstation_events = None + self._token = token + + def update_events(self, events): + """Update the doorbird events.""" self.events = events self.doorstation_events = [self._get_event_name(event) for event in self.events] - self._token = token @property def name(self): @@ -305,16 +317,7 @@ def _register_event(self, hass_url, event): def webhook_is_registered(self, url, favs=None) -> bool: """Return whether the given URL is registered as a device favorite.""" - favs = favs if favs else self.device.favorites() - - if "http" not in favs: - return False - - for fav in favs["http"].values(): - if fav["value"] == url: - return True - - return False + return self.get_webhook_id(url, favs) is not None def get_webhook_id(self, url, favs=None) -> str or None: """ diff --git a/homeassistant/components/doorbird/const.py b/homeassistant/components/doorbird/const.py index af847dac673a26..46a95f0d5009ff 100644 --- a/homeassistant/components/doorbird/const.py +++ b/homeassistant/components/doorbird/const.py @@ -17,3 +17,5 @@ DOORBIRD_INFO_KEY_RELAYS = "RELAYS" DOORBIRD_INFO_KEY_PRIMARY_MAC_ADDR = "PRIMARY_MAC_ADDR" DOORBIRD_INFO_KEY_WIFI_MAC_ADDR = "WIFI_MAC_ADDR" + +UNDO_UPDATE_LISTENER = "undo_update_listener" diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py index f1f146aebb96fe..424bb79092f40b 100644 --- a/homeassistant/components/doorbird/switch.py +++ b/homeassistant/components/doorbird/switch.py @@ -17,8 +17,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] config_entry_id = config_entry.entry_id - doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] - doorstation_info = hass.data[DOMAIN][config_entry_id][DOOR_STATION_INFO] + data = hass.data[DOMAIN][config_entry_id] + doorstation = data[DOOR_STATION] + doorstation_info = data[DOOR_STATION_INFO] relays = doorstation_info["RELAYS"] relays.append(IR_RELAY) From 633a7aeb22f140d9defb61bc82cf0cbb3b0befd1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 00:48:19 -0800 Subject: [PATCH 0770/1818] Remove deprecated credstash + keyring (#47033) --- homeassistant/scripts/check_config.py | 7 +-- homeassistant/scripts/credstash.py | 74 --------------------------- homeassistant/scripts/keyring.py | 62 ---------------------- homeassistant/util/yaml/__init__.py | 3 +- homeassistant/util/yaml/const.py | 2 - homeassistant/util/yaml/loader.py | 53 +------------------ requirements_all.txt | 9 ---- requirements_test_all.txt | 9 ---- script/gen_requirements_all.py | 3 +- tests/util/yaml/test_init.py | 44 ---------------- 10 files changed, 4 insertions(+), 262 deletions(-) delete mode 100644 homeassistant/scripts/credstash.py delete mode 100644 homeassistant/scripts/keyring.py diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 992fce2ac87585..f75594a546e15c 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -141,12 +141,7 @@ def run(script_args: List) -> int: if sval is None: print(" -", skey + ":", color("red", "not found")) continue - print( - " -", - skey + ":", - sval, - color("cyan", "[from:", flatsecret.get(skey, "keyring") + "]"), - ) + print(" -", skey + ":", sval) return len(res["except"]) diff --git a/homeassistant/scripts/credstash.py b/homeassistant/scripts/credstash.py deleted file mode 100644 index 99227d81b663ce..00000000000000 --- a/homeassistant/scripts/credstash.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Script to get, put and delete secrets stored in credstash.""" -import argparse -import getpass - -from homeassistant.util.yaml import _SECRET_NAMESPACE - -# mypy: allow-untyped-defs - -REQUIREMENTS = ["credstash==1.15.0"] - - -def run(args): - """Handle credstash script.""" - parser = argparse.ArgumentParser( - description=( - "Modify Home Assistant secrets in credstash." - "Use the secrets in configuration files with: " - "!secret " - ) - ) - parser.add_argument("--script", choices=["credstash"]) - parser.add_argument( - "action", - choices=["get", "put", "del", "list"], - help="Get, put or delete a secret, or list all available secrets", - ) - parser.add_argument("name", help="Name of the secret", nargs="?", default=None) - parser.add_argument( - "value", help="The value to save when putting a secret", nargs="?", default=None - ) - - # pylint: disable=import-error, no-member, import-outside-toplevel - import credstash - - args = parser.parse_args(args) - table = _SECRET_NAMESPACE - - try: - credstash.listSecrets(table=table) - except Exception: # pylint: disable=broad-except - credstash.createDdbTable(table=table) - - if args.action == "list": - secrets = [i["name"] for i in credstash.listSecrets(table=table)] - deduped_secrets = sorted(set(secrets)) - - print("Saved secrets:") - for secret in deduped_secrets: - print(secret) - return 0 - - if args.name is None: - parser.print_help() - return 1 - - if args.action == "put": - if args.value: - the_secret = args.value - else: - the_secret = getpass.getpass(f"Please enter the secret for {args.name}: ") - current_version = credstash.getHighestVersion(args.name, table=table) - credstash.putSecret( - args.name, the_secret, version=int(current_version) + 1, table=table - ) - print(f"Secret {args.name} put successfully") - elif args.action == "get": - the_secret = credstash.getSecret(args.name, table=table) - if the_secret is None: - print(f"Secret {args.name} not found") - else: - print(f"Secret {args.name}={the_secret}") - elif args.action == "del": - credstash.deleteSecrets(args.name, table=table) - print(f"Deleted secret {args.name}") diff --git a/homeassistant/scripts/keyring.py b/homeassistant/scripts/keyring.py deleted file mode 100644 index 0166d41ce0c0c6..00000000000000 --- a/homeassistant/scripts/keyring.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Script to get, set and delete secrets stored in the keyring.""" -import argparse -import getpass -import os - -from homeassistant.util.yaml import _SECRET_NAMESPACE - -# mypy: allow-untyped-defs -REQUIREMENTS = ["keyring==21.2.0", "keyrings.alt==3.4.0"] - - -def run(args): - """Handle keyring script.""" - parser = argparse.ArgumentParser( - description=( - "Modify Home Assistant secrets in the default keyring. " - "Use the secrets in configuration files with: " - "!secret " - ) - ) - parser.add_argument("--script", choices=["keyring"]) - parser.add_argument( - "action", - choices=["get", "set", "del", "info"], - help="Get, set or delete a secret", - ) - parser.add_argument("name", help="Name of the secret", nargs="?", default=None) - - import keyring # pylint: disable=import-outside-toplevel - - # pylint: disable=import-outside-toplevel - from keyring.util import platform_ as platform - - args = parser.parse_args(args) - - if args.action == "info": - keyr = keyring.get_keyring() - print("Keyring version {}\n".format(REQUIREMENTS[0].split("==")[1])) - print(f"Active keyring : {keyr.__module__}") - config_name = os.path.join(platform.config_root(), "keyringrc.cfg") - print(f"Config location : {config_name}") - print(f"Data location : {platform.data_root()}\n") - elif args.name is None: - parser.print_help() - return 1 - - if args.action == "set": - entered_secret = getpass.getpass(f"Please enter the secret for {args.name}: ") - keyring.set_password(_SECRET_NAMESPACE, args.name, entered_secret) - print(f"Secret {args.name} set successfully") - elif args.action == "get": - the_secret = keyring.get_password(_SECRET_NAMESPACE, args.name) - if the_secret is None: - print(f"Secret {args.name} not found") - else: - print(f"Secret {args.name}={the_secret}") - elif args.action == "del": - try: - keyring.delete_password(_SECRET_NAMESPACE, args.name) - print(f"Deleted secret {args.name}") - except keyring.errors.PasswordDeleteError: - print(f"Secret {args.name} not found") diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index ac4ac2f9a16951..a152086ea82911 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -1,5 +1,5 @@ """YAML utility functions.""" -from .const import _SECRET_NAMESPACE, SECRET_YAML +from .const import SECRET_YAML from .dumper import dump, save_yaml from .input import UndefinedSubstitution, extract_inputs, substitute from .loader import clear_secret_cache, load_yaml, parse_yaml, secret_yaml @@ -7,7 +7,6 @@ __all__ = [ "SECRET_YAML", - "_SECRET_NAMESPACE", "Input", "dump", "save_yaml", diff --git a/homeassistant/util/yaml/const.py b/homeassistant/util/yaml/const.py index bf1615edb9343b..9d930b50fd6f63 100644 --- a/homeassistant/util/yaml/const.py +++ b/homeassistant/util/yaml/const.py @@ -1,4 +1,2 @@ """Constants.""" SECRET_YAML = "secrets.yaml" - -_SECRET_NAMESPACE = "homeassistant" diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 294cd0ac570331..7d713c9f0c0651 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -10,20 +10,9 @@ from homeassistant.exceptions import HomeAssistantError -from .const import _SECRET_NAMESPACE, SECRET_YAML +from .const import SECRET_YAML from .objects import Input, NodeListClass, NodeStrClass -try: - import keyring -except ImportError: - keyring = None - -try: - import credstash -except ImportError: - credstash = None - - # mypy: allow-untyped-calls, no-warn-return-any JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name @@ -32,9 +21,6 @@ _LOGGER = logging.getLogger(__name__) __SECRET_CACHE: Dict[str, JSON_TYPE] = {} -CREDSTASH_WARN = False -KEYRING_WARN = False - def clear_secret_cache() -> None: """Clear the secret cache. @@ -299,43 +285,6 @@ def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: if not os.path.exists(secret_path) or len(secret_path) < 5: break # Somehow we got past the .homeassistant config folder - if keyring: - # do some keyring stuff - pwd = keyring.get_password(_SECRET_NAMESPACE, node.value) - if pwd: - global KEYRING_WARN # pylint: disable=global-statement - - if not KEYRING_WARN: - KEYRING_WARN = True - _LOGGER.warning( - "Keyring is deprecated and will be removed in March 2021." - ) - - _LOGGER.debug("Secret %s retrieved from keyring", node.value) - return pwd - - global credstash # pylint: disable=invalid-name, global-statement - - if credstash: - # pylint: disable=no-member - try: - pwd = credstash.getSecret(node.value, table=_SECRET_NAMESPACE) - if pwd: - global CREDSTASH_WARN # pylint: disable=global-statement - - if not CREDSTASH_WARN: - CREDSTASH_WARN = True - _LOGGER.warning( - "Credstash is deprecated and will be removed in March 2021." - ) - _LOGGER.debug("Secret %s retrieved from credstash", node.value) - return pwd - except credstash.ItemNotFound: - pass - except Exception: # pylint: disable=broad-except - # Catch if package installed and no config - credstash = None - raise HomeAssistantError(f"Secret {node.value} not defined") diff --git a/requirements_all.txt b/requirements_all.txt index b170a784cf3182..c88761fce248c1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -448,9 +448,6 @@ construct==2.10.56 # homeassistant.components.coronavirus coronavirus==1.1.1 -# homeassistant.scripts.credstash -# credstash==1.15.0 - # homeassistant.components.datadog datadog==0.15.0 @@ -844,12 +841,6 @@ kaiterra-async-client==0.0.2 # homeassistant.components.keba keba-kecontact==1.1.0 -# homeassistant.scripts.keyring -keyring==21.2.0 - -# homeassistant.scripts.keyring -keyrings.alt==3.4.0 - # homeassistant.components.kiwi kiwiki-client==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 28478c4349aa2d..6cfb1119aad24d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -239,9 +239,6 @@ construct==2.10.56 # homeassistant.components.coronavirus coronavirus==1.1.1 -# homeassistant.scripts.credstash -# credstash==1.15.0 - # homeassistant.components.datadog datadog==0.15.0 @@ -455,12 +452,6 @@ influxdb==5.2.3 # homeassistant.components.verisure jsonpath==0.82 -# homeassistant.scripts.keyring -keyring==21.2.0 - -# homeassistant.scripts.keyring -keyrings.alt==3.4.0 - # homeassistant.components.konnected konnected==1.2.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 7dd4924dac81c2..94365be9a50f44 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -21,7 +21,6 @@ "blinkt", "bluepy", "bme680", - "credstash", "decora", "decora_wifi", "envirophat", @@ -47,7 +46,7 @@ "VL53L1X2", ) -IGNORE_PIN = ("colorlog>2.1,<3", "keyring>=9.3,<10.0", "urllib3") +IGNORE_PIN = ("colorlog>2.1,<3", "urllib3") URL_PIN = ( "https://developers.home-assistant.io/docs/" diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index e28a12acf71ab1..b3a8ca4e486cc8 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -1,6 +1,5 @@ """Test Home Assistant yaml loader.""" import io -import logging import os import unittest from unittest.mock import patch @@ -15,14 +14,6 @@ from tests.common import get_test_config_dir, patch_yaml_files -@pytest.fixture(autouse=True) -def mock_credstash(): - """Mock credstash so it doesn't connect to the internet.""" - with patch.object(yaml_loader, "credstash") as mock_credstash: - mock_credstash.getSecret.return_value = None - yield mock_credstash - - def test_simple_list(): """Test simple list.""" conf = "config:\n - simple\n - list" @@ -294,20 +285,6 @@ def load_yaml(fname, string): return load_yaml_config_file(fname) -class FakeKeyring: - """Fake a keyring class.""" - - def __init__(self, secrets_dict): - """Store keyring dictionary.""" - self._secrets = secrets_dict - - # pylint: disable=protected-access - def get_password(self, domain, name): - """Retrieve password.""" - assert domain == yaml._SECRET_NAMESPACE - return self._secrets.get(name) - - class TestSecrets(unittest.TestCase): """Test the secrets parameter in the yaml utility.""" @@ -395,27 +372,6 @@ def test_secrets_from_unrelated_fails(self): "http:\n api_password: !secret test", ) - def test_secrets_keyring(self): - """Test keyring fallback & get_password.""" - yaml_loader.keyring = None # Ensure its not there - yaml_str = "http:\n api_password: !secret http_pw_keyring" - with pytest.raises(HomeAssistantError): - load_yaml(self._yaml_path, yaml_str) - - yaml_loader.keyring = FakeKeyring({"http_pw_keyring": "yeah"}) - _yaml = load_yaml(self._yaml_path, yaml_str) - assert {"http": {"api_password": "yeah"}} == _yaml - - @patch.object(yaml_loader, "credstash") - def test_secrets_credstash(self, mock_credstash): - """Test credstash fallback & get_password.""" - mock_credstash.getSecret.return_value = "yeah" - yaml_str = "http:\n api_password: !secret http_pw_credstash" - _yaml = load_yaml(self._yaml_path, yaml_str) - log = logging.getLogger() - log.error(_yaml["http"]) - assert {"api_password": "yeah"} == _yaml["http"] - def test_secrets_logger_removed(self): """Ensure logger: debug was removed.""" with pytest.raises(HomeAssistantError): From 47938a1355e8a5b8de0294b1017c02972d1d9189 Mon Sep 17 00:00:00 2001 From: Florian Heilmann Date: Thu, 25 Feb 2021 09:49:52 +0100 Subject: [PATCH 0771/1818] hm climate: Return PRESET_NONE instead of None (#47003) Signed-off-by: Florian Heilmann --- homeassistant/components/homematic/climate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index 3b77b90ff2528f..aa5fb4a8e44591 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -7,6 +7,7 @@ PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, + PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -92,14 +93,14 @@ def preset_mode(self): return "boost" if not self._hm_control_mode: - return None + return PRESET_NONE mode = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][self._hm_control_mode] mode = mode.lower() # Filter HVAC states if mode not in (HVAC_MODE_AUTO, HVAC_MODE_HEAT): - return None + return PRESET_NONE return mode @property From 65a2f07a014cb8b6fe13abd4d4482cac3dc220c4 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Thu, 25 Feb 2021 09:51:18 +0100 Subject: [PATCH 0772/1818] Fix missing Shelly external input (#47028) * Add support for external input (Shelly 1/1pm add-on) * Make external sensor naming consistent * Fix case consistency --- homeassistant/components/shelly/binary_sensor.py | 7 ++++++- homeassistant/components/shelly/sensor.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 8f99e6a7a6e0ed..18220fc9e3ac89 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -73,6 +73,11 @@ default_enabled=False, removal_condition=is_momentary_input, ), + ("sensor", "extInput"): BlockAttributeDescription( + name="External Input", + device_class=DEVICE_CLASS_POWER, + default_enabled=False, + ), ("sensor", "motion"): BlockAttributeDescription( name="Motion", device_class=DEVICE_CLASS_MOTION ), @@ -86,7 +91,7 @@ default_enabled=False, ), "fwupdate": RestAttributeDescription( - name="Firmware update", + name="Firmware Update", icon="mdi:update", value=lambda status, _: status["update"]["has_update"], default_enabled=False, diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 32fb33877d3e6d..472f3be4daeef4 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -133,7 +133,7 @@ available=lambda block: block.sensorOp == "normal", ), ("sensor", "extTemp"): BlockAttributeDescription( - name="Temperature", + name="External Temperature", unit=temperature_unit, value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_TEMPERATURE, @@ -155,7 +155,7 @@ icon="mdi:angle-acute", ), ("relay", "totalWorkTime"): BlockAttributeDescription( - name="Lamp life", + name="Lamp Life", unit=PERCENTAGE, icon="mdi:progress-wrench", value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1), From 6b0c569a70672d04af6257eec4ec143842544a1d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 25 Feb 2021 09:54:46 +0100 Subject: [PATCH 0773/1818] Normally there should only be one battery sensor per device from deCONZ. (#46761) With these Danfoss devices each endpoint can report its own battery state. --- homeassistant/components/deconz/sensor.py | 13 +- tests/components/deconz/test_sensor.py | 137 ++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 9d71fd0a9f9870..e3a7e6a001a071 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -200,7 +200,18 @@ def async_update_callback(self, force_update=False): @property def unique_id(self): - """Return a unique identifier for this device.""" + """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.modelid in [ + "0x8030", + "0x8031", + "0x8034", + "0x8035", + ]: + return f"{super().unique_id}-battery" return f"{self.serial}-battery" @property diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 8a00385ccb97b8..1a52194633512a 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -4,6 +4,7 @@ from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, @@ -241,6 +242,142 @@ async def test_add_battery_later(hass, aioclient_mock): assert hass.states.get("sensor.switch_1_battery_level") +async def test_special_danfoss_battery_creation(hass, aioclient_mock): + """Test the special Danfoss battery creation works. + + Normally there should only be one battery sensor per device from deCONZ. + With specific Danfoss devices each endpoint can report its own battery state. + """ + data = deepcopy(DECONZ_WEB_REQUEST) + data["sensors"] = { + "1": { + "config": { + "battery": 70, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 1, + "etag": "982d9acc38bee5b251e24a9be26558e4", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:07.994", + "on": False, + "temperature": 2307, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-01-0201", + }, + "2": { + "config": { + "battery": 86, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 2, + "etag": "62f12749f9f51c950086aff37dd02b61", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:22.399", + "on": False, + "temperature": 2316, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-02-0201", + }, + "3": { + "config": { + "battery": 86, + "heatsetpoint": 2350, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 3, + "etag": "f50061174bb7f18a3d95789bab8b646d", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:25.466", + "on": False, + "temperature": 2337, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-03-0201", + }, + "4": { + "config": { + "battery": 85, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 4, + "etag": "eea97adf8ce1b971b8b6a3a31793f96b", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:41.939", + "on": False, + "temperature": 2333, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-04-0201", + }, + "5": { + "config": { + "battery": 83, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 5, + "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": {"lastupdated": "none", "on": False, "temperature": 2325}, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-05-0201", + }, + } + await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + + assert len(hass.states.async_all()) == 10 + 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 = deepcopy(DECONZ_WEB_REQUEST) From 7a691f9d26e0788d540326bcf23ba1dec982995a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 11:11:40 +0100 Subject: [PATCH 0774/1818] Upgrade icmplib to 2.0.2 (#47039) --- homeassistant/components/ping/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ping/manifest.json b/homeassistant/components/ping/manifest.json index 258a75caa02332..bf997d8a4c98dc 100644 --- a/homeassistant/components/ping/manifest.json +++ b/homeassistant/components/ping/manifest.json @@ -3,6 +3,6 @@ "name": "Ping (ICMP)", "documentation": "https://www.home-assistant.io/integrations/ping", "codeowners": [], - "requirements": ["icmplib==2.0"], + "requirements": ["icmplib==2.0.2"], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index c88761fce248c1..bdd2e440fc64c6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -811,7 +811,7 @@ ibm-watson==4.0.1 ibmiotf==0.3.4 # homeassistant.components.ping -icmplib==2.0 +icmplib==2.0.2 # homeassistant.components.iglo iglo==1.2.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6cfb1119aad24d..871f3406fe472c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -440,7 +440,7 @@ hyperion-py==0.7.0 iaqualink==0.3.4 # homeassistant.components.ping -icmplib==2.0 +icmplib==2.0.2 # homeassistant.components.influxdb influxdb-client==1.14.0 From 1989b8c07d791a60f57c2eb440dbf578f093c98d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 03:19:21 -0800 Subject: [PATCH 0775/1818] Clean up discovery integration (#47022) * Clean up discovery integration * Fix tests * Remove discovery step from freebox --- .../components/daikin/config_flow.py | 10 +-------- .../components/discovery/__init__.py | 22 ++++++++++--------- homeassistant/components/freebox/__init__.py | 20 ++--------------- .../components/freebox/config_flow.py | 4 ---- .../components/freebox/manifest.json | 2 +- homeassistant/generated/zeroconf.py | 5 +++++ tests/components/daikin/test_config_flow.py | 10 ++------- tests/components/discovery/test_init.py | 6 ++--- tests/components/freebox/test_config_flow.py | 13 +---------- 9 files changed, 27 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index b9956a87af0b23..155fdd18376ad9 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -12,7 +12,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD -from .const import CONF_UUID, KEY_IP, KEY_MAC, TIMEOUT +from .const import CONF_UUID, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) @@ -124,14 +124,6 @@ async def async_step_import(self, user_input): return await self.async_step_user() return await self._create_device(host) - async def async_step_discovery(self, discovery_info): - """Initialize step from discovery.""" - _LOGGER.debug("Discovered device: %s", discovery_info) - await self.async_set_unique_id(discovery_info[KEY_MAC]) - self._abort_if_unique_id_configured() - self.host = discovery_info[KEY_IP] - return await self.async_step_user() - async def async_step_zeroconf(self, discovery_info): """Prepare configuration for a discovered Daikin device.""" _LOGGER.debug("Zeroconf user_input: %s", discovery_info) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 2b2931798885bd..0a3deeef33bfb7 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -38,25 +38,17 @@ SERVICE_WINK = "wink" SERVICE_XIAOMI_GW = "xiaomi_gw" +# These have custom protocols CONFIG_ENTRY_HANDLERS = { - SERVICE_DAIKIN: "daikin", SERVICE_TELLDUSLIVE: "tellduslive", "logitech_mediaserver": "squeezebox", } +# These have no config flows SERVICE_HANDLERS = { - SERVICE_MOBILE_APP: ("mobile_app", None), - SERVICE_HASS_IOS_APP: ("ios", None), SERVICE_NETGEAR: ("device_tracker", None), - SERVICE_HASSIO: ("hassio", None), - SERVICE_APPLE_TV: ("apple_tv", None), SERVICE_ENIGMA2: ("media_player", "enigma2"), - SERVICE_WINK: ("wink", None), SERVICE_SABNZBD: ("sabnzbd", None), - SERVICE_SAMSUNG_PRINTER: ("sensor", None), - SERVICE_KONNECTED: ("konnected", None), - SERVICE_OCTOPRINT: ("octoprint", None), - SERVICE_FREEBOX: ("freebox", None), "yamaha": ("media_player", "yamaha"), "frontier_silicon": ("media_player", "frontier_silicon"), "openhome": ("media_player", "openhome"), @@ -69,20 +61,30 @@ OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")} MIGRATED_SERVICE_HANDLERS = [ + SERVICE_APPLE_TV, "axis", "deconz", + SERVICE_DAIKIN, "denonavr", "esphome", + SERVICE_FREEBOX, "google_cast", + SERVICE_HASS_IOS_APP, + SERVICE_HASSIO, SERVICE_HEOS, "harmony", "homekit", "ikea_tradfri", "kodi", + SERVICE_KONNECTED, + SERVICE_MOBILE_APP, + SERVICE_OCTOPRINT, "philips_hue", + SERVICE_SAMSUNG_PRINTER, "sonos", "songpal", SERVICE_WEMO, + SERVICE_WINK, SERVICE_XIAOMI_GW, "volumio", SERVICE_YEELIGHT, diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 9120c7d0866824..35e89eb2b09fa3 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -4,10 +4,9 @@ import voluptuous as vol -from homeassistant.components.discovery import SERVICE_FREEBOX -from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers import config_validation as cv, discovery +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN, PLATFORMS @@ -29,21 +28,6 @@ async def async_setup(hass, config): """Set up the Freebox component.""" conf = config.get(DOMAIN) - async def discovery_dispatch(service, discovery_info): - if conf is None: - host = discovery_info.get("properties", {}).get("api_domain") - port = discovery_info.get("properties", {}).get("https_port") - _LOGGER.info("Discovered Freebox server: %s:%s", host, port) - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_DISCOVERY}, - data={CONF_HOST: host, CONF_PORT: port}, - ) - ) - - discovery.async_listen(hass, SERVICE_FREEBOX, discovery_dispatch) - if conf is None: return True diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 2ee52884c88b3f..49354f16705fa2 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -105,7 +105,3 @@ async def async_step_link(self, user_input=None): async def async_step_import(self, user_input=None): """Import a config entry.""" return await self.async_step_user(user_input) - - async def async_step_discovery(self, discovery_info): - """Initialize step from discovery.""" - return await self.async_step_user(discovery_info) diff --git a/homeassistant/components/freebox/manifest.json b/homeassistant/components/freebox/manifest.json index 2739849b547911..e6a7a17a119209 100644 --- a/homeassistant/components/freebox/manifest.json +++ b/homeassistant/components/freebox/manifest.json @@ -4,6 +4,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/freebox", "requirements": ["freebox-api==0.0.9"], - "after_dependencies": ["discovery"], + "zeroconf": ["_fbx-api._tcp.local."], "codeowners": ["@hacf-fr", "@Quentame"] } diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 5521ab9da8f21a..d3a976e78ea980 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -59,6 +59,11 @@ "domain": "esphome" } ], + "_fbx-api._tcp.local.": [ + { + "domain": "freebox" + } + ], "_googlecast._tcp.local.": [ { "domain": "cast" diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index a7165b2cb9b3ae..076f9f54878e84 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -7,13 +7,8 @@ from aiohttp.web_exceptions import HTTPForbidden import pytest -from homeassistant.components.daikin.const import KEY_IP, KEY_MAC -from homeassistant.config_entries import ( - SOURCE_DISCOVERY, - SOURCE_IMPORT, - SOURCE_USER, - SOURCE_ZEROCONF, -) +from homeassistant.components.daikin.const import KEY_MAC +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -132,7 +127,6 @@ async def test_device_abort(hass, mock_daikin, s_effect, reason): @pytest.mark.parametrize( "source, data, unique_id", [ - (SOURCE_DISCOVERY, {KEY_IP: HOST, KEY_MAC: MAC}, MAC), (SOURCE_ZEROCONF, {CONF_HOST: HOST}, MAC), ], ) diff --git a/tests/components/discovery/test_init.py b/tests/components/discovery/test_init.py index 2c1e41e8285156..4dd77c981873a3 100644 --- a/tests/components/discovery/test_init.py +++ b/tests/components/discovery/test_init.py @@ -16,8 +16,8 @@ SERVICE = "yamaha" SERVICE_COMPONENT = "media_player" -SERVICE_NO_PLATFORM = "hass_ios" -SERVICE_NO_PLATFORM_COMPONENT = "ios" +SERVICE_NO_PLATFORM = "netgear_router" +SERVICE_NO_PLATFORM_COMPONENT = "device_tracker" SERVICE_INFO = {"key": "value"} # Can be anything UNKNOWN_SERVICE = "this_service_will_never_be_supported" @@ -39,7 +39,7 @@ async def mock_discovery(hass, discoveries, config=BASE_CONFIG): with patch("homeassistant.components.zeroconf.async_get_instance"), patch( "homeassistant.components.zeroconf.async_setup", return_value=True ), patch.object(discovery, "_discover", discoveries), patch( - "homeassistant.components.discovery.async_discover", return_value=mock_coro() + "homeassistant.components.discovery.async_discover" ) as mock_discover, patch( "homeassistant.components.discovery.async_load_platform", return_value=mock_coro(), diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 197be7bd3a64a5..ad935c47cc47e5 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -10,7 +10,7 @@ from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN -from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_PORT from tests.common import MockConfigEntry @@ -66,17 +66,6 @@ async def test_import(hass): assert result["step_id"] == "link" -async def test_discovery(hass): - """Test discovery step.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_DISCOVERY}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "link" - - async def test_link(hass, connect): """Test linking.""" result = await hass.config_entries.flow.async_init( From 5ec4360ac11fb042fa886de9e902d130449d3049 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 13:08:44 +0100 Subject: [PATCH 0776/1818] Upgrade pyowm to 3.2.0 (#47042) --- homeassistant/components/openweathermap/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/openweathermap/manifest.json b/homeassistant/components/openweathermap/manifest.json index e355e2e4752e49..27cda9fb26dcd2 100644 --- a/homeassistant/components/openweathermap/manifest.json +++ b/homeassistant/components/openweathermap/manifest.json @@ -3,6 +3,6 @@ "name": "OpenWeatherMap", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/openweathermap", - "requirements": ["pyowm==3.1.1"], + "requirements": ["pyowm==3.2.0"], "codeowners": ["@fabaff", "@freekode", "@nzapponi"] } diff --git a/requirements_all.txt b/requirements_all.txt index bdd2e440fc64c6..afe8c65b532ca4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1611,7 +1611,7 @@ pyotgw==1.0b1 pyotp==2.3.0 # homeassistant.components.openweathermap -pyowm==3.1.1 +pyowm==3.2.0 # homeassistant.components.onewire pyownet==0.10.0.post1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 871f3406fe472c..ba5c9222a65e97 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -859,7 +859,7 @@ pyotgw==1.0b1 pyotp==2.3.0 # homeassistant.components.openweathermap -pyowm==3.1.1 +pyowm==3.2.0 # homeassistant.components.onewire pyownet==0.10.0.post1 From c9d7a2be88084cafa8aabcf4c9a2145a2e66d5fd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Feb 2021 13:09:52 +0100 Subject: [PATCH 0777/1818] Upgrade sendgrid to 6.6.0 (#47041) --- homeassistant/components/sendgrid/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sendgrid/manifest.json b/homeassistant/components/sendgrid/manifest.json index 1a93040837a631..21ebcd828c2f6a 100644 --- a/homeassistant/components/sendgrid/manifest.json +++ b/homeassistant/components/sendgrid/manifest.json @@ -2,6 +2,6 @@ "domain": "sendgrid", "name": "SendGrid", "documentation": "https://www.home-assistant.io/integrations/sendgrid", - "requirements": ["sendgrid==6.5.0"], + "requirements": ["sendgrid==6.6.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index afe8c65b532ca4..b45cf562fc1953 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2016,7 +2016,7 @@ schiene==0.23 scsgate==0.1.0 # homeassistant.components.sendgrid -sendgrid==6.5.0 +sendgrid==6.6.0 # homeassistant.components.sensehat sense-hat==2.2.0 From 372ed2db910cd6c73c6681fcc34a69645819cf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel?= Date: Thu, 25 Feb 2021 09:40:01 -0300 Subject: [PATCH 0778/1818] Add remote control platform to Panasonic Viera (#42450) * Adding remote platform * Update homeassistant/components/panasonic_viera/remote.py Simplify entity creation Co-authored-by: Martin Hjelmare * Use Pytest fixture * Use Pytest fixtures and assert service calls * Adding conftest.py and organizing tests * Reorganizing tests Co-authored-by: Martin Hjelmare --- .../components/panasonic_viera/__init__.py | 4 +- .../components/panasonic_viera/remote.py | 90 ++++++ tests/components/panasonic_viera/conftest.py | 104 +++++++ .../panasonic_viera/test_config_flow.py | 257 ++++-------------- tests/components/panasonic_viera/test_init.py | 196 ++++++------- .../components/panasonic_viera/test_remote.py | 58 ++++ 6 files changed, 386 insertions(+), 323 deletions(-) create mode 100644 homeassistant/components/panasonic_viera/remote.py create mode 100644 tests/components/panasonic_viera/conftest.py create mode 100644 tests/components/panasonic_viera/test_remote.py diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 3305c935890e0d..9902d39a56c96f 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant.components.media_player.const import DOMAIN as MEDIA_PLAYER_DOMAIN +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv @@ -46,7 +47,7 @@ extra=vol.ALLOW_EXTRA, ) -PLATFORMS = [MEDIA_PLAYER_DOMAIN] +PLATFORMS = [MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN] async def async_setup(hass, config): @@ -219,6 +220,7 @@ async def async_turn_off(self): """Turn off the TV.""" if self.state != STATE_OFF: await self.async_send_key(Keys.power) + self.state = STATE_OFF await self.async_update() async def async_set_mute(self, enable): diff --git a/homeassistant/components/panasonic_viera/remote.py b/homeassistant/components/panasonic_viera/remote.py new file mode 100644 index 00000000000000..8f3fab80215bfa --- /dev/null +++ b/homeassistant/components/panasonic_viera/remote.py @@ -0,0 +1,90 @@ +"""Remote control support for Panasonic Viera TV.""" +import logging + +from homeassistant.components.remote import RemoteEntity +from homeassistant.const import CONF_NAME, STATE_ON + +from .const import ( + ATTR_DEVICE_INFO, + ATTR_MANUFACTURER, + ATTR_MODEL_NUMBER, + ATTR_REMOTE, + ATTR_UDN, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Panasonic Viera TV Remote from a config entry.""" + + config = config_entry.data + + remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE] + name = config[CONF_NAME] + device_info = config[ATTR_DEVICE_INFO] + + async_add_entities([PanasonicVieraRemoteEntity(remote, name, device_info)]) + + +class PanasonicVieraRemoteEntity(RemoteEntity): + """Representation of a Panasonic Viera TV Remote.""" + + def __init__(self, remote, name, device_info): + """Initialize the entity.""" + # Save a reference to the imported class + self._remote = remote + self._name = name + self._device_info = device_info + + @property + def unique_id(self): + """Return the unique ID of the device.""" + if self._device_info is None: + return None + return self._device_info[ATTR_UDN] + + @property + def device_info(self): + """Return device specific attributes.""" + if self._device_info is None: + return None + return { + "name": self._name, + "identifiers": {(DOMAIN, self._device_info[ATTR_UDN])}, + "manufacturer": self._device_info.get( + ATTR_MANUFACTURER, DEFAULT_MANUFACTURER + ), + "model": self._device_info.get(ATTR_MODEL_NUMBER, DEFAULT_MODEL_NUMBER), + } + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def available(self): + """Return True if the device is available.""" + return self._remote.available + + @property + def is_on(self): + """Return true if device is on.""" + return self._remote.state == STATE_ON + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + await self._remote.async_turn_on(context=self._context) + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + await self._remote.async_turn_off() + + async def async_send_command(self, command, **kwargs): + """Send a command to one device.""" + for cmd in command: + await self._remote.async_send_key(cmd) diff --git a/tests/components/panasonic_viera/conftest.py b/tests/components/panasonic_viera/conftest.py new file mode 100644 index 00000000000000..d1444f014777de --- /dev/null +++ b/tests/components/panasonic_viera/conftest.py @@ -0,0 +1,104 @@ +"""Test helpers for Panasonic Viera.""" + +from unittest.mock import Mock, patch + +from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED +import pytest + +from homeassistant.components.panasonic_viera.const import ( + ATTR_FRIENDLY_NAME, + ATTR_MANUFACTURER, + ATTR_MODEL_NUMBER, + ATTR_UDN, + CONF_APP_ID, + CONF_ENCRYPTION_KEY, + CONF_ON_ACTION, + DEFAULT_MANUFACTURER, + DEFAULT_MODEL_NUMBER, + DEFAULT_NAME, + DEFAULT_PORT, +) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT + +MOCK_BASIC_DATA = { + CONF_HOST: "0.0.0.0", + CONF_NAME: DEFAULT_NAME, +} + +MOCK_CONFIG_DATA = { + **MOCK_BASIC_DATA, + CONF_PORT: DEFAULT_PORT, + CONF_ON_ACTION: None, +} + +MOCK_ENCRYPTION_DATA = { + CONF_APP_ID: "mock-app-id", + CONF_ENCRYPTION_KEY: "mock-encryption-key", +} + +MOCK_DEVICE_INFO = { + ATTR_FRIENDLY_NAME: DEFAULT_NAME, + ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, + ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, + ATTR_UDN: "mock-unique-id", +} + + +def get_mock_remote( + request_error=None, + authorize_error=None, + encrypted=False, + app_id=None, + encryption_key=None, + device_info=MOCK_DEVICE_INFO, +): + """Return a mock remote.""" + mock_remote = Mock() + + mock_remote.type = TV_TYPE_ENCRYPTED if encrypted else TV_TYPE_NONENCRYPTED + mock_remote.app_id = app_id + mock_remote.enc_key = encryption_key + + def request_pin_code(name=None): + if request_error is not None: + raise request_error + + mock_remote.request_pin_code = request_pin_code + + def authorize_pin_code(pincode): + if pincode == "1234": + return + + if authorize_error is not None: + raise authorize_error + + mock_remote.authorize_pin_code = authorize_pin_code + + def get_device_info(): + return device_info + + mock_remote.get_device_info = get_device_info + + def send_key(key): + return + + mock_remote.send_key = Mock(send_key) + + def get_volume(key): + return 100 + + mock_remote.get_volume = Mock(get_volume) + + return mock_remote + + +@pytest.fixture(name="mock_remote") +def mock_remote_fixture(): + """Patch the library remote.""" + mock_remote = get_mock_remote() + + with patch( + "homeassistant.components.panasonic_viera.RemoteControl", + return_value=mock_remote, + ): + yield mock_remote diff --git a/tests/components/panasonic_viera/test_config_flow.py b/tests/components/panasonic_viera/test_config_flow.py index e099862604a2d3..dd7f629c29bf4e 100644 --- a/tests/components/panasonic_viera/test_config_flow.py +++ b/tests/components/panasonic_viera/test_config_flow.py @@ -1,90 +1,28 @@ """Test the Panasonic Viera config flow.""" -from unittest.mock import Mock, patch +from unittest.mock import patch -from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED, SOAPError -import pytest +from panasonic_viera import SOAPError from homeassistant import config_entries from homeassistant.components.panasonic_viera.const import ( ATTR_DEVICE_INFO, - ATTR_FRIENDLY_NAME, - ATTR_MANUFACTURER, - ATTR_MODEL_NUMBER, - ATTR_UDN, - CONF_APP_ID, - CONF_ENCRYPTION_KEY, - CONF_ON_ACTION, - DEFAULT_MANUFACTURER, - DEFAULT_MODEL_NUMBER, DEFAULT_NAME, - DEFAULT_PORT, DOMAIN, ERROR_INVALID_PIN_CODE, ) -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT +from homeassistant.const import CONF_PIN + +from .conftest import ( + MOCK_BASIC_DATA, + MOCK_CONFIG_DATA, + MOCK_DEVICE_INFO, + MOCK_ENCRYPTION_DATA, + get_mock_remote, +) from tests.common import MockConfigEntry -@pytest.fixture(name="panasonic_viera_setup", autouse=True) -def panasonic_viera_setup_fixture(): - """Mock panasonic_viera setup.""" - with patch( - "homeassistant.components.panasonic_viera.async_setup", return_value=True - ), patch( - "homeassistant.components.panasonic_viera.async_setup_entry", - return_value=True, - ): - yield - - -def get_mock_remote( - host="1.2.3.4", - request_error=None, - authorize_error=None, - encrypted=False, - app_id=None, - encryption_key=None, - name=DEFAULT_NAME, - manufacturer=DEFAULT_MANUFACTURER, - model_number=DEFAULT_MODEL_NUMBER, - unique_id="mock-unique-id", -): - """Return a mock remote.""" - mock_remote = Mock() - - mock_remote.type = TV_TYPE_ENCRYPTED if encrypted else TV_TYPE_NONENCRYPTED - mock_remote.app_id = app_id - mock_remote.enc_key = encryption_key - - def request_pin_code(name=None): - if request_error is not None: - raise request_error - - mock_remote.request_pin_code = request_pin_code - - def authorize_pin_code(pincode): - if pincode == "1234": - return - - if authorize_error is not None: - raise authorize_error - - mock_remote.authorize_pin_code = authorize_pin_code - - def get_device_info(): - return { - ATTR_FRIENDLY_NAME: name, - ATTR_MANUFACTURER: manufacturer, - ATTR_MODEL_NUMBER: model_number, - ATTR_UDN: unique_id, - } - - mock_remote.get_device_info = get_device_info - - return mock_remote - - async def test_flow_non_encrypted(hass): """Test flow without encryption.""" @@ -103,23 +41,12 @@ async def test_flow_non_encrypted(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME - assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: None, - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, - } + assert result["data"] == {**MOCK_CONFIG_DATA, ATTR_DEVICE_INFO: MOCK_DEVICE_INFO} async def test_flow_not_connected_error(hass): @@ -138,7 +65,7 @@ async def test_flow_not_connected_error(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -162,7 +89,7 @@ async def test_flow_unknown_abort(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -187,7 +114,7 @@ async def test_flow_encrypted_not_connected_pin_code_request(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -212,7 +139,7 @@ async def test_flow_encrypted_unknown_pin_code_request(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -231,8 +158,8 @@ async def test_flow_encrypted_valid_pin_code(hass): mock_remote = get_mock_remote( encrypted=True, - app_id="test-app-id", - encryption_key="test-encryption-key", + app_id="mock-app-id", + encryption_key="mock-encryption-key", ) with patch( @@ -241,7 +168,7 @@ async def test_flow_encrypted_valid_pin_code(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -255,18 +182,9 @@ async def test_flow_encrypted_valid_pin_code(hass): assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: None, - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, + **MOCK_CONFIG_DATA, + **MOCK_ENCRYPTION_DATA, + ATTR_DEVICE_INFO: MOCK_DEVICE_INFO, } @@ -288,7 +206,7 @@ async def test_flow_encrypted_invalid_pin_code_error(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -326,7 +244,7 @@ async def test_flow_encrypted_not_connected_abort(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -359,7 +277,7 @@ async def test_flow_encrypted_unknown_abort(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + MOCK_BASIC_DATA, ) assert result["type"] == "form" @@ -379,14 +297,14 @@ async def test_flow_non_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME, CONF_PORT: DEFAULT_PORT}, + unique_id="0.0.0.0", + data=MOCK_CONFIG_DATA, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -398,20 +316,14 @@ async def test_flow_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - }, + unique_id="0.0.0.0", + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA}, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -430,28 +342,12 @@ async def test_imported_flow_non_encrypted(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME - assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, - } + assert result["data"] == {**MOCK_CONFIG_DATA, ATTR_DEVICE_INFO: MOCK_DEVICE_INFO} async def test_imported_flow_encrypted_valid_pin_code(hass): @@ -459,8 +355,8 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): mock_remote = get_mock_remote( encrypted=True, - app_id="test-app-id", - encryption_key="test-encryption-key", + app_id="mock-app-id", + encryption_key="mock-encryption-key", ) with patch( @@ -470,12 +366,7 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -489,18 +380,9 @@ async def test_imported_flow_encrypted_valid_pin_code(hass): assert result["type"] == "create_entry" assert result["title"] == DEFAULT_NAME assert result["data"] == { - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - ATTR_DEVICE_INFO: { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", - }, + **MOCK_CONFIG_DATA, + **MOCK_ENCRYPTION_DATA, + ATTR_DEVICE_INFO: MOCK_DEVICE_INFO, } @@ -516,12 +398,7 @@ async def test_imported_flow_encrypted_invalid_pin_code_error(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -553,12 +430,7 @@ async def test_imported_flow_encrypted_not_connected_abort(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -585,12 +457,7 @@ async def test_imported_flow_encrypted_unknown_abort(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -615,12 +482,7 @@ async def test_imported_flow_not_connected_error(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "form" @@ -638,12 +500,7 @@ async def test_imported_flow_unknown_abort(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + data=MOCK_CONFIG_DATA, ) assert result["type"] == "abort" @@ -655,19 +512,14 @@ async def test_imported_flow_non_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - }, + unique_id="0.0.0.0", + data=MOCK_CONFIG_DATA, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" @@ -679,21 +531,14 @@ async def test_imported_flow_encrypted_already_configured_abort(hass): MockConfigEntry( domain=DOMAIN, - unique_id="1.2.3.4", - data={ - CONF_HOST: "1.2.3.4", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: "test-on-action", - CONF_APP_ID: "test-app-id", - CONF_ENCRYPTION_KEY: "test-encryption-key", - }, + unique_id="0.0.0.0", + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA}, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME}, + data=MOCK_BASIC_DATA, ) assert result["type"] == "abort" diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py index 5c9bf183c6f60a..7351b4e5544a1b 100644 --- a/tests/components/panasonic_viera/test_init.py +++ b/tests/components/panasonic_viera/test_init.py @@ -1,65 +1,27 @@ """Test the Panasonic Viera setup process.""" -from unittest.mock import Mock, patch +from unittest.mock import patch from homeassistant.components.panasonic_viera.const import ( ATTR_DEVICE_INFO, - ATTR_FRIENDLY_NAME, - ATTR_MANUFACTURER, - ATTR_MODEL_NUMBER, ATTR_UDN, - CONF_APP_ID, - CONF_ENCRYPTION_KEY, - CONF_ON_ACTION, - DEFAULT_MANUFACTURER, - DEFAULT_MODEL_NUMBER, DEFAULT_NAME, - DEFAULT_PORT, DOMAIN, ) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_UNAVAILABLE +from homeassistant.const import CONF_HOST, STATE_UNAVAILABLE from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry - -MOCK_CONFIG_DATA = { - CONF_HOST: "0.0.0.0", - CONF_NAME: DEFAULT_NAME, - CONF_PORT: DEFAULT_PORT, - CONF_ON_ACTION: None, -} - -MOCK_ENCRYPTION_DATA = { - CONF_APP_ID: "mock-app-id", - CONF_ENCRYPTION_KEY: "mock-encryption-key", -} - -MOCK_DEVICE_INFO = { - ATTR_FRIENDLY_NAME: DEFAULT_NAME, - ATTR_MANUFACTURER: DEFAULT_MANUFACTURER, - ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER, - ATTR_UDN: "mock-unique-id", -} - - -def get_mock_remote(device_info=MOCK_DEVICE_INFO): - """Return a mock remote.""" - mock_remote = Mock() - - async def async_create_remote_control(during_setup=False): - return - - mock_remote.async_create_remote_control = async_create_remote_control - - async def async_get_device_info(): - return device_info - - mock_remote.async_get_device_info = async_get_device_info +from .conftest import ( + MOCK_CONFIG_DATA, + MOCK_DEVICE_INFO, + MOCK_ENCRYPTION_DATA, + get_mock_remote, +) - return mock_remote +from tests.common import MockConfigEntry -async def test_setup_entry_encrypted(hass): +async def test_setup_entry_encrypted(hass, mock_remote): """Test setup with encrypted config entry.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -69,22 +31,20 @@ async def test_setup_entry_encrypted(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME -async def test_setup_entry_encrypted_missing_device_info(hass): +async def test_setup_entry_encrypted_missing_device_info(hass, mock_remote): """Test setup with encrypted config entry and missing device info.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -94,22 +54,20 @@ async def test_setup_entry_encrypted_missing_device_info(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO + assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] - assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO - assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME async def test_setup_entry_encrypted_missing_device_info_none(hass): @@ -125,7 +83,7 @@ async def test_setup_entry_encrypted_missing_device_info_none(hass): mock_remote = get_mock_remote(device_info=None) with patch( - "homeassistant.components.panasonic_viera.Remote", + "homeassistant.components.panasonic_viera.RemoteControl", return_value=mock_remote, ): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -134,13 +92,17 @@ async def test_setup_entry_encrypted_missing_device_info_none(hass): assert mock_entry.data[ATTR_DEVICE_INFO] is None assert mock_entry.unique_id == MOCK_CONFIG_DATA[CONF_HOST] - state = hass.states.get("media_player.panasonic_viera_tv") + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") + + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME -async def test_setup_entry_unencrypted(hass): +async def test_setup_entry_unencrypted(hass, mock_remote): """Test setup with unencrypted config entry.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -150,22 +112,20 @@ async def test_setup_entry_unencrypted(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME -async def test_setup_entry_unencrypted_missing_device_info(hass): +async def test_setup_entry_unencrypted_missing_device_info(hass, mock_remote): """Test setup with unencrypted config entry and missing device info.""" mock_entry = MockConfigEntry( domain=DOMAIN, @@ -175,22 +135,20 @@ async def test_setup_entry_unencrypted_missing_device_info(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO + assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] - assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO - assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN] + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - state = hass.states.get("media_player.panasonic_viera_tv") + assert state_tv + assert state_tv.name == DEFAULT_NAME - assert state - assert state.name == DEFAULT_NAME + assert state_remote + assert state_remote.name == DEFAULT_NAME async def test_setup_entry_unencrypted_missing_device_info_none(hass): @@ -206,7 +164,7 @@ async def test_setup_entry_unencrypted_missing_device_info_none(hass): mock_remote = get_mock_remote(device_info=None) with patch( - "homeassistant.components.panasonic_viera.Remote", + "homeassistant.components.panasonic_viera.RemoteControl", return_value=mock_remote, ): await hass.config_entries.async_setup(mock_entry.entry_id) @@ -215,10 +173,14 @@ async def test_setup_entry_unencrypted_missing_device_info_none(hass): assert mock_entry.data[ATTR_DEVICE_INFO] is None assert mock_entry.unique_id == MOCK_CONFIG_DATA[CONF_HOST] - state = hass.states.get("media_player.panasonic_viera_tv") + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") - assert state - assert state.name == DEFAULT_NAME + assert state_tv + assert state_tv.name == DEFAULT_NAME + + assert state_remote + assert state_remote.name == DEFAULT_NAME async def test_setup_config_flow_initiated(hass): @@ -235,7 +197,7 @@ async def test_setup_config_flow_initiated(hass): assert len(hass.config_entries.flow.async_progress()) == 1 -async def test_setup_unload_entry(hass): +async def test_setup_unload_entry(hass, mock_remote): """Test if config entry is unloaded.""" mock_entry = MockConfigEntry( domain=DOMAIN, unique_id=MOCK_DEVICE_INFO[ATTR_UDN], data=MOCK_CONFIG_DATA @@ -243,21 +205,23 @@ async def test_setup_unload_entry(hass): mock_entry.add_to_hass(hass) - mock_remote = get_mock_remote() - - with patch( - "homeassistant.components.panasonic_viera.Remote", - return_value=mock_remote, - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() await hass.config_entries.async_unload(mock_entry.entry_id) assert mock_entry.state == ENTRY_STATE_NOT_LOADED - state = hass.states.get("media_player.panasonic_viera_tv") - assert state.state == STATE_UNAVAILABLE + + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") + + assert state_tv.state == STATE_UNAVAILABLE + assert state_remote.state == STATE_UNAVAILABLE await hass.config_entries.async_remove(mock_entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("media_player.panasonic_viera_tv") - assert state is None + + state_tv = hass.states.get("media_player.panasonic_viera_tv") + state_remote = hass.states.get("remote.panasonic_viera_tv") + + assert state_tv is None + assert state_remote is None diff --git a/tests/components/panasonic_viera/test_remote.py b/tests/components/panasonic_viera/test_remote.py new file mode 100644 index 00000000000000..6bfd7dee8eb3bd --- /dev/null +++ b/tests/components/panasonic_viera/test_remote.py @@ -0,0 +1,58 @@ +"""Test the Panasonic Viera remote entity.""" + +from unittest.mock import call + +from panasonic_viera import Keys + +from homeassistant.components.panasonic_viera.const import ATTR_UDN, DOMAIN +from homeassistant.components.remote import ( + ATTR_COMMAND, + DOMAIN as REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, +) +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON + +from .conftest import MOCK_CONFIG_DATA, MOCK_DEVICE_INFO, MOCK_ENCRYPTION_DATA + +from tests.common import MockConfigEntry + + +async def setup_panasonic_viera(hass): + """Initialize integration for tests.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=MOCK_DEVICE_INFO[ATTR_UDN], + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA, **MOCK_DEVICE_INFO}, + ) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + +async def test_onoff(hass, mock_remote): + """Test the on/off service calls.""" + + await setup_panasonic_viera(hass) + + data = {ATTR_ENTITY_ID: "remote.panasonic_viera_tv"} + + await hass.services.async_call(REMOTE_DOMAIN, SERVICE_TURN_OFF, data) + await hass.services.async_call(REMOTE_DOMAIN, SERVICE_TURN_ON, data) + await hass.async_block_till_done() + + power = getattr(Keys.power, "value", Keys.power) + assert mock_remote.send_key.call_args_list == [call(power), call(power)] + + +async def test_send_command(hass, mock_remote): + """Test the send_command service call.""" + + await setup_panasonic_viera(hass) + + data = {ATTR_ENTITY_ID: "remote.panasonic_viera_tv", ATTR_COMMAND: "command"} + await hass.services.async_call(REMOTE_DOMAIN, SERVICE_SEND_COMMAND, data) + await hass.async_block_till_done() + + assert mock_remote.send_key.call_args == call("command") From 09bafafee2238d53d845a1f6f51c7fe08c51645e Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 25 Feb 2021 15:26:00 +0100 Subject: [PATCH 0779/1818] Bump gios library to version 0.2.0 (#47050) --- homeassistant/components/gios/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index 468e22260b514a..ff1f82e6ce3eaa 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -3,7 +3,7 @@ "name": "GIOŚ", "documentation": "https://www.home-assistant.io/integrations/gios", "codeowners": ["@bieniu"], - "requirements": ["gios==0.1.5"], + "requirements": ["gios==0.2.0"], "config_flow": true, "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index b45cf562fc1953..948f786269bc33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -655,7 +655,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.1.5 +gios==0.2.0 # homeassistant.components.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ba5c9222a65e97..9749e1c8574a32 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -349,7 +349,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.1.5 +gios==0.2.0 # homeassistant.components.glances glances_api==0.2.0 From 5bba532dd46be758bc2879fb666b9f2f4e9d15c7 Mon Sep 17 00:00:00 2001 From: Johan Josua Storm Date: Thu, 25 Feb 2021 15:38:45 +0100 Subject: [PATCH 0780/1818] Replace wrong domain returned from xbox api 2.0 (#47021) * Change solution to use yarl lib * Add check if base url needs changing * Actively remove padding query instead of omitting * Fixed popping the wrong query * Change explaination about removing mode query --- homeassistant/components/xbox/base_sensor.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/xbox/base_sensor.py b/homeassistant/components/xbox/base_sensor.py index 028f1d4c9ecb36..d19fbfb918d6ea 100644 --- a/homeassistant/components/xbox/base_sensor.py +++ b/homeassistant/components/xbox/base_sensor.py @@ -1,6 +1,8 @@ """Base Sensor for the Xbox Integration.""" from typing import Optional +from yarl import URL + from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import PresenceData, XboxUpdateCoordinator @@ -44,7 +46,17 @@ def entity_picture(self) -> str: if not self.data: return None - return self.data.display_pic.replace("&mode=Padding", "") + # Xbox sometimes returns a domain that uses a wrong certificate which creates issues + # with loading the image. + # The correct domain is images-eds-ssl which can just be replaced + # to point to the correct image, with the correct domain and certificate. + # We need to also remove the 'mode=Padding' query because with it, it results in an error 400. + url = URL(self.data.display_pic) + if url.host == "images-eds.xboxlive.com": + url = url.with_host("images-eds-ssl.xboxlive.com") + query = dict(url.query) + query.pop("mode", None) + return str(url.with_query(query)) @property def entity_registry_enabled_default(self) -> bool: From 125206adbf85cf49bf72336db658f7776b924fe5 Mon Sep 17 00:00:00 2001 From: Quentame Date: Thu, 25 Feb 2021 16:50:58 +0100 Subject: [PATCH 0781/1818] Add zeroconf discovery to Freebox (#47045) * Add zeroconf discovery to Freebox - remove deprecated discovery - tried with SSDP too but the presentation URL is not the same (*.fbxos.fr for zeroconf, http://mafreebox.freebox.fr/ for SSDP) - so config entry unique_id should be the MAC (included into SSDP, but not zeroconf, can be retrieve from `fbx.system.get_config()`) - DHCP discovery might be added in the future too * host and port are required on zeroconf * cleanup in other PR --- .../components/discovery/__init__.py | 2 -- homeassistant/components/freebox/__init__.py | 22 +++++-------- .../components/freebox/config_flow.py | 6 ++++ tests/components/freebox/test_config_flow.py | 32 ++++++++++++++++++- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 0a3deeef33bfb7..883958226d8d09 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -22,7 +22,6 @@ SERVICE_DAIKIN = "daikin" SERVICE_DLNA_DMR = "dlna_dmr" SERVICE_ENIGMA2 = "enigma2" -SERVICE_FREEBOX = "freebox" SERVICE_HASS_IOS_APP = "hass_ios" SERVICE_HASSIO = "hassio" SERVICE_HEOS = "heos" @@ -67,7 +66,6 @@ SERVICE_DAIKIN, "denonavr", "esphome", - SERVICE_FREEBOX, "google_cast", SERVICE_HASS_IOS_APP, SERVICE_HASSIO, diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index 35e89eb2b09fa3..f36a2303b6d7c1 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -25,26 +25,20 @@ async def async_setup(hass, config): - """Set up the Freebox component.""" - conf = config.get(DOMAIN) - - if conf is None: - return True - - for freebox_conf in conf: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=freebox_conf, + """Set up the Freebox integration.""" + if DOMAIN in config: + for entry_config in config[DOMAIN]: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry_config + ) ) - ) return True async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): - """Set up Freebox component.""" + """Set up Freebox entry.""" router = FreeboxRouter(hass, entry) await router.setup() diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 49354f16705fa2..f4fde23473f149 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -105,3 +105,9 @@ async def async_step_link(self, user_input=None): async def async_step_import(self, user_input=None): """Import a config entry.""" return await self.async_step_user(user_input) + + async def async_step_zeroconf(self, discovery_info: dict): + """Initialize flow from zeroconf.""" + host = discovery_info["properties"]["api_domain"] + port = discovery_info["properties"]["https_port"] + return await self.async_step_user({CONF_HOST: host, CONF_PORT: port}) diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index ad935c47cc47e5..5f3aace94652e3 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -10,7 +10,7 @@ from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_PORT from tests.common import MockConfigEntry @@ -18,6 +18,25 @@ HOST = "myrouter.freeboxos.fr" PORT = 1234 +MOCK_ZEROCONF_DATA = { + "host": "192.168.0.254", + "port": 80, + "hostname": "Freebox-Server.local.", + "type": "_fbx-api._tcp.local.", + "name": "Freebox Server._fbx-api._tcp.local.", + "properties": { + "api_version": "8.0", + "device_type": "FreeboxServer1,2", + "api_base_url": "/api/", + "uid": "b15ab20debb399f95001a9ca207d2777", + "https_available": "1", + "https_port": f"{PORT}", + "box_model": "fbxgw-r2/full", + "box_model_name": "Freebox Server (r2)", + "api_domain": HOST, + }, +} + @pytest.fixture(name="connect") def mock_controller_connect(): @@ -66,6 +85,17 @@ async def test_import(hass): assert result["step_id"] == "link" +async def test_zeroconf(hass): + """Test zeroconf step.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DATA, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "link" + + async def test_link(hass, connect): """Test linking.""" result = await hass.config_entries.flow.async_init( From 4e09d7ee0feadb25f3c318c7dc931586bb40c427 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 25 Feb 2021 16:53:59 +0100 Subject: [PATCH 0782/1818] Clean up Netatmo webhook handler (#47037) --- homeassistant/components/netatmo/__init__.py | 10 ++++---- homeassistant/components/netatmo/webhook.py | 25 ++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index cdbd34991f2473..5827486429ab79 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -43,7 +43,7 @@ OAUTH2_TOKEN, ) from .data_handler import NetatmoDataHandler -from .webhook import handle_webhook +from .webhook import async_handle_webhook _LOGGER = logging.getLogger(__name__) @@ -157,18 +157,20 @@ async def register_webhook(event): try: webhook_register( - hass, DOMAIN, "Netatmo", entry.data[CONF_WEBHOOK_ID], handle_webhook + hass, + DOMAIN, + "Netatmo", + entry.data[CONF_WEBHOOK_ID], + async_handle_webhook, ) async def handle_event(event): """Handle webhook events.""" if event["data"]["push_type"] == "webhook_activation": if activation_listener is not None: - _LOGGER.debug("sub called") activation_listener() if activation_timeout is not None: - _LOGGER.debug("Unsub called") activation_timeout() activation_listener = async_dispatcher_connect( diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 1fe7302038eae2..5ecc3d41789522 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -1,14 +1,13 @@ """The Netatmo integration.""" import logging -from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID, ATTR_NAME from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( ATTR_EVENT_TYPE, ATTR_FACE_URL, ATTR_IS_KNOWN, - ATTR_NAME, ATTR_PERSONS, DATA_DEVICE_IDS, DATA_PERSONS, @@ -20,13 +19,13 @@ _LOGGER = logging.getLogger(__name__) -EVENT_TYPE_MAP = { +SUBEVENT_TYPE_MAP = { "outdoor": "", "therm_mode": "", } -async def handle_webhook(hass, webhook_id, request): +async def async_handle_webhook(hass, webhook_id, request): """Handle webhook callback.""" try: data = await request.json() @@ -38,17 +37,17 @@ async def handle_webhook(hass, webhook_id, request): event_type = data.get(ATTR_EVENT_TYPE) - if event_type in EVENT_TYPE_MAP: - await async_send_event(hass, event_type, data) + if event_type in SUBEVENT_TYPE_MAP: + async_send_event(hass, event_type, data) - for event_data in data.get(EVENT_TYPE_MAP[event_type], []): - await async_evaluate_event(hass, event_data) + for event_data in data.get(SUBEVENT_TYPE_MAP[event_type], []): + async_evaluate_event(hass, event_data) else: - await async_evaluate_event(hass, data) + async_evaluate_event(hass, data) -async def async_evaluate_event(hass, event_data): +def async_evaluate_event(hass, event_data): """Evaluate events from webhook.""" event_type = event_data.get(ATTR_EVENT_TYPE) @@ -62,13 +61,13 @@ async def async_evaluate_event(hass, event_data): person_event_data[ATTR_IS_KNOWN] = person.get(ATTR_IS_KNOWN) person_event_data[ATTR_FACE_URL] = person.get(ATTR_FACE_URL) - await async_send_event(hass, event_type, person_event_data) + async_send_event(hass, event_type, person_event_data) else: - await async_send_event(hass, event_type, event_data) + async_send_event(hass, event_type, event_data) -async def async_send_event(hass, event_type, data): +def async_send_event(hass, event_type, data): """Send events.""" _LOGGER.debug("%s: %s", event_type, data) async_dispatcher_send( From 3a829174007edd171b1b187d47a83934fa26f15e Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Thu, 25 Feb 2021 17:39:57 +0100 Subject: [PATCH 0783/1818] Bump python-garminconnect to 0.1.19 to fix broken api (#47020) --- homeassistant/components/garmin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/garmin_connect/manifest.json b/homeassistant/components/garmin_connect/manifest.json index c7880f9b416a65..59597750ce8123 100644 --- a/homeassistant/components/garmin_connect/manifest.json +++ b/homeassistant/components/garmin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "garmin_connect", "name": "Garmin Connect", "documentation": "https://www.home-assistant.io/integrations/garmin_connect", - "requirements": ["garminconnect==0.1.16"], + "requirements": ["garminconnect==0.1.19"], "codeowners": ["@cyberjunky"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 948f786269bc33..341487e3e35cca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -623,7 +623,7 @@ fritzconnection==1.4.0 gTTS==2.2.2 # homeassistant.components.garmin_connect -garminconnect==0.1.16 +garminconnect==0.1.19 # homeassistant.components.geizhals geizhals==0.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9749e1c8574a32..5633b1557de6ce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -323,7 +323,7 @@ fritzconnection==1.4.0 gTTS==2.2.2 # homeassistant.components.garmin_connect -garminconnect==0.1.16 +garminconnect==0.1.19 # homeassistant.components.geo_json_events # homeassistant.components.usgs_earthquakes_feed From f4db74fe732bdda2dfa7d806b01e8e17a8354e0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 11:08:04 -0600 Subject: [PATCH 0784/1818] Fix bond typing in config flow (#47055) --- homeassistant/components/bond/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index f81e3a0be5c1ca..f4e9babdff447e 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -59,7 +59,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize config flow.""" - self._discovered: dict = None + self._discovered: Optional[dict] = None async def _async_try_automatic_configure(self): """Try to auto configure the device. From 7d90cdea1e751436ad148cd27cdc48455ca5b827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 25 Feb 2021 19:52:11 +0100 Subject: [PATCH 0785/1818] Use dispatch instead of eventbus for supervisor events (#46986) Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/const.py | 3 +- .../components/hassio/websocket_api.py | 27 ++++- tests/components/hassio/__init__.py | 45 +++++++ tests/components/hassio/test_init.py | 114 +----------------- tests/components/hassio/test_websocket_api.py | 90 ++++++++++++++ 5 files changed, 164 insertions(+), 115 deletions(-) create mode 100644 tests/components/hassio/test_websocket_api.py diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index a3e4451312a3da..b2878c8143f3fd 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -33,7 +33,8 @@ WS_TYPE = "type" WS_ID = "id" -WS_TYPE_EVENT = "supervisor/event" WS_TYPE_API = "supervisor/api" +WS_TYPE_EVENT = "supervisor/event" +WS_TYPE_SUBSCRIBE = "supervisor/subscribe" EVENT_SUPERVISOR_EVENT = "supervisor_event" diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index d2c0bc9ed104cf..387aa9264891a4 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -7,6 +7,10 @@ from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from .const import ( ATTR_DATA, @@ -20,6 +24,7 @@ WS_TYPE, WS_TYPE_API, WS_TYPE_EVENT, + WS_TYPE_SUBSCRIBE, ) from .handler import HassIO @@ -36,6 +41,26 @@ def async_load_websocket_api(hass: HomeAssistant): """Set up the websocket API.""" websocket_api.async_register_command(hass, websocket_supervisor_event) websocket_api.async_register_command(hass, websocket_supervisor_api) + websocket_api.async_register_command(hass, websocket_subscribe) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required(WS_TYPE): WS_TYPE_SUBSCRIBE}) +async def websocket_subscribe( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +): + """Subscribe to supervisor events.""" + + @callback + def forward_messages(data): + """Forward events to websocket.""" + connection.send_message(websocket_api.event_message(msg[WS_ID], data)) + + connection.subscriptions[msg[WS_ID]] = async_dispatcher_connect( + hass, EVENT_SUPERVISOR_EVENT, forward_messages + ) + connection.send_message(websocket_api.result_message(msg[WS_ID])) @websocket_api.async_response @@ -49,7 +74,7 @@ async def websocket_supervisor_event( hass: HomeAssistant, connection: ActiveConnection, msg: dict ): """Publish events from the Supervisor.""" - hass.bus.async_fire(EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) + async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) connection.send_result(msg[WS_ID]) diff --git a/tests/components/hassio/__init__.py b/tests/components/hassio/__init__.py index ad9829f17ff3bb..f3f35b62562a75 100644 --- a/tests/components/hassio/__init__.py +++ b/tests/components/hassio/__init__.py @@ -1,3 +1,48 @@ """Tests for Hass.io component.""" +import pytest HASSIO_TOKEN = "123456" + + +@pytest.fixture(autouse=True) +def mock_all(aioclient_mock): + """Mock all setup requests.""" + aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) + aioclient_mock.get( + "http://127.0.0.1/info", + json={ + "result": "ok", + "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/host/info", + json={ + "result": "ok", + "data": { + "result": "ok", + "data": { + "chassis": "vm", + "operating_system": "Debian GNU/Linux 10 (buster)", + "kernel": "4.19.0-6-amd64", + }, + }, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/os/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/supervisor/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} + ) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index eaeed74fbf7c43..2efb5b0744e9d6 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -2,73 +2,16 @@ import os from unittest.mock import patch -import pytest - from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend from homeassistant.components.hassio import STORAGE_KEY -from homeassistant.components.hassio.const import ( - ATTR_DATA, - ATTR_ENDPOINT, - ATTR_METHOD, - EVENT_SUPERVISOR_EVENT, - WS_ID, - WS_TYPE, - WS_TYPE_API, - WS_TYPE_EVENT, -) -from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import async_capture_events +from . import mock_all # noqa MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} -@pytest.fixture(autouse=True) -def mock_all(aioclient_mock): - """Mock all setup requests.""" - aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) - aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) - aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) - aioclient_mock.get( - "http://127.0.0.1/info", - json={ - "result": "ok", - "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, - }, - ) - aioclient_mock.get( - "http://127.0.0.1/host/info", - json={ - "result": "ok", - "data": { - "result": "ok", - "data": { - "chassis": "vm", - "operating_system": "Debian GNU/Linux 10 (buster)", - "kernel": "4.19.0-6-amd64", - }, - }, - }, - ) - aioclient_mock.get( - "http://127.0.0.1/core/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/os/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/supervisor/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} - ) - - async def test_setup_api_ping(hass, aioclient_mock): """Test setup with API ping.""" with patch.dict(os.environ, MOCK_ENVIRON): @@ -359,58 +302,3 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert mock_check_config.called assert aioclient_mock.call_count == 5 - - -async def test_websocket_supervisor_event( - hassio_env, hass: HomeAssistant, hass_ws_client -): - """Test Supervisor websocket event.""" - assert await async_setup_component(hass, "hassio", {}) - websocket_client = await hass_ws_client(hass) - - test_event = async_capture_events(hass, EVENT_SUPERVISOR_EVENT) - - await websocket_client.send_json( - {WS_ID: 1, WS_TYPE: WS_TYPE_EVENT, ATTR_DATA: {"event": "test"}} - ) - - assert await websocket_client.receive_json() - await hass.async_block_till_done() - - assert test_event[0].data == {"event": "test"} - - -async def test_websocket_supervisor_api( - hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock -): - """Test Supervisor websocket api.""" - assert await async_setup_component(hass, "hassio", {}) - websocket_client = await hass_ws_client(hass) - aioclient_mock.post( - "http://127.0.0.1/snapshots/new/partial", - json={"result": "ok", "data": {"slug": "sn_slug"}}, - ) - - await websocket_client.send_json( - { - WS_ID: 1, - WS_TYPE: WS_TYPE_API, - ATTR_ENDPOINT: "/snapshots/new/partial", - ATTR_METHOD: "post", - } - ) - - msg = await websocket_client.receive_json() - assert msg["result"]["slug"] == "sn_slug" - - await websocket_client.send_json( - { - WS_ID: 2, - WS_TYPE: WS_TYPE_API, - ATTR_ENDPOINT: "/supervisor/info", - ATTR_METHOD: "get", - } - ) - - msg = await websocket_client.receive_json() - assert msg["result"]["version_latest"] == "1.0.0" diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py new file mode 100644 index 00000000000000..18da5df13ea956 --- /dev/null +++ b/tests/components/hassio/test_websocket_api.py @@ -0,0 +1,90 @@ +"""Test websocket API.""" +from homeassistant.components.hassio.const import ( + ATTR_DATA, + ATTR_ENDPOINT, + ATTR_METHOD, + ATTR_WS_EVENT, + EVENT_SUPERVISOR_EVENT, + WS_ID, + WS_TYPE, + WS_TYPE_API, + WS_TYPE_SUBSCRIBE, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +from . import mock_all # noqa + +from tests.common import async_mock_signal + + +async def test_ws_subscription(hassio_env, hass: HomeAssistant, hass_ws_client): + """Test websocket subscription.""" + assert await async_setup_component(hass, "hassio", {}) + client = await hass_ws_client(hass) + await client.send_json({WS_ID: 5, WS_TYPE: WS_TYPE_SUBSCRIBE}) + response = await client.receive_json() + assert response["success"] + + calls = async_mock_signal(hass, EVENT_SUPERVISOR_EVENT) + async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, {"lorem": "ipsum"}) + + response = await client.receive_json() + assert response["event"]["lorem"] == "ipsum" + assert len(calls) == 1 + + await client.send_json( + { + WS_ID: 6, + WS_TYPE: "supervisor/event", + ATTR_DATA: {ATTR_WS_EVENT: "test", "lorem": "ipsum"}, + } + ) + response = await client.receive_json() + assert response["success"] + assert len(calls) == 2 + + response = await client.receive_json() + assert response["event"]["lorem"] == "ipsum" + + # Unsubscribe + await client.send_json({WS_ID: 7, WS_TYPE: "unsubscribe_events", "subscription": 5}) + response = await client.receive_json() + assert response["success"] + + +async def test_websocket_supervisor_api( + hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock +): + """Test Supervisor websocket api.""" + assert await async_setup_component(hass, "hassio", {}) + websocket_client = await hass_ws_client(hass) + aioclient_mock.post( + "http://127.0.0.1/snapshots/new/partial", + json={"result": "ok", "data": {"slug": "sn_slug"}}, + ) + + await websocket_client.send_json( + { + WS_ID: 1, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/snapshots/new/partial", + ATTR_METHOD: "post", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["slug"] == "sn_slug" + + await websocket_client.send_json( + { + WS_ID: 2, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/supervisor/info", + ATTR_METHOD: "get", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["version_latest"] == "1.0.0" From 2fef4c4eef76fd70f2ccffeda4f5e24f51dd1fcc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 01:16:20 -0600 Subject: [PATCH 0786/1818] Ensure doorbird events are re-registered when changing options (#46860) - Fixed the update listener not being unsubscribed - DRY up some of the code - Fix sync code being called in async - Reduce executor jumps --- homeassistant/components/doorbird/__init__.py | 43 ++++++++++--------- homeassistant/components/doorbird/const.py | 2 + homeassistant/components/doorbird/switch.py | 5 ++- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 1dc5bf56c8638c..22db3c762736bf 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -34,6 +34,7 @@ DOOR_STATION_EVENT_ENTITY_IDS, DOOR_STATION_INFO, PLATFORMS, + UNDO_UPDATE_LISTENER, ) from .util import get_doorstation_by_token @@ -128,8 +129,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): device = DoorBird(device_ip, username, password) try: - status = await hass.async_add_executor_job(device.ready) - info = await hass.async_add_executor_job(device.info) + status, info = await hass.async_add_executor_job(_init_doorbird_device, device) except urllib.error.HTTPError as err: if err.code == HTTP_UNAUTHORIZED: _LOGGER.error( @@ -154,18 +154,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): custom_url = doorstation_config.get(CONF_CUSTOM_URL) name = doorstation_config.get(CONF_NAME) events = doorstation_options.get(CONF_EVENTS, []) - doorstation = ConfiguredDoorBird(device, name, events, custom_url, token) + doorstation = ConfiguredDoorBird(device, name, custom_url, token) + doorstation.update_events(events) # Subscribe to doorbell or motion events if not await _async_register_events(hass, doorstation): raise ConfigEntryNotReady + undo_listener = entry.add_update_listener(_update_listener) + hass.data[DOMAIN][config_entry_id] = { DOOR_STATION: doorstation, DOOR_STATION_INFO: info, + UNDO_UPDATE_LISTENER: undo_listener, } - entry.add_update_listener(_update_listener) - for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) @@ -174,9 +176,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True +def _init_doorbird_device(device): + return device.ready(), device.info() + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() + unload_ok = all( await asyncio.gather( *[ @@ -195,7 +203,7 @@ async def _async_register_events(hass, doorstation): try: await hass.async_add_executor_job(doorstation.register_events, hass) except HTTPError: - hass.components.persistent_notification.create( + hass.components.persistent_notification.async_create( "Doorbird configuration failed. Please verify that API " "Operator permission is enabled for the Doorbird user. " "A restart will be required once permissions have been " @@ -212,8 +220,7 @@ async def _update_listener(hass: HomeAssistant, entry: ConfigEntry): """Handle options update.""" config_entry_id = entry.entry_id doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] - - doorstation.events = entry.options[CONF_EVENTS] + doorstation.update_events(entry.options[CONF_EVENTS]) # Subscribe to doorbell or motion events await _async_register_events(hass, doorstation) @@ -234,14 +241,19 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi class ConfiguredDoorBird: """Attach additional information to pass along with configured device.""" - def __init__(self, device, name, events, custom_url, token): + def __init__(self, device, name, custom_url, token): """Initialize configured device.""" self._name = name self._device = device self._custom_url = custom_url + self.events = None + self.doorstation_events = None + self._token = token + + def update_events(self, events): + """Update the doorbird events.""" self.events = events self.doorstation_events = [self._get_event_name(event) for event in self.events] - self._token = token @property def name(self): @@ -305,16 +317,7 @@ def _register_event(self, hass_url, event): def webhook_is_registered(self, url, favs=None) -> bool: """Return whether the given URL is registered as a device favorite.""" - favs = favs if favs else self.device.favorites() - - if "http" not in favs: - return False - - for fav in favs["http"].values(): - if fav["value"] == url: - return True - - return False + return self.get_webhook_id(url, favs) is not None def get_webhook_id(self, url, favs=None) -> str or None: """ diff --git a/homeassistant/components/doorbird/const.py b/homeassistant/components/doorbird/const.py index af847dac673a26..46a95f0d5009ff 100644 --- a/homeassistant/components/doorbird/const.py +++ b/homeassistant/components/doorbird/const.py @@ -17,3 +17,5 @@ DOORBIRD_INFO_KEY_RELAYS = "RELAYS" DOORBIRD_INFO_KEY_PRIMARY_MAC_ADDR = "PRIMARY_MAC_ADDR" DOORBIRD_INFO_KEY_WIFI_MAC_ADDR = "WIFI_MAC_ADDR" + +UNDO_UPDATE_LISTENER = "undo_update_listener" diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py index f1f146aebb96fe..424bb79092f40b 100644 --- a/homeassistant/components/doorbird/switch.py +++ b/homeassistant/components/doorbird/switch.py @@ -17,8 +17,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] config_entry_id = config_entry.entry_id - doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION] - doorstation_info = hass.data[DOMAIN][config_entry_id][DOOR_STATION_INFO] + data = hass.data[DOMAIN][config_entry_id] + doorstation = data[DOOR_STATION] + doorstation_info = data[DOOR_STATION_INFO] relays = doorstation_info["RELAYS"] relays.append(IR_RELAY) From a58931280ab40cf9b27ff2bb2470eea2aef6eef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 25 Feb 2021 19:52:11 +0100 Subject: [PATCH 0787/1818] Use dispatch instead of eventbus for supervisor events (#46986) Co-authored-by: Martin Hjelmare Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/const.py | 3 +- .../components/hassio/websocket_api.py | 27 ++++- tests/components/hassio/__init__.py | 45 +++++++ tests/components/hassio/test_init.py | 114 +----------------- tests/components/hassio/test_websocket_api.py | 90 ++++++++++++++ 5 files changed, 164 insertions(+), 115 deletions(-) create mode 100644 tests/components/hassio/test_websocket_api.py diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index a3e4451312a3da..b2878c8143f3fd 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -33,7 +33,8 @@ WS_TYPE = "type" WS_ID = "id" -WS_TYPE_EVENT = "supervisor/event" WS_TYPE_API = "supervisor/api" +WS_TYPE_EVENT = "supervisor/event" +WS_TYPE_SUBSCRIBE = "supervisor/subscribe" EVENT_SUPERVISOR_EVENT = "supervisor_event" diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index d2c0bc9ed104cf..387aa9264891a4 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -7,6 +7,10 @@ from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from .const import ( ATTR_DATA, @@ -20,6 +24,7 @@ WS_TYPE, WS_TYPE_API, WS_TYPE_EVENT, + WS_TYPE_SUBSCRIBE, ) from .handler import HassIO @@ -36,6 +41,26 @@ def async_load_websocket_api(hass: HomeAssistant): """Set up the websocket API.""" websocket_api.async_register_command(hass, websocket_supervisor_event) websocket_api.async_register_command(hass, websocket_supervisor_api) + websocket_api.async_register_command(hass, websocket_subscribe) + + +@websocket_api.require_admin +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required(WS_TYPE): WS_TYPE_SUBSCRIBE}) +async def websocket_subscribe( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +): + """Subscribe to supervisor events.""" + + @callback + def forward_messages(data): + """Forward events to websocket.""" + connection.send_message(websocket_api.event_message(msg[WS_ID], data)) + + connection.subscriptions[msg[WS_ID]] = async_dispatcher_connect( + hass, EVENT_SUPERVISOR_EVENT, forward_messages + ) + connection.send_message(websocket_api.result_message(msg[WS_ID])) @websocket_api.async_response @@ -49,7 +74,7 @@ async def websocket_supervisor_event( hass: HomeAssistant, connection: ActiveConnection, msg: dict ): """Publish events from the Supervisor.""" - hass.bus.async_fire(EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) + async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, msg[ATTR_DATA]) connection.send_result(msg[WS_ID]) diff --git a/tests/components/hassio/__init__.py b/tests/components/hassio/__init__.py index ad9829f17ff3bb..f3f35b62562a75 100644 --- a/tests/components/hassio/__init__.py +++ b/tests/components/hassio/__init__.py @@ -1,3 +1,48 @@ """Tests for Hass.io component.""" +import pytest HASSIO_TOKEN = "123456" + + +@pytest.fixture(autouse=True) +def mock_all(aioclient_mock): + """Mock all setup requests.""" + aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) + aioclient_mock.get( + "http://127.0.0.1/info", + json={ + "result": "ok", + "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/host/info", + json={ + "result": "ok", + "data": { + "result": "ok", + "data": { + "chassis": "vm", + "operating_system": "Debian GNU/Linux 10 (buster)", + "kernel": "4.19.0-6-amd64", + }, + }, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/os/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/supervisor/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} + ) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index eaeed74fbf7c43..2efb5b0744e9d6 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -2,73 +2,16 @@ import os from unittest.mock import patch -import pytest - from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend from homeassistant.components.hassio import STORAGE_KEY -from homeassistant.components.hassio.const import ( - ATTR_DATA, - ATTR_ENDPOINT, - ATTR_METHOD, - EVENT_SUPERVISOR_EVENT, - WS_ID, - WS_TYPE, - WS_TYPE_API, - WS_TYPE_EVENT, -) -from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import async_capture_events +from . import mock_all # noqa MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} -@pytest.fixture(autouse=True) -def mock_all(aioclient_mock): - """Mock all setup requests.""" - aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) - aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) - aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) - aioclient_mock.get( - "http://127.0.0.1/info", - json={ - "result": "ok", - "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, - }, - ) - aioclient_mock.get( - "http://127.0.0.1/host/info", - json={ - "result": "ok", - "data": { - "result": "ok", - "data": { - "chassis": "vm", - "operating_system": "Debian GNU/Linux 10 (buster)", - "kernel": "4.19.0-6-amd64", - }, - }, - }, - ) - aioclient_mock.get( - "http://127.0.0.1/core/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/os/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/supervisor/info", - json={"result": "ok", "data": {"version_latest": "1.0.0"}}, - ) - aioclient_mock.get( - "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} - ) - - async def test_setup_api_ping(hass, aioclient_mock): """Test setup with API ping.""" with patch.dict(os.environ, MOCK_ENVIRON): @@ -359,58 +302,3 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert mock_check_config.called assert aioclient_mock.call_count == 5 - - -async def test_websocket_supervisor_event( - hassio_env, hass: HomeAssistant, hass_ws_client -): - """Test Supervisor websocket event.""" - assert await async_setup_component(hass, "hassio", {}) - websocket_client = await hass_ws_client(hass) - - test_event = async_capture_events(hass, EVENT_SUPERVISOR_EVENT) - - await websocket_client.send_json( - {WS_ID: 1, WS_TYPE: WS_TYPE_EVENT, ATTR_DATA: {"event": "test"}} - ) - - assert await websocket_client.receive_json() - await hass.async_block_till_done() - - assert test_event[0].data == {"event": "test"} - - -async def test_websocket_supervisor_api( - hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock -): - """Test Supervisor websocket api.""" - assert await async_setup_component(hass, "hassio", {}) - websocket_client = await hass_ws_client(hass) - aioclient_mock.post( - "http://127.0.0.1/snapshots/new/partial", - json={"result": "ok", "data": {"slug": "sn_slug"}}, - ) - - await websocket_client.send_json( - { - WS_ID: 1, - WS_TYPE: WS_TYPE_API, - ATTR_ENDPOINT: "/snapshots/new/partial", - ATTR_METHOD: "post", - } - ) - - msg = await websocket_client.receive_json() - assert msg["result"]["slug"] == "sn_slug" - - await websocket_client.send_json( - { - WS_ID: 2, - WS_TYPE: WS_TYPE_API, - ATTR_ENDPOINT: "/supervisor/info", - ATTR_METHOD: "get", - } - ) - - msg = await websocket_client.receive_json() - assert msg["result"]["version_latest"] == "1.0.0" diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py new file mode 100644 index 00000000000000..18da5df13ea956 --- /dev/null +++ b/tests/components/hassio/test_websocket_api.py @@ -0,0 +1,90 @@ +"""Test websocket API.""" +from homeassistant.components.hassio.const import ( + ATTR_DATA, + ATTR_ENDPOINT, + ATTR_METHOD, + ATTR_WS_EVENT, + EVENT_SUPERVISOR_EVENT, + WS_ID, + WS_TYPE, + WS_TYPE_API, + WS_TYPE_SUBSCRIBE, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +from . import mock_all # noqa + +from tests.common import async_mock_signal + + +async def test_ws_subscription(hassio_env, hass: HomeAssistant, hass_ws_client): + """Test websocket subscription.""" + assert await async_setup_component(hass, "hassio", {}) + client = await hass_ws_client(hass) + await client.send_json({WS_ID: 5, WS_TYPE: WS_TYPE_SUBSCRIBE}) + response = await client.receive_json() + assert response["success"] + + calls = async_mock_signal(hass, EVENT_SUPERVISOR_EVENT) + async_dispatcher_send(hass, EVENT_SUPERVISOR_EVENT, {"lorem": "ipsum"}) + + response = await client.receive_json() + assert response["event"]["lorem"] == "ipsum" + assert len(calls) == 1 + + await client.send_json( + { + WS_ID: 6, + WS_TYPE: "supervisor/event", + ATTR_DATA: {ATTR_WS_EVENT: "test", "lorem": "ipsum"}, + } + ) + response = await client.receive_json() + assert response["success"] + assert len(calls) == 2 + + response = await client.receive_json() + assert response["event"]["lorem"] == "ipsum" + + # Unsubscribe + await client.send_json({WS_ID: 7, WS_TYPE: "unsubscribe_events", "subscription": 5}) + response = await client.receive_json() + assert response["success"] + + +async def test_websocket_supervisor_api( + hassio_env, hass: HomeAssistant, hass_ws_client, aioclient_mock +): + """Test Supervisor websocket api.""" + assert await async_setup_component(hass, "hassio", {}) + websocket_client = await hass_ws_client(hass) + aioclient_mock.post( + "http://127.0.0.1/snapshots/new/partial", + json={"result": "ok", "data": {"slug": "sn_slug"}}, + ) + + await websocket_client.send_json( + { + WS_ID: 1, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/snapshots/new/partial", + ATTR_METHOD: "post", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["slug"] == "sn_slug" + + await websocket_client.send_json( + { + WS_ID: 2, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/supervisor/info", + ATTR_METHOD: "get", + } + ) + + msg = await websocket_client.receive_json() + assert msg["result"]["version_latest"] == "1.0.0" From a3cde9b19ea6fedfeb1107b3d36e20739050b400 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Thu, 25 Feb 2021 17:39:57 +0100 Subject: [PATCH 0788/1818] Bump python-garminconnect to 0.1.19 to fix broken api (#47020) --- homeassistant/components/garmin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/garmin_connect/manifest.json b/homeassistant/components/garmin_connect/manifest.json index c7880f9b416a65..59597750ce8123 100644 --- a/homeassistant/components/garmin_connect/manifest.json +++ b/homeassistant/components/garmin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "garmin_connect", "name": "Garmin Connect", "documentation": "https://www.home-assistant.io/integrations/garmin_connect", - "requirements": ["garminconnect==0.1.16"], + "requirements": ["garminconnect==0.1.19"], "codeowners": ["@cyberjunky"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 677ce0d0c2fd9b..b90aa3ce936324 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -626,7 +626,7 @@ fritzconnection==1.4.0 gTTS==2.2.2 # homeassistant.components.garmin_connect -garminconnect==0.1.16 +garminconnect==0.1.19 # homeassistant.components.geizhals geizhals==0.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 28478c4349aa2d..9773824d2210e5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -326,7 +326,7 @@ fritzconnection==1.4.0 gTTS==2.2.2 # homeassistant.components.garmin_connect -garminconnect==0.1.16 +garminconnect==0.1.19 # homeassistant.components.geo_json_events # homeassistant.components.usgs_earthquakes_feed From cb2dd6d908a84c1488fcb5527323296b0dbb63a6 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Thu, 25 Feb 2021 09:51:18 +0100 Subject: [PATCH 0789/1818] Fix missing Shelly external input (#47028) * Add support for external input (Shelly 1/1pm add-on) * Make external sensor naming consistent * Fix case consistency --- homeassistant/components/shelly/binary_sensor.py | 7 ++++++- homeassistant/components/shelly/sensor.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 8f99e6a7a6e0ed..18220fc9e3ac89 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -73,6 +73,11 @@ default_enabled=False, removal_condition=is_momentary_input, ), + ("sensor", "extInput"): BlockAttributeDescription( + name="External Input", + device_class=DEVICE_CLASS_POWER, + default_enabled=False, + ), ("sensor", "motion"): BlockAttributeDescription( name="Motion", device_class=DEVICE_CLASS_MOTION ), @@ -86,7 +91,7 @@ default_enabled=False, ), "fwupdate": RestAttributeDescription( - name="Firmware update", + name="Firmware Update", icon="mdi:update", value=lambda status, _: status["update"]["has_update"], default_enabled=False, diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 32fb33877d3e6d..472f3be4daeef4 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -133,7 +133,7 @@ available=lambda block: block.sensorOp == "normal", ), ("sensor", "extTemp"): BlockAttributeDescription( - name="Temperature", + name="External Temperature", unit=temperature_unit, value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_TEMPERATURE, @@ -155,7 +155,7 @@ icon="mdi:angle-acute", ), ("relay", "totalWorkTime"): BlockAttributeDescription( - name="Lamp life", + name="Lamp Life", unit=PERCENTAGE, icon="mdi:progress-wrench", value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1), From c797e0c8de88dd2f4e024df8110181b5c316e5d2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 25 Feb 2021 02:14:32 -0500 Subject: [PATCH 0790/1818] Fix zwave_js unique ID migration logic (#47031) --- homeassistant/components/zwave_js/__init__.py | 74 +++++++++++++----- .../components/zwave_js/discovery.py | 5 -- homeassistant/components/zwave_js/entity.py | 2 +- tests/components/zwave_js/test_init.py | 77 ++++++++++++++++++- 4 files changed, 130 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index cc58e31066a00f..75bc95b7fe4026 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -85,6 +85,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) + @callback + def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> None: + """Check if entity with old unique ID exists, and if so migrate it to new ID.""" + if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): + LOGGER.debug( + "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", + entity_id, + old_unique_id, + new_unique_id, + ) + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -97,26 +112,49 @@ def async_on_node_ready(node: ZwaveNode) -> None: for disc_info in async_discover_values(node): LOGGER.debug("Discovered entity: %s", disc_info) - # This migration logic was added in 2021.3 to handle breaking change to - # value_id format. Some time in the future, this code block - # (and get_old_value_id helper) can be removed. - old_value_id = get_old_value_id(disc_info.primary_value) - old_unique_id = get_unique_id( - client.driver.controller.home_id, old_value_id + # This migration logic was added in 2021.3 to handle a breaking change to + # the value_id format. Some time in the future, this code block + # (as well as get_old_value_id helper and migrate_entity closure) can be + # removed. + value_ids = [ + # 2021.2.* format + get_old_value_id(disc_info.primary_value), + # 2021.3.0b0 format + disc_info.primary_value.value_id, + ] + + new_unique_id = get_unique_id( + client.driver.controller.home_id, + disc_info.primary_value.value_id, ) - if entity_id := ent_reg.async_get_entity_id( - disc_info.platform, DOMAIN, old_unique_id - ): - LOGGER.debug( - "Entity %s is using old unique ID, migrating to new one", entity_id - ) - ent_reg.async_update_entity( - entity_id, - new_unique_id=get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ), + + for value_id in value_ids: + old_unique_id = get_unique_id( + client.driver.controller.home_id, + f"{disc_info.primary_value.node.node_id}.{value_id}", ) + # Most entities have the same ID format, but notification binary sensors + # have a state key in their ID so we need to handle them differently + if ( + disc_info.platform == "binary_sensor" + and disc_info.platform_hint == "notification" + ): + for state_key in disc_info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + + migrate_entity( + disc_info.platform, + f"{old_unique_id}.{state_key}", + f"{new_unique_id}.{state_key}", + ) + + # Once we've iterated through all state keys, we can move on to the + # next item + continue + + migrate_entity(disc_info.platform, old_unique_id, new_unique_id) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 248a34547b5c5e..a40eb10de8b792 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -24,11 +24,6 @@ class ZwaveDiscoveryInfo: # hint for the platform about this discovered entity platform_hint: Optional[str] = "" - @property - def value_id(self) -> str: - """Return the unique value_id belonging to primary value.""" - return f"{self.node.node_id}.{self.primary_value.value_id}" - @dataclass class ZWaveValueDiscoverySchema: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 685fe50c9b6891..d0ed9eb5291309 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -32,7 +32,7 @@ def __init__( self.info = info self._name = self.generate_name() self._unique_id = get_unique_id( - self.client.driver.controller.home_id, self.info.value_id + self.client.driver.controller.home_id, self.info.primary_value.value_id ) # entities requiring additional values, can add extra ids to this list self.watched_value_ids = {self.info.primary_value.value_id} diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 3634454544fe4e..f2815bec7f6250 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,7 @@ from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry, entity_registry -from .common import AIR_TEMPERATURE_SENSOR +from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR from tests.common import MockConfigEntry @@ -124,13 +124,15 @@ async def test_on_node_added_ready( ) -async def test_unique_id_migration(hass, multisensor_6_state, client, integration): - """Test unique ID is migrated from old format to new.""" +async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 1).""" ent_reg = entity_registry.async_get(hass) + + # Migrate version 1 entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] # Create entity RegistryEntry using old unique ID format - old_unique_id = f"{client.driver.controller.home_id}.52-49-00-Air temperature-00" + old_unique_id = f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00" entity_entry = ent_reg.async_get_or_create( "sensor", DOMAIN, @@ -155,6 +157,73 @@ async def test_unique_id_migration(hass, multisensor_6_state, client, integratio assert entity_entry.unique_id == new_unique_id +async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 2).""" + ent_reg = entity_registry.async_get(hass) + # Migrate version 2 + ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" + entity_name = ILLUMINANCE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52.52-49-0-Illuminance-00-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == ILLUMINANCE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_notification_binary_sensor( + hass, multisensor_6_state, client, integration +): + """Test unique ID is migrated from old format to new for a notification binary sensor.""" + ent_reg = entity_registry.async_get(hass) + + entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52.52-113-00-Home Security-Motion sensor status.8" + entity_entry = ent_reg.async_get_or_create( + "binary_sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == NOTIFICATION_MOTION_BINARY_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8" + assert entity_entry.unique_id == new_unique_id + + async def test_on_node_added_not_ready( hass, multisensor_6_state, client, integration, device_registry ): From 399c299cf2d40b745f61a7342a0667b9ac1971c4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 00:48:19 -0800 Subject: [PATCH 0791/1818] Remove deprecated credstash + keyring (#47033) --- homeassistant/scripts/check_config.py | 7 +-- homeassistant/scripts/credstash.py | 74 --------------------------- homeassistant/scripts/keyring.py | 62 ---------------------- homeassistant/util/yaml/__init__.py | 3 +- homeassistant/util/yaml/const.py | 2 - homeassistant/util/yaml/loader.py | 53 +------------------ requirements_all.txt | 9 ---- requirements_test_all.txt | 9 ---- script/gen_requirements_all.py | 3 +- tests/util/yaml/test_init.py | 44 ---------------- 10 files changed, 4 insertions(+), 262 deletions(-) delete mode 100644 homeassistant/scripts/credstash.py delete mode 100644 homeassistant/scripts/keyring.py diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 992fce2ac87585..f75594a546e15c 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -141,12 +141,7 @@ def run(script_args: List) -> int: if sval is None: print(" -", skey + ":", color("red", "not found")) continue - print( - " -", - skey + ":", - sval, - color("cyan", "[from:", flatsecret.get(skey, "keyring") + "]"), - ) + print(" -", skey + ":", sval) return len(res["except"]) diff --git a/homeassistant/scripts/credstash.py b/homeassistant/scripts/credstash.py deleted file mode 100644 index 99227d81b663ce..00000000000000 --- a/homeassistant/scripts/credstash.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Script to get, put and delete secrets stored in credstash.""" -import argparse -import getpass - -from homeassistant.util.yaml import _SECRET_NAMESPACE - -# mypy: allow-untyped-defs - -REQUIREMENTS = ["credstash==1.15.0"] - - -def run(args): - """Handle credstash script.""" - parser = argparse.ArgumentParser( - description=( - "Modify Home Assistant secrets in credstash." - "Use the secrets in configuration files with: " - "!secret " - ) - ) - parser.add_argument("--script", choices=["credstash"]) - parser.add_argument( - "action", - choices=["get", "put", "del", "list"], - help="Get, put or delete a secret, or list all available secrets", - ) - parser.add_argument("name", help="Name of the secret", nargs="?", default=None) - parser.add_argument( - "value", help="The value to save when putting a secret", nargs="?", default=None - ) - - # pylint: disable=import-error, no-member, import-outside-toplevel - import credstash - - args = parser.parse_args(args) - table = _SECRET_NAMESPACE - - try: - credstash.listSecrets(table=table) - except Exception: # pylint: disable=broad-except - credstash.createDdbTable(table=table) - - if args.action == "list": - secrets = [i["name"] for i in credstash.listSecrets(table=table)] - deduped_secrets = sorted(set(secrets)) - - print("Saved secrets:") - for secret in deduped_secrets: - print(secret) - return 0 - - if args.name is None: - parser.print_help() - return 1 - - if args.action == "put": - if args.value: - the_secret = args.value - else: - the_secret = getpass.getpass(f"Please enter the secret for {args.name}: ") - current_version = credstash.getHighestVersion(args.name, table=table) - credstash.putSecret( - args.name, the_secret, version=int(current_version) + 1, table=table - ) - print(f"Secret {args.name} put successfully") - elif args.action == "get": - the_secret = credstash.getSecret(args.name, table=table) - if the_secret is None: - print(f"Secret {args.name} not found") - else: - print(f"Secret {args.name}={the_secret}") - elif args.action == "del": - credstash.deleteSecrets(args.name, table=table) - print(f"Deleted secret {args.name}") diff --git a/homeassistant/scripts/keyring.py b/homeassistant/scripts/keyring.py deleted file mode 100644 index 0166d41ce0c0c6..00000000000000 --- a/homeassistant/scripts/keyring.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Script to get, set and delete secrets stored in the keyring.""" -import argparse -import getpass -import os - -from homeassistant.util.yaml import _SECRET_NAMESPACE - -# mypy: allow-untyped-defs -REQUIREMENTS = ["keyring==21.2.0", "keyrings.alt==3.4.0"] - - -def run(args): - """Handle keyring script.""" - parser = argparse.ArgumentParser( - description=( - "Modify Home Assistant secrets in the default keyring. " - "Use the secrets in configuration files with: " - "!secret " - ) - ) - parser.add_argument("--script", choices=["keyring"]) - parser.add_argument( - "action", - choices=["get", "set", "del", "info"], - help="Get, set or delete a secret", - ) - parser.add_argument("name", help="Name of the secret", nargs="?", default=None) - - import keyring # pylint: disable=import-outside-toplevel - - # pylint: disable=import-outside-toplevel - from keyring.util import platform_ as platform - - args = parser.parse_args(args) - - if args.action == "info": - keyr = keyring.get_keyring() - print("Keyring version {}\n".format(REQUIREMENTS[0].split("==")[1])) - print(f"Active keyring : {keyr.__module__}") - config_name = os.path.join(platform.config_root(), "keyringrc.cfg") - print(f"Config location : {config_name}") - print(f"Data location : {platform.data_root()}\n") - elif args.name is None: - parser.print_help() - return 1 - - if args.action == "set": - entered_secret = getpass.getpass(f"Please enter the secret for {args.name}: ") - keyring.set_password(_SECRET_NAMESPACE, args.name, entered_secret) - print(f"Secret {args.name} set successfully") - elif args.action == "get": - the_secret = keyring.get_password(_SECRET_NAMESPACE, args.name) - if the_secret is None: - print(f"Secret {args.name} not found") - else: - print(f"Secret {args.name}={the_secret}") - elif args.action == "del": - try: - keyring.delete_password(_SECRET_NAMESPACE, args.name) - print(f"Deleted secret {args.name}") - except keyring.errors.PasswordDeleteError: - print(f"Secret {args.name} not found") diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index ac4ac2f9a16951..a152086ea82911 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -1,5 +1,5 @@ """YAML utility functions.""" -from .const import _SECRET_NAMESPACE, SECRET_YAML +from .const import SECRET_YAML from .dumper import dump, save_yaml from .input import UndefinedSubstitution, extract_inputs, substitute from .loader import clear_secret_cache, load_yaml, parse_yaml, secret_yaml @@ -7,7 +7,6 @@ __all__ = [ "SECRET_YAML", - "_SECRET_NAMESPACE", "Input", "dump", "save_yaml", diff --git a/homeassistant/util/yaml/const.py b/homeassistant/util/yaml/const.py index bf1615edb9343b..9d930b50fd6f63 100644 --- a/homeassistant/util/yaml/const.py +++ b/homeassistant/util/yaml/const.py @@ -1,4 +1,2 @@ """Constants.""" SECRET_YAML = "secrets.yaml" - -_SECRET_NAMESPACE = "homeassistant" diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 294cd0ac570331..7d713c9f0c0651 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -10,20 +10,9 @@ from homeassistant.exceptions import HomeAssistantError -from .const import _SECRET_NAMESPACE, SECRET_YAML +from .const import SECRET_YAML from .objects import Input, NodeListClass, NodeStrClass -try: - import keyring -except ImportError: - keyring = None - -try: - import credstash -except ImportError: - credstash = None - - # mypy: allow-untyped-calls, no-warn-return-any JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name @@ -32,9 +21,6 @@ _LOGGER = logging.getLogger(__name__) __SECRET_CACHE: Dict[str, JSON_TYPE] = {} -CREDSTASH_WARN = False -KEYRING_WARN = False - def clear_secret_cache() -> None: """Clear the secret cache. @@ -299,43 +285,6 @@ def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: if not os.path.exists(secret_path) or len(secret_path) < 5: break # Somehow we got past the .homeassistant config folder - if keyring: - # do some keyring stuff - pwd = keyring.get_password(_SECRET_NAMESPACE, node.value) - if pwd: - global KEYRING_WARN # pylint: disable=global-statement - - if not KEYRING_WARN: - KEYRING_WARN = True - _LOGGER.warning( - "Keyring is deprecated and will be removed in March 2021." - ) - - _LOGGER.debug("Secret %s retrieved from keyring", node.value) - return pwd - - global credstash # pylint: disable=invalid-name, global-statement - - if credstash: - # pylint: disable=no-member - try: - pwd = credstash.getSecret(node.value, table=_SECRET_NAMESPACE) - if pwd: - global CREDSTASH_WARN # pylint: disable=global-statement - - if not CREDSTASH_WARN: - CREDSTASH_WARN = True - _LOGGER.warning( - "Credstash is deprecated and will be removed in March 2021." - ) - _LOGGER.debug("Secret %s retrieved from credstash", node.value) - return pwd - except credstash.ItemNotFound: - pass - except Exception: # pylint: disable=broad-except - # Catch if package installed and no config - credstash = None - raise HomeAssistantError(f"Secret {node.value} not defined") diff --git a/requirements_all.txt b/requirements_all.txt index b90aa3ce936324..f88d343bd5c6f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -448,9 +448,6 @@ construct==2.10.56 # homeassistant.components.coronavirus coronavirus==1.1.1 -# homeassistant.scripts.credstash -# credstash==1.15.0 - # homeassistant.components.datadog datadog==0.15.0 @@ -844,12 +841,6 @@ kaiterra-async-client==0.0.2 # homeassistant.components.keba keba-kecontact==1.1.0 -# homeassistant.scripts.keyring -keyring==21.2.0 - -# homeassistant.scripts.keyring -keyrings.alt==3.4.0 - # homeassistant.components.kiwi kiwiki-client==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9773824d2210e5..a23f7da4353c8f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -239,9 +239,6 @@ construct==2.10.56 # homeassistant.components.coronavirus coronavirus==1.1.1 -# homeassistant.scripts.credstash -# credstash==1.15.0 - # homeassistant.components.datadog datadog==0.15.0 @@ -455,12 +452,6 @@ influxdb==5.2.3 # homeassistant.components.verisure jsonpath==0.82 -# homeassistant.scripts.keyring -keyring==21.2.0 - -# homeassistant.scripts.keyring -keyrings.alt==3.4.0 - # homeassistant.components.konnected konnected==1.2.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 7dd4924dac81c2..94365be9a50f44 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -21,7 +21,6 @@ "blinkt", "bluepy", "bme680", - "credstash", "decora", "decora_wifi", "envirophat", @@ -47,7 +46,7 @@ "VL53L1X2", ) -IGNORE_PIN = ("colorlog>2.1,<3", "keyring>=9.3,<10.0", "urllib3") +IGNORE_PIN = ("colorlog>2.1,<3", "urllib3") URL_PIN = ( "https://developers.home-assistant.io/docs/" diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index e28a12acf71ab1..b3a8ca4e486cc8 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -1,6 +1,5 @@ """Test Home Assistant yaml loader.""" import io -import logging import os import unittest from unittest.mock import patch @@ -15,14 +14,6 @@ from tests.common import get_test_config_dir, patch_yaml_files -@pytest.fixture(autouse=True) -def mock_credstash(): - """Mock credstash so it doesn't connect to the internet.""" - with patch.object(yaml_loader, "credstash") as mock_credstash: - mock_credstash.getSecret.return_value = None - yield mock_credstash - - def test_simple_list(): """Test simple list.""" conf = "config:\n - simple\n - list" @@ -294,20 +285,6 @@ def load_yaml(fname, string): return load_yaml_config_file(fname) -class FakeKeyring: - """Fake a keyring class.""" - - def __init__(self, secrets_dict): - """Store keyring dictionary.""" - self._secrets = secrets_dict - - # pylint: disable=protected-access - def get_password(self, domain, name): - """Retrieve password.""" - assert domain == yaml._SECRET_NAMESPACE - return self._secrets.get(name) - - class TestSecrets(unittest.TestCase): """Test the secrets parameter in the yaml utility.""" @@ -395,27 +372,6 @@ def test_secrets_from_unrelated_fails(self): "http:\n api_password: !secret test", ) - def test_secrets_keyring(self): - """Test keyring fallback & get_password.""" - yaml_loader.keyring = None # Ensure its not there - yaml_str = "http:\n api_password: !secret http_pw_keyring" - with pytest.raises(HomeAssistantError): - load_yaml(self._yaml_path, yaml_str) - - yaml_loader.keyring = FakeKeyring({"http_pw_keyring": "yeah"}) - _yaml = load_yaml(self._yaml_path, yaml_str) - assert {"http": {"api_password": "yeah"}} == _yaml - - @patch.object(yaml_loader, "credstash") - def test_secrets_credstash(self, mock_credstash): - """Test credstash fallback & get_password.""" - mock_credstash.getSecret.return_value = "yeah" - yaml_str = "http:\n api_password: !secret http_pw_credstash" - _yaml = load_yaml(self._yaml_path, yaml_str) - log = logging.getLogger() - log.error(_yaml["http"]) - assert {"api_password": "yeah"} == _yaml["http"] - def test_secrets_logger_removed(self): """Ensure logger: debug was removed.""" with pytest.raises(HomeAssistantError): From 33a6fb1baf9dcbad72df542d7947f7a0f10cd37e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 19:42:48 +0000 Subject: [PATCH 0792/1818] Bumped version to 2021.3.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index f9a8e3e99b3769..19f9da062c9003 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From d0842910554d6d4eea22a65857b6a65314c342f2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 25 Feb 2021 21:34:04 +0100 Subject: [PATCH 0793/1818] Updated frontend to 20210225.0 (#47059) --- 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 623aaf42ca596f..e8e9c44ae78a76 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==20210224.0" + "home-assistant-frontend==20210225.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e4f84f0c8ef478..8f84546371e8fe 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 341487e3e35cca..780dca13d7a2ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5633b1557de6ce..d587cb98a0a717 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From e3105c7eb14af02b773fca5316f271b7301f078c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 26 Feb 2021 00:28:22 +0100 Subject: [PATCH 0794/1818] Revert CORS changes for my home assistant (#47064) * Revert CORS changes for my home assistant * Update test_init.py * Update test_init.py --- homeassistant/components/api/__init__.py | 1 - homeassistant/components/http/__init__.py | 2 +- tests/components/http/test_init.py | 5 +---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index a82309094e3095..e40a9332c38459 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -178,7 +178,6 @@ class APIDiscoveryView(HomeAssistantView): requires_auth = False url = URL_API_DISCOVERY_INFO name = "api:discovery" - cors_allowed = True async def get(self, request): """Get discovery information.""" diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index d09cfe754a9425..993d466ae18ed7 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -59,7 +59,7 @@ DEFAULT_DEVELOPMENT = "0" # Cast to be able to load custom cards. # My to be able to check url and version info. -DEFAULT_CORS = ["https://cast.home-assistant.io", "https://my.home-assistant.io"] +DEFAULT_CORS = ["https://cast.home-assistant.io"] NO_LOGIN_ATTEMPT_THRESHOLD = -1 MAX_CLIENT_SIZE: int = 1024 ** 2 * 16 diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 9621b2690818b7..993f0dba1fd646 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -175,10 +175,7 @@ async def test_cors_defaults(hass): assert await async_setup_component(hass, "http", {}) assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == [ - "https://cast.home-assistant.io", - "https://my.home-assistant.io", - ] + assert mock_setup.mock_calls[0][1][1] == ["https://cast.home-assistant.io"] async def test_storing_config(hass, aiohttp_client, aiohttp_unused_port): From 989d3e5c872b45e9808f2df8d205377feee58f42 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 26 Feb 2021 00:06:13 +0000 Subject: [PATCH 0795/1818] [ci skip] Translation update --- .../components/airvisual/translations/lb.json | 9 ++++- .../components/bond/translations/cs.json | 2 +- .../components/climacell/translations/cs.json | 20 ++++++++++ .../components/climacell/translations/es.json | 30 +++++++++++++++ .../climacell/translations/zh-Hant.json | 34 +++++++++++++++++ .../faa_delays/translations/cs.json | 8 ++++ .../faa_delays/translations/es.json | 19 ++++++++++ .../faa_delays/translations/et.json | 21 +++++++++++ .../faa_delays/translations/ru.json | 21 +++++++++++ .../faa_delays/translations/zh-Hant.json | 21 +++++++++++ .../components/homekit/translations/cs.json | 2 +- .../components/kmtronic/translations/cs.json | 21 +++++++++++ .../components/litejet/translations/es.json | 16 ++++++++ .../components/mazda/translations/es.json | 1 + .../components/mullvad/translations/cs.json | 21 +++++++++++ .../components/mullvad/translations/es.json | 9 +++++ .../mullvad/translations/zh-Hant.json | 22 +++++++++++ .../components/netatmo/translations/es.json | 22 +++++++++++ .../netatmo/translations/zh-Hant.json | 22 +++++++++++ .../translations/es.json | 9 +++++ .../components/shelly/translations/nl.json | 4 +- .../components/soma/translations/cs.json | 2 +- .../components/spotify/translations/cs.json | 2 +- .../components/subaru/translations/cs.json | 28 ++++++++++++++ .../components/subaru/translations/es.json | 37 +++++++++++++++++++ .../totalconnect/translations/cs.json | 8 +++- .../totalconnect/translations/es.json | 10 ++++- .../wolflink/translations/sensor.nl.json | 16 ++++++++ .../xiaomi_miio/translations/es.json | 1 + .../components/zwave_js/translations/es.json | 7 +++- 30 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/climacell/translations/cs.json create mode 100644 homeassistant/components/climacell/translations/es.json create mode 100644 homeassistant/components/climacell/translations/zh-Hant.json create mode 100644 homeassistant/components/faa_delays/translations/cs.json create mode 100644 homeassistant/components/faa_delays/translations/es.json create mode 100644 homeassistant/components/faa_delays/translations/et.json create mode 100644 homeassistant/components/faa_delays/translations/ru.json create mode 100644 homeassistant/components/faa_delays/translations/zh-Hant.json create mode 100644 homeassistant/components/kmtronic/translations/cs.json create mode 100644 homeassistant/components/litejet/translations/es.json create mode 100644 homeassistant/components/mullvad/translations/cs.json create mode 100644 homeassistant/components/mullvad/translations/es.json create mode 100644 homeassistant/components/mullvad/translations/zh-Hant.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/es.json create mode 100644 homeassistant/components/subaru/translations/cs.json create mode 100644 homeassistant/components/subaru/translations/es.json create mode 100644 homeassistant/components/wolflink/translations/sensor.nl.json diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index 5e45098c11d837..d6799ba6e37b96 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Feeler beim verbannen", "general_error": "Onerwaarte Feeler", - "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel" + "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel", + "location_not_found": "Standuert net fonnt." }, "step": { "geography": { @@ -19,6 +20,12 @@ "description": "Benotz Airvisual cloud API fir eng geografescher Lag z'iwwerwaachen.", "title": "Geografie ariichten" }, + "geography_by_name": { + "data": { + "city": "Stad", + "country": "Land" + } + }, "node_pro": { "data": { "ip_address": "Host", diff --git a/homeassistant/components/bond/translations/cs.json b/homeassistant/components/bond/translations/cs.json index 677c7e80236996..13135dbf53e592 100644 --- a/homeassistant/components/bond/translations/cs.json +++ b/homeassistant/components/bond/translations/cs.json @@ -15,7 +15,7 @@ "data": { "access_token": "P\u0159\u00edstupov\u00fd token" }, - "description": "Chcete nastavit {bond_id} ?" + "description": "Chcete nastavit {name}?" }, "user": { "data": { diff --git a/homeassistant/components/climacell/translations/cs.json b/homeassistant/components/climacell/translations/cs.json new file mode 100644 index 00000000000000..1ae29deb08c1fb --- /dev/null +++ b/homeassistant/components/climacell/translations/cs.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_api_key": "Neplatn\u00fd kl\u00ed\u010d API", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", + "name": "Jm\u00e9no" + } + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es.json b/homeassistant/components/climacell/translations/es.json new file mode 100644 index 00000000000000..4c4d8fcc9bb9d8 --- /dev/null +++ b/homeassistant/components/climacell/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde." + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nombre" + }, + "description": "Si no se proporcionan Latitud y Longitud , se utilizar\u00e1n los valores predeterminados en la configuraci\u00f3n de Home Assistant. Se crear\u00e1 una entidad para cada tipo de pron\u00f3stico, pero solo las que seleccione estar\u00e1n habilitadas de forma predeterminada." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipo(s) de pron\u00f3stico", + "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.", + "title": "Actualizar las opciones ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hant.json b/homeassistant/components/climacell/translations/zh-Hant.json new file mode 100644 index 00000000000000..76eaf50b932535 --- /dev/null +++ b/homeassistant/components/climacell/translations/zh-Hant.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", + "rate_limited": "\u9054\u5230\u9650\u5236\u983b\u7387\u3001\u8acb\u7a0d\u5019\u518d\u8a66\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "name": "\u540d\u7a31" + }, + "description": "\u5047\u5982\u672a\u63d0\u4f9b\u7def\u5ea6\u8207\u7d93\u5ea6\uff0c\u5c07\u6703\u4f7f\u7528 Home Assistant \u8a2d\u5b9a\u4f5c\u70ba\u9810\u8a2d\u503c\u3002\u6bcf\u4e00\u500b\u9810\u5831\u985e\u578b\u90fd\u6703\u7522\u751f\u4e00\u7d44\u5be6\u9ad4\uff0c\u6216\u8005\u9810\u8a2d\u70ba\u6240\u9078\u64c7\u555f\u7528\u7684\u9810\u5831\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u9810\u5831\u985e\u578b", + "timestep": "NowCast \u9810\u5831\u9593\u9694\u5206\u9418" + }, + "description": "\u5047\u5982\u9078\u64c7\u958b\u555f `nowcast` \u9810\u5831\u5be6\u9ad4\u3001\u5c07\u53ef\u4ee5\u8a2d\u5b9a\u9810\u5831\u983b\u7387\u9593\u9694\u5206\u9418\u6578\u3002\u6839\u64da\u6240\u8f38\u5165\u7684\u9593\u9694\u6642\u9593\u5c07\u6c7a\u5b9a\u9810\u5831\u7684\u6578\u76ee\u3002", + "title": "\u66f4\u65b0 ClimaCell \u9078\u9805" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/cs.json b/homeassistant/components/faa_delays/translations/cs.json new file mode 100644 index 00000000000000..60e4aed57a2784 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/cs.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/es.json b/homeassistant/components/faa_delays/translations/es.json new file mode 100644 index 00000000000000..94eca99dda3774 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Este aeropuerto ya est\u00e1 configurado." + }, + "error": { + "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido" + }, + "step": { + "user": { + "data": { + "id": "Aeropuerto" + }, + "description": "Introduzca un c\u00f3digo de aeropuerto estadounidense en formato IATA", + "title": "Retrasos de la FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/et.json b/homeassistant/components/faa_delays/translations/et.json new file mode 100644 index 00000000000000..75b52558374920 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "See lennujaam on juba seadistatud." + }, + "error": { + "cannot_connect": "\u00dchendumine nurjus", + "invalid_airport": "Lennujaama kood ei sobi", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "id": "Lennujaam" + }, + "description": "Sisesta USA lennujaama kood IATA vormingus", + "title": "" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/ru.json b/homeassistant/components/faa_delays/translations/ru.json new file mode 100644 index 00000000000000..d68810fc9575bc --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ru.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "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_airport": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "id": "\u0410\u044d\u0440\u043e\u043f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430 \u0421\u0428\u0410 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 IATA.", + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/zh-Hant.json b/homeassistant/components/faa_delays/translations/zh-Hant.json new file mode 100644 index 00000000000000..f2585bb790f07c --- /dev/null +++ b/homeassistant/components/faa_delays/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u6b64\u6a5f\u5834\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_airport": "\u6a5f\u5834\u4ee3\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "id": "\u6a5f\u5834" + }, + "description": "\u8f38\u5165\u7f8e\u570b\u6a5f\u5834 IATA \u4ee3\u78bc", + "title": "FAA \u822a\u73ed\u5ef6\u8aa4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json index faf1b1d74fc0d4..cdfaed9183c1f4 100644 --- a/homeassistant/components/homekit/translations/cs.json +++ b/homeassistant/components/homekit/translations/cs.json @@ -17,7 +17,7 @@ "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", "mode": "Re\u017eim" }, - "title": "Aktivace HomeKit" + "title": "Vyberte dom\u00e9ny, kter\u00e9 chcete zahrnout" } } }, diff --git a/homeassistant/components/kmtronic/translations/cs.json b/homeassistant/components/kmtronic/translations/cs.json new file mode 100644 index 00000000000000..0f02cd974c207c --- /dev/null +++ b/homeassistant/components/kmtronic/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/es.json b/homeassistant/components/litejet/translations/es.json new file mode 100644 index 00000000000000..b0641022bf01d1 --- /dev/null +++ b/homeassistant/components/litejet/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "open_failed": "No se puede abrir el puerto serie especificado." + }, + "step": { + "user": { + "data": { + "port": "Puerto" + }, + "description": "Conecte el puerto RS232-2 del LiteJet a su computadora e ingrese la ruta al dispositivo del puerto serial. \n\nEl LiteJet MCP debe configurarse para 19,2 K baudios, 8 bits de datos, 1 bit de parada, sin paridad y para transmitir un 'CR' despu\u00e9s de cada respuesta.", + "title": "Conectarse a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/es.json b/homeassistant/components/mazda/translations/es.json index 72fc9ce7389103..868ae0d770ea6c 100644 --- a/homeassistant/components/mazda/translations/es.json +++ b/homeassistant/components/mazda/translations/es.json @@ -6,6 +6,7 @@ "step": { "reauth": { "data": { + "password": "Contrase\u00f1a", "region": "Regi\u00f3n" }, "description": "Ha fallado la autenticaci\u00f3n para los Servicios Conectados de Mazda. Por favor, introduce tus credenciales actuales.", diff --git a/homeassistant/components/mullvad/translations/cs.json b/homeassistant/components/mullvad/translations/cs.json new file mode 100644 index 00000000000000..0f02cd974c207c --- /dev/null +++ b/homeassistant/components/mullvad/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/es.json b/homeassistant/components/mullvad/translations/es.json new file mode 100644 index 00000000000000..d6a17561c3d354 --- /dev/null +++ b/homeassistant/components/mullvad/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u00bfConfigurar la integraci\u00f3n VPN de Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/zh-Hant.json b/homeassistant/components/mullvad/translations/zh-Hant.json new file mode 100644 index 00000000000000..d78c36b72d77cd --- /dev/null +++ b/homeassistant/components/mullvad/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8a2d\u5b9a Mullvad VPN \u6574\u5408\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/es.json b/homeassistant/components/netatmo/translations/es.json index 556fe2626d491b..b1159c1dd9d306 100644 --- a/homeassistant/components/netatmo/translations/es.json +++ b/homeassistant/components/netatmo/translations/es.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "fuera", + "hg": "protector contra las heladas", + "schedule": "Horario" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha detectado una alarma", + "animal": "{entity_name} ha detectado un animal", + "cancel_set_point": "{entity_name} ha reanudado su programaci\u00f3n", + "human": "{entity_name} ha detectado una persona", + "movement": "{entity_name} ha detectado movimiento", + "outdoor": "{entity_name} ha detectado un evento en el exterior", + "person": "{entity_name} ha detectado una persona", + "person_away": "{entity_name} ha detectado que una persona se ha ido", + "set_point": "Temperatura objetivo {entity_name} fijada manualmente", + "therm_mode": "{entity_name} cambi\u00f3 a \" {subtype} \"", + "turned_off": "{entity_name} desactivado", + "turned_on": "{entity_name} activado", + "vehicle": "{entity_name} ha detectado un veh\u00edculo" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/zh-Hant.json b/homeassistant/components/netatmo/translations/zh-Hant.json index e396deabb68c0b..e62836f9a7e4ad 100644 --- a/homeassistant/components/netatmo/translations/zh-Hant.json +++ b/homeassistant/components/netatmo/translations/zh-Hant.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\u96e2\u5bb6", + "hg": "\u9632\u51cd\u6a21\u5f0f", + "schedule": "\u6392\u7a0b" + }, + "trigger_type": { + "alarm_started": "{entity_name}\u5075\u6e2c\u5230\u8b66\u5831", + "animal": "{entity_name}\u5075\u6e2c\u5230\u52d5\u7269", + "cancel_set_point": "{entity_name}\u5df2\u6062\u5fa9\u5176\u6392\u7a0b", + "human": "{entity_name}\u5075\u6e2c\u5230\u4eba\u985e", + "movement": "{entity_name}\u5075\u6e2c\u5230\u52d5\u4f5c", + "outdoor": "{entity_name}\u5075\u6e2c\u5230\u6236\u5916\u52d5\u4f5c", + "person": "{entity_name}\u5075\u6e2c\u5230\u4eba\u54e1", + "person_away": "{entity_name}\u5075\u6e2c\u5230\u4eba\u54e1\u5df2\u96e2\u958b", + "set_point": "\u624b\u52d5\u8a2d\u5b9a{entity_name}\u76ee\u6a19\u6eab\u5ea6", + "therm_mode": "{entity_name}\u5207\u63db\u81f3 \"{subtype}\"", + "turned_off": "{entity_name}\u5df2\u95dc\u9589", + "turned_on": "{entity_name}\u5df2\u958b\u555f", + "vehicle": "{entity_name}\u5075\u6e2c\u5230\u8eca\u8f1b" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/es.json b/homeassistant/components/rituals_perfume_genie/translations/es.json new file mode 100644 index 00000000000000..bc74ecfd7ea20d --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Con\u00e9ctese a su cuenta de Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json index 7084a972e291d5..c486b9c6bfe7a6 100644 --- a/homeassistant/components/shelly/translations/nl.json +++ b/homeassistant/components/shelly/translations/nl.json @@ -8,7 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Wilt u het {model} bij {host} instellen? Voordat apparaten op batterijen kunnen worden ingesteld, moet het worden gewekt door op de knop op het apparaat te drukken." @@ -16,7 +16,7 @@ "credentials": { "data": { "password": "Wachtwoord", - "username": "Benutzername" + "username": "Gebruikersnaam" } }, "user": { diff --git a/homeassistant/components/soma/translations/cs.json b/homeassistant/components/soma/translations/cs.json index 5a27562df71f71..ba1261c1100915 100644 --- a/homeassistant/components/soma/translations/cs.json +++ b/homeassistant/components/soma/translations/cs.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "M\u016f\u017eete nastavit pouze jeden \u00fa\u010det Soma.", - "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el.", "connection_error": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed SOMA Connect se nezda\u0159ilo.", "missing_configuration": "Integrace Soma nen\u00ed nastavena. Postupujte podle dokumentace.", "result_error": "SOMA Connect odpov\u011bd\u011blo chybov\u00fdm stavem." diff --git a/homeassistant/components/spotify/translations/cs.json b/homeassistant/components/spotify/translations/cs.json index f8f122e63e2c18..69cd1b1623ada0 100644 --- a/homeassistant/components/spotify/translations/cs.json +++ b/homeassistant/components/spotify/translations/cs.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "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})" }, diff --git a/homeassistant/components/subaru/translations/cs.json b/homeassistant/components/subaru/translations/cs.json new file mode 100644 index 00000000000000..ee3bf7347ca256 --- /dev/null +++ b/homeassistant/components/subaru/translations/cs.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "error": { + "bad_pin_format": "PIN by m\u011bl m\u00edt 4 \u010d\u00edslice", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "incorrect_pin": "Nespr\u00e1vn\u00fd PIN", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "pin": { + "data": { + "pin": "PIN k\u00f3d" + } + }, + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/es.json b/homeassistant/components/subaru/translations/es.json new file mode 100644 index 00000000000000..deccc23c75dbb2 --- /dev/null +++ b/homeassistant/components/subaru/translations/es.json @@ -0,0 +1,37 @@ +{ + "config": { + "error": { + "bad_pin_format": "El PIN debe tener 4 d\u00edgitos", + "incorrect_pin": "PIN incorrecto" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Por favor, introduzca su PIN de MySubaru\nNOTA: Todos los veh\u00edculos de la cuenta deben tener el mismo PIN", + "title": "Configuraci\u00f3n de Subaru Starlink" + }, + "user": { + "data": { + "country": "Seleccionar pa\u00eds", + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Por favor, introduzca sus credenciales de MySubaru\nNOTA: La configuraci\u00f3n inicial puede tardar hasta 30 segundos", + "title": "Configuraci\u00f3n de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Habilitar el sondeo de veh\u00edculos" + }, + "description": "Cuando est\u00e1 habilitado, el sondeo de veh\u00edculos enviar\u00e1 un comando remoto a su veh\u00edculo cada 2 horas para obtener nuevos datos del sensor. Sin sondeo del veh\u00edculo, los nuevos datos del sensor solo se reciben cuando el veh\u00edculo env\u00eda datos autom\u00e1ticamente (normalmente despu\u00e9s de apagar el motor).", + "title": "Opciones de Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/cs.json b/homeassistant/components/totalconnect/translations/cs.json index 60e2196b38761b..74dece0c54ef6d 100644 --- a/homeassistant/components/totalconnect/translations/cs.json +++ b/homeassistant/components/totalconnect/translations/cs.json @@ -1,12 +1,18 @@ { "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": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "step": { + "locations": { + "data": { + "location": "Um\u00edst\u011bn\u00ed" + } + }, "user": { "data": { "password": "Heslo", diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 48af1bed0f4a5b..090d9271dee5a0 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -4,9 +4,17 @@ "already_configured": "La cuenta ya ha sido configurada" }, "error": { - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "usercode": "El c\u00f3digo de usuario no es v\u00e1lido para este usuario en esta ubicaci\u00f3n" }, "step": { + "locations": { + "description": "Ingrese el c\u00f3digo de usuario para este usuario en esta ubicaci\u00f3n", + "title": "C\u00f3digos de usuario de ubicaci\u00f3n" + }, + "reauth_confirm": { + "description": "Total Connect necesita volver a autentificar tu cuenta" + }, "user": { "data": { "password": "Contrase\u00f1a", diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json new file mode 100644 index 00000000000000..da03cc43b4b317 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -0,0 +1,16 @@ +{ + "state": { + "wolflink__state": { + "frost_warmwasser": "DHW vorst", + "frostschutz": "Vorstbescherming", + "gasdruck": "Gasdruk", + "glt_betrieb": "BMS-modus", + "heizbetrieb": "Verwarmingsmodus", + "heizgerat_mit_speicher": "Boiler met cilinder", + "heizung": "Verwarmen", + "initialisierung": "Initialisatie", + "kalibration": "Kalibratie", + "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json index fd4b8c36a8b228..60a989ade0d6ce 100644 --- a/homeassistant/components/xiaomi_miio/translations/es.json +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -13,6 +13,7 @@ "step": { "device": { "data": { + "model": "Modelo de dispositivo (opcional)", "name": "Nombre del dispositivo" }, "description": "Necesitar\u00e1 la clave de 32 caracteres Token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones. Tenga en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index 32d7a6d2e6d808..26fd155a0ad2ae 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -6,6 +6,7 @@ "addon_install_failed": "No se ha podido instalar el complemento Z-Wave JS.", "addon_missing_discovery_info": "Falta informaci\u00f3n de descubrimiento del complemento Z-Wave JS.", "addon_set_config_failed": "Fallo en la configuraci\u00f3n de Z-Wave JS.", + "addon_start_failed": "No se ha podido iniciar el complemento Z-Wave JS.", "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar" @@ -17,7 +18,8 @@ "unknown": "Error inesperado" }, "progress": { - "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos." + "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos.", + "start_addon": "Espere mientras se completa el inicio del complemento Z-Wave JS. Esto puede tardar unos segundos." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u00bfQuieres utilizar el complemento Z-Wave JS Supervisor?", "title": "Selecciona el m\u00e9todo de conexi\u00f3n" }, + "start_addon": { + "title": "Se est\u00e1 iniciando el complemento Z-Wave JS." + }, "user": { "data": { "url": "URL" From 7118b7169c097509022a67ab6b2d17d914e83d18 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 25 Feb 2021 20:41:54 -0500 Subject: [PATCH 0796/1818] catch ValueError when unique ID update fails because its taken and remove the duplicate entity (#47072) --- homeassistant/components/zwave_js/__init__.py | 18 +++++-- tests/components/zwave_js/test_init.py | 53 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 75bc95b7fe4026..93d511875af63d 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -95,10 +95,20 @@ def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> Non old_unique_id, new_unique_id, ) - ent_reg.async_update_entity( - entity_id, - new_unique_id=new_unique_id, - ) + try: + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + except ValueError: + LOGGER.debug( + ( + "Entity %s can't be migrated because the unique ID is taken. " + "Cleaning it up since it is likely no longer valid." + ), + entity_id, + ) + ent_reg.async_remove(entity_id) @callback def async_on_node_ready(node: ZwaveNode) -> None: diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index f2815bec7f6250..bff2ecd198c56a 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -124,6 +124,59 @@ async def test_on_node_added_ready( ) +async def test_unique_id_migration_dupes( + hass, multisensor_6_state, client, integration +): + """Test we remove an entity when .""" + ent_reg = entity_registry.async_get(hass) + + entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id_1 = ( + f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id_1, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == AIR_TEMPERATURE_SENSOR + assert entity_entry.unique_id == old_unique_id_1 + + # Create entity RegistryEntry using b0 unique ID format + old_unique_id_2 = ( + f"{client.driver.controller.home_id}.52.52-49-0-Air temperature-00-00" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id_2, + suggested_object_id=f"{entity_name}_1", + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == f"{AIR_TEMPERATURE_SENSOR}_1" + assert entity_entry.unique_id == old_unique_id_2 + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + assert entity_entry.unique_id == new_unique_id + + assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None + + async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 1).""" ent_reg = entity_registry.async_get(hass) From 8ab163eda853481ac4bd23226c65f37e34c5a8f1 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 26 Feb 2021 00:11:06 -0500 Subject: [PATCH 0797/1818] Fix Z-Wave JS API docstrings (#47061) --- homeassistant/components/zwave_js/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index a48eadfad1da86..12ec66906a8707 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -350,7 +350,7 @@ async def websocket_set_config_parameter( def websocket_get_config_parameters( hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: - """Get a list of configuration parameterss for a Z-Wave node.""" + """Get a list of configuration parameters for a Z-Wave node.""" entry_id = msg[ENTRY_ID] node_id = msg[NODE_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] @@ -446,7 +446,7 @@ async def websocket_update_log_config( async def websocket_get_log_config( hass: HomeAssistant, connection: ActiveConnection, msg: dict ) -> None: - """Cancel removing a node from the Z-Wave network.""" + """Get log configuration for the Z-Wave JS driver.""" entry_id = msg[ENTRY_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] result = await client.driver.async_get_log_config() From 6af67c9558c7f216455ee599d6e218cd569e64c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 23:58:35 -0600 Subject: [PATCH 0798/1818] Ensure hue options show the defaults when the config options have not yet been saved (#47067) --- homeassistant/components/hue/config_flow.py | 6 ++++-- homeassistant/components/hue/const.py | 2 +- tests/components/hue/test_config_flow.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index ecb3fd8c48901a..580b69251c271b 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -18,6 +18,8 @@ from .const import ( # pylint: disable=unused-import CONF_ALLOW_HUE_GROUPS, CONF_ALLOW_UNREACHABLE, + DEFAULT_ALLOW_HUE_GROUPS, + DEFAULT_ALLOW_UNREACHABLE, DOMAIN, LOGGER, ) @@ -246,13 +248,13 @@ async def async_step_init( vol.Optional( CONF_ALLOW_HUE_GROUPS, default=self.config_entry.options.get( - CONF_ALLOW_HUE_GROUPS, False + CONF_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_HUE_GROUPS ), ): bool, vol.Optional( CONF_ALLOW_UNREACHABLE, default=self.config_entry.options.get( - CONF_ALLOW_UNREACHABLE, False + CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_UNREACHABLE ), ): bool, } diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 4fa11f2ad584a9..593f74331ec46c 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -12,6 +12,6 @@ DEFAULT_ALLOW_UNREACHABLE = False CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" -DEFAULT_ALLOW_HUE_GROUPS = True +DEFAULT_ALLOW_HUE_GROUPS = False DEFAULT_SCENE_TRANSITION = 4 diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index c7dc83183aeb86..57f4bd7fbcae4c 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -640,6 +640,15 @@ async def test_options_flow(hass): assert result["type"] == "form" assert result["step_id"] == "init" + schema = result["data_schema"].schema + assert ( + _get_schema_default(schema, const.CONF_ALLOW_HUE_GROUPS) + == const.DEFAULT_ALLOW_HUE_GROUPS + ) + assert ( + _get_schema_default(schema, const.CONF_ALLOW_UNREACHABLE) + == const.DEFAULT_ALLOW_UNREACHABLE + ) result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -654,3 +663,11 @@ async def test_options_flow(hass): const.CONF_ALLOW_HUE_GROUPS: True, const.CONF_ALLOW_UNREACHABLE: True, } + + +def _get_schema_default(schema, key_name): + """Iterate schema to find a key.""" + for schema_key in schema: + if schema_key == key_name: + return schema_key.default() + raise KeyError(f"{key_name} not found in schema") From 6bd253094f1888e57549a70a28d83f77bffda862 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 22:01:08 -0800 Subject: [PATCH 0799/1818] Bump Z-Wave JS Server Python to 0.20.0 (#47076) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_config_flow.py | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index f5d9461e9e09c1..9e57a3b72e208a 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.19.0"], + "requirements": ["zwave-js-server-python==0.20.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 780dca13d7a2ca..ed93abbda6019c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2397,4 +2397,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.19.0 +zwave-js-server-python==0.20.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d587cb98a0a717..36d2f295e9ce81 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,4 +1234,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.19.0 +zwave-js-server-python==0.20.0 diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 73057f3fe21b00..08b0ffe3080f2b 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -144,6 +144,8 @@ def mock_get_server_version(server_version_side_effect, server_version_timeout): driver_version="mock-driver-version", server_version="mock-server-version", home_id=1234, + min_schema_version=0, + max_schema_version=1, ) with patch( "homeassistant.components.zwave_js.config_flow.get_server_version", From 35bce434ccc11b2757bb34efedf9071c5bc9da51 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 25 Feb 2021 21:34:04 +0100 Subject: [PATCH 0800/1818] Updated frontend to 20210225.0 (#47059) --- 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 623aaf42ca596f..e8e9c44ae78a76 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==20210224.0" + "home-assistant-frontend==20210225.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e4f84f0c8ef478..8f84546371e8fe 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index f88d343bd5c6f3..579f7b3c5688f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a23f7da4353c8f..f92d7060bd1cf4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210224.0 +home-assistant-frontend==20210225.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 5228bbd43c9c5a10bbac250f26ce603c72415c4a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 26 Feb 2021 00:28:22 +0100 Subject: [PATCH 0801/1818] Revert CORS changes for my home assistant (#47064) * Revert CORS changes for my home assistant * Update test_init.py * Update test_init.py --- homeassistant/components/api/__init__.py | 1 - homeassistant/components/http/__init__.py | 2 +- tests/components/http/test_init.py | 5 +---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index a82309094e3095..e40a9332c38459 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -178,7 +178,6 @@ class APIDiscoveryView(HomeAssistantView): requires_auth = False url = URL_API_DISCOVERY_INFO name = "api:discovery" - cors_allowed = True async def get(self, request): """Get discovery information.""" diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index d09cfe754a9425..993d466ae18ed7 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -59,7 +59,7 @@ DEFAULT_DEVELOPMENT = "0" # Cast to be able to load custom cards. # My to be able to check url and version info. -DEFAULT_CORS = ["https://cast.home-assistant.io", "https://my.home-assistant.io"] +DEFAULT_CORS = ["https://cast.home-assistant.io"] NO_LOGIN_ATTEMPT_THRESHOLD = -1 MAX_CLIENT_SIZE: int = 1024 ** 2 * 16 diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 9621b2690818b7..993f0dba1fd646 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -175,10 +175,7 @@ async def test_cors_defaults(hass): assert await async_setup_component(hass, "http", {}) assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == [ - "https://cast.home-assistant.io", - "https://my.home-assistant.io", - ] + assert mock_setup.mock_calls[0][1][1] == ["https://cast.home-assistant.io"] async def test_storing_config(hass, aiohttp_client, aiohttp_unused_port): From a7a66e8ddb6419580ab1c4f51abf36b41f9d5302 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 23:58:35 -0600 Subject: [PATCH 0802/1818] Ensure hue options show the defaults when the config options have not yet been saved (#47067) --- homeassistant/components/hue/config_flow.py | 6 ++++-- homeassistant/components/hue/const.py | 2 +- tests/components/hue/test_config_flow.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index ecb3fd8c48901a..580b69251c271b 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -18,6 +18,8 @@ from .const import ( # pylint: disable=unused-import CONF_ALLOW_HUE_GROUPS, CONF_ALLOW_UNREACHABLE, + DEFAULT_ALLOW_HUE_GROUPS, + DEFAULT_ALLOW_UNREACHABLE, DOMAIN, LOGGER, ) @@ -246,13 +248,13 @@ async def async_step_init( vol.Optional( CONF_ALLOW_HUE_GROUPS, default=self.config_entry.options.get( - CONF_ALLOW_HUE_GROUPS, False + CONF_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_HUE_GROUPS ), ): bool, vol.Optional( CONF_ALLOW_UNREACHABLE, default=self.config_entry.options.get( - CONF_ALLOW_UNREACHABLE, False + CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_UNREACHABLE ), ): bool, } diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 4fa11f2ad584a9..593f74331ec46c 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -12,6 +12,6 @@ DEFAULT_ALLOW_UNREACHABLE = False CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" -DEFAULT_ALLOW_HUE_GROUPS = True +DEFAULT_ALLOW_HUE_GROUPS = False DEFAULT_SCENE_TRANSITION = 4 diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index c7dc83183aeb86..57f4bd7fbcae4c 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -640,6 +640,15 @@ async def test_options_flow(hass): assert result["type"] == "form" assert result["step_id"] == "init" + schema = result["data_schema"].schema + assert ( + _get_schema_default(schema, const.CONF_ALLOW_HUE_GROUPS) + == const.DEFAULT_ALLOW_HUE_GROUPS + ) + assert ( + _get_schema_default(schema, const.CONF_ALLOW_UNREACHABLE) + == const.DEFAULT_ALLOW_UNREACHABLE + ) result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -654,3 +663,11 @@ async def test_options_flow(hass): const.CONF_ALLOW_HUE_GROUPS: True, const.CONF_ALLOW_UNREACHABLE: True, } + + +def _get_schema_default(schema, key_name): + """Iterate schema to find a key.""" + for schema_key in schema: + if schema_key == key_name: + return schema_key.default() + raise KeyError(f"{key_name} not found in schema") From ae0d301fd91e0322000699709c3a3de70f157e5c Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 25 Feb 2021 20:41:54 -0500 Subject: [PATCH 0803/1818] catch ValueError when unique ID update fails because its taken and remove the duplicate entity (#47072) --- homeassistant/components/zwave_js/__init__.py | 18 +++++-- tests/components/zwave_js/test_init.py | 53 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 75bc95b7fe4026..93d511875af63d 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -95,10 +95,20 @@ def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> Non old_unique_id, new_unique_id, ) - ent_reg.async_update_entity( - entity_id, - new_unique_id=new_unique_id, - ) + try: + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + except ValueError: + LOGGER.debug( + ( + "Entity %s can't be migrated because the unique ID is taken. " + "Cleaning it up since it is likely no longer valid." + ), + entity_id, + ) + ent_reg.async_remove(entity_id) @callback def async_on_node_ready(node: ZwaveNode) -> None: diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index f2815bec7f6250..bff2ecd198c56a 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -124,6 +124,59 @@ async def test_on_node_added_ready( ) +async def test_unique_id_migration_dupes( + hass, multisensor_6_state, client, integration +): + """Test we remove an entity when .""" + ent_reg = entity_registry.async_get(hass) + + entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id_1 = ( + f"{client.driver.controller.home_id}.52.52-49-00-Air temperature-00" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id_1, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == AIR_TEMPERATURE_SENSOR + assert entity_entry.unique_id == old_unique_id_1 + + # Create entity RegistryEntry using b0 unique ID format + old_unique_id_2 = ( + f"{client.driver.controller.home_id}.52.52-49-0-Air temperature-00-00" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id_2, + suggested_object_id=f"{entity_name}_1", + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == f"{AIR_TEMPERATURE_SENSOR}_1" + assert entity_entry.unique_id == old_unique_id_2 + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + assert entity_entry.unique_id == new_unique_id + + assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None + + async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 1).""" ent_reg = entity_registry.async_get(hass) From 2c30579a118ec78895d2a387687ff4cc45fc750c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Feb 2021 22:01:08 -0800 Subject: [PATCH 0804/1818] Bump Z-Wave JS Server Python to 0.20.0 (#47076) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_config_flow.py | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index f5d9461e9e09c1..9e57a3b72e208a 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.19.0"], + "requirements": ["zwave-js-server-python==0.20.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 579f7b3c5688f3..bfd55ed68bc511 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2397,4 +2397,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.19.0 +zwave-js-server-python==0.20.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f92d7060bd1cf4..9dd674925d62fa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,4 +1234,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.19.0 +zwave-js-server-python==0.20.0 diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 73057f3fe21b00..08b0ffe3080f2b 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -144,6 +144,8 @@ def mock_get_server_version(server_version_side_effect, server_version_timeout): driver_version="mock-driver-version", server_version="mock-server-version", home_id=1234, + min_schema_version=0, + max_schema_version=1, ) with patch( "homeassistant.components.zwave_js.config_flow.get_server_version", From 6cdd6c3f44d96599b457eb3e6b7c57a7c8f3acee Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 06:01:42 +0000 Subject: [PATCH 0805/1818] Bumped version to 2021.3.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 19f9da062c9003..aca184b4606597 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 3a3c12bb17ab589dab39ea8b0f09d65c6a9cc982 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 00:16:11 -0800 Subject: [PATCH 0806/1818] Upgrade aiohttp to 3.7.4 (#47077) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8f84546371e8fe..10cf300b76b32e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ PyJWT==1.7.1 PyNaCl==1.3.0 -aiohttp==3.7.3 +aiohttp==3.7.4 aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 diff --git a/requirements.txt b/requirements.txt index 14ebf2708ada8d..0a5b754dbfc286 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -c homeassistant/package_constraints.txt # Home Assistant Core -aiohttp==3.7.3 +aiohttp==3.7.4 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 diff --git a/setup.py b/setup.py index 6dbe35760a6ddf..ce7d6b6883d5e6 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ PACKAGES = find_packages(exclude=["tests", "tests.*"]) REQUIRES = [ - "aiohttp==3.7.3", + "aiohttp==3.7.4", "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", From afb6e313936371e957119e44cadc0d11f3f8cdfe Mon Sep 17 00:00:00 2001 From: chpego <38792705+chpego@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:39:31 +0100 Subject: [PATCH 0807/1818] Upgrade youtube_dl to version 2021.02.22 (#47078) * Upgrade youtube_dl to version 2021.02.22 * Update requirements_all.txt --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index c6ee6ccb8a4c9a..48589b37bd214b 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -2,7 +2,7 @@ "domain": "media_extractor", "name": "Media Extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", - "requirements": ["youtube_dl==2021.01.24.1"], + "requirements": ["youtube_dl==2021.02.22"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index ed93abbda6019c..f6ab9f62d14300 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2355,7 +2355,7 @@ yeelight==0.5.4 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2021.01.24.1 +youtube_dl==2021.02.22 # homeassistant.components.onvif zeep[async]==4.0.0 From 5780615251ec4153edb9a7109c9ad6015be22f52 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Feb 2021 10:47:22 +0100 Subject: [PATCH 0808/1818] Bump pychromecast to 8.1.2 (#47085) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 5963e93cf8cb62..28ccb78d5b9ca6 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==8.1.0"], + "requirements": ["pychromecast==8.1.2"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index f6ab9f62d14300..ea5abe0f221230 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1305,7 +1305,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==8.1.0 +pychromecast==8.1.2 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 36d2f295e9ce81..f2054da1aa6210 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -688,7 +688,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==8.1.0 +pychromecast==8.1.2 # homeassistant.components.climacell pyclimacell==0.14.0 From d5ee49cd4e0b468a2fcd874d40f6131989564a43 Mon Sep 17 00:00:00 2001 From: CurrentThread <62957822+CurrentThread@users.noreply.github.com> Date: Fri, 26 Feb 2021 11:52:47 +0100 Subject: [PATCH 0809/1818] Add support for Shelly SHBTN-2 device triggers (#46644) --- homeassistant/components/shelly/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index b4148801b3575d..0058374cfe7330 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -111,7 +111,7 @@ def get_device_channel_name( def is_momentary_input(settings: dict, block: aioshelly.Block) -> bool: """Return true if input button settings is set to a momentary type.""" # Shelly Button type is fixed to momentary and no btn_type - if settings["device"]["type"] == "SHBTN-1": + if settings["device"]["type"] in ("SHBTN-1", "SHBTN-2"): return True button = settings.get("relays") or settings.get("lights") or settings.get("inputs") @@ -158,7 +158,7 @@ def get_input_triggers( else: subtype = f"button{int(block.channel)+1}" - if device.settings["device"]["type"] == "SHBTN-1": + if device.settings["device"]["type"] in ("SHBTN-1", "SHBTN-2"): trigger_types = SHBTN_1_INPUTS_EVENTS_TYPES elif device.settings["device"]["type"] == "SHIX3-1": trigger_types = SHIX3_1_INPUTS_EVENTS_TYPES From dfbb6531073f93afe3f8a90abe6814a48c31392b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Feb 2021 13:43:53 +0100 Subject: [PATCH 0810/1818] Bump pychromecast to 9.0.0 (#47086) * Adapt to Pychromecast 9.0.0 * Bump pychromecast to 9.0.0 * Fix lint issues --- homeassistant/components/cast/discovery.py | 95 +++++------ homeassistant/components/cast/helpers.py | 19 +-- homeassistant/components/cast/manifest.json | 2 +- homeassistant/components/cast/media_player.py | 16 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/test_media_player.py | 148 ++++++++++-------- 7 files changed, 136 insertions(+), 148 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 4858d37f732777..81048b35a97eaa 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -9,6 +9,7 @@ from homeassistant.helpers.dispatcher import dispatcher_send from .const import ( + DEFAULT_PORT, INTERNAL_DISCOVERY_RUNNING_KEY, KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, @@ -19,8 +20,17 @@ _LOGGER = logging.getLogger(__name__) -def discover_chromecast(hass: HomeAssistant, info: ChromecastInfo): +def discover_chromecast(hass: HomeAssistant, device_info): """Discover a Chromecast.""" + + info = ChromecastInfo( + services=device_info.services, + uuid=device_info.uuid, + model_name=device_info.model_name, + friendly_name=device_info.friendly_name, + is_audio_group=device_info.port != DEFAULT_PORT, + ) + if info.uuid is None: _LOGGER.error("Discovered chromecast without uuid %s", info) return @@ -51,72 +61,39 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: # Internal discovery is already running return - def internal_add_update_callback(uuid, service_name): - """Handle zeroconf discovery of a new or updated chromecast.""" - service = listener.services[uuid] - - # For support of deprecated IP based white listing - zconf = ChromeCastZeroconf.get_zeroconf() - service_info = None - tries = 0 - while service_info is None and tries < 4: - try: - service_info = zconf.get_service_info( - "_googlecast._tcp.local.", service_name - ) - except OSError: - # If the zeroconf fails to receive the necessary data we abort - # adding the service - break - tries += 1 - - if not service_info: - _LOGGER.warning( - "setup_internal_discovery failed to get info for %s, %s", - uuid, - service_name, + class CastListener(pychromecast.discovery.AbstractCastListener): + """Listener for discovering chromecasts.""" + + def add_cast(self, uuid, _): + """Handle zeroconf discovery of a new chromecast.""" + discover_chromecast(hass, browser.devices[uuid]) + + def update_cast(self, uuid, _): + """Handle zeroconf discovery of an updated chromecast.""" + discover_chromecast(hass, browser.devices[uuid]) + + def remove_cast(self, uuid, service, cast_info): + """Handle zeroconf discovery of a removed chromecast.""" + _remove_chromecast( + hass, + ChromecastInfo( + services=cast_info.services, + uuid=cast_info.uuid, + model_name=cast_info.model_name, + friendly_name=cast_info.friendly_name, + ), ) - return - - addresses = service_info.parsed_addresses() - host = addresses[0] if addresses else service_info.server - - discover_chromecast( - hass, - ChromecastInfo( - services=service[0], - uuid=service[1], - model_name=service[2], - friendly_name=service[3], - host=host, - port=service_info.port, - ), - ) - - def internal_remove_callback(uuid, service_name, service): - """Handle zeroconf discovery of a removed chromecast.""" - _remove_chromecast( - hass, - ChromecastInfo( - services=service[0], - uuid=service[1], - model_name=service[2], - friendly_name=service[3], - ), - ) _LOGGER.debug("Starting internal pychromecast discovery") - listener = pychromecast.CastListener( - internal_add_update_callback, - internal_remove_callback, - internal_add_update_callback, + browser = pychromecast.discovery.CastBrowser( + CastListener(), ChromeCastZeroconf.get_zeroconf() ) - browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf()) + browser.start_discovery() def stop_discovery(event): """Stop discovery of new chromecasts.""" _LOGGER.debug("Stopping internal pychromecast discovery") - pychromecast.discovery.stop_discovery(browser) + browser.stop_discovery() hass.data[INTERNAL_DISCOVERY_RUNNING_KEY].release() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_discovery) diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index b8742ec2b5eb3e..91382c695914f2 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -7,8 +7,6 @@ from pychromecast import dial from pychromecast.const import CAST_MANUFACTURERS -from .const import DEFAULT_PORT - @attr.s(slots=True, frozen=True) class ChromecastInfo: @@ -18,21 +16,15 @@ class ChromecastInfo: """ services: Optional[set] = attr.ib() - host: Optional[str] = attr.ib(default=None) - port: Optional[int] = attr.ib(default=0) uuid: Optional[str] = attr.ib( converter=attr.converters.optional(str), default=None ) # always convert UUID to string if not None _manufacturer = attr.ib(type=Optional[str], default=None) model_name: str = attr.ib(default="") friendly_name: Optional[str] = attr.ib(default=None) + is_audio_group = attr.ib(type=Optional[bool], default=False) is_dynamic_group = attr.ib(type=Optional[bool], default=None) - @property - def is_audio_group(self) -> bool: - """Return if this is an audio group.""" - return self.port != DEFAULT_PORT - @property def is_information_complete(self) -> bool: """Return if all information is filled out.""" @@ -74,7 +66,7 @@ def fill_out_missing_chromecast_info(self) -> ChromecastInfo: http_group_status = None if self.uuid: http_group_status = dial.get_multizone_status( - self.host, + None, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf(), ) @@ -86,17 +78,16 @@ def fill_out_missing_chromecast_info(self) -> ChromecastInfo: return ChromecastInfo( services=self.services, - host=self.host, - port=self.port, uuid=self.uuid, friendly_name=self.friendly_name, model_name=self.model_name, + is_audio_group=True, is_dynamic_group=is_dynamic_group, ) # Fill out some missing information (friendly_name, uuid) via HTTP dial. http_device_status = dial.get_device_status( - self.host, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf() + None, services=self.services, zconf=ChromeCastZeroconf.get_zeroconf() ) if http_device_status is None: # HTTP dial didn't give us any new information. @@ -104,8 +95,6 @@ def fill_out_missing_chromecast_info(self) -> ChromecastInfo: return ChromecastInfo( services=self.services, - host=self.host, - port=self.port, uuid=(self.uuid or http_device_status.uuid), friendly_name=(self.friendly_name or http_device_status.friendly_name), manufacturer=(self.manufacturer or http_device_status.manufacturer), diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 28ccb78d5b9ca6..ac728b4ec4587a 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==8.1.2"], + "requirements": ["pychromecast==9.0.0"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 981d67f0caa561..235c7ab447972e 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -154,15 +154,15 @@ async def _async_setup_platform( hass.data.setdefault(ADDED_CAST_DEVICES_KEY, set()) hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, {}) - info = None + wanted_uuid = None if CONF_UUID in config: - info = ChromecastInfo(uuid=config[CONF_UUID], services=None) + wanted_uuid = config[CONF_UUID] @callback def async_cast_discovered(discover: ChromecastInfo) -> None: """Handle discovery of a new chromecast.""" - # If info is set, we're handling a specific cast device identified by UUID - if info is not None and (info.uuid is not None and info.uuid != discover.uuid): + # If wanted_uuid is set, we're handling a specific cast device identified by UUID + if wanted_uuid is not None and wanted_uuid != discover.uuid: # UUID not matching, this is not it. return @@ -251,8 +251,8 @@ async def async_connect_to_chromecast(self): self.services, ) chromecast = await self.hass.async_add_executor_job( - pychromecast.get_chromecast_from_service, - ( + pychromecast.get_chromecast_from_cast_info, + pychromecast.discovery.CastInfo( self.services, self._cast_info.uuid, self._cast_info.model_name, @@ -875,8 +875,8 @@ async def async_connect_to_chromecast(self): self.services, ) chromecast = await self.hass.async_add_executor_job( - pychromecast.get_chromecast_from_service, - ( + pychromecast.get_chromecast_from_cast_info, + pychromecast.discovery.CastInfo( self.services, self._cast_info.uuid, self._cast_info.model_name, diff --git a/requirements_all.txt b/requirements_all.txt index ea5abe0f221230..135e2c74f6353b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1305,7 +1305,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==8.1.2 +pychromecast==9.0.0 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2054da1aa6210..1499679fd5d230 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -688,7 +688,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==8.1.2 +pychromecast==9.0.0 # homeassistant.components.climacell pyclimacell==0.14.0 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index be24afcb5382d8..51c49484c50163 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -6,6 +6,7 @@ from uuid import UUID import attr +import pychromecast import pytest from homeassistant.components import tts @@ -47,6 +48,12 @@ def dial_mock(): return dial_mock +@pytest.fixture() +def castbrowser_mock(): + """Mock pychromecast CastBrowser.""" + return MagicMock() + + @pytest.fixture() def mz_mock(): """Mock pychromecast MultizoneManager.""" @@ -54,10 +61,13 @@ def mz_mock(): @pytest.fixture() -def pycast_mock(): +def pycast_mock(castbrowser_mock): """Mock pychromecast.""" pycast_mock = MagicMock() - pycast_mock.start_discovery.return_value = (None, Mock()) + pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock + pycast_mock.discovery.AbstractCastListener = ( + pychromecast.discovery.AbstractCastListener + ) return pycast_mock @@ -97,7 +107,7 @@ def cast_mock(dial_mock, mz_mock, pycast_mock, quick_play_mock): def get_fake_chromecast(info: ChromecastInfo): """Generate a Fake Chromecast object with the specified arguments.""" - mock = MagicMock(host=info.host, port=info.port, uuid=info.uuid) + mock = MagicMock(uuid=info.uuid) mock.media_controller.status = None return mock @@ -106,12 +116,35 @@ def get_fake_chromecast_info( host="192.168.178.42", port=8009, uuid: Optional[UUID] = FakeUUID ): """Generate a Fake ChromecastInfo with the specified arguments.""" - return ChromecastInfo( + + @attr.s(slots=True, frozen=True, eq=False) + class ExtendedChromecastInfo(ChromecastInfo): + host: Optional[str] = attr.ib(default=None) + port: Optional[int] = attr.ib(default=0) + + def __eq__(self, other): + if isinstance(other, ChromecastInfo): + return ( + ChromecastInfo( + services=self.services, + uuid=self.uuid, + manufacturer=self.manufacturer, + model_name=self.model_name, + friendly_name=self.friendly_name, + is_audio_group=self.is_audio_group, + is_dynamic_group=self.is_dynamic_group, + ) + == other + ) + return super().__eq__(other) + + return ExtendedChromecastInfo( host=host, port=port, uuid=uuid, friendly_name="Speaker", services={"the-service"}, + is_audio_group=port != 8009, ) @@ -141,32 +174,30 @@ async def async_setup_cast(hass, config=None): async def async_setup_cast_internal_discovery(hass, config=None): """Set up the cast platform and the discovery.""" - listener = MagicMock(services={}) - browser = MagicMock(zc={}) + browser = MagicMock(devices={}, zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.CastListener", - return_value=listener, - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", + "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", return_value=browser, - ) as start_discovery: + ) as cast_browser: add_entities = await async_setup_cast(hass, config) await hass.async_block_till_done() await hass.async_block_till_done() - assert start_discovery.call_count == 1 + assert browser.start_discovery.call_count == 1 - discovery_callback = cast_listener.call_args[0][0] - remove_callback = cast_listener.call_args[0][1] + discovery_callback = cast_browser.call_args[0][0].add_cast + remove_callback = cast_browser.call_args[0][0].remove_cast def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" - listener.services[info.uuid] = ( + browser.devices[info.uuid] = pychromecast.discovery.CastInfo( {service_name}, info.uuid, info.model_name, info.friendly_name, + info.host, + info.port, ) discovery_callback(info.uuid, service_name) @@ -175,7 +206,14 @@ def remove_chromecast(service_name: str, info: ChromecastInfo) -> None: remove_callback( info.uuid, service_name, - (set(), info.uuid, info.model_name, info.friendly_name), + pychromecast.discovery.CastInfo( + set(), + info.uuid, + info.model_name, + info.friendly_name, + info.host, + info.port, + ), ) return discover_chromecast, remove_chromecast, add_entities @@ -183,21 +221,17 @@ def remove_chromecast(service_name: str, info: ChromecastInfo) -> None: async def async_setup_media_player_cast(hass: HomeAssistantType, info: ChromecastInfo): """Set up the cast platform with async_setup_component.""" - listener = MagicMock(services={}) - browser = MagicMock(zc={}) + browser = MagicMock(devices={}, zc={}) chromecast = get_fake_chromecast(info) zconf = get_fake_zconf(host=info.host, port=info.port) with patch( - "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service", + "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_cast_info", return_value=chromecast, ) as get_chromecast, patch( - "homeassistant.components.cast.discovery.pychromecast.CastListener", - return_value=listener, - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", + "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", return_value=browser, - ), patch( + ) as cast_browser, patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", return_value=zconf, ): @@ -205,15 +239,18 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas hass, "cast", {"cast": {"media_player": {"uuid": info.uuid}}} ) await hass.async_block_till_done() + await hass.async_block_till_done() - discovery_callback = cast_listener.call_args[0][0] + discovery_callback = cast_browser.call_args[0][0].add_cast service_name = "the-service" - listener.services[info.uuid] = ( + browser.devices[info.uuid] = pychromecast.discovery.CastInfo( {service_name}, info.uuid, info.model_name, info.friendly_name, + info.host, + info.port, ) discovery_callback(info.uuid, service_name) @@ -223,11 +260,13 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" - listener.services[info.uuid] = ( + browser.devices[info.uuid] = pychromecast.discovery.CastInfo( {service_name}, info.uuid, info.model_name, info.friendly_name, + info.host, + info.port, ) discovery_callback(info.uuid, service_name) @@ -253,18 +292,13 @@ def get_status_callbacks(chromecast_mock, mz_mock=None): return cast_status_cb, conn_status_cb, media_status_cb, group_media_status_cb -async def test_start_discovery_called_once(hass): +async def test_start_discovery_called_once(hass, castbrowser_mock): """Test pychromecast.start_discovery called exactly once.""" - with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=Mock(), - ) as start_discovery: - await async_setup_cast(hass) - - assert start_discovery.call_count == 1 + await async_setup_cast(hass) + assert castbrowser_mock.start_discovery.call_count == 1 - await async_setup_cast(hass) - assert start_discovery.call_count == 1 + await async_setup_cast(hass) + assert castbrowser_mock.start_discovery.call_count == 1 async def test_internal_discovery_callback_fill_out(hass): @@ -350,7 +384,6 @@ async def test_internal_discovery_callback_fill_out_fail(hass): # when called with incomplete info, it should use HTTP to get missing discover = signal.mock_calls[0][1][0] assert discover == full_info - # assert 1 == 2 async def test_internal_discovery_callback_fill_out_group(hass): @@ -384,27 +417,16 @@ async def test_internal_discovery_callback_fill_out_group(hass): assert discover == full_info -async def test_stop_discovery_called_on_stop(hass): +async def test_stop_discovery_called_on_stop(hass, castbrowser_mock): """Test pychromecast.stop_discovery called on shutdown.""" - browser = MagicMock(zc={}) - - with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=browser, - ) as start_discovery: - # start_discovery should be called with empty config - await async_setup_cast(hass, {}) - - assert start_discovery.call_count == 1 + # start_discovery should be called with empty config + await async_setup_cast(hass, {}) + assert castbrowser_mock.start_discovery.call_count == 1 - with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.stop_discovery" - ) as stop_discovery: - # stop discovery should be called on shutdown - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - - stop_discovery.assert_called_once_with(browser) + # stop discovery should be called on shutdown + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert castbrowser_mock.stop_discovery.call_count == 1 async def test_create_cast_device_without_uuid(hass): @@ -539,7 +561,7 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): tmp2.uuid = FakeUUID2 dial_mock.get_multizone_status.return_value.dynamic_groups = [tmp1, tmp2] - pycast_mock.get_chromecast_from_service.assert_not_called() + pycast_mock.get_chromecast_from_cast_info.assert_not_called() discover_cast, remove_cast, add_dev1 = await async_setup_cast_internal_discovery( hass ) @@ -552,8 +574,8 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): discover_cast("service", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs - pycast_mock.get_chromecast_from_service.assert_called() - pycast_mock.get_chromecast_from_service.reset_mock() + pycast_mock.get_chromecast_from_cast_info.assert_called() + pycast_mock.get_chromecast_from_cast_info.reset_mock() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None @@ -565,8 +587,8 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): discover_cast("service", cast_2) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs - pycast_mock.get_chromecast_from_service.assert_called() - pycast_mock.get_chromecast_from_service.reset_mock() + pycast_mock.get_chromecast_from_cast_info.assert_called() + pycast_mock.get_chromecast_from_cast_info.reset_mock() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None @@ -578,7 +600,7 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): discover_cast("service", cast_1) await hass.async_block_till_done() await hass.async_block_till_done() # having tasks that add jobs - pycast_mock.get_chromecast_from_service.assert_not_called() + pycast_mock.get_chromecast_from_cast_info.assert_not_called() assert add_dev1.call_count == 0 assert reg.async_get_entity_id("media_player", "cast", cast_1.uuid) is None From 56673f7edff97dd60f3b8ffa5ec22d49aad4d603 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 26 Feb 2021 07:45:21 -0500 Subject: [PATCH 0811/1818] Remove flaky climacell test (#47080) --- tests/components/climacell/test_init.py | 46 +------------------------ 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/tests/components/climacell/test_init.py b/tests/components/climacell/test_init.py index 1456a068d77c8d..f3d7e490090ade 100644 --- a/tests/components/climacell/test_init.py +++ b/tests/components/climacell/test_init.py @@ -1,7 +1,5 @@ """Tests for Climacell init.""" -from datetime import timedelta import logging -from unittest.mock import patch import pytest @@ -12,11 +10,10 @@ from homeassistant.components.climacell.const import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.util import dt as dt_util from .const import MIN_CONFIG -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import MockConfigEntry _LOGGER = logging.getLogger(__name__) @@ -39,44 +36,3 @@ async def test_load_and_unload( assert await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 0 - - -async def test_update_interval( - hass: HomeAssistantType, - climacell_config_entry_update: pytest.fixture, -) -> None: - """Test that update_interval changes based on number of entries.""" - now = dt_util.utcnow() - async_fire_time_changed(hass, now) - config = _get_config_schema(hass)(MIN_CONFIG) - for i in range(1, 3): - config_entry = MockConfigEntry( - domain=DOMAIN, data=config, unique_id=_get_unique_id(hass, config) + str(i) - ) - config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - with patch("homeassistant.components.climacell.ClimaCell.realtime") as mock_api: - # First entry refresh will happen in 7 minutes due to original update interval. - # Next refresh for this entry will happen at 20 minutes due to the update interval - # change. - mock_api.return_value = {} - async_fire_time_changed(hass, now + timedelta(minutes=7)) - await hass.async_block_till_done() - assert mock_api.call_count == 1 - - # Second entry refresh will happen in 13 minutes due to the update interval set - # when it was set up. Next refresh for this entry will happen at 26 minutes due to the - # update interval change. - mock_api.reset_mock() - async_fire_time_changed(hass, now + timedelta(minutes=13)) - await hass.async_block_till_done() - assert not mock_api.call_count == 1 - - # 19 minutes should be after the first update for each config entry and before the - # second update for the first config entry - mock_api.reset_mock() - async_fire_time_changed(hass, now + timedelta(minutes=19)) - await hass.async_block_till_done() - assert not mock_api.call_count == 0 From 9c67f83f4e058f86ba6c4a2d6a0b4fb4e6d99ae6 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Fri, 26 Feb 2021 13:57:47 +0100 Subject: [PATCH 0812/1818] Bump bimmer_connected to 0.7.15 and fix bugs (#47066) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/__init__.py | 2 +- .../components/bmw_connected_drive/device_tracker.py | 4 ++-- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 9d794ace5be144..a8bebfbc617b0a 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -122,7 +122,7 @@ async def _async_update_all(service_call=None): def _update_all() -> None: """Update all BMW accounts.""" - for entry in hass.data[DOMAIN][DATA_ENTRIES].values(): + for entry in hass.data[DOMAIN][DATA_ENTRIES].copy().values(): entry[CONF_ACCOUNT].update() # Add update listener for config entry changes (options) diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 7f069e741b8ecd..25adf6cb09f946 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -42,12 +42,12 @@ def __init__(self, account, vehicle): @property def latitude(self): """Return latitude value of the device.""" - return self._location[0] + return self._location[0] if self._location else None @property def longitude(self): """Return longitude value of the device.""" - return self._location[1] + return self._location[1] if self._location else None @property def name(self): diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index c1d90f713f4adf..bbff139187e8b9 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.7.14"], + "requirements": ["bimmer_connected==0.7.15"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 135e2c74f6353b..2353797b8b8a69 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -343,7 +343,7 @@ beautifulsoup4==4.9.3 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.14 +bimmer_connected==0.7.15 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1499679fd5d230..b6cfca171e21a0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -196,7 +196,7 @@ base36==0.1.1 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.14 +bimmer_connected==0.7.15 # homeassistant.components.blebox blebox_uniapi==1.3.2 From 71cf982a281a37e13a8f8f261654a485cf65ee26 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Fri, 26 Feb 2021 15:48:36 +0100 Subject: [PATCH 0813/1818] Add suggested_area support to devolo Home Control (#47063) --- homeassistant/components/devolo_home_control/devolo_device.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/devolo_home_control/devolo_device.py b/homeassistant/components/devolo_home_control/devolo_device.py index d0be9543bf4189..6aef842ffff185 100644 --- a/homeassistant/components/devolo_home_control/devolo_device.py +++ b/homeassistant/components/devolo_home_control/devolo_device.py @@ -18,6 +18,7 @@ def __init__(self, homecontrol, device_instance, element_uid): self._unique_id = element_uid self._homecontrol = homecontrol self._name = device_instance.settings_property["general_device_settings"].name + self._area = device_instance.settings_property["general_device_settings"].zone self._device_class = None self._value = None self._unit = None @@ -59,6 +60,7 @@ def device_info(self): "name": self._name, "manufacturer": self._brand, "model": self._model, + "suggested_area": self._area, } @property From 6e77ca70fcb7370189d7dc667394af71168d537a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 26 Feb 2021 15:49:33 +0100 Subject: [PATCH 0814/1818] Add new machine generic-x86-64 to build matrix (#47095) The Intel NUC machine runs on most UEFI capable x86-64 machines today. Lets start a new machine generic-x86-64 which will replace intel-nuc over time. --- azure-pipelines-release.yml | 4 +++- machine/generic-x86-64 | 34 ++++++++++++++++++++++++++++++++++ machine/intel-nuc | 3 +++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 machine/generic-x86-64 diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 418fdf5b26c5bf..5fe9132558207d 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -114,10 +114,12 @@ stages: pool: vmImage: 'ubuntu-latest' strategy: - maxParallel: 15 + maxParallel: 17 matrix: qemux86-64: buildMachine: 'qemux86-64' + generic-x86-64: + buildMachine: 'generic-x86-64' intel-nuc: buildMachine: 'intel-nuc' qemux86: diff --git a/machine/generic-x86-64 b/machine/generic-x86-64 new file mode 100644 index 00000000000000..e858c38222192b --- /dev/null +++ b/machine/generic-x86-64 @@ -0,0 +1,34 @@ +ARG BUILD_VERSION +FROM agners/amd64-homeassistant:$BUILD_VERSION + +RUN apk --no-cache add \ + libva-intel-driver \ + usbutils + +## +# Build libcec for HDMI-CEC +ARG LIBCEC_VERSION=6.0.2 +RUN apk add --no-cache \ + eudev-libs \ + p8-platform \ + && apk add --no-cache --virtual .build-dependencies \ + build-base \ + cmake \ + eudev-dev \ + swig \ + p8-platform-dev \ + linux-headers \ + && git clone --depth 1 -b libcec-${LIBCEC_VERSION} https://github.com/Pulse-Eight/libcec /usr/src/libcec \ + && cd /usr/src/libcec \ + && mkdir -p /usr/src/libcec/build \ + && cd /usr/src/libcec/build \ + && cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local \ + -DPYTHON_LIBRARY="/usr/local/lib/libpython3.8.so" \ + -DPYTHON_INCLUDE_DIR="/usr/local/include/python3.8" \ + -DHAVE_LINUX_API=1 \ + .. \ + && make -j$(nproc) \ + && make install \ + && echo "cec" > "/usr/local/lib/python3.8/site-packages/cec.pth" \ + && apk del .build-dependencies \ + && rm -rf /usr/src/libcec* diff --git a/machine/intel-nuc b/machine/intel-nuc index 4c83228387ddb9..b5538b8ccad233 100644 --- a/machine/intel-nuc +++ b/machine/intel-nuc @@ -1,6 +1,9 @@ ARG BUILD_VERSION FROM homeassistant/amd64-homeassistant:$BUILD_VERSION +# NOTE: intel-nuc will be replaced by generic-x86-64. Make sure to apply +# changes in generic-x86-64 as well. + RUN apk --no-cache add \ libva-intel-driver \ usbutils From d8633f94f6b94b77003dfd83aece47707bbac1db Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 26 Feb 2021 10:07:50 -0500 Subject: [PATCH 0815/1818] Guard zwave_js missing nodes in websocket api (#47096) --- homeassistant/components/zwave_js/api.py | 14 ++++++++++-- tests/components/zwave_js/test_api.py | 27 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 12ec66906a8707..599183eba7e8fd 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -106,7 +106,12 @@ def websocket_node_status( entry_id = msg[ENTRY_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] node_id = msg[NODE_ID] - node = client.driver.controller.nodes[node_id] + node = client.driver.controller.nodes.get(node_id) + + if node is None: + connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found") + return + data = { "node_id": node.node_id, "is_routing": node.is_routing, @@ -354,7 +359,12 @@ def websocket_get_config_parameters( entry_id = msg[ENTRY_ID] node_id = msg[NODE_ID] client = hass.data[DOMAIN][entry_id][DATA_CLIENT] - node = client.driver.controller.nodes[node_id] + node = client.driver.controller.nodes.get(node_id) + + if node is None: + connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found") + return + values = node.get_configuration_values() result = {} for value_id, zwave_value in values.items(): diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index dcbd924c86e6ca..9760e1a06b7038 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -6,6 +6,7 @@ from zwave_js_server.event import Event from zwave_js_server.exceptions import InvalidNewValue, NotFoundError, SetValueFailed +from homeassistant.components.websocket_api.const import ERR_NOT_FOUND from homeassistant.components.zwave_js.api import ( CONFIG, ENABLED, @@ -76,6 +77,32 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): assert result[key]["configuration_value_type"] == "enumerated" assert result[key]["metadata"]["states"] + # Test getting non-existent node fails + await ws_client.send_json( + { + ID: 5, + TYPE: "zwave_js/node_status", + ENTRY_ID: entry.entry_id, + NODE_ID: 99999, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND + + # Test getting non-existent node config params fails + await ws_client.send_json( + { + ID: 6, + TYPE: "zwave_js/get_config_parameters", + ENTRY_ID: entry.entry_id, + NODE_ID: 99999, + } + ) + msg = await ws_client.receive_json() + assert not msg["success"] + assert msg["error"]["code"] == ERR_NOT_FOUND + async def test_add_node( hass, integration, client, hass_ws_client, nortek_thermostat_added_event From 7ab2d91bf09dededf76e20c3797ae2188e87d3ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 10:35:09 -0600 Subject: [PATCH 0816/1818] Add suggested area to hue (#47056) --- homeassistant/components/hue/const.py | 5 + homeassistant/components/hue/light.py | 151 ++++++++++++++++++++------ tests/components/hue/test_light.py | 99 ++++++++++++----- 3 files changed, 191 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 593f74331ec46c..b782ce70193890 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -15,3 +15,8 @@ DEFAULT_ALLOW_HUE_GROUPS = False DEFAULT_SCENE_TRANSITION = 4 + +GROUP_TYPE_LIGHT_GROUP = "LightGroup" +GROUP_TYPE_ROOM = "Room" +GROUP_TYPE_LUMINAIRE = "Luminaire" +GROUP_TYPE_LIGHT_SOURCE = "LightSource" diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 821d482ec25dd9..6384e47b45e823 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -36,7 +36,14 @@ ) from homeassistant.util import color -from .const import DOMAIN as HUE_DOMAIN, REQUEST_REFRESH_DELAY +from .const import ( + DOMAIN as HUE_DOMAIN, + GROUP_TYPE_LIGHT_GROUP, + GROUP_TYPE_LIGHT_SOURCE, + GROUP_TYPE_LUMINAIRE, + GROUP_TYPE_ROOM, + REQUEST_REFRESH_DELAY, +) from .helpers import remove_devices SCAN_INTERVAL = timedelta(seconds=5) @@ -74,24 +81,35 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """ -def create_light(item_class, coordinator, bridge, is_group, api, item_id): +def create_light(item_class, coordinator, bridge, is_group, rooms, api, item_id): """Create the light.""" + api_item = api[item_id] + if is_group: supported_features = 0 - for light_id in api[item_id].lights: + for light_id in api_item.lights: if light_id not in bridge.api.lights: continue light = bridge.api.lights[light_id] supported_features |= SUPPORT_HUE.get(light.type, SUPPORT_HUE_EXTENDED) supported_features = supported_features or SUPPORT_HUE_EXTENDED else: - supported_features = SUPPORT_HUE.get(api[item_id].type, SUPPORT_HUE_EXTENDED) - return item_class(coordinator, bridge, is_group, api[item_id], supported_features) + supported_features = SUPPORT_HUE.get(api_item.type, SUPPORT_HUE_EXTENDED) + return item_class( + coordinator, bridge, is_group, api_item, supported_features, rooms + ) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Hue lights from a config entry.""" bridge = hass.data[HUE_DOMAIN][config_entry.entry_id] + api_version = tuple(int(v) for v in bridge.api.config.apiversion.split(".")) + rooms = {} + + allow_groups = bridge.allow_groups + supports_groups = api_version >= GROUP_MIN_API_VERSION + if allow_groups and not supports_groups: + _LOGGER.warning("Please update your Hue bridge to support groups") light_coordinator = DataUpdateCoordinator( hass, @@ -111,27 +129,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if not light_coordinator.last_update_success: raise PlatformNotReady - update_lights = partial( - async_update_items, - bridge, - bridge.api.lights, - {}, - async_add_entities, - partial(create_light, HueLight, light_coordinator, bridge, False), - ) - - # We add a listener after fetching the data, so manually trigger listener - bridge.reset_jobs.append(light_coordinator.async_add_listener(update_lights)) - update_lights() - - api_version = tuple(int(v) for v in bridge.api.config.apiversion.split(".")) - - allow_groups = bridge.allow_groups - if allow_groups and api_version < GROUP_MIN_API_VERSION: - _LOGGER.warning("Please update your Hue bridge to support groups") - allow_groups = False - - if not allow_groups: + if not supports_groups: + update_lights_without_group_support = partial( + async_update_items, + bridge, + bridge.api.lights, + {}, + async_add_entities, + partial(create_light, HueLight, light_coordinator, bridge, False, rooms), + None, + ) + # We add a listener after fetching the data, so manually trigger listener + bridge.reset_jobs.append( + light_coordinator.async_add_listener(update_lights_without_group_support) + ) return group_coordinator = DataUpdateCoordinator( @@ -145,17 +156,69 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ), ) - update_groups = partial( + if allow_groups: + update_groups = partial( + async_update_items, + bridge, + bridge.api.groups, + {}, + async_add_entities, + partial(create_light, HueLight, group_coordinator, bridge, True, None), + None, + ) + + bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups)) + + cancel_update_rooms_listener = None + + @callback + def _async_update_rooms(): + """Update rooms.""" + nonlocal cancel_update_rooms_listener + rooms.clear() + for item_id in bridge.api.groups: + group = bridge.api.groups[item_id] + if group.type != GROUP_TYPE_ROOM: + continue + for light_id in group.lights: + rooms[light_id] = group.name + + # Once we do a rooms update, we cancel the listener + # until the next time lights are added + bridge.reset_jobs.remove(cancel_update_rooms_listener) + cancel_update_rooms_listener() # pylint: disable=not-callable + cancel_update_rooms_listener = None + + @callback + def _setup_rooms_listener(): + nonlocal cancel_update_rooms_listener + if cancel_update_rooms_listener is not None: + # If there are new lights added before _async_update_rooms + # is called we should not add another listener + return + + cancel_update_rooms_listener = group_coordinator.async_add_listener( + _async_update_rooms + ) + bridge.reset_jobs.append(cancel_update_rooms_listener) + + _setup_rooms_listener() + await group_coordinator.async_refresh() + + update_lights_with_group_support = partial( async_update_items, bridge, - bridge.api.groups, + bridge.api.lights, {}, async_add_entities, - partial(create_light, HueLight, group_coordinator, bridge, True), + partial(create_light, HueLight, light_coordinator, bridge, False, rooms), + _setup_rooms_listener, ) - - bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups)) - await group_coordinator.async_refresh() + # We add a listener after fetching the data, so manually trigger listener + bridge.reset_jobs.append( + light_coordinator.async_add_listener(update_lights_with_group_support) + ) + update_lights_with_group_support() async def async_safe_fetch(bridge, fetch_method): @@ -171,7 +234,9 @@ async def async_safe_fetch(bridge, fetch_method): @callback -def async_update_items(bridge, api, current, async_add_entities, create_item): +def async_update_items( + bridge, api, current, async_add_entities, create_item, new_items_callback +): """Update items.""" new_items = [] @@ -185,6 +250,9 @@ def async_update_items(bridge, api, current, async_add_entities, create_item): bridge.hass.async_create_task(remove_devices(bridge, api, current)) if new_items: + # This is currently used to setup the listener to update rooms + if new_items_callback: + new_items_callback() async_add_entities(new_items) @@ -201,13 +269,14 @@ def hass_to_hue_brightness(value): class HueLight(CoordinatorEntity, LightEntity): """Representation of a Hue light.""" - def __init__(self, coordinator, bridge, is_group, light, supported_features): + def __init__(self, coordinator, bridge, is_group, light, supported_features, rooms): """Initialize the light.""" super().__init__(coordinator) self.light = light self.bridge = bridge self.is_group = is_group self._supported_features = supported_features + self._rooms = rooms if is_group: self.is_osram = False @@ -355,10 +424,15 @@ def effect_list(self): @property def device_info(self): """Return the device info.""" - if self.light.type in ("LightGroup", "Room", "Luminaire", "LightSource"): + if self.light.type in ( + GROUP_TYPE_LIGHT_GROUP, + GROUP_TYPE_ROOM, + GROUP_TYPE_LUMINAIRE, + GROUP_TYPE_LIGHT_SOURCE, + ): return None - return { + info = { "identifiers": {(HUE_DOMAIN, self.device_id)}, "name": self.name, "manufacturer": self.light.manufacturername, @@ -370,6 +444,11 @@ def device_info(self): "via_device": (HUE_DOMAIN, self.bridge.api.config.bridgeid), } + if self.light.id in self._rooms: + info["suggested_area"] = self._rooms[self.light.id] + + return info + async def async_turn_on(self, **kwargs): """Turn the specified or all lights on.""" command = {"on": True} diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 629a9a4c98bad8..39b9a5a23fc7b0 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -7,6 +7,12 @@ from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import light as hue_light +from homeassistant.helpers.device_registry import ( + async_get_registry as async_get_device_registry, +) +from homeassistant.helpers.entity_registry import ( + async_get_registry as async_get_entity_registry, +) from homeassistant.util import color HUE_LIGHT_NS = "homeassistant.components.light.hue." @@ -211,8 +217,10 @@ async def test_no_lights_or_groups(hass, mock_bridge): async def test_lights(hass, mock_bridge): """Test the update_lights function with some lights.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 # 2 lights assert len(hass.states.async_all()) == 2 @@ -230,6 +238,8 @@ async def test_lights(hass, mock_bridge): async def test_lights_color_mode(hass, mock_bridge): """Test that lights only report appropriate color mode.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) lamp_1 = hass.states.get("light.hue_lamp_1") @@ -249,8 +259,8 @@ async def test_lights_color_mode(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_2"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 lamp_1 = hass.states.get("light.hue_lamp_1") assert lamp_1 is not None @@ -332,9 +342,10 @@ async def test_new_group_discovered(hass, mock_bridge): async def test_new_light_discovered(hass, mock_bridge): """Test if 2nd update has a new light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 new_light_response = dict(LIGHT_RESPONSE) @@ -366,8 +377,8 @@ async def test_new_light_discovered(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 3 light = hass.states.get("light.hue_lamp_3") @@ -407,9 +418,10 @@ async def test_group_removed(hass, mock_bridge): async def test_light_removed(hass, mock_bridge): """Test if 2nd update has removed light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 mock_bridge.mock_light_responses.clear() @@ -420,8 +432,8 @@ async def test_light_removed(hass, mock_bridge): "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 1 light = hass.states.get("light.hue_lamp_1") @@ -487,9 +499,10 @@ async def test_other_group_update(hass, mock_bridge): async def test_other_light_update(hass, mock_bridge): """Test changing one light that will impact state of other light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 lamp_2 = hass.states.get("light.hue_lamp_2") @@ -526,8 +539,8 @@ async def test_other_light_update(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 2 lamp_2 = hass.states.get("light.hue_lamp_2") @@ -549,7 +562,6 @@ async def test_update_timeout(hass, mock_bridge): async def test_update_unauthorized(hass, mock_bridge): """Test bridge marked as not authorized if unauthorized during update.""" mock_bridge.api.lights.update = Mock(side_effect=aiohue.Unauthorized) - mock_bridge.api.groups.update = Mock(side_effect=aiohue.Unauthorized) await setup_bridge(hass, mock_bridge) assert len(mock_bridge.mock_requests) == 0 assert len(hass.states.async_all()) == 0 @@ -559,6 +571,8 @@ async def test_update_unauthorized(hass, mock_bridge): async def test_light_turn_on_service(hass, mock_bridge): """Test calling the turn on service on a light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) light = hass.states.get("light.hue_lamp_2") assert light is not None @@ -575,10 +589,10 @@ async def test_light_turn_on_service(hass, mock_bridge): {"entity_id": "light.hue_lamp_2", "brightness": 100, "color_temp": 300}, blocking=True, ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 - assert mock_bridge.mock_requests[1]["json"] == { + assert mock_bridge.mock_requests[2]["json"] == { "bri": 100, "on": True, "ct": 300, @@ -599,9 +613,9 @@ async def test_light_turn_on_service(hass, mock_bridge): blocking=True, ) - assert len(mock_bridge.mock_requests) == 5 + assert len(mock_bridge.mock_requests) == 6 - assert mock_bridge.mock_requests[3]["json"] == { + assert mock_bridge.mock_requests[4]["json"] == { "on": True, "xy": (0.138, 0.08), "alert": "none", @@ -611,6 +625,8 @@ async def test_light_turn_on_service(hass, mock_bridge): async def test_light_turn_off_service(hass, mock_bridge): """Test calling the turn on service on a light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) light = hass.states.get("light.hue_lamp_1") assert light is not None @@ -624,10 +640,11 @@ async def test_light_turn_off_service(hass, mock_bridge): await hass.services.async_call( "light", "turn_off", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 - assert mock_bridge.mock_requests[1]["json"] == {"on": False, "alert": "none"} + # 2x light update, 1 for group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 + + assert mock_bridge.mock_requests[2]["json"] == {"on": False, "alert": "none"} assert len(hass.states.async_all()) == 2 @@ -649,6 +666,7 @@ def test_available(): bridge=Mock(allow_unreachable=False), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is False @@ -664,6 +682,7 @@ def test_available(): bridge=Mock(allow_unreachable=True), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is True @@ -679,6 +698,7 @@ def test_available(): bridge=Mock(allow_unreachable=False), is_group=True, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is True @@ -697,6 +717,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color is None @@ -712,6 +733,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color is None @@ -727,6 +749,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color == color.color_xy_to_hs(0.4, 0.5, LIGHT_GAMUT) @@ -742,7 +765,7 @@ async def test_group_features(hass, mock_bridge): "1": { "name": "Group 1", "lights": ["1", "2"], - "type": "Room", + "type": "LightGroup", "action": { "on": True, "bri": 254, @@ -757,8 +780,8 @@ async def test_group_features(hass, mock_bridge): "state": {"any_on": True, "all_on": False}, }, "2": { - "name": "Group 2", - "lights": ["3", "4"], + "name": "Living Room", + "lights": ["2", "3"], "type": "Room", "action": { "on": True, @@ -774,8 +797,8 @@ async def test_group_features(hass, mock_bridge): "state": {"any_on": True, "all_on": False}, }, "3": { - "name": "Group 3", - "lights": ["1", "3"], + "name": "Dining Room", + "lights": ["4"], "type": "Room", "action": { "on": True, @@ -900,6 +923,7 @@ async def test_group_features(hass, mock_bridge): mock_bridge.mock_light_responses.append(light_response) mock_bridge.mock_group_responses.append(group_response) await setup_bridge(hass, mock_bridge) + assert len(mock_bridge.mock_requests) == 2 color_temp_feature = hue_light.SUPPORT_HUE["Color temperature light"] extended_color_feature = hue_light.SUPPORT_HUE["Extended color light"] @@ -907,8 +931,27 @@ async def test_group_features(hass, mock_bridge): group_1 = hass.states.get("light.group_1") assert group_1.attributes["supported_features"] == color_temp_feature - group_2 = hass.states.get("light.group_2") + group_2 = hass.states.get("light.living_room") assert group_2.attributes["supported_features"] == extended_color_feature - group_3 = hass.states.get("light.group_3") + group_3 = hass.states.get("light.dining_room") assert group_3.attributes["supported_features"] == extended_color_feature + + entity_registry = await async_get_entity_registry(hass) + device_registry = await async_get_device_registry(hass) + + entry = entity_registry.async_get("light.hue_lamp_1") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area is None + + entry = entity_registry.async_get("light.hue_lamp_2") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Living Room" + + entry = entity_registry.async_get("light.hue_lamp_3") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Living Room" + + entry = entity_registry.async_get("light.hue_lamp_4") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Dining Room" From e12eba1989b7319cc24af82be8e8ffbc449a6d04 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 26 Feb 2021 18:34:40 +0100 Subject: [PATCH 0817/1818] Add support for v6 features to philips js integration (#46422) --- .../components/philips_js/__init__.py | 54 ++- .../components/philips_js/config_flow.py | 137 ++++-- homeassistant/components/philips_js/const.py | 3 + .../components/philips_js/manifest.json | 2 +- .../components/philips_js/media_player.py | 403 ++++++++++++++---- .../components/philips_js/strings.json | 6 +- .../philips_js/translations/en.json | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/philips_js/__init__.py | 56 ++- tests/components/philips_js/conftest.py | 13 +- .../components/philips_js/test_config_flow.py | 144 ++++++- .../philips_js/test_device_trigger.py | 6 +- 13 files changed, 698 insertions(+), 134 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 11e84b6cd82990..f3c2eb59789412 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -8,8 +8,13 @@ from homeassistant.components.automation import AutomationActionType from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_VERSION, CONF_HOST -from homeassistant.core import Context, HassJob, HomeAssistant, callback +from homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, +) +from homeassistant.core import CALLBACK_TYPE, Context, HassJob, HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -30,7 +35,12 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Philips TV from a config entry.""" - tvapi = PhilipsTV(entry.data[CONF_HOST], entry.data[CONF_API_VERSION]) + tvapi = PhilipsTV( + entry.data[CONF_HOST], + entry.data[CONF_API_VERSION], + username=entry.data.get(CONF_USERNAME), + password=entry.data.get(CONF_PASSWORD), + ) coordinator = PhilipsTVDataUpdateCoordinator(hass, tvapi) @@ -103,7 +113,9 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): def __init__(self, hass, api: PhilipsTV) -> None: """Set up the coordinator.""" self.api = api + self._notify_future: Optional[asyncio.Task] = None + @callback def _update_listeners(): for update_callback in self._listeners: update_callback() @@ -120,9 +132,43 @@ def _update_listeners(): ), ) + async def _notify_task(self): + while self.api.on and self.api.notify_change_supported: + if await self.api.notifyChange(130): + self.async_set_updated_data(None) + + @callback + def _async_notify_stop(self): + if self._notify_future: + self._notify_future.cancel() + self._notify_future = None + + @callback + def _async_notify_schedule(self): + if ( + (self._notify_future is None or self._notify_future.done()) + and self.api.on + and self.api.notify_change_supported + ): + self._notify_future = self.hass.loop.create_task(self._notify_task()) + + @callback + def async_remove_listener(self, update_callback: CALLBACK_TYPE) -> None: + """Remove data update.""" + super().async_remove_listener(update_callback) + if not self._listeners: + self._async_notify_stop() + + @callback + def _async_stop_refresh(self, event: asyncio.Event) -> None: + super()._async_stop_refresh(event) + self._async_notify_stop() + + @callback async def _async_update_data(self): """Fetch the latest data from the source.""" try: - await self.hass.async_add_executor_job(self.api.update) + await self.api.update() + self._async_notify_schedule() except ConnectionFailure: pass diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 523918daa7c6e3..778bcba282bbbf 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,35 +1,47 @@ """Config flow for Philips TV integration.""" -import logging -from typing import Any, Dict, Optional, TypedDict +import platform +from typing import Any, Dict, Optional, Tuple, TypedDict -from haphilipsjs import ConnectionFailure, PhilipsTV +from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import CONF_API_VERSION, CONF_HOST - -from .const import DOMAIN # pylint:disable=unused-import - -_LOGGER = logging.getLogger(__name__) +from homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_PIN, + CONF_USERNAME, +) + +from . import LOGGER +from .const import ( # pylint:disable=unused-import + CONF_SYSTEM, + CONST_APP_ID, + CONST_APP_NAME, + DOMAIN, +) class FlowUserDict(TypedDict): """Data for user step.""" host: str - api_version: int -async def validate_input(hass: core.HomeAssistant, data: FlowUserDict): +async def validate_input( + hass: core.HomeAssistant, host: str, api_version: int +) -> Tuple[Dict, PhilipsTV]: """Validate the user input allows us to connect.""" - hub = PhilipsTV(data[CONF_HOST], data[CONF_API_VERSION]) + hub = PhilipsTV(host, api_version) - await hass.async_add_executor_job(hub.getSystem) + await hub.getSystem() + await hub.setTransport(hub.secured_transport) - if hub.system is None: - raise ConnectionFailure + if not hub.system: + raise ConnectionFailure("System data is empty") - return hub.system + return hub class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -38,7 +50,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - _default = {} + _current = {} + _hub: PhilipsTV + _pair_state: Any async def async_step_import(self, conf: Dict[str, Any]): """Import a configuration from config.yaml.""" @@ -53,34 +67,99 @@ async def async_step_import(self, conf: Dict[str, Any]): } ) + async def _async_create_current(self): + + system = self._current[CONF_SYSTEM] + return self.async_create_entry( + title=f"{system['name']} ({system['serialnumber']})", + data=self._current, + ) + + async def async_step_pair(self, user_input: Optional[Dict] = None): + """Attempt to pair with device.""" + assert self._hub + + errors = {} + schema = vol.Schema( + { + vol.Required(CONF_PIN): str, + } + ) + + if not user_input: + try: + self._pair_state = await self._hub.pairRequest( + CONST_APP_ID, + CONST_APP_NAME, + platform.node(), + platform.system(), + "native", + ) + except PairingFailure as exc: + LOGGER.debug(str(exc)) + return self.async_abort( + reason="pairing_failure", + description_placeholders={"error_id": exc.data.get("error_id")}, + ) + return self.async_show_form( + step_id="pair", data_schema=schema, errors=errors + ) + + try: + username, password = await self._hub.pairGrant( + self._pair_state, user_input[CONF_PIN] + ) + except PairingFailure as exc: + LOGGER.debug(str(exc)) + if exc.data.get("error_id") == "INVALID_PIN": + errors[CONF_PIN] = "invalid_pin" + return self.async_show_form( + step_id="pair", data_schema=schema, errors=errors + ) + + return self.async_abort( + reason="pairing_failure", + description_placeholders={"error_id": exc.data.get("error_id")}, + ) + + self._current[CONF_USERNAME] = username + self._current[CONF_PASSWORD] = password + return await self._async_create_current() + async def async_step_user(self, user_input: Optional[FlowUserDict] = None): """Handle the initial step.""" errors = {} if user_input: - self._default = user_input + self._current = user_input try: - system = await validate_input(self.hass, user_input) - except ConnectionFailure: + hub = await validate_input( + self.hass, user_input[CONF_HOST], user_input[CONF_API_VERSION] + ) + except ConnectionFailure as exc: + LOGGER.error(str(exc)) errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") + LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(system["serialnumber"]) - self._abort_if_unique_id_configured(updates=user_input) - data = {**user_input, "system": system} + await self.async_set_unique_id(hub.system["serialnumber"]) + self._abort_if_unique_id_configured() - return self.async_create_entry( - title=f"{system['name']} ({system['serialnumber']})", data=data - ) + self._current[CONF_SYSTEM] = hub.system + self._current[CONF_API_VERSION] = hub.api_version + self._hub = hub + + if hub.pairing_type == "digest_auth_pairing": + return await self.async_step_pair() + return await self._async_create_current() schema = vol.Schema( { - vol.Required(CONF_HOST, default=self._default.get(CONF_HOST)): str, + vol.Required(CONF_HOST, default=self._current.get(CONF_HOST)): str, vol.Required( - CONF_API_VERSION, default=self._default.get(CONF_API_VERSION) - ): vol.In([1, 6]), + CONF_API_VERSION, default=self._current.get(CONF_API_VERSION, 1) + ): vol.In([1, 5, 6]), } ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) diff --git a/homeassistant/components/philips_js/const.py b/homeassistant/components/philips_js/const.py index 893766b0083632..5769a8979ced25 100644 --- a/homeassistant/components/philips_js/const.py +++ b/homeassistant/components/philips_js/const.py @@ -2,3 +2,6 @@ DOMAIN = "philips_js" CONF_SYSTEM = "system" + +CONST_APP_ID = "homeassistant.io" +CONST_APP_NAME = "Home Assistant" diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index e41aa348732827..e1e1fa69b6ba1b 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==0.1.0" + "ha-philipsjs==2.3.0" ], "codeowners": [ "@elupus" diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 20ef6ed9c0ffe2..2b2714b20ce9d0 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,6 +1,7 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" -from typing import Any, Dict +from typing import Any, Dict, Optional +from haphilipsjs import ConnectionFailure import voluptuous as vol from homeassistant import config_entries @@ -11,15 +12,21 @@ MediaPlayerEntity, ) from homeassistant.components.media_player.const import ( + MEDIA_CLASS_APP, MEDIA_CLASS_CHANNEL, MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_APP, + MEDIA_TYPE_APPS, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_CHANNELS, SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, @@ -27,7 +34,6 @@ SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError -from homeassistant.components.philips_js import PhilipsTVDataUpdateCoordinator from homeassistant.const import ( CONF_API_VERSION, CONF_HOST, @@ -40,7 +46,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import LOGGER as _LOGGER +from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator from .const import CONF_SYSTEM, DOMAIN SUPPORT_PHILIPS_JS = ( @@ -53,16 +59,15 @@ | SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_BROWSE_MEDIA + | SUPPORT_PLAY + | SUPPORT_PAUSE + | SUPPORT_STOP ) CONF_ON_ACTION = "turn_on_action" DEFAULT_API_VERSION = 1 -PREFIX_SEPARATOR = ": " -PREFIX_SOURCE = "Input" -PREFIX_CHANNEL = "Channel" - PLATFORM_SCHEMA = vol.All( cv.deprecated(CONF_HOST), cv.deprecated(CONF_NAME), @@ -131,12 +136,19 @@ def __init__( self._supports = SUPPORT_PHILIPS_JS self._system = system self._unique_id = unique_id + self._state = STATE_OFF + self._media_content_type: Optional[str] = None + self._media_content_id: Optional[str] = None + self._media_title: Optional[str] = None + self._media_channel: Optional[str] = None + super().__init__(coordinator) self._update_from_coordinator() - def _update_soon(self): + async def _async_update_soon(self): """Reschedule update task.""" - self.hass.add_job(self.coordinator.async_request_refresh) + self.async_write_ha_state() + await self.coordinator.async_request_refresh() @property def name(self): @@ -147,7 +159,9 @@ def name(self): def supported_features(self): """Flag media player features that are supported.""" supports = self._supports - if self._coordinator.turn_on: + if self._coordinator.turn_on or ( + self._tv.on and self._tv.powerstate is not None + ): supports |= SUPPORT_TURN_ON return supports @@ -155,7 +169,8 @@ def supported_features(self): def state(self): """Get the device state. An exception means OFF state.""" if self._tv.on: - return STATE_ON + if self._tv.powerstate == "On" or self._tv.powerstate is None: + return STATE_ON return STATE_OFF @property @@ -168,22 +183,12 @@ def source_list(self): """List of available input sources.""" return list(self._sources.values()) - def select_source(self, source): + async def async_select_source(self, source): """Set the input source.""" - data = source.split(PREFIX_SEPARATOR, 1) - if data[0] == PREFIX_SOURCE: # Legacy way to set source - source_id = _inverted(self._sources).get(data[1]) - if source_id: - self._tv.setSource(source_id) - elif data[0] == PREFIX_CHANNEL: # Legacy way to set channel - channel_id = _inverted(self._channels).get(data[1]) - if channel_id: - self._tv.setChannel(channel_id) - else: - source_id = _inverted(self._sources).get(source) - if source_id: - self._tv.setSource(source_id) - self._update_soon() + source_id = _inverted(self._sources).get(source) + if source_id: + await self._tv.setSource(source_id) + await self._async_update_soon() @property def volume_level(self): @@ -197,78 +202,118 @@ def is_volume_muted(self): async def async_turn_on(self): """Turn on the device.""" - await self._coordinator.turn_on.async_run(self.hass, self._context) + if self._tv.on and self._tv.powerstate: + await self._tv.setPowerState("On") + self._state = STATE_ON + else: + await self._coordinator.turn_on.async_run(self.hass, self._context) + await self._async_update_soon() - def turn_off(self): + async def async_turn_off(self): """Turn off the device.""" - self._tv.sendKey("Standby") - self._tv.on = False - self._update_soon() + await self._tv.sendKey("Standby") + self._state = STATE_OFF + await self._async_update_soon() - def volume_up(self): + async def async_volume_up(self): """Send volume up command.""" - self._tv.sendKey("VolumeUp") - self._update_soon() + await self._tv.sendKey("VolumeUp") + await self._async_update_soon() - def volume_down(self): + async def async_volume_down(self): """Send volume down command.""" - self._tv.sendKey("VolumeDown") - self._update_soon() + await self._tv.sendKey("VolumeDown") + await self._async_update_soon() - def mute_volume(self, mute): + async def async_mute_volume(self, mute): """Send mute command.""" - self._tv.setVolume(None, mute) - self._update_soon() + if self._tv.muted != mute: + await self._tv.sendKey("Mute") + await self._async_update_soon() + else: + _LOGGER.debug("Ignoring request when already in expected state") - def set_volume_level(self, volume): + async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" - self._tv.setVolume(volume, self._tv.muted) - self._update_soon() + await self._tv.setVolume(volume, self._tv.muted) + await self._async_update_soon() - def media_previous_track(self): + async def async_media_previous_track(self): """Send rewind command.""" - self._tv.sendKey("Previous") - self._update_soon() + await self._tv.sendKey("Previous") + await self._async_update_soon() - def media_next_track(self): + async def async_media_next_track(self): """Send fast forward command.""" - self._tv.sendKey("Next") - self._update_soon() + await self._tv.sendKey("Next") + await self._async_update_soon() + + async def async_media_play_pause(self): + """Send pause command to media player.""" + if self._tv.quirk_playpause_spacebar: + await self._tv.sendUnicode(" ") + else: + await self._tv.sendKey("PlayPause") + await self._async_update_soon() + + async def async_media_play(self): + """Send pause command to media player.""" + await self._tv.sendKey("Play") + await self._async_update_soon() + + async def async_media_pause(self): + """Send play command to media player.""" + await self._tv.sendKey("Pause") + await self._async_update_soon() + + async def async_media_stop(self): + """Send play command to media player.""" + await self._tv.sendKey("Stop") + await self._async_update_soon() @property def media_channel(self): """Get current channel if it's a channel.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) - return None + return self._media_channel @property def media_title(self): """Title of current playing media.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) - return self._sources.get(self._tv.source_id) + return self._media_title @property def media_content_type(self): """Return content type of playing media.""" - if self._tv.source_id == "tv" or self._tv.source_id == "11": - return MEDIA_TYPE_CHANNEL - if self._tv.source_id is None and self._tv.channels: - return MEDIA_TYPE_CHANNEL - return None + return self._media_content_type @property def media_content_id(self): """Content type of current playing media.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) + return self._media_content_id + + @property + def media_image_url(self): + """Image url of current playing media.""" + if self._media_content_id and self._media_content_type in ( + MEDIA_CLASS_APP, + MEDIA_CLASS_CHANNEL, + ): + return self.get_browse_image_url( + self._media_content_type, self._media_content_id, media_image_id=None + ) return None @property - def device_state_attributes(self): - """Return the state attributes.""" - return {"channel_list": list(self._channels.values())} + def app_id(self): + """ID of the current running app.""" + return self._tv.application_id + + @property + def app_name(self): + """Name of the current running app.""" + app = self._tv.applications.get(self._tv.application_id) + if app: + return app.get("label") @property def device_class(self): @@ -293,57 +338,243 @@ def device_info(self): "sw_version": self._system.get("softwareversion"), } - def play_media(self, media_type, media_id, **kwargs): + async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) if media_type == MEDIA_TYPE_CHANNEL: - channel_id = _inverted(self._channels).get(media_id) + list_id, _, channel_id = media_id.partition("/") if channel_id: - self._tv.setChannel(channel_id) - self._update_soon() + await self._tv.setChannel(channel_id, list_id) + await self._async_update_soon() else: _LOGGER.error("Unable to find channel <%s>", media_id) + elif media_type == MEDIA_TYPE_APP: + app = self._tv.applications.get(media_id) + if app: + await self._tv.setApplication(app["intent"]) + await self._async_update_soon() + else: + _LOGGER.error("Unable to find application <%s>", media_id) else: _LOGGER.error("Unsupported media type <%s>", media_type) - async def async_browse_media(self, media_content_type=None, media_content_id=None): - """Implement the websocket media browsing helper.""" - if media_content_id not in (None, ""): - raise BrowseError( - f"Media not found: {media_content_type} / {media_content_id}" - ) + async def async_browse_media_channels(self, expanded): + """Return channel media objects.""" + if expanded: + children = [ + BrowseMedia( + title=channel.get("name", f"Channel: {channel_id}"), + media_class=MEDIA_CLASS_CHANNEL, + media_content_id=f"alltv/{channel_id}", + media_content_type=MEDIA_TYPE_CHANNEL, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, channel_id, media_image_id=None + ), + ) + for channel_id, channel in self._tv.channels.items() + ] + else: + children = None return BrowseMedia( title="Channels", media_class=MEDIA_CLASS_DIRECTORY, - media_content_id="", + media_content_id="channels", media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, can_play=False, can_expand=True, - children=[ + children=children, + ) + + async def async_browse_media_favorites(self, list_id, expanded): + """Return channel media objects.""" + if expanded: + favorites = await self._tv.getFavoriteList(list_id) + if favorites: + + def get_name(channel): + channel_data = self._tv.channels.get(str(channel["ccid"])) + if channel_data: + return channel_data["name"] + return f"Channel: {channel['ccid']}" + + children = [ + BrowseMedia( + title=get_name(channel), + media_class=MEDIA_CLASS_CHANNEL, + media_content_id=f"{list_id}/{channel['ccid']}", + media_content_type=MEDIA_TYPE_CHANNEL, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, channel, media_image_id=None + ), + ) + for channel in favorites + ] + else: + children = None + else: + children = None + + favorite = self._tv.favorite_lists[list_id] + return BrowseMedia( + title=favorite.get("name", f"Favorites {list_id}"), + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id=f"favorites/{list_id}", + media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_applications(self, expanded): + """Return application media objects.""" + if expanded: + children = [ BrowseMedia( - title=channel, - media_class=MEDIA_CLASS_CHANNEL, - media_content_id=channel, - media_content_type=MEDIA_TYPE_CHANNEL, + title=application["label"], + media_class=MEDIA_CLASS_APP, + media_content_id=application_id, + media_content_type=MEDIA_TYPE_APP, can_play=True, can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, application_id, media_image_id=None + ), ) - for channel in self._channels.values() + for application_id, application in self._tv.applications.items() + ] + else: + children = None + + return BrowseMedia( + title="Applications", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="applications", + media_content_type=MEDIA_TYPE_APPS, + children_media_class=MEDIA_TYPE_APP, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_favorite_lists(self, expanded): + """Return favorite media objects.""" + if self._tv.favorite_lists and expanded: + children = [ + await self.async_browse_media_favorites(list_id, False) + for list_id in self._tv.favorite_lists + ] + else: + children = None + + return BrowseMedia( + title="Favorites", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="favorite_lists", + media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_root(self): + """Return root media objects.""" + + return BrowseMedia( + title="Library", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="", + media_content_type="", + can_play=False, + can_expand=True, + children=[ + await self.async_browse_media_channels(False), + await self.async_browse_media_applications(False), + await self.async_browse_media_favorite_lists(False), ], ) + async def async_browse_media(self, media_content_type=None, media_content_id=None): + """Implement the websocket media browsing helper.""" + if not self._tv.on: + raise BrowseError("Can't browse when tv is turned off") + + if media_content_id in (None, ""): + return await self.async_browse_media_root() + path = media_content_id.partition("/") + if path[0] == "channels": + return await self.async_browse_media_channels(True) + if path[0] == "applications": + return await self.async_browse_media_applications(True) + if path[0] == "favorite_lists": + return await self.async_browse_media_favorite_lists(True) + if path[0] == "favorites": + return await self.async_browse_media_favorites(path[2], True) + + raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}") + + async def async_get_browse_image( + self, media_content_type, media_content_id, media_image_id=None + ): + """Serve album art. Returns (content, content_type).""" + try: + if media_content_type == MEDIA_TYPE_APP and media_content_id: + return await self._tv.getApplicationIcon(media_content_id) + if media_content_type == MEDIA_TYPE_CHANNEL and media_content_id: + return await self._tv.getChannelLogo(media_content_id) + except ConnectionFailure: + _LOGGER.warning("Failed to fetch image") + return None, None + + async def async_get_media_image(self): + """Serve album art. Returns (content, content_type).""" + return await self.async_get_browse_image( + self.media_content_type, self.media_content_id, None + ) + + @callback def _update_from_coordinator(self): + + if self._tv.on: + if self._tv.powerstate in ("Standby", "StandbyKeep"): + self._state = STATE_OFF + else: + self._state = STATE_ON + else: + self._state = STATE_OFF + self._sources = { srcid: source.get("name") or f"Source {srcid}" for srcid, source in (self._tv.sources or {}).items() } - self._channels = { - chid: channel.get("name") or f"Channel {chid}" - for chid, channel in (self._tv.channels or {}).items() - } + if self._tv.channel_active: + self._media_content_type = MEDIA_TYPE_CHANNEL + self._media_content_id = f"all/{self._tv.channel_id}" + self._media_title = self._tv.channels.get(self._tv.channel_id, {}).get( + "name" + ) + self._media_channel = self._media_title + elif self._tv.application_id: + self._media_content_type = MEDIA_TYPE_APP + self._media_content_id = self._tv.application_id + self._media_title = self._tv.applications.get( + self._tv.application_id, {} + ).get("label") + self._media_channel = None + else: + self._media_content_type = None + self._media_content_id = None + self._media_title = self._sources.get(self._tv.source_id) + self._media_channel = None @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/philips_js/strings.json b/homeassistant/components/philips_js/strings.json index 2267315501f771..df65d453f2b0cf 100644 --- a/homeassistant/components/philips_js/strings.json +++ b/homeassistant/components/philips_js/strings.json @@ -10,8 +10,10 @@ }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, + "unknown": "[%key:common::config_flow::error::unknown%]", + "pairing_failure": "Unable to pair: {error_id}", + "invalid_pin": "Invalid PIN" +}, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index 249fe5a892dbc8..b2022a01824de1 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -5,7 +5,9 @@ }, "error": { "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" + "unknown": "Unexpected error", + "pairing_failure": "Unable to pair: {error_id}", + "invalid_pin": "Invalid PIN" }, "step": { "user": { diff --git a/requirements_all.txt b/requirements_all.txt index 2353797b8b8a69..482e0aeaa871ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -721,7 +721,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.1.0 +ha-philipsjs==2.3.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b6cfca171e21a0..e394c54ebbf113 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -382,7 +382,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.1.0 +ha-philipsjs==2.3.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/tests/components/philips_js/__init__.py b/tests/components/philips_js/__init__.py index 1c96a6d4e55d62..9dea390a600615 100644 --- a/tests/components/philips_js/__init__.py +++ b/tests/components/philips_js/__init__.py @@ -3,6 +3,9 @@ MOCK_SERIAL_NO = "1234567890" MOCK_NAME = "Philips TV" +MOCK_USERNAME = "mock_user" +MOCK_PASSWORD = "mock_password" + MOCK_SYSTEM = { "menulanguage": "English", "name": MOCK_NAME, @@ -12,14 +15,63 @@ "model": "modelname", } +MOCK_SYSTEM_UNPAIRED = { + "menulanguage": "Dutch", + "name": "55PUS7181/12", + "country": "Netherlands", + "serialnumber": "ABCDEFGHIJKLF", + "softwareversion": "TPM191E_R.101.001.208.001", + "model": "65OLED855/12", + "deviceid": "1234567890", + "nettvversion": "6.0.2", + "epgsource": "one", + "api_version": {"Major": 6, "Minor": 2, "Patch": 0}, + "featuring": { + "jsonfeatures": { + "editfavorites": ["TVChannels", "SatChannels"], + "recordings": ["List", "Schedule", "Manage"], + "ambilight": ["LoungeLight", "Hue", "Ambilight"], + "menuitems": ["Setup_Menu"], + "textentry": [ + "context_based", + "initial_string_available", + "editor_info_available", + ], + "applications": ["TV_Apps", "TV_Games", "TV_Settings"], + "pointer": ["not_available"], + "inputkey": ["key"], + "activities": ["intent"], + "channels": ["preset_string"], + "mappings": ["server_mapping"], + }, + "systemfeatures": { + "tvtype": "consumer", + "content": ["dmr", "dms_tad"], + "tvsearch": "intent", + "pairing_type": "digest_auth_pairing", + "secured_transport": "True", + }, + }, +} + MOCK_USERINPUT = { "host": "1.1.1.1", - "api_version": 1, } +MOCK_IMPORT = {"host": "1.1.1.1", "api_version": 6} + MOCK_CONFIG = { - **MOCK_USERINPUT, + "host": "1.1.1.1", + "api_version": 1, "system": MOCK_SYSTEM, } +MOCK_CONFIG_PAIRED = { + "host": "1.1.1.1", + "api_version": 6, + "username": MOCK_USERNAME, + "password": MOCK_PASSWORD, + "system": MOCK_SYSTEM_UNPAIRED, +} + MOCK_ENTITY_ID = "media_player.philips_tv" diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py index 549ad77fb069f0..4b6150f9f8103c 100644 --- a/tests/components/philips_js/conftest.py +++ b/tests/components/philips_js/conftest.py @@ -1,6 +1,7 @@ """Standard setup for tests.""" -from unittest.mock import Mock, patch +from unittest.mock import create_autospec, patch +from haphilipsjs import PhilipsTV from pytest import fixture from homeassistant import setup @@ -20,10 +21,18 @@ async def setup_notification(hass): @fixture(autouse=True) def mock_tv(): """Disable component actual use.""" - tv = Mock(autospec="philips_js.PhilipsTV") + tv = create_autospec(PhilipsTV) tv.sources = {} tv.channels = {} + tv.application = None + tv.applications = {} tv.system = MOCK_SYSTEM + tv.api_version = 1 + tv.api_version_detected = None + tv.on = True + tv.notify_change_supported = False + tv.pairing_type = None + tv.powerstate = None with patch( "homeassistant.components.philips_js.config_flow.PhilipsTV", return_value=tv diff --git a/tests/components/philips_js/test_config_flow.py b/tests/components/philips_js/test_config_flow.py index 75caff788914d2..45e896319f1552 100644 --- a/tests/components/philips_js/test_config_flow.py +++ b/tests/components/philips_js/test_config_flow.py @@ -1,12 +1,21 @@ """Test the Philips TV config flow.""" -from unittest.mock import patch +from unittest.mock import ANY, patch +from haphilipsjs import PairingFailure from pytest import fixture from homeassistant import config_entries from homeassistant.components.philips_js.const import DOMAIN -from . import MOCK_CONFIG, MOCK_USERINPUT +from . import ( + MOCK_CONFIG, + MOCK_CONFIG_PAIRED, + MOCK_IMPORT, + MOCK_PASSWORD, + MOCK_SYSTEM_UNPAIRED, + MOCK_USERINPUT, + MOCK_USERNAME, +) @fixture(autouse=True) @@ -27,12 +36,26 @@ def mock_setup_entry(): yield mock_setup_entry +@fixture +async def mock_tv_pairable(mock_tv): + """Return a mock tv that is pariable.""" + mock_tv.system = MOCK_SYSTEM_UNPAIRED + mock_tv.pairing_type = "digest_auth_pairing" + mock_tv.api_version = 6 + mock_tv.api_version_detected = 6 + mock_tv.secured_transport = True + + mock_tv.pairRequest.return_value = {} + mock_tv.pairGrant.return_value = MOCK_USERNAME, MOCK_PASSWORD + return mock_tv + + async def test_import(hass, mock_setup, mock_setup_entry): """Test we get an item on import.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_USERINPUT, + data=MOCK_IMPORT, ) assert result["type"] == "create_entry" @@ -47,7 +70,7 @@ async def test_import_exist(hass, mock_config_entry): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_USERINPUT, + data=MOCK_IMPORT, ) assert result["type"] == "abort" @@ -103,3 +126,116 @@ async def test_form_unexpected_error(hass, mock_tv): assert result["type"] == "form" assert result["errors"] == {"base": "unknown"} + + +async def test_pairing(hass, mock_tv_pairable, mock_setup, mock_setup_entry): + """Test we get the form.""" + mock_tv = mock_tv_pairable + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + + assert result["type"] == "form" + assert result["errors"] == {} + + mock_tv.setTransport.assert_called_with(True) + mock_tv.pairRequest.assert_called() + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result == { + "flow_id": ANY, + "type": "create_entry", + "description": None, + "description_placeholders": None, + "handler": "philips_js", + "result": ANY, + "title": "55PUS7181/12 (ABCDEFGHIJKLF)", + "data": MOCK_CONFIG_PAIRED, + "version": 1, + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_pair_request_failed( + hass, mock_tv_pairable, mock_setup, mock_setup_entry +): + """Test we get the form.""" + mock_tv = mock_tv_pairable + mock_tv.pairRequest.side_effect = PairingFailure({}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + + assert result == { + "flow_id": ANY, + "description_placeholders": {"error_id": None}, + "handler": "philips_js", + "reason": "pairing_failure", + "type": "abort", + } + + +async def test_pair_grant_failed(hass, mock_tv_pairable, mock_setup, mock_setup_entry): + """Test we get the form.""" + mock_tv = mock_tv_pairable + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_tv.setTransport.assert_called_with(True) + mock_tv.pairRequest.assert_called() + + # Test with invalid pin + mock_tv.pairGrant.side_effect = PairingFailure({"error_id": "INVALID_PIN"}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result["type"] == "form" + assert result["errors"] == {"pin": "invalid_pin"} + + # Test with unexpected failure + mock_tv.pairGrant.side_effect = PairingFailure({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result == { + "flow_id": ANY, + "description_placeholders": {"error_id": None}, + "handler": "philips_js", + "reason": "pairing_failure", + "type": "abort", + } diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py index 43c7c424cf9e8d..ebda40f13e58fb 100644 --- a/tests/components/philips_js/test_device_trigger.py +++ b/tests/components/philips_js/test_device_trigger.py @@ -33,9 +33,13 @@ async def test_get_triggers(hass, mock_device): assert_lists_same(triggers, expected_triggers) -async def test_if_fires_on_turn_on_request(hass, calls, mock_entity, mock_device): +async def test_if_fires_on_turn_on_request( + hass, calls, mock_tv, mock_entity, mock_device +): """Test for turn_on and turn_off triggers firing.""" + mock_tv.on = False + assert await async_setup_component( hass, automation.DOMAIN, From 8971ab2edccb604c2ef46b53280c5db0245e373e Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 26 Feb 2021 20:07:53 +0100 Subject: [PATCH 0818/1818] Bump aioshelly to 0.6.1 (#47088) --- 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 c38869b3e0df13..a757947c5cfbb6 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==0.6.0"], + "requirements": ["aioshelly==0.6.1"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/requirements_all.txt b/requirements_all.txt index 482e0aeaa871ae..ea49c7b232eeb2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,7 +221,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.6.0 +aioshelly==0.6.1 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e394c54ebbf113..bf56c1699d4a8b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -140,7 +140,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.6.0 +aioshelly==0.6.1 # homeassistant.components.switcher_kis aioswitcher==1.2.1 From 7ca148f65ddd27f13a4b4dcdac907fd9e9479721 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 26 Feb 2021 20:19:23 +0100 Subject: [PATCH 0819/1818] Fix Z-Wave JS discovery schema for thermostat devices (#47087) Co-authored-by: Martin Hjelmare --- .../components/zwave_js/discovery.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index a40eb10de8b792..f5f3d9e5c5bd17 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -75,6 +75,8 @@ class ZWaveDiscoverySchema: device_class_specific: Optional[Set[Union[str, int]]] = None # [optional] additional values that ALL need to be present on the node for this scheme to pass required_values: Optional[List[ZWaveValueDiscoverySchema]] = None + # [optional] additional values that MAY NOT be present on the node for this scheme to pass + absent_values: Optional[List[ZWaveValueDiscoverySchema]] = None # [optional] bool to specify if this primary value may be discovered by multiple platforms allow_multi: bool = False @@ -186,36 +188,30 @@ class ZWaveDiscoverySchema: ), ), # climate + # thermostats supporting mode (and optional setpoint) ZWaveDiscoverySchema( platform="climate", - device_class_generic={"Thermostat"}, - device_class_specific={ - "Setback Thermostat", - "Thermostat General", - "Thermostat General V2", - "General Thermostat", - "General Thermostat V2", - }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_MODE}, property={"mode"}, type={"number"}, ), ), - # climate - # setpoint thermostats + # thermostats supporting setpoint only (and thus not mode) ZWaveDiscoverySchema( platform="climate", - device_class_generic={"Thermostat"}, - device_class_specific={ - "Setpoint Thermostat", - "Unused", - }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_SETPOINT}, property={"setpoint"}, type={"number"}, ), + absent_values=[ # mode must not be present to prevent dupes + ZWaveValueDiscoverySchema( + command_class={CommandClass.THERMOSTAT_MODE}, + property={"mode"}, + type={"number"}, + ), + ], ), # binary sensors ZWaveDiscoverySchema( @@ -436,6 +432,13 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None for val_scheme in schema.required_values ): continue + # check for values that may not be present + if schema.absent_values is not None: + if any( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.absent_values + ): + continue # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( node=value.node, From b1898cc1767ef20bb2e17bd931e8ec52e57fa1fc Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 26 Feb 2021 11:20:32 -0800 Subject: [PATCH 0820/1818] Bump google-nest-sdm to v0.2.12 to improve API call error messages (#47108) --- 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 c68dbe6ee2f42e..734261d9b08611 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.10"], + "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.12"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [{"macaddress":"18B430*"}] diff --git a/requirements_all.txt b/requirements_all.txt index ea49c7b232eeb2..a2f3a6e45c8716 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -682,7 +682,7 @@ google-cloud-pubsub==2.1.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==0.2.10 +google-nest-sdm==0.2.12 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bf56c1699d4a8b..e5b4a3d04b1d82 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.1.0 # homeassistant.components.nest -google-nest-sdm==0.2.10 +google-nest-sdm==0.2.12 # homeassistant.components.gree greeclimate==0.10.3 From 101897c260d6d36399eac13ec6824b487c56bc6b Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 26 Feb 2021 18:34:40 +0100 Subject: [PATCH 0821/1818] Add support for v6 features to philips js integration (#46422) --- .../components/philips_js/__init__.py | 54 ++- .../components/philips_js/config_flow.py | 137 ++++-- homeassistant/components/philips_js/const.py | 3 + .../components/philips_js/manifest.json | 2 +- .../components/philips_js/media_player.py | 403 ++++++++++++++---- .../components/philips_js/strings.json | 6 +- .../philips_js/translations/en.json | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/philips_js/__init__.py | 56 ++- tests/components/philips_js/conftest.py | 13 +- .../components/philips_js/test_config_flow.py | 144 ++++++- .../philips_js/test_device_trigger.py | 6 +- 13 files changed, 698 insertions(+), 134 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 11e84b6cd82990..f3c2eb59789412 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -8,8 +8,13 @@ from homeassistant.components.automation import AutomationActionType from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_VERSION, CONF_HOST -from homeassistant.core import Context, HassJob, HomeAssistant, callback +from homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, +) +from homeassistant.core import CALLBACK_TYPE, Context, HassJob, HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -30,7 +35,12 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Philips TV from a config entry.""" - tvapi = PhilipsTV(entry.data[CONF_HOST], entry.data[CONF_API_VERSION]) + tvapi = PhilipsTV( + entry.data[CONF_HOST], + entry.data[CONF_API_VERSION], + username=entry.data.get(CONF_USERNAME), + password=entry.data.get(CONF_PASSWORD), + ) coordinator = PhilipsTVDataUpdateCoordinator(hass, tvapi) @@ -103,7 +113,9 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): def __init__(self, hass, api: PhilipsTV) -> None: """Set up the coordinator.""" self.api = api + self._notify_future: Optional[asyncio.Task] = None + @callback def _update_listeners(): for update_callback in self._listeners: update_callback() @@ -120,9 +132,43 @@ def _update_listeners(): ), ) + async def _notify_task(self): + while self.api.on and self.api.notify_change_supported: + if await self.api.notifyChange(130): + self.async_set_updated_data(None) + + @callback + def _async_notify_stop(self): + if self._notify_future: + self._notify_future.cancel() + self._notify_future = None + + @callback + def _async_notify_schedule(self): + if ( + (self._notify_future is None or self._notify_future.done()) + and self.api.on + and self.api.notify_change_supported + ): + self._notify_future = self.hass.loop.create_task(self._notify_task()) + + @callback + def async_remove_listener(self, update_callback: CALLBACK_TYPE) -> None: + """Remove data update.""" + super().async_remove_listener(update_callback) + if not self._listeners: + self._async_notify_stop() + + @callback + def _async_stop_refresh(self, event: asyncio.Event) -> None: + super()._async_stop_refresh(event) + self._async_notify_stop() + + @callback async def _async_update_data(self): """Fetch the latest data from the source.""" try: - await self.hass.async_add_executor_job(self.api.update) + await self.api.update() + self._async_notify_schedule() except ConnectionFailure: pass diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 523918daa7c6e3..778bcba282bbbf 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,35 +1,47 @@ """Config flow for Philips TV integration.""" -import logging -from typing import Any, Dict, Optional, TypedDict +import platform +from typing import Any, Dict, Optional, Tuple, TypedDict -from haphilipsjs import ConnectionFailure, PhilipsTV +from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import CONF_API_VERSION, CONF_HOST - -from .const import DOMAIN # pylint:disable=unused-import - -_LOGGER = logging.getLogger(__name__) +from homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_PIN, + CONF_USERNAME, +) + +from . import LOGGER +from .const import ( # pylint:disable=unused-import + CONF_SYSTEM, + CONST_APP_ID, + CONST_APP_NAME, + DOMAIN, +) class FlowUserDict(TypedDict): """Data for user step.""" host: str - api_version: int -async def validate_input(hass: core.HomeAssistant, data: FlowUserDict): +async def validate_input( + hass: core.HomeAssistant, host: str, api_version: int +) -> Tuple[Dict, PhilipsTV]: """Validate the user input allows us to connect.""" - hub = PhilipsTV(data[CONF_HOST], data[CONF_API_VERSION]) + hub = PhilipsTV(host, api_version) - await hass.async_add_executor_job(hub.getSystem) + await hub.getSystem() + await hub.setTransport(hub.secured_transport) - if hub.system is None: - raise ConnectionFailure + if not hub.system: + raise ConnectionFailure("System data is empty") - return hub.system + return hub class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -38,7 +50,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - _default = {} + _current = {} + _hub: PhilipsTV + _pair_state: Any async def async_step_import(self, conf: Dict[str, Any]): """Import a configuration from config.yaml.""" @@ -53,34 +67,99 @@ async def async_step_import(self, conf: Dict[str, Any]): } ) + async def _async_create_current(self): + + system = self._current[CONF_SYSTEM] + return self.async_create_entry( + title=f"{system['name']} ({system['serialnumber']})", + data=self._current, + ) + + async def async_step_pair(self, user_input: Optional[Dict] = None): + """Attempt to pair with device.""" + assert self._hub + + errors = {} + schema = vol.Schema( + { + vol.Required(CONF_PIN): str, + } + ) + + if not user_input: + try: + self._pair_state = await self._hub.pairRequest( + CONST_APP_ID, + CONST_APP_NAME, + platform.node(), + platform.system(), + "native", + ) + except PairingFailure as exc: + LOGGER.debug(str(exc)) + return self.async_abort( + reason="pairing_failure", + description_placeholders={"error_id": exc.data.get("error_id")}, + ) + return self.async_show_form( + step_id="pair", data_schema=schema, errors=errors + ) + + try: + username, password = await self._hub.pairGrant( + self._pair_state, user_input[CONF_PIN] + ) + except PairingFailure as exc: + LOGGER.debug(str(exc)) + if exc.data.get("error_id") == "INVALID_PIN": + errors[CONF_PIN] = "invalid_pin" + return self.async_show_form( + step_id="pair", data_schema=schema, errors=errors + ) + + return self.async_abort( + reason="pairing_failure", + description_placeholders={"error_id": exc.data.get("error_id")}, + ) + + self._current[CONF_USERNAME] = username + self._current[CONF_PASSWORD] = password + return await self._async_create_current() + async def async_step_user(self, user_input: Optional[FlowUserDict] = None): """Handle the initial step.""" errors = {} if user_input: - self._default = user_input + self._current = user_input try: - system = await validate_input(self.hass, user_input) - except ConnectionFailure: + hub = await validate_input( + self.hass, user_input[CONF_HOST], user_input[CONF_API_VERSION] + ) + except ConnectionFailure as exc: + LOGGER.error(str(exc)) errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") + LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(system["serialnumber"]) - self._abort_if_unique_id_configured(updates=user_input) - data = {**user_input, "system": system} + await self.async_set_unique_id(hub.system["serialnumber"]) + self._abort_if_unique_id_configured() - return self.async_create_entry( - title=f"{system['name']} ({system['serialnumber']})", data=data - ) + self._current[CONF_SYSTEM] = hub.system + self._current[CONF_API_VERSION] = hub.api_version + self._hub = hub + + if hub.pairing_type == "digest_auth_pairing": + return await self.async_step_pair() + return await self._async_create_current() schema = vol.Schema( { - vol.Required(CONF_HOST, default=self._default.get(CONF_HOST)): str, + vol.Required(CONF_HOST, default=self._current.get(CONF_HOST)): str, vol.Required( - CONF_API_VERSION, default=self._default.get(CONF_API_VERSION) - ): vol.In([1, 6]), + CONF_API_VERSION, default=self._current.get(CONF_API_VERSION, 1) + ): vol.In([1, 5, 6]), } ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) diff --git a/homeassistant/components/philips_js/const.py b/homeassistant/components/philips_js/const.py index 893766b0083632..5769a8979ced25 100644 --- a/homeassistant/components/philips_js/const.py +++ b/homeassistant/components/philips_js/const.py @@ -2,3 +2,6 @@ DOMAIN = "philips_js" CONF_SYSTEM = "system" + +CONST_APP_ID = "homeassistant.io" +CONST_APP_NAME = "Home Assistant" diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index e41aa348732827..e1e1fa69b6ba1b 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==0.1.0" + "ha-philipsjs==2.3.0" ], "codeowners": [ "@elupus" diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 20ef6ed9c0ffe2..2b2714b20ce9d0 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,6 +1,7 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" -from typing import Any, Dict +from typing import Any, Dict, Optional +from haphilipsjs import ConnectionFailure import voluptuous as vol from homeassistant import config_entries @@ -11,15 +12,21 @@ MediaPlayerEntity, ) from homeassistant.components.media_player.const import ( + MEDIA_CLASS_APP, MEDIA_CLASS_CHANNEL, MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_APP, + MEDIA_TYPE_APPS, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_CHANNELS, SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, + SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, @@ -27,7 +34,6 @@ SUPPORT_VOLUME_STEP, ) from homeassistant.components.media_player.errors import BrowseError -from homeassistant.components.philips_js import PhilipsTVDataUpdateCoordinator from homeassistant.const import ( CONF_API_VERSION, CONF_HOST, @@ -40,7 +46,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import LOGGER as _LOGGER +from . import LOGGER as _LOGGER, PhilipsTVDataUpdateCoordinator from .const import CONF_SYSTEM, DOMAIN SUPPORT_PHILIPS_JS = ( @@ -53,16 +59,15 @@ | SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_BROWSE_MEDIA + | SUPPORT_PLAY + | SUPPORT_PAUSE + | SUPPORT_STOP ) CONF_ON_ACTION = "turn_on_action" DEFAULT_API_VERSION = 1 -PREFIX_SEPARATOR = ": " -PREFIX_SOURCE = "Input" -PREFIX_CHANNEL = "Channel" - PLATFORM_SCHEMA = vol.All( cv.deprecated(CONF_HOST), cv.deprecated(CONF_NAME), @@ -131,12 +136,19 @@ def __init__( self._supports = SUPPORT_PHILIPS_JS self._system = system self._unique_id = unique_id + self._state = STATE_OFF + self._media_content_type: Optional[str] = None + self._media_content_id: Optional[str] = None + self._media_title: Optional[str] = None + self._media_channel: Optional[str] = None + super().__init__(coordinator) self._update_from_coordinator() - def _update_soon(self): + async def _async_update_soon(self): """Reschedule update task.""" - self.hass.add_job(self.coordinator.async_request_refresh) + self.async_write_ha_state() + await self.coordinator.async_request_refresh() @property def name(self): @@ -147,7 +159,9 @@ def name(self): def supported_features(self): """Flag media player features that are supported.""" supports = self._supports - if self._coordinator.turn_on: + if self._coordinator.turn_on or ( + self._tv.on and self._tv.powerstate is not None + ): supports |= SUPPORT_TURN_ON return supports @@ -155,7 +169,8 @@ def supported_features(self): def state(self): """Get the device state. An exception means OFF state.""" if self._tv.on: - return STATE_ON + if self._tv.powerstate == "On" or self._tv.powerstate is None: + return STATE_ON return STATE_OFF @property @@ -168,22 +183,12 @@ def source_list(self): """List of available input sources.""" return list(self._sources.values()) - def select_source(self, source): + async def async_select_source(self, source): """Set the input source.""" - data = source.split(PREFIX_SEPARATOR, 1) - if data[0] == PREFIX_SOURCE: # Legacy way to set source - source_id = _inverted(self._sources).get(data[1]) - if source_id: - self._tv.setSource(source_id) - elif data[0] == PREFIX_CHANNEL: # Legacy way to set channel - channel_id = _inverted(self._channels).get(data[1]) - if channel_id: - self._tv.setChannel(channel_id) - else: - source_id = _inverted(self._sources).get(source) - if source_id: - self._tv.setSource(source_id) - self._update_soon() + source_id = _inverted(self._sources).get(source) + if source_id: + await self._tv.setSource(source_id) + await self._async_update_soon() @property def volume_level(self): @@ -197,78 +202,118 @@ def is_volume_muted(self): async def async_turn_on(self): """Turn on the device.""" - await self._coordinator.turn_on.async_run(self.hass, self._context) + if self._tv.on and self._tv.powerstate: + await self._tv.setPowerState("On") + self._state = STATE_ON + else: + await self._coordinator.turn_on.async_run(self.hass, self._context) + await self._async_update_soon() - def turn_off(self): + async def async_turn_off(self): """Turn off the device.""" - self._tv.sendKey("Standby") - self._tv.on = False - self._update_soon() + await self._tv.sendKey("Standby") + self._state = STATE_OFF + await self._async_update_soon() - def volume_up(self): + async def async_volume_up(self): """Send volume up command.""" - self._tv.sendKey("VolumeUp") - self._update_soon() + await self._tv.sendKey("VolumeUp") + await self._async_update_soon() - def volume_down(self): + async def async_volume_down(self): """Send volume down command.""" - self._tv.sendKey("VolumeDown") - self._update_soon() + await self._tv.sendKey("VolumeDown") + await self._async_update_soon() - def mute_volume(self, mute): + async def async_mute_volume(self, mute): """Send mute command.""" - self._tv.setVolume(None, mute) - self._update_soon() + if self._tv.muted != mute: + await self._tv.sendKey("Mute") + await self._async_update_soon() + else: + _LOGGER.debug("Ignoring request when already in expected state") - def set_volume_level(self, volume): + async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" - self._tv.setVolume(volume, self._tv.muted) - self._update_soon() + await self._tv.setVolume(volume, self._tv.muted) + await self._async_update_soon() - def media_previous_track(self): + async def async_media_previous_track(self): """Send rewind command.""" - self._tv.sendKey("Previous") - self._update_soon() + await self._tv.sendKey("Previous") + await self._async_update_soon() - def media_next_track(self): + async def async_media_next_track(self): """Send fast forward command.""" - self._tv.sendKey("Next") - self._update_soon() + await self._tv.sendKey("Next") + await self._async_update_soon() + + async def async_media_play_pause(self): + """Send pause command to media player.""" + if self._tv.quirk_playpause_spacebar: + await self._tv.sendUnicode(" ") + else: + await self._tv.sendKey("PlayPause") + await self._async_update_soon() + + async def async_media_play(self): + """Send pause command to media player.""" + await self._tv.sendKey("Play") + await self._async_update_soon() + + async def async_media_pause(self): + """Send play command to media player.""" + await self._tv.sendKey("Pause") + await self._async_update_soon() + + async def async_media_stop(self): + """Send play command to media player.""" + await self._tv.sendKey("Stop") + await self._async_update_soon() @property def media_channel(self): """Get current channel if it's a channel.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) - return None + return self._media_channel @property def media_title(self): """Title of current playing media.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) - return self._sources.get(self._tv.source_id) + return self._media_title @property def media_content_type(self): """Return content type of playing media.""" - if self._tv.source_id == "tv" or self._tv.source_id == "11": - return MEDIA_TYPE_CHANNEL - if self._tv.source_id is None and self._tv.channels: - return MEDIA_TYPE_CHANNEL - return None + return self._media_content_type @property def media_content_id(self): """Content type of current playing media.""" - if self.media_content_type == MEDIA_TYPE_CHANNEL: - return self._channels.get(self._tv.channel_id) + return self._media_content_id + + @property + def media_image_url(self): + """Image url of current playing media.""" + if self._media_content_id and self._media_content_type in ( + MEDIA_CLASS_APP, + MEDIA_CLASS_CHANNEL, + ): + return self.get_browse_image_url( + self._media_content_type, self._media_content_id, media_image_id=None + ) return None @property - def device_state_attributes(self): - """Return the state attributes.""" - return {"channel_list": list(self._channels.values())} + def app_id(self): + """ID of the current running app.""" + return self._tv.application_id + + @property + def app_name(self): + """Name of the current running app.""" + app = self._tv.applications.get(self._tv.application_id) + if app: + return app.get("label") @property def device_class(self): @@ -293,57 +338,243 @@ def device_info(self): "sw_version": self._system.get("softwareversion"), } - def play_media(self, media_type, media_id, **kwargs): + async def async_play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) if media_type == MEDIA_TYPE_CHANNEL: - channel_id = _inverted(self._channels).get(media_id) + list_id, _, channel_id = media_id.partition("/") if channel_id: - self._tv.setChannel(channel_id) - self._update_soon() + await self._tv.setChannel(channel_id, list_id) + await self._async_update_soon() else: _LOGGER.error("Unable to find channel <%s>", media_id) + elif media_type == MEDIA_TYPE_APP: + app = self._tv.applications.get(media_id) + if app: + await self._tv.setApplication(app["intent"]) + await self._async_update_soon() + else: + _LOGGER.error("Unable to find application <%s>", media_id) else: _LOGGER.error("Unsupported media type <%s>", media_type) - async def async_browse_media(self, media_content_type=None, media_content_id=None): - """Implement the websocket media browsing helper.""" - if media_content_id not in (None, ""): - raise BrowseError( - f"Media not found: {media_content_type} / {media_content_id}" - ) + async def async_browse_media_channels(self, expanded): + """Return channel media objects.""" + if expanded: + children = [ + BrowseMedia( + title=channel.get("name", f"Channel: {channel_id}"), + media_class=MEDIA_CLASS_CHANNEL, + media_content_id=f"alltv/{channel_id}", + media_content_type=MEDIA_TYPE_CHANNEL, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, channel_id, media_image_id=None + ), + ) + for channel_id, channel in self._tv.channels.items() + ] + else: + children = None return BrowseMedia( title="Channels", media_class=MEDIA_CLASS_DIRECTORY, - media_content_id="", + media_content_id="channels", media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, can_play=False, can_expand=True, - children=[ + children=children, + ) + + async def async_browse_media_favorites(self, list_id, expanded): + """Return channel media objects.""" + if expanded: + favorites = await self._tv.getFavoriteList(list_id) + if favorites: + + def get_name(channel): + channel_data = self._tv.channels.get(str(channel["ccid"])) + if channel_data: + return channel_data["name"] + return f"Channel: {channel['ccid']}" + + children = [ + BrowseMedia( + title=get_name(channel), + media_class=MEDIA_CLASS_CHANNEL, + media_content_id=f"{list_id}/{channel['ccid']}", + media_content_type=MEDIA_TYPE_CHANNEL, + can_play=True, + can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, channel, media_image_id=None + ), + ) + for channel in favorites + ] + else: + children = None + else: + children = None + + favorite = self._tv.favorite_lists[list_id] + return BrowseMedia( + title=favorite.get("name", f"Favorites {list_id}"), + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id=f"favorites/{list_id}", + media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_applications(self, expanded): + """Return application media objects.""" + if expanded: + children = [ BrowseMedia( - title=channel, - media_class=MEDIA_CLASS_CHANNEL, - media_content_id=channel, - media_content_type=MEDIA_TYPE_CHANNEL, + title=application["label"], + media_class=MEDIA_CLASS_APP, + media_content_id=application_id, + media_content_type=MEDIA_TYPE_APP, can_play=True, can_expand=False, + thumbnail=self.get_browse_image_url( + MEDIA_TYPE_APP, application_id, media_image_id=None + ), ) - for channel in self._channels.values() + for application_id, application in self._tv.applications.items() + ] + else: + children = None + + return BrowseMedia( + title="Applications", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="applications", + media_content_type=MEDIA_TYPE_APPS, + children_media_class=MEDIA_TYPE_APP, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_favorite_lists(self, expanded): + """Return favorite media objects.""" + if self._tv.favorite_lists and expanded: + children = [ + await self.async_browse_media_favorites(list_id, False) + for list_id in self._tv.favorite_lists + ] + else: + children = None + + return BrowseMedia( + title="Favorites", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="favorite_lists", + media_content_type=MEDIA_TYPE_CHANNELS, + children_media_class=MEDIA_TYPE_CHANNEL, + can_play=False, + can_expand=True, + children=children, + ) + + async def async_browse_media_root(self): + """Return root media objects.""" + + return BrowseMedia( + title="Library", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="", + media_content_type="", + can_play=False, + can_expand=True, + children=[ + await self.async_browse_media_channels(False), + await self.async_browse_media_applications(False), + await self.async_browse_media_favorite_lists(False), ], ) + async def async_browse_media(self, media_content_type=None, media_content_id=None): + """Implement the websocket media browsing helper.""" + if not self._tv.on: + raise BrowseError("Can't browse when tv is turned off") + + if media_content_id in (None, ""): + return await self.async_browse_media_root() + path = media_content_id.partition("/") + if path[0] == "channels": + return await self.async_browse_media_channels(True) + if path[0] == "applications": + return await self.async_browse_media_applications(True) + if path[0] == "favorite_lists": + return await self.async_browse_media_favorite_lists(True) + if path[0] == "favorites": + return await self.async_browse_media_favorites(path[2], True) + + raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}") + + async def async_get_browse_image( + self, media_content_type, media_content_id, media_image_id=None + ): + """Serve album art. Returns (content, content_type).""" + try: + if media_content_type == MEDIA_TYPE_APP and media_content_id: + return await self._tv.getApplicationIcon(media_content_id) + if media_content_type == MEDIA_TYPE_CHANNEL and media_content_id: + return await self._tv.getChannelLogo(media_content_id) + except ConnectionFailure: + _LOGGER.warning("Failed to fetch image") + return None, None + + async def async_get_media_image(self): + """Serve album art. Returns (content, content_type).""" + return await self.async_get_browse_image( + self.media_content_type, self.media_content_id, None + ) + + @callback def _update_from_coordinator(self): + + if self._tv.on: + if self._tv.powerstate in ("Standby", "StandbyKeep"): + self._state = STATE_OFF + else: + self._state = STATE_ON + else: + self._state = STATE_OFF + self._sources = { srcid: source.get("name") or f"Source {srcid}" for srcid, source in (self._tv.sources or {}).items() } - self._channels = { - chid: channel.get("name") or f"Channel {chid}" - for chid, channel in (self._tv.channels or {}).items() - } + if self._tv.channel_active: + self._media_content_type = MEDIA_TYPE_CHANNEL + self._media_content_id = f"all/{self._tv.channel_id}" + self._media_title = self._tv.channels.get(self._tv.channel_id, {}).get( + "name" + ) + self._media_channel = self._media_title + elif self._tv.application_id: + self._media_content_type = MEDIA_TYPE_APP + self._media_content_id = self._tv.application_id + self._media_title = self._tv.applications.get( + self._tv.application_id, {} + ).get("label") + self._media_channel = None + else: + self._media_content_type = None + self._media_content_id = None + self._media_title = self._sources.get(self._tv.source_id) + self._media_channel = None @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/philips_js/strings.json b/homeassistant/components/philips_js/strings.json index 2267315501f771..df65d453f2b0cf 100644 --- a/homeassistant/components/philips_js/strings.json +++ b/homeassistant/components/philips_js/strings.json @@ -10,8 +10,10 @@ }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, + "unknown": "[%key:common::config_flow::error::unknown%]", + "pairing_failure": "Unable to pair: {error_id}", + "invalid_pin": "Invalid PIN" +}, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index 249fe5a892dbc8..b2022a01824de1 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -5,7 +5,9 @@ }, "error": { "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" + "unknown": "Unexpected error", + "pairing_failure": "Unable to pair: {error_id}", + "invalid_pin": "Invalid PIN" }, "step": { "user": { diff --git a/requirements_all.txt b/requirements_all.txt index bfd55ed68bc511..2cfcc67a25da2d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -721,7 +721,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.1.0 +ha-philipsjs==2.3.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9dd674925d62fa..de10ccaf6a972e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -382,7 +382,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==0.1.0 +ha-philipsjs==2.3.0 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/tests/components/philips_js/__init__.py b/tests/components/philips_js/__init__.py index 1c96a6d4e55d62..9dea390a600615 100644 --- a/tests/components/philips_js/__init__.py +++ b/tests/components/philips_js/__init__.py @@ -3,6 +3,9 @@ MOCK_SERIAL_NO = "1234567890" MOCK_NAME = "Philips TV" +MOCK_USERNAME = "mock_user" +MOCK_PASSWORD = "mock_password" + MOCK_SYSTEM = { "menulanguage": "English", "name": MOCK_NAME, @@ -12,14 +15,63 @@ "model": "modelname", } +MOCK_SYSTEM_UNPAIRED = { + "menulanguage": "Dutch", + "name": "55PUS7181/12", + "country": "Netherlands", + "serialnumber": "ABCDEFGHIJKLF", + "softwareversion": "TPM191E_R.101.001.208.001", + "model": "65OLED855/12", + "deviceid": "1234567890", + "nettvversion": "6.0.2", + "epgsource": "one", + "api_version": {"Major": 6, "Minor": 2, "Patch": 0}, + "featuring": { + "jsonfeatures": { + "editfavorites": ["TVChannels", "SatChannels"], + "recordings": ["List", "Schedule", "Manage"], + "ambilight": ["LoungeLight", "Hue", "Ambilight"], + "menuitems": ["Setup_Menu"], + "textentry": [ + "context_based", + "initial_string_available", + "editor_info_available", + ], + "applications": ["TV_Apps", "TV_Games", "TV_Settings"], + "pointer": ["not_available"], + "inputkey": ["key"], + "activities": ["intent"], + "channels": ["preset_string"], + "mappings": ["server_mapping"], + }, + "systemfeatures": { + "tvtype": "consumer", + "content": ["dmr", "dms_tad"], + "tvsearch": "intent", + "pairing_type": "digest_auth_pairing", + "secured_transport": "True", + }, + }, +} + MOCK_USERINPUT = { "host": "1.1.1.1", - "api_version": 1, } +MOCK_IMPORT = {"host": "1.1.1.1", "api_version": 6} + MOCK_CONFIG = { - **MOCK_USERINPUT, + "host": "1.1.1.1", + "api_version": 1, "system": MOCK_SYSTEM, } +MOCK_CONFIG_PAIRED = { + "host": "1.1.1.1", + "api_version": 6, + "username": MOCK_USERNAME, + "password": MOCK_PASSWORD, + "system": MOCK_SYSTEM_UNPAIRED, +} + MOCK_ENTITY_ID = "media_player.philips_tv" diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py index 549ad77fb069f0..4b6150f9f8103c 100644 --- a/tests/components/philips_js/conftest.py +++ b/tests/components/philips_js/conftest.py @@ -1,6 +1,7 @@ """Standard setup for tests.""" -from unittest.mock import Mock, patch +from unittest.mock import create_autospec, patch +from haphilipsjs import PhilipsTV from pytest import fixture from homeassistant import setup @@ -20,10 +21,18 @@ async def setup_notification(hass): @fixture(autouse=True) def mock_tv(): """Disable component actual use.""" - tv = Mock(autospec="philips_js.PhilipsTV") + tv = create_autospec(PhilipsTV) tv.sources = {} tv.channels = {} + tv.application = None + tv.applications = {} tv.system = MOCK_SYSTEM + tv.api_version = 1 + tv.api_version_detected = None + tv.on = True + tv.notify_change_supported = False + tv.pairing_type = None + tv.powerstate = None with patch( "homeassistant.components.philips_js.config_flow.PhilipsTV", return_value=tv diff --git a/tests/components/philips_js/test_config_flow.py b/tests/components/philips_js/test_config_flow.py index 75caff788914d2..45e896319f1552 100644 --- a/tests/components/philips_js/test_config_flow.py +++ b/tests/components/philips_js/test_config_flow.py @@ -1,12 +1,21 @@ """Test the Philips TV config flow.""" -from unittest.mock import patch +from unittest.mock import ANY, patch +from haphilipsjs import PairingFailure from pytest import fixture from homeassistant import config_entries from homeassistant.components.philips_js.const import DOMAIN -from . import MOCK_CONFIG, MOCK_USERINPUT +from . import ( + MOCK_CONFIG, + MOCK_CONFIG_PAIRED, + MOCK_IMPORT, + MOCK_PASSWORD, + MOCK_SYSTEM_UNPAIRED, + MOCK_USERINPUT, + MOCK_USERNAME, +) @fixture(autouse=True) @@ -27,12 +36,26 @@ def mock_setup_entry(): yield mock_setup_entry +@fixture +async def mock_tv_pairable(mock_tv): + """Return a mock tv that is pariable.""" + mock_tv.system = MOCK_SYSTEM_UNPAIRED + mock_tv.pairing_type = "digest_auth_pairing" + mock_tv.api_version = 6 + mock_tv.api_version_detected = 6 + mock_tv.secured_transport = True + + mock_tv.pairRequest.return_value = {} + mock_tv.pairGrant.return_value = MOCK_USERNAME, MOCK_PASSWORD + return mock_tv + + async def test_import(hass, mock_setup, mock_setup_entry): """Test we get an item on import.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_USERINPUT, + data=MOCK_IMPORT, ) assert result["type"] == "create_entry" @@ -47,7 +70,7 @@ async def test_import_exist(hass, mock_config_entry): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_USERINPUT, + data=MOCK_IMPORT, ) assert result["type"] == "abort" @@ -103,3 +126,116 @@ async def test_form_unexpected_error(hass, mock_tv): assert result["type"] == "form" assert result["errors"] == {"base": "unknown"} + + +async def test_pairing(hass, mock_tv_pairable, mock_setup, mock_setup_entry): + """Test we get the form.""" + mock_tv = mock_tv_pairable + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + + assert result["type"] == "form" + assert result["errors"] == {} + + mock_tv.setTransport.assert_called_with(True) + mock_tv.pairRequest.assert_called() + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result == { + "flow_id": ANY, + "type": "create_entry", + "description": None, + "description_placeholders": None, + "handler": "philips_js", + "result": ANY, + "title": "55PUS7181/12 (ABCDEFGHIJKLF)", + "data": MOCK_CONFIG_PAIRED, + "version": 1, + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_pair_request_failed( + hass, mock_tv_pairable, mock_setup, mock_setup_entry +): + """Test we get the form.""" + mock_tv = mock_tv_pairable + mock_tv.pairRequest.side_effect = PairingFailure({}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + + assert result == { + "flow_id": ANY, + "description_placeholders": {"error_id": None}, + "handler": "philips_js", + "reason": "pairing_failure", + "type": "abort", + } + + +async def test_pair_grant_failed(hass, mock_tv_pairable, mock_setup, mock_setup_entry): + """Test we get the form.""" + mock_tv = mock_tv_pairable + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + MOCK_USERINPUT, + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_tv.setTransport.assert_called_with(True) + mock_tv.pairRequest.assert_called() + + # Test with invalid pin + mock_tv.pairGrant.side_effect = PairingFailure({"error_id": "INVALID_PIN"}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result["type"] == "form" + assert result["errors"] == {"pin": "invalid_pin"} + + # Test with unexpected failure + mock_tv.pairGrant.side_effect = PairingFailure({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"pin": "1234"} + ) + + assert result == { + "flow_id": ANY, + "description_placeholders": {"error_id": None}, + "handler": "philips_js", + "reason": "pairing_failure", + "type": "abort", + } diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py index 43c7c424cf9e8d..ebda40f13e58fb 100644 --- a/tests/components/philips_js/test_device_trigger.py +++ b/tests/components/philips_js/test_device_trigger.py @@ -33,9 +33,13 @@ async def test_get_triggers(hass, mock_device): assert_lists_same(triggers, expected_triggers) -async def test_if_fires_on_turn_on_request(hass, calls, mock_entity, mock_device): +async def test_if_fires_on_turn_on_request( + hass, calls, mock_tv, mock_entity, mock_device +): """Test for turn_on and turn_off triggers firing.""" + mock_tv.on = False + assert await async_setup_component( hass, automation.DOMAIN, From 6a850a14816005146d140416be580cc8f6d80150 Mon Sep 17 00:00:00 2001 From: CurrentThread <62957822+CurrentThread@users.noreply.github.com> Date: Fri, 26 Feb 2021 11:52:47 +0100 Subject: [PATCH 0822/1818] Add support for Shelly SHBTN-2 device triggers (#46644) --- homeassistant/components/shelly/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index b4148801b3575d..0058374cfe7330 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -111,7 +111,7 @@ def get_device_channel_name( def is_momentary_input(settings: dict, block: aioshelly.Block) -> bool: """Return true if input button settings is set to a momentary type.""" # Shelly Button type is fixed to momentary and no btn_type - if settings["device"]["type"] == "SHBTN-1": + if settings["device"]["type"] in ("SHBTN-1", "SHBTN-2"): return True button = settings.get("relays") or settings.get("lights") or settings.get("inputs") @@ -158,7 +158,7 @@ def get_input_triggers( else: subtype = f"button{int(block.channel)+1}" - if device.settings["device"]["type"] == "SHBTN-1": + if device.settings["device"]["type"] in ("SHBTN-1", "SHBTN-2"): trigger_types = SHBTN_1_INPUTS_EVENTS_TYPES elif device.settings["device"]["type"] == "SHIX3-1": trigger_types = SHIX3_1_INPUTS_EVENTS_TYPES From c12769213d8e1a0a3bb1c3fec5ba9b6dbaf4dd4d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 10:35:09 -0600 Subject: [PATCH 0823/1818] Add suggested area to hue (#47056) --- homeassistant/components/hue/const.py | 5 + homeassistant/components/hue/light.py | 151 ++++++++++++++++++++------ tests/components/hue/test_light.py | 99 ++++++++++++----- 3 files changed, 191 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 593f74331ec46c..b782ce70193890 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -15,3 +15,8 @@ DEFAULT_ALLOW_HUE_GROUPS = False DEFAULT_SCENE_TRANSITION = 4 + +GROUP_TYPE_LIGHT_GROUP = "LightGroup" +GROUP_TYPE_ROOM = "Room" +GROUP_TYPE_LUMINAIRE = "Luminaire" +GROUP_TYPE_LIGHT_SOURCE = "LightSource" diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 821d482ec25dd9..6384e47b45e823 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -36,7 +36,14 @@ ) from homeassistant.util import color -from .const import DOMAIN as HUE_DOMAIN, REQUEST_REFRESH_DELAY +from .const import ( + DOMAIN as HUE_DOMAIN, + GROUP_TYPE_LIGHT_GROUP, + GROUP_TYPE_LIGHT_SOURCE, + GROUP_TYPE_LUMINAIRE, + GROUP_TYPE_ROOM, + REQUEST_REFRESH_DELAY, +) from .helpers import remove_devices SCAN_INTERVAL = timedelta(seconds=5) @@ -74,24 +81,35 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """ -def create_light(item_class, coordinator, bridge, is_group, api, item_id): +def create_light(item_class, coordinator, bridge, is_group, rooms, api, item_id): """Create the light.""" + api_item = api[item_id] + if is_group: supported_features = 0 - for light_id in api[item_id].lights: + for light_id in api_item.lights: if light_id not in bridge.api.lights: continue light = bridge.api.lights[light_id] supported_features |= SUPPORT_HUE.get(light.type, SUPPORT_HUE_EXTENDED) supported_features = supported_features or SUPPORT_HUE_EXTENDED else: - supported_features = SUPPORT_HUE.get(api[item_id].type, SUPPORT_HUE_EXTENDED) - return item_class(coordinator, bridge, is_group, api[item_id], supported_features) + supported_features = SUPPORT_HUE.get(api_item.type, SUPPORT_HUE_EXTENDED) + return item_class( + coordinator, bridge, is_group, api_item, supported_features, rooms + ) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Hue lights from a config entry.""" bridge = hass.data[HUE_DOMAIN][config_entry.entry_id] + api_version = tuple(int(v) for v in bridge.api.config.apiversion.split(".")) + rooms = {} + + allow_groups = bridge.allow_groups + supports_groups = api_version >= GROUP_MIN_API_VERSION + if allow_groups and not supports_groups: + _LOGGER.warning("Please update your Hue bridge to support groups") light_coordinator = DataUpdateCoordinator( hass, @@ -111,27 +129,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if not light_coordinator.last_update_success: raise PlatformNotReady - update_lights = partial( - async_update_items, - bridge, - bridge.api.lights, - {}, - async_add_entities, - partial(create_light, HueLight, light_coordinator, bridge, False), - ) - - # We add a listener after fetching the data, so manually trigger listener - bridge.reset_jobs.append(light_coordinator.async_add_listener(update_lights)) - update_lights() - - api_version = tuple(int(v) for v in bridge.api.config.apiversion.split(".")) - - allow_groups = bridge.allow_groups - if allow_groups and api_version < GROUP_MIN_API_VERSION: - _LOGGER.warning("Please update your Hue bridge to support groups") - allow_groups = False - - if not allow_groups: + if not supports_groups: + update_lights_without_group_support = partial( + async_update_items, + bridge, + bridge.api.lights, + {}, + async_add_entities, + partial(create_light, HueLight, light_coordinator, bridge, False, rooms), + None, + ) + # We add a listener after fetching the data, so manually trigger listener + bridge.reset_jobs.append( + light_coordinator.async_add_listener(update_lights_without_group_support) + ) return group_coordinator = DataUpdateCoordinator( @@ -145,17 +156,69 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ), ) - update_groups = partial( + if allow_groups: + update_groups = partial( + async_update_items, + bridge, + bridge.api.groups, + {}, + async_add_entities, + partial(create_light, HueLight, group_coordinator, bridge, True, None), + None, + ) + + bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups)) + + cancel_update_rooms_listener = None + + @callback + def _async_update_rooms(): + """Update rooms.""" + nonlocal cancel_update_rooms_listener + rooms.clear() + for item_id in bridge.api.groups: + group = bridge.api.groups[item_id] + if group.type != GROUP_TYPE_ROOM: + continue + for light_id in group.lights: + rooms[light_id] = group.name + + # Once we do a rooms update, we cancel the listener + # until the next time lights are added + bridge.reset_jobs.remove(cancel_update_rooms_listener) + cancel_update_rooms_listener() # pylint: disable=not-callable + cancel_update_rooms_listener = None + + @callback + def _setup_rooms_listener(): + nonlocal cancel_update_rooms_listener + if cancel_update_rooms_listener is not None: + # If there are new lights added before _async_update_rooms + # is called we should not add another listener + return + + cancel_update_rooms_listener = group_coordinator.async_add_listener( + _async_update_rooms + ) + bridge.reset_jobs.append(cancel_update_rooms_listener) + + _setup_rooms_listener() + await group_coordinator.async_refresh() + + update_lights_with_group_support = partial( async_update_items, bridge, - bridge.api.groups, + bridge.api.lights, {}, async_add_entities, - partial(create_light, HueLight, group_coordinator, bridge, True), + partial(create_light, HueLight, light_coordinator, bridge, False, rooms), + _setup_rooms_listener, ) - - bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups)) - await group_coordinator.async_refresh() + # We add a listener after fetching the data, so manually trigger listener + bridge.reset_jobs.append( + light_coordinator.async_add_listener(update_lights_with_group_support) + ) + update_lights_with_group_support() async def async_safe_fetch(bridge, fetch_method): @@ -171,7 +234,9 @@ async def async_safe_fetch(bridge, fetch_method): @callback -def async_update_items(bridge, api, current, async_add_entities, create_item): +def async_update_items( + bridge, api, current, async_add_entities, create_item, new_items_callback +): """Update items.""" new_items = [] @@ -185,6 +250,9 @@ def async_update_items(bridge, api, current, async_add_entities, create_item): bridge.hass.async_create_task(remove_devices(bridge, api, current)) if new_items: + # This is currently used to setup the listener to update rooms + if new_items_callback: + new_items_callback() async_add_entities(new_items) @@ -201,13 +269,14 @@ def hass_to_hue_brightness(value): class HueLight(CoordinatorEntity, LightEntity): """Representation of a Hue light.""" - def __init__(self, coordinator, bridge, is_group, light, supported_features): + def __init__(self, coordinator, bridge, is_group, light, supported_features, rooms): """Initialize the light.""" super().__init__(coordinator) self.light = light self.bridge = bridge self.is_group = is_group self._supported_features = supported_features + self._rooms = rooms if is_group: self.is_osram = False @@ -355,10 +424,15 @@ def effect_list(self): @property def device_info(self): """Return the device info.""" - if self.light.type in ("LightGroup", "Room", "Luminaire", "LightSource"): + if self.light.type in ( + GROUP_TYPE_LIGHT_GROUP, + GROUP_TYPE_ROOM, + GROUP_TYPE_LUMINAIRE, + GROUP_TYPE_LIGHT_SOURCE, + ): return None - return { + info = { "identifiers": {(HUE_DOMAIN, self.device_id)}, "name": self.name, "manufacturer": self.light.manufacturername, @@ -370,6 +444,11 @@ def device_info(self): "via_device": (HUE_DOMAIN, self.bridge.api.config.bridgeid), } + if self.light.id in self._rooms: + info["suggested_area"] = self._rooms[self.light.id] + + return info + async def async_turn_on(self, **kwargs): """Turn the specified or all lights on.""" command = {"on": True} diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 629a9a4c98bad8..39b9a5a23fc7b0 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -7,6 +7,12 @@ from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import light as hue_light +from homeassistant.helpers.device_registry import ( + async_get_registry as async_get_device_registry, +) +from homeassistant.helpers.entity_registry import ( + async_get_registry as async_get_entity_registry, +) from homeassistant.util import color HUE_LIGHT_NS = "homeassistant.components.light.hue." @@ -211,8 +217,10 @@ async def test_no_lights_or_groups(hass, mock_bridge): async def test_lights(hass, mock_bridge): """Test the update_lights function with some lights.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 # 2 lights assert len(hass.states.async_all()) == 2 @@ -230,6 +238,8 @@ async def test_lights(hass, mock_bridge): async def test_lights_color_mode(hass, mock_bridge): """Test that lights only report appropriate color mode.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) lamp_1 = hass.states.get("light.hue_lamp_1") @@ -249,8 +259,8 @@ async def test_lights_color_mode(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_2"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 lamp_1 = hass.states.get("light.hue_lamp_1") assert lamp_1 is not None @@ -332,9 +342,10 @@ async def test_new_group_discovered(hass, mock_bridge): async def test_new_light_discovered(hass, mock_bridge): """Test if 2nd update has a new light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 new_light_response = dict(LIGHT_RESPONSE) @@ -366,8 +377,8 @@ async def test_new_light_discovered(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 3 light = hass.states.get("light.hue_lamp_3") @@ -407,9 +418,10 @@ async def test_group_removed(hass, mock_bridge): async def test_light_removed(hass, mock_bridge): """Test if 2nd update has removed light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 mock_bridge.mock_light_responses.clear() @@ -420,8 +432,8 @@ async def test_light_removed(hass, mock_bridge): "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 1 light = hass.states.get("light.hue_lamp_1") @@ -487,9 +499,10 @@ async def test_other_group_update(hass, mock_bridge): async def test_other_light_update(hass, mock_bridge): """Test changing one light that will impact state of other light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) await setup_bridge(hass, mock_bridge) - assert len(mock_bridge.mock_requests) == 1 + assert len(mock_bridge.mock_requests) == 2 assert len(hass.states.async_all()) == 2 lamp_2 = hass.states.get("light.hue_lamp_2") @@ -526,8 +539,8 @@ async def test_other_light_update(hass, mock_bridge): await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 assert len(hass.states.async_all()) == 2 lamp_2 = hass.states.get("light.hue_lamp_2") @@ -549,7 +562,6 @@ async def test_update_timeout(hass, mock_bridge): async def test_update_unauthorized(hass, mock_bridge): """Test bridge marked as not authorized if unauthorized during update.""" mock_bridge.api.lights.update = Mock(side_effect=aiohue.Unauthorized) - mock_bridge.api.groups.update = Mock(side_effect=aiohue.Unauthorized) await setup_bridge(hass, mock_bridge) assert len(mock_bridge.mock_requests) == 0 assert len(hass.states.async_all()) == 0 @@ -559,6 +571,8 @@ async def test_update_unauthorized(hass, mock_bridge): async def test_light_turn_on_service(hass, mock_bridge): """Test calling the turn on service on a light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) light = hass.states.get("light.hue_lamp_2") assert light is not None @@ -575,10 +589,10 @@ async def test_light_turn_on_service(hass, mock_bridge): {"entity_id": "light.hue_lamp_2", "brightness": 100, "color_temp": 300}, blocking=True, ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 + # 2x light update, 1 group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 - assert mock_bridge.mock_requests[1]["json"] == { + assert mock_bridge.mock_requests[2]["json"] == { "bri": 100, "on": True, "ct": 300, @@ -599,9 +613,9 @@ async def test_light_turn_on_service(hass, mock_bridge): blocking=True, ) - assert len(mock_bridge.mock_requests) == 5 + assert len(mock_bridge.mock_requests) == 6 - assert mock_bridge.mock_requests[3]["json"] == { + assert mock_bridge.mock_requests[4]["json"] == { "on": True, "xy": (0.138, 0.08), "alert": "none", @@ -611,6 +625,8 @@ async def test_light_turn_on_service(hass, mock_bridge): async def test_light_turn_off_service(hass, mock_bridge): """Test calling the turn on service on a light.""" mock_bridge.mock_light_responses.append(LIGHT_RESPONSE) + mock_bridge.mock_group_responses.append(GROUP_RESPONSE) + await setup_bridge(hass, mock_bridge) light = hass.states.get("light.hue_lamp_1") assert light is not None @@ -624,10 +640,11 @@ async def test_light_turn_off_service(hass, mock_bridge): await hass.services.async_call( "light", "turn_off", {"entity_id": "light.hue_lamp_1"}, blocking=True ) - # 2x light update, 1 turn on request - assert len(mock_bridge.mock_requests) == 3 - assert mock_bridge.mock_requests[1]["json"] == {"on": False, "alert": "none"} + # 2x light update, 1 for group update, 1 turn on request + assert len(mock_bridge.mock_requests) == 4 + + assert mock_bridge.mock_requests[2]["json"] == {"on": False, "alert": "none"} assert len(hass.states.async_all()) == 2 @@ -649,6 +666,7 @@ def test_available(): bridge=Mock(allow_unreachable=False), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is False @@ -664,6 +682,7 @@ def test_available(): bridge=Mock(allow_unreachable=True), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is True @@ -679,6 +698,7 @@ def test_available(): bridge=Mock(allow_unreachable=False), is_group=True, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.available is True @@ -697,6 +717,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color is None @@ -712,6 +733,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color is None @@ -727,6 +749,7 @@ def test_hs_color(): bridge=Mock(), is_group=False, supported_features=hue_light.SUPPORT_HUE_EXTENDED, + rooms={}, ) assert light.hs_color == color.color_xy_to_hs(0.4, 0.5, LIGHT_GAMUT) @@ -742,7 +765,7 @@ async def test_group_features(hass, mock_bridge): "1": { "name": "Group 1", "lights": ["1", "2"], - "type": "Room", + "type": "LightGroup", "action": { "on": True, "bri": 254, @@ -757,8 +780,8 @@ async def test_group_features(hass, mock_bridge): "state": {"any_on": True, "all_on": False}, }, "2": { - "name": "Group 2", - "lights": ["3", "4"], + "name": "Living Room", + "lights": ["2", "3"], "type": "Room", "action": { "on": True, @@ -774,8 +797,8 @@ async def test_group_features(hass, mock_bridge): "state": {"any_on": True, "all_on": False}, }, "3": { - "name": "Group 3", - "lights": ["1", "3"], + "name": "Dining Room", + "lights": ["4"], "type": "Room", "action": { "on": True, @@ -900,6 +923,7 @@ async def test_group_features(hass, mock_bridge): mock_bridge.mock_light_responses.append(light_response) mock_bridge.mock_group_responses.append(group_response) await setup_bridge(hass, mock_bridge) + assert len(mock_bridge.mock_requests) == 2 color_temp_feature = hue_light.SUPPORT_HUE["Color temperature light"] extended_color_feature = hue_light.SUPPORT_HUE["Extended color light"] @@ -907,8 +931,27 @@ async def test_group_features(hass, mock_bridge): group_1 = hass.states.get("light.group_1") assert group_1.attributes["supported_features"] == color_temp_feature - group_2 = hass.states.get("light.group_2") + group_2 = hass.states.get("light.living_room") assert group_2.attributes["supported_features"] == extended_color_feature - group_3 = hass.states.get("light.group_3") + group_3 = hass.states.get("light.dining_room") assert group_3.attributes["supported_features"] == extended_color_feature + + entity_registry = await async_get_entity_registry(hass) + device_registry = await async_get_device_registry(hass) + + entry = entity_registry.async_get("light.hue_lamp_1") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area is None + + entry = entity_registry.async_get("light.hue_lamp_2") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Living Room" + + entry = entity_registry.async_get("light.hue_lamp_3") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Living Room" + + entry = entity_registry.async_get("light.hue_lamp_4") + device_entry = device_registry.async_get(entry.device_id) + assert device_entry.suggested_area == "Dining Room" From 4cd40d0f9f05e8e062c69705ca5352d91df3e9ac Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Fri, 26 Feb 2021 13:57:47 +0100 Subject: [PATCH 0824/1818] Bump bimmer_connected to 0.7.15 and fix bugs (#47066) Co-authored-by: rikroe --- homeassistant/components/bmw_connected_drive/__init__.py | 2 +- .../components/bmw_connected_drive/device_tracker.py | 4 ++-- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 9d794ace5be144..a8bebfbc617b0a 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -122,7 +122,7 @@ async def _async_update_all(service_call=None): def _update_all() -> None: """Update all BMW accounts.""" - for entry in hass.data[DOMAIN][DATA_ENTRIES].values(): + for entry in hass.data[DOMAIN][DATA_ENTRIES].copy().values(): entry[CONF_ACCOUNT].update() # Add update listener for config entry changes (options) diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index 7f069e741b8ecd..25adf6cb09f946 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -42,12 +42,12 @@ def __init__(self, account, vehicle): @property def latitude(self): """Return latitude value of the device.""" - return self._location[0] + return self._location[0] if self._location else None @property def longitude(self): """Return longitude value of the device.""" - return self._location[1] + return self._location[1] if self._location else None @property def name(self): diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index c1d90f713f4adf..bbff139187e8b9 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.7.14"], + "requirements": ["bimmer_connected==0.7.15"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 2cfcc67a25da2d..1a6ffa47e8a8b5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -343,7 +343,7 @@ beautifulsoup4==4.9.3 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.14 +bimmer_connected==0.7.15 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de10ccaf6a972e..3235c215b3dbce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -196,7 +196,7 @@ base36==0.1.1 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.14 +bimmer_connected==0.7.15 # homeassistant.components.blebox blebox_uniapi==1.3.2 From 255b6faa7f0d1f450f815bbd3cae488796677f45 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 00:16:11 -0800 Subject: [PATCH 0825/1818] Upgrade aiohttp to 3.7.4 (#47077) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8f84546371e8fe..10cf300b76b32e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ PyJWT==1.7.1 PyNaCl==1.3.0 -aiohttp==3.7.3 +aiohttp==3.7.4 aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 diff --git a/requirements.txt b/requirements.txt index 14ebf2708ada8d..0a5b754dbfc286 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -c homeassistant/package_constraints.txt # Home Assistant Core -aiohttp==3.7.3 +aiohttp==3.7.4 astral==1.10.1 async_timeout==3.0.1 attrs==19.3.0 diff --git a/setup.py b/setup.py index 6dbe35760a6ddf..ce7d6b6883d5e6 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ PACKAGES = find_packages(exclude=["tests", "tests.*"]) REQUIRES = [ - "aiohttp==3.7.3", + "aiohttp==3.7.4", "astral==1.10.1", "async_timeout==3.0.1", "attrs==19.3.0", From 96e118ccfef382d38f15b6ecb4e8d02987381ec6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Feb 2021 10:47:22 +0100 Subject: [PATCH 0826/1818] Bump pychromecast to 8.1.2 (#47085) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 5963e93cf8cb62..28ccb78d5b9ca6 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==8.1.0"], + "requirements": ["pychromecast==8.1.2"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 1a6ffa47e8a8b5..e8f09ad002a5be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1305,7 +1305,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==8.1.0 +pychromecast==8.1.2 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3235c215b3dbce..a55805b5365cf7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -688,7 +688,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==8.1.0 +pychromecast==8.1.2 # homeassistant.components.climacell pyclimacell==0.14.0 From d55f0df09afb2c1227d1e4e6cdf56c459c821f0a Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 26 Feb 2021 20:19:23 +0100 Subject: [PATCH 0827/1818] Fix Z-Wave JS discovery schema for thermostat devices (#47087) Co-authored-by: Martin Hjelmare --- .../components/zwave_js/discovery.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index a40eb10de8b792..f5f3d9e5c5bd17 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -75,6 +75,8 @@ class ZWaveDiscoverySchema: device_class_specific: Optional[Set[Union[str, int]]] = None # [optional] additional values that ALL need to be present on the node for this scheme to pass required_values: Optional[List[ZWaveValueDiscoverySchema]] = None + # [optional] additional values that MAY NOT be present on the node for this scheme to pass + absent_values: Optional[List[ZWaveValueDiscoverySchema]] = None # [optional] bool to specify if this primary value may be discovered by multiple platforms allow_multi: bool = False @@ -186,36 +188,30 @@ class ZWaveDiscoverySchema: ), ), # climate + # thermostats supporting mode (and optional setpoint) ZWaveDiscoverySchema( platform="climate", - device_class_generic={"Thermostat"}, - device_class_specific={ - "Setback Thermostat", - "Thermostat General", - "Thermostat General V2", - "General Thermostat", - "General Thermostat V2", - }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_MODE}, property={"mode"}, type={"number"}, ), ), - # climate - # setpoint thermostats + # thermostats supporting setpoint only (and thus not mode) ZWaveDiscoverySchema( platform="climate", - device_class_generic={"Thermostat"}, - device_class_specific={ - "Setpoint Thermostat", - "Unused", - }, primary_value=ZWaveValueDiscoverySchema( command_class={CommandClass.THERMOSTAT_SETPOINT}, property={"setpoint"}, type={"number"}, ), + absent_values=[ # mode must not be present to prevent dupes + ZWaveValueDiscoverySchema( + command_class={CommandClass.THERMOSTAT_MODE}, + property={"mode"}, + type={"number"}, + ), + ], ), # binary sensors ZWaveDiscoverySchema( @@ -436,6 +432,13 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None for val_scheme in schema.required_values ): continue + # check for values that may not be present + if schema.absent_values is not None: + if any( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.absent_values + ): + continue # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( node=value.node, From 1d1be8ad1a58d8c71064272cbebc0e1e75cb794f Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 26 Feb 2021 20:07:53 +0100 Subject: [PATCH 0828/1818] Bump aioshelly to 0.6.1 (#47088) --- 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 c38869b3e0df13..a757947c5cfbb6 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==0.6.0"], + "requirements": ["aioshelly==0.6.1"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/requirements_all.txt b/requirements_all.txt index e8f09ad002a5be..37eecdba573dc3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,7 +221,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.6.0 +aioshelly==0.6.1 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a55805b5365cf7..91232ad8eaa670 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -140,7 +140,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.6.0 +aioshelly==0.6.1 # homeassistant.components.switcher_kis aioswitcher==1.2.1 From 5e2bafca563ffbe582096e90bdef242bffb5626c Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 26 Feb 2021 15:49:33 +0100 Subject: [PATCH 0829/1818] Add new machine generic-x86-64 to build matrix (#47095) The Intel NUC machine runs on most UEFI capable x86-64 machines today. Lets start a new machine generic-x86-64 which will replace intel-nuc over time. --- azure-pipelines-release.yml | 4 +++- machine/generic-x86-64 | 34 ++++++++++++++++++++++++++++++++++ machine/intel-nuc | 3 +++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 machine/generic-x86-64 diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 418fdf5b26c5bf..5fe9132558207d 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -114,10 +114,12 @@ stages: pool: vmImage: 'ubuntu-latest' strategy: - maxParallel: 15 + maxParallel: 17 matrix: qemux86-64: buildMachine: 'qemux86-64' + generic-x86-64: + buildMachine: 'generic-x86-64' intel-nuc: buildMachine: 'intel-nuc' qemux86: diff --git a/machine/generic-x86-64 b/machine/generic-x86-64 new file mode 100644 index 00000000000000..e858c38222192b --- /dev/null +++ b/machine/generic-x86-64 @@ -0,0 +1,34 @@ +ARG BUILD_VERSION +FROM agners/amd64-homeassistant:$BUILD_VERSION + +RUN apk --no-cache add \ + libva-intel-driver \ + usbutils + +## +# Build libcec for HDMI-CEC +ARG LIBCEC_VERSION=6.0.2 +RUN apk add --no-cache \ + eudev-libs \ + p8-platform \ + && apk add --no-cache --virtual .build-dependencies \ + build-base \ + cmake \ + eudev-dev \ + swig \ + p8-platform-dev \ + linux-headers \ + && git clone --depth 1 -b libcec-${LIBCEC_VERSION} https://github.com/Pulse-Eight/libcec /usr/src/libcec \ + && cd /usr/src/libcec \ + && mkdir -p /usr/src/libcec/build \ + && cd /usr/src/libcec/build \ + && cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local \ + -DPYTHON_LIBRARY="/usr/local/lib/libpython3.8.so" \ + -DPYTHON_INCLUDE_DIR="/usr/local/include/python3.8" \ + -DHAVE_LINUX_API=1 \ + .. \ + && make -j$(nproc) \ + && make install \ + && echo "cec" > "/usr/local/lib/python3.8/site-packages/cec.pth" \ + && apk del .build-dependencies \ + && rm -rf /usr/src/libcec* diff --git a/machine/intel-nuc b/machine/intel-nuc index 4c83228387ddb9..b5538b8ccad233 100644 --- a/machine/intel-nuc +++ b/machine/intel-nuc @@ -1,6 +1,9 @@ ARG BUILD_VERSION FROM homeassistant/amd64-homeassistant:$BUILD_VERSION +# NOTE: intel-nuc will be replaced by generic-x86-64. Make sure to apply +# changes in generic-x86-64 as well. + RUN apk --no-cache add \ libva-intel-driver \ usbutils From 0969cc985bebed16de5e85c3a371a12fc2648bec Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 26 Feb 2021 11:20:32 -0800 Subject: [PATCH 0830/1818] Bump google-nest-sdm to v0.2.12 to improve API call error messages (#47108) --- 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 c68dbe6ee2f42e..734261d9b08611 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http"], "documentation": "https://www.home-assistant.io/integrations/nest", - "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.10"], + "requirements": ["python-nest==4.1.0", "google-nest-sdm==0.2.12"], "codeowners": ["@allenporter"], "quality_scale": "platinum", "dhcp": [{"macaddress":"18B430*"}] diff --git a/requirements_all.txt b/requirements_all.txt index 37eecdba573dc3..d0437c6f9bcc66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -682,7 +682,7 @@ google-cloud-pubsub==2.1.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==0.2.10 +google-nest-sdm==0.2.12 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 91232ad8eaa670..3eab63288a3c9a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.1.0 # homeassistant.components.nest -google-nest-sdm==0.2.10 +google-nest-sdm==0.2.12 # homeassistant.components.gree greeclimate==0.10.3 From cdf7372fd8c8e0d2caa208000ebd7bc3a2450891 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 19:21:15 +0000 Subject: [PATCH 0831/1818] Bumped version to 2021.3.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index aca184b4606597..57308e6eed052b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 7c2545af6e874951ce9ef9262cef1de3399443a9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 13:28:52 -0800 Subject: [PATCH 0832/1818] Use async_capture_events to avoid running in executor (#47111) --- tests/components/alexa/test_smart_home.py | 10 ++----- tests/components/automation/test_init.py | 12 ++++---- tests/components/demo/test_notify.py | 6 ++-- .../google_assistant/test_smart_home.py | 29 +++++++++---------- .../components/google_assistant/test_trait.py | 5 ++-- tests/components/homeassistant/test_scene.py | 7 ++--- tests/components/homekit/conftest.py | 9 ++---- tests/components/shelly/conftest.py | 12 ++++---- 8 files changed, 39 insertions(+), 51 deletions(-) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 657bc407fb0b96..c018e07c2648b0 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -26,7 +26,7 @@ import homeassistant.components.vacuum as vacuum from homeassistant.config import async_process_ha_core_config from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.core import Context, callback +from homeassistant.core import Context from homeassistant.helpers import entityfilter from homeassistant.setup import async_setup_component @@ -42,17 +42,13 @@ reported_properties, ) -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service @pytest.fixture def events(hass): """Fixture that catches alexa events.""" - events = [] - hass.bus.async_listen( - smart_home.EVENT_ALEXA_SMART_HOME, callback(lambda e: events.append(e)) - ) - yield events + return async_capture_events(hass, smart_home.EVENT_ALEXA_SMART_HOME) @pytest.fixture diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 3e498b52a08910..91531481a993c8 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -30,7 +30,12 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import assert_setup_component, async_mock_service, mock_restore_cache +from tests.common import ( + assert_setup_component, + async_capture_events, + async_mock_service, + mock_restore_cache, +) from tests.components.logbook.test_init import MockLazyEventPartialState @@ -496,10 +501,7 @@ async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_onl assert len(calls) == 1 assert calls[0].data.get("event") == "test_event" - test_reload_event = [] - hass.bus.async_listen( - EVENT_AUTOMATION_RELOADED, lambda event: test_reload_event.append(event) - ) + test_reload_event = async_capture_events(hass, EVENT_AUTOMATION_RELOADED) with patch( "homeassistant.config.load_yaml_config_file", diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index 7c7f83312dda3c..153f065235cc20 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -12,7 +12,7 @@ from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component +from tests.common import assert_setup_component, async_capture_events CONFIG = {notify.DOMAIN: {"platform": "demo"}} @@ -20,9 +20,7 @@ @pytest.fixture def events(hass): """Fixture that catches notify events.""" - events = [] - hass.bus.async_listen(demo.EVENT_NOTIFY, callback(lambda e: events.append(e))) - yield events + return async_capture_events(hass, demo.EVENT_NOTIFY) @pytest.fixture diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 9c8f9a483388d7..9531602ef0c512 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -30,7 +30,12 @@ from . import BASIC_CONFIG, MockConfig -from tests.common import mock_area_registry, mock_device_registry, mock_registry +from tests.common import ( + async_capture_events, + mock_area_registry, + mock_device_registry, + mock_registry, +) REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -77,8 +82,7 @@ async def test_sync_message(hass): }, ) - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, @@ -192,8 +196,7 @@ async def test_sync_in_area(area_on_device, hass, registries): config = MockConfig(should_expose=lambda _: True, entity_config={}) - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, @@ -295,8 +298,7 @@ async def test_query_message(hass): light3.entity_id = "light.color_temp_light" await light3.async_update_ha_state() - events = [] - hass.bus.async_listen(EVENT_QUERY_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_QUERY_RECEIVED) result = await sh.async_handle_message( hass, @@ -387,11 +389,8 @@ async def test_execute(hass): "light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True ) - events = [] - hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append) - - service_events = [] - hass.bus.async_listen(EVENT_CALL_SERVICE, service_events.append) + events = async_capture_events(hass, EVENT_COMMAND_RECEIVED) + service_events = async_capture_events(hass, EVENT_CALL_SERVICE) result = await sh.async_handle_message( hass, @@ -570,8 +569,7 @@ async def test_raising_error_trait(hass): {ATTR_MIN_TEMP: 15, ATTR_MAX_TEMP: 30, ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}, ) - events = [] - hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_COMMAND_RECEIVED) await hass.async_block_till_done() result = await sh.async_handle_message( @@ -660,8 +658,7 @@ async def test_unavailable_state_does_sync(hass): light._available = False # pylint: disable=protected-access await light.async_update_ha_state() - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index a9b1e9a97fb452..ba1890205130d3 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -54,7 +54,7 @@ from . import BASIC_CONFIG, MockConfig -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -84,8 +84,7 @@ async def test_brightness_light(hass): assert trt.query_attributes() == {"brightness": 95} - events = [] - hass.bus.async_listen(EVENT_CALL_SERVICE, events.append) + events = async_capture_events(hass, EVENT_CALL_SERVICE) calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON) await trt.execute( diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 3098543271833a..610bc371b25102 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -8,17 +8,14 @@ from homeassistant.components.homeassistant.scene import EVENT_SCENE_RELOADED from homeassistant.setup import async_setup_component -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service async def test_reload_config_service(hass): """Test the reload config service.""" assert await async_setup_component(hass, "scene", {}) - test_reloaded_event = [] - hass.bus.async_listen( - EVENT_SCENE_RELOADED, lambda event: test_reloaded_event.append(event) - ) + test_reloaded_event = async_capture_events(hass, EVENT_SCENE_RELOADED) with patch( "homeassistant.config.load_yaml_config_file", diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index ac51c4e636875a..228b5f078376be 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -5,7 +5,8 @@ import pytest from homeassistant.components.homekit.const import EVENT_HOMEKIT_CHANGED -from homeassistant.core import callback as ha_callback + +from tests.common import async_capture_events @pytest.fixture @@ -24,8 +25,4 @@ def hk_driver(loop): @pytest.fixture def events(hass): """Yield caught homekit_changed events.""" - events = [] - hass.bus.async_listen( - EVENT_HOMEKIT_CHANGED, ha_callback(lambda e: events.append(e)) - ) - yield events + return async_capture_events(hass, EVENT_HOMEKIT_CHANGED) diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 7e7bd0688422df..51659cf77361d8 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -10,10 +10,14 @@ DOMAIN, EVENT_SHELLY_CLICK, ) -from homeassistant.core import callback as ha_callback from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, async_mock_service, mock_device_registry +from tests.common import ( + MockConfigEntry, + async_capture_events, + async_mock_service, + mock_device_registry, +) MOCK_SETTINGS = { "name": "Test name", @@ -81,9 +85,7 @@ def calls(hass): @pytest.fixture def events(hass): """Yield caught shelly_click events.""" - ha_events = [] - hass.bus.async_listen(EVENT_SHELLY_CLICK, ha_callback(ha_events.append)) - yield ha_events + return async_capture_events(hass, EVENT_SHELLY_CLICK) @pytest.fixture From e9b8e035b42c1c222def6dc4d1d96767fecbb7ad Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 27 Feb 2021 00:28:16 +0200 Subject: [PATCH 0833/1818] Fix Shelly RGBW (#47116) --- homeassistant/components/shelly/const.py | 4 +- homeassistant/components/shelly/light.py | 47 ++++++++++-------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 9d1c333b2017a6..4fda656e7b4463 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -74,5 +74,5 @@ # Kelvin value for colorTemp KELVIN_MAX_VALUE = 6500 -KELVIN_MIN_VALUE = 2700 -KELVIN_MIN_VALUE_SHBLB_1 = 3000 +KELVIN_MIN_VALUE_WHITE = 2700 +KELVIN_MIN_VALUE_COLOR = 3000 diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 848ef9903404b0..0379bfec1cfd48 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -28,20 +28,13 @@ DATA_CONFIG_ENTRY, DOMAIN, KELVIN_MAX_VALUE, - KELVIN_MIN_VALUE, - KELVIN_MIN_VALUE_SHBLB_1, + KELVIN_MIN_VALUE_COLOR, + KELVIN_MIN_VALUE_WHITE, ) from .entity import ShellyBlockEntity from .utils import async_remove_shelly_entity -def min_kelvin(model: str): - """Kelvin (min) for colorTemp.""" - if model in ["SHBLB-1"]: - return KELVIN_MIN_VALUE_SHBLB_1 - return KELVIN_MIN_VALUE - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up lights for device.""" wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP] @@ -76,6 +69,8 @@ def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None: self.control_result = None self.mode_result = None self._supported_features = 0 + self._min_kelvin = KELVIN_MIN_VALUE_WHITE + self._max_kelvin = KELVIN_MAX_VALUE if hasattr(block, "brightness") or hasattr(block, "gain"): self._supported_features |= SUPPORT_BRIGHTNESS @@ -85,6 +80,7 @@ def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None: self._supported_features |= SUPPORT_WHITE_VALUE if hasattr(block, "red") and hasattr(block, "green") and hasattr(block, "blue"): self._supported_features |= SUPPORT_COLOR + self._min_kelvin = KELVIN_MIN_VALUE_COLOR @property def supported_features(self) -> int: @@ -168,22 +164,19 @@ def color_temp(self) -> Optional[int]: else: color_temp = self.block.colorTemp - # If you set DUO to max mireds in Shelly app, 2700K, - # It reports 0 temp - if color_temp == 0: - return min_kelvin(self.wrapper.model) + color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) return int(color_temperature_kelvin_to_mired(color_temp)) @property def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" - return int(color_temperature_kelvin_to_mired(KELVIN_MAX_VALUE)) + return int(color_temperature_kelvin_to_mired(self._max_kelvin)) @property def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" - return int(color_temperature_kelvin_to_mired(min_kelvin(self.wrapper.model))) + return int(color_temperature_kelvin_to_mired(self._min_kelvin)) async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" @@ -192,6 +185,7 @@ async def async_turn_on(self, **kwargs) -> None: self.async_write_ha_state() return + set_mode = None params = {"turn": "on"} if ATTR_BRIGHTNESS in kwargs: tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100) @@ -201,27 +195,26 @@ async def async_turn_on(self, **kwargs) -> None: params["brightness"] = tmp_brightness if ATTR_COLOR_TEMP in kwargs: color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - color_temp = min( - KELVIN_MAX_VALUE, max(min_kelvin(self.wrapper.model), color_temp) - ) + color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) # Color temperature change - used only in white mode, switch device mode to white - if self.mode == "color": - self.mode_result = await self.wrapper.device.switch_light_mode("white") - params["red"] = params["green"] = params["blue"] = 255 + set_mode = "white" + params["red"] = params["green"] = params["blue"] = 255 params["temp"] = int(color_temp) - elif ATTR_HS_COLOR in kwargs: + if ATTR_HS_COLOR in kwargs: red, green, blue = color_hs_to_RGB(*kwargs[ATTR_HS_COLOR]) # Color channels change - used only in color mode, switch device mode to color - if self.mode == "white": - self.mode_result = await self.wrapper.device.switch_light_mode("color") + set_mode = "color" params["red"] = red params["green"] = green params["blue"] = blue - elif ATTR_WHITE_VALUE in kwargs: + if ATTR_WHITE_VALUE in kwargs: # White channel change - used only in color mode, switch device mode device to color - if self.mode == "white": - self.mode_result = await self.wrapper.device.switch_light_mode("color") + set_mode = "color" params["white"] = int(kwargs[ATTR_WHITE_VALUE]) + + if set_mode and self.mode != set_mode: + self.mode_result = await self.wrapper.device.switch_light_mode(set_mode) + self.control_result = await self.block.set_state(**params) self.async_write_ha_state() From 43621091b7fad7b2c42ed2d3cdcf38df11d2c42b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 27 Feb 2021 00:05:45 +0000 Subject: [PATCH 0834/1818] [ci skip] Translation update --- .../accuweather/translations/nl.json | 4 ++ .../alarmdecoder/translations/nl.json | 2 +- .../azure_devops/translations/nl.json | 14 +++++- .../components/bond/translations/it.json | 4 +- .../components/bond/translations/nl.json | 5 +++ .../components/broadlink/translations/nl.json | 9 ++++ .../components/climacell/translations/it.json | 34 ++++++++++++++ .../components/climacell/translations/no.json | 2 +- .../components/cover/translations/nl.json | 3 +- .../faa_delays/translations/it.json | 21 +++++++++ .../faa_delays/translations/no.json | 21 +++++++++ .../components/gogogate2/translations/nl.json | 2 +- .../components/hlk_sw16/translations/nl.json | 1 + .../components/homekit/translations/it.json | 8 ++-- .../components/homekit/translations/nl.json | 6 +-- .../components/insteon/translations/nl.json | 3 ++ .../components/kmtronic/translations/it.json | 21 +++++++++ .../components/kodi/translations/nl.json | 7 ++- .../components/litejet/translations/it.json | 19 ++++++++ .../litterrobot/translations/it.json | 20 +++++++++ .../media_player/translations/nl.json | 2 +- .../components/mqtt/translations/nl.json | 1 + .../components/mullvad/translations/it.json | 22 ++++++++++ .../components/netatmo/translations/it.json | 22 ++++++++++ .../components/netatmo/translations/nl.json | 7 ++- .../components/netatmo/translations/no.json | 22 ++++++++++ .../philips_js/translations/en.json | 4 +- .../philips_js/translations/et.json | 2 + .../rainmachine/translations/nl.json | 2 +- .../components/risco/translations/nl.json | 4 +- .../components/sentry/translations/nl.json | 3 ++ .../simplisafe/translations/nl.json | 6 ++- .../somfy_mylink/translations/nl.json | 2 +- .../components/spotify/translations/nl.json | 2 +- .../components/subaru/translations/it.json | 44 +++++++++++++++++++ .../components/syncthru/translations/nl.json | 5 +++ .../totalconnect/translations/it.json | 17 ++++++- .../components/volumio/translations/nl.json | 1 + .../wolflink/translations/sensor.nl.json | 11 ++++- .../xiaomi_miio/translations/it.json | 1 + .../xiaomi_miio/translations/nl.json | 1 + .../zoneminder/translations/nl.json | 2 +- .../components/zwave_js/translations/it.json | 7 ++- .../components/zwave_js/translations/nl.json | 2 +- 44 files changed, 365 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/climacell/translations/it.json create mode 100644 homeassistant/components/faa_delays/translations/it.json create mode 100644 homeassistant/components/faa_delays/translations/no.json create mode 100644 homeassistant/components/kmtronic/translations/it.json create mode 100644 homeassistant/components/litejet/translations/it.json create mode 100644 homeassistant/components/litterrobot/translations/it.json create mode 100644 homeassistant/components/mullvad/translations/it.json create mode 100644 homeassistant/components/subaru/translations/it.json diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index ff0d81f94d38b5..4bf5f9fce45e82 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "API-sleutel", "requests_exceeded": "Het toegestane aantal verzoeken aan de Accuweather API is overschreden. U moet wachten of de API-sleutel wijzigen." }, diff --git a/homeassistant/components/alarmdecoder/translations/nl.json b/homeassistant/components/alarmdecoder/translations/nl.json index 1af1e8d803c9c2..1ea9cb98b56a69 100644 --- a/homeassistant/components/alarmdecoder/translations/nl.json +++ b/homeassistant/components/alarmdecoder/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "AlarmDecoder-apparaat is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "create_entry": { "default": "Succesvol verbonden met AlarmDecoder." diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index aef6a7178954e4..07dc59e1a56ac3 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -1,11 +1,21 @@ { "config": { "abort": { - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "project_error": "Kon geen projectinformatie ophalen." + }, + "step": { + "user": { + "data": { + "organization": "Organisatie", + "project": "Project" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/it.json b/homeassistant/components/bond/translations/it.json index d3ac1ab6b49094..e22ad82e1fd1a1 100644 --- a/homeassistant/components/bond/translations/it.json +++ b/homeassistant/components/bond/translations/it.json @@ -9,13 +9,13 @@ "old_firmware": "Firmware precedente non supportato sul dispositivo Bond - si prega di aggiornare prima di continuare", "unknown": "Errore imprevisto" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token di accesso" }, - "description": "Vuoi configurare {bond_id}?" + "description": "Vuoi configurare {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index b5d8c593ea9e93..a76c7a69d7f68a 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -10,6 +10,11 @@ }, "flow_title": "Bond: {bond_id} ({host})", "step": { + "confirm": { + "data": { + "access_token": "Toegangstoken" + } + }, "user": { "data": { "access_token": "Toegangstoken", diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 7f85335d7bb375..d2db5476555607 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -18,6 +18,15 @@ "finish": { "data": { "name": "Naam" + }, + "title": "Kies een naam voor het apparaat" + }, + "reset": { + "title": "Ontgrendel het apparaat" + }, + "unlock": { + "data": { + "unlock": "Ja, doe het." } }, "user": { diff --git a/homeassistant/components/climacell/translations/it.json b/homeassistant/components/climacell/translations/it.json new file mode 100644 index 00000000000000..cc7df4f8ab3800 --- /dev/null +++ b/homeassistant/components/climacell/translations/it.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_api_key": "Chiave API non valida", + "rate_limited": "Al momento la tariffa \u00e8 limitata, riprova pi\u00f9 tardi.", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine", + "name": "Nome" + }, + "description": "Se Latitudine e Logitudine non vengono forniti, verranno utilizzati i valori predefiniti nella configurazione di Home Assistant. Verr\u00e0 creata un'entit\u00e0 per ogni tipo di previsione, ma solo quelli selezionati saranno abilitati per impostazione predefinita." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipo(i) di previsione", + "timestep": "Minuti tra le previsioni di NowCast" + }, + "description": "Se scegli di abilitare l'entit\u00e0 di previsione `nowcast`, puoi configurare il numero di minuti tra ogni previsione. Il numero di previsioni fornite dipende dal numero di minuti scelti tra le previsioni.", + "title": "Aggiorna le opzioni di ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index 64845ff76978c0..af07ce716d0917 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -14,7 +14,7 @@ "longitude": "Lengdegrad", "name": "Navn" }, - "description": "Hvis Breddegrad and Lengdegrad er ikke gitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." + "description": "Hvis Breddegrad og Lengdegrad ikke er oppgitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." } } }, diff --git a/homeassistant/components/cover/translations/nl.json b/homeassistant/components/cover/translations/nl.json index 679d9360a821c4..8b1ca3c3500f7c 100644 --- a/homeassistant/components/cover/translations/nl.json +++ b/homeassistant/components/cover/translations/nl.json @@ -6,7 +6,8 @@ "open": "Open {entity_name}", "open_tilt": "Open de kanteling {entity_name}", "set_position": "Stel de positie van {entity_name} in", - "set_tilt_position": "Stel de {entity_name} kantelpositie in" + "set_tilt_position": "Stel de {entity_name} kantelpositie in", + "stop": "Stop {entity_name}" }, "condition_type": { "is_closed": "{entity_name} is gesloten", diff --git a/homeassistant/components/faa_delays/translations/it.json b/homeassistant/components/faa_delays/translations/it.json new file mode 100644 index 00000000000000..e1bf6ad0646a09 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Questo aeroporto \u00e8 gi\u00e0 configurato." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_airport": "Il codice dell'aeroporto non \u00e8 valido", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "id": "Aeroporto" + }, + "description": "Immettere un codice aeroporto statunitense in formato IATA", + "title": "Ritardi FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/no.json b/homeassistant/components/faa_delays/translations/no.json new file mode 100644 index 00000000000000..c481f90bf75068 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Denne flyplassen er allerede konfigurert." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_airport": "Flyplasskoden er ikke gyldig", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "id": "Flyplass" + }, + "description": "Skriv inn en amerikansk flyplasskode i IATA-format", + "title": "FAA forsinkelser" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/nl.json b/homeassistant/components/gogogate2/translations/nl.json index ad8e894d093ffe..5418735ec07a91 100644 --- a/homeassistant/components/gogogate2/translations/nl.json +++ b/homeassistant/components/gogogate2/translations/nl.json @@ -15,7 +15,7 @@ "username": "Gebruikersnaam" }, "description": "Geef hieronder de vereiste informatie op.", - "title": "Stel GogoGate2 in" + "title": "Stel GogoGate2 of iSmartGate in" } } } diff --git a/homeassistant/components/hlk_sw16/translations/nl.json b/homeassistant/components/hlk_sw16/translations/nl.json index 0569c39321a24b..8ad15260b0de5c 100644 --- a/homeassistant/components/hlk_sw16/translations/nl.json +++ b/homeassistant/components/hlk_sw16/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 9a85d1e6e9fc52..fee64457652dd0 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -19,7 +19,7 @@ "title": "Seleziona i domini da includere" }, "pairing": { - "description": "Non appena il {name} \u00e8 pronto, l'associazione sar\u00e0 disponibile in \"Notifiche\" come \"Configurazione HomeKit Bridge\".", + "description": "Per completare l'associazione, seguire le istruzioni in \"Notifiche\" sotto \"Associazione HomeKit\".", "title": "Associa HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domini da includere", "mode": "Modalit\u00e0" }, - "description": "L'integrazione di HomeKit ti consentir\u00e0 di accedere alle entit\u00e0 di Home Assistant in HomeKit. In modalit\u00e0 bridge, i bridge HomeKit sono limitati a 150 accessori per istanza, incluso il bridge stesso. Se desideri eseguire il bridge di un numero di accessori superiore a quello massimo, si consiglia di utilizzare pi\u00f9 bridge HomeKit per domini diversi. La configurazione dettagliata dell'entit\u00e0 \u00e8 disponibile solo tramite YAML per il bridge principale.", - "title": "Attiva HomeKit" + "description": "Scegli i domini da includere. Verranno incluse tutte le entit\u00e0 supportate nel dominio. Verr\u00e0 creata un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale TV e telecamera.", + "title": "Seleziona i domini da includere" } } }, @@ -55,7 +55,7 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali e per evitare una indisponibilit\u00e0 imprevista, creare e associare un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, ci sar\u00e0 una HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", "title": "Seleziona le entit\u00e0 da includere" }, "init": { diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index bcf61fe9868faa..9013723ac6cd8b 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -11,7 +11,7 @@ }, "pairing": { "description": "Zodra de {name} klaar is, is het koppelen beschikbaar in \"Meldingen\" als \"HomeKit Bridge Setup\".", - "title": "Koppel HomeKit Bridge" + "title": "Koppel HomeKit" }, "user": { "data": { @@ -20,7 +20,7 @@ "mode": "Mode" }, "description": "De HomeKit-integratie geeft u toegang tot uw Home Assistant-entiteiten in HomeKit. In bridge-modus zijn HomeKit-bruggen beperkt tot 150 accessoires per exemplaar, inclusief de brug zelf. Als u meer dan het maximale aantal accessoires wilt overbruggen, is het aan te raden om meerdere HomeKit-bridges voor verschillende domeinen te gebruiken. Gedetailleerde entiteitsconfiguratie is alleen beschikbaar via YAML voor de primaire bridge.", - "title": "Activeer HomeKit Bridge" + "title": "Selecteer domeinen die u wilt opnemen" } } }, @@ -57,7 +57,7 @@ }, "yaml": { "description": "Deze invoer wordt beheerd via YAML", - "title": "Pas de HomeKit Bridge-opties aan" + "title": "Pas de HomeKit-opties aan" } } } diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index e4f7d4a8102f70..98a27fb1139382 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -87,6 +87,9 @@ "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." } + }, + "remove_x10": { + "title": "Insteon" } } } diff --git a/homeassistant/components/kmtronic/translations/it.json b/homeassistant/components/kmtronic/translations/it.json new file mode 100644 index 00000000000000..e9356485e087f8 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 8eb4a39cfb698a..57476791b8fa1a 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -11,6 +11,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Kodi: {name}", "step": { "credentials": { "data": { @@ -19,11 +20,15 @@ }, "description": "Voer uw Kodi gebruikersnaam en wachtwoord in. Deze zijn te vinden in Systeem / Instellingen / Netwerk / Services." }, + "discovery_confirm": { + "description": "Wil je Kodi (`{name}`) toevoegen aan Home Assistant?", + "title": "Kodi ontdekt" + }, "user": { "data": { "host": "Host", "port": "Poort", - "ssl": "Maak verbinding via SSL" + "ssl": "Gebruik een SSL-certificaat" }, "description": "Kodi-verbindingsinformatie. Zorg ervoor dat u \"Controle van Kodi via HTTP toestaan\" in Systeem / Instellingen / Netwerk / Services inschakelt." }, diff --git a/homeassistant/components/litejet/translations/it.json b/homeassistant/components/litejet/translations/it.json new file mode 100644 index 00000000000000..5b3dc46753d888 --- /dev/null +++ b/homeassistant/components/litejet/translations/it.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "open_failed": "Impossibile aprire la porta seriale specificata." + }, + "step": { + "user": { + "data": { + "port": "Porta" + }, + "description": "Collega la porta RS232-2 del LiteJet al tuo computer e inserisci il percorso del dispositivo della porta seriale. \n\nL'MCP LiteJet deve essere configurato per 19,2 K baud, 8 bit di dati, 1 bit di stop, nessuna parit\u00e0 e per trasmettere un \"CR\" dopo ogni risposta.", + "title": "Connetti a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/it.json b/homeassistant/components/litterrobot/translations/it.json new file mode 100644 index 00000000000000..843262aa31858b --- /dev/null +++ b/homeassistant/components/litterrobot/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ 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 37c1d6b4d9ef9b..6ad22742533bf7 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -22,7 +22,7 @@ "on": "Aan", "paused": "Gepauzeerd", "playing": "Afspelen", - "standby": "Standby" + "standby": "Stand-by" } }, "title": "Mediaspeler" diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index a0ab0e497dabfc..3b3ebf9fe3b9a5 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -63,6 +63,7 @@ }, "options": { "data": { + "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", "birth_topic": "Birth message onderwerp" } diff --git a/homeassistant/components/mullvad/translations/it.json b/homeassistant/components/mullvad/translations/it.json new file mode 100644 index 00000000000000..47cd8290f215ad --- /dev/null +++ b/homeassistant/components/mullvad/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Nome utente" + }, + "description": "Configurare l'integrazione VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/it.json b/homeassistant/components/netatmo/translations/it.json index 46c2d7d2721814..152f7d47597ac8 100644 --- a/homeassistant/components/netatmo/translations/it.json +++ b/homeassistant/components/netatmo/translations/it.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "Fuori casa", + "hg": "protezione antigelo", + "schedule": "programma" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha rilevato un allarme", + "animal": "{entity_name} ha rilevato un animale", + "cancel_set_point": "{entity_name} ha ripreso il suo programma", + "human": "{entity_name} ha rilevato un essere umano", + "movement": "{entity_name} ha rilevato un movimento", + "outdoor": "{entity_name} ha rilevato un evento all'esterno", + "person": "{entity_name} ha rilevato una persona", + "person_away": "{entity_name} ha rilevato che una persona \u00e8 uscita", + "set_point": "{entity_name} temperatura desiderata impostata manualmente", + "therm_mode": "{entity_name} \u00e8 passato a \"{subtype}\"", + "turned_off": "{entity_name} disattivato", + "turned_on": "{entity_name} attivato", + "vehicle": "{entity_name} ha rilevato un veicolo" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 431f105df3d8c8..0bdc3170a5a5b6 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -3,7 +3,8 @@ "abort": { "authorize_url_timeout": "Time-out genereren autorisatie-URL.", "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "create_entry": { "default": "Succesvol geauthenticeerd met Netatmo." @@ -41,6 +42,10 @@ "public_weather": { "data": { "area_name": "Naam van het gebied", + "lat_ne": "Breedtegraad Noordoostelijke hoek", + "lat_sw": "Breedtegraad Zuidwestelijke hoek", + "lon_ne": "Lengtegraad Noordoostelijke hoek", + "lon_sw": "Lengtegraad Zuidwestelijke hoek", "mode": "Berekening", "show_on_map": "Toon op kaart" } diff --git a/homeassistant/components/netatmo/translations/no.json b/homeassistant/components/netatmo/translations/no.json index 387dbe7b26cf3c..9e3e24d5771b66 100644 --- a/homeassistant/components/netatmo/translations/no.json +++ b/homeassistant/components/netatmo/translations/no.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "borte", + "hg": "frostvakt", + "schedule": "Tidsplan" + }, + "trigger_type": { + "alarm_started": "{entity_name} oppdaget en alarm", + "animal": "{entity_name} oppdaget et dyr", + "cancel_set_point": "{entity_name} har gjenopptatt tidsplanen", + "human": "{entity_name} oppdaget et menneske", + "movement": "{entity_name} oppdaget bevegelse", + "outdoor": "{entity_name} oppdaget en utend\u00f8rs hendelse", + "person": "{entity_name} oppdaget en person", + "person_away": "{entity_name} oppdaget at en person har forlatt", + "set_point": "M\u00e5ltemperatur {entity_name} angis manuelt", + "therm_mode": "{entity_name} byttet til \"{subtype}\"", + "turned_off": "{entity_name} sl\u00e5tt av", + "turned_on": "{entity_name} sl\u00e5tt p\u00e5", + "vehicle": "{entity_name} oppdaget et kj\u00f8ret\u00f8y" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index b2022a01824de1..65d4f417b9f9ec 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -5,9 +5,9 @@ }, "error": { "cannot_connect": "Failed to connect", - "unknown": "Unexpected error", + "invalid_pin": "Invalid PIN", "pairing_failure": "Unable to pair: {error_id}", - "invalid_pin": "Invalid PIN" + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json index c77ef726411ce6..9953df9c272940 100644 --- a/homeassistant/components/philips_js/translations/et.json +++ b/homeassistant/components/philips_js/translations/et.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", + "invalid_pin": "Vale PIN kood", + "pairing_failure": "Sidumine nurjus: {error_id}", "unknown": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index 02411ea999f0c3..119e4c641afdf0 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze RainMachine controller is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json index 34bcb4ab98a491..97d0d454a4f0c2 100644 --- a/homeassistant/components/risco/translations/nl.json +++ b/homeassistant/components/risco/translations/nl.json @@ -30,8 +30,8 @@ }, "init": { "data": { - "code_arm_required": "Pincode vereist om in te schakelen", - "code_disarm_required": "Pincode vereist om uit te schakelen" + "code_arm_required": "PIN-code vereist om in te schakelen", + "code_disarm_required": "PIN-code vereist om uit te schakelen" }, "title": "Configureer opties" }, diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 37437dfe8368fa..64b7f1b73f767f 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -9,6 +9,9 @@ }, "step": { "user": { + "data": { + "dsn": "DSN" + }, "description": "Voer uw Sentry DSN in", "title": "Sentry" } diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index b285b288525a1d..d3196c591cb192 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Dit SimpliSafe-account is al in gebruik." + "already_configured": "Dit SimpliSafe-account is al in gebruik.", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "identifier_exists": "Account bestaat al", @@ -13,7 +14,8 @@ "data": { "password": "Wachtwoord" }, - "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen." + "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen.", + "title": "Verifieer de integratie opnieuw" }, "user": { "data": { diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index a63320919c6dcf..b0ae5c9d3ad6ff 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -8,7 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Somfy MyLink {mac} ( {ip} )", + "flow_title": "Somfy MyLink {mac} ({ip})", "step": { "user": { "data": { diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index bdc86919f7466b..46b18857fe8b6a 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -15,7 +15,7 @@ }, "reauth_confirm": { "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", - "title": "Verifieer opnieuw met Spotify" + "title": "Verifieer de integratie opnieuw" } } } diff --git a/homeassistant/components/subaru/translations/it.json b/homeassistant/components/subaru/translations/it.json new file mode 100644 index 00000000000000..6dbb0702f46682 --- /dev/null +++ b/homeassistant/components/subaru/translations/it.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi" + }, + "error": { + "bad_pin_format": "Il PIN deve essere di 4 cifre", + "cannot_connect": "Impossibile connettersi", + "incorrect_pin": "PIN errato", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Inserisci il tuo PIN MySubaru\nNOTA: tutti i veicoli nell'account devono avere lo stesso PIN", + "title": "Configurazione Subaru Starlink" + }, + "user": { + "data": { + "country": "Seleziona il paese", + "password": "Password", + "username": "Nome utente" + }, + "description": "Inserisci le tue credenziali MySubaru\nNOTA: la configurazione iniziale pu\u00f2 richiedere fino a 30 secondi", + "title": "Configurazione Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Abilita l'interrogazione del veicolo" + }, + "description": "Quando abilitata, l'interrogazione del veicolo invier\u00e0 un comando remoto al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo, i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", + "title": "Opzioni Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index b1beb4058bc9d3..799e19ea37104f 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -8,6 +8,11 @@ }, "flow_title": "Samsung SyncThru Printer: {name}", "step": { + "confirm": { + "data": { + "name": "Naam" + } + }, "user": { "data": { "name": "Naam", diff --git a/homeassistant/components/totalconnect/translations/it.json b/homeassistant/components/totalconnect/translations/it.json index 2a12d00f57d3d0..18ecf6483108d4 100644 --- a/homeassistant/components/totalconnect/translations/it.json +++ b/homeassistant/components/totalconnect/translations/it.json @@ -1,12 +1,25 @@ { "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" }, "error": { - "invalid_auth": "Autenticazione non valida" + "invalid_auth": "Autenticazione non valida", + "usercode": "Codice utente non valido per questo utente in questa posizione" }, "step": { + "locations": { + "data": { + "location": "Posizione" + }, + "description": "Immettere il codice utente per questo utente in questa posizione", + "title": "Codici utente posizione" + }, + "reauth_confirm": { + "description": "Total Connect deve autenticare nuovamente il tuo account", + "title": "Autenticare nuovamente l'integrazione" + }, "user": { "data": { "password": "Password", diff --git a/homeassistant/components/volumio/translations/nl.json b/homeassistant/components/volumio/translations/nl.json index 9179418def99c0..9e11dbad82ba3b 100644 --- a/homeassistant/components/volumio/translations/nl.json +++ b/homeassistant/components/volumio/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index da03cc43b4b317..ae205d79aef701 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -10,7 +10,16 @@ "heizung": "Verwarmen", "initialisierung": "Initialisatie", "kalibration": "Kalibratie", - "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus" + "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "permanent": "Permanent", + "standby": "Stand-by", + "start": "Start", + "storung": "Fout", + "test": "Test", + "tpw": "TPW", + "urlaubsmodus": "Vakantiemodus", + "ventilprufung": "Kleptest", + "warmwasser": "DHW" } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index 68202e1631e5fe..aa48ba7cfa8022 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Indirizzo IP", + "model": "Modello del dispositivo (opzionale)", "name": "Nome del dispositivo", "token": "Token API" }, diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 3ea12e3a465785..66209e61ee67f5 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -8,6 +8,7 @@ "cannot_connect": "Kan geen verbinding maken", "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" }, + "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { diff --git a/homeassistant/components/zoneminder/translations/nl.json b/homeassistant/components/zoneminder/translations/nl.json index f4f071d9097fc6..8aed5085391b50 100644 --- a/homeassistant/components/zoneminder/translations/nl.json +++ b/homeassistant/components/zoneminder/translations/nl.json @@ -23,7 +23,7 @@ "password": "Wachtwoord", "path": "ZM-pad", "path_zms": "ZMS-pad", - "ssl": "Gebruik SSL voor verbindingen met ZoneMinder", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Verifieer SSL-certificaat" }, diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index 5f0868a3f743b4..abe0ab066fb111 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -6,6 +6,7 @@ "addon_install_failed": "Impossibile installare il componente aggiuntivo Z-Wave JS.", "addon_missing_discovery_info": "Informazioni sul rilevamento del componente aggiuntivo Z-Wave JS mancanti.", "addon_set_config_failed": "Impossibile impostare la configurazione di Z-Wave JS.", + "addon_start_failed": "Impossibile avviare il componente aggiuntivo Z-Wave JS.", "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi" @@ -17,7 +18,8 @@ "unknown": "Errore imprevisto" }, "progress": { - "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti." + "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti.", + "start_addon": "Attendi il completamento dell'avvio del componente aggiuntivo Z-Wave JS. L'operazione potrebbe richiedere alcuni secondi." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS Supervisor?", "title": "Seleziona il metodo di connessione" }, + "start_addon": { + "title": "Il componente aggiuntivo Z-Wave JS si sta avviando." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index 7f46c02ece5ecc..c15cfd26f31c78 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -5,7 +5,7 @@ "addon_info_failed": "Ophalen van Z-Wave JS add-on-info is mislukt.", "addon_install_failed": "Kan de Z-Wave JS add-on niet installeren.", "addon_missing_discovery_info": "De Z-Wave JS addon mist ontdekkings informatie", - "addon_set_config_failed": "Instellen van de Z-Wave JS-configuratie is mislukt.", + "addon_set_config_failed": "Instellen van de Z-Wave JS configuratie is mislukt.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" From 84e01baa5a104fb17ca1270cadc2a99aad2e1011 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 27 Feb 2021 01:32:51 +0100 Subject: [PATCH 0835/1818] Update frontend to 20210226.0 (#47123) --- 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 e8e9c44ae78a76..01f1c72f8d6092 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==20210225.0" + "home-assistant-frontend==20210226.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 10cf300b76b32e..9506171303bb57 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a2f3a6e45c8716..57e0d027ea28d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e5b4a3d04b1d82..ab133763afea80 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 86f8cd80379f6c2b2f59a2963786d80151d22257 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 18:33:13 -0600 Subject: [PATCH 0836/1818] Provide a human readable exception for the percentage util (#47121) --- homeassistant/util/percentage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index 10a72a85dff438..949af7dbb3235c 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -19,7 +19,7 @@ def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: """ if item not in ordered_list: - raise ValueError + raise ValueError(f'The item "{item}"" is not in "{ordered_list}"') list_len = len(ordered_list) list_position = ordered_list.index(item) + 1 @@ -42,7 +42,7 @@ def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> """ list_len = len(ordered_list) if not list_len: - raise ValueError + raise ValueError("The ordered list is empty") for offset, speed in enumerate(ordered_list): list_position = offset + 1 From 49315a90d9f16e208b957d6526cdb6267020b680 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 18:33:31 -0600 Subject: [PATCH 0837/1818] Handle lutron_caseta fan speed being none (#47120) --- homeassistant/components/lutron_caseta/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 330ff81d1d2cff..57b87b18320f98 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -44,6 +44,8 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): @property def percentage(self) -> str: """Return the current speed percentage.""" + if self._device["fan_speed"] is None: + return None return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) From 97b59023d18be22c5e639193f9c1ac26cd80a747 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 27 Feb 2021 11:20:58 -0800 Subject: [PATCH 0838/1818] Improve handling for recording start of nest cam stream (#47144) * Improve handling for start of nest cam stream Add negative_cts_offsets to segment container options in order to better handle recording at the start of a stream. Nest streams start off with a negative offset, and if the segment container does not support it, then it adjusts the timestamps making it out of order with the next segment as described in issue #46968 * Update homeassistant/components/stream/__init__.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> --- homeassistant/components/stream/__init__.py | 1 + homeassistant/components/stream/worker.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 2d115c6978d78e..c2bf63063e5e46 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -258,6 +258,7 @@ async def async_record(self, video_path, duration=30, lookback=5): recorder.video_path = video_path self.start() + _LOGGER.debug("Started a stream recording of %s seconds", duration) # Take advantage of lookback hls = self.outputs().get("hls") diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index d5760877c43da5..773170449e17e7 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -25,7 +25,7 @@ def create_stream_buffer(video_stream, audio_stream, sequence): segment = io.BytesIO() container_options = { # Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970 - "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont", + "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets", "avoid_negative_ts": "disabled", "fragment_index": str(sequence), } From da309ce342bc09377e21b87cb80cf5774a7d37e1 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 27 Feb 2021 21:09:25 +0100 Subject: [PATCH 0839/1818] Change device class of window covers to shade (#47129) --- homeassistant/components/deconz/cover.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 6e57d08302a831..301d17535917e6 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -2,7 +2,8 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - DEVICE_CLASS_WINDOW, + DEVICE_CLASS_DAMPER, + DEVICE_CLASS_SHADE, DOMAIN, SUPPORT_CLOSE, SUPPORT_CLOSE_TILT, @@ -80,9 +81,9 @@ def supported_features(self): def device_class(self): """Return the class of the cover.""" if self._device.type in DAMPERS: - return "damper" + return DEVICE_CLASS_DAMPER if self._device.type in WINDOW_COVERS: - return DEVICE_CLASS_WINDOW + return DEVICE_CLASS_SHADE @property def current_cover_position(self): From eb7220ff262fe81d53c929884107bcedc8af7850 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 28 Feb 2021 00:07:08 +0000 Subject: [PATCH 0840/1818] [ci skip] Translation update --- .../components/climacell/translations/no.json | 2 +- .../faa_delays/translations/ca.json | 21 ++++++++++++++++++ .../faa_delays/translations/no.json | 2 +- .../components/litejet/translations/fr.json | 3 +++ .../components/litejet/translations/no.json | 2 +- .../components/netatmo/translations/ca.json | 22 +++++++++++++++++++ .../philips_js/translations/ca.json | 2 ++ .../philips_js/translations/fr.json | 2 ++ .../philips_js/translations/no.json | 2 ++ .../philips_js/translations/ru.json | 2 ++ .../components/subaru/translations/fr.json | 15 ++++++++++++- .../components/subaru/translations/no.json | 2 +- .../totalconnect/translations/fr.json | 8 +++++-- .../xiaomi_miio/translations/fr.json | 1 + .../components/zwave_js/translations/no.json | 4 ++-- 15 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/faa_delays/translations/ca.json diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index af07ce716d0917..d59f55905182f0 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -23,7 +23,7 @@ "init": { "data": { "forecast_types": "Prognosetype(r)", - "timestep": "Min. Mellom NowCast Prognoser" + "timestep": "Min. mellom NowCast prognoser" }, "description": "Hvis du velger \u00e5 aktivere \u00abnowcast\u00bb -varselenheten, kan du konfigurere antall minutter mellom hver prognose. Antall angitte prognoser avhenger av antall minutter som er valgt mellom prognosene.", "title": "Oppdater ClimaCell Alternativer" diff --git a/homeassistant/components/faa_delays/translations/ca.json b/homeassistant/components/faa_delays/translations/ca.json new file mode 100644 index 00000000000000..e7e600f7f075fa --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Aeroport ja est\u00e0 configurat." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_airport": "Codi d'aeroport inv\u00e0lid", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "id": "Aeroport" + }, + "description": "Introdueix codi d'un aeroport dels EUA en format IATA", + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/no.json b/homeassistant/components/faa_delays/translations/no.json index c481f90bf75068..5a5aac723ad71d 100644 --- a/homeassistant/components/faa_delays/translations/no.json +++ b/homeassistant/components/faa_delays/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Denne flyplassen er allerede konfigurert." + "already_configured": "Denne flyplassen er allerede konfigurert" }, "error": { "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/litejet/translations/fr.json b/homeassistant/components/litejet/translations/fr.json index 455ba7fdc0c569..89459d1829f3e0 100644 --- a/homeassistant/components/litejet/translations/fr.json +++ b/homeassistant/components/litejet/translations/fr.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, + "error": { + "open_failed": "Impossible d'ouvrir le port s\u00e9rie sp\u00e9cifi\u00e9." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/litejet/translations/no.json b/homeassistant/components/litejet/translations/no.json index 26ccd3335468b5..d3206ca2897c74 100644 --- a/homeassistant/components/litejet/translations/no.json +++ b/homeassistant/components/litejet/translations/no.json @@ -4,7 +4,7 @@ "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "open_failed": "Kan ikke \u00e5pne den angitte serielle porten." + "open_failed": "Kan ikke \u00e5pne den angitte serielle porten" }, "step": { "user": { diff --git a/homeassistant/components/netatmo/translations/ca.json b/homeassistant/components/netatmo/translations/ca.json index a6b8b5c2b82443..809223a04ae3af 100644 --- a/homeassistant/components/netatmo/translations/ca.json +++ b/homeassistant/components/netatmo/translations/ca.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "a fora", + "hg": "protecci\u00f3 contra gelades", + "schedule": "programaci\u00f3" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha detectat una alarma", + "animal": "{entity_name} ha detectat un animal", + "cancel_set_point": "{entity_name} ha repr\u00e8s la programaci\u00f3", + "human": "{entity_name} ha detectat un hum\u00e0", + "movement": "{entity_name} ha detectat moviment", + "outdoor": "{entity_name} ha detectat un esdeveniment a fora", + "person": "{entity_name} ha detectat una persona", + "person_away": "{entity_name} ha detectat una marxant", + "set_point": "Temperatura objectiu {entity_name} configurada manualment", + "therm_mode": "{entity_name} ha canviar a \"{subtype}\"", + "turned_off": "{entity_name} s'ha apagat", + "turned_on": "{entity_name} s'ha engegat", + "vehicle": "{entity_name} ha detectat un vehicle" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index 505a6472ea823a..980bb6800e1719 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_pin": "PIN inv\u00e0lid", + "pairing_failure": "No s'ha pogut vincular: {error_id}", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 9ae65c18fa408e..25c28edcf1da04 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", + "invalid_pin": "NIP invalide", + "pairing_failure": "Association impossible: {error_id}", "unknown": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/philips_js/translations/no.json b/homeassistant/components/philips_js/translations/no.json index dadf15fb67adfb..a9c647a644be22 100644 --- a/homeassistant/components/philips_js/translations/no.json +++ b/homeassistant/components/philips_js/translations/no.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes", + "invalid_pin": "Ugyldig PIN", + "pairing_failure": "Kan ikke parre: {error_id}", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/philips_js/translations/ru.json b/homeassistant/components/philips_js/translations/ru.json index 9306ecf7a2958c..83511ff246aae0 100644 --- a/homeassistant/components/philips_js/translations/ru.json +++ b/homeassistant/components/philips_js/translations/ru.json @@ -5,6 +5,8 @@ }, "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_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434.", + "pairing_failure": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435: {error_id}.", "unknown": "\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/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json index a6bf6902aabd11..25544534297718 100644 --- a/homeassistant/components/subaru/translations/fr.json +++ b/homeassistant/components/subaru/translations/fr.json @@ -24,7 +24,20 @@ "country": "Choisissez le pays", "password": "Mot de passe", "username": "Nom d'utilisateur" - } + }, + "description": "Veuillez saisir vos identifiants MySubaru\n REMARQUE: la configuration initiale peut prendre jusqu'\u00e0 30 secondes", + "title": "Configuration de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Activer l'interrogation des v\u00e9hicules" + }, + "description": "Lorsqu'elle est activ\u00e9e, l'interrogation du v\u00e9hicule enverra une commande \u00e0 distance \u00e0 votre v\u00e9hicule toutes les 2 heures pour obtenir de nouvelles donn\u00e9es de capteur. Sans interrogation du v\u00e9hicule, les nouvelles donn\u00e9es de capteur ne sont re\u00e7ues que lorsque le v\u00e9hicule pousse automatiquement les donn\u00e9es (normalement apr\u00e8s l'arr\u00eat du moteur).", + "title": "Options de Subaru Starlink" } } } diff --git a/homeassistant/components/subaru/translations/no.json b/homeassistant/components/subaru/translations/no.json index f1a263d5cb4bd8..25b0f7bec295b2 100644 --- a/homeassistant/components/subaru/translations/no.json +++ b/homeassistant/components/subaru/translations/no.json @@ -37,7 +37,7 @@ "update_enabled": "Aktiver polling av kj\u00f8ret\u00f8y" }, "description": "N\u00e5r dette er aktivert, sender polling av kj\u00f8ret\u00f8y en fjernkommando til kj\u00f8ret\u00f8yet annenhver time for \u00e5 skaffe nye sensordata. Uten kj\u00f8ret\u00f8yoppm\u00e5ling mottas nye sensordata bare n\u00e5r kj\u00f8ret\u00f8yet automatisk skyver data (normalt etter motorstans).", - "title": "Subaru Starlink Alternativer" + "title": "Subaru Starlink alternativer" } } } diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index 40ca767b4acbec..b46bf12796347a 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -5,15 +5,19 @@ "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { - "invalid_auth": "Authentification invalide" + "invalid_auth": "Authentification invalide", + "usercode": "Code d'utilisateur non valide pour cet utilisateur \u00e0 cet emplacement" }, "step": { "locations": { "data": { "location": "Emplacement" - } + }, + "description": "Saisissez le code d'utilisateur de cet utilisateur \u00e0 cet emplacement", + "title": "Codes d'utilisateur de l'emplacement" }, "reauth_confirm": { + "description": "Total Connect doit r\u00e9-authentifier votre compte", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json index 10ce9972818d1c..30def127e7a66b 100644 --- a/homeassistant/components/xiaomi_miio/translations/fr.json +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adresse IP", + "model": "Mod\u00e8le d'appareil (facultatif)", "name": "Nom de l'appareil", "token": "Jeton d'API" }, diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index acd049fc5619a0..f893d2d7684c35 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -19,7 +19,7 @@ }, "progress": { "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter.", - "start_addon": "Vent mens Z-Wave JS-tilleggsstarten er fullf\u00f8rt. Dette kan ta noen sekunder." + "start_addon": "Vent mens Z-Wave JS-tillegget er ferdig startet. Dette kan ta noen sekunder." }, "step": { "configure_addon": { @@ -48,7 +48,7 @@ "title": "Velg tilkoblingsmetode" }, "start_addon": { - "title": "Z-Wave JS-tillegget starter." + "title": "Z-Wave JS-tillegget starter" }, "user": { "data": { From 66027bcef5a0f8d425ebb64ded7339eaa4fef72a Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 28 Feb 2021 12:53:13 +0100 Subject: [PATCH 0841/1818] Bump airly library to version 1.1.0 (#47163) --- homeassistant/components/airly/__init__.py | 6 ++++++ homeassistant/components/airly/manifest.json | 2 +- homeassistant/components/airly/strings.json | 4 +++- .../components/airly/system_health.py | 9 ++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/airly/test_system_health.py | 20 +++++++++++++++---- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index 6a9c23624f0ab7..fd1defc64f6ad2 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -144,6 +144,12 @@ async def _async_update_data(self): except (AirlyError, ClientConnectorError) as error: raise UpdateFailed(error) from error + _LOGGER.debug( + "Requests remaining: %s/%s", + self.airly.requests_remaining, + self.airly.requests_per_day, + ) + values = measurements.current["values"] index = measurements.current["indexes"][0] standards = measurements.current["standards"] diff --git a/homeassistant/components/airly/manifest.json b/homeassistant/components/airly/manifest.json index 77de843ffce631..a5ff485d1d0aa1 100644 --- a/homeassistant/components/airly/manifest.json +++ b/homeassistant/components/airly/manifest.json @@ -3,7 +3,7 @@ "name": "Airly", "documentation": "https://www.home-assistant.io/integrations/airly", "codeowners": ["@bieniu"], - "requirements": ["airly==1.0.0"], + "requirements": ["airly==1.1.0"], "config_flow": true, "quality_scale": "platinum" } diff --git a/homeassistant/components/airly/strings.json b/homeassistant/components/airly/strings.json index afda73ae887986..c6b6f1e6a41223 100644 --- a/homeassistant/components/airly/strings.json +++ b/homeassistant/components/airly/strings.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Reach Airly server" + "can_reach_server": "Reach Airly server", + "requests_remaining": "Remaining allowed requests", + "requests_per_day": "Allowed requests per day" } } } diff --git a/homeassistant/components/airly/system_health.py b/homeassistant/components/airly/system_health.py index 6b683518ebdead..3f2ed8e8d65cd8 100644 --- a/homeassistant/components/airly/system_health.py +++ b/homeassistant/components/airly/system_health.py @@ -4,6 +4,8 @@ from homeassistant.components import system_health from homeassistant.core import HomeAssistant, callback +from .const import DOMAIN + @callback def async_register( @@ -15,8 +17,13 @@ def async_register( async def system_health_info(hass): """Get info for the info page.""" + requests_remaining = list(hass.data[DOMAIN].values())[0].airly.requests_remaining + requests_per_day = list(hass.data[DOMAIN].values())[0].airly.requests_per_day + return { "can_reach_server": system_health.async_check_can_reach_url( hass, Airly.AIRLY_API_URL - ) + ), + "requests_remaining": requests_remaining, + "requests_per_day": requests_per_day, } diff --git a/requirements_all.txt b/requirements_all.txt index 57e0d027ea28d7..326fc962388cc6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -233,7 +233,7 @@ aiounifi==26 aioymaps==1.1.0 # homeassistant.components.airly -airly==1.0.0 +airly==1.1.0 # homeassistant.components.aladdin_connect aladdin_connect==0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ab133763afea80..561b877a48f1f4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -152,7 +152,7 @@ aiounifi==26 aioymaps==1.1.0 # homeassistant.components.airly -airly==1.0.0 +airly==1.1.0 # homeassistant.components.ambiclimate ambiclimate==0.2.1 diff --git a/tests/components/airly/test_system_health.py b/tests/components/airly/test_system_health.py index 02ee67ae4524a9..42fc50ed0510ed 100644 --- a/tests/components/airly/test_system_health.py +++ b/tests/components/airly/test_system_health.py @@ -18,7 +18,11 @@ async def test_airly_system_health(hass, aioclient_mock): hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = Mock( - airly=Mock(AIRLY_API_URL="https://airapi.airly.eu/v2/") + airly=Mock( + AIRLY_API_URL="https://airapi.airly.eu/v2/", + requests_remaining=42, + requests_per_day=100, + ) ) info = await get_system_health_info(hass, DOMAIN) @@ -27,7 +31,9 @@ async def test_airly_system_health(hass, aioclient_mock): if asyncio.iscoroutine(val): info[key] = await val - assert info == {"can_reach_server": "ok"} + assert info["can_reach_server"] == "ok" + assert info["requests_remaining"] == 42 + assert info["requests_per_day"] == 100 async def test_airly_system_health_fail(hass, aioclient_mock): @@ -38,7 +44,11 @@ async def test_airly_system_health_fail(hass, aioclient_mock): hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = Mock( - airly=Mock(AIRLY_API_URL="https://airapi.airly.eu/v2/") + airly=Mock( + AIRLY_API_URL="https://airapi.airly.eu/v2/", + requests_remaining=0, + requests_per_day=1000, + ) ) info = await get_system_health_info(hass, DOMAIN) @@ -47,4 +57,6 @@ async def test_airly_system_health_fail(hass, aioclient_mock): if asyncio.iscoroutine(val): info[key] = await val - assert info == {"can_reach_server": {"type": "failed", "error": "unreachable"}} + assert info["can_reach_server"] == {"type": "failed", "error": "unreachable"} + assert info["requests_remaining"] == 0 + assert info["requests_per_day"] == 1000 From fa1d91d1fe30f07a5433b6723919c25374df70ea Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 28 Feb 2021 08:16:37 -0500 Subject: [PATCH 0842/1818] Clean up mqtt_room (#46882) --- homeassistant/components/mqtt_room/sensor.py | 11 ++++++++--- tests/components/mqtt_room/test_sensor.py | 5 +---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index 0d07133b39653f..3b61003e60192b 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -8,7 +8,14 @@ from homeassistant.components import mqtt from homeassistant.components.mqtt import CONF_STATE_TOPIC from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ID, CONF_NAME, CONF_TIMEOUT, STATE_NOT_HOME +from homeassistant.const import ( + ATTR_DEVICE_ID, + ATTR_ID, + CONF_DEVICE_ID, + CONF_NAME, + CONF_TIMEOUT, + STATE_NOT_HOME, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -16,11 +23,9 @@ _LOGGER = logging.getLogger(__name__) -ATTR_DEVICE_ID = "device_id" ATTR_DISTANCE = "distance" ATTR_ROOM = "room" -CONF_DEVICE_ID = "device_id" CONF_AWAY_TIMEOUT = "away_timeout" DEFAULT_AWAY_TIMEOUT = 0 diff --git a/tests/components/mqtt_room/test_sensor.py b/tests/components/mqtt_room/test_sensor.py index ca5f9420dc567a..c3b8704c75467a 100644 --- a/tests/components/mqtt_room/test_sensor.py +++ b/tests/components/mqtt_room/test_sensor.py @@ -5,7 +5,7 @@ from homeassistant.components.mqtt import CONF_QOS, CONF_STATE_TOPIC, DEFAULT_QOS import homeassistant.components.sensor as sensor -from homeassistant.const import CONF_NAME, CONF_PLATFORM +from homeassistant.const import CONF_DEVICE_ID, CONF_NAME, CONF_PLATFORM, CONF_TIMEOUT from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -21,9 +21,6 @@ SENSOR_STATE = f"sensor.{NAME}" -CONF_DEVICE_ID = "device_id" -CONF_TIMEOUT = "timeout" - NEAR_MESSAGE = {"id": DEVICE_ID, "name": NAME, "distance": 1} FAR_MESSAGE = {"id": DEVICE_ID, "name": NAME, "distance": 10} From f4189510e9e0abe142403d59cade98b961fbaf96 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 28 Feb 2021 14:33:48 +0100 Subject: [PATCH 0843/1818] Bump builder to get generic-x86-64 nightly builds (#47164) --- azure-pipelines-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 5fe9132558207d..74aa05e58f33a7 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -14,7 +14,7 @@ schedules: always: true variables: - name: versionBuilder - value: '2020.11.0' + value: '2021.02.0' - group: docker - group: github - group: twine From 98be703d90e44efe43b1a17c7e5243e5097b00b1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 28 Feb 2021 05:41:06 -0800 Subject: [PATCH 0844/1818] Fix the updater schema (#47128) --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 9d65bb4c5d4b3f..81910db38d6d70 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ CONFIG_SCHEMA = vol.Schema( { - DOMAIN: { + vol.Optional(DOMAIN, default={}): { vol.Optional(CONF_REPORTING, default=True): cv.boolean, vol.Optional(CONF_COMPONENT_REPORTING, default=False): cv.boolean, } @@ -56,13 +56,13 @@ async def async_setup(hass, config): # This component only makes sense in release versions _LOGGER.info("Running on 'dev', only analytics will be submitted") - conf = config.get(DOMAIN, {}) - if conf.get(CONF_REPORTING): + conf = config[DOMAIN] + if conf[CONF_REPORTING]: huuid = await hass.helpers.instance_id.async_get() else: huuid = None - include_components = conf.get(CONF_COMPONENT_REPORTING) + include_components = conf[CONF_COMPONENT_REPORTING] async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From 4853a81366759da792a218b8b4e677a1464661fc Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 28 Feb 2021 10:55:14 -0500 Subject: [PATCH 0845/1818] Bump ZHA quirks to 0.0.54 (#47172) --- 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 ad2bf5f17c5488..d7bb0dbe5bc965 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.21.0", "pyserial==3.5", "pyserial-asyncio==0.5", - "zha-quirks==0.0.53", + "zha-quirks==0.0.54", "zigpy-cc==0.5.2", "zigpy-deconz==0.11.1", "zigpy==0.32.0", diff --git a/requirements_all.txt b/requirements_all.txt index 326fc962388cc6..1c5f27fd4cf07a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2367,7 +2367,7 @@ zengge==0.2 zeroconf==0.28.8 # homeassistant.components.zha -zha-quirks==0.0.53 +zha-quirks==0.0.54 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 561b877a48f1f4..923cf92c3fd6bd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1213,7 +1213,7 @@ zeep[async]==4.0.0 zeroconf==0.28.8 # homeassistant.components.zha -zha-quirks==0.0.53 +zha-quirks==0.0.54 # homeassistant.components.zha zigpy-cc==0.5.2 From da5902e4f8b986f7cc1afa47b9f309e5425e27be Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 17:48:23 +0100 Subject: [PATCH 0846/1818] Tweak Tasmota fan typing (#47175) --- homeassistant/components/tasmota/fan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/tasmota/fan.py b/homeassistant/components/tasmota/fan.py index 77b4532c001ff1..876d1a4cf60410 100644 --- a/homeassistant/components/tasmota/fan.py +++ b/homeassistant/components/tasmota/fan.py @@ -1,7 +1,5 @@ """Support for Tasmota fans.""" -from typing import Optional - from hatasmota import const as tasmota_const from homeassistant.components import fan @@ -59,7 +57,7 @@ def __init__(self, **kwds): ) @property - def speed_count(self) -> Optional[int]: + def speed_count(self) -> int: """Return the number of speeds the fan supports.""" return len(ORDERED_NAMED_FAN_SPEEDS) From 261d86f06b2ced3ce6ce3401354f95c0bff0708b Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 28 Feb 2021 19:19:50 +0100 Subject: [PATCH 0847/1818] Apply recommendations to synology_dsm (#47178) --- .../components/synology_dsm/__init__.py | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 6f0476b403c950..50921944a6d987 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -191,7 +191,7 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): try: await api.async_setup() except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.debug("async_setup_entry() - Unable to connect to DSM: %s", err) + _LOGGER.debug("Unable to connect to DSM during setup: %s", err) raise ConfigEntryNotReady from err hass.data.setdefault(DOMAIN, {}) @@ -225,9 +225,6 @@ async def async_coordinator_update_data_cameras(): async with async_timeout.timeout(10): await hass.async_add_executor_job(surveillance_station.update) except SynologyDSMAPIErrorException as err: - _LOGGER.debug( - "async_coordinator_update_data_cameras() - exception: %s", err - ) raise UpdateFailed(f"Error communicating with API: {err}") from err return { @@ -241,9 +238,6 @@ async def async_coordinator_update_data_central(): try: await api.async_update() except Exception as err: - _LOGGER.debug( - "async_coordinator_update_data_central() - exception: %s", err - ) raise UpdateFailed(f"Error communicating with API: {err}") from err return None @@ -338,15 +332,13 @@ async def service_handler(call: ServiceCall): serial = next(iter(dsm_devices)) else: _LOGGER.error( - "service_handler - more than one DSM configured, must specify one of serials %s", + "More than one DSM configured, must specify one of serials %s", sorted(dsm_devices), ) return if not dsm_device: - _LOGGER.error( - "service_handler - DSM with specified serial %s not found", serial - ) + _LOGGER.error("DSM with specified serial %s not found", serial) return _LOGGER.debug("%s DSM with serial %s", call.service, serial) @@ -409,11 +401,11 @@ async def async_setup(self): self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( - "SynoAPI.async_setup() - self._with_surveillance_station:%s", + "State of Surveillance_station during setup:%s", self._with_surveillance_station, ) - self._setup_api_requests() + self._async_setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() @@ -422,7 +414,7 @@ async def async_setup(self): def subscribe(self, api_key, unique_id): """Subscribe an entity to API fetches.""" _LOGGER.debug( - "SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id + "Subscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id ) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() @@ -432,7 +424,7 @@ def subscribe(self, api_key, unique_id): def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" _LOGGER.debug( - "SynoAPI.unsubscribe() - api_key:%s, unique_id:%s", api_key, unique_id + "Unsubscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id ) self._fetching_entities[api_key].remove(unique_id) if len(self._fetching_entities[api_key]) == 0: @@ -441,13 +433,11 @@ def unsubscribe() -> None: return unsubscribe @callback - def _setup_api_requests(self): + def _async_setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: - _LOGGER.debug( - "SynoAPI._setup_api_requests() - Entities not added yet, fetch all" - ) + _LOGGER.debug("Entities not added yet, fetch all") return # Determine if we should fetch an API @@ -470,34 +460,32 @@ def _setup_api_requests(self): # Reset not used API, information is not reset since it's used in device_info if not self._with_security: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable security") + _LOGGER.debug("Disable security api from being updated") self.dsm.reset(self.security) self.security = None if not self._with_storage: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable storage") + _LOGGER.debug("Disable storage api from being updated") self.dsm.reset(self.storage) self.storage = None if not self._with_system: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable system") + _LOGGER.debug("Disable system api from being updated") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable upgrade") + _LOGGER.debug("Disable upgrade api from being updated") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: - _LOGGER.debug("SynoAPI._setup_api_requests() - disable utilisation") + _LOGGER.debug("Disable utilisation api from being updated") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: - _LOGGER.debug( - "SynoAPI._setup_api_requests() - disable surveillance_station" - ) + _LOGGER.debug("Disable surveillance_station api from being updated") self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -508,29 +496,27 @@ def _fetch_device_configuration(self): self.network.update() if self._with_security: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch security") + _LOGGER.debug("Enable security api for updates") self.security = self.dsm.security if self._with_storage: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch storage") + _LOGGER.debug("Enable storage api for updates") self.storage = self.dsm.storage if self._with_upgrade: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch upgrade") + _LOGGER.debug("Enable upgrade api for updates") self.upgrade = self.dsm.upgrade if self._with_system: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch system") + _LOGGER.debug("Enable system api for updates") self.system = self.dsm.system if self._with_utilisation: - _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch utilisation") + _LOGGER.debug("Enable utilisation api for updates") self.utilisation = self.dsm.utilisation if self._with_surveillance_station: - _LOGGER.debug( - "SynoAPI._fetch_device_configuration() - fetch surveillance_station" - ) + _LOGGER.debug("Enable surveillance_station api for updates") self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): @@ -558,17 +544,17 @@ async def async_unload(self): async def async_update(self, now=None): """Update function for updating API information.""" - _LOGGER.debug("SynoAPI.async_update()") - self._setup_api_requests() + _LOGGER.debug("Start data update") + self._async_setup_api_requests() try: await self._hass.async_add_executor_job( self.dsm.update, self._with_information ) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.warning( - "async_update - connection error during update, fallback by reloading the entry" + "Connection error during update, fallback by reloading the entry" ) - _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) + _LOGGER.debug("Connection error during update with exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return From 5cc8302e6a3912a889aadfd12c12bfd1e6392190 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 28 Feb 2021 10:25:07 -0800 Subject: [PATCH 0848/1818] Fix flaky hls keepalive test (#47186) Remove a call to stream.start() which is issued before the test is fully setup (e.g. keepalive is not set to True, and mock calls are not registered) --- tests/components/stream/test_hls.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index c11576d257047b..b554ee6b20aae1 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -229,7 +229,6 @@ async def test_stream_keepalive(hass): stream = create_stream(hass, source) track = stream.add_provider("hls") track.num_segments = 2 - stream.start() cur_time = 0 From 6ff3eb0569c4fdec81996c39368a137b74468b47 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 12:27:36 -0600 Subject: [PATCH 0849/1818] Update HAP-python to 3.3.1 (#47180) Fixes disconnect when setting a single char fails https://github.com/ikalchev/HAP-python/compare/v3.3.0...v3.3.1 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index acc61408a4855c..4d1598c728c09e 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.0", + "HAP-python==3.3.1", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index 1c5f27fd4cf07a..673cbf4551d0da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.0 +HAP-python==3.3.1 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 923cf92c3fd6bd..57d3dcf739b81b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.0 +HAP-python==3.3.1 # homeassistant.components.flick_electric PyFlick==0.0.2 From 19cd29affa5690692c375a81af82288a244e9de1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 21:19:27 +0100 Subject: [PATCH 0850/1818] Fix MQTT trigger where wanted payload may be parsed as an integer (#47162) --- .../components/mqtt/device_trigger.py | 12 ++- homeassistant/components/mqtt/trigger.py | 6 +- tests/components/mqtt/test_device_trigger.py | 75 +++++++++++++++++++ tests/components/mqtt/test_trigger.py | 25 +++++++ 4 files changed, 114 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 8969072553cd5d..d6e2ee0fc65721 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -13,6 +13,7 @@ CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE, + CONF_VALUE_TEMPLATE, ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -66,10 +67,11 @@ { vol.Required(CONF_AUTOMATION_TYPE): str, vol.Required(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_PAYLOAD, default=None): vol.Any(None, cv.string), - vol.Required(CONF_TYPE): cv.string, vol.Required(CONF_SUBTYPE): cv.string, + vol.Required(CONF_TOPIC): cv.string, + vol.Required(CONF_TYPE): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE, default=None): vol.Any(None, cv.string), }, validate_device_has_at_least_one_identifier, ) @@ -96,6 +98,8 @@ async def async_attach_trigger(self): } if self.trigger.payload: mqtt_config[CONF_PAYLOAD] = self.trigger.payload + if self.trigger.value_template: + mqtt_config[CONF_VALUE_TEMPLATE] = self.trigger.value_template mqtt_config = mqtt_trigger.TRIGGER_SCHEMA(mqtt_config) if self.remove: @@ -121,6 +125,7 @@ class Trigger: subtype: str = attr.ib() topic: str = attr.ib() type: str = attr.ib() + value_template: str = attr.ib() trigger_instances: List[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): @@ -153,6 +158,7 @@ async def update_trigger(self, config, discovery_hash, remove_signal): self.qos = config[CONF_QOS] topic_changed = self.topic != config[CONF_TOPIC] self.topic = config[CONF_TOPIC] + self.value_template = config[CONF_VALUE_TEMPLATE] # Unsubscribe+subscribe if this trigger is in use and topic has changed # If topic is same unsubscribe+subscribe will execute in the wrong order @@ -245,6 +251,7 @@ async def discovery_update(payload): payload=config[CONF_PAYLOAD], qos=config[CONF_QOS], remove_signal=remove_signal, + value_template=config[CONF_VALUE_TEMPLATE], ) else: await hass.data[DEVICE_TRIGGERS][discovery_id].update_trigger( @@ -325,6 +332,7 @@ async def async_attach_trigger( topic=None, payload=None, qos=None, + value_template=None, ) return await hass.data[DEVICE_TRIGGERS][discovery_id].add_trigger( action, automation_info diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 459adabd418fa1..82f7885b85d4d4 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -48,11 +48,13 @@ async def async_attach_trigger(hass, config, action, automation_info): template.attach(hass, wanted_payload) if wanted_payload: - wanted_payload = wanted_payload.async_render(variables, limited=True) + wanted_payload = wanted_payload.async_render( + variables, limited=True, parse_result=False + ) template.attach(hass, topic) if isinstance(topic, template.Template): - topic = topic.async_render(variables, limited=True) + topic = topic.async_render(variables, limited=True, parse_result=False) topic = mqtt.util.valid_subscribe_topic(topic) template.attach(hass, value_template) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index f200de6a27418f..210dac19e0c62a 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -290,6 +290,81 @@ async def test_if_fires_on_mqtt_message(hass, device_reg, calls, mqtt_mock): assert calls[1].data["some"] == "long_press" +async def test_if_fires_on_mqtt_message_template(hass, device_reg, calls, mqtt_mock): + """Test triggers firing.""" + data1 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + " \"payload\": \"{{ 'foo_press'|regex_replace('foo', 'short') }}\"," + ' "topic": "foobar/triggers/button{{ sqrt(16)|round }}",' + ' "type": "button_short_press",' + ' "subtype": "button_1",' + ' "value_template": "{{ value_json.button }}"}' + ) + data2 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + " \"payload\": \"{{ 'foo_press'|regex_replace('foo', 'long') }}\"," + ' "topic": "foobar/triggers/button{{ sqrt(16)|round }}",' + ' "type": "button_long_press",' + ' "subtype": "button_2",' + ' "value_template": "{{ value_json.button }}"}' + ) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2) + await hass.async_block_till_done() + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla1", + "type": "button_short_press", + "subtype": "button_1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("short_press")}, + }, + }, + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla2", + "type": "button_1", + "subtype": "button_long_press", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("long_press")}, + }, + }, + ] + }, + ) + + # Fake short press. + async_fire_mqtt_message(hass, "foobar/triggers/button4", '{"button":"short_press"}') + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "short_press" + + # Fake long press. + async_fire_mqtt_message(hass, "foobar/triggers/button4", '{"button":"long_press"}') + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["some"] == "long_press" + + async def test_if_fires_on_mqtt_message_late_discover( hass, device_reg, calls, mqtt_mock ): diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 23078b9ba23df6..d0a86e08655a40 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -81,6 +81,31 @@ async def test_if_fires_on_topic_and_payload_match(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_topic_and_payload_match2(hass, calls): + """Test if message is fired on topic and payload match. + + Make sure a payload which would render as a non string can still be matched. + """ + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "mqtt", + "topic": "test-topic", + "payload": "0", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + async_fire_mqtt_message(hass, "test-topic", "0") + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_on_templated_topic_and_payload_match(hass, calls): """Test if message is fired on templated topic and payload match.""" assert await async_setup_component( From 72b82449d885323d707513c15bdeaa9d955fc899 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 21:20:09 +0100 Subject: [PATCH 0851/1818] Update MQTT device triggers with support for templates (#47142) From 807bf15ff35d19599145991b22fe4eb3bc428f4c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 13:28:52 -0800 Subject: [PATCH 0852/1818] Use async_capture_events to avoid running in executor (#47111) --- tests/components/alexa/test_smart_home.py | 10 ++----- tests/components/automation/test_init.py | 12 ++++---- tests/components/demo/test_notify.py | 6 ++-- .../google_assistant/test_smart_home.py | 29 +++++++++---------- .../components/google_assistant/test_trait.py | 5 ++-- tests/components/homeassistant/test_scene.py | 7 ++--- tests/components/homekit/conftest.py | 9 ++---- tests/components/shelly/conftest.py | 12 ++++---- 8 files changed, 39 insertions(+), 51 deletions(-) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 657bc407fb0b96..c018e07c2648b0 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -26,7 +26,7 @@ import homeassistant.components.vacuum as vacuum from homeassistant.config import async_process_ha_core_config from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.core import Context, callback +from homeassistant.core import Context from homeassistant.helpers import entityfilter from homeassistant.setup import async_setup_component @@ -42,17 +42,13 @@ reported_properties, ) -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service @pytest.fixture def events(hass): """Fixture that catches alexa events.""" - events = [] - hass.bus.async_listen( - smart_home.EVENT_ALEXA_SMART_HOME, callback(lambda e: events.append(e)) - ) - yield events + return async_capture_events(hass, smart_home.EVENT_ALEXA_SMART_HOME) @pytest.fixture diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 3e498b52a08910..91531481a993c8 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -30,7 +30,12 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import assert_setup_component, async_mock_service, mock_restore_cache +from tests.common import ( + assert_setup_component, + async_capture_events, + async_mock_service, + mock_restore_cache, +) from tests.components.logbook.test_init import MockLazyEventPartialState @@ -496,10 +501,7 @@ async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_onl assert len(calls) == 1 assert calls[0].data.get("event") == "test_event" - test_reload_event = [] - hass.bus.async_listen( - EVENT_AUTOMATION_RELOADED, lambda event: test_reload_event.append(event) - ) + test_reload_event = async_capture_events(hass, EVENT_AUTOMATION_RELOADED) with patch( "homeassistant.config.load_yaml_config_file", diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index 7c7f83312dda3c..153f065235cc20 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -12,7 +12,7 @@ from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component -from tests.common import assert_setup_component +from tests.common import assert_setup_component, async_capture_events CONFIG = {notify.DOMAIN: {"platform": "demo"}} @@ -20,9 +20,7 @@ @pytest.fixture def events(hass): """Fixture that catches notify events.""" - events = [] - hass.bus.async_listen(demo.EVENT_NOTIFY, callback(lambda e: events.append(e))) - yield events + return async_capture_events(hass, demo.EVENT_NOTIFY) @pytest.fixture diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 9c8f9a483388d7..9531602ef0c512 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -30,7 +30,12 @@ from . import BASIC_CONFIG, MockConfig -from tests.common import mock_area_registry, mock_device_registry, mock_registry +from tests.common import ( + async_capture_events, + mock_area_registry, + mock_device_registry, + mock_registry, +) REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -77,8 +82,7 @@ async def test_sync_message(hass): }, ) - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, @@ -192,8 +196,7 @@ async def test_sync_in_area(area_on_device, hass, registries): config = MockConfig(should_expose=lambda _: True, entity_config={}) - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, @@ -295,8 +298,7 @@ async def test_query_message(hass): light3.entity_id = "light.color_temp_light" await light3.async_update_ha_state() - events = [] - hass.bus.async_listen(EVENT_QUERY_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_QUERY_RECEIVED) result = await sh.async_handle_message( hass, @@ -387,11 +389,8 @@ async def test_execute(hass): "light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True ) - events = [] - hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append) - - service_events = [] - hass.bus.async_listen(EVENT_CALL_SERVICE, service_events.append) + events = async_capture_events(hass, EVENT_COMMAND_RECEIVED) + service_events = async_capture_events(hass, EVENT_CALL_SERVICE) result = await sh.async_handle_message( hass, @@ -570,8 +569,7 @@ async def test_raising_error_trait(hass): {ATTR_MIN_TEMP: 15, ATTR_MAX_TEMP: 30, ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}, ) - events = [] - hass.bus.async_listen(EVENT_COMMAND_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_COMMAND_RECEIVED) await hass.async_block_till_done() result = await sh.async_handle_message( @@ -660,8 +658,7 @@ async def test_unavailable_state_does_sync(hass): light._available = False # pylint: disable=protected-access await light.async_update_ha_state() - events = [] - hass.bus.async_listen(EVENT_SYNC_RECEIVED, events.append) + events = async_capture_events(hass, EVENT_SYNC_RECEIVED) result = await sh.async_handle_message( hass, diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index a9b1e9a97fb452..ba1890205130d3 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -54,7 +54,7 @@ from . import BASIC_CONFIG, MockConfig -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -84,8 +84,7 @@ async def test_brightness_light(hass): assert trt.query_attributes() == {"brightness": 95} - events = [] - hass.bus.async_listen(EVENT_CALL_SERVICE, events.append) + events = async_capture_events(hass, EVENT_CALL_SERVICE) calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON) await trt.execute( diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 3098543271833a..610bc371b25102 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -8,17 +8,14 @@ from homeassistant.components.homeassistant.scene import EVENT_SCENE_RELOADED from homeassistant.setup import async_setup_component -from tests.common import async_mock_service +from tests.common import async_capture_events, async_mock_service async def test_reload_config_service(hass): """Test the reload config service.""" assert await async_setup_component(hass, "scene", {}) - test_reloaded_event = [] - hass.bus.async_listen( - EVENT_SCENE_RELOADED, lambda event: test_reloaded_event.append(event) - ) + test_reloaded_event = async_capture_events(hass, EVENT_SCENE_RELOADED) with patch( "homeassistant.config.load_yaml_config_file", diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index ac51c4e636875a..228b5f078376be 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -5,7 +5,8 @@ import pytest from homeassistant.components.homekit.const import EVENT_HOMEKIT_CHANGED -from homeassistant.core import callback as ha_callback + +from tests.common import async_capture_events @pytest.fixture @@ -24,8 +25,4 @@ def hk_driver(loop): @pytest.fixture def events(hass): """Yield caught homekit_changed events.""" - events = [] - hass.bus.async_listen( - EVENT_HOMEKIT_CHANGED, ha_callback(lambda e: events.append(e)) - ) - yield events + return async_capture_events(hass, EVENT_HOMEKIT_CHANGED) diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 7e7bd0688422df..51659cf77361d8 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -10,10 +10,14 @@ DOMAIN, EVENT_SHELLY_CLICK, ) -from homeassistant.core import callback as ha_callback from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, async_mock_service, mock_device_registry +from tests.common import ( + MockConfigEntry, + async_capture_events, + async_mock_service, + mock_device_registry, +) MOCK_SETTINGS = { "name": "Test name", @@ -81,9 +85,7 @@ def calls(hass): @pytest.fixture def events(hass): """Yield caught shelly_click events.""" - ha_events = [] - hass.bus.async_listen(EVENT_SHELLY_CLICK, ha_callback(ha_events.append)) - yield ha_events + return async_capture_events(hass, EVENT_SHELLY_CLICK) @pytest.fixture From 505ca07c4e2c63f15792918e9b2d9ac7a6f9768a Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 27 Feb 2021 00:28:16 +0200 Subject: [PATCH 0853/1818] Fix Shelly RGBW (#47116) --- homeassistant/components/shelly/const.py | 4 +- homeassistant/components/shelly/light.py | 47 ++++++++++-------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 9d1c333b2017a6..4fda656e7b4463 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -74,5 +74,5 @@ # Kelvin value for colorTemp KELVIN_MAX_VALUE = 6500 -KELVIN_MIN_VALUE = 2700 -KELVIN_MIN_VALUE_SHBLB_1 = 3000 +KELVIN_MIN_VALUE_WHITE = 2700 +KELVIN_MIN_VALUE_COLOR = 3000 diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 848ef9903404b0..0379bfec1cfd48 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -28,20 +28,13 @@ DATA_CONFIG_ENTRY, DOMAIN, KELVIN_MAX_VALUE, - KELVIN_MIN_VALUE, - KELVIN_MIN_VALUE_SHBLB_1, + KELVIN_MIN_VALUE_COLOR, + KELVIN_MIN_VALUE_WHITE, ) from .entity import ShellyBlockEntity from .utils import async_remove_shelly_entity -def min_kelvin(model: str): - """Kelvin (min) for colorTemp.""" - if model in ["SHBLB-1"]: - return KELVIN_MIN_VALUE_SHBLB_1 - return KELVIN_MIN_VALUE - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up lights for device.""" wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP] @@ -76,6 +69,8 @@ def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None: self.control_result = None self.mode_result = None self._supported_features = 0 + self._min_kelvin = KELVIN_MIN_VALUE_WHITE + self._max_kelvin = KELVIN_MAX_VALUE if hasattr(block, "brightness") or hasattr(block, "gain"): self._supported_features |= SUPPORT_BRIGHTNESS @@ -85,6 +80,7 @@ def __init__(self, wrapper: ShellyDeviceWrapper, block: Block) -> None: self._supported_features |= SUPPORT_WHITE_VALUE if hasattr(block, "red") and hasattr(block, "green") and hasattr(block, "blue"): self._supported_features |= SUPPORT_COLOR + self._min_kelvin = KELVIN_MIN_VALUE_COLOR @property def supported_features(self) -> int: @@ -168,22 +164,19 @@ def color_temp(self) -> Optional[int]: else: color_temp = self.block.colorTemp - # If you set DUO to max mireds in Shelly app, 2700K, - # It reports 0 temp - if color_temp == 0: - return min_kelvin(self.wrapper.model) + color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) return int(color_temperature_kelvin_to_mired(color_temp)) @property def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" - return int(color_temperature_kelvin_to_mired(KELVIN_MAX_VALUE)) + return int(color_temperature_kelvin_to_mired(self._max_kelvin)) @property def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" - return int(color_temperature_kelvin_to_mired(min_kelvin(self.wrapper.model))) + return int(color_temperature_kelvin_to_mired(self._min_kelvin)) async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" @@ -192,6 +185,7 @@ async def async_turn_on(self, **kwargs) -> None: self.async_write_ha_state() return + set_mode = None params = {"turn": "on"} if ATTR_BRIGHTNESS in kwargs: tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100) @@ -201,27 +195,26 @@ async def async_turn_on(self, **kwargs) -> None: params["brightness"] = tmp_brightness if ATTR_COLOR_TEMP in kwargs: color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - color_temp = min( - KELVIN_MAX_VALUE, max(min_kelvin(self.wrapper.model), color_temp) - ) + color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp)) # Color temperature change - used only in white mode, switch device mode to white - if self.mode == "color": - self.mode_result = await self.wrapper.device.switch_light_mode("white") - params["red"] = params["green"] = params["blue"] = 255 + set_mode = "white" + params["red"] = params["green"] = params["blue"] = 255 params["temp"] = int(color_temp) - elif ATTR_HS_COLOR in kwargs: + if ATTR_HS_COLOR in kwargs: red, green, blue = color_hs_to_RGB(*kwargs[ATTR_HS_COLOR]) # Color channels change - used only in color mode, switch device mode to color - if self.mode == "white": - self.mode_result = await self.wrapper.device.switch_light_mode("color") + set_mode = "color" params["red"] = red params["green"] = green params["blue"] = blue - elif ATTR_WHITE_VALUE in kwargs: + if ATTR_WHITE_VALUE in kwargs: # White channel change - used only in color mode, switch device mode device to color - if self.mode == "white": - self.mode_result = await self.wrapper.device.switch_light_mode("color") + set_mode = "color" params["white"] = int(kwargs[ATTR_WHITE_VALUE]) + + if set_mode and self.mode != set_mode: + self.mode_result = await self.wrapper.device.switch_light_mode(set_mode) + self.control_result = await self.block.set_state(**params) self.async_write_ha_state() From dd4f8bf4b4d569b6d4ac3f49a7519826cfb6e69d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 18:33:31 -0600 Subject: [PATCH 0854/1818] Handle lutron_caseta fan speed being none (#47120) --- homeassistant/components/lutron_caseta/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 330ff81d1d2cff..57b87b18320f98 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -44,6 +44,8 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): @property def percentage(self) -> str: """Return the current speed percentage.""" + if self._device["fan_speed"] is None: + return None return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) From 2b0f6716b32a5f2d8d737f36a0fee2477b50f655 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Feb 2021 18:33:13 -0600 Subject: [PATCH 0855/1818] Provide a human readable exception for the percentage util (#47121) --- homeassistant/util/percentage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index 10a72a85dff438..949af7dbb3235c 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -19,7 +19,7 @@ def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: """ if item not in ordered_list: - raise ValueError + raise ValueError(f'The item "{item}"" is not in "{ordered_list}"') list_len = len(ordered_list) list_position = ordered_list.index(item) + 1 @@ -42,7 +42,7 @@ def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> """ list_len = len(ordered_list) if not list_len: - raise ValueError + raise ValueError("The ordered list is empty") for offset, speed in enumerate(ordered_list): list_position = offset + 1 From e65b2231ba4e2f6adf02d7118e56941b709bd917 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 27 Feb 2021 01:32:51 +0100 Subject: [PATCH 0856/1818] Update frontend to 20210226.0 (#47123) --- 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 e8e9c44ae78a76..01f1c72f8d6092 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==20210225.0" + "home-assistant-frontend==20210226.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 10cf300b76b32e..9506171303bb57 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index d0437c6f9bcc66..e6fb8b88e4e52a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3eab63288a3c9a..e3fc888d43f437 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210225.0 +home-assistant-frontend==20210226.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From d9d979d50e948c38f4a4f0289563e1e9fd75f70f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 28 Feb 2021 05:41:06 -0800 Subject: [PATCH 0857/1818] Fix the updater schema (#47128) --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 9d65bb4c5d4b3f..81910db38d6d70 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ CONFIG_SCHEMA = vol.Schema( { - DOMAIN: { + vol.Optional(DOMAIN, default={}): { vol.Optional(CONF_REPORTING, default=True): cv.boolean, vol.Optional(CONF_COMPONENT_REPORTING, default=False): cv.boolean, } @@ -56,13 +56,13 @@ async def async_setup(hass, config): # This component only makes sense in release versions _LOGGER.info("Running on 'dev', only analytics will be submitted") - conf = config.get(DOMAIN, {}) - if conf.get(CONF_REPORTING): + conf = config[DOMAIN] + if conf[CONF_REPORTING]: huuid = await hass.helpers.instance_id.async_get() else: huuid = None - include_components = conf.get(CONF_COMPONENT_REPORTING) + include_components = conf[CONF_COMPONENT_REPORTING] async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From 104d5c510fefe5e64c4c704eab7f1fead8c33047 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 28 Feb 2021 21:19:27 +0100 Subject: [PATCH 0858/1818] Fix MQTT trigger where wanted payload may be parsed as an integer (#47162) --- .../components/mqtt/device_trigger.py | 12 ++- homeassistant/components/mqtt/trigger.py | 6 +- tests/components/mqtt/test_device_trigger.py | 75 +++++++++++++++++++ tests/components/mqtt/test_trigger.py | 25 +++++++ 4 files changed, 114 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 8969072553cd5d..d6e2ee0fc65721 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -13,6 +13,7 @@ CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE, + CONF_VALUE_TEMPLATE, ) from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -66,10 +67,11 @@ { vol.Required(CONF_AUTOMATION_TYPE): str, vol.Required(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_PAYLOAD, default=None): vol.Any(None, cv.string), - vol.Required(CONF_TYPE): cv.string, vol.Required(CONF_SUBTYPE): cv.string, + vol.Required(CONF_TOPIC): cv.string, + vol.Required(CONF_TYPE): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE, default=None): vol.Any(None, cv.string), }, validate_device_has_at_least_one_identifier, ) @@ -96,6 +98,8 @@ async def async_attach_trigger(self): } if self.trigger.payload: mqtt_config[CONF_PAYLOAD] = self.trigger.payload + if self.trigger.value_template: + mqtt_config[CONF_VALUE_TEMPLATE] = self.trigger.value_template mqtt_config = mqtt_trigger.TRIGGER_SCHEMA(mqtt_config) if self.remove: @@ -121,6 +125,7 @@ class Trigger: subtype: str = attr.ib() topic: str = attr.ib() type: str = attr.ib() + value_template: str = attr.ib() trigger_instances: List[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): @@ -153,6 +158,7 @@ async def update_trigger(self, config, discovery_hash, remove_signal): self.qos = config[CONF_QOS] topic_changed = self.topic != config[CONF_TOPIC] self.topic = config[CONF_TOPIC] + self.value_template = config[CONF_VALUE_TEMPLATE] # Unsubscribe+subscribe if this trigger is in use and topic has changed # If topic is same unsubscribe+subscribe will execute in the wrong order @@ -245,6 +251,7 @@ async def discovery_update(payload): payload=config[CONF_PAYLOAD], qos=config[CONF_QOS], remove_signal=remove_signal, + value_template=config[CONF_VALUE_TEMPLATE], ) else: await hass.data[DEVICE_TRIGGERS][discovery_id].update_trigger( @@ -325,6 +332,7 @@ async def async_attach_trigger( topic=None, payload=None, qos=None, + value_template=None, ) return await hass.data[DEVICE_TRIGGERS][discovery_id].add_trigger( action, automation_info diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 459adabd418fa1..82f7885b85d4d4 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -48,11 +48,13 @@ async def async_attach_trigger(hass, config, action, automation_info): template.attach(hass, wanted_payload) if wanted_payload: - wanted_payload = wanted_payload.async_render(variables, limited=True) + wanted_payload = wanted_payload.async_render( + variables, limited=True, parse_result=False + ) template.attach(hass, topic) if isinstance(topic, template.Template): - topic = topic.async_render(variables, limited=True) + topic = topic.async_render(variables, limited=True, parse_result=False) topic = mqtt.util.valid_subscribe_topic(topic) template.attach(hass, value_template) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index f200de6a27418f..210dac19e0c62a 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -290,6 +290,81 @@ async def test_if_fires_on_mqtt_message(hass, device_reg, calls, mqtt_mock): assert calls[1].data["some"] == "long_press" +async def test_if_fires_on_mqtt_message_template(hass, device_reg, calls, mqtt_mock): + """Test triggers firing.""" + data1 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + " \"payload\": \"{{ 'foo_press'|regex_replace('foo', 'short') }}\"," + ' "topic": "foobar/triggers/button{{ sqrt(16)|round }}",' + ' "type": "button_short_press",' + ' "subtype": "button_1",' + ' "value_template": "{{ value_json.button }}"}' + ) + data2 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + " \"payload\": \"{{ 'foo_press'|regex_replace('foo', 'long') }}\"," + ' "topic": "foobar/triggers/button{{ sqrt(16)|round }}",' + ' "type": "button_long_press",' + ' "subtype": "button_2",' + ' "value_template": "{{ value_json.button }}"}' + ) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla2/config", data2) + await hass.async_block_till_done() + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla1", + "type": "button_short_press", + "subtype": "button_1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("short_press")}, + }, + }, + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla2", + "type": "button_1", + "subtype": "button_long_press", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("long_press")}, + }, + }, + ] + }, + ) + + # Fake short press. + async_fire_mqtt_message(hass, "foobar/triggers/button4", '{"button":"short_press"}') + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == "short_press" + + # Fake long press. + async_fire_mqtt_message(hass, "foobar/triggers/button4", '{"button":"long_press"}') + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data["some"] == "long_press" + + async def test_if_fires_on_mqtt_message_late_discover( hass, device_reg, calls, mqtt_mock ): diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 23078b9ba23df6..d0a86e08655a40 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -81,6 +81,31 @@ async def test_if_fires_on_topic_and_payload_match(hass, calls): assert len(calls) == 1 +async def test_if_fires_on_topic_and_payload_match2(hass, calls): + """Test if message is fired on topic and payload match. + + Make sure a payload which would render as a non string can still be matched. + """ + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "mqtt", + "topic": "test-topic", + "payload": "0", + }, + "action": {"service": "test.automation"}, + } + }, + ) + + async_fire_mqtt_message(hass, "test-topic", "0") + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_fires_on_templated_topic_and_payload_match(hass, calls): """Test if message is fired on templated topic and payload match.""" assert await async_setup_component( From 552da0327eaa32917d26f75940bad8cd36f80246 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 28 Feb 2021 14:33:48 +0100 Subject: [PATCH 0859/1818] Bump builder to get generic-x86-64 nightly builds (#47164) --- azure-pipelines-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 5fe9132558207d..74aa05e58f33a7 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -14,7 +14,7 @@ schedules: always: true variables: - name: versionBuilder - value: '2020.11.0' + value: '2021.02.0' - group: docker - group: github - group: twine From db098d90ddc87ec535c54953a55d9077ce0c598e Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 28 Feb 2021 10:55:14 -0500 Subject: [PATCH 0860/1818] Bump ZHA quirks to 0.0.54 (#47172) --- 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 ad2bf5f17c5488..d7bb0dbe5bc965 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.21.0", "pyserial==3.5", "pyserial-asyncio==0.5", - "zha-quirks==0.0.53", + "zha-quirks==0.0.54", "zigpy-cc==0.5.2", "zigpy-deconz==0.11.1", "zigpy==0.32.0", diff --git a/requirements_all.txt b/requirements_all.txt index e6fb8b88e4e52a..6dd457f792fae9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2367,7 +2367,7 @@ zengge==0.2 zeroconf==0.28.8 # homeassistant.components.zha -zha-quirks==0.0.53 +zha-quirks==0.0.54 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e3fc888d43f437..1ae58db2865398 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1213,7 +1213,7 @@ zeep[async]==4.0.0 zeroconf==0.28.8 # homeassistant.components.zha -zha-quirks==0.0.53 +zha-quirks==0.0.54 # homeassistant.components.zha zigpy-cc==0.5.2 From e93868f85b016ecadb452a3c02cb22917d80c8c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 12:27:36 -0600 Subject: [PATCH 0861/1818] Update HAP-python to 3.3.1 (#47180) Fixes disconnect when setting a single char fails https://github.com/ikalchev/HAP-python/compare/v3.3.0...v3.3.1 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index acc61408a4855c..4d1598c728c09e 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.0", + "HAP-python==3.3.1", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index 6dd457f792fae9..010cb3d49e19d9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.0 +HAP-python==3.3.1 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1ae58db2865398..5e8df0c89fbde3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.0 +HAP-python==3.3.1 # homeassistant.components.flick_electric PyFlick==0.0.2 From 6887474ddc4f7525d446b58cd04e79b1654f6102 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 28 Feb 2021 20:22:46 +0000 Subject: [PATCH 0862/1818] Bumped version to 2021.3.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 57308e6eed052b..c6cccb49d434bf 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 1c9a9be197dae67f0428d2599fa47f882cc5ef81 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 28 Feb 2021 21:25:40 +0100 Subject: [PATCH 0863/1818] Fix Xiaomi Miio discovery (#47134) --- .../components/xiaomi_miio/config_flow.py | 17 ++++++++---- .../components/xiaomi_miio/strings.json | 26 +++++++++---------- .../xiaomi_miio/test_config_flow.py | 14 +++++----- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index c9c363b61eb9f9..9eaf4c1effa98e 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -1,5 +1,6 @@ """Config flow to configure Xiaomi Miio.""" import logging +from re import search import voluptuous as vol @@ -24,7 +25,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_GATEWAY_NAME = "Xiaomi Gateway" -DEFAULT_DEVICE_NAME = "Xiaomi Device" DEVICE_SETTINGS = { vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), @@ -57,14 +57,21 @@ async def async_step_zeroconf(self, discovery_info): name = discovery_info.get("name") self.host = discovery_info.get("host") self.mac = discovery_info.get("properties", {}).get("mac") + if self.mac is None: + poch = discovery_info.get("properties", {}).get("poch", "") + result = search(r"mac=\w+", poch) + if result is not None: + self.mac = result.group(0).split("=")[1] if not name or not self.host or not self.mac: return self.async_abort(reason="not_xiaomi_miio") + self.mac = format_mac(self.mac) + # Check which device is discovered. for gateway_model in MODELS_GATEWAY: if name.startswith(gateway_model.replace(".", "-")): - unique_id = format_mac(self.mac) + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) @@ -76,12 +83,12 @@ async def async_step_zeroconf(self, discovery_info): for device_model in MODELS_ALL_DEVICES: if name.startswith(device_model.replace(".", "-")): - unique_id = format_mac(self.mac) + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) self.context.update( - {"title_placeholders": {"name": f"Miio Device {self.host}"}} + {"title_placeholders": {"name": f"{device_model} {self.host}"}} ) return await self.async_step_device() @@ -133,7 +140,7 @@ async def async_step_device(self, user_input=None): ) # Setup all other Miio Devices - name = user_input.get(CONF_NAME, DEFAULT_DEVICE_NAME) + name = user_input.get(CONF_NAME, model) for device_model in MODELS_ALL_DEVICES: if model.startswith(device_model): diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 90710baebca73d..e3d9376bc318c7 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -1,24 +1,24 @@ { "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown_device": "The device model is not known, not able to setup the device using config flow." + }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway", - "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", "data": { "host": "[%key:common::config_flow::data::ip%]", - "token": "[%key:common::config_flow::data::api_token%]", - "model": "Device model (Optional)" - } + "model": "Device model (Optional)", + "token": "[%key:common::config_flow::data::api_token%]" + }, + "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", + "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown_device": "The device model is not known, not able to setup the device using config flow." - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" } } } diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index f4f7b5e2b46fe5..f53fe6e40b4867 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -6,10 +6,7 @@ from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.components.xiaomi_miio import const -from homeassistant.components.xiaomi_miio.config_flow import ( - DEFAULT_DEVICE_NAME, - DEFAULT_GATEWAY_NAME, -) +from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN ZEROCONF_NAME = "name" @@ -21,6 +18,7 @@ TEST_NAME = "Test_Gateway" TEST_MODEL = const.MODELS_GATEWAY[0] TEST_MAC = "ab:cd:ef:gh:ij:kl" +TEST_MAC_DEVICE = "abcdefghijkl" TEST_GATEWAY_ID = TEST_MAC TEST_HARDWARE_VERSION = "AB123" TEST_FIRMWARE_VERSION = "1.2.3_456" @@ -294,7 +292,7 @@ async def test_config_flow_step_device_manual_model_succes(hass): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == overwrite_model assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, @@ -328,7 +326,7 @@ async def config_flow_device_success(hass, model_to_test): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == model_to_test assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, @@ -346,7 +344,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): data={ zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: zeroconf_name_to_test, - ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, + ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"}, }, ) @@ -368,7 +366,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == model_to_test assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, From b8c8fe0820dd37083430a727b85375dec0e162e2 Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Sun, 28 Feb 2021 18:21:04 -0500 Subject: [PATCH 0864/1818] Update AlarmDecoder dependency (#46841) --- homeassistant/components/alarmdecoder/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index 1697858718d273..c3e72e407c2620 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -2,7 +2,7 @@ "domain": "alarmdecoder", "name": "AlarmDecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder", - "requirements": ["adext==0.3"], + "requirements": ["adext==0.4.1"], "codeowners": ["@ajschmidt8"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 673cbf4551d0da..277d42022e2a7c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -108,7 +108,7 @@ adafruit-circuitpython-mcp230xx==2.2.2 adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder -adext==0.3 +adext==0.4.1 # homeassistant.components.adguard adguardhome==0.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 57d3dcf739b81b..9ace25468ef97e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -48,7 +48,7 @@ accuweather==0.1.0 adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder -adext==0.3 +adext==0.4.1 # homeassistant.components.adguard adguardhome==0.4.2 From bab66a5cb968bcc7c6ef7787c9dc645885569429 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 28 Feb 2021 15:48:30 -0800 Subject: [PATCH 0865/1818] Remove turn_on and turn_off from SmartTub pump switches (#47184) --- homeassistant/components/smarttub/switch.py | 14 ------------- tests/components/smarttub/test_switch.py | 22 +++++++-------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index 7e4c83f6feb9d4..736611e54115ee 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -61,20 +61,6 @@ def is_on(self) -> bool: """Return True if the pump is on.""" return self.pump.state != SpaPump.PumpState.OFF - async def async_turn_on(self, **kwargs) -> None: - """Turn the pump on.""" - - # the API only supports toggling - if not self.is_on: - await self.async_toggle() - - async def async_turn_off(self, **kwargs) -> None: - """Turn the pump off.""" - - # the API only supports toggling - if self.is_on: - await self.async_toggle() - async def async_toggle(self, **kwargs) -> None: """Toggle the pump on or off.""" async with async_timeout.timeout(API_TIMEOUT): diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index 8750bf79747fe3..1ef8463119675f 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -18,21 +18,13 @@ async def test_pumps(spa, setup_entry, hass): assert state is not None if pump.state == SpaPump.PumpState.OFF: assert state.state == "off" - - await hass.services.async_call( - "switch", - "turn_on", - {"entity_id": entity_id}, - blocking=True, - ) - pump.toggle.assert_called() else: assert state.state == "on" - await hass.services.async_call( - "switch", - "turn_off", - {"entity_id": entity_id}, - blocking=True, - ) - pump.toggle.assert_called() + await hass.services.async_call( + "switch", + "toggle", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From c6223873f42df945f3ad792d9826784bd0c67272 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 28 Feb 2021 15:49:25 -0800 Subject: [PATCH 0866/1818] Move SmartTub climate constants to module level (#47190) --- homeassistant/components/smarttub/climate.py | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 66c03a22e1f77c..3e18bc12672458 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -23,6 +23,19 @@ PRESET_DAY = "day" +PRESET_MODES = { + Spa.HeatMode.AUTO: PRESET_NONE, + Spa.HeatMode.ECONOMY: PRESET_ECO, + Spa.HeatMode.DAY: PRESET_DAY, +} + +HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} + +HVAC_ACTIONS = { + "OFF": CURRENT_HVAC_IDLE, + "ON": CURRENT_HVAC_HEAT, +} + async def async_setup_entry(hass, entry, async_add_entities): """Set up climate entity for the thermostat in the tub.""" @@ -39,19 +52,6 @@ async def async_setup_entry(hass, entry, async_add_entities): class SmartTubThermostat(SmartTubEntity, ClimateEntity): """The target water temperature for the spa.""" - PRESET_MODES = { - Spa.HeatMode.AUTO: PRESET_NONE, - Spa.HeatMode.ECONOMY: PRESET_ECO, - Spa.HeatMode.DAY: PRESET_DAY, - } - - HEAT_MODES = {v: k for k, v in PRESET_MODES.items()} - - HVAC_ACTIONS = { - "OFF": CURRENT_HVAC_IDLE, - "ON": CURRENT_HVAC_HEAT, - } - def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__(coordinator, spa, "thermostat") @@ -64,7 +64,7 @@ def temperature_unit(self): @property def hvac_action(self): """Return the current running hvac operation.""" - return self.HVAC_ACTIONS.get(self.spa_status.heater) + return HVAC_ACTIONS.get(self.spa_status.heater) @property def hvac_modes(self): @@ -112,12 +112,12 @@ def supported_features(self): @property def preset_mode(self): """Return the current preset mode.""" - return self.PRESET_MODES[self.spa_status.heat_mode] + return PRESET_MODES[self.spa_status.heat_mode] @property def preset_modes(self): """Return the available preset modes.""" - return list(self.PRESET_MODES.values()) + return list(PRESET_MODES.values()) @property def current_temperature(self): @@ -137,6 +137,6 @@ async def async_set_temperature(self, **kwargs): async def async_set_preset_mode(self, preset_mode: str): """Activate the specified preset mode.""" - heat_mode = self.HEAT_MODES[preset_mode] + heat_mode = HEAT_MODES[preset_mode] await self.spa.set_heat_mode(heat_mode) await self.coordinator.async_refresh() From 1d7660f0716b4212a80a853da8e7b4cddaa4cef3 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Sun, 28 Feb 2021 15:51:43 -0800 Subject: [PATCH 0867/1818] Explain why should_pool is True initially for wemo (#47191) --- homeassistant/components/wemo/entity.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 4fac786af9a69e..d9b4b0548f86fd 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -142,8 +142,15 @@ def is_on(self) -> bool: def should_poll(self) -> bool: """Return True if the the device requires local polling, False otherwise. + It is desirable to allow devices to enter periods of polling when the + callback subscription (device push) is not working. To work with the + entity platform polling logic, this entity needs to report True for + should_poll initially. That is required to cause the entity platform + logic to start the polling task (see the discussion in #47182). + Polling can be disabled if three conditions are met: - 1. The device has polled to get the initial state (self._has_polled). + 1. The device has polled to get the initial state (self._has_polled) and + to satisfy the entity platform constraint mentioned above. 2. The polling was successful and the device is in a healthy state (self.available). 3. The pywemo subscription registry reports that there is an active From 277c3cb6616d48a6ab9b00ae98ecb34f71ea8544 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Sun, 28 Feb 2021 15:53:57 -0800 Subject: [PATCH 0868/1818] Cleanup SmartTub filtration cycles (#47192) --- homeassistant/components/smarttub/sensor.py | 8 +++++--- tests/components/smarttub/test_sensor.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index be3d60c02419ad..44f09989a99bc2 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -7,9 +7,11 @@ _LOGGER = logging.getLogger(__name__) +# the desired duration, in hours, of the cycle ATTR_DURATION = "duration" -ATTR_LAST_UPDATED = "last_updated" +ATTR_CYCLE_LAST_UPDATED = "cycle_last_updated" ATTR_MODE = "mode" +# the hour of the day at which to start the cycle (0-23) ATTR_START_HOUR = "start_hour" @@ -73,7 +75,7 @@ def device_state_attributes(self): state = self._state return { ATTR_DURATION: state.duration, - ATTR_LAST_UPDATED: state.last_updated.isoformat(), + ATTR_CYCLE_LAST_UPDATED: state.last_updated.isoformat(), ATTR_MODE: state.mode.name.lower(), ATTR_START_HOUR: state.start_hour, } @@ -98,6 +100,6 @@ def device_state_attributes(self): """Return the state attributes.""" state = self._state return { - ATTR_LAST_UPDATED: state.last_updated.isoformat(), + ATTR_CYCLE_LAST_UPDATED: state.last_updated.isoformat(), ATTR_MODE: state.mode.name.lower(), } diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 5b0163daf262ce..2d52d6d07a5ef0 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -47,7 +47,7 @@ async def test_sensors(spa, setup_entry, hass): assert state is not None assert state.state == "inactive" assert state.attributes["duration"] == 4 - assert state.attributes["last_updated"] is not None + assert state.attributes["cycle_last_updated"] is not None assert state.attributes["mode"] == "normal" assert state.attributes["start_hour"] == 2 @@ -55,5 +55,5 @@ async def test_sensors(spa, setup_entry, hass): state = hass.states.get(entity_id) assert state is not None assert state.state == "inactive" - assert state.attributes["last_updated"] is not None + assert state.attributes["cycle_last_updated"] is not None assert state.attributes["mode"] == "away" From 44ed6cda40639faf12f70ead990bc45e8e651f02 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 1 Mar 2021 00:09:01 +0000 Subject: [PATCH 0869/1818] [ci skip] Translation update --- .../components/aemet/translations/de.json | 19 ++++++++++++ .../components/airly/translations/ca.json | 4 ++- .../components/airly/translations/en.json | 4 ++- .../components/airly/translations/et.json | 4 ++- .../components/airly/translations/ru.json | 4 ++- .../airly/translations/zh-Hant.json | 4 ++- .../components/asuswrt/translations/de.json | 24 +++++++++++++++ .../awair/translations/zh-Hant.json | 10 +++---- .../blink/translations/zh-Hant.json | 2 +- .../components/bond/translations/zh-Hant.json | 4 +-- .../components/climacell/translations/de.json | 19 ++++++++++++ .../components/climacell/translations/he.json | 17 +++++++++++ .../cloudflare/translations/zh-Hant.json | 4 +-- .../components/econet/translations/de.json | 21 ++++++++++++++ .../faa_delays/translations/de.json | 8 +++++ .../faa_delays/translations/he.json | 18 ++++++++++++ .../fireservicerota/translations/zh-Hant.json | 2 +- .../components/fritzbox/translations/de.json | 2 +- .../components/habitica/translations/de.json | 17 +++++++++++ .../components/homekit/translations/he.json | 10 +++++++ .../huisbaasje/translations/de.json | 21 ++++++++++++++ .../hyperion/translations/zh-Hant.json | 16 +++++----- .../juicenet/translations/zh-Hant.json | 4 +-- .../keenetic_ndms2/translations/de.json | 21 ++++++++++++++ .../components/kmtronic/translations/de.json | 21 ++++++++++++++ .../components/litejet/translations/de.json | 14 +++++++++ .../components/litejet/translations/he.json | 11 +++++++ .../litterrobot/translations/de.json | 20 +++++++++++++ .../lutron_caseta/translations/de.json | 7 +++++ .../components/lyric/translations/de.json | 16 ++++++++++ .../components/mazda/translations/de.json | 29 +++++++++++++++++++ .../media_player/translations/de.json | 7 +++++ .../melcloud/translations/zh-Hant.json | 2 +- .../components/mullvad/translations/de.json | 21 ++++++++++++++ .../components/mullvad/translations/he.json | 21 ++++++++++++++ .../components/mysensors/translations/de.json | 16 ++++++++++ .../components/netatmo/translations/he.json | 11 +++++++ .../nightscout/translations/et.json | 2 +- .../components/nuki/translations/de.json | 18 ++++++++++++ .../components/nuki/translations/zh-Hant.json | 2 +- .../philips_js/translations/de.json | 20 +++++++++++++ .../philips_js/translations/he.json | 7 +++++ .../philips_js/translations/zh-Hant.json | 2 ++ .../components/plaato/translations/de.json | 1 + .../plaato/translations/zh-Hant.json | 8 ++--- .../components/plex/translations/zh-Hant.json | 8 ++--- .../point/translations/zh-Hant.json | 2 +- .../components/powerwall/translations/de.json | 7 +++-- .../components/powerwall/translations/et.json | 2 +- .../translations/de.json | 20 +++++++++++++ .../components/roku/translations/de.json | 1 + .../simplisafe/translations/zh-Hant.json | 2 +- .../smartthings/translations/et.json | 2 +- .../smartthings/translations/zh-Hant.json | 12 ++++---- .../components/smarttub/translations/de.json | 20 +++++++++++++ .../components/subaru/translations/de.json | 28 ++++++++++++++++++ .../components/tesla/translations/de.json | 4 +++ .../tibber/translations/zh-Hant.json | 6 ++-- .../totalconnect/translations/de.json | 11 ++++++- .../components/tuya/translations/et.json | 4 +-- .../components/unifi/translations/de.json | 4 ++- .../vilfo/translations/zh-Hant.json | 4 +-- .../vizio/translations/zh-Hant.json | 6 ++-- .../xiaomi_miio/translations/de.json | 7 +++++ .../xiaomi_miio/translations/en.json | 2 +- .../xiaomi_miio/translations/zh-Hant.json | 8 ++--- .../components/zwave_js/translations/de.json | 14 ++++++++- 67 files changed, 621 insertions(+), 68 deletions(-) create mode 100644 homeassistant/components/aemet/translations/de.json create mode 100644 homeassistant/components/asuswrt/translations/de.json create mode 100644 homeassistant/components/climacell/translations/de.json create mode 100644 homeassistant/components/climacell/translations/he.json create mode 100644 homeassistant/components/econet/translations/de.json create mode 100644 homeassistant/components/faa_delays/translations/de.json create mode 100644 homeassistant/components/faa_delays/translations/he.json create mode 100644 homeassistant/components/habitica/translations/de.json create mode 100644 homeassistant/components/huisbaasje/translations/de.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/de.json create mode 100644 homeassistant/components/kmtronic/translations/de.json create mode 100644 homeassistant/components/litejet/translations/de.json create mode 100644 homeassistant/components/litejet/translations/he.json create mode 100644 homeassistant/components/litterrobot/translations/de.json create mode 100644 homeassistant/components/lyric/translations/de.json create mode 100644 homeassistant/components/mazda/translations/de.json create mode 100644 homeassistant/components/mullvad/translations/de.json create mode 100644 homeassistant/components/mullvad/translations/he.json create mode 100644 homeassistant/components/mysensors/translations/de.json create mode 100644 homeassistant/components/netatmo/translations/he.json create mode 100644 homeassistant/components/nuki/translations/de.json create mode 100644 homeassistant/components/philips_js/translations/de.json create mode 100644 homeassistant/components/philips_js/translations/he.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/de.json create mode 100644 homeassistant/components/smarttub/translations/de.json create mode 100644 homeassistant/components/subaru/translations/de.json diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json new file mode 100644 index 00000000000000..d7254aea92f600 --- /dev/null +++ b/homeassistant/components/aemet/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, + "error": { + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 95400de23b438b..e76cec94f4ccdc 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Servidor d'Airly accessible" + "can_reach_server": "Servidor d'Airly accessible", + "requests_per_day": "Sol\u00b7licituds per dia permeses", + "requests_remaining": "Sol\u00b7licituds permeses restants" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/en.json b/homeassistant/components/airly/translations/en.json index 720f68f8349c9d..0a5426c87d845a 100644 --- a/homeassistant/components/airly/translations/en.json +++ b/homeassistant/components/airly/translations/en.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Reach Airly server" + "can_reach_server": "Reach Airly server", + "requests_per_day": "Allowed requests per day", + "requests_remaining": "Remaining allowed requests" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index 8cbfd138257a91..c5c9359c67fd63 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u00dchendus Airly serveriga" + "can_reach_server": "\u00dchendus Airly serveriga", + "requests_per_day": "Lubatud taotlusi p\u00e4evas", + "requests_remaining": "J\u00e4\u00e4nud lubatud taotlusi" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ru.json b/homeassistant/components/airly/translations/ru.json index b1469af787e617..41ca90a8c027d1 100644 --- a/homeassistant/components/airly/translations/ru.json +++ b/homeassistant/components/airly/translations/ru.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Airly" + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Airly", + "requests_per_day": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0434\u0435\u043d\u044c", + "requests_remaining": "\u0421\u0447\u0451\u0442\u0447\u0438\u043a \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0445\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/zh-Hant.json b/homeassistant/components/airly/translations/zh-Hant.json index 4d60b158c4c276..19ef2ae7532cde 100644 --- a/homeassistant/components/airly/translations/zh-Hant.json +++ b/homeassistant/components/airly/translations/zh-Hant.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u9023\u7dda Airly \u4f3a\u670d\u5668" + "can_reach_server": "\u9023\u7dda Airly \u4f3a\u670d\u5668", + "requests_per_day": "\u6bcf\u65e5\u5141\u8a31\u7684\u8acb\u6c42", + "requests_remaining": "\u5176\u9918\u5141\u8a31\u7684\u8acb\u6c42" } } } \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json new file mode 100644 index 00000000000000..433bf17b8143a9 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Modus", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/zh-Hant.json b/homeassistant/components/awair/translations/zh-Hant.json index 11fe9ff88b39aa..0bd7749c65fb20 100644 --- a/homeassistant/components/awair/translations/zh-Hant.json +++ b/homeassistant/components/awair/translations/zh-Hant.json @@ -6,23 +6,23 @@ "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "reauth": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "email": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u8acb\u91cd\u65b0\u8f38\u5165 Awair \u958b\u767c\u8005\u5b58\u53d6\u5bc6\u9470\u3002" + "description": "\u8acb\u91cd\u65b0\u8f38\u5165 Awair \u958b\u767c\u8005\u5b58\u53d6\u6b0a\u6756\u3002" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "email": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u5fc5\u9808\u5148\u8a3b\u518a Awair \u958b\u767c\u8005\u5b58\u53d6\u5bc6\u9470\uff1ahttps://developer.getawair.com/onboard/login" + "description": "\u5fc5\u9808\u5148\u8a3b\u518a Awair \u958b\u767c\u8005\u5b58\u53d6\u6b0a\u6756\uff1ahttps://developer.getawair.com/onboard/login" } } } diff --git a/homeassistant/components/blink/translations/zh-Hant.json b/homeassistant/components/blink/translations/zh-Hant.json index 3d05dc82abcbbb..d2c42bf5531682 100644 --- a/homeassistant/components/blink/translations/zh-Hant.json +++ b/homeassistant/components/blink/translations/zh-Hant.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/bond/translations/zh-Hant.json b/homeassistant/components/bond/translations/zh-Hant.json index 1c5327dc6627ed..8bb8e178869aa9 100644 --- a/homeassistant/components/bond/translations/zh-Hant.json +++ b/homeassistant/components/bond/translations/zh-Hant.json @@ -13,13 +13,13 @@ "step": { "confirm": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" } } diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json new file mode 100644 index 00000000000000..f18197e1ccad1a --- /dev/null +++ b/homeassistant/components/climacell/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "name": "Name" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/he.json b/homeassistant/components/climacell/translations/he.json new file mode 100644 index 00000000000000..81a4b5c1fce612 --- /dev/null +++ b/homeassistant/components/climacell/translations/he.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", + "name": "\u05e9\u05dd" + } + } + } + } +} \ 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 1be70def0344e0..d9a05269748f5f 100644 --- a/homeassistant/components/cloudflare/translations/zh-Hant.json +++ b/homeassistant/components/cloudflare/translations/zh-Hant.json @@ -19,9 +19,9 @@ }, "user": { "data": { - "api_token": "API \u5bc6\u9470" + "api_token": "API \u6b0a\u6756" }, - "description": "\u6b64\u6574\u5408\u9700\u8981\u5e33\u865f\u4e2d\u6240\u6709\u5340\u57df Zone:Zone:Read \u8207 Zone:DNS:Edit \u6b0a\u9650 API \u5bc6\u9470\u3002", + "description": "\u6b64\u6574\u5408\u9700\u8981\u5e33\u865f\u4e2d\u6240\u6709\u5340\u57df Zone:Zone:Read \u8207 Zone:DNS:Edit \u6b0a\u9650 API \u6b0a\u6756\u3002", "title": "\u9023\u7dda\u81f3 Cloudflare" }, "zone": { diff --git a/homeassistant/components/econet/translations/de.json b/homeassistant/components/econet/translations/de.json new file mode 100644 index 00000000000000..854d61f1790b48 --- /dev/null +++ b/homeassistant/components/econet/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/de.json b/homeassistant/components/faa_delays/translations/de.json new file mode 100644 index 00000000000000..72b837c862ca05 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/de.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/he.json b/homeassistant/components/faa_delays/translations/he.json new file mode 100644 index 00000000000000..af8d410eb18c34 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e0\u05de\u05dc \u05ea\u05e2\u05d5\u05e4\u05d4 \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\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": { + "id": "\u05e0\u05de\u05dc \u05ea\u05e2\u05d5\u05e4\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/zh-Hant.json b/homeassistant/components/fireservicerota/translations/zh-Hant.json index af3cba40dc6785..8e5f4d9f20db82 100644 --- a/homeassistant/components/fireservicerota/translations/zh-Hant.json +++ b/homeassistant/components/fireservicerota/translations/zh-Hant.json @@ -15,7 +15,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u8a8d\u8b49\u5bc6\u9470\u5df2\u7d93\u5931\u6548\uff0c\u8acb\u767b\u5165\u91cd\u65b0\u65b0\u589e\u3002" + "description": "\u8a8d\u8b49\u6b0a\u6756\u5df2\u7d93\u5931\u6548\uff0c\u8acb\u767b\u5165\u91cd\u65b0\u65b0\u589e\u3002" }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 8e79076bda6adc..16263722482430 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -24,7 +24,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name} ." + "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name}." }, "user": { "data": { diff --git a/homeassistant/components/habitica/translations/de.json b/homeassistant/components/habitica/translations/de.json new file mode 100644 index 00000000000000..04f985946fb509 --- /dev/null +++ b/homeassistant/components/habitica/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_credentials": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "url": "URL" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index 87ad743dca5be9..6acebca0ca47c0 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -1,6 +1,16 @@ { "options": { "step": { + "include_exclude": { + "data": { + "mode": "\u05de\u05e6\u05d1" + } + }, + "init": { + "data": { + "mode": "\u05de\u05e6\u05d1" + } + }, "yaml": { "description": "\u05d9\u05e9\u05d5\u05ea \u05d6\u05d5 \u05e0\u05e9\u05dc\u05d8\u05ea \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea YAML" } diff --git a/homeassistant/components/huisbaasje/translations/de.json b/homeassistant/components/huisbaasje/translations/de.json new file mode 100644 index 00000000000000..ca3f90536d40f1 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "connection_exception": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unauthenticated_exception": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/zh-Hant.json b/homeassistant/components/hyperion/translations/zh-Hant.json index ed003131bf296c..bb8eacd537651e 100644 --- a/homeassistant/components/hyperion/translations/zh-Hant.json +++ b/homeassistant/components/hyperion/translations/zh-Hant.json @@ -3,8 +3,8 @@ "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", - "auth_new_token_not_granted_error": "\u65b0\u5275\u5bc6\u9470\u672a\u7372\u5f97 Hyperion UI \u6838\u51c6", - "auth_new_token_not_work_error": "\u4f7f\u7528\u65b0\u5275\u5bc6\u9470\u8a8d\u8b49\u5931\u6557", + "auth_new_token_not_granted_error": "\u65b0\u5275\u6b0a\u6756\u672a\u7372\u5f97 Hyperion UI \u6838\u51c6", + "auth_new_token_not_work_error": "\u4f7f\u7528\u65b0\u5275\u6b0a\u6756\u8a8d\u8b49\u5931\u6557", "auth_required_error": "\u7121\u6cd5\u5224\u5b9a\u662f\u5426\u9700\u8981\u9a57\u8b49", "cannot_connect": "\u9023\u7dda\u5931\u6557", "no_id": "Hyperion Ambilight \u5be6\u9ad4\u672a\u56de\u5831\u5176 ID", @@ -12,13 +12,13 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548" + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548" }, "step": { "auth": { "data": { - "create_token": "\u81ea\u52d5\u65b0\u5275\u5bc6\u9470", - "token": "\u6216\u63d0\u4f9b\u73fe\u6709\u5bc6\u9470" + "create_token": "\u81ea\u52d5\u65b0\u5275\u6b0a\u6756", + "token": "\u6216\u63d0\u4f9b\u73fe\u6709\u6b0a\u6756" }, "description": "\u8a2d\u5b9a Hyperion Ambilight \u4f3a\u670d\u5668\u8a8d\u8b49" }, @@ -27,11 +27,11 @@ "title": "\u78ba\u8a8d\u9644\u52a0 Hyperion Ambilight \u670d\u52d9" }, "create_token": { - "description": "\u9ede\u9078\u4e0b\u65b9 **\u50b3\u9001** \u4ee5\u8acb\u6c42\u65b0\u8a8d\u8b49\u5bc6\u9470\u3002\u5c07\u6703\u91cd\u65b0\u5c0e\u5411\u81f3 Hyperion UI \u4ee5\u6838\u51c6\u8981\u6c42\u3002\u8acb\u78ba\u8a8d\u986f\u793a ID \u70ba \"{auth_id}\"", - "title": "\u81ea\u52d5\u65b0\u5275\u8a8d\u8b49\u5bc6\u9470" + "description": "\u9ede\u9078\u4e0b\u65b9 **\u50b3\u9001** \u4ee5\u8acb\u6c42\u65b0\u8a8d\u8b49\u6b0a\u6756\u3002\u5c07\u6703\u91cd\u65b0\u5c0e\u5411\u81f3 Hyperion UI \u4ee5\u6838\u51c6\u8981\u6c42\u3002\u8acb\u78ba\u8a8d\u986f\u793a ID \u70ba \"{auth_id}\"", + "title": "\u81ea\u52d5\u65b0\u5275\u8a8d\u8b49\u6b0a\u6756" }, "create_token_external": { - "title": "\u63a5\u53d7 Hyperion UI \u4e2d\u7684\u65b0\u5bc6\u9470" + "title": "\u63a5\u53d7 Hyperion UI \u4e2d\u7684\u65b0\u6b0a\u6756" }, "user": { "data": { diff --git a/homeassistant/components/juicenet/translations/zh-Hant.json b/homeassistant/components/juicenet/translations/zh-Hant.json index 815edb1fb2710b..f310babfd80aa1 100644 --- a/homeassistant/components/juicenet/translations/zh-Hant.json +++ b/homeassistant/components/juicenet/translations/zh-Hant.json @@ -11,9 +11,9 @@ "step": { "user": { "data": { - "api_token": "API \u5bc6\u9470" + "api_token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u5bc6\u9470\u3002", + "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u6b0a\u6756\u3002", "title": "\u9023\u7dda\u81f3 JuiceNet" } } diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json new file mode 100644 index 00000000000000..71ce0154639b81 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/de.json b/homeassistant/components/kmtronic/translations/de.json new file mode 100644 index 00000000000000..625c7372347a61 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/de.json b/homeassistant/components/litejet/translations/de.json new file mode 100644 index 00000000000000..492314e5cc6d32 --- /dev/null +++ b/homeassistant/components/litejet/translations/de.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/he.json b/homeassistant/components/litejet/translations/he.json new file mode 100644 index 00000000000000..a06c89f1d2ac72 --- /dev/null +++ b/homeassistant/components/litejet/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u05e4\u05d5\u05e8\u05d8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/de.json b/homeassistant/components/litterrobot/translations/de.json new file mode 100644 index 00000000000000..0eee2778d05365 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index 13f8c6bd800db1..b6aacf2d0ef02e 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -6,6 +6,13 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/de.json b/homeassistant/components/lyric/translations/de.json new file mode 100644 index 00000000000000..5bab6ed132bf52 --- /dev/null +++ b/homeassistant/components/lyric/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/de.json b/homeassistant/components/mazda/translations/de.json new file mode 100644 index 00000000000000..4e23becb8af642 --- /dev/null +++ b/homeassistant/components/mazda/translations/de.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "reauth": { + "data": { + "email": "E-Mail", + "password": "Passwort", + "region": "Region" + } + }, + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort", + "region": "Region" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/de.json b/homeassistant/components/media_player/translations/de.json index a7f25fa9d7cddd..4909c85d053b80 100644 --- a/homeassistant/components/media_player/translations/de.json +++ b/homeassistant/components/media_player/translations/de.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} ist eingeschaltet", "is_paused": "{entity_name} ist pausiert", "is_playing": "{entity_name} spielt" + }, + "trigger_type": { + "idle": "{entity_name} wird inaktiv", + "paused": "{entity_name} ist angehalten", + "playing": "{entity_name} beginnt zu spielen", + "turned_off": "{entity_name} ausgeschaltet", + "turned_on": "{entity_name} eingeschaltet" } }, "state": { diff --git a/homeassistant/components/melcloud/translations/zh-Hant.json b/homeassistant/components/melcloud/translations/zh-Hant.json index 9947b5ac990869..27f4d0e5d7f0d2 100644 --- a/homeassistant/components/melcloud/translations/zh-Hant.json +++ b/homeassistant/components/melcloud/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u5bc6\u9470\u5df2\u66f4\u65b0\u3002" + "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u6b0a\u6756\u5df2\u66f4\u65b0\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/mullvad/translations/de.json b/homeassistant/components/mullvad/translations/de.json new file mode 100644 index 00000000000000..625c7372347a61 --- /dev/null +++ b/homeassistant/components/mullvad/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/he.json b/homeassistant/components/mullvad/translations/he.json new file mode 100644 index 00000000000000..7f60f15d598ac0 --- /dev/null +++ b/homeassistant/components/mullvad/translations/he.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05ea\u05e7\u05d9\u05df", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "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/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json new file mode 100644 index 00000000000000..189226f29d5e90 --- /dev/null +++ b/homeassistant/components/mysensors/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "error": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/he.json b/homeassistant/components/netatmo/translations/he.json new file mode 100644 index 00000000000000..54bef84c30a4a2 --- /dev/null +++ b/homeassistant/components/netatmo/translations/he.json @@ -0,0 +1,11 @@ +{ + "device_automation": { + "trigger_type": { + "animal": "\u05d6\u05d9\u05d4\u05d4 \u05d1\u05e2\u05dc-\u05d7\u05d9\u05d9\u05dd", + "human": "\u05d6\u05d9\u05d4\u05d4 \u05d0\u05d3\u05dd", + "movement": "\u05d6\u05d9\u05d4\u05d4 \u05ea\u05e0\u05d5\u05e2\u05d4", + "turned_off": "\u05db\u05d1\u05d4", + "turned_on": "\u05e0\u05d3\u05dc\u05e7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/et.json b/homeassistant/components/nightscout/translations/et.json index 361b47893288a0..0d00cebb6a5f8e 100644 --- a/homeassistant/components/nightscout/translations/et.json +++ b/homeassistant/components/nightscout/translations/et.json @@ -15,7 +15,7 @@ "api_key": "API v\u00f5ti", "url": "" }, - "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui teie eksemplar on kaitstud (auth_default_roles! = readable).", + "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui eksemplar on kaitstud (auth_default_roles! = readable).", "title": "Sisesta oma Nightscouti serveri teave." } } diff --git a/homeassistant/components/nuki/translations/de.json b/homeassistant/components/nuki/translations/de.json new file mode 100644 index 00000000000000..30d7e6865cdc80 --- /dev/null +++ b/homeassistant/components/nuki/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Zugangstoken" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/zh-Hant.json b/homeassistant/components/nuki/translations/zh-Hant.json index 662d7ed6ed95c5..4bf21552952d2a 100644 --- a/homeassistant/components/nuki/translations/zh-Hant.json +++ b/homeassistant/components/nuki/translations/zh-Hant.json @@ -10,7 +10,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "token": "\u5b58\u53d6\u5bc6\u9470" + "token": "\u5b58\u53d6\u6b0a\u6756" } } } diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json new file mode 100644 index 00000000000000..f59a17bce49668 --- /dev/null +++ b/homeassistant/components/philips_js/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_pin": "Ung\u00fcltige PIN", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_version": "API-Version", + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/he.json b/homeassistant/components/philips_js/translations/he.json new file mode 100644 index 00000000000000..04648fe58451ef --- /dev/null +++ b/homeassistant/components/philips_js/translations/he.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "pairing_failure": "\u05e6\u05d9\u05de\u05d5\u05d3 \u05e0\u05db\u05e9\u05dc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/zh-Hant.json b/homeassistant/components/philips_js/translations/zh-Hant.json index af161b6b16b5f5..13bfd52e980148 100644 --- a/homeassistant/components/philips_js/translations/zh-Hant.json +++ b/homeassistant/components/philips_js/translations/zh-Hant.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_pin": "PIN \u78bc\u7121\u6548", + "pairing_failure": "\u7121\u6cd5\u914d\u5c0d\uff1a{error_id}", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index 5171baab6544f0..eaf68b507f9f83 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Konto wurde bereits konfiguriert", "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/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index 2890c5c31c694a..26d7b728771b72 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -10,16 +10,16 @@ }, "error": { "invalid_webhook_device": "\u6240\u9078\u64c7\u7684\u88dd\u7f6e\u4e0d\u652f\u63f4\u50b3\u9001\u8cc7\u6599\u81f3 Webhook\u3001AirLock \u50c5\u652f\u63f4\u6b64\u985e\u578b", - "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470\u6216\u9078\u64c7 Webhook", - "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470" + "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u6b0a\u6756\u6216\u9078\u64c7 Webhook", + "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u6b0a\u6756" }, "step": { "api_method": { "data": { - "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u5bc6\u9470", + "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u6b0a\u6756", "use_webhook": "\u4f7f\u7528 Webhook" }, - "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u5bc6\u9470\u6b04\u4f4d\u7a7a\u767d", + "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u6b0a\u6756\u6b04\u4f4d\u7a7a\u767d", "title": "\u9078\u64c7 API \u65b9\u5f0f" }, "user": { diff --git a/homeassistant/components/plex/translations/zh-Hant.json b/homeassistant/components/plex/translations/zh-Hant.json index 137b953a145745..7f19fa0d035441 100644 --- a/homeassistant/components/plex/translations/zh-Hant.json +++ b/homeassistant/components/plex/translations/zh-Hant.json @@ -5,12 +5,12 @@ "already_configured": "Plex \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "token_request_timeout": "\u53d6\u5f97\u5bc6\u9470\u903e\u6642", + "token_request_timeout": "\u53d6\u5f97\u6b0a\u6756\u903e\u6642", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { - "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u5bc6\u9470", - "host_or_token": "\u5fc5\u9808\u81f3\u5c11\u63d0\u4f9b\u4e3b\u6a5f\u7aef\u6216\u5bc6\u9470", + "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u6b0a\u6756", + "host_or_token": "\u5fc5\u9808\u81f3\u5c11\u63d0\u4f9b\u4e3b\u6a5f\u7aef\u6216\u6b0a\u6756", "no_servers": "Plex \u5e33\u865f\u672a\u7d81\u5b9a\u4efb\u4f55\u4f3a\u670d\u5668", "not_found": "\u627e\u4e0d\u5230 Plex \u4f3a\u670d\u5668", "ssl_error": "SSL \u8a8d\u8b49\u554f\u984c" @@ -22,7 +22,7 @@ "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", - "token": "\u5bc6\u9470\uff08\u9078\u9805\uff09", + "token": "\u6b0a\u6756\uff08\u9078\u9805\uff09", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "title": "Plex \u624b\u52d5\u8a2d\u5b9a" diff --git a/homeassistant/components/point/translations/zh-Hant.json b/homeassistant/components/point/translations/zh-Hant.json index 710d363f771383..2bb1a8fc23901e 100644 --- a/homeassistant/components/point/translations/zh-Hant.json +++ b/homeassistant/components/point/translations/zh-Hant.json @@ -13,7 +13,7 @@ }, "error": { "follow_link": "\u8acb\u65bc\u50b3\u9001\u524d\uff0c\u5148\u4f7f\u7528\u9023\u7d50\u4e26\u9032\u884c\u8a8d\u8b49\u3002", - "no_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548" + "no_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548" }, "step": { "auth": { diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index c30286d874450a..0ccd42c812ba1f 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -1,17 +1,20 @@ { "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", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { - "ip_address": "IP-Adresse" + "ip_address": "IP-Adresse", + "password": "Passwort" }, "title": "Stellen Sie eine Verbindung zur Powerwall her" } diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index 4a937029296427..8811b87031699a 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -8,7 +8,7 @@ "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", "invalid_auth": "Vigane autentimine", "unknown": "Ootamatu t\u00f5rge", - "wrong_version": "Teie Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." + "wrong_version": "Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, "flow_title": "Tesla Powerwall ( {ip_address} )", "step": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/de.json b/homeassistant/components/rituals_perfume_genie/translations/de.json new file mode 100644 index 00000000000000..67b8ed59e0b6b3 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 4bfb3c7503ddda..152161cb27fbfb 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Das Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "unknown": "Unerwarteter Fehler" }, "error": { diff --git a/homeassistant/components/simplisafe/translations/zh-Hant.json b/homeassistant/components/simplisafe/translations/zh-Hant.json index ad5323d3957d6f..27064ed1055a38 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hant.json +++ b/homeassistant/components/simplisafe/translations/zh-Hant.json @@ -19,7 +19,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u5b58\u53d6\u5bc6\u9470\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", + "description": "\u5b58\u53d6\u6b0a\u6756\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, "user": { diff --git a/homeassistant/components/smartthings/translations/et.json b/homeassistant/components/smartthings/translations/et.json index 04cd0d70218bf9..18d6076898db4c 100644 --- a/homeassistant/components/smartthings/translations/et.json +++ b/homeassistant/components/smartthings/translations/et.json @@ -19,7 +19,7 @@ "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks teie SmartThingsi kontol.", + "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks SmartThingsi kontol.", "title": "Sisesta isiklik juurdep\u00e4\u00e4suluba (PAT)" }, "select_location": { diff --git a/homeassistant/components/smartthings/translations/zh-Hant.json b/homeassistant/components/smartthings/translations/zh-Hant.json index d9a17e460582c3..88360c756781a6 100644 --- a/homeassistant/components/smartthings/translations/zh-Hant.json +++ b/homeassistant/components/smartthings/translations/zh-Hant.json @@ -6,9 +6,9 @@ }, "error": { "app_setup_error": "\u7121\u6cd5\u8a2d\u5b9a SmartApp\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", - "token_forbidden": "\u5bc6\u9470\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", - "token_invalid_format": "\u5bc6\u9470\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", - "token_unauthorized": "\u5bc6\u9470\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", + "token_forbidden": "\u6b0a\u6756\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", + "token_invalid_format": "\u6b0a\u6756\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", + "token_unauthorized": "\u6b0a\u6756\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", "webhook_error": "SmartThings \u7121\u6cd5\u8a8d\u8b49 Webhook URL\u3002\u8acb\u78ba\u8a8d Webhook URL \u53ef\u7531\u7db2\u8def\u5b58\u53d6\u5f8c\u518d\u8a66\u4e00\u6b21\u3002" }, "step": { @@ -17,10 +17,10 @@ }, "pat": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u5bc6\u9470]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", - "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u5bc6\u9470" + "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u6b0a\u6756]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", + "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u6b0a\u6756" }, "select_location": { "data": { diff --git a/homeassistant/components/smarttub/translations/de.json b/homeassistant/components/smarttub/translations/de.json new file mode 100644 index 00000000000000..fbb3411a6c5b26 --- /dev/null +++ b/homeassistant/components/smarttub/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/de.json b/homeassistant/components/subaru/translations/de.json new file mode 100644 index 00000000000000..1c162d61e994fd --- /dev/null +++ b/homeassistant/components/subaru/translations/de.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "bad_pin_format": "Die PIN sollte 4-stellig sein", + "cannot_connect": "Verbindung fehlgeschlagen", + "incorrect_pin": "Falsche PIN", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + } + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/de.json b/homeassistant/components/tesla/translations/de.json index 558209af411791..2fd964fe01320c 100644 --- a/homeassistant/components/tesla/translations/de.json +++ b/homeassistant/components/tesla/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { "already_configured": "Konto wurde bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/tibber/translations/zh-Hant.json b/homeassistant/components/tibber/translations/zh-Hant.json index ce10615a2892b6..e4d0ec10e234c2 100644 --- a/homeassistant/components/tibber/translations/zh-Hant.json +++ b/homeassistant/components/tibber/translations/zh-Hant.json @@ -5,15 +5,15 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "timeout": "\u9023\u7dda\u81f3 Tibber \u903e\u6642" }, "step": { "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u5bc6\u9470", + "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u6b0a\u6756", "title": "Tibber" } } diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 530fef95af23f1..3fb5bb8f3e1f08 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Konto wurde bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { + "locations": { + "data": { + "location": "Standort" + } + }, + "reauth_confirm": { + "title": "Integration erneut authentifizieren" + }, "user": { "data": { "password": "Passwort", diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json index 0fc1297ce7c461..48161f552b8284 100644 --- a/homeassistant/components/tuya/translations/et.json +++ b/homeassistant/components/tuya/translations/et.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "country_code": "Teie konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", + "country_code": "Konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", "password": "Salas\u00f5na", - "platform": "\u00c4pp kus teie konto registreeriti", + "platform": "\u00c4pp kus konto registreeriti", "username": "Kasutajanimi" }, "description": "Sisesta oma Tuya konto andmed.", diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index be38ddf1a4d760..05dd66fe56c798 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Controller-Site ist bereits konfiguriert" + "already_configured": "Controller-Site ist bereits konfiguriert", + "configuration_updated": "Konfiguration aktualisiert.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "faulty_credentials": "Ung\u00fcltige Authentifizierung", diff --git a/homeassistant/components/vilfo/translations/zh-Hant.json b/homeassistant/components/vilfo/translations/zh-Hant.json index b266e25b39cb3f..88180f9bacf822 100644 --- a/homeassistant/components/vilfo/translations/zh-Hant.json +++ b/homeassistant/components/vilfo/translations/zh-Hant.json @@ -11,10 +11,10 @@ "step": { "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u5bc6\u9470\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", + "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u6b0a\u6756\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", "title": "\u9023\u7dda\u81f3 Vilfo \u8def\u7531\u5668" } } diff --git a/homeassistant/components/vizio/translations/zh-Hant.json b/homeassistant/components/vizio/translations/zh-Hant.json index 257ed829b6ae50..5f21dd0c2b6b49 100644 --- a/homeassistant/components/vizio/translations/zh-Hant.json +++ b/homeassistant/components/vizio/translations/zh-Hant.json @@ -23,17 +23,17 @@ "title": "\u914d\u5c0d\u5b8c\u6210" }, "pairing_complete_import": { - "description": "VIZIO SmartCast \u88dd\u7f6e \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u5bc6\u9470\u70ba '**{access_token}**'\u3002", + "description": "VIZIO SmartCast \u88dd\u7f6e \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u6b0a\u6756\u70ba '**{access_token}**'\u3002", "title": "\u914d\u5c0d\u5b8c\u6210" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "device_class": "\u88dd\u7f6e\u985e\u5225", "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u5bc6\u9470\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u5bc6\u9470 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", + "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u6b0a\u6756\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u6b0a\u6756 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", "title": "VIZIO SmartCast \u88dd\u7f6e" } } diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index d56a81e14d4582..7cf11a1085e2a1 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -10,6 +10,13 @@ }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP-Adresse", + "name": "Name des Ger\u00e4ts", + "token": "API-Token" + } + }, "gateway": { "data": { "host": "IP-Adresse", diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 951ae546b5618c..3d893ade2f0805 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -18,7 +18,7 @@ "name": "Name of the device", "token": "API Token" }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", + "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index dce2002faa957b..3b0a89b7485e62 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -16,18 +16,18 @@ "host": "IP \u4f4d\u5740", "model": "\u88dd\u7f6e\u578b\u865f\uff08\u9078\u9805\uff09", "name": "\u88dd\u7f6e\u540d\u7a31", - "token": "API \u5bc6\u9470" + "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" }, "gateway": { "data": { "host": "IP \u4f4d\u5740", "name": "\u7db2\u95dc\u540d\u7a31", - "token": "API \u5bc6\u9470" + "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" }, "user": { diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index d4903bc8c6dae4..9ff130605efdd1 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,13 +1,25 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { + "configure_addon": { + "data": { + "usb_path": "USB-Ger\u00e4te-Pfad" + } + }, + "manual": { + "data": { + "url": "URL" + } + }, "user": { "data": { "url": "URL" From 715a254913dfdf24c6bf90d56e9ecbf4186191e7 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 28 Feb 2021 18:01:28 -0800 Subject: [PATCH 0870/1818] Handle stream failures in recorder (#47151) * Handle stream failures in recorder Fail gracefully with an error message when the recorder is invoked with no segments due to a stream failure. * Update homeassistant/components/stream/recorder.py Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> --- homeassistant/components/stream/recorder.py | 5 +++++ tests/components/stream/test_recorder.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 0344e220647855..f61211340efb20 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -21,6 +21,11 @@ def async_setup_recorder(hass): def recorder_save_worker(file_out: str, segments: Deque[Segment]): """Handle saving stream.""" + + if not segments: + _LOGGER.error("Recording failed to capture anything") + return + if not os.path.exists(os.path.dirname(file_out)): os.makedirs(os.path.dirname(file_out), exist_ok=True) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 199020097bda50..48fe48d3337471 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -193,6 +193,18 @@ async def test_recorder_discontinuity(tmpdir): assert os.path.exists(filename) +async def test_recorder_no_segements(tmpdir): + """Test recorder behavior with a stream failure which causes no segments.""" + # Setup + filename = f"{tmpdir}/test.mp4" + + # Run + recorder_save_worker("unused-file", []) + + # Assert + assert not os.path.exists(filename) + + async def test_record_stream_audio( hass, hass_client, stream_worker_sync, record_worker_sync ): From 5784e14d0cde5e61d09a6005b298e5ced495c97c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 20:16:30 -0600 Subject: [PATCH 0871/1818] Enforce typing in bond (#47187) Co-authored-by: Martin Hjelmare --- homeassistant/components/bond/__init__.py | 7 ++-- homeassistant/components/bond/config_flow.py | 17 +++++---- homeassistant/components/bond/cover.py | 12 ++++--- homeassistant/components/bond/entity.py | 20 ++++++----- homeassistant/components/bond/fan.py | 10 +++--- homeassistant/components/bond/light.py | 12 +++---- homeassistant/components/bond/switch.py | 4 +-- homeassistant/components/bond/utils.py | 36 +++++++++++--------- setup.cfg | 2 +- 9 files changed, 63 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 9d0a613000ad46..ae9cc2111a2aa1 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -19,13 +19,13 @@ _API_TIMEOUT = SLOW_UPDATE_WARNING - 1 -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the Bond component.""" hass.data.setdefault(DOMAIN, {}) return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Bond from a config entry.""" host = entry.data[CONF_HOST] token = entry.data[CONF_ACCESS_TOKEN] @@ -50,6 +50,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id) + assert hub.bond_id is not None hub_name = hub.name or hub.bond_id device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( @@ -96,7 +97,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @callback def _async_remove_old_device_identifiers( config_entry_id: str, device_registry: dr.DeviceRegistry, hub: BondHub -): +) -> None: """Remove the non-unique device registry entries.""" for device in hub.devices: dev = device_registry.async_get_device(identifiers={(DOMAIN, device.device_id)}) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index f4e9babdff447e..af889b803b5e1e 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -13,6 +13,7 @@ CONF_NAME, HTTP_UNAUTHORIZED, ) +from homeassistant.helpers.typing import DiscoveryInfoType from .const import DOMAIN # pylint:disable=unused-import from .utils import BondHub @@ -27,7 +28,7 @@ TOKEN_SCHEMA = vol.Schema({}) -async def _validate_input(data: Dict[str, Any]) -> Tuple[str, Optional[str]]: +async def _validate_input(data: Dict[str, Any]) -> Tuple[str, str]: """Validate the user input allows us to connect.""" bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) @@ -57,11 +58,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH - def __init__(self): + def __init__(self) -> None: """Initialize config flow.""" - self._discovered: Optional[dict] = None + self._discovered: Dict[str, str] = {} - async def _async_try_automatic_configure(self): + async def _async_try_automatic_configure(self) -> None: """Try to auto configure the device. Failure is acceptable here since the device may have been @@ -82,9 +83,7 @@ async def _async_try_automatic_configure(self): _, hub_name = await _validate_input(self._discovered) self._discovered[CONF_NAME] = hub_name - async def async_step_zeroconf( - self, discovery_info: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> Dict[str, Any]: # type: ignore """Handle a flow initialized by zeroconf discovery.""" name: str = discovery_info[CONF_NAME] host: str = discovery_info[CONF_HOST] @@ -107,7 +106,7 @@ async def async_step_zeroconf( return await self.async_step_confirm() async def async_step_confirm( - self, user_input: Dict[str, Any] = None + self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle confirmation flow for discovered bond hub.""" errors = {} @@ -148,7 +147,7 @@ async def async_step_confirm( ) async def async_step_user( - self, user_input: Dict[str, Any] = None + self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 6b3c8d6bc02828..0c73bdbc8f9262 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -23,7 +23,7 @@ async def async_setup_entry( hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - covers = [ + covers: List[Entity] = [ BondCover(hub, device, bpup_subs) for device in hub.devices if device.type == DeviceType.MOTORIZED_SHADES @@ -35,13 +35,15 @@ async def async_setup_entry( class BondCover(BondEntity, CoverEntity): """Representation of a Bond cover.""" - def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions): + def __init__( + self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions + ) -> None: """Create HA entity representing Bond cover.""" super().__init__(hub, device, bpup_subs) self._closed: Optional[bool] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: cover_open = state.get("open") self._closed = True if cover_open == 0 else False if cover_open == 1 else None @@ -51,7 +53,7 @@ def device_class(self) -> Optional[str]: return DEVICE_CLASS_SHADE @property - def is_closed(self): + def is_closed(self) -> Optional[bool]: """Return if the cover is closed or not.""" return self._closed @@ -63,6 +65,6 @@ async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" await self._hub.bond.action(self._device.device_id, Action.close()) - async def async_stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs: Any) -> None: """Hold cover.""" await self._hub.bond.action(self._device.device_id, Action.hold()) diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 5b2e27b94cc5b0..6c56b47a9dc536 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -38,7 +38,7 @@ def __init__( self._sub_device = sub_device self._available = True self._bpup_subs = bpup_subs - self._update_lock = None + self._update_lock: Optional[Lock] = None self._initialized = False @property @@ -58,7 +58,7 @@ def name(self) -> Optional[str]: return self._device.name @property - def should_poll(self): + def should_poll(self) -> bool: """No polling needed.""" return False @@ -96,15 +96,16 @@ def available(self) -> bool: """Report availability of this entity based on last API call results.""" return self._available - async def async_update(self): + async def async_update(self) -> None: """Fetch assumed state of the cover from the hub using API.""" await self._async_update_from_api() - async def _async_update_if_bpup_not_alive(self, *_): + async def _async_update_if_bpup_not_alive(self, *_: Any) -> None: """Fetch via the API if BPUP is not alive.""" if self._bpup_subs.alive and self._initialized: return + assert self._update_lock is not None if self._update_lock.locked(): _LOGGER.warning( "Updating %s took longer than the scheduled update interval %s", @@ -117,7 +118,7 @@ async def _async_update_if_bpup_not_alive(self, *_): await self._async_update_from_api() self.async_write_ha_state() - async def _async_update_from_api(self): + async def _async_update_from_api(self) -> None: """Fetch via the API.""" try: state: dict = await self._hub.bond.device_state(self._device_id) @@ -131,11 +132,11 @@ async def _async_update_from_api(self): self._async_state_callback(state) @abstractmethod - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: raise NotImplementedError @callback - def _async_state_callback(self, state): + def _async_state_callback(self, state: dict) -> None: """Process a state change.""" self._initialized = True if not self._available: @@ -147,16 +148,17 @@ def _async_state_callback(self, state): self._apply_state(state) @callback - def _async_bpup_callback(self, state): + def _async_bpup_callback(self, state: dict) -> None: """Process a state change from BPUP.""" self._async_state_callback(state) self.async_write_ha_state() - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Subscribe to BPUP and start polling.""" await super().async_added_to_hass() self._update_lock = Lock() self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) + assert self.hass is not None self.async_on_remove( async_track_time_interval( self.hass, self._async_update_if_bpup_not_alive, _FALLBACK_SCAN_INTERVAL diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 5ff7e0c7065377..1c94a6f3e9a7a8 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -38,7 +38,7 @@ async def async_setup_entry( hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - fans = [ + fans: List[Entity] = [ BondFan(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) @@ -58,7 +58,7 @@ def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscription self._speed: Optional[int] = None self._direction: Optional[int] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._power = state.get("power") self._speed = state.get("speed") self._direction = state.get("direction") @@ -80,7 +80,7 @@ def _speed_range(self) -> Tuple[int, int]: return (1, self._device.props.get("max_speed", 3)) @property - def percentage(self) -> Optional[str]: + def percentage(self) -> int: """Return the current speed percentage for the fan.""" if not self._speed or not self._power: return 0 @@ -128,7 +128,7 @@ async def async_turn_on( speed: Optional[str] = None, percentage: Optional[int] = None, preset_mode: Optional[str] = None, - **kwargs, + **kwargs: Any, ) -> None: """Turn on the fan.""" _LOGGER.debug("Fan async_turn_on called with percentage %s", percentage) @@ -142,7 +142,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Turn the fan off.""" await self._hub.bond.action(self._device.device_id, Action.turn_off()) - async def async_set_direction(self, direction: str): + async def async_set_direction(self, direction: str) -> None: """Set fan rotation direction.""" bond_direction = ( Direction.REVERSE if direction == DIRECTION_REVERSE else Direction.FORWARD diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index 194a009a857896..d5f7cb29207dc0 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -114,7 +114,7 @@ def __init__( super().__init__(hub, device, bpup_subs, sub_device) self._brightness: Optional[int] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._light = state.get("light") self._brightness = state.get("brightness") @@ -126,7 +126,7 @@ def supported_features(self) -> Optional[int]: return 0 @property - def brightness(self) -> int: + def brightness(self) -> Optional[int]: """Return the brightness of this light between 1..255.""" brightness_value = ( round(self._brightness * 255 / 100) if self._brightness else None @@ -152,7 +152,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: class BondDownLight(BondBaseLight, BondEntity, LightEntity): """Representation of a Bond light.""" - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._light = state.get("down_light") and state.get("light") async def async_turn_on(self, **kwargs: Any) -> None: @@ -171,7 +171,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: class BondUpLight(BondBaseLight, BondEntity, LightEntity): """Representation of a Bond light.""" - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._light = state.get("up_light") and state.get("light") async def async_turn_on(self, **kwargs: Any) -> None: @@ -198,7 +198,7 @@ def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscription # Bond flame level, 0-100 self._flame: Optional[int] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._power = state.get("power") self._flame = state.get("flame") @@ -230,7 +230,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self._hub.bond.action(self._device.device_id, Action.turn_off()) @property - def brightness(self): + def brightness(self) -> Optional[int]: """Return the flame of this fireplace converted to HA brightness between 0..255.""" return round(self._flame * 255 / 100) if self._flame else None diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index 8319d31c714054..abbc2e2b44c4ed 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -23,7 +23,7 @@ async def async_setup_entry( hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - switches = [ + switches: List[Entity] = [ BondSwitch(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_generic(device.type) @@ -41,7 +41,7 @@ def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscription self._power: Optional[bool] = None - def _apply_state(self, state: dict): + def _apply_state(self, state: dict) -> None: self._power = state.get("power") @property diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 225eec87d98a99..28580ae415e42e 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,7 +1,7 @@ """Reusable utilities for the Bond component.""" import asyncio import logging -from typing import List, Optional, Set +from typing import Any, Dict, List, Optional, Set, cast from aiohttp import ClientResponseError from bond_api import Action, Bond @@ -14,13 +14,15 @@ class BondDevice: """Helper device class to hold ID and attributes together.""" - def __init__(self, device_id: str, attrs: dict, props: dict): + def __init__( + self, device_id: str, attrs: Dict[str, Any], props: Dict[str, Any] + ) -> None: """Create a helper device from ID and attributes returned by API.""" self.device_id = device_id self.props = props self._attrs = attrs - def __repr__(self): + def __repr__(self) -> str: """Return readable representation of a bond device.""" return { "device_id": self.device_id, @@ -31,25 +33,25 @@ def __repr__(self): @property def name(self) -> str: """Get the name of this device.""" - return self._attrs["name"] + return cast(str, self._attrs["name"]) @property def type(self) -> str: """Get the type of this device.""" - return self._attrs["type"] + return cast(str, self._attrs["type"]) @property - def location(self) -> str: + def location(self) -> Optional[str]: """Get the location of this device.""" return self._attrs.get("location") @property - def template(self) -> str: + def template(self) -> Optional[str]: """Return this model template.""" return self._attrs.get("template") @property - def branding_profile(self) -> str: + def branding_profile(self) -> Optional[str]: """Return this branding profile.""" return self.props.get("branding_profile") @@ -58,7 +60,7 @@ def trust_state(self) -> bool: """Check if Trust State is turned on.""" return self.props.get("trust_state", False) - def _has_any_action(self, actions: Set[str]): + def _has_any_action(self, actions: Set[str]) -> bool: """Check to see if the device supports any of the actions.""" supported_actions: List[str] = self._attrs["actions"] for action in supported_actions: @@ -99,11 +101,11 @@ class BondHub: def __init__(self, bond: Bond): """Initialize Bond Hub.""" self.bond: Bond = bond - self._bridge: Optional[dict] = None - self._version: Optional[dict] = None - self._devices: Optional[List[BondDevice]] = None + self._bridge: Dict[str, Any] = {} + self._version: Dict[str, Any] = {} + self._devices: List[BondDevice] = [] - async def setup(self, max_devices=None): + async def setup(self, max_devices: Optional[int] = None) -> None: """Read hub version information.""" self._version = await self.bond.version() _LOGGER.debug("Bond reported the following version info: %s", self._version) @@ -135,12 +137,12 @@ def bond_id(self) -> Optional[str]: return self._version.get("bondid") @property - def target(self) -> str: + def target(self) -> Optional[str]: """Return this hub target.""" return self._version.get("target") @property - def model(self) -> str: + def model(self) -> Optional[str]: """Return this hub model.""" return self._version.get("model") @@ -154,7 +156,7 @@ def name(self) -> str: """Get the name of this bridge.""" if not self.is_bridge and self._devices: return self._devices[0].name - return self._bridge["name"] + return cast(str, self._bridge["name"]) @property def location(self) -> Optional[str]: @@ -164,7 +166,7 @@ def location(self) -> Optional[str]: return self._bridge.get("location") @property - def fw_ver(self) -> str: + def fw_ver(self) -> Optional[str]: """Return this hub firmware version.""" return self._version.get("fw_ver") diff --git a/setup.cfg b/setup.cfg index 7761ff2d67edae..69cf931185e600 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,7 @@ warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] strict = true ignore_errors = false warn_unreachable = true From 0e9f2dc2720edda035d7909eb3e2220d0a9fe8f3 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 28 Feb 2021 21:41:09 -0700 Subject: [PATCH 0872/1818] Bump simplisafe-python to 9.6.7 (#47206) --- 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 b18bafb0bbfaa9..de5199ccd4cda6 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.4"], + "requirements": ["simplisafe-python==9.6.7"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 277d42022e2a7c..d03417a1bc5398 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2044,7 +2044,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.4 +simplisafe-python==9.6.7 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9ace25468ef97e..67ebea6ab2c166 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1051,7 +1051,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.4 +simplisafe-python==9.6.7 # homeassistant.components.slack slackclient==2.5.0 From 853da40e703933b17646863be373c3b5653de8f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 22:42:09 -0600 Subject: [PATCH 0873/1818] Increment the homekit config version when restarting (#47209) If an entity changes between restart the iOS/controller device may have cached the old chars for the accessory. To force the iOS/controller to reload the chars, we increment the config version when Home Assistant restarts --- homeassistant/components/homekit/__init__.py | 7 +++++-- tests/components/homekit/test_homekit.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 534ea3c6f95270..c042872f4cd632 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -491,8 +491,11 @@ def setup(self, zeroconf_instance): # as pyhap uses a random one until state is restored if os.path.exists(persist_file): self.driver.load() - else: - self.driver.persist() + self.driver.state.config_version += 1 + if self.driver.state.config_version > 65535: + self.driver.state.config_version = 1 + + self.driver.persist() def reset_accessories(self, entity_ids): """Reset the accessory to load the latest configuration.""" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index ec32460268488d..9ce3e96f06f985 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -540,6 +540,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): assert (device_registry.CONNECTION_NETWORK_MAC, formatted_mac) in device.connections assert len(device_reg.devices) == 1 + assert homekit.driver.state.config_version == 2 async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf): From cb94e7949b1486784f3809911cf0c26fffe49e75 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 02:00:31 -0600 Subject: [PATCH 0874/1818] Bump HAP-python to 3.3.2 to fix unavailable condition on restart (#47213) Fixes https://github.com/ikalchev/HAP-python/compare/v3.3.1...v3.3.2 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index 4d1598c728c09e..ac3fb0251e2e74 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.1", + "HAP-python==3.3.2", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index d03417a1bc5398..d4e7be4d98ed9e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.1 +HAP-python==3.3.2 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 67ebea6ab2c166..78f234e9dff8e0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.1 +HAP-python==3.3.2 # homeassistant.components.flick_electric PyFlick==0.0.2 From 16dcbf1467246d18e7c55575f53f01032e4c1ece Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 1 Mar 2021 09:09:01 +0100 Subject: [PATCH 0875/1818] Update pylint (#47205) --- .../auth/providers/trusted_networks.py | 2 +- homeassistant/components/alexa/intent.py | 2 ++ .../components/apple_tv/config_flow.py | 2 +- .../components/isy994/binary_sensor.py | 8 ++--- homeassistant/components/isy994/helpers.py | 12 +++---- homeassistant/components/knx/const.py | 2 ++ homeassistant/components/slack/notify.py | 36 +++++++++---------- homeassistant/components/stream/__init__.py | 2 +- .../components/telegram_bot/__init__.py | 2 +- .../components/universal/media_player.py | 2 +- homeassistant/components/vicare/__init__.py | 1 + homeassistant/components/withings/common.py | 14 +++----- .../components/zha/core/channels/general.py | 2 +- .../components/zha/core/channels/hvac.py | 2 +- homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/cover.py | 2 +- .../components/zhong_hong/climate.py | 2 +- homeassistant/core.py | 3 ++ homeassistant/helpers/typing.py | 1 + requirements_test.txt | 4 +-- 20 files changed, 50 insertions(+), 52 deletions(-) diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index 2afdbf98196dd1..c45cca4bd1a2d4 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -111,7 +111,7 @@ async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: if ( user.id in user_list or any( - [group.id in flattened_group_list for group in user.groups] + group.id in flattened_group_list for group in user.groups ) ) ] diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index f64031250e258a..edc7ec6fa987bf 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -18,6 +18,7 @@ class SpeechType(enum.Enum): + # pylint: disable=invalid-name """The Alexa speech types.""" plaintext = "PlainText" @@ -28,6 +29,7 @@ class SpeechType(enum.Enum): class CardType(enum.Enum): + # pylint: disable=invalid-name """The Alexa card types.""" simple = "Simple" diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index ef0a0cfe59e2d5..ad56561ef9b92d 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -44,7 +44,7 @@ def _filter_device(dev): return True if identifier == dev.name: return True - return any([service.identifier == identifier for service in dev.services]) + return any(service.identifier == identifier for service in dev.services) def _host_filter(): try: diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 6355a9bcece144..e8c08d98aa7bf5 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -127,7 +127,7 @@ async def async_setup_entry( if ( device_class == DEVICE_CLASS_MOTION and device_type is not None - and any([device_type.startswith(t) for t in TYPE_INSTEON_MOTION]) + and any(device_type.startswith(t) for t in TYPE_INSTEON_MOTION) ): # Special cases for Insteon Motion Sensors I & II: # Some subnodes never report status until activated, so @@ -194,10 +194,8 @@ def _detect_device_type_and_class(node: Union[Group, Node]) -> (str, str): # Other devices (incl Insteon.) for device_class in [*BINARY_SENSOR_DEVICE_TYPES_ISY]: if any( - [ - device_type.startswith(t) - for t in set(BINARY_SENSOR_DEVICE_TYPES_ISY[device_class]) - ] + device_type.startswith(t) + for t in set(BINARY_SENSOR_DEVICE_TYPES_ISY[device_class]) ): return device_class, device_type return (None, device_type) diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index e1ab689eb7a0ff..f983458658335f 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -97,10 +97,8 @@ def _check_for_insteon_type( platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( - [ - device_type.startswith(t) - for t in set(NODE_FILTERS[platform][FILTER_INSTEON_TYPE]) - ] + device_type.startswith(t) + for t in set(NODE_FILTERS[platform][FILTER_INSTEON_TYPE]) ): # Hacky special-cases for certain devices with different platforms @@ -164,10 +162,8 @@ def _check_for_zwave_cat( platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( - [ - device_type.startswith(t) - for t in set(NODE_FILTERS[platform][FILTER_ZWAVE_CAT]) - ] + device_type.startswith(t) + for t in set(NODE_FILTERS[platform][FILTER_ZWAVE_CAT]) ): hass_isy_data[ISY994_NODES][platform].append(node) diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 83ffc2557c2fb4..1829826834c343 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -24,6 +24,7 @@ class ColorTempModes(Enum): + # pylint: disable=invalid-name """Color temperature modes for config validation.""" ABSOLUTE = "DPT-7.600" @@ -31,6 +32,7 @@ class ColorTempModes(Enum): class SupportedPlatforms(Enum): + # pylint: disable=invalid-name """Supported platforms.""" BINARY_SENSOR = "binary_sensor" diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index 985f59a67156db..fb7b949e3edd3b 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -4,7 +4,7 @@ import asyncio import logging import os -from typing import Any, List, Optional, TypedDict +from typing import Any, TypedDict from urllib.parse import urlparse from aiohttp import BasicAuth, FormData @@ -106,14 +106,14 @@ class MessageT(TypedDict, total=False): username: str # Optional key icon_url: str # Optional key icon_emoji: str # Optional key - blocks: List[Any] # Optional key + blocks: list[Any] # Optional key async def async_get_service( hass: HomeAssistantType, config: ConfigType, - discovery_info: Optional[DiscoveryInfoType] = None, -) -> Optional[SlackNotificationService]: + discovery_info: DiscoveryInfoType | None = None, +) -> SlackNotificationService | None: """Set up the Slack notification service.""" session = aiohttp_client.async_get_clientsession(hass) client = WebClient(token=config[CONF_API_KEY], run_async=True, session=session) @@ -147,7 +147,7 @@ def _async_get_filename_from_url(url: str) -> str: @callback -def _async_sanitize_channel_names(channel_list: List[str]) -> List[str]: +def _async_sanitize_channel_names(channel_list: list[str]) -> list[str]: """Remove any # symbols from a channel list.""" return [channel.lstrip("#") for channel in channel_list] @@ -174,8 +174,8 @@ def __init__( hass: HomeAssistantType, client: WebClient, default_channel: str, - username: Optional[str], - icon: Optional[str], + username: str | None, + icon: str | None, ) -> None: """Initialize.""" self._client = client @@ -187,9 +187,9 @@ def __init__( async def _async_send_local_file_message( self, path: str, - targets: List[str], + targets: list[str], message: str, - title: Optional[str], + title: str | None, ) -> None: """Upload a local file (with message) to Slack.""" if not self._hass.config.is_allowed_path(path): @@ -213,12 +213,12 @@ async def _async_send_local_file_message( async def _async_send_remote_file_message( self, url: str, - targets: List[str], + targets: list[str], message: str, - title: Optional[str], + title: str | None, *, - username: Optional[str] = None, - password: Optional[str] = None, + username: str | None = None, + password: str | None = None, ) -> None: """Upload a remote file (with message) to Slack. @@ -263,13 +263,13 @@ async def _async_send_remote_file_message( async def _async_send_text_only_message( self, - targets: List[str], + targets: list[str], message: str, - title: Optional[str], + title: str | None, *, - username: Optional[str] = None, - icon: Optional[str] = None, - blocks: Optional[Any] = None, + username: str | None = None, + icon: str | None = None, + blocks: Any | None = None, ) -> None: """Send a text-only message.""" message_dict: MessageT = {"link_names": True, "text": message} diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index c2bf63063e5e46..184ed1f2719c67 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -157,7 +157,7 @@ def remove_provider(self, provider): def check_idle(self): """Reset access token if all providers are idle.""" - if all([p.idle for p in self._outputs.values()]): + if all(p.idle for p in self._outputs.values()): self.access_token = None def start(self): diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index b6ca78816156a5..86bf4c24407765 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -330,7 +330,7 @@ def _render_template_attr(data, attribute): attribute_templ = data.get(attribute) if attribute_templ: if any( - [isinstance(attribute_templ, vtype) for vtype in [float, int, str]] + isinstance(attribute_templ, vtype) for vtype in [float, int, str] ): data[attribute] = attribute_templ else: diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 1834d22855c389..2702f26a3d3c20 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -470,7 +470,7 @@ def supported_features(self): if SERVICE_MEDIA_PREVIOUS_TRACK in self._cmds: flags |= SUPPORT_PREVIOUS_TRACK - if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]]): + if any(cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]): flags |= SUPPORT_VOLUME_STEP if SERVICE_VOLUME_SET in self._cmds: flags |= SUPPORT_VOLUME_SET diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 2b1a367215bbfa..940e076c813656 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -33,6 +33,7 @@ class HeatingType(enum.Enum): + # pylint: disable=invalid-name """Possible options for heating type.""" generic = "generic" diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index c08ddddf4a5f25..e2e0b06e34203d 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1070,11 +1070,9 @@ def get_data_manager_by_webhook_id( def get_all_data_managers(hass: HomeAssistant) -> Tuple[DataManager, ...]: """Get all configured data managers.""" return tuple( - [ - config_entry_data[const.DATA_MANAGER] - for config_entry_data in hass.data[const.DOMAIN].values() - if const.DATA_MANAGER in config_entry_data - ] + config_entry_data[const.DATA_MANAGER] + for config_entry_data in hass.data[const.DOMAIN].values() + if const.DATA_MANAGER in config_entry_data ) @@ -1101,11 +1099,7 @@ async def async_create_entities( def get_platform_attributes(platform: str) -> Tuple[WithingsAttribute, ...]: """Get withings attributes used for a specific platform.""" return tuple( - [ - attribute - for attribute in WITHINGS_ATTRIBUTES - if attribute.platform == platform - ] + attribute for attribute in WITHINGS_ATTRIBUTES if attribute.platform == platform ) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index d105572c182a23..1c1ab28ba5d5b6 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -91,7 +91,7 @@ async def async_set_present_value(self, value: float) -> bool: self.error("Could not set value: %s", ex) return False if isinstance(res, list) and all( - [record.status == Status.SUCCESS for record in res[0]] + record.status == Status.SUCCESS for record in res[0] ): return True return False diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index 1647c5ce52d96b..e02f6c01836b3e 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -434,7 +434,7 @@ def check_result(res: list) -> bool: if not isinstance(res, list): return False - return all([record.status == Status.SUCCESS for record in res[0]]) + return all(record.status == Status.SUCCESS for record in res[0]) @registries.ZIGBEE_CHANNEL_REGISTRY.register(hvac.UserInterface.cluster_id) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 1d3f767353b04f..ac4f53d2a8c716 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -176,6 +176,7 @@ class RadioType(enum.Enum): + # pylint: disable=invalid-name """Possible options for radio type.""" znp = ( diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index 45114c677aff1f..e202def46c5ab7 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -301,7 +301,7 @@ async def async_open_cover(self, **kwargs): self._on_off_channel.on(), ] results = await asyncio.gather(*tasks, return_exceptions=True) - if any([isinstance(result, Exception) for result in results]): + if any(isinstance(result, Exception) for result in results): self.debug("couldn't open cover") return diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index 68247537847f5b..f20c9f7e328ae4 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -95,7 +95,7 @@ def _start_hub(): async def startup(): """Start hub socket after all climate entity is set up.""" nonlocal hub_is_initialized - if not all([device.is_initialized for device in devices]): + if not all(device.is_initialized for device in devices): return if hub_is_initialized: diff --git a/homeassistant/core.py b/homeassistant/core.py index b62dd1ee7d5763..3483dc96069a34 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -153,6 +153,7 @@ def is_callback(func: Callable[..., Any]) -> bool: @enum.unique class HassJobType(enum.Enum): + # pylint: disable=invalid-name """Represent a job type.""" Coroutinefunction = 1 @@ -198,6 +199,7 @@ def _get_callable_job_type(target: Callable) -> HassJobType: class CoreState(enum.Enum): + # pylint: disable=invalid-name """Represent the current state of Home Assistant.""" not_running = "NOT_RUNNING" @@ -589,6 +591,7 @@ def as_dict(self) -> Dict[str, Optional[str]]: class EventOrigin(enum.Enum): + # pylint: disable=invalid-name """Represent the origin of an event.""" local = "LOCAL" diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index 279bc0f686f82b..ffc32efeb2a111 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -20,6 +20,7 @@ class UndefinedType(Enum): + # pylint: disable=invalid-name """Singleton type for use with not set sentinel values.""" _singleton = 0 diff --git a/requirements_test.txt b/requirements_test.txt index 12f215f177a46b..1f0a3c4dba27ef 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -10,8 +10,8 @@ jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 pre-commit==2.10.1 -pylint==2.6.0 -astroid==2.4.2 +pylint==2.7.2 +astroid==2.5.1 pipdeptree==1.0.0 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 From d2db58d138cfe9eba7073c7f32f0faa6ef606fdc Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 1 Mar 2021 09:17:41 +0100 Subject: [PATCH 0876/1818] Fix generic-x86-64 build (#47214) Replace the wrong Docker Hub repository slipped in during testing. --- machine/generic-x86-64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine/generic-x86-64 b/machine/generic-x86-64 index e858c38222192b..4c83228387ddb9 100644 --- a/machine/generic-x86-64 +++ b/machine/generic-x86-64 @@ -1,5 +1,5 @@ ARG BUILD_VERSION -FROM agners/amd64-homeassistant:$BUILD_VERSION +FROM homeassistant/amd64-homeassistant:$BUILD_VERSION RUN apk --no-cache add \ libva-intel-driver \ From 8513250628a61b387304c7682d664200fff757bf Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Sun, 28 Feb 2021 18:21:04 -0500 Subject: [PATCH 0877/1818] Update AlarmDecoder dependency (#46841) --- homeassistant/components/alarmdecoder/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index 1697858718d273..c3e72e407c2620 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -2,7 +2,7 @@ "domain": "alarmdecoder", "name": "AlarmDecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder", - "requirements": ["adext==0.3"], + "requirements": ["adext==0.4.1"], "codeowners": ["@ajschmidt8"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 010cb3d49e19d9..05bc4f01b79d44 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -108,7 +108,7 @@ adafruit-circuitpython-mcp230xx==2.2.2 adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder -adext==0.3 +adext==0.4.1 # homeassistant.components.adguard adguardhome==0.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5e8df0c89fbde3..78cb4d8f8ce39f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -48,7 +48,7 @@ accuweather==0.1.0 adb-shell[async]==0.2.1 # homeassistant.components.alarmdecoder -adext==0.3 +adext==0.4.1 # homeassistant.components.adguard adguardhome==0.4.2 From aa9b4458568e93be1929bc5aa9bfef20b2ca1ab0 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 28 Feb 2021 21:25:40 +0100 Subject: [PATCH 0878/1818] Fix Xiaomi Miio discovery (#47134) --- .../components/xiaomi_miio/config_flow.py | 17 ++++++++---- .../components/xiaomi_miio/strings.json | 26 +++++++++---------- .../xiaomi_miio/test_config_flow.py | 14 +++++----- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index d7e2198f72f024..2e069b30da3cdd 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -1,5 +1,6 @@ """Config flow to configure Xiaomi Miio.""" import logging +from re import search import voluptuous as vol @@ -24,7 +25,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_GATEWAY_NAME = "Xiaomi Gateway" -DEFAULT_DEVICE_NAME = "Xiaomi Device" DEVICE_SETTINGS = { vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), @@ -57,14 +57,21 @@ async def async_step_zeroconf(self, discovery_info): name = discovery_info.get("name") self.host = discovery_info.get("host") self.mac = discovery_info.get("properties", {}).get("mac") + if self.mac is None: + poch = discovery_info.get("properties", {}).get("poch", "") + result = search(r"mac=\w+", poch) + if result is not None: + self.mac = result.group(0).split("=")[1] if not name or not self.host or not self.mac: return self.async_abort(reason="not_xiaomi_miio") + self.mac = format_mac(self.mac) + # Check which device is discovered. for gateway_model in MODELS_GATEWAY: if name.startswith(gateway_model.replace(".", "-")): - unique_id = format_mac(self.mac) + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) @@ -75,12 +82,12 @@ async def async_step_zeroconf(self, discovery_info): return await self.async_step_device() for device_model in MODELS_ALL_DEVICES: if name.startswith(device_model.replace(".", "-")): - unique_id = format_mac(self.mac) + unique_id = self.mac await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured({CONF_HOST: self.host}) self.context.update( - {"title_placeholders": {"name": f"Miio Device {self.host}"}} + {"title_placeholders": {"name": f"{device_model} {self.host}"}} ) return await self.async_step_device() @@ -132,7 +139,7 @@ async def async_step_device(self, user_input=None): ) # Setup all other Miio Devices - name = user_input.get(CONF_NAME, DEFAULT_DEVICE_NAME) + name = user_input.get(CONF_NAME, model) for device_model in MODELS_ALL_DEVICES: if model.startswith(device_model): diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 90710baebca73d..e3d9376bc318c7 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -1,24 +1,24 @@ { "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown_device": "The device model is not known, not able to setup the device using config flow." + }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { - "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway", - "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", "data": { "host": "[%key:common::config_flow::data::ip%]", - "token": "[%key:common::config_flow::data::api_token%]", - "model": "Device model (Optional)" - } + "model": "Device model (Optional)", + "token": "[%key:common::config_flow::data::api_token%]" + }, + "description": "You will need the 32 character [%key:common::config_flow::data::api_token%], see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this [%key:common::config_flow::data::api_token%] is different from the key used by the Xiaomi Aqara integration.", + "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" } - }, - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown_device": "The device model is not known, not able to setup the device using config flow." - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" } } } diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index f4f7b5e2b46fe5..f53fe6e40b4867 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -6,10 +6,7 @@ from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.components.xiaomi_miio import const -from homeassistant.components.xiaomi_miio.config_flow import ( - DEFAULT_DEVICE_NAME, - DEFAULT_GATEWAY_NAME, -) +from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN ZEROCONF_NAME = "name" @@ -21,6 +18,7 @@ TEST_NAME = "Test_Gateway" TEST_MODEL = const.MODELS_GATEWAY[0] TEST_MAC = "ab:cd:ef:gh:ij:kl" +TEST_MAC_DEVICE = "abcdefghijkl" TEST_GATEWAY_ID = TEST_MAC TEST_HARDWARE_VERSION = "AB123" TEST_FIRMWARE_VERSION = "1.2.3_456" @@ -294,7 +292,7 @@ async def test_config_flow_step_device_manual_model_succes(hass): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == overwrite_model assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, @@ -328,7 +326,7 @@ async def config_flow_device_success(hass, model_to_test): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == model_to_test assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, @@ -346,7 +344,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): data={ zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: zeroconf_name_to_test, - ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, + ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"}, }, ) @@ -368,7 +366,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test): ) assert result["type"] == "create_entry" - assert result["title"] == DEFAULT_DEVICE_NAME + assert result["title"] == model_to_test assert result["data"] == { const.CONF_FLOW_TYPE: const.CONF_DEVICE, CONF_HOST: TEST_HOST, From 0e951f288b2da43bc0398dd94d2eea8b9552cb26 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 28 Feb 2021 21:41:09 -0700 Subject: [PATCH 0879/1818] Bump simplisafe-python to 9.6.7 (#47206) --- 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 b18bafb0bbfaa9..de5199ccd4cda6 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.4"], + "requirements": ["simplisafe-python==9.6.7"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 05bc4f01b79d44..1418ebb2d7b657 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2044,7 +2044,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.4 +simplisafe-python==9.6.7 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 78cb4d8f8ce39f..c5f401d2eca1ac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1051,7 +1051,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.4 +simplisafe-python==9.6.7 # homeassistant.components.slack slackclient==2.5.0 From 62e224ecb098d8cd0ffe45eed5450e9c3b30c1fa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 28 Feb 2021 22:42:09 -0600 Subject: [PATCH 0880/1818] Increment the homekit config version when restarting (#47209) If an entity changes between restart the iOS/controller device may have cached the old chars for the accessory. To force the iOS/controller to reload the chars, we increment the config version when Home Assistant restarts --- homeassistant/components/homekit/__init__.py | 7 +++++-- tests/components/homekit/test_homekit.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 534ea3c6f95270..c042872f4cd632 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -491,8 +491,11 @@ def setup(self, zeroconf_instance): # as pyhap uses a random one until state is restored if os.path.exists(persist_file): self.driver.load() - else: - self.driver.persist() + self.driver.state.config_version += 1 + if self.driver.state.config_version > 65535: + self.driver.state.config_version = 1 + + self.driver.persist() def reset_accessories(self, entity_ids): """Reset the accessory to load the latest configuration.""" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index ec32460268488d..9ce3e96f06f985 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -540,6 +540,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): assert (device_registry.CONNECTION_NETWORK_MAC, formatted_mac) in device.connections assert len(device_reg.devices) == 1 + assert homekit.driver.state.config_version == 2 async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf): From 4907c12964a8d6eb228c144456d5f0fa8f141c8d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 02:00:31 -0600 Subject: [PATCH 0881/1818] Bump HAP-python to 3.3.2 to fix unavailable condition on restart (#47213) Fixes https://github.com/ikalchev/HAP-python/compare/v3.3.1...v3.3.2 --- homeassistant/components/homekit/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/manifest.json b/homeassistant/components/homekit/manifest.json index 4d1598c728c09e..ac3fb0251e2e74 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.1", + "HAP-python==3.3.2", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index 1418ebb2d7b657..48d2374c034eaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.1 +HAP-python==3.3.2 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5f401d2eca1ac..09eacd9b78a5ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.1 +HAP-python==3.3.2 # homeassistant.components.flick_electric PyFlick==0.0.2 From b9edd0d7added1bce1fbfb58650ba7a5000049b0 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 1 Mar 2021 09:17:41 +0100 Subject: [PATCH 0882/1818] Fix generic-x86-64 build (#47214) Replace the wrong Docker Hub repository slipped in during testing. --- machine/generic-x86-64 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine/generic-x86-64 b/machine/generic-x86-64 index e858c38222192b..4c83228387ddb9 100644 --- a/machine/generic-x86-64 +++ b/machine/generic-x86-64 @@ -1,5 +1,5 @@ ARG BUILD_VERSION -FROM agners/amd64-homeassistant:$BUILD_VERSION +FROM homeassistant/amd64-homeassistant:$BUILD_VERSION RUN apk --no-cache add \ libva-intel-driver \ From f192b3c1e5e5de21d5e5928d0a61b46fbf79e8ea Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 1 Mar 2021 08:32:13 +0000 Subject: [PATCH 0883/1818] Bumped version to 2021.3.0b5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index c6cccb49d434bf..86abfa635f0e1e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 0592309b652eaf541e281d64e3149b021cb2f919 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 03:41:04 -0500 Subject: [PATCH 0884/1818] Add hassio addon_update service and hassio config entry with addon and OS devices and entities (#46342) * add addon_update service, use config flow to set up config entry, create disabled sensors * move most of entity logic to common entity class, improve device info, get rid of config_flow user step * fix setup logic * additional refactor * fix refactored logic * fix config flow tests * add test for addon_update service and get_addons_info * add entry setup and unload test and fix update coordinator * handle if entry setup calls unload * return nothing for coordinator if entry is being reloaded because coordinator will get recreated anyway * remove entry when HA instance is no longer hassio and add corresponding test * handle adding and removing device registry entries * better config entry reload logic * fix comment * bugfix * fix flake error * switch pass to return * use repository attribute for model and fallback to url * use custom 'system' source since hassio source is misleading * Update homeassistant/components/hassio/entity.py Co-authored-by: Franck Nijhof * update remove addons function name * Update homeassistant/components/hassio/__init__.py Co-authored-by: Franck Nijhof * fix import * pop coordinator after unload * additional fixes * always pass in sensor name when creating entity * prefix one more function with async and fix tests * use supervisor info for addons since list is already filtered on what's installed * remove unused service * update sensor names * remove added handler function * use walrus * add OS device and sensors * fix * re-add addon_update service schema * add more test coverage and exclude entities from tests * check if instance is using hass OS in order to create OS entities Co-authored-by: Franck Nijhof --- .coveragerc | 3 + homeassistant/components/hassio/__init__.py | 165 ++++++++++++- .../components/hassio/binary_sensor.py | 50 ++++ .../components/hassio/config_flow.py | 22 ++ homeassistant/components/hassio/const.py | 9 +- homeassistant/components/hassio/entity.py | 93 +++++++ homeassistant/components/hassio/sensor.py | 52 ++++ homeassistant/components/hassio/services.yaml | 12 + tests/components/hassio/test_config_flow.py | 36 +++ tests/components/hassio/test_init.py | 229 +++++++++++++++++- 10 files changed, 661 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/hassio/binary_sensor.py create mode 100644 homeassistant/components/hassio/config_flow.py create mode 100644 homeassistant/components/hassio/entity.py create mode 100644 homeassistant/components/hassio/sensor.py create mode 100644 tests/components/hassio/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 50fcf151821634..281c8d5be83f18 100644 --- a/.coveragerc +++ b/.coveragerc @@ -377,6 +377,9 @@ omit = homeassistant/components/harmony/data.py homeassistant/components/harmony/remote.py homeassistant/components/harmony/util.py + homeassistant/components/hassio/binary_sensor.py + homeassistant/components/hassio/entity.py + homeassistant/components/hassio/sensor.py homeassistant/components/haveibeenpwned/sensor.py homeassistant/components/hdmi_cec/* homeassistant/components/heatmiser/climate.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index fdeb10bcafe35b..82797874445cbd 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -1,24 +1,29 @@ """Support for Hass.io.""" +import asyncio from datetime import timedelta import logging import os -from typing import Optional +from typing import Any, Dict, List, Optional import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.homeassistant import SERVICE_CHECK_CONFIG import homeassistant.config as conf_util +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_NAME, + ATTR_SERVICE, EVENT_CORE_CONFIG_UPDATE, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, ) -from homeassistant.core import DOMAIN as HASS_DOMAIN, callback +from homeassistant.core import DOMAIN as HASS_DOMAIN, Config, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceRegistry, async_get_registry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.loader import bind_hass from homeassistant.util.dt import utcnow @@ -32,7 +37,11 @@ ATTR_HOMEASSISTANT, ATTR_INPUT, ATTR_PASSWORD, + ATTR_REPOSITORY, + ATTR_SLUG, ATTR_SNAPSHOT, + ATTR_URL, + ATTR_VERSION, DOMAIN, ) from .discovery import async_setup_discovery_view @@ -46,6 +55,7 @@ STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 +PLATFORMS = ["binary_sensor", "sensor"] CONF_FRONTEND_REPO = "development_repo" @@ -62,9 +72,12 @@ DATA_SUPERVISOR_INFO = "hassio_supervisor_info" HASSIO_UPDATE_INTERVAL = timedelta(minutes=55) +ADDONS_COORDINATOR = "hassio_addons_coordinator" + SERVICE_ADDON_START = "addon_start" SERVICE_ADDON_STOP = "addon_stop" SERVICE_ADDON_RESTART = "addon_restart" +SERVICE_ADDON_UPDATE = "addon_update" SERVICE_ADDON_STDIN = "addon_stdin" SERVICE_HOST_SHUTDOWN = "host_shutdown" SERVICE_HOST_REBOOT = "host_reboot" @@ -110,6 +123,7 @@ SERVICE_ADDON_START: ("/addons/{addon}/start", SCHEMA_ADDON, 60, False), SERVICE_ADDON_STOP: ("/addons/{addon}/stop", SCHEMA_ADDON, 60, False), SERVICE_ADDON_RESTART: ("/addons/{addon}/restart", SCHEMA_ADDON, 60, False), + SERVICE_ADDON_UPDATE: ("/addons/{addon}/update", SCHEMA_ADDON, 60, False), SERVICE_ADDON_STDIN: ("/addons/{addon}/stdin", SCHEMA_ADDON_STDIN, 60, False), SERVICE_HOST_SHUTDOWN: ("/host/shutdown", SCHEMA_NO_DATA, 60, False), SERVICE_HOST_REBOOT: ("/host/reboot", SCHEMA_NO_DATA, 60, False), @@ -286,13 +300,17 @@ def get_supervisor_ip(): return os.environ["SUPERVISOR"].partition(":")[0] -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: Config) -> bool: """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 config_entries := hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.async_remove(config_entries[0].entry_id) + ) return False async_load_websocket_api(hass) @@ -402,6 +420,8 @@ async def update_info_data(now): hass.data[DATA_CORE_INFO] = await hassio.get_core_info() hass.data[DATA_SUPERVISOR_INFO] = await hassio.get_supervisor_info() hass.data[DATA_OS_INFO] = await hassio.get_os_info() + if ADDONS_COORDINATOR in hass.data: + await hass.data[ADDONS_COORDINATOR].async_refresh() except HassioAPIError as err: _LOGGER.warning("Can't read last version: %s", err) @@ -455,4 +475,143 @@ async def async_handle_core_service(call): # Init add-on ingress panels await async_setup_addon_panel(hass, hassio) + hass.async_create_task( + hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"}) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up a config entry.""" + dev_reg = await async_get_registry(hass) + coordinator = HassioDataUpdateCoordinator(hass, config_entry, dev_reg) + hass.data[ADDONS_COORDINATOR] = coordinator + await coordinator.async_refresh() + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) + return True + + +async def async_unload_entry( + hass: HomeAssistantType, config_entry: ConfigEntry +) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS + ] + ) + ) + + # Pop add-on data + hass.data.pop(ADDONS_COORDINATOR, None) + + return unload_ok + + +@callback +def async_register_addons_in_dev_reg( + entry_id: str, dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] +) -> None: + """Register addons in the device registry.""" + for addon in addons: + dev_reg.async_get_or_create( + config_entry_id=entry_id, + identifiers={(DOMAIN, addon[ATTR_SLUG])}, + manufacturer=addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL) or "unknown", + model="Home Assistant Add-on", + sw_version=addon[ATTR_VERSION], + name=addon[ATTR_NAME], + entry_type=ATTR_SERVICE, + ) + + +@callback +def async_register_os_in_dev_reg( + entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any] +) -> None: + """Register OS in the device registry.""" + dev_reg.async_get_or_create( + config_entry_id=entry_id, + identifiers={(DOMAIN, "OS")}, + manufacturer="Home Assistant", + model="Home Assistant Operating System", + sw_version=os_dict[ATTR_VERSION], + name="Home Assistant Operating System", + entry_type=ATTR_SERVICE, + ) + + +@callback +def async_remove_addons_from_dev_reg( + dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] +) -> None: + """Remove addons from the device registry.""" + for addon_slug in addons: + if dev := dev_reg.async_get_device({(DOMAIN, addon_slug)}): + dev_reg.async_remove_device(dev.id) + + +class HassioDataUpdateCoordinator(DataUpdateCoordinator): + """Class to retrieve Hass.io status.""" + + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, dev_reg: DeviceRegistry + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_method=self._async_update_data, + ) + self.data = {} + self.entry_id = config_entry.entry_id + self.dev_reg = dev_reg + self.is_hass_os = "hassos" in get_info(self.hass) + + async def _async_update_data(self) -> Dict[str, Any]: + """Update data via library.""" + new_data = {} + addon_data = get_supervisor_info(self.hass) + + new_data["addons"] = { + addon[ATTR_SLUG]: addon for addon in addon_data.get("addons", []) + } + if self.is_hass_os: + new_data["os"] = get_os_info(self.hass) + + # If this is the initial refresh, register all addons and return the dict + if not self.data: + async_register_addons_in_dev_reg( + self.entry_id, self.dev_reg, new_data["addons"].values() + ) + if self.is_hass_os: + async_register_os_in_dev_reg( + self.entry_id, self.dev_reg, new_data["os"] + ) + return new_data + + # Remove add-ons that are no longer installed from device registry + if removed_addons := list( + set(self.data["addons"].keys()) - set(new_data["addons"].keys()) + ): + async_remove_addons_from_dev_reg(self.dev_reg, removed_addons) + + # If there are new add-ons, we should reload the config entry so we can + # create new devices and entities. We can return an empty dict because + # coordinator will be recreated. + if list(set(new_data["addons"].keys()) - set(self.data["addons"].keys())): + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry_id) + ) + return {} + + return new_data diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py new file mode 100644 index 00000000000000..c3daaa07f2843f --- /dev/null +++ b/homeassistant/components/hassio/binary_sensor.py @@ -0,0 +1,50 @@ +"""Binary sensor platform for Hass.io addons.""" +from typing import Callable, List + +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from . import ADDONS_COORDINATOR +from .const import ATTR_UPDATE_AVAILABLE +from .entity import HassioAddonEntity, HassioOSEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Binary sensor set up for Hass.io config entry.""" + coordinator = hass.data[ADDONS_COORDINATOR] + + entities = [ + HassioAddonBinarySensor( + coordinator, addon, ATTR_UPDATE_AVAILABLE, "Update Available" + ) + for addon in coordinator.data.values() + ] + if coordinator.is_hass_os: + entities.append( + HassioOSBinarySensor(coordinator, ATTR_UPDATE_AVAILABLE, "Update Available") + ) + async_add_entities(entities) + + +class HassioAddonBinarySensor(HassioAddonEntity, BinarySensorEntity): + """Binary sensor to track whether an update is available for a Hass.io add-on.""" + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self.addon_info[self.attribute_name] + + +class HassioOSBinarySensor(HassioOSEntity, BinarySensorEntity): + """Binary sensor to track whether an update is available for Hass.io OS.""" + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + return self.os_info[self.attribute_name] diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py new file mode 100644 index 00000000000000..56c7d324a61f1c --- /dev/null +++ b/homeassistant/components/hassio/config_flow.py @@ -0,0 +1,22 @@ +"""Config flow for Home Assistant Supervisor integration.""" +import logging + +from homeassistant import config_entries + +from . import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Home Assistant Supervisor.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_system(self, user_input=None): + """Handle the initial step.""" + # We only need one Hass.io config entry + await self.async_set_unique_id(DOMAIN) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=DOMAIN.title(), data={}) diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index b2878c8143f3fd..417a62a1a8c08d 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -29,7 +29,6 @@ X_HASS_USER_ID = "X-Hass-User-ID" X_HASS_IS_ADMIN = "X-Hass-Is-Admin" - WS_TYPE = "type" WS_ID = "id" @@ -38,3 +37,11 @@ WS_TYPE_SUBSCRIBE = "supervisor/subscribe" EVENT_SUPERVISOR_EVENT = "supervisor_event" + +# Add-on keys +ATTR_VERSION = "version" +ATTR_VERSION_LATEST = "version_latest" +ATTR_UPDATE_AVAILABLE = "update_available" +ATTR_SLUG = "slug" +ATTR_URL = "url" +ATTR_REPOSITORY = "repository" diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py new file mode 100644 index 00000000000000..daadeb514a2e90 --- /dev/null +++ b/homeassistant/components/hassio/entity.py @@ -0,0 +1,93 @@ +"""Base for Hass.io entities.""" +from typing import Any, Dict + +from homeassistant.const import ATTR_NAME +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import DOMAIN, HassioDataUpdateCoordinator +from .const import ATTR_SLUG + + +class HassioAddonEntity(CoordinatorEntity): + """Base entity for a Hass.io add-on.""" + + def __init__( + self, + coordinator: HassioDataUpdateCoordinator, + addon: Dict[str, Any], + attribute_name: str, + sensor_name: str, + ) -> None: + """Initialize base entity.""" + self.addon_slug = addon[ATTR_SLUG] + self.addon_name = addon[ATTR_NAME] + self._data_key = "addons" + self.attribute_name = attribute_name + self.sensor_name = sensor_name + super().__init__(coordinator) + + @property + def addon_info(self) -> Dict[str, Any]: + """Return add-on info.""" + return self.coordinator.data[self._data_key][self.addon_slug] + + @property + def name(self) -> str: + """Return entity name.""" + return f"{self.addon_name}: {self.sensor_name}" + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return False + + @property + def unique_id(self) -> str: + """Return unique ID for entity.""" + return f"{self.addon_slug}_{self.attribute_name}" + + @property + def device_info(self) -> Dict[str, Any]: + """Return device specific attributes.""" + return {"identifiers": {(DOMAIN, self.addon_slug)}} + + +class HassioOSEntity(CoordinatorEntity): + """Base Entity for Hass.io OS.""" + + def __init__( + self, + coordinator: HassioDataUpdateCoordinator, + attribute_name: str, + sensor_name: str, + ) -> None: + """Initialize base entity.""" + self._data_key = "os" + self.attribute_name = attribute_name + self.sensor_name = sensor_name + super().__init__(coordinator) + + @property + def os_info(self) -> Dict[str, Any]: + """Return OS info.""" + return self.coordinator.data[self._data_key] + + @property + def name(self) -> str: + """Return entity name.""" + return f"Home Assistant Operating System: {self.sensor_name}" + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return False + + @property + def unique_id(self) -> str: + """Return unique ID for entity.""" + return f"home_assistant_os_{self.attribute_name}" + + @property + def device_info(self) -> Dict[str, Any]: + """Return device specific attributes.""" + return {"identifiers": {(DOMAIN, "OS")}} diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py new file mode 100644 index 00000000000000..857f4831587af2 --- /dev/null +++ b/homeassistant/components/hassio/sensor.py @@ -0,0 +1,52 @@ +"""Sensor platform for Hass.io addons.""" +from typing import Callable, List + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from . import ADDONS_COORDINATOR +from .const import ATTR_VERSION, ATTR_VERSION_LATEST +from .entity import HassioAddonEntity, HassioOSEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Sensor set up for Hass.io config entry.""" + coordinator = hass.data[ADDONS_COORDINATOR] + + entities = [] + + for attribute_name, sensor_name in ( + (ATTR_VERSION, "Version"), + (ATTR_VERSION_LATEST, "Newest Version"), + ): + for addon in coordinator.data.values(): + entities.append( + HassioAddonSensor(coordinator, addon, attribute_name, sensor_name) + ) + if coordinator.is_hass_os: + entities.append(HassioOSSensor(coordinator, attribute_name, sensor_name)) + + async_add_entities(entities) + + +class HassioAddonSensor(HassioAddonEntity): + """Sensor to track a Hass.io add-on attribute.""" + + @property + def state(self) -> str: + """Return state of entity.""" + return self.addon_info[self.attribute_name] + + +class HassioOSSensor(HassioOSEntity): + """Sensor to track a Hass.io add-on attribute.""" + + @property + def state(self) -> str: + """Return state of entity.""" + return self.os_info[self.attribute_name] diff --git a/homeassistant/components/hassio/services.yaml b/homeassistant/components/hassio/services.yaml index 3570a857c554e1..0652b65d6e281f 100644 --- a/homeassistant/components/hassio/services.yaml +++ b/homeassistant/components/hassio/services.yaml @@ -46,6 +46,18 @@ addon_stop: selector: addon: +addon_update: + name: Update add-on. + description: Update add-on. This service should be used with caution since add-on updates can contain breaking changes. It is highly recommended that you review release notes/change logs before updating an add-on. + fields: + addon: + name: Add-on + required: true + description: The add-on slug. + example: core_ssh + selector: + addon: + host_reboot: name: Reboot the host system. description: Reboot the host system. diff --git a/tests/components/hassio/test_config_flow.py b/tests/components/hassio/test_config_flow.py new file mode 100644 index 00000000000000..c2d306183f0804 --- /dev/null +++ b/tests/components/hassio/test_config_flow.py @@ -0,0 +1,36 @@ +"""Test the Home Assistant Supervisor config flow.""" +from unittest.mock import patch + +from homeassistant import setup +from homeassistant.components.hassio import DOMAIN + + +async def test_config_flow(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.hassio.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hassio.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + assert result["type"] == "create_entry" + assert result["title"] == DOMAIN.title() + assert result["data"] == {} + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_multiple_entries(hass): + """Test creating multiple hassio entries.""" + await test_config_flow(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "system"} + ) + assert result["type"] == "abort" + assert result["reason"] == "already_configured" diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 2efb5b0744e9d6..bd9eb30be5c4af 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,17 +1,91 @@ """The tests for the hassio component.""" +from datetime import timedelta import os from unittest.mock import patch +import pytest + from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import frontend -from homeassistant.components.hassio import STORAGE_KEY +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.hassio import ADDONS_COORDINATOR, DOMAIN, STORAGE_KEY +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.helpers.device_registry import async_get from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util -from . import mock_all # noqa +from tests.common import MockConfigEntry, async_fire_time_changed MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} +@pytest.fixture(autouse=True) +def mock_all(aioclient_mock, request): + """Mock all setup requests.""" + aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"}) + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"}) + aioclient_mock.get( + "http://127.0.0.1/info", + json={ + "result": "ok", + "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None}, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/host/info", + json={ + "result": "ok", + "data": { + "result": "ok", + "data": { + "chassis": "vm", + "operating_system": "Debian GNU/Linux 10 (buster)", + "kernel": "4.19.0-6-amd64", + }, + }, + }, + ) + aioclient_mock.get( + "http://127.0.0.1/core/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/os/info", + json={"result": "ok", "data": {"version_latest": "1.0.0"}}, + ) + aioclient_mock.get( + "http://127.0.0.1/supervisor/info", + json={ + "result": "ok", + "data": {"version_latest": "1.0.0"}, + "addons": [ + { + "name": "test", + "slug": "test", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com/home-assistant/addons/test", + }, + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ], + }, + ) + aioclient_mock.get( + "http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}} + ) + + async def test_setup_api_ping(hass, aioclient_mock): """Test setup with API ping.""" with patch.dict(os.environ, MOCK_ENVIRON): @@ -193,6 +267,7 @@ async def test_service_register(hassio_env, hass): assert hass.services.has_service("hassio", "addon_start") assert hass.services.has_service("hassio", "addon_stop") assert hass.services.has_service("hassio", "addon_restart") + assert hass.services.has_service("hassio", "addon_update") assert hass.services.has_service("hassio", "addon_stdin") assert hass.services.has_service("hassio", "host_shutdown") assert hass.services.has_service("hassio", "host_reboot") @@ -210,6 +285,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): aioclient_mock.post("http://127.0.0.1/addons/test/start", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/stop", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/restart", json={"result": "ok"}) + aioclient_mock.post("http://127.0.0.1/addons/test/update", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/addons/test/stdin", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/host/shutdown", json={"result": "ok"}) aioclient_mock.post("http://127.0.0.1/host/reboot", json={"result": "ok"}) @@ -225,19 +301,20 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): await hass.services.async_call("hassio", "addon_start", {"addon": "test"}) await hass.services.async_call("hassio", "addon_stop", {"addon": "test"}) await hass.services.async_call("hassio", "addon_restart", {"addon": "test"}) + await hass.services.async_call("hassio", "addon_update", {"addon": "test"}) await hass.services.async_call( "hassio", "addon_stdin", {"addon": "test", "input": "test"} ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 7 + assert aioclient_mock.call_count == 8 assert aioclient_mock.mock_calls[-1][2] == "test" await hass.services.async_call("hassio", "host_shutdown", {}) await hass.services.async_call("hassio", "host_reboot", {}) await hass.async_block_till_done() - assert aioclient_mock.call_count == 9 + assert aioclient_mock.call_count == 10 await hass.services.async_call("hassio", "snapshot_full", {}) await hass.services.async_call( @@ -247,7 +324,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 11 + assert aioclient_mock.call_count == 12 assert aioclient_mock.mock_calls[-1][2] == { "addons": ["test"], "folders": ["ssl"], @@ -268,7 +345,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock): ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 13 + assert aioclient_mock.call_count == 14 assert aioclient_mock.mock_calls[-1][2] == { "addons": ["test"], "folders": ["ssl"], @@ -302,3 +379,143 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert mock_check_config.called assert aioclient_mock.call_count == 5 + + +async def test_entry_load_and_unload(hass): + """Test loading and unloading config entry.""" + with patch.dict(os.environ, MOCK_ENVIRON): + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert SENSOR_DOMAIN in hass.config.components + assert BINARY_SENSOR_DOMAIN in hass.config.components + assert ADDONS_COORDINATOR in hass.data + + assert await config_entry.async_unload(hass) + await hass.async_block_till_done() + assert ADDONS_COORDINATOR not in hass.data + + +async def test_migration_off_hassio(hass): + """Test that when a user moves instance off Hass.io, config entry gets cleaned up.""" + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.config_entries.async_entries(DOMAIN) == [] + + +async def test_device_registry_calls(hass): + """Test device registry entries for hassio.""" + dev_reg = async_get(hass) + supervisor_mock_data = { + "addons": [ + { + "name": "test", + "slug": "test", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "repository": "test", + "url": "https://github.com/home-assistant/addons/test", + }, + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ] + } + os_mock_data = { + "board": "odroid-n2", + "boot": "A", + "update_available": False, + "version": "5.12", + "version_latest": "5.12", + } + + with patch.dict(os.environ, MOCK_ENVIRON), patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ): + config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 3 + + supervisor_mock_data = { + "addons": [ + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ] + } + + # Test that when addon is removed, next update will remove the add-on and subsequent updates won't + with patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ): + async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1)) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 2 + + async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2)) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 2 + + supervisor_mock_data = { + "addons": [ + { + "name": "test2", + "slug": "test2", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + { + "name": "test3", + "slug": "test3", + "installed": True, + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "url": "https://github.com", + }, + ] + } + + # Test that when addon is added, next update will reload the entry so we register + # a new device + with patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ): + async_fire_time_changed(hass, dt_util.now() + timedelta(hours=3)) + await hass.async_block_till_done() + assert len(dev_reg.devices) == 3 From e1d24c69b81f1da5df2ffd7929037a825cd98321 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 1 Mar 2021 10:28:41 +0100 Subject: [PATCH 0885/1818] Improve CI workflow (#46696) --- .github/workflows/ci.yaml | 277 ++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 160 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 28410123914505..b7599340506ffb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ on: env: CACHE_VERSION: 1 DEFAULT_PYTHON: 3.8 - PRE_COMMIT_HOME: ~/.cache/pre-commit + PRE_COMMIT_CACHE: ~/.cache/pre-commit jobs: # Separate job to pre-populate the base dependency cache @@ -20,6 +20,9 @@ jobs: prepare-base: name: Prepare base dependencies runs-on: ubuntu-latest + outputs: + python-key: ${{ steps.generate-python-key.outputs.key }} + pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} steps: - name: Check out code from GitHub uses: actions/checkout@v2 @@ -28,21 +31,25 @@ jobs: uses: actions/setup-python@v2.2.1 with: python-version: ${{ env.DEFAULT_PYTHON }} + - name: Generate partial Python venv restore key + id: generate-python-key + run: >- + echo "::set-output name=key::base-venv-${{ env.CACHE_VERSION }}-${{ + hashFiles('requirements.txt') }}-${{ + hashFiles('requirements_test.txt') }}-${{ + hashFiles('homeassistant/package_constraints.txt') }}" - name: Restore base Python virtual environment id: cache-venv uses: actions/cache@v2.1.4 with: path: venv key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + steps.generate-python-key.outputs.key }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }} - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}- - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -50,15 +57,20 @@ jobs: . venv/bin/activate pip install -U "pip<20.3" setuptools pip install -r requirements.txt -r requirements_test.txt + - name: Generate partial pre-commit restore key + id: generate-pre-commit-key + run: >- + echo "::set-output name=key::pre-commit-${{ env.CACHE_VERSION }}-${{ + hashFiles('.pre-commit-config.yaml') }}" - name: Restore pre-commit environment from cache id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + path: ${{ env.PRE_COMMIT_CACHE }} + key: >- + ${{ runner.os }}-${{ steps.generate-pre-commit-key.outputs.key }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit- + ${{ runner.os }}-pre-commit-${{ env.CACHE_VERSION }}- - name: Install pre-commit dependencies if: steps.cache-precommit.outputs.cache-hit != 'true' run: | @@ -82,12 +94,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -97,13 +105,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run bandit run: | @@ -127,12 +134,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -142,13 +145,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run black run: | @@ -172,12 +174,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -187,13 +185,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register codespell problem matcher run: | @@ -239,12 +236,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -254,13 +247,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register check executables problem matcher run: | @@ -287,12 +279,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -302,13 +290,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register flake8 problem matcher run: | @@ -335,12 +322,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -350,13 +333,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run isort run: | @@ -380,12 +362,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -395,13 +373,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register check-json problem matcher run: | @@ -428,12 +405,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -443,13 +416,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Run pyupgrade run: | @@ -484,12 +456,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -499,13 +467,12 @@ jobs: id: cache-precommit uses: actions/cache@v2.1.4 with: - path: ${{ env.PRE_COMMIT_HOME }} - key: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Fail job if cache restore failed - if: steps.cache-venv.outputs.cache-hit != 'true' + path: ${{ env.PRE_COMMIT_CACHE }} + key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }} + - name: Fail job if pre-commit cache restore failed + if: steps.cache-precommit.outputs.cache-hit != 'true' run: | - echo "Failed to restore Python virtual environment from cache" + echo "Failed to restore pre-commit environment from cache" exit 1 - name: Register yamllint problem matcher run: | @@ -531,11 +498,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -563,12 +527,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ - steps.python.outputs.python-version }}-${{ - hashFiles('requirements.txt') }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.prepare-base.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -585,24 +545,31 @@ jobs: strategy: matrix: python-version: [3.8, 3.9] + outputs: + python-key: ${{ steps.generate-python-key.outputs.key }} container: homeassistant/ci-azure:${{ matrix.python-version }} steps: - name: Check out code from GitHub uses: actions/checkout@v2 + - name: Generate partial Python venv restore key + id: generate-python-key + run: >- + echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{ + hashFiles('requirements_test.txt') }}-${{ + hashFiles('requirements_all.txt') }}-${{ + hashFiles('homeassistant/package_constraints.txt') }}" - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv uses: actions/cache@v2.1.4 with: path: venv key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + ${{ runner.os }}-${{ matrix.python-version }}-${{ + steps.generate-python-key.outputs.key }} restore-keys: | - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }} - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }} - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ matrix.python-version }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}- + ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}- - name: Create full Python ${{ matrix.python-version }} virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -633,11 +600,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -667,11 +631,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -689,6 +650,7 @@ jobs: runs-on: ubuntu-latest needs: prepare-tests strategy: + fail-fast: false matrix: group: [1, 2, 3, 4] python-version: [3.8, 3.9] @@ -703,11 +665,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -735,6 +694,7 @@ jobs: --test-group-count 4 \ --test-group=${{ matrix.group }} \ --cov homeassistant \ + --cov-report= \ -o console_output_style=count \ -p no:sugar \ tests @@ -750,7 +710,7 @@ jobs: coverage: name: Process test coverage runs-on: ubuntu-latest - needs: pytest + needs: ["prepare-tests", "pytest"] strategy: matrix: python-version: [3.8] @@ -763,11 +723,8 @@ jobs: uses: actions/cache@v2.1.4 with: path: venv - key: >- - ${{ env.CACHE_VERSION}}-${{ runner.os }}-venv-${{ - matrix.python-version }}-${{ hashFiles('requirements_test.txt') - }}-${{ hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }} + key: ${{ runner.os }}-${{ matrix.python-version }}-${{ + needs.prepare-tests.outputs.python-key }} - name: Fail job if Python cache restore failed if: steps.cache-venv.outputs.cache-hit != 'true' run: | From 732db3b67c88a920c1028f25c406b7175850f431 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 1 Mar 2021 10:31:13 +0100 Subject: [PATCH 0886/1818] Revert const replacement in fritzbox_callmonitor (#47211) --- .../components/fritzbox_callmonitor/config_flow.py | 4 ++-- homeassistant/components/fritzbox_callmonitor/const.py | 1 + tests/components/fritzbox_callmonitor/test_config_flow.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index 01a43f7c7ef405..a08450e20a1832 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -7,7 +7,6 @@ from homeassistant import config_entries from homeassistant.const import ( - ATTR_NAME, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -28,6 +27,7 @@ DEFAULT_USERNAME, DOMAIN, FRITZ_ACTION_GET_INFO, + FRITZ_ATTR_NAME, FRITZ_ATTR_SERIAL_NUMBER, FRITZ_SERVICE_DEVICE_INFO, SERIAL_NUMBER, @@ -119,7 +119,7 @@ async def _get_name_of_phonebook(self, phonebook_id): phonebook_info = await self.hass.async_add_executor_job( self._fritzbox_phonebook.fph.phonebook_info, phonebook_id ) - return phonebook_info[ATTR_NAME] + return phonebook_info[FRITZ_ATTR_NAME] async def _get_list_of_phonebook_names(self): """Return list of names for all available phonebooks.""" diff --git a/homeassistant/components/fritzbox_callmonitor/const.py b/homeassistant/components/fritzbox_callmonitor/const.py index 6f0c87f5273cd1..a71f14401b3cf2 100644 --- a/homeassistant/components/fritzbox_callmonitor/const.py +++ b/homeassistant/components/fritzbox_callmonitor/const.py @@ -15,6 +15,7 @@ ATTR_PREFIXES = "prefixes" FRITZ_ACTION_GET_INFO = "GetInfo" +FRITZ_ATTR_NAME = "name" FRITZ_ATTR_SERIAL_NUMBER = "NewSerialNumber" FRITZ_SERVICE_DEVICE_INFO = "DeviceInfo" diff --git a/tests/components/fritzbox_callmonitor/test_config_flow.py b/tests/components/fritzbox_callmonitor/test_config_flow.py index cde30b615eb2c5..00bc1e18679b4c 100644 --- a/tests/components/fritzbox_callmonitor/test_config_flow.py +++ b/tests/components/fritzbox_callmonitor/test_config_flow.py @@ -14,12 +14,12 @@ CONF_PHONEBOOK, CONF_PREFIXES, DOMAIN, + FRITZ_ATTR_NAME, FRITZ_ATTR_SERIAL_NUMBER, SERIAL_NUMBER, ) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import ( - ATTR_NAME, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -69,8 +69,8 @@ CONF_NAME: MOCK_NAME, } MOCK_DEVICE_INFO = {FRITZ_ATTR_SERIAL_NUMBER: MOCK_SERIAL_NUMBER} -MOCK_PHONEBOOK_INFO_1 = {ATTR_NAME: MOCK_PHONEBOOK_NAME_1} -MOCK_PHONEBOOK_INFO_2 = {ATTR_NAME: MOCK_PHONEBOOK_NAME_2} +MOCK_PHONEBOOK_INFO_1 = {FRITZ_ATTR_NAME: MOCK_PHONEBOOK_NAME_1} +MOCK_PHONEBOOK_INFO_2 = {FRITZ_ATTR_NAME: MOCK_PHONEBOOK_NAME_2} MOCK_UNIQUE_ID = f"{MOCK_SERIAL_NUMBER}-{MOCK_PHONEBOOK_ID}" From 2de01ddaeb67aab200c17df8d1befd62e6f06cd5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 03:35:23 -0600 Subject: [PATCH 0887/1818] Remove griddy integration (#47218) --- CODEOWNERS | 1 - homeassistant/components/griddy/__init__.py | 96 ------------------- .../components/griddy/config_flow.py | 75 --------------- homeassistant/components/griddy/const.py | 7 -- homeassistant/components/griddy/manifest.json | 8 -- homeassistant/components/griddy/sensor.py | 48 ---------- homeassistant/components/griddy/strings.json | 16 ---- .../components/griddy/translations/ca.json | 20 ---- .../components/griddy/translations/cs.json | 11 --- .../components/griddy/translations/de.json | 20 ---- .../components/griddy/translations/en.json | 20 ---- .../griddy/translations/es-419.json | 20 ---- .../components/griddy/translations/es.json | 20 ---- .../components/griddy/translations/et.json | 20 ---- .../components/griddy/translations/fr.json | 20 ---- .../components/griddy/translations/it.json | 20 ---- .../components/griddy/translations/ko.json | 20 ---- .../components/griddy/translations/lb.json | 20 ---- .../components/griddy/translations/nl.json | 20 ---- .../components/griddy/translations/no.json | 20 ---- .../components/griddy/translations/pl.json | 20 ---- .../components/griddy/translations/pt-BR.json | 8 -- .../components/griddy/translations/pt.json | 11 --- .../components/griddy/translations/ru.json | 20 ---- .../components/griddy/translations/sl.json | 20 ---- .../components/griddy/translations/sv.json | 8 -- .../components/griddy/translations/tr.json | 11 --- .../components/griddy/translations/uk.json | 20 ---- .../griddy/translations/zh-Hant.json | 20 ---- homeassistant/generated/config_flows.py | 1 - requirements_all.txt | 3 - requirements_test_all.txt | 3 - tests/components/griddy/__init__.py | 1 - tests/components/griddy/test_config_flow.py | 56 ----------- tests/components/griddy/test_sensor.py | 39 -------- 35 files changed, 743 deletions(-) delete mode 100644 homeassistant/components/griddy/__init__.py delete mode 100644 homeassistant/components/griddy/config_flow.py delete mode 100644 homeassistant/components/griddy/const.py delete mode 100644 homeassistant/components/griddy/manifest.json delete mode 100644 homeassistant/components/griddy/sensor.py delete mode 100644 homeassistant/components/griddy/strings.json delete mode 100644 homeassistant/components/griddy/translations/ca.json delete mode 100644 homeassistant/components/griddy/translations/cs.json delete mode 100644 homeassistant/components/griddy/translations/de.json delete mode 100644 homeassistant/components/griddy/translations/en.json delete mode 100644 homeassistant/components/griddy/translations/es-419.json delete mode 100644 homeassistant/components/griddy/translations/es.json delete mode 100644 homeassistant/components/griddy/translations/et.json delete mode 100644 homeassistant/components/griddy/translations/fr.json delete mode 100644 homeassistant/components/griddy/translations/it.json delete mode 100644 homeassistant/components/griddy/translations/ko.json delete mode 100644 homeassistant/components/griddy/translations/lb.json delete mode 100644 homeassistant/components/griddy/translations/nl.json delete mode 100644 homeassistant/components/griddy/translations/no.json delete mode 100644 homeassistant/components/griddy/translations/pl.json delete mode 100644 homeassistant/components/griddy/translations/pt-BR.json delete mode 100644 homeassistant/components/griddy/translations/pt.json delete mode 100644 homeassistant/components/griddy/translations/ru.json delete mode 100644 homeassistant/components/griddy/translations/sl.json delete mode 100644 homeassistant/components/griddy/translations/sv.json delete mode 100644 homeassistant/components/griddy/translations/tr.json delete mode 100644 homeassistant/components/griddy/translations/uk.json delete mode 100644 homeassistant/components/griddy/translations/zh-Hant.json delete mode 100644 tests/components/griddy/__init__.py delete mode 100644 tests/components/griddy/test_config_flow.py delete mode 100644 tests/components/griddy/test_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index b0a31203009799..5324c15db6a042 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -180,7 +180,6 @@ homeassistant/components/google_cloud/* @lufton homeassistant/components/gpsd/* @fabaff homeassistant/components/gree/* @cmroche homeassistant/components/greeneye_monitor/* @jkeljo -homeassistant/components/griddy/* @bdraco homeassistant/components/group/* @home-assistant/core homeassistant/components/growatt_server/* @indykoning homeassistant/components/guardian/* @bachya diff --git a/homeassistant/components/griddy/__init__.py b/homeassistant/components/griddy/__init__.py deleted file mode 100644 index fb5079b00f8c66..00000000000000 --- a/homeassistant/components/griddy/__init__.py +++ /dev/null @@ -1,96 +0,0 @@ -"""The Griddy Power integration.""" -import asyncio -from datetime import timedelta -import logging - -from griddypower.async_api import LOAD_ZONES, AsyncGriddy -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from .const import CONF_LOADZONE, DOMAIN, UPDATE_INTERVAL - -_LOGGER = logging.getLogger(__name__) - -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema({vol.Required(CONF_LOADZONE): vol.In(LOAD_ZONES)})}, - extra=vol.ALLOW_EXTRA, -) - -PLATFORMS = ["sensor"] - - -async def async_setup(hass: HomeAssistant, config: dict): - """Set up the Griddy Power component.""" - - hass.data.setdefault(DOMAIN, {}) - conf = config.get(DOMAIN) - - if not conf: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_LOADZONE: conf.get(CONF_LOADZONE)}, - ) - ) - return True - - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): - """Set up Griddy Power from a config entry.""" - - entry_data = entry.data - - async_griddy = AsyncGriddy( - aiohttp_client.async_get_clientsession(hass), - settlement_point=entry_data[CONF_LOADZONE], - ) - - async def async_update_data(): - """Fetch data from API endpoint.""" - return await async_griddy.async_getnow() - - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name="Griddy getnow", - update_method=async_update_data, - update_interval=timedelta(seconds=UPDATE_INTERVAL), - ) - - await coordinator.async_refresh() - - if not coordinator.last_update_success: - raise ConfigEntryNotReady - - hass.data[DOMAIN][entry.entry_id] = coordinator - - for component in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) - ) - - return True - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): - """Unload a config entry.""" - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS - ] - ) - ) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok diff --git a/homeassistant/components/griddy/config_flow.py b/homeassistant/components/griddy/config_flow.py deleted file mode 100644 index 675e48cc9993dc..00000000000000 --- a/homeassistant/components/griddy/config_flow.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Config flow for Griddy Power integration.""" -import asyncio -import logging - -from aiohttp import ClientError -from griddypower.async_api import LOAD_ZONES, AsyncGriddy -import voluptuous as vol - -from homeassistant import config_entries, core, exceptions -from homeassistant.helpers import aiohttp_client - -from .const import CONF_LOADZONE -from .const import DOMAIN # pylint:disable=unused-import - -_LOGGER = logging.getLogger(__name__) - -DATA_SCHEMA = vol.Schema({vol.Required(CONF_LOADZONE): vol.In(LOAD_ZONES)}) - - -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. - """ - client_session = aiohttp_client.async_get_clientsession(hass) - - try: - await AsyncGriddy( - client_session, settlement_point=data[CONF_LOADZONE] - ).async_getnow() - except (asyncio.TimeoutError, ClientError) as err: - raise CannotConnect from err - - # Return info that you want to store in the config entry. - return {"title": f"Load Zone {data[CONF_LOADZONE]}"} - - -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Griddy Power.""" - - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - async def async_step_user(self, user_input=None): - """Handle the initial step.""" - errors = {} - info = None - if user_input is not None: - try: - info = await validate_input(self.hass, user_input) - except CannotConnect: - errors["base"] = "cannot_connect" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - if "base" not in errors: - await self.async_set_unique_id(user_input[CONF_LOADZONE]) - self._abort_if_unique_id_configured() - return self.async_create_entry(title=info["title"], data=user_input) - - return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors - ) - - async def async_step_import(self, user_input): - """Handle import.""" - await self.async_set_unique_id(user_input[CONF_LOADZONE]) - self._abort_if_unique_id_configured() - - return await self.async_step_user(user_input) - - -class CannotConnect(exceptions.HomeAssistantError): - """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/griddy/const.py b/homeassistant/components/griddy/const.py deleted file mode 100644 index 034567a806e150..00000000000000 --- a/homeassistant/components/griddy/const.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Constants for the Griddy Power integration.""" - -DOMAIN = "griddy" - -UPDATE_INTERVAL = 90 - -CONF_LOADZONE = "loadzone" diff --git a/homeassistant/components/griddy/manifest.json b/homeassistant/components/griddy/manifest.json deleted file mode 100644 index 1e31b1b7aa8320..00000000000000 --- a/homeassistant/components/griddy/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "griddy", - "name": "Griddy Power", - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/griddy", - "requirements": ["griddypower==0.1.0"], - "codeowners": ["@bdraco"] -} diff --git a/homeassistant/components/griddy/sensor.py b/homeassistant/components/griddy/sensor.py deleted file mode 100644 index f8a900d92be8cf..00000000000000 --- a/homeassistant/components/griddy/sensor.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Support for August sensors.""" -from homeassistant.const import CURRENCY_CENT, ENERGY_KILO_WATT_HOUR -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import CONF_LOADZONE, DOMAIN - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up the August sensors.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] - - settlement_point = config_entry.data[CONF_LOADZONE] - - async_add_entities([GriddyPriceSensor(settlement_point, coordinator)], True) - - -class GriddyPriceSensor(CoordinatorEntity): - """Representation of an August sensor.""" - - def __init__(self, settlement_point, coordinator): - """Initialize the sensor.""" - super().__init__(coordinator) - self._settlement_point = settlement_point - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return f"{CURRENCY_CENT}/{ENERGY_KILO_WATT_HOUR}" - - @property - def name(self): - """Device Name.""" - return f"{self._settlement_point} Price Now" - - @property - def icon(self): - """Device Ice.""" - return "mdi:currency-usd" - - @property - def unique_id(self): - """Device Uniqueid.""" - return f"{self._settlement_point}_price_now" - - @property - def state(self): - """Get the current price.""" - return round(float(self.coordinator.data.now.price_cents_kwh), 4) diff --git a/homeassistant/components/griddy/strings.json b/homeassistant/components/griddy/strings.json deleted file mode 100644 index 99bd8946c3437c..00000000000000 --- a/homeassistant/components/griddy/strings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "step": { - "user": { - "description": "Your Load Zone is in your Griddy account under \u201cAccount > Meter > Load Zone.\u201d", - "data": { "loadzone": "Load Zone (Settlement Point)" }, - "title": "Setup your Griddy Load Zone" - } - }, - "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_location%]" } - } -} diff --git a/homeassistant/components/griddy/translations/ca.json b/homeassistant/components/griddy/translations/ca.json deleted file mode 100644 index 33aca3cd302a4c..00000000000000 --- a/homeassistant/components/griddy/translations/ca.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada" - }, - "error": { - "cannot_connect": "Ha fallat la connexi\u00f3", - "unknown": "Error inesperat" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona de c\u00e0rrega (Load Zone)" - }, - "description": "La teva zona de c\u00e0rrega (Load Zone) est\u00e0 al teu compte de Griddy v\u00e9s a \"Account > Meter > Load Zone\".", - "title": "Configuraci\u00f3 de la zona de c\u00e0rrega (Load Zone) de Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/cs.json b/homeassistant/components/griddy/translations/cs.json deleted file mode 100644 index fa5918fa5da8a7..00000000000000 --- a/homeassistant/components/griddy/translations/cs.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Um\u00edst\u011bn\u00ed je ji\u017e nastaveno" - }, - "error": { - "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", - "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/de.json b/homeassistant/components/griddy/translations/de.json deleted file mode 100644 index 4a6c477059ccf4..00000000000000 --- a/homeassistant/components/griddy/translations/de.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Standort ist bereits konfiguriert" - }, - "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "unknown": "Unerwarteter Fehler" - }, - "step": { - "user": { - "data": { - "loadzone": "Ladezone (Abwicklungspunkt)" - }, - "description": "Ihre Ladezone befindet sich in Ihrem Griddy-Konto unter \"Konto > Messger\u00e4t > Ladezone\".", - "title": "Richten Sie Ihre Griddy Ladezone ein" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/en.json b/homeassistant/components/griddy/translations/en.json deleted file mode 100644 index 2a82421dd7c12b..00000000000000 --- a/homeassistant/components/griddy/translations/en.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Location is already configured" - }, - "error": { - "cannot_connect": "Failed to connect", - "unknown": "Unexpected error" - }, - "step": { - "user": { - "data": { - "loadzone": "Load Zone (Settlement Point)" - }, - "description": "Your Load Zone is in your Griddy account under \u201cAccount > Meter > Load Zone.\u201d", - "title": "Setup your Griddy Load Zone" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/es-419.json b/homeassistant/components/griddy/translations/es-419.json deleted file mode 100644 index 652c8484b4e727..00000000000000 --- a/homeassistant/components/griddy/translations/es-419.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Esta zona de carga ya est\u00e1 configurada" - }, - "error": { - "cannot_connect": "No se pudo conectar, intente nuevamente", - "unknown": "Error inesperado" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona de carga (punto de asentamiento)" - }, - "description": "Su zona de carga est\u00e1 en su cuenta de Griddy en \"Cuenta > Medidor > Zona de carga\".", - "title": "Configura tu zona de carga Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/es.json b/homeassistant/components/griddy/translations/es.json deleted file mode 100644 index a3727721b2d552..00000000000000 --- a/homeassistant/components/griddy/translations/es.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Esta Zona de Carga ya est\u00e1 configurada" - }, - "error": { - "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", - "unknown": "Error inesperado" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona de Carga (Punto del Asentamiento)" - }, - "description": "Tu Zona de Carga est\u00e1 en tu cuenta de Griddy en \"Account > Meter > Load Zone\"", - "title": "Configurar tu Zona de Carga de Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/et.json b/homeassistant/components/griddy/translations/et.json deleted file mode 100644 index 82d2232b04e23e..00000000000000 --- a/homeassistant/components/griddy/translations/et.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "See Load Zone on juba m\u00e4\u00e4ratud" - }, - "error": { - "cannot_connect": "\u00dchendamine nurjus", - "unknown": "Tundmatu viga" - }, - "step": { - "user": { - "data": { - "loadzone": "Load Zone (arvelduspunkt)" - }, - "description": "Load Zone asub Griddy konto valikutes \u201cAccount > Meter > Load Zone.\u201d", - "title": "Seadista oma Griddy Load Zone" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/fr.json b/homeassistant/components/griddy/translations/fr.json deleted file mode 100644 index c2fd4d8d62772c..00000000000000 --- a/homeassistant/components/griddy/translations/fr.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Cette zone de chargement est d\u00e9j\u00e0 configur\u00e9e" - }, - "error": { - "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", - "unknown": "Erreur inattendue" - }, - "step": { - "user": { - "data": { - "loadzone": "Zone de charge (point d'\u00e9tablissement)" - }, - "description": "Votre zone de charge se trouve dans votre compte Griddy sous \"Compte > Compteur > Zone de charge\".", - "title": "Configurez votre zone de charge Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/it.json b/homeassistant/components/griddy/translations/it.json deleted file mode 100644 index 40fa69b1229bc8..00000000000000 --- a/homeassistant/components/griddy/translations/it.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "La posizione \u00e8 gi\u00e0 configurata" - }, - "error": { - "cannot_connect": "Impossibile connettersi", - "unknown": "Errore imprevisto" - }, - "step": { - "user": { - "data": { - "loadzone": "Zona di Carico (Punto di insediamento)" - }, - "description": "La tua Zona di Carico si trova nel tuo account Griddy in \"Account > Meter > Load zone\".", - "title": "Configurazione della Zona di Carico Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/ko.json b/homeassistant/components/griddy/translations/ko.json deleted file mode 100644 index df9178fab93c41..00000000000000 --- a/homeassistant/components/griddy/translations/ko.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." - }, - "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" - }, - "step": { - "user": { - "data": { - "loadzone": "\uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed (\uc815\uc0b0\uc810)" - }, - "description": "\uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed\uc740 Griddy \uacc4\uc815\uc758 \"Account > Meter > Load Zone\"\uc5d0\uc11c \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "Griddy \uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed \uc124\uc815\ud558\uae30" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/lb.json b/homeassistant/components/griddy/translations/lb.json deleted file mode 100644 index 84511186f88b60..00000000000000 --- a/homeassistant/components/griddy/translations/lb.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Standuert ass scho konfigur\u00e9iert" - }, - "error": { - "cannot_connect": "Feeler beim verbannen", - "unknown": "Onerwaarte Feeler" - }, - "step": { - "user": { - "data": { - "loadzone": "Lued Zone (Punkt vum R\u00e9glement)" - }, - "description": "Deng Lued Zon ass an dengem Griddy Kont enner \"Account > Meter > Load Zone.\"", - "title": "Griddy Lued Zon ariichten" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/nl.json b/homeassistant/components/griddy/translations/nl.json deleted file mode 100644 index bd97b9ccf7c141..00000000000000 --- a/homeassistant/components/griddy/translations/nl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Deze laadzone is al geconfigureerd" - }, - "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", - "unknown": "Onverwachte fout" - }, - "step": { - "user": { - "data": { - "loadzone": "Laadzone (vestigingspunt)" - }, - "description": "Uw Load Zone staat op uw Griddy account onder \"Account > Meter > Load Zone\".", - "title": "Stel uw Griddy Load Zone in" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/no.json b/homeassistant/components/griddy/translations/no.json deleted file mode 100644 index 7f01fa198a34f1..00000000000000 --- a/homeassistant/components/griddy/translations/no.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Plasseringen er allerede konfigurert" - }, - "error": { - "cannot_connect": "Tilkobling mislyktes", - "unknown": "Uventet feil" - }, - "step": { - "user": { - "data": { - "loadzone": "Load Zone (settlingspunkt)" - }, - "description": "Din Load Zone er p\u00e5 din Griddy-konto under \"Konto > M\u00e5ler > Lastesone.\"", - "title": "Sett opp din Griddy Load Zone" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pl.json b/homeassistant/components/griddy/translations/pl.json deleted file mode 100644 index 035521336f6850..00000000000000 --- a/homeassistant/components/griddy/translations/pl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Lokalizacja jest ju\u017c skonfigurowana" - }, - "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", - "unknown": "Nieoczekiwany b\u0142\u0105d" - }, - "step": { - "user": { - "data": { - "loadzone": "Strefa obci\u0105\u017cenia (punkt rozliczenia)" - }, - "description": "Twoja strefa obci\u0105\u017cenia znajduje si\u0119 na twoim koncie Griddy w sekcji \"Konto > Licznik > Strefa obci\u0105\u017cenia\".", - "title": "Konfiguracja strefy obci\u0105\u017cenia Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pt-BR.json b/homeassistant/components/griddy/translations/pt-BR.json deleted file mode 100644 index dc9c1362dc4418..00000000000000 --- a/homeassistant/components/griddy/translations/pt-BR.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "Falha ao conectar, tente novamente", - "unknown": "Erro inesperado" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pt.json b/homeassistant/components/griddy/translations/pt.json deleted file mode 100644 index 9b067d35f897c5..00000000000000 --- a/homeassistant/components/griddy/translations/pt.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "A localiza\u00e7\u00e3o j\u00e1 est\u00e1 configurada" - }, - "error": { - "cannot_connect": "Falha na liga\u00e7\u00e3o", - "unknown": "Erro inesperado" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/ru.json b/homeassistant/components/griddy/translations/ru.json deleted file mode 100644 index 3483953fce16a2..00000000000000 --- a/homeassistant/components/griddy/translations/ru.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." - }, - "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": { - "loadzone": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 (\u0440\u0430\u0441\u0447\u0435\u0442\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430)" - }, - "description": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Griddy \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 Account > Meter > Load Zone.", - "title": "Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/sl.json b/homeassistant/components/griddy/translations/sl.json deleted file mode 100644 index 8df85c6dc67af9..00000000000000 --- a/homeassistant/components/griddy/translations/sl.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Ta obremenitvena cona je \u017ee konfigurirana" - }, - "error": { - "cannot_connect": "Povezava ni uspela, poskusite znova", - "unknown": "Nepri\u010dakovana napaka" - }, - "step": { - "user": { - "data": { - "loadzone": "Obremenitvena cona (poselitvena to\u010dka)" - }, - "description": "Va\u0161a obremenitvena cona je v va\u0161em ra\u010dunu Griddy pod \"Ra\u010dun > Merilnik > Nalo\u017ei cono.\"", - "title": "Nastavite svojo Griddy Load Cono" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/sv.json b/homeassistant/components/griddy/translations/sv.json deleted file mode 100644 index e9ddacf2714959..00000000000000 --- a/homeassistant/components/griddy/translations/sv.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "config": { - "error": { - "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", - "unknown": "Ov\u00e4ntat fel" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/tr.json b/homeassistant/components/griddy/translations/tr.json deleted file mode 100644 index 26e0fa73065088..00000000000000 --- a/homeassistant/components/griddy/translations/tr.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Konum zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" - }, - "error": { - "cannot_connect": "Ba\u011flant\u0131 kurulamad\u0131, l\u00fctfen tekrar deneyin", - "unknown": "Beklenmeyen hata" - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/uk.json b/homeassistant/components/griddy/translations/uk.json deleted file mode 100644 index e366f0e8b24e0b..00000000000000 --- a/homeassistant/components/griddy/translations/uk.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f \u0432\u0436\u0435 \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u0435." - }, - "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": { - "loadzone": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f (\u0440\u043e\u0437\u0440\u0430\u0445\u0443\u043d\u043a\u043e\u0432\u0430 \u0442\u043e\u0447\u043a\u0430)" - }, - "description": "\u0417\u043e\u043d\u0430 \u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0443 \u0432\u0430\u0448\u043e\u043c\u0443 \u043f\u0440\u043e\u0444\u0456\u043b\u0456 Griddy \u0432 \u0440\u043e\u0437\u0434\u0456\u043b\u0456 Account > Meter > Load Zone.", - "title": "Griddy" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/zh-Hant.json b/homeassistant/components/griddy/translations/zh-Hant.json deleted file mode 100644 index 4918c1e818e5a3..00000000000000 --- a/homeassistant/components/griddy/translations/zh-Hant.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u5ea7\u6a19\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" - }, - "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" - }, - "step": { - "user": { - "data": { - "loadzone": "\u8ca0\u8f09\u5340\u57df\uff08\u5c45\u4f4f\u9ede\uff09" - }, - "description": "\u8ca0\u8f09\u5340\u57df\u986f\u793a\u65bc Griddy \u5e33\u865f\uff0c\u4f4d\u65bc \u201cAccount > Meter > Load Zone\u201d\u3002", - "title": "\u8a2d\u5b9a Griddy \u8ca0\u8f09\u5340\u57df" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c3d629ebe29264..a0a846c0d44e4a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -86,7 +86,6 @@ "gogogate2", "gpslogger", "gree", - "griddy", "guardian", "habitica", "hangouts", diff --git a/requirements_all.txt b/requirements_all.txt index d4e7be4d98ed9e..ff8b5ba4c60d1a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -705,9 +705,6 @@ greeneye_monitor==2.1 # homeassistant.components.greenwave greenwavereality==0.5.1 -# homeassistant.components.griddy -griddypower==0.1.0 - # homeassistant.components.growatt_server growattServer==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 78f234e9dff8e0..9c6ef98fb69625 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -372,9 +372,6 @@ google-nest-sdm==0.2.12 # homeassistant.components.gree greeclimate==0.10.3 -# homeassistant.components.griddy -griddypower==0.1.0 - # homeassistant.components.profiler guppy3==3.1.0 diff --git a/tests/components/griddy/__init__.py b/tests/components/griddy/__init__.py deleted file mode 100644 index 415ddc3ba5cd9c..00000000000000 --- a/tests/components/griddy/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the Griddy Power integration.""" diff --git a/tests/components/griddy/test_config_flow.py b/tests/components/griddy/test_config_flow.py deleted file mode 100644 index cfc2b23a8ed95f..00000000000000 --- a/tests/components/griddy/test_config_flow.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Test the Griddy Power config flow.""" -import asyncio -from unittest.mock import MagicMock, patch - -from homeassistant import config_entries, setup -from homeassistant.components.griddy.const import DOMAIN - - -async def test_form(hass): - """Test we get the form.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == "form" - assert result["errors"] == {} - - with patch( - "homeassistant.components.griddy.config_flow.AsyncGriddy.async_getnow", - return_value=MagicMock(), - ), patch( - "homeassistant.components.griddy.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.griddy.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"loadzone": "LZ_HOUSTON"}, - ) - await hass.async_block_till_done() - - assert result2["type"] == "create_entry" - assert result2["title"] == "Load Zone LZ_HOUSTON" - assert result2["data"] == {"loadzone": "LZ_HOUSTON"} - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 1 - - -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( - "homeassistant.components.griddy.config_flow.AsyncGriddy.async_getnow", - side_effect=asyncio.TimeoutError, - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"loadzone": "LZ_NORTH"}, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} diff --git a/tests/components/griddy/test_sensor.py b/tests/components/griddy/test_sensor.py deleted file mode 100644 index 46f8d238c49d65..00000000000000 --- a/tests/components/griddy/test_sensor.py +++ /dev/null @@ -1,39 +0,0 @@ -"""The sensor tests for the griddy platform.""" -import json -import os -from unittest.mock import patch - -from griddypower.async_api import GriddyPriceData - -from homeassistant.components.griddy import CONF_LOADZONE, DOMAIN -from homeassistant.setup import async_setup_component - -from tests.common import load_fixture - - -async def _load_json_fixture(hass, path): - fixture = await hass.async_add_executor_job( - load_fixture, os.path.join("griddy", path) - ) - return json.loads(fixture) - - -def _mock_get_config(): - """Return a default griddy config.""" - return {DOMAIN: {CONF_LOADZONE: "LZ_HOUSTON"}} - - -async def test_houston_loadzone(hass): - """Test creation of the houston load zone.""" - - getnow_json = await _load_json_fixture(hass, "getnow.json") - griddy_price_data = GriddyPriceData(getnow_json) - with patch( - "homeassistant.components.griddy.AsyncGriddy.async_getnow", - return_value=griddy_price_data, - ): - assert await async_setup_component(hass, DOMAIN, _mock_get_config()) - await hass.async_block_till_done() - - sensor_lz_houston_price_now = hass.states.get("sensor.lz_houston_price_now") - assert sensor_lz_houston_price_now.state == "1.269" From 4c42e469b3eaa1ef4edb70dd85b55971d83e4882 Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Mon, 1 Mar 2021 09:38:07 +0000 Subject: [PATCH 0888/1818] Fix number of reported issues by github integration (#47203) --- homeassistant/components/github/sensor.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 312e726b91d39e..80d05ae1b9c081 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -228,18 +228,25 @@ def update(self): self.stargazers = repo.stargazers_count self.forks = repo.forks_count - open_issues = repo.get_issues(state="open", sort="created") - if open_issues is not None: - self.open_issue_count = open_issues.totalCount - if open_issues.totalCount > 0: - self.latest_open_issue_url = open_issues[0].html_url - open_pull_requests = repo.get_pulls(state="open", sort="created") if open_pull_requests is not None: self.pull_request_count = open_pull_requests.totalCount if open_pull_requests.totalCount > 0: self.latest_open_pr_url = open_pull_requests[0].html_url + open_issues = repo.get_issues(state="open", sort="created") + if open_issues is not None: + if self.pull_request_count is None: + self.open_issue_count = open_issues.totalCount + else: + # pull requests are treated as issues too so we need to reduce the received count + self.open_issue_count = ( + open_issues.totalCount - self.pull_request_count + ) + + if open_issues.totalCount > 0: + self.latest_open_issue_url = open_issues[0].html_url + latest_commit = repo.get_commits()[0] self.latest_commit_sha = latest_commit.sha self.latest_commit_message = latest_commit.commit.message From 92afcb6b4bafef0f5004cd6d6cf073e8e5d30266 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 1 Mar 2021 11:51:59 +0100 Subject: [PATCH 0889/1818] KNX services send and event_register accept multiple group addresses (#46908) * send and event_register service accept lists of group addresses * remove lambda * object selector for lists * knx.read takes lists too --- homeassistant/components/knx/__init__.py | 84 +++++++++++++--------- homeassistant/components/knx/services.yaml | 14 ++-- 2 files changed, 57 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index b8feb010e29f45..c32755b7f45450 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -1,6 +1,7 @@ """Support KNX devices.""" import asyncio import logging +from typing import Union import voluptuous as vol from xknx import XKNX @@ -148,7 +149,10 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_ADDRESS): vol.All( + cv.ensure_list, + [ga_validator], + ), vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), } @@ -156,7 +160,10 @@ vol.Schema( # without type given payload is treated as raw bytes { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_ADDRESS): vol.All( + cv.ensure_list, + [ga_validator], + ), vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any( cv.positive_int, [cv.positive_int] ), @@ -175,7 +182,10 @@ SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(CONF_ADDRESS): vol.All( + cv.ensure_list, + [ga_validator], + ), vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } ) @@ -401,21 +411,26 @@ def register_callback(self) -> TelegramQueue.Callback: async def service_event_register_modify(self, call): """Service for adding or removing a GroupAddress to the knx_event filter.""" - group_address = GroupAddress(call.data[CONF_ADDRESS]) + attr_address = call.data.get(CONF_ADDRESS) + group_addresses = map(GroupAddress, attr_address) + if call.data.get(SERVICE_KNX_ATTR_REMOVE): - try: - self._knx_event_callback.group_addresses.remove(group_address) - except ValueError: - _LOGGER.warning( - "Service event_register could not remove event for '%s'", - group_address, - ) - elif group_address not in self._knx_event_callback.group_addresses: - self._knx_event_callback.group_addresses.append(group_address) - _LOGGER.debug( - "Service event_register registered event for '%s'", - group_address, - ) + for group_address in group_addresses: + try: + self._knx_event_callback.group_addresses.remove(group_address) + except ValueError: + _LOGGER.warning( + "Service event_register could not remove event for '%s'", + str(group_address), + ) + else: + for group_address in group_addresses: + if group_address not in self._knx_event_callback.group_addresses: + self._knx_event_callback.group_addresses.append(group_address) + _LOGGER.debug( + "Service event_register registered event for '%s'", + str(group_address), + ) async def service_exposure_register_modify(self, call): """Service for adding or removing an exposure to KNX bus.""" @@ -450,26 +465,27 @@ async def service_exposure_register_modify(self, call): async def service_send_to_knx_bus(self, call): """Service for sending an arbitrary KNX message to the KNX bus.""" - attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_address = call.data.get(CONF_ADDRESS) + attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) - def calculate_payload(attr_payload): - """Calculate payload depending on type of attribute.""" - if attr_type is not None: - transcoder = DPTBase.parse_transcoder(attr_type) - if transcoder is None: - raise ValueError(f"Invalid type for knx.send service: {attr_type}") - return DPTArray(transcoder.to_knx(attr_payload)) - if isinstance(attr_payload, int): - return DPTBinary(attr_payload) - return DPTArray(attr_payload) - - telegram = Telegram( - destination_address=GroupAddress(attr_address), - payload=GroupValueWrite(calculate_payload(attr_payload)), - ) - await self.xknx.telegrams.put(telegram) + payload: Union[DPTBinary, DPTArray] + if attr_type is not None: + transcoder = DPTBase.parse_transcoder(attr_type) + if transcoder is None: + raise ValueError(f"Invalid type for knx.send service: {attr_type}") + payload = DPTArray(transcoder.to_knx(attr_payload)) + elif isinstance(attr_payload, int): + payload = DPTBinary(attr_payload) + else: + payload = DPTArray(attr_payload) + + for address in attr_address: + telegram = Telegram( + destination_address=GroupAddress(address), + payload=GroupValueWrite(payload), + ) + await self.xknx.telegrams.put(telegram) async def service_read_to_knx_bus(self, call): """Service for sending a GroupValueRead telegram to the KNX bus.""" diff --git a/homeassistant/components/knx/services.yaml b/homeassistant/components/knx/services.yaml index 3fae7dfce0e799..c13abb23d94fb9 100644 --- a/homeassistant/components/knx/services.yaml +++ b/homeassistant/components/knx/services.yaml @@ -4,11 +4,11 @@ send: fields: address: name: "Group address" - description: "Group address(es) to write to." + description: "Group address(es) to write to. Lists will send to multiple group addresses successively." required: true example: "1/1/0" selector: - text: + object: payload: name: "Payload" description: "Payload to send to the bus. Integers are treated as DPT 1/2/3 payloads. For DPTs > 6 bits send a list. Each value represents 1 octet (0-255). Pad with 0 to DPT byte length." @@ -33,21 +33,21 @@ read: required: true example: "1/1/0" selector: - text: + object: event_register: name: "Register knx_event" - description: "Add or remove single group address to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." + description: "Add or remove group addresses to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed." fields: address: name: "Group address" - description: "Group address that shall be added or removed." + description: "Group address(es) that shall be added or removed. Lists are allowed." required: true example: "1/1/0" selector: - text: + object: remove: name: "Remove event registration" - description: "Optional. If `True` the group address will be removed." + description: "Optional. If `True` the group address(es) will be removed." default: false selector: boolean: From dadc99dbd3f8cba62d9e9879f4374f386dfa4645 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 1 Mar 2021 11:55:55 +0100 Subject: [PATCH 0890/1818] Deprecate knx config_file (#46874) * deprecate config_file * removed cv.deprecated for now, added persistent notification * Update homeassistant/components/knx/__init__.py Co-authored-by: Philip Allgaier * remove notification, add cv.deprecated again * Update homeassistant/components/knx/__init__.py Co-authored-by: Franck Nijhof * remove cv.deprecated again Co-authored-by: Philip Allgaier Co-authored-by: Franck Nijhof --- homeassistant/components/knx/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index c32755b7f45450..ca1c19c3f5c76c 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -78,6 +78,7 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( + # deprecated since 2021.2 cv.deprecated(CONF_KNX_FIRE_EVENT), cv.deprecated("fire_event_filter", replacement_key=CONF_KNX_EVENT_FILTER), vol.Schema( @@ -242,6 +243,15 @@ async def async_setup(hass, config): "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" ) + # deprecation warning since 2021.3 + if CONF_KNX_CONFIG in config[DOMAIN]: + _LOGGER.warning( + "The 'config_file' option is deprecated. Please replace it with Home Assistant config schema " + "directly in `configuration.yaml` (see https://www.home-assistant.io/integrations/knx/). \n" + "An online configuration converter tool for your `%s` is available at https://xknx.io/config-converter/", + config[DOMAIN][CONF_KNX_CONFIG], + ) + hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, From 003fee2a355368d6d2c5920ff1f27ab767feb6d4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Mar 2021 12:38:49 +0100 Subject: [PATCH 0891/1818] Fix race when disabling config entries (#47210) * Fix race when disabling config entries * Remove unused constant --- homeassistant/config_entries.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index dbc0dd01454364..b54300faaa77c7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -69,8 +69,6 @@ ENTRY_STATE_NOT_LOADED = "not_loaded" # An error occurred when trying to unload the entry ENTRY_STATE_FAILED_UNLOAD = "failed_unload" -# The config entry is disabled -ENTRY_STATE_DISABLED = "disabled" UNRECOVERABLE_STATES = (ENTRY_STATE_MIGRATION_ERROR, ENTRY_STATE_FAILED_UNLOAD) @@ -802,11 +800,14 @@ async def async_set_disabled_by( entry.disabled_by = disabled_by self._async_schedule_save() + # Unload the config entry, then fire an event + reload_result = await self.async_reload(entry_id) + self.hass.bus.async_fire( EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} ) - return await self.async_reload(entry_id) + return reload_result @callback def async_update_entry( From 084cfa4a1d83dca58fe7fa134025e007eb382a0e Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 1 Mar 2021 12:46:02 +0100 Subject: [PATCH 0892/1818] Fix Xiaomi Miio flow unique_id for non discovery flows (#47222) --- homeassistant/components/xiaomi_miio/config_flow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 9eaf4c1effa98e..efb668f0196e4d 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -126,7 +126,9 @@ async def async_step_device(self, user_input=None): for gateway_model in MODELS_GATEWAY: if model.startswith(gateway_model): unique_id = self.mac - await self.async_set_unique_id(unique_id) + await self.async_set_unique_id( + unique_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=DEFAULT_GATEWAY_NAME, @@ -145,7 +147,9 @@ async def async_step_device(self, user_input=None): for device_model in MODELS_ALL_DEVICES: if model.startswith(device_model): unique_id = self.mac - await self.async_set_unique_id(unique_id) + await self.async_set_unique_id( + unique_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=name, From a2b13785c2606e5a67739262082966926dab5f15 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Mar 2021 13:40:46 +0100 Subject: [PATCH 0893/1818] Restore pylint concurrency (#47221) --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 445f13e8724943..731b7f15730438 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,7 @@ ignore = [ ] # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. -# Disabled for now: https://github.com/PyCQA/pylint/issues/3584 -#jobs = 2 +jobs = 2 load-plugins = [ "pylint_strict_informational", ] From 947f6ea51e29e2b3f3133ba3203a7be6f53d583c Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Mon, 1 Mar 2021 04:53:57 -0800 Subject: [PATCH 0894/1818] Parameterize SmartTub tests (#47189) * Parameterize SmartTub tests * parameterize light service calls * remove stray print() * add comment --- homeassistant/components/smarttub/light.py | 2 +- tests/components/smarttub/test_light.py | 65 ++++++++++++---------- tests/components/smarttub/test_sensor.py | 56 ++++++++----------- tests/components/smarttub/test_switch.py | 44 +++++++-------- 4 files changed, 81 insertions(+), 86 deletions(-) diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py index a4ada7c3024cdf..1baf7e527eb2ea 100644 --- a/homeassistant/components/smarttub/light.py +++ b/homeassistant/components/smarttub/light.py @@ -137,5 +137,5 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): """Turn the light off.""" - await self.light.set_mode(self.light.LightMode.OFF, 0) + await self.light.set_mode(SpaLight.LightMode.OFF, 0) await self.coordinator.async_request_refresh() diff --git a/tests/components/smarttub/test_light.py b/tests/components/smarttub/test_light.py index 5e9d9459eabfbe..fe178278beef94 100644 --- a/tests/components/smarttub/test_light.py +++ b/tests/components/smarttub/test_light.py @@ -1,38 +1,45 @@ """Test the SmartTub light platform.""" +import pytest from smarttub import SpaLight -async def test_light(spa, setup_entry, hass): +# the light in light_zone should have initial state light_state. we will call +# service_name with service_params, and expect the resultant call to +# SpaLight.set_mode to have set_mode_args parameters +@pytest.mark.parametrize( + "light_zone,light_state,service_name,service_params,set_mode_args", + [ + (1, "off", "turn_on", {}, (SpaLight.LightMode.PURPLE, 50)), + (1, "off", "turn_on", {"brightness": 255}, (SpaLight.LightMode.PURPLE, 100)), + (2, "on", "turn_off", {}, (SpaLight.LightMode.OFF, 0)), + ], +) +async def test_light( + spa, + setup_entry, + hass, + light_zone, + light_state, + service_name, + service_params, + set_mode_args, +): """Test light entity.""" - for light in spa.get_lights.return_value: - entity_id = f"light.{spa.brand}_{spa.model}_light_{light.zone}" - state = hass.states.get(entity_id) - assert state is not None - if light.mode == SpaLight.LightMode.OFF: - assert state.state == "off" - await hass.services.async_call( - "light", - "turn_on", - {"entity_id": entity_id}, - blocking=True, - ) - light.set_mode.assert_called() + entity_id = f"light.{spa.brand}_{spa.model}_light_{light_zone}" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == light_state - await hass.services.async_call( - "light", - "turn_on", - {"entity_id": entity_id, "brightness": 255}, - blocking=True, - ) - light.set_mode.assert_called_with(SpaLight.LightMode.PURPLE, 100) + light: SpaLight = next( + light for light in await spa.get_lights() if light.zone == light_zone + ) - else: - assert state.state == "on" - await hass.services.async_call( - "light", - "turn_off", - {"entity_id": entity_id}, - blocking=True, - ) + await hass.services.async_call( + "light", + service_name, + {"entity_id": entity_id, **service_params}, + blocking=True, + ) + light.set_mode.assert_called_with(*set_mode_args) diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 2d52d6d07a5ef0..4f179b2491004f 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -1,46 +1,30 @@ """Test the SmartTub sensor platform.""" -from . import trigger_update +import pytest -async def test_sensors(spa, setup_entry, hass): - """Test the sensors.""" +@pytest.mark.parametrize( + "entity_suffix,expected_state", + [ + ("state", "normal"), + ("flow_switch", "open"), + ("ozone", "off"), + ("uv", "off"), + ("blowout_cycle", "inactive"), + ("cleanup_cycle", "inactive"), + ], +) +async def test_sensor(spa, setup_entry, hass, entity_suffix, expected_state): + """Test simple sensors.""" - entity_id = f"sensor.{spa.brand}_{spa.model}_state" + entity_id = f"sensor.{spa.brand}_{spa.model}_{entity_suffix}" state = hass.states.get(entity_id) assert state is not None - assert state.state == "normal" + assert state.state == expected_state - spa.get_status.return_value.state = "BAD" - await trigger_update(hass) - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "bad" - - entity_id = f"sensor.{spa.brand}_{spa.model}_flow_switch" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "open" - - entity_id = f"sensor.{spa.brand}_{spa.model}_ozone" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "off" - - entity_id = f"sensor.{spa.brand}_{spa.model}_uv" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "off" - - entity_id = f"sensor.{spa.brand}_{spa.model}_blowout_cycle" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "inactive" - entity_id = f"sensor.{spa.brand}_{spa.model}_cleanup_cycle" - state = hass.states.get(entity_id) - assert state is not None - assert state.state == "inactive" +async def test_primary_filtration(spa, setup_entry, hass): + """Test the primary filtration cycle sensor.""" entity_id = f"sensor.{spa.brand}_{spa.model}_primary_filtration_cycle" state = hass.states.get(entity_id) @@ -51,6 +35,10 @@ async def test_sensors(spa, setup_entry, hass): assert state.attributes["mode"] == "normal" assert state.attributes["start_hour"] == 2 + +async def test_secondary_filtration(spa, setup_entry, hass): + """Test the secondary filtration cycle sensor.""" + entity_id = f"sensor.{spa.brand}_{spa.model}_secondary_filtration_cycle" state = hass.states.get(entity_id) assert state is not None diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index 1ef8463119675f..89e0ec03b238cd 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -1,30 +1,30 @@ """Test the SmartTub switch platform.""" -from smarttub import SpaPump +import pytest -async def test_pumps(spa, setup_entry, hass): +@pytest.mark.parametrize( + "pump_id,entity_suffix,pump_state", + [ + ("CP", "circulation_pump", "off"), + ("P1", "jet_p1", "off"), + ("P2", "jet_p2", "on"), + ], +) +async def test_pumps(spa, setup_entry, hass, pump_id, pump_state, entity_suffix): """Test pump entities.""" - for pump in spa.get_pumps.return_value: - if pump.type == SpaPump.PumpType.CIRCULATION: - entity_id = f"switch.{spa.brand}_{spa.model}_circulation_pump" - elif pump.type == SpaPump.PumpType.JET: - entity_id = f"switch.{spa.brand}_{spa.model}_jet_{pump.id.lower()}" - else: - raise NotImplementedError("Unknown pump type") + pump = next(pump for pump in await spa.get_pumps() if pump.id == pump_id) - state = hass.states.get(entity_id) - assert state is not None - if pump.state == SpaPump.PumpState.OFF: - assert state.state == "off" - else: - assert state.state == "on" + entity_id = f"switch.{spa.brand}_{spa.model}_{entity_suffix}" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == pump_state - await hass.services.async_call( - "switch", - "toggle", - {"entity_id": entity_id}, - blocking=True, - ) - pump.toggle.assert_called() + await hass.services.async_call( + "switch", + "toggle", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From 93d4e46bb69f487da4e635b46d7521984ec49b66 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Mar 2021 15:37:03 +0100 Subject: [PATCH 0895/1818] Upgrade coverage to 5.5 (#47227) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1f0a3c4dba27ef..c906543f54c756 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ -c homeassistant/package_constraints.txt -r requirements_test_pre_commit.txt codecov==2.1.10 -coverage==5.4 +coverage==5.5 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 From 5cb2fdb6f7f7511bc0732bde51ec9dbc6c9d4766 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Mar 2021 16:02:55 +0100 Subject: [PATCH 0896/1818] Upgrade spotipy to 2.17.1 (#47228) --- homeassistant/components/spotify/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index c4d7378c060522..bd92217e9cf4d8 100644 --- a/homeassistant/components/spotify/manifest.json +++ b/homeassistant/components/spotify/manifest.json @@ -2,7 +2,7 @@ "domain": "spotify", "name": "Spotify", "documentation": "https://www.home-assistant.io/integrations/spotify", - "requirements": ["spotipy==2.16.1"], + "requirements": ["spotipy==2.17.1"], "zeroconf": ["_spotify-connect._tcp.local."], "dependencies": ["http"], "codeowners": ["@frenck"], diff --git a/requirements_all.txt b/requirements_all.txt index ff8b5ba4c60d1a..cdd2f51cc0d449 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2112,7 +2112,7 @@ spiderpy==1.4.2 spotcrime==1.0.4 # homeassistant.components.spotify -spotipy==2.16.1 +spotipy==2.17.1 # homeassistant.components.recorder # homeassistant.components.sql diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c6ef98fb69625..27e2068936b80c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1087,7 +1087,7 @@ speedtest-cli==2.1.2 spiderpy==1.4.2 # homeassistant.components.spotify -spotipy==2.16.1 +spotipy==2.17.1 # homeassistant.components.recorder # homeassistant.components.sql From 3ebd5aff98fa2bc4a910ee4caea02af148dd6571 Mon Sep 17 00:00:00 2001 From: jdeath <17914369+jdeath@users.noreply.github.com> Date: Mon, 1 Mar 2021 10:35:47 -0500 Subject: [PATCH 0897/1818] Bump mcstatus to 5.1.1 (#47169) --- homeassistant/components/minecraft_server/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/minecraft_server/manifest.json b/homeassistant/components/minecraft_server/manifest.json index 03710520b90cdb..2c4a2ae4b8eacb 100644 --- a/homeassistant/components/minecraft_server/manifest.json +++ b/homeassistant/components/minecraft_server/manifest.json @@ -3,7 +3,7 @@ "name": "Minecraft Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/minecraft_server", - "requirements": ["aiodns==2.0.0", "getmac==0.8.2", "mcstatus==2.3.0"], + "requirements": ["aiodns==2.0.0", "getmac==0.8.2", "mcstatus==5.1.1"], "codeowners": ["@elmurato"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index cdd2f51cc0d449..5bd96e376839a0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -917,7 +917,7 @@ maxcube-api==0.3.0 mbddns==0.1.2 # homeassistant.components.minecraft_server -mcstatus==2.3.0 +mcstatus==5.1.1 # homeassistant.components.message_bird messagebird==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27e2068936b80c..510bb91b265299 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -474,7 +474,7 @@ luftdaten==0.6.4 mbddns==0.1.2 # homeassistant.components.minecraft_server -mcstatus==2.3.0 +mcstatus==5.1.1 # homeassistant.components.meteo_france meteofrance-api==1.0.1 From be8584c0bcc64de0bca12e8a38d01165221169f0 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 1 Mar 2021 08:27:04 -0800 Subject: [PATCH 0898/1818] Overhaul command_line tests (#46682) --- .../components/command_line/cover.py | 11 +- .../components/command_line/sensor.py | 7 +- .../components/command_line/switch.py | 3 - .../command_line/test_binary_sensor.py | 101 +++-- tests/components/command_line/test_cover.py | 171 +++++--- tests/components/command_line/test_notify.py | 202 +++++---- tests/components/command_line/test_sensor.py | 411 +++++++++--------- tests/components/command_line/test_switch.py | 366 +++++++++++----- 8 files changed, 718 insertions(+), 554 deletions(-) diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 05d2b9634f20f6..961d9a31f4e89d 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -107,11 +107,6 @@ def _move_cover(self, command): return success - def _query_state_value(self, command): - """Execute state command for return value.""" - _LOGGER.info("Running state value command: %s", command) - return check_output_or_log(command, self._timeout) - @property def should_poll(self): """Only poll if we have state command.""" @@ -138,10 +133,8 @@ def current_cover_position(self): def _query_state(self): """Query for the state.""" - if not self._command_state: - _LOGGER.error("No state command specified") - return - return self._query_state_value(self._command_state) + _LOGGER.info("Running state value command: %s", self._command_state) + return check_output_or_log(self._command_state, self._timeout) def update(self): """Update device state.""" diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index 35f7c5a4811b6e..3e6f384cca3e0f 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -145,19 +145,14 @@ def __init__(self, hass, command, command_timeout): def update(self): """Get the latest data with a shell command.""" command = self.command - cache = {} - if command in cache: - prog, args, args_compiled = cache[command] - elif " " not in command: + if " " not in command: prog = command args = None args_compiled = None - cache[command] = (prog, args, args_compiled) else: prog, args = command.split(" ", 1) args_compiled = template.Template(args, self.hass) - cache[command] = (prog, args, args_compiled) if args_compiled: try: diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index ce46cd4f2cd731..ae6c1c0c925f92 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -144,9 +144,6 @@ def assumed_state(self): def _query_state(self): """Query for state.""" - if not self._command_state: - _LOGGER.error("No state command specified") - return if self._value_template: return self._query_state_value(self._command_state) return self._query_state_code(self._command_state) diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index 90871faaf78807..21209a8b60dc08 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -1,68 +1,65 @@ """The tests for the Command line Binary sensor platform.""" -import unittest - -from homeassistant.components.command_line import binary_sensor as command_line +from homeassistant import setup +from homeassistant.components.binary_sensor import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.helpers import template - -from tests.common import get_test_home_assistant +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType -class TestCommandSensorBinarySensor(unittest.TestCase): - """Test the Command line Binary sensor.""" +async def setup_test_entity( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line binary_sensor entity.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + {DOMAIN: {"platform": "command_line", "name": "Test", **config_dict}}, + ) + await hass.async_block_till_done() - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.hass.stop) - def test_setup(self): - """Test sensor setup.""" - config = { - "name": "Test", +async def test_setup(hass: HomeAssistantType) -> None: + """Test sensor setup.""" + await setup_test_entity( + hass, + { "command": "echo 1", "payload_on": "1", "payload_off": "0", - "command_timeout": 15, - } - - devices = [] + }, + ) - def add_dev_callback(devs, update): - """Add callback to add devices.""" - for dev in devs: - devices.append(dev) + entity_state = hass.states.get("binary_sensor.test") + assert entity_state + assert entity_state.state == STATE_ON + assert entity_state.name == "Test" - command_line.setup_platform(self.hass, config, add_dev_callback) - assert 1 == len(devices) - entity = devices[0] - entity.update() - assert "Test" == entity.name - assert STATE_ON == entity.state +async def test_template(hass: HomeAssistantType) -> None: + """Test setting the state with a template.""" - def test_template(self): - """Test setting the state with a template.""" - data = command_line.CommandSensorData(self.hass, "echo 10", 15) + await setup_test_entity( + hass, + { + "command": "echo 10", + "payload_on": "1.0", + "payload_off": "0", + "value_template": "{{ value | multiply(0.1) }}", + }, + ) - entity = command_line.CommandBinarySensor( - self.hass, - data, - "test", - None, - "1.0", - "0", - template.Template("{{ value | multiply(0.1) }}", self.hass), - ) - entity.update() - assert STATE_ON == entity.state + entity_state = hass.states.get("binary_sensor.test") + assert entity_state.state == STATE_ON - def test_sensor_off(self): - """Test setting the state with a template.""" - data = command_line.CommandSensorData(self.hass, "echo 0", 15) - entity = command_line.CommandBinarySensor( - self.hass, data, "test", None, "1", "0", None - ) - entity.update() - assert STATE_OFF == entity.state +async def test_sensor_off(hass: HomeAssistantType) -> None: + """Test setting the state with a template.""" + await setup_test_entity( + hass, + { + "command": "echo 0", + "payload_on": "1", + "payload_off": "0", + }, + ) + entity_state = hass.states.get("binary_sensor.test") + assert entity_state.state == STATE_OFF diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index ee692413bcd258..093c1e86212d1b 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -1,15 +1,10 @@ """The tests the cover command line platform.""" import os -from os import path import tempfile -from unittest import mock from unittest.mock import patch -import pytest - -from homeassistant import config as hass_config -import homeassistant.components.command_line.cover as cmd_rs -from homeassistant.components.cover import DOMAIN +from homeassistant import config as hass_config, setup +from homeassistant.components.cover import DOMAIN, SCAN_INTERVAL from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, @@ -17,101 +12,128 @@ SERVICE_RELOAD, SERVICE_STOP_COVER, ) -from homeassistant.setup import async_setup_component +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType +import homeassistant.util.dt as dt_util + +from tests.common import async_fire_time_changed -@pytest.fixture -def rs(hass): - """Return CommandCover instance.""" - return cmd_rs.CommandCover( +async def setup_test_entity( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line notify service.""" + assert await setup.async_setup_component( hass, - "foo", - "command_open", - "command_close", - "command_stop", - "command_state", - None, - 15, + DOMAIN, + { + DOMAIN: [ + {"platform": "command_line", "covers": config_dict}, + ] + }, ) + await hass.async_block_till_done() -def test_should_poll_new(rs): - """Test the setting of polling.""" - assert rs.should_poll is True - rs._command_state = None - assert rs.should_poll is False +async def test_no_covers(caplog: Any, hass: HomeAssistantType) -> None: + """Test that the cover does not polls when there's no state command.""" + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"50\n", + ): + await setup_test_entity(hass, {}) + assert "No covers added" in caplog.text -def test_query_state_value(rs): - """Test with state value.""" - with mock.patch("subprocess.check_output") as mock_run: - mock_run.return_value = b" foo bar " - result = rs._query_state_value("runme") - assert "foo bar" == result - assert mock_run.call_count == 1 - assert mock_run.call_args == mock.call( - "runme", shell=True, timeout=15 # nosec # shell by design + +async def test_no_poll_when_cover_has_no_command_state(hass: HomeAssistantType) -> None: + """Test that the cover does not polls when there's no state command.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"50\n", + ) as check_output: + await setup_test_entity(hass, {"test": {}}) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert not check_output.called + + +async def test_poll_when_cover_has_command_state(hass: HomeAssistantType) -> None: + """Test that the cover polls when there's a state command.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"50\n", + ) as check_output: + await setup_test_entity(hass, {"test": {"command_state": "echo state"}}) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + check_output.assert_called_once_with( + "echo state", shell=True, timeout=15 # nosec # shell by design ) -async def test_state_value(hass): +async def test_state_value(hass: HomeAssistantType) -> None: """Test with state value.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "cover_status") - test_cover = { - "command_state": f"cat {path}", - "command_open": f"echo 1 > {path}", - "command_close": f"echo 1 > {path}", - "command_stop": f"echo 0 > {path}", - "value_template": "{{ value }}", - } - assert ( - await async_setup_component( - hass, - DOMAIN, - {"cover": {"platform": "command_line", "covers": {"test": test_cover}}}, - ) - is True + await setup_test_entity( + hass, + { + "test": { + "command_state": f"cat {path}", + "command_open": f"echo 1 > {path}", + "command_close": f"echo 1 > {path}", + "command_stop": f"echo 0 > {path}", + "value_template": "{{ value }}", + } + }, ) - await hass.async_block_till_done() - assert "unknown" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "unknown" await hass.services.async_call( DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True ) - assert "open" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "open" await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True ) - assert "open" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "open" await hass.services.async_call( DOMAIN, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True ) - assert "closed" == hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "closed" -async def test_reload(hass): +async def test_reload(hass: HomeAssistantType) -> None: """Verify we can reload command_line covers.""" - test_cover = { - "command_state": "echo open", - "value_template": "{{ value }}", - } - await async_setup_component( + await setup_test_entity( hass, - DOMAIN, - {"cover": {"platform": "command_line", "covers": {"test": test_cover}}}, + { + "test": { + "command_state": "echo open", + "value_template": "{{ value }}", + } + }, ) - await hass.async_block_till_done() - - assert len(hass.states.async_all()) == 1 - assert hass.states.get("cover.test").state + entity_state = hass.states.get("cover.test") + assert entity_state + assert entity_state.state == "unknown" - yaml_path = path.join( - _get_fixtures_base_path(), + yaml_path = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "fixtures", "command_line/configuration.yaml", ) @@ -126,9 +148,18 @@ async def test_reload(hass): assert len(hass.states.async_all()) == 1 - assert hass.states.get("cover.test") is None + assert not hass.states.get("cover.test") assert hass.states.get("cover.from_yaml") -def _get_fixtures_base_path(): - return path.dirname(path.dirname(path.dirname(__file__))) +async def test_move_cover_failure(caplog: Any, hass: HomeAssistantType) -> None: + """Test with state value.""" + + await setup_test_entity( + hass, + {"test": {"command_open": "exit 1"}}, + ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: "cover.test"}, blocking=True + ) + assert "Command failed" in caplog.text diff --git a/tests/components/command_line/test_notify.py b/tests/components/command_line/test_notify.py index 3dcb521cfd283d..4166b9e8bbf251 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -1,117 +1,115 @@ """The tests for the command line notification platform.""" import os +import subprocess import tempfile -import unittest from unittest.mock import patch -import homeassistant.components.notify as notify -from homeassistant.setup import async_setup_component, setup_component - -from tests.common import assert_setup_component, get_test_home_assistant - - -class TestCommandLine(unittest.TestCase): - """Test the command line notifications.""" - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - - def tear_down_cleanup(self): - """Stop down everything that was started.""" - self.hass.stop() - - def test_setup(self): - """Test setup.""" - with assert_setup_component(1) as handle_config: - assert setup_component( - self.hass, - "notify", - { - "notify": { - "name": "test", - "platform": "command_line", - "command": "echo $(cat); exit 1", - } - }, - ) - assert handle_config[notify.DOMAIN] - - def test_bad_config(self): - """Test set up the platform with bad/missing configuration.""" - config = {notify.DOMAIN: {"name": "test", "platform": "command_line"}} - with assert_setup_component(0) as handle_config: - assert setup_component(self.hass, notify.DOMAIN, config) - assert not handle_config[notify.DOMAIN] - - def test_command_line_output(self): - """Test the command line output.""" - with tempfile.TemporaryDirectory() as tempdirname: - filename = os.path.join(tempdirname, "message.txt") - message = "one, two, testing, testing" - with assert_setup_component(1) as handle_config: - assert setup_component( - self.hass, - notify.DOMAIN, - { - "notify": { - "name": "test", - "platform": "command_line", - "command": f"echo $(cat) > {filename}", - } - }, - ) - assert handle_config[notify.DOMAIN] - - assert self.hass.services.call( - "notify", "test", {"message": message}, blocking=True - ) - - with open(filename) as fil: - # the echo command adds a line break - assert fil.read() == f"{message}\n" - - @patch("homeassistant.components.command_line.notify._LOGGER.error") - def test_error_for_none_zero_exit_code(self, mock_error): - """Test if an error is logged for non zero exit codes.""" - with assert_setup_component(1) as handle_config: - assert setup_component( - self.hass, - notify.DOMAIN, - { - "notify": { - "name": "test", - "platform": "command_line", - "command": "echo $(cat); exit 1", - } - }, - ) - assert handle_config[notify.DOMAIN] - - assert self.hass.services.call( - "notify", "test", {"message": "error"}, blocking=True - ) - assert mock_error.call_count == 1 +from homeassistant import setup +from homeassistant.components.notify import DOMAIN +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType -async def test_timeout(hass, caplog): - """Test we do not block forever.""" - assert await async_setup_component( +async def setup_test_service( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line notify service.""" + assert await setup.async_setup_component( hass, - notify.DOMAIN, + DOMAIN, { - "notify": { - "name": "test", - "platform": "command_line", - "command": "sleep 10000", - "command_timeout": 0.0000001, - } + DOMAIN: [ + {"platform": "command_line", "name": "Test", **config_dict}, + ] }, ) await hass.async_block_till_done() + + +async def test_setup(hass: HomeAssistantType) -> None: + """Test sensor setup.""" + await setup_test_service(hass, {"command": "exit 0"}) + assert hass.services.has_service(DOMAIN, "test") + + +async def test_bad_config(hass: HomeAssistantType) -> None: + """Test set up the platform with bad/missing configuration.""" + await setup_test_service(hass, {}) + assert not hass.services.has_service(DOMAIN, "test") + + +async def test_command_line_output(hass: HomeAssistantType) -> None: + """Test the command line output.""" + with tempfile.TemporaryDirectory() as tempdirname: + filename = os.path.join(tempdirname, "message.txt") + message = "one, two, testing, testing" + await setup_test_service( + hass, + { + "command": f"cat > {filename}", + }, + ) + + assert hass.services.has_service(DOMAIN, "test") + + assert await hass.services.async_call( + DOMAIN, "test", {"message": message}, blocking=True + ) + with open(filename) as handle: + # the echo command adds a line break + assert message == handle.read() + + +async def test_error_for_none_zero_exit_code( + caplog: Any, hass: HomeAssistantType +) -> None: + """Test if an error is logged for non zero exit codes.""" + await setup_test_service( + hass, + { + "command": "exit 1", + }, + ) + assert await hass.services.async_call( - "notify", "test", {"message": "error"}, blocking=True + DOMAIN, "test", {"message": "error"}, blocking=True + ) + assert "Command failed" in caplog.text + + +async def test_timeout(caplog: Any, hass: HomeAssistantType) -> None: + """Test blocking is not forever.""" + await setup_test_service( + hass, + { + "command": "sleep 10000", + "command_timeout": 0.0000001, + }, + ) + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True ) - await hass.async_block_till_done() assert "Timeout" in caplog.text + + +async def test_subprocess_exceptions(caplog: Any, hass: HomeAssistantType) -> None: + """Test that notify subprocess exceptions are handled correctly.""" + + with patch( + "homeassistant.components.command_line.notify.subprocess.Popen", + side_effect=[ + subprocess.TimeoutExpired("cmd", 10), + subprocess.SubprocessError(), + ], + ) as check_output: + await setup_test_service(hass, {"command": "exit 0"}) + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True + ) + assert check_output.call_count == 1 + assert "Timeout for command" in caplog.text + + assert await hass.services.async_call( + DOMAIN, "test", {"message": "error"}, blocking=True + ) + assert check_output.call_count == 2 + assert "Error trying to exec command" in caplog.text diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 042c9acf432646..66472c5feba9e8 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -1,201 +1,224 @@ """The tests for the Command line sensor platform.""" -import unittest from unittest.mock import patch -from homeassistant.components.command_line import sensor as command_line -from homeassistant.helpers.template import Template - -from tests.common import get_test_home_assistant - - -class TestCommandSensorSensor(unittest.TestCase): - """Test the Command line sensor.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.hass.stop) - - def update_side_effect(self, data): - """Side effect function for mocking CommandSensorData.update().""" - self.commandline.data = data - - def test_setup(self): - """Test sensor setup.""" - config = { - "name": "Test", - "unit_of_measurement": "in", +from homeassistant import setup +from homeassistant.components.sensor import DOMAIN +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType + + +async def setup_test_entities( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line sensor entity.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "platform": "template", + "sensors": { + "template_sensor": { + "value_template": "template_value", + } + }, + }, + {"platform": "command_line", "name": "Test", **config_dict}, + ] + }, + ) + await hass.async_block_till_done() + + +async def test_setup(hass: HomeAssistantType) -> None: + """Test sensor setup.""" + await setup_test_entities( + hass, + { "command": "echo 5", - "command_timeout": 15, - } - devices = [] - - def add_dev_callback(devs, update): - """Add callback to add devices.""" - for dev in devs: - devices.append(dev) - - command_line.setup_platform(self.hass, config, add_dev_callback) - - assert len(devices) == 1 - entity = devices[0] - entity.update() - assert entity.name == "Test" - assert entity.unit_of_measurement == "in" - assert entity.state == "5" - - def test_template(self): - """Test command sensor with template.""" - data = command_line.CommandSensorData(self.hass, "echo 50", 15) - - entity = command_line.CommandSensor( - self.hass, - data, - "test", - "in", - Template("{{ value | multiply(0.1) }}", self.hass), - [], + "unit_of_measurement": "in", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "5" + assert entity_state.name == "Test" + assert entity_state.attributes["unit_of_measurement"] == "in" + + +async def test_template(hass: HomeAssistantType) -> None: + """Test command sensor with template.""" + await setup_test_entities( + hass, + { + "command": "echo 50", + "unit_of_measurement": "in", + "value_template": "{{ value | multiply(0.1) }}", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert float(entity_state.state) == 5 + + +async def test_template_render(hass: HomeAssistantType) -> None: + """Ensure command with templates get rendered properly.""" + + await setup_test_entities( + hass, + { + "command": "echo {{ states.sensor.template_sensor.state }}", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "template_value" + + +async def test_template_render_with_quote(hass: HomeAssistantType) -> None: + """Ensure command with templates and quotes get rendered properly.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + return_value=b"Works\n", + ) as check_output: + await setup_test_entities( + hass, + { + "command": 'echo "{{ states.sensor.template_sensor.state }}" "3 4"', + }, ) - entity.update() - assert float(entity.state) == 5 - - def test_template_render(self): - """Ensure command with templates get rendered properly.""" - self.hass.states.set("sensor.test_state", "Works") - data = command_line.CommandSensorData( - self.hass, "echo {{ states.sensor.test_state.state }}", 15 - ) - data.update() - - assert data.value == "Works" - - def test_template_render_with_quote(self): - """Ensure command with templates and quotes get rendered properly.""" - self.hass.states.set("sensor.test_state", "Works 2") - with patch( - "homeassistant.components.command_line.subprocess.check_output", - return_value=b"Works\n", - ) as check_output: - data = command_line.CommandSensorData( - self.hass, - 'echo "{{ states.sensor.test_state.state }}" "3 4"', - 15, - ) - data.update() - - assert data.value == "Works" check_output.assert_called_once_with( - 'echo "Works 2" "3 4"', shell=True, timeout=15 # nosec # shell by design - ) - - def test_bad_command(self): - """Test bad command.""" - data = command_line.CommandSensorData(self.hass, "asdfasdf", 15) - data.update() - - assert data.value is None - - def test_update_with_json_attrs(self): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, - ( - 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ - \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }' - ), - 15, - ) - - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key", "another_key", "key_three"] - ) - self.sensor.update() - assert self.sensor.device_state_attributes["key"] == "some_json_value" - assert ( - self.sensor.device_state_attributes["another_key"] == "another_json_value" - ) - assert self.sensor.device_state_attributes["key_three"] == "value_three" - - @patch("homeassistant.components.command_line.sensor._LOGGER") - def test_update_with_json_attrs_no_data(self, mock_logger): - """Test attributes when no JSON result fetched.""" - data = command_line.CommandSensorData(self.hass, "echo ", 15) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key"] - ) - self.sensor.update() - assert {} == self.sensor.device_state_attributes - assert mock_logger.warning.called - - @patch("homeassistant.components.command_line.sensor._LOGGER") - def test_update_with_json_attrs_not_dict(self, mock_logger): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData(self.hass, "echo [1, 2, 3]", 15) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key"] - ) - self.sensor.update() - assert {} == self.sensor.device_state_attributes - assert mock_logger.warning.called - - @patch("homeassistant.components.command_line.sensor._LOGGER") - def test_update_with_json_attrs_bad_JSON(self, mock_logger): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, "echo This is text rather than JSON data.", 15 - ) - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key"] - ) - self.sensor.update() - assert {} == self.sensor.device_state_attributes - assert mock_logger.warning.called - - def test_update_with_missing_json_attrs(self): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, - ( - 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ - \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }' - ), - 15, - ) - - self.sensor = command_line.CommandSensor( - self.hass, - data, - "test", - None, - None, - ["key", "another_key", "key_three", "special_key"], - ) - self.sensor.update() - assert self.sensor.device_state_attributes["key"] == "some_json_value" - assert ( - self.sensor.device_state_attributes["another_key"] == "another_json_value" - ) - assert self.sensor.device_state_attributes["key_three"] == "value_three" - assert "special_key" not in self.sensor.device_state_attributes - - def test_update_with_unnecessary_json_attrs(self): - """Test attributes get extracted from a JSON result.""" - data = command_line.CommandSensorData( - self.hass, - ( - 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ - \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }' - ), - 15, - ) - - self.sensor = command_line.CommandSensor( - self.hass, data, "test", None, None, ["key", "another_key"] - ) - self.sensor.update() - assert self.sensor.device_state_attributes["key"] == "some_json_value" - assert ( - self.sensor.device_state_attributes["another_key"] == "another_json_value" - ) - assert "key_three" not in self.sensor.device_state_attributes + 'echo "template_value" "3 4"', + shell=True, # nosec # shell by design + timeout=15, + ) + + +async def test_bad_template_render(caplog: Any, hass: HomeAssistantType) -> None: + """Test rendering a broken template.""" + + await setup_test_entities( + hass, + { + "command": "echo {{ this template doesn't parse", + }, + ) + + assert "Error rendering command template" in caplog.text + + +async def test_bad_command(hass: HomeAssistantType) -> None: + """Test bad command.""" + await setup_test_entities( + hass, + { + "command": "asdfasdf", + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.state == "unknown" + + +async def test_update_with_json_attrs(hass: HomeAssistantType) -> None: + """Test attributes get extracted from a JSON result.""" + await setup_test_entities( + hass, + { + "command": 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }', + "json_attributes": ["key", "another_key", "key_three"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.attributes["key"] == "some_json_value" + assert entity_state.attributes["another_key"] == "another_json_value" + assert entity_state.attributes["key_three"] == "value_three" + + +async def test_update_with_json_attrs_no_data(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when no JSON result fetched.""" + + await setup_test_entities( + hass, + { + "command": "echo", + "json_attributes": ["key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert "key" not in entity_state.attributes + assert "Empty reply found when expecting JSON data" in caplog.text + + +async def test_update_with_json_attrs_not_dict(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when the return value not a dict.""" + + await setup_test_entities( + hass, + { + "command": "echo [1, 2, 3]", + "json_attributes": ["key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert "key" not in entity_state.attributes + assert "JSON result was not a dictionary" in caplog.text + + +async def test_update_with_json_attrs_bad_json(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when the return value is invalid JSON.""" + + await setup_test_entities( + hass, + { + "command": "echo This is text rather than JSON data.", + "json_attributes": ["key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert "key" not in entity_state.attributes + assert "Unable to parse output as JSON" in caplog.text + + +async def test_update_with_missing_json_attrs(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when an expected key is missing.""" + + await setup_test_entities( + hass, + { + "command": 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }', + "json_attributes": ["key", "another_key", "key_three", "missing_key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.attributes["key"] == "some_json_value" + assert entity_state.attributes["another_key"] == "another_json_value" + assert entity_state.attributes["key_three"] == "value_three" + assert "missing_key" not in entity_state.attributes + + +async def test_update_with_unnecessary_json_attrs(caplog, hass: HomeAssistantType) -> None: # type: ignore[no-untyped-def] + """Test attributes when an expected key is missing.""" + + await setup_test_entities( + hass, + { + "command": 'echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }', + "json_attributes": ["key", "another_key"], + }, + ) + entity_state = hass.states.get("sensor.test") + assert entity_state + assert entity_state.attributes["key"] == "some_json_value" + assert entity_state.attributes["another_key"] == "another_json_value" + assert "key_three" not in entity_state.attributes diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index c6d315b05b59e1..0e31999f928890 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -1,10 +1,12 @@ """The tests for the Command line switch platform.""" import json import os +import subprocess import tempfile +from unittest.mock import patch -import homeassistant.components.command_line.switch as command_line -import homeassistant.components.switch as switch +from homeassistant import setup +from homeassistant.components.switch import DOMAIN, SCAN_INTERVAL from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -12,230 +14,358 @@ STATE_OFF, STATE_ON, ) -from homeassistant.setup import async_setup_component +from homeassistant.helpers.typing import Any, Dict, HomeAssistantType +import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed -async def test_state_none(hass): + +async def setup_test_entity( + hass: HomeAssistantType, config_dict: Dict[str, Any] +) -> None: + """Set up a test command line switch entity.""" + assert await setup.async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + {"platform": "command_line", "switches": config_dict}, + ] + }, + ) + await hass.async_block_till_done() + + +async def test_state_none(hass: HomeAssistantType) -> None: """Test with none state.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") - test_switch = { - "command_on": f"echo 1 > {path}", - "command_off": f"echo 0 > {path}", - } - assert await async_setup_component( + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_on": f"echo 1 > {path}", + "command_off": f"echo 0 > {path}", } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF -async def test_state_value(hass): +async def test_state_value(hass: HomeAssistantType) -> None: """Test with state value.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") - test_switch = { - "command_state": f"cat {path}", - "command_on": f"echo 1 > {path}", - "command_off": f"echo 0 > {path}", - "value_template": '{{ value=="1" }}', - } - assert await async_setup_component( + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_state": f"cat {path}", + "command_on": f"echo 1 > {path}", + "command_off": f"echo 0 > {path}", + "value_template": '{{ value=="1" }}', } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF -async def test_state_json_value(hass): +async def test_state_json_value(hass: HomeAssistantType) -> None: """Test with state JSON value.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") oncmd = json.dumps({"status": "ok"}) offcmd = json.dumps({"status": "nope"}) - test_switch = { - "command_state": f"cat {path}", - "command_on": f"echo '{oncmd}' > {path}", - "command_off": f"echo '{offcmd}' > {path}", - "value_template": '{{ value_json.status=="ok" }}', - } - assert await async_setup_component( + + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_state": f"cat {path}", + "command_on": f"echo '{oncmd}' > {path}", + "command_off": f"echo '{offcmd}' > {path}", + "value_template": '{{ value_json.status=="ok" }}', } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF -async def test_state_code(hass): +async def test_state_code(hass: HomeAssistantType) -> None: """Test with state code.""" with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, "switch_status") - test_switch = { - "command_state": f"cat {path}", - "command_on": f"echo 1 > {path}", - "command_off": f"echo 0 > {path}", - } - assert await async_setup_component( + await setup_test_entity( hass, - switch.DOMAIN, { - "switch": { - "platform": "command_line", - "switches": {"test": test_switch}, + "test": { + "command_state": f"cat {path}", + "command_on": f"echo 1 > {path}", + "command_off": f"echo 0 > {path}", } }, ) - await hass.async_block_till_done() - state = hass.states.get("switch.test") - assert STATE_OFF == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_OFF await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON await hass.services.async_call( - switch.DOMAIN, + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"}, blocking=True, ) - state = hass.states.get("switch.test") - assert STATE_ON == state.state + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.state == STATE_ON + +async def test_assumed_state_should_be_true_if_command_state_is_none( + hass: HomeAssistantType, +) -> None: + """Test with state value.""" -def test_assumed_state_should_be_true_if_command_state_is_none(hass): + await setup_test_entity( + hass, + { + "test": { + "command_on": "echo 'on command'", + "command_off": "echo 'off command'", + } + }, + ) + entity_state = hass.states.get("switch.test") + assert entity_state + assert entity_state.attributes["assumed_state"] + + +async def test_assumed_state_should_absent_if_command_state_present( + hass: HomeAssistantType, +) -> None: """Test with state value.""" - # args: hass, device_name, friendly_name, command_on, command_off, - # command_state, value_template - init_args = [ + + await setup_test_entity( + hass, + { + "test": { + "command_on": "echo 'on command'", + "command_off": "echo 'off command'", + "command_state": "cat {}", + } + }, + ) + entity_state = hass.states.get("switch.test") + assert entity_state + assert "assumed_state" not in entity_state.attributes + + +async def test_name_is_set_correctly(hass: HomeAssistantType) -> None: + """Test that name is set correctly.""" + await setup_test_entity( + hass, + { + "test": { + "command_on": "echo 'on command'", + "command_off": "echo 'off command'", + "friendly_name": "Test friendly name!", + } + }, + ) + + entity_state = hass.states.get("switch.test") + assert entity_state.name == "Test friendly name!" + + +async def test_switch_command_state_fail(caplog: Any, hass: HomeAssistantType) -> None: + """Test that switch failures are handled correctly.""" + await setup_test_entity( hass, - "test_device_name", - "Test friendly name!", - "echo 'on command'", - "echo 'off command'", - None, - None, - 15, - ] + { + "test": { + "command_on": "exit 0", + "command_off": "exit 0'", + "command_state": "echo 1", + } + }, + ) + + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + entity_state = hass.states.get("switch.test") + assert entity_state.state == "on" + + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.test"}, + blocking=True, + ) + await hass.async_block_till_done() + + entity_state = hass.states.get("switch.test") + assert entity_state.state == "on" + + assert "Command failed" in caplog.text + + +async def test_switch_command_state_code_exceptions( + caplog: Any, hass: HomeAssistantType +) -> None: + """Test that switch state code exceptions are handled correctly.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + side_effect=[ + subprocess.TimeoutExpired("cmd", 10), + subprocess.SubprocessError(), + ], + ) as check_output: + await setup_test_entity( + hass, + { + "test": { + "command_on": "exit 0", + "command_off": "exit 0'", + "command_state": "echo 1", + } + }, + ) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert check_output.called + assert "Timeout for command" in caplog.text - no_state_device = command_line.CommandSwitch(*init_args) - assert no_state_device.assumed_state + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) + await hass.async_block_till_done() + assert check_output.called + assert "Error trying to exec command" in caplog.text + + +async def test_switch_command_state_value_exceptions( + caplog: Any, hass: HomeAssistantType +) -> None: + """Test that switch state value exceptions are handled correctly.""" + + with patch( + "homeassistant.components.command_line.subprocess.check_output", + side_effect=[ + subprocess.TimeoutExpired("cmd", 10), + subprocess.SubprocessError(), + ], + ) as check_output: + await setup_test_entity( + hass, + { + "test": { + "command_on": "exit 0", + "command_off": "exit 0'", + "command_state": "echo 1", + "value_template": '{{ value=="1" }}', + } + }, + ) + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert check_output.call_count == 1 + assert "Timeout for command" in caplog.text - # Set state command - init_args[-3] = "cat {}" + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) + await hass.async_block_till_done() + assert check_output.call_count == 2 + assert "Error trying to exec command" in caplog.text - state_device = command_line.CommandSwitch(*init_args) - assert not state_device.assumed_state +async def test_no_switches(caplog: Any, hass: HomeAssistantType) -> None: + """Test with no switches.""" -def test_entity_id_set_correctly(hass): - """Test that entity_id is set correctly from object_id.""" - init_args = [ - hass, - "test_device_name", - "Test friendly name!", - "echo 'on command'", - "echo 'off command'", - False, - None, - 15, - ] - - test_switch = command_line.CommandSwitch(*init_args) - assert test_switch.entity_id == "switch.test_device_name" - assert test_switch.name == "Test friendly name!" + await setup_test_entity(hass, {}) + assert "No switches" in caplog.text From adad4a7785f168acf383cdaafd3e67902d7bd972 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 1 Mar 2021 18:27:43 +0200 Subject: [PATCH 0899/1818] Fix Shelly Polling (#47224) --- homeassistant/components/shelly/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index d4423dc3a88968..ccb5212752546e 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -108,6 +108,7 @@ def _async_device_online(_): "Setup for device %s will resume when device is online", entry.title ) device.subscribe_updates(_async_device_online) + await device.coap_request("s") else: # Restore sensors for sleeping device _LOGGER.debug("Setting up offline device %s", entry.title) From 61f509bdd82045db362cd482cc0d0a76bc5639b5 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 1 Mar 2021 09:10:28 -0800 Subject: [PATCH 0900/1818] Minor Hyperion mypy cleanups (#45765) --- homeassistant/components/hyperion/__init__.py | 3 ++- homeassistant/components/hyperion/light.py | 19 ++++++++++++++----- homeassistant/components/hyperion/switch.py | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index 9e35ae2e6b8562..8c001c2b46789f 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -286,7 +286,8 @@ async def setup_then_listen() -> None: ] ) assert hyperion_client - await async_instances_to_clients_raw(hyperion_client.instances) + if hyperion_client.instances is not None: + await async_instances_to_clients_raw(hyperion_client.instances) hass.data[DOMAIN][config_entry.entry_id][CONF_ON_UNLOAD].append( config_entry.add_update_listener(_async_entry_updated) ) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 7bb8a75dfc7451..c49a65b2bfd588 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -4,7 +4,7 @@ import functools import logging from types import MappingProxyType -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple +from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple from hyperion import client, const @@ -147,7 +147,7 @@ def __init__( self._static_effect_list += list(const.KEY_COMPONENTID_EXTERNAL_SOURCES) self._effect_list: List[str] = self._static_effect_list[:] - self._client_callbacks = { + self._client_callbacks: Mapping[str, Callable[[Dict[str, Any]], None]] = { f"{const.KEY_ADJUSTMENT}-{const.KEY_UPDATE}": self._update_adjustment, f"{const.KEY_COMPONENTS}-{const.KEY_UPDATE}": self._update_components, f"{const.KEY_EFFECTS}-{const.KEY_UPDATE}": self._update_effect_list, @@ -236,7 +236,7 @@ async def async_turn_on(self, **kwargs: Any) -> None: # == Set brightness == if ATTR_BRIGHTNESS in kwargs: brightness = kwargs[ATTR_BRIGHTNESS] - for item in self._client.adjustment: + for item in self._client.adjustment or []: if const.KEY_ID in item: if not await self._client.async_send_set_adjustment( **{ @@ -423,7 +423,12 @@ def _support_external_effects(self) -> bool: def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: """Get the relevant Hyperion priority entry to consider.""" # Return the visible priority (whether or not it is the HA priority). - return self._client.visible_priority # type: ignore[no-any-return] + + # Explicit type specifier to ensure this works when the underlying (typed) + # library is installed along with the tests. Casts would trigger a + # redundant-cast warning in this case. + priority: Optional[Dict[str, Any]] = self._client.visible_priority + return priority # pylint: disable=no-self-use def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: @@ -530,7 +535,11 @@ def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: if candidate[const.KEY_PRIORITY] == self._get_option( CONF_PRIORITY ) and candidate.get(const.KEY_ACTIVE, False): - return candidate # type: ignore[no-any-return] + # Explicit type specifier to ensure this works when the underlying + # (typed) library is installed along with the tests. Casts would trigger + # a redundant-cast warning in this case. + output: Dict[str, Any] = candidate + return output return None @classmethod diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index 9d90e1e12ef4e9..0412018650aa68 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -157,7 +157,7 @@ def name(self) -> str: @property def is_on(self) -> bool: """Return true if the switch is on.""" - for component in self._client.components: + for component in self._client.components or []: if component[KEY_NAME] == self._component_name: return bool(component.setdefault(KEY_ENABLED, False)) return False From 3c290c9a44e377ac9cfbf9452e52558d9e16aad4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 12:10:51 -0500 Subject: [PATCH 0901/1818] Address late hassio review (#47229) * hassio code cleanup to address comments in #46342 * fix code --- homeassistant/components/hassio/__init__.py | 45 +++++++++++---------- tests/components/hassio/test_init.py | 2 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 82797874445cbd..4e9a78e75a8cc2 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -522,15 +522,17 @@ def async_register_addons_in_dev_reg( ) -> None: """Register addons in the device registry.""" for addon in addons: - dev_reg.async_get_or_create( - config_entry_id=entry_id, - identifiers={(DOMAIN, addon[ATTR_SLUG])}, - manufacturer=addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL) or "unknown", - model="Home Assistant Add-on", - sw_version=addon[ATTR_VERSION], - name=addon[ATTR_NAME], - entry_type=ATTR_SERVICE, - ) + params = { + "config_entry_id": entry_id, + "identifiers": {(DOMAIN, addon[ATTR_SLUG])}, + "model": "Home Assistant Add-on", + "sw_version": addon[ATTR_VERSION], + "name": addon[ATTR_NAME], + "entry_type": ATTR_SERVICE, + } + if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): + params["manufacturer"] = manufacturer + dev_reg.async_get_or_create(**params) @callback @@ -538,15 +540,16 @@ def async_register_os_in_dev_reg( entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any] ) -> None: """Register OS in the device registry.""" - dev_reg.async_get_or_create( - config_entry_id=entry_id, - identifiers={(DOMAIN, "OS")}, - manufacturer="Home Assistant", - model="Home Assistant Operating System", - sw_version=os_dict[ATTR_VERSION], - name="Home Assistant Operating System", - entry_type=ATTR_SERVICE, - ) + params = { + "config_entry_id": entry_id, + "identifiers": {(DOMAIN, "OS")}, + "manufacturer": "Home Assistant", + "model": "Home Assistant Operating System", + "sw_version": os_dict[ATTR_VERSION], + "name": "Home Assistant Operating System", + "entry_type": ATTR_SERVICE, + } + dev_reg.async_get_or_create(**params) @callback @@ -600,15 +603,13 @@ async def _async_update_data(self) -> Dict[str, Any]: return new_data # Remove add-ons that are no longer installed from device registry - if removed_addons := list( - set(self.data["addons"].keys()) - set(new_data["addons"].keys()) - ): + if removed_addons := list(set(self.data["addons"]) - set(new_data["addons"])): async_remove_addons_from_dev_reg(self.dev_reg, removed_addons) # If there are new add-ons, we should reload the config entry so we can # create new devices and entities. We can return an empty dict because # coordinator will be recreated. - if list(set(new_data["addons"].keys()) - set(self.data["addons"].keys())): + if list(set(new_data["addons"]) - set(self.data["addons"])): self.hass.async_create_task( self.hass.config_entries.async_reload(self.entry_id) ) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index bd9eb30be5c4af..5bf8a45ab52190 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -393,7 +393,7 @@ async def test_entry_load_and_unload(hass): assert BINARY_SENSOR_DOMAIN in hass.config.components assert ADDONS_COORDINATOR in hass.data - assert await config_entry.async_unload(hass) + assert await hass.config_entries.async_unload(config_entry.entry_id) await hass.async_block_till_done() assert ADDONS_COORDINATOR not in hass.data From 3fda9fd0c636b93ef091002f9035e21d8e19a381 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 1 Mar 2021 21:59:36 +0100 Subject: [PATCH 0902/1818] KNX address constant (#47196) --- homeassistant/components/knx/__init__.py | 21 ++++++++++----------- homeassistant/components/knx/const.py | 7 +++++-- homeassistant/components/knx/expose.py | 4 ++-- homeassistant/components/knx/factory.py | 16 ++++++++-------- homeassistant/components/knx/schema.py | 24 ++++++++++++------------ 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index ca1c19c3f5c76c..697b4cfb5244df 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -18,7 +18,6 @@ from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite from homeassistant.const import ( - CONF_ADDRESS, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, @@ -32,7 +31,7 @@ from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ServiceCallType -from .const import DOMAIN, SupportedPlatforms +from .const import DOMAIN, KNX_ADDRESS, SupportedPlatforms from .expose import create_knx_exposure from .factory import create_knx_device from .schema import ( @@ -150,7 +149,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any( vol.Schema( { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ), @@ -161,7 +160,7 @@ vol.Schema( # without type given payload is treated as raw bytes { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ), @@ -174,7 +173,7 @@ SERVICE_KNX_READ_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ) @@ -183,7 +182,7 @@ SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( { - vol.Required(CONF_ADDRESS): vol.All( + vol.Required(KNX_ADDRESS): vol.All( cv.ensure_list, [ga_validator], ), @@ -200,7 +199,7 @@ vol.Schema( # for removing only `address` is required { - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True), }, extra=vol.ALLOW_EXTRA, @@ -421,7 +420,7 @@ def register_callback(self) -> TelegramQueue.Callback: async def service_event_register_modify(self, call): """Service for adding or removing a GroupAddress to the knx_event filter.""" - attr_address = call.data.get(CONF_ADDRESS) + attr_address = call.data.get(KNX_ADDRESS) group_addresses = map(GroupAddress, attr_address) if call.data.get(SERVICE_KNX_ATTR_REMOVE): @@ -444,7 +443,7 @@ async def service_event_register_modify(self, call): async def service_exposure_register_modify(self, call): """Service for adding or removing an exposure to KNX bus.""" - group_address = call.data.get(CONF_ADDRESS) + group_address = call.data.get(KNX_ADDRESS) if call.data.get(SERVICE_KNX_ATTR_REMOVE): try: @@ -475,7 +474,7 @@ async def service_exposure_register_modify(self, call): async def service_send_to_knx_bus(self, call): """Service for sending an arbitrary KNX message to the KNX bus.""" - attr_address = call.data.get(CONF_ADDRESS) + attr_address = call.data.get(KNX_ADDRESS) attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) @@ -499,7 +498,7 @@ async def service_send_to_knx_bus(self, call): async def service_read_to_knx_bus(self, call): """Service for sending a GroupValueRead telegram to the KNX bus.""" - for address in call.data.get(CONF_ADDRESS): + for address in call.data.get(KNX_ADDRESS): telegram = Telegram( destination_address=GroupAddress(address), payload=GroupValueRead(), diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 1829826834c343..268f766bfc2b7d 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -17,11 +17,16 @@ DOMAIN = "knx" +# Address is used for configuration and services by the same functions so the key has to match +KNX_ADDRESS = "address" + CONF_INVERT = "invert" CONF_STATE_ADDRESS = "state_address" CONF_SYNC_STATE = "sync_state" CONF_RESET_AFTER = "reset_after" +ATTR_COUNTER = "counter" + class ColorTempModes(Enum): # pylint: disable=invalid-name @@ -66,5 +71,3 @@ class SupportedPlatforms(Enum): "Standby": PRESET_AWAY, "Comfort": PRESET_COMFORT, } - -ATTR_COUNTER = "counter" diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 5abc58f82cca8a..3a010810862308 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -5,7 +5,6 @@ from xknx.devices import DateTime, ExposeSensor from homeassistant.const import ( - CONF_ADDRESS, CONF_ENTITY_ID, STATE_OFF, STATE_ON, @@ -16,6 +15,7 @@ from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.typing import ConfigType +from .const import KNX_ADDRESS from .schema import ExposeSchema @@ -24,7 +24,7 @@ def create_knx_exposure( hass: HomeAssistant, xknx: XKNX, config: ConfigType ) -> Union["KNXExposeSensor", "KNXExposeTime"]: """Create exposures from config.""" - address = config[CONF_ADDRESS] + address = config[KNX_ADDRESS] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) entity_id = config.get(CONF_ENTITY_ID) expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 51a94bc06e3c00..893ac1e55e32ef 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -17,10 +17,10 @@ Weather as XknxWeather, ) -from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE from homeassistant.helpers.typing import ConfigType -from .const import ColorTempModes, SupportedPlatforms +from .const import KNX_ADDRESS, ColorTempModes, SupportedPlatforms from .schema import ( BinarySensorSchema, ClimateSchema, @@ -99,7 +99,7 @@ def _create_light_color( """Load color configuration from configuration structure.""" if "individual_colors" in config and color in config["individual_colors"]: sub_config = config["individual_colors"][color] - group_address_switch = sub_config.get(CONF_ADDRESS) + group_address_switch = sub_config.get(KNX_ADDRESS) group_address_switch_state = sub_config.get(LightSchema.CONF_STATE_ADDRESS) group_address_brightness = sub_config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS) group_address_brightness_state = sub_config.get( @@ -160,7 +160,7 @@ def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight: return XknxLight( knx_module, name=config[CONF_NAME], - group_address_switch=config.get(CONF_ADDRESS), + group_address_switch=config.get(KNX_ADDRESS), group_address_switch_state=config.get(LightSchema.CONF_STATE_ADDRESS), group_address_brightness=config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS), group_address_brightness_state=config.get( @@ -275,7 +275,7 @@ def _create_switch(knx_module: XKNX, config: ConfigType) -> XknxSwitch: return XknxSwitch( knx_module, name=config[CONF_NAME], - group_address=config[CONF_ADDRESS], + group_address=config[KNX_ADDRESS], group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS), invert=config.get(SwitchSchema.CONF_INVERT), ) @@ -298,7 +298,7 @@ def _create_notify(knx_module: XKNX, config: ConfigType) -> XknxNotification: return XknxNotification( knx_module, name=config[CONF_NAME], - group_address=config[CONF_ADDRESS], + group_address=config[KNX_ADDRESS], ) @@ -307,7 +307,7 @@ def _create_scene(knx_module: XKNX, config: ConfigType) -> XknxScene: return XknxScene( knx_module, name=config[CONF_NAME], - group_address=config[CONF_ADDRESS], + group_address=config[KNX_ADDRESS], scene_number=config[SceneSchema.CONF_SCENE_NUMBER], ) @@ -372,7 +372,7 @@ def _create_fan(knx_module: XKNX, config: ConfigType) -> XknxFan: fan = XknxFan( knx_module, name=config[CONF_NAME], - group_address_speed=config.get(CONF_ADDRESS), + group_address_speed=config.get(KNX_ADDRESS), group_address_speed_state=config.get(FanSchema.CONF_STATE_ADDRESS), group_address_oscillation=config.get(FanSchema.CONF_OSCILLATION_ADDRESS), group_address_oscillation_state=config.get( diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 08a8c62adc421b..c57539b501fbf5 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -5,7 +5,6 @@ from xknx.telegram.address import GroupAddress, IndividualAddress from homeassistant.const import ( - CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_HOST, @@ -21,6 +20,7 @@ CONF_STATE_ADDRESS, CONF_SYNC_STATE, CONTROLLER_MODES, + KNX_ADDRESS, PRESET_MODES, ColorTempModes, ) @@ -256,7 +256,7 @@ class ExposeSchema: SCHEMA = vol.Schema( { vol.Required(CONF_KNX_EXPOSE_TYPE): vol.Any(int, float, str), - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, @@ -277,7 +277,7 @@ class FanSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_OSCILLATION_ADDRESS): ga_validator, vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_validator, @@ -315,7 +315,7 @@ class LightSchema: COLOR_SCHEMA = vol.Schema( { - vol.Optional(CONF_ADDRESS): ga_validator, + vol.Optional(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Required(CONF_BRIGHTNESS_ADDRESS): ga_validator, vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator, @@ -326,7 +326,7 @@ class LightSchema: vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_ADDRESS): ga_validator, + vol.Optional(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_BRIGHTNESS_ADDRESS): ga_validator, vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator, @@ -358,16 +358,16 @@ class LightSchema: vol.Schema( { vol.Required(CONF_INDIVIDUAL_COLORS): { - vol.Required(CONF_RED): {vol.Required(CONF_ADDRESS): object}, - vol.Required(CONF_GREEN): {vol.Required(CONF_ADDRESS): object}, - vol.Required(CONF_BLUE): {vol.Required(CONF_ADDRESS): object}, + vol.Required(CONF_RED): {vol.Required(KNX_ADDRESS): object}, + vol.Required(CONF_GREEN): {vol.Required(KNX_ADDRESS): object}, + vol.Required(CONF_BLUE): {vol.Required(KNX_ADDRESS): object}, }, }, extra=vol.ALLOW_EXTRA, ), vol.Schema( { - vol.Required(CONF_ADDRESS): object, + vol.Required(KNX_ADDRESS): object, }, extra=vol.ALLOW_EXTRA, ), @@ -383,7 +383,7 @@ class NotifySchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, } ) @@ -397,7 +397,7 @@ class SceneSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Required(CONF_SCENE_NUMBER): cv.positive_int, } ) @@ -432,7 +432,7 @@ class SwitchSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ADDRESS): ga_validator, + vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_INVERT): cv.boolean, } From dd9e926689557eaf70e41a171f3ec7393998eeae Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Mar 2021 23:34:26 +0100 Subject: [PATCH 0903/1818] Pass variables to initial evaluation of template trigger (#47236) * Pass variables to initial evaluation of template trigger * Add test * Clarify test --- homeassistant/components/template/trigger.py | 4 ++- tests/components/template/test_trigger.py | 38 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 9e6ee086c734c5..1f378c593353ea 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -41,7 +41,9 @@ async def async_attach_trigger( # Arm at setup if the template is already false. try: - if not result_as_boolean(value_template.async_render()): + if not result_as_boolean( + value_template.async_render(automation_info["variables"]) + ): armed = True except exceptions.TemplateError as ex: _LOGGER.warning( diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 3ba79e85bf2e31..55311005201bf3 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -135,6 +135,44 @@ async def test_if_not_fires_when_true_at_setup(hass, calls): assert len(calls) == 0 +async def test_if_not_fires_when_true_at_setup_variables(hass, calls): + """Test for not firing during startup + trigger_variables.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"entity": "test.entity"}, + "trigger": { + "platform": "template", + "value_template": '{{ is_state(entity|default("test.entity2"), "hello") }}', + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + # Assert that the trigger doesn't fire immediately when it's setup + # If trigger_variable 'entity' is not passed to initial check at setup, the + # trigger will immediately fire + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + hass.states.async_set("test.entity", "goodbye", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + # Assert that the trigger fires after state change + # If trigger_variable 'entity' is not passed to the template trigger, the + # trigger will never fire because it falls back to 'test.entity2' + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_not_fires_because_fail(hass, calls): """Test for not firing after TemplateError.""" hass.states.async_set("test.number", "1") From 96cc17b462c107117382a30bbc30ede246e723ed Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 00:18:18 +0100 Subject: [PATCH 0904/1818] Add support for a list of known hosts to Google Cast (#47232) --- homeassistant/components/cast/config_flow.py | 149 +++++++++++++++--- homeassistant/components/cast/const.py | 4 + homeassistant/components/cast/discovery.py | 17 +- homeassistant/components/cast/manifest.json | 2 +- homeassistant/components/cast/media_player.py | 8 +- homeassistant/components/cast/strings.json | 26 ++- .../components/cast/translations/en.json | 24 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/conftest.py | 76 +++++++++ tests/components/cast/test_init.py | 140 +++++++++++++++- tests/components/cast/test_media_player.py | 69 +------- 12 files changed, 417 insertions(+), 102 deletions(-) create mode 100644 tests/components/cast/conftest.py diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index e00048a7589077..4a4426a5db1ed5 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -1,35 +1,136 @@ """Config flow for Cast.""" -import functools - -from pychromecast.discovery import discover_chromecasts, stop_discovery +import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import zeroconf -from homeassistant.helpers import config_entry_flow +from homeassistant.helpers import config_validation as cv + +from .const import CONF_KNOWN_HOSTS, DOMAIN + +KNOWN_HOSTS_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) + + +class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + def __init__(self): + """Initialize flow.""" + self._known_hosts = None + + @staticmethod + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return CastOptionsFlowHandler(config_entry) + + async def async_step_import(self, import_data=None): + """Import data.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + data = {CONF_KNOWN_HOSTS: self._known_hosts} + return self.async_create_entry(title="Google Cast", data=data) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return await self.async_step_config() + + async def async_step_zeroconf(self, discovery_info): + """Handle a flow initialized by zeroconf discovery.""" + if self._async_in_progress() or self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + await self.async_set_unique_id(DOMAIN) + + return await self.async_step_confirm() + + async def async_step_config(self, user_input=None): + """Confirm the setup.""" + errors = {} + data = {CONF_KNOWN_HOSTS: self._known_hosts} + + if user_input is not None: + bad_hosts = False + known_hosts = user_input[CONF_KNOWN_HOSTS] + known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] + try: + known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) + except vol.Invalid: + errors["base"] = "invalid_known_hosts" + bad_hosts = True + else: + data[CONF_KNOWN_HOSTS] = known_hosts + if not bad_hosts: + return self.async_create_entry(title="Google Cast", data=data) + + fields = {} + fields[vol.Optional(CONF_KNOWN_HOSTS, default="")] = str + + return self.async_show_form( + step_id="config", data_schema=vol.Schema(fields), errors=errors + ) + + async def async_step_confirm(self, user_input=None): + """Confirm the setup.""" + + data = {CONF_KNOWN_HOSTS: self._known_hosts} + + if user_input is not None: + return self.async_create_entry(title="Google Cast", data=data) + + return self.async_show_form(step_id="confirm") -from .const import DOMAIN -from .helpers import ChromeCastZeroconf +class CastOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Google Cast options.""" -async def _async_has_devices(hass): - """ - Return if there are devices that can be discovered. + def __init__(self, config_entry): + """Initialize MQTT options flow.""" + self.config_entry = config_entry + self.broker_config = {} + self.options = dict(config_entry.options) - This function will be called if no devices are already found through the zeroconf - integration. - """ + async def async_step_init(self, user_input=None): + """Manage the Cast options.""" + return await self.async_step_options() - zeroconf_instance = ChromeCastZeroconf.get_zeroconf() - if zeroconf_instance is None: - zeroconf_instance = await zeroconf.async_get_instance(hass) + async def async_step_options(self, user_input=None): + """Manage the MQTT options.""" + errors = {} + current_config = self.config_entry.data + if user_input is not None: + bad_hosts = False - casts, browser = await hass.async_add_executor_job( - functools.partial(discover_chromecasts, zeroconf_instance=zeroconf_instance) - ) - stop_discovery(browser) - return casts + known_hosts = user_input.get(CONF_KNOWN_HOSTS, "") + known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] + try: + known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) + except vol.Invalid: + errors["base"] = "invalid_known_hosts" + bad_hosts = True + if not bad_hosts: + updated_config = {} + updated_config[CONF_KNOWN_HOSTS] = known_hosts + self.hass.config_entries.async_update_entry( + self.config_entry, data=updated_config + ) + return self.async_create_entry(title="", data=None) + fields = {} + known_hosts_string = "" + if current_config.get(CONF_KNOWN_HOSTS): + known_hosts_string = ",".join(current_config.get(CONF_KNOWN_HOSTS)) + fields[ + vol.Optional( + "known_hosts", description={"suggested_value": known_hosts_string} + ) + ] = str -config_entry_flow.register_discovery_flow( - DOMAIN, "Google Cast", _async_has_devices, config_entries.CONN_CLASS_LOCAL_PUSH -) + return self.async_show_form( + step_id="options", + data_schema=vol.Schema(fields), + errors=errors, + ) diff --git a/homeassistant/components/cast/const.py b/homeassistant/components/cast/const.py index c6164484dbbb14..993315b551850f 100644 --- a/homeassistant/components/cast/const.py +++ b/homeassistant/components/cast/const.py @@ -13,6 +13,8 @@ ADDED_CAST_DEVICES_KEY = "cast_added_cast_devices" # Stores an audio group manager. CAST_MULTIZONE_MANAGER_KEY = "cast_multizone_manager" +# Store a CastBrowser +CAST_BROWSER_KEY = "cast_browser" # Dispatcher signal fired with a ChromecastInfo every time we discover a new # Chromecast or receive it through configuration @@ -24,3 +26,5 @@ # Dispatcher signal fired when a Chromecast should show a Home Assistant Cast view. SIGNAL_HASS_CAST_SHOW_VIEW = "cast_show_view" + +CONF_KNOWN_HOSTS = "known_hosts" diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 81048b35a97eaa..fcae28b5bfe124 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -9,6 +9,8 @@ from homeassistant.helpers.dispatcher import dispatcher_send from .const import ( + CAST_BROWSER_KEY, + CONF_KNOWN_HOSTS, DEFAULT_PORT, INTERNAL_DISCOVERY_RUNNING_KEY, KNOWN_CHROMECAST_INFO_KEY, @@ -52,7 +54,7 @@ def _remove_chromecast(hass: HomeAssistant, info: ChromecastInfo): dispatcher_send(hass, SIGNAL_CAST_REMOVED, info) -def setup_internal_discovery(hass: HomeAssistant) -> None: +def setup_internal_discovery(hass: HomeAssistant, config_entry) -> None: """Set up the pychromecast internal discovery.""" if INTERNAL_DISCOVERY_RUNNING_KEY not in hass.data: hass.data[INTERNAL_DISCOVERY_RUNNING_KEY] = threading.Lock() @@ -86,8 +88,11 @@ def remove_cast(self, uuid, service, cast_info): _LOGGER.debug("Starting internal pychromecast discovery") browser = pychromecast.discovery.CastBrowser( - CastListener(), ChromeCastZeroconf.get_zeroconf() + CastListener(), + ChromeCastZeroconf.get_zeroconf(), + config_entry.data.get(CONF_KNOWN_HOSTS), ) + hass.data[CAST_BROWSER_KEY] = browser browser.start_discovery() def stop_discovery(event): @@ -97,3 +102,11 @@ def stop_discovery(event): hass.data[INTERNAL_DISCOVERY_RUNNING_KEY].release() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_discovery) + + config_entry.add_update_listener(config_entry_updated) + + +async def config_entry_updated(hass, config_entry): + """Handle config entry being updated.""" + browser = hass.data[CAST_BROWSER_KEY] + browser.host_browser.update_hosts(config_entry.data.get(CONF_KNOWN_HOSTS)) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index ac728b4ec4587a..0c9d0dfc4a583d 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==9.0.0"], + "requirements": ["pychromecast==9.1.1"], "after_dependencies": ["cloud", "http", "media_source", "plex", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 235c7ab447972e..fbb084378305b8 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -134,7 +134,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # no pending task done, _ = await asyncio.wait( [ - _async_setup_platform(hass, ENTITY_SCHEMA(cfg), async_add_entities) + _async_setup_platform( + hass, ENTITY_SCHEMA(cfg), async_add_entities, config_entry + ) for cfg in config ] ) @@ -146,7 +148,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def _async_setup_platform( - hass: HomeAssistantType, config: ConfigType, async_add_entities + hass: HomeAssistantType, config: ConfigType, async_add_entities, config_entry ): """Set up the cast platform.""" # Import CEC IGNORE attributes @@ -177,7 +179,7 @@ def async_cast_discovered(discover: ChromecastInfo) -> None: async_cast_discovered(chromecast) ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) - hass.async_add_executor_job(setup_internal_discovery, hass) + hass.async_add_executor_job(setup_internal_discovery, hass, config_entry) class CastDevice(MediaPlayerEntity): diff --git a/homeassistant/components/cast/strings.json b/homeassistant/components/cast/strings.json index ad8f0f41ae7b29..7cd07518db8871 100644 --- a/homeassistant/components/cast/strings.json +++ b/homeassistant/components/cast/strings.json @@ -3,11 +3,33 @@ "step": { "confirm": { "description": "[%key:common::config_flow::description::confirm_setup%]" + }, + "config": { + "title": "Google Cast", + "description": "Please enter the Google Cast configuration.", + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + } } }, "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." + } + }, + "options": { + "step": { + "options": { + "description": "Please enter the Google Cast configuration.", + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + } + } + }, + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." } } } diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index f05becffed3131..9ef2f10cda2160 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -1,13 +1,35 @@ { "config": { "abort": { - "no_devices_found": "No devices found on the network", "single_instance_allowed": "Already configured. Only a single configuration possible." }, + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." + }, "step": { + "config": { + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + }, + "description": "Please enter the Google Cast configuration.", + "title": "Google Cast" + }, "confirm": { "description": "Do you want to start set up?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Known hosts must be a comma separated list of hosts." + }, + "step": { + "options": { + "data": { + "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + }, + "description": "Please enter the Google Cast configuration." + } + } } } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index 5bd96e376839a0..761c47e34da93d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1302,7 +1302,7 @@ pycfdns==1.2.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==9.0.0 +pychromecast==9.1.1 # homeassistant.components.pocketcasts pycketcasts==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 510bb91b265299..9f84817843c32d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -685,7 +685,7 @@ pybotvac==0.0.20 pycfdns==1.2.1 # homeassistant.components.cast -pychromecast==9.0.0 +pychromecast==9.1.1 # homeassistant.components.climacell pyclimacell==0.14.0 diff --git a/tests/components/cast/conftest.py b/tests/components/cast/conftest.py new file mode 100644 index 00000000000000..875d831afa9f18 --- /dev/null +++ b/tests/components/cast/conftest.py @@ -0,0 +1,76 @@ +"""Test fixtures for the cast integration.""" +# pylint: disable=protected-access +from unittest.mock import AsyncMock, MagicMock, patch + +import pychromecast +import pytest + + +@pytest.fixture() +def dial_mock(): + """Mock pychromecast dial.""" + dial_mock = MagicMock() + dial_mock.get_device_status.return_value.uuid = "fake_uuid" + dial_mock.get_device_status.return_value.manufacturer = "fake_manufacturer" + dial_mock.get_device_status.return_value.model_name = "fake_model_name" + dial_mock.get_device_status.return_value.friendly_name = "fake_friendly_name" + dial_mock.get_multizone_status.return_value.dynamic_groups = [] + return dial_mock + + +@pytest.fixture() +def castbrowser_mock(): + """Mock pychromecast CastBrowser.""" + return MagicMock() + + +@pytest.fixture() +def castbrowser_constructor_mock(): + """Mock pychromecast CastBrowser constructor.""" + return MagicMock() + + +@pytest.fixture() +def mz_mock(): + """Mock pychromecast MultizoneManager.""" + return MagicMock() + + +@pytest.fixture() +def pycast_mock(castbrowser_mock, castbrowser_constructor_mock): + """Mock pychromecast.""" + pycast_mock = MagicMock() + pycast_mock.discovery.CastBrowser = castbrowser_constructor_mock + pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock + pycast_mock.discovery.AbstractCastListener = ( + pychromecast.discovery.AbstractCastListener + ) + return pycast_mock + + +@pytest.fixture() +def quick_play_mock(): + """Mock pychromecast quick_play.""" + return MagicMock() + + +@pytest.fixture(autouse=True) +def cast_mock(dial_mock, mz_mock, pycast_mock, quick_play_mock): + """Mock pychromecast.""" + with patch( + "homeassistant.components.cast.media_player.pychromecast", pycast_mock + ), patch( + "homeassistant.components.cast.discovery.pychromecast", pycast_mock + ), patch( + "homeassistant.components.cast.helpers.dial", dial_mock + ), patch( + "homeassistant.components.cast.media_player.MultizoneManager", + return_value=mz_mock, + ), patch( + "homeassistant.components.cast.media_player.zeroconf.async_get_instance", + AsyncMock(), + ), patch( + "homeassistant.components.cast.media_player.quick_play", + quick_play_mock, + ): + yield diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index d364256b703625..77268e7de978aa 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -1,11 +1,14 @@ """Tests for the Cast config flow.""" +from unittest.mock import ANY, patch -from unittest.mock import patch +import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components import cast from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry + async def test_creating_entry_sets_up_media_player(hass): """Test setting up Cast loads the media player.""" @@ -54,3 +57,138 @@ async def test_not_configuring_cast_not_creates_entry(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 0 + + +@pytest.mark.parametrize("source", ["import", "user", "zeroconf"]) +async def test_single_instance(hass, source): + """Test we only allow a single config flow.""" + MockConfigEntry(domain="cast").add_to_hass(hass) + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + "cast", context={"source": source} + ) + assert result["type"] == "abort" + assert result["reason"] == "single_instance_allowed" + + +async def test_user_setup(hass, mqtt_mock): + """Test we can finish a config flow.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "user"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + users = await hass.auth.async_get_users() + assert len(users) == 1 + assert result["type"] == "create_entry" + assert result["result"].data == { + "known_hosts": [], + "user_id": users[0].id, # Home Assistant cast user + } + + +async def test_user_setup_options(hass, mqtt_mock): + """Test we can finish a config flow.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "user"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"known_hosts": "192.168.0.1, , 192.168.0.2 "} + ) + + users = await hass.auth.async_get_users() + assert len(users) == 1 + assert result["type"] == "create_entry" + assert result["result"].data == { + "known_hosts": ["192.168.0.1", "192.168.0.2"], + "user_id": users[0].id, # Home Assistant cast user + } + + +async def test_zeroconf_setup(hass): + """Test we can finish a config flow through zeroconf.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "zeroconf"} + ) + assert result["type"] == "form" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + users = await hass.auth.async_get_users() + assert len(users) == 1 + assert result["type"] == "create_entry" + assert result["result"].data == { + "known_hosts": None, + "user_id": users[0].id, # Home Assistant cast user + } + + +def get_suggested(schema, key): + """Get suggested value for key in voluptuous schema.""" + for k in schema.keys(): + if k == key: + if k.description is None or "suggested_value" not in k.description: + return None + return k.description["suggested_value"] + + +async def test_option_flow(hass): + """Test config flow options.""" + config_entry = MockConfigEntry( + domain="cast", data={"known_hosts": ["192.168.0.10", "192.168.0.11"]} + ) + config_entry.add_to_hass(hass) + await hass.async_block_till_done() + + 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"] == "options" + data_schema = result["data_schema"].schema + assert get_suggested(data_schema, "known_hosts") == "192.168.0.10,192.168.0.11" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"known_hosts": "192.168.0.1, , 192.168.0.2 "}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] is None + assert config_entry.data == {"known_hosts": ["192.168.0.1", "192.168.0.2"]} + + +async def test_known_hosts(hass, castbrowser_mock, castbrowser_constructor_mock): + """Test known hosts is passed to pychromecasts.""" + result = await hass.config_entries.flow.async_init( + "cast", context={"source": "user"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"known_hosts": "192.168.0.1, 192.168.0.2"} + ) + assert result["type"] == "create_entry" + await hass.async_block_till_done() + config_entry = hass.config_entries.async_entries("cast")[0] + + assert castbrowser_mock.start_discovery.call_count == 1 + castbrowser_constructor_mock.assert_called_once_with( + ANY, ANY, ["192.168.0.1", "192.168.0.2"] + ) + castbrowser_mock.reset_mock() + castbrowser_constructor_mock.reset_mock() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"known_hosts": "192.168.0.11, 192.168.0.12"}, + ) + + await hass.async_block_till_done() + + castbrowser_mock.start_discovery.assert_not_called() + castbrowser_constructor_mock.assert_not_called() + castbrowser_mock.host_browser.update_hosts.assert_called_once_with( + ["192.168.0.11", "192.168.0.12"] + ) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 51c49484c50163..27f817b6771246 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -2,7 +2,7 @@ # pylint: disable=protected-access import json from typing import Optional -from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch +from unittest.mock import ANY, MagicMock, Mock, patch from uuid import UUID import attr @@ -35,70 +35,6 @@ from tests.common import MockConfigEntry, assert_setup_component from tests.components.media_player import common - -@pytest.fixture() -def dial_mock(): - """Mock pychromecast dial.""" - dial_mock = MagicMock() - dial_mock.get_device_status.return_value.uuid = "fake_uuid" - dial_mock.get_device_status.return_value.manufacturer = "fake_manufacturer" - dial_mock.get_device_status.return_value.model_name = "fake_model_name" - dial_mock.get_device_status.return_value.friendly_name = "fake_friendly_name" - dial_mock.get_multizone_status.return_value.dynamic_groups = [] - return dial_mock - - -@pytest.fixture() -def castbrowser_mock(): - """Mock pychromecast CastBrowser.""" - return MagicMock() - - -@pytest.fixture() -def mz_mock(): - """Mock pychromecast MultizoneManager.""" - return MagicMock() - - -@pytest.fixture() -def pycast_mock(castbrowser_mock): - """Mock pychromecast.""" - pycast_mock = MagicMock() - pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock - pycast_mock.discovery.AbstractCastListener = ( - pychromecast.discovery.AbstractCastListener - ) - return pycast_mock - - -@pytest.fixture() -def quick_play_mock(): - """Mock pychromecast quick_play.""" - return MagicMock() - - -@pytest.fixture(autouse=True) -def cast_mock(dial_mock, mz_mock, pycast_mock, quick_play_mock): - """Mock pychromecast.""" - with patch( - "homeassistant.components.cast.media_player.pychromecast", pycast_mock - ), patch( - "homeassistant.components.cast.discovery.pychromecast", pycast_mock - ), patch( - "homeassistant.components.cast.helpers.dial", dial_mock - ), patch( - "homeassistant.components.cast.media_player.MultizoneManager", - return_value=mz_mock, - ), patch( - "homeassistant.components.cast.media_player.zeroconf.async_get_instance", - AsyncMock(), - ), patch( - "homeassistant.components.cast.media_player.quick_play", - quick_play_mock, - ): - yield - - # pylint: disable=invalid-name FakeUUID = UUID("57355bce-9364-4aa6-ac1e-eb849dccf9e2") FakeUUID2 = UUID("57355bce-9364-4aa6-ac1e-eb849dccf9e4") @@ -482,7 +418,8 @@ async def test_replay_past_chromecasts(hass): assert add_dev1.call_count == 1 add_dev2 = Mock() - await cast._async_setup_platform(hass, {"host": "host2"}, add_dev2) + entry = hass.config_entries.async_entries("cast")[0] + await cast._async_setup_platform(hass, {"host": "host2"}, add_dev2, entry) await hass.async_block_till_done() assert add_dev2.call_count == 1 From 2e65a60624fef94ee42c24408a0a1bb120328279 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 17:18:47 -0600 Subject: [PATCH 0905/1818] Fix lutron caseta fan handling of speed off (#47244) --- homeassistant/components/lutron_caseta/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 57b87b18320f98..edda379aedca15 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -46,6 +46,8 @@ def percentage(self) -> str: """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None + if self._device["fan_speed"] == FAN_OFF: + return 0 return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) From c6cfcc2abb34a569eecdcc06f94bc9b272463252 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 2 Mar 2021 00:21:10 +0100 Subject: [PATCH 0906/1818] Add remote control support to philips_js (#47249) --- .../components/philips_js/__init__.py | 2 +- homeassistant/components/philips_js/remote.py | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/philips_js/remote.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index f3c2eb59789412..dd3e8d194e9bb2 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -21,7 +21,7 @@ from .const import DOMAIN -PLATFORMS = ["media_player"] +PLATFORMS = ["media_player", "remote"] LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py new file mode 100644 index 00000000000000..30b46499e2865f --- /dev/null +++ b/homeassistant/components/philips_js/remote.py @@ -0,0 +1,108 @@ +"""Remote control support for Apple TV.""" + +import asyncio + +from haphilipsjs.typing import SystemType + +from homeassistant.components.remote import ( + ATTR_DELAY_SECS, + ATTR_NUM_REPEATS, + DEFAULT_DELAY_SECS, + RemoteEntity, +) + +from . import LOGGER, PhilipsTVDataUpdateCoordinator +from .const import CONF_SYSTEM, DOMAIN + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the configuration entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + [ + PhilipsTVRemote( + coordinator, + config_entry.data[CONF_SYSTEM], + config_entry.unique_id, + ) + ] + ) + + +class PhilipsTVRemote(RemoteEntity): + """Device that sends commands.""" + + def __init__( + self, + coordinator: PhilipsTVDataUpdateCoordinator, + system: SystemType, + unique_id: str, + ): + """Initialize the Philips TV.""" + self._tv = coordinator.api + self._coordinator = coordinator + self._system = system + self._unique_id = unique_id + + @property + def name(self): + """Return the device name.""" + return self._system["name"] + + @property + def is_on(self): + """Return true if device is on.""" + if self._tv.on: + if self._tv.powerstate == "On" or self._tv.powerstate is None: + return True + return False + + @property + def should_poll(self): + """No polling needed for Apple TV.""" + return False + + @property + def unique_id(self): + """Return unique identifier if known.""" + return self._unique_id + + @property + def device_info(self): + """Return a device description for device registry.""" + return { + "name": self._system["name"], + "identifiers": { + (DOMAIN, self._unique_id), + }, + "model": self._system.get("model"), + "manufacturer": "Philips", + "sw_version": self._system.get("softwareversion"), + } + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + 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) + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + if self._tv.on: + await self._tv.sendKey("Standby") + self.async_write_ha_state() + else: + LOGGER.debug("Tv was already turned off") + + async def async_send_command(self, command, **kwargs): + """Send a command to one device.""" + num_repeats = kwargs[ATTR_NUM_REPEATS] + delay = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS) + + for _ in range(num_repeats): + for single_command in command: + LOGGER.debug("Sending command %s", single_command) + await self._tv.sendKey(single_command) + await asyncio.sleep(delay) From 3e34bb3e89c83b3ba39602713721b689d874ea3f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 18:24:55 -0500 Subject: [PATCH 0907/1818] Add suggested area for zwave_js devices (#47250) --- homeassistant/components/zwave_js/__init__.py | 19 ++++++++++-------- tests/components/zwave_js/common.py | 3 +++ tests/components/zwave_js/conftest.py | 6 ++---- tests/components/zwave_js/test_init.py | 20 ++++++++++++++++++- tests/components/zwave_js/test_light.py | 8 +++++--- .../zwave_js/eaton_rf9640_dimmer_state.json | 2 +- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 93d511875af63d..798fd9fda2c838 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -67,14 +67,17 @@ def register_node_in_dev_reg( node: ZwaveNode, ) -> None: """Register node in dev reg.""" - device = dev_reg.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={get_device_id(client, node)}, - sw_version=node.firmware_version, - name=node.name or node.device_config.description or f"Node {node.node_id}", - model=node.device_config.label, - manufacturer=node.device_config.manufacturer, - ) + params = { + "config_entry_id": entry.entry_id, + "identifiers": {get_device_id(client, node)}, + "sw_version": node.firmware_version, + "name": node.name or node.device_config.description or f"Node {node.node_id}", + "model": node.device_config.label, + "manufacturer": node.device_config.manufacturer, + } + if node.location: + params["suggested_area"] = node.location + device = dev_reg.async_get_or_create(**params) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index ebba16136a0334..a5ee628754e91d 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,3 +16,6 @@ CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" +EATON_RF9640_ENTITY = "light.allloaddimmer" +AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index e0bc588abf421d..72835fb17c11e3 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -10,9 +10,7 @@ from zwave_js_server.model.node import Node from zwave_js_server.version import VersionInfo -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers.device_registry import async_get as async_get_device_registry from tests.common import MockConfigEntry, load_fixture @@ -20,7 +18,7 @@ @pytest.fixture(name="device_registry") async def device_registry_fixture(hass): """Return the device registry.""" - return await async_get_device_registry(hass) + return async_get_device_registry(hass) @pytest.fixture(name="controller_state", scope="session") diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index bff2ecd198c56a..2a2f249c361854 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,11 @@ from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry, entity_registry -from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR +from .common import ( + AIR_TEMPERATURE_SENSOR, + EATON_RF9640_ENTITY, + NOTIFICATION_MOTION_BINARY_SENSOR, +) from tests.common import MockConfigEntry @@ -467,3 +471,17 @@ async def test_removed_device(hass, client, multiple_devices, integration): ) assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None + + +async def test_suggested_area(hass, client, eaton_rf9640_dimmer): + """Test that suggested area works.""" + dev_reg = device_registry.async_get(hass) + ent_reg = entity_registry.async_get(hass) + + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + entity = ent_reg.async_get(EATON_RF9640_ENTITY) + assert dev_reg.async_get(entity.device_id).area_id is not None diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index ca36ea35393ab8..d6d6c030d34b82 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -12,9 +12,11 @@ ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON -BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" -EATON_RF9640_ENTITY = "light.allloaddimmer" -AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" +from .common import ( + AEON_SMART_SWITCH_LIGHT_ENTITY, + BULB_6_MULTI_COLOR_LIGHT_ENTITY, + EATON_RF9640_ENTITY, +) async def test_light(hass, client, bulb_6_multi_color, integration): diff --git a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json index db815506a6be1c..b11d2bfd180898 100644 --- a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json +++ b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json @@ -27,7 +27,7 @@ "nodeType": 0, "roleType": 5, "name": "AllLoadDimmer", - "location": "", + "location": "LivingRoom", "deviceConfig": { "manufacturerId": 26, "manufacturer": "Eaton", From 036ce55bea285e73e67f546f534f23ba874a1f7f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Mar 2021 00:32:39 +0100 Subject: [PATCH 0908/1818] Update frontend to 20210301.0 (#47252) --- 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 01f1c72f8d6092..e8f9ff2698d71b 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==20210226.0" + "home-assistant-frontend==20210301.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9506171303bb57..cb211fb1962d7b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 761c47e34da93d..a59048ec4b1f1d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f84817843c32d..520325e1aeff0e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 3ee589d973d5317104e2433a6fa33df52fe91899 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 1 Mar 2021 17:08:36 -0700 Subject: [PATCH 0909/1818] Bump simplisafe-python to 9.6.8 (#47241) --- 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 de5199ccd4cda6..6122428ea98d32 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.7"], + "requirements": ["simplisafe-python==9.6.8"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index a59048ec4b1f1d..75e660f0fb0472 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2041,7 +2041,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.7 +simplisafe-python==9.6.8 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 520325e1aeff0e..2037a55bd49f76 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1048,7 +1048,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.7 +simplisafe-python==9.6.8 # homeassistant.components.slack slackclient==2.5.0 From cc6293623f603240aa05508f4a89976598b3d20d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Mar 2021 16:12:48 -0800 Subject: [PATCH 0910/1818] Revert "Fix the updater schema (#47128)" (#47254) This reverts commit 98be703d90e44efe43b1a17c7e5243e5097b00b1. --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 81910db38d6d70..9d65bb4c5d4b3f 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ CONFIG_SCHEMA = vol.Schema( { - vol.Optional(DOMAIN, default={}): { + DOMAIN: { vol.Optional(CONF_REPORTING, default=True): cv.boolean, vol.Optional(CONF_COMPONENT_REPORTING, default=False): cv.boolean, } @@ -56,13 +56,13 @@ async def async_setup(hass, config): # This component only makes sense in release versions _LOGGER.info("Running on 'dev', only analytics will be submitted") - conf = config[DOMAIN] - if conf[CONF_REPORTING]: + conf = config.get(DOMAIN, {}) + if conf.get(CONF_REPORTING): huuid = await hass.helpers.instance_id.async_get() else: huuid = None - include_components = conf[CONF_COMPONENT_REPORTING] + include_components = conf.get(CONF_COMPONENT_REPORTING) async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From 4f9f870e7de481a479caf9fea2d4308dfcad9098 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 01:27:26 +0100 Subject: [PATCH 0911/1818] Fix duplicate template handling in Persistent Notifications (#47217) --- .../persistent_notification/__init__.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 5f08f79dc00d75..589cc97baeafd9 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -11,6 +11,7 @@ from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.template import Template from homeassistant.loader import bind_hass from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -35,8 +36,8 @@ SCHEMA_SERVICE_CREATE = vol.Schema( { - vol.Required(ATTR_MESSAGE): cv.template, - vol.Optional(ATTR_TITLE): cv.template, + vol.Required(ATTR_MESSAGE): vol.Any(cv.dynamic_template, cv.string), + vol.Optional(ATTR_TITLE): vol.Any(cv.dynamic_template, cv.string), vol.Optional(ATTR_NOTIFICATION_ID): cv.string, } ) @@ -118,22 +119,24 @@ def create_service(call): attr = {} if title is not None: - try: - title.hass = hass - title = title.async_render(parse_result=False) - except TemplateError as ex: - _LOGGER.error("Error rendering title %s: %s", title, ex) - title = title.template + if isinstance(title, Template): + try: + title.hass = hass + title = title.async_render(parse_result=False) + except TemplateError as ex: + _LOGGER.error("Error rendering title %s: %s", title, ex) + title = title.template attr[ATTR_TITLE] = title attr[ATTR_FRIENDLY_NAME] = title - try: - message.hass = hass - message = message.async_render(parse_result=False) - except TemplateError as ex: - _LOGGER.error("Error rendering message %s: %s", message, ex) - message = message.template + if isinstance(message, Template): + try: + message.hass = hass + message = message.async_render(parse_result=False) + except TemplateError as ex: + _LOGGER.error("Error rendering message %s: %s", message, ex) + message = message.template attr[ATTR_MESSAGE] = message From d1afef843bcb7a08ba12d23e6ef5faeb5557687d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 01:34:14 +0100 Subject: [PATCH 0912/1818] Deprecate LIFX Legacy integration (#47235) --- homeassistant/components/lifx/light.py | 7 ------- homeassistant/components/lifx_legacy/light.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index f06a7720bb2146..9f1c5747aa8614 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -4,7 +4,6 @@ from functools import partial import logging import math -import sys import aiolifx as aiolifx_module import aiolifx_effects as aiolifx_effects_module @@ -166,12 +165,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry(hass, config_entry, async_add_entities): """Set up LIFX from a config entry.""" - if sys.platform == "win32": - _LOGGER.warning( - "The lifx platform is known to not work on Windows. " - "Consider using the lifx_legacy platform instead" - ) - # Priority 1: manual config interfaces = hass.data[LIFX_DOMAIN].get(DOMAIN) if not interfaces: diff --git a/homeassistant/components/lifx_legacy/light.py b/homeassistant/components/lifx_legacy/light.py index f0ed9105b99cab..4d50ecbecf252e 100644 --- a/homeassistant/components/lifx_legacy/light.py +++ b/homeassistant/components/lifx_legacy/light.py @@ -46,13 +46,22 @@ SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | SUPPORT_TRANSITION ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_SERVER): cv.string, vol.Optional(CONF_BROADCAST): cv.string} +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_SERVER), + cv.deprecated(CONF_BROADCAST), + PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_SERVER): cv.string, vol.Optional(CONF_BROADCAST): cv.string} + ), ) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the LIFX platform.""" + _LOGGER.warning( + "The LIFX Legacy platform is deprecated and will be removed in " + "Home Assistant Core 2021.6.0. Use the LIFX integration instead." + ) + server_addr = config.get(CONF_SERVER) broadcast_addr = config.get(CONF_BROADCAST) From cb99969845b77097c1b3f5f350ef04b54be70394 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Tue, 2 Mar 2021 01:34:39 +0100 Subject: [PATCH 0913/1818] Fix typo in plaato strings (#47245) --- homeassistant/components/plaato/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plaato/strings.json b/homeassistant/components/plaato/strings.json index 852ecc88ddec8b..85bc39a8d83fcd 100644 --- a/homeassistant/components/plaato/strings.json +++ b/homeassistant/components/plaato/strings.json @@ -23,7 +23,7 @@ } }, "error": { - "invalid_webhook_device": "You have selected a device that doesn't not support sending data to a webhook. It is only available for the Airlock", + "invalid_webhook_device": "You have selected a device that does not support sending data to a webhook. It is only available for the Airlock", "no_auth_token": "You need to add an auth token", "no_api_method": "You need to add an auth token or select webhook" }, From d02218ff307dc41ffae25bffee7d6b7fb616d63d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 18:56:42 -0600 Subject: [PATCH 0914/1818] Fix harmony failing to switch activities when a switch is in progress (#47212) Co-authored-by: Paulus Schoutsen --- homeassistant/components/harmony/data.py | 28 +++++++++++-------- .../components/harmony/subscriber.py | 15 ++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index 8c1d137bc854f5..340596ff1efb4d 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -22,17 +22,8 @@ def __init__(self, hass, address: str, name: str, unique_id: str): self._name = name self._unique_id = unique_id self._available = False - - callbacks = { - "config_updated": self._config_updated, - "connect": self._connected, - "disconnect": self._disconnected, - "new_activity_starting": self._activity_starting, - "new_activity": self._activity_started, - } - self._client = HarmonyClient( - ip_address=address, callbacks=ClientCallbackType(**callbacks) - ) + self._client = None + self._address = address @property def activities(self): @@ -105,6 +96,18 @@ def device_info(self, domain: str): async def connect(self) -> bool: """Connect to the Harmony Hub.""" _LOGGER.debug("%s: Connecting", self._name) + + callbacks = { + "config_updated": self._config_updated, + "connect": self._connected, + "disconnect": self._disconnected, + "new_activity_starting": self._activity_starting, + "new_activity": self._activity_started, + } + self._client = HarmonyClient( + ip_address=self._address, callbacks=ClientCallbackType(**callbacks) + ) + try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB", self._name) @@ -113,6 +116,7 @@ async def connect(self) -> bool: except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False + return True async def shutdown(self): @@ -159,10 +163,12 @@ async def async_start_activity(self, activity: str): ) return + await self.async_lock_start_activity() try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) + self.async_unlock_start_activity() async def async_power_off(self): """Start the PowerOff activity.""" diff --git a/homeassistant/components/harmony/subscriber.py b/homeassistant/components/harmony/subscriber.py index d3bed33d560669..b2652cc43d118d 100644 --- a/homeassistant/components/harmony/subscriber.py +++ b/homeassistant/components/harmony/subscriber.py @@ -1,5 +1,6 @@ """Mixin class for handling harmony callback subscriptions.""" +import asyncio import logging from typing import Any, Callable, NamedTuple, Optional @@ -29,6 +30,17 @@ def __init__(self, hass): super().__init__() self._hass = hass self._subscriptions = [] + self._activity_lock = asyncio.Lock() + + async def async_lock_start_activity(self): + """Acquire the lock.""" + await self._activity_lock.acquire() + + @callback + def async_unlock_start_activity(self): + """Release the lock.""" + if self._activity_lock.locked(): + self._activity_lock.release() @callback def async_subscribe(self, update_callbacks: HarmonyCallback) -> Callable: @@ -51,11 +63,13 @@ def _config_updated(self, _=None) -> None: def _connected(self, _=None) -> None: _LOGGER.debug("connected") + self.async_unlock_start_activity() self._available = True self._call_callbacks("connected") def _disconnected(self, _=None) -> None: _LOGGER.debug("disconnected") + self.async_unlock_start_activity() self._available = False self._call_callbacks("disconnected") @@ -65,6 +79,7 @@ def _activity_starting(self, activity_info: tuple) -> None: def _activity_started(self, activity_info: tuple) -> None: _LOGGER.debug("activity %s started", activity_info) + self.async_unlock_start_activity() self._call_callbacks("activity_started", activity_info) def _call_callbacks(self, callback_func_name: str, argument: tuple = None): From 7bc232880268d5f2a3facd5aaa8c53f49b0b9c1d Mon Sep 17 00:00:00 2001 From: stephan192 Date: Tue, 2 Mar 2021 02:00:42 +0100 Subject: [PATCH 0915/1818] Remove rounding from The Things Network (#47157) --- homeassistant/components/thethingsnetwork/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 2e7b7f9499b703..0afec8f751077b 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -81,8 +81,8 @@ def state(self): """Return the state of the entity.""" if self._ttn_data_storage.data is not None: try: - return round(self._state[self._value], 1) - except (KeyError, TypeError): + return self._state[self._value] + except KeyError: return None return None From 853d9ac4a9fbe67fa0a9d859441fde97a6ac1dbb Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Mar 2021 02:12:49 +0100 Subject: [PATCH 0916/1818] Update color logic for zwave_js light platform (#47110) Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- homeassistant/components/zwave_js/light.py | 159 +++++++++++++-------- tests/components/zwave_js/test_light.py | 45 +++--- 2 files changed, 122 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index 6ed0286e184465..d9c31210beab67 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -1,6 +1,6 @@ """Support for Z-Wave lights.""" import logging -from typing import Any, Callable, Optional, Tuple +from typing import Any, Callable, Dict, Optional, Tuple from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ColorComponent, CommandClass @@ -30,6 +30,17 @@ LOGGER = logging.getLogger(__name__) +MULTI_COLOR_MAP = { + ColorComponent.WARM_WHITE: "warmWhite", + ColorComponent.COLD_WHITE: "coldWhite", + ColorComponent.RED: "red", + ColorComponent.GREEN: "green", + ColorComponent.BLUE: "blue", + ColorComponent.AMBER: "amber", + ColorComponent.CYAN: "cyan", + ColorComponent.PURPLE: "purple", +} + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable @@ -149,21 +160,21 @@ async def async_turn_on(self, **kwargs: Any) -> None: # RGB/HS color hs_color = kwargs.get(ATTR_HS_COLOR) if hs_color is not None and self._supports_color: - # set white levels to 0 when setting rgb - await self._async_set_color("Warm White", 0) - await self._async_set_color("Cold White", 0) red, green, blue = color_util.color_hs_to_RGB(*hs_color) - await self._async_set_color("Red", red) - await self._async_set_color("Green", green) - await self._async_set_color("Blue", blue) + colors = { + ColorComponent.RED: red, + ColorComponent.GREEN: green, + ColorComponent.BLUE: blue, + } + if self._supports_color_temp: + # turn of white leds when setting rgb + colors[ColorComponent.WARM_WHITE] = 0 + colors[ColorComponent.COLD_WHITE] = 0 + await self._async_set_colors(colors) # Color temperature color_temp = kwargs.get(ATTR_COLOR_TEMP) if color_temp is not None and self._supports_color_temp: - # turn off rgb when setting white values - await self._async_set_color("Red", 0) - await self._async_set_color("Green", 0) - await self._async_set_color("Blue", 0) # Limit color temp to min/max values cold = max( 0, @@ -177,17 +188,28 @@ async def async_turn_on(self, **kwargs: Any) -> None: ), ) warm = 255 - cold - await self._async_set_color("Warm White", warm) - await self._async_set_color("Cold White", cold) + await self._async_set_colors( + { + # turn off color leds when setting color temperature + ColorComponent.RED: 0, + ColorComponent.GREEN: 0, + ColorComponent.BLUE: 0, + ColorComponent.WARM_WHITE: warm, + ColorComponent.COLD_WHITE: cold, + } + ) # White value white_value = kwargs.get(ATTR_WHITE_VALUE) if white_value is not None and self._supports_white_value: - # turn off rgb when setting white values - await self._async_set_color("Red", 0) - await self._async_set_color("Green", 0) - await self._async_set_color("Blue", 0) - await self._async_set_color("Warm White", white_value) + # white led brightness is controlled by white level + # rgb leds (if any) can be on at the same time + await self._async_set_colors( + { + ColorComponent.WARM_WHITE: white_value, + ColorComponent.COLD_WHITE: white_value, + } + ) # set brightness await self._async_set_brightness( @@ -198,24 +220,33 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION)) - async def _async_set_color(self, color_name: str, new_value: int) -> None: - """Set defined color to given value.""" - try: - property_key = ColorComponent[color_name.upper().replace(" ", "_")].value - except KeyError: - raise ValueError( - "Illegal color name specified, color must be one of " - f"{','.join([color.name for color in ColorComponent])}" - ) from None - cur_zwave_value = self.get_zwave_value( - "currentColor", + async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: + """Set (multiple) defined colors to given value(s).""" + # prefer the (new) combined color property + # https://github.com/zwave-js/node-zwave-js/pull/1782 + combined_color_val = self.get_zwave_value( + "targetColor", CommandClass.SWITCH_COLOR, - value_property_key=property_key.key, - value_property_key_name=property_key.name, + value_property_key=None, + value_property_key_name=None, ) - # guard for unsupported command - if cur_zwave_value is None: + if combined_color_val and isinstance(combined_color_val.value, dict): + colors_dict = {} + for color, value in colors.items(): + color_name = MULTI_COLOR_MAP[color] + colors_dict[color_name] = value + # set updated color object + await self.info.node.async_set_value(combined_color_val, colors_dict) return + + # fallback to setting the color(s) one by one if multicolor fails + # not sure this is needed at all, but just in case + for color, value in colors.items(): + await self._async_set_color(color, value) + + async def _async_set_color(self, color: ColorComponent, new_value: int) -> None: + """Set defined color to given value.""" + property_key = color.value # actually set the new color value target_zwave_value = self.get_zwave_value( "targetColor", @@ -224,6 +255,7 @@ async def _async_set_color(self, color_name: str, new_value: int) -> None: value_property_key_name=property_key.name, ) if target_zwave_value is None: + # guard for unsupported color return await self.info.node.async_set_value(target_zwave_value, new_value) @@ -231,9 +263,6 @@ async def _async_set_brightness( self, brightness: Optional[int], transition: Optional[int] = None ) -> None: """Set new brightness to light.""" - if brightness is None and self.info.primary_value.value: - # there is no point in setting default brightness when light is already on - return if brightness is None: # Level 255 means to set it to previous value. zwave_brightness = 255 @@ -282,8 +311,9 @@ async def _async_set_transition_duration( @callback def _calculate_color_values(self) -> None: """Calculate light colors.""" - - # RGB support + # NOTE: We lookup all values here (instead of relying on the multicolor one) + # to find out what colors are supported + # as this is a simple lookup by key, this not heavy red_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, @@ -302,19 +332,6 @@ def _calculate_color_values(self) -> None: value_property_key=ColorComponent.BLUE.value.key, value_property_key_name=ColorComponent.BLUE.value.name, ) - if red_val and green_val and blue_val: - self._supports_color = True - # convert to HS - if ( - red_val.value is not None - and green_val.value is not None - and blue_val.value is not None - ): - self._hs_color = color_util.color_RGB_to_hs( - red_val.value, green_val.value, blue_val.value - ) - - # White colors ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, @@ -327,23 +344,47 @@ def _calculate_color_values(self) -> None: value_property_key=ColorComponent.COLD_WHITE.value.key, value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) + # prefer the (new) combined color property + # https://github.com/zwave-js/node-zwave-js/pull/1782 + combined_color_val = self.get_zwave_value( + "currentColor", + CommandClass.SWITCH_COLOR, + value_property_key=None, + value_property_key_name=None, + ) + if combined_color_val and isinstance(combined_color_val.value, dict): + multi_color = combined_color_val.value + else: + multi_color = {} + + # RGB support + if red_val and green_val and blue_val: + # prefer values from the multicolor property + red = multi_color.get("red", red_val.value) + green = multi_color.get("green", green_val.value) + blue = multi_color.get("blue", blue_val.value) + self._supports_color = True + # convert to HS + self._hs_color = color_util.color_RGB_to_hs(red, green, blue) + + # color temperature support if ww_val and cw_val: - # Color temperature (CW + WW) Support self._supports_color_temp = True + warm_white = multi_color.get("warmWhite", ww_val.value) + cold_white = multi_color.get("coldWhite", cw_val.value) # Calculate color temps based on whites - cold_level = cw_val.value or 0 - if cold_level or ww_val.value is not None: + if cold_white or warm_white: self._color_temp = round( self._max_mireds - - ((cold_level / 255) * (self._max_mireds - self._min_mireds)) + - ((cold_white / 255) * (self._max_mireds - self._min_mireds)) ) else: self._color_temp = None + # only one white channel (warm white) = white_level support elif ww_val: - # only one white channel (warm white) self._supports_white_value = True - self._white_value = ww_val.value + self._white_value = multi_color.get("warmWhite", ww_val.value) + # only one white channel (cool white) = white_level support elif cw_val: - # only one white channel (cool white) self._supports_white_value = True - self._white_value = cw_val.value + self._white_value = multi_color.get("coldWhite", cw_val.value) diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index d6d6c030d34b82..c16e2474980029 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -139,62 +139,62 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 - warm_args = client.async_send_command_no_wait.call_args_list[0][0][ - 0 - ] # warm white 0 + assert len(client.async_send_command_no_wait.call_args_list) == 6 + warm_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 255 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" assert warm_args["valueId"]["commandClass"] == 51 assert warm_args["valueId"]["endpoint"] == 0 - assert warm_args["valueId"]["metadata"]["label"] == "Target value (Warm White)" + assert warm_args["valueId"]["metadata"]["label"] == "Target value (Red)" assert warm_args["valueId"]["property"] == "targetColor" assert warm_args["valueId"]["propertyName"] == "targetColor" - assert warm_args["value"] == 0 + assert warm_args["value"] == 255 - cold_args = client.async_send_command_no_wait.call_args_list[1][0][ - 0 - ] # cold white 0 + cold_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 76 assert cold_args["command"] == "node.set_value" assert cold_args["nodeId"] == 39 assert cold_args["valueId"]["commandClassName"] == "Color Switch" assert cold_args["valueId"]["commandClass"] == 51 assert cold_args["valueId"]["endpoint"] == 0 - assert cold_args["valueId"]["metadata"]["label"] == "Target value (Cold White)" + assert cold_args["valueId"]["metadata"]["label"] == "Target value (Green)" assert cold_args["valueId"]["property"] == "targetColor" assert cold_args["valueId"]["propertyName"] == "targetColor" - assert cold_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # red 255 + assert cold_args["value"] == 76 + red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 255 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" assert red_args["valueId"]["commandClass"] == 51 assert red_args["valueId"]["endpoint"] == 0 - assert red_args["valueId"]["metadata"]["label"] == "Target value (Red)" + assert red_args["valueId"]["metadata"]["label"] == "Target value (Blue)" assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 255 - green_args = client.async_send_command_no_wait.call_args_list[3][0][0] # green 76 + green_args = client.async_send_command_no_wait.call_args_list[3][0][ + 0 + ] # warm white 0 assert green_args["command"] == "node.set_value" assert green_args["nodeId"] == 39 assert green_args["valueId"]["commandClassName"] == "Color Switch" assert green_args["valueId"]["commandClass"] == 51 assert green_args["valueId"]["endpoint"] == 0 - assert green_args["valueId"]["metadata"]["label"] == "Target value (Green)" + assert green_args["valueId"]["metadata"]["label"] == "Target value (Warm White)" assert green_args["valueId"]["property"] == "targetColor" assert green_args["valueId"]["propertyName"] == "targetColor" - assert green_args["value"] == 76 - blue_args = client.async_send_command_no_wait.call_args_list[4][0][0] # blue 255 + assert green_args["value"] == 0 + blue_args = client.async_send_command_no_wait.call_args_list[4][0][ + 0 + ] # cold white 0 assert blue_args["command"] == "node.set_value" assert blue_args["nodeId"] == 39 assert blue_args["valueId"]["commandClassName"] == "Color Switch" assert blue_args["valueId"]["commandClass"] == 51 assert blue_args["valueId"]["endpoint"] == 0 - assert blue_args["valueId"]["metadata"]["label"] == "Target value (Blue)" + assert blue_args["valueId"]["metadata"]["label"] == "Target value (Cold White)" assert blue_args["valueId"]["property"] == "targetColor" assert blue_args["valueId"]["propertyName"] == "targetColor" - assert blue_args["value"] == 255 + assert blue_args["value"] == 0 # Test rgb color update from value updated event red_event = Event( @@ -234,7 +234,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY) assert state.state == STATE_ON assert state.attributes[ATTR_BRIGHTNESS] == 255 - assert state.attributes[ATTR_COLOR_TEMP] == 370 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) client.async_send_command_no_wait.reset_mock() @@ -247,7 +246,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 client.async_send_command_no_wait.reset_mock() @@ -259,7 +258,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 @@ -369,7 +368,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 client.async_send_command_no_wait.reset_mock() From 819738a15c5b197ad661c46db5d6d0bfbf8370ba Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Mar 2021 02:12:49 +0100 Subject: [PATCH 0917/1818] Update color logic for zwave_js light platform (#47110) Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> --- homeassistant/components/zwave_js/light.py | 159 +++++++++++++-------- tests/components/zwave_js/test_light.py | 45 +++--- 2 files changed, 122 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index 6ed0286e184465..d9c31210beab67 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -1,6 +1,6 @@ """Support for Z-Wave lights.""" import logging -from typing import Any, Callable, Optional, Tuple +from typing import Any, Callable, Dict, Optional, Tuple from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ColorComponent, CommandClass @@ -30,6 +30,17 @@ LOGGER = logging.getLogger(__name__) +MULTI_COLOR_MAP = { + ColorComponent.WARM_WHITE: "warmWhite", + ColorComponent.COLD_WHITE: "coldWhite", + ColorComponent.RED: "red", + ColorComponent.GREEN: "green", + ColorComponent.BLUE: "blue", + ColorComponent.AMBER: "amber", + ColorComponent.CYAN: "cyan", + ColorComponent.PURPLE: "purple", +} + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable @@ -149,21 +160,21 @@ async def async_turn_on(self, **kwargs: Any) -> None: # RGB/HS color hs_color = kwargs.get(ATTR_HS_COLOR) if hs_color is not None and self._supports_color: - # set white levels to 0 when setting rgb - await self._async_set_color("Warm White", 0) - await self._async_set_color("Cold White", 0) red, green, blue = color_util.color_hs_to_RGB(*hs_color) - await self._async_set_color("Red", red) - await self._async_set_color("Green", green) - await self._async_set_color("Blue", blue) + colors = { + ColorComponent.RED: red, + ColorComponent.GREEN: green, + ColorComponent.BLUE: blue, + } + if self._supports_color_temp: + # turn of white leds when setting rgb + colors[ColorComponent.WARM_WHITE] = 0 + colors[ColorComponent.COLD_WHITE] = 0 + await self._async_set_colors(colors) # Color temperature color_temp = kwargs.get(ATTR_COLOR_TEMP) if color_temp is not None and self._supports_color_temp: - # turn off rgb when setting white values - await self._async_set_color("Red", 0) - await self._async_set_color("Green", 0) - await self._async_set_color("Blue", 0) # Limit color temp to min/max values cold = max( 0, @@ -177,17 +188,28 @@ async def async_turn_on(self, **kwargs: Any) -> None: ), ) warm = 255 - cold - await self._async_set_color("Warm White", warm) - await self._async_set_color("Cold White", cold) + await self._async_set_colors( + { + # turn off color leds when setting color temperature + ColorComponent.RED: 0, + ColorComponent.GREEN: 0, + ColorComponent.BLUE: 0, + ColorComponent.WARM_WHITE: warm, + ColorComponent.COLD_WHITE: cold, + } + ) # White value white_value = kwargs.get(ATTR_WHITE_VALUE) if white_value is not None and self._supports_white_value: - # turn off rgb when setting white values - await self._async_set_color("Red", 0) - await self._async_set_color("Green", 0) - await self._async_set_color("Blue", 0) - await self._async_set_color("Warm White", white_value) + # white led brightness is controlled by white level + # rgb leds (if any) can be on at the same time + await self._async_set_colors( + { + ColorComponent.WARM_WHITE: white_value, + ColorComponent.COLD_WHITE: white_value, + } + ) # set brightness await self._async_set_brightness( @@ -198,24 +220,33 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION)) - async def _async_set_color(self, color_name: str, new_value: int) -> None: - """Set defined color to given value.""" - try: - property_key = ColorComponent[color_name.upper().replace(" ", "_")].value - except KeyError: - raise ValueError( - "Illegal color name specified, color must be one of " - f"{','.join([color.name for color in ColorComponent])}" - ) from None - cur_zwave_value = self.get_zwave_value( - "currentColor", + async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: + """Set (multiple) defined colors to given value(s).""" + # prefer the (new) combined color property + # https://github.com/zwave-js/node-zwave-js/pull/1782 + combined_color_val = self.get_zwave_value( + "targetColor", CommandClass.SWITCH_COLOR, - value_property_key=property_key.key, - value_property_key_name=property_key.name, + value_property_key=None, + value_property_key_name=None, ) - # guard for unsupported command - if cur_zwave_value is None: + if combined_color_val and isinstance(combined_color_val.value, dict): + colors_dict = {} + for color, value in colors.items(): + color_name = MULTI_COLOR_MAP[color] + colors_dict[color_name] = value + # set updated color object + await self.info.node.async_set_value(combined_color_val, colors_dict) return + + # fallback to setting the color(s) one by one if multicolor fails + # not sure this is needed at all, but just in case + for color, value in colors.items(): + await self._async_set_color(color, value) + + async def _async_set_color(self, color: ColorComponent, new_value: int) -> None: + """Set defined color to given value.""" + property_key = color.value # actually set the new color value target_zwave_value = self.get_zwave_value( "targetColor", @@ -224,6 +255,7 @@ async def _async_set_color(self, color_name: str, new_value: int) -> None: value_property_key_name=property_key.name, ) if target_zwave_value is None: + # guard for unsupported color return await self.info.node.async_set_value(target_zwave_value, new_value) @@ -231,9 +263,6 @@ async def _async_set_brightness( self, brightness: Optional[int], transition: Optional[int] = None ) -> None: """Set new brightness to light.""" - if brightness is None and self.info.primary_value.value: - # there is no point in setting default brightness when light is already on - return if brightness is None: # Level 255 means to set it to previous value. zwave_brightness = 255 @@ -282,8 +311,9 @@ async def _async_set_transition_duration( @callback def _calculate_color_values(self) -> None: """Calculate light colors.""" - - # RGB support + # NOTE: We lookup all values here (instead of relying on the multicolor one) + # to find out what colors are supported + # as this is a simple lookup by key, this not heavy red_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, @@ -302,19 +332,6 @@ def _calculate_color_values(self) -> None: value_property_key=ColorComponent.BLUE.value.key, value_property_key_name=ColorComponent.BLUE.value.name, ) - if red_val and green_val and blue_val: - self._supports_color = True - # convert to HS - if ( - red_val.value is not None - and green_val.value is not None - and blue_val.value is not None - ): - self._hs_color = color_util.color_RGB_to_hs( - red_val.value, green_val.value, blue_val.value - ) - - # White colors ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, @@ -327,23 +344,47 @@ def _calculate_color_values(self) -> None: value_property_key=ColorComponent.COLD_WHITE.value.key, value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) + # prefer the (new) combined color property + # https://github.com/zwave-js/node-zwave-js/pull/1782 + combined_color_val = self.get_zwave_value( + "currentColor", + CommandClass.SWITCH_COLOR, + value_property_key=None, + value_property_key_name=None, + ) + if combined_color_val and isinstance(combined_color_val.value, dict): + multi_color = combined_color_val.value + else: + multi_color = {} + + # RGB support + if red_val and green_val and blue_val: + # prefer values from the multicolor property + red = multi_color.get("red", red_val.value) + green = multi_color.get("green", green_val.value) + blue = multi_color.get("blue", blue_val.value) + self._supports_color = True + # convert to HS + self._hs_color = color_util.color_RGB_to_hs(red, green, blue) + + # color temperature support if ww_val and cw_val: - # Color temperature (CW + WW) Support self._supports_color_temp = True + warm_white = multi_color.get("warmWhite", ww_val.value) + cold_white = multi_color.get("coldWhite", cw_val.value) # Calculate color temps based on whites - cold_level = cw_val.value or 0 - if cold_level or ww_val.value is not None: + if cold_white or warm_white: self._color_temp = round( self._max_mireds - - ((cold_level / 255) * (self._max_mireds - self._min_mireds)) + - ((cold_white / 255) * (self._max_mireds - self._min_mireds)) ) else: self._color_temp = None + # only one white channel (warm white) = white_level support elif ww_val: - # only one white channel (warm white) self._supports_white_value = True - self._white_value = ww_val.value + self._white_value = multi_color.get("warmWhite", ww_val.value) + # only one white channel (cool white) = white_level support elif cw_val: - # only one white channel (cool white) self._supports_white_value = True - self._white_value = cw_val.value + self._white_value = multi_color.get("coldWhite", cw_val.value) diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index ca36ea35393ab8..8991776fed6a83 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -137,62 +137,62 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 - warm_args = client.async_send_command_no_wait.call_args_list[0][0][ - 0 - ] # warm white 0 + assert len(client.async_send_command_no_wait.call_args_list) == 6 + warm_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 255 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" assert warm_args["valueId"]["commandClass"] == 51 assert warm_args["valueId"]["endpoint"] == 0 - assert warm_args["valueId"]["metadata"]["label"] == "Target value (Warm White)" + assert warm_args["valueId"]["metadata"]["label"] == "Target value (Red)" assert warm_args["valueId"]["property"] == "targetColor" assert warm_args["valueId"]["propertyName"] == "targetColor" - assert warm_args["value"] == 0 + assert warm_args["value"] == 255 - cold_args = client.async_send_command_no_wait.call_args_list[1][0][ - 0 - ] # cold white 0 + cold_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 76 assert cold_args["command"] == "node.set_value" assert cold_args["nodeId"] == 39 assert cold_args["valueId"]["commandClassName"] == "Color Switch" assert cold_args["valueId"]["commandClass"] == 51 assert cold_args["valueId"]["endpoint"] == 0 - assert cold_args["valueId"]["metadata"]["label"] == "Target value (Cold White)" + assert cold_args["valueId"]["metadata"]["label"] == "Target value (Green)" assert cold_args["valueId"]["property"] == "targetColor" assert cold_args["valueId"]["propertyName"] == "targetColor" - assert cold_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # red 255 + assert cold_args["value"] == 76 + red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 255 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" assert red_args["valueId"]["commandClass"] == 51 assert red_args["valueId"]["endpoint"] == 0 - assert red_args["valueId"]["metadata"]["label"] == "Target value (Red)" + assert red_args["valueId"]["metadata"]["label"] == "Target value (Blue)" assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 255 - green_args = client.async_send_command_no_wait.call_args_list[3][0][0] # green 76 + green_args = client.async_send_command_no_wait.call_args_list[3][0][ + 0 + ] # warm white 0 assert green_args["command"] == "node.set_value" assert green_args["nodeId"] == 39 assert green_args["valueId"]["commandClassName"] == "Color Switch" assert green_args["valueId"]["commandClass"] == 51 assert green_args["valueId"]["endpoint"] == 0 - assert green_args["valueId"]["metadata"]["label"] == "Target value (Green)" + assert green_args["valueId"]["metadata"]["label"] == "Target value (Warm White)" assert green_args["valueId"]["property"] == "targetColor" assert green_args["valueId"]["propertyName"] == "targetColor" - assert green_args["value"] == 76 - blue_args = client.async_send_command_no_wait.call_args_list[4][0][0] # blue 255 + assert green_args["value"] == 0 + blue_args = client.async_send_command_no_wait.call_args_list[4][0][ + 0 + ] # cold white 0 assert blue_args["command"] == "node.set_value" assert blue_args["nodeId"] == 39 assert blue_args["valueId"]["commandClassName"] == "Color Switch" assert blue_args["valueId"]["commandClass"] == 51 assert blue_args["valueId"]["endpoint"] == 0 - assert blue_args["valueId"]["metadata"]["label"] == "Target value (Blue)" + assert blue_args["valueId"]["metadata"]["label"] == "Target value (Cold White)" assert blue_args["valueId"]["property"] == "targetColor" assert blue_args["valueId"]["propertyName"] == "targetColor" - assert blue_args["value"] == 255 + assert blue_args["value"] == 0 # Test rgb color update from value updated event red_event = Event( @@ -232,7 +232,6 @@ async def test_light(hass, client, bulb_6_multi_color, integration): state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY) assert state.state == STATE_ON assert state.attributes[ATTR_BRIGHTNESS] == 255 - assert state.attributes[ATTR_COLOR_TEMP] == 370 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) client.async_send_command_no_wait.reset_mock() @@ -245,7 +244,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 client.async_send_command_no_wait.reset_mock() @@ -257,7 +256,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 @@ -367,7 +366,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 5 + assert len(client.async_send_command_no_wait.call_args_list) == 6 client.async_send_command_no_wait.reset_mock() From ea65f612cc4efc1e41c2357dc7a4ad98f86047dc Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Mon, 1 Mar 2021 09:38:07 +0000 Subject: [PATCH 0918/1818] Fix number of reported issues by github integration (#47203) --- homeassistant/components/github/sensor.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 312e726b91d39e..80d05ae1b9c081 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -228,18 +228,25 @@ def update(self): self.stargazers = repo.stargazers_count self.forks = repo.forks_count - open_issues = repo.get_issues(state="open", sort="created") - if open_issues is not None: - self.open_issue_count = open_issues.totalCount - if open_issues.totalCount > 0: - self.latest_open_issue_url = open_issues[0].html_url - open_pull_requests = repo.get_pulls(state="open", sort="created") if open_pull_requests is not None: self.pull_request_count = open_pull_requests.totalCount if open_pull_requests.totalCount > 0: self.latest_open_pr_url = open_pull_requests[0].html_url + open_issues = repo.get_issues(state="open", sort="created") + if open_issues is not None: + if self.pull_request_count is None: + self.open_issue_count = open_issues.totalCount + else: + # pull requests are treated as issues too so we need to reduce the received count + self.open_issue_count = ( + open_issues.totalCount - self.pull_request_count + ) + + if open_issues.totalCount > 0: + self.latest_open_issue_url = open_issues[0].html_url + latest_commit = repo.get_commits()[0] self.latest_commit_sha = latest_commit.sha self.latest_commit_message = latest_commit.commit.message From b2a3c35e3a36d6372a6c4d3daf0d806339a28821 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Mar 2021 12:38:49 +0100 Subject: [PATCH 0919/1818] Fix race when disabling config entries (#47210) * Fix race when disabling config entries * Remove unused constant --- homeassistant/config_entries.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index dbc0dd01454364..b54300faaa77c7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -69,8 +69,6 @@ ENTRY_STATE_NOT_LOADED = "not_loaded" # An error occurred when trying to unload the entry ENTRY_STATE_FAILED_UNLOAD = "failed_unload" -# The config entry is disabled -ENTRY_STATE_DISABLED = "disabled" UNRECOVERABLE_STATES = (ENTRY_STATE_MIGRATION_ERROR, ENTRY_STATE_FAILED_UNLOAD) @@ -802,11 +800,14 @@ async def async_set_disabled_by( entry.disabled_by = disabled_by self._async_schedule_save() + # Unload the config entry, then fire an event + reload_result = await self.async_reload(entry_id) + self.hass.bus.async_fire( EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} ) - return await self.async_reload(entry_id) + return reload_result @callback def async_update_entry( From c28903103d97e07b9772d2c31d629d3f85770a9b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 18:56:42 -0600 Subject: [PATCH 0920/1818] Fix harmony failing to switch activities when a switch is in progress (#47212) Co-authored-by: Paulus Schoutsen --- homeassistant/components/harmony/data.py | 28 +++++++++++-------- .../components/harmony/subscriber.py | 15 ++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index 8c1d137bc854f5..340596ff1efb4d 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -22,17 +22,8 @@ def __init__(self, hass, address: str, name: str, unique_id: str): self._name = name self._unique_id = unique_id self._available = False - - callbacks = { - "config_updated": self._config_updated, - "connect": self._connected, - "disconnect": self._disconnected, - "new_activity_starting": self._activity_starting, - "new_activity": self._activity_started, - } - self._client = HarmonyClient( - ip_address=address, callbacks=ClientCallbackType(**callbacks) - ) + self._client = None + self._address = address @property def activities(self): @@ -105,6 +96,18 @@ def device_info(self, domain: str): async def connect(self) -> bool: """Connect to the Harmony Hub.""" _LOGGER.debug("%s: Connecting", self._name) + + callbacks = { + "config_updated": self._config_updated, + "connect": self._connected, + "disconnect": self._disconnected, + "new_activity_starting": self._activity_starting, + "new_activity": self._activity_started, + } + self._client = HarmonyClient( + ip_address=self._address, callbacks=ClientCallbackType(**callbacks) + ) + try: if not await self._client.connect(): _LOGGER.warning("%s: Unable to connect to HUB", self._name) @@ -113,6 +116,7 @@ async def connect(self) -> bool: except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False + return True async def shutdown(self): @@ -159,10 +163,12 @@ async def async_start_activity(self, activity: str): ) return + await self.async_lock_start_activity() try: await self._client.start_activity(activity_id) except aioexc.TimeOut: _LOGGER.error("%s: Starting activity %s timed-out", self.name, activity) + self.async_unlock_start_activity() async def async_power_off(self): """Start the PowerOff activity.""" diff --git a/homeassistant/components/harmony/subscriber.py b/homeassistant/components/harmony/subscriber.py index d3bed33d560669..b2652cc43d118d 100644 --- a/homeassistant/components/harmony/subscriber.py +++ b/homeassistant/components/harmony/subscriber.py @@ -1,5 +1,6 @@ """Mixin class for handling harmony callback subscriptions.""" +import asyncio import logging from typing import Any, Callable, NamedTuple, Optional @@ -29,6 +30,17 @@ def __init__(self, hass): super().__init__() self._hass = hass self._subscriptions = [] + self._activity_lock = asyncio.Lock() + + async def async_lock_start_activity(self): + """Acquire the lock.""" + await self._activity_lock.acquire() + + @callback + def async_unlock_start_activity(self): + """Release the lock.""" + if self._activity_lock.locked(): + self._activity_lock.release() @callback def async_subscribe(self, update_callbacks: HarmonyCallback) -> Callable: @@ -51,11 +63,13 @@ def _config_updated(self, _=None) -> None: def _connected(self, _=None) -> None: _LOGGER.debug("connected") + self.async_unlock_start_activity() self._available = True self._call_callbacks("connected") def _disconnected(self, _=None) -> None: _LOGGER.debug("disconnected") + self.async_unlock_start_activity() self._available = False self._call_callbacks("disconnected") @@ -65,6 +79,7 @@ def _activity_starting(self, activity_info: tuple) -> None: def _activity_started(self, activity_info: tuple) -> None: _LOGGER.debug("activity %s started", activity_info) + self.async_unlock_start_activity() self._call_callbacks("activity_started", activity_info) def _call_callbacks(self, callback_func_name: str, argument: tuple = None): From acdad8a28cf0e04326f852ebc6126c6c59b67d70 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 01:27:26 +0100 Subject: [PATCH 0921/1818] Fix duplicate template handling in Persistent Notifications (#47217) --- .../persistent_notification/__init__.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 5f08f79dc00d75..589cc97baeafd9 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -11,6 +11,7 @@ from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id +from homeassistant.helpers.template import Template from homeassistant.loader import bind_hass from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -35,8 +36,8 @@ SCHEMA_SERVICE_CREATE = vol.Schema( { - vol.Required(ATTR_MESSAGE): cv.template, - vol.Optional(ATTR_TITLE): cv.template, + vol.Required(ATTR_MESSAGE): vol.Any(cv.dynamic_template, cv.string), + vol.Optional(ATTR_TITLE): vol.Any(cv.dynamic_template, cv.string), vol.Optional(ATTR_NOTIFICATION_ID): cv.string, } ) @@ -118,22 +119,24 @@ def create_service(call): attr = {} if title is not None: - try: - title.hass = hass - title = title.async_render(parse_result=False) - except TemplateError as ex: - _LOGGER.error("Error rendering title %s: %s", title, ex) - title = title.template + if isinstance(title, Template): + try: + title.hass = hass + title = title.async_render(parse_result=False) + except TemplateError as ex: + _LOGGER.error("Error rendering title %s: %s", title, ex) + title = title.template attr[ATTR_TITLE] = title attr[ATTR_FRIENDLY_NAME] = title - try: - message.hass = hass - message = message.async_render(parse_result=False) - except TemplateError as ex: - _LOGGER.error("Error rendering message %s: %s", message, ex) - message = message.template + if isinstance(message, Template): + try: + message.hass = hass + message = message.async_render(parse_result=False) + except TemplateError as ex: + _LOGGER.error("Error rendering message %s: %s", message, ex) + message = message.template attr[ATTR_MESSAGE] = message From 30ccd33e7f483ab6f8b878f129eacfde63d39bb3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 1 Mar 2021 12:46:02 +0100 Subject: [PATCH 0922/1818] Fix Xiaomi Miio flow unique_id for non discovery flows (#47222) --- homeassistant/components/xiaomi_miio/config_flow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 2e069b30da3cdd..d6ee83e9842ddd 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -125,7 +125,9 @@ async def async_step_device(self, user_input=None): for gateway_model in MODELS_GATEWAY: if model.startswith(gateway_model): unique_id = self.mac - await self.async_set_unique_id(unique_id) + await self.async_set_unique_id( + unique_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=DEFAULT_GATEWAY_NAME, @@ -144,7 +146,9 @@ async def async_step_device(self, user_input=None): for device_model in MODELS_ALL_DEVICES: if model.startswith(device_model): unique_id = self.mac - await self.async_set_unique_id(unique_id) + await self.async_set_unique_id( + unique_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=name, From c411f0dcdc45af31c9fe4c4e3d23ac7c11203b53 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 1 Mar 2021 18:27:43 +0200 Subject: [PATCH 0923/1818] Fix Shelly Polling (#47224) --- homeassistant/components/shelly/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index d4423dc3a88968..ccb5212752546e 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -108,6 +108,7 @@ def _async_device_online(_): "Setup for device %s will resume when device is online", entry.title ) device.subscribe_updates(_async_device_online) + await device.coap_request("s") else: # Restore sensors for sleeping device _LOGGER.debug("Setting up offline device %s", entry.title) From 118c996a9fca6c1dd744f21b87dee762e2adc4ec Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Mar 2021 23:34:26 +0100 Subject: [PATCH 0924/1818] Pass variables to initial evaluation of template trigger (#47236) * Pass variables to initial evaluation of template trigger * Add test * Clarify test --- homeassistant/components/template/trigger.py | 4 ++- tests/components/template/test_trigger.py | 38 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 9e6ee086c734c5..1f378c593353ea 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -41,7 +41,9 @@ async def async_attach_trigger( # Arm at setup if the template is already false. try: - if not result_as_boolean(value_template.async_render()): + if not result_as_boolean( + value_template.async_render(automation_info["variables"]) + ): armed = True except exceptions.TemplateError as ex: _LOGGER.warning( diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 3ba79e85bf2e31..55311005201bf3 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -135,6 +135,44 @@ async def test_if_not_fires_when_true_at_setup(hass, calls): assert len(calls) == 0 +async def test_if_not_fires_when_true_at_setup_variables(hass, calls): + """Test for not firing during startup + trigger_variables.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"entity": "test.entity"}, + "trigger": { + "platform": "template", + "value_template": '{{ is_state(entity|default("test.entity2"), "hello") }}', + }, + "action": {"service": "test.automation"}, + } + }, + ) + + assert len(calls) == 0 + + # Assert that the trigger doesn't fire immediately when it's setup + # If trigger_variable 'entity' is not passed to initial check at setup, the + # trigger will immediately fire + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + hass.states.async_set("test.entity", "goodbye", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 0 + + # Assert that the trigger fires after state change + # If trigger_variable 'entity' is not passed to the template trigger, the + # trigger will never fire because it falls back to 'test.entity2' + hass.states.async_set("test.entity", "hello", force_update=True) + await hass.async_block_till_done() + assert len(calls) == 1 + + async def test_if_not_fires_because_fail(hass, calls): """Test for not firing after TemplateError.""" hass.states.async_set("test.number", "1") From 8cf0fcc7f3ba25f810267a1a9f5e684ab0aedb7f Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 1 Mar 2021 17:08:36 -0700 Subject: [PATCH 0925/1818] Bump simplisafe-python to 9.6.8 (#47241) --- 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 de5199ccd4cda6..6122428ea98d32 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.7"], + "requirements": ["simplisafe-python==9.6.8"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 48d2374c034eaf..5ab088e578875b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2044,7 +2044,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.7 +simplisafe-python==9.6.8 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 09eacd9b78a5ab..fbc39974f4076c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1051,7 +1051,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.7 +simplisafe-python==9.6.8 # homeassistant.components.slack slackclient==2.5.0 From 3ebe31e172d4090dd83d9c4322b71b62a9f9c214 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Mar 2021 17:18:47 -0600 Subject: [PATCH 0926/1818] Fix lutron caseta fan handling of speed off (#47244) --- homeassistant/components/lutron_caseta/fan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 57b87b18320f98..edda379aedca15 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -46,6 +46,8 @@ def percentage(self) -> str: """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None + if self._device["fan_speed"] == FAN_OFF: + return 0 return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device["fan_speed"] ) From 88d29bcf2016eb39b2bf2ab31981cf45c05f3840 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 1 Mar 2021 18:24:55 -0500 Subject: [PATCH 0927/1818] Add suggested area for zwave_js devices (#47250) --- homeassistant/components/zwave_js/__init__.py | 19 ++++++++++-------- tests/components/zwave_js/common.py | 3 +++ tests/components/zwave_js/conftest.py | 6 ++---- tests/components/zwave_js/test_init.py | 20 ++++++++++++++++++- tests/components/zwave_js/test_light.py | 8 +++++--- .../zwave_js/eaton_rf9640_dimmer_state.json | 2 +- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 93d511875af63d..798fd9fda2c838 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -67,14 +67,17 @@ def register_node_in_dev_reg( node: ZwaveNode, ) -> None: """Register node in dev reg.""" - device = dev_reg.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={get_device_id(client, node)}, - sw_version=node.firmware_version, - name=node.name or node.device_config.description or f"Node {node.node_id}", - model=node.device_config.label, - manufacturer=node.device_config.manufacturer, - ) + params = { + "config_entry_id": entry.entry_id, + "identifiers": {get_device_id(client, node)}, + "sw_version": node.firmware_version, + "name": node.name or node.device_config.description or f"Node {node.node_id}", + "model": node.device_config.label, + "manufacturer": node.device_config.manufacturer, + } + if node.location: + params["suggested_area"] = node.location + device = dev_reg.async_get_or_create(**params) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index ebba16136a0334..a5ee628754e91d 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,3 +16,6 @@ CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" +EATON_RF9640_ENTITY = "light.allloaddimmer" +AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index e0bc588abf421d..72835fb17c11e3 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -10,9 +10,7 @@ from zwave_js_server.model.node import Node from zwave_js_server.version import VersionInfo -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers.device_registry import async_get as async_get_device_registry from tests.common import MockConfigEntry, load_fixture @@ -20,7 +18,7 @@ @pytest.fixture(name="device_registry") async def device_registry_fixture(hass): """Return the device registry.""" - return await async_get_device_registry(hass) + return async_get_device_registry(hass) @pytest.fixture(name="controller_state", scope="session") diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index bff2ecd198c56a..2a2f249c361854 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,11 @@ from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import device_registry, entity_registry -from .common import AIR_TEMPERATURE_SENSOR, NOTIFICATION_MOTION_BINARY_SENSOR +from .common import ( + AIR_TEMPERATURE_SENSOR, + EATON_RF9640_ENTITY, + NOTIFICATION_MOTION_BINARY_SENSOR, +) from tests.common import MockConfigEntry @@ -467,3 +471,17 @@ async def test_removed_device(hass, client, multiple_devices, integration): ) assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None + + +async def test_suggested_area(hass, client, eaton_rf9640_dimmer): + """Test that suggested area works.""" + dev_reg = device_registry.async_get(hass) + ent_reg = entity_registry.async_get(hass) + + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + entity = ent_reg.async_get(EATON_RF9640_ENTITY) + assert dev_reg.async_get(entity.device_id).area_id is not None diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index 8991776fed6a83..c16e2474980029 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -12,9 +12,11 @@ ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON -BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" -EATON_RF9640_ENTITY = "light.allloaddimmer" -AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" +from .common import ( + AEON_SMART_SWITCH_LIGHT_ENTITY, + BULB_6_MULTI_COLOR_LIGHT_ENTITY, + EATON_RF9640_ENTITY, +) async def test_light(hass, client, bulb_6_multi_color, integration): diff --git a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json index db815506a6be1c..b11d2bfd180898 100644 --- a/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json +++ b/tests/fixtures/zwave_js/eaton_rf9640_dimmer_state.json @@ -27,7 +27,7 @@ "nodeType": 0, "roleType": 5, "name": "AllLoadDimmer", - "location": "", + "location": "LivingRoom", "deviceConfig": { "manufacturerId": 26, "manufacturer": "Eaton", From bd29d82728ed3c0e9e1c1b58bc7d47e422fc8351 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Mar 2021 00:32:39 +0100 Subject: [PATCH 0928/1818] Update frontend to 20210301.0 (#47252) --- 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 01f1c72f8d6092..e8f9ff2698d71b 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==20210226.0" + "home-assistant-frontend==20210301.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9506171303bb57..cb211fb1962d7b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 5ab088e578875b..e27b43f94a8713 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fbc39974f4076c..400cc372b2b00b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210226.0 +home-assistant-frontend==20210301.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 3117e47e1be6742c2844dad1aee9b7e0b5e3aa65 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Mar 2021 16:12:48 -0800 Subject: [PATCH 0929/1818] Revert "Fix the updater schema (#47128)" (#47254) This reverts commit 98be703d90e44efe43b1a17c7e5243e5097b00b1. --- homeassistant/components/updater/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 81910db38d6d70..9d65bb4c5d4b3f 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ CONFIG_SCHEMA = vol.Schema( { - vol.Optional(DOMAIN, default={}): { + DOMAIN: { vol.Optional(CONF_REPORTING, default=True): cv.boolean, vol.Optional(CONF_COMPONENT_REPORTING, default=False): cv.boolean, } @@ -56,13 +56,13 @@ async def async_setup(hass, config): # This component only makes sense in release versions _LOGGER.info("Running on 'dev', only analytics will be submitted") - conf = config[DOMAIN] - if conf[CONF_REPORTING]: + conf = config.get(DOMAIN, {}) + if conf.get(CONF_REPORTING): huuid = await hass.helpers.instance_id.async_get() else: huuid = None - include_components = conf[CONF_COMPONENT_REPORTING] + include_components = conf.get(CONF_COMPONENT_REPORTING) async def check_new_version() -> Updater: """Check if a new version is available and report if one is.""" From ec954746040e2b12e7b45bc346868401f25cf0f6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 01:17:40 +0000 Subject: [PATCH 0930/1818] Bumped version to 2021.3.0b6 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 86abfa635f0e1e..2edbfa33a10f25 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0b6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 7e71050669dd1809d0206cfba8f89408b0724289 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 04:34:37 +0100 Subject: [PATCH 0931/1818] Add battery sensor for gogogate2 wireless door sensor (#47145) Co-authored-by: J. Nick Koston --- .../components/gogogate2/__init__.py | 25 +- homeassistant/components/gogogate2/common.py | 48 +++- homeassistant/components/gogogate2/cover.py | 41 +-- homeassistant/components/gogogate2/sensor.py | 59 ++++ tests/components/gogogate2/test_sensor.py | 261 ++++++++++++++++++ 5 files changed, 388 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/gogogate2/sensor.py create mode 100644 tests/components/gogogate2/test_sensor.py diff --git a/homeassistant/components/gogogate2/__init__.py b/homeassistant/components/gogogate2/__init__.py index 93f000e6a3ab0a..6d0318b99ad627 100644 --- a/homeassistant/components/gogogate2/__init__.py +++ b/homeassistant/components/gogogate2/__init__.py @@ -1,5 +1,8 @@ """The gogogate2 component.""" -from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +import asyncio + +from homeassistant.components.cover import DOMAIN as COVER +from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE from homeassistant.core import HomeAssistant @@ -8,6 +11,8 @@ from .common import get_data_update_coordinator from .const import DEVICE_TYPE_GOGOGATE2 +PLATFORMS = [COVER, SENSOR] + async def async_setup(hass: HomeAssistant, base_config: dict) -> bool: """Set up for Gogogate2 controllers.""" @@ -34,17 +39,23 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not data_update_coordinator.last_update_success: raise ConfigEntryNotReady() - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, COVER_DOMAIN) - ) + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) return True async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload Gogogate2 config entry.""" - hass.async_create_task( - hass.config_entries.async_forward_entry_unload(config_entry, COVER_DOMAIN) + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS + ] + ) ) - return True + return unload_ok diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 2817c351013819..761f92119218f0 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -4,7 +4,7 @@ from typing import Awaitable, Callable, NamedTuple, Optional from gogogate2_api import AbstractGateApi, GogoGate2Api, ISmartGateApi -from gogogate2_api.common import AbstractDoor +from gogogate2_api.common import AbstractDoor, get_door_by_id from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -15,9 +15,13 @@ ) from homeassistant.core import HomeAssistant from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) -from .const import DATA_UPDATE_COORDINATOR, DEVICE_TYPE_ISMARTGATE, DOMAIN +from .const import DATA_UPDATE_COORDINATOR, DEVICE_TYPE_ISMARTGATE, DOMAIN, MANUFACTURER _LOGGER = logging.getLogger(__name__) @@ -57,6 +61,44 @@ def __init__( self.api = api +class GoGoGate2Entity(CoordinatorEntity): + """Base class for gogogate2 entities.""" + + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: DeviceDataUpdateCoordinator, + door: AbstractDoor, + ) -> None: + """Initialize gogogate2 base entity.""" + super().__init__(data_update_coordinator) + self._config_entry = config_entry + self._door = door + self._unique_id = cover_unique_id(config_entry, door) + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return self._unique_id + + def _get_door(self) -> AbstractDoor: + door = get_door_by_id(self._door.door_id, self.coordinator.data) + self._door = door or self._door + return self._door + + @property + def device_info(self): + """Device info for the controller.""" + data = self.coordinator.data + return { + "identifiers": {(DOMAIN, self._config_entry.unique_id)}, + "name": self._config_entry.title, + "manufacturer": MANUFACTURER, + "model": data.model, + "sw_version": data.firmwareversion, + } + + def get_data_update_coordinator( hass: HomeAssistant, config_entry: ConfigEntry ) -> DeviceDataUpdateCoordinator: diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 8b83073d0c8b49..f2e05b10599c7d 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,12 +1,7 @@ """Support for Gogogate2 garage Doors.""" from typing import Callable, List, Optional -from gogogate2_api.common import ( - AbstractDoor, - DoorStatus, - get_configured_doors, - get_door_by_id, -) +from gogogate2_api.common import AbstractDoor, DoorStatus, get_configured_doors import voluptuous as vol from homeassistant.components.cover import ( @@ -26,14 +21,13 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .common import ( DeviceDataUpdateCoordinator, - cover_unique_id, + GoGoGate2Entity, get_data_update_coordinator, ) -from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN, MANUFACTURER +from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN COVER_SCHEMA = vol.Schema( { @@ -74,7 +68,7 @@ async def async_setup_entry( ) -class DeviceCover(CoordinatorEntity, CoverEntity): +class DeviceCover(GoGoGate2Entity, CoverEntity): """Cover entity for goggate2.""" def __init__( @@ -84,18 +78,10 @@ def __init__( door: AbstractDoor, ) -> None: """Initialize the object.""" - super().__init__(data_update_coordinator) - self._config_entry = config_entry - self._door = door + super().__init__(config_entry, data_update_coordinator, door) self._api = data_update_coordinator.api - self._unique_id = cover_unique_id(config_entry, door) self._is_available = True - @property - def unique_id(self) -> Optional[str]: - """Return a unique ID.""" - return self._unique_id - @property def name(self): """Return the name of the door.""" @@ -141,20 +127,3 @@ def state_attributes(self): attrs = super().state_attributes attrs["door_id"] = self._get_door().door_id return attrs - - def _get_door(self) -> AbstractDoor: - door = get_door_by_id(self._door.door_id, self.coordinator.data) - self._door = door or self._door - return self._door - - @property - def device_info(self): - """Device info for the controller.""" - data = self.coordinator.data - return { - "identifiers": {(DOMAIN, self._config_entry.unique_id)}, - "name": self._config_entry.title, - "manufacturer": MANUFACTURER, - "model": data.model, - "sw_version": data.firmwareversion, - } diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py new file mode 100644 index 00000000000000..55aacd2fdbb6c5 --- /dev/null +++ b/homeassistant/components/gogogate2/sensor.py @@ -0,0 +1,59 @@ +"""Support for Gogogate2 garage Doors.""" +from typing import Callable, List, Optional + +from gogogate2_api.common import get_configured_doors + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from .common import GoGoGate2Entity, get_data_update_coordinator + +SENSOR_ID_WIRED = "WIRE" + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the config entry.""" + data_update_coordinator = get_data_update_coordinator(hass, config_entry) + + async_add_entities( + [ + DoorSensor(config_entry, data_update_coordinator, door) + for door in get_configured_doors(data_update_coordinator.data) + if door.sensorid and door.sensorid != SENSOR_ID_WIRED + ] + ) + + +class DoorSensor(GoGoGate2Entity): + """Sensor entity for goggate2.""" + + @property + def name(self): + """Return the name of the door.""" + return f"{self._get_door().name} battery" + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_BATTERY + + @property + def state(self): + """Return the state of the entity.""" + door = self._get_door() + return door.voltage # This is a percentage, not an absolute voltage + + @property + def state_attributes(self): + """Return the state attributes.""" + attrs = super().state_attributes or {} + door = self._get_door() + if door.sensorid is not None: + attrs["sensorid"] = door.door_id + return attrs diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py new file mode 100644 index 00000000000000..0bd67dfc92a046 --- /dev/null +++ b/tests/components/gogogate2/test_sensor.py @@ -0,0 +1,261 @@ +"""Tests for the GogoGate2 component.""" +from datetime import timedelta +from unittest.mock import MagicMock, patch + +from gogogate2_api import GogoGate2Api, ISmartGateApi +from gogogate2_api.common import ( + DoorMode, + DoorStatus, + GogoGate2ActivateResponse, + GogoGate2Door, + GogoGate2InfoResponse, + ISmartGateDoor, + ISmartGateInfoResponse, + Network, + Outputs, + Wifi, +) + +from homeassistant.components.gogogate2.const import DEVICE_TYPE_ISMARTGATE, DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + CONF_DEVICE, + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_USERNAME, + DEVICE_CLASS_BATTERY, + STATE_UNAVAILABLE, + STATE_UNKNOWN, +) +from homeassistant.core import HomeAssistant +from homeassistant.util.dt import utcnow + +from tests.common import MockConfigEntry, async_fire_time_changed + + +def _mocked_gogogate_sensor_response(battery_level: int): + return GogoGate2InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=GogoGate2Door( + door_id=1, + permission=True, + name="Door1", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid="ABCD", + camera=False, + events=2, + temperature=None, + voltage=battery_level, + ), + door2=GogoGate2Door( + door_id=2, + permission=True, + name="Door2", + gate=True, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid="WIRE", + camera=False, + events=0, + temperature=None, + voltage=battery_level, + ), + door3=GogoGate2Door( + door_id=3, + permission=True, + name="Door3", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + voltage=battery_level, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + +def _mocked_ismartgate_sensor_response(battery_level: int): + return ISmartGateInfoResponse( + user="user1", + ismartgatename="ismartgatename0", + model="ismartgatePRO", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc321.blah.blah", + firmwareversion="555", + pin=123, + lang="en", + newfirmware=False, + door1=ISmartGateDoor( + door_id=1, + permission=True, + name="Door1", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid="ABCD", + camera=False, + events=2, + temperature=None, + enabled=True, + apicode="apicode0", + customimage=False, + voltage=battery_level, + ), + door2=ISmartGateDoor( + door_id=2, + permission=True, + name="Door2", + gate=True, + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid="WIRE", + camera=False, + events=2, + temperature=None, + enabled=True, + apicode="apicode0", + customimage=False, + voltage=battery_level, + ), + door3=ISmartGateDoor( + door_id=3, + permission=True, + name="Door3", + gate=False, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + enabled=True, + apicode="apicode0", + customimage=False, + voltage=battery_level, + ), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + +@patch("homeassistant.components.gogogate2.common.GogoGate2Api") +async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: + """Test data update.""" + + api = MagicMock(GogoGate2Api) + api.async_activate.return_value = GogoGate2ActivateResponse(result=True) + api.async_info.return_value = _mocked_gogogate_sensor_response(25) + gogogate2api_mock.return_value = api + + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data={ + CONF_IP_ADDRESS: "127.0.0.1", + CONF_USERNAME: "admin", + CONF_PASSWORD: "password", + }, + ) + config_entry.add_to_hass(hass) + + assert hass.states.get("cover.door1") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("sensor.door1_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.get("cover.door1") + assert hass.states.get("cover.door2") + assert hass.states.get("cover.door2") + assert hass.states.get("sensor.door1_battery").state == "25" + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + + api.async_info.return_value = _mocked_gogogate_sensor_response(40) + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == "40" + + api.async_info.return_value = _mocked_gogogate_sensor_response(None) + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == STATE_UNKNOWN + + assert await hass.config_entries.async_unload(config_entry.entry_id) + assert not hass.states.async_entity_ids(DOMAIN) + + +@patch("homeassistant.components.gogogate2.common.ISmartGateApi") +async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: + """Test availability.""" + sensor_response = _mocked_ismartgate_sensor_response(35) + api = MagicMock(ISmartGateApi) + api.async_info.return_value = sensor_response + ismartgateapi_mock.return_value = api + + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data={ + CONF_DEVICE: DEVICE_TYPE_ISMARTGATE, + CONF_IP_ADDRESS: "127.0.0.1", + CONF_USERNAME: "admin", + CONF_PASSWORD: "password", + }, + ) + config_entry.add_to_hass(hass) + + assert hass.states.get("cover.door1") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door2") is None + assert hass.states.get("sensor.door1_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.get("cover.door1") + assert hass.states.get("cover.door2") + assert hass.states.get("cover.door2") + assert hass.states.get("sensor.door1_battery").state == "35" + assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door2_battery") is None + assert ( + hass.states.get("sensor.door1_battery").attributes[ATTR_DEVICE_CLASS] + == DEVICE_CLASS_BATTERY + ) + + api.async_info.side_effect = Exception("Error") + + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == STATE_UNAVAILABLE + + api.async_info.side_effect = None + api.async_info.return_value = sensor_response + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + assert hass.states.get("sensor.door1_battery").state == "35" From 38a2f196b8f28d7c9e9921b74f47c223d34d2bca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Mar 2021 01:32:24 -0600 Subject: [PATCH 0932/1818] Fix typing on fan percentage (#47259) --- homeassistant/components/comfoconnect/fan.py | 3 ++- homeassistant/components/demo/fan.py | 4 ++-- homeassistant/components/esphome/fan.py | 2 +- homeassistant/components/insteon/fan.py | 2 +- homeassistant/components/isy994/fan.py | 6 +++--- homeassistant/components/lutron_caseta/fan.py | 3 ++- homeassistant/components/smartthings/fan.py | 2 +- homeassistant/components/wemo/fan.py | 2 +- homeassistant/components/wilight/fan.py | 4 +++- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index 26abd85522a7b9..d248bf74ac4938 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,6 +1,7 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" import logging import math +from typing import Optional from pycomfoconnect import ( CMD_FAN_MODE_AWAY, @@ -95,7 +96,7 @@ def supported_features(self) -> int: return SUPPORT_SET_SPEED @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" speed = self._ccb.data.get(SENSOR_FAN_SPEED_MODE) if speed is None: diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index c79b53c0918e09..77d6f39a794247 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -211,7 +211,7 @@ class DemoPercentageFan(BaseDemoFan, FanEntity): """A demonstration fan component that uses percentages.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed.""" return self._percentage @@ -271,7 +271,7 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): """An async demonstration fan component that uses percentages.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed.""" return self._percentage diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index df23f37cb63eb9..8cf28d6b2aa231 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -111,7 +111,7 @@ def is_on(self) -> Optional[bool]: return self._state.state @esphome_state_property - def percentage(self) -> Optional[str]: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if not self._static_info.supports_speed: return None diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index a641d35345025c..cef19f57a9f96e 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -41,7 +41,7 @@ class InsteonFanEntity(InsteonEntity, FanEntity): """An INSTEON fan entity.""" @property - def percentage(self) -> str: + def percentage(self) -> int: """Return the current speed percentage.""" if self._insteon_device_group.value is None: return None diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index f565383f0078a4..43323cc5546d7b 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -1,6 +1,6 @@ """Support for ISY994 fans.""" import math -from typing import Callable +from typing import Callable, Optional from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON @@ -43,7 +43,7 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): """Representation of an ISY994 fan device.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None @@ -97,7 +97,7 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity): """Representation of an ISY994 fan program.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index edda379aedca15..55799315ba02ae 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -1,5 +1,6 @@ """Support for Lutron Caseta fans.""" import logging +from typing import Optional from pylutron_caseta import FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_OFF @@ -42,7 +43,7 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Representation of a Lutron Caseta fan. Including Fan Speed.""" @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 4cd451e2416b9a..ec133a1f6aa93b 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -76,7 +76,7 @@ def is_on(self) -> bool: return self._device.status.switch @property - def percentage(self) -> str: + def percentage(self) -> int: """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed) diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 1f45194659d499..d8f54057557f75 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -127,7 +127,7 @@ def device_state_attributes(self): } @property - def percentage(self) -> str: + def percentage(self) -> int: """Return the current speed percentage.""" return ranged_value_to_percentage(SPEED_RANGE, self._fan_mode) diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index d663dc39dedb28..35727b19927d89 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -1,5 +1,7 @@ """Support for WiLight Fan.""" +from typing import Optional + from pywilight.const import ( FAN_V1, ITEM_FAN, @@ -77,7 +79,7 @@ def is_on(self): return self._status.get("direction", WL_DIRECTION_OFF) != WL_DIRECTION_OFF @property - def percentage(self) -> str: + def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if "direction" in self._status: if self._status["direction"] == WL_DIRECTION_OFF: From dc880118a40f91be2172e6b07d2597b701555e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 2 Mar 2021 10:02:04 +0200 Subject: [PATCH 0933/1818] Lint suppression cleanups (#47248) * Unused pylint suppression cleanups * Remove outdated pylint bug references * Add flake8-noqa config and note to run it every now and then * Add codes to noqa's * Unused noqa cleanups --- .pre-commit-config.yaml | 3 +++ homeassistant/auth/permissions/models.py | 3 +-- homeassistant/components/automation/__init__.py | 2 +- homeassistant/components/avion/light.py | 2 -- homeassistant/components/awair/config_flow.py | 2 +- homeassistant/components/bbb_gpio/__init__.py | 1 - homeassistant/components/bh1750/sensor.py | 2 +- homeassistant/components/blinkt/light.py | 1 - homeassistant/components/blueprint/__init__.py | 8 ++++---- .../bluetooth_le_tracker/device_tracker.py | 2 +- .../components/bluetooth_tracker/device_tracker.py | 3 +-- homeassistant/components/bme280/sensor.py | 2 +- homeassistant/components/bme680/sensor.py | 3 +-- homeassistant/components/braviatv/config_flow.py | 2 +- homeassistant/components/cert_expiry/helper.py | 3 +-- homeassistant/components/cmus/media_player.py | 1 - homeassistant/components/decora/light.py | 6 ++---- .../components/device_tracker/__init__.py | 14 ++++---------- .../components/environment_canada/camera.py | 2 +- .../components/environment_canada/sensor.py | 2 +- .../components/environment_canada/weather.py | 2 +- homeassistant/components/eq3btsmart/climate.py | 2 +- homeassistant/components/fan/__init__.py | 1 - homeassistant/components/flume/sensor.py | 2 +- homeassistant/components/foscam/config_flow.py | 3 +-- homeassistant/components/fritzbox/config_flow.py | 1 - homeassistant/components/gc100/__init__.py | 1 - homeassistant/components/geniushub/__init__.py | 1 - homeassistant/components/google/calendar.py | 2 +- homeassistant/components/google_pubsub/__init__.py | 4 +--- homeassistant/components/habitica/__init__.py | 2 +- homeassistant/components/hassio/config_flow.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 14 +++++--------- homeassistant/components/hdmi_cec/media_player.py | 8 ++------ .../components/here_travel_time/sensor.py | 3 --- homeassistant/components/homekit/__init__.py | 1 - homeassistant/components/homekit/config_flow.py | 2 +- homeassistant/components/htu21d/sensor.py | 2 +- .../components/hvv_departures/binary_sensor.py | 2 +- .../components/hvv_departures/config_flow.py | 7 +------ homeassistant/components/hyperion/config_flow.py | 1 - homeassistant/components/hyperion/light.py | 1 - homeassistant/components/hyperion/switch.py | 2 -- homeassistant/components/icloud/config_flow.py | 2 +- homeassistant/components/knx/const.py | 2 -- homeassistant/components/lcn/__init__.py | 2 +- homeassistant/components/lirc/__init__.py | 2 +- homeassistant/components/lovelace/__init__.py | 2 +- homeassistant/components/mcp23017/binary_sensor.py | 8 ++++---- homeassistant/components/mcp23017/switch.py | 8 ++++---- homeassistant/components/meteo_france/sensor.py | 1 - homeassistant/components/miflora/sensor.py | 2 +- homeassistant/components/mjpeg/camera.py | 2 -- homeassistant/components/mqtt/__init__.py | 4 ++-- homeassistant/components/mqtt/binary_sensor.py | 1 - homeassistant/components/mqtt/sensor.py | 1 - homeassistant/components/mullvad/binary_sensor.py | 2 +- homeassistant/components/mullvad/config_flow.py | 2 +- homeassistant/components/mysensors/config_flow.py | 2 -- homeassistant/components/neato/config_flow.py | 1 - homeassistant/components/noaa_tides/sensor.py | 2 +- homeassistant/components/omnilogic/config_flow.py | 2 +- homeassistant/components/onewire/config_flow.py | 2 +- homeassistant/components/owntracks/config_flow.py | 2 +- homeassistant/components/ozw/config_flow.py | 3 +-- .../components/pi4ioe5v9xxxx/binary_sensor.py | 2 +- homeassistant/components/pi4ioe5v9xxxx/switch.py | 2 +- homeassistant/components/plex/config_flow.py | 2 +- homeassistant/components/plex/server.py | 2 +- homeassistant/components/ps4/__init__.py | 2 +- homeassistant/components/rest/__init__.py | 2 +- homeassistant/components/rpi_rf/switch.py | 1 - .../components/sharkiq/update_coordinator.py | 2 +- homeassistant/components/sms/config_flow.py | 2 +- homeassistant/components/sms/gateway.py | 8 +++----- homeassistant/components/sms/notify.py | 8 ++++---- homeassistant/components/sms/sensor.py | 4 ++-- .../components/somfy_mylink/config_flow.py | 2 +- .../components/speedtestdotnet/config_flow.py | 2 +- homeassistant/components/switchbot/switch.py | 2 +- homeassistant/components/switchmate/switch.py | 2 +- homeassistant/components/syncthru/config_flow.py | 1 - homeassistant/components/tasmota/config_flow.py | 6 +----- .../components/tensorflow/image_processing.py | 3 +-- homeassistant/components/tuya/config_flow.py | 2 +- homeassistant/components/updater/__init__.py | 2 +- homeassistant/components/vera/config_flow.py | 6 +----- homeassistant/components/websocket_api/__init__.py | 10 +++++----- homeassistant/components/websocket_api/const.py | 2 +- homeassistant/components/xbox/media_source.py | 3 +-- .../components/xiaomi_miio/device_tracker.py | 2 +- homeassistant/components/xiaomi_miio/fan.py | 12 ++++++------ homeassistant/components/xiaomi_miio/light.py | 10 ++++++++-- homeassistant/components/xiaomi_miio/remote.py | 2 +- homeassistant/components/xiaomi_miio/sensor.py | 3 +-- homeassistant/components/xiaomi_miio/switch.py | 5 ++--- homeassistant/components/xiaomi_miio/vacuum.py | 2 +- homeassistant/components/zha/__init__.py | 2 +- .../components/zha/core/channels/__init__.py | 2 +- homeassistant/components/zha/core/store.py | 1 - homeassistant/components/zone/config_flow.py | 2 +- homeassistant/components/zwave/__init__.py | 2 -- homeassistant/components/zwave/node_entity.py | 1 - homeassistant/components/zwave_js/discovery.py | 1 - homeassistant/config_entries.py | 5 ++--- homeassistant/core.py | 1 - homeassistant/data_entry_flow.py | 3 +-- homeassistant/exceptions.py | 2 +- homeassistant/helpers/entity_platform.py | 2 +- homeassistant/helpers/entity_registry.py | 2 +- homeassistant/helpers/service.py | 2 +- homeassistant/helpers/sun.py | 2 +- homeassistant/helpers/template.py | 2 +- homeassistant/util/color.py | 3 ++- homeassistant/util/ssl.py | 4 ++-- setup.cfg | 1 + tests/common.py | 2 +- tests/components/abode/conftest.py | 2 +- .../alarm_control_panel/test_device_action.py | 2 +- .../alarm_control_panel/test_device_condition.py | 2 +- .../alarm_control_panel/test_device_trigger.py | 2 +- tests/components/arcam_fmj/test_device_trigger.py | 2 +- tests/components/automation/conftest.py | 2 +- tests/components/axis/conftest.py | 2 +- .../binary_sensor/test_device_condition.py | 2 +- .../binary_sensor/test_device_trigger.py | 2 +- tests/components/blebox/conftest.py | 2 +- tests/components/bond/conftest.py | 2 +- tests/components/climate/test_device_action.py | 2 +- tests/components/climate/test_device_condition.py | 2 +- tests/components/climate/test_device_trigger.py | 2 +- tests/components/config/test_automation.py | 2 +- tests/components/config/test_device_registry.py | 2 +- tests/components/cover/test_device_action.py | 2 +- tests/components/cover/test_device_condition.py | 2 +- tests/components/cover/test_device_trigger.py | 2 +- tests/components/deconz/conftest.py | 2 +- tests/components/deconz/test_device_trigger.py | 2 +- tests/components/default_config/test_init.py | 2 +- tests/components/demo/conftest.py | 2 +- tests/components/device_automation/test_init.py | 2 +- .../device_tracker/test_device_condition.py | 2 +- .../device_tracker/test_device_trigger.py | 2 +- tests/components/dynalite/conftest.py | 2 +- tests/components/elgato/conftest.py | 2 +- tests/components/everlights/conftest.py | 2 +- tests/components/fan/test_device_action.py | 2 +- tests/components/fan/test_device_condition.py | 2 +- tests/components/fan/test_device_trigger.py | 2 +- tests/components/geo_location/test_trigger.py | 2 +- tests/components/google_assistant/test_helpers.py | 2 +- tests/components/group/conftest.py | 2 +- tests/components/hassio/test_websocket_api.py | 2 +- .../components/homeassistant/triggers/conftest.py | 2 +- tests/components/homekit_controller/conftest.py | 2 +- .../homekit_controller/test_device_trigger.py | 2 +- tests/components/homematicip_cloud/conftest.py | 2 +- tests/components/hue/conftest.py | 2 +- tests/components/hue/test_device_trigger.py | 2 +- tests/components/humidifier/test_device_action.py | 2 +- .../components/humidifier/test_device_condition.py | 2 +- tests/components/humidifier/test_device_trigger.py | 2 +- tests/components/hyperion/conftest.py | 2 +- tests/components/kodi/test_device_trigger.py | 2 +- tests/components/light/test_device_action.py | 2 +- tests/components/light/test_device_condition.py | 2 +- tests/components/light/test_device_trigger.py | 2 +- tests/components/litejet/test_trigger.py | 2 +- tests/components/lock/test_device_action.py | 2 +- tests/components/lock/test_device_condition.py | 2 +- tests/components/lock/test_device_trigger.py | 2 +- .../media_player/test_device_condition.py | 2 +- .../components/media_player/test_device_trigger.py | 2 +- tests/components/mochad/conftest.py | 2 +- tests/components/mqtt/conftest.py | 2 +- tests/components/mqtt/test_device_trigger.py | 2 +- tests/components/mqtt/test_trigger.py | 2 +- tests/components/number/test_device_action.py | 2 +- tests/components/ozw/conftest.py | 2 +- tests/components/philips_js/test_device_trigger.py | 2 +- tests/components/remote/test_device_action.py | 2 +- tests/components/remote/test_device_condition.py | 2 +- tests/components/remote/test_device_trigger.py | 2 +- tests/components/rflink/conftest.py | 2 +- tests/components/rfxtrx/conftest.py | 2 +- tests/components/ring/conftest.py | 2 +- tests/components/search/test_init.py | 2 +- tests/components/sensor/test_device_condition.py | 2 +- tests/components/sensor/test_device_trigger.py | 2 +- tests/components/smartthings/conftest.py | 2 +- tests/components/sun/test_trigger.py | 2 +- tests/components/switch/conftest.py | 2 +- tests/components/switch/test_device_action.py | 2 +- tests/components/switch/test_device_condition.py | 2 +- tests/components/switch/test_device_trigger.py | 2 +- tests/components/tag/test_trigger.py | 2 +- tests/components/tasmota/conftest.py | 2 +- tests/components/tasmota/test_device_trigger.py | 2 +- tests/components/template/conftest.py | 2 +- tests/components/template/test_trigger.py | 2 +- tests/components/tplink/conftest.py | 2 +- tests/components/tradfri/conftest.py | 2 +- tests/components/vacuum/test_device_action.py | 2 +- tests/components/vacuum/test_device_condition.py | 2 +- tests/components/vacuum/test_device_trigger.py | 2 +- tests/components/vera/conftest.py | 2 +- .../components/water_heater/test_device_action.py | 2 +- tests/components/webhook/test_trigger.py | 2 +- tests/components/wilight/conftest.py | 2 +- tests/components/wled/conftest.py | 2 +- tests/components/yeelight/conftest.py | 2 +- tests/components/zerproc/conftest.py | 2 +- tests/components/zha/conftest.py | 2 +- tests/components/zha/test_device_action.py | 2 +- tests/components/zha/test_device_trigger.py | 2 +- tests/components/zone/test_trigger.py | 2 +- tests/components/zwave/conftest.py | 2 +- 217 files changed, 237 insertions(+), 306 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b650ebac798f5..8e2fb5cb3b35ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,9 @@ repos: - id: flake8 additional_dependencies: - flake8-docstrings==1.5.0 + # Temporarily every now and then for noqa cleanup; not done by + # default yet due to https://github.com/plinss/flake8-noqa/issues/1 + # - flake8-noqa==1.1.0 - pydocstyle==5.1.1 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit diff --git a/homeassistant/auth/permissions/models.py b/homeassistant/auth/permissions/models.py index 2542be14cc613d..b2d1955865a2ed 100644 --- a/homeassistant/auth/permissions/models.py +++ b/homeassistant/auth/permissions/models.py @@ -4,8 +4,7 @@ import attr if TYPE_CHECKING: - # pylint: disable=unused-import - from homeassistant.helpers import ( # noqa: F401 + from homeassistant.helpers import ( device_registry as dev_reg, entity_registry as ent_reg, ) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7f006d929b15aa..1b1a927d1475f2 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -59,7 +59,7 @@ from homeassistant.util.dt import parse_datetime # Not used except by packages to check config structure -from .config import PLATFORM_SCHEMA # noqa +from .config import PLATFORM_SCHEMA # noqa: F401 from .config import async_validate_config_item from .const import ( CONF_ACTION, diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index 0af2f15b34e78e..0d242b952dd3f9 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -41,7 +41,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up an Avion switch.""" - # pylint: disable=no-member avion = importlib.import_module("avion") lights = [] @@ -111,7 +110,6 @@ def assumed_state(self): def set_state(self, brightness): """Set the state of this lamp to the provided brightness.""" - # pylint: disable=no-member avion = importlib.import_module("avion") # Bluetooth LE is unreliable, and the connection may drop at any diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index a4768014f96a52..c28ac55f21660d 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -10,7 +10,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN, LOGGER # pylint: disable=unused-import +from .const import DOMAIN, LOGGER class AwairFlowHandler(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/bbb_gpio/__init__.py b/homeassistant/components/bbb_gpio/__init__.py index 61d98c7413ea2c..f7d146e073edc9 100644 --- a/homeassistant/components/bbb_gpio/__init__.py +++ b/homeassistant/components/bbb_gpio/__init__.py @@ -8,7 +8,6 @@ def setup(hass, config): """Set up the BeagleBone Black GPIO component.""" - # pylint: disable=import-error def cleanup_gpio(event): """Stuff to do before stopping.""" diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index 7680b8b09ade68..eb9bbc2dcccf2c 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -3,7 +3,7 @@ import logging from i2csense.bh1750 import BH1750 # pylint: disable=import-error -import smbus # pylint: disable=import-error +import smbus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/blinkt/light.py b/homeassistant/components/blinkt/light.py index 0d1aff7b8260f6..bb9bbf315e49ed 100644 --- a/homeassistant/components/blinkt/light.py +++ b/homeassistant/components/blinkt/light.py @@ -26,7 +26,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Blinkt Light platform.""" - # pylint: disable=no-member blinkt = importlib.import_module("blinkt") # ensure that the lights are off when exiting diff --git a/homeassistant/components/blueprint/__init__.py b/homeassistant/components/blueprint/__init__.py index 9e8b1260eff6b7..309365710ad394 100644 --- a/homeassistant/components/blueprint/__init__.py +++ b/homeassistant/components/blueprint/__init__.py @@ -1,7 +1,7 @@ """The blueprint integration.""" from . import websocket_api -from .const import DOMAIN # noqa -from .errors import ( # noqa +from .const import DOMAIN # noqa: F401 +from .errors import ( # noqa: F401 BlueprintException, BlueprintWithNameException, FailedToLoad, @@ -9,8 +9,8 @@ InvalidBlueprintInputs, MissingInput, ) -from .models import Blueprint, BlueprintInputs, DomainBlueprints # noqa -from .schemas import is_blueprint_instance_config # noqa +from .models import Blueprint, BlueprintInputs, DomainBlueprints # noqa: F401 +from .schemas import is_blueprint_instance_config # noqa: F401 async def async_setup(hass, config): diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index 76df4e65ac7e98..9ac79afde2cfbc 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -4,7 +4,7 @@ import logging from uuid import UUID -import pygatt # pylint: disable=import-error +import pygatt import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 380d8091bd683f..9bc2e630ba0825 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -3,8 +3,7 @@ import logging from typing import List, Optional, Set, Tuple -# pylint: disable=import-error -import bluetooth +import bluetooth # pylint: disable=import-error from bt_proximity import BluetoothRSSI import voluptuous as vol diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index 265ec01b6dbc8b..d3d97881035c8b 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -4,7 +4,7 @@ import logging from i2csense.bme280 import BME280 # pylint: disable=import-error -import smbus # pylint: disable=import-error +import smbus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 49d95bbc53be7d..6af9d46c0bcedc 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -4,7 +4,7 @@ from time import monotonic, sleep import bme680 # pylint: disable=import-error -from smbus import SMBus # pylint: disable=import-error +from smbus import SMBus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -131,7 +131,6 @@ def _setup_bme680(config): sensor_handler = None sensor = None try: - # pylint: disable=no-member i2c_address = config[CONF_I2C_ADDRESS] bus = SMBus(config[CONF_I2C_BUS]) sensor = bme680.BME680(i2c_address, bus) diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index d8831dd14941b6..1ac31972f33337 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from .const import ( # pylint:disable=unused-import +from .const import ( ATTR_CID, ATTR_MAC, ATTR_MODEL, diff --git a/homeassistant/components/cert_expiry/helper.py b/homeassistant/components/cert_expiry/helper.py index 6c49e9e26b9c6d..c00a99c8e86dab 100644 --- a/homeassistant/components/cert_expiry/helper.py +++ b/homeassistant/components/cert_expiry/helper.py @@ -19,8 +19,7 @@ def get_cert(host, port): address = (host, port) with socket.create_connection(address, timeout=TIMEOUT) as sock: with ctx.wrap_socket(sock, server_hostname=address[0]) as ssock: - # pylint disable: https://github.com/PyCQA/pylint/issues/3166 - cert = ssock.getpeercert() # pylint: disable=no-member + cert = ssock.getpeercert() return cert diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index 49c10ab92a54fc..3968ebbe9d7fc6 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -98,7 +98,6 @@ def connect(self): class CmusDevice(MediaPlayerEntity): """Representation of a running cmus.""" - # pylint: disable=no-member def __init__(self, device, name, server): """Initialize the CMUS device.""" diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 2fc436eda20e1e..96126bfcc988ff 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -4,10 +4,8 @@ import logging import time -from bluepy.btle import ( # pylint: disable=import-error, no-member, no-name-in-module - BTLEException, -) -import decora # pylint: disable=import-error, no-member +from bluepy.btle import BTLEException # pylint: disable=import-error +import decora # pylint: disable=import-error import voluptuous as vol from homeassistant.components.light import ( diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index d785ee826e86b3..688213bbcb3296 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -1,16 +1,10 @@ """Provide functionality to keep track of devices.""" -from homeassistant.const import ( # noqa: F401 pylint: disable=unused-import - ATTR_GPS_ACCURACY, - STATE_HOME, -) +from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME # noqa: F401 from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.loader import bind_hass -from .config_entry import ( # noqa: F401 pylint: disable=unused-import - async_setup_entry, - async_unload_entry, -) -from .const import ( # noqa: F401 pylint: disable=unused-import +from .config_entry import async_setup_entry, async_unload_entry # noqa: F401 +from .const import ( # noqa: F401 ATTR_ATTRIBUTES, ATTR_BATTERY, ATTR_DEV_ID, @@ -29,7 +23,7 @@ SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER, ) -from .legacy import ( # noqa: F401 pylint: disable=unused-import +from .legacy import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, SERVICE_SEE, diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 66079ac73ff83b..7021d637cfd94b 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -1,7 +1,7 @@ """Support for the Environment Canada radar imagery.""" import datetime -from env_canada import ECRadar # pylint: disable=import-error +from env_canada import ECRadar import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, Camera diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index a8772909f68374..20eb6fac9eef66 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -3,7 +3,7 @@ import logging import re -from env_canada import ECData # pylint: disable=import-error +from env_canada import ECData import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index dd2252a585ff9b..9abbc33bc93376 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -2,7 +2,7 @@ import datetime import re -from env_canada import ECData # pylint: disable=import-error +from env_canada import ECData import voluptuous as vol from homeassistant.components.weather import ( diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index 737b3fe357a4b7..43c6bd9f35b3cc 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -1,7 +1,7 @@ """Support for eQ-3 Bluetooth Smart thermostats.""" import logging -from bluepy.btle import BTLEException # pylint: disable=import-error, no-name-in-module +from bluepy.btle import BTLEException # pylint: disable=import-error import eq3bt as eq3 # pylint: disable=import-error import voluptuous as vol diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 18f46b3d6190ff..25911eb2d06658 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -344,7 +344,6 @@ def turn_on( """Turn on the fan.""" raise NotImplementedError() - # pylint: disable=arguments-differ async def async_turn_on_compat( self, speed: Optional[str] = None, diff --git a/homeassistant/components/flume/sensor.py b/homeassistant/components/flume/sensor.py index 6b19c9c5476bca..65584239910bfa 100644 --- a/homeassistant/components/flume/sensor.py +++ b/homeassistant/components/flume/sensor.py @@ -175,7 +175,7 @@ async def _async_update_data(): _LOGGER.debug("Updating Flume data") try: await hass.async_add_executor_job(flume_device.update_force) - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: raise UpdateFailed(f"Error communicating with flume API: {ex}") from ex _LOGGER.debug( "Flume update details: %s", diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index bfeefb9e406055..bfd13c730f8b1f 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -17,8 +17,7 @@ ) from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_RTSP_PORT, CONF_STREAM, LOGGER -from .const import DOMAIN # pylint:disable=unused-import +from .const import CONF_RTSP_PORT, CONF_STREAM, DOMAIN, LOGGER STREAMS = ["Main", "Sub"] diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 904081ef99fa12..a462f885484ea7 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -13,7 +13,6 @@ ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -# pylint:disable=unused-import from .const import DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN DATA_SCHEMA_USER = vol.Schema( diff --git a/homeassistant/components/gc100/__init__.py b/homeassistant/components/gc100/__init__.py index 77380afc2b5f1c..51abd9d5693322 100644 --- a/homeassistant/components/gc100/__init__.py +++ b/homeassistant/components/gc100/__init__.py @@ -25,7 +25,6 @@ ) -# pylint: disable=no-member def setup(hass, base_config): """Set up the gc100 component.""" config = base_config[DOMAIN] diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 909de81521c109..def964157e40f4 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -173,7 +173,6 @@ def __init__(self, hass, client, hub_uid) -> None: @property def hub_uid(self) -> int: """Return the Hub UID (MAC address).""" - # pylint: disable=no-member return self._hub_uid if self._hub_uid is not None else self.client.uid async def async_update(self, now, **kwargs) -> None: diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 2fcde78354bf69..9040d37935d84d 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging -from httplib2 import ServerNotFoundError # pylint: disable=import-error +from httplib2 import ServerNotFoundError from homeassistant.components.calendar import ( ENTITY_ID_FORMAT, diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index 8d7a675860c07c..c832c87318c336 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -57,9 +57,7 @@ def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): service_principal_path ) - topic_path = publisher.topic_path( # pylint: disable=no-member - project_id, topic_name - ) + topic_path = publisher.topic_path(project_id, topic_name) encoder = DateTimeJSONEncoder() diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index 64680a56bb31bc..c70d374dfe9cdd 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -46,7 +46,7 @@ ), ) -has_unique_values = vol.Schema(vol.Unique()) # pylint: disable=invalid-name +has_unique_values = vol.Schema(vol.Unique()) # because we want a handy alias diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py index 56c7d324a61f1c..8b2c68d752d90b 100644 --- a/homeassistant/components/hassio/config_flow.py +++ b/homeassistant/components/hassio/config_flow.py @@ -3,7 +3,7 @@ from homeassistant import config_entries -from . import DOMAIN # pylint:disable=unused-import +from . import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index c272ad19c8d2fb..c9a5d27a3be9f0 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -4,13 +4,9 @@ import logging import multiprocessing -from pycec.cec import CecAdapter # pylint: disable=import-error -from pycec.commands import ( # pylint: disable=import-error - CecCommand, - KeyPressCommand, - KeyReleaseCommand, -) -from pycec.const import ( # pylint: disable=import-error +from pycec.cec import CecAdapter +from pycec.commands import CecCommand, KeyPressCommand, KeyReleaseCommand +from pycec.const import ( ADDR_AUDIOSYSTEM, ADDR_BROADCAST, ADDR_UNREGISTERED, @@ -25,8 +21,8 @@ STATUS_STILL, STATUS_STOP, ) -from pycec.network import HDMINetwork, PhysicalAddress # pylint: disable=import-error -from pycec.tcp import TcpAdapter # pylint: disable=import-error +from pycec.network import HDMINetwork, PhysicalAddress +from pycec.tcp import TcpAdapter import voluptuous as vol from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index f81ee20afe33ad..c3cab6a8f981e1 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -1,12 +1,8 @@ """Support for HDMI CEC devices as media players.""" import logging -from pycec.commands import ( # pylint: disable=import-error - CecCommand, - KeyPressCommand, - KeyReleaseCommand, -) -from pycec.const import ( # pylint: disable=import-error +from pycec.commands import CecCommand, KeyPressCommand, KeyReleaseCommand +from pycec.const import ( KEY_BACKWARD, KEY_FORWARD, KEY_MUTE_TOGGLE, diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e51e7a067fcd0b..e9506fceed5209 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -457,11 +457,9 @@ def update(self) -> None: _LOGGER.debug("Raw response is: %s", response.response) - # pylint: disable=no-member source_attribution = response.response.get("sourceAttribution") if source_attribution is not None: self.attribution = self._build_hass_attribution(source_attribution) - # pylint: disable=no-member route = response.response["route"] summary = route[0]["summary"] waypoint = route[0]["waypoint"] @@ -477,7 +475,6 @@ def update(self) -> None: else: # Convert to kilometers self.distance = distance / 1000 - # pylint: disable=no-member self.route = response.route_short self.origin_name = waypoint[0]["mappedRoadName"] self.destination_name = waypoint[1]["mappedRoadName"] diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index c042872f4cd632..0dfe7336bcf45f 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -42,7 +42,6 @@ from homeassistant.loader import IntegrationNotFound, async_get_integration from homeassistant.util import get_local_ip -# pylint: disable=unused-import from . import ( # noqa: F401 type_cameras, type_covers, diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index c21c27fba83862..2eabdd98e79c65 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -36,13 +36,13 @@ DEFAULT_AUTO_START, DEFAULT_CONFIG_FLOW_PORT, DEFAULT_HOMEKIT_MODE, + DOMAIN, HOMEKIT_MODE_ACCESSORY, HOMEKIT_MODE_BRIDGE, HOMEKIT_MODES, SHORT_BRIDGE_NAME, VIDEO_CODEC_COPY, ) -from .const import DOMAIN # pylint:disable=unused-import from .util import async_find_next_available_port, state_needs_accessory_mode CONF_CAMERA_COPY = "camera_copy" diff --git a/homeassistant/components/htu21d/sensor.py b/homeassistant/components/htu21d/sensor.py index a9b235faa0b270..615177ed6af233 100644 --- a/homeassistant/components/htu21d/sensor.py +++ b/homeassistant/components/htu21d/sensor.py @@ -4,7 +4,7 @@ import logging from i2csense.htu21d import HTU21D # pylint: disable=import-error -import smbus # pylint: disable=import-error +import smbus import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/hvv_departures/binary_sensor.py b/homeassistant/components/hvv_departures/binary_sensor.py index 7d19fcc8fdfcc1..f625ef7c3444e4 100644 --- a/homeassistant/components/hvv_departures/binary_sensor.py +++ b/homeassistant/components/hvv_departures/binary_sensor.py @@ -92,7 +92,7 @@ async def async_update_data(): raise UpdateFailed(f"Authentication failed: {err}") from err except ClientConnectorError as err: raise UpdateFailed(f"Network not available: {err}") from err - except Exception as err: # pylint: disable=broad-except + except Exception as err: raise UpdateFailed(f"Error occurred while fetching data: {err}") from err coordinator = DataUpdateCoordinator( diff --git a/homeassistant/components/hvv_departures/config_flow.py b/homeassistant/components/hvv_departures/config_flow.py index 1a49bffd2f55e5..556505101eb74c 100644 --- a/homeassistant/components/hvv_departures/config_flow.py +++ b/homeassistant/components/hvv_departures/config_flow.py @@ -11,12 +11,7 @@ from homeassistant.helpers import aiohttp_client import homeassistant.helpers.config_validation as cv -from .const import ( # pylint:disable=unused-import - CONF_FILTER, - CONF_REAL_TIME, - CONF_STATION, - DOMAIN, -) +from .const import CONF_FILTER, CONF_REAL_TIME, CONF_STATION, DOMAIN from .hub import GTIHub _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 642bc0e93fd0ee..8d02028dc38b37 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -221,7 +221,6 @@ async def async_step_ssdp( # type: ignore[override] return self.async_abort(reason="cannot_connect") return await self._advance_to_auth_step_if_necessary(hyperion_client) - # pylint: disable=arguments-differ async def async_step_user( self, user_input: Optional[ConfigType] = None, diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index c49a65b2bfd588..838b69cd52b568 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -553,7 +553,6 @@ def _is_priority_entry_black(cls, priority: Optional[Dict[str, Any]]) -> bool: return True return False - # pylint: disable=no-self-use def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: """Determine whether to allow a Hyperion priority to update entity attributes.""" # Black is treated as 'off' (and Home Assistant does not support selecting black diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index 0412018650aa68..e2da80e509327f 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -178,12 +178,10 @@ async def _async_send_set_component(self, value: bool) -> None: } ) - # pylint: disable=unused-argument async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the switch.""" await self._async_send_set_component(True) - # pylint: disable=unused-argument async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the switch.""" await self._async_send_set_component(False) diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index c79024c4f64e8c..28570f3d93c6bb 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -21,10 +21,10 @@ DEFAULT_GPS_ACCURACY_THRESHOLD, DEFAULT_MAX_INTERVAL, DEFAULT_WITH_FAMILY, + DOMAIN, STORAGE_KEY, STORAGE_VERSION, ) -from .const import DOMAIN # pylint: disable=unused-import CONF_TRUSTED_DEVICE = "trusted_device" CONF_VERIFICATION_CODE = "verification_code" diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 268f766bfc2b7d..dfe357ef33c21d 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -29,7 +29,6 @@ class ColorTempModes(Enum): - # pylint: disable=invalid-name """Color temperature modes for config validation.""" ABSOLUTE = "DPT-7.600" @@ -37,7 +36,6 @@ class ColorTempModes(Enum): class SupportedPlatforms(Enum): - # pylint: disable=invalid-name """Supported platforms.""" BINARY_SENSOR = "binary_sensor" diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index cc1e47d71fc0ed..fe9195584ea9b0 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -27,7 +27,7 @@ DATA_LCN, DOMAIN, ) -from .schemas import CONFIG_SCHEMA # noqa: 401 +from .schemas import CONFIG_SCHEMA # noqa: F401 from .services import ( DynText, Led, diff --git a/homeassistant/components/lirc/__init__.py b/homeassistant/components/lirc/__init__.py index bf939fb7535137..b52cf3cf1074c9 100644 --- a/homeassistant/components/lirc/__init__.py +++ b/homeassistant/components/lirc/__init__.py @@ -1,5 +1,5 @@ """Support for LIRC devices.""" -# pylint: disable=no-member, import-error +# pylint: disable=import-error import logging import threading import time diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index e673b2a470b1c8..45011239f167ee 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -34,7 +34,7 @@ STORAGE_DASHBOARD_UPDATE_FIELDS, url_slug, ) -from .system_health import system_health_info # NOQA +from .system_health import system_health_info # noqa: F401 _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mcp23017/binary_sensor.py b/homeassistant/components/mcp23017/binary_sensor.py index f6dafad43acbf7..c650393a26f3de 100644 --- a/homeassistant/components/mcp23017/binary_sensor.py +++ b/homeassistant/components/mcp23017/binary_sensor.py @@ -1,8 +1,8 @@ """Support for binary sensor using I2C MCP23017 chip.""" -from adafruit_mcp230xx.mcp23017 import MCP23017 # pylint: disable=import-error -import board # pylint: disable=import-error -import busio # pylint: disable=import-error -import digitalio # pylint: disable=import-error +from adafruit_mcp230xx.mcp23017 import MCP23017 +import board +import busio +import digitalio import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity diff --git a/homeassistant/components/mcp23017/switch.py b/homeassistant/components/mcp23017/switch.py index d22593a4c3ef84..6b1ced540aeaa2 100644 --- a/homeassistant/components/mcp23017/switch.py +++ b/homeassistant/components/mcp23017/switch.py @@ -1,8 +1,8 @@ """Support for switch sensor using I2C MCP23017 chip.""" -from adafruit_mcp230xx.mcp23017 import MCP23017 # pylint: disable=import-error -import board # pylint: disable=import-error -import busio # pylint: disable=import-error -import digitalio # pylint: disable=import-error +from adafruit_mcp230xx.mcp23017 import MCP23017 +import board +import busio +import digitalio import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 201cca7ae9daf6..4802c20c1e55f8 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -193,7 +193,6 @@ def device_state_attributes(self): class MeteoFranceAlertSensor(MeteoFranceSensor): """Representation of a Meteo-France alert sensor.""" - # pylint: disable=super-init-not-called def __init__(self, sensor_type: str, coordinator: DataUpdateCoordinator): """Initialize the Meteo-France sensor.""" super().__init__(sensor_type, coordinator) diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index 6583f5f7e0ca2e..fa1d8b57734be0 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -5,7 +5,7 @@ import btlewrap from btlewrap import BluetoothBackendException -from miflora import miflora_poller # pylint: disable=import-error +from miflora import miflora_poller import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 1a8b132709360c..15f7f5c80bfc07 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -144,8 +144,6 @@ def camera_image(self): else: req = requests.get(self._mjpeg_url, stream=True, timeout=10) - # https://github.com/PyCQA/pylint/issues/1437 - # pylint: disable=no-member with closing(req) as response: return extract_image_from_mjpeg(response.iter_content(102400)) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 29f7e24da006f5..503083b0067299 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -668,7 +668,7 @@ def init_client(self): will_message = None if will_message is not None: - self._mqttc.will_set( # pylint: disable=no-value-for-parameter + self._mqttc.will_set( topic=will_message.topic, payload=will_message.payload, qos=will_message.qos, @@ -833,7 +833,7 @@ def _mqtt_on_connect(self, _mqttc, _userdata, _flags, result_code: int) -> None: async def publish_birth_message(birth_message): await self._ha_started.wait() # Wait for Home Assistant to start await self._discovery_cooldown() # Wait for MQTT discovery to cool down - await self.async_publish( # pylint: disable=no-value-for-parameter + await self.async_publish( topic=birth_message.topic, payload=birth_message.payload, qos=birth_message.qos, diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index b9fb297cd5cd2d..a1f13e7873ef0b 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -242,7 +242,6 @@ def force_update(self): def available(self) -> bool: """Return true if the device is available and value has not expired.""" expire_after = self._config.get(CONF_EXPIRE_AFTER) - # pylint: disable=no-member return MqttAvailability.available.fget(self) and ( expire_after is None or not self._expired ) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index d017cb2ce85460..cb263febe13c41 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -197,7 +197,6 @@ def device_class(self) -> Optional[str]: def available(self) -> bool: """Return true if the device is available and value has not expired.""" expire_after = self._config.get(CONF_EXPIRE_AFTER) - # pylint: disable=no-member return MqttAvailability.available.fget(self) and ( expire_after is None or not self._expired ) diff --git a/homeassistant/components/mullvad/binary_sensor.py b/homeassistant/components/mullvad/binary_sensor.py index f85820cd7d0a76..1a91bba2038db4 100644 --- a/homeassistant/components/mullvad/binary_sensor.py +++ b/homeassistant/components/mullvad/binary_sensor.py @@ -29,7 +29,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class MullvadBinarySensor(CoordinatorEntity, BinarySensorEntity): """Represents a Mullvad binary sensor.""" - def __init__(self, coordinator, sensor): # pylint: disable=super-init-not-called + def __init__(self, coordinator, sensor): """Initialize the Mullvad binary sensor.""" super().__init__(coordinator) self.id = sensor[CONF_ID] diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py index 674308c1d1a246..50f67d10e257b2 100644 --- a/homeassistant/components/mullvad/config_flow.py +++ b/homeassistant/components/mullvad/config_flow.py @@ -5,7 +5,7 @@ from homeassistant import config_entries -from .const import DOMAIN # pylint:disable=unused-import +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index d2cd7f3bccd03d..7bf30e4cab5b47 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -23,8 +23,6 @@ import homeassistant.helpers.config_validation as cv from . import CONF_RETAIN, CONF_VERSION, DEFAULT_VERSION - -# pylint: disable=unused-import from .const import ( CONF_BAUD_RATE, CONF_GATEWAY_TYPE, diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 1f2f575ae505a9..5f7487fe5ccb79 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -8,7 +8,6 @@ from homeassistant.const import CONF_TOKEN from homeassistant.helpers import config_entry_oauth2_flow -# pylint: disable=unused-import from .const import NEATO_DOMAIN diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index a0453e3acb1883..0759c5093c84c4 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta import logging -import noaa_coops as coops # pylint: disable=import-error +import noaa_coops as coops import requests import voluptuous as vol diff --git a/homeassistant/components/omnilogic/config_flow.py b/homeassistant/components/omnilogic/config_flow.py index 641ec5a8d94bfd..f8dffaeda44bb9 100644 --- a/homeassistant/components/omnilogic/config_flow.py +++ b/homeassistant/components/omnilogic/config_flow.py @@ -9,7 +9,7 @@ from homeassistant.core import callback from homeassistant.helpers import aiohttp_client -from .const import CONF_SCAN_INTERVAL, DOMAIN # pylint:disable=unused-import +from .const import CONF_SCAN_INTERVAL, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 9ad4d5347f0cc0..fbb1d5debefeb3 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -5,7 +5,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE from homeassistant.helpers.typing import HomeAssistantType -from .const import ( # pylint: disable=unused-import +from .const import ( CONF_MOUNT_DIR, CONF_TYPE_OWFS, CONF_TYPE_OWSERVER, diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index f0838b510ec8b3..2e588ff933ab81 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -4,7 +4,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_WEBHOOK_ID -from .const import DOMAIN # noqa pylint: disable=unused-import +from .const import DOMAIN # pylint: disable=unused-import from .helper import supports_encryption CONF_SECRET = "secret" diff --git a/homeassistant/components/ozw/config_flow.py b/homeassistant/components/ozw/config_flow.py index 00917c0609c932..2546a2e0aff662 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -7,8 +7,7 @@ from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow -from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON -from .const import DOMAIN # pylint:disable=unused-import +from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py b/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py index 61cedb8f063271..bdec7714eef310 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py +++ b/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py @@ -1,5 +1,5 @@ """Support for binary sensor using RPi GPIO.""" -from pi4ioe5v9xxxx import pi4ioe5v9xxxx # pylint: disable=import-error +from pi4ioe5v9xxxx import pi4ioe5v9xxxx import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity diff --git a/homeassistant/components/pi4ioe5v9xxxx/switch.py b/homeassistant/components/pi4ioe5v9xxxx/switch.py index 81de76c086c226..85bde509070618 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/switch.py +++ b/homeassistant/components/pi4ioe5v9xxxx/switch.py @@ -1,5 +1,5 @@ """Allows to configure a switch using RPi GPIO.""" -from pi4ioe5v9xxxx import pi4ioe5v9xxxx # pylint: disable=import-error +from pi4ioe5v9xxxx import pi4ioe5v9xxxx import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index d611c09c43e00f..d1fa5684cf5f23 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -27,7 +27,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.network import get_url -from .const import ( # pylint: disable=unused-import +from .const import ( AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH, AUTOMATIC_SETUP_STRING, diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 8f9d4d1cc515f0..c8d383867cfadf 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -189,7 +189,7 @@ def _update_plexdirect_hostname(): _connect_with_url() except requests.exceptions.SSLError as error: while error and not isinstance(error, ssl.SSLCertVerificationError): - error = error.__context__ # pylint: disable=no-member + error = error.__context__ if isinstance(error, ssl.SSLCertVerificationError): domain = urlparse(self._url).netloc.split(":")[0] if domain.endswith("plex.direct") and error.args[0].startswith( diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index 390637c26a35fa..11d271be543a0d 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -25,7 +25,7 @@ from homeassistant.util import location from homeassistant.util.json import load_json, save_json -from .config_flow import PlayStation4FlowHandler # noqa: pylint: disable=unused-import +from .config_flow import PlayStation4FlowHandler # noqa: F401 from .const import ATTR_MEDIA_IMAGE_URL, COMMANDS, DOMAIN, GAMES_FILE, PS4_DATA _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index ebeddcfd7c7c22..233a12f44c8b3a 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -35,7 +35,7 @@ from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_IDX from .data import RestData -from .schema import CONFIG_SCHEMA # noqa:F401 pylint: disable=unused-import +from .schema import CONFIG_SCHEMA # noqa: F401 _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/rpi_rf/switch.py b/homeassistant/components/rpi_rf/switch.py index 4ac7283b1942d0..a374300a264589 100644 --- a/homeassistant/components/rpi_rf/switch.py +++ b/homeassistant/components/rpi_rf/switch.py @@ -45,7 +45,6 @@ ) -# pylint: disable=no-member def setup_platform(hass, config, add_entities, discovery_info=None): """Find and return switches controlled by a generic RF device via GPIO.""" rpi_rf = importlib.import_module("rpi_rf") diff --git a/homeassistant/components/sharkiq/update_coordinator.py b/homeassistant/components/sharkiq/update_coordinator.py index eff18064dcc3b0..8675058de8612c 100644 --- a/homeassistant/components/sharkiq/update_coordinator.py +++ b/homeassistant/components/sharkiq/update_coordinator.py @@ -99,7 +99,7 @@ async def _async_update_data(self) -> bool: _LOGGER.debug("Matching flow found") raise UpdateFailed(err) from err - except Exception as err: # pylint: disable=broad-except + except Exception as err: _LOGGER.exception("Unexpected error updating SharkIQ") raise UpdateFailed(err) from err diff --git a/homeassistant/components/sms/config_flow.py b/homeassistant/components/sms/config_flow.py index 01c1d182c93949..da5db672959b41 100644 --- a/homeassistant/components/sms/config_flow.py +++ b/homeassistant/components/sms/config_flow.py @@ -1,7 +1,7 @@ """Config flow for SMS integration.""" import logging -import gammu # pylint: disable=import-error, no-member +import gammu # pylint: disable=import-error import voluptuous as vol from homeassistant import config_entries, core, exceptions diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index 9bdfbad0f5597f..e28b3947e3745b 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -1,10 +1,8 @@ """The sms gateway to interact with a GSM modem.""" import logging -import gammu # pylint: disable=import-error, no-member -from gammu.asyncworker import ( # pylint: disable=import-error, no-member - GammuAsyncWorker, -) +import gammu # pylint: disable=import-error +from gammu.asyncworker import GammuAsyncWorker # pylint: disable=import-error from homeassistant.core import callback @@ -165,6 +163,6 @@ async def create_sms_gateway(config, hass): gateway = Gateway(worker, hass) await gateway.init_async() return gateway - except gammu.GSMError as exc: # pylint: disable=no-member + except gammu.GSMError as exc: _LOGGER.error("Failed to initialize, error %s", exc) return None diff --git a/homeassistant/components/sms/notify.py b/homeassistant/components/sms/notify.py index f030409b6cace7..04964c15878ea9 100644 --- a/homeassistant/components/sms/notify.py +++ b/homeassistant/components/sms/notify.py @@ -1,7 +1,7 @@ """Support for SMS notification services.""" import logging -import gammu # pylint: disable=import-error, no-member +import gammu # pylint: disable=import-error import voluptuous as vol from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService @@ -51,8 +51,8 @@ async def async_send_message(self, message="", **kwargs): } try: # Encode messages - encoded = gammu.EncodeSMS(smsinfo) # pylint: disable=no-member - except gammu.GSMError as exc: # pylint: disable=no-member + encoded = gammu.EncodeSMS(smsinfo) + except gammu.GSMError as exc: _LOGGER.error("Encoding message %s failed: %s", message, exc) return @@ -64,5 +64,5 @@ async def async_send_message(self, message="", **kwargs): try: # Actually send the message await self.gateway.send_sms_async(encoded_message) - except gammu.GSMError as exc: # pylint: disable=no-member + except gammu.GSMError as exc: _LOGGER.error("Sending to %s failed: %s", self.number, exc) diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index eaad395eaa6910..e775b4a0e051b2 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -1,7 +1,7 @@ """Support for SMS dongle sensor.""" import logging -import gammu # pylint: disable=import-error, no-member +import gammu # pylint: disable=import-error from homeassistant.const import DEVICE_CLASS_SIGNAL_STRENGTH, SIGNAL_STRENGTH_DECIBELS from homeassistant.helpers.entity import Entity @@ -71,7 +71,7 @@ async def async_update(self): """Get the latest data from the modem.""" try: self._state = await self._gateway.get_signal_quality_async() - except gammu.GSMError as exc: # pylint: disable=no-member + except gammu.GSMError as exc: _LOGGER.error("Failed to read signal quality: %s", exc) @property diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index b6d647b9a3b58a..d1a1e19609ad38 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -19,9 +19,9 @@ CONF_TARGET_ID, CONF_TARGET_NAME, DEFAULT_PORT, + DOMAIN, MYLINK_STATUS, ) -from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 5ff405b59a839a..84d63ebc33bd13 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -13,8 +13,8 @@ DEFAULT_NAME, DEFAULT_SCAN_INTERVAL, DEFAULT_SERVER, + DOMAIN, ) -from .const import DOMAIN # pylint: disable=unused-import class SpeedTestFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index 3dd931abe49a5a..89ba7b5be5edaf 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -1,7 +1,7 @@ """Support for Switchbot.""" from typing import Any, Dict -# pylint: disable=import-error, no-member +# pylint: disable=import-error import switchbot import voluptuous as vol diff --git a/homeassistant/components/switchmate/switch.py b/homeassistant/components/switchmate/switch.py index a052b9051a1f69..24b54537dc839e 100644 --- a/homeassistant/components/switchmate/switch.py +++ b/homeassistant/components/switchmate/switch.py @@ -1,7 +1,7 @@ """Support for Switchmate.""" from datetime import timedelta -# pylint: disable=import-error, no-member +# pylint: disable=import-error import switchmate import voluptuous as vol diff --git a/homeassistant/components/syncthru/config_flow.py b/homeassistant/components/syncthru/config_flow.py index 83f044d8ebcb3f..2c6eb65e4ce42c 100644 --- a/homeassistant/components/syncthru/config_flow.py +++ b/homeassistant/components/syncthru/config_flow.py @@ -62,7 +62,6 @@ async def async_step_ssdp(self, discovery_info): # Remove trailing " (ip)" if present for consistency with user driven config self.name = re.sub(r"\s+\([\d.]+\)\s*$", "", self.name) - # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {CONF_NAME: self.name} return await self.async_step_confirm() diff --git a/homeassistant/components/tasmota/config_flow.py b/homeassistant/components/tasmota/config_flow.py index 5d39fa024382f0..320b4ff2448736 100644 --- a/homeassistant/components/tasmota/config_flow.py +++ b/homeassistant/components/tasmota/config_flow.py @@ -4,11 +4,7 @@ from homeassistant import config_entries from homeassistant.components.mqtt import valid_subscribe_topic -from .const import ( # pylint:disable=unused-import - CONF_DISCOVERY_PREFIX, - DEFAULT_PREFIX, - DOMAIN, -) +from .const import CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX, DOMAIN class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index e387ae97afef4c..65240dc04dc876 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -341,9 +341,8 @@ def process_image(self, image): start = time.perf_counter() try: - import cv2 # pylint: disable=import-error, import-outside-toplevel + import cv2 # pylint: disable=import-outside-toplevel - # pylint: disable=no-member img = cv2.imdecode(np.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) inp = img[:, :, [2, 1, 0]] # BGR->RGB inp_expanded = inp.reshape(1, inp.shape[0], inp.shape[1], 3) diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index b705c2c7c36ef1..18820098a9134a 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -42,11 +42,11 @@ DEFAULT_DISCOVERY_INTERVAL, DEFAULT_QUERY_INTERVAL, DEFAULT_TUYA_MAX_COLTEMP, + DOMAIN, TUYA_DATA, TUYA_PLATFORMS, TUYA_TYPE_NOT_QUERY, ) -from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 9d65bb4c5d4b3f..282c477d20d5ba 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -5,7 +5,7 @@ import async_timeout from awesomeversion import AwesomeVersion -from distro import linux_distribution # pylint: disable=import-error +from distro import linux_distribution import voluptuous as vol from homeassistant.const import __version__ as current_version diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 754d2eca542801..e62b21c82ea7b8 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -13,11 +13,7 @@ from homeassistant.core import callback from homeassistant.helpers.entity_registry import EntityRegistry -from .const import ( # pylint: disable=unused-import - CONF_CONTROLLER, - CONF_LEGACY_UNIQUE_ID, - DOMAIN, -) +from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 2d591455eaf1c5..43f5c807770a9c 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -6,9 +6,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.loader import bind_hass -from . import commands, connection, const, decorators, http, messages # noqa -from .connection import ActiveConnection # noqa -from .const import ( # noqa +from . import commands, connection, const, decorators, http, messages # noqa: F401 +from .connection import ActiveConnection # noqa: F401 +from .const import ( # noqa: F401 ERR_HOME_ASSISTANT_ERROR, ERR_INVALID_FORMAT, ERR_NOT_FOUND, @@ -19,13 +19,13 @@ ERR_UNKNOWN_COMMAND, ERR_UNKNOWN_ERROR, ) -from .decorators import ( # noqa +from .decorators import ( # noqa: F401 async_response, require_admin, websocket_command, ws_require_user, ) -from .messages import ( # noqa +from .messages import ( # noqa: F401 BASE_COMMAND_MESSAGE_SCHEMA, error_message, event_message, diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index 5f2cfb2257dc8a..7c3f18f856c5fb 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -9,7 +9,7 @@ from homeassistant.helpers.json import JSONEncoder if TYPE_CHECKING: - from .connection import ActiveConnection # noqa + from .connection import ActiveConnection WebSocketCommandHandler = Callable[[HomeAssistant, "ActiveConnection", dict], None] diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 750300e49ee716..53346f3750d559 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -2,8 +2,7 @@ from dataclasses import dataclass from typing import List, Tuple -# pylint: disable=no-name-in-module -from pydantic.error_wrappers import ValidationError +from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module from xbox.webapi.api.client import XboxLiveClient from xbox.webapi.api.provider.catalog.models import FieldsTemplate, Image from xbox.webapi.api.provider.gameclips.models import GameclipsResponse diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py index ef527d0aa407a2..a6c1c7e5a28d61 100644 --- a/homeassistant/components/xiaomi_miio/device_tracker.py +++ b/homeassistant/components/xiaomi_miio/device_tracker.py @@ -1,7 +1,7 @@ """Support for Xiaomi Mi WiFi Repeater 2.""" import logging -from miio import DeviceException, WifiRepeater # pylint: disable=import-error +from miio import DeviceException, WifiRepeater import voluptuous as vol from homeassistant.components.device_tracker import ( diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 055690a4264437..c8e200516a0f80 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -4,7 +4,7 @@ from functools import partial import logging -from miio import ( # pylint: disable=import-error +from miio import ( AirFresh, AirHumidifier, AirHumidifierMiot, @@ -12,24 +12,24 @@ AirPurifierMiot, DeviceException, ) -from miio.airfresh import ( # pylint: disable=import-error, import-error +from miio.airfresh import ( LedBrightness as AirfreshLedBrightness, OperationMode as AirfreshOperationMode, ) -from miio.airhumidifier import ( # pylint: disable=import-error, import-error +from miio.airhumidifier import ( LedBrightness as AirhumidifierLedBrightness, OperationMode as AirhumidifierOperationMode, ) -from miio.airhumidifier_miot import ( # pylint: disable=import-error, import-error +from miio.airhumidifier_miot import ( LedBrightness as AirhumidifierMiotLedBrightness, OperationMode as AirhumidifierMiotOperationMode, PressedButton as AirhumidifierPressedButton, ) -from miio.airpurifier import ( # pylint: disable=import-error, import-error +from miio.airpurifier import ( LedBrightness as AirpurifierLedBrightness, OperationMode as AirpurifierOperationMode, ) -from miio.airpurifier_miot import ( # pylint: disable=import-error, import-error +from miio.airpurifier_miot import ( LedBrightness as AirpurifierMiotLedBrightness, OperationMode as AirpurifierMiotOperationMode, ) diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 7f168cf0e3ee72..b03aa1b0d2a45d 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -6,8 +6,14 @@ import logging from math import ceil -from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight -from miio import Device # pylint: disable=import-error +from miio import ( + Ceil, + Device, + DeviceException, + PhilipsBulb, + PhilipsEyecare, + PhilipsMoonlight, +) from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index c946533ab54c4b..7d75e943d4dfc5 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -4,7 +4,7 @@ import logging import time -from miio import ChuangmiIr, DeviceException # pylint: disable=import-error +from miio import ChuangmiIr, DeviceException import voluptuous as vol from homeassistant.components.remote import ( diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 821fe164ea97f5..52cd4fdca5e575 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -2,8 +2,7 @@ from dataclasses import dataclass import logging -from miio import AirQualityMonitor # pylint: disable=import-error -from miio import DeviceException +from miio import AirQualityMonitor, DeviceException from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 3cc95572e6cc86..04bb40ec27dd65 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -3,9 +3,8 @@ from functools import partial import logging -from miio import AirConditioningCompanionV3 # pylint: disable=import-error -from miio import ChuangmiPlug, DeviceException, PowerStrip -from miio.powerstrip import PowerMode # pylint: disable=import-error +from miio import AirConditioningCompanionV3, ChuangmiPlug, DeviceException, PowerStrip +from miio.powerstrip import PowerMode import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 7bdbfca7bc9190..bbb4cfd1b7f3dd 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -2,7 +2,7 @@ from functools import partial import logging -from miio import DeviceException, Vacuum # pylint: disable=import-error +from miio import DeviceException, Vacuum import voluptuous as vol from homeassistant.components.vacuum import ( diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index d5f76fa5e232c3..351fc86a1e5b86 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -94,7 +94,7 @@ async def async_setup_entry(hass, config_entry): if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading # before zhaquirks is imported - import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error + import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel zha_gateway = ZHAGateway(hass, config, config_entry) await zha_gateway.async_initialize() diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index 852d576c03593a..d3de5f7004df64 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -10,7 +10,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from . import ( # noqa: F401 # pylint: disable=unused-import +from . import ( # noqa: F401 base, closures, general, diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 051fcaa29252b1..2db803258bc129 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -1,5 +1,4 @@ """Data storage helper for ZHA.""" -# pylint: disable=unused-import from collections import OrderedDict import datetime import time diff --git a/homeassistant/components/zone/config_flow.py b/homeassistant/components/zone/config_flow.py index bb34a83ad260ce..a778d71d2e5822 100644 --- a/homeassistant/components/zone/config_flow.py +++ b/homeassistant/components/zone/config_flow.py @@ -6,7 +6,7 @@ """ from homeassistant import config_entries -from .const import DOMAIN # noqa # pylint:disable=unused-import +from .const import DOMAIN # pylint: disable=unused-import class ZoneConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 3acf361dd52f43..8e9889b5fac46d 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -396,7 +396,6 @@ async def async_setup_entry(hass, config_entry): Will automatically load components to support devices found on the network. """ - # pylint: disable=import-error from openzwave.group import ZWaveGroup from openzwave.network import ZWaveNetwork from openzwave.option import ZWaveOption @@ -1251,7 +1250,6 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): def __init__(self, values, domain): """Initialize the z-Wave device.""" - # pylint: disable=import-error super().__init__() from openzwave.network import ZWaveNetwork from pydispatch import dispatcher diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index faaea30e0ee4d9..05e5951f921f2d 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -118,7 +118,6 @@ class ZWaveNodeEntity(ZWaveBaseEntity): def __init__(self, node, network): """Initialize node.""" - # pylint: disable=import-error super().__init__() from openzwave.network import ZWaveNetwork from pydispatch import dispatcher diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index f5f3d9e5c5bd17..804212d310a985 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -380,7 +380,6 @@ class ZWaveDiscoverySchema: @callback def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]: """Run discovery on ZWave node and return matching (primary) values.""" - # pylint: disable=too-many-nested-blocks for value in node.values.values(): for schema in DISCOVERY_SCHEMAS: # check manufacturer_id diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b54300faaa77c7..d45f2b945160bc 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -927,7 +927,6 @@ def __init_subclass__(cls, domain: Optional[str] = None, **kwargs: Any) -> None: @property def unique_id(self) -> Optional[str]: """Return unique ID if available.""" - # pylint: disable=no-member if not self.context: return None @@ -976,7 +975,7 @@ async def async_set_unique_id( Returns optionally existing config entry with same ID. """ if unique_id is None: - self.context["unique_id"] = None # pylint: disable=no-member + self.context["unique_id"] = None return None if raise_on_progress: @@ -984,7 +983,7 @@ async def async_set_unique_id( if progress["context"].get("unique_id") == unique_id: raise data_entry_flow.AbortFlow("already_in_progress") - self.context["unique_id"] = unique_id # pylint: disable=no-member + self.context["unique_id"] = unique_id # Abort discoveries done using the default discovery unique id if unique_id != DEFAULT_DISCOVERY_UNIQUE_ID: diff --git a/homeassistant/core.py b/homeassistant/core.py index 3483dc96069a34..b7bb645be3619d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -642,7 +642,6 @@ def as_dict(self) -> Dict[str, Any]: def __repr__(self) -> str: """Return the representation.""" - # pylint: disable=maybe-no-member if self.data: return f"" diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index e8235c9a23cc0e..15ad417c6ad70b 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -266,11 +266,10 @@ class FlowHandler: # Set by flow manager cur_step: Optional[Dict[str, str]] = None - # Ignore types, pylint workaround: https://github.com/PyCQA/pylint/issues/3167 + # Ignore types: https://github.com/PyCQA/pylint/issues/3167 flow_id: str = None # type: ignore hass: HomeAssistant = None # type: ignore handler: str = None # type: ignore - # Pylint workaround: https://github.com/PyCQA/pylint/issues/3167 # Ensure the attribute has a subscriptable, but immutable, default value. context: Dict = MappingProxyType({}) # type: ignore diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 84ba2cfa3485da..9a7b3da0f1d836 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -4,7 +4,7 @@ import attr if TYPE_CHECKING: - from .core import Context # noqa: F401 pylint: disable=unused-import + from .core import Context class HomeAssistantError(Exception): diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 2caf7fe46ab0ed..d860a8a33902be 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -62,7 +62,7 @@ def __init__( self.scan_interval = scan_interval self.entity_namespace = entity_namespace self.config_entry: Optional[config_entries.ConfigEntry] = None - self.entities: Dict[str, Entity] = {} # pylint: disable=used-before-assignment + self.entities: Dict[str, Entity] = {} self._tasks: List[asyncio.Future] = [] # Stop tracking tasks after setup is completed self._setup_complete = False diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index c86bd64d73e311..f18dda71529418 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -45,7 +45,7 @@ from .typing import UNDEFINED, HomeAssistantType, UndefinedType if TYPE_CHECKING: - from homeassistant.config_entries import ConfigEntry # noqa: F401 + from homeassistant.config_entries import ConfigEntry PATH_REGISTRY = "entity_registry.yaml" DATA_REGISTRY = "entity_registry" diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 932384493f3143..85595998a54206 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -62,7 +62,7 @@ from homeassistant.util.yaml.loader import JSON_TYPE if TYPE_CHECKING: - from homeassistant.helpers.entity import Entity # noqa + from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import EntityPlatform diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 2b82e19b8ce5b3..ce0f0598318c06 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -12,7 +12,7 @@ from .typing import HomeAssistantType if TYPE_CHECKING: - import astral # pylint: disable=unused-import + import astral DATA_LOCATION_CACHE = "astral_location_cache" diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7377120af40a20..8b3f6ec6e59ba5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -366,7 +366,7 @@ def async_render( try: render_result = compiled.render(kwargs) - except Exception as err: # pylint: disable=broad-except + except Exception as err: raise TemplateError(err) from err render_result = render_result.strip() diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 9a5fbdb180faf2..4a9162707fa1ab 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -195,6 +195,8 @@ def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: # pylint: disable=invalid-name + + def color_RGB_to_xy( iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None ) -> Tuple[float, float]: @@ -205,7 +207,6 @@ def color_RGB_to_xy( # Taken from: # http://www.developers.meethue.com/documentation/color-conversions-rgb-xy # License: Code is given as is. Use at your own risk and discretion. -# pylint: disable=invalid-name def color_RGB_to_xy_brightness( iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None ) -> Tuple[float, float, int]: diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py index 7b987d8eeb218f..4f10809ff21172 100644 --- a/homeassistant/util/ssl.py +++ b/homeassistant/util/ssl.py @@ -23,7 +23,7 @@ def server_context_modern() -> ssl.SSLContext: https://wiki.mozilla.org/Security/Server_Side_TLS Modern guidelines are followed. """ - context = ssl.SSLContext(ssl.PROTOCOL_TLS) # pylint: disable=no-member + context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.options |= ( ssl.OP_NO_SSLv2 @@ -53,7 +53,7 @@ def server_context_intermediate() -> ssl.SSLContext: https://wiki.mozilla.org/Security/Server_Side_TLS Intermediate guidelines are followed. """ - context = ssl.SSLContext(ssl.PROTOCOL_TLS) # pylint: disable=no-member + context = ssl.SSLContext(ssl.PROTOCOL_TLS) context.options |= ( ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_CIPHER_SERVER_PREFERENCE diff --git a/setup.cfg b/setup.cfg index 69cf931185e600..98a01278838bbe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ ignore = E203, D202, W504 +noqa-require-code = True [mypy] python_version = 3.8 diff --git a/tests/common.py b/tests/common.py index 0ae6f7ef5c7f84..df8f8fd2feaa9e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -18,7 +18,7 @@ from unittest.mock import AsyncMock, Mock, patch import uuid -from aiohttp.test_utils import unused_port as get_test_instance_port # noqa +from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401 from homeassistant import auth, config_entries, core as ha, loader from homeassistant.auth import ( diff --git a/tests/components/abode/conftest.py b/tests/components/abode/conftest.py index cc18597c56ec4b..21d64a644a9c2f 100644 --- a/tests/components/abode/conftest.py +++ b/tests/components/abode/conftest.py @@ -3,7 +3,7 @@ import pytest from tests.common import load_fixture -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index 514e8fa81f241a..2b50f83fd8d7e5 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -23,7 +23,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/alarm_control_panel/test_device_condition.py b/tests/components/alarm_control_panel/test_device_condition.py index 33f717e189304a..450393135af2c0 100644 --- a/tests/components/alarm_control_panel/test_device_condition.py +++ b/tests/components/alarm_control_panel/test_device_condition.py @@ -22,7 +22,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 56316026c9a0bd..ca6260863f1013 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -22,7 +22,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py index 0cae565f7bbb6d..ff3cf55ecd169f 100644 --- a/tests/components/arcam_fmj/test_device_trigger.py +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -12,7 +12,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/automation/conftest.py b/tests/components/automation/conftest.py index a967e0af19254d..438948e84754fb 100644 --- a/tests/components/automation/conftest.py +++ b/tests/components/automation/conftest.py @@ -1,3 +1,3 @@ """Conftest for automation tests.""" -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 diff --git a/tests/components/axis/conftest.py b/tests/components/axis/conftest.py index 33ea820f9cfefd..b3964663767f6f 100644 --- a/tests/components/axis/conftest.py +++ b/tests/components/axis/conftest.py @@ -1,2 +1,2 @@ """axis conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index f25b529d426fdb..d8c9e1ca894103 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -20,7 +20,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index fef109eb9d5437..9b50d52b785914 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -20,7 +20,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index 3b12c682f3feb0..c603c15c32b4dc 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -10,7 +10,7 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 def patch_product_identify(path=None, **kwargs): diff --git a/tests/components/bond/conftest.py b/tests/components/bond/conftest.py index 7ab1805cb73416..378c3340e2940d 100644 --- a/tests/components/bond/conftest.py +++ b/tests/components/bond/conftest.py @@ -1,2 +1,2 @@ """bond conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index 4084d37358eca1..dc956f0738ce8c 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 8e6d5829c415b5..009d29dab39368 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/climate/test_device_trigger.py b/tests/components/climate/test_device_trigger.py index 69bb4626e495c7..017385362d17bf 100644 --- a/tests/components/climate/test_device_trigger.py +++ b/tests/components/climate/test_device_trigger.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 00c89edeef0642..541cd3068d2a49 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -5,7 +5,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import config -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 async def test_get_device_config(hass, hass_client): diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index b2273d640de907..a123a2edb35cf9 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -4,7 +4,7 @@ from homeassistant.components.config import device_registry from tests.common import mock_device_registry -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index cad6074ff34806..5cec3d901e1180 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index b3098ceeca97a9..04415661515965 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -22,7 +22,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index e8bb3cdc8df762..e0f6d6d5fb9b71 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -22,7 +22,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index 4c45d3c912b9ce..5cf122213c4dd3 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,2 +1,2 @@ """deconz conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index b9d538588cdc49..20fd79e9e8cf24 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -19,7 +19,7 @@ from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration from tests.common import assert_lists_same, async_get_device_automations -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 SENSORS = { "1": { diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index 9830d94447128b..ec2d207a68be0b 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -5,7 +5,7 @@ from homeassistant.setup import async_setup_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/demo/conftest.py b/tests/components/demo/conftest.py index 666224fb737fff..13574c5018213a 100644 --- a/tests/components/demo/conftest.py +++ b/tests/components/demo/conftest.py @@ -1,2 +1,2 @@ """demo conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 83ec146b53edc8..a2f042bcd73761 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -13,7 +13,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index a187f21e954d51..2cd4aceeb0780c 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/device_tracker/test_device_trigger.py b/tests/components/device_tracker/test_device_trigger.py index 2f0ec14ec4baf6..0ec1ee67d3638d 100644 --- a/tests/components/device_tracker/test_device_trigger.py +++ b/tests/components/device_tracker/test_device_trigger.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 AWAY_LATITUDE = 32.881011 AWAY_LONGITUDE = -117.234758 diff --git a/tests/components/dynalite/conftest.py b/tests/components/dynalite/conftest.py index 187e4f9cbaac6b..59f109e7e47525 100644 --- a/tests/components/dynalite/conftest.py +++ b/tests/components/dynalite/conftest.py @@ -1,2 +1,2 @@ """dynalite conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/elgato/conftest.py b/tests/components/elgato/conftest.py index f2ac45155fb51f..fe86f26b535f3d 100644 --- a/tests/components/elgato/conftest.py +++ b/tests/components/elgato/conftest.py @@ -1,2 +1,2 @@ """elgato conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/everlights/conftest.py b/tests/components/everlights/conftest.py index 31915934b314bd..5009251b10201a 100644 --- a/tests/components/everlights/conftest.py +++ b/tests/components/everlights/conftest.py @@ -1,2 +1,2 @@ """everlights conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index d3a9aedcf9c443..491e6afab6a16f 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -14,7 +14,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/fan/test_device_condition.py b/tests/components/fan/test_device_condition.py index 6725587aeda3d2..8f11d963ed3d80 100644 --- a/tests/components/fan/test_device_condition.py +++ b/tests/components/fan/test_device_condition.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/fan/test_device_trigger.py b/tests/components/fan/test_device_trigger.py index d96f0a828f361a..59af7666a03c96 100644 --- a/tests/components/fan/test_device_trigger.py +++ b/tests/components/fan/test_device_trigger.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/geo_location/test_trigger.py b/tests/components/geo_location/test_trigger.py index ab984a2c309142..c547cb61230228 100644 --- a/tests/components/geo_location/test_trigger.py +++ b/tests/components/geo_location/test_trigger.py @@ -7,7 +7,7 @@ from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index abf2773d67e4b8..d6094a771bd14e 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -5,7 +5,7 @@ import pytest from homeassistant.components.google_assistant import helpers -from homeassistant.components.google_assistant.const import ( # noqa: F401 +from homeassistant.components.google_assistant.const import ( EVENT_COMMAND_RECEIVED, NOT_EXPOSE_LOCAL, ) diff --git a/tests/components/group/conftest.py b/tests/components/group/conftest.py index 6fe34aca91cae1..e26e98598e6e73 100644 --- a/tests/components/group/conftest.py +++ b/tests/components/group/conftest.py @@ -1,2 +1,2 @@ """group conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py index 18da5df13ea956..dcf6b64d9e2715 100644 --- a/tests/components/hassio/test_websocket_api.py +++ b/tests/components/hassio/test_websocket_api.py @@ -14,7 +14,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component -from . import mock_all # noqa +from . import mock_all # noqa: F401 from tests.common import async_mock_signal diff --git a/tests/components/homeassistant/triggers/conftest.py b/tests/components/homeassistant/triggers/conftest.py index 5c983ba698e5c2..77520a1bf689d9 100644 --- a/tests/components/homeassistant/triggers/conftest.py +++ b/tests/components/homeassistant/triggers/conftest.py @@ -1,3 +1,3 @@ """Conftest for HA triggers.""" -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 26adb25df21b76..4e095b1d2d9621 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -8,7 +8,7 @@ import homeassistant.util.dt as dt_util -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index 9de9a30f99f71a..ce771eac76857b 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -12,7 +12,7 @@ async_get_device_automations, async_mock_service, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 from tests.components.homekit_controller.common import setup_test_component diff --git a/tests/components/homematicip_cloud/conftest.py b/tests/components/homematicip_cloud/conftest.py index b05683d2361c97..aac7f60558ca0c 100644 --- a/tests/components/homematicip_cloud/conftest.py +++ b/tests/components/homematicip_cloud/conftest.py @@ -25,7 +25,7 @@ from .helper import AUTH_TOKEN, HAPID, HAPPIN, HomeFactory from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(name="mock_connection") diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index fc42babbb3561b..db45b9fcd4d31f 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -12,7 +12,7 @@ from homeassistant.components import hue from homeassistant.components.hue import sensor_base as hue_sensor_base -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/hue/test_device_trigger.py b/tests/components/hue/test_device_trigger.py index 178cd7b09f1b8b..5711c36da98a5c 100644 --- a/tests/components/hue/test_device_trigger.py +++ b/tests/components/hue/test_device_trigger.py @@ -15,7 +15,7 @@ async_mock_service, mock_device_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 REMOTES_RESPONSE = {"7": HUE_TAP_REMOTE_1, "8": HUE_DIMMER_REMOTE_1} diff --git a/tests/components/humidifier/test_device_action.py b/tests/components/humidifier/test_device_action.py index 93b97408c39af3..1bf1c110ec6847 100644 --- a/tests/components/humidifier/test_device_action.py +++ b/tests/components/humidifier/test_device_action.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index ad001d52ae0af6..d72d0e3b70e64d 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -17,7 +17,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index 7cd736b79f4179..0b6154a84df1b7 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -20,7 +20,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/hyperion/conftest.py b/tests/components/hyperion/conftest.py index 4eb59770fae22b..f971fa3c76728a 100644 --- a/tests/components/hyperion/conftest.py +++ b/tests/components/hyperion/conftest.py @@ -1,2 +1,2 @@ """hyperion conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/kodi/test_device_trigger.py b/tests/components/kodi/test_device_trigger.py index 0dd75b9c3577f6..1edf7da66045b9 100644 --- a/tests/components/kodi/test_device_trigger.py +++ b/tests/components/kodi/test_device_trigger.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index 63670d9bfab082..4760dfd1c5377a 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -21,7 +21,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 2a877478b1ec86..d529c82bfa5bd3 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index fad39898467578..1c9f6cf145407b 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/litejet/test_trigger.py b/tests/components/litejet/test_trigger.py index 216da9b54efd31..50d7fb407157d0 100644 --- a/tests/components/litejet/test_trigger.py +++ b/tests/components/litejet/test_trigger.py @@ -13,7 +13,7 @@ from . import async_init_integration from tests.common import async_fire_time_changed, async_mock_service -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/lock/test_device_action.py b/tests/components/lock/test_device_action.py index 91cab9cdaf4f58..7d484ae96aa378 100644 --- a/tests/components/lock/test_device_action.py +++ b/tests/components/lock/test_device_action.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/lock/test_device_condition.py b/tests/components/lock/test_device_condition.py index 949100daa552ae..b021ef2339188e 100644 --- a/tests/components/lock/test_device_condition.py +++ b/tests/components/lock/test_device_condition.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/lock/test_device_trigger.py b/tests/components/lock/test_device_trigger.py index 20674c483fde3f..272cf57bccd0ca 100644 --- a/tests/components/lock/test_device_trigger.py +++ b/tests/components/lock/test_device_trigger.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/media_player/test_device_condition.py b/tests/components/media_player/test_device_condition.py index c7668d748af6c6..63cdb1c55a7d2a 100644 --- a/tests/components/media_player/test_device_condition.py +++ b/tests/components/media_player/test_device_condition.py @@ -21,7 +21,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/media_player/test_device_trigger.py b/tests/components/media_player/test_device_trigger.py index 93d9127f8b8648..912edfc9c3cb83 100644 --- a/tests/components/media_player/test_device_trigger.py +++ b/tests/components/media_player/test_device_trigger.py @@ -21,7 +21,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/mochad/conftest.py b/tests/components/mochad/conftest.py index 9b095046d9c980..bd543eae94349e 100644 --- a/tests/components/mochad/conftest.py +++ b/tests/components/mochad/conftest.py @@ -1,2 +1,2 @@ """mochad conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/mqtt/conftest.py b/tests/components/mqtt/conftest.py index 5f0acc3e5c2539..6f70a4dbadb00c 100644 --- a/tests/components/mqtt/conftest.py +++ b/tests/components/mqtt/conftest.py @@ -1,2 +1,2 @@ """Test fixtures for mqtt component.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index 210dac19e0c62a..c1e012a90d6b71 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index d0a86e08655a40..70ee5e9327cdf5 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -8,7 +8,7 @@ from homeassistant.setup import async_setup_component from tests.common import async_fire_mqtt_message, async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/number/test_device_action.py b/tests/components/number/test_device_action.py index 21c79a9f7413f6..a981a331e7ead1 100644 --- a/tests/components/number/test_device_action.py +++ b/tests/components/number/test_device_action.py @@ -15,7 +15,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index a59388f118f675..1df365054d4a63 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -9,7 +9,7 @@ from .common import MQTTMessage from tests.common import MockConfigEntry, load_fixture -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(name="generic_data", scope="session") diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py index ebda40f13e58fb..e0ee10c6abb86d 100644 --- a/tests/components/philips_js/test_device_trigger.py +++ b/tests/components/philips_js/test_device_trigger.py @@ -10,7 +10,7 @@ async_get_device_automations, async_mock_service, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/remote/test_device_action.py b/tests/components/remote/test_device_action.py index 17165639e25270..7cd5a63298241f 100644 --- a/tests/components/remote/test_device_action.py +++ b/tests/components/remote/test_device_action.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/remote/test_device_condition.py b/tests/components/remote/test_device_condition.py index c6a2b3f0c52c03..12cf0e05493ccb 100644 --- a/tests/components/remote/test_device_condition.py +++ b/tests/components/remote/test_device_condition.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/remote/test_device_trigger.py b/tests/components/remote/test_device_trigger.py index eccf96c04f63b6..616c356936c010 100644 --- a/tests/components/remote/test_device_trigger.py +++ b/tests/components/remote/test_device_trigger.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/rflink/conftest.py b/tests/components/rflink/conftest.py index f33c8ab89dbbc8..dcaeb0a5e01e51 100644 --- a/tests/components/rflink/conftest.py +++ b/tests/components/rflink/conftest.py @@ -1,2 +1,2 @@ """rflink conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index ee695bee9dd0fb..06e37545d252b9 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -9,7 +9,7 @@ from homeassistant.util.dt import utcnow from tests.common import MockConfigEntry, async_fire_time_changed -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 def create_rfx_test_cfg(device="abcd", automatic_add=False, devices=None): diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index f2e05189dea285..cda662aab6491d 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -5,7 +5,7 @@ import requests_mock from tests.common import load_fixture -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture(name="requests_mock") diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index 5710fa04698500..6f6a7793981ffd 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -3,7 +3,7 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 async def test_search(hass): diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 9a023d6f5ade82..80c20cb8e2d0f8 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index d5755ac3288c3a..eb38060f0dd0b9 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -20,7 +20,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index b99309bea52597..d145e7644a9ce6 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -47,7 +47,7 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 COMPONENT_PREFIX = "homeassistant.components.smartthings." diff --git a/tests/components/sun/test_trigger.py b/tests/components/sun/test_trigger.py index a288150517dd2d..fd01daac5b7140 100644 --- a/tests/components/sun/test_trigger.py +++ b/tests/components/sun/test_trigger.py @@ -18,7 +18,7 @@ import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/components/switch/conftest.py b/tests/components/switch/conftest.py index d69757a9b1b929..11f1563b72387a 100644 --- a/tests/components/switch/conftest.py +++ b/tests/components/switch/conftest.py @@ -1,2 +1,2 @@ """switch conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index 4da401a215c7e6..2a98cd2fad49d4 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -16,7 +16,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index 67ba3e8e38e1a0..9273610dee9bbc 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 34817b687f8006..d958dd21911d57 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/tag/test_trigger.py b/tests/components/tag/test_trigger.py index 9a97d95e7d54c1..5563b5644a4f68 100644 --- a/tests/components/tag/test_trigger.py +++ b/tests/components/tag/test_trigger.py @@ -9,7 +9,7 @@ from homeassistant.setup import async_setup_component from tests.common import async_mock_service -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/tasmota/conftest.py b/tests/components/tasmota/conftest.py index 3c530a93d1ebbb..5c0b7d315f9e64 100644 --- a/tests/components/tasmota/conftest.py +++ b/tests/components/tasmota/conftest.py @@ -17,7 +17,7 @@ mock_device_registry, mock_registry, ) -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture diff --git a/tests/components/tasmota/test_device_trigger.py b/tests/components/tasmota/test_device_trigger.py index ec8744881c5f08..1fa1c629a337be 100644 --- a/tests/components/tasmota/test_device_trigger.py +++ b/tests/components/tasmota/test_device_trigger.py @@ -18,7 +18,7 @@ async_fire_mqtt_message, async_get_device_automations, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 async def test_get_triggers_btn(hass, device_reg, entity_reg, mqtt_mock, setup_tasmota): diff --git a/tests/components/template/conftest.py b/tests/components/template/conftest.py index 8e3491b160ab7c..0848200b35d8f4 100644 --- a/tests/components/template/conftest.py +++ b/tests/components/template/conftest.py @@ -1,2 +1,2 @@ """template conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 55311005201bf3..b40eee7cab3051 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -18,7 +18,7 @@ async_mock_service, mock_component, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/tplink/conftest.py b/tests/components/tplink/conftest.py index d1800513486f22..61b242c5d2e3e2 100644 --- a/tests/components/tplink/conftest.py +++ b/tests/components/tplink/conftest.py @@ -1,2 +1,2 @@ """tplink conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index 93675a9e4d1cf5..54a8625f23c139 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -5,7 +5,7 @@ from . import MOCK_GATEWAY_ID -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 # pylint: disable=protected-access diff --git a/tests/components/vacuum/test_device_action.py b/tests/components/vacuum/test_device_action.py index 3edeaba2a4115f..aa5dc1786f7029 100644 --- a/tests/components/vacuum/test_device_action.py +++ b/tests/components/vacuum/test_device_action.py @@ -14,7 +14,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/vacuum/test_device_condition.py b/tests/components/vacuum/test_device_condition.py index 3dc7a628741247..84a25d183b8512 100644 --- a/tests/components/vacuum/test_device_condition.py +++ b/tests/components/vacuum/test_device_condition.py @@ -19,7 +19,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/vacuum/test_device_trigger.py b/tests/components/vacuum/test_device_trigger.py index e3f615891e6cce..fd1918b8e69086 100644 --- a/tests/components/vacuum/test_device_trigger.py +++ b/tests/components/vacuum/test_device_trigger.py @@ -14,7 +14,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/vera/conftest.py b/tests/components/vera/conftest.py index da0272077487f2..e5d2dac1dbf2f9 100644 --- a/tests/components/vera/conftest.py +++ b/tests/components/vera/conftest.py @@ -5,7 +5,7 @@ from .common import ComponentFactory -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture() diff --git a/tests/components/water_heater/test_device_action.py b/tests/components/water_heater/test_device_action.py index 06bd43ec6544e1..060d9ead29fa4e 100644 --- a/tests/components/water_heater/test_device_action.py +++ b/tests/components/water_heater/test_device_action.py @@ -14,7 +14,7 @@ mock_device_registry, mock_registry, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/webhook/test_trigger.py b/tests/components/webhook/test_trigger.py index 2a22c330e14cad..4909c668dfe08d 100644 --- a/tests/components/webhook/test_trigger.py +++ b/tests/components/webhook/test_trigger.py @@ -6,7 +6,7 @@ from homeassistant.core import callback from homeassistant.setup import async_setup_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture(autouse=True) diff --git a/tests/components/wilight/conftest.py b/tests/components/wilight/conftest.py index a8fd13553dddc9..b20a7757e22b9b 100644 --- a/tests/components/wilight/conftest.py +++ b/tests/components/wilight/conftest.py @@ -1,2 +1,2 @@ """wilight conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/wled/conftest.py b/tests/components/wled/conftest.py index 31b71a92f198e7..7b8eb9cd50c26a 100644 --- a/tests/components/wled/conftest.py +++ b/tests/components/wled/conftest.py @@ -1,2 +1,2 @@ """wled conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/yeelight/conftest.py b/tests/components/yeelight/conftest.py index 3e65a60f374d41..f418e90e84840c 100644 --- a/tests/components/yeelight/conftest.py +++ b/tests/components/yeelight/conftest.py @@ -1,2 +1,2 @@ """yeelight conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/zerproc/conftest.py b/tests/components/zerproc/conftest.py index b4c35bebc71260..9d6bd9dea2388f 100644 --- a/tests/components/zerproc/conftest.py +++ b/tests/components/zerproc/conftest.py @@ -1,2 +1,2 @@ """zerproc conftest.""" -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 57241b9bb74437..b3ac4aff16e560 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -16,7 +16,7 @@ from .common import FakeDevice, FakeEndpoint, get_zha_gateway from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 FIXTURE_GRP_ID = 0x1001 FIXTURE_GRP_NAME = "fixture group" diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index 316d475f17f4a5..1160995e8d7bec 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -16,7 +16,7 @@ from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_coro -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 SHORT_PRESS = "remote_button_short_press" COMMAND = "command" diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 96ee5520e2aa40..ec947846801b89 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -19,7 +19,7 @@ async_get_device_automations, async_mock_service, ) -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 ON = 1 OFF = 0 diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index d7f5857b466c2c..52fbb55ba97502 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -7,7 +7,7 @@ from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_component -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @pytest.fixture diff --git a/tests/components/zwave/conftest.py b/tests/components/zwave/conftest.py index 13da12c67fff70..027d3a82ea2a89 100644 --- a/tests/components/zwave/conftest.py +++ b/tests/components/zwave/conftest.py @@ -5,7 +5,7 @@ from homeassistant.components.zwave import const -from tests.components.light.conftest import mock_light_profiles # noqa +from tests.components.light.conftest import mock_light_profiles # noqa: F401 from tests.mock.zwave import MockNetwork, MockNode, MockOption, MockValue From ab53b49d3f29e3bd0950d3d9ce9a76cf8a554787 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Tue, 2 Mar 2021 06:52:00 -0500 Subject: [PATCH 0934/1818] Clean up constants (#46948) * Clean up constants * clean up humidifier constants * fix tests * fix prometheus tests Co-authored-by: Tobias Sauerwein --- .../components/google_assistant/trait.py | 7 ++++--- homeassistant/components/humidifier/const.py | 2 -- .../components/humidifier/device_action.py | 7 ++++--- .../components/humidifier/device_condition.py | 5 +++-- homeassistant/components/humidifier/intent.py | 3 +-- .../components/humidifier/reproduce_state.py | 16 +++++++-------- .../components/input_select/__init__.py | 2 +- homeassistant/components/ipp/const.py | 1 - homeassistant/components/ipp/sensor.py | 3 +-- homeassistant/components/melcloud/sensor.py | 4 ++-- .../components/mobile_app/__init__.py | 3 +-- .../components/mobile_app/config_flow.py | 3 ++- homeassistant/components/mobile_app/const.py | 1 - .../components/mobile_app/device_tracker.py | 8 ++++++-- .../components/mobile_app/helpers.py | 8 ++++++-- .../components/mobile_app/http_api.py | 3 +-- .../components/mobile_app/webhook.py | 2 +- homeassistant/components/netatmo/const.py | 1 - homeassistant/components/ombi/__init__.py | 2 +- homeassistant/components/ombi/const.py | 1 - .../components/prometheus/__init__.py | 2 +- homeassistant/components/pushsafer/notify.py | 3 +-- homeassistant/components/remote/__init__.py | 2 +- homeassistant/components/sesame/lock.py | 2 +- tests/components/demo/test_humidifier.py | 2 +- .../google_assistant/test_google_assistant.py | 2 +- .../components/google_assistant/test_trait.py | 3 ++- .../humidifier/test_device_condition.py | 20 +++++++------------ .../humidifier/test_device_trigger.py | 8 ++++---- tests/components/humidifier/test_intent.py | 2 +- .../humidifier/test_reproduce_state.py | 9 +++++++-- 31 files changed, 69 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 8b0bde09010e7a..be7ceb98ad31a2 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -27,6 +27,7 @@ ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, CAST_APP_ID_HOMEASSISTANT, @@ -1424,8 +1425,8 @@ def query_attributes(self): elif self.state.domain == input_select.DOMAIN: mode_settings["option"] = self.state.state elif self.state.domain == humidifier.DOMAIN: - if humidifier.ATTR_MODE in attrs: - mode_settings["mode"] = attrs.get(humidifier.ATTR_MODE) + if ATTR_MODE in attrs: + mode_settings["mode"] = attrs.get(ATTR_MODE) elif self.state.domain == light.DOMAIN: if light.ATTR_EFFECT in attrs: mode_settings["effect"] = attrs.get(light.ATTR_EFFECT) @@ -1460,7 +1461,7 @@ async def execute(self, command, data, params, challenge): humidifier.DOMAIN, humidifier.SERVICE_SET_MODE, { - humidifier.ATTR_MODE: requested_mode, + ATTR_MODE: requested_mode, ATTR_ENTITY_ID: self.state.entity_id, }, blocking=True, diff --git a/homeassistant/components/humidifier/const.py b/homeassistant/components/humidifier/const.py index 7e70c51df28020..c25087701877dc 100644 --- a/homeassistant/components/humidifier/const.py +++ b/homeassistant/components/humidifier/const.py @@ -1,6 +1,4 @@ """Provides the constants needed for component.""" -from homeassistant.const import ATTR_MODE # noqa: F401 pylint: disable=unused-import - MODE_NORMAL = "normal" MODE_ECO = "eco" MODE_AWAY = "away" diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index 6bccd375207cc2..c702a7c2a2de31 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -6,6 +6,7 @@ from homeassistant.components.device_automation import toggle_entity from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, CONF_DEVICE_ID, CONF_DOMAIN, @@ -30,7 +31,7 @@ { vol.Required(CONF_TYPE): "set_mode", vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), - vol.Required(const.ATTR_MODE): cv.string, + vol.Required(ATTR_MODE): cv.string, } ) @@ -90,7 +91,7 @@ async def async_call_action_from_config( service_data[const.ATTR_HUMIDITY] = config[const.ATTR_HUMIDITY] elif config[CONF_TYPE] == "set_mode": service = const.SERVICE_SET_MODE - service_data[const.ATTR_MODE] = config[const.ATTR_MODE] + service_data[ATTR_MODE] = config[ATTR_MODE] else: return await toggle_entity.async_call_action_from_config( hass, config, variables, context, DOMAIN @@ -115,7 +116,7 @@ async def async_get_action_capabilities(hass, config): available_modes = state.attributes.get(const.ATTR_AVAILABLE_MODES, []) else: available_modes = [] - fields[vol.Required(const.ATTR_MODE)] = vol.In(available_modes) + fields[vol.Required(ATTR_MODE)] = vol.In(available_modes) else: return {} diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 714a51ab0161dc..86a049d838b730 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -6,6 +6,7 @@ from homeassistant.components.device_automation import toggle_entity from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, CONF_CONDITION, CONF_DEVICE_ID, @@ -28,7 +29,7 @@ { vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_TYPE): "is_mode", - vol.Required(const.ATTR_MODE): str, + vol.Required(ATTR_MODE): str, } ) @@ -72,7 +73,7 @@ def async_condition_from_config( config = CONDITION_SCHEMA(config) if config[CONF_TYPE] == "is_mode": - attribute = const.ATTR_MODE + attribute = ATTR_MODE else: return toggle_entity.async_condition_from_config(config) diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py index fafbb0a494a624..d9ecafbc537c46 100644 --- a/homeassistant/components/humidifier/intent.py +++ b/homeassistant/components/humidifier/intent.py @@ -1,7 +1,7 @@ """Intents for the humidifier integration.""" import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF +from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, STATE_OFF from homeassistant.core import HomeAssistant from homeassistant.helpers import intent import homeassistant.helpers.config_validation as cv @@ -9,7 +9,6 @@ from . import ( ATTR_AVAILABLE_MODES, ATTR_HUMIDITY, - ATTR_MODE, DOMAIN, SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, diff --git a/homeassistant/components/humidifier/reproduce_state.py b/homeassistant/components/humidifier/reproduce_state.py index e9b1777d63f99f..f6fff75203e894 100644 --- a/homeassistant/components/humidifier/reproduce_state.py +++ b/homeassistant/components/humidifier/reproduce_state.py @@ -3,17 +3,17 @@ import logging from typing import Any, Dict, Iterable, Optional -from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON +from homeassistant.const import ( + ATTR_MODE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from homeassistant.core import Context, State from homeassistant.helpers.typing import HomeAssistantType -from .const import ( - ATTR_HUMIDITY, - ATTR_MODE, - DOMAIN, - SERVICE_SET_HUMIDITY, - SERVICE_SET_MODE, -) +from .const import ATTR_HUMIDITY, DOMAIN, SERVICE_SET_HUMIDITY, SERVICE_SET_MODE _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index f6831dc3e887ba..5c10c33421aefe 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -8,6 +8,7 @@ from homeassistant.const import ( ATTR_EDITABLE, + ATTR_OPTION, CONF_ICON, CONF_ID, CONF_NAME, @@ -29,7 +30,6 @@ CONF_INITIAL = "initial" CONF_OPTIONS = "options" -ATTR_OPTION = "option" ATTR_OPTIONS = "options" ATTR_CYCLE = "cycle" diff --git a/homeassistant/components/ipp/const.py b/homeassistant/components/ipp/const.py index a5345f4145e09b..d482f2d73e4505 100644 --- a/homeassistant/components/ipp/const.py +++ b/homeassistant/components/ipp/const.py @@ -7,7 +7,6 @@ ATTR_COMMAND_SET = "command_set" ATTR_IDENTIFIERS = "identifiers" ATTR_INFO = "info" -ATTR_LOCATION = "location" ATTR_MANUFACTURER = "manufacturer" ATTR_MARKER_TYPE = "marker_type" ATTR_MARKER_LOW_LEVEL = "marker_low_level" diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 6991e6d19ea999..a278f6e8ef9fa0 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -3,7 +3,7 @@ from typing import Any, Callable, Dict, List, Optional from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE +from homeassistant.const import ATTR_LOCATION, DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow @@ -12,7 +12,6 @@ from .const import ( ATTR_COMMAND_SET, ATTR_INFO, - ATTR_LOCATION, ATTR_MARKER_HIGH_LEVEL, ATTR_MARKER_LOW_LEVEL, ATTR_MARKER_TYPE, diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index c96433f17dfd20..bd85d1b13bc54f 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -3,6 +3,8 @@ from pymelcloud.atw_device import Zone from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ICON, DEVICE_CLASS_TEMPERATURE, ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS, @@ -13,9 +15,7 @@ from .const import DOMAIN ATTR_MEASUREMENT_NAME = "measurement_name" -ATTR_ICON = "icon" ATTR_UNIT = "unit" -ATTR_DEVICE_CLASS = "device_class" ATTR_VALUE_FN = "value_fn" ATTR_ENABLED_FN = "enabled" diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 54fa3398ee2bcd..e1476270844251 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -6,12 +6,11 @@ async_register as webhook_register, async_unregister as webhook_unregister, ) -from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID from homeassistant.helpers import device_registry as dr, discovery from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, diff --git a/homeassistant/components/mobile_app/config_flow.py b/homeassistant/components/mobile_app/config_flow.py index 80b6c8db5e1487..752ef86d68dd0e 100644 --- a/homeassistant/components/mobile_app/config_flow.py +++ b/homeassistant/components/mobile_app/config_flow.py @@ -3,9 +3,10 @@ from homeassistant import config_entries from homeassistant.components import person +from homeassistant.const import ATTR_DEVICE_ID from homeassistant.helpers import entity_registry as er -from .const import ATTR_APP_ID, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN +from .const import ATTR_APP_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN @config_entries.HANDLERS.register(DOMAIN) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index b603e117c4c9f6..af828ce423e2a7 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -20,7 +20,6 @@ ATTR_APP_NAME = "app_name" ATTR_APP_VERSION = "app_version" ATTR_CONFIG_ENTRY_ID = "entry_id" -ATTR_DEVICE_ID = "device_id" ATTR_DEVICE_NAME = "device_name" ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index d2e987066ef79e..f0cd30074fa9ea 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -7,14 +7,18 @@ ) from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS -from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.const import ( + ATTR_BATTERY_LEVEL, + ATTR_DEVICE_ID, + ATTR_LATITUDE, + ATTR_LONGITUDE, +) from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity from .const import ( ATTR_ALTITUDE, ATTR_COURSE, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_SPEED, ATTR_VERTICAL_ACCURACY, diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index a9079be4f04834..fed322df464f5f 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -7,7 +7,12 @@ from nacl.encoding import Base64Encoder from nacl.secret import SecretBox -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_BAD_REQUEST, HTTP_OK +from homeassistant.const import ( + ATTR_DEVICE_ID, + CONTENT_TYPE_JSON, + HTTP_BAD_REQUEST, + HTTP_OK, +) from homeassistant.core import Context from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.typing import HomeAssistantType @@ -17,7 +22,6 @@ ATTR_APP_ID, ATTR_APP_NAME, ATTR_APP_VERSION, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index a5a96b83bc6e01..4bd8d0cd76b07f 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -9,7 +9,7 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator -from homeassistant.const import CONF_WEBHOOK_ID, HTTP_CREATED +from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID, HTTP_CREATED from homeassistant.helpers import config_validation as cv from homeassistant.util import slugify @@ -18,7 +18,6 @@ ATTR_APP_ID, ATTR_APP_NAME, ATTR_APP_VERSION, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 3044f2df21225e..a7b3f38a015b44 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -23,6 +23,7 @@ from homeassistant.components.sensor import DEVICE_CLASSES as SENSOR_CLASSES from homeassistant.components.zone.const import DOMAIN as ZONE_DOMAIN from homeassistant.const import ( + ATTR_DEVICE_ID, ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA, @@ -49,7 +50,6 @@ ATTR_APP_VERSION, ATTR_CAMERA_ENTITY_ID, ATTR_COURSE, - ATTR_DEVICE_ID, ATTR_DEVICE_NAME, ATTR_EVENT_DATA, ATTR_EVENT_TYPE, diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index baee3e4035c775..ab268b8703b056 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -72,7 +72,6 @@ DEFAULT_WEBHOOKS = False ATTR_PSEUDO = "pseudo" -ATTR_NAME = "name" ATTR_EVENT_TYPE = "event_type" ATTR_HEATING_POWER_REQUEST = "heating_power_request" ATTR_HOME_ID = "home_id" diff --git a/homeassistant/components/ombi/__init__.py b/homeassistant/components/ombi/__init__.py index dcd8f2641612b0..5db46658eb1589 100644 --- a/homeassistant/components/ombi/__init__.py +++ b/homeassistant/components/ombi/__init__.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant.const import ( + ATTR_NAME, CONF_API_KEY, CONF_HOST, CONF_PASSWORD, @@ -15,7 +16,6 @@ import homeassistant.helpers.config_validation as cv from .const import ( - ATTR_NAME, ATTR_SEASON, CONF_URLBASE, DEFAULT_PORT, diff --git a/homeassistant/components/ombi/const.py b/homeassistant/components/ombi/const.py index 42b58e7f50d631..784b46a99b7e0f 100644 --- a/homeassistant/components/ombi/const.py +++ b/homeassistant/components/ombi/const.py @@ -1,5 +1,4 @@ """Support for Ombi.""" -ATTR_NAME = "name" ATTR_SEASON = "season" CONF_URLBASE = "urlbase" diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index bd9a6e35276df6..bbab9af83e82d6 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -16,12 +16,12 @@ from homeassistant.components.humidifier.const import ( ATTR_AVAILABLE_MODES, ATTR_HUMIDITY, - ATTR_MODE, ) from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, + ATTR_MODE, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONTENT_TYPE_TEXT_PLAIN, diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py index 12735764b4ba5f..bec8540901033f 100644 --- a/homeassistant/components/pushsafer/notify.py +++ b/homeassistant/components/pushsafer/notify.py @@ -15,7 +15,7 @@ PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import HTTP_OK +from homeassistant.const import ATTR_ICON, HTTP_OK import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -28,7 +28,6 @@ # Top level attributes in 'data' ATTR_SOUND = "sound" ATTR_VIBRATION = "vibration" -ATTR_ICON = "icon" ATTR_ICONCOLOR = "iconcolor" ATTR_URL = "url" ATTR_URLTITLE = "urltitle" diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 44a318988b2e24..b3b84669ed1704 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -8,6 +8,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_COMMAND, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -29,7 +30,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_ACTIVITY = "activity" -ATTR_COMMAND = "command" ATTR_COMMAND_TYPE = "command_type" ATTR_DEVICE = "device" ATTR_NUM_REPEATS = "num_repeats" diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index 0ad3d87a1715f0..9c86c2622353b9 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -7,6 +7,7 @@ from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, + ATTR_DEVICE_ID, CONF_API_KEY, STATE_LOCKED, STATE_UNLOCKED, @@ -14,7 +15,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -ATTR_DEVICE_ID = "device_id" ATTR_SERIAL_NO = "serial" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_API_KEY): cv.string}) diff --git a/tests/components/demo/test_humidifier.py b/tests/components/demo/test_humidifier.py index ba2bd60f8f22fe..cd400f983471d9 100644 --- a/tests/components/demo/test_humidifier.py +++ b/tests/components/demo/test_humidifier.py @@ -7,7 +7,6 @@ ATTR_HUMIDITY, ATTR_MAX_HUMIDITY, ATTR_MIN_HUMIDITY, - ATTR_MODE, DOMAIN, MODE_AWAY, MODE_ECO, @@ -16,6 +15,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index cd268a5c2d9640..bc9195264d9ab1 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -473,4 +473,4 @@ async def test_execute_request(hass_fixture, assistant_client, auth_header): assert dehumidifier.attributes.get(humidifier.ATTR_HUMIDITY) == 45 hygrostat = hass_fixture.states.get("humidifier.hygrostat") - assert hygrostat.attributes.get(humidifier.ATTR_MODE) == "eco" + assert hygrostat.attributes.get(const.ATTR_MODE) == "eco" diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index ba1890205130d3..43e9c30f91a8ba 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -31,6 +31,7 @@ ATTR_ASSUMED_STATE, ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, SERVICE_TURN_OFF, @@ -1778,7 +1779,7 @@ async def test_modes_humidifier(hass): humidifier.ATTR_MIN_HUMIDITY: 30, humidifier.ATTR_MAX_HUMIDITY: 99, humidifier.ATTR_HUMIDITY: 50, - humidifier.ATTR_MODE: humidifier.MODE_AUTO, + ATTR_MODE: humidifier.MODE_AUTO, }, ), BASIC_CONFIG, diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index d72d0e3b70e64d..8b35655223334b 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -4,7 +4,7 @@ import homeassistant.components.automation as automation from homeassistant.components.humidifier import DOMAIN, const, device_condition -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import ATTR_MODE, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv, device_registry from homeassistant.setup import async_setup_component @@ -51,7 +51,7 @@ async def test_get_conditions(hass, device_reg, entity_reg): f"{DOMAIN}.test_5678", STATE_ON, { - const.ATTR_MODE: const.MODE_AWAY, + ATTR_MODE: const.MODE_AWAY, const.ATTR_AVAILABLE_MODES: [const.MODE_HOME, const.MODE_AWAY], }, ) @@ -98,7 +98,7 @@ async def test_get_conditions_toggle_only(hass, device_reg, entity_reg): f"{DOMAIN}.test_5678", STATE_ON, { - const.ATTR_MODE: const.MODE_AWAY, + ATTR_MODE: const.MODE_AWAY, const.ATTR_AVAILABLE_MODES: [const.MODE_HOME, const.MODE_AWAY], }, ) @@ -127,9 +127,7 @@ async def test_get_conditions_toggle_only(hass, device_reg, entity_reg): async def test_if_state(hass, calls): """Test for turn_on and turn_off conditions.""" - hass.states.async_set( - "humidifier.entity", STATE_ON, {const.ATTR_MODE: const.MODE_AWAY} - ) + hass.states.async_set("humidifier.entity", STATE_ON, {ATTR_MODE: const.MODE_AWAY}) assert await async_setup_component( hass, @@ -213,9 +211,7 @@ async def test_if_state(hass, calls): assert len(calls) == 2 assert calls[1].data["some"] == "is_off event - test_event2" - hass.states.async_set( - "humidifier.entity", STATE_ON, {const.ATTR_MODE: const.MODE_AWAY} - ) + hass.states.async_set("humidifier.entity", STATE_ON, {ATTR_MODE: const.MODE_AWAY}) hass.bus.async_fire("test_event3") await hass.async_block_till_done() @@ -223,9 +219,7 @@ async def test_if_state(hass, calls): assert len(calls) == 3 assert calls[2].data["some"] == "is_mode - event - test_event3" - hass.states.async_set( - "humidifier.entity", STATE_ON, {const.ATTR_MODE: const.MODE_HOME} - ) + hass.states.async_set("humidifier.entity", STATE_ON, {ATTR_MODE: const.MODE_HOME}) # Should not fire hass.bus.async_fire("test_event3") @@ -239,7 +233,7 @@ async def test_capabilities(hass): "humidifier.entity", STATE_ON, { - const.ATTR_MODE: const.MODE_AWAY, + ATTR_MODE: const.MODE_AWAY, const.ATTR_AVAILABLE_MODES: [const.MODE_HOME, const.MODE_AWAY], }, ) diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index 0b6154a84df1b7..12918684df76c0 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -6,7 +6,7 @@ import homeassistant.components.automation as automation from homeassistant.components.humidifier import DOMAIN, const, device_trigger -from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON +from homeassistant.const import ATTR_MODE, ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv, device_registry from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -56,7 +56,7 @@ async def test_get_triggers(hass, device_reg, entity_reg): STATE_ON, { const.ATTR_HUMIDITY: 23, - const.ATTR_MODE: "home", + ATTR_MODE: "home", const.ATTR_AVAILABLE_MODES: ["home", "away"], ATTR_SUPPORTED_FEATURES: 1, }, @@ -95,7 +95,7 @@ async def test_if_fires_on_state_change(hass, calls): STATE_ON, { const.ATTR_HUMIDITY: 23, - const.ATTR_MODE: "home", + ATTR_MODE: "home", const.ATTR_AVAILABLE_MODES: ["home", "away"], ATTR_SUPPORTED_FEATURES: 1, }, @@ -243,7 +243,7 @@ async def test_invalid_config(hass, calls): STATE_ON, { const.ATTR_HUMIDITY: 23, - const.ATTR_MODE: "home", + ATTR_MODE: "home", const.ATTR_AVAILABLE_MODES: ["home", "away"], ATTR_SUPPORTED_FEATURES: 1, }, diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py index 18c5b632aa6b79..66ff62872f30e7 100644 --- a/tests/components/humidifier/test_intent.py +++ b/tests/components/humidifier/test_intent.py @@ -2,7 +2,6 @@ from homeassistant.components.humidifier import ( ATTR_AVAILABLE_MODES, ATTR_HUMIDITY, - ATTR_MODE, DOMAIN, SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, @@ -10,6 +9,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON, STATE_OFF, diff --git a/tests/components/humidifier/test_reproduce_state.py b/tests/components/humidifier/test_reproduce_state.py index 8c1f69353a089c..15b797c66a864c 100644 --- a/tests/components/humidifier/test_reproduce_state.py +++ b/tests/components/humidifier/test_reproduce_state.py @@ -4,7 +4,6 @@ from homeassistant.components.humidifier.const import ( ATTR_HUMIDITY, - ATTR_MODE, DOMAIN, MODE_AWAY, MODE_ECO, @@ -13,7 +12,13 @@ SERVICE_SET_MODE, ) from homeassistant.components.humidifier.reproduce_state import async_reproduce_states -from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, STATE_ON +from homeassistant.const import ( + ATTR_MODE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) from homeassistant.core import Context, State from tests.common import async_mock_service From 6b9abfc2c647df8fab1326341c51e84f6418abf2 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 13:37:33 +0100 Subject: [PATCH 0935/1818] Add init test to Freebox (#46998) * Add init test to Freebox * Review : more readable conftest * Expect 2 blank lines between defs * Review : Not I/O in the event loop * Fix test_setup test * remove useless const * Review : mock setup methods * Add service test * Add import test --- .coveragerc | 1 - homeassistant/components/freebox/__init__.py | 5 +- homeassistant/components/freebox/const.py | 1 + homeassistant/components/freebox/router.py | 23 +- tests/components/freebox/conftest.py | 32 +- tests/components/freebox/const.py | 376 +++++++++++++++++++ tests/components/freebox/test_config_flow.py | 92 +++-- tests/components/freebox/test_init.py | 119 ++++++ 8 files changed, 585 insertions(+), 64 deletions(-) create mode 100644 tests/components/freebox/const.py create mode 100644 tests/components/freebox/test_init.py diff --git a/.coveragerc b/.coveragerc index 281c8d5be83f18..36d4399466fcc9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -314,7 +314,6 @@ omit = homeassistant/components/foscam/camera.py homeassistant/components/foursquare/* homeassistant/components/free_mobile/notify.py - homeassistant/components/freebox/__init__.py homeassistant/components/freebox/device_tracker.py homeassistant/components/freebox/router.py homeassistant/components/freebox/sensor.py diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index f36a2303b6d7c1..c6c98e6c2dfb10 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -9,7 +9,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, PLATFORMS +from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT from .router import FreeboxRouter _LOGGER = logging.getLogger(__name__) @@ -55,7 +55,7 @@ async def async_reboot(call): """Handle reboot service call.""" await router.reboot() - hass.services.async_register(DOMAIN, "reboot", async_reboot) + hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot) async def async_close_connection(event): """Close Freebox connection on HA Stop.""" @@ -79,5 +79,6 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): if unload_ok: router = hass.data[DOMAIN].pop(entry.unique_id) await router.close() + hass.services.async_remove(DOMAIN, SERVICE_REBOOT) return unload_ok diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index d0ac63fa9bb957..e47211f092635b 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -8,6 +8,7 @@ ) DOMAIN = "freebox" +SERVICE_REBOOT = "reboot" APP_DESC = { "app_id": "hass", diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 2511280f71930e..623c1fcc564d3f 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,6 +1,7 @@ """Represent the Freebox router and its devices and sensors.""" from datetime import datetime, timedelta import logging +import os from pathlib import Path from typing import Any, Dict, List, Optional @@ -31,6 +32,18 @@ SCAN_INTERVAL = timedelta(seconds=30) +async def get_api(hass: HomeAssistantType, host: str) -> Freepybox: + """Get the Freebox API.""" + freebox_path = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path + + if not os.path.exists(freebox_path): + await hass.async_add_executor_job(os.makedirs, freebox_path) + + token_file = Path(f"{freebox_path}/{slugify(host)}.conf") + + return Freepybox(APP_DESC, token_file, API_VERSION) + + class FreeboxRouter: """Representation of a Freebox router.""" @@ -188,13 +201,3 @@ def sensors(self) -> Dict[str, Any]: def wifi(self) -> Wifi: """Return the wifi.""" return self._api.wifi - - -async def get_api(hass: HomeAssistantType, host: str) -> Freepybox: - """Get the Freebox API.""" - freebox_path = Path(hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path) - freebox_path.mkdir(exist_ok=True) - - token_file = Path(f"{freebox_path}/{slugify(host)}.conf") - - return Freepybox(APP_DESC, token_file, API_VERSION) diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index e813469cbbfb4d..3220552b6cf9d4 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -1,11 +1,41 @@ """Test helpers for Freebox.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest +from .const import ( + DATA_CALL_GET_CALLS_LOG, + DATA_CONNECTION_GET_STATUS, + DATA_LAN_GET_HOSTS_LIST, + DATA_STORAGE_GET_DISKS, + DATA_SYSTEM_GET_CONFIG, + WIFI_GET_GLOBAL_CONFIG, +) + @pytest.fixture(autouse=True) def mock_path(): """Mock path lib.""" with patch("homeassistant.components.freebox.router.Path"): yield + + +@pytest.fixture(name="router") +def mock_router(): + """Mock a successful connection.""" + with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: + instance = service_mock.return_value + instance.open = AsyncMock() + instance.system.get_config = AsyncMock(return_value=DATA_SYSTEM_GET_CONFIG) + # sensor + instance.call.get_calls_log = AsyncMock(return_value=DATA_CALL_GET_CALLS_LOG) + instance.storage.get_disks = AsyncMock(return_value=DATA_STORAGE_GET_DISKS) + instance.connection.get_status = AsyncMock( + return_value=DATA_CONNECTION_GET_STATUS + ) + # switch + instance.wifi.get_global_config = AsyncMock(return_value=WIFI_GET_GLOBAL_CONFIG) + # device_tracker + instance.lan.get_hosts_list = AsyncMock(return_value=DATA_LAN_GET_HOSTS_LIST) + instance.close = AsyncMock() + yield service_mock diff --git a/tests/components/freebox/const.py b/tests/components/freebox/const.py new file mode 100644 index 00000000000000..cc3d720d7ef385 --- /dev/null +++ b/tests/components/freebox/const.py @@ -0,0 +1,376 @@ +"""Test constants.""" + +MOCK_HOST = "myrouter.freeboxos.fr" +MOCK_PORT = 1234 + +# router +DATA_SYSTEM_GET_CONFIG = { + "mac": "68:A3:78:00:00:00", + "model_info": { + "has_ext_telephony": True, + "has_speakers_jack": True, + "wifi_type": "2d4_5g", + "pretty_name": "Freebox Server (r2)", + "customer_hdd_slots": 0, + "name": "fbxgw-r2/full", + "has_speakers": True, + "internal_hdd_size": 250, + "has_femtocell_exp": True, + "has_internal_hdd": True, + "has_dect": True, + }, + "fans": [{"id": "fan0_speed", "name": "Ventilateur 1", "value": 2130}], + "sensors": [ + {"id": "temp_hdd", "name": "Disque dur", "value": 40}, + {"id": "temp_sw", "name": "Température Switch", "value": 50}, + {"id": "temp_cpum", "name": "Température CPU M", "value": 60}, + {"id": "temp_cpub", "name": "Température CPU B", "value": 56}, + ], + "board_name": "fbxgw2r", + "disk_status": "active", + "uptime": "156 jours 19 heures 56 minutes 16 secondes", + "uptime_val": 13550176, + "user_main_storage": "Disque dur", + "box_authenticated": True, + "serial": "762601T190510709", + "firmware_version": "4.2.5", +} + +# sensors +DATA_CONNECTION_GET_STATUS = { + "type": "ethernet", + "rate_down": 198900, + "bytes_up": 12035728872949, + "ipv4_port_range": [0, 65535], + "rate_up": 1440000, + "bandwidth_up": 700000000, + "ipv6": "2a01:e35:ffff:ffff::1", + "bandwidth_down": 1000000000, + "media": "ftth", + "state": "up", + "bytes_down": 2355966141297, + "ipv4": "82.67.00.00", +} + +DATA_CALL_GET_CALLS_LOG = [ + { + "number": "0988290475", + "type": "missed", + "id": 94, + "duration": 15, + "datetime": 1613752718, + "contact_id": 0, + "line_id": 0, + "name": "0988290475", + "new": True, + }, + { + "number": "0367250217", + "type": "missed", + "id": 93, + "duration": 25, + "datetime": 1613662328, + "contact_id": 0, + "line_id": 0, + "name": "0367250217", + "new": True, + }, + { + "number": "0184726018", + "type": "missed", + "id": 92, + "duration": 25, + "datetime": 1613225098, + "contact_id": 0, + "line_id": 0, + "name": "0184726018", + "new": True, + }, +] + +DATA_STORAGE_GET_DISKS = [ + { + "idle_duration": 0, + "read_error_requests": 0, + "read_requests": 110, + "spinning": True, + # "table_type": "ms-dos", API returns without dash, but codespell isn't agree + "firmware": "SC1D", + "type": "internal", + "idle": False, + "connector": 0, + "id": 0, + "write_error_requests": 0, + "state": "enabled", + "write_requests": 2708929, + "total_bytes": 250050000000, + "model": "ST9250311CS", + "active_duration": 0, + "temp": 40, + "serial": "6VCQY907", + "partitions": [ + { + "fstype": "ext4", + "total_bytes": 244950000000, + "label": "Disque dur", + "id": 2, + "internal": True, + "fsck_result": "no_run_yet", + "state": "mounted", + "disk_id": 0, + "free_bytes": 227390000000, + "used_bytes": 5090000000, + "path": "L0Rpc3F1ZSBkdXI=", + } + ], + } +] + +# switch +WIFI_GET_GLOBAL_CONFIG = {"enabled": True, "mac_filter_state": "disabled"} + +# device_tracker +DATA_LAN_GET_HOSTS_LIST = [ + { + "l2ident": {"id": "8C:97:EA:00:00:00", "type": "mac_address"}, + "active": True, + "persistent": False, + "names": [ + {"name": "d633d0c8-958c-43cc-e807-d881b076924b", "source": "mdns"}, + {"name": "Freebox Player POP", "source": "mdns_srv"}, + ], + "vendor_name": "Freebox SAS", + "host_type": "smartphone", + "interface": "pub", + "id": "ether-8c:97:ea:00:00:00", + "last_time_reachable": 1614107652, + "primary_name_manual": False, + "l3connectivities": [ + { + "addr": "192.168.1.180", + "active": True, + "reachable": True, + "last_activity": 1614107614, + "af": "ipv4", + "last_time_reachable": 1614104242, + }, + { + "addr": "fe80::dcef:dbba:6604:31d1", + "active": True, + "reachable": True, + "last_activity": 1614107645, + "af": "ipv6", + "last_time_reachable": 1614107645, + }, + { + "addr": "2a01:e34:eda1:eb40:8102:4704:7ce0:2ace", + "active": False, + "reachable": False, + "last_activity": 1611574428, + "af": "ipv6", + "last_time_reachable": 1611574428, + }, + { + "addr": "2a01:e34:eda1:eb40:c8e5:c524:c96d:5f5e", + "active": False, + "reachable": False, + "last_activity": 1612475101, + "af": "ipv6", + "last_time_reachable": 1612475101, + }, + { + "addr": "2a01:e34:eda1:eb40:583a:49df:1df0:c2df", + "active": True, + "reachable": True, + "last_activity": 1614107652, + "af": "ipv6", + "last_time_reachable": 1614107652, + }, + { + "addr": "2a01:e34:eda1:eb40:147e:3569:86ab:6aaa", + "active": False, + "reachable": False, + "last_activity": 1612486752, + "af": "ipv6", + "last_time_reachable": 1612486752, + }, + ], + "default_name": "Freebox Player POP", + "model": "fbx8am", + "reachable": True, + "last_activity": 1614107652, + "primary_name": "Freebox Player POP", + }, + { + "l2ident": {"id": "DE:00:B0:00:00:00", "type": "mac_address"}, + "active": False, + "persistent": False, + "vendor_name": "", + "host_type": "workstation", + "interface": "pub", + "id": "ether-de:00:b0:00:00:00", + "last_time_reachable": 1607125599, + "primary_name_manual": False, + "default_name": "", + "l3connectivities": [ + { + "addr": "192.168.1.181", + "active": False, + "reachable": False, + "last_activity": 1607125599, + "af": "ipv4", + "last_time_reachable": 1607125599, + }, + { + "addr": "192.168.1.182", + "active": False, + "reachable": False, + "last_activity": 1605958758, + "af": "ipv4", + "last_time_reachable": 1605958758, + }, + { + "addr": "2a01:e34:eda1:eb40:dc00:b0ff:fedf:e30", + "active": False, + "reachable": False, + "last_activity": 1607125594, + "af": "ipv6", + "last_time_reachable": 1607125594, + }, + ], + "reachable": False, + "last_activity": 1607125599, + "primary_name": "", + }, + { + "l2ident": {"id": "DC:00:B0:00:00:00", "type": "mac_address"}, + "active": True, + "persistent": False, + "names": [ + {"name": "Repeteur-Wifi-Freebox", "source": "mdns"}, + {"name": "Repeteur Wifi Freebox", "source": "mdns_srv"}, + ], + "vendor_name": "", + "host_type": "freebox_wifi", + "interface": "pub", + "id": "ether-dc:00:b0:00:00:00", + "last_time_reachable": 1614107678, + "primary_name_manual": False, + "l3connectivities": [ + { + "addr": "192.168.1.145", + "active": True, + "reachable": True, + "last_activity": 1614107678, + "af": "ipv4", + "last_time_reachable": 1614107678, + }, + { + "addr": "fe80::de00:b0ff:fe52:6ef6", + "active": True, + "reachable": True, + "last_activity": 1614107608, + "af": "ipv6", + "last_time_reachable": 1614107603, + }, + { + "addr": "2a01:e34:eda1:eb40:de00:b0ff:fe52:6ef6", + "active": True, + "reachable": True, + "last_activity": 1614107618, + "af": "ipv6", + "last_time_reachable": 1614107618, + }, + ], + "default_name": "Repeteur Wifi Freebox", + "model": "fbxwmr", + "reachable": True, + "last_activity": 1614107678, + "primary_name": "Repeteur Wifi Freebox", + }, + { + "l2ident": {"id": "5E:65:55:00:00:00", "type": "mac_address"}, + "active": False, + "persistent": False, + "names": [ + {"name": "iPhoneofQuentin", "source": "dhcp"}, + {"name": "iPhone-of-Quentin", "source": "mdns"}, + ], + "vendor_name": "", + "host_type": "smartphone", + "interface": "pub", + "id": "ether-5e:65:55:00:00:00", + "last_time_reachable": 1612611982, + "primary_name_manual": False, + "default_name": "iPhonedeQuentin", + "l3connectivities": [ + { + "addr": "192.168.1.148", + "active": False, + "reachable": False, + "last_activity": 1612611973, + "af": "ipv4", + "last_time_reachable": 1612611973, + }, + { + "addr": "fe80::14ca:6c30:938b:e281", + "active": False, + "reachable": False, + "last_activity": 1609693223, + "af": "ipv6", + "last_time_reachable": 1609693223, + }, + { + "addr": "fe80::1c90:2b94:1ba2:bd8b", + "active": False, + "reachable": False, + "last_activity": 1610797303, + "af": "ipv6", + "last_time_reachable": 1610797303, + }, + { + "addr": "fe80::8c8:e58b:838e:6785", + "active": False, + "reachable": False, + "last_activity": 1612611951, + "af": "ipv6", + "last_time_reachable": 1612611946, + }, + { + "addr": "2a01:e34:eda1:eb40:f0e7:e198:3a69:58", + "active": False, + "reachable": False, + "last_activity": 1609693245, + "af": "ipv6", + "last_time_reachable": 1609693245, + }, + { + "addr": "2a01:e34:eda1:eb40:1dc4:c6f8:aa20:c83b", + "active": False, + "reachable": False, + "last_activity": 1610797176, + "af": "ipv6", + "last_time_reachable": 1610797176, + }, + { + "addr": "2a01:e34:eda1:eb40:6cf6:5811:1770:c662", + "active": False, + "reachable": False, + "last_activity": 1612611982, + "af": "ipv6", + "last_time_reachable": 1612611982, + }, + { + "addr": "2a01:e34:eda1:eb40:438:9b2c:4f8f:f48a", + "active": False, + "reachable": False, + "last_activity": 1612611946, + "af": "ipv6", + "last_time_reachable": 1612611946, + }, + ], + "reachable": False, + "last_activity": 1612611982, + "primary_name": "iPhoneofQuentin", + }, +] diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 5f3aace94652e3..565387d3fb4895 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -1,22 +1,21 @@ """Tests for the Freebox config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import Mock, patch from freebox_api.exceptions import ( AuthorizationError, HttpRequestError, InvalidTokenError, ) -import pytest from homeassistant import data_entry_flow from homeassistant.components.freebox.const import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers.typing import HomeAssistantType -from tests.common import MockConfigEntry +from .const import MOCK_HOST, MOCK_PORT -HOST = "myrouter.freeboxos.fr" -PORT = 1234 +from tests.common import MockConfigEntry MOCK_ZEROCONF_DATA = { "host": "192.168.0.254", @@ -30,33 +29,15 @@ "api_base_url": "/api/", "uid": "b15ab20debb399f95001a9ca207d2777", "https_available": "1", - "https_port": f"{PORT}", + "https_port": f"{MOCK_PORT}", "box_model": "fbxgw-r2/full", "box_model_name": "Freebox Server (r2)", - "api_domain": HOST, + "api_domain": MOCK_HOST, }, } -@pytest.fixture(name="connect") -def mock_controller_connect(): - """Mock a successful connection.""" - with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: - service_mock.return_value.open = AsyncMock() - service_mock.return_value.system.get_config = AsyncMock( - return_value={ - "mac": "abcd", - "model_info": {"pretty_name": "Pretty Model"}, - "firmware_version": "123", - } - ) - service_mock.return_value.lan.get_hosts_list = AsyncMock() - service_mock.return_value.connection.get_status = AsyncMock() - service_mock.return_value.close = AsyncMock() - yield service_mock - - -async def test_user(hass): +async def test_user(hass: HomeAssistantType): """Test user config.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -68,24 +49,24 @@ async def test_user(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" -async def test_import(hass): +async def test_import(hass: HomeAssistantType): """Test import step.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" -async def test_zeroconf(hass): +async def test_zeroconf(hass: HomeAssistantType): """Test zeroconf step.""" result = await hass.config_entries.flow.async_init( DOMAIN, @@ -96,53 +77,64 @@ async def test_zeroconf(hass): assert result["step_id"] == "link" -async def test_link(hass, connect): +async def test_link(hass: HomeAssistantType, router: Mock): """Test linking.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, - ) + with patch( + "homeassistant.components.freebox.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.freebox.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == MOCK_HOST + assert result["title"] == MOCK_HOST + assert result["data"][CONF_HOST] == MOCK_HOST + assert result["data"][CONF_PORT] == MOCK_PORT - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == HOST - assert result["title"] == HOST - assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_PORT] == PORT + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 -async def test_abort_if_already_setup(hass): +async def test_abort_if_already_setup(hass: HomeAssistantType): """Test we abort if component is already setup.""" MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: HOST, CONF_PORT: PORT}, unique_id=HOST + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + unique_id=MOCK_HOST, ).add_to_hass(hass) - # Should fail, same HOST (import) + # Should fail, same MOCK_HOST (import) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" - # Should fail, same HOST (flow) + # Should fail, same MOCK_HOST (flow) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" -async def test_on_link_failed(hass): +async def test_on_link_failed(hass: HomeAssistantType): """Test when we have errors during linking the router.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_PORT: PORT}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, ) with patch( diff --git a/tests/components/freebox/test_init.py b/tests/components/freebox/test_init.py new file mode 100644 index 00000000000000..aae5f911e10797 --- /dev/null +++ b/tests/components/freebox/test_init.py @@ -0,0 +1,119 @@ +"""Tests for the Freebox config flow.""" +from unittest.mock import Mock, patch + +from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN +from homeassistant.components.freebox.const import DOMAIN as DOMAIN, SERVICE_REBOOT +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.setup import async_setup_component + +from .const import MOCK_HOST, MOCK_PORT + +from tests.common import MockConfigEntry + + +async def test_setup(hass: HomeAssistantType, router: Mock): + """Test setup of integration.""" + 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 + + assert hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + with patch( + "homeassistant.components.freebox.router.FreeboxRouter.reboot" + ) as mock_service: + await hass.services.async_call( + DOMAIN, + SERVICE_REBOOT, + blocking=True, + ) + await hass.async_block_till_done() + mock_service.assert_called_once() + + +async def test_setup_import(hass: HomeAssistantType, router: Mock): + """Test setup of integration from import.""" + await async_setup_component(hass, "persistent_notification", {}) + + 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, {DOMAIN: {CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}} + ) + await hass.async_block_till_done() + assert hass.config_entries.async_entries() == [entry] + + assert router.call_count == 1 + assert router().open.call_count == 1 + + assert hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + +async def test_unload_remove(hass: HomeAssistantType, router: Mock): + """Test unload and remove of integration.""" + entity_id_dt = f"{DT_DOMAIN}.freebox_server_r2" + entity_id_sensor = f"{SENSOR_DOMAIN}.freebox_download_speed" + entity_id_switch = f"{SWITCH_DOMAIN}.freebox_wifi" + + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) + entry.add_to_hass(hass) + + config_entries = hass.config_entries.async_entries(DOMAIN) + assert len(config_entries) == 1 + assert entry is config_entries[0] + + assert await async_setup_component(hass, DOMAIN, {}) is True + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor + state_switch = hass.states.get(entity_id_switch) + assert state_switch + + await hass.config_entries.async_unload(entry.entry_id) + + assert entry.state == ENTRY_STATE_NOT_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt.state == STATE_UNAVAILABLE + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor.state == STATE_UNAVAILABLE + state_switch = hass.states.get(entity_id_switch) + assert state_switch.state == STATE_UNAVAILABLE + + assert router().close.call_count == 1 + assert not hass.services.has_service(DOMAIN, SERVICE_REBOOT) + + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert router().close.call_count == 1 + assert entry.state == ENTRY_STATE_NOT_LOADED + state_dt = hass.states.get(entity_id_dt) + assert state_dt is None + state_sensor = hass.states.get(entity_id_sensor) + assert state_sensor is None + state_switch = hass.states.get(entity_id_switch) + assert state_switch is None From 027d125617af8c016c1cc9a3906b8868f7543154 Mon Sep 17 00:00:00 2001 From: Nick Adams <4012017+Nick-Adams-AU@users.noreply.github.com> Date: Tue, 2 Mar 2021 22:58:41 +1000 Subject: [PATCH 0936/1818] Add services for izone airflow min/max (#45727) * Create airflow_min and airflow_max services for the izone component * Bump pizone library requirement --- homeassistant/components/izone/climate.py | 62 ++++++++++++++++++++ homeassistant/components/izone/manifest.json | 2 +- homeassistant/components/izone/services.yaml | 18 ++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/izone/services.yaml diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 776d3f120c90a0..5f00720ae9d371 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -1,8 +1,10 @@ """Support for the iZone HVAC.""" + import logging from typing import List, Optional from pizone import Controller, Zone +import voluptuous as vol from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -24,12 +26,14 @@ ) from homeassistant.const import ( ATTR_TEMPERATURE, + CONF_ENTITY_ID, CONF_EXCLUDE, PRECISION_HALVES, PRECISION_TENTHS, TEMP_CELSIUS, ) from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -54,6 +58,20 @@ Controller.Fan.AUTO: FAN_AUTO, } +ATTR_AIRFLOW = "airflow" + +IZONE_SERVICE_AIRFLOW_MIN = "airflow_min" +IZONE_SERVICE_AIRFLOW_MAX = "airflow_max" + +IZONE_SERVICE_AIRFLOW_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_AIRFLOW): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow" + ), + } +) + async def async_setup_entry( hass: HomeAssistantType, config: ConfigType, async_add_entities @@ -83,6 +101,18 @@ def init_controller(ctrl: Controller): # connect to register any further components async_dispatcher_connect(hass, DISPATCH_CONTROLLER_DISCOVERED, init_controller) + platform = entity_platform.current_platform.get() + platform.async_register_entity_service( + IZONE_SERVICE_AIRFLOW_MIN, + IZONE_SERVICE_AIRFLOW_SCHEMA, + "async_set_airflow_min", + ) + platform.async_register_entity_service( + IZONE_SERVICE_AIRFLOW_MAX, + IZONE_SERVICE_AIRFLOW_SCHEMA, + "async_set_airflow_max", + ) + return True @@ -572,6 +602,38 @@ def max_temp(self): """Return the maximum temperature.""" return self._controller.max_temp + @property + def airflow_min(self): + """Return the minimum air flow.""" + return self._zone.airflow_min + + @property + def airflow_max(self): + """Return the maximum air flow.""" + return self._zone.airflow_max + + @property + def device_state_attributes(self): + """Return the optional state attributes.""" + return { + "airflow_min": self._zone.airflow_min, + "airflow_max": self._zone.airflow_max, + } + + async def async_set_airflow_min(self, **kwargs): + """Set new airflow minimum.""" + await self._controller.wrap_and_catch( + self._zone.set_airflow_min(int(kwargs[ATTR_AIRFLOW])) + ) + self.async_write_ha_state() + + async def async_set_airflow_max(self, **kwargs): + """Set new airflow maximum.""" + await self._controller.wrap_and_catch( + self._zone.set_airflow_max(int(kwargs[ATTR_AIRFLOW])) + ) + self.async_write_ha_state() + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" if self._zone.mode != Zone.Mode.AUTO: diff --git a/homeassistant/components/izone/manifest.json b/homeassistant/components/izone/manifest.json index 479ac4969060da..bed7654b7e836d 100644 --- a/homeassistant/components/izone/manifest.json +++ b/homeassistant/components/izone/manifest.json @@ -2,7 +2,7 @@ "domain": "izone", "name": "iZone", "documentation": "https://www.home-assistant.io/integrations/izone", - "requirements": ["python-izone==1.1.3"], + "requirements": ["python-izone==1.1.4"], "codeowners": ["@Swamp-Ig"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/izone/services.yaml b/homeassistant/components/izone/services.yaml new file mode 100644 index 00000000000000..14aaa69349a385 --- /dev/null +++ b/homeassistant/components/izone/services.yaml @@ -0,0 +1,18 @@ +airflow_min: + description: Set the airflow minimum percent for a zone + fields: + entity_id: + description: iZone Zone entity + example: "climate.bed_1" + airflow: + description: Airflow percent in 5% increments + example: "95" +airflow_max: + description: Set the airflow maximum percent for a zone + fields: + entity_id: + description: iZone Zone entity + example: "climate.bed_1" + airflow: + description: Airflow percent in 5% increments + example: "95" diff --git a/requirements_all.txt b/requirements_all.txt index 75e660f0fb0472..09c328b2842a7e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1784,7 +1784,7 @@ python-gitlab==1.6.0 python-hpilo==4.3 # homeassistant.components.izone -python-izone==1.1.3 +python-izone==1.1.4 # homeassistant.components.joaoapps_join python-join-api==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2037a55bd49f76..63eeddffe5a42e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -930,7 +930,7 @@ python-ecobee-api==0.2.10 python-forecastio==1.4.0 # homeassistant.components.izone -python-izone==1.1.3 +python-izone==1.1.4 # homeassistant.components.juicenet python-juicenet==1.0.1 From 959181a2e9bcca173dd838599287959a4b91888f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 14:28:31 +0100 Subject: [PATCH 0937/1818] Make MQTT number respect retain setting (#47270) --- homeassistant/components/mqtt/number.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 969eb254072389..aa24f81eb6967e 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -28,6 +28,7 @@ subscription, ) from .. import mqtt +from .const import CONF_RETAIN from .debug_info import log_messages from .mixins import ( MQTT_AVAILABILITY_SCHEMA, @@ -161,6 +162,7 @@ async def async_set_value(self, value: float) -> None: self._config[CONF_COMMAND_TOPIC], current_number, self._config[CONF_QOS], + self._config[CONF_RETAIN], ) @property From 2ebca88950ce5d2ca7dbd172f750914431cf36f5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 06:13:45 -0800 Subject: [PATCH 0938/1818] Fix Alexa doorbells (#47257) --- .../components/alexa/state_report.py | 30 +++++++------------ tests/components/alexa/test_state_report.py | 16 ++++++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index d66906810b2ef6..c34dc34f0dd224 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -73,10 +73,7 @@ async def async_entity_state_listener( if not should_report and interface.properties_proactively_reported(): should_report = True - if ( - interface.name() == "Alexa.DoorbellEventSource" - and new_state.state == STATE_ON - ): + if interface.name() == "Alexa.DoorbellEventSource": should_doorbell = True break @@ -84,27 +81,22 @@ async def async_entity_state_listener( return if should_doorbell: - should_report = False + if new_state.state == STATE_ON: + await async_send_doorbell_event_message( + hass, smart_home_config, alexa_changed_entity + ) + return - if should_report: - alexa_properties = list(alexa_changed_entity.serialize_properties()) - else: - alexa_properties = None + alexa_properties = list(alexa_changed_entity.serialize_properties()) if not checker.async_is_significant_change( new_state, extra_arg=alexa_properties ): return - if should_report: - await async_send_changereport_message( - hass, smart_home_config, alexa_changed_entity, alexa_properties - ) - - elif should_doorbell: - await async_send_doorbell_event_message( - hass, smart_home_config, alexa_changed_entity - ) + await async_send_changereport_message( + hass, smart_home_config, alexa_changed_entity, alexa_properties + ) return hass.helpers.event.async_track_state_change( MATCH_ALL, async_entity_state_listener @@ -246,7 +238,7 @@ async def async_send_delete_message(hass, config, entity_ids): async def async_send_doorbell_event_message(hass, config, alexa_entity): """Send a DoorbellPress event message for an Alexa entity. - https://developer.amazon.com/docs/smarthome/send-events-to-the-alexa-event-gateway.html + https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-doorbelleventsource.html """ token = await config.async_get_access_token() diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index a057eada531e42..2cbf8636d79c1b 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -175,6 +175,22 @@ async def test_doorbell_event(hass, aioclient_mock): assert call_json["event"]["payload"]["cause"]["type"] == "PHYSICAL_INTERACTION" assert call_json["event"]["endpoint"]["endpointId"] == "binary_sensor#test_doorbell" + hass.states.async_set( + "binary_sensor.test_doorbell", + "off", + {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + ) + + hass.states.async_set( + "binary_sensor.test_doorbell", + "on", + {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + ) + + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 2 + async def test_proactive_mode_filter_states(hass, aioclient_mock): """Test all the cases that filter states.""" From 4904207f774a39a92f38eb68fa0a4fdb2dda9e4b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 16:58:02 +0100 Subject: [PATCH 0939/1818] Fix izone flake8 error (#47276) --- homeassistant/components/izone/climate.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 5f00720ae9d371..ef054bcfb29102 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -612,14 +612,6 @@ def airflow_max(self): """Return the maximum air flow.""" return self._zone.airflow_max - @property - def device_state_attributes(self): - """Return the optional state attributes.""" - return { - "airflow_min": self._zone.airflow_min, - "airflow_max": self._zone.airflow_max, - } - async def async_set_airflow_min(self, **kwargs): """Set new airflow minimum.""" await self._controller.wrap_and_catch( @@ -675,5 +667,7 @@ def zone_index(self): def device_state_attributes(self): """Return the optional state attributes.""" return { + "airflow_max": self._zone.airflow_max, + "airflow_min": self._zone.airflow_min, "zone_index": self.zone_index, } From 227292569936408475a8d169e4e54f4d1beeb16f Mon Sep 17 00:00:00 2001 From: Rene Lehfeld <54720674+rlehfeld@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:57:14 +0100 Subject: [PATCH 0940/1818] Add force_update to tasmota sensors (#47052) * Add force update also to non-binary sensors as e.g. POWER Measurement agerage cannot be calculated otherwise. This is the same behavior as set with the obsolete tasmota detection * add tests in binary_sensor and test_sensor for force_update flag * satisfy flake8 * next try for force_update test but this time on the entity object which is the correct level * once again satisfy flake8 * one more try for a test * fix typo * satisfy black --- homeassistant/components/tasmota/sensor.py | 5 +++++ tests/components/tasmota/test_binary_sensor.py | 6 ++++++ tests/components/tasmota/test_sensor.py | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 17a6e2a35c2fcb..39577e9e558179 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -192,6 +192,11 @@ def state(self): return self._state.isoformat() return self._state + @property + def force_update(self): + """Force update.""" + return True + @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" diff --git a/tests/components/tasmota/test_binary_sensor.py b/tests/components/tasmota/test_binary_sensor.py index 6d4263853dcce2..6b13dcc89ec5ad 100644 --- a/tests/components/tasmota/test_binary_sensor.py +++ b/tests/components/tasmota/test_binary_sensor.py @@ -95,6 +95,12 @@ async def test_controlling_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("binary_sensor.tasmota_binary_sensor_1") assert state.state == STATE_OFF + # Test force update flag + entity = hass.data["entity_components"]["binary_sensor"].get_entity( + "binary_sensor.tasmota_binary_sensor_1" + ) + assert entity.force_update + async def test_controlling_state_via_mqtt_switchname(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index fe415c264efb3c..6e5160273d6114 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -275,6 +275,12 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): state = hass.states.get("sensor.tasmota_status") assert state.state == "20.0" + # Test force update flag + entity = hass.data["entity_components"]["sensor"].get_entity( + "sensor.tasmota_status" + ) + assert entity.force_update + @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_single_shot_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): From 8e80e428d045b9191275c389779f3b0252c841a5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Mar 2021 20:15:09 +0100 Subject: [PATCH 0941/1818] Update frontend to 20210302.0 (#47278) --- 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 e8f9ff2698d71b..e7d7723a510667 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==20210301.0" + "home-assistant-frontend==20210302.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cb211fb1962d7b..d4b18de88ee58f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 09c328b2842a7e..89cdbc873c722c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 63eeddffe5a42e..48b018803a297a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 112b0391076d307ef8533045c3b5c4966d78e2cd Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 2 Mar 2021 12:19:04 -0700 Subject: [PATCH 0942/1818] Bump simplisafe-python to 9.6.9 (#47273) --- 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 6122428ea98d32..45deb938b59c27 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.8"], + "requirements": ["simplisafe-python==9.6.9"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index 89cdbc873c722c..6fcc28924afd51 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2041,7 +2041,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.8 +simplisafe-python==9.6.9 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 48b018803a297a..789486f0561f6a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1048,7 +1048,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.8 +simplisafe-python==9.6.9 # homeassistant.components.slack slackclient==2.5.0 From 7ef174fb5e3f568f2ea442e29960ddbfd83e2ed4 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 2 Mar 2021 15:12:30 -0500 Subject: [PATCH 0943/1818] Update ZHA dependencies (#47282) --- 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 d7bb0dbe5bc965..7d367c3dc00867 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.21.0", + "bellows==0.22.0", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index 6fcc28924afd51..2a2e37dd3a8801 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.21.0 +bellows==0.22.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 789486f0561f6a..18a1dc33839157 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.21.0 +bellows==0.22.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From b0c873dd34cd4cb24a366ff98e44e7dadda5d4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 2 Mar 2021 22:16:11 +0200 Subject: [PATCH 0944/1818] Upgrade isort to 5.7.0 (#47279) https://pycqa.github.io/isort/CHANGELOG/#570-december-30th-2020 --- .pre-commit-config.yaml | 2 +- requirements_test_pre_commit.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e2fb5cb3b35ff..4ab91245b9ee86 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: - --configfile=tests/bandit.yaml files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/isort - rev: 5.5.3 + rev: 5.7.0 hooks: - id: isort - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 953c7d75394bd6..07f1efbc6946ef 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -5,7 +5,7 @@ black==20.8b1 codespell==2.0.0 flake8-docstrings==1.5.0 flake8==3.8.4 -isort==5.5.3 +isort==5.7.0 pydocstyle==5.1.1 pyupgrade==2.7.2 yamllint==1.24.2 From ca54de095d297a75fce0c8529ac205fbe4641435 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 21:23:02 +0100 Subject: [PATCH 0945/1818] Add disk sensor to Freebox (#46689) * Add disk sensor to Freebox * Add debug logging into sensors * Remove useless sensor[X] assignement in disk sensor --- homeassistant/components/freebox/const.py | 10 ++++ homeassistant/components/freebox/router.py | 27 ++++++--- homeassistant/components/freebox/sensor.py | 66 +++++++++++++++++++++- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index e47211f092635b..df251dcf954790 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -4,6 +4,7 @@ from homeassistant.const import ( DATA_RATE_KILOBYTES_PER_SECOND, DEVICE_CLASS_TEMPERATURE, + PERCENTAGE, TEMP_CELSIUS, ) @@ -56,6 +57,15 @@ }, } +DISK_PARTITION_SENSORS = { + "partition_free_space": { + SENSOR_NAME: "free space", + SENSOR_UNIT: PERCENTAGE, + SENSOR_ICON: "mdi:harddisk", + SENSOR_DEVICE_CLASS: None, + }, +} + TEMPERATURE_SENSOR_TEMPLATE = { SENSOR_NAME: None, SENSOR_UNIT: TEMP_CELSIUS, diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 623c1fcc564d3f..a38992320eb7a2 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -55,12 +55,13 @@ def __init__(self, hass: HomeAssistantType, entry: ConfigEntry) -> None: self._port = entry.data[CONF_PORT] self._api: Freepybox = None - self._name = None + self.name = None self.mac = None self._sw_v = None self._attrs = {} - self.devices: Dict[str, Any] = {} + self.devices: Dict[str, Dict[str, Any]] = {} + self.disks: Dict[int, Dict[str, Any]] = {} self.sensors_temperature: Dict[str, int] = {} self.sensors_connection: Dict[str, float] = {} self.call_list: List[Dict[str, Any]] = [] @@ -81,7 +82,7 @@ async def setup(self) -> None: # System fbx_config = await self._api.system.get_config() self.mac = fbx_config["mac"] - self._name = fbx_config["model_info"]["pretty_name"] + self.name = fbx_config["model_info"]["pretty_name"] self._sw_v = fbx_config["firmware_version"] # Devices & sensors @@ -92,18 +93,18 @@ async def setup(self) -> None: async def update_all(self, now: Optional[datetime] = None) -> None: """Update all Freebox platforms.""" + await self.update_device_trackers() await self.update_sensors() - await self.update_devices() - async def update_devices(self) -> None: + async def update_device_trackers(self) -> None: """Update Freebox devices.""" new_device = False - fbx_devices: Dict[str, Any] = await self._api.lan.get_hosts_list() + fbx_devices: [Dict[str, Any]] = await self._api.lan.get_hosts_list() # Adds the Freebox itself fbx_devices.append( { - "primary_name": self._name, + "primary_name": self.name, "l2ident": {"id": self.mac}, "vendor_name": "Freebox SAS", "host_type": "router", @@ -153,8 +154,18 @@ async def update_sensors(self) -> None: self.call_list = await self._api.call.get_calls_log() + await self._update_disks_sensors() + async_dispatcher_send(self.hass, self.signal_sensor_update) + 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 [] + + for fbx_disk in fbx_disks: + self.disks[fbx_disk["id"]] = fbx_disk + async def reboot(self) -> None: """Reboot the Freebox.""" await self._api.system.reboot() @@ -172,7 +183,7 @@ def device_info(self) -> Dict[str, Any]: return { "connections": {(CONNECTION_NETWORK_MAC, self.mac)}, "identifiers": {(DOMAIN, self.mac)}, - "name": self._name, + "name": self.name, "manufacturer": "Freebox SAS", "sw_version": self._sw_v, } diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index aeeaba438ffc14..b8881ad7949e0a 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +import logging from typing import Dict from homeassistant.config_entries import ConfigEntry @@ -12,6 +13,7 @@ from .const import ( CALL_SENSORS, CONNECTION_SENSORS, + DISK_PARTITION_SENSORS, DOMAIN, SENSOR_DEVICE_CLASS, SENSOR_ICON, @@ -21,6 +23,8 @@ ) from .router import FreeboxRouter +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities @@ -29,6 +33,12 @@ async def async_setup_entry( router = hass.data[DOMAIN][entry.unique_id] entities = [] + _LOGGER.debug( + "%s - %s - %s temperature sensors", + router.name, + router.mac, + len(router.sensors_temperature), + ) for sensor_name in router.sensors_temperature: entities.append( FreeboxSensor( @@ -46,6 +56,20 @@ async def async_setup_entry( for sensor_key in CALL_SENSORS: entities.append(FreeboxCallSensor(router, sensor_key, CALL_SENSORS[sensor_key])) + _LOGGER.debug("%s - %s - %s disk(s)", router.name, router.mac, len(router.disks)) + for disk in router.disks.values(): + for partition in disk["partitions"]: + for sensor_key in DISK_PARTITION_SENSORS: + entities.append( + FreeboxDiskSensor( + router, + disk, + partition, + sensor_key, + DISK_PARTITION_SENSORS[sensor_key], + ) + ) + async_add_entities(entities, True) @@ -139,8 +163,8 @@ def __init__( self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any] ) -> None: """Initialize a Freebox call sensor.""" - self._call_list_for_type = [] super().__init__(router, sensor_type, sensor) + self._call_list_for_type = [] @callback def async_update_state(self) -> None: @@ -162,3 +186,43 @@ def device_state_attributes(self) -> Dict[str, any]: dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"] for call in self._call_list_for_type } + + +class FreeboxDiskSensor(FreeboxSensor): + """Representation of a Freebox disk sensor.""" + + def __init__( + self, + router: FreeboxRouter, + disk: Dict[str, any], + partition: Dict[str, any], + sensor_type: str, + sensor: Dict[str, any], + ) -> None: + """Initialize a Freebox disk sensor.""" + super().__init__(router, sensor_type, sensor) + self._disk = disk + self._partition = partition + self._name = f"{partition['label']} {sensor[SENSOR_NAME]}" + self._unique_id = f"{self._router.mac} {sensor_type} {self._disk['id']} {self._partition['id']}" + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self._disk["id"])}, + "name": f"Disk {self._disk['id']}", + "model": self._disk["model"], + "sw_version": self._disk["firmware"], + "via_device": ( + DOMAIN, + self._router.mac, + ), + } + + @callback + def async_update_state(self) -> None: + """Update the Freebox disk sensor.""" + self._state = round( + self._partition["free_bytes"] * 100 / self._partition["total_bytes"], 2 + ) From 198ecb0945aa062971afc471fc860b9069a567da Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Mar 2021 21:43:59 +0100 Subject: [PATCH 0946/1818] Uniformize platform setup (#47101) * A platform is not a component * Fix dynalite * SUPPORTED_PLATFORMS --> PLATFORMS * In tests * In tests 2 * Fix SmartThings * Fix ZHA test * Fix Z-Wave * Revert Z-Wave * Use PLATFORMS const in ambient_station * Fix ihc comment --- homeassistant/components/abode/__init__.py | 6 +- .../components/accuweather/__init__.py | 8 +- homeassistant/components/acmeda/__init__.py | 8 +- homeassistant/components/adguard/__init__.py | 10 +- .../components/advantage_air/__init__.py | 8 +- homeassistant/components/aemet/__init__.py | 10 +- homeassistant/components/aemet/const.py | 2 +- homeassistant/components/airly/__init__.py | 8 +- homeassistant/components/airnow/__init__.py | 8 +- .../components/airvisual/__init__.py | 8 +- .../components/alarmdecoder/__init__.py | 8 +- .../components/ambient_station/__init__.py | 206 +++++++++--------- .../ambient_station/binary_sensor.py | 15 +- .../components/ambient_station/const.py | 3 - .../components/ambient_station/sensor.py | 11 +- homeassistant/components/apple_tv/__init__.py | 4 +- homeassistant/components/atag/__init__.py | 4 +- homeassistant/components/august/__init__.py | 10 +- homeassistant/components/august/const.py | 2 +- homeassistant/components/aurora/__init__.py | 8 +- homeassistant/components/blebox/__init__.py | 4 +- homeassistant/components/blink/__init__.py | 8 +- homeassistant/components/bloomsky/__init__.py | 8 +- .../bmw_connected_drive/__init__.py | 12 +- homeassistant/components/bond/__init__.py | 8 +- homeassistant/components/braviatv/__init__.py | 8 +- homeassistant/components/brother/__init__.py | 8 +- homeassistant/components/canary/__init__.py | 8 +- .../components/climacell/__init__.py | 8 +- homeassistant/components/control4/__init__.py | 8 +- .../components/coronavirus/__init__.py | 8 +- homeassistant/components/daikin/__init__.py | 10 +- .../components/danfoss_air/__init__.py | 4 +- homeassistant/components/deconz/const.py | 2 +- homeassistant/components/deconz/gateway.py | 10 +- homeassistant/components/demo/__init__.py | 8 +- homeassistant/components/dexcom/__init__.py | 8 +- homeassistant/components/directv/__init__.py | 8 +- homeassistant/components/doorbird/__init__.py | 8 +- homeassistant/components/dsmr/__init__.py | 4 +- homeassistant/components/dunehd/__init__.py | 8 +- homeassistant/components/dynalite/__init__.py | 9 +- homeassistant/components/dynalite/bridge.py | 11 +- homeassistant/components/dynalite/const.py | 2 +- homeassistant/components/dyson/__init__.py | 4 +- homeassistant/components/ecobee/__init__.py | 16 +- homeassistant/components/ecobee/const.py | 2 +- homeassistant/components/econet/__init__.py | 8 +- homeassistant/components/elkm1/__init__.py | 10 +- homeassistant/components/epson/__init__.py | 8 +- .../components/faa_delays/__init__.py | 8 +- homeassistant/components/fibaro/__init__.py | 16 +- .../components/fireservicerota/__init__.py | 6 +- homeassistant/components/flo/__init__.py | 8 +- homeassistant/components/flume/__init__.py | 8 +- .../components/flunearyou/__init__.py | 8 +- homeassistant/components/foscam/__init__.py | 8 +- homeassistant/components/fritzbox/__init__.py | 8 +- .../fritzbox_callmonitor/__init__.py | 8 +- .../components/garmin_connect/__init__.py | 8 +- homeassistant/components/goalzero/__init__.py | 4 +- homeassistant/components/guardian/__init__.py | 8 +- homeassistant/components/habitica/__init__.py | 8 +- homeassistant/components/harmony/__init__.py | 8 +- .../components/home_connect/__init__.py | 8 +- .../components/homematicip_cloud/const.py | 2 +- .../components/homematicip_cloud/hap.py | 10 +- .../hunterdouglas_powerview/__init__.py | 8 +- .../components/hvv_departures/__init__.py | 8 +- homeassistant/components/hyperion/__init__.py | 8 +- homeassistant/components/ihc/__init__.py | 32 +-- homeassistant/components/insteon/__init__.py | 6 +- homeassistant/components/insteon/const.py | 2 +- homeassistant/components/ipp/__init__.py | 8 +- homeassistant/components/iqvia/__init__.py | 8 +- homeassistant/components/isy994/__init__.py | 8 +- homeassistant/components/isy994/const.py | 2 +- homeassistant/components/isy994/helpers.py | 12 +- homeassistant/components/isy994/services.py | 4 +- homeassistant/components/juicenet/__init__.py | 8 +- homeassistant/components/kaiterra/__init__.py | 8 +- homeassistant/components/kaiterra/const.py | 2 +- .../components/keenetic_ndms2/__init__.py | 8 +- homeassistant/components/kmtronic/__init__.py | 8 +- homeassistant/components/kodi/__init__.py | 8 +- .../components/konnected/__init__.py | 8 +- homeassistant/components/kulersky/__init__.py | 8 +- homeassistant/components/litejet/__init__.py | 8 +- .../components/litterrobot/__init__.py | 8 +- .../components/logi_circle/__init__.py | 10 +- homeassistant/components/lutron/__init__.py | 8 +- .../components/lutron_caseta/__init__.py | 12 +- homeassistant/components/lyric/__init__.py | 8 +- homeassistant/components/mazda/__init__.py | 8 +- .../components/metoffice/__init__.py | 8 +- .../components/mobile_app/__init__.py | 4 +- .../components/monoprice/__init__.py | 8 +- .../components/motion_blinds/__init__.py | 10 +- .../components/motion_blinds/const.py | 2 +- homeassistant/components/mullvad/__init__.py | 8 +- homeassistant/components/myq/__init__.py | 8 +- .../components/mysensors/__init__.py | 6 +- homeassistant/components/mysensors/const.py | 2 +- homeassistant/components/n26/__init__.py | 8 +- homeassistant/components/neato/__init__.py | 4 +- homeassistant/components/nest/__init__.py | 8 +- .../components/nest/legacy/__init__.py | 6 +- homeassistant/components/netatmo/__init__.py | 8 +- homeassistant/components/nexia/__init__.py | 8 +- .../components/nextcloud/__init__.py | 6 +- .../components/nightscout/__init__.py | 8 +- .../components/nissan_leaf/__init__.py | 8 +- homeassistant/components/nuheat/__init__.py | 8 +- homeassistant/components/nuki/__init__.py | 4 +- homeassistant/components/nut/__init__.py | 8 +- homeassistant/components/nws/__init__.py | 8 +- homeassistant/components/nzbget/__init__.py | 8 +- .../components/omnilogic/__init__.py | 8 +- .../components/ondilo_ico/__init__.py | 8 +- homeassistant/components/onewire/__init__.py | 10 +- homeassistant/components/onewire/const.py | 2 +- homeassistant/components/onvif/__init__.py | 8 +- homeassistant/components/openuv/__init__.py | 8 +- .../components/openweathermap/__init__.py | 10 +- .../components/openweathermap/const.py | 2 +- homeassistant/components/ozw/__init__.py | 8 +- .../components/panasonic_viera/__init__.py | 8 +- .../components/philips_js/__init__.py | 8 +- homeassistant/components/plugwise/gateway.py | 8 +- .../components/plum_lightpad/__init__.py | 4 +- homeassistant/components/point/__init__.py | 18 +- .../components/poolsense/__init__.py | 8 +- .../components/powerwall/__init__.py | 8 +- .../components/progettihwsw/__init__.py | 8 +- .../components/proxmoxve/__init__.py | 4 +- homeassistant/components/rachio/__init__.py | 12 +- homeassistant/components/rainbird/__init__.py | 4 +- .../components/rainmachine/__init__.py | 8 +- .../components/recollect_waste/__init__.py | 8 +- homeassistant/components/rfxtrx/__init__.py | 10 +- homeassistant/components/ring/__init__.py | 8 +- homeassistant/components/risco/__init__.py | 8 +- .../rituals_perfume_genie/__init__.py | 8 +- homeassistant/components/roku/__init__.py | 8 +- homeassistant/components/roomba/__init__.py | 10 +- homeassistant/components/roomba/const.py | 2 +- .../components/ruckus_unleashed/__init__.py | 4 +- homeassistant/components/sense/__init__.py | 8 +- homeassistant/components/sharkiq/__init__.py | 10 +- homeassistant/components/sharkiq/const.py | 2 +- homeassistant/components/shelly/__init__.py | 8 +- .../components/simplisafe/__init__.py | 8 +- homeassistant/components/smappee/__init__.py | 10 +- homeassistant/components/smappee/const.py | 2 +- .../components/smart_meter_texas/__init__.py | 8 +- homeassistant/components/smarthab/__init__.py | 10 +- .../components/smartthings/__init__.py | 22 +- homeassistant/components/smartthings/const.py | 2 +- homeassistant/components/sms/__init__.py | 8 +- homeassistant/components/soma/__init__.py | 10 +- homeassistant/components/somfy/__init__.py | 10 +- .../components/somfy_mylink/__init__.py | 10 +- .../components/somfy_mylink/const.py | 2 +- homeassistant/components/sonarr/__init__.py | 8 +- homeassistant/components/spider/__init__.py | 8 +- .../components/streamlabswater/__init__.py | 8 +- homeassistant/components/subaru/__init__.py | 10 +- homeassistant/components/subaru/const.py | 2 +- homeassistant/components/tado/__init__.py | 10 +- homeassistant/components/tahoma/__init__.py | 10 +- homeassistant/components/tasmota/__init__.py | 12 +- .../components/tellduslive/__init__.py | 4 +- homeassistant/components/tesla/__init__.py | 12 +- homeassistant/components/tesla/const.py | 4 +- homeassistant/components/tibber/__init__.py | 8 +- homeassistant/components/tile/__init__.py | 8 +- homeassistant/components/toon/__init__.py | 10 +- .../components/totalconnect/__init__.py | 4 +- homeassistant/components/tradfri/__init__.py | 8 +- homeassistant/components/tuya/__init__.py | 4 +- homeassistant/components/unifi/controller.py | 6 +- homeassistant/components/upb/__init__.py | 10 +- homeassistant/components/velbus/__init__.py | 20 +- homeassistant/components/velux/__init__.py | 6 +- homeassistant/components/verisure/__init__.py | 22 +- homeassistant/components/vesync/__init__.py | 4 +- homeassistant/components/vicare/__init__.py | 4 +- homeassistant/components/volumio/__init__.py | 8 +- .../components/volvooncall/__init__.py | 6 +- homeassistant/components/wiffi/__init__.py | 8 +- homeassistant/components/wilight/__init__.py | 8 +- homeassistant/components/wled/__init__.py | 10 +- homeassistant/components/xbox/__init__.py | 8 +- .../components/xiaomi_aqara/__init__.py | 8 +- .../components/xiaomi_miio/__init__.py | 8 +- homeassistant/components/xs1/__init__.py | 12 +- homeassistant/components/yeelight/__init__.py | 8 +- homeassistant/components/zerproc/__init__.py | 8 +- homeassistant/components/zha/__init__.py | 14 +- homeassistant/components/zha/core/const.py | 2 +- .../components/zha/core/discovery.py | 4 +- homeassistant/components/zwave/__init__.py | 6 +- homeassistant/components/zwave_js/__init__.py | 8 +- .../config_flow/integration/__init__.py | 8 +- .../integration/__init__.py | 8 +- .../integration/__init__.py | 8 +- tests/components/abode/common.py | 2 +- tests/components/dynalite/test_init.py | 6 +- tests/components/dyson/conftest.py | 4 +- tests/components/dyson/test_init.py | 4 +- tests/components/dyson/test_sensor.py | 4 +- .../components/onewire/test_binary_sensor.py | 2 +- .../onewire/test_entity_owserver.py | 10 +- tests/components/onewire/test_sensor.py | 2 +- tests/components/onewire/test_switch.py | 2 +- tests/components/smartthings/test_init.py | 10 +- tests/components/unifi/test_controller.py | 7 +- tests/components/zha/test_discover.py | 2 +- 218 files changed, 924 insertions(+), 932 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 20c0624742c228..47e03eea44c6b9 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -66,7 +66,7 @@ AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) -ABODE_PLATFORMS = [ +PLATFORMS = [ "alarm_control_panel", "binary_sensor", "lock", @@ -138,7 +138,7 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN] = AbodeSystem(abode, polling) - for platform in ABODE_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @@ -158,7 +158,7 @@ async def async_unload_entry(hass, config_entry): tasks = [] - for platform in ABODE_PLATFORMS: + for platform in PLATFORMS: tasks.append( hass.config_entries.async_forward_entry_unload(config_entry, platform) ) diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index 27dbae7a41fdef..b2ec7e0224bed5 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -57,9 +57,9 @@ async def async_setup_entry(hass, config_entry) -> bool: UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -70,8 +70,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/acmeda/__init__.py b/homeassistant/components/acmeda/__init__.py index 3b4f135a6fd937..ede6751a6fe104 100644 --- a/homeassistant/components/acmeda/__init__.py +++ b/homeassistant/components/acmeda/__init__.py @@ -28,9 +28,9 @@ async def async_setup_entry( hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = hub - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -45,8 +45,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 71dff2ab6eed86..0a316784f8b252 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -43,6 +43,8 @@ {vol.Optional(CONF_FORCE, default=False): cv.boolean} ) +PLATFORMS = ["sensor", "switch"] + async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up the AdGuard Home components.""" @@ -69,9 +71,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool except AdGuardHomeConnectionError as exception: raise ConfigEntryNotReady from exception - for component in "sensor", "switch": + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def add_url(call) -> None: @@ -123,8 +125,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL) hass.services.async_remove(DOMAIN, SERVICE_REFRESH) - for component in "sensor", "switch": - await hass.config_entries.async_forward_entry_unload(entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) del hass.data[DOMAIN] diff --git a/homeassistant/components/advantage_air/__init__.py b/homeassistant/components/advantage_air/__init__.py index 7b270f1f33532c..d98e991364de7f 100644 --- a/homeassistant/components/advantage_air/__init__.py +++ b/homeassistant/components/advantage_air/__init__.py @@ -14,7 +14,7 @@ from .const import ADVANTAGE_AIR_RETRY, DOMAIN ADVANTAGE_AIR_SYNC_INTERVAL = 15 -ADVANTAGE_AIR_PLATFORMS = ["climate", "cover", "binary_sensor", "sensor", "switch"] +PLATFORMS = ["climate", "cover", "binary_sensor", "sensor", "switch"] _LOGGER = logging.getLogger(__name__) @@ -67,7 +67,7 @@ async def async_change(change): "async_change": async_change, } - for platform in ADVANTAGE_AIR_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -80,8 +80,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in ADVANTAGE_AIR_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/aemet/__init__.py b/homeassistant/components/aemet/__init__.py index 58b1a3b10f0399..54c93f43a25ae9 100644 --- a/homeassistant/components/aemet/__init__.py +++ b/homeassistant/components/aemet/__init__.py @@ -8,7 +8,7 @@ from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant -from .const import COMPONENTS, DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR +from .const import DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR, PLATFORMS from .weather_update_coordinator import WeatherUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -37,9 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): ENTRY_WEATHER_COORDINATOR: weather_coordinator, } - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -50,8 +50,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/aemet/const.py b/homeassistant/components/aemet/const.py index 13b9d944bf07dc..390ccb860034ad 100644 --- a/homeassistant/components/aemet/const.py +++ b/homeassistant/components/aemet/const.py @@ -34,7 +34,7 @@ ) ATTRIBUTION = "Powered by AEMET OpenData" -COMPONENTS = ["sensor", "weather"] +PLATFORMS = ["sensor", "weather"] DEFAULT_NAME = "AEMET" DOMAIN = "aemet" ENTRY_NAME = "name" diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index fd1defc64f6ad2..ff7a87f92fc242 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -79,9 +79,9 @@ async def async_setup_entry(hass, config_entry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -92,8 +92,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/airnow/__init__.py b/homeassistant/components/airnow/__init__.py index 5cbc87947f92de..1732445c566c0d 100644 --- a/homeassistant/components/airnow/__init__.py +++ b/homeassistant/components/airnow/__init__.py @@ -68,9 +68,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -81,8 +81,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 3a88243b0b9547..c04f56f6b09d81 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -268,9 +268,9 @@ async def async_update_data(): hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -323,8 +323,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 8dd704f133345a..34493b3b9d0241 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -130,9 +130,9 @@ def handle_rel_message(sender, message): await open_connection() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -144,8 +144,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 4a5558c5963648..b4ac6992459f70 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -5,7 +5,11 @@ from aioambient.errors import WebsocketError import voluptuous as vol -from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DOMAIN as BINARY_SENSOR, +) +from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_LOCATION, @@ -39,10 +43,10 @@ DATA_CLIENT, DOMAIN, LOGGER, - TYPE_BINARY_SENSOR, - TYPE_SENSOR, ) +PLATFORMS = [BINARY_SENSOR, SENSOR] + DATA_CONFIG = "config" DEFAULT_SOCKET_MIN_RETRY = 15 @@ -141,109 +145,109 @@ TYPE_WINDSPEEDMPH = "windspeedmph" TYPE_YEARLYRAININ = "yearlyrainin" SENSOR_TYPES = { - TYPE_24HOURRAININ: ("24 Hr Rain", "in", TYPE_SENSOR, None), - TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"), - TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"), - TYPE_BATT10: ("Battery 10", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT1: ("Battery 1", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT2: ("Battery 2", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT3: ("Battery 3", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT4: ("Battery 4", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT5: ("Battery 5", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT6: ("Battery 6", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT7: ("Battery 7", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT8: ("Battery 8", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATT9: ("Battery 9", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_BATTOUT: ("Battery", None, TYPE_BINARY_SENSOR, "battery"), - TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, TYPE_SENSOR, None), - TYPE_DAILYRAININ: ("Daily Rain", "in", TYPE_SENSOR, None), - TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_EVENTRAININ: ("Event Rain", "in", TYPE_SENSOR, None), - TYPE_FEELSLIKE: ("Feels Like", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", TYPE_SENSOR, None), - TYPE_HUMIDITY10: ("Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY1: ("Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY2: ("Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY3: ("Humidity 3", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY4: ("Humidity 4", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY5: ("Humidity 5", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY6: ("Humidity 6", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY7: ("Humidity 7", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY8: ("Humidity 8", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY9: ("Humidity 9", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITY: ("Humidity", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_HUMIDITYIN: ("Humidity In", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"), - TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None), - TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), - TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM3: ("Soil Humidity 3", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM4: ("Soil Humidity 4", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM5: ("Soil Humidity 5", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM6: ("Soil Humidity 6", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM7: ("Soil Humidity 7", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM8: ("Soil Humidity 8", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILHUM9: ("Soil Humidity 9", PERCENTAGE, TYPE_SENSOR, "humidity"), - TYPE_SOILTEMP10F: ("Soil Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP1F: ("Soil Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP2F: ("Soil Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP3F: ("Soil Temp 3", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP4F: ("Soil Temp 4", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP5F: ("Soil Temp 5", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP6F: ("Soil Temp 6", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), + TYPE_24HOURRAININ: ("24 Hr Rain", "in", SENSOR, None), + TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, SENSOR, "pressure"), + TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, SENSOR, "pressure"), + TYPE_BATT10: ("Battery 10", None, BINARY_SENSOR, "battery"), + TYPE_BATT1: ("Battery 1", None, BINARY_SENSOR, "battery"), + TYPE_BATT2: ("Battery 2", None, BINARY_SENSOR, "battery"), + TYPE_BATT3: ("Battery 3", None, BINARY_SENSOR, "battery"), + TYPE_BATT4: ("Battery 4", None, BINARY_SENSOR, "battery"), + TYPE_BATT5: ("Battery 5", None, BINARY_SENSOR, "battery"), + TYPE_BATT6: ("Battery 6", None, BINARY_SENSOR, "battery"), + TYPE_BATT7: ("Battery 7", None, BINARY_SENSOR, "battery"), + TYPE_BATT8: ("Battery 8", None, BINARY_SENSOR, "battery"), + TYPE_BATT9: ("Battery 9", None, BINARY_SENSOR, "battery"), + TYPE_BATTOUT: ("Battery", None, BINARY_SENSOR, "battery"), + TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, None), + TYPE_DAILYRAININ: ("Daily Rain", "in", SENSOR, None), + TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_EVENTRAININ: ("Event Rain", "in", SENSOR, None), + TYPE_FEELSLIKE: ("Feels Like", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", SENSOR, None), + TYPE_HUMIDITY10: ("Humidity 10", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY1: ("Humidity 1", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY2: ("Humidity 2", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY3: ("Humidity 3", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY4: ("Humidity 4", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY5: ("Humidity 5", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY6: ("Humidity 6", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY7: ("Humidity 7", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY8: ("Humidity 8", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY9: ("Humidity 9", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITY: ("Humidity", PERCENTAGE, SENSOR, "humidity"), + TYPE_HUMIDITYIN: ("Humidity In", PERCENTAGE, SENSOR, "humidity"), + TYPE_LASTRAIN: ("Last Rain", None, SENSOR, "timestamp"), + TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_MONTHLYRAININ: ("Monthly Rain", "in", SENSOR, None), + TYPE_RELAY10: ("Relay 10", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY1: ("Relay 1", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY2: ("Relay 2", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY3: ("Relay 3", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY4: ("Relay 4", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY5: ("Relay 5", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY6: ("Relay 6", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY7: ("Relay 7", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY8: ("Relay 8", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY9: ("Relay 9", None, BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM3: ("Soil Humidity 3", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM4: ("Soil Humidity 4", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM5: ("Soil Humidity 5", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM6: ("Soil Humidity 6", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM7: ("Soil Humidity 7", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM8: ("Soil Humidity 8", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILHUM9: ("Soil Humidity 9", PERCENTAGE, SENSOR, "humidity"), + TYPE_SOILTEMP10F: ("Soil Temp 10", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP1F: ("Soil Temp 1", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP2F: ("Soil Temp 2", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP3F: ("Soil Temp 3", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP4F: ("Soil Temp 4", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP5F: ("Soil Temp 5", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP6F: ("Soil Temp 6", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, SENSOR, "temperature"), TYPE_SOLARRADIATION: ( "Solar Rad", IRRADIATION_WATTS_PER_SQUARE_METER, - TYPE_SENSOR, + SENSOR, None, ), - TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", LIGHT_LUX, TYPE_SENSOR, "illuminance"), - TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP3F: ("Temp 3", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP4F: ("Temp 4", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP5F: ("Temp 5", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP6F: ("Temp 6", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP7F: ("Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP8F: ("Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMP9F: ("Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMPF: ("Temp", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_TOTALRAININ: ("Lifetime Rain", "in", TYPE_SENSOR, None), - TYPE_UV: ("uv", "Index", TYPE_SENSOR, None), - TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, TYPE_SENSOR, None), + TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", LIGHT_LUX, SENSOR, "illuminance"), + TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP3F: ("Temp 3", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP4F: ("Temp 4", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP5F: ("Temp 5", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP6F: ("Temp 6", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP7F: ("Temp 7", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP8F: ("Temp 8", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMP9F: ("Temp 9", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMPF: ("Temp", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, SENSOR, "temperature"), + TYPE_TOTALRAININ: ("Lifetime Rain", "in", SENSOR, None), + TYPE_UV: ("uv", "Index", SENSOR, None), + TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, SENSOR, None), TYPE_PM25_24H: ( "PM25 24h Avg", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - TYPE_SENSOR, + SENSOR, None, ), - TYPE_WEEKLYRAININ: ("Weekly Rain", "in", TYPE_SENSOR, None), - TYPE_WINDDIR: ("Wind Dir", DEGREE, TYPE_SENSOR, None), - TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, TYPE_SENSOR, None), - TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDGUSTDIR: ("Gust Dir", DEGREE, TYPE_SENSOR, None), - TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), - TYPE_YEARLYRAININ: ("Yearly Rain", "in", TYPE_SENSOR, None), + TYPE_WEEKLYRAININ: ("Weekly Rain", "in", SENSOR, None), + TYPE_WINDDIR: ("Wind Dir", DEGREE, SENSOR, None), + TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, SENSOR, None), + TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDGUSTDIR: ("Gust Dir", DEGREE, SENSOR, None), + TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, SENSOR, None), + TYPE_YEARLYRAININ: ("Yearly Rain", "in", SENSOR, None), } CONFIG_SCHEMA = vol.Schema( @@ -260,7 +264,7 @@ async def async_setup(hass, config): - """Set up the Ambient PWS component.""" + """Set up the Ambient PWS integration.""" hass.data[DOMAIN] = {} hass.data[DOMAIN][DATA_CLIENT] = {} @@ -322,8 +326,8 @@ async def async_unload_entry(hass, config_entry): hass.async_create_task(ambient.ws_disconnect()) tasks = [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in ("binary_sensor", "sensor") + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] await asyncio.gather(*tasks) @@ -431,10 +435,10 @@ def on_subscribed(data): # attempt forward setup of the config entry (because it will have # already been done): if not self._entry_setup_complete: - for component in ("binary_sensor", "sensor"): + for platform in PLATFORMS: self._hass.async_create_task( self._hass.config_entries.async_forward_entry_setup( - self._config_entry, component + self._config_entry, platform ) ) self._entry_setup_complete = True diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 0a3a91e515c919..8fdeb862e2e090 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -1,5 +1,8 @@ """Support for Ambient Weather Station binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DOMAIN as BINARY_SENSOR, + BinarySensorEntity, +) from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -18,13 +21,7 @@ TYPE_BATTOUT, AmbientWeatherEntity, ) -from .const import ( - ATTR_LAST_DATA, - ATTR_MONITORED_CONDITIONS, - DATA_CLIENT, - DOMAIN, - TYPE_BINARY_SENSOR, -) +from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN async def async_setup_entry(hass, entry, async_add_entities): @@ -35,7 +32,7 @@ async def async_setup_entry(hass, entry, async_add_entities): for mac_address, station in ambient.stations.items(): for condition in station[ATTR_MONITORED_CONDITIONS]: name, _, kind, device_class = SENSOR_TYPES[condition] - if kind == TYPE_BINARY_SENSOR: + if kind == BINARY_SENSOR: binary_sensor_list.append( AmbientWeatherBinarySensor( ambient, diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index e59f926eac3f94..87b5ff61877527 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -10,6 +10,3 @@ CONF_APP_KEY = "app_key" DATA_CLIENT = "data_client" - -TYPE_BINARY_SENSOR = "binary_sensor" -TYPE_SENSOR = "sensor" diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 540e2facd4d34d..184927a1ef5414 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,4 +1,5 @@ """Support for Ambient Weather Station sensors.""" +from homeassistant.components.binary_sensor import DOMAIN as SENSOR from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -8,13 +9,7 @@ TYPE_SOLARRADIATION_LX, AmbientWeatherEntity, ) -from .const import ( - ATTR_LAST_DATA, - ATTR_MONITORED_CONDITIONS, - DATA_CLIENT, - DOMAIN, - TYPE_SENSOR, -) +from .const import ATTR_LAST_DATA, ATTR_MONITORED_CONDITIONS, DATA_CLIENT, DOMAIN async def async_setup_entry(hass, entry, async_add_entities): @@ -25,7 +20,7 @@ async def async_setup_entry(hass, entry, async_add_entities): for mac_address, station in ambient.stations.items(): for condition in station[ATTR_MONITORED_CONDITIONS]: name, unit, kind, device_class = SENSOR_TYPES[condition] - if kind == TYPE_SENSOR: + if kind == SENSOR: sensor_list.append( AmbientWeatherSensor( ambient, diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index b41a107d126cbf..c735ebc57e704f 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -62,8 +62,8 @@ async def setup_platforms(): """Set up platforms and initiate connection.""" await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) await manager.init() diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index 7489ada3341a1c..c4cffe1c41ad0c 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -78,8 +78,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 6f16f7d5b312d2..23419f4df2d143 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -21,7 +21,6 @@ from .activity import ActivityStream from .const import ( - AUGUST_COMPONENTS, CONF_ACCESS_TOKEN_CACHE_FILE, CONF_INSTALL_ID, CONF_LOGIN_METHOD, @@ -32,6 +31,7 @@ DOMAIN, LOGIN_METHODS, MIN_TIME_BETWEEN_DETAIL_UPDATES, + PLATFORMS, VERIFICATION_CODE_KEY, ) from .exceptions import CannotConnect, InvalidAuth, RequireValidation @@ -137,9 +137,9 @@ async def async_setup_august(hass, config_entry, august_gateway): await hass.data[DOMAIN][entry_id][DATA_AUGUST].async_setup() - for component in AUGUST_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -209,8 +209,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in AUGUST_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/august/const.py b/homeassistant/components/august/const.py index a32e187647a1cf..57e0d5a7fb7cb3 100644 --- a/homeassistant/components/august/const.py +++ b/homeassistant/components/august/const.py @@ -43,4 +43,4 @@ LOGIN_METHODS = ["phone", "email"] -AUGUST_COMPONENTS = ["camera", "binary_sensor", "lock", "sensor"] +PLATFORMS = ["camera", "binary_sensor", "lock", "sensor"] diff --git a/homeassistant/components/aurora/__init__.py b/homeassistant/components/aurora/__init__.py index a187288e2e4b88..044b118b030e3b 100644 --- a/homeassistant/components/aurora/__init__.py +++ b/homeassistant/components/aurora/__init__.py @@ -79,9 +79,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): AURORA_API: api, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -92,8 +92,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index 3d3c997596af7d..e0b5a3c4f5d1ad 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): domain_entry = domain.setdefault(entry.entry_id, {}) product = domain_entry.setdefault(PRODUCT, product) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 0809018522ea07..c9c03dc654db28 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -86,9 +86,9 @@ async def async_setup_entry(hass, entry): if not hass.data[DOMAIN][entry.entry_id].available: raise ConfigEntryNotReady - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def blink_refresh(event_time=None): @@ -133,8 +133,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index cd993e0332a366..76ed9cdd12a0ab 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) -BLOOMSKY_TYPE = ["camera", "binary_sensor", "sensor"] +PLATFORMS = ["camera", "binary_sensor", "sensor"] DOMAIN = "bloomsky" @@ -32,7 +32,7 @@ def setup(hass, config): - """Set up the BloomSky component.""" + """Set up the BloomSky integration.""" api_key = config[DOMAIN][CONF_API_KEY] try: @@ -42,8 +42,8 @@ def setup(hass, config): hass.data[DOMAIN] = bloomsky - for component in BLOOMSKY_TYPE: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index a8bebfbc617b0a..3a653ec252f78f 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -59,7 +59,7 @@ CONF_USE_LOCATION: False, } -BMW_PLATFORMS = ["binary_sensor", "device_tracker", "lock", "notify", "sensor"] +PLATFORMS = ["binary_sensor", "device_tracker", "lock", "notify", "sensor"] UPDATE_INTERVAL = 5 # in minutes SERVICE_UPDATE_STATE = "update_state" @@ -138,13 +138,13 @@ def _update_all() -> None: await _async_update_all() - for platform in BMW_PLATFORMS: + for platform in PLATFORMS: if platform != NOTIFY_DOMAIN: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) - # set up notify platform, no entry support for notify component yet, + # set up notify platform, no entry support for notify platform yet, # have to use discovery to load platform. hass.async_create_task( discovery.async_load_platform( @@ -164,9 +164,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in BMW_PLATFORMS - if component != NOTIFY_DOMAIN + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + if platform != NOTIFY_DOMAIN ] ) ) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index ae9cc2111a2aa1..9ec0c662b8e31a 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -65,9 +65,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _async_remove_old_device_identifiers(config_entry_id, device_registry, hub) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -78,8 +78,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/braviatv/__init__.py b/homeassistant/components/braviatv/__init__.py index 46fd8675358df4..95debd9170debb 100644 --- a/homeassistant/components/braviatv/__init__.py +++ b/homeassistant/components/braviatv/__init__.py @@ -28,9 +28,9 @@ async def async_setup_entry(hass, config_entry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -41,8 +41,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index d7cf906a87ca88..9cddf0efdd5d35 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -46,9 +46,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = coordinator hass.data[DOMAIN][SNMP] = snmp_engine - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -59,8 +59,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index 64f2d00735eabe..b3fcf6e8641af0 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -107,9 +107,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool DATA_UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -120,8 +120,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index d6bf0ec4e124d3..6f1ac730f2fb16 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -117,9 +117,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -132,8 +132,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index a45cdc4006a0f6..7f8ec09cdde58a 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -115,9 +115,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry_data[CONF_CONFIG_LISTENER] = entry.add_update_listener(update_listener) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -134,8 +134,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/coronavirus/__init__.py b/homeassistant/components/coronavirus/__init__.py index fa8efebe154a81..d05c4cef862ba7 100644 --- a/homeassistant/components/coronavirus/__init__.py +++ b/homeassistant/components/coronavirus/__init__.py @@ -44,9 +44,9 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"]) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -57,8 +57,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index 16fd2b2ff56c26..d0bc109c0822ab 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -25,7 +25,7 @@ PARALLEL_UPDATES = 0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -COMPONENT_TYPES = ["climate", "sensor", "switch"] +PLATFORMS = ["climate", "sensor", "switch"] CONFIG_SCHEMA = vol.Schema( vol.All( @@ -83,9 +83,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): if not daikin_api: return False hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api}) - for component in COMPONENT_TYPES: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -94,8 +94,8 @@ async def async_unload_entry(hass, config_entry): """Unload a config entry.""" await asyncio.wait( [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENT_TYPES + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) hass.data[DOMAIN].pop(config_entry.entry_id) diff --git a/homeassistant/components/danfoss_air/__init__.py b/homeassistant/components/danfoss_air/__init__.py index b1dbf890eb970f..18780c10310a91 100644 --- a/homeassistant/components/danfoss_air/__init__.py +++ b/homeassistant/components/danfoss_air/__init__.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) -DANFOSS_AIR_PLATFORMS = ["sensor", "binary_sensor", "switch"] +PLATFORMS = ["sensor", "binary_sensor", "switch"] DOMAIN = "danfoss_air" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) @@ -29,7 +29,7 @@ def setup(hass, config): hass.data[DOMAIN] = DanfossAir(conf[CONF_HOST]) - for platform in DANFOSS_AIR_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index 67effa4e81b0ec..64667a9e616ab1 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -28,7 +28,7 @@ CONF_ALLOW_NEW_DEVICES = "allow_new_devices" CONF_MASTER_GATEWAY = "master" -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, COVER_DOMAIN, diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index a6cbb2acef9582..2b38f6956beb7b 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -26,7 +26,7 @@ NEW_LIGHT, NEW_SCENE, NEW_SENSOR, - SUPPORTED_PLATFORMS, + PLATFORMS, ) from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect @@ -184,10 +184,10 @@ async def async_setup(self) -> bool: ) return False - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, component + self.config_entry, platform ) ) @@ -259,9 +259,9 @@ async def async_reset(self): self.api.async_connection_status_callback = None self.api.close() - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, component + self.config_entry, platform ) for unsub_dispatcher in self.listeners: diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 09c3d27a1bc1cf..b32537ae44e01a 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -50,9 +50,9 @@ async def async_setup(hass, config): ) # Set up demo platforms - for component in COMPONENTS_WITH_DEMO_PLATFORM: + for platform in COMPONENTS_WITH_DEMO_PLATFORM: hass.async_create_task( - hass.helpers.discovery.async_load_platform(component, DOMAIN, {}, config) + hass.helpers.discovery.async_load_platform(platform, DOMAIN, {}, config) ) config.setdefault(ha.DOMAIN, {}) @@ -146,9 +146,9 @@ async def demo_start_listener(_event): async def async_setup_entry(hass, config_entry): """Set the config entry up.""" # Set up demo platforms with config entry - for component in COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM: + for platform in COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True diff --git a/homeassistant/components/dexcom/__init__.py b/homeassistant/components/dexcom/__init__.py index c2eb9bd466d917..5455c49232b094 100644 --- a/homeassistant/components/dexcom/__init__.py +++ b/homeassistant/components/dexcom/__init__.py @@ -70,9 +70,9 @@ async def async_update_data(): await hass.data[DOMAIN][entry.entry_id][COORDINATOR].async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -83,8 +83,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/directv/__init__.py b/homeassistant/components/directv/__init__.py index 22a97b9e82e85b..34b8c5cd30c65e 100644 --- a/homeassistant/components/directv/__init__.py +++ b/homeassistant/components/directv/__init__.py @@ -45,9 +45,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = dtv - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -58,8 +58,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 22db3c762736bf..8376d75ccbbe88 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -168,9 +168,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -188,8 +188,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/dsmr/__init__.py b/homeassistant/components/dsmr/__init__.py index 50823bb1d29293..cda3838bf783bf 100644 --- a/homeassistant/components/dsmr/__init__.py +++ b/homeassistant/components/dsmr/__init__.py @@ -44,8 +44,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/dunehd/__init__.py b/homeassistant/components/dunehd/__init__.py index a1fa456aa093c2..68a6f0d83de6f3 100644 --- a/homeassistant/components/dunehd/__init__.py +++ b/homeassistant/components/dunehd/__init__.py @@ -24,9 +24,9 @@ async def async_setup_entry(hass, config_entry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = player - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -37,8 +37,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index e52ec5946b6cd7..e19ab3198969dd 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -47,8 +47,8 @@ DEFAULT_PORT, DEFAULT_TEMPLATES, DOMAIN, - ENTITY_PLATFORMS, LOGGER, + PLATFORMS, SERVICE_REQUEST_AREA_PRESET, SERVICE_REQUEST_CHANNEL_LEVEL, ) @@ -267,14 +267,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # need to do it before the listener hass.data[DOMAIN][entry.entry_id] = bridge entry.add_update_listener(async_entry_changed) + if not await bridge.async_setup(): LOGGER.error("Could not set up bridge for entry %s", entry.data) hass.data[DOMAIN][entry.entry_id] = None raise ConfigEntryNotReady - for platform in ENTITY_PLATFORMS: + + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) + return True @@ -284,7 +287,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) tasks = [ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in ENTITY_PLATFORMS + for platform in PLATFORMS ] results = await asyncio.gather(*tasks) return False not in results diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index 5bf21801b3d1db..e024ba11802882 100644 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -16,14 +16,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import ( - ATTR_AREA, - ATTR_HOST, - ATTR_PACKET, - ATTR_PRESET, - ENTITY_PLATFORMS, - LOGGER, -) +from .const import ATTR_AREA, ATTR_HOST, ATTR_PACKET, ATTR_PRESET, LOGGER, PLATFORMS from .convert_config import convert_config @@ -107,7 +100,7 @@ def register_add_devices(self, platform: str, async_add_devices: Callable) -> No def add_devices_when_registered(self, devices: List[DynaliteBaseDevice]) -> None: """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" - for platform in ENTITY_PLATFORMS: + for platform in PLATFORMS: platform_devices = [ device for device in devices if device.category == platform ] diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 3f1e201f3fd5ce..eda43305461d70 100644 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -6,7 +6,7 @@ LOGGER = logging.getLogger(__package__) DOMAIN = "dynalite" -ENTITY_PLATFORMS = ["light", "switch", "cover"] +PLATFORMS = ["light", "switch", "cover"] CONF_ACTIVE = "active" diff --git a/homeassistant/components/dyson/__init__.py b/homeassistant/components/dyson/__init__.py index b39af2a2fd1599..d8023b42973265 100644 --- a/homeassistant/components/dyson/__init__.py +++ b/homeassistant/components/dyson/__init__.py @@ -17,7 +17,7 @@ DEFAULT_TIMEOUT = 5 DEFAULT_RETRY = 10 DYSON_DEVICES = "dyson_devices" -DYSON_PLATFORMS = ["sensor", "fan", "vacuum", "climate", "air_quality"] +PLATFORMS = ["sensor", "fan", "vacuum", "climate", "air_quality"] DOMAIN = "dyson" @@ -105,7 +105,7 @@ def setup(hass, config): # Start fan/sensors components if hass.data[DYSON_DEVICES]: _LOGGER.debug("Starting sensor/fan components") - for platform in DYSON_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index 26bfbe5b3dadc1..015ee1fbf6cab0 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -10,13 +10,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.util import Throttle -from .const import ( - _LOGGER, - CONF_REFRESH_TOKEN, - DATA_ECOBEE_CONFIG, - DOMAIN, - ECOBEE_PLATFORMS, -) +from .const import _LOGGER, CONF_REFRESH_TOKEN, DATA_ECOBEE_CONFIG, DOMAIN, PLATFORMS MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180) @@ -32,7 +26,7 @@ async def async_setup(hass, config): But, an "ecobee:" entry in configuration.yaml will trigger an import flow if a config entry doesn't already exist. If ecobee.conf exists, the import flow will attempt to import it and create a config entry, to assist users - migrating from the old ecobee component. Otherwise, the user will have to + migrating from the old ecobee integration. Otherwise, the user will have to continue setting up the integration via the config flow. """ hass.data[DATA_ECOBEE_CONFIG] = config.get(DOMAIN, {}) @@ -66,9 +60,9 @@ async def async_setup_entry(hass, entry): hass.data[DOMAIN] = data - for component in ECOBEE_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -120,7 +114,7 @@ async def async_unload_entry(hass, config_entry): hass.data.pop(DOMAIN) tasks = [] - for platform in ECOBEE_PLATFORMS: + for platform in PLATFORMS: tasks.append( hass.config_entries.async_forward_entry_unload(config_entry, platform) ) diff --git a/homeassistant/components/ecobee/const.py b/homeassistant/components/ecobee/const.py index 5ec3a0fcf96981..44abafe83804d6 100644 --- a/homeassistant/components/ecobee/const.py +++ b/homeassistant/components/ecobee/const.py @@ -37,7 +37,7 @@ "vulcanSmart": "ecobee4 Smart", } -ECOBEE_PLATFORMS = ["binary_sensor", "climate", "sensor", "weather"] +PLATFORMS = ["binary_sensor", "climate", "sensor", "weather"] MANUFACTURER = "ecobee" diff --git a/homeassistant/components/econet/__init__.py b/homeassistant/components/econet/__init__.py index dce4550eb1bc68..03ca9c76120b80 100644 --- a/homeassistant/components/econet/__init__.py +++ b/homeassistant/components/econet/__init__.py @@ -60,9 +60,9 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][API_CLIENT][config_entry.entry_id] = api hass.data[DOMAIN][EQUIPMENT][config_entry.entry_id] = equipment - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) api.subscribe() @@ -92,8 +92,8 @@ async def fetch_update(now): async def async_unload_entry(hass, entry): """Unload a EcoNet config entry.""" tasks = [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] await asyncio.gather(*tasks) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index d50c5d65d90622..3dddd670bb4cd1 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -52,7 +52,7 @@ _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = [ +PLATFORMS = [ "alarm_control_panel", "climate", "light", @@ -262,9 +262,9 @@ def _element_changed(element, changeset): "keypads": {}, } - for component in SUPPORTED_DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -289,8 +289,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_DOMAINS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/epson/__init__.py b/homeassistant/components/epson/__init__.py index c7278ed2cc9bd8..51d464dacb5c79 100644 --- a/homeassistant/components/epson/__init__.py +++ b/homeassistant/components/epson/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): _LOGGER.warning("Cannot connect to projector %s", entry.data[CONF_HOST]) return False hass.data[DOMAIN][entry.entry_id] = projector - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -60,8 +60,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/faa_delays/__init__.py b/homeassistant/components/faa_delays/__init__.py index b9def765123122..fd834da48d852f 100644 --- a/homeassistant/components/faa_delays/__init__.py +++ b/homeassistant/components/faa_delays/__init__.py @@ -39,9 +39,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -52,8 +52,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 51e31cf27adf17..d2a81468e05bf8 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -37,7 +37,7 @@ DOMAIN = "fibaro" FIBARO_CONTROLLERS = "fibaro_controllers" FIBARO_DEVICES = "fibaro_devices" -FIBARO_COMPONENTS = [ +PLATFORMS = [ "binary_sensor", "climate", "cover", @@ -365,21 +365,21 @@ def stop_fibaro(event): controller.disable_state_handler() hass.data[FIBARO_DEVICES] = {} - for component in FIBARO_COMPONENTS: - hass.data[FIBARO_DEVICES][component] = [] + for platform in PLATFORMS: + hass.data[FIBARO_DEVICES][platform] = [] for gateway in gateways: controller = FibaroController(gateway) if controller.connect(): hass.data[FIBARO_CONTROLLERS][controller.hub_serial] = controller - for component in FIBARO_COMPONENTS: - hass.data[FIBARO_DEVICES][component].extend( - controller.fibaro_devices[component] + for platform in PLATFORMS: + hass.data[FIBARO_DEVICES][platform].extend( + controller.fibaro_devices[platform] ) if hass.data[FIBARO_CONTROLLERS]: - for component in FIBARO_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, base_config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, base_config) for controller in hass.data[FIBARO_CONTROLLERS].values(): controller.enable_state_handler() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_fibaro) diff --git a/homeassistant/components/fireservicerota/__init__.py b/homeassistant/components/fireservicerota/__init__.py index bf5f3f6beea54a..513a572b2c2e44 100644 --- a/homeassistant/components/fireservicerota/__init__.py +++ b/homeassistant/components/fireservicerota/__init__.py @@ -26,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) -SUPPORTED_PLATFORMS = {SENSOR_DOMAIN, BINARYSENSOR_DOMAIN, SWITCH_DOMAIN} +PLATFORMS = [SENSOR_DOMAIN, BINARYSENSOR_DOMAIN, SWITCH_DOMAIN] async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -64,7 +64,7 @@ async def async_update_data(): DATA_COORDINATOR: coordinator, } - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -83,7 +83,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in SUPPORTED_PLATFORMS + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/flo/__init__.py b/homeassistant/components/flo/__init__.py index b57cdd5f8715ff..71f8a8bfe5c42c 100644 --- a/homeassistant/components/flo/__init__.py +++ b/homeassistant/components/flo/__init__.py @@ -49,9 +49,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): tasks = [device.async_refresh() for device in devices] await asyncio.gather(*tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -62,8 +62,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/flume/__init__.py b/homeassistant/components/flume/__init__.py index b9a5fc17682d2a..fb87588ac8247a 100644 --- a/homeassistant/components/flume/__init__.py +++ b/homeassistant/components/flume/__init__.py @@ -79,9 +79,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): FLUME_HTTP_SESSION: http_session, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -92,8 +92,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/flunearyou/__init__.py b/homeassistant/components/flunearyou/__init__.py index 46442f112b6487..8e5e3762f32c89 100644 --- a/homeassistant/components/flunearyou/__init__.py +++ b/homeassistant/components/flunearyou/__init__.py @@ -67,9 +67,9 @@ async def async_update(api_category): await asyncio.gather(*data_init_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -80,8 +80,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/foscam/__init__.py b/homeassistant/components/foscam/__init__.py index 6a2c961544f69d..1b3ae5e72163b1 100644 --- a/homeassistant/components/foscam/__init__.py +++ b/homeassistant/components/foscam/__init__.py @@ -22,9 +22,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up foscam from a config entry.""" - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.data[DOMAIN][entry.entry_id] = entry.data @@ -37,8 +37,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index 3c01657da4e25b..34f56ddc6f9f78 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -93,9 +93,9 @@ async def async_setup_entry(hass, entry): hass.data.setdefault(DOMAIN, {CONF_CONNECTIONS: {}, CONF_DEVICES: set()}) hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = fritz - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def logout_fritzbox(event): @@ -115,8 +115,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/fritzbox_callmonitor/__init__.py b/homeassistant/components/fritzbox_callmonitor/__init__.py index 933dd797dfcef4..0527b7164d39f7 100644 --- a/homeassistant/components/fritzbox_callmonitor/__init__.py +++ b/homeassistant/components/fritzbox_callmonitor/__init__.py @@ -59,9 +59,9 @@ async def async_setup_entry(hass, config_entry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -73,8 +73,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/garmin_connect/__init__.py b/homeassistant/components/garmin_connect/__init__.py index 896c243f386d04..c009124b024499 100644 --- a/homeassistant/components/garmin_connect/__init__.py +++ b/homeassistant/components/garmin_connect/__init__.py @@ -57,9 +57,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): garmin_data = GarminConnectData(hass, garmin_client) hass.data[DOMAIN][entry.entry_id] = garmin_data - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -70,8 +70,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/goalzero/__init__.py b/homeassistant/components/goalzero/__init__.py index ff60a9ac0435bf..e00b17ebae4b1e 100644 --- a/homeassistant/components/goalzero/__init__.py +++ b/homeassistant/components/goalzero/__init__.py @@ -76,8 +76,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index fdc82f14778f7d..465e1d730afa6c 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -104,9 +104,9 @@ def async_process_paired_sensor_uids(): ].async_add_listener(async_process_paired_sensor_uids) # Set up all of the Guardian entity platforms: - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -117,8 +117,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index c70d374dfe9cdd..159e760e2235d7 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -148,9 +148,9 @@ async def handle_api_call(call): ) data[config_entry.entry_id] = api - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) if not hass.services.has_service(DOMAIN, SERVICE_API_CALL): @@ -166,8 +166,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index 8445c7be937d85..c4056044ca0747 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.add_update_listener(_update_listener) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -108,8 +108,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index 301bd1976e66a3..baf4fd17f85b72 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -71,9 +71,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await update_all_devices(hass, entry) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -84,8 +84,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/homematicip_cloud/const.py b/homeassistant/components/homematicip_cloud/const.py index 4fb21febb40d11..31ccb8b9bc7248 100644 --- a/homeassistant/components/homematicip_cloud/const.py +++ b/homeassistant/components/homematicip_cloud/const.py @@ -16,7 +16,7 @@ DOMAIN = "homematicip_cloud" -COMPONENTS = [ +PLATFORMS = [ ALARM_CONTROL_PANEL_DOMAIN, BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 151807391b9589..5ad4efed1f674b 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -13,7 +13,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import HomeAssistantType -from .const import COMPONENTS, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN +from .const import HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN, PLATFORMS from .errors import HmipcConnectionError _LOGGER = logging.getLogger(__name__) @@ -102,10 +102,10 @@ async def async_setup(self, tries: int = 0) -> bool: "Connected to HomematicIP with HAP %s", self.config_entry.unique_id ) - for component in COMPONENTS: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, component + self.config_entry, platform ) ) return True @@ -215,9 +215,9 @@ async def async_reset(self) -> bool: self._retry_task.cancel() await self.home.disable_events() _LOGGER.info("Closed connection to HomematicIP cloud server") - for component in COMPONENTS: + for platform in PLATFORMS: await self.hass.config_entries.async_forward_entry_unload( - self.config_entry, component + self.config_entry, platform ) self.hmip_device_by_entity_id = {} return True diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 7555146ba8e4f4..2a5c5061cae614 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -132,9 +132,9 @@ async def async_update_data(): DEVICE_INFO: device_info, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -180,8 +180,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/hvv_departures/__init__.py b/homeassistant/components/hvv_departures/__init__.py index e003b25ea85a50..c90e5cb6d9c123 100644 --- a/homeassistant/components/hvv_departures/__init__.py +++ b/homeassistant/components/hvv_departures/__init__.py @@ -32,9 +32,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = hub - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -45,8 +45,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index 8c001c2b46789f..e5e6cb4494a9cb 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -281,8 +281,8 @@ async def async_instances_to_clients_raw(instances: List[Dict[str, Any]]) -> Non async def setup_then_listen() -> None: await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(config_entry, platform) + for platform in PLATFORMS ] ) assert hyperion_client @@ -310,8 +310,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index c539156b7595e7..959d86a7cc1787 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -55,7 +55,7 @@ IHC_CONTROLLER = "controller" IHC_INFO = "info" -IHC_PLATFORMS = ("binary_sensor", "light", "sensor", "switch") +PLATFORMS = ("binary_sensor", "light", "sensor", "switch") def validate_name(config): @@ -219,7 +219,7 @@ def validate_name(config): def setup(hass, config): - """Set up the IHC platform.""" + """Set up the IHC integration.""" conf = config.get(DOMAIN) for index, controller_conf in enumerate(conf): if not ihc_setup(hass, config, controller_conf, index): @@ -229,7 +229,7 @@ def setup(hass, config): def ihc_setup(hass, config, conf, controller_id): - """Set up the IHC component.""" + """Set up the IHC integration.""" url = conf[CONF_URL] username = conf[CONF_USERNAME] password = conf[CONF_PASSWORD] @@ -256,11 +256,11 @@ def ihc_setup(hass, config, conf, controller_id): def get_manual_configuration(hass, config, conf, ihc_controller, controller_id): """Get manual configuration for IHC devices.""" - for component in IHC_PLATFORMS: + for platform in PLATFORMS: discovery_info = {} - if component in conf: - component_setup = conf.get(component) - for sensor_cfg in component_setup: + 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], @@ -281,7 +281,7 @@ def get_manual_configuration(hass, config, conf, ihc_controller, controller_id): } discovery_info[name] = device if discovery_info: - discovery.load_platform(hass, component, DOMAIN, discovery_info, config) + discovery.load_platform(hass, platform, DOMAIN, discovery_info, config) def autosetup_ihc_products( @@ -304,21 +304,23 @@ def autosetup_ihc_products( except vol.Invalid as exception: _LOGGER.error("Invalid IHC auto setup data: %s", exception) return False + groups = project.findall(".//group") - for component in IHC_PLATFORMS: - component_setup = auto_setup_conf[component] - discovery_info = get_discovery_info(component_setup, groups, controller_id) + for platform in PLATFORMS: + platform_setup = auto_setup_conf[platform] + discovery_info = get_discovery_info(platform_setup, groups, controller_id) if discovery_info: - discovery.load_platform(hass, component, DOMAIN, discovery_info, config) + discovery.load_platform(hass, platform, DOMAIN, discovery_info, config) + return True -def get_discovery_info(component_setup, groups, controller_id): - """Get discovery info for specified IHC component.""" +def get_discovery_info(platform_setup, groups, controller_id): + """Get discovery info for specified IHC platform.""" discovery_data = {} for group in groups: groupname = group.attrib["name"] - for product_cfg in component_setup: + for product_cfg in platform_setup: products = group.findall(product_cfg[CONF_XPATH]) for product in products: nodes = product.findall(product_cfg[CONF_NODE]) diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index 0188671e07f888..7fb2178def4ced 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -17,7 +17,7 @@ CONF_UNITCODE, CONF_X10, DOMAIN, - INSTEON_COMPONENTS, + INSTEON_PLATFORMS, ON_OFF_EVENTS, ) from .schemas import convert_yaml_to_config_flow @@ -138,9 +138,9 @@ async def async_setup_entry(hass, entry): ) device = devices.add_x10_device(housecode, unitcode, x10_type, steps) - for component in INSTEON_COMPONENTS: + for platform in INSTEON_PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) for address in devices: diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index 07717dade9bca8..a40a0b0d4b022e 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -34,7 +34,7 @@ DOMAIN = "insteon" -INSTEON_COMPONENTS = [ +INSTEON_PLATFORMS = [ "binary_sensor", "climate", "cover", diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index 7a18da03ddcff6..c36b0cb7959961 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -66,9 +66,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not coordinator.last_update_success: raise ConfigEntryNotReady - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -79,8 +79,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index bf1725a036a9ed..4dc066d7629832 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -84,9 +84,9 @@ async def async_get_data_from_api(api_coro): await asyncio.gather(*init_data_update_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -97,8 +97,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 9fd844521d705d..d07fef97eddf76 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -31,7 +31,7 @@ ISY994_PROGRAMS, ISY994_VARIABLES, MANUFACTURER, - SUPPORTED_PLATFORMS, + PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, UNDO_UPDATE_LISTENER, ) @@ -111,7 +111,7 @@ async def async_setup_entry( hass_isy_data = hass.data[DOMAIN][entry.entry_id] hass_isy_data[ISY994_NODES] = {} - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass_isy_data[ISY994_NODES][platform] = [] hass_isy_data[ISY994_PROGRAMS] = {} @@ -176,7 +176,7 @@ async def async_setup_entry( await _async_get_or_create_isy_device_in_registry(hass, entry, isy) # Load platforms for the devices in the ISY controller that we support. - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -248,7 +248,7 @@ async def async_unload_entry( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in SUPPORTED_PLATFORMS + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index ef75a974377e5f..9fdef92c84f990 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -129,7 +129,7 @@ KEY_ACTIONS = "actions" KEY_STATUS = "status" -SUPPORTED_PLATFORMS = [BINARY_SENSOR, SENSOR, LOCK, FAN, COVER, LIGHT, SWITCH, CLIMATE] +PLATFORMS = [BINARY_SENSOR, SENSOR, LOCK, FAN, COVER, LIGHT, SWITCH, CLIMATE] SUPPORTED_PROGRAM_PLATFORMS = [BINARY_SENSOR, LOCK, FAN, COVER, SWITCH] SUPPORTED_BIN_SENS_CLASSES = ["moisture", "opening", "motion", "climate"] diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index f983458658335f..fecf18f789b536 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -38,12 +38,12 @@ KEY_ACTIONS, KEY_STATUS, NODE_FILTERS, + PLATFORMS, SUBNODE_CLIMATE_COOL, SUBNODE_CLIMATE_HEAT, SUBNODE_EZIO2X4_SENSORS, SUBNODE_FANLINC_LIGHT, SUBNODE_IOLINC_RELAY, - SUPPORTED_PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, TYPE_CATEGORY_SENSOR_ACTUATORS, TYPE_EZIO2X4, @@ -69,7 +69,7 @@ def _check_for_node_def( node_def_id = node.node_def_id - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if node_def_id in NODE_FILTERS[platform][FILTER_NODE_DEF_ID]: hass_isy_data[ISY994_NODES][platform].append(node) @@ -94,7 +94,7 @@ def _check_for_insteon_type( return False device_type = node.type - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( device_type.startswith(t) @@ -159,7 +159,7 @@ def _check_for_zwave_cat( return False device_type = node.zwave_props.category - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if any( device_type.startswith(t) @@ -198,7 +198,7 @@ def _check_for_uom_id( return True return False - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if node_uom in NODE_FILTERS[platform][FILTER_UOM]: hass_isy_data[ISY994_NODES][platform].append(node) @@ -235,7 +235,7 @@ def _check_for_states_in_uom( return True return False - platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform] + platforms = PLATFORMS if not single_platform else [single_platform] for platform in platforms: if node_uom == set(NODE_FILTERS[platform][FILTER_STATES]): hass_isy_data[ISY994_NODES][platform].append(node) diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index 3e9e0ae1d290cf..b9c4bcdeef2651 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -27,7 +27,7 @@ ISY994_NODES, ISY994_PROGRAMS, ISY994_VARIABLES, - SUPPORTED_PLATFORMS, + PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, ) @@ -279,7 +279,7 @@ async def async_cleanup_registry_entries(service) -> None: hass_isy_data = hass.data[DOMAIN][config_entry_id] uuid = hass_isy_data[ISY994_ISY].configuration["uuid"] - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: for node in hass_isy_data[ISY994_NODES][platform]: if hasattr(node, "address"): current_unique_ids.append(f"{uuid}_{node.address}") diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 080df7be6bf730..42bce53f911143 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -91,9 +91,9 @@ async def async_update_data(): await coordinator.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -104,8 +104,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/kaiterra/__init__.py b/homeassistant/components/kaiterra/__init__.py index d043dc15eafc26..eae14bd330e2e8 100644 --- a/homeassistant/components/kaiterra/__init__.py +++ b/homeassistant/components/kaiterra/__init__.py @@ -25,7 +25,7 @@ DEFAULT_PREFERRED_UNIT, DEFAULT_SCAN_INTERVAL, DOMAIN, - KAITERRA_COMPONENTS, + PLATFORMS, ) KAITERRA_DEVICE_SCHEMA = vol.Schema( @@ -54,7 +54,7 @@ async def async_setup(hass, config): - """Set up the Kaiterra components.""" + """Set up the Kaiterra integration.""" conf = config[DOMAIN] scan_interval = conf[CONF_SCAN_INTERVAL] @@ -76,11 +76,11 @@ async def _update(now=None): device.get(CONF_NAME) or device[CONF_TYPE], device[CONF_DEVICE_ID], ) - for component in KAITERRA_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( async_load_platform( hass, - component, + platform, DOMAIN, {CONF_NAME: device_name, CONF_DEVICE_ID: device_id}, config, diff --git a/homeassistant/components/kaiterra/const.py b/homeassistant/components/kaiterra/const.py index 583cd60085d920..f4cc5638c18a06 100644 --- a/homeassistant/components/kaiterra/const.py +++ b/homeassistant/components/kaiterra/const.py @@ -71,4 +71,4 @@ DEFAULT_PREFERRED_UNIT = [] DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) -KAITERRA_COMPONENTS = ["sensor", "air_quality"] +PLATFORMS = ["sensor", "air_quality"] diff --git a/homeassistant/components/keenetic_ndms2/__init__.py b/homeassistant/components/keenetic_ndms2/__init__.py index 42d747b5238de6..d0217b2a4f556e 100644 --- a/homeassistant/components/keenetic_ndms2/__init__.py +++ b/homeassistant/components/keenetic_ndms2/__init__.py @@ -44,9 +44,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -56,8 +56,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> """Unload a config entry.""" hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() - for component in PLATFORMS: - await hass.config_entries.async_forward_entry_unload(config_entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) router: KeeneticRouter = hass.data[DOMAIN][config_entry.entry_id][ROUTER] diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index b55ab9e1c9c3d8..1f012985447181 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -80,9 +80,9 @@ async def async_update_data(): DATA_COORDINATOR: coordinator, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -93,8 +93,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 4dcb25b3ea9a00..ea867e8c407f14 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -72,9 +72,9 @@ async def _close(event): DATA_REMOVE_LISTENER: remove_stop_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -85,8 +85,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 348eaeda3ac5aa..7ed4d5c1ac6c34 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -261,9 +261,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # async_connect will handle retries until it establishes a connection await client.async_connect() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # config entry specific data to enable unload @@ -278,8 +278,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/kulersky/__init__.py b/homeassistant/components/kulersky/__init__.py index 9459d44805c0a2..666239ad22b27c 100644 --- a/homeassistant/components/kulersky/__init__.py +++ b/homeassistant/components/kulersky/__init__.py @@ -16,9 +16,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Kuler Sky from a config entry.""" - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -29,8 +29,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py index 0c8f59c412743e..f00853af524837 100644 --- a/homeassistant/components/litejet/__init__.py +++ b/homeassistant/components/litejet/__init__.py @@ -59,9 +59,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN] = system - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -73,8 +73,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/litterrobot/__init__.py b/homeassistant/components/litterrobot/__init__.py index 19e76b9bb193d4..84e6822dc13f48 100644 --- a/homeassistant/components/litterrobot/__init__.py +++ b/homeassistant/components/litterrobot/__init__.py @@ -30,9 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except LitterRobotException as ex: raise ConfigEntryNotReady from ex - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -43,8 +43,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py index 056783ef6dace6..3364cd725c71be 100644 --- a/homeassistant/components/logi_circle/__init__.py +++ b/homeassistant/components/logi_circle/__init__.py @@ -47,6 +47,8 @@ ATTR_VALUE = "value" ATTR_DURATION = "duration" +PLATFORMS = ["camera", "sensor"] + SENSOR_SCHEMA = vol.Schema( { vol.Optional(CONF_MONITORED_CONDITIONS, default=list(LOGI_SENSORS)): vol.All( @@ -171,9 +173,9 @@ async def async_setup_entry(hass, entry): hass.data[DATA_LOGI] = logi_circle - for component in "camera", "sensor": + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def service_handler(service): @@ -219,8 +221,8 @@ async def shut_down(event=None): async def async_unload_entry(hass, entry): """Unload a config entry.""" - for component in "camera", "sensor": - await hass.config_entries.async_forward_entry_unload(entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) logi_circle = hass.data.pop(DATA_LOGI) diff --git a/homeassistant/components/lutron/__init__.py b/homeassistant/components/lutron/__init__.py index 2eeebac4c96ceb..b38968c36b8e2c 100644 --- a/homeassistant/components/lutron/__init__.py +++ b/homeassistant/components/lutron/__init__.py @@ -12,6 +12,8 @@ DOMAIN = "lutron" +PLATFORMS = ["light", "cover", "switch", "scene", "binary_sensor"] + _LOGGER = logging.getLogger(__name__) LUTRON_BUTTONS = "lutron_buttons" @@ -37,7 +39,7 @@ def setup(hass, base_config): - """Set up the Lutron component.""" + """Set up the Lutron integration.""" hass.data[LUTRON_BUTTONS] = [] hass.data[LUTRON_CONTROLLER] = None hass.data[LUTRON_DEVICES] = { @@ -92,8 +94,8 @@ def setup(hass, base_config): (area.name, area.occupancy_group) ) - for component in ("light", "cover", "switch", "scene", "binary_sensor"): - discovery.load_platform(hass, component, DOMAIN, {}, base_config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, base_config) return True diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 56cc7a78c96619..f93833acfc5a88 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -62,7 +62,7 @@ extra=vol.ALLOW_EXTRA, ) -LUTRON_CASETA_COMPONENTS = ["light", "switch", "cover", "scene", "fan", "binary_sensor"] +PLATFORMS = ["light", "switch", "cover", "scene", "fan", "binary_sensor"] async def async_setup(hass, base_config): @@ -125,7 +125,7 @@ async def async_setup_entry(hass, config_entry): bridge_device = devices[BRIDGE_DEVICE_ID] await _async_register_bridge_device(hass, config_entry.entry_id, bridge_device) # Store this bridge (keyed by entry_id) so it can be retrieved by the - # components we're setting up. + # platforms we're setting up. hass.data[DOMAIN][config_entry.entry_id] = { BRIDGE_LEAP: bridge, BRIDGE_DEVICE: bridge_device, @@ -139,9 +139,9 @@ async def async_setup_entry(hass, config_entry): # pico remotes to control other devices. await async_setup_lip(hass, config_entry, bridge.lip_devices) - for component in LUTRON_CASETA_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -289,8 +289,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in LUTRON_CASETA_COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 12990d66ba93c2..8b23d6f85cf7a0 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -113,9 +113,9 @@ async def async_update_data() -> Lyric: if not coordinator.last_update_success: raise ConfigEntryNotReady - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -126,8 +126,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py index 14b33df66c0f71..1b1e6584a0eaf2 100644 --- a/homeassistant/components/mazda/__init__.py +++ b/homeassistant/components/mazda/__init__.py @@ -121,9 +121,9 @@ async def with_timeout(task): raise ConfigEntryNotReady # Setup components - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -134,8 +134,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/metoffice/__init__.py b/homeassistant/components/metoffice/__init__.py index 8a68646240a412..87a5488fe0196b 100644 --- a/homeassistant/components/metoffice/__init__.py +++ b/homeassistant/components/metoffice/__init__.py @@ -61,9 +61,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if metoffice_data.now is None: raise ConfigEntryNotReady() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -74,8 +74,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index e1476270844251..4ba6a6f20869b3 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -104,8 +104,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/monoprice/__init__.py b/homeassistant/components/monoprice/__init__.py index 06883ddc8a8fd4..adc0b05bab7699 100644 --- a/homeassistant/components/monoprice/__init__.py +++ b/homeassistant/components/monoprice/__init__.py @@ -54,9 +54,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): FIRST_RUN: first_run, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -67,8 +67,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index 5d02d5a14a8577..c6226bed91043f 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -18,7 +18,7 @@ KEY_GATEWAY, KEY_MULTICAST_LISTENER, MANUFACTURER, - MOTION_PLATFORMS, + PLATFORMS, ) from .gateway import ConnectMotionGateway @@ -107,9 +107,9 @@ async def async_update_data(): sw_version=motion_gateway.protocol, ) - for component in MOTION_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -122,8 +122,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in MOTION_PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index 27f2310c7ce277..4b1f0ee052747b 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -3,7 +3,7 @@ MANUFACTURER = "Motion Blinds, Coulisse B.V." DEFAULT_GATEWAY_NAME = "Motion Blinds Gateway" -MOTION_PLATFORMS = ["cover", "sensor"] +PLATFORMS = ["cover", "sensor"] KEY_GATEWAY = "gateway" KEY_COORDINATOR = "coordinator" diff --git a/homeassistant/components/mullvad/__init__.py b/homeassistant/components/mullvad/__init__.py index 20a7092d58a513..b506be93100b56 100644 --- a/homeassistant/components/mullvad/__init__.py +++ b/homeassistant/components/mullvad/__init__.py @@ -43,9 +43,9 @@ async def async_get_mullvad_api_data(): hass.data[DOMAIN] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -56,8 +56,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/myq/__init__.py b/homeassistant/components/myq/__init__.py index 6b3a52ba7b06f1..b25751d7270db7 100644 --- a/homeassistant/components/myq/__init__.py +++ b/homeassistant/components/myq/__init__.py @@ -58,9 +58,9 @@ async def async_update_data(): hass.data[DOMAIN][entry.entry_id] = {MYQ_GATEWAY: myq, MYQ_COORDINATOR: coordinator} - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -71,8 +71,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 25b4d3106da5c9..33ac4932889225 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -30,7 +30,7 @@ DOMAIN, MYSENSORS_GATEWAYS, MYSENSORS_ON_UNLOAD, - SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, + PLATFORMS_WITH_ENTRY_SUPPORT, DevId, GatewayId, SensorType, @@ -192,7 +192,7 @@ async def finish(): await asyncio.gather( *[ hass.config_entries.async_forward_entry_setup(entry, platform) - for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT + for platform in PLATFORMS_WITH_ENTRY_SUPPORT ] ) await finish_setup(hass, entry, gateway) @@ -211,7 +211,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT + for platform in PLATFORMS_WITH_ENTRY_SUPPORT ] ) ) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 66bee128d4d27e..700c2bb930ae0a 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -158,7 +158,7 @@ for s_type_name in platform_types: TYPE_TO_PLATFORMS[s_type_name].append(platform) -SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT = set(PLATFORM_TYPES.keys()) - { +PLATFORMS_WITH_ENTRY_SUPPORT = set(PLATFORM_TYPES.keys()) - { "notify", "device_tracker", } diff --git a/homeassistant/components/n26/__init__.py b/homeassistant/components/n26/__init__.py index f8379cb310f188..b1e83cd5311300 100644 --- a/homeassistant/components/n26/__init__.py +++ b/homeassistant/components/n26/__init__.py @@ -36,7 +36,7 @@ extra=vol.ALLOW_EXTRA, ) -N26_COMPONENTS = ["sensor", "switch"] +PLATFORMS = ["sensor", "switch"] def setup(hass, config): @@ -65,9 +65,9 @@ def setup(hass, config): hass.data[DOMAIN] = {} hass.data[DOMAIN][DATA] = api_data_list - # Load components for supported devices - for component in N26_COMPONENTS: - load_platform(hass, component, DOMAIN, {}, config) + # Load platforms for supported devices + for platform in PLATFORMS: + load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py index 1d9d3de4f89a80..bb0db8ebd8559a 100644 --- a/homeassistant/components/neato/__init__.py +++ b/homeassistant/components/neato/__init__.py @@ -103,9 +103,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[NEATO_LOGIN] = hub - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index b0abd24012a023..cd3f6ed9ed3361 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -198,9 +198,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN].pop(DATA_NEST_UNAVAILABLE, None) hass.data[DOMAIN][DATA_SUBSCRIBER] = subscriber - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -217,8 +217,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nest/legacy/__init__.py b/homeassistant/components/nest/legacy/__init__.py index 218b01fd71b38d..11bc1ab33ff642 100644 --- a/homeassistant/components/nest/legacy/__init__.py +++ b/homeassistant/components/nest/legacy/__init__.py @@ -28,6 +28,8 @@ _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) +PLATFORMS = ["climate", "camera", "sensor", "binary_sensor"] + # Configuration for the legacy nest API SERVICE_CANCEL_ETA = "cancel_eta" SERVICE_SET_ETA = "set_eta" @@ -131,9 +133,9 @@ async def async_setup_legacy_entry(hass, entry): if not await hass.async_add_executor_job(hass.data[DATA_NEST].initialize): return False - for component in "climate", "camera", "sensor", "binary_sensor": + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def validate_structures(target_structures): diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 5827486429ab79..d76133c91abf8d 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -111,9 +111,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await data_handler.async_setup() hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] = data_handler - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def unregister_webhook(_): @@ -214,8 +214,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nexia/__init__.py b/homeassistant/components/nexia/__init__.py index fc2ef7fef354a6..4dde2084400114 100644 --- a/homeassistant/components/nexia/__init__.py +++ b/homeassistant/components/nexia/__init__.py @@ -80,9 +80,9 @@ async def _async_update_data(): UPDATE_COORDINATOR: coordinator, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -93,8 +93,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nextcloud/__init__.py b/homeassistant/components/nextcloud/__init__.py index 1a773040980781..efa5b2e2f32cfc 100644 --- a/homeassistant/components/nextcloud/__init__.py +++ b/homeassistant/components/nextcloud/__init__.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "nextcloud" -NEXTCLOUD_COMPONENTS = ("sensor", "binary_sensor") +PLATFORMS = ("sensor", "binary_sensor") SCAN_INTERVAL = timedelta(seconds=60) # Validate user configuration @@ -116,8 +116,8 @@ def nextcloud_update(event_time): # Update sensors on time interval track_time_interval(hass, nextcloud_update, conf[CONF_SCAN_INTERVAL]) - for component in NEXTCLOUD_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/nightscout/__init__.py b/homeassistant/components/nightscout/__init__.py index 4c8ab756ddf42d..dfaaf28048e804 100644 --- a/homeassistant/components/nightscout/__init__.py +++ b/homeassistant/components/nightscout/__init__.py @@ -48,9 +48,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry_type="service", ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -61,8 +61,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 6417926702df83..bace7f14556c9c 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -81,7 +81,7 @@ extra=vol.ALLOW_EXTRA, ) -LEAF_COMPONENTS = ["sensor", "switch", "binary_sensor"] +PLATFORMS = ["sensor", "switch", "binary_sensor"] SIGNAL_UPDATE_LEAF = "nissan_leaf_update" @@ -94,7 +94,7 @@ def setup(hass, config): - """Set up the Nissan Leaf component.""" + """Set up the Nissan Leaf integration.""" async def async_handle_update(service): """Handle service to update leaf data from Nissan servers.""" @@ -170,8 +170,8 @@ def setup_leaf(car_config): data_store = LeafDataStore(hass, leaf, car_config) hass.data[DATA_LEAF][leaf.vin] = data_store - for component in LEAF_COMPONENTS: - load_platform(hass, component, DOMAIN, {}, car_config) + for platform in PLATFORMS: + load_platform(hass, platform, DOMAIN, {}, car_config) async_track_point_in_utc_time( hass, data_store.async_update_data, utcnow() + INITIAL_UPDATE diff --git a/homeassistant/components/nuheat/__init__.py b/homeassistant/components/nuheat/__init__.py index 2f51ae377a5200..9fe4764e1afecd 100644 --- a/homeassistant/components/nuheat/__init__.py +++ b/homeassistant/components/nuheat/__init__.py @@ -80,9 +80,9 @@ async def _async_update_data(): hass.data[DOMAIN][entry.entry_id] = (thermostat, coordinator) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -93,8 +93,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index 4af3e0d8ed45bf..6aa945a52bf53a 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -11,7 +11,7 @@ from .const import DEFAULT_PORT, DOMAIN -NUKI_PLATFORMS = ["lock"] +PLATFORMS = ["lock"] UPDATE_INTERVAL = timedelta(seconds=30) NUKI_SCHEMA = vol.Schema( @@ -29,7 +29,7 @@ async def async_setup(hass, config): """Set up the Nuki component.""" hass.data.setdefault(DOMAIN, {}) - for platform in NUKI_PLATFORMS: + for platform in PLATFORMS: confs = config.get(platform) if confs is None: continue diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 5669b8a5c3bb63..3aba05ddd2b1f6 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -101,9 +101,9 @@ async def async_update_data(): UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -178,8 +178,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index a0958be8d9e55c..83fca527d4376c 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -157,9 +157,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator_forecast.async_refresh() await coordinator_forecast_hourly.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -169,8 +169,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/nzbget/__init__.py b/homeassistant/components/nzbget/__init__.py index eba5eb58baf712..57a741f0469c4f 100644 --- a/homeassistant/components/nzbget/__init__.py +++ b/homeassistant/components/nzbget/__init__.py @@ -107,9 +107,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool DATA_UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) _async_register_services(hass, coordinator) @@ -122,8 +122,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/omnilogic/__init__.py b/homeassistant/components/omnilogic/__init__.py index ff4dd93a0e1537..7196cd28cd7d85 100644 --- a/homeassistant/components/omnilogic/__init__.py +++ b/homeassistant/components/omnilogic/__init__.py @@ -66,9 +66,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): OMNI_API: api, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -79,8 +79,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/ondilo_ico/__init__.py b/homeassistant/components/ondilo_ico/__init__.py index 69538c5e8b384a..4dac83815ba8e6 100644 --- a/homeassistant/components/ondilo_ico/__init__.py +++ b/homeassistant/components/ondilo_ico/__init__.py @@ -35,9 +35,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = api.OndiloClient(hass, entry, implementation) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -48,8 +48,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index 6d64478aa7217b..779bc6dfd3a0b5 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -5,7 +5,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, SUPPORTED_PLATFORMS +from .const import DOMAIN, PLATFORMS from .onewirehub import CannotConnect, OneWireHub @@ -26,9 +26,9 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry): hass.data[DOMAIN][config_entry.unique_id] = onewirehub - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -38,8 +38,8 @@ async def async_unload_entry(hass: HomeAssistantType, config_entry: ConfigEntry) unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index e68039078e9df1..54b18f7c9053a4 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -61,7 +61,7 @@ SWITCH_TYPE_PIO: [None, None], } -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN, diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index b332b7a795ac99..0eb39064db7cd7 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -88,9 +88,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if device.capabilities.events: platforms += ["binary_sensor", "sensor"] - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.async_stop) @@ -111,8 +111,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in platforms + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms ] ) ) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index ce75365771d4e1..387b7547514f07 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -69,9 +69,9 @@ async def async_setup_entry(hass, config_entry): LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady from err - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @_verify_domain_control @@ -110,8 +110,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/openweathermap/__init__.py b/homeassistant/components/openweathermap/__init__.py index 4754c4b2eff5cc..82b1545e359389 100644 --- a/homeassistant/components/openweathermap/__init__.py +++ b/homeassistant/components/openweathermap/__init__.py @@ -17,7 +17,6 @@ from homeassistant.exceptions import ConfigEntryNotReady from .const import ( - COMPONENTS, CONF_LANGUAGE, CONFIG_FLOW_VERSION, DOMAIN, @@ -25,6 +24,7 @@ ENTRY_WEATHER_COORDINATOR, FORECAST_MODE_FREE_DAILY, FORECAST_MODE_ONECALL_DAILY, + PLATFORMS, UPDATE_LISTENER, ) from .weather_update_coordinator import WeatherUpdateCoordinator @@ -65,9 +65,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): ENTRY_WEATHER_COORDINATOR: weather_coordinator, } - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) update_listener = config_entry.add_update_listener(async_update_options) @@ -108,8 +108,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index c70afa9cab06a4..7c8e8aeee57a2a 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -62,7 +62,7 @@ SENSOR_UNIT = "sensor_unit" SENSOR_DEVICE_CLASS = "sensor_device_class" UPDATE_LISTENER = "update_listener" -COMPONENTS = ["sensor", "weather"] +PLATFORMS = ["sensor", "weather"] FORECAST_MODE_HOURLY = "hourly" FORECAST_MODE_DAILY = "daily" diff --git a/homeassistant/components/ozw/__init__.py b/homeassistant/components/ozw/__init__.py index a75c05416dcefb..3f54c731e39147 100644 --- a/homeassistant/components/ozw/__init__.py +++ b/homeassistant/components/ozw/__init__.py @@ -267,8 +267,8 @@ def async_receive_message(msg): async def start_platforms(): await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) if entry.data.get(CONF_USE_ADDON): @@ -310,8 +310,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 9902d39a56c96f..449515802b9b76 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -104,9 +104,9 @@ async def async_setup_entry(hass, config_entry): data={**config, ATTR_DEVICE_INFO: device_info}, ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -117,8 +117,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index dd3e8d194e9bb2..b3abaa2813296e 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -47,9 +47,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await coordinator.async_refresh() hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -60,8 +60,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index c14395319d41de..280de59898b80d 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -139,9 +139,9 @@ async def async_update_data(): if single_master_thermostat is None: platforms = SENSOR_PLATFORMS - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -160,8 +160,8 @@ async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS_GATEWAY + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS_GATEWAY ] ) ) diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py index 2a7ce4497bb4bc..858f87e74f8907 100644 --- a/homeassistant/components/plum_lightpad/__init__.py +++ b/homeassistant/components/plum_lightpad/__init__.py @@ -67,9 +67,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = plum - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) def cleanup(event): diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 13dce13c0dacea..a92f4f3f14f3e9 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -39,6 +39,8 @@ DATA_CONFIG_ENTRY_LOCK = "point_config_entry_lock" CONFIG_ENTRY_IS_SETUP = "point_config_entry_is_setup" +PLATFORMS = ["binary_sensor", "sensor"] + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -137,8 +139,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): session = hass.data[DOMAIN].pop(entry.entry_id) await session.remove_webhook() - for component in ("binary_sensor", "sensor"): - await hass.config_entries.async_forward_entry_unload(entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(entry, platform) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) @@ -186,18 +188,18 @@ async def _sync(self): async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) return - async def new_device(device_id, component): + async def new_device(device_id, platform): """Load new device.""" - config_entries_key = f"{component}.{DOMAIN}" + config_entries_key = f"{platform}.{DOMAIN}" async with self._hass.data[DATA_CONFIG_ENTRY_LOCK]: if config_entries_key not in self._hass.data[CONFIG_ENTRY_IS_SETUP]: await self._hass.config_entries.async_forward_entry_setup( - self._config_entry, component + self._config_entry, platform ) self._hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) async_dispatcher_send( - self._hass, POINT_DISCOVERY_NEW.format(component, DOMAIN), device_id + self._hass, POINT_DISCOVERY_NEW.format(platform, DOMAIN), device_id ) self._is_available = True @@ -207,8 +209,8 @@ async def new_device(device_id, component): self._known_homes.add(home_id) for device in self._client.devices: if device.device_id not in self._known_devices: - for component in ("sensor", "binary_sensor"): - await new_device(device.device_id, component) + for platform in PLATFORMS: + await new_device(device.device_id, platform) self._known_devices.add(device.device_id) async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY) diff --git a/homeassistant/components/poolsense/__init__.py b/homeassistant/components/poolsense/__init__.py index 315816f4e1c107..3dc9ace1b8f12c 100644 --- a/homeassistant/components/poolsense/__init__.py +++ b/homeassistant/components/poolsense/__init__.py @@ -56,9 +56,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -69,8 +69,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index b392b71374142d..b529680da50f2c 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -158,9 +158,9 @@ async def async_update_data(): await coordinator.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -228,8 +228,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/progettihwsw/__init__.py b/homeassistant/components/progettihwsw/__init__.py index 02418c963d4cfa..7597b2ff1a2927 100644 --- a/homeassistant/components/progettihwsw/__init__.py +++ b/homeassistant/components/progettihwsw/__init__.py @@ -30,9 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # Check board validation again to load new values to API. await hass.data[DOMAIN][entry.entry_id].check_board() - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -43,8 +43,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/proxmoxve/__init__.py b/homeassistant/components/proxmoxve/__init__.py index 2f42ca8fe9ecd4..663afc69b1d23d 100644 --- a/homeassistant/components/proxmoxve/__init__.py +++ b/homeassistant/components/proxmoxve/__init__.py @@ -187,10 +187,10 @@ def poll_api() -> dict: # Fetch initial data await coordinator.async_refresh() - for component in PLATFORMS: + for platform in PLATFORMS: await hass.async_create_task( hass.helpers.discovery.async_load_platform( - component, DOMAIN, {"config": config}, config + platform, DOMAIN, {"config": config}, config ) ) diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 672ff272344cd5..30015dcf8c1de0 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = ["switch", "binary_sensor"] +PLATFORMS = ["switch", "binary_sensor"] CONFIG_SCHEMA = cv.deprecated(DOMAIN) @@ -39,8 +39,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_DOMAINS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) @@ -99,13 +99,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): webhook_url, ) - # Enable component + # Enable platform hass.data[DOMAIN][entry.entry_id] = person async_register_webhook(hass, webhook_id, entry.entry_id) - for component in SUPPORTED_DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py index 83c358c480b0e7..d8334470b60a11 100644 --- a/homeassistant/components/rainbird/__init__.py +++ b/homeassistant/components/rainbird/__init__.py @@ -16,7 +16,7 @@ CONF_ZONES = "zones" -SUPPORTED_PLATFORMS = [switch.DOMAIN, sensor.DOMAIN, binary_sensor.DOMAIN] +PLATFORMS = [switch.DOMAIN, sensor.DOMAIN, binary_sensor.DOMAIN] _LOGGER = logging.getLogger(__name__) @@ -80,7 +80,7 @@ def _setup_controller(hass, controller_config, config): return False hass.data[DATA_RAINBIRD].append(controller) _LOGGER.debug("Rain Bird Controller %d set to: %s", position, server) - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform( hass, platform, diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 98fbdbcf4015e9..b4d8510cc77ae3 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -155,9 +155,9 @@ async def async_update(api_category: str) -> dict: await asyncio.gather(*controller_init_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.data[DOMAIN][DATA_LISTENER] = entry.add_update_listener(async_reload_entry) @@ -170,8 +170,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py index 0600d73d8a1bf7..0d6961831d80d1 100644 --- a/homeassistant/components/recollect_waste/__init__.py +++ b/homeassistant/components/recollect_waste/__init__.py @@ -61,9 +61,9 @@ async def async_get_pickup_events() -> List[PickupEvent]: hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) hass.data[DOMAIN][DATA_LISTENER][entry.entry_id] = entry.add_update_listener( @@ -83,8 +83,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 5952cb62a71804..c9e8c0dee7576d 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -151,7 +151,7 @@ def _ensure_device(value): extra=vol.ALLOW_EXTRA, ) -DOMAINS = ["switch", "sensor", "light", "binary_sensor", "cover"] +PLATFORMS = ["switch", "sensor", "light", "binary_sensor", "cover"] async def async_setup(hass, config): @@ -202,9 +202,9 @@ async def async_setup_entry(hass, entry: config_entries.ConfigEntry): ) return False - for domain in DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, domain) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -215,8 +215,8 @@ async def async_unload_entry(hass, entry: config_entries.ConfigEntry): if not all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in DOMAINS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ): diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index ed22575bcccdde..a4d005d3c44a8f 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -99,9 +99,9 @@ def token_updater(token): ), } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) if hass.services.has_service(DOMAIN, "update"): @@ -126,8 +126,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index 685fee43adfd77..f5ebc4e13efa06 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -63,8 +63,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def start_platforms(): await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) await events_coordinator.async_refresh() @@ -79,8 +79,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/rituals_perfume_genie/__init__.py b/homeassistant/components/rituals_perfume_genie/__init__.py index ba11206d49650d..f2fd13a9ef4516 100644 --- a/homeassistant/components/rituals_perfume_genie/__init__.py +++ b/homeassistant/components/rituals_perfume_genie/__init__.py @@ -37,9 +37,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {})[entry.entry_id] = account - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -50,8 +50,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index a75fc813fb917f..06e8d0ae848306 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -54,9 +54,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[DOMAIN][entry.entry_id] = coordinator - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -67,8 +67,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index 658c230c3a77a4..6de775e1d997ff 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -8,7 +8,7 @@ from homeassistant import exceptions from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD -from .const import BLID, COMPONENTS, CONF_BLID, CONF_CONTINUOUS, DOMAIN, ROOMBA_SESSION +from .const import BLID, CONF_BLID, CONF_CONTINUOUS, DOMAIN, PLATFORMS, ROOMBA_SESSION _LOGGER = logging.getLogger(__name__) @@ -51,9 +51,9 @@ async def async_setup_entry(hass, config_entry): BLID: config_entry.data[CONF_BLID], } - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) if not config_entry.update_listeners: @@ -105,8 +105,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 2ffb34eb7c8773..0509cd92116278 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -1,6 +1,6 @@ """The roomba constants.""" DOMAIN = "roomba" -COMPONENTS = ["sensor", "binary_sensor", "vacuum"] +PLATFORMS = ["sensor", "binary_sensor", "vacuum"] CONF_CERT = "certificate" CONF_CONTINUOUS = "continuous" CONF_BLID = "blid" diff --git a/homeassistant/components/ruckus_unleashed/__init__.py b/homeassistant/components/ruckus_unleashed/__init__.py index ed38f5a2a3a3c4..2bab1747f0f9ef 100644 --- a/homeassistant/components/ruckus_unleashed/__init__.py +++ b/homeassistant/components/ruckus_unleashed/__init__.py @@ -84,8 +84,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 2d6c0c41e5bd19..1689d8c48341d9 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -139,9 +139,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): SENSE_DISCOVERED_DEVICES_DATA: sense_discovered_devices, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def async_sense_update(_): @@ -169,8 +169,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/sharkiq/__init__.py b/homeassistant/components/sharkiq/__init__.py index b7684f988553da..e43338cd5b0f9a 100644 --- a/homeassistant/components/sharkiq/__init__.py +++ b/homeassistant/components/sharkiq/__init__.py @@ -14,7 +14,7 @@ from homeassistant import exceptions from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from .const import _LOGGER, API_TIMEOUT, COMPONENTS, DOMAIN +from .const import _LOGGER, API_TIMEOUT, DOMAIN, PLATFORMS from .update_coordinator import SharkIqUpdateCoordinator @@ -70,9 +70,9 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -98,8 +98,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/sharkiq/const.py b/homeassistant/components/sharkiq/const.py index e0feb306f77882..8f4c56b65dba74 100644 --- a/homeassistant/components/sharkiq/const.py +++ b/homeassistant/components/sharkiq/const.py @@ -5,7 +5,7 @@ _LOGGER = logging.getLogger(__package__) API_TIMEOUT = 20 -COMPONENTS = ["vacuum"] +PLATFORMS = ["vacuum"] DOMAIN = "sharkiq" SHARK = "Shark" UPDATE_INTERVAL = timedelta(seconds=30) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index ccb5212752546e..52dae7f164dbef 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -134,9 +134,9 @@ async def async_device_setup( ] = ShellyDeviceRestWrapper(hass, device) platforms = PLATFORMS - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) @@ -323,8 +323,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in platforms + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms ] ) ) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 495ba29fefb524..a6d95e1d9af8b3 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -71,7 +71,7 @@ DEFAULT_SOCKET_MIN_RETRY = 15 -SUPPORTED_PLATFORMS = ( +PLATFORMS = ( "alarm_control_panel", "binary_sensor", "lock", @@ -219,7 +219,7 @@ async def async_setup_entry(hass, config_entry): ) await simplisafe.async_init() - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, platform) ) @@ -327,8 +327,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smappee/__init__.py b/homeassistant/components/smappee/__init__.py index aed67c5c1676b8..f803f38b8ea038 100644 --- a/homeassistant/components/smappee/__init__.py +++ b/homeassistant/components/smappee/__init__.py @@ -21,7 +21,7 @@ CONF_SERIALNUMBER, DOMAIN, MIN_TIME_BETWEEN_UPDATES, - SMAPPEE_PLATFORMS, + PLATFORMS, TOKEN_URL, ) @@ -92,9 +92,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = SmappeeBase(hass, smappee) - for component in SMAPPEE_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -105,8 +105,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SMAPPEE_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smappee/const.py b/homeassistant/components/smappee/const.py index 2c69f1ccb961bd..fc059509ced65d 100644 --- a/homeassistant/components/smappee/const.py +++ b/homeassistant/components/smappee/const.py @@ -12,7 +12,7 @@ ENV_CLOUD = "cloud" ENV_LOCAL = "local" -SMAPPEE_PLATFORMS = ["binary_sensor", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "sensor", "switch"] SUPPORTED_LOCAL_DEVICES = ("Smappee1", "Smappee2") diff --git a/homeassistant/components/smart_meter_texas/__init__.py b/homeassistant/components/smart_meter_texas/__init__.py index 7b1c6cfa9b7173..3180248fcd1060 100644 --- a/homeassistant/components/smart_meter_texas/__init__.py +++ b/homeassistant/components/smart_meter_texas/__init__.py @@ -83,9 +83,9 @@ async def async_update_data(): asyncio.create_task(coordinator.async_refresh()) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -122,8 +122,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smarthab/__init__.py b/homeassistant/components/smarthab/__init__.py index c826b5d8f4d2b0..c259ef71aab961 100644 --- a/homeassistant/components/smarthab/__init__.py +++ b/homeassistant/components/smarthab/__init__.py @@ -13,7 +13,7 @@ DOMAIN = "smarthab" DATA_HUB = "hub" -COMPONENTS = ["light", "cover"] +PLATFORMS = ["light", "cover"] _LOGGER = logging.getLogger(__name__) @@ -69,9 +69,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): # Pass hub object to child platforms hass.data[DOMAIN][entry.entry_id] = {DATA_HUB: hub} - for component in COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -83,8 +83,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): result = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index d184a3ca6ce20f..77ef913c629cf5 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -36,8 +36,8 @@ DATA_MANAGER, DOMAIN, EVENT_BUTTON, + PLATFORMS, SIGNAL_SMARTTHINGS_UPDATE, - SUPPORTED_PLATFORMS, TOKEN_REFRESH_INTERVAL, ) from .smartapp import ( @@ -184,9 +184,9 @@ async def retrieve_device_status(device): ) return False - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -213,8 +213,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): broker.disconnect() tasks = [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] return all(await asyncio.gather(*tasks)) @@ -293,11 +293,13 @@ def _assign_capabilities(self, devices: Iterable): for device in devices: capabilities = device.capabilities.copy() slots = {} - for platform_name in SUPPORTED_PLATFORMS: - platform = importlib.import_module(f".{platform_name}", self.__module__) - if not hasattr(platform, "get_capabilities"): + for platform in PLATFORMS: + platform_module = importlib.import_module( + f".{platform}", self.__module__ + ) + if not hasattr(platform_module, "get_capabilities"): continue - assigned = platform.get_capabilities(capabilities) + assigned = platform_module.get_capabilities(capabilities) if not assigned: continue # Draw-down capabilities and set slot assignment @@ -305,7 +307,7 @@ def _assign_capabilities(self, devices: Iterable): if capability not in capabilities: continue capabilities.remove(capability) - slots[capability] = platform_name + slots[capability] = platform assignments[device.device_id] = slots return assignments diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 03188411f07bd7..a7aa9066dd2f74 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -31,7 +31,7 @@ # Ordered 'specific to least-specific platform' in order for capabilities # to be drawn-down and represented by the most appropriate platform. -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ "climate", "fan", "light", diff --git a/homeassistant/components/sms/__init__.py b/homeassistant/components/sms/__init__.py index 8752dfd90da8a1..c4fdb38ebaa633 100644 --- a/homeassistant/components/sms/__init__.py +++ b/homeassistant/components/sms/__init__.py @@ -46,9 +46,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not gateway: return False hass.data[DOMAIN][SMS_GATEWAY] = gateway - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -59,8 +59,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/soma/__init__.py b/homeassistant/components/soma/__init__.py index bd5695cb7ecf05..3f15199c162bed 100644 --- a/homeassistant/components/soma/__init__.py +++ b/homeassistant/components/soma/__init__.py @@ -24,7 +24,7 @@ extra=vol.ALLOW_EXTRA, ) -SOMA_COMPONENTS = ["cover", "sensor"] +PLATFORMS = ["cover", "sensor"] async def async_setup(hass, config): @@ -50,9 +50,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): devices = await hass.async_add_executor_job(hass.data[DOMAIN][API].list_devices) hass.data[DOMAIN][DEVICES] = devices["shades"] - for component in SOMA_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -63,8 +63,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SOMA_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index ac32b9d53791d9..0a83ac2fc52b08 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -48,7 +48,7 @@ extra=vol.ALLOW_EXTRA, ) -SOMFY_COMPONENTS = ["climate", "cover", "sensor", "switch"] +PLATFORMS = ["climate", "cover", "sensor", "switch"] async def async_setup(hass, config): @@ -134,9 +134,9 @@ async def _update_all_devices(): model=hub.type, ) - for component in SOMFY_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -147,8 +147,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): hass.data[DOMAIN].pop(API, None) await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SOMFY_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) return True diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index d371fd96310ca0..a2ade387c019f6 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -23,7 +23,7 @@ DEFAULT_PORT, DOMAIN, MYLINK_STATUS, - SOMFY_MYLINK_COMPONENTS, + PLATFORMS, ) CONFIG_OPTIONS = (CONF_DEFAULT_REVERSE, CONF_ENTITY_CONFIG) @@ -121,9 +121,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UNDO_UPDATE_LISTENER: undo_listener, } - for component in SOMFY_MYLINK_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -182,8 +182,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SOMFY_MYLINK_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/somfy_mylink/const.py b/homeassistant/components/somfy_mylink/const.py index a7cbf864cd958e..bf58ee1af929a9 100644 --- a/homeassistant/components/somfy_mylink/const.py +++ b/homeassistant/components/somfy_mylink/const.py @@ -14,6 +14,6 @@ MYLINK_STATUS = "mylink_status" DOMAIN = "somfy_mylink" -SOMFY_MYLINK_COMPONENTS = ["cover"] +PLATFORMS = ["cover"] MANUFACTURER = "Somfy" diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 3e2a5498c553c2..636653dad00d17 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -84,9 +84,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool DATA_UNDO_UPDATE_LISTENER: undo_listener, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -97,8 +97,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/spider/__init__.py b/homeassistant/components/spider/__init__.py index b0c34ae5a08100..d9ccdfd248a130 100644 --- a/homeassistant/components/spider/__init__.py +++ b/homeassistant/components/spider/__init__.py @@ -66,9 +66,9 @@ async def async_setup_entry(hass, entry): hass.data[DOMAIN][entry.entry_id] = api - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -79,8 +79,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/streamlabswater/__init__.py b/homeassistant/components/streamlabswater/__init__.py index 336e92358ee667..0a49e128c2f7dc 100644 --- a/homeassistant/components/streamlabswater/__init__.py +++ b/homeassistant/components/streamlabswater/__init__.py @@ -17,7 +17,7 @@ AWAY_MODE_AWAY = "away" AWAY_MODE_HOME = "home" -STREAMLABSWATER_COMPONENTS = ["sensor", "binary_sensor"] +PLATFORMS = ["sensor", "binary_sensor"] CONF_LOCATION_ID = "location_id" @@ -39,7 +39,7 @@ def setup(hass, config): - """Set up the streamlabs water component.""" + """Set up the streamlabs water integration.""" conf = config[DOMAIN] api_key = conf.get(CONF_API_KEY) @@ -74,8 +74,8 @@ def setup(hass, config): "location_name": location_name, } - for component in STREAMLABSWATER_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) def set_away_mode(service): """Set the StreamLabsWater Away Mode.""" diff --git a/homeassistant/components/subaru/__init__.py b/homeassistant/components/subaru/__init__.py index 63bc644b50ab4a..04f6111167105f 100644 --- a/homeassistant/components/subaru/__init__.py +++ b/homeassistant/components/subaru/__init__.py @@ -22,7 +22,7 @@ ENTRY_COORDINATOR, ENTRY_VEHICLES, FETCH_INTERVAL, - SUPPORTED_PLATFORMS, + PLATFORMS, UPDATE_INTERVAL, VEHICLE_API_GEN, VEHICLE_HAS_EV, @@ -94,9 +94,9 @@ async def async_update_data(): ENTRY_VEHICLES: vehicle_info, } - for component in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -107,8 +107,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in SUPPORTED_PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py index 7349f9c32d6d38..fa6fe984e61267 100644 --- a/homeassistant/components/subaru/const.py +++ b/homeassistant/components/subaru/const.py @@ -30,7 +30,7 @@ API_GEN_2 = "g2" MANUFACTURER = "Subaru Corp." -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ "sensor", ] diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index c7fb180e6d88ee..36af3bb0dbac34 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -31,7 +31,7 @@ _LOGGER = logging.getLogger(__name__) -TADO_COMPONENTS = ["binary_sensor", "sensor", "climate", "water_heater"] +PLATFORMS = ["binary_sensor", "sensor", "climate", "water_heater"] MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=4) SCAN_INTERVAL = timedelta(minutes=5) @@ -92,9 +92,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): UPDATE_LISTENER: update_listener, } - for component in TADO_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -118,8 +118,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in TADO_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index d75ccaec414318..b322d454002e86 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -31,7 +31,7 @@ extra=vol.ALLOW_EXTRA, ) -TAHOMA_COMPONENTS = ["binary_sensor", "cover", "lock", "scene", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "cover", "lock", "scene", "sensor", "switch"] TAHOMA_TYPES = { "io:AwningValanceIOComponent": "cover", @@ -73,7 +73,7 @@ def setup(hass, config): - """Activate Tahoma component.""" + """Set up Tahoma integration.""" conf = config[DOMAIN] username = conf.get(CONF_USERNAME) @@ -111,14 +111,14 @@ def setup(hass, config): for scene in scenes: hass.data[DOMAIN]["scenes"].append(scene) - for component in TAHOMA_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True def map_tahoma_device(tahoma_device): - """Map Tahoma device types to Home Assistant components.""" + """Map Tahoma device types to Home Assistant platforms.""" return TAHOMA_TYPES.get(tahoma_device.type) diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index c0ebae7695eb84..0b42725cdcf65f 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -92,8 +92,8 @@ async def start_platforms(): await device_automation.async_setup_entry(hass, entry) await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) @@ -113,8 +113,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) @@ -128,8 +128,8 @@ async def async_unload_entry(hass, entry): for unsub in hass.data[DATA_UNSUB]: unsub() hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation"))() - for component in PLATFORMS: - hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(component))() + for platform in PLATFORMS: + hass.data.pop(DATA_REMOVE_DISCOVER_COMPONENT.format(platform))() # deattach device triggers device_registry = await hass.helpers.device_registry.async_get_registry() diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 5d4721e60e6544..70cc884881457d 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -123,8 +123,8 @@ async def async_unload_entry(hass, config_entry): interval_tracker() await asyncio.wait( [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in hass.data.pop(CONFIG_ENTRY_IS_SETUP) + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in hass.data.pop(CONFIG_ENTRY_IS_SETUP) ] ) del hass.data[DOMAIN] diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index b31f8ae6dd39df..a2ed908948fff8 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -39,7 +39,7 @@ DOMAIN, ICONS, MIN_SCAN_INTERVAL, - TESLA_COMPONENTS, + PLATFORMS, ) _LOGGER = logging.getLogger(__name__) @@ -190,10 +190,10 @@ async def async_setup_entry(hass, config_entry): for device in all_devices: entry_data["devices"][device.hass_type].append(device) - for component in TESLA_COMPONENTS: - _LOGGER.debug("Loading %s", component) + for platform in PLATFORMS: + _LOGGER.debug("Loading %s", platform) hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -203,8 +203,8 @@ async def async_unload_entry(hass, config_entry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in TESLA_COMPONENTS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tesla/const.py b/homeassistant/components/tesla/const.py index 2b8485c76164ef..94883e4a833b1d 100644 --- a/homeassistant/components/tesla/const.py +++ b/homeassistant/components/tesla/const.py @@ -5,7 +5,8 @@ DEFAULT_SCAN_INTERVAL = 660 DEFAULT_WAKE_ON_START = False MIN_SCAN_INTERVAL = 60 -TESLA_COMPONENTS = [ + +PLATFORMS = [ "sensor", "lock", "climate", @@ -13,6 +14,7 @@ "device_tracker", "switch", ] + ICONS = { "battery sensor": "mdi:battery", "range sensor": "mdi:gauge", diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index b4b29a84297887..fd7fc389c75c89 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -73,9 +73,9 @@ async def _close(event): _LOGGER.error("Failed to login. %s", exp) return False - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # set up notify platform, no entry support for notify component yet, @@ -93,8 +93,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tile/__init__.py b/homeassistant/components/tile/__init__.py index 205742017d3dee..48bf8177c63647 100644 --- a/homeassistant/components/tile/__init__.py +++ b/homeassistant/components/tile/__init__.py @@ -74,9 +74,9 @@ async def async_update_tile(tile): await gather_with_concurrency(DEFAULT_INIT_TASK_LIMIT, *coordinator_init_tasks) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -87,8 +87,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index d1753ad4d026af..2a3322991e1c34 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -27,7 +27,7 @@ from .coordinator import ToonDataUpdateCoordinator from .oauth2 import register_oauth2_implementations -ENTITY_COMPONENTS = { +PLATFORMS = { BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, SENSOR_DOMAIN, @@ -119,9 +119,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) # Spin up the platforms - for component in ENTITY_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # If Home Assistant is already in a running state, register the webhook @@ -146,8 +146,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in ENTITY_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ) ) ) diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index 179d60b794ac94..8ef223c49a5f8f 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -82,9 +82,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = client - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 4c984067ada45e..3323c54d9c2768 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -148,9 +148,9 @@ async def on_hass_stop(event): sw_version=gateway_info.firmware_version, ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def async_keep_alive(now): @@ -174,8 +174,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 7f6ba6b26fd210..1f16d131e39eea 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -240,9 +240,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload( - entry, component.split(".", 1)[0] + entry, platform.split(".", 1)[0] ) - for component in hass.data[DOMAIN][ENTRY_IS_SETUP] + for platform in hass.data[DOMAIN][ENTRY_IS_SETUP] ] ) ) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 128f01079848d2..45268a341e1d9f 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -73,7 +73,7 @@ RETRY_TIMER = 15 CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) -SUPPORTED_PLATFORMS = [TRACKER_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] +PLATFORMS = [TRACKER_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] CLIENT_CONNECTED = ( WIRED_CLIENT_CONNECTED, @@ -368,7 +368,7 @@ async def async_setup(self): self.wireless_clients = wireless_clients.get_data(self.config_entry) self.update_wireless_clients() - for platform in SUPPORTED_PLATFORMS: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( self.config_entry, platform @@ -465,7 +465,7 @@ async def async_reset(self): self.hass.config_entries.async_forward_entry_unload( self.config_entry, platform ) - for platform in SUPPORTED_PLATFORMS + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index f2765ff317d6de..70f37451e6bb94 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -17,7 +17,7 @@ EVENT_UPB_SCENE_CHANGED, ) -UPB_PLATFORMS = ["light", "scene"] +PLATFORMS = ["light", "scene"] async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: @@ -36,9 +36,9 @@ async def async_setup_entry(hass, config_entry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = {"upb": upb} - for component in UPB_PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) def _element_changed(element, changeset): @@ -71,8 +71,8 @@ async def async_unload_entry(hass, config_entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in UPB_PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index a859567e21950e..a15b0a641ef1e5 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -22,7 +22,7 @@ {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA ) -COMPONENT_TYPES = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] +PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] async def async_setup(hass, config): @@ -51,19 +51,19 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): def callback(): modules = controller.get_modules() discovery_info = {"cntrl": controller} - for category in COMPONENT_TYPES: - discovery_info[category] = [] + for platform in PLATFORMS: + discovery_info[platform] = [] for module in modules: for channel in range(1, module.number_of_channels() + 1): - for category in COMPONENT_TYPES: - if category in module.get_categories(channel): - discovery_info[category].append( + for platform in PLATFORMS: + if platform in module.get_categories(channel): + discovery_info[platform].append( (module.get_module_address(), channel) ) hass.data[DOMAIN][entry.entry_id] = discovery_info - for category in COMPONENT_TYPES: - hass.add_job(hass.config_entries.async_forward_entry_setup(entry, category)) + for platform in PLATFORMS: + hass.add_job(hass.config_entries.async_forward_entry_setup(entry, platform)) try: controller = velbus.Controller(entry.data[CONF_PORT]) @@ -113,8 +113,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): """Remove the velbus connection.""" await asyncio.wait( [ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in COMPONENT_TYPES + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 90ed0a91b14eb8..5c1d8bfd37060a 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -10,7 +10,7 @@ DOMAIN = "velux" DATA_VELUX = "data_velux" -SUPPORTED_DOMAINS = ["cover", "scene"] +PLATFORMS = ["cover", "scene"] _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( @@ -34,9 +34,9 @@ async def async_setup(hass, config): _LOGGER.exception("Can't connect to velux interface: %s", ex) return False - for component in SUPPORTED_DOMAINS: + for platform in PLATFORMS: hass.async_create_task( - discovery.async_load_platform(hass, component, DOMAIN, {}, config) + discovery.async_load_platform(hass, platform, DOMAIN, {}, config) ) return True diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 2348d42a0d3cd5..ab061faccad154 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -38,6 +38,15 @@ SERVICE_ENABLE_AUTOLOCK, ) +PLATFORMS = [ + "sensor", + "switch", + "alarm_control_panel", + "lock", + "camera", + "binary_sensor", +] + HUB = None CONFIG_SCHEMA = vol.Schema( @@ -70,7 +79,7 @@ def setup(hass, config): - """Set up the Verisure component.""" + """Set up the Verisure integration.""" global HUB # pylint: disable=global-statement HUB = VerisureHub(config[DOMAIN]) HUB.update_overview = Throttle(config[DOMAIN][CONF_SCAN_INTERVAL])( @@ -81,15 +90,8 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: HUB.logout()) HUB.update_overview() - for component in ( - "sensor", - "switch", - "alarm_control_panel", - "lock", - "camera", - "binary_sensor", - ): - discovery.load_platform(hass, component, DOMAIN, {}, config) + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) async def capture_smartcam(service): """Capture a new picture from a smartcam.""" diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py index 24bd0f000df338..686a71427c33e7 100644 --- a/homeassistant/components/vesync/__init__.py +++ b/homeassistant/components/vesync/__init__.py @@ -156,8 +156,8 @@ async def async_unload_entry(hass, entry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 940e076c813656..3c8dfe948d03e0 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) -VICARE_PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"] +PLATFORMS = ["climate", "sensor", "binary_sensor", "water_heater"] DOMAIN = "vicare" PYVICARE_ERROR = "error" @@ -91,7 +91,7 @@ def setup(hass, config): hass.data[DOMAIN][VICARE_NAME] = conf[CONF_NAME] hass.data[DOMAIN][VICARE_HEATING_TYPE] = heating_type - for platform in VICARE_PLATFORMS: + for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/volumio/__init__.py b/homeassistant/components/volumio/__init__.py index 8d171cab9d2dfa..ec1fdec685a23a 100644 --- a/homeassistant/components/volumio/__init__.py +++ b/homeassistant/components/volumio/__init__.py @@ -35,9 +35,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): DATA_INFO: info, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -48,8 +48,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 792fcc25eff684..743bb903c72611 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -39,7 +39,7 @@ SIGNAL_STATE_UPDATED = f"{DOMAIN}.updated" -COMPONENTS = { +PLATFORMS = { "sensor": "sensor", "binary_sensor": "binary_sensor", "lock": "lock", @@ -146,7 +146,7 @@ def discover_vehicle(vehicle): for instrument in ( instrument for instrument in dashboard.instruments - if instrument.component in COMPONENTS and is_enabled(instrument.slug_attr) + if instrument.component in PLATFORMS and is_enabled(instrument.slug_attr) ): data.instruments.add(instrument) @@ -154,7 +154,7 @@ def discover_vehicle(vehicle): hass.async_create_task( discovery.async_load_platform( hass, - COMPONENTS[instrument.component], + PLATFORMS[instrument.component], DOMAIN, (vehicle.vin, instrument.component, instrument.attr), config, diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index 000b961bda9f87..d93bd54437b5ea 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -59,9 +59,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): _LOGGER.error("Port %s already in use", config_entry.data[CONF_PORT]) raise ConfigEntryNotReady from exc - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) + hass.config_entries.async_forward_entry_setup(config_entry, platform) ) return True @@ -80,8 +80,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/wilight/__init__.py b/homeassistant/components/wilight/__init__.py index 67433772551ef6..3e14ea20b0cdc2 100644 --- a/homeassistant/components/wilight/__init__.py +++ b/homeassistant/components/wilight/__init__.py @@ -33,9 +33,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = parent # Set up all platforms for this device/entry. - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -47,8 +47,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): # Unload entities for this entry/device. await asyncio.gather( *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ) ) diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index 7cc91d32062bbb..72fee5f078c960 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -30,7 +30,7 @@ ) SCAN_INTERVAL = timedelta(seconds=5) -WLED_COMPONENTS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN) +PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN) _LOGGER = logging.getLogger(__name__) @@ -60,9 +60,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) # Set up all platforms for this device/entry. - for component in WLED_COMPONENTS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -75,8 +75,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *( - hass.config_entries.async_forward_entry_unload(entry, component) - for component in WLED_COMPONENTS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ) ) ) diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 3e8d537799ab88..1d921f5fd18218 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -101,9 +101,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): "coordinator": coordinator, } - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -114,8 +114,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index f54c262abba493..a33f697abdf676 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -188,9 +188,9 @@ def stop_xiaomi(event): else: platforms = GATEWAY_PLATFORMS_NO_KEY - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -208,8 +208,8 @@ async def async_unload_entry( unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in platforms + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in platforms ] ) ) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 42b491c9b556b2..a72298c7c4406a 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -106,9 +106,9 @@ async def async_update_data(): KEY_COORDINATOR: coordinator, } - for component in GATEWAY_PLATFORMS: + for platform in GATEWAY_PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -133,9 +133,9 @@ async def async_setup_device_entry( if not platforms: return False - for component in platforms: + for platform in platforms: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index 8651c33546c3a1..9392b9be4032de 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -38,7 +38,7 @@ extra=vol.ALLOW_EXTRA, ) -XS1_COMPONENTS = ["climate", "sensor", "switch"] +PLATFORMS = ["climate", "sensor", "switch"] # Lock used to limit the amount of concurrent update requests # as the XS1 Gateway can only handle a very @@ -47,7 +47,7 @@ def setup(hass, config): - """Set up XS1 Component.""" + """Set up XS1 integration.""" _LOGGER.debug("Initializing XS1") host = config[DOMAIN][CONF_HOST] @@ -78,10 +78,10 @@ def setup(hass, config): hass.data[DOMAIN][ACTUATORS] = actuators hass.data[DOMAIN][SENSORS] = sensors - _LOGGER.debug("Loading components for XS1 platform...") - # Load components for supported devices - for component in XS1_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + _LOGGER.debug("Loading platforms for XS1 integration...") + # Load platforms for supported devices + for platform in PLATFORMS: + discovery.load_platform(hass, platform, DOMAIN, {}, config) return True diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index f24847a2d54667..a86af10fe1c51f 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -198,9 +198,9 @@ async def _initialize(host: str, capabilities: Optional[dict] = None) -> None: async def _load_platforms(): - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) # Move options from data for imported entries @@ -246,8 +246,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index 2c652f61c21ed9..f3d2e9daebc5d6 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -20,9 +20,9 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Zerproc from a config entry.""" - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -33,8 +33,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 351fc86a1e5b86..de5c5423fe11f8 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -16,7 +16,6 @@ from .core import ZHAGateway from .core.const import ( BAUD_RATES, - COMPONENTS, CONF_BAUDRATE, CONF_DATABASE, CONF_DEVICE_CONFIG, @@ -30,6 +29,7 @@ DATA_ZHA_GATEWAY, DATA_ZHA_PLATFORM_LOADED, DOMAIN, + PLATFORMS, SIGNAL_ADD_ENTITIES, RadioType, ) @@ -88,8 +88,8 @@ async def async_setup_entry(hass, config_entry): zha_data = hass.data.setdefault(DATA_ZHA, {}) config = zha_data.get(DATA_ZHA_CONFIG, {}) - for component in COMPONENTS: - zha_data.setdefault(component, []) + for platform in PLATFORMS: + zha_data.setdefault(platform, []) if config.get(CONF_ENABLE_QUIRKS, True): # needs to be done here so that the ZHA module is finished loading @@ -101,8 +101,8 @@ async def async_setup_entry(hass, config_entry): zha_data[DATA_ZHA_DISPATCHERS] = [] zha_data[DATA_ZHA_PLATFORM_LOADED] = [] - for component in COMPONENTS: - coro = hass.config_entries.async_forward_entry_setup(config_entry, component) + for platform in PLATFORMS: + coro = hass.config_entries.async_forward_entry_setup(config_entry, platform) zha_data[DATA_ZHA_PLATFORM_LOADED].append(hass.async_create_task(coro)) device_registry = await hass.helpers.device_registry.async_get_registry() @@ -138,8 +138,8 @@ async def async_unload_entry(hass, config_entry): for unsub_dispatcher in dispatchers: unsub_dispatcher() - for component in COMPONENTS: - await hass.config_entries.async_forward_entry_unload(config_entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) return True diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index ac4f53d2a8c716..2454085d9a4f29 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -104,7 +104,7 @@ CLUSTER_TYPE_IN = "in" CLUSTER_TYPE_OUT = "out" -COMPONENTS = ( +PLATFORMS = ( BINARY_SENSOR, CLIMATE, COVER, diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index e071a523321db0..ba970570ecbd1c 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -75,7 +75,7 @@ def discover_by_device_type(self, channel_pool: zha_typing.ChannelPoolType) -> N ep_device_type = channel_pool.endpoint.device_type component = zha_regs.DEVICE_CLASS[ep_profile_id].get(ep_device_type) - if component and component in zha_const.COMPONENTS: + if component and component in zha_const.PLATFORMS: channels = channel_pool.unclaimed_channels() entity_class, claimed = zha_regs.ZHA_ENTITIES.get_entity( component, channel_pool.manufacturer, channel_pool.model, channels @@ -122,7 +122,7 @@ def probe_single_cluster( ep_channels: zha_typing.ChannelPoolType, ) -> None: """Probe specified cluster for specific component.""" - if component is None or component not in zha_const.COMPONENTS: + if component is None or component not in zha_const.PLATFORMS: return channel_list = [channel] unique_id = f"{ep_channels.unique_id}-{channel.cluster.cluster_id}" diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 8e9889b5fac46d..0ab1f786555a05 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -89,7 +89,7 @@ DEFAULT_CONF_REFRESH_VALUE = False DEFAULT_CONF_REFRESH_DELAY = 5 -SUPPORTED_PLATFORMS = [ +PLATFORMS = [ "binary_sensor", "climate", "cover", @@ -1060,7 +1060,7 @@ def _finalize_start(): hass.services.async_register(DOMAIN, const.SERVICE_START_NETWORK, start_zwave) - for entry_component in SUPPORTED_PLATFORMS: + for entry_component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, entry_component) ) @@ -1228,7 +1228,7 @@ async def discover_device(component, device): return self._hass.data[DATA_DEVICES][device.unique_id] = device - if component in SUPPORTED_PLATFORMS: + if component in PLATFORMS: async_dispatcher_send(self._hass, f"zwave_new_{component}", device) else: await discovery.async_load_platform( diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 798fd9fda2c838..4f4c8d6eab0c83 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -278,8 +278,8 @@ async def start_platforms() -> None: # wait until all required platforms are ready await asyncio.gather( *[ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_setup(entry, platform) + for platform in PLATFORMS ] ) @@ -388,8 +388,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py index c6df6e99979587..334ac8dbbc907d 100644 --- a/script/scaffold/templates/config_flow/integration/__init__.py +++ b/script/scaffold/templates/config_flow/integration/__init__.py @@ -21,9 +21,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -34,8 +34,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py index c6df6e99979587..334ac8dbbc907d 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/__init__.py +++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py @@ -21,9 +21,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -34,8 +34,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index c51061b57fed37..4e290921047c9e 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -72,9 +72,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): aiohttp_client.async_get_clientsession(hass), session ) - for component in PLATFORMS: + for platform in PLATFORMS: hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, component) + hass.config_entries.async_forward_entry_setup(entry, platform) ) return True @@ -85,8 +85,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): unload_ok = all( await asyncio.gather( *[ - hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS ] ) ) diff --git a/tests/components/abode/common.py b/tests/components/abode/common.py index aabc732daa2e6f..c134552ccd41a6 100644 --- a/tests/components/abode/common.py +++ b/tests/components/abode/common.py @@ -16,7 +16,7 @@ async def setup_platform(hass, platform): ) mock_entry.add_to_hass(hass) - with patch("homeassistant.components.abode.ABODE_PLATFORMS", [platform]), patch( + with patch("homeassistant.components.abode.PLATFORMS", [platform]), patch( "abodepy.event_controller.sio" ), patch("abodepy.utils.save_cache"): assert await async_setup_component(hass, ABODE_DOMAIN, {}) diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index eab88fb18ca4d6..34b66399a3e604 100644 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -277,9 +277,7 @@ async def test_unload_entry(hass): ) as mock_unload: assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() - assert mock_unload.call_count == len(dynalite.ENTITY_PLATFORMS) - expected_calls = [ - call(entry, platform) for platform in dynalite.ENTITY_PLATFORMS - ] + assert mock_unload.call_count == len(dynalite.PLATFORMS) + expected_calls = [call(entry, platform) for platform in dynalite.PLATFORMS] for cur_call in mock_unload.mock_calls: assert cur_call in expected_calls diff --git a/tests/components/dyson/conftest.py b/tests/components/dyson/conftest.py index 747f7a43986567..300c80f3a730e8 100644 --- a/tests/components/dyson/conftest.py +++ b/tests/components/dyson/conftest.py @@ -26,8 +26,8 @@ async def device(hass: HomeAssistant, request) -> DysonDevice: device = get_device() with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=[device] - ), patch(f"{BASE_PATH}.DYSON_PLATFORMS", [platform]): - # DYSON_PLATFORMS is patched so that only the platform being tested is set up + ), patch(f"{BASE_PATH}.PLATFORMS", [platform]): + # PLATFORMS is patched so that only the platform being tested is set up await async_setup_component( hass, DOMAIN, diff --git a/tests/components/dyson/test_init.py b/tests/components/dyson/test_init.py index 2535da4d166dd2..714ac919c19270 100644 --- a/tests/components/dyson/test_init.py +++ b/tests/components/dyson/test_init.py @@ -54,7 +54,7 @@ async def test_setup_manual(hass: HomeAssistant): with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True) as login, patch( f"{BASE_PATH}.DysonAccount.devices", return_value=devices ) as devices_method, patch( - f"{BASE_PATH}.DYSON_PLATFORMS", ["fan", "vacuum"] + f"{BASE_PATH}.PLATFORMS", ["fan", "vacuum"] ): # Patch platforms to get rid of sensors assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() @@ -85,7 +85,7 @@ async def test_setup_autoconnect(hass: HomeAssistant): with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=devices ), patch( - f"{BASE_PATH}.DYSON_PLATFORMS", ["fan"] + f"{BASE_PATH}.PLATFORMS", ["fan"] ): # Patch platforms to get rid of sensors assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index a1f8e4bb37c881..4541ef2190ad1f 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -168,8 +168,8 @@ async def test_temperature( device = async_get_device(DysonPureCoolLink) with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=[device] - ), patch(f"{BASE_PATH}.DYSON_PLATFORMS", [PLATFORM_DOMAIN]): - # DYSON_PLATFORMS is patched so that only the platform being tested is set up + ), patch(f"{BASE_PATH}.PLATFORMS", [PLATFORM_DOMAIN]): + # PLATFORMS is patched so that only the platform being tested is set up await async_setup_component( hass, DOMAIN, diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index aee1641c24fe9a..dd44510e0ad7d8 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -68,7 +68,7 @@ async def test_owserver_binary_sensor(owproxy, hass, device_id): item["default_disabled"] = False with patch( - "homeassistant.components.onewire.SUPPORTED_PLATFORMS", [BINARY_SENSOR_DOMAIN] + "homeassistant.components.onewire.PLATFORMS", [BINARY_SENSOR_DOMAIN] ), patch.dict( "homeassistant.components.onewire.binary_sensor.DEVICE_BINARY_SENSORS", patch_device_binary_sensors, diff --git a/tests/components/onewire/test_entity_owserver.py b/tests/components/onewire/test_entity_owserver.py index 42cbf77711ce84..a3a205795bfdda 100644 --- a/tests/components/onewire/test_entity_owserver.py +++ b/tests/components/onewire/test_entity_owserver.py @@ -5,11 +5,7 @@ import pytest from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.components.onewire.const import ( - DOMAIN, - PRESSURE_CBAR, - SUPPORTED_PLATFORMS, -) +from homeassistant.components.onewire.const import DOMAIN, PLATFORMS, PRESSURE_CBAR from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( @@ -754,7 +750,7 @@ @pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys()) -@pytest.mark.parametrize("platform", SUPPORTED_PLATFORMS) +@pytest.mark.parametrize("platform", PLATFORMS) @patch("homeassistant.components.onewire.onewirehub.protocol.proxy") async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform): """Test for 1-Wire device. @@ -782,7 +778,7 @@ async def test_owserver_setup_valid_device(owproxy, hass, device_id, platform): owproxy.return_value.dir.return_value = dir_return_value owproxy.return_value.read.side_effect = read_side_effect - with patch("homeassistant.components.onewire.SUPPORTED_PLATFORMS", [platform]): + with patch("homeassistant.components.onewire.PLATFORMS", [platform]): await setup_onewire_patched_owserver_integration(hass) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 9e91da01b2111c..44351cf9a6313a 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -134,7 +134,7 @@ async def test_sensors_on_owserver_coupler(owproxy, hass, device_id): owproxy.return_value.dir.side_effect = dir_side_effect owproxy.return_value.read.side_effect = read_side_effect - with patch("homeassistant.components.onewire.SUPPORTED_PLATFORMS", [SENSOR_DOMAIN]): + with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]): await setup_onewire_patched_owserver_integration(hass) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 1c778d4e264df3..0d8c9918711963 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -94,7 +94,7 @@ async def test_owserver_switch(owproxy, hass, device_id): item["default_disabled"] = False with patch( - "homeassistant.components.onewire.SUPPORTED_PLATFORMS", [SWITCH_DOMAIN] + "homeassistant.components.onewire.PLATFORMS", [SWITCH_DOMAIN] ), patch.dict( "homeassistant.components.onewire.switch.DEVICE_SWITCHES", patch_device_switches ): diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 9024b72bb85478..eed1d5d26b1f99 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -14,8 +14,8 @@ DATA_BROKERS, DOMAIN, EVENT_BUTTON, + PLATFORMS, SIGNAL_SMARTTHINGS_UPDATE, - SUPPORTED_PLATFORMS, ) from homeassistant.config import async_process_ha_core_config from homeassistant.const import HTTP_FORBIDDEN, HTTP_INTERNAL_SERVER_ERROR @@ -174,7 +174,7 @@ async def test_scenes_unauthorized_loads_platforms( assert await smartthings.async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_config_entry_loads_platforms( @@ -206,7 +206,7 @@ async def test_config_entry_loads_platforms( assert await smartthings.async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_config_entry_loads_unconnected_cloud( @@ -237,7 +237,7 @@ async def test_config_entry_loads_unconnected_cloud( with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: assert await smartthings.async_setup_entry(hass, config_entry) await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_unload_entry(hass, config_entry): @@ -258,7 +258,7 @@ async def test_unload_entry(hass, config_entry): assert config_entry.entry_id not in hass.data[DOMAIN][DATA_BROKERS] # Assert platforms unloaded await hass.async_block_till_done() - assert forward_mock.call_count == len(SUPPORTED_PLATFORMS) + assert forward_mock.call_count == len(PLATFORMS) async def test_remove_entry(hass, config_entry, smartthings_mock): diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 00865b4e910d4d..6b40df5857fa18 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -22,10 +22,7 @@ DOMAIN as UNIFI_DOMAIN, UNIFI_WIRELESS_CLIENTS, ) -from homeassistant.components.unifi.controller import ( - SUPPORTED_PLATFORMS, - get_controller, -) +from homeassistant.components.unifi.controller import PLATFORMS, get_controller from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.const import ( CONF_HOST, @@ -211,7 +208,7 @@ async def test_controller_setup(hass, aioclient_mock): controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] entry = controller.config_entry - assert len(forward_entry_setup.mock_calls) == len(SUPPORTED_PLATFORMS) + assert len(forward_entry_setup.mock_calls) == len(PLATFORMS) assert forward_entry_setup.mock_calls[0][1] == (entry, TRACKER_DOMAIN) assert forward_entry_setup.mock_calls[1][1] == (entry, SENSOR_DOMAIN) assert forward_entry_setup.mock_calls[2][1] == (entry, SWITCH_DOMAIN) diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index ac2ef085e14cf6..d4195c681e75e9 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -96,7 +96,7 @@ async def test_devices( entity_ids = hass_disable_services.states.async_entity_ids() await hass_disable_services.async_block_till_done() zha_entity_ids = { - ent for ent in entity_ids if ent.split(".")[0] in zha_const.COMPONENTS + ent for ent in entity_ids if ent.split(".")[0] in zha_const.PLATFORMS } if cluster_identify: From 17444e2f2f835b647a3b8b48245e983e3a1c564c Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 2 Mar 2021 21:50:28 +0100 Subject: [PATCH 0947/1818] Limit log spam by ConfigEntryNotReady (#47201) --- homeassistant/config_entries.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d45f2b945160bc..d3e8f66abc4c91 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -257,12 +257,19 @@ async def async_setup( self.state = ENTRY_STATE_SETUP_RETRY wait_time = 2 ** min(tries, 4) * 5 tries += 1 - _LOGGER.warning( - "Config entry '%s' for %s integration not ready yet. Retrying in %d seconds", - self.title, - self.domain, - wait_time, - ) + if tries == 1: + _LOGGER.warning( + "Config entry '%s' for %s integration not ready yet. Retrying in background", + self.title, + self.domain, + ) + else: + _LOGGER.debug( + "Config entry '%s' for %s integration not ready yet. Retrying in %d seconds", + self.title, + self.domain, + wait_time, + ) async def setup_again(now: Any) -> None: """Run setup again.""" From 2df644c6cce91cad8d9eabd842e4d4f7223e3280 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 12:58:53 -0800 Subject: [PATCH 0948/1818] Clean up secret loading (#47034) --- homeassistant/bootstrap.py | 3 - .../components/lovelace/dashboard.py | 5 +- homeassistant/config.py | 19 +- homeassistant/helpers/check_config.py | 12 +- homeassistant/scripts/check_config.py | 31 +-- homeassistant/util/yaml/__init__.py | 4 +- homeassistant/util/yaml/loader.py | 179 ++++++++++-------- tests/util/yaml/test_init.py | 53 +++--- 8 files changed, 172 insertions(+), 134 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index fd2d580a879eaf..6d334ac8953d39 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -28,7 +28,6 @@ from homeassistant.util.async_ import gather_with_concurrency from homeassistant.util.logging import async_activate_log_queue_handler from homeassistant.util.package import async_get_user_site, is_virtual_env -from homeassistant.util.yaml import clear_secret_cache if TYPE_CHECKING: from .runner import RuntimeConfig @@ -122,8 +121,6 @@ async def async_setup_hass( basic_setup_success = ( await async_from_config_dict(config_dict, hass) is not None ) - finally: - clear_secret_cache() if config_dict is None: safe_mode = True diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index 2d3196054e30a9..c6f4726724bcb6 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -2,6 +2,7 @@ from abc import ABC, abstractmethod import logging import os +from pathlib import Path import time from typing import Optional, cast @@ -12,7 +13,7 @@ from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection, storage -from homeassistant.util.yaml import load_yaml +from homeassistant.util.yaml import Secrets, load_yaml from .const import ( CONF_ICON, @@ -201,7 +202,7 @@ def _load_config(self, force): is_updated = self._cache is not None try: - config = load_yaml(self.path) + config = load_yaml(self.path, Secrets(Path(self.hass.config.config_dir))) except FileNotFoundError: raise ConfigNotFound from None diff --git a/homeassistant/config.py b/homeassistant/config.py index 90df365c349304..cfc1390a37ba2c 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -2,6 +2,7 @@ from collections import OrderedDict import logging import os +from pathlib import Path import re import shutil from types import ModuleType @@ -59,7 +60,7 @@ ) from homeassistant.util.package import is_docker_env from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM -from homeassistant.util.yaml import SECRET_YAML, load_yaml +from homeassistant.util.yaml import SECRET_YAML, Secrets, load_yaml _LOGGER = logging.getLogger(__name__) @@ -318,23 +319,33 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: This function allow a component inside the asyncio loop to reload its configuration by itself. Include package merge. """ + if hass.config.config_dir is None: + secrets = None + else: + secrets = Secrets(Path(hass.config.config_dir)) + # Not using async_add_executor_job because this is an internal method. config = await hass.loop.run_in_executor( - None, load_yaml_config_file, hass.config.path(YAML_CONFIG_FILE) + None, + load_yaml_config_file, + hass.config.path(YAML_CONFIG_FILE), + secrets, ) core_config = config.get(CONF_CORE, {}) await merge_packages_config(hass, config, core_config.get(CONF_PACKAGES, {})) return config -def load_yaml_config_file(config_path: str) -> Dict[Any, Any]: +def load_yaml_config_file( + config_path: str, secrets: Optional[Secrets] = None +) -> Dict[Any, Any]: """Parse a YAML configuration file. Raises FileNotFoundError or HomeAssistantError. This method needs to run in an executor. """ - conf_dict = load_yaml(config_path) + conf_dict = load_yaml(config_path, secrets) if not isinstance(conf_dict, dict): msg = ( diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 7b7b53d3c0fed3..5dd7623ecc8ec8 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -4,6 +4,7 @@ from collections import OrderedDict import logging import os +from pathlib import Path from typing import List, NamedTuple, Optional import voluptuous as vol @@ -87,13 +88,18 @@ def _comp_error(ex: Exception, domain: str, config: ConfigType) -> None: try: if not await hass.async_add_executor_job(os.path.isfile, config_path): return result.add_error("File configuration.yaml not found.") - config = await hass.async_add_executor_job(load_yaml_config_file, config_path) + + assert hass.config.config_dir is not None + + config = await hass.async_add_executor_job( + load_yaml_config_file, + config_path, + yaml_loader.Secrets(Path(hass.config.config_dir)), + ) except FileNotFoundError: return result.add_error(f"File not found: {config_path}") except HomeAssistantError as err: return result.add_error(f"Error loading {config_path}: {err}") - finally: - yaml_loader.clear_secret_cache() # Extract and validate core [homeassistant] config try: diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index f75594a546e15c..4fc8383782cc86 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -9,10 +9,11 @@ from typing import Any, Callable, Dict, List, Tuple from unittest.mock import patch -from homeassistant import bootstrap, core +from homeassistant import core from homeassistant.config import get_default_config_dir from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.check_config import async_check_ha_config_file +from homeassistant.util.yaml import Secrets import homeassistant.util.yaml.loader as yaml_loader # mypy: allow-untyped-calls, allow-untyped-defs @@ -26,7 +27,6 @@ "load*": ("homeassistant.config.load_yaml", yaml_loader.load_yaml), "secrets": ("homeassistant.util.yaml.loader.secret_yaml", yaml_loader.secret_yaml), } -SILENCE = ("homeassistant.scripts.check_config.yaml_loader.clear_secret_cache",) PATCHES: Dict[str, Any] = {} @@ -154,14 +154,14 @@ def check(config_dir, secrets=False): "secrets": OrderedDict(), # secret cache and secrets loaded "except": OrderedDict(), # exceptions raised (with config) #'components' is a HomeAssistantConfig # noqa: E265 - "secret_cache": None, + "secret_cache": {}, } # pylint: disable=possibly-unused-variable - def mock_load(filename): + def mock_load(filename, secrets=None): """Mock hass.util.load_yaml to save config file names.""" res["yaml_files"][filename] = True - return MOCKS["load"][1](filename) + return MOCKS["load"][1](filename, secrets) # pylint: disable=possibly-unused-variable def mock_secrets(ldr, node): @@ -173,10 +173,6 @@ def mock_secrets(ldr, node): res["secrets"][node.value] = val return val - # Patches to skip functions - for sil in SILENCE: - PATCHES[sil] = patch(sil) - # Patches with local mock functions for key, val in MOCKS.items(): if not secrets and key == "secrets": @@ -192,11 +188,19 @@ def mock_secrets(ldr, node): if secrets: # Ensure !secrets point to the patched function - yaml_loader.yaml.SafeLoader.add_constructor("!secret", yaml_loader.secret_yaml) + yaml_loader.SafeLineLoader.add_constructor("!secret", yaml_loader.secret_yaml) + + def secrets_proxy(*args): + secrets = Secrets(*args) + res["secret_cache"] = secrets._cache + return secrets try: - res["components"] = asyncio.run(async_check_config(config_dir)) - res["secret_cache"] = OrderedDict(yaml_loader.__SECRET_CACHE) + with patch.object(yaml_loader, "Secrets", secrets_proxy): + res["components"] = asyncio.run(async_check_config(config_dir)) + res["secret_cache"] = { + str(key): val for key, val in res["secret_cache"].items() + } for err in res["components"].errors: domain = err.domain or ERROR_STR res["except"].setdefault(domain, []).append(err.message) @@ -212,10 +216,9 @@ def mock_secrets(ldr, node): pat.stop() if secrets: # Ensure !secrets point to the original function - yaml_loader.yaml.SafeLoader.add_constructor( + yaml_loader.SafeLineLoader.add_constructor( "!secret", yaml_loader.secret_yaml ) - bootstrap.clear_secret_cache() return res diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index a152086ea82911..b3f1b7ecd43768 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -2,7 +2,7 @@ from .const import SECRET_YAML from .dumper import dump, save_yaml from .input import UndefinedSubstitution, extract_inputs, substitute -from .loader import clear_secret_cache, load_yaml, parse_yaml, secret_yaml +from .loader import Secrets, load_yaml, parse_yaml, secret_yaml from .objects import Input __all__ = [ @@ -10,7 +10,7 @@ "Input", "dump", "save_yaml", - "clear_secret_cache", + "Secrets", "load_yaml", "secret_yaml", "parse_yaml", diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 7d713c9f0c0651..04e51a5a9c598b 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -3,8 +3,8 @@ import fnmatch import logging import os -import sys -from typing import Dict, Iterator, List, TextIO, TypeVar, Union, overload +from pathlib import Path +from typing import Any, Dict, Iterator, List, Optional, TextIO, TypeVar, Union, overload import yaml @@ -19,20 +19,82 @@ DICT_T = TypeVar("DICT_T", bound=Dict) # pylint: disable=invalid-name _LOGGER = logging.getLogger(__name__) -__SECRET_CACHE: Dict[str, JSON_TYPE] = {} -def clear_secret_cache() -> None: - """Clear the secret cache. +class Secrets: + """Store secrets while loading YAML.""" - Async friendly. - """ - __SECRET_CACHE.clear() + def __init__(self, config_dir: Path): + """Initialize secrets.""" + self.config_dir = config_dir + self._cache: Dict[Path, Dict[str, str]] = {} + + def get(self, requester_path: str, secret: str) -> str: + """Return the value of a secret.""" + current_path = Path(requester_path) + + secret_dir = current_path + while True: + secret_dir = secret_dir.parent + + try: + secret_dir.relative_to(self.config_dir) + except ValueError: + # We went above the config dir + break + + secrets = self._load_secret_yaml(secret_dir) + + if secret in secrets: + _LOGGER.debug( + "Secret %s retrieved from secrets.yaml in folder %s", + secret, + secret_dir, + ) + return secrets[secret] + + raise HomeAssistantError(f"Secret {secret} not defined") + + def _load_secret_yaml(self, secret_dir: Path) -> Dict[str, str]: + """Load the secrets yaml from path.""" + secret_path = secret_dir / SECRET_YAML + + if secret_path in self._cache: + return self._cache[secret_path] + + _LOGGER.debug("Loading %s", secret_path) + try: + secrets = load_yaml(str(secret_path)) + + if not isinstance(secrets, dict): + raise HomeAssistantError("Secrets is not a dictionary") + + if "logger" in secrets: + logger = str(secrets["logger"]).lower() + if logger == "debug": + _LOGGER.setLevel(logging.DEBUG) + else: + _LOGGER.error( + "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", + logger, + ) + del secrets["logger"] + except FileNotFoundError: + secrets = {} + + self._cache[secret_path] = secrets + + return secrets class SafeLineLoader(yaml.SafeLoader): """Loader class that keeps track of line numbers.""" + def __init__(self, stream: Any, secrets: Optional[Secrets] = None) -> None: + """Initialize a safe line loader.""" + super().__init__(stream) + self.secrets = secrets + def compose_node(self, parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: """Annotate a node with the first line it was seen.""" last_line: int = self.line @@ -41,22 +103,27 @@ def compose_node(self, parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: return node -def load_yaml(fname: str) -> JSON_TYPE: +def load_yaml(fname: str, secrets: Optional[Secrets] = None) -> JSON_TYPE: """Load a YAML file.""" try: with open(fname, encoding="utf-8") as conf_file: - return parse_yaml(conf_file) + return parse_yaml(conf_file, secrets) except UnicodeDecodeError as exc: _LOGGER.error("Unable to read file %s: %s", fname, exc) raise HomeAssistantError(exc) from exc -def parse_yaml(content: Union[str, TextIO]) -> JSON_TYPE: +def parse_yaml( + content: Union[str, TextIO], secrets: Optional[Secrets] = None +) -> JSON_TYPE: """Load a YAML file.""" try: # If configuration file is empty YAML returns None # We convert that to an empty dict - return yaml.load(content, Loader=SafeLineLoader) or OrderedDict() + return ( + yaml.load(content, Loader=lambda stream: SafeLineLoader(stream, secrets)) + or OrderedDict() + ) except yaml.YAMLError as exc: _LOGGER.error(str(exc)) raise HomeAssistantError(exc) from exc @@ -64,21 +131,21 @@ def parse_yaml(content: Union[str, TextIO]) -> JSON_TYPE: @overload def _add_reference( - obj: Union[list, NodeListClass], loader: yaml.SafeLoader, node: yaml.nodes.Node + obj: Union[list, NodeListClass], loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeListClass: ... @overload def _add_reference( - obj: Union[str, NodeStrClass], loader: yaml.SafeLoader, node: yaml.nodes.Node + obj: Union[str, NodeStrClass], loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeStrClass: ... @overload def _add_reference( - obj: DICT_T, loader: yaml.SafeLoader, node: yaml.nodes.Node + obj: DICT_T, loader: SafeLineLoader, node: yaml.nodes.Node ) -> DICT_T: ... @@ -103,7 +170,7 @@ def _include_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: """ fname = os.path.join(os.path.dirname(loader.name), node.value) try: - return _add_reference(load_yaml(fname), loader, node) + return _add_reference(load_yaml(fname, loader.secrets), loader, node) except FileNotFoundError as exc: raise HomeAssistantError( f"{node.start_mark}: Unable to read file {fname}." @@ -135,7 +202,7 @@ def _include_dir_named_yaml( filename = os.path.splitext(os.path.basename(fname))[0] if os.path.basename(fname) == SECRET_YAML: continue - mapping[filename] = load_yaml(fname) + mapping[filename] = load_yaml(fname, loader.secrets) return _add_reference(mapping, loader, node) @@ -148,7 +215,7 @@ def _include_dir_merge_named_yaml( for fname in _find_files(loc, "*.yaml"): if os.path.basename(fname) == SECRET_YAML: continue - loaded_yaml = load_yaml(fname) + loaded_yaml = load_yaml(fname, loader.secrets) if isinstance(loaded_yaml, dict): mapping.update(loaded_yaml) return _add_reference(mapping, loader, node) @@ -175,7 +242,7 @@ def _include_dir_merge_list_yaml( for fname in _find_files(loc, "*.yaml"): if os.path.basename(fname) == SECRET_YAML: continue - loaded_yaml = load_yaml(fname) + loaded_yaml = load_yaml(fname, loader.secrets) if isinstance(loaded_yaml, list): merged_list.extend(loaded_yaml) return _add_reference(merged_list, loader, node) @@ -232,75 +299,27 @@ def _env_var_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> str: raise HomeAssistantError(node.value) -def _load_secret_yaml(secret_path: str) -> JSON_TYPE: - """Load the secrets yaml from path.""" - secret_path = os.path.join(secret_path, SECRET_YAML) - if secret_path in __SECRET_CACHE: - return __SECRET_CACHE[secret_path] - - _LOGGER.debug("Loading %s", secret_path) - try: - secrets = load_yaml(secret_path) - if not isinstance(secrets, dict): - raise HomeAssistantError("Secrets is not a dictionary") - if "logger" in secrets: - logger = str(secrets["logger"]).lower() - if logger == "debug": - _LOGGER.setLevel(logging.DEBUG) - else: - _LOGGER.error( - "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", - logger, - ) - del secrets["logger"] - except FileNotFoundError: - secrets = {} - __SECRET_CACHE[secret_path] = secrets - return secrets - - def secret_yaml(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: """Load secrets and embed it into the configuration YAML.""" - if os.path.basename(loader.name) == SECRET_YAML: - _LOGGER.error("secrets.yaml: attempt to load secret from within secrets file") - raise HomeAssistantError( - "secrets.yaml: attempt to load secret from within secrets file" - ) - secret_path = os.path.dirname(loader.name) - while True: - secrets = _load_secret_yaml(secret_path) - - if node.value in secrets: - _LOGGER.debug( - "Secret %s retrieved from secrets.yaml in folder %s", - node.value, - secret_path, - ) - return secrets[node.value] - - if secret_path == os.path.dirname(sys.path[0]): - break # sys.path[0] set to config/deps folder by bootstrap - - secret_path = os.path.dirname(secret_path) - if not os.path.exists(secret_path) or len(secret_path) < 5: - break # Somehow we got past the .homeassistant config folder + if loader.secrets is None: + raise HomeAssistantError("Secrets not supported in this YAML file") - raise HomeAssistantError(f"Secret {node.value} not defined") + return loader.secrets.get(loader.name, node.value) -yaml.SafeLoader.add_constructor("!include", _include_yaml) -yaml.SafeLoader.add_constructor( +SafeLineLoader.add_constructor("!include", _include_yaml) +SafeLineLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _ordered_dict ) -yaml.SafeLoader.add_constructor( +SafeLineLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, _construct_seq ) -yaml.SafeLoader.add_constructor("!env_var", _env_var_yaml) -yaml.SafeLoader.add_constructor("!secret", secret_yaml) -yaml.SafeLoader.add_constructor("!include_dir_list", _include_dir_list_yaml) -yaml.SafeLoader.add_constructor("!include_dir_merge_list", _include_dir_merge_list_yaml) -yaml.SafeLoader.add_constructor("!include_dir_named", _include_dir_named_yaml) -yaml.SafeLoader.add_constructor( +SafeLineLoader.add_constructor("!env_var", _env_var_yaml) +SafeLineLoader.add_constructor("!secret", secret_yaml) +SafeLineLoader.add_constructor("!include_dir_list", _include_dir_list_yaml) +SafeLineLoader.add_constructor("!include_dir_merge_list", _include_dir_merge_list_yaml) +SafeLineLoader.add_constructor("!include_dir_named", _include_dir_named_yaml) +SafeLineLoader.add_constructor( "!include_dir_merge_named", _include_dir_merge_named_yaml ) -yaml.SafeLoader.add_constructor("!input", Input.from_node) +SafeLineLoader.add_constructor("!input", Input.from_node) diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index b3a8ca4e486cc8..daa0275b7aaebf 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -18,7 +18,7 @@ def test_simple_list(): """Test simple list.""" conf = "config:\n - simple\n - list" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["config"] == ["simple", "list"] @@ -26,7 +26,7 @@ def test_simple_dict(): """Test simple dict.""" conf = "key: value" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == "value" @@ -49,7 +49,7 @@ def test_environment_variable(): os.environ["PASSWORD"] = "secret_password" conf = "password: !env_var PASSWORD" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["password"] == "secret_password" del os.environ["PASSWORD"] @@ -58,7 +58,7 @@ def test_environment_variable_default(): """Test config file with default value for environment variable.""" conf = "password: !env_var PASSWORD secret_password" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["password"] == "secret_password" @@ -67,7 +67,7 @@ def test_invalid_environment_variable(): conf = "password: !env_var PASSWORD" with pytest.raises(HomeAssistantError): with io.StringIO(conf) as file: - yaml_loader.yaml.safe_load(file) + yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) def test_include_yaml(): @@ -75,13 +75,13 @@ def test_include_yaml(): with patch_yaml_files({"test.yaml": "value"}): conf = "key: !include test.yaml" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == "value" with patch_yaml_files({"test.yaml": None}): conf = "key: !include test.yaml" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == {} @@ -93,7 +93,7 @@ def test_include_dir_list(mock_walk): with patch_yaml_files({"/test/one.yaml": "one", "/test/two.yaml": "two"}): conf = "key: !include_dir_list /test" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == sorted(["one", "two"]) @@ -118,7 +118,7 @@ def test_include_dir_list_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) @@ -135,7 +135,7 @@ def test_include_dir_named(mock_walk): conf = "key: !include_dir_named /test" correct = {"first": "one", "second": "two"} with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == correct @@ -161,7 +161,7 @@ def test_include_dir_named_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert doc["key"] == correct @@ -177,7 +177,7 @@ def test_include_dir_merge_list(mock_walk): ): conf = "key: !include_dir_merge_list /test" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert sorted(doc["key"]) == sorted(["one", "two", "three"]) @@ -202,7 +202,7 @@ def test_include_dir_merge_list_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert sorted(doc["key"]) == sorted(["one", "two", "three", "four"]) @@ -221,7 +221,7 @@ def test_include_dir_merge_named(mock_walk): with patch_yaml_files(files): conf = "key: !include_dir_merge_named /test" with io.StringIO(conf) as file: - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert doc["key"] == {"key1": "one", "key2": "two", "key3": "three"} @@ -246,7 +246,7 @@ def test_include_dir_merge_named_recursive(mock_walk): assert ( ".ignore" in mock_walk.return_value[0][1] ), "Expecting .ignore in here" - doc = yaml_loader.yaml.safe_load(file) + doc = yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) assert "tmp2" in mock_walk.return_value[0][1] assert ".ignore" not in mock_walk.return_value[0][1] assert doc["key"] == { @@ -278,11 +278,11 @@ def test_dump_unicode(): FILES = {} -def load_yaml(fname, string): +def load_yaml(fname, string, secrets=None): """Write a string to file and return the parsed yaml.""" FILES[fname] = string with patch_yaml_files(FILES): - return load_yaml_config_file(fname) + return load_yaml_config_file(fname, secrets) class TestSecrets(unittest.TestCase): @@ -293,7 +293,6 @@ class TestSecrets(unittest.TestCase): def setUp(self): """Create & load secrets file.""" config_dir = get_test_config_dir() - yaml.clear_secret_cache() self._yaml_path = os.path.join(config_dir, YAML_CONFIG_FILE) self._secret_path = os.path.join(config_dir, yaml.SECRET_YAML) self._sub_folder_path = os.path.join(config_dir, "subFolder") @@ -315,11 +314,11 @@ def setUp(self): " username: !secret comp1_un\n" " password: !secret comp1_pw\n" "", + yaml_loader.Secrets(config_dir), ) def tearDown(self): """Clean up secrets.""" - yaml.clear_secret_cache() FILES.clear() def test_secrets_from_yaml(self): @@ -341,6 +340,7 @@ def test_secrets_from_parent_folder(self): " username: !secret comp1_un\n" " password: !secret comp1_pw\n" "", + yaml_loader.Secrets(get_test_config_dir()), ) assert expected == self._yaml["http"] @@ -359,6 +359,7 @@ def test_secret_overrides_parent(self): " username: !secret comp1_un\n" " password: !secret comp1_pw\n" "", + yaml_loader.Secrets(get_test_config_dir()), ) assert expected == self._yaml["http"] @@ -380,9 +381,12 @@ def test_secrets_logger_removed(self): @patch("homeassistant.util.yaml.loader._LOGGER.error") def test_bad_logger_value(self, mock_error): """Ensure logger: debug was removed.""" - yaml.clear_secret_cache() load_yaml(self._secret_path, "logger: info\npw: abc") - load_yaml(self._yaml_path, "api_password: !secret pw") + load_yaml( + self._yaml_path, + "api_password: !secret pw", + yaml_loader.Secrets(get_test_config_dir()), + ) assert mock_error.call_count == 1, "Expected an error about logger: value" def test_secrets_are_not_dict(self): @@ -390,7 +394,6 @@ def test_secrets_are_not_dict(self): FILES[ self._secret_path ] = "- http_pw: pwhttp\n comp1_un: un1\n comp1_pw: pw1\n" - yaml.clear_secret_cache() with pytest.raises(HomeAssistantError): load_yaml( self._yaml_path, @@ -424,10 +427,8 @@ def test_no_recursive_secrets(caplog): files = {YAML_CONFIG_FILE: "key: !secret a", yaml.SECRET_YAML: "a: 1\nb: !secret a"} with patch_yaml_files(files), pytest.raises(HomeAssistantError) as e: load_yaml_config_file(YAML_CONFIG_FILE) - assert e.value.args == ( - "secrets.yaml: attempt to load secret from within secrets file", - ) - assert "attempt to load secret from within secrets file" in caplog.text + + assert e.value.args == ("Secrets not supported in this YAML file",) def test_input_class(): From 1926941d8ea7f9e74322706d8ce757dc838ca8ad Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 22:02:41 +0100 Subject: [PATCH 0949/1818] Upgrade pillow to 8.1.1 (#47223) --- 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 ecbcd8563a78fd..f5d425cb9ef046 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,6 +2,6 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==8.1.0"], + "requirements": ["pydoods==1.0.2", "pillow==8.1.1"], "codeowners": [] } diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 6978f09ab680f0..c8029c2e3130f9 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==8.1.0"], + "requirements": ["pillow==8.1.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 65d8d21fc0cf12..c1a01004fe9520 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==8.1.0"], + "requirements": ["pillow==8.1.1"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index b16eace14fdc26..5867d0d6b51a24 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,6 +2,6 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==8.1.0", "pyzbar==0.1.7"], + "requirements": ["pillow==8.1.1", "pyzbar==0.1.7"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index 01e0275feeb31d..4f9f6514531221 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,6 +2,6 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==8.1.0"], + "requirements": ["pillow==8.1.1"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 99902b8dd363cf..aa9519fd68b399 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,6 +2,6 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==8.1.0", "simplehound==0.3"], + "requirements": ["pillow==8.1.1", "simplehound==0.3"], "codeowners": ["@robmarkcole"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index f039a14d5b3eec..300c3ddd1db6ba 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.3.0", "pycocotools==2.0.1", "numpy==1.19.2", - "pillow==8.1.0" + "pillow==8.1.1" ], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d4b18de88ee58f..44e6195317de64 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 -pillow==8.1.0 +pillow==8.1.1 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2021.1 diff --git a/requirements_all.txt b/requirements_all.txt index 2a2e37dd3a8801..37c69382519ddc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1126,7 +1126,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.0 +pillow==8.1.1 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18a1dc33839157..9b5a7cd7651cad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -575,7 +575,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.0 +pillow==8.1.1 # homeassistant.components.plex plexapi==4.4.0 From d20659d2eee63e244791de8705183d6b5abfa749 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 2 Mar 2021 22:02:59 +0100 Subject: [PATCH 0950/1818] Fix issue when setting boost preset for a turned off Netatmo thermostat (#47275) --- homeassistant/components/netatmo/climate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 91026c40c2fa29..a53f7f9fb089e1 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -352,6 +352,9 @@ def set_hvac_mode(self, hvac_mode: str) -> None: def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" + if self.hvac_mode == HVAC_MODE_OFF: + self.turn_on() + if self.target_temperature == 0: self._home_status.set_room_thermpoint( self._id, From 42af775f53ffffda58fd23a27254220c105a62d3 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 16:10:30 -0500 Subject: [PATCH 0951/1818] Add raw values to zwave_js value notification event (#47258) * add value_raw to value notification event that always shows the untranslated state value * add property key and property to event params --- homeassistant/components/zwave_js/__init__.py | 8 +++++++- homeassistant/components/zwave_js/const.py | 3 +++ tests/components/zwave_js/test_events.py | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 4f4c8d6eab0c83..8272bfdac2cf42 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -30,10 +30,13 @@ ATTR_LABEL, ATTR_NODE_ID, ATTR_PARAMETERS, + ATTR_PROPERTY, + ATTR_PROPERTY_KEY, ATTR_PROPERTY_KEY_NAME, ATTR_PROPERTY_NAME, ATTR_TYPE, ATTR_VALUE, + ATTR_VALUE_RAW, CONF_INTEGRATION_CREATED_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, @@ -213,7 +216,7 @@ def async_on_node_removed(node: ZwaveNode) -> None: def async_on_value_notification(notification: ValueNotification) -> None: """Relay stateless value notification events from Z-Wave nodes to hass.""" device = dev_reg.async_get_device({get_device_id(client, notification.node)}) - value = notification.value + raw_value = value = notification.value if notification.metadata.states: value = notification.metadata.states.get(str(value), value) hass.bus.async_fire( @@ -228,9 +231,12 @@ def async_on_value_notification(notification: ValueNotification) -> None: ATTR_COMMAND_CLASS: notification.command_class, ATTR_COMMAND_CLASS_NAME: notification.command_class_name, ATTR_LABEL: notification.metadata.label, + ATTR_PROPERTY: notification.property_, ATTR_PROPERTY_NAME: notification.property_name, + ATTR_PROPERTY_KEY: notification.property_key, ATTR_PROPERTY_KEY_NAME: notification.property_key_name, ATTR_VALUE: value, + ATTR_VALUE_RAW: raw_value, }, ) diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 19e6fc3db14bd4..27be45c43a0f76 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -26,12 +26,15 @@ ATTR_ENDPOINT = "endpoint" ATTR_LABEL = "label" ATTR_VALUE = "value" +ATTR_VALUE_RAW = "value_raw" ATTR_COMMAND_CLASS = "command_class" ATTR_COMMAND_CLASS_NAME = "command_class_name" ATTR_TYPE = "type" ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" +ATTR_PROPERTY = "property" +ATTR_PROPERTY_KEY = "property_key" ATTR_PARAMETERS = "parameters" # service constants diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index 2a347f6afea9de..e40782270a9cfb 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -47,6 +47,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[0].data["command_class_name"] == "Basic" assert events[0].data["label"] == "Event value" assert events[0].data["value"] == 255 + assert events[0].data["value_raw"] == 255 # Publish fake Scene Activation value notification event = Event( @@ -82,6 +83,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[1].data["command_class_name"] == "Scene Activation" assert events[1].data["label"] == "Scene ID" assert events[1].data["value"] == 16 + assert events[1].data["value_raw"] == 16 # Publish fake Central Scene value notification event = Event( @@ -128,6 +130,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[2].data["command_class_name"] == "Central Scene" assert events[2].data["label"] == "Scene 001" assert events[2].data["value"] == "KeyPressed3x" + assert events[2].data["value_raw"] == 4 async def test_notifications(hass, hank_binary_switch, integration, client): From c327f3fc42744bdda053aac996dc2095b61b45d0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 16:25:09 -0500 Subject: [PATCH 0952/1818] Convert climacell forecast timestamp to isoformat so that UI shows the right times (#47286) --- homeassistant/components/climacell/weather.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index da3282108a5ce6..c77bbfbd50a305 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -1,4 +1,5 @@ """Weather component that handles meteorological data for your location.""" +from datetime import datetime import logging from typing import Any, Callable, Dict, List, Optional @@ -80,7 +81,7 @@ def _translate_condition( def _forecast_dict( hass: HomeAssistantType, - time: str, + forecast_dt: datetime, use_datetime: bool, condition: str, precipitation: Optional[float], @@ -92,10 +93,7 @@ def _forecast_dict( ) -> Dict[str, Any]: """Return formatted Forecast dict from ClimaCell forecast data.""" if use_datetime: - translated_condition = _translate_condition( - condition, - is_up(hass, dt_util.as_utc(dt_util.parse_datetime(time))), - ) + translated_condition = _translate_condition(condition, is_up(hass, forecast_dt)) else: translated_condition = _translate_condition(condition, True) @@ -112,7 +110,7 @@ def _forecast_dict( wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) data = { - ATTR_FORECAST_TIME: time, + ATTR_FORECAST_TIME: forecast_dt.isoformat(), ATTR_FORECAST_CONDITION: translated_condition, ATTR_FORECAST_PRECIPITATION: precipitation, ATTR_FORECAST_PRECIPITATION_PROBABILITY: precipitation_probability, @@ -246,7 +244,9 @@ def forecast(self): # Set default values (in cases where keys don't exist), None will be # returned. Override properties per forecast type as needed for forecast in self.coordinator.data[FORECASTS][self.forecast_type]: - timestamp = self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + forecast_dt = dt_util.parse_datetime( + self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + ) use_datetime = True condition = self._get_cc_value(forecast, CC_ATTR_CONDITION) precipitation = self._get_cc_value(forecast, CC_ATTR_PRECIPITATION) @@ -290,7 +290,7 @@ def forecast(self): forecasts.append( _forecast_dict( self.hass, - timestamp, + forecast_dt, use_datetime, condition, precipitation, From e443597b46d39d16ceccfd319b0574cad5723782 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 17:09:50 -0500 Subject: [PATCH 0953/1818] Bump zwave-js-server-python to 0.20.1 (#47289) --- 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 9e57a3b72e208a..c812515a179cce 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.20.0"], + "requirements": ["zwave-js-server-python==0.20.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 37c69382519ddc..b79401f72df24a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2394,4 +2394,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.0 +zwave-js-server-python==0.20.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b5a7cd7651cad..9e32b02bf397a4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1231,4 +1231,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.0 +zwave-js-server-python==0.20.1 From d3721bcf2618bcdf6e7516fceb57c88dcf90a825 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Mar 2021 23:22:42 +0100 Subject: [PATCH 0954/1818] Add zwave_js add-on manager (#47251) Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/__init__.py | 27 ++ homeassistant/components/zwave_js/__init__.py | 101 +++++-- homeassistant/components/zwave_js/addon.py | 246 ++++++++++++++++ .../components/zwave_js/config_flow.py | 55 ++-- homeassistant/components/zwave_js/const.py | 8 + .../components/zwave_js/strings.json | 1 - .../components/zwave_js/translations/en.json | 6 - tests/components/zwave_js/conftest.py | 118 ++++++++ tests/components/zwave_js/test_config_flow.py | 127 +++------ tests/components/zwave_js/test_init.py | 263 ++++++++++++++++-- 10 files changed, 797 insertions(+), 155 deletions(-) create mode 100644 homeassistant/components/zwave_js/addon.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 4e9a78e75a8cc2..c09927fa7d2f38 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -183,6 +183,18 @@ async def async_uninstall_addon(hass: HomeAssistantType, slug: str) -> dict: return await hassio.send_command(command, timeout=60) +@bind_hass +@api_data +async def async_update_addon(hass: HomeAssistantType, slug: str) -> dict: + """Update add-on. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + command = f"/addons/{slug}/update" + return await hassio.send_command(command, timeout=None) + + @bind_hass @api_data async def async_start_addon(hass: HomeAssistantType, slug: str) -> dict: @@ -232,6 +244,21 @@ async def async_get_addon_discovery_info( return next((addon for addon in discovered_addons if addon["addon"] == slug), None) +@bind_hass +@api_data +async def async_create_snapshot( + hass: HomeAssistantType, payload: dict, partial: bool = False +) -> dict: + """Create a full or partial snapshot. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + snapshot_type = "partial" if partial else "full" + command = f"/snapshots/new/{snapshot_type}" + return await hassio.send_command(command, payload=payload, timeout=None) + + @callback @bind_hass def get_info(hass): diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 8272bfdac2cf42..0c5a55c25fb2be 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,16 +1,14 @@ """The Z-Wave JS integration.""" import asyncio -import logging from typing import Callable, List from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.exceptions import BaseZwaveJSServerError +from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification -from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -19,9 +17,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send +from .addon import AddonError, AddonManager, get_addon_manager from .api import async_register_api from .const import ( - ADDON_SLUG, ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DEVICE_ID, @@ -38,10 +36,14 @@ ATTR_VALUE, ATTR_VALUE_RAW, CONF_INTEGRATION_CREATED_ADDON, + CONF_NETWORK_KEY, + CONF_USB_PATH, + CONF_USE_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, + LOGGER, PLATFORMS, ZWAVE_JS_EVENT, ) @@ -49,10 +51,11 @@ from .helpers import get_device_id, get_old_value_id, get_unique_id from .services import ZWaveServices -LOGGER = logging.getLogger(__package__) CONNECT_TIMEOUT = 10 DATA_CLIENT_LISTEN_TASK = "client_listen_task" DATA_START_PLATFORM_TASK = "start_platform_task" +DATA_CONNECT_FAILED_LOGGED = "connect_failed_logged" +DATA_INVALID_SERVER_VERSION_LOGGED = "invalid_server_version_logged" async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -87,6 +90,10 @@ def register_node_in_dev_reg( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" + use_addon = entry.data.get(CONF_USE_ADDON) + if use_addon: + await async_ensure_addon_running(hass, entry) + client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) @@ -257,21 +264,31 @@ def async_on_notification(notification: Notification) -> None: }, ) + entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) # connect and throw error if connection failed try: async with timeout(CONNECT_TIMEOUT): await client.connect() + except InvalidServerVersion as err: + if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED): + LOGGER.error("Invalid server version: %s", err) + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True + if use_addon: + async_ensure_addon_updated(hass) + raise ConfigEntryNotReady from err except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: - LOGGER.error("Failed to connect: %s", err) + if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED): + LOGGER.error("Failed to connect: %s", err) + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True raise ConfigEntryNotReady from err else: LOGGER.info("Connected to Zwave JS Server") + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False unsubscribe_callbacks: List[Callable] = [] - hass.data[DOMAIN][entry.entry_id] = { - DATA_CLIENT: client, - DATA_UNSUBSCRIBE: unsubscribe_callbacks, - } + entry_hass_data[DATA_CLIENT] = client + entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks services = ZWaveServices(hass, ent_reg) services.async_register() @@ -298,7 +315,7 @@ async def handle_ha_shutdown(event: Event) -> None: listen_task = asyncio.create_task( client_listen(hass, entry, client, driver_ready) ) - hass.data[DOMAIN][entry.entry_id][DATA_CLIENT_LISTEN_TASK] = listen_task + entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task unsubscribe_callbacks.append( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) ) @@ -340,7 +357,7 @@ async def handle_ha_shutdown(event: Event) -> None: ) platform_task = hass.async_create_task(start_platforms()) - hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task + entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task return True @@ -416,6 +433,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: platform_task=info[DATA_START_PLATFORM_TASK], ) + if entry.data.get(CONF_USE_ADDON) and entry.disabled_by: + addon_manager: AddonManager = get_addon_manager(hass) + LOGGER.debug("Stopping Z-Wave JS add-on") + try: + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) + return False + return True @@ -424,12 +450,51 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON): return + addon_manager: AddonManager = get_addon_manager(hass) try: - await hass.components.hassio.async_stop_addon(ADDON_SLUG) - except HassioAPIError as err: - LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error(err) return try: - await hass.components.hassio.async_uninstall_addon(ADDON_SLUG) - except HassioAPIError as err: - LOGGER.error("Failed to uninstall the Z-Wave JS add-on: %s", err) + await addon_manager.async_create_snapshot() + except AddonError as err: + LOGGER.error(err) + return + try: + await addon_manager.async_uninstall_addon() + except AddonError as err: + LOGGER.error(err) + + +async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Ensure that Z-Wave JS add-on is installed and running.""" + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + try: + addon_is_installed = await addon_manager.async_is_addon_installed() + addon_is_running = await addon_manager.async_is_addon_running() + except AddonError as err: + LOGGER.error("Failed to get the Z-Wave JS add-on info") + raise ConfigEntryNotReady from err + + usb_path: str = entry.data[CONF_USB_PATH] + network_key: str = entry.data[CONF_NETWORK_KEY] + + if not addon_is_installed: + addon_manager.async_schedule_install_addon(usb_path, network_key) + raise ConfigEntryNotReady + + if not addon_is_running: + addon_manager.async_schedule_setup_addon(usb_path, network_key) + raise ConfigEntryNotReady + + +@callback +def async_ensure_addon_updated(hass: HomeAssistant) -> None: + """Ensure that Z-Wave JS add-on is updated and running.""" + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + addon_manager.async_schedule_update_addon() diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py new file mode 100644 index 00000000000000..54169dcaf94701 --- /dev/null +++ b/homeassistant/components/zwave_js/addon.py @@ -0,0 +1,246 @@ +"""Provide add-on management.""" +from __future__ import annotations + +import asyncio +from functools import partial +from typing import Any, Callable, Optional, TypeVar, cast + +from homeassistant.components.hassio import ( + async_create_snapshot, + async_get_addon_discovery_info, + async_get_addon_info, + async_install_addon, + async_set_addon_options, + async_start_addon, + async_stop_addon, + async_uninstall_addon, + async_update_addon, +) +from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.singleton import singleton + +from .const import ADDON_SLUG, CONF_ADDON_DEVICE, CONF_ADDON_NETWORK_KEY, DOMAIN, LOGGER + +F = TypeVar("F", bound=Callable[..., Any]) # pylint: disable=invalid-name + +DATA_ADDON_MANAGER = f"{DOMAIN}_addon_manager" + + +@singleton(DATA_ADDON_MANAGER) +@callback +def get_addon_manager(hass: HomeAssistant) -> AddonManager: + """Get the add-on manager.""" + return AddonManager(hass) + + +def api_error(error_message: str) -> Callable[[F], F]: + """Handle HassioAPIError and raise a specific AddonError.""" + + def handle_hassio_api_error(func: F) -> F: + """Handle a HassioAPIError.""" + + async def wrapper(*args, **kwargs): # type: ignore + """Wrap an add-on manager method.""" + try: + return_value = await func(*args, **kwargs) + except HassioAPIError as err: + raise AddonError(error_message) from err + + return return_value + + return cast(F, wrapper) + + return handle_hassio_api_error + + +class AddonManager: + """Manage the add-on. + + Methods may raise AddonError. + Only one instance of this class may exist + to keep track of running add-on tasks. + """ + + def __init__(self, hass: HomeAssistant) -> None: + """Set up the add-on manager.""" + self._hass = hass + self._install_task: Optional[asyncio.Task] = None + self._update_task: Optional[asyncio.Task] = None + self._setup_task: Optional[asyncio.Task] = None + + def task_in_progress(self) -> bool: + """Return True if any of the add-on tasks are in progress.""" + return any( + task and not task.done() + for task in ( + self._install_task, + self._setup_task, + self._update_task, + ) + ) + + @api_error("Failed to get Z-Wave JS add-on discovery info") + async def async_get_addon_discovery_info(self) -> dict: + """Return add-on discovery info.""" + discovery_info = await async_get_addon_discovery_info(self._hass, ADDON_SLUG) + + if not discovery_info: + raise AddonError("Failed to get Z-Wave JS add-on discovery info") + + discovery_info_config: dict = discovery_info["config"] + return discovery_info_config + + @api_error("Failed to get the Z-Wave JS add-on info") + async def async_get_addon_info(self) -> dict: + """Return and cache Z-Wave JS add-on info.""" + addon_info: dict = await async_get_addon_info(self._hass, ADDON_SLUG) + return addon_info + + async def async_is_addon_running(self) -> bool: + """Return True if Z-Wave JS add-on is running.""" + addon_info = await self.async_get_addon_info() + return bool(addon_info["state"] == "started") + + async def async_is_addon_installed(self) -> bool: + """Return True if Z-Wave JS add-on is installed.""" + addon_info = await self.async_get_addon_info() + return addon_info["version"] is not None + + async def async_get_addon_options(self) -> dict: + """Get Z-Wave JS add-on options.""" + addon_info = await self.async_get_addon_info() + return cast(dict, addon_info["options"]) + + @api_error("Failed to set the Z-Wave JS add-on options") + async def async_set_addon_options(self, config: dict) -> None: + """Set Z-Wave JS add-on options.""" + options = {"options": config} + await async_set_addon_options(self._hass, ADDON_SLUG, options) + + @api_error("Failed to install the Z-Wave JS add-on") + async def async_install_addon(self) -> None: + """Install the Z-Wave JS add-on.""" + await async_install_addon(self._hass, ADDON_SLUG) + + @callback + def async_schedule_install_addon( + self, usb_path: str, network_key: str + ) -> asyncio.Task: + """Schedule a task that installs and sets up the Z-Wave JS add-on. + + Only schedule a new install task if the there's no running task. + """ + if not self._install_task or self._install_task.done(): + LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") + self._install_task = self._async_schedule_addon_operation( + self.async_install_addon, + partial(self.async_setup_addon, usb_path, network_key), + ) + return self._install_task + + @api_error("Failed to uninstall the Z-Wave JS add-on") + async def async_uninstall_addon(self) -> None: + """Uninstall the Z-Wave JS add-on.""" + await async_uninstall_addon(self._hass, ADDON_SLUG) + + @api_error("Failed to update the Z-Wave JS add-on") + async def async_update_addon(self) -> None: + """Update the Z-Wave JS add-on if needed.""" + addon_info = await self.async_get_addon_info() + addon_version = addon_info["version"] + update_available = addon_info["update_available"] + + if addon_version is None: + raise AddonError("Z-Wave JS add-on is not installed") + + if not update_available: + return + + await async_update_addon(self._hass, ADDON_SLUG) + + @callback + def async_schedule_update_addon(self) -> asyncio.Task: + """Schedule a task that updates and sets up the Z-Wave JS add-on. + + Only schedule a new update task if the there's no running task. + """ + if not self._update_task or self._update_task.done(): + LOGGER.info("Trying to update the Z-Wave JS add-on") + self._update_task = self._async_schedule_addon_operation( + self.async_create_snapshot, self.async_update_addon + ) + return self._update_task + + @api_error("Failed to start the Z-Wave JS add-on") + async def async_start_addon(self) -> None: + """Start the Z-Wave JS add-on.""" + await async_start_addon(self._hass, ADDON_SLUG) + + @api_error("Failed to stop the Z-Wave JS add-on") + async def async_stop_addon(self) -> None: + """Stop the Z-Wave JS add-on.""" + await async_stop_addon(self._hass, ADDON_SLUG) + + async def async_setup_addon(self, usb_path: str, network_key: str) -> None: + """Configure and start Z-Wave JS add-on.""" + addon_options = await self.async_get_addon_options() + + new_addon_options = { + CONF_ADDON_DEVICE: usb_path, + CONF_ADDON_NETWORK_KEY: network_key, + } + + if new_addon_options != addon_options: + await self.async_set_addon_options(new_addon_options) + + await self.async_start_addon() + + @callback + def async_schedule_setup_addon( + self, usb_path: str, network_key: str + ) -> asyncio.Task: + """Schedule a task that configures and starts the Z-Wave JS add-on. + + Only schedule a new setup task if the there's no running task. + """ + if not self._setup_task or self._setup_task.done(): + LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") + self._setup_task = self._async_schedule_addon_operation( + partial(self.async_setup_addon, usb_path, network_key) + ) + return self._setup_task + + @api_error("Failed to create a snapshot of the Z-Wave JS add-on.") + async def async_create_snapshot(self) -> None: + """Create a partial snapshot of the Z-Wave JS add-on.""" + addon_info = await self.async_get_addon_info() + addon_version = addon_info["version"] + name = f"addon_{ADDON_SLUG}_{addon_version}" + + LOGGER.debug("Creating snapshot: %s", name) + await async_create_snapshot( + self._hass, + {"name": name, "addons": [ADDON_SLUG]}, + partial=True, + ) + + @callback + def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task: + """Schedule an add-on task.""" + + async def addon_operation() -> None: + """Do the add-on operation and catch AddonError.""" + for func in funcs: + try: + await func() + except AddonError as err: + LOGGER.error(err) + break + + return self._hass.async_create_task(addon_operation()) + + +class AddonError(HomeAssistantError): + """Represent an error with Z-Wave JS add-on.""" diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index cc19fb85d3a147..37923c574b4371 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -9,33 +9,25 @@ from zwave_js_server.version import VersionInfo, get_server_version from homeassistant import config_entries, exceptions -from homeassistant.components.hassio import ( - async_get_addon_discovery_info, - async_get_addon_info, - async_install_addon, - async_set_addon_options, - async_start_addon, - is_hassio, -) -from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.components.hassio import is_hassio from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .addon import AddonError, AddonManager, get_addon_manager from .const import ( # pylint:disable=unused-import - ADDON_SLUG, + CONF_ADDON_DEVICE, + CONF_ADDON_NETWORK_KEY, CONF_INTEGRATION_CREATED_ADDON, + CONF_NETWORK_KEY, + CONF_USB_PATH, CONF_USE_ADDON, DOMAIN, ) _LOGGER = logging.getLogger(__name__) -CONF_ADDON_DEVICE = "device" -CONF_ADDON_NETWORK_KEY = "network_key" -CONF_NETWORK_KEY = "network_key" -CONF_USB_PATH = "usb_path" DEFAULT_URL = "ws://localhost:3000" TITLE = "Z-Wave JS" @@ -180,6 +172,11 @@ async def async_step_on_supervisor( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle logic when on Supervisor host.""" + # Only one entry with Supervisor add-on support is allowed. + for entry in self.hass.config_entries.async_entries(DOMAIN): + if entry.data.get(CONF_USE_ADDON): + return await self.async_step_manual() + if user_input is None: return self.async_show_form( step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA @@ -212,7 +209,7 @@ async def async_step_install_addon( try: await self.install_task - except HassioAPIError as err: + except AddonError as err: _LOGGER.error("Failed to install Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="install_failed") @@ -275,7 +272,7 @@ async def async_step_start_addon( try: await self.start_task - except (CannotConnect, HassioAPIError) as err: + except (CannotConnect, AddonError) as err: _LOGGER.error("Failed to start Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="start_failed") @@ -290,8 +287,9 @@ async def async_step_start_failed( async def _async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" assert self.hass + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_start_addon(self.hass, ADDON_SLUG) + await addon_manager.async_start_addon() # Sleep some seconds to let the add-on start properly before connecting. for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): await asyncio.sleep(ADDON_SETUP_TIMEOUT) @@ -345,9 +343,10 @@ async def async_step_finish_addon_setup( async def _async_get_addon_info(self) -> dict: """Return and cache Z-Wave JS add-on info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - addon_info: dict = await async_get_addon_info(self.hass, ADDON_SLUG) - except HassioAPIError as err: + addon_info: dict = await addon_manager.async_get_addon_info() + except AddonError as err: _LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err) raise AbortFlow("addon_info_failed") from err @@ -371,16 +370,18 @@ async def _async_get_addon_config(self) -> dict: async def _async_set_addon_config(self, config: dict) -> None: """Set Z-Wave JS add-on config.""" options = {"options": config} + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_set_addon_options(self.hass, ADDON_SLUG, options) - except HassioAPIError as err: + await addon_manager.async_set_addon_options(options) + except AddonError as err: _LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err) raise AbortFlow("addon_set_config_failed") from err async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_install_addon(self.hass, ADDON_SLUG) + await addon_manager.async_install_addon() finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( @@ -389,17 +390,13 @@ async def _async_install_addon(self) -> None: async def _async_get_addon_discovery_info(self) -> dict: """Return add-on discovery info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - discovery_info = await async_get_addon_discovery_info(self.hass, ADDON_SLUG) - except HassioAPIError as err: + discovery_info_config = await addon_manager.async_get_addon_discovery_info() + except AddonError as err: _LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err) raise AbortFlow("addon_get_discovery_info_failed") from err - if not discovery_info: - _LOGGER.error("Failed to get Z-Wave JS add-on discovery info") - raise AbortFlow("addon_missing_discovery_info") - - discovery_info_config: dict = discovery_info["config"] return discovery_info_config diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 27be45c43a0f76..ffd6031349a966 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -1,5 +1,11 @@ """Constants for the Z-Wave JS integration.""" +import logging + +CONF_ADDON_DEVICE = "device" +CONF_ADDON_NETWORK_KEY = "network_key" CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon" +CONF_NETWORK_KEY = "network_key" +CONF_USB_PATH = "usb_path" CONF_USE_ADDON = "use_addon" DOMAIN = "zwave_js" PLATFORMS = [ @@ -19,6 +25,8 @@ EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry" +LOGGER = logging.getLogger(__package__) + # constants for events ZWAVE_JS_EVENT = f"{DOMAIN}_event" ATTR_NODE_ID = "node_id" diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 5d3aa730a7c29d..eb13ad512e3e3c 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -41,7 +41,6 @@ "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", - "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "progress": { diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 5be980d52cb07a..101942dc717bad 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -4,7 +4,6 @@ "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", "addon_info_failed": "Failed to get Z-Wave JS add-on info.", "addon_install_failed": "Failed to install the Z-Wave JS add-on.", - "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "already_configured": "Device is already configured", @@ -49,11 +48,6 @@ }, "start_addon": { "title": "The Z-Wave JS add-on is starting." - }, - "user": { - "data": { - "url": "URL" - } } } }, diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 72835fb17c11e3..50cacd974229cf 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -14,6 +14,124 @@ from tests.common import MockConfigEntry, load_fixture +# Add-on fixtures + + +@pytest.fixture(name="addon_info_side_effect") +def addon_info_side_effect_fixture(): + """Return the add-on info side effect.""" + return None + + +@pytest.fixture(name="addon_info") +def mock_addon_info(addon_info_side_effect): + """Mock Supervisor add-on info.""" + with patch( + "homeassistant.components.zwave_js.addon.async_get_addon_info", + side_effect=addon_info_side_effect, + ) as addon_info: + addon_info.return_value = {} + yield addon_info + + +@pytest.fixture(name="addon_running") +def mock_addon_running(addon_info): + """Mock add-on already running.""" + addon_info.return_value["state"] = "started" + return addon_info + + +@pytest.fixture(name="addon_installed") +def mock_addon_installed(addon_info): + """Mock add-on already installed but not running.""" + addon_info.return_value["state"] = "stopped" + addon_info.return_value["version"] = "1.0" + return addon_info + + +@pytest.fixture(name="addon_options") +def mock_addon_options(addon_info): + """Mock add-on options.""" + addon_info.return_value["options"] = {} + return addon_info.return_value["options"] + + +@pytest.fixture(name="set_addon_options_side_effect") +def set_addon_options_side_effect_fixture(): + """Return the set add-on options side effect.""" + return None + + +@pytest.fixture(name="set_addon_options") +def mock_set_addon_options(set_addon_options_side_effect): + """Mock set add-on options.""" + with patch( + "homeassistant.components.zwave_js.addon.async_set_addon_options", + side_effect=set_addon_options_side_effect, + ) as set_options: + yield set_options + + +@pytest.fixture(name="install_addon") +def mock_install_addon(): + """Mock install add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_install_addon" + ) as install_addon: + yield install_addon + + +@pytest.fixture(name="update_addon") +def mock_update_addon(): + """Mock update add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_update_addon" + ) as update_addon: + yield update_addon + + +@pytest.fixture(name="start_addon_side_effect") +def start_addon_side_effect_fixture(): + """Return the set add-on options side effect.""" + return None + + +@pytest.fixture(name="start_addon") +def mock_start_addon(start_addon_side_effect): + """Mock start add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_start_addon", + side_effect=start_addon_side_effect, + ) as start_addon: + yield start_addon + + +@pytest.fixture(name="stop_addon") +def stop_addon_fixture(): + """Mock stop add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_stop_addon" + ) as stop_addon: + yield stop_addon + + +@pytest.fixture(name="uninstall_addon") +def uninstall_addon_fixture(): + """Mock uninstall add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_uninstall_addon" + ) as uninstall_addon: + yield uninstall_addon + + +@pytest.fixture(name="create_shapshot") +def create_snapshot_fixture(): + """Mock create snapshot.""" + with patch( + "homeassistant.components.zwave_js.addon.async_create_snapshot" + ) as create_shapshot: + yield create_shapshot + @pytest.fixture(name="device_registry") async def device_registry_fixture(hass): diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 08b0ffe3080f2b..fc97f7420cf22d 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -44,93 +44,13 @@ def discovery_info_side_effect_fixture(): def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect): """Mock get add-on discovery info.""" with patch( - "homeassistant.components.zwave_js.config_flow.async_get_addon_discovery_info", + "homeassistant.components.zwave_js.addon.async_get_addon_discovery_info", side_effect=discovery_info_side_effect, return_value=discovery_info, ) as get_addon_discovery_info: yield get_addon_discovery_info -@pytest.fixture(name="addon_info_side_effect") -def addon_info_side_effect_fixture(): - """Return the add-on info side effect.""" - return None - - -@pytest.fixture(name="addon_info") -def mock_addon_info(addon_info_side_effect): - """Mock Supervisor add-on info.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_get_addon_info", - side_effect=addon_info_side_effect, - ) as addon_info: - addon_info.return_value = {} - yield addon_info - - -@pytest.fixture(name="addon_running") -def mock_addon_running(addon_info): - """Mock add-on already running.""" - addon_info.return_value["state"] = "started" - return addon_info - - -@pytest.fixture(name="addon_installed") -def mock_addon_installed(addon_info): - """Mock add-on already installed but not running.""" - addon_info.return_value["state"] = "stopped" - addon_info.return_value["version"] = "1.0" - return addon_info - - -@pytest.fixture(name="addon_options") -def mock_addon_options(addon_info): - """Mock add-on options.""" - addon_info.return_value["options"] = {} - return addon_info.return_value["options"] - - -@pytest.fixture(name="set_addon_options_side_effect") -def set_addon_options_side_effect_fixture(): - """Return the set add-on options side effect.""" - return None - - -@pytest.fixture(name="set_addon_options") -def mock_set_addon_options(set_addon_options_side_effect): - """Mock set add-on options.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_set_addon_options", - side_effect=set_addon_options_side_effect, - ) as set_options: - yield set_options - - -@pytest.fixture(name="install_addon") -def mock_install_addon(): - """Mock install add-on.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_install_addon" - ) as install_addon: - yield install_addon - - -@pytest.fixture(name="start_addon_side_effect") -def start_addon_side_effect_fixture(): - """Return the set add-on options side effect.""" - return None - - -@pytest.fixture(name="start_addon") -def mock_start_addon(start_addon_side_effect): - """Mock start add-on.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_start_addon", - side_effect=start_addon_side_effect, - ) as start_addon: - yield start_addon - - @pytest.fixture(name="server_version_side_effect") def server_version_side_effect_fixture(): """Return the server version side effect.""" @@ -587,6 +507,49 @@ async def test_not_addon(hass, supervisor): assert len(mock_setup_entry.mock_calls) == 1 +async def test_addon_already_configured(hass, supervisor): + """Test add-on already configured leads to manual step.""" + entry = MockConfigEntry( + domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678 + ) + entry.add_to_hass(hass) + + await setup.async_setup_component(hass, "persistent_notification", {}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "manual" + + with patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:3000", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == TITLE + assert result["data"] == { + "url": "ws://localhost:3000", + "usb_path": None, + "network_key": None, + "use_addon": False, + "integration_created_addon": False, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 2 + + @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running( hass, @@ -654,7 +617,7 @@ async def test_addon_running( None, None, None, - "addon_missing_discovery_info", + "addon_get_discovery_info_failed", ), ( {"config": ADDON_DISCOVERY_INFO}, diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 2a2f249c361854..6f60bbc0300490 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -1,9 +1,9 @@ """Test the Z-Wave JS init module.""" from copy import deepcopy -from unittest.mock import patch +from unittest.mock import call, patch import pytest -from zwave_js_server.exceptions import BaseZwaveJSServerError +from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion from zwave_js_server.model.node import Node from homeassistant.components.hassio.handler import HassioAPIError @@ -11,6 +11,7 @@ from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.config_entries import ( CONN_CLASS_LOCAL_PUSH, + DISABLED_USER, ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_RETRY, @@ -34,22 +35,6 @@ def connect_timeout_fixture(): yield timeout -@pytest.fixture(name="stop_addon") -def stop_addon_fixture(): - """Mock stop add-on.""" - with patch("homeassistant.components.hassio.async_stop_addon") as stop_addon: - yield stop_addon - - -@pytest.fixture(name="uninstall_addon") -def uninstall_addon_fixture(): - """Mock uninstall add-on.""" - with patch( - "homeassistant.components.hassio.async_uninstall_addon" - ) as uninstall_addon: - yield uninstall_addon - - async def test_entry_setup_unload(hass, client, integration): """Test the integration set up and unload.""" entry = integration @@ -367,7 +352,203 @@ async def test_existing_node_not_ready(hass, client, multisensor_6, device_regis ) -async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): +async def test_start_addon( + hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon +): + """Test start the Z-Wave JS add-on during entry setup.""" + device = "/test" + network_key = "abc123" + addon_options = { + "device": device, + "network_key": network_key, + } + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 0 + assert set_addon_options.call_count == 1 + assert set_addon_options.call_args == call( + hass, "core_zwave_js", {"options": addon_options} + ) + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_zwave_js") + + +async def test_install_addon( + hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon +): + """Test install and start the Z-Wave JS add-on during entry setup.""" + addon_installed.return_value["version"] = None + device = "/test" + network_key = "abc123" + addon_options = { + "device": device, + "network_key": network_key, + } + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 1 + assert install_addon.call_args == call(hass, "core_zwave_js") + assert set_addon_options.call_count == 1 + assert set_addon_options.call_args == call( + hass, "core_zwave_js", {"options": addon_options} + ) + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_zwave_js") + + +@pytest.mark.parametrize("addon_info_side_effect", [HassioAPIError("Boom")]) +async def test_addon_info_failure( + hass, + addon_installed, + install_addon, + addon_options, + set_addon_options, + start_addon, +): + """Test failure to get add-on info for Z-Wave JS add-on during entry setup.""" + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 0 + assert start_addon.call_count == 0 + + +@pytest.mark.parametrize( + "addon_version, update_available, update_calls, update_addon_side_effect", + [ + ("1.0", True, 1, None), + ("1.0", False, 0, None), + ("1.0", True, 1, HassioAPIError("Boom")), + ], +) +async def test_update_addon( + hass, + client, + addon_info, + addon_installed, + addon_running, + create_shapshot, + update_addon, + addon_options, + addon_version, + update_available, + update_calls, + update_addon_side_effect, +): + """Test update the Z-Wave JS add-on during entry setup.""" + addon_info.return_value["version"] = addon_version + addon_info.return_value["update_available"] = update_available + update_addon.side_effect = update_addon_side_effect + client.connect.side_effect = InvalidServerVersion("Invalid version") + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={ + "url": "ws://host1:3001", + "use_addon": True, + "usb_path": device, + "network_key": network_key, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]}, + partial=True, + ) + assert update_addon.call_count == update_calls + + +@pytest.mark.parametrize( + "stop_addon_side_effect, entry_state", + [ + (None, ENTRY_STATE_NOT_LOADED), + (HassioAPIError("Boom"), ENTRY_STATE_LOADED), + ], +) +async def test_stop_addon( + hass, + client, + addon_installed, + addon_running, + addon_options, + stop_addon, + stop_addon_side_effect, + entry_state, +): + """Test stop the Z-Wave JS add-on on entry unload if entry is disabled.""" + stop_addon.side_effect = stop_addon_side_effect + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={ + "url": "ws://host1:3001", + "use_addon": True, + "usb_path": device, + "network_key": network_key, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_set_disabled_by(entry.entry_id, DISABLED_USER) + await hass.async_block_till_done() + + assert entry.state == entry_state + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + + +async def test_remove_entry( + hass, addon_installed, stop_addon, create_shapshot, uninstall_addon, caplog +): """Test remove the config entry.""" # test successful remove without created add-on entry = MockConfigEntry( @@ -398,10 +579,19 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_zwave_js") assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 stop_addon.reset_mock() + create_shapshot.reset_mock() uninstall_addon.reset_mock() # test add-on stop failure @@ -412,12 +602,39 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 0 assert uninstall_addon.call_count == 0 assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to stop the Z-Wave JS add-on" in caplog.text stop_addon.side_effect = None stop_addon.reset_mock() + create_shapshot.reset_mock() + uninstall_addon.reset_mock() + + # test create snapshot failure + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + create_shapshot.side_effect = HassioAPIError() + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) + assert uninstall_addon.call_count == 0 + assert entry.state == ENTRY_STATE_NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert "Failed to create a snapshot of the Z-Wave JS add-on" in caplog.text + create_shapshot.side_effect = None + stop_addon.reset_mock() + create_shapshot.reset_mock() uninstall_addon.reset_mock() # test add-on uninstall failure @@ -428,7 +645,15 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_zwave_js") assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text From ab5173c4cf07c7f2b9ec64241137c060e4711154 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 25 Feb 2021 00:05:20 +0000 Subject: [PATCH 0955/1818] [ci skip] Translation update --- .../components/arcam_fmj/translations/nl.json | 1 + .../components/asuswrt/translations/nl.json | 1 + .../components/august/translations/nl.json | 3 +- .../components/blink/translations/nl.json | 1 + .../components/bond/translations/ca.json | 4 +-- .../components/bond/translations/et.json | 4 +-- .../components/bond/translations/fr.json | 4 +-- .../components/bond/translations/nl.json | 1 + .../components/bond/translations/no.json | 4 +-- .../components/bond/translations/pl.json | 4 +-- .../components/bond/translations/ru.json | 4 +-- .../components/bond/translations/zh-Hant.json | 4 +-- .../components/broadlink/translations/nl.json | 1 + .../components/climacell/translations/af.json | 10 ++++++ .../components/climacell/translations/ca.json | 34 +++++++++++++++++++ .../components/climacell/translations/en.json | 34 +++++++++++++++++++ .../components/climacell/translations/et.json | 34 +++++++++++++++++++ .../components/climacell/translations/fr.json | 34 +++++++++++++++++++ .../components/climacell/translations/nl.json | 32 +++++++++++++++++ .../components/climacell/translations/no.json | 34 +++++++++++++++++++ .../components/climacell/translations/ru.json | 34 +++++++++++++++++++ .../components/daikin/translations/nl.json | 4 ++- .../components/enocean/translations/nl.json | 7 ++++ .../faa_delays/translations/en.json | 12 ++++--- .../faa_delays/translations/fr.json | 21 ++++++++++++ .../faa_delays/translations/nl.json | 21 ++++++++++++ .../fireservicerota/translations/nl.json | 3 +- .../fireservicerota/translations/no.json | 2 +- .../components/firmata/translations/nl.json | 7 ++++ .../flunearyou/translations/nl.json | 3 ++ .../components/fritzbox/translations/nl.json | 1 + .../components/goalzero/translations/nl.json | 1 + .../home_connect/translations/nl.json | 3 +- .../components/homekit/translations/ca.json | 8 ++--- .../components/homekit/translations/en.json | 21 ++++++++++-- .../components/homekit/translations/et.json | 10 +++--- .../components/homekit/translations/fr.json | 8 ++--- .../components/homekit/translations/no.json | 8 ++--- .../components/homekit/translations/pl.json | 8 ++--- .../components/homekit/translations/ru.json | 8 ++--- .../homekit/translations/zh-Hant.json | 8 ++--- .../huawei_lte/translations/nl.json | 1 + .../components/icloud/translations/nl.json | 6 ++-- .../components/ifttt/translations/nl.json | 3 +- .../components/insteon/translations/nl.json | 3 ++ .../keenetic_ndms2/translations/nl.json | 3 +- .../components/kmtronic/translations/fr.json | 21 ++++++++++++ .../components/kmtronic/translations/pl.json | 21 ++++++++++++ .../components/litejet/translations/ca.json | 19 +++++++++++ .../components/litejet/translations/fr.json | 16 +++++++++ .../components/litejet/translations/no.json | 19 +++++++++++ .../components/litejet/translations/pl.json | 19 +++++++++++ .../components/litejet/translations/ru.json | 19 +++++++++++ .../components/litejet/translations/tr.json | 9 +++++ .../litejet/translations/zh-Hant.json | 19 +++++++++++ .../litterrobot/translations/fr.json | 20 +++++++++++ .../litterrobot/translations/no.json | 20 +++++++++++ .../litterrobot/translations/pl.json | 20 +++++++++++ .../components/locative/translations/nl.json | 3 +- .../components/mailgun/translations/nl.json | 3 +- .../components/mazda/translations/nl.json | 3 +- .../components/mullvad/translations/ca.json | 22 ++++++++++++ .../components/mullvad/translations/en.json | 6 ++++ .../components/mullvad/translations/et.json | 22 ++++++++++++ .../components/mullvad/translations/fr.json | 22 ++++++++++++ .../components/mullvad/translations/nl.json | 22 ++++++++++++ .../components/mullvad/translations/no.json | 22 ++++++++++++ .../components/mullvad/translations/ru.json | 22 ++++++++++++ .../components/mullvad/translations/tr.json | 12 +++++++ .../components/netatmo/translations/et.json | 22 ++++++++++++ .../components/netatmo/translations/fr.json | 22 ++++++++++++ .../components/netatmo/translations/nl.json | 25 +++++++++++++- .../components/netatmo/translations/ru.json | 22 ++++++++++++ .../components/netatmo/translations/tr.json | 16 +++++++++ .../nightscout/translations/nl.json | 1 + .../components/nzbget/translations/nl.json | 1 + .../plum_lightpad/translations/nl.json | 3 +- .../components/poolsense/translations/nl.json | 3 +- .../translations/fr.json | 21 ++++++++++++ .../components/rpi_power/translations/nl.json | 5 +++ .../ruckus_unleashed/translations/nl.json | 1 + .../components/sharkiq/translations/nl.json | 1 + .../components/smappee/translations/nl.json | 3 +- .../components/sms/translations/nl.json | 3 +- .../components/somfy/translations/nl.json | 1 + .../speedtestdotnet/translations/nl.json | 5 +++ .../components/spider/translations/nl.json | 3 ++ .../components/subaru/translations/fr.json | 31 +++++++++++++++++ .../components/syncthru/translations/nl.json | 3 +- .../components/tile/translations/nl.json | 3 +- .../components/toon/translations/nl.json | 2 ++ .../totalconnect/translations/fr.json | 11 +++++- .../totalconnect/translations/nl.json | 5 +++ .../totalconnect/translations/no.json | 17 ++++++++-- .../totalconnect/translations/pl.json | 17 ++++++++-- .../xiaomi_miio/translations/no.json | 1 + .../xiaomi_miio/translations/pl.json | 1 + .../components/zwave_js/translations/ca.json | 7 +++- .../components/zwave_js/translations/et.json | 7 +++- .../components/zwave_js/translations/fr.json | 7 +++- .../components/zwave_js/translations/no.json | 7 +++- .../components/zwave_js/translations/pl.json | 7 +++- .../components/zwave_js/translations/ru.json | 7 +++- .../zwave_js/translations/zh-Hant.json | 7 +++- 104 files changed, 1060 insertions(+), 81 deletions(-) create mode 100644 homeassistant/components/climacell/translations/af.json create mode 100644 homeassistant/components/climacell/translations/ca.json create mode 100644 homeassistant/components/climacell/translations/en.json create mode 100644 homeassistant/components/climacell/translations/et.json create mode 100644 homeassistant/components/climacell/translations/fr.json create mode 100644 homeassistant/components/climacell/translations/nl.json create mode 100644 homeassistant/components/climacell/translations/no.json create mode 100644 homeassistant/components/climacell/translations/ru.json create mode 100644 homeassistant/components/enocean/translations/nl.json create mode 100644 homeassistant/components/faa_delays/translations/fr.json create mode 100644 homeassistant/components/faa_delays/translations/nl.json create mode 100644 homeassistant/components/firmata/translations/nl.json create mode 100644 homeassistant/components/kmtronic/translations/fr.json create mode 100644 homeassistant/components/kmtronic/translations/pl.json create mode 100644 homeassistant/components/litejet/translations/ca.json create mode 100644 homeassistant/components/litejet/translations/fr.json create mode 100644 homeassistant/components/litejet/translations/no.json create mode 100644 homeassistant/components/litejet/translations/pl.json create mode 100644 homeassistant/components/litejet/translations/ru.json create mode 100644 homeassistant/components/litejet/translations/tr.json create mode 100644 homeassistant/components/litejet/translations/zh-Hant.json create mode 100644 homeassistant/components/litterrobot/translations/fr.json create mode 100644 homeassistant/components/litterrobot/translations/no.json create mode 100644 homeassistant/components/litterrobot/translations/pl.json create mode 100644 homeassistant/components/mullvad/translations/ca.json create mode 100644 homeassistant/components/mullvad/translations/et.json create mode 100644 homeassistant/components/mullvad/translations/fr.json create mode 100644 homeassistant/components/mullvad/translations/nl.json create mode 100644 homeassistant/components/mullvad/translations/no.json create mode 100644 homeassistant/components/mullvad/translations/ru.json create mode 100644 homeassistant/components/mullvad/translations/tr.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/fr.json create mode 100644 homeassistant/components/subaru/translations/fr.json diff --git a/homeassistant/components/arcam_fmj/translations/nl.json b/homeassistant/components/arcam_fmj/translations/nl.json index 5607b426cc988f..03465d5c53df4e 100644 --- a/homeassistant/components/arcam_fmj/translations/nl.json +++ b/homeassistant/components/arcam_fmj/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" }, "error": { diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index 1128a820cd577c..9d1e76aaf2b5c2 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", + "ssh_not_file": "SSH-sleutelbestand niet gevonden", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 1697f634d9a82e..e48d27801ccdc7 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd" + "already_configured": "Account al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json index 4067bf75f834e0..f1f1ce7888bd12 100644 --- a/homeassistant/components/blink/translations/nl.json +++ b/homeassistant/components/blink/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_access_token": "Ongeldig toegangstoken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/bond/translations/ca.json b/homeassistant/components/bond/translations/ca.json index 3903ea77c34166..1d1df91563057b 100644 --- a/homeassistant/components/bond/translations/ca.json +++ b/homeassistant/components/bond/translations/ca.json @@ -9,13 +9,13 @@ "old_firmware": "Hi ha un programari antic i no compatible al dispositiu Bond - actualitza'l abans de continuar", "unknown": "Error inesperat" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token d'acc\u00e9s" }, - "description": "Vols configurar {bond_id}?" + "description": "Vols configurar {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/et.json b/homeassistant/components/bond/translations/et.json index dc6a8414bce9ef..5e9a8e4493f44b 100644 --- a/homeassistant/components/bond/translations/et.json +++ b/homeassistant/components/bond/translations/et.json @@ -9,13 +9,13 @@ "old_firmware": "Bondi seadme ei toeta vana p\u00fcsivara - uuenda enne j\u00e4tkamist", "unknown": "Tundmatu viga" }, - "flow_title": "Bond: {bond_id} ( {host} )", + "flow_title": "Bond: {name} ( {host} )", "step": { "confirm": { "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Kas soovid seadistada teenuse {bond_id} ?" + "description": "Kas soovid seadistada teenust {name} ?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/fr.json b/homeassistant/components/bond/translations/fr.json index 496a21339cbf24..d9eb14b1a620cc 100644 --- a/homeassistant/components/bond/translations/fr.json +++ b/homeassistant/components/bond/translations/fr.json @@ -9,13 +9,13 @@ "old_firmware": "Ancien micrologiciel non pris en charge sur l'appareil Bond - veuillez mettre \u00e0 niveau avant de continuer", "unknown": "Erreur inattendue" }, - "flow_title": "Bond : {bond_id} ({h\u00f4te})", + "flow_title": "Lien : {name} ({host})", "step": { "confirm": { "data": { "access_token": "Jeton d'acc\u00e8s" }, - "description": "Voulez-vous configurer {bond_id} ?" + "description": "Voulez-vous configurer {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index 8010dfc2e78645..b5d8c593ea9e93 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "access_token": "Toegangstoken", "host": "Host" } } diff --git a/homeassistant/components/bond/translations/no.json b/homeassistant/components/bond/translations/no.json index 01ff745eed36c7..c09b7a1763533b 100644 --- a/homeassistant/components/bond/translations/no.json +++ b/homeassistant/components/bond/translations/no.json @@ -9,13 +9,13 @@ "old_firmware": "Gammel fastvare som ikke st\u00f8ttes p\u00e5 Bond-enheten \u2013 vennligst oppgrader f\u00f8r du fortsetter", "unknown": "Uventet feil" }, - "flow_title": "", + "flow_title": "Obligasjon: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Tilgangstoken" }, - "description": "Vil du konfigurere {bond_id}?" + "description": "Vil du konfigurere {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/pl.json b/homeassistant/components/bond/translations/pl.json index c50c270b74cd50..6f5f2d276ff391 100644 --- a/homeassistant/components/bond/translations/pl.json +++ b/homeassistant/components/bond/translations/pl.json @@ -9,13 +9,13 @@ "old_firmware": "Stare, nieobs\u0142ugiwane oprogramowanie na urz\u0105dzeniu Bond - zaktualizuj przed kontynuowaniem", "unknown": "Nieoczekiwany b\u0142\u0105d" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token dost\u0119pu" }, - "description": "Czy chcesz skonfigurowa\u0107 {bond_id}?" + "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/ru.json b/homeassistant/components/bond/translations/ru.json index e6c4067d8ac585..cdc37fc27f7021 100644 --- a/homeassistant/components/bond/translations/ru.json +++ b/homeassistant/components/bond/translations/ru.json @@ -9,13 +9,13 @@ "old_firmware": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430 \u0438 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0435\u0439.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, - "flow_title": "Bond {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" }, - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {bond_id}?" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/zh-Hant.json b/homeassistant/components/bond/translations/zh-Hant.json index af652c54509efa..1c5327dc6627ed 100644 --- a/homeassistant/components/bond/translations/zh-Hant.json +++ b/homeassistant/components/bond/translations/zh-Hant.json @@ -9,13 +9,13 @@ "old_firmware": "Bond \u88dd\u7f6e\u4f7f\u7528\u4e0d\u652f\u63f4\u7684\u820a\u7248\u672c\u97cc\u9ad4 - \u8acb\u66f4\u65b0\u5f8c\u518d\u7e7c\u7e8c", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, - "flow_title": "Bond\uff1a{bond_id} ({host})", + "flow_title": "Bond\uff1a{name} ({host})", "step": { "confirm": { "data": { "access_token": "\u5b58\u53d6\u5bc6\u9470" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {bond_id}\uff1f" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 2f3a7313f75c78..7f85335d7bb375 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kon niet verbinden", "invalid_host": "Ongeldige hostnaam of IP-adres", "not_supported": "Apparaat wordt niet ondersteund", diff --git a/homeassistant/components/climacell/translations/af.json b/homeassistant/components/climacell/translations/af.json new file mode 100644 index 00000000000000..b62fc7023a47b1 --- /dev/null +++ b/homeassistant/components/climacell/translations/af.json @@ -0,0 +1,10 @@ +{ + "options": { + "step": { + "init": { + "title": "Update ClimaCell opties" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ca.json b/homeassistant/components/climacell/translations/ca.json new file mode 100644 index 00000000000000..23afb6a3d9057c --- /dev/null +++ b/homeassistant/components/climacell/translations/ca.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_api_key": "Clau API inv\u00e0lida", + "rate_limited": "Freq\u00fc\u00e8ncia limitada temporalment, torna-ho a provar m\u00e9s tard.", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "api_key": "Clau API", + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nom" + }, + "description": "Si no es proporcionen la Latitud i Longitud, s'utilitzaran els valors per defecte de la configuraci\u00f3 de Home Assistant. Es crear\u00e0 una entitat per a cada tipus de previsi\u00f3, per\u00f2 nom\u00e9s s'habilitaran les que seleccionis." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipus de previsi\u00f3", + "timestep": "Minuts entre previsions NowCast" + }, + "description": "Si decideixes activar l'entitat de predicci\u00f3 \"nowcast\", podr\u00e0s configurar l'interval en minuts entre cada previsi\u00f3. El nombre de previsions proporcionades dep\u00e8n d'aquest interval de minuts.", + "title": "Actualitzaci\u00f3 de les opcions de ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/en.json b/homeassistant/components/climacell/translations/en.json new file mode 100644 index 00000000000000..ed3ead421e1e5b --- /dev/null +++ b/homeassistant/components/climacell/translations/en.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Failed to connect", + "invalid_api_key": "Invalid API key", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Name" + }, + "description": "If Latitude and Longitude are not provided, the default values in the Home Assistant configuration will be used. An entity will be created for each forecast type but only the ones you select will be enabled by default." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Forecast Type(s)", + "timestep": "Min. Between NowCast Forecasts" + }, + "description": "If you choose to enable the `nowcast` forecast entity, you can configure the number of minutes between each forecast. The number of forecasts provided depends on the number of minutes chosen between forecasts.", + "title": "Update ClimaCell Options" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/et.json b/homeassistant/components/climacell/translations/et.json new file mode 100644 index 00000000000000..3722c258afaa46 --- /dev/null +++ b/homeassistant/components/climacell/translations/et.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_api_key": "Vale API v\u00f5ti", + "rate_limited": "Hetkel on p\u00e4ringud piiratud, proovi hiljem uuesti.", + "unknown": "Tundmatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "name": "Nimi" + }, + "description": "Kui [%key:component::climacell::config::step::user::d ata::latitude%] ja [%key:component::climacell::config::step::user::d ata::longitude%] andmed pole sisestatud kasutatakse Home Assistanti vaikev\u00e4\u00e4rtusi. Olem luuakse iga prognoosit\u00fc\u00fcbi jaoks kuid vaikimisi lubatakse ainult need, mille valid." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Prognoosi t\u00fc\u00fcp (t\u00fc\u00fcbid)", + "timestep": "Minuteid NowCasti prognooside vahel" + }, + "description": "Kui otsustad lubada \"nowcast\" prognoosi\u00fcksuse, saad seadistada minutite arvu iga prognoosi vahel. Esitatavate prognooside arv s\u00f5ltub prognooside vahel valitud minutite arvust.", + "title": "V\u00e4rskenda ClimaCell suvandeid" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/fr.json b/homeassistant/components/climacell/translations/fr.json new file mode 100644 index 00000000000000..8fd3f7b71221a2 --- /dev/null +++ b/homeassistant/components/climacell/translations/fr.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_api_key": "Cl\u00e9 API invalide", + "rate_limited": "Currently rate limited, please try again later.", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom" + }, + "description": "Si Latitude et Longitude ne sont pas fournis, les valeurs par d\u00e9faut de la configuration de Home Assistant seront utilis\u00e9es. Une entit\u00e9 sera cr\u00e9\u00e9e pour chaque type de pr\u00e9vision, mais seules celles que vous s\u00e9lectionnez seront activ\u00e9es par d\u00e9faut." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Type(s) de pr\u00e9vision", + "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": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/nl.json b/homeassistant/components/climacell/translations/nl.json new file mode 100644 index 00000000000000..488a43ae24ecd2 --- /dev/null +++ b/homeassistant/components/climacell/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_api_key": "Ongeldige API-sleutel", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "name": "Naam" + }, + "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype maar alleen degene die u selecteert worden standaard ingeschakeld." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Voorspellingstype(n)" + }, + "description": "Als u ervoor kiest om de `nowcast` voorspellingsentiteit in te schakelen, kan u het aantal minuten tussen elke voorspelling configureren. Het aantal voorspellingen hangt af van het aantal gekozen minuten tussen de voorspellingen.", + "title": "Update ClimaCell Opties" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json new file mode 100644 index 00000000000000..64845ff76978c0 --- /dev/null +++ b/homeassistant/components/climacell/translations/no.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "rate_limited": "Prisen er for \u00f8yeblikket begrenset. Pr\u00f8v igjen senere.", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "name": "Navn" + }, + "description": "Hvis Breddegrad and Lengdegrad er ikke gitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Prognosetype(r)", + "timestep": "Min. Mellom NowCast Prognoser" + }, + "description": "Hvis du velger \u00e5 aktivere \u00abnowcast\u00bb -varselenheten, kan du konfigurere antall minutter mellom hver prognose. Antall angitte prognoser avhenger av antall minutter som er valgt mellom prognosene.", + "title": "Oppdater ClimaCell Alternativer" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ru.json b/homeassistant/components/climacell/translations/ru.json new file mode 100644 index 00000000000000..2cce63d95ea03f --- /dev/null +++ b/homeassistant/components/climacell/translations/ru.json @@ -0,0 +1,34 @@ +{ + "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.", + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "rate_limited": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a, \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." + }, + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0415\u0441\u043b\u0438 \u0428\u0438\u0440\u043e\u0442\u0430 \u0438 \u0414\u043e\u043b\u0433\u043e\u0442\u0430 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u044b, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 Home Assistant. \u041e\u0431\u044a\u0435\u043a\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u044b \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430, \u043d\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0443\u0434\u0443\u0442 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0435 \u0412\u0430\u043c\u0438." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u0422\u0438\u043f(\u044b) \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430", + "timestep": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + }, + "description": "\u0415\u0441\u043b\u0438 \u0412\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 'nowcast', \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430.", + "title": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index 69d52436beb986..e4cf54eb365ff9 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -5,7 +5,9 @@ "cannot_connect": "Kon niet verbinden" }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" }, "step": { "user": { diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json new file mode 100644 index 00000000000000..79aaec23123a61 --- /dev/null +++ b/homeassistant/components/enocean/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/en.json b/homeassistant/components/faa_delays/translations/en.json index 48e9e1c8993cf2..e78b15c68cb186 100644 --- a/homeassistant/components/faa_delays/translations/en.json +++ b/homeassistant/components/faa_delays/translations/en.json @@ -4,16 +4,18 @@ "already_configured": "This airport is already configured." }, "error": { - "invalid_airport": "Airport code is not valid" + "cannot_connect": "Failed to connect", + "invalid_airport": "Airport code is not valid", + "unknown": "Unexpected error" }, "step": { "user": { - "title": "FAA Delays", - "description": "Enter a US Airport Code in IATA Format", "data": { "id": "Airport" - } + }, + "description": "Enter a US Airport Code in IATA Format", + "title": "FAA Delays" } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/fr.json b/homeassistant/components/faa_delays/translations/fr.json new file mode 100644 index 00000000000000..996a22c842209c --- /dev/null +++ b/homeassistant/components/faa_delays/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Cet a\u00e9roport est d\u00e9j\u00e0 configur\u00e9." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_airport": "Le code de l'a\u00e9roport n'est pas valide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "id": "A\u00e9roport" + }, + "description": "Entrez un code d'a\u00e9roport am\u00e9ricain au format IATA", + "title": "D\u00e9lais FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/nl.json b/homeassistant/components/faa_delays/translations/nl.json new file mode 100644 index 00000000000000..3dbc55f5b1b863 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Deze luchthaven is al geconfigureerd." + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_airport": "Luchthavencode is ongeldig", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "id": "Luchthaven" + }, + "description": "Voer een Amerikaanse luchthavencode in IATA-indeling in", + "title": "FAA-vertragingen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/nl.json b/homeassistant/components/fireservicerota/translations/nl.json index 7289d53e71fb0f..3a6ba936dee1d1 100644 --- a/homeassistant/components/fireservicerota/translations/nl.json +++ b/homeassistant/components/fireservicerota/translations/nl.json @@ -14,7 +14,8 @@ "reauth": { "data": { "password": "Wachtwoord" - } + }, + "description": "Authenticatietokens zijn ongeldig geworden, log in om ze opnieuw te maken." }, "user": { "data": { diff --git a/homeassistant/components/fireservicerota/translations/no.json b/homeassistant/components/fireservicerota/translations/no.json index af1ceba2c97e90..be485577e65aea 100644 --- a/homeassistant/components/fireservicerota/translations/no.json +++ b/homeassistant/components/fireservicerota/translations/no.json @@ -15,7 +15,7 @@ "data": { "password": "Passord" }, - "description": "Godkjenningstokener ble ugyldige, logg inn for \u00e5 gjenopprette dem" + "description": "Autentiseringstokener ble ugyldige, logg inn for \u00e5 gjenskape dem." }, "user": { "data": { diff --git a/homeassistant/components/firmata/translations/nl.json b/homeassistant/components/firmata/translations/nl.json new file mode 100644 index 00000000000000..7cb0141826a8fb --- /dev/null +++ b/homeassistant/components/firmata/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Kan geen verbinding maken" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index c63a59e18e72bf..0ff044abc5e578 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." }, + "error": { + "unknown": "Onverwachte fout" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 71a80dbd577c46..9bfe2ef6be6ed3 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", + "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", "reauth_successful": "Herauthenticatie was succesvol" }, diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index 86958670d70af5..4d9b5a397ddd63 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "Host", "name": "Naam" }, "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router." diff --git a/homeassistant/components/home_connect/translations/nl.json b/homeassistant/components/home_connect/translations/nl.json index 41b27cc387fdca..25a812096074dd 100644 --- a/homeassistant/components/home_connect/translations/nl.json +++ b/homeassistant/components/home_connect/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie." + "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geverifieerd" diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index 0870b05a6d1665..dbd83622d8a7ab 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -19,7 +19,7 @@ "title": "Selecciona els dominis a incloure" }, "pairing": { - "description": "Tan aviat com {name} estigui llest, la vinculaci\u00f3 estar\u00e0 disponible a \"Notificacions\" com a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\".", + "description": "Per completar la vinculaci\u00f3, segueix les instruccions a \"Configuraci\u00f3 de l'enlla\u00e7 HomeKit\" sota \"Notificacions\".", "title": "Vinculaci\u00f3 HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Dominis a incloure", "mode": "Mode" }, - "description": "La integraci\u00f3 HomeKit et permetr\u00e0 l'acc\u00e9s a les teves entitats de Home Assistant a HomeKit. En mode enlla\u00e7, els enlla\u00e7os HomeKit estan limitats a un m\u00e0xim de 150 accessoris per inst\u00e0ncia (incl\u00f2s el propi enlla\u00e7). Si volguessis enlla\u00e7ar m\u00e9s accessoris que el m\u00e0xim perm\u00e8s, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat nom\u00e9s est\u00e0 disponible en YAML. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", - "title": "Activaci\u00f3 de HomeKit" + "description": "Selecciona els dominis a incloure. S'inclouran totes les entitats del domini compatibles. Es crear\u00e0 una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "title": "Selecciona els dominis a incloure" } } }, @@ -55,7 +55,7 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment i evitar errors de disponibilitat inesperats , crea i vincula una inst\u00e0ncia HomeKit en mode accessori per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Selecciona les entitats a incloure" }, "init": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index e9aaeb60df8730..3b0129567c4ee3 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,13 +4,29 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entity" + }, + "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", + "title": "Select entity to be included" + }, + "bridge_mode": { + "data": { + "include_domains": "Domains to include" + }, + "description": "Choose the domains to be included. All supported entities in the domain will be included.", + "title": "Select domains to be included" + }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "include_domains": "Domains to include" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", + "mode": "Mode" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -21,7 +37,8 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 37bff5f9b70e2c..8c24d2d225180f 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -19,7 +19,7 @@ "title": "Vali kaasatavad domeenid" }, "pairing": { - "description": "Niipea kui {name} on valmis, on sidumine saadaval jaotises \"Notifications\" kui \"HomeKit Bridge Setup\".", + "description": "Sidumise l\u00f5puleviimiseks j\u00e4rgi jaotises \"HomeKiti sidumine\" toodud juhiseid alajaotises \"Teatised\".", "title": "HomeKiti sidumine" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Kaasatavad domeenid", "mode": "Re\u017eiim" }, - "description": "HomeKiti integreerimine v\u00f5imaldab teil p\u00e4\u00e4seda juurde HomeKiti \u00fcksustele Home Assistant. Sildire\u017eiimis on HomeKit Bridges piiratud 150 lisaseadmega, sealhulgas sild ise. Kui soovid \u00fchendada rohkem lisatarvikuid, on soovitatav kasutada erinevate domeenide jaoks mitut HomeKiti silda. \u00dcksuse \u00fcksikasjalik konfiguratsioon on esmase silla jaoks saadaval ainult YAML-i kaudu. Parema tulemuse saavutamiseks ja ootamatute seadmete kadumise v\u00e4ltimiseks loo ja seo eraldi HomeKiti seade tarviku re\u017eiimis kga meediaesitaja ja kaamera jaoks.", - "title": "Aktiveeri HomeKit" + "description": "Vali kaasatavad domeenid. Kaasatakse k\u00f5ik domeenis toetatud olemid. Iga telemeedia pleieri ja kaamera jaoks luuakse eraldi HomeKiti eksemplar tarvikure\u017eiimis.", + "title": "Vali kaasatavad domeenid" } } }, @@ -55,12 +55,12 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid.", + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", "title": "Vali kaasatavd olemid" }, "init": { "data": { - "include_domains": "Kaasatavad domeenid", + "include_domains": "Kaasatud domeenid", "mode": "Re\u017eiim" }, "description": "HomeKiti saab seadistada silla v\u00f5i \u00fche lisaseadme avaldamiseks. Lisare\u017eiimis saab kasutada ainult \u00fchte \u00fcksust. Teleriseadmete klassiga meediumipleierite n\u00f5uetekohaseks toimimiseks on vaja lisare\u017eiimi. \u201eKaasatavate domeenide\u201d \u00fcksused puutuvad kokku HomeKitiga. J\u00e4rgmisel ekraanil saad valida, millised \u00fcksused sellesse loendisse lisada v\u00f5i sellest v\u00e4lja j\u00e4tta.", diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index a0f10c9684a5a0..4721514e61520e 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -19,7 +19,7 @@ "title": "S\u00e9lectionnez les domaines \u00e0 inclure" }, "pairing": { - "description": "D\u00e8s que le pont {name} est pr\u00eat, l'appairage sera disponible dans \"Notifications\" sous \"Configuration de la Passerelle HomeKit\".", + "description": "Pour compl\u00e9ter l'appariement, suivez les instructions dans les \"Notifications\" sous \"Appariement HomeKit\".", "title": "Appairage de la Passerelle Homekit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domaines \u00e0 inclure", "mode": "Mode" }, - "description": "La passerelle HomeKit vous permettra d'acc\u00e9der \u00e0 vos entit\u00e9s Home Assistant dans HomeKit. Les passerelles HomeKit sont limit\u00e9es \u00e0 150 accessoires par instance, y compris la passerelle elle-m\u00eame. Si vous souhaitez connecter plus que le nombre maximum d'accessoires, il est recommand\u00e9 d'utiliser plusieurs passerelles HomeKit pour diff\u00e9rents domaines. La configuration d\u00e9taill\u00e9e des entit\u00e9s est uniquement disponible via YAML pour la passerelle principale.", - "title": "Activer la Passerelle HomeKit" + "description": "Choisissez les domaines \u00e0 inclure. Toutes les entit\u00e9s prises en charge dans le domaine seront incluses. Une instance HomeKit distincte en mode accessoire sera cr\u00e9\u00e9e pour chaque lecteur multim\u00e9dia TV et cam\u00e9ra.", + "title": "S\u00e9lectionnez les domaines \u00e0 inclure" } } }, @@ -60,7 +60,7 @@ }, "init": { "data": { - "include_domains": "Domaine \u00e0 inclure", + "include_domains": "Domaines \u00e0 inclure", "mode": "Mode" }, "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.", diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 9a64def41569d2..4748fe63af204e 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -19,7 +19,7 @@ "title": "Velg domener som skal inkluderes" }, "pairing": { - "description": "S\u00e5 snart {name} er klart, vil sammenkobling v\u00e6re tilgjengelig i \"Notifications\" som \"HomeKit Bridge Setup\".", + "description": "For \u00e5 fullf\u00f8re sammenkoblingen ved \u00e5 f\u00f8lge instruksjonene i \"Varsler\" under \"Sammenkobling av HomeKit\".", "title": "Koble sammen HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domener \u00e5 inkludere", "mode": "Modus" }, - "description": "HomeKit-integrasjonen gir deg tilgang til Home Assistant-enhetene dine i HomeKit. I bromodus er HomeKit Bridges begrenset til 150 tilbeh\u00f8r per forekomst inkludert selve broen. Hvis du \u00f8nsker \u00e5 bygge bro over maksimalt antall tilbeh\u00f8r, anbefales det at du bruker flere HomeKit-broer for forskjellige domener. Detaljert enhetskonfigurasjon er bare tilgjengelig via YAML. For best ytelse og for \u00e5 forhindre uventet utilgjengelighet, opprett og par sammen en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", - "title": "Aktiver HomeKit" + "description": "Velg domenene som skal inkluderes. Alle st\u00f8ttede enheter i domenet vil bli inkludert. Det opprettes en egen HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediaspiller og kamera.", + "title": "Velg domener som skal inkluderes" } } }, @@ -55,7 +55,7 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare \u00e9n enkelt enhet inkludert. I bridge include-modus inkluderes alle enheter i domenet med mindre bestemte enheter er valgt. I brounnlatingsmodus inkluderes alle enheter i domenet, med unntak av de utelatte enhetene. For best mulig ytelse, og for \u00e5 forhindre uventet utilgjengelighet, opprett og par en separat HomeKit-forekomst i tilbeh\u00f8rsmodus for hver tv-mediespiller og kamera.", + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse vil et eget HomeKit-tilbeh\u00f8r v\u00e6re TV-mediaspiller og kamera.", "title": "Velg enheter som skal inkluderes" }, "init": { diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json index 2679a4de20afb1..ef35ff667c4107 100644 --- a/homeassistant/components/homekit/translations/pl.json +++ b/homeassistant/components/homekit/translations/pl.json @@ -19,7 +19,7 @@ "title": "Wybierz uwzgl\u0119dniane domeny" }, "pairing": { - "description": "Gdy tylko {name} b\u0119dzie gotowy, opcja parowania b\u0119dzie dost\u0119pna w \u201ePowiadomieniach\u201d jako \u201eKonfiguracja mostka HomeKit\u201d.", + "description": "Aby doko\u0144czy\u0107 parowanie, post\u0119puj wg instrukcji \u201eParowanie HomeKit\u201d w \u201ePowiadomieniach\u201d.", "title": "Parowanie z HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domeny do uwzgl\u0119dnienia", "mode": "Tryb" }, - "description": "Integracja HomeKit pozwala na dost\u0119p do Twoich encji Home Assistant w HomeKit. W trybie \"Mostka\", mostki HomeKit s\u0105 ograniczone do 150 urz\u0105dze\u0144, w\u0142\u0105czaj\u0105c w to sam mostek. Je\u015bli chcesz wi\u0119cej ni\u017c dozwolona maksymalna liczba urz\u0105dze\u0144, zaleca si\u0119 u\u017cywanie wielu most\u00f3w HomeKit dla r\u00f3\u017cnych domen. Szczeg\u00f3\u0142owa konfiguracja encji jest dost\u0119pna tylko w trybie YAML dla g\u0142\u00f3wnego mostka. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", - "title": "Aktywacja HomeKit" + "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 oraz kamery.", + "title": "Wybierz uwzgl\u0119dniane domeny" } } }, @@ -55,7 +55,7 @@ "entities": "Encje", "mode": "Tryb" }, - "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci oraz by zapobiec nieprzewidzianej niedost\u0119pno\u015bci urz\u0105dzenia, utw\u00f3rz i sparuj oddzieln\u0105 instancj\u0119 HomeKit w trybie akcesorium dla ka\u017cdego media playera oraz kamery.", + "description": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione. W trybie \"Akcesorium\" tylko jedna encja jest uwzgl\u0119dniona. W trybie \"Uwzgl\u0119dnij mostek\", wszystkie encje w danej domenie b\u0119d\u0105 uwzgl\u0119dnione, chyba \u017ce wybrane s\u0105 tylko konkretne encje. W trybie \"Wyklucz mostek\", wszystkie encje b\u0119d\u0105 uwzgl\u0119dnione, z wyj\u0105tkiem tych wybranych. Dla najlepszej wydajno\u015bci, zostanie utworzone oddzielne akcesorium HomeKit dla ka\u017cdego tv media playera oraz kamery.", "title": "Wybierz encje, kt\u00f3re maj\u0105 by\u0107 uwzgl\u0119dnione" }, "init": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index 84346aed2ef66c..d00744e4cb4419 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -19,7 +19,7 @@ "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "pairing": { - "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u043e, \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 \"\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u0445\" \u043a\u0430\u043a \"HomeKit Bridge Setup\".", + "description": "\u0414\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f \u0441\u043b\u0435\u0434\u0443\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0432 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0438 \"HomeKit Pairing\".", "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", - "title": "HomeKit" + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u043c\u0435\u043d\u044b. \u0411\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438\u0437 \u0434\u043e\u043c\u0435\u043d\u0430. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430.", + "title": "\u0412\u044b\u0431\u043e\u0440 \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" } } }, @@ -55,7 +55,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043d\u0435\u0438\u0441\u043f\u0440\u0430\u0432\u043d\u043e\u0441\u0442\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json index 605263c4489728..95a0782cf1283f 100644 --- a/homeassistant/components/homekit/translations/zh-Hant.json +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -19,7 +19,7 @@ "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" }, "pairing": { - "description": "\u65bc {name} \u5c31\u7dd2\u5f8c\u3001\u5c07\u6703\u65bc\u300c\u901a\u77e5\u300d\u4e2d\u986f\u793a\u300cHomeKit Bridge \u8a2d\u5b9a\u300d\u7684\u914d\u5c0d\u8cc7\u8a0a\u3002", + "description": "\u6b32\u5b8c\u6210\u914d\u5c0d\u3001\u8acb\u8ddf\u96a8\u300c\u901a\u77e5\u300d\u5167\u7684\u300cHomekit \u914d\u5c0d\u300d\u6307\u5f15\u3002", "title": "\u914d\u5c0d HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "\u5305\u542b\u7db2\u57df", "mode": "\u6a21\u5f0f" }, - "description": "HomeKit \u6574\u5408\u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6a21\u5f0f\u4e0b\u3001HomeKit Bridges \u6700\u9ad8\u9650\u5236\u70ba 150 \u500b\u914d\u4ef6\u3001\u5305\u542b Bridge \u672c\u8eab\u3002\u5047\u5982\u60f3\u8981\u4f7f\u7528\u8d85\u904e\u9650\u5236\u4ee5\u4e0a\u7684\u914d\u4ef6\uff0c\u5efa\u8b70\u53ef\u4ee5\u4e0d\u540c\u7db2\u57df\u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", - "title": "\u555f\u7528 HomeKit" + "description": "\u9078\u64c7\u6240\u8981\u5305\u542b\u7684\u7db2\u57df\uff0c\u6240\u6709\u8a72\u7db2\u57df\u5167\u652f\u63f4\u7684\u5be6\u9ad4\u90fd\u5c07\u6703\u88ab\u5305\u542b\u3002 \u5176\u4ed6 Homekit \u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\u5be6\u4f8b\uff0c\u5c07\u6703\u4ee5\u914d\u4ef6\u6a21\u5f0f\u65b0\u589e\u3002", + "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u7db2\u57df" } } }, @@ -55,7 +55,7 @@ "entities": "\u5be6\u9ad4", "mode": "\u6a21\u5f0f" }, - "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u4e26\u907f\u514d\u672a\u9810\u671f\u7121\u6cd5\u4f7f\u7528\u72c0\u614b\uff0c\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u8acb\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u4e2d\u5206\u5225\u9032\u884c\u914d\u5c0d\u3002", + "description": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4\u3002\u65bc\u914d\u4ef6\u6a21\u5f0f\u4e0b\u3001\u50c5\u6709\u55ae\u4e00\u5be6\u9ad4\u5c07\u6703\u5305\u542b\u3002\u65bc\u6a4b\u63a5\u5305\u542b\u6a21\u5f0f\u4e0b\u3001\u6240\u6709\u7db2\u57df\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u975e\u9078\u64c7\u7279\u5b9a\u7684\u5be6\u9ad4\u3002\u65bc\u6a4b\u63a5\u6392\u9664\u6a21\u5f0f\u4e2d\u3001\u6240\u6709\u7db2\u57df\u4e2d\u7684\u5be6\u9ad4\u90fd\u5c07\u5305\u542b\uff0c\u9664\u4e86\u6392\u9664\u7684\u5be6\u9ad4\u3002\u70ba\u53d6\u5f97\u6700\u4f73\u6548\u80fd\u3001\u96fb\u8996\u5a92\u9ad4\u64ad\u653e\u5668\u8207\u651d\u5f71\u6a5f\uff0c\u5c07\u65bc Homekit \u914d\u4ef6\u6a21\u5f0f\u9032\u884c\u3002", "title": "\u9078\u64c7\u8981\u5305\u542b\u7684\u5be6\u9ad4" }, "init": { diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index dd51fdc1bc5edb..d420093996c768 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -19,6 +19,7 @@ "user": { "data": { "password": "Wachtwoord", + "url": "URL", "username": "Gebruikersnaam" }, "description": "Voer de toegangsgegevens van het apparaat in. Opgeven van gebruikersnaam en wachtwoord is optioneel, maar biedt ondersteuning voor meer integratiefuncties. Aan de andere kant kan het gebruik van een geautoriseerde verbinding problemen veroorzaken bij het openen van het webinterface van het apparaat buiten de Home Assitant, terwijl de integratie actief is en andersom.", diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index 97673069054f1d..b150c8d5b16fac 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Account reeds geconfigureerd", - "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd" + "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "invalid_auth": "Ongeldige authenticatie", @@ -14,7 +15,8 @@ "data": { "password": "Wachtwoord" }, - "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken." + "description": "Uw eerder ingevoerde wachtwoord voor {username} werkt niet meer. Update uw wachtwoord om deze integratie te blijven gebruiken.", + "title": "Verifieer de integratie opnieuw" }, "trusted_device": { "data": { diff --git a/homeassistant/components/ifttt/translations/nl.json b/homeassistant/components/ifttt/translations/nl.json index e7da47dd658b76..82006860db3adc 100644 --- a/homeassistant/components/ifttt/translations/nl.json +++ b/homeassistant/components/ifttt/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar de Home Assistant te verzenden, moet u de actie \"Een webverzoek doen\" gebruiken vanuit de [IFTTT Webhook-applet]({applet_url}). \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [the documentation]({docs_url}) voor informatie over het configureren van automatiseringen om inkomende gegevens te verwerken." diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index d2f73fca37bb74..e4f7d4a8102f70 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -28,6 +28,9 @@ "title": "Insteon Hub versie 2" }, "plm": { + "data": { + "device": "USB-apparaatpad" + }, "description": "Configureer de Insteon PowerLink Modem (PLM).", "title": "Insteon PLM" }, diff --git a/homeassistant/components/keenetic_ndms2/translations/nl.json b/homeassistant/components/keenetic_ndms2/translations/nl.json index f422e2641f6cbf..c3c08575052fe1 100644 --- a/homeassistant/components/keenetic_ndms2/translations/nl.json +++ b/homeassistant/components/keenetic_ndms2/translations/nl.json @@ -21,7 +21,8 @@ "step": { "user": { "data": { - "interfaces": "Kies interfaces om te scannen" + "interfaces": "Kies interfaces om te scannen", + "scan_interval": "Scaninterval" } } } diff --git a/homeassistant/components/kmtronic/translations/fr.json b/homeassistant/components/kmtronic/translations/fr.json new file mode 100644 index 00000000000000..45620fe7795250 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pl.json b/homeassistant/components/kmtronic/translations/pl.json new file mode 100644 index 00000000000000..25dab56796cc15 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/pl.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ca.json b/homeassistant/components/litejet/translations/ca.json new file mode 100644 index 00000000000000..39e2a56dc4dd18 --- /dev/null +++ b/homeassistant/components/litejet/translations/ca.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "error": { + "open_failed": "No s'ha pogut obrir el port s\u00e8rie especificat." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connecta el port RS232-2 LiteJet a l'ordinador i introdueix la ruta al port s\u00e8rie del dispositiu.\n\nEl LiteJet MCP ha d'estar configurat amb una velocitat de 19.2 k baudis, 8 bits de dades, 1 bit de parada, sense paritat i ha de transmetre un 'CR' despr\u00e9s de cada resposta.", + "title": "Connexi\u00f3 amb LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/fr.json b/homeassistant/components/litejet/translations/fr.json new file mode 100644 index 00000000000000..455ba7fdc0c569 --- /dev/null +++ b/homeassistant/components/litejet/translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Connectez le port RS232-2 du LiteJet \u00e0 votre ordinateur et entrez le chemin d'acc\u00e8s au p\u00e9riph\u00e9rique de port s\u00e9rie. \n\n Le LiteJet MCP doit \u00eatre configur\u00e9 pour 19,2 K bauds, 8 bits de donn\u00e9es, 1 bit d'arr\u00eat, sans parit\u00e9 et pour transmettre un \u00abCR\u00bb apr\u00e8s chaque r\u00e9ponse.", + "title": "Connectez-vous \u00e0 LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/no.json b/homeassistant/components/litejet/translations/no.json new file mode 100644 index 00000000000000..26ccd3335468b5 --- /dev/null +++ b/homeassistant/components/litejet/translations/no.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "open_failed": "Kan ikke \u00e5pne den angitte serielle porten." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Koble LiteJets RS232-2-port til datamaskinen og skriv stien til den serielle portenheten. \n\n LiteJet MCP m\u00e5 konfigureres for 19,2 K baud, 8 databiter, 1 stoppbit, ingen paritet, og for \u00e5 overf\u00f8re en 'CR' etter hvert svar.", + "title": "Koble til LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/pl.json b/homeassistant/components/litejet/translations/pl.json new file mode 100644 index 00000000000000..20e5d68288d89a --- /dev/null +++ b/homeassistant/components/litejet/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "open_failed": "Nie mo\u017cna otworzy\u0107 okre\u015blonego portu szeregowego." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Pod\u0142\u0105cz port RS232-2 LiteJet do komputera i wprowad\u017a \u015bcie\u017ck\u0119 do urz\u0105dzenia portu szeregowego. \n\nLiteJet MCP musi by\u0107 skonfigurowany dla szybko\u015bci 19,2K, 8 bit\u00f3w danych, 1 bit stopu, brak parzysto\u015bci i przesy\u0142anie \u201eCR\u201d po ka\u017cdej odpowiedzi.", + "title": "Po\u0142\u0105czenie z LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ru.json b/homeassistant/components/litejet/translations/ru.json new file mode 100644 index 00000000000000..c90e6956301436 --- /dev/null +++ b/homeassistant/components/litejet/translations/ru.json @@ -0,0 +1,19 @@ +{ + "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." + }, + "error": { + "open_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442." + }, + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u043f\u043e\u0440\u0442 RS232-2 LiteJet \u043a \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0443, \u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u0442\u0443. \n\nLiteJet MCP \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c 19,2 \u041a\u0431\u043e\u0434, 8 \u0431\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0445, 1 \u0441\u0442\u043e\u043f\u043e\u0432\u044b\u0439 \u0431\u0438\u0442, \u0431\u0435\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0447\u0435\u0442\u043d\u043e\u0441\u0442\u0438 \u0438 \u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0443 'CR' \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/tr.json b/homeassistant/components/litejet/translations/tr.json new file mode 100644 index 00000000000000..de4ea12cb6f360 --- /dev/null +++ b/homeassistant/components/litejet/translations/tr.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "LiteJet'e Ba\u011flan\u0131n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/zh-Hant.json b/homeassistant/components/litejet/translations/zh-Hant.json new file mode 100644 index 00000000000000..8a268f3db494b5 --- /dev/null +++ b/homeassistant/components/litejet/translations/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u88dd\u7f6e\u3002" + }, + "error": { + "open_failed": "\u7121\u6cd5\u958b\u555f\u6307\u5b9a\u7684\u5e8f\u5217\u57e0" + }, + "step": { + "user": { + "data": { + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u9023\u7dda\u81f3\u96fb\u8166\u4e0a\u7684 LiteJet RS232-2 \u57e0\uff0c\u4e26\u8f38\u5165\u5e8f\u5217\u57e0\u88dd\u7f6e\u7684\u8def\u5f91\u3002\n\nLiteJet MCP \u5fc5\u9808\u8a2d\u5b9a\u70ba\u901a\u8a0a\u901f\u7387 19.2 K baud\u30018 \u6578\u64da\u4f4d\u5143\u30011 \u505c\u6b62\u4f4d\u5143\u3001\u7121\u540c\u4f4d\u4f4d\u5143\u4e26\u65bc\u6bcf\u500b\u56de\u5fa9\u5f8c\u50b3\u9001 'CR'\u3002", + "title": "\u9023\u7dda\u81f3 LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/fr.json b/homeassistant/components/litterrobot/translations/fr.json new file mode 100644 index 00000000000000..aa84ec33d8cdad --- /dev/null +++ b/homeassistant/components/litterrobot/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/no.json b/homeassistant/components/litterrobot/translations/no.json new file mode 100644 index 00000000000000..4ea7b2401c30f5 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/pl.json b/homeassistant/components/litterrobot/translations/pl.json new file mode 100644 index 00000000000000..8a08a06c69904c --- /dev/null +++ b/homeassistant/components/litterrobot/translations/pl.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index e02378432abaa5..16cbbc77277af2 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om locaties naar Home Assistant te sturen, moet u de Webhook-functie instellen in de Locative app. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n\n Zie [de documentatie]({docs_url}) voor meer informatie." diff --git a/homeassistant/components/mailgun/translations/nl.json b/homeassistant/components/mailgun/translations/nl.json index 772a67c118e6df..dea33946af51f6 100644 --- a/homeassistant/components/mailgun/translations/nl.json +++ b/homeassistant/components/mailgun/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + "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." }, "create_entry": { "default": "Om evenementen naar Home Assistant te verzenden, moet u [Webhooks with Mailgun]({mailgun_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Methode: POST \n - Inhoudstype: application/json \n\n Zie [de documentatie]({docs_url}) voor informatie over het configureren van automatiseringen om binnenkomende gegevens te verwerken." diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index 86f1e656e519dd..3198bfb4192e5f 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -25,5 +25,6 @@ } } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ca.json b/homeassistant/components/mullvad/translations/ca.json new file mode 100644 index 00000000000000..f81781cbc0fa43 --- /dev/null +++ b/homeassistant/components/mullvad/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Vols configurar la integraci\u00f3 Mullvad VPN?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/en.json b/homeassistant/components/mullvad/translations/en.json index 45664554aed74f..fcfa89ef0829ee 100644 --- a/homeassistant/components/mullvad/translations/en.json +++ b/homeassistant/components/mullvad/translations/en.json @@ -5,10 +5,16 @@ }, "error": { "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "step": { "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Username" + }, "description": "Set up the Mullvad VPN integration?" } } diff --git a/homeassistant/components/mullvad/translations/et.json b/homeassistant/components/mullvad/translations/et.json new file mode 100644 index 00000000000000..671d18a2cd344c --- /dev/null +++ b/homeassistant/components/mullvad/translations/et.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendumine nurjus", + "invalid_auth": "Tuvastamise viga", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Kas seadistada Mullvad VPN sidumine?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/fr.json b/homeassistant/components/mullvad/translations/fr.json new file mode 100644 index 00000000000000..1a8b10de809cfd --- /dev/null +++ b/homeassistant/components/mullvad/translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Configurez l'int\u00e9gration VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/nl.json b/homeassistant/components/mullvad/translations/nl.json new file mode 100644 index 00000000000000..aa4d80ac71dd67 --- /dev/null +++ b/homeassistant/components/mullvad/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "De Mullvad VPN-integratie instellen?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/no.json b/homeassistant/components/mullvad/translations/no.json new file mode 100644 index 00000000000000..d33f26404452be --- /dev/null +++ b/homeassistant/components/mullvad/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Sette opp Mullvad VPN-integrasjon?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ru.json b/homeassistant/components/mullvad/translations/ru.json new file mode 100644 index 00000000000000..ff34530e4a9b48 --- /dev/null +++ b/homeassistant/components/mullvad/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "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", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Mullvad VPN." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/tr.json b/homeassistant/components/mullvad/translations/tr.json new file mode 100644 index 00000000000000..0f3ddabfc4f44b --- /dev/null +++ b/homeassistant/components/mullvad/translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 Ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/et.json b/homeassistant/components/netatmo/translations/et.json index 99e062b38422b6..8725eb48016a13 100644 --- a/homeassistant/components/netatmo/translations/et.json +++ b/homeassistant/components/netatmo/translations/et.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "eemal", + "hg": "k\u00fclmumiskaitse", + "schedule": "ajastus" + }, + "trigger_type": { + "alarm_started": "{entity_name} tuvastas h\u00e4ire", + "animal": "{entity_name} tuvastas looma", + "cancel_set_point": "{entity_name} on oma ajakava j\u00e4tkanud", + "human": "{entity_name} tuvastas inimese", + "movement": "{entity_name} tuvastas liikumise", + "outdoor": "{entity_name} tuvastas v\u00e4lise s\u00fcndmuse", + "person": "{entity_name} tuvastas isiku", + "person_away": "{entity_name} tuvastas inimese eemaldumise", + "set_point": "Sihttemperatuur {entity_name} on k\u00e4sitsi m\u00e4\u00e4ratud", + "therm_mode": "{entity_name} l\u00fclitus olekusse {subtype}.", + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse", + "vehicle": "{entity_name} tuvastas s\u00f5iduki" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/fr.json b/homeassistant/components/netatmo/translations/fr.json index fe8fc74d273473..6c294d467abb6c 100644 --- a/homeassistant/components/netatmo/translations/fr.json +++ b/homeassistant/components/netatmo/translations/fr.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "absent", + "hg": "garde-gel", + "schedule": "horaire" + }, + "trigger_type": { + "alarm_started": "{entity_name} a d\u00e9tect\u00e9 une alarme", + "animal": "{entity_name} a d\u00e9tect\u00e9 un animal", + "cancel_set_point": "{entity_name} a repris son programme", + "human": "{entity_name} a d\u00e9tect\u00e9 une personne", + "movement": "{entity_name} a d\u00e9tect\u00e9 un mouvement", + "outdoor": "{entity_name} a d\u00e9tect\u00e9 un \u00e9v\u00e9nement ext\u00e9rieur", + "person": "{entity_name} a d\u00e9tect\u00e9 une personne", + "person_away": "{entity_name} a d\u00e9tect\u00e9 qu\u2019une personne est partie", + "set_point": "Temp\u00e9rature cible {entity_name} d\u00e9finie manuellement", + "therm_mode": "{entity_name} est pass\u00e9 \u00e0 \"{subtype}\"", + "turned_off": "{entity_name} d\u00e9sactiv\u00e9", + "turned_on": "{entity_name} activ\u00e9", + "vehicle": "{entity_name} a d\u00e9tect\u00e9 un v\u00e9hicule" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index eab1d9741ad136..431f105df3d8c8 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "authorize_url_timeout": "Time-out genereren autorisatie-URL.", - "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie." + "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geauthenticeerd met Netatmo." @@ -13,6 +14,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "afwezig", + "hg": "vorstbescherming", + "schedule": "schema" + }, + "trigger_type": { + "alarm_started": "{entity_name} heeft een alarm gedetecteerd", + "animal": "{entity_name} heeft een dier gedetecteerd", + "cancel_set_point": "{entity_name} heeft zijn schema hervat", + "human": "{entity_name} heeft een mens gedetecteerd", + "movement": "{entity_name} heeft beweging gedetecteerd", + "outdoor": "{entity_name} heeft een buitengebeurtenis gedetecteerd", + "person": "{entity_name} heeft een persoon gedetecteerd", + "person_away": "{entity_name} heeft gedetecteerd dat een persoon is vertrokken", + "set_point": "Doeltemperatuur {entity_name} handmatig ingesteld", + "therm_mode": "{entity_name} is overgeschakeld naar \"{subtype}\"", + "turned_off": "{entity_name} uitgeschakeld", + "turned_on": "{entity_name} ingeschakeld", + "vehicle": "{entity_name} heeft een voertuig gedetecteerd" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/ru.json b/homeassistant/components/netatmo/translations/ru.json index c9be7e60825004..b25e0843967487 100644 --- a/homeassistant/components/netatmo/translations/ru.json +++ b/homeassistant/components/netatmo/translations/ru.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\u043d\u0435 \u0434\u043e\u043c\u0430", + "hg": "\u0437\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u0437\u0430\u043c\u0435\u0440\u0437\u0430\u043d\u0438\u044f", + "schedule": "\u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0440\u0435\u0432\u043e\u0433\u0443", + "animal": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0436\u0438\u0432\u043e\u0442\u043d\u043e\u0435", + "cancel_set_point": "{entity_name} \u0432\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0441\u0432\u043e\u0435 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435", + "human": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430", + "movement": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435", + "outdoor": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u043e\u0437\u0434\u0443\u0445\u0435", + "person": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0441\u043e\u043d\u0443", + "person_away": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430 \u0443\u0448\u043b\u0430", + "set_point": "\u0426\u0435\u043b\u0435\u0432\u0430\u044f \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 {entity_name} \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430 \u0432\u0440\u0443\u0447\u043d\u0443\u044e", + "therm_mode": "{entity_name} \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \"{subtype}\"", + "turned_off": "{entity_name} \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "turned_on": "{entity_name} \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f", + "vehicle": "{entity_name} \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u043e\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/tr.json b/homeassistant/components/netatmo/translations/tr.json index 94dd5b3fb0f4c6..69646be2292b1b 100644 --- a/homeassistant/components/netatmo/translations/tr.json +++ b/homeassistant/components/netatmo/translations/tr.json @@ -4,6 +4,22 @@ "single_instance_allowed": "Zaten yap\u0131land\u0131r\u0131lm\u0131\u015f. Yaln\u0131zca tek bir konfig\u00fcrasyon m\u00fcmk\u00fcnd\u00fcr." } }, + "device_automation": { + "trigger_subtype": { + "away": "uzakta", + "hg": "donma korumas\u0131", + "schedule": "Zamanlama" + }, + "trigger_type": { + "alarm_started": "{entity_name} bir alarm alg\u0131lad\u0131", + "animal": "{entity_name} bir hayvan tespit etti", + "cancel_set_point": "{entity_name} zamanlamas\u0131na devam etti", + "human": "{entity_name} bir insan alg\u0131lad\u0131", + "movement": "{entity_name} hareket alg\u0131lad\u0131", + "turned_off": "{entity_name} kapat\u0131ld\u0131", + "turned_on": "{entity_name} a\u00e7\u0131ld\u0131" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/nightscout/translations/nl.json b/homeassistant/components/nightscout/translations/nl.json index 208299fd4427ff..0146996dce586d 100644 --- a/homeassistant/components/nightscout/translations/nl.json +++ b/homeassistant/components/nightscout/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/nzbget/translations/nl.json b/homeassistant/components/nzbget/translations/nl.json index f5f1bfd39ed9f1..89d58d14292ae5 100644 --- a/homeassistant/components/nzbget/translations/nl.json +++ b/homeassistant/components/nzbget/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "host": "Host", "name": "Naam", "password": "Wachtwoord", "port": "Poort", diff --git a/homeassistant/components/plum_lightpad/translations/nl.json b/homeassistant/components/plum_lightpad/translations/nl.json index 7f0f85b7326aa7..8410cabbbb92c1 100644 --- a/homeassistant/components/plum_lightpad/translations/nl.json +++ b/homeassistant/components/plum_lightpad/translations/nl.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "E-mail" } } } diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 46fc915d7bd4a6..38ef34d5afc9c8 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -11,7 +11,8 @@ "data": { "email": "E-mail", "password": "Wachtwoord" - } + }, + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/fr.json b/homeassistant/components/rituals_perfume_genie/translations/fr.json new file mode 100644 index 00000000000000..2a1fb9c8bb8a89 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Mot de passe" + }, + "title": "Connectez-vous \u00e0 votre compte Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json index 72f9ff82ba4c9f..8c15279dca8c8f 100644 --- a/homeassistant/components/rpi_power/translations/nl.json +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -2,6 +2,11 @@ "config": { "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." + }, + "step": { + "confirm": { + "description": "Wil je beginnen met instellen?" + } } }, "title": "Raspberry Pi Voeding Checker" diff --git a/homeassistant/components/ruckus_unleashed/translations/nl.json b/homeassistant/components/ruckus_unleashed/translations/nl.json index 0569c39321a24b..8ad15260b0de5c 100644 --- a/homeassistant/components/ruckus_unleashed/translations/nl.json +++ b/homeassistant/components/ruckus_unleashed/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/sharkiq/translations/nl.json b/homeassistant/components/sharkiq/translations/nl.json index 96c10f3e2f0812..3acfdbdf0747cd 100644 --- a/homeassistant/components/sharkiq/translations/nl.json +++ b/homeassistant/components/sharkiq/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Account is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", + "reauth_successful": "Herauthenticatie was succesvol", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index ebcc16dafacfbc..10a4fe2efab23e 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "cannot_connect": "Kan geen verbinding maken", "invalid_mdns": "Niet-ondersteund apparaat voor de Smappee-integratie.", - "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "step": { "local": { diff --git a/homeassistant/components/sms/translations/nl.json b/homeassistant/components/sms/translations/nl.json index 75dd593982a24e..ddcc54d239f50b 100644 --- a/homeassistant/components/sms/translations/nl.json +++ b/homeassistant/components/sms/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { "cannot_connect": "Kon niet verbinden", diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index 423dbb6a2bb8e9..b7f077f2c7342f 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "missing_configuration": "Het Somfy-component is niet geconfigureerd. Gelieve de documentatie te volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { diff --git a/homeassistant/components/speedtestdotnet/translations/nl.json b/homeassistant/components/speedtestdotnet/translations/nl.json index 0c0c184b5fe582..1fe99195f7a0a0 100644 --- a/homeassistant/components/speedtestdotnet/translations/nl.json +++ b/homeassistant/components/speedtestdotnet/translations/nl.json @@ -3,6 +3,11 @@ "abort": { "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk.", "wrong_server_id": "Server-ID is niet geldig" + }, + "step": { + "user": { + "description": "Wil je beginnen met instellen?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json index f0b4ddf59a9ea8..bc7683ac0a4165 100644 --- a/homeassistant/components/spider/translations/nl.json +++ b/homeassistant/components/spider/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json new file mode 100644 index 00000000000000..a6bf6902aabd11 --- /dev/null +++ b/homeassistant/components/subaru/translations/fr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "\u00c9chec de connexion" + }, + "error": { + "bad_pin_format": "Le code PIN doit \u00eatre compos\u00e9 de 4 chiffres", + "cannot_connect": "\u00c9chec de connexion", + "incorrect_pin": "PIN incorrect", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Veuillez entrer votre NIP MySubaru\nREMARQUE : Tous les v\u00e9hicules en compte doivent avoir le m\u00eame NIP", + "title": "Configuration de Subaru Starlink" + }, + "user": { + "data": { + "country": "Choisissez le pays", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index 349b4b2818e69c..b1beb4058bc9d3 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -10,7 +10,8 @@ "step": { "user": { "data": { - "name": "Naam" + "name": "Naam", + "url": "Webinterface URL" } } } diff --git a/homeassistant/components/tile/translations/nl.json b/homeassistant/components/tile/translations/nl.json index c160ac631ee0dd..236d250122a689 100644 --- a/homeassistant/components/tile/translations/nl.json +++ b/homeassistant/components/tile/translations/nl.json @@ -9,7 +9,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "E-mail" }, "title": "Tegel configureren" } diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index cf77b94d025e88..a0cda915172de3 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -3,6 +3,8 @@ "abort": { "already_configured": "De geselecteerde overeenkomst is al geconfigureerd.", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie-URL.", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_agreements": "Dit account heeft geen Toon schermen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index 6526d0a98001c4..40ca767b4acbec 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "invalid_auth": "Authentification invalide" }, "step": { + "locations": { + "data": { + "location": "Emplacement" + } + }, + "reauth_confirm": { + "title": "R\u00e9-authentifier l'int\u00e9gration" + }, "user": { "data": { "password": "Mot de passe", diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 1f4fb5490d1408..94d8e3ac01ee9a 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -8,6 +8,11 @@ "invalid_auth": "Ongeldige authenticatie" }, "step": { + "locations": { + "data": { + "location": "Locatie" + } + }, "reauth_confirm": { "title": "Verifieer de integratie opnieuw" }, diff --git a/homeassistant/components/totalconnect/translations/no.json b/homeassistant/components/totalconnect/translations/no.json index e80f86696fcd3f..9c98d6ad1e7155 100644 --- a/homeassistant/components/totalconnect/translations/no.json +++ b/homeassistant/components/totalconnect/translations/no.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" }, "error": { - "invalid_auth": "Ugyldig godkjenning" + "invalid_auth": "Ugyldig godkjenning", + "usercode": "Brukerkode er ikke gyldig for denne brukeren p\u00e5 dette stedet" }, "step": { + "locations": { + "data": { + "location": "Plassering" + }, + "description": "Angi brukerkoden for denne brukeren p\u00e5 denne plasseringen", + "title": "Brukerkoder for plassering" + }, + "reauth_confirm": { + "description": "Total Connect m\u00e5 godkjenne kontoen din p\u00e5 nytt", + "title": "Godkjenne integrering p\u00e5 nytt" + }, "user": { "data": { "password": "Passord", diff --git a/homeassistant/components/totalconnect/translations/pl.json b/homeassistant/components/totalconnect/translations/pl.json index 530d632040ce35..ff2ca2351e6d25 100644 --- a/homeassistant/components/totalconnect/translations/pl.json +++ b/homeassistant/components/totalconnect/translations/pl.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane" + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie" + "invalid_auth": "Niepoprawne uwierzytelnienie", + "usercode": "Nieprawid\u0142owy kod u\u017cytkownika dla u\u017cytkownika w tej lokalizacji" }, "step": { + "locations": { + "data": { + "location": "Lokalizacja" + }, + "description": "Wprowad\u017a kod u\u017cytkownika dla u\u017cytkownika w tej lokalizacji", + "title": "Kody lokalizacji u\u017cytkownika" + }, + "reauth_confirm": { + "description": "Integracja Total Connect wymaga ponownego uwierzytelnienia Twojego konta", + "title": "Ponownie uwierzytelnij integracj\u0119" + }, "user": { "data": { "password": "Has\u0142o", diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 3375cac31d510b..0a6cf433d87740 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "IP adresse", + "model": "Enhetsmodell (valgfritt)", "name": "Navnet p\u00e5 enheten", "token": "API-token" }, diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 50eb4d16887816..80528b71370146 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adres IP", + "model": "Model urz\u0105dzenia (opcjonalnie)", "name": "Nazwa urz\u0105dzenia", "token": "Token API" }, diff --git a/homeassistant/components/zwave_js/translations/ca.json b/homeassistant/components/zwave_js/translations/ca.json index 6806b5072c1aaf..731c0bbcea8535 100644 --- a/homeassistant/components/zwave_js/translations/ca.json +++ b/homeassistant/components/zwave_js/translations/ca.json @@ -6,6 +6,7 @@ "addon_install_failed": "No s'ha pogut instal\u00b7lar el complement Z-Wave JS.", "addon_missing_discovery_info": "Falta la informaci\u00f3 de descobriment del complement Z-Wave JS.", "addon_set_config_failed": "No s'ha pogut establir la configuraci\u00f3 de Z-Wave JS.", + "addon_start_failed": "No s'ha pogut iniciar el complement Z-Wave JS.", "already_configured": "El dispositiu ja est\u00e0 configurat", "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "cannot_connect": "Ha fallat la connexi\u00f3" @@ -17,7 +18,8 @@ "unknown": "Error inesperat" }, "progress": { - "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts." + "install_addon": "Espera mentre finalitza la instal\u00b7laci\u00f3 del complement Z-Wave JS. Pot tardar uns quants minuts.", + "start_addon": "Espera mentre es completa la inicialitzaci\u00f3 del complement Z-Wave JS. Pot tardar uns segons." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Vols utilitzar el complement Supervisor de Z-Wave JS?", "title": "Selecciona el m\u00e8tode de connexi\u00f3" }, + "start_addon": { + "title": "El complement Z-Wave JS s'est\u00e0 iniciant." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/et.json b/homeassistant/components/zwave_js/translations/et.json index d51507b616f0b7..4c68e63530ff82 100644 --- a/homeassistant/components/zwave_js/translations/et.json +++ b/homeassistant/components/zwave_js/translations/et.json @@ -6,6 +6,7 @@ "addon_install_failed": "Z-Wave JS lisandmooduli paigaldamine nurjus.", "addon_missing_discovery_info": "Z-Wave JS lisandmooduli tuvastusteave puudub.", "addon_set_config_failed": "Z-Wave JS konfiguratsiooni m\u00e4\u00e4ramine nurjus.", + "addon_start_failed": "Z-Wave JS-i lisandmooduli k\u00e4ivitamine nurjus.", "already_configured": "Seade on juba h\u00e4\u00e4lestatud", "already_in_progress": "Seadistamine on juba k\u00e4imas", "cannot_connect": "\u00dchendamine nurjus" @@ -17,7 +18,8 @@ "unknown": "Ootamatu t\u00f5rge" }, "progress": { - "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit." + "install_addon": "Palun oota kuni Z-Wave JS lisandmoodul on paigaldatud. See v\u00f5ib v\u00f5tta mitu minutit.", + "start_addon": "Palun oota kuni Z-Wave JS lisandmooduli ak\u00e4ivitumine l\u00f5ppeb. See v\u00f5ib v\u00f5tta m\u00f5ned sekundid." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Kas soovid kasutada Z-Wave JSi halduri lisandmoodulit?", "title": "Vali \u00fchendusviis" }, + "start_addon": { + "title": "Z-Wave JS lisandmoodul k\u00e4ivitub." + }, "user": { "data": { "url": "" diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 040e3997ac17ea..9cc8bf822b87d4 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -6,6 +6,7 @@ "addon_install_failed": "\u00c9chec de l'installation du module compl\u00e9mentaire Z-Wave JS.", "addon_missing_discovery_info": "Informations manquantes sur la d\u00e9couverte du module compl\u00e9mentaire Z-Wave JS.", "addon_set_config_failed": "\u00c9chec de la d\u00e9finition de la configuration Z-Wave JS.", + "addon_start_failed": "\u00c9chec du d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS.", "already_configured": "Le p\u00e9riph\u00e9rique est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", "cannot_connect": "\u00c9chec de la connexion " @@ -17,7 +18,8 @@ "unknown": "Erreur inattendue" }, "progress": { - "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes." + "install_addon": "Veuillez patienter pendant l'installation du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre plusieurs minutes.", + "start_addon": "Veuillez patienter pendant le d\u00e9marrage du module compl\u00e9mentaire Z-Wave JS. Cela peut prendre quelques secondes." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, + "start_addon": { + "title": "Le module compl\u00e9mentaire Z-Wave JS est d\u00e9marr\u00e9." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index b724fb34e48385..acd049fc5619a0 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -6,6 +6,7 @@ "addon_install_failed": "Kunne ikke installere Z-Wave JS-tillegg", "addon_missing_discovery_info": "Manglende oppdagelsesinformasjon for Z-Wave JS-tillegg", "addon_set_config_failed": "Kunne ikke angi Z-Wave JS-konfigurasjon", + "addon_start_failed": "Kunne ikke starte Z-Wave JS-tillegget.", "already_configured": "Enheten er allerede konfigurert", "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Tilkobling mislyktes" @@ -17,7 +18,8 @@ "unknown": "Uventet feil" }, "progress": { - "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter." + "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter.", + "start_addon": "Vent mens Z-Wave JS-tilleggsstarten er fullf\u00f8rt. Dette kan ta noen sekunder." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Vil du bruke Z-Wave JS Supervisor-tillegg?", "title": "Velg tilkoblingsmetode" }, + "start_addon": { + "title": "Z-Wave JS-tillegget starter." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/pl.json b/homeassistant/components/zwave_js/translations/pl.json index b139b0dacc6657..2bfd994132b7ab 100644 --- a/homeassistant/components/zwave_js/translations/pl.json +++ b/homeassistant/components/zwave_js/translations/pl.json @@ -6,6 +6,7 @@ "addon_install_failed": "Nie uda\u0142o si\u0119 zainstalowa\u0107 dodatku Z-Wave JS", "addon_missing_discovery_info": "Brak informacji wykrywania dodatku Z-Wave JS", "addon_set_config_failed": "Nie uda\u0142o si\u0119 skonfigurowa\u0107 Z-Wave JS", + "addon_start_failed": "Nie uda\u0142o si\u0119 uruchomi\u0107 dodatku Z-Wave JS.", "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja jest ju\u017c w toku", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" @@ -17,7 +18,8 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "progress": { - "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut." + "install_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 instalacja dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 kilka minut.", + "start_addon": "Poczekaj, a\u017c zako\u0144czy si\u0119 uruchamianie dodatku Z-Wave JS. Mo\u017ce to zaj\u0105\u0107 chwil\u0119." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Czy chcesz skorzysta\u0107 z dodatku Z-Wave JS Supervisor?", "title": "Wybierz metod\u0119 po\u0142\u0105czenia" }, + "start_addon": { + "title": "Dodatek Z-Wave JS uruchamia si\u0119." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/ru.json b/homeassistant/components/zwave_js/translations/ru.json index 5b7e5f470175f7..1a65ce3ea71184 100644 --- a/homeassistant/components/zwave_js/translations/ru.json +++ b/homeassistant/components/zwave_js/translations/ru.json @@ -6,6 +6,7 @@ "addon_install_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", "addon_missing_discovery_info": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 Z-Wave JS.", "addon_set_config_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e Z-Wave JS.", + "addon_start_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS.", "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.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." @@ -17,7 +18,8 @@ "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "progress": { - "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442." + "install_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442.", + "start_addon": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u043a \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f Z-Wave JS. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Supervisor Z-Wave JS?", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" }, + "start_addon": { + "title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 Z-Wave JS \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f" + }, "user": { "data": { "url": "URL-\u0430\u0434\u0440\u0435\u0441" diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index b9ff1b419202f1..f1495b1aedadb9 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -6,6 +6,7 @@ "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "addon_start_failed": "Z-Wave JS add-on \u555f\u59cb\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557" @@ -17,7 +18,8 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", + "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, + "start_addon": { + "title": "Z-Wave JS add-on \u555f\u59cb\u4e2d\u3002" + }, "user": { "data": { "url": "\u7db2\u5740" From be4de15a109e663eca547b147574365f25fa9767 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 26 Feb 2021 00:06:13 +0000 Subject: [PATCH 0956/1818] [ci skip] Translation update --- .../components/airvisual/translations/lb.json | 9 ++++- .../components/bond/translations/cs.json | 2 +- .../components/climacell/translations/cs.json | 20 ++++++++++ .../components/climacell/translations/es.json | 30 +++++++++++++++ .../climacell/translations/zh-Hant.json | 34 +++++++++++++++++ .../faa_delays/translations/cs.json | 8 ++++ .../faa_delays/translations/es.json | 19 ++++++++++ .../faa_delays/translations/et.json | 21 +++++++++++ .../faa_delays/translations/ru.json | 21 +++++++++++ .../faa_delays/translations/zh-Hant.json | 21 +++++++++++ .../components/homekit/translations/cs.json | 2 +- .../components/kmtronic/translations/cs.json | 21 +++++++++++ .../components/litejet/translations/es.json | 16 ++++++++ .../components/mazda/translations/es.json | 1 + .../components/mullvad/translations/cs.json | 21 +++++++++++ .../components/mullvad/translations/es.json | 9 +++++ .../mullvad/translations/zh-Hant.json | 22 +++++++++++ .../components/netatmo/translations/es.json | 22 +++++++++++ .../netatmo/translations/zh-Hant.json | 22 +++++++++++ .../translations/es.json | 9 +++++ .../components/shelly/translations/nl.json | 4 +- .../components/soma/translations/cs.json | 2 +- .../components/spotify/translations/cs.json | 2 +- .../components/subaru/translations/cs.json | 28 ++++++++++++++ .../components/subaru/translations/es.json | 37 +++++++++++++++++++ .../totalconnect/translations/cs.json | 8 +++- .../totalconnect/translations/es.json | 10 ++++- .../wolflink/translations/sensor.nl.json | 16 ++++++++ .../xiaomi_miio/translations/es.json | 1 + .../components/zwave_js/translations/es.json | 7 +++- 30 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/climacell/translations/cs.json create mode 100644 homeassistant/components/climacell/translations/es.json create mode 100644 homeassistant/components/climacell/translations/zh-Hant.json create mode 100644 homeassistant/components/faa_delays/translations/cs.json create mode 100644 homeassistant/components/faa_delays/translations/es.json create mode 100644 homeassistant/components/faa_delays/translations/et.json create mode 100644 homeassistant/components/faa_delays/translations/ru.json create mode 100644 homeassistant/components/faa_delays/translations/zh-Hant.json create mode 100644 homeassistant/components/kmtronic/translations/cs.json create mode 100644 homeassistant/components/litejet/translations/es.json create mode 100644 homeassistant/components/mullvad/translations/cs.json create mode 100644 homeassistant/components/mullvad/translations/es.json create mode 100644 homeassistant/components/mullvad/translations/zh-Hant.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/es.json create mode 100644 homeassistant/components/subaru/translations/cs.json create mode 100644 homeassistant/components/subaru/translations/es.json create mode 100644 homeassistant/components/wolflink/translations/sensor.nl.json diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index 5e45098c11d837..d6799ba6e37b96 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -7,7 +7,8 @@ "error": { "cannot_connect": "Feeler beim verbannen", "general_error": "Onerwaarte Feeler", - "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel" + "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel", + "location_not_found": "Standuert net fonnt." }, "step": { "geography": { @@ -19,6 +20,12 @@ "description": "Benotz Airvisual cloud API fir eng geografescher Lag z'iwwerwaachen.", "title": "Geografie ariichten" }, + "geography_by_name": { + "data": { + "city": "Stad", + "country": "Land" + } + }, "node_pro": { "data": { "ip_address": "Host", diff --git a/homeassistant/components/bond/translations/cs.json b/homeassistant/components/bond/translations/cs.json index 677c7e80236996..13135dbf53e592 100644 --- a/homeassistant/components/bond/translations/cs.json +++ b/homeassistant/components/bond/translations/cs.json @@ -15,7 +15,7 @@ "data": { "access_token": "P\u0159\u00edstupov\u00fd token" }, - "description": "Chcete nastavit {bond_id} ?" + "description": "Chcete nastavit {name}?" }, "user": { "data": { diff --git a/homeassistant/components/climacell/translations/cs.json b/homeassistant/components/climacell/translations/cs.json new file mode 100644 index 00000000000000..1ae29deb08c1fb --- /dev/null +++ b/homeassistant/components/climacell/translations/cs.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_api_key": "Neplatn\u00fd kl\u00ed\u010d API", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "api_key": "Kl\u00ed\u010d API", + "latitude": "Zem\u011bpisn\u00e1 \u0161\u00ed\u0159ka", + "longitude": "Zem\u011bpisn\u00e1 d\u00e9lka", + "name": "Jm\u00e9no" + } + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es.json b/homeassistant/components/climacell/translations/es.json new file mode 100644 index 00000000000000..4c4d8fcc9bb9d8 --- /dev/null +++ b/homeassistant/components/climacell/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde." + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud", + "name": "Nombre" + }, + "description": "Si no se proporcionan Latitud y Longitud , se utilizar\u00e1n los valores predeterminados en la configuraci\u00f3n de Home Assistant. Se crear\u00e1 una entidad para cada tipo de pron\u00f3stico, pero solo las que seleccione estar\u00e1n habilitadas de forma predeterminada." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipo(s) de pron\u00f3stico", + "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.", + "title": "Actualizar las opciones ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hant.json b/homeassistant/components/climacell/translations/zh-Hant.json new file mode 100644 index 00000000000000..76eaf50b932535 --- /dev/null +++ b/homeassistant/components/climacell/translations/zh-Hant.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", + "rate_limited": "\u9054\u5230\u9650\u5236\u983b\u7387\u3001\u8acb\u7a0d\u5019\u518d\u8a66\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "name": "\u540d\u7a31" + }, + "description": "\u5047\u5982\u672a\u63d0\u4f9b\u7def\u5ea6\u8207\u7d93\u5ea6\uff0c\u5c07\u6703\u4f7f\u7528 Home Assistant \u8a2d\u5b9a\u4f5c\u70ba\u9810\u8a2d\u503c\u3002\u6bcf\u4e00\u500b\u9810\u5831\u985e\u578b\u90fd\u6703\u7522\u751f\u4e00\u7d44\u5be6\u9ad4\uff0c\u6216\u8005\u9810\u8a2d\u70ba\u6240\u9078\u64c7\u555f\u7528\u7684\u9810\u5831\u3002" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u9810\u5831\u985e\u578b", + "timestep": "NowCast \u9810\u5831\u9593\u9694\u5206\u9418" + }, + "description": "\u5047\u5982\u9078\u64c7\u958b\u555f `nowcast` \u9810\u5831\u5be6\u9ad4\u3001\u5c07\u53ef\u4ee5\u8a2d\u5b9a\u9810\u5831\u983b\u7387\u9593\u9694\u5206\u9418\u6578\u3002\u6839\u64da\u6240\u8f38\u5165\u7684\u9593\u9694\u6642\u9593\u5c07\u6c7a\u5b9a\u9810\u5831\u7684\u6578\u76ee\u3002", + "title": "\u66f4\u65b0 ClimaCell \u9078\u9805" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/cs.json b/homeassistant/components/faa_delays/translations/cs.json new file mode 100644 index 00000000000000..60e4aed57a2784 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/cs.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/es.json b/homeassistant/components/faa_delays/translations/es.json new file mode 100644 index 00000000000000..94eca99dda3774 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Este aeropuerto ya est\u00e1 configurado." + }, + "error": { + "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido" + }, + "step": { + "user": { + "data": { + "id": "Aeropuerto" + }, + "description": "Introduzca un c\u00f3digo de aeropuerto estadounidense en formato IATA", + "title": "Retrasos de la FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/et.json b/homeassistant/components/faa_delays/translations/et.json new file mode 100644 index 00000000000000..75b52558374920 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "See lennujaam on juba seadistatud." + }, + "error": { + "cannot_connect": "\u00dchendumine nurjus", + "invalid_airport": "Lennujaama kood ei sobi", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "id": "Lennujaam" + }, + "description": "Sisesta USA lennujaama kood IATA vormingus", + "title": "" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/ru.json b/homeassistant/components/faa_delays/translations/ru.json new file mode 100644 index 00000000000000..d68810fc9575bc --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ru.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "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_airport": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "id": "\u0410\u044d\u0440\u043e\u043f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u044d\u0440\u043e\u043f\u043e\u0440\u0442\u0430 \u0421\u0428\u0410 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 IATA.", + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/zh-Hant.json b/homeassistant/components/faa_delays/translations/zh-Hant.json new file mode 100644 index 00000000000000..f2585bb790f07c --- /dev/null +++ b/homeassistant/components/faa_delays/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u6b64\u6a5f\u5834\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_airport": "\u6a5f\u5834\u4ee3\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "id": "\u6a5f\u5834" + }, + "description": "\u8f38\u5165\u7f8e\u570b\u6a5f\u5834 IATA \u4ee3\u78bc", + "title": "FAA \u822a\u73ed\u5ef6\u8aa4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json index faf1b1d74fc0d4..cdfaed9183c1f4 100644 --- a/homeassistant/components/homekit/translations/cs.json +++ b/homeassistant/components/homekit/translations/cs.json @@ -17,7 +17,7 @@ "include_domains": "Dom\u00e9ny, kter\u00e9 maj\u00ed b\u00fdt zahrnuty", "mode": "Re\u017eim" }, - "title": "Aktivace HomeKit" + "title": "Vyberte dom\u00e9ny, kter\u00e9 chcete zahrnout" } } }, diff --git a/homeassistant/components/kmtronic/translations/cs.json b/homeassistant/components/kmtronic/translations/cs.json new file mode 100644 index 00000000000000..0f02cd974c207c --- /dev/null +++ b/homeassistant/components/kmtronic/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/es.json b/homeassistant/components/litejet/translations/es.json new file mode 100644 index 00000000000000..b0641022bf01d1 --- /dev/null +++ b/homeassistant/components/litejet/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "open_failed": "No se puede abrir el puerto serie especificado." + }, + "step": { + "user": { + "data": { + "port": "Puerto" + }, + "description": "Conecte el puerto RS232-2 del LiteJet a su computadora e ingrese la ruta al dispositivo del puerto serial. \n\nEl LiteJet MCP debe configurarse para 19,2 K baudios, 8 bits de datos, 1 bit de parada, sin paridad y para transmitir un 'CR' despu\u00e9s de cada respuesta.", + "title": "Conectarse a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/es.json b/homeassistant/components/mazda/translations/es.json index 72fc9ce7389103..868ae0d770ea6c 100644 --- a/homeassistant/components/mazda/translations/es.json +++ b/homeassistant/components/mazda/translations/es.json @@ -6,6 +6,7 @@ "step": { "reauth": { "data": { + "password": "Contrase\u00f1a", "region": "Regi\u00f3n" }, "description": "Ha fallado la autenticaci\u00f3n para los Servicios Conectados de Mazda. Por favor, introduce tus credenciales actuales.", diff --git a/homeassistant/components/mullvad/translations/cs.json b/homeassistant/components/mullvad/translations/cs.json new file mode 100644 index 00000000000000..0f02cd974c207c --- /dev/null +++ b/homeassistant/components/mullvad/translations/cs.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" + }, + "error": { + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "user": { + "data": { + "host": "Hostitel", + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/es.json b/homeassistant/components/mullvad/translations/es.json new file mode 100644 index 00000000000000..d6a17561c3d354 --- /dev/null +++ b/homeassistant/components/mullvad/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u00bfConfigurar la integraci\u00f3n VPN de Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/zh-Hant.json b/homeassistant/components/mullvad/translations/zh-Hant.json new file mode 100644 index 00000000000000..d78c36b72d77cd --- /dev/null +++ b/homeassistant/components/mullvad/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8a2d\u5b9a Mullvad VPN \u6574\u5408\uff1f" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/es.json b/homeassistant/components/netatmo/translations/es.json index 556fe2626d491b..b1159c1dd9d306 100644 --- a/homeassistant/components/netatmo/translations/es.json +++ b/homeassistant/components/netatmo/translations/es.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "fuera", + "hg": "protector contra las heladas", + "schedule": "Horario" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha detectado una alarma", + "animal": "{entity_name} ha detectado un animal", + "cancel_set_point": "{entity_name} ha reanudado su programaci\u00f3n", + "human": "{entity_name} ha detectado una persona", + "movement": "{entity_name} ha detectado movimiento", + "outdoor": "{entity_name} ha detectado un evento en el exterior", + "person": "{entity_name} ha detectado una persona", + "person_away": "{entity_name} ha detectado que una persona se ha ido", + "set_point": "Temperatura objetivo {entity_name} fijada manualmente", + "therm_mode": "{entity_name} cambi\u00f3 a \" {subtype} \"", + "turned_off": "{entity_name} desactivado", + "turned_on": "{entity_name} activado", + "vehicle": "{entity_name} ha detectado un veh\u00edculo" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/zh-Hant.json b/homeassistant/components/netatmo/translations/zh-Hant.json index e396deabb68c0b..e62836f9a7e4ad 100644 --- a/homeassistant/components/netatmo/translations/zh-Hant.json +++ b/homeassistant/components/netatmo/translations/zh-Hant.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\u96e2\u5bb6", + "hg": "\u9632\u51cd\u6a21\u5f0f", + "schedule": "\u6392\u7a0b" + }, + "trigger_type": { + "alarm_started": "{entity_name}\u5075\u6e2c\u5230\u8b66\u5831", + "animal": "{entity_name}\u5075\u6e2c\u5230\u52d5\u7269", + "cancel_set_point": "{entity_name}\u5df2\u6062\u5fa9\u5176\u6392\u7a0b", + "human": "{entity_name}\u5075\u6e2c\u5230\u4eba\u985e", + "movement": "{entity_name}\u5075\u6e2c\u5230\u52d5\u4f5c", + "outdoor": "{entity_name}\u5075\u6e2c\u5230\u6236\u5916\u52d5\u4f5c", + "person": "{entity_name}\u5075\u6e2c\u5230\u4eba\u54e1", + "person_away": "{entity_name}\u5075\u6e2c\u5230\u4eba\u54e1\u5df2\u96e2\u958b", + "set_point": "\u624b\u52d5\u8a2d\u5b9a{entity_name}\u76ee\u6a19\u6eab\u5ea6", + "therm_mode": "{entity_name}\u5207\u63db\u81f3 \"{subtype}\"", + "turned_off": "{entity_name}\u5df2\u95dc\u9589", + "turned_on": "{entity_name}\u5df2\u958b\u555f", + "vehicle": "{entity_name}\u5075\u6e2c\u5230\u8eca\u8f1b" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/es.json b/homeassistant/components/rituals_perfume_genie/translations/es.json new file mode 100644 index 00000000000000..bc74ecfd7ea20d --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/es.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Con\u00e9ctese a su cuenta de Rituals" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json index 7084a972e291d5..c486b9c6bfe7a6 100644 --- a/homeassistant/components/shelly/translations/nl.json +++ b/homeassistant/components/shelly/translations/nl.json @@ -8,7 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Shelly: {name}", + "flow_title": "{name}", "step": { "confirm_discovery": { "description": "Wilt u het {model} bij {host} instellen? Voordat apparaten op batterijen kunnen worden ingesteld, moet het worden gewekt door op de knop op het apparaat te drukken." @@ -16,7 +16,7 @@ "credentials": { "data": { "password": "Wachtwoord", - "username": "Benutzername" + "username": "Gebruikersnaam" } }, "user": { diff --git a/homeassistant/components/soma/translations/cs.json b/homeassistant/components/soma/translations/cs.json index 5a27562df71f71..ba1261c1100915 100644 --- a/homeassistant/components/soma/translations/cs.json +++ b/homeassistant/components/soma/translations/cs.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "M\u016f\u017eete nastavit pouze jeden \u00fa\u010det Soma.", - "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el.", "connection_error": "P\u0159ipojen\u00ed k za\u0159\u00edzen\u00ed SOMA Connect se nezda\u0159ilo.", "missing_configuration": "Integrace Soma nen\u00ed nastavena. Postupujte podle dokumentace.", "result_error": "SOMA Connect odpov\u011bd\u011blo chybov\u00fdm stavem." diff --git a/homeassistant/components/spotify/translations/cs.json b/homeassistant/components/spotify/translations/cs.json index f8f122e63e2c18..69cd1b1623ada0 100644 --- a/homeassistant/components/spotify/translations/cs.json +++ b/homeassistant/components/spotify/translations/cs.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "\u010casov\u00fd limit autoriza\u010dn\u00edho URL vypr\u0161el", + "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})" }, diff --git a/homeassistant/components/subaru/translations/cs.json b/homeassistant/components/subaru/translations/cs.json new file mode 100644 index 00000000000000..ee3bf7347ca256 --- /dev/null +++ b/homeassistant/components/subaru/translations/cs.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "\u00da\u010det je ji\u017e nastaven", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit" + }, + "error": { + "bad_pin_format": "PIN by m\u011bl m\u00edt 4 \u010d\u00edslice", + "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", + "incorrect_pin": "Nespr\u00e1vn\u00fd PIN", + "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed", + "unknown": "Neo\u010dek\u00e1van\u00e1 chyba" + }, + "step": { + "pin": { + "data": { + "pin": "PIN k\u00f3d" + } + }, + "user": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/es.json b/homeassistant/components/subaru/translations/es.json new file mode 100644 index 00000000000000..deccc23c75dbb2 --- /dev/null +++ b/homeassistant/components/subaru/translations/es.json @@ -0,0 +1,37 @@ +{ + "config": { + "error": { + "bad_pin_format": "El PIN debe tener 4 d\u00edgitos", + "incorrect_pin": "PIN incorrecto" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Por favor, introduzca su PIN de MySubaru\nNOTA: Todos los veh\u00edculos de la cuenta deben tener el mismo PIN", + "title": "Configuraci\u00f3n de Subaru Starlink" + }, + "user": { + "data": { + "country": "Seleccionar pa\u00eds", + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Por favor, introduzca sus credenciales de MySubaru\nNOTA: La configuraci\u00f3n inicial puede tardar hasta 30 segundos", + "title": "Configuraci\u00f3n de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Habilitar el sondeo de veh\u00edculos" + }, + "description": "Cuando est\u00e1 habilitado, el sondeo de veh\u00edculos enviar\u00e1 un comando remoto a su veh\u00edculo cada 2 horas para obtener nuevos datos del sensor. Sin sondeo del veh\u00edculo, los nuevos datos del sensor solo se reciben cuando el veh\u00edculo env\u00eda datos autom\u00e1ticamente (normalmente despu\u00e9s de apagar el motor).", + "title": "Opciones de Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/cs.json b/homeassistant/components/totalconnect/translations/cs.json index 60e2196b38761b..74dece0c54ef6d 100644 --- a/homeassistant/components/totalconnect/translations/cs.json +++ b/homeassistant/components/totalconnect/translations/cs.json @@ -1,12 +1,18 @@ { "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": { "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" }, "step": { + "locations": { + "data": { + "location": "Um\u00edst\u011bn\u00ed" + } + }, "user": { "data": { "password": "Heslo", diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 48af1bed0f4a5b..090d9271dee5a0 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -4,9 +4,17 @@ "already_configured": "La cuenta ya ha sido configurada" }, "error": { - "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "usercode": "El c\u00f3digo de usuario no es v\u00e1lido para este usuario en esta ubicaci\u00f3n" }, "step": { + "locations": { + "description": "Ingrese el c\u00f3digo de usuario para este usuario en esta ubicaci\u00f3n", + "title": "C\u00f3digos de usuario de ubicaci\u00f3n" + }, + "reauth_confirm": { + "description": "Total Connect necesita volver a autentificar tu cuenta" + }, "user": { "data": { "password": "Contrase\u00f1a", diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json new file mode 100644 index 00000000000000..da03cc43b4b317 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -0,0 +1,16 @@ +{ + "state": { + "wolflink__state": { + "frost_warmwasser": "DHW vorst", + "frostschutz": "Vorstbescherming", + "gasdruck": "Gasdruk", + "glt_betrieb": "BMS-modus", + "heizbetrieb": "Verwarmingsmodus", + "heizgerat_mit_speicher": "Boiler met cilinder", + "heizung": "Verwarmen", + "initialisierung": "Initialisatie", + "kalibration": "Kalibratie", + "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json index fd4b8c36a8b228..60a989ade0d6ce 100644 --- a/homeassistant/components/xiaomi_miio/translations/es.json +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -13,6 +13,7 @@ "step": { "device": { "data": { + "model": "Modelo de dispositivo (opcional)", "name": "Nombre del dispositivo" }, "description": "Necesitar\u00e1 la clave de 32 caracteres Token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones. Tenga en cuenta que esta Token API es diferente de la clave utilizada por la integraci\u00f3n de Xiaomi Aqara.", diff --git a/homeassistant/components/zwave_js/translations/es.json b/homeassistant/components/zwave_js/translations/es.json index 32d7a6d2e6d808..26fd155a0ad2ae 100644 --- a/homeassistant/components/zwave_js/translations/es.json +++ b/homeassistant/components/zwave_js/translations/es.json @@ -6,6 +6,7 @@ "addon_install_failed": "No se ha podido instalar el complemento Z-Wave JS.", "addon_missing_discovery_info": "Falta informaci\u00f3n de descubrimiento del complemento Z-Wave JS.", "addon_set_config_failed": "Fallo en la configuraci\u00f3n de Z-Wave JS.", + "addon_start_failed": "No se ha podido iniciar el complemento Z-Wave JS.", "already_configured": "El dispositivo ya est\u00e1 configurado", "already_in_progress": "El flujo de configuraci\u00f3n ya est\u00e1 en proceso", "cannot_connect": "No se pudo conectar" @@ -17,7 +18,8 @@ "unknown": "Error inesperado" }, "progress": { - "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos." + "install_addon": "Espera mientras termina la instalaci\u00f3n del complemento Z-Wave JS. Puede tardar varios minutos.", + "start_addon": "Espere mientras se completa el inicio del complemento Z-Wave JS. Esto puede tardar unos segundos." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "\u00bfQuieres utilizar el complemento Z-Wave JS Supervisor?", "title": "Selecciona el m\u00e9todo de conexi\u00f3n" }, + "start_addon": { + "title": "Se est\u00e1 iniciando el complemento Z-Wave JS." + }, "user": { "data": { "url": "URL" From 3b459cd59af1873317a1bd90d507c9a87a2131c2 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 27 Feb 2021 00:05:45 +0000 Subject: [PATCH 0957/1818] [ci skip] Translation update --- .../accuweather/translations/nl.json | 4 ++ .../alarmdecoder/translations/nl.json | 2 +- .../azure_devops/translations/nl.json | 14 +++++- .../components/bond/translations/it.json | 4 +- .../components/bond/translations/nl.json | 5 +++ .../components/broadlink/translations/nl.json | 9 ++++ .../components/climacell/translations/it.json | 34 ++++++++++++++ .../components/climacell/translations/no.json | 2 +- .../components/cover/translations/nl.json | 3 +- .../faa_delays/translations/it.json | 21 +++++++++ .../faa_delays/translations/no.json | 21 +++++++++ .../components/gogogate2/translations/nl.json | 2 +- .../components/hlk_sw16/translations/nl.json | 1 + .../components/homekit/translations/it.json | 8 ++-- .../components/homekit/translations/nl.json | 6 +-- .../components/insteon/translations/nl.json | 3 ++ .../components/kmtronic/translations/it.json | 21 +++++++++ .../components/kodi/translations/nl.json | 7 ++- .../components/litejet/translations/it.json | 19 ++++++++ .../litterrobot/translations/it.json | 20 +++++++++ .../media_player/translations/nl.json | 2 +- .../components/mqtt/translations/nl.json | 1 + .../components/mullvad/translations/it.json | 22 ++++++++++ .../components/netatmo/translations/it.json | 22 ++++++++++ .../components/netatmo/translations/nl.json | 7 ++- .../components/netatmo/translations/no.json | 22 ++++++++++ .../philips_js/translations/en.json | 4 +- .../philips_js/translations/et.json | 2 + .../rainmachine/translations/nl.json | 2 +- .../components/risco/translations/nl.json | 4 +- .../components/sentry/translations/nl.json | 3 ++ .../simplisafe/translations/nl.json | 6 ++- .../somfy_mylink/translations/nl.json | 2 +- .../components/spotify/translations/nl.json | 2 +- .../components/subaru/translations/it.json | 44 +++++++++++++++++++ .../components/syncthru/translations/nl.json | 5 +++ .../totalconnect/translations/it.json | 17 ++++++- .../components/volumio/translations/nl.json | 1 + .../wolflink/translations/sensor.nl.json | 11 ++++- .../xiaomi_miio/translations/it.json | 1 + .../xiaomi_miio/translations/nl.json | 1 + .../zoneminder/translations/nl.json | 2 +- .../components/zwave_js/translations/it.json | 7 ++- .../components/zwave_js/translations/nl.json | 2 +- 44 files changed, 365 insertions(+), 33 deletions(-) create mode 100644 homeassistant/components/climacell/translations/it.json create mode 100644 homeassistant/components/faa_delays/translations/it.json create mode 100644 homeassistant/components/faa_delays/translations/no.json create mode 100644 homeassistant/components/kmtronic/translations/it.json create mode 100644 homeassistant/components/litejet/translations/it.json create mode 100644 homeassistant/components/litterrobot/translations/it.json create mode 100644 homeassistant/components/mullvad/translations/it.json create mode 100644 homeassistant/components/subaru/translations/it.json diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index ff0d81f94d38b5..4bf5f9fce45e82 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "API-sleutel", "requests_exceeded": "Het toegestane aantal verzoeken aan de Accuweather API is overschreden. U moet wachten of de API-sleutel wijzigen." }, diff --git a/homeassistant/components/alarmdecoder/translations/nl.json b/homeassistant/components/alarmdecoder/translations/nl.json index 1af1e8d803c9c2..1ea9cb98b56a69 100644 --- a/homeassistant/components/alarmdecoder/translations/nl.json +++ b/homeassistant/components/alarmdecoder/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "AlarmDecoder-apparaat is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "create_entry": { "default": "Succesvol verbonden met AlarmDecoder." diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index aef6a7178954e4..07dc59e1a56ac3 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -1,11 +1,21 @@ { "config": { "abort": { - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "project_error": "Kon geen projectinformatie ophalen." + }, + "step": { + "user": { + "data": { + "organization": "Organisatie", + "project": "Project" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/it.json b/homeassistant/components/bond/translations/it.json index d3ac1ab6b49094..e22ad82e1fd1a1 100644 --- a/homeassistant/components/bond/translations/it.json +++ b/homeassistant/components/bond/translations/it.json @@ -9,13 +9,13 @@ "old_firmware": "Firmware precedente non supportato sul dispositivo Bond - si prega di aggiornare prima di continuare", "unknown": "Errore imprevisto" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "Token di accesso" }, - "description": "Vuoi configurare {bond_id}?" + "description": "Vuoi configurare {name}?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index b5d8c593ea9e93..a76c7a69d7f68a 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -10,6 +10,11 @@ }, "flow_title": "Bond: {bond_id} ({host})", "step": { + "confirm": { + "data": { + "access_token": "Toegangstoken" + } + }, "user": { "data": { "access_token": "Toegangstoken", diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 7f85335d7bb375..d2db5476555607 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -18,6 +18,15 @@ "finish": { "data": { "name": "Naam" + }, + "title": "Kies een naam voor het apparaat" + }, + "reset": { + "title": "Ontgrendel het apparaat" + }, + "unlock": { + "data": { + "unlock": "Ja, doe het." } }, "user": { diff --git a/homeassistant/components/climacell/translations/it.json b/homeassistant/components/climacell/translations/it.json new file mode 100644 index 00000000000000..cc7df4f8ab3800 --- /dev/null +++ b/homeassistant/components/climacell/translations/it.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_api_key": "Chiave API non valida", + "rate_limited": "Al momento la tariffa \u00e8 limitata, riprova pi\u00f9 tardi.", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API", + "latitude": "Latitudine", + "longitude": "Logitudine", + "name": "Nome" + }, + "description": "Se Latitudine e Logitudine non vengono forniti, verranno utilizzati i valori predefiniti nella configurazione di Home Assistant. Verr\u00e0 creata un'entit\u00e0 per ogni tipo di previsione, ma solo quelli selezionati saranno abilitati per impostazione predefinita." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Tipo(i) di previsione", + "timestep": "Minuti tra le previsioni di NowCast" + }, + "description": "Se scegli di abilitare l'entit\u00e0 di previsione `nowcast`, puoi configurare il numero di minuti tra ogni previsione. Il numero di previsioni fornite dipende dal numero di minuti scelti tra le previsioni.", + "title": "Aggiorna le opzioni di ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index 64845ff76978c0..af07ce716d0917 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -14,7 +14,7 @@ "longitude": "Lengdegrad", "name": "Navn" }, - "description": "Hvis Breddegrad and Lengdegrad er ikke gitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." + "description": "Hvis Breddegrad og Lengdegrad ikke er oppgitt, vil standardverdiene i Home Assistant-konfigurasjonen bli brukt. Det blir opprettet en enhet for hver prognosetype, men bare de du velger blir aktivert som standard." } } }, diff --git a/homeassistant/components/cover/translations/nl.json b/homeassistant/components/cover/translations/nl.json index 679d9360a821c4..8b1ca3c3500f7c 100644 --- a/homeassistant/components/cover/translations/nl.json +++ b/homeassistant/components/cover/translations/nl.json @@ -6,7 +6,8 @@ "open": "Open {entity_name}", "open_tilt": "Open de kanteling {entity_name}", "set_position": "Stel de positie van {entity_name} in", - "set_tilt_position": "Stel de {entity_name} kantelpositie in" + "set_tilt_position": "Stel de {entity_name} kantelpositie in", + "stop": "Stop {entity_name}" }, "condition_type": { "is_closed": "{entity_name} is gesloten", diff --git a/homeassistant/components/faa_delays/translations/it.json b/homeassistant/components/faa_delays/translations/it.json new file mode 100644 index 00000000000000..e1bf6ad0646a09 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Questo aeroporto \u00e8 gi\u00e0 configurato." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_airport": "Il codice dell'aeroporto non \u00e8 valido", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "id": "Aeroporto" + }, + "description": "Immettere un codice aeroporto statunitense in formato IATA", + "title": "Ritardi FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/no.json b/homeassistant/components/faa_delays/translations/no.json new file mode 100644 index 00000000000000..c481f90bf75068 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/no.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Denne flyplassen er allerede konfigurert." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes", + "invalid_airport": "Flyplasskoden er ikke gyldig", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "id": "Flyplass" + }, + "description": "Skriv inn en amerikansk flyplasskode i IATA-format", + "title": "FAA forsinkelser" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/nl.json b/homeassistant/components/gogogate2/translations/nl.json index ad8e894d093ffe..5418735ec07a91 100644 --- a/homeassistant/components/gogogate2/translations/nl.json +++ b/homeassistant/components/gogogate2/translations/nl.json @@ -15,7 +15,7 @@ "username": "Gebruikersnaam" }, "description": "Geef hieronder de vereiste informatie op.", - "title": "Stel GogoGate2 in" + "title": "Stel GogoGate2 of iSmartGate in" } } } diff --git a/homeassistant/components/hlk_sw16/translations/nl.json b/homeassistant/components/hlk_sw16/translations/nl.json index 0569c39321a24b..8ad15260b0de5c 100644 --- a/homeassistant/components/hlk_sw16/translations/nl.json +++ b/homeassistant/components/hlk_sw16/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index 9a85d1e6e9fc52..fee64457652dd0 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -19,7 +19,7 @@ "title": "Seleziona i domini da includere" }, "pairing": { - "description": "Non appena il {name} \u00e8 pronto, l'associazione sar\u00e0 disponibile in \"Notifiche\" come \"Configurazione HomeKit Bridge\".", + "description": "Per completare l'associazione, seguire le istruzioni in \"Notifiche\" sotto \"Associazione HomeKit\".", "title": "Associa HomeKit" }, "user": { @@ -28,8 +28,8 @@ "include_domains": "Domini da includere", "mode": "Modalit\u00e0" }, - "description": "L'integrazione di HomeKit ti consentir\u00e0 di accedere alle entit\u00e0 di Home Assistant in HomeKit. In modalit\u00e0 bridge, i bridge HomeKit sono limitati a 150 accessori per istanza, incluso il bridge stesso. Se desideri eseguire il bridge di un numero di accessori superiore a quello massimo, si consiglia di utilizzare pi\u00f9 bridge HomeKit per domini diversi. La configurazione dettagliata dell'entit\u00e0 \u00e8 disponibile solo tramite YAML per il bridge principale.", - "title": "Attiva HomeKit" + "description": "Scegli i domini da includere. Verranno incluse tutte le entit\u00e0 supportate nel dominio. Verr\u00e0 creata un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale TV e telecamera.", + "title": "Seleziona i domini da includere" } } }, @@ -55,7 +55,7 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali e per evitare una indisponibilit\u00e0 imprevista, creare e associare un'istanza HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, ci sar\u00e0 una HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", "title": "Seleziona le entit\u00e0 da includere" }, "init": { diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index bcf61fe9868faa..9013723ac6cd8b 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -11,7 +11,7 @@ }, "pairing": { "description": "Zodra de {name} klaar is, is het koppelen beschikbaar in \"Meldingen\" als \"HomeKit Bridge Setup\".", - "title": "Koppel HomeKit Bridge" + "title": "Koppel HomeKit" }, "user": { "data": { @@ -20,7 +20,7 @@ "mode": "Mode" }, "description": "De HomeKit-integratie geeft u toegang tot uw Home Assistant-entiteiten in HomeKit. In bridge-modus zijn HomeKit-bruggen beperkt tot 150 accessoires per exemplaar, inclusief de brug zelf. Als u meer dan het maximale aantal accessoires wilt overbruggen, is het aan te raden om meerdere HomeKit-bridges voor verschillende domeinen te gebruiken. Gedetailleerde entiteitsconfiguratie is alleen beschikbaar via YAML voor de primaire bridge.", - "title": "Activeer HomeKit Bridge" + "title": "Selecteer domeinen die u wilt opnemen" } } }, @@ -57,7 +57,7 @@ }, "yaml": { "description": "Deze invoer wordt beheerd via YAML", - "title": "Pas de HomeKit Bridge-opties aan" + "title": "Pas de HomeKit-opties aan" } } } diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index e4f7d4a8102f70..98a27fb1139382 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -87,6 +87,9 @@ "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." } + }, + "remove_x10": { + "title": "Insteon" } } } diff --git a/homeassistant/components/kmtronic/translations/it.json b/homeassistant/components/kmtronic/translations/it.json new file mode 100644 index 00000000000000..e9356485e087f8 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 8eb4a39cfb698a..57476791b8fa1a 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -11,6 +11,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Kodi: {name}", "step": { "credentials": { "data": { @@ -19,11 +20,15 @@ }, "description": "Voer uw Kodi gebruikersnaam en wachtwoord in. Deze zijn te vinden in Systeem / Instellingen / Netwerk / Services." }, + "discovery_confirm": { + "description": "Wil je Kodi (`{name}`) toevoegen aan Home Assistant?", + "title": "Kodi ontdekt" + }, "user": { "data": { "host": "Host", "port": "Poort", - "ssl": "Maak verbinding via SSL" + "ssl": "Gebruik een SSL-certificaat" }, "description": "Kodi-verbindingsinformatie. Zorg ervoor dat u \"Controle van Kodi via HTTP toestaan\" in Systeem / Instellingen / Netwerk / Services inschakelt." }, diff --git a/homeassistant/components/litejet/translations/it.json b/homeassistant/components/litejet/translations/it.json new file mode 100644 index 00000000000000..5b3dc46753d888 --- /dev/null +++ b/homeassistant/components/litejet/translations/it.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "open_failed": "Impossibile aprire la porta seriale specificata." + }, + "step": { + "user": { + "data": { + "port": "Porta" + }, + "description": "Collega la porta RS232-2 del LiteJet al tuo computer e inserisci il percorso del dispositivo della porta seriale. \n\nL'MCP LiteJet deve essere configurato per 19,2 K baud, 8 bit di dati, 1 bit di stop, nessuna parit\u00e0 e per trasmettere un \"CR\" dopo ogni risposta.", + "title": "Connetti a LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/it.json b/homeassistant/components/litterrobot/translations/it.json new file mode 100644 index 00000000000000..843262aa31858b --- /dev/null +++ b/homeassistant/components/litterrobot/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ 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 37c1d6b4d9ef9b..6ad22742533bf7 100644 --- a/homeassistant/components/media_player/translations/nl.json +++ b/homeassistant/components/media_player/translations/nl.json @@ -22,7 +22,7 @@ "on": "Aan", "paused": "Gepauzeerd", "playing": "Afspelen", - "standby": "Standby" + "standby": "Stand-by" } }, "title": "Mediaspeler" diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index a0ab0e497dabfc..3b3ebf9fe3b9a5 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -63,6 +63,7 @@ }, "options": { "data": { + "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", "birth_topic": "Birth message onderwerp" } diff --git a/homeassistant/components/mullvad/translations/it.json b/homeassistant/components/mullvad/translations/it.json new file mode 100644 index 00000000000000..47cd8290f215ad --- /dev/null +++ b/homeassistant/components/mullvad/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Password", + "username": "Nome utente" + }, + "description": "Configurare l'integrazione VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/it.json b/homeassistant/components/netatmo/translations/it.json index 46c2d7d2721814..152f7d47597ac8 100644 --- a/homeassistant/components/netatmo/translations/it.json +++ b/homeassistant/components/netatmo/translations/it.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "Fuori casa", + "hg": "protezione antigelo", + "schedule": "programma" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha rilevato un allarme", + "animal": "{entity_name} ha rilevato un animale", + "cancel_set_point": "{entity_name} ha ripreso il suo programma", + "human": "{entity_name} ha rilevato un essere umano", + "movement": "{entity_name} ha rilevato un movimento", + "outdoor": "{entity_name} ha rilevato un evento all'esterno", + "person": "{entity_name} ha rilevato una persona", + "person_away": "{entity_name} ha rilevato che una persona \u00e8 uscita", + "set_point": "{entity_name} temperatura desiderata impostata manualmente", + "therm_mode": "{entity_name} \u00e8 passato a \"{subtype}\"", + "turned_off": "{entity_name} disattivato", + "turned_on": "{entity_name} attivato", + "vehicle": "{entity_name} ha rilevato un veicolo" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 431f105df3d8c8..0bdc3170a5a5b6 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -3,7 +3,8 @@ "abort": { "authorize_url_timeout": "Time-out genereren autorisatie-URL.", "missing_configuration": "Het component is niet geconfigureerd. Volg de documentatie.", - "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "create_entry": { "default": "Succesvol geauthenticeerd met Netatmo." @@ -41,6 +42,10 @@ "public_weather": { "data": { "area_name": "Naam van het gebied", + "lat_ne": "Breedtegraad Noordoostelijke hoek", + "lat_sw": "Breedtegraad Zuidwestelijke hoek", + "lon_ne": "Lengtegraad Noordoostelijke hoek", + "lon_sw": "Lengtegraad Zuidwestelijke hoek", "mode": "Berekening", "show_on_map": "Toon op kaart" } diff --git a/homeassistant/components/netatmo/translations/no.json b/homeassistant/components/netatmo/translations/no.json index 387dbe7b26cf3c..9e3e24d5771b66 100644 --- a/homeassistant/components/netatmo/translations/no.json +++ b/homeassistant/components/netatmo/translations/no.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "borte", + "hg": "frostvakt", + "schedule": "Tidsplan" + }, + "trigger_type": { + "alarm_started": "{entity_name} oppdaget en alarm", + "animal": "{entity_name} oppdaget et dyr", + "cancel_set_point": "{entity_name} har gjenopptatt tidsplanen", + "human": "{entity_name} oppdaget et menneske", + "movement": "{entity_name} oppdaget bevegelse", + "outdoor": "{entity_name} oppdaget en utend\u00f8rs hendelse", + "person": "{entity_name} oppdaget en person", + "person_away": "{entity_name} oppdaget at en person har forlatt", + "set_point": "M\u00e5ltemperatur {entity_name} angis manuelt", + "therm_mode": "{entity_name} byttet til \"{subtype}\"", + "turned_off": "{entity_name} sl\u00e5tt av", + "turned_on": "{entity_name} sl\u00e5tt p\u00e5", + "vehicle": "{entity_name} oppdaget et kj\u00f8ret\u00f8y" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index b2022a01824de1..65d4f417b9f9ec 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -5,9 +5,9 @@ }, "error": { "cannot_connect": "Failed to connect", - "unknown": "Unexpected error", + "invalid_pin": "Invalid PIN", "pairing_failure": "Unable to pair: {error_id}", - "invalid_pin": "Invalid PIN" + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json index c77ef726411ce6..9953df9c272940 100644 --- a/homeassistant/components/philips_js/translations/et.json +++ b/homeassistant/components/philips_js/translations/et.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u00dchendamine nurjus", + "invalid_pin": "Vale PIN kood", + "pairing_failure": "Sidumine nurjus: {error_id}", "unknown": "Ootamatu t\u00f5rge" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index 02411ea999f0c3..119e4c641afdf0 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze RainMachine controller is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "invalid_auth": "Ongeldige authenticatie" diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json index 34bcb4ab98a491..97d0d454a4f0c2 100644 --- a/homeassistant/components/risco/translations/nl.json +++ b/homeassistant/components/risco/translations/nl.json @@ -30,8 +30,8 @@ }, "init": { "data": { - "code_arm_required": "Pincode vereist om in te schakelen", - "code_disarm_required": "Pincode vereist om uit te schakelen" + "code_arm_required": "PIN-code vereist om in te schakelen", + "code_disarm_required": "PIN-code vereist om uit te schakelen" }, "title": "Configureer opties" }, diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 37437dfe8368fa..64b7f1b73f767f 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -9,6 +9,9 @@ }, "step": { "user": { + "data": { + "dsn": "DSN" + }, "description": "Voer uw Sentry DSN in", "title": "Sentry" } diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index b285b288525a1d..d3196c591cb192 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Dit SimpliSafe-account is al in gebruik." + "already_configured": "Dit SimpliSafe-account is al in gebruik.", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "identifier_exists": "Account bestaat al", @@ -13,7 +14,8 @@ "data": { "password": "Wachtwoord" }, - "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen." + "description": "Uw toegangstoken is verlopen of ingetrokken. Voer uw wachtwoord in om uw account opnieuw te koppelen.", + "title": "Verifieer de integratie opnieuw" }, "user": { "data": { diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index a63320919c6dcf..b0ae5c9d3ad6ff 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -8,7 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, - "flow_title": "Somfy MyLink {mac} ( {ip} )", + "flow_title": "Somfy MyLink {mac} ({ip})", "step": { "user": { "data": { diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index bdc86919f7466b..46b18857fe8b6a 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -15,7 +15,7 @@ }, "reauth_confirm": { "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", - "title": "Verifieer opnieuw met Spotify" + "title": "Verifieer de integratie opnieuw" } } } diff --git a/homeassistant/components/subaru/translations/it.json b/homeassistant/components/subaru/translations/it.json new file mode 100644 index 00000000000000..6dbb0702f46682 --- /dev/null +++ b/homeassistant/components/subaru/translations/it.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi" + }, + "error": { + "bad_pin_format": "Il PIN deve essere di 4 cifre", + "cannot_connect": "Impossibile connettersi", + "incorrect_pin": "PIN errato", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Inserisci il tuo PIN MySubaru\nNOTA: tutti i veicoli nell'account devono avere lo stesso PIN", + "title": "Configurazione Subaru Starlink" + }, + "user": { + "data": { + "country": "Seleziona il paese", + "password": "Password", + "username": "Nome utente" + }, + "description": "Inserisci le tue credenziali MySubaru\nNOTA: la configurazione iniziale pu\u00f2 richiedere fino a 30 secondi", + "title": "Configurazione Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Abilita l'interrogazione del veicolo" + }, + "description": "Quando abilitata, l'interrogazione del veicolo invier\u00e0 un comando remoto al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo, i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", + "title": "Opzioni Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index b1beb4058bc9d3..799e19ea37104f 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -8,6 +8,11 @@ }, "flow_title": "Samsung SyncThru Printer: {name}", "step": { + "confirm": { + "data": { + "name": "Naam" + } + }, "user": { "data": { "name": "Naam", diff --git a/homeassistant/components/totalconnect/translations/it.json b/homeassistant/components/totalconnect/translations/it.json index 2a12d00f57d3d0..18ecf6483108d4 100644 --- a/homeassistant/components/totalconnect/translations/it.json +++ b/homeassistant/components/totalconnect/translations/it.json @@ -1,12 +1,25 @@ { "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" }, "error": { - "invalid_auth": "Autenticazione non valida" + "invalid_auth": "Autenticazione non valida", + "usercode": "Codice utente non valido per questo utente in questa posizione" }, "step": { + "locations": { + "data": { + "location": "Posizione" + }, + "description": "Immettere il codice utente per questo utente in questa posizione", + "title": "Codici utente posizione" + }, + "reauth_confirm": { + "description": "Total Connect deve autenticare nuovamente il tuo account", + "title": "Autenticare nuovamente l'integrazione" + }, "user": { "data": { "password": "Password", diff --git a/homeassistant/components/volumio/translations/nl.json b/homeassistant/components/volumio/translations/nl.json index 9179418def99c0..9e11dbad82ba3b 100644 --- a/homeassistant/components/volumio/translations/nl.json +++ b/homeassistant/components/volumio/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index da03cc43b4b317..ae205d79aef701 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -10,7 +10,16 @@ "heizung": "Verwarmen", "initialisierung": "Initialisatie", "kalibration": "Kalibratie", - "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus" + "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "permanent": "Permanent", + "standby": "Stand-by", + "start": "Start", + "storung": "Fout", + "test": "Test", + "tpw": "TPW", + "urlaubsmodus": "Vakantiemodus", + "ventilprufung": "Kleptest", + "warmwasser": "DHW" } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index 68202e1631e5fe..aa48ba7cfa8022 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Indirizzo IP", + "model": "Modello del dispositivo (opzionale)", "name": "Nome del dispositivo", "token": "Token API" }, diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 3ea12e3a465785..66209e61ee67f5 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -8,6 +8,7 @@ "cannot_connect": "Kan geen verbinding maken", "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" }, + "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { diff --git a/homeassistant/components/zoneminder/translations/nl.json b/homeassistant/components/zoneminder/translations/nl.json index f4f071d9097fc6..8aed5085391b50 100644 --- a/homeassistant/components/zoneminder/translations/nl.json +++ b/homeassistant/components/zoneminder/translations/nl.json @@ -23,7 +23,7 @@ "password": "Wachtwoord", "path": "ZM-pad", "path_zms": "ZMS-pad", - "ssl": "Gebruik SSL voor verbindingen met ZoneMinder", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Verifieer SSL-certificaat" }, diff --git a/homeassistant/components/zwave_js/translations/it.json b/homeassistant/components/zwave_js/translations/it.json index 5f0868a3f743b4..abe0ab066fb111 100644 --- a/homeassistant/components/zwave_js/translations/it.json +++ b/homeassistant/components/zwave_js/translations/it.json @@ -6,6 +6,7 @@ "addon_install_failed": "Impossibile installare il componente aggiuntivo Z-Wave JS.", "addon_missing_discovery_info": "Informazioni sul rilevamento del componente aggiuntivo Z-Wave JS mancanti.", "addon_set_config_failed": "Impossibile impostare la configurazione di Z-Wave JS.", + "addon_start_failed": "Impossibile avviare il componente aggiuntivo Z-Wave JS.", "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi" @@ -17,7 +18,8 @@ "unknown": "Errore imprevisto" }, "progress": { - "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti." + "install_addon": "Attendi il termine dell'installazione del componente aggiuntivo Z-Wave JS. Questa operazione pu\u00f2 richiedere diversi minuti.", + "start_addon": "Attendi il completamento dell'avvio del componente aggiuntivo Z-Wave JS. L'operazione potrebbe richiedere alcuni secondi." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Desideri utilizzare il componente aggiuntivo Z-Wave JS Supervisor?", "title": "Seleziona il metodo di connessione" }, + "start_addon": { + "title": "Il componente aggiuntivo Z-Wave JS si sta avviando." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index 7f46c02ece5ecc..c15cfd26f31c78 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -5,7 +5,7 @@ "addon_info_failed": "Ophalen van Z-Wave JS add-on-info is mislukt.", "addon_install_failed": "Kan de Z-Wave JS add-on niet installeren.", "addon_missing_discovery_info": "De Z-Wave JS addon mist ontdekkings informatie", - "addon_set_config_failed": "Instellen van de Z-Wave JS-configuratie is mislukt.", + "addon_set_config_failed": "Instellen van de Z-Wave JS configuratie is mislukt.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" From d81155327ad33bfbbc3465f7631b0a5f3b497f4e Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 28 Feb 2021 00:07:08 +0000 Subject: [PATCH 0958/1818] [ci skip] Translation update --- .../components/climacell/translations/no.json | 2 +- .../faa_delays/translations/ca.json | 21 ++++++++++++++++++ .../faa_delays/translations/no.json | 2 +- .../components/litejet/translations/fr.json | 3 +++ .../components/litejet/translations/no.json | 2 +- .../components/netatmo/translations/ca.json | 22 +++++++++++++++++++ .../philips_js/translations/ca.json | 2 ++ .../philips_js/translations/fr.json | 2 ++ .../philips_js/translations/no.json | 2 ++ .../philips_js/translations/ru.json | 2 ++ .../components/subaru/translations/fr.json | 15 ++++++++++++- .../components/subaru/translations/no.json | 2 +- .../totalconnect/translations/fr.json | 8 +++++-- .../xiaomi_miio/translations/fr.json | 1 + .../components/zwave_js/translations/no.json | 4 ++-- 15 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/faa_delays/translations/ca.json diff --git a/homeassistant/components/climacell/translations/no.json b/homeassistant/components/climacell/translations/no.json index af07ce716d0917..d59f55905182f0 100644 --- a/homeassistant/components/climacell/translations/no.json +++ b/homeassistant/components/climacell/translations/no.json @@ -23,7 +23,7 @@ "init": { "data": { "forecast_types": "Prognosetype(r)", - "timestep": "Min. Mellom NowCast Prognoser" + "timestep": "Min. mellom NowCast prognoser" }, "description": "Hvis du velger \u00e5 aktivere \u00abnowcast\u00bb -varselenheten, kan du konfigurere antall minutter mellom hver prognose. Antall angitte prognoser avhenger av antall minutter som er valgt mellom prognosene.", "title": "Oppdater ClimaCell Alternativer" diff --git a/homeassistant/components/faa_delays/translations/ca.json b/homeassistant/components/faa_delays/translations/ca.json new file mode 100644 index 00000000000000..e7e600f7f075fa --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Aeroport ja est\u00e0 configurat." + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_airport": "Codi d'aeroport inv\u00e0lid", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "id": "Aeroport" + }, + "description": "Introdueix codi d'un aeroport dels EUA en format IATA", + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/no.json b/homeassistant/components/faa_delays/translations/no.json index c481f90bf75068..5a5aac723ad71d 100644 --- a/homeassistant/components/faa_delays/translations/no.json +++ b/homeassistant/components/faa_delays/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Denne flyplassen er allerede konfigurert." + "already_configured": "Denne flyplassen er allerede konfigurert" }, "error": { "cannot_connect": "Tilkobling mislyktes", diff --git a/homeassistant/components/litejet/translations/fr.json b/homeassistant/components/litejet/translations/fr.json index 455ba7fdc0c569..89459d1829f3e0 100644 --- a/homeassistant/components/litejet/translations/fr.json +++ b/homeassistant/components/litejet/translations/fr.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, + "error": { + "open_failed": "Impossible d'ouvrir le port s\u00e9rie sp\u00e9cifi\u00e9." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/litejet/translations/no.json b/homeassistant/components/litejet/translations/no.json index 26ccd3335468b5..d3206ca2897c74 100644 --- a/homeassistant/components/litejet/translations/no.json +++ b/homeassistant/components/litejet/translations/no.json @@ -4,7 +4,7 @@ "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "open_failed": "Kan ikke \u00e5pne den angitte serielle porten." + "open_failed": "Kan ikke \u00e5pne den angitte serielle porten" }, "step": { "user": { diff --git a/homeassistant/components/netatmo/translations/ca.json b/homeassistant/components/netatmo/translations/ca.json index a6b8b5c2b82443..809223a04ae3af 100644 --- a/homeassistant/components/netatmo/translations/ca.json +++ b/homeassistant/components/netatmo/translations/ca.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "a fora", + "hg": "protecci\u00f3 contra gelades", + "schedule": "programaci\u00f3" + }, + "trigger_type": { + "alarm_started": "{entity_name} ha detectat una alarma", + "animal": "{entity_name} ha detectat un animal", + "cancel_set_point": "{entity_name} ha repr\u00e8s la programaci\u00f3", + "human": "{entity_name} ha detectat un hum\u00e0", + "movement": "{entity_name} ha detectat moviment", + "outdoor": "{entity_name} ha detectat un esdeveniment a fora", + "person": "{entity_name} ha detectat una persona", + "person_away": "{entity_name} ha detectat una marxant", + "set_point": "Temperatura objectiu {entity_name} configurada manualment", + "therm_mode": "{entity_name} ha canviar a \"{subtype}\"", + "turned_off": "{entity_name} s'ha apagat", + "turned_on": "{entity_name} s'ha engegat", + "vehicle": "{entity_name} ha detectat un vehicle" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index 505a6472ea823a..980bb6800e1719 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_pin": "PIN inv\u00e0lid", + "pairing_failure": "No s'ha pogut vincular: {error_id}", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 9ae65c18fa408e..25c28edcf1da04 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", + "invalid_pin": "NIP invalide", + "pairing_failure": "Association impossible: {error_id}", "unknown": "Erreur inattendue" }, "step": { diff --git a/homeassistant/components/philips_js/translations/no.json b/homeassistant/components/philips_js/translations/no.json index dadf15fb67adfb..a9c647a644be22 100644 --- a/homeassistant/components/philips_js/translations/no.json +++ b/homeassistant/components/philips_js/translations/no.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes", + "invalid_pin": "Ugyldig PIN", + "pairing_failure": "Kan ikke parre: {error_id}", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/philips_js/translations/ru.json b/homeassistant/components/philips_js/translations/ru.json index 9306ecf7a2958c..83511ff246aae0 100644 --- a/homeassistant/components/philips_js/translations/ru.json +++ b/homeassistant/components/philips_js/translations/ru.json @@ -5,6 +5,8 @@ }, "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_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434.", + "pairing_failure": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435: {error_id}.", "unknown": "\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/subaru/translations/fr.json b/homeassistant/components/subaru/translations/fr.json index a6bf6902aabd11..25544534297718 100644 --- a/homeassistant/components/subaru/translations/fr.json +++ b/homeassistant/components/subaru/translations/fr.json @@ -24,7 +24,20 @@ "country": "Choisissez le pays", "password": "Mot de passe", "username": "Nom d'utilisateur" - } + }, + "description": "Veuillez saisir vos identifiants MySubaru\n REMARQUE: la configuration initiale peut prendre jusqu'\u00e0 30 secondes", + "title": "Configuration de Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Activer l'interrogation des v\u00e9hicules" + }, + "description": "Lorsqu'elle est activ\u00e9e, l'interrogation du v\u00e9hicule enverra une commande \u00e0 distance \u00e0 votre v\u00e9hicule toutes les 2 heures pour obtenir de nouvelles donn\u00e9es de capteur. Sans interrogation du v\u00e9hicule, les nouvelles donn\u00e9es de capteur ne sont re\u00e7ues que lorsque le v\u00e9hicule pousse automatiquement les donn\u00e9es (normalement apr\u00e8s l'arr\u00eat du moteur).", + "title": "Options de Subaru Starlink" } } } diff --git a/homeassistant/components/subaru/translations/no.json b/homeassistant/components/subaru/translations/no.json index f1a263d5cb4bd8..25b0f7bec295b2 100644 --- a/homeassistant/components/subaru/translations/no.json +++ b/homeassistant/components/subaru/translations/no.json @@ -37,7 +37,7 @@ "update_enabled": "Aktiver polling av kj\u00f8ret\u00f8y" }, "description": "N\u00e5r dette er aktivert, sender polling av kj\u00f8ret\u00f8y en fjernkommando til kj\u00f8ret\u00f8yet annenhver time for \u00e5 skaffe nye sensordata. Uten kj\u00f8ret\u00f8yoppm\u00e5ling mottas nye sensordata bare n\u00e5r kj\u00f8ret\u00f8yet automatisk skyver data (normalt etter motorstans).", - "title": "Subaru Starlink Alternativer" + "title": "Subaru Starlink alternativer" } } } diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index 40ca767b4acbec..b46bf12796347a 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -5,15 +5,19 @@ "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { - "invalid_auth": "Authentification invalide" + "invalid_auth": "Authentification invalide", + "usercode": "Code d'utilisateur non valide pour cet utilisateur \u00e0 cet emplacement" }, "step": { "locations": { "data": { "location": "Emplacement" - } + }, + "description": "Saisissez le code d'utilisateur de cet utilisateur \u00e0 cet emplacement", + "title": "Codes d'utilisateur de l'emplacement" }, "reauth_confirm": { + "description": "Total Connect doit r\u00e9-authentifier votre compte", "title": "R\u00e9-authentifier l'int\u00e9gration" }, "user": { diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json index 10ce9972818d1c..30def127e7a66b 100644 --- a/homeassistant/components/xiaomi_miio/translations/fr.json +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -14,6 +14,7 @@ "device": { "data": { "host": "Adresse IP", + "model": "Mod\u00e8le d'appareil (facultatif)", "name": "Nom de l'appareil", "token": "Jeton d'API" }, diff --git a/homeassistant/components/zwave_js/translations/no.json b/homeassistant/components/zwave_js/translations/no.json index acd049fc5619a0..f893d2d7684c35 100644 --- a/homeassistant/components/zwave_js/translations/no.json +++ b/homeassistant/components/zwave_js/translations/no.json @@ -19,7 +19,7 @@ }, "progress": { "install_addon": "Vent mens installasjonen av Z-Wave JS-tillegg er ferdig. Dette kan ta flere minutter.", - "start_addon": "Vent mens Z-Wave JS-tilleggsstarten er fullf\u00f8rt. Dette kan ta noen sekunder." + "start_addon": "Vent mens Z-Wave JS-tillegget er ferdig startet. Dette kan ta noen sekunder." }, "step": { "configure_addon": { @@ -48,7 +48,7 @@ "title": "Velg tilkoblingsmetode" }, "start_addon": { - "title": "Z-Wave JS-tillegget starter." + "title": "Z-Wave JS-tillegget starter" }, "user": { "data": { From 13516aa90c79b9a557af620efe1c40c28adf8455 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 1 Mar 2021 00:09:01 +0000 Subject: [PATCH 0959/1818] [ci skip] Translation update --- .../components/aemet/translations/de.json | 19 ++++++++++++ .../components/airly/translations/ca.json | 4 ++- .../components/airly/translations/en.json | 4 ++- .../components/airly/translations/et.json | 4 ++- .../components/airly/translations/ru.json | 4 ++- .../airly/translations/zh-Hant.json | 4 ++- .../components/asuswrt/translations/de.json | 24 +++++++++++++++ .../awair/translations/zh-Hant.json | 10 +++---- .../blink/translations/zh-Hant.json | 2 +- .../components/bond/translations/zh-Hant.json | 4 +-- .../components/climacell/translations/de.json | 19 ++++++++++++ .../components/climacell/translations/he.json | 17 +++++++++++ .../cloudflare/translations/zh-Hant.json | 4 +-- .../components/econet/translations/de.json | 21 ++++++++++++++ .../faa_delays/translations/de.json | 8 +++++ .../faa_delays/translations/he.json | 18 ++++++++++++ .../fireservicerota/translations/zh-Hant.json | 2 +- .../components/fritzbox/translations/de.json | 2 +- .../components/habitica/translations/de.json | 17 +++++++++++ .../components/homekit/translations/he.json | 10 +++++++ .../huisbaasje/translations/de.json | 21 ++++++++++++++ .../hyperion/translations/zh-Hant.json | 16 +++++----- .../juicenet/translations/zh-Hant.json | 4 +-- .../keenetic_ndms2/translations/de.json | 21 ++++++++++++++ .../components/kmtronic/translations/de.json | 21 ++++++++++++++ .../components/litejet/translations/de.json | 14 +++++++++ .../components/litejet/translations/he.json | 11 +++++++ .../litterrobot/translations/de.json | 20 +++++++++++++ .../lutron_caseta/translations/de.json | 7 +++++ .../components/lyric/translations/de.json | 16 ++++++++++ .../components/mazda/translations/de.json | 29 +++++++++++++++++++ .../media_player/translations/de.json | 7 +++++ .../melcloud/translations/zh-Hant.json | 2 +- .../components/mullvad/translations/de.json | 21 ++++++++++++++ .../components/mullvad/translations/he.json | 21 ++++++++++++++ .../components/mysensors/translations/de.json | 16 ++++++++++ .../components/netatmo/translations/he.json | 11 +++++++ .../nightscout/translations/et.json | 2 +- .../components/nuki/translations/de.json | 18 ++++++++++++ .../components/nuki/translations/zh-Hant.json | 2 +- .../philips_js/translations/de.json | 20 +++++++++++++ .../philips_js/translations/he.json | 7 +++++ .../philips_js/translations/zh-Hant.json | 2 ++ .../components/plaato/translations/de.json | 1 + .../plaato/translations/zh-Hant.json | 8 ++--- .../components/plex/translations/zh-Hant.json | 8 ++--- .../point/translations/zh-Hant.json | 2 +- .../components/powerwall/translations/de.json | 7 +++-- .../components/powerwall/translations/et.json | 2 +- .../translations/de.json | 20 +++++++++++++ .../components/roku/translations/de.json | 1 + .../simplisafe/translations/zh-Hant.json | 2 +- .../smartthings/translations/et.json | 2 +- .../smartthings/translations/zh-Hant.json | 12 ++++---- .../components/smarttub/translations/de.json | 20 +++++++++++++ .../components/subaru/translations/de.json | 28 ++++++++++++++++++ .../components/tesla/translations/de.json | 4 +++ .../tibber/translations/zh-Hant.json | 6 ++-- .../totalconnect/translations/de.json | 11 ++++++- .../components/tuya/translations/et.json | 4 +-- .../components/unifi/translations/de.json | 4 ++- .../vilfo/translations/zh-Hant.json | 4 +-- .../vizio/translations/zh-Hant.json | 6 ++-- .../xiaomi_miio/translations/de.json | 7 +++++ .../xiaomi_miio/translations/en.json | 2 +- .../xiaomi_miio/translations/zh-Hant.json | 8 ++--- .../components/zwave_js/translations/de.json | 14 ++++++++- 67 files changed, 621 insertions(+), 68 deletions(-) create mode 100644 homeassistant/components/aemet/translations/de.json create mode 100644 homeassistant/components/asuswrt/translations/de.json create mode 100644 homeassistant/components/climacell/translations/de.json create mode 100644 homeassistant/components/climacell/translations/he.json create mode 100644 homeassistant/components/econet/translations/de.json create mode 100644 homeassistant/components/faa_delays/translations/de.json create mode 100644 homeassistant/components/faa_delays/translations/he.json create mode 100644 homeassistant/components/habitica/translations/de.json create mode 100644 homeassistant/components/huisbaasje/translations/de.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/de.json create mode 100644 homeassistant/components/kmtronic/translations/de.json create mode 100644 homeassistant/components/litejet/translations/de.json create mode 100644 homeassistant/components/litejet/translations/he.json create mode 100644 homeassistant/components/litterrobot/translations/de.json create mode 100644 homeassistant/components/lyric/translations/de.json create mode 100644 homeassistant/components/mazda/translations/de.json create mode 100644 homeassistant/components/mullvad/translations/de.json create mode 100644 homeassistant/components/mullvad/translations/he.json create mode 100644 homeassistant/components/mysensors/translations/de.json create mode 100644 homeassistant/components/netatmo/translations/he.json create mode 100644 homeassistant/components/nuki/translations/de.json create mode 100644 homeassistant/components/philips_js/translations/de.json create mode 100644 homeassistant/components/philips_js/translations/he.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/de.json create mode 100644 homeassistant/components/smarttub/translations/de.json create mode 100644 homeassistant/components/subaru/translations/de.json diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json new file mode 100644 index 00000000000000..d7254aea92f600 --- /dev/null +++ b/homeassistant/components/aemet/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Standort ist bereits konfiguriert" + }, + "error": { + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 95400de23b438b..e76cec94f4ccdc 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Servidor d'Airly accessible" + "can_reach_server": "Servidor d'Airly accessible", + "requests_per_day": "Sol\u00b7licituds per dia permeses", + "requests_remaining": "Sol\u00b7licituds permeses restants" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/en.json b/homeassistant/components/airly/translations/en.json index 720f68f8349c9d..0a5426c87d845a 100644 --- a/homeassistant/components/airly/translations/en.json +++ b/homeassistant/components/airly/translations/en.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Reach Airly server" + "can_reach_server": "Reach Airly server", + "requests_per_day": "Allowed requests per day", + "requests_remaining": "Remaining allowed requests" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index 8cbfd138257a91..c5c9359c67fd63 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u00dchendus Airly serveriga" + "can_reach_server": "\u00dchendus Airly serveriga", + "requests_per_day": "Lubatud taotlusi p\u00e4evas", + "requests_remaining": "J\u00e4\u00e4nud lubatud taotlusi" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ru.json b/homeassistant/components/airly/translations/ru.json index b1469af787e617..41ca90a8c027d1 100644 --- a/homeassistant/components/airly/translations/ru.json +++ b/homeassistant/components/airly/translations/ru.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Airly" + "can_reach_server": "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Airly", + "requests_per_day": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u0434\u0435\u043d\u044c", + "requests_remaining": "\u0421\u0447\u0451\u0442\u0447\u0438\u043a \u043e\u0441\u0442\u0430\u0432\u0448\u0438\u0445\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/zh-Hant.json b/homeassistant/components/airly/translations/zh-Hant.json index 4d60b158c4c276..19ef2ae7532cde 100644 --- a/homeassistant/components/airly/translations/zh-Hant.json +++ b/homeassistant/components/airly/translations/zh-Hant.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "\u9023\u7dda Airly \u4f3a\u670d\u5668" + "can_reach_server": "\u9023\u7dda Airly \u4f3a\u670d\u5668", + "requests_per_day": "\u6bcf\u65e5\u5141\u8a31\u7684\u8acb\u6c42", + "requests_remaining": "\u5176\u9918\u5141\u8a31\u7684\u8acb\u6c42" } } } \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json new file mode 100644 index 00000000000000..433bf17b8143a9 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Modus", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/zh-Hant.json b/homeassistant/components/awair/translations/zh-Hant.json index 11fe9ff88b39aa..0bd7749c65fb20 100644 --- a/homeassistant/components/awair/translations/zh-Hant.json +++ b/homeassistant/components/awair/translations/zh-Hant.json @@ -6,23 +6,23 @@ "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "reauth": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "email": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u8acb\u91cd\u65b0\u8f38\u5165 Awair \u958b\u767c\u8005\u5b58\u53d6\u5bc6\u9470\u3002" + "description": "\u8acb\u91cd\u65b0\u8f38\u5165 Awair \u958b\u767c\u8005\u5b58\u53d6\u6b0a\u6756\u3002" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "email": "\u96fb\u5b50\u90f5\u4ef6" }, - "description": "\u5fc5\u9808\u5148\u8a3b\u518a Awair \u958b\u767c\u8005\u5b58\u53d6\u5bc6\u9470\uff1ahttps://developer.getawair.com/onboard/login" + "description": "\u5fc5\u9808\u5148\u8a3b\u518a Awair \u958b\u767c\u8005\u5b58\u53d6\u6b0a\u6756\uff1ahttps://developer.getawair.com/onboard/login" } } } diff --git a/homeassistant/components/blink/translations/zh-Hant.json b/homeassistant/components/blink/translations/zh-Hant.json index 3d05dc82abcbbb..d2c42bf5531682 100644 --- a/homeassistant/components/blink/translations/zh-Hant.json +++ b/homeassistant/components/blink/translations/zh-Hant.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/bond/translations/zh-Hant.json b/homeassistant/components/bond/translations/zh-Hant.json index 1c5327dc6627ed..8bb8e178869aa9 100644 --- a/homeassistant/components/bond/translations/zh-Hant.json +++ b/homeassistant/components/bond/translations/zh-Hant.json @@ -13,13 +13,13 @@ "step": { "confirm": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" } } diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json new file mode 100644 index 00000000000000..f18197e1ccad1a --- /dev/null +++ b/homeassistant/components/climacell/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "name": "Name" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/he.json b/homeassistant/components/climacell/translations/he.json new file mode 100644 index 00000000000000..81a4b5c1fce612 --- /dev/null +++ b/homeassistant/components/climacell/translations/he.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "latitude": "\u05e7\u05d5 \u05e8\u05d5\u05d7\u05d1", + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da", + "name": "\u05e9\u05dd" + } + } + } + } +} \ 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 1be70def0344e0..d9a05269748f5f 100644 --- a/homeassistant/components/cloudflare/translations/zh-Hant.json +++ b/homeassistant/components/cloudflare/translations/zh-Hant.json @@ -19,9 +19,9 @@ }, "user": { "data": { - "api_token": "API \u5bc6\u9470" + "api_token": "API \u6b0a\u6756" }, - "description": "\u6b64\u6574\u5408\u9700\u8981\u5e33\u865f\u4e2d\u6240\u6709\u5340\u57df Zone:Zone:Read \u8207 Zone:DNS:Edit \u6b0a\u9650 API \u5bc6\u9470\u3002", + "description": "\u6b64\u6574\u5408\u9700\u8981\u5e33\u865f\u4e2d\u6240\u6709\u5340\u57df Zone:Zone:Read \u8207 Zone:DNS:Edit \u6b0a\u9650 API \u6b0a\u6756\u3002", "title": "\u9023\u7dda\u81f3 Cloudflare" }, "zone": { diff --git a/homeassistant/components/econet/translations/de.json b/homeassistant/components/econet/translations/de.json new file mode 100644 index 00000000000000..854d61f1790b48 --- /dev/null +++ b/homeassistant/components/econet/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/de.json b/homeassistant/components/faa_delays/translations/de.json new file mode 100644 index 00000000000000..72b837c862ca05 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/de.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/he.json b/homeassistant/components/faa_delays/translations/he.json new file mode 100644 index 00000000000000..af8d410eb18c34 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "\u05e0\u05de\u05dc \u05ea\u05e2\u05d5\u05e4\u05d4 \u05d6\u05d4 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\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": { + "id": "\u05e0\u05de\u05dc \u05ea\u05e2\u05d5\u05e4\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/zh-Hant.json b/homeassistant/components/fireservicerota/translations/zh-Hant.json index af3cba40dc6785..8e5f4d9f20db82 100644 --- a/homeassistant/components/fireservicerota/translations/zh-Hant.json +++ b/homeassistant/components/fireservicerota/translations/zh-Hant.json @@ -15,7 +15,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u8a8d\u8b49\u5bc6\u9470\u5df2\u7d93\u5931\u6548\uff0c\u8acb\u767b\u5165\u91cd\u65b0\u65b0\u589e\u3002" + "description": "\u8a8d\u8b49\u6b0a\u6756\u5df2\u7d93\u5931\u6548\uff0c\u8acb\u767b\u5165\u91cd\u65b0\u65b0\u589e\u3002" }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 8e79076bda6adc..16263722482430 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -24,7 +24,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name} ." + "description": "Aktualisiere deine Anmeldeinformationen f\u00fcr {name}." }, "user": { "data": { diff --git a/homeassistant/components/habitica/translations/de.json b/homeassistant/components/habitica/translations/de.json new file mode 100644 index 00000000000000..04f985946fb509 --- /dev/null +++ b/homeassistant/components/habitica/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_credentials": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "url": "URL" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index 87ad743dca5be9..6acebca0ca47c0 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -1,6 +1,16 @@ { "options": { "step": { + "include_exclude": { + "data": { + "mode": "\u05de\u05e6\u05d1" + } + }, + "init": { + "data": { + "mode": "\u05de\u05e6\u05d1" + } + }, "yaml": { "description": "\u05d9\u05e9\u05d5\u05ea \u05d6\u05d5 \u05e0\u05e9\u05dc\u05d8\u05ea \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea YAML" } diff --git a/homeassistant/components/huisbaasje/translations/de.json b/homeassistant/components/huisbaasje/translations/de.json new file mode 100644 index 00000000000000..ca3f90536d40f1 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "connection_exception": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unauthenticated_exception": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/zh-Hant.json b/homeassistant/components/hyperion/translations/zh-Hant.json index ed003131bf296c..bb8eacd537651e 100644 --- a/homeassistant/components/hyperion/translations/zh-Hant.json +++ b/homeassistant/components/hyperion/translations/zh-Hant.json @@ -3,8 +3,8 @@ "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", - "auth_new_token_not_granted_error": "\u65b0\u5275\u5bc6\u9470\u672a\u7372\u5f97 Hyperion UI \u6838\u51c6", - "auth_new_token_not_work_error": "\u4f7f\u7528\u65b0\u5275\u5bc6\u9470\u8a8d\u8b49\u5931\u6557", + "auth_new_token_not_granted_error": "\u65b0\u5275\u6b0a\u6756\u672a\u7372\u5f97 Hyperion UI \u6838\u51c6", + "auth_new_token_not_work_error": "\u4f7f\u7528\u65b0\u5275\u6b0a\u6756\u8a8d\u8b49\u5931\u6557", "auth_required_error": "\u7121\u6cd5\u5224\u5b9a\u662f\u5426\u9700\u8981\u9a57\u8b49", "cannot_connect": "\u9023\u7dda\u5931\u6557", "no_id": "Hyperion Ambilight \u5be6\u9ad4\u672a\u56de\u5831\u5176 ID", @@ -12,13 +12,13 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548" + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548" }, "step": { "auth": { "data": { - "create_token": "\u81ea\u52d5\u65b0\u5275\u5bc6\u9470", - "token": "\u6216\u63d0\u4f9b\u73fe\u6709\u5bc6\u9470" + "create_token": "\u81ea\u52d5\u65b0\u5275\u6b0a\u6756", + "token": "\u6216\u63d0\u4f9b\u73fe\u6709\u6b0a\u6756" }, "description": "\u8a2d\u5b9a Hyperion Ambilight \u4f3a\u670d\u5668\u8a8d\u8b49" }, @@ -27,11 +27,11 @@ "title": "\u78ba\u8a8d\u9644\u52a0 Hyperion Ambilight \u670d\u52d9" }, "create_token": { - "description": "\u9ede\u9078\u4e0b\u65b9 **\u50b3\u9001** \u4ee5\u8acb\u6c42\u65b0\u8a8d\u8b49\u5bc6\u9470\u3002\u5c07\u6703\u91cd\u65b0\u5c0e\u5411\u81f3 Hyperion UI \u4ee5\u6838\u51c6\u8981\u6c42\u3002\u8acb\u78ba\u8a8d\u986f\u793a ID \u70ba \"{auth_id}\"", - "title": "\u81ea\u52d5\u65b0\u5275\u8a8d\u8b49\u5bc6\u9470" + "description": "\u9ede\u9078\u4e0b\u65b9 **\u50b3\u9001** \u4ee5\u8acb\u6c42\u65b0\u8a8d\u8b49\u6b0a\u6756\u3002\u5c07\u6703\u91cd\u65b0\u5c0e\u5411\u81f3 Hyperion UI \u4ee5\u6838\u51c6\u8981\u6c42\u3002\u8acb\u78ba\u8a8d\u986f\u793a ID \u70ba \"{auth_id}\"", + "title": "\u81ea\u52d5\u65b0\u5275\u8a8d\u8b49\u6b0a\u6756" }, "create_token_external": { - "title": "\u63a5\u53d7 Hyperion UI \u4e2d\u7684\u65b0\u5bc6\u9470" + "title": "\u63a5\u53d7 Hyperion UI \u4e2d\u7684\u65b0\u6b0a\u6756" }, "user": { "data": { diff --git a/homeassistant/components/juicenet/translations/zh-Hant.json b/homeassistant/components/juicenet/translations/zh-Hant.json index 815edb1fb2710b..f310babfd80aa1 100644 --- a/homeassistant/components/juicenet/translations/zh-Hant.json +++ b/homeassistant/components/juicenet/translations/zh-Hant.json @@ -11,9 +11,9 @@ "step": { "user": { "data": { - "api_token": "API \u5bc6\u9470" + "api_token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u5bc6\u9470\u3002", + "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u6b0a\u6756\u3002", "title": "\u9023\u7dda\u81f3 JuiceNet" } } diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json new file mode 100644 index 00000000000000..71ce0154639b81 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/de.json b/homeassistant/components/kmtronic/translations/de.json new file mode 100644 index 00000000000000..625c7372347a61 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/de.json b/homeassistant/components/litejet/translations/de.json new file mode 100644 index 00000000000000..492314e5cc6d32 --- /dev/null +++ b/homeassistant/components/litejet/translations/de.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/he.json b/homeassistant/components/litejet/translations/he.json new file mode 100644 index 00000000000000..a06c89f1d2ac72 --- /dev/null +++ b/homeassistant/components/litejet/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u05e4\u05d5\u05e8\u05d8" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/de.json b/homeassistant/components/litterrobot/translations/de.json new file mode 100644 index 00000000000000..0eee2778d05365 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index 13f8c6bd800db1..b6aacf2d0ef02e 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -6,6 +6,13 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "host": "Host" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/de.json b/homeassistant/components/lyric/translations/de.json new file mode 100644 index 00000000000000..5bab6ed132bf52 --- /dev/null +++ b/homeassistant/components/lyric/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Zeit\u00fcberschreitung beim Erstellen der Authorisierungs-URL.", + "missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen." + }, + "create_entry": { + "default": "Erfolgreich authentifiziert" + }, + "step": { + "pick_implementation": { + "title": "W\u00e4hle die Authentifizierungsmethode" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/de.json b/homeassistant/components/mazda/translations/de.json new file mode 100644 index 00000000000000..4e23becb8af642 --- /dev/null +++ b/homeassistant/components/mazda/translations/de.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "reauth": { + "data": { + "email": "E-Mail", + "password": "Passwort", + "region": "Region" + } + }, + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort", + "region": "Region" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/de.json b/homeassistant/components/media_player/translations/de.json index a7f25fa9d7cddd..4909c85d053b80 100644 --- a/homeassistant/components/media_player/translations/de.json +++ b/homeassistant/components/media_player/translations/de.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} ist eingeschaltet", "is_paused": "{entity_name} ist pausiert", "is_playing": "{entity_name} spielt" + }, + "trigger_type": { + "idle": "{entity_name} wird inaktiv", + "paused": "{entity_name} ist angehalten", + "playing": "{entity_name} beginnt zu spielen", + "turned_off": "{entity_name} ausgeschaltet", + "turned_on": "{entity_name} eingeschaltet" } }, "state": { diff --git a/homeassistant/components/melcloud/translations/zh-Hant.json b/homeassistant/components/melcloud/translations/zh-Hant.json index 9947b5ac990869..27f4d0e5d7f0d2 100644 --- a/homeassistant/components/melcloud/translations/zh-Hant.json +++ b/homeassistant/components/melcloud/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u5bc6\u9470\u5df2\u66f4\u65b0\u3002" + "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u6b0a\u6756\u5df2\u66f4\u65b0\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/mullvad/translations/de.json b/homeassistant/components/mullvad/translations/de.json new file mode 100644 index 00000000000000..625c7372347a61 --- /dev/null +++ b/homeassistant/components/mullvad/translations/de.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/he.json b/homeassistant/components/mullvad/translations/he.json new file mode 100644 index 00000000000000..7f60f15d598ac0 --- /dev/null +++ b/homeassistant/components/mullvad/translations/he.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u05d4\u05de\u05db\u05e9\u05d9\u05e8 \u05db\u05d1\u05e8 \u05de\u05d5\u05d2\u05d3\u05e8" + }, + "error": { + "cannot_connect": "\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", + "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05ea\u05e7\u05d9\u05df", + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "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/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json new file mode 100644 index 00000000000000..189226f29d5e90 --- /dev/null +++ b/homeassistant/components/mysensors/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "error": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/he.json b/homeassistant/components/netatmo/translations/he.json new file mode 100644 index 00000000000000..54bef84c30a4a2 --- /dev/null +++ b/homeassistant/components/netatmo/translations/he.json @@ -0,0 +1,11 @@ +{ + "device_automation": { + "trigger_type": { + "animal": "\u05d6\u05d9\u05d4\u05d4 \u05d1\u05e2\u05dc-\u05d7\u05d9\u05d9\u05dd", + "human": "\u05d6\u05d9\u05d4\u05d4 \u05d0\u05d3\u05dd", + "movement": "\u05d6\u05d9\u05d4\u05d4 \u05ea\u05e0\u05d5\u05e2\u05d4", + "turned_off": "\u05db\u05d1\u05d4", + "turned_on": "\u05e0\u05d3\u05dc\u05e7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/et.json b/homeassistant/components/nightscout/translations/et.json index 361b47893288a0..0d00cebb6a5f8e 100644 --- a/homeassistant/components/nightscout/translations/et.json +++ b/homeassistant/components/nightscout/translations/et.json @@ -15,7 +15,7 @@ "api_key": "API v\u00f5ti", "url": "" }, - "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui teie eksemplar on kaitstud (auth_default_roles! = readable).", + "description": "- URL: NightScout eksemplari aadress. St: https://myhomeassistant.duckdns.org:5423\n - API v\u00f5ti (valikuline): kasuta ainult siis kui eksemplar on kaitstud (auth_default_roles! = readable).", "title": "Sisesta oma Nightscouti serveri teave." } } diff --git a/homeassistant/components/nuki/translations/de.json b/homeassistant/components/nuki/translations/de.json new file mode 100644 index 00000000000000..30d7e6865cdc80 --- /dev/null +++ b/homeassistant/components/nuki/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Zugangstoken" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/zh-Hant.json b/homeassistant/components/nuki/translations/zh-Hant.json index 662d7ed6ed95c5..4bf21552952d2a 100644 --- a/homeassistant/components/nuki/translations/zh-Hant.json +++ b/homeassistant/components/nuki/translations/zh-Hant.json @@ -10,7 +10,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "token": "\u5b58\u53d6\u5bc6\u9470" + "token": "\u5b58\u53d6\u6b0a\u6756" } } } diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json new file mode 100644 index 00000000000000..f59a17bce49668 --- /dev/null +++ b/homeassistant/components/philips_js/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_pin": "Ung\u00fcltige PIN", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "api_version": "API-Version", + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/he.json b/homeassistant/components/philips_js/translations/he.json new file mode 100644 index 00000000000000..04648fe58451ef --- /dev/null +++ b/homeassistant/components/philips_js/translations/he.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "pairing_failure": "\u05e6\u05d9\u05de\u05d5\u05d3 \u05e0\u05db\u05e9\u05dc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/zh-Hant.json b/homeassistant/components/philips_js/translations/zh-Hant.json index af161b6b16b5f5..13bfd52e980148 100644 --- a/homeassistant/components/philips_js/translations/zh-Hant.json +++ b/homeassistant/components/philips_js/translations/zh-Hant.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_pin": "PIN \u78bc\u7121\u6548", + "pairing_failure": "\u7121\u6cd5\u914d\u5c0d\uff1a{error_id}", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index 5171baab6544f0..eaf68b507f9f83 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Konto wurde bereits konfiguriert", "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/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index 2890c5c31c694a..26d7b728771b72 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -10,16 +10,16 @@ }, "error": { "invalid_webhook_device": "\u6240\u9078\u64c7\u7684\u88dd\u7f6e\u4e0d\u652f\u63f4\u50b3\u9001\u8cc7\u6599\u81f3 Webhook\u3001AirLock \u50c5\u652f\u63f4\u6b64\u985e\u578b", - "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470\u6216\u9078\u64c7 Webhook", - "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u5bc6\u9470" + "no_api_method": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u6b0a\u6756\u6216\u9078\u64c7 Webhook", + "no_auth_token": "\u9700\u8981\u65b0\u589e\u6388\u6b0a\u6b0a\u6756" }, "step": { "api_method": { "data": { - "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u5bc6\u9470", + "token": "\u65bc\u6b64\u8cbc\u4e0a\u6388\u6b0a\u6b0a\u6756", "use_webhook": "\u4f7f\u7528 Webhook" }, - "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u5bc6\u9470\u6b04\u4f4d\u7a7a\u767d", + "description": "\u9700\u8981\u6388\u6b0a\u5bc6\u8981 `auth_token` \u65b9\u80fd\u67e5\u8a62 API\u3002\u7372\u5f97\u7684\u65b9\u6cd5\u8acb [\u53c3\u95b1](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) \u6559\u5b78\n\n\u9078\u64c7\u7684\u88dd\u7f6e\uff1a**{device_type}** \n\n\u5047\u5982\u9078\u64c7\u5167\u5efa Webhook \u65b9\u6cd5\uff08Airlock \u552f\u4e00\u652f\u63f4\uff09\uff0c\u8acb\u6aa2\u67e5\u4e0b\u65b9\u6838\u9078\u76d2\u4e26\u78ba\u5b9a\u4fdd\u6301\u6388\u6b0a\u6b0a\u6756\u6b04\u4f4d\u7a7a\u767d", "title": "\u9078\u64c7 API \u65b9\u5f0f" }, "user": { diff --git a/homeassistant/components/plex/translations/zh-Hant.json b/homeassistant/components/plex/translations/zh-Hant.json index 137b953a145745..7f19fa0d035441 100644 --- a/homeassistant/components/plex/translations/zh-Hant.json +++ b/homeassistant/components/plex/translations/zh-Hant.json @@ -5,12 +5,12 @@ "already_configured": "Plex \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", - "token_request_timeout": "\u53d6\u5f97\u5bc6\u9470\u903e\u6642", + "token_request_timeout": "\u53d6\u5f97\u6b0a\u6756\u903e\u6642", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { - "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u5bc6\u9470", - "host_or_token": "\u5fc5\u9808\u81f3\u5c11\u63d0\u4f9b\u4e3b\u6a5f\u7aef\u6216\u5bc6\u9470", + "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u6b0a\u6756", + "host_or_token": "\u5fc5\u9808\u81f3\u5c11\u63d0\u4f9b\u4e3b\u6a5f\u7aef\u6216\u6b0a\u6756", "no_servers": "Plex \u5e33\u865f\u672a\u7d81\u5b9a\u4efb\u4f55\u4f3a\u670d\u5668", "not_found": "\u627e\u4e0d\u5230 Plex \u4f3a\u670d\u5668", "ssl_error": "SSL \u8a8d\u8b49\u554f\u984c" @@ -22,7 +22,7 @@ "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", - "token": "\u5bc6\u9470\uff08\u9078\u9805\uff09", + "token": "\u6b0a\u6756\uff08\u9078\u9805\uff09", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "title": "Plex \u624b\u52d5\u8a2d\u5b9a" diff --git a/homeassistant/components/point/translations/zh-Hant.json b/homeassistant/components/point/translations/zh-Hant.json index 710d363f771383..2bb1a8fc23901e 100644 --- a/homeassistant/components/point/translations/zh-Hant.json +++ b/homeassistant/components/point/translations/zh-Hant.json @@ -13,7 +13,7 @@ }, "error": { "follow_link": "\u8acb\u65bc\u50b3\u9001\u524d\uff0c\u5148\u4f7f\u7528\u9023\u7d50\u4e26\u9032\u884c\u8a8d\u8b49\u3002", - "no_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548" + "no_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548" }, "step": { "auth": { diff --git a/homeassistant/components/powerwall/translations/de.json b/homeassistant/components/powerwall/translations/de.json index c30286d874450a..0ccd42c812ba1f 100644 --- a/homeassistant/components/powerwall/translations/de.json +++ b/homeassistant/components/powerwall/translations/de.json @@ -1,17 +1,20 @@ { "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", + "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { - "ip_address": "IP-Adresse" + "ip_address": "IP-Adresse", + "password": "Passwort" }, "title": "Stellen Sie eine Verbindung zur Powerwall her" } diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json index 4a937029296427..8811b87031699a 100644 --- a/homeassistant/components/powerwall/translations/et.json +++ b/homeassistant/components/powerwall/translations/et.json @@ -8,7 +8,7 @@ "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", "invalid_auth": "Vigane autentimine", "unknown": "Ootamatu t\u00f5rge", - "wrong_version": "Teie Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." + "wrong_version": "Powerwall kasutab tarkvaraversiooni, mida ei toetata. Kaaluge tarkvara uuendamist v\u00f5i probleemist teavitamist, et see saaks lahendatud." }, "flow_title": "Tesla Powerwall ( {ip_address} )", "step": { diff --git a/homeassistant/components/rituals_perfume_genie/translations/de.json b/homeassistant/components/rituals_perfume_genie/translations/de.json new file mode 100644 index 00000000000000..67b8ed59e0b6b3 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 4bfb3c7503ddda..152161cb27fbfb 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Das Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "unknown": "Unerwarteter Fehler" }, "error": { diff --git a/homeassistant/components/simplisafe/translations/zh-Hant.json b/homeassistant/components/simplisafe/translations/zh-Hant.json index ad5323d3957d6f..27064ed1055a38 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hant.json +++ b/homeassistant/components/simplisafe/translations/zh-Hant.json @@ -19,7 +19,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u5b58\u53d6\u5bc6\u9470\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", + "description": "\u5b58\u53d6\u6b0a\u6756\u5df2\u7d93\u904e\u671f\u6216\u53d6\u6d88\uff0c\u8acb\u8f38\u5165\u5bc6\u78bc\u4ee5\u91cd\u65b0\u9023\u7d50\u5e33\u865f\u3002", "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" }, "user": { diff --git a/homeassistant/components/smartthings/translations/et.json b/homeassistant/components/smartthings/translations/et.json index 04cd0d70218bf9..18d6076898db4c 100644 --- a/homeassistant/components/smartthings/translations/et.json +++ b/homeassistant/components/smartthings/translations/et.json @@ -19,7 +19,7 @@ "data": { "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" }, - "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks teie SmartThingsi kontol.", + "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks SmartThingsi kontol.", "title": "Sisesta isiklik juurdep\u00e4\u00e4suluba (PAT)" }, "select_location": { diff --git a/homeassistant/components/smartthings/translations/zh-Hant.json b/homeassistant/components/smartthings/translations/zh-Hant.json index d9a17e460582c3..88360c756781a6 100644 --- a/homeassistant/components/smartthings/translations/zh-Hant.json +++ b/homeassistant/components/smartthings/translations/zh-Hant.json @@ -6,9 +6,9 @@ }, "error": { "app_setup_error": "\u7121\u6cd5\u8a2d\u5b9a SmartApp\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", - "token_forbidden": "\u5bc6\u9470\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", - "token_invalid_format": "\u5bc6\u9470\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", - "token_unauthorized": "\u5bc6\u9470\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", + "token_forbidden": "\u6b0a\u6756\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", + "token_invalid_format": "\u6b0a\u6756\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", + "token_unauthorized": "\u6b0a\u6756\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", "webhook_error": "SmartThings \u7121\u6cd5\u8a8d\u8b49 Webhook URL\u3002\u8acb\u78ba\u8a8d Webhook URL \u53ef\u7531\u7db2\u8def\u5b58\u53d6\u5f8c\u518d\u8a66\u4e00\u6b21\u3002" }, "step": { @@ -17,10 +17,10 @@ }, "pat": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u5bc6\u9470]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", - "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u5bc6\u9470" + "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u6b0a\u6756]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", + "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u6b0a\u6756" }, "select_location": { "data": { diff --git a/homeassistant/components/smarttub/translations/de.json b/homeassistant/components/smarttub/translations/de.json new file mode 100644 index 00000000000000..fbb3411a6c5b26 --- /dev/null +++ b/homeassistant/components/smarttub/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/de.json b/homeassistant/components/subaru/translations/de.json new file mode 100644 index 00000000000000..1c162d61e994fd --- /dev/null +++ b/homeassistant/components/subaru/translations/de.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "bad_pin_format": "Die PIN sollte 4-stellig sein", + "cannot_connect": "Verbindung fehlgeschlagen", + "incorrect_pin": "Falsche PIN", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + } + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/de.json b/homeassistant/components/tesla/translations/de.json index 558209af411791..2fd964fe01320c 100644 --- a/homeassistant/components/tesla/translations/de.json +++ b/homeassistant/components/tesla/translations/de.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, "error": { "already_configured": "Konto wurde bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/tibber/translations/zh-Hant.json b/homeassistant/components/tibber/translations/zh-Hant.json index ce10615a2892b6..e4d0ec10e234c2 100644 --- a/homeassistant/components/tibber/translations/zh-Hant.json +++ b/homeassistant/components/tibber/translations/zh-Hant.json @@ -5,15 +5,15 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", - "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", + "invalid_access_token": "\u5b58\u53d6\u6b0a\u6756\u7121\u6548", "timeout": "\u9023\u7dda\u81f3 Tibber \u903e\u6642" }, "step": { "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470" + "access_token": "\u5b58\u53d6\u6b0a\u6756" }, - "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u5bc6\u9470", + "description": "\u8f38\u5165\u7531 https://developer.tibber.com/settings/accesstoken \u6240\u7372\u5f97\u7684\u5b58\u53d6\u6b0a\u6756", "title": "Tibber" } } diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 530fef95af23f1..3fb5bb8f3e1f08 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Konto wurde bereits konfiguriert" + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "invalid_auth": "Ung\u00fcltige Authentifizierung" }, "step": { + "locations": { + "data": { + "location": "Standort" + } + }, + "reauth_confirm": { + "title": "Integration erneut authentifizieren" + }, "user": { "data": { "password": "Passwort", diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json index 0fc1297ce7c461..48161f552b8284 100644 --- a/homeassistant/components/tuya/translations/et.json +++ b/homeassistant/components/tuya/translations/et.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "country_code": "Teie konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", + "country_code": "Konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", "password": "Salas\u00f5na", - "platform": "\u00c4pp kus teie konto registreeriti", + "platform": "\u00c4pp kus konto registreeriti", "username": "Kasutajanimi" }, "description": "Sisesta oma Tuya konto andmed.", diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index be38ddf1a4d760..05dd66fe56c798 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { - "already_configured": "Controller-Site ist bereits konfiguriert" + "already_configured": "Controller-Site ist bereits konfiguriert", + "configuration_updated": "Konfiguration aktualisiert.", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { "faulty_credentials": "Ung\u00fcltige Authentifizierung", diff --git a/homeassistant/components/vilfo/translations/zh-Hant.json b/homeassistant/components/vilfo/translations/zh-Hant.json index b266e25b39cb3f..88180f9bacf822 100644 --- a/homeassistant/components/vilfo/translations/zh-Hant.json +++ b/homeassistant/components/vilfo/translations/zh-Hant.json @@ -11,10 +11,10 @@ "step": { "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u5bc6\u9470\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", + "description": "\u8a2d\u5b9a Vilfo \u8def\u7531\u5668\u6574\u5408\u3002\u9700\u8981\u8f38\u5165 Vilfo \u8def\u7531\u5668\u4e3b\u6a5f\u540d\u7a31/IP \u4f4d\u5740\u3001API \u5b58\u53d6\u6b0a\u6756\u3002\u5176\u4ed6\u6574\u5408\u76f8\u95dc\u8cc7\u8a0a\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/vilfo", "title": "\u9023\u7dda\u81f3 Vilfo \u8def\u7531\u5668" } } diff --git a/homeassistant/components/vizio/translations/zh-Hant.json b/homeassistant/components/vizio/translations/zh-Hant.json index 257ed829b6ae50..5f21dd0c2b6b49 100644 --- a/homeassistant/components/vizio/translations/zh-Hant.json +++ b/homeassistant/components/vizio/translations/zh-Hant.json @@ -23,17 +23,17 @@ "title": "\u914d\u5c0d\u5b8c\u6210" }, "pairing_complete_import": { - "description": "VIZIO SmartCast \u88dd\u7f6e \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u5bc6\u9470\u70ba '**{access_token}**'\u3002", + "description": "VIZIO SmartCast \u88dd\u7f6e \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u6b0a\u6756\u70ba '**{access_token}**'\u3002", "title": "\u914d\u5c0d\u5b8c\u6210" }, "user": { "data": { - "access_token": "\u5b58\u53d6\u5bc6\u9470", + "access_token": "\u5b58\u53d6\u6b0a\u6756", "device_class": "\u88dd\u7f6e\u985e\u5225", "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u5bc6\u9470\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u5bc6\u9470 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", + "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u6b0a\u6756\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u6b0a\u6756 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", "title": "VIZIO SmartCast \u88dd\u7f6e" } } diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index d56a81e14d4582..7cf11a1085e2a1 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -10,6 +10,13 @@ }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP-Adresse", + "name": "Name des Ger\u00e4ts", + "token": "API-Token" + } + }, "gateway": { "data": { "host": "IP-Adresse", diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 951ae546b5618c..3d893ade2f0805 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -18,7 +18,7 @@ "name": "Name of the device", "token": "API Token" }, - "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", + "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "Connect to a Xiaomi Miio Device or Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index dce2002faa957b..3b0a89b7485e62 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -16,18 +16,18 @@ "host": "IP \u4f4d\u5740", "model": "\u88dd\u7f6e\u578b\u865f\uff08\u9078\u9805\uff09", "name": "\u88dd\u7f6e\u540d\u7a31", - "token": "API \u5bc6\u9470" + "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" }, "gateway": { "data": { "host": "IP \u4f4d\u5740", "name": "\u7db2\u95dc\u540d\u7a31", - "token": "API \u5bc6\u9470" + "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u5bc6\u9470\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u5bc6\u9470\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u5bc6\u9470\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u5bc6\u9470\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" }, "user": { diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index d4903bc8c6dae4..9ff130605efdd1 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,13 +1,25 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, "step": { + "configure_addon": { + "data": { + "usb_path": "USB-Ger\u00e4te-Pfad" + } + }, + "manual": { + "data": { + "url": "URL" + } + }, "user": { "data": { "url": "URL" From dddf28b13899d29c70b9fa202a9e5c840621a2b1 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 2 Mar 2021 21:50:28 +0100 Subject: [PATCH 0960/1818] Limit log spam by ConfigEntryNotReady (#47201) --- homeassistant/config_entries.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b54300faaa77c7..b0ec71be9cf3d4 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -257,12 +257,19 @@ async def async_setup( self.state = ENTRY_STATE_SETUP_RETRY wait_time = 2 ** min(tries, 4) * 5 tries += 1 - _LOGGER.warning( - "Config entry '%s' for %s integration not ready yet. Retrying in %d seconds", - self.title, - self.domain, - wait_time, - ) + if tries == 1: + _LOGGER.warning( + "Config entry '%s' for %s integration not ready yet. Retrying in background", + self.title, + self.domain, + ) + else: + _LOGGER.debug( + "Config entry '%s' for %s integration not ready yet. Retrying in %d seconds", + self.title, + self.domain, + wait_time, + ) async def setup_again(now: Any) -> None: """Run setup again.""" From d88ee3bf4a9019c44be2ec2538170528baf645ee Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Mar 2021 22:02:41 +0100 Subject: [PATCH 0961/1818] Upgrade pillow to 8.1.1 (#47223) --- 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 ecbcd8563a78fd..f5d425cb9ef046 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,6 +2,6 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==8.1.0"], + "requirements": ["pydoods==1.0.2", "pillow==8.1.1"], "codeowners": [] } diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 6978f09ab680f0..c8029c2e3130f9 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==8.1.0"], + "requirements": ["pillow==8.1.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 65d8d21fc0cf12..c1a01004fe9520 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==8.1.0"], + "requirements": ["pillow==8.1.1"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index b16eace14fdc26..5867d0d6b51a24 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,6 +2,6 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==8.1.0", "pyzbar==0.1.7"], + "requirements": ["pillow==8.1.1", "pyzbar==0.1.7"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index 01e0275feeb31d..4f9f6514531221 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,6 +2,6 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==8.1.0"], + "requirements": ["pillow==8.1.1"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 99902b8dd363cf..aa9519fd68b399 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,6 +2,6 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==8.1.0", "simplehound==0.3"], + "requirements": ["pillow==8.1.1", "simplehound==0.3"], "codeowners": ["@robmarkcole"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index f039a14d5b3eec..300c3ddd1db6ba 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.3.0", "pycocotools==2.0.1", "numpy==1.19.2", - "pillow==8.1.0" + "pillow==8.1.1" ], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cb211fb1962d7b..5aaa5b1b4699da 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 -pillow==8.1.0 +pillow==8.1.1 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2021.1 diff --git a/requirements_all.txt b/requirements_all.txt index e27b43f94a8713..d63ab3c5b8318b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1129,7 +1129,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.0 +pillow==8.1.1 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 400cc372b2b00b..ef6c0706c87493 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -578,7 +578,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.0 +pillow==8.1.1 # homeassistant.components.plex plexapi==4.4.0 From 23049955f8349f16d0b41ac81be22fb91a3d2d9b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Mar 2021 23:22:42 +0100 Subject: [PATCH 0962/1818] Add zwave_js add-on manager (#47251) Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/__init__.py | 27 ++ homeassistant/components/zwave_js/__init__.py | 101 +++++-- homeassistant/components/zwave_js/addon.py | 246 ++++++++++++++++ .../components/zwave_js/config_flow.py | 55 ++-- homeassistant/components/zwave_js/const.py | 8 + .../components/zwave_js/strings.json | 1 - .../components/zwave_js/translations/en.json | 6 - tests/components/zwave_js/conftest.py | 118 ++++++++ tests/components/zwave_js/test_config_flow.py | 127 +++------ tests/components/zwave_js/test_init.py | 263 ++++++++++++++++-- 10 files changed, 797 insertions(+), 155 deletions(-) create mode 100644 homeassistant/components/zwave_js/addon.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index fdeb10bcafe35b..5b40d7142f1cb1 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -169,6 +169,18 @@ async def async_uninstall_addon(hass: HomeAssistantType, slug: str) -> dict: return await hassio.send_command(command, timeout=60) +@bind_hass +@api_data +async def async_update_addon(hass: HomeAssistantType, slug: str) -> dict: + """Update add-on. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + command = f"/addons/{slug}/update" + return await hassio.send_command(command, timeout=None) + + @bind_hass @api_data async def async_start_addon(hass: HomeAssistantType, slug: str) -> dict: @@ -218,6 +230,21 @@ async def async_get_addon_discovery_info( return next((addon for addon in discovered_addons if addon["addon"] == slug), None) +@bind_hass +@api_data +async def async_create_snapshot( + hass: HomeAssistantType, payload: dict, partial: bool = False +) -> dict: + """Create a full or partial snapshot. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + snapshot_type = "partial" if partial else "full" + command = f"/snapshots/new/{snapshot_type}" + return await hassio.send_command(command, payload=payload, timeout=None) + + @callback @bind_hass def get_info(hass): diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 798fd9fda2c838..c19b1b355a4137 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,16 +1,14 @@ """The Z-Wave JS integration.""" import asyncio -import logging from typing import Callable, List from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.exceptions import BaseZwaveJSServerError +from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import Notification from zwave_js_server.model.value import ValueNotification -from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -19,9 +17,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send +from .addon import AddonError, AddonManager, get_addon_manager from .api import async_register_api from .const import ( - ADDON_SLUG, ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, ATTR_DEVICE_ID, @@ -35,10 +33,14 @@ ATTR_TYPE, ATTR_VALUE, CONF_INTEGRATION_CREATED_ADDON, + CONF_NETWORK_KEY, + CONF_USB_PATH, + CONF_USE_ADDON, DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN, EVENT_DEVICE_ADDED_TO_REGISTRY, + LOGGER, PLATFORMS, ZWAVE_JS_EVENT, ) @@ -46,10 +48,11 @@ from .helpers import get_device_id, get_old_value_id, get_unique_id from .services import ZWaveServices -LOGGER = logging.getLogger(__package__) CONNECT_TIMEOUT = 10 DATA_CLIENT_LISTEN_TASK = "client_listen_task" DATA_START_PLATFORM_TASK = "start_platform_task" +DATA_CONNECT_FAILED_LOGGED = "connect_failed_logged" +DATA_INVALID_SERVER_VERSION_LOGGED = "invalid_server_version_logged" async def async_setup(hass: HomeAssistant, config: dict) -> bool: @@ -84,6 +87,10 @@ def register_node_in_dev_reg( async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" + use_addon = entry.data.get(CONF_USE_ADDON) + if use_addon: + await async_ensure_addon_running(hass, entry) + client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) @@ -251,21 +258,31 @@ def async_on_notification(notification: Notification) -> None: }, ) + entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) # connect and throw error if connection failed try: async with timeout(CONNECT_TIMEOUT): await client.connect() + except InvalidServerVersion as err: + if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED): + LOGGER.error("Invalid server version: %s", err) + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True + if use_addon: + async_ensure_addon_updated(hass) + raise ConfigEntryNotReady from err except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: - LOGGER.error("Failed to connect: %s", err) + if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED): + LOGGER.error("Failed to connect: %s", err) + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True raise ConfigEntryNotReady from err else: LOGGER.info("Connected to Zwave JS Server") + entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False + entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False unsubscribe_callbacks: List[Callable] = [] - hass.data[DOMAIN][entry.entry_id] = { - DATA_CLIENT: client, - DATA_UNSUBSCRIBE: unsubscribe_callbacks, - } + entry_hass_data[DATA_CLIENT] = client + entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks services = ZWaveServices(hass, ent_reg) services.async_register() @@ -292,7 +309,7 @@ async def handle_ha_shutdown(event: Event) -> None: listen_task = asyncio.create_task( client_listen(hass, entry, client, driver_ready) ) - hass.data[DOMAIN][entry.entry_id][DATA_CLIENT_LISTEN_TASK] = listen_task + entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task unsubscribe_callbacks.append( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown) ) @@ -334,7 +351,7 @@ async def handle_ha_shutdown(event: Event) -> None: ) platform_task = hass.async_create_task(start_platforms()) - hass.data[DOMAIN][entry.entry_id][DATA_START_PLATFORM_TASK] = platform_task + entry_hass_data[DATA_START_PLATFORM_TASK] = platform_task return True @@ -410,6 +427,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: platform_task=info[DATA_START_PLATFORM_TASK], ) + if entry.data.get(CONF_USE_ADDON) and entry.disabled_by: + addon_manager: AddonManager = get_addon_manager(hass) + LOGGER.debug("Stopping Z-Wave JS add-on") + try: + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) + return False + return True @@ -418,12 +444,51 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON): return + addon_manager: AddonManager = get_addon_manager(hass) try: - await hass.components.hassio.async_stop_addon(ADDON_SLUG) - except HassioAPIError as err: - LOGGER.error("Failed to stop the Z-Wave JS add-on: %s", err) + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error(err) return try: - await hass.components.hassio.async_uninstall_addon(ADDON_SLUG) - except HassioAPIError as err: - LOGGER.error("Failed to uninstall the Z-Wave JS add-on: %s", err) + await addon_manager.async_create_snapshot() + except AddonError as err: + LOGGER.error(err) + return + try: + await addon_manager.async_uninstall_addon() + except AddonError as err: + LOGGER.error(err) + + +async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Ensure that Z-Wave JS add-on is installed and running.""" + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + try: + addon_is_installed = await addon_manager.async_is_addon_installed() + addon_is_running = await addon_manager.async_is_addon_running() + except AddonError as err: + LOGGER.error("Failed to get the Z-Wave JS add-on info") + raise ConfigEntryNotReady from err + + usb_path: str = entry.data[CONF_USB_PATH] + network_key: str = entry.data[CONF_NETWORK_KEY] + + if not addon_is_installed: + addon_manager.async_schedule_install_addon(usb_path, network_key) + raise ConfigEntryNotReady + + if not addon_is_running: + addon_manager.async_schedule_setup_addon(usb_path, network_key) + raise ConfigEntryNotReady + + +@callback +def async_ensure_addon_updated(hass: HomeAssistant) -> None: + """Ensure that Z-Wave JS add-on is updated and running.""" + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + addon_manager.async_schedule_update_addon() diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py new file mode 100644 index 00000000000000..54169dcaf94701 --- /dev/null +++ b/homeassistant/components/zwave_js/addon.py @@ -0,0 +1,246 @@ +"""Provide add-on management.""" +from __future__ import annotations + +import asyncio +from functools import partial +from typing import Any, Callable, Optional, TypeVar, cast + +from homeassistant.components.hassio import ( + async_create_snapshot, + async_get_addon_discovery_info, + async_get_addon_info, + async_install_addon, + async_set_addon_options, + async_start_addon, + async_stop_addon, + async_uninstall_addon, + async_update_addon, +) +from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.singleton import singleton + +from .const import ADDON_SLUG, CONF_ADDON_DEVICE, CONF_ADDON_NETWORK_KEY, DOMAIN, LOGGER + +F = TypeVar("F", bound=Callable[..., Any]) # pylint: disable=invalid-name + +DATA_ADDON_MANAGER = f"{DOMAIN}_addon_manager" + + +@singleton(DATA_ADDON_MANAGER) +@callback +def get_addon_manager(hass: HomeAssistant) -> AddonManager: + """Get the add-on manager.""" + return AddonManager(hass) + + +def api_error(error_message: str) -> Callable[[F], F]: + """Handle HassioAPIError and raise a specific AddonError.""" + + def handle_hassio_api_error(func: F) -> F: + """Handle a HassioAPIError.""" + + async def wrapper(*args, **kwargs): # type: ignore + """Wrap an add-on manager method.""" + try: + return_value = await func(*args, **kwargs) + except HassioAPIError as err: + raise AddonError(error_message) from err + + return return_value + + return cast(F, wrapper) + + return handle_hassio_api_error + + +class AddonManager: + """Manage the add-on. + + Methods may raise AddonError. + Only one instance of this class may exist + to keep track of running add-on tasks. + """ + + def __init__(self, hass: HomeAssistant) -> None: + """Set up the add-on manager.""" + self._hass = hass + self._install_task: Optional[asyncio.Task] = None + self._update_task: Optional[asyncio.Task] = None + self._setup_task: Optional[asyncio.Task] = None + + def task_in_progress(self) -> bool: + """Return True if any of the add-on tasks are in progress.""" + return any( + task and not task.done() + for task in ( + self._install_task, + self._setup_task, + self._update_task, + ) + ) + + @api_error("Failed to get Z-Wave JS add-on discovery info") + async def async_get_addon_discovery_info(self) -> dict: + """Return add-on discovery info.""" + discovery_info = await async_get_addon_discovery_info(self._hass, ADDON_SLUG) + + if not discovery_info: + raise AddonError("Failed to get Z-Wave JS add-on discovery info") + + discovery_info_config: dict = discovery_info["config"] + return discovery_info_config + + @api_error("Failed to get the Z-Wave JS add-on info") + async def async_get_addon_info(self) -> dict: + """Return and cache Z-Wave JS add-on info.""" + addon_info: dict = await async_get_addon_info(self._hass, ADDON_SLUG) + return addon_info + + async def async_is_addon_running(self) -> bool: + """Return True if Z-Wave JS add-on is running.""" + addon_info = await self.async_get_addon_info() + return bool(addon_info["state"] == "started") + + async def async_is_addon_installed(self) -> bool: + """Return True if Z-Wave JS add-on is installed.""" + addon_info = await self.async_get_addon_info() + return addon_info["version"] is not None + + async def async_get_addon_options(self) -> dict: + """Get Z-Wave JS add-on options.""" + addon_info = await self.async_get_addon_info() + return cast(dict, addon_info["options"]) + + @api_error("Failed to set the Z-Wave JS add-on options") + async def async_set_addon_options(self, config: dict) -> None: + """Set Z-Wave JS add-on options.""" + options = {"options": config} + await async_set_addon_options(self._hass, ADDON_SLUG, options) + + @api_error("Failed to install the Z-Wave JS add-on") + async def async_install_addon(self) -> None: + """Install the Z-Wave JS add-on.""" + await async_install_addon(self._hass, ADDON_SLUG) + + @callback + def async_schedule_install_addon( + self, usb_path: str, network_key: str + ) -> asyncio.Task: + """Schedule a task that installs and sets up the Z-Wave JS add-on. + + Only schedule a new install task if the there's no running task. + """ + if not self._install_task or self._install_task.done(): + LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") + self._install_task = self._async_schedule_addon_operation( + self.async_install_addon, + partial(self.async_setup_addon, usb_path, network_key), + ) + return self._install_task + + @api_error("Failed to uninstall the Z-Wave JS add-on") + async def async_uninstall_addon(self) -> None: + """Uninstall the Z-Wave JS add-on.""" + await async_uninstall_addon(self._hass, ADDON_SLUG) + + @api_error("Failed to update the Z-Wave JS add-on") + async def async_update_addon(self) -> None: + """Update the Z-Wave JS add-on if needed.""" + addon_info = await self.async_get_addon_info() + addon_version = addon_info["version"] + update_available = addon_info["update_available"] + + if addon_version is None: + raise AddonError("Z-Wave JS add-on is not installed") + + if not update_available: + return + + await async_update_addon(self._hass, ADDON_SLUG) + + @callback + def async_schedule_update_addon(self) -> asyncio.Task: + """Schedule a task that updates and sets up the Z-Wave JS add-on. + + Only schedule a new update task if the there's no running task. + """ + if not self._update_task or self._update_task.done(): + LOGGER.info("Trying to update the Z-Wave JS add-on") + self._update_task = self._async_schedule_addon_operation( + self.async_create_snapshot, self.async_update_addon + ) + return self._update_task + + @api_error("Failed to start the Z-Wave JS add-on") + async def async_start_addon(self) -> None: + """Start the Z-Wave JS add-on.""" + await async_start_addon(self._hass, ADDON_SLUG) + + @api_error("Failed to stop the Z-Wave JS add-on") + async def async_stop_addon(self) -> None: + """Stop the Z-Wave JS add-on.""" + await async_stop_addon(self._hass, ADDON_SLUG) + + async def async_setup_addon(self, usb_path: str, network_key: str) -> None: + """Configure and start Z-Wave JS add-on.""" + addon_options = await self.async_get_addon_options() + + new_addon_options = { + CONF_ADDON_DEVICE: usb_path, + CONF_ADDON_NETWORK_KEY: network_key, + } + + if new_addon_options != addon_options: + await self.async_set_addon_options(new_addon_options) + + await self.async_start_addon() + + @callback + def async_schedule_setup_addon( + self, usb_path: str, network_key: str + ) -> asyncio.Task: + """Schedule a task that configures and starts the Z-Wave JS add-on. + + Only schedule a new setup task if the there's no running task. + """ + if not self._setup_task or self._setup_task.done(): + LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") + self._setup_task = self._async_schedule_addon_operation( + partial(self.async_setup_addon, usb_path, network_key) + ) + return self._setup_task + + @api_error("Failed to create a snapshot of the Z-Wave JS add-on.") + async def async_create_snapshot(self) -> None: + """Create a partial snapshot of the Z-Wave JS add-on.""" + addon_info = await self.async_get_addon_info() + addon_version = addon_info["version"] + name = f"addon_{ADDON_SLUG}_{addon_version}" + + LOGGER.debug("Creating snapshot: %s", name) + await async_create_snapshot( + self._hass, + {"name": name, "addons": [ADDON_SLUG]}, + partial=True, + ) + + @callback + def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task: + """Schedule an add-on task.""" + + async def addon_operation() -> None: + """Do the add-on operation and catch AddonError.""" + for func in funcs: + try: + await func() + except AddonError as err: + LOGGER.error(err) + break + + return self._hass.async_create_task(addon_operation()) + + +class AddonError(HomeAssistantError): + """Represent an error with Z-Wave JS add-on.""" diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index cc19fb85d3a147..37923c574b4371 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -9,33 +9,25 @@ from zwave_js_server.version import VersionInfo, get_server_version from homeassistant import config_entries, exceptions -from homeassistant.components.hassio import ( - async_get_addon_discovery_info, - async_get_addon_info, - async_install_addon, - async_set_addon_options, - async_start_addon, - is_hassio, -) -from homeassistant.components.hassio.handler import HassioAPIError +from homeassistant.components.hassio import is_hassio from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .addon import AddonError, AddonManager, get_addon_manager from .const import ( # pylint:disable=unused-import - ADDON_SLUG, + CONF_ADDON_DEVICE, + CONF_ADDON_NETWORK_KEY, CONF_INTEGRATION_CREATED_ADDON, + CONF_NETWORK_KEY, + CONF_USB_PATH, CONF_USE_ADDON, DOMAIN, ) _LOGGER = logging.getLogger(__name__) -CONF_ADDON_DEVICE = "device" -CONF_ADDON_NETWORK_KEY = "network_key" -CONF_NETWORK_KEY = "network_key" -CONF_USB_PATH = "usb_path" DEFAULT_URL = "ws://localhost:3000" TITLE = "Z-Wave JS" @@ -180,6 +172,11 @@ async def async_step_on_supervisor( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle logic when on Supervisor host.""" + # Only one entry with Supervisor add-on support is allowed. + for entry in self.hass.config_entries.async_entries(DOMAIN): + if entry.data.get(CONF_USE_ADDON): + return await self.async_step_manual() + if user_input is None: return self.async_show_form( step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA @@ -212,7 +209,7 @@ async def async_step_install_addon( try: await self.install_task - except HassioAPIError as err: + except AddonError as err: _LOGGER.error("Failed to install Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="install_failed") @@ -275,7 +272,7 @@ async def async_step_start_addon( try: await self.start_task - except (CannotConnect, HassioAPIError) as err: + except (CannotConnect, AddonError) as err: _LOGGER.error("Failed to start Z-Wave JS add-on: %s", err) return self.async_show_progress_done(next_step_id="start_failed") @@ -290,8 +287,9 @@ async def async_step_start_failed( async def _async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" assert self.hass + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_start_addon(self.hass, ADDON_SLUG) + await addon_manager.async_start_addon() # Sleep some seconds to let the add-on start properly before connecting. for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): await asyncio.sleep(ADDON_SETUP_TIMEOUT) @@ -345,9 +343,10 @@ async def async_step_finish_addon_setup( async def _async_get_addon_info(self) -> dict: """Return and cache Z-Wave JS add-on info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - addon_info: dict = await async_get_addon_info(self.hass, ADDON_SLUG) - except HassioAPIError as err: + addon_info: dict = await addon_manager.async_get_addon_info() + except AddonError as err: _LOGGER.error("Failed to get Z-Wave JS add-on info: %s", err) raise AbortFlow("addon_info_failed") from err @@ -371,16 +370,18 @@ async def _async_get_addon_config(self) -> dict: async def _async_set_addon_config(self, config: dict) -> None: """Set Z-Wave JS add-on config.""" options = {"options": config} + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_set_addon_options(self.hass, ADDON_SLUG, options) - except HassioAPIError as err: + await addon_manager.async_set_addon_options(options) + except AddonError as err: _LOGGER.error("Failed to set Z-Wave JS add-on config: %s", err) raise AbortFlow("addon_set_config_failed") from err async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - await async_install_addon(self.hass, ADDON_SLUG) + await addon_manager.async_install_addon() finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( @@ -389,17 +390,13 @@ async def _async_install_addon(self) -> None: async def _async_get_addon_discovery_info(self) -> dict: """Return add-on discovery info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) try: - discovery_info = await async_get_addon_discovery_info(self.hass, ADDON_SLUG) - except HassioAPIError as err: + discovery_info_config = await addon_manager.async_get_addon_discovery_info() + except AddonError as err: _LOGGER.error("Failed to get Z-Wave JS add-on discovery info: %s", err) raise AbortFlow("addon_get_discovery_info_failed") from err - if not discovery_info: - _LOGGER.error("Failed to get Z-Wave JS add-on discovery info") - raise AbortFlow("addon_missing_discovery_info") - - discovery_info_config: dict = discovery_info["config"] return discovery_info_config diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 19e6fc3db14bd4..e3f20366ab08d7 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -1,5 +1,11 @@ """Constants for the Z-Wave JS integration.""" +import logging + +CONF_ADDON_DEVICE = "device" +CONF_ADDON_NETWORK_KEY = "network_key" CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon" +CONF_NETWORK_KEY = "network_key" +CONF_USB_PATH = "usb_path" CONF_USE_ADDON = "use_addon" DOMAIN = "zwave_js" PLATFORMS = [ @@ -19,6 +25,8 @@ EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry" +LOGGER = logging.getLogger(__package__) + # constants for events ZWAVE_JS_EVENT = f"{DOMAIN}_event" ATTR_NODE_ID = "node_id" diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 5d3aa730a7c29d..eb13ad512e3e3c 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -41,7 +41,6 @@ "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", - "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "progress": { diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 5be980d52cb07a..101942dc717bad 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -4,7 +4,6 @@ "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", "addon_info_failed": "Failed to get Z-Wave JS add-on info.", "addon_install_failed": "Failed to install the Z-Wave JS add-on.", - "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "already_configured": "Device is already configured", @@ -49,11 +48,6 @@ }, "start_addon": { "title": "The Z-Wave JS add-on is starting." - }, - "user": { - "data": { - "url": "URL" - } } } }, diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 72835fb17c11e3..50cacd974229cf 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -14,6 +14,124 @@ from tests.common import MockConfigEntry, load_fixture +# Add-on fixtures + + +@pytest.fixture(name="addon_info_side_effect") +def addon_info_side_effect_fixture(): + """Return the add-on info side effect.""" + return None + + +@pytest.fixture(name="addon_info") +def mock_addon_info(addon_info_side_effect): + """Mock Supervisor add-on info.""" + with patch( + "homeassistant.components.zwave_js.addon.async_get_addon_info", + side_effect=addon_info_side_effect, + ) as addon_info: + addon_info.return_value = {} + yield addon_info + + +@pytest.fixture(name="addon_running") +def mock_addon_running(addon_info): + """Mock add-on already running.""" + addon_info.return_value["state"] = "started" + return addon_info + + +@pytest.fixture(name="addon_installed") +def mock_addon_installed(addon_info): + """Mock add-on already installed but not running.""" + addon_info.return_value["state"] = "stopped" + addon_info.return_value["version"] = "1.0" + return addon_info + + +@pytest.fixture(name="addon_options") +def mock_addon_options(addon_info): + """Mock add-on options.""" + addon_info.return_value["options"] = {} + return addon_info.return_value["options"] + + +@pytest.fixture(name="set_addon_options_side_effect") +def set_addon_options_side_effect_fixture(): + """Return the set add-on options side effect.""" + return None + + +@pytest.fixture(name="set_addon_options") +def mock_set_addon_options(set_addon_options_side_effect): + """Mock set add-on options.""" + with patch( + "homeassistant.components.zwave_js.addon.async_set_addon_options", + side_effect=set_addon_options_side_effect, + ) as set_options: + yield set_options + + +@pytest.fixture(name="install_addon") +def mock_install_addon(): + """Mock install add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_install_addon" + ) as install_addon: + yield install_addon + + +@pytest.fixture(name="update_addon") +def mock_update_addon(): + """Mock update add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_update_addon" + ) as update_addon: + yield update_addon + + +@pytest.fixture(name="start_addon_side_effect") +def start_addon_side_effect_fixture(): + """Return the set add-on options side effect.""" + return None + + +@pytest.fixture(name="start_addon") +def mock_start_addon(start_addon_side_effect): + """Mock start add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_start_addon", + side_effect=start_addon_side_effect, + ) as start_addon: + yield start_addon + + +@pytest.fixture(name="stop_addon") +def stop_addon_fixture(): + """Mock stop add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_stop_addon" + ) as stop_addon: + yield stop_addon + + +@pytest.fixture(name="uninstall_addon") +def uninstall_addon_fixture(): + """Mock uninstall add-on.""" + with patch( + "homeassistant.components.zwave_js.addon.async_uninstall_addon" + ) as uninstall_addon: + yield uninstall_addon + + +@pytest.fixture(name="create_shapshot") +def create_snapshot_fixture(): + """Mock create snapshot.""" + with patch( + "homeassistant.components.zwave_js.addon.async_create_snapshot" + ) as create_shapshot: + yield create_shapshot + @pytest.fixture(name="device_registry") async def device_registry_fixture(hass): diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 08b0ffe3080f2b..fc97f7420cf22d 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -44,93 +44,13 @@ def discovery_info_side_effect_fixture(): def mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect): """Mock get add-on discovery info.""" with patch( - "homeassistant.components.zwave_js.config_flow.async_get_addon_discovery_info", + "homeassistant.components.zwave_js.addon.async_get_addon_discovery_info", side_effect=discovery_info_side_effect, return_value=discovery_info, ) as get_addon_discovery_info: yield get_addon_discovery_info -@pytest.fixture(name="addon_info_side_effect") -def addon_info_side_effect_fixture(): - """Return the add-on info side effect.""" - return None - - -@pytest.fixture(name="addon_info") -def mock_addon_info(addon_info_side_effect): - """Mock Supervisor add-on info.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_get_addon_info", - side_effect=addon_info_side_effect, - ) as addon_info: - addon_info.return_value = {} - yield addon_info - - -@pytest.fixture(name="addon_running") -def mock_addon_running(addon_info): - """Mock add-on already running.""" - addon_info.return_value["state"] = "started" - return addon_info - - -@pytest.fixture(name="addon_installed") -def mock_addon_installed(addon_info): - """Mock add-on already installed but not running.""" - addon_info.return_value["state"] = "stopped" - addon_info.return_value["version"] = "1.0" - return addon_info - - -@pytest.fixture(name="addon_options") -def mock_addon_options(addon_info): - """Mock add-on options.""" - addon_info.return_value["options"] = {} - return addon_info.return_value["options"] - - -@pytest.fixture(name="set_addon_options_side_effect") -def set_addon_options_side_effect_fixture(): - """Return the set add-on options side effect.""" - return None - - -@pytest.fixture(name="set_addon_options") -def mock_set_addon_options(set_addon_options_side_effect): - """Mock set add-on options.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_set_addon_options", - side_effect=set_addon_options_side_effect, - ) as set_options: - yield set_options - - -@pytest.fixture(name="install_addon") -def mock_install_addon(): - """Mock install add-on.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_install_addon" - ) as install_addon: - yield install_addon - - -@pytest.fixture(name="start_addon_side_effect") -def start_addon_side_effect_fixture(): - """Return the set add-on options side effect.""" - return None - - -@pytest.fixture(name="start_addon") -def mock_start_addon(start_addon_side_effect): - """Mock start add-on.""" - with patch( - "homeassistant.components.zwave_js.config_flow.async_start_addon", - side_effect=start_addon_side_effect, - ) as start_addon: - yield start_addon - - @pytest.fixture(name="server_version_side_effect") def server_version_side_effect_fixture(): """Return the server version side effect.""" @@ -587,6 +507,49 @@ async def test_not_addon(hass, supervisor): assert len(mock_setup_entry.mock_calls) == 1 +async def test_addon_already_configured(hass, supervisor): + """Test add-on already configured leads to manual step.""" + entry = MockConfigEntry( + domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678 + ) + entry.add_to_hass(hass) + + await setup.async_setup_component(hass, "persistent_notification", {}) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "manual" + + with patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:3000", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == TITLE + assert result["data"] == { + "url": "ws://localhost:3000", + "usb_path": None, + "network_key": None, + "use_addon": False, + "integration_created_addon": False, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 2 + + @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running( hass, @@ -654,7 +617,7 @@ async def test_addon_running( None, None, None, - "addon_missing_discovery_info", + "addon_get_discovery_info_failed", ), ( {"config": ADDON_DISCOVERY_INFO}, diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 2a2f249c361854..6f60bbc0300490 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -1,9 +1,9 @@ """Test the Z-Wave JS init module.""" from copy import deepcopy -from unittest.mock import patch +from unittest.mock import call, patch import pytest -from zwave_js_server.exceptions import BaseZwaveJSServerError +from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion from zwave_js_server.model.node import Node from homeassistant.components.hassio.handler import HassioAPIError @@ -11,6 +11,7 @@ from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.config_entries import ( CONN_CLASS_LOCAL_PUSH, + DISABLED_USER, ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_RETRY, @@ -34,22 +35,6 @@ def connect_timeout_fixture(): yield timeout -@pytest.fixture(name="stop_addon") -def stop_addon_fixture(): - """Mock stop add-on.""" - with patch("homeassistant.components.hassio.async_stop_addon") as stop_addon: - yield stop_addon - - -@pytest.fixture(name="uninstall_addon") -def uninstall_addon_fixture(): - """Mock uninstall add-on.""" - with patch( - "homeassistant.components.hassio.async_uninstall_addon" - ) as uninstall_addon: - yield uninstall_addon - - async def test_entry_setup_unload(hass, client, integration): """Test the integration set up and unload.""" entry = integration @@ -367,7 +352,203 @@ async def test_existing_node_not_ready(hass, client, multisensor_6, device_regis ) -async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): +async def test_start_addon( + hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon +): + """Test start the Z-Wave JS add-on during entry setup.""" + device = "/test" + network_key = "abc123" + addon_options = { + "device": device, + "network_key": network_key, + } + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 0 + assert set_addon_options.call_count == 1 + assert set_addon_options.call_args == call( + hass, "core_zwave_js", {"options": addon_options} + ) + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_zwave_js") + + +async def test_install_addon( + hass, addon_installed, install_addon, addon_options, set_addon_options, start_addon +): + """Test install and start the Z-Wave JS add-on during entry setup.""" + addon_installed.return_value["version"] = None + device = "/test" + network_key = "abc123" + addon_options = { + "device": device, + "network_key": network_key, + } + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 1 + assert install_addon.call_args == call(hass, "core_zwave_js") + assert set_addon_options.call_count == 1 + assert set_addon_options.call_args == call( + hass, "core_zwave_js", {"options": addon_options} + ) + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_zwave_js") + + +@pytest.mark.parametrize("addon_info_side_effect", [HassioAPIError("Boom")]) +async def test_addon_info_failure( + hass, + addon_installed, + install_addon, + addon_options, + set_addon_options, + start_addon, +): + """Test failure to get add-on info for Z-Wave JS add-on during entry setup.""" + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={"use_addon": True, "usb_path": device, "network_key": network_key}, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert install_addon.call_count == 0 + assert start_addon.call_count == 0 + + +@pytest.mark.parametrize( + "addon_version, update_available, update_calls, update_addon_side_effect", + [ + ("1.0", True, 1, None), + ("1.0", False, 0, None), + ("1.0", True, 1, HassioAPIError("Boom")), + ], +) +async def test_update_addon( + hass, + client, + addon_info, + addon_installed, + addon_running, + create_shapshot, + update_addon, + addon_options, + addon_version, + update_available, + update_calls, + update_addon_side_effect, +): + """Test update the Z-Wave JS add-on during entry setup.""" + addon_info.return_value["version"] = addon_version + addon_info.return_value["update_available"] = update_available + update_addon.side_effect = update_addon_side_effect + client.connect.side_effect = InvalidServerVersion("Invalid version") + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={ + "url": "ws://host1:3001", + "use_addon": True, + "usb_path": device, + "network_key": network_key, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_SETUP_RETRY + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]}, + partial=True, + ) + assert update_addon.call_count == update_calls + + +@pytest.mark.parametrize( + "stop_addon_side_effect, entry_state", + [ + (None, ENTRY_STATE_NOT_LOADED), + (HassioAPIError("Boom"), ENTRY_STATE_LOADED), + ], +) +async def test_stop_addon( + hass, + client, + addon_installed, + addon_running, + addon_options, + stop_addon, + stop_addon_side_effect, + entry_state, +): + """Test stop the Z-Wave JS add-on on entry unload if entry is disabled.""" + stop_addon.side_effect = stop_addon_side_effect + device = "/test" + network_key = "abc123" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave JS", + connection_class=CONN_CLASS_LOCAL_PUSH, + data={ + "url": "ws://host1:3001", + "use_addon": True, + "usb_path": device, + "network_key": network_key, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_set_disabled_by(entry.entry_id, DISABLED_USER) + await hass.async_block_till_done() + + assert entry.state == entry_state + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + + +async def test_remove_entry( + hass, addon_installed, stop_addon, create_shapshot, uninstall_addon, caplog +): """Test remove the config entry.""" # test successful remove without created add-on entry = MockConfigEntry( @@ -398,10 +579,19 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_zwave_js") assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 stop_addon.reset_mock() + create_shapshot.reset_mock() uninstall_addon.reset_mock() # test add-on stop failure @@ -412,12 +602,39 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 0 assert uninstall_addon.call_count == 0 assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to stop the Z-Wave JS add-on" in caplog.text stop_addon.side_effect = None stop_addon.reset_mock() + create_shapshot.reset_mock() + uninstall_addon.reset_mock() + + # test create snapshot failure + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + create_shapshot.side_effect = HassioAPIError() + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) + assert uninstall_addon.call_count == 0 + assert entry.state == ENTRY_STATE_NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert "Failed to create a snapshot of the Z-Wave JS add-on" in caplog.text + create_shapshot.side_effect = None + stop_addon.reset_mock() + create_shapshot.reset_mock() uninstall_addon.reset_mock() # test add-on uninstall failure @@ -428,7 +645,15 @@ async def test_remove_entry(hass, stop_addon, uninstall_addon, caplog): await hass.config_entries.async_remove(entry.entry_id) assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_zwave_js") + assert create_shapshot.call_count == 1 + assert create_shapshot.call_args == call( + hass, + {"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]}, + partial=True, + ) assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_zwave_js") assert entry.state == ENTRY_STATE_NOT_LOADED assert len(hass.config_entries.async_entries(DOMAIN)) == 0 assert "Failed to uninstall the Z-Wave JS add-on" in caplog.text From d7f4416421202ebe5ee533e1b714789da85d1808 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 06:13:45 -0800 Subject: [PATCH 0963/1818] Fix Alexa doorbells (#47257) --- .../components/alexa/state_report.py | 30 +++++++------------ tests/components/alexa/test_state_report.py | 16 ++++++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index d66906810b2ef6..c34dc34f0dd224 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -73,10 +73,7 @@ async def async_entity_state_listener( if not should_report and interface.properties_proactively_reported(): should_report = True - if ( - interface.name() == "Alexa.DoorbellEventSource" - and new_state.state == STATE_ON - ): + if interface.name() == "Alexa.DoorbellEventSource": should_doorbell = True break @@ -84,27 +81,22 @@ async def async_entity_state_listener( return if should_doorbell: - should_report = False + if new_state.state == STATE_ON: + await async_send_doorbell_event_message( + hass, smart_home_config, alexa_changed_entity + ) + return - if should_report: - alexa_properties = list(alexa_changed_entity.serialize_properties()) - else: - alexa_properties = None + alexa_properties = list(alexa_changed_entity.serialize_properties()) if not checker.async_is_significant_change( new_state, extra_arg=alexa_properties ): return - if should_report: - await async_send_changereport_message( - hass, smart_home_config, alexa_changed_entity, alexa_properties - ) - - elif should_doorbell: - await async_send_doorbell_event_message( - hass, smart_home_config, alexa_changed_entity - ) + await async_send_changereport_message( + hass, smart_home_config, alexa_changed_entity, alexa_properties + ) return hass.helpers.event.async_track_state_change( MATCH_ALL, async_entity_state_listener @@ -246,7 +238,7 @@ async def async_send_delete_message(hass, config, entity_ids): async def async_send_doorbell_event_message(hass, config, alexa_entity): """Send a DoorbellPress event message for an Alexa entity. - https://developer.amazon.com/docs/smarthome/send-events-to-the-alexa-event-gateway.html + https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-doorbelleventsource.html """ token = await config.async_get_access_token() diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index a057eada531e42..2cbf8636d79c1b 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -175,6 +175,22 @@ async def test_doorbell_event(hass, aioclient_mock): assert call_json["event"]["payload"]["cause"]["type"] == "PHYSICAL_INTERACTION" assert call_json["event"]["endpoint"]["endpointId"] == "binary_sensor#test_doorbell" + hass.states.async_set( + "binary_sensor.test_doorbell", + "off", + {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + ) + + hass.states.async_set( + "binary_sensor.test_doorbell", + "on", + {"friendly_name": "Test Doorbell Sensor", "device_class": "occupancy"}, + ) + + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 2 + async def test_proactive_mode_filter_states(hass, aioclient_mock): """Test all the cases that filter states.""" From 6c5c3233f1ca81c8d311b6c95354820b4b1d5c20 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 16:10:30 -0500 Subject: [PATCH 0964/1818] Add raw values to zwave_js value notification event (#47258) * add value_raw to value notification event that always shows the untranslated state value * add property key and property to event params --- homeassistant/components/zwave_js/__init__.py | 8 +++++++- homeassistant/components/zwave_js/const.py | 3 +++ tests/components/zwave_js/test_events.py | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index c19b1b355a4137..d4e349645cf58b 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -28,10 +28,13 @@ ATTR_LABEL, ATTR_NODE_ID, ATTR_PARAMETERS, + ATTR_PROPERTY, + ATTR_PROPERTY_KEY, ATTR_PROPERTY_KEY_NAME, ATTR_PROPERTY_NAME, ATTR_TYPE, ATTR_VALUE, + ATTR_VALUE_RAW, CONF_INTEGRATION_CREATED_ADDON, CONF_NETWORK_KEY, CONF_USB_PATH, @@ -220,7 +223,7 @@ def async_on_node_removed(node: ZwaveNode) -> None: def async_on_value_notification(notification: ValueNotification) -> None: """Relay stateless value notification events from Z-Wave nodes to hass.""" device = dev_reg.async_get_device({get_device_id(client, notification.node)}) - value = notification.value + raw_value = value = notification.value if notification.metadata.states: value = notification.metadata.states.get(str(value), value) hass.bus.async_fire( @@ -235,9 +238,12 @@ def async_on_value_notification(notification: ValueNotification) -> None: ATTR_COMMAND_CLASS: notification.command_class, ATTR_COMMAND_CLASS_NAME: notification.command_class_name, ATTR_LABEL: notification.metadata.label, + ATTR_PROPERTY: notification.property_, ATTR_PROPERTY_NAME: notification.property_name, + ATTR_PROPERTY_KEY: notification.property_key, ATTR_PROPERTY_KEY_NAME: notification.property_key_name, ATTR_VALUE: value, + ATTR_VALUE_RAW: raw_value, }, ) diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index e3f20366ab08d7..ffd6031349a966 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -34,12 +34,15 @@ ATTR_ENDPOINT = "endpoint" ATTR_LABEL = "label" ATTR_VALUE = "value" +ATTR_VALUE_RAW = "value_raw" ATTR_COMMAND_CLASS = "command_class" ATTR_COMMAND_CLASS_NAME = "command_class_name" ATTR_TYPE = "type" ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" +ATTR_PROPERTY = "property" +ATTR_PROPERTY_KEY = "property_key" ATTR_PARAMETERS = "parameters" # service constants diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index 2a347f6afea9de..e40782270a9cfb 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -47,6 +47,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[0].data["command_class_name"] == "Basic" assert events[0].data["label"] == "Event value" assert events[0].data["value"] == 255 + assert events[0].data["value_raw"] == 255 # Publish fake Scene Activation value notification event = Event( @@ -82,6 +83,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[1].data["command_class_name"] == "Scene Activation" assert events[1].data["label"] == "Scene ID" assert events[1].data["value"] == 16 + assert events[1].data["value_raw"] == 16 # Publish fake Central Scene value notification event = Event( @@ -128,6 +130,7 @@ async def test_scenes(hass, hank_binary_switch, integration, client): assert events[2].data["command_class_name"] == "Central Scene" assert events[2].data["label"] == "Scene 001" assert events[2].data["value"] == "KeyPressed3x" + assert events[2].data["value_raw"] == 4 async def test_notifications(hass, hank_binary_switch, integration, client): From 7a6edf9725326764c26ba2c251e64602f27160b6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Mar 2021 14:28:31 +0100 Subject: [PATCH 0965/1818] Make MQTT number respect retain setting (#47270) --- homeassistant/components/mqtt/number.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 969eb254072389..aa24f81eb6967e 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -28,6 +28,7 @@ subscription, ) from .. import mqtt +from .const import CONF_RETAIN from .debug_info import log_messages from .mixins import ( MQTT_AVAILABILITY_SCHEMA, @@ -161,6 +162,7 @@ async def async_set_value(self, value: float) -> None: self._config[CONF_COMMAND_TOPIC], current_number, self._config[CONF_QOS], + self._config[CONF_RETAIN], ) @property From b8bc0a7fe9b5b4577e167e2e01a5420a0f7ef645 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 2 Mar 2021 12:19:04 -0700 Subject: [PATCH 0966/1818] Bump simplisafe-python to 9.6.9 (#47273) --- 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 6122428ea98d32..45deb938b59c27 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.6.8"], + "requirements": ["simplisafe-python==9.6.9"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index d63ab3c5b8318b..a2d1c502081952 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2044,7 +2044,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.6.8 +simplisafe-python==9.6.9 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef6c0706c87493..5e65e950654089 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1051,7 +1051,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.6.8 +simplisafe-python==9.6.9 # homeassistant.components.slack slackclient==2.5.0 From 4b9c1489893db6968b33ad5f141a5f72647b1636 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 2 Mar 2021 22:02:59 +0100 Subject: [PATCH 0967/1818] Fix issue when setting boost preset for a turned off Netatmo thermostat (#47275) --- homeassistant/components/netatmo/climate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 91026c40c2fa29..a53f7f9fb089e1 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -352,6 +352,9 @@ def set_hvac_mode(self, hvac_mode: str) -> None: def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" + if self.hvac_mode == HVAC_MODE_OFF: + self.turn_on() + if self.target_temperature == 0: self._home_status.set_room_thermpoint( self._id, From ebb9008c270b51d3a56a42e2469c4ad0d34be00d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Mar 2021 20:15:09 +0100 Subject: [PATCH 0968/1818] Update frontend to 20210302.0 (#47278) --- 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 e8f9ff2698d71b..e7d7723a510667 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==20210301.0" + "home-assistant-frontend==20210302.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5aaa5b1b4699da..44e6195317de64 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a2d1c502081952..c4962d2440094e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5e65e950654089..ed722a0725630b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210301.0 +home-assistant-frontend==20210302.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 39b9ad0ca0ed4f3e2ceba10b37724417deea3878 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 2 Mar 2021 15:12:30 -0500 Subject: [PATCH 0969/1818] Update ZHA dependencies (#47282) --- 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 d7bb0dbe5bc965..7d367c3dc00867 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.21.0", + "bellows==0.22.0", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index c4962d2440094e..e97062e4c11442 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.21.0 +bellows==0.22.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ed722a0725630b..2c9ac79095d25e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.21.0 +bellows==0.22.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From eb981fb007f93998a9c5da7c94d03ef41fad5a55 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 16:25:09 -0500 Subject: [PATCH 0970/1818] Convert climacell forecast timestamp to isoformat so that UI shows the right times (#47286) --- homeassistant/components/climacell/weather.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index da3282108a5ce6..c77bbfbd50a305 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -1,4 +1,5 @@ """Weather component that handles meteorological data for your location.""" +from datetime import datetime import logging from typing import Any, Callable, Dict, List, Optional @@ -80,7 +81,7 @@ def _translate_condition( def _forecast_dict( hass: HomeAssistantType, - time: str, + forecast_dt: datetime, use_datetime: bool, condition: str, precipitation: Optional[float], @@ -92,10 +93,7 @@ def _forecast_dict( ) -> Dict[str, Any]: """Return formatted Forecast dict from ClimaCell forecast data.""" if use_datetime: - translated_condition = _translate_condition( - condition, - is_up(hass, dt_util.as_utc(dt_util.parse_datetime(time))), - ) + translated_condition = _translate_condition(condition, is_up(hass, forecast_dt)) else: translated_condition = _translate_condition(condition, True) @@ -112,7 +110,7 @@ def _forecast_dict( wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) data = { - ATTR_FORECAST_TIME: time, + ATTR_FORECAST_TIME: forecast_dt.isoformat(), ATTR_FORECAST_CONDITION: translated_condition, ATTR_FORECAST_PRECIPITATION: precipitation, ATTR_FORECAST_PRECIPITATION_PROBABILITY: precipitation_probability, @@ -246,7 +244,9 @@ def forecast(self): # Set default values (in cases where keys don't exist), None will be # returned. Override properties per forecast type as needed for forecast in self.coordinator.data[FORECASTS][self.forecast_type]: - timestamp = self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + forecast_dt = dt_util.parse_datetime( + self._get_cc_value(forecast, CC_ATTR_TIMESTAMP) + ) use_datetime = True condition = self._get_cc_value(forecast, CC_ATTR_CONDITION) precipitation = self._get_cc_value(forecast, CC_ATTR_PRECIPITATION) @@ -290,7 +290,7 @@ def forecast(self): forecasts.append( _forecast_dict( self.hass, - timestamp, + forecast_dt, use_datetime, condition, precipitation, From f74b88a29cdc3bff10bba03bafe4b49328bc66b6 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 17:09:50 -0500 Subject: [PATCH 0971/1818] Bump zwave-js-server-python to 0.20.1 (#47289) --- 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 9e57a3b72e208a..c812515a179cce 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.20.0"], + "requirements": ["zwave-js-server-python==0.20.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index e97062e4c11442..aa5c9d2bb855e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2397,4 +2397,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.0 +zwave-js-server-python==0.20.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2c9ac79095d25e..28b2d0ca08c928 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,4 +1234,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.0 +zwave-js-server-python==0.20.1 From da2c7dc743bfa25c9460604ae3b4fc13460988f5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Mar 2021 22:37:27 +0000 Subject: [PATCH 0972/1818] Bumped version to 2021.3.0b7 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2edbfa33a10f25..47fb090305d897 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0b7" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From ce8871ef598d0670fbdff6288d93425a4a4846ab Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Wed, 3 Mar 2021 00:50:40 +0100 Subject: [PATCH 0973/1818] KNX remove custom deprecation warnings (#47238) --- homeassistant/components/knx/__init__.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 697b4cfb5244df..e74bae7442cd1f 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -77,6 +77,8 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( + # deprecated since 2021.3 + cv.deprecated(CONF_KNX_CONFIG), # deprecated since 2021.2 cv.deprecated(CONF_KNX_FIRE_EVENT), cv.deprecated("fire_event_filter", replacement_key=CONF_KNX_EVENT_FILTER), @@ -236,21 +238,6 @@ async def async_setup(hass, config): discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) ) - if not knx_module.xknx.devices: - _LOGGER.warning( - "No KNX devices are configured. Please read " - "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" - ) - - # deprecation warning since 2021.3 - if CONF_KNX_CONFIG in config[DOMAIN]: - _LOGGER.warning( - "The 'config_file' option is deprecated. Please replace it with Home Assistant config schema " - "directly in `configuration.yaml` (see https://www.home-assistant.io/integrations/knx/). \n" - "An online configuration converter tool for your `%s` is available at https://xknx.io/config-converter/", - config[DOMAIN][CONF_KNX_CONFIG], - ) - hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, From 6019bcf9d140458dba3b7eb39d99e9e2d4f2354a Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:55:10 -0500 Subject: [PATCH 0974/1818] Correct climacell device info (#47292) --- homeassistant/components/climacell/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index 6f1ac730f2fb16..c3c3f702780f13 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -256,7 +256,8 @@ def device_info(self) -> Dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])}, - "name": self.name, + "name": "ClimaCell", "manufacturer": "ClimaCell", + "sw_version": "v3", "entry_type": "service", } From 32fe4fa37854cb38ba4f64227c5a751a668cb1cc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Mar 2021 17:57:36 -1000 Subject: [PATCH 0975/1818] Add activity properties to remote entity model (#47237) --- homeassistant/components/harmony/const.py | 2 -- homeassistant/components/harmony/remote.py | 20 ++++++++++++---- homeassistant/components/remote/__init__.py | 26 ++++++++++++++++++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/harmony/const.py b/homeassistant/components/harmony/const.py index ee4a454847e629..d7b4d8248ed139 100644 --- a/homeassistant/components/harmony/const.py +++ b/homeassistant/components/harmony/const.py @@ -6,9 +6,7 @@ UNIQUE_ID = "unique_id" ACTIVITY_POWER_OFF = "PowerOff" HARMONY_OPTIONS_UPDATE = "harmony_options_update" -ATTR_ACTIVITY_LIST = "activity_list" ATTR_DEVICES_LIST = "devices_list" ATTR_LAST_ACTIVITY = "last_activity" -ATTR_CURRENT_ACTIVITY = "current_activity" ATTR_ACTIVITY_STARTING = "activity_starting" PREVIOUS_ACTIVE_ACTIVITY = "Previous Active Activity" diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 9b3d53c21fa81f..55af62e6d392ce 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -12,6 +12,7 @@ ATTR_HOLD_SECS, ATTR_NUM_REPEATS, DEFAULT_DELAY_SECS, + SUPPORT_ACTIVITY, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID @@ -24,9 +25,7 @@ from .connection_state import ConnectionStateMixin from .const import ( ACTIVITY_POWER_OFF, - ATTR_ACTIVITY_LIST, ATTR_ACTIVITY_STARTING, - ATTR_CURRENT_ACTIVITY, ATTR_DEVICES_LIST, ATTR_LAST_ACTIVITY, DOMAIN, @@ -100,6 +99,11 @@ def __init__(self, data, activity, delay_secs, out_path): self._last_activity = None self._config_path = out_path + @property + def supported_features(self): + """Supported features for the remote.""" + return SUPPORT_ACTIVITY + async def _async_update_options(self, data): """Change options when the options flow does.""" if ATTR_DELAY_SECS in data: @@ -178,13 +182,21 @@ def should_poll(self): """Return the fact that we should not be polled.""" return False + @property + def current_activity(self): + """Return the current activity.""" + return self._current_activity + + @property + def activity_list(self): + """Return the available activities.""" + return self._data.activity_names + @property def device_state_attributes(self): """Add platform specific attributes.""" return { ATTR_ACTIVITY_STARTING: self._activity_starting, - ATTR_CURRENT_ACTIVITY: self._current_activity, - ATTR_ACTIVITY_LIST: self._data.activity_names, ATTR_DEVICES_LIST: self._data.device_names, ATTR_LAST_ACTIVITY: self._last_activity, } diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index b3b84669ed1704..720f7c5ff166c1 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import functools as ft import logging -from typing import Any, Iterable, cast +from typing import Any, Dict, Iterable, List, Optional, cast import voluptuous as vol @@ -30,6 +30,8 @@ _LOGGER = logging.getLogger(__name__) ATTR_ACTIVITY = "activity" +ATTR_ACTIVITY_LIST = "activity_list" +ATTR_CURRENT_ACTIVITY = "current_activity" ATTR_COMMAND_TYPE = "command_type" ATTR_DEVICE = "device" ATTR_NUM_REPEATS = "num_repeats" @@ -56,6 +58,7 @@ SUPPORT_LEARN_COMMAND = 1 SUPPORT_DELETE_COMMAND = 2 +SUPPORT_ACTIVITY = 4 REMOTE_SERVICE_ACTIVITY_SCHEMA = make_entity_service_schema( {vol.Optional(ATTR_ACTIVITY): cv.string} @@ -143,6 +146,27 @@ def supported_features(self) -> int: """Flag supported features.""" return 0 + @property + def current_activity(self) -> Optional[str]: + """Active activity.""" + return None + + @property + def activity_list(self) -> Optional[List[str]]: + """List of available activities.""" + return None + + @property + def state_attributes(self) -> Optional[Dict[str, Any]]: + """Return optional state attributes.""" + if not self.supported_features & SUPPORT_ACTIVITY: + return None + + return { + ATTR_ACTIVITY_LIST: self.activity_list, + ATTR_CURRENT_ACTIVITY: self.current_activity, + } + def send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send commands to a device.""" raise NotImplementedError() From 9022b909459946bb7b880629c318c4b8529fe383 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 2 Mar 2021 22:04:17 -0800 Subject: [PATCH 0976/1818] bump python-smarttub to 0.0.19 (#47294) --- homeassistant/components/smarttub/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smarttub/manifest.json b/homeassistant/components/smarttub/manifest.json index 292ce81b4fb431..2425268e05c75b 100644 --- a/homeassistant/components/smarttub/manifest.json +++ b/homeassistant/components/smarttub/manifest.json @@ -6,7 +6,7 @@ "dependencies": [], "codeowners": ["@mdz"], "requirements": [ - "python-smarttub==0.0.17" + "python-smarttub==0.0.19" ], "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index b79401f72df24a..67b52a29bed076 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1820,7 +1820,7 @@ python-qbittorrent==0.4.2 python-ripple-api==0.0.3 # homeassistant.components.smarttub -python-smarttub==0.0.17 +python-smarttub==0.0.19 # homeassistant.components.sochain python-sochain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9e32b02bf397a4..6d9307eb52bba3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -945,7 +945,7 @@ python-nest==4.1.0 python-openzwave-mqtt[mqtt-client]==1.4.0 # homeassistant.components.smarttub -python-smarttub==0.0.17 +python-smarttub==0.0.19 # homeassistant.components.songpal python-songpal==0.12 From 8f3c2573e26755c6113b2efd1cecf00f51076631 Mon Sep 17 00:00:00 2001 From: Milan Meulemans Date: Wed, 3 Mar 2021 10:12:26 +0100 Subject: [PATCH 0977/1818] Remove name from keenetic-ndms2 strings (#47113) --- homeassistant/components/keenetic_ndms2/strings.json | 1 - homeassistant/components/keenetic_ndms2/translations/en.json | 1 - 2 files changed, 2 deletions(-) diff --git a/homeassistant/components/keenetic_ndms2/strings.json b/homeassistant/components/keenetic_ndms2/strings.json index 15629ba0f2f1f1..0dc1c9c302fc40 100644 --- a/homeassistant/components/keenetic_ndms2/strings.json +++ b/homeassistant/components/keenetic_ndms2/strings.json @@ -4,7 +4,6 @@ "user": { "title": "Set up Keenetic NDMS2 Router", "data": { - "name": "Name", "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", diff --git a/homeassistant/components/keenetic_ndms2/translations/en.json b/homeassistant/components/keenetic_ndms2/translations/en.json index e95f2f740efd6c..5a946751ff4c1b 100644 --- a/homeassistant/components/keenetic_ndms2/translations/en.json +++ b/homeassistant/components/keenetic_ndms2/translations/en.json @@ -10,7 +10,6 @@ "user": { "data": { "host": "Host", - "name": "Name", "password": "Password", "port": "Port", "username": "Username" From b147ba137731e6e2cc5ee4e5254b8123bf1ed08d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Mar 2021 10:20:48 +0100 Subject: [PATCH 0978/1818] Correct gogogate2 battery sensor attributes (#47302) --- homeassistant/components/gogogate2/cover.py | 6 ++---- homeassistant/components/gogogate2/sensor.py | 7 +++---- tests/components/gogogate2/test_cover.py | 18 ++++++++++++++++++ tests/components/gogogate2/test_sensor.py | 20 ++++++++++++++++++++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index f2e05b10599c7d..37c362893c8f7c 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -122,8 +122,6 @@ async def async_close_cover(self, **kwargs): await self._api.async_close_door(self._get_door().door_id) @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes.""" - attrs = super().state_attributes - attrs["door_id"] = self._get_door().door_id - return attrs + return {"door_id": self._get_door().door_id} diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index 55aacd2fdbb6c5..c4f6cff9dfa2b3 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -50,10 +50,9 @@ def state(self): return door.voltage # This is a percentage, not an absolute voltage @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes.""" - attrs = super().state_attributes or {} door = self._get_door() if door.sensorid is not None: - attrs["sensorid"] = door.door_id - return attrs + return {"door_id": door.door_id, "sensor_id": door.sensorid} + return None diff --git a/tests/components/gogogate2/test_cover.py b/tests/components/gogogate2/test_cover.py index 31810cb67d0786..41b4368c64078d 100644 --- a/tests/components/gogogate2/test_cover.py +++ b/tests/components/gogogate2/test_cover.py @@ -21,6 +21,8 @@ DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE, DOMAIN as COVER_DOMAIN, + SUPPORT_CLOSE, + SUPPORT_OPEN, ) from homeassistant.components.gogogate2.const import ( DEVICE_TYPE_GOGOGATE2, @@ -319,6 +321,13 @@ def info_response(door_status: DoorStatus) -> GogoGate2InfoResponse: wifi=Wifi(SSID="", linkquality="", signal=""), ) + expected_attributes = { + "device_class": "garage", + "door_id": 1, + "friendly_name": "Door1", + "supported_features": SUPPORT_CLOSE | SUPPORT_OPEN, + } + api = MagicMock(GogoGate2Api) api.async_activate.return_value = GogoGate2ActivateResponse(result=True) api.async_info.return_value = info_response(DoorStatus.OPENED) @@ -339,6 +348,7 @@ def info_response(door_status: DoorStatus) -> GogoGate2InfoResponse: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("cover.door1").state == STATE_OPEN + assert dict(hass.states.get("cover.door1").attributes) == expected_attributes api.async_info.return_value = info_response(DoorStatus.CLOSED) await hass.services.async_call( @@ -376,6 +386,13 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: """Test availability.""" closed_door_response = _mocked_ismartgate_closed_door_response() + expected_attributes = { + "device_class": "garage", + "door_id": 1, + "friendly_name": "Door1", + "supported_features": SUPPORT_CLOSE | SUPPORT_OPEN, + } + api = MagicMock(ISmartGateApi) api.async_info.return_value = closed_door_response ismartgateapi_mock.return_value = api @@ -416,6 +433,7 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("cover.door1").state == STATE_CLOSED + assert dict(hass.states.get("cover.door1").attributes) == expected_attributes @patch("homeassistant.components.gogogate2.common.ISmartGateApi") diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py index 0bd67dfc92a046..f8eb9bf88b73ff 100644 --- a/tests/components/gogogate2/test_sensor.py +++ b/tests/components/gogogate2/test_sensor.py @@ -164,6 +164,13 @@ def _mocked_ismartgate_sensor_response(battery_level: int): async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: """Test data update.""" + expected_attributes = { + "device_class": "battery", + "door_id": 1, + "friendly_name": "Door1 battery", + "sensor_id": "ABCD", + } + api = MagicMock(GogoGate2Api) api.async_activate.return_value = GogoGate2ActivateResponse(result=True) api.async_info.return_value = _mocked_gogogate_sensor_response(25) @@ -192,6 +199,9 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: assert hass.states.get("cover.door2") assert hass.states.get("cover.door2") assert hass.states.get("sensor.door1_battery").state == "25" + assert ( + dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + ) assert hass.states.get("sensor.door2_battery") is None assert hass.states.get("sensor.door2_battery") is None @@ -212,6 +222,13 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: @patch("homeassistant.components.gogogate2.common.ISmartGateApi") async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: """Test availability.""" + expected_attributes = { + "device_class": "battery", + "door_id": 1, + "friendly_name": "Door1 battery", + "sensor_id": "ABCD", + } + sensor_response = _mocked_ismartgate_sensor_response(35) api = MagicMock(ISmartGateApi) api.async_info.return_value = sensor_response @@ -259,3 +276,6 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == "35" + assert ( + dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + ) From cc72cf0c0d0af3a2121193d34a8ad3dc18100193 Mon Sep 17 00:00:00 2001 From: Nick Adams <4012017+Nick-Adams-AU@users.noreply.github.com> Date: Wed, 3 Mar 2021 20:51:40 +1000 Subject: [PATCH 0979/1818] Update izone services.yaml and remove entity_id from schema. (#47305) Co-authored-by: Franck Nijhof --- homeassistant/components/izone/climate.py | 16 ++++----- homeassistant/components/izone/services.yaml | 38 +++++++++++++++----- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index ef054bcfb29102..77ce9cb24528f4 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -26,14 +26,13 @@ ) from homeassistant.const import ( ATTR_TEMPERATURE, - CONF_ENTITY_ID, CONF_EXCLUDE, PRECISION_HALVES, PRECISION_TENTHS, TEMP_CELSIUS, ) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -63,14 +62,11 @@ IZONE_SERVICE_AIRFLOW_MIN = "airflow_min" IZONE_SERVICE_AIRFLOW_MAX = "airflow_max" -IZONE_SERVICE_AIRFLOW_SCHEMA = vol.Schema( - { - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_AIRFLOW): vol.All( - vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow" - ), - } -) +IZONE_SERVICE_AIRFLOW_SCHEMA = { + vol.Required(ATTR_AIRFLOW): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100), msg="invalid airflow" + ), +} async def async_setup_entry( diff --git a/homeassistant/components/izone/services.yaml b/homeassistant/components/izone/services.yaml index 14aaa69349a385..d03ad66421a279 100644 --- a/homeassistant/components/izone/services.yaml +++ b/homeassistant/components/izone/services.yaml @@ -1,18 +1,40 @@ airflow_min: + name: Set minimum airflow description: Set the airflow minimum percent for a zone + target: + entity: + integration: izone + domain: climate fields: - entity_id: - description: iZone Zone entity - example: "climate.bed_1" airflow: + name: Percent description: Airflow percent in 5% increments - example: "95" + required: true + example: 95 + selector: + number: + min: 0 + max: 100 + step: 5 + unit_of_measurement: "%" + mode: slider airflow_max: + name: Set maximum airflow description: Set the airflow maximum percent for a zone + target: + entity: + integration: izone + domain: climate fields: - entity_id: - description: iZone Zone entity - example: "climate.bed_1" airflow: + name: Percent description: Airflow percent in 5% increments - example: "95" + required: true + example: 95 + selector: + number: + min: 0 + max: 100 + step: 5 + unit_of_measurement: "%" + mode: slider From c192a44e870667c5f0a2ff9160b5133c1d6d2590 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Mar 2021 14:35:58 +0100 Subject: [PATCH 0980/1818] Update frontend to 20210302.3 (#47310) --- 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 e7d7723a510667..4d4127fc2f2795 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==20210302.0" + "home-assistant-frontend==20210302.3" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 44e6195317de64..752a3755169c5f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 67b52a29bed076..c1f3d577251392 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6d9307eb52bba3..a27ec0f8051e86 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 099c9c59cb40f7e1dfdd33ef60aaf679d3057e7c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 3 Mar 2021 14:37:36 +0100 Subject: [PATCH 0981/1818] Fix Supervisor platform coordinator data lookup (#47308) --- homeassistant/components/hassio/binary_sensor.py | 2 +- homeassistant/components/hassio/sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py index c3daaa07f2843f..2208f3fb580867 100644 --- a/homeassistant/components/hassio/binary_sensor.py +++ b/homeassistant/components/hassio/binary_sensor.py @@ -23,7 +23,7 @@ async def async_setup_entry( HassioAddonBinarySensor( coordinator, addon, ATTR_UPDATE_AVAILABLE, "Update Available" ) - for addon in coordinator.data.values() + for addon in coordinator.data["addons"].values() ] if coordinator.is_hass_os: entities.append( diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index 857f4831587af2..711a2a4630005f 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -24,7 +24,7 @@ async def async_setup_entry( (ATTR_VERSION, "Version"), (ATTR_VERSION_LATEST, "Newest Version"), ): - for addon in coordinator.data.values(): + for addon in coordinator.data["addons"].values(): entities.append( HassioAddonSensor(coordinator, addon, attribute_name, sensor_name) ) From 24919e99b81aa99bba0e5962f957af7a48583ccc Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:55:10 -0500 Subject: [PATCH 0982/1818] Correct climacell device info (#47292) --- homeassistant/components/climacell/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index d6bf0ec4e124d3..b6e70ab56e814e 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -256,7 +256,8 @@ def device_info(self) -> Dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])}, - "name": self.name, + "name": "ClimaCell", "manufacturer": "ClimaCell", + "sw_version": "v3", "entry_type": "service", } From 15c89ebada8a41e43891c885569ecde73d79778d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Mar 2021 14:35:58 +0100 Subject: [PATCH 0983/1818] Update frontend to 20210302.3 (#47310) --- 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 e7d7723a510667..4d4127fc2f2795 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==20210302.0" + "home-assistant-frontend==20210302.3" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 44e6195317de64..752a3755169c5f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index aa5c9d2bb855e6..ffbd439ee2e5c2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 28b2d0ca08c928..318b04e5e70e3b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.0 +home-assistant-frontend==20210302.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 7626aa5c945556e6cd03483831a0e327731411c6 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 3 Mar 2021 18:51:58 +0100 Subject: [PATCH 0984/1818] Philips JS correct post review comments (#47247) --- .coveragerc | 1 + .../components/philips_js/__init__.py | 2 +- .../components/philips_js/config_flow.py | 29 +++++++++---------- .../components/philips_js/media_player.py | 12 ++++---- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.coveragerc b/.coveragerc index 36d4399466fcc9..5d502a7503aa66 100644 --- a/.coveragerc +++ b/.coveragerc @@ -725,6 +725,7 @@ omit = homeassistant/components/pencom/switch.py homeassistant/components/philips_js/__init__.py homeassistant/components/philips_js/media_player.py + homeassistant/components/philips_js/remote.py homeassistant/components/pi_hole/sensor.py homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py homeassistant/components/pi4ioe5v9xxxx/switch.py diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index b3abaa2813296e..088e29e4e26b6e 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -150,7 +150,7 @@ def _async_notify_schedule(self): and self.api.on and self.api.notify_change_supported ): - self._notify_future = self.hass.loop.create_task(self._notify_task()) + self._notify_future = asyncio.create_task(self._notify_task()) @callback def async_remove_listener(self, update_callback: CALLBACK_TYPE) -> None: diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 778bcba282bbbf..39150be9ce8562 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,6 +1,6 @@ """Config flow for Philips TV integration.""" import platform -from typing import Any, Dict, Optional, Tuple, TypedDict +from typing import Any, Dict, Optional, Tuple from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol @@ -23,12 +23,6 @@ ) -class FlowUserDict(TypedDict): - """Data for user step.""" - - host: str - - async def validate_input( hass: core.HomeAssistant, host: str, api_version: int ) -> Tuple[Dict, PhilipsTV]: @@ -50,11 +44,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - _current = {} - _hub: PhilipsTV - _pair_state: Any + def __init__(self) -> None: + """Initialize flow.""" + super().__init__() + self._current = {} + self._hub: Optional[PhilipsTV] = None + self._pair_state: Any = None - async def async_step_import(self, conf: Dict[str, Any]): + async def async_step_import(self, conf: dict) -> dict: """Import a configuration from config.yaml.""" for entry in self._async_current_entries(): if entry.data[CONF_HOST] == conf[CONF_HOST]: @@ -75,7 +72,7 @@ async def _async_create_current(self): data=self._current, ) - async def async_step_pair(self, user_input: Optional[Dict] = None): + async def async_step_pair(self, user_input: Optional[dict] = None) -> dict: """Attempt to pair with device.""" assert self._hub @@ -96,7 +93,7 @@ async def async_step_pair(self, user_input: Optional[Dict] = None): "native", ) except PairingFailure as exc: - LOGGER.debug(str(exc)) + LOGGER.debug(exc) return self.async_abort( reason="pairing_failure", description_placeholders={"error_id": exc.data.get("error_id")}, @@ -110,7 +107,7 @@ async def async_step_pair(self, user_input: Optional[Dict] = None): self._pair_state, user_input[CONF_PIN] ) except PairingFailure as exc: - LOGGER.debug(str(exc)) + LOGGER.debug(exc) if exc.data.get("error_id") == "INVALID_PIN": errors[CONF_PIN] = "invalid_pin" return self.async_show_form( @@ -126,7 +123,7 @@ async def async_step_pair(self, user_input: Optional[Dict] = None): self._current[CONF_PASSWORD] = password return await self._async_create_current() - async def async_step_user(self, user_input: Optional[FlowUserDict] = None): + async def async_step_user(self, user_input: Optional[dict] = None) -> dict: """Handle the initial step.""" errors = {} if user_input: @@ -136,7 +133,7 @@ async def async_step_user(self, user_input: Optional[FlowUserDict] = None): self.hass, user_input[CONF_HOST], user_input[CONF_API_VERSION] ) except ConnectionFailure as exc: - LOGGER.error(str(exc)) + LOGGER.error(exc) errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected exception") diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 2b2714b20ce9d0..38521989567a95 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -295,8 +295,8 @@ def media_content_id(self): def media_image_url(self): """Image url of current playing media.""" if self._media_content_id and self._media_content_type in ( - MEDIA_CLASS_APP, - MEDIA_CLASS_CHANNEL, + MEDIA_TYPE_APP, + MEDIA_TYPE_CHANNEL, ): return self.get_browse_image_url( self._media_content_type, self._media_content_id, media_image_id=None @@ -384,7 +384,7 @@ async def async_browse_media_channels(self, expanded): media_class=MEDIA_CLASS_DIRECTORY, media_content_id="channels", media_content_type=MEDIA_TYPE_CHANNELS, - children_media_class=MEDIA_TYPE_CHANNEL, + children_media_class=MEDIA_CLASS_CHANNEL, can_play=False, can_expand=True, children=children, @@ -427,7 +427,7 @@ def get_name(channel): media_class=MEDIA_CLASS_DIRECTORY, media_content_id=f"favorites/{list_id}", media_content_type=MEDIA_TYPE_CHANNELS, - children_media_class=MEDIA_TYPE_CHANNEL, + children_media_class=MEDIA_CLASS_CHANNEL, can_play=False, can_expand=True, children=children, @@ -458,7 +458,7 @@ async def async_browse_media_applications(self, expanded): media_class=MEDIA_CLASS_DIRECTORY, media_content_id="applications", media_content_type=MEDIA_TYPE_APPS, - children_media_class=MEDIA_TYPE_APP, + children_media_class=MEDIA_CLASS_APP, can_play=False, can_expand=True, children=children, @@ -479,7 +479,7 @@ async def async_browse_media_favorite_lists(self, expanded): media_class=MEDIA_CLASS_DIRECTORY, media_content_id="favorite_lists", media_content_type=MEDIA_TYPE_CHANNELS, - children_media_class=MEDIA_TYPE_CHANNEL, + children_media_class=MEDIA_CLASS_CHANNEL, can_play=False, can_expand=True, children=children, From 504e5b77ca966ff153fb68a314bf556b5b815d42 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Mar 2021 19:12:37 +0100 Subject: [PATCH 0985/1818] Improve behaviour when disabling or enabling config entries (#47301) --- homeassistant/config_entries.py | 32 ++++++--- homeassistant/const.py | 1 - homeassistant/helpers/device_registry.py | 71 +++++++++---------- homeassistant/helpers/entity_registry.py | 87 +++++++++++------------- tests/helpers/test_entity_registry.py | 2 +- 5 files changed, 97 insertions(+), 96 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d3e8f66abc4c91..f74acede50729f 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -11,10 +11,9 @@ import attr from homeassistant import data_entry_flow, loader -from homeassistant.const import EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.event import Event from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.setup import async_process_deps_reqs, async_setup_component @@ -807,12 +806,21 @@ async def async_set_disabled_by( entry.disabled_by = disabled_by self._async_schedule_save() - # Unload the config entry, then fire an event + dev_reg = device_registry.async_get(self.hass) + ent_reg = entity_registry.async_get(self.hass) + + if not entry.disabled_by: + # The config entry will no longer be disabled, enable devices and entities + device_registry.async_config_entry_disabled_by_changed(dev_reg, entry) + entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry) + + # Load or unload the config entry reload_result = await self.async_reload(entry_id) - self.hass.bus.async_fire( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} - ) + if entry.disabled_by: + # The config entry has been disabled, disable devices and entities + device_registry.async_config_entry_disabled_by_changed(dev_reg, entry) + entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry) return reload_result @@ -1250,8 +1258,16 @@ async def _handle_reload(self, _now: Any) -> None: @callback def _handle_entry_updated_filter(event: Event) -> bool: - """Handle entity registry entry update filter.""" - if event.data["action"] != "update" or "disabled_by" not in event.data["changes"]: + """Handle entity registry entry update filter. + + Only handle changes to "disabled_by". + If "disabled_by" was DISABLED_CONFIG_ENTRY, reload is not needed. + """ + if ( + event.data["action"] != "update" + or "disabled_by" not in event.data["changes"] + or event.data["changes"]["disabled_by"] == entity_registry.DISABLED_CONFIG_ENTRY + ): return False return True diff --git a/homeassistant/const.py b/homeassistant/const.py index 712f7ede0d3fbe..1076b962f2a0d7 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -202,7 +202,6 @@ # #### EVENTS #### EVENT_CALL_SERVICE = "call_service" EVENT_COMPONENT_LOADED = "component_loaded" -EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED = "config_entry_disabled_by_updated" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close" EVENT_HOMEASSISTANT_START = "homeassistant_start" diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 705f6cdd89a5a5..d311538f27f23a 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -6,10 +6,7 @@ import attr -from homeassistant.const import ( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - EVENT_HOMEASSISTANT_STARTED, -) +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import Event, callback from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util @@ -20,6 +17,8 @@ # mypy: disallow_any_generics if TYPE_CHECKING: + from homeassistant.config_entries import ConfigEntry + from . import entity_registry _LOGGER = logging.getLogger(__name__) @@ -143,10 +142,6 @@ def __init__(self, hass: HomeAssistantType) -> None: self.hass = hass self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._clear_index() - self.hass.bus.async_listen( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - self.async_config_entry_disabled_by_changed, - ) @callback def async_get(self, device_id: str) -> Optional[DeviceEntry]: @@ -618,38 +613,6 @@ def async_clear_area_id(self, area_id: str) -> None: if area_id == device.area_id: self._async_update_device(dev_id, area_id=None) - @callback - def async_config_entry_disabled_by_changed(self, event: Event) -> None: - """Handle a config entry being disabled or enabled. - - Disable devices in the registry that are associated to a config entry when - the config entry is disabled. - """ - config_entry = self.hass.config_entries.async_get_entry( - event.data["config_entry_id"] - ) - - # The config entry may be deleted already if the event handling is late - if not config_entry: - return - - if not config_entry.disabled_by: - devices = async_entries_for_config_entry( - self, event.data["config_entry_id"] - ) - for device in devices: - if device.disabled_by != DISABLED_CONFIG_ENTRY: - continue - self.async_update_device(device.id, disabled_by=None) - return - - devices = async_entries_for_config_entry(self, event.data["config_entry_id"]) - for device in devices: - if device.disabled: - # Entity already disabled, do not overwrite - continue - self.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) - @callback def async_get(hass: HomeAssistantType) -> DeviceRegistry: @@ -691,6 +654,34 @@ def async_entries_for_config_entry( ] +@callback +def async_config_entry_disabled_by_changed( + registry: DeviceRegistry, config_entry: "ConfigEntry" +) -> None: + """Handle a config entry being disabled or enabled. + + Disable devices in the registry that are associated with a config entry when + the config entry is disabled, enable devices in the registry that are associated + with a config entry when the config entry is enabled and the devices are marked + DISABLED_CONFIG_ENTRY. + """ + + devices = async_entries_for_config_entry(registry, config_entry.entry_id) + + if not config_entry.disabled_by: + for device in devices: + if device.disabled_by != DISABLED_CONFIG_ENTRY: + continue + registry.async_update_device(device.id, disabled_by=None) + return + + for device in devices: + if device.disabled: + # Device already disabled, do not overwrite + continue + registry.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) + + @callback def async_cleanup( hass: HomeAssistantType, diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index f18dda71529418..36b010c82a034a 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -31,7 +31,6 @@ ATTR_RESTORED, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) @@ -158,10 +157,6 @@ def __init__(self, hass: HomeAssistantType): self.hass.bus.async_listen( EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified ) - self.hass.bus.async_listen( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - self.async_config_entry_disabled_by_changed, - ) @callback def async_get_device_class_lookup(self, domain_device_classes: set) -> dict: @@ -363,40 +358,6 @@ def async_device_modified(self, event: Event) -> None: for entity in entities: self.async_update_entity(entity.entity_id, disabled_by=DISABLED_DEVICE) - @callback - def async_config_entry_disabled_by_changed(self, event: Event) -> None: - """Handle a config entry being disabled or enabled. - - Disable entities in the registry that are associated to a config entry when - the config entry is disabled. - """ - config_entry = self.hass.config_entries.async_get_entry( - event.data["config_entry_id"] - ) - - # The config entry may be deleted already if the event handling is late - if not config_entry: - return - - if not config_entry.disabled_by: - entities = async_entries_for_config_entry( - self, event.data["config_entry_id"] - ) - for entity in entities: - if entity.disabled_by != DISABLED_CONFIG_ENTRY: - continue - self.async_update_entity(entity.entity_id, disabled_by=None) - return - - entities = async_entries_for_config_entry(self, event.data["config_entry_id"]) - for entity in entities: - if entity.disabled: - # Entity already disabled, do not overwrite - continue - self.async_update_entity( - entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY - ) - @callback def async_update_entity( self, @@ -443,7 +404,8 @@ def _async_update_entity( """Private facing update properties method.""" old = self.entities[entity_id] - changes = {} + new_values = {} # Dict with new key/value pairs + old_values = {} # Dict with old key/value pairs for attr_name, value in ( ("name", name), @@ -460,7 +422,8 @@ def _async_update_entity( ("original_icon", original_icon), ): 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 new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): @@ -473,7 +436,8 @@ def _async_update_entity( raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) - entity_id = changes["entity_id"] = new_entity_id + entity_id = new_values["entity_id"] = new_entity_id + old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( @@ -484,18 +448,19 @@ def _async_update_entity( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'" ) - changes["unique_id"] = new_unique_id + new_values["unique_id"] = new_unique_id + old_values["unique_id"] = old.unique_id - if not changes: + if not new_values: return old self._remove_index(old) - new = attr.evolve(old, **changes) + new = attr.evolve(old, **new_values) self._register_entry(new) self.async_schedule_save() - data = {"action": "update", "entity_id": entity_id, "changes": list(changes)} + data = {"action": "update", "entity_id": entity_id, "changes": old_values} if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id @@ -670,6 +635,36 @@ def async_entries_for_config_entry( ] +@callback +def async_config_entry_disabled_by_changed( + registry: EntityRegistry, config_entry: "ConfigEntry" +) -> None: + """Handle a config entry being disabled or enabled. + + Disable entities in the registry that are associated with a config entry when + the config entry is disabled, enable entities in the registry that are associated + with a config entry when the config entry is enabled and the entities are marked + DISABLED_CONFIG_ENTRY. + """ + + entities = async_entries_for_config_entry(registry, config_entry.entry_id) + + if not config_entry.disabled_by: + for entity in entities: + if entity.disabled_by != DISABLED_CONFIG_ENTRY: + continue + registry.async_update_entity(entity.entity_id, disabled_by=None) + return + + for entity in entities: + if entity.disabled: + # Entity already disabled, do not overwrite + continue + registry.async_update_entity( + entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY + ) + + async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: """Migrate the YAML config file to storage helper format.""" return { diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 86cdab822382c3..0a1a27efef5886 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -313,7 +313,7 @@ async def test_updating_config_entry_id(hass, registry, update_events): assert update_events[0]["entity_id"] == entry.entity_id assert update_events[1]["action"] == "update" assert update_events[1]["entity_id"] == entry.entity_id - assert update_events[1]["changes"] == ["config_entry_id"] + assert update_events[1]["changes"] == {"config_entry_id": "mock-id-1"} async def test_removing_config_entry_id(hass, registry, update_events): From e6a6b2a6802bb1ff625fc0ec759d1a061e15ab86 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Mar 2021 10:13:04 -0800 Subject: [PATCH 0986/1818] Simplify switch light (#47317) --- homeassistant/components/switch/light.py | 50 +++++++++--------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 5128a49d8b7c17..2650bd61bfb05c 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -12,7 +12,7 @@ STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event @@ -37,7 +37,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, - async_add_entities: Callable[[Sequence[Entity], bool], None], + async_add_entities: Callable[[Sequence[Entity]], None], discovery_info: Optional[DiscoveryInfoType] = None, ) -> None: """Initialize Light Switch platform.""" @@ -53,8 +53,7 @@ async def async_setup_platform( config[CONF_ENTITY_ID], unique_id, ) - ], - True, + ] ) @@ -66,9 +65,7 @@ def __init__(self, name: str, switch_entity_id: str, unique_id: str) -> None: self._name = name self._switch_entity_id = switch_entity_id self._unique_id = unique_id - self._is_on = False - self._available = False - self._async_unsub_state_changed: Optional[CALLBACK_TYPE] = None + self._switch_state: Optional[State] = None @property def name(self) -> str: @@ -78,12 +75,16 @@ def name(self) -> str: @property def is_on(self) -> bool: """Return true if light switch is on.""" - return self._is_on + assert self._switch_state is not None + return self._switch_state.state == STATE_ON @property def available(self) -> bool: """Return true if light switch is on.""" - return self._available + return ( + self._switch_state is not None + and self._switch_state.state != STATE_UNAVAILABLE + ) @property def should_poll(self) -> bool: @@ -117,33 +118,20 @@ async def async_turn_off(self, **kwargs): context=self._context, ) - async def async_update(self): - """Query the switch in this light switch and determine the state.""" - switch_state = self.hass.states.get(self._switch_entity_id) - - if switch_state is None: - self._available = False - return - - self._is_on = switch_state.state == STATE_ON - self._available = switch_state.state != STATE_UNAVAILABLE - async def async_added_to_hass(self) -> None: """Register callbacks.""" + assert self.hass is not None + self._switch_state = self.hass.states.get(self._switch_entity_id) @callback def async_state_changed_listener(*_: Any) -> None: """Handle child updates.""" - self.async_schedule_update_ha_state(True) + assert self.hass is not None + self._switch_state = self.hass.states.get(self._switch_entity_id) + self.async_write_ha_state() - assert self.hass is not None - self._async_unsub_state_changed = async_track_state_change_event( - self.hass, [self._switch_entity_id], async_state_changed_listener + self.async_on_remove( + async_track_state_change_event( + self.hass, [self._switch_entity_id], async_state_changed_listener + ) ) - - async def async_will_remove_from_hass(self): - """Handle removal from Home Assistant.""" - if self._async_unsub_state_changed is not None: - self._async_unsub_state_changed() - self._async_unsub_state_changed = None - self._available = False From a89ba0ed8e0a86ef61a8fce3f839d2c834389c34 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Mar 2021 19:12:37 +0100 Subject: [PATCH 0987/1818] Improve behaviour when disabling or enabling config entries (#47301) --- homeassistant/config_entries.py | 32 ++++++--- homeassistant/const.py | 1 - homeassistant/helpers/device_registry.py | 71 +++++++++---------- homeassistant/helpers/entity_registry.py | 87 +++++++++++------------- tests/helpers/test_entity_registry.py | 2 +- 5 files changed, 97 insertions(+), 96 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b0ec71be9cf3d4..12a795d0a51253 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -11,10 +11,9 @@ import attr from homeassistant import data_entry_flow, loader -from homeassistant.const import EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry, entity_registry from homeassistant.helpers.event import Event from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.setup import async_process_deps_reqs, async_setup_component @@ -807,12 +806,21 @@ async def async_set_disabled_by( entry.disabled_by = disabled_by self._async_schedule_save() - # Unload the config entry, then fire an event + dev_reg = device_registry.async_get(self.hass) + ent_reg = entity_registry.async_get(self.hass) + + if not entry.disabled_by: + # The config entry will no longer be disabled, enable devices and entities + device_registry.async_config_entry_disabled_by_changed(dev_reg, entry) + entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry) + + # Load or unload the config entry reload_result = await self.async_reload(entry_id) - self.hass.bus.async_fire( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, {"config_entry_id": entry_id} - ) + if entry.disabled_by: + # The config entry has been disabled, disable devices and entities + device_registry.async_config_entry_disabled_by_changed(dev_reg, entry) + entity_registry.async_config_entry_disabled_by_changed(ent_reg, entry) return reload_result @@ -1251,8 +1259,16 @@ async def _handle_reload(self, _now: Any) -> None: @callback def _handle_entry_updated_filter(event: Event) -> bool: - """Handle entity registry entry update filter.""" - if event.data["action"] != "update" or "disabled_by" not in event.data["changes"]: + """Handle entity registry entry update filter. + + Only handle changes to "disabled_by". + If "disabled_by" was DISABLED_CONFIG_ENTRY, reload is not needed. + """ + if ( + event.data["action"] != "update" + or "disabled_by" not in event.data["changes"] + or event.data["changes"]["disabled_by"] == entity_registry.DISABLED_CONFIG_ENTRY + ): return False return True diff --git a/homeassistant/const.py b/homeassistant/const.py index 47fb090305d897..2a9bd69c761cf9 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -202,7 +202,6 @@ # #### EVENTS #### EVENT_CALL_SERVICE = "call_service" EVENT_COMPONENT_LOADED = "component_loaded" -EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED = "config_entry_disabled_by_updated" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close" EVENT_HOMEASSISTANT_START = "homeassistant_start" diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 705f6cdd89a5a5..d311538f27f23a 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -6,10 +6,7 @@ import attr -from homeassistant.const import ( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - EVENT_HOMEASSISTANT_STARTED, -) +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import Event, callback from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util @@ -20,6 +17,8 @@ # mypy: disallow_any_generics if TYPE_CHECKING: + from homeassistant.config_entries import ConfigEntry + from . import entity_registry _LOGGER = logging.getLogger(__name__) @@ -143,10 +142,6 @@ def __init__(self, hass: HomeAssistantType) -> None: self.hass = hass self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._clear_index() - self.hass.bus.async_listen( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - self.async_config_entry_disabled_by_changed, - ) @callback def async_get(self, device_id: str) -> Optional[DeviceEntry]: @@ -618,38 +613,6 @@ def async_clear_area_id(self, area_id: str) -> None: if area_id == device.area_id: self._async_update_device(dev_id, area_id=None) - @callback - def async_config_entry_disabled_by_changed(self, event: Event) -> None: - """Handle a config entry being disabled or enabled. - - Disable devices in the registry that are associated to a config entry when - the config entry is disabled. - """ - config_entry = self.hass.config_entries.async_get_entry( - event.data["config_entry_id"] - ) - - # The config entry may be deleted already if the event handling is late - if not config_entry: - return - - if not config_entry.disabled_by: - devices = async_entries_for_config_entry( - self, event.data["config_entry_id"] - ) - for device in devices: - if device.disabled_by != DISABLED_CONFIG_ENTRY: - continue - self.async_update_device(device.id, disabled_by=None) - return - - devices = async_entries_for_config_entry(self, event.data["config_entry_id"]) - for device in devices: - if device.disabled: - # Entity already disabled, do not overwrite - continue - self.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) - @callback def async_get(hass: HomeAssistantType) -> DeviceRegistry: @@ -691,6 +654,34 @@ def async_entries_for_config_entry( ] +@callback +def async_config_entry_disabled_by_changed( + registry: DeviceRegistry, config_entry: "ConfigEntry" +) -> None: + """Handle a config entry being disabled or enabled. + + Disable devices in the registry that are associated with a config entry when + the config entry is disabled, enable devices in the registry that are associated + with a config entry when the config entry is enabled and the devices are marked + DISABLED_CONFIG_ENTRY. + """ + + devices = async_entries_for_config_entry(registry, config_entry.entry_id) + + if not config_entry.disabled_by: + for device in devices: + if device.disabled_by != DISABLED_CONFIG_ENTRY: + continue + registry.async_update_device(device.id, disabled_by=None) + return + + for device in devices: + if device.disabled: + # Device already disabled, do not overwrite + continue + registry.async_update_device(device.id, disabled_by=DISABLED_CONFIG_ENTRY) + + @callback def async_cleanup( hass: HomeAssistantType, diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index c86bd64d73e311..8a7a4de970aeb1 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -31,7 +31,6 @@ ATTR_RESTORED, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) @@ -158,10 +157,6 @@ def __init__(self, hass: HomeAssistantType): self.hass.bus.async_listen( EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified ) - self.hass.bus.async_listen( - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, - self.async_config_entry_disabled_by_changed, - ) @callback def async_get_device_class_lookup(self, domain_device_classes: set) -> dict: @@ -363,40 +358,6 @@ def async_device_modified(self, event: Event) -> None: for entity in entities: self.async_update_entity(entity.entity_id, disabled_by=DISABLED_DEVICE) - @callback - def async_config_entry_disabled_by_changed(self, event: Event) -> None: - """Handle a config entry being disabled or enabled. - - Disable entities in the registry that are associated to a config entry when - the config entry is disabled. - """ - config_entry = self.hass.config_entries.async_get_entry( - event.data["config_entry_id"] - ) - - # The config entry may be deleted already if the event handling is late - if not config_entry: - return - - if not config_entry.disabled_by: - entities = async_entries_for_config_entry( - self, event.data["config_entry_id"] - ) - for entity in entities: - if entity.disabled_by != DISABLED_CONFIG_ENTRY: - continue - self.async_update_entity(entity.entity_id, disabled_by=None) - return - - entities = async_entries_for_config_entry(self, event.data["config_entry_id"]) - for entity in entities: - if entity.disabled: - # Entity already disabled, do not overwrite - continue - self.async_update_entity( - entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY - ) - @callback def async_update_entity( self, @@ -443,7 +404,8 @@ def _async_update_entity( """Private facing update properties method.""" old = self.entities[entity_id] - changes = {} + new_values = {} # Dict with new key/value pairs + old_values = {} # Dict with old key/value pairs for attr_name, value in ( ("name", name), @@ -460,7 +422,8 @@ def _async_update_entity( ("original_icon", original_icon), ): 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 new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): @@ -473,7 +436,8 @@ def _async_update_entity( raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) - entity_id = changes["entity_id"] = new_entity_id + entity_id = new_values["entity_id"] = new_entity_id + old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( @@ -484,18 +448,19 @@ def _async_update_entity( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'" ) - changes["unique_id"] = new_unique_id + new_values["unique_id"] = new_unique_id + old_values["unique_id"] = old.unique_id - if not changes: + if not new_values: return old self._remove_index(old) - new = attr.evolve(old, **changes) + new = attr.evolve(old, **new_values) self._register_entry(new) self.async_schedule_save() - data = {"action": "update", "entity_id": entity_id, "changes": list(changes)} + data = {"action": "update", "entity_id": entity_id, "changes": old_values} if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id @@ -670,6 +635,36 @@ def async_entries_for_config_entry( ] +@callback +def async_config_entry_disabled_by_changed( + registry: EntityRegistry, config_entry: "ConfigEntry" +) -> None: + """Handle a config entry being disabled or enabled. + + Disable entities in the registry that are associated with a config entry when + the config entry is disabled, enable entities in the registry that are associated + with a config entry when the config entry is enabled and the entities are marked + DISABLED_CONFIG_ENTRY. + """ + + entities = async_entries_for_config_entry(registry, config_entry.entry_id) + + if not config_entry.disabled_by: + for entity in entities: + if entity.disabled_by != DISABLED_CONFIG_ENTRY: + continue + registry.async_update_entity(entity.entity_id, disabled_by=None) + return + + for entity in entities: + if entity.disabled: + # Entity already disabled, do not overwrite + continue + registry.async_update_entity( + entity.entity_id, disabled_by=DISABLED_CONFIG_ENTRY + ) + + async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: """Migrate the YAML config file to storage helper format.""" return { diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 86cdab822382c3..0a1a27efef5886 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -313,7 +313,7 @@ async def test_updating_config_entry_id(hass, registry, update_events): assert update_events[0]["entity_id"] == entry.entity_id assert update_events[1]["action"] == "update" assert update_events[1]["entity_id"] == entry.entity_id - assert update_events[1]["changes"] == ["config_entry_id"] + assert update_events[1]["changes"] == {"config_entry_id": "mock-id-1"} async def test_removing_config_entry_id(hass, registry, update_events): From 584ad0756770f08019f6972dcc20972a7f19253c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 3 Mar 2021 10:13:04 -0800 Subject: [PATCH 0988/1818] Simplify switch light (#47317) --- homeassistant/components/switch/light.py | 50 +++++++++--------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 5128a49d8b7c17..2650bd61bfb05c 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -12,7 +12,7 @@ STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event @@ -37,7 +37,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, - async_add_entities: Callable[[Sequence[Entity], bool], None], + async_add_entities: Callable[[Sequence[Entity]], None], discovery_info: Optional[DiscoveryInfoType] = None, ) -> None: """Initialize Light Switch platform.""" @@ -53,8 +53,7 @@ async def async_setup_platform( config[CONF_ENTITY_ID], unique_id, ) - ], - True, + ] ) @@ -66,9 +65,7 @@ def __init__(self, name: str, switch_entity_id: str, unique_id: str) -> None: self._name = name self._switch_entity_id = switch_entity_id self._unique_id = unique_id - self._is_on = False - self._available = False - self._async_unsub_state_changed: Optional[CALLBACK_TYPE] = None + self._switch_state: Optional[State] = None @property def name(self) -> str: @@ -78,12 +75,16 @@ def name(self) -> str: @property def is_on(self) -> bool: """Return true if light switch is on.""" - return self._is_on + assert self._switch_state is not None + return self._switch_state.state == STATE_ON @property def available(self) -> bool: """Return true if light switch is on.""" - return self._available + return ( + self._switch_state is not None + and self._switch_state.state != STATE_UNAVAILABLE + ) @property def should_poll(self) -> bool: @@ -117,33 +118,20 @@ async def async_turn_off(self, **kwargs): context=self._context, ) - async def async_update(self): - """Query the switch in this light switch and determine the state.""" - switch_state = self.hass.states.get(self._switch_entity_id) - - if switch_state is None: - self._available = False - return - - self._is_on = switch_state.state == STATE_ON - self._available = switch_state.state != STATE_UNAVAILABLE - async def async_added_to_hass(self) -> None: """Register callbacks.""" + assert self.hass is not None + self._switch_state = self.hass.states.get(self._switch_entity_id) @callback def async_state_changed_listener(*_: Any) -> None: """Handle child updates.""" - self.async_schedule_update_ha_state(True) + assert self.hass is not None + self._switch_state = self.hass.states.get(self._switch_entity_id) + self.async_write_ha_state() - assert self.hass is not None - self._async_unsub_state_changed = async_track_state_change_event( - self.hass, [self._switch_entity_id], async_state_changed_listener + self.async_on_remove( + async_track_state_change_event( + self.hass, [self._switch_entity_id], async_state_changed_listener + ) ) - - async def async_will_remove_from_hass(self): - """Handle removal from Home Assistant.""" - if self._async_unsub_state_changed is not None: - self._async_unsub_state_changed() - self._async_unsub_state_changed = None - self._available = False From b711686e10791f92979d0e96b83d8b28b0e26fc1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Mar 2021 19:17:17 +0100 Subject: [PATCH 0989/1818] Bumped version to 2021.3.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2a9bd69c761cf9..ec2ab3bff0c683 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0b7" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 53e62a897bce6d0b8367c073c453a585d20fb59e Mon Sep 17 00:00:00 2001 From: tkdrob Date: Wed, 3 Mar 2021 18:20:35 -0500 Subject: [PATCH 0990/1818] Fix grammar in pi_hole logs (#47324) --- homeassistant/components/pi_hole/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index 2d540d936e5bf7..bc486a0c9014ec 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -112,7 +112,7 @@ async def async_update_data(): try: await api.get_data() except HoleError as err: - raise UpdateFailed(f"Failed to communicating with API: {err}") from err + raise UpdateFailed(f"Failed to communicate with API: {err}") from err coordinator = DataUpdateCoordinator( hass, From 6d478804e735e5260edc66cf0e8867de8a0522c1 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 3 Mar 2021 16:32:37 -0700 Subject: [PATCH 0991/1818] Add LZW36 device schema to zwave_js discovery (#47314) * Add LZW26 device schema to discovery Co-authored-by: @kpine * Update homeassistant/components/zwave_js/discovery.py Co-authored-by: kpine * Add tests * Fix test Co-authored-by: kpine --- .../components/zwave_js/discovery.py | 14 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_discovery.py | 12 + .../zwave_js/inovelli_lzw36_state.json | 1956 +++++++++++++++++ 4 files changed, 1996 insertions(+) create mode 100644 tests/fixtures/zwave_js/inovelli_lzw36_state.json diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 804212d310a985..f27325b87d2864 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -115,6 +115,20 @@ class ZWaveDiscoverySchema: product_type={0x0038}, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), + # Inovelli LZW36 light / fan controller combo using switch multilevel CC + # The fan is endpoint 2, the light is endpoint 1. + ZWaveDiscoverySchema( + platform="fan", + manufacturer_id={0x031E}, + product_id={0x0001}, + product_type={0x000E}, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + endpoint={2}, + property={"currentValue"}, + type={"number"}, + ), + ), # Fibaro Shutter Fibaro FGS222 ZWaveDiscoverySchema( platform="cover", diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 50cacd974229cf..af87d9d49e1d37 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -282,6 +282,12 @@ def aeotec_radiator_thermostat_state_fixture(): return json.loads(load_fixture("zwave_js/aeotec_radiator_thermostat_state.json")) +@pytest.fixture(name="inovelli_lzw36_state", scope="session") +def inovelli_lzw36_state_fixture(): + """Load the Inovelli LZW36 node state fixture data.""" + return json.loads(load_fixture("zwave_js/inovelli_lzw36_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -512,3 +518,11 @@ def ge_12730_fixture(client, ge_12730_state): node = Node(client, copy.deepcopy(ge_12730_state)) client.driver.controller.nodes[node.node_id] = node return node + + +@pytest.fixture(name="inovelli_lzw36") +def inovelli_lzw36_fixture(client, inovelli_lzw36_state): + """Mock a Inovelli LZW36 fan controller node.""" + node = Node(client, copy.deepcopy(inovelli_lzw36_state)) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index e28c8ae15632fa..810ccb8df33ca9 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -23,3 +23,15 @@ async def test_ge_12730(hass, client, ge_12730, integration): state = hass.states.get("fan.in_wall_smart_fan_control") assert state + + +async def test_inovelli_lzw36(hass, client, inovelli_lzw36, integration): + """Test LZW36 Fan Controller multilevel switch endpoint 2 is discovered as a fan.""" + node = inovelli_lzw36 + assert node.device_class.specific.label == "Unused" + + state = hass.states.get("light.family_room_combo") + assert state.state == "off" + + state = hass.states.get("fan.family_room_combo_2") + assert state diff --git a/tests/fixtures/zwave_js/inovelli_lzw36_state.json b/tests/fixtures/zwave_js/inovelli_lzw36_state.json new file mode 100644 index 00000000000000..bfa56891413373 --- /dev/null +++ b/tests/fixtures/zwave_js/inovelli_lzw36_state.json @@ -0,0 +1,1956 @@ +{ + "nodeId": 19, + "index": 0, + "installerIcon": 7168, + "userIcon": 7168, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 17, + "label": "Multilevel Switch" + }, + "specific": { + "key": 0, + "label": "Unused" + }, + "mandatorySupportedCCs": [ + 32, + 38 + ], + "mandatoryControlledCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 798, + "productId": 1, + "productType": 14, + "firmwareVersion": "1.34", + "zwavePlusVersion": 2, + "nodeType": 0, + "roleType": 5, + "name": "family_room_combo", + "deviceConfig": { + "filename": "/opt/node_modules/@zwave-js/config/config/devices/0x031e/lzw36.json", + "manufacturerId": 798, + "manufacturer": "Inovelli", + "label": "LZW36", + "description": "Fan/Light Dimmer", + "devices": [ + { + "productType": "0x000e", + "productId": "0x0001" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "associations": {}, + "paramInformation": { + "_map": {} + } + }, + "label": "LZW36", + "neighbors": [ + 1, + 13, + 14, + 15, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30 + ], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": true, + "individualEndpointCount": 2, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 38, + "name": "Multilevel Switch", + "version": 4, + "isSecure": false + }, + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 152, + "name": "Security", + "version": 1, + "isSecure": true + }, + { + "id": 108, + "name": "Supervision", + "version": 1, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 4, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 3, + "isSecure": false + }, + { + "id": 142, + "name": "Multi Channel Association", + "version": 3, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 3, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 117, + "name": "Protection", + "version": 2, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 5, + "isSecure": false + }, + { + "id": 91, + "name": "Central Scene", + "version": 3, + "isSecure": false + }, + { + "id": 135, + "name": "Indicator", + "version": 3, + "isSecure": false + }, + { + "id": 96, + "name": "Multi Channel", + "version": 4, + "isSecure": false + }, + { + "id": 50, + "name": "Meter", + "version": 3, + "isSecure": false + } + ], + "endpoints": [ + { + "nodeId": 19, + "index": 0, + "installerIcon": 7168, + "userIcon": 7168 + }, + { + "nodeId": 19, + "index": 1, + "installerIcon": 1536, + "userIcon": 1536 + }, + { + "nodeId": 19, + "index": 2, + "installerIcon": 1536, + "userIcon": 1536 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "Light Dimming Speed", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 98, + "default": 4, + "format": 1, + "allowManualEntry": true, + "label": "Light Dimming Speed", + "description": "This changes the speed in which the attached light dims up or down. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. Range:0-98 Default: 4", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "Light Dimming Speed (From Switch)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Light Dimming Speed (From Switch)", + "description": "This changes the speed in which the attached light dims up or down when controlled from the physical switch. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. A setting of 99 should keep this in sync with parameter 1. Range:0-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Light Ramp Rate", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Light Ramp Rate", + "description": "This changes the speed in which the attached light turns on or off. For example, when a user sends the switch a basicSet(value: 0xFF) or basicSet(value: 0x00), this is the speed in which those actions take place. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. A setting of 99 should keep this in sync with parameter 1. Range:0-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Light Ramp Rate (From Switch)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Light Ramp Rate (From Switch)", + "description": "This changes the speed in which the attached light turns on or off from the physical switch. For example, when a user presses the up or down button, this is the speed in which those actions take place. A setting of 0 should turn the light immediately on or off (almost like an on/off switch). Increasing the value should slow down the transition speed. A setting of 99 should keep this in sync with parameter 1. Range:0-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Minimum Light Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 45, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Minimum Light Level", + "description": "The minimum level that the dimmer allows the bulb to be dimmed to. Useful when the user has an LED bulb that does not turn on or flickers at a lower level. Range:1-45 Default: 1", + "isFromConfig": true + }, + "value": 40 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Maximum Light Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 55, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Maximum Light Level", + "description": "The maximum level that the dimmer allows the bulb to be dimmed to. Useful when the user has an LED bulb that reaches its maximum level before the dimmer value of 99. Range:55-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Minimum Fan Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 45, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Minimum Fan Level", + "description": "The minimum level that the dimmer allows the fan to be dimmed to. Useful when the user has a fan that does not turn at a lower level. Range:1-45 Default: 1", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Maximum Fan Level", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 55, + "max": 99, + "default": 99, + "format": 1, + "allowManualEntry": true, + "label": "Maximum Fan Level", + "description": "The maximum level that the dimmer allows the fan to be dimmed to. Range:55-99 Default: 99", + "isFromConfig": true + }, + "value": 99 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 10, + "propertyName": "Auto Off Light Timer", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 0, + "unit": "s", + "format": 1, + "allowManualEntry": true, + "label": "Auto Off Light Timer", + "description": "Automatically turns the light switch off after this many seconds. When the switch is turned on a timer is started that is the duration of this setting. When the timer expires, the switch is turned off. Range:0-32767 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 11, + "propertyName": "Auto Off Fan Timer", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Auto Off Fan Timer", + "description": "Automatically turns the fan switch off after this many seconds. When the switch is turned on a timer is started that is the duration of this setting. When the timer expires, the switch is turned off. Range:0-32767 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 12, + "propertyName": "Default Light Level (Local)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Default Light Level (Local)", + "description": "Default level for the dimmer when it is powered on from the local switch. A setting of 0 means that the switch will return to the level that it was on before it was turned off. Range:1-99 Default: 0", + "isFromConfig": true + }, + "value": 70 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 13, + "propertyName": "Default Light Level (Z-Wave)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 1, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Default Light Level (Z-Wave)", + "description": "Default level for the dimmer when it is powered on from a Z-Wave command. A setting of 0 means that the switch will return to the level that it was on before it was turned off. Range:1-99 Default: 0", + "isFromConfig": true + }, + "value": 85 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 14, + "propertyName": "Default Fan Level (Local)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Last State", + "33": "Low", + "66": "Medium", + "99": "High" + }, + "label": "Default Fan Level (Local)", + "description": "Default level for the fan dimmer when it is powered on from the local switch. Default: Last State", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 15, + "propertyName": "Default Fan Level (Z-Wave)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 99, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Last State", + "33": "Low", + "66": "Medium", + "99": "High" + }, + "label": "Default Fan Level (Z-Wave)", + "description": "Default level for the fan dimmer when it is powered on from the local switch. Default: Last State", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 16, + "propertyName": "Light State After Power Restored", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Light State After Power Restored", + "description": "The state the switch should return to once power is restored after power failure. 0 = off, 1-99 = level, 100=previous. Range:0-100 Default: 100", + "isFromConfig": true + }, + "value": 100 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 17, + "propertyName": "Fan State After Power Restored", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Fan State After Power Restored", + "description": "The state the switch should return to once power is restored after power failure. Default: Off", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 18, + "propertyName": "Light LED Indicator Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 255, + "default": 170, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Indicator Color", + "description": "This is the color of the Light LED strip represented as part of the HUE color wheel. Since the wheel has 360 values and this parameter only has 255, the following equation can be used to determine the color: value/255 * 360 = Hue color wheel value Range: 0 to 255 Default: 170", + "isFromConfig": true + }, + "value": 170 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 19, + "propertyName": "Light LED Strip Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 5, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Intensity", + "description": "This is the intensity of the Light LED strip. Range: 0-10 Default: 5", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 20, + "propertyName": "Fan LED Indicator Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 255, + "default": 170, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Indicator Color", + "description": "This is the color of the Fan LED strip represented as part of the HUE color wheel. Since the wheel has 360 values and this parameter only has 255, the following equation can be used to determine the color: value/255 * 360 = Hue color wheel value Range: 0 to 255 Default: 170", + "isFromConfig": true + }, + "value": 170 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 21, + "propertyName": "Fan LED Strip Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 5, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Intensity", + "description": "This is the intensity of the Fan LED strip. Range: 0-10 Default: 5", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 22, + "propertyName": "Light LED Strip Intensity (When OFF)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Intensity (When OFF)", + "description": "This is the intensity of the Light LED strip when the switch is off. This is useful for users to see the light switch location when the lights are off. Range: 0-10 Default: 1", + "isFromConfig": true + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 23, + "propertyName": "Fan LED Strip Intensity (When OFF)", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 1, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Intensity (When OFF)", + "description": "This is the intensity of the Fan LED strip when the switch is off. This is useful for users to see the light switch location when the lights are off. Range: 0-10 Default: 1", + "isFromConfig": true + }, + "value": 4 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 26, + "propertyName": "Light LED Strip Timeout", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 3, + "unit": "s", + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Timeout", + "description": "When the LED strip is disabled (Light LED Strip Intensity is set to 0), this setting allows the LED strip to turn on temporarily while being adjusted. Range: 0-10 Default: 3 Disabled: 0", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 27, + "propertyName": "Fan LED Strip Timeout", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 10, + "default": 3, + "unit": "s", + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Timeout", + "description": "When the LED strip is disabled (Fan LED Strip Intensity is set to 0), this setting allows the LED strip to turn on temporarily while being adjusted. Range: 0-10 Default: 3 Disabled: 0", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 28, + "propertyName": "Active Power Reports", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 10, + "format": 1, + "allowManualEntry": true, + "label": "Active Power Reports", + "description": "The power level change that will result in a new power report being sent. The value is a percentage of the previous report. 0 = disabled. Range:0-100 Default: 10", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 29, + "propertyName": "Periodic Power & Energy Reports", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 2, + "min": 0, + "max": 32767, + "default": 3600, + "format": 1, + "allowManualEntry": true, + "label": "Periodic Power & Energy Reports", + "description": "Time period between consecutive power & energy reports being sent (in seconds). The timer is reset after each report is sent. Range:0-32767 Default: 3600", + "isFromConfig": true + }, + "value": 3600 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 30, + "propertyName": "Energy Reports", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 10, + "format": 1, + "allowManualEntry": true, + "label": "Energy Reports", + "description": "The energy level change that will result in a new energy report being sent. The value is a percentage of the previous report. Range:0-100 Default: 10", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 31, + "propertyName": "Local Protection", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 3, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "None", + "1": "light", + "2": "fan", + "3": "Both" + }, + "label": "Local Protection", + "description": "Enable local protection on these buttons. 0 = none, 1 = light, 2 = fan, 3 = both.", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 2130706432, + "propertyName": "Light LED Strip Effect", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 5, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Off", + "1": "Solid", + "2": "Slow Blink", + "3": "Fast Blink", + "4": "Chase", + "5": "Pulse" + }, + "label": "Light LED Strip Effect", + "description": "Light LED Strip Effect. 0 = Off, 1 = Solid, 2 = Slow Blink, 3 = Fast Blink, 4 = Chase, 5 = Pulse Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 16711680, + "propertyName": "Light LED Strip Effect Duration", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Effect Duration", + "description": "Light LED Strip Effect Duration. 1 to 60 = seconds, 61 to 120 minutes, 121 - 254 = hours, 255 = Indefinitely Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 65280, + "propertyName": "Light LED Strip Effect Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 9, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Effect Intensity", + "description": "Light LED Strip Effect Intensity. 0 to 9. 0 = dim, 9 = bright Default: 3", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 24, + "propertyKey": 255, + "propertyName": "Light LED Strip Effect Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Light LED Strip Effect Color", + "description": "Light LED Strip Effect Color. Color - 0 - 255. Hue color wheel. value/255 * 360 = Hue color wheel value Range: 0-255 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 2130706432, + "propertyName": "Fan LED Strip Effect", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 5, + "default": 0, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Off", + "1": "Solid", + "2": "Slow Blink", + "3": "Fast Blink", + "4": "Chase", + "5": "Pulse" + }, + "label": "Fan LED Strip Effect", + "description": "Fan LED Strip Effect. 0 = Off, 1 = Solid, 2 = Slow Blink, 3 = Fast Blink, 4 = Chase, 5 = Pulse Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 16711680, + "propertyName": "Fan LED Strip Effect Duration", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Effect Duration", + "description": "Fan LED Strip Duration. 1 to 60 = seconds, 61 to 120 minutes, 121 - 254 = hours, 255 = Indefinitely Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 65280, + "propertyName": "Fan LED Strip Effect Intensity", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 9, + "default": 3, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Effect Intensity", + "description": "Fan LED Strip Intensity 0 to 9. 0 = dim, 9 = bright Default: 3", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 25, + "propertyKey": 255, + "propertyName": "Fan LED Strip Effect Color", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 4, + "min": 0, + "max": 255, + "default": 0, + "format": 1, + "allowManualEntry": true, + "label": "Fan LED Strip Effect Color", + "description": "Fan LED Color 0 - 255. Hue color wheel. value/255 * 360 = Hue color wheel value Range: 0-255 Default: 0", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 51, + "propertyName": "Instant On", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 1, + "allowManualEntry": false, + "states": { + "0": "Enabled (no delay)", + "1": "Disabled (700ms delay)" + }, + "label": "Instant On", + "description": "Enables instant on (ie: disables the 700ms button delay). Note, if you disable the delay, it will also disable scene control except for the following: Light on/off pressed, held, released, Fan on/off pressed, held, released & light up/down fan up/down pressed (firmware 1.36+).", + "isFromConfig": true + } + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "7.13" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "1.34" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "sdkVersion", + "propertyName": "sdkVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "7.13.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkAPIVersion", + "propertyName": "applicationFrameworkAPIVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "10.13.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationFrameworkBuildNumber", + "propertyName": "applicationFrameworkBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 310 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceVersion", + "propertyName": "hostInterfaceVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "unused" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hostInterfaceBuildNumber", + "propertyName": "hostInterfaceBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolVersion", + "propertyName": "zWaveProtocolVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "7.13.4" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "zWaveProtocolBuildNumber", + "propertyName": "zWaveProtocolBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 310 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationVersion", + "propertyName": "applicationVersion", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": "1.34.1" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "applicationBuildNumber", + "propertyName": "applicationBuildNumber", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + }, + "value": 43707 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 798 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 14 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "1": "ProtectedBySequence", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "rf", + "propertyName": "rf", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "RF protection state", + "states": { + "0": "Unprotected", + "1": "NoControl", + "2": "NoResponse" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "exclusiveControlNodeId", + "propertyName": "exclusiveControlNodeId", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "timeout", + "propertyName": "timeout", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "slowRefresh", + "propertyName": "slowRefresh", + "ccVersion": 3, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Send held down notifications at a slow rate", + "description": "When this is true, KeyHeldDown notifications are sent every 55s. When this is false, the notifications are sent every 200ms." + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "001", + "propertyName": "scene", + "propertyKeyName": "001", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 001", + "states": { + "0": "KeyPressed", + "1": "KeyReleased", + "2": "KeyHeldDown", + "3": "KeyPressed2x", + "4": "KeyPressed3x", + "5": "KeyPressed4x", + "6": "KeyPressed5x" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "002", + "propertyName": "scene", + "propertyKeyName": "002", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 002", + "states": { + "0": "KeyPressed", + "1": "KeyReleased", + "2": "KeyHeldDown", + "3": "KeyPressed2x", + "4": "KeyPressed3x", + "5": "KeyPressed4x", + "6": "KeyPressed5x" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "003", + "propertyName": "scene", + "propertyKeyName": "003", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 003", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "004", + "propertyName": "scene", + "propertyKeyName": "004", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 004", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "005", + "propertyName": "scene", + "propertyKeyName": "005", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 005", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 91, + "commandClassName": "Central Scene", + "property": "scene", + "propertyKey": "006", + "propertyName": "scene", + "propertyKeyName": "006", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Scene 006", + "states": { + "0": "KeyPressed" + } + } + }, + { + "endpoint": 0, + "commandClass": 135, + "commandClassName": "Indicator", + "property": 80, + "propertyKey": 3, + "propertyName": "Node Identify", + "propertyKeyName": "On/Off Period: Duration", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Node Identify - On/Off Period: Duration", + "description": "Sets the duration of an on/off period in 1/10th seconds. Must be set together with \"On/Off Cycle Count\"", + "ccSpecific": { + "indicatorId": 80, + "propertyId": 3 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 135, + "commandClassName": "Indicator", + "property": 80, + "propertyKey": 4, + "propertyName": "Node Identify", + "propertyKeyName": "On/Off Cycle Count", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Node Identify - On/Off Cycle Count", + "description": "Sets the number of on/off periods. 0xff means infinite. Must be set together with \"On/Off Period duration\"", + "ccSpecific": { + "indicatorId": 80, + "propertyId": 4 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 135, + "commandClassName": "Indicator", + "property": 80, + "propertyKey": 5, + "propertyName": "Node Identify", + "propertyKeyName": "On/Off Period: On time", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Node Identify - On/Off Period: On time", + "description": "This property is used to set the length of the On time during an On/Off period. It allows asymetic On/Off periods. The value 0x00 MUST represent symmetric On/Off period (On time equal to Off time)", + "ccSpecific": { + "indicatorId": 80, + "propertyId": 5 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 65537, + "propertyName": "value", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh]", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 78.057 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 65537, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh] (prev. time delta)", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "value", + "propertyKey": 66049, + "propertyName": "value", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W]", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0.4 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "deltaTime", + "propertyKey": 66049, + "propertyName": "deltaTime", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W] (prev. time delta)", + "unit": "s", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "reset", + "propertyName": "reset", + "ccVersion": 3, + "metadata": { + "type": "boolean", + "readable": false, + "writeable": true, + "label": "Reset accumulated values" + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 65537, + "propertyName": "previousValue", + "propertyKeyName": "Electric_kWh_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [kWh] (prev. value)", + "unit": "kWh", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 0 + } + } + }, + { + "endpoint": 0, + "commandClass": 50, + "commandClassName": "Meter", + "property": "previousValue", + "propertyKey": 66049, + "propertyName": "previousValue", + "propertyKeyName": "Electric_W_Consumed", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Electric Consumed [W] (prev. value)", + "unit": "W", + "ccSpecific": { + "meterType": 1, + "rateType": 1, + "scale": 2 + } + } + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 4, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + }, + "value": { + "value": 0, + "unit": "seconds" + } + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 0 + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 1, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + }, + "value": 0 + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 4, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + }, + "value": { + "value": 0, + "unit": "seconds" + } + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 0 + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 2, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + } + ] + } \ No newline at end of file From 17401cbc29ae7ba07b4d4fa0ea7fad3acc1ae71f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 4 Mar 2021 14:16:24 +0100 Subject: [PATCH 0992/1818] Initial automation tracing (#46755) * Initial prototype of automation tracing * Small fixes * Lint * Move trace helpers to its own file * Improve trace for state and numeric_state conditions * Tweaks + apply suggestions from code review * Index traces by automation_id, trace while script is running * Refactor condition tracing * Improve WS API to get traces for single automation * Add tests * Fix imports * Fix imports * Address review comments * Cap logging of loops * Remove unused ContextVar action_config --- .../components/automation/__init__.py | 267 +++++++++++--- homeassistant/components/automation/config.py | 18 + homeassistant/components/config/automation.py | 27 ++ homeassistant/components/script/__init__.py | 11 +- homeassistant/helpers/condition.py | 220 +++++++++++- homeassistant/helpers/script.py | 206 +++++++++-- homeassistant/helpers/trace.py | 78 +++++ tests/components/config/test_automation.py | 161 +++++++++ tests/helpers/test_condition.py | 330 ++++++++++++++++++ tests/helpers/test_script.py | 43 ++- 10 files changed, 1258 insertions(+), 103 deletions(-) create mode 100644 homeassistant/helpers/trace.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 1b1a927d1475f2..acb28df05b08b8 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,4 +1,6 @@ """Allow to set up simple automation rules via the config file.""" +from collections import deque +from contextlib import contextmanager import logging from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast @@ -39,6 +41,11 @@ HomeAssistantError, ) from homeassistant.helpers import condition, extract_domain_configs, template +from homeassistant.helpers.condition import ( + condition_path, + condition_trace_clear, + condition_trace_get, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent @@ -50,17 +57,22 @@ CONF_MAX, CONF_MAX_EXCEEDED, Script, + action_path, + action_trace_clear, + action_trace_get, ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass +from homeassistant.util import dt as dt_util from homeassistant.util.dt import parse_datetime +from .config import AutomationConfig, async_validate_config_item + # Not used except by packages to check config structure from .config import PLATFORM_SCHEMA # noqa: F401 -from .config import async_validate_config_item from .const import ( CONF_ACTION, CONF_INITIAL_STATE, @@ -90,6 +102,10 @@ ATTR_VARIABLES = "variables" SERVICE_TRIGGER = "trigger" +DATA_AUTOMATION_TRACE = "automation_trace" +STORED_TRACES = 5 # Stored traces per automation + +_LOGGER = logging.getLogger(__name__) AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] @@ -166,8 +182,9 @@ def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: async def async_setup(hass, config): - """Set up the automation.""" + """Set up all automations.""" hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) + hass.data.setdefault(DATA_AUTOMATION_TRACE, {}) # To register the automation blueprints async_get_blueprints(hass) @@ -176,7 +193,7 @@ async def async_setup(hass, config): await async_get_blueprints(hass).async_populate() async def trigger_service_handler(entity, service_call): - """Handle automation triggers.""" + """Handle forced automation trigger, e.g. from frontend.""" await entity.async_trigger( service_call.data[ATTR_VARIABLES], skip_condition=service_call.data[CONF_SKIP_CONDITION], @@ -215,6 +232,103 @@ async def reload_service_handler(service_call): return True +class AutomationTrace: + """Container for automation trace.""" + + def __init__(self, unique_id, config, trigger, context, action_trace): + """Container for automation trace.""" + self._action_trace = action_trace + self._condition_trace = None + self._config = config + self._context = context + self._error = None + self._state = "running" + self._timestamp_finish = None + self._timestamp_start = dt_util.utcnow() + self._trigger = trigger + self._unique_id = unique_id + self._variables = None + + def set_error(self, ex): + """Set error.""" + self._error = ex + + def set_variables(self, variables): + """Set variables.""" + self._variables = variables + + def set_condition_trace(self, condition_trace): + """Set condition trace.""" + self._condition_trace = condition_trace + + def finished(self): + """Set finish time.""" + self._timestamp_finish = dt_util.utcnow() + self._state = "stopped" + + def as_dict(self): + """Return dictionary version of this AutomationTrace.""" + + action_traces = {} + condition_traces = {} + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] + + if self._condition_trace: + for key, trace_list in self._condition_trace.items(): + condition_traces[key] = [item.as_dict() for item in trace_list] + + result = { + "action_trace": action_traces, + "condition_trace": condition_traces, + "config": self._config, + "context": self._context, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "trigger": self._trigger, + "unique_id": self._unique_id, + "variables": self._variables, + } + if self._error is not None: + result["error"] = str(self._error) + return result + + +@contextmanager +def trace_automation(hass, unique_id, config, trigger, context): + """Trace action execution of automation with automation_id.""" + action_trace_clear() + action_trace = action_trace_get() + automation_trace = AutomationTrace( + unique_id, config, trigger, context, action_trace + ) + + if unique_id: + if unique_id not in hass.data[DATA_AUTOMATION_TRACE]: + hass.data[DATA_AUTOMATION_TRACE][unique_id] = deque([], STORED_TRACES) + traces = hass.data[DATA_AUTOMATION_TRACE][unique_id] + traces.append(automation_trace) + + try: + yield automation_trace + except Exception as ex: # pylint: disable=broad-except + if unique_id: + automation_trace.set_error(ex) + raise ex + finally: + if unique_id: + automation_trace.finished() + _LOGGER.debug( + "Automation finished. Summary:\n\ttrigger: %s\n\tcondition: %s\n\taction: %s", + automation_trace._trigger, # pylint: disable=protected-access + automation_trace._condition_trace, # pylint: disable=protected-access + action_trace, + ) + + class AutomationEntity(ToggleEntity, RestoreEntity): """Entity to show status of entity.""" @@ -228,6 +342,7 @@ def __init__( initial_state, variables, trigger_variables, + raw_config, ): """Initialize an automation entity.""" self._id = automation_id @@ -244,6 +359,7 @@ def __init__( self._logger = LOGGER self._variables: ScriptVariables = variables self._trigger_variables: ScriptVariables = trigger_variables + self._raw_config = raw_config @property def name(self): @@ -374,52 +490,73 @@ async def async_trigger(self, run_variables, context=None, skip_condition=False) This method is a coroutine. """ - if self._variables: - try: - variables = self._variables.async_render(self.hass, run_variables) - except template.TemplateError as err: - self._logger.error("Error rendering variables: %s", err) + reason = "" + if "trigger" in run_variables and "description" in run_variables["trigger"]: + reason = f' by {run_variables["trigger"]["description"]}' + self._logger.debug("Automation triggered%s", reason) + + trigger = run_variables["trigger"] if "trigger" in run_variables else None + with trace_automation( + self.hass, self.unique_id, self._raw_config, trigger, context + ) as automation_trace: + if self._variables: + try: + variables = self._variables.async_render(self.hass, run_variables) + except template.TemplateError as err: + self._logger.error("Error rendering variables: %s", err) + automation_trace.set_error(err) + return + else: + variables = run_variables + automation_trace.set_variables(variables) + + if ( + not skip_condition + and self._cond_func is not None + and not self._cond_func(variables) + ): + self._logger.debug( + "Conditions not met, aborting automation. Condition summary: %s", + condition_trace_get(), + ) + automation_trace.set_condition_trace(condition_trace_get()) return - else: - variables = run_variables - - if ( - not skip_condition - and self._cond_func is not None - and not self._cond_func(variables) - ): - return - - # Create a new context referring to the old context. - parent_id = None if context is None else context.id - trigger_context = Context(parent_id=parent_id) - - self.async_set_context(trigger_context) - event_data = { - ATTR_NAME: self._name, - ATTR_ENTITY_ID: self.entity_id, - } - if "trigger" in variables and "description" in variables["trigger"]: - event_data[ATTR_SOURCE] = variables["trigger"]["description"] - - @callback - def started_action(): - self.hass.bus.async_fire( - EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context - ) + automation_trace.set_condition_trace(condition_trace_get()) + condition_trace_clear() + + # Create a new context referring to the old context. + parent_id = None if context is None else context.id + trigger_context = Context(parent_id=parent_id) + + self.async_set_context(trigger_context) + event_data = { + ATTR_NAME: self._name, + ATTR_ENTITY_ID: self.entity_id, + } + if "trigger" in variables and "description" in variables["trigger"]: + event_data[ATTR_SOURCE] = variables["trigger"]["description"] + + @callback + def started_action(): + self.hass.bus.async_fire( + EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context + ) - try: - await self.action_script.async_run( - variables, trigger_context, started_action - ) - except (vol.Invalid, HomeAssistantError) as err: - self._logger.error( - "Error while executing automation %s: %s", - self.entity_id, - err, - ) - except Exception: # pylint: disable=broad-except - self._logger.exception("While executing automation %s", self.entity_id) + try: + with action_path("action"): + await self.action_script.async_run( + variables, trigger_context, started_action + ) + except (vol.Invalid, HomeAssistantError) as err: + self._logger.error( + "Error while executing automation %s: %s", + self.entity_id, + err, + ) + automation_trace.set_error(err) + except Exception as err: # pylint: disable=broad-except + self._logger.exception("While executing automation %s", self.entity_id) + automation_trace.set_error(err) async def async_will_remove_from_hass(self): """Remove listeners when removing automation from Home Assistant.""" @@ -527,16 +664,16 @@ async def _async_process_config( ] for list_no, config_block in enumerate(conf): + raw_config = None if isinstance(config_block, blueprint.BlueprintInputs): # type: ignore blueprints_used = True blueprint_inputs = config_block try: + raw_config = blueprint_inputs.async_substitute() config_block = cast( Dict[str, Any], - await async_validate_config_item( - hass, blueprint_inputs.async_substitute() - ), + await async_validate_config_item(hass, raw_config), ) except vol.Invalid as err: LOGGER.error( @@ -546,6 +683,8 @@ async def _async_process_config( humanize_error(config_block, err), ) continue + else: + raw_config = cast(AutomationConfig, config_block).raw_config automation_id = config_block.get(CONF_ID) name = config_block.get(CONF_ALIAS) or f"{config_key} {list_no}" @@ -596,6 +735,7 @@ async def _async_process_config( initial_state, variables, config_block.get(CONF_TRIGGER_VARIABLES), + raw_config, ) entities.append(entity) @@ -623,8 +763,9 @@ def if_action(variables=None): errors = [] for index, check in enumerate(checks): try: - if not check(hass, variables): - return False + with condition_path(["condition", str(index)]): + if not check(hass, variables): + return False except ConditionError as ex: errors.append( ConditionErrorIndex( @@ -672,3 +813,25 @@ def _trigger_extract_entities(trigger_conf: dict) -> List[str]: return ["sun.sun"] return [] + + +@callback +def get_debug_traces_for_automation(hass, automation_id): + """Return a serializable list of debug traces for an automation.""" + traces = [] + + for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, []): + traces.append(trace.as_dict()) + + return traces + + +@callback +def get_debug_traces(hass): + """Return a serializable list of debug traces.""" + traces = {} + + for automation_id in hass.data[DATA_AUTOMATION_TRACE]: + traces[automation_id] = get_debug_traces_for_automation(hass, automation_id) + + return traces diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 32ad92cb86e382..5abff8fe9744e2 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -79,8 +79,21 @@ async def async_validate_config_item(hass, config, full_config=None): return config +class AutomationConfig(dict): + """Dummy class to allow adding attributes.""" + + raw_config = None + + async def _try_async_validate_config_item(hass, config, full_config=None): """Validate config item.""" + raw_config = None + try: + raw_config = dict(config) + except ValueError: + # Invalid config + pass + try: config = await async_validate_config_item(hass, config, full_config) except ( @@ -92,6 +105,11 @@ async def _try_async_validate_config_item(hass, config, full_config=None): async_log_exception(ex, DOMAIN, full_config or config, hass) return None + if isinstance(config, blueprint.BlueprintInputs): + return config + + config = AutomationConfig(config) + config.raw_config = raw_config return config diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 01e22297c0d42a..23baa0c8843a5b 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -2,6 +2,13 @@ from collections import OrderedDict import uuid +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.automation import ( + get_debug_traces, + get_debug_traces_for_automation, +) from homeassistant.components.automation.config import ( DOMAIN, PLATFORM_SCHEMA, @@ -17,6 +24,8 @@ async def async_setup(hass): """Set up the Automation config API.""" + websocket_api.async_register_command(hass, websocket_automation_trace) + async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) @@ -80,3 +89,21 @@ def _write_value(self, hass, data, config_key, new_value): updated_value.update(cur_value) updated_value.update(new_value) data[index] = updated_value + + +@websocket_api.websocket_command( + {vol.Required("type"): "automation/trace", vol.Optional("automation_id"): str} +) +@websocket_api.async_response +async def websocket_automation_trace(hass, connection, msg): + """Get automation traces.""" + automation_id = msg.get("automation_id") + + if not automation_id: + automation_traces = get_debug_traces(hass) + else: + automation_traces = { + automation_id: get_debug_traces_for_automation(hass, automation_id) + } + + connection.send_result(msg["id"], automation_traces) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 5de3cb8264f893..429e97230ce69d 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -308,7 +308,11 @@ def async_change_listener(self): self._changed.set() async def async_turn_on(self, **kwargs): - """Turn the script on.""" + """Run the script. + + Depending on the script's run mode, this may do nothing, restart the script or + fire an additional parallel run. + """ variables = kwargs.get("variables") context = kwargs.get("context") wait = kwargs.get("wait", True) @@ -331,7 +335,10 @@ async def async_turn_on(self, **kwargs): await self._changed.wait() async def async_turn_off(self, **kwargs): - """Turn script off.""" + """Stop running the script. + + If multiple runs are in progress, all will be stopped. + """ await self.script.async_stop() async def async_will_remove_from_hass(self): diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 40087650141419..1abbf550bb151a 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -1,12 +1,25 @@ """Offer reusable conditions.""" import asyncio from collections import deque +from contextlib import contextmanager +from contextvars import ContextVar from datetime import datetime, timedelta import functools as ft import logging import re import sys -from typing import Any, Callable, Container, List, Optional, Set, Union, cast +from typing import ( + Any, + Callable, + Container, + Dict, + Generator, + List, + Optional, + Set, + Union, + cast, +) from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( @@ -51,6 +64,14 @@ from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as dt_util +from .trace import ( + TraceElement, + trace_append_element, + trace_stack_pop, + trace_stack_push, + trace_stack_top, +) + FROM_CONFIG_FORMAT = "{}_from_config" ASYNC_FROM_CONFIG_FORMAT = "async_{}_from_config" @@ -63,6 +84,126 @@ ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool] +# Context variables for tracing +# Trace of condition being evaluated +condition_trace = ContextVar("condition_trace", default=None) +# Stack of TraceElements +condition_trace_stack: ContextVar[Optional[List[TraceElement]]] = ContextVar( + "condition_trace_stack", default=None +) +# Current location in config tree +condition_path_stack: ContextVar[Optional[List[str]]] = ContextVar( + "condition_path_stack", default=None +) + + +def condition_trace_stack_push(node: TraceElement) -> None: + """Push a TraceElement to the top of the trace stack.""" + trace_stack_push(condition_trace_stack, node) + + +def condition_trace_stack_pop() -> None: + """Remove the top element from the trace stack.""" + trace_stack_pop(condition_trace_stack) + + +def condition_trace_stack_top() -> Optional[TraceElement]: + """Return the element at the top of the trace stack.""" + return cast(Optional[TraceElement], trace_stack_top(condition_trace_stack)) + + +def condition_path_push(suffix: Union[str, List[str]]) -> int: + """Go deeper in the config tree.""" + if isinstance(suffix, str): + suffix = [suffix] + for node in suffix: + trace_stack_push(condition_path_stack, node) + return len(suffix) + + +def condition_path_pop(count: int) -> None: + """Go n levels up in the config tree.""" + for _ in range(count): + trace_stack_pop(condition_path_stack) + + +def condition_path_get() -> str: + """Return a string representing the current location in the config tree.""" + path = condition_path_stack.get() + if not path: + return "" + return "/".join(path) + + +def condition_trace_get() -> Optional[Dict[str, TraceElement]]: + """Return the trace of the condition that was evaluated.""" + return condition_trace.get() + + +def condition_trace_clear() -> None: + """Clear the condition trace.""" + condition_trace.set(None) + condition_trace_stack.set(None) + condition_path_stack.set(None) + + +def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement: + """Append a TraceElement to trace[path].""" + trace_element = TraceElement(variables) + trace_append_element(condition_trace, trace_element, path) + return trace_element + + +def condition_trace_set_result(result: bool, **kwargs: Any) -> None: + """Set the result of TraceElement at the top of the stack.""" + node = condition_trace_stack_top() + + # The condition function may be called directly, in which case tracing + # is not setup + if not node: + return + + node.set_result(result=result, **kwargs) + + +@contextmanager +def trace_condition(variables: TemplateVarsType) -> Generator: + """Trace condition evaluation.""" + trace_element = condition_trace_append(variables, condition_path_get()) + condition_trace_stack_push(trace_element) + try: + yield trace_element + except Exception as ex: # pylint: disable=broad-except + trace_element.set_error(ex) + raise ex + finally: + condition_trace_stack_pop() + + +@contextmanager +def condition_path(suffix: Union[str, List[str]]) -> Generator: + """Go deeper in the config tree.""" + count = condition_path_push(suffix) + try: + yield + finally: + condition_path_pop(count) + + +def trace_condition_function(condition: ConditionCheckerType) -> ConditionCheckerType: + """Wrap a condition function to enable basic tracing.""" + + @ft.wraps(condition) + def wrapper(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: + """Trace condition.""" + with trace_condition(variables): + result = condition(hass, variables) + condition_trace_set_result(result) + return result + + return wrapper + + async def async_from_config( hass: HomeAssistant, config: Union[ConfigType, Template], @@ -111,6 +252,7 @@ async def async_and_from_config( await async_from_config(hass, entry, False) for entry in config["conditions"] ] + @trace_condition_function def if_and_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -118,8 +260,9 @@ def if_and_condition( errors = [] for index, check in enumerate(checks): try: - if not check(hass, variables): - return False + with condition_path(["conditions", str(index)]): + if not check(hass, variables): + return False except ConditionError as ex: errors.append( ConditionErrorIndex("and", index=index, total=len(checks), error=ex) @@ -144,6 +287,7 @@ async def async_or_from_config( await async_from_config(hass, entry, False) for entry in config["conditions"] ] + @trace_condition_function def if_or_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -151,8 +295,9 @@ def if_or_condition( errors = [] for index, check in enumerate(checks): try: - if check(hass, variables): - return True + with condition_path(["conditions", str(index)]): + if check(hass, variables): + return True except ConditionError as ex: errors.append( ConditionErrorIndex("or", index=index, total=len(checks), error=ex) @@ -177,6 +322,7 @@ async def async_not_from_config( await async_from_config(hass, entry, False) for entry in config["conditions"] ] + @trace_condition_function def if_not_condition( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -184,8 +330,9 @@ def if_not_condition( errors = [] for index, check in enumerate(checks): try: - if check(hass, variables): - return False + with condition_path(["conditions", str(index)]): + if check(hass, variables): + return False except ConditionError as ex: errors.append( ConditionErrorIndex("not", index=index, total=len(checks), error=ex) @@ -290,6 +437,11 @@ def async_numeric_state( ) try: if fvalue >= float(below_entity.state): + condition_trace_set_result( + False, + state=fvalue, + wanted_state_below=float(below_entity.state), + ) return False except (ValueError, TypeError) as ex: raise ConditionErrorMessage( @@ -297,6 +449,7 @@ def async_numeric_state( f"the 'below' entity {below} state '{below_entity.state}' cannot be processed as a number", ) from ex elif fvalue >= below: + condition_trace_set_result(False, state=fvalue, wanted_state_below=below) return False if above is not None: @@ -311,6 +464,11 @@ def async_numeric_state( ) try: if fvalue <= float(above_entity.state): + condition_trace_set_result( + False, + state=fvalue, + wanted_state_above=float(above_entity.state), + ) return False except (ValueError, TypeError) as ex: raise ConditionErrorMessage( @@ -318,8 +476,10 @@ def async_numeric_state( f"the 'above' entity {above} state '{above_entity.state}' cannot be processed as a number", ) from ex elif fvalue <= above: + condition_trace_set_result(False, state=fvalue, wanted_state_above=above) return False + condition_trace_set_result(True, state=fvalue) return True @@ -335,6 +495,7 @@ def async_numeric_state_from_config( above = config.get(CONF_ABOVE) value_template = config.get(CONF_VALUE_TEMPLATE) + @trace_condition_function def if_numeric_state( hass: HomeAssistant, variables: TemplateVarsType = None ) -> bool: @@ -345,10 +506,19 @@ def if_numeric_state( errors = [] for index, entity_id in enumerate(entity_ids): try: - if not async_numeric_state( - hass, entity_id, below, above, value_template, variables, attribute + with condition_path(["entity_id", str(index)]), trace_condition( + variables ): - return False + if not async_numeric_state( + hass, + entity_id, + below, + above, + value_template, + variables, + attribute, + ): + return False except ConditionError as ex: errors.append( ConditionErrorIndex( @@ -421,9 +591,13 @@ def state( break if for_period is None or not is_state: + condition_trace_set_result(is_state, state=value, wanted_state=state_value) return is_state - return dt_util.utcnow() - for_period > entity.last_changed + duration = dt_util.utcnow() - for_period + duration_ok = duration > entity.last_changed + condition_trace_set_result(duration_ok, state=value, duration=duration) + return duration_ok def state_from_config( @@ -440,13 +614,17 @@ def state_from_config( if not isinstance(req_states, list): req_states = [req_states] + @trace_condition_function def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" errors = [] for index, entity_id in enumerate(entity_ids): try: - if not state(hass, entity_id, req_states, for_period, attribute): - return False + with condition_path(["entity_id", str(index)]), trace_condition( + variables + ): + if not state(hass, entity_id, req_states, for_period, attribute): + return False except ConditionError as ex: errors.append( ConditionErrorIndex( @@ -529,11 +707,12 @@ def sun_from_config( before_offset = config.get("before_offset") after_offset = config.get("after_offset") - def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: + @trace_condition_function + def sun_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Validate time based if-condition.""" return sun(hass, before, after, before_offset, after_offset) - return time_if + return sun_if def template( @@ -565,6 +744,7 @@ def async_template_from_config( config = cv.TEMPLATE_CONDITION_SCHEMA(config) value_template = cast(Template, config.get(CONF_VALUE_TEMPLATE)) + @trace_condition_function def template_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Validate template based if-condition.""" value_template.hass = hass @@ -645,6 +825,7 @@ def time_from_config( after = config.get(CONF_AFTER) weekday = config.get(CONF_WEEKDAY) + @trace_condition_function def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Validate time based if-condition.""" return time(hass, before, after, weekday) @@ -710,6 +891,7 @@ def zone_from_config( entity_ids = config.get(CONF_ENTITY_ID, []) zone_entity_ids = config.get(CONF_ZONE, []) + @trace_condition_function def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: """Test if condition.""" errors = [] @@ -750,9 +932,11 @@ async def async_device_from_config( platform = await async_get_device_automation_platform( hass, config[CONF_DOMAIN], "condition" ) - return cast( - ConditionCheckerType, - platform.async_condition_from_config(config, config_validation), # type: ignore + return trace_condition_function( + cast( + ConditionCheckerType, + platform.async_condition_from_config(config, config_validation), # type: ignore + ) ) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index e4eb0d4a901788..aaed10f7814f26 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,5 +1,7 @@ """Helpers to execute scripts.""" import asyncio +from contextlib import contextmanager +from contextvars import ContextVar from datetime import datetime, timedelta from functools import partial import itertools @@ -63,6 +65,12 @@ callback, ) from homeassistant.helpers import condition, config_validation as cv, service, template +from homeassistant.helpers.condition import ( + condition_path, + condition_trace_clear, + condition_trace_get, + trace_condition_function, +) from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( @@ -73,6 +81,14 @@ from homeassistant.util import slugify from homeassistant.util.dt import utcnow +from .trace import ( + TraceElement, + trace_append_element, + trace_stack_pop, + trace_stack_push, + trace_stack_top, +) + # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs SCRIPT_MODE_PARALLEL = "parallel" @@ -108,6 +124,115 @@ _SHUTDOWN_MAX_WAIT = 60 +ACTION_TRACE_NODE_MAX_LEN = 20 # Max the length of a trace node for repeated actions + +action_trace = ContextVar("action_trace", default=None) +action_trace_stack = ContextVar("action_trace_stack", default=None) +action_path_stack = ContextVar("action_path_stack", default=None) + + +def action_trace_stack_push(node): + """Push a TraceElement to the top of the trace stack.""" + trace_stack_push(action_trace_stack, node) + + +def action_trace_stack_pop(): + """Remove the top element from the trace stack.""" + trace_stack_pop(action_trace_stack) + + +def action_trace_stack_top(): + """Return the element at the top of the trace stack.""" + return trace_stack_top(action_trace_stack) + + +def action_path_push(suffix): + """Go deeper in the config tree.""" + if isinstance(suffix, str): + suffix = [suffix] + for node in suffix: + trace_stack_push(action_path_stack, node) + return len(suffix) + + +def action_path_pop(count): + """Go n levels up in the config tree.""" + for _ in range(count): + trace_stack_pop(action_path_stack) + + +def action_path_get(): + """Return a string representing the current location in the config tree.""" + path = action_path_stack.get() + if not path: + return "" + return "/".join(path) + + +def action_trace_get(): + """Return the trace of the script that was executed.""" + return action_trace.get() + + +def action_trace_clear(): + """Clear the action trace.""" + action_trace.set({}) + action_trace_stack.set(None) + action_path_stack.set(None) + + +def action_trace_append(variables, path): + """Append a TraceElement to trace[path].""" + trace_element = TraceElement(variables) + trace_append_element(action_trace, trace_element, path, ACTION_TRACE_NODE_MAX_LEN) + return trace_element + + +def action_trace_set_result(**kwargs): + """Set the result of TraceElement at the top of the stack.""" + node = action_trace_stack_top() + node.set_result(**kwargs) + + +def action_trace_add_conditions(): + """Add the result of condition evaluation to the action trace.""" + condition_trace = condition_trace_get() + condition_trace_clear() + + if condition_trace is None: + return + + _action_path = action_path_get() + for cond_path, conditions in condition_trace.items(): + path = _action_path + "/" + cond_path if cond_path else _action_path + for cond in conditions: + trace_append_element(action_trace, cond, path) + + +@contextmanager +def trace_action(variables): + """Trace action execution.""" + trace_element = action_trace_append(variables, action_path_get()) + action_trace_stack_push(trace_element) + try: + yield trace_element + except Exception as ex: # pylint: disable=broad-except + trace_element.set_error(ex) + raise ex + finally: + action_trace_stack_pop() + + +@contextmanager +def action_path(suffix): + """Go deeper in the config tree.""" + count = action_path_push(suffix) + try: + yield + finally: + action_path_pop(count) + + def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA): """Make a schema for a component that uses the script helper.""" return vol.Schema( @@ -258,16 +383,16 @@ async def async_run(self) -> None: self._finish() async def _async_step(self, log_exceptions): - try: - await getattr( - self, f"_async_{cv.determine_script_action(self._action)}_step" - )() - except Exception as ex: - if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( - self._log_exceptions or log_exceptions - ): - self._log_exception(ex) - raise + with action_path(str(self._step)), trace_action(None): + try: + handler = f"_async_{cv.determine_script_action(self._action)}_step" + await getattr(self, handler)() + except Exception as ex: + if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( + self._log_exceptions or log_exceptions + ): + self._log_exception(ex) + raise def _finish(self) -> None: self._script._runs.remove(self) # pylint: disable=protected-access @@ -514,15 +639,37 @@ async def _async_condition_step(self): ) cond = await self._async_get_condition(self._action) try: - check = cond(self._hass, self._variables) + with condition_path("condition"): + check = cond(self._hass, self._variables) except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'condition' evaluation:\n%s", ex) check = False self._log("Test condition %s: %s", self._script.last_action, check) + action_trace_set_result(result=check) + action_trace_add_conditions() if not check: raise _StopScript + def _test_conditions(self, conditions, name): + @trace_condition_function + def traced_test_conditions(hass, variables): + try: + with condition_path("conditions"): + for idx, cond in enumerate(conditions): + with condition_path(str(idx)): + if not cond(hass, variables): + return False + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in '%s[%s]' evaluation: %s", name, idx, ex) + return None + + return True + + result = traced_test_conditions(self._hass, self._variables) + action_trace_add_conditions() + return result + async def _async_repeat_step(self): """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") @@ -541,7 +688,8 @@ def set_repeat_var(iteration, count=None): async def async_run_sequence(iteration, extra_msg=""): self._log("Repeating %s: Iteration %i%s", description, iteration, extra_msg) - await self._async_run_script(script) + with action_path(str(self._step)): + await self._async_run_script(script) if CONF_COUNT in repeat: count = repeat[CONF_COUNT] @@ -570,9 +718,9 @@ async def async_run_sequence(iteration, extra_msg=""): for iteration in itertools.count(1): set_repeat_var(iteration) try: - if self._stop.is_set() or not all( - cond(self._hass, self._variables) for cond in conditions - ): + if self._stop.is_set(): + break + if not self._test_conditions(conditions, "while"): break except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'while' evaluation:\n%s", ex) @@ -588,9 +736,9 @@ async def async_run_sequence(iteration, extra_msg=""): set_repeat_var(iteration) await async_run_sequence(iteration) try: - if self._stop.is_set() or all( - cond(self._hass, self._variables) for cond in conditions - ): + if self._stop.is_set(): + break + if self._test_conditions(conditions, "until") in [True, None]: break except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'until' evaluation:\n%s", ex) @@ -606,18 +754,20 @@ async def _async_choose_step(self) -> None: # pylint: disable=protected-access choose_data = await self._script._async_get_choose_data(self._step) - for conditions, script in choose_data["choices"]: - try: - if all( - condition(self._hass, self._variables) for condition in conditions - ): - await self._async_run_script(script) - return - except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) + for idx, (conditions, script) in enumerate(choose_data["choices"]): + with action_path(str(idx)): + try: + if self._test_conditions(conditions, "choose"): + action_trace_set_result(choice=idx) + await self._async_run_script(script) + return + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: - await self._async_run_script(choose_data["default"]) + action_trace_set_result(choice="default") + with action_path("default"): + await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): """Wait for a trigger event.""" diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py new file mode 100644 index 00000000000000..450faa0336f260 --- /dev/null +++ b/homeassistant/helpers/trace.py @@ -0,0 +1,78 @@ +"""Helpers for script and condition tracing.""" +from collections import deque +from contextvars import ContextVar +from typing import Any, Dict, Optional + +from homeassistant.helpers.typing import TemplateVarsType +import homeassistant.util.dt as dt_util + + +def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: + """Push an element to the top of a trace stack.""" + trace_stack = trace_stack_var.get() + if trace_stack is None: + trace_stack = [] + trace_stack_var.set(trace_stack) + trace_stack.append(node) + + +def trace_stack_pop(trace_stack_var: ContextVar) -> None: + """Remove the top element from a trace stack.""" + trace_stack = trace_stack_var.get() + trace_stack.pop() + + +def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: + """Return the element at the top of a trace stack.""" + trace_stack = trace_stack_var.get() + return trace_stack[-1] if trace_stack else None + + +class TraceElement: + """Container for trace data.""" + + def __init__(self, variables: TemplateVarsType): + """Container for trace data.""" + self._error: Optional[Exception] = None + self._result: Optional[dict] = None + self._timestamp = dt_util.utcnow() + self._variables = variables + + def __repr__(self) -> str: + """Container for trace data.""" + return str(self.as_dict()) + + def set_error(self, ex: Exception) -> None: + """Set error.""" + self._error = ex + + def set_result(self, **kwargs: Any) -> None: + """Set result.""" + self._result = {**kwargs} + + def as_dict(self) -> Dict[str, Any]: + """Return dictionary version of this TraceElement.""" + result: Dict[str, Any] = {"timestamp": self._timestamp} + # Commented out because we get too many copies of the same data + # result["variables"] = self._variables + if self._error is not None: + result["error"] = str(self._error) + if self._result is not None: + result["result"] = self._result + return result + + +def trace_append_element( + trace_var: ContextVar, + trace_element: TraceElement, + path: str, + maxlen: Optional[int] = None, +) -> None: + """Append a TraceElement to trace[path].""" + trace = trace_var.get() + if trace is None: + trace_var.set({}) + trace = trace_var.get() + if path not in trace: + trace[path] = deque(maxlen=maxlen) + trace[path].append(trace_element) diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 541cd3068d2a49..7e0cf9e8e4d619 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -164,3 +164,164 @@ def mock_write(path, data): assert written[0][0]["id"] == "moon" assert len(ent_reg.entities) == 1 + + +async def test_get_automation_trace(hass, hass_ws_client): + """Test deleting an automation.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"sun": []} + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert "moon" not in response["result"] + assert len(response["result"]["sun"]) == 1 + trace = response["result"]["sun"][0] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert trace["action_trace"]["action/0"][0]["error"] + assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["condition_trace"] == {} + assert trace["config"] == sun_config + assert trace["context"] + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert "sun" not in response["result"] + assert len(response["result"]["moon"]) == 1 + trace = response["result"]["moon"][0] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # Get trace + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert "sun" not in response["result"] + assert len(response["result"]["moon"]) == 2 + trace = response["result"]["moon"][1] + assert len(trace["action_trace"]) == 0 + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert "sun" not in response["result"] + assert len(response["result"]["moon"]) == 3 + trace = response["result"]["moon"][2] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"]["description"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 5074b6e70c462d..b3e950131b0476 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -11,6 +11,32 @@ from homeassistant.util import dt +def assert_element(trace_element, expected_element, path): + """Assert a trace element is as expected. + + Note: Unused variable path is passed to get helpful errors from pytest. + """ + for result_key, result in expected_element.get("result", {}).items(): + assert trace_element._result[result_key] == result + if "error_type" in expected_element: + assert isinstance(trace_element._error, expected_element["error_type"]) + else: + assert trace_element._error is None + + +def assert_condition_trace(expected): + """Assert a trace condition sequence is as expected.""" + condition_trace = condition.condition_trace_get() + condition.condition_trace_clear() + expected_trace_keys = list(expected.keys()) + assert list(condition_trace.keys()) == expected_trace_keys + for trace_key_index, key in enumerate(expected_trace_keys): + assert len(condition_trace[key]) == len(expected[key]) + for index, element in enumerate(expected[key]): + path = f"[{trace_key_index}][{index}]" + assert_element(condition_trace[key][index], element, path) + + async def test_invalid_condition(hass): """Test if invalid condition raises.""" with pytest.raises(HomeAssistantError): @@ -53,15 +79,112 @@ async def test_and_condition(hass): with pytest.raises(ConditionError): test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) hass.states.async_set("sensor.temperature", 120) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 105) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 100) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) + + +async def test_and_condition_raises(hass): + """Test the 'and' condition.""" + test = await condition.async_from_config( + hass, + { + "alias": "And Condition", + "condition": "and", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature2", + "above": 110, + }, + ], + }, + ) + + # All subconditions raise, the AND-condition should raise + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) + + # The first subconditions raises, the second returns True, the AND-condition + # should raise + hass.states.async_set("sensor.temperature2", 120) + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) + + # The first subconditions raises, the second returns False, the AND-condition + # should return False + hass.states.async_set("sensor.temperature2", 90) + assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) async def test_and_condition_with_template(hass): @@ -119,15 +242,114 @@ async def test_or_condition(hass): with pytest.raises(ConditionError): test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) hass.states.async_set("sensor.temperature", 120) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 105) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) hass.states.async_set("sensor.temperature", 100) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [{"result": {"result": True}}], + } + ) + + +async def test_or_condition_raises(hass): + """Test the 'or' condition.""" + test = await condition.async_from_config( + hass, + { + "alias": "Or Condition", + "condition": "or", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature2", + "above": 110, + }, + ], + }, + ) + + # All subconditions raise, the OR-condition should raise + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) + + # The first subconditions raises, the second returns False, the OR-condition + # should raise + hass.states.async_set("sensor.temperature2", 100) + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) + + # The first subconditions raises, the second returns True, the OR-condition + # should return True + hass.states.async_set("sensor.temperature2", 120) + assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) async def test_or_condition_with_template(hass): @@ -181,18 +403,126 @@ async def test_not_condition(hass): with pytest.raises(ConditionError): test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) hass.states.async_set("sensor.temperature", 101) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 50) assert test(hass) + assert_condition_trace( + { + "": [{"result": {"result": True}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) hass.states.async_set("sensor.temperature", 49) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) hass.states.async_set("sensor.temperature", 100) assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [{"result": {"result": True}}], + } + ) + + +async def test_not_condition_raises(hass): + """Test the 'and' condition.""" + test = await condition.async_from_config( + hass, + { + "alias": "Not Condition", + "condition": "not", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature2", + "below": 50, + }, + ], + }, + ) + + # All subconditions raise, the NOT-condition should raise + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"error_type": ConditionError}], + "conditions/1/entity_id/0": [{"error_type": ConditionError}], + } + ) + + # The first subconditions raises, the second returns False, the NOT-condition + # should raise + hass.states.async_set("sensor.temperature2", 90) + with pytest.raises(ConditionError): + test(hass) + assert_condition_trace( + { + "": [{"error_type": ConditionError}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [{"result": {"result": False}}], + } + ) + + # The first subconditions raises, the second returns True, the NOT-condition + # should return False + hass.states.async_set("sensor.temperature2", 40) + assert not test(hass) + assert_condition_trace( + { + "": [{"result": {"result": False}}], + "conditions/0": [{"error_type": ConditionError}], + "conditions/0/entity_id/0": [{"error_type": ConditionError}], + "conditions/1": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True}}], + } + ) async def test_not_condition_with_template(hass): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index d2946fcd4947eb..04f922b685e0fe 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -30,6 +30,32 @@ ENTITY_ID = "script.test" +def assert_element(trace_element, expected_element, path): + """Assert a trace element is as expected. + + Note: Unused variable 'path' is passed to get helpful errors from pytest. + """ + for result_key, result in expected_element.get("result", {}).items(): + assert trace_element._result[result_key] == result + if "error_type" in expected_element: + assert isinstance(trace_element._error, expected_element["error_type"]) + else: + assert trace_element._error is None + + +def assert_action_trace(expected): + """Assert a trace condition sequence is as expected.""" + action_trace = script.action_trace_get() + script.action_trace_clear() + expected_trace_keys = list(expected.keys()) + assert list(action_trace.keys()) == expected_trace_keys + for trace_key_index, key in enumerate(expected_trace_keys): + assert len(action_trace[key]) == len(expected[key]) + for index, element in enumerate(expected[key]): + path = f"[{trace_key_index}][{index}]" + assert_element(action_trace[key][index], element, path) + + def async_watch_for_action(script_obj, message): """Watch for message in last_action.""" flag = asyncio.Event() @@ -54,9 +80,14 @@ async def test_firing_event_basic(hass, caplog): sequence = cv.SCRIPT_SCHEMA( {"alias": alias, "event": event, "event_data": {"hello": "world"}} ) - script_obj = script.Script( - hass, sequence, "Test Name", "test_domain", running_description="test script" - ) + with script.trace_action(None): + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + running_description="test script", + ) await script_obj.async_run(context=context) await hass.async_block_till_done() @@ -67,6 +98,12 @@ async def test_firing_event_basic(hass, caplog): assert ".test_name:" in caplog.text assert "Test Name: Running test script" in caplog.text assert f"Executing step {alias}" in caplog.text + assert_action_trace( + { + "": [{}], + "0": [{}], + } + ) async def test_firing_event_template(hass): From 208a104e96290bcbc6760f62a4b4ba36c8cd5a86 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 18:33:35 +0100 Subject: [PATCH 0993/1818] Fix secrets in files included via include_dir_list (#47350) --- homeassistant/util/yaml/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 04e51a5a9c598b..b4699ed95d2602 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -227,7 +227,7 @@ def _include_dir_list_yaml( """Load multiple files from directory as a list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) return [ - load_yaml(f) + load_yaml(f, loader.secrets) for f in _find_files(loc, "*.yaml") if os.path.basename(f) != SECRET_YAML ] From b49a672fd5e0bbe7475864f55c30cebb97fe1b78 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Thu, 4 Mar 2021 21:47:24 +0100 Subject: [PATCH 0994/1818] Catch ConditionError in generic_thermostat climate (#47359) --- .../components/generic_thermostat/climate.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 5fbdf49914614d..7062267de19751 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -35,6 +35,7 @@ STATE_UNKNOWN, ) from homeassistant.core import DOMAIN as HA_DOMAIN, CoreState, callback +from homeassistant.exceptions import ConditionError from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( @@ -439,12 +440,16 @@ async def _async_control_heating(self, time=None, force=False): current_state = STATE_ON else: current_state = HVAC_MODE_OFF - long_enough = condition.state( - self.hass, - self.heater_entity_id, - current_state, - self.min_cycle_duration, - ) + try: + long_enough = condition.state( + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) + except ConditionError: + long_enough = False + if not long_enough: return From b58f9ce33aacd4aec41f1d5f37dcd09fc98003b3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 4 Mar 2021 21:53:09 +0100 Subject: [PATCH 0995/1818] Fix Xiaomi Miio setup of switch entity for lumi.acpartner.v3 (#47345) --- .../components/xiaomi_miio/__init__.py | 2 +- homeassistant/components/xiaomi_miio/const.py | 1 - homeassistant/components/xiaomi_miio/switch.py | 18 +++++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index a72298c7c4406a..139ac017f66f07 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) -GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] +GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 5dc381b17fb6b7..ed34192eae3a04 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -68,7 +68,6 @@ "chuangmi.plug.v2", "chuangmi.plug.hmi205", "chuangmi.plug.hmi206", - "lumi.acpartner.v3", ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum"] diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 04bb40ec27dd65..b290cc6a956dad 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -21,6 +21,7 @@ from .const import ( CONF_DEVICE, CONF_FLOW_TYPE, + CONF_GATEWAY, CONF_MODEL, DOMAIN, SERVICE_SET_POWER_MODE, @@ -128,16 +129,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the switch from a config entry.""" entities = [] - if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE or ( + config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY + and model == "lumi.acpartner.v3" + ): if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {} - host = config_entry.data[CONF_HOST] - token = config_entry.data[CONF_TOKEN] - name = config_entry.title - model = config_entry.data[CONF_MODEL] - unique_id = config_entry.unique_id - _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]: From 62d8e47c5184927f7eebe919cab524b2b68a1f42 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Mar 2021 11:02:50 -1000 Subject: [PATCH 0996/1818] Map silent as a preset mode for fan backcompat (#47396) The original change did not map silent as a preset mode because it was not clear if it was a speed or a preset. --- homeassistant/components/fan/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 25911eb2d06658..7a50997d76dfa1 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -77,6 +77,7 @@ _NOT_SPEED_IDLE = "idle" _NOT_SPEED_FAVORITE = "favorite" _NOT_SPEED_SLEEP = "sleep" +_NOT_SPEED_SILENT = "silent" _NOT_SPEEDS_FILTER = { _NOT_SPEED_OFF, @@ -85,6 +86,7 @@ _NOT_SPEED_SMART, _NOT_SPEED_INTERVAL, _NOT_SPEED_IDLE, + _NOT_SPEED_SILENT, _NOT_SPEED_SLEEP, _NOT_SPEED_FAVORITE, } @@ -651,7 +653,7 @@ def speed_list_without_preset_modes(speed_list: List): output: ["1", "2", "3", "4", "5", "6", "7"] input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"] - output: ["Silent", "Medium", "High", "Strong"] + output: ["Medium", "High", "Strong"] """ return [speed for speed in speed_list if speed.lower() not in _NOT_SPEEDS_FILTER] @@ -673,7 +675,7 @@ def preset_modes_from_speed_list(speed_list: List): output: ["smart"] input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"] - output: ["Auto", "Favorite", "Idle"] + output: ["Auto", "Silent", "Favorite", "Idle"] """ return [ From f05f60c4c472dd7473e5a2af41424b146fc1d112 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 4 Mar 2021 13:07:42 -0800 Subject: [PATCH 0997/1818] Revert "Speed-up wemo discovery (#46821)" (#47392) This reverts commit 6e52b26c06098052d379065b00f570c2a44653e1. --- homeassistant/components/wemo/__init__.py | 48 ++++------------------- tests/components/wemo/test_init.py | 33 +++++----------- 2 files changed, 17 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index df737f101baaa6..db380ae11cac2f 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,4 +1,5 @@ """Support for WeMo device discovery.""" +import asyncio import logging import pywemo @@ -15,14 +16,9 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from homeassistant.util.async_ import gather_with_concurrency from .const import DOMAIN -# Max number of devices to initialize at once. This limit is in place to -# avoid tying up too many executor threads with WeMo device setup. -MAX_CONCURRENCY = 3 - # Mapping from Wemo model_name to domain. WEMO_MODEL_DISPATCH = { "Bridge": LIGHT_DOMAIN, @@ -118,12 +114,11 @@ async def async_stop_wemo(event): static_conf = config.get(CONF_STATIC, []) if static_conf: _LOGGER.debug("Adding statically configured WeMo devices...") - for device in await gather_with_concurrency( - MAX_CONCURRENCY, + for device in await asyncio.gather( *[ hass.async_add_executor_job(validate_static_config, host, port) for host, port in static_conf - ], + ] ): if device: wemo_dispatcher.async_add_unique_device(hass, device) @@ -192,44 +187,15 @@ def __init__(self, hass: HomeAssistant, wemo_dispatcher: WemoDispatcher) -> None self._wemo_dispatcher = wemo_dispatcher self._stop = None self._scan_delay = 0 - self._upnp_entries = set() - - async def async_add_from_upnp_entry(self, entry: pywemo.ssdp.UPNPEntry) -> None: - """Create a WeMoDevice from an UPNPEntry and add it to the dispatcher. - - Uses the self._upnp_entries set to avoid interrogating the same device - multiple times. - """ - if entry in self._upnp_entries: - return - try: - device = await self._hass.async_add_executor_job( - pywemo.discovery.device_from_uuid_and_location, - entry.udn, - entry.location, - ) - except pywemo.PyWeMoException as err: - _LOGGER.error("Unable to setup WeMo %r (%s)", entry, err) - else: - self._wemo_dispatcher.async_add_unique_device(self._hass, device) - self._upnp_entries.add(entry) async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" _LOGGER.debug("Scanning network for WeMo devices...") try: - # pywemo.ssdp.scan is a light-weight UDP UPnP scan for WeMo devices. - entries = await self._hass.async_add_executor_job(pywemo.ssdp.scan) - - # async_add_from_upnp_entry causes multiple HTTP requests to be sent - # to the WeMo device for the initial setup of the WeMoDevice - # instance. This may take some time to complete. The per-device - # setup work is done in parallel to speed up initial setup for the - # component. - await gather_with_concurrency( - MAX_CONCURRENCY, - *[self.async_add_from_upnp_entry(entry) for entry in entries], - ) + for device in await self._hass.async_add_executor_job( + pywemo.discover_devices + ): + self._wemo_dispatcher.async_add_unique_device(self._hass, device) finally: # Run discovery more frequently after hass has just started. self._scan_delay = min( diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 7c2b43dfd8cfd2..374222d86888da 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -100,41 +100,28 @@ async def test_static_config_with_invalid_host(hass): async def test_discovery(hass, pywemo_registry): """Verify that discovery dispatches devices to the platform for setup.""" - def create_device(uuid, location): + def create_device(counter): """Create a unique mock Motion detector device for each counter value.""" device = create_autospec(pywemo.Motion, instance=True) - device.host = location - device.port = MOCK_PORT - device.name = f"{MOCK_NAME}_{uuid}" - device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{uuid}" + device.host = f"{MOCK_HOST}_{counter}" + device.port = MOCK_PORT + counter + device.name = f"{MOCK_NAME}_{counter}" + device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{counter}" device.model_name = "Motion" device.get_state.return_value = 0 # Default to Off return device - def create_upnp_entry(counter): - return pywemo.ssdp.UPNPEntry.from_response( - "\r\n".join( - [ - "", - f"LOCATION: http://192.168.1.100:{counter}/setup.xml", - f"USN: uuid:Socket-1_0-SERIAL{counter}::upnp:rootdevice", - "", - ] - ) - ) - - upnp_entries = [create_upnp_entry(0), create_upnp_entry(1)] + pywemo_devices = [create_device(0), create_device(1)] # Setup the component and start discovery. with patch( - "pywemo.discovery.device_from_uuid_and_location", side_effect=create_device - ), patch("pywemo.ssdp.scan", return_value=upnp_entries) as mock_scan: + "pywemo.discover_devices", return_value=pywemo_devices + ) as mock_discovery: assert await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_DISCOVERY: True}} ) await pywemo_registry.semaphore.acquire() # Returns after platform setup. - mock_scan.assert_called() - # Add two of the same entries to test deduplication. - upnp_entries.extend([create_upnp_entry(2), create_upnp_entry(2)]) + mock_discovery.assert_called() + pywemo_devices.append(create_device(2)) # Test that discovery runs periodically and the async_dispatcher_send code works. async_fire_time_changed( From 7a8b7224c8639b7e7f3fd22e2e596f15ac23c53d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 22:09:08 +0100 Subject: [PATCH 0998/1818] Don't raise on known non-matching states in numeric state condition (#47378) --- homeassistant/helpers/condition.py | 25 ++++--- .../triggers/test_numeric_state.py | 61 +---------------- tests/helpers/test_condition.py | 68 +++++++++---------- 3 files changed, 48 insertions(+), 106 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 1abbf550bb151a..bc1ff21b9cc658 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -412,10 +412,9 @@ def async_numeric_state( "numeric_state", f"template error: {ex}" ) from ex + # Known states that never match the numeric condition if value in (STATE_UNAVAILABLE, STATE_UNKNOWN): - raise ConditionErrorMessage( - "numeric_state", f"state of {entity_id} is unavailable" - ) + return False try: fvalue = float(value) @@ -428,13 +427,15 @@ def async_numeric_state( if below is not None: if isinstance(below, str): below_entity = hass.states.get(below) - if not below_entity or below_entity.state in ( + if not below_entity: + raise ConditionErrorMessage( + "numeric_state", f"unknown 'below' entity {below}" + ) + if below_entity.state in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionErrorMessage( - "numeric_state", f"the 'below' entity {below} is unavailable" - ) + return False try: if fvalue >= float(below_entity.state): condition_trace_set_result( @@ -455,13 +456,15 @@ def async_numeric_state( if above is not None: if isinstance(above, str): above_entity = hass.states.get(above) - if not above_entity or above_entity.state in ( + if not above_entity: + raise ConditionErrorMessage( + "numeric_state", f"unknown 'above' entity {above}" + ) + if above_entity.state in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionErrorMessage( - "numeric_state", f"the 'above' entity {above} is unavailable" - ) + return False try: if fvalue <= float(above_entity.state): condition_trace_set_result( diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 831e20b78a1dbc..9eb9ac79a941e6 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -10,12 +10,7 @@ from homeassistant.components.homeassistant.triggers import ( numeric_state as numeric_state_trigger, ) -from homeassistant.const import ( - ATTR_ENTITY_ID, - ENTITY_MATCH_ALL, - SERVICE_TURN_OFF, - STATE_UNAVAILABLE, -) +from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -347,52 +342,6 @@ async def test_if_fires_on_entity_unavailable_at_startup(hass, calls): assert len(calls) == 0 -async def test_if_not_fires_on_entity_unavailable(hass, calls): - """Test the firing with entity changing to unavailable.""" - # set initial state - hass.states.async_set("test.entity", 9) - await hass.async_block_till_done() - - assert await async_setup_component( - hass, - automation.DOMAIN, - { - automation.DOMAIN: { - "trigger": { - "platform": "numeric_state", - "entity_id": "test.entity", - "above": 10, - }, - "action": {"service": "test.automation"}, - } - }, - ) - - # 11 is above 10 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 1 - - # Going to unavailable and back should not fire - hass.states.async_set("test.entity", STATE_UNAVAILABLE) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 1 - - # Crossing threshold via unavailable should fire - hass.states.async_set("test.entity", 9) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", STATE_UNAVAILABLE) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 2 - - @pytest.mark.parametrize("above", (10, "input_number.value_10")) async def test_if_fires_on_entity_change_below_to_above(hass, calls, above): """Test the firing with changed entity.""" @@ -1522,7 +1471,7 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls, above, below) assert len(calls) == 1 -async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): +async def test_if_not_fires_on_error_with_for_template(hass, calls): """Test for not firing on error with for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1547,17 +1496,11 @@ async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): await hass.async_block_till_done() assert len(calls) == 0 - caplog.clear() - caplog.set_level(logging.WARNING) - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) hass.states.async_set("test.entity", "unavailable") await hass.async_block_till_done() assert len(calls) == 0 - assert len(caplog.record_tuples) == 1 - assert caplog.record_tuples[0][1] == logging.WARNING - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) hass.states.async_set("test.entity", 101) await hass.async_block_till_done() diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index b3e950131b0476..cfed8ebbcf650b 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,5 +1,4 @@ """Test the condition helper.""" -from logging import WARNING from unittest.mock import patch import pytest @@ -693,27 +692,6 @@ async def test_time_using_input_datetime(hass): condition.time(hass, before="input_datetime.not_existing") -async def test_if_numeric_state_raises_on_unavailable(hass, caplog): - """Test numeric_state raises on unavailable/unknown state.""" - test = await condition.async_from_config( - hass, - {"condition": "numeric_state", "entity_id": "sensor.temperature", "below": 42}, - ) - - caplog.clear() - caplog.set_level(WARNING) - - hass.states.async_set("sensor.temperature", "unavailable") - with pytest.raises(ConditionError): - test(hass) - assert len(caplog.record_tuples) == 0 - - hass.states.async_set("sensor.temperature", "unknown") - with pytest.raises(ConditionError): - test(hass) - assert len(caplog.record_tuples) == 0 - - async def test_state_raises(hass): """Test that state raises ConditionError on errors.""" # No entity @@ -961,6 +939,26 @@ async def test_state_using_input_entities(hass): assert test(hass) +async def test_numeric_state_known_non_matching(hass): + """Test that numeric_state doesn't match on known non-matching states.""" + hass.states.async_set("sensor.temperature", "unavailable") + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "above": 0, + }, + ) + + # Unavailable state + assert not test(hass) + + # Unknown state + hass.states.async_set("sensor.temperature", "unknown") + assert not test(hass) + + async def test_numeric_state_raises(hass): """Test that numeric_state raises ConditionError on errors.""" # Unknown entities @@ -1007,20 +1005,6 @@ async def test_numeric_state_raises(hass): hass.states.async_set("sensor.temperature", 50) test(hass) - # Unavailable state - with pytest.raises(ConditionError, match="state of .* is unavailable"): - test = await condition.async_from_config( - hass, - { - "condition": "numeric_state", - "entity_id": "sensor.temperature", - "above": 0, - }, - ) - - hass.states.async_set("sensor.temperature", "unavailable") - test(hass) - # Bad number with pytest.raises(ConditionError, match="cannot be processed as a number"): test = await condition.async_from_config( @@ -1182,6 +1166,12 @@ async def test_numeric_state_using_input_number(hass): hass.states.async_set("sensor.temperature", 100) assert not test(hass) + hass.states.async_set("input_number.high", "unknown") + assert not test(hass) + + hass.states.async_set("input_number.high", "unavailable") + assert not test(hass) + await hass.services.async_call( "input_number", "set_value", @@ -1193,6 +1183,12 @@ async def test_numeric_state_using_input_number(hass): ) assert test(hass) + hass.states.async_set("input_number.low", "unknown") + assert not test(hass) + + hass.states.async_set("input_number.low", "unavailable") + assert not test(hass) + with pytest.raises(ConditionError): condition.async_numeric_state( hass, entity="sensor.temperature", below="input_number.not_exist" From cea4808db80a5b0fdafea692926a3a0cf9a09b92 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Mar 2021 22:09:51 +0100 Subject: [PATCH 0999/1818] Update frontend to 20210302.4 (#47383) --- 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 4d4127fc2f2795..a5b4c6f10d5397 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==20210302.3" + "home-assistant-frontend==20210302.4" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 752a3755169c5f..919b53f64ed31f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index c1f3d577251392..c3914ec2c25e6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a27ec0f8051e86..06fb55c9f0bbb8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From d64fe6ea326314d7e8df6489afe664cbdb9a12fd Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 22:11:07 +0100 Subject: [PATCH 1000/1818] Fix zwave_js manual reconfiguration of add-on managed entry (#47364) --- homeassistant/components/zwave_js/config_flow.py | 10 +++++++++- tests/components/zwave_js/test_config_flow.py | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 37923c574b4371..ac466223fb6e92 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -117,7 +117,15 @@ async def async_step_manual( await self.async_set_unique_id( version_info.home_id, raise_on_progress=False ) - self._abort_if_unique_id_configured(user_input) + # Make sure we disable any add-on handling + # if the controller is reconfigured in a manual step. + self._abort_if_unique_id_configured( + updates={ + **user_input, + CONF_USE_ADDON: False, + CONF_INTEGRATION_CREATED_ADDON: False, + } + ) self.ws_address = user_input[CONF_URL] return self._async_create_entry_from_vars() diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index fc97f7420cf22d..c6b59e0741239c 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -184,7 +184,16 @@ async def test_manual_errors( async def test_manual_already_configured(hass): """Test that only one unique instance is allowed.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "use_addon": True, + "integration_created_addon": True, + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -198,12 +207,15 @@ async def test_manual_already_configured(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], { - "url": "ws://localhost:3000", + "url": "ws://1.1.1.1:3001", }, ) assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://1.1.1.1:3001" + assert entry.data["use_addon"] is False + assert entry.data["integration_created_addon"] is False @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) From 5ced7395f3e5e84ae1ac3da3ac935d22d4ce4791 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 22:11:38 +0100 Subject: [PATCH 1001/1818] Fix access of missing zwave_js climate unit value (#47380) --- homeassistant/components/zwave_js/climate.py | 8 +- tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_climate.py | 17 ++ .../zwave_js/srt321_hrt4_zw_state.json | 262 ++++++++++++++++++ 5 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/zwave_js/srt321_hrt4_zw_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 54966538aaee0f..cc449b89e91a06 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -118,7 +118,7 @@ def __init__( super().__init__(config_entry, client, info) self._hvac_modes: Dict[str, Optional[int]] = {} self._hvac_presets: Dict[str, Optional[int]] = {} - self._unit_value: ZwaveValue = None + self._unit_value: Optional[ZwaveValue] = None self._current_mode = self.get_zwave_value( THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE @@ -215,7 +215,11 @@ def _current_mode_setpoint_enums(self) -> List[Optional[ThermostatSetpointType]] @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - if "f" in self._unit_value.metadata.unit.lower(): + if ( + self._unit_value + and self._unit_value.metadata.unit + and "f" in self._unit_value.metadata.unit.lower() + ): return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index a5ee628754e91d..ec54e13940424e 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,6 +16,7 @@ CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner" BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" EATON_RF9640_ENTITY = "light.allloaddimmer" AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index af87d9d49e1d37..b171fb38425099 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -240,6 +240,12 @@ def nortek_thermostat_state_fixture(): return json.loads(load_fixture("zwave_js/nortek_thermostat_state.json")) +@pytest.fixture(name="srt321_hrt4_zw_state", scope="session") +def srt321_hrt4_zw_state_fixture(): + """Load the climate HRT4-ZW / SRT321 / SRT322 thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/srt321_hrt4_zw_state.json")) + + @pytest.fixture(name="chain_actuator_zws12_state", scope="session") def window_cover_state_fixture(): """Load the window cover node state fixture data.""" @@ -423,6 +429,14 @@ def nortek_thermostat_fixture(client, nortek_thermostat_state): return node +@pytest.fixture(name="srt321_hrt4_zw") +def srt321_hrt4_zw_fixture(client, srt321_hrt4_zw_state): + """Mock a HRT4-ZW / SRT321 / SRT322 thermostat node.""" + node = Node(client, copy.deepcopy(srt321_hrt4_zw_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="aeotec_radiator_thermostat") def aeotec_radiator_thermostat_fixture(client, aeotec_radiator_thermostat_state): """Mock a Aeotec thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 44804825885f1b..ea75f10328c2ce 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -31,6 +31,7 @@ from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_FLOOR_THERMOSTAT_ENTITY, + CLIMATE_MAIN_HEAT_ACTIONNER, CLIMATE_RADIO_THERMOSTAT_ENTITY, ) @@ -488,3 +489,19 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + +async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): + """Test a climate entity from a HRT4-ZW / SRT321 thermostat device. + + This device currently has no setpoint values. + """ + state = hass.states.get(CLIMATE_MAIN_HEAT_ACTIONNER) + + assert state + assert state.state == HVAC_MODE_OFF + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + ] + assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None diff --git a/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json b/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json new file mode 100644 index 00000000000000..a2fdaa995614bd --- /dev/null +++ b/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json @@ -0,0 +1,262 @@ +{ + "nodeId": 20, + "index": 0, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 0, + "label": "Unused" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 3, + "isBeaming": true, + "manufacturerId": 89, + "productId": 1, + "productType": 3, + "firmwareVersion": "2.0", + "name": "main_heat_actionner", + "location": "kitchen", + "deviceConfig": { + "filename": "/opt/node_modules/@zwave-js/config/config/devices/0x0059/asr-zw.json", + "manufacturerId": 89, + "manufacturer": "Secure Meters (UK) Ltd.", + "label": "SRT322", + "description": "Thermostat Receiver", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0001" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + } + }, + "label": "SRT322", + "neighbors": [ + 1, + 5, + 10, + 12, + 13, + 14, + 15, + 18, + 21 + ], + "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 37, + "name": "Binary Switch", + "version": 1, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + } + ], + "endpoints": [ + { + "nodeId": 20, + "index": 0 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "states": { + "0": "Off", + "1": "Heat" + }, + "label": "Thermostat mode" + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 89 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "2.78" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "2.0" + ] + } + ] + } From 541e1663175ce2725b9e10698659388389ec7721 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Thu, 4 Mar 2021 22:12:04 +0100 Subject: [PATCH 1002/1818] Fix measurement unit (Closes: #47390) (#47398) --- homeassistant/components/xiaomi_miio/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 52cd4fdca5e575..d6b87000f40c59 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -21,7 +21,6 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, - LIGHT_LUX, PERCENTAGE, PRESSURE_HPA, TEMP_CELSIUS, @@ -37,6 +36,7 @@ DEFAULT_NAME = "Xiaomi Miio Sensor" DATA_KEY = "sensor.xiaomi_miio" +UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -301,7 +301,7 @@ def available(self): @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return LIGHT_LUX + return UNIT_LUMEN @property def device_class(self): From 972baa2ce4ac553ee01ff7704c1aa87bb91c6775 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:15:27 -0500 Subject: [PATCH 1003/1818] Don't convert Climacell forecast temperatures to celsius because platform does it automatically (#47406) --- homeassistant/components/climacell/weather.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index c77bbfbd50a305..1f0fe76cff63f9 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -22,7 +22,6 @@ LENGTH_MILES, PRESSURE_HPA, PRESSURE_INHG, - TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity @@ -31,7 +30,6 @@ from homeassistant.util import dt as dt_util from homeassistant.util.distance import convert as distance_convert from homeassistant.util.pressure import convert as pressure_convert -from homeassistant.util.temperature import convert as temp_convert from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity from .const import ( @@ -102,10 +100,6 @@ def _forecast_dict( precipitation = ( distance_convert(precipitation / 12, LENGTH_FEET, LENGTH_METERS) * 1000 ) - if temp: - temp = temp_convert(temp, TEMP_FAHRENHEIT, TEMP_CELSIUS) - if temp_low: - temp_low = temp_convert(temp_low, TEMP_FAHRENHEIT, TEMP_CELSIUS) if wind_speed: wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) From fa8ded5ad82039e9a17d9df1affd7da78345018b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 4 Mar 2021 14:20:08 -0700 Subject: [PATCH 1004/1818] Fix AirVisual exception when config entry contains old integration type (#47405) --- .../components/airvisual/__init__.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index c04f56f6b09d81..e900bfa65de930 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -39,6 +39,7 @@ DATA_COORDINATOR, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_NODE_PRO, LOGGER, ) @@ -142,12 +143,21 @@ def _standardize_geography_config_entry(hass, config_entry): if not config_entry.options: # If the config entry doesn't already have any options set, set defaults: entry_updates["options"] = {CONF_SHOW_ON_MAP: True} - if CONF_INTEGRATION_TYPE not in config_entry.data: - # If the config entry data doesn't contain the integration type, add it: - entry_updates["data"] = { - **config_entry.data, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, - } + if config_entry.data.get(CONF_INTEGRATION_TYPE) not in [ + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + ]: + # If the config entry data doesn't contain an integration type that we know + # about, infer it from the data we have: + entry_updates["data"] = {**config_entry.data} + if CONF_CITY in config_entry.data: + entry_updates["data"][ + CONF_INTEGRATION_TYPE + ] = INTEGRATION_TYPE_GEOGRAPHY_NAME + else: + entry_updates["data"][ + CONF_INTEGRATION_TYPE + ] = INTEGRATION_TYPE_GEOGRAPHY_COORDS if not entry_updates: return From 74746125ce03e24acd541b9582086d6a8b6024a9 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:21:56 -0500 Subject: [PATCH 1005/1818] Fix Climacell timezone issue with daily forecasts (#47402) --- homeassistant/components/climacell/weather.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index 1f0fe76cff63f9..e5a24197d6bad1 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -254,6 +254,7 @@ def forecast(self): if self.forecast_type == DAILY: use_datetime = False + forecast_dt = dt_util.start_of_local_day(forecast_dt) precipitation = self._get_cc_value( forecast, CC_ATTR_PRECIPITATION_DAILY ) From 6f7179dce9e6c47b7fcabf9b53160e1ec55573f4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 22:27:59 +0100 Subject: [PATCH 1006/1818] Fix older Roborock models (#47412) --- homeassistant/components/xiaomi_miio/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index ed34192eae3a04..da4229869009c7 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -70,7 +70,7 @@ "chuangmi.plug.hmi206", ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT -MODELS_VACUUM = ["roborock.vacuum"] +MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY From 7ed80d6c3903787c95410cf05365a9068b9f9e91 Mon Sep 17 00:00:00 2001 From: Petru Paler Date: Thu, 4 Mar 2021 21:29:19 +0000 Subject: [PATCH 1007/1818] Update Solax library to 0.2.6 (#47384) --- homeassistant/components/solax/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/solax/manifest.json b/homeassistant/components/solax/manifest.json index 232715ebe18e7b..90bfd8e61840d6 100644 --- a/homeassistant/components/solax/manifest.json +++ b/homeassistant/components/solax/manifest.json @@ -2,6 +2,6 @@ "domain": "solax", "name": "SolaX Power", "documentation": "https://www.home-assistant.io/integrations/solax", - "requirements": ["solax==0.2.5"], + "requirements": ["solax==0.2.6"], "codeowners": ["@squishykid"] } diff --git a/requirements_all.txt b/requirements_all.txt index c3914ec2c25e6f..465aaadd454a53 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2088,7 +2088,7 @@ solaredge-local==0.2.0 solaredge==0.0.2 # homeassistant.components.solax -solax==0.2.5 +solax==0.2.6 # homeassistant.components.honeywell somecomfort==0.5.2 From 682943511a41a5f7d3ca56bdc01bcaad36e70883 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 23:14:24 +0100 Subject: [PATCH 1008/1818] Make zwave_js add-on manager more flexible (#47356) --- homeassistant/components/zwave_js/__init__.py | 10 ++- homeassistant/components/zwave_js/addon.py | 66 ++++++++++++---- .../components/zwave_js/config_flow.py | 17 ++-- tests/components/zwave_js/test_config_flow.py | 77 +++++++------------ 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 0c5a55c25fb2be..637ea0fe08ac8c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -483,11 +483,15 @@ async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> network_key: str = entry.data[CONF_NETWORK_KEY] if not addon_is_installed: - addon_manager.async_schedule_install_addon(usb_path, network_key) + addon_manager.async_schedule_install_setup_addon( + usb_path, network_key, catch_error=True + ) raise ConfigEntryNotReady if not addon_is_running: - addon_manager.async_schedule_setup_addon(usb_path, network_key) + addon_manager.async_schedule_setup_addon( + usb_path, network_key, catch_error=True + ) raise ConfigEntryNotReady @@ -497,4 +501,4 @@ def async_ensure_addon_updated(hass: HomeAssistant) -> None: addon_manager: AddonManager = get_addon_manager(hass) if addon_manager.task_in_progress(): raise ConfigEntryNotReady - addon_manager.async_schedule_update_addon() + addon_manager.async_schedule_update_addon(catch_error=True) diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index 54169dcaf94701..b8d020cfb00c7e 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -67,8 +67,8 @@ def __init__(self, hass: HomeAssistant) -> None: """Set up the add-on manager.""" self._hass = hass self._install_task: Optional[asyncio.Task] = None + self._start_task: Optional[asyncio.Task] = None self._update_task: Optional[asyncio.Task] = None - self._setup_task: Optional[asyncio.Task] = None def task_in_progress(self) -> bool: """Return True if any of the add-on tasks are in progress.""" @@ -76,7 +76,7 @@ def task_in_progress(self) -> bool: task and not task.done() for task in ( self._install_task, - self._setup_task, + self._start_task, self._update_task, ) ) @@ -125,8 +125,21 @@ async def async_install_addon(self) -> None: await async_install_addon(self._hass, ADDON_SLUG) @callback - def async_schedule_install_addon( - self, usb_path: str, network_key: str + def async_schedule_install_addon(self, catch_error: bool = False) -> asyncio.Task: + """Schedule a task that installs the Z-Wave JS add-on. + + Only schedule a new install task if the there's no running task. + """ + if not self._install_task or self._install_task.done(): + LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") + self._install_task = self._async_schedule_addon_operation( + self.async_install_addon, catch_error=catch_error + ) + return self._install_task + + @callback + def async_schedule_install_setup_addon( + self, usb_path: str, network_key: str, catch_error: bool = False ) -> asyncio.Task: """Schedule a task that installs and sets up the Z-Wave JS add-on. @@ -136,7 +149,9 @@ def async_schedule_install_addon( LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") self._install_task = self._async_schedule_addon_operation( self.async_install_addon, - partial(self.async_setup_addon, usb_path, network_key), + partial(self.async_configure_addon, usb_path, network_key), + self.async_start_addon, + catch_error=catch_error, ) return self._install_task @@ -161,7 +176,7 @@ async def async_update_addon(self) -> None: await async_update_addon(self._hass, ADDON_SLUG) @callback - def async_schedule_update_addon(self) -> asyncio.Task: + def async_schedule_update_addon(self, catch_error: bool = False) -> asyncio.Task: """Schedule a task that updates and sets up the Z-Wave JS add-on. Only schedule a new update task if the there's no running task. @@ -169,7 +184,9 @@ def async_schedule_update_addon(self) -> asyncio.Task: if not self._update_task or self._update_task.done(): LOGGER.info("Trying to update the Z-Wave JS add-on") self._update_task = self._async_schedule_addon_operation( - self.async_create_snapshot, self.async_update_addon + self.async_create_snapshot, + self.async_update_addon, + catch_error=catch_error, ) return self._update_task @@ -178,12 +195,25 @@ async def async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" await async_start_addon(self._hass, ADDON_SLUG) + @callback + def async_schedule_start_addon(self, catch_error: bool = False) -> asyncio.Task: + """Schedule a task that starts the Z-Wave JS add-on. + + Only schedule a new start task if the there's no running task. + """ + if not self._start_task or self._start_task.done(): + LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") + self._start_task = self._async_schedule_addon_operation( + self.async_start_addon, catch_error=catch_error + ) + return self._start_task + @api_error("Failed to stop the Z-Wave JS add-on") async def async_stop_addon(self) -> None: """Stop the Z-Wave JS add-on.""" await async_stop_addon(self._hass, ADDON_SLUG) - async def async_setup_addon(self, usb_path: str, network_key: str) -> None: + async def async_configure_addon(self, usb_path: str, network_key: str) -> None: """Configure and start Z-Wave JS add-on.""" addon_options = await self.async_get_addon_options() @@ -195,22 +225,22 @@ async def async_setup_addon(self, usb_path: str, network_key: str) -> None: if new_addon_options != addon_options: await self.async_set_addon_options(new_addon_options) - await self.async_start_addon() - @callback def async_schedule_setup_addon( - self, usb_path: str, network_key: str + self, usb_path: str, network_key: str, catch_error: bool = False ) -> asyncio.Task: """Schedule a task that configures and starts the Z-Wave JS add-on. Only schedule a new setup task if the there's no running task. """ - if not self._setup_task or self._setup_task.done(): + if not self._start_task or self._start_task.done(): LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") - self._setup_task = self._async_schedule_addon_operation( - partial(self.async_setup_addon, usb_path, network_key) + self._start_task = self._async_schedule_addon_operation( + partial(self.async_configure_addon, usb_path, network_key), + self.async_start_addon, + catch_error=catch_error, ) - return self._setup_task + return self._start_task @api_error("Failed to create a snapshot of the Z-Wave JS add-on.") async def async_create_snapshot(self) -> None: @@ -227,7 +257,9 @@ async def async_create_snapshot(self) -> None: ) @callback - def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task: + def _async_schedule_addon_operation( + self, *funcs: Callable, catch_error: bool = False + ) -> asyncio.Task: """Schedule an add-on task.""" async def addon_operation() -> None: @@ -236,6 +268,8 @@ async def addon_operation() -> None: try: await func() except AddonError as err: + if not catch_error: + raise LOGGER.error(err) break diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index ac466223fb6e92..4929f7e78691d0 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -180,11 +180,6 @@ async def async_step_on_supervisor( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle logic when on Supervisor host.""" - # Only one entry with Supervisor add-on support is allowed. - for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data.get(CONF_USE_ADDON): - return await self.async_step_manual() - if user_input is None: return self.async_show_form( step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA @@ -297,7 +292,7 @@ async def _async_start_addon(self) -> None: assert self.hass addon_manager: AddonManager = get_addon_manager(self.hass) try: - await addon_manager.async_start_addon() + await addon_manager.async_schedule_start_addon() # Sleep some seconds to let the add-on start properly before connecting. for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): await asyncio.sleep(ADDON_SETUP_TIMEOUT) @@ -346,7 +341,13 @@ async def async_step_finish_addon_setup( version_info.home_id, raise_on_progress=False ) - self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured( + updates={ + CONF_URL: self.ws_address, + CONF_USB_PATH: self.usb_path, + CONF_NETWORK_KEY: self.network_key, + } + ) return self._async_create_entry_from_vars() async def _async_get_addon_info(self) -> dict: @@ -389,7 +390,7 @@ async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" addon_manager: AddonManager = get_addon_manager(self.hass) try: - await addon_manager.async_install_addon() + await addon_manager.async_schedule_install_addon() finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index c6b59e0741239c..7eea126e52ef06 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -519,49 +519,6 @@ async def test_not_addon(hass, supervisor): assert len(mock_setup_entry.mock_calls) == 1 -async def test_addon_already_configured(hass, supervisor): - """Test add-on already configured leads to manual step.""" - entry = MockConfigEntry( - domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678 - ) - entry.add_to_hass(hass) - - await setup.async_setup_component(hass, "persistent_notification", {}) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "manual" - - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "url": "ws://localhost:3000", - }, - ) - await hass.async_block_till_done() - - assert result["type"] == "create_entry" - assert result["title"] == TITLE - assert result["data"] == { - "url": "ws://localhost:3000", - "usb_path": None, - "network_key": None, - "use_addon": False, - "integration_created_addon": False, - } - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 2 - - @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running( hass, @@ -673,9 +630,18 @@ async def test_addon_running_already_configured( hass, supervisor, addon_running, addon_options, get_addon_discovery_info ): """Test that only one unique instance is allowed when add-on is running.""" - addon_options["device"] = "/test" - addon_options["network_key"] = "abc123" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + addon_options["device"] = "/test_new" + addon_options["network_key"] = "def456" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "usb_path": "/test", + "network_key": "abc123", + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -692,6 +658,9 @@ async def test_addon_running_already_configured( assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["usb_path"] == "/test_new" + assert entry.data["network_key"] == "def456" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) @@ -897,7 +866,16 @@ async def test_addon_installed_already_configured( get_addon_discovery_info, ): """Test that only one unique instance is allowed when add-on is installed.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "usb_path": "/test", + "network_key": "abc123", + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -916,7 +894,7 @@ async def test_addon_installed_already_configured( assert result["step_id"] == "configure_addon" result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + result["flow_id"], {"usb_path": "/test_new", "network_key": "def456"} ) assert result["type"] == "progress" @@ -927,6 +905,9 @@ async def test_addon_installed_already_configured( assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["usb_path"] == "/test_new" + assert entry.data["network_key"] == "def456" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) From c3bddc0fa6d724c09f277c4c829dbc482d736dd1 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 4 Mar 2021 23:35:39 +0100 Subject: [PATCH 1009/1818] Update browse_media.py (#47414) --- homeassistant/components/xbox/browse_media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index a91713931c2243..2aebb07df1bddb 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -169,7 +169,7 @@ def item_payload(item: InstalledPackage, images: Dict[str, List[Image]]): ) -def _find_media_image(images=List[Image]) -> Optional[Image]: +def _find_media_image(images: List[Image]) -> Optional[Image]: purpose_order = ["Poster", "Tile", "Logo", "BoxArt"] for purpose in purpose_order: for image in images: From 35d5522e79d4ab5ed5ce0f02fe6cc732da309820 Mon Sep 17 00:00:00 2001 From: Cooper Dale Date: Fri, 5 Mar 2021 00:58:42 +0100 Subject: [PATCH 1010/1818] Fix typo in docs link for forked_daapd (#47413) corrected link to existing site --- homeassistant/components/forked_daapd/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/forked_daapd/manifest.json b/homeassistant/components/forked_daapd/manifest.json index 15f043dbbfed0f..b9f78875a2d65a 100644 --- a/homeassistant/components/forked_daapd/manifest.json +++ b/homeassistant/components/forked_daapd/manifest.json @@ -1,7 +1,7 @@ { "domain": "forked_daapd", "name": "forked-daapd", - "documentation": "https://www.home-assistant.io/integrations/forked-daapd", + "documentation": "https://www.home-assistant.io/integrations/forked_daapd", "codeowners": ["@uvjustin"], "requirements": ["pyforked-daapd==0.1.11", "pylibrespot-java==0.1.0"], "config_flow": true, From a1faba29f08eb98460b446cb74ed13edf7b417d9 Mon Sep 17 00:00:00 2001 From: Christophe Painchaud Date: Fri, 5 Mar 2021 01:09:54 +0100 Subject: [PATCH 1011/1818] Fix RFLink TCP KeepAlive error log (#47395) --- homeassistant/components/rflink/__init__.py | 41 +++++++++++---------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 3cff3beed3ce72..68783c3426af3d 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -203,25 +203,28 @@ def event_callback(event): # TCP port when host configured, otherwise serial port port = config[DOMAIN][CONF_PORT] - # TCP KEEPALIVE will be enabled if value > 0 - keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] - if keepalive_idle_timer < 0: - _LOGGER.error( - "A bogus TCP Keepalive IDLE timer was provided (%d secs), " - "default value will be used. " - "Recommended values: 60-3600 (seconds)", - keepalive_idle_timer, - ) - keepalive_idle_timer = DEFAULT_TCP_KEEPALIVE_IDLE_TIMER - elif keepalive_idle_timer == 0: - keepalive_idle_timer = None - elif keepalive_idle_timer <= 30: - _LOGGER.warning( - "A very short TCP Keepalive IDLE timer was provided (%d secs), " - "and may produce unexpected disconnections from RFlink device." - " Recommended values: 60-3600 (seconds)", - keepalive_idle_timer, - ) + keepalive_idle_timer = None + # TCP KeepAlive only if this is TCP based connection (not serial) + if host is not None: + # TCP KEEPALIVE will be enabled if value > 0 + keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] + if keepalive_idle_timer < 0: + _LOGGER.error( + "A bogus TCP Keepalive IDLE timer was provided (%d secs), " + "it will be disabled. " + "Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) + keepalive_idle_timer = None + elif keepalive_idle_timer == 0: + keepalive_idle_timer = None + elif keepalive_idle_timer <= 30: + _LOGGER.warning( + "A very short TCP Keepalive IDLE timer was provided (%d secs) " + "and may produce unexpected disconnections from RFlink device." + " Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) @callback def reconnect(exc=None): From ee69e93b46d91b57eb5466a087ae1ad78b5fb1c7 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:15:50 -0500 Subject: [PATCH 1012/1818] Bump zwave-js-server-python to 0.21.0 (#47408) Co-authored-by: Tobias Sauerwein --- homeassistant/components/zwave_js/__init__.py | 74 +-------- homeassistant/components/zwave_js/climate.py | 8 - homeassistant/components/zwave_js/entity.py | 3 - homeassistant/components/zwave_js/helpers.py | 11 -- homeassistant/components/zwave_js/light.py | 8 - .../components/zwave_js/manifest.json | 2 +- homeassistant/components/zwave_js/migrate.py | 113 +++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_api.py | 2 +- tests/components/zwave_js/test_climate.py | 6 +- tests/components/zwave_js/test_init.py | 148 +++++++++++++++++- .../zwave_js/climate_danfoss_lc_13_state.json | 90 +++++++++-- .../zwave_js/climate_heatit_z_trm3_state.json | 1 + 14 files changed, 352 insertions(+), 118 deletions(-) create mode 100644 homeassistant/components/zwave_js/migrate.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 637ea0fe08ac8c..519771c1a49212 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -48,7 +48,8 @@ ZWAVE_JS_EVENT, ) from .discovery import async_discover_values -from .helpers import get_device_id, get_old_value_id, get_unique_id +from .helpers import get_device_id +from .migrate import async_migrate_discovered_value from .services import ZWaveServices CONNECT_TIMEOUT = 10 @@ -98,31 +99,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) - @callback - def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> None: - """Check if entity with old unique ID exists, and if so migrate it to new ID.""" - if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): - LOGGER.debug( - "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", - entity_id, - old_unique_id, - new_unique_id, - ) - try: - ent_reg.async_update_entity( - entity_id, - new_unique_id=new_unique_id, - ) - except ValueError: - LOGGER.debug( - ( - "Entity %s can't be migrated because the unique ID is taken. " - "Cleaning it up since it is likely no longer valid." - ), - entity_id, - ) - ent_reg.async_remove(entity_id) - @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -136,49 +112,9 @@ def async_on_node_ready(node: ZwaveNode) -> None: LOGGER.debug("Discovered entity: %s", disc_info) # This migration logic was added in 2021.3 to handle a breaking change to - # the value_id format. Some time in the future, this code block - # (as well as get_old_value_id helper and migrate_entity closure) can be - # removed. - value_ids = [ - # 2021.2.* format - get_old_value_id(disc_info.primary_value), - # 2021.3.0b0 format - disc_info.primary_value.value_id, - ] - - new_unique_id = get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ) - - for value_id in value_ids: - old_unique_id = get_unique_id( - client.driver.controller.home_id, - f"{disc_info.primary_value.node.node_id}.{value_id}", - ) - # Most entities have the same ID format, but notification binary sensors - # have a state key in their ID so we need to handle them differently - if ( - disc_info.platform == "binary_sensor" - and disc_info.platform_hint == "notification" - ): - for state_key in disc_info.primary_value.metadata.states: - # ignore idle key (0) - if state_key == "0": - continue - - migrate_entity( - disc_info.platform, - f"{old_unique_id}.{state_key}", - f"{new_unique_id}.{state_key}", - ) - - # Once we've iterated through all state keys, we can move on to the - # next item - continue - - migrate_entity(disc_info.platform, old_unique_id, new_unique_id) - + # the value_id format. Some time in the future, this call (as well as the + # helper functions) can be removed. + async_migrate_discovered_value(ent_reg, client, disc_info) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index cc449b89e91a06..325cf14b379df4 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -125,18 +125,10 @@ def __init__( ) self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: - # Some devices don't include a property key so we need to check for value - # ID's, both with and without the property key self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, command_class=CommandClass.THERMOSTAT_SETPOINT, value_property_key=enum.value.key, - value_property_key_name=enum.value.name, - add_to_watched_value_ids=True, - ) or self.get_zwave_value( - THERMOSTAT_SETPOINT_PROPERTY, - command_class=CommandClass.THERMOSTAT_SETPOINT, - value_property_key_name=enum.value.name, add_to_watched_value_ids=True, ) # Use the first found setpoint value to always determine the temperature unit diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index d0ed9eb5291309..c061abc4d0ddfe 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -169,7 +169,6 @@ def get_zwave_value( command_class: Optional[int] = None, endpoint: Optional[int] = None, value_property_key: Optional[int] = None, - value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: @@ -188,7 +187,6 @@ def get_zwave_value( value_property, endpoint=endpoint, property_key=value_property_key, - property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) @@ -203,7 +201,6 @@ def get_zwave_value( value_property, endpoint=endpoint_.index, property_key=value_property_key, - property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) if return_value: diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 9582b7ee054f01..16baeb816c2d0b 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -3,7 +3,6 @@ from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode -from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -13,16 +12,6 @@ from .const import DATA_CLIENT, DOMAIN -@callback -def get_old_value_id(value: ZwaveValue) -> str: - """Get old value ID so we can migrate entity unique ID.""" - command_class = value.command_class - endpoint = value.endpoint or "00" - property_ = value.property_ - property_key_name = value.property_key_name or "00" - return f"{value.node.node_id}-{command_class}-{endpoint}-{property_}-{property_key_name}" - - @callback def get_unique_id(home_id: str, value_id: str) -> str: """Get unique ID from home ID and value ID.""" diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index d9c31210beab67..b501ecb58e7fab 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -228,7 +228,6 @@ async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: "targetColor", CommandClass.SWITCH_COLOR, value_property_key=None, - value_property_key_name=None, ) if combined_color_val and isinstance(combined_color_val.value, dict): colors_dict = {} @@ -252,7 +251,6 @@ async def _async_set_color(self, color: ColorComponent, new_value: int) -> None: "targetColor", CommandClass.SWITCH_COLOR, value_property_key=property_key.key, - value_property_key_name=property_key.name, ) if target_zwave_value is None: # guard for unsupported color @@ -318,31 +316,26 @@ def _calculate_color_values(self) -> None: "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.RED.value.key, - value_property_key_name=ColorComponent.RED.value.name, ) green_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.GREEN.value.key, - value_property_key_name=ColorComponent.GREEN.value.name, ) blue_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.BLUE.value.key, - value_property_key_name=ColorComponent.BLUE.value.name, ) ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.WARM_WHITE.value.key, - value_property_key_name=ColorComponent.WARM_WHITE.value.name, ) cw_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.COLD_WHITE.value.key, - value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) # prefer the (new) combined color property # https://github.com/zwave-js/node-zwave-js/pull/1782 @@ -350,7 +343,6 @@ def _calculate_color_values(self) -> None: "currentColor", CommandClass.SWITCH_COLOR, value_property_key=None, - value_property_key_name=None, ) if combined_color_val and isinstance(combined_color_val.value, dict): multi_color = combined_color_val.value diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index c812515a179cce..2a6f036fa809ae 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.20.1"], + "requirements": ["zwave-js-server-python==0.21.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py new file mode 100644 index 00000000000000..49c18073de5897 --- /dev/null +++ b/homeassistant/components/zwave_js/migrate.py @@ -0,0 +1,113 @@ +"""Functions used to migrate unique IDs for Z-Wave JS entities.""" +import logging +from typing import List + +from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.value import Value as ZwaveValue + +from homeassistant.core import callback +from homeassistant.helpers.entity_registry import EntityRegistry + +from .const import DOMAIN +from .discovery import ZwaveDiscoveryInfo +from .helpers import get_unique_id + +_LOGGER = logging.getLogger(__name__) + + +@callback +def async_migrate_entity( + ent_reg: EntityRegistry, platform: str, old_unique_id: str, new_unique_id: str +) -> None: + """Check if entity with old unique ID exists, and if so migrate it to new ID.""" + if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): + _LOGGER.debug( + "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", + entity_id, + old_unique_id, + new_unique_id, + ) + try: + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + except ValueError: + _LOGGER.debug( + ( + "Entity %s can't be migrated because the unique ID is taken. " + "Cleaning it up since it is likely no longer valid." + ), + entity_id, + ) + ent_reg.async_remove(entity_id) + + +@callback +def async_migrate_discovered_value( + ent_reg: EntityRegistry, client: ZwaveClient, disc_info: ZwaveDiscoveryInfo +) -> 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, + ) + + # 2021.2.*, 2021.3.0b0, and 2021.3.0 formats + for value_id in get_old_value_ids(disc_info.primary_value): + old_unique_id = get_unique_id( + client.driver.controller.home_id, + value_id, + ) + # Most entities have the same ID format, but notification binary sensors + # have a state key in their ID so we need to handle them differently + if ( + disc_info.platform == "binary_sensor" + and disc_info.platform_hint == "notification" + ): + for state_key in disc_info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + + async_migrate_entity( + ent_reg, + disc_info.platform, + f"{old_unique_id}.{state_key}", + f"{new_unique_id}.{state_key}", + ) + + # Once we've iterated through all state keys, we can move on to the + # next item + continue + + async_migrate_entity(ent_reg, disc_info.platform, old_unique_id, new_unique_id) + + +@callback +def get_old_value_ids(value: ZwaveValue) -> List[str]: + """Get old value IDs so we can migrate entity unique ID.""" + value_ids = [] + + # Pre 2021.3.0 value ID + command_class = value.command_class + endpoint = value.endpoint or "00" + property_ = value.property_ + property_key_name = value.property_key_name or "00" + value_ids.append( + f"{value.node.node_id}.{value.node.node_id}-{command_class}-{endpoint}-" + f"{property_}-{property_key_name}" + ) + + endpoint = "00" if value.endpoint is None else value.endpoint + property_key = "00" if value.property_key is None else value.property_key + property_key_name = value.property_key_name or "00" + + value_id = ( + f"{value.node.node_id}-{command_class}-{endpoint}-" + f"{property_}-{property_key}-{property_key_name}" + ) + # 2021.3.0b0 and 2021.3.0 value IDs + value_ids.extend([f"{value.node.node_id}.{value_id}", value_id]) + + return value_ids diff --git a/requirements_all.txt b/requirements_all.txt index 465aaadd454a53..99854ac3d9fc77 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2394,4 +2394,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.1 +zwave-js-server-python==0.21.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 06fb55c9f0bbb8..4517457935ba85 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1231,4 +1231,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.1 +zwave-js-server-python==0.21.0 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 9760e1a06b7038..78bac5518a02cf 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -71,7 +71,7 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): result = msg["result"] assert len(result) == 61 - key = "52-112-0-2-00-00" + key = "52-112-0-2" assert result[key]["property"] == 2 assert result[key]["metadata"]["type"] == "number" assert result[key]["configuration_value_type"] == "enumerated" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index ea75f10328c2ce..fe3e0708acc409 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -405,7 +405,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state assert state.state == HVAC_MODE_HEAT - assert state.attributes[ATTR_TEMPERATURE] == 25 + assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE @@ -432,6 +432,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClassName": "Thermostat Setpoint", "property": "setpoint", "propertyName": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "ccVersion": 2, "metadata": { @@ -441,7 +442,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "unit": "\u00b0C", "ccSpecific": {"setpointType": 1}, }, - "value": 25, + "value": 14, } assert args["value"] == 21.5 @@ -459,6 +460,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClass": 67, "endpoint": 0, "property": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "propertyName": "setpoint", "newValue": 23, diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 6f60bbc0300490..cd2017f2c669f4 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -160,7 +160,7 @@ async def test_unique_id_migration_dupes( # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature" assert entity_entry.unique_id == new_unique_id assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None @@ -195,7 +195,7 @@ async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integra # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature" assert entity_entry.unique_id == new_unique_id @@ -228,7 +228,147 @@ async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integra # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_v3(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 3).""" + ent_reg = entity_registry.async_get(hass) + # Migrate version 2 + ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" + entity_name = ILLUMINANCE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == ILLUMINANCE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v1( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 1).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.32.32-50-00-value-W_Consumed" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + 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"{client.driver.controller.home_id}.32-50-0-value-66049" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v2( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 2).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = ( + f"{client.driver.controller.home_id}.32.32-50-0-value-66049-W_Consumed" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + 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"{client.driver.controller.home_id}.32-50-0-value-66049" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v3( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 3).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049-W_Consumed" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + 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"{client.driver.controller.home_id}.32-50-0-value-66049" assert entity_entry.unique_id == new_unique_id @@ -262,7 +402,7 @@ async def test_unique_id_migration_notification_binary_sensor( # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8" + new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8" assert entity_entry.unique_id == new_unique_id diff --git a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json index 90410998597146..8574674714f073 100644 --- a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json +++ b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json @@ -4,11 +4,25 @@ "status": 1, "ready": true, "deviceClass": { - "basic": {"key": 4, "label":"Routing Slave"}, - "generic": {"key": 8, "label":"Thermostat"}, - "specific": {"key": 4, "label":"Setpoint Thermostat"}, - "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 4, + "label": "Setpoint Thermostat" + }, + "mandatorySupportedCCs": [ + 114, + 143, + 67, + 134 + ], + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": false, @@ -22,6 +36,7 @@ "productType": 5, "firmwareVersion": "1.1", "deviceConfig": { + "filename": "/usr/src/app/node_modules/@zwave-js/config/config/devices/0x0002/lc-13.json", "manufacturerId": 2, "manufacturer": "Danfoss", "label": "LC-13", @@ -66,19 +81,76 @@ 14 ], "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 70, + "name": "Climate Control Schedule", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 117, + "name": "Protection", + "version": 2, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 132, + "name": "Wake Up", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + }, + { + "id": 143, + "name": "Multi Command", + "version": 1, + "isSecure": false + } + ], "endpoints": [ { "nodeId": 5, "index": 0 } ], - "commandClasses": [], "values": [ { "endpoint": 0, "commandClass": 67, "commandClassName": "Thermostat Setpoint", "property": "setpoint", + "propertyKey": 1, "propertyName": "setpoint", "propertyKeyName": "Heating", "ccVersion": 2, @@ -91,7 +163,7 @@ "setpointType": 1 } }, - "value": 25 + "value": 14 }, { "endpoint": 0, @@ -262,7 +334,7 @@ "unit": "%", "label": "Battery level" }, - "value": 53 + "value": 49 }, { "endpoint": 0, @@ -361,4 +433,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json index 0dc040c6cb24b9..b26b69be9ad5e4 100644 --- a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json +++ b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json @@ -837,6 +837,7 @@ "commandClassName": "Thermostat Setpoint", "property": "setpoint", "propertyName": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "ccVersion": 3, "metadata": { From c2f7a38d09152577ebc61462b0e51ba8d04be963 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 4 Mar 2021 21:53:09 +0100 Subject: [PATCH 1013/1818] Fix Xiaomi Miio setup of switch entity for lumi.acpartner.v3 (#47345) --- .../components/xiaomi_miio/__init__.py | 2 +- homeassistant/components/xiaomi_miio/const.py | 1 - homeassistant/components/xiaomi_miio/switch.py | 18 +++++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index a8b32a31576ee5..9c5f72d58776af 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -23,7 +23,7 @@ _LOGGER = logging.getLogger(__name__) -GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "light"] +GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] VACUUM_PLATFORMS = ["vacuum"] diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index d6c39146f6ad0f..4fa575118f8a84 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -21,7 +21,6 @@ "chuangmi.plug.v2", "chuangmi.plug.hmi205", "chuangmi.plug.hmi206", - "lumi.acpartner.v3", ] MODELS_VACUUM = ["roborock.vacuum"] diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 3cc95572e6cc86..e523dfb0bb7f31 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -22,6 +22,7 @@ from .const import ( CONF_DEVICE, CONF_FLOW_TYPE, + CONF_GATEWAY, CONF_MODEL, DOMAIN, SERVICE_SET_POWER_MODE, @@ -129,16 +130,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the switch from a config entry.""" entities = [] - if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id + + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE or ( + config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY + and model == "lumi.acpartner.v3" + ): if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {} - host = config_entry.data[CONF_HOST] - token = config_entry.data[CONF_TOKEN] - name = config_entry.title - model = config_entry.data[CONF_MODEL] - unique_id = config_entry.unique_id - _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]: From d175ac8e0d940614a6b03ea6ea637dee8969bf13 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 23:14:24 +0100 Subject: [PATCH 1014/1818] Make zwave_js add-on manager more flexible (#47356) --- homeassistant/components/zwave_js/__init__.py | 10 ++- homeassistant/components/zwave_js/addon.py | 66 ++++++++++++---- .../components/zwave_js/config_flow.py | 17 ++-- tests/components/zwave_js/test_config_flow.py | 77 +++++++------------ 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index d4e349645cf58b..6bd951fdf5eead 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -483,11 +483,15 @@ async def async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> network_key: str = entry.data[CONF_NETWORK_KEY] if not addon_is_installed: - addon_manager.async_schedule_install_addon(usb_path, network_key) + addon_manager.async_schedule_install_setup_addon( + usb_path, network_key, catch_error=True + ) raise ConfigEntryNotReady if not addon_is_running: - addon_manager.async_schedule_setup_addon(usb_path, network_key) + addon_manager.async_schedule_setup_addon( + usb_path, network_key, catch_error=True + ) raise ConfigEntryNotReady @@ -497,4 +501,4 @@ def async_ensure_addon_updated(hass: HomeAssistant) -> None: addon_manager: AddonManager = get_addon_manager(hass) if addon_manager.task_in_progress(): raise ConfigEntryNotReady - addon_manager.async_schedule_update_addon() + addon_manager.async_schedule_update_addon(catch_error=True) diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index 54169dcaf94701..b8d020cfb00c7e 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -67,8 +67,8 @@ def __init__(self, hass: HomeAssistant) -> None: """Set up the add-on manager.""" self._hass = hass self._install_task: Optional[asyncio.Task] = None + self._start_task: Optional[asyncio.Task] = None self._update_task: Optional[asyncio.Task] = None - self._setup_task: Optional[asyncio.Task] = None def task_in_progress(self) -> bool: """Return True if any of the add-on tasks are in progress.""" @@ -76,7 +76,7 @@ def task_in_progress(self) -> bool: task and not task.done() for task in ( self._install_task, - self._setup_task, + self._start_task, self._update_task, ) ) @@ -125,8 +125,21 @@ async def async_install_addon(self) -> None: await async_install_addon(self._hass, ADDON_SLUG) @callback - def async_schedule_install_addon( - self, usb_path: str, network_key: str + def async_schedule_install_addon(self, catch_error: bool = False) -> asyncio.Task: + """Schedule a task that installs the Z-Wave JS add-on. + + Only schedule a new install task if the there's no running task. + """ + if not self._install_task or self._install_task.done(): + LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") + self._install_task = self._async_schedule_addon_operation( + self.async_install_addon, catch_error=catch_error + ) + return self._install_task + + @callback + def async_schedule_install_setup_addon( + self, usb_path: str, network_key: str, catch_error: bool = False ) -> asyncio.Task: """Schedule a task that installs and sets up the Z-Wave JS add-on. @@ -136,7 +149,9 @@ def async_schedule_install_addon( LOGGER.info("Z-Wave JS add-on is not installed. Installing add-on") self._install_task = self._async_schedule_addon_operation( self.async_install_addon, - partial(self.async_setup_addon, usb_path, network_key), + partial(self.async_configure_addon, usb_path, network_key), + self.async_start_addon, + catch_error=catch_error, ) return self._install_task @@ -161,7 +176,7 @@ async def async_update_addon(self) -> None: await async_update_addon(self._hass, ADDON_SLUG) @callback - def async_schedule_update_addon(self) -> asyncio.Task: + def async_schedule_update_addon(self, catch_error: bool = False) -> asyncio.Task: """Schedule a task that updates and sets up the Z-Wave JS add-on. Only schedule a new update task if the there's no running task. @@ -169,7 +184,9 @@ def async_schedule_update_addon(self) -> asyncio.Task: if not self._update_task or self._update_task.done(): LOGGER.info("Trying to update the Z-Wave JS add-on") self._update_task = self._async_schedule_addon_operation( - self.async_create_snapshot, self.async_update_addon + self.async_create_snapshot, + self.async_update_addon, + catch_error=catch_error, ) return self._update_task @@ -178,12 +195,25 @@ async def async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" await async_start_addon(self._hass, ADDON_SLUG) + @callback + def async_schedule_start_addon(self, catch_error: bool = False) -> asyncio.Task: + """Schedule a task that starts the Z-Wave JS add-on. + + Only schedule a new start task if the there's no running task. + """ + if not self._start_task or self._start_task.done(): + LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") + self._start_task = self._async_schedule_addon_operation( + self.async_start_addon, catch_error=catch_error + ) + return self._start_task + @api_error("Failed to stop the Z-Wave JS add-on") async def async_stop_addon(self) -> None: """Stop the Z-Wave JS add-on.""" await async_stop_addon(self._hass, ADDON_SLUG) - async def async_setup_addon(self, usb_path: str, network_key: str) -> None: + async def async_configure_addon(self, usb_path: str, network_key: str) -> None: """Configure and start Z-Wave JS add-on.""" addon_options = await self.async_get_addon_options() @@ -195,22 +225,22 @@ async def async_setup_addon(self, usb_path: str, network_key: str) -> None: if new_addon_options != addon_options: await self.async_set_addon_options(new_addon_options) - await self.async_start_addon() - @callback def async_schedule_setup_addon( - self, usb_path: str, network_key: str + self, usb_path: str, network_key: str, catch_error: bool = False ) -> asyncio.Task: """Schedule a task that configures and starts the Z-Wave JS add-on. Only schedule a new setup task if the there's no running task. """ - if not self._setup_task or self._setup_task.done(): + if not self._start_task or self._start_task.done(): LOGGER.info("Z-Wave JS add-on is not running. Starting add-on") - self._setup_task = self._async_schedule_addon_operation( - partial(self.async_setup_addon, usb_path, network_key) + self._start_task = self._async_schedule_addon_operation( + partial(self.async_configure_addon, usb_path, network_key), + self.async_start_addon, + catch_error=catch_error, ) - return self._setup_task + return self._start_task @api_error("Failed to create a snapshot of the Z-Wave JS add-on.") async def async_create_snapshot(self) -> None: @@ -227,7 +257,9 @@ async def async_create_snapshot(self) -> None: ) @callback - def _async_schedule_addon_operation(self, *funcs: Callable) -> asyncio.Task: + def _async_schedule_addon_operation( + self, *funcs: Callable, catch_error: bool = False + ) -> asyncio.Task: """Schedule an add-on task.""" async def addon_operation() -> None: @@ -236,6 +268,8 @@ async def addon_operation() -> None: try: await func() except AddonError as err: + if not catch_error: + raise LOGGER.error(err) break diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 37923c574b4371..35fc4417d132de 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -172,11 +172,6 @@ async def async_step_on_supervisor( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle logic when on Supervisor host.""" - # Only one entry with Supervisor add-on support is allowed. - for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data.get(CONF_USE_ADDON): - return await self.async_step_manual() - if user_input is None: return self.async_show_form( step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA @@ -289,7 +284,7 @@ async def _async_start_addon(self) -> None: assert self.hass addon_manager: AddonManager = get_addon_manager(self.hass) try: - await addon_manager.async_start_addon() + await addon_manager.async_schedule_start_addon() # Sleep some seconds to let the add-on start properly before connecting. for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): await asyncio.sleep(ADDON_SETUP_TIMEOUT) @@ -338,7 +333,13 @@ async def async_step_finish_addon_setup( version_info.home_id, raise_on_progress=False ) - self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured( + updates={ + CONF_URL: self.ws_address, + CONF_USB_PATH: self.usb_path, + CONF_NETWORK_KEY: self.network_key, + } + ) return self._async_create_entry_from_vars() async def _async_get_addon_info(self) -> dict: @@ -381,7 +382,7 @@ async def _async_install_addon(self) -> None: """Install the Z-Wave JS add-on.""" addon_manager: AddonManager = get_addon_manager(self.hass) try: - await addon_manager.async_install_addon() + await addon_manager.async_schedule_install_addon() finally: # Continue the flow after show progress when the task is done. self.hass.async_create_task( diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index fc97f7420cf22d..a9aa837aac196f 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -507,49 +507,6 @@ async def test_not_addon(hass, supervisor): assert len(mock_setup_entry.mock_calls) == 1 -async def test_addon_already_configured(hass, supervisor): - """Test add-on already configured leads to manual step.""" - entry = MockConfigEntry( - domain=DOMAIN, data={"use_addon": True}, title=TITLE, unique_id=5678 - ) - entry.add_to_hass(hass) - - await setup.async_setup_component(hass, "persistent_notification", {}) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == "form" - assert result["step_id"] == "manual" - - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "url": "ws://localhost:3000", - }, - ) - await hass.async_block_till_done() - - assert result["type"] == "create_entry" - assert result["title"] == TITLE - assert result["data"] == { - "url": "ws://localhost:3000", - "usb_path": None, - "network_key": None, - "use_addon": False, - "integration_created_addon": False, - } - assert len(mock_setup.mock_calls) == 1 - assert len(mock_setup_entry.mock_calls) == 2 - - @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) async def test_addon_running( hass, @@ -661,9 +618,18 @@ async def test_addon_running_already_configured( hass, supervisor, addon_running, addon_options, get_addon_discovery_info ): """Test that only one unique instance is allowed when add-on is running.""" - addon_options["device"] = "/test" - addon_options["network_key"] = "abc123" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + addon_options["device"] = "/test_new" + addon_options["network_key"] = "def456" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "usb_path": "/test", + "network_key": "abc123", + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -680,6 +646,9 @@ async def test_addon_running_already_configured( assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["usb_path"] == "/test_new" + assert entry.data["network_key"] == "def456" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) @@ -885,7 +854,16 @@ async def test_addon_installed_already_configured( get_addon_discovery_info, ): """Test that only one unique instance is allowed when add-on is installed.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "usb_path": "/test", + "network_key": "abc123", + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -904,7 +882,7 @@ async def test_addon_installed_already_configured( assert result["step_id"] == "configure_addon" result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"usb_path": "/test", "network_key": "abc123"} + result["flow_id"], {"usb_path": "/test_new", "network_key": "def456"} ) assert result["type"] == "progress" @@ -915,6 +893,9 @@ async def test_addon_installed_already_configured( assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://host1:3001" + assert entry.data["usb_path"] == "/test_new" + assert entry.data["network_key"] == "def456" @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) From 33c35661063e49601904847df3794bf84f3f511c Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Thu, 4 Mar 2021 21:47:24 +0100 Subject: [PATCH 1015/1818] Catch ConditionError in generic_thermostat climate (#47359) --- .../components/generic_thermostat/climate.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 5fbdf49914614d..7062267de19751 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -35,6 +35,7 @@ STATE_UNKNOWN, ) from homeassistant.core import DOMAIN as HA_DOMAIN, CoreState, callback +from homeassistant.exceptions import ConditionError from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import ( @@ -439,12 +440,16 @@ async def _async_control_heating(self, time=None, force=False): current_state = STATE_ON else: current_state = HVAC_MODE_OFF - long_enough = condition.state( - self.hass, - self.heater_entity_id, - current_state, - self.min_cycle_duration, - ) + try: + long_enough = condition.state( + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) + except ConditionError: + long_enough = False + if not long_enough: return From c0840e22dcaf603ab470dea2bcaec4a1e25b3e63 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 22:11:07 +0100 Subject: [PATCH 1016/1818] Fix zwave_js manual reconfiguration of add-on managed entry (#47364) --- homeassistant/components/zwave_js/config_flow.py | 10 +++++++++- tests/components/zwave_js/test_config_flow.py | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 35fc4417d132de..4929f7e78691d0 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -117,7 +117,15 @@ async def async_step_manual( await self.async_set_unique_id( version_info.home_id, raise_on_progress=False ) - self._abort_if_unique_id_configured(user_input) + # Make sure we disable any add-on handling + # if the controller is reconfigured in a manual step. + self._abort_if_unique_id_configured( + updates={ + **user_input, + CONF_USE_ADDON: False, + CONF_INTEGRATION_CREATED_ADDON: False, + } + ) self.ws_address = user_input[CONF_URL] return self._async_create_entry_from_vars() diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index a9aa837aac196f..7eea126e52ef06 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -184,7 +184,16 @@ async def test_manual_errors( async def test_manual_already_configured(hass): """Test that only one unique instance is allowed.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE, unique_id=1234) + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:3000", + "use_addon": True, + "integration_created_addon": True, + }, + title=TITLE, + unique_id=1234, + ) entry.add_to_hass(hass) await setup.async_setup_component(hass, "persistent_notification", {}) @@ -198,12 +207,15 @@ async def test_manual_already_configured(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], { - "url": "ws://localhost:3000", + "url": "ws://1.1.1.1:3001", }, ) assert result["type"] == "abort" assert result["reason"] == "already_configured" + assert entry.data["url"] == "ws://1.1.1.1:3001" + assert entry.data["use_addon"] is False + assert entry.data["integration_created_addon"] is False @pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) From d83ccdc97ace0f951efe76b30daaefe9d6d25ccb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 22:09:08 +0100 Subject: [PATCH 1017/1818] Don't raise on known non-matching states in numeric state condition (#47378) --- homeassistant/helpers/condition.py | 25 ++++--- .../triggers/test_numeric_state.py | 61 +---------------- tests/helpers/test_condition.py | 68 +++++++++---------- 3 files changed, 48 insertions(+), 106 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 40087650141419..c592681a0150e4 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -265,10 +265,9 @@ def async_numeric_state( "numeric_state", f"template error: {ex}" ) from ex + # Known states that never match the numeric condition if value in (STATE_UNAVAILABLE, STATE_UNKNOWN): - raise ConditionErrorMessage( - "numeric_state", f"state of {entity_id} is unavailable" - ) + return False try: fvalue = float(value) @@ -281,13 +280,15 @@ def async_numeric_state( if below is not None: if isinstance(below, str): below_entity = hass.states.get(below) - if not below_entity or below_entity.state in ( + if not below_entity: + raise ConditionErrorMessage( + "numeric_state", f"unknown 'below' entity {below}" + ) + if below_entity.state in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionErrorMessage( - "numeric_state", f"the 'below' entity {below} is unavailable" - ) + return False try: if fvalue >= float(below_entity.state): return False @@ -302,13 +303,15 @@ def async_numeric_state( if above is not None: if isinstance(above, str): above_entity = hass.states.get(above) - if not above_entity or above_entity.state in ( + if not above_entity: + raise ConditionErrorMessage( + "numeric_state", f"unknown 'above' entity {above}" + ) + if above_entity.state in ( STATE_UNAVAILABLE, STATE_UNKNOWN, ): - raise ConditionErrorMessage( - "numeric_state", f"the 'above' entity {above} is unavailable" - ) + return False try: if fvalue <= float(above_entity.state): return False diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 831e20b78a1dbc..9eb9ac79a941e6 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -10,12 +10,7 @@ from homeassistant.components.homeassistant.triggers import ( numeric_state as numeric_state_trigger, ) -from homeassistant.const import ( - ATTR_ENTITY_ID, - ENTITY_MATCH_ALL, - SERVICE_TURN_OFF, - STATE_UNAVAILABLE, -) +from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -347,52 +342,6 @@ async def test_if_fires_on_entity_unavailable_at_startup(hass, calls): assert len(calls) == 0 -async def test_if_not_fires_on_entity_unavailable(hass, calls): - """Test the firing with entity changing to unavailable.""" - # set initial state - hass.states.async_set("test.entity", 9) - await hass.async_block_till_done() - - assert await async_setup_component( - hass, - automation.DOMAIN, - { - automation.DOMAIN: { - "trigger": { - "platform": "numeric_state", - "entity_id": "test.entity", - "above": 10, - }, - "action": {"service": "test.automation"}, - } - }, - ) - - # 11 is above 10 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 1 - - # Going to unavailable and back should not fire - hass.states.async_set("test.entity", STATE_UNAVAILABLE) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 1 - - # Crossing threshold via unavailable should fire - hass.states.async_set("test.entity", 9) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", STATE_UNAVAILABLE) - await hass.async_block_till_done() - assert len(calls) == 1 - hass.states.async_set("test.entity", 11) - await hass.async_block_till_done() - assert len(calls) == 2 - - @pytest.mark.parametrize("above", (10, "input_number.value_10")) async def test_if_fires_on_entity_change_below_to_above(hass, calls, above): """Test the firing with changed entity.""" @@ -1522,7 +1471,7 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls, above, below) assert len(calls) == 1 -async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): +async def test_if_not_fires_on_error_with_for_template(hass, calls): """Test for not firing on error with for template.""" hass.states.async_set("test.entity", 0) await hass.async_block_till_done() @@ -1547,17 +1496,11 @@ async def test_if_not_fires_on_error_with_for_template(hass, caplog, calls): await hass.async_block_till_done() assert len(calls) == 0 - caplog.clear() - caplog.set_level(logging.WARNING) - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) hass.states.async_set("test.entity", "unavailable") await hass.async_block_till_done() assert len(calls) == 0 - assert len(caplog.record_tuples) == 1 - assert caplog.record_tuples[0][1] == logging.WARNING - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=3)) hass.states.async_set("test.entity", 101) await hass.async_block_till_done() diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 5074b6e70c462d..a041dc7fc7fbf2 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,5 +1,4 @@ """Test the condition helper.""" -from logging import WARNING from unittest.mock import patch import pytest @@ -363,27 +362,6 @@ async def test_time_using_input_datetime(hass): condition.time(hass, before="input_datetime.not_existing") -async def test_if_numeric_state_raises_on_unavailable(hass, caplog): - """Test numeric_state raises on unavailable/unknown state.""" - test = await condition.async_from_config( - hass, - {"condition": "numeric_state", "entity_id": "sensor.temperature", "below": 42}, - ) - - caplog.clear() - caplog.set_level(WARNING) - - hass.states.async_set("sensor.temperature", "unavailable") - with pytest.raises(ConditionError): - test(hass) - assert len(caplog.record_tuples) == 0 - - hass.states.async_set("sensor.temperature", "unknown") - with pytest.raises(ConditionError): - test(hass) - assert len(caplog.record_tuples) == 0 - - async def test_state_raises(hass): """Test that state raises ConditionError on errors.""" # No entity @@ -631,6 +609,26 @@ async def test_state_using_input_entities(hass): assert test(hass) +async def test_numeric_state_known_non_matching(hass): + """Test that numeric_state doesn't match on known non-matching states.""" + hass.states.async_set("sensor.temperature", "unavailable") + test = await condition.async_from_config( + hass, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "above": 0, + }, + ) + + # Unavailable state + assert not test(hass) + + # Unknown state + hass.states.async_set("sensor.temperature", "unknown") + assert not test(hass) + + async def test_numeric_state_raises(hass): """Test that numeric_state raises ConditionError on errors.""" # Unknown entities @@ -677,20 +675,6 @@ async def test_numeric_state_raises(hass): hass.states.async_set("sensor.temperature", 50) test(hass) - # Unavailable state - with pytest.raises(ConditionError, match="state of .* is unavailable"): - test = await condition.async_from_config( - hass, - { - "condition": "numeric_state", - "entity_id": "sensor.temperature", - "above": 0, - }, - ) - - hass.states.async_set("sensor.temperature", "unavailable") - test(hass) - # Bad number with pytest.raises(ConditionError, match="cannot be processed as a number"): test = await condition.async_from_config( @@ -852,6 +836,12 @@ async def test_numeric_state_using_input_number(hass): hass.states.async_set("sensor.temperature", 100) assert not test(hass) + hass.states.async_set("input_number.high", "unknown") + assert not test(hass) + + hass.states.async_set("input_number.high", "unavailable") + assert not test(hass) + await hass.services.async_call( "input_number", "set_value", @@ -863,6 +853,12 @@ async def test_numeric_state_using_input_number(hass): ) assert test(hass) + hass.states.async_set("input_number.low", "unknown") + assert not test(hass) + + hass.states.async_set("input_number.low", "unavailable") + assert not test(hass) + with pytest.raises(ConditionError): condition.async_numeric_state( hass, entity="sensor.temperature", below="input_number.not_exist" From ef9b9663c56bd9a6f925d455aeb39546e755693d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 4 Mar 2021 22:11:38 +0100 Subject: [PATCH 1018/1818] Fix access of missing zwave_js climate unit value (#47380) --- homeassistant/components/zwave_js/climate.py | 8 +- tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_climate.py | 17 ++ .../zwave_js/srt321_hrt4_zw_state.json | 262 ++++++++++++++++++ 5 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/zwave_js/srt321_hrt4_zw_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 54966538aaee0f..cc449b89e91a06 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -118,7 +118,7 @@ def __init__( super().__init__(config_entry, client, info) self._hvac_modes: Dict[str, Optional[int]] = {} self._hvac_presets: Dict[str, Optional[int]] = {} - self._unit_value: ZwaveValue = None + self._unit_value: Optional[ZwaveValue] = None self._current_mode = self.get_zwave_value( THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE @@ -215,7 +215,11 @@ def _current_mode_setpoint_enums(self) -> List[Optional[ThermostatSetpointType]] @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - if "f" in self._unit_value.metadata.unit.lower(): + if ( + self._unit_value + and self._unit_value.metadata.unit + and "f" in self._unit_value.metadata.unit.lower() + ): return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index a5ee628754e91d..ec54e13940424e 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,6 +16,7 @@ CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" +CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner" BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" EATON_RF9640_ENTITY = "light.allloaddimmer" AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 50cacd974229cf..aa9da282635509 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -240,6 +240,12 @@ def nortek_thermostat_state_fixture(): return json.loads(load_fixture("zwave_js/nortek_thermostat_state.json")) +@pytest.fixture(name="srt321_hrt4_zw_state", scope="session") +def srt321_hrt4_zw_state_fixture(): + """Load the climate HRT4-ZW / SRT321 / SRT322 thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/srt321_hrt4_zw_state.json")) + + @pytest.fixture(name="chain_actuator_zws12_state", scope="session") def window_cover_state_fixture(): """Load the window cover node state fixture data.""" @@ -417,6 +423,14 @@ def nortek_thermostat_fixture(client, nortek_thermostat_state): return node +@pytest.fixture(name="srt321_hrt4_zw") +def srt321_hrt4_zw_fixture(client, srt321_hrt4_zw_state): + """Mock a HRT4-ZW / SRT321 / SRT322 thermostat node.""" + node = Node(client, copy.deepcopy(srt321_hrt4_zw_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="aeotec_radiator_thermostat") def aeotec_radiator_thermostat_fixture(client, aeotec_radiator_thermostat_state): """Mock a Aeotec thermostat node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 44804825885f1b..ea75f10328c2ce 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -31,6 +31,7 @@ from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_FLOOR_THERMOSTAT_ENTITY, + CLIMATE_MAIN_HEAT_ACTIONNER, CLIMATE_RADIO_THERMOSTAT_ENTITY, ) @@ -488,3 +489,19 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + +async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): + """Test a climate entity from a HRT4-ZW / SRT321 thermostat device. + + This device currently has no setpoint values. + """ + state = hass.states.get(CLIMATE_MAIN_HEAT_ACTIONNER) + + assert state + assert state.state == HVAC_MODE_OFF + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + ] + assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None diff --git a/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json b/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json new file mode 100644 index 00000000000000..a2fdaa995614bd --- /dev/null +++ b/tests/fixtures/zwave_js/srt321_hrt4_zw_state.json @@ -0,0 +1,262 @@ +{ + "nodeId": 20, + "index": 0, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 0, + "label": "Unused" + }, + "mandatorySupportedCCs": [], + "mandatoryControlledCCs": [] + }, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 3, + "isBeaming": true, + "manufacturerId": 89, + "productId": 1, + "productType": 3, + "firmwareVersion": "2.0", + "name": "main_heat_actionner", + "location": "kitchen", + "deviceConfig": { + "filename": "/opt/node_modules/@zwave-js/config/config/devices/0x0059/asr-zw.json", + "manufacturerId": 89, + "manufacturer": "Secure Meters (UK) Ltd.", + "label": "SRT322", + "description": "Thermostat Receiver", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0001" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + } + }, + "label": "SRT322", + "neighbors": [ + 1, + 5, + 10, + 12, + 13, + 14, + 15, + 18, + 21 + ], + "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 37, + "name": "Binary Switch", + "version": 1, + "isSecure": false + }, + { + "id": 64, + "name": "Thermostat Mode", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + } + ], + "endpoints": [ + { + "nodeId": 20, + "index": 0 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 255, + "states": { + "0": "Off", + "1": "Heat" + }, + "label": "Thermostat mode" + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 89 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 6 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "2.78" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 1, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "2.0" + ] + } + ] + } From 5685b4aa33c97b03d1940d4bb565f369f71abb96 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Mar 2021 22:09:51 +0100 Subject: [PATCH 1019/1818] Update frontend to 20210302.4 (#47383) --- 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 4d4127fc2f2795..a5b4c6f10d5397 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==20210302.3" + "home-assistant-frontend==20210302.4" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 752a3755169c5f..919b53f64ed31f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index ffbd439ee2e5c2..1b7723629e61ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 318b04e5e70e3b..14c12395170dd9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.3 +home-assistant-frontend==20210302.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 0bf3dea40cd6ce35e484ab84c5861da6ccf37b9b Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 4 Mar 2021 13:07:42 -0800 Subject: [PATCH 1020/1818] Revert "Speed-up wemo discovery (#46821)" (#47392) This reverts commit 6e52b26c06098052d379065b00f570c2a44653e1. --- homeassistant/components/wemo/__init__.py | 48 ++++------------------- tests/components/wemo/test_init.py | 33 +++++----------- 2 files changed, 17 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index df737f101baaa6..db380ae11cac2f 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,4 +1,5 @@ """Support for WeMo device discovery.""" +import asyncio import logging import pywemo @@ -15,14 +16,9 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from homeassistant.util.async_ import gather_with_concurrency from .const import DOMAIN -# Max number of devices to initialize at once. This limit is in place to -# avoid tying up too many executor threads with WeMo device setup. -MAX_CONCURRENCY = 3 - # Mapping from Wemo model_name to domain. WEMO_MODEL_DISPATCH = { "Bridge": LIGHT_DOMAIN, @@ -118,12 +114,11 @@ async def async_stop_wemo(event): static_conf = config.get(CONF_STATIC, []) if static_conf: _LOGGER.debug("Adding statically configured WeMo devices...") - for device in await gather_with_concurrency( - MAX_CONCURRENCY, + for device in await asyncio.gather( *[ hass.async_add_executor_job(validate_static_config, host, port) for host, port in static_conf - ], + ] ): if device: wemo_dispatcher.async_add_unique_device(hass, device) @@ -192,44 +187,15 @@ def __init__(self, hass: HomeAssistant, wemo_dispatcher: WemoDispatcher) -> None self._wemo_dispatcher = wemo_dispatcher self._stop = None self._scan_delay = 0 - self._upnp_entries = set() - - async def async_add_from_upnp_entry(self, entry: pywemo.ssdp.UPNPEntry) -> None: - """Create a WeMoDevice from an UPNPEntry and add it to the dispatcher. - - Uses the self._upnp_entries set to avoid interrogating the same device - multiple times. - """ - if entry in self._upnp_entries: - return - try: - device = await self._hass.async_add_executor_job( - pywemo.discovery.device_from_uuid_and_location, - entry.udn, - entry.location, - ) - except pywemo.PyWeMoException as err: - _LOGGER.error("Unable to setup WeMo %r (%s)", entry, err) - else: - self._wemo_dispatcher.async_add_unique_device(self._hass, device) - self._upnp_entries.add(entry) async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" _LOGGER.debug("Scanning network for WeMo devices...") try: - # pywemo.ssdp.scan is a light-weight UDP UPnP scan for WeMo devices. - entries = await self._hass.async_add_executor_job(pywemo.ssdp.scan) - - # async_add_from_upnp_entry causes multiple HTTP requests to be sent - # to the WeMo device for the initial setup of the WeMoDevice - # instance. This may take some time to complete. The per-device - # setup work is done in parallel to speed up initial setup for the - # component. - await gather_with_concurrency( - MAX_CONCURRENCY, - *[self.async_add_from_upnp_entry(entry) for entry in entries], - ) + for device in await self._hass.async_add_executor_job( + pywemo.discover_devices + ): + self._wemo_dispatcher.async_add_unique_device(self._hass, device) finally: # Run discovery more frequently after hass has just started. self._scan_delay = min( diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 7c2b43dfd8cfd2..374222d86888da 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -100,41 +100,28 @@ async def test_static_config_with_invalid_host(hass): async def test_discovery(hass, pywemo_registry): """Verify that discovery dispatches devices to the platform for setup.""" - def create_device(uuid, location): + def create_device(counter): """Create a unique mock Motion detector device for each counter value.""" device = create_autospec(pywemo.Motion, instance=True) - device.host = location - device.port = MOCK_PORT - device.name = f"{MOCK_NAME}_{uuid}" - device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{uuid}" + device.host = f"{MOCK_HOST}_{counter}" + device.port = MOCK_PORT + counter + device.name = f"{MOCK_NAME}_{counter}" + device.serialnumber = f"{MOCK_SERIAL_NUMBER}_{counter}" device.model_name = "Motion" device.get_state.return_value = 0 # Default to Off return device - def create_upnp_entry(counter): - return pywemo.ssdp.UPNPEntry.from_response( - "\r\n".join( - [ - "", - f"LOCATION: http://192.168.1.100:{counter}/setup.xml", - f"USN: uuid:Socket-1_0-SERIAL{counter}::upnp:rootdevice", - "", - ] - ) - ) - - upnp_entries = [create_upnp_entry(0), create_upnp_entry(1)] + pywemo_devices = [create_device(0), create_device(1)] # Setup the component and start discovery. with patch( - "pywemo.discovery.device_from_uuid_and_location", side_effect=create_device - ), patch("pywemo.ssdp.scan", return_value=upnp_entries) as mock_scan: + "pywemo.discover_devices", return_value=pywemo_devices + ) as mock_discovery: assert await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_DISCOVERY: True}} ) await pywemo_registry.semaphore.acquire() # Returns after platform setup. - mock_scan.assert_called() - # Add two of the same entries to test deduplication. - upnp_entries.extend([create_upnp_entry(2), create_upnp_entry(2)]) + mock_discovery.assert_called() + pywemo_devices.append(create_device(2)) # Test that discovery runs periodically and the async_dispatcher_send code works. async_fire_time_changed( From 181714799518c422b667cce863c0cd9010079e1e Mon Sep 17 00:00:00 2001 From: Christophe Painchaud Date: Fri, 5 Mar 2021 01:09:54 +0100 Subject: [PATCH 1021/1818] Fix RFLink TCP KeepAlive error log (#47395) --- homeassistant/components/rflink/__init__.py | 41 +++++++++++---------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 3cff3beed3ce72..68783c3426af3d 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -203,25 +203,28 @@ def event_callback(event): # TCP port when host configured, otherwise serial port port = config[DOMAIN][CONF_PORT] - # TCP KEEPALIVE will be enabled if value > 0 - keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] - if keepalive_idle_timer < 0: - _LOGGER.error( - "A bogus TCP Keepalive IDLE timer was provided (%d secs), " - "default value will be used. " - "Recommended values: 60-3600 (seconds)", - keepalive_idle_timer, - ) - keepalive_idle_timer = DEFAULT_TCP_KEEPALIVE_IDLE_TIMER - elif keepalive_idle_timer == 0: - keepalive_idle_timer = None - elif keepalive_idle_timer <= 30: - _LOGGER.warning( - "A very short TCP Keepalive IDLE timer was provided (%d secs), " - "and may produce unexpected disconnections from RFlink device." - " Recommended values: 60-3600 (seconds)", - keepalive_idle_timer, - ) + keepalive_idle_timer = None + # TCP KeepAlive only if this is TCP based connection (not serial) + if host is not None: + # TCP KEEPALIVE will be enabled if value > 0 + keepalive_idle_timer = config[DOMAIN][CONF_KEEPALIVE_IDLE] + if keepalive_idle_timer < 0: + _LOGGER.error( + "A bogus TCP Keepalive IDLE timer was provided (%d secs), " + "it will be disabled. " + "Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) + keepalive_idle_timer = None + elif keepalive_idle_timer == 0: + keepalive_idle_timer = None + elif keepalive_idle_timer <= 30: + _LOGGER.warning( + "A very short TCP Keepalive IDLE timer was provided (%d secs) " + "and may produce unexpected disconnections from RFlink device." + " Recommended values: 60-3600 (seconds)", + keepalive_idle_timer, + ) @callback def reconnect(exc=None): From 14dca8e7838f3e63483c39d6222674ccf99d3f39 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Mar 2021 11:02:50 -1000 Subject: [PATCH 1022/1818] Map silent as a preset mode for fan backcompat (#47396) The original change did not map silent as a preset mode because it was not clear if it was a speed or a preset. --- homeassistant/components/fan/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 18f46b3d6190ff..1fbf35b1603459 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -77,6 +77,7 @@ _NOT_SPEED_IDLE = "idle" _NOT_SPEED_FAVORITE = "favorite" _NOT_SPEED_SLEEP = "sleep" +_NOT_SPEED_SILENT = "silent" _NOT_SPEEDS_FILTER = { _NOT_SPEED_OFF, @@ -85,6 +86,7 @@ _NOT_SPEED_SMART, _NOT_SPEED_INTERVAL, _NOT_SPEED_IDLE, + _NOT_SPEED_SILENT, _NOT_SPEED_SLEEP, _NOT_SPEED_FAVORITE, } @@ -652,7 +654,7 @@ def speed_list_without_preset_modes(speed_list: List): output: ["1", "2", "3", "4", "5", "6", "7"] input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"] - output: ["Silent", "Medium", "High", "Strong"] + output: ["Medium", "High", "Strong"] """ return [speed for speed in speed_list if speed.lower() not in _NOT_SPEEDS_FILTER] @@ -674,7 +676,7 @@ def preset_modes_from_speed_list(speed_list: List): output: ["smart"] input: ["Auto", "Silent", "Favorite", "Idle", "Medium", "High", "Strong"] - output: ["Auto", "Favorite", "Idle"] + output: ["Auto", "Silent", "Favorite", "Idle"] """ return [ From 6724d86565bcf634bd505ad93e2d1de43afa4681 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Thu, 4 Mar 2021 22:12:04 +0100 Subject: [PATCH 1023/1818] Fix measurement unit (Closes: #47390) (#47398) --- homeassistant/components/xiaomi_miio/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 821fe164ea97f5..a47b32fc6d1fae 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -22,7 +22,6 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, - LIGHT_LUX, PERCENTAGE, PRESSURE_HPA, TEMP_CELSIUS, @@ -38,6 +37,7 @@ DEFAULT_NAME = "Xiaomi Miio Sensor" DATA_KEY = "sensor.xiaomi_miio" +UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -302,7 +302,7 @@ def available(self): @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return LIGHT_LUX + return UNIT_LUMEN @property def device_class(self): From ee55a04b4b173e5535678fc6f1b4717e899b1f81 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:21:56 -0500 Subject: [PATCH 1024/1818] Fix Climacell timezone issue with daily forecasts (#47402) --- homeassistant/components/climacell/weather.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index c77bbfbd50a305..c72220d88567cd 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -260,6 +260,7 @@ def forecast(self): if self.forecast_type == DAILY: use_datetime = False + forecast_dt = dt_util.start_of_local_day(forecast_dt) precipitation = self._get_cc_value( forecast, CC_ATTR_PRECIPITATION_DAILY ) From 36a25217993e7fe72eb9ac17dc0f65e50cf57280 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 4 Mar 2021 14:20:08 -0700 Subject: [PATCH 1025/1818] Fix AirVisual exception when config entry contains old integration type (#47405) --- .../components/airvisual/__init__.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 3a88243b0b9547..f82bdcd96b8600 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -39,6 +39,7 @@ DATA_COORDINATOR, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_NODE_PRO, LOGGER, ) @@ -142,12 +143,21 @@ def _standardize_geography_config_entry(hass, config_entry): if not config_entry.options: # If the config entry doesn't already have any options set, set defaults: entry_updates["options"] = {CONF_SHOW_ON_MAP: True} - if CONF_INTEGRATION_TYPE not in config_entry.data: - # If the config entry data doesn't contain the integration type, add it: - entry_updates["data"] = { - **config_entry.data, - CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY_COORDS, - } + if config_entry.data.get(CONF_INTEGRATION_TYPE) not in [ + INTEGRATION_TYPE_GEOGRAPHY_COORDS, + INTEGRATION_TYPE_GEOGRAPHY_NAME, + ]: + # If the config entry data doesn't contain an integration type that we know + # about, infer it from the data we have: + entry_updates["data"] = {**config_entry.data} + if CONF_CITY in config_entry.data: + entry_updates["data"][ + CONF_INTEGRATION_TYPE + ] = INTEGRATION_TYPE_GEOGRAPHY_NAME + else: + entry_updates["data"][ + CONF_INTEGRATION_TYPE + ] = INTEGRATION_TYPE_GEOGRAPHY_COORDS if not entry_updates: return From f53cff49d52ea48debade4985a9f908ca733a58c Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:15:27 -0500 Subject: [PATCH 1026/1818] Don't convert Climacell forecast temperatures to celsius because platform does it automatically (#47406) --- homeassistant/components/climacell/weather.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index c72220d88567cd..e5a24197d6bad1 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -22,7 +22,6 @@ LENGTH_MILES, PRESSURE_HPA, PRESSURE_INHG, - TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity @@ -31,7 +30,6 @@ from homeassistant.util import dt as dt_util from homeassistant.util.distance import convert as distance_convert from homeassistant.util.pressure import convert as pressure_convert -from homeassistant.util.temperature import convert as temp_convert from . import ClimaCellDataUpdateCoordinator, ClimaCellEntity from .const import ( @@ -102,10 +100,6 @@ def _forecast_dict( precipitation = ( distance_convert(precipitation / 12, LENGTH_FEET, LENGTH_METERS) * 1000 ) - if temp: - temp = temp_convert(temp, TEMP_FAHRENHEIT, TEMP_CELSIUS) - if temp_low: - temp_low = temp_convert(temp_low, TEMP_FAHRENHEIT, TEMP_CELSIUS) if wind_speed: wind_speed = distance_convert(wind_speed, LENGTH_MILES, LENGTH_KILOMETERS) From 25ff2e745dd10a412bce7eaa9ca811ef9c4ea4e0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:15:50 -0500 Subject: [PATCH 1027/1818] Bump zwave-js-server-python to 0.21.0 (#47408) Co-authored-by: Tobias Sauerwein --- homeassistant/components/zwave_js/__init__.py | 74 +-------- homeassistant/components/zwave_js/climate.py | 8 - homeassistant/components/zwave_js/entity.py | 3 - homeassistant/components/zwave_js/helpers.py | 11 -- homeassistant/components/zwave_js/light.py | 8 - .../components/zwave_js/manifest.json | 2 +- homeassistant/components/zwave_js/migrate.py | 113 +++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_api.py | 2 +- tests/components/zwave_js/test_climate.py | 6 +- tests/components/zwave_js/test_init.py | 148 +++++++++++++++++- .../zwave_js/climate_danfoss_lc_13_state.json | 90 +++++++++-- .../zwave_js/climate_heatit_z_trm3_state.json | 1 + 14 files changed, 352 insertions(+), 118 deletions(-) create mode 100644 homeassistant/components/zwave_js/migrate.py diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 6bd951fdf5eead..0da394721ac190 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -48,7 +48,8 @@ ZWAVE_JS_EVENT, ) from .discovery import async_discover_values -from .helpers import get_device_id, get_old_value_id, get_unique_id +from .helpers import get_device_id +from .migrate import async_migrate_discovered_value from .services import ZWaveServices CONNECT_TIMEOUT = 10 @@ -98,31 +99,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: dev_reg = await device_registry.async_get_registry(hass) ent_reg = entity_registry.async_get(hass) - @callback - def migrate_entity(platform: str, old_unique_id: str, new_unique_id: str) -> None: - """Check if entity with old unique ID exists, and if so migrate it to new ID.""" - if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): - LOGGER.debug( - "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", - entity_id, - old_unique_id, - new_unique_id, - ) - try: - ent_reg.async_update_entity( - entity_id, - new_unique_id=new_unique_id, - ) - except ValueError: - LOGGER.debug( - ( - "Entity %s can't be migrated because the unique ID is taken. " - "Cleaning it up since it is likely no longer valid." - ), - entity_id, - ) - ent_reg.async_remove(entity_id) - @callback def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" @@ -136,49 +112,9 @@ def async_on_node_ready(node: ZwaveNode) -> None: LOGGER.debug("Discovered entity: %s", disc_info) # This migration logic was added in 2021.3 to handle a breaking change to - # the value_id format. Some time in the future, this code block - # (as well as get_old_value_id helper and migrate_entity closure) can be - # removed. - value_ids = [ - # 2021.2.* format - get_old_value_id(disc_info.primary_value), - # 2021.3.0b0 format - disc_info.primary_value.value_id, - ] - - new_unique_id = get_unique_id( - client.driver.controller.home_id, - disc_info.primary_value.value_id, - ) - - for value_id in value_ids: - old_unique_id = get_unique_id( - client.driver.controller.home_id, - f"{disc_info.primary_value.node.node_id}.{value_id}", - ) - # Most entities have the same ID format, but notification binary sensors - # have a state key in their ID so we need to handle them differently - if ( - disc_info.platform == "binary_sensor" - and disc_info.platform_hint == "notification" - ): - for state_key in disc_info.primary_value.metadata.states: - # ignore idle key (0) - if state_key == "0": - continue - - migrate_entity( - disc_info.platform, - f"{old_unique_id}.{state_key}", - f"{new_unique_id}.{state_key}", - ) - - # Once we've iterated through all state keys, we can move on to the - # next item - continue - - migrate_entity(disc_info.platform, old_unique_id, new_unique_id) - + # the value_id format. Some time in the future, this call (as well as the + # helper functions) can be removed. + async_migrate_discovered_value(ent_reg, client, disc_info) async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info ) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index cc449b89e91a06..325cf14b379df4 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -125,18 +125,10 @@ def __init__( ) self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: - # Some devices don't include a property key so we need to check for value - # ID's, both with and without the property key self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, command_class=CommandClass.THERMOSTAT_SETPOINT, value_property_key=enum.value.key, - value_property_key_name=enum.value.name, - add_to_watched_value_ids=True, - ) or self.get_zwave_value( - THERMOSTAT_SETPOINT_PROPERTY, - command_class=CommandClass.THERMOSTAT_SETPOINT, - value_property_key_name=enum.value.name, add_to_watched_value_ids=True, ) # Use the first found setpoint value to always determine the temperature unit diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index d0ed9eb5291309..c061abc4d0ddfe 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -169,7 +169,6 @@ def get_zwave_value( command_class: Optional[int] = None, endpoint: Optional[int] = None, value_property_key: Optional[int] = None, - value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: @@ -188,7 +187,6 @@ def get_zwave_value( value_property, endpoint=endpoint, property_key=value_property_key, - property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) @@ -203,7 +201,6 @@ def get_zwave_value( value_property, endpoint=endpoint_.index, property_key=value_property_key, - property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) if return_value: diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 9582b7ee054f01..16baeb816c2d0b 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -3,7 +3,6 @@ from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode -from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -13,16 +12,6 @@ from .const import DATA_CLIENT, DOMAIN -@callback -def get_old_value_id(value: ZwaveValue) -> str: - """Get old value ID so we can migrate entity unique ID.""" - command_class = value.command_class - endpoint = value.endpoint or "00" - property_ = value.property_ - property_key_name = value.property_key_name or "00" - return f"{value.node.node_id}-{command_class}-{endpoint}-{property_}-{property_key_name}" - - @callback def get_unique_id(home_id: str, value_id: str) -> str: """Get unique ID from home ID and value ID.""" diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index d9c31210beab67..b501ecb58e7fab 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -228,7 +228,6 @@ async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: "targetColor", CommandClass.SWITCH_COLOR, value_property_key=None, - value_property_key_name=None, ) if combined_color_val and isinstance(combined_color_val.value, dict): colors_dict = {} @@ -252,7 +251,6 @@ async def _async_set_color(self, color: ColorComponent, new_value: int) -> None: "targetColor", CommandClass.SWITCH_COLOR, value_property_key=property_key.key, - value_property_key_name=property_key.name, ) if target_zwave_value is None: # guard for unsupported color @@ -318,31 +316,26 @@ def _calculate_color_values(self) -> None: "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.RED.value.key, - value_property_key_name=ColorComponent.RED.value.name, ) green_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.GREEN.value.key, - value_property_key_name=ColorComponent.GREEN.value.name, ) blue_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.BLUE.value.key, - value_property_key_name=ColorComponent.BLUE.value.name, ) ww_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.WARM_WHITE.value.key, - value_property_key_name=ColorComponent.WARM_WHITE.value.name, ) cw_val = self.get_zwave_value( "currentColor", CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.COLD_WHITE.value.key, - value_property_key_name=ColorComponent.COLD_WHITE.value.name, ) # prefer the (new) combined color property # https://github.com/zwave-js/node-zwave-js/pull/1782 @@ -350,7 +343,6 @@ def _calculate_color_values(self) -> None: "currentColor", CommandClass.SWITCH_COLOR, value_property_key=None, - value_property_key_name=None, ) if combined_color_val and isinstance(combined_color_val.value, dict): multi_color = combined_color_val.value diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index c812515a179cce..2a6f036fa809ae 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.20.1"], + "requirements": ["zwave-js-server-python==0.21.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py new file mode 100644 index 00000000000000..49c18073de5897 --- /dev/null +++ b/homeassistant/components/zwave_js/migrate.py @@ -0,0 +1,113 @@ +"""Functions used to migrate unique IDs for Z-Wave JS entities.""" +import logging +from typing import List + +from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.value import Value as ZwaveValue + +from homeassistant.core import callback +from homeassistant.helpers.entity_registry import EntityRegistry + +from .const import DOMAIN +from .discovery import ZwaveDiscoveryInfo +from .helpers import get_unique_id + +_LOGGER = logging.getLogger(__name__) + + +@callback +def async_migrate_entity( + ent_reg: EntityRegistry, platform: str, old_unique_id: str, new_unique_id: str +) -> None: + """Check if entity with old unique ID exists, and if so migrate it to new ID.""" + if entity_id := ent_reg.async_get_entity_id(platform, DOMAIN, old_unique_id): + _LOGGER.debug( + "Migrating entity %s from old unique ID '%s' to new unique ID '%s'", + entity_id, + old_unique_id, + new_unique_id, + ) + try: + ent_reg.async_update_entity( + entity_id, + new_unique_id=new_unique_id, + ) + except ValueError: + _LOGGER.debug( + ( + "Entity %s can't be migrated because the unique ID is taken. " + "Cleaning it up since it is likely no longer valid." + ), + entity_id, + ) + ent_reg.async_remove(entity_id) + + +@callback +def async_migrate_discovered_value( + ent_reg: EntityRegistry, client: ZwaveClient, disc_info: ZwaveDiscoveryInfo +) -> 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, + ) + + # 2021.2.*, 2021.3.0b0, and 2021.3.0 formats + for value_id in get_old_value_ids(disc_info.primary_value): + old_unique_id = get_unique_id( + client.driver.controller.home_id, + value_id, + ) + # Most entities have the same ID format, but notification binary sensors + # have a state key in their ID so we need to handle them differently + if ( + disc_info.platform == "binary_sensor" + and disc_info.platform_hint == "notification" + ): + for state_key in disc_info.primary_value.metadata.states: + # ignore idle key (0) + if state_key == "0": + continue + + async_migrate_entity( + ent_reg, + disc_info.platform, + f"{old_unique_id}.{state_key}", + f"{new_unique_id}.{state_key}", + ) + + # Once we've iterated through all state keys, we can move on to the + # next item + continue + + async_migrate_entity(ent_reg, disc_info.platform, old_unique_id, new_unique_id) + + +@callback +def get_old_value_ids(value: ZwaveValue) -> List[str]: + """Get old value IDs so we can migrate entity unique ID.""" + value_ids = [] + + # Pre 2021.3.0 value ID + command_class = value.command_class + endpoint = value.endpoint or "00" + property_ = value.property_ + property_key_name = value.property_key_name or "00" + value_ids.append( + f"{value.node.node_id}.{value.node.node_id}-{command_class}-{endpoint}-" + f"{property_}-{property_key_name}" + ) + + endpoint = "00" if value.endpoint is None else value.endpoint + property_key = "00" if value.property_key is None else value.property_key + property_key_name = value.property_key_name or "00" + + value_id = ( + f"{value.node.node_id}-{command_class}-{endpoint}-" + f"{property_}-{property_key}-{property_key_name}" + ) + # 2021.3.0b0 and 2021.3.0 value IDs + value_ids.extend([f"{value.node.node_id}.{value_id}", value_id]) + + return value_ids diff --git a/requirements_all.txt b/requirements_all.txt index 1b7723629e61ab..5d69ddd65bdb5c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2397,4 +2397,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.1 +zwave-js-server-python==0.21.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 14c12395170dd9..5fdf5100605201 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,4 +1234,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.20.1 +zwave-js-server-python==0.21.0 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index dcbd924c86e6ca..1b3f29e9cb11a4 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -70,7 +70,7 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): result = msg["result"] assert len(result) == 61 - key = "52-112-0-2-00-00" + key = "52-112-0-2" assert result[key]["property"] == 2 assert result[key]["metadata"]["type"] == "number" assert result[key]["configuration_value_type"] == "enumerated" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index ea75f10328c2ce..fe3e0708acc409 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -405,7 +405,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state assert state.state == HVAC_MODE_HEAT - assert state.attributes[ATTR_TEMPERATURE] == 25 + assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE @@ -432,6 +432,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClassName": "Thermostat Setpoint", "property": "setpoint", "propertyName": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "ccVersion": 2, "metadata": { @@ -441,7 +442,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "unit": "\u00b0C", "ccSpecific": {"setpointType": 1}, }, - "value": 25, + "value": 14, } assert args["value"] == 21.5 @@ -459,6 +460,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat "commandClass": 67, "endpoint": 0, "property": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "propertyName": "setpoint", "newValue": 23, diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 6f60bbc0300490..cd2017f2c669f4 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -160,7 +160,7 @@ async def test_unique_id_migration_dupes( # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature" assert entity_entry.unique_id == new_unique_id assert ent_reg.async_get(f"{AIR_TEMPERATURE_SENSOR}_1") is None @@ -195,7 +195,7 @@ async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integra # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Air temperature" assert entity_entry.unique_id == new_unique_id @@ -228,7 +228,147 @@ async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integra # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_v3(hass, multisensor_6_state, client, integration): + """Test unique ID is migrated from old format to new (version 3).""" + ent_reg = entity_registry.async_get(hass) + # Migrate version 2 + ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" + entity_name = ILLUMINANCE_SENSOR.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance-00-00" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == ILLUMINANCE_SENSOR + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, multisensor_6_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + await hass.async_block_till_done() + + # Check that new RegistryEntry is using new unique ID format + entity_entry = ent_reg.async_get(ILLUMINANCE_SENSOR) + new_unique_id = f"{client.driver.controller.home_id}.52-49-0-Illuminance" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v1( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 1).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.32.32-50-00-value-W_Consumed" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + 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"{client.driver.controller.home_id}.32-50-0-value-66049" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v2( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 2).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = ( + f"{client.driver.controller.home_id}.32.32-50-0-value-66049-W_Consumed" + ) + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + 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"{client.driver.controller.home_id}.32-50-0-value-66049" + assert entity_entry.unique_id == new_unique_id + + +async def test_unique_id_migration_property_key_v3( + hass, hank_binary_switch_state, client, integration +): + """Test unique ID with property key is migrated from old format to new (version 3).""" + ent_reg = entity_registry.async_get(hass) + + SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" + entity_name = SENSOR_NAME.split(".")[1] + + # Create entity RegistryEntry using old unique ID format + old_unique_id = f"{client.driver.controller.home_id}.32-50-0-value-66049-W_Consumed" + entity_entry = ent_reg.async_get_or_create( + "sensor", + DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=integration, + original_name=entity_name, + ) + assert entity_entry.entity_id == SENSOR_NAME + assert entity_entry.unique_id == old_unique_id + + # Add a ready node, unique ID should be migrated + node = Node(client, hank_binary_switch_state) + event = {"node": node} + + client.driver.controller.emit("node added", event) + 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"{client.driver.controller.home_id}.32-50-0-value-66049" assert entity_entry.unique_id == new_unique_id @@ -262,7 +402,7 @@ async def test_unique_id_migration_notification_binary_sensor( # Check that new RegistryEntry is using new unique ID format entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_BINARY_SENSOR) - new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status-Motion sensor status.8" + new_unique_id = f"{client.driver.controller.home_id}.52-113-0-Home Security-Motion sensor status.8" assert entity_entry.unique_id == new_unique_id diff --git a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json index 90410998597146..8574674714f073 100644 --- a/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json +++ b/tests/fixtures/zwave_js/climate_danfoss_lc_13_state.json @@ -4,11 +4,25 @@ "status": 1, "ready": true, "deviceClass": { - "basic": {"key": 4, "label":"Routing Slave"}, - "generic": {"key": 8, "label":"Thermostat"}, - "specific": {"key": 4, "label":"Setpoint Thermostat"}, - "mandatorySupportedCCs": [], - "mandatoryControlCCs": [] + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 4, + "label": "Setpoint Thermostat" + }, + "mandatorySupportedCCs": [ + 114, + 143, + 67, + 134 + ], + "mandatoryControlledCCs": [] }, "isListening": false, "isFrequentListening": false, @@ -22,6 +36,7 @@ "productType": 5, "firmwareVersion": "1.1", "deviceConfig": { + "filename": "/usr/src/app/node_modules/@zwave-js/config/config/devices/0x0002/lc-13.json", "manufacturerId": 2, "manufacturer": "Danfoss", "label": "LC-13", @@ -66,19 +81,76 @@ 14 ], "interviewAttempts": 1, + "interviewStage": 7, + "commandClasses": [ + { + "id": 67, + "name": "Thermostat Setpoint", + "version": 2, + "isSecure": false + }, + { + "id": 70, + "name": "Climate Control Schedule", + "version": 1, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 1, + "isSecure": false + }, + { + "id": 117, + "name": "Protection", + "version": 2, + "isSecure": false + }, + { + "id": 128, + "name": "Battery", + "version": 1, + "isSecure": false + }, + { + "id": 129, + "name": "Clock", + "version": 1, + "isSecure": false + }, + { + "id": 132, + "name": "Wake Up", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 1, + "isSecure": false + }, + { + "id": 143, + "name": "Multi Command", + "version": 1, + "isSecure": false + } + ], "endpoints": [ { "nodeId": 5, "index": 0 } ], - "commandClasses": [], "values": [ { "endpoint": 0, "commandClass": 67, "commandClassName": "Thermostat Setpoint", "property": "setpoint", + "propertyKey": 1, "propertyName": "setpoint", "propertyKeyName": "Heating", "ccVersion": 2, @@ -91,7 +163,7 @@ "setpointType": 1 } }, - "value": 25 + "value": 14 }, { "endpoint": 0, @@ -262,7 +334,7 @@ "unit": "%", "label": "Battery level" }, - "value": 53 + "value": 49 }, { "endpoint": 0, @@ -361,4 +433,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json index 0dc040c6cb24b9..b26b69be9ad5e4 100644 --- a/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json +++ b/tests/fixtures/zwave_js/climate_heatit_z_trm3_state.json @@ -837,6 +837,7 @@ "commandClassName": "Thermostat Setpoint", "property": "setpoint", "propertyName": "setpoint", + "propertyKey": 1, "propertyKeyName": "Heating", "ccVersion": 3, "metadata": { From ff86f648060b2e980ddfecf612fb5bd8166b9f3b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 4 Mar 2021 22:27:59 +0100 Subject: [PATCH 1028/1818] Fix older Roborock models (#47412) --- homeassistant/components/xiaomi_miio/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 4fa575118f8a84..b2b63344c176dd 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -22,7 +22,7 @@ "chuangmi.plug.hmi205", "chuangmi.plug.hmi206", ] -MODELS_VACUUM = ["roborock.vacuum"] +MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY From 915ee2f4ee5793e5f3c58c0444fb9ae541987438 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Mar 2021 00:22:31 +0000 Subject: [PATCH 1029/1818] Bumped version to 2021.3.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ec2ab3bff0c683..e454f3ab09d6e4 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 0350a6ed215fef56696053efa6a63acea468ff45 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 5 Mar 2021 01:38:33 +0100 Subject: [PATCH 1030/1818] Only create snapshot if add-on update will be done (#47424) --- homeassistant/components/zwave_js/addon.py | 2 +- tests/components/zwave_js/test_init.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index b8d020cfb00c7e..818e46a34aa001 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -173,6 +173,7 @@ async def async_update_addon(self) -> None: if not update_available: return + await self.async_create_snapshot() await async_update_addon(self._hass, ADDON_SLUG) @callback @@ -184,7 +185,6 @@ def async_schedule_update_addon(self, catch_error: bool = False) -> asyncio.Task if not self._update_task or self._update_task.done(): LOGGER.info("Trying to update the Z-Wave JS add-on") self._update_task = self._async_schedule_addon_operation( - self.async_create_snapshot, self.async_update_addon, catch_error=catch_error, ) diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index cd2017f2c669f4..e56db58f3cc540 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -585,11 +585,13 @@ async def test_addon_info_failure( @pytest.mark.parametrize( - "addon_version, update_available, update_calls, update_addon_side_effect", + "addon_version, update_available, update_calls, snapshot_calls, " + "update_addon_side_effect, create_shapshot_side_effect", [ - ("1.0", True, 1, None), - ("1.0", False, 0, None), - ("1.0", True, 1, HassioAPIError("Boom")), + ("1.0", True, 1, 1, None, None), + ("1.0", False, 0, 0, None, None), + ("1.0", True, 1, 1, HassioAPIError("Boom"), None), + ("1.0", True, 0, 1, None, HassioAPIError("Boom")), ], ) async def test_update_addon( @@ -604,11 +606,14 @@ async def test_update_addon( addon_version, update_available, update_calls, + snapshot_calls, update_addon_side_effect, + create_shapshot_side_effect, ): """Test update the Z-Wave JS add-on during entry setup.""" addon_info.return_value["version"] = addon_version addon_info.return_value["update_available"] = update_available + create_shapshot.side_effect = create_shapshot_side_effect update_addon.side_effect = update_addon_side_effect client.connect.side_effect = InvalidServerVersion("Invalid version") device = "/test" @@ -630,12 +635,7 @@ async def test_update_addon( await hass.async_block_till_done() assert entry.state == ENTRY_STATE_SETUP_RETRY - assert create_shapshot.call_count == 1 - assert create_shapshot.call_args == call( - hass, - {"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]}, - partial=True, - ) + assert create_shapshot.call_count == snapshot_calls assert update_addon.call_count == update_calls From d9542c2efe54f72f690e2f8bfe00e6f405c6dd52 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 5 Mar 2021 01:38:33 +0100 Subject: [PATCH 1031/1818] Only create snapshot if add-on update will be done (#47424) --- homeassistant/components/zwave_js/addon.py | 2 +- tests/components/zwave_js/test_init.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index b8d020cfb00c7e..818e46a34aa001 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -173,6 +173,7 @@ async def async_update_addon(self) -> None: if not update_available: return + await self.async_create_snapshot() await async_update_addon(self._hass, ADDON_SLUG) @callback @@ -184,7 +185,6 @@ def async_schedule_update_addon(self, catch_error: bool = False) -> asyncio.Task if not self._update_task or self._update_task.done(): LOGGER.info("Trying to update the Z-Wave JS add-on") self._update_task = self._async_schedule_addon_operation( - self.async_create_snapshot, self.async_update_addon, catch_error=catch_error, ) diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index cd2017f2c669f4..e56db58f3cc540 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -585,11 +585,13 @@ async def test_addon_info_failure( @pytest.mark.parametrize( - "addon_version, update_available, update_calls, update_addon_side_effect", + "addon_version, update_available, update_calls, snapshot_calls, " + "update_addon_side_effect, create_shapshot_side_effect", [ - ("1.0", True, 1, None), - ("1.0", False, 0, None), - ("1.0", True, 1, HassioAPIError("Boom")), + ("1.0", True, 1, 1, None, None), + ("1.0", False, 0, 0, None, None), + ("1.0", True, 1, 1, HassioAPIError("Boom"), None), + ("1.0", True, 0, 1, None, HassioAPIError("Boom")), ], ) async def test_update_addon( @@ -604,11 +606,14 @@ async def test_update_addon( addon_version, update_available, update_calls, + snapshot_calls, update_addon_side_effect, + create_shapshot_side_effect, ): """Test update the Z-Wave JS add-on during entry setup.""" addon_info.return_value["version"] = addon_version addon_info.return_value["update_available"] = update_available + create_shapshot.side_effect = create_shapshot_side_effect update_addon.side_effect = update_addon_side_effect client.connect.side_effect = InvalidServerVersion("Invalid version") device = "/test" @@ -630,12 +635,7 @@ async def test_update_addon( await hass.async_block_till_done() assert entry.state == ENTRY_STATE_SETUP_RETRY - assert create_shapshot.call_count == 1 - assert create_shapshot.call_args == call( - hass, - {"name": f"addon_core_zwave_js_{addon_version}", "addons": ["core_zwave_js"]}, - partial=True, - ) + assert create_shapshot.call_count == snapshot_calls assert update_addon.call_count == update_calls From 6a4b755fafad5137fe29e3799b5f3f80429e80a5 Mon Sep 17 00:00:00 2001 From: Paul Dee <647633+systemcrash@users.noreply.github.com> Date: Fri, 5 Mar 2021 15:36:07 +0100 Subject: [PATCH 1032/1818] Spellcheck on Synology component (#47451) --- homeassistant/components/synology_dsm/const.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 97f378c8e766af..ba1aa393c8594c 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -128,21 +128,21 @@ ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_1min_load": { - ENTITY_NAME: "CPU Load Averarge (1 min)", + ENTITY_NAME: "CPU Load Average (1 min)", ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: False, }, f"{SynoCoreUtilization.API_KEY}:cpu_5min_load": { - ENTITY_NAME: "CPU Load Averarge (5 min)", + ENTITY_NAME: "CPU Load Average (5 min)", ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, ENTITY_ENABLE: True, }, f"{SynoCoreUtilization.API_KEY}:cpu_15min_load": { - ENTITY_NAME: "CPU Load Averarge (15 min)", + ENTITY_NAME: "CPU Load Average (15 min)", ENTITY_UNIT: ENTITY_UNIT_LOAD, ENTITY_ICON: "mdi:chip", ENTITY_CLASS: None, From 864380e77c277b58ab4a85f5d1d3c023b31cf4c7 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 5 Mar 2021 22:51:07 +0800 Subject: [PATCH 1033/1818] Add allenporter to stream codeowners (#47431) --- CODEOWNERS | 2 +- homeassistant/components/stream/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 5324c15db6a042..0734006558872d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -453,7 +453,7 @@ homeassistant/components/starline/* @anonym-tsk homeassistant/components/statistics/* @fabaff homeassistant/components/stiebel_eltron/* @fucm homeassistant/components/stookalert/* @fwestenberg -homeassistant/components/stream/* @hunterjm @uvjustin +homeassistant/components/stream/* @hunterjm @uvjustin @allenporter homeassistant/components/stt/* @pvizeli homeassistant/components/subaru/* @G-Two homeassistant/components/suez_water/* @ooii diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 19b9e7b2e8a6b2..400b50eae04590 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -4,6 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/stream", "requirements": ["av==8.0.3"], "dependencies": ["http"], - "codeowners": ["@hunterjm", "@uvjustin"], + "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "quality_scale": "internal" } From 79ebe930e31a91967928a5642c98e58b522109b0 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 5 Mar 2021 16:16:07 +0100 Subject: [PATCH 1034/1818] Limit log spam by ESPHome (#47456) --- homeassistant/components/esphome/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 6ce411b5169927..b9720dcdf804ba 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -256,13 +256,16 @@ async def try_connect(tries: int = 0, is_disconnect: bool = True) -> None: # really short reconnect interval. tries = min(tries, 10) # prevent OverflowError wait_time = int(round(min(1.8 ** tries, 60.0))) - _LOGGER.info("Trying to reconnect to %s in %s seconds", host, wait_time) + if tries == 1: + _LOGGER.info("Trying to reconnect to %s in the background", host) + _LOGGER.debug("Retrying %s in %d seconds", host, wait_time) await asyncio.sleep(wait_time) try: await cli.connect(on_stop=try_connect, login=True) except APIConnectionError as error: - _LOGGER.info( + logger = _LOGGER.info if tries == 0 else _LOGGER.debug + logger( "Can't connect to ESPHome API for %s (%s): %s", entry.unique_id, host, From cad5e675889341206da86f442f04ac9ee402f51f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Mar 2021 12:41:36 -0500 Subject: [PATCH 1035/1818] Bump zwave-js-server-python to 0.21.1 (#47464) --- 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 2a6f036fa809ae..de0f2cc0a6f146 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.21.0"], + "requirements": ["zwave-js-server-python==0.21.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 99854ac3d9fc77..cc2c08a39aa774 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2394,4 +2394,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.0 +zwave-js-server-python==0.21.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4517457935ba85..607b7deb7e6504 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1231,4 +1231,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.0 +zwave-js-server-python==0.21.1 From a6c5e79de21a1f97c64478d14aa3d8d53cda373e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 5 Mar 2021 18:42:08 +0100 Subject: [PATCH 1036/1818] Update frontend to 20210302.5 (#47462) --- 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 a5b4c6f10d5397..8093c65d91a9c2 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==20210302.4" + "home-assistant-frontend==20210302.5" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 919b53f64ed31f..0586b956f39548 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index cc2c08a39aa774..883d78815ba563 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 607b7deb7e6504..a916bb029925d7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From f2a2dbb561b31cc559918b786b2ca04454750e89 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 5 Mar 2021 18:42:20 +0100 Subject: [PATCH 1037/1818] Bump version with fix for v1 (#47458) --- homeassistant/components/philips_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/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index e1e1fa69b6ba1b..9ed1cedbf05ef2 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==2.3.0" + "ha-philipsjs==2.3.1" ], "codeowners": [ "@elupus" diff --git a/requirements_all.txt b/requirements_all.txt index 883d78815ba563..f52f9b7e353ffc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -718,7 +718,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.0 +ha-philipsjs==2.3.1 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a916bb029925d7..b60ce427b00096 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -379,7 +379,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.0 +ha-philipsjs==2.3.1 # homeassistant.components.habitica habitipy==0.2.0 From cc99fd5e32c6755fa9a1a4768ebc897cc6820c61 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 18:43:26 +0100 Subject: [PATCH 1038/1818] Fix Hue scene overriding Hue default transition times (#47454) --- homeassistant/components/hue/bridge.py | 7 ++----- homeassistant/components/hue/const.py | 2 -- tests/components/hue/test_bridge.py | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 201e9f3a546382..dc9b56fcdfeea0 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -19,7 +19,6 @@ CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_UNREACHABLE, - DEFAULT_SCENE_TRANSITION, LOGGER, ) from .errors import AuthenticationRequired, CannotConnect @@ -34,9 +33,7 @@ { vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string, - vol.Optional( - ATTR_TRANSITION, default=DEFAULT_SCENE_TRANSITION - ): cv.positive_int, + vol.Optional(ATTR_TRANSITION): cv.positive_int, } ) # How long should we sleep if the hub is busy @@ -209,7 +206,7 @@ async def hue_activate_scene(self, call, updated=False, hide_warnings=False): """Service to call directly into bridge to set scenes.""" group_name = call.data[ATTR_GROUP_NAME] scene_name = call.data[ATTR_SCENE_NAME] - transition = call.data.get(ATTR_TRANSITION, DEFAULT_SCENE_TRANSITION) + transition = call.data.get(ATTR_TRANSITION) group = next( (group for group in self.api.groups.values() if group.name == group_name), diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index b782ce70193890..8d01617073b8d3 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -14,8 +14,6 @@ CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" DEFAULT_ALLOW_HUE_GROUPS = False -DEFAULT_SCENE_TRANSITION = 4 - GROUP_TYPE_LIGHT_GROUP = "LightGroup" GROUP_TYPE_ROOM = "Room" GROUP_TYPE_LUMINAIRE = "Luminaire" diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 29bc2acf03aea6..093f6356b09004 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -189,6 +189,7 @@ async def test_hue_activate_scene(hass, mock_api): assert len(mock_api.mock_requests) == 3 assert mock_api.mock_requests[2]["json"]["scene"] == "scene_1" + assert "transitiontime" not in mock_api.mock_requests[2]["json"] assert mock_api.mock_requests[2]["path"] == "groups/group_1/action" From 3baeed3684cd053b40c513380cf2f99ba2603cdb Mon Sep 17 00:00:00 2001 From: tkdrob Date: Fri, 5 Mar 2021 13:08:04 -0500 Subject: [PATCH 1039/1818] Clean up constants (#47323) --- .../components/automation/__init__.py | 2 +- homeassistant/components/script/__init__.py | 2 +- .../components/seventeentrack/sensor.py | 2 +- .../components/shopping_list/__init__.py | 3 +- homeassistant/components/slack/notify.py | 3 +- homeassistant/components/spaceapi/__init__.py | 2 +- homeassistant/components/upb/__init__.py | 3 +- homeassistant/components/upb/const.py | 1 - homeassistant/components/upcloud/__init__.py | 1 - homeassistant/components/vilfo/const.py | 9 ++++-- homeassistant/components/vilfo/sensor.py | 2 +- homeassistant/components/webostv/__init__.py | 28 +++++++++---------- homeassistant/components/webostv/const.py | 1 - .../components/xiaomi_aqara/__init__.py | 2 +- homeassistant/components/xiaomi_miio/fan.py | 2 +- .../components/xiaomi_miio/sensor.py | 2 +- .../components/xiaomi_miio/switch.py | 2 +- homeassistant/components/zha/api.py | 3 +- .../components/zha/core/channels/base.py | 2 +- homeassistant/components/zha/core/const.py | 2 -- homeassistant/components/zha/core/device.py | 3 +- homeassistant/components/zha/entity.py | 2 +- homeassistant/components/zwave/__init__.py | 9 +++--- homeassistant/components/zwave/const.py | 1 - homeassistant/components/zwave_js/__init__.py | 8 ++++-- homeassistant/components/zwave_js/const.py | 1 - homeassistant/helpers/script.py | 1 - tests/components/webostv/test_media_player.py | 2 +- tests/components/zha/test_api.py | 2 +- tests/components/zwave/test_init.py | 12 ++++---- 30 files changed, 55 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index acb28df05b08b8..f12a7398f7ea67 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -10,6 +10,7 @@ from homeassistant.components import blueprint from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_NAME, CONF_ALIAS, CONF_CONDITION, @@ -53,7 +54,6 @@ from homeassistant.helpers.script import ( ATTR_CUR, ATTR_MAX, - ATTR_MODE, CONF_MAX, CONF_MAX_EXCEEDED, Script, diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 429e97230ce69d..e408be47f656a9 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -7,6 +7,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_MODE, ATTR_NAME, CONF_ALIAS, CONF_ICON, @@ -27,7 +28,6 @@ from homeassistant.helpers.script import ( ATTR_CUR, ATTR_MAX, - ATTR_MODE, CONF_MAX, CONF_MAX_EXCEEDED, SCRIPT_MODE_SINGLE, diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index fa94ca4e3848ed..07cfe9ca66fcce 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -9,6 +9,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, + ATTR_FRIENDLY_NAME, ATTR_LOCATION, CONF_PASSWORD, CONF_SCAN_INTERVAL, @@ -22,7 +23,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_DESTINATION_COUNTRY = "destination_country" -ATTR_FRIENDLY_NAME = "friendly_name" ATTR_INFO_TEXT = "info_text" ATTR_ORIGIN_COUNTRY = "origin_country" ATTR_PACKAGES = "packages" diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index e438bf3b8f4b8a..841865cd759b35 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -7,14 +7,13 @@ from homeassistant import config_entries from homeassistant.components import http, websocket_api from homeassistant.components.http.data_validator import RequestDataValidator -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND +from homeassistant.const import ATTR_NAME, HTTP_BAD_REQUEST, HTTP_NOT_FOUND from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.util.json import load_json, save_json from .const import DOMAIN -ATTR_NAME = "name" ATTR_COMPLETE = "complete" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index fb7b949e3edd3b..f1e293773bd3a2 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -20,7 +20,7 @@ PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_USERNAME +from homeassistant.const import ATTR_ICON, CONF_API_KEY, CONF_ICON, CONF_USERNAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv import homeassistant.helpers.template as template @@ -35,7 +35,6 @@ ATTR_BLOCKS = "blocks" ATTR_BLOCKS_TEMPLATE = "blocks_template" ATTR_FILE = "file" -ATTR_ICON = "icon" ATTR_PASSWORD = "password" ATTR_PATH = "path" ATTR_URL = "url" diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index 571e0ab62f377c..f6def03ec6a016 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -6,6 +6,7 @@ ATTR_ENTITY_ID, ATTR_ICON, ATTR_LOCATION, + ATTR_NAME, ATTR_STATE, ATTR_UNIT_OF_MEASUREMENT, CONF_ADDRESS, @@ -35,7 +36,6 @@ ATTR_ISSUE_REPORT_CHANNELS = "issue_report_channels" ATTR_LASTCHANGE = "lastchange" ATTR_LOGO = "logo" -ATTR_NAME = "name" ATTR_OPEN = "open" ATTR_SENSORS = "sensors" ATTR_SPACE = "space" diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index 70f37451e6bb94..bf23be16af223d 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -3,7 +3,7 @@ import upb_lib -from homeassistant.const import CONF_FILE_PATH, CONF_HOST +from homeassistant.const import ATTR_COMMAND, CONF_FILE_PATH, CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType @@ -11,7 +11,6 @@ from .const import ( ATTR_ADDRESS, ATTR_BRIGHTNESS_PCT, - ATTR_COMMAND, ATTR_RATE, DOMAIN, EVENT_UPB_SCENE_CHANGED, diff --git a/homeassistant/components/upb/const.py b/homeassistant/components/upb/const.py index 75d754087e4397..8a2c435a70f3a4 100644 --- a/homeassistant/components/upb/const.py +++ b/homeassistant/components/upb/const.py @@ -10,7 +10,6 @@ ATTR_BLINK_RATE = "blink_rate" ATTR_BRIGHTNESS = "brightness" ATTR_BRIGHTNESS_PCT = "brightness_pct" -ATTR_COMMAND = "command" ATTR_RATE = "rate" CONF_NETWORK = "network" EVENT_UPB_SCENE_CHANGED = "upb.scene_changed" diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index 5acf9e364bc5cf..bb96ef0f1d693d 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -40,7 +40,6 @@ ATTR_CORE_NUMBER = "core_number" ATTR_HOSTNAME = "hostname" ATTR_MEMORY_AMOUNT = "memory_amount" -ATTR_STATE = "state" ATTR_TITLE = "title" ATTR_UUID = "uuid" ATTR_ZONE = "zone" diff --git a/homeassistant/components/vilfo/const.py b/homeassistant/components/vilfo/const.py index 74eb813bcc5699..d47e738a858e5f 100644 --- a/homeassistant/components/vilfo/const.py +++ b/homeassistant/components/vilfo/const.py @@ -1,13 +1,16 @@ """Constants for the Vilfo Router integration.""" -from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ICON, + DEVICE_CLASS_TIMESTAMP, + PERCENTAGE, +) DOMAIN = "vilfo" ATTR_API_DATA_FIELD = "api_data_field" ATTR_API_DATA_FIELD_LOAD = "load" ATTR_API_DATA_FIELD_BOOT_TIME = "boot_time" -ATTR_DEVICE_CLASS = "device_class" -ATTR_ICON = "icon" ATTR_LABEL = "label" ATTR_LOAD = "load" ATTR_UNIT = "unit" diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index e2909647c2d6ee..80a14354913555 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -1,10 +1,10 @@ """Support for Vilfo Router sensors.""" +from homeassistant.const import ATTR_ICON from homeassistant.helpers.entity import Entity from .const import ( ATTR_API_DATA_FIELD, ATTR_DEVICE_CLASS, - ATTR_ICON, ATTR_LABEL, ATTR_UNIT, DOMAIN, diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index ebac158c452500..3a117955bee9c3 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -6,20 +6,8 @@ import voluptuous as vol from websockets.exceptions import ConnectionClosed -from homeassistant.components.webostv.const import ( - ATTR_BUTTON, - ATTR_COMMAND, - ATTR_PAYLOAD, - CONF_ON_ACTION, - CONF_SOURCES, - DEFAULT_NAME, - DOMAIN, - SERVICE_BUTTON, - SERVICE_COMMAND, - SERVICE_SELECT_SOUND_OUTPUT, - WEBOSTV_CONFIG_FILE, -) from homeassistant.const import ( + ATTR_COMMAND, ATTR_ENTITY_ID, CONF_CUSTOMIZE, CONF_HOST, @@ -30,7 +18,19 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import ATTR_SOUND_OUTPUT +from .const import ( + ATTR_BUTTON, + ATTR_PAYLOAD, + ATTR_SOUND_OUTPUT, + CONF_ON_ACTION, + CONF_SOURCES, + DEFAULT_NAME, + DOMAIN, + SERVICE_BUTTON, + SERVICE_COMMAND, + SERVICE_SELECT_SOUND_OUTPUT, + WEBOSTV_CONFIG_FILE, +) CUSTOMIZE_SCHEMA = vol.Schema( {vol.Optional(CONF_SOURCES, default=[]): vol.All(cv.ensure_list, [cv.string])} diff --git a/homeassistant/components/webostv/const.py b/homeassistant/components/webostv/const.py index bea485a7d68202..9091491a29da98 100644 --- a/homeassistant/components/webostv/const.py +++ b/homeassistant/components/webostv/const.py @@ -4,7 +4,6 @@ DEFAULT_NAME = "LG webOS Smart TV" ATTR_BUTTON = "button" -ATTR_COMMAND = "command" ATTR_PAYLOAD = "payload" ATTR_SOUND_OUTPUT = "sound_output" diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index a33f697abdf676..53a9427d30ac49 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -9,6 +9,7 @@ from homeassistant import config_entries, core from homeassistant.const import ( ATTR_BATTERY_LEVEL, + ATTR_DEVICE_ID, ATTR_VOLTAGE, CONF_HOST, CONF_MAC, @@ -42,7 +43,6 @@ ATTR_GW_MAC = "gw_mac" ATTR_RINGTONE_ID = "ringtone_id" ATTR_RINGTONE_VOL = "ringtone_vol" -ATTR_DEVICE_ID = "device_id" TIME_TILL_UNAVAILABLE = timedelta(minutes=150) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index c8e200516a0f80..addb1a3cbcabe9 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -47,6 +47,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, + ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_TOKEN, @@ -112,7 +113,6 @@ ATTR_MODEL = "model" # Air Purifier -ATTR_TEMPERATURE = "temperature" ATTR_HUMIDITY = "humidity" ATTR_AIR_QUALITY_INDEX = "aqi" ATTR_FILTER_HOURS_USED = "filter_hours_used" diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index d6b87000f40c59..a7cfdd788a5382 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -14,6 +14,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + ATTR_BATTERY_LEVEL, CONF_HOST, CONF_NAME, CONF_TOKEN, @@ -48,7 +49,6 @@ ATTR_POWER = "power" ATTR_CHARGING = "charging" -ATTR_BATTERY_LEVEL = "battery_level" ATTR_DISPLAY_CLOCK = "display_clock" ATTR_NIGHT_MODE = "night_mode" ATTR_NIGHT_TIME_BEGIN = "night_time_begin" diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index b290cc6a956dad..0a35e8e0a35e02 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -12,6 +12,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, + ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_TOKEN, @@ -63,7 +64,6 @@ ) ATTR_POWER = "power" -ATTR_TEMPERATURE = "temperature" ATTR_LOAD_POWER = "load_power" ATTR_MODEL = "model" ATTR_POWER_MODE = "power_mode" diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 2bd712ff681c24..45f7d540052ddc 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -11,6 +11,7 @@ import zigpy.zdo.types as zdo_types from homeassistant.components import websocket_api +from homeassistant.const import ATTR_COMMAND, ATTR_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -20,14 +21,12 @@ ATTR_ATTRIBUTE, ATTR_CLUSTER_ID, ATTR_CLUSTER_TYPE, - ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ENDPOINT_ID, ATTR_IEEE, ATTR_LEVEL, ATTR_MANUFACTURER, ATTR_MEMBERS, - ATTR_NAME, ATTR_VALUE, ATTR_WARNING_DEVICE_DURATION, ATTR_WARNING_DEVICE_MODE, diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index 2dbd1629487a31..14774c09550e33 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -8,6 +8,7 @@ import zigpy.exceptions +from homeassistant.const import ATTR_COMMAND from homeassistant.core import callback from .. import typing as zha_typing @@ -16,7 +17,6 @@ ATTR_ATTRIBUTE_ID, ATTR_ATTRIBUTE_NAME, ATTR_CLUSTER_ID, - ATTR_COMMAND, ATTR_UNIQUE_ID, ATTR_VALUE, CHANNEL_ZDO, diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 2454085d9a4f29..672fafdd98f08b 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -31,7 +31,6 @@ ATTR_AVAILABLE = "available" ATTR_CLUSTER_ID = "cluster_id" ATTR_CLUSTER_TYPE = "cluster_type" -ATTR_COMMAND = "command" ATTR_COMMAND_TYPE = "command_type" ATTR_DEVICE_IEEE = "device_ieee" ATTR_DEVICE_TYPE = "device_type" @@ -47,7 +46,6 @@ ATTR_MANUFACTURER_CODE = "manufacturer_code" ATTR_MEMBERS = "members" ATTR_MODEL = "model" -ATTR_NAME = "name" ATTR_NEIGHBORS = "neighbors" ATTR_NODE_DESCRIPTOR = "node_descriptor" ATTR_NWK = "nwk" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index cd3b1bd93cec39..4f93a7a95d68f0 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -14,6 +14,7 @@ from zigpy.zcl.clusters.general import Groups import zigpy.zdo.types as zdo_types +from homeassistant.const import ATTR_COMMAND, ATTR_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -28,7 +29,6 @@ ATTR_ATTRIBUTE, ATTR_AVAILABLE, ATTR_CLUSTER_ID, - ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_DEVICE_TYPE, ATTR_ENDPOINT_ID, @@ -40,7 +40,6 @@ ATTR_MANUFACTURER, ATTR_MANUFACTURER_CODE, ATTR_MODEL, - ATTR_NAME, ATTR_NEIGHBORS, ATTR_NODE_DESCRIPTOR, ATTR_NWK, diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index db30e9e178ce2b..3c8f0c59cb1945 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -5,6 +5,7 @@ import logging from typing import Any, Awaitable, Dict, List, Optional +from homeassistant.const import ATTR_NAME from homeassistant.core import CALLBACK_TYPE, Event, callback from homeassistant.helpers import entity from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE @@ -18,7 +19,6 @@ from .core.const import ( ATTR_MANUFACTURER, ATTR_MODEL, - ATTR_NAME, DATA_ZHA, DATA_ZHA_BRIDGE_ID, DOMAIN, diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 0ab1f786555a05..649f17ff08fd5b 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -11,6 +11,7 @@ from homeassistant import config_entries from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) @@ -103,7 +104,7 @@ RENAME_NODE_SCHEMA = vol.Schema( { vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), - vol.Required(const.ATTR_NAME): cv.string, + vol.Required(ATTR_NAME): cv.string, vol.Optional(const.ATTR_UPDATE_IDS, default=False): cv.boolean, } ) @@ -112,7 +113,7 @@ { vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int), - vol.Required(const.ATTR_NAME): cv.string, + vol.Required(ATTR_NAME): cv.string, vol.Optional(const.ATTR_UPDATE_IDS, default=False): cv.boolean, } ) @@ -661,7 +662,7 @@ async def rename_node(service): """Rename a node.""" node_id = service.data.get(const.ATTR_NODE_ID) node = network.nodes[node_id] # pylint: disable=unsubscriptable-object - name = service.data.get(const.ATTR_NAME) + name = service.data.get(ATTR_NAME) node.name = name _LOGGER.info("Renamed Z-Wave node %d to %s", node_id, name) update_ids = service.data.get(const.ATTR_UPDATE_IDS) @@ -682,7 +683,7 @@ async def rename_value(service): value_id = service.data.get(const.ATTR_VALUE_ID) node = network.nodes[node_id] # pylint: disable=unsubscriptable-object value = node.values[value_id] - name = service.data.get(const.ATTR_NAME) + name = service.data.get(ATTR_NAME) value.label = name _LOGGER.info( "Renamed Z-Wave value (Node %d Value %d) to %s", node_id, value_id, name diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py index 83fb43fd3fbe79..d11d308c4907b8 100644 --- a/homeassistant/components/zwave/const.py +++ b/homeassistant/components/zwave/const.py @@ -8,7 +8,6 @@ ATTR_GROUP = "group" ATTR_VALUE_ID = "value_id" ATTR_MESSAGES = "messages" -ATTR_NAME = "name" ATTR_RETURN_ROUTES = "return_routes" ATTR_SCENE_ID = "scene_id" ATTR_SCENE_DATA = "scene_data" diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 519771c1a49212..2d4db96088661c 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -10,7 +10,12 @@ from zwave_js_server.model.value import ValueNotification from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_DOMAIN, CONF_URL, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + ATTR_DEVICE_ID, + ATTR_DOMAIN, + CONF_URL, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry, entity_registry @@ -22,7 +27,6 @@ from .const import ( ATTR_COMMAND_CLASS, ATTR_COMMAND_CLASS_NAME, - ATTR_DEVICE_ID, ATTR_ENDPOINT, ATTR_HOME_ID, ATTR_LABEL, diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index ffd6031349a966..49beb06283e2c4 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -38,7 +38,6 @@ ATTR_COMMAND_CLASS = "command_class" ATTR_COMMAND_CLASS_NAME = "command_class_name" ATTR_TYPE = "type" -ATTR_DEVICE_ID = "device_id" ATTR_PROPERTY_NAME = "property_name" ATTR_PROPERTY_KEY_NAME = "property_key_name" ATTR_PROPERTY = "property" diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index aaed10f7814f26..3925535d06b0a7 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -112,7 +112,6 @@ ATTR_CUR = "current" ATTR_MAX = "max" -ATTR_MODE = "mode" DATA_SCRIPTS = "helpers.script" diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 70bc8274684860..3015fcccf9f480 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -12,13 +12,13 @@ ) from homeassistant.components.webostv.const import ( ATTR_BUTTON, - ATTR_COMMAND, ATTR_PAYLOAD, DOMAIN, SERVICE_BUTTON, SERVICE_COMMAND, ) from homeassistant.const import ( + ATTR_COMMAND, ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index 363aa12db6ebce..8694b59ecfb252 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -28,7 +28,6 @@ ATTR_IEEE, ATTR_MANUFACTURER, ATTR_MODEL, - ATTR_NAME, ATTR_NEIGHBORS, ATTR_QUIRK_APPLIED, CLUSTER_TYPE_IN, @@ -38,6 +37,7 @@ GROUP_IDS, GROUP_NAME, ) +from homeassistant.const import ATTR_NAME from homeassistant.core import Context from .conftest import FIXTURE_GRP_ID, FIXTURE_GRP_NAME diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index d70c3d631d529c..d64e191e1f3921 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -17,7 +17,7 @@ const, ) from homeassistant.components.zwave.binary_sensor import get_device -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg from homeassistant.helpers.entity_registry import async_get_registry @@ -506,7 +506,7 @@ def mock_connect(receiver, signal, *args, **kwargs): await hass.services.async_call( "zwave", "rename_node", - {const.ATTR_NODE_ID: node.node_id, const.ATTR_NAME: "Demo Node"}, + {const.ATTR_NODE_ID: node.node_id, ATTR_NAME: "Demo Node"}, ) await hass.async_block_till_done() @@ -537,7 +537,7 @@ def mock_connect(receiver, signal, *args, **kwargs): { const.ATTR_NODE_ID: node.node_id, const.ATTR_UPDATE_IDS: True, - const.ATTR_NAME: "New Node", + ATTR_NAME: "New Node", }, ) await hass.async_block_till_done() @@ -568,7 +568,7 @@ def mock_connect(receiver, signal, *args, **kwargs): const.ATTR_NODE_ID: node.node_id, const.ATTR_VALUE_ID: value.object_id, const.ATTR_UPDATE_IDS: True, - const.ATTR_NAME: "New Label", + ATTR_NAME: "New Label", }, ) await hass.async_block_till_done() @@ -1360,7 +1360,7 @@ async def test_rename_node(hass, mock_openzwave, zwave_setup_ready): await hass.services.async_call( "zwave", "rename_node", - {const.ATTR_NODE_ID: 11, const.ATTR_NAME: "test_name"}, + {const.ATTR_NODE_ID: 11, ATTR_NAME: "test_name"}, ) await hass.async_block_till_done() @@ -1383,7 +1383,7 @@ async def test_rename_value(hass, mock_openzwave, zwave_setup_ready): { const.ATTR_NODE_ID: 11, const.ATTR_VALUE_ID: 123456, - const.ATTR_NAME: "New Label", + ATTR_NAME: "New Label", }, ) await hass.async_block_till_done() From a547d0fea2825a7f6a9943ade9bda09e72bb1b9b Mon Sep 17 00:00:00 2001 From: Emily Mills Date: Fri, 5 Mar 2021 13:14:03 -0600 Subject: [PATCH 1040/1818] Prevent Zerproc leaving open unnecessary connections (#47401) * Zerproc: Prevent leaving open unnecessary connections * Fix config entry unloading --- homeassistant/components/zerproc/__init__.py | 6 ++++ homeassistant/components/zerproc/light.py | 33 ++++------------- .../components/zerproc/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zerproc/test_light.py | 35 +++---------------- 6 files changed, 20 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index f3d2e9daebc5d6..d6faaf7a981f63 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -20,6 +20,11 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Zerproc from a config entry.""" + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + if "addresses" not in hass.data[DOMAIN]: + hass.data[DOMAIN]["addresses"] = set() + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) @@ -30,6 +35,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + hass.data.pop(DOMAIN, None) return all( await asyncio.gather( *[ diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index 89f60faf84ebb4..bf9f917f230d5b 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -1,5 +1,4 @@ """Zerproc light platform.""" -import asyncio from datetime import timedelta import logging from typing import Callable, List, Optional @@ -30,16 +29,6 @@ DISCOVERY_INTERVAL = timedelta(seconds=60) -async def connect_light(light: pyzerproc.Light) -> Optional[pyzerproc.Light]: - """Return the given light if it connects successfully.""" - try: - await light.connect() - except pyzerproc.ZerprocException: - _LOGGER.debug("Unable to connect to '%s'", light.address, exc_info=True) - return None - return light - - async def discover_entities(hass: HomeAssistant) -> List[Entity]: """Attempt to discover new lights.""" lights = await pyzerproc.discover() @@ -50,14 +39,9 @@ async def discover_entities(hass: HomeAssistant) -> List[Entity]: ] entities = [] - connected_lights = filter( - None, await asyncio.gather(*(connect_light(light) for light in new_lights)) - ) - for light in connected_lights: - # Double-check the light hasn't been added in the meantime - if light.address not in hass.data[DOMAIN]["addresses"]: - hass.data[DOMAIN]["addresses"].add(light.address) - entities.append(ZerprocLight(light)) + for light in new_lights: + hass.data[DOMAIN]["addresses"].add(light.address) + entities.append(ZerprocLight(light)) return entities @@ -68,11 +52,6 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Zerproc light devices.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if "addresses" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["addresses"] = set() - warned = False async def discover(*args): @@ -120,7 +99,7 @@ async def async_will_remove_from_hass(self, *args) -> None: await self._light.disconnect() except pyzerproc.ZerprocException: _LOGGER.debug( - "Exception disconnected from %s", self.entity_id, exc_info=True + "Exception disconnecting from %s", self._light.address, exc_info=True ) @property @@ -198,11 +177,11 @@ async def async_update(self): state = await self._light.get_state() except pyzerproc.ZerprocException: if self._available: - _LOGGER.warning("Unable to connect to %s", self.entity_id) + _LOGGER.warning("Unable to connect to %s", self._light.address) self._available = False return if self._available is False: - _LOGGER.info("Reconnected to %s", self.entity_id) + _LOGGER.info("Reconnected to %s", self._light.address) self._available = True self._is_on = state.is_on hsv = color_util.color_RGB_to_hsv(*state.color) diff --git a/homeassistant/components/zerproc/manifest.json b/homeassistant/components/zerproc/manifest.json index 54b70d78673f83..d2d00987ab7a38 100644 --- a/homeassistant/components/zerproc/manifest.json +++ b/homeassistant/components/zerproc/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zerproc", "requirements": [ - "pyzerproc==0.4.7" + "pyzerproc==0.4.8" ], "codeowners": [ "@emlove" diff --git a/requirements_all.txt b/requirements_all.txt index f52f9b7e353ffc..9b00a2b7267528 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1920,7 +1920,7 @@ pyxeoma==1.4.1 pyzbar==0.1.7 # homeassistant.components.zerproc -pyzerproc==0.4.7 +pyzerproc==0.4.8 # homeassistant.components.qnap qnapstats==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b60ce427b00096..02bc7304084938 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -993,7 +993,7 @@ pywemo==0.6.3 pywilight==0.0.68 # homeassistant.components.zerproc -pyzerproc==0.4.7 +pyzerproc==0.4.8 # homeassistant.components.rachio rachiopy==1.0.3 diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 1f0c7652bfd69c..1dc608e18df590 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -118,6 +118,8 @@ async def test_init(hass, mock_entry): assert mock_light_1.disconnect.called assert mock_light_2.disconnect.called + assert hass.data[DOMAIN]["addresses"] == {"AA:BB:CC:DD:EE:FF", "11:22:33:44:55:66"} + async def test_discovery_exception(hass, mock_entry): """Test platform setup.""" @@ -136,42 +138,15 @@ async def test_discovery_exception(hass, mock_entry): assert len(hass.data[DOMAIN]["addresses"]) == 0 -async def test_connect_exception(hass, mock_entry): - """Test platform setup.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - mock_entry.add_to_hass(hass) - - mock_light_1 = MagicMock(spec=pyzerproc.Light) - mock_light_1.address = "AA:BB:CC:DD:EE:FF" - mock_light_1.name = "LEDBlue-CCDDEEFF" - mock_light_1.is_connected.return_value = False - - mock_light_2 = MagicMock(spec=pyzerproc.Light) - mock_light_2.address = "11:22:33:44:55:66" - mock_light_2.name = "LEDBlue-33445566" - mock_light_2.is_connected.return_value = False - - with patch( - "homeassistant.components.zerproc.light.pyzerproc.discover", - return_value=[mock_light_1, mock_light_2], - ), patch.object( - mock_light_1, "connect", side_effect=pyzerproc.ZerprocException("TEST") - ): - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - # The exception connecting to light 1 should be captured, but light 2 - # should still be added - assert len(hass.data[DOMAIN]["addresses"]) == 1 - - async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" + assert hass.data[DOMAIN]["addresses"] == {"AA:BB:CC:DD:EE:FF"} + with patch.object(mock_light, "disconnect") as mock_disconnect: await hass.config_entries.async_remove(mock_entry.entry_id) assert mock_disconnect.called + assert DOMAIN not in hass.data async def test_remove_entry_exceptions_caught(hass, mock_light, mock_entry): From a2ee7d598bb02dcf9f4efb9ce4521b8da7b6607e Mon Sep 17 00:00:00 2001 From: functionpointer Date: Fri, 5 Mar 2021 20:21:24 +0100 Subject: [PATCH 1041/1818] Use conn_made callback in MySensors (#47463) --- homeassistant/components/mysensors/const.py | 1 - homeassistant/components/mysensors/gateway.py | 42 ++++++++----------- homeassistant/components/mysensors/handler.py | 23 +--------- 3 files changed, 18 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 700c2bb930ae0a..1b8fa5e24e8d7d 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -29,7 +29,6 @@ DOMAIN: str = "mysensors" -MYSENSORS_GATEWAY_READY: str = "mysensors_gateway_ready_{}" MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}" MYSENSORS_GATEWAYS: str = "mysensors_gateways" PLATFORM: str = "platform" diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 4267ba5cbb3938..b6797cafb37b8c 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -26,7 +26,6 @@ CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, - MYSENSORS_GATEWAY_READY, MYSENSORS_GATEWAY_START_TASK, MYSENSORS_GATEWAYS, GatewayId, @@ -36,7 +35,7 @@ _LOGGER = logging.getLogger(__name__) -GATEWAY_READY_TIMEOUT = 15.0 +GATEWAY_READY_TIMEOUT = 20.0 MQTT_COMPONENT = "mqtt" @@ -64,24 +63,16 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo if user_input[CONF_DEVICE] == MQTT_COMPONENT: return True # dont validate mqtt. mqtt gateways dont send ready messages :( try: - gateway_ready = asyncio.Future() - - def gateway_ready_callback(msg): - msg_type = msg.gateway.const.MessageType(msg.type) - _LOGGER.debug("Received MySensors msg type %s: %s", msg_type.name, msg) - if msg_type.name != "internal": - return - internal = msg.gateway.const.Internal(msg.sub_type) - if internal.name != "I_GATEWAY_READY": - return - _LOGGER.debug("Received gateway ready") - gateway_ready.set_result(True) + gateway_ready = asyncio.Event() + + def on_conn_made(_: BaseAsyncGateway) -> None: + gateway_ready.set() gateway: Optional[BaseAsyncGateway] = await _get_gateway( hass, device=user_input[CONF_DEVICE], version=user_input[CONF_VERSION], - event_callback=gateway_ready_callback, + event_callback=lambda _: None, persistence_file=None, baud_rate=user_input.get(CONF_BAUD_RATE), tcp_port=user_input.get(CONF_TCP_PORT), @@ -92,12 +83,13 @@ def gateway_ready_callback(msg): ) if gateway is None: return False + gateway.on_conn_made = on_conn_made connect_task = None try: connect_task = asyncio.create_task(gateway.start()) - with async_timeout.timeout(20): - await gateway_ready + with async_timeout.timeout(GATEWAY_READY_TIMEOUT): + await gateway_ready.wait() return True except asyncio.TimeoutError: _LOGGER.info("Try gateway connect failed with timeout") @@ -280,6 +272,12 @@ async def _gw_start( hass: HomeAssistantType, entry: ConfigEntry, gateway: BaseAsyncGateway ): """Start the gateway.""" + gateway_ready = asyncio.Event() + + def gateway_connected(_: BaseAsyncGateway): + gateway_ready.set() + + gateway.on_conn_made = gateway_connected # Don't use hass.async_create_task to avoid holding up setup indefinitely. hass.data[DOMAIN][ MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) @@ -294,21 +292,15 @@ async def stop_this_gw(_: Event): if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return - gateway_ready = asyncio.Future() - gateway_ready_key = MYSENSORS_GATEWAY_READY.format(entry.entry_id) - hass.data[DOMAIN][gateway_ready_key] = gateway_ready - try: with async_timeout.timeout(GATEWAY_READY_TIMEOUT): - await gateway_ready + await gateway_ready.wait() except asyncio.TimeoutError: _LOGGER.warning( - "Gateway %s not ready after %s secs so continuing with setup", + "Gateway %s not connected after %s secs so continuing with setup", entry.data[CONF_DEVICE], GATEWAY_READY_TIMEOUT, ) - finally: - hass.data[DOMAIN].pop(gateway_ready_key, None) def _gw_callback_factory( diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index 10165a171e0a03..a47c9174b23bf8 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -8,14 +8,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import decorator -from .const import ( - CHILD_CALLBACK, - DOMAIN, - MYSENSORS_GATEWAY_READY, - NODE_CALLBACK, - DevId, - GatewayId, -) +from .const import CHILD_CALLBACK, NODE_CALLBACK, DevId, GatewayId from .device import get_mysensors_devices from .helpers import discover_mysensors_platform, validate_set_msg @@ -75,20 +68,6 @@ async def handle_sketch_version( _handle_node_update(hass, gateway_id, msg) -@HANDLERS.register("I_GATEWAY_READY") -async def handle_gateway_ready( - hass: HomeAssistantType, gateway_id: GatewayId, msg: Message -) -> None: - """Handle an internal gateway ready message. - - Set asyncio future result if gateway is ready. - """ - gateway_ready = hass.data[DOMAIN].get(MYSENSORS_GATEWAY_READY.format(gateway_id)) - if gateway_ready is None or gateway_ready.cancelled(): - return - gateway_ready.set_result(True) - - @callback def _handle_child_update( hass: HomeAssistantType, gateway_id: GatewayId, validated: Dict[str, List[DevId]] From ab0a5bccabf786118ec16d105d8a92c3a53cf95d Mon Sep 17 00:00:00 2001 From: mvn23 Date: Fri, 5 Mar 2021 20:22:40 +0100 Subject: [PATCH 1042/1818] Update pyotgw to 1.1b1 (#47446) --- homeassistant/components/opentherm_gw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 066cee61c050be..baa02dc3f4604c 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -2,7 +2,7 @@ "domain": "opentherm_gw", "name": "OpenTherm Gateway", "documentation": "https://www.home-assistant.io/integrations/opentherm_gw", - "requirements": ["pyotgw==1.0b1"], + "requirements": ["pyotgw==1.1b1"], "codeowners": ["@mvn23"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 9b00a2b7267528..64071253d2fa2a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1600,7 +1600,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==1.0b1 +pyotgw==1.1b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 02bc7304084938..9d50753b9447f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -848,7 +848,7 @@ pyopenuv==1.0.9 pyopnsense==0.2.0 # homeassistant.components.opentherm_gw -pyotgw==1.0b1 +pyotgw==1.1b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From 61be29117d38962e2f16fce2b35906840f8760b1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 20:51:49 +0100 Subject: [PATCH 1043/1818] Deprecate HomeKit auto start (#47470) --- homeassistant/components/homekit/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 0dfe7336bcf45f..e6d3a8abff426b 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -131,6 +131,7 @@ def _has_all_unique_names_and_ports(bridges): BRIDGE_SCHEMA = vol.All( cv.deprecated(CONF_ZEROCONF_DEFAULT_INTERFACE), cv.deprecated(CONF_SAFE_MODE), + cv.deprecated(CONF_AUTO_START), vol.Schema( { vol.Optional(CONF_HOMEKIT_MODE, default=DEFAULT_HOMEKIT_MODE): vol.In( From 6debf52e9b9c6311045bf7d31f4a5a50b3f28fdf Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:57:06 -0500 Subject: [PATCH 1044/1818] Update zwave_js.refresh_value service description (#47469) --- homeassistant/components/zwave_js/services.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index 8e6d907fc96dc7..7277f540d7682e 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -70,10 +70,15 @@ set_config_parameter: refresh_value: name: Refresh value(s) of a Z-Wave entity description: Force update value(s) for a Z-Wave entity - target: - entity: - integration: zwave_js fields: + entity_id: + name: Entity + description: Entity whose value(s) should be refreshed + required: true + example: sensor.family_room_motion + selector: + entity: + integration: zwave_js refresh_all_values: name: Refresh all values? description: Whether to refresh all values (true) or just the primary value (false) From 7c08592b5af11132c4d75664262041f2946ffecf Mon Sep 17 00:00:00 2001 From: Emily Mills Date: Fri, 5 Mar 2021 14:24:55 -0600 Subject: [PATCH 1045/1818] Convert kulersky to use new async backend (#47403) --- .../components/kulersky/config_flow.py | 4 +- homeassistant/components/kulersky/light.py | 105 +++---- .../components/kulersky/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/kulersky/test_config_flow.py | 18 +- tests/components/kulersky/test_light.py | 266 ++++++------------ 7 files changed, 135 insertions(+), 264 deletions(-) diff --git a/homeassistant/components/kulersky/config_flow.py b/homeassistant/components/kulersky/config_flow.py index 04f7719b8e6d92..2a11a3c2e178e9 100644 --- a/homeassistant/components/kulersky/config_flow.py +++ b/homeassistant/components/kulersky/config_flow.py @@ -15,9 +15,7 @@ async def _async_has_devices(hass) -> bool: """Return if there are devices that can be discovered.""" # Check if there are any devices that can be discovered in the network. try: - devices = await hass.async_add_executor_job( - pykulersky.discover_bluetooth_devices - ) + devices = await pykulersky.discover() except pykulersky.PykulerskyException as exc: _LOGGER.error("Unable to discover nearby Kuler Sky devices: %s", exc) return False diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 71dd4a158ca134..9098975d500133 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -1,5 +1,4 @@ """Kuler Sky light platform.""" -import asyncio from datetime import timedelta import logging from typing import Callable, List @@ -17,6 +16,7 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType @@ -30,14 +30,6 @@ DISCOVERY_INTERVAL = timedelta(seconds=60) -PARALLEL_UPDATES = 0 - - -def check_light(light: pykulersky.Light): - """Attempt to connect to this light and read the color.""" - light.connect() - light.get_color() - async def async_setup_entry( hass: HomeAssistantType, @@ -47,45 +39,26 @@ async def async_setup_entry( """Set up Kuler sky light devices.""" if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - if "devices" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["devices"] = set() - if "discovery" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["discovery"] = asyncio.Lock() + if "addresses" not in hass.data[DOMAIN]: + hass.data[DOMAIN]["addresses"] = set() async def discover(*args): """Attempt to discover new lights.""" - # Since discovery needs to connect to all discovered bluetooth devices, and - # only rules out devices after a timeout, it can potentially take a long - # time. If there's already a discovery running, just skip this poll. - if hass.data[DOMAIN]["discovery"].locked(): - return + lights = await pykulersky.discover() - async with hass.data[DOMAIN]["discovery"]: - bluetooth_devices = await hass.async_add_executor_job( - pykulersky.discover_bluetooth_devices - ) + # Filter out already discovered lights + new_lights = [ + light + for light in lights + if light.address not in hass.data[DOMAIN]["addresses"] + ] + + new_entities = [] + for light in new_lights: + hass.data[DOMAIN]["addresses"].add(light.address) + new_entities.append(KulerskyLight(light)) - # Filter out already connected lights - new_devices = [ - device - for device in bluetooth_devices - if device["address"] not in hass.data[DOMAIN]["devices"] - ] - - for device in new_devices: - light = pykulersky.Light(device["address"], device["name"]) - try: - # If the connection fails, either this is not a Kuler Sky - # light, or it's bluetooth connection is currently locked - # by another device. If the vendor's app is connected to - # the light when home assistant tries to connect, this - # connection will fail. - await hass.async_add_executor_job(check_light, light) - except pykulersky.PykulerskyException: - continue - # The light has successfully connected - hass.data[DOMAIN]["devices"].add(device["address"]) - async_add_entities([KulerskyLight(light)], update_before_add=True) + async_add_entities(new_entities, update_before_add=True) # Start initial discovery hass.async_create_task(discover()) @@ -94,6 +67,11 @@ async def discover(*args): async_track_time_interval(hass, discover, DISCOVERY_INTERVAL) +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Cleanup the Kuler sky integration.""" + hass.data.pop(DOMAIN, None) + + class KulerskyLight(LightEntity): """Representation of an Kuler Sky Light.""" @@ -103,21 +81,24 @@ def __init__(self, light: pykulersky.Light): self._hs_color = None self._brightness = None self._white_value = None - self._available = True + self._available = None async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" self.async_on_remove( - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.disconnect) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self.async_will_remove_from_hass + ) ) - async def async_will_remove_from_hass(self) -> None: + async def async_will_remove_from_hass(self, *args) -> None: """Run when entity will be removed from hass.""" - await self.hass.async_add_executor_job(self.disconnect) - - def disconnect(self, *args) -> None: - """Disconnect the underlying device.""" - self._light.disconnect() + try: + await self._light.disconnect() + except pykulersky.PykulerskyException: + _LOGGER.debug( + "Exception disconnected from %s", self._light.address, exc_info=True + ) @property def name(self): @@ -168,7 +149,7 @@ def available(self) -> bool: """Return True if entity is available.""" return self._available - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Instruct the light to turn on.""" default_hs = (0, 0) if self._hs_color is None else self._hs_color hue_sat = kwargs.get(ATTR_HS_COLOR, default_hs) @@ -187,28 +168,28 @@ def turn_on(self, **kwargs): rgb = color_util.color_hsv_to_RGB(*hue_sat, brightness / 255 * 100) - self._light.set_color(*rgb, white_value) + await self._light.set_color(*rgb, white_value) - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Instruct the light to turn off.""" - self._light.set_color(0, 0, 0, 0) + await self._light.set_color(0, 0, 0, 0) - def update(self): + async def async_update(self): """Fetch new state data for this light.""" try: - if not self._light.connected: - self._light.connect() + if not self._available: + await self._light.connect() # pylint: disable=invalid-name - r, g, b, w = self._light.get_color() + r, g, b, w = await self._light.get_color() except pykulersky.PykulerskyException as exc: if self._available: _LOGGER.warning("Unable to connect to %s: %s", self._light.address, exc) self._available = False return - if not self._available: - _LOGGER.info("Reconnected to %s", self.entity_id) - self._available = True + if self._available is False: + _LOGGER.info("Reconnected to %s", self._light.address) + self._available = True hsv = color_util.color_RGB_to_hsv(r, g, b) self._hs_color = hsv[:2] self._brightness = int(round((hsv[2] / 100) * 255)) diff --git a/homeassistant/components/kulersky/manifest.json b/homeassistant/components/kulersky/manifest.json index 4f445e4fc18caa..b690d94e8d4a5b 100644 --- a/homeassistant/components/kulersky/manifest.json +++ b/homeassistant/components/kulersky/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/kulersky", "requirements": [ - "pykulersky==0.4.0" + "pykulersky==0.5.2" ], "codeowners": [ "@emlove" diff --git a/requirements_all.txt b/requirements_all.txt index 64071253d2fa2a..5253f58a9ee5b2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1477,7 +1477,7 @@ pykmtronic==0.0.3 pykodi==0.2.1 # homeassistant.components.kulersky -pykulersky==0.4.0 +pykulersky==0.5.2 # homeassistant.components.kwb pykwb==0.0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9d50753b9447f9..45c06066e20070 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -779,7 +779,7 @@ pykmtronic==0.0.3 pykodi==0.2.1 # homeassistant.components.kulersky -pykulersky==0.4.0 +pykulersky==0.5.2 # homeassistant.components.lastfm pylast==4.1.0 diff --git a/tests/components/kulersky/test_config_flow.py b/tests/components/kulersky/test_config_flow.py index 24f3f9a010e4b7..c6933a01d3a914 100644 --- a/tests/components/kulersky/test_config_flow.py +++ b/tests/components/kulersky/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Kuler Sky config flow.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pykulersky @@ -16,14 +16,12 @@ async def test_flow_success(hass): assert result["type"] == "form" assert result["errors"] is None + light = MagicMock(spec=pykulersky.Light) + light.address = "AA:BB:CC:11:22:33" + light.name = "Bedroom" with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover_bluetooth_devices", - return_value=[ - { - "address": "AA:BB:CC:11:22:33", - "name": "Bedroom", - } - ], + "homeassistant.components.kulersky.config_flow.pykulersky.discover", + return_value=[light], ), patch( "homeassistant.components.kulersky.async_setup", return_value=True ) as mock_setup, patch( @@ -54,7 +52,7 @@ async def test_flow_no_devices_found(hass): assert result["errors"] is None with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover_bluetooth_devices", + "homeassistant.components.kulersky.config_flow.pykulersky.discover", return_value=[], ), patch( "homeassistant.components.kulersky.async_setup", return_value=True @@ -84,7 +82,7 @@ async def test_flow_exceptions_caught(hass): assert result["errors"] is None with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover_bluetooth_devices", + "homeassistant.components.kulersky.config_flow.pykulersky.discover", side_effect=pykulersky.PykulerskyException("TEST"), ), patch( "homeassistant.components.kulersky.async_setup", return_value=True diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index fd5db92908bc5c..9dd13fad18b32a 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -1,5 +1,4 @@ """Test the Kuler Sky lights.""" -import asyncio from unittest.mock import MagicMock, patch import pykulersky @@ -45,28 +44,17 @@ async def mock_light(hass, mock_entry): light = MagicMock(spec=pykulersky.Light) light.address = "AA:BB:CC:11:22:33" light.name = "Bedroom" - light.connected = False + light.connect.return_value = True + light.get_color.return_value = (0, 0, 0, 0) with patch( - "homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices", - return_value=[ - { - "address": "AA:BB:CC:11:22:33", - "name": "Bedroom", - } - ], + "homeassistant.components.kulersky.light.pykulersky.discover", + return_value=[light], ): - with patch( - "homeassistant.components.kulersky.light.pykulersky.Light", - return_value=light, - ), patch.object(light, "connect") as mock_connect, patch.object( - light, "get_color", return_value=(0, 0, 0, 0) - ): - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - assert mock_connect.called - light.connected = True + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + assert light.connect.called yield light @@ -82,113 +70,34 @@ async def test_init(hass, mock_light): | SUPPORT_WHITE_VALUE, } - with patch.object(hass.loop, "stop"), patch.object( - mock_light, "disconnect" - ) as mock_disconnect: + with patch.object(hass.loop, "stop"): await hass.async_stop() await hass.async_block_till_done() - assert mock_disconnect.called - - -async def test_discovery_lock(hass, mock_entry): - """Test discovery lock.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - discovery_finished = None - first_discovery_started = asyncio.Event() - - async def mock_discovery(*args): - """Block to simulate multiple discovery calls while one still running.""" - nonlocal discovery_finished - if discovery_finished: - first_discovery_started.set() - await discovery_finished.wait() - return [] - - with patch( - "homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices", - return_value=[], - ), patch( - "homeassistant.components.kulersky.light.async_track_time_interval", - ) as mock_track_time_interval: - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - with patch.object( - hass, "async_add_executor_job", side_effect=mock_discovery - ) as mock_run_discovery: - discovery_coroutine = mock_track_time_interval.call_args[0][1] - - discovery_finished = asyncio.Event() - - # Schedule multiple discoveries - hass.async_create_task(discovery_coroutine()) - hass.async_create_task(discovery_coroutine()) - hass.async_create_task(discovery_coroutine()) + assert mock_light.disconnect.called - # Wait until the first discovery call is blocked - await first_discovery_started.wait() - # Unblock the first discovery - discovery_finished.set() +async def test_remove_entry(hass, mock_light, mock_entry): + """Test platform setup.""" + await hass.config_entries.async_remove(mock_entry.entry_id) - # Flush the remaining jobs - await hass.async_block_till_done() + assert mock_light.disconnect.called - # The discovery method should only have been called once - mock_run_discovery.assert_called_once() +async def test_remove_entry_exceptions_caught(hass, mock_light, mock_entry): + """Assert that disconnect exceptions are caught.""" + mock_light.disconnect.side_effect = pykulersky.PykulerskyException("Mock error") + await hass.config_entries.async_remove(mock_entry.entry_id) -async def test_discovery_connection_error(hass, mock_entry): - """Test that invalid devices are skipped.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - light = MagicMock(spec=pykulersky.Light) - light.address = "AA:BB:CC:11:22:33" - light.name = "Bedroom" - light.connected = False - with patch( - "homeassistant.components.kulersky.light.pykulersky.discover_bluetooth_devices", - return_value=[ - { - "address": "AA:BB:CC:11:22:33", - "name": "Bedroom", - } - ], - ): - with patch( - "homeassistant.components.kulersky.light.pykulersky.Light" - ) as mockdevice, patch.object( - light, "connect", side_effect=pykulersky.PykulerskyException - ): - mockdevice.return_value = light - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) - await hass.async_block_till_done() - - # Assert entity was not added - state = hass.states.get("light.bedroom") - assert state is None - - -async def test_remove_entry(hass, mock_light, mock_entry): - """Test platform setup.""" - with patch.object(mock_light, "disconnect") as mock_disconnect: - await hass.config_entries.async_remove(mock_entry.entry_id) - - assert mock_disconnect.called + assert mock_light.disconnect.called async def test_update_exception(hass, mock_light): """Test platform setup.""" await setup.async_setup_component(hass, "persistent_notification", {}) - with patch.object( - mock_light, "get_color", side_effect=pykulersky.PykulerskyException - ): - await hass.helpers.entity_component.async_update_entity("light.bedroom") + mock_light.get_color.side_effect = pykulersky.PykulerskyException + await hass.helpers.entity_component.async_update_entity("light.bedroom") state = hass.states.get("light.bedroom") assert state is not None assert state.state == STATE_UNAVAILABLE @@ -196,69 +105,59 @@ async def test_update_exception(hass, mock_light): async def test_light_turn_on(hass, mock_light): """Test KulerSkyLight turn_on.""" - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(255, 255, 255, 255) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom"}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(255, 255, 255, 255) - - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(50, 50, 50, 255) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 50}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(50, 50, 50, 255) - - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(50, 45, 25, 255) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (50, 50)}, - blocking=True, - ) - await hass.async_block_till_done() - - mock_set_color.assert_called_with(50, 45, 25, 255) - - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(220, 201, 110, 180) - ): - await hass.services.async_call( - "light", - "turn_on", - {ATTR_ENTITY_ID: "light.bedroom", ATTR_WHITE_VALUE: 180}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(50, 45, 25, 180) + mock_light.get_color.return_value = (255, 255, 255, 255) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(255, 255, 255, 255) + + mock_light.get_color.return_value = (50, 50, 50, 255) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom", ATTR_BRIGHTNESS: 50}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(50, 50, 50, 255) + + mock_light.get_color.return_value = (50, 45, 25, 255) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom", ATTR_HS_COLOR: (50, 50)}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_light.set_color.assert_called_with(50, 45, 25, 255) + + mock_light.get_color.return_value = (220, 201, 110, 180) + await hass.services.async_call( + "light", + "turn_on", + {ATTR_ENTITY_ID: "light.bedroom", ATTR_WHITE_VALUE: 180}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(50, 45, 25, 180) async def test_light_turn_off(hass, mock_light): """Test KulerSkyLight turn_on.""" - with patch.object(mock_light, "set_color") as mock_set_color, patch.object( - mock_light, "get_color", return_value=(0, 0, 0, 0) - ): - await hass.services.async_call( - "light", - "turn_off", - {ATTR_ENTITY_ID: "light.bedroom"}, - blocking=True, - ) - await hass.async_block_till_done() - mock_set_color.assert_called_with(0, 0, 0, 0) + mock_light.get_color.return_value = (0, 0, 0, 0) + await hass.services.async_call( + "light", + "turn_off", + {ATTR_ENTITY_ID: "light.bedroom"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_light.set_color.assert_called_with(0, 0, 0, 0) async def test_light_update(hass, mock_light): @@ -275,12 +174,10 @@ async def test_light_update(hass, mock_light): } # Test an exception during discovery - with patch.object( - mock_light, "get_color", side_effect=pykulersky.PykulerskyException("TEST") - ): - utcnow = utcnow + SCAN_INTERVAL - async_fire_time_changed(hass, utcnow) - await hass.async_block_till_done() + mock_light.get_color.side_effect = pykulersky.PykulerskyException("TEST") + utcnow = utcnow + SCAN_INTERVAL + async_fire_time_changed(hass, utcnow) + await hass.async_block_till_done() state = hass.states.get("light.bedroom") assert state.state == STATE_UNAVAILABLE @@ -291,14 +188,11 @@ async def test_light_update(hass, mock_light): | SUPPORT_WHITE_VALUE, } - with patch.object( - mock_light, - "get_color", - return_value=(80, 160, 200, 240), - ): - utcnow = utcnow + SCAN_INTERVAL - async_fire_time_changed(hass, utcnow) - await hass.async_block_till_done() + mock_light.get_color.side_effect = None + mock_light.get_color.return_value = (80, 160, 200, 240) + utcnow = utcnow + SCAN_INTERVAL + async_fire_time_changed(hass, utcnow) + await hass.async_block_till_done() state = hass.states.get("light.bedroom") assert state.state == STATE_ON From 793929f2ea12f2c04b88bc9be9f82c7a09f55b4a Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Mar 2021 21:28:41 +0100 Subject: [PATCH 1046/1818] Increase test coverage of UniFi integration (#46347) * Increase coverage of init * Increase coverage of config_flow * Improve coverage of controller * Minor improvement to switch test * Fix review comment * Mock websocket class * Replace the rest of the old websocket event tests * Improve websocket fixture for cleaner tests * Fix typing * Improve connection state signalling based on Martins feedback * Improve tests of reconnection_mechanisms based on Martins review comments * Fix unload entry * Fix isort issue after rebase * Fix martins comment on not using caplog * Fix wireless clients test * Fix martins comments on wireless clients test --- homeassistant/components/unifi/config_flow.py | 5 +- homeassistant/components/unifi/controller.py | 3 +- tests/components/unifi/conftest.py | 21 ++ tests/components/unifi/test_config_flow.py | 32 +++ tests/components/unifi/test_controller.py | 169 +++++++++++-- tests/components/unifi/test_device_tracker.py | 232 ++++++++++++------ tests/components/unifi/test_init.py | 83 +++++-- tests/components/unifi/test_sensor.py | 28 ++- tests/components/unifi/test_switch.py | 104 ++++---- 9 files changed, 498 insertions(+), 179 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 5a0a4969f09f27..6d8c37e8b04b58 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -123,8 +123,9 @@ async def async_step_user(self, user_input=None): return await self.async_step_site() - host = self.config.get(CONF_HOST) - if not host and await async_discover_unifi(self.hass): + if not (host := self.config.get(CONF_HOST, "")) and await async_discover_unifi( + self.hass + ): host = "unifi" data = self.reauth_schema or { diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 45268a341e1d9f..7c95058e8e4269 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -415,9 +415,8 @@ async def async_config_entry_updated(hass, config_entry) -> None: If config entry is updated due to reauth flow the entry might already have been reset and thus is not available. """ - if config_entry.entry_id not in hass.data[UNIFI_DOMAIN]: + if not (controller := hass.data[UNIFI_DOMAIN].get(config_entry.entry_id)): return - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.load_config_entry_options() async_dispatcher_send(hass, controller.signal_options_update) diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index b0491a9fa2ad76..83dc99fdaf87cb 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -1,9 +1,30 @@ """Fixtures for UniFi methods.""" +from typing import Optional from unittest.mock import patch +from aiounifi.websocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA import pytest +@pytest.fixture(autouse=True) +def mock_unifi_websocket(): + """No real websocket allowed.""" + with patch("aiounifi.controller.WSClient") as mock: + + def make_websocket_call(data: Optional[dict] = None, state: str = ""): + """Generate a websocket call.""" + if data: + mock.return_value.data = data + mock.call_args[1]["callback"](SIGNAL_DATA) + elif state: + mock.return_value.state = state + mock.call_args[1]["callback"](SIGNAL_CONNECTION_STATE) + else: + raise NotImplementedError + + yield make_websocket_call + + @pytest.fixture(autouse=True) def mock_discovery(): """No real network traffic allowed.""" diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index a28f5f5f7c5b2c..106c18524142aa 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -1,9 +1,12 @@ """Test UniFi config flow.""" + +import socket from unittest.mock import patch import aiounifi from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.unifi.config_flow import async_discover_unifi from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_UPTIME_SENSORS, @@ -151,6 +154,23 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): } +async def test_flow_works_negative_discovery(hass, aioclient_mock, mock_discovery): + """Test config flow with a negative outcome of async_discovery_unifi.""" + result = await hass.config_entries.flow.async_init( + UNIFI_DOMAIN, context={"source": "user"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["data_schema"]({CONF_USERNAME: "", CONF_PASSWORD: ""}) == { + CONF_HOST: "", + CONF_USERNAME: "", + CONF_PASSWORD: "", + CONF_PORT: 443, + CONF_VERIFY_SSL: False, + } + + async def test_flow_multiple_sites(hass, aioclient_mock): """Test config flow works when finding multiple sites.""" result = await hass.config_entries.flow.async_init( @@ -617,3 +637,15 @@ async def test_form_ssdp_gets_form_with_ignored_entry(hass): "host": "1.2.3.4", "site": "default", } + + +async def test_discover_unifi_positive(hass): + """Verify positive run of UniFi discovery.""" + with patch("socket.gethostbyname", return_value=True): + assert await async_discover_unifi(hass) + + +async def test_discover_unifi_negative(hass): + """Verify negative run of UniFi discovery.""" + with patch("socket.gethostbyname", side_effect=socket.gaierror): + assert await async_discover_unifi(hass) is None diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 6b40df5857fa18..50d464d23c0e14 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -1,10 +1,12 @@ """Test UniFi Controller.""" +import asyncio from copy import deepcopy from datetime import timedelta -from unittest.mock import patch +from unittest.mock import Mock, patch import aiounifi +from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING import pytest from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -13,6 +15,8 @@ from homeassistant.components.unifi.const import ( CONF_CONTROLLER, CONF_SITE_ID, + CONF_TRACK_CLIENTS, + CONF_TRACK_DEVICES, DEFAULT_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS, DEFAULT_DETECTION_TIME, @@ -22,7 +26,11 @@ DOMAIN as UNIFI_DOMAIN, UNIFI_WIRELESS_CLIENTS, ) -from homeassistant.components.unifi.controller import PLATFORMS, get_controller +from homeassistant.components.unifi.controller import ( + PLATFORMS, + RETRY_TIMER, + get_controller, +) from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.const import ( CONF_HOST, @@ -32,10 +40,13 @@ CONF_VERIFY_SSL, CONTENT_TYPE_JSON, ) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed +DEFAULT_CONFIG_ENTRY_ID = 1 DEFAULT_HOST = "1.2.3.4" DEFAULT_SITE = "site_id" @@ -154,6 +165,7 @@ async def setup_unifi_integration( wlans_response=None, known_wireless_clients=None, controllers=None, + unique_id="1", ): """Create the UniFi controller.""" assert await async_setup_component(hass, UNIFI_DOMAIN, {}) @@ -162,8 +174,8 @@ async def setup_unifi_integration( domain=UNIFI_DOMAIN, data=deepcopy(config), options=deepcopy(options), - entry_id=1, - unique_id="1", + unique_id=unique_id, + entry_id=DEFAULT_CONFIG_ENTRY_ID, version=1, ) config_entry.add_to_hass(hass) @@ -188,8 +200,7 @@ async def setup_unifi_integration( wlans_response=wlans_response, ) - with patch.object(aiounifi.websocket.WSClient, "start", return_value=True): - await hass.config_entries.async_setup(config_entry.entry_id) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() if config_entry.entry_id not in hass.data[UNIFI_DOMAIN]: @@ -276,6 +287,27 @@ async def test_controller_unknown_error(hass): assert hass.data[UNIFI_DOMAIN] == {} +async def test_config_entry_updated(hass, aioclient_mock): + """Calling reset when the entry has been setup.""" + config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + + event_call = Mock() + unsub = async_dispatcher_connect(hass, controller.signal_options_update, event_call) + + hass.config_entries.async_update_entry( + config_entry, options={CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False} + ) + await hass.async_block_till_done() + + assert config_entry.options[CONF_TRACK_CLIENTS] is False + assert config_entry.options[CONF_TRACK_DEVICES] is False + + event_call.assert_called_once() + + unsub() + + async def test_reset_after_successful_setup(hass, aioclient_mock): """Calling reset when the entry has been setup.""" config_entry = await setup_unifi_integration(hass, aioclient_mock) @@ -290,33 +322,126 @@ async def test_reset_after_successful_setup(hass, aioclient_mock): assert len(controller.listeners) == 0 +async def test_reset_fails(hass, aioclient_mock): + """Calling reset when the entry has been setup can return false.""" + config_entry = await setup_unifi_integration(hass, aioclient_mock) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + + with patch( + "homeassistant.config_entries.ConfigEntries.async_forward_entry_unload", + return_value=False, + ): + result = await controller.async_reset() + await hass.async_block_till_done() + + assert result is False + + +async def test_connection_state_signalling(hass, aioclient_mock, mock_unifi_websocket): + """Verify connection statesignalling and connection state are working.""" + client = { + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": True, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } + await setup_unifi_integration(hass, aioclient_mock, clients_response=[client]) + + # Controller is connected + assert hass.states.get("device_tracker.client").state == "home" + + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + # Controller is disconnected + assert hass.states.get("device_tracker.client").state == "unavailable" + + mock_unifi_websocket(state=STATE_RUNNING) + await hass.async_block_till_done() + + # Controller is once again connected + assert hass.states.get("device_tracker.client").state == "home" + + async def test_wireless_client_event_calls_update_wireless_devices( - hass, aioclient_mock + hass, aioclient_mock, mock_unifi_websocket ): """Call update_wireless_devices method when receiving wireless client event.""" - config_entry = await setup_unifi_integration(hass, aioclient_mock) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + await setup_unifi_integration(hass, aioclient_mock) with patch( "homeassistant.components.unifi.controller.UniFiController.update_wireless_clients", return_value=None, ) as wireless_clients_mock: - controller.api.websocket._data = { - "meta": {"rc": "ok", "message": "events"}, - "data": [ - { - "datetime": "2020-01-20T19:37:04Z", - "key": aiounifi.events.WIRELESS_CLIENT_CONNECTED, - "msg": "User[11:22:33:44:55:66] has connected to WLAN", - "time": 1579549024893, - } - ], - } - controller.api.session_handler("data") + mock_unifi_websocket( + data={ + "meta": {"rc": "ok", "message": "events"}, + "data": [ + { + "datetime": "2020-01-20T19:37:04Z", + "key": aiounifi.events.WIRELESS_CLIENT_CONNECTED, + "msg": "User[11:22:33:44:55:66] has connected to WLAN", + "time": 1579549024893, + } + ], + }, + ) assert wireless_clients_mock.assert_called_once +async def test_reconnect_mechanism(hass, aioclient_mock, mock_unifi_websocket): + """Verify reconnect prints only on first reconnection try.""" + await setup_unifi_integration(hass, aioclient_mock) + + aioclient_mock.clear_requests() + aioclient_mock.post(f"https://{DEFAULT_HOST}:1234/api/login", status=502) + + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 0 + + new_time = dt_util.utcnow() + timedelta(seconds=RETRY_TIMER) + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 1 + + new_time = dt_util.utcnow() + timedelta(seconds=RETRY_TIMER) + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + assert aioclient_mock.call_count == 2 + + +@pytest.mark.parametrize( + "exception", + [ + asyncio.TimeoutError, + aiounifi.BadGateway, + aiounifi.ServiceUnavailable, + aiounifi.AiounifiException, + ], +) +async def test_reconnect_mechanism_exceptions( + hass, aioclient_mock, mock_unifi_websocket, exception +): + """Verify async_reconnect calls expected methods.""" + await setup_unifi_integration(hass, aioclient_mock) + + with patch("aiounifi.Controller.login", side_effect=exception), patch( + "homeassistant.components.unifi.controller.UniFiController.reconnect" + ) as mock_reconnect: + mock_unifi_websocket(state=STATE_DISCONNECTED) + await hass.async_block_till_done() + + new_time = dt_util.utcnow() + timedelta(seconds=RETRY_TIMER) + async_fire_time_changed(hass, new_time) + mock_reconnect.assert_called_once() + + async def test_get_controller(hass): """Successful call.""" with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index e8081a831c2f91..51dbd735e105e4 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -8,9 +8,8 @@ MESSAGE_CLIENT_REMOVED, MESSAGE_DEVICE, MESSAGE_EVENT, - SIGNAL_CONNECTION_STATE, ) -from aiounifi.websocket import SIGNAL_DATA, STATE_DISCONNECTED, STATE_RUNNING +from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING from homeassistant import config_entries from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -157,7 +156,7 @@ async def test_no_clients(hass, aioclient_mock): assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 0 -async def test_tracked_wireless_clients(hass, aioclient_mock): +async def test_tracked_wireless_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[CLIENT_1] @@ -171,11 +170,12 @@ async def test_tracked_wireless_clients(hass, aioclient_mock): # State change signalling works without events client_1_copy = copy(CLIENT_1) - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -186,11 +186,13 @@ async def test_tracked_wireless_clients(hass, aioclient_mock): assert client_1.attributes["host_name"] == "client_1" # State change signalling works with events - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -204,30 +206,30 @@ async def test_tracked_wireless_clients(hass, aioclient_mock): client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "not_home" - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "home" -async def test_tracked_clients(hass, aioclient_mock): +async def test_tracked_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" client_4_copy = copy(CLIENT_4) client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], known_wireless_clients=(CLIENT_4["mac"],), ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 client_1 = hass.states.get("device_tracker.client_1") @@ -254,22 +256,26 @@ async def test_tracked_clients(hass, aioclient_mock): # State change signalling works client_1_copy = copy(CLIENT_1) - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "home" -async def test_tracked_devices(hass, aioclient_mock): +async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some devices.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, devices_response=[DEVICE_1, DEVICE_2], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 device_1 = hass.states.get("device_tracker.device_1") @@ -283,12 +289,20 @@ async def test_tracked_devices(hass, aioclient_mock): # State change signalling work device_1_copy = copy(DEVICE_1) device_1_copy["next_interval"] = 20 - event = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_1_copy], + } + ) device_2_copy = copy(DEVICE_2) device_2_copy["next_interval"] = 50 - event = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_2_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_2_copy], + } + ) await hass.async_block_till_done() device_1 = hass.states.get("device_tracker.device_1") @@ -309,8 +323,12 @@ async def test_tracked_devices(hass, aioclient_mock): # Disabled device is unavailable device_1_copy = copy(DEVICE_1) device_1_copy["disabled"] = True - event = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_1_copy], + } + ) await hass.async_block_till_done() device_1 = hass.states.get("device_tracker.device_1") @@ -319,10 +337,18 @@ async def test_tracked_devices(hass, aioclient_mock): # Update device registry when device is upgraded device_2_copy = copy(DEVICE_2) device_2_copy["version"] = EVENT_DEVICE_2_UPGRADED["version_to"] - message = {"meta": {"message": MESSAGE_DEVICE}, "data": [device_2_copy]} - controller.api.message_handler(message) - event = {"meta": {"message": MESSAGE_EVENT}, "data": [EVENT_DEVICE_2_UPGRADED]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_DEVICE}, + "data": [device_2_copy], + } + ) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_DEVICE_2_UPGRADED], + } + ) await hass.async_block_till_done() # Verify device registry has been updated @@ -333,12 +359,12 @@ async def test_tracked_devices(hass, aioclient_mock): assert device.sw_version == EVENT_DEVICE_2_UPGRADED["version_to"] -async def test_remove_clients(hass, aioclient_mock): +async def test_remove_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, clients_response=[CLIENT_1, CLIENT_2] ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -347,11 +373,12 @@ async def test_remove_clients(hass, aioclient_mock): wired_client = hass.states.get("device_tracker.wired_client") assert wired_client is not None - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT_REMOVED}, + "data": [CLIENT_1], + } + ) await hass.async_block_till_done() await hass.async_block_till_done() @@ -364,15 +391,15 @@ async def test_remove_clients(hass, aioclient_mock): assert wired_client is not None -async def test_controller_state_change(hass, aioclient_mock): +async def test_controller_state_change(hass, aioclient_mock, mock_unifi_websocket): """Verify entities state reflect on controller becoming unavailable.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 client_1 = hass.states.get("device_tracker.client_1") @@ -382,9 +409,7 @@ async def test_controller_state_change(hass, aioclient_mock): assert device_1.state == "home" # Controller unavailable - controller.async_unifi_signalling_callback( - SIGNAL_CONNECTION_STATE, STATE_DISCONNECTED - ) + mock_unifi_websocket(state=STATE_DISCONNECTED) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -394,7 +419,7 @@ async def test_controller_state_change(hass, aioclient_mock): assert device_1.state == STATE_UNAVAILABLE # Controller available - controller.async_unifi_signalling_callback(SIGNAL_CONNECTION_STATE, STATE_RUNNING) + mock_unifi_websocket(state=STATE_RUNNING) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -554,7 +579,7 @@ async def test_option_track_devices(hass, aioclient_mock): assert device_1 is not None -async def test_option_ssid_filter(hass, aioclient_mock): +async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): """Test the SSID filter works. Client 1 will travel from a supported SSID to an unsupported ssid. @@ -593,13 +618,21 @@ async def test_option_ssid_filter(hass, aioclient_mock): # Roams to SSID outside of filter client_1_copy = copy(CLIENT_1) client_1_copy["essid"] = "other_ssid" - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) # Data update while SSID filter is in effect shouldn't create the client client_3_copy = copy(CLIENT_3) client_3_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_3_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() # SSID filter marks client as away @@ -616,10 +649,19 @@ async def test_option_ssid_filter(hass, aioclient_mock): options={CONF_SSID_FILTER: []}, ) await hass.async_block_till_done() - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_copy]} - controller.api.message_handler(event) - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_3_copy]} - controller.api.message_handler(event) + + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_copy], + } + ) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() client_1 = hass.states.get("device_tracker.client_1") @@ -636,16 +678,24 @@ async def test_option_ssid_filter(hass, aioclient_mock): client_1 = hass.states.get("device_tracker.client_1") assert client_1.state == "not_home" - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_3_copy]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() # Client won't go away until after next update client_3 = hass.states.get("device_tracker.client_3") assert client_3.state == "home" # Trigger update to get client marked as away - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [CLIENT_3]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_3_copy], + } + ) await hass.async_block_till_done() new_time = ( @@ -659,7 +709,9 @@ async def test_option_ssid_filter(hass, aioclient_mock): assert client_3.state == "not_home" -async def test_wireless_client_go_wired_issue(hass, aioclient_mock): +async def test_wireless_client_go_wired_issue( + hass, aioclient_mock, mock_unifi_websocket +): """Test the solution to catch wireless device go wired UniFi issue. UniFi has a known issue that when a wireless device goes away it sometimes gets marked as wired. @@ -681,8 +733,12 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): # Trigger wired bug client_1_client["is_wired"] = True - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Wired bug fix keeps client marked as wireless @@ -702,8 +758,12 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): assert client_1.attributes["is_wired"] is False # Try to mark client as connected - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Make sure it don't go online again until wired bug disappears @@ -713,8 +773,12 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): # Make client wireless client_1_client["is_wired"] = False - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Client is no longer affected by wired bug and can be marked online @@ -723,7 +787,7 @@ async def test_wireless_client_go_wired_issue(hass, aioclient_mock): assert client_1.attributes["is_wired"] is False -async def test_option_ignore_wired_bug(hass, aioclient_mock): +async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocket): """Test option to ignore wired bug.""" client_1_client = copy(CLIENT_1) client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) @@ -745,8 +809,12 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock): # Trigger wired bug client_1_client["is_wired"] = True - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Wired bug in effect @@ -766,8 +834,12 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock): assert client_1.attributes["is_wired"] is True # Mark client as connected again - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Ignoring wired bug allows client to go home again even while affected @@ -777,8 +849,12 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock): # Make client wireless client_1_client["is_wired"] = False - event = {"meta": {"message": MESSAGE_CLIENT}, "data": [client_1_client]} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client_1_client], + } + ) await hass.async_block_till_done() # Client is wireless and still connected diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 6d8b894fc347c1..591165dabf2756 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -1,14 +1,20 @@ """Test UniFi setup process.""" -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, patch from homeassistant.components import unifi from homeassistant.components.unifi import async_flatten_entry_data from homeassistant.components.unifi.const import CONF_CONTROLLER, DOMAIN as UNIFI_DOMAIN +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.setup import async_setup_component -from .test_controller import CONTROLLER_DATA, ENTRY_CONFIG, setup_unifi_integration +from .test_controller import ( + CONTROLLER_DATA, + DEFAULT_CONFIG_ENTRY_ID, + ENTRY_CONFIG, + setup_unifi_integration, +) -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry async def test_setup_with_no_config(hass): @@ -19,7 +25,7 @@ async def test_setup_with_no_config(hass): async def test_successful_config_entry(hass, aioclient_mock): """Test that configured options for a host are loaded via config entry.""" - await setup_unifi_integration(hass, aioclient_mock) + await setup_unifi_integration(hass, aioclient_mock, unique_id=None) assert hass.data[UNIFI_DOMAIN] @@ -32,29 +38,28 @@ async def test_controller_fail_setup(hass): assert hass.data[UNIFI_DOMAIN] == {} -async def test_controller_no_mac(hass): +async def test_controller_mac(hass): """Test that configured options for a host are loaded via config entry.""" entry = MockConfigEntry( - domain=UNIFI_DOMAIN, - data=ENTRY_CONFIG, - unique_id="1", - version=1, + domain=UNIFI_DOMAIN, data=ENTRY_CONFIG, unique_id="1", entry_id=1 ) entry.add_to_hass(hass) - mock_registry = Mock() - with patch( - "homeassistant.components.unifi.UniFiController" - ) as mock_controller, patch( - "homeassistant.helpers.device_registry.async_get_registry", - return_value=mock_coro(mock_registry), - ): + + with patch("homeassistant.components.unifi.UniFiController") as mock_controller: mock_controller.return_value.async_setup = AsyncMock(return_value=True) - mock_controller.return_value.mac = None + mock_controller.return_value.mac = "mac1" assert await unifi.async_setup_entry(hass, entry) is True assert len(mock_controller.mock_calls) == 2 - assert len(mock_registry.mock_calls) == 0 + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, connections={(CONNECTION_NETWORK_MAC, "mac1")} + ) + assert device.manufacturer == "Ubiquiti Networks" + assert device.model == "UniFi Controller" + assert device.name == "UniFi Controller" + assert device.sw_version is None async def test_flatten_entry_data(hass): @@ -73,5 +78,45 @@ async def test_unload_entry(hass, aioclient_mock): config_entry = await setup_unifi_integration(hass, aioclient_mock) assert hass.data[UNIFI_DOMAIN] - assert await unifi.async_unload_entry(hass, config_entry) + assert await hass.config_entries.async_unload(config_entry.entry_id) assert not hass.data[UNIFI_DOMAIN] + + +async def test_wireless_clients(hass, hass_storage, aioclient_mock): + """Verify wireless clients class.""" + hass_storage[unifi.STORAGE_KEY] = { + "version": unifi.STORAGE_VERSION, + "data": { + DEFAULT_CONFIG_ENTRY_ID: { + "wireless_devices": ["00:00:00:00:00:00", "00:00:00:00:00:01"] + } + }, + } + + client_1 = { + "hostname": "client_1", + "ip": "10.0.0.1", + "is_wired": False, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "hostname": "client_2", + "ip": "10.0.0.2", + "is_wired": False, + "mac": "00:00:00:00:00:02", + } + config_entry = await setup_unifi_integration( + hass, aioclient_mock, clients_response=[client_1, client_2] + ) + + for mac in [ + "00:00:00:00:00:00", + "00:00:00:00:00:01", + "00:00:00:00:00:02", + ]: + assert ( + mac + in hass_storage[unifi.STORAGE_KEY]["data"][config_entry.entry_id][ + "wireless_devices" + ] + ) diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index c668bf3789f8ec..db1794e087847d 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -2,7 +2,6 @@ from copy import deepcopy from aiounifi.controller import MESSAGE_CLIENT, MESSAGE_CLIENT_REMOVED -from aiounifi.websocket import SIGNAL_DATA from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -63,7 +62,7 @@ async def test_no_clients(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 -async def test_sensors(hass, aioclient_mock): +async def test_sensors(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( hass, @@ -104,8 +103,12 @@ async def test_sensors(hass, aioclient_mock): clients[1]["tx_bytes"] = 6789000000 clients[1]["uptime"] = 1600180860 - event = {"meta": {"message": MESSAGE_CLIENT}, "data": clients} - controller.api.message_handler(event) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": clients, + } + ) await hass.async_block_till_done() wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") @@ -178,9 +181,9 @@ async def test_sensors(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 -async def test_remove_sensors(hass, aioclient_mock): +async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={ @@ -189,7 +192,7 @@ async def test_remove_sensors(hass, aioclient_mock): }, clients_response=CLIENTS, ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 @@ -209,11 +212,12 @@ async def test_remove_sensors(hass, aioclient_mock): wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") assert wireless_client_uptime is not None - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENTS[0]], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT_REMOVED}, + "data": [CLIENTS[0]], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3 diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index e5a3a7eccc4120..a0dc8e984e1c8d 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -2,7 +2,6 @@ from copy import deepcopy from aiounifi.controller import MESSAGE_CLIENT_REMOVED, MESSAGE_EVENT -from aiounifi.websocket import SIGNAL_DATA from homeassistant import config_entries from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -17,6 +16,7 @@ ) from homeassistant.components.unifi.switch import POE_SWITCH from homeassistant.helpers import entity_registry +from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_controller import ( CONTROLLER_HOST, @@ -370,6 +370,7 @@ async def test_switches(hass, aioclient_mock): dpi_switch = hass.states.get("switch.block_media_streaming") assert dpi_switch is not None assert dpi_switch.state == "on" + assert dpi_switch.attributes["icon"] == "mdi:network" # Block and unblock client @@ -419,17 +420,22 @@ async def test_switches(hass, aioclient_mock): assert aioclient_mock.call_count == 14 assert aioclient_mock.mock_calls[13][2] == {"enabled": True} + # Make sure no duplicates arise on generic signal update + async_dispatcher_send(hass, controller.signal_update) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 4 + -async def test_remove_switches(hass, aioclient_mock): +async def test_remove_switches(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, clients_response=[CLIENT_1, UNBLOCKED], devices_response=[DEVICE_1], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 poe_switch = hass.states.get("switch.poe_client_1") @@ -438,11 +444,12 @@ async def test_remove_switches(hass, aioclient_mock): block_switch = hass.states.get("switch.block_client_2") assert block_switch is not None - controller.api.websocket._data = { - "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1, UNBLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT_REMOVED}, + "data": [CLIENT_1, UNBLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 @@ -454,7 +461,7 @@ async def test_remove_switches(hass, aioclient_mock): assert block_switch is None -async def test_block_switches(hass, aioclient_mock): +async def test_block_switches(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" config_entry = await setup_unifi_integration( hass, @@ -479,11 +486,12 @@ async def test_block_switches(hass, aioclient_mock): assert unblocked is not None assert unblocked.state == "on" - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_BLOCKED_CLIENT_UNBLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_BLOCKED_CLIENT_UNBLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 @@ -491,11 +499,12 @@ async def test_block_switches(hass, aioclient_mock): assert blocked is not None assert blocked.state == "on" - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_BLOCKED_CLIENT_BLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_BLOCKED_CLIENT_BLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 @@ -526,9 +535,11 @@ async def test_block_switches(hass, aioclient_mock): } -async def test_new_client_discovered_on_block_control(hass, aioclient_mock): +async def test_new_client_discovered_on_block_control( + hass, aioclient_mock, mock_unifi_websocket +): """Test if 2nd update has a new client.""" - config_entry = await setup_unifi_integration( + await setup_unifi_integration( hass, aioclient_mock, options={ @@ -538,27 +549,28 @@ async def test_new_client_discovered_on_block_control(hass, aioclient_mock): CONF_DPI_RESTRICTIONS: False, }, ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 blocked = hass.states.get("switch.block_client_1") assert blocked is None - controller.api.websocket._data = { - "meta": {"message": "sta:sync"}, - "data": [BLOCKED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": "sta:sync"}, + "data": [BLOCKED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_BLOCKED_CLIENT_CONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_BLOCKED_CLIENT_CONNECTED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 @@ -634,7 +646,9 @@ async def test_option_remove_switches(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 -async def test_new_client_discovered_on_poe_control(hass, aioclient_mock): +async def test_new_client_discovered_on_poe_control( + hass, aioclient_mock, mock_unifi_websocket +): """Test if 2nd update has a new client.""" config_entry = await setup_unifi_integration( hass, @@ -647,20 +661,22 @@ async def test_new_client_discovered_on_poe_control(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - controller.api.websocket._data = { - "meta": {"message": "sta:sync"}, - "data": [CLIENT_2], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": "sta:sync"}, + "data": [CLIENT_2], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - controller.api.websocket._data = { - "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_2_CONNECTED], - } - controller.api.session_handler(SIGNAL_DATA) + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_EVENT}, + "data": [EVENT_CLIENT_2_CONNECTED], + } + ) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 From b3c33fc1be3b41239fec5c607e9fee59e602195d Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 5 Mar 2021 21:41:55 +0100 Subject: [PATCH 1047/1818] Fix issue at Netatmo startup (#47452) --- homeassistant/components/netatmo/camera.py | 48 ++++++++++--------- homeassistant/components/netatmo/climate.py | 8 +++- .../components/netatmo/data_handler.py | 6 ++- homeassistant/components/netatmo/light.py | 9 ++-- homeassistant/components/netatmo/sensor.py | 18 ++++++- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 6e55b884d4d19d..5163c9582b0095 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -7,6 +7,7 @@ from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.core import callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -49,15 +50,17 @@ async def async_setup_entry(hass, entry, async_add_entities): 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 + ) + + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + async def get_entities(): """Retrieve Netatmo entities.""" - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) - - data = data_handler.data - if not data.get(CAMERA_DATA_CLASS_NAME): + if not data_handler.data.get(CAMERA_DATA_CLASS_NAME): return [] data_class = data_handler.data[CAMERA_DATA_CLASS_NAME] @@ -94,24 +97,25 @@ async def get_entities(): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(CAMERA_DATA_CLASS_NAME, None) + platform = entity_platform.current_platform.get() - if data_handler.data[CAMERA_DATA_CLASS_NAME] is not None: - platform.async_register_entity_service( - SERVICE_SET_PERSONS_HOME, - {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])}, - "_service_set_persons_home", - ) - platform.async_register_entity_service( - SERVICE_SET_PERSON_AWAY, - {vol.Optional(ATTR_PERSON): cv.string}, - "_service_set_person_away", - ) - platform.async_register_entity_service( - SERVICE_SET_CAMERA_LIGHT, - {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)}, - "_service_set_camera_light", - ) + platform.async_register_entity_service( + SERVICE_SET_PERSONS_HOME, + {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])}, + "_service_set_persons_home", + ) + platform.async_register_entity_service( + SERVICE_SET_PERSON_AWAY, + {vol.Optional(ATTR_PERSON): cv.string}, + "_service_set_person_away", + ) + platform.async_register_entity_service( + SERVICE_SET_CAMERA_LIGHT, + {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)}, + "_service_set_camera_light", + ) class NetatmoCamera(NetatmoBase, Camera): diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index a53f7f9fb089e1..d12ee9263db43c 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -25,6 +25,7 @@ TEMP_CELSIUS, ) from homeassistant.core import callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -81,6 +82,7 @@ STATE_NETATMO_AWAY: PRESET_AWAY, STATE_NETATMO_OFF: STATE_NETATMO_OFF, STATE_NETATMO_MANUAL: STATE_NETATMO_MANUAL, + STATE_NETATMO_HOME: PRESET_SCHEDULE, } HVAC_MAP_NETATMO = { @@ -111,8 +113,8 @@ async def async_setup_entry(hass, entry, async_add_entities): ) home_data = data_handler.data.get(HOMEDATA_DATA_CLASS_NAME) - if not home_data: - return + if HOMEDATA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady async def get_entities(): """Retrieve Netatmo entities.""" @@ -151,6 +153,8 @@ async def get_entities(): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(HOMEDATA_DATA_CLASS_NAME, None) + platform = entity_platform.current_platform.get() if home_data is not None: diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 9bc4b197f1b77b..be0120bd1a0a31 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -129,7 +129,11 @@ async def async_fetch_data(self, data_class, data_class_entry, **kwargs): if update_callback: update_callback() - except (pyatmo.NoDevice, pyatmo.ApiError) as err: + except pyatmo.NoDevice as err: + _LOGGER.debug(err) + self.data[data_class_entry] = None + + except pyatmo.ApiError as err: _LOGGER.debug(err) async def register_data_class( diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index dc8bf3f1fc8c01..eed12f048c85e3 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -31,18 +31,15 @@ async def async_setup_entry(hass, entry, async_add_entities): data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + async def get_entities(): """Retrieve Netatmo entities.""" - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) entities = [] all_cameras = [] - if CAMERA_DATA_CLASS_NAME not in data_handler.data: - raise PlatformNotReady - try: for home in data_handler.data[CAMERA_DATA_CLASS_NAME].cameras.values(): for camera in home.values(): diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index f848444481811d..9176b670bea4e9 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -20,6 +20,7 @@ TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.device_registry import async_entries_for_config_entry from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -129,14 +130,25 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up the Netatmo weather and homecoach platform.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + await data_handler.register_data_class( + WEATHERSTATION_DATA_CLASS_NAME, WEATHERSTATION_DATA_CLASS_NAME, None + ) + await data_handler.register_data_class( + HOMECOACH_DATA_CLASS_NAME, HOMECOACH_DATA_CLASS_NAME, None + ) + async def find_entities(data_class_name): """Find all entities.""" - await data_handler.register_data_class(data_class_name, data_class_name, None) + if data_class_name not in data_handler.data: + raise PlatformNotReady all_module_infos = {} data = data_handler.data - if not data.get(data_class_name): + if data_class_name not in data: + return [] + + if data[data_class_name] is None: return [] data_class = data[data_class_name] @@ -174,6 +186,8 @@ async def find_entities(data_class_name): NetatmoSensor(data_handler, data_class_name, module, condition) ) + await data_handler.unregister_data_class(data_class_name, None) + return entities for data_class_name in [ From 02e723f2066e0fddcd07698b32dedc395a97c75b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 21:48:02 +0100 Subject: [PATCH 1048/1818] Typing tweak to the Elgato integration (#47471) --- .../components/elgato/config_flow.py | 20 +++++++++---------- homeassistant/components/elgato/light.py | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index e9138afd86c7c9..7c818d6f3fa677 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure the Elgato Key Light integration.""" from __future__ import annotations -from typing import Any, Dict +from typing import Any from elgato import Elgato, ElgatoError import voluptuous as vol @@ -25,8 +25,8 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): serial_number: str async def async_step_user( - self, user_input: Dict[str, Any] | None = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._async_show_setup_form() @@ -42,8 +42,8 @@ async def async_step_user( return self._async_create_entry() async def async_step_zeroconf( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle zeroconf discovery.""" self.host = discovery_info[CONF_HOST] self.port = discovery_info[CONF_PORT] @@ -59,15 +59,15 @@ async def async_step_zeroconf( ) async def async_step_zeroconf_confirm( - self, _: Dict[str, Any] | None = None - ) -> Dict[str, Any]: + self, _: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by zeroconf.""" return self._async_create_entry() @callback def _async_show_setup_form( - self, errors: Dict[str, str] | None = None - ) -> Dict[str, Any]: + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -81,7 +81,7 @@ def _async_show_setup_form( ) @callback - def _async_create_entry(self) -> Dict[str, Any]: + def _async_create_entry(self) -> dict[str, Any]: return self.async_create_entry( title=self.serial_number, data={ diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 0648a4817bc3dd..3005560e2eaae2 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging -from typing import Any, Callable, Dict, List +from typing import Any, Callable from elgato import Elgato, ElgatoError, Info, State @@ -38,7 +38,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Elgato Key Light based on a config entry.""" elgato: Elgato = hass.data[DOMAIN][entry.entry_id][DATA_ELGATO_CLIENT] @@ -116,7 +116,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - data: Dict[str, bool | int] = {ATTR_ON: True} + data: dict[str, bool | int] = {ATTR_ON: True} if ATTR_ON in kwargs: data[ATTR_ON] = kwargs[ATTR_ON] @@ -149,7 +149,7 @@ async def async_update(self) -> None: self._temperature = state.temperature @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Elgato Key Light.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self._info.serial_number)}, From 50d3aae418cfb06f205eb7cac087a0be621bb6a4 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 5 Mar 2021 22:09:05 +0100 Subject: [PATCH 1049/1818] Improve restoring UniFi POE entity state (#47148) * Improve restoring data and better handling when the restore data is empty Improve readability of some logic related to POE clients * There is no need to check clients_all in Switch platform * Add better tests when restoring state * Port except handling shouldn't be needed anymore * Walrusify get_last_state --- homeassistant/components/unifi/controller.py | 24 ++- homeassistant/components/unifi/switch.py | 73 ++++---- tests/components/unifi/test_switch.py | 171 +++++++++++++++++-- 3 files changed, 200 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 7c95058e8e4269..9096620f0eda16 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -40,6 +40,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.event import async_track_time_interval import homeassistant.util.dt as dt_util @@ -338,21 +339,18 @@ async def async_setup(self): self._site_role = description[0]["site_role"] - # Restore clients that is not a part of active clients list. + # Restore clients that are not a part of active clients list. entity_registry = await self.hass.helpers.entity_registry.async_get_registry() - for entity in entity_registry.entities.values(): - if ( - entity.config_entry_id != self.config_entry.entry_id - or "-" not in entity.unique_id - ): + for entry in async_entries_for_config_entry( + entity_registry, self.config_entry.entry_id + ): + if entry.domain == TRACKER_DOMAIN: + mac = entry.unique_id.split("-", 1)[0] + elif entry.domain == SWITCH_DOMAIN: + mac = entry.unique_id.split("-", 1)[1] + else: continue - mac = "" - if entity.domain == TRACKER_DOMAIN: - mac = entity.unique_id.split("-", 1)[0] - elif entity.domain == SWITCH_DOMAIN: - mac = entity.unique_id.split("-", 1)[1] - if mac in self.api.clients or mac not in self.api.clients_all: continue @@ -360,7 +358,7 @@ async def async_setup(self): self.api.clients.process_raw([client.raw]) LOGGER.debug( "Restore disconnected client %s (%s)", - entity.entity_id, + entry.entity_id, client.mac, ) diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index e596e0b1e2a119..6c8b6ea35cd5ba 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -18,6 +18,7 @@ from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.restore_state import RestoreEntity from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN @@ -50,19 +51,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities): return # Store previously known POE control entities in case their POE are turned off. - previously_known_poe_clients = [] + known_poe_clients = [] entity_registry = await hass.helpers.entity_registry.async_get_registry() - for entity in entity_registry.entities.values(): + for entry in async_entries_for_config_entry(entity_registry, config_entry.entry_id): - if ( - entity.config_entry_id != config_entry.entry_id - or not entity.unique_id.startswith(POE_SWITCH) - ): + if not entry.unique_id.startswith(POE_SWITCH): + continue + + mac = entry.unique_id.replace(f"{POE_SWITCH}-", "") + if mac not in controller.api.clients: continue - mac = entity.unique_id.replace(f"{POE_SWITCH}-", "") - if mac in controller.api.clients or mac in controller.api.clients_all: - previously_known_poe_clients.append(entity.unique_id) + known_poe_clients.append(mac) for mac in controller.option_block_clients: if mac not in controller.api.clients and mac in controller.api.clients_all: @@ -80,9 +80,7 @@ def items_added( add_block_entities(controller, async_add_entities, clients) if controller.option_poe_clients: - add_poe_entities( - controller, async_add_entities, clients, previously_known_poe_clients - ) + add_poe_entities(controller, async_add_entities, clients, known_poe_clients) if controller.option_dpi_restrictions: add_dpi_entities(controller, async_add_entities, dpi_groups) @@ -91,7 +89,7 @@ def items_added( controller.listeners.append(async_dispatcher_connect(hass, signal, items_added)) items_added() - previously_known_poe_clients.clear() + known_poe_clients.clear() @callback @@ -111,9 +109,7 @@ def add_block_entities(controller, async_add_entities, clients): @callback -def add_poe_entities( - controller, async_add_entities, clients, previously_known_poe_clients -): +def add_poe_entities(controller, async_add_entities, clients, known_poe_clients): """Add new switch entities from the controller.""" switches = [] @@ -123,10 +119,13 @@ def add_poe_entities( if mac in controller.entities[DOMAIN][POE_SWITCH]: continue - poe_client_id = f"{POE_SWITCH}-{mac}" client = controller.api.clients[mac] - if poe_client_id not in previously_known_poe_clients and ( + # Try to identify new clients powered by POE. + # Known POE clients have been created in previous HASS sessions. + # If port_poe is None the port does not support POE + # If poe_enable is False we can't know if a POE client is available for control. + if mac not in known_poe_clients and ( mac in controller.wireless_clients or client.sw_mac not in devices or not devices[client.sw_mac].ports[client.sw_port].port_poe @@ -139,7 +138,7 @@ def add_poe_entities( multi_clients_on_port = False for client2 in controller.api.clients.values(): - if poe_client_id in previously_known_poe_clients: + if mac in known_poe_clients: break if ( @@ -196,18 +195,19 @@ async def async_added_to_hass(self): """Call when entity about to be added to Home Assistant.""" await super().async_added_to_hass() - state = await self.async_get_last_state() - if state is None: + if self.poe_mode: # POE is enabled and client in a known state + return + + if (state := await self.async_get_last_state()) is None: return - if self.poe_mode is None: - self.poe_mode = state.attributes["poe_mode"] + self.poe_mode = state.attributes.get("poe_mode") if not self.client.sw_mac: - self.client.raw["sw_mac"] = state.attributes["switch"] + self.client.raw["sw_mac"] = state.attributes.get("switch") if not self.client.sw_port: - self.client.raw["sw_port"] = state.attributes["port"] + self.client.raw["sw_port"] = state.attributes.get("port") @property def is_on(self): @@ -218,16 +218,15 @@ def is_on(self): def available(self): """Return if switch is available. - Poe_mode None means its poe state is unknown. + Poe_mode None means its POE state is unknown. Sw_mac unavailable means restored client. """ return ( - self.poe_mode is None - or self.client.sw_mac - and ( - self.controller.available - and self.client.sw_mac in self.controller.api.devices - ) + self.poe_mode is not None + and self.controller.available + and self.client.sw_port + and self.client.sw_mac + and self.client.sw_mac in self.controller.api.devices ) async def async_turn_on(self, **kwargs): @@ -257,15 +256,7 @@ def device(self): @property def port(self): """Shortcut to the switch port that client is connected to.""" - try: - return self.device.ports[self.client.sw_port] - except (AttributeError, KeyError, TypeError): - _LOGGER.warning( - "Entity %s reports faulty device %s or port %s", - self.entity_id, - self.client.sw_mac, - self.client.sw_port, - ) + return self.device.ports[self.client.sw_port] async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index a0dc8e984e1c8d..44120a18ee36a0 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -1,9 +1,10 @@ """UniFi switch platform tests.""" from copy import deepcopy +from unittest.mock import patch from aiounifi.controller import MESSAGE_CLIENT_REMOVED, MESSAGE_EVENT -from homeassistant import config_entries +from homeassistant import config_entries, core from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.unifi.const import ( @@ -726,8 +727,66 @@ async def test_ignore_multiple_poe_clients_on_same_port(hass, aioclient_mock): assert switch_2 is None -async def test_restoring_client(hass, aioclient_mock): - """Test the update_items function with some clients.""" +async def test_restore_client_succeed(hass, aioclient_mock): + """Test that RestoreEntity works as expected.""" + POE_DEVICE = { + "device_id": "12345", + "ip": "1.0.1.1", + "mac": "00:00:00:00:01:01", + "last_seen": 1562600145, + "model": "US16P150", + "name": "POE Switch", + "port_overrides": [ + { + "poe_mode": "off", + "port_idx": 1, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + } + ], + "port_table": [ + { + "media": "GE", + "name": "Port 1", + "op_mode": "switch", + "poe_caps": 7, + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": False, + "poe_good": False, + "poe_mode": "off", + "poe_power": "0.00", + "poe_voltage": "0.00", + "port_idx": 1, + "port_poe": True, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + "up": False, + }, + ], + "state": 1, + "type": "usw", + "version": "4.0.42.10433", + } + POE_CLIENT = { + "hostname": "poe_client", + "ip": "1.0.0.1", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + "name": "POE Client", + "oui": "Producer", + } + + fake_state = core.State( + "switch.poe_client", + "off", + { + "power": "0.00", + "switch": POE_DEVICE["mac"], + "port": 1, + "poe_mode": "auto", + }, + ) + config_entry = config_entries.ConfigEntry( version=1, domain=UNIFI_DOMAIN, @@ -744,15 +803,100 @@ async def test_restoring_client(hass, aioclient_mock): registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, - f'{POE_SWITCH}-{CLIENT_1["mac"]}', - suggested_object_id=CLIENT_1["hostname"], + f'{POE_SWITCH}-{POE_CLIENT["mac"]}', + suggested_object_id=POE_CLIENT["hostname"], config_entry=config_entry, ) + + with patch( + "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", + return_value=fake_state, + ): + await setup_unifi_integration( + hass, + aioclient_mock, + options={ + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + }, + clients_response=[], + devices_response=[POE_DEVICE], + clients_all_response=[POE_CLIENT], + ) + + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 + + poe_client = hass.states.get("switch.poe_client") + assert poe_client.state == "off" + + +async def test_restore_client_no_old_state(hass, aioclient_mock): + """Test that RestoreEntity without old state makes entity unavailable.""" + POE_DEVICE = { + "device_id": "12345", + "ip": "1.0.1.1", + "mac": "00:00:00:00:01:01", + "last_seen": 1562600145, + "model": "US16P150", + "name": "POE Switch", + "port_overrides": [ + { + "poe_mode": "off", + "port_idx": 1, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + } + ], + "port_table": [ + { + "media": "GE", + "name": "Port 1", + "op_mode": "switch", + "poe_caps": 7, + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": False, + "poe_good": False, + "poe_mode": "off", + "poe_power": "0.00", + "poe_voltage": "0.00", + "port_idx": 1, + "port_poe": True, + "portconf_id": "5f3edd2aba4cc806a19f2db2", + "up": False, + }, + ], + "state": 1, + "type": "usw", + "version": "4.0.42.10433", + } + POE_CLIENT = { + "hostname": "poe_client", + "ip": "1.0.0.1", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + "name": "POE Client", + "oui": "Producer", + } + + config_entry = config_entries.ConfigEntry( + version=1, + domain=UNIFI_DOMAIN, + title="Mock Title", + data=ENTRY_CONFIG, + source="test", + connection_class=config_entries.CONN_CLASS_LOCAL_POLL, + system_options={}, + options={}, + entry_id=1, + ) + + registry = await entity_registry.async_get_registry(hass) registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, - f'{POE_SWITCH}-{CLIENT_2["mac"]}', - suggested_object_id=CLIENT_2["hostname"], + f'{POE_SWITCH}-{POE_CLIENT["mac"]}', + suggested_object_id=POE_CLIENT["hostname"], config_entry=config_entry, ) @@ -760,16 +904,15 @@ async def test_restoring_client(hass, aioclient_mock): hass, aioclient_mock, options={ - CONF_BLOCK_CLIENT: ["random mac"], CONF_TRACK_CLIENTS: False, CONF_TRACK_DEVICES: False, }, - clients_response=[CLIENT_2], - devices_response=[DEVICE_1], - clients_all_response=[CLIENT_1], + clients_response=[], + devices_response=[POE_DEVICE], + clients_all_response=[POE_CLIENT], ) - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 - device_1 = hass.states.get("switch.client_1") - assert device_1 is not None + poe_client = hass.states.get("switch.poe_client") + assert poe_client.state == "unavailable" # self.poe_mode is None From a12b98e30e6f6021bedfb0b674f6fc137d7e6cca Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 5 Mar 2021 17:01:54 -0500 Subject: [PATCH 1050/1818] Update ZHA dependencies (#47479) --- 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 7d367c3dc00867..df3c0fcfda91fc 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -10,7 +10,7 @@ "zha-quirks==0.0.54", "zigpy-cc==0.5.2", "zigpy-deconz==0.11.1", - "zigpy==0.32.0", + "zigpy==0.33.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.7.3", "zigpy-znp==0.4.0" diff --git a/requirements_all.txt b/requirements_all.txt index 5253f58a9ee5b2..b7d76130393449 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2388,7 +2388,7 @@ zigpy-zigate==0.7.3 zigpy-znp==0.4.0 # homeassistant.components.zha -zigpy==0.32.0 +zigpy==0.33.0 # homeassistant.components.zoneminder zm-py==0.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 45c06066e20070..e8242230052920 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1228,7 +1228,7 @@ zigpy-zigate==0.7.3 zigpy-znp==0.4.0 # homeassistant.components.zha -zigpy==0.32.0 +zigpy==0.33.0 # homeassistant.components.zwave_js zwave-js-server-python==0.21.1 From 292f4262aab5bfcca0859b5d3ecbfa4b9dfce59a Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Fri, 5 Mar 2021 23:40:04 +0100 Subject: [PATCH 1051/1818] Move AsusWrt sensors update logic in router module (#46606) --- homeassistant/components/asuswrt/__init__.py | 2 +- .../components/asuswrt/config_flow.py | 3 +- homeassistant/components/asuswrt/const.py | 8 +- homeassistant/components/asuswrt/router.py | 151 ++++++++ homeassistant/components/asuswrt/sensor.py | 327 +++++++----------- tests/components/asuswrt/test_sensor.py | 53 +-- 6 files changed, 319 insertions(+), 225 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 28e8fe76684b3b..25a78f6a523ee6 100644 --- a/homeassistant/components/asuswrt/__init__.py +++ b/homeassistant/components/asuswrt/__init__.py @@ -31,7 +31,6 @@ MODE_ROUTER, PROTOCOL_SSH, PROTOCOL_TELNET, - SENSOR_TYPES, ) from .router import AsusWrtRouter @@ -39,6 +38,7 @@ 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( diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index 303b3cc3822ce9..b312d9b8186ff9 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -21,7 +21,6 @@ from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -# pylint:disable=unused-import from .const import ( CONF_DNSMASQ, CONF_INTERFACE, @@ -32,12 +31,12 @@ DEFAULT_INTERFACE, DEFAULT_SSH_PORT, DEFAULT_TRACK_UNKNOWN, - DOMAIN, MODE_AP, MODE_ROUTER, PROTOCOL_SSH, PROTOCOL_TELNET, ) +from .const import DOMAIN # pylint:disable=unused-import from .router import get_api RESULT_CONN_ERROR = "cannot_connect" diff --git a/homeassistant/components/asuswrt/const.py b/homeassistant/components/asuswrt/const.py index 40752e81a08fd5..a8977a77ea8e11 100644 --- a/homeassistant/components/asuswrt/const.py +++ b/homeassistant/components/asuswrt/const.py @@ -20,5 +20,9 @@ PROTOCOL_SSH = "ssh" PROTOCOL_TELNET = "telnet" -# Sensor -SENSOR_TYPES = ["devices", "upload_speed", "download_speed", "download", "upload"] +# Sensors +SENSOR_CONNECTED_DEVICE = "sensor_connected_device" +SENSOR_RX_BYTES = "sensor_rx_bytes" +SENSOR_TX_BYTES = "sensor_tx_bytes" +SENSOR_RX_RATES = "sensor_rx_rates" +SENSOR_TX_RATES = "sensor_tx_rates" diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 11545919b43abb..c94135423d83a8 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -24,6 +24,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util from .const import ( @@ -37,14 +38,97 @@ DEFAULT_TRACK_UNKNOWN, DOMAIN, PROTOCOL_TELNET, + SENSOR_CONNECTED_DEVICE, + SENSOR_RX_BYTES, + SENSOR_RX_RATES, + SENSOR_TX_BYTES, + SENSOR_TX_RATES, ) CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] + +KEY_COORDINATOR = "coordinator" +KEY_SENSORS = "sensors" + SCAN_INTERVAL = timedelta(seconds=30) +SENSORS_TYPE_BYTES = "sensors_bytes" +SENSORS_TYPE_COUNT = "sensors_count" +SENSORS_TYPE_RATES = "sensors_rates" + _LOGGER = logging.getLogger(__name__) +class AsusWrtSensorDataHandler: + """Data handler for AsusWrt sensor.""" + + def __init__(self, hass, api): + """Initialize a AsusWrt sensor data handler.""" + self._hass = hass + self._api = api + self._connected_devices = 0 + + async def _get_connected_devices(self): + """Return number of connected devices.""" + return {SENSOR_CONNECTED_DEVICE: self._connected_devices} + + async def _get_bytes(self): + """Fetch byte information from the router.""" + ret_dict: Dict[str, Any] = {} + try: + datas = await self._api.async_get_bytes_total() + except OSError as exc: + raise UpdateFailed from exc + + ret_dict[SENSOR_RX_BYTES] = datas[0] + ret_dict[SENSOR_TX_BYTES] = datas[1] + + return ret_dict + + async def _get_rates(self): + """Fetch rates information from the router.""" + ret_dict: Dict[str, Any] = {} + try: + rates = await self._api.async_get_current_transfer_rates() + except OSError as exc: + raise UpdateFailed from exc + + ret_dict[SENSOR_RX_RATES] = rates[0] + ret_dict[SENSOR_TX_RATES] = rates[1] + + return ret_dict + + def update_device_count(self, conn_devices: int): + """Update connected devices attribute.""" + if self._connected_devices == conn_devices: + return False + self._connected_devices = conn_devices + return True + + async def get_coordinator(self, sensor_type: str, should_poll=True): + """Get the coordinator for a specific sensor type.""" + if sensor_type == SENSORS_TYPE_COUNT: + method = self._get_connected_devices + elif sensor_type == SENSORS_TYPE_BYTES: + method = self._get_bytes + elif sensor_type == SENSORS_TYPE_RATES: + method = self._get_rates + else: + raise RuntimeError(f"Invalid sensor type: {sensor_type}") + + coordinator = DataUpdateCoordinator( + self._hass, + _LOGGER, + name=sensor_type, + update_method=method, + # Polling interval. Will only be polled if there are subscribers. + update_interval=SCAN_INTERVAL if should_poll else None, + ) + await coordinator.async_refresh() + + return coordinator + + class AsusWrtDevInfo: """Representation of a AsusWrt device info.""" @@ -111,8 +195,12 @@ def __init__(self, hass: HomeAssistantType, entry: ConfigEntry) -> None: self._host = entry.data[CONF_HOST] self._devices: Dict[str, Any] = {} + self._connected_devices = 0 self._connect_error = False + self._sensors_data_handler: AsusWrtSensorDataHandler = None + self._sensors_coordinator: Dict[str, Any] = {} + self._on_close = [] self._options = { @@ -150,6 +238,9 @@ async def setup(self) -> None: # Update devices await self.update_devices() + # Init Sensors + await self.init_sensors_coordinator() + self.async_on_close( async_track_time_interval(self.hass, self.update_all, SCAN_INTERVAL) ) @@ -201,6 +292,51 @@ async def update_devices(self) -> None: if new_device: async_dispatcher_send(self.hass, self.signal_device_new) + self._connected_devices = len(wrt_devices) + await self._update_unpolled_sensors() + + async def init_sensors_coordinator(self) -> None: + """Init AsusWrt sensors coordinators.""" + if self._sensors_data_handler: + return + + self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api) + self._sensors_data_handler.update_device_count(self._connected_devices) + + conn_dev_coordinator = await self._sensors_data_handler.get_coordinator( + SENSORS_TYPE_COUNT, False + ) + self._sensors_coordinator[SENSORS_TYPE_COUNT] = { + KEY_COORDINATOR: conn_dev_coordinator, + KEY_SENSORS: [SENSOR_CONNECTED_DEVICE], + } + + bytes_coordinator = await self._sensors_data_handler.get_coordinator( + SENSORS_TYPE_BYTES + ) + self._sensors_coordinator[SENSORS_TYPE_BYTES] = { + KEY_COORDINATOR: bytes_coordinator, + KEY_SENSORS: [SENSOR_RX_BYTES, SENSOR_TX_BYTES], + } + + rates_coordinator = await self._sensors_data_handler.get_coordinator( + SENSORS_TYPE_RATES + ) + self._sensors_coordinator[SENSORS_TYPE_RATES] = { + KEY_COORDINATOR: rates_coordinator, + KEY_SENSORS: [SENSOR_RX_RATES, SENSOR_TX_RATES], + } + + async def _update_unpolled_sensors(self) -> None: + """Request refresh for AsusWrt unpolled sensors.""" + if not self._sensors_data_handler: + return + + if SENSORS_TYPE_COUNT in self._sensors_coordinator: + coordinator = self._sensors_coordinator[SENSORS_TYPE_COUNT][KEY_COORDINATOR] + if self._sensors_data_handler.update_device_count(self._connected_devices): + await coordinator.async_refresh() + async def close(self) -> None: """Close the connection.""" if self._api is not None: @@ -230,6 +366,16 @@ def update_options(self, new_options: Dict) -> bool: self._options.update(new_options) return req_reload + @property + def device_info(self) -> Dict[str, Any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, "AsusWRT")}, + "name": self._host, + "model": "Asus Router", + "manufacturer": "Asus", + } + @property def signal_device_new(self) -> str: """Event specific per AsusWrt entry to signal new device.""" @@ -250,6 +396,11 @@ def devices(self) -> Dict[str, Any]: """Return devices.""" return self._devices + @property + def sensors_coordinator(self) -> Dict[str, Any]: + """Return sensors coordinators.""" + return self._sensors_coordinator + @property def api(self) -> AsusWrt: """Return router API.""" diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 2a39d339f06901..2751e161ea9102 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -1,236 +1,169 @@ """Asuswrt status sensors.""" -from datetime import timedelta -import enum import logging -from typing import Any, Dict, List, Optional - -from aioasuswrt.asuswrt import AsusWrt +from numbers import Number +from typing import Dict from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME, DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND +from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) -from .const import DATA_ASUSWRT, DOMAIN, SENSOR_TYPES - -UPLOAD_ICON = "mdi:upload-network" -DOWNLOAD_ICON = "mdi:download-network" +from .const import ( + DATA_ASUSWRT, + DOMAIN, + SENSOR_CONNECTED_DEVICE, + SENSOR_RX_BYTES, + SENSOR_RX_RATES, + SENSOR_TX_BYTES, + SENSOR_TX_RATES, +) +from .router import KEY_COORDINATOR, KEY_SENSORS, AsusWrtRouter + +DEFAULT_PREFIX = "Asuswrt" + +SENSOR_DEVICE_CLASS = "device_class" +SENSOR_ICON = "icon" +SENSOR_NAME = "name" +SENSOR_UNIT = "unit" +SENSOR_FACTOR = "factor" +SENSOR_DEFAULT_ENABLED = "default_enabled" + +UNIT_DEVICES = "Devices" + +CONNECTION_SENSORS = { + SENSOR_CONNECTED_DEVICE: { + SENSOR_NAME: "Devices Connected", + SENSOR_UNIT: UNIT_DEVICES, + SENSOR_FACTOR: 0, + SENSOR_ICON: "mdi:router-network", + SENSOR_DEVICE_CLASS: None, + SENSOR_DEFAULT_ENABLED: True, + }, + SENSOR_RX_RATES: { + SENSOR_NAME: "Download Speed", + SENSOR_UNIT: DATA_RATE_MEGABITS_PER_SECOND, + SENSOR_FACTOR: 125000, + SENSOR_ICON: "mdi:download-network", + SENSOR_DEVICE_CLASS: None, + }, + SENSOR_TX_RATES: { + SENSOR_NAME: "Upload Speed", + SENSOR_UNIT: DATA_RATE_MEGABITS_PER_SECOND, + SENSOR_FACTOR: 125000, + SENSOR_ICON: "mdi:upload-network", + SENSOR_DEVICE_CLASS: None, + }, + SENSOR_RX_BYTES: { + SENSOR_NAME: "Download", + SENSOR_UNIT: DATA_GIGABYTES, + SENSOR_FACTOR: 1000000000, + SENSOR_ICON: "mdi:download", + SENSOR_DEVICE_CLASS: None, + }, + SENSOR_TX_BYTES: { + SENSOR_NAME: "Upload", + SENSOR_UNIT: DATA_GIGABYTES, + SENSOR_FACTOR: 1000000000, + SENSOR_ICON: "mdi:upload", + SENSOR_DEVICE_CLASS: None, + }, +} _LOGGER = logging.getLogger(__name__) -@enum.unique -class _SensorTypes(enum.Enum): - DEVICES = "devices" - UPLOAD = "upload" - DOWNLOAD = "download" - DOWNLOAD_SPEED = "download_speed" - UPLOAD_SPEED = "upload_speed" - - @property - def unit_of_measurement(self) -> Optional[str]: - """Return a string with the unit of the sensortype.""" - if self in (_SensorTypes.UPLOAD, _SensorTypes.DOWNLOAD): - return DATA_GIGABYTES - if self in (_SensorTypes.UPLOAD_SPEED, _SensorTypes.DOWNLOAD_SPEED): - return DATA_RATE_MEGABITS_PER_SECOND - if self == _SensorTypes.DEVICES: - return "devices" - return None - - @property - def icon(self) -> Optional[str]: - """Return the expected icon for the sensortype.""" - if self in (_SensorTypes.UPLOAD, _SensorTypes.UPLOAD_SPEED): - return UPLOAD_ICON - if self in (_SensorTypes.DOWNLOAD, _SensorTypes.DOWNLOAD_SPEED): - return DOWNLOAD_ICON - return None - - @property - def sensor_name(self) -> Optional[str]: - """Return the name of the sensor.""" - if self is _SensorTypes.DEVICES: - return "Asuswrt Devices Connected" - if self is _SensorTypes.UPLOAD: - return "Asuswrt Upload" - if self is _SensorTypes.DOWNLOAD: - return "Asuswrt Download" - if self is _SensorTypes.UPLOAD_SPEED: - return "Asuswrt Upload Speed" - if self is _SensorTypes.DOWNLOAD_SPEED: - return "Asuswrt Download Speed" - return None - - @property - def is_speed(self) -> bool: - """Return True if the type is an upload/download speed.""" - return self in (_SensorTypes.UPLOAD_SPEED, _SensorTypes.DOWNLOAD_SPEED) - - @property - def is_size(self) -> bool: - """Return True if the type is the total upload/download size.""" - return self in (_SensorTypes.UPLOAD, _SensorTypes.DOWNLOAD) - +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the sensors.""" + router: AsusWrtRouter = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] + entities = [] -class _SensorInfo: - """Class handling sensor information.""" + for sensor_data in router.sensors_coordinator.values(): + coordinator = sensor_data[KEY_COORDINATOR] + sensors = sensor_data[KEY_SENSORS] + for sensor_key in sensors: + if sensor_key in CONNECTION_SENSORS: + entities.append( + AsusWrtSensor( + coordinator, router, sensor_key, CONNECTION_SENSORS[sensor_key] + ) + ) - def __init__(self, sensor_type: _SensorTypes): - """Initialize the handler class.""" - self.type = sensor_type - self.enabled = False + async_add_entities(entities, True) -async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities -) -> None: - """Set up the asuswrt sensors.""" - - router = hass.data[DOMAIN][entry.entry_id][DATA_ASUSWRT] - api: AsusWrt = router.api - device_name = entry.data.get(CONF_NAME, "AsusWRT") - - # Let's discover the valid sensor types. - sensors = [_SensorInfo(_SensorTypes(x)) for x in SENSOR_TYPES] - - data_handler = AsuswrtDataHandler(sensors, api) - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name="sensor", - update_method=data_handler.update_data, - # Polling interval. Will only be polled if there are subscribers. - update_interval=timedelta(seconds=30), - ) - - await coordinator.async_refresh() - async_add_entities( - [AsuswrtSensor(coordinator, data_handler, device_name, x.type) for x in sensors] - ) - - -class AsuswrtDataHandler: - """Class handling the API updates.""" - - def __init__(self, sensors: List[_SensorInfo], api: AsusWrt): - """Initialize the handler class.""" - self._api = api - self._sensors = sensors - self._connected = True - - def enable_sensor(self, sensor_type: _SensorTypes): - """Enable a specific sensor type.""" - for index, sensor in enumerate(self._sensors): - if sensor.type == sensor_type: - self._sensors[index].enabled = True - return - - def disable_sensor(self, sensor_type: _SensorTypes): - """Disable a specific sensor type.""" - for index, sensor in enumerate(self._sensors): - if sensor.type == sensor_type: - self._sensors[index].enabled = False - return - - async def update_data(self) -> Dict[_SensorTypes, Any]: - """Fetch the relevant data from the router.""" - ret_dict: Dict[_SensorTypes, Any] = {} - try: - if _SensorTypes.DEVICES in [x.type for x in self._sensors if x.enabled]: - # Let's check the nr of devices. - devices = await self._api.async_get_connected_devices() - ret_dict[_SensorTypes.DEVICES] = len(devices) - - if any(x.type.is_speed for x in self._sensors if x.enabled): - # Let's check the upload and download speed - speed = await self._api.async_get_current_transfer_rates() - ret_dict[_SensorTypes.DOWNLOAD_SPEED] = round(speed[0] / 125000, 2) - ret_dict[_SensorTypes.UPLOAD_SPEED] = round(speed[1] / 125000, 2) - - if any(x.type.is_size for x in self._sensors if x.enabled): - rates = await self._api.async_get_bytes_total() - ret_dict[_SensorTypes.DOWNLOAD] = round(rates[0] / 1000000000, 1) - ret_dict[_SensorTypes.UPLOAD] = round(rates[1] / 1000000000, 1) - - if not self._connected: - # Log a successful reconnect - self._connected = True - _LOGGER.warning("Successfully reconnected to ASUS router") - - except OSError as err: - if self._connected: - # Log the first time connection was lost - _LOGGER.warning("Lost connection to router error due to: '%s'", err) - self._connected = False - - return ret_dict - - -class AsuswrtSensor(CoordinatorEntity): - """The asuswrt specific sensor class.""" +class AsusWrtSensor(CoordinatorEntity): + """Representation of a AsusWrt sensor.""" def __init__( self, coordinator: DataUpdateCoordinator, - data_handler: AsuswrtDataHandler, - device_name: str, - sensor_type: _SensorTypes, - ): - """Initialize the sensor class.""" + router: AsusWrtRouter, + sensor_type: str, + sensor: Dict[str, any], + ) -> None: + """Initialize a AsusWrt sensor.""" super().__init__(coordinator) - self._handler = data_handler - self._device_name = device_name - self._type = sensor_type + self._router = router + self._sensor_type = sensor_type + self._name = f"{DEFAULT_PREFIX} {sensor[SENSOR_NAME]}" + self._unique_id = f"{DOMAIN} {self._name}" + self._unit = sensor[SENSOR_UNIT] + self._factor = sensor[SENSOR_FACTOR] + self._icon = sensor[SENSOR_ICON] + self._device_class = sensor[SENSOR_DEVICE_CLASS] + self._default_enabled = sensor.get(SENSOR_DEFAULT_ENABLED, False) @property - def state(self): - """Return the state of the sensor.""" - return self.coordinator.data.get(self._type) + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._default_enabled @property - def name(self) -> str: - """Return the name of the sensor.""" - return self._type.sensor_name + def state(self) -> str: + """Return current state.""" + state = self.coordinator.data.get(self._sensor_type) + if state is None: + return None + if self._factor and isinstance(state, Number): + return round(state / self._factor, 2) + return state @property - def icon(self) -> Optional[str]: - """Return the icon to use in the frontend.""" - return self._type.icon + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id @property - def unit_of_measurement(self) -> Optional[str]: - """Return the unit.""" - return self._type.unit_of_measurement + def name(self) -> str: + """Return the name.""" + return self._name @property - def unique_id(self) -> str: - """Return the unique_id of the sensor.""" - return f"{DOMAIN} {self._type.sensor_name}" + def unit_of_measurement(self) -> str: + """Return the unit.""" + return self._unit @property - def device_info(self) -> Dict[str, any]: - """Return the device information.""" - return { - "identifiers": {(DOMAIN, "AsusWRT")}, - "name": self._device_name, - "model": "Asus Router", - "manufacturer": "Asus", - } + def icon(self) -> str: + """Return the icon.""" + return self._icon @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return False + def device_class(self) -> str: + """Return the device_class.""" + return self._device_class - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - self._handler.enable_sensor(self._type) - await super().async_added_to_hass() + @property + def device_state_attributes(self) -> Dict[str, any]: + """Return the attributes.""" + return {"hostname": self._router.host} - async def async_will_remove_from_hass(self): - """Call when entity is removed from hass.""" - self._handler.disable_sensor(self._type) + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return self._router.device_info diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 994111370fda88..334f06d85a6164 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -7,7 +7,7 @@ from homeassistant.components import device_tracker, sensor from homeassistant.components.asuswrt.const import DOMAIN -from homeassistant.components.asuswrt.sensor import _SensorTypes +from homeassistant.components.asuswrt.sensor import DEFAULT_PREFIX from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME from homeassistant.const import ( CONF_HOST, @@ -66,49 +66,56 @@ async def test_sensors(hass, connect): """Test creating an AsusWRT sensor.""" entity_reg = await hass.helpers.entity_registry.async_get_registry() + # init config entry + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + options={CONF_CONSIDER_HOME: 60}, + ) + + # init variable + unique_id = DOMAIN + name_prefix = DEFAULT_PREFIX + obj_prefix = name_prefix.lower() + sensor_prefix = f"{sensor.DOMAIN}.{obj_prefix}" + # Pre-enable the status sensor entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.DEVICES).sensor_name}", - suggested_object_id="asuswrt_connected_devices", + f"{unique_id} {name_prefix} Devices Connected", + suggested_object_id=f"{obj_prefix}_devices_connected", disabled_by=None, ) entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.DOWNLOAD_SPEED).sensor_name}", - suggested_object_id="asuswrt_download_speed", + f"{unique_id} {name_prefix} Download Speed", + suggested_object_id=f"{obj_prefix}_download_speed", disabled_by=None, ) entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.DOWNLOAD).sensor_name}", - suggested_object_id="asuswrt_download", + f"{unique_id} {name_prefix} Download", + suggested_object_id=f"{obj_prefix}_download", disabled_by=None, ) entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.UPLOAD_SPEED).sensor_name}", - suggested_object_id="asuswrt_upload_speed", + f"{unique_id} {name_prefix} Upload Speed", + suggested_object_id=f"{obj_prefix}_upload_speed", disabled_by=None, ) entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{DOMAIN} {_SensorTypes(_SensorTypes.UPLOAD).sensor_name}", - suggested_object_id="asuswrt_upload", + f"{unique_id} {name_prefix} Upload", + suggested_object_id=f"{obj_prefix}_upload", disabled_by=None, ) - # init config entry - config_entry = MockConfigEntry( - domain=DOMAIN, - data=CONFIG_DATA, - options={CONF_CONSIDER_HOME: 60}, - ) config_entry.add_to_hass(hass) # initial devices setup @@ -119,11 +126,11 @@ async def test_sensors(hass, connect): assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_connected_devices").state == "2" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download_speed").state == "160.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_download").state == "60.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload_speed").state == "80.0" - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_upload").state == "50.0" + assert hass.states.get(f"{sensor_prefix}_download_speed").state == "160.0" + assert hass.states.get(f"{sensor_prefix}_download").state == "60.0" + assert hass.states.get(f"{sensor_prefix}_upload_speed").state == "80.0" + assert hass.states.get(f"{sensor_prefix}_upload").state == "50.0" + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" # add one device and remove another MOCK_DEVICES.pop("a1:b1:c1:d1:e1:f1") @@ -137,7 +144,7 @@ async def test_sensors(hass, connect): assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testthree").state == STATE_HOME - assert hass.states.get(f"{sensor.DOMAIN}.asuswrt_connected_devices").state == "2" + assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" hass.config_entries.async_update_entry( config_entry, options={CONF_CONSIDER_HOME: 0} From 2472dad1faaf7680fe4c9debb2a6fa17e38da732 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Fri, 5 Mar 2021 17:05:36 -0600 Subject: [PATCH 1052/1818] Bump amcrest package version to 1.7.1 (#47483) --- 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 0b6fbbdc09a02a..0b7a59edb79c44 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.7.0"], + "requirements": ["amcrest==1.7.1"], "dependencies": ["ffmpeg"], "codeowners": ["@pnbruckner"] } diff --git a/requirements_all.txt b/requirements_all.txt index b7d76130393449..446b30b223bc52 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -245,7 +245,7 @@ alpha_vantage==2.3.1 ambiclimate==0.2.1 # homeassistant.components.amcrest -amcrest==1.7.0 +amcrest==1.7.1 # homeassistant.components.androidtv androidtv[async]==0.0.57 From 8f31b09b55f4c87d6a274ce7340f26c944934f99 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 6 Mar 2021 00:33:26 +0100 Subject: [PATCH 1053/1818] Complete typing on TwenteMilieu integration (#47480) --- .../components/twentemilieu/__init__.py | 23 ++++++++-------- .../components/twentemilieu/config_flow.py | 26 +++++++++++-------- .../components/twentemilieu/sensor.py | 16 +++++++----- .../twentemilieu/test_config_flow.py | 20 ++++++++++---- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/twentemilieu/__init__.py b/homeassistant/components/twentemilieu/__init__.py index 06c2cb27f35780..f53e4463146f47 100644 --- a/homeassistant/components/twentemilieu/__init__.py +++ b/homeassistant/components/twentemilieu/__init__.py @@ -1,7 +1,8 @@ """Support for Twente Milieu.""" +from __future__ import annotations + import asyncio from datetime import timedelta -from typing import Optional from twentemilieu import TwenteMilieu import voluptuous as vol @@ -15,11 +16,12 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv 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 -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import ConfigType SCAN_INTERVAL = timedelta(seconds=3600) @@ -27,9 +29,7 @@ SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_ID): cv.string}) -async def _update_twentemilieu( - hass: HomeAssistantType, unique_id: Optional[str] -) -> None: +async def _update_twentemilieu(hass: HomeAssistant, unique_id: str | None) -> None: """Update Twente Milieu.""" if unique_id is not None: twentemilieu = hass.data[DOMAIN].get(unique_id) @@ -37,16 +37,15 @@ async def _update_twentemilieu( await twentemilieu.update() async_dispatcher_send(hass, DATA_UPDATE, unique_id) else: - tasks = [] - for twentemilieu in hass.data[DOMAIN].values(): - tasks.append(twentemilieu.update()) - await asyncio.wait(tasks) + await asyncio.wait( + [twentemilieu.update() for twentemilieu in hass.data[DOMAIN].values()] + ) for uid in hass.data[DOMAIN]: async_dispatcher_send(hass, DATA_UPDATE, uid) -async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Twente Milieu components.""" async def update(call) -> None: @@ -59,7 +58,7 @@ async def update(call) -> None: return True -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Twente Milieu from a config entry.""" session = async_get_clientsession(hass) twentemilieu = TwenteMilieu( @@ -85,7 +84,7 @@ async def _interval_update(now=None) -> None: return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Twente Milieu config entry.""" await hass.config_entries.async_forward_entry_unload(entry, "sensor") diff --git a/homeassistant/components/twentemilieu/config_flow.py b/homeassistant/components/twentemilieu/config_flow.py index 76c9f33b3e9ff8..769d24e334a320 100644 --- a/homeassistant/components/twentemilieu/config_flow.py +++ b/homeassistant/components/twentemilieu/config_flow.py @@ -1,4 +1,8 @@ """Config flow to configure the Twente Milieu integration.""" +from __future__ import annotations + +from typing import Any + from twentemilieu import ( TwenteMilieu, TwenteMilieuAddressError, @@ -7,25 +11,23 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.twentemilieu.const import ( - CONF_HOUSE_LETTER, - CONF_HOUSE_NUMBER, - CONF_POST_CODE, - DOMAIN, -) from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ID from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .const import CONF_HOUSE_LETTER, CONF_HOUSE_NUMBER, CONF_POST_CODE +from .const import DOMAIN # pylint: disable=unused-import + -@config_entries.HANDLERS.register(DOMAIN) -class TwenteMilieuFlowHandler(ConfigFlow): +class TwenteMilieuFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Twente Milieu config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - async def _show_setup_form(self, errors=None): + async def _show_setup_form( + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -39,7 +41,9 @@ async def _show_setup_form(self, errors=None): errors=errors or {}, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return await self._show_setup_form(user_input) @@ -70,7 +74,7 @@ async def async_step_user(self, user_input=None): return self.async_abort(reason="already_configured") return self.async_create_entry( - title=unique_id, + title=str(unique_id), data={ CONF_ID: unique_id, CONF_POST_CODE: user_input[CONF_POST_CODE], diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index 92194e1217235e..34da746cf0607f 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -1,5 +1,7 @@ """Support for Twente Milieu sensors.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any, Callable from twentemilieu import ( WASTE_TYPE_NON_RECYCLABLE, @@ -10,20 +12,22 @@ TwenteMilieuConnectionError, ) -from homeassistant.components.twentemilieu.const import DATA_UPDATE, DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import HomeAssistantType + +from .const import DATA_UPDATE, DOMAIN PARALLEL_UPDATES = 1 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Twente Milieu sensor based on a config entry.""" twentemilieu = hass.data[DOMAIN][entry.data[CONF_ID]] @@ -142,7 +146,7 @@ async def async_update(self) -> None: self._state = next_pickup.date().isoformat() @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about Twente Milieu.""" return { "identifiers": {(DOMAIN, self._unique_id)}, diff --git a/tests/components/twentemilieu/test_config_flow.py b/tests/components/twentemilieu/test_config_flow.py index ec3edf73f29d0c..27fd86d0868f2e 100644 --- a/tests/components/twentemilieu/test_config_flow.py +++ b/tests/components/twentemilieu/test_config_flow.py @@ -10,8 +10,10 @@ DOMAIN, ) from homeassistant.const import CONF_ID, CONTENT_TYPE_JSON +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker FIXTURE_USER_INPUT = { CONF_ID: "12345", @@ -21,7 +23,7 @@ } -async def test_show_set_form(hass): +async def test_show_set_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" flow = config_flow.TwenteMilieuFlowHandler() flow.hass = hass @@ -31,7 +33,9 @@ async def test_show_set_form(hass): assert result["step_id"] == "user" -async def test_connection_error(hass, aioclient_mock): +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show user form on Twente Milieu connection error.""" aioclient_mock.post( "https://twentemilieuapi.ximmio.com/api/FetchAdress", exc=aiohttp.ClientError @@ -46,7 +50,9 @@ async def test_connection_error(hass, aioclient_mock): assert result["errors"] == {"base": "cannot_connect"} -async def test_invalid_address(hass, aioclient_mock): +async def test_invalid_address( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show user form on Twente Milieu invalid address error.""" aioclient_mock.post( "https://twentemilieuapi.ximmio.com/api/FetchAdress", @@ -63,7 +69,9 @@ async def test_invalid_address(hass, aioclient_mock): assert result["errors"] == {"base": "invalid_address"} -async def test_address_already_set_up(hass, aioclient_mock): +async def test_address_already_set_up( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we abort if address has already been set up.""" MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT, title="12345").add_to_hass( hass @@ -83,7 +91,9 @@ async def test_address_already_set_up(hass, aioclient_mock): assert result["reason"] == "already_configured" -async def test_full_flow_implementation(hass, aioclient_mock): +async def test_full_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test registering an integration and finishing flow works.""" aioclient_mock.post( "https://twentemilieuapi.ximmio.com/api/FetchAdress", From 4c181bbfe5f4f62dbd467c589c010ebecd844bd4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Mar 2021 15:34:18 -0800 Subject: [PATCH 1054/1818] Raise error instead of crashing when template passed to call service target (#47467) --- .../components/websocket_api/commands.py | 16 +++++--- .../components/websocket_api/test_commands.py | 40 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index ddd7548cd684e3..53531cf9ba9931 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -13,10 +13,9 @@ TemplateError, Unauthorized, ) -from homeassistant.helpers import config_validation as cv, entity +from homeassistant.helpers import config_validation as cv, entity, template from homeassistant.helpers.event import TrackTemplate, async_track_template_result from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.template import Template from homeassistant.loader import IntegrationNotFound, async_get_integration from . import const, decorators, messages @@ -132,6 +131,11 @@ async def handle_call_service(hass, connection, msg): if msg["domain"] == HASS_DOMAIN and msg["service"] in ["restart", "stop"]: blocking = False + # We do not support templates. + target = msg.get("target") + if template.is_complex(target): + raise vol.Invalid("Templates are not supported here") + try: context = connection.context(msg) await hass.services.async_call( @@ -140,7 +144,7 @@ async def handle_call_service(hass, connection, msg): msg.get("service_data"), blocking, context, - target=msg.get("target"), + target=target, ) connection.send_message( messages.result_message(msg["id"], {"context": context}) @@ -256,14 +260,14 @@ def handle_ping(hass, connection, msg): async def handle_render_template(hass, connection, msg): """Handle render_template command.""" template_str = msg["template"] - template = Template(template_str, hass) + template_obj = template.Template(template_str, hass) variables = msg.get("variables") timeout = msg.get("timeout") info = None if timeout: try: - timed_out = await template.async_render_will_timeout(timeout) + timed_out = await template_obj.async_render_will_timeout(timeout) except TemplateError as ex: connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) return @@ -294,7 +298,7 @@ def _template_listener(event, updates): try: info = async_track_template_result( hass, - [TrackTemplate(template, variables)], + [TrackTemplate(template_obj, variables)], _template_listener, raise_on_template_error=True, ) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 1f7abc42c4e939..f596db63c5ea70 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -21,13 +21,7 @@ async def test_call_service(hass, websocket_client): """Test call service command.""" - calls = [] - - @callback - def service_call(call): - calls.append(call) - - hass.services.async_register("domain_test", "test_service", service_call) + calls = async_mock_service(hass, "domain_test", "test_service") await websocket_client.send_json( { @@ -54,13 +48,7 @@ def service_call(call): async def test_call_service_target(hass, websocket_client): """Test call service command with target.""" - calls = [] - - @callback - def service_call(call): - calls.append(call) - - hass.services.async_register("domain_test", "test_service", service_call) + calls = async_mock_service(hass, "domain_test", "test_service") await websocket_client.send_json( { @@ -93,6 +81,28 @@ def service_call(call): } +async def test_call_service_target_template(hass, websocket_client): + """Test call service command with target does not allow template.""" + await websocket_client.send_json( + { + "id": 5, + "type": "call_service", + "domain": "domain_test", + "service": "test_service", + "service_data": {"hello": "world"}, + "target": { + "entity_id": "{{ 1 }}", + }, + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert not msg["success"] + assert msg["error"]["code"] == const.ERR_INVALID_FORMAT + + async def test_call_service_not_found(hass, websocket_client): """Test call service command.""" await websocket_client.send_json( @@ -232,7 +242,6 @@ async def unknown_error_call(_): ) msg = await websocket_client.receive_json() - print(msg) assert msg["id"] == 5 assert msg["type"] == const.TYPE_RESULT assert msg["success"] is False @@ -249,7 +258,6 @@ async def unknown_error_call(_): ) msg = await websocket_client.receive_json() - print(msg) assert msg["id"] == 6 assert msg["type"] == const.TYPE_RESULT assert msg["success"] is False From 9fa0de86005716817931482dcd12cf53433bd0c5 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Fri, 5 Mar 2021 20:22:40 +0100 Subject: [PATCH 1055/1818] Update pyotgw to 1.1b1 (#47446) --- homeassistant/components/opentherm_gw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 066cee61c050be..baa02dc3f4604c 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -2,7 +2,7 @@ "domain": "opentherm_gw", "name": "OpenTherm Gateway", "documentation": "https://www.home-assistant.io/integrations/opentherm_gw", - "requirements": ["pyotgw==1.0b1"], + "requirements": ["pyotgw==1.1b1"], "codeowners": ["@mvn23"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 5d69ddd65bdb5c..86a8a705a5f795 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1603,7 +1603,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==1.0b1 +pyotgw==1.1b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5fdf5100605201..fb684893daf198 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -851,7 +851,7 @@ pyopenuv==1.0.9 pyopnsense==0.2.0 # homeassistant.components.opentherm_gw -pyotgw==1.0b1 +pyotgw==1.1b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From f99ef25f8801ac5bcc1295d99f5988b7c3d23f6f Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 5 Mar 2021 21:41:55 +0100 Subject: [PATCH 1056/1818] Fix issue at Netatmo startup (#47452) --- homeassistant/components/netatmo/camera.py | 48 ++++++++++--------- homeassistant/components/netatmo/climate.py | 8 +++- .../components/netatmo/data_handler.py | 6 ++- homeassistant/components/netatmo/light.py | 9 ++-- homeassistant/components/netatmo/sensor.py | 18 ++++++- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 6e55b884d4d19d..5163c9582b0095 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -7,6 +7,7 @@ from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.core import callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -49,15 +50,17 @@ async def async_setup_entry(hass, entry, async_add_entities): 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 + ) + + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + async def get_entities(): """Retrieve Netatmo entities.""" - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) - - data = data_handler.data - if not data.get(CAMERA_DATA_CLASS_NAME): + if not data_handler.data.get(CAMERA_DATA_CLASS_NAME): return [] data_class = data_handler.data[CAMERA_DATA_CLASS_NAME] @@ -94,24 +97,25 @@ async def get_entities(): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(CAMERA_DATA_CLASS_NAME, None) + platform = entity_platform.current_platform.get() - if data_handler.data[CAMERA_DATA_CLASS_NAME] is not None: - platform.async_register_entity_service( - SERVICE_SET_PERSONS_HOME, - {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])}, - "_service_set_persons_home", - ) - platform.async_register_entity_service( - SERVICE_SET_PERSON_AWAY, - {vol.Optional(ATTR_PERSON): cv.string}, - "_service_set_person_away", - ) - platform.async_register_entity_service( - SERVICE_SET_CAMERA_LIGHT, - {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)}, - "_service_set_camera_light", - ) + platform.async_register_entity_service( + SERVICE_SET_PERSONS_HOME, + {vol.Required(ATTR_PERSONS): vol.All(cv.ensure_list, [cv.string])}, + "_service_set_persons_home", + ) + platform.async_register_entity_service( + SERVICE_SET_PERSON_AWAY, + {vol.Optional(ATTR_PERSON): cv.string}, + "_service_set_person_away", + ) + platform.async_register_entity_service( + SERVICE_SET_CAMERA_LIGHT, + {vol.Required(ATTR_CAMERA_LIGHT_MODE): vol.In(CAMERA_LIGHT_MODES)}, + "_service_set_camera_light", + ) class NetatmoCamera(NetatmoBase, Camera): diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index a53f7f9fb089e1..d12ee9263db43c 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -25,6 +25,7 @@ TEMP_CELSIUS, ) from homeassistant.core import callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -81,6 +82,7 @@ STATE_NETATMO_AWAY: PRESET_AWAY, STATE_NETATMO_OFF: STATE_NETATMO_OFF, STATE_NETATMO_MANUAL: STATE_NETATMO_MANUAL, + STATE_NETATMO_HOME: PRESET_SCHEDULE, } HVAC_MAP_NETATMO = { @@ -111,8 +113,8 @@ async def async_setup_entry(hass, entry, async_add_entities): ) home_data = data_handler.data.get(HOMEDATA_DATA_CLASS_NAME) - if not home_data: - return + if HOMEDATA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady async def get_entities(): """Retrieve Netatmo entities.""" @@ -151,6 +153,8 @@ async def get_entities(): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(HOMEDATA_DATA_CLASS_NAME, None) + platform = entity_platform.current_platform.get() if home_data is not None: diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 9bc4b197f1b77b..be0120bd1a0a31 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -129,7 +129,11 @@ async def async_fetch_data(self, data_class, data_class_entry, **kwargs): if update_callback: update_callback() - except (pyatmo.NoDevice, pyatmo.ApiError) as err: + except pyatmo.NoDevice as err: + _LOGGER.debug(err) + self.data[data_class_entry] = None + + except pyatmo.ApiError as err: _LOGGER.debug(err) async def register_data_class( diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index dc8bf3f1fc8c01..eed12f048c85e3 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -31,18 +31,15 @@ async def async_setup_entry(hass, entry, async_add_entities): data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + if CAMERA_DATA_CLASS_NAME not in data_handler.data: + raise PlatformNotReady + async def get_entities(): """Retrieve Netatmo entities.""" - await data_handler.register_data_class( - CAMERA_DATA_CLASS_NAME, CAMERA_DATA_CLASS_NAME, None - ) entities = [] all_cameras = [] - if CAMERA_DATA_CLASS_NAME not in data_handler.data: - raise PlatformNotReady - try: for home in data_handler.data[CAMERA_DATA_CLASS_NAME].cameras.values(): for camera in home.values(): diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index f848444481811d..9176b670bea4e9 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -20,6 +20,7 @@ TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.device_registry import async_entries_for_config_entry from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -129,14 +130,25 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up the Netatmo weather and homecoach platform.""" data_handler = hass.data[DOMAIN][entry.entry_id][DATA_HANDLER] + await data_handler.register_data_class( + WEATHERSTATION_DATA_CLASS_NAME, WEATHERSTATION_DATA_CLASS_NAME, None + ) + await data_handler.register_data_class( + HOMECOACH_DATA_CLASS_NAME, HOMECOACH_DATA_CLASS_NAME, None + ) + async def find_entities(data_class_name): """Find all entities.""" - await data_handler.register_data_class(data_class_name, data_class_name, None) + if data_class_name not in data_handler.data: + raise PlatformNotReady all_module_infos = {} data = data_handler.data - if not data.get(data_class_name): + if data_class_name not in data: + return [] + + if data[data_class_name] is None: return [] data_class = data[data_class_name] @@ -174,6 +186,8 @@ async def find_entities(data_class_name): NetatmoSensor(data_handler, data_class_name, module, condition) ) + await data_handler.unregister_data_class(data_class_name, None) + return entities for data_class_name in [ From d11da43551bed2beeeec057a141edf2777094034 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Mar 2021 18:43:26 +0100 Subject: [PATCH 1057/1818] Fix Hue scene overriding Hue default transition times (#47454) --- homeassistant/components/hue/bridge.py | 7 ++----- homeassistant/components/hue/const.py | 2 -- tests/components/hue/test_bridge.py | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 201e9f3a546382..dc9b56fcdfeea0 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -19,7 +19,6 @@ CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_UNREACHABLE, - DEFAULT_SCENE_TRANSITION, LOGGER, ) from .errors import AuthenticationRequired, CannotConnect @@ -34,9 +33,7 @@ { vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string, - vol.Optional( - ATTR_TRANSITION, default=DEFAULT_SCENE_TRANSITION - ): cv.positive_int, + vol.Optional(ATTR_TRANSITION): cv.positive_int, } ) # How long should we sleep if the hub is busy @@ -209,7 +206,7 @@ async def hue_activate_scene(self, call, updated=False, hide_warnings=False): """Service to call directly into bridge to set scenes.""" group_name = call.data[ATTR_GROUP_NAME] scene_name = call.data[ATTR_SCENE_NAME] - transition = call.data.get(ATTR_TRANSITION, DEFAULT_SCENE_TRANSITION) + transition = call.data.get(ATTR_TRANSITION) group = next( (group for group in self.api.groups.values() if group.name == group_name), diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index b782ce70193890..8d01617073b8d3 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -14,8 +14,6 @@ CONF_ALLOW_HUE_GROUPS = "allow_hue_groups" DEFAULT_ALLOW_HUE_GROUPS = False -DEFAULT_SCENE_TRANSITION = 4 - GROUP_TYPE_LIGHT_GROUP = "LightGroup" GROUP_TYPE_ROOM = "Room" GROUP_TYPE_LUMINAIRE = "Luminaire" diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 29bc2acf03aea6..093f6356b09004 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -189,6 +189,7 @@ async def test_hue_activate_scene(hass, mock_api): assert len(mock_api.mock_requests) == 3 assert mock_api.mock_requests[2]["json"]["scene"] == "scene_1" + assert "transitiontime" not in mock_api.mock_requests[2]["json"] assert mock_api.mock_requests[2]["path"] == "groups/group_1/action" From a4369fc352f776e55ab0b5e57e11d492cacd9d98 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 5 Mar 2021 18:42:20 +0100 Subject: [PATCH 1058/1818] Bump version with fix for v1 (#47458) --- homeassistant/components/philips_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/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index e1e1fa69b6ba1b..9ed1cedbf05ef2 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==2.3.0" + "ha-philipsjs==2.3.1" ], "codeowners": [ "@elupus" diff --git a/requirements_all.txt b/requirements_all.txt index 86a8a705a5f795..ba0cb7e2314490 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -721,7 +721,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.0 +ha-philipsjs==2.3.1 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb684893daf198..1e2c7ff0fb159b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -382,7 +382,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.0 +ha-philipsjs==2.3.1 # homeassistant.components.habitica habitipy==0.2.0 From ddc6cd6da125119e58283a895dfaf58b806f6e57 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 5 Mar 2021 18:42:08 +0100 Subject: [PATCH 1059/1818] Update frontend to 20210302.5 (#47462) --- 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 a5b4c6f10d5397..8093c65d91a9c2 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==20210302.4" + "home-assistant-frontend==20210302.5" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 919b53f64ed31f..0586b956f39548 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index ba0cb7e2314490..def728244f51bd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1e2c7ff0fb159b..58885bf07e795a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.4 +home-assistant-frontend==20210302.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 6c45a7d53329076a47b6c3bb36838d084fb5a7aa Mon Sep 17 00:00:00 2001 From: functionpointer Date: Fri, 5 Mar 2021 20:21:24 +0100 Subject: [PATCH 1060/1818] Use conn_made callback in MySensors (#47463) --- homeassistant/components/mysensors/const.py | 1 - homeassistant/components/mysensors/gateway.py | 42 ++++++++----------- homeassistant/components/mysensors/handler.py | 23 +--------- 3 files changed, 18 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 66bee128d4d27e..9116009d7b1814 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -29,7 +29,6 @@ DOMAIN: str = "mysensors" -MYSENSORS_GATEWAY_READY: str = "mysensors_gateway_ready_{}" MYSENSORS_GATEWAY_START_TASK: str = "mysensors_gateway_start_task_{}" MYSENSORS_GATEWAYS: str = "mysensors_gateways" PLATFORM: str = "platform" diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 4267ba5cbb3938..b6797cafb37b8c 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -26,7 +26,6 @@ CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, - MYSENSORS_GATEWAY_READY, MYSENSORS_GATEWAY_START_TASK, MYSENSORS_GATEWAYS, GatewayId, @@ -36,7 +35,7 @@ _LOGGER = logging.getLogger(__name__) -GATEWAY_READY_TIMEOUT = 15.0 +GATEWAY_READY_TIMEOUT = 20.0 MQTT_COMPONENT = "mqtt" @@ -64,24 +63,16 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo if user_input[CONF_DEVICE] == MQTT_COMPONENT: return True # dont validate mqtt. mqtt gateways dont send ready messages :( try: - gateway_ready = asyncio.Future() - - def gateway_ready_callback(msg): - msg_type = msg.gateway.const.MessageType(msg.type) - _LOGGER.debug("Received MySensors msg type %s: %s", msg_type.name, msg) - if msg_type.name != "internal": - return - internal = msg.gateway.const.Internal(msg.sub_type) - if internal.name != "I_GATEWAY_READY": - return - _LOGGER.debug("Received gateway ready") - gateway_ready.set_result(True) + gateway_ready = asyncio.Event() + + def on_conn_made(_: BaseAsyncGateway) -> None: + gateway_ready.set() gateway: Optional[BaseAsyncGateway] = await _get_gateway( hass, device=user_input[CONF_DEVICE], version=user_input[CONF_VERSION], - event_callback=gateway_ready_callback, + event_callback=lambda _: None, persistence_file=None, baud_rate=user_input.get(CONF_BAUD_RATE), tcp_port=user_input.get(CONF_TCP_PORT), @@ -92,12 +83,13 @@ def gateway_ready_callback(msg): ) if gateway is None: return False + gateway.on_conn_made = on_conn_made connect_task = None try: connect_task = asyncio.create_task(gateway.start()) - with async_timeout.timeout(20): - await gateway_ready + with async_timeout.timeout(GATEWAY_READY_TIMEOUT): + await gateway_ready.wait() return True except asyncio.TimeoutError: _LOGGER.info("Try gateway connect failed with timeout") @@ -280,6 +272,12 @@ async def _gw_start( hass: HomeAssistantType, entry: ConfigEntry, gateway: BaseAsyncGateway ): """Start the gateway.""" + gateway_ready = asyncio.Event() + + def gateway_connected(_: BaseAsyncGateway): + gateway_ready.set() + + gateway.on_conn_made = gateway_connected # Don't use hass.async_create_task to avoid holding up setup indefinitely. hass.data[DOMAIN][ MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) @@ -294,21 +292,15 @@ async def stop_this_gw(_: Event): if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return - gateway_ready = asyncio.Future() - gateway_ready_key = MYSENSORS_GATEWAY_READY.format(entry.entry_id) - hass.data[DOMAIN][gateway_ready_key] = gateway_ready - try: with async_timeout.timeout(GATEWAY_READY_TIMEOUT): - await gateway_ready + await gateway_ready.wait() except asyncio.TimeoutError: _LOGGER.warning( - "Gateway %s not ready after %s secs so continuing with setup", + "Gateway %s not connected after %s secs so continuing with setup", entry.data[CONF_DEVICE], GATEWAY_READY_TIMEOUT, ) - finally: - hass.data[DOMAIN].pop(gateway_ready_key, None) def _gw_callback_factory( diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index 10165a171e0a03..a47c9174b23bf8 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -8,14 +8,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import decorator -from .const import ( - CHILD_CALLBACK, - DOMAIN, - MYSENSORS_GATEWAY_READY, - NODE_CALLBACK, - DevId, - GatewayId, -) +from .const import CHILD_CALLBACK, NODE_CALLBACK, DevId, GatewayId from .device import get_mysensors_devices from .helpers import discover_mysensors_platform, validate_set_msg @@ -75,20 +68,6 @@ async def handle_sketch_version( _handle_node_update(hass, gateway_id, msg) -@HANDLERS.register("I_GATEWAY_READY") -async def handle_gateway_ready( - hass: HomeAssistantType, gateway_id: GatewayId, msg: Message -) -> None: - """Handle an internal gateway ready message. - - Set asyncio future result if gateway is ready. - """ - gateway_ready = hass.data[DOMAIN].get(MYSENSORS_GATEWAY_READY.format(gateway_id)) - if gateway_ready is None or gateway_ready.cancelled(): - return - gateway_ready.set_result(True) - - @callback def _handle_child_update( hass: HomeAssistantType, gateway_id: GatewayId, validated: Dict[str, List[DevId]] From 65859b41073643364bdf6830ff44b93d46a7707e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Mar 2021 12:41:36 -0500 Subject: [PATCH 1061/1818] Bump zwave-js-server-python to 0.21.1 (#47464) --- 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 2a6f036fa809ae..de0f2cc0a6f146 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.21.0"], + "requirements": ["zwave-js-server-python==0.21.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index def728244f51bd..ea34186457a070 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2397,4 +2397,4 @@ zigpy==0.32.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.0 +zwave-js-server-python==0.21.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 58885bf07e795a..360fd28143483d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1234,4 +1234,4 @@ zigpy-znp==0.4.0 zigpy==0.32.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.0 +zwave-js-server-python==0.21.1 From 90e0801c1bb70d7cca05daf219b6a1f2d9bf807a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Mar 2021 15:34:18 -0800 Subject: [PATCH 1062/1818] Raise error instead of crashing when template passed to call service target (#47467) --- .../components/websocket_api/commands.py | 16 +++++--- .../components/websocket_api/test_commands.py | 40 +++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index ddd7548cd684e3..53531cf9ba9931 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -13,10 +13,9 @@ TemplateError, Unauthorized, ) -from homeassistant.helpers import config_validation as cv, entity +from homeassistant.helpers import config_validation as cv, entity, template from homeassistant.helpers.event import TrackTemplate, async_track_template_result from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.template import Template from homeassistant.loader import IntegrationNotFound, async_get_integration from . import const, decorators, messages @@ -132,6 +131,11 @@ async def handle_call_service(hass, connection, msg): if msg["domain"] == HASS_DOMAIN and msg["service"] in ["restart", "stop"]: blocking = False + # We do not support templates. + target = msg.get("target") + if template.is_complex(target): + raise vol.Invalid("Templates are not supported here") + try: context = connection.context(msg) await hass.services.async_call( @@ -140,7 +144,7 @@ async def handle_call_service(hass, connection, msg): msg.get("service_data"), blocking, context, - target=msg.get("target"), + target=target, ) connection.send_message( messages.result_message(msg["id"], {"context": context}) @@ -256,14 +260,14 @@ def handle_ping(hass, connection, msg): async def handle_render_template(hass, connection, msg): """Handle render_template command.""" template_str = msg["template"] - template = Template(template_str, hass) + template_obj = template.Template(template_str, hass) variables = msg.get("variables") timeout = msg.get("timeout") info = None if timeout: try: - timed_out = await template.async_render_will_timeout(timeout) + timed_out = await template_obj.async_render_will_timeout(timeout) except TemplateError as ex: connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) return @@ -294,7 +298,7 @@ def _template_listener(event, updates): try: info = async_track_template_result( hass, - [TrackTemplate(template, variables)], + [TrackTemplate(template_obj, variables)], _template_listener, raise_on_template_error=True, ) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 1f7abc42c4e939..f596db63c5ea70 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -21,13 +21,7 @@ async def test_call_service(hass, websocket_client): """Test call service command.""" - calls = [] - - @callback - def service_call(call): - calls.append(call) - - hass.services.async_register("domain_test", "test_service", service_call) + calls = async_mock_service(hass, "domain_test", "test_service") await websocket_client.send_json( { @@ -54,13 +48,7 @@ def service_call(call): async def test_call_service_target(hass, websocket_client): """Test call service command with target.""" - calls = [] - - @callback - def service_call(call): - calls.append(call) - - hass.services.async_register("domain_test", "test_service", service_call) + calls = async_mock_service(hass, "domain_test", "test_service") await websocket_client.send_json( { @@ -93,6 +81,28 @@ def service_call(call): } +async def test_call_service_target_template(hass, websocket_client): + """Test call service command with target does not allow template.""" + await websocket_client.send_json( + { + "id": 5, + "type": "call_service", + "domain": "domain_test", + "service": "test_service", + "service_data": {"hello": "world"}, + "target": { + "entity_id": "{{ 1 }}", + }, + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert not msg["success"] + assert msg["error"]["code"] == const.ERR_INVALID_FORMAT + + async def test_call_service_not_found(hass, websocket_client): """Test call service command.""" await websocket_client.send_json( @@ -232,7 +242,6 @@ async def unknown_error_call(_): ) msg = await websocket_client.receive_json() - print(msg) assert msg["id"] == 5 assert msg["type"] == const.TYPE_RESULT assert msg["success"] is False @@ -249,7 +258,6 @@ async def unknown_error_call(_): ) msg = await websocket_client.receive_json() - print(msg) assert msg["id"] == 6 assert msg["type"] == const.TYPE_RESULT assert msg["success"] is False From 55c1b67de496460c73fbc2b5604bd313381a3c54 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:57:06 -0500 Subject: [PATCH 1063/1818] Update zwave_js.refresh_value service description (#47469) --- homeassistant/components/zwave_js/services.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index 8e6d907fc96dc7..7277f540d7682e 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -70,10 +70,15 @@ set_config_parameter: refresh_value: name: Refresh value(s) of a Z-Wave entity description: Force update value(s) for a Z-Wave entity - target: - entity: - integration: zwave_js fields: + entity_id: + name: Entity + description: Entity whose value(s) should be refreshed + required: true + example: sensor.family_room_motion + selector: + entity: + integration: zwave_js refresh_all_values: name: Refresh all values? description: Whether to refresh all values (true) or just the primary value (false) From ef79d24a8c0d8f4a1eab92a39a6c54ead647dcbb Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Fri, 5 Mar 2021 17:05:36 -0600 Subject: [PATCH 1064/1818] Bump amcrest package version to 1.7.1 (#47483) --- 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 0b6fbbdc09a02a..0b7a59edb79c44 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.7.0"], + "requirements": ["amcrest==1.7.1"], "dependencies": ["ffmpeg"], "codeowners": ["@pnbruckner"] } diff --git a/requirements_all.txt b/requirements_all.txt index ea34186457a070..33d2ee7aad64e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -245,7 +245,7 @@ alpha_vantage==2.3.1 ambiclimate==0.2.1 # homeassistant.components.amcrest -amcrest==1.7.0 +amcrest==1.7.1 # homeassistant.components.androidtv androidtv[async]==0.0.57 From 10dae253e5c10b0f2feecf899d6d0fae426b1043 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 6 Mar 2021 00:37:56 +0100 Subject: [PATCH 1065/1818] Complete typing on Verisure integration (#47482) --- homeassistant/components/verisure/__init__.py | 31 +++++++------ .../verisure/alarm_control_panel.py | 39 ++++++++++------ .../components/verisure/binary_sensor.py | 33 ++++++++----- homeassistant/components/verisure/camera.py | 26 ++++++++--- homeassistant/components/verisure/lock.py | 34 +++++++++----- homeassistant/components/verisure/sensor.py | 46 +++++++++++-------- homeassistant/components/verisure/switch.py | 38 +++++++++------ 7 files changed, 158 insertions(+), 89 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index ab061faccad154..ccb479814abcfc 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,5 +1,8 @@ """Support for Verisure devices.""" +from __future__ import annotations + from datetime import timedelta +from typing import Any, Literal from jsonpath import jsonpath import verisure @@ -12,8 +15,10 @@ EVENT_HOMEASSISTANT_STOP, HTTP_SERVICE_UNAVAILABLE, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import ConfigType from homeassistant.util import Throttle from .const import ( @@ -78,7 +83,7 @@ DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) -def setup(hass, config): +def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Verisure integration.""" global HUB # pylint: disable=global-statement HUB = VerisureHub(config[DOMAIN]) @@ -137,7 +142,7 @@ async def enable_autolock(service): class VerisureHub: """A Verisure hub wrapper class.""" - def __init__(self, domain_config): + def __init__(self, domain_config: ConfigType): """Initialize the Verisure hub.""" self.overview = {} self.imageseries = {} @@ -150,7 +155,7 @@ def __init__(self, domain_config): self.giid = domain_config.get(CONF_GIID) - def login(self): + def login(self) -> bool: """Login to Verisure.""" try: self.session.login() @@ -161,7 +166,7 @@ def login(self): return self.set_giid() return True - def logout(self): + def logout(self) -> bool: """Logout from Verisure.""" try: self.session.logout() @@ -170,7 +175,7 @@ def logout(self): return False return True - def set_giid(self): + def set_giid(self) -> bool: """Set installation GIID.""" try: self.session.set_giid(self.giid) @@ -179,7 +184,7 @@ def set_giid(self): return False return True - def update_overview(self): + def update_overview(self) -> None: """Update the overview.""" try: self.overview = self.session.get_overview() @@ -192,34 +197,34 @@ def update_overview(self): raise @Throttle(timedelta(seconds=60)) - def update_smartcam_imageseries(self): + def update_smartcam_imageseries(self) -> None: """Update the image series.""" self.imageseries = self.session.get_camera_imageseries() @Throttle(timedelta(seconds=30)) - def smartcam_capture(self, device_id): + def smartcam_capture(self, device_id: str) -> None: """Capture a new image from a smartcam.""" self.session.capture_image(device_id) - def disable_autolock(self, device_id): + def disable_autolock(self, device_id: str) -> None: """Disable autolock.""" self.session.set_lock_config(device_id, auto_lock_enabled=False) - def enable_autolock(self, device_id): + def enable_autolock(self, device_id: str) -> None: """Enable autolock.""" self.session.set_lock_config(device_id, auto_lock_enabled=True) - def get(self, jpath, *args): + def get(self, jpath: str, *args) -> list[Any] | Literal[False]: """Get values from the overview that matches the jsonpath.""" res = jsonpath(self.overview, jpath % args) return res or [] - def get_first(self, jpath, *args): + def get_first(self, jpath: str, *args) -> Any | None: """Get first value from the overview that matches the jsonpath.""" res = self.get(jpath, *args) return res[0] if res else None - def get_image_info(self, jpath, *args): + def get_image_info(self, jpath: str, *args) -> list[Any] | Literal[False]: """Get values from the imageseries that matches the jsonpath.""" res = jsonpath(self.imageseries, jpath % args) return res or [] diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index fff58433a9caaa..c791bfc38dce83 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -1,7 +1,13 @@ """Support for Verisure alarm control panels.""" +from __future__ import annotations + from time import sleep +from typing import Any, Callable -import homeassistant.components.alarm_control_panel as alarm +from homeassistant.components.alarm_control_panel import ( + FORMAT_NUMBER, + AlarmControlPanelEntity, +) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -11,12 +17,19 @@ STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_ALARM, CONF_CODE_DIGITS, CONF_GIID, LOGGER -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure platform.""" alarms = [] if int(hub.config.get(CONF_ALARM, 1)): @@ -25,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(alarms) -def set_arm_state(state, code=None): +def set_arm_state(state: str, code: str | None = None) -> None: """Send set arm state command.""" transaction_id = hub.session.set_arm_state(code, state)[ "armStateChangeTransactionId" @@ -38,7 +51,7 @@ def set_arm_state(state, code=None): hub.update_overview() -class VerisureAlarm(alarm.AlarmControlPanelEntity): +class VerisureAlarm(AlarmControlPanelEntity): """Representation of a Verisure alarm status.""" def __init__(self): @@ -48,7 +61,7 @@ def __init__(self): self._changed_by = None @property - def name(self): + def name(self) -> str: """Return the name of the device.""" giid = hub.config.get(CONF_GIID) if giid is not None: @@ -61,7 +74,7 @@ def name(self): return "{} alarm".format(hub.session.installations[0]["alias"]) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return self._state @@ -71,16 +84,16 @@ def supported_features(self) -> int: return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY @property - def code_format(self): + def code_format(self) -> str: """Return one or more digits/characters.""" - return alarm.FORMAT_NUMBER + return FORMAT_NUMBER @property - def changed_by(self): + def changed_by(self) -> str | None: """Return the last change triggered by.""" return self._changed_by - def update(self): + def update(self) -> None: """Update alarm status.""" hub.update_overview() status = hub.get_first("$.armState.statusType") @@ -94,14 +107,14 @@ def update(self): LOGGER.error("Unknown alarm state %s", status) self._changed_by = hub.get_first("$.armState.name") - def alarm_disarm(self, code=None): + def alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" set_arm_state("DISARMED", code) - def alarm_arm_home(self, code=None): + def alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" set_arm_state("ARMED_HOME", code) - def alarm_arm_away(self, code=None): + def alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" set_arm_state("ARMED_AWAY", code) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 5a7f4386eced25..e30f008dba9405 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,13 +1,24 @@ """Support for Verisure binary sensors.""" +from __future__ import annotations + +from typing import Any, Callable + from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, BinarySensorEntity, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import CONF_DOOR_WINDOW, HUB as hub -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure binary sensors.""" sensors = [] hub.update_overview() @@ -29,12 +40,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureDoorWindowSensor(BinarySensorEntity): """Representation of a Verisure door window sensor.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the Verisure door window sensor.""" self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the binary sensor.""" return hub.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", @@ -42,7 +53,7 @@ def name(self): ) @property - def is_on(self): + def is_on(self) -> bool: """Return the state of the sensor.""" return ( hub.get_first( @@ -53,7 +64,7 @@ def is_on(self): ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -64,7 +75,7 @@ def available(self): ) # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the state of the sensor.""" hub.update_overview() @@ -73,26 +84,26 @@ class VerisureEthernetStatus(BinarySensorEntity): """Representation of a Verisure VBOX internet status.""" @property - def name(self): + def name(self) -> str: """Return the name of the binary sensor.""" return "Verisure Ethernet status" @property - def is_on(self): + def is_on(self) -> bool: """Return the state of the sensor.""" return hub.get_first("$.ethernetConnectedNow") @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return hub.get_first("$.ethernetConnectedNow") is not None # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the state of the sensor.""" hub.update_overview() @property - def device_class(self): + def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_CONNECTIVITY diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index a69e1fb95d8542..ad6840c0614c38 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -1,22 +1,34 @@ """Support for Verisure cameras.""" +from __future__ import annotations + import errno import os +from typing import Any, Callable, Literal from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_SMARTCAM, LOGGER -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None | Literal[False]: """Set up the Verisure Camera.""" if not int(hub.config.get(CONF_SMARTCAM, 1)): return False + directory_path = hass.config.config_dir if not os.access(directory_path, os.R_OK): LOGGER.error("file path %s is not readable", directory_path) return False + hub.update_overview() smartcams = [ VerisureSmartcam(hass, device_label, directory_path) @@ -29,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureSmartcam(Camera): """Representation of a Verisure camera.""" - def __init__(self, hass, device_label, directory_path): + def __init__(self, hass: HomeAssistant, device_label: str, directory_path: str): """Initialize Verisure File Camera component.""" super().__init__() @@ -39,7 +51,7 @@ def __init__(self, hass, device_label, directory_path): self._image_id = None hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.delete_image) - def camera_image(self): + def camera_image(self) -> bytes | None: """Return image response.""" self.check_imagelist() if not self._image: @@ -49,7 +61,7 @@ def camera_image(self): with open(self._image, "rb") as file: return file.read() - def check_imagelist(self): + def check_imagelist(self) -> None: """Check the contents of the image list.""" hub.update_smartcam_imageseries() image_ids = hub.get_image_info( @@ -67,12 +79,12 @@ def check_imagelist(self): ) hub.session.download_image(self._device_label, new_image_id, new_image_path) LOGGER.debug("Old image_id=%s", self._image_id) - self.delete_image(self) + self.delete_image() self._image_id = new_image_id self._image = new_image_path - def delete_image(self, event): + def delete_image(self) -> None: """Delete an old image.""" remove_image = os.path.join( self._directory_path, "{}{}".format(self._image_id, ".jpg") @@ -85,7 +97,7 @@ def delete_image(self, event): raise @property - def name(self): + def name(self) -> str: """Return the name of this camera.""" return hub.get_first( "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 228c8c6c176522..b2e1cfb3db05c7 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,14 +1,24 @@ """Support for Verisure locks.""" +from __future__ import annotations + from time import monotonic, sleep +from typing import Any, Callable from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, LOGGER -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure lock platform.""" locks = [] if int(hub.config.get(CONF_LOCKS, 1)): @@ -26,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureDoorlock(LockEntity): """Representation of a Verisure doorlock.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the Verisure lock.""" self._device_label = device_label self._state = None @@ -36,19 +46,19 @@ def __init__(self, device_label): self._default_lock_code = hub.config.get(CONF_DEFAULT_LOCK_CODE) @property - def name(self): + def name(self) -> str: """Return the name of the lock.""" return hub.get_first( "$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label ) @property - def state(self): + def state(self) -> str | None: """Return the state of the lock.""" return self._state @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -58,16 +68,16 @@ def available(self): ) @property - def changed_by(self): + def changed_by(self) -> str | None: """Last change triggered by.""" return self._changed_by @property - def code_format(self): + def code_format(self) -> str: """Return the required six digit code.""" return "^\\d{%s}$" % self._digits - def update(self): + def update(self) -> None: """Update lock status.""" if monotonic() - self._change_timestamp < 10: return @@ -88,11 +98,11 @@ def update(self): ) @property - def is_locked(self): + def is_locked(self) -> bool: """Return true if lock is locked.""" return self._state == STATE_LOCKED - def unlock(self, **kwargs): + def unlock(self, **kwargs) -> None: """Send unlock command.""" if self._state is None: return @@ -104,7 +114,7 @@ def unlock(self, **kwargs): self.set_lock_state(code, STATE_UNLOCKED) - def lock(self, **kwargs): + def lock(self, **kwargs) -> None: """Send lock command.""" if self._state == STATE_LOCKED: return @@ -116,7 +126,7 @@ def lock(self, **kwargs): self.set_lock_state(code, STATE_LOCKED) - def set_lock_state(self, code, state): + def set_lock_state(self, code: str, state: str) -> None: """Send set lock state command.""" lock_state = "lock" if state == STATE_LOCKED else "unlock" transaction_id = hub.session.set_lock_state( diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index ac7c8f40e8d764..6582dfc409adfc 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,12 +1,22 @@ """Support for Verisure sensors.""" +from __future__ import annotations + +from typing import Any, Callable + from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from . import HUB as hub from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None: """Set up the Verisure platform.""" sensors = [] hub.update_overview() @@ -47,12 +57,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class VerisureThermometer(Entity): """Representation of a Verisure thermometer.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the sensor.""" self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return ( hub.get_first( @@ -62,14 +72,14 @@ def name(self): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return hub.get_first( "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -80,12 +90,12 @@ def available(self): ) @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return TEMP_CELSIUS # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the sensor.""" hub.update_overview() @@ -93,12 +103,12 @@ def update(self): class VerisureHygrometer(Entity): """Representation of a Verisure hygrometer.""" - def __init__(self, device_label): + def __init__(self, device_label: str): """Initialize the sensor.""" self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return ( hub.get_first( @@ -108,14 +118,14 @@ def name(self): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return hub.get_first( "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first( @@ -125,12 +135,12 @@ def available(self): ) @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return PERCENTAGE # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the sensor.""" hub.update_overview() @@ -143,7 +153,7 @@ def __init__(self, device_label): self._device_label = device_label @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return ( hub.get_first( @@ -153,14 +163,14 @@ def name(self): ) @property - def state(self): + def state(self) -> str | None: """Return the state of the device.""" return hub.get_first( "$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first("$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label) @@ -168,11 +178,11 @@ def available(self): ) @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return "Mice" # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Update the sensor.""" hub.update_overview() diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 4615e0a2a494ab..e5e19bd6d13109 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -1,45 +1,53 @@ """Support for Verisure Smartplugs.""" +from __future__ import annotations + from time import monotonic +from typing import Any, Callable, Literal from homeassistant.components.switch import SwitchEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from . import CONF_SMARTPLUGS, HUB as hub -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform( + hass: HomeAssistant, + config: dict[str, Any], + add_entities: Callable[[list[Entity], bool], None], + discovery_info: dict[str, Any] | None = None, +) -> None | Literal[False]: """Set up the Verisure switch platform.""" if not int(hub.config.get(CONF_SMARTPLUGS, 1)): return False hub.update_overview() - switches = [] - switches.extend( - [ - VerisureSmartplug(device_label) - for device_label in hub.get("$.smartPlugs[*].deviceLabel") - ] - ) + switches = [ + VerisureSmartplug(device_label) + for device_label in hub.get("$.smartPlugs[*].deviceLabel") + ] + add_entities(switches) class VerisureSmartplug(SwitchEntity): """Representation of a Verisure smartplug.""" - def __init__(self, device_id): + def __init__(self, device_id: str): """Initialize the Verisure device.""" self._device_label = device_id self._change_timestamp = 0 self._state = False @property - def name(self): + def name(self) -> str: """Return the name or location of the smartplug.""" return hub.get_first( "$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label ) @property - def is_on(self): + def is_on(self) -> bool: """Return true if on.""" if monotonic() - self._change_timestamp < 10: return self._state @@ -53,26 +61,26 @@ def is_on(self): return self._state @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return ( hub.get_first("$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label) is not None ) - def turn_on(self, **kwargs): + def turn_on(self, **kwargs) -> None: """Set smartplug status on.""" hub.session.set_smartplug_state(self._device_label, True) self._state = True self._change_timestamp = monotonic() - def turn_off(self, **kwargs): + def turn_off(self, **kwargs) -> None: """Set smartplug status off.""" hub.session.set_smartplug_state(self._device_label, False) self._state = False self._change_timestamp = monotonic() # pylint: disable=no-self-use - def update(self): + def update(self) -> None: """Get the latest date of the smartplug.""" hub.update_overview() From 939da2403f27d1b050d6b1bbd255a3e4f09fd267 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Mar 2021 23:38:27 +0000 Subject: [PATCH 1066/1818] Bumped version to 2021.3.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e454f3ab09d6e4..015a347a5e3ccd 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From c7718f2b3b5fb18c0828589e44a21bf874da9215 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 6 Mar 2021 10:21:00 +0100 Subject: [PATCH 1067/1818] Fix Sonos polling mode (#47498) --- homeassistant/components/sonos/media_player.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index e6ee45e7a57b29..1a9e9ef58df5cd 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -646,9 +646,12 @@ def update_media(self, event=None): update_position = new_status != self._status self._status = new_status - track_uri = variables["current_track_uri"] if variables else None - - music_source = self.soco.music_source_from_uri(track_uri) + if variables: + track_uri = variables["current_track_uri"] + music_source = self.soco.music_source_from_uri(track_uri) + else: + # This causes a network round-trip so we avoid it when possible + music_source = self.soco.music_source if music_source == MUSIC_SRC_TV: self.update_media_linein(SOURCE_TV) From 4cade4b7363199062f42c86ece24ac61fe68fa07 Mon Sep 17 00:00:00 2001 From: FidgetyRat Date: Sat, 6 Mar 2021 04:26:04 -0500 Subject: [PATCH 1068/1818] Add OPENING & CLOSING state to MySensors cover (#47285) * Added OPENING & CLOSING State Support Added support for OPENING and CLOSING states using a combination of the required V_ variables. Simplified the determination of the cover's state by use of a new enumeration and single method allowing the state to be used by all three HomeAssistant query methods. * Fixes for HomeAssistant Style Corrections to style to allow flake8, isort, and black to pass. * Peer Review Changes Added @unique to the main enumeration. Removed unnecessary parens from door state logic. Reordered CLOSING and CLOSED in the enumeration. --- homeassistant/components/mysensors/cover.py | 52 ++++++++++++++++++-- homeassistant/components/mysensors/device.py | 3 ++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index 782ab88c48899e..0e3478a57bf37c 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -1,4 +1,5 @@ """Support for MySensors covers.""" +from enum import Enum, unique import logging from typing import Callable @@ -14,6 +15,16 @@ _LOGGER = logging.getLogger(__name__) +@unique +class CoverState(Enum): + """An enumeration of the standard cover states.""" + + OPEN = 0 + OPENING = 1 + CLOSING = 2 + CLOSED = 3 + + async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities: Callable ): @@ -43,13 +54,44 @@ async def async_discover(discovery_info): class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity): """Representation of the value of a MySensors Cover child node.""" - @property - def is_closed(self): - """Return True if cover is closed.""" + def get_cover_state(self): + """Return a CoverState enum representing the state of the cover.""" set_req = self.gateway.const.SetReq + v_up = self._values.get(set_req.V_UP) == STATE_ON + v_down = self._values.get(set_req.V_DOWN) == STATE_ON + v_stop = self._values.get(set_req.V_STOP) == STATE_ON + + # If a V_DIMMER or V_PERCENTAGE is available, that is the amount + # the cover is open. Otherwise, use 0 or 100 based on the V_LIGHT + # or V_STATUS. + amount = 100 if set_req.V_DIMMER in self._values: - return self._values.get(set_req.V_DIMMER) == 0 - return self._values.get(set_req.V_LIGHT) == STATE_OFF + amount = self._values.get(set_req.V_DIMMER) + else: + amount = 100 if self._values.get(set_req.V_LIGHT) == STATE_ON else 0 + + if v_up and not v_down and not v_stop: + return CoverState.OPENING + if not v_up and v_down and not v_stop: + return CoverState.CLOSING + if not v_up and not v_down and v_stop and amount == 0: + return CoverState.CLOSED + return CoverState.OPEN + + @property + def is_closed(self): + """Return True if the cover is closed.""" + return self.get_cover_state() == CoverState.CLOSED + + @property + def is_closing(self): + """Return True if the cover is closing.""" + return self.get_cover_state() == CoverState.CLOSING + + @property + def is_opening(self): + """Return True if the cover is opening.""" + return self.get_cover_state() == CoverState.OPENING @property def current_cover_position(self): diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 6841486734520f..25b892d70b3690 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -162,6 +162,9 @@ async def async_update(self): set_req.V_LIGHT, set_req.V_LOCK_STATUS, set_req.V_TRIPPED, + set_req.V_UP, + set_req.V_DOWN, + set_req.V_STOP, ): self._values[value_type] = STATE_ON if int(value) == 1 else STATE_OFF elif value_type == set_req.V_DIMMER: From 022184176aa10cc45a44fcc5a0d0982a4f12c0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 6 Mar 2021 13:36:20 +0200 Subject: [PATCH 1069/1818] Upgrade upcloud-api to 1.0.1 (#47501) https://github.com/UpCloudLtd/upcloud-python-api/releases/tag/0.4.6 https://github.com/UpCloudLtd/upcloud-python-api/releases/tag/v1.0.0 https://github.com/UpCloudLtd/upcloud-python-api/releases/tag/v1.0.1 --- homeassistant/components/upcloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/upcloud/manifest.json b/homeassistant/components/upcloud/manifest.json index 3a85f847c9d92e..f161e273bc3614 100644 --- a/homeassistant/components/upcloud/manifest.json +++ b/homeassistant/components/upcloud/manifest.json @@ -3,6 +3,6 @@ "name": "UpCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upcloud", - "requirements": ["upcloud-api==0.4.5"], + "requirements": ["upcloud-api==1.0.1"], "codeowners": ["@scop"] } diff --git a/requirements_all.txt b/requirements_all.txt index 446b30b223bc52..46c25915af96a6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2251,7 +2251,7 @@ unifiled==0.11 upb_lib==0.4.12 # homeassistant.components.upcloud -upcloud-api==0.4.5 +upcloud-api==1.0.1 # homeassistant.components.huawei_lte # homeassistant.components.syncthru diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e8242230052920..4404d099050014 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1154,7 +1154,7 @@ twinkly-client==0.0.2 upb_lib==0.4.12 # homeassistant.components.upcloud -upcloud-api==0.4.5 +upcloud-api==1.0.1 # homeassistant.components.huawei_lte # homeassistant.components.syncthru From 2f9d03d115db15103ecf66110ed7305bae069358 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 6 Mar 2021 12:57:21 +0100 Subject: [PATCH 1070/1818] Merge action and condition traces (#47373) * Merge action and condition traces * Update __init__.py * Add typing to AutomationTrace * Make trace_get prepare a new trace by default * Correct typing of trace_cv * Fix tests --- .../components/automation/__init__.py | 103 ++++++++------ homeassistant/helpers/condition.py | 114 ++-------------- homeassistant/helpers/script.py | 128 +++--------------- homeassistant/helpers/trace.py | 120 ++++++++++++---- tests/helpers/test_condition.py | 6 +- tests/helpers/test_script.py | 6 +- 6 files changed, 192 insertions(+), 285 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index f12a7398f7ea67..df7102effde322 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,8 +1,20 @@ """Allow to set up simple automation rules via the config file.""" from collections import deque from contextlib import contextmanager +import datetime as dt import logging -from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast +from typing import ( + Any, + Awaitable, + Callable, + Deque, + Dict, + List, + Optional, + Set, + Union, + cast, +) import voluptuous as vol from voluptuous.humanize import humanize_error @@ -42,11 +54,6 @@ HomeAssistantError, ) from homeassistant.helpers import condition, extract_domain_configs, template -from homeassistant.helpers.condition import ( - condition_path, - condition_trace_clear, - condition_trace_get, -) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent @@ -57,12 +64,10 @@ CONF_MAX, CONF_MAX_EXCEEDED, Script, - action_path, - action_trace_clear, - action_trace_get, ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service +from homeassistant.helpers.trace import TraceElement, trace_get, trace_path from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass @@ -235,44 +240,55 @@ async def reload_service_handler(service_call): class AutomationTrace: """Container for automation trace.""" - def __init__(self, unique_id, config, trigger, context, action_trace): + def __init__( + self, + unique_id: Optional[str], + config: Dict[str, Any], + trigger: Dict[str, Any], + context: Context, + ): """Container for automation trace.""" - self._action_trace = action_trace - self._condition_trace = None - self._config = config - self._context = context - self._error = None - self._state = "running" - self._timestamp_finish = None - self._timestamp_start = dt_util.utcnow() - self._trigger = trigger - self._unique_id = unique_id - self._variables = None - - def set_error(self, ex): + self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._config: Dict[str, Any] = config + self._context: Context = context + self._error: Optional[Exception] = None + self._state: str = "running" + self._timestamp_finish: Optional[dt.datetime] = None + self._timestamp_start: dt.datetime = dt_util.utcnow() + self._trigger: Dict[str, Any] = trigger + self._unique_id: Optional[str] = unique_id + self._variables: Optional[Dict[str, Any]] = None + + def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set action trace.""" + self._action_trace = trace + + def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set condition trace.""" + self._condition_trace = trace + + def set_error(self, ex: Exception) -> None: """Set error.""" self._error = ex - def set_variables(self, variables): + def set_variables(self, variables: Dict[str, Any]) -> None: """Set variables.""" self._variables = variables - def set_condition_trace(self, condition_trace): - """Set condition trace.""" - self._condition_trace = condition_trace - - def finished(self): + def finished(self) -> None: """Set finish time.""" self._timestamp_finish = dt_util.utcnow() self._state = "stopped" - def as_dict(self): + def as_dict(self) -> Dict[str, Any]: """Return dictionary version of this AutomationTrace.""" action_traces = {} condition_traces = {} - for key, trace_list in self._action_trace.items(): - action_traces[key] = [item.as_dict() for item in trace_list] + if self._action_trace: + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] if self._condition_trace: for key, trace_list in self._condition_trace.items(): @@ -300,11 +316,7 @@ def as_dict(self): @contextmanager def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" - action_trace_clear() - action_trace = action_trace_get() - automation_trace = AutomationTrace( - unique_id, config, trigger, context, action_trace - ) + automation_trace = AutomationTrace(unique_id, config, trigger, context) if unique_id: if unique_id not in hass.data[DATA_AUTOMATION_TRACE]: @@ -325,7 +337,7 @@ def trace_automation(hass, unique_id, config, trigger, context): "Automation finished. Summary:\n\ttrigger: %s\n\tcondition: %s\n\taction: %s", automation_trace._trigger, # pylint: disable=protected-access automation_trace._condition_trace, # pylint: disable=protected-access - action_trace, + automation_trace._action_trace, # pylint: disable=protected-access ) @@ -510,6 +522,9 @@ async def async_trigger(self, run_variables, context=None, skip_condition=False) variables = run_variables automation_trace.set_variables(variables) + # Prepare tracing the evaluation of the automation's conditions + automation_trace.set_condition_trace(trace_get()) + if ( not skip_condition and self._cond_func is not None @@ -517,12 +532,12 @@ async def async_trigger(self, run_variables, context=None, skip_condition=False) ): self._logger.debug( "Conditions not met, aborting automation. Condition summary: %s", - condition_trace_get(), + trace_get(clear=False), ) - automation_trace.set_condition_trace(condition_trace_get()) return - automation_trace.set_condition_trace(condition_trace_get()) - condition_trace_clear() + + # Prepare tracing the execution of the automation's actions + automation_trace.set_action_trace(trace_get()) # Create a new context referring to the old context. parent_id = None if context is None else context.id @@ -543,7 +558,7 @@ def started_action(): ) try: - with action_path("action"): + with trace_path("action"): await self.action_script.async_run( variables, trigger_context, started_action ) @@ -763,7 +778,7 @@ def if_action(variables=None): errors = [] for index, check in enumerate(checks): try: - with condition_path(["condition", str(index)]): + with trace_path(["condition", str(index)]): if not check(hass, variables): return False except ConditionError as ex: diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index bc1ff21b9cc658..71bbaa5f0f4585 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -2,24 +2,12 @@ import asyncio from collections import deque from contextlib import contextmanager -from contextvars import ContextVar from datetime import datetime, timedelta import functools as ft import logging import re import sys -from typing import ( - Any, - Callable, - Container, - Dict, - Generator, - List, - Optional, - Set, - Union, - cast, -) +from typing import Any, Callable, Container, Generator, List, Optional, Set, Union, cast from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( @@ -67,6 +55,9 @@ from .trace import ( TraceElement, trace_append_element, + trace_path, + trace_path_get, + trace_stack_cv, trace_stack_pop, trace_stack_push, trace_stack_top, @@ -84,79 +75,16 @@ ConditionCheckerType = Callable[[HomeAssistant, TemplateVarsType], bool] -# Context variables for tracing -# Trace of condition being evaluated -condition_trace = ContextVar("condition_trace", default=None) -# Stack of TraceElements -condition_trace_stack: ContextVar[Optional[List[TraceElement]]] = ContextVar( - "condition_trace_stack", default=None -) -# Current location in config tree -condition_path_stack: ContextVar[Optional[List[str]]] = ContextVar( - "condition_path_stack", default=None -) - - -def condition_trace_stack_push(node: TraceElement) -> None: - """Push a TraceElement to the top of the trace stack.""" - trace_stack_push(condition_trace_stack, node) - - -def condition_trace_stack_pop() -> None: - """Remove the top element from the trace stack.""" - trace_stack_pop(condition_trace_stack) - - -def condition_trace_stack_top() -> Optional[TraceElement]: - """Return the element at the top of the trace stack.""" - return cast(Optional[TraceElement], trace_stack_top(condition_trace_stack)) - - -def condition_path_push(suffix: Union[str, List[str]]) -> int: - """Go deeper in the config tree.""" - if isinstance(suffix, str): - suffix = [suffix] - for node in suffix: - trace_stack_push(condition_path_stack, node) - return len(suffix) - - -def condition_path_pop(count: int) -> None: - """Go n levels up in the config tree.""" - for _ in range(count): - trace_stack_pop(condition_path_stack) - - -def condition_path_get() -> str: - """Return a string representing the current location in the config tree.""" - path = condition_path_stack.get() - if not path: - return "" - return "/".join(path) - - -def condition_trace_get() -> Optional[Dict[str, TraceElement]]: - """Return the trace of the condition that was evaluated.""" - return condition_trace.get() - - -def condition_trace_clear() -> None: - """Clear the condition trace.""" - condition_trace.set(None) - condition_trace_stack.set(None) - condition_path_stack.set(None) - - def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement: """Append a TraceElement to trace[path].""" trace_element = TraceElement(variables) - trace_append_element(condition_trace, trace_element, path) + trace_append_element(trace_element, path) return trace_element def condition_trace_set_result(result: bool, **kwargs: Any) -> None: """Set the result of TraceElement at the top of the stack.""" - node = condition_trace_stack_top() + node = trace_stack_top(trace_stack_cv) # The condition function may be called directly, in which case tracing # is not setup @@ -169,25 +97,15 @@ def condition_trace_set_result(result: bool, **kwargs: Any) -> None: @contextmanager def trace_condition(variables: TemplateVarsType) -> Generator: """Trace condition evaluation.""" - trace_element = condition_trace_append(variables, condition_path_get()) - condition_trace_stack_push(trace_element) + trace_element = condition_trace_append(variables, trace_path_get()) + trace_stack_push(trace_stack_cv, trace_element) try: yield trace_element except Exception as ex: # pylint: disable=broad-except trace_element.set_error(ex) raise ex finally: - condition_trace_stack_pop() - - -@contextmanager -def condition_path(suffix: Union[str, List[str]]) -> Generator: - """Go deeper in the config tree.""" - count = condition_path_push(suffix) - try: - yield - finally: - condition_path_pop(count) + trace_stack_pop(trace_stack_cv) def trace_condition_function(condition: ConditionCheckerType) -> ConditionCheckerType: @@ -260,7 +178,7 @@ def if_and_condition( errors = [] for index, check in enumerate(checks): try: - with condition_path(["conditions", str(index)]): + with trace_path(["conditions", str(index)]): if not check(hass, variables): return False except ConditionError as ex: @@ -295,7 +213,7 @@ def if_or_condition( errors = [] for index, check in enumerate(checks): try: - with condition_path(["conditions", str(index)]): + with trace_path(["conditions", str(index)]): if check(hass, variables): return True except ConditionError as ex: @@ -330,7 +248,7 @@ def if_not_condition( errors = [] for index, check in enumerate(checks): try: - with condition_path(["conditions", str(index)]): + with trace_path(["conditions", str(index)]): if check(hass, variables): return False except ConditionError as ex: @@ -509,9 +427,7 @@ def if_numeric_state( errors = [] for index, entity_id in enumerate(entity_ids): try: - with condition_path(["entity_id", str(index)]), trace_condition( - variables - ): + with trace_path(["entity_id", str(index)]), trace_condition(variables): if not async_numeric_state( hass, entity_id, @@ -623,9 +539,7 @@ def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: errors = [] for index, entity_id in enumerate(entity_ids): try: - with condition_path(["entity_id", str(index)]), trace_condition( - variables - ): + with trace_path(["entity_id", str(index)]), trace_condition(variables): if not state(hass, entity_id, req_states, for_period, attribute): return False except ConditionError as ex: diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 3925535d06b0a7..8ee9b12bc1b565 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,7 +1,6 @@ """Helpers to execute scripts.""" import asyncio from contextlib import contextmanager -from contextvars import ContextVar from datetime import datetime, timedelta from functools import partial import itertools @@ -65,12 +64,7 @@ callback, ) from homeassistant.helpers import condition, config_validation as cv, service, template -from homeassistant.helpers.condition import ( - condition_path, - condition_trace_clear, - condition_trace_get, - trace_condition_function, -) +from homeassistant.helpers.condition import trace_condition_function from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( @@ -84,9 +78,12 @@ from .trace import ( TraceElement, trace_append_element, + trace_path, + trace_path_get, + trace_set_result, + trace_stack_cv, trace_stack_pop, trace_stack_push, - trace_stack_top, ) # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs @@ -125,111 +122,26 @@ ACTION_TRACE_NODE_MAX_LEN = 20 # Max the length of a trace node for repeated actions -action_trace = ContextVar("action_trace", default=None) -action_trace_stack = ContextVar("action_trace_stack", default=None) -action_path_stack = ContextVar("action_path_stack", default=None) - - -def action_trace_stack_push(node): - """Push a TraceElement to the top of the trace stack.""" - trace_stack_push(action_trace_stack, node) - - -def action_trace_stack_pop(): - """Remove the top element from the trace stack.""" - trace_stack_pop(action_trace_stack) - - -def action_trace_stack_top(): - """Return the element at the top of the trace stack.""" - return trace_stack_top(action_trace_stack) - - -def action_path_push(suffix): - """Go deeper in the config tree.""" - if isinstance(suffix, str): - suffix = [suffix] - for node in suffix: - trace_stack_push(action_path_stack, node) - return len(suffix) - - -def action_path_pop(count): - """Go n levels up in the config tree.""" - for _ in range(count): - trace_stack_pop(action_path_stack) - - -def action_path_get(): - """Return a string representing the current location in the config tree.""" - path = action_path_stack.get() - if not path: - return "" - return "/".join(path) - - -def action_trace_get(): - """Return the trace of the script that was executed.""" - return action_trace.get() - - -def action_trace_clear(): - """Clear the action trace.""" - action_trace.set({}) - action_trace_stack.set(None) - action_path_stack.set(None) - def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" trace_element = TraceElement(variables) - trace_append_element(action_trace, trace_element, path, ACTION_TRACE_NODE_MAX_LEN) + trace_append_element(trace_element, path, ACTION_TRACE_NODE_MAX_LEN) return trace_element -def action_trace_set_result(**kwargs): - """Set the result of TraceElement at the top of the stack.""" - node = action_trace_stack_top() - node.set_result(**kwargs) - - -def action_trace_add_conditions(): - """Add the result of condition evaluation to the action trace.""" - condition_trace = condition_trace_get() - condition_trace_clear() - - if condition_trace is None: - return - - _action_path = action_path_get() - for cond_path, conditions in condition_trace.items(): - path = _action_path + "/" + cond_path if cond_path else _action_path - for cond in conditions: - trace_append_element(action_trace, cond, path) - - @contextmanager def trace_action(variables): """Trace action execution.""" - trace_element = action_trace_append(variables, action_path_get()) - action_trace_stack_push(trace_element) + trace_element = action_trace_append(variables, trace_path_get()) + trace_stack_push(trace_stack_cv, trace_element) try: yield trace_element except Exception as ex: # pylint: disable=broad-except trace_element.set_error(ex) raise ex finally: - action_trace_stack_pop() - - -@contextmanager -def action_path(suffix): - """Go deeper in the config tree.""" - count = action_path_push(suffix) - try: - yield - finally: - action_path_pop(count) + trace_stack_pop(trace_stack_cv) def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA): @@ -382,7 +294,7 @@ async def async_run(self) -> None: self._finish() async def _async_step(self, log_exceptions): - with action_path(str(self._step)), trace_action(None): + with trace_path(str(self._step)), trace_action(None): try: handler = f"_async_{cv.determine_script_action(self._action)}_step" await getattr(self, handler)() @@ -638,15 +550,14 @@ async def _async_condition_step(self): ) cond = await self._async_get_condition(self._action) try: - with condition_path("condition"): + with trace_path("condition"): check = cond(self._hass, self._variables) except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'condition' evaluation:\n%s", ex) check = False self._log("Test condition %s: %s", self._script.last_action, check) - action_trace_set_result(result=check) - action_trace_add_conditions() + trace_set_result(result=check) if not check: raise _StopScript @@ -654,9 +565,9 @@ def _test_conditions(self, conditions, name): @trace_condition_function def traced_test_conditions(hass, variables): try: - with condition_path("conditions"): + with trace_path("conditions"): for idx, cond in enumerate(conditions): - with condition_path(str(idx)): + with trace_path(str(idx)): if not cond(hass, variables): return False except exceptions.ConditionError as ex: @@ -666,7 +577,6 @@ def traced_test_conditions(hass, variables): return True result = traced_test_conditions(self._hass, self._variables) - action_trace_add_conditions() return result async def _async_repeat_step(self): @@ -687,7 +597,7 @@ def set_repeat_var(iteration, count=None): async def async_run_sequence(iteration, extra_msg=""): self._log("Repeating %s: Iteration %i%s", description, iteration, extra_msg) - with action_path(str(self._step)): + with trace_path(str(self._step)): await self._async_run_script(script) if CONF_COUNT in repeat: @@ -754,18 +664,18 @@ async def _async_choose_step(self) -> None: choose_data = await self._script._async_get_choose_data(self._step) for idx, (conditions, script) in enumerate(choose_data["choices"]): - with action_path(str(idx)): + with trace_path(str(idx)): try: if self._test_conditions(conditions, "choose"): - action_trace_set_result(choice=idx) + trace_set_result(choice=idx) await self._async_run_script(script) return except exceptions.ConditionError as ex: _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: - action_trace_set_result(choice="default") - with action_path("default"): + trace_set_result(choice="default") + with trace_path("default"): await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 450faa0336f260..8c5c181ea2482d 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -1,33 +1,13 @@ """Helpers for script and condition tracing.""" from collections import deque +from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Dict, Optional +from typing import Any, Deque, Dict, Generator, List, Optional, Union, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util -def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: - """Push an element to the top of a trace stack.""" - trace_stack = trace_stack_var.get() - if trace_stack is None: - trace_stack = [] - trace_stack_var.set(trace_stack) - trace_stack.append(node) - - -def trace_stack_pop(trace_stack_var: ContextVar) -> None: - """Remove the top element from a trace stack.""" - trace_stack = trace_stack_var.get() - trace_stack.pop() - - -def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: - """Return the element at the top of a trace stack.""" - trace_stack = trace_stack_var.get() - return trace_stack[-1] if trace_stack else None - - class TraceElement: """Container for trace data.""" @@ -62,17 +42,105 @@ def as_dict(self) -> Dict[str, Any]: return result +# Context variables for tracing +# Current trace +trace_cv: ContextVar[Optional[Dict[str, Deque[TraceElement]]]] = ContextVar( + "trace_cv", default=None +) +# Stack of TraceElements +trace_stack_cv: ContextVar[Optional[List[TraceElement]]] = ContextVar( + "trace_stack_cv", default=None +) +# Current location in config tree +trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( + "trace_path_stack_cv", default=None +) + + +def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: + """Push an element to the top of a trace stack.""" + trace_stack = trace_stack_var.get() + if trace_stack is None: + trace_stack = [] + trace_stack_var.set(trace_stack) + trace_stack.append(node) + + +def trace_stack_pop(trace_stack_var: ContextVar) -> None: + """Remove the top element from a trace stack.""" + trace_stack = trace_stack_var.get() + trace_stack.pop() + + +def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: + """Return the element at the top of a trace stack.""" + trace_stack = trace_stack_var.get() + return trace_stack[-1] if trace_stack else None + + +def trace_path_push(suffix: Union[str, List[str]]) -> int: + """Go deeper in the config tree.""" + if isinstance(suffix, str): + suffix = [suffix] + for node in suffix: + trace_stack_push(trace_path_stack_cv, node) + return len(suffix) + + +def trace_path_pop(count: int) -> None: + """Go n levels up in the config tree.""" + for _ in range(count): + trace_stack_pop(trace_path_stack_cv) + + +def trace_path_get() -> str: + """Return a string representing the current location in the config tree.""" + path = trace_path_stack_cv.get() + if not path: + return "" + return "/".join(path) + + def trace_append_element( - trace_var: ContextVar, trace_element: TraceElement, path: str, maxlen: Optional[int] = None, ) -> None: """Append a TraceElement to trace[path].""" - trace = trace_var.get() + trace = trace_cv.get() if trace is None: - trace_var.set({}) - trace = trace_var.get() + trace = {} + trace_cv.set(trace) if path not in trace: trace[path] = deque(maxlen=maxlen) trace[path].append(trace_element) + + +def trace_get(clear: bool = True) -> Optional[Dict[str, Deque[TraceElement]]]: + """Return the current trace.""" + if clear: + trace_clear() + return trace_cv.get() + + +def trace_clear() -> None: + """Clear the trace.""" + trace_cv.set({}) + trace_stack_cv.set(None) + trace_path_stack_cv.set(None) + + +def trace_set_result(**kwargs: Any) -> None: + """Set the result of TraceElement at the top of the stack.""" + node = cast(TraceElement, trace_stack_top(trace_stack_cv)) + node.set_result(**kwargs) + + +@contextmanager +def trace_path(suffix: Union[str, List[str]]) -> Generator: + """Go deeper in the config tree.""" + count = trace_path_push(suffix) + try: + yield + finally: + trace_path_pop(count) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index cfed8ebbcf650b..c1bf727bc0f208 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -4,7 +4,7 @@ import pytest from homeassistant.exceptions import ConditionError, HomeAssistantError -from homeassistant.helpers import condition +from homeassistant.helpers import condition, trace from homeassistant.helpers.template import Template from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -25,8 +25,8 @@ def assert_element(trace_element, expected_element, path): def assert_condition_trace(expected): """Assert a trace condition sequence is as expected.""" - condition_trace = condition.condition_trace_get() - condition.condition_trace_clear() + condition_trace = trace.trace_get(clear=False) + trace.trace_clear() expected_trace_keys = list(expected.keys()) assert list(condition_trace.keys()) == expected_trace_keys for trace_key_index, key in enumerate(expected_trace_keys): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 04f922b685e0fe..027254ee03e32f 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -17,7 +17,7 @@ import homeassistant.components.scene as scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON from homeassistant.core import Context, CoreState, callback -from homeassistant.helpers import config_validation as cv, script +from homeassistant.helpers import config_validation as cv, script, trace from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -45,8 +45,8 @@ def assert_element(trace_element, expected_element, path): def assert_action_trace(expected): """Assert a trace condition sequence is as expected.""" - action_trace = script.action_trace_get() - script.action_trace_clear() + action_trace = trace.trace_get(clear=False) + trace.trace_clear() expected_trace_keys = list(expected.keys()) assert list(action_trace.keys()) == expected_trace_keys for trace_key_index, key in enumerate(expected_trace_keys): From 14f85d87314bb2bee4226962873f0c9de8034242 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sat, 6 Mar 2021 23:40:49 +0800 Subject: [PATCH 1071/1818] Disable audio stream when ADTS AAC detected (#47441) * Disable audio stream when ADTS AAC detected * Use context manager for memoryview * Fix tests * Add test * Fix tests * Change FakePacket bytearray size to 3 --- homeassistant/components/stream/worker.py | 10 ++++++ tests/components/stream/test_worker.py | 37 ++++++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 773170449e17e7..8d1df37d0395e3 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -208,6 +208,16 @@ def peek_first_pts(): missing_dts += 1 continue if packet.stream == audio_stream: + # detect ADTS AAC and disable audio + if audio_stream.codec.name == "aac" and packet.size > 2: + with memoryview(packet) as packet_view: + if packet_view[0] == 0xFF and packet_view[1] & 0xF0 == 0xF0: + _LOGGER.warning( + "ADTS AAC detected - disabling audio stream" + ) + container_packets = container.demux(video_stream) + audio_stream = None + continue found_audio = True elif ( segment_start_pts is None diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 2c202a290ce470..bef5d366a8fd95 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -57,6 +57,11 @@ def __init__(self, name, rate): self.time_base = fractions.Fraction(1, rate) self.profile = "ignored-profile" + class FakeCodec: + name = "aac" + + self.codec = FakeCodec() + VIDEO_STREAM = FakePyAvStream(VIDEO_STREAM_FORMAT, VIDEO_FRAME_RATE) AUDIO_STREAM = FakePyAvStream(AUDIO_STREAM_FORMAT, AUDIO_SAMPLE_RATE) @@ -87,13 +92,18 @@ def __next__(self): raise StopIteration self.packet += 1 - class FakePacket: + class FakePacket(bytearray): + # Be a bytearray so that memoryview works + def __init__(self): + super().__init__(3) + time_base = fractions.Fraction(1, VIDEO_FRAME_RATE) dts = self.packet * PACKET_DURATION / time_base pts = self.packet * PACKET_DURATION / time_base duration = PACKET_DURATION / time_base stream = VIDEO_STREAM is_keyframe = True + size = 3 return FakePacket() @@ -107,8 +117,8 @@ def __init__(self, video_stream, audio_stream): self.packets = PacketSequence(0) class FakePyAvStreams: - video = video_stream - audio = audio_stream + video = [video_stream] if video_stream else [] + audio = [audio_stream] if audio_stream else [] self.streams = FakePyAvStreams() @@ -171,8 +181,8 @@ class MockPyAv: def __init__(self, video=True, audio=False): """Initialize the MockPyAv.""" - video_stream = [VIDEO_STREAM] if video else [] - audio_stream = [AUDIO_STREAM] if audio else [] + video_stream = VIDEO_STREAM if video else None + audio_stream = AUDIO_STREAM if audio else None self.container = FakePyAvContainer( video_stream=video_stream, audio_stream=audio_stream ) @@ -413,6 +423,23 @@ async def test_audio_packets_not_found(hass): assert len(decoded_stream.audio_packets) == 0 +async def test_adts_aac_audio(hass): + """Set up an ADTS AAC audio stream and disable audio.""" + py_av = MockPyAv(audio=True) + + num_packets = PACKETS_TO_WAIT_FOR_AUDIO + 1 + packets = list(PacketSequence(num_packets)) + packets[1].stream = AUDIO_STREAM + packets[1].dts = packets[0].dts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + packets[1].pts = packets[0].pts / VIDEO_FRAME_RATE * AUDIO_SAMPLE_RATE + # The following is packet data is a sign of ADTS AAC + packets[1][0] = 255 + packets[1][1] = 241 + + decoded_stream = await async_decode_stream(hass, iter(packets), py_av=py_av) + assert len(decoded_stream.audio_packets) == 0 + + async def test_audio_is_first_packet(hass): """Set up an audio stream and audio packet is the first packet in the stream.""" py_av = MockPyAv(audio=True) From e9052233a626a4c54e4efca9bdd3ce2845f8c3f9 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sat, 6 Mar 2021 09:28:33 -0700 Subject: [PATCH 1072/1818] Adjust litterrobot tests and code to match guidelines (#47060) * Use SwitchEntity instead of ToggleEntity and adjust test patches as recommended * Move async_create_entry out of try block in config_flow * Patch pypi package instead of HA code * Bump pylitterbot to 2021.2.6, fix tests, and implement other code review suggestions * Bump pylitterbot to 2021.2.8, remove sleep mode start/end time from vacuum, adjust and add sensors for sleep mode start/end time * Move icon helper back to Litter-Robot component and isoformat times on time sensors --- .../components/litterrobot/config_flow.py | 8 +- homeassistant/components/litterrobot/hub.py | 21 ++--- .../components/litterrobot/manifest.json | 2 +- .../components/litterrobot/sensor.py | 93 +++++++++++++------ .../components/litterrobot/switch.py | 6 +- .../components/litterrobot/vacuum.py | 37 ++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/litterrobot/conftest.py | 76 +++++++++------ .../litterrobot/test_config_flow.py | 33 +++++-- tests/components/litterrobot/test_init.py | 38 +++++++- tests/components/litterrobot/test_sensor.py | 57 ++++++++++-- tests/components/litterrobot/test_switch.py | 14 +-- tests/components/litterrobot/test_vacuum.py | 32 +++++-- 14 files changed, 278 insertions(+), 143 deletions(-) diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index d6c92d8dad64b3..36fc2064abbc33 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -35,9 +35,6 @@ async def async_step_user(self, user_input=None): hub = LitterRobotHub(self.hass, user_input) try: await hub.login() - return self.async_create_entry( - title=user_input[CONF_USERNAME], data=user_input - ) except LitterRobotLoginException: errors["base"] = "invalid_auth" except LitterRobotException: @@ -46,6 +43,11 @@ async def async_step_user(self, user_input=None): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" + if not errors: + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 0d0559140c7431..943ef5bfe37a6c 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -4,7 +4,7 @@ from types import MethodType from typing import Any, Optional -from pylitterbot import Account, Robot +import pylitterbot from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -49,7 +49,7 @@ async def _async_update_data(): async def login(self, load_robots: bool = False): """Login to Litter-Robot.""" self.logged_in = False - self.account = Account() + self.account = pylitterbot.Account() try: await self.account.connect( username=self._data[CONF_USERNAME], @@ -69,11 +69,11 @@ async def login(self, load_robots: bool = False): class LitterRobotEntity(CoordinatorEntity): """Generic Litter-Robot entity representing common data and methods.""" - def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub): + def __init__(self, robot: pylitterbot.Robot, entity_type: str, hub: LitterRobotHub): """Pass coordinator to CoordinatorEntity.""" super().__init__(hub.coordinator) self.robot = robot - self.entity_type = entity_type if entity_type else "" + self.entity_type = entity_type self.hub = hub @property @@ -89,22 +89,21 @@ def unique_id(self): @property def device_info(self): """Return the device information for a Litter-Robot.""" - model = "Litter-Robot 3 Connect" - if not self.robot.serial.startswith("LR3C"): - model = "Other Litter-Robot Connected Device" return { "identifiers": {(DOMAIN, self.robot.serial)}, "name": self.robot.name, "manufacturer": "Litter-Robot", - "model": model, + "model": self.robot.model, } async def perform_action_and_refresh(self, action: MethodType, *args: Any): """Perform an action and initiates a refresh of the robot data after a few seconds.""" + + async def async_call_later_callback(*_) -> None: + await self.hub.coordinator.async_request_refresh() + await action(*args) - async_call_later( - self.hass, REFRESH_WAIT_TIME, self.hub.coordinator.async_request_refresh - ) + async_call_later(self.hass, REFRESH_WAIT_TIME, async_call_later_callback) @staticmethod def parse_time_at_default_timezone(time_str: str) -> Optional[time]: diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index 1c6ac7274bf657..8fa7ab8dcb5404 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -3,6 +3,6 @@ "name": "Litter-Robot", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/litterrobot", - "requirements": ["pylitterbot==2021.2.5"], + "requirements": ["pylitterbot==2021.2.8"], "codeowners": ["@natekspencer"] } diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 2843660bceecb3..8900c6c54ca80b 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,32 +1,44 @@ """Support for Litter-Robot sensors.""" -from homeassistant.const import PERCENTAGE +from typing import Optional + +from pylitterbot.robot import Robot + +from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity from .const import DOMAIN -from .hub import LitterRobotEntity - -WASTE_DRAWER = "Waste Drawer" +from .hub import LitterRobotEntity, LitterRobotHub -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Litter-Robot sensors using config entry.""" - hub = hass.data[DOMAIN][config_entry.entry_id] - - entities = [] - for robot in hub.account.robots: - entities.append(LitterRobotSensor(robot, WASTE_DRAWER, hub)) +def icon_for_gauge_level(gauge_level: Optional[int] = None, offset: int = 0) -> str: + """Return a gauge icon valid identifier.""" + if gauge_level is None or gauge_level <= 0 + offset: + return "mdi:gauge-empty" + if gauge_level > 70 + offset: + return "mdi:gauge-full" + if gauge_level > 30 + offset: + return "mdi:gauge" + return "mdi:gauge-low" - if entities: - async_add_entities(entities, True) +class LitterRobotPropertySensor(LitterRobotEntity, Entity): + """Litter-Robot property sensors.""" -class LitterRobotSensor(LitterRobotEntity, Entity): - """Litter-Robot sensors.""" + def __init__( + self, robot: Robot, entity_type: str, hub: LitterRobotHub, sensor_attribute: str + ): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(robot, entity_type, hub) + self.sensor_attribute = sensor_attribute @property def state(self): """Return the state.""" - return self.robot.waste_drawer_gauge + return getattr(self.robot, self.sensor_attribute) + + +class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity): + """Litter-Robot sensors.""" @property def unit_of_measurement(self): @@ -36,19 +48,40 @@ def unit_of_measurement(self): @property def icon(self): """Return the icon to use in the frontend, if any.""" - if self.robot.waste_drawer_gauge <= 10: - return "mdi:gauge-empty" - if self.robot.waste_drawer_gauge < 50: - return "mdi:gauge-low" - if self.robot.waste_drawer_gauge <= 90: - return "mdi:gauge" - return "mdi:gauge-full" + return icon_for_gauge_level(self.state, 10) + + +class LitterRobotSleepTimeSensor(LitterRobotPropertySensor, Entity): + """Litter-Robot sleep time sensors.""" + + @property + def state(self): + """Return the state.""" + if self.robot.sleep_mode_active: + return super().state.isoformat() + return None @property - def device_state_attributes(self): - """Return device specific state attributes.""" - return { - "cycle_count": self.robot.cycle_count, - "cycle_capacity": self.robot.cycle_capacity, - "cycles_after_drawer_full": self.robot.cycles_after_drawer_full, - } + def device_class(self): + """Return the device class, if any.""" + return DEVICE_CLASS_TIMESTAMP + + +ROBOT_SENSORS = [ + (LitterRobotWasteSensor, "Waste Drawer", "waste_drawer_gauge"), + (LitterRobotSleepTimeSensor, "Sleep Mode Start Time", "sleep_mode_start_time"), + (LitterRobotSleepTimeSensor, "Sleep Mode End Time", "sleep_mode_end_time"), +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Litter-Robot sensors using config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + entities = [] + for robot in hub.account.robots: + for (sensor_class, entity_type, sensor_attribute) in ROBOT_SENSORS: + entities.append(sensor_class(robot, entity_type, hub, sensor_attribute)) + + if entities: + async_add_entities(entities, True) diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py index b94b29a35e1f59..9164cc35e900af 100644 --- a/homeassistant/components/litterrobot/switch.py +++ b/homeassistant/components/litterrobot/switch.py @@ -1,11 +1,11 @@ """Support for Litter-Robot switches.""" -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.switch import SwitchEntity from .const import DOMAIN from .hub import LitterRobotEntity -class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity): +class LitterRobotNightLightModeSwitch(LitterRobotEntity, SwitchEntity): """Litter-Robot Night Light Mode Switch.""" @property @@ -27,7 +27,7 @@ async def async_turn_off(self, **kwargs): await self.perform_action_and_refresh(self.robot.set_night_light, False) -class LitterRobotPanelLockoutSwitch(LitterRobotEntity, ToggleEntity): +class LitterRobotPanelLockoutSwitch(LitterRobotEntity, SwitchEntity): """Litter-Robot Panel Lockout Switch.""" @property diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 6ee92993869b69..4fe76d446f4779 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -14,7 +14,6 @@ VacuumEntity, ) from homeassistant.const import STATE_OFF -import homeassistant.util.dt as dt_util from .const import DOMAIN from .hub import LitterRobotEntity @@ -54,27 +53,22 @@ def supported_features(self): def state(self): """Return the state of the cleaner.""" switcher = { - Robot.UnitStatus.CCP: STATE_CLEANING, - Robot.UnitStatus.EC: STATE_CLEANING, - Robot.UnitStatus.CCC: STATE_DOCKED, - Robot.UnitStatus.CST: STATE_DOCKED, - Robot.UnitStatus.DF1: STATE_DOCKED, - Robot.UnitStatus.DF2: STATE_DOCKED, - Robot.UnitStatus.RDY: STATE_DOCKED, + Robot.UnitStatus.CLEAN_CYCLE: STATE_CLEANING, + Robot.UnitStatus.EMPTY_CYCLE: STATE_CLEANING, + Robot.UnitStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED, + Robot.UnitStatus.CAT_SENSOR_TIMING: STATE_DOCKED, + Robot.UnitStatus.DRAWER_FULL_1: STATE_DOCKED, + Robot.UnitStatus.DRAWER_FULL_2: STATE_DOCKED, + Robot.UnitStatus.READY: STATE_DOCKED, Robot.UnitStatus.OFF: STATE_OFF, } return switcher.get(self.robot.unit_status, STATE_ERROR) - @property - def error(self): - """Return the error associated with the current state, if any.""" - return self.robot.unit_status.value - @property def status(self): """Return the status of the cleaner.""" - return f"{self.robot.unit_status.value}{' (Sleeping)' if self.robot.is_sleeping else ''}" + return f"{self.robot.unit_status.label}{' (Sleeping)' if self.robot.is_sleeping else ''}" async def async_turn_on(self, **kwargs): """Turn the cleaner on, starting a clean cycle.""" @@ -119,22 +113,11 @@ async def async_send_command(self, command, params=None, **kwargs): @property def device_state_attributes(self): """Return device specific state attributes.""" - [sleep_mode_start_time, sleep_mode_end_time] = [None, None] - - if self.robot.sleep_mode_active: - sleep_mode_start_time = dt_util.as_local( - self.robot.sleep_mode_start_time - ).strftime("%H:%M:00") - sleep_mode_end_time = dt_util.as_local( - self.robot.sleep_mode_end_time - ).strftime("%H:%M:00") - return { "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, "is_sleeping": self.robot.is_sleeping, - "sleep_mode_start_time": sleep_mode_start_time, - "sleep_mode_end_time": sleep_mode_end_time, + "sleep_mode_active": self.robot.sleep_mode_active, "power_status": self.robot.power_status, - "unit_status_code": self.robot.unit_status.name, + "unit_status_code": self.robot.unit_status.value, "last_seen": self.robot.last_seen, } diff --git a/requirements_all.txt b/requirements_all.txt index 46c25915af96a6..2e0e0a16ad9d27 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1501,7 +1501,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2021.2.5 +pylitterbot==2021.2.8 # homeassistant.components.loopenergy pyloopenergy==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4404d099050014..c5c96aefd78429 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -791,7 +791,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2021.2.5 +pylitterbot==2021.2.8 # homeassistant.components.lutron_caseta pylutron-caseta==0.9.0 diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index dae183b4cf6f04..aadf7d810aa5d2 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,45 +1,59 @@ """Configure pytest for Litter-Robot tests.""" +from typing import Optional from unittest.mock import AsyncMock, MagicMock, patch +import pylitterbot from pylitterbot import Robot import pytest from homeassistant.components import litterrobot -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .common import CONFIG, ROBOT_DATA from tests.common import MockConfigEntry -def create_mock_robot(hass): +def create_mock_robot(unit_status_code: Optional[str] = None): """Create a mock Litter-Robot device.""" - robot = Robot(data=ROBOT_DATA) - robot.start_cleaning = AsyncMock() - robot.set_power_status = AsyncMock() - robot.reset_waste_drawer = AsyncMock() - robot.set_sleep_mode = AsyncMock() - robot.set_night_light = AsyncMock() - robot.set_panel_lockout = AsyncMock() - return robot - - -@pytest.fixture() -def mock_hub(hass): - """Mock a Litter-Robot hub.""" - hub = MagicMock( - hass=hass, - account=MagicMock(), - logged_in=True, - coordinator=MagicMock(spec=DataUpdateCoordinator), - spec=litterrobot.LitterRobotHub, - ) - hub.coordinator.last_update_success = True - hub.account.robots = [create_mock_robot(hass)] - return hub + if not ( + unit_status_code + and Robot.UnitStatus(unit_status_code) != Robot.UnitStatus.UNKNOWN + ): + unit_status_code = ROBOT_DATA["unitStatus"] + + with patch.dict(ROBOT_DATA, {"unitStatus": unit_status_code}): + robot = Robot(data=ROBOT_DATA) + robot.start_cleaning = AsyncMock() + robot.set_power_status = AsyncMock() + robot.reset_waste_drawer = AsyncMock() + robot.set_sleep_mode = AsyncMock() + robot.set_night_light = AsyncMock() + robot.set_panel_lockout = AsyncMock() + return robot + + +def create_mock_account(unit_status_code: Optional[str] = None): + """Create a mock Litter-Robot account.""" + account = MagicMock(spec=pylitterbot.Account) + account.connect = AsyncMock() + account.refresh_robots = AsyncMock() + account.robots = [create_mock_robot(unit_status_code)] + return account -async def setup_hub(hass, mock_hub, platform_domain): +@pytest.fixture +def mock_account(): + """Mock a Litter-Robot account.""" + return create_mock_account() + + +@pytest.fixture +def mock_account_with_error(): + """Mock a Litter-Robot account with error.""" + return create_mock_account("BR") + + +async def setup_integration(hass, mock_account, platform_domain=None): """Load a Litter-Robot platform with the provided hub.""" entry = MockConfigEntry( domain=litterrobot.DOMAIN, @@ -47,9 +61,11 @@ async def setup_hub(hass, mock_hub, platform_domain): ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.litterrobot.LitterRobotHub", - return_value=mock_hub, + with patch("pylitterbot.Account", return_value=mock_account), patch( + "homeassistant.components.litterrobot.PLATFORMS", + [platform_domain] if platform_domain else [], ): - await hass.config_entries.async_forward_entry_setup(entry, platform_domain) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() + + return entry diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py index fd88595d37e19e..5068ecf721bdd3 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -4,11 +4,14 @@ from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from homeassistant import config_entries, setup +from homeassistant.components import litterrobot from .common import CONF_USERNAME, CONFIG, DOMAIN +from tests.common import MockConfigEntry -async def test_form(hass): + +async def test_form(hass, mock_account): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -17,10 +20,7 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", - return_value=True, - ), patch( + with patch("pylitterbot.Account", return_value=mock_account), patch( "homeassistant.components.litterrobot.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.litterrobot.async_setup_entry", @@ -38,6 +38,23 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_already_configured(hass): + """Test we handle already configured.""" + MockConfigEntry( + domain=litterrobot.DOMAIN, + data=CONFIG[litterrobot.DOMAIN], + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=CONFIG[litterrobot.DOMAIN], + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + async def test_form_invalid_auth(hass): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -45,7 +62,7 @@ async def test_form_invalid_auth(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=LitterRobotLoginException, ): result2 = await hass.config_entries.flow.async_configure( @@ -63,7 +80,7 @@ async def test_form_cannot_connect(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=LitterRobotException, ): result2 = await hass.config_entries.flow.async_configure( @@ -81,7 +98,7 @@ async def test_form_unknown_error(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=Exception, ): result2 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py index 1d0ed075cc77e7..7cd36f33883c18 100644 --- a/tests/components/litterrobot/test_init.py +++ b/tests/components/litterrobot/test_init.py @@ -1,20 +1,48 @@ """Test Litter-Robot setup process.""" +from unittest.mock import patch + +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException +import pytest + from homeassistant.components import litterrobot -from homeassistant.setup import async_setup_component +from homeassistant.config_entries import ( + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) from .common import CONFIG +from .conftest import setup_integration from tests.common import MockConfigEntry -async def test_unload_entry(hass): +async def test_unload_entry(hass, mock_account): """Test being able to unload an entry.""" + entry = await setup_integration(hass, mock_account) + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert hass.data[litterrobot.DOMAIN] == {} + + +@pytest.mark.parametrize( + "side_effect,expected_state", + ( + (LitterRobotLoginException, ENTRY_STATE_SETUP_ERROR), + (LitterRobotException, ENTRY_STATE_SETUP_RETRY), + ), +) +async def test_entry_not_setup(hass, side_effect, expected_state): + """Test being able to handle config entry not setup.""" entry = MockConfigEntry( domain=litterrobot.DOMAIN, data=CONFIG[litterrobot.DOMAIN], ) entry.add_to_hass(hass) - assert await async_setup_component(hass, litterrobot.DOMAIN, {}) is True - assert await litterrobot.async_unload_entry(hass, entry) - assert hass.data[litterrobot.DOMAIN] == {} + with patch( + "pylitterbot.Account.connect", + side_effect=side_effect, + ): + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state == expected_state diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index 2421489e2376b5..7f1570c553eeb2 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -1,20 +1,57 @@ """Test the Litter-Robot sensor entity.""" +from unittest.mock import Mock + +from homeassistant.components.litterrobot.sensor import LitterRobotSleepTimeSensor from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN -from homeassistant.const import PERCENTAGE +from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE -from .conftest import setup_hub +from .conftest import create_mock_robot, setup_integration -ENTITY_ID = "sensor.test_waste_drawer" +WASTE_DRAWER_ENTITY_ID = "sensor.test_waste_drawer" -async def test_sensor(hass, mock_hub): - """Tests the sensor entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) +async def test_waste_drawer_sensor(hass, mock_account): + """Tests the waste drawer sensor entity was set up.""" + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) - sensor = hass.states.get(ENTITY_ID) + sensor = hass.states.get(WASTE_DRAWER_ENTITY_ID) assert sensor assert sensor.state == "50" - assert sensor.attributes["cycle_count"] == 15 - assert sensor.attributes["cycle_capacity"] == 30 - assert sensor.attributes["cycles_after_drawer_full"] == 0 assert sensor.attributes["unit_of_measurement"] == PERCENTAGE + + +async def test_sleep_time_sensor_with_none_state(hass): + """Tests the sleep mode start time sensor where sleep mode is inactive.""" + robot = create_mock_robot() + robot.sleep_mode_active = False + sensor = LitterRobotSleepTimeSensor( + robot, "Sleep Mode Start Time", Mock(), "sleep_mode_start_time" + ) + + assert sensor + assert sensor.state is None + assert sensor.device_class == DEVICE_CLASS_TIMESTAMP + + +async def test_gauge_icon(): + """Test icon generator for gauge sensor.""" + from homeassistant.components.litterrobot.sensor import icon_for_gauge_level + + GAUGE_EMPTY = "mdi:gauge-empty" + GAUGE_LOW = "mdi:gauge-low" + GAUGE = "mdi:gauge" + GAUGE_FULL = "mdi:gauge-full" + + assert icon_for_gauge_level(None) == GAUGE_EMPTY + assert icon_for_gauge_level(0) == GAUGE_EMPTY + assert icon_for_gauge_level(5) == GAUGE_LOW + assert icon_for_gauge_level(40) == GAUGE + assert icon_for_gauge_level(80) == GAUGE_FULL + assert icon_for_gauge_level(100) == GAUGE_FULL + + assert icon_for_gauge_level(None, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(0, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(5, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(40, 10) == GAUGE_LOW + assert icon_for_gauge_level(80, 10) == GAUGE + assert icon_for_gauge_level(100, 10) == GAUGE_FULL diff --git a/tests/components/litterrobot/test_switch.py b/tests/components/litterrobot/test_switch.py index c7f85db74125b8..69154bef8f5c8e 100644 --- a/tests/components/litterrobot/test_switch.py +++ b/tests/components/litterrobot/test_switch.py @@ -12,7 +12,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_ON from homeassistant.util.dt import utcnow -from .conftest import setup_hub +from .conftest import setup_integration from tests.common import async_fire_time_changed @@ -20,9 +20,9 @@ PANEL_LOCKOUT_ENTITY_ID = "switch.test_panel_lockout" -async def test_switch(hass, mock_hub): +async def test_switch(hass, mock_account): """Tests the switch entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) switch = hass.states.get(NIGHT_LIGHT_MODE_ENTITY_ID) assert switch @@ -36,9 +36,9 @@ async def test_switch(hass, mock_hub): (PANEL_LOCKOUT_ENTITY_ID, "set_panel_lockout"), ], ) -async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): +async def test_on_off_commands(hass, mock_account, entity_id, robot_command): """Test sending commands to the switch.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) switch = hass.states.get(entity_id) assert switch @@ -48,12 +48,14 @@ async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): count = 0 for service in [SERVICE_TURN_ON, SERVICE_TURN_OFF]: count += 1 + await hass.services.async_call( PLATFORM_DOMAIN, service, data, blocking=True, ) + future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) async_fire_time_changed(hass, future) - assert getattr(mock_hub.account.robots[0], robot_command).call_count == count + assert getattr(mock_account.robots[0], robot_command).call_count == count diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py index 03e63b472b62f4..2db2ef21546be5 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -12,20 +12,21 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_DOCKED, + STATE_ERROR, ) from homeassistant.const import ATTR_COMMAND, ATTR_ENTITY_ID from homeassistant.util.dt import utcnow -from .conftest import setup_hub +from .conftest import setup_integration from tests.common import async_fire_time_changed ENTITY_ID = "vacuum.test_litter_box" -async def test_vacuum(hass, mock_hub): +async def test_vacuum(hass, mock_account): """Tests the vacuum entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) assert vacuum @@ -33,6 +34,15 @@ async def test_vacuum(hass, mock_hub): assert vacuum.attributes["is_sleeping"] is False +async def test_vacuum_with_error(hass, mock_account_with_error): + """Tests a vacuum entity with an error.""" + await setup_integration(hass, mock_account_with_error, PLATFORM_DOMAIN) + + vacuum = hass.states.get(ENTITY_ID) + assert vacuum + assert vacuum.state == STATE_ERROR + + @pytest.mark.parametrize( "service,command,extra", [ @@ -52,14 +62,22 @@ async def test_vacuum(hass, mock_hub): ATTR_PARAMS: {"enabled": True, "sleep_time": "22:30"}, }, ), + ( + SERVICE_SEND_COMMAND, + "set_sleep_mode", + { + ATTR_COMMAND: "set_sleep_mode", + ATTR_PARAMS: {"enabled": True, "sleep_time": None}, + }, + ), ], ) -async def test_commands(hass, mock_hub, service, command, extra): +async def test_commands(hass, mock_account, service, command, extra): """Test sending commands to the vacuum.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) - assert vacuum is not None + assert vacuum assert vacuum.state == STATE_DOCKED data = {ATTR_ENTITY_ID: ENTITY_ID} @@ -74,4 +92,4 @@ async def test_commands(hass, mock_hub, service, command, extra): ) future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) async_fire_time_changed(hass, future) - getattr(mock_hub.account.robots[0], command).assert_called_once() + getattr(mock_account.robots[0], command).assert_called_once() From 1600207f5cd73ecd70bf965f1787a7f5aae6b81d Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 18:33:55 +0100 Subject: [PATCH 1073/1818] Fix mysensors notify platform (#47517) --- .../components/mysensors/__init__.py | 47 ++++++++++++++----- .../components/mysensors/device_tracker.py | 3 ++ homeassistant/components/mysensors/helpers.py | 4 +- homeassistant/components/mysensors/notify.py | 3 ++ 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 33ac4932889225..f2478da5b57240 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,5 +1,6 @@ """Connect to a MySensors gateway via pymysensors API.""" import asyncio +from functools import partial import logging from typing import Callable, Dict, List, Optional, Tuple, Type, Union @@ -8,10 +9,13 @@ from homeassistant import config_entries from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic +from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_OPTIMISTIC -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( @@ -28,6 +32,7 @@ CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, + MYSENSORS_DISCOVERY, MYSENSORS_GATEWAYS, MYSENSORS_ON_UNLOAD, PLATFORMS_WITH_ENTRY_SUPPORT, @@ -43,6 +48,8 @@ CONF_DEBUG = "debug" CONF_NODE_NAME = "name" +DATA_HASS_CONFIG = "hass_config" + DEFAULT_BAUD_RATE = 115200 DEFAULT_TCP_PORT = 5003 DEFAULT_VERSION = "1.4" @@ -134,6 +141,8 @@ def validator(config): async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up the MySensors component.""" + hass.data[DOMAIN] = {DATA_HASS_CONFIG: config} + if DOMAIN not in config or bool(hass.config_entries.async_entries(DOMAIN)): return True @@ -181,14 +190,31 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool _LOGGER.error("Gateway setup failed for %s", entry.data) return False - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway - async def finish(): + # Connect notify discovery as that integration doesn't support entry forwarding. + + load_notify_platform = partial( + async_load_platform, + hass, + NOTIFY_DOMAIN, + DOMAIN, + hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], + ) + + await on_unload( + hass, + entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), + load_notify_platform, + ), + ) + + async def finish() -> None: await asyncio.gather( *[ hass.config_entries.async_forward_entry_setup(entry, platform) @@ -248,14 +274,14 @@ async def on_unload( @callback def setup_mysensors_platform( - hass, + hass: HomeAssistant, domain: str, # hass platform name - discovery_info: Optional[Dict[str, List[DevId]]], + discovery_info: Dict[str, List[DevId]], device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]], device_args: Optional[ Tuple ] = None, # extra arguments that will be given to the entity constructor - async_add_entities: Callable = None, + async_add_entities: Optional[Callable] = None, ) -> Optional[List[MySensorsDevice]]: """Set up a MySensors platform. @@ -264,11 +290,6 @@ def setup_mysensors_platform( The function is also given a class. A new instance of the class is created for every device id, and the device id is given to the constructor of the class """ - # Only act if called via MySensors by discovery event. - # Otherwise gateway is not set up. - if not discovery_info: - _LOGGER.debug("Skipping setup due to no discovery info") - return None if device_args is None: device_args = () new_devices: List[MySensorsDevice] = [] diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index b395a48f28b7da..d1f89e4fe04733 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -12,6 +12,9 @@ async def async_setup_scanner( hass: HomeAssistantType, config, async_see, discovery_info=None ): """Set up the MySensors device scanner.""" + if not discovery_info: + return False + new_devices = mysensors.setup_mysensors_platform( hass, DOMAIN, diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index d06bf0dee2fe57..4452dd0575b3f3 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.const import CONF_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util.decorator import Registry @@ -33,7 +33,7 @@ @callback def discover_mysensors_platform( - hass, gateway_id: GatewayId, platform: str, new_devices: List[DevId] + hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] ) -> None: """Discover a MySensors platform.""" _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices) diff --git a/homeassistant/components/mysensors/notify.py b/homeassistant/components/mysensors/notify.py index 99e731762df5c8..50fca55ab39703 100644 --- a/homeassistant/components/mysensors/notify.py +++ b/homeassistant/components/mysensors/notify.py @@ -5,6 +5,9 @@ async def async_get_service(hass, config, discovery_info=None): """Get the MySensors notification service.""" + if not discovery_info: + return None + new_devices = mysensors.setup_mysensors_platform( hass, DOMAIN, discovery_info, MySensorsNotificationDevice ) From d944bbbc5256aade0db0d594aaa91632fe2ab0d8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 23:06:50 +0100 Subject: [PATCH 1074/1818] Bump pymysensors to 0.21.0 (#47530) --- homeassistant/components/mysensors/manifest.json | 13 +++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mysensors/manifest.json b/homeassistant/components/mysensors/manifest.json index 8371f2930c2fb1..c7d439dedc44dd 100644 --- a/homeassistant/components/mysensors/manifest.json +++ b/homeassistant/components/mysensors/manifest.json @@ -2,15 +2,8 @@ "domain": "mysensors", "name": "MySensors", "documentation": "https://www.home-assistant.io/integrations/mysensors", - "requirements": [ - "pymysensors==0.20.1" - ], - "after_dependencies": [ - "mqtt" - ], - "codeowners": [ - "@MartinHjelmare", - "@functionpointer" - ], + "requirements": ["pymysensors==0.21.0"], + "after_dependencies": ["mqtt"], + "codeowners": ["@MartinHjelmare", "@functionpointer"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 2e0e0a16ad9d27..c31d6ee7aa640d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1552,7 +1552,7 @@ pymusiccast==0.1.6 pymyq==3.0.4 # homeassistant.components.mysensors -pymysensors==0.20.1 +pymysensors==0.21.0 # homeassistant.components.nanoleaf pynanoleaf==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5c96aefd78429..496ed7ec4a875b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -824,7 +824,7 @@ pymonoprice==0.3 pymyq==3.0.4 # homeassistant.components.mysensors -pymysensors==0.20.1 +pymysensors==0.21.0 # homeassistant.components.nuki pynuki==1.3.8 From b01a6367cc631c22b6a71994df00006bd5ef301e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 6 Mar 2021 23:19:03 +0100 Subject: [PATCH 1075/1818] Complete typing on AdGuard Home integration (#47477) --- homeassistant/components/adguard/__init__.py | 8 ++-- .../components/adguard/config_flow.py | 28 ++++++++---- homeassistant/components/adguard/sensor.py | 43 ++++++++++--------- homeassistant/components/adguard/switch.py | 42 ++++++++++-------- tests/components/adguard/test_config_flow.py | 30 +++++++++---- 5 files changed, 94 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 0a316784f8b252..6ad7d9579a8496 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -1,6 +1,8 @@ """Support for AdGuard Home.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError import voluptuous as vol @@ -117,7 +119,7 @@ async def refresh(call) -> None: return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigType) -> bool: +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Unload AdGuard Home config entry.""" hass.services.async_remove(DOMAIN, SERVICE_ADD_URL) hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL) @@ -191,7 +193,7 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity): """Defines a AdGuard Home device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this AdGuard Home instance.""" return { "identifiers": { diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index d728eed3003c4a..308670272b5902 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -1,9 +1,12 @@ """Config flow to configure the AdGuard Home integration.""" +from __future__ import annotations + +from typing import Any + from adguardhome import AdGuardHome, AdGuardHomeConnectionError import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.adguard.const import DOMAIN from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_HOST, @@ -15,9 +18,10 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .const import DOMAIN # pylint: disable=unused-import + -@config_entries.HANDLERS.register(DOMAIN) -class AdGuardHomeFlowHandler(ConfigFlow): +class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a AdGuard Home config flow.""" VERSION = 1 @@ -25,7 +29,9 @@ class AdGuardHomeFlowHandler(ConfigFlow): _hassio_discovery = None - async def _show_setup_form(self, errors=None): + async def _show_setup_form( + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -42,7 +48,9 @@ async def _show_setup_form(self, errors=None): errors=errors or {}, ) - async def _show_hassio_form(self, errors=None): + async def _show_hassio_form( + self, errors: dict[str, str] | None = None + ) -> dict[str, Any]: """Show the Hass.io confirmation form to the user.""" return self.async_show_form( step_id="hassio_confirm", @@ -51,7 +59,9 @@ async def _show_hassio_form(self, errors=None): errors=errors or {}, ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -91,7 +101,7 @@ async def async_step_user(self, user_input=None): }, ) - async def async_step_hassio(self, discovery_info): + async def async_step_hassio(self, discovery_info: dict[str, Any]) -> dict[str, Any]: """Prepare configuration for a Hass.io AdGuard Home add-on. This flow is triggered by the discovery component. @@ -129,7 +139,9 @@ async def async_step_hassio(self, discovery_info): return self.async_abort(reason="existing_instance_updated") - async def async_step_hassio_confirm(self, user_input=None): + async def async_step_hassio_confirm( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm Hass.io discovery.""" if user_input is None: return await self._show_hassio_form() diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 05e23ba8b80dfa..edd9fe22ba9ee4 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -1,25 +1,28 @@ """Support for AdGuard Home sensors.""" +from __future__ import annotations + from datetime import timedelta +from typing import Callable -from adguardhome import AdGuardHomeConnectionError +from adguardhome import AdGuardHome, AdGuardHomeConnectionError -from homeassistant.components.adguard import AdGuardHomeDeviceEntity -from homeassistant.components.adguard.const import ( - DATA_ADGUARD_CLIENT, - DATA_ADGUARD_VERION, - DOMAIN, -) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS +from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.entity import Entity + +from . import AdGuardHomeDeviceEntity +from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN SCAN_INTERVAL = timedelta(seconds=300) PARALLEL_UPDATES = 4 async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up AdGuard Home sensor based on a config entry.""" adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT] @@ -50,7 +53,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity): def __init__( self, - adguard, + adguard: AdGuardHome, name: str, icon: str, measurement: str, @@ -78,12 +81,12 @@ def unique_id(self) -> str: ) @property - def state(self): + def state(self) -> str | None: """Return the state of the sensor.""" return self._state @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return self._unit_of_measurement @@ -91,7 +94,7 @@ def unit_of_measurement(self) -> str: class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor): """Defines a AdGuard Home DNS Queries sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, "AdGuard DNS Queries", "mdi:magnify", "dns_queries", "queries" @@ -105,7 +108,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor): """Defines a AdGuard Home blocked by filtering sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -124,7 +127,7 @@ async def _adguard_update(self) -> None: class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor): """Defines a AdGuard Home blocked percentage sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -143,7 +146,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor): """Defines a AdGuard Home replaced by parental control sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -161,7 +164,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor): """Defines a AdGuard Home replaced by safe browsing sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -179,7 +182,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor): """Defines a AdGuard Home replaced by safe search sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -197,7 +200,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor): """Defines a AdGuard Home average processing time sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, @@ -216,7 +219,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeRulesCountSensor(AdGuardHomeSensor): """Defines a AdGuard Home rules count sensor.""" - def __init__(self, adguard): + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home sensor.""" super().__init__( adguard, diff --git a/homeassistant/components/adguard/switch.py b/homeassistant/components/adguard/switch.py index 44aab11573d04a..0b127a280cfad5 100644 --- a/homeassistant/components/adguard/switch.py +++ b/homeassistant/components/adguard/switch.py @@ -1,19 +1,20 @@ """Support for AdGuard Home switches.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Callable -from adguardhome import AdGuardHomeConnectionError, AdGuardHomeError +from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError -from homeassistant.components.adguard import AdGuardHomeDeviceEntity -from homeassistant.components.adguard.const import ( - DATA_ADGUARD_CLIENT, - DATA_ADGUARD_VERION, - DOMAIN, -) from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.entity import Entity + +from . import AdGuardHomeDeviceEntity +from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -22,7 +23,9 @@ async def async_setup_entry( - hass: HomeAssistantType, entry: ConfigEntry, async_add_entities + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up AdGuard Home switch based on a config entry.""" adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT] @@ -49,8 +52,13 @@ class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity): """Defines a AdGuard Home switch.""" def __init__( - self, adguard, name: str, icon: str, key: str, enabled_default: bool = True - ): + self, + adguard: AdGuardHome, + name: str, + icon: str, + key: str, + enabled_default: bool = True, + ) -> None: """Initialize AdGuard Home switch.""" self._state = False self._key = key @@ -96,7 +104,7 @@ async def _adguard_turn_on(self) -> None: class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home protection switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Protection", "mdi:shield-check", "protection" @@ -118,7 +126,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeParentalSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home parental control switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Parental Control", "mdi:shield-check", "parental" @@ -140,7 +148,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home safe search switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Safe Search", "mdi:shield-check", "safesearch" @@ -162,7 +170,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home safe search switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, "AdGuard Safe Browsing", "mdi:shield-check", "safebrowsing" @@ -184,7 +192,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home filtering switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__(adguard, "AdGuard Filtering", "mdi:shield-check", "filtering") @@ -204,7 +212,7 @@ async def _adguard_update(self) -> None: class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch): """Defines a AdGuard Home query log switch.""" - def __init__(self, adguard) -> None: + def __init__(self, adguard: AdGuardHome) -> None: """Initialize AdGuard Home switch.""" super().__init__( adguard, diff --git a/tests/components/adguard/test_config_flow.py b/tests/components/adguard/test_config_flow.py index 06fe235741f746..94760cade9fa4d 100644 --- a/tests/components/adguard/test_config_flow.py +++ b/tests/components/adguard/test_config_flow.py @@ -16,8 +16,10 @@ CONF_VERIFY_SSL, CONTENT_TYPE_JSON, ) +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker FIXTURE_USER_INPUT = { CONF_HOST: "127.0.0.1", @@ -29,7 +31,7 @@ } -async def test_show_authenticate_form(hass): +async def test_show_authenticate_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" flow = config_flow.AdGuardHomeFlowHandler() flow.hass = hass @@ -39,7 +41,9 @@ async def test_show_authenticate_form(hass): assert result["step_id"] == "user" -async def test_connection_error(hass, aioclient_mock): +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show user form on AdGuard Home connection error.""" aioclient_mock.get( f"{'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http'}" @@ -57,7 +61,9 @@ async def test_connection_error(hass, aioclient_mock): assert result["errors"] == {"base": "cannot_connect"} -async def test_full_flow_implementation(hass, aioclient_mock): +async def test_full_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test registering an integration and finishing flow works.""" aioclient_mock.get( f"{'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http'}" @@ -84,7 +90,7 @@ async def test_full_flow_implementation(hass, aioclient_mock): assert result["data"][CONF_VERIFY_SSL] == FIXTURE_USER_INPUT[CONF_VERIFY_SSL] -async def test_integration_already_exists(hass): +async def test_integration_already_exists(hass: HomeAssistant) -> None: """Test we only allow a single config flow.""" MockConfigEntry(domain=DOMAIN).add_to_hass(hass) @@ -95,7 +101,7 @@ async def test_integration_already_exists(hass): assert result["reason"] == "single_instance_allowed" -async def test_hassio_single_instance(hass): +async def test_hassio_single_instance(hass: HomeAssistant) -> None: """Test we only allow a single config flow.""" MockConfigEntry( domain="adguard", data={"host": "mock-adguard", "port": "3000"} @@ -110,7 +116,7 @@ async def test_hassio_single_instance(hass): assert result["reason"] == "single_instance_allowed" -async def test_hassio_update_instance_not_running(hass): +async def test_hassio_update_instance_not_running(hass: HomeAssistant) -> None: """Test we only allow a single config flow.""" entry = MockConfigEntry( domain="adguard", data={"host": "mock-adguard", "port": "3000"} @@ -131,7 +137,9 @@ async def test_hassio_update_instance_not_running(hass): assert result["reason"] == "existing_instance_updated" -async def test_hassio_update_instance_running(hass, aioclient_mock): +async def test_hassio_update_instance_running( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we only allow a single config flow.""" aioclient_mock.get( "http://mock-adguard-updated:3000/control/status", @@ -192,7 +200,9 @@ async def test_hassio_update_instance_running(hass, aioclient_mock): assert entry.data["host"] == "mock-adguard-updated" -async def test_hassio_confirm(hass, aioclient_mock): +async def test_hassio_confirm( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we can finish a config flow.""" aioclient_mock.get( "http://mock-adguard:3000/control/status", @@ -220,7 +230,9 @@ async def test_hassio_confirm(hass, aioclient_mock): assert result["data"][CONF_VERIFY_SSL] -async def test_hassio_connection_error(hass, aioclient_mock): +async def test_hassio_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test we show Hass.io confirm form on AdGuard Home connection error.""" aioclient_mock.get( "http://mock-adguard:3000/control/status", exc=aiohttp.ClientError From f542b360d5a82e52afc84c106775b07d86064017 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 23:41:43 +0100 Subject: [PATCH 1076/1818] Fix mysensors device tracker (#47536) --- .../components/mysensors/__init__.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index f2478da5b57240..bcab1ed86b41ad 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry @@ -195,24 +196,27 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway # Connect notify discovery as that integration doesn't support entry forwarding. + # Allow loading device tracker platform via discovery + # until refactor to config entry is done. - load_notify_platform = partial( - async_load_platform, - hass, - NOTIFY_DOMAIN, - DOMAIN, - hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], - ) + for platform in (DEVICE_TRACKER_DOMAIN, NOTIFY_DOMAIN): + load_discovery_platform = partial( + async_load_platform, + hass, + platform, + DOMAIN, + hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], + ) - await on_unload( - hass, - entry.entry_id, - async_dispatcher_connect( + await on_unload( hass, - MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), - load_notify_platform, - ), - ) + entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(entry.entry_id, platform), + load_discovery_platform, + ), + ) async def finish() -> None: await asyncio.gather( From 9101ed27327bfac96323b44665326d1cff575b80 Mon Sep 17 00:00:00 2001 From: N1c093 <59510296+N1c093@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:48:22 +0100 Subject: [PATCH 1077/1818] Add precipitation probability forecast to owm (#47284) * Add precipitation probability forecast to owm * Update weather_update_coordinator.py Reformat the code based on black --- homeassistant/components/openweathermap/const.py | 3 +++ .../components/openweathermap/weather_update_coordinator.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 7c8e8aeee57a2a..5b0165b2bee0e1 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -16,6 +16,7 @@ ATTR_CONDITION_WINDY_VARIANT, ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, @@ -93,6 +94,7 @@ FORECAST_MONITORED_CONDITIONS = [ ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, @@ -212,6 +214,7 @@ FORECAST_SENSOR_TYPES = { ATTR_FORECAST_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_FORECAST_PRECIPITATION: {SENSOR_NAME: "Precipitation"}, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: {SENSOR_NAME: "Precipitation probability"}, ATTR_FORECAST_PRESSURE: {SENSOR_NAME: "Pressure"}, ATTR_FORECAST_TEMP: { SENSOR_NAME: "Temperature", diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 93db4ca26d8125..201029c39795f9 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -10,6 +10,7 @@ ATTR_CONDITION_SUNNY, ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_PRESSURE, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, @@ -143,6 +144,9 @@ def _convert_forecast(self, entry): ATTR_FORECAST_PRECIPITATION: self._calc_precipitation( entry.rain, entry.snow ), + ATTR_FORECAST_PRECIPITATION_PROBABILITY: ( + entry.precipitation_probability * 100 + ), ATTR_FORECAST_PRESSURE: entry.pressure.get("press"), ATTR_FORECAST_WIND_SPEED: entry.wind().get("speed"), ATTR_FORECAST_WIND_BEARING: entry.wind().get("deg"), From 79b5ca9415a9977f6f030d055149f57645e57d3e Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 6 Mar 2021 18:52:43 -0500 Subject: [PATCH 1078/1818] Add device classes for CO and CO2 measurements (#47487) --- homeassistant/components/sensor/__init__.py | 4 ++++ homeassistant/components/sensor/device_condition.py | 8 ++++++++ homeassistant/components/sensor/device_trigger.py | 8 ++++++++ homeassistant/components/sensor/strings.json | 4 ++++ homeassistant/const.py | 2 ++ tests/components/sensor/test_device_trigger.py | 2 +- tests/testing_config/custom_components/test/sensor.py | 9 ++++++++- 7 files changed, 35 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 464620197492d8..187cb0c410d477 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -7,6 +7,8 @@ from homeassistant.const import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -36,6 +38,8 @@ SCAN_INTERVAL = timedelta(seconds=30) DEVICE_CLASSES = [ DEVICE_CLASS_BATTERY, # % of battery that is left + DEVICE_CLASS_CO, # ppm (parts per million) Carbon Monoxide gas concentration + DEVICE_CLASS_CO2, # ppm (parts per million) Carbon Dioxide gas concentration DEVICE_CLASS_CURRENT, # current (A) DEVICE_CLASS_ENERGY, # energy (kWh, Wh) DEVICE_CLASS_HUMIDITY, # % of humidity in the air diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index a9d44f2f860f53..e2efac7b141f69 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -14,6 +14,8 @@ CONF_ENTITY_ID, CONF_TYPE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -41,6 +43,8 @@ DEVICE_CLASS_NONE = "none" CONF_IS_BATTERY_LEVEL = "is_battery_level" +CONF_IS_CO = "is_carbon_monoxide" +CONF_IS_CO2 = "is_carbon_dioxide" CONF_IS_CURRENT = "is_current" CONF_IS_ENERGY = "is_energy" CONF_IS_HUMIDITY = "is_humidity" @@ -56,6 +60,8 @@ ENTITY_CONDITIONS = { DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_IS_BATTERY_LEVEL}], + DEVICE_CLASS_CO: [{CONF_TYPE: CONF_IS_CO}], + DEVICE_CLASS_CO2: [{CONF_TYPE: CONF_IS_CO2}], DEVICE_CLASS_CURRENT: [{CONF_TYPE: CONF_IS_CURRENT}], DEVICE_CLASS_ENERGY: [{CONF_TYPE: CONF_IS_ENERGY}], DEVICE_CLASS_HUMIDITY: [{CONF_TYPE: CONF_IS_HUMIDITY}], @@ -77,6 +83,8 @@ vol.Required(CONF_TYPE): vol.In( [ CONF_IS_BATTERY_LEVEL, + CONF_IS_CO, + CONF_IS_CO2, CONF_IS_CURRENT, CONF_IS_ENERGY, CONF_IS_HUMIDITY, diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 86dda53cd2bf28..9586261a191082 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -17,6 +17,8 @@ CONF_FOR, CONF_TYPE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, @@ -39,6 +41,8 @@ DEVICE_CLASS_NONE = "none" CONF_BATTERY_LEVEL = "battery_level" +CONF_CO = "carbon_monoxide" +CONF_CO2 = "carbon_dioxide" CONF_CURRENT = "current" CONF_ENERGY = "energy" CONF_HUMIDITY = "humidity" @@ -54,6 +58,8 @@ ENTITY_TRIGGERS = { DEVICE_CLASS_BATTERY: [{CONF_TYPE: CONF_BATTERY_LEVEL}], + DEVICE_CLASS_CO: [{CONF_TYPE: CONF_CO}], + DEVICE_CLASS_CO2: [{CONF_TYPE: CONF_CO2}], DEVICE_CLASS_CURRENT: [{CONF_TYPE: CONF_CURRENT}], DEVICE_CLASS_ENERGY: [{CONF_TYPE: CONF_ENERGY}], DEVICE_CLASS_HUMIDITY: [{CONF_TYPE: CONF_HUMIDITY}], @@ -76,6 +82,8 @@ vol.Required(CONF_TYPE): vol.In( [ CONF_BATTERY_LEVEL, + CONF_CO, + CONF_CO2, CONF_CURRENT, CONF_ENERGY, CONF_HUMIDITY, diff --git a/homeassistant/components/sensor/strings.json b/homeassistant/components/sensor/strings.json index 76ea9efabc331a..4298a367c2c0f8 100644 --- a/homeassistant/components/sensor/strings.json +++ b/homeassistant/components/sensor/strings.json @@ -3,6 +3,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Current {entity_name} battery level", + "is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level", + "is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level", "is_humidity": "Current {entity_name} humidity", "is_illuminance": "Current {entity_name} illuminance", "is_power": "Current {entity_name} power", @@ -18,6 +20,8 @@ }, "trigger_type": { "battery_level": "{entity_name} battery level changes", + "carbon_monoxide": "{entity_name} carbon monoxide concentration changes", + "carbon_dioxide": "{entity_name} carbon dioxide concentration changes", "humidity": "{entity_name} humidity changes", "illuminance": "{entity_name} illuminance changes", "power": "{entity_name} power changes", diff --git a/homeassistant/const.py b/homeassistant/const.py index 1076b962f2a0d7..b601135279ff19 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -219,6 +219,8 @@ # #### DEVICE CLASSES #### DEVICE_CLASS_BATTERY = "battery" +DEVICE_CLASS_CO = "carbon_monoxide" +DEVICE_CLASS_CO2 = "carbon_dioxide" DEVICE_CLASS_HUMIDITY = "humidity" DEVICE_CLASS_ILLUMINANCE = "illuminance" DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index eb38060f0dd0b9..ed1da9f86dda89 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -77,7 +77,7 @@ async def test_get_triggers(hass, device_reg, entity_reg): if device_class != "none" ] triggers = await async_get_device_automations(hass, "trigger", device_entry.id) - assert len(triggers) == 12 + assert len(triggers) == 14 assert triggers == expected_triggers diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index d467a93fd6214d..de6f179daa76bd 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -4,7 +4,12 @@ Call init before using it in your tests to ensure clean test data. """ import homeassistant.components.sensor as sensor -from homeassistant.const import PERCENTAGE, PRESSURE_HPA, SIGNAL_STRENGTH_DECIBELS +from homeassistant.const import ( + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, + PRESSURE_HPA, + SIGNAL_STRENGTH_DECIBELS, +) from tests.common import MockEntity @@ -13,6 +18,8 @@ UNITS_OF_MEASUREMENT = { sensor.DEVICE_CLASS_BATTERY: PERCENTAGE, # % of battery that is left + sensor.DEVICE_CLASS_CO: CONCENTRATION_PARTS_PER_MILLION, # ppm of CO concentration + sensor.DEVICE_CLASS_CO2: CONCENTRATION_PARTS_PER_MILLION, # ppm of CO2 concentration sensor.DEVICE_CLASS_HUMIDITY: PERCENTAGE, # % of humidity in the air sensor.DEVICE_CLASS_ILLUMINANCE: "lm", # current light level (lx/lm) sensor.DEVICE_CLASS_SIGNAL_STRENGTH: SIGNAL_STRENGTH_DECIBELS, # signal strength (dB/dBm) From 2e89f152ba13ffd768f54181721a45c590801cad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Mar 2021 14:30:57 -1000 Subject: [PATCH 1079/1818] Bump HAP-python to 3.4.0 (#47476) * Bump HAP-python to 3.3.3 * bump * fix mocking --- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_homekit.py | 26 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index e6d3a8abff426b..23492b12ccc64d 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -616,7 +616,7 @@ async def async_start(self, *args): self._async_register_bridge(dev_reg) await self._async_start(bridged_states) _LOGGER.debug("Driver start for %s", self._name) - self.hass.add_job(self.driver.start_service) + await self.driver.async_start() self.status = STATUS_RUNNING @callback diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index ac3fb0251e2e74..d7ec3297fa499b 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.2", + "HAP-python==3.4.0", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index c31d6ee7aa640d..f27e3835467e24 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.2 +HAP-python==3.4.0 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 496ed7ec4a875b..d06ac8bfd5bfd6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.2 +HAP-python==3.4.0 # homeassistant.components.flick_electric PyFlick==0.0.2 diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 9ce3e96f06f985..4d2fbfe951d27f 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -493,7 +493,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -528,7 +528,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -567,7 +567,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory", ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -630,7 +630,7 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf): ), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch( "pyhap.accessory_driver.AccessoryDriver.config_changed" ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await async_init_entry(hass, entry) @@ -674,7 +674,7 @@ def _mock_bridge(*_): hass.states.async_set("light.demo2", "on") hass.states.async_set("light.demo3", "on") - with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge @@ -738,7 +738,7 @@ async def test_homekit_finds_linked_batteries( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -810,7 +810,7 @@ async def test_homekit_async_get_integration_fails( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -895,7 +895,7 @@ async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): assert await async_setup_component(hass, "zeroconf", {"zeroconf": {}}) system_zc = await zeroconf.async_get_instance(hass) - with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( f"{PATH_HOMEKIT}.HomeKit.async_stop" ): entry.add_to_hass(hass) @@ -963,7 +963,7 @@ async def test_homekit_ignored_missing_devices( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1025,7 +1025,7 @@ async def test_homekit_finds_linked_motion_sensors( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1090,7 +1090,7 @@ async def test_homekit_finds_linked_humidity_sensors( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1153,7 +1153,7 @@ async def test_reload(hass, mock_zeroconf): ), patch( f"{PATH_HOMEKIT}.get_accessory" ), patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): mock_homekit2.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -1205,7 +1205,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() From 0d07dae3bc206f3bc59e027b3635550dc36f0e05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Mar 2021 21:41:56 -1000 Subject: [PATCH 1080/1818] Change default homekit ports to 21063 and 21064 (#47491) We previously used a value in the linux default ephemerial port range which meant that if something else happened to use that port HomeKit would not start up. We now use a value below 32768 to ensure that the port is not randomly unavailable --- homeassistant/components/homekit/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 67312903b50de4..840e9ebe60773e 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -74,8 +74,8 @@ DEFAULT_MAX_FPS = 30 DEFAULT_MAX_HEIGHT = 1080 DEFAULT_MAX_WIDTH = 1920 -DEFAULT_PORT = 51827 -DEFAULT_CONFIG_FLOW_PORT = 51828 +DEFAULT_PORT = 21063 +DEFAULT_CONFIG_FLOW_PORT = 21064 DEFAULT_SAFE_MODE = False DEFAULT_VIDEO_CODEC = VIDEO_CODEC_LIBX264 DEFAULT_VIDEO_MAP = "0:v:0" From c8c394ef916373da73088cd7f2d345112f1293c7 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sun, 7 Mar 2021 11:36:28 +0100 Subject: [PATCH 1081/1818] Increase ESPHome log level on first connection failure (#47547) --- homeassistant/components/esphome/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index b9720dcdf804ba..dee0813007cec2 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -264,8 +264,9 @@ async def try_connect(tries: int = 0, is_disconnect: bool = True) -> None: try: await cli.connect(on_stop=try_connect, login=True) except APIConnectionError as error: - logger = _LOGGER.info if tries == 0 else _LOGGER.debug - logger( + level = logging.WARNING if tries == 0 else logging.DEBUG + _LOGGER.log( + level, "Can't connect to ESPHome API for %s (%s): %s", entry.unique_id, host, From 48f1a55a28e53014a1d54a241fcc951730a8fc10 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 7 Mar 2021 13:20:04 +0100 Subject: [PATCH 1082/1818] Improve common structure in UniFi device tracker tests (#47526) --- tests/components/unifi/test_device_tracker.py | 1032 +++++++++-------- 1 file changed, 575 insertions(+), 457 deletions(-) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 51dbd735e105e4..33dda33be2463b 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -1,5 +1,5 @@ """The tests for the UniFi device tracker platform.""" -from copy import copy + from datetime import timedelta from unittest.mock import patch @@ -22,7 +22,7 @@ CONF_TRACK_WIRED_CLIENTS, DOMAIN as UNIFI_DOMAIN, ) -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE from homeassistant.helpers import entity_registry import homeassistant.util.dt as dt_util @@ -30,126 +30,8 @@ from tests.common import async_fire_time_changed -CLIENT_1 = { - "ap_mac": "00:00:00:00:02:01", - "essid": "ssid", - "hostname": "client_1", - "ip": "10.0.0.1", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", -} -CLIENT_2 = { - "hostname": "client_2", - "ip": "10.0.0.2", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:02", - "name": "Wired Client", -} -CLIENT_3 = { - "essid": "ssid2", - "hostname": "client_3", - "ip": "10.0.0.3", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:03", -} -CLIENT_4 = { - "essid": "ssid", - "hostname": "client_4", - "ip": "10.0.0.4", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:04", -} -CLIENT_5 = { - "essid": "ssid", - "hostname": "client_5", - "ip": "10.0.0.5", - "is_wired": True, - "last_seen": None, - "mac": "00:00:00:00:00:05", -} - -DEVICE_1 = { - "board_rev": 3, - "device_id": "mock-id", - "has_fan": True, - "fan_level": 0, - "ip": "10.0.1.1", - "last_seen": 1562600145, - "mac": "00:00:00:00:01:01", - "model": "US16P150", - "name": "device_1", - "next_interval": 20, - "overheating": True, - "state": 1, - "type": "usw", - "upgradable": True, - "version": "4.0.42.10433", -} -DEVICE_2 = { - "board_rev": 3, - "device_id": "mock-id", - "has_fan": True, - "ip": "10.0.1.2", - "mac": "00:00:00:00:01:02", - "model": "US16P150", - "name": "device_2", - "next_interval": 20, - "state": 0, - "type": "usw", - "version": "4.0.42.10433", -} - -EVENT_CLIENT_1_WIRELESS_CONNECTED = { - "user": CLIENT_1["mac"], - "ssid": CLIENT_1["essid"], - "ap": CLIENT_1["ap_mac"], - "radio": "na", - "channel": "44", - "hostname": CLIENT_1["hostname"], - "key": "EVT_WU_Connected", - "subsystem": "wlan", - "site_id": "name", - "time": 1587753456179, - "datetime": "2020-04-24T18:37:36Z", - "msg": f'User{[CLIENT_1["mac"]]} has connected to AP[{CLIENT_1["ap_mac"]}] with SSID "{CLIENT_1["essid"]}" on "channel 44(na)"', - "_id": "5ea331fa30c49e00f90ddc1a", -} - -EVENT_CLIENT_1_WIRELESS_DISCONNECTED = { - "user": CLIENT_1["mac"], - "ssid": CLIENT_1["essid"], - "hostname": CLIENT_1["hostname"], - "ap": CLIENT_1["ap_mac"], - "duration": 467, - "bytes": 459039, - "key": "EVT_WU_Disconnected", - "subsystem": "wlan", - "site_id": "name", - "time": 1587752927000, - "datetime": "2020-04-24T18:28:47Z", - "msg": f'User{[CLIENT_1["mac"]]} disconnected from "{CLIENT_1["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{CLIENT_1["ap_mac"]}])', - "_id": "5ea32ff730c49e00f90dca1a", -} - -EVENT_DEVICE_2_UPGRADED = { - "_id": "5eae7fe02ab79c00f9d38960", - "datetime": "2020-05-09T20:06:37Z", - "key": "EVT_SW_Upgraded", - "msg": f'Switch[{DEVICE_2["mac"]}] was upgraded from "{DEVICE_2["version"]}" to "4.3.13.11253"', - "subsystem": "lan", - "sw": DEVICE_2["mac"], - "sw_name": DEVICE_2["name"], - "time": 1589054797635, - "version_from": {DEVICE_2["version"]}, - "version_to": "4.3.13.11253", -} - - -async def test_no_clients(hass, aioclient_mock): + +async def test_no_entities(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" await setup_unifi_integration(hass, aioclient_mock) @@ -157,196 +39,297 @@ async def test_no_clients(hass, aioclient_mock): async def test_tracked_wireless_clients(hass, aioclient_mock, mock_unifi_websocket): - """Test the update_items function with some clients.""" + """Verify tracking of wireless clients.""" + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[CLIENT_1] + hass, aioclient_mock, clients_response=[client] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "not_home" + 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 - client_1_copy = copy(CLIENT_1) + mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["ip"] == "10.0.0.1" - assert client_1.attributes["mac"] == "00:00:00:00:00:01" - assert client_1.attributes["hostname"] == "client_1" - assert client_1.attributes["host_name"] == "client_1" + client_state = hass.states.get("device_tracker.client") + assert client_state.state == "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" # State change signalling works with events + # Disconnected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "hostname": client["hostname"], + "ap": client["ap_mac"], + "duration": 467, + "bytes": 459039, + "key": "EVT_WU_Disconnected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587752927000, + "datetime": "2020-04-24T18:28:47Z", + "msg": f'User{[client["mac"]]} disconnected from "{client["essid"]}" (7m 47s connected, 448.28K bytes, last AP[{client["ap_mac"]}])', + "_id": "5ea32ff730c49e00f90dca1a", + } mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_DISCONNECTED], + "data": [event], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME + + # Change time to mark client as away new_time = dt_util.utcnow() + controller.option_detection_time with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + + # Connected event + + event = { + "user": client["mac"], + "ssid": client["essid"], + "ap": client["ap_mac"], + "radio": "na", + "channel": "44", + "hostname": client["hostname"], + "key": "EVT_WU_Connected", + "subsystem": "wlan", + "site_id": "name", + "time": 1587753456179, + "datetime": "2020-04-24T18:37:36Z", + "msg": f'User{[client["mac"]]} has connected to AP[{client["ap_mac"]}] with SSID "{client["essid"]}" on "channel 44(na)"', + "_id": "5ea331fa30c49e00f90ddc1a", + } mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_CLIENT_1_WIRELESS_CONNECTED], + "data": [event], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME async def test_tracked_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some clients.""" - client_4_copy = copy(CLIENT_4) - client_4_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client_1 = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client_1", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "ip": "10.0.0.2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Client 2", + } + client_3 = { + "essid": "ssid2", + "hostname": "client_3", + "ip": "10.0.0.3", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:03", + } + client_4 = { + "essid": "ssid", + "hostname": "client_4", + "ip": "10.0.0.4", + "is_wired": True, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:04", + } + client_5 = { + "essid": "ssid", + "hostname": "client_5", + "ip": "10.0.0.5", + "is_wired": True, + "last_seen": None, + "mac": "00:00:00:00:00:05", + } await setup_unifi_integration( hass, aioclient_mock, options={CONF_SSID_FILTER: ["ssid"]}, - clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], - known_wireless_clients=(CLIENT_4["mac"],), + clients_response=[client_1, client_2, client_3, client_4, client_5], + known_wireless_clients=(client_4["mac"],), ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "not_home" - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - assert client_2.state == "not_home" + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 4 + assert hass.states.get("device_tracker.client_1").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.client_2").state == STATE_NOT_HOME # Client on SSID not in SSID filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_3") # Wireless client with wired bug, if bug active on restart mark device away - client_4 = hass.states.get("device_tracker.client_4") - assert client_4 is not None - assert client_4.state == "not_home" + assert hass.states.get("device_tracker.client_4").state == STATE_NOT_HOME # A client that has never been seen should be marked away. - client_5 = hass.states.get("device_tracker.client_5") - assert client_5 is not None - assert client_5.state == "not_home" + assert hass.states.get("device_tracker.client_5").state == STATE_NOT_HOME # State change signalling works - client_1_copy = copy(CLIENT_1) + mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client_1], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client_1").state == STATE_HOME async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): """Test the update_items function with some devices.""" + device_1 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device 1", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + device_2 = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "ip": "10.0.1.2", + "mac": "00:00:00:00:01:02", + "model": "US16P150", + "name": "Device 2", + "next_interval": 20, + "state": 0, + "type": "usw", + "version": "4.0.42.10433", + } await setup_unifi_integration( hass, aioclient_mock, - devices_response=[DEVICE_1, DEVICE_2], + devices_response=[device_1, device_2], ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 - assert device_1.state == "home" - - device_2 = hass.states.get("device_tracker.device_2") - assert device_2 - assert device_2.state == "not_home" + assert hass.states.get("device_tracker.device_1").state == STATE_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_NOT_HOME # State change signalling work - device_1_copy = copy(DEVICE_1) - device_1_copy["next_interval"] = 20 + + device_1["next_interval"] = 20 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_1_copy], + "data": [device_1], } ) - device_2_copy = copy(DEVICE_2) - device_2_copy["next_interval"] = 50 + device_2["next_interval"] = 50 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_2_copy], + "data": [device_2], } ) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" - device_2 = hass.states.get("device_tracker.device_2") - assert device_2.state == "home" + assert hass.states.get("device_tracker.device_1").state == STATE_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_HOME + + # Change of time can mark device not_home outside of expected reporting interval new_time = dt_util.utcnow() + timedelta(seconds=90) with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "not_home" - device_2 = hass.states.get("device_tracker.device_2") - assert device_2.state == "home" + assert hass.states.get("device_tracker.device_1").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device_2").state == STATE_HOME # Disabled device is unavailable - device_1_copy = copy(DEVICE_1) - device_1_copy["disabled"] = True + + device_1["disabled"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_1_copy], + "data": [device_1], } ) await hass.async_block_till_done() - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device_1").state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device_2").state == STATE_HOME # Update device registry when device is upgraded - device_2_copy = copy(DEVICE_2) - device_2_copy["version"] = EVENT_DEVICE_2_UPGRADED["version_to"] + + event = { + "_id": "5eae7fe02ab79c00f9d38960", + "datetime": "2020-05-09T20:06:37Z", + "key": "EVT_SW_Upgraded", + "msg": f'Switch[{device_2["mac"]}] was upgraded from "{device_2["version"]}" to "4.3.13.11253"', + "subsystem": "lan", + "sw": device_2["mac"], + "sw_name": device_2["name"], + "time": 1589054797635, + "version_from": {device_2["version"]}, + "version_to": "4.3.13.11253", + } + + device_2["version"] = event["version_to"] mock_unifi_websocket( data={ "meta": {"message": MESSAGE_DEVICE}, - "data": [device_2_copy], + "data": [device_2], } ) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_EVENT}, - "data": [EVENT_DEVICE_2_UPGRADED], + "data": [event], } ) await hass.async_block_till_done() @@ -356,97 +339,145 @@ async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): entry = entity_registry.async_get("device_tracker.device_2") device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(entry.device_id) - assert device.sw_version == EVENT_DEVICE_2_UPGRADED["version_to"] + assert device.sw_version == event["version_to"] async def test_remove_clients(hass, aioclient_mock, mock_unifi_websocket): """Test the remove_items function with some clients.""" + client_1 = { + "essid": "ssid", + "hostname": "client_1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + client_2 = { + "hostname": "client_2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } await setup_unifi_integration( - hass, aioclient_mock, clients_response=[CLIENT_1, CLIENT_2] + hass, aioclient_mock, clients_response=[client_1, client_2] ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client_1") + assert hass.states.get("device_tracker.client_2") - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - wired_client = hass.states.get("device_tracker.wired_client") - assert wired_client is not None + # Remove client mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENT_1], + "data": [client_1], } ) await hass.async_block_till_done() await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - wired_client = hass.states.get("device_tracker.wired_client") - assert wired_client is not None + assert not hass.states.get("device_tracker.client_1") + assert hass.states.get("device_tracker.client_2") async def test_controller_state_change(hass, aioclient_mock, mock_unifi_websocket): """Verify entities state reflect on controller becoming unavailable.""" + client = { + "essid": "ssid", + "hostname": "client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device").state == STATE_HOME # Controller unavailable mock_unifi_websocket(state=STATE_DISCONNECTED) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == STATE_UNAVAILABLE - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.client").state == STATE_UNAVAILABLE + assert hass.states.get("device_tracker.device").state == STATE_UNAVAILABLE # Controller available mock_unifi_websocket(state=STATE_RUNNING) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME + assert hass.states.get("device_tracker.device").state == STATE_HOME async def test_option_track_clients(hass, aioclient_mock): """Test the tracking of clients can be turned off.""" + wireless_client = { + "essid": "ssid", + "hostname": "wireless_client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -454,14 +485,9 @@ async def test_option_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert not hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -469,34 +495,55 @@ async def test_option_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_option_track_wired_clients(hass, aioclient_mock): """Test the tracking of wired clients can be turned off.""" + wireless_client = { + "essid": "ssid", + "hostname": "wireless_client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -504,14 +551,9 @@ async def test_option_track_wired_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -519,34 +561,44 @@ async def test_option_track_wired_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_option_track_devices(hass, aioclient_mock): """Test the tracking of devices can be turned off.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - clients_response=[CLIENT_1, CLIENT_2], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -554,14 +606,8 @@ async def test_option_track_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is None + assert hass.states.get("device_tracker.client") + assert not hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -569,36 +615,40 @@ async def test_option_track_devices(hass, aioclient_mock): ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): """Test the SSID filter works. - Client 1 will travel from a supported SSID to an unsupported ssid. - Client 3 will be removed on change of options since it is in an unsupported SSID. + Client will travel from a supported SSID to an unsupported ssid. + Client on SSID2 will be removed on change of options. """ - client_1_copy = copy(CLIENT_1) - client_1_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "essid": "ssid", + "hostname": "client", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } + client_on_ssid2 = { + "essid": "ssid2", + "hostname": "client_on_ssid2", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[client_1_copy, CLIENT_3] + hass, aioclient_mock, clients_response=[client, client_on_ssid2] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - client_3 = hass.states.get("device_tracker.client_3") - assert client_3 + assert hass.states.get("device_tracker.client").state == STATE_HOME + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME # Setting SSID filter will remove clients outside of filter hass.config_entries.async_update_entry( @@ -608,40 +658,34 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): await hass.async_block_till_done() # Not affected by SSID filter - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME # Removed due to SSID filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_on_ssid2") # Roams to SSID outside of filter - client_1_copy = copy(CLIENT_1) - client_1_copy["essid"] = "other_ssid" + client["essid"] = "other_ssid" mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) # Data update while SSID filter is in effect shouldn't create the client - client_3_copy = copy(CLIENT_3) - client_3_copy["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client_on_ssid2["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() # SSID filter marks client as away - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME # SSID still outside of filter - client_3 = hass.states.get("device_tracker.client_3") - assert not client_3 + assert not hass.states.get("device_tracker.client_on_ssid2") # Remove SSID filter hass.config_entries.async_update_entry( @@ -653,47 +697,45 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_copy], + "data": [client], } ) mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" + assert hass.states.get("device_tracker.client").state == STATE_HOME + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_HOME - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "home" + # Time pass to mark client as away new_time = dt_util.utcnow() + controller.option_detection_time with patch("homeassistant.util.dt.utcnow", return_value=new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" + assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() + # Client won't go away until after next update - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "home" + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_HOME # Trigger update to get client marked as away mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_3_copy], + "data": [client_on_ssid2], } ) await hass.async_block_till_done() @@ -705,8 +747,7 @@ async def test_option_ssid_filter(hass, aioclient_mock, mock_unifi_websocket): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() - client_3 = hass.states.get("device_tracker.client_3") - assert client_3.state == "not_home" + assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME async def test_wireless_client_go_wired_issue( @@ -716,35 +757,41 @@ async def test_wireless_client_go_wired_issue( UniFi has a known issue that when a wireless device goes away it sometimes gets marked as wired. """ - client_1_client = copy(CLIENT_1) - client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( - hass, aioclient_mock, clients_response=[client_1_client] + hass, aioclient_mock, clients_response=[client] ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Trigger wired bug - client_1_client["is_wired"] = True + client["is_wired"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Wired bug fix keeps client marked as wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Pass time new_time = dt_util.utcnow() + controller.option_detection_time @@ -753,74 +800,80 @@ async def test_wireless_client_go_wired_issue( await hass.async_block_till_done() # Marked as home according to the timer - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is False # Try to mark client as connected mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Make sure it don't go online again until wired bug disappears - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is False # Make client wireless - client_1_client["is_wired"] = False + client["is_wired"] = False mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Client is no longer affected by wired bug and can be marked online - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocket): """Test option to ignore wired bug.""" - client_1_client = copy(CLIENT_1) - client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) + client = { + "ap_mac": "00:00:00:00:02:01", + "essid": "ssid", + "hostname": "client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": dt_util.as_timestamp(dt_util.utcnow()), + "mac": "00:00:00:00:00:01", + } config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_IGNORE_WIRED_BUG: True}, - clients_response=[client_1_client], + clients_response=[client], ) controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False # Trigger wired bug - client_1_client["is_wired"] = True + client["is_wired"] = True mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Wired bug in effect - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is True # pass time new_time = dt_util.utcnow() + controller.option_detection_time @@ -829,42 +882,61 @@ async def test_option_ignore_wired_bug(hass, aioclient_mock, mock_unifi_websocke await hass.async_block_till_done() # Timer marks client as away - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "not_home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_NOT_HOME + assert client_state.attributes["is_wired"] is True # Mark client as connected again mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Ignoring wired bug allows client to go home again even while affected - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is True + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is True # Make client wireless - client_1_client["is_wired"] = False + client["is_wired"] = False mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": [client_1_client], + "data": [client], } ) await hass.async_block_till_done() # Client is wireless and still connected - client_1 = hass.states.get("device_tracker.client_1") - assert client_1.state == "home" - assert client_1.attributes["is_wired"] is False + client_state = hass.states.get("device_tracker.client") + assert client_state.state == STATE_HOME + assert client_state.attributes["is_wired"] is False async def test_restoring_client(hass, aioclient_mock): - """Test the update_items function with some clients.""" + """Verify clients are restored from clients_all if they ever was registered to entity registry.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + restored = { + "hostname": "restored", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } + not_restored = { + "hostname": "not_restored", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:03", + } + config_entry = config_entries.ConfigEntry( version=1, domain=UNIFI_DOMAIN, @@ -881,15 +953,8 @@ async def test_restoring_client(hass, aioclient_mock): registry.async_get_or_create( TRACKER_DOMAIN, UNIFI_DOMAIN, - f'{CLIENT_1["mac"]}-site_id', - suggested_object_id=CLIENT_1["hostname"], - config_entry=config_entry, - ) - registry.async_get_or_create( - TRACKER_DOMAIN, - UNIFI_DOMAIN, - f'{CLIENT_2["mac"]}-site_id', - suggested_object_id=CLIENT_2["hostname"], + f'{restored["mac"]}-site_id', + suggested_object_id=restored["hostname"], config_entry=config_entry, ) @@ -897,31 +962,63 @@ async def test_restoring_client(hass, aioclient_mock): hass, aioclient_mock, options={CONF_BLOCK_CLIENT: True}, - clients_response=[CLIENT_2], - clients_all_response=[CLIENT_1], + clients_response=[client], + clients_all_response=[restored, not_restored], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - device_1 = hass.states.get("device_tracker.client_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.restored") + assert not hass.states.get("device_tracker.not_restored") async def test_dont_track_clients(hass, aioclient_mock): """Test don't track clients config works.""" + wireless_client = { + "essid": "ssid", + "hostname": "Wireless client", + "ip": "10.0.0.1", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "hostname": "Wired client", + "ip": "10.0.0.2", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_CLIENTS: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[wireless_client, wired_client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 + assert not hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -929,31 +1026,49 @@ async def test_dont_track_clients(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") + assert hass.states.get("device_tracker.device") async def test_dont_track_devices(hass, aioclient_mock): """Test don't track devices config works.""" + client = { + "hostname": "client", + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + device = { + "board_rev": 3, + "device_id": "mock-id", + "has_fan": True, + "fan_level": 0, + "ip": "10.0.1.1", + "last_seen": 1562600145, + "mac": "00:00:00:00:01:01", + "model": "US16P150", + "name": "Device", + "next_interval": 20, + "overheating": True, + "state": 1, + "type": "usw", + "upgradable": True, + "version": "4.0.42.10433", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_DEVICES: False}, - clients_response=[CLIENT_1], - devices_response=[DEVICE_1], + clients_response=[client], + devices_response=[device], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 + assert hass.states.get("device_tracker.client") + assert not hass.states.get("device_tracker.device") hass.config_entries.async_update_entry( config_entry, @@ -962,29 +1077,36 @@ async def test_dont_track_devices(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - device_1 = hass.states.get("device_tracker.device_1") - assert device_1 is not None + assert hass.states.get("device_tracker.client") + assert hass.states.get("device_tracker.device") async def test_dont_track_wired_clients(hass, aioclient_mock): """Test don't track wired clients config works.""" + wireless_client = { + "essid": "ssid", + "hostname": "Wireless Client", + "is_wired": False, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:01", + } + wired_client = { + "is_wired": True, + "last_seen": 1562600145, + "mac": "00:00:00:00:00:02", + "name": "Wired Client", + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, options={CONF_TRACK_WIRED_CLIENTS: False}, - clients_response=[CLIENT_1, CLIENT_2], + clients_response=[wireless_client, wired_client], ) - assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is None + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 + assert hass.states.get("device_tracker.wireless_client") + assert not hass.states.get("device_tracker.wired_client") hass.config_entries.async_update_entry( config_entry, @@ -993,9 +1115,5 @@ async def test_dont_track_wired_clients(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - - client_1 = hass.states.get("device_tracker.client_1") - assert client_1 is not None - - client_2 = hass.states.get("device_tracker.wired_client") - assert client_2 is not None + assert hass.states.get("device_tracker.wireless_client") + assert hass.states.get("device_tracker.wired_client") From 4018d0a1523e547117cfb1f5c17a57bd5a50d722 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Sun, 7 Mar 2021 13:53:48 +0100 Subject: [PATCH 1083/1818] Correctly close lacrosse on homeassistant stop (#47555) Since lacrosse.close() takes no arguments, but was directly added as a listener to EVENT_HOMEASSISTANT_STOP, the following occured on shutdown: Traceback (most recent call last): File "/usr/lib/python/concurrent/futures/thread.py", line 57, in run result = self.fn(*self.args, **self.kwargs) TypeError: close() takes 1 positional argument but 2 were given --- homeassistant/components/lacrosse/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index 2c7f5d294a989f..f65c792ddb0e95 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.warning("Unable to open serial port: %s", exc) return False - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lacrosse.close) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: lacrosse.close()) if CONF_JEELINK_LED in config: lacrosse.led_mode_state(config.get(CONF_JEELINK_LED)) From 07fd1b3b4375227a019c1048d6b62de6560b3bc8 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Mar 2021 08:14:15 -0500 Subject: [PATCH 1084/1818] Clean up Lutron Caseta (#47534) --- homeassistant/components/lutron_caseta/__init__.py | 3 --- homeassistant/components/lutron_caseta/binary_sensor.py | 1 - homeassistant/components/lutron_caseta/config_flow.py | 2 -- homeassistant/components/lutron_caseta/const.py | 1 - homeassistant/components/lutron_caseta/cover.py | 1 - homeassistant/components/lutron_caseta/fan.py | 1 - homeassistant/components/lutron_caseta/light.py | 1 - homeassistant/components/lutron_caseta/scene.py | 1 - homeassistant/components/lutron_caseta/switch.py | 1 - 9 files changed, 12 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index f93833acfc5a88..07851db1e2371d 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -67,7 +67,6 @@ async def async_setup(hass, base_config): """Set up the Lutron component.""" - hass.data.setdefault(DOMAIN, {}) if DOMAIN in base_config: @@ -92,7 +91,6 @@ async def async_setup(hass, base_config): async def async_setup_entry(hass, config_entry): """Set up a bridge from a config entry.""" - host = config_entry.data[CONF_HOST] keyfile = hass.config.path(config_entry.data[CONF_KEYFILE]) certfile = hass.config.path(config_entry.data[CONF_CERTFILE]) @@ -280,7 +278,6 @@ def _async_lip_event(lip_message): async def async_unload_entry(hass, config_entry): """Unload the bridge bridge from a config entry.""" - data = hass.data[DOMAIN][config_entry.entry_id] data[BRIDGE_LEAP].close() if data[BRIDGE_LIP]: diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index 97053eba08c98a..b58afd22a90f76 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -16,7 +16,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds occupancy groups from the Caseta bridge associated with the config_entry as binary_sensor entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 6cd30a78f0c865..98b30b886f3c94 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -171,7 +171,6 @@ async def async_step_import(self, import_info): This flow is triggered by `async_setup`. """ - host = import_info[CONF_HOST] # Store the imported config for other steps in this flow to access. self.data[CONF_HOST] = host @@ -213,7 +212,6 @@ async def async_step_import_failed(self, user_input=None): async def async_validate_connectable_bridge_config(self): """Check if we can connect to the bridge with the current config.""" - bridge = None try: diff --git a/homeassistant/components/lutron_caseta/const.py b/homeassistant/components/lutron_caseta/const.py index fcc647f00bac5f..40a5d2b01fd73c 100644 --- a/homeassistant/components/lutron_caseta/const.py +++ b/homeassistant/components/lutron_caseta/const.py @@ -31,7 +31,6 @@ ACTION_PRESS = "press" ACTION_RELEASE = "release" -CONF_TYPE = "type" CONF_SUBTYPE = "subtype" BRIDGE_TIMEOUT = 35 diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index b3924ba31c8839..31f7e9b55bd1f9 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -24,7 +24,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds shades from the Caseta bridge associated with the config_entry as cover entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index 55799315ba02ae..de2b2a2ae8ca01 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -25,7 +25,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds fan controllers from the Caseta bridge associated with the config_entry as fan entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index ec20011808281c..016dd925b23720 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -33,7 +33,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds dimmers from the Caseta bridge associated with the config_entry as light entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index d70048db8cda11..43e0429d151ac7 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -12,7 +12,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds scenes from the Caseta bridge associated with the config_entry as scene entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index 1e5b4ab6fe5bc8..c6aea447055d5d 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -15,7 +15,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Adds switches from the Caseta bridge associated with the config_entry as switch entities. """ - entities = [] data = hass.data[CASETA_DOMAIN][config_entry.entry_id] bridge = data[BRIDGE_LEAP] From 13d4d7039eb114bafb06f5ccbbec662c62641847 Mon Sep 17 00:00:00 2001 From: tkdrob Date: Sun, 7 Mar 2021 08:15:43 -0500 Subject: [PATCH 1085/1818] Clean up kmtronic (#47537) --- homeassistant/components/kmtronic/__init__.py | 15 +++------------ homeassistant/components/kmtronic/config_flow.py | 7 +++---- homeassistant/components/kmtronic/const.py | 5 ----- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index 1f012985447181..0ac4ea8cb59668 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -10,20 +10,12 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import ( - CONF_HOSTNAME, - CONF_PASSWORD, - CONF_USERNAME, - DATA_COORDINATOR, - DATA_HOST, - DATA_HUB, - DOMAIN, - MANUFACTURER, -) +from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN, MANUFACTURER CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -41,11 +33,10 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up kmtronic from a config entry.""" - session = aiohttp_client.async_get_clientsession(hass) auth = Auth( session, - f"http://{entry.data[CONF_HOSTNAME]}", + f"http://{entry.data[CONF_HOST]}", entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], ) diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py index 376bb64c34c72e..841c541dd4ebcd 100644 --- a/homeassistant/components/kmtronic/config_flow.py +++ b/homeassistant/components/kmtronic/config_flow.py @@ -7,23 +7,22 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import aiohttp_client -from .const import CONF_HOSTNAME, CONF_PASSWORD, CONF_USERNAME from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({CONF_HOSTNAME: str, CONF_USERNAME: str, CONF_PASSWORD: str}) +DATA_SCHEMA = vol.Schema({CONF_HOST: str, CONF_USERNAME: str, CONF_PASSWORD: str}) async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect.""" - session = aiohttp_client.async_get_clientsession(hass) auth = Auth( session, - f"http://{data[CONF_HOSTNAME]}", + f"http://{data[CONF_HOST]}", data[CONF_USERNAME], data[CONF_PASSWORD], ) diff --git a/homeassistant/components/kmtronic/const.py b/homeassistant/components/kmtronic/const.py index 58553217799bb1..8ca37a0b797ae7 100644 --- a/homeassistant/components/kmtronic/const.py +++ b/homeassistant/components/kmtronic/const.py @@ -2,10 +2,6 @@ DOMAIN = "kmtronic" -CONF_HOSTNAME = "host" -CONF_USERNAME = "username" -CONF_PASSWORD = "password" - DATA_HUB = "hub" DATA_HOST = "host" DATA_COORDINATOR = "coordinator" @@ -13,4 +9,3 @@ MANUFACTURER = "KMtronic" ATTR_MANUFACTURER = "manufacturer" ATTR_IDENTIFIERS = "identifiers" -ATTR_NAME = "name" From d85d1a65a7a00670ce18fdff7483930e440334cf Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 7 Mar 2021 14:20:21 +0100 Subject: [PATCH 1086/1818] Fix mysensors unload clean up (#47541) --- .../components/mysensors/__init__.py | 21 +++--------------- homeassistant/components/mysensors/gateway.py | 18 +++++++++++---- homeassistant/components/mysensors/helpers.py | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index bcab1ed86b41ad..7dd118099f764b 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -38,11 +38,11 @@ MYSENSORS_ON_UNLOAD, PLATFORMS_WITH_ENTRY_SUPPORT, DevId, - GatewayId, SensorType, ) from .device import MySensorsDevice, MySensorsEntity, get_mysensors_devices from .gateway import finish_setup, get_mysensors_gateway, gw_stop, setup_gateway +from .helpers import on_unload _LOGGER = logging.getLogger(__name__) @@ -253,29 +253,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo for fnct in hass.data[DOMAIN][key]: fnct() + hass.data[DOMAIN].pop(key) + del hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] await gw_stop(hass, entry, gateway) return True -async def on_unload( - hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable -) -> None: - """Register a callback to be called when entry is unloaded. - - This function is used by platforms to cleanup after themselves - """ - if isinstance(entry, GatewayId): - uniqueid = entry - else: - uniqueid = entry.entry_id - key = MYSENSORS_ON_UNLOAD.format(uniqueid) - if key not in hass.data[DOMAIN]: - hass.data[DOMAIN][key] = [] - hass.data[DOMAIN][key].append(fnct) - - @callback def setup_mysensors_platform( hass: HomeAssistant, diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index b6797cafb37b8c..a7f3a053d3fc60 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -31,7 +31,12 @@ GatewayId, ) from .handler import HANDLERS -from .helpers import discover_mysensors_platform, validate_child, validate_node +from .helpers import ( + discover_mysensors_platform, + on_unload, + validate_child, + validate_node, +) _LOGGER = logging.getLogger(__name__) @@ -260,8 +265,8 @@ async def _discover_persistent_devices( async def gw_stop(hass, entry: ConfigEntry, gateway: BaseAsyncGateway): """Stop the gateway.""" - connect_task = hass.data[DOMAIN].get( - MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) + connect_task = hass.data[DOMAIN].pop( + MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id), None ) if connect_task is not None and not connect_task.done(): connect_task.cancel() @@ -288,7 +293,12 @@ def gateway_connected(_: BaseAsyncGateway): async def stop_this_gw(_: Event): await gw_stop(hass, entry, gateway) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw) + await on_unload( + hass, + entry.entry_id, + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw), + ) + if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 4452dd0575b3f3..0b8dc361158fd4 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -2,16 +2,18 @@ from collections import defaultdict from enum import IntEnum import logging -from typing import DefaultDict, Dict, List, Optional, Set +from typing import Callable, DefaultDict, Dict, List, Optional, Set, Union from mysensors import BaseAsyncGateway, Message from mysensors.sensor import ChildSensor import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.decorator import Registry from .const import ( @@ -20,6 +22,7 @@ DOMAIN, FLAT_PLATFORM_TYPES, MYSENSORS_DISCOVERY, + MYSENSORS_ON_UNLOAD, TYPE_TO_PLATFORMS, DevId, GatewayId, @@ -31,6 +34,23 @@ SCHEMAS = Registry() +async def on_unload( + hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable +) -> None: + """Register a callback to be called when entry is unloaded. + + This function is used by platforms to cleanup after themselves. + """ + if isinstance(entry, GatewayId): + uniqueid = entry + else: + uniqueid = entry.entry_id + key = MYSENSORS_ON_UNLOAD.format(uniqueid) + if key not in hass.data[DOMAIN]: + hass.data[DOMAIN][key] = [] + hass.data[DOMAIN][key].append(fnct) + + @callback def discover_mysensors_platform( hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] From d3bd2378ba07c5df89052d5e7307fb775e63edcb Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 7 Mar 2021 15:07:02 +0000 Subject: [PATCH 1087/1818] Correct weather entities forecast time (#47565) --- homeassistant/components/aemet/weather_update_coordinator.py | 4 ++-- .../components/openweathermap/weather_update_coordinator.py | 4 +++- tests/components/aemet/test_sensor.py | 4 +++- tests/components/aemet/test_weather.py | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 6a06b1dd39147a..619429c9a5b275 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -393,7 +393,7 @@ def _convert_forecast_day(self, date, day): ), ATTR_FORECAST_TEMP: self._get_temperature_day(day), ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day), - ATTR_FORECAST_TIME: dt_util.as_utc(date), + ATTR_FORECAST_TIME: dt_util.as_utc(date).isoformat(), ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day), ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day), } @@ -412,7 +412,7 @@ def _convert_forecast_hour(self, date, day, hour): day, hour ), ATTR_FORECAST_TEMP: self._get_temperature(day, hour), - ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt), + ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(), ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour), ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour), } diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 201029c39795f9..b4d9b7a9c80034 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -140,7 +140,9 @@ def _get_forecast_from_weather_response(self, weather_response): def _convert_forecast(self, entry): forecast = { - ATTR_FORECAST_TIME: dt.utc_from_timestamp(entry.reference_time("unix")), + ATTR_FORECAST_TIME: dt.utc_from_timestamp( + entry.reference_time("unix") + ).isoformat(), ATTR_FORECAST_PRECIPITATION: self._calc_precipitation( entry.rain, entry.snow ), diff --git a/tests/components/aemet/test_sensor.py b/tests/components/aemet/test_sensor.py index 05f2d8d0b500d0..b265b9967097e6 100644 --- a/tests/components/aemet/test_sensor.py +++ b/tests/components/aemet/test_sensor.py @@ -37,7 +37,9 @@ async def test_aemet_forecast_create_sensors(hass): assert state.state == "-4" state = hass.states.get("sensor.aemet_daily_forecast_time") - assert state.state == "2021-01-10 00:00:00+00:00" + assert ( + state.state == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() + ) state = hass.states.get("sensor.aemet_daily_forecast_wind_bearing") assert state.state == "45.0" diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index eef6107d54322c..43acf4c1c8731c 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -51,8 +51,9 @@ async def test_aemet_weather(hass): assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 30 assert forecast.get(ATTR_FORECAST_TEMP) == 4 assert forecast.get(ATTR_FORECAST_TEMP_LOW) == -4 - assert forecast.get(ATTR_FORECAST_TIME) == dt_util.parse_datetime( - "2021-01-10 00:00:00+00:00" + assert ( + forecast.get(ATTR_FORECAST_TIME) + == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() ) assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0 assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20 From 7050c71524fa0891b4feace0b363f8e9b30c1e6c Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Sun, 7 Mar 2021 09:46:14 -0800 Subject: [PATCH 1088/1818] Round miles in myChevy sensors (#46879) --- homeassistant/components/mychevy/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index 413118451856df..df8b136741a970 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -160,6 +160,8 @@ def async_update_callback(self): """Update state.""" if self._car is not None: self._state = getattr(self._car, self._attr, None) + if self._unit_of_measurement == "miles": + self._state = round(self._state) for attr in self._extra_attrs: self._state_attributes[attr] = getattr(self._car, attr) self.async_write_ha_state() From a066f8482866d69a38f69dd99cc20682565d9f4b Mon Sep 17 00:00:00 2001 From: Alex <7845120+newAM@users.noreply.github.com> Date: Sun, 7 Mar 2021 09:49:13 -0800 Subject: [PATCH 1089/1818] Remove @newAM from hdmi_cec codeowners (#47542) --- CODEOWNERS | 1 - homeassistant/components/hdmi_cec/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 0734006558872d..3f0b3d89e05bc2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -186,7 +186,6 @@ homeassistant/components/guardian/* @bachya homeassistant/components/habitica/* @ASMfreaK @leikoilja homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey homeassistant/components/hassio/* @home-assistant/supervisor -homeassistant/components/hdmi_cec/* @newAM homeassistant/components/heatmiser/* @andylockran homeassistant/components/heos/* @andrewsayre homeassistant/components/here_travel_time/* @eifinger diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index 4c307582281157..90ed7cc33592e1 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -3,5 +3,5 @@ "name": "HDMI-CEC", "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", "requirements": ["pyCEC==0.4.14"], - "codeowners": ["@newAM"] + "codeowners": [] } From 683425876f6fa8ca509cd2ebc8090284027a53ad Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Mar 2021 06:45:15 +0100 Subject: [PATCH 1090/1818] Update frontend to 20210302.6 (#47592) --- 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 8093c65d91a9c2..694be0382f7801 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==20210302.5" + "home-assistant-frontend==20210302.6" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0586b956f39548..7642e14bda8c44 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index f27e3835467e24..285f8ed513376d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d06ac8bfd5bfd6..7804e3c3926f66 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 1d387e12ccd83a681ab29209d329ae437acd67ea Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 8 Mar 2021 03:08:17 -0500 Subject: [PATCH 1091/1818] Add fallback zwave_js entity name using node ID (#47582) * add fallback zwave_js entity name using node ID * add new fixture and test for name that was failing --- homeassistant/components/zwave_js/entity.py | 6 +- tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_init.py | 6 + .../zwave_js/null_name_check_state.json | 414 ++++++++++++++++++ 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/zwave_js/null_name_check_state.json diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index c061abc4d0ddfe..7620323d9405b3 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -105,7 +105,11 @@ def generate_name( """Generate entity name.""" if additional_info is None: additional_info = [] - name: str = self.info.node.name or self.info.node.device_config.description + name: str = ( + self.info.node.name + or self.info.node.device_config.description + or f"Node {self.info.node.node_id}" + ) if include_value_name: value_name = ( alternate_value_name diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index b171fb38425099..c9f7d35eb138db 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -294,6 +294,12 @@ def inovelli_lzw36_state_fixture(): return json.loads(load_fixture("zwave_js/inovelli_lzw36_state.json")) +@pytest.fixture(name="null_name_check_state", scope="session") +def null_name_check_state_fixture(): + """Load the null name check node state fixture data.""" + return json.loads(load_fixture("zwave_js/null_name_check_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -490,6 +496,14 @@ def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state): return node +@pytest.fixture(name="null_name_check") +def null_name_check_fixture(client, null_name_check_state): + """Mock a node with no name.""" + node = Node(client, copy.deepcopy(null_name_check_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="multiple_devices") def multiple_devices_fixture( client, climate_radio_thermostat_ct100_plus_state, lock_schlage_be469_state diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index e56db58f3cc540..00574bd2d2f49e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -459,6 +459,12 @@ async def test_existing_node_ready( ) +async def test_null_name(hass, client, null_name_check, integration): + """Test that node without a name gets a generic node name.""" + node = null_name_check + assert hass.states.get(f"switch.node_{node.node_id}") + + async def test_existing_node_not_ready(hass, client, multisensor_6, device_registry): """Test we handle a non ready node that exists during integration setup.""" node = multisensor_6 diff --git a/tests/fixtures/zwave_js/null_name_check_state.json b/tests/fixtures/zwave_js/null_name_check_state.json new file mode 100644 index 00000000000000..fe63eaee20787d --- /dev/null +++ b/tests/fixtures/zwave_js/null_name_check_state.json @@ -0,0 +1,414 @@ +{ + "nodeId": 10, + "index": 0, + "installerIcon": 3328, + "userIcon": 3328, + "status": 4, + "ready": true, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 277, + "productId": 1, + "productType": 272, + "firmwareVersion": "2.17", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 1, + "neighbors": [], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 4, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "interviewStage": 7, + "endpoints": [ + { + "nodeId": 10, + "index": 0, + "installerIcon": 3328, + "userIcon": 3328 + }, + { + "nodeId": 10, + "index": 1 + }, + { + "nodeId": 10, + "index": 2 + }, + { + "nodeId": 10, + "index": 3 + }, + { + "nodeId": 10, + "index": 4 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 2.9 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Humidity", + "propertyName": "Humidity", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "%", + "label": "Humidity", + "ccSpecific": { + "sensorType": 5, + "scale": 0 + } + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 277 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 272 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.38" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["2.17"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 1, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 1, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 2, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 2, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 3, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 3, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + } + }, + { + "endpoint": 4, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": true + }, + { + "endpoint": 4, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": true + } + ], + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 33, + "label": "Multilevel Sensor" + }, + "specific": { + "key": 1, + "label": "Routing Multilevel Sensor" + }, + "mandatorySupportedCCs": [32, 49], + "mandatoryControlledCCs": [] + }, + "commandClasses": [ + { + "id": 37, + "name": "Binary Switch", + "version": 1, + "isSecure": false + }, + { + "id": 49, + "name": "Multilevel Sensor", + "version": 7, + "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": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "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 + } + ] +} From 629700c97ce6d3c281fdeda682cc0ceed3bb238d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 10:37:42 +0100 Subject: [PATCH 1092/1818] Bump actions/stale from v3.0.17 to v3.0.18 (#47612) Bumps [actions/stale](https://github.com/actions/stale) from v3.0.17 to v3.0.18. - [Release notes](https://github.com/actions/stale/releases) - [Commits](https://github.com/actions/stale/compare/v3.0.17...3b3c3f03cd4d8e2b61e179ef744a0d20efbe90b4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a280d0ee89a8e4..3ff0f47cedc147 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: # - No PRs marked as no-stale # - No issues marked as no-stale or help-wanted - name: 90 days stale issues & PRs policy - uses: actions/stale@v3.0.17 + uses: actions/stale@v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 90 @@ -53,7 +53,7 @@ jobs: # - No PRs marked as no-stale or new-integrations # - No issues (-1) - name: 30 days stale PRs policy - uses: actions/stale@v3.0.17 + uses: actions/stale@v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -78,7 +78,7 @@ jobs: # - No Issues marked as no-stale or help-wanted # - No PRs (-1) - name: Needs more information stale issues policy - uses: actions/stale@v3.0.17 + uses: actions/stale@v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: "needs-more-information" From 597d8eaa4ccb2694d69a60cd4891198926985d19 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 8 Mar 2021 05:15:08 -0600 Subject: [PATCH 1093/1818] Update rokuecp to 0.8.1 (#47589) --- 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 10cef13cda02a1..981a9b080777d4 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.8.0"], + "requirements": ["rokuecp==0.8.1"], "homekit": { "models": [ "3810X", diff --git a/requirements_all.txt b/requirements_all.txt index 285f8ed513376d..c31341addb165a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1968,7 +1968,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.8.0 +rokuecp==0.8.1 # homeassistant.components.roomba roombapy==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7804e3c3926f66..19bb8af07182a6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1011,7 +1011,7 @@ rflink==0.0.58 ring_doorbell==0.6.2 # homeassistant.components.roku -rokuecp==0.8.0 +rokuecp==0.8.1 # homeassistant.components.roomba roombapy==1.6.2 From 457db1d0c366a5c00f3132889c034abe7a7087d7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 12:57:27 +0100 Subject: [PATCH 1094/1818] Upgrade elgato to 2.0.1 (#47616) --- homeassistant/components/elgato/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/elgato/manifest.json b/homeassistant/components/elgato/manifest.json index 1da98a4121170d..9a166b86b8e7d3 100644 --- a/homeassistant/components/elgato/manifest.json +++ b/homeassistant/components/elgato/manifest.json @@ -3,7 +3,7 @@ "name": "Elgato Key Light", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/elgato", - "requirements": ["elgato==1.0.0"], + "requirements": ["elgato==2.0.1"], "zeroconf": ["_elg._tcp.local."], "codeowners": ["@frenck"], "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index c31341addb165a..7714669102ad6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -524,7 +524,7 @@ ecoaliface==0.4.0 eebrightbox==0.0.4 # homeassistant.components.elgato -elgato==1.0.0 +elgato==2.0.1 # homeassistant.components.eliqonline eliqonline==1.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 19bb8af07182a6..248dade5e634fa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -279,7 +279,7 @@ dynalite_devices==0.1.46 eebrightbox==0.0.4 # homeassistant.components.elgato -elgato==1.0.0 +elgato==2.0.1 # homeassistant.components.elkm1 elkm1-lib==0.8.10 From d37fb7d88d7ef3f48ca2e5f181ea16f2cdea0156 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 13:03:39 +0100 Subject: [PATCH 1095/1818] Upgrade pre-commit to 2.11.0 (#47618) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index c906543f54c756..79203fba763ff1 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -9,7 +9,7 @@ coverage==5.5 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 -pre-commit==2.10.1 +pre-commit==2.11.0 pylint==2.7.2 astroid==2.5.1 pipdeptree==1.0.0 From ad86eb4be3ece16a08eedb4487e86334c17ac942 Mon Sep 17 00:00:00 2001 From: Adam Ernst Date: Mon, 8 Mar 2021 06:36:03 -0600 Subject: [PATCH 1096/1818] Add support for Flo "pucks" (#47074) So far the Flo integration only supports shutoff valves. Add support for Flo leak detector pucks, which measure temperature and humidity in addition to providing leak alerts. --- homeassistant/components/flo/binary_sensor.py | 34 +++- homeassistant/components/flo/device.py | 19 ++- homeassistant/components/flo/sensor.py | 95 +++++++++-- homeassistant/components/flo/switch.py | 6 +- tests/components/flo/conftest.py | 8 +- tests/components/flo/test_binary_sensor.py | 19 ++- tests/components/flo/test_device.py | 76 +++++---- tests/components/flo/test_init.py | 2 +- tests/components/flo/test_sensor.py | 13 +- tests/components/flo/test_services.py | 12 +- tests/components/flo/test_switch.py | 2 +- .../flo/device_info_response_detector.json | 161 ++++++++++++++++++ .../flo/location_info_base_response.json | 4 + .../user_info_expand_locations_response.json | 4 + 14 files changed, 389 insertions(+), 66 deletions(-) create mode 100644 tests/fixtures/flo/device_info_response_detector.json diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index a8bac49867471b..3ab1152f83e809 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -17,7 +17,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] - entities = [FloPendingAlertsBinarySensor(device) for device in devices] + entities = [] + for device in devices: + if device.device_type == "puck_oem": + # Flo "pucks" (leak detectors) *do* support pending alerts. + # However these pending alerts mix unrelated issues like + # low-battery alerts, humidity alerts, & temperature alerts + # in addition to the critical "water detected" alert. + # + # Since there are non-binary sensors for battery, humidity, + # and temperature, the binary sensor should only cover + # water detection. If this sensor trips, you really have + # a problem - vs. battery/temp/humidity which are warnings. + entities.append(FloWaterDetectedBinarySensor(device)) + else: + entities.append(FloPendingAlertsBinarySensor(device)) async_add_entities(entities) @@ -48,3 +62,21 @@ def device_state_attributes(self): def device_class(self): """Return the device class for the binary sensor.""" return DEVICE_CLASS_PROBLEM + + +class FloWaterDetectedBinarySensor(FloEntity, BinarySensorEntity): + """Binary sensor that reports if water is detected (for leak detectors).""" + + def __init__(self, device): + """Initialize the pending alerts binary sensor.""" + super().__init__("water_detected", "Water Detected", device) + + @property + def is_on(self): + """Return true if the Flo device is detecting water.""" + return self._device.water_detected + + @property + def device_class(self): + """Return the device class for the binary sensor.""" + return DEVICE_CLASS_PROBLEM diff --git a/homeassistant/components/flo/device.py b/homeassistant/components/flo/device.py index af36034026d710..49e0e9913767bd 100644 --- a/homeassistant/components/flo/device.py +++ b/homeassistant/components/flo/device.py @@ -58,7 +58,9 @@ def id(self) -> str: @property def device_name(self) -> str: """Return device name.""" - return f"{self.manufacturer} {self.model}" + return self._device_information.get( + "nickname", f"{self.manufacturer} {self.model}" + ) @property def manufacturer(self) -> str: @@ -120,6 +122,11 @@ def temperature(self) -> float: """Return the current temperature in degrees F.""" return self._device_information["telemetry"]["current"]["tempF"] + @property + def humidity(self) -> float: + """Return the current humidity in percent (0-100).""" + return self._device_information["telemetry"]["current"]["humidity"] + @property def consumption_today(self) -> float: """Return the current consumption for today in gallons.""" @@ -159,6 +166,11 @@ def has_alerts(self) -> bool: or self.pending_warning_alerts_count ) + @property + def water_detected(self) -> bool: + """Return whether water is detected, for leak detectors.""" + return self._device_information["fwProperties"]["telemetry_water"] + @property def last_known_valve_state(self) -> str: """Return the last known valve state for the device.""" @@ -169,6 +181,11 @@ def target_valve_state(self) -> str: """Return the target valve state for the device.""" return self._device_information["valve"]["target"] + @property + def battery_level(self) -> float: + """Return the battery level for battery-powered device, e.g. leak detectors.""" + return self._device_information["battery"]["level"] + async def async_set_mode_home(self): """Set the Flo location to home mode.""" await self.api_client.location.set_mode_home(self._flo_location_id) diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index 2feeb3702a6e96..d88f2b950fc47f 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -3,13 +3,15 @@ from typing import List, Optional from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + PERCENTAGE, PRESSURE_PSI, - TEMP_CELSIUS, + TEMP_FAHRENHEIT, VOLUME_GALLONS, ) -from homeassistant.util.temperature import fahrenheit_to_celsius from .const import DOMAIN as FLO_DOMAIN from .device import FloDeviceDataUpdateCoordinator @@ -20,8 +22,11 @@ NAME_DAILY_USAGE = "Today's Water Usage" NAME_CURRENT_SYSTEM_MODE = "Current System Mode" NAME_FLOW_RATE = "Water Flow Rate" -NAME_TEMPERATURE = "Water Temperature" +NAME_WATER_TEMPERATURE = "Water Temperature" +NAME_AIR_TEMPERATURE = "Temperature" NAME_WATER_PRESSURE = "Water Pressure" +NAME_HUMIDITY = "Humidity" +NAME_BATTERY = "Battery" async def async_setup_entry(hass, config_entry, async_add_entities): @@ -30,11 +35,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities): config_entry.entry_id ]["devices"] entities = [] - entities.extend([FloDailyUsageSensor(device) for device in devices]) - entities.extend([FloSystemModeSensor(device) for device in devices]) - entities.extend([FloCurrentFlowRateSensor(device) for device in devices]) - entities.extend([FloTemperatureSensor(device) for device in devices]) - entities.extend([FloPressureSensor(device) for device in devices]) + for device in devices: + if device.device_type == "puck_oem": + entities.extend( + [ + FloTemperatureSensor(NAME_AIR_TEMPERATURE, device), + FloHumiditySensor(device), + FloBatterySensor(device), + ] + ) + else: + entities.extend( + [ + FloDailyUsageSensor(device), + FloSystemModeSensor(device), + FloCurrentFlowRateSensor(device), + FloTemperatureSensor(NAME_WATER_TEMPERATURE, device), + FloPressureSensor(device), + ] + ) async_add_entities(entities) @@ -109,9 +128,9 @@ def unit_of_measurement(self) -> str: class FloTemperatureSensor(FloEntity): """Monitors the temperature.""" - def __init__(self, device): + def __init__(self, name, device): """Initialize the temperature sensor.""" - super().__init__("temperature", NAME_TEMPERATURE, device) + super().__init__("temperature", name, device) self._state: float = None @property @@ -119,12 +138,12 @@ def state(self) -> Optional[float]: """Return the current temperature.""" if self._device.temperature is None: return None - return round(fahrenheit_to_celsius(self._device.temperature), 1) + return round(self._device.temperature, 1) @property def unit_of_measurement(self) -> str: - """Return gallons as the unit measurement for water.""" - return TEMP_CELSIUS + """Return fahrenheit as the unit measurement for temperature.""" + return TEMP_FAHRENHEIT @property def device_class(self) -> Optional[str]: @@ -132,6 +151,32 @@ def device_class(self) -> Optional[str]: return DEVICE_CLASS_TEMPERATURE +class FloHumiditySensor(FloEntity): + """Monitors the humidity.""" + + def __init__(self, device): + """Initialize the humidity sensor.""" + super().__init__("humidity", NAME_HUMIDITY, device) + self._state: float = None + + @property + def state(self) -> Optional[float]: + """Return the current humidity.""" + if self._device.humidity is None: + return None + return round(self._device.humidity, 1) + + @property + def unit_of_measurement(self) -> str: + """Return percent as the unit measurement for humidity.""" + return PERCENTAGE + + @property + def device_class(self) -> Optional[str]: + """Return the device class for this sensor.""" + return DEVICE_CLASS_HUMIDITY + + class FloPressureSensor(FloEntity): """Monitors the water pressure.""" @@ -156,3 +201,27 @@ def unit_of_measurement(self) -> str: def device_class(self) -> Optional[str]: """Return the device class for this sensor.""" return DEVICE_CLASS_PRESSURE + + +class FloBatterySensor(FloEntity): + """Monitors the battery level for battery-powered leak detectors.""" + + def __init__(self, device): + """Initialize the battery sensor.""" + super().__init__("battery", NAME_BATTERY, device) + self._state: float = None + + @property + def state(self) -> Optional[float]: + """Return the current battery level.""" + return self._device.battery_level + + @property + def unit_of_measurement(self) -> str: + """Return percentage as the unit measurement for battery.""" + return PERCENTAGE + + @property + def device_class(self) -> Optional[str]: + """Return the device class for this sensor.""" + return DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index 91f3fdf54e4f3c..895212c56a0b12 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -26,7 +26,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] - async_add_entities([FloSwitch(device) for device in devices]) + entities = [] + for device in devices: + if device.device_type != "puck_oem": + entities.append(FloSwitch(device)) + async_add_entities(entities) platform = entity_platform.current_platform.get() diff --git a/tests/components/flo/conftest.py b/tests/components/flo/conftest.py index 907feb855698a3..d4ba80e6406ddc 100644 --- a/tests/components/flo/conftest.py +++ b/tests/components/flo/conftest.py @@ -43,13 +43,19 @@ def aioclient_mock_fixture(aioclient_mock): headers={"Content-Type": CONTENT_TYPE_JSON}, status=200, ) - # Mocks the device for flo. + # Mocks the devices for flo. aioclient_mock.get( "https://api-gw.meetflo.com/api/v2/devices/98765", text=load_fixture("flo/device_info_response.json"), status=200, headers={"Content-Type": CONTENT_TYPE_JSON}, ) + aioclient_mock.get( + "https://api-gw.meetflo.com/api/v2/devices/32839", + text=load_fixture("flo/device_info_response_detector.json"), + status=200, + headers={"Content-Type": CONTENT_TYPE_JSON}, + ) # Mocks the water consumption for flo. aioclient_mock.get( "https://api-gw.meetflo.com/api/v2/water/consumption", diff --git a/tests/components/flo/test_binary_sensor.py b/tests/components/flo/test_binary_sensor.py index 64b8f787a85903..b6a8abf727cf98 100644 --- a/tests/components/flo/test_binary_sensor.py +++ b/tests/components/flo/test_binary_sensor.py @@ -4,6 +4,7 @@ ATTR_FRIENDLY_NAME, CONF_PASSWORD, CONF_USERNAME, + STATE_OFF, STATE_ON, ) from homeassistant.setup import async_setup_component @@ -19,12 +20,14 @@ async def test_binary_sensors(hass, config_entry, aioclient_mock_fixture): ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 - # we should have 6 entities for the device - state = hass.states.get("binary_sensor.pending_system_alerts") - assert state.state == STATE_ON - assert state.attributes.get("info") == 0 - assert state.attributes.get("warning") == 2 - assert state.attributes.get("critical") == 0 - assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Pending System Alerts" + valve_state = hass.states.get("binary_sensor.pending_system_alerts") + assert valve_state.state == STATE_ON + assert valve_state.attributes.get("info") == 0 + assert valve_state.attributes.get("warning") == 2 + assert valve_state.attributes.get("critical") == 0 + assert valve_state.attributes.get(ATTR_FRIENDLY_NAME) == "Pending System Alerts" + + detector_state = hass.states.get("binary_sensor.water_detected") + assert detector_state.state == STATE_OFF diff --git a/tests/components/flo/test_device.py b/tests/components/flo/test_device.py index 63e81a16fb438d..5ac31a2bff91d1 100644 --- a/tests/components/flo/test_device.py +++ b/tests/components/flo/test_device.py @@ -13,46 +13,64 @@ async def test_device(hass, config_entry, aioclient_mock_fixture, aioclient_mock): - """Test Flo by Moen device.""" + """Test Flo by Moen devices.""" config_entry.add_to_hass(hass) assert await async_setup_component( hass, FLO_DOMAIN, {CONF_USERNAME: TEST_USER_ID, CONF_PASSWORD: TEST_PASSWORD} ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 - device: FloDeviceDataUpdateCoordinator = hass.data[FLO_DOMAIN][ + valve: FloDeviceDataUpdateCoordinator = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"][0] - assert device.api_client is not None - assert device.available - assert device.consumption_today == 3.674 - assert device.current_flow_rate == 0 - assert device.current_psi == 54.20000076293945 - assert device.current_system_mode == "home" - assert device.target_system_mode == "home" - assert device.firmware_version == "6.1.1" - assert device.device_type == "flo_device_v2" - assert device.id == "98765" - assert device.last_heard_from_time == "2020-07-24T12:45:00Z" - assert device.location_id == "mmnnoopp" - assert device.hass is not None - assert device.temperature == 70 - assert device.mac_address == "111111111111" - assert device.model == "flo_device_075_v2" - assert device.manufacturer == "Flo by Moen" - assert device.device_name == "Flo by Moen flo_device_075_v2" - assert device.rssi == -47 - assert device.pending_info_alerts_count == 0 - assert device.pending_critical_alerts_count == 0 - assert device.pending_warning_alerts_count == 2 - assert device.has_alerts is True - assert device.last_known_valve_state == "open" - assert device.target_valve_state == "open" + assert valve.api_client is not None + assert valve.available + assert valve.consumption_today == 3.674 + assert valve.current_flow_rate == 0 + assert valve.current_psi == 54.20000076293945 + assert valve.current_system_mode == "home" + assert valve.target_system_mode == "home" + assert valve.firmware_version == "6.1.1" + assert valve.device_type == "flo_device_v2" + assert valve.id == "98765" + assert valve.last_heard_from_time == "2020-07-24T12:45:00Z" + assert valve.location_id == "mmnnoopp" + assert valve.hass is not None + assert valve.temperature == 70 + assert valve.mac_address == "111111111111" + assert valve.model == "flo_device_075_v2" + assert valve.manufacturer == "Flo by Moen" + assert valve.device_name == "Smart Water Shutoff" + assert valve.rssi == -47 + assert valve.pending_info_alerts_count == 0 + assert valve.pending_critical_alerts_count == 0 + assert valve.pending_warning_alerts_count == 2 + assert valve.has_alerts is True + assert valve.last_known_valve_state == "open" + assert valve.target_valve_state == "open" + + detector: FloDeviceDataUpdateCoordinator = hass.data[FLO_DOMAIN][ + config_entry.entry_id + ]["devices"][1] + assert detector.api_client is not None + assert detector.available + assert detector.device_type == "puck_oem" + assert detector.id == "32839" + assert detector.last_heard_from_time == "2021-03-07T14:05:00Z" + assert detector.location_id == "mmnnoopp" + assert detector.hass is not None + assert detector.temperature == 61 + assert detector.humidity == 43 + assert detector.water_detected is False + assert detector.mac_address == "1a2b3c4d5e6f" + assert detector.model == "puck_v1" + assert detector.manufacturer == "Flo by Moen" + assert detector.device_name == "Kitchen Sink" call_count = aioclient_mock.call_count async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=90)) await hass.async_block_till_done() - assert aioclient_mock.call_count == call_count + 2 + assert aioclient_mock.call_count == call_count + 4 diff --git a/tests/components/flo/test_init.py b/tests/components/flo/test_init.py index 9061477da47765..13f16f06cbfbe5 100644 --- a/tests/components/flo/test_init.py +++ b/tests/components/flo/test_init.py @@ -13,6 +13,6 @@ async def test_setup_entry(hass, config_entry, aioclient_mock_fixture): hass, FLO_DOMAIN, {CONF_USERNAME: TEST_USER_ID, CONF_PASSWORD: TEST_PASSWORD} ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 assert await hass.config_entries.async_unload(config_entry.entry_id) diff --git a/tests/components/flo/test_sensor.py b/tests/components/flo/test_sensor.py index 309dfc11266d77..f1572dae02c43c 100644 --- a/tests/components/flo/test_sensor.py +++ b/tests/components/flo/test_sensor.py @@ -14,14 +14,19 @@ async def test_sensors(hass, config_entry, aioclient_mock_fixture): ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 - # we should have 5 entities for the device + # we should have 5 entities for the valve assert hass.states.get("sensor.current_system_mode").state == "home" assert hass.states.get("sensor.today_s_water_usage").state == "3.7" assert hass.states.get("sensor.water_flow_rate").state == "0" assert hass.states.get("sensor.water_pressure").state == "54.2" - assert hass.states.get("sensor.water_temperature").state == "21.1" + assert hass.states.get("sensor.water_temperature").state == "21" + + # and 3 entities for the detector + assert hass.states.get("sensor.temperature").state == "16" + assert hass.states.get("sensor.humidity").state == "43" + assert hass.states.get("sensor.battery").state == "100" async def test_manual_update_entity( @@ -34,7 +39,7 @@ async def test_manual_update_entity( ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 await async_setup_component(hass, "homeassistant", {}) diff --git a/tests/components/flo/test_services.py b/tests/components/flo/test_services.py index 270279e4a9d947..4941a118e48f3d 100644 --- a/tests/components/flo/test_services.py +++ b/tests/components/flo/test_services.py @@ -25,8 +25,8 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 - assert aioclient_mock.call_count == 4 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 + assert aioclient_mock.call_count == 6 await hass.services.async_call( FLO_DOMAIN, @@ -35,7 +35,7 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 5 + assert aioclient_mock.call_count == 7 await hass.services.async_call( FLO_DOMAIN, @@ -44,7 +44,7 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 8 await hass.services.async_call( FLO_DOMAIN, @@ -53,7 +53,7 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 7 + assert aioclient_mock.call_count == 9 await hass.services.async_call( FLO_DOMAIN, @@ -66,4 +66,4 @@ async def test_services(hass, config_entry, aioclient_mock_fixture, aioclient_mo blocking=True, ) await hass.async_block_till_done() - assert aioclient_mock.call_count == 8 + assert aioclient_mock.call_count == 10 diff --git a/tests/components/flo/test_switch.py b/tests/components/flo/test_switch.py index 25a64433a29365..cc6ab7e3a7ede3 100644 --- a/tests/components/flo/test_switch.py +++ b/tests/components/flo/test_switch.py @@ -15,7 +15,7 @@ async def test_valve_switches(hass, config_entry, aioclient_mock_fixture): ) await hass.async_block_till_done() - assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 1 + assert len(hass.data[FLO_DOMAIN][config_entry.entry_id]["devices"]) == 2 entity_id = "switch.shutoff_valve" assert hass.states.get(entity_id).state == STATE_ON diff --git a/tests/fixtures/flo/device_info_response_detector.json b/tests/fixtures/flo/device_info_response_detector.json new file mode 100644 index 00000000000000..aac24ab5e68e50 --- /dev/null +++ b/tests/fixtures/flo/device_info_response_detector.json @@ -0,0 +1,161 @@ +{ + "actionRules": [], + "battery": { + "level": 100, + "updated": "2021-03-01T12:05:00Z" + }, + "connectivity": { + "ssid": "SOMESSID" + }, + "deviceModel": "puck_v1", + "deviceType": "puck_oem", + "fwProperties": { + "alert_battery_active": false, + "alert_humidity_high_active": false, + "alert_humidity_high_count": 0, + "alert_humidity_low_active": false, + "alert_humidity_low_count": 1, + "alert_state": "inactive", + "alert_temperature_high_active": false, + "alert_temperature_high_count": 0, + "alert_temperature_low_active": false, + "alert_temperature_low_count": 0, + "alert_water_active": false, + "alert_water_count": 0, + "ap_mode_count": 1, + "beep_pattern": "off", + "button_click_count": 1, + "date": "2021-03-07T14:00:05.054Z", + "deep_sleep_count": 8229, + "device_boot_count": 25, + "device_boot_reason": "wakeup_timer", + "device_count": 8230, + "device_failed_count": 36, + "device_id": "1a2b3c4d5e6f", + "device_time_total": 405336, + "device_time_up": 1502, + "device_uuid": "32839", + "device_wakeup_count": 8254, + "flosense_shut_off_level": 2, + "fw_name": "1.1.15", + "fw_version": 10115, + "led_pattern": "led_blue_solid", + "limit_ota_battery_min": 30, + "pairing_state": "configured", + "reason": "heartbeat", + "serial_number": "111111111112", + "telemetry_battery_percent": 100, + "telemetry_battery_voltage": 2.9896278381347656, + "telemetry_count": 8224, + "telemetry_failed_count": 27, + "telemetry_humidity": 43.21965408325195, + "telemetry_rssi": 100, + "telemetry_temperature": 61.43144607543945, + "telemetry_water": false, + "timer_alarm_active": 10, + "timer_heartbeat_battery_low": 3600, + "timer_heartbeat_battery_ok": 1740, + "timer_heartbeat_last": 1740, + "timer_heartbeat_not_configured": 10, + "timer_heartbeat_retry_attempts": 3, + "timer_heartbeat_retry_delay": 600, + "timer_water_debounce": 2000, + "timer_wifi_ap_timeout": 600000, + "wifi_ap_ssid": "FloDetector-a123", + "wifi_sta_enc": "wpa2-psk", + "wifi_sta_failed_count": 21, + "wifi_sta_mac": "50:01:01:01:01:44", + "wifi_sta_ssid": "SOMESSID" + }, + "fwVersion": "1.1.15", + "hardwareThresholds": { + "battery": { + "maxValue": 100, + "minValue": 0, + "okMax": 100, + "okMin": 20 + }, + "batteryEnabled": true, + "humidity": { + "maxValue": 100, + "minValue": 0, + "okMax": 85, + "okMin": 15 + }, + "humidityEnabled": true, + "tempC": { + "maxValue": 60, + "minValue": -17.77777777777778, + "okMax": 37.77777777777778, + "okMin": 0 + }, + "tempEnabled": true, + "tempF": { + "maxValue": 140, + "minValue": 0, + "okMax": 100, + "okMin": 32 + } + }, + "id": "32839", + "installStatus": { + "isInstalled": false + }, + "isConnected": true, + "isPaired": true, + "lastHeardFromTime": "2021-03-07T14:05:00Z", + "location": { + "id": "mmnnoopp" + }, + "macAddress": "1a2b3c4d5e6f", + "nickname": "Kitchen Sink", + "notifications": { + "pending": { + "alarmCount": [], + "critical": { + "count": 0, + "devices": { + "absolute": 0, + "count": 0 + } + }, + "criticalCount": 0, + "info": { + "count": 0, + "devices": { + "absolute": 0, + "count": 0 + } + }, + "infoCount": 0, + "warning": { + "count": 0, + "devices": { + "absolute": 0, + "count": 0 + } + }, + "warningCount": 0 + } + }, + "puckConfig": { + "configuredAt": "2020-09-01T18:15:12.216Z", + "isConfigured": true + }, + "serialNumber": "111111111112", + "shutoff": { + "scheduledAt": "1970-01-01T00:00:00.000Z" + }, + "systemMode": { + "isLocked": false, + "shouldInherit": true + }, + "telemetry": { + "current": { + "humidity": 43, + "tempF": 61, + "updated": "2021-03-07T14:05:00Z" + } + }, + "valve": {} +} diff --git a/tests/fixtures/flo/location_info_base_response.json b/tests/fixtures/flo/location_info_base_response.json index f6840a0742bf90..a5a25da2d6c297 100644 --- a/tests/fixtures/flo/location_info_base_response.json +++ b/tests/fixtures/flo/location_info_base_response.json @@ -9,6 +9,10 @@ { "id": "98765", "macAddress": "123456abcdef" + }, + { + "id": "32839", + "macAddress": "1a2b3c4d5e6f" } ], "userRoles": [ diff --git a/tests/fixtures/flo/user_info_expand_locations_response.json b/tests/fixtures/flo/user_info_expand_locations_response.json index 829596b6849f27..18643e049baecc 100644 --- a/tests/fixtures/flo/user_info_expand_locations_response.json +++ b/tests/fixtures/flo/user_info_expand_locations_response.json @@ -19,6 +19,10 @@ { "id": "98765", "macAddress": "606405c11e10" + }, + { + "id": "32839", + "macAddress": "1a2b3c4d5e6f" } ], "userRoles": [ From f3c71a69f05adc415c309369b6a283ecc2b33712 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 8 Mar 2021 07:37:33 -0500 Subject: [PATCH 1097/1818] Allow 10mV precision for ZHA battery sensor entities (#47520) --- homeassistant/components/zha/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index b02b3a549be9dc..78bc8fcbe4eca8 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -186,7 +186,9 @@ def device_state_attributes(self) -> Dict[str, Any]: state_attrs["battery_quantity"] = battery_quantity battery_voltage = self._channel.cluster.get("battery_voltage") if battery_voltage is not None: - state_attrs["battery_voltage"] = round(battery_voltage / 10, 1) + v_10mv = round(battery_voltage / 10, 2) + v_100mv = round(battery_voltage / 10, 1) + state_attrs["battery_voltage"] = v_100mv if v_100mv == v_10mv else v_10mv return state_attrs From cf507b51cb2190d053b03574181442bba242f950 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 8 Mar 2021 13:51:26 +0100 Subject: [PATCH 1098/1818] Add feels like temperature sensor to OpenWeatherMap (#47559) --- homeassistant/components/openweathermap/const.py | 7 +++++++ .../openweathermap/weather_update_coordinator.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 5b0165b2bee0e1..8457ceb65e9dd0 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -49,6 +49,7 @@ ATTR_API_DATETIME = "datetime" ATTR_API_WEATHER = "weather" ATTR_API_TEMPERATURE = "temperature" +ATTR_API_FEELS_LIKE_TEMPERATURE = "feels_like_temperature" ATTR_API_WIND_SPEED = "wind_speed" ATTR_API_WIND_BEARING = "wind_bearing" ATTR_API_HUMIDITY = "humidity" @@ -81,6 +82,7 @@ MONITORED_CONDITIONS = [ ATTR_API_WEATHER, ATTR_API_TEMPERATURE, + ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_WIND_SPEED, ATTR_API_WIND_BEARING, ATTR_API_HUMIDITY, @@ -190,6 +192,11 @@ SENSOR_UNIT: TEMP_CELSIUS, SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, }, + ATTR_API_FEELS_LIKE_TEMPERATURE: { + SENSOR_NAME: "Feels like temperature", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, ATTR_API_WIND_SPEED: { SENSOR_NAME: "Wind speed", SENSOR_UNIT: SPEED_METERS_PER_SECOND, diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index b4d9b7a9c80034..07b8c507c873d1 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -25,6 +25,7 @@ from .const import ( ATTR_API_CLOUDS, ATTR_API_CONDITION, + ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_FORECAST, ATTR_API_HUMIDITY, ATTR_API_PRESSURE, @@ -115,6 +116,9 @@ def _convert_weather_response(self, weather_response): return { ATTR_API_TEMPERATURE: current_weather.temperature("celsius").get("temp"), + ATTR_API_FEELS_LIKE_TEMPERATURE: current_weather.temperature("celsius").get( + "feels_like" + ), ATTR_API_PRESSURE: current_weather.pressure.get("press"), ATTR_API_HUMIDITY: current_weather.humidity, ATTR_API_WIND_BEARING: current_weather.wind().get("deg"), From 9774ada4aa847f8942b84171b51a8ad633ac0254 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Mon, 8 Mar 2021 05:05:39 -0800 Subject: [PATCH 1099/1818] Code cleanup for SmartTub integration (#47584) --- homeassistant/components/smarttub/const.py | 4 ++++ homeassistant/components/smarttub/controller.py | 15 +++++++++++---- homeassistant/components/smarttub/light.py | 3 ++- homeassistant/components/smarttub/sensor.py | 2 +- homeassistant/components/smarttub/switch.py | 4 ++-- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 0b97926cc4390a..082d46cb26a447 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -20,3 +20,7 @@ DEFAULT_LIGHT_EFFECT = "purple" # default to 50% brightness DEFAULT_LIGHT_BRIGHTNESS = 128 + +ATTR_STATUS = "status" +ATTR_PUMPS = "pumps" +ATTR_LIGHTS = "lights" diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index bf8de2f4e2e381..e5246446665db6 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -15,7 +15,14 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN, POLLING_TIMEOUT, SCAN_INTERVAL +from .const import ( + ATTR_LIGHTS, + ATTR_PUMPS, + ATTR_STATUS, + DOMAIN, + POLLING_TIMEOUT, + SCAN_INTERVAL, +) from .helpers import get_spa_name _LOGGER = logging.getLogger(__name__) @@ -92,9 +99,9 @@ async def _get_spa_data(self, spa): spa.get_lights(), ) return { - "status": status, - "pumps": {pump.id: pump for pump in pumps}, - "lights": {light.zone: light for light in lights}, + ATTR_STATUS: status, + ATTR_PUMPS: {pump.id: pump for pump in pumps}, + ATTR_LIGHTS: {light.zone: light for light in lights}, } async def async_register_devices(self, entry): diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py index 1baf7e527eb2ea..57acf583415146 100644 --- a/homeassistant/components/smarttub/light.py +++ b/homeassistant/components/smarttub/light.py @@ -13,6 +13,7 @@ ) from .const import ( + ATTR_LIGHTS, DEFAULT_LIGHT_BRIGHTNESS, DEFAULT_LIGHT_EFFECT, DOMAIN, @@ -32,7 +33,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [ SmartTubLight(controller.coordinator, light) for spa in controller.spas - for light in await spa.get_lights() + for light in controller.coordinator.data[spa.id][ATTR_LIGHTS].values() ] async_add_entities(entities) diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 44f09989a99bc2..563c16b3ff1230 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -61,7 +61,7 @@ class SmartTubPrimaryFiltrationCycle(SmartTubSensor): def __init__(self, coordinator, spa): """Initialize the entity.""" super().__init__( - coordinator, spa, "primary filtration cycle", "primary_filtration" + coordinator, spa, "Primary Filtration Cycle", "primary_filtration" ) @property diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index 736611e54115ee..d2891932546150 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -6,7 +6,7 @@ from homeassistant.components.switch import SwitchEntity -from .const import API_TIMEOUT, DOMAIN, SMARTTUB_CONTROLLER +from .const import API_TIMEOUT, ATTR_PUMPS, DOMAIN, SMARTTUB_CONTROLLER from .entity import SmartTubEntity from .helpers import get_spa_name @@ -21,7 +21,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [ SmartTubPump(controller.coordinator, pump) for spa in controller.spas - for pump in await spa.get_pumps() + for pump in controller.coordinator.data[spa.id][ATTR_PUMPS].values() ] async_add_entities(entities) From 197687399d4a67f4dd52177b5985ab8dfb1ab219 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 14:26:52 +0100 Subject: [PATCH 1100/1818] Upgrade pillow to 8.1.2 (#47619) --- 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 f5d425cb9ef046..6f6fcb0d6b3b55 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,6 +2,6 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==8.1.1"], + "requirements": ["pydoods==1.0.2", "pillow==8.1.2"], "codeowners": [] } diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index c8029c2e3130f9..741fb8511a6e55 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==8.1.1"], + "requirements": ["pillow==8.1.2"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index c1a01004fe9520..86f3d23d308775 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==8.1.1"], + "requirements": ["pillow==8.1.2"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 5867d0d6b51a24..bd574af0297d45 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,6 +2,6 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==8.1.1", "pyzbar==0.1.7"], + "requirements": ["pillow==8.1.2", "pyzbar==0.1.7"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index 4f9f6514531221..13f3cf22506b94 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,6 +2,6 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==8.1.1"], + "requirements": ["pillow==8.1.2"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index aa9519fd68b399..0cab5b45b848f1 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,6 +2,6 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==8.1.1", "simplehound==0.3"], + "requirements": ["pillow==8.1.2", "simplehound==0.3"], "codeowners": ["@robmarkcole"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 300c3ddd1db6ba..49ee22176a72ea 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.3.0", "pycocotools==2.0.1", "numpy==1.19.2", - "pillow==8.1.1" + "pillow==8.1.2" ], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7642e14bda8c44..46dc2d027815f3 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 -pillow==8.1.1 +pillow==8.1.2 pip>=8.0.3,<20.3 python-slugify==4.0.1 pytz>=2021.1 diff --git a/requirements_all.txt b/requirements_all.txt index 7714669102ad6f..0028e6e343ae10 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1126,7 +1126,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.1 +pillow==8.1.2 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 248dade5e634fa..6ccb68983936fe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -575,7 +575,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==8.1.1 +pillow==8.1.2 # homeassistant.components.plex plexapi==4.4.0 From 24db0ff956a29d7883f235c925025e587ca0684f Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 8 Mar 2021 16:59:54 +0200 Subject: [PATCH 1101/1818] Fix Shelly logbook exception when missing COAP (#47620) --- homeassistant/components/shelly/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 0058374cfe7330..27152997ef70a7 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -177,9 +177,9 @@ def get_device_wrapper(hass: HomeAssistant, device_id: str): return None for config_entry in hass.data[DOMAIN][DATA_CONFIG_ENTRY]: - wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry][COAP] + wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(COAP) - if wrapper.device_id == device_id: + if wrapper and wrapper.device_id == device_id: return wrapper return None From ee2658f9e6b6bd3333eabfaf2d8294c0facffb18 Mon Sep 17 00:00:00 2001 From: B-Hartley Date: Mon, 8 Mar 2021 16:22:13 +0000 Subject: [PATCH 1102/1818] Add (some) of ZCL concentration clusters to ZHA component (#47590) * Update registries.py Add concentration clusters recently added to zigpy * Update measurement.py Add concentration clusters recently added to ZigPy * Update sensor.py Add concentration clusters recently added to ZigPy * Update sensor.py remove unnecessary tabs * Update measurement.py remove unnecessary tabs * Update sensor.py Just adding CO and CO2 for now. * Update registries.py Just adding CO2 and CO for now. * Update measurement.py Just adding CO2 and CO for now * Update sensor.py import const CONCENTRATION_PARTS_PER_MILLION * Update registries.py removed trailing whitespace * Update sensor.py added extra blank lines and removed trailing whitespace * Update measurement.py added extra blank lines and removed trailing whitespace * Update sensor.py add device classes for CO and CO2 --- .../zha/core/channels/measurement.py | 28 +++++++++++++++++++ .../components/zha/core/registries.py | 2 ++ homeassistant/components/zha/sensor.py | 25 +++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/homeassistant/components/zha/core/channels/measurement.py b/homeassistant/components/zha/core/channels/measurement.py index 64db1aa82ac98a..78ff12a9bf300e 100644 --- a/homeassistant/components/zha/core/channels/measurement.py +++ b/homeassistant/components/zha/core/channels/measurement.py @@ -74,3 +74,31 @@ class TemperatureMeasurement(ZigbeeChannel): "config": (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 50), } ] + + +@registries.ZIGBEE_CHANNEL_REGISTRY.register( + measurement.CarbonMonoxideConcentration.cluster_id +) +class CarbonMonoxideConcentration(ZigbeeChannel): + """Carbon Monoxide measurement channel.""" + + REPORT_CONFIG = [ + { + "attr": "measured_value", + "config": (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 0.000001), + } + ] + + +@registries.ZIGBEE_CHANNEL_REGISTRY.register( + measurement.CarbonDioxideConcentration.cluster_id +) +class CarbonDioxideConcentration(ZigbeeChannel): + """Carbon Dioxide measurement channel.""" + + REPORT_CONFIG = [ + { + "attr": "measured_value", + "config": (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 0.000001), + } + ] diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 4dcccc98c05aff..66a9a70e752317 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -68,6 +68,8 @@ zcl.clusters.general.PowerConfiguration.cluster_id: SENSOR, zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id: SENSOR, zcl.clusters.hvac.Fan.cluster_id: FAN, + zcl.clusters.measurement.CarbonDioxideConcentration.cluster_id: SENSOR, + zcl.clusters.measurement.CarbonMonoxideConcentration.cluster_id: SENSOR, zcl.clusters.measurement.IlluminanceMeasurement.cluster_id: SENSOR, zcl.clusters.measurement.OccupancySensing.cluster_id: BINARY_SENSOR, zcl.clusters.measurement.PressureMeasurement.cluster_id: SENSOR, diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 78bc8fcbe4eca8..84d2dfb06bb288 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -5,6 +5,8 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -14,6 +16,7 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, PERCENTAGE, POWER_WATT, @@ -279,3 +282,25 @@ class Temperature(Sensor): _device_class = DEVICE_CLASS_TEMPERATURE _divisor = 100 _unit = TEMP_CELSIUS + + +@STRICT_MATCH(channel_names="carbon_dioxide_concentration") +class CarbonDioxideConcentration(Sensor): + """Carbon Dioxide Concentration sensor.""" + + SENSOR_ATTR = "measured_value" + _device_class = DEVICE_CLASS_CO2 + _decimals = 0 + _multiplier = 1e6 + _unit = CONCENTRATION_PARTS_PER_MILLION + + +@STRICT_MATCH(channel_names="carbon_monoxide_concentration") +class CarbonMonoxideConcentration(Sensor): + """Carbon Monoxide Concentration sensor.""" + + SENSOR_ATTR = "measured_value" + _device_class = DEVICE_CLASS_CO + _decimals = 0 + _multiplier = 1e6 + _unit = CONCENTRATION_PARTS_PER_MILLION From 8fe51b8ea7e5912a70f3650bd0fb794681e67524 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Mar 2021 18:04:41 +0100 Subject: [PATCH 1103/1818] Store automation traces indexed by run_id (#47509) * Store traces indexed by run_id * Format * Add test * Add test * Clarify comment --- .../components/automation/__init__.py | 38 +++++++-- tests/components/config/test_automation.py | 80 ++++++++++++++++++- tests/helpers/test_script.py | 14 +++- 3 files changed, 122 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index df7102effde322..9963c942f08128 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,7 +1,8 @@ """Allow to set up simple automation rules via the config file.""" -from collections import deque +from collections import OrderedDict from contextlib import contextmanager import datetime as dt +from itertools import count import logging from typing import ( Any, @@ -240,6 +241,8 @@ async def reload_service_handler(service_call): class AutomationTrace: """Container for automation trace.""" + _runids = count(0) + def __init__( self, unique_id: Optional[str], @@ -254,6 +257,7 @@ def __init__( self._context: Context = context self._error: Optional[Exception] = None self._state: str = "running" + self.runid: str = str(next(self._runids)) self._timestamp_finish: Optional[dt.datetime] = None self._timestamp_start: dt.datetime = dt_util.utcnow() self._trigger: Dict[str, Any] = trigger @@ -300,6 +304,7 @@ def as_dict(self) -> Dict[str, Any]: "config": self._config, "context": self._context, "state": self._state, + "run_id": self.runid, "timestamp": { "start": self._timestamp_start, "finish": self._timestamp_finish, @@ -313,16 +318,37 @@ def as_dict(self) -> Dict[str, Any]: return result +class LimitedSizeDict(OrderedDict): + """OrderedDict limited in size.""" + + def __init__(self, *args, **kwds): + """Initialize OrderedDict limited in size.""" + self.size_limit = kwds.pop("size_limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_size_limit() + + def __setitem__(self, key, value): + """Set item and check dict size.""" + OrderedDict.__setitem__(self, key, value) + self._check_size_limit() + + def _check_size_limit(self): + """Check dict size and evict items in FIFO order if needed.""" + if self.size_limit is not None: + while len(self) > self.size_limit: + self.popitem(last=False) + + @contextmanager def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" automation_trace = AutomationTrace(unique_id, config, trigger, context) if unique_id: - if unique_id not in hass.data[DATA_AUTOMATION_TRACE]: - hass.data[DATA_AUTOMATION_TRACE][unique_id] = deque([], STORED_TRACES) - traces = hass.data[DATA_AUTOMATION_TRACE][unique_id] - traces.append(automation_trace) + automation_traces = hass.data[DATA_AUTOMATION_TRACE] + if unique_id not in automation_traces: + automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) + automation_traces[unique_id][automation_trace.runid] = automation_trace try: yield automation_trace @@ -835,7 +861,7 @@ def get_debug_traces_for_automation(hass, automation_id): """Return a serializable list of debug traces for an automation.""" traces = [] - for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, []): + for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): traces.append(trace.as_dict()) return traces diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 7e0cf9e8e4d619..cb192befade380 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -3,7 +3,7 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component -from homeassistant.components import config +from homeassistant.components import automation, config from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -325,3 +325,81 @@ def next_id(): assert trace["trigger"]["description"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + + +async def test_automation_trace_overflow(hass, hass_ws_client): + """Test the number of stored traces per automation is limited.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"event": "some_event"}, + } + moon_config = { + "id": "moon", + "trigger": {"platform": "event", "event_type": "test_event2"}, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + await client.send_json( + {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"sun": []} + + # Trigger "sun" and "moon" automation once + hass.bus.async_fire("test_event") + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get traces + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 1 + moon_run_id = response["result"]["moon"][0]["run_id"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation enough times to overflow the number of stored traces + for _ in range(automation.STORED_TRACES): + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + await client.send_json({"id": next_id(), "type": "automation/trace"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == automation.STORED_TRACES + assert len(response["result"]["sun"]) == 1 + assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 + assert ( + int(response["result"]["moon"][-1]["run_id"]) + == int(moon_run_id) + automation.STORED_TRACES + ) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 027254ee03e32f..18c769fee73e5f 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1192,11 +1192,11 @@ async def test_condition_all_cached(hass): assert len(script_obj._config_cache) == 2 -async def test_repeat_count(hass, caplog): +@pytest.mark.parametrize("count", [3, script.ACTION_TRACE_NODE_MAX_LEN * 2]) +async def test_repeat_count(hass, caplog, count): """Test repeat action w/ count option.""" event = "test_event" events = async_capture_events(hass, event) - count = 3 alias = "condition step" sequence = cv.SCRIPT_SCHEMA( @@ -1215,7 +1215,8 @@ async def test_repeat_count(hass, caplog): }, } ) - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + with script.trace_action(None): + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=Context()) await hass.async_block_till_done() @@ -1226,6 +1227,13 @@ async def test_repeat_count(hass, caplog): assert event.data.get("index") == index + 1 assert event.data.get("last") == (index == count - 1) assert caplog.text.count(f"Repeating {alias}") == count + assert_action_trace( + { + "": [{}], + "0": [{}], + "0/0/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + } + ) @pytest.mark.parametrize("condition", ["while", "until"]) From 53952b96623e255d06b8f1674b81eff329fe680d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:34:34 -1000 Subject: [PATCH 1104/1818] Ensure template fan value_template always determines on state (#47598) --- homeassistant/components/template/fan.py | 3 --- tests/components/template/test_fan.py | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 18a7d8262e005f..51dce0f8d56191 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -523,7 +523,6 @@ def _update_speed(self, speed): speed = str(speed) if speed in self._speed_list: - self._state = STATE_OFF if speed == SPEED_OFF else STATE_ON self._speed = speed self._percentage = self.speed_to_percentage(speed) self._preset_mode = speed if speed in self.preset_modes else None @@ -552,7 +551,6 @@ def _update_percentage(self, percentage): return if 0 <= percentage <= 100: - self._state = STATE_OFF if percentage == 0 else STATE_ON self._percentage = percentage if self._speed_list: self._speed = self.percentage_to_speed(percentage) @@ -569,7 +567,6 @@ def _update_preset_mode(self, preset_mode): preset_mode = str(preset_mode) if preset_mode in self.preset_modes: - self._state = STATE_ON self._speed = preset_mode self._percentage = None self._preset_mode = preset_mode diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 2b9059017c6d50..34dccd7d172899 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -246,6 +246,10 @@ async def test_templates_with_entities(hass, calls): await hass.async_block_till_done() _verify(hass, STATE_ON, None, 0, True, DIRECTION_FORWARD, None) + hass.states.async_set(_STATE_INPUT_BOOLEAN, False) + await hass.async_block_till_done() + _verify(hass, STATE_OFF, None, 0, True, DIRECTION_FORWARD, None) + async def test_templates_with_entities_and_invalid_percentage(hass, calls): """Test templates with values from other entities.""" @@ -274,7 +278,7 @@ async def test_templates_with_entities_and_invalid_percentage(hass, calls): await hass.async_start() await hass.async_block_till_done() - _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + _verify(hass, STATE_ON, SPEED_OFF, 0, None, None, None) hass.states.async_set("sensor.percentage", "33") await hass.async_block_till_done() @@ -299,7 +303,7 @@ async def test_templates_with_entities_and_invalid_percentage(hass, calls): hass.states.async_set("sensor.percentage", "0") await hass.async_block_till_done() - _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + _verify(hass, STATE_ON, SPEED_OFF, 0, None, None, None) async def test_templates_with_entities_and_preset_modes(hass, calls): From 8018097c544fe4e56d71fa56ff23029b9464910c Mon Sep 17 00:00:00 2001 From: Czapla <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:37:37 +0100 Subject: [PATCH 1105/1818] Add title key to allow mobile app title translation to other languages (#46593) --- homeassistant/components/mobile_app/strings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/mobile_app/strings.json b/homeassistant/components/mobile_app/strings.json index b18f3e7265c552..9e388ebc76cd71 100644 --- a/homeassistant/components/mobile_app/strings.json +++ b/homeassistant/components/mobile_app/strings.json @@ -1,4 +1,5 @@ { + "title": "Mobile App", "config": { "step": { "confirm": { From 9d14ff810522c893bcf9533ff30f48a8cd3019c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:39:57 -1000 Subject: [PATCH 1106/1818] Add suggested_area support to Apple TV (#47015) --- homeassistant/components/apple_tv/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index c735ebc57e704f..75c4d6ccc8affb 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -348,10 +348,16 @@ async def _async_setup_device_registry(self): "name": self.config_entry.data[CONF_NAME], } + area = attrs["name"] + name_trailer = f" {DEFAULT_NAME}" + if area.endswith(name_trailer): + area = area[: -len(name_trailer)] + attrs["suggested_area"] = area + if self.atv: dev_info = self.atv.device_info - attrs["model"] = "Apple TV " + dev_info.model.name.replace("Gen", "") + attrs["model"] = DEFAULT_NAME + " " + dev_info.model.name.replace("Gen", "") attrs["sw_version"] = dev_info.version if dev_info.mac: From 65776ef9808a5fa8f041610322c4011b702361f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:40:39 -1000 Subject: [PATCH 1107/1818] Remove self as code owner for mylink (#46242) Sadly these devices turn out to be so unreliable that I gave up on them and replaced them with bond (bondhome.io) devices which have been rock solid --- CODEOWNERS | 1 - homeassistant/components/somfy_mylink/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 3f0b3d89e05bc2..b4498aecc2717e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -436,7 +436,6 @@ homeassistant/components/solarlog/* @Ernst79 homeassistant/components/solax/* @squishykid homeassistant/components/soma/* @ratsept homeassistant/components/somfy/* @tetienne -homeassistant/components/somfy_mylink/* @bdraco homeassistant/components/sonarr/* @ctalkington homeassistant/components/songpal/* @rytilahti @shenxn homeassistant/components/sonos/* @cgtobi diff --git a/homeassistant/components/somfy_mylink/manifest.json b/homeassistant/components/somfy_mylink/manifest.json index a7be33583d2784..a71661f57f4458 100644 --- a/homeassistant/components/somfy_mylink/manifest.json +++ b/homeassistant/components/somfy_mylink/manifest.json @@ -5,7 +5,7 @@ "requirements": [ "somfy-mylink-synergy==1.0.6" ], - "codeowners": ["@bdraco"], + "codeowners": [], "config_flow": true, "dhcp": [{ "hostname":"somfy_*", "macaddress":"B8B7F1*" From f9e33a4a0d69db4b19ad7b280f2403d27be22157 Mon Sep 17 00:00:00 2001 From: Tony Roman Date: Mon, 8 Mar 2021 13:26:08 -0500 Subject: [PATCH 1108/1818] Allow running and restarting with both ozw and zwave active (#47566) Co-authored-by: Martin Hjelmare --- homeassistant/components/ozw/manifest.json | 3 +-- script/hassfest/dependencies.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json index 984e3f9c51afc7..a1409fd79a81ec 100644 --- a/homeassistant/components/ozw/manifest.json +++ b/homeassistant/components/ozw/manifest.json @@ -7,8 +7,7 @@ "python-openzwave-mqtt[mqtt-client]==1.4.0" ], "after_dependencies": [ - "mqtt", - "zwave" + "mqtt" ], "codeowners": [ "@cgarwood", diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 6283b2d8665c09..b13d792904243e 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -147,6 +147,8 @@ def visit_Attribute(self, node): # Demo ("demo", "manual"), ("demo", "openalpr_local"), + # Migration wizard from zwave to ozw. + "ozw", # This should become a helper method that integrations can submit data to ("websocket_api", "lovelace"), ("websocket_api", "shopping_list"), From 32476a3fed939e476c59a0625eaa901c18363906 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:34:12 +0100 Subject: [PATCH 1109/1818] Fix AsusWRT wrong api call (#47522) --- CODEOWNERS | 2 +- homeassistant/components/asuswrt/config_flow.py | 2 +- homeassistant/components/asuswrt/manifest.json | 2 +- homeassistant/components/asuswrt/router.py | 2 +- tests/components/asuswrt/test_config_flow.py | 4 ++-- tests/components/asuswrt/test_sensor.py | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b4498aecc2717e..bece933d9c8f80 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,7 +46,7 @@ homeassistant/components/arcam_fmj/* @elupus homeassistant/components/arduino/* @fabaff homeassistant/components/arest/* @fabaff homeassistant/components/arris_tg2492lg/* @vanbalken -homeassistant/components/asuswrt/* @kennedyshead +homeassistant/components/asuswrt/* @kennedyshead @ollo69 homeassistant/components/atag/* @MatsNL homeassistant/components/aten_pe/* @mtdcr homeassistant/components/atome/* @baqs diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index b312d9b8186ff9..94b5fd0f691a26 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -127,7 +127,7 @@ async def _async_check_connection(self, user_input): conf_protocol = user_input[CONF_PROTOCOL] if conf_protocol == PROTOCOL_TELNET: - await api.connection.disconnect() + api.connection.disconnect() return RESULT_SUCCESS async def async_step_user(self, user_input=None): diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json index 744a05b9728ec4..ab739f1c7ec47a 100644 --- a/homeassistant/components/asuswrt/manifest.json +++ b/homeassistant/components/asuswrt/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/asuswrt", "requirements": ["aioasuswrt==1.3.1"], - "codeowners": ["@kennedyshead"] + "codeowners": ["@kennedyshead", "@ollo69"] } diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index c94135423d83a8..4c92ee2ef6771b 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -341,7 +341,7 @@ async def close(self) -> None: """Close the connection.""" if self._api is not None: if self._protocol == PROTOCOL_TELNET: - await self._api.connection.disconnect() + self._api.connection.disconnect() self._api = None for func in self._on_close: diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py index 7faec5d336cce9..a6e24b09462154 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -1,6 +1,6 @@ """Tests for the AsusWrt config flow.""" from socket import gaierror -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch import pytest @@ -46,7 +46,7 @@ def mock_controller_connect(): with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True - service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.connection.disconnect = Mock() yield service_mock diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 334f06d85a6164..4a8a1f27653d2c 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -1,6 +1,6 @@ """Tests for the AsusWrt sensor.""" from datetime import timedelta -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from aioasuswrt.asuswrt import Device import pytest @@ -49,7 +49,7 @@ def mock_controller_connect(): with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True - service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.connection.disconnect = Mock() service_mock.return_value.async_get_connected_devices = AsyncMock( return_value=MOCK_DEVICES ) From b315fcab1160e8daa272a8b75719b1382ca68083 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:43:22 -1000 Subject: [PATCH 1110/1818] Fix turn on without speed in homekit controller (#47597) --- .../components/homekit_controller/fan.py | 9 +- .../components/homekit_controller/conftest.py | 7 ++ .../components/homekit_controller/test_fan.py | 97 +++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index e2cdf7b3cfdea8..591050f5fd9c17 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -80,6 +80,13 @@ def supported_features(self): return features + @property + def speed_count(self): + """Speed count for the fan.""" + return round( + 100 / max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) + ) + async def async_set_direction(self, direction): """Set the direction of the fan.""" await self.async_put_characteristics( @@ -110,7 +117,7 @@ async def async_turn_on( if not self.is_on: characteristics[self.on_characteristic] = True - if self.supported_features & SUPPORT_SET_SPEED: + if percentage is not None and self.supported_features & SUPPORT_SET_SPEED: characteristics[CharacteristicsTypes.ROTATION_SPEED] = percentage if characteristics: diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 4e095b1d2d9621..3cde3912709090 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -11,6 +11,13 @@ from tests.components.light.conftest import mock_light_profiles # noqa: F401 +@pytest.fixture(autouse=True) +def mock_zeroconf(): + """Mock zeroconf.""" + with mock.patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: + yield mock_zc.return_value + + @pytest.fixture def utcnow(request): """Freeze time at a known point.""" diff --git a/tests/components/homekit_controller/test_fan.py b/tests/components/homekit_controller/test_fan.py index b8d42b21643f89..d66ce81d5349f7 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -50,6 +50,38 @@ def create_fanv2_service(accessory): swing_mode.value = 0 +def create_fanv2_service_with_min_step(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED) + speed.value = 0 + speed.minStep = 25 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + +def create_fanv2_service_without_rotation_speed(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + 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) @@ -95,6 +127,29 @@ async def test_turn_on(hass, utcnow): assert helper.characteristics[V1_ROTATION_SPEED].value == 33.0 +async def test_turn_on_off_without_rotation_speed(hass, utcnow): + """Test that we can turn a fan on.""" + helper = await setup_test_component( + hass, create_fanv2_service_without_rotation_speed + ) + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 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) @@ -181,6 +236,7 @@ async def test_speed_read(hass, utcnow): state = await helper.poll_and_get_state() 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() @@ -277,6 +333,24 @@ async def test_v2_turn_on(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 1 assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + async def test_v2_turn_off(hass, utcnow): """Test that we can turn a fan off.""" @@ -355,6 +429,29 @@ async def test_v2_set_percentage(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 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 hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 66}, + blocking=True, + ) + assert helper.characteristics[V2_ROTATION_SPEED].value == 75 + + await hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 0}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 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) From 573c40cb1121420d7ba6e41238a0575a70a6d48c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:44:28 -1000 Subject: [PATCH 1111/1818] Ensure bond devices recover when wifi disconnects and reconnects (#47591) --- homeassistant/components/bond/entity.py | 2 +- tests/components/bond/test_entity.py | 169 ++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 tests/components/bond/test_entity.py diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 6c56b47a9dc536..cd120d79d56669 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -102,7 +102,7 @@ async def async_update(self) -> None: async def _async_update_if_bpup_not_alive(self, *_: Any) -> None: """Fetch via the API if BPUP is not alive.""" - if self._bpup_subs.alive and self._initialized: + if self._bpup_subs.alive and self._initialized and self._available: return assert self._update_lock is not None diff --git a/tests/components/bond/test_entity.py b/tests/components/bond/test_entity.py new file mode 100644 index 00000000000000..e0a3f156ff51ae --- /dev/null +++ b/tests/components/bond/test_entity.py @@ -0,0 +1,169 @@ +"""Tests for the Bond entities.""" +import asyncio +from datetime import timedelta +from unittest.mock import patch + +from bond_api import BPUPSubscriptions, DeviceType + +from homeassistant import core +from homeassistant.components import fan +from homeassistant.components.fan import DOMAIN as FAN_DOMAIN +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE +from homeassistant.util import utcnow + +from .common import patch_bond_device_state, setup_platform + +from tests.common import async_fire_time_changed + + +def ceiling_fan(name: str): + """Create a ceiling fan with given name.""" + return { + "name": name, + "type": DeviceType.CEILING_FAN, + "actions": ["SetSpeed", "SetDirection"], + } + + +async def test_bpup_goes_offline_and_recovers_same_entity(hass: core.HomeAssistant): + """Test that push updates fail and we fallback to polling and then bpup recovers. + + The BPUP recovery is triggered by an update for the entity and + we do not fallback to polling because state is in sync. + """ + bpup_subs = BPUPSubscriptions() + with patch( + "homeassistant.components.bond.BPUPSubscriptions", + return_value=bpup_subs, + ): + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 3, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 + + bpup_subs.last_message_time = 0 + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + # Ensure we do not poll to get the state + # since bpup has recovered and we know we + # are back in sync + with patch_bond_device_state(side_effect=Exception): + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 2, "direction": 0}, + } + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + + +async def test_bpup_goes_offline_and_recovers_different_entity( + hass: core.HomeAssistant, +): + """Test that push updates fail and we fallback to polling and then bpup recovers. + + The BPUP recovery is triggered by an update for a different entity which + forces a poll since we need to re-get the state. + """ + bpup_subs = BPUPSubscriptions() + with patch( + "homeassistant.components.bond.BPUPSubscriptions", + return_value=bpup_subs, + ): + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 3, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 + + bpup_subs.last_message_time = 0 + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + bpup_subs.notify( + { + "s": 200, + "t": "bond/not-this-device-id/update", + "b": {"power": 1, "speed": 2, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + with patch_bond_device_state(return_value={"power": 1, "speed": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=430)) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + + +async def test_polling_fails_and_recovers(hass: core.HomeAssistant): + """Test that polling fails and we recover.""" + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + with patch_bond_device_state(return_value={"power": 1, "speed": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 From 67effbc8c40cce4fd8560173141f2fb1d4cdf21c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Mar 2021 10:54:51 -0800 Subject: [PATCH 1112/1818] Config flow to allow marking itself as confirm only (#47607) --- homeassistant/config_entries.py | 7 +++++++ homeassistant/helpers/config_entry_flow.py | 1 + tests/helpers/test_config_entry_flow.py | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index f74acede50729f..c105be42ba3e92 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1012,6 +1012,13 @@ async def async_set_unique_id( return None + @callback + def _set_confirm_only( + self, + ) -> None: + """Mark the config flow as only needing user confirmation to finish flow.""" + self.context["confirm_only"] = True + @callback def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEntry]: """Return current entries. diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 889981537c62fb..69248186b53a8a 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -42,6 +42,7 @@ async def async_step_confirm( ) -> Dict[str, Any]: """Confirm setup.""" if user_input is None: + self._set_confirm_only() return self.async_show_form(step_id="confirm") if self.source == config_entries.SOURCE_USER: diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 874fd5df29addd..b3233556957bfd 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -78,6 +78,15 @@ async def test_user_has_confirmation(hass, discovery_flow_conf): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "confirm" + progress = hass.config_entries.flow.async_progress() + assert len(progress) == 1 + assert progress[0]["flow_id"] == result["flow_id"] + assert progress[0]["context"] == { + "confirm_only": True, + "source": config_entries.SOURCE_USER, + "unique_id": "test", + } + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY From d9bf63103fde44ddd38fb6b9a510d82609802b36 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 9 Mar 2021 03:23:57 +0800 Subject: [PATCH 1113/1818] Fix I-frame interval in stream test video (#47638) --- tests/components/stream/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/stream/common.py b/tests/components/stream/common.py index 5ec4f4217ce3aa..4c6841d03dbc86 100644 --- a/tests/components/stream/common.py +++ b/tests/components/stream/common.py @@ -40,6 +40,7 @@ def generate_audio_frame(pcm_mulaw=False): stream.width = 480 stream.height = 320 stream.pix_fmt = "yuv420p" + stream.options.update({"g": str(fps), "keyint_min": str(fps)}) a_packet = None last_a_dts = -1 From a243adc5518c7c286c2e53a0b684a3ac505a9ec8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Mar 2021 20:30:52 +0100 Subject: [PATCH 1114/1818] Add WS command to get a summary of automation traces (#47557) * Add WS command to get a summary of automation traces * Update tests * Correct rebase mistake, update tests --- .../components/automation/__init__.py | 46 +++++- homeassistant/components/config/automation.py | 16 +- tests/components/config/test_automation.py | 141 +++++++++++++++--- 3 files changed, 178 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 9963c942f08128..8b0fa1687eeb1a 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -303,8 +303,8 @@ def as_dict(self) -> Dict[str, Any]: "condition_trace": condition_traces, "config": self._config, "context": self._context, - "state": self._state, "run_id": self.runid, + "state": self._state, "timestamp": { "start": self._timestamp_start, "finish": self._timestamp_finish, @@ -317,6 +317,37 @@ def as_dict(self) -> Dict[str, Any]: result["error"] = str(self._error) return result + def as_short_dict(self) -> Dict[str, Any]: + """Return a brief dictionary version of this AutomationTrace.""" + + last_action = None + last_condition = None + + if self._action_trace: + last_action = list(self._action_trace.keys())[-1] + if self._condition_trace: + last_condition = list(self._condition_trace.keys())[-1] + + result = { + "last_action": last_action, + "last_condition": last_condition, + "run_id": self.runid, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "trigger": self._trigger.get("description"), + "unique_id": self._unique_id, + } + if self._error is not None: + result["error"] = str(self._error) + if last_action is not None: + result["last_action"] = last_action + result["last_condition"] = last_condition + + return result + class LimitedSizeDict(OrderedDict): """OrderedDict limited in size.""" @@ -857,22 +888,27 @@ def _trigger_extract_entities(trigger_conf: dict) -> List[str]: @callback -def get_debug_traces_for_automation(hass, automation_id): +def get_debug_traces_for_automation(hass, automation_id, summary=False): """Return a serializable list of debug traces for an automation.""" traces = [] for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): - traces.append(trace.as_dict()) + if summary: + traces.append(trace.as_short_dict()) + else: + traces.append(trace.as_dict()) return traces @callback -def get_debug_traces(hass): +def get_debug_traces(hass, summary=False): """Return a serializable list of debug traces.""" traces = {} for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces[automation_id] = get_debug_traces_for_automation(hass, automation_id) + traces[automation_id] = get_debug_traces_for_automation( + hass, automation_id, summary + ) return traces diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 23baa0c8843a5b..708ad55aaeb66e 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -24,7 +24,8 @@ async def async_setup(hass): """Set up the Automation config API.""" - websocket_api.async_register_command(hass, websocket_automation_trace) + websocket_api.async_register_command(hass, websocket_automation_trace_get) + websocket_api.async_register_command(hass, websocket_automation_trace_list) async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" @@ -92,10 +93,10 @@ def _write_value(self, hass, data, config_key, new_value): @websocket_api.websocket_command( - {vol.Required("type"): "automation/trace", vol.Optional("automation_id"): str} + {vol.Required("type"): "automation/trace/get", vol.Optional("automation_id"): str} ) @websocket_api.async_response -async def websocket_automation_trace(hass, connection, msg): +async def websocket_automation_trace_get(hass, connection, msg): """Get automation traces.""" automation_id = msg.get("automation_id") @@ -107,3 +108,12 @@ async def websocket_automation_trace(hass, connection, msg): } connection.send_result(msg["id"], automation_traces) + + +@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) +@websocket_api.async_response +async def websocket_automation_trace_list(hass, connection, msg): + """Summarize automation traces.""" + automation_traces = get_debug_traces(hass, summary=True) + + connection.send_result(msg["id"], automation_traces) diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index cb192befade380..aaf97e22ccdc0f 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -167,7 +167,7 @@ def mock_write(path, data): async def test_get_automation_trace(hass, hass_ws_client): - """Test deleting an automation.""" + """Test tracing an automation.""" id = 1 def next_id(): @@ -209,13 +209,13 @@ def next_id(): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/get"}) response = await client.receive_json() assert response["success"] assert response["result"] == {} await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "sun"} ) response = await client.receive_json() assert response["success"] @@ -226,7 +226,7 @@ def next_id(): await hass.async_block_till_done() # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/get"}) response = await client.receive_json() assert response["success"] assert "moon" not in response["result"] @@ -251,7 +251,7 @@ def next_id(): # Get trace await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} ) response = await client.receive_json() assert response["success"] @@ -279,7 +279,7 @@ def next_id(): # Get trace await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} ) response = await client.receive_json() assert response["success"] @@ -304,7 +304,7 @@ def next_id(): # Get trace await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "moon"} + {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} ) response = await client.receive_json() assert response["success"] @@ -363,25 +363,18 @@ def next_id(): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == {} - await client.send_json( - {"id": next_id(), "type": "automation/trace", "automation_id": "sun"} - ) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {"sun": []} - # Trigger "sun" and "moon" automation once hass.bus.async_fire("test_event") hass.bus.async_fire("test_event2") await hass.async_block_till_done() # Get traces - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] assert len(response["result"]["moon"]) == 1 @@ -393,7 +386,7 @@ def next_id(): hass.bus.async_fire("test_event2") await hass.async_block_till_done() - await client.send_json({"id": next_id(), "type": "automation/trace"}) + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] assert len(response["result"]["moon"]) == automation.STORED_TRACES @@ -403,3 +396,117 @@ def next_id(): int(response["result"]["moon"][-1]["run_id"]) == int(moon_run_id) + automation.STORED_TRACES ) + + +async def test_list_automation_traces(hass, hass_ws_client): + """Test listing automation traces.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert "moon" not in response["result"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 3 + assert len(response["result"]["sun"]) == 1 + trace = response["result"]["sun"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] is None + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + + trace = response["result"]["moon"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][1] + assert trace["last_action"] is None + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][2] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" From 215ab5fd40eefbccd5677d7c8a247e3114da946c Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 8 Mar 2021 22:21:45 +0200 Subject: [PATCH 1115/1818] Add type hints to LightEntity (#47024) --- homeassistant/components/group/light.py | 8 ++++---- homeassistant/components/light/__init__.py | 18 +++++++++--------- homeassistant/components/zwave_js/light.py | 2 +- homeassistant/util/color.py | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 00b7321076f950..5720948bc01c90 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -88,8 +88,8 @@ def __init__(self, name: str, entity_ids: List[str]) -> None: self._brightness: Optional[int] = None self._hs_color: Optional[Tuple[float, float]] = None self._color_temp: Optional[int] = None - self._min_mireds: Optional[int] = 154 - self._max_mireds: Optional[int] = 500 + self._min_mireds: int = 154 + self._max_mireds: int = 500 self._white_value: Optional[int] = None self._effect_list: Optional[List[str]] = None self._effect: Optional[str] = None @@ -152,12 +152,12 @@ def color_temp(self) -> Optional[int]: return self._color_temp @property - def min_mireds(self) -> Optional[int]: + def min_mireds(self) -> int: """Return the coldest color_temp that this light group supports.""" return self._min_mireds @property - def max_mireds(self) -> Optional[int]: + def max_mireds(self) -> int: """Return the warmest color_temp that this light group supports.""" return self._max_mireds diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 55476c754f20ac..4156736f74d3a0 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -407,46 +407,46 @@ class LightEntity(ToggleEntity): """Representation of a light.""" @property - def brightness(self): + def brightness(self) -> Optional[int]: """Return the brightness of this light between 0..255.""" return None @property - def hs_color(self): + def hs_color(self) -> Optional[Tuple[float, float]]: """Return the hue and saturation color value [float, float].""" return None @property - def color_temp(self): + def color_temp(self) -> Optional[int]: """Return the CT color value in mireds.""" return None @property - def min_mireds(self): + def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" # Default to the Philips Hue value that HA has always assumed # https://developers.meethue.com/documentation/core-concepts return 153 @property - def max_mireds(self): + def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" # Default to the Philips Hue value that HA has always assumed # https://developers.meethue.com/documentation/core-concepts return 500 @property - def white_value(self): + def white_value(self) -> Optional[int]: """Return the white value of this light between 0..255.""" return None @property - def effect_list(self): + def effect_list(self) -> Optional[List[str]]: """Return the list of supported effects.""" return None @property - def effect(self): + def effect(self) -> Optional[str]: """Return the current effect.""" return None @@ -495,7 +495,7 @@ def state_attributes(self): return {key: val for key, val in data.items() if val is not None} @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return 0 diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index b501ecb58e7fab..d66821c9ba3797 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -151,7 +151,7 @@ def max_mireds(self) -> int: return self._max_mireds @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag supported features.""" return self._supported_features diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 4a9162707fa1ab..36bf47aaf968db 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -508,12 +508,12 @@ def _get_blue(temperature: float) -> float: return _bound(blue) -def color_temperature_mired_to_kelvin(mired_temperature: float) -> float: +def color_temperature_mired_to_kelvin(mired_temperature: float) -> int: """Convert absolute mired shift to degrees kelvin.""" return math.floor(1000000 / mired_temperature) -def color_temperature_kelvin_to_mired(kelvin_temperature: float) -> float: +def color_temperature_kelvin_to_mired(kelvin_temperature: float) -> int: """Convert degrees kelvin to mired shift.""" return math.floor(1000000 / kelvin_temperature) From 665e2c34730d4c4c78bd742446542b41d6f99838 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 8 Mar 2021 21:53:44 +0100 Subject: [PATCH 1116/1818] Ensure bond light follows proper typing (#47641) --- homeassistant/components/bond/light.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index d5f7cb29207dc0..d2b06012ed342f 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -95,7 +95,7 @@ def is_on(self) -> bool: return self._light == 1 @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag supported features.""" return 0 @@ -119,7 +119,7 @@ def _apply_state(self, state: dict) -> None: self._brightness = state.get("brightness") @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag supported features.""" if self._device.supports_set_brightness(): return SUPPORT_BRIGHTNESS @@ -203,7 +203,7 @@ def _apply_state(self, state: dict) -> None: self._flame = state.get("flame") @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int: """Flag brightness as supported feature to represent flame level.""" return SUPPORT_BRIGHTNESS From ea4f3e31d58f8473b7af6bfd85d51196ac66e451 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 8 Mar 2021 22:48:36 +0100 Subject: [PATCH 1117/1818] Include changed variables in automation trace (#47549) * Include changed variables in automation trace * Deduplicate some code * Tweak * Apply suggestions from code review Co-authored-by: Paulus Schoutsen * Fix format Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/script.py | 4 ++-- homeassistant/helpers/trace.py | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 8ee9b12bc1b565..b3fcffd4944418 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -120,7 +120,7 @@ _SHUTDOWN_MAX_WAIT = 60 -ACTION_TRACE_NODE_MAX_LEN = 20 # Max the length of a trace node for repeated actions +ACTION_TRACE_NODE_MAX_LEN = 20 # Max length of a trace node for repeated actions def action_trace_append(variables, path): @@ -294,7 +294,7 @@ async def async_run(self) -> None: self._finish() async def _async_step(self, log_exceptions): - with trace_path(str(self._step)), trace_action(None): + with trace_path(str(self._step)), trace_action(self._variables): try: handler = f"_async_{cv.determine_script_action(self._action)}_step" await getattr(self, handler)() diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 8c5c181ea2482d..0c1969a8ac6d8d 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -16,7 +16,17 @@ def __init__(self, variables: TemplateVarsType): self._error: Optional[Exception] = None self._result: Optional[dict] = None self._timestamp = dt_util.utcnow() - self._variables = variables + + if variables is None: + variables = {} + last_variables = variables_cv.get() or {} + variables_cv.set(dict(variables)) + changed_variables = { + key: value + for key, value in variables.items() + if key not in last_variables or last_variables[key] != value + } + self._variables = changed_variables def __repr__(self) -> str: """Container for trace data.""" @@ -33,8 +43,8 @@ def set_result(self, **kwargs: Any) -> None: def as_dict(self) -> Dict[str, Any]: """Return dictionary version of this TraceElement.""" result: Dict[str, Any] = {"timestamp": self._timestamp} - # Commented out because we get too many copies of the same data - # result["variables"] = self._variables + if self._variables: + result["changed_variables"] = self._variables if self._error is not None: result["error"] = str(self._error) if self._result is not None: @@ -55,6 +65,8 @@ def as_dict(self) -> Dict[str, Any]: trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( "trace_path_stack_cv", default=None ) +# Copy of last variables +variables_cv: ContextVar[Optional[Any]] = ContextVar("variables_cv", default=None) def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: @@ -128,6 +140,7 @@ def trace_clear() -> None: trace_cv.set({}) trace_stack_cv.set(None) trace_path_stack_cv.set(None) + variables_cv.set(None) def trace_set_result(**kwargs: Any) -> None: From 520c4a8ee34fc050cbf6f49731a93c4521921577 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 8 Mar 2021 22:49:54 +0100 Subject: [PATCH 1118/1818] Update attrs to 20.3.0 (#47642) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 46dc2d027815f3..d9d8618f78e434 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -5,7 +5,7 @@ aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 async_timeout==3.0.1 -attrs==19.3.0 +attrs==20.3.0 awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 diff --git a/requirements.txt b/requirements.txt index 0a5b754dbfc286..6f6640764a60cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ aiohttp==3.7.4 astral==1.10.1 async_timeout==3.0.1 -attrs==19.3.0 +attrs==20.3.0 awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 diff --git a/setup.py b/setup.py index ce7d6b6883d5e6..04a334cfd210aa 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ "aiohttp==3.7.4", "astral==1.10.1", "async_timeout==3.0.1", - "attrs==19.3.0", + "attrs==20.3.0", "awesomeversion==21.2.3", "bcrypt==3.1.7", "certifi>=2020.12.5", From ee257234689c682fa674528cc0c23c0bebda320b Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 8 Mar 2021 21:56:24 +0000 Subject: [PATCH 1119/1818] Add option to reverse switch behaviour in KMTronic (#47532) --- homeassistant/components/kmtronic/__init__.py | 13 +++- .../components/kmtronic/config_flow.py | 41 ++++++++++- homeassistant/components/kmtronic/const.py | 5 +- .../components/kmtronic/strings.json | 9 +++ homeassistant/components/kmtronic/switch.py | 32 ++++----- .../components/kmtronic/translations/en.json | 12 +++- tests/components/kmtronic/test_config_flow.py | 70 +++++++++++-------- tests/components/kmtronic/test_init.py | 62 ++++++++++++++++ tests/components/kmtronic/test_switch.py | 67 +++++++++++++----- 9 files changed, 240 insertions(+), 71 deletions(-) create mode 100644 tests/components/kmtronic/test_init.py diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index 0ac4ea8cb59668..2903eb926e5a0b 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -15,7 +15,7 @@ from homeassistant.helpers import aiohttp_client from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN, MANUFACTURER +from .const import DATA_COORDINATOR, DATA_HUB, DOMAIN, MANUFACTURER, UPDATE_LISTENER CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -67,7 +67,6 @@ async def async_update_data(): hass.data[DOMAIN][entry.entry_id] = { DATA_HUB: hub, - DATA_HOST: entry.data[DATA_HOST], DATA_COORDINATOR: coordinator, } @@ -76,9 +75,17 @@ async def async_update_data(): hass.config_entries.async_forward_entry_setup(entry, platform) ) + update_listener = entry.add_update_listener(async_update_options) + hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER] = update_listener + return True +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Update options.""" + await hass.config_entries.async_reload(config_entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" unload_ok = all( @@ -90,6 +97,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) if unload_ok: + update_listener = hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER] + update_listener() hass.data[DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py index 841c541dd4ebcd..736e6a46c11e95 100644 --- a/homeassistant/components/kmtronic/config_flow.py +++ b/homeassistant/components/kmtronic/config_flow.py @@ -8,13 +8,21 @@ from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback from homeassistant.helpers import aiohttp_client +from .const import CONF_REVERSE from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({CONF_HOST: str, CONF_USERNAME: str, CONF_PASSWORD: str}) +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } +) async def validate_input(hass: core.HomeAssistant, data): @@ -44,6 +52,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return KMTronicOptionsFlow(config_entry) + async def async_step_user(self, user_input=None): """Handle the initial step.""" errors = {} @@ -71,3 +85,28 @@ class CannotConnect(exceptions.HomeAssistantError): class InvalidAuth(exceptions.HomeAssistantError): """Error to indicate there is invalid auth.""" + + +class KMTronicOptionsFlow(config_entries.OptionsFlow): + """Handle options.""" + + def __init__(self, config_entry): + """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) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_REVERSE, + default=self.config_entry.options.get(CONF_REVERSE), + ): bool, + } + ), + ) diff --git a/homeassistant/components/kmtronic/const.py b/homeassistant/components/kmtronic/const.py index 8ca37a0b797ae7..8b34d423724392 100644 --- a/homeassistant/components/kmtronic/const.py +++ b/homeassistant/components/kmtronic/const.py @@ -2,10 +2,13 @@ DOMAIN = "kmtronic" +CONF_REVERSE = "reverse" + DATA_HUB = "hub" -DATA_HOST = "host" DATA_COORDINATOR = "coordinator" MANUFACTURER = "KMtronic" ATTR_MANUFACTURER = "manufacturer" ATTR_IDENTIFIERS = "identifiers" + +UPDATE_LISTENER = "update_listener" diff --git a/homeassistant/components/kmtronic/strings.json b/homeassistant/components/kmtronic/strings.json index 7becb830d91dba..2aaa0d2f8dddec 100644 --- a/homeassistant/components/kmtronic/strings.json +++ b/homeassistant/components/kmtronic/strings.json @@ -17,5 +17,14 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Reverse switch logic (use NC)" + } + } + } } } diff --git a/homeassistant/components/kmtronic/switch.py b/homeassistant/components/kmtronic/switch.py index 5970ec20cb8f3c..d37cd54ce1a00c 100644 --- a/homeassistant/components/kmtronic/switch.py +++ b/homeassistant/components/kmtronic/switch.py @@ -3,19 +3,19 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DATA_COORDINATOR, DATA_HOST, DATA_HUB, DOMAIN +from .const import CONF_REVERSE, DATA_COORDINATOR, DATA_HUB, DOMAIN async def async_setup_entry(hass, entry, async_add_entities): """Config entry example.""" coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] hub = hass.data[DOMAIN][entry.entry_id][DATA_HUB] - host = hass.data[DOMAIN][entry.entry_id][DATA_HOST] + reverse = entry.options.get(CONF_REVERSE, False) await hub.async_get_relays() async_add_entities( [ - KMtronicSwitch(coordinator, host, relay, entry.unique_id) + KMtronicSwitch(coordinator, relay, reverse, entry.entry_id) for relay in hub.relays ] ) @@ -24,17 +24,12 @@ async def async_setup_entry(hass, entry, async_add_entities): class KMtronicSwitch(CoordinatorEntity, SwitchEntity): """KMtronic Switch Entity.""" - def __init__(self, coordinator, host, relay, config_entry_id): + def __init__(self, coordinator, relay, reverse, config_entry_id): """Pass coordinator to CoordinatorEntity.""" super().__init__(coordinator) - self._host = host self._relay = relay self._config_entry_id = config_entry_id - - @property - def available(self) -> bool: - """Return whether the entity is available.""" - return self.coordinator.last_update_success + self._reverse = reverse @property def name(self) -> str: @@ -46,22 +41,25 @@ def unique_id(self) -> str: """Return the unique ID of the entity.""" return f"{self._config_entry_id}_relay{self._relay.id}" - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return True - @property def is_on(self): """Return entity state.""" + if self._reverse: + return not self._relay.is_on return self._relay.is_on async def async_turn_on(self, **kwargs) -> None: """Turn the switch on.""" - await self._relay.turn_on() + if self._reverse: + await self._relay.turn_off() + else: + await self._relay.turn_on() self.async_write_ha_state() async def async_turn_off(self, **kwargs) -> None: """Turn the switch off.""" - await self._relay.turn_off() + if self._reverse: + await self._relay.turn_on() + else: + await self._relay.turn_off() self.async_write_ha_state() diff --git a/homeassistant/components/kmtronic/translations/en.json b/homeassistant/components/kmtronic/translations/en.json index f15fe84c3ed6e9..0a1bde9fb196da 100644 --- a/homeassistant/components/kmtronic/translations/en.json +++ b/homeassistant/components/kmtronic/translations/en.json @@ -13,7 +13,17 @@ "data": { "host": "Host", "password": "Password", - "username": "Username" + "username": "Username", + "reverse": "Reverse switch logic (use NC)" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Reverse switch logic (use NC)" } } } diff --git a/tests/components/kmtronic/test_config_flow.py b/tests/components/kmtronic/test_config_flow.py index ebbbf626451fc7..b5ebdc79c8ba44 100644 --- a/tests/components/kmtronic/test_config_flow.py +++ b/tests/components/kmtronic/test_config_flow.py @@ -3,9 +3,9 @@ from aiohttp import ClientConnectorError, ClientResponseError -from homeassistant import config_entries, setup -from homeassistant.components.kmtronic.const import DOMAIN -from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.kmtronic.const import CONF_REVERSE, DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED from tests.common import MockConfigEntry @@ -49,6 +49,43 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_options(hass, aioclient_mock): + """Test that the options form.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "1.1.1.1", + "username": "admin", + "password": "admin", + }, + ) + config_entry.add_to_hass(hass) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_LOADED + + 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_REVERSE: True} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == {CONF_REVERSE: True} + + await hass.async_block_till_done() + + assert config_entry.state == "loaded" + + async def test_form_invalid_auth(hass): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -116,30 +153,3 @@ async def test_form_unknown_error(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "unknown"} - - -async def test_unload_config_entry(hass, aioclient_mock): - """Test entry unloading.""" - - config_entry = MockConfigEntry( - domain=DOMAIN, - data={"host": "1.1.1.1", "username": "admin", "password": "admin"}, - ) - config_entry.add_to_hass(hass) - - aioclient_mock.get( - "http://1.1.1.1/status.xml", - text="00", - ) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - config_entries = hass.config_entries.async_entries(DOMAIN) - assert len(config_entries) == 1 - assert config_entries[0] is config_entry - assert config_entry.state == ENTRY_STATE_LOADED - - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/kmtronic/test_init.py b/tests/components/kmtronic/test_init.py new file mode 100644 index 00000000000000..1b9cf7cb40713a --- /dev/null +++ b/tests/components/kmtronic/test_init.py @@ -0,0 +1,62 @@ +"""The tests for the KMtronic component.""" +import asyncio + +from homeassistant.components.kmtronic.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_RETRY, +) + +from tests.common import MockConfigEntry + + +async def test_unload_config_entry(hass, aioclient_mock): + """Test entry unloading.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "1.1.1.1", + "username": "admin", + "password": "admin", + }, + ) + config_entry.add_to_hass(hass) + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_NOT_LOADED + + +async def test_config_entry_not_ready(hass, aioclient_mock): + """Tests configuration entry not ready.""" + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + exc=asyncio.TimeoutError(), + ) + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + "host": "1.1.1.1", + "username": "foo", + "password": "bar", + }, + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ENTRY_STATE_SETUP_RETRY diff --git a/tests/components/kmtronic/test_switch.py b/tests/components/kmtronic/test_switch.py index 5eec3537176756..df8ecda2c2ec95 100644 --- a/tests/components/kmtronic/test_switch.py +++ b/tests/components/kmtronic/test_switch.py @@ -3,7 +3,6 @@ from datetime import timedelta from homeassistant.components.kmtronic.const import DOMAIN -from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from homeassistant.const import STATE_UNAVAILABLE from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -88,24 +87,6 @@ async def test_update(hass, aioclient_mock): assert state.state == "on" -async def test_config_entry_not_ready(hass, aioclient_mock): - """Tests configuration entry not ready.""" - - aioclient_mock.get( - "http://1.1.1.1/status.xml", - exc=asyncio.TimeoutError(), - ) - - config_entry = MockConfigEntry( - domain=DOMAIN, data={"host": "1.1.1.1", "username": "foo", "password": "bar"} - ) - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.state == ENTRY_STATE_SETUP_RETRY - - async def test_failed_update(hass, aioclient_mock): """Tests coordinator update fails.""" now = dt_util.utcnow() @@ -148,3 +129,51 @@ async def test_failed_update(hass, aioclient_mock): await hass.async_block_till_done() state = hass.states.get("switch.relay1") assert state.state == STATE_UNAVAILABLE + + +async def test_relay_on_off_reversed(hass, aioclient_mock): + """Tests the relay turns on correctly when configured as reverse.""" + + aioclient_mock.get( + "http://1.1.1.1/status.xml", + text="00", + ) + + MockConfigEntry( + domain=DOMAIN, + data={"host": "1.1.1.1", "username": "foo", "password": "bar"}, + options={"reverse": True}, + ).add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + # Mocks the response for turning a relay1 off + aioclient_mock.get( + "http://1.1.1.1/FF0101", + text="", + ) + + state = hass.states.get("switch.relay1") + assert state.state == "on" + + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.relay1"}, blocking=True + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "off" + + # Mocks the response for turning a relay1 off + aioclient_mock.get( + "http://1.1.1.1/FF0100", + text="", + ) + + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.relay1"}, blocking=True + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.relay1") + assert state.state == "on" From fbf8b68488817b8a5ad80a78f5a5d372072744a8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 23:13:18 +0100 Subject: [PATCH 1120/1818] Upgrade sentry-sdk to 1.0.0 (#47626) --- 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 d0592493f15633..04735d98687493 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,6 +3,6 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==0.20.3"], + "requirements": ["sentry-sdk==1.0.0"], "codeowners": ["@dcramer", "@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0028e6e343ae10..e16a101ce808e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2023,7 +2023,7 @@ sense-hat==2.2.0 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.3 +sentry-sdk==1.0.0 # homeassistant.components.sharkiq sharkiqpy==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6ccb68983936fe..94f1bbba4649f1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1039,7 +1039,7 @@ scapy==2.4.4 sense_energy==0.9.0 # homeassistant.components.sentry -sentry-sdk==0.20.3 +sentry-sdk==1.0.0 # homeassistant.components.sharkiq sharkiqpy==0.1.8 From efe415f2252e8d53400f87647bb1274f15265502 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Mar 2021 23:18:55 +0100 Subject: [PATCH 1121/1818] Upgrade aiohttp to 3.7.4.post0 (#47627) --- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d9d8618f78e434..bcbe614ca2056b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ PyJWT==1.7.1 PyNaCl==1.3.0 -aiohttp==3.7.4 +aiohttp==3.7.4.post0 aiohttp_cors==0.7.0 astral==1.10.1 async-upnp-client==0.14.13 diff --git a/requirements.txt b/requirements.txt index 6f6640764a60cc..dafb5686e4a0a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -c homeassistant/package_constraints.txt # Home Assistant Core -aiohttp==3.7.4 +aiohttp==3.7.4.post0 astral==1.10.1 async_timeout==3.0.1 attrs==20.3.0 diff --git a/setup.py b/setup.py index 04a334cfd210aa..d3a8a5aea7070b 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ PACKAGES = find_packages(exclude=["tests", "tests.*"]) REQUIRES = [ - "aiohttp==3.7.4", + "aiohttp==3.7.4.post0", "astral==1.10.1", "async_timeout==3.0.1", "attrs==20.3.0", From 6af754a7d3c2e69b6fc3cf4a78cc8fb4b8a5bf1e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 12:19:05 -1000 Subject: [PATCH 1122/1818] Fix turning off scene in homekit (#47604) --- homeassistant/components/homekit/type_switches.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 1ce6c364896ef5..8ea19897420b82 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -27,7 +27,7 @@ STATE_ON, ) from homeassistant.core import callback, split_entity_id -from homeassistant.helpers.event import call_later +from homeassistant.helpers.event import async_call_later from .accessories import TYPES, HomeAccessory from .const import ( @@ -134,7 +134,7 @@ def set_state(self, value): self.async_call_service(self._domain, service, params) if self.activate_only: - call_later(self.hass, 1, self.reset_switch) + async_call_later(self.hass, 1, self.reset_switch) @callback def async_update_state(self, new_state): From d9ffb65898e9f9796d82f02aabb370dae014955c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 12:20:21 -1000 Subject: [PATCH 1123/1818] Fix insteon fan speeds (#47603) --- homeassistant/components/insteon/fan.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index cef19f57a9f96e..596c254dc6573d 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -1,8 +1,6 @@ """Support for INSTEON fans via PowerLinc Modem.""" import math -from pyinsteon.constants import FanSpeed - from homeassistant.components.fan import ( DOMAIN as FAN_DOMAIN, SUPPORT_SET_SPEED, @@ -19,7 +17,7 @@ from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (1, FanSpeed.HIGH) # off is not included +SPEED_RANGE = (0x00, 0xFF) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): @@ -52,6 +50,11 @@ def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED + @property + def speed_count(self) -> int: + """Flag supported features.""" + return 3 + async def async_turn_on( self, speed: str = None, @@ -60,9 +63,7 @@ async def async_turn_on( **kwargs, ) -> None: """Turn on the fan.""" - if percentage is None: - percentage = 50 - await self.async_set_percentage(percentage) + await self.async_set_percentage(percentage or 67) async def async_turn_off(self, **kwargs) -> None: """Turn off the fan.""" @@ -71,7 +72,7 @@ async def async_turn_off(self, **kwargs) -> None: async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" if percentage == 0: - await self._insteon_device.async_fan_off() - else: - on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) - await self._insteon_device.async_fan_on(on_level=on_level) + await self.async_turn_off() + return + on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + await self._insteon_device.async_on(group=2, on_level=on_level) From 797ee81fc9b8f4d0ef6314df6f45b98b5507f3c4 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:11:54 -0500 Subject: [PATCH 1124/1818] Update zwave_js supported features list to be static (#47623) --- homeassistant/components/zwave_js/climate.py | 22 +++++++++-------- tests/components/zwave_js/test_climate.py | 26 +++++++++++++++++++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 325cf14b379df4..26e9e730283efe 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -162,6 +162,15 @@ def __init__( add_to_watched_value_ids=True, ) self._set_modes_and_presets() + self._supported_features = SUPPORT_PRESET_MODE + # If any setpoint value exists, we can assume temperature + # can be set + if any(self._setpoint_values.values()): + self._supported_features |= SUPPORT_TARGET_TEMPERATURE + if HVAC_MODE_HEAT_COOL in self.hvac_modes: + self._supported_features |= SUPPORT_TARGET_TEMPERATURE_RANGE + if self._fan_mode: + self._supported_features |= SUPPORT_FAN_MODE def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: """Optionally return a ZwaveValue for a setpoint.""" @@ -259,7 +268,7 @@ def target_temperature(self) -> Optional[float]: return None try: temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) - except ValueError: + except (IndexError, ValueError): return None return temp.value if temp else None @@ -271,7 +280,7 @@ def target_temperature_high(self) -> Optional[float]: return None try: temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) - except ValueError: + except (IndexError, ValueError): return None return temp.value if temp else None @@ -335,14 +344,7 @@ def device_state_attributes(self) -> Optional[Dict[str, str]]: @property def supported_features(self) -> int: """Return the list of supported features.""" - support = SUPPORT_PRESET_MODE - if len(self._current_mode_setpoint_enums) == 1: - support |= SUPPORT_TARGET_TEMPERATURE - if len(self._current_mode_setpoint_enums) > 1: - support |= SUPPORT_TARGET_TEMPERATURE_RANGE - if self._fan_mode: - support |= SUPPORT_FAN_MODE - return support + return self._supported_features async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index fe3e0708acc409..a31aad19603aca 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -24,9 +24,17 @@ SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE -from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, @@ -58,6 +66,13 @@ async def test_thermostat_v2( assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE assert state.attributes[ATTR_FAN_MODE] == "Auto low" assert state.attributes[ATTR_FAN_STATE] == "Idle / off" + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE + | SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + ) # Test setting preset mode await hass.services.async_call( @@ -408,6 +423,10 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) client.async_send_command_no_wait.reset_mock() @@ -491,6 +510,10 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): @@ -507,3 +530,4 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati HVAC_MODE_HEAT, ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_PRESET_MODE From 10eca5b98684508d19abac54f19ca1325ba21c33 Mon Sep 17 00:00:00 2001 From: unaiur Date: Tue, 9 Mar 2021 00:14:24 +0100 Subject: [PATCH 1125/1818] Fix maxcube thermostat transition from off to heat mode (#47643) Transition from HVAC_MODE_OFF to HVAC_MODE_HEAT are not executed because target temperature is kept at OFF_TEMPERATURE, turning it into a no-op. This change ensures that we increase the target temperature to at least the minimum temperature when transitioning to HVAC_MODE_HEAT mode. --- homeassistant/components/maxcube/climate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index c17cc988c1d9e3..5db4cc1e7bd2f8 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -107,7 +107,9 @@ def min_temp(self): device = self._cubehandle.cube.device_by_rf(self._rf_address) if device.min_temperature is None: return MIN_TEMPERATURE - return device.min_temperature + # OFF_TEMPERATURE (always off) a is valid temperature to maxcube but not to Home Assistant. + # We use HVAC_MODE_OFF instead to represent a turned off thermostat. + return max(device.min_temperature, MIN_TEMPERATURE) @property def max_temp(self): @@ -155,7 +157,9 @@ def set_hvac_mode(self, hvac_mode: str): if hvac_mode == HVAC_MODE_OFF: temp = OFF_TEMPERATURE - elif hvac_mode != HVAC_MODE_HEAT: + elif hvac_mode == HVAC_MODE_HEAT: + temp = max(temp, self.min_temp) + else: # Reset the temperature to a sane value. # Ideally, we should send 0 and the device will set its # temperature according to the schedule. However, current From 1dd35ff059ecd9cfa73591c104f43bfbc6451fad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 13:15:22 -1000 Subject: [PATCH 1126/1818] Catch dhcp setup permission errors sooner (#47639) This solves an unexpected thread exception on macs when running as a user intead of root --- homeassistant/components/dhcp/__init__.py | 18 ++++++++++-------- tests/components/dhcp/test_init.py | 10 ++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index d33c6159888fa7..304eea24fd42b3 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -207,8 +207,11 @@ def _stop(self): async def async_start(self): """Start watching for dhcp packets.""" + # disable scapy promiscuous mode as we do not need it + conf.sniff_promisc = 0 + try: - _verify_l2socket_creation_permission() + await self.hass.async_add_executor_job(_verify_l2socket_setup, FILTER) except (Scapy_Exception, OSError) as ex: if os.geteuid() == 0: _LOGGER.error("Cannot watch for dhcp packets: %s", ex) @@ -219,7 +222,7 @@ async def async_start(self): return try: - await _async_verify_working_pcap(self.hass, FILTER) + await self.hass.async_add_executor_job(_verify_working_pcap, FILTER) except (Scapy_Exception, ImportError) as ex: _LOGGER.error( "Cannot watch for dhcp packets without a functional packet filter: %s", @@ -233,6 +236,7 @@ async def async_start(self): prn=self.handle_dhcp_packet, store=0, ) + self._sniffer.start() def handle_dhcp_packet(self, packet): @@ -283,7 +287,7 @@ def _format_mac(mac_address): return format_mac(mac_address).replace(":", "") -def _verify_l2socket_creation_permission(): +def _verify_l2socket_setup(cap_filter): """Create a socket using the scapy configured l2socket. Try to create the socket @@ -292,15 +296,13 @@ def _verify_l2socket_creation_permission(): thread so we will not be able to capture any permission or bind errors. """ - # disable scapy promiscuous mode as we do not need it - conf.sniff_promisc = 0 - conf.L2socket() + conf.L2socket(filter=cap_filter) -async def _async_verify_working_pcap(hass, cap_filter): +def _verify_working_pcap(cap_filter): """Verify we can create a packet filter. If we cannot create a filter we will be listening for all traffic which is too intensive. """ - await hass.async_add_executor_job(compile_filter, cap_filter) + compile_filter(cap_filter) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index fc24c8201e26b1..f5cc5f1728de37 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -281,7 +281,7 @@ async def test_setup_and_stop(hass): await hass.async_block_till_done() with patch("homeassistant.components.dhcp.AsyncSniffer.start") as start_call, patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", + "homeassistant.components.dhcp._verify_l2socket_setup", ), patch( "homeassistant.components.dhcp.compile_filter", ): @@ -307,7 +307,7 @@ async def test_setup_fails_as_root(hass, caplog): wait_event = threading.Event() with patch("os.geteuid", return_value=0), patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", + "homeassistant.components.dhcp._verify_l2socket_setup", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -330,7 +330,7 @@ async def test_setup_fails_non_root(hass, caplog): await hass.async_block_till_done() with patch("os.geteuid", return_value=10), patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", + "homeassistant.components.dhcp._verify_l2socket_setup", side_effect=Scapy_Exception, ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -351,9 +351,7 @@ async def test_setup_fails_with_broken_libpcap(hass, caplog): ) await hass.async_block_till_done() - with patch( - "homeassistant.components.dhcp._verify_l2socket_creation_permission", - ), patch( + with patch("homeassistant.components.dhcp._verify_l2socket_setup",), patch( "homeassistant.components.dhcp.compile_filter", side_effect=ImportError, ) as compile_filter, patch( From 3b05a12e62139fc992f905f07943facfc8e51b86 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sat, 6 Mar 2021 09:28:33 -0700 Subject: [PATCH 1127/1818] Adjust litterrobot tests and code to match guidelines (#47060) * Use SwitchEntity instead of ToggleEntity and adjust test patches as recommended * Move async_create_entry out of try block in config_flow * Patch pypi package instead of HA code * Bump pylitterbot to 2021.2.6, fix tests, and implement other code review suggestions * Bump pylitterbot to 2021.2.8, remove sleep mode start/end time from vacuum, adjust and add sensors for sleep mode start/end time * Move icon helper back to Litter-Robot component and isoformat times on time sensors --- .../components/litterrobot/config_flow.py | 8 +- homeassistant/components/litterrobot/hub.py | 21 ++--- .../components/litterrobot/manifest.json | 2 +- .../components/litterrobot/sensor.py | 93 +++++++++++++------ .../components/litterrobot/switch.py | 6 +- .../components/litterrobot/vacuum.py | 37 ++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/litterrobot/conftest.py | 76 +++++++++------ .../litterrobot/test_config_flow.py | 33 +++++-- tests/components/litterrobot/test_init.py | 38 +++++++- tests/components/litterrobot/test_sensor.py | 57 ++++++++++-- tests/components/litterrobot/test_switch.py | 14 +-- tests/components/litterrobot/test_vacuum.py | 32 +++++-- 14 files changed, 278 insertions(+), 143 deletions(-) diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index d6c92d8dad64b3..36fc2064abbc33 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -35,9 +35,6 @@ async def async_step_user(self, user_input=None): hub = LitterRobotHub(self.hass, user_input) try: await hub.login() - return self.async_create_entry( - title=user_input[CONF_USERNAME], data=user_input - ) except LitterRobotLoginException: errors["base"] = "invalid_auth" except LitterRobotException: @@ -46,6 +43,11 @@ async def async_step_user(self, user_input=None): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" + if not errors: + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 0d0559140c7431..943ef5bfe37a6c 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -4,7 +4,7 @@ from types import MethodType from typing import Any, Optional -from pylitterbot import Account, Robot +import pylitterbot from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -49,7 +49,7 @@ async def _async_update_data(): async def login(self, load_robots: bool = False): """Login to Litter-Robot.""" self.logged_in = False - self.account = Account() + self.account = pylitterbot.Account() try: await self.account.connect( username=self._data[CONF_USERNAME], @@ -69,11 +69,11 @@ async def login(self, load_robots: bool = False): class LitterRobotEntity(CoordinatorEntity): """Generic Litter-Robot entity representing common data and methods.""" - def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub): + def __init__(self, robot: pylitterbot.Robot, entity_type: str, hub: LitterRobotHub): """Pass coordinator to CoordinatorEntity.""" super().__init__(hub.coordinator) self.robot = robot - self.entity_type = entity_type if entity_type else "" + self.entity_type = entity_type self.hub = hub @property @@ -89,22 +89,21 @@ def unique_id(self): @property def device_info(self): """Return the device information for a Litter-Robot.""" - model = "Litter-Robot 3 Connect" - if not self.robot.serial.startswith("LR3C"): - model = "Other Litter-Robot Connected Device" return { "identifiers": {(DOMAIN, self.robot.serial)}, "name": self.robot.name, "manufacturer": "Litter-Robot", - "model": model, + "model": self.robot.model, } async def perform_action_and_refresh(self, action: MethodType, *args: Any): """Perform an action and initiates a refresh of the robot data after a few seconds.""" + + async def async_call_later_callback(*_) -> None: + await self.hub.coordinator.async_request_refresh() + await action(*args) - async_call_later( - self.hass, REFRESH_WAIT_TIME, self.hub.coordinator.async_request_refresh - ) + async_call_later(self.hass, REFRESH_WAIT_TIME, async_call_later_callback) @staticmethod def parse_time_at_default_timezone(time_str: str) -> Optional[time]: diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index 1c6ac7274bf657..8fa7ab8dcb5404 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -3,6 +3,6 @@ "name": "Litter-Robot", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/litterrobot", - "requirements": ["pylitterbot==2021.2.5"], + "requirements": ["pylitterbot==2021.2.8"], "codeowners": ["@natekspencer"] } diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 2843660bceecb3..8900c6c54ca80b 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,32 +1,44 @@ """Support for Litter-Robot sensors.""" -from homeassistant.const import PERCENTAGE +from typing import Optional + +from pylitterbot.robot import Robot + +from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity from .const import DOMAIN -from .hub import LitterRobotEntity - -WASTE_DRAWER = "Waste Drawer" +from .hub import LitterRobotEntity, LitterRobotHub -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up Litter-Robot sensors using config entry.""" - hub = hass.data[DOMAIN][config_entry.entry_id] - - entities = [] - for robot in hub.account.robots: - entities.append(LitterRobotSensor(robot, WASTE_DRAWER, hub)) +def icon_for_gauge_level(gauge_level: Optional[int] = None, offset: int = 0) -> str: + """Return a gauge icon valid identifier.""" + if gauge_level is None or gauge_level <= 0 + offset: + return "mdi:gauge-empty" + if gauge_level > 70 + offset: + return "mdi:gauge-full" + if gauge_level > 30 + offset: + return "mdi:gauge" + return "mdi:gauge-low" - if entities: - async_add_entities(entities, True) +class LitterRobotPropertySensor(LitterRobotEntity, Entity): + """Litter-Robot property sensors.""" -class LitterRobotSensor(LitterRobotEntity, Entity): - """Litter-Robot sensors.""" + def __init__( + self, robot: Robot, entity_type: str, hub: LitterRobotHub, sensor_attribute: str + ): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(robot, entity_type, hub) + self.sensor_attribute = sensor_attribute @property def state(self): """Return the state.""" - return self.robot.waste_drawer_gauge + return getattr(self.robot, self.sensor_attribute) + + +class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity): + """Litter-Robot sensors.""" @property def unit_of_measurement(self): @@ -36,19 +48,40 @@ def unit_of_measurement(self): @property def icon(self): """Return the icon to use in the frontend, if any.""" - if self.robot.waste_drawer_gauge <= 10: - return "mdi:gauge-empty" - if self.robot.waste_drawer_gauge < 50: - return "mdi:gauge-low" - if self.robot.waste_drawer_gauge <= 90: - return "mdi:gauge" - return "mdi:gauge-full" + return icon_for_gauge_level(self.state, 10) + + +class LitterRobotSleepTimeSensor(LitterRobotPropertySensor, Entity): + """Litter-Robot sleep time sensors.""" + + @property + def state(self): + """Return the state.""" + if self.robot.sleep_mode_active: + return super().state.isoformat() + return None @property - def device_state_attributes(self): - """Return device specific state attributes.""" - return { - "cycle_count": self.robot.cycle_count, - "cycle_capacity": self.robot.cycle_capacity, - "cycles_after_drawer_full": self.robot.cycles_after_drawer_full, - } + def device_class(self): + """Return the device class, if any.""" + return DEVICE_CLASS_TIMESTAMP + + +ROBOT_SENSORS = [ + (LitterRobotWasteSensor, "Waste Drawer", "waste_drawer_gauge"), + (LitterRobotSleepTimeSensor, "Sleep Mode Start Time", "sleep_mode_start_time"), + (LitterRobotSleepTimeSensor, "Sleep Mode End Time", "sleep_mode_end_time"), +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Litter-Robot sensors using config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + entities = [] + for robot in hub.account.robots: + for (sensor_class, entity_type, sensor_attribute) in ROBOT_SENSORS: + entities.append(sensor_class(robot, entity_type, hub, sensor_attribute)) + + if entities: + async_add_entities(entities, True) diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py index b94b29a35e1f59..9164cc35e900af 100644 --- a/homeassistant/components/litterrobot/switch.py +++ b/homeassistant/components/litterrobot/switch.py @@ -1,11 +1,11 @@ """Support for Litter-Robot switches.""" -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.switch import SwitchEntity from .const import DOMAIN from .hub import LitterRobotEntity -class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity): +class LitterRobotNightLightModeSwitch(LitterRobotEntity, SwitchEntity): """Litter-Robot Night Light Mode Switch.""" @property @@ -27,7 +27,7 @@ async def async_turn_off(self, **kwargs): await self.perform_action_and_refresh(self.robot.set_night_light, False) -class LitterRobotPanelLockoutSwitch(LitterRobotEntity, ToggleEntity): +class LitterRobotPanelLockoutSwitch(LitterRobotEntity, SwitchEntity): """Litter-Robot Panel Lockout Switch.""" @property diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 6ee92993869b69..4fe76d446f4779 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -14,7 +14,6 @@ VacuumEntity, ) from homeassistant.const import STATE_OFF -import homeassistant.util.dt as dt_util from .const import DOMAIN from .hub import LitterRobotEntity @@ -54,27 +53,22 @@ def supported_features(self): def state(self): """Return the state of the cleaner.""" switcher = { - Robot.UnitStatus.CCP: STATE_CLEANING, - Robot.UnitStatus.EC: STATE_CLEANING, - Robot.UnitStatus.CCC: STATE_DOCKED, - Robot.UnitStatus.CST: STATE_DOCKED, - Robot.UnitStatus.DF1: STATE_DOCKED, - Robot.UnitStatus.DF2: STATE_DOCKED, - Robot.UnitStatus.RDY: STATE_DOCKED, + Robot.UnitStatus.CLEAN_CYCLE: STATE_CLEANING, + Robot.UnitStatus.EMPTY_CYCLE: STATE_CLEANING, + Robot.UnitStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED, + Robot.UnitStatus.CAT_SENSOR_TIMING: STATE_DOCKED, + Robot.UnitStatus.DRAWER_FULL_1: STATE_DOCKED, + Robot.UnitStatus.DRAWER_FULL_2: STATE_DOCKED, + Robot.UnitStatus.READY: STATE_DOCKED, Robot.UnitStatus.OFF: STATE_OFF, } return switcher.get(self.robot.unit_status, STATE_ERROR) - @property - def error(self): - """Return the error associated with the current state, if any.""" - return self.robot.unit_status.value - @property def status(self): """Return the status of the cleaner.""" - return f"{self.robot.unit_status.value}{' (Sleeping)' if self.robot.is_sleeping else ''}" + return f"{self.robot.unit_status.label}{' (Sleeping)' if self.robot.is_sleeping else ''}" async def async_turn_on(self, **kwargs): """Turn the cleaner on, starting a clean cycle.""" @@ -119,22 +113,11 @@ async def async_send_command(self, command, params=None, **kwargs): @property def device_state_attributes(self): """Return device specific state attributes.""" - [sleep_mode_start_time, sleep_mode_end_time] = [None, None] - - if self.robot.sleep_mode_active: - sleep_mode_start_time = dt_util.as_local( - self.robot.sleep_mode_start_time - ).strftime("%H:%M:00") - sleep_mode_end_time = dt_util.as_local( - self.robot.sleep_mode_end_time - ).strftime("%H:%M:00") - return { "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, "is_sleeping": self.robot.is_sleeping, - "sleep_mode_start_time": sleep_mode_start_time, - "sleep_mode_end_time": sleep_mode_end_time, + "sleep_mode_active": self.robot.sleep_mode_active, "power_status": self.robot.power_status, - "unit_status_code": self.robot.unit_status.name, + "unit_status_code": self.robot.unit_status.value, "last_seen": self.robot.last_seen, } diff --git a/requirements_all.txt b/requirements_all.txt index 33d2ee7aad64e6..688f9aefdceaf6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1504,7 +1504,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2021.2.5 +pylitterbot==2021.2.8 # homeassistant.components.loopenergy pyloopenergy==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 360fd28143483d..3131f9c31e857a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -794,7 +794,7 @@ pylibrespot-java==0.1.0 pylitejet==0.3.0 # homeassistant.components.litterrobot -pylitterbot==2021.2.5 +pylitterbot==2021.2.8 # homeassistant.components.lutron_caseta pylutron-caseta==0.9.0 diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index dae183b4cf6f04..aadf7d810aa5d2 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,45 +1,59 @@ """Configure pytest for Litter-Robot tests.""" +from typing import Optional from unittest.mock import AsyncMock, MagicMock, patch +import pylitterbot from pylitterbot import Robot import pytest from homeassistant.components import litterrobot -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .common import CONFIG, ROBOT_DATA from tests.common import MockConfigEntry -def create_mock_robot(hass): +def create_mock_robot(unit_status_code: Optional[str] = None): """Create a mock Litter-Robot device.""" - robot = Robot(data=ROBOT_DATA) - robot.start_cleaning = AsyncMock() - robot.set_power_status = AsyncMock() - robot.reset_waste_drawer = AsyncMock() - robot.set_sleep_mode = AsyncMock() - robot.set_night_light = AsyncMock() - robot.set_panel_lockout = AsyncMock() - return robot - - -@pytest.fixture() -def mock_hub(hass): - """Mock a Litter-Robot hub.""" - hub = MagicMock( - hass=hass, - account=MagicMock(), - logged_in=True, - coordinator=MagicMock(spec=DataUpdateCoordinator), - spec=litterrobot.LitterRobotHub, - ) - hub.coordinator.last_update_success = True - hub.account.robots = [create_mock_robot(hass)] - return hub + if not ( + unit_status_code + and Robot.UnitStatus(unit_status_code) != Robot.UnitStatus.UNKNOWN + ): + unit_status_code = ROBOT_DATA["unitStatus"] + + with patch.dict(ROBOT_DATA, {"unitStatus": unit_status_code}): + robot = Robot(data=ROBOT_DATA) + robot.start_cleaning = AsyncMock() + robot.set_power_status = AsyncMock() + robot.reset_waste_drawer = AsyncMock() + robot.set_sleep_mode = AsyncMock() + robot.set_night_light = AsyncMock() + robot.set_panel_lockout = AsyncMock() + return robot + + +def create_mock_account(unit_status_code: Optional[str] = None): + """Create a mock Litter-Robot account.""" + account = MagicMock(spec=pylitterbot.Account) + account.connect = AsyncMock() + account.refresh_robots = AsyncMock() + account.robots = [create_mock_robot(unit_status_code)] + return account -async def setup_hub(hass, mock_hub, platform_domain): +@pytest.fixture +def mock_account(): + """Mock a Litter-Robot account.""" + return create_mock_account() + + +@pytest.fixture +def mock_account_with_error(): + """Mock a Litter-Robot account with error.""" + return create_mock_account("BR") + + +async def setup_integration(hass, mock_account, platform_domain=None): """Load a Litter-Robot platform with the provided hub.""" entry = MockConfigEntry( domain=litterrobot.DOMAIN, @@ -47,9 +61,11 @@ async def setup_hub(hass, mock_hub, platform_domain): ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.litterrobot.LitterRobotHub", - return_value=mock_hub, + with patch("pylitterbot.Account", return_value=mock_account), patch( + "homeassistant.components.litterrobot.PLATFORMS", + [platform_domain] if platform_domain else [], ): - await hass.config_entries.async_forward_entry_setup(entry, platform_domain) + await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() + + return entry diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py index fd88595d37e19e..5068ecf721bdd3 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -4,11 +4,14 @@ from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException from homeassistant import config_entries, setup +from homeassistant.components import litterrobot from .common import CONF_USERNAME, CONFIG, DOMAIN +from tests.common import MockConfigEntry -async def test_form(hass): + +async def test_form(hass, mock_account): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -17,10 +20,7 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", - return_value=True, - ), patch( + with patch("pylitterbot.Account", return_value=mock_account), patch( "homeassistant.components.litterrobot.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.litterrobot.async_setup_entry", @@ -38,6 +38,23 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_already_configured(hass): + """Test we handle already configured.""" + MockConfigEntry( + domain=litterrobot.DOMAIN, + data=CONFIG[litterrobot.DOMAIN], + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=CONFIG[litterrobot.DOMAIN], + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + + async def test_form_invalid_auth(hass): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -45,7 +62,7 @@ async def test_form_invalid_auth(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=LitterRobotLoginException, ): result2 = await hass.config_entries.flow.async_configure( @@ -63,7 +80,7 @@ async def test_form_cannot_connect(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=LitterRobotException, ): result2 = await hass.config_entries.flow.async_configure( @@ -81,7 +98,7 @@ async def test_form_unknown_error(hass): ) with patch( - "homeassistant.components.litterrobot.config_flow.LitterRobotHub.login", + "pylitterbot.Account.connect", side_effect=Exception, ): result2 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py index 1d0ed075cc77e7..7cd36f33883c18 100644 --- a/tests/components/litterrobot/test_init.py +++ b/tests/components/litterrobot/test_init.py @@ -1,20 +1,48 @@ """Test Litter-Robot setup process.""" +from unittest.mock import patch + +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException +import pytest + from homeassistant.components import litterrobot -from homeassistant.setup import async_setup_component +from homeassistant.config_entries import ( + ENTRY_STATE_SETUP_ERROR, + ENTRY_STATE_SETUP_RETRY, +) from .common import CONFIG +from .conftest import setup_integration from tests.common import MockConfigEntry -async def test_unload_entry(hass): +async def test_unload_entry(hass, mock_account): """Test being able to unload an entry.""" + entry = await setup_integration(hass, mock_account) + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert hass.data[litterrobot.DOMAIN] == {} + + +@pytest.mark.parametrize( + "side_effect,expected_state", + ( + (LitterRobotLoginException, ENTRY_STATE_SETUP_ERROR), + (LitterRobotException, ENTRY_STATE_SETUP_RETRY), + ), +) +async def test_entry_not_setup(hass, side_effect, expected_state): + """Test being able to handle config entry not setup.""" entry = MockConfigEntry( domain=litterrobot.DOMAIN, data=CONFIG[litterrobot.DOMAIN], ) entry.add_to_hass(hass) - assert await async_setup_component(hass, litterrobot.DOMAIN, {}) is True - assert await litterrobot.async_unload_entry(hass, entry) - assert hass.data[litterrobot.DOMAIN] == {} + with patch( + "pylitterbot.Account.connect", + side_effect=side_effect, + ): + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state == expected_state diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index 2421489e2376b5..7f1570c553eeb2 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -1,20 +1,57 @@ """Test the Litter-Robot sensor entity.""" +from unittest.mock import Mock + +from homeassistant.components.litterrobot.sensor import LitterRobotSleepTimeSensor from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN -from homeassistant.const import PERCENTAGE +from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE -from .conftest import setup_hub +from .conftest import create_mock_robot, setup_integration -ENTITY_ID = "sensor.test_waste_drawer" +WASTE_DRAWER_ENTITY_ID = "sensor.test_waste_drawer" -async def test_sensor(hass, mock_hub): - """Tests the sensor entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) +async def test_waste_drawer_sensor(hass, mock_account): + """Tests the waste drawer sensor entity was set up.""" + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) - sensor = hass.states.get(ENTITY_ID) + sensor = hass.states.get(WASTE_DRAWER_ENTITY_ID) assert sensor assert sensor.state == "50" - assert sensor.attributes["cycle_count"] == 15 - assert sensor.attributes["cycle_capacity"] == 30 - assert sensor.attributes["cycles_after_drawer_full"] == 0 assert sensor.attributes["unit_of_measurement"] == PERCENTAGE + + +async def test_sleep_time_sensor_with_none_state(hass): + """Tests the sleep mode start time sensor where sleep mode is inactive.""" + robot = create_mock_robot() + robot.sleep_mode_active = False + sensor = LitterRobotSleepTimeSensor( + robot, "Sleep Mode Start Time", Mock(), "sleep_mode_start_time" + ) + + assert sensor + assert sensor.state is None + assert sensor.device_class == DEVICE_CLASS_TIMESTAMP + + +async def test_gauge_icon(): + """Test icon generator for gauge sensor.""" + from homeassistant.components.litterrobot.sensor import icon_for_gauge_level + + GAUGE_EMPTY = "mdi:gauge-empty" + GAUGE_LOW = "mdi:gauge-low" + GAUGE = "mdi:gauge" + GAUGE_FULL = "mdi:gauge-full" + + assert icon_for_gauge_level(None) == GAUGE_EMPTY + assert icon_for_gauge_level(0) == GAUGE_EMPTY + assert icon_for_gauge_level(5) == GAUGE_LOW + assert icon_for_gauge_level(40) == GAUGE + assert icon_for_gauge_level(80) == GAUGE_FULL + assert icon_for_gauge_level(100) == GAUGE_FULL + + assert icon_for_gauge_level(None, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(0, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(5, 10) == GAUGE_EMPTY + assert icon_for_gauge_level(40, 10) == GAUGE_LOW + assert icon_for_gauge_level(80, 10) == GAUGE + assert icon_for_gauge_level(100, 10) == GAUGE_FULL diff --git a/tests/components/litterrobot/test_switch.py b/tests/components/litterrobot/test_switch.py index c7f85db74125b8..69154bef8f5c8e 100644 --- a/tests/components/litterrobot/test_switch.py +++ b/tests/components/litterrobot/test_switch.py @@ -12,7 +12,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_ON from homeassistant.util.dt import utcnow -from .conftest import setup_hub +from .conftest import setup_integration from tests.common import async_fire_time_changed @@ -20,9 +20,9 @@ PANEL_LOCKOUT_ENTITY_ID = "switch.test_panel_lockout" -async def test_switch(hass, mock_hub): +async def test_switch(hass, mock_account): """Tests the switch entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) switch = hass.states.get(NIGHT_LIGHT_MODE_ENTITY_ID) assert switch @@ -36,9 +36,9 @@ async def test_switch(hass, mock_hub): (PANEL_LOCKOUT_ENTITY_ID, "set_panel_lockout"), ], ) -async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): +async def test_on_off_commands(hass, mock_account, entity_id, robot_command): """Test sending commands to the switch.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) switch = hass.states.get(entity_id) assert switch @@ -48,12 +48,14 @@ async def test_on_off_commands(hass, mock_hub, entity_id, robot_command): count = 0 for service in [SERVICE_TURN_ON, SERVICE_TURN_OFF]: count += 1 + await hass.services.async_call( PLATFORM_DOMAIN, service, data, blocking=True, ) + future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) async_fire_time_changed(hass, future) - assert getattr(mock_hub.account.robots[0], robot_command).call_count == count + assert getattr(mock_account.robots[0], robot_command).call_count == count diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py index 03e63b472b62f4..2db2ef21546be5 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -12,20 +12,21 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_DOCKED, + STATE_ERROR, ) from homeassistant.const import ATTR_COMMAND, ATTR_ENTITY_ID from homeassistant.util.dt import utcnow -from .conftest import setup_hub +from .conftest import setup_integration from tests.common import async_fire_time_changed ENTITY_ID = "vacuum.test_litter_box" -async def test_vacuum(hass, mock_hub): +async def test_vacuum(hass, mock_account): """Tests the vacuum entity was set up.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) assert vacuum @@ -33,6 +34,15 @@ async def test_vacuum(hass, mock_hub): assert vacuum.attributes["is_sleeping"] is False +async def test_vacuum_with_error(hass, mock_account_with_error): + """Tests a vacuum entity with an error.""" + await setup_integration(hass, mock_account_with_error, PLATFORM_DOMAIN) + + vacuum = hass.states.get(ENTITY_ID) + assert vacuum + assert vacuum.state == STATE_ERROR + + @pytest.mark.parametrize( "service,command,extra", [ @@ -52,14 +62,22 @@ async def test_vacuum(hass, mock_hub): ATTR_PARAMS: {"enabled": True, "sleep_time": "22:30"}, }, ), + ( + SERVICE_SEND_COMMAND, + "set_sleep_mode", + { + ATTR_COMMAND: "set_sleep_mode", + ATTR_PARAMS: {"enabled": True, "sleep_time": None}, + }, + ), ], ) -async def test_commands(hass, mock_hub, service, command, extra): +async def test_commands(hass, mock_account, service, command, extra): """Test sending commands to the vacuum.""" - await setup_hub(hass, mock_hub, PLATFORM_DOMAIN) + await setup_integration(hass, mock_account, PLATFORM_DOMAIN) vacuum = hass.states.get(ENTITY_ID) - assert vacuum is not None + assert vacuum assert vacuum.state == STATE_DOCKED data = {ATTR_ENTITY_ID: ENTITY_ID} @@ -74,4 +92,4 @@ async def test_commands(hass, mock_hub, service, command, extra): ) future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME) async_fire_time_changed(hass, future) - getattr(mock_hub.account.robots[0], command).assert_called_once() + getattr(mock_account.robots[0], command).assert_called_once() From c1a5a18b53ef66aa61a4c25324c86cd676044d08 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 6 Mar 2021 14:30:57 -1000 Subject: [PATCH 1128/1818] Bump HAP-python to 3.4.0 (#47476) * Bump HAP-python to 3.3.3 * bump * fix mocking --- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_homekit.py | 26 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index c042872f4cd632..28e2683c25908e 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -616,7 +616,7 @@ async def async_start(self, *args): self._async_register_bridge(dev_reg) await self._async_start(bridged_states) _LOGGER.debug("Driver start for %s", self._name) - self.hass.add_job(self.driver.start_service) + await self.driver.async_start() self.status = STATUS_RUNNING @callback diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index ac3fb0251e2e74..d7ec3297fa499b 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", "requirements": [ - "HAP-python==3.3.2", + "HAP-python==3.4.0", "fnvhash==0.1.0", "PyQRCode==1.2.1", "base36==0.1.1", diff --git a/requirements_all.txt b/requirements_all.txt index 688f9aefdceaf6..112f69e817caa2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -17,7 +17,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==3.3.2 +HAP-python==3.4.0 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3131f9c31e857a..f6f39403ac817c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.1.8 # homeassistant.components.homekit -HAP-python==3.3.2 +HAP-python==3.4.0 # homeassistant.components.flick_electric PyFlick==0.0.2 diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 9ce3e96f06f985..4d2fbfe951d27f 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -493,7 +493,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -528,7 +528,7 @@ async def test_homekit_start(hass, hk_driver, device_reg): ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -567,7 +567,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory", ) as hk_driver_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -630,7 +630,7 @@ async def test_homekit_reset_accessories(hass, mock_zeroconf): ), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch( "pyhap.accessory_driver.AccessoryDriver.config_changed" ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await async_init_entry(hass, entry) @@ -674,7 +674,7 @@ def _mock_bridge(*_): hass.states.async_set("light.demo2", "on") hass.states.async_set("light.demo3", "on") - with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge @@ -738,7 +738,7 @@ async def test_homekit_finds_linked_batteries( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -810,7 +810,7 @@ async def test_homekit_async_get_integration_fails( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -895,7 +895,7 @@ async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): assert await async_setup_component(hass, "zeroconf", {"zeroconf": {}}) system_zc = await zeroconf.async_get_instance(hass) - with patch("pyhap.accessory_driver.AccessoryDriver.start_service"), patch( + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( f"{PATH_HOMEKIT}.HomeKit.async_stop" ): entry.add_to_hass(hass) @@ -963,7 +963,7 @@ async def test_homekit_ignored_missing_devices( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1025,7 +1025,7 @@ async def test_homekit_finds_linked_motion_sensors( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1090,7 +1090,7 @@ async def test_homekit_finds_linked_humidity_sensors( with patch.object(homekit.bridge, "add_accessory"), patch( f"{PATH_HOMEKIT}.show_setup_message" ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): await homekit.async_start() await hass.async_block_till_done() @@ -1153,7 +1153,7 @@ async def test_reload(hass, mock_zeroconf): ), patch( f"{PATH_HOMEKIT}.get_accessory" ), patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ): mock_homekit2.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -1205,7 +1205,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.add_accessory" ), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.start_service" + "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() From b25f8461365ecb2430b27d9cf2a160e5db7bda62 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 6 Mar 2021 10:21:00 +0100 Subject: [PATCH 1129/1818] Fix Sonos polling mode (#47498) --- homeassistant/components/sonos/media_player.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index e6ee45e7a57b29..1a9e9ef58df5cd 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -646,9 +646,12 @@ def update_media(self, event=None): update_position = new_status != self._status self._status = new_status - track_uri = variables["current_track_uri"] if variables else None - - music_source = self.soco.music_source_from_uri(track_uri) + if variables: + track_uri = variables["current_track_uri"] + music_source = self.soco.music_source_from_uri(track_uri) + else: + # This causes a network round-trip so we avoid it when possible + music_source = self.soco.music_source if music_source == MUSIC_SRC_TV: self.update_media_linein(SOURCE_TV) From ba2b62305b2b491adeca5468638c46f7f5754f9b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 18:33:55 +0100 Subject: [PATCH 1130/1818] Fix mysensors notify platform (#47517) --- .../components/mysensors/__init__.py | 47 ++++++++++++++----- .../components/mysensors/device_tracker.py | 3 ++ homeassistant/components/mysensors/helpers.py | 4 +- homeassistant/components/mysensors/notify.py | 3 ++ 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 25b4d3106da5c9..d7f1ffab400e04 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,5 +1,6 @@ """Connect to a MySensors gateway via pymysensors API.""" import asyncio +from functools import partial import logging from typing import Callable, Dict, List, Optional, Tuple, Type, Union @@ -8,10 +9,13 @@ from homeassistant import config_entries from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic +from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_OPTIMISTIC -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( @@ -28,6 +32,7 @@ CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, + MYSENSORS_DISCOVERY, MYSENSORS_GATEWAYS, MYSENSORS_ON_UNLOAD, SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, @@ -43,6 +48,8 @@ CONF_DEBUG = "debug" CONF_NODE_NAME = "name" +DATA_HASS_CONFIG = "hass_config" + DEFAULT_BAUD_RATE = 115200 DEFAULT_TCP_PORT = 5003 DEFAULT_VERSION = "1.4" @@ -134,6 +141,8 @@ def validator(config): async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up the MySensors component.""" + hass.data[DOMAIN] = {DATA_HASS_CONFIG: config} + if DOMAIN not in config or bool(hass.config_entries.async_entries(DOMAIN)): return True @@ -181,14 +190,31 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool _LOGGER.error("Gateway setup failed for %s", entry.data) return False - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway - async def finish(): + # Connect notify discovery as that integration doesn't support entry forwarding. + + load_notify_platform = partial( + async_load_platform, + hass, + NOTIFY_DOMAIN, + DOMAIN, + hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], + ) + + await on_unload( + hass, + entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), + load_notify_platform, + ), + ) + + async def finish() -> None: await asyncio.gather( *[ hass.config_entries.async_forward_entry_setup(entry, platform) @@ -248,14 +274,14 @@ async def on_unload( @callback def setup_mysensors_platform( - hass, + hass: HomeAssistant, domain: str, # hass platform name - discovery_info: Optional[Dict[str, List[DevId]]], + discovery_info: Dict[str, List[DevId]], device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]], device_args: Optional[ Tuple ] = None, # extra arguments that will be given to the entity constructor - async_add_entities: Callable = None, + async_add_entities: Optional[Callable] = None, ) -> Optional[List[MySensorsDevice]]: """Set up a MySensors platform. @@ -264,11 +290,6 @@ def setup_mysensors_platform( The function is also given a class. A new instance of the class is created for every device id, and the device id is given to the constructor of the class """ - # Only act if called via MySensors by discovery event. - # Otherwise gateway is not set up. - if not discovery_info: - _LOGGER.debug("Skipping setup due to no discovery info") - return None if device_args is None: device_args = () new_devices: List[MySensorsDevice] = [] diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index b395a48f28b7da..d1f89e4fe04733 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -12,6 +12,9 @@ async def async_setup_scanner( hass: HomeAssistantType, config, async_see, discovery_info=None ): """Set up the MySensors device scanner.""" + if not discovery_info: + return False + new_devices = mysensors.setup_mysensors_platform( hass, DOMAIN, diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index d06bf0dee2fe57..4452dd0575b3f3 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.const import CONF_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util.decorator import Registry @@ -33,7 +33,7 @@ @callback def discover_mysensors_platform( - hass, gateway_id: GatewayId, platform: str, new_devices: List[DevId] + hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] ) -> None: """Discover a MySensors platform.""" _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices) diff --git a/homeassistant/components/mysensors/notify.py b/homeassistant/components/mysensors/notify.py index 99e731762df5c8..50fca55ab39703 100644 --- a/homeassistant/components/mysensors/notify.py +++ b/homeassistant/components/mysensors/notify.py @@ -5,6 +5,9 @@ async def async_get_service(hass, config, discovery_info=None): """Get the MySensors notification service.""" + if not discovery_info: + return None + new_devices = mysensors.setup_mysensors_platform( hass, DOMAIN, discovery_info, MySensorsNotificationDevice ) From e7717694a3e7f5f0e4b96ae32b5177d974695999 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:34:12 +0100 Subject: [PATCH 1131/1818] Fix AsusWRT wrong api call (#47522) --- CODEOWNERS | 2 +- homeassistant/components/asuswrt/config_flow.py | 2 +- homeassistant/components/asuswrt/manifest.json | 2 +- homeassistant/components/asuswrt/router.py | 2 +- tests/components/asuswrt/test_config_flow.py | 4 ++-- tests/components/asuswrt/test_sensor.py | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b0a31203009799..0e069f94e73589 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,7 +46,7 @@ homeassistant/components/arcam_fmj/* @elupus homeassistant/components/arduino/* @fabaff homeassistant/components/arest/* @fabaff homeassistant/components/arris_tg2492lg/* @vanbalken -homeassistant/components/asuswrt/* @kennedyshead +homeassistant/components/asuswrt/* @kennedyshead @ollo69 homeassistant/components/atag/* @MatsNL homeassistant/components/aten_pe/* @mtdcr homeassistant/components/atome/* @baqs diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index 303b3cc3822ce9..b3e3ec4d68db36 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -128,7 +128,7 @@ async def _async_check_connection(self, user_input): conf_protocol = user_input[CONF_PROTOCOL] if conf_protocol == PROTOCOL_TELNET: - await api.connection.disconnect() + api.connection.disconnect() return RESULT_SUCCESS async def async_step_user(self, user_input=None): diff --git a/homeassistant/components/asuswrt/manifest.json b/homeassistant/components/asuswrt/manifest.json index 744a05b9728ec4..ab739f1c7ec47a 100644 --- a/homeassistant/components/asuswrt/manifest.json +++ b/homeassistant/components/asuswrt/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/asuswrt", "requirements": ["aioasuswrt==1.3.1"], - "codeowners": ["@kennedyshead"] + "codeowners": ["@kennedyshead", "@ollo69"] } diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 11545919b43abb..4af157387f97fc 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -205,7 +205,7 @@ async def close(self) -> None: """Close the connection.""" if self._api is not None: if self._protocol == PROTOCOL_TELNET: - await self._api.connection.disconnect() + self._api.connection.disconnect() self._api = None for func in self._on_close: diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py index 7faec5d336cce9..a6e24b09462154 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -1,6 +1,6 @@ """Tests for the AsusWrt config flow.""" from socket import gaierror -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch import pytest @@ -46,7 +46,7 @@ def mock_controller_connect(): with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True - service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.connection.disconnect = Mock() yield service_mock diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 994111370fda88..0e663ae548b0a8 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -1,6 +1,6 @@ """Tests for the AsusWrt sensor.""" from datetime import timedelta -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from aioasuswrt.asuswrt import Device import pytest @@ -49,7 +49,7 @@ def mock_controller_connect(): with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock: service_mock.return_value.connection.async_connect = AsyncMock() service_mock.return_value.is_connected = True - service_mock.return_value.connection.disconnect = AsyncMock() + service_mock.return_value.connection.disconnect = Mock() service_mock.return_value.async_get_connected_devices = AsyncMock( return_value=MOCK_DEVICES ) From e63f766c2073df936fa5ed113ee6d0ffbef25d8a Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 23:06:50 +0100 Subject: [PATCH 1132/1818] Bump pymysensors to 0.21.0 (#47530) --- homeassistant/components/mysensors/manifest.json | 13 +++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mysensors/manifest.json b/homeassistant/components/mysensors/manifest.json index 8371f2930c2fb1..c7d439dedc44dd 100644 --- a/homeassistant/components/mysensors/manifest.json +++ b/homeassistant/components/mysensors/manifest.json @@ -2,15 +2,8 @@ "domain": "mysensors", "name": "MySensors", "documentation": "https://www.home-assistant.io/integrations/mysensors", - "requirements": [ - "pymysensors==0.20.1" - ], - "after_dependencies": [ - "mqtt" - ], - "codeowners": [ - "@MartinHjelmare", - "@functionpointer" - ], + "requirements": ["pymysensors==0.21.0"], + "after_dependencies": ["mqtt"], + "codeowners": ["@MartinHjelmare", "@functionpointer"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 112f69e817caa2..4ddf638867f811 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1555,7 +1555,7 @@ pymusiccast==0.1.6 pymyq==3.0.4 # homeassistant.components.mysensors -pymysensors==0.20.1 +pymysensors==0.21.0 # homeassistant.components.nanoleaf pynanoleaf==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f6f39403ac817c..0f1a59fff35c5d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -827,7 +827,7 @@ pymonoprice==0.3 pymyq==3.0.4 # homeassistant.components.mysensors -pymysensors==0.20.1 +pymysensors==0.21.0 # homeassistant.components.nuki pynuki==1.3.8 From a2e00324a8af7f78ffaa75fb5f23d9af7fcea1ed Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 6 Mar 2021 23:41:43 +0100 Subject: [PATCH 1133/1818] Fix mysensors device tracker (#47536) --- .../components/mysensors/__init__.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index d7f1ffab400e04..d0ab8ea712e881 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_topic from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ConfigEntry @@ -195,24 +196,27 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] = gateway # Connect notify discovery as that integration doesn't support entry forwarding. + # Allow loading device tracker platform via discovery + # until refactor to config entry is done. - load_notify_platform = partial( - async_load_platform, - hass, - NOTIFY_DOMAIN, - DOMAIN, - hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], - ) + for platform in (DEVICE_TRACKER_DOMAIN, NOTIFY_DOMAIN): + load_discovery_platform = partial( + async_load_platform, + hass, + platform, + DOMAIN, + hass_config=hass.data[DOMAIN][DATA_HASS_CONFIG], + ) - await on_unload( - hass, - entry.entry_id, - async_dispatcher_connect( + await on_unload( hass, - MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), - load_notify_platform, - ), - ) + entry.entry_id, + async_dispatcher_connect( + hass, + MYSENSORS_DISCOVERY.format(entry.entry_id, platform), + load_discovery_platform, + ), + ) async def finish() -> None: await asyncio.gather( From 56efae3cb51ca96499ee7bdc8bb20d6c9ebb0a31 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 7 Mar 2021 14:20:21 +0100 Subject: [PATCH 1134/1818] Fix mysensors unload clean up (#47541) --- .../components/mysensors/__init__.py | 21 +++--------------- homeassistant/components/mysensors/gateway.py | 18 +++++++++++---- homeassistant/components/mysensors/helpers.py | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index d0ab8ea712e881..0f8123e3a3165c 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -38,11 +38,11 @@ MYSENSORS_ON_UNLOAD, SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, DevId, - GatewayId, SensorType, ) from .device import MySensorsDevice, MySensorsEntity, get_mysensors_devices from .gateway import finish_setup, get_mysensors_gateway, gw_stop, setup_gateway +from .helpers import on_unload _LOGGER = logging.getLogger(__name__) @@ -253,29 +253,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo for fnct in hass.data[DOMAIN][key]: fnct() + hass.data[DOMAIN].pop(key) + del hass.data[DOMAIN][MYSENSORS_GATEWAYS][entry.entry_id] await gw_stop(hass, entry, gateway) return True -async def on_unload( - hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable -) -> None: - """Register a callback to be called when entry is unloaded. - - This function is used by platforms to cleanup after themselves - """ - if isinstance(entry, GatewayId): - uniqueid = entry - else: - uniqueid = entry.entry_id - key = MYSENSORS_ON_UNLOAD.format(uniqueid) - if key not in hass.data[DOMAIN]: - hass.data[DOMAIN][key] = [] - hass.data[DOMAIN][key].append(fnct) - - @callback def setup_mysensors_platform( hass: HomeAssistant, diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index b6797cafb37b8c..a7f3a053d3fc60 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -31,7 +31,12 @@ GatewayId, ) from .handler import HANDLERS -from .helpers import discover_mysensors_platform, validate_child, validate_node +from .helpers import ( + discover_mysensors_platform, + on_unload, + validate_child, + validate_node, +) _LOGGER = logging.getLogger(__name__) @@ -260,8 +265,8 @@ async def _discover_persistent_devices( async def gw_stop(hass, entry: ConfigEntry, gateway: BaseAsyncGateway): """Stop the gateway.""" - connect_task = hass.data[DOMAIN].get( - MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) + connect_task = hass.data[DOMAIN].pop( + MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id), None ) if connect_task is not None and not connect_task.done(): connect_task.cancel() @@ -288,7 +293,12 @@ def gateway_connected(_: BaseAsyncGateway): async def stop_this_gw(_: Event): await gw_stop(hass, entry, gateway) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw) + await on_unload( + hass, + entry.entry_id, + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_this_gw), + ) + if entry.data[CONF_DEVICE] == MQTT_COMPONENT: # Gatways connected via mqtt doesn't send gateway ready message. return diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 4452dd0575b3f3..0b8dc361158fd4 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -2,16 +2,18 @@ from collections import defaultdict from enum import IntEnum import logging -from typing import DefaultDict, Dict, List, Optional, Set +from typing import Callable, DefaultDict, Dict, List, Optional, Set, Union from mysensors import BaseAsyncGateway, Message from mysensors.sensor import ChildSensor import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.decorator import Registry from .const import ( @@ -20,6 +22,7 @@ DOMAIN, FLAT_PLATFORM_TYPES, MYSENSORS_DISCOVERY, + MYSENSORS_ON_UNLOAD, TYPE_TO_PLATFORMS, DevId, GatewayId, @@ -31,6 +34,23 @@ SCHEMAS = Registry() +async def on_unload( + hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable +) -> None: + """Register a callback to be called when entry is unloaded. + + This function is used by platforms to cleanup after themselves. + """ + if isinstance(entry, GatewayId): + uniqueid = entry + else: + uniqueid = entry.entry_id + key = MYSENSORS_ON_UNLOAD.format(uniqueid) + if key not in hass.data[DOMAIN]: + hass.data[DOMAIN][key] = [] + hass.data[DOMAIN][key].append(fnct) + + @callback def discover_mysensors_platform( hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] From 69f63129aac564468ef61f506fe6fe2b05c20c50 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 7 Mar 2021 15:07:02 +0000 Subject: [PATCH 1135/1818] Correct weather entities forecast time (#47565) --- homeassistant/components/aemet/weather_update_coordinator.py | 4 ++-- .../components/openweathermap/weather_update_coordinator.py | 4 +++- tests/components/aemet/test_sensor.py | 4 +++- tests/components/aemet/test_weather.py | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 6a06b1dd39147a..619429c9a5b275 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -393,7 +393,7 @@ def _convert_forecast_day(self, date, day): ), ATTR_FORECAST_TEMP: self._get_temperature_day(day), ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day), - ATTR_FORECAST_TIME: dt_util.as_utc(date), + ATTR_FORECAST_TIME: dt_util.as_utc(date).isoformat(), ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day), ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day), } @@ -412,7 +412,7 @@ def _convert_forecast_hour(self, date, day, hour): day, hour ), ATTR_FORECAST_TEMP: self._get_temperature(day, hour), - ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt), + ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(), ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour), ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour), } diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 93db4ca26d8125..e07a8f32608573 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -139,7 +139,9 @@ def _get_forecast_from_weather_response(self, weather_response): def _convert_forecast(self, entry): forecast = { - ATTR_FORECAST_TIME: dt.utc_from_timestamp(entry.reference_time("unix")), + ATTR_FORECAST_TIME: dt.utc_from_timestamp( + entry.reference_time("unix") + ).isoformat(), ATTR_FORECAST_PRECIPITATION: self._calc_precipitation( entry.rain, entry.snow ), diff --git a/tests/components/aemet/test_sensor.py b/tests/components/aemet/test_sensor.py index 05f2d8d0b500d0..b265b9967097e6 100644 --- a/tests/components/aemet/test_sensor.py +++ b/tests/components/aemet/test_sensor.py @@ -37,7 +37,9 @@ async def test_aemet_forecast_create_sensors(hass): assert state.state == "-4" state = hass.states.get("sensor.aemet_daily_forecast_time") - assert state.state == "2021-01-10 00:00:00+00:00" + assert ( + state.state == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() + ) state = hass.states.get("sensor.aemet_daily_forecast_wind_bearing") assert state.state == "45.0" diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index eef6107d54322c..43acf4c1c8731c 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -51,8 +51,9 @@ async def test_aemet_weather(hass): assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 30 assert forecast.get(ATTR_FORECAST_TEMP) == 4 assert forecast.get(ATTR_FORECAST_TEMP_LOW) == -4 - assert forecast.get(ATTR_FORECAST_TIME) == dt_util.parse_datetime( - "2021-01-10 00:00:00+00:00" + assert ( + forecast.get(ATTR_FORECAST_TIME) + == dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat() ) assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0 assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20 From 37f486941a23da977147ea5e34d3ee9411bd2af6 Mon Sep 17 00:00:00 2001 From: Tony Roman Date: Mon, 8 Mar 2021 13:26:08 -0500 Subject: [PATCH 1136/1818] Allow running and restarting with both ozw and zwave active (#47566) Co-authored-by: Martin Hjelmare --- homeassistant/components/ozw/manifest.json | 3 +-- script/hassfest/dependencies.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json index 984e3f9c51afc7..a1409fd79a81ec 100644 --- a/homeassistant/components/ozw/manifest.json +++ b/homeassistant/components/ozw/manifest.json @@ -7,8 +7,7 @@ "python-openzwave-mqtt[mqtt-client]==1.4.0" ], "after_dependencies": [ - "mqtt", - "zwave" + "mqtt" ], "codeowners": [ "@cgarwood", diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 6283b2d8665c09..b13d792904243e 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -147,6 +147,8 @@ def visit_Attribute(self, node): # Demo ("demo", "manual"), ("demo", "openalpr_local"), + # Migration wizard from zwave to ozw. + "ozw", # This should become a helper method that integrations can submit data to ("websocket_api", "lovelace"), ("websocket_api", "shopping_list"), From f1fc6c4b2565266055449a38899ef0a0aa1cf642 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 8 Mar 2021 03:08:17 -0500 Subject: [PATCH 1137/1818] Add fallback zwave_js entity name using node ID (#47582) * add fallback zwave_js entity name using node ID * add new fixture and test for name that was failing --- homeassistant/components/zwave_js/entity.py | 6 +- tests/components/zwave_js/conftest.py | 20 + tests/components/zwave_js/test_init.py | 6 + .../zwave_js/null_name_check_state.json | 414 ++++++++++++++++++ 4 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/zwave_js/null_name_check_state.json diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index c061abc4d0ddfe..7620323d9405b3 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -105,7 +105,11 @@ def generate_name( """Generate entity name.""" if additional_info is None: additional_info = [] - name: str = self.info.node.name or self.info.node.device_config.description + name: str = ( + self.info.node.name + or self.info.node.device_config.description + or f"Node {self.info.node.node_id}" + ) if include_value_name: value_name = ( alternate_value_name diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index aa9da282635509..740d14763d4ae8 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -288,6 +288,18 @@ def aeotec_radiator_thermostat_state_fixture(): return json.loads(load_fixture("zwave_js/aeotec_radiator_thermostat_state.json")) +@pytest.fixture(name="inovelli_lzw36_state", scope="session") +def inovelli_lzw36_state_fixture(): + """Load the Inovelli LZW36 node state fixture data.""" + return json.loads(load_fixture("zwave_js/inovelli_lzw36_state.json")) + + +@pytest.fixture(name="null_name_check_state", scope="session") +def null_name_check_state_fixture(): + """Load the null name check node state fixture data.""" + return json.loads(load_fixture("zwave_js/null_name_check_state.json")) + + @pytest.fixture(name="client") def mock_client_fixture(controller_state, version_state): """Mock a client.""" @@ -484,6 +496,14 @@ def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state): return node +@pytest.fixture(name="null_name_check") +def null_name_check_fixture(client, null_name_check_state): + """Mock a node with no name.""" + node = Node(client, copy.deepcopy(null_name_check_state)) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="multiple_devices") def multiple_devices_fixture( client, climate_radio_thermostat_ct100_plus_state, lock_schlage_be469_state diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index e56db58f3cc540..00574bd2d2f49e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -459,6 +459,12 @@ async def test_existing_node_ready( ) +async def test_null_name(hass, client, null_name_check, integration): + """Test that node without a name gets a generic node name.""" + node = null_name_check + assert hass.states.get(f"switch.node_{node.node_id}") + + async def test_existing_node_not_ready(hass, client, multisensor_6, device_registry): """Test we handle a non ready node that exists during integration setup.""" node = multisensor_6 diff --git a/tests/fixtures/zwave_js/null_name_check_state.json b/tests/fixtures/zwave_js/null_name_check_state.json new file mode 100644 index 00000000000000..fe63eaee20787d --- /dev/null +++ b/tests/fixtures/zwave_js/null_name_check_state.json @@ -0,0 +1,414 @@ +{ + "nodeId": 10, + "index": 0, + "installerIcon": 3328, + "userIcon": 3328, + "status": 4, + "ready": true, + "isListening": true, + "isFrequentListening": false, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 277, + "productId": 1, + "productType": 272, + "firmwareVersion": "2.17", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 1, + "neighbors": [], + "endpointCountIsDynamic": false, + "endpointsHaveIdenticalCapabilities": false, + "individualEndpointCount": 4, + "aggregatedEndpointCount": 0, + "interviewAttempts": 1, + "interviewStage": 7, + "endpoints": [ + { + "nodeId": 10, + "index": 0, + "installerIcon": 3328, + "userIcon": 3328 + }, + { + "nodeId": 10, + "index": 1 + }, + { + "nodeId": 10, + "index": 2 + }, + { + "nodeId": 10, + "index": 3 + }, + { + "nodeId": 10, + "index": 4 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "\u00b0C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 2.9 + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Humidity", + "propertyName": "Humidity", + "ccVersion": 7, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "%", + "label": "Humidity", + "ccSpecific": { + "sensorType": 5, + "scale": 0 + } + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 277 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 272 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.38" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": ["2.17"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + }, + { + "endpoint": 1, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 1, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 2, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 2, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": false + }, + { + "endpoint": 3, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": false + }, + { + "endpoint": 3, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + } + }, + { + "endpoint": 4, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Current value" + }, + "value": true + }, + { + "endpoint": 4, + "commandClass": 37, + "commandClassName": "Binary Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Target value" + }, + "value": true + } + ], + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 33, + "label": "Multilevel Sensor" + }, + "specific": { + "key": 1, + "label": "Routing Multilevel Sensor" + }, + "mandatorySupportedCCs": [32, 49], + "mandatoryControlledCCs": [] + }, + "commandClasses": [ + { + "id": 37, + "name": "Binary Switch", + "version": 1, + "isSecure": false + }, + { + "id": 49, + "name": "Multilevel Sensor", + "version": 7, + "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": 122, + "name": "Firmware Update Meta Data", + "version": 3, + "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 + } + ] +} From 0f115f69378271f65e5c0f1c323976adc3d95bc6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:44:28 -1000 Subject: [PATCH 1138/1818] Ensure bond devices recover when wifi disconnects and reconnects (#47591) --- homeassistant/components/bond/entity.py | 2 +- tests/components/bond/test_entity.py | 169 ++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 tests/components/bond/test_entity.py diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 5b2e27b94cc5b0..ec885f454e3dfd 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -102,7 +102,7 @@ async def async_update(self): async def _async_update_if_bpup_not_alive(self, *_): """Fetch via the API if BPUP is not alive.""" - if self._bpup_subs.alive and self._initialized: + if self._bpup_subs.alive and self._initialized and self._available: return if self._update_lock.locked(): diff --git a/tests/components/bond/test_entity.py b/tests/components/bond/test_entity.py new file mode 100644 index 00000000000000..e0a3f156ff51ae --- /dev/null +++ b/tests/components/bond/test_entity.py @@ -0,0 +1,169 @@ +"""Tests for the Bond entities.""" +import asyncio +from datetime import timedelta +from unittest.mock import patch + +from bond_api import BPUPSubscriptions, DeviceType + +from homeassistant import core +from homeassistant.components import fan +from homeassistant.components.fan import DOMAIN as FAN_DOMAIN +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE +from homeassistant.util import utcnow + +from .common import patch_bond_device_state, setup_platform + +from tests.common import async_fire_time_changed + + +def ceiling_fan(name: str): + """Create a ceiling fan with given name.""" + return { + "name": name, + "type": DeviceType.CEILING_FAN, + "actions": ["SetSpeed", "SetDirection"], + } + + +async def test_bpup_goes_offline_and_recovers_same_entity(hass: core.HomeAssistant): + """Test that push updates fail and we fallback to polling and then bpup recovers. + + The BPUP recovery is triggered by an update for the entity and + we do not fallback to polling because state is in sync. + """ + bpup_subs = BPUPSubscriptions() + with patch( + "homeassistant.components.bond.BPUPSubscriptions", + return_value=bpup_subs, + ): + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 3, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 + + bpup_subs.last_message_time = 0 + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + # Ensure we do not poll to get the state + # since bpup has recovered and we know we + # are back in sync + with patch_bond_device_state(side_effect=Exception): + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 2, "direction": 0}, + } + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 66 + + +async def test_bpup_goes_offline_and_recovers_different_entity( + hass: core.HomeAssistant, +): + """Test that push updates fail and we fallback to polling and then bpup recovers. + + The BPUP recovery is triggered by an update for a different entity which + forces a poll since we need to re-get the state. + """ + bpup_subs = BPUPSubscriptions() + with patch( + "homeassistant.components.bond.BPUPSubscriptions", + return_value=bpup_subs, + ): + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 3, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 100 + + bpup_subs.notify( + { + "s": 200, + "t": "bond/test-device-id/update", + "b": {"power": 1, "speed": 1, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").attributes[fan.ATTR_PERCENTAGE] == 33 + + bpup_subs.last_message_time = 0 + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + bpup_subs.notify( + { + "s": 200, + "t": "bond/not-this-device-id/update", + "b": {"power": 1, "speed": 2, "direction": 0}, + } + ) + await hass.async_block_till_done() + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + with patch_bond_device_state(return_value={"power": 1, "speed": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=430)) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 + + +async def test_polling_fails_and_recovers(hass: core.HomeAssistant): + """Test that polling fails and we recover.""" + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + with patch_bond_device_state(side_effect=asyncio.TimeoutError): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + assert hass.states.get("fan.name_1").state == STATE_UNAVAILABLE + + with patch_bond_device_state(return_value={"power": 1, "speed": 1}): + async_fire_time_changed(hass, utcnow() + timedelta(seconds=230)) + await hass.async_block_till_done() + + state = hass.states.get("fan.name_1") + assert state.state == STATE_ON + assert state.attributes[fan.ATTR_PERCENTAGE] == 33 From 3c1aac10343f9ffa21060c172e1bf09a76754bb5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Mar 2021 06:45:15 +0100 Subject: [PATCH 1139/1818] Update frontend to 20210302.6 (#47592) --- 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 8093c65d91a9c2..694be0382f7801 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==20210302.5" + "home-assistant-frontend==20210302.6" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0586b956f39548..7642e14bda8c44 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.41.0 -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 4ddf638867f811..5eb66a3a200e72 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -763,7 +763,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0f1a59fff35c5d..628b62899cdcd9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -412,7 +412,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.5 +home-assistant-frontend==20210302.6 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 96b266b2e800e48895c42a1551aaf18c3e01f22e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 08:43:22 -1000 Subject: [PATCH 1140/1818] Fix turn on without speed in homekit controller (#47597) --- .../components/homekit_controller/fan.py | 9 +- .../components/homekit_controller/conftest.py | 7 ++ .../components/homekit_controller/test_fan.py | 97 +++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index e2cdf7b3cfdea8..591050f5fd9c17 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -80,6 +80,13 @@ def supported_features(self): return features + @property + def speed_count(self): + """Speed count for the fan.""" + return round( + 100 / max(1, self.service[CharacteristicsTypes.ROTATION_SPEED].minStep or 0) + ) + async def async_set_direction(self, direction): """Set the direction of the fan.""" await self.async_put_characteristics( @@ -110,7 +117,7 @@ async def async_turn_on( if not self.is_on: characteristics[self.on_characteristic] = True - if self.supported_features & SUPPORT_SET_SPEED: + if percentage is not None and self.supported_features & SUPPORT_SET_SPEED: characteristics[CharacteristicsTypes.ROTATION_SPEED] = percentage if characteristics: diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 26adb25df21b76..62382eec5eb577 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -11,6 +11,13 @@ from tests.components.light.conftest import mock_light_profiles # noqa +@pytest.fixture(autouse=True) +def mock_zeroconf(): + """Mock zeroconf.""" + with mock.patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc: + yield mock_zc.return_value + + @pytest.fixture def utcnow(request): """Freeze time at a known point.""" diff --git a/tests/components/homekit_controller/test_fan.py b/tests/components/homekit_controller/test_fan.py index b8d42b21643f89..d66ce81d5349f7 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -50,6 +50,38 @@ def create_fanv2_service(accessory): swing_mode.value = 0 +def create_fanv2_service_with_min_step(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + speed = service.add_char(CharacteristicsTypes.ROTATION_SPEED) + speed.value = 0 + speed.minStep = 25 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + +def create_fanv2_service_without_rotation_speed(accessory): + """Define fan v2 characteristics as per HAP spec.""" + service = accessory.add_service(ServicesTypes.FAN_V2) + + cur_state = service.add_char(CharacteristicsTypes.ACTIVE) + cur_state.value = 0 + + direction = service.add_char(CharacteristicsTypes.ROTATION_DIRECTION) + direction.value = 0 + + swing_mode = service.add_char(CharacteristicsTypes.SWING_MODE) + swing_mode.value = 0 + + 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) @@ -95,6 +127,29 @@ async def test_turn_on(hass, utcnow): assert helper.characteristics[V1_ROTATION_SPEED].value == 33.0 +async def test_turn_on_off_without_rotation_speed(hass, utcnow): + """Test that we can turn a fan on.""" + helper = await setup_test_component( + hass, create_fanv2_service_without_rotation_speed + ) + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 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) @@ -181,6 +236,7 @@ async def test_speed_read(hass, utcnow): state = await helper.poll_and_get_state() 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() @@ -277,6 +333,24 @@ async def test_v2_turn_on(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 1 assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 0 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.testdevice"}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 1 + assert helper.characteristics[V2_ROTATION_SPEED].value == 33.0 + async def test_v2_turn_off(hass, utcnow): """Test that we can turn a fan off.""" @@ -355,6 +429,29 @@ async def test_v2_set_percentage(hass, utcnow): assert helper.characteristics[V2_ACTIVE].value == 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 hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 66}, + blocking=True, + ) + assert helper.characteristics[V2_ROTATION_SPEED].value == 75 + + await hass.services.async_call( + "fan", + "set_percentage", + {"entity_id": "fan.testdevice", "percentage": 0}, + blocking=True, + ) + assert helper.characteristics[V2_ACTIVE].value == 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) From 9601cb74450cf5f7a51e01a72f9fa46a6682c0f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 07:34:34 -1000 Subject: [PATCH 1141/1818] Ensure template fan value_template always determines on state (#47598) --- homeassistant/components/template/fan.py | 3 --- tests/components/template/test_fan.py | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 18a7d8262e005f..51dce0f8d56191 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -523,7 +523,6 @@ def _update_speed(self, speed): speed = str(speed) if speed in self._speed_list: - self._state = STATE_OFF if speed == SPEED_OFF else STATE_ON self._speed = speed self._percentage = self.speed_to_percentage(speed) self._preset_mode = speed if speed in self.preset_modes else None @@ -552,7 +551,6 @@ def _update_percentage(self, percentage): return if 0 <= percentage <= 100: - self._state = STATE_OFF if percentage == 0 else STATE_ON self._percentage = percentage if self._speed_list: self._speed = self.percentage_to_speed(percentage) @@ -569,7 +567,6 @@ def _update_preset_mode(self, preset_mode): preset_mode = str(preset_mode) if preset_mode in self.preset_modes: - self._state = STATE_ON self._speed = preset_mode self._percentage = None self._preset_mode = preset_mode diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 2b9059017c6d50..34dccd7d172899 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -246,6 +246,10 @@ async def test_templates_with_entities(hass, calls): await hass.async_block_till_done() _verify(hass, STATE_ON, None, 0, True, DIRECTION_FORWARD, None) + hass.states.async_set(_STATE_INPUT_BOOLEAN, False) + await hass.async_block_till_done() + _verify(hass, STATE_OFF, None, 0, True, DIRECTION_FORWARD, None) + async def test_templates_with_entities_and_invalid_percentage(hass, calls): """Test templates with values from other entities.""" @@ -274,7 +278,7 @@ async def test_templates_with_entities_and_invalid_percentage(hass, calls): await hass.async_start() await hass.async_block_till_done() - _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + _verify(hass, STATE_ON, SPEED_OFF, 0, None, None, None) hass.states.async_set("sensor.percentage", "33") await hass.async_block_till_done() @@ -299,7 +303,7 @@ async def test_templates_with_entities_and_invalid_percentage(hass, calls): hass.states.async_set("sensor.percentage", "0") await hass.async_block_till_done() - _verify(hass, STATE_OFF, SPEED_OFF, 0, None, None, None) + _verify(hass, STATE_ON, SPEED_OFF, 0, None, None, None) async def test_templates_with_entities_and_preset_modes(hass, calls): From a51ad137a19fd1b81ec30c5c331bb255409efdae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 12:20:21 -1000 Subject: [PATCH 1142/1818] Fix insteon fan speeds (#47603) --- homeassistant/components/insteon/fan.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index a641d35345025c..2c397188640ea0 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -1,8 +1,6 @@ """Support for INSTEON fans via PowerLinc Modem.""" import math -from pyinsteon.constants import FanSpeed - from homeassistant.components.fan import ( DOMAIN as FAN_DOMAIN, SUPPORT_SET_SPEED, @@ -19,7 +17,7 @@ from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (1, FanSpeed.HIGH) # off is not included +SPEED_RANGE = (0x00, 0xFF) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): @@ -52,6 +50,11 @@ def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_SET_SPEED + @property + def speed_count(self) -> int: + """Flag supported features.""" + return 3 + async def async_turn_on( self, speed: str = None, @@ -60,9 +63,7 @@ async def async_turn_on( **kwargs, ) -> None: """Turn on the fan.""" - if percentage is None: - percentage = 50 - await self.async_set_percentage(percentage) + await self.async_set_percentage(percentage or 67) async def async_turn_off(self, **kwargs) -> None: """Turn off the fan.""" @@ -71,7 +72,7 @@ async def async_turn_off(self, **kwargs) -> None: async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" if percentage == 0: - await self._insteon_device.async_fan_off() - else: - on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) - await self._insteon_device.async_fan_on(on_level=on_level) + await self.async_turn_off() + return + on_level = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) + await self._insteon_device.async_on(group=2, on_level=on_level) From 58573dc74d026b203efe4ad3f966ce72a78a3556 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 12:19:05 -1000 Subject: [PATCH 1143/1818] Fix turning off scene in homekit (#47604) --- homeassistant/components/homekit/type_switches.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 1ce6c364896ef5..8ea19897420b82 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -27,7 +27,7 @@ STATE_ON, ) from homeassistant.core import callback, split_entity_id -from homeassistant.helpers.event import call_later +from homeassistant.helpers.event import async_call_later from .accessories import TYPES, HomeAccessory from .const import ( @@ -134,7 +134,7 @@ def set_state(self, value): self.async_call_service(self._domain, service, params) if self.activate_only: - call_later(self.hass, 1, self.reset_switch) + async_call_later(self.hass, 1, self.reset_switch) @callback def async_update_state(self, new_state): From 9f6007b4e2128f9e0082349bd7d2c3b1c3c4c0ed Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 8 Mar 2021 16:59:54 +0200 Subject: [PATCH 1144/1818] Fix Shelly logbook exception when missing COAP (#47620) --- homeassistant/components/shelly/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 0058374cfe7330..27152997ef70a7 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -177,9 +177,9 @@ def get_device_wrapper(hass: HomeAssistant, device_id: str): return None for config_entry in hass.data[DOMAIN][DATA_CONFIG_ENTRY]: - wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry][COAP] + wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry].get(COAP) - if wrapper.device_id == device_id: + if wrapper and wrapper.device_id == device_id: return wrapper return None From b352c5840faf8e3fe42921b3107fa6d3c872b349 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:11:54 -0500 Subject: [PATCH 1145/1818] Update zwave_js supported features list to be static (#47623) --- homeassistant/components/zwave_js/climate.py | 22 +++++++++-------- tests/components/zwave_js/test_climate.py | 26 +++++++++++++++++++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 325cf14b379df4..26e9e730283efe 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -162,6 +162,15 @@ def __init__( add_to_watched_value_ids=True, ) self._set_modes_and_presets() + self._supported_features = SUPPORT_PRESET_MODE + # If any setpoint value exists, we can assume temperature + # can be set + if any(self._setpoint_values.values()): + self._supported_features |= SUPPORT_TARGET_TEMPERATURE + if HVAC_MODE_HEAT_COOL in self.hvac_modes: + self._supported_features |= SUPPORT_TARGET_TEMPERATURE_RANGE + if self._fan_mode: + self._supported_features |= SUPPORT_FAN_MODE def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: """Optionally return a ZwaveValue for a setpoint.""" @@ -259,7 +268,7 @@ def target_temperature(self) -> Optional[float]: return None try: temp = self._setpoint_value(self._current_mode_setpoint_enums[0]) - except ValueError: + except (IndexError, ValueError): return None return temp.value if temp else None @@ -271,7 +280,7 @@ def target_temperature_high(self) -> Optional[float]: return None try: temp = self._setpoint_value(self._current_mode_setpoint_enums[1]) - except ValueError: + except (IndexError, ValueError): return None return temp.value if temp else None @@ -335,14 +344,7 @@ def device_state_attributes(self) -> Optional[Dict[str, str]]: @property def supported_features(self) -> int: """Return the list of supported features.""" - support = SUPPORT_PRESET_MODE - if len(self._current_mode_setpoint_enums) == 1: - support |= SUPPORT_TARGET_TEMPERATURE - if len(self._current_mode_setpoint_enums) > 1: - support |= SUPPORT_TARGET_TEMPERATURE_RANGE - if self._fan_mode: - support |= SUPPORT_FAN_MODE - return support + return self._supported_features async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index fe3e0708acc409..a31aad19603aca 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -24,9 +24,17 @@ SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE -from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, @@ -58,6 +66,13 @@ async def test_thermostat_v2( assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE assert state.attributes[ATTR_FAN_MODE] == "Auto low" assert state.attributes[ATTR_FAN_STATE] == "Idle / off" + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE + | SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + ) # Test setting preset mode await hass.services.async_call( @@ -408,6 +423,10 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) client.async_send_command_no_wait.reset_mock() @@ -491,6 +510,10 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] + == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE + ) async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): @@ -507,3 +530,4 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati HVAC_MODE_HEAT, ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_PRESET_MODE From b80c2d426c8d2da8a468830a8f9c4810dedae22e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Mar 2021 23:23:04 +0000 Subject: [PATCH 1146/1818] Bumped version to 2021.3.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 015a347a5e3ccd..ae748b3ccc2859 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 5d7b53603fdd3d6776c801bb9d5f7b22507b1146 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Mar 2021 13:44:55 -1000 Subject: [PATCH 1147/1818] Harmony: set confirm only (#47617) --- homeassistant/components/harmony/config_flow.py | 1 + tests/components/harmony/test_config_flow.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index 899edeb8a912f1..a91c1f3b5ca236 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -119,6 +119,7 @@ async def async_step_link(self, user_input=None): self.harmony_config, {} ) + self._set_confirm_only() return self.async_show_form( step_id="link", errors=errors, diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 2a7f80d5c2ffd8..7f651890868142 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -74,6 +74,10 @@ async def test_form_ssdp(hass): "host": "Harmony Hub", "name": "192.168.1.12", } + progress = hass.config_entries.flow.async_progress() + assert len(progress) == 1 + assert progress[0]["flow_id"] == result["flow_id"] + assert progress[0]["context"]["confirm_only"] is True with patch( "homeassistant.components.harmony.util.HarmonyAPI", From 8e58c3aa7bc8d2c2b09b6bd329daa1c092d52d3c Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 9 Mar 2021 01:12:52 +0100 Subject: [PATCH 1148/1818] Add error message to options flow if connection fails for nut integration (#46972) --- homeassistant/components/nut/config_flow.py | 24 +++++++++++++++---- homeassistant/components/nut/strings.json | 4 ++++ .../components/nut/translations/en.json | 4 ++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 7407958cdc00cf..b6f56192d8014e 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -6,6 +6,7 @@ from homeassistant import config_entries, core, exceptions from homeassistant.const import ( CONF_ALIAS, + CONF_BASE, CONF_HOST, CONF_PASSWORD, CONF_PORT, @@ -211,10 +212,10 @@ async def _async_validate_or_error(self, config): try: info = await validate_input(self.hass, config) except CannotConnect: - errors["base"] = "cannot_connect" + errors[CONF_BASE] = "cannot_connect" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + errors[CONF_BASE] = "unknown" return info, errors @staticmethod @@ -241,7 +242,17 @@ async def async_step_init(self, user_input=None): CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL ) - info = await validate_input(self.hass, self.config_entry.data) + errors = {} + try: + info = await validate_input(self.hass, self.config_entry.data) + except CannotConnect: + errors[CONF_BASE] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors[CONF_BASE] = "unknown" + + if errors: + return self.async_show_form(step_id="abort", errors=errors) base_schema = _resource_schema_base(info["available_resources"], resources) base_schema[ @@ -249,10 +260,13 @@ async def async_step_init(self, user_input=None): ] = cv.positive_int return self.async_show_form( - step_id="init", - data_schema=vol.Schema(base_schema), + step_id="init", data_schema=vol.Schema(base_schema), errors=errors ) + async def async_step_abort(self, user_input=None): + """Abort options flow.""" + return self.async_create_entry(title="", data=self.config_entry.options) + class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/nut/strings.json b/homeassistant/components/nut/strings.json index 1b71280b6a95c8..97e637fdcb35c2 100644 --- a/homeassistant/components/nut/strings.json +++ b/homeassistant/components/nut/strings.json @@ -41,6 +41,10 @@ "scan_interval": "Scan Interval (seconds)" } } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" } } } diff --git a/homeassistant/components/nut/translations/en.json b/homeassistant/components/nut/translations/en.json index 2e5db79d81c328..3d57189f7a5deb 100644 --- a/homeassistant/components/nut/translations/en.json +++ b/homeassistant/components/nut/translations/en.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error" + }, "step": { "init": { "data": { From 34b9e6f6fce982ecb2f9bf4b95d1318ed7142809 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 04:13:43 -0800 Subject: [PATCH 1149/1818] Shelly: set confirm only (#47608) --- .../components/shelly/config_flow.py | 54 ++++------- tests/components/shelly/test_config_flow.py | 95 +++---------------- 2 files changed, 31 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index dfb078ee9c7cf1..0ad95d67833b5d 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -160,52 +160,34 @@ async def async_step_zeroconf(self, zeroconf_info): self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] - if not info["auth"] and info.get("sleep_mode", False): - try: - self.device_info = await validate_input(self.hass, self.host, {}) - except HTTP_CONNECT_ERRORS: - return self.async_abort(reason="cannot_connect") - self.context["title_placeholders"] = { "name": zeroconf_info.get("name", "").split(".")[0] } + + if info["auth"]: + return await self.async_step_credentials() + + try: + self.device_info = await validate_input(self.hass, self.host, {}) + except HTTP_CONNECT_ERRORS: + return self.async_abort(reason="cannot_connect") + return await self.async_step_confirm_discovery() async def async_step_confirm_discovery(self, user_input=None): """Handle discovery confirm.""" errors = {} if user_input is not None: - if self.info["auth"]: - return await self.async_step_credentials() - - if self.device_info: - return self.async_create_entry( - title=self.device_info["title"] or self.device_info["hostname"], - data={ - "host": self.host, - "sleep_period": self.device_info["sleep_period"], - "model": self.device_info["model"], - }, - ) + return self.async_create_entry( + title=self.device_info["title"] or self.device_info["hostname"], + data={ + "host": self.host, + "sleep_period": self.device_info["sleep_period"], + "model": self.device_info["model"], + }, + ) - try: - device_info = await validate_input(self.hass, self.host, {}) - except HTTP_CONNECT_ERRORS: - errors["base"] = "cannot_connect" - except aioshelly.AuthRequired: - return await self.async_step_credentials() - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: - return self.async_create_entry( - title=device_info["title"] or device_info["hostname"], - data={ - "host": self.host, - "sleep_period": device_info["sleep_period"], - "model": device_info["model"], - }, - ) + self._set_confirm_only() return self.async_show_form( step_id="confirm_discovery", diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 9dfbc19255be0a..463c9111a60efb 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -338,6 +338,13 @@ async def test_zeroconf(hass): with patch( "aioshelly.get_info", return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + ), patch( + "aioshelly.Device.create", + new=AsyncMock( + return_value=Mock( + settings=MOCK_SETTINGS, + ) + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -352,14 +359,8 @@ async def test_zeroconf(hass): if flow["flow_id"] == result["flow_id"] ) assert context["title_placeholders"]["name"] == "shelly1pm-12345" + assert context["confirm_only"] is True with patch( - "aioshelly.Device.create", - new=AsyncMock( - return_value=Mock( - settings=MOCK_SETTINGS, - ) - ), - ), patch( "homeassistant.components.shelly.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.shelly.async_setup_entry", @@ -479,69 +480,6 @@ async def test_zeroconf_sleeping_device_error(hass, error): assert result["reason"] == "cannot_connect" -@pytest.mark.parametrize( - "error", [(asyncio.TimeoutError, "cannot_connect"), (ValueError, "unknown")] -) -async def test_zeroconf_confirm_error(hass, error): - """Test we get the form.""" - exc, base_error = error - await setup.async_setup_component(hass, "persistent_notification", {}) - - with patch( - "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - data=DISCOVERY_INFO, - context={"source": config_entries.SOURCE_ZEROCONF}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "aioshelly.Device.create", - new=AsyncMock(side_effect=exc), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["errors"] == {"base": base_error} - - -async def test_zeroconf_confirm_auth_error(hass): - """Test we get credentials form after an auth error when confirming discovery.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - - with patch( - "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - data=DISCOVERY_INFO, - context={"source": config_entries.SOURCE_ZEROCONF}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - with patch( - "aioshelly.Device.create", - new=AsyncMock(side_effect=aioshelly.AuthRequired), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["step_id"] == "credentials" - assert result2["errors"] == {} - - async def test_zeroconf_already_configured(hass): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -607,13 +545,6 @@ async def test_zeroconf_require_auth(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {} - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {}, - ) - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["errors"] == {} - with patch( "aioshelly.Device.create", new=AsyncMock( @@ -627,15 +558,15 @@ async def test_zeroconf_require_auth(hass): "homeassistant.components.shelly.async_setup_entry", return_value=True, ) as mock_setup_entry: - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"username": "test username", "password": "test password"}, ) await hass.async_block_till_done() - assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result3["title"] == "Test name" - assert result3["data"] == { + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Test name" + assert result2["data"] == { "host": "1.1.1.1", "model": "SHSW-1", "sleep_period": 0, From 19f67335ec94093ee2bd2c6f83e9d974662cd406 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 9 Mar 2021 14:45:58 +0200 Subject: [PATCH 1150/1818] Revert Shelly temperature sensor name change (#47664) --- homeassistant/components/shelly/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 472f3be4daeef4..10237629223ccd 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -133,7 +133,7 @@ available=lambda block: block.sensorOp == "normal", ), ("sensor", "extTemp"): BlockAttributeDescription( - name="External Temperature", + name="Temperature", unit=temperature_unit, value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_TEMPERATURE, From 3a054c3be7a61a253831bc5d53ca8014f5762edb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 13:58:43 +0100 Subject: [PATCH 1151/1818] Replace Entity.device_state_attributes with Entity.extra_state_attributes (#47304) --- .../components/huawei_lte/binary_sensor.py | 4 +--- homeassistant/helpers/entity.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index 9a5f148d1388d7..ea9d68bc843e54 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -144,10 +144,8 @@ def entity_registry_enabled_default(self) -> bool: @property def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Get additional attributes related to connection status.""" - attributes = super().device_state_attributes + attributes = {} if self._raw_state in CONNECTION_STATE_ATTRIBUTES: - if attributes is None: - attributes = {} attributes["additional_state"] = CONNECTION_STATE_ATTRIBUTES[ self._raw_state ] diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7d0e38ab119bff..7afe1b2ad25293 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -160,14 +160,23 @@ def capability_attributes(self) -> Optional[Dict[str, Any]]: def state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes. - Implemented by component base class. Convention for attribute names - is lowercase snake_case. + Implemented by component base class, should not be extended by integrations. + Convention for attribute names is lowercase snake_case. """ return None @property def device_state_attributes(self) -> Optional[Dict[str, Any]]: - """Return device specific state attributes. + """Return entity specific state attributes. + + This method is deprecated, platform classes should implement + extra_state_attributes instead. + """ + return None + + @property + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return entity specific state attributes. Implemented by platform classes. Convention for attribute names is lowercase snake_case. @@ -319,7 +328,12 @@ def _async_write_ha_state(self) -> None: sstate = self.state state = STATE_UNKNOWN if sstate is None else str(sstate) attr.update(self.state_attributes or {}) - attr.update(self.device_state_attributes or {}) + extra_state_attributes = self.extra_state_attributes + # Backwards compatibility for "device_state_attributes" deprecated in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + if extra_state_attributes is None: + extra_state_attributes = self.device_state_attributes + attr.update(extra_state_attributes or {}) unit_of_measurement = self.unit_of_measurement if unit_of_measurement is not None: From 78b21b1ad1835d26bac8cb0955c5e41031e013fb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:24:34 +0100 Subject: [PATCH 1152/1818] Update tests a-b to use async_get() instead of async_get_registry() (#47651) --- .../components/abode/test_alarm_control_panel.py | 3 ++- tests/components/abode/test_binary_sensor.py | 3 ++- tests/components/abode/test_camera.py | 3 ++- tests/components/abode/test_cover.py | 3 ++- tests/components/abode/test_light.py | 3 ++- tests/components/abode/test_lock.py | 3 ++- tests/components/abode/test_sensor.py | 3 ++- tests/components/abode/test_switch.py | 3 ++- tests/components/accuweather/test_sensor.py | 9 +++++---- tests/components/accuweather/test_weather.py | 5 +++-- .../advantage_air/test_binary_sensor.py | 3 ++- tests/components/advantage_air/test_climate.py | 3 ++- tests/components/advantage_air/test_cover.py | 3 ++- tests/components/advantage_air/test_sensor.py | 3 ++- tests/components/advantage_air/test_switch.py | 4 ++-- tests/components/airly/test_air_quality.py | 3 ++- tests/components/airly/test_sensor.py | 3 ++- tests/components/asuswrt/test_sensor.py | 3 ++- tests/components/atag/test_climate.py | 4 ++-- tests/components/atag/test_sensors.py | 4 ++-- tests/components/atag/test_water_heater.py | 4 ++-- tests/components/august/test_binary_sensor.py | 4 ++-- tests/components/august/test_lock.py | 6 +++--- tests/components/august/test_sensor.py | 16 ++++++++-------- tests/components/awair/test_sensor.py | 14 +++++++------- tests/components/axis/test_init.py | 4 ++-- tests/components/blebox/conftest.py | 3 ++- tests/components/blebox/test_air_quality.py | 4 ++-- tests/components/blebox/test_climate.py | 4 ++-- tests/components/blebox/test_cover.py | 8 ++++---- tests/components/blebox/test_light.py | 8 ++++---- tests/components/blebox/test_sensor.py | 4 ++-- tests/components/blebox/test_switch.py | 8 ++++---- tests/components/bond/test_cover.py | 3 ++- tests/components/bond/test_fan.py | 3 ++- tests/components/bond/test_init.py | 8 ++++---- tests/components/bond/test_light.py | 13 +++++++------ tests/components/bond/test_switch.py | 3 ++- tests/components/brother/test_sensor.py | 5 +++-- tests/test_config_entries.py | 3 ++- 40 files changed, 110 insertions(+), 86 deletions(-) diff --git a/tests/components/abode/test_alarm_control_panel.py b/tests/components/abode/test_alarm_control_panel.py index 63ae20441f5bad..55ff22b9ee3922 100644 --- a/tests/components/abode/test_alarm_control_panel.py +++ b/tests/components/abode/test_alarm_control_panel.py @@ -16,6 +16,7 @@ STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -25,7 +26,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, ALARM_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) # Abode alarm device unique_id is the MAC address diff --git a/tests/components/abode/test_binary_sensor.py b/tests/components/abode/test_binary_sensor.py index a826191ccf3f6d..e4aa08c7f5fd77 100644 --- a/tests/components/abode/test_binary_sensor.py +++ b/tests/components/abode/test_binary_sensor.py @@ -11,6 +11,7 @@ ATTR_FRIENDLY_NAME, STATE_OFF, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -18,7 +19,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, BINARY_SENSOR_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("binary_sensor.front_door") assert entry.unique_id == "2834013428b6035fba7d4054aa7b25a3" diff --git a/tests/components/abode/test_camera.py b/tests/components/abode/test_camera.py index 06540955464a4f..7dc943a0a741a0 100644 --- a/tests/components/abode/test_camera.py +++ b/tests/components/abode/test_camera.py @@ -4,6 +4,7 @@ from homeassistant.components.abode.const import DOMAIN as ABODE_DOMAIN from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, STATE_IDLE +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -11,7 +12,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, CAMERA_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("camera.test_cam") assert entry.unique_id == "d0a3a1c316891ceb00c20118aae2a133" diff --git a/tests/components/abode/test_cover.py b/tests/components/abode/test_cover.py index bb1b8fceffb4d6..edd40a867079ab 100644 --- a/tests/components/abode/test_cover.py +++ b/tests/components/abode/test_cover.py @@ -10,6 +10,7 @@ SERVICE_OPEN_COVER, STATE_CLOSED, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -19,7 +20,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, COVER_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry.unique_id == "61cbz3b542d2o33ed2fz02721bda3324" diff --git a/tests/components/abode/test_light.py b/tests/components/abode/test_light.py index f0eee4b209bd62..b5160aece2a26b 100644 --- a/tests/components/abode/test_light.py +++ b/tests/components/abode/test_light.py @@ -16,6 +16,7 @@ SERVICE_TURN_ON, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -25,7 +26,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, LIGHT_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry.unique_id == "741385f4388b2637df4c6b398fe50581" diff --git a/tests/components/abode/test_lock.py b/tests/components/abode/test_lock.py index 45e17861d33216..c688b6f02bcda4 100644 --- a/tests/components/abode/test_lock.py +++ b/tests/components/abode/test_lock.py @@ -10,6 +10,7 @@ SERVICE_UNLOCK, STATE_LOCKED, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -19,7 +20,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, LOCK_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(DEVICE_ID) assert entry.unique_id == "51cab3b545d2o34ed7fz02731bda5324" diff --git a/tests/components/abode/test_sensor.py b/tests/components/abode/test_sensor.py index d99fac50ddee69..5e3195430ab181 100644 --- a/tests/components/abode/test_sensor.py +++ b/tests/components/abode/test_sensor.py @@ -9,6 +9,7 @@ PERCENTAGE, TEMP_CELSIUS, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -16,7 +17,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, SENSOR_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.environment_sensor_humidity") assert entry.unique_id == "13545b21f4bdcd33d9abd461f8443e65-humidity" diff --git a/tests/components/abode/test_switch.py b/tests/components/abode/test_switch.py index 3ec9648d87de0c..829c5e8ae374e1 100644 --- a/tests/components/abode/test_switch.py +++ b/tests/components/abode/test_switch.py @@ -13,6 +13,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -25,7 +26,7 @@ async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, SWITCH_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(AUTOMATION_ID) assert entry.unique_id == AUTOMATION_UID diff --git a/tests/components/accuweather/test_sensor.py b/tests/components/accuweather/test_sensor.py index 361422883d4e71..bafad72ec0bd84 100644 --- a/tests/components/accuweather/test_sensor.py +++ b/tests/components/accuweather/test_sensor.py @@ -22,6 +22,7 @@ TIME_HOURS, UV_INDEX, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -32,7 +33,7 @@ async def test_sensor_without_forecast(hass): """Test states of the sensor without forecast.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.home_cloud_ceiling") assert state @@ -94,7 +95,7 @@ async def test_sensor_without_forecast(hass): async def test_sensor_with_forecast(hass): """Test states of the sensor with forecast.""" await init_integration(hass, forecast=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.home_hours_of_sun_0d") assert state @@ -166,7 +167,7 @@ async def test_sensor_with_forecast(hass): async def test_sensor_disabled(hass): """Test sensor disabled by default.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.home_apparent_temperature") assert entry @@ -185,7 +186,7 @@ async def test_sensor_disabled(hass): async def test_sensor_enabled_without_forecast(hass): """Test enabling an advanced sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( SENSOR_DOMAIN, diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index 0c1559ef0d6be3..8190d96e634772 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -23,6 +23,7 @@ ATTR_WEATHER_WIND_SPEED, ) from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -33,7 +34,7 @@ async def test_weather_without_forecast(hass): """Test states of the weather without forecast.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("weather.home") assert state @@ -56,7 +57,7 @@ async def test_weather_without_forecast(hass): async def test_weather_with_forecast(hass): """Test states of the weather with forecast.""" await init_integration(hass, forecast=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("weather.home") assert state diff --git a/tests/components/advantage_air/test_binary_sensor.py b/tests/components/advantage_air/test_binary_sensor.py index d0b1a90aaada40..dee4b9fd99a317 100644 --- a/tests/components/advantage_air/test_binary_sensor.py +++ b/tests/components/advantage_air/test_binary_sensor.py @@ -1,6 +1,7 @@ """Test the Advantage Air Binary Sensor Platform.""" from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -24,7 +25,7 @@ async def test_binary_sensor_async_setup_entry(hass, aioclient_mock): ) await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_climate.py b/tests/components/advantage_air/test_climate.py index 7eb1729a5db092..ea0cf02546211d 100644 --- a/tests/components/advantage_air/test_climate.py +++ b/tests/components/advantage_air/test_climate.py @@ -22,6 +22,7 @@ SERVICE_SET_TEMPERATURE, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -45,7 +46,7 @@ async def test_climate_async_setup_entry(hass, aioclient_mock): ) await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_cover.py b/tests/components/advantage_air/test_cover.py index 18fd4f05b5aaf8..29f0d288fdb117 100644 --- a/tests/components/advantage_air/test_cover.py +++ b/tests/components/advantage_air/test_cover.py @@ -15,6 +15,7 @@ SERVICE_SET_COVER_POSITION, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OPEN +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -39,7 +40,7 @@ async def test_cover_async_setup_entry(hass, aioclient_mock): await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_sensor.py b/tests/components/advantage_air/test_sensor.py index e420ab978fd955..684b965d94f712 100644 --- a/tests/components/advantage_air/test_sensor.py +++ b/tests/components/advantage_air/test_sensor.py @@ -8,6 +8,7 @@ ADVANTAGE_AIR_SET_COUNTDOWN_VALUE, ) from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -31,7 +32,7 @@ async def test_sensor_platform(hass, aioclient_mock): ) await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/advantage_air/test_switch.py b/tests/components/advantage_air/test_switch.py index f45477adc70fc3..1a78025df70b76 100644 --- a/tests/components/advantage_air/test_switch.py +++ b/tests/components/advantage_air/test_switch.py @@ -1,5 +1,4 @@ """Test the Advantage Air Switch Platform.""" - from json import loads from homeassistant.components.advantage_air.const import ( @@ -12,6 +11,7 @@ SERVICE_TURN_ON, ) from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF +from homeassistant.helpers import entity_registry as er from tests.components.advantage_air import ( TEST_SET_RESPONSE, @@ -36,7 +36,7 @@ async def test_cover_async_setup_entry(hass, aioclient_mock): await add_mock_config(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/components/airly/test_air_quality.py b/tests/components/airly/test_air_quality.py index 24a98cbf155415..de059e84aa4a69 100644 --- a/tests/components/airly/test_air_quality.py +++ b/tests/components/airly/test_air_quality.py @@ -23,6 +23,7 @@ HTTP_INTERNAL_SERVER_ERROR, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -35,7 +36,7 @@ async def test_air_quality(hass, aioclient_mock): """Test states of the air_quality.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("air_quality.home") assert state diff --git a/tests/components/airly/test_sensor.py b/tests/components/airly/test_sensor.py index abc53294bbc765..925f3acb6d2495 100644 --- a/tests/components/airly/test_sensor.py +++ b/tests/components/airly/test_sensor.py @@ -17,6 +17,7 @@ STATE_UNAVAILABLE, TEMP_CELSIUS, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -29,7 +30,7 @@ async def test_sensor(hass, aioclient_mock): """Test states of the sensor.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.home_humidity") assert state diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 4a8a1f27653d2c..e69af0cd3222f6 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -19,6 +19,7 @@ STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import entity_registry as er from homeassistant.util.dt import utcnow from tests.common import MockConfigEntry, async_fire_time_changed @@ -64,7 +65,7 @@ def mock_controller_connect(): async def test_sensors(hass, connect): """Test creating an AsusWRT sensor.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # init config entry config_entry = MockConfigEntry( diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py index 3d511821bafb0f..e263ade7c75aa0 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -1,5 +1,4 @@ """Tests for the Atag climate platform.""" - from unittest.mock import PropertyMock, patch from homeassistant.components.atag import CLIMATE, DOMAIN @@ -19,6 +18,7 @@ ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.components.atag import UID, init_integration @@ -33,7 +33,7 @@ async def test_climate( """Test the creation and values of Atag climate device.""" with patch("pyatag.entities.Climate.status"): entry = await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert registry.async_is_registered(CLIMATE_ID) entry = registry.async_get(CLIMATE_ID) diff --git a/tests/components/atag/test_sensors.py b/tests/components/atag/test_sensors.py index e7bf4df44e9040..aeefcb2789b97d 100644 --- a/tests/components/atag/test_sensors.py +++ b/tests/components/atag/test_sensors.py @@ -1,7 +1,7 @@ """Tests for the Atag sensor platform.""" - from homeassistant.components.atag.sensor import SENSORS from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.components.atag import UID, init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -12,7 +12,7 @@ async def test_sensors( ) -> None: """Test the creation of ATAG sensors.""" entry = await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for item in SENSORS: sensor_id = "_".join(f"sensor.{item}".lower().split()) diff --git a/tests/components/atag/test_water_heater.py b/tests/components/atag/test_water_heater.py index 5eb219fa3bcd6d..4c78302224d359 100644 --- a/tests/components/atag/test_water_heater.py +++ b/tests/components/atag/test_water_heater.py @@ -1,11 +1,11 @@ """Tests for the Atag water heater platform.""" - from unittest.mock import patch from homeassistant.components.atag import DOMAIN, WATER_HEATER from homeassistant.components.water_heater import SERVICE_SET_TEMPERATURE from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.components.atag import UID, init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -19,7 +19,7 @@ async def test_water_heater( """Test the creation of Atag water heater.""" with patch("pyatag.entities.DHW.status"): entry = await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert registry.async_is_registered(WATER_HEATER_ID) entry = registry.async_get(WATER_HEATER_ID) diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 763f9f9528f8ed..0e337813f52605 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -1,5 +1,4 @@ """The binary_sensor tests for the august platform.""" - from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -9,6 +8,7 @@ STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr from tests.components.august.mocks import ( _create_august_with_devices, @@ -119,7 +119,7 @@ async def test_doorbell_device_registry(hass): doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json") await _create_august_with_devices(hass, [doorbell_one]) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device(identifiers={("august", "tmt100")}) assert reg_device.model == "hydra1" diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index d013da30ff6435..dadd6de2d4fa63 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -1,5 +1,4 @@ """The lock tests for the august platform.""" - from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -9,6 +8,7 @@ STATE_UNKNOWN, STATE_UNLOCKED, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.august.mocks import ( _create_august_with_devices, @@ -23,7 +23,7 @@ async def test_lock_device_registry(hass): lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) await _create_august_with_devices(hass, [lock_one]) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={("august", "online_with_doorsense")} @@ -90,7 +90,7 @@ async def test_one_lock_operation(hass): assert lock_online_with_doorsense_name.state == STATE_LOCKED # No activity means it will be unavailable until the activity feed has data - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) diff --git a/tests/components/august/test_sensor.py b/tests/components/august/test_sensor.py index fb7ddfde979a32..254f88ef4e913a 100644 --- a/tests/components/august/test_sensor.py +++ b/tests/components/august/test_sensor.py @@ -1,6 +1,6 @@ """The sensor tests for the august platform.""" - from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, STATE_UNKNOWN +from homeassistant.helpers import entity_registry as er from tests.components.august.mocks import ( _create_august_with_devices, @@ -29,7 +29,7 @@ async def test_create_doorbell_offline(hass): """Test creation of a doorbell that is offline.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json") await _create_august_with_devices(hass, [doorbell_one]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor_tmt100_name_battery = hass.states.get("sensor.tmt100_name_battery") assert sensor_tmt100_name_battery.state == "81" @@ -55,7 +55,7 @@ async def test_create_lock_with_linked_keypad(hass): """Test creation of a lock with a linked keypad that both have a battery.""" lock_one = await _mock_lock_from_fixture(hass, "get_lock.doorsense_init.json") await _create_august_with_devices(hass, [lock_one]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor_a6697750d607098bae8d6baa11ef8063_name_battery = hass.states.get( "sensor.a6697750d607098bae8d6baa11ef8063_name_battery" @@ -85,7 +85,7 @@ async def test_create_lock_with_low_battery_linked_keypad(hass): """Test creation of a lock with a linked keypad that both have a battery.""" lock_one = await _mock_lock_from_fixture(hass, "get_lock.low_keypad_battery.json") await _create_august_with_devices(hass, [lock_one]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor_a6697750d607098bae8d6baa11ef8063_name_battery = hass.states.get( "sensor.a6697750d607098bae8d6baa11ef8063_name_battery" @@ -133,7 +133,7 @@ async def test_lock_operator_bluetooth(hass): ) await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) @@ -177,7 +177,7 @@ async def test_lock_operator_keypad(hass): ) await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) @@ -219,7 +219,7 @@ async def test_lock_operator_remote(hass): activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json") await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) @@ -263,7 +263,7 @@ async def test_lock_operator_autorelock(hass): ) await _create_august_with_devices(hass, [lock_one], activities=activities) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) lock_operator_sensor = entity_registry.async_get( "sensor.online_with_doorsense_name_operator" ) diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index 0fcbab99a3a207..b37e8dbf5d2c33 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -1,5 +1,4 @@ """Tests for the Awair sensor platform.""" - from unittest.mock import patch from homeassistant.components.awair.const import ( @@ -27,6 +26,7 @@ STATE_UNAVAILABLE, TEMP_CELSIUS, ) +from homeassistant.helpers import entity_registry as er from .const import ( AWAIR_UUID, @@ -74,7 +74,7 @@ async def test_awair_gen1_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GEN1_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -170,7 +170,7 @@ async def test_awair_gen2_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GEN2_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -204,7 +204,7 @@ async def test_awair_mint_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, MINT_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -246,7 +246,7 @@ async def test_awair_glow_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GLOW_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -266,7 +266,7 @@ async def test_awair_omni_sensors(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, OMNI_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, @@ -319,7 +319,7 @@ async def test_awair_unavailable(hass): fixtures = [USER_FIXTURE, DEVICES_FIXTURE, GEN1_DATA_FIXTURE] await setup_awair(hass, fixtures) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert_expected_properties( hass, diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index 36a603ea7b339d..5ca9fb1eb8dbaf 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -13,7 +13,7 @@ CONF_PORT, CONF_USERNAME, ) -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import format_mac from homeassistant.setup import async_setup_component @@ -83,7 +83,7 @@ async def test_migrate_entry(hass): assert not entry.unique_id # Create entity entry to migrate to new unique ID - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( BINARY_SENSOR_DOMAIN, AXIS_DOMAIN, diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index c603c15c32b4dc..a63a0090c3a3e4 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -7,6 +7,7 @@ from homeassistant.components.blebox.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -84,7 +85,7 @@ async def async_setup_entities(hass, config, entity_ids): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) return [entity_registry.async_get(entity_id) for entity_id in entity_ids] diff --git a/tests/components/blebox/test_air_quality.py b/tests/components/blebox/test_air_quality.py index 4f1f6dff6717b4..8b5bc67d4bc0fe 100644 --- a/tests/components/blebox/test_air_quality.py +++ b/tests/components/blebox/test_air_quality.py @@ -1,5 +1,4 @@ """Blebox air_quality tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -8,6 +7,7 @@ from homeassistant.components.air_quality import ATTR_PM_0_1, ATTR_PM_2_5, ATTR_PM_10 from homeassistant.const import ATTR_ICON, STATE_UNKNOWN +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -49,7 +49,7 @@ async def test_init(airsensor, hass, config): assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My air sensor" diff --git a/tests/components/blebox/test_climate.py b/tests/components/blebox/test_climate.py index baaa5a5009ee35..0b27846e654cae 100644 --- a/tests/components/blebox/test_climate.py +++ b/tests/components/blebox/test_climate.py @@ -1,5 +1,4 @@ """BleBox climate entities tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -28,6 +27,7 @@ ATTR_TEMPERATURE, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -79,7 +79,7 @@ async def test_init(saunabox, hass, config): assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My sauna" diff --git a/tests/components/blebox/test_cover.py b/tests/components/blebox/test_cover.py index a5d3a8f705bad5..8355411a0bb0e7 100644 --- a/tests/components/blebox/test_cover.py +++ b/tests/components/blebox/test_cover.py @@ -1,5 +1,4 @@ """BleBox cover entities tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -30,6 +29,7 @@ SERVICE_STOP_COVER, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -117,7 +117,7 @@ async def test_init_gatecontroller(gatecontroller, hass, config): assert ATTR_CURRENT_POSITION not in state.attributes assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My gate controller" @@ -147,7 +147,7 @@ async def test_init_shutterbox(shutterbox, hass, config): assert ATTR_CURRENT_POSITION not in state.attributes assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My shutter" @@ -179,7 +179,7 @@ async def test_init_gatebox(gatebox, hass, config): assert ATTR_CURRENT_POSITION not in state.attributes assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My gatebox" diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index 5d9e5709e4d0b2..6c8c26fe9383e6 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -1,5 +1,4 @@ """BleBox light entities tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -21,6 +20,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.helpers import device_registry as dr from homeassistant.util import color from .conftest import async_setup_entity, mock_feature @@ -65,7 +65,7 @@ async def test_dimmer_init(dimmer, hass, config): assert state.attributes[ATTR_BRIGHTNESS] == 65 assert state.state == STATE_ON - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My dimmer" @@ -236,7 +236,7 @@ async def test_wlightbox_s_init(wlightbox_s, hass, config): assert ATTR_BRIGHTNESS not in state.attributes assert state.state == STATE_OFF - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My wLightBoxS" @@ -339,7 +339,7 @@ async def test_wlightbox_init(wlightbox, hass, config): assert ATTR_BRIGHTNESS not in state.attributes assert state.state == STATE_OFF - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My wLightBox" diff --git a/tests/components/blebox/test_sensor.py b/tests/components/blebox/test_sensor.py index aeb726cc726d05..2281c4ea68cde2 100644 --- a/tests/components/blebox/test_sensor.py +++ b/tests/components/blebox/test_sensor.py @@ -1,5 +1,4 @@ """Blebox sensors tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -13,6 +12,7 @@ STATE_UNKNOWN, TEMP_CELSIUS, ) +from homeassistant.helpers import device_registry as dr from .conftest import async_setup_entity, mock_feature @@ -49,7 +49,7 @@ async def test_init(tempsensor, hass, config): assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS assert state.state == STATE_UNKNOWN - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My temperature sensor" diff --git a/tests/components/blebox/test_switch.py b/tests/components/blebox/test_switch.py index e2bc1240510a17..e67c0479cb3f7d 100644 --- a/tests/components/blebox/test_switch.py +++ b/tests/components/blebox/test_switch.py @@ -1,5 +1,4 @@ """Blebox switch tests.""" - import logging from unittest.mock import AsyncMock, PropertyMock @@ -14,6 +13,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.helpers import device_registry as dr from .conftest import ( async_setup_entities, @@ -58,7 +58,7 @@ async def test_switchbox_init(switchbox, hass, config): assert state.state == STATE_OFF - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My switch box" @@ -204,7 +204,7 @@ async def test_switchbox_d_init(switchbox_d, hass, config): assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN? - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My relays" @@ -221,7 +221,7 @@ async def test_switchbox_d_init(switchbox_d, hass, config): assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN? - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My relays" diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index dbb8ee0f3b73f6..f516d84d50a634 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -11,6 +11,7 @@ SERVICE_OPEN_COVER, SERVICE_STOP_COVER, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -39,7 +40,7 @@ async def test_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["cover.name_1"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index 49a6e4a5b686e5..ca3bc9ac7e796c 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -18,6 +18,7 @@ SPEED_OFF, ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -71,7 +72,7 @@ async def test_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["fan.name_1"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index 7346acc52761e4..0bba04b4d97ceb 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -79,7 +79,7 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(hass: HomeAss assert config_entry.unique_id == "test-bond-id" # verify hub device is registered correctly - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) hub = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) assert hub.name == "bond-name" assert hub.manufacturer == "Olibra" @@ -127,7 +127,7 @@ async def test_old_identifiers_are_removed(hass: HomeAssistant): old_identifers = (DOMAIN, "device_id") new_identifiers = (DOMAIN, "test-bond-id", "device_id") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={old_identifers}, @@ -204,7 +204,7 @@ async def test_smart_by_bond_device_suggested_area(hass: HomeAssistant): assert config_entry.state == ENTRY_STATE_LOADED assert config_entry.unique_id == "test-bond-id" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) assert device is not None assert device.suggested_area == "Den" @@ -250,7 +250,7 @@ async def test_bridge_device_suggested_area(hass: HomeAssistant): assert config_entry.state == ENTRY_STATE_LOADED assert config_entry.unique_id == "test-bond-id" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(DOMAIN, "test-bond-id")}) assert device is not None assert device.suggested_area == "Office" diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index 59d051fbe8671c..e59efcd7bcff2a 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -16,6 +16,7 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -107,7 +108,7 @@ async def test_fan_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fan_name"] assert entity.unique_id == "test-hub-id_test-device-id" @@ -122,7 +123,7 @@ async def test_fan_up_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fan_name_up_light"] assert entity.unique_id == "test-hub-id_test-device-id_up_light" @@ -137,7 +138,7 @@ async def test_fan_down_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fan_name_down_light"] assert entity.unique_id == "test-hub-id_test-device-id_down_light" @@ -152,7 +153,7 @@ async def test_fireplace_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.fireplace_name"] assert entity.unique_id == "test-hub-id_test-device-id" @@ -167,7 +168,7 @@ async def test_fireplace_with_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity_flame = registry.entities["light.fireplace_name"] assert entity_flame.unique_id == "test-hub-id_test-device-id" entity_light = registry.entities["light.fireplace_name_light"] @@ -184,7 +185,7 @@ async def test_light_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["light.light_name"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/bond/test_switch.py b/tests/components/bond/test_switch.py index f2568f5fb9940d..94a9179d3a7b96 100644 --- a/tests/components/bond/test_switch.py +++ b/tests/components/bond/test_switch.py @@ -6,6 +6,7 @@ from homeassistant import core from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -34,7 +35,7 @@ async def test_entity_registry(hass: core.HomeAssistant): bond_device_id="test-device-id", ) - registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + registry: EntityRegistry = er.async_get(hass) entity = registry.entities["switch.name_1"] assert entity.unique_id == "test-hub-id_test-device-id" diff --git a/tests/components/brother/test_sensor.py b/tests/components/brother/test_sensor.py index b386b0753b79ce..ab48721dec545f 100644 --- a/tests/components/brother/test_sensor.py +++ b/tests/components/brother/test_sensor.py @@ -14,6 +14,7 @@ PERCENTAGE, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import UTC, utcnow @@ -28,7 +29,7 @@ async def test_sensors(hass): """Test states of the sensors.""" entry = await init_integration(hass, skip_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -241,7 +242,7 @@ async def test_disabled_by_default_sensors(hass): """Test the disabled by default Brother sensors.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.hl_l2340dw_uptime") assert state is None diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 8a479a802e45fa..9abb68e97c9872 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -8,6 +8,7 @@ from homeassistant import config_entries, data_entry_flow, loader from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -299,7 +300,7 @@ async def mock_setup_entry_platform(hass, entry, async_add_entities): assert len(hass.states.async_all()) == 1 # Check entity got added to entity registry - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 entity_entry = list(ent_reg.entities.values())[0] assert entity_entry.config_entry_id == entry.entry_id From b3fecb1c958a9571c6ddf453656477fa95d8881f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:25:03 +0100 Subject: [PATCH 1153/1818] Update tests t-z to use async_get() instead of async_get_registry() (#47655) --- tests/components/tasmota/test_common.py | 13 ++--- tests/components/tasmota/test_sensor.py | 9 ++-- tests/components/timer/test_init.py | 10 ++-- tests/components/traccar/test_init.py | 5 +- tests/components/tradfri/test_init.py | 9 ++-- tests/components/twinkly/test_twinkly.py | 6 +-- tests/components/unifi/test_device_tracker.py | 8 +-- tests/components/unifi/test_switch.py | 6 +-- tests/components/vera/test_init.py | 7 +-- tests/components/wemo/conftest.py | 3 +- tests/components/wemo/test_init.py | 9 ++-- tests/components/wilight/test_cover.py | 3 +- tests/components/wilight/test_fan.py | 3 +- tests/components/wilight/test_light.py | 3 +- .../components/withings/test_binary_sensor.py | 5 +- tests/components/withings/test_sensor.py | 9 ++-- tests/components/wled/test_light.py | 3 +- tests/components/wled/test_sensor.py | 5 +- tests/components/wled/test_switch.py | 3 +- tests/components/yeelight/test_init.py | 34 ++++++------ tests/components/yeelight/test_light.py | 4 +- tests/components/zha/test_device.py | 4 +- tests/components/zha/test_device_action.py | 6 +-- tests/components/zha/test_device_trigger.py | 12 ++--- tests/components/zha/test_discover.py | 2 +- tests/components/zone/test_init.py | 10 ++-- tests/components/zwave/test_init.py | 7 ++- tests/components/zwave_js/test_api.py | 4 +- .../components/zwave_js/test_binary_sensor.py | 3 +- tests/components/zwave_js/test_init.py | 42 ++++++--------- tests/components/zwave_js/test_sensor.py | 9 ++-- tests/helpers/test_device_registry.py | 5 +- tests/helpers/test_entity_platform.py | 28 +++++----- tests/helpers/test_entity_registry.py | 52 +++++++++---------- 34 files changed, 171 insertions(+), 170 deletions(-) diff --git a/tests/components/tasmota/test_common.py b/tests/components/tasmota/test_common.py index 973ecd3c890a57..74e8d2a5e590ad 100644 --- a/tests/components/tasmota/test_common.py +++ b/tests/components/tasmota/test_common.py @@ -20,6 +20,7 @@ from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_mqtt_message @@ -363,8 +364,8 @@ async def help_test_discovery_removal( name="Test", ): """Test removal of discovered entity.""" - device_reg = await hass.helpers.device_registry.async_get_registry() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + device_reg = dr.async_get(hass) + entity_reg = er.async_get(hass) data1 = json.dumps(config1) data2 = json.dumps(config2) @@ -470,8 +471,8 @@ async def help_test_discovery_device_remove( hass, mqtt_mock, domain, unique_id, config, sensor_config=None ): """Test domain entity is removed when device is removed.""" - device_reg = await hass.helpers.device_registry.async_get_registry() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + device_reg = dr.async_get(hass) + entity_reg = er.async_get(hass) config = copy.deepcopy(config) @@ -502,7 +503,7 @@ async def help_test_entity_id_update_subscriptions( hass, mqtt_mock, domain, config, topics=None, sensor_config=None, entity_id="test" ): """Test MQTT subscriptions are managed when entity_id is updated.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) config = copy.deepcopy(config) data = json.dumps(config) @@ -548,7 +549,7 @@ async def help_test_entity_id_update_discovery_update( hass, mqtt_mock, domain, config, sensor_config=None, entity_id="test" ): """Test MQTT discovery update after entity_id is updated.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) config = copy.deepcopy(config) data = json.dumps(config) diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index 6e5160273d6114..c08b1b531de764 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -17,6 +17,7 @@ from homeassistant.components import sensor from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNKNOWN +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt from .test_common import ( @@ -226,7 +227,7 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # Pre-enable the status sensor entity_reg.async_get_or_create( @@ -285,7 +286,7 @@ async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_single_shot_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # Pre-enable the status sensor entity_reg.async_get_or_create( @@ -369,7 +370,7 @@ async def test_restart_time_status_sensor_state_via_mqtt( hass, mqtt_mock, setup_tasmota ): """Test state update via MQTT.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) # Pre-enable the status sensor entity_reg.async_get_or_create( @@ -524,7 +525,7 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota): @pytest.mark.parametrize("status_sensor_disabled", [False]) async def test_enable_status_sensor(hass, mqtt_mock, setup_tasmota): """Test enabling status sensor.""" - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) config = copy.deepcopy(DEFAULT_CONFIG) mac = config["mac"] diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 74c3eceeea271d..21f48ed6147a01 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -39,7 +39,7 @@ ) from homeassistant.core import Context, CoreState from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -248,7 +248,7 @@ async def test_no_initial_state_and_no_restore_state(hass): async def test_config_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) @@ -498,7 +498,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): timer_id = "from_storage" timer_entity_id = f"{DOMAIN}.{DOMAIN}_{timer_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(timer_entity_id) assert state is not None @@ -525,7 +525,7 @@ async def test_update(hass, hass_ws_client, storage_setup): timer_id = "from_storage" timer_entity_id = f"{DOMAIN}.{DOMAIN}_{timer_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(timer_entity_id) assert state.attributes[ATTR_FRIENDLY_NAME] == "timer from storage" @@ -554,7 +554,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): timer_id = "new_timer" timer_entity_id = f"{DOMAIN}.{timer_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(timer_entity_id) assert state is None diff --git a/tests/components/traccar/test_init.py b/tests/components/traccar/test_init.py index c7e031b2ca6f18..0e741751b8ae45 100644 --- a/tests/components/traccar/test_init.py +++ b/tests/components/traccar/test_init.py @@ -14,6 +14,7 @@ STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component @@ -136,10 +137,10 @@ async def test_enter_and_exit(hass, client, webhook_id): ).state assert STATE_NOT_HOME == state_name - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index 0983b5aa22fc15..da9ae9da146c20 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -2,10 +2,7 @@ from unittest.mock import patch from homeassistant.components import tradfri -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -99,8 +96,8 @@ async def test_entry_setup_unload(hass, api_factory, gateway_id): await hass.async_block_till_done() assert setup.call_count == len(tradfri.PLATFORMS) - dev_reg = await async_get_device_registry(hass) - dev_entries = async_entries_for_config_entry(dev_reg, entry.entry_id) + dev_reg = dr.async_get(hass) + dev_entries = dr.async_entries_for_config_entry(dev_reg, entry.entry_id) assert dev_entries dev_entry = dev_entries[0] diff --git a/tests/components/twinkly/test_twinkly.py b/tests/components/twinkly/test_twinkly.py index c8158354195bb4..d4afe02c11ba6e 100644 --- a/tests/components/twinkly/test_twinkly.py +++ b/tests/components/twinkly/test_twinkly.py @@ -1,5 +1,4 @@ """Tests for the integration of a twinly device.""" - from typing import Tuple from unittest.mock import patch @@ -12,6 +11,7 @@ ) from homeassistant.components.twinkly.light import TwinklyLight from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_registry import RegistryEntry @@ -211,8 +211,8 @@ def get_client_mock(client, _): assert await hass.config_entries.async_setup(client.id) await hass.async_block_till_done() - 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) entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id) entity = entity_registry.async_get(entity_id) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 33dda33be2463b..2018ae39b64a89 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -23,7 +23,7 @@ DOMAIN as UNIFI_DOMAIN, ) from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, setup_unifi_integration @@ -335,9 +335,9 @@ async def test_tracked_devices(hass, aioclient_mock, mock_unifi_websocket): await hass.async_block_till_done() # Verify device registry has been updated - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("device_tracker.device_2") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.sw_version == event["version_to"] @@ -949,7 +949,7 @@ async def test_restoring_client(hass, aioclient_mock): entry_id=1, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( TRACKER_DOMAIN, UNIFI_DOMAIN, diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 44120a18ee36a0..b8f6e2da5534c3 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -16,7 +16,7 @@ DOMAIN as UNIFI_DOMAIN, ) from homeassistant.components.unifi.switch import POE_SWITCH -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .test_controller import ( @@ -799,7 +799,7 @@ async def test_restore_client_succeed(hass, aioclient_mock): entry_id=1, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, @@ -891,7 +891,7 @@ async def test_restore_client_no_old_state(hass, aioclient_mock): entry_id=1, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( SWITCH_DOMAIN, UNIFI_DOMAIN, diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index 33b6843d7e528a..85735d0320e5b6 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -13,6 +13,7 @@ ) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from .common import ComponentFactory, ConfigSource, new_simple_controller_config @@ -40,7 +41,7 @@ async def test_init( ), ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry1 = entity_registry.async_get(entity1_id) assert entry1 assert entry1.unique_id == "vera_first_serial_1" @@ -67,7 +68,7 @@ async def test_init_from_file( ), ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry1 = entity_registry.async_get(entity1_id) assert entry1 assert entry1.unique_id == "vera_first_serial_1" @@ -117,7 +118,7 @@ async def test_multiple_controllers_with_legacy_one( ), ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry1 = entity_registry.async_get(entity1_id) assert entry1 diff --git a/tests/components/wemo/conftest.py b/tests/components/wemo/conftest.py index 0e0a69216b2acc..69b4b84dcd3994 100644 --- a/tests/components/wemo/conftest.py +++ b/tests/components/wemo/conftest.py @@ -7,6 +7,7 @@ from homeassistant.components.wemo import CONF_DISCOVERY, CONF_STATIC from homeassistant.components.wemo.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component MOCK_HOST = "127.0.0.1" @@ -72,7 +73,7 @@ async def async_wemo_entity_fixture(hass, pywemo_device): ) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_entries = list(entity_registry.entities.values()) assert len(entity_entries) == 1 diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 374222d86888da..1164af7cf958d6 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -6,6 +6,7 @@ from homeassistant.components.wemo import CONF_DISCOVERY, CONF_STATIC, WemoDiscovery from homeassistant.components.wemo.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -41,7 +42,7 @@ async def test_static_duplicate_static_entry(hass, pywemo_device): }, ) await hass.async_block_till_done() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 1 @@ -59,7 +60,7 @@ async def test_static_config_with_port(hass, pywemo_device): }, ) await hass.async_block_till_done() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 1 @@ -77,7 +78,7 @@ async def test_static_config_without_port(hass, pywemo_device): }, ) await hass.async_block_till_done() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 1 @@ -132,7 +133,7 @@ def create_device(counter): await hass.async_block_till_done() # Verify that the expected number of devices were setup. - entity_reg = await hass.helpers.entity_registry.async_get_registry() + entity_reg = er.async_get(hass) entity_entries = list(entity_reg.entities.values()) assert len(entity_entries) == 3 diff --git a/tests/components/wilight/test_cover.py b/tests/components/wilight/test_cover.py index 85f62c9d120a48..8b058d95836448 100644 --- a/tests/components/wilight/test_cover.py +++ b/tests/components/wilight/test_cover.py @@ -20,6 +20,7 @@ STATE_OPEN, STATE_OPENING, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from . import ( @@ -64,7 +65,7 @@ async def test_loading_cover( assert entry assert entry.unique_id == WILIGHT_ID - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("cover.wl000000000099_1") diff --git a/tests/components/wilight/test_fan.py b/tests/components/wilight/test_fan.py index 1247b622ae7b08..0ad7789c52cfe7 100644 --- a/tests/components/wilight/test_fan.py +++ b/tests/components/wilight/test_fan.py @@ -20,6 +20,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from . import ( @@ -64,7 +65,7 @@ async def test_loading_light_fan( assert entry assert entry.unique_id == WILIGHT_ID - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("fan.wl000000000099_2") diff --git a/tests/components/wilight/test_light.py b/tests/components/wilight/test_light.py index b7250df546db51..9abe17ce9e56fc 100644 --- a/tests/components/wilight/test_light.py +++ b/tests/components/wilight/test_light.py @@ -16,6 +16,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from tests.components.wilight import ( @@ -140,7 +141,7 @@ async def test_loading_light( assert entry assert entry.unique_id == WILIGHT_ID - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("light.wl000000000099_1") diff --git a/tests/components/withings/test_binary_sensor.py b/tests/components/withings/test_binary_sensor.py index 3477671ea7960d..22c6b6de862a8b 100644 --- a/tests/components/withings/test_binary_sensor.py +++ b/tests/components/withings/test_binary_sensor.py @@ -8,6 +8,7 @@ from homeassistant.components.withings.const import Measurement from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from .common import ComponentFactory, new_profile_config @@ -21,9 +22,7 @@ async def test_binary_sensor( person0 = new_profile_config("person0", 0) person1 = new_profile_config("person1", 1) - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry: EntityRegistry = er.async_get(hass) await component_factory.configure_component(profile_configs=(person0, person1)) assert not await async_get_entity_id(hass, in_bed_attribute, person0.user_id) diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index 16b83a585aa805..8b51f62514d3ea 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -27,6 +27,7 @@ ) from homeassistant.components.withings.const import Measurement from homeassistant.core import HomeAssistant, State +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry from .common import ComponentFactory, new_profile_config @@ -304,9 +305,7 @@ async def test_sensor_default_enabled_entities( hass: HomeAssistant, component_factory: ComponentFactory ) -> None: """Test entities enabled by default.""" - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry: EntityRegistry = er.async_get(hass) await component_factory.configure_component(profile_configs=(PERSON0,)) @@ -347,9 +346,7 @@ async def test_all_entities( hass: HomeAssistant, component_factory: ComponentFactory ) -> None: """Test all entities.""" - entity_registry: EntityRegistry = ( - await hass.helpers.entity_registry.async_get_registry() - ) + entity_registry: EntityRegistry = er.async_get(hass) with patch( "homeassistant.components.withings.sensor.BaseWithingsSensor.entity_registry_enabled_default" diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index eb9124ab906ed9..0077cea02025c0 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -36,6 +36,7 @@ STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed, load_fixture @@ -49,7 +50,7 @@ async def test_rgb_light_state( """Test the creation and values of the WLED lights.""" await init_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("light.wled_rgb_light_segment_0") diff --git a/tests/components/wled/test_sensor.py b/tests/components/wled/test_sensor.py index 11e14bd79d9b6d..9cebf2cda32203 100644 --- a/tests/components/wled/test_sensor.py +++ b/tests/components/wled/test_sensor.py @@ -23,6 +23,7 @@ SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from tests.components.wled import init_integration @@ -35,7 +36,7 @@ async def test_sensors( """Test the creation and values of the WLED sensors.""" entry = await init_integration(hass, aioclient_mock, skip_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -185,7 +186,7 @@ async def test_disabled_by_default_sensors( ) -> None: """Test the disabled by default WLED sensors.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get(entity_id) assert state is None diff --git a/tests/components/wled/test_switch.py b/tests/components/wled/test_switch.py index c6e30ef903ee85..ddeeee41ac8da9 100644 --- a/tests/components/wled/test_switch.py +++ b/tests/components/wled/test_switch.py @@ -20,6 +20,7 @@ STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -31,7 +32,7 @@ async def test_switch_state( """Test the creation and values of the WLED switches.""" await init_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) state = hass.states.get("switch.wled_rgb_light_nightlight") assert state diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index 05a0bd0d8d4bb3..b6a59809d30c7a 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -13,7 +13,7 @@ ) from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_NAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from . import ( @@ -106,11 +106,14 @@ async def test_unique_ids_device(hass: HomeAssistant): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_BINARY_SENSOR).unique_id == f"{ID}-nightlight_sensor" - assert er.async_get(ENTITY_LIGHT).unique_id == ID - assert er.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{ID}-nightlight" - assert er.async_get(ENTITY_AMBILIGHT).unique_id == f"{ID}-ambilight" + entity_registry = er.async_get(hass) + assert ( + entity_registry.async_get(ENTITY_BINARY_SENSOR).unique_id + == f"{ID}-nightlight_sensor" + ) + assert entity_registry.async_get(ENTITY_LIGHT).unique_id == ID + assert entity_registry.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{ID}-nightlight" + assert entity_registry.async_get(ENTITY_AMBILIGHT).unique_id == f"{ID}-ambilight" async def test_unique_ids_entry(hass: HomeAssistant): @@ -131,18 +134,19 @@ async def test_unique_ids_entry(hass: HomeAssistant): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - er = await entity_registry.async_get_registry(hass) + entity_registry = er.async_get(hass) assert ( - er.async_get(ENTITY_BINARY_SENSOR).unique_id + entity_registry.async_get(ENTITY_BINARY_SENSOR).unique_id == f"{config_entry.entry_id}-nightlight_sensor" ) - assert er.async_get(ENTITY_LIGHT).unique_id == config_entry.entry_id + assert entity_registry.async_get(ENTITY_LIGHT).unique_id == config_entry.entry_id assert ( - er.async_get(ENTITY_NIGHTLIGHT).unique_id + entity_registry.async_get(ENTITY_NIGHTLIGHT).unique_id == f"{config_entry.entry_id}-nightlight" ) assert ( - er.async_get(ENTITY_AMBILIGHT).unique_id == f"{config_entry.entry_id}-ambilight" + entity_registry.async_get(ENTITY_AMBILIGHT).unique_id + == f"{config_entry.entry_id}-ambilight" ) @@ -170,8 +174,8 @@ async def test_bulb_off_while_adding_in_ha(hass: HomeAssistant): binary_sensor_entity_id = ENTITY_BINARY_SENSOR_TEMPLATE.format( IP_ADDRESS.replace(".", "_") ) - er = await entity_registry.async_get_registry(hass) - assert er.async_get(binary_sensor_entity_id) is None + entity_registry = er.async_get(hass) + assert entity_registry.async_get(binary_sensor_entity_id) is None type(mocked_bulb).get_capabilities = MagicMock(CAPABILITIES) type(mocked_bulb).get_properties = MagicMock(None) @@ -179,5 +183,5 @@ async def test_bulb_off_while_adding_in_ha(hass: HomeAssistant): hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][DATA_DEVICE].update() await hass.async_block_till_done() - er = await entity_registry.async_get_registry(hass) - assert er.async_get(binary_sensor_entity_id) is not None + entity_registry = er.async_get(hass) + assert entity_registry.async_get(binary_sensor_entity_id) is not None diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 0b7415140a35bc..90a8d1f9e6ee5e 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -85,7 +85,7 @@ ) from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.color import ( color_hs_to_RGB, @@ -376,7 +376,7 @@ async def _async_test( await hass.config_entries.async_unload(config_entry.entry_id) await config_entry.async_remove(hass) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_clear_config_entry(config_entry.entry_id) # nightlight diff --git a/tests/components/zha/test_device.py b/tests/components/zha/test_device.py index 1ce75045d38e06..d3ab3c3ada2fbe 100644 --- a/tests/components/zha/test_device.py +++ b/tests/components/zha/test_device.py @@ -10,7 +10,7 @@ import homeassistant.components.zha.core.device as zha_core_device from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE -import homeassistant.helpers.device_registry as ha_dev_reg +import homeassistant.helpers.device_registry as dr import homeassistant.util.dt as dt_util from .common import async_enable_traffic, make_zcl_header @@ -227,7 +227,7 @@ async def test_ota_sw_version(hass, ota_zha_device): """Test device entry gets sw_version updated via OTA channel.""" ota_ch = ota_zha_device.channels.pools[0].client_channels["1:0x0019"] - dev_registry = await ha_dev_reg.async_get_registry(hass) + dev_registry = dr.async_get(hass) entry = dev_registry.async_get(ota_zha_device.device_id) assert entry.sw_version is None diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index 1160995e8d7bec..4a777fcebb616b 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -12,7 +12,7 @@ _async_get_device_automations as async_get_device_automations, ) from homeassistant.components.zha import DOMAIN -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import async_mock_service, mock_coro @@ -49,7 +49,7 @@ async def test_get_actions(hass, device_ias): ieee_address = str(device_ias[0].ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)}) actions = await async_get_device_automations(hass, "action", reg_device.id) @@ -72,7 +72,7 @@ async def test_action(hass, device_ias): ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)}) with patch( diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index ec947846801b89..b2f964e26953ae 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -8,7 +8,7 @@ import homeassistant.components.automation as automation import homeassistant.components.zha.core.device as zha_core_device -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -86,7 +86,7 @@ async def test_triggers(hass, mock_devices): ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) triggers = await async_get_device_automations(hass, "trigger", reg_device.id) @@ -144,7 +144,7 @@ async def test_no_triggers(hass, mock_devices): _, zha_device = mock_devices ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) triggers = await async_get_device_automations(hass, "trigger", reg_device.id) @@ -173,7 +173,7 @@ async def test_if_fires_on_event(hass, mock_devices, calls): } ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) assert await async_setup_component( @@ -282,7 +282,7 @@ async def test_exception_no_triggers(hass, mock_devices, calls, caplog): _, zha_device = mock_devices ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) await async_setup_component( @@ -324,7 +324,7 @@ async def test_exception_bad_trigger(hass, mock_devices, calls, caplog): } ieee_address = str(zha_device.ieee) - ha_device_registry = await async_get_registry(hass) + ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}) await async_setup_component( diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index d4195c681e75e9..c84c22e3251db2 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -67,7 +67,7 @@ async def test_devices( zha_device_joined_restored, ): """Test device discovery.""" - entity_registry = await homeassistant.helpers.entity_registry.async_get_registry( + entity_registry = homeassistant.helpers.entity_registry.async_get( hass_disable_services ) diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 07fd83cbe779fd..3884605064381b 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -15,7 +15,7 @@ ) from homeassistant.core import Context from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -244,7 +244,7 @@ async def test_core_config_update(hass): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await setup.async_setup_component( hass, @@ -365,7 +365,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -401,7 +401,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes["latitude"] == 1 @@ -435,7 +435,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index d64e191e1f3921..878513da579030 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -18,8 +18,7 @@ ) from homeassistant.components.zwave.binary_sensor import get_device from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME -from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_time_changed, mock_registry from tests.mock.zwave import MockEntityValues, MockNetwork, MockNode, MockValue @@ -472,8 +471,8 @@ def mock_connect(receiver, signal, *args, **kwargs): assert hass.states.get("binary_sensor.mock_node_mock_value").state == "off" assert hass.states.get("binary_sensor.mock_node_mock_value_b").state == "off" - ent_reg = await async_get_registry(hass) - dev_reg = await get_dev_reg(hass) + ent_reg = er.async_get(hass) + dev_reg = dr.async_get(hass) entry = ent_reg.async_get("zwave.mock_node") assert entry is not None diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index 78bac5518a02cf..aa085836b65a0d 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -23,7 +23,7 @@ VALUE, ) from homeassistant.components.zwave_js.const import DOMAIN -from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.helpers import device_registry as dr async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): @@ -197,7 +197,7 @@ async def test_remove_node( # Add mock node to controller client.driver.controller.nodes[67] = nortek_thermostat - dev_reg = await async_get_registry(hass) + dev_reg = dr.async_get(hass) # Create device registry entry for mock node device = dev_reg.async_get_or_create( diff --git a/tests/components/zwave_js/test_binary_sensor.py b/tests/components/zwave_js/test_binary_sensor.py index e8361d8b03ef6f..ddfed9727e65ad 100644 --- a/tests/components/zwave_js/test_binary_sensor.py +++ b/tests/components/zwave_js/test_binary_sensor.py @@ -3,6 +3,7 @@ from homeassistant.components.binary_sensor import DEVICE_CLASS_MOTION from homeassistant.const import DEVICE_CLASS_BATTERY, STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er from .common import ( DISABLED_LEGACY_BINARY_SENSOR, @@ -61,7 +62,7 @@ async def test_disabled_legacy_sensor(hass, multisensor_6, integration): """Test disabled legacy boolean binary sensor.""" # this node has Notification CC implemented so legacy binary sensor should be disabled - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = DISABLED_LEGACY_BINARY_SENSOR state = hass.states.get(entity_id) assert state is None diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 00574bd2d2f49e..3e7f79b9cec27e 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -17,7 +17,7 @@ ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import STATE_UNAVAILABLE -from homeassistant.helpers import device_registry, entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import ( AIR_TEMPERATURE_SENSOR, @@ -117,7 +117,7 @@ async def test_unique_id_migration_dupes( hass, multisensor_6_state, client, integration ): """Test we remove an entity when .""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] @@ -168,7 +168,7 @@ async def test_unique_id_migration_dupes( async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 1).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) # Migrate version 1 entity_name = AIR_TEMPERATURE_SENSOR.split(".")[1] @@ -201,7 +201,7 @@ async def test_unique_id_migration_v1(hass, multisensor_6_state, client, integra async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 2).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) # Migrate version 2 ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" entity_name = ILLUMINANCE_SENSOR.split(".")[1] @@ -234,7 +234,7 @@ async def test_unique_id_migration_v2(hass, multisensor_6_state, client, integra async def test_unique_id_migration_v3(hass, multisensor_6_state, client, integration): """Test unique ID is migrated from old format to new (version 3).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) # Migrate version 2 ILLUMINANCE_SENSOR = "sensor.multisensor_6_illuminance" entity_name = ILLUMINANCE_SENSOR.split(".")[1] @@ -269,7 +269,7 @@ async def test_unique_id_migration_property_key_v1( hass, hank_binary_switch_state, client, integration ): """Test unique ID with property key is migrated from old format to new (version 1).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" entity_name = SENSOR_NAME.split(".")[1] @@ -304,7 +304,7 @@ async def test_unique_id_migration_property_key_v2( hass, hank_binary_switch_state, client, integration ): """Test unique ID with property key is migrated from old format to new (version 2).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" entity_name = SENSOR_NAME.split(".")[1] @@ -341,7 +341,7 @@ async def test_unique_id_migration_property_key_v3( hass, hank_binary_switch_state, client, integration ): """Test unique ID with property key is migrated from old format to new (version 3).""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) SENSOR_NAME = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" entity_name = SENSOR_NAME.split(".")[1] @@ -376,7 +376,7 @@ async def test_unique_id_migration_notification_binary_sensor( hass, multisensor_6_state, client, integration ): """Test unique ID is migrated from old format to new for a notification binary sensor.""" - ent_reg = entity_registry.async_get(hass) + ent_reg = er.async_get(hass) entity_name = NOTIFICATION_MOTION_BINARY_SENSOR.split(".")[1] @@ -813,17 +813,13 @@ async def test_removed_device(hass, client, multiple_devices, integration): assert len(client.driver.controller.nodes) == 2 # Make sure there are the same number of devices - dev_reg = await device_registry.async_get_registry(hass) - device_entries = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - ) + dev_reg = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 2 # Check how many entities there are - ent_reg = await entity_registry.async_get_registry(hass) - entity_entries = entity_registry.async_entries_for_config_entry( - ent_reg, integration.entry_id - ) + ent_reg = er.async_get(hass) + entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 24 # Remove a node and reload the entry @@ -833,21 +829,17 @@ async def test_removed_device(hass, client, multiple_devices, integration): # Assert that the node and all of it's entities were removed from the device and # entity registry - device_entries = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - ) + device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 1 - entity_entries = entity_registry.async_entries_for_config_entry( - ent_reg, integration.entry_id - ) + entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None async def test_suggested_area(hass, client, eaton_rf9640_dimmer): """Test that suggested area works.""" - dev_reg = device_registry.async_get(hass) - ent_reg = entity_registry.async_get(hass) + dev_reg = dr.async_get(hass) + ent_reg = er.async_get(hass) entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) entry.add_to_hass(hass) diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index bd6fb9f25691cd..398d831e6928a7 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -7,10 +7,7 @@ POWER_WATT, TEMP_CELSIUS, ) -from homeassistant.helpers.entity_registry import ( - DISABLED_INTEGRATION, - async_get_registry, -) +from homeassistant.helpers import entity_registry as er from .common import ( AIR_TEMPERATURE_SENSOR, @@ -49,12 +46,12 @@ async def test_energy_sensors(hass, hank_binary_switch, integration): async def test_disabled_notification_sensor(hass, multisensor_6, integration): """Test sensor is created from Notification CC and is disabled.""" - ent_reg = await async_get_registry(hass) + ent_reg = er.async_get(hass) entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_SENSOR) assert entity_entry assert entity_entry.disabled - assert entity_entry.disabled_by == DISABLED_INTEGRATION + assert entity_entry.disabled_by == er.DISABLED_INTEGRATION # Test enabling entity updated_entry = ent_reg.async_update_entity( diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 965ebcd3e234b7..1d1b1a3cca3c3b 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -797,7 +797,7 @@ async def test_cleanup_device_registry(hass, registry): identifiers={("something", "d4")}, config_entry_id="non_existing" ) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) ent_reg.async_get_or_create("light", "hue", "e1", device_id=d1.id) ent_reg.async_get_or_create("light", "hue", "e2", device_id=d1.id) ent_reg.async_get_or_create("light", "hue", "e3", device_id=d3.id) @@ -829,7 +829,7 @@ async def test_cleanup_device_registry_removes_expired_orphaned_devices(hass, re assert len(registry.devices) == 0 assert len(registry.deleted_devices) == 3 - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = entity_registry.async_get(hass) device_registry.async_cleanup(hass, registry, ent_reg) assert len(registry.devices) == 0 @@ -847,7 +847,6 @@ async def test_cleanup_device_registry_removes_expired_orphaned_devices(hass, re async def test_cleanup_startup(hass): """Test we run a cleanup on startup.""" hass.state = CoreState.not_running - await device_registry.async_get_registry(hass) with patch( "homeassistant.helpers.device_registry.Debouncer.async_call" diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index ab3e04843f939a..3f26535de18797 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -9,7 +9,11 @@ from homeassistant.const import PERCENTAGE from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError, PlatformNotReady -from homeassistant.helpers import entity_platform, entity_registry +from homeassistant.helpers import ( + device_registry as dr, + entity_platform, + entity_registry as er, +) from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_component import ( DEFAULT_SCAN_INTERVAL, @@ -467,7 +471,7 @@ async def test_overriding_name_from_registry(hass): mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" @@ -499,12 +503,12 @@ async def test_registry_respect_entity_disabled(hass): mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" platform="test_platform", - disabled_by=entity_registry.DISABLED_USER, + disabled_by=er.DISABLED_USER, ) }, ) @@ -520,7 +524,7 @@ async def test_entity_registry_updates_name(hass): registry = mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" @@ -624,7 +628,7 @@ async def test_entity_registry_updates_entity_id(hass): registry = mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" @@ -656,14 +660,14 @@ async def test_entity_registry_updates_invalid_entity_id(hass): registry = mock_registry( hass, { - "test_domain.world": entity_registry.RegistryEntry( + "test_domain.world": er.RegistryEntry( entity_id="test_domain.world", unique_id="1234", # Using component.async_add_entities is equal to platform "domain" platform="test_platform", name="Some name", ), - "test_domain.existing": entity_registry.RegistryEntry( + "test_domain.existing": er.RegistryEntry( entity_id="test_domain.existing", unique_id="5678", platform="test_platform", @@ -703,7 +707,7 @@ async def test_entity_registry_updates_invalid_entity_id(hass): async def test_device_info_called(hass): """Test device info is forwarded correctly.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) via = registry.async_get_or_create( config_entry_id="123", connections=set(), @@ -763,7 +767,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async def test_device_info_not_overrides(hass): """Test device info is forwarded correctly.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_or_create( config_entry_id="bla", connections={("mac", "abcd")}, @@ -823,7 +827,7 @@ async def test_entity_disabled_by_integration(hass): assert entity_disabled.hass is None assert entity_disabled.platform is None - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry_default = registry.async_get_or_create(DOMAIN, DOMAIN, "default") assert entry_default.disabled_by is None @@ -845,7 +849,7 @@ async def test_entity_info_added_to_entity_registry(hass): await component.async_add_entities([entity_default]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry_default = registry.async_get_or_create(DOMAIN, DOMAIN, "default") print(entry_default) diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 0a1a27efef5886..d671aacebb3d98 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -5,7 +5,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE from homeassistant.core import CoreState, callback, valid_entity_id -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import ( MockConfigEntry, @@ -32,7 +32,7 @@ def update_events(hass): def async_capture(event): events.append(event.data) - hass.bus.async_listen(entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, async_capture) + hass.bus.async_listen(er.EVENT_ENTITY_REGISTRY_UPDATED, async_capture) return events @@ -74,7 +74,7 @@ def test_get_or_create_updates_data(registry): capabilities={"max": 100}, supported_features=5, device_class="mock-device-class", - disabled_by=entity_registry.DISABLED_HASS, + disabled_by=er.DISABLED_HASS, unit_of_measurement="initial-unit_of_measurement", original_name="initial-original_name", original_icon="initial-original_icon", @@ -85,7 +85,7 @@ def test_get_or_create_updates_data(registry): assert orig_entry.capabilities == {"max": 100} assert orig_entry.supported_features == 5 assert orig_entry.device_class == "mock-device-class" - assert orig_entry.disabled_by == entity_registry.DISABLED_HASS + assert orig_entry.disabled_by == er.DISABLED_HASS assert orig_entry.unit_of_measurement == "initial-unit_of_measurement" assert orig_entry.original_name == "initial-original_name" assert orig_entry.original_icon == "initial-original_icon" @@ -101,7 +101,7 @@ def test_get_or_create_updates_data(registry): capabilities={"new-max": 100}, supported_features=10, device_class="new-mock-device-class", - disabled_by=entity_registry.DISABLED_USER, + disabled_by=er.DISABLED_USER, unit_of_measurement="updated-unit_of_measurement", original_name="updated-original_name", original_icon="updated-original_icon", @@ -116,7 +116,7 @@ def test_get_or_create_updates_data(registry): assert new_entry.original_name == "updated-original_name" assert new_entry.original_icon == "updated-original_icon" # Should not be updated - assert new_entry.disabled_by == entity_registry.DISABLED_HASS + assert new_entry.disabled_by == er.DISABLED_HASS def test_get_or_create_suggested_object_id_conflict_register(registry): @@ -162,7 +162,7 @@ async def test_loading_saving_data(hass, registry): capabilities={"max": 100}, supported_features=5, device_class="mock-device-class", - disabled_by=entity_registry.DISABLED_HASS, + disabled_by=er.DISABLED_HASS, original_name="Original Name", original_icon="hass:original-icon", ) @@ -173,7 +173,7 @@ async def test_loading_saving_data(hass, registry): assert len(registry.entities) == 2 # Now load written data in new registry - registry2 = entity_registry.EntityRegistry(hass) + registry2 = er.EntityRegistry(hass) await flush_store(registry._store) await registry2.async_load() @@ -187,7 +187,7 @@ async def test_loading_saving_data(hass, registry): assert new_entry2.device_id == "mock-dev-id" assert new_entry2.area_id == "mock-area-id" - assert new_entry2.disabled_by == entity_registry.DISABLED_HASS + assert new_entry2.disabled_by == er.DISABLED_HASS assert new_entry2.capabilities == {"max": 100} assert new_entry2.supported_features == 5 assert new_entry2.device_class == "mock-device-class" @@ -220,8 +220,8 @@ def test_is_registered(registry): @pytest.mark.parametrize("load_registries", [False]) async def test_loading_extra_values(hass, hass_storage): """Test we load extra data from the registry.""" - hass_storage[entity_registry.STORAGE_KEY] = { - "version": entity_registry.STORAGE_VERSION, + hass_storage[er.STORAGE_KEY] = { + "version": er.STORAGE_VERSION, "data": { "entities": [ { @@ -257,8 +257,8 @@ async def test_loading_extra_values(hass, hass_storage): }, } - await entity_registry.async_load(hass) - registry = entity_registry.async_get(hass) + await er.async_load(hass) + registry = er.async_get(hass) assert len(registry.entities) == 4 @@ -279,9 +279,9 @@ async def test_loading_extra_values(hass, hass_storage): "test", "super_platform", "disabled-user" ) assert entry_disabled_hass.disabled - assert entry_disabled_hass.disabled_by == entity_registry.DISABLED_HASS + assert entry_disabled_hass.disabled_by == er.DISABLED_HASS assert entry_disabled_user.disabled - assert entry_disabled_user.disabled_by == entity_registry.DISABLED_USER + assert entry_disabled_user.disabled_by == er.DISABLED_USER def test_async_get_entity_id(registry): @@ -367,8 +367,8 @@ async def test_migration(hass): with patch("os.path.isfile", return_value=True), patch("os.remove"), patch( "homeassistant.helpers.entity_registry.load_yaml", return_value=old_conf ): - await entity_registry.async_load(hass) - registry = entity_registry.async_get(hass) + await er.async_load(hass) + registry = er.async_get(hass) assert registry.async_is_registered("light.kitchen") entry = registry.async_get_or_create( @@ -384,8 +384,8 @@ async def test_migration(hass): async def test_loading_invalid_entity_id(hass, hass_storage): """Test we autofix invalid entity IDs.""" - hass_storage[entity_registry.STORAGE_KEY] = { - "version": entity_registry.STORAGE_VERSION, + hass_storage[er.STORAGE_KEY] = { + "version": er.STORAGE_VERSION, "data": { "entities": [ { @@ -408,7 +408,7 @@ async def test_loading_invalid_entity_id(hass, hass_storage): }, } - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) entity_invalid_middle = registry.async_get_or_create( "test", "super_platform", "id-invalid-middle" @@ -479,7 +479,7 @@ async def test_update_entity(registry): for attr_name, new_value in ( ("name", "new name"), ("icon", "new icon"), - ("disabled_by", entity_registry.DISABLED_USER), + ("disabled_by", er.DISABLED_USER), ): changes = {attr_name: new_value} updated_entry = registry.async_update_entity(entry.entity_id, **changes) @@ -531,7 +531,7 @@ async def test_restore_states(hass): """Test restoring states.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "light", @@ -545,7 +545,7 @@ async def test_restore_states(hass): "hue", "5678", suggested_object_id="disabled", - disabled_by=entity_registry.DISABLED_HASS, + disabled_by=er.DISABLED_HASS, ) registry.async_get_or_create( "light", @@ -597,7 +597,7 @@ async def test_async_get_device_class_lookup(hass): """Test registry device class lookup.""" hass.state = CoreState.not_running - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) ent_reg.async_get_or_create( "binary_sensor", @@ -888,10 +888,10 @@ async def test_disabled_entities_excluded_from_entity_list(hass, registry): disabled_by="user", ) - entries = entity_registry.async_entries_for_device(registry, device_entry.id) + entries = er.async_entries_for_device(registry, device_entry.id) assert entries == [entry1] - entries = entity_registry.async_entries_for_device( + entries = er.async_entries_for_device( registry, device_entry.id, include_disabled_entities=True ) assert entries == [entry1, entry2] From ba2978c6933f13329ab4c59a7b19b2c9fd313482 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:28:32 +0100 Subject: [PATCH 1154/1818] Update tests p-s to use async_get() instead of async_get_registry() (#47654) --- tests/components/person/test_init.py | 6 ++-- tests/components/powerwall/test_sensor.py | 4 +-- .../pvpc_hourly_pricing/test_config_flow.py | 4 +-- tests/components/rfxtrx/test_config_flow.py | 36 ++++++++----------- tests/components/rfxtrx/test_init.py | 7 ++-- tests/components/ring/test_light.py | 3 +- tests/components/ring/test_switch.py | 3 +- .../risco/test_alarm_control_panel.py | 13 +++---- tests/components/risco/test_binary_sensor.py | 9 ++--- tests/components/risco/test_sensor.py | 7 ++-- tests/components/roku/test_media_player.py | 7 ++-- tests/components/roku/test_remote.py | 3 +- .../ruckus_unleashed/test_device_tracker.py | 6 ++-- .../components/ruckus_unleashed/test_init.py | 3 +- tests/components/search/test_init.py | 15 +++++--- tests/components/sharkiq/test_vacuum.py | 5 +-- .../smartthings/test_binary_sensor.py | 5 +-- tests/components/smartthings/test_climate.py | 5 +-- tests/components/smartthings/test_cover.py | 5 +-- tests/components/smartthings/test_fan.py | 5 +-- tests/components/smartthings/test_light.py | 5 +-- tests/components/smartthings/test_lock.py | 5 +-- tests/components/smartthings/test_scene.py | 3 +- tests/components/smartthings/test_sensor.py | 5 +-- tests/components/smartthings/test_switch.py | 5 +-- tests/components/sonarr/test_sensor.py | 5 +-- tests/components/songpal/test_media_player.py | 4 +-- tests/components/sonos/test_media_player.py | 3 +- .../surepetcare/test_binary_sensor.py | 3 +- 29 files changed, 103 insertions(+), 86 deletions(-) diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 86ec71c1452f31..4e6793c07bba69 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -22,7 +22,7 @@ STATE_UNKNOWN, ) from homeassistant.core import Context, CoreState, State -from homeassistant.helpers import collection, entity_registry +from homeassistant.helpers import collection, entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_restore_cache @@ -589,7 +589,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): assert resp["success"] assert len(hass.states.async_entity_ids("person")) == 0 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert not ent_reg.async_is_registered("person.tracked_person") @@ -681,7 +681,7 @@ async def test_update_person_when_user_removed( async def test_removing_device_tracker(hass, storage_setup): """Test we automatically remove removed device trackers.""" storage_collection = hass.data[DOMAIN][1] - reg = await entity_registry.async_get_registry(hass) + reg = er.async_get(hass) entry = reg.async_get_or_create( "device_tracker", "mobile_app", "bla", suggested_object_id="pixel" ) diff --git a/tests/components/powerwall/test_sensor.py b/tests/components/powerwall/test_sensor.py index eff4631c7e5a3e..32c7da9c78e6a6 100644 --- a/tests/components/powerwall/test_sensor.py +++ b/tests/components/powerwall/test_sensor.py @@ -1,9 +1,9 @@ """The sensor tests for the powerwall platform.""" - from unittest.mock import patch from homeassistant.components.powerwall.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS, PERCENTAGE +from homeassistant.helpers import device_registry as dr from .mocks import _mock_powerwall_with_fixtures @@ -26,7 +26,7 @@ async def test_sensors(hass): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={("powerwall", "TG0123456789AB_TG9876543210BA")}, ) diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index ad321181ec40a9..038b106f6c81ad 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -7,7 +7,7 @@ from homeassistant import data_entry_flow from homeassistant.components.pvpc_hourly_pricing import ATTR_TARIFF, DOMAIN from homeassistant.const import CONF_NAME -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .conftest import check_valid_state @@ -60,7 +60,7 @@ def mock_now(): assert pvpc_aioclient_mock.call_count == 1 # Check removal - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry_entity = registry.async_get("sensor.test") assert await hass.config_entries.async_remove(registry_entity.config_entry_id) diff --git a/tests/components/rfxtrx/test_config_flow.py b/tests/components/rfxtrx/test_config_flow.py index e39c766bfd20eb..1c16507f960cfc 100644 --- a/tests/components/rfxtrx/test_config_flow.py +++ b/tests/components/rfxtrx/test_config_flow.py @@ -6,13 +6,7 @@ from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.rfxtrx import DOMAIN, config_flow -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - async_get_registry as async_get_entity_registry, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry @@ -610,8 +604,8 @@ async def test_options_add_remove_device(hass): assert state.state == "off" assert state.attributes.get("friendly_name") == "AC 213c7f2:48" - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id @@ -704,8 +698,8 @@ async def test_options_replace_sensor_device(hass): ) assert state - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) old_device = next( ( @@ -751,7 +745,7 @@ async def test_options_replace_sensor_device(hass): await hass.async_block_till_done() - entity_registry = await async_get_entity_registry(hass) + entity_registry = er.async_get(hass) entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_rssi_numeric" @@ -843,8 +837,8 @@ async def test_options_replace_control_device(hass): state = hass.states.get("switch.ac_1118cdea_2") assert state - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) old_device = next( ( @@ -890,7 +884,7 @@ async def test_options_replace_control_device(hass): await hass.async_block_till_done() - entity_registry = await async_get_entity_registry(hass) + entity_registry = er.async_get(hass) entry = entity_registry.async_get("binary_sensor.ac_118cdea_2") assert entry @@ -941,8 +935,8 @@ async def test_options_remove_multiple_devices(hass): state = hass.states.get("binary_sensor.ac_1118cdea_2") assert state - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert len(device_entries) == 3 @@ -1061,8 +1055,8 @@ async def test_options_add_and_configure_device(hass): assert state.state == "off" assert state.attributes.get("friendly_name") == "PT2262 22670e" - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id @@ -1151,8 +1145,8 @@ async def test_options_configure_rfy_cover_device(hass): assert entry.data["devices"]["071a000001020301"]["venetian_blind_mode"] == "EU" - device_registry = await async_get_device_registry(hass) - device_entries = async_entries_for_config_entry(device_registry, entry.entry_id) + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id diff --git a/tests/components/rfxtrx/test_init.py b/tests/components/rfxtrx/test_init.py index 9112082a956a9a..b3829e2b5cc317 100644 --- a/tests/components/rfxtrx/test_init.py +++ b/tests/components/rfxtrx/test_init.py @@ -5,10 +5,7 @@ from homeassistant.components.rfxtrx import DOMAIN from homeassistant.components.rfxtrx.const import EVENT_RFXTRX_EVENT from homeassistant.core import callback -from homeassistant.helpers.device_registry import ( - DeviceRegistry, - async_get_registry as async_get_device_registry, -) +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -79,7 +76,7 @@ async def test_fire_event(hass, rfxtrx): await hass.async_block_till_done() await hass.async_start() - device_registry: DeviceRegistry = await async_get_device_registry(hass) + device_registry: dr.DeviceRegistry = dr.async_get(hass) calls = [] diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index 5a2687e4cf91c0..603c0bf3e84f77 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -1,5 +1,6 @@ """The tests for the Ring light platform.""" from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -9,7 +10,7 @@ async def test_entity_registry(hass, requests_mock): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, LIGHT_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("light.front_light") assert entry.unique_id == 765432 diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index 6979fafc01d76d..ab81ed5c69a3ed 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -1,5 +1,6 @@ """The tests for the Ring switch platform.""" from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -9,7 +10,7 @@ async def test_entity_registry(hass, requests_mock): """Tests that the devices are registered in the entity registry.""" await setup_platform(hass, SWITCH_DOMAIN) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("switch.front_siren") assert entry.unique_id == "765432-siren" diff --git a/tests/components/risco/test_alarm_control_panel.py b/tests/components/risco/test_alarm_control_panel.py index a5a16379fe93ed..23c4de4c7aafbd 100644 --- a/tests/components/risco/test_alarm_control_panel.py +++ b/tests/components/risco/test_alarm_control_panel.py @@ -27,6 +27,7 @@ STATE_ALARM_TRIGGERED, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from .util import TEST_CONFIG, TEST_SITE_UUID, setup_risco @@ -114,7 +115,7 @@ async def test_cannot_connect(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -130,14 +131,14 @@ async def test_unauthorized(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) async def test_setup(hass, two_part_alarm): """Test entity setup.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -147,7 +148,7 @@ async def test_setup(hass, two_part_alarm): assert registry.async_is_registered(FIRST_ENTITY_ID) assert registry.async_is_registered(SECOND_ENTITY_ID) - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_device({(DOMAIN, TEST_SITE_UUID + "_0")}) assert device is not None assert device.manufacturer == "Risco" @@ -251,7 +252,7 @@ async def test_sets_custom_mapping(hass, two_part_alarm): """Test settings the various modes when mapping some states.""" await setup_risco(hass, [], CUSTOM_MAPPING_OPTIONS) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get(FIRST_ENTITY_ID) assert entity.supported_features == EXPECTED_FEATURES @@ -277,7 +278,7 @@ async def test_sets_full_custom_mapping(hass, two_part_alarm): """Test settings the various modes when mapping all states.""" await setup_risco(hass, [], FULL_CUSTOM_MAPPING) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get(FIRST_ENTITY_ID) assert ( entity.supported_features == EXPECTED_FEATURES | SUPPORT_ALARM_ARM_CUSTOM_BYPASS diff --git a/tests/components/risco/test_binary_sensor.py b/tests/components/risco/test_binary_sensor.py index 7533512e3ef653..7f68db7939d484 100644 --- a/tests/components/risco/test_binary_sensor.py +++ b/tests/components/risco/test_binary_sensor.py @@ -4,6 +4,7 @@ from homeassistant.components.risco import CannotConnectError, UnauthorizedError from homeassistant.components.risco.const import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from .util import TEST_CONFIG, TEST_SITE_UUID, setup_risco @@ -26,7 +27,7 @@ async def test_cannot_connect(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -42,14 +43,14 @@ async def test_unauthorized(hass): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) async def test_setup(hass, two_zone_alarm): # noqa: F811 """Test entity setup.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) assert not registry.async_is_registered(FIRST_ENTITY_ID) assert not registry.async_is_registered(SECOND_ENTITY_ID) @@ -59,7 +60,7 @@ async def test_setup(hass, two_zone_alarm): # noqa: F811 assert registry.async_is_registered(FIRST_ENTITY_ID) assert registry.async_is_registered(SECOND_ENTITY_ID) - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_device({(DOMAIN, TEST_SITE_UUID + "_zone_0")}) assert device is not None assert device.manufacturer == "Risco" diff --git a/tests/components/risco/test_sensor.py b/tests/components/risco/test_sensor.py index 3d449f10e46f32..eb7ed990bd99b4 100644 --- a/tests/components/risco/test_sensor.py +++ b/tests/components/risco/test_sensor.py @@ -8,6 +8,7 @@ UnauthorizedError, ) from homeassistant.components.risco.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt from .util import TEST_CONFIG, TEST_SITE_UUID, setup_risco @@ -120,7 +121,7 @@ async def test_cannot_connect(hass): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for id in ENTITY_IDS.values(): assert not registry.async_is_registered(id) @@ -137,7 +138,7 @@ async def test_unauthorized(hass): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for id in ENTITY_IDS.values(): assert not registry.async_is_registered(id) @@ -167,7 +168,7 @@ def _check_state(hass, category, entity_id): async def test_setup(hass, two_zone_alarm): # noqa: F811 """Test entity setup.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) for id in ENTITY_IDS.values(): assert not registry.async_is_registered(id) diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 09124ecdf37325..dca336be076d09 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -61,6 +61,7 @@ STATE_STANDBY, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -85,7 +86,7 @@ async def test_setup( """Test setup with basic config.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert hass.states.get(MAIN_ENTITY_ID) @@ -117,7 +118,7 @@ async def test_tv_setup( unique_id=TV_SERIAL, ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) tv = entity_registry.async_get(TV_ENTITY_ID) assert hass.states.get(TV_ENTITY_ID) @@ -321,7 +322,7 @@ async def test_tv_device_registry( unique_id=TV_SERIAL, ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TV_SERIAL)}) assert reg_device.model == TV_MODEL diff --git a/tests/components/roku/test_remote.py b/tests/components/roku/test_remote.py index 4122e0af1d1ecd..363c7134bad6a9 100644 --- a/tests/components/roku/test_remote.py +++ b/tests/components/roku/test_remote.py @@ -7,6 +7,7 @@ SERVICE_SEND_COMMAND, ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from tests.components.roku import UPNP_SERIAL, setup_integration @@ -31,7 +32,7 @@ async def test_unique_id( """Test unique id.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert main.unique_id == UPNP_SERIAL diff --git a/tests/components/ruckus_unleashed/test_device_tracker.py b/tests/components/ruckus_unleashed/test_device_tracker.py index 37bae441abf7f9..9238200727396e 100644 --- a/tests/components/ruckus_unleashed/test_device_tracker.py +++ b/tests/components/ruckus_unleashed/test_device_tracker.py @@ -5,7 +5,7 @@ from homeassistant.components.ruckus_unleashed import API_MAC, DOMAIN from homeassistant.components.ruckus_unleashed.const import API_AP, API_ID, API_NAME from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.util import utcnow @@ -80,7 +80,7 @@ async def test_restoring_clients(hass): entry = mock_config_entry() entry.add_to_hass(hass) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "device_tracker", DOMAIN, @@ -120,7 +120,7 @@ async def test_client_device_setup(hass): router_info = DEFAULT_AP_INFO[API_AP][API_ID]["1"] - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) client_device = device_registry.async_get_device( identifiers={}, connections={(CONNECTION_NETWORK_MAC, TEST_CLIENT[API_MAC])}, diff --git a/tests/components/ruckus_unleashed/test_init.py b/tests/components/ruckus_unleashed/test_init.py index 7b379a510113a8..0340f72891adea 100644 --- a/tests/components/ruckus_unleashed/test_init.py +++ b/tests/components/ruckus_unleashed/test_init.py @@ -19,6 +19,7 @@ ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_RETRY, ) +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from tests.components.ruckus_unleashed import ( @@ -64,7 +65,7 @@ async def test_router_device_setup(hass): device_info = DEFAULT_AP_INFO[API_AP][API_ID]["1"] - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_device( identifiers={(CONNECTION_NETWORK_MAC, device_info[API_MAC])}, connections={(CONNECTION_NETWORK_MAC, device_info[API_MAC])}, diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index 6f6a7793981ffd..57d2c365e71a42 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -1,5 +1,10 @@ """Tests for Search integration.""" from homeassistant.components import search +from homeassistant.helpers import ( + area_registry as ar, + device_registry as dr, + entity_registry as er, +) from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -8,9 +13,9 @@ async def test_search(hass): """Test that search works.""" - area_reg = await hass.helpers.area_registry.async_get_registry() - device_reg = await hass.helpers.device_registry.async_get_registry() - entity_reg = await hass.helpers.entity_registry.async_get_registry() + area_reg = ar.async_get(hass) + device_reg = dr.async_get(hass) + entity_reg = er.async_get(hass) living_room_area = area_reg.async_create("Living Room") @@ -275,8 +280,8 @@ async def test_ws_api(hass, hass_ws_client): """Test WS API.""" assert await async_setup_component(hass, "search", {}) - area_reg = await hass.helpers.area_registry.async_get_registry() - device_reg = await hass.helpers.device_registry.async_get_registry() + area_reg = ar.async_get(hass) + device_reg = dr.async_get(hass) kitchen_area = area_reg.async_create("Kitchen") diff --git a/tests/components/sharkiq/test_vacuum.py b/tests/components/sharkiq/test_vacuum.py index edce5d75b2cd04..fb35d9d4cd2b32 100644 --- a/tests/components/sharkiq/test_vacuum.py +++ b/tests/components/sharkiq/test_vacuum.py @@ -46,6 +46,7 @@ STATE_UNAVAILABLE, ) 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 .const import ( @@ -128,7 +129,7 @@ async def setup_integration(hass): async def test_simple_properties(hass: HomeAssistant): """Test that simple properties work as intended.""" state = hass.states.get(VAC_ENTITY_ID) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get(VAC_ENTITY_ID) assert entity @@ -199,7 +200,7 @@ async def test_device_properties( hass: HomeAssistant, device_property: str, target_value: str ): """Test device properties.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) device = registry.async_get_device({(DOMAIN, "AC000Wxxxxxxxxx")}) assert getattr(device, device_property) == target_value diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index e10d63a2e07e82..7e8ab7d2c9b762 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -13,6 +13,7 @@ from homeassistant.components.smartthings import binary_sensor from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -49,8 +50,8 @@ async def test_entity_and_device_attributes(hass, device_factory): device = device_factory( "Motion Sensor 1", [Capability.motion_sensor], {Attribute.motion: "inactive"} ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, BINARY_SENSOR_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 11f7695a775ea1..dc8f2acc9fa54d 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -44,6 +44,7 @@ SERVICE_TURN_ON, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from .conftest import setup_platform @@ -569,8 +570,8 @@ async def test_set_turn_on(hass, air_conditioner): async def test_entity_and_device_attributes(hass, thermostat): """Test the attributes of the entries are correct.""" await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) entry = entity_registry.async_get("climate.thermostat") assert entry diff --git a/tests/components/smartthings/test_cover.py b/tests/components/smartthings/test_cover.py index 178c905208e6a9..44c2b2f9285c9d 100644 --- a/tests/components/smartthings/test_cover.py +++ b/tests/components/smartthings/test_cover.py @@ -20,6 +20,7 @@ ) from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.const import ATTR_BATTERY_LEVEL, 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 .conftest import setup_platform @@ -31,8 +32,8 @@ async def test_entity_and_device_attributes(hass, device_factory): device = device_factory( "Garage", [Capability.garage_door_control], {Attribute.door: "open"} ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, COVER_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_fan.py b/tests/components/smartthings/test_fan.py index 1f837d58bf821e..6cdfa5b8917704 100644 --- a/tests/components/smartthings/test_fan.py +++ b/tests/components/smartthings/test_fan.py @@ -22,6 +22,7 @@ ATTR_SUPPORTED_FEATURES, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -59,8 +60,8 @@ async def test_entity_and_device_attributes(hass, device_factory): ) # Act await setup_platform(hass, FAN_DOMAIN, devices=[device]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Assert entry = entity_registry.async_get("fan.fan_1") assert entry diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index f6d7d8dd9f40e5..c9dbb0941619ca 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -24,6 +24,7 @@ ATTR_SUPPORTED_FEATURES, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -110,8 +111,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Light 1", [Capability.switch, Capability.switch_level]) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, LIGHT_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_lock.py b/tests/components/smartthings/test_lock.py index 185eae22ccfe4a..1168108656e3d4 100644 --- a/tests/components/smartthings/test_lock.py +++ b/tests/components/smartthings/test_lock.py @@ -10,6 +10,7 @@ from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -19,8 +20,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Lock_1", [Capability.lock], {Attribute.lock: "unlocked"}) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, LOCK_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_scene.py b/tests/components/smartthings/test_scene.py index 6ab4bc080807dc..647389eeb427bc 100644 --- a/tests/components/smartthings/test_scene.py +++ b/tests/components/smartthings/test_scene.py @@ -6,6 +6,7 @@ """ from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from .conftest import setup_platform @@ -13,7 +14,7 @@ async def test_entity_and_device_attributes(hass, scene): """Test the attributes of the entity are correct.""" # Arrange - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Act await setup_platform(hass, SCENE_DOMAIN, scenes=[scene]) # Assert diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py index 53f4b2c72442c0..0f148b8931fe7f 100644 --- a/tests/components/smartthings/test_sensor.py +++ b/tests/components/smartthings/test_sensor.py @@ -16,6 +16,7 @@ STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -78,8 +79,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Sensor 1", [Capability.battery], {Attribute.battery: 100}) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, SENSOR_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index 27ed5050beecfe..21d508bcbc2a5e 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -13,6 +13,7 @@ DOMAIN as SWITCH_DOMAIN, ) from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from .conftest import setup_platform @@ -22,8 +23,8 @@ async def test_entity_and_device_attributes(hass, device_factory): """Test the attributes of the entity are correct.""" # Arrange device = device_factory("Switch_1", [Capability.switch], {Attribute.switch: "on"}) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Act await setup_platform(hass, SWITCH_DOMAIN, devices=[device]) # Assert diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 0b306dc824088d..3a11688a56f9d7 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -12,6 +12,7 @@ DATA_GIGABYTES, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -27,7 +28,7 @@ async def test_sensors( ) -> None: """Test the creation and values of the sensors.""" entry = await setup_integration(hass, aioclient_mock, skip_entry_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors sensors = { @@ -107,7 +108,7 @@ async def test_disabled_by_default_sensors( ) -> None: """Test the disabled by default sensors.""" await setup_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) print(registry.entities) state = hass.states.get(entity_id) diff --git a/tests/components/songpal/test_media_player.py b/tests/components/songpal/test_media_player.py index aff79ef62ffef1..0fd5644e794ac4 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -113,7 +113,7 @@ async def test_state(hass): assert attributes["source"] == "title2" assert attributes["supported_features"] == SUPPORT_SONGPAL - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get_device(identifiers={(songpal.DOMAIN, MAC)}) assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)} assert device.manufacturer == "Sony Corporation" @@ -121,7 +121,7 @@ async def test_state(hass): assert device.sw_version == SW_VERSION assert device.model == MODEL - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entity = entity_registry.async_get(ENTITY_ID) assert entity.unique_id == MAC diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index ba9ba1c6db6285..466a0df5905e22 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -4,6 +4,7 @@ from homeassistant.components.sonos import DOMAIN, media_player from homeassistant.core import Context from homeassistant.exceptions import Unauthorized +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component @@ -48,7 +49,7 @@ async def test_device_registry(hass, config_entry, config, soco): """Test sonos device registered in the device registry.""" await setup_platform(hass, config_entry, config) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={("sonos", "RINCON_test")} ) diff --git a/tests/components/surepetcare/test_binary_sensor.py b/tests/components/surepetcare/test_binary_sensor.py index ad723f707f5bb3..67755dbd6450d0 100644 --- a/tests/components/surepetcare/test_binary_sensor.py +++ b/tests/components/surepetcare/test_binary_sensor.py @@ -2,6 +2,7 @@ from surepy import MESTART_RESOURCE from homeassistant.components.surepetcare.const import DOMAIN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from . import MOCK_API_DATA, MOCK_CONFIG, _patch_sensor_setup @@ -24,7 +25,7 @@ async def test_binary_sensors(hass, surepetcare) -> None: assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) state_entity_ids = hass.states.async_entity_ids() for entity_id, unique_id in EXPECTED_ENTITY_IDS.items(): From 87e7cebd36ce6be6d79d3ff35b60d1ebf33edcd1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:31:17 +0100 Subject: [PATCH 1155/1818] Update tests c-h to use registry async_get (#47652) --- tests/components/cast/test_media_player.py | 21 ++++++++++--------- tests/components/config/test_automation.py | 3 ++- tests/components/config/test_scene.py | 3 ++- tests/components/coronavirus/test_init.py | 8 +++---- tests/components/counter/test_init.py | 8 +++---- tests/components/deconz/test_binary_sensor.py | 4 ++-- tests/components/deconz/test_init.py | 6 +++--- tests/components/deconz/test_services.py | 6 +++--- tests/components/directv/test_media_player.py | 3 ++- tests/components/directv/test_remote.py | 3 ++- tests/components/dsmr/test_sensor.py | 5 +++-- tests/components/dynalite/common.py | 4 ++-- tests/components/dyson/test_climate.py | 6 +++--- tests/components/dyson/test_fan.py | 10 ++++----- tests/components/dyson/test_sensor.py | 6 +++--- tests/components/dyson/test_vacuum.py | 6 +++--- tests/components/elgato/test_light.py | 3 ++- tests/components/gdacs/test_geo_location.py | 4 ++-- .../generic_thermostat/test_climate.py | 3 ++- tests/components/geofency/test_init.py | 5 +++-- .../geonetnz_quakes/test_geo_location.py | 4 ++-- tests/components/gios/test_air_quality.py | 5 +++-- tests/components/gpslogger/test_init.py | 5 +++-- tests/components/harmony/test_init.py | 12 +++++------ tests/components/heos/test_media_player.py | 5 +++-- tests/components/homekit/test_type_covers.py | 6 +++--- tests/components/homekit/test_type_fans.py | 4 ++-- tests/components/homekit/test_type_lights.py | 4 ++-- .../homekit/test_type_media_players.py | 4 ++-- tests/components/homekit/test_type_sensors.py | 4 ++-- .../homekit/test_type_thermostats.py | 6 +++--- .../specific_devices/test_anker_eufycam.py | 5 +++-- .../specific_devices/test_aqara_gateway.py | 5 +++-- .../specific_devices/test_aqara_switch.py | 4 +++- .../specific_devices/test_ecobee3.py | 11 +++++----- .../specific_devices/test_ecobee_occupancy.py | 6 ++++-- .../test_homeassistant_bridge.py | 5 +++-- .../specific_devices/test_hue_bridge.py | 6 ++++-- .../specific_devices/test_koogeek_ls1.py | 5 +++-- .../specific_devices/test_koogeek_p1eu.py | 6 ++++-- .../specific_devices/test_lennox_e30.py | 5 +++-- .../specific_devices/test_lg_tv.py | 5 +++-- .../test_rainmachine_pro_8.py | 6 ++++-- .../test_simpleconnect_fan.py | 5 +++-- .../specific_devices/test_velux_gateway.py | 5 +++-- .../homekit_controller/test_device_trigger.py | 17 ++++++++------- .../homematicip_cloud/test_device.py | 16 +++++++------- tests/components/hue/test_light.py | 11 +++------- tests/components/hyperion/test_light.py | 4 ++-- 49 files changed, 165 insertions(+), 138 deletions(-) diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 27f817b6771246..a3d5676a878251 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -28,6 +28,7 @@ from homeassistant.config import async_process_ha_core_config from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component @@ -489,7 +490,7 @@ async def test_discover_dynamic_group(hass, dial_mock, pycast_mock, caplog): zconf_1 = get_fake_zconf(host="host_1", port=23456) zconf_2 = get_fake_zconf(host="host_2", port=34567) - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) # Fake dynamic group info tmp1 = MagicMock() @@ -613,7 +614,7 @@ async def test_entity_availability(hass: HomeAssistantType): async def test_entity_cast_status(hass: HomeAssistantType): """Test handling of cast status.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -682,7 +683,7 @@ async def test_entity_cast_status(hass: HomeAssistantType): async def test_entity_play_media(hass: HomeAssistantType): """Test playing media.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -711,7 +712,7 @@ async def test_entity_play_media(hass: HomeAssistantType): async def test_entity_play_media_cast(hass: HomeAssistantType, quick_play_mock): """Test playing media with cast special features.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -744,7 +745,7 @@ async def test_entity_play_media_cast(hass: HomeAssistantType, quick_play_mock): async def test_entity_play_media_cast_invalid(hass, caplog, quick_play_mock): """Test playing media.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -817,7 +818,7 @@ async def test_entity_play_media_sign_URL(hass: HomeAssistantType): async def test_entity_media_content_type(hass: HomeAssistantType): """Test various content types.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -871,7 +872,7 @@ async def test_entity_media_content_type(hass: HomeAssistantType): async def test_entity_control(hass: HomeAssistantType): """Test various device and media controls.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -980,7 +981,7 @@ async def test_entity_control(hass: HomeAssistantType): async def test_entity_media_states(hass: HomeAssistantType): """Test various entity media states.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -1039,7 +1040,7 @@ async def test_entity_media_states(hass: HomeAssistantType): async def test_group_media_states(hass, mz_mock): """Test media states are read from group if entity has no state.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( @@ -1092,7 +1093,7 @@ async def test_group_media_states(hass, mz_mock): async def test_group_media_control(hass, mz_mock): """Test media controls are handled by group if entity has no state.""" entity_id = "media_player.speaker" - reg = await hass.helpers.entity_registry.async_get_registry() + reg = er.async_get(hass) info = get_fake_chromecast_info() full_info = attr.evolve( diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index aaf97e22ccdc0f..d52295c75f73cd 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -4,6 +4,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import automation, config +from homeassistant.helpers import entity_registry as er from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -110,7 +111,7 @@ def mock_write(path, data): async def test_delete_automation(hass, hass_client): """Test deleting an automation.""" - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert await async_setup_component( hass, diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index bdb2a2e3f10928..8e4276cc9fd7d5 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -4,6 +4,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.helpers import entity_registry as er from homeassistant.util.yaml import dump @@ -114,7 +115,7 @@ def mock_write(path, data): async def test_delete_scene(hass, hass_client): """Test deleting a scene.""" - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert await async_setup_component( hass, diff --git a/tests/components/coronavirus/test_init.py b/tests/components/coronavirus/test_init.py index 483fabab9f9c21..cc49bf7d4b6fed 100644 --- a/tests/components/coronavirus/test_init.py +++ b/tests/components/coronavirus/test_init.py @@ -1,6 +1,6 @@ """Test init of Coronavirus integration.""" from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, mock_registry @@ -17,13 +17,13 @@ async def test_migration(hass): mock_registry( hass, { - "sensor.netherlands_confirmed": entity_registry.RegistryEntry( + "sensor.netherlands_confirmed": er.RegistryEntry( entity_id="sensor.netherlands_confirmed", unique_id="34-confirmed", platform="coronavirus", config_entry_id=nl_entry.entry_id, ), - "sensor.worldwide_confirmed": entity_registry.RegistryEntry( + "sensor.worldwide_confirmed": er.RegistryEntry( entity_id="sensor.worldwide_confirmed", unique_id="__worldwide-confirmed", platform="coronavirus", @@ -34,7 +34,7 @@ async def test_migration(hass): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) sensor_nl = ent_reg.async_get("sensor.netherlands_confirmed") assert sensor_nl.unique_id == "Netherlands-confirmed" diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index d6a41af6deb2c1..7e5859497c52c9 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -21,7 +21,7 @@ ) from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON, ATTR_NAME from homeassistant.core import Context, CoreState, State -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_restore_cache @@ -569,7 +569,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -606,7 +606,7 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -683,7 +683,7 @@ async def test_create(hass, hass_ws_client, storage_setup): counter_id = "new_counter" input_entity_id = f"{DOMAIN}.{counter_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 70d3db4149b6fb..39809ecc231dbf 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -1,5 +1,4 @@ """deCONZ binary sensor platform tests.""" - from copy import deepcopy from homeassistant.components.binary_sensor import ( @@ -15,6 +14,7 @@ from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import async_entries_for_config_entry from .test_gateway import ( @@ -191,7 +191,7 @@ async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 assert not hass.states.get("binary_sensor.presence_sensor") - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) assert ( len(async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 0 ) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index ed7655bf620b06..46b563ca00714a 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -16,7 +16,7 @@ ) 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 +from homeassistant.helpers import entity_registry as er from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -133,7 +133,7 @@ async def test_update_group_unique_id(hass): }, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) # Create entity entry to migrate to new unique ID registry.async_get_or_create( LIGHT_DOMAIN, @@ -172,7 +172,7 @@ async def test_update_group_unique_id_no_legacy_group_id(hass): data={}, ) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) # Create entity entry to migrate to new unique ID registry.async_get_or_create( LIGHT_DOMAIN, diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 41eefa95785a72..35279572113c92 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -1,5 +1,4 @@ """deCONZ service tests.""" - from copy import deepcopy from unittest.mock import Mock, patch @@ -23,6 +22,7 @@ async_unload_services, ) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_registry import async_entries_for_config_entry from .test_gateway import ( @@ -245,7 +245,7 @@ async def test_remove_orphaned_entries_service(hass, aioclient_mock): data = {CONF_BRIDGE_ID: BRIDGEID} - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={("mac", "123")} ) @@ -261,7 +261,7 @@ async def test_remove_orphaned_entries_service(hass, aioclient_mock): == 5 # Host, gateway, light, switch and orphan ) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entity_registry.async_get_or_create( SENSOR_DOMAIN, DECONZ_DOMAIN, diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index 506cf62a44d0f3..aeaa245161dcba 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -53,6 +53,7 @@ STATE_PLAYING, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util @@ -167,7 +168,7 @@ async def test_unique_id( """Test unique id.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert main.device_class == DEVICE_CLASS_RECEIVER diff --git a/tests/components/directv/test_remote.py b/tests/components/directv/test_remote.py index 33521958747f72..92bcd6af014d25 100644 --- a/tests/components/directv/test_remote.py +++ b/tests/components/directv/test_remote.py @@ -7,6 +7,7 @@ SERVICE_SEND_COMMAND, ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType from tests.components.directv import setup_integration @@ -36,7 +37,7 @@ async def test_unique_id( """Test unique id.""" await setup_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) main = entity_registry.async_get(MAIN_ENTITY_ID) assert main.unique_id == "028877455858" diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 31c4f2be8db608..9aaa558e92ec00 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -19,6 +19,7 @@ VOLUME_CUBIC_METERS, VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, ) +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, patch @@ -107,7 +108,7 @@ async def test_default_setup(hass, dsmr_connection_fixture): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.power_consumption") assert entry @@ -167,7 +168,7 @@ async def test_setup_only_energy(hass, dsmr_connection_fixture): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.power_consumption") assert entry diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index 072e222c19430b..446cdc74c0bd01 100644 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -3,7 +3,7 @@ from homeassistant.components import dynalite from homeassistant.const import ATTR_SERVICE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -23,7 +23,7 @@ def create_mock_device(platform, spec): async def get_entry_id_from_hass(hass): """Get the config entry id from hass.""" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert ent_reg conf_entries = hass.config_entries.async_entries(dynalite.DOMAIN) assert len(conf_entries) == 1 diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index 0e389000c2965b..90d91092dca702 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -60,7 +60,7 @@ ATTR_TEMPERATURE, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .common import ( ENTITY_NAME, @@ -99,8 +99,8 @@ def async_get_device(spec: Type[DysonDevice]) -> DysonDevice: ) async def test_state_common(hass: HomeAssistant, device: DysonDevice) -> None: """Test common state and attributes of two types of climate entities.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.name == NAME diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index dacde12c5697e6..eb83f319c1f4cb 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -52,7 +52,7 @@ STATE_ON, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .common import ( ENTITY_NAME, @@ -79,8 +79,8 @@ async def test_state_purecoollink( hass: HomeAssistant, device: DysonPureCoolLink ) -> None: """Test the state of a PureCoolLink fan.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON @@ -128,8 +128,8 @@ async def test_state_purecoollink( @pytest.mark.parametrize("device", [DysonPureCool], indirect=True) async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> None: """Test the state of a PureCool fan.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index 4541ef2190ad1f..a27aeeb99e171d 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -16,7 +16,7 @@ TEMP_FAHRENHEIT, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem from .common import ( @@ -120,12 +120,12 @@ async def test_sensors( # Make sure no other sensors are set up assert len(hass.states.async_all()) == len(sensors) - er = await entity_registry.async_get_registry(hass) + entity_registry = er.async_get(hass) for sensor in sensors: entity_id = _async_get_entity_id(sensor) # Test unique id - assert er.async_get(entity_id).unique_id == f"{SERIAL}-{sensor}" + assert entity_registry.async_get(entity_id).unique_id == f"{SERIAL}-{sensor}" # Test state state = hass.states.get(entity_id) diff --git a/tests/components/dyson/test_vacuum.py b/tests/components/dyson/test_vacuum.py index 03a3b076b029de..b77dee3270f0cd 100644 --- a/tests/components/dyson/test_vacuum.py +++ b/tests/components/dyson/test_vacuum.py @@ -24,7 +24,7 @@ STATE_ON, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from .common import ( ENTITY_NAME, @@ -45,8 +45,8 @@ def async_get_device(state=Dyson360EyeMode.FULL_CLEAN_RUNNING) -> Dyson360Eye: async def test_state(hass: HomeAssistant, device: Dyson360Eye) -> None: """Test the state of the vacuum.""" - er = await entity_registry.async_get_registry(hass) - assert er.async_get(ENTITY_ID).unique_id == SERIAL + entity_registry = er.async_get(hass) + assert entity_registry.async_get(ENTITY_ID).unique_id == SERIAL state = hass.states.get(ENTITY_ID) assert state.name == NAME diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py index aed569c18fe884..48e6bd24625e2a 100644 --- a/tests/components/elgato/test_light.py +++ b/tests/components/elgato/test_light.py @@ -16,6 +16,7 @@ STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from tests.common import mock_coro from tests.components.elgato import init_integration @@ -28,7 +29,7 @@ async def test_light_state( """Test the creation and values of the Elgato Key Lights.""" await init_integration(hass, aioclient_mock) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # First segment of the strip state = hass.states.get("light.frenck") diff --git a/tests/components/gdacs/test_geo_location.py b/tests/components/gdacs/test_geo_location.py index 7dc23eaa5d8c2e..d93db73aa79e85 100644 --- a/tests/components/gdacs/test_geo_location.py +++ b/tests/components/gdacs/test_geo_location.py @@ -29,7 +29,7 @@ EVENT_HOMEASSISTANT_START, LENGTH_KILOMETERS, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM @@ -99,7 +99,7 @@ async def test_setup(hass, legacy_patchable_time): all_states = hass.states.async_all() # 3 geolocation and 1 sensor entities assert len(all_states) == 4 - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) assert len(entity_registry.entities) == 4 state = hass.states.get("geo_location.drought_name_1") diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index e6cdf962d24e09..b83828c86c7b27 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -35,6 +35,7 @@ ) import homeassistant.core as ha from homeassistant.core import DOMAIN as HASS_DOMAIN, CoreState, State, callback +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.unit_system import METRIC_SYSTEM @@ -179,7 +180,7 @@ async def test_unique_id(hass, setup_comp_1): ) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get(ENTITY) assert entry diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index b87b201a144cc5..92a2f5b19f2d48 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -16,6 +16,7 @@ STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import slugify @@ -223,10 +224,10 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): ] assert NOT_HOME_LONGITUDE == current_longitude - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index b0e54e899298b1..9c700c2b38e4be 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -25,7 +25,7 @@ EVENT_HOMEASSISTANT_START, LENGTH_KILOMETERS, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM @@ -75,7 +75,7 @@ async def test_setup(hass): all_states = hass.states.async_all() # 3 geolocation and 1 sensor entities assert len(all_states) == 4 - entity_registry = await async_get_registry(hass) + entity_registry = er.async_get(hass) assert len(entity_registry.entities) == 4 state = hass.states.get("geo_location.title_1") diff --git a/tests/components/gios/test_air_quality.py b/tests/components/gios/test_air_quality.py index 21a1abf637aa3b..873e1e089a3a37 100644 --- a/tests/components/gios/test_air_quality.py +++ b/tests/components/gios/test_air_quality.py @@ -23,6 +23,7 @@ CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, STATE_UNAVAILABLE, ) +from homeassistant.helpers import entity_registry as er from homeassistant.util.dt import utcnow from tests.common import async_fire_time_changed, load_fixture @@ -32,7 +33,7 @@ async def test_air_quality(hass): """Test states of the air_quality.""" await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("air_quality.home") assert state @@ -60,7 +61,7 @@ async def test_air_quality(hass): async def test_air_quality_with_incomplete_data(hass): """Test states of the air_quality with incomplete data from measuring station.""" await init_integration(hass, incomplete_data=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("air_quality.home") assert state diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py index d30f57f0f33459..8a4880c4878adf 100644 --- a/tests/components/gpslogger/test_init.py +++ b/tests/components/gpslogger/test_init.py @@ -14,6 +14,7 @@ STATE_HOME, STATE_NOT_HOME, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component @@ -135,10 +136,10 @@ async def test_enter_and_exit(hass, gpslogger_client, webhook_id): state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state assert STATE_NOT_HOME == state_name - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 1 diff --git a/tests/components/harmony/test_init.py b/tests/components/harmony/test_init.py index c63727f87380a3..29a1ff26b82191 100644 --- a/tests/components/harmony/test_init.py +++ b/tests/components/harmony/test_init.py @@ -1,7 +1,7 @@ """Test init of Logitch Harmony Hub integration.""" from homeassistant.components.harmony.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from .const import ( @@ -28,28 +28,28 @@ async def test_unique_id_migration(mock_hc, hass, mock_write_config): hass, { # old format - ENTITY_WATCH_TV: entity_registry.RegistryEntry( + ENTITY_WATCH_TV: er.RegistryEntry( entity_id=ENTITY_WATCH_TV, unique_id="123443-Watch TV", platform="harmony", config_entry_id=entry.entry_id, ), # old format, activity name with - - ENTITY_NILE_TV: entity_registry.RegistryEntry( + ENTITY_NILE_TV: er.RegistryEntry( entity_id=ENTITY_NILE_TV, unique_id="123443-Nile-TV", platform="harmony", config_entry_id=entry.entry_id, ), # new format - ENTITY_PLAY_MUSIC: entity_registry.RegistryEntry( + ENTITY_PLAY_MUSIC: er.RegistryEntry( entity_id=ENTITY_PLAY_MUSIC, unique_id=f"activity_{PLAY_MUSIC_ACTIVITY_ID}", platform="harmony", config_entry_id=entry.entry_id, ), # old entity which no longer has a matching activity on the hub. skipped. - "switch.some_other_activity": entity_registry.RegistryEntry( + "switch.some_other_activity": er.RegistryEntry( entity_id="switch.some_other_activity", unique_id="123443-Some Other Activity", platform="harmony", @@ -60,7 +60,7 @@ async def test_unique_id_migration(mock_hc, hass, mock_write_config): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) switch_tv = ent_reg.async_get(ENTITY_WATCH_TV) assert switch_tv.unique_id == f"activity_{WATCH_TV_ACTIVITY_ID}" diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index 4d979f8e5560e1..8b87acfd9fd95d 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -53,6 +53,7 @@ STATE_PLAYING, STATE_UNAVAILABLE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component @@ -237,8 +238,8 @@ async def test_updates_from_players_changed_new_ids( ): """Test player updates from changes to available players.""" await setup_platform(hass, config_entry, config) - 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) player = controller.players[1] event = asyncio.Event() diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index 1c0de6c3af253b..8514801687c39e 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -40,7 +40,7 @@ STATE_UNKNOWN, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -446,7 +446,7 @@ async def test_windowcovering_basic_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "cover", @@ -484,7 +484,7 @@ async def test_windowcovering_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "cover", diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index ba660f2f12dce1..15e2366a883f25 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -28,7 +28,7 @@ STATE_UNKNOWN, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -526,7 +526,7 @@ async def test_fan_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "fan", diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 0ab3ef8e45dd8d..0c81de2efe7436 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -24,7 +24,7 @@ STATE_UNKNOWN, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -354,7 +354,7 @@ async def test_light_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create("light", "hue", "1234", suggested_object_id="simple") registry.async_get_or_create( diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 0b9d25ce3ecb28..b95903d3e3f68a 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -37,7 +37,7 @@ STATE_STANDBY, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -421,7 +421,7 @@ async def test_tv_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "media_player", diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index fe2ae7566d5142..c7c06dcc90a189 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -30,7 +30,7 @@ TEMP_FAHRENHEIT, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er async def test_temperature(hass, hk_driver): @@ -326,7 +326,7 @@ async def test_sensor_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "sensor", diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 7d3d0d14c2fab6..d9c2a6bf0ed908 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -61,7 +61,7 @@ TEMP_FAHRENHEIT, ) from homeassistant.core import CoreState -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from tests.common import async_mock_service @@ -889,7 +889,7 @@ async def test_thermostat_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "climate", "generic", "1234", suggested_object_id="simple" @@ -1705,7 +1705,7 @@ async def test_water_heater_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( "water_heater", "generic", "1234", suggested_object_id="simple" diff --git a/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py b/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py index 45e0466ceeaaed..15ac6d2d3ab485 100644 --- a/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py +++ b/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py @@ -1,4 +1,5 @@ """Test against characteristics captured from a eufycam.""" +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -12,7 +13,7 @@ async def test_eufycam_setup(hass): accessories = await setup_accessories_from_file(hass, "anker_eufycam.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the camera is correctly found and set up camera_id = "camera.eufycam2_0000" @@ -32,7 +33,7 @@ async def test_eufycam_setup(hass): assert camera_state.state == "idle" assert camera_state.attributes["supported_features"] == 0 - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(camera.device_id) assert device.manufacturer == "Anker" diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py index 8bee6e0591d9ec..b4437a7a9b5c06 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py @@ -5,6 +5,7 @@ """ from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -18,7 +19,7 @@ async def test_aqara_gateway_setup(hass): accessories = await setup_accessories_from_file(hass, "aqara_gateway.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the light is correctly found and set up alarm_id = "alarm_control_panel.aqara_hub_1563" @@ -48,7 +49,7 @@ async def test_aqara_gateway_setup(hass): SUPPORT_BRIGHTNESS | SUPPORT_COLOR ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) # All the entities are services of the same accessory # So it looks at the protocol like a single physical device diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py index a9744fb7bfc68e..6ed0d861193ee8 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py @@ -7,6 +7,8 @@ https://github.com/home-assistant/core/pull/39090 """ +from homeassistant.helpers import entity_registry as er + from tests.common import assert_lists_same, async_get_device_automations from tests.components.homekit_controller.common import ( setup_accessories_from_file, @@ -19,7 +21,7 @@ async def test_aqara_switch_setup(hass): accessories = await setup_accessories_from_file(hass, "aqara_switch.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) battery_id = "sensor.programmable_switch_battery" battery = entity_registry.async_get(battery_id) diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index d05e36ed0ebac8..ae050f673242c2 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -15,6 +15,7 @@ SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -30,7 +31,7 @@ async def test_ecobee3_setup(hass): accessories = await setup_accessories_from_file(hass, "ecobee3.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) climate = entity_registry.async_get("climate.homew") assert climate.unique_id == "homekit-123456789012-16" @@ -73,7 +74,7 @@ async def test_ecobee3_setup(hass): occ3 = entity_registry.async_get("binary_sensor.basement") assert occ3.unique_id == "homekit-AB3C-56" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) climate_device = device_registry.async_get(climate.device_id) assert climate_device.manufacturer == "ecobee Inc." @@ -112,7 +113,7 @@ async def test_ecobee3_setup_from_cache(hass, hass_storage): await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) climate = entity_registry.async_get("climate.homew") assert climate.unique_id == "homekit-123456789012-16" @@ -131,7 +132,7 @@ async def test_ecobee3_setup_connection_failure(hass): """Test that Ecbobee can be correctly setup from its cached entity map.""" accessories = await setup_accessories_from_file(hass, "ecobee3.json") - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Test that the connection fails during initial setup. # No entities should be created. @@ -170,7 +171,7 @@ async def test_ecobee3_setup_connection_failure(hass): async def test_ecobee3_add_sensors_at_runtime(hass): """Test that new sensors are automatically added.""" - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Set up a base Ecobee 3 with no additional sensors. # There shouldn't be any entities but climate visible. diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py b/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py index 6823cdc16ead81..63f5d22e04b2f5 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee_occupancy.py @@ -4,6 +4,8 @@ https://github.com/home-assistant/core/issues/31827 """ +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.components.homekit_controller.common import ( Helper, setup_accessories_from_file, @@ -16,7 +18,7 @@ async def test_ecobee_occupancy_setup(hass): accessories = await setup_accessories_from_file(hass, "ecobee_occupancy.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) sensor = entity_registry.async_get("binary_sensor.master_fan") assert sensor.unique_id == "homekit-111111111111-56" @@ -27,7 +29,7 @@ async def test_ecobee_occupancy_setup(hass): sensor_state = await sensor_helper.poll_and_get_state() assert sensor_state.attributes["friendly_name"] == "Master Fan" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(sensor.device_id) assert device.manufacturer == "ecobee Inc." diff --git a/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py b/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py index fbdf00698f7855..1cbfa23b64cfa0 100644 --- a/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_homeassistant_bridge.py @@ -5,6 +5,7 @@ SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -20,7 +21,7 @@ async def test_homeassistant_bridge_fan_setup(hass): ) config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the fan is correctly found and set up fan_id = "fan.living_room_fan" @@ -42,7 +43,7 @@ async def test_homeassistant_bridge_fan_setup(hass): SUPPORT_DIRECTION | SUPPORT_SET_SPEED | SUPPORT_OSCILLATE ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(fan.device_id) assert device.manufacturer == "Home Assistant" diff --git a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py index 168ae85b228d77..0452407bfb8ed2 100644 --- a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py @@ -1,5 +1,7 @@ """Tests for handling accessories on a Hue bridge via HomeKit.""" +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.common import assert_lists_same, async_get_device_automations from tests.components.homekit_controller.common import ( Helper, @@ -13,7 +15,7 @@ async def test_hue_bridge_setup(hass): accessories = await setup_accessories_from_file(hass, "hue_bridge.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the battery is correctly found and set up battery_id = "sensor.hue_dimmer_switch_battery" @@ -28,7 +30,7 @@ async def test_hue_bridge_setup(hass): assert battery_state.attributes["icon"] == "mdi:battery" assert battery_state.state == "100" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(battery.device_id) assert device.manufacturer == "Philips" 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 4682d4b2bcce84..505ff2aacc7f8a 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -8,6 +8,7 @@ import pytest from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.util.dt as dt_util from tests.common import async_fire_time_changed @@ -25,7 +26,7 @@ async def test_koogeek_ls1_setup(hass): accessories = await setup_accessories_from_file(hass, "koogeek_ls1.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Assert that the entity is correctly added to the entity registry entry = entity_registry.async_get("light.koogeek_ls1_20833f") @@ -44,7 +45,7 @@ async def test_koogeek_ls1_setup(hass): SUPPORT_BRIGHTNESS | SUPPORT_COLOR ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.manufacturer == "Koogeek" 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 f97821ef111831..db72aad754174b 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_p1eu.py @@ -1,5 +1,7 @@ """Make sure that existing Koogeek P1EU support isn't broken.""" +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.components.homekit_controller.common import ( Helper, setup_accessories_from_file, @@ -12,8 +14,8 @@ async def test_koogeek_p1eu_setup(hass): accessories = await setup_accessories_from_file(hass, "koogeek_p1eu.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - device_registry = await hass.helpers.device_registry.async_get_registry() + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) # Check that the switch entity is handled correctly diff --git a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py index a49effdb75dcfe..cdab08039e1c75 100644 --- a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py +++ b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py @@ -8,6 +8,7 @@ SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -21,7 +22,7 @@ async def test_lennox_e30_setup(hass): accessories = await setup_accessories_from_file(hass, "lennox_e30.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) climate = entity_registry.async_get("climate.lennox") assert climate.unique_id == "homekit-XXXXXXXX-100" @@ -35,7 +36,7 @@ async def test_lennox_e30_setup(hass): SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(climate.device_id) assert device.manufacturer == "Lennox" diff --git a/tests/components/homekit_controller/specific_devices/test_lg_tv.py b/tests/components/homekit_controller/specific_devices/test_lg_tv.py index ebc50fda8bc65b..7f7ada4ac1fb2c 100644 --- a/tests/components/homekit_controller/specific_devices/test_lg_tv.py +++ b/tests/components/homekit_controller/specific_devices/test_lg_tv.py @@ -5,6 +5,7 @@ SUPPORT_PLAY, SUPPORT_SELECT_SOURCE, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_get_device_automations from tests.components.homekit_controller.common import ( @@ -19,7 +20,7 @@ async def test_lg_tv(hass): accessories = await setup_accessories_from_file(hass, "lg_tv.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Assert that the entity is correctly added to the entity registry entry = entity_registry.async_get("media_player.lg_webos_tv_af80") @@ -54,7 +55,7 @@ async def test_lg_tv(hass): # CURRENT_MEDIA_STATE. Therefore "ok" is the best we can say. assert state.state == "ok" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.manufacturer == "LG Electronics" diff --git a/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py b/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py index fd95ef98c097fd..81e31918c912d7 100644 --- a/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py +++ b/tests/components/homekit_controller/specific_devices/test_rainmachine_pro_8.py @@ -4,6 +4,8 @@ https://github.com/home-assistant/core/issues/31745 """ +from homeassistant.helpers import device_registry as dr, entity_registry as er + from tests.components.homekit_controller.common import ( Helper, setup_accessories_from_file, @@ -16,7 +18,7 @@ async def test_rainmachine_pro_8_setup(hass): accessories = await setup_accessories_from_file(hass, "rainmachine-pro-8.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Assert that the entity is correctly added to the entity registry entry = entity_registry.async_get("switch.rainmachine_00ce4a") @@ -30,7 +32,7 @@ async def test_rainmachine_pro_8_setup(hass): # Assert that the friendly name is detected correctly assert state.attributes["friendly_name"] == "RainMachine-00ce4a" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.manufacturer == "Green Electronics LLC" diff --git a/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py b/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py index b30dd1e9c89d65..a21953202938b7 100644 --- a/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py +++ b/tests/components/homekit_controller/specific_devices/test_simpleconnect_fan.py @@ -5,6 +5,7 @@ """ from homeassistant.components.fan import SUPPORT_DIRECTION, SUPPORT_SET_SPEED +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -18,7 +19,7 @@ async def test_simpleconnect_fan_setup(hass): accessories = await setup_accessories_from_file(hass, "simpleconnect_fan.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the fan is correctly found and set up fan_id = "fan.simpleconnect_fan_06f674" @@ -40,7 +41,7 @@ async def test_simpleconnect_fan_setup(hass): SUPPORT_DIRECTION | SUPPORT_SET_SPEED ) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(fan.device_id) assert device.manufacturer == "Hunter Fan" diff --git a/tests/components/homekit_controller/specific_devices/test_velux_gateway.py b/tests/components/homekit_controller/specific_devices/test_velux_gateway.py index 033b4aa7b4db95..b93afdfbfa4ecd 100644 --- a/tests/components/homekit_controller/specific_devices/test_velux_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_velux_gateway.py @@ -9,6 +9,7 @@ SUPPORT_OPEN, SUPPORT_SET_POSITION, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.components.homekit_controller.common import ( Helper, @@ -22,7 +23,7 @@ async def test_simpleconnect_cover_setup(hass): accessories = await setup_accessories_from_file(hass, "velux_gateway.json") config_entry, pairing = await setup_test_accessories(hass, accessories) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Check that the cover is correctly found and set up cover_id = "cover.velux_window" @@ -64,7 +65,7 @@ async def test_simpleconnect_cover_setup(hass): # The cover and sensor are different devices (accessories) attached to the same bridge assert cover.device_id != sensor.device_id - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(cover.device_id) assert device.manufacturer == "VELUX" diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index ce771eac76857b..cbb8da36bc2493 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -5,6 +5,7 @@ import homeassistant.components.automation as automation from homeassistant.components.homekit_controller.const import DOMAIN +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from tests.common import ( @@ -82,10 +83,10 @@ async def test_enumerate_remote(hass, utcnow): """Test that remote is correctly enumerated.""" await setup_test_component(hass, create_remote) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) expected = [ @@ -118,10 +119,10 @@ async def test_enumerate_button(hass, utcnow): """Test that a button is correctly enumerated.""" await setup_test_component(hass, create_button) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) expected = [ @@ -153,10 +154,10 @@ async def test_enumerate_doorbell(hass, utcnow): """Test that a button is correctly enumerated.""" await setup_test_component(hass, create_doorbell) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) expected = [ @@ -188,10 +189,10 @@ async def test_handle_events(hass, utcnow, calls): """Test that events are handled.""" helper = await setup_test_component(hass, create_remote) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) entry = entity_registry.async_get("sensor.testdevice_battery") - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert await async_setup_component( diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index 264d93c4145411..6f1d3071714d95 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -41,8 +41,8 @@ async def test_hmip_remove_device(hass, default_mock_hap_factory): assert ha_state.state == STATE_ON assert hmip_device - device_registry = await dr.async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) pre_device_count = len(device_registry.devices) pre_entity_count = len(entity_registry.entities) @@ -73,8 +73,8 @@ async def test_hmip_add_device(hass, default_mock_hap_factory, hmip_config_entry assert ha_state.state == STATE_ON assert hmip_device - device_registry = await dr.async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) pre_device_count = len(device_registry.devices) pre_entity_count = len(entity_registry.entities) @@ -119,8 +119,8 @@ async def test_hmip_remove_group(hass, default_mock_hap_factory): assert ha_state.state == STATE_ON assert hmip_device - device_registry = await dr.async_get_registry(hass) - entity_registry = await er.async_get_registry(hass) + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) pre_device_count = len(device_registry.devices) pre_entity_count = len(entity_registry.entities) @@ -257,12 +257,12 @@ async def test_hmip_multi_area_device(hass, default_mock_hap_factory): assert ha_state # get the entity - entity_registry = await er.async_get_registry(hass) + entity_registry = er.async_get(hass) entity = entity_registry.async_get(ha_state.entity_id) assert entity # get the device - device_registry = await dr.async_get_registry(hass) + device_registry = dr.async_get(hass) device = device_registry.async_get(entity.device_id) assert device.name == "Wired Eingangsmodul – 32-fach" diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 39b9a5a23fc7b0..df873536ce239e 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -7,12 +7,7 @@ from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import light as hue_light -from homeassistant.helpers.device_registry import ( - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - async_get_registry as async_get_entity_registry, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import color HUE_LIGHT_NS = "homeassistant.components.light.hue." @@ -937,8 +932,8 @@ async def test_group_features(hass, mock_bridge): group_3 = hass.states.get("light.dining_room") assert group_3.attributes["supported_features"] == extended_color_feature - entity_registry = await async_get_entity_registry(hass) - device_registry = await async_get_device_registry(hass) + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) entry = entity_registry.async_get("light.hue_lamp_1") device_entry = device_registry.async_get(entry.device_id) diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index fe4279ed731c71..94f41aa4fd005e 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -26,7 +26,7 @@ SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.helpers.entity_registry import async_get_registry +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util @@ -109,7 +109,7 @@ async def test_setup_config_entry_not_ready_load_state_fail( async def test_setup_config_entry_dynamic_instances(hass: HomeAssistantType) -> None: """Test dynamic changes in the instance configuration.""" - registry = await async_get_registry(hass) + registry = er.async_get(hass) config_entry = add_test_config_entry(hass) From 84226da40486e0991096342b78dd35312550b882 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 14:32:08 +0100 Subject: [PATCH 1156/1818] Update tests i-o to use async_get() instead of async_get_registry() (#47653) --- tests/components/input_boolean/test_init.py | 6 ++--- tests/components/input_datetime/test_init.py | 10 +++---- tests/components/input_number/test_init.py | 10 +++---- tests/components/input_select/test_init.py | 10 +++---- tests/components/input_text/test_init.py | 8 +++--- tests/components/ipma/test_config_flow.py | 8 +++--- tests/components/ipp/test_sensor.py | 7 ++--- .../jewish_calendar/test_binary_sensor.py | 3 ++- .../components/jewish_calendar/test_sensor.py | 3 ++- tests/components/litejet/__init__.py | 3 ++- tests/components/litejet/test_scene.py | 3 ++- tests/components/mazda/test_sensor.py | 7 ++--- tests/components/met/test_weather.py | 5 ++-- .../mikrotik/test_device_tracker.py | 4 +-- .../mobile_app/test_binary_sensor.py | 4 +-- tests/components/mobile_app/test_init.py | 5 ++-- tests/components/mobile_app/test_sensor.py | 4 +-- .../components/monoprice/test_media_player.py | 7 ++--- tests/components/mqtt/test_common.py | 21 ++++++++------- tests/components/mqtt/test_device_trigger.py | 9 ++++--- tests/components/mqtt/test_init.py | 18 ++++++------- tests/components/mqtt/test_sensor.py | 3 ++- tests/components/mqtt/test_tag.py | 8 +++--- tests/components/nest/camera_sdm_test.py | 5 ++-- tests/components/nest/sensor_sdm_test.py | 10 ++++--- tests/components/nest/test_device_trigger.py | 11 ++++---- tests/components/nest/test_events.py | 13 ++++----- tests/components/nut/test_sensor.py | 17 ++++++------ tests/components/nws/test_weather.py | 7 ++--- tests/components/nzbget/test_sensor.py | 3 ++- tests/components/nzbget/test_switch.py | 3 ++- tests/components/onboarding/test_views.py | 3 ++- tests/components/opentherm_gw/test_init.py | 3 ++- tests/components/ozw/test_binary_sensor.py | 5 ++-- tests/components/ozw/test_migration.py | 27 +++++++------------ tests/components/ozw/test_sensor.py | 7 ++--- 36 files changed, 150 insertions(+), 130 deletions(-) diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index c5d4d40e0d59b5..6534006dd81314 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -20,7 +20,7 @@ STATE_ON, ) from homeassistant.core import Context, CoreState, State -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_component, mock_restore_cache @@ -192,7 +192,7 @@ async def test_input_boolean_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids()) @@ -313,7 +313,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None diff --git a/tests/components/input_datetime/test_init.py b/tests/components/input_datetime/test_init.py index 8d9ddf9546d211..39497e1164c5fe 100644 --- a/tests/components/input_datetime/test_init.py +++ b/tests/components/input_datetime/test_init.py @@ -28,7 +28,7 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_NAME from homeassistant.core import Context, CoreState, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -415,7 +415,7 @@ async def test_input_datetime_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await async_setup_component( hass, @@ -544,7 +544,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.datetime_from_storage" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -570,7 +570,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.datetime_from_storage" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes[ATTR_FRIENDLY_NAME] == "datetime from storage" @@ -602,7 +602,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_datetime" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 28b9d27d23fd05..78fa54b03bed42 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -21,7 +21,7 @@ ) from homeassistant.core import Context, CoreState, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import mock_restore_cache @@ -300,7 +300,7 @@ async def test_input_number_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await async_setup_component( hass, @@ -442,7 +442,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -478,7 +478,7 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -518,7 +518,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 38a3c3ba7a23f6..70663f71e7a810 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -26,7 +26,7 @@ ) from homeassistant.core import Context, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -425,7 +425,7 @@ async def test_input_select_context(hass, hass_admin_user): async def test_reload(hass, hass_admin_user, hass_read_only_user): """Test reload service.""" count_start = len(hass.states.async_entity_ids()) - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) assert await async_setup_component( hass, @@ -559,7 +559,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -592,7 +592,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes[ATTR_OPTIONS] == ["yaml update 1", "yaml update 2"] @@ -633,7 +633,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index cc226dc1d8741c..531606d67edb59 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -25,7 +25,7 @@ ) from homeassistant.core import Context, CoreState, State from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -393,7 +393,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is not None @@ -419,7 +419,7 @@ async def test_update(hass, hass_ws_client, storage_setup): input_id = "from_storage" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state.attributes[ATTR_FRIENDLY_NAME] == "from storage" @@ -457,7 +457,7 @@ async def test_ws_create(hass, hass_ws_client, storage_setup): input_id = "new_input" input_entity_id = f"{DOMAIN}.{input_id}" - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) state = hass.states.get(input_entity_id) assert state is None diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py index 493bcfe28cf4b0..8c96b9a01d8745 100644 --- a/tests/components/ipma/test_config_flow.py +++ b/tests/components/ipma/test_config_flow.py @@ -4,7 +4,7 @@ from homeassistant.components.ipma import DOMAIN, config_flow from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from .test_weather import MockLocation @@ -143,13 +143,13 @@ async def test_config_entry_migration(hass): mock_registry( hass, { - "weather.hometown": entity_registry.RegistryEntry( + "weather.hometown": er.RegistryEntry( entity_id="weather.hometown", unique_id="0, 0", platform="ipma", config_entry_id=ipma_entry.entry_id, ), - "weather.hometown_2": entity_registry.RegistryEntry( + "weather.hometown_2": er.RegistryEntry( entity_id="weather.hometown_2", unique_id="0, 0, hourly", platform="ipma", @@ -165,7 +165,7 @@ async def test_config_entry_migration(hass): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() - ent_reg = await entity_registry.async_get_registry(hass) + ent_reg = er.async_get(hass) weather_home = ent_reg.async_get("weather.hometown") assert weather_home.unique_id == "0, 0, daily" diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index 69143faec64f16..9366b290feffd3 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -6,6 +6,7 @@ from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from tests.components.ipp import init_integration, mock_connection @@ -19,7 +20,7 @@ async def test_sensors( mock_connection(aioclient_mock) entry = await init_integration(hass, aioclient_mock, skip_setup=True) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) # Pre-create registry entries for disabled by default sensors registry.async_get_or_create( @@ -86,7 +87,7 @@ async def test_disabled_by_default_sensors( ) -> None: """Test the disabled by default IPP sensors.""" await init_integration(hass, aioclient_mock) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("sensor.epson_xp_6000_series_uptime") assert state is None @@ -102,7 +103,7 @@ async def test_missing_entry_unique_id( ) -> None: """Test the unique_id of IPP sensor when printer is missing identifiers.""" entry = await init_integration(hass, aioclient_mock, uuid=None, unique_id=None) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity = registry.async_get("sensor.epson_xp_6000_series") assert entity diff --git a/tests/components/jewish_calendar/test_binary_sensor.py b/tests/components/jewish_calendar/test_binary_sensor.py index 8986c2e6f53ecd..ca31381f164d77 100644 --- a/tests/components/jewish_calendar/test_binary_sensor.py +++ b/tests/components/jewish_calendar/test_binary_sensor.py @@ -5,6 +5,7 @@ from homeassistant.components import jewish_calendar from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -84,7 +85,7 @@ async def test_issur_melacha_sensor( hass.config.latitude = latitude hass.config.longitude = longitude - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) with alter_time(test_time): assert await async_setup_component( diff --git a/tests/components/jewish_calendar/test_sensor.py b/tests/components/jewish_calendar/test_sensor.py index 1c771c71a3a16b..a5c99c850b8e16 100644 --- a/tests/components/jewish_calendar/test_sensor.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -4,6 +4,7 @@ import pytest from homeassistant.components import jewish_calendar +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -511,7 +512,7 @@ async def test_shabbat_times_sensor( hass.config.latitude = latitude hass.config.longitude = longitude - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) with alter_time(test_time): assert await async_setup_component( diff --git a/tests/components/litejet/__init__.py b/tests/components/litejet/__init__.py index 13e2b547cd84b2..e0304c21617022 100644 --- a/tests/components/litejet/__init__.py +++ b/tests/components/litejet/__init__.py @@ -2,6 +2,7 @@ from homeassistant.components import scene, switch from homeassistant.components.litejet import DOMAIN from homeassistant.const import CONF_PORT +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry @@ -11,7 +12,7 @@ async def async_init_integration( ) -> MockConfigEntry: """Set up the LiteJet integration in Home Assistant.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry_data = {CONF_PORT: "/dev/mock"} diff --git a/tests/components/litejet/test_scene.py b/tests/components/litejet/test_scene.py index 5df26f8c68022e..c76c8738f86d89 100644 --- a/tests/components/litejet/test_scene.py +++ b/tests/components/litejet/test_scene.py @@ -1,6 +1,7 @@ """The tests for the litejet component.""" from homeassistant.components import scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON +from homeassistant.helpers import entity_registry as er from . import async_init_integration @@ -14,7 +15,7 @@ async def test_disabled_by_default(hass, mock_litejet): """Test the scene is disabled by default.""" await async_init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get(ENTITY_SCENE) assert state is None diff --git a/tests/components/mazda/test_sensor.py b/tests/components/mazda/test_sensor.py index 1cb9f7ac4b79e6..d5f25bce2f31b8 100644 --- a/tests/components/mazda/test_sensor.py +++ b/tests/components/mazda/test_sensor.py @@ -10,6 +10,7 @@ PERCENTAGE, PRESSURE_PSI, ) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util.unit_system import IMPERIAL_SYSTEM from tests.components.mazda import init_integration @@ -19,7 +20,7 @@ async def test_device_nickname(hass): """Test creation of the device when vehicle has a nickname.""" await init_integration(hass, use_nickname=True) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={(DOMAIN, "JM000000000000000")}, ) @@ -33,7 +34,7 @@ async def test_device_no_nickname(hass): """Test creation of the device when vehicle has no nickname.""" await init_integration(hass, use_nickname=False) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) reg_device = device_registry.async_get_device( identifiers={(DOMAIN, "JM000000000000000")}, ) @@ -47,7 +48,7 @@ async def test_sensors(hass): """Test creation of the sensors.""" await init_integration(hass) - entity_registry = await hass.helpers.entity_registry.async_get_registry() + entity_registry = er.async_get(hass) # Fuel Remaining Percentage state = hass.states.get("sensor.my_mazda3_fuel_remaining_percentage") diff --git a/tests/components/met/test_weather.py b/tests/components/met/test_weather.py index 24a81be3896430..89c1dc62612349 100644 --- a/tests/components/met/test_weather.py +++ b/tests/components/met/test_weather.py @@ -2,6 +2,7 @@ from homeassistant.components.met import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN +from homeassistant.helpers import entity_registry as er async def test_tracking_home(hass, mock_weather): @@ -12,7 +13,7 @@ async def test_tracking_home(hass, mock_weather): assert len(mock_weather.mock_calls) == 4 # Test the hourly sensor is disabled by default - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) state = hass.states.get("weather.test_home_hourly") assert state is None @@ -38,7 +39,7 @@ async def test_not_tracking_home(hass, mock_weather): """Test when we not track home.""" # Pre-create registry entry for disabled by default hourly weather - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( WEATHER_DOMAIN, DOMAIN, diff --git a/tests/components/mikrotik/test_device_tracker.py b/tests/components/mikrotik/test_device_tracker.py index d4151be0addb24..fcd29c18682c77 100644 --- a/tests/components/mikrotik/test_device_tracker.py +++ b/tests/components/mikrotik/test_device_tracker.py @@ -3,7 +3,7 @@ from homeassistant.components import mikrotik import homeassistant.components.device_tracker as device_tracker -from homeassistant.helpers import entity_registry +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -101,7 +101,7 @@ async def test_restoring_devices(hass): ) config_entry.add_to_hass(hass) - registry = await entity_registry.async_get_registry(hass) + registry = er.async_get(hass) registry.async_get_or_create( device_tracker.DOMAIN, mikrotik.DOMAIN, diff --git a/tests/components/mobile_app/test_binary_sensor.py b/tests/components/mobile_app/test_binary_sensor.py index 5ada948a5d66ff..7965bf472cbe08 100644 --- a/tests/components/mobile_app/test_binary_sensor.py +++ b/tests/components/mobile_app/test_binary_sensor.py @@ -1,6 +1,6 @@ """Entity tests for mobile_app.""" from homeassistant.const import STATE_OFF -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr async def test_sensor(hass, create_registrations, webhook_client): @@ -70,7 +70,7 @@ async def test_sensor(hass, create_registrations, webhook_client): assert updated_entity.state == "off" assert "foo" not in updated_entity.attributes - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == len(create_registrations) # Reload to verify state is restored diff --git a/tests/components/mobile_app/test_init.py b/tests/components/mobile_app/test_init.py index fe956796a965c1..abd4b0a72185b4 100644 --- a/tests/components/mobile_app/test_init.py +++ b/tests/components/mobile_app/test_init.py @@ -1,5 +1,6 @@ """Tests for the mobile app integration.""" from homeassistant.components.mobile_app.const import DATA_DELETED_IDS, DOMAIN +from homeassistant.helpers import device_registry as dr, entity_registry as er from .const import CALL_SERVICE @@ -30,8 +31,8 @@ async def test_remove_entry(hass, create_registrations): await hass.config_entries.async_remove(config_entry.entry_id) assert config_entry.data["webhook_id"] in hass.data[DOMAIN][DATA_DELETED_IDS] - dev_reg = await hass.helpers.device_registry.async_get_registry() + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 0 - ent_reg = await hass.helpers.entity_registry.async_get_registry() + ent_reg = er.async_get(hass) assert len(ent_reg.entities) == 0 diff --git a/tests/components/mobile_app/test_sensor.py b/tests/components/mobile_app/test_sensor.py index 0ba1cf3096d519..ed638301bd6e3d 100644 --- a/tests/components/mobile_app/test_sensor.py +++ b/tests/components/mobile_app/test_sensor.py @@ -1,6 +1,6 @@ """Entity tests for mobile_app.""" from homeassistant.const import PERCENTAGE, STATE_UNKNOWN -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr async def test_sensor(hass, create_registrations, webhook_client): @@ -68,7 +68,7 @@ async def test_sensor(hass, create_registrations, webhook_client): assert updated_entity.state == "123" assert "foo" not in updated_entity.attributes - dev_reg = await device_registry.async_get_registry(hass) + dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == len(create_registrations) # Reload to verify state is restored diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index d7b505b5279b32..9d3bbd40a4640c 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -33,6 +33,7 @@ SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, ) +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from tests.common import MockConfigEntry @@ -497,7 +498,7 @@ async def test_first_run_with_available_zones(hass): monoprice = MockMonoprice() await _setup_monoprice(hass, monoprice) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get(ZONE_7_ID) assert not entry.disabled @@ -510,7 +511,7 @@ async def test_first_run_with_failing_zones(hass): with patch.object(MockMonoprice, "zone_status", side_effect=SerialException): await _setup_monoprice(hass, monoprice) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get(ZONE_1_ID) assert not entry.disabled @@ -527,7 +528,7 @@ async def test_not_first_run_with_failing_zone(hass): with patch.object(MockMonoprice, "zone_status", side_effect=SerialException): await _setup_monoprice_not_first_run(hass, monoprice) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get(ZONE_1_ID) assert not entry.disabled diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index c8cd80372c6aaa..f1243918e4ee14 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -8,6 +8,7 @@ from homeassistant.components.mqtt import debug_info from homeassistant.components.mqtt.const import MQTT_DISCONNECTED from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE +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 @@ -725,7 +726,7 @@ async def help_test_entity_device_info_with_identifier(hass, mqtt_mock, domain, config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -750,7 +751,7 @@ async def help_test_entity_device_info_with_connection(hass, mqtt_mock, domain, config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_MAC) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -772,8 +773,8 @@ async def help_test_entity_device_info_remove(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - dev_registry = await hass.helpers.device_registry.async_get_registry() - ent_registry = await hass.helpers.entity_registry.async_get_registry() + dev_registry = dr.async_get(hass) + ent_registry = er.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -801,7 +802,7 @@ async def help_test_entity_device_info_update(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -913,7 +914,7 @@ async def help_test_entity_debug_info(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -946,7 +947,7 @@ async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, conf config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -1008,7 +1009,7 @@ async def help_test_entity_debug_info_message( if payload is None: payload = "ON" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -1054,7 +1055,7 @@ async def help_test_entity_debug_info_remove(hass, mqtt_mock, domain, config): config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) @@ -1097,7 +1098,7 @@ async def help_test_entity_debug_info_update_entity_id(hass, mqtt_mock, domain, config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) config["unique_id"] = "veryunique" - dev_registry = await hass.helpers.device_registry.async_get_registry() + dev_registry = dr.async_get(hass) ent_registry = mock_registry(hass, {}) data = json.dumps(config) diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index c1e012a90d6b71..62ffade11f4d26 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -6,6 +6,7 @@ import homeassistant.components.automation as automation from homeassistant.components.mqtt import DOMAIN, debug_info from homeassistant.components.mqtt.device_trigger import async_attach_trigger +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from tests.common import ( @@ -836,7 +837,7 @@ def callback(trigger): async def test_entity_device_info_with_connection(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -867,7 +868,7 @@ async def test_entity_device_info_with_connection(hass, mqtt_mock): async def test_entity_device_info_with_identifier(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -898,7 +899,7 @@ async def test_entity_device_info_with_identifier(hass, mqtt_mock): async def test_entity_device_info_update(hass, mqtt_mock): """Test device registry update.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) config = { "automation_type": "trigger", @@ -1159,7 +1160,7 @@ async def test_trigger_debug_info(hass, mqtt_mock): This is a test helper for MQTT debug_info. """ - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) config = { "platform": "mqtt", diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 2907a0e4cfc42f..15401a6d02f7c7 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -19,7 +19,7 @@ TEMP_CELSIUS, ) from homeassistant.core import callback -from homeassistant.helpers import device_registry +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -1058,7 +1058,7 @@ async def test_mqtt_ws_remove_non_mqtt_device( device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) assert device_entry is not None @@ -1157,7 +1157,7 @@ async def test_debug_info_multiple_devices(hass, mqtt_mock): }, ] - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) for d in devices: data = json.dumps(d["config"]) @@ -1236,7 +1236,7 @@ async def test_debug_info_multiple_entities_triggers(hass, mqtt_mock): }, ] - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) for c in config: data = json.dumps(c["config"]) @@ -1284,7 +1284,7 @@ async def test_debug_info_non_mqtt(hass, device_reg, entity_reg): config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) for device_class in DEVICE_CLASSES: entity_reg.async_get_or_create( @@ -1311,7 +1311,7 @@ async def test_debug_info_wildcard(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1357,7 +1357,7 @@ async def test_debug_info_filter_same(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1416,7 +1416,7 @@ async def test_debug_info_same_topic(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -1467,7 +1467,7 @@ async def test_debug_info_qos_retain(hass, mqtt_mock): "unique_id": "veryunique", } - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps(config) async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index a2c2605d6ab4e1..4e3634ebfa8804 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -9,6 +9,7 @@ import homeassistant.components.sensor as sensor from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE import homeassistant.core as ha +from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -574,7 +575,7 @@ async def test_entity_id_update_discovery_update(hass, mqtt_mock): async def test_entity_device_info_with_hub(hass, mqtt_mock): """Test MQTT sensor device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) hub = registry.async_get_or_create( config_entry_id="123", connections=set(), diff --git a/tests/components/mqtt/test_tag.py b/tests/components/mqtt/test_tag.py index 67964a36e1a28f..98dcfa050e5424 100644 --- a/tests/components/mqtt/test_tag.py +++ b/tests/components/mqtt/test_tag.py @@ -5,6 +5,8 @@ import pytest +from homeassistant.helpers import device_registry as dr + from tests.common import ( async_fire_mqtt_message, async_get_device_automations, @@ -378,7 +380,7 @@ async def test_not_fires_on_mqtt_message_after_remove_from_registry( async def test_entity_device_info_with_connection(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -406,7 +408,7 @@ async def test_entity_device_info_with_connection(hass, mqtt_mock): async def test_entity_device_info_with_identifier(hass, mqtt_mock): """Test MQTT device registry integration.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) data = json.dumps( { @@ -434,7 +436,7 @@ async def test_entity_device_info_with_identifier(hass, mqtt_mock): async def test_entity_device_info_update(hass, mqtt_mock): """Test device registry update.""" - registry = await hass.helpers.device_registry.async_get_registry() + registry = dr.async_get(hass) config = { "topic": "test-topic", diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py index 956d6036aed544..57747ad9f599f8 100644 --- a/tests/components/nest/camera_sdm_test.py +++ b/tests/components/nest/camera_sdm_test.py @@ -16,6 +16,7 @@ from homeassistant.components import camera from homeassistant.components.camera import STATE_IDLE 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 @@ -168,13 +169,13 @@ async def test_camera_device(hass): assert camera is not None assert camera.state == STATE_IDLE - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.my_camera") assert entry.unique_id == "some-device-id-camera" assert entry.original_name == "My Camera" assert entry.domain == "camera" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My Camera" assert device.model == "Camera" diff --git a/tests/components/nest/sensor_sdm_test.py b/tests/components/nest/sensor_sdm_test.py index b8b2912b124c1b..dfdfd58d546980 100644 --- a/tests/components/nest/sensor_sdm_test.py +++ b/tests/components/nest/sensor_sdm_test.py @@ -8,6 +8,8 @@ from google_nest_sdm.device import Device from google_nest_sdm.event import EventMessage +from homeassistant.helpers import device_registry as dr, entity_registry as er + from .common import async_setup_sdm_platform PLATFORM = "sensor" @@ -52,13 +54,13 @@ async def test_thermostat_device(hass): assert humidity is not None assert humidity.state == "35.0" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.my_sensor_temperature") assert entry.unique_id == "some-device-id-temperature" assert entry.original_name == "My Sensor Temperature" assert entry.domain == "sensor" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My Sensor" assert device.model == "Thermostat" @@ -197,13 +199,13 @@ async def test_device_with_unknown_type(hass): assert temperature is not None assert temperature.state == "25.1" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.my_sensor_temperature") assert entry.unique_id == "some-device-id-temperature" assert entry.original_name == "My Sensor Temperature" assert entry.domain == "sensor" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "My Sensor" assert device.model is None diff --git a/tests/components/nest/test_device_trigger.py b/tests/components/nest/test_device_trigger.py index 5e3b9bde442562..c9dc9992410d0c 100644 --- a/tests/components/nest/test_device_trigger.py +++ b/tests/components/nest/test_device_trigger.py @@ -9,6 +9,7 @@ ) from homeassistant.components.nest import DOMAIN from homeassistant.components.nest.events import NEST_EVENT +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 @@ -101,7 +102,7 @@ async def test_get_triggers(hass): ) await async_setup_camera(hass, {DEVICE_ID: camera}) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) expected_triggers = [ @@ -140,7 +141,7 @@ async def test_multiple_devices(hass): ) await async_setup_camera(hass, {"device-id-1": camera1, "device-id-2": camera2}) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry1 = registry.async_get("camera.camera_1") assert entry1.unique_id == "device-id-1-camera" entry2 = registry.async_get("camera.camera_2") @@ -176,7 +177,7 @@ async def test_triggers_for_invalid_device_id(hass): ) await async_setup_camera(hass, {DEVICE_ID: camera}) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) assert device_entry is not None @@ -198,7 +199,7 @@ async def test_no_triggers(hass): camera = make_camera(device_id=DEVICE_ID, traits={}) await async_setup_camera(hass, {DEVICE_ID: camera}) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.my_camera") assert entry.unique_id == "some-device-id-camera" @@ -288,7 +289,7 @@ async def test_subscriber_automation(hass, calls): ) subscriber = await async_setup_camera(hass, {DEVICE_ID: camera}) - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) assert await setup_automation(hass, device_entry.id, "camera_motion") diff --git a/tests/components/nest/test_events.py b/tests/components/nest/test_events.py index 692507d6ff9050..df459a35f718bb 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -7,6 +7,7 @@ from google_nest_sdm.device import Device from google_nest_sdm.event import EventMessage +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util.dt import utcnow from .common import async_setup_sdm_platform @@ -91,14 +92,14 @@ async def test_doorbell_chime_event(hass): create_device_traits("sdm.devices.traits.DoorbellChime"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None assert entry.unique_id == "some-device-id-camera" assert entry.original_name == "Front" assert entry.domain == "camera" - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(entry.device_id) assert device.name == "Front" assert device.model == "Doorbell" @@ -127,7 +128,7 @@ async def test_camera_motion_event(hass): "sdm.devices.types.CAMERA", create_device_traits("sdm.devices.traits.CameraMotion"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None @@ -154,7 +155,7 @@ async def test_camera_sound_event(hass): "sdm.devices.types.CAMERA", create_device_traits("sdm.devices.traits.CameraSound"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None @@ -181,7 +182,7 @@ async def test_camera_person_event(hass): "sdm.devices.types.DOORBELL", create_device_traits("sdm.devices.traits.CameraEventImage"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None @@ -208,7 +209,7 @@ async def test_camera_multiple_event(hass): "sdm.devices.types.DOORBELL", create_device_traits("sdm.devices.traits.CameraEventImage"), ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 4950d467d6afcb..0d8fec71d51d33 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -1,6 +1,7 @@ """The sensor tests for the nut platform.""" from homeassistant.const import PERCENTAGE +from homeassistant.helpers import entity_registry as er from .util import async_init_integration @@ -9,7 +10,7 @@ async def test_pr3000rt2u(hass): """Test creation of PR3000RT2U sensors.""" await async_init_integration(hass, "PR3000RT2U", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == "CPS_PR3000RT2U_PYVJO2000034_battery.charge" @@ -35,7 +36,7 @@ async def test_cp1350c(hass): config_entry = await async_init_integration(hass, "CP1350C", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -60,7 +61,7 @@ async def test_5e850i(hass): """Test creation of 5E850I sensors.""" config_entry = await async_init_integration(hass, "5E850I", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -85,7 +86,7 @@ async def test_5e650i(hass): """Test creation of 5E650I sensors.""" config_entry = await async_init_integration(hass, "5E650I", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -110,7 +111,7 @@ async def test_backupsses600m1(hass): """Test creation of BACKUPSES600M1 sensors.""" await async_init_integration(hass, "BACKUPSES600M1", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert ( @@ -140,7 +141,7 @@ async def test_cp1500pfclcd(hass): config_entry = await async_init_integration( hass, "CP1500PFCLCD", ["battery.charge"] ) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -165,7 +166,7 @@ async def test_dl650elcd(hass): """Test creation of DL650ELCD sensors.""" config_entry = await async_init_integration(hass, "DL650ELCD", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" @@ -190,7 +191,7 @@ async def test_blazer_usb(hass): """Test creation of blazer_usb sensors.""" config_entry = await async_init_integration(hass, "blazer_usb", ["battery.charge"]) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get("sensor.ups1_battery_charge") assert entry assert entry.unique_id == f"{config_entry.entry_id}_battery.charge" diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index c7cb5b81c2a5bd..1679e489ab8ac7 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -12,6 +12,7 @@ DOMAIN as WEATHER_DOMAIN, ) from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM @@ -40,7 +41,7 @@ async def test_imperial_metric( ): """Test with imperial and metric units.""" # enable the hourly entity - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( WEATHER_DOMAIN, nws.DOMAIN, @@ -284,7 +285,7 @@ async def test_error_forecast_hourly(hass, mock_simple_nws): instance.update_forecast_hourly.side_effect = aiohttp.ClientError # enable the hourly entity - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) registry.async_get_or_create( WEATHER_DOMAIN, nws.DOMAIN, @@ -330,7 +331,7 @@ async def test_forecast_hourly_disable_enable(hass, mock_simple_nws): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( WEATHER_DOMAIN, nws.DOMAIN, diff --git a/tests/components/nzbget/test_sensor.py b/tests/components/nzbget/test_sensor.py index f5954bc7ee0abc..de5e2382a63869 100644 --- a/tests/components/nzbget/test_sensor.py +++ b/tests/components/nzbget/test_sensor.py @@ -8,6 +8,7 @@ DATA_RATE_MEGABYTES_PER_SECOND, DEVICE_CLASS_TIMESTAMP, ) +from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from . import init_integration @@ -19,7 +20,7 @@ async def test_sensors(hass, nzbget_api) -> None: with patch("homeassistant.components.nzbget.sensor.utcnow", return_value=now): entry = await init_integration(hass) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) uptime = now - timedelta(seconds=600) diff --git a/tests/components/nzbget/test_switch.py b/tests/components/nzbget/test_switch.py index c12fe8ca526e10..debfd9a1be8735 100644 --- a/tests/components/nzbget/test_switch.py +++ b/tests/components/nzbget/test_switch.py @@ -7,6 +7,7 @@ STATE_OFF, STATE_ON, ) +from homeassistant.helpers import entity_registry as er from . import init_integration @@ -18,7 +19,7 @@ async def test_download_switch(hass, nzbget_api) -> None: entry = await init_integration(hass) assert entry - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = "switch.nzbgettest_download" entity_entry = registry.async_get(entity_id) assert entity_entry diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index fe956b2ac0a936..558cab7ee99e78 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -8,6 +8,7 @@ from homeassistant.components import onboarding from homeassistant.components.onboarding import const, views from homeassistant.const import HTTP_FORBIDDEN +from homeassistant.helpers import area_registry as ar from homeassistant.setup import async_setup_component from . import mock_storage @@ -181,7 +182,7 @@ async def test_onboarding_user(hass, hass_storage, aiohttp_client): ) # Validate created areas - area_registry = await hass.helpers.area_registry.async_get_registry() + area_registry = ar.async_get(hass) assert len(area_registry.areas) == 3 assert sorted([area.name for area in area_registry.async_list_areas()]) == [ "Bedroom", diff --git a/tests/components/opentherm_gw/test_init.py b/tests/components/opentherm_gw/test_init.py index b28f869e1e6795..554f58fd81b05c 100644 --- a/tests/components/opentherm_gw/test_init.py +++ b/tests/components/opentherm_gw/test_init.py @@ -6,6 +6,7 @@ from homeassistant import setup from homeassistant.components.opentherm_gw.const import DOMAIN from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME +from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry, mock_device_registry @@ -38,7 +39,7 @@ async def test_device_registry_insert(hass): await hass.async_block_till_done() - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) gw_dev = device_registry.async_get_device(identifiers={(DOMAIN, MOCK_GATEWAY_ID)}) assert gw_dev.sw_version == VERSION_OLD diff --git a/tests/components/ozw/test_binary_sensor.py b/tests/components/ozw/test_binary_sensor.py index 62b23be0ccae05..95b150b579141e 100644 --- a/tests/components/ozw/test_binary_sensor.py +++ b/tests/components/ozw/test_binary_sensor.py @@ -5,6 +5,7 @@ ) from homeassistant.components.ozw.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS +from homeassistant.helpers import entity_registry as er from .common import setup_ozw @@ -14,7 +15,7 @@ async def test_binary_sensor(hass, generic_data, binary_sensor_msg): receive_msg = await setup_ozw(hass, fixture=generic_data) # Test Legacy sensor (disabled by default) - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = "binary_sensor.trisensor_sensor" state = hass.states.get(entity_id) assert state is None @@ -46,7 +47,7 @@ async def test_binary_sensor(hass, generic_data, binary_sensor_msg): async def test_sensor_enabled(hass, generic_data, binary_sensor_alt_msg): """Test enabling a legacy binary_sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( BINARY_SENSOR_DOMAIN, diff --git a/tests/components/ozw/test_migration.py b/tests/components/ozw/test_migration.py index d83a39f2b15331..076974bc48f30a 100644 --- a/tests/components/ozw/test_migration.py +++ b/tests/components/ozw/test_migration.py @@ -4,14 +4,7 @@ import pytest from homeassistant.components.ozw.websocket_api import ID, TYPE -from homeassistant.helpers.device_registry import ( - DeviceEntry, - async_get_registry as async_get_device_registry, -) -from homeassistant.helpers.entity_registry import ( - RegistryEntry, - async_get_registry as async_get_entity_registry, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import setup_ozw @@ -41,35 +34,35 @@ @pytest.fixture(name="zwave_migration_data") def zwave_migration_data_fixture(hass): """Return mock zwave migration data.""" - zwave_source_node_device = DeviceEntry( + zwave_source_node_device = dr.DeviceEntry( id=ZWAVE_SOURCE_NODE_DEVICE_ID, name_by_user=ZWAVE_SOURCE_NODE_DEVICE_NAME, area_id=ZWAVE_SOURCE_NODE_DEVICE_AREA, ) - zwave_source_node_entry = RegistryEntry( + zwave_source_node_entry = er.RegistryEntry( entity_id=ZWAVE_SOURCE_ENTITY, unique_id=ZWAVE_SOURCE_NODE_UNIQUE_ID, platform="zwave", name="Z-Wave Source Node", ) - zwave_battery_device = DeviceEntry( + zwave_battery_device = dr.DeviceEntry( id=ZWAVE_BATTERY_DEVICE_ID, name_by_user=ZWAVE_BATTERY_DEVICE_NAME, area_id=ZWAVE_BATTERY_DEVICE_AREA, ) - zwave_battery_entry = RegistryEntry( + zwave_battery_entry = er.RegistryEntry( entity_id=ZWAVE_BATTERY_ENTITY, unique_id=ZWAVE_BATTERY_UNIQUE_ID, platform="zwave", name=ZWAVE_BATTERY_NAME, icon=ZWAVE_BATTERY_ICON, ) - zwave_power_device = DeviceEntry( + zwave_power_device = dr.DeviceEntry( id=ZWAVE_POWER_DEVICE_ID, name_by_user=ZWAVE_POWER_DEVICE_NAME, area_id=ZWAVE_POWER_DEVICE_AREA, ) - zwave_power_entry = RegistryEntry( + zwave_power_entry = er.RegistryEntry( entity_id=ZWAVE_POWER_ENTITY, unique_id=ZWAVE_POWER_UNIQUE_ID, platform="zwave", @@ -169,8 +162,8 @@ async def test_migrate_zwave(hass, migration_data, hass_ws_client, zwave_integra assert result["migration_entity_map"] == migration_entity_map assert result["migrated"] is True - dev_reg = await async_get_device_registry(hass) - ent_reg = await async_get_entity_registry(hass) + dev_reg = dr.async_get(hass) + ent_reg = er.async_get(hass) # check the device registry migration @@ -252,7 +245,7 @@ async def test_migrate_zwave_dry_run( assert result["migration_entity_map"] == migration_entity_map assert result["migrated"] is False - ent_reg = await async_get_entity_registry(hass) + ent_reg = er.async_get(hass) # no real migration should have been done assert ent_reg.async_is_registered("sensor.water_sensor_6_battery_level") diff --git a/tests/components/ozw/test_sensor.py b/tests/components/ozw/test_sensor.py index 91de895648e134..500bd81aa0be20 100644 --- a/tests/components/ozw/test_sensor.py +++ b/tests/components/ozw/test_sensor.py @@ -7,6 +7,7 @@ DOMAIN as SENSOR_DOMAIN, ) from homeassistant.const import ATTR_DEVICE_CLASS +from homeassistant.helpers import entity_registry as er from .common import setup_ozw @@ -34,7 +35,7 @@ async def test_sensor(hass, generic_data): assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER # Test ZWaveListSensor disabled by default - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entity_id = "sensor.water_sensor_6_instance_1_water" state = hass.states.get(entity_id) assert state is None @@ -55,7 +56,7 @@ async def test_sensor(hass, generic_data): async def test_sensor_enabled(hass, generic_data, sensor_msg): """Test enabling an advanced sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( SENSOR_DOMAIN, @@ -79,7 +80,7 @@ async def test_sensor_enabled(hass, generic_data, sensor_msg): async def test_string_sensor(hass, string_sensor_data): """Test so the returned type is a string sensor.""" - registry = await hass.helpers.entity_registry.async_get_registry() + registry = er.async_get(hass) entry = registry.async_get_or_create( SENSOR_DOMAIN, From 0c61cb555c34bc55aea0260fe0359e8ed82a9207 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 9 Mar 2021 14:57:44 +0100 Subject: [PATCH 1157/1818] Add TYPE_CHECKING to coverage excludes (#47668) --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 5d502a7503aa66..2347ee1902eca0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1167,3 +1167,6 @@ exclude_lines = # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError + + # TYPE_CHECKING block is never executed during pytest run + if TYPE_CHECKING: From fd1add8f159761c57aa679f0c1a4258d65ec7112 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 9 Mar 2021 15:49:41 +0100 Subject: [PATCH 1158/1818] Rename AutomationTrace.runid to AutomationTrace.run_id (#47669) --- homeassistant/components/automation/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 8b0fa1687eeb1a..b9acfc0dde939f 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -241,7 +241,7 @@ async def reload_service_handler(service_call): class AutomationTrace: """Container for automation trace.""" - _runids = count(0) + _run_ids = count(0) def __init__( self, @@ -257,7 +257,7 @@ def __init__( self._context: Context = context self._error: Optional[Exception] = None self._state: str = "running" - self.runid: str = str(next(self._runids)) + self.run_id: str = str(next(self._run_ids)) self._timestamp_finish: Optional[dt.datetime] = None self._timestamp_start: dt.datetime = dt_util.utcnow() self._trigger: Dict[str, Any] = trigger @@ -303,7 +303,7 @@ def as_dict(self) -> Dict[str, Any]: "condition_trace": condition_traces, "config": self._config, "context": self._context, - "run_id": self.runid, + "run_id": self.run_id, "state": self._state, "timestamp": { "start": self._timestamp_start, @@ -331,7 +331,7 @@ def as_short_dict(self) -> Dict[str, Any]: result = { "last_action": last_action, "last_condition": last_condition, - "run_id": self.runid, + "run_id": self.run_id, "state": self._state, "timestamp": { "start": self._timestamp_start, @@ -379,7 +379,7 @@ def trace_automation(hass, unique_id, config, trigger, context): automation_traces = hass.data[DATA_AUTOMATION_TRACE] if unique_id not in automation_traces: automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) - automation_traces[unique_id][automation_trace.runid] = automation_trace + automation_traces[unique_id][automation_trace.run_id] = automation_trace try: yield automation_trace From ed679b263b89a76f6f567a4efc1fc06e9b72a68c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 10:52:53 -0800 Subject: [PATCH 1159/1818] Improve logging elgato (#47681) --- homeassistant/components/elgato/__init__.py | 3 ++ homeassistant/components/elgato/light.py | 41 +++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/elgato/__init__.py b/homeassistant/components/elgato/__init__.py index d6c849a4c5a351..4f7731c122d694 100644 --- a/homeassistant/components/elgato/__init__.py +++ b/homeassistant/components/elgato/__init__.py @@ -1,4 +1,6 @@ """Support for Elgato Key Lights.""" +import logging + from elgato import Elgato, ElgatoConnectionError from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN @@ -30,6 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await elgato.info() except ElgatoConnectionError as exception: + logging.getLogger(__name__).debug("Unable to connect: %s", exception) raise ConfigEntryNotReady from exception hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 3005560e2eaae2..ae3d8274281425 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -55,25 +55,20 @@ def __init__( info: Info, ): """Initialize Elgato Key Light.""" - self._brightness: int | None = None self._info: Info = info - self._state: bool | None = None - self._temperature: int | None = None - self._available = True + self._state: State | None = None self.elgato = elgato @property def name(self) -> str: """Return the name of the entity.""" # Return the product name, if display name is not set - if not self._info.display_name: - return self._info.product_name - return self._info.display_name + return self._info.display_name or self._info.product_name @property def available(self) -> bool: """Return True if entity is available.""" - return self._available + return self._state is not None @property def unique_id(self) -> str: @@ -83,12 +78,14 @@ def unique_id(self) -> str: @property def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" - return self._brightness + assert self._state is not None + return round((self._state.brightness * 255) / 100) @property def color_temp(self) -> int | None: """Return the CT color value in mireds.""" - return self._temperature + assert self._state is not None + return self._state.temperature @property def min_mireds(self) -> int: @@ -108,7 +105,8 @@ def supported_features(self) -> int: @property def is_on(self) -> bool: """Return the state of the light.""" - return bool(self._state) + assert self._state is not None + return self._state.on async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" @@ -131,22 +129,19 @@ async def async_turn_on(self, **kwargs: Any) -> None: await self.elgato.light(**data) except ElgatoError: _LOGGER.error("An error occurred while updating the Elgato Key Light") - self._available = False + self._state = None async def async_update(self) -> None: """Update Elgato entity.""" + restoring = self._state is None try: - state: State = await self.elgato.state() - except ElgatoError: - if self._available: - _LOGGER.error("An error occurred while updating the Elgato Key Light") - self._available = False - return - - self._available = True - self._brightness = round((state.brightness * 255) / 100) - self._state = state.on - self._temperature = state.temperature + self._state: State = await self.elgato.state() + if restoring: + _LOGGER.info("Connection restored") + except ElgatoError as err: + meth = _LOGGER.error if self._state else _LOGGER.debug + meth("An error occurred while updating the Elgato Key Light: %s", err) + self._state = None @property def device_info(self) -> dict[str, Any]: From 46e593485e516b7b48d847c061040b6503925b80 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 12:14:00 -0800 Subject: [PATCH 1160/1818] Handle zeroconf updated events (#47683) --- homeassistant/components/zeroconf/__init__.py | 2 +- tests/components/zeroconf/test_init.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 2ef7db3a1b4db2..fb2d097d0630ac 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -242,7 +242,7 @@ def service_update(zeroconf, service_type, name, state_change): nonlocal zeroconf_types nonlocal homekit_models - if state_change != ServiceStateChange.Added: + if state_change == ServiceStateChange.Removed: return try: diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index cc34a511573e29..7bd670fb4c927b 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -3,6 +3,7 @@ from zeroconf import ( BadTypeInNameException, + Error as ZeroconfError, InterfaceChoice, IPVersion, ServiceInfo, @@ -495,3 +496,35 @@ async def test_get_instance(hass, mock_zeroconf): hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() assert len(mock_zeroconf.ha_close.mock_calls) == 1 + + +async def test_removed_ignored(hass, mock_zeroconf): + """Test we remove it when a zeroconf entry is removed.""" + mock_zeroconf.get_service_info.side_effect = ZeroconfError + + def service_update_mock(zeroconf, services, handlers): + """Call service update handler.""" + handlers[0]( + zeroconf, "_service.added", "name._service.added", ServiceStateChange.Added + ) + handlers[0]( + zeroconf, + "_service.updated", + "name._service.updated", + ServiceStateChange.Updated, + ) + handlers[0]( + zeroconf, + "_service.removed", + "name._service.removed", + ServiceStateChange.Removed, + ) + + with patch.object(zeroconf, "HaServiceBrowser", side_effect=service_update_mock): + assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + assert len(mock_zeroconf.get_service_info.mock_calls) == 2 + assert mock_zeroconf.get_service_info.mock_calls[0][1][0] == "_service.added" + assert mock_zeroconf.get_service_info.mock_calls[1][1][0] == "_service.updated" From a060acc2b1b00dacebeb6d447196b22b416ad16c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Mar 2021 11:16:19 -1000 Subject: [PATCH 1161/1818] Fix recorder with MSSQL (#46678) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .coveragerc | 1 + homeassistant/components/recorder/const.py | 3 + homeassistant/components/recorder/models.py | 2 +- homeassistant/components/recorder/purge.py | 162 ++++++++++++-------- homeassistant/components/recorder/repack.py | 35 +++++ setup.cfg | 2 +- tests/components/recorder/test_purge.py | 99 ++++++------ 7 files changed, 196 insertions(+), 108 deletions(-) create mode 100644 homeassistant/components/recorder/repack.py diff --git a/.coveragerc b/.coveragerc index 2347ee1902eca0..db940ed642b450 100644 --- a/.coveragerc +++ b/.coveragerc @@ -785,6 +785,7 @@ omit = homeassistant/components/raspyrfm/* homeassistant/components/recollect_waste/__init__.py homeassistant/components/recollect_waste/sensor.py + homeassistant/components/recorder/repack.py homeassistant/components/recswitch/switch.py homeassistant/components/reddit/* homeassistant/components/rejseplanen/sensor.py diff --git a/homeassistant/components/recorder/const.py b/homeassistant/components/recorder/const.py index a2b5ffc6f2a786..026628a32dfc0b 100644 --- a/homeassistant/components/recorder/const.py +++ b/homeassistant/components/recorder/const.py @@ -5,3 +5,6 @@ DOMAIN = "recorder" CONF_DB_INTEGRITY_CHECK = "db_integrity_check" + +# The maximum number of rows (events) we purge in one delete statement +MAX_ROWS_TO_PURGE = 1000 diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 9481e954bde8ff..69e2115ce34026 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -117,7 +117,7 @@ class States(Base): # type: ignore last_updated = Column(DateTime(timezone=True), default=dt_util.utcnow, index=True) created = Column(DateTime(timezone=True), default=dt_util.utcnow) old_state_id = Column( - Integer, ForeignKey("states.state_id", ondelete="SET NULL"), index=True + Integer, ForeignKey("states.state_id", ondelete="NO ACTION"), index=True ) event = relationship("Events", uselist=False) old_state = relationship("States", remote_side=[state_id]) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 43e84785f7d4f2..ac10dadc227e58 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -1,88 +1,51 @@ """Purge old data helper.""" -from datetime import timedelta +from __future__ import annotations + +from datetime import datetime, timedelta import logging import time +from typing import TYPE_CHECKING from sqlalchemy.exc import OperationalError, SQLAlchemyError +from sqlalchemy.orm.session import Session import homeassistant.util.dt as dt_util +from .const import MAX_ROWS_TO_PURGE from .models import Events, RecorderRuns, States -from .util import execute, session_scope +from .repack import repack_database +from .util import session_scope + +if TYPE_CHECKING: + from . import Recorder _LOGGER = logging.getLogger(__name__) -def purge_old_data(instance, purge_days: int, repack: bool) -> bool: +def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: """Purge events and states older than purge_days ago. Cleans up an timeframe of an hour, based on the oldest record. """ purge_before = dt_util.utcnow() - timedelta(days=purge_days) _LOGGER.debug("Purging states and events before target %s", purge_before) - try: - with session_scope(session=instance.get_session()) as session: - # Purge a max of 1 hour, based on the oldest states or events record - batch_purge_before = purge_before - - query = session.query(States).order_by(States.last_updated.asc()).limit(1) - states = execute(query, to_native=True, validate_entity_ids=False) - if states: - batch_purge_before = min( - batch_purge_before, - states[0].last_updated + timedelta(hours=1), - ) - - query = session.query(Events).order_by(Events.time_fired.asc()).limit(1) - events = execute(query, to_native=True) - if events: - batch_purge_before = min( - batch_purge_before, - events[0].time_fired + timedelta(hours=1), - ) - - _LOGGER.debug("Purging states and events before %s", batch_purge_before) - - deleted_rows = ( - session.query(States) - .filter(States.last_updated < batch_purge_before) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s states", deleted_rows) - - deleted_rows = ( - session.query(Events) - .filter(Events.time_fired < batch_purge_before) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s events", deleted_rows) - - # If states or events purging isn't processing the purge_before yet, - # return false, as we are not done yet. - if batch_purge_before != purge_before: + with session_scope(session=instance.get_session()) as session: # type: ignore + # 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) + if state_ids: + _disconnect_states_about_to_be_purged(session, state_ids) + _purge_state_ids(session, state_ids) + if event_ids: + _purge_event_ids(session, event_ids) + # If states or events purging isn't processing the purge_before yet, + # return false, as we are not done yet. _LOGGER.debug("Purging hasn't fully completed yet") return False - - # Recorder runs is small, no need to batch run it - deleted_rows = ( - session.query(RecorderRuns) - .filter(RecorderRuns.start < purge_before) - .filter(RecorderRuns.run_id != instance.run_info.run_id) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) - + _purge_old_recorder_runs(instance, session, purge_before) if repack: - # Execute sqlite or postgresql vacuum command to free up space on disk - if instance.engine.driver in ("pysqlite", "postgresql"): - _LOGGER.debug("Vacuuming SQL DB to free space") - instance.engine.execute("VACUUM") - # Optimize mysql / mariadb tables to free up space on disk - elif instance.engine.driver in ("mysqldb", "pymysql"): - _LOGGER.debug("Optimizing SQL DB to free space") - instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") - + repack_database(instance) except OperationalError as err: # Retry when one of the following MySQL errors occurred: # 1205: Lock wait timeout exceeded; try restarting transaction @@ -101,3 +64,78 @@ def purge_old_data(instance, purge_days: int, repack: bool) -> bool: except SQLAlchemyError as err: _LOGGER.warning("Error purging history: %s", err) return True + + +def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list: + """Return a list of event ids to purge.""" + events = ( + session.query(Events.event_id) + .filter(Events.time_fired < purge_before) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) + _LOGGER.debug("Selected %s event ids to remove", len(events)) + return [event.event_id for event in events] + + +def _select_state_ids_to_purge( + session: Session, purge_before: datetime, event_ids: list +) -> list: + """Return a list of state ids to purge.""" + if not event_ids: + return [] + states = ( + session.query(States.state_id) + .filter(States.last_updated < purge_before) + .filter(States.event_id.in_(event_ids)) + .all() + ) + _LOGGER.debug("Selected %s state ids to remove", len(states)) + return [state.state_id for state in states] + + +def _disconnect_states_about_to_be_purged(session: Session, state_ids: list) -> None: + # Update old_state_id to NULL before deleting to ensure + # the delete does not fail due to a foreign key constraint + # since some databases (MSSQL) cannot do the ON DELETE SET NULL + # for us. + disconnected_rows = ( + session.query(States) + .filter(States.old_state_id.in_(state_ids)) + .update({"old_state_id": None}, synchronize_session=False) + ) + _LOGGER.debug("Updated %s states to remove old_state_id", disconnected_rows) + + +def _purge_state_ids(session: Session, state_ids: list) -> None: + """Delete by state id.""" + deleted_rows = ( + session.query(States) + .filter(States.state_id.in_(state_ids)) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s states", deleted_rows) + + +def _purge_event_ids(session: Session, event_ids: list) -> None: + """Delete by event id.""" + deleted_rows = ( + session.query(Events) + .filter(Events.event_id.in_(event_ids)) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s events", deleted_rows) + + +def _purge_old_recorder_runs( + instance: Recorder, session: Session, purge_before: datetime +) -> None: + """Purge all old recorder runs.""" + # Recorder runs is small, no need to batch run it + deleted_rows = ( + session.query(RecorderRuns) + .filter(RecorderRuns.start < purge_before) + .filter(RecorderRuns.run_id != instance.run_info.run_id) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) diff --git a/homeassistant/components/recorder/repack.py b/homeassistant/components/recorder/repack.py new file mode 100644 index 00000000000000..68d7d5954c92b6 --- /dev/null +++ b/homeassistant/components/recorder/repack.py @@ -0,0 +1,35 @@ +"""Purge repack helper.""" +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . import Recorder + +_LOGGER = logging.getLogger(__name__) + + +def repack_database(instance: Recorder) -> None: + """Repack based on engine type.""" + + # Execute sqlite command to free up space on disk + if instance.engine.dialect.name == "sqlite": + _LOGGER.debug("Vacuuming SQL DB to free space") + instance.engine.execute("VACUUM") + return + + # Execute postgresql vacuum command to free up space on disk + if instance.engine.dialect.name == "postgresql": + _LOGGER.debug("Vacuuming SQL DB to free space") + with instance.engine.connect().execution_options( + isolation_level="AUTOCOMMIT" + ) as conn: + conn.execute("VACUUM") + return + + # Optimize mysql / mariadb tables to free up space on disk + if instance.engine.dialect.name == "mysql": + _LOGGER.debug("Optimizing SQL DB to free space") + instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") + return diff --git a/setup.cfg b/setup.cfg index 98a01278838bbe..dbdb61b5fcf9e2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,7 @@ warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] strict = true ignore_errors = false warn_unreachable = true diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 791bd84b11b603..aaf5300086531b 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,7 +1,6 @@ """Test data purging.""" -from datetime import datetime, timedelta +from datetime import timedelta import json -from unittest.mock import patch from homeassistant.components import recorder from homeassistant.components.recorder.const import DATA_INSTANCE @@ -22,16 +21,21 @@ def test_purge_old_states(hass, hass_recorder): with session_scope(hass=hass) as session: states = session.query(States) assert states.count() == 6 + assert states[0].old_state_id is None + assert states[-1].old_state_id == states[-2].state_id - # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) - assert not finished - assert states.count() == 4 + events = session.query(Events).filter(Events.event_type == "state_changed") + assert events.count() == 6 + # run purge_old_data() finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) assert not finished assert states.count() == 2 + states_after_purge = session.query(States) + assert states_after_purge[1].old_state_id == states_after_purge[0].state_id + assert states_after_purge[0].old_state_id is None + finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) assert finished assert states.count() == 2 @@ -47,10 +51,6 @@ def test_purge_old_events(hass, hass_recorder): assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) - assert not finished - assert events.count() == 4 - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) assert not finished assert events.count() == 2 @@ -72,12 +72,15 @@ def test_purge_old_recorder_runs(hass, hass_recorder): assert recorder_runs.count() == 7 # run purge_old_data() + finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) + assert not finished + finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) assert finished assert recorder_runs.count() == 1 -def test_purge_method(hass, hass_recorder): +def test_purge_method(hass, hass_recorder, caplog): """Test purge method.""" hass = hass_recorder() service_data = {"keep_days": 4} @@ -131,23 +134,19 @@ def test_purge_method(hass, hass_recorder): assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all())) # run purge method - correct service data, with repack - with patch("homeassistant.components.recorder.purge._LOGGER") as mock_logger: - service_data["repack"] = True - hass.services.call("recorder", "purge", service_data=service_data) - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) - assert ( - mock_logger.debug.mock_calls[5][1][0] - == "Vacuuming SQL DB to free space" - ) + service_data["repack"] = True + hass.services.call("recorder", "purge", service_data=service_data) + hass.block_till_done() + hass.data[DATA_INSTANCE].block_till_done() + wait_recording_done(hass) + assert "Vacuuming SQL DB to free space" in caplog.text def _add_test_states(hass): """Add multiple states to the db for testing.""" - now = datetime.now() - five_days_ago = now - timedelta(days=5) - eleven_days_ago = now - timedelta(days=11) + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) attributes = {"test_attr": 5, "test_attr_10": "nice"} hass.block_till_done() @@ -155,6 +154,7 @@ def _add_test_states(hass): wait_recording_done(hass) with recorder.session_scope(hass=hass) as session: + old_state_id = None for event_id in range(6): if event_id < 2: timestamp = eleven_days_ago @@ -163,28 +163,39 @@ def _add_test_states(hass): timestamp = five_days_ago state = "purgeme" else: - timestamp = now + timestamp = utcnow state = "dontpurgeme" - session.add( - States( - entity_id="test.recorder2", - domain="sensor", - state=state, - attributes=json.dumps(attributes), - last_changed=timestamp, - last_updated=timestamp, - created=timestamp, - event_id=event_id + 1000, - ) + event = Events( + event_type="state_changed", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + session.add(event) + session.flush() + state = States( + entity_id="test.recorder2", + domain="sensor", + state=state, + attributes=json.dumps(attributes), + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + event_id=event.event_id, + old_state_id=old_state_id, ) + session.add(state) + session.flush() + old_state_id = state.state_id def _add_test_events(hass): """Add a few events for testing.""" - now = datetime.now() - five_days_ago = now - timedelta(days=5) - eleven_days_ago = now - timedelta(days=11) + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) event_data = {"test_attr": 5, "test_attr_10": "nice"} hass.block_till_done() @@ -200,7 +211,7 @@ def _add_test_events(hass): timestamp = five_days_ago event_type = "EVENT_TEST_PURGE" else: - timestamp = now + timestamp = utcnow event_type = "EVENT_TEST" session.add( @@ -216,9 +227,9 @@ def _add_test_events(hass): def _add_test_recorder_runs(hass): """Add a few recorder_runs for testing.""" - now = datetime.now() - five_days_ago = now - timedelta(days=5) - eleven_days_ago = now - timedelta(days=11) + utcnow = dt_util.utcnow() + five_days_ago = utcnow - timedelta(days=5) + eleven_days_ago = utcnow - timedelta(days=11) hass.block_till_done() hass.data[DATA_INSTANCE].block_till_done() @@ -231,7 +242,7 @@ def _add_test_recorder_runs(hass): elif rec_id < 4: timestamp = five_days_ago else: - timestamp = now + timestamp = utcnow session.add( RecorderRuns( From 7840db0598318061ea19e015c1df9e633f0cc34c Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 9 Mar 2021 16:26:54 -0800 Subject: [PATCH 1162/1818] Restore switch.turn_on and switch.turn_off functionality for SmartTub pumps (#47586) Revert "Remove turn_on and turn_off from SmartTub pump switches (#47184)" This reverts commit bab66a5cb968bcc7c6ef7787c9dc645885569429. --- homeassistant/components/smarttub/switch.py | 14 ++++++++++++++ tests/components/smarttub/test_switch.py | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index d2891932546150..26239df9dff799 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -61,6 +61,20 @@ def is_on(self) -> bool: """Return True if the pump is on.""" return self.pump.state != SpaPump.PumpState.OFF + async def async_turn_on(self, **kwargs) -> None: + """Turn the pump on.""" + + # the API only supports toggling + if not self.is_on: + await self.async_toggle() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the pump off.""" + + # the API only supports toggling + if self.is_on: + await self.async_toggle() + async def async_toggle(self, **kwargs) -> None: """Toggle the pump on or off.""" async with async_timeout.timeout(API_TIMEOUT): diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index 89e0ec03b238cd..81b53604065534 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -2,6 +2,8 @@ import pytest +from homeassistant.const import STATE_OFF, STATE_ON + @pytest.mark.parametrize( "pump_id,entity_suffix,pump_state", @@ -28,3 +30,22 @@ async def test_pumps(spa, setup_entry, hass, pump_id, pump_state, entity_suffix) blocking=True, ) pump.toggle.assert_called() + + if state.state == STATE_OFF: + await hass.services.async_call( + "switch", + "turn_on", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() + else: + assert state.state == STATE_ON + + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": entity_id}, + blocking=True, + ) + pump.toggle.assert_called() From 62e49e545b942b93733eda9e30f1e336314603fd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Mar 2021 01:53:00 +0100 Subject: [PATCH 1163/1818] Add confirm only for Elgato (#47684) --- homeassistant/components/elgato/config_flow.py | 1 + tests/components/elgato/test_config_flow.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index 7c818d6f3fa677..79960cc95ffbf2 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -53,6 +53,7 @@ async def async_step_zeroconf( except ElgatoError: return self.async_abort(reason="cannot_connect") + self._set_confirm_only() return self.async_show_form( step_id="zeroconf_confirm", description_placeholders={"serial_number": self.serial_number}, diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py index 0f3ff032722a91..49c85c5f2a25a3 100644 --- a/tests/components/elgato/test_config_flow.py +++ b/tests/components/elgato/test_config_flow.py @@ -82,6 +82,11 @@ async def test_full_zeroconf_flow_implementation( assert result["step_id"] == "zeroconf_confirm" assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + progress = hass.config_entries.flow.async_progress() + assert len(progress) == 1 + assert progress[0]["flow_id"] == result["flow_id"] + assert progress[0]["context"]["confirm_only"] is True + result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) From bf64421be92663ba807c3151980b36f1e4dd8ede Mon Sep 17 00:00:00 2001 From: Sam Steele Date: Tue, 9 Mar 2021 23:52:32 -0500 Subject: [PATCH 1164/1818] Use the local timezone when parsing Todoist due dates (#45994) --- homeassistant/components/todoist/calendar.py | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 1188831c26d786..b9ab4fe6dbb5d0 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -226,15 +226,14 @@ def handle_new_task(call): ) -def _parse_due_date(data: dict) -> datetime: +def _parse_due_date(data: dict, gmt_string) -> datetime: """Parse the due date dict into a datetime object.""" # Add time information to date only strings. if len(data["date"]) == 10: data["date"] += "T00:00:00" - # If there is no timezone provided, use UTC. - if data["timezone"] is None: - data["date"] += "Z" - return dt.parse_datetime(data["date"]) + if dt.parse_datetime(data["date"]).tzinfo is None: + data["date"] += gmt_string + return dt.as_utc(dt.parse_datetime(data["date"])) class TodoistProjectDevice(CalendarEventDevice): @@ -407,7 +406,9 @@ def create_todoist_task(self, data): # Generally speaking, that means right now. task[START] = dt.utcnow() if data[DUE] is not None: - task[END] = _parse_due_date(data[DUE]) + task[END] = _parse_due_date( + data[DUE], self._api.state["user"]["tz_info"]["gmt_string"] + ) if self._due_date_days is not None and ( task[END] > dt.utcnow() + self._due_date_days @@ -529,9 +530,19 @@ async def async_get_events(self, hass, start_date, end_date): for task in project_task_data: if task["due"] is None: continue - due_date = _parse_due_date(task["due"]) + due_date = _parse_due_date( + task["due"], self._api.state["user"]["tz_info"]["gmt_string"] + ) + midnight = dt.as_utc( + dt.parse_datetime( + due_date.strftime("%Y-%m-%d") + + "T00:00:00" + + self._api.state["user"]["tz_info"]["gmt_string"] + ) + ) + if start_date < due_date < end_date: - if due_date.hour == 0 and due_date.minute == 0: + if due_date == midnight: # If the due date has no time data, return just the date so that it # will render correctly as an all day event on a calendar. due_date_value = due_date.strftime("%Y-%m-%d") From 704000c0495bdd7ba73c1c312346ab894d047ddc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Mar 2021 06:23:11 +0100 Subject: [PATCH 1165/1818] Add support for breakpoints in scripts (#47632) --- .../components/automation/__init__.py | 8 +- homeassistant/components/config/automation.py | 195 +++++++- homeassistant/helpers/script.py | 159 ++++++- homeassistant/helpers/trace.py | 16 +- tests/components/config/test_automation.py | 424 ++++++++++++++++++ tests/helpers/test_script.py | 191 +++++++- 6 files changed, 961 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b9acfc0dde939f..6410954191dc0c 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -68,7 +68,12 @@ ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service -from homeassistant.helpers.trace import TraceElement, trace_get, trace_path +from homeassistant.helpers.trace import ( + TraceElement, + trace_get, + trace_id_set, + trace_path, +) from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass @@ -374,6 +379,7 @@ def _check_size_limit(self): def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" automation_trace = AutomationTrace(unique_id, config, trigger, context) + trace_id_set((unique_id, automation_trace.runid)) if unique_id: automation_traces = hass.data[DATA_AUTOMATION_TRACE] diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 708ad55aaeb66e..b5aa1bf7af5f01 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -16,7 +16,25 @@ ) from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD +from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.dispatcher import ( + DATA_DISPATCHER, + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.script import ( + SCRIPT_BREAKPOINT_HIT, + SCRIPT_DEBUG_CONTINUE_ALL, + breakpoint_clear, + breakpoint_clear_all, + breakpoint_list, + breakpoint_set, + debug_continue, + debug_step, + debug_stop, +) from . import ACTION_DELETE, EditIdBasedConfigView @@ -26,6 +44,13 @@ async def async_setup(hass): websocket_api.async_register_command(hass, websocket_automation_trace_get) websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) + websocket_api.async_register_command(hass, websocket_automation_debug_continue) + websocket_api.async_register_command(hass, websocket_automation_debug_step) + websocket_api.async_register_command(hass, websocket_automation_debug_stop) + websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" @@ -92,11 +117,12 @@ def _write_value(self, hass, data, config_key, new_value): data[index] = updated_value +@callback +@websocket_api.require_admin @websocket_api.websocket_command( {vol.Required("type"): "automation/trace/get", vol.Optional("automation_id"): str} ) -@websocket_api.async_response -async def websocket_automation_trace_get(hass, connection, msg): +def websocket_automation_trace_get(hass, connection, msg): """Get automation traces.""" automation_id = msg.get("automation_id") @@ -110,10 +136,171 @@ async def websocket_automation_trace_get(hass, connection, msg): connection.send_result(msg["id"], automation_traces) +@callback +@websocket_api.require_admin @websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) -@websocket_api.async_response -async def websocket_automation_trace_list(hass, connection, msg): +def websocket_automation_trace_list(hass, connection, msg): """Summarize automation traces.""" automation_traces = get_debug_traces(hass, summary=True) connection.send_result(msg["id"], automation_traces) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/set", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_set(hass, connection, msg): + """Set breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + raise HomeAssistantError("No breakpoint subscription") + + result = breakpoint_set(hass, automation_id, run_id, node) + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/clear", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_clear(hass, connection, msg): + """Clear breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + result = breakpoint_clear(hass, automation_id, run_id, node) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/list"} +) +def websocket_automation_breakpoint_list(hass, connection, msg): + """List breakpoints.""" + breakpoints = breakpoint_list(hass) + for _breakpoint in breakpoints: + _breakpoint["automation_id"] = _breakpoint.pop("unique_id") + + connection.send_result(msg["id"], breakpoints) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/subscribe"} +) +def websocket_subscribe_breakpoint_events(hass, connection, msg): + """Subscribe to breakpoint events.""" + + @callback + def breakpoint_hit(automation_id, run_id, node): + """Forward events to websocket.""" + connection.send_message( + websocket_api.event_message( + msg["id"], + { + "automation_id": automation_id, + "run_id": run_id, + "node": node, + }, + ) + ) + + remove_signal = async_dispatcher_connect( + hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit + ) + + @callback + def unsub(): + """Unsubscribe from breakpoint events.""" + remove_signal() + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + breakpoint_clear_all(hass) + async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL) + + connection.subscriptions[msg["id"]] = unsub + + connection.send_message(websocket_api.result_message(msg["id"])) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/continue", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_continue(hass, connection, msg): + """Resume execution of halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_continue(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/step", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_step(hass, connection, msg): + """Single step a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_step(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/stop", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_stop(hass, connection, msg): + """Stop a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_stop(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index b3fcffd4944418..257fd6d971577f 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,6 +1,6 @@ """Helpers to execute scripts.""" import asyncio -from contextlib import contextmanager +from contextlib import asynccontextmanager from datetime import datetime, timedelta from functools import partial import itertools @@ -65,6 +65,10 @@ ) from homeassistant.helpers import condition, config_validation as cv, service, template from homeassistant.helpers.condition import trace_condition_function +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( @@ -78,6 +82,7 @@ from .trace import ( TraceElement, trace_append_element, + trace_id_get, trace_path, trace_path_get, trace_set_result, @@ -111,6 +116,9 @@ ATTR_MAX = "max" DATA_SCRIPTS = "helpers.script" +DATA_SCRIPT_BREAKPOINTS = "helpers.script_breakpoints" +RUN_ID_ANY = "*" +NODE_ANY = "*" _LOGGER = logging.getLogger(__name__) @@ -122,6 +130,10 @@ ACTION_TRACE_NODE_MAX_LEN = 20 # Max length of a trace node for repeated actions +SCRIPT_BREAKPOINT_HIT = "script_breakpoint_hit" +SCRIPT_DEBUG_CONTINUE_STOP = "script_debug_continue_stop_{}_{}" +SCRIPT_DEBUG_CONTINUE_ALL = "script_debug_continue_all" + def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" @@ -130,11 +142,57 @@ def action_trace_append(variables, path): return trace_element -@contextmanager -def trace_action(variables): +@asynccontextmanager +async def trace_action(hass, script_run, stop, variables): """Trace action execution.""" - trace_element = action_trace_append(variables, trace_path_get()) + path = trace_path_get() + trace_element = action_trace_append(variables, path) trace_stack_push(trace_stack_cv, trace_element) + + trace_id = trace_id_get() + if trace_id: + unique_id = trace_id[0] + run_id = trace_id[1] + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + if unique_id in breakpoints and ( + ( + run_id in breakpoints[unique_id] + and ( + path in breakpoints[unique_id][run_id] + or NODE_ANY in breakpoints[unique_id][run_id] + ) + ) + or ( + RUN_ID_ANY in breakpoints[unique_id] + and ( + path in breakpoints[unique_id][RUN_ID_ANY] + or NODE_ANY in breakpoints[unique_id][RUN_ID_ANY] + ) + ) + ): + async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, unique_id, run_id, path) + + done = asyncio.Event() + + @callback + def async_continue_stop(command=None): + if command == "stop": + stop.set() + done.set() + + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + remove_signal1 = async_dispatcher_connect(hass, signal, async_continue_stop) + remove_signal2 = async_dispatcher_connect( + hass, SCRIPT_DEBUG_CONTINUE_ALL, async_continue_stop + ) + + tasks = [hass.async_create_task(flag.wait()) for flag in (stop, done)] + await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + for task in tasks: + task.cancel() + remove_signal1() + remove_signal2() + try: yield trace_element except Exception as ex: # pylint: disable=broad-except @@ -294,16 +352,19 @@ async def async_run(self) -> None: self._finish() async def _async_step(self, log_exceptions): - with trace_path(str(self._step)), trace_action(self._variables): - try: - handler = f"_async_{cv.determine_script_action(self._action)}_step" - await getattr(self, handler)() - except Exception as ex: - if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( - self._log_exceptions or log_exceptions - ): - self._log_exception(ex) - raise + with trace_path(str(self._step)): + async with trace_action(self._hass, self, self._stop, self._variables): + if self._stop.is_set(): + return + try: + handler = f"_async_{cv.determine_script_action(self._action)}_step" + await getattr(self, handler)() + except Exception as ex: + if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( + self._log_exceptions or log_exceptions + ): + self._log_exception(ex) + raise def _finish(self) -> None: self._script._runs.remove(self) # pylint: disable=protected-access @@ -876,6 +937,8 @@ def __init__( all_scripts.append( {"instance": self, "started_before_shutdown": not hass.is_stopping} ) + if DATA_SCRIPT_BREAKPOINTS not in hass.data: + hass.data[DATA_SCRIPT_BREAKPOINTS] = {} self._hass = hass self.sequence = sequence @@ -1213,3 +1276,71 @@ def _log( self._logger.exception(msg, *args, **kwargs) else: self._logger.log(level, msg, *args, **kwargs) + + +@callback +def breakpoint_clear(hass, unique_id, run_id, node): + """Clear a breakpoint.""" + run_id = run_id or RUN_ID_ANY + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + if unique_id not in breakpoints or run_id not in breakpoints[unique_id]: + return + breakpoints[unique_id][run_id].discard(node) + + +@callback +def breakpoint_clear_all(hass): + """Clear all breakpoints.""" + hass.data[DATA_SCRIPT_BREAKPOINTS] = {} + + +@callback +def breakpoint_set(hass, unique_id, run_id, node): + """Set a breakpoint.""" + run_id = run_id or RUN_ID_ANY + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + if unique_id not in breakpoints: + breakpoints[unique_id] = {} + if run_id not in breakpoints[unique_id]: + breakpoints[unique_id][run_id] = set() + breakpoints[unique_id][run_id].add(node) + + +@callback +def breakpoint_list(hass): + """List breakpoints.""" + breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] + + return [ + {"unique_id": unique_id, "run_id": run_id, "node": node} + for unique_id in breakpoints + for run_id in breakpoints[unique_id] + for node in breakpoints[unique_id][run_id] + ] + + +@callback +def debug_continue(hass, unique_id, run_id): + """Continue execution of a halted script.""" + # Clear any wildcard breakpoint + breakpoint_clear(hass, unique_id, run_id, NODE_ANY) + + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + async_dispatcher_send(hass, signal, "continue") + + +@callback +def debug_step(hass, unique_id, run_id): + """Single step a halted script.""" + # Set a wildcard breakpoint + breakpoint_set(hass, unique_id, run_id, NODE_ANY) + + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + async_dispatcher_send(hass, signal, "continue") + + +@callback +def debug_stop(hass, unique_id, run_id): + """Stop execution of a running or halted script.""" + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + async_dispatcher_send(hass, signal, "stop") diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 0c1969a8ac6d8d..e0c67a1de546d0 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -2,7 +2,7 @@ from collections import deque from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Deque, Dict, Generator, List, Optional, Union, cast +from typing import Any, Deque, Dict, Generator, List, Optional, Tuple, Union, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util @@ -67,6 +67,20 @@ def as_dict(self) -> Dict[str, Any]: ) # Copy of last variables variables_cv: ContextVar[Optional[Any]] = ContextVar("variables_cv", default=None) +# Automation ID + Run ID +trace_id_cv: ContextVar[Optional[Tuple[str, str]]] = ContextVar( + "trace_id_cv", default=None +) + + +def trace_id_set(trace_id: Tuple[str, str]) -> None: + """Set id of the current trace.""" + trace_id_cv.set(trace_id) + + +def trace_id_get() -> Optional[Tuple[str, str]]: + """Get id if the current trace.""" + return trace_id_cv.get() def trace_stack_push(trace_stack_var: ContextVar, node: Any) -> None: diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index d52295c75f73cd..2880287be941e9 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -6,6 +6,7 @@ from homeassistant.components import automation, config from homeassistant.helpers import entity_registry as er +from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -511,3 +512,426 @@ def next_id(): assert trace["timestamp"] assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" + + +async def test_automation_breakpoints(hass, hass_ws_client): + """Test automation breakpoints.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert_lists_same( + response["result"], + [ + {"node": "action/1", "run_id": "*", "automation_id": "sun"}, + {"node": "action/5", "run_id": "*", "automation_id": "sun"}, + ], + ) + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/step", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/2", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/2", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + +async def test_automation_breakpoints_2(hass, hass_ws_client): + """Test execution resumes and breakpoints are removed after subscription removed.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + # Unsubscribe - execution should resume + await client.send_json( + {"id": next_id(), "type": "unsubscribe_events", "subscription": subscription_id} + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/8", "stopped") + + # Should not be possible to set breakpoints + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + # Trigger "sun" automation, should finish without stopping on breakpoints + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + new_run_id = await assert_last_action("sun", "action/8", "stopped") + assert new_run_id != run_id + + +async def test_automation_breakpoints_3(hass, hass_ws_client): + """Test breakpoints can be cleared.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + # Clear 1st breakpoint + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/clear", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 18c769fee73e5f..7cb4b627a945f3 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -18,6 +18,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON from homeassistant.core import Context, CoreState, callback from homeassistant.helpers import config_validation as cv, script, trace +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -80,14 +81,17 @@ async def test_firing_event_basic(hass, caplog): sequence = cv.SCRIPT_SCHEMA( {"alias": alias, "event": event, "event_data": {"hello": "world"}} ) - with script.trace_action(None): - script_obj = script.Script( - hass, - sequence, - "Test Name", - "test_domain", - running_description="test script", - ) + + # Prepare tracing + trace.trace_get() + + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + running_description="test script", + ) await script_obj.async_run(context=context) await hass.async_block_till_done() @@ -100,7 +104,6 @@ async def test_firing_event_basic(hass, caplog): assert f"Executing step {alias}" in caplog.text assert_action_trace( { - "": [{}], "0": [{}], } ) @@ -1215,8 +1218,11 @@ async def test_repeat_count(hass, caplog, count): }, } ) - with script.trace_action(None): - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + + # Prepare tracing + trace.trace_get() + + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=Context()) await hass.async_block_till_done() @@ -1229,7 +1235,6 @@ async def test_repeat_count(hass, caplog, count): assert caplog.text.count(f"Repeating {alias}") == count assert_action_trace( { - "": [{}], "0": [{}], "0/0/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), } @@ -2348,3 +2353,165 @@ async def trigger_wait_event(_): await hass.async_block_till_done() assert len(mock_calls) == 1 + + +async def test_breakpoints_1(hass): + """Test setting a breakpoint halts execution, and execution can be resumed.""" + event = "test_event" + events = async_capture_events(hass, event) + sequence = cv.SCRIPT_SCHEMA( + [ + {"event": event, "event_data": {"value": 0}}, # Node "0" + {"event": event, "event_data": {"value": 1}}, # Node "1" + {"event": event, "event_data": {"value": 2}}, # Node "2" + {"event": event, "event_data": {"value": 3}}, # Node "3" + {"event": event, "event_data": {"value": 4}}, # Node "4" + {"event": event, "event_data": {"value": 5}}, # Node "5" + {"event": event, "event_data": {"value": 6}}, # Node "6" + {"event": event, "event_data": {"value": 7}}, # Node "7" + ] + ) + logger = logging.getLogger("TEST") + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + script_mode="queued", + max_runs=2, + logger=logger, + ) + trace.trace_id_set(("script_1", "1")) + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "1") + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "5") + + breakpoint_hit_event = asyncio.Event() + + @callback + def breakpoint_hit(*_): + breakpoint_hit_event.set() + + async_dispatcher_connect(hass, script.SCRIPT_BREAKPOINT_HIT, breakpoint_hit) + + watch_messages = [] + + @callback + def check_action(): + for message, flag in watch_messages: + if script_obj.last_action and message in script_obj.last_action: + flag.set() + + script_obj.change_listener = check_action + + assert not script_obj.is_running + assert script_obj.runs == 0 + + # Start script, should stop on breakpoint at node "1" + hass.async_create_task(script_obj.async_run(context=Context())) + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 1 + assert events[-1].data["value"] == 0 + + # Single step script, should stop at node "2" + breakpoint_hit_event.clear() + script.debug_step(hass, "script_1", "1") + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 2 + assert events[-1].data["value"] == 1 + + # Single step script, should stop at node "3" + breakpoint_hit_event.clear() + script.debug_step(hass, "script_1", "1") + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 3 + assert events[-1].data["value"] == 2 + + # Resume script, should stop on breakpoint at node "5" + breakpoint_hit_event.clear() + script.debug_continue(hass, "script_1", "1") + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 5 + assert events[-1].data["value"] == 4 + + # Resume script, should run until completion + script.debug_continue(hass, "script_1", "1") + await hass.async_block_till_done() + assert not script_obj.is_running + assert script_obj.runs == 0 + assert len(events) == 8 + assert events[-1].data["value"] == 7 + + +async def test_breakpoints_2(hass): + """Test setting a breakpoint halts execution, and execution can be aborted.""" + event = "test_event" + events = async_capture_events(hass, event) + sequence = cv.SCRIPT_SCHEMA( + [ + {"event": event, "event_data": {"value": 0}}, # Node "0" + {"event": event, "event_data": {"value": 1}}, # Node "1" + {"event": event, "event_data": {"value": 2}}, # Node "2" + {"event": event, "event_data": {"value": 3}}, # Node "3" + {"event": event, "event_data": {"value": 4}}, # Node "4" + {"event": event, "event_data": {"value": 5}}, # Node "5" + {"event": event, "event_data": {"value": 6}}, # Node "6" + {"event": event, "event_data": {"value": 7}}, # Node "7" + ] + ) + logger = logging.getLogger("TEST") + script_obj = script.Script( + hass, + sequence, + "Test Name", + "test_domain", + script_mode="queued", + max_runs=2, + logger=logger, + ) + trace.trace_id_set(("script_1", "1")) + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "1") + script.breakpoint_set(hass, "script_1", script.RUN_ID_ANY, "5") + + breakpoint_hit_event = asyncio.Event() + + @callback + def breakpoint_hit(*_): + breakpoint_hit_event.set() + + async_dispatcher_connect(hass, script.SCRIPT_BREAKPOINT_HIT, breakpoint_hit) + + watch_messages = [] + + @callback + def check_action(): + for message, flag in watch_messages: + if script_obj.last_action and message in script_obj.last_action: + flag.set() + + script_obj.change_listener = check_action + + assert not script_obj.is_running + assert script_obj.runs == 0 + + # Start script, should stop on breakpoint at node "1" + hass.async_create_task(script_obj.async_run(context=Context())) + await breakpoint_hit_event.wait() + assert script_obj.is_running + assert script_obj.runs == 1 + assert len(events) == 1 + assert events[-1].data["value"] == 0 + + # Abort script + script.debug_stop(hass, "script_1", "1") + await hass.async_block_till_done() + assert not script_obj.is_running + assert script_obj.runs == 0 + assert len(events) == 1 From a50e9812cb8f952c3bd36a960b4ac286c79e4b24 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Mar 2021 22:40:17 -0800 Subject: [PATCH 1166/1818] Fix automations with traces. (#47705) --- homeassistant/components/automation/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 6410954191dc0c..7e1352afc2f598 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -379,7 +379,7 @@ def _check_size_limit(self): def trace_automation(hass, unique_id, config, trigger, context): """Trace action execution of automation with automation_id.""" automation_trace = AutomationTrace(unique_id, config, trigger, context) - trace_id_set((unique_id, automation_trace.runid)) + trace_id_set((unique_id, automation_trace.run_id)) if unique_id: automation_traces = hass.data[DATA_AUTOMATION_TRACE] From 5c70dd8ab4f531947f6a4796ad4216a984875e5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Mar 2021 09:40:03 +0100 Subject: [PATCH 1167/1818] Bump codecov/codecov-action from v1.2.1 to v1.2.2 (#47707) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.2.1 to v1.2.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1.2.1...1f8f3abcccf7960749744fd13547965f0e7d1bdd) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b7599340506ffb..f395a4b3c425eb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -739,4 +739,4 @@ jobs: coverage report --fail-under=94 coverage xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.2.1 + uses: codecov/codecov-action@v1.2.2 From 5bc0e9a50f06253df69e74d8eab05debc69ce415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Wed, 10 Mar 2021 10:25:04 +0100 Subject: [PATCH 1168/1818] Fix aemet temperatures with a value of 0 (#47680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * aemet: catch TypeError exceptions format_float() and format_int() should also catch possible TypeError exceptions. Signed-off-by: Álvaro Fernández Rojas * aemet: correctly parse temperatures with a value of 0 Right now, when a temperature with a value of 0 is provided by the API, the if condition isn't satisfied, return None instead of 0. Signed-off-by: Álvaro Fernández Rojas * aemet: group format int/float exceptions Signed-off-by: Álvaro Fernández Rojas --- .../aemet/weather_update_coordinator.py | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 619429c9a5b275..1a70baa6765a82 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -98,7 +98,7 @@ def format_float(value) -> float: """Try converting string to float.""" try: return float(value) - except ValueError: + except (TypeError, ValueError): return None @@ -106,7 +106,7 @@ def format_int(value) -> int: """Try converting string to int.""" try: return int(value) - except ValueError: + except (TypeError, ValueError): return None @@ -535,9 +535,7 @@ def _get_storm_prob(day_data, hour): def _get_temperature(day_data, hour): """Get temperature (hour) from weather data.""" val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE], hour) - if val: - return format_int(val) - return None + return format_int(val) @staticmethod def _get_temperature_day(day_data): @@ -545,9 +543,7 @@ def _get_temperature_day(day_data): val = get_forecast_day_value( day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MAX ) - if val: - return format_int(val) - return None + return format_int(val) @staticmethod def _get_temperature_low_day(day_data): @@ -555,17 +551,13 @@ def _get_temperature_low_day(day_data): val = get_forecast_day_value( day_data[AEMET_ATTR_TEMPERATURE], key=AEMET_ATTR_MIN ) - if val: - return format_int(val) - return None + return format_int(val) @staticmethod def _get_temperature_feeling(day_data, hour): """Get temperature from weather data.""" val = get_forecast_hour_value(day_data[AEMET_ATTR_TEMPERATURE_FEELING], hour) - if val: - return format_int(val) - return None + return format_int(val) def _get_town_id(self): """Get town ID from weather data.""" From 461e766a93556b47996ab35ae85cd8123cbcfa7d Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Wed, 10 Mar 2021 12:52:55 +0100 Subject: [PATCH 1169/1818] Add device class CO2 to various integrations (#47676) * Add device class CO2 to Fibaro * Add device class CO2 to Awair * Add device class CO2 to Tasmota * Add device class CO2 to Netatmo * Add device class CO2 to Ambient Station * Update Tasmota tests * Remove icon --- homeassistant/components/ambient_station/__init__.py | 3 ++- homeassistant/components/awair/const.py | 3 ++- homeassistant/components/fibaro/sensor.py | 9 ++++++++- homeassistant/components/netatmo/sensor.py | 3 ++- homeassistant/components/tasmota/sensor.py | 3 ++- tests/components/tasmota/test_sensor.py | 8 ++++---- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index b4ac6992459f70..bee5ab33db74b5 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -18,6 +18,7 @@ CONCENTRATION_PARTS_PER_MILLION, CONF_API_KEY, DEGREE, + DEVICE_CLASS_CO2, EVENT_HOMEASSISTANT_STOP, IRRADIATION_WATTS_PER_SQUARE_METER, LIGHT_LUX, @@ -159,7 +160,7 @@ TYPE_BATT8: ("Battery 8", None, BINARY_SENSOR, "battery"), TYPE_BATT9: ("Battery 9", None, BINARY_SENSOR, "battery"), TYPE_BATTOUT: ("Battery", None, BINARY_SENSOR, "battery"), - TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, None), + TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, SENSOR, DEVICE_CLASS_CO2), TYPE_DAILYRAININ: ("Daily Rain", "in", SENSOR, None), TYPE_DEWPOINT: ("Dew Point", TEMP_FAHRENHEIT, SENSOR, "temperature"), TYPE_EVENTRAININ: ("Event Rain", "in", SENSOR, None), diff --git a/homeassistant/components/awair/const.py b/homeassistant/components/awair/const.py index 44490b8401f4dc..2853ef9dd6c81e 100644 --- a/homeassistant/components/awair/const.py +++ b/homeassistant/components/awair/const.py @@ -12,6 +12,7 @@ CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, @@ -104,7 +105,7 @@ ATTR_UNIQUE_ID: "PM10", # matches legacy format }, API_CO2: { - ATTR_DEVICE_CLASS: None, + ATTR_DEVICE_CLASS: DEVICE_CLASS_CO2, ATTR_ICON: "mdi:cloud", ATTR_UNIT: CONCENTRATION_PARTS_PER_MILLION, ATTR_LABEL: "Carbon dioxide", diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index 4e9af8803f23b4..40a843bc7bba02 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -2,6 +2,7 @@ from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, @@ -27,7 +28,13 @@ "mdi:fire", None, ], - "CO2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:cloud", None], + "CO2": [ + "CO2", + CONCENTRATION_PARTS_PER_MILLION, + None, + None, + DEVICE_CLASS_CO2, + ], "com.fibaro.humiditySensor": [ "Humidity", PERCENTAGE, diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 9176b670bea4e9..efda94d6399c4f 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -8,6 +8,7 @@ CONCENTRATION_PARTS_PER_MILLION, DEGREE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_SIGNAL_STRENGTH, @@ -52,7 +53,7 @@ SENSOR_TYPES = { "temperature": ["Temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE, True], "temp_trend": ["Temperature trend", None, "mdi:trending-up", None, False], - "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:molecule-co2", None, True], + "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, None, DEVICE_CLASS_CO2, True], "pressure": ["Pressure", PRESSURE_MBAR, None, DEVICE_CLASS_PRESSURE, True], "pressure_trend": ["Pressure trend", None, "mdi:trending-up", None, False], "noise": ["Noise", "dB", "mdi:volume-high", None, True], diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 39577e9e558179..0387e835522622 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -9,6 +9,7 @@ CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -52,7 +53,7 @@ hc.SENSOR_APPARENT_POWERUSAGE: {DEVICE_CLASS: DEVICE_CLASS_POWER}, hc.SENSOR_BATTERY: {DEVICE_CLASS: DEVICE_CLASS_BATTERY}, hc.SENSOR_CCT: {ICON: "mdi:temperature-kelvin"}, - hc.SENSOR_CO2: {ICON: "mdi:molecule-co2"}, + hc.SENSOR_CO2: {DEVICE_CLASS: DEVICE_CLASS_CO2}, hc.SENSOR_COLOR_BLUE: {ICON: "mdi:palette"}, hc.SENSOR_COLOR_GREEN: {ICON: "mdi:palette"}, hc.SENSOR_COLOR_RED: {ICON: "mdi:palette"}, diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index c08b1b531de764..2b7c388ca2f19f 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -446,9 +446,9 @@ async def test_attributes(hass, mqtt_mock, setup_tasmota): assert state.attributes.get("unit_of_measurement") == "°C" state = hass.states.get("sensor.tasmota_beer_CarbonDioxide") - assert state.attributes.get("device_class") is None + assert state.attributes.get("device_class") == "carbon_dioxide" assert state.attributes.get("friendly_name") == "Tasmota Beer CarbonDioxide" - assert state.attributes.get("icon") == "mdi:molecule-co2" + assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "ppm" @@ -516,9 +516,9 @@ async def test_indexed_sensor_attributes(hass, mqtt_mock, setup_tasmota): assert state.attributes.get("unit_of_measurement") == "°C" state = hass.states.get("sensor.tasmota_dummy2_carbondioxide_1") - assert state.attributes.get("device_class") is None + assert state.attributes.get("device_class") == "carbon_dioxide" assert state.attributes.get("friendly_name") == "Tasmota Dummy2 CarbonDioxide 1" - assert state.attributes.get("icon") == "mdi:molecule-co2" + assert state.attributes.get("icon") is None assert state.attributes.get("unit_of_measurement") == "ppm" From d53f1e98acac70b217c41a7f4e9005f0508983bc Mon Sep 17 00:00:00 2001 From: David Bonnes Date: Wed, 10 Mar 2021 17:58:04 +0000 Subject: [PATCH 1170/1818] bump client library (#47722) --- homeassistant/components/evohome/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/evohome/manifest.json b/homeassistant/components/evohome/manifest.json index 8bcecca551b14d..e707387ce4f000 100644 --- a/homeassistant/components/evohome/manifest.json +++ b/homeassistant/components/evohome/manifest.json @@ -2,6 +2,6 @@ "domain": "evohome", "name": "Honeywell Total Connect Comfort (Europe)", "documentation": "https://www.home-assistant.io/integrations/evohome", - "requirements": ["evohome-async==0.3.5.post1"], + "requirements": ["evohome-async==0.3.8"], "codeowners": ["@zxdavb"] } diff --git a/requirements_all.txt b/requirements_all.txt index e16a101ce808e6..2a4242bef656bb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -569,7 +569,7 @@ eternalegypt==0.0.12 # evdev==1.1.2 # homeassistant.components.evohome -evohome-async==0.3.5.post1 +evohome-async==0.3.8 # homeassistant.components.faa_delays faadelays==0.0.6 From 2103335323acd5575e050a3840f5a1e9ea01ee37 Mon Sep 17 00:00:00 2001 From: David Bonnes Date: Wed, 10 Mar 2021 17:58:37 +0000 Subject: [PATCH 1171/1818] Bump incomfort client to 0.4.4 (#47718) * bump incomfort client * bump client to 0.4.4 * restore launch.json --- homeassistant/components/incomfort/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/incomfort/manifest.json b/homeassistant/components/incomfort/manifest.json index 80b6952c3836a6..891cbb20be453c 100644 --- a/homeassistant/components/incomfort/manifest.json +++ b/homeassistant/components/incomfort/manifest.json @@ -2,6 +2,6 @@ "domain": "incomfort", "name": "Intergas InComfort/Intouch Lan2RF gateway", "documentation": "https://www.home-assistant.io/integrations/incomfort", - "requirements": ["incomfort-client==0.4.0"], + "requirements": ["incomfort-client==0.4.4"], "codeowners": ["@zxdavb"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2a4242bef656bb..7bc5592a62654c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -817,7 +817,7 @@ iglo==1.2.7 ihcsdk==2.7.0 # homeassistant.components.incomfort -incomfort-client==0.4.0 +incomfort-client==0.4.4 # homeassistant.components.influxdb influxdb-client==1.14.0 From 7c8851264fbaa3574e58095a47b39f73720cab7f Mon Sep 17 00:00:00 2001 From: CurrentThread <62957822+CurrentThread@users.noreply.github.com> Date: Wed, 10 Mar 2021 19:12:58 +0100 Subject: [PATCH 1172/1818] Use LONGTEXT column instead of TEXT for MySQL/MariaDB and migrate existing databases (#47026) --- .../components/recorder/migration.py | 42 +++++++++++++++++++ homeassistant/components/recorder/models.py | 7 ++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index aeb62cc111da0a..e730b1af239f24 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -204,6 +204,44 @@ def _add_columns(engine, table_name, columns_def): ) +def _modify_columns(engine, table_name, columns_def): + """Modify columns in a table.""" + _LOGGER.warning( + "Modifying columns %s in table %s. Note: this can take several " + "minutes on large databases and slow computers. Please " + "be patient!", + ", ".join(column.split(" ")[0] for column in columns_def), + table_name, + ) + columns_def = [f"MODIFY {col_def}" for col_def in columns_def] + + try: + engine.execute( + text( + "ALTER TABLE {table} {columns_def}".format( + table=table_name, columns_def=", ".join(columns_def) + ) + ) + ) + return + except (InternalError, OperationalError): + _LOGGER.info("Unable to use quick column modify. Modifying 1 by 1") + + for column_def in columns_def: + try: + engine.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(engine): """Add the options to foreign key constraints.""" inspector = reflection.Inspector.from_engine(engine) @@ -321,6 +359,10 @@ def _apply_update(engine, new_version, old_version): elif new_version == 11: _create_index(engine, "states", "ix_states_old_state_id") _update_states_table_with_foreign_key_options(engine) + elif new_version == 12: + if engine.dialect.name == "mysql": + _modify_columns(engine, "events", ["event_data LONGTEXT"]) + _modify_columns(engine, "states", ["attributes LONGTEXT"]) else: raise ValueError(f"No schema migration defined for version {new_version}") diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 69e2115ce34026..551abeac15ab2a 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -13,6 +13,7 @@ Text, distinct, ) +from sqlalchemy.dialects import mysql from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from sqlalchemy.orm.session import Session @@ -25,7 +26,7 @@ # pylint: disable=invalid-name Base = declarative_base() -SCHEMA_VERSION = 11 +SCHEMA_VERSION = 12 _LOGGER = logging.getLogger(__name__) @@ -49,7 +50,7 @@ class Events(Base): # type: ignore __tablename__ = TABLE_EVENTS event_id = Column(Integer, primary_key=True) event_type = Column(String(32)) - event_data = Column(Text) + event_data = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) origin = Column(String(32)) time_fired = Column(DateTime(timezone=True), index=True) created = Column(DateTime(timezone=True), default=dt_util.utcnow) @@ -109,7 +110,7 @@ class States(Base): # type: ignore domain = Column(String(64)) entity_id = Column(String(255)) state = Column(String(255)) - attributes = Column(Text) + attributes = Column(Text().with_variant(mysql.LONGTEXT, "mysql")) event_id = Column( Integer, ForeignKey("events.event_id", ondelete="CASCADE"), index=True ) From 78c974d5278a5fb975ced94a6a37ec609c21b99e Mon Sep 17 00:00:00 2001 From: Mike Keesey Date: Wed, 10 Mar 2021 11:19:04 -0700 Subject: [PATCH 1173/1818] Refactor Harmony tests to better follow Home Assistant conventions (#47141) --- tests/components/harmony/conftest.py | 27 +++- .../harmony/test_connection_changes.py | 67 --------- .../{test_commands.py => test_remote.py} | 131 +++++++++++++++++- ...est_activity_changes.py => test_switch.py} | 100 ++++++------- 4 files changed, 188 insertions(+), 137 deletions(-) delete mode 100644 tests/components/harmony/test_connection_changes.py rename tests/components/harmony/{test_commands.py => test_remote.py} (62%) rename tests/components/harmony/{test_activity_changes.py => test_switch.py} (64%) diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index 29e897916b9bbe..5072eb0deb9bbb 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -37,10 +37,10 @@ class FakeHarmonyClient: """FakeHarmonyClient to mock away network calls.""" - def __init__( + def initialize( self, ip_address: str = "", callbacks: ClientCallbackType = MagicMock() ): - """Initialize FakeHarmonyClient class.""" + """Initialize FakeHarmonyClient class to capture callbacks.""" self._activity_name = "Watch TV" self.close = AsyncMock() self.send_commands = AsyncMock() @@ -49,6 +49,8 @@ def __init__( self._callbacks = callbacks self.fw_version = "123.456" + return self + async def connect(self): """Connect and call the appropriate callbacks.""" self._callbacks.connect(None) @@ -130,13 +132,28 @@ def hub_config(self): ) return config + def mock_reconnection(self): + """Simulate reconnection to the hub.""" + self._callbacks.connect(None) + + def mock_disconnection(self): + """Simulate disconnection to the hub.""" + self._callbacks.disconnect(None) + @pytest.fixture() -def mock_hc(): - """Create a mock HarmonyClient.""" +def harmony_client(): + """Create the FakeHarmonyClient instance.""" + return FakeHarmonyClient() + + +@pytest.fixture() +def mock_hc(harmony_client): + """Patch the real HarmonyClient with initialization side effect.""" + with patch( "homeassistant.components.harmony.data.HarmonyClient", - side_effect=FakeHarmonyClient, + side_effect=harmony_client.initialize, ) as fake: yield fake diff --git a/tests/components/harmony/test_connection_changes.py b/tests/components/harmony/test_connection_changes.py deleted file mode 100644 index 15d462988555a0..00000000000000 --- a/tests/components/harmony/test_connection_changes.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Test the Logitech Harmony Hub entities with connection state changes.""" - -from datetime import timedelta - -from homeassistant.components.harmony.const import DOMAIN -from homeassistant.const import ( - CONF_HOST, - CONF_NAME, - STATE_OFF, - STATE_ON, - STATE_UNAVAILABLE, -) -from homeassistant.util import utcnow - -from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME - -from tests.common import MockConfigEntry, async_fire_time_changed - - -async def test_connection_state_changes(mock_hc, hass, mock_write_config): - """Ensure connection changes are reflected in the switch states.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - data = hass.data[DOMAIN][entry.entry_id] - - # mocks start with current activity == Watch TV - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - data._disconnected() - await hass.async_block_till_done() - - # Entities do not immediately show as unavailable - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - future_time = utcnow() + timedelta(seconds=10) - async_fire_time_changed(hass, future_time) - await hass.async_block_till_done() - assert hass.states.is_state(ENTITY_REMOTE, STATE_UNAVAILABLE) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_UNAVAILABLE) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_UNAVAILABLE) - - data._connected() - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - data._disconnected() - data._connected() - future_time = utcnow() + timedelta(seconds=10) - async_fire_time_changed(hass, future_time) - - await hass.async_block_till_done() - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) diff --git a/tests/components/harmony/test_commands.py b/tests/components/harmony/test_remote.py similarity index 62% rename from tests/components/harmony/test_commands.py rename to tests/components/harmony/test_remote.py index 62056a08e1dff2..8c4d67e1117da0 100644 --- a/tests/components/harmony/test_commands.py +++ b/tests/components/harmony/test_remote.py @@ -1,4 +1,6 @@ -"""Test sending commands to the Harmony Hub remote.""" +"""Test the Logitech Harmony Hub remote.""" + +from datetime import timedelta from aioharmony.const import SendCommandDevice @@ -9,6 +11,7 @@ ) from homeassistant.components.harmony.remote import ATTR_CHANNEL, ATTR_DELAY_SECS from homeassistant.components.remote import ( + ATTR_ACTIVITY, ATTR_COMMAND, ATTR_DEVICE, ATTR_NUM_REPEATS, @@ -16,18 +19,136 @@ DEFAULT_HOLD_SECS, DOMAIN as REMOTE_DOMAIN, SERVICE_SEND_COMMAND, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + CONF_NAME, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, ) -from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME +from homeassistant.util import utcnow -from .conftest import TV_DEVICE_ID, TV_DEVICE_NAME -from .const import ENTITY_REMOTE, HUB_NAME +from .conftest import ACTIVITIES_TO_IDS, TV_DEVICE_ID, TV_DEVICE_NAME +from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed PLAY_COMMAND = "Play" STOP_COMMAND = "Stop" +async def test_connection_state_changes( + harmony_client, mock_hc, hass, mock_write_config +): + """Ensure connection changes are reflected in the remote state.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} + ) + + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # mocks start with current activity == Watch TV + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + harmony_client.mock_disconnection() + await hass.async_block_till_done() + + # Entities do not immediately show as unavailable + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_REMOTE, STATE_UNAVAILABLE) + + harmony_client.mock_reconnection() + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + harmony_client.mock_disconnection() + harmony_client.mock_reconnection() + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + + +async def test_remote_toggles(mock_hc, hass, mock_write_config): + """Ensure calls to the remote also updates the switches.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} + ) + + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # mocks start with current activity == Watch TV + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + # turn off remote + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_REMOTE}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + # turn on remote, restoring the last activity + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_REMOTE}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + # send new activity command, with activity name + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: "Play Music"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_ON) + + # send new activity command, with activity id + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: ACTIVITIES_TO_IDS["Watch TV"]}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + async def test_async_send_command(mock_hc, hass, mock_write_config): """Ensure calls to send remote commands properly propagate to devices.""" entry = MockConfigEntry( diff --git a/tests/components/harmony/test_activity_changes.py b/tests/components/harmony/test_switch.py similarity index 64% rename from tests/components/harmony/test_activity_changes.py rename to tests/components/harmony/test_switch.py index dbbc6beef5bdad..1940c54e1123a5 100644 --- a/tests/components/harmony/test_activity_changes.py +++ b/tests/components/harmony/test_switch.py @@ -1,6 +1,8 @@ """Test the Logitech Harmony Hub activity switches.""" + +from datetime import timedelta + from homeassistant.components.harmony.const import DOMAIN -from homeassistant.components.remote import ATTR_ACTIVITY, DOMAIN as REMOTE_DOMAIN from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -12,16 +14,19 @@ CONF_NAME, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, ) +from homeassistant.util import utcnow -from .conftest import ACTIVITIES_TO_IDS from .const import ENTITY_PLAY_MUSIC, ENTITY_REMOTE, ENTITY_WATCH_TV, HUB_NAME -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed -async def test_switch_toggles(mock_hc, hass, mock_write_config): - """Ensure calls to the switch modify the harmony state.""" +async def test_connection_state_changes( + harmony_client, mock_hc, hass, mock_write_config +): + """Ensure connection changes are reflected in the switch states.""" entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} ) @@ -31,31 +36,40 @@ async def test_switch_toggles(mock_hc, hass, mock_write_config): await hass.async_block_till_done() # mocks start with current activity == Watch TV - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - # turn off watch tv switch - await _toggle_switch_and_wait(hass, SERVICE_TURN_OFF, ENTITY_WATCH_TV) - assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) + harmony_client.mock_disconnection() + await hass.async_block_till_done() + + # Entities do not immediately show as unavailable + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - # turn on play music switch - await _toggle_switch_and_wait(hass, SERVICE_TURN_ON, ENTITY_PLAY_MUSIC) - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_ON) + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_UNAVAILABLE) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_UNAVAILABLE) + + harmony_client.mock_reconnection() + await hass.async_block_till_done() - # turn on watch tv switch - await _toggle_switch_and_wait(hass, SERVICE_TURN_ON, ENTITY_WATCH_TV) - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + harmony_client.mock_disconnection() + harmony_client.mock_reconnection() + future_time = utcnow() + timedelta(seconds=10) + async_fire_time_changed(hass, future_time) -async def test_remote_toggles(mock_hc, hass, mock_write_config): - """Ensure calls to the remote also updates the switches.""" + await hass.async_block_till_done() + assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) + assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) + + +async def test_switch_toggles(mock_hc, hass, mock_write_config): + """Ensure calls to the switch modify the harmony state.""" entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} ) @@ -69,54 +83,20 @@ async def test_remote_toggles(mock_hc, hass, mock_write_config): assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - # turn off remote - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: ENTITY_REMOTE}, - blocking=True, - ) - await hass.async_block_till_done() - + # turn off watch tv switch + await _toggle_switch_and_wait(hass, SERVICE_TURN_OFF, ENTITY_WATCH_TV) assert hass.states.is_state(ENTITY_REMOTE, STATE_OFF) assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - # turn on remote, restoring the last activity - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: ENTITY_REMOTE}, - blocking=True, - ) - await hass.async_block_till_done() - - assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) - assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) - assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) - - # send new activity command, with activity name - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: "Play Music"}, - blocking=True, - ) - await hass.async_block_till_done() - + # turn on play music switch + await _toggle_switch_and_wait(hass, SERVICE_TURN_ON, ENTITY_PLAY_MUSIC) assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) assert hass.states.is_state(ENTITY_WATCH_TV, STATE_OFF) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_ON) - # send new activity command, with activity id - await hass.services.async_call( - REMOTE_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: ENTITY_REMOTE, ATTR_ACTIVITY: ACTIVITIES_TO_IDS["Watch TV"]}, - blocking=True, - ) - await hass.async_block_till_done() - + # turn on watch tv switch + await _toggle_switch_and_wait(hass, SERVICE_TURN_ON, ENTITY_WATCH_TV) assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) assert hass.states.is_state(ENTITY_WATCH_TV, STATE_ON) assert hass.states.is_state(ENTITY_PLAY_MUSIC, STATE_OFF) From 3ad4c26f982deb7d4fe7bf1855bcc7e2defc546b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Mar 2021 10:21:51 -0800 Subject: [PATCH 1174/1818] Allow SSDP discovery modern Hue hubs (#47725) --- homeassistant/components/hue/config_flow.py | 7 +++++-- tests/components/hue/test_config_flow.py | 17 +++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 580b69251c271b..2c3a90318b7046 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -25,7 +25,7 @@ ) from .errors import AuthenticationRequired, CannotConnect -HUE_MANUFACTURERURL = "http://www.philips.com" +HUE_MANUFACTURERURL = ("http://www.philips.com", "http://www.philips-hue.com") HUE_IGNORED_BRIDGE_NAMES = ["Home Assistant Bridge", "Espalexa"] HUE_MANUAL_BRIDGE_ID = "manual" @@ -179,7 +179,10 @@ async def async_step_ssdp(self, discovery_info): host is already configured and delegate to the import step if not. """ # Filter out non-Hue bridges #1 - if discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) != HUE_MANUFACTURERURL: + if ( + discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) + not in HUE_MANUFACTURERURL + ): return self.async_abort(reason="not_hue_bridge") # Filter out non-Hue bridges #2 diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 57f4bd7fbcae4c..9ba34f23bf4946 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -375,14 +375,15 @@ async def test_flow_link_unknown_host(hass): assert result["reason"] == "cannot_connect" -async def test_bridge_ssdp(hass): +@pytest.mark.parametrize("mf_url", config_flow.HUE_MANUFACTURERURL) +async def test_bridge_ssdp(hass, mf_url): """Test a bridge being discovered.""" result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: mf_url, ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -411,7 +412,7 @@ async def test_bridge_ssdp_emulated_hue(hass): data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Home Assistant Bridge", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -426,7 +427,7 @@ async def test_bridge_ssdp_missing_location(hass): const.DOMAIN, context={"source": "ssdp"}, data={ - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -442,7 +443,7 @@ async def test_bridge_ssdp_missing_serial(hass): context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], }, ) @@ -458,7 +459,7 @@ async def test_bridge_ssdp_espalexa(hass): data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", ssdp.ATTR_UPNP_FRIENDLY_NAME: "Espalexa (0.0.0.0)", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -478,7 +479,7 @@ async def test_bridge_ssdp_already_configured(hass): context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://0.0.0.0/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "1234", }, ) @@ -617,7 +618,7 @@ async def test_ssdp_discovery_update_configuration(hass): context={"source": "ssdp"}, data={ ssdp.ATTR_SSDP_LOCATION: "http://1.1.1.1/", - ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL, + ssdp.ATTR_UPNP_MANUFACTURER_URL: config_flow.HUE_MANUFACTURERURL[0], ssdp.ATTR_UPNP_SERIAL: "aabbccddeeff", }, ) From 10535018cc6575106f207bb0cbc4426abe16cfdc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Mar 2021 20:20:51 +0100 Subject: [PATCH 1175/1818] Improve HomeKit discovered Hue config flow (#47729) --- homeassistant/components/hue/config_flow.py | 11 +++++++++++ tests/components/hue/test_config_flow.py | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 2c3a90318b7046..95e4a1ad7f21a8 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -210,6 +210,17 @@ async def async_step_ssdp(self, discovery_info): self.bridge = bridge return await self.async_step_link() + async def async_step_homekit(self, discovery_info): + """Handle a discovered Hue bridge on HomeKit. + + The bridge ID communicated over HomeKit differs, so we cannot use that + as the unique identifier. Therefore, this method uses discovery without + a unique ID. + """ + self.bridge = self._async_get_bridge(discovery_info[CONF_HOST]) + await self._async_handle_discovery_without_unique_id() + return await self.async_step_link() + async def async_step_import(self, import_info): """Import a new bridge as a config entry. diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 9ba34f23bf4946..12a360cdf94282 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -571,7 +571,14 @@ async def test_bridge_homekit(hass, aioclient_mock): ) assert result["type"] == "form" - assert result["step_id"] == "init" + assert result["step_id"] == "link" + + flow = next( + flow + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert flow["context"]["unique_id"] == config_entries.DEFAULT_DISCOVERY_UNIQUE_ID async def test_bridge_import_already_configured(hass): From ff09643b33461876f754a1b6ee75c8a102e2598d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Wed, 10 Mar 2021 21:31:37 +0100 Subject: [PATCH 1176/1818] Add Tado weather support (#44807) --- homeassistant/components/tado/__init__.py | 11 +- homeassistant/components/tado/const.py | 16 +++ homeassistant/components/tado/entity.py | 22 +++- homeassistant/components/tado/sensor.py | 125 +++++++++++++++++++++- tests/components/tado/test_sensor.py | 15 +++ tests/components/tado/util.py | 5 + tests/fixtures/tado/weather.json | 22 ++++ 7 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/tado/weather.json diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index 36af3bb0dbac34..094465d38aaaba 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -144,11 +144,13 @@ def __init__(self, hass, username, password, fallback): self._fallback = fallback self.home_id = None + self.home_name = None self.tado = None self.zones = None self.devices = None self.data = { "device": {}, + "weather": {}, "zone": {}, } @@ -164,7 +166,9 @@ def setup(self): # Load zones and devices self.zones = self.tado.getZones() self.devices = self.tado.getDevices() - self.home_id = self.tado.getMe()["homes"][0]["id"] + tado_home = self.tado.getMe()["homes"][0] + self.home_id = tado_home["id"] + self.home_name = tado_home["name"] @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): @@ -173,6 +177,11 @@ def update(self): self.update_sensor("device", device["shortSerialNo"]) for zone in self.zones: self.update_sensor("zone", zone["id"]) + self.data["weather"] = self.tado.getWeather() + dispatcher_send( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "weather", "data"), + ) def update_sensor(self, sensor_type, sensor): """Update the internal data from Tado.""" diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 6e009df7ca29cc..2c86fa2d6423f1 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -48,6 +48,21 @@ DATA = "data" UPDATE_TRACK = "update_track" +# Weather +CONDITIONS_MAP = { + "clear-night": {"NIGHT_CLEAR"}, + "cloudy": {"CLOUDY", "CLOUDY_MOSTLY", "NIGHT_CLOUDY"}, + "fog": {"FOGGY"}, + "hail": {"HAIL", "RAIN_HAIL"}, + "lightning": {"THUNDERSTORM"}, + "partlycloudy": {"CLOUDY_PARTLY"}, + "rainy": {"DRIZZLE", "RAIN", "SCATTERED_RAIN"}, + "snowy": {"FREEZING", "SCATTERED_SNOW", "SNOW"}, + "snowy-rainy": {"RAIN_SNOW", "SCATTERED_RAIN_SNOW"}, + "sunny": {"SUN"}, + "windy": {"WIND"}, +} + # Types TYPE_AIR_CONDITIONING = "AIR_CONDITIONING" TYPE_HEATING = "HEATING" @@ -149,6 +164,7 @@ DEFAULT_NAME = "Tado" +TADO_HOME = "Home" TADO_ZONE = "Zone" UPDATE_LISTENER = "update_listener" diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index 34473a45c98151..270d6f1e9115bf 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -1,7 +1,7 @@ """Base class for Tado entity.""" from homeassistant.helpers.entity import Entity -from .const import DEFAULT_NAME, DOMAIN, TADO_ZONE +from .const import DEFAULT_NAME, DOMAIN, TADO_HOME, TADO_ZONE class TadoDeviceEntity(Entity): @@ -32,6 +32,26 @@ def should_poll(self): return False +class TadoHomeEntity(Entity): + """Base implementation for Tado home.""" + + def __init__(self, tado): + """Initialize a Tado home.""" + super().__init__() + self.home_name = tado.home_name + self.home_id = tado.home_id + + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, self.home_id)}, + "name": self.home_name, + "manufacturer": DEFAULT_NAME, + "model": TADO_HOME, + } + + class TadoZoneEntity(Entity): """Base implementation for Tado zone.""" diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 6613de82bff332..8d38d9eab96239 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -13,6 +13,7 @@ from homeassistant.helpers.entity import Entity from .const import ( + CONDITIONS_MAP, DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED, @@ -20,10 +21,16 @@ TYPE_HEATING, TYPE_HOT_WATER, ) -from .entity import TadoZoneEntity +from .entity import TadoHomeEntity, TadoZoneEntity _LOGGER = logging.getLogger(__name__) +HOME_SENSORS = { + "outdoor temperature", + "solar percentage", + "weather condition", +} + ZONE_SENSORS = { TYPE_HEATING: [ "temperature", @@ -41,6 +48,14 @@ } +def format_condition(condition: str) -> str: + """Return condition from dict CONDITIONS_MAP.""" + for key, value in CONDITIONS_MAP.items(): + if condition in value: + return key + return condition + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities ): @@ -50,6 +65,9 @@ async def async_setup_entry( zones = tado.zones entities = [] + # Create home sensors + entities.extend([TadoHomeSensor(tado, variable) for variable in HOME_SENSORS]) + # Create zone sensors for zone in zones: zone_type = zone["type"] @@ -68,6 +86,111 @@ async def async_setup_entry( async_add_entities(entities, True) +class TadoHomeSensor(TadoHomeEntity, Entity): + """Representation of a Tado Sensor.""" + + def __init__(self, tado, home_variable): + """Initialize of the Tado Sensor.""" + super().__init__(tado) + self._tado = tado + + self.home_variable = home_variable + + self._unique_id = f"{home_variable} {tado.home_id}" + + self._state = None + self._state_attributes = None + self._tado_weather_data = self._tado.data["weather"] + + async def async_added_to_hass(self): + """Register for sensor updates.""" + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_TADO_UPDATE_RECEIVED.format( + self._tado.home_id, "weather", "data" + ), + self._async_update_callback, + ) + ) + self._async_update_home_data() + + @property + def unique_id(self): + """Return the unique id.""" + return self._unique_id + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._tado.home_name} {self.home_variable}" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._state_attributes + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + if self.home_variable == "temperature": + return TEMP_CELSIUS + if self.home_variable == "solar percentage": + return PERCENTAGE + if self.home_variable == "weather condition": + return None + + @property + def device_class(self): + """Return the device class.""" + if self.home_variable == "outdoor temperature": + return DEVICE_CLASS_TEMPERATURE + return None + + @callback + def _async_update_callback(self): + """Update and write state.""" + self._async_update_home_data() + self.async_write_ha_state() + + @callback + def _async_update_home_data(self): + """Handle update callbacks.""" + try: + self._tado_weather_data = self._tado.data["weather"] + except KeyError: + return + + if self.home_variable == "outdoor temperature": + self._state = self.hass.config.units.temperature( + self._tado_weather_data["outsideTemperature"]["celsius"], + TEMP_CELSIUS, + ) + self._state_attributes = { + "time": self._tado_weather_data["outsideTemperature"]["timestamp"], + } + + elif self.home_variable == "solar percentage": + self._state = self._tado_weather_data["solarIntensity"]["percentage"] + self._state_attributes = { + "time": self._tado_weather_data["solarIntensity"]["timestamp"], + } + + elif self.home_variable == "weather condition": + self._state = format_condition( + self._tado_weather_data["weatherState"]["value"] + ) + self._state_attributes = { + "time": self._tado_weather_data["weatherState"]["timestamp"] + } + + class TadoZoneSensor(TadoZoneEntity, Entity): """Representation of a tado Sensor.""" diff --git a/tests/components/tado/test_sensor.py b/tests/components/tado/test_sensor.py index 2fac88bc22e90e..bb926ff1ae2c78 100644 --- a/tests/components/tado/test_sensor.py +++ b/tests/components/tado/test_sensor.py @@ -21,6 +21,21 @@ async def test_air_con_create_sensors(hass): assert state.state == "60.9" +async def test_home_create_sensors(hass): + """Test creation of home sensors.""" + + await async_init_integration(hass) + + state = hass.states.get("sensor.home_name_outdoor_temperature") + assert state.state == "7.46" + + state = hass.states.get("sensor.home_name_solar_percentage") + assert state.state == "2.1" + + state = hass.states.get("sensor.home_name_weather_condition") + assert state.state == "fog" + + async def test_heater_create_sensors(hass): """Test creation of heater sensors.""" diff --git a/tests/components/tado/util.py b/tests/components/tado/util.py index c5bf8cf28a41bc..ce1dd92942de7e 100644 --- a/tests/components/tado/util.py +++ b/tests/components/tado/util.py @@ -18,6 +18,7 @@ async def async_init_integration( token_fixture = "tado/token.json" devices_fixture = "tado/devices.json" me_fixture = "tado/me.json" + weather_fixture = "tado/weather.json" zones_fixture = "tado/zones.json" # WR1 Device @@ -55,6 +56,10 @@ async def async_init_integration( "https://my.tado.com/api/v2/me", text=load_fixture(me_fixture), ) + m.get( + "https://my.tado.com/api/v2/homes/1/weather", + text=load_fixture(weather_fixture), + ) m.get( "https://my.tado.com/api/v2/homes/1/devices", text=load_fixture(devices_fixture), diff --git a/tests/fixtures/tado/weather.json b/tests/fixtures/tado/weather.json new file mode 100644 index 00000000000000..72379f05512bf8 --- /dev/null +++ b/tests/fixtures/tado/weather.json @@ -0,0 +1,22 @@ +{ + "outsideTemperature": { + "celsius": 7.46, + "fahrenheit": 45.43, + "precision": { + "celsius": 0.01, + "fahrenheit": 0.01 + }, + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "TEMPERATURE" + }, + "solarIntensity": { + "percentage": 2.1, + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "PERCENTAGE" + }, + "weatherState": { + "timestamp": "2020-12-22T08:13:13.652Z", + "type": "WEATHER_STATE", + "value": "FOGGY" + } +} From 54a9b69ecb0313c783c4a0877bc6055b7ae4ad7d Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Wed, 10 Mar 2021 21:32:22 +0100 Subject: [PATCH 1177/1818] Update xknx to 0.17.2 (#47732) --- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index e74bae7442cd1f..99198fbaa99352 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -77,7 +77,7 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( - # deprecated since 2021.3 + # deprecated since 2021.4 cv.deprecated(CONF_KNX_CONFIG), # deprecated since 2021.2 cv.deprecated(CONF_KNX_FIRE_EVENT), diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 7ca7657d0ff535..f8ed51f364a38b 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.1"], + "requirements": ["xknx==0.17.2"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index 7bc5592a62654c..5170f3b25749e7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2330,7 +2330,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.1 +xknx==0.17.2 # homeassistant.components.bluesound # homeassistant.components.rest From 15da1c478517f0d9eeae31b12544c836a1a2e275 Mon Sep 17 00:00:00 2001 From: hung2kgithub <73251414+hung2kgithub@users.noreply.github.com> Date: Thu, 11 Mar 2021 04:48:06 +0800 Subject: [PATCH 1178/1818] Add missing clear-night weather condition (#47666) --- homeassistant/components/template/weather.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 560bd5639ba199..0db94520afe788 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -2,6 +2,7 @@ import voluptuous as vol from homeassistant.components.weather import ( + ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, ATTR_CONDITION_EXCEPTIONAL, ATTR_CONDITION_FOG, @@ -29,6 +30,7 @@ from .template_entity import TemplateEntity CONDITION_CLASSES = { + ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, ATTR_CONDITION_FOG, ATTR_CONDITION_HAIL, From a9a9e1f199e936f4528a2c96e7cb09afe27516d1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 10 Mar 2021 23:42:13 +0100 Subject: [PATCH 1179/1818] Tweak automation tracing (#47721) --- .../components/automation/__init__.py | 224 +---- homeassistant/components/automation/trace.py | 206 +++++ .../components/automation/websocket_api.py | 229 +++++ homeassistant/components/config/automation.py | 224 ----- homeassistant/helpers/script.py | 2 +- .../automation/test_websocket_api.py | 803 ++++++++++++++++++ tests/components/config/test_automation.py | 772 +---------------- 7 files changed, 1248 insertions(+), 1212 deletions(-) create mode 100644 homeassistant/components/automation/trace.py create mode 100644 homeassistant/components/automation/websocket_api.py create mode 100644 tests/components/automation/test_websocket_api.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7e1352afc2f598..50b9fc43bf53a1 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,21 +1,6 @@ """Allow to set up simple automation rules via the config file.""" -from collections import OrderedDict -from contextlib import contextmanager -import datetime as dt -from itertools import count import logging -from typing import ( - Any, - Awaitable, - Callable, - Deque, - Dict, - List, - Optional, - Set, - Union, - cast, -) +from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast import voluptuous as vol from voluptuous.humanize import humanize_error @@ -68,18 +53,13 @@ ) from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.service import async_register_admin_service -from homeassistant.helpers.trace import ( - TraceElement, - trace_get, - trace_id_set, - trace_path, -) +from homeassistant.helpers.trace import trace_get, trace_path from homeassistant.helpers.trigger import async_initialize_triggers from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass -from homeassistant.util import dt as dt_util from homeassistant.util.dt import parse_datetime +from . import websocket_api from .config import AutomationConfig, async_validate_config_item # Not used except by packages to check config structure @@ -94,6 +74,7 @@ LOGGER, ) from .helpers import async_get_blueprints +from .trace import DATA_AUTOMATION_TRACE, trace_automation # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -113,9 +94,6 @@ ATTR_VARIABLES = "variables" SERVICE_TRIGGER = "trigger" -DATA_AUTOMATION_TRACE = "automation_trace" -STORED_TRACES = 5 # Stored traces per automation - _LOGGER = logging.getLogger(__name__) AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] @@ -194,9 +172,12 @@ def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: async def async_setup(hass, config): """Set up all automations.""" + # Local import to avoid circular import hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) hass.data.setdefault(DATA_AUTOMATION_TRACE, {}) + websocket_api.async_setup(hass) + # To register the automation blueprints async_get_blueprints(hass) @@ -243,167 +224,6 @@ async def reload_service_handler(service_call): return True -class AutomationTrace: - """Container for automation trace.""" - - _run_ids = count(0) - - def __init__( - self, - unique_id: Optional[str], - config: Dict[str, Any], - trigger: Dict[str, Any], - context: Context, - ): - """Container for automation trace.""" - self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._config: Dict[str, Any] = config - self._context: Context = context - self._error: Optional[Exception] = None - self._state: str = "running" - self.run_id: str = str(next(self._run_ids)) - self._timestamp_finish: Optional[dt.datetime] = None - self._timestamp_start: dt.datetime = dt_util.utcnow() - self._trigger: Dict[str, Any] = trigger - self._unique_id: Optional[str] = unique_id - self._variables: Optional[Dict[str, Any]] = None - - def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: - """Set action trace.""" - self._action_trace = trace - - def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: - """Set condition trace.""" - self._condition_trace = trace - - def set_error(self, ex: Exception) -> None: - """Set error.""" - self._error = ex - - def set_variables(self, variables: Dict[str, Any]) -> None: - """Set variables.""" - self._variables = variables - - def finished(self) -> None: - """Set finish time.""" - self._timestamp_finish = dt_util.utcnow() - self._state = "stopped" - - def as_dict(self) -> Dict[str, Any]: - """Return dictionary version of this AutomationTrace.""" - - action_traces = {} - condition_traces = {} - if self._action_trace: - for key, trace_list in self._action_trace.items(): - action_traces[key] = [item.as_dict() for item in trace_list] - - if self._condition_trace: - for key, trace_list in self._condition_trace.items(): - condition_traces[key] = [item.as_dict() for item in trace_list] - - result = { - "action_trace": action_traces, - "condition_trace": condition_traces, - "config": self._config, - "context": self._context, - "run_id": self.run_id, - "state": self._state, - "timestamp": { - "start": self._timestamp_start, - "finish": self._timestamp_finish, - }, - "trigger": self._trigger, - "unique_id": self._unique_id, - "variables": self._variables, - } - if self._error is not None: - result["error"] = str(self._error) - return result - - def as_short_dict(self) -> Dict[str, Any]: - """Return a brief dictionary version of this AutomationTrace.""" - - last_action = None - last_condition = None - - if self._action_trace: - last_action = list(self._action_trace.keys())[-1] - if self._condition_trace: - last_condition = list(self._condition_trace.keys())[-1] - - result = { - "last_action": last_action, - "last_condition": last_condition, - "run_id": self.run_id, - "state": self._state, - "timestamp": { - "start": self._timestamp_start, - "finish": self._timestamp_finish, - }, - "trigger": self._trigger.get("description"), - "unique_id": self._unique_id, - } - if self._error is not None: - result["error"] = str(self._error) - if last_action is not None: - result["last_action"] = last_action - result["last_condition"] = last_condition - - return result - - -class LimitedSizeDict(OrderedDict): - """OrderedDict limited in size.""" - - def __init__(self, *args, **kwds): - """Initialize OrderedDict limited in size.""" - self.size_limit = kwds.pop("size_limit", None) - OrderedDict.__init__(self, *args, **kwds) - self._check_size_limit() - - def __setitem__(self, key, value): - """Set item and check dict size.""" - OrderedDict.__setitem__(self, key, value) - self._check_size_limit() - - def _check_size_limit(self): - """Check dict size and evict items in FIFO order if needed.""" - if self.size_limit is not None: - while len(self) > self.size_limit: - self.popitem(last=False) - - -@contextmanager -def trace_automation(hass, unique_id, config, trigger, context): - """Trace action execution of automation with automation_id.""" - automation_trace = AutomationTrace(unique_id, config, trigger, context) - trace_id_set((unique_id, automation_trace.run_id)) - - if unique_id: - automation_traces = hass.data[DATA_AUTOMATION_TRACE] - if unique_id not in automation_traces: - automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) - automation_traces[unique_id][automation_trace.run_id] = automation_trace - - try: - yield automation_trace - except Exception as ex: # pylint: disable=broad-except - if unique_id: - automation_trace.set_error(ex) - raise ex - finally: - if unique_id: - automation_trace.finished() - _LOGGER.debug( - "Automation finished. Summary:\n\ttrigger: %s\n\tcondition: %s\n\taction: %s", - automation_trace._trigger, # pylint: disable=protected-access - automation_trace._condition_trace, # pylint: disable=protected-access - automation_trace._action_trace, # pylint: disable=protected-access - ) - - class AutomationEntity(ToggleEntity, RestoreEntity): """Entity to show status of entity.""" @@ -570,9 +390,8 @@ async def async_trigger(self, run_variables, context=None, skip_condition=False) reason = f' by {run_variables["trigger"]["description"]}' self._logger.debug("Automation triggered%s", reason) - trigger = run_variables["trigger"] if "trigger" in run_variables else None with trace_automation( - self.hass, self.unique_id, self._raw_config, trigger, context + self.hass, self.unique_id, self._raw_config, context ) as automation_trace: if self._variables: try: @@ -891,30 +710,3 @@ def _trigger_extract_entities(trigger_conf: dict) -> List[str]: return ["sun.sun"] return [] - - -@callback -def get_debug_traces_for_automation(hass, automation_id, summary=False): - """Return a serializable list of debug traces for an automation.""" - traces = [] - - for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): - if summary: - traces.append(trace.as_short_dict()) - else: - traces.append(trace.as_dict()) - - return traces - - -@callback -def get_debug_traces(hass, summary=False): - """Return a serializable list of debug traces.""" - traces = {} - - for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces[automation_id] = get_debug_traces_for_automation( - hass, automation_id, summary - ) - - return traces diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py new file mode 100644 index 00000000000000..ebd981f254160a --- /dev/null +++ b/homeassistant/components/automation/trace.py @@ -0,0 +1,206 @@ +"""Trace support for automation.""" +from collections import OrderedDict +from contextlib import contextmanager +import datetime as dt +from itertools import count +import logging +from typing import Any, Awaitable, Callable, Deque, Dict, Optional + +from homeassistant.core import Context, HomeAssistant, callback +from homeassistant.helpers.trace import TraceElement, trace_id_set +from homeassistant.helpers.typing import TemplateVarsType +from homeassistant.util import dt as dt_util + +DATA_AUTOMATION_TRACE = "automation_trace" +STORED_TRACES = 5 # Stored traces per automation + +_LOGGER = logging.getLogger(__name__) +AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] + +# mypy: allow-untyped-calls, allow-untyped-defs +# mypy: no-check-untyped-defs, no-warn-return-any + + +class AutomationTrace: + """Container for automation trace.""" + + _run_ids = count(0) + + def __init__( + self, + unique_id: Optional[str], + config: Dict[str, Any], + context: Context, + ): + """Container for automation trace.""" + self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None + self._config: Dict[str, Any] = config + self._context: Context = context + self._error: Optional[Exception] = None + self._state: str = "running" + self.run_id: str = str(next(self._run_ids)) + self._timestamp_finish: Optional[dt.datetime] = None + self._timestamp_start: dt.datetime = dt_util.utcnow() + self._unique_id: Optional[str] = unique_id + self._variables: Optional[Dict[str, Any]] = None + + def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set action trace.""" + self._action_trace = trace + + def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + """Set condition trace.""" + self._condition_trace = trace + + def set_error(self, ex: Exception) -> None: + """Set error.""" + self._error = ex + + def set_variables(self, variables: Dict[str, Any]) -> None: + """Set variables.""" + self._variables = variables + + def finished(self) -> None: + """Set finish time.""" + self._timestamp_finish = dt_util.utcnow() + self._state = "stopped" + + def as_dict(self) -> Dict[str, Any]: + """Return dictionary version of this AutomationTrace.""" + + result = self.as_short_dict() + + action_traces = {} + condition_traces = {} + if self._action_trace: + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] + + if self._condition_trace: + for key, trace_list in self._condition_trace.items(): + condition_traces[key] = [item.as_dict() for item in trace_list] + + result.update( + { + "action_trace": action_traces, + "condition_trace": condition_traces, + "config": self._config, + "context": self._context, + "variables": self._variables, + } + ) + if self._error is not None: + result["error"] = str(self._error) + return result + + def as_short_dict(self) -> Dict[str, Any]: + """Return a brief dictionary version of this AutomationTrace.""" + + last_action = None + last_condition = None + trigger = None + + if self._action_trace: + last_action = list(self._action_trace)[-1] + if self._condition_trace: + last_condition = list(self._condition_trace)[-1] + if self._variables: + trigger = self._variables.get("trigger", {}).get("description") + + result = { + "last_action": last_action, + "last_condition": last_condition, + "run_id": self.run_id, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "trigger": trigger, + "unique_id": self._unique_id, + } + if self._error is not None: + result["error"] = str(self._error) + if last_action is not None: + result["last_action"] = last_action + result["last_condition"] = last_condition + + return result + + +class LimitedSizeDict(OrderedDict): + """OrderedDict limited in size.""" + + def __init__(self, *args, **kwds): + """Initialize OrderedDict limited in size.""" + self.size_limit = kwds.pop("size_limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_size_limit() + + def __setitem__(self, key, value): + """Set item and check dict size.""" + OrderedDict.__setitem__(self, key, value) + self._check_size_limit() + + def _check_size_limit(self): + """Check dict size and evict items in FIFO order if needed.""" + if self.size_limit is not None: + while len(self) > self.size_limit: + self.popitem(last=False) + + +@contextmanager +def trace_automation(hass, unique_id, config, context): + """Trace action execution of automation with automation_id.""" + automation_trace = AutomationTrace(unique_id, config, context) + trace_id_set((unique_id, automation_trace.run_id)) + + if unique_id: + automation_traces = hass.data[DATA_AUTOMATION_TRACE] + if unique_id not in automation_traces: + automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) + automation_traces[unique_id][automation_trace.run_id] = automation_trace + + try: + yield automation_trace + except Exception as ex: # pylint: disable=broad-except + if unique_id: + automation_trace.set_error(ex) + raise ex + finally: + if unique_id: + automation_trace.finished() + + +@callback +def get_debug_trace(hass, automation_id, run_id): + """Return a serializable debug trace.""" + return hass.data[DATA_AUTOMATION_TRACE][automation_id][run_id] + + +@callback +def get_debug_traces_for_automation(hass, automation_id, summary=False): + """Return a serializable list of debug traces for an automation.""" + traces = [] + + for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): + if summary: + traces.append(trace.as_short_dict()) + else: + traces.append(trace.as_dict()) + + return traces + + +@callback +def get_debug_traces(hass, summary=False): + """Return a serializable list of debug traces.""" + traces = {} + + for automation_id in hass.data[DATA_AUTOMATION_TRACE]: + traces[automation_id] = get_debug_traces_for_automation( + hass, automation_id, summary + ) + + return traces diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py new file mode 100644 index 00000000000000..aaebbef7f83674 --- /dev/null +++ b/homeassistant/components/automation/websocket_api.py @@ -0,0 +1,229 @@ +"""Websocket API for automation.""" +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.dispatcher import ( + DATA_DISPATCHER, + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.script import ( + SCRIPT_BREAKPOINT_HIT, + SCRIPT_DEBUG_CONTINUE_ALL, + breakpoint_clear, + breakpoint_clear_all, + breakpoint_list, + breakpoint_set, + debug_continue, + debug_step, + debug_stop, +) + +from .trace import get_debug_trace, get_debug_traces + +# mypy: allow-untyped-calls, allow-untyped-defs + + +@callback +def async_setup(hass: HomeAssistant) -> None: + """Set up the websocket API.""" + websocket_api.async_register_command(hass, websocket_automation_trace_get) + websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) + websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) + websocket_api.async_register_command(hass, websocket_automation_debug_continue) + websocket_api.async_register_command(hass, websocket_automation_debug_step) + websocket_api.async_register_command(hass, websocket_automation_debug_stop) + websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/trace/get", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_trace_get(hass, connection, msg): + """Get automation traces.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + trace = get_debug_trace(hass, automation_id, run_id) + + connection.send_result(msg["id"], trace) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) +def websocket_automation_trace_list(hass, connection, msg): + """Summarize automation traces.""" + automation_traces = get_debug_traces(hass, summary=True) + + connection.send_result(msg["id"], automation_traces) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/set", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_set(hass, connection, msg): + """Set breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + raise HomeAssistantError("No breakpoint subscription") + + result = breakpoint_set(hass, automation_id, run_id, node) + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/breakpoint/clear", + vol.Required("automation_id"): str, + vol.Required("node"): str, + vol.Optional("run_id"): str, + } +) +def websocket_automation_breakpoint_clear(hass, connection, msg): + """Clear breakpoint.""" + automation_id = msg["automation_id"] + node = msg["node"] + run_id = msg.get("run_id") + + result = breakpoint_clear(hass, automation_id, run_id, node) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/list"} +) +def websocket_automation_breakpoint_list(hass, connection, msg): + """List breakpoints.""" + breakpoints = breakpoint_list(hass) + for _breakpoint in breakpoints: + _breakpoint["automation_id"] = _breakpoint.pop("unique_id") + + connection.send_result(msg["id"], breakpoints) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + {vol.Required("type"): "automation/debug/breakpoint/subscribe"} +) +def websocket_subscribe_breakpoint_events(hass, connection, msg): + """Subscribe to breakpoint events.""" + + @callback + def breakpoint_hit(automation_id, run_id, node): + """Forward events to websocket.""" + connection.send_message( + websocket_api.event_message( + msg["id"], + { + "automation_id": automation_id, + "run_id": run_id, + "node": node, + }, + ) + ) + + remove_signal = async_dispatcher_connect( + hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit + ) + + @callback + def unsub(): + """Unsubscribe from breakpoint events.""" + remove_signal() + if ( + SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) + or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] + ): + breakpoint_clear_all(hass) + async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL) + + connection.subscriptions[msg["id"]] = unsub + + connection.send_message(websocket_api.result_message(msg["id"])) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/continue", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_continue(hass, connection, msg): + """Resume execution of halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_continue(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/step", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_step(hass, connection, msg): + """Single step a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_step(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) + + +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/debug/stop", + vol.Required("automation_id"): str, + vol.Required("run_id"): str, + } +) +def websocket_automation_debug_stop(hass, connection, msg): + """Stop a halted automation.""" + automation_id = msg["automation_id"] + run_id = msg["run_id"] + + result = debug_stop(hass, automation_id, run_id) + + connection.send_result(msg["id"], result) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index b5aa1bf7af5f01..01e22297c0d42a 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -2,13 +2,6 @@ from collections import OrderedDict import uuid -import voluptuous as vol - -from homeassistant.components import websocket_api -from homeassistant.components.automation import ( - get_debug_traces, - get_debug_traces_for_automation, -) from homeassistant.components.automation.config import ( DOMAIN, PLATFORM_SCHEMA, @@ -16,25 +9,7 @@ ) from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD -from homeassistant.core import callback -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_registry -from homeassistant.helpers.dispatcher import ( - DATA_DISPATCHER, - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.script import ( - SCRIPT_BREAKPOINT_HIT, - SCRIPT_DEBUG_CONTINUE_ALL, - breakpoint_clear, - breakpoint_clear_all, - breakpoint_list, - breakpoint_set, - debug_continue, - debug_step, - debug_stop, -) from . import ACTION_DELETE, EditIdBasedConfigView @@ -42,16 +17,6 @@ async def async_setup(hass): """Set up the Automation config API.""" - websocket_api.async_register_command(hass, websocket_automation_trace_get) - websocket_api.async_register_command(hass, websocket_automation_trace_list) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) - websocket_api.async_register_command(hass, websocket_automation_debug_continue) - websocket_api.async_register_command(hass, websocket_automation_debug_step) - websocket_api.async_register_command(hass, websocket_automation_debug_stop) - websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) - async def hook(action, config_key): """post_write_hook for Config View that reloads automations.""" await hass.services.async_call(DOMAIN, SERVICE_RELOAD) @@ -115,192 +80,3 @@ def _write_value(self, hass, data, config_key, new_value): updated_value.update(cur_value) updated_value.update(new_value) data[index] = updated_value - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/trace/get", vol.Optional("automation_id"): str} -) -def websocket_automation_trace_get(hass, connection, msg): - """Get automation traces.""" - automation_id = msg.get("automation_id") - - if not automation_id: - automation_traces = get_debug_traces(hass) - else: - automation_traces = { - automation_id: get_debug_traces_for_automation(hass, automation_id) - } - - connection.send_result(msg["id"], automation_traces) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) -def websocket_automation_trace_list(hass, connection, msg): - """Summarize automation traces.""" - automation_traces = get_debug_traces(hass, summary=True) - - connection.send_result(msg["id"], automation_traces) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/breakpoint/set", - vol.Required("automation_id"): str, - vol.Required("node"): str, - vol.Optional("run_id"): str, - } -) -def websocket_automation_breakpoint_set(hass, connection, msg): - """Set breakpoint.""" - automation_id = msg["automation_id"] - node = msg["node"] - run_id = msg.get("run_id") - - if ( - SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) - or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] - ): - raise HomeAssistantError("No breakpoint subscription") - - result = breakpoint_set(hass, automation_id, run_id, node) - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/breakpoint/clear", - vol.Required("automation_id"): str, - vol.Required("node"): str, - vol.Optional("run_id"): str, - } -) -def websocket_automation_breakpoint_clear(hass, connection, msg): - """Clear breakpoint.""" - automation_id = msg["automation_id"] - node = msg["node"] - run_id = msg.get("run_id") - - result = breakpoint_clear(hass, automation_id, run_id, node) - - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/list"} -) -def websocket_automation_breakpoint_list(hass, connection, msg): - """List breakpoints.""" - breakpoints = breakpoint_list(hass) - for _breakpoint in breakpoints: - _breakpoint["automation_id"] = _breakpoint.pop("unique_id") - - connection.send_result(msg["id"], breakpoints) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/subscribe"} -) -def websocket_subscribe_breakpoint_events(hass, connection, msg): - """Subscribe to breakpoint events.""" - - @callback - def breakpoint_hit(automation_id, run_id, node): - """Forward events to websocket.""" - connection.send_message( - websocket_api.event_message( - msg["id"], - { - "automation_id": automation_id, - "run_id": run_id, - "node": node, - }, - ) - ) - - remove_signal = async_dispatcher_connect( - hass, SCRIPT_BREAKPOINT_HIT, breakpoint_hit - ) - - @callback - def unsub(): - """Unsubscribe from breakpoint events.""" - remove_signal() - if ( - SCRIPT_BREAKPOINT_HIT not in hass.data.get(DATA_DISPATCHER, {}) - or not hass.data[DATA_DISPATCHER][SCRIPT_BREAKPOINT_HIT] - ): - breakpoint_clear_all(hass) - async_dispatcher_send(hass, SCRIPT_DEBUG_CONTINUE_ALL) - - connection.subscriptions[msg["id"]] = unsub - - connection.send_message(websocket_api.result_message(msg["id"])) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/continue", - vol.Required("automation_id"): str, - vol.Required("run_id"): str, - } -) -def websocket_automation_debug_continue(hass, connection, msg): - """Resume execution of halted automation.""" - automation_id = msg["automation_id"] - run_id = msg["run_id"] - - result = debug_continue(hass, automation_id, run_id) - - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/step", - vol.Required("automation_id"): str, - vol.Required("run_id"): str, - } -) -def websocket_automation_debug_step(hass, connection, msg): - """Single step a halted automation.""" - automation_id = msg["automation_id"] - run_id = msg["run_id"] - - result = debug_step(hass, automation_id, run_id) - - connection.send_result(msg["id"], result) - - -@callback -@websocket_api.require_admin -@websocket_api.websocket_command( - { - vol.Required("type"): "automation/debug/stop", - vol.Required("automation_id"): str, - vol.Required("run_id"): str, - } -) -def websocket_automation_debug_stop(hass, connection, msg): - """Stop a halted automation.""" - automation_id = msg["automation_id"] - run_id = msg["run_id"] - - result = debug_stop(hass, automation_id, run_id) - - connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 257fd6d971577f..a2df055331dd91 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -360,7 +360,7 @@ async def _async_step(self, log_exceptions): handler = f"_async_{cv.determine_script_action(self._action)}_step" await getattr(self, handler)() except Exception as ex: - if not isinstance(ex, (_StopScript, asyncio.CancelledError)) and ( + if not isinstance(ex, _StopScript) and ( self._log_exceptions or log_exceptions ): self._log_exception(ex) diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py new file mode 100644 index 00000000000000..106f687f4eed3e --- /dev/null +++ b/tests/components/automation/test_websocket_api.py @@ -0,0 +1,803 @@ +"""Test Automation config panel.""" +from unittest.mock import patch + +from homeassistant.bootstrap import async_setup_component +from homeassistant.components import automation, config + +from tests.common import assert_lists_same +from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 + + +async def test_get_automation_trace(hass, hass_ws_client): + """Test tracing an automation.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["sun"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert trace["action_trace"]["action/0"][0]["error"] + assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["condition_trace"] == {} + assert trace["config"] == sun_config + assert trace["context"] + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["moon"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "moon", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["moon"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "moon", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 0 + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + run_id = response["result"]["moon"][-1]["run_id"] + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "automation/trace/get", + "automation_id": "moon", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"]["action/0"]) == 1 + assert "error" not in trace["action_trace"]["action/0"][0] + assert "result" not in trace["action_trace"]["action/0"][0] + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["config"] == moon_config + assert trace["context"] + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + assert trace["variables"] + + +async def test_automation_trace_overflow(hass, hass_ws_client): + """Test the number of stored traces per automation is limited.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"event": "some_event"}, + } + moon_config = { + "id": "moon", + "trigger": {"platform": "event", "event_type": "test_event2"}, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + # Trigger "sun" and "moon" automation once + hass.bus.async_fire("test_event") + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 1 + moon_run_id = response["result"]["moon"][0]["run_id"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation enough times to overflow the number of stored traces + for _ in range(automation.trace.STORED_TRACES): + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == automation.trace.STORED_TRACES + assert len(response["result"]["sun"]) == 1 + assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 + assert ( + int(response["result"]["moon"][-1]["run_id"]) + == int(moon_run_id) + automation.trace.STORED_TRACES + ) + + +async def test_list_automation_traces(hass, hass_ws_client): + """Test listing automation traces.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "test.automation"}, + } + moon_config = { + "id": "moon", + "trigger": [ + {"platform": "event", "event_type": "test_event2"}, + {"platform": "event", "event_type": "test_event3"}, + ], + "condition": { + "condition": "template", + "value_template": "{{ trigger.event.event_type=='test_event2' }}", + }, + "action": {"event": "another_event"}, + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + moon_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {} + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert "moon" not in response["result"] + assert len(response["result"]["sun"]) == 1 + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Trigger "moon" automation, with failing condition + hass.bus.async_fire("test_event3") + await hass.async_block_till_done() + + # Trigger "moon" automation, with passing condition + hass.bus.async_fire("test_event2") + await hass.async_block_till_done() + + # Get trace + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]["moon"]) == 3 + assert len(response["result"]["sun"]) == 1 + trace = response["result"]["sun"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] is None + assert trace["error"] == "Unable to find service test.automation" + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event'" + assert trace["unique_id"] == "sun" + + trace = response["result"]["moon"][0] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][1] + assert trace["last_action"] is None + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event3'" + assert trace["unique_id"] == "moon" + + trace = response["result"]["moon"][2] + assert trace["last_action"] == "action/0" + assert trace["last_condition"] == "condition/0" + assert "error" not in trace + assert trace["state"] == "stopped" + assert trace["timestamp"] + assert trace["trigger"] == "event 'test_event2'" + assert trace["unique_id"] == "moon" + + +async def test_automation_breakpoints(hass, hass_ws_client): + """Test automation breakpoints.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + {"id": next_id(), "type": "automation/debug/breakpoint/list"} + ) + response = await client.receive_json() + assert response["success"] + assert_lists_same( + response["result"], + [ + {"node": "action/1", "run_id": "*", "automation_id": "sun"}, + {"node": "action/5", "run_id": "*", "automation_id": "sun"}, + ], + ) + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/step", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/2", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/2", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + +async def test_automation_breakpoints_2(hass, hass_ws_client): + """Test execution resumes and breakpoints are removed after subscription removed.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + # Unsubscribe - execution should resume + await client.send_json( + {"id": next_id(), "type": "unsubscribe_events", "subscription": subscription_id} + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/8", "stopped") + + # Should not be possible to set breakpoints + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "1", + } + ) + response = await client.receive_json() + assert not response["success"] + + # Trigger "sun" automation, should finish without stopping on breakpoints + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + + new_run_id = await assert_last_action("sun", "action/8", "stopped") + assert new_run_id != run_id + + +async def test_automation_breakpoints_3(hass, hass_ws_client): + """Test breakpoints can be cleared.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + async def assert_last_action(automation_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + response = await client.receive_json() + assert response["success"] + trace = response["result"][automation_id][-1] + assert trace["last_action"] == expected_action + assert trace["state"] == expected_state + return trace["run_id"] + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": [ + {"event": "event0"}, + {"event": "event1"}, + {"event": "event2"}, + {"event": "event3"}, + {"event": "event4"}, + {"event": "event5"}, + {"event": "event6"}, + {"event": "event7"}, + {"event": "event8"}, + ], + } + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + sun_config, + ] + }, + ) + + with patch.object(config, "SECTIONS", ["automation"]): + await async_setup_component(hass, "config", {}) + + client = await hass_ws_client() + + subscription_id = next_id() + await client.send_json( + {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/set", + "automation_id": "sun", + "node": "action/5", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/1", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/1", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/continue", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } + + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/stop", + "automation_id": "sun", + "run_id": run_id, + } + ) + response = await client.receive_json() + assert response["success"] + await hass.async_block_till_done() + await assert_last_action("sun", "action/5", "stopped") + + # Clear 1st breakpoint + await client.send_json( + { + "id": next_id(), + "type": "automation/debug/breakpoint/clear", + "automation_id": "sun", + "node": "action/1", + } + ) + response = await client.receive_json() + assert response["success"] + + # Trigger "sun" automation + hass.bus.async_fire("test_event") + + response = await client.receive_json() + run_id = await assert_last_action("sun", "action/5", "running") + assert response["event"] == { + "automation_id": "sun", + "node": "action/5", + "run_id": run_id, + } diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 2880287be941e9..6aeb71a7fd0551 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -3,10 +3,9 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component -from homeassistant.components import automation, config +from homeassistant.components import config from homeassistant.helpers import entity_registry as er -from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -166,772 +165,3 @@ def mock_write(path, data): assert written[0][0]["id"] == "moon" assert len(ent_reg.entities) == 1 - - -async def test_get_automation_trace(hass, hass_ws_client): - """Test tracing an automation.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": {"service": "test.automation"}, - } - moon_config = { - "id": "moon", - "trigger": [ - {"platform": "event", "event_type": "test_event2"}, - {"platform": "event", "event_type": "test_event3"}, - ], - "condition": { - "condition": "template", - "value_template": "{{ trigger.event.event_type=='test_event2' }}", - }, - "action": {"event": "another_event"}, - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json({"id": next_id(), "type": "automation/trace/get"}) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {} - - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "sun"} - ) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {"sun": []} - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - - # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/get"}) - response = await client.receive_json() - assert response["success"] - assert "moon" not in response["result"] - assert len(response["result"]["sun"]) == 1 - trace = response["result"]["sun"][0] - assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert trace["action_trace"]["action/0"][0]["error"] - assert "result" not in trace["action_trace"]["action/0"][0] - assert trace["condition_trace"] == {} - assert trace["config"] == sun_config - assert trace["context"] - assert trace["error"] == "Unable to find service test.automation" - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event'" - assert trace["unique_id"] == "sun" - assert trace["variables"] - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get trace - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} - ) - response = await client.receive_json() - assert response["success"] - assert "sun" not in response["result"] - assert len(response["result"]["moon"]) == 1 - trace = response["result"]["moon"][0] - assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} - assert trace["config"] == moon_config - assert trace["context"] - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - assert trace["variables"] - - # Trigger "moon" automation, with failing condition - hass.bus.async_fire("test_event3") - await hass.async_block_till_done() - - # Get trace - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} - ) - response = await client.receive_json() - assert response["success"] - assert "sun" not in response["result"] - assert len(response["result"]["moon"]) == 2 - trace = response["result"]["moon"][1] - assert len(trace["action_trace"]) == 0 - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False} - assert trace["config"] == moon_config - assert trace["context"] - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" - assert trace["variables"] - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get trace - await client.send_json( - {"id": next_id(), "type": "automation/trace/get", "automation_id": "moon"} - ) - response = await client.receive_json() - assert response["success"] - assert "sun" not in response["result"] - assert len(response["result"]["moon"]) == 3 - trace = response["result"]["moon"][2] - assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} - assert trace["config"] == moon_config - assert trace["context"] - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["trigger"]["description"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - assert trace["variables"] - - -async def test_automation_trace_overflow(hass, hass_ws_client): - """Test the number of stored traces per automation is limited.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": {"event": "some_event"}, - } - moon_config = { - "id": "moon", - "trigger": {"platform": "event", "event_type": "test_event2"}, - "action": {"event": "another_event"}, - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {} - - # Trigger "sun" and "moon" automation once - hass.bus.async_fire("test_event") - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert len(response["result"]["moon"]) == 1 - moon_run_id = response["result"]["moon"][0]["run_id"] - assert len(response["result"]["sun"]) == 1 - - # Trigger "moon" automation enough times to overflow the number of stored traces - for _ in range(automation.STORED_TRACES): - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert len(response["result"]["moon"]) == automation.STORED_TRACES - assert len(response["result"]["sun"]) == 1 - assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 - assert ( - int(response["result"]["moon"][-1]["run_id"]) - == int(moon_run_id) + automation.STORED_TRACES - ) - - -async def test_list_automation_traces(hass, hass_ws_client): - """Test listing automation traces.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": {"service": "test.automation"}, - } - moon_config = { - "id": "moon", - "trigger": [ - {"platform": "event", "event_type": "test_event2"}, - {"platform": "event", "event_type": "test_event3"}, - ], - "condition": { - "condition": "template", - "value_template": "{{ trigger.event.event_type=='test_event2' }}", - }, - "action": {"event": "another_event"}, - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert response["result"] == {} - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - - # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert "moon" not in response["result"] - assert len(response["result"]["sun"]) == 1 - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Trigger "moon" automation, with failing condition - hass.bus.async_fire("test_event3") - await hass.async_block_till_done() - - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") - await hass.async_block_till_done() - - # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - assert len(response["result"]["moon"]) == 3 - assert len(response["result"]["sun"]) == 1 - trace = response["result"]["sun"][0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] is None - assert trace["error"] == "Unable to find service test.automation" - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event'" - assert trace["unique_id"] == "sun" - - trace = response["result"]["moon"][0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - - trace = response["result"]["moon"][1] - assert trace["last_action"] is None - assert trace["last_condition"] == "condition/0" - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" - - trace = response["result"]["moon"][2] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" - assert "error" not in trace - assert trace["state"] == "stopped" - assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" - - -async def test_automation_breakpoints(hass, hass_ws_client): - """Test automation breakpoints.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - trace = response["result"][automation_id][-1] - assert trace["last_action"] == expected_action - assert trace["state"] == expected_state - return trace["run_id"] - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": [ - {"event": "event0"}, - {"event": "event1"}, - {"event": "event2"}, - {"event": "event3"}, - {"event": "event4"}, - {"event": "event5"}, - {"event": "event6"}, - {"event": "event7"}, - {"event": "event8"}, - ], - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "1", - } - ) - response = await client.receive_json() - assert not response["success"] - - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) - response = await client.receive_json() - assert response["success"] - assert response["result"] == [] - - subscription_id = next_id() - await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/5", - } - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) - response = await client.receive_json() - assert response["success"] - assert_lists_same( - response["result"], - [ - {"node": "action/1", "run_id": "*", "automation_id": "sun"}, - {"node": "action/5", "run_id": "*", "automation_id": "sun"}, - ], - ) - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/1", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/step", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/2", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/2", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/5", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") - - -async def test_automation_breakpoints_2(hass, hass_ws_client): - """Test execution resumes and breakpoints are removed after subscription removed.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - trace = response["result"][automation_id][-1] - assert trace["last_action"] == expected_action - assert trace["state"] == expected_state - return trace["run_id"] - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": [ - {"event": "event0"}, - {"event": "event1"}, - {"event": "event2"}, - {"event": "event3"}, - {"event": "event4"}, - {"event": "event5"}, - {"event": "event6"}, - {"event": "event7"}, - {"event": "event8"}, - ], - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - subscription_id = next_id() - await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/1", - "run_id": run_id, - } - - # Unsubscribe - execution should resume - await client.send_json( - {"id": next_id(), "type": "unsubscribe_events", "subscription": subscription_id} - ) - response = await client.receive_json() - assert response["success"] - await hass.async_block_till_done() - await assert_last_action("sun", "action/8", "stopped") - - # Should not be possible to set breakpoints - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "1", - } - ) - response = await client.receive_json() - assert not response["success"] - - # Trigger "sun" automation, should finish without stopping on breakpoints - hass.bus.async_fire("test_event") - await hass.async_block_till_done() - - new_run_id = await assert_last_action("sun", "action/8", "stopped") - assert new_run_id != run_id - - -async def test_automation_breakpoints_3(hass, hass_ws_client): - """Test breakpoints can be cleared.""" - id = 1 - - def next_id(): - nonlocal id - id += 1 - return id - - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) - response = await client.receive_json() - assert response["success"] - trace = response["result"][automation_id][-1] - assert trace["last_action"] == expected_action - assert trace["state"] == expected_state - return trace["run_id"] - - sun_config = { - "id": "sun", - "trigger": {"platform": "event", "event_type": "test_event"}, - "action": [ - {"event": "event0"}, - {"event": "event1"}, - {"event": "event2"}, - {"event": "event3"}, - {"event": "event4"}, - {"event": "event5"}, - {"event": "event6"}, - {"event": "event7"}, - {"event": "event8"}, - ], - } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) - - client = await hass_ws_client() - - subscription_id = next_id() - await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", - "node": "action/5", - } - ) - response = await client.receive_json() - assert response["success"] - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/1", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/5", - "run_id": run_id, - } - - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", - "run_id": run_id, - } - ) - response = await client.receive_json() - assert response["success"] - await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") - - # Clear 1st breakpoint - await client.send_json( - { - "id": next_id(), - "type": "automation/debug/breakpoint/clear", - "automation_id": "sun", - "node": "action/1", - } - ) - response = await client.receive_json() - assert response["success"] - - # Trigger "sun" automation - hass.bus.async_fire("test_event") - - response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") - assert response["event"] == { - "automation_id": "sun", - "node": "action/5", - "run_id": run_id, - } From 00bd591238ca714988c559a8fc4580ab38d99ba5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 10 Mar 2021 16:08:12 -0800 Subject: [PATCH 1180/1818] Verify get_zones webhook works (#47741) --- tests/components/mobile_app/test_webhook.py | 42 +++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index a7dc675b7b7ebb..d5cb72fa850de2 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -9,7 +9,6 @@ from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.setup import async_setup_component from .const import CALL_SERVICE, FIRE_EVENT, REGISTER_CLEARTEXT, RENDER_TEMPLATE, UPDATE @@ -152,11 +151,29 @@ async def test_webhook_update_registration(webhook_client, authed_api_client): async def test_webhook_handle_get_zones(hass, create_registrations, webhook_client): """Test that we can get zones properly.""" - await async_setup_component( - hass, - ZONE_DOMAIN, - {ZONE_DOMAIN: {}}, - ) + # Zone is already loaded as part of the fixture, + # so we just trigger a reload. + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + ZONE_DOMAIN: [ + { + "name": "School", + "latitude": 32.8773367, + "longitude": -117.2494053, + "radius": 250, + "icon": "mdi:school", + }, + { + "name": "Work", + "latitude": 33.8773367, + "longitude": -118.2494053, + }, + ] + }, + ): + await hass.services.async_call(ZONE_DOMAIN, "reload", blocking=True) resp = await webhook_client.post( "/api/webhook/{}".format(create_registrations[1]["webhook_id"]), @@ -166,10 +183,21 @@ async def test_webhook_handle_get_zones(hass, create_registrations, webhook_clie assert resp.status == 200 json = await resp.json() - assert len(json) == 1 + assert len(json) == 3 zones = sorted(json, key=lambda entry: entry["entity_id"]) assert zones[0]["entity_id"] == "zone.home" + assert zones[1]["entity_id"] == "zone.school" + assert zones[1]["attributes"]["icon"] == "mdi:school" + assert zones[1]["attributes"]["latitude"] == 32.8773367 + assert zones[1]["attributes"]["longitude"] == -117.2494053 + assert zones[1]["attributes"]["radius"] == 250 + + assert zones[2]["entity_id"] == "zone.work" + assert "icon" not in zones[2]["attributes"] + assert zones[2]["attributes"]["latitude"] == 33.8773367 + assert zones[2]["attributes"]["longitude"] == -118.2494053 + async def test_webhook_handle_get_config(hass, create_registrations, webhook_client): """Test that we can get config properly.""" From 9a686d148e5d98e559fccccb9ba6fcd28adaf1e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Mar 2021 21:12:02 -1000 Subject: [PATCH 1181/1818] Ensure startup can proceed when there is package metadata cruft (#47706) If a package fails to install or partially installed importlib version can return None. We now try pkg_resources first, then try importlib, and handle the case where version unexpectedly returns None --- homeassistant/util/package.py | 12 ++++++++- tests/util/test_package.py | 47 ++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 5391d92ed89cbf..34628b4ca4d30c 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -34,6 +34,9 @@ def is_installed(package: str) -> bool: Returns False when the package is not installed or doesn't meet req. """ try: + pkg_resources.get_distribution(package) + return True + except (pkg_resources.ResolutionError, pkg_resources.ExtractionError): req = pkg_resources.Requirement.parse(package) except ValueError: # This is a zip file. We no longer use this in Home Assistant, @@ -41,7 +44,14 @@ def is_installed(package: str) -> bool: req = pkg_resources.Requirement.parse(urlparse(package).fragment) try: - return version(req.project_name) in req + installed_version = version(req.project_name) + # This will happen when an install failed or + # 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 + return False + return installed_version in req except PackageNotFoundError: return False diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 0c251662444406..494fe5fa11fbe8 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -239,10 +239,55 @@ async def test_async_get_user_site(mock_env_copy): def test_check_package_global(): """Test for an installed package.""" - installed_package = list(pkg_resources.working_set)[0].project_name + first_package = list(pkg_resources.working_set)[0] + installed_package = first_package.project_name + installed_version = first_package.version + assert package.is_installed(installed_package) + assert package.is_installed(f"{installed_package}=={installed_version}") + assert package.is_installed(f"{installed_package}>={installed_version}") + assert package.is_installed(f"{installed_package}<={installed_version}") + assert not package.is_installed(f"{installed_package}<{installed_version}") + + +def test_check_package_version_does_not_match(): + """Test for version mismatch.""" + installed_package = list(pkg_resources.working_set)[0].project_name + assert not package.is_installed(f"{installed_package}==999.999.999") + assert not package.is_installed(f"{installed_package}>=999.999.999") def test_check_package_zip(): """Test for an installed zip package.""" assert not package.is_installed(TEST_ZIP_REQ) + + +def test_get_distribution_falls_back_to_version(): + """Test for get_distribution failing and fallback to version.""" + first_package = list(pkg_resources.working_set)[0] + installed_package = first_package.project_name + installed_version = first_package.version + + with patch( + "homeassistant.util.package.pkg_resources.get_distribution", + side_effect=pkg_resources.ExtractionError, + ): + assert package.is_installed(installed_package) + assert package.is_installed(f"{installed_package}=={installed_version}") + assert package.is_installed(f"{installed_package}>={installed_version}") + assert package.is_installed(f"{installed_package}<={installed_version}") + assert not package.is_installed(f"{installed_package}<{installed_version}") + + +def test_check_package_previous_failed_install(): + """Test for when a previously install package failed and left cruft behind.""" + first_package = list(pkg_resources.working_set)[0] + installed_package = first_package.project_name + installed_version = first_package.version + + with patch( + "homeassistant.util.package.pkg_resources.get_distribution", + side_effect=pkg_resources.ExtractionError, + ), patch("homeassistant.util.package.version", return_value=None): + assert not package.is_installed(installed_package) + assert not package.is_installed(f"{installed_package}=={installed_version}") From 7e615cb7fdde6de265384d2635072c07c4b66505 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 11 Mar 2021 10:16:52 +0100 Subject: [PATCH 1182/1818] Fixed string typos in Lutron and Roomba (#47745) --- homeassistant/components/lutron_caseta/strings.json | 2 +- homeassistant/components/roomba/strings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lutron_caseta/strings.json b/homeassistant/components/lutron_caseta/strings.json index 604a3c24ab2e57..9464523fcce3c6 100644 --- a/homeassistant/components/lutron_caseta/strings.json +++ b/homeassistant/components/lutron_caseta/strings.json @@ -7,7 +7,7 @@ "description": "Couldn’t setup bridge (host: {host}) imported from configuration.yaml." }, "user": { - "title": "Automaticlly connect to the bridge", + "title": "Automatically connect to the bridge", "description": "Enter the IP address of the device.", "data": { "host": "[%key:common::config_flow::data::host%]" diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index 48e130df4f5123..59039a8d2765c3 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -3,7 +3,7 @@ "flow_title": "iRobot {name} ({host})", "step": { "init": { - "title": "Automaticlly connect to the device", + "title": "Automatically connect to the device", "description": "Select a Roomba or Braava.", "data": { "host": "[%key:common::config_flow::data::host%]" From b9c2f80cab187d8e0be14935996aa04ec51c160a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 11:46:32 +0100 Subject: [PATCH 1183/1818] Fix light brightness_step on multiple entities (#47746) * Fix light brightness_step on multiple entities * Fix comment Co-authored-by: Martin Hjelmare --- homeassistant/components/light/__init__.py | 2 +- tests/components/light/test_init.py | 47 ++++++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 4156736f74d3a0..693eb1a573ad3f 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -207,7 +207,7 @@ async def async_handle_light_on_service(light, call): If brightness is set to 0, this service will turn the light off. """ - params = call.data["params"] + params = dict(call.data["params"]) # Only process params once we processed brightness step if params and ( diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index c36fa623e37756..10f475a580dab0 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -706,36 +706,51 @@ async def test_light_turn_on_auth(hass, hass_admin_user): async def test_light_brightness_step(hass): """Test that light context works.""" platform = getattr(hass.components, "test.light") - platform.init() - entity = platform.ENTITIES[0] - entity.supported_features = light.SUPPORT_BRIGHTNESS - entity.brightness = 100 + platform.init(empty=True) + platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON)) + entity0 = platform.ENTITIES[0] + entity0.supported_features = light.SUPPORT_BRIGHTNESS + entity0.brightness = 100 + entity1 = platform.ENTITIES[1] + entity1.supported_features = light.SUPPORT_BRIGHTNESS + entity1.brightness = 50 assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() - state = hass.states.get(entity.entity_id) + state = hass.states.get(entity0.entity_id) assert state is not None assert state.attributes["brightness"] == 100 + state = hass.states.get(entity1.entity_id) + assert state is not None + assert state.attributes["brightness"] == 50 await hass.services.async_call( "light", "turn_on", - {"entity_id": entity.entity_id, "brightness_step": -10}, + {"entity_id": [entity0.entity_id, entity1.entity_id], "brightness_step": -10}, blocking=True, ) - _, data = entity.last_call("turn_on") - assert data["brightness"] == 90, data + _, data = entity0.last_call("turn_on") + assert data["brightness"] == 90 # 100 - 10 + _, data = entity1.last_call("turn_on") + assert data["brightness"] == 40 # 50 - 10 await hass.services.async_call( "light", "turn_on", - {"entity_id": entity.entity_id, "brightness_step_pct": 10}, + { + "entity_id": [entity0.entity_id, entity1.entity_id], + "brightness_step_pct": 10, + }, blocking=True, ) - _, data = entity.last_call("turn_on") - assert data["brightness"] == 126, data + _, data = entity0.last_call("turn_on") + assert data["brightness"] == 126 # 100 + (255 * 0.10) + _, data = entity1.last_call("turn_on") + assert data["brightness"] == 76 # 50 + (255 * 0.10) async def test_light_brightness_pct_conversion(hass): @@ -760,7 +775,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 3, data + assert data["brightness"] == 3 await hass.services.async_call( "light", @@ -770,7 +785,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 5, data + assert data["brightness"] == 5 await hass.services.async_call( "light", @@ -780,7 +795,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 128, data + assert data["brightness"] == 128 await hass.services.async_call( "light", @@ -790,7 +805,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 252, data + assert data["brightness"] == 252 await hass.services.async_call( "light", @@ -800,7 +815,7 @@ async def test_light_brightness_pct_conversion(hass): ) _, data = entity.last_call("turn_on") - assert data["brightness"] == 255, data + assert data["brightness"] == 255 def test_deprecated_base_class(caplog): From 724574d336598034ca51c1dc107e4394e778a6cb Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 11 Mar 2021 11:48:48 +0100 Subject: [PATCH 1184/1818] Add Xiaomi Miio sensor config flow (#46964) * add config flow * fix styling * Add air_quality platform * fix imports * fix black * Update homeassistant/components/xiaomi_miio/sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/xiaomi_miio/air_quality.py Co-authored-by: Martin Hjelmare * process revieuw feedback * remove unused import * fix formatting Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 5 ++ .../components/xiaomi_miio/air_quality.py | 89 +++++++++---------- homeassistant/components/xiaomi_miio/const.py | 17 ++-- .../components/xiaomi_miio/sensor.py | 79 +++++++--------- 4 files changed, 89 insertions(+), 101 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 139ac017f66f07..5b67d21b481697 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -16,6 +16,7 @@ CONF_MODEL, DOMAIN, KEY_COORDINATOR, + MODELS_AIR_MONITOR, MODELS_FAN, MODELS_SWITCH, MODELS_VACUUM, @@ -28,6 +29,7 @@ SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] VACUUM_PLATFORMS = ["vacuum"] +AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"] async def async_setup(hass: core.HomeAssistant, config: dict): @@ -129,6 +131,9 @@ async def async_setup_device_entry( for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS + for air_monitor_model in MODELS_AIR_MONITOR: + if model.startswith(air_monitor_model): + platforms = AIR_MONITOR_PLATFORMS if not platforms: return False diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index 1e1e1b58632f66..b278a60bd48973 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -1,19 +1,24 @@ """Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" import logging -from miio import AirQualityMonitor, Device, DeviceException +from miio import AirQualityMonitor, DeviceException import voluptuous as vol from homeassistant.components.air_quality import PLATFORM_SCHEMA, AirQualityEntity +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.exceptions import NoEntitySpecifiedError, PlatformNotReady import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE, + CONF_FLOW_TYPE, + CONF_MODEL, + DOMAIN, MODEL_AIRQUALITYMONITOR_B1, MODEL_AIRQUALITYMONITOR_S1, MODEL_AIRQUALITYMONITOR_V1, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) @@ -41,52 +46,54 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor from config.""" + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Air Quality via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Xiaomi Air Quality from a config entry.""" + entities = [] - miio_device = Device(host, token) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id - try: - device_info = await hass.async_add_executor_job(miio_device.info) - except DeviceException as ex: - raise PlatformNotReady from ex + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.debug( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) + device = AirQualityMonitor(host, token, model=model) - device = AirQualityMonitor(host, token, model=model) + if model == MODEL_AIRQUALITYMONITOR_S1: + entities.append(AirMonitorS1(name, device, config_entry, unique_id)) + elif model == MODEL_AIRQUALITYMONITOR_B1: + entities.append(AirMonitorB1(name, device, config_entry, unique_id)) + elif model == MODEL_AIRQUALITYMONITOR_V1: + entities.append(AirMonitorV1(name, device, config_entry, unique_id)) + else: + _LOGGER.warning("AirQualityMonitor model '%s' is not yet supported", model) - if model == MODEL_AIRQUALITYMONITOR_S1: - entity = AirMonitorS1(name, device, unique_id) - elif model == MODEL_AIRQUALITYMONITOR_B1: - entity = AirMonitorB1(name, device, unique_id) - elif model == MODEL_AIRQUALITYMONITOR_V1: - entity = AirMonitorV1(name, device, unique_id) - else: - raise NoEntitySpecifiedError(f"Not support for entity {unique_id}") + async_add_entities(entities, update_before_add=True) - async_add_entities([entity], update_before_add=True) - -class AirMonitorB1(AirQualityEntity): +class AirMonitorB1(XiaomiMiioEntity, AirQualityEntity): """Air Quality class for Xiaomi cgllc.airmonitor.b1 device.""" - def __init__(self, name, device, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the entity.""" - self._name = name - self._device = device - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) + self._icon = "mdi:cloud" self._available = None self._air_quality_index = None @@ -112,11 +119,6 @@ async def async_update(self): self._available = False _LOGGER.error("Got exception while fetching the state: %s", ex) - @property - def name(self): - """Return the name of this entity, if any.""" - return self._name - @property def icon(self): """Return the icon to use for device if any.""" @@ -127,11 +129,6 @@ def available(self): """Return true when state is known.""" return self._available - @property - def unique_id(self): - """Return the unique ID.""" - return self._unique_id - @property def air_quality_index(self): """Return the Air Quality Index (AQI).""" diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index da4229869009c7..6bf2e2f69d1fdc 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -55,6 +55,11 @@ MODEL_AIRFRESH_VA2, ] +# AirQuality Models +MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" +MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" +MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" + # Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ @@ -71,8 +76,13 @@ ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] +MODELS_AIR_MONITOR = [ + MODEL_AIRQUALITYMONITOR_V1, + MODEL_AIRQUALITYMONITOR_B1, + MODEL_AIRQUALITYMONITOR_S1, +] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_FAN + MODELS_VACUUM +MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services @@ -126,8 +136,3 @@ SERVICE_CLEAN_SEGMENT = "vacuum_clean_segment" SERVICE_CLEAN_ZONE = "vacuum_clean_zone" SERVICE_GOTO = "vacuum_goto" - -# AirQuality Model -MODEL_AIRQUALITYMONITOR_V1 = "zhimi.airmonitor.v1" -MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" -MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index a7cfdd788a5382..6d43b835f1c24f 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -13,6 +13,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONF_HOST, @@ -26,17 +27,16 @@ PRESSURE_HPA, TEMP_CELSIUS, ) -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .const import CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR +from .const import CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR +from .device import XiaomiMiioEntity from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Sensor" -DATA_KEY = "sensor.xiaomi_miio" UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -54,7 +54,6 @@ ATTR_NIGHT_TIME_BEGIN = "night_time_begin" ATTR_NIGHT_TIME_END = "night_time_end" ATTR_SENSOR_STATE = "sensor_state" -ATTR_MODEL = "model" SUCCESS = ["ok"] @@ -81,6 +80,21 @@ class SensorType: } +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Sensor via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi sensor from a config entry.""" entities = [] @@ -114,48 +128,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ] ) - async_add_entities(entities, update_before_add=True) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + unique_id = config_entry.unique_id + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} - - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) - - try: - air_quality_monitor = AirQualityMonitor(host, token) - device_info = await hass.async_add_executor_job(air_quality_monitor.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) - device = XiaomiAirQualityMonitor(name, air_quality_monitor, model, unique_id) - except DeviceException as ex: - raise PlatformNotReady from ex + device = AirQualityMonitor(host, token) + entities.append(XiaomiAirQualityMonitor(name, device, config_entry, unique_id)) - hass.data[DATA_KEY][host] = device - async_add_entities([device], update_before_add=True) + async_add_entities(entities, update_before_add=True) -class XiaomiAirQualityMonitor(Entity): +class XiaomiAirQualityMonitor(XiaomiMiioEntity): """Representation of a Xiaomi Air Quality Monitor.""" - def __init__(self, name, device, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the entity.""" - self._name = name - self._device = device - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._icon = "mdi:cloud" self._unit_of_measurement = "AQI" @@ -170,19 +162,8 @@ def __init__(self, name, device, model, unique_id): ATTR_NIGHT_TIME_BEGIN: None, ATTR_NIGHT_TIME_END: None, ATTR_SENSOR_STATE: None, - ATTR_MODEL: self._model, } - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of this entity, if any.""" - return self._name - @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" From 9e487eb260271c11bb67e9ffaa584941da17b739 Mon Sep 17 00:00:00 2001 From: Kristian Heljas Date: Thu, 11 Mar 2021 14:42:13 +0200 Subject: [PATCH 1185/1818] Hoist mqtt name property and add icon support to MqttEntity (#47165) * hoist common MqttEntity properties * remove default name for MqttEntity Default naming is sensible enough * disable overriding common MqttEntity schema * merge common MqttEntity schemas into MQTT_ENTITY_COMMON_SCHEMA --- .../components/mqtt/alarm_control_panel.py | 66 +++---- .../components/mqtt/binary_sensor.py | 40 ++-- homeassistant/components/mqtt/camera.py | 36 +--- homeassistant/components/mqtt/climate.py | 178 ++++++++---------- homeassistant/components/mqtt/cover.py | 22 +-- .../mqtt/device_tracker/schema_discovery.py | 46 +---- homeassistant/components/mqtt/fan.py | 80 +++----- .../components/mqtt/light/schema_basic.py | 21 +-- .../components/mqtt/light/schema_json.py | 21 +-- .../components/mqtt/light/schema_template.py | 21 +-- homeassistant/components/mqtt/lock.py | 53 ++---- homeassistant/components/mqtt/mixins.py | 23 ++- homeassistant/components/mqtt/number.py | 49 +---- homeassistant/components/mqtt/sensor.py | 43 +---- homeassistant/components/mqtt/switch.py | 50 ++--- .../components/mqtt/vacuum/schema_legacy.py | 25 +-- .../components/mqtt/vacuum/schema_state.py | 26 +-- 17 files changed, 235 insertions(+), 565 deletions(-) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 2f2ba06d6d79c6..0f10e91e41ca9c 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -14,9 +14,7 @@ ) from homeassistant.const import ( CONF_CODE, - CONF_DEVICE, CONF_NAME, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, @@ -44,13 +42,7 @@ ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) @@ -70,34 +62,28 @@ DEFAULT_ARM_CUSTOM_BYPASS = "ARM_CUSTOM_BYPASS" DEFAULT_DISARM = "DISARM" DEFAULT_NAME = "MQTT Alarm" -PLATFORM_SCHEMA = ( - mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_CODE): cv.string, - vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, - vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean, - vol.Optional( - CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE - ): cv.template, - vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, - vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, - vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, - vol.Optional( - CONF_PAYLOAD_ARM_CUSTOM_BYPASS, default=DEFAULT_ARM_CUSTOM_BYPASS - ): cv.string, - vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, - vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, - vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_CODE): cv.string, + vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, + vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean, + vol.Optional( + CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE + ): cv.template, + vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, + vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, + vol.Optional( + CONF_PAYLOAD_ARM_CUSTOM_BYPASS, default=DEFAULT_ARM_CUSTOM_BYPASS + ): cv.string, + vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string, + vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, + vol.Required(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -138,7 +124,6 @@ def config_schema(): return PLATFORM_SCHEMA def _setup_from_config(self, config): - self._config = config value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass @@ -186,11 +171,6 @@ def message_received(msg): }, ) - @property - def name(self): - """Return the name of the device.""" - return self._config[CONF_NAME] - @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 a1f13e7873ef0b..fbd5e7535c519a 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -11,13 +11,11 @@ BinarySensorEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_NAME, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, ) from homeassistant.core import callback @@ -32,9 +30,7 @@ from .. import mqtt from .debug_info import log_messages from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, + MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, async_setup_entry_helper, @@ -49,23 +45,17 @@ DEFAULT_FORCE_UPDATE = False CONF_EXPIRE_AFTER = "expire_after" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OFF_DELAY): cv.positive_int, - 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_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OFF_DELAY): cv.positive_int, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -113,7 +103,6 @@ def config_schema(): return PLATFORM_SCHEMA def _setup_from_config(self, config): - self._config = config value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass @@ -218,11 +207,6 @@ def _value_is_expired(self, *_): self.async_write_ha_state() - @property - def name(self): - """Return the name of the binary sensor.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index cc58741a92315c..0a1a35b2ddd4a4 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -5,7 +5,7 @@ from homeassistant.components import camera from homeassistant.components.camera import Camera -from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_UNIQUE_ID +from homeassistant.const import CONF_NAME from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service @@ -14,29 +14,17 @@ from . import CONF_QOS, DOMAIN, PLATFORMS, subscription from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_TOPIC = "topic" DEFAULT_NAME = "MQTT Camera" -PLATFORM_SCHEMA = ( - mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -77,9 +65,6 @@ def config_schema(): """Return the config schema.""" return PLATFORM_SCHEMA - def _setup_from_config(self, config): - self._config = config - async def _subscribe_topics(self): """(Re)Subscribe to topics.""" @@ -105,8 +90,3 @@ def message_received(msg): async def async_camera_image(self): """Return image response.""" return self._last_image - - @property - def name(self): - """Return the name of this camera.""" - return self._config[CONF_NAME] diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index ede39103791097..8ab7a9ca3cfb66 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -36,12 +36,10 @@ ) from homeassistant.const import ( ATTR_TEMPERATURE, - CONF_DEVICE, CONF_NAME, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_TEMPERATURE_UNIT, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, PRECISION_HALVES, PRECISION_TENTHS, @@ -63,13 +61,7 @@ ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) @@ -178,90 +170,84 @@ ) SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) -PLATFORM_SCHEMA = ( - 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, - 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, - vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template, - vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_FAN_MODE_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_FAN_MODE_LIST, - default=[FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], - ): cv.ensure_list, - vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - 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, - vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list, - vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_MODE_LIST, - default=[ - HVAC_MODE_AUTO, - HVAC_MODE_OFF, - HVAC_MODE_COOL, - HVAC_MODE_HEAT, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - ], - ): cv.ensure_list, - vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, - vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_PRECISION): vol.In( - [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] - ), - vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, - 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, - vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_SWING_MODE_LIST, default=[STATE_ON, HVAC_MODE_OFF] - ): cv.ensure_list, - vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int, - vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float), - vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float), - vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMP_HIGH_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_HIGH_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMP_HIGH_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMP_HIGH_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_LOW_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMP_LOW_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit, - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = 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, + 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, + vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_FAN_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_FAN_MODE_LIST, + default=[FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH], + ): cv.ensure_list, + vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + 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, + vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list, + vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_MODE_LIST, + default=[ + HVAC_MODE_AUTO, + HVAC_MODE_OFF, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + ], + ): cv.ensure_list, + vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, + vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PRECISION): vol.In( + [PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), + vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, + 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, + vol.Optional(CONF_SWING_MODE_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_SWING_MODE_LIST, default=[STATE_ON, HVAC_MODE_OFF] + ): cv.ensure_list, + vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int, + vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float), + vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float), + vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_HIGH_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_HIGH_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_HIGH_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_HIGH_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_LOW_COMMAND_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_LOW_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMP_LOW_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_LOW_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMP_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -321,7 +307,6 @@ async def async_added_to_hass(self): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config self._topic = {key: config.get(key) for key in TOPIC_KEYS} # set to None in non-optimistic mode @@ -563,11 +548,6 @@ def handle_hold_mode_received(msg): self.hass, self._sub_state, topics ) - @property - def name(self): - """Return the name of the climate device.""" - return self._config[CONF_NAME] - @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 7b7f983b1e49a4..5c7b0c10cf6eee 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -20,11 +20,9 @@ CoverEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_DEVICE_CLASS, CONF_NAME, CONF_OPTIMISTIC, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_CLOSED, STATE_CLOSING, @@ -48,13 +46,7 @@ ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) @@ -147,7 +139,6 @@ def validate_options(value): mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_GET_POSITION_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -183,14 +174,11 @@ def validate_options(value): ): cv.boolean, vol.Optional(CONF_TILT_STATUS_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_TILT_STATUS_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_GET_POSITION_TEMPLATE): cv.template, vol.Optional(CONF_TILT_COMMAND_TEMPLATE): cv.template, } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema), + ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema), validate_options, ) @@ -238,7 +226,6 @@ def config_schema(): return PLATFORM_SCHEMA def _setup_from_config(self, config): - self._config = config self._optimistic = config[CONF_OPTIMISTIC] or ( config.get(CONF_STATE_TOPIC) is None and config.get(CONF_GET_POSITION_TOPIC) is None @@ -399,11 +386,6 @@ def assumed_state(self): """Return true if we do optimistic updates.""" return self._optimistic - @property - def name(self): - """Return the name of the cover.""" - return self._config[CONF_NAME] - @property def is_closed(self): """Return true if the cover is closed or None if the status is unknown.""" diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index bd5d9a1e60ef3f..a1279f512a519e 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -10,10 +10,7 @@ ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, - CONF_DEVICE, - CONF_ICON, CONF_NAME, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_HOME, STATE_NOT_HOME, @@ -25,33 +22,20 @@ from ... import mqtt from ..const import CONF_QOS, CONF_STATE_TOPIC from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_PAYLOAD_HOME = "payload_home" CONF_PAYLOAD_NOT_HOME = "payload_not_home" CONF_SOURCE_TYPE = "source_type" -PLATFORM_SCHEMA_DISCOVERY = ( - mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, - vol.Optional(CONF_PAYLOAD_NOT_HOME, default=STATE_NOT_HOME): cv.string, - vol.Optional(CONF_SOURCE_TYPE): vol.In(SOURCE_TYPES), - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA_DISCOVERY = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, + vol.Optional(CONF_PAYLOAD_NOT_HOME, default=STATE_NOT_HOME): cv.string, + vol.Optional(CONF_SOURCE_TYPE): vol.In(SOURCE_TYPES), + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_entry_from_discovery(hass, config_entry, async_add_entities): @@ -87,8 +71,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - value_template = self._config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = self.hass @@ -125,11 +107,6 @@ def message_received(msg): }, ) - @property - def icon(self): - """Return the icon of the device.""" - return self._config.get(CONF_ICON) - @property def latitude(self): """Return latitude if provided in device_state_attributes or None.""" @@ -165,11 +142,6 @@ def location_name(self): """Return a location name for the current location of the device.""" return self._location_name - @property - def name(self): - """Return the name of the device tracker.""" - return self._config.get(CONF_NAME) - @property def source_type(self): """Return the source type, eg gps or router, of the device.""" diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 24d652062ae10d..c0663370805383 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -15,13 +15,11 @@ FanEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_STATE, - CONF_UNIQUE_ID, ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -39,13 +37,7 @@ ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_SPEED_STATE_TOPIC = "speed_state_topic" @@ -72,41 +64,35 @@ OSCILLATION = "oscillation" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, - vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, - vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, - vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, - 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_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD - ): cv.string, - vol.Optional( - CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD - ): cv.string, - vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_SPEED_LIST, - default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], - ): cv.ensure_list, - vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, + vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, + vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, + vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, + 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_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD + ): cv.string, + vol.Optional( + CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD + ): cv.string, + vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_SPEED_LIST, + default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + ): cv.ensure_list, + vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -158,7 +144,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config self._topic = { key: config.get(key) for key in ( @@ -288,11 +273,6 @@ def is_on(self): """Return true if device is on.""" return self._state - @property - def name(self) -> str: - """Get entity name.""" - return self._config[CONF_NAME] - @property def speed_list(self) -> list: """Get the list of available speeds.""" diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 04f01ea0d3a278..e2443ec1df6585 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -17,12 +17,10 @@ LightEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_ON, ) @@ -34,12 +32,7 @@ from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_LIGHT_SCHEMA_SCHEMA _LOGGER = logging.getLogger(__name__) @@ -110,7 +103,6 @@ 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_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, 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, @@ -132,7 +124,6 @@ vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_WHITE_VALUE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional( CONF_WHITE_VALUE_SCALE, default=DEFAULT_WHITE_VALUE_SCALE @@ -144,8 +135,7 @@ vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) @@ -194,8 +184,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - topic = { key: config.get(key) for key in ( @@ -540,11 +528,6 @@ def white_value(self): return white_value return None - @property - def name(self): - """Return the name of the device if any.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if device is on.""" diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 8ec5c29db621c8..99c48aa1c8f2a0 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -26,13 +26,11 @@ from homeassistant.const import ( CONF_BRIGHTNESS, CONF_COLOR_TEMP, - CONF_DEVICE, CONF_EFFECT, CONF_HS, CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, - CONF_UNIQUE_ID, CONF_WHITE_VALUE, CONF_XY, STATE_ON, @@ -46,12 +44,7 @@ from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_LIGHT_SCHEMA_SCHEMA from .schema_basic import CONF_BRIGHTNESS_SCALE @@ -89,7 +82,6 @@ CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE ): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional( @@ -109,13 +101,11 @@ vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean, vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean, vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) @@ -153,8 +143,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - self._topic = { key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC) } @@ -338,11 +326,6 @@ def white_value(self): """Return the white property.""" return self._white_value - @property - def name(self): - """Return the name of the device if any.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if device is on.""" diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 665e1a30d99a9a..118746f2229f6b 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -21,11 +21,9 @@ LightEntity, ) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, CONF_STATE_TEMPLATE, - CONF_UNIQUE_ID, STATE_OFF, STATE_ON, ) @@ -37,12 +35,7 @@ from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_LIGHT_SCHEMA_SCHEMA _LOGGER = logging.getLogger(__name__) @@ -73,7 +66,6 @@ vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_TEMPLATE): cv.template, vol.Optional(CONF_GREEN_TEMPLATE): cv.template, @@ -83,12 +75,10 @@ vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_RED_TEMPLATE): cv.template, vol.Optional(CONF_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) ) @@ -127,8 +117,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - self._topics = { key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC) } @@ -299,11 +287,6 @@ def white_value(self): """Return the white property.""" return self._white_value - @property - def name(self): - """Return the name of the entity.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return True if entity is on.""" diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index e66b93f51c0d6b..cdfa51015480ac 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -5,13 +5,7 @@ from homeassistant.components import lock from homeassistant.components.lock import LockEntity -from homeassistant.const import ( - CONF_DEVICE, - CONF_NAME, - CONF_OPTIMISTIC, - CONF_UNIQUE_ID, - CONF_VALUE_TEMPLATE, -) +from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service @@ -28,13 +22,7 @@ ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper CONF_PAYLOAD_LOCK = "payload_lock" CONF_PAYLOAD_UNLOCK = "payload_unlock" @@ -49,26 +37,16 @@ DEFAULT_STATE_LOCKED = "LOCKED" DEFAULT_STATE_UNLOCKED = "UNLOCKED" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, - vol.Optional( - CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK - ): cv.string, - vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string, - vol.Optional( - CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED - ): cv.string, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, + vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string, + vol.Optional(CONF_STATE_LOCKED, default=DEFAULT_STATE_LOCKED): cv.string, + vol.Optional(CONF_STATE_UNLOCKED, default=DEFAULT_STATE_UNLOCKED): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -111,8 +89,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - self._optimistic = config[CONF_OPTIMISTIC] value_template = self._config.get(CONF_VALUE_TEMPLATE) @@ -153,11 +129,6 @@ def message_received(msg): }, ) - @property - def name(self): - """Return the name of the lock.""" - return self._config[CONF_NAME] - @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 8d9c9533ed3fb3..8bcb5e5ca97710 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -6,7 +6,7 @@ import voluptuous as vol -from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_UNIQUE_ID +from homeassistant.const import CONF_DEVICE, CONF_ICON, CONF_NAME, CONF_UNIQUE_ID from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -134,10 +134,13 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType validate_device_has_at_least_one_identifier, ) -MQTT_JSON_ATTRS_SCHEMA = vol.Schema( +MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend( { + vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, + vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic, vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template, + vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -527,11 +530,12 @@ class MqttEntity( def __init__(self, hass, config, config_entry, discovery_data): """Init the MQTT Entity.""" self.hass = hass + self._config = config self._unique_id = config.get(CONF_UNIQUE_ID) self._sub_state = None # Load config - self._setup_from_config(config) + self._setup_from_config(self._config) # Initialize mixin classes MqttAttributes.__init__(self, config) @@ -547,7 +551,8 @@ async def async_added_to_hass(self): async def discovery_update(self, discovery_payload): """Handle updated discovery message.""" config = self.config_schema()(discovery_payload) - self._setup_from_config(config) + self._config = config + self._setup_from_config(self._config) await self.attributes_discovery_update(config) await self.availability_discovery_update(config) await self.device_info_discovery_update(config) @@ -575,6 +580,16 @@ def _setup_from_config(self, config): async def _subscribe_topics(self): """(Re)Subscribe to topics.""" + @property + def icon(self): + """Return icon of the entity if any.""" + return self._config.get(CONF_ICON) + + @property + def name(self): + """Return the name of the device if any.""" + return self._config.get(CONF_NAME) + @property def should_poll(self): """No polling needed.""" diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index aa24f81eb6967e..e7839f8e483bdf 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -6,13 +6,7 @@ from homeassistant.components import number from homeassistant.components.number import NumberEntity -from homeassistant.const import ( - CONF_DEVICE, - CONF_ICON, - CONF_NAME, - CONF_OPTIMISTIC, - CONF_UNIQUE_ID, -) +from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service @@ -30,32 +24,19 @@ from .. import mqtt from .const import CONF_RETAIN from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "MQTT Number" DEFAULT_OPTIMISTIC = False -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -90,7 +71,6 @@ def __init__(self, config, config_entry, discovery_data): self._current_number = None self._optimistic = config.get(CONF_OPTIMISTIC) - self._unique_id = config.get(CONF_UNIQUE_ID) NumberEntity.__init__(self) MqttEntity.__init__(self, None, config, config_entry, discovery_data) @@ -100,9 +80,6 @@ def config_schema(): """Return the config schema.""" return PLATFORM_SCHEMA - def _setup_from_config(self, config): - self._config = config - async def _subscribe_topics(self): """(Re)Subscribe to topics.""" @@ -165,17 +142,7 @@ async def async_set_value(self, value: float) -> None: self._config[CONF_RETAIN], ) - @property - def name(self): - """Return the name of this number.""" - return self._config[CONF_NAME] - @property def assumed_state(self): """Return true if we do optimistic updates.""" return self._optimistic - - @property - def icon(self): - """Return the icon.""" - return self._config.get(CONF_ICON) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index cb263febe13c41..d32f4c423831c9 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -8,12 +8,9 @@ from homeassistant.components import sensor from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA from homeassistant.const import ( - CONF_DEVICE, CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, - CONF_ICON, CONF_NAME, - CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, ) @@ -29,9 +26,7 @@ from .. import mqtt from .debug_info import log_messages from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, + MQTT_ENTITY_COMMON_SCHEMA, MqttAvailability, MqttEntity, async_setup_entry_helper, @@ -41,22 +36,15 @@ DEFAULT_NAME = "MQTT Sensor" DEFAULT_FORCE_UPDATE = False -PLATFORM_SCHEMA = ( - mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UNIQUE_ID): cv.string, - vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -105,7 +93,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config template = self._config.get(CONF_VALUE_TEMPLATE) if template is not None: template.hass = self.hass @@ -163,11 +150,6 @@ def _value_is_expired(self, *_): self._expired = True self.async_write_ha_state() - @property - def name(self): - """Return the name of the sensor.""" - return self._config[CONF_NAME] - @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" @@ -183,11 +165,6 @@ def state(self): """Return the state of the entity.""" return self._state - @property - def icon(self): - """Return the icon.""" - return self._config.get(CONF_ICON) - @property def device_class(self) -> Optional[str]: """Return the device class of the sensor.""" diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 939d6bb98b128c..2b272b0f9be986 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -6,13 +6,10 @@ from homeassistant.components import switch from homeassistant.components.switch import SwitchEntity from homeassistant.const import ( - CONF_DEVICE, - CONF_ICON, CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, - CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_ON, ) @@ -33,13 +30,7 @@ ) from .. import mqtt from .debug_info import log_messages -from .mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, - async_setup_entry_helper, -) +from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper DEFAULT_NAME = "MQTT Switch" DEFAULT_PAYLOAD_ON = "ON" @@ -48,23 +39,16 @@ CONF_STATE_ON = "state_on" CONF_STATE_OFF = "state_off" -PLATFORM_SCHEMA = ( - mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, - vol.Optional(CONF_ICON): cv.icon, - 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_UNIQUE_ID): cv.string, - } - ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) -) +PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + 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, + } +).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) async def async_setup_platform( @@ -110,8 +94,6 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" - self._config = config - state_on = config.get(CONF_STATE_ON) self._state_on = state_on if state_on else config[CONF_PAYLOAD_ON] @@ -163,11 +145,6 @@ def state_message_received(msg): if last_state: self._state = last_state.state == STATE_ON - @property - def name(self): - """Return the name of the switch.""" - return self._config[CONF_NAME] - @property def is_on(self): """Return true if device is on.""" @@ -178,11 +155,6 @@ def assumed_state(self): """Return true if we do optimistic updates.""" return self._optimistic - @property - def icon(self): - """Return the icon.""" - return self._config.get(CONF_ICON) - async def async_turn_on(self, **kwargs): """Turn the device on. diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index ca91b8948d44b8..f0f00a72bb4e73 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -17,12 +17,7 @@ SUPPORT_TURN_ON, VacuumEntity, ) -from homeassistant.const import ( - ATTR_SUPPORTED_FEATURES, - CONF_DEVICE, - CONF_NAME, - CONF_UNIQUE_ID, -) +from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level @@ -30,12 +25,7 @@ from .. import subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services SERVICE_TO_STRING = { @@ -117,7 +107,6 @@ vol.Inclusive(CONF_CHARGING_TOPIC, "charging"): mqtt.valid_publish_topic, vol.Inclusive(CONF_CLEANING_TEMPLATE, "cleaning"): cv.template, vol.Inclusive(CONF_CLEANING_TOPIC, "cleaning"): mqtt.valid_publish_topic, - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Inclusive(CONF_DOCKED_TEMPLATE, "docked"): cv.template, vol.Inclusive(CONF_DOCKED_TOPIC, "docked"): mqtt.valid_publish_topic, vol.Inclusive(CONF_ERROR_TEMPLATE, "error"): cv.template, @@ -152,13 +141,11 @@ vol.Optional( CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS ): vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]), - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_VACUUM_SCHEMA.schema) ) @@ -192,7 +179,6 @@ def config_schema(): return PLATFORM_SCHEMA_LEGACY def _setup_from_config(self, config): - self._name = config[CONF_NAME] supported_feature_strings = config[CONF_SUPPORTED_FEATURES] self._supported_features = strings_to_services( supported_feature_strings, STRING_TO_SERVICE @@ -338,11 +324,6 @@ def message_received(msg): }, ) - @property - def name(self): - """Return the name of the vacuum.""" - return self._name - @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 3e43736ab2e386..37a12d33df644e 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -22,24 +22,14 @@ SUPPORT_STOP, StateVacuumEntity, ) -from homeassistant.const import ( - ATTR_SUPPORTED_FEATURES, - CONF_DEVICE, - CONF_NAME, - CONF_UNIQUE_ID, -) +from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription from ... import mqtt from ..debug_info import log_messages -from ..mixins import ( - MQTT_AVAILABILITY_SCHEMA, - MQTT_ENTITY_DEVICE_INFO_SCHEMA, - MQTT_JSON_ATTRS_SCHEMA, - MqttEntity, -) +from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services SERVICE_TO_STRING = { @@ -113,7 +103,6 @@ PLATFORM_SCHEMA_STATE = ( mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( { - vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All( cv.ensure_list, [cv.string] ), @@ -136,13 +125,11 @@ vol.Optional( CONF_SUPPORTED_FEATURES, default=DEFAULT_SERVICE_STRINGS ): vol.All(cv.ensure_list, [vol.In(STRING_TO_SERVICE.keys())]), - vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, } ) - .extend(MQTT_AVAILABILITY_SCHEMA.schema) - .extend(MQTT_JSON_ATTRS_SCHEMA.schema) + .extend(MQTT_ENTITY_COMMON_SCHEMA.schema) .extend(MQTT_VACUUM_SCHEMA.schema) ) @@ -171,8 +158,6 @@ def config_schema(): return PLATFORM_SCHEMA_STATE def _setup_from_config(self, config): - self._config = config - self._name = config[CONF_NAME] supported_feature_strings = config[CONF_SUPPORTED_FEATURES] self._supported_features = strings_to_services( supported_feature_strings, STRING_TO_SERVICE @@ -219,11 +204,6 @@ def state_message_received(msg): self.hass, self._sub_state, topics ) - @property - def name(self): - """Return the name of the vacuum.""" - return self._name - @property def state(self): """Return state of vacuum.""" From b162c45e0ad4c376c12bb5a132940460d618035c Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Thu, 11 Mar 2021 07:49:10 -0500 Subject: [PATCH 1186/1818] Cover Tilt Position Bugfix (#47682) * Report tilt position properly when inverting using tilt_max < tilt_min * Add warning per review comment * Add test for inverted tilt position configuration * Separate non-numeric and out of range warnings per review comment * Fix out of range message and add tests for not numeric and out of range messages --- homeassistant/components/mqtt/cover.py | 15 +++- tests/components/mqtt/test_cover.py | 106 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 5c7b0c10cf6eee..7f810501e1a432 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -267,15 +267,26 @@ def tilt_message_received(msg): payload ) - if payload.isnumeric() and ( + if not payload.isnumeric(): + _LOGGER.warning("Payload '%s' is not numeric", payload) + elif ( self._config[CONF_TILT_MIN] <= int(payload) <= self._config[CONF_TILT_MAX] + or self._config[CONF_TILT_MAX] + <= int(payload) + <= self._config[CONF_TILT_MIN] ): - level = self.find_percentage_in_range(float(payload)) self._tilt_value = level self.async_write_ha_state() + else: + _LOGGER.warning( + "Payload '%s' is out of range, must be between '%s' and '%s' inclusive", + payload, + self._config[CONF_TILT_MIN], + self._config[CONF_TILT_MAX], + ) @callback @log_messages(self.hass, self.entity_id) diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 44144642f40959..d6899d5149ac8b 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1315,6 +1315,112 @@ async def test_tilt_via_topic_altered_range(hass, mqtt_mock): assert current_cover_tilt_position == 50 +async def test_tilt_status_out_of_range_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt out of range warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "60") + + assert ( + "Payload '60' is out of range, must be between '0' and '50' inclusive" + ) in caplog.text + + +async def test_tilt_status_not_numeric_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt not numeric warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "abc") + + assert ("Payload 'abc' is not numeric") in caplog.text + + +async def test_tilt_via_topic_altered_range_inverted(hass, mqtt_mock): + """Test tilt status via MQTT with altered tilt range and inverted tilt position.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 50, + "tilt_max": 0, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "0") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 100 + + async_fire_mqtt_message(hass, "tilt-status-topic", "50") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 0 + + async_fire_mqtt_message(hass, "tilt-status-topic", "25") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 50 + + async def test_tilt_via_topic_template_altered_range(hass, mqtt_mock): """Test tilt status via MQTT and template with altered tilt range.""" assert await async_setup_component( From cf4954feadda7252b8e758efb17f00bc0ecb3c55 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 11 Mar 2021 14:09:21 +0100 Subject: [PATCH 1187/1818] Add Xiaomi Miio light config flow (#47161) Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 4 + homeassistant/components/xiaomi_miio/const.py | 25 +- homeassistant/components/xiaomi_miio/light.py | 363 ++++++++---------- 3 files changed, 191 insertions(+), 201 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 5b67d21b481697..069520ada7db59 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -18,6 +18,7 @@ KEY_COORDINATOR, MODELS_AIR_MONITOR, MODELS_FAN, + MODELS_LIGHT, MODELS_SWITCH, MODELS_VACUUM, ) @@ -28,6 +29,7 @@ GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] +LIGHT_PLATFORMS = ["light"] VACUUM_PLATFORMS = ["vacuum"] AIR_MONITOR_PLATFORMS = ["air_quality", "sensor"] @@ -128,6 +130,8 @@ async def async_setup_device_entry( platforms = SWITCH_PLATFORMS elif model in MODELS_FAN: platforms = FAN_PLATFORMS + elif model in MODELS_LIGHT: + platforms = LIGHT_PLATFORMS for vacuum_model in MODELS_VACUUM: if model.startswith(vacuum_model): platforms = VACUUM_PLATFORMS diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 6bf2e2f69d1fdc..977e390f26bfb9 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -9,7 +9,7 @@ KEY_COORDINATOR = "coordinator" -# Fam Models +# Fan Models MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" MODEL_AIRPURIFIER_V3 = "zhimi.airpurifier.v3" @@ -60,6 +60,18 @@ MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" +# Light Models +MODELS_LIGHT_EYECARE = ["philips.light.sread1"] +MODELS_LIGHT_CEILING = ["philips.light.ceiling", "philips.light.zyceiling"] +MODELS_LIGHT_MOON = ["philips.light.moonlight"] +MODELS_LIGHT_BULB = [ + "philips.light.bulb", + "philips.light.candle", + "philips.light.candle2", + "philips.light.downlight", +] +MODELS_LIGHT_MONO = ["philips.light.mono1"] + # Model lists MODELS_GATEWAY = ["lumi.gateway", "lumi.acpartner"] MODELS_SWITCH = [ @@ -75,6 +87,13 @@ "chuangmi.plug.hmi206", ] MODELS_FAN = MODELS_FAN_MIIO + MODELS_HUMIDIFIER_MIOT + MODELS_PURIFIER_MIOT +MODELS_LIGHT = ( + MODELS_LIGHT_EYECARE + + MODELS_LIGHT_CEILING + + MODELS_LIGHT_MOON + + MODELS_LIGHT_BULB + + MODELS_LIGHT_MONO +) MODELS_VACUUM = ["roborock.vacuum", "rockrobo.vacuum"] MODELS_AIR_MONITOR = [ MODEL_AIRQUALITYMONITOR_V1, @@ -82,7 +101,9 @@ MODEL_AIRQUALITYMONITOR_S1, ] -MODELS_ALL_DEVICES = MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN +MODELS_ALL_DEVICES = ( + MODELS_SWITCH + MODELS_VACUUM + MODELS_AIR_MONITOR + MODELS_FAN + MODELS_LIGHT +) MODELS_ALL = MODELS_ALL_DEVICES + MODELS_GATEWAY # Fan Services diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index b03aa1b0d2a45d..c0590fbb332406 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -6,14 +6,7 @@ import logging from math import ceil -from miio import ( - Ceil, - Device, - DeviceException, - PhilipsBulb, - PhilipsEyecare, - PhilipsMoonlight, -) +from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight from miio.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, @@ -32,15 +25,23 @@ SUPPORT_COLOR_TEMP, LightEntity, ) +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.util import color, dt from .const import ( + CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, + CONF_MODEL, DOMAIN, + MODELS_LIGHT, + MODELS_LIGHT_BULB, + MODELS_LIGHT_CEILING, + MODELS_LIGHT_EYECARE, + MODELS_LIGHT_MONO, + MODELS_LIGHT_MOON, SERVICE_EYECARE_MODE_OFF, SERVICE_EYECARE_MODE_ON, SERVICE_NIGHT_LIGHT_MODE_OFF, @@ -50,32 +51,19 @@ SERVICE_SET_DELAYED_TURN_OFF, SERVICE_SET_SCENE, ) +from .device import XiaomiMiioEntity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Philips Light" DATA_KEY = "light.xiaomi_miio" -CONF_MODEL = "model" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MODEL): vol.In( - [ - "philips.light.sread1", - "philips.light.ceiling", - "philips.light.zyceiling", - "philips.light.moonlight", - "philips.light.bulb", - "philips.light.candle", - "philips.light.candle2", - "philips.light.mono1", - "philips.light.downlight", - ] - ), + vol.Optional(CONF_MODEL): vol.In(MODELS_LIGHT), } ) @@ -87,7 +75,6 @@ DELAYED_TURN_OFF_MAX_DEVIATION_MINUTES = 1 SUCCESS = ["ok"] -ATTR_MODEL = "model" ATTR_SCENE = "scene" ATTR_DELAYED_TURN_OFF = "delayed_turn_off" ATTR_TIME_PERIOD = "time_period" @@ -100,8 +87,8 @@ ATTR_SLEEP_ASSISTANT = "sleep_assistant" ATTR_SLEEP_OFF_TIME = "sleep_off_time" ATTR_TOTAL_ASSISTANT_SLEEP_TIME = "total_assistant_sleep_time" -ATTR_BRAND_SLEEP = "brand_sleep" -ATTR_BRAND = "brand" +ATTR_BAND_SLEEP = "band_sleep" +ATTR_BAND = "band" XIAOMI_MIIO_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) @@ -131,6 +118,21 @@ } +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Import Miio configuration from YAML.""" + _LOGGER.warning( + "Loading Xiaomi Miio Light via platform setup is deprecated. " + "Please remove it from your configuration" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi light from a config entry.""" entities = [] @@ -147,147 +149,110 @@ async def async_setup_entry(hass, config_entry, async_add_entities): XiaomiGatewayLight(gateway, config_entry.title, config_entry.unique_id) ) - async_add_entities(entities, update_before_add=True) - + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: + if DATA_KEY not in hass.data: + hass.data[DATA_KEY] = {} -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the light from config.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {} + host = config_entry.data[CONF_HOST] + token = config_entry.data[CONF_TOKEN] + name = config_entry.title + model = config_entry.data[CONF_MODEL] + unique_id = config_entry.unique_id - host = config[CONF_HOST] - token = config[CONF_TOKEN] - name = config[CONF_NAME] - model = config.get(CONF_MODEL) + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) - _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) + if model in MODELS_LIGHT_EYECARE: + light = PhilipsEyecare(host, token) + entity = XiaomiPhilipsEyecareLamp(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity - devices = [] - unique_id = None - - if model is None: - try: - miio_device = Device(host, token) - device_info = await hass.async_add_executor_job(miio_device.info) - model = device_info.model - unique_id = f"{model}-{device_info.mac_address}" - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, + entities.append( + XiaomiPhilipsEyecareLampAmbientLight( + name, light, config_entry, unique_id + ) ) - except DeviceException as ex: - raise PlatformNotReady from ex - - if model == "philips.light.sread1": - light = PhilipsEyecare(host, token) - primary_device = XiaomiPhilipsEyecareLamp(name, light, model, unique_id) - devices.append(primary_device) - hass.data[DATA_KEY][host] = primary_device - - secondary_device = XiaomiPhilipsEyecareLampAmbientLight( - name, light, model, unique_id - ) - devices.append(secondary_device) - # The ambient light doesn't expose additional services. - # A hass.data[DATA_KEY] entry isn't needed. - elif model in ["philips.light.ceiling", "philips.light.zyceiling"]: - light = Ceil(host, token) - device = XiaomiPhilipsCeilingLamp(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model == "philips.light.moonlight": - light = PhilipsMoonlight(host, token) - device = XiaomiPhilipsMoonlightLamp(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model in [ - "philips.light.bulb", - "philips.light.candle", - "philips.light.candle2", - "philips.light.downlight", - ]: - light = PhilipsBulb(host, token) - device = XiaomiPhilipsBulb(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - elif model == "philips.light.mono1": - light = PhilipsBulb(host, token) - device = XiaomiPhilipsGenericLight(name, light, model, unique_id) - devices.append(device) - hass.data[DATA_KEY][host] = device - else: - _LOGGER.error( - "Unsupported device found! Please create an issue at " - "https://github.com/syssi/philipslight/issues " - "and provide the following data: %s", - model, - ) - return False - - async_add_entities(devices, update_before_add=True) - - async def async_service_handler(service): - """Map services to methods on Xiaomi Philips Lights.""" - method = SERVICE_TO_METHOD.get(service.service) - params = { - key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID - } - entity_ids = service.data.get(ATTR_ENTITY_ID) - if entity_ids: - target_devices = [ - dev - for dev in hass.data[DATA_KEY].values() - if dev.entity_id in entity_ids - ] + # The ambient light doesn't expose additional services. + # A hass.data[DATA_KEY] entry isn't needed. + elif model in MODELS_LIGHT_CEILING: + light = Ceil(host, token) + entity = XiaomiPhilipsCeilingLamp(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + elif model in MODELS_LIGHT_MOON: + light = PhilipsMoonlight(host, token) + entity = XiaomiPhilipsMoonlightLamp(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + elif model in MODELS_LIGHT_BULB: + light = PhilipsBulb(host, token) + entity = XiaomiPhilipsBulb(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity + elif model in MODELS_LIGHT_MONO: + light = PhilipsBulb(host, token) + entity = XiaomiPhilipsGenericLight(name, light, config_entry, unique_id) + entities.append(entity) + hass.data[DATA_KEY][host] = entity else: - target_devices = hass.data[DATA_KEY].values() - - update_tasks = [] - for target_device in target_devices: - if not hasattr(target_device, method["method"]): - continue - await getattr(target_device, method["method"])(**params) - update_tasks.append(target_device.async_update_ha_state(True)) + _LOGGER.error( + "Unsupported device found! Please create an issue at " + "https://github.com/syssi/philipslight/issues " + "and provide the following data: %s", + model, + ) + return - if update_tasks: - await asyncio.wait(update_tasks) + async def async_service_handler(service): + """Map services to methods on Xiaomi Philips Lights.""" + method = SERVICE_TO_METHOD.get(service.service) + params = { + key: value + for key, value in service.data.items() + if key != ATTR_ENTITY_ID + } + entity_ids = service.data.get(ATTR_ENTITY_ID) + if entity_ids: + target_devices = [ + dev + for dev in hass.data[DATA_KEY].values() + if dev.entity_id in entity_ids + ] + else: + target_devices = hass.data[DATA_KEY].values() + + update_tasks = [] + for target_device in target_devices: + if not hasattr(target_device, method["method"]): + continue + await getattr(target_device, method["method"])(**params) + update_tasks.append(target_device.async_update_ha_state(True)) + + if update_tasks: + await asyncio.wait(update_tasks) + + for xiaomi_miio_service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[xiaomi_miio_service].get( + "schema", XIAOMI_MIIO_SERVICE_SCHEMA + ) + hass.services.async_register( + DOMAIN, xiaomi_miio_service, async_service_handler, schema=schema + ) - for xiaomi_miio_service in SERVICE_TO_METHOD: - schema = SERVICE_TO_METHOD[xiaomi_miio_service].get( - "schema", XIAOMI_MIIO_SERVICE_SCHEMA - ) - hass.services.async_register( - DOMAIN, xiaomi_miio_service, async_service_handler, schema=schema - ) + async_add_entities(entities, update_before_add=True) -class XiaomiPhilipsAbstractLight(LightEntity): +class XiaomiPhilipsAbstractLight(XiaomiMiioEntity, LightEntity): """Representation of a Abstract Xiaomi Philips Light.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - self._name = name - self._light = light - self._model = model - self._unique_id = unique_id + super().__init__(name, device, entry, unique_id) self._brightness = None - self._available = False self._state = None - self._state_attrs = {ATTR_MODEL: self._model} - - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of the device if any.""" - return self._name + self._state_attrs = {} @property def available(self): @@ -341,23 +306,23 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness failed: %s", - self._light.set_brightness, + self._device.set_brightness, percent_brightness, ) if result: self._brightness = brightness else: - await self._try_command("Turning the light on failed.", self._light.on) + await self._try_command("Turning the light on failed.", self._device.on) async def async_turn_off(self, **kwargs): """Turn the light off.""" - await self._try_command("Turning the light off failed.", self._light.off) + await self._try_command("Turning the light off failed.", self._device.off) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -374,16 +339,16 @@ async def async_update(self): class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight): """Representation of a Generic Xiaomi Philips Light.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._state_attrs.update({ATTR_SCENE: None, ATTR_DELAYED_TURN_OFF: None}) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -409,14 +374,14 @@ async def async_update(self): async def async_set_scene(self, scene: int = 1): """Set the fixed scene.""" await self._try_command( - "Setting a fixed scene failed.", self._light.set_scene, scene + "Setting a fixed scene failed.", self._device.set_scene, scene ) async def async_set_delayed_turn_off(self, time_period: timedelta): """Set delayed turn off.""" await self._try_command( "Setting the turn off delay failed.", - self._light.delay_off, + self._device.delay_off, time_period.total_seconds(), ) @@ -445,9 +410,9 @@ def delayed_turn_off_timestamp( class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight): """Representation of a Xiaomi Philips Bulb.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._color_temp = None @@ -495,7 +460,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness and color temperature failed: %s bri, %s cct", - self._light.set_brightness_and_color_temperature, + self._device.set_brightness_and_color_temperature, percent_brightness, percent_color_temp, ) @@ -513,7 +478,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting color temperature failed: %s cct", - self._light.set_color_temperature, + self._device.set_color_temperature, percent_color_temp, ) @@ -528,7 +493,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness failed: %s", - self._light.set_brightness, + self._device.set_brightness, percent_brightness, ) @@ -536,12 +501,12 @@ async def async_turn_on(self, **kwargs): self._brightness = brightness else: - await self._try_command("Turning the light on failed.", self._light.on) + await self._try_command("Turning the light on failed.", self._device.on) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -579,9 +544,9 @@ def translate(value, left_min, left_max, right_min, right_max): class XiaomiPhilipsCeilingLamp(XiaomiPhilipsBulb): """Representation of a Xiaomi Philips Ceiling Lamp.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._state_attrs.update( {ATTR_NIGHT_LIGHT_MODE: None, ATTR_AUTOMATIC_COLOR_TEMPERATURE: None} @@ -600,7 +565,7 @@ def max_mireds(self): async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -635,9 +600,9 @@ async def async_update(self): class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight): """Representation of a Xiaomi Philips Eyecare Lamp 2.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._state_attrs.update( {ATTR_REMINDER: None, ATTR_NIGHT_LIGHT_MODE: None, ATTR_EYECARE_MODE: None} @@ -646,7 +611,7 @@ def __init__(self, name, light, model, unique_id): async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -679,46 +644,46 @@ async def async_set_delayed_turn_off(self, time_period: timedelta): """Set delayed turn off.""" await self._try_command( "Setting the turn off delay failed.", - self._light.delay_off, + self._device.delay_off, round(time_period.total_seconds() / 60), ) async def async_reminder_on(self): """Enable the eye fatigue notification.""" await self._try_command( - "Turning on the reminder failed.", self._light.reminder_on + "Turning on the reminder failed.", self._device.reminder_on ) async def async_reminder_off(self): """Disable the eye fatigue notification.""" await self._try_command( - "Turning off the reminder failed.", self._light.reminder_off + "Turning off the reminder failed.", self._device.reminder_off ) async def async_night_light_mode_on(self): """Turn the smart night light mode on.""" await self._try_command( "Turning on the smart night light mode failed.", - self._light.smart_night_light_on, + self._device.smart_night_light_on, ) async def async_night_light_mode_off(self): """Turn the smart night light mode off.""" await self._try_command( "Turning off the smart night light mode failed.", - self._light.smart_night_light_off, + self._device.smart_night_light_off, ) async def async_eyecare_mode_on(self): """Turn the eyecare mode on.""" await self._try_command( - "Turning on the eyecare mode failed.", self._light.eyecare_on + "Turning on the eyecare mode failed.", self._device.eyecare_on ) async def async_eyecare_mode_off(self): """Turn the eyecare mode off.""" await self._try_command( - "Turning off the eyecare mode failed.", self._light.eyecare_off + "Turning off the eyecare mode failed.", self._device.eyecare_off ) @staticmethod @@ -748,12 +713,12 @@ def delayed_turn_off_timestamp( class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight): """Representation of a Xiaomi Philips Eyecare Lamp Ambient Light.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" name = f"{name} Ambient Light" if unique_id is not None: unique_id = f"{unique_id}-ambient" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) async def async_turn_on(self, **kwargs): """Turn the light on.""" @@ -769,7 +734,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness of the ambient failed: %s", - self._light.set_ambient_brightness, + self._device.set_ambient_brightness, percent_brightness, ) @@ -777,19 +742,19 @@ async def async_turn_on(self, **kwargs): self._brightness = brightness else: await self._try_command( - "Turning the ambient light on failed.", self._light.ambient_on + "Turning the ambient light on failed.", self._device.ambient_on ) async def async_turn_off(self, **kwargs): """Turn the light off.""" await self._try_command( - "Turning the ambient light off failed.", self._light.ambient_off + "Turning the ambient light off failed.", self._device.ambient_off ) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -806,9 +771,9 @@ async def async_update(self): class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb): """Representation of a Xiaomi Philips Zhirui Bedside Lamp.""" - def __init__(self, name, light, model, unique_id): + def __init__(self, name, device, entry, unique_id): """Initialize the light device.""" - super().__init__(name, light, model, unique_id) + super().__init__(name, device, entry, unique_id) self._hs_color = None self._state_attrs.pop(ATTR_DELAYED_TURN_OFF) @@ -817,8 +782,8 @@ def __init__(self, name, light, model, unique_id): ATTR_SLEEP_ASSISTANT: None, ATTR_SLEEP_OFF_TIME: None, ATTR_TOTAL_ASSISTANT_SLEEP_TIME: None, - ATTR_BRAND_SLEEP: None, - ATTR_BRAND: None, + ATTR_BAND_SLEEP: None, + ATTR_BAND: None, } ) @@ -868,7 +833,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness and color failed: %s bri, %s color", - self._light.set_brightness_and_rgb, + self._device.set_brightness_and_rgb, percent_brightness, rgb, ) @@ -889,7 +854,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness and color temperature failed: %s bri, %s cct", - self._light.set_brightness_and_color_temperature, + self._device.set_brightness_and_color_temperature, percent_brightness, percent_color_temp, ) @@ -902,7 +867,7 @@ async def async_turn_on(self, **kwargs): _LOGGER.debug("Setting color: %s", rgb) result = await self._try_command( - "Setting color failed: %s", self._light.set_rgb, rgb + "Setting color failed: %s", self._device.set_rgb, rgb ) if result: @@ -917,7 +882,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting color temperature failed: %s cct", - self._light.set_color_temperature, + self._device.set_color_temperature, percent_color_temp, ) @@ -932,7 +897,7 @@ async def async_turn_on(self, **kwargs): result = await self._try_command( "Setting brightness failed: %s", - self._light.set_brightness, + self._device.set_brightness, percent_brightness, ) @@ -940,12 +905,12 @@ async def async_turn_on(self, **kwargs): self._brightness = brightness else: - await self._try_command("Turning the light on failed.", self._light.on) + await self._try_command("Turning the light on failed.", self._device.on) async def async_update(self): """Fetch state from the device.""" try: - state = await self.hass.async_add_executor_job(self._light.status) + state = await self.hass.async_add_executor_job(self._device.status) except DeviceException as ex: if self._available: self._available = False @@ -968,8 +933,8 @@ async def async_update(self): ATTR_SLEEP_ASSISTANT: state.sleep_assistant, ATTR_SLEEP_OFF_TIME: state.sleep_off_time, ATTR_TOTAL_ASSISTANT_SLEEP_TIME: state.total_assistant_sleep_time, - ATTR_BRAND_SLEEP: state.brand_sleep, - ATTR_BRAND: state.brand, + ATTR_BAND_SLEEP: state.brand_sleep, + ATTR_BAND: state.brand, } ) From f92b75cbb210d68d1fb1d17514fe94a0d8737652 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 11 Mar 2021 07:22:35 -0700 Subject: [PATCH 1188/1818] Write SimpliSafe alarm control panel state after arming/disarming (#47649) * Write SimpliSafe alarm control panel state after arming/disarming * Include locks --- homeassistant/components/simplisafe/alarm_control_panel.py | 3 +++ homeassistant/components/simplisafe/lock.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 7634f1cce86f8c..8f394890ad4eec 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -163,6 +163,7 @@ async def async_alarm_disarm(self, code=None): return self._state = STATE_ALARM_DISARMED + self.async_write_ha_state() async def async_alarm_arm_home(self, code=None): """Send arm home command.""" @@ -178,6 +179,7 @@ async def async_alarm_arm_home(self, code=None): return self._state = STATE_ALARM_ARMED_HOME + self.async_write_ha_state() async def async_alarm_arm_away(self, code=None): """Send arm away command.""" @@ -193,6 +195,7 @@ async def async_alarm_arm_away(self, code=None): return self._state = STATE_ALARM_ARMING + self.async_write_ha_state() @callback def async_update_from_rest_api(self): diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 08ffb82d24fcae..a4d823efe386a6 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -55,6 +55,9 @@ async def async_lock(self, **kwargs): LOGGER.error('Error while locking "%s": %s', self._lock.name, err) return + self._is_locked = True + self.async_write_ha_state() + async def async_unlock(self, **kwargs): """Unlock the lock.""" try: @@ -63,6 +66,9 @@ async def async_unlock(self, **kwargs): LOGGER.error('Error while unlocking "%s": %s', self._lock.name, err) return + self._is_locked = False + self.async_write_ha_state() + @callback def async_update_from_rest_api(self): """Update the entity with the provided REST API data.""" From 6c084ae6ce567ec9f21125e529675aeeda61626e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 16:51:03 +0100 Subject: [PATCH 1189/1818] Update integrations a-e to override extra_state_attributes() (#47756) --- homeassistant/components/abode/__init__.py | 4 ++-- .../components/abode/alarm_control_panel.py | 2 +- homeassistant/components/accuweather/sensor.py | 2 +- .../components/aemet/abstract_aemet_sensor.py | 2 +- homeassistant/components/aftership/sensor.py | 2 +- homeassistant/components/agent_dvr/camera.py | 2 +- homeassistant/components/airly/air_quality.py | 2 +- homeassistant/components/airly/sensor.py | 2 +- homeassistant/components/airnow/sensor.py | 2 +- homeassistant/components/airvisual/__init__.py | 2 +- .../components/alarmdecoder/alarm_control_panel.py | 2 +- .../components/alarmdecoder/binary_sensor.py | 2 +- homeassistant/components/alpha_vantage/sensor.py | 4 ++-- homeassistant/components/amcrest/camera.py | 2 +- homeassistant/components/amcrest/sensor.py | 2 +- .../components/android_ip_webcam/__init__.py | 2 +- homeassistant/components/androidtv/media_player.py | 2 +- .../components/arlo/alarm_control_panel.py | 2 +- homeassistant/components/arlo/camera.py | 2 +- homeassistant/components/arlo/sensor.py | 2 +- homeassistant/components/asuswrt/device_tracker.py | 2 +- homeassistant/components/asuswrt/sensor.py | 2 +- homeassistant/components/atome/sensor.py | 2 +- homeassistant/components/august/lock.py | 2 +- homeassistant/components/august/sensor.py | 2 +- homeassistant/components/aurora/__init__.py | 2 +- homeassistant/components/automation/__init__.py | 2 +- homeassistant/components/awair/sensor.py | 2 +- homeassistant/components/azure_devops/sensor.py | 2 +- homeassistant/components/bayesian/binary_sensor.py | 2 +- homeassistant/components/bbox/sensor.py | 4 ++-- homeassistant/components/bitcoin/sensor.py | 2 +- .../components/blink/alarm_control_panel.py | 2 +- homeassistant/components/blink/camera.py | 2 +- homeassistant/components/blockchain/sensor.py | 2 +- homeassistant/components/bluesound/media_player.py | 2 +- .../components/bmw_connected_drive/__init__.py | 2 +- .../bmw_connected_drive/binary_sensor.py | 2 +- .../components/bmw_connected_drive/lock.py | 2 +- homeassistant/components/brother/sensor.py | 2 +- .../components/brottsplatskartan/sensor.py | 2 +- homeassistant/components/brunt/cover.py | 2 +- homeassistant/components/buienradar/sensor.py | 2 +- homeassistant/components/caldav/calendar.py | 2 +- .../components/canary/alarm_control_panel.py | 2 +- homeassistant/components/canary/sensor.py | 2 +- homeassistant/components/cert_expiry/sensor.py | 2 +- homeassistant/components/citybikes/sensor.py | 2 +- homeassistant/components/co2signal/sensor.py | 2 +- homeassistant/components/coinbase/sensor.py | 4 ++-- .../components/comed_hourly_pricing/sensor.py | 2 +- homeassistant/components/command_line/sensor.py | 2 +- homeassistant/components/coronavirus/sensor.py | 2 +- homeassistant/components/cpuspeed/sensor.py | 2 +- homeassistant/components/cups/sensor.py | 6 +++--- homeassistant/components/currencylayer/sensor.py | 2 +- homeassistant/components/darksky/sensor.py | 4 ++-- homeassistant/components/deconz/binary_sensor.py | 2 +- homeassistant/components/deconz/climate.py | 2 +- homeassistant/components/deconz/light.py | 6 +++--- homeassistant/components/deconz/sensor.py | 4 ++-- homeassistant/components/delijn/sensor.py | 2 +- homeassistant/components/demo/remote.py | 2 +- homeassistant/components/demo/sensor.py | 2 +- homeassistant/components/demo/vacuum.py | 4 ++-- homeassistant/components/denonavr/media_player.py | 2 +- homeassistant/components/derivative/sensor.py | 2 +- homeassistant/components/deutsche_bahn/sensor.py | 2 +- homeassistant/components/device_tracker/legacy.py | 2 +- .../components/digital_ocean/binary_sensor.py | 2 +- homeassistant/components/digital_ocean/switch.py | 2 +- homeassistant/components/directv/media_player.py | 2 +- homeassistant/components/discogs/sensor.py | 2 +- homeassistant/components/dlink/switch.py | 2 +- homeassistant/components/doods/image_processing.py | 2 +- homeassistant/components/dovado/sensor.py | 2 +- .../components/dublin_bus_transport/sensor.py | 2 +- .../components/dwd_weather_warnings/sensor.py | 2 +- homeassistant/components/dyson/air_quality.py | 2 +- homeassistant/components/dyson/fan.py | 6 +++--- homeassistant/components/dyson/vacuum.py | 2 +- homeassistant/components/eafm/sensor.py | 2 +- homeassistant/components/ebusd/sensor.py | 2 +- homeassistant/components/ecobee/climate.py | 2 +- homeassistant/components/ecovacs/vacuum.py | 2 +- homeassistant/components/edl21/sensor.py | 2 +- homeassistant/components/eight_sleep/sensor.py | 4 ++-- homeassistant/components/elkm1/__init__.py | 2 +- .../components/elkm1/alarm_control_panel.py | 2 +- homeassistant/components/elkm1/sensor.py | 8 ++++---- homeassistant/components/elv/switch.py | 2 +- homeassistant/components/emoncms/sensor.py | 2 +- homeassistant/components/enigma2/media_player.py | 2 +- homeassistant/components/enphase_envoy/sensor.py | 2 +- .../components/entur_public_transport/sensor.py | 2 +- .../components/environment_canada/camera.py | 2 +- .../components/environment_canada/sensor.py | 2 +- .../components/envisalink/binary_sensor.py | 2 +- homeassistant/components/envisalink/sensor.py | 2 +- homeassistant/components/epson/media_player.py | 2 +- homeassistant/components/eq3btsmart/climate.py | 2 +- homeassistant/components/etherscan/sensor.py | 2 +- homeassistant/components/evohome/__init__.py | 2 +- homeassistant/components/ezviz/camera.py | 2 +- tests/components/arlo/test_sensor.py | 4 ++-- tests/components/ecobee/test_climate.py | 14 +++++++------- 106 files changed, 130 insertions(+), 130 deletions(-) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 47e03eea44c6b9..c1c89951c3f8f5 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -363,7 +363,7 @@ def name(self): return self._device.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, @@ -411,7 +411,7 @@ def name(self): return self._automation.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, "type": "CUE automation"} diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index c508d0f02408d0..6d0c030e3e1e04 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -69,7 +69,7 @@ def alarm_arm_away(self, code=None): self._device.set_away() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 90058e254dcd56..aa7f604e27f801 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -141,7 +141,7 @@ def unit_of_measurement(self): return SENSOR_TYPES[self.kind][self._unit_system] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.forecast_day is not None: if self.kind in ["WindDay", "WindNight", "WindGustDay", "WindGustNight"]: diff --git a/homeassistant/components/aemet/abstract_aemet_sensor.py b/homeassistant/components/aemet/abstract_aemet_sensor.py index 6b7c3c69fee30a..7699a5f99a4998 100644 --- a/homeassistant/components/aemet/abstract_aemet_sensor.py +++ b/homeassistant/components/aemet/abstract_aemet_sensor.py @@ -52,6 +52,6 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 2d9021f80095c8..754b3ce72eb464 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -134,7 +134,7 @@ def unit_of_measurement(self): return "packages" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 571b5239de7ce5..7904f98216b269 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -108,7 +108,7 @@ async def async_update(self): self._removed = True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Agent DVR camera state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/airly/air_quality.py b/homeassistant/components/airly/air_quality.py index 4c4c239a84b5c4..f89a804ab3bc11 100644 --- a/homeassistant/components/airly/air_quality.py +++ b/homeassistant/components/airly/air_quality.py @@ -117,7 +117,7 @@ def device_info(self): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { LABEL_AQI_DESCRIPTION: self.coordinator.data[ATTR_API_CAQI_DESCRIPTION], diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index 789dbbb4657052..dae167559e0ff5 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -102,7 +102,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index 4488098701f135..ea2ff9856fb1d9 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -84,7 +84,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.kind == ATTR_API_AQI: self._attrs[SENSOR_AQI_ATTR_DESCR] = self.coordinator.data[ diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index e900bfa65de930..6254d533a0fbcd 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -372,7 +372,7 @@ def __init__(self, coordinator): self._unit = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index 5e3118b1d3df25..9cab2afa43cbcb 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -163,7 +163,7 @@ def code_arm_required(self): return self._code_arm_required @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { "ac_power": self._ac_power, diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index 55bf13d7fef0b5..4cc3bb6b5cf8bd 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -118,7 +118,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {CONF_ZONE_NUMBER: self._zone_number} if self._rfid and self._rfstate is not None: diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index 0d0aec47915e9f..cd161b1870c10e 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -133,7 +133,7 @@ def state(self): return self.values["1. open"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.values is not None: return { @@ -193,7 +193,7 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.values is not None: return { diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index a8aabc233d179f..046da7b270d838 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -266,7 +266,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Amcrest-specific camera state attributes.""" attr = {} if self._audio_enabled is not None: diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 44ebcfcdb95643..15a610b23b4ec9 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -66,7 +66,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index 905d0262862720..54a281feacd1fb 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -321,7 +321,7 @@ def available(self): return self._ipcam.available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state_attr = {ATTR_HOST: self._host} if self._ipcam.status_data is None: diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 4d17b4ecdad4d4..b2a8ceffc9f1df 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -469,7 +469,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Provide the last ADB command's response and the device's HDMI input as attributes.""" return { "adb_response": self._adb_response, diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py index 47328d5cbc2234..dd899cbd04ff27 100644 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ b/homeassistant/components/arlo/alarm_control_panel.py @@ -136,7 +136,7 @@ def name(self): return self._base_station.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/arlo/camera.py b/homeassistant/components/arlo/camera.py index 36e321817022ac..c1848661429888 100644 --- a/homeassistant/components/arlo/camera.py +++ b/homeassistant/components/arlo/camera.py @@ -108,7 +108,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { name: value diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index d5d583de22c9c4..1e0c55c5d313cd 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -183,7 +183,7 @@ def update(self): self._state = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attrs = {} diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 385b25755b08f9..f5cb9b934e30ab 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -103,7 +103,7 @@ def icon(self) -> str: return self._icon @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 2751e161ea9102..0cd427d3b6406f 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -159,7 +159,7 @@ def device_class(self) -> str: return self._device_class @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the attributes.""" return {"hostname": self._router.host} diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index 1a8585653fe088..e50f7cd5de6504 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -243,7 +243,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index e16c603d919ea7..4b4ae906190aee 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -105,7 +105,7 @@ def changed_by(self): return self._changed_by @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = {ATTR_BATTERY_LEVEL: self._detail.battery_level} diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 6004a07f605ab3..9dba5bb2766f47 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -166,7 +166,7 @@ def _update_from_data(self): self._entity_picture = lock_activity.operator_thumbnail_url @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = {} diff --git a/homeassistant/components/aurora/__init__.py b/homeassistant/components/aurora/__init__.py index 044b118b030e3b..321b25de7b9944 100644 --- a/homeassistant/components/aurora/__init__.py +++ b/homeassistant/components/aurora/__init__.py @@ -168,7 +168,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"attribution": ATTRIBUTION} diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 50b9fc43bf53a1..24121359ac06b2 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -535,7 +535,7 @@ def log_cb(level, msg, **kwargs): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return automation attributes.""" if self._id is None: return None diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 421fa3d8a26e6e..d685b3ec17b984 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -179,7 +179,7 @@ def unit_of_measurement(self) -> str: return SENSOR_TYPES[self._kind][ATTR_UNIT] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the Awair Index alongside state attributes. The Awair Index is a subjective score ranging from 0-4 (inclusive) that diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 6f259afb9a978c..a26f0c65f9c472 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -92,7 +92,7 @@ def state(self) -> str: return self._state @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" return self._attributes diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 69553e921eba50..6879e278bab03f 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -384,7 +384,7 @@ def device_class(self): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attr_observations_list = [ diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 13c8f5bb03f099..8d6708c6342a17 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -115,7 +115,7 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -167,7 +167,7 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index c748b2f72f94e6..f6f5a24d22ffa9 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -110,7 +110,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index dbcb6d30143a2e..ed2b46acaa1b21 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -62,7 +62,7 @@ def name(self): return f"{DOMAIN} {self._name}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = self.sync.attributes attr["network_info"] = self.data.networks diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index d4282bed606964..a25b978ee7b012 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -60,7 +60,7 @@ def unique_id(self): return self._unique_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera attributes.""" return self._camera.attributes diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py index feb9d582cff0f9..790051dad96367 100644 --- a/homeassistant/components/blockchain/sensor.py +++ b/homeassistant/components/blockchain/sensor.py @@ -75,7 +75,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 0ace6f5679a242..dff45ca68bd701 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -850,7 +850,7 @@ async def async_join(self, master): _LOGGER.error("Master not found %s", master_device) @property - def device_state_attributes(self): + def extra_state_attributes(self): """List members in group.""" attributes = {} if self._group_list: diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 3a653ec252f78f..ebf1fd6f74ea82 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -334,7 +334,7 @@ def device_info(self) -> dict: } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return self._attrs diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index cad5426d548345..bebb55bbde0845 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -109,7 +109,7 @@ def is_on(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" vehicle_state = self._vehicle.state result = self._attrs.copy() diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 0d281e78f14707..97c9be7216b36c 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -52,7 +52,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the lock.""" vehicle_state = self._vehicle.state result = self._attrs.copy() diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index a379d9b415461b..dcbf92fba7df12 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -88,7 +88,7 @@ def device_class(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" remaining_pages = None drum_counter = None diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index 7b2c3e585e309f..b75edcc7f4abd9 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -99,7 +99,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index ceb56ba03fa750..9c539fe51fe1c9 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -131,7 +131,7 @@ def is_closing(self): return self.move_state == 2 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the detailed device state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index 4e35542b581254..ae57ed3c43cf02 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -430,7 +430,7 @@ def entity_picture(self): return self._entity_picture @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.type.startswith(PRECIPITATION_FORECAST): result = {ATTR_ATTRIBUTION: self._attribution} diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index 66b3c974306e8b..62be361df3b5d8 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -123,7 +123,7 @@ def __init__(self, name, calendar, entity_id, days, all_day=False, search=None): self._offset_reached = False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"offset_reached": self._offset_reached} diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index e4dda0f9f33636..d0beece8df2c9b 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -87,7 +87,7 @@ def supported_features(self) -> int: return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"private": self.location.is_private} diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 99dcdf48fceddb..2d5a7885fbf6ab 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -163,7 +163,7 @@ def icon(self): return self._sensor_type[2] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" reading = self.reading diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 0e329b1898f507..582205f9c0de39 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -68,7 +68,7 @@ def icon(self): return "mdi:certificate" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return additional sensor state attributes.""" return { "is_valid": self.coordinator.is_cert_valid, diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index 924ef2fa6b4dc3..2dd60078bd5f93 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -286,7 +286,7 @@ async def async_update(self): break @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._station_data: return { diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index a61615d0e23507..2153a8a7af5bb0 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -91,7 +91,7 @@ def unit_of_measurement(self): return CO2_INTENSITY_UNIT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index f191bb778f4969..1d239ba457ed1a 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -71,7 +71,7 @@ def icon(self): return CURRENCY_ICONS.get(self._unit_of_measurement, DEFAULT_COIN_ICON) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, @@ -120,7 +120,7 @@ def icon(self): return CURRENCY_ICONS.get(self.currency, DEFAULT_COIN_ICON) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py index 90830d5223672c..c1a784f619ca2f 100644 --- a/homeassistant/components/comed_hourly_pricing/sensor.py +++ b/homeassistant/components/comed_hourly_pricing/sensor.py @@ -97,7 +97,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index 3e6f384cca3e0f..fc99befb821208 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -95,7 +95,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index 7f0e0c230e6cfe..acfc5569f342ac 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -73,6 +73,6 @@ def unit_of_measurement(self): return "people" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index df1a224bccefc8..f21513f616e6ee 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -54,7 +54,7 @@ def unit_of_measurement(self): return FREQUENCY_GIGAHERTZ @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.info is not None: attrs = { diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 72d2aa62ae079d..54e674b1811f18 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -131,7 +131,7 @@ def icon(self): return ICON_PRINTER @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._printer is None: return None @@ -193,7 +193,7 @@ def state(self): return PRINTER_STATES.get(key, key) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._attributes is None: return None @@ -271,7 +271,7 @@ def unit_of_measurement(self): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._attributes is None: return None diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index f2cb29515b0381..66b26eb0692edc 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -86,7 +86,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 0bafdbb3d819ae..7e6463dc08e288 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -620,7 +620,7 @@ def device_class(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -739,7 +739,7 @@ def icon(self): return "mdi:alert-circle-outline" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._alerts diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 616206949edd32..99f559eec3d837 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -92,7 +92,7 @@ def device_class(self): return DEVICE_CLASS.get(type(self._device)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attr = {} diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 44111fbbb1e4bb..dad3f3adf67667 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -244,7 +244,7 @@ def temperature_unit(self): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the thermostat.""" attr = {} diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 2da435c5530828..f7ae45781acc85 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -223,7 +223,7 @@ async def async_turn_off(self, **kwargs): await self._device.async_set_state(data) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"is_deconz_group": self._device.type == "LightGroup"} @@ -275,9 +275,9 @@ def device_info(self): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = dict(super().device_state_attributes) + attributes = dict(super().extra_state_attributes) attributes["all_on"] = self._device.all_on return attributes diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index e3a7e6a001a071..b08ec0d091b289 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -155,7 +155,7 @@ def unit_of_measurement(self): return UNIT_OF_MEASUREMENT.get(type(self._device)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attr = {} @@ -235,7 +235,7 @@ def unit_of_measurement(self): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the battery.""" attr = {} diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 0058816d3185d9..44b70c4e7a86d0 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -126,6 +126,6 @@ def icon(self): return "mdi:bus" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py index 9d12621fef1341..98e949f38c33a7 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -49,7 +49,7 @@ def is_on(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" if self._last_command_sent is not None: return {"last_command_sent": self._last_command_sent} diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 99aadf356d7882..49930a35377669 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -96,7 +96,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._battery: return {ATTR_BATTERY_LEVEL: self._battery} diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index ebb6b57ce14ec5..39413a1b9f7e9a 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -140,7 +140,7 @@ def battery_level(self): return max(0, min(100, self._battery_level)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return {ATTR_CLEANED_AREA: round(self._cleaned_area, 2)} @@ -288,7 +288,7 @@ def fan_speed_list(self): return FAN_SPEEDS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return {ATTR_CLEANED_AREA: round(self._cleaned_area, 2)} diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 73fe0f2152d464..c1fbb15a2635c1 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -306,7 +306,7 @@ def media_episode(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if ( self._sound_mode_raw is not None diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index 7c2cc444a5a25f..ddbe1d19e31239 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -211,7 +211,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_SOURCE_ID: self._sensor_source_id} diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index b3e4cd432ac0a7..1de6609c756c5e 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -65,7 +65,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" connections = self.data.connections[0] if len(self.data.connections) > 1: diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index b7583d80f82800..e947ae7b828c04 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -675,7 +675,7 @@ def state_attributes(self): return attributes @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return self._attributes diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index eb1345df45c011..9a9f82c36d2d51 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -79,7 +79,7 @@ def device_class(self): return DEVICE_CLASS_MOVING @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Digital Ocean droplet.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index 811b844e35cdf5..0678b9ab1a157e 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -71,7 +71,7 @@ def is_on(self): return self.data.status == "active" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Digital Ocean droplet.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index dfd88ca885b5ff..ee290ba5f2c4d6 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -124,7 +124,7 @@ async def async_update(self): self._assumed_state = self._is_recorded @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if self._is_standby: return {} diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py index 4d78b540a0c7b0..6f45a4ab959874 100644 --- a/homeassistant/components/discogs/sensor.py +++ b/homeassistant/components/discogs/sensor.py @@ -121,7 +121,7 @@ def unit_of_measurement(self): return SENSORS[self._type]["unit_of_measurement"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes of the sensor.""" if self._state is None or self._attrs is None: return None diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py index c173c879ad1808..432bc7ec0b366d 100644 --- a/homeassistant/components/dlink/switch.py +++ b/homeassistant/components/dlink/switch.py @@ -71,7 +71,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" try: ui_temp = self.units.temperature(int(self.data.temperature), TEMP_CELSIUS) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index fb2b6daecdab84..3e41c1871bfe1d 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -221,7 +221,7 @@ def state(self): return self._total_matches @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_MATCHES: self._matches, diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 46ff402788ec22..8c8dffbb8b6ef8 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -105,6 +105,6 @@ def unit_of_measurement(self): return SENSORS[self._sensor][2] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {k: v for k, v in self._data.state.items() if k not in ["date", "time"]} diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 96e485a94e6721..5b0cbaf9f189e6 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -87,7 +87,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._times is not None: next_up = "None" diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index 79beebb005d121..af91e279600cb9 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -119,7 +119,7 @@ def state(self): return self._api.api.expected_warning_level @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the DWD-Weather-Warnings.""" data = { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/dyson/air_quality.py b/homeassistant/components/dyson/air_quality.py index 3bf4f2bb34c9c4..48b66fe7683586 100644 --- a/homeassistant/components/dyson/air_quality.py +++ b/homeassistant/components/dyson/air_quality.py @@ -88,6 +88,6 @@ def volatile_organic_compounds(self): return int(self._device.environmental_state.volatile_organic_compounds) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {ATTR_VOC: self.volatile_organic_compounds} diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index a8e737bb48b7e9..5b69092ac5d907 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -200,7 +200,7 @@ def supported_features(self) -> int: return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return optional state attributes.""" return { ATTR_NIGHT_MODE: self.night_mode, @@ -455,10 +455,10 @@ def carbon_filter(self): return int(self._device.state.carbon_filter_state) @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return optional state attributes.""" return { - **super().device_state_attributes, + **super().extra_state_attributes, ATTR_ANGLE_LOW: self.angle_low, ATTR_ANGLE_HIGH: self.angle_high, ATTR_FLOW_DIRECTION_FRONT: self.flow_direction_front, diff --git a/homeassistant/components/dyson/vacuum.py b/homeassistant/components/dyson/vacuum.py index 466b409c342ead..f4035d33cf3af5 100644 --- a/homeassistant/components/dyson/vacuum.py +++ b/homeassistant/components/dyson/vacuum.py @@ -95,7 +95,7 @@ def fan_speed_list(self): return ["Quiet", "Max"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the specific state attributes of this vacuum cleaner.""" return {ATTR_POSITION: str(self._device.state.position)} diff --git a/homeassistant/components/eafm/sensor.py b/homeassistant/components/eafm/sensor.py index 746f6f34abcf7d..9e2d4e5cef9d36 100644 --- a/homeassistant/components/eafm/sensor.py +++ b/homeassistant/components/eafm/sensor.py @@ -156,7 +156,7 @@ def unit_of_measurement(self): return UNIT_MAPPING.get(measure["unit"], measure["unitName"]) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor specific state attributes.""" return {ATTR_ATTRIBUTION: self.attribution} diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index badb94a6f8541f..53dac4eb4c08d7 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -55,7 +55,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self._type == 1 and self._state is not None: schedule = { diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 089f0950854749..c3224410993022 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -519,7 +519,7 @@ def hvac_action(self): return CURRENT_HVAC_IDLE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" status = self.thermostat["equipmentStatus"] return { diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 6ad51e6c4741c0..934833c0f952b4 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -189,7 +189,7 @@ def send_command(self, command, params=None, **kwargs): self.device.run(sucks.VacBotCommand(command, params)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device-specific state attributes of this vacuum.""" data = {} data[ATTR_ERROR] = self._error diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index dc0f51abe61f88..1f21eaa4004f40 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -269,7 +269,7 @@ def state(self) -> str: return self._telegram.get("value") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Enumerate supported attributes.""" return { self._state_attrs[k]: v diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index 43bcb4c2f0930f..f858309d02cb9a 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -110,7 +110,7 @@ async def async_update(self): self._state = self._usrobj.heating_level @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" return { ATTR_TARGET_HEAT: self._usrobj.target_heating_level, @@ -202,7 +202,7 @@ async def async_update(self): self._state = self._usrobj.current_values["stage"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device state attributes.""" if self._attr is None: # Skip attributes if sensor type doesn't support diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 3dddd670bb4cd1..568b3109227cf7 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -429,7 +429,7 @@ def should_poll(self) -> bool: return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the default attributes of the element.""" return {**self._element.as_dict(), **self.initial_attrs()} diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 8f752cd9adf32e..756166c86a60df 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -173,7 +173,7 @@ def supported_features(self) -> int: return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the area.""" attrs = self.initial_attrs() elmt = self._element diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index c6442af2e44c91..7d63f283f0b239 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -136,7 +136,7 @@ def icon(self): return "mdi:thermometer-lines" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["area"] = self._element.area + 1 @@ -163,7 +163,7 @@ def icon(self): return "mdi:home" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["system_trouble_status"] = self._element.system_trouble_status @@ -190,7 +190,7 @@ def _element_changed(self, element, changeset): self._state = self._element.value @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["value_format"] = SettingFormat(self._element.value_format).name.lower() @@ -227,7 +227,7 @@ def icon(self): return f"mdi:{zone_icons.get(self._element.definition, 'alarm-bell')}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Attributes of the sensor.""" attrs = self.initial_attrs() attrs["physical_status"] = ZonePhysicalStatus( diff --git a/homeassistant/components/elv/switch.py b/homeassistant/components/elv/switch.py index 12b21c23d1a75f..48eb9675277b4e 100644 --- a/homeassistant/components/elv/switch.py +++ b/homeassistant/components/elv/switch.py @@ -74,7 +74,7 @@ def turn_off(self, **kwargs): self._pca.turn_off(self._device_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._emeter_params diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index dca9c870022e1b..523350532a02e1 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -175,7 +175,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the sensor.""" return { ATTR_FEEDID: self._elem["id"], diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 4baa6aaf047cf6..1c32b1ab805272 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -251,7 +251,7 @@ def update(self): self.e2_box.update() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes. isRecording: Is the box currently recording. diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 64b4fdf66ad909..e40c8e94372f9d 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -202,7 +202,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if ( self._type == "inverters" diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index 883b5c43d7eca9..23c895c58a17d0 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -172,7 +172,7 @@ def state(self) -> str: return self._state @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION self._attributes[ATTR_STOP_ID] = self._stop diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 7021d637cfd94b..019dcb1aee5a24 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -81,7 +81,7 @@ def name(self): return "Environment Canada Radar" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp} diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index 20eb6fac9eef66..f178b3c6275f1e 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -95,7 +95,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attr diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index 54445660484540..22089ee79073b0 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -56,7 +56,7 @@ async def async_added_to_hass(self): async_dispatcher_connect(self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {} diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index 3f3711b2e40a27..9551b51b3e91aa 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -66,7 +66,7 @@ def state(self): return self._info["status"]["alpha"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._info["status"] diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index 24ed62067f91a9..6115cdd6ef4978 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -215,7 +215,7 @@ async def async_media_previous_track(self): await self._projector.send_command(BACK) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if self._cmode is None: return {} diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index 43c6bd9f35b3cc..f803c9c0bd5c3a 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -155,7 +155,7 @@ def max_temp(self): return self._thermostat.max_temp @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" dev_specific = { ATTR_STATE_AWAY_END: self._thermostat.away_end, diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py index 1c14ce578c16fc..e56b49181d4554 100644 --- a/homeassistant/components/etherscan/sensor.py +++ b/homeassistant/components/etherscan/sensor.py @@ -70,7 +70,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 268e7709af38c4..006fbb1610d820 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -556,7 +556,7 @@ def name(self) -> str: return self._name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the evohome-specific state attributes.""" status = self._device_state_attrs if "systemModeStatus" in status: diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index b9b2463314b166..4cce0e68654653 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -158,7 +158,7 @@ def should_poll(self) -> bool: return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Ezviz-specific camera state attributes.""" return { # if privacy == true, the device closed the lid or did a 180° tilt diff --git a/tests/components/arlo/test_sensor.py b/tests/components/arlo/test_sensor.py index 5d729a5a6589e9..b8389d1903fb80 100644 --- a/tests/components/arlo/test_sensor.py +++ b/tests/components/arlo/test_sensor.py @@ -194,7 +194,7 @@ def test_update_captured_today(captured_sensor): def _test_attributes(sensor_type): data = _get_named_tuple({"model_id": "TEST123"}) sensor = _get_sensor("test", sensor_type, data) - attrs = sensor.device_state_attributes + attrs = sensor.extra_state_attributes assert attrs.get(ATTR_ATTRIBUTION) == "Data provided by arlo.netgear.com" assert attrs.get("brand") == "Netgear Arlo" assert attrs.get("model") == "TEST123" @@ -211,7 +211,7 @@ def test_state_attributes(): def test_attributes_total_cameras(cameras_sensor): """Test attributes for total cameras sensor type.""" - attrs = cameras_sensor.device_state_attributes + attrs = cameras_sensor.extra_state_attributes assert attrs.get(ATTR_ATTRIBUTION) == "Data provided by arlo.netgear.com" assert attrs.get("brand") == "Netgear Arlo" assert attrs.get("model") is None diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 975fabcf9ab330..92ad310f68d03f 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -147,7 +147,7 @@ async def test_hvac_mode2(ecobee_fixture, thermostat): assert thermostat.hvac_mode == "heat" -async def test_device_state_attributes(ecobee_fixture, thermostat): +async def test_extra_state_attributes(ecobee_fixture, thermostat): """Test device state attributes property.""" ecobee_fixture["equipmentStatus"] = "heatPump2" assert { @@ -155,7 +155,7 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "heatPump2", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "auxHeat2" assert { @@ -163,21 +163,21 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "auxHeat2", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "compCool1" assert { "fan": "off", "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "compCool1", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "" assert { "fan": "off", "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["equipmentStatus"] = "Unknown" assert { @@ -185,7 +185,7 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate1", "fan_min_on_time": 10, "equipment_running": "Unknown", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes ecobee_fixture["program"]["currentClimateRef"] = "c2" assert { @@ -193,7 +193,7 @@ async def test_device_state_attributes(ecobee_fixture, thermostat): "climate_mode": "Climate2", "fan_min_on_time": 10, "equipment_running": "Unknown", - } == thermostat.device_state_attributes + } == thermostat.extra_state_attributes async def test_is_aux_heat_on(ecobee_fixture, thermostat): From af4d06b12e7237de90b62b5aaa31025acee05a2e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 16:57:47 +0100 Subject: [PATCH 1190/1818] Update integrations f-i to override extra_state_attributes() (#47757) --- .../components/faa_delays/binary_sensor.py | 2 +- .../components/facebox/image_processing.py | 2 +- homeassistant/components/fibaro/__init__.py | 2 +- homeassistant/components/fido/sensor.py | 2 +- homeassistant/components/filesize/sensor.py | 2 +- homeassistant/components/filter/sensor.py | 2 +- homeassistant/components/fints/sensor.py | 4 ++-- .../fireservicerota/binary_sensor.py | 2 +- .../components/fireservicerota/sensor.py | 2 +- .../components/fireservicerota/switch.py | 2 +- homeassistant/components/fitbit/sensor.py | 2 +- homeassistant/components/fixer/sensor.py | 2 +- homeassistant/components/flexit/climate.py | 2 +- .../components/flic/binary_sensor.py | 2 +- .../components/flick_electric/sensor.py | 2 +- homeassistant/components/flo/binary_sensor.py | 2 +- homeassistant/components/flunearyou/sensor.py | 2 +- homeassistant/components/folder/sensor.py | 2 +- .../components/freebox/device_tracker.py | 2 +- homeassistant/components/freebox/sensor.py | 2 +- homeassistant/components/fritzbox/climate.py | 2 +- homeassistant/components/fritzbox/sensor.py | 2 +- homeassistant/components/fritzbox/switch.py | 2 +- .../components/fritzbox_callmonitor/sensor.py | 2 +- homeassistant/components/garadget/cover.py | 2 +- .../components/garmin_connect/sensor.py | 2 +- .../components/gdacs/geo_location.py | 2 +- homeassistant/components/gdacs/sensor.py | 2 +- homeassistant/components/geizhals/sensor.py | 2 +- .../components/geniushub/__init__.py | 4 ++-- homeassistant/components/geniushub/sensor.py | 2 +- .../geo_json_events/geo_location.py | 2 +- .../components/geo_rss_events/sensor.py | 2 +- .../components/geofency/device_tracker.py | 2 +- .../geonetnz_quakes/geo_location.py | 2 +- .../components/geonetnz_quakes/sensor.py | 2 +- .../components/geonetnz_volcano/sensor.py | 2 +- homeassistant/components/gios/air_quality.py | 2 +- homeassistant/components/github/sensor.py | 2 +- homeassistant/components/gitlab_ci/sensor.py | 2 +- homeassistant/components/gitter/sensor.py | 2 +- homeassistant/components/gogogate2/cover.py | 2 +- homeassistant/components/gogogate2/sensor.py | 2 +- homeassistant/components/google/calendar.py | 2 +- .../components/google_travel_time/sensor.py | 2 +- homeassistant/components/gpsd/sensor.py | 2 +- .../components/gpslogger/device_tracker.py | 2 +- .../components/greeneye_monitor/sensor.py | 4 ++-- homeassistant/components/group/cover.py | 2 +- homeassistant/components/group/light.py | 2 +- homeassistant/components/gtfs/sensor.py | 2 +- homeassistant/components/guardian/__init__.py | 2 +- homeassistant/components/habitica/sensor.py | 2 +- homeassistant/components/harmony/remote.py | 2 +- .../components/haveibeenpwned/sensor.py | 2 +- homeassistant/components/hddtemp/sensor.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 2 +- homeassistant/components/heos/media_player.py | 2 +- .../components/here_travel_time/sensor.py | 2 +- .../components/hikvision/binary_sensor.py | 2 +- .../components/history_stats/sensor.py | 2 +- .../components/hive/binary_sensor.py | 2 +- homeassistant/components/hive/climate.py | 2 +- homeassistant/components/hive/light.py | 2 +- homeassistant/components/hive/sensor.py | 2 +- homeassistant/components/hive/switch.py | 2 +- .../components/homeassistant/scene.py | 2 +- .../homekit_controller/air_quality.py | 2 +- .../homekit_controller/alarm_control_panel.py | 2 +- .../components/homekit_controller/cover.py | 4 ++-- .../components/homekit_controller/lock.py | 2 +- .../components/homekit_controller/switch.py | 4 ++-- .../homematicip_cloud/binary_sensor.py | 20 +++++++++---------- .../components/homematicip_cloud/climate.py | 4 ++-- .../homematicip_cloud/generic_entity.py | 2 +- .../components/homematicip_cloud/light.py | 8 ++++---- .../components/homematicip_cloud/sensor.py | 16 +++++++-------- .../components/homematicip_cloud/switch.py | 4 ++-- homeassistant/components/homeworks/light.py | 2 +- homeassistant/components/honeywell/climate.py | 2 +- homeassistant/components/hp_ilo/sensor.py | 2 +- .../components/huawei_lte/binary_sensor.py | 2 +- .../components/huawei_lte/device_tracker.py | 10 +++++----- homeassistant/components/hue/binary_sensor.py | 4 ++-- homeassistant/components/hue/light.py | 2 +- homeassistant/components/hue/sensor.py | 4 ++-- homeassistant/components/hue/sensor_base.py | 2 +- .../hunterdouglas_powerview/cover.py | 2 +- .../hunterdouglas_powerview/scene.py | 2 +- .../hvv_departures/binary_sensor.py | 2 +- .../components/hvv_departures/sensor.py | 2 +- .../components/hydrawise/__init__.py | 2 +- .../components/icloud/device_tracker.py | 2 +- homeassistant/components/icloud/sensor.py | 2 +- .../components/ign_sismologia/geo_location.py | 2 +- homeassistant/components/ihc/ihcdevice.py | 2 +- .../components/imap_email_content/sensor.py | 2 +- .../components/incomfort/binary_sensor.py | 2 +- homeassistant/components/incomfort/climate.py | 2 +- homeassistant/components/incomfort/sensor.py | 2 +- .../components/incomfort/water_heater.py | 2 +- homeassistant/components/insteon/climate.py | 4 ++-- .../components/insteon/insteon_entity.py | 2 +- .../components/integration/sensor.py | 2 +- .../components/intesishome/climate.py | 2 +- homeassistant/components/ios/sensor.py | 2 +- homeassistant/components/iota/__init__.py | 2 +- homeassistant/components/iota/sensor.py | 2 +- homeassistant/components/iperf3/sensor.py | 2 +- homeassistant/components/ipp/sensor.py | 4 ++-- homeassistant/components/iqvia/__init__.py | 2 +- .../components/irish_rail_transport/sensor.py | 2 +- homeassistant/components/iss/binary_sensor.py | 2 +- .../components/isy994/binary_sensor.py | 4 ++-- homeassistant/components/isy994/light.py | 4 ++-- homeassistant/components/isy994/sensor.py | 2 +- homeassistant/components/izone/climate.py | 4 ++-- tests/components/honeywell/test_climate.py | 6 +++--- .../imap_email_content/test_sensor.py | 16 +++++++-------- 119 files changed, 165 insertions(+), 165 deletions(-) diff --git a/homeassistant/components/faa_delays/binary_sensor.py b/homeassistant/components/faa_delays/binary_sensor.py index 6c5876b7017f49..b96ee24a5bc217 100644 --- a/homeassistant/components/faa_delays/binary_sensor.py +++ b/homeassistant/components/faa_delays/binary_sensor.py @@ -68,7 +68,7 @@ def unique_id(self): return f"{self._id}_{self._sensor_type}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for sensor.""" if self._sensor_type == "GROUND_DELAY": self._attrs["average"] = self.coordinator.data.ground_delay.average diff --git a/homeassistant/components/facebox/image_processing.py b/homeassistant/components/facebox/image_processing.py index 6a460ac305be82..5c90ce73560ffe 100644 --- a/homeassistant/components/facebox/image_processing.py +++ b/homeassistant/components/facebox/image_processing.py @@ -263,7 +263,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the classifier attributes.""" return { "matched_faces": self._matched, diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index d2a81468e05bf8..9ce284d51ab992 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -506,7 +506,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = {"fibaro_id": self.fibaro_device.id} diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index 22522d1ab74f44..fe9c60c85a6e39 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -125,7 +125,7 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {"number": self._number} diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 27122a3cb9c568..312805e4942a77 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -76,7 +76,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" return { "path": self._path, diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index d46709924ea0a2..f4be797d12ba53 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -351,7 +351,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ENTITY_ID: self._entity} diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 6cd62333a8714b..4ccd2b6f848bcb 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -193,7 +193,7 @@ def unit_of_measurement(self) -> str: return self._currency @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" attributes = {ATTR_ACCOUNT: self._account.iban, ATTR_ACCOUNT_TYPE: "balance"} if self._client.name: @@ -238,7 +238,7 @@ def icon(self) -> str: return ICON @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor. Lists each holding of the account with the current value. diff --git a/homeassistant/components/fireservicerota/binary_sensor.py b/homeassistant/components/fireservicerota/binary_sensor.py index 3f04adc2f7254b..29fc97ae50300c 100644 --- a/homeassistant/components/fireservicerota/binary_sensor.py +++ b/homeassistant/components/fireservicerota/binary_sensor.py @@ -62,7 +62,7 @@ def is_on(self) -> bool: return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return available attributes for binary sensor.""" attr = {} if not self.coordinator.data: diff --git a/homeassistant/components/fireservicerota/sensor.py b/homeassistant/components/fireservicerota/sensor.py index 83272eff926c36..7dc8f546c984fa 100644 --- a/homeassistant/components/fireservicerota/sensor.py +++ b/homeassistant/components/fireservicerota/sensor.py @@ -64,7 +64,7 @@ def should_poll(self) -> bool: return False @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return available attributes for sensor.""" attr = {} data = self._state_attributes diff --git a/homeassistant/components/fireservicerota/switch.py b/homeassistant/components/fireservicerota/switch.py index 7519270ca5c5b8..e2385f02e5c9b2 100644 --- a/homeassistant/components/fireservicerota/switch.py +++ b/homeassistant/components/fireservicerota/switch.py @@ -73,7 +73,7 @@ def available(self): return self._client.on_duty @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return available attributes for switch.""" attr = {} if not self._state_attributes: diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index 387eb78448c7a8..b90d33adaffa6c 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -457,7 +457,7 @@ def icon(self): return f"mdi:{FITBIT_RESOURCES_LIST[self.resource_type][2]}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index 99ebbdd6bb63d4..9641f596e6182c 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -75,7 +75,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.data.rate is not None: return { diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index 450d09edeb8af9..ef3997968982e4 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -93,7 +93,7 @@ def update(self): self._current_operation = self.unit.get_operation @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { "filter_hours": self._filter_hours, diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py index e81f8f2f5b048d..6ddaddced0de80 100644 --- a/homeassistant/components/flic/binary_sensor.py +++ b/homeassistant/components/flic/binary_sensor.py @@ -186,7 +186,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {"address": self.address} diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index 9d441ce7574d99..4f152c54e3c4e4 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -61,7 +61,7 @@ def unit_of_measurement(self): return UNIT_NAME @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index 3ab1152f83e809..96909b3bd9c5f9 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -48,7 +48,7 @@ def is_on(self): return self._device.has_alerts @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self._device.has_alerts: return {} diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py index 8bb5f1317d12cd..225ed15d98b7b6 100644 --- a/homeassistant/components/flunearyou/sensor.py +++ b/homeassistant/components/flunearyou/sensor.py @@ -100,7 +100,7 @@ def __init__(self, coordinator, config_entry, sensor_type, name, icon, unit): self._unit = unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index 9a062133718579..cfbfd670d0522a 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -92,7 +92,7 @@ def icon(self): return self.ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" return { "path": self._folder_path, diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index 10c5b8eb2c51b7..8b1b214c33a48a 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -105,7 +105,7 @@ def icon(self) -> str: return self._icon @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index b8881ad7949e0a..d014d85f6dca1c 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -180,7 +180,7 @@ def async_update_state(self) -> None: self._state = len(self._call_list_for_type) @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return device specific state attributes.""" return { dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"] diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 4abe82776a9cca..50f56f3d510f9f 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -191,7 +191,7 @@ def max_temp(self): return MAX_TEMPERATURE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = { ATTR_STATE_BATTERY_LOW: self._device.battery_low, diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 85238d80f27695..470d110429c310 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -80,7 +80,7 @@ def update(self): self._fritz.login() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attrs = { ATTR_STATE_DEVICE_LOCKED: self._device.device_lock, diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index b179464182ff22..50c60f7bb39266 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -93,7 +93,7 @@ def update(self): self._fritz.login() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attrs = {} attrs[ATTR_STATE_DEVICE_LOCKED] = self._device.device_lock diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 891bf8131d6faa..82ce075268b0a4 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -169,7 +169,7 @@ def icon(self): return ICON_PHONE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._prefixes: self._attributes[ATTR_PREFIXES] = self._prefixes diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index 9c089e85811768..206aaa6614d341 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -135,7 +135,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" data = {} diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index 5d18f0a0dd0930..d2a6419cc75d11 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -120,7 +120,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for sensor.""" if not self._data.data: return {} diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index 890c9f8e050c7c..a2178bb78e07d8 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -213,7 +213,7 @@ def unit_of_measurement(self): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index fbbb199499b32e..c3642181427903 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -129,7 +129,7 @@ def unit_of_measurement(self): return DEFAULT_UNIT_OF_MEASUREMENT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py index 43e41e25e5eb62..5f11ae7d0cac68 100644 --- a/homeassistant/components/geizhals/sensor.py +++ b/homeassistant/components/geizhals/sensor.py @@ -72,7 +72,7 @@ def state(self): return self._device.prices[0] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" while len(self._device.prices) < 4: self._device.prices.append("None") diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index def964157e40f4..15a2c8e7686e3e 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -250,7 +250,7 @@ def __init__(self, broker, device) -> None: self._last_comms = self._state_attr = None @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" attrs = {} attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"] @@ -317,7 +317,7 @@ def name(self) -> str: return self._zone.name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" status = {k: v for k, v in self._zone.data.items() if k in GH_ZONE_ATTRS} return {"status": status} diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 7e4fe81fc7781a..9d87fb46cb0ddb 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -106,7 +106,7 @@ def state(self) -> str: return len(self._issues) @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" return {f"{self._level}_list": self._issues} diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index 40386648138ca9..feb0b98b3facce 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -201,7 +201,7 @@ def unit_of_measurement(self): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if not self._external_id: return {} diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index c75234f5f2b312..00ff63982fda48 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -137,7 +137,7 @@ def icon(self): return DEFAULT_ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index f090f516fb1e1f..5a58e73d44a423 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -56,7 +56,7 @@ def __init__(self, device, gps=None, location_name=None, attributes=None): self._unique_id = device @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._attributes diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 718b4c06b9c3f3..62086f059f42e1 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -184,7 +184,7 @@ def unit_of_measurement(self): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index 1cb2d0dc0915fd..e54767570247d8 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -130,7 +130,7 @@ def unit_of_measurement(self): return DEFAULT_UNIT_OF_MEASUREMENT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 3d5d0681f02222..7044b3c5609830 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -149,7 +149,7 @@ def unit_of_measurement(self): return "alert level" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/gios/air_quality.py b/homeassistant/components/gios/air_quality.py index 2853570ce58eb0..ab83191a1ac0d8 100644 --- a/homeassistant/components/gios/air_quality.py +++ b/homeassistant/components/gios/air_quality.py @@ -131,7 +131,7 @@ def device_info(self): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" # Different measuring stations have different sets of sensors. We don't know # what data we will get. diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 80d05ae1b9c081..f5f40c270a8419 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -119,7 +119,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_PATH: self._repository_path, diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py index 9edbe9733a8991..5eaa62e601d75c 100644 --- a/homeassistant/components/gitlab_ci/sensor.py +++ b/homeassistant/components/gitlab_ci/sensor.py @@ -99,7 +99,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py index aff6dc17923b83..3f7888d8593f88 100644 --- a/homeassistant/components/gitter/sensor.py +++ b/homeassistant/components/gitter/sensor.py @@ -76,7 +76,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_USERNAME: self._username, diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 37c362893c8f7c..053c35c171ad7d 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -122,6 +122,6 @@ async def async_close_cover(self, **kwargs): await self._api.async_close_door(self._get_door().door_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"door_id": self._get_door().door_id} diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index c4f6cff9dfa2b3..ed53779a95aaf3 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -50,7 +50,7 @@ def state(self): return door.voltage # This is a percentage, not an absolute voltage @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" door = self._get_door() if door.sensorid is not None: diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 9040d37935d84d..2cc6612194800f 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -80,7 +80,7 @@ def __init__(self, calendar_service, calendar, data, entity_id): self.entity_id = entity_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"offset_reached": self._offset_reached} diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 098c6d2d59c9ad..109b65a4225b63 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -230,7 +230,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._matrix is None: return None diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py index ea238269e598e5..978589e9a744a9 100644 --- a/homeassistant/components/gpsd/sensor.py +++ b/homeassistant/components/gpsd/sensor.py @@ -94,7 +94,7 @@ def state(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the GPS.""" return { ATTR_LATITUDE: self.agps_thread.data_stream.lat, diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index 6999f26d7520ff..25701e8c2e79a5 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -79,7 +79,7 @@ def battery_level(self): return self._battery @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._attributes diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index f026bdfe3a4e28..84352328312564 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -175,7 +175,7 @@ def state(self): return self._sensor.watts @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return total wattseconds in the state dictionary.""" if not self._sensor: return None @@ -242,7 +242,7 @@ def unit_of_measurement(self): return f"{self._counted_quantity}/{self._time_unit}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return total pulses in the data dictionary.""" if not self._sensor: return None diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index b52546c48d7cae..da842ee9f007ee 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -208,7 +208,7 @@ def current_cover_tilt_position(self): return self._tilt_position @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the cover group.""" return {ATTR_ENTITY_ID: self._entities} diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 5720948bc01c90..482bdbafdabeb6 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -187,7 +187,7 @@ def should_poll(self) -> bool: return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the light group.""" return {ATTR_ENTITY_ID: self._entity_ids} diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index d21ab67f053468..23f9e2a80218db 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -569,7 +569,7 @@ def available(self) -> bool: return self._available @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 465e1d730afa6c..98f35d34e14df1 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -246,7 +246,7 @@ def device_info(self) -> dict: return self._device_info @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index 29e494d89ee313..4d61a86de75d2f 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -200,7 +200,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of all user tasks.""" if self._updater.tasks is not None: all_received_tasks = self._updater.tasks diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 55af62e6d392ce..a09f32ee95e4dd 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -193,7 +193,7 @@ def activity_list(self): return self._data.activity_names @property - def device_state_attributes(self): + def extra_state_attributes(self): """Add platform specific attributes.""" return { ATTR_ACTIVITY_STARTING: self._activity_starting, diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 0f5a9b5ebfd865..5aa336905e998d 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -80,7 +80,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the sensor.""" val = {ATTR_ATTRIBUTION: ATTRIBUTION} if self._email not in self._data.data: diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index a1052b0440a11a..e5f6621defecc7 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -88,7 +88,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._details is not None: return {ATTR_DEVICE: self._details[0], ATTR_MODEL: self._details[1]} diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index c9a5d27a3be9f0..2f821c1d3a7ee5 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -450,7 +450,7 @@ def icon(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state_attr = {} if self.vendor_id is not None: diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 15d3c4573dbab7..6e271bf60cd995 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -262,7 +262,7 @@ def device_info(self) -> dict: } @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Get additional attribute about the state.""" return { "media_album_id": self._player.now_playing_media.album_id, diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e9506fceed5209..e9b3f4ff9a4d96 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -271,7 +271,7 @@ def name(self) -> str: return self._name @property - def device_state_attributes( + def extra_state_attributes( self, ) -> Optional[Dict[str, Union[None, float, str, bool]]]: """Return the state attributes.""" diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index 90c4b6ce8b918e..0d57278c826f5d 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -252,7 +252,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {ATTR_LAST_TRIP_TIME: self._sensor_last_update()} diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 6778e893f6f330..7658146d102e01 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -174,7 +174,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self.value is None: return {} diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 41f1dacc8f3dc2..eed08c45b3a804 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -69,7 +69,7 @@ def available(self): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return { ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index f1901147f17eb5..c0b33dbb3ae263 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -94,7 +94,7 @@ def available(self): return self.device["deviceData"]["online"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index f458c27d019523..12779ef9d2e34a 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -56,7 +56,7 @@ def available(self): return self.device["deviceData"]["online"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return { ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index e828dff9b4e67f..fe413a35b2f1f1 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -68,7 +68,7 @@ def state(self): return self.device["status"]["state"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 8ab820589cff76..821f48dbf974b7 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -47,7 +47,7 @@ def available(self): return self.device["deviceData"].get("online") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return { ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 1ff3915f1215c9..cca2c601493cd6 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -298,7 +298,7 @@ def unique_id(self): return self.scene_config.id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the scene state attributes.""" attributes = {ATTR_ENTITY_ID: list(self.scene_config.states)} unique_id = self.unique_id diff --git a/homeassistant/components/homekit_controller/air_quality.py b/homeassistant/components/homekit_controller/air_quality.py index 896034a2ca02b8..2a162eb2b2a370 100644 --- a/homeassistant/components/homekit_controller/air_quality.py +++ b/homeassistant/components/homekit_controller/air_quality.py @@ -69,7 +69,7 @@ def volatile_organic_compounds(self): return self.service.value(CharacteristicsTypes.DENSITY_VOC) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" data = {"air_quality_text": self.air_quality_text} diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 621fb01ff74a3a..c40252c9fdc4bb 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -105,7 +105,7 @@ async def set_alarm_state(self, state, code=None): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL) diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 6c945c8111524a..dd25e32b3c4605 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -108,7 +108,7 @@ async def set_door_state(self, state): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED @@ -235,7 +235,7 @@ async def async_set_cover_tilt_position(self, **kwargs): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index 8ac7fd608fd414..09c02ce0ff97b0 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -63,7 +63,7 @@ async def _set_lock_state(self, state): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" attributes = {} diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index b9d0b273cb1ba8..36ed379bc805b4 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -39,7 +39,7 @@ async def async_turn_off(self, **kwargs): await self.async_put_characteristics({CharacteristicsTypes.ON: False}) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" outlet_in_use = self.service.value(CharacteristicsTypes.OUTLET_IN_USE) if outlet_in_use is not None: @@ -77,7 +77,7 @@ def is_on(self): return self.service.value(CharacteristicsTypes.ACTIVE) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" attrs = {} diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 4f1ae523ecc76a..0d21967b24215f 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -210,9 +210,9 @@ def is_on(self) -> bool: return self._device.accelerationSensorTriggered @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the acceleration sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes for attr, attr_key in SAM_DEVICE_ATTRIBUTES.items(): attr_value = getattr(self._device, attr, None) @@ -285,9 +285,9 @@ def device_class(self) -> str: return DEVICE_CLASS_DOOR @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the Shutter Contact.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self.has_additional_state: window_state = getattr(self._device, "windowState", None) @@ -412,9 +412,9 @@ def is_on(self) -> bool: return self._device.sunshine @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the illuminance sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes today_sunshine_duration = getattr(self._device, "todaySunshineDuration", None) if today_sunshine_duration: @@ -482,9 +482,9 @@ def available(self) -> bool: return True @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the security zone group.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes for attr, attr_key in GROUP_ATTRIBUTES.items(): attr_value = getattr(self._device, attr, None) @@ -526,9 +526,9 @@ def __init__(self, hap: HomematicipHAP, device) -> None: super().__init__(hap, device, post="Sensors") @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the security group.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes smoke_detector_at = getattr(self._device, "smokeDetectorAlarmType", None) if smoke_detector_at: diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index dcd5aeb284e3db..c09bd2cc53e056 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -237,9 +237,9 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: await self._device.set_active_profile(profile_idx) @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the access point.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self._device.controlMode == HMIP_ECO_CM: if self._indoor_climate.absenceType in [ diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index a1e13658d20113..345446e78b2eef 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -232,7 +232,7 @@ def icon(self) -> Optional[str]: return None @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the generic entity.""" state_attr = {} diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 1909ff818b9323..c453cf516dca6d 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -90,9 +90,9 @@ class HomematicipLightMeasuring(HomematicipLight): """Representation of the HomematicIP measuring light.""" @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the light.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes current_power_w = self._device.currentPowerConsumption if current_power_w > 0.05: @@ -206,9 +206,9 @@ def hs_color(self) -> tuple: return self._color_switcher.get(simple_rgb_color, [0.0, 0.0]) @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the notification light sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self.is_on: state_attr[ATTR_COLOR_NAME] = self._func_channel.simpleRGBColorState diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 9e202302c1083b..9f7c7517c3a7a2 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -222,9 +222,9 @@ def unit_of_measurement(self) -> str: return TEMP_CELSIUS @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the windspeed sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes temperature_offset = getattr(self._device, "temperatureOffset", None) if temperature_offset: @@ -259,9 +259,9 @@ def unit_of_measurement(self) -> str: return LIGHT_LUX @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the wind speed sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes for attr, attr_key in ILLUMINATION_DEVICE_ATTRIBUTES.items(): attr_value = getattr(self._device, attr, None) @@ -312,9 +312,9 @@ def unit_of_measurement(self) -> str: return SPEED_KILOMETERS_PER_HOUR @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the wind speed sensor.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes wind_direction = getattr(self._device, "windDirection", None) if wind_direction is not None: @@ -354,9 +354,9 @@ def state(self) -> int: return self._device.leftRightCounterDelta @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the delta counter.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes state_attr[ATTR_LEFT_COUNTER] = self._device.leftCounter state_attr[ATTR_RIGHT_COUNTER] = self._device.rightCounter diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index f8c37d336d507f..e75615bf3361d6 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -141,9 +141,9 @@ def available(self) -> bool: return True @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the switch-group.""" - state_attr = super().device_state_attributes + state_attr = super().extra_state_attributes if self._device.unreach: state_attr[ATTR_GROUP_MEMBER_UNREACHABLE] = True diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index a5a3b9ed07713f..f62477148f72f2 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -82,7 +82,7 @@ def _set_brightness(self, level): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Supported attributes.""" return {"homeworks_address": self._addr} diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 4b87350aec8169..a825916628dcd6 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -197,7 +197,7 @@ def name(self) -> Optional[str]: return self._device.name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device specific state attributes.""" data = {} data[ATTR_FAN_ACTION] = "running" if self._device.fan_running else "idle" diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index da597acb8b7336..0126ab780ce715 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -144,7 +144,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._state_attributes diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index ea9d68bc843e54..525dd3352e7266 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -142,7 +142,7 @@ def entity_registry_enabled_default(self) -> bool: return True @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Get additional attributes related to connection status.""" attributes = {} if self._raw_state in CONNECTION_STATE_ATTRIBUTES: diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 52e59b713dd4aa..3e889263b76fd0 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -126,11 +126,11 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): _is_connected: bool = attr.ib(init=False, default=False) _hostname: Optional[str] = attr.ib(init=False, default=None) - _device_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) + _extra_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) def __attrs_post_init__(self) -> None: """Initialize internal state.""" - self._device_state_attributes["mac_address"] = self.mac + self._extra_state_attributes["mac_address"] = self.mac @property def _entity_name(self) -> str: @@ -151,9 +151,9 @@ def is_connected(self) -> bool: return self._is_connected @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Get additional attributes related to entity state.""" - return self._device_state_attributes + return self._extra_state_attributes async def async_update(self) -> None: """Update state.""" @@ -162,6 +162,6 @@ async def async_update(self) -> None: self._is_connected = host is not None if host is not None: self._hostname = host.get("HostName") - self._device_state_attributes = { + self._extra_state_attributes = { _better_snakecase(k): v for k, v in host.items() if k != "HostName" } diff --git a/homeassistant/components/hue/binary_sensor.py b/homeassistant/components/hue/binary_sensor.py index cfbe041aafe4e6..e408e995ad42f4 100644 --- a/homeassistant/components/hue/binary_sensor.py +++ b/homeassistant/components/hue/binary_sensor.py @@ -31,9 +31,9 @@ def is_on(self): return self.sensor.presence @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes if "sensitivity" in self.sensor.config: attributes["sensitivity"] = self.sensor.config["sensitivity"] if "sensitivitymax" in self.sensor.config: diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 6384e47b45e823..8adde810fbe3cb 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -535,7 +535,7 @@ async def async_turn_off(self, **kwargs): await self.coordinator.async_request_refresh() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if not self.is_group: return {} diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py index f5911bbb50c7d3..a3c5fcc3fa710b 100644 --- a/homeassistant/components/hue/sensor.py +++ b/homeassistant/components/hue/sensor.py @@ -58,9 +58,9 @@ def state(self): return round(float(10 ** ((self.sensor.lightlevel - 1) / 10000)), 2) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes attributes.update( { "lightlevel": self.sensor.lightlevel, diff --git a/homeassistant/components/hue/sensor_base.py b/homeassistant/components/hue/sensor_base.py index 263140464aa1cd..9f764e04d28f92 100644 --- a/homeassistant/components/hue/sensor_base.py +++ b/homeassistant/components/hue/sensor_base.py @@ -197,6 +197,6 @@ class GenericZLLSensor(GenericHueSensor): """Representation of a Hue-brand, physical sensor.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {"battery_level": self.sensor.battery} diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index e90b315fd168ab..9ac45c6bd9a818 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -111,7 +111,7 @@ def __init__(self, coordinator, device_info, room_name, shade, name): self._current_cover_position = MIN_POSITION @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name} diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index 33c7e7129fc2de..c30cde8d043ca1 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -71,7 +71,7 @@ def name(self): return self._scene.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name} diff --git a/homeassistant/components/hvv_departures/binary_sensor.py b/homeassistant/components/hvv_departures/binary_sensor.py index f625ef7c3444e4..45ac0e45ad970d 100644 --- a/homeassistant/components/hvv_departures/binary_sensor.py +++ b/homeassistant/components/hvv_departures/binary_sensor.py @@ -174,7 +174,7 @@ def device_class(self): return DEVICE_CLASS_PROBLEM @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not ( self.coordinator.last_update_success diff --git a/homeassistant/components/hvv_departures/sensor.py b/homeassistant/components/hvv_departures/sensor.py index d6957e6beec29b..bbabb1b8caaee5 100644 --- a/homeassistant/components/hvv_departures/sensor.py +++ b/homeassistant/components/hvv_departures/sensor.py @@ -199,6 +199,6 @@ def device_class(self): return DEVICE_CLASS_TIMESTAMP @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.attr diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 08827baae6846a..6fa05ebef16347 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -151,7 +151,7 @@ def unit_of_measurement(self): ] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, "identifier": self.data.get("relay")} diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 59554c001ef237..77fcf0a3039ee7 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -108,7 +108,7 @@ def icon(self) -> str: return icon_for_icloud_device(self._device) @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the device state attributes.""" return self._device.state_attributes diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index 859148d8190f2c..9a7c568112db62 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -91,7 +91,7 @@ def icon(self) -> str: ) @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return default attributes for the iCloud device entity.""" return self._device.state_attributes diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index 0db580701d038f..fc9bdcbe87e352 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -238,7 +238,7 @@ def unit_of_measurement(self): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py index 0b3dc763ca07fe..e351d2f38eafff 100644 --- a/homeassistant/components/ihc/ihcdevice.py +++ b/homeassistant/components/ihc/ihcdevice.py @@ -42,7 +42,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self.info: return {} diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 04d4ca97c5ab2e..84dc527ac52fb6 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -171,7 +171,7 @@ def state(self): return self._message @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other state attributes for the message.""" return self._state_attributes diff --git a/homeassistant/components/incomfort/binary_sensor.py b/homeassistant/components/incomfort/binary_sensor.py index bf1340fb235496..86cdf2d8687832 100644 --- a/homeassistant/components/incomfort/binary_sensor.py +++ b/homeassistant/components/incomfort/binary_sensor.py @@ -40,6 +40,6 @@ def is_on(self) -> bool: return self._heater.status["is_failed"] @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the device state attributes.""" return {"fault_code": self._heater.status["fault_code"]} diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index 274308efe064c1..c3db86a79ac4d0 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -39,7 +39,7 @@ def __init__(self, client, heater, room) -> None: self._room = room @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" return {"status": self._room.status} diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 692eecf2317b55..0672d19b2a9ec2 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -95,6 +95,6 @@ def __init__(self, client, heater, name) -> None: self._unit_of_measurement = TEMP_CELSIUS @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the device state attributes.""" return {self._attr: self._heater.status[self._attr]} diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index da6e6d893150a7..b3db66bd93db84 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -50,7 +50,7 @@ def icon(self) -> str: return "mdi:thermometer-lines" @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" return {k: v for k, v in self._heater.status.items() if k in HEATER_ATTRS} diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index c699e76c4f3659..536c30bc6b9a77 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -177,9 +177,9 @@ def hvac_action(self) -> Optional[str]: return CURRENT_HVAC_IDLE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Provide attributes for display on device card.""" - attr = super().device_state_attributes + attr = super().extra_state_attributes humidifier = "off" if self._insteon_device.groups[DEHUMIDIFYING].value: humidifier = "dehumidifying" diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index 2234eb4750c36e..3f83440d69006d 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -75,7 +75,7 @@ def name(self): return f"{description} {self._insteon_device.address}{extension}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Provide attributes for display on device card.""" return {"insteon_address": self.address, "insteon_group": self.group} diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 6c59035adb4397..3be60dcd7f732d 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -201,7 +201,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_SOURCE_ID: self._sensor_source_id} diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py index a41161c7a6ef73..d58efddeb3c35e 100644 --- a/homeassistant/components/intesishome/climate.py +++ b/homeassistant/components/intesishome/climate.py @@ -212,7 +212,7 @@ def temperature_unit(self): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = {} if self._outdoor_temp: diff --git a/homeassistant/components/ios/sensor.py b/homeassistant/components/ios/sensor.py index ccbc118a68171e..a8cffacabe9aa6 100644 --- a/homeassistant/components/ios/sensor.py +++ b/homeassistant/components/ios/sensor.py @@ -88,7 +88,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" device = self._device[ios.ATTR_DEVICE] device_battery = self._device[ios.ATTR_BATTERY] diff --git a/homeassistant/components/iota/__init__.py b/homeassistant/components/iota/__init__.py index 41c5ad35d83e09..04db9140122e73 100644 --- a/homeassistant/components/iota/__init__.py +++ b/homeassistant/components/iota/__init__.py @@ -67,7 +67,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {CONF_WALLET_NAME: self._name} diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py index a6f689e8c2d93c..751324d61b1c0a 100644 --- a/homeassistant/components/iota/sensor.py +++ b/homeassistant/components/iota/sensor.py @@ -85,7 +85,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attr diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py index 749a3e83217a65..a8a790088171c0 100644 --- a/homeassistant/components/iperf3/sensor.py +++ b/homeassistant/components/iperf3/sensor.py @@ -55,7 +55,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index a278f6e8ef9fa0..b24e05f2720544 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -117,7 +117,7 @@ def __init__( ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_MARKER_HIGH_LEVEL: self.coordinator.data.markers[ @@ -160,7 +160,7 @@ def __init__( ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_INFO: self.coordinator.data.info.printer_info, diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index 4dc066d7629832..962f13ca07ea15 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -123,7 +123,7 @@ def __init__(self, coordinator, entry, sensor_type, name, icon): self._type = sensor_type @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/irish_rail_transport/sensor.py b/homeassistant/components/irish_rail_transport/sensor.py index fba69a6f578120..bd277020125ae4 100644 --- a/homeassistant/components/irish_rail_transport/sensor.py +++ b/homeassistant/components/irish_rail_transport/sensor.py @@ -89,7 +89,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._times: next_up = "None" diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index e1f0d7a19ceca5..787d7471d4374f 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -79,7 +79,7 @@ def device_class(self): return DEFAULT_DEVICE_CLASS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.iss_data: attrs = { diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index e8c08d98aa7bf5..807f99734b703c 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -455,9 +455,9 @@ def device_class(self) -> str: return DEVICE_CLASS_BATTERY @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get the state attributes for the device.""" - attr = super().device_state_attributes + attr = super().extra_state_attributes attr["parent_entity_id"] = self._parent_device.entity_id return attr diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 7ff44863f6b6ab..04800a3f2118cc 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -98,9 +98,9 @@ def turn_on(self, brightness=None, **kwargs) -> None: _LOGGER.debug("Unable to turn on light") @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return the light attributes.""" - attribs = super().device_state_attributes + attribs = super().extra_state_attributes attribs[ATTR_LAST_BRIGHTNESS] = self._last_brightness return attribs diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index d3d192b1b3b93c..2f393227df509c 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -117,7 +117,7 @@ def state(self): return convert_isy_value_to_hass(self._node.status, "", self._node.prec) @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Get the state attributes for the device.""" return { "init_value": convert_isy_value_to_hass( diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 77ce9cb24528f4..53651683725508 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -282,7 +282,7 @@ def precision(self) -> float: return PRECISION_TENTHS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" return { "supply_temperature": show_temp( @@ -660,7 +660,7 @@ def zone_index(self): return self._zone.index @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" return { "airflow_max": self._zone.airflow_max, diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index 058988203e5d36..ee107d4985e8d5 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -392,10 +392,10 @@ def test_attributes(self): ATTR_FAN_MODES: somecomfort.FAN_MODES, ATTR_HVAC_MODES: somecomfort.SYSTEM_MODES, } - assert expected == self.honeywell.device_state_attributes + assert expected == self.honeywell.extra_state_attributes expected["fan"] = "idle" self.device.fan_running = False - assert expected == self.honeywell.device_state_attributes + assert expected == self.honeywell.extra_state_attributes def test_with_no_fan(self): """Test if there is on fan.""" @@ -407,7 +407,7 @@ def test_with_no_fan(self): ATTR_FAN_MODES: somecomfort.FAN_MODES, ATTR_HVAC_MODES: somecomfort.SYSTEM_MODES, } - assert expected == self.honeywell.device_state_attributes + assert expected == self.honeywell.extra_state_attributes def test_heat_away_mode(self): """Test setting the heat away mode.""" diff --git a/tests/components/imap_email_content/test_sensor.py b/tests/components/imap_email_content/test_sensor.py index 241dc4dc5064fe..101896f4a33fcb 100644 --- a/tests/components/imap_email_content/test_sensor.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -48,12 +48,12 @@ async def test_allowed_sender(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert "Test" == sensor.state - assert "Test Message" == sensor.device_state_attributes["body"] - assert "sender@test.com" == sensor.device_state_attributes["from"] - assert "Test" == sensor.device_state_attributes["subject"] + assert "Test Message" == sensor.extra_state_attributes["body"] + assert "sender@test.com" == sensor.extra_state_attributes["from"] + assert "Test" == sensor.extra_state_attributes["subject"] assert ( datetime.datetime(2016, 1, 1, 12, 44, 57) - == sensor.device_state_attributes["date"] + == sensor.extra_state_attributes["date"] ) @@ -84,7 +84,7 @@ async def test_multi_part_with_text(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert "Link" == sensor.state - assert "Test Message" == sensor.device_state_attributes["body"] + assert "Test Message" == sensor.extra_state_attributes["body"] async def test_multi_part_only_html(hass): @@ -113,7 +113,7 @@ async def test_multi_part_only_html(hass): assert "Link" == sensor.state assert ( "Test Message" - == sensor.device_state_attributes["body"] + == sensor.extra_state_attributes["body"] ) @@ -141,7 +141,7 @@ async def test_multi_part_only_other_text(hass): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() assert "Link" == sensor.state - assert "Test Message" == sensor.device_state_attributes["body"] + assert "Test Message" == sensor.extra_state_attributes["body"] async def test_multiple_emails(hass): @@ -183,7 +183,7 @@ def state_changed_listener(entity_id, from_s, to_s): assert "Test" == states[0].state assert "Test 2" == states[1].state - assert "Test Message 2" == sensor.device_state_attributes["body"] + assert "Test Message 2" == sensor.extra_state_attributes["body"] async def test_sender_not_allowed(hass): From 10848b9bdf62dcdd3867dc3d2c9627f07ceb20fc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 11 Mar 2021 18:52:07 +0100 Subject: [PATCH 1191/1818] Recorder improvements (#47739) --- homeassistant/components/recorder/__init__.py | 9 +- homeassistant/components/recorder/models.py | 39 +++++++ homeassistant/components/recorder/purge.py | 21 ++-- homeassistant/components/recorder/util.py | 9 +- tests/components/recorder/common.py | 38 ++++++- tests/components/recorder/conftest.py | 36 +++++- tests/components/recorder/test_init.py | 27 +++-- tests/components/recorder/test_purge.py | 105 ++++++++++-------- 8 files changed, 207 insertions(+), 77 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 3935aa97eb8753..9b84518b6d3183 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,6 +1,5 @@ """Support for recording details.""" import asyncio -from collections import namedtuple import concurrent.futures from datetime import datetime import logging @@ -8,7 +7,7 @@ import sqlite3 import threading import time -from typing import Any, Callable, List, Optional +from typing import Any, Callable, List, NamedTuple, Optional from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select from sqlalchemy.orm import scoped_session, sessionmaker @@ -223,7 +222,11 @@ async def async_handle_disable_service(service): return await instance.async_db_ready -PurgeTask = namedtuple("PurgeTask", ["keep_days", "repack"]) +class PurgeTask(NamedTuple): + """Object to store information about purge task.""" + + keep_days: int + repack: bool class WaitTask: diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 551abeac15ab2a..6ed25e64eda1f3 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -64,6 +64,15 @@ class Events(Base): # type: ignore Index("ix_events_event_type_time_fired", "event_type", "time_fired"), ) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + @staticmethod def from_event(event, event_data=None): """Create an event database object from a native event.""" @@ -129,6 +138,17 @@ class States(Base): # type: ignore Index("ix_states_entity_id_last_updated", "entity_id", "last_updated"), ) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + @staticmethod def from_event(event): """Create object from a state_changed event.""" @@ -185,6 +205,16 @@ class RecorderRuns(Base): # type: ignore __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + def entity_ids(self, point_in_time=None): """Return the entity ids that existed in this run. @@ -219,6 +249,15 @@ class SchemaChanges(Base): # type: ignore schema_version = Column(Integer) changed = Column(DateTime(timezone=True), default=dt_util.utcnow) + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + def process_timestamp(ts): """Process a timestamp into datetime object.""" diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index ac10dadc227e58..3717ed49f300f3 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -28,14 +28,16 @@ def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: Cleans up an timeframe of an hour, based on the oldest record. """ purge_before = dt_util.utcnow() - timedelta(days=purge_days) - _LOGGER.debug("Purging states and events before target %s", purge_before) + _LOGGER.debug( + "Purging states and events before target %s", + purge_before.isoformat(sep=" ", timespec="seconds"), + ) try: with session_scope(session=instance.get_session()) as session: # type: ignore # 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) if state_ids: - _disconnect_states_about_to_be_purged(session, state_ids) _purge_state_ids(session, state_ids) if event_ids: _purge_event_ids(session, event_ids) @@ -66,7 +68,7 @@ def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: return True -def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list: +def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list[int]: """Return a list of event ids to purge.""" events = ( session.query(Events.event_id) @@ -79,8 +81,8 @@ def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list def _select_state_ids_to_purge( - session: Session, purge_before: datetime, event_ids: list -) -> list: + session: Session, purge_before: datetime, event_ids: list[int] +) -> list[int]: """Return a list of state ids to purge.""" if not event_ids: return [] @@ -94,7 +96,9 @@ def _select_state_ids_to_purge( return [state.state_id for state in states] -def _disconnect_states_about_to_be_purged(session: Session, state_ids: list) -> None: +def _purge_state_ids(session: Session, state_ids: list[int]) -> None: + """Disconnect states and delete by state id.""" + # Update old_state_id to NULL before deleting to ensure # the delete does not fail due to a foreign key constraint # since some databases (MSSQL) cannot do the ON DELETE SET NULL @@ -106,9 +110,6 @@ def _disconnect_states_about_to_be_purged(session: Session, state_ids: list) -> ) _LOGGER.debug("Updated %s states to remove old_state_id", disconnected_rows) - -def _purge_state_ids(session: Session, state_ids: list) -> None: - """Delete by state id.""" deleted_rows = ( session.query(States) .filter(States.state_id.in_(state_ids)) @@ -117,7 +118,7 @@ def _purge_state_ids(session: Session, state_ids: list) -> None: _LOGGER.debug("Deleted %s states", deleted_rows) -def _purge_event_ids(session: Session, event_ids: list) -> None: +def _purge_event_ids(session: Session, event_ids: list[int]) -> None: """Delete by event id.""" deleted_rows = ( session.query(Events) diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index b945386de82b49..b04a4fb7f1f987 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -1,4 +1,7 @@ """SQLAlchemy util functions.""" +from __future__ import annotations + +from collections.abc import Generator from contextlib import contextmanager from datetime import timedelta import logging @@ -6,7 +9,9 @@ import time from sqlalchemy.exc import OperationalError, SQLAlchemyError +from sqlalchemy.orm.session import Session +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util from .const import CONF_DB_INTEGRITY_CHECK, DATA_INSTANCE, SQLITE_URL_PREFIX @@ -25,7 +30,9 @@ @contextmanager -def session_scope(*, hass=None, session=None): +def session_scope( + *, hass: HomeAssistantType | None = None, session: Session | None = None +) -> Generator[Session, None, None]: """Provide a transactional scope around a series of operations.""" if session is None and hass is not None: session = hass.data[DATA_INSTANCE].get_session() diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index d2b731777e2912..79f0f1f00d0a45 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -1,14 +1,15 @@ """Common test utils for working with recorder.""" - from datetime import timedelta +from homeassistant import core as ha from homeassistant.components import recorder +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from tests.common import fire_time_changed +from tests.common import async_fire_time_changed, fire_time_changed -def wait_recording_done(hass): +def wait_recording_done(hass: HomeAssistantType) -> None: """Block till recording is done.""" hass.block_till_done() trigger_db_commit(hass) @@ -17,18 +18,45 @@ def wait_recording_done(hass): hass.block_till_done() -async def async_wait_recording_done(hass): +async def async_wait_recording_done_without_instance(hass: HomeAssistantType) -> None: """Block till recording is done.""" await hass.loop.run_in_executor(None, wait_recording_done, hass) -def trigger_db_commit(hass): +def trigger_db_commit(hass: HomeAssistantType) -> None: """Force the recorder to commit.""" for _ in range(recorder.DEFAULT_COMMIT_INTERVAL): # We only commit on time change fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1)) +async def async_wait_recording_done( + hass: HomeAssistantType, + instance: recorder.Recorder, +) -> None: + """Async wait until recording is done.""" + await hass.async_block_till_done() + async_trigger_db_commit(hass) + await hass.async_block_till_done() + await async_recorder_block_till_done(hass, instance) + await hass.async_block_till_done() + + +@ha.callback +def async_trigger_db_commit(hass: HomeAssistantType) -> None: + """Fore the recorder to commit. Async friendly.""" + for _ in range(recorder.DEFAULT_COMMIT_INTERVAL): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1)) + + +async def async_recorder_block_till_done( + hass: HomeAssistantType, + instance: recorder.Recorder, +) -> None: + """Non blocking version of recorder.block_till_done().""" + await hass.async_add_executor_job(instance.block_till_done) + + def corrupt_db_file(test_db_file): """Corrupt an sqlite3 database file.""" with open(test_db_file, "w+") as fhandle: diff --git a/tests/components/recorder/conftest.py b/tests/components/recorder/conftest.py index d91a86402ac52d..6eadb1c62ed000 100644 --- a/tests/components/recorder/conftest.py +++ b/tests/components/recorder/conftest.py @@ -1,10 +1,24 @@ """Common test tools.""" +from __future__ import annotations + +from collections.abc import AsyncGenerator +from typing import Awaitable, Callable, cast import pytest +from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + +from .common import async_recorder_block_till_done -from tests.common import get_test_home_assistant, init_recorder_component +from tests.common import ( + async_init_recorder_component, + get_test_home_assistant, + init_recorder_component, +) + +SetupRecorderInstanceT = Callable[..., Awaitable[Recorder]] @pytest.fixture @@ -22,3 +36,23 @@ def setup_recorder(config=None): yield setup_recorder hass.stop() + + +@pytest.fixture +async def async_setup_recorder_instance() -> AsyncGenerator[ + SetupRecorderInstanceT, None +]: + """Yield callable to setup recorder instance.""" + + async def async_setup_recorder( + hass: HomeAssistantType, config: ConfigType | None = None + ) -> Recorder: + """Setup and return recorder instance.""" # noqa: D401 + await async_init_recorder_component(hass, config) + await hass.async_block_till_done() + instance = cast(Recorder, hass.data[DATA_INSTANCE]) + await async_recorder_block_till_done(hass, instance) + assert isinstance(instance, Recorder) + return instance + + yield async_setup_recorder diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 63f4b9887c6c53..b3c58995b37e26 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -28,10 +28,17 @@ STATE_UNLOCKED, ) from homeassistant.core import Context, CoreState, callback +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component, setup_component from homeassistant.util import dt as dt_util -from .common import async_wait_recording_done, corrupt_db_file, wait_recording_done +from .common import ( + async_wait_recording_done, + async_wait_recording_done_without_instance, + corrupt_db_file, + wait_recording_done, +) +from .conftest import SetupRecorderInstanceT from tests.common import ( async_init_recorder_component, @@ -62,17 +69,19 @@ async def test_shutdown_before_startup_finishes(hass): assert run_info.end is not None -def test_saving_state(hass, hass_recorder): +async def test_saving_state( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test saving and restoring a state.""" - hass = hass_recorder() + instance = await async_setup_recorder_instance(hass) entity_id = "test.recorder" state = "restoring_from_db" attributes = {"test_attr": 5, "test_attr_10": "nice"} - hass.states.set(entity_id, state, attributes) + hass.states.async_set(entity_id, state, attributes) - wait_recording_done(hass) + await async_wait_recording_done(hass, instance) with session_scope(hass=hass) as session: db_states = list(session.query(States)) @@ -690,15 +699,15 @@ def _create_tmpdir_for_test_db(): hass.states.async_set("test.lost", "on", {}) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) await hass.async_add_executor_job(corrupt_db_file, test_db_file) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) # This state will not be recorded because # the database corruption will be discovered # and we will have to rollback to recover hass.states.async_set("test.one", "off", {}) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) assert "Unrecoverable sqlite3 database corruption detected" in caplog.text assert "The system will rename the corrupt database file" in caplog.text @@ -706,7 +715,7 @@ def _create_tmpdir_for_test_db(): # This state should go into the new database hass.states.async_set("test.two", "on", {}) - await async_wait_recording_done(hass) + await async_wait_recording_done_without_instance(hass) def _get_last_state(): with session_scope(hass=hass) as session: diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index aaf5300086531b..3535a58d33d3d1 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -3,19 +3,23 @@ import json from homeassistant.components import recorder -from homeassistant.components.recorder.const import DATA_INSTANCE from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.purge import purge_old_data from homeassistant.components.recorder.util import session_scope +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util -from .common import wait_recording_done +from .common import async_wait_recording_done +from .conftest import SetupRecorderInstanceT -def test_purge_old_states(hass, hass_recorder): +async def test_purge_old_states( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test deleting old states.""" - hass = hass_recorder() - _add_test_states(hass) + instance = await async_setup_recorder_instance(hass) + + await _add_test_states(hass, instance) # make sure we start with 6 states with session_scope(hass=hass) as session: @@ -28,7 +32,7 @@ def test_purge_old_states(hass, hass_recorder): assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert not finished assert states.count() == 2 @@ -36,35 +40,41 @@ def test_purge_old_states(hass, hass_recorder): assert states_after_purge[1].old_state_id == states_after_purge[0].state_id assert states_after_purge[0].old_state_id is None - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert finished assert states.count() == 2 -def test_purge_old_events(hass, hass_recorder): +async def test_purge_old_events( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test deleting old events.""" - hass = hass_recorder() - _add_test_events(hass) + instance = await async_setup_recorder_instance(hass) + + await _add_test_events(hass, instance) with session_scope(hass=hass) as session: events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%")) assert events.count() == 6 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert not finished assert events.count() == 2 # we should only have 2 events left - finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(instance, 4, repack=False) assert finished assert events.count() == 2 -def test_purge_old_recorder_runs(hass, hass_recorder): +async def test_purge_old_recorder_runs( + hass: HomeAssistantType, async_setup_recorder_instance: SetupRecorderInstanceT +): """Test deleting old recorder runs keeps current run.""" - hass = hass_recorder() - _add_test_recorder_runs(hass) + instance = await async_setup_recorder_instance(hass) + + await _add_test_recorder_runs(hass, instance) # make sure we start with 7 recorder runs with session_scope(hass=hass) as session: @@ -72,21 +82,26 @@ def test_purge_old_recorder_runs(hass, hass_recorder): assert recorder_runs.count() == 7 # run purge_old_data() - finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) + finished = purge_old_data(instance, 0, repack=False) assert not finished - finished = purge_old_data(hass.data[DATA_INSTANCE], 0, repack=False) + finished = purge_old_data(instance, 0, repack=False) assert finished assert recorder_runs.count() == 1 -def test_purge_method(hass, hass_recorder, caplog): +async def test_purge_method( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, + caplog, +): """Test purge method.""" - hass = hass_recorder() + instance = await async_setup_recorder_instance(hass) + service_data = {"keep_days": 4} - _add_test_events(hass) - _add_test_states(hass) - _add_test_recorder_runs(hass) + await _add_test_events(hass, instance) + await _add_test_states(hass, instance) + await _add_test_recorder_runs(hass, instance) # make sure we start with 6 states with session_scope(hass=hass) as session: @@ -99,28 +114,26 @@ def test_purge_method(hass, hass_recorder, caplog): recorder_runs = session.query(RecorderRuns) assert recorder_runs.count() == 7 - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) # run purge method - no service data, use defaults - hass.services.call("recorder", "purge") - hass.block_till_done() + await hass.services.async_call("recorder", "purge") + await hass.async_block_till_done() # Small wait for recorder thread - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await async_wait_recording_done(hass, instance) # only purged old events assert states.count() == 4 assert events.count() == 4 # run purge method - correct service data - hass.services.call("recorder", "purge", service_data=service_data) - hass.block_till_done() + await hass.services.async_call("recorder", "purge", service_data=service_data) + await hass.async_block_till_done() # Small wait for recorder thread - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await async_wait_recording_done(hass, instance) # we should only have 2 states left after purging assert states.count() == 2 @@ -135,23 +148,21 @@ def test_purge_method(hass, hass_recorder, caplog): # run purge method - correct service data, with repack service_data["repack"] = True - hass.services.call("recorder", "purge", service_data=service_data) - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.services.async_call("recorder", "purge", service_data=service_data) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) assert "Vacuuming SQL DB to free space" in caplog.text -def _add_test_states(hass): +async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): """Add multiple states to the db for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) attributes = {"test_attr": 5, "test_attr_10": "nice"} - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: old_state_id = None @@ -191,16 +202,15 @@ def _add_test_states(hass): old_state_id = state.state_id -def _add_test_events(hass): +async def _add_test_events(hass: HomeAssistantType, instance: recorder.Recorder): """Add a few events for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) event_data = {"test_attr": 5, "test_attr_10": "nice"} - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: for event_id in range(6): @@ -225,15 +235,14 @@ def _add_test_events(hass): ) -def _add_test_recorder_runs(hass): +async def _add_test_recorder_runs(hass: HomeAssistantType, instance: recorder.Recorder): """Add a few recorder_runs for testing.""" utcnow = dt_util.utcnow() five_days_ago = utcnow - timedelta(days=5) eleven_days_ago = utcnow - timedelta(days=11) - hass.block_till_done() - hass.data[DATA_INSTANCE].block_till_done() - wait_recording_done(hass) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) with recorder.session_scope(hass=hass) as session: for rec_id in range(6): From 1095905f8c031eafab182b27bb1f3601b5d839a5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 11 Mar 2021 19:41:01 +0100 Subject: [PATCH 1192/1818] Add DataUpdateCoordinator to Verisure (#47574) --- homeassistant/components/verisure/__init__.py | 98 +++++++----- .../verisure/alarm_control_panel.py | 102 +++++++------ .../components/verisure/binary_sensor.py | 48 +++--- homeassistant/components/verisure/camera.py | 53 ++++--- homeassistant/components/verisure/lock.py | 108 ++++++------- homeassistant/components/verisure/sensor.py | 89 +++++------ homeassistant/components/verisure/switch.py | 51 ++++--- requirements_test_all.txt | 3 - tests/components/verisure/__init__.py | 1 - .../verisure/test_ethernet_status.py | 67 -------- tests/components/verisure/test_lock.py | 144 ------------------ 11 files changed, 288 insertions(+), 476 deletions(-) delete mode 100644 tests/components/verisure/__init__.py delete mode 100644 tests/components/verisure/test_ethernet_status.py delete mode 100644 tests/components/verisure/test_lock.py diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index ccb479814abcfc..00d970f133f6d4 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -5,7 +5,11 @@ from typing import Any, Literal from jsonpath import jsonpath -import verisure +from verisure import ( + Error as VerisureError, + ResponseError as VerisureResponseError, + Session as Verisure, +) import voluptuous as vol from homeassistant.const import ( @@ -19,6 +23,7 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import Throttle from .const import ( @@ -52,8 +57,6 @@ "binary_sensor", ] -HUB = None - CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -83,31 +86,43 @@ DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) -def setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Verisure integration.""" - global HUB # pylint: disable=global-statement - HUB = VerisureHub(config[DOMAIN]) - HUB.update_overview = Throttle(config[DOMAIN][CONF_SCAN_INTERVAL])( - HUB.update_overview + verisure = Verisure(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) + coordinator = VerisureDataUpdateCoordinator( + hass, session=verisure, domain_config=config[DOMAIN] ) - if not HUB.login(): + + if not await hass.async_add_executor_job(coordinator.login): + LOGGER.error("Login failed") + return False + + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, lambda event: coordinator.logout() + ) + + await coordinator.async_refresh() + if not coordinator.last_update_success: + LOGGER.error("Update failed") return False - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: HUB.logout()) - HUB.update_overview() + + hass.data[DOMAIN] = coordinator for platform in PLATFORMS: - discovery.load_platform(hass, platform, DOMAIN, {}, config) + hass.async_create_task( + discovery.async_load_platform(hass, platform, DOMAIN, {}, config) + ) async def capture_smartcam(service): """Capture a new picture from a smartcam.""" device_id = service.data[ATTR_DEVICE_SERIAL] try: - await hass.async_add_executor_job(HUB.smartcam_capture, device_id) + await hass.async_add_executor_job(coordinator.smartcam_capture, device_id) LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not capture image, %s", ex) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA ) @@ -115,12 +130,12 @@ async def disable_autolock(service): """Disable autolock on a doorlock.""" device_id = service.data[ATTR_DEVICE_SERIAL] try: - await hass.async_add_executor_job(HUB.disable_autolock, device_id) + await hass.async_add_executor_job(coordinator.disable_autolock, device_id) LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not disable autolock, %s", ex) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA ) @@ -128,38 +143,39 @@ async def enable_autolock(service): """Enable autolock on a doorlock.""" device_id = service.data[ATTR_DEVICE_SERIAL] try: - await hass.async_add_executor_job(HUB.enable_autolock, device_id) + await hass.async_add_executor_job(coordinator.enable_autolock, device_id) LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not enable autolock, %s", ex) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA ) return True -class VerisureHub: - """A Verisure hub wrapper class.""" +class VerisureDataUpdateCoordinator(DataUpdateCoordinator): + """A Verisure Data Update Coordinator.""" - def __init__(self, domain_config: ConfigType): + def __init__( + self, hass: HomeAssistant, domain_config: ConfigType, session: Verisure + ) -> None: """Initialize the Verisure hub.""" - self.overview = {} self.imageseries = {} - self.config = domain_config + self.giid = domain_config.get(CONF_GIID) - self.session = verisure.Session( - domain_config[CONF_USERNAME], domain_config[CONF_PASSWORD] - ) + self.session = session - self.giid = domain_config.get(CONF_GIID) + super().__init__( + hass, LOGGER, name=DOMAIN, update_interval=domain_config[CONF_SCAN_INTERVAL] + ) def login(self) -> bool: """Login to Verisure.""" try: self.session.login() - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not log in to verisure, %s", ex) return False if self.giid: @@ -170,7 +186,7 @@ def logout(self) -> bool: """Logout from Verisure.""" try: self.session.logout() - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not log out from verisure, %s", ex) return False return True @@ -179,22 +195,22 @@ def set_giid(self) -> bool: """Set installation GIID.""" try: self.session.set_giid(self.giid) - except verisure.Error as ex: + except VerisureError as ex: LOGGER.error("Could not set installation GIID, %s", ex) return False return True - def update_overview(self) -> None: - """Update the overview.""" + async def _async_update_data(self) -> dict: + """Fetch data from Verisure.""" try: - self.overview = self.session.get_overview() - except verisure.ResponseError as ex: + return await self.hass.async_add_executor_job(self.session.get_overview) + except VerisureResponseError as ex: LOGGER.error("Could not read overview, %s", ex) if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable LOGGER.info("Trying to log in again") - self.login() - else: - raise + await self.hass.async_add_executor_job(self.login) + return {} + raise @Throttle(timedelta(seconds=60)) def update_smartcam_imageseries(self) -> None: @@ -216,7 +232,7 @@ def enable_autolock(self, device_id: str) -> None: def get(self, jpath: str, *args) -> list[Any] | Literal[False]: """Get values from the overview that matches the jsonpath.""" - res = jsonpath(self.overview, jpath % args) + res = jsonpath(self.data, jpath % args) return res or [] def get_first(self, jpath: str, *args) -> Any | None: diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index c791bfc38dce83..d0a93fb45f925e 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -1,7 +1,7 @@ """Support for Verisure alarm control panels.""" from __future__ import annotations -from time import sleep +import asyncio from typing import Any, Callable from homeassistant.components.alarm_control_panel import ( @@ -16,12 +16,14 @@ STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_ALARM, CONF_CODE_DIGITS, CONF_GIID, LOGGER +from . import VerisureDataUpdateCoordinator +from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER def setup_platform( @@ -31,51 +33,53 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure platform.""" + coordinator = hass.data[DOMAIN] alarms = [] - if int(hub.config.get(CONF_ALARM, 1)): - hub.update_overview() - alarms.append(VerisureAlarm()) + if int(coordinator.config.get(CONF_ALARM, 1)): + alarms.append(VerisureAlarm(coordinator)) add_entities(alarms) -def set_arm_state(state: str, code: str | None = None) -> None: - """Send set arm state command.""" - transaction_id = hub.session.set_arm_state(code, state)[ - "armStateChangeTransactionId" - ] - LOGGER.info("verisure set arm state %s", state) - transaction = {} - while "result" not in transaction: - sleep(0.5) - transaction = hub.session.get_arm_state_transaction(transaction_id) - hub.update_overview() - - -class VerisureAlarm(AlarmControlPanelEntity): +class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): """Representation of a Verisure alarm status.""" - def __init__(self): + coordinator: VerisureDataUpdateCoordinator + + def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None: """Initialize the Verisure alarm panel.""" + super().__init__(coordinator) self._state = None - self._digits = hub.config.get(CONF_CODE_DIGITS) - self._changed_by = None @property def name(self) -> str: """Return the name of the device.""" - giid = hub.config.get(CONF_GIID) + giid = self.coordinator.config.get(CONF_GIID) if giid is not None: - aliass = {i["giid"]: i["alias"] for i in hub.session.installations} + aliass = { + i["giid"]: i["alias"] for i in self.coordinator.session.installations + } if giid in aliass: return "{} alarm".format(aliass[giid]) LOGGER.error("Verisure installation giid not found: %s", giid) - return "{} alarm".format(hub.session.installations[0]["alias"]) + return "{} alarm".format(self.coordinator.session.installations[0]["alias"]) @property def state(self) -> str | None: """Return the state of the device.""" + status = self.coordinator.get_first("$.armState.statusType") + if status == "DISARMED": + self._state = STATE_ALARM_DISARMED + elif status == "ARMED_HOME": + self._state = STATE_ALARM_ARMED_HOME + elif status == "ARMED_AWAY": + self._state = STATE_ALARM_ARMED_AWAY + elif status == "PENDING": + self._state = STATE_ALARM_PENDING + else: + LOGGER.error("Unknown alarm state %s", status) + return self._state @property @@ -91,30 +95,32 @@ def code_format(self) -> str: @property def changed_by(self) -> str | None: """Return the last change triggered by.""" - return self._changed_by - - def update(self) -> None: - """Update alarm status.""" - hub.update_overview() - status = hub.get_first("$.armState.statusType") - if status == "DISARMED": - self._state = STATE_ALARM_DISARMED - elif status == "ARMED_HOME": - self._state = STATE_ALARM_ARMED_HOME - elif status == "ARMED_AWAY": - self._state = STATE_ALARM_ARMED_AWAY - elif status != "PENDING": - LOGGER.error("Unknown alarm state %s", status) - self._changed_by = hub.get_first("$.armState.name") - - def alarm_disarm(self, code: str | None = None) -> None: + return self.coordinator.get_first("$.armState.name") + + async def _async_set_arm_state(self, state: str, code: str | None = None) -> None: + """Send set arm state command.""" + arm_state = await self.hass.async_add_executor_job( + self.coordinator.session.set_arm_state, code, state + ) + LOGGER.debug("Verisure set arm state %s", state) + transaction = {} + while "result" not in transaction: + await asyncio.sleep(0.5) + transaction = await self.hass.async_add_executor_job( + self.coordinator.session.get_arm_state_transaction, + arm_state["armStateChangeTransactionId"], + ) + + await self.coordinator.async_refresh() + + async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - set_arm_state("DISARMED", code) + await self._async_set_arm_state("DISARMED", code) - def alarm_arm_home(self, code: str | None = None) -> None: + async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" - set_arm_state("ARMED_HOME", code) + await self._async_set_arm_state("ARMED_HOME", code) - def alarm_arm_away(self, code: str | None = None) -> None: + async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" - set_arm_state("ARMED_AWAY", code) + await self._async_set_arm_state("ARMED_AWAY", code) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index e30f008dba9405..bdefa2af85888f 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -9,8 +9,9 @@ ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_DOOR_WINDOW, HUB as hub +from . import CONF_DOOR_WINDOW, DOMAIN, VerisureDataUpdateCoordinator def setup_platform( @@ -20,34 +21,39 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure binary sensors.""" - sensors = [] - hub.update_overview() + coordinator = hass.data[DOMAIN] - if int(hub.config.get(CONF_DOOR_WINDOW, 1)): + sensors = [VerisureEthernetStatus(coordinator)] + + if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)): sensors.extend( [ - VerisureDoorWindowSensor(device_label) - for device_label in hub.get( + VerisureDoorWindowSensor(coordinator, device_label) + for device_label in coordinator.get( "$.doorWindow.doorWindowDevice[*].deviceLabel" ) ] ) - sensors.extend([VerisureEthernetStatus()]) add_entities(sensors) -class VerisureDoorWindowSensor(BinarySensorEntity): +class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): """Representation of a Verisure door window sensor.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the Verisure door window sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the binary sensor.""" - return hub.get_first( + return self.coordinator.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", self._device_label, ) @@ -56,7 +62,7 @@ def name(self) -> str: def is_on(self) -> bool: """Return the state of the sensor.""" return ( - hub.get_first( + self.coordinator.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state", self._device_label, ) @@ -67,22 +73,19 @@ def is_on(self) -> bool: def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", self._device_label, ) is not None ) - # pylint: disable=no-self-use - def update(self) -> None: - """Update the state of the sensor.""" - hub.update_overview() - -class VerisureEthernetStatus(BinarySensorEntity): +class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): """Representation of a Verisure VBOX internet status.""" + coordinator: VerisureDataUpdateCoordinator + @property def name(self) -> str: """Return the name of the binary sensor.""" @@ -91,17 +94,12 @@ def name(self) -> str: @property def is_on(self) -> bool: """Return the state of the sensor.""" - return hub.get_first("$.ethernetConnectedNow") + return self.coordinator.get_first("$.ethernetConnectedNow") @property def available(self) -> bool: """Return True if entity is available.""" - return hub.get_first("$.ethernetConnectedNow") is not None - - # pylint: disable=no-self-use - def update(self) -> None: - """Update the state of the sensor.""" - hub.update_overview() + return self.coordinator.get_first("$.ethernetConnectedNow") is not None @property def device_class(self) -> str: diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index ad6840c0614c38..4e15b7a88b2fb0 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -3,15 +3,16 @@ import errno import os -from typing import Any, Callable, Literal +from typing import Any, Callable from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_SMARTCAM, LOGGER +from . import VerisureDataUpdateCoordinator +from .const import CONF_SMARTCAM, DOMAIN, LOGGER def setup_platform( @@ -19,31 +20,39 @@ def setup_platform( config: dict[str, Any], add_entities: Callable[[list[Entity], bool], None], discovery_info: dict[str, Any] | None = None, -) -> None | Literal[False]: +) -> None: """Set up the Verisure Camera.""" - if not int(hub.config.get(CONF_SMARTCAM, 1)): - return False + coordinator = hass.data[DOMAIN] + if not int(coordinator.config.get(CONF_SMARTCAM, 1)): + return directory_path = hass.config.config_dir if not os.access(directory_path, os.R_OK): LOGGER.error("file path %s is not readable", directory_path) - return False + return - hub.update_overview() - smartcams = [ - VerisureSmartcam(hass, device_label, directory_path) - for device_label in hub.get("$.customerImageCameras[*].deviceLabel") - ] + add_entities( + [ + VerisureSmartcam(hass, coordinator, device_label, directory_path) + for device_label in coordinator.get("$.customerImageCameras[*].deviceLabel") + ] + ) - add_entities(smartcams) - -class VerisureSmartcam(Camera): +class VerisureSmartcam(CoordinatorEntity, Camera): """Representation of a Verisure camera.""" - def __init__(self, hass: HomeAssistant, device_label: str, directory_path: str): + coordinator = VerisureDataUpdateCoordinator + + def __init__( + self, + hass: HomeAssistant, + coordinator: VerisureDataUpdateCoordinator, + device_label: str, + directory_path: str, + ): """Initialize Verisure File Camera component.""" - super().__init__() + super().__init__(coordinator) self._device_label = device_label self._directory_path = directory_path @@ -63,8 +72,8 @@ def camera_image(self) -> bytes | None: def check_imagelist(self) -> None: """Check the contents of the image list.""" - hub.update_smartcam_imageseries() - image_ids = hub.get_image_info( + self.coordinator.update_smartcam_imageseries() + image_ids = self.coordinator.get_image_info( "$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label ) if not image_ids: @@ -77,7 +86,9 @@ def check_imagelist(self) -> None: new_image_path = os.path.join( self._directory_path, "{}{}".format(new_image_id, ".jpg") ) - hub.session.download_image(self._device_label, new_image_id, new_image_path) + self.coordinator.session.download_image( + self._device_label, new_image_id, new_image_path + ) LOGGER.debug("Old image_id=%s", self._image_id) self.delete_image() @@ -99,6 +110,6 @@ def delete_image(self) -> None: @property def name(self) -> str: """Return the name of this camera.""" - return hub.get_first( + return self.coordinator.get_first( "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label ) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index b2e1cfb3db05c7..8fc067308fb996 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,16 +1,17 @@ """Support for Verisure locks.""" from __future__ import annotations -from time import monotonic, sleep +import asyncio from typing import Any, Callable from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, LOGGER +from . import VerisureDataUpdateCoordinator +from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER def setup_platform( @@ -20,48 +21,48 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure lock platform.""" + coordinator = hass.data[DOMAIN] locks = [] - if int(hub.config.get(CONF_LOCKS, 1)): - hub.update_overview() + if int(coordinator.config.get(CONF_LOCKS, 1)): locks.extend( [ - VerisureDoorlock(device_label) - for device_label in hub.get("$.doorLockStatusList[*].deviceLabel") + VerisureDoorlock(coordinator, device_label) + for device_label in coordinator.get( + "$.doorLockStatusList[*].deviceLabel" + ) ] ) add_entities(locks) -class VerisureDoorlock(LockEntity): +class VerisureDoorlock(CoordinatorEntity, LockEntity): """Representation of a Verisure doorlock.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the Verisure lock.""" + super().__init__(coordinator) self._device_label = device_label self._state = None - self._digits = hub.config.get(CONF_CODE_DIGITS) - self._changed_by = None - self._change_timestamp = 0 - self._default_lock_code = hub.config.get(CONF_DEFAULT_LOCK_CODE) + self._digits = coordinator.config.get(CONF_CODE_DIGITS) + self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE) @property def name(self) -> str: """Return the name of the lock.""" - return hub.get_first( + return self.coordinator.get_first( "$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label ) - @property - def state(self) -> str | None: - """Return the state of the lock.""" - return self._state - @property def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.doorLockStatusList[?(@.deviceLabel=='%s')]", self._device_label ) is not None @@ -70,78 +71,65 @@ def available(self) -> bool: @property def changed_by(self) -> str | None: """Last change triggered by.""" - return self._changed_by + return self.coordinator.get_first( + "$.doorLockStatusList[?(@.deviceLabel=='%s')].userString", + self._device_label, + ) @property def code_format(self) -> str: """Return the required six digit code.""" return "^\\d{%s}$" % self._digits - def update(self) -> None: - """Update lock status.""" - if monotonic() - self._change_timestamp < 10: - return - hub.update_overview() - status = hub.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", - self._device_label, - ) - if status == "UNLOCKED": - self._state = STATE_UNLOCKED - elif status == "LOCKED": - self._state = STATE_LOCKED - elif status != "PENDING": - LOGGER.error("Unknown lock state %s", status) - self._changed_by = hub.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].userString", - self._device_label, - ) - @property def is_locked(self) -> bool: """Return true if lock is locked.""" - return self._state == STATE_LOCKED + status = self.coordinator.get_first( + "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", + self._device_label, + ) + return status == "LOCKED" - def unlock(self, **kwargs) -> None: + async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" - if self._state is None: - return - code = kwargs.get(ATTR_CODE, self._default_lock_code) if code is None: LOGGER.error("Code required but none provided") return - self.set_lock_state(code, STATE_UNLOCKED) + await self.async_set_lock_state(code, STATE_UNLOCKED) - def lock(self, **kwargs) -> None: + async def async_lock(self, **kwargs) -> None: """Send lock command.""" - if self._state == STATE_LOCKED: - return - code = kwargs.get(ATTR_CODE, self._default_lock_code) if code is None: LOGGER.error("Code required but none provided") return - self.set_lock_state(code, STATE_LOCKED) + await self.async_set_lock_state(code, STATE_LOCKED) - def set_lock_state(self, code: str, state: str) -> None: + async def async_set_lock_state(self, code: str, state: str) -> None: """Send set lock state command.""" - lock_state = "lock" if state == STATE_LOCKED else "unlock" - transaction_id = hub.session.set_lock_state( - code, self._device_label, lock_state - )["doorLockStateChangeTransactionId"] + target_state = "lock" if state == STATE_LOCKED else "unlock" + lock_state = await self.hass.async_add_executor_job( + self.coordinator.session.set_lock_state, + code, + self._device_label, + target_state, + ) + LOGGER.debug("Verisure doorlock %s", state) transaction = {} attempts = 0 while "result" not in transaction: - transaction = hub.session.get_lock_state_transaction(transaction_id) + transaction = await self.hass.async_add_executor_job( + self.coordinator.session.get_lock_state_transaction, + lock_state["doorLockStateChangeTransactionId"], + ) attempts += 1 if attempts == 30: break if attempts > 1: - sleep(0.5) + await asyncio.sleep(0.5) if transaction["result"] == "OK": self._state = state - self._change_timestamp = monotonic() diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 6582dfc409adfc..483d03a1bb5231 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -6,9 +6,10 @@ from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import HUB as hub -from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS +from . import VerisureDataUpdateCoordinator +from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN def setup_platform( @@ -18,34 +19,34 @@ def setup_platform( discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure platform.""" - sensors = [] - hub.update_overview() + coordinator = hass.data[DOMAIN] - if int(hub.config.get(CONF_THERMOMETERS, 1)): + sensors = [] + if int(coordinator.config.get(CONF_THERMOMETERS, 1)): sensors.extend( [ - VerisureThermometer(device_label) - for device_label in hub.get( + VerisureThermometer(coordinator, device_label) + for device_label in coordinator.get( "$.climateValues[?(@.temperature)].deviceLabel" ) ] ) - if int(hub.config.get(CONF_HYDROMETERS, 1)): + if int(coordinator.config.get(CONF_HYDROMETERS, 1)): sensors.extend( [ - VerisureHygrometer(device_label) - for device_label in hub.get( + VerisureHygrometer(coordinator, device_label) + for device_label in coordinator.get( "$.climateValues[?(@.humidity)].deviceLabel" ) ] ) - if int(hub.config.get(CONF_MOUSE, 1)): + if int(coordinator.config.get(CONF_MOUSE, 1)): sensors.extend( [ - VerisureMouseDetection(device_label) - for device_label in hub.get( + VerisureMouseDetection(coordinator, device_label) + for device_label in coordinator.get( "$.eventCounts[?(@.deviceType=='MOUSE1')].deviceLabel" ) ] @@ -54,18 +55,23 @@ def setup_platform( add_entities(sensors) -class VerisureThermometer(Entity): +class VerisureThermometer(CoordinatorEntity, Entity): """Representation of a Verisure thermometer.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the device.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label ) + " temperature" @@ -74,7 +80,7 @@ def name(self) -> str: @property def state(self) -> str | None: """Return the state of the device.""" - return hub.get_first( + return self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label ) @@ -82,7 +88,7 @@ def state(self) -> str | None: def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label, ) @@ -94,24 +100,24 @@ def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return TEMP_CELSIUS - # pylint: disable=no-self-use - def update(self) -> None: - """Update the sensor.""" - hub.update_overview() - -class VerisureHygrometer(Entity): +class VerisureHygrometer(CoordinatorEntity, Entity): """Representation of a Verisure hygrometer.""" - def __init__(self, device_label: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the device.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label ) + " humidity" @@ -120,7 +126,7 @@ def name(self) -> str: @property def state(self) -> str | None: """Return the state of the device.""" - return hub.get_first( + return self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label ) @@ -128,7 +134,7 @@ def state(self) -> str | None: def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first( + self.coordinator.get_first( "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label ) is not None @@ -139,24 +145,24 @@ def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return PERCENTAGE - # pylint: disable=no-self-use - def update(self) -> None: - """Update the sensor.""" - hub.update_overview() - -class VerisureMouseDetection(Entity): +class VerisureMouseDetection(CoordinatorEntity, Entity): """Representation of a Verisure mouse detector.""" - def __init__(self, device_label): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_label: str + ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self._device_label = device_label @property def name(self) -> str: """Return the name of the device.""" return ( - hub.get_first( + self.coordinator.get_first( "$.eventCounts[?(@.deviceLabel=='%s')].area", self._device_label ) + " mouse" @@ -165,7 +171,7 @@ def name(self) -> str: @property def state(self) -> str | None: """Return the state of the device.""" - return hub.get_first( + return self.coordinator.get_first( "$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label ) @@ -173,7 +179,9 @@ def state(self) -> str | None: def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first("$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label) + self.coordinator.get_first( + "$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label + ) is not None ) @@ -181,8 +189,3 @@ def available(self) -> bool: def unit_of_measurement(self) -> str: """Return the unit of measurement of this entity.""" return "Mice" - - # pylint: disable=no-self-use - def update(self) -> None: - """Update the sensor.""" - hub.update_overview() diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index e5e19bd6d13109..9329d94331a087 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,13 +2,15 @@ from __future__ import annotations from time import monotonic -from typing import Any, Callable, Literal +from typing import Any, Callable from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_SMARTPLUGS, HUB as hub +from . import VerisureDataUpdateCoordinator +from .const import CONF_SMARTPLUGS, DOMAIN def setup_platform( @@ -16,25 +18,31 @@ def setup_platform( config: dict[str, Any], add_entities: Callable[[list[Entity], bool], None], discovery_info: dict[str, Any] | None = None, -) -> None | Literal[False]: +) -> None: """Set up the Verisure switch platform.""" - if not int(hub.config.get(CONF_SMARTPLUGS, 1)): - return False + coordinator = hass.data[DOMAIN] - hub.update_overview() - switches = [ - VerisureSmartplug(device_label) - for device_label in hub.get("$.smartPlugs[*].deviceLabel") - ] + if not int(coordinator.config.get(CONF_SMARTPLUGS, 1)): + return - add_entities(switches) + add_entities( + [ + VerisureSmartplug(coordinator, device_label) + for device_label in coordinator.get("$.smartPlugs[*].deviceLabel") + ] + ) -class VerisureSmartplug(SwitchEntity): +class VerisureSmartplug(CoordinatorEntity, SwitchEntity): """Representation of a Verisure smartplug.""" - def __init__(self, device_id: str): + coordinator: VerisureDataUpdateCoordinator + + def __init__( + self, coordinator: VerisureDataUpdateCoordinator, device_id: str + ) -> None: """Initialize the Verisure device.""" + super().__init__(coordinator) self._device_label = device_id self._change_timestamp = 0 self._state = False @@ -42,7 +50,7 @@ def __init__(self, device_id: str): @property def name(self) -> str: """Return the name or location of the smartplug.""" - return hub.get_first( + return self.coordinator.get_first( "$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label ) @@ -52,7 +60,7 @@ def is_on(self) -> bool: if monotonic() - self._change_timestamp < 10: return self._state self._state = ( - hub.get_first( + self.coordinator.get_first( "$.smartPlugs[?(@.deviceLabel == '%s')].currentState", self._device_label, ) @@ -64,23 +72,20 @@ def is_on(self) -> bool: def available(self) -> bool: """Return True if entity is available.""" return ( - hub.get_first("$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label) + self.coordinator.get_first( + "$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label + ) is not None ) def turn_on(self, **kwargs) -> None: """Set smartplug status on.""" - hub.session.set_smartplug_state(self._device_label, True) + self.coordinator.session.set_smartplug_state(self._device_label, True) self._state = True self._change_timestamp = monotonic() def turn_off(self, **kwargs) -> None: """Set smartplug status off.""" - hub.session.set_smartplug_state(self._device_label, False) + self.coordinator.session.set_smartplug_state(self._device_label, False) self._state = False self._change_timestamp = monotonic() - - # pylint: disable=no-self-use - def update(self) -> None: - """Get the latest date of the smartplug.""" - hub.update_overview() diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 94f1bbba4649f1..2b77de8022bd6a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1166,9 +1166,6 @@ uvcclient==0.11.0 # homeassistant.components.vilfo vilfo-api-client==0.3.2 -# homeassistant.components.verisure -vsure==1.7.2 - # homeassistant.components.vultr vultr==0.1.2 diff --git a/tests/components/verisure/__init__.py b/tests/components/verisure/__init__.py deleted file mode 100644 index 0382661dbe3edf..00000000000000 --- a/tests/components/verisure/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for Verisure integration.""" diff --git a/tests/components/verisure/test_ethernet_status.py b/tests/components/verisure/test_ethernet_status.py deleted file mode 100644 index 611adde19d9fd3..00000000000000 --- a/tests/components/verisure/test_ethernet_status.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Test Verisure ethernet status.""" -from contextlib import contextmanager -from unittest.mock import patch - -from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN -from homeassistant.const import STATE_UNAVAILABLE -from homeassistant.setup import async_setup_component - -CONFIG = { - "verisure": { - "username": "test", - "password": "test", - "alarm": False, - "door_window": False, - "hygrometers": False, - "mouse": False, - "smartplugs": False, - "thermometers": False, - "smartcam": False, - } -} - - -@contextmanager -def mock_hub(config, response): - """Extensively mock out a verisure hub.""" - hub_prefix = "homeassistant.components.verisure.binary_sensor.hub" - verisure_prefix = "verisure.Session" - with patch(verisure_prefix) as session, patch(hub_prefix) as hub: - session.login.return_value = True - - hub.config = config["verisure"] - hub.get.return_value = response - hub.get_first.return_value = response.get("ethernetConnectedNow", None) - - yield hub - - -async def setup_verisure(hass, config, response): - """Set up mock verisure.""" - with mock_hub(config, response): - await async_setup_component(hass, VERISURE_DOMAIN, config) - await hass.async_block_till_done() - - -async def test_verisure_no_ethernet_status(hass): - """Test no data from API.""" - await setup_verisure(hass, CONFIG, {}) - assert len(hass.states.async_all()) == 1 - entity_id = hass.states.async_entity_ids()[0] - assert hass.states.get(entity_id).state == STATE_UNAVAILABLE - - -async def test_verisure_ethernet_status_disconnected(hass): - """Test disconnected.""" - await setup_verisure(hass, CONFIG, {"ethernetConnectedNow": False}) - assert len(hass.states.async_all()) == 1 - entity_id = hass.states.async_entity_ids()[0] - assert hass.states.get(entity_id).state == "off" - - -async def test_verisure_ethernet_status_connected(hass): - """Test connected.""" - await setup_verisure(hass, CONFIG, {"ethernetConnectedNow": True}) - assert len(hass.states.async_all()) == 1 - entity_id = hass.states.async_entity_ids()[0] - assert hass.states.get(entity_id).state == "on" diff --git a/tests/components/verisure/test_lock.py b/tests/components/verisure/test_lock.py deleted file mode 100644 index d41bbab20379b2..00000000000000 --- a/tests/components/verisure/test_lock.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Tests for the Verisure platform.""" - -from contextlib import contextmanager -from unittest.mock import call, patch - -from homeassistant.components.lock import ( - DOMAIN as LOCK_DOMAIN, - SERVICE_LOCK, - SERVICE_UNLOCK, -) -from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN -from homeassistant.const import STATE_UNLOCKED -from homeassistant.setup import async_setup_component - -NO_DEFAULT_LOCK_CODE_CONFIG = { - "verisure": { - "username": "test", - "password": "test", - "locks": True, - "alarm": False, - "door_window": False, - "hygrometers": False, - "mouse": False, - "smartplugs": False, - "thermometers": False, - "smartcam": False, - } -} - -DEFAULT_LOCK_CODE_CONFIG = { - "verisure": { - "username": "test", - "password": "test", - "locks": True, - "default_lock_code": "9999", - "alarm": False, - "door_window": False, - "hygrometers": False, - "mouse": False, - "smartplugs": False, - "thermometers": False, - "smartcam": False, - } -} - -LOCKS = ["door_lock"] - - -@contextmanager -def mock_hub(config, get_response=LOCKS[0]): - """Extensively mock out a verisure hub.""" - hub_prefix = "homeassistant.components.verisure.lock.hub" - # Since there is no conf to disable ethernet status, mock hub for - # binary sensor too - hub_binary_sensor = "homeassistant.components.verisure.binary_sensor.hub" - verisure_prefix = "verisure.Session" - with patch(verisure_prefix) as session, patch(hub_prefix) as hub: - session.login.return_value = True - - hub.config = config["verisure"] - hub.get.return_value = LOCKS - hub.get_first.return_value = get_response.upper() - hub.session.set_lock_state.return_value = { - "doorLockStateChangeTransactionId": "test" - } - hub.session.get_lock_state_transaction.return_value = {"result": "OK"} - - with patch(hub_binary_sensor, hub): - yield hub - - -async def setup_verisure_locks(hass, config): - """Set up mock verisure locks.""" - with mock_hub(config): - await async_setup_component(hass, VERISURE_DOMAIN, config) - await hass.async_block_till_done() - # lock.door_lock, ethernet_status - assert len(hass.states.async_all()) == 2 - - -async def test_verisure_no_default_code(hass): - """Test configs without a default lock code.""" - await setup_verisure_locks(hass, NO_DEFAULT_LOCK_CODE_CONFIG) - with mock_hub(NO_DEFAULT_LOCK_CODE_CONFIG, STATE_UNLOCKED) as hub: - - mock = hub.session.set_lock_state - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_count == 0 - - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock", "code": "12345"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "lock") - - mock.reset_mock() - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_UNLOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_count == 0 - - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_UNLOCK, - {"entity_id": "lock.door_lock", "code": "12345"}, - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "unlock") - - -async def test_verisure_default_code(hass): - """Test configs with a default lock code.""" - await setup_verisure_locks(hass, DEFAULT_LOCK_CODE_CONFIG) - with mock_hub(DEFAULT_LOCK_CODE_CONFIG, STATE_UNLOCKED) as hub: - mock = hub.session.set_lock_state - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("9999", LOCKS[0], "lock") - - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_UNLOCK, {"entity_id": "lock.door_lock"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("9999", LOCKS[0], "unlock") - - await hass.services.async_call( - LOCK_DOMAIN, SERVICE_LOCK, {"entity_id": "lock.door_lock", "code": "12345"} - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "lock") - - await hass.services.async_call( - LOCK_DOMAIN, - SERVICE_UNLOCK, - {"entity_id": "lock.door_lock", "code": "12345"}, - ) - await hass.async_block_till_done() - assert mock.call_args == call("12345", LOCKS[0], "unlock") From 14a59d290a3f25e58a705a156945e3853257d9c4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 20:11:25 +0100 Subject: [PATCH 1193/1818] Update integrations j-o to override extra_state_attributes() (#47758) --- homeassistant/components/homematic/entity.py | 2 +- homeassistant/components/isy994/entity.py | 4 ++-- .../components/jewish_calendar/sensor.py | 4 ++-- .../components/kaiterra/air_quality.py | 2 +- .../components/keba/binary_sensor.py | 2 +- homeassistant/components/keba/sensor.py | 2 +- .../keenetic_ndms2/device_tracker.py | 2 +- homeassistant/components/kef/media_player.py | 2 +- homeassistant/components/kira/sensor.py | 2 +- homeassistant/components/kiwi/lock.py | 2 +- homeassistant/components/knx/binary_sensor.py | 2 +- homeassistant/components/lacrosse/sensor.py | 2 +- homeassistant/components/lastfm/sensor.py | 2 +- .../components/launch_library/sensor.py | 2 +- .../components/linode/binary_sensor.py | 2 +- homeassistant/components/linode/switch.py | 2 +- .../components/linux_battery/sensor.py | 2 +- homeassistant/components/litejet/light.py | 2 +- homeassistant/components/litejet/scene.py | 2 +- homeassistant/components/litejet/switch.py | 2 +- .../components/litterrobot/vacuum.py | 2 +- homeassistant/components/local_file/camera.py | 2 +- .../components/logi_circle/camera.py | 2 +- .../components/logi_circle/sensor.py | 2 +- homeassistant/components/london_air/sensor.py | 2 +- .../components/london_underground/sensor.py | 2 +- homeassistant/components/luftdaten/sensor.py | 2 +- .../components/lutron/binary_sensor.py | 2 +- homeassistant/components/lutron/cover.py | 2 +- homeassistant/components/lutron/light.py | 2 +- homeassistant/components/lutron/switch.py | 4 ++-- .../components/lutron_caseta/__init__.py | 2 +- .../components/lutron_caseta/binary_sensor.py | 2 +- homeassistant/components/lyft/sensor.py | 2 +- .../components/magicseaweed/sensor.py | 2 +- .../components/manual/alarm_control_panel.py | 2 +- .../manual_mqtt/alarm_control_panel.py | 2 +- homeassistant/components/maxcube/climate.py | 2 +- homeassistant/components/melcloud/climate.py | 4 ++-- .../components/melcloud/water_heater.py | 2 +- .../components/meteo_france/sensor.py | 6 ++--- .../components/meteoalarm/binary_sensor.py | 2 +- homeassistant/components/metoffice/sensor.py | 2 +- homeassistant/components/mfi/switch.py | 2 +- homeassistant/components/mhz19/sensor.py | 2 +- .../components/microsoft_face/__init__.py | 2 +- homeassistant/components/miflora/sensor.py | 2 +- .../components/mikrotik/device_tracker.py | 2 +- homeassistant/components/mill/climate.py | 2 +- homeassistant/components/min_max/sensor.py | 2 +- .../components/minecraft_server/__init__.py | 2 +- .../components/minecraft_server/sensor.py | 10 ++++---- .../components/mobile_app/device_tracker.py | 2 +- homeassistant/components/mobile_app/entity.py | 2 +- .../components/modem_callerid/sensor.py | 2 +- .../components/mold_indicator/sensor.py | 2 +- .../components/motion_blinds/cover.py | 2 +- .../components/motion_blinds/sensor.py | 4 ++-- .../mqtt/device_tracker/schema_discovery.py | 24 +++++++++---------- homeassistant/components/mqtt/mixins.py | 2 +- homeassistant/components/mqtt_room/sensor.py | 2 +- homeassistant/components/mvglive/sensor.py | 2 +- homeassistant/components/mychevy/sensor.py | 2 +- homeassistant/components/mysensors/device.py | 2 +- .../components/mysensors/device_tracker.py | 2 +- homeassistant/components/n26/sensor.py | 6 ++--- homeassistant/components/neato/camera.py | 2 +- homeassistant/components/neato/vacuum.py | 2 +- .../nederlandse_spoorwegen/sensor.py | 2 +- homeassistant/components/nello/lock.py | 2 +- homeassistant/components/netatmo/camera.py | 2 +- homeassistant/components/netatmo/climate.py | 2 +- homeassistant/components/netatmo/sensor.py | 2 +- homeassistant/components/nexia/climate.py | 4 ++-- homeassistant/components/nexia/entity.py | 2 +- homeassistant/components/nexia/scene.py | 4 ++-- homeassistant/components/nextbus/sensor.py | 2 +- homeassistant/components/nightscout/sensor.py | 2 +- homeassistant/components/nilu/air_quality.py | 2 +- .../components/nissan_leaf/__init__.py | 2 +- .../components/nissan_leaf/switch.py | 4 ++-- homeassistant/components/nmbs/sensor.py | 4 ++-- homeassistant/components/noaa_tides/sensor.py | 2 +- .../components/norway_air/air_quality.py | 2 +- homeassistant/components/notion/__init__.py | 2 +- .../components/nsw_fuel_station/sensor.py | 2 +- .../geo_location.py | 2 +- homeassistant/components/nuki/lock.py | 2 +- homeassistant/components/nut/sensor.py | 2 +- .../components/nx584/binary_sensor.py | 2 +- .../components/oasa_telematics/sensor.py | 2 +- homeassistant/components/ohmconnect/sensor.py | 2 +- homeassistant/components/omnilogic/common.py | 2 +- .../components/onewire/onewire_entities.py | 2 +- .../components/onkyo/media_player.py | 2 +- .../components/openexchangerates/sensor.py | 2 +- homeassistant/components/opengarage/cover.py | 12 +++++----- homeassistant/components/opensky/sensor.py | 2 +- homeassistant/components/openuv/__init__.py | 2 +- .../openweathermap/abstract_owm_sensor.py | 2 +- .../components/osramlightify/light.py | 2 +- homeassistant/components/ovo_energy/sensor.py | 8 +++---- .../components/owntracks/device_tracker.py | 2 +- homeassistant/components/ozw/climate.py | 4 ++-- homeassistant/components/ozw/entity.py | 2 +- homeassistant/components/ozw/sensor.py | 4 ++-- tests/components/kira/test_sensor.py | 2 +- tests/components/mfi/test_switch.py | 4 ++-- tests/components/mhz19/test_sensor.py | 4 ++-- tests/components/nx584/test_binary_sensor.py | 2 +- 110 files changed, 150 insertions(+), 150 deletions(-) diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index bb87d691fc01db..3643871c607317 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -71,7 +71,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" # Static attributes attr = { diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index a484b56b145b5e..f3dbe579dd85d9 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -134,7 +134,7 @@ class ISYNodeEntity(ISYEntity): """Representation of a ISY Nodebase (Node/Group) entity.""" @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Get the state attributes for the device. The 'aux_properties' in the pyisy Node class are combined with the @@ -186,7 +186,7 @@ def __init__(self, name: str, status, actions=None) -> None: self._actions = actions @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Get the state attributes for the device.""" attr = {} if self._actions: diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index 6881f29b963df9..14336b1f935b20 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -111,7 +111,7 @@ def make_zmanim(self, date): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._type != "holiday": return {} @@ -153,7 +153,7 @@ def device_class(self): return DEVICE_CLASS_TIMESTAMP @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} diff --git a/homeassistant/components/kaiterra/air_quality.py b/homeassistant/components/kaiterra/air_quality.py index ae5df387884fb5..68377d6b254f7a 100644 --- a/homeassistant/components/kaiterra/air_quality.py +++ b/homeassistant/components/kaiterra/air_quality.py @@ -96,7 +96,7 @@ def unique_id(self): return f"{self._device_id}_air_quality" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" data = {} attributes = [ diff --git a/homeassistant/components/keba/binary_sensor.py b/homeassistant/components/keba/binary_sensor.py index 3fed7bbf5ab50e..292924701559cf 100644 --- a/homeassistant/components/keba/binary_sensor.py +++ b/homeassistant/components/keba/binary_sensor.py @@ -71,7 +71,7 @@ def is_on(self): return self._is_on @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" return self._attributes diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index f7993c283938a4..ed85ccd06a62a5 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -114,7 +114,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" return self._attributes diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index 9df222a326c719..a8b0f943dd6f58 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -207,7 +207,7 @@ def available(self) -> bool: return self._router.available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.is_connected: return { diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index a36fe11ef652de..5316568ab52e91 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -395,7 +395,7 @@ async def async_will_remove_from_hass(self): self._update_dsp_task_remover = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the DSP settings of the KEF device.""" return self._dsp or {} diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index 2d6322918c71f1..a8c49d1c04b7ba 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -55,7 +55,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {CONF_DEVICE: self._device} diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py index 047eaa1ed3c2c9..6a94cc5a393c3d 100644 --- a/homeassistant/components/kiwi/lock.py +++ b/homeassistant/components/kiwi/lock.py @@ -86,7 +86,7 @@ def is_locked(self): return self._state == STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return self._device_attrs diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index f7ec3e80fa12d6..ecb79664afdd9b 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -38,7 +38,7 @@ def is_on(self): return self._device.is_on() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return device specific state attributes.""" if self._device.counter is not None: return {ATTR_COUNTER: self._device.counter} diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index f65c792ddb0e95..35cdcecddba8b7 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -138,7 +138,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = { "low_battery": self._low_battery, diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index 56124e2c0fee0a..2f599ad37fd1d5 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -107,7 +107,7 @@ def update(self): self._state = f"{now_playing.artist} - {now_playing.title}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index ef816eef0ba431..366cd4e7d442c3 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -76,7 +76,7 @@ def icon(self) -> str: return "mdi:rocket" @property - def device_state_attributes(self) -> Optional[dict]: + def extra_state_attributes(self) -> Optional[dict]: """Return attributes for the sensor.""" if self.next_launch: return { diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index bb81a022891139..70a15eaf4e0e3a 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -75,7 +75,7 @@ def device_class(self): return DEVICE_CLASS_MOVING @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Linode Node.""" return self._attrs diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py index c9207ec1be709f..9002cb7bd1176e 100644 --- a/homeassistant/components/linode/switch.py +++ b/homeassistant/components/linode/switch.py @@ -67,7 +67,7 @@ def is_on(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Linode Node.""" return self._attrs diff --git a/homeassistant/components/linux_battery/sensor.py b/homeassistant/components/linux_battery/sensor.py index f4d4e92cb785e2..feefa34a7a718b 100644 --- a/homeassistant/components/linux_battery/sensor.py +++ b/homeassistant/components/linux_battery/sensor.py @@ -101,7 +101,7 @@ def unit_of_measurement(self): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._system == "android": return { diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index 27ce904cc2c681..5248afb4dbd413 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -85,7 +85,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return {ATTR_NUMBER: self._index} diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index daadfce90dcc2d..5ae0aec95591cb 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -47,7 +47,7 @@ def unique_id(self): return f"{self._entry_id}_{self._index}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index b782a4a9d98421..343d8393f1c724 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -77,7 +77,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device-specific state attributes.""" return {ATTR_NUMBER: self._index} diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 4fe76d446f4779..a36ef6563610b9 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -111,7 +111,7 @@ async def async_send_command(self, command, params=None, **kwargs): raise NotImplementedError() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { "clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes, diff --git a/homeassistant/components/local_file/camera.py b/homeassistant/components/local_file/camera.py index b0b846771835b1..c94aeff24b0fda 100644 --- a/homeassistant/components/local_file/camera.py +++ b/homeassistant/components/local_file/camera.py @@ -105,6 +105,6 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera state attributes.""" return {"file_path": self._file_path} diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py index 20bc829d75d517..1afeb190c8bd12 100644 --- a/homeassistant/components/logi_circle/camera.py +++ b/homeassistant/components/logi_circle/camera.py @@ -125,7 +125,7 @@ def device_info(self): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index 4a5fedaf57a860..3d980b1dae390a 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -83,7 +83,7 @@ def device_info(self): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index a8bebc20cf566d..c39d77585a8325 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -124,7 +124,7 @@ def icon(self): return self.ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" attrs = {} attrs["updated"] = self._updated diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py index c39ef2885b0d03..acb605901c90e8 100644 --- a/homeassistant/components/london_underground/sensor.py +++ b/homeassistant/components/london_underground/sensor.py @@ -78,7 +78,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" self.attrs["Description"] = self._description return self.attrs diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index 515d8ad577f0d8..5985c63801ec7d 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -94,7 +94,7 @@ def unique_id(self) -> str: return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" self._attrs[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION diff --git a/homeassistant/components/lutron/binary_sensor.py b/homeassistant/components/lutron/binary_sensor.py index f77a2b120daced..6fb394d333cf6b 100644 --- a/homeassistant/components/lutron/binary_sensor.py +++ b/homeassistant/components/lutron/binary_sensor.py @@ -49,6 +49,6 @@ def name(self): return f"{self._area_name} Occupancy" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index f1faed32161f53..6ee53950ef2445 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -64,6 +64,6 @@ def update(self): _LOGGER.debug("Lutron ID: %d updated to %f", self._lutron_device.id, level) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index d74d24f71a197c..de94b6d6ead2f8 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -65,7 +65,7 @@ def turn_off(self, **kwargs): self._lutron_device.level = 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 21586aaa26640b..f78f46b6733592 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -42,7 +42,7 @@ def turn_off(self, **kwargs): self._lutron_device.level = 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"lutron_integration_id": self._lutron_device.id} @@ -75,7 +75,7 @@ def turn_off(self, **kwargs): self._lutron_device.state = 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { "keypad": self._keypad_name, diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 07851db1e2371d..89eef781c256cd 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -349,7 +349,7 @@ def device_info(self): } @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"device_id": self.device_id, "zone_id": self._device["zone"]} diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index b58afd22a90f76..c2fc311de439d3 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -69,6 +69,6 @@ def device_info(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"device_id": self.device_id} diff --git a/homeassistant/components/lyft/sensor.py b/homeassistant/components/lyft/sensor.py index 98084b28f0c154..872281f685cc12 100644 --- a/homeassistant/components/lyft/sensor.py +++ b/homeassistant/components/lyft/sensor.py @@ -110,7 +110,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" params = { "Product ID": self._product["ride_type"], diff --git a/homeassistant/components/magicseaweed/sensor.py b/homeassistant/components/magicseaweed/sensor.py index 9364bee27b2d0a..406f5e5395538a 100644 --- a/homeassistant/components/magicseaweed/sensor.py +++ b/homeassistant/components/magicseaweed/sensor.py @@ -136,7 +136,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 2313bcace19a76..00c155615ee6f7 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -394,7 +394,7 @@ def _validate_code(self, code, state): return check @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.state == STATE_ALARM_PENDING or self.state == STATE_ALARM_ARMING: return { diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index f11938396a7ec9..2fa0e631c1d342 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -415,7 +415,7 @@ def _validate_code(self, code, state): return check @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self.state != STATE_ALARM_PENDING: return {} diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 5db4cc1e7bd2f8..87da5953f26800 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -286,7 +286,7 @@ def set_preset_mode(self, preset_mode): return @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" cube = self._cubehandle.cube device = cube.device_by_rf(self._rf_address) diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index 4c409ec5a4d0fc..1abb86cf5e523c 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -138,7 +138,7 @@ def name(self): return self._name @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the optional state attributes with device specific additions.""" attr = {} @@ -310,7 +310,7 @@ def name(self) -> str: return f"{self._name} {self._zone.name}" @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the optional state attributes with device specific additions.""" data = { ATTR_STATUS: ATW_ZONE_HVAC_MODE_LOOKUP.get( diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index ae10b5140f76a1..3474fc07540c98 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -72,7 +72,7 @@ async def async_turn_off(self) -> None: await self._device.set({PROPERTY_POWER: False}) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes with device specific additions.""" data = {ATTR_STATUS: self._device.status} return data diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 4802c20c1e55f8..74b4ab5a6d1e75 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -154,7 +154,7 @@ def entity_registry_enabled_default(self) -> bool: return SENSOR_TYPES[self._type][ENTITY_ENABLE] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -177,7 +177,7 @@ def state(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" reference_dt = self.coordinator.data.forecast[0]["dt"] return { @@ -208,7 +208,7 @@ def state(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { **readeable_phenomenoms_dict(self.coordinator.data.phenomenons_max_colors), diff --git a/homeassistant/components/meteoalarm/binary_sensor.py b/homeassistant/components/meteoalarm/binary_sensor.py index 6b13d03ebbad44..6d237c696f675b 100644 --- a/homeassistant/components/meteoalarm/binary_sensor.py +++ b/homeassistant/components/meteoalarm/binary_sensor.py @@ -73,7 +73,7 @@ def is_on(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION return self._attributes diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py index aed763ca4a415c..2f4dd72b3a1317 100644 --- a/homeassistant/components/metoffice/sensor.py +++ b/homeassistant/components/metoffice/sensor.py @@ -171,7 +171,7 @@ def device_class(self): return SENSOR_TYPES[self._type][1] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/mfi/switch.py b/homeassistant/components/mfi/switch.py index 21963140547f14..150f81298cda90 100644 --- a/homeassistant/components/mfi/switch.py +++ b/homeassistant/components/mfi/switch.py @@ -107,7 +107,7 @@ def current_power_w(self): return int(self._port.data.get("active_pwr", 0)) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the device.""" return { "volts": round(self._port.data.get("v_rms", 0), 1), diff --git a/homeassistant/components/mhz19/sensor.py b/homeassistant/components/mhz19/sensor.py index e77f17c9140c65..f26481f9d25d1b 100644 --- a/homeassistant/components/mhz19/sensor.py +++ b/homeassistant/components/mhz19/sensor.py @@ -107,7 +107,7 @@ def update(self): self._ppm = data.get(SENSOR_CO2) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" result = {} if self._sensor_type == SENSOR_TEMPERATURE and self._ppm is not None: diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index b904642960388e..9f7131d1935b70 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -231,7 +231,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" attr = {} for name, p_id in self._api.store[self._id].items(): diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index fa1d8b57734be0..8db81ecf5dd392 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -189,7 +189,7 @@ def available(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {ATTR_LAST_SUCCESSFUL_UPDATE: self.last_successful_update} diff --git a/homeassistant/components/mikrotik/device_tracker.py b/homeassistant/components/mikrotik/device_tracker.py index b9e0b051aba584..025eff8d07a3b7 100644 --- a/homeassistant/components/mikrotik/device_tracker.py +++ b/homeassistant/components/mikrotik/device_tracker.py @@ -123,7 +123,7 @@ def available(self) -> bool: return self.hub.available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.is_connected: return {k: v for k, v in self.device.attrs.items() if k not in FILTER_ATTRS} diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index 0bb94242d646bb..7a1adc6a0bca8f 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -107,7 +107,7 @@ def name(self): return self._heater.name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" res = { "open_window": self._heater.open_window, diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index d2ffb9f5ec0733..5bea9379690307 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -188,7 +188,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { attr: getattr(self, attr) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 164bb264f90ae4..0e7096881f5051 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -246,7 +246,7 @@ def __init__( "sw_version": self._server.protocol_version, } self._device_class = device_class - self._device_state_attributes = None + self._extra_state_attributes = None self._disconnect_dispatcher = None @property diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 171ff9d17013ad..8cc626390dd914 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -141,19 +141,19 @@ async def async_update(self) -> None: """Update online players state and device state attributes.""" self._state = self._server.players_online - device_state_attributes = None + extra_state_attributes = None players_list = self._server.players_list if players_list is not None: if len(players_list) != 0: - device_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} + extra_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} - self._device_state_attributes = device_state_attributes + self._extra_state_attributes = extra_state_attributes @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return players list in device state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes class MinecraftServerPlayersMaxSensor(MinecraftServerSensorEntity): diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index f0cd30074fa9ea..1b006f698279c6 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -56,7 +56,7 @@ def battery_level(self): return self._data.get(ATTR_BATTERY) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" attrs = {} for key in ATTR_KEYS: diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 748f680da5e5b1..2f30c4b9f1bb7c 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -81,7 +81,7 @@ def device_class(self): return self._config.get(ATTR_SENSOR_DEVICE_CLASS) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._config[ATTR_SENSOR_ATTRIBUTES] diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index c58a4b67eedb97..f91b6d7a169354 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -86,7 +86,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index e2d9909c7ca1d4..126b57cb412254 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -375,7 +375,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._is_metric: return { diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index 3087401c3aea6c..4ac09a5fd1108b 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -294,7 +294,7 @@ def is_closed(self): return self._blind.position[self._motor_key] == 100 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" attributes = {} if self._blind.position is not None: diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index f8a673b3079816..9f80c85b959a59 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -88,7 +88,7 @@ def state(self): return self._blind.battery_level @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_BATTERY_VOLTAGE: self._blind.battery_voltage} @@ -134,7 +134,7 @@ def state(self): return self._blind.battery_level[self._motor[0]] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" attributes = {} if self._blind.battery_voltage is not None: diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index a1279f512a519e..d6688636bb2aa7 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -109,32 +109,32 @@ def message_received(msg): @property def latitude(self): - """Return latitude if provided in device_state_attributes or None.""" + """Return latitude if provided in extra_state_attributes or None.""" if ( - self.device_state_attributes is not None - and ATTR_LATITUDE in self.device_state_attributes + self.extra_state_attributes is not None + and ATTR_LATITUDE in self.extra_state_attributes ): - return self.device_state_attributes[ATTR_LATITUDE] + return self.extra_state_attributes[ATTR_LATITUDE] return None @property def location_accuracy(self): - """Return location accuracy if provided in device_state_attributes or None.""" + """Return location accuracy if provided in extra_state_attributes or None.""" if ( - self.device_state_attributes is not None - and ATTR_GPS_ACCURACY in self.device_state_attributes + self.extra_state_attributes is not None + and ATTR_GPS_ACCURACY in self.extra_state_attributes ): - return self.device_state_attributes[ATTR_GPS_ACCURACY] + return self.extra_state_attributes[ATTR_GPS_ACCURACY] return None @property def longitude(self): - """Return longitude if provided in device_state_attributes or None.""" + """Return longitude if provided in extra_state_attributes or None.""" if ( - self.device_state_attributes is not None - and ATTR_LONGITUDE in self.device_state_attributes + self.extra_state_attributes is not None + and ATTR_LONGITUDE in self.extra_state_attributes ): - return self.device_state_attributes[ATTR_LONGITUDE] + return self.extra_state_attributes[ATTR_LONGITUDE] return None @property diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 8bcb5e5ca97710..898072de5f9e47 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -229,7 +229,7 @@ async def async_will_remove_from_hass(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index 3b61003e60192b..e15dfd179dab28 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -135,7 +135,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_DISTANCE: self._distance} diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py index 2ceca024a6f1ea..15b0d2d218e70c 100644 --- a/homeassistant/components/mvglive/sensor.py +++ b/homeassistant/components/mvglive/sensor.py @@ -114,7 +114,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" dep = self.data.departures if not dep: diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index df8b136741a970..832f1a03f25d3b 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -172,7 +172,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return all the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index 25b892d70b3690..d5ae6c0c156638 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -122,7 +122,7 @@ def name(self): return f"{self.node_name} {self.child_id}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" node = self.gateway.sensors[self.node_id] child = node.children[self.child_id] diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index d1f89e4fe04733..068029af9602fe 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -72,5 +72,5 @@ async def _async_update_callback(self): host_name=self.name, gps=(latitude, longitude), battery=node.battery_level, - attributes=self.device_state_attributes, + attributes=self.extra_state_attributes, ) diff --git a/homeassistant/components/n26/sensor.py b/homeassistant/components/n26/sensor.py index b9a8b21f9d0c96..df687d9689ac1a 100644 --- a/homeassistant/components/n26/sensor.py +++ b/homeassistant/components/n26/sensor.py @@ -86,7 +86,7 @@ def unit_of_measurement(self) -> str: return self._data.balance.get("currency") @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" attributes = { ATTR_IBAN: self._data.balance.get("iban"), @@ -147,7 +147,7 @@ def state(self) -> float: return self._card["status"] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" attributes = { "apple_pay_eligible": self._card.get("applePayEligible"), @@ -220,7 +220,7 @@ def unit_of_measurement(self) -> str: return self._space["balance"]["currency"] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Additional attributes of the sensor.""" goal_value = "" if "goal" in self._space: diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 1698a1d944a6e6..74a3cb4bc77c9a 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -126,7 +126,7 @@ def device_info(self): return {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}} @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the vacuum cleaner.""" data = {} diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index ce4156244b7c07..3b6711d3b726ac 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -284,7 +284,7 @@ def unique_id(self): return self._robot_serial @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the vacuum cleaner.""" data = {} diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index 3d15e3c4d9b355..7d90c2c3c0dac8 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -124,7 +124,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self._trips: return diff --git a/homeassistant/components/nello/lock.py b/homeassistant/components/nello/lock.py index 61241660847332..93e63b05da9f22 100644 --- a/homeassistant/components/nello/lock.py +++ b/homeassistant/components/nello/lock.py @@ -48,7 +48,7 @@ def is_locked(self): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return self._device_attrs diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 5163c9582b0095..b02d77e45fb07a 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -217,7 +217,7 @@ def camera_image(self): return response.content @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the Netatmo-specific camera state attributes.""" return { "id": self._id, diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index d12ee9263db43c..c2a6e484771cdd 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -418,7 +418,7 @@ def set_temperature(self, **kwargs): self.async_write_ha_state() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the thermostat.""" attr = {} diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index efda94d6399c4f..ac86e86b96068c 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -536,7 +536,7 @@ def device_class(self): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the device.""" attrs = {} diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 2b3f7de44893ea..4084f4d297c968 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -354,9 +354,9 @@ def is_aux_heat(self): return self._thermostat.is_emergency_heat_active() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes data[ATTR_ZONE_STATUS] = self._zone.get_status() diff --git a/homeassistant/components/nexia/entity.py b/homeassistant/components/nexia/entity.py index 62f6e8275c4053..fc69c7ef38980a 100644 --- a/homeassistant/components/nexia/entity.py +++ b/homeassistant/components/nexia/entity.py @@ -33,7 +33,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/nexia/scene.py b/homeassistant/components/nexia/scene.py index d3a6691d59ec11..495a8fb4d3a792 100644 --- a/homeassistant/components/nexia/scene.py +++ b/homeassistant/components/nexia/scene.py @@ -41,9 +41,9 @@ def __init__(self, coordinator, automation): self._automation = automation @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the scene specific state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes data[ATTR_DESCRIPTION] = self._automation.description return data diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index 2b5da2a97fac65..3357d84fd69f86 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -156,7 +156,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return additional state attributes.""" return self._attributes diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index efa625577d9353..53f13f3b69bf24 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -115,6 +115,6 @@ def _parse_icon(self) -> str: return switcher.get(self._attributes[ATTR_DIRECTION], "mdi:cloud-question") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/nilu/air_quality.py b/homeassistant/components/nilu/air_quality.py index 8e851592de3807..d6fcad3ac7eddd 100644 --- a/homeassistant/components/nilu/air_quality.py +++ b/homeassistant/components/nilu/air_quality.py @@ -175,7 +175,7 @@ def attribution(self) -> str: return ATTRIBUTION @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return other details about the sensor state.""" return self._attrs diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index bace7f14556c9c..967857aedc509f 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -450,7 +450,7 @@ def log_registration(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return default attributes for Nissan leaf entities.""" return { "next_update": self.car.next_update, diff --git a/homeassistant/components/nissan_leaf/switch.py b/homeassistant/components/nissan_leaf/switch.py index d95d3e4ed39c23..2b8d557c2dd56b 100644 --- a/homeassistant/components/nissan_leaf/switch.py +++ b/homeassistant/components/nissan_leaf/switch.py @@ -37,9 +37,9 @@ def log_registration(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return climate control attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes attrs["updated_on"] = self.car.last_climate_response return attrs diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py index bdf4658434c4d1..ac6753ce0d67a2 100644 --- a/homeassistant/components/nmbs/sensor.py +++ b/homeassistant/components/nmbs/sensor.py @@ -126,7 +126,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor attributes if data is available.""" if self._state is None or not self._attrs: return None @@ -202,7 +202,7 @@ def icon(self): return "mdi:train" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return sensor attributes if data is available.""" if self._state is None or not self._attrs: return None diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index 0759c5093c84c4..b6771d9293a378 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -90,7 +90,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of this device.""" attr = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} if self.data is None: diff --git a/homeassistant/components/norway_air/air_quality.py b/homeassistant/components/norway_air/air_quality.py index 8e6c13260e5b8d..788f900ef70be6 100644 --- a/homeassistant/components/norway_air/air_quality.py +++ b/homeassistant/components/norway_air/air_quality.py @@ -80,7 +80,7 @@ def attribution(self) -> str: return ATTRIBUTION @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return other details about the sensor state.""" return { "level": self._api.data.get("level"), diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index c2cbdb85289fb0..c8c385143cf52d 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -180,7 +180,7 @@ def device_class(self) -> str: return self._device_class @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index b6c0d1a5d9bd0c..0bf82c1d162708 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -168,7 +168,7 @@ def state(self) -> Optional[float]: return None @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes of the device.""" return { ATTR_STATION_ID: self._station_data.station_id, diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py index 12ae9d8990ad46..0467b9cc353e02 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py +++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py @@ -284,7 +284,7 @@ def unit_of_measurement(self): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 818784a2b2e24e..360153d14feabb 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -99,7 +99,7 @@ def is_locked(self): """Return true if lock is locked.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" data = { ATTR_BATTERY_CRITICAL: self._nuki_device.battery_critical, diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index 174405e22e2347..d4fdd03adc81d7 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -160,7 +160,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor attributes.""" return {ATTR_STATE: _format_display_state(self._data.status)} diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py index 2db3531f879555..058ac6c5795269 100644 --- a/homeassistant/components/nx584/binary_sensor.py +++ b/homeassistant/components/nx584/binary_sensor.py @@ -105,7 +105,7 @@ def is_on(self): return self._zone["state"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"zone_number": self._zone["number"]} diff --git a/homeassistant/components/oasa_telematics/sensor.py b/homeassistant/components/oasa_telematics/sensor.py index 4bf6b395d5f58e..8af74b5cd0e469 100644 --- a/homeassistant/components/oasa_telematics/sensor.py +++ b/homeassistant/components/oasa_telematics/sensor.py @@ -79,7 +79,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" params = {} if self._times is not None: diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index 7c7331990ea6c1..6c7c04b25cbcdb 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -56,7 +56,7 @@ def state(self): return "Inactive" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"Address": self._data.get("address"), "ID": self._ohmid} diff --git a/homeassistant/components/omnilogic/common.py b/homeassistant/components/omnilogic/common.py index 791d81b6757a53..6f7ee6e5eb574f 100644 --- a/homeassistant/components/omnilogic/common.py +++ b/homeassistant/components/omnilogic/common.py @@ -141,7 +141,7 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index 9238bb5d32c3f5..724783b5686e8e 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -58,7 +58,7 @@ def unit_of_measurement(self) -> Optional[str]: return self._unit_of_measurement @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return {"device_file": self._device_file, "raw_value": self._value_raw} diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 7ac9b5fdfc6f25..3d882884aa2be3 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -392,7 +392,7 @@ def source_list(self): return self._source_list @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return self._attributes diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py index 9846e305291b0b..f3f0d825ff67e7 100644 --- a/homeassistant/components/openexchangerates/sensor.py +++ b/homeassistant/components/openexchangerates/sensor.py @@ -79,7 +79,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other attributes of the sensor.""" attr = self.rest.data attr[ATTR_ATTRIBUTION] = ATTRIBUTION diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index cf6825c867bda6..154cb4df3ae09f 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -92,7 +92,7 @@ def __init__(self, name, open_garage, device_id): self._open_garage = open_garage self._state = None self._state_before_move = None - self._device_state_attributes = {} + self._extra_state_attributes = {} self._available = True self._device_id = device_id @@ -107,9 +107,9 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @property def is_closed(self): @@ -154,11 +154,11 @@ async def async_update(self): _LOGGER.debug("%s status: %s", self._name, self._state) if status.get("rssi") is not None: - self._device_state_attributes[ATTR_SIGNAL_STRENGTH] = status.get("rssi") + self._extra_state_attributes[ATTR_SIGNAL_STRENGTH] = status.get("rssi") if status.get("dist") is not None: - self._device_state_attributes[ATTR_DISTANCE_SENSOR] = status.get("dist") + self._extra_state_attributes[ATTR_DISTANCE_SENSOR] = status.get("dist") if self._state is not None: - self._device_state_attributes[ATTR_DOOR_STATE] = self._state + self._extra_state_attributes[ATTR_DOOR_STATE] = self._state self._available = True diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index 06132e83e8896e..e5ffb2384f5370 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -174,7 +174,7 @@ def update(self): self._previously_tracked = currently_tracked @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: OPENSKY_ATTRIBUTION} diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 387b7547514f07..aeefe435845d93 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -187,7 +187,7 @@ def available(self) -> bool: return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/openweathermap/abstract_owm_sensor.py b/homeassistant/components/openweathermap/abstract_owm_sensor.py index 809d2c2e572b64..a69f542589bd28 100644 --- a/homeassistant/components/openweathermap/abstract_owm_sensor.py +++ b/homeassistant/components/openweathermap/abstract_owm_sensor.py @@ -57,7 +57,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/osramlightify/light.py b/homeassistant/components/osramlightify/light.py index 49c32da69bccc3..e01ad9704886a3 100644 --- a/homeassistant/components/osramlightify/light.py +++ b/homeassistant/components/osramlightify/light.py @@ -269,7 +269,7 @@ def unique_id(self): return self._unique_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return self._device_attributes diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 2f2e1b8dd50d92..3171b3231dcfb4 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -100,7 +100,7 @@ def state(self) -> str: return usage.electricity[-1].consumption @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.electricity: @@ -135,7 +135,7 @@ def state(self) -> str: return usage.gas[-1].consumption @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.gas: @@ -171,7 +171,7 @@ def state(self) -> str: return usage.electricity[-1].cost.amount @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.electricity: @@ -207,7 +207,7 @@ def state(self) -> str: return usage.gas[-1].cost.amount @property - def device_state_attributes(self) -> object: + def extra_state_attributes(self) -> object: """Return the attributes of the sensor.""" usage: OVODailyUsage = self.coordinator.data if usage is None or not usage.gas: diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index 8a8fdc52fb1b79..d50e5b9c414064 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -74,7 +74,7 @@ def battery_level(self): return self._data.get("battery") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._data.get("attributes") diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index a74fd869f0f443..a6532af4d2624b 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -308,9 +308,9 @@ async def async_set_preset_mode(self, preset_mode): self.values.mode.send_value(preset_mode_value) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes if self.values.fan_action: data[ATTR_FAN_ACTION] = self.values.fan_action.value if self.values.valve_position: diff --git a/homeassistant/components/ozw/entity.py b/homeassistant/components/ozw/entity.py index c1cb9617a5c40b..305601a2333e0b 100644 --- a/homeassistant/components/ozw/entity.py +++ b/homeassistant/components/ozw/entity.py @@ -209,7 +209,7 @@ def device_info(self): return device_info @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return {const.ATTR_NODE_ID: self.values.primary.node.node_id} diff --git a/homeassistant/components/ozw/sensor.py b/homeassistant/components/ozw/sensor.py index 5bd0d1c482f16e..db695bcf6bc8e6 100644 --- a/homeassistant/components/ozw/sensor.py +++ b/homeassistant/components/ozw/sensor.py @@ -149,9 +149,9 @@ def state(self): return self.values.primary.value["Selected_id"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes # add the value's label as property attributes["label"] = self.values.primary.value["Selected"] return attributes diff --git a/tests/components/kira/test_sensor.py b/tests/components/kira/test_sensor.py index cd4bee60ae6dfc..b835a25ae9095b 100644 --- a/tests/components/kira/test_sensor.py +++ b/tests/components/kira/test_sensor.py @@ -47,4 +47,4 @@ def test_kira_sensor_callback(self): sensor._update_callback(codeTuple) assert sensor.state == codeName - assert sensor.device_state_attributes == {kira.CONF_DEVICE: deviceName} + assert sensor.extra_state_attributes == {kira.CONF_DEVICE: deviceName} diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index 0409a4f387ad8f..1e9f56853c3dd9 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -118,7 +118,7 @@ async def test_current_power_w_no_data(port, switch): assert switch.current_power_w == 0 -async def test_device_state_attributes(port, switch): +async def test_extra_state_attributes(port, switch): """Test the state attributes.""" port.data = {"v_rms": 1.25, "i_rms": 2.75} - assert switch.device_state_attributes == {"volts": 1.2, "amps": 2.8} + assert switch.extra_state_attributes == {"volts": 1.2, "amps": 2.8} diff --git a/tests/components/mhz19/test_sensor.py b/tests/components/mhz19/test_sensor.py index 5b6e1e9fa37e5c..e827b5dfbd2f96 100644 --- a/tests/components/mhz19/test_sensor.py +++ b/tests/components/mhz19/test_sensor.py @@ -93,7 +93,7 @@ async def test_co2_sensor(mock_function): assert sensor.state == 1000 assert sensor.unit_of_measurement == CONCENTRATION_PARTS_PER_MILLION assert sensor.should_poll - assert sensor.device_state_attributes == {"temperature": 24} + assert sensor.extra_state_attributes == {"temperature": 24} @patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) @@ -107,7 +107,7 @@ async def test_temperature_sensor(mock_function): assert sensor.state == 24 assert sensor.unit_of_measurement == TEMP_CELSIUS assert sensor.should_poll - assert sensor.device_state_attributes == {"co2_concentration": 1000} + assert sensor.extra_state_attributes == {"co2_concentration": 1000} @patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index c12e8e24ebc5df..d6d410b3700947 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -146,7 +146,7 @@ def test_nx584_zone_sensor_normal(): assert "foo" == sensor.name assert not sensor.should_poll assert sensor.is_on - assert sensor.device_state_attributes["zone_number"] == 1 + assert sensor.extra_state_attributes["zone_number"] == 1 zone["state"] = False assert not sensor.is_on From 1fc8e32d8662c1cdc4b097c9ffc3dd98e16af654 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 20:16:26 +0100 Subject: [PATCH 1194/1818] Update integrations t-z to override extra_state_attributes() (#47760) --- .../components/tado/binary_sensor.py | 2 +- homeassistant/components/tado/climate.py | 2 +- homeassistant/components/tado/sensor.py | 4 +-- homeassistant/components/tahoma/__init__.py | 2 +- .../components/tahoma/binary_sensor.py | 4 +-- homeassistant/components/tahoma/cover.py | 4 +-- homeassistant/components/tahoma/lock.py | 4 +-- homeassistant/components/tahoma/scene.py | 2 +- homeassistant/components/tahoma/sensor.py | 4 +-- homeassistant/components/tahoma/switch.py | 4 +-- .../components/tank_utility/sensor.py | 2 +- .../components/tankerkoenig/sensor.py | 2 +- homeassistant/components/tautulli/sensor.py | 2 +- homeassistant/components/tellduslive/entry.py | 2 +- .../components/template/template_entity.py | 2 +- .../components/tensorflow/image_processing.py | 2 +- homeassistant/components/tesla/__init__.py | 2 +- .../components/tesla/device_tracker.py | 4 +-- homeassistant/components/tesla/sensor.py | 2 +- .../components/thermoworks_smoke/sensor.py | 2 +- .../components/thethingsnetwork/sensor.py | 2 +- .../components/threshold/binary_sensor.py | 2 +- homeassistant/components/tibber/sensor.py | 18 ++++++------ .../components/tile/device_tracker.py | 2 +- homeassistant/components/tmb/sensor.py | 2 +- homeassistant/components/tod/binary_sensor.py | 2 +- homeassistant/components/todoist/calendar.py | 2 +- homeassistant/components/toon/climate.py | 2 +- .../totalconnect/alarm_control_panel.py | 8 +++--- .../components/totalconnect/binary_sensor.py | 2 +- homeassistant/components/tplink/light.py | 2 +- homeassistant/components/tplink/switch.py | 2 +- .../components/traccar/device_tracker.py | 2 +- homeassistant/components/tradfri/cover.py | 2 +- .../components/trafikverket_train/sensor.py | 2 +- .../trafikverket_weatherstation/sensor.py | 2 +- .../components/transmission/sensor.py | 2 +- .../components/transport_nsw/sensor.py | 2 +- homeassistant/components/travisci/sensor.py | 2 +- .../components/trend/binary_sensor.py | 2 +- homeassistant/components/twitch/sensor.py | 2 +- .../components/uk_transport/sensor.py | 4 +-- .../components/unifi/device_tracker.py | 4 +-- homeassistant/components/unifi/switch.py | 2 +- .../components/universal/media_player.py | 2 +- homeassistant/components/upb/__init__.py | 2 +- homeassistant/components/upcloud/__init__.py | 2 +- .../components/updater/binary_sensor.py | 2 +- .../components/uptimerobot/binary_sensor.py | 2 +- homeassistant/components/uscis/sensor.py | 2 +- .../usgs_earthquakes_feed/geo_location.py | 2 +- .../components/utility_meter/sensor.py | 2 +- homeassistant/components/uvc/camera.py | 2 +- homeassistant/components/vallox/fan.py | 2 +- homeassistant/components/vasttrafik/sensor.py | 2 +- homeassistant/components/venstar/climate.py | 2 +- homeassistant/components/vera/__init__.py | 2 +- homeassistant/components/vera/lock.py | 4 +-- homeassistant/components/vera/scene.py | 2 +- homeassistant/components/version/sensor.py | 2 +- homeassistant/components/vesync/fan.py | 2 +- homeassistant/components/vesync/switch.py | 2 +- .../components/viaggiatreno/sensor.py | 2 +- homeassistant/components/vicare/climate.py | 2 +- .../components/volvooncall/__init__.py | 2 +- .../components/vultr/binary_sensor.py | 2 +- homeassistant/components/vultr/switch.py | 2 +- homeassistant/components/waqi/sensor.py | 2 +- .../components/waze_travel_time/sensor.py | 2 +- .../components/webostv/media_player.py | 2 +- homeassistant/components/wemo/fan.py | 2 +- homeassistant/components/wemo/switch.py | 2 +- homeassistant/components/whois/sensor.py | 2 +- homeassistant/components/wink/__init__.py | 10 +++---- .../components/wink/alarm_control_panel.py | 2 +- .../components/wink/binary_sensor.py | 20 ++++++------- homeassistant/components/wink/climate.py | 4 +-- homeassistant/components/wink/lock.py | 4 +-- homeassistant/components/wink/sensor.py | 4 +-- homeassistant/components/wink/switch.py | 4 +-- homeassistant/components/wink/water_heater.py | 2 +- .../components/wirelesstag/__init__.py | 2 +- homeassistant/components/wled/light.py | 2 +- homeassistant/components/wled/sensor.py | 2 +- homeassistant/components/wled/switch.py | 6 ++-- homeassistant/components/wolflink/sensor.py | 2 +- .../components/worldtidesinfo/sensor.py | 2 +- homeassistant/components/wsdot/sensor.py | 2 +- .../components/wunderground/sensor.py | 20 ++++++------- homeassistant/components/xbox_live/sensor.py | 2 +- .../components/xiaomi_aqara/__init__.py | 10 +++---- .../components/xiaomi_aqara/binary_sensor.py | 28 +++++++++---------- homeassistant/components/xiaomi_aqara/lock.py | 2 +- .../components/xiaomi_aqara/sensor.py | 2 +- .../components/xiaomi_aqara/switch.py | 4 +-- .../components/xiaomi_miio/air_quality.py | 2 +- homeassistant/components/xiaomi_miio/fan.py | 2 +- homeassistant/components/xiaomi_miio/light.py | 2 +- .../components/xiaomi_miio/sensor.py | 2 +- .../components/xiaomi_miio/switch.py | 2 +- .../components/xiaomi_miio/vacuum.py | 2 +- .../components/yandex_transport/sensor.py | 2 +- homeassistant/components/yeelight/light.py | 2 +- homeassistant/components/zabbix/sensor.py | 2 +- homeassistant/components/zamg/sensor.py | 2 +- homeassistant/components/zestimate/sensor.py | 2 +- homeassistant/components/zha/climate.py | 2 +- homeassistant/components/zha/entity.py | 8 +++--- homeassistant/components/zha/light.py | 2 +- homeassistant/components/zha/lock.py | 2 +- homeassistant/components/zha/sensor.py | 2 +- homeassistant/components/zodiac/sensor.py | 2 +- homeassistant/components/zwave/__init__.py | 2 +- homeassistant/components/zwave/climate.py | 4 +-- homeassistant/components/zwave/lock.py | 4 +-- homeassistant/components/zwave/node_entity.py | 2 +- homeassistant/components/zwave_js/climate.py | 2 +- homeassistant/components/zwave_js/sensor.py | 2 +- tests/components/uvc/test_camera.py | 2 +- tests/components/vultr/test_binary_sensor.py | 2 +- tests/components/vultr/test_switch.py | 2 +- tests/components/wsdot/test_sensor.py | 4 +-- tests/components/zwave/test_climate.py | 4 +-- tests/components/zwave/test_init.py | 2 +- tests/components/zwave/test_lock.py | 26 ++++++++--------- tests/components/zwave/test_node_entity.py | 17 ++++------- .../test/image_processing.py | 2 +- 127 files changed, 218 insertions(+), 223 deletions(-) diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index 068c3a7ce93160..9f68aa8a4e7758 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -231,7 +231,7 @@ def device_class(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 9547617a36b5b4..b86eb08b1b0075 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -462,7 +462,7 @@ def swing_modes(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return temperature offset.""" return self._tado_zone_temp_offset diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 8d38d9eab96239..cfafeb14ddfd08 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -132,7 +132,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes @@ -237,7 +237,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._state_attributes diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index b322d454002e86..8db7b23a8ce25e 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -137,7 +137,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return {"tahoma_device_id": self.tahoma_device.url} diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py index af06bf5ca4c77f..c094601346962a 100644 --- a/homeassistant/components/tahoma/binary_sensor.py +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -57,10 +57,10 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py index 2eec9160811de7..a02f21fb5e1409 100644 --- a/homeassistant/components/tahoma/cover.py +++ b/homeassistant/components/tahoma/cover.py @@ -194,10 +194,10 @@ def device_class(self): return TAHOMA_DEVICE_CLASSES.get(self.tahoma_device.type) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tahoma/lock.py b/homeassistant/components/tahoma/lock.py index 93d82bffc99501..3d160cd95b3909 100644 --- a/homeassistant/components/tahoma/lock.py +++ b/homeassistant/components/tahoma/lock.py @@ -78,12 +78,12 @@ def is_locked(self): return self._lock_status == STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the lock state attributes.""" attr = { ATTR_BATTERY_LEVEL: self._battery_level, } - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) return attr diff --git a/homeassistant/components/tahoma/scene.py b/homeassistant/components/tahoma/scene.py index 1d53b65d5d5695..3cfa26a5f8912f 100644 --- a/homeassistant/components/tahoma/scene.py +++ b/homeassistant/components/tahoma/scene.py @@ -37,6 +37,6 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the scene.""" return {"tahoma_scene_oid": self.tahoma_scene.oid} diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index fb1129cfa0edc1..91629137318503 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -110,10 +110,10 @@ def update(self): _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py index 808f80d8cfaf53..2ea68b93e6b799 100644 --- a/homeassistant/components/tahoma/switch.py +++ b/homeassistant/components/tahoma/switch.py @@ -105,10 +105,10 @@ def is_on(self): return bool(self._state == STATE_ON) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attr = {} - super_attr = super().device_state_attributes + super_attr = super().extra_state_attributes if super_attr is not None: attr.update(super_attr) diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index ab1cc8b23da42d..d0566ec7c9e0d1 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -97,7 +97,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the device.""" return self._attributes diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index 6985072b065e30..a1cbed2ba115b2 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -125,7 +125,7 @@ def unique_id(self) -> str: return f"{self._station_id}_{self._fuel_type}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the device.""" data = self.coordinator.data[self._station_id] diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index ed96bb62ace041..2d2d005e95d02a 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -130,7 +130,7 @@ def unit_of_measurement(self): return "Watching" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self._attributes diff --git a/homeassistant/components/tellduslive/entry.py b/homeassistant/components/tellduslive/entry.py index 851823385dc494..4453622b21e126 100644 --- a/homeassistant/components/tellduslive/entry.py +++ b/homeassistant/components/tellduslive/entry.py @@ -81,7 +81,7 @@ def available(self): return self._client.is_available(self.device_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} if self._battery_level: diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index f350eb87d61686..0ae540edf9714d 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -168,7 +168,7 @@ def entity_picture(self): return self._entity_picture @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index 65240dc04dc876..0b8fafd57a8fb4 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -279,7 +279,7 @@ def state(self): return self._total_matches @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_MATCHES: self._matches, diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py index a2ed908948fff8..76b7f872024da2 100644 --- a/homeassistant/components/tesla/__init__.py +++ b/homeassistant/components/tesla/__init__.py @@ -307,7 +307,7 @@ def icon(self): return ICONS.get(self.tesla_device.type) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = self._attributes if self.tesla_device.has_battery(): diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index cac89d58d3a009..16e8ce6dbe2d3b 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -42,9 +42,9 @@ def source_type(self): return SOURCE_TYPE_GPS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - attr = super().device_state_attributes.copy() + attr = super().extra_state_attributes.copy() location = self.tesla_device.get_location() if location: attr.update( diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 3b66845c78621a..9094a7d6b013e0 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -81,7 +81,7 @@ def device_class(self) -> Optional[str]: ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = self._attributes.copy() if self.tesla_device.type == "charging rate sensor": diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index 83a2fd12d24e58..d768f009364ad2 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -124,7 +124,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 0afec8f751077b..d758a20f3845af 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -92,7 +92,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._ttn_data_storage.data is not None: return { diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py index fa05fc096870de..5bd6f77253b26c 100644 --- a/homeassistant/components/threshold/binary_sensor.py +++ b/homeassistant/components/threshold/binary_sensor.py @@ -144,7 +144,7 @@ def threshold_type(self): return TYPE_UPPER @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_ENTITY_ID: self._entity_id, diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index bb5ebe8011f42a..01e050fdd24e42 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -54,7 +54,7 @@ def __init__(self, tibber_home): self._last_updated = None self._state = None self._is_available = False - self._device_state_attributes = {} + self._extra_state_attributes = {} self._name = tibber_home.info["viewer"]["home"]["appNickname"] if self._name is None: self._name = tibber_home.info["viewer"]["home"]["address"].get( @@ -63,9 +63,9 @@ def __init__(self, tibber_home): self._spread_load_constant = randrange(3600) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @property def model(self): @@ -121,10 +121,10 @@ async def async_update(self): res = self._tibber_home.current_price_data() self._state, price_level, self._last_updated = res - self._device_state_attributes["price_level"] = price_level + self._extra_state_attributes["price_level"] = price_level attrs = self._tibber_home.current_attributes() - self._device_state_attributes.update(attrs) + self._extra_state_attributes.update(attrs) self._is_available = self._state is not None @property @@ -165,11 +165,11 @@ async def _fetch_data(self): except (asyncio.TimeoutError, aiohttp.ClientError): return data = self._tibber_home.info["viewer"]["home"] - self._device_state_attributes["app_nickname"] = data["appNickname"] - self._device_state_attributes["grid_company"] = data["meteringPointData"][ + self._extra_state_attributes["app_nickname"] = data["appNickname"] + self._extra_state_attributes["grid_company"] = data["meteringPointData"][ "gridCompany" ] - self._device_state_attributes["estimated_annual_consumption"] = data[ + self._extra_state_attributes["estimated_annual_consumption"] = data[ "meteringPointData" ]["estimatedAnnualConsumption"] @@ -197,7 +197,7 @@ async def _async_callback(self, payload): for key, value in live_measurement.items(): if value is None: continue - self._device_state_attributes[key] = value + self._extra_state_attributes[key] = value self.async_write_ha_state() diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index f7cc4e1736e6cd..7571e235ef1206 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -81,7 +81,7 @@ def battery_level(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/tmb/sensor.py b/homeassistant/components/tmb/sensor.py index f731b912d65c61..c3f813d796a105 100644 --- a/homeassistant/components/tmb/sensor.py +++ b/homeassistant/components/tmb/sensor.py @@ -101,7 +101,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index fde26acf604f78..26e0ead680a01e 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -109,7 +109,7 @@ def next_update(self): return self._next_update @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_AFTER: self.after.astimezone(self.hass.config.time_zone).isoformat(), diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index b9ab4fe6dbb5d0..976462c95faaa2 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -284,7 +284,7 @@ async def async_get_events(self, hass, start_date, end_date): return await self.data.async_get_events(hass, start_date, end_date) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.data.event is None: # No tasks, we don't REALLY need to show anything. diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index ba3ef8ee807f2e..99b76600dce973 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -114,7 +114,7 @@ def max_temp(self) -> float: return DEFAULT_MAX_TEMP @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the current state of the burner.""" return {"heating_type": self.coordinator.data.agreement.heating_type} diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index affff3823659b8..c277198b683e44 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -44,7 +44,7 @@ def __init__(self, name, location_id, client): self._location_id = location_id self._client = client self._state = None - self._device_state_attributes = {} + self._extra_state_attributes = {} @property def name(self): @@ -62,9 +62,9 @@ def supported_features(self) -> int: return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_NIGHT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - return self._device_state_attributes + return self._extra_state_attributes def update(self): """Return the state of the device.""" @@ -109,7 +109,7 @@ def update(self): state = None self._state = state - self._device_state_attributes = attr + self._extra_state_attributes = attr def alarm_disarm(self, code=None): """Send disarm command.""" diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py index 6bee603d1b1f4b..ef02c5d1fd3384 100644 --- a/homeassistant/components/totalconnect/binary_sensor.py +++ b/homeassistant/components/totalconnect/binary_sensor.py @@ -73,7 +73,7 @@ def device_class(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = { "zone_id": self._zone_id, diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index ceb0944efe6c39..31b2319ead044a 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -163,7 +163,7 @@ def available(self) -> bool: return self._is_available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._emeter_params diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 23000fe7b59c63..dec20edec652c9 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -100,7 +100,7 @@ def turn_off(self, **kwargs): self.smartplug.turn_off() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._emeter_params diff --git a/homeassistant/components/traccar/device_tracker.py b/homeassistant/components/traccar/device_tracker.py index cdb2033951085a..d558129e323bec 100644 --- a/homeassistant/components/traccar/device_tracker.py +++ b/homeassistant/components/traccar/device_tracker.py @@ -345,7 +345,7 @@ def battery_level(self): return self._battery @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._attributes diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 72597637bd3e0b..4c7cde1dfd114f 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -29,7 +29,7 @@ def __init__(self, device, api, gateway_id): self._refresh(device) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_MODEL: self._device.device_info.model_number} diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 12f3cf73e50dc1..458994225644f3 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -153,7 +153,7 @@ async def async_update(self): self._delay_in_minutes = self._state.get_delay_time() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._state is None: return None diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index bb1bad67f829b6..f2e2521a90b969 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -172,7 +172,7 @@ def icon(self): return self._icon @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of Trafikverket Weatherstation.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 2a24b80be161bd..d2b52771124b63 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -148,7 +148,7 @@ def unit_of_measurement(self): return "Torrents" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes, if any.""" info = _torrents_info( torrents=self._tm_client.api.torrents, diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py index 2aa27f8c4b39a7..f6d4e40e4e8cb1 100644 --- a/homeassistant/components/transport_nsw/sensor.py +++ b/homeassistant/components/transport_nsw/sensor.py @@ -87,7 +87,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._times is not None: return { diff --git a/homeassistant/components/travisci/sensor.py b/homeassistant/components/travisci/sensor.py index 8a548d8cb6b5fc..6464667bffe2e0 100644 --- a/homeassistant/components/travisci/sensor.py +++ b/homeassistant/components/travisci/sensor.py @@ -124,7 +124,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {} attrs[ATTR_ATTRIBUTION] = ATTRIBUTION diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index b7079a3311aaa4..52a72eca8c956b 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -146,7 +146,7 @@ def device_class(self): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return { ATTR_ENTITY_ID: self._entity_id, diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 4b0196281584e4..0e5abb58747148 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -88,7 +88,7 @@ def entity_picture(self): return self._preview @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = dict(self._statistics) diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index e7c01479a96255..80b97b186be030 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -191,7 +191,7 @@ def _update(self): self._state = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" attrs = {} if self._data is not None: @@ -261,7 +261,7 @@ def _update(self): self._state = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" attrs = {} if self._data is not None: diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index ac28f7475f6582..dddb4d0e5e32d9 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -249,7 +249,7 @@ def unique_id(self) -> str: return f"{self.client.mac}-{self.controller.site}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the client state attributes.""" raw = self.client.raw @@ -421,7 +421,7 @@ async def async_update_device_registry(self) -> None: ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self.device.state == 0: return {} diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 6c8b6ea35cd5ba..da3139317d178b 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -238,7 +238,7 @@ async def async_turn_off(self, **kwargs): await self.device.async_set_port_poe_mode(self.client.sw_port, "off") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = { "power": self.port.poe_power, diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 2702f26a3d3c20..c814abf5a82873 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -502,7 +502,7 @@ def supported_features(self): return flags @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" active_child = self._child_state return {ATTR_ACTIVE_CHILD: active_child.entity_id} if active_child else {} diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index bf23be16af223d..bd591447472944 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -110,7 +110,7 @@ def should_poll(self) -> bool: return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the default attributes of the element.""" return self._element.as_dict() diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index bb96ef0f1d693d..eba61058ca0bcb 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -296,7 +296,7 @@ def device_class(self): return DEFAULT_COMPONENT_DEVICE_CLASS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the UpCloud server.""" return { x: getattr(self._server, x, None) diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py index 36e05513d43788..93d1902999239b 100644 --- a/homeassistant/components/updater/binary_sensor.py +++ b/homeassistant/components/updater/binary_sensor.py @@ -35,7 +35,7 @@ def is_on(self) -> bool: return self.coordinator.data.update_available @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" if not self.coordinator.data: return None diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index e31d8b44b100fa..6c0bb63c70fac4 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -75,7 +75,7 @@ def device_class(self): return DEVICE_CLASS_CONNECTIVITY @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the binary sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_TARGET: self._target} diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py index 2e15a4b14226a0..5f26a0ff82ef91 100644 --- a/homeassistant/components/uscis/sensor.py +++ b/homeassistant/components/uscis/sensor.py @@ -60,7 +60,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 9fd98de42dfb76..364e0599b75dbb 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -280,7 +280,7 @@ def unit_of_measurement(self): return DEFAULT_UNIT_OF_MEASUREMENT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index e8d551ba280fa0..09f788806f6eb1 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -324,7 +324,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" state_attr = { ATTR_SOURCE_ID: self._sensor_source_id, diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index a20b99d673a0e7..82f59a40131d3a 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -111,7 +111,7 @@ def supported_features(self): return 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the camera state attributes.""" attr = {} if self.motion_detection_enabled: diff --git a/homeassistant/components/vallox/fan.py b/homeassistant/components/vallox/fan.py index 525bf00f50e1e8..e167791e70237c 100644 --- a/homeassistant/components/vallox/fan.py +++ b/homeassistant/components/vallox/fan.py @@ -83,7 +83,7 @@ def is_on(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_PROFILE_FAN_SPEED_HOME["description"]: self._fan_speed_home, diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index 882274f8e84ad5..d1a461d94faaaf 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -106,7 +106,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index 261339f70dd43c..b4d8264a3ab807 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -200,7 +200,7 @@ def fan_mode(self): return FAN_AUTO @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" return { ATTR_FAN_STATE: self._client.fanstate, diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 4bfa72b5eb648d..ff8dc8b96d1f2f 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -248,7 +248,7 @@ def should_poll(self) -> bool: return self.vera_device.should_poll @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attr = {} diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index b77f17d3b0ab18..f98319b6ccf0f4 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -61,14 +61,14 @@ def is_locked(self) -> Optional[bool]: return self._state == STATE_LOCKED @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Who unlocked the lock and did a low battery alert fire. Reports on the previous poll cycle. changed_by_name is a string like 'Bob'. low_battery is 1 if an alert fired, 0 otherwise. """ - data = super().device_state_attributes + data = super().extra_state_attributes last_user = self.vera_device.get_last_user_alert() if last_user is not None: diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 2274b67f6835f6..4ecbe8c724e1a2 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -53,6 +53,6 @@ def name(self) -> str: return self._name @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the scene.""" return {"vera_scene_id": self.vera_scene.vera_scene_id} diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index c030383a4a692b..645cde63459706 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -118,7 +118,7 @@ def state(self): return self.haversion.api.version @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return attributes for the sensor.""" return self.haversion.api.version_data diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index 1d1320d8d7832c..d01d3d4dc5dd3f 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -101,7 +101,7 @@ def unique_info(self): return self.smartfan.uuid @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the fan.""" return { "mode": self.smartfan.mode, diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index 0ce4b931def2f1..1d01e340b203eb 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -72,7 +72,7 @@ def __init__(self, plug): self.smartplug = plug @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" if not hasattr(self.smartplug, "weekly_energy_total"): return {} diff --git a/homeassistant/components/viaggiatreno/sensor.py b/homeassistant/components/viaggiatreno/sensor.py index 5de968f5eacc4f..e886b9d9728dd4 100644 --- a/homeassistant/components/viaggiatreno/sensor.py +++ b/homeassistant/components/viaggiatreno/sensor.py @@ -119,7 +119,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return extra attributes.""" self._attributes[ATTR_ATTRIBUTION] = ATTRIBUTION return self._attributes diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index d1accd8ea0a4c8..4878ea330f4370 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -278,7 +278,7 @@ def set_preset_mode(self, preset_mode): self._api.activateProgram(vicare_program) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Show Device Attributes.""" return self._attributes diff --git a/homeassistant/components/volvooncall/__init__.py b/homeassistant/components/volvooncall/__init__.py index 743bb903c72611..556a5f2511411b 100644 --- a/homeassistant/components/volvooncall/__init__.py +++ b/homeassistant/components/volvooncall/__init__.py @@ -277,7 +277,7 @@ def assumed_state(self): return True @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return dict( self.instrument.attributes, diff --git a/homeassistant/components/vultr/binary_sensor.py b/homeassistant/components/vultr/binary_sensor.py index c1b60479e7af8c..c62d5136aa6f46 100644 --- a/homeassistant/components/vultr/binary_sensor.py +++ b/homeassistant/components/vultr/binary_sensor.py @@ -86,7 +86,7 @@ def device_class(self): return DEFAULT_DEVICE_CLASS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Vultr subscription.""" return { ATTR_ALLOWED_BANDWIDTH: self.data.get("allowed_bandwidth_gb"), diff --git a/homeassistant/components/vultr/switch.py b/homeassistant/components/vultr/switch.py index a9c43717a71fd0..f93c4f444d6981 100644 --- a/homeassistant/components/vultr/switch.py +++ b/homeassistant/components/vultr/switch.py @@ -80,7 +80,7 @@ def icon(self): return "mdi:server" if self.is_on else "mdi:server-off" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Vultr subscription.""" return { ATTR_ALLOWED_BANDWIDTH: self.data.get("allowed_bandwidth_gb"), diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index ec18880b5ba1e7..ac43da68641f49 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -151,7 +151,7 @@ def unit_of_measurement(self): return "AQI" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" attrs = {} diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index 0357825cb12711..be2cf7ca2da492 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -176,7 +176,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the last update.""" if self._waze_data.duration is None: return None diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 4807d780a480d6..0fc442b37e98f1 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -318,7 +318,7 @@ def supported_features(self): return supported @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" if self._client.sound_output is None and self.state == STATE_OFF: return {} diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index d8f54057557f75..a3da5edae766be 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -115,7 +115,7 @@ def icon(self): return "mdi:water-percent" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return { ATTR_CURRENT_HUMIDITY: self._current_humidity, diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 15b38550b938bc..5e97031786c177 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -59,7 +59,7 @@ def __init__(self, device): self._mode_string = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" attr = {} if self.maker_params: diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 0e3c0c6e0da365..72c992456a7e72 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -81,7 +81,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get the more info attributes.""" return self._attributes diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index 26666bf4b15674..198bddc937b560 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -778,7 +778,7 @@ def should_poll(self): return self.wink.pubnub_channel is None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = {} battery = self._battery_level @@ -855,9 +855,9 @@ def icon(self): return "mdi:bell-ring" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes auto_shutoff = self.wink.auto_shutoff() if auto_shutoff is not None: @@ -913,9 +913,9 @@ def name(self): return f"{self.parent.name()} dial {self.wink.index() + 1}" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes dial_attributes = self.dial_attributes() return {**attributes, **dial_attributes} diff --git a/homeassistant/components/wink/alarm_control_panel.py b/homeassistant/components/wink/alarm_control_panel.py index 5c45cc7b03dc21..2f5ac83c6f528c 100644 --- a/homeassistant/components/wink/alarm_control_panel.py +++ b/homeassistant/components/wink/alarm_control_panel.py @@ -70,6 +70,6 @@ def alarm_arm_away(self, code=None): self.wink.set_mode("away") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"private": self.wink.private()} diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index 77ff464a5bf523..ea864e912f0203 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -119,18 +119,18 @@ def device_class(self): return SENSOR_TYPES.get(self.capability) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - return super().device_state_attributes + return super().extra_state_attributes class WinkSmokeDetector(WinkBinarySensorEntity): """Representation of a Wink Smoke detector.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["test_activated"] = self.wink.test_activated() return _attributes @@ -139,9 +139,9 @@ class WinkHub(WinkBinarySensorEntity): """Representation of a Wink Hub.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["update_needed"] = self.wink.update_needed() _attributes["firmware_version"] = self.wink.firmware_version() _attributes["pairing_mode"] = self.wink.pairing_mode() @@ -159,9 +159,9 @@ class WinkRemote(WinkBinarySensorEntity): """Representation of a Wink Lutron Connected bulb remote.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["button_on_pressed"] = self.wink.button_on_pressed() _attributes["button_off_pressed"] = self.wink.button_off_pressed() _attributes["button_up_pressed"] = self.wink.button_up_pressed() @@ -178,9 +178,9 @@ class WinkButton(WinkBinarySensorEntity): """Representation of a Wink Relay button.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" - _attributes = super().device_state_attributes + _attributes = super().extra_state_attributes _attributes["pressed"] = self.wink.pressed() _attributes["long_pressed"] = self.wink.long_pressed() return _attributes diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index 7ee05f0a729c81..4c783e6bde1568 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -99,7 +99,7 @@ def temperature_unit(self): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional device state attributes.""" data = {} if self.external_temperature is not None: @@ -396,7 +396,7 @@ def temperature_unit(self): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional device state attributes.""" data = {} data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption() diff --git a/homeassistant/components/wink/lock.py b/homeassistant/components/wink/lock.py index f82b74e7712310..63a67d9f1ac3c1 100644 --- a/homeassistant/components/wink/lock.py +++ b/homeassistant/components/wink/lock.py @@ -187,9 +187,9 @@ def set_alarm_mode(self, mode): self.wink.set_alarm_mode(mode) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - super_attrs = super().device_state_attributes + super_attrs = super().extra_state_attributes sensitivity = dict_value_to_key( ALARM_SENSITIVITY_MAP, self.wink.alarm_sensitivity() ) diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index cd3eb756fb3d12..d2de4c43945901 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -83,9 +83,9 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - super_attrs = super().device_state_attributes + super_attrs = super().extra_state_attributes try: super_attrs["egg_times"] = self.wink.eggs() except AttributeError: diff --git a/homeassistant/components/wink/switch.py b/homeassistant/components/wink/switch.py index 2632036095ad37..d377ae0cddf5e1 100644 --- a/homeassistant/components/wink/switch.py +++ b/homeassistant/components/wink/switch.py @@ -48,9 +48,9 @@ def turn_off(self, **kwargs): self.wink.set_state(False) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attributes = super().device_state_attributes + attributes = super().extra_state_attributes try: event = self.wink.last_event() if event is not None: diff --git a/homeassistant/components/wink/water_heater.py b/homeassistant/components/wink/water_heater.py index 0ce31762c7a656..bf5e84347460bc 100644 --- a/homeassistant/components/wink/water_heater.py +++ b/homeassistant/components/wink/water_heater.py @@ -66,7 +66,7 @@ def temperature_unit(self): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional device state attributes.""" data = {} data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled() diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 83e92c2250b72d..0efbc80f13c2ed 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -272,7 +272,7 @@ def update(self): self._state = self.updated_state_value() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_BATTERY_LEVEL: int(self._tag.battery_remaining * 100), diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index f89cf06a44c592..d25c0fc9064089 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -188,7 +188,7 @@ def available(self) -> bool: return super().available @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" playlist = self.coordinator.data.state.playlist if playlist == -1: diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index 89d76776a82fb9..da002e1e8f0cfa 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -92,7 +92,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_LED_COUNT: self.coordinator.data.info.leds.count, diff --git a/homeassistant/components/wled/switch.py b/homeassistant/components/wled/switch.py index 38ebd0e9b29881..e58b32425cbd61 100644 --- a/homeassistant/components/wled/switch.py +++ b/homeassistant/components/wled/switch.py @@ -72,7 +72,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return { ATTR_DURATION: self.coordinator.data.state.nightlight.duration, @@ -110,7 +110,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} @@ -144,7 +144,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator): ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index 201979d4dc3349..9ea7f9d116301a 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -69,7 +69,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { "parameter_id": self.wolf_object.parameter_id, diff --git a/homeassistant/components/worldtidesinfo/sensor.py b/homeassistant/components/worldtidesinfo/sensor.py index aaa9f2d1585919..43c9446b6ce0e5 100644 --- a/homeassistant/components/worldtidesinfo/sensor.py +++ b/homeassistant/components/worldtidesinfo/sensor.py @@ -72,7 +72,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of this device.""" attr = {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py index 786fd07f6265c3..34ad5a37ec8dab 100644 --- a/homeassistant/components/wsdot/sensor.py +++ b/homeassistant/components/wsdot/sensor.py @@ -120,7 +120,7 @@ def update(self): self._state = self._data.get(ATTR_CURRENT_TIME) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" if self._data is not None: attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index e1bd79b7ea0efe..1f4332c35e7946 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -70,7 +70,7 @@ def __init__( unit_of_measurement: Optional[str] = None, entity_picture=None, icon: str = "mdi:gauge", - device_state_attributes=None, + extra_state_attributes=None, device_class=None, ): """Initialize sensor configuration. @@ -82,7 +82,7 @@ def __init__( :param unit_of_measurement: unit of measurement :param entity_picture: value or callback returning URL of entity picture :param icon: icon name or URL - :param device_state_attributes: dictionary of attributes, or callable that returns it + :param extra_state_attributes: dictionary of attributes, or callable that returns it """ self.friendly_name = friendly_name self.unit_of_measurement = unit_of_measurement @@ -90,7 +90,7 @@ def __init__( self.value = value self.entity_picture = entity_picture self.icon = icon - self.device_state_attributes = device_state_attributes or {} + self.extra_state_attributes = extra_state_attributes or {} self.device_class = device_class @@ -121,7 +121,7 @@ def __init__( entity_picture=lambda wu: wu.data["current_observation"]["icon_url"] if icon is None else None, - device_state_attributes={ + extra_state_attributes={ "date": lambda wu: wu.data["current_observation"]["observation_time"] }, device_class=device_class, @@ -152,7 +152,7 @@ def __init__( "forecastday" ][period]["icon_url"], unit_of_measurement=unit_of_measurement, - device_state_attributes={ + extra_state_attributes={ "date": lambda wu: wu.data["forecast"]["txt_forecast"]["date"] }, ) @@ -201,7 +201,7 @@ def __init__( if not icon else None, icon=icon, - device_state_attributes={ + extra_state_attributes={ "date": lambda wu: wu.data["forecast"]["simpleforecast"]["forecastday"][ period ]["date"]["pretty"] @@ -227,7 +227,7 @@ def __init__(self, period: int, field: int): feature="hourly", value=lambda wu: wu.data["hourly_forecast"][period][field], entity_picture=lambda wu: wu.data["hourly_forecast"][period]["icon_url"], - device_state_attributes={ + extra_state_attributes={ "temp_c": lambda wu: wu.data["hourly_forecast"][period]["temp"][ "metric" ], @@ -315,7 +315,7 @@ def __init__(self, friendly_name: Union[str, Callable]): icon=lambda wu: "mdi:alert-circle-outline" if wu.data["alerts"] else "mdi:check-circle-outline", - device_state_attributes=self._get_attributes, + extra_state_attributes=self._get_attributes, ) @staticmethod @@ -1157,7 +1157,7 @@ def _cfg_expand(self, what, default=None): def _update_attrs(self): """Parse and update device state attributes.""" - attrs = self._cfg_expand("device_state_attributes", {}) + attrs = self._cfg_expand("extra_state_attributes", {}) for (attr, callback) in attrs.items(): if callable(callback): @@ -1185,7 +1185,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index 300fcbfb095e63..780051b2d872b2 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -104,7 +104,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = {"gamerscore": self._gamerscore, "tier": self._tier} diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index 53a9427d30ac49..ba7f717f42196e 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -241,7 +241,7 @@ def __init__(self, device, device_type, xiaomi_hub, config_entry): self._type = device_type self._write_to_hub = xiaomi_hub.write_to_hub self._get_from_hub = xiaomi_hub.get_from_hub - self._device_state_attributes = {} + self._extra_state_attributes = {} self._remove_unavailability_tracker = None self._xiaomi_hub = xiaomi_hub self.parse_data(device["data"], device["raw_data"]) @@ -319,9 +319,9 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @callback def _async_set_unavailable(self, now): @@ -364,11 +364,11 @@ def parse_voltage(self, data): max_volt = 3300 min_volt = 2800 voltage = data[voltage_key] - self._device_state_attributes[ATTR_VOLTAGE] = round(voltage / 1000.0, 2) + self._extra_state_attributes[ATTR_VOLTAGE] = round(voltage / 1000.0, 2) voltage = min(voltage, max_volt) voltage = max(voltage, min_volt) percent = ((voltage - min_volt) / (max_volt - min_volt)) * 100 - self._device_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1) + self._extra_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1) return True def parse_data(self, data, raw_data): diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index 8fbecee46e9d70..3d9437e377815e 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -170,10 +170,10 @@ def __init__(self, device, xiaomi_hub, config_entry): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_DENSITY: self._density} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -214,10 +214,10 @@ def __init__(self, device, hass, xiaomi_hub, config_entry): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_NO_MOTION_SINCE: self._no_motion_since} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs @callback @@ -308,10 +308,10 @@ def __init__(self, device, xiaomi_hub, config_entry): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_OPEN_SINCE: self._open_since} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -389,10 +389,10 @@ def __init__(self, device, xiaomi_hub, config_entry): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_DENSITY: self._density} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -424,10 +424,10 @@ def __init__(self, device, name, data_key, xiaomi_hub, config_entry): super().__init__(device, name, xiaomi_hub, data_key, None, config_entry) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_ACTION: self._last_action} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -459,10 +459,10 @@ def __init__(self, device, name, data_key, hass, xiaomi_hub, config_entry): super().__init__(device, name, xiaomi_hub, data_key, None, config_entry) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_ACTION: self._last_action} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): @@ -519,10 +519,10 @@ def __init__(self, device, hass, xiaomi_hub, config_entry): super().__init__(device, "Cube", xiaomi_hub, data_key, None, config_entry) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_LAST_ACTION: self._last_action} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs def parse_data(self, data, raw_data): diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index 7c5334e0f5c565..5afb1701e336b6 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -50,7 +50,7 @@ def changed_by(self) -> int: return self._changed_by @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" attributes = {ATTR_VERIFIED_WRONG_TIMES: self._verified_wrong_times} return attributes diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 5b1d3467d25b63..969980bf7c8104 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -194,7 +194,7 @@ def parse_data(self, data, raw_data): succeed = super().parse_voltage(data) if not succeed: return False - battery_level = int(self._device_state_attributes.pop(ATTR_BATTERY_LEVEL)) + battery_level = int(self._extra_state_attributes.pop(ATTR_BATTERY_LEVEL)) if battery_level <= 0 or battery_level > 100: return False self._state = battery_level diff --git a/homeassistant/components/xiaomi_aqara/switch.py b/homeassistant/components/xiaomi_aqara/switch.py index 6e75ddb487e01a..8b16b6491c7ace 100644 --- a/homeassistant/components/xiaomi_aqara/switch.py +++ b/homeassistant/components/xiaomi_aqara/switch.py @@ -157,7 +157,7 @@ def is_on(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._supports_power_consumption: attrs = { @@ -167,7 +167,7 @@ def device_state_attributes(self): } else: attrs = {} - attrs.update(super().device_state_attributes) + attrs.update(super().extra_state_attributes) return attrs @property diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index b278a60bd48973..1a5aabcc14149b 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -165,7 +165,7 @@ def humidity(self): return self._humidity @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" data = {} diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index addb1a3cbcabe9..cc8ee74feb0bd1 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -651,7 +651,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index c0590fbb332406..c6d8b67bb07601 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -260,7 +260,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 6d43b835f1c24f..5769c1fb475cf5 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -185,7 +185,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 0a35e8e0a35e02..fd75e9f00888f9 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -252,7 +252,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._state_attrs diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index bbb4cfd1b7f3dd..b6cb2b76ae68fe 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -305,7 +305,7 @@ def timers(self): ] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the specific state attributes of this vacuum cleaner.""" attrs = {} if self.vacuum_state is not None: diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index 957844e519d9ae..0df073f581d25d 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -124,7 +124,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index e4044303ef0232..4c1cb9a0e82e60 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -560,7 +560,7 @@ def _predefined_effects(self): return YEELIGHT_MONO_EFFECT_LIST @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = { "flowing": self.device.is_color_flow_enabled, diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 3fa29a0789688b..4ef0da85daac29 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -116,7 +116,7 @@ def update(self): self._state = len(triggers) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attributes diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index 4852e874672ac5..ef0a476f612da5 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -157,7 +157,7 @@ def unit_of_measurement(self): return SENSOR_TYPES[self.variable][1] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py index cdf7e6304adafb..ed15d42b7e3565 100644 --- a/homeassistant/components/zestimate/sensor.py +++ b/homeassistant/components/zestimate/sensor.py @@ -86,7 +86,7 @@ def state(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attributes = {} if self.data is not None: diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index ab0f15f7559515..f947012d3afaad 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -185,7 +185,7 @@ def current_temperature(self): return self._thrm.local_temp / ZCL_TEMP @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" data = {} if self.hvac_mode: diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 3c8f0c59cb1945..c19bad21455dae 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -44,7 +44,7 @@ def __init__(self, unique_id: str, zha_device: ZhaDeviceType, **kwargs): self._should_poll: bool = False self._unique_id: str = unique_id self._state: Any = None - self._device_state_attributes: Dict[str, Any] = {} + self._extra_state_attributes: Dict[str, Any] = {} self._zha_device: ZhaDeviceType = zha_device self._unsubs: List[CALLABLE_T] = [] self.remove_future: Awaitable[None] = None @@ -65,9 +65,9 @@ def zha_device(self) -> ZhaDeviceType: return self._zha_device @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return device specific state attributes.""" - return self._device_state_attributes + return self._extra_state_attributes @property def force_update(self) -> bool: @@ -101,7 +101,7 @@ def async_state_changed(self) -> None: @callback def async_update_state_attribute(self, key: str, value: Any) -> None: """Update a single device state attribute.""" - self._device_state_attributes.update({key: value}) + self._extra_state_attributes.update({key: value}) self.async_write_ha_state() @callback diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 32b8a064054bb6..e7d9be62374970 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -135,7 +135,7 @@ def __init__(self, *args, **kwargs): self._identify_channel = None @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return state attributes.""" attributes = {"off_brightness": self._off_brightness} return attributes diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index 5cc7d7c56f6cb4..2ed186d807ca12 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -73,7 +73,7 @@ def is_locked(self) -> bool: return self._state == STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self.state_attributes diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 84d2dfb06bb288..926fd4555e111f 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -178,7 +178,7 @@ def formatter(value: int) -> int: return value @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return device state attrs for battery sensors.""" state_attrs = {} battery_size = self._channel.cluster.get("battery_size") diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py index 5113c5c6e1821f..b602d7a50c40d1 100644 --- a/homeassistant/components/zodiac/sensor.py +++ b/homeassistant/components/zodiac/sensor.py @@ -196,7 +196,7 @@ def icon(self): return ZODIAC_ICONS.get(self._state) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 649f17ff08fd5b..b8265f1e089cd1 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -1362,7 +1362,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = { const.ATTR_NODE_ID: self.node_id, diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 9c9c1ed6128296..20eb52c2c194b6 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -571,9 +571,9 @@ def set_swing_mode(self, swing_mode): self.values.zxt_120_swing_mode.data = swing_mode @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the optional state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes if self._fan_action: data[ATTR_FAN_ACTION] = self._fan_action return data diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index c9601679f573f9..605a64ada5d1b0 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -374,9 +374,9 @@ def unlock(self, **kwargs): self.values.primary.data = False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" - data = super().device_state_attributes + data = super().extra_state_attributes if self._notification: data[ATTR_NOTIFICATION] = self._notification if self._lock_status: diff --git a/homeassistant/components/zwave/node_entity.py b/homeassistant/components/zwave/node_entity.py index 05e5951f921f2d..3fa26439ad5642 100644 --- a/homeassistant/components/zwave/node_entity.py +++ b/homeassistant/components/zwave/node_entity.py @@ -351,7 +351,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attrs = { ATTR_NODE_ID: self.node_id, diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 26e9e730283efe..1a15c45049ba9e 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -326,7 +326,7 @@ def fan_modes(self) -> Optional[List[str]]: return None @property - def device_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> Optional[Dict[str, str]]: """Return the optional state attributes.""" if ( self._fan_state diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 8e22323c733fd0..a6d44cb62d0ddd 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -199,7 +199,7 @@ def state(self) -> Optional[str]: ) @property - def device_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> Optional[Dict[str, str]]: """Return the device specific state attributes.""" # add the value's int value as property for multi-value (list) items return {"value": self.info.primary_value.value} diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 1dd44625ebe955..24d73272f18345 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -257,7 +257,7 @@ def test_motion_recording_mode_properties(self): assert not self.uvc.is_recording assert ( datetime(2021, 1, 8, 1, 56, 32, 367000) - == self.uvc.device_state_attributes["last_recording_start_time"] + == self.uvc.extra_state_attributes["last_recording_start_time"] ) self.nvr.get_camera.return_value["recordingIndicator"] = "DISABLED" diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index ab12bfda12c2b9..7fb0c90362a776 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -76,7 +76,7 @@ def test_binary_sensor(self, mock): assert "Vultr {}" == device.name device.update() - device_attrs = device.device_state_attributes + device_attrs = device.extra_state_attributes if device.subscription == "555555": assert "Vultr Another Server" == device.name diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index 12af400a44aee4..d6b7392ca9c0ae 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -77,7 +77,7 @@ def test_switch(self, mock): tested += 1 device.update() - device_attrs = device.device_state_attributes + device_attrs = device.extra_state_attributes if device.subscription == "555555": assert device.name == "Vultr Another Server" diff --git a/tests/components/wsdot/test_sensor.py b/tests/components/wsdot/test_sensor.py index 1b1c2e090f2b3b..bbb56efdedadc2 100644 --- a/tests/components/wsdot/test_sensor.py +++ b/tests/components/wsdot/test_sensor.py @@ -50,9 +50,9 @@ def add_entities(new_entities, update_before_add=False): assert sensor.name == "I90 EB" assert sensor.state == 11 assert ( - sensor.device_state_attributes[ATTR_DESCRIPTION] + sensor.extra_state_attributes[ATTR_DESCRIPTION] == "Downtown Seattle to Downtown Bellevue via I-90" ) - assert sensor.device_state_attributes[ATTR_TIME_UPDATED] == datetime( + assert sensor.extra_state_attributes[ATTR_TIME_UPDATED] == datetime( 2017, 1, 21, 15, 10, tzinfo=timezone(timedelta(hours=-8)) ) diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index 5275ca79506721..1afe961709745d 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -845,10 +845,10 @@ def test_hvac_action_value_changed_unknown(device_unknown): def test_fan_action_value_changed(device): """Test values changed for climate device.""" - assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 7 + assert device.extra_state_attributes[climate.ATTR_FAN_ACTION] == 7 device.values.fan_action.data = 9 value_changed(device.values.fan_action) - assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9 + assert device.extra_state_attributes[climate.ATTR_FAN_ACTION] == 9 def test_aux_heat_unsupported_set(device): diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 878513da579030..e76754a9872cb9 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -242,7 +242,7 @@ async def test_device_entity(hass, mock_openzwave): assert not device.should_poll assert device.unique_id == "10-11" assert device.name == "Mock Node Sensor" - assert device.device_state_attributes[zwave.ATTR_POWER] == 50.123 + assert device.extra_state_attributes[zwave.ATTR_POWER] == 50.123 async def test_node_removed(hass, mock_openzwave): diff --git a/tests/components/zwave/test_lock.py b/tests/components/zwave/test_lock.py index d5b6d0a0d275dd..9050f06f87bb23 100644 --- a/tests/components/zwave/test_lock.py +++ b/tests/components/zwave/test_lock.py @@ -98,7 +98,7 @@ def test_track_message_workaround(mock_openzwave): device = lock.get_device(node=node, values=values) value_changed(values.primary) assert device.is_locked - assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" + assert device.extra_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" # Simulate a keypad unlock. We trigger a value_changed() which simulates # the Alarm notification received from the lock. Then, we trigger @@ -113,7 +113,7 @@ def test_track_message_workaround(mock_openzwave): value_changed(values.primary) assert not device.is_locked assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Unlocked with Keypad by user 3" ) @@ -122,7 +122,7 @@ def test_track_message_workaround(mock_openzwave): node.stats["lastReceivedMessage"][5] = const.COMMAND_CLASS_DOOR_LOCK value_changed(values.primary) assert device.is_locked - assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" + assert device.extra_state_attributes[lock.ATTR_NOTIFICATION] == "RF Lock" def test_v2btze_value_changed(mock_openzwave): @@ -198,7 +198,7 @@ def test_lock_access_control(mock_openzwave): ) device = lock.get_device(node=node, values=values, node_config={}) - assert device.device_state_attributes[lock.ATTR_NOTIFICATION] == "Lock Jammed" + assert device.extra_state_attributes[lock.ATTR_NOTIFICATION] == "Lock Jammed" def test_lock_alarm_type(mock_openzwave): @@ -212,28 +212,28 @@ def test_lock_alarm_type(mock_openzwave): ) device = lock.get_device(node=node, values=values, node_config={}) - assert lock.ATTR_LOCK_STATUS not in device.device_state_attributes + assert lock.ATTR_LOCK_STATUS not in device.extra_state_attributes values.alarm_type.data = 21 value_changed(values.alarm_type) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] == "Manually Locked None" + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Manually Locked None" ) values.alarm_type.data = 18 value_changed(values.alarm_type) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Locked with Keypad by user None" ) values.alarm_type.data = 161 value_changed(values.alarm_type) - assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == "Tamper Alarm: None" + assert device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Tamper Alarm: None" values.alarm_type.data = 9 value_changed(values.alarm_type) - assert device.device_state_attributes[lock.ATTR_LOCK_STATUS] == "Deadbolt Jammed" + assert device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Deadbolt Jammed" def test_lock_alarm_level(mock_openzwave): @@ -247,14 +247,14 @@ def test_lock_alarm_level(mock_openzwave): ) device = lock.get_device(node=node, values=values, node_config={}) - assert lock.ATTR_LOCK_STATUS not in device.device_state_attributes + assert lock.ATTR_LOCK_STATUS not in device.extra_state_attributes values.alarm_type.data = 21 values.alarm_level.data = 1 value_changed(values.alarm_type) value_changed(values.alarm_level) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Manually Locked by Key Cylinder or Inside thumb turn" ) @@ -263,7 +263,7 @@ def test_lock_alarm_level(mock_openzwave): value_changed(values.alarm_type) value_changed(values.alarm_level) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Locked with Keypad by user alice" ) @@ -272,7 +272,7 @@ def test_lock_alarm_level(mock_openzwave): value_changed(values.alarm_type) value_changed(values.alarm_level) assert ( - device.device_state_attributes[lock.ATTR_LOCK_STATUS] + device.extra_state_attributes[lock.ATTR_LOCK_STATUS] == "Tamper Alarm: Too many keypresses" ) diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index ba77aabc923866..c47201fb1682a4 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -193,8 +193,7 @@ def listener(event): # Make sure application version isn't set before assert ( - node_entity.ATTR_APPLICATION_VERSION - not in entity.device_state_attributes.keys() + node_entity.ATTR_APPLICATION_VERSION not in entity.extra_state_attributes.keys() ) # Add entity to hass @@ -212,9 +211,7 @@ def listener(event): ) await hass.async_block_till_done() - assert ( - entity.device_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "5.10" - ) + assert entity.extra_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "5.10" # Fire off a changed value = mock_zwave.MockValue( @@ -227,9 +224,7 @@ def listener(event): ) await hass.async_block_till_done() - assert ( - entity.device_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "4.14" - ) + assert entity.extra_state_attributes[node_entity.ATTR_APPLICATION_VERSION] == "4.14" async def test_network_node_changed_from_value(hass, mock_openzwave): @@ -306,7 +301,7 @@ async def test_node_changed(hass, mock_openzwave): "node_name": "Mock Node", "manufacturer_name": "Test Manufacturer", "product_name": "Test Product", - } == entity.device_state_attributes + } == entity.extra_state_attributes node.get_values.return_value = {1: mock_zwave.MockValue(data=1800)} zwave_network.manager.getNodeStatistics.return_value = { @@ -616,12 +611,12 @@ async def test_node_changed(hass, mock_openzwave): "sentCnt": 7, "sentFailed": 1, "sentTS": "2017-03-27 15:38:15:620 ", - } == entity.device_state_attributes + } == entity.extra_state_attributes node.can_wake_up_value = False entity.node_changed() - assert "wake_up_interval" not in entity.device_state_attributes + assert "wake_up_interval" not in entity.extra_state_attributes async def test_name(hass, mock_openzwave): diff --git a/tests/testing_config/custom_components/test/image_processing.py b/tests/testing_config/custom_components/test/image_processing.py index a2004fe32bdab8..343c60a78fef03 100644 --- a/tests/testing_config/custom_components/test/image_processing.py +++ b/tests/testing_config/custom_components/test/image_processing.py @@ -41,7 +41,7 @@ def state(self): return self._count @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {"image": self._image} From 14ff6d4d1ff307de6b644496ece4e50acb1d68db Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 11 Mar 2021 21:23:20 +0100 Subject: [PATCH 1195/1818] Update integrations p-s to override extra_state_attributes() (#47759) --- homeassistant/components/pencom/switch.py | 2 +- homeassistant/components/pi_hole/sensor.py | 2 +- homeassistant/components/ping/binary_sensor.py | 2 +- homeassistant/components/plaato/entity.py | 2 +- homeassistant/components/plex/media_player.py | 2 +- homeassistant/components/plex/sensor.py | 2 +- .../components/plugwise/binary_sensor.py | 2 +- homeassistant/components/plugwise/climate.py | 2 +- homeassistant/components/point/__init__.py | 2 +- homeassistant/components/poolsense/sensor.py | 2 +- homeassistant/components/powerwall/sensor.py | 2 +- homeassistant/components/proliphix/climate.py | 2 +- homeassistant/components/push/camera.py | 2 +- homeassistant/components/pushbullet/sensor.py | 2 +- homeassistant/components/pvoutput/sensor.py | 2 +- .../components/pvpc_hourly_pricing/sensor.py | 2 +- .../components/qld_bushfire/geo_location.py | 2 +- homeassistant/components/qnap/sensor.py | 10 +++++----- homeassistant/components/qvr_pro/camera.py | 2 +- homeassistant/components/rachio/switch.py | 4 ++-- homeassistant/components/radarr/sensor.py | 2 +- homeassistant/components/radiotherm/climate.py | 2 +- homeassistant/components/rainbird/switch.py | 2 +- homeassistant/components/raincloud/__init__.py | 2 +- homeassistant/components/raincloud/switch.py | 2 +- .../components/rainmachine/__init__.py | 2 +- homeassistant/components/random/sensor.py | 2 +- .../components/recollect_waste/sensor.py | 2 +- homeassistant/components/reddit/sensor.py | 2 +- homeassistant/components/rejseplanen/sensor.py | 2 +- homeassistant/components/repetier/sensor.py | 2 +- homeassistant/components/rest/sensor.py | 2 +- homeassistant/components/rflink/light.py | 4 ++-- homeassistant/components/rfxtrx/__init__.py | 2 +- homeassistant/components/ring/binary_sensor.py | 4 ++-- homeassistant/components/ring/camera.py | 2 +- homeassistant/components/ring/entity.py | 2 +- homeassistant/components/ring/sensor.py | 4 ++-- homeassistant/components/ripple/sensor.py | 2 +- .../components/risco/binary_sensor.py | 2 +- homeassistant/components/risco/sensor.py | 2 +- .../components/rituals_perfume_genie/switch.py | 2 +- homeassistant/components/roomba/braava.py | 4 ++-- homeassistant/components/roomba/irobot_base.py | 2 +- homeassistant/components/roomba/roomba.py | 4 ++-- .../components/sense/binary_sensor.py | 2 +- homeassistant/components/sense/sensor.py | 8 ++++---- homeassistant/components/sensibo/climate.py | 2 +- homeassistant/components/serial/sensor.py | 2 +- homeassistant/components/sesame/lock.py | 2 +- .../components/seventeentrack/sensor.py | 4 ++-- homeassistant/components/sharkiq/vacuum.py | 2 +- .../components/shelly/binary_sensor.py | 4 ++-- homeassistant/components/shelly/entity.py | 18 ++++++++---------- homeassistant/components/shelly/sensor.py | 2 +- homeassistant/components/shodan/sensor.py | 2 +- homeassistant/components/sigfox/sensor.py | 2 +- .../components/sighthound/image_processing.py | 2 +- .../components/simplisafe/__init__.py | 2 +- homeassistant/components/simulated/sensor.py | 2 +- homeassistant/components/skybeacon/sensor.py | 4 ++-- homeassistant/components/skybell/__init__.py | 2 +- .../components/skybell/binary_sensor.py | 4 ++-- homeassistant/components/slide/cover.py | 2 +- homeassistant/components/sma/sensor.py | 2 +- .../components/smart_meter_texas/sensor.py | 2 +- .../components/smartthings/climate.py | 2 +- homeassistant/components/smartthings/cover.py | 2 +- homeassistant/components/smartthings/lock.py | 2 +- homeassistant/components/smartthings/scene.py | 2 +- homeassistant/components/smarttub/sensor.py | 4 ++-- homeassistant/components/smhi/weather.py | 2 +- homeassistant/components/sms/sensor.py | 2 +- .../components/snapcast/media_player.py | 4 ++-- homeassistant/components/sochain/sensor.py | 2 +- homeassistant/components/socialblade/sensor.py | 2 +- homeassistant/components/solaredge/sensor.py | 8 ++++---- .../components/solaredge_local/sensor.py | 2 +- homeassistant/components/sonarr/sensor.py | 12 ++++++------ homeassistant/components/sonos/media_player.py | 2 +- .../components/soundtouch/media_player.py | 2 +- .../components/speedtestdotnet/sensor.py | 2 +- homeassistant/components/spotcrime/sensor.py | 2 +- homeassistant/components/sql/sensor.py | 2 +- .../components/squeezebox/media_player.py | 2 +- homeassistant/components/srp_energy/sensor.py | 2 +- .../components/starline/device_tracker.py | 2 +- homeassistant/components/starline/lock.py | 2 +- homeassistant/components/starline/sensor.py | 2 +- homeassistant/components/starline/switch.py | 2 +- homeassistant/components/statistics/sensor.py | 2 +- .../components/steam_online/sensor.py | 2 +- .../components/stiebel_eltron/climate.py | 2 +- .../components/stookalert/binary_sensor.py | 2 +- homeassistant/components/suez_water/sensor.py | 2 +- homeassistant/components/supervisord/sensor.py | 2 +- .../components/surepetcare/binary_sensor.py | 6 +++--- homeassistant/components/surepetcare/sensor.py | 4 ++-- .../swiss_hydrological_data/sensor.py | 2 +- .../swiss_public_transport/sensor.py | 2 +- homeassistant/components/switchbot/switch.py | 2 +- .../components/switcher_kis/switch.py | 2 +- homeassistant/components/syncthru/sensor.py | 2 +- .../components/synology_dsm/__init__.py | 2 +- .../components/synology_dsm/binary_sensor.py | 2 +- tests/components/srp_energy/test_sensor.py | 4 ++-- 106 files changed, 144 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/pencom/switch.py b/homeassistant/components/pencom/switch.py index 7f193bc09a1da2..5621846e4967a4 100644 --- a/homeassistant/components/pencom/switch.py +++ b/homeassistant/components/pencom/switch.py @@ -93,6 +93,6 @@ def update(self): self._state = self._hub.get(self._board, self._addr) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return supported attributes.""" return {"board": self._board, "addr": self._addr} diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 2f5873b14c1c50..4bd4c7b7f6fe9a 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -73,6 +73,6 @@ def state(self): return self.api.data[self._condition] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the Pi-hole.""" return {ATTR_BLOCKED_DOMAINS: self.api.data["domains_being_blocked"]} diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 98c36c01d987eb..a91f7235254a86 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -105,7 +105,7 @@ def is_on(self) -> bool: return self._ping.available @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes of the ICMP checo request.""" if self._ping.data is not False: return { diff --git a/homeassistant/components/plaato/entity.py b/homeassistant/components/plaato/entity.py index 7cb1a77a9fb7b0..a28dfefb56727d 100644 --- a/homeassistant/components/plaato/entity.py +++ b/homeassistant/components/plaato/entity.py @@ -68,7 +68,7 @@ def device_info(self): return device_info @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the monitored installation.""" if self._attributes: return { diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 1a57186bd9b7e2..d32abf86ddb6f5 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -522,7 +522,7 @@ def play_media(self, media_type, media_id, **kwargs): _LOGGER.error("Timed out playing on %s", self.name) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the scene state attributes.""" attributes = {} for attr in [ diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 8c3733a7450c06..47dd05a557b238 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -89,7 +89,7 @@ def icon(self): return "mdi:plex" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._server.sensor_attributes diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 825d27d59bb6d1..023ffa3de701cb 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -143,7 +143,7 @@ def __init__(self, api, coordinator, name, dev_id, binary_sensor): self._attributes = {} @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index c8a2191963ee8e..3efd6dbc3ca778 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -124,7 +124,7 @@ def supported_features(self): return SUPPORT_FLAGS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = {} if self._schema_names: diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index a92f4f3f14f3e9..e5c209004de14a 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -296,7 +296,7 @@ def device_id(self): return self._id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return status of device.""" attrs = self.device.device_status attrs["last_heard_from"] = as_local(self.last_update).strftime( diff --git a/homeassistant/components/poolsense/sensor.py b/homeassistant/components/poolsense/sensor.py index a64cc0aef61e57..bf5c3eb0163a21 100644 --- a/homeassistant/components/poolsense/sensor.py +++ b/homeassistant/components/poolsense/sensor.py @@ -108,6 +108,6 @@ def unit_of_measurement(self): return SENSORS[self.info_type]["unit"] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index 5026d2fb35736b..3b4d7918cf76fd 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -136,7 +136,7 @@ def state(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" meter = self.coordinator.data[POWERWALL_API_METERS].get_meter(self._meter) return { diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py index 5dff4725ea0d42..a293642038e427 100644 --- a/homeassistant/components/proliphix/climate.py +++ b/homeassistant/components/proliphix/climate.py @@ -84,7 +84,7 @@ def precision(self): return PRECISION_TENTHS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return {ATTR_FAN: self._pdp.fan_state} diff --git a/homeassistant/components/push/camera.py b/homeassistant/components/push/camera.py index 31f2f88dac7223..ff0ac45c1393dc 100644 --- a/homeassistant/components/push/camera.py +++ b/homeassistant/components/push/camera.py @@ -175,7 +175,7 @@ def motion_detection_enabled(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { name: value diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py index ff18e86aad992c..f7aaa693c675d2 100644 --- a/homeassistant/components/pushbullet/sensor.py +++ b/homeassistant/components/pushbullet/sensor.py @@ -85,7 +85,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return all known attributes of the sensor.""" return self._state_attributes diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py index 32d33f19e80f29..9e88cb0a664aab 100644 --- a/homeassistant/components/pvoutput/sensor.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -100,7 +100,7 @@ def state(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the monitored installation.""" if self.pvcoutput is not None: return { diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index a9b53c970bd49f..b3486f9d53458d 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -112,7 +112,7 @@ def available(self) -> bool: return self._pvpc_data.state_available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._pvpc_data.attributes diff --git a/homeassistant/components/qld_bushfire/geo_location.py b/homeassistant/components/qld_bushfire/geo_location.py index f608f6e12aedbf..0887e6b7cdd0f6 100644 --- a/homeassistant/components/qld_bushfire/geo_location.py +++ b/homeassistant/components/qld_bushfire/geo_location.py @@ -234,7 +234,7 @@ def unit_of_measurement(self): return LENGTH_KILOMETERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = {} for key, value in ( diff --git a/homeassistant/components/qnap/sensor.py b/homeassistant/components/qnap/sensor.py index 11faba0f210ba7..5f7695e5a6010d 100644 --- a/homeassistant/components/qnap/sensor.py +++ b/homeassistant/components/qnap/sensor.py @@ -268,7 +268,7 @@ def state(self): return round(used / total * 100) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["system_stats"]["memory"] @@ -294,7 +294,7 @@ def state(self): return round_nicely(data["rx"] / 1024 / 1024) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["system_stats"]["nics"][self.monitor_device] @@ -322,7 +322,7 @@ def state(self): return int(self._api.data["system_stats"]["system"]["temp_c"]) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["system_stats"] @@ -360,7 +360,7 @@ def name(self): return f"{server_name} {self.var_name} (Drive {self.monitor_device})" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["smart_drive_health"][self.monitor_device] @@ -394,7 +394,7 @@ def state(self): return round(used_gb / total_gb * 100) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._api.data: data = self._api.data["volumes"][self.monitor_device] diff --git a/homeassistant/components/qvr_pro/camera.py b/homeassistant/components/qvr_pro/camera.py index 9dd8e3c4f20ba1..2f4353063d1149 100644 --- a/homeassistant/components/qvr_pro/camera.py +++ b/homeassistant/components/qvr_pro/camera.py @@ -82,7 +82,7 @@ def brand(self): return self._brand @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get the state attributes.""" attrs = {"qvr_guid": self.guid} diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 44a17acaecfb35..726a6e26ce5e52 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -388,7 +388,7 @@ def entity_picture(self): return self._entity_picture @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" props = {ATTR_ZONE_NUMBER: self._zone_number, ATTR_ZONE_SUMMARY: self._summary} if self._shade_type: @@ -494,7 +494,7 @@ def icon(self) -> str: return "mdi:water" if self.schedule_is_enabled else "mdi:water-off" @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" return { ATTR_SCHEDULE_SUMMARY: self._summary, diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index 27365271014789..9baed6c41c7aea 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -144,7 +144,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" attributes = {} if self.type == "upcoming": diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index f09ef95170f68d..d7bca1175cb419 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -181,7 +181,7 @@ def precision(self): return PRECISION_HALVES @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" return {ATTR_FAN_ACTION: self._fstate} diff --git a/homeassistant/components/rainbird/switch.py b/homeassistant/components/rainbird/switch.py index bccd4d2986ca69..7acb9740616570 100644 --- a/homeassistant/components/rainbird/switch.py +++ b/homeassistant/components/rainbird/switch.py @@ -78,7 +78,7 @@ def __init__(self, controller: RainbirdController, zone, time, name): self._attributes = {ATTR_DURATION: self._duration, "zone": self._zone} @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self._attributes diff --git a/homeassistant/components/raincloud/__init__.py b/homeassistant/components/raincloud/__init__.py index 5955ef67168745..6b0ca39df03722 100644 --- a/homeassistant/components/raincloud/__init__.py +++ b/homeassistant/components/raincloud/__init__.py @@ -166,7 +166,7 @@ def unit_of_measurement(self): return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION, "identifier": self.data.serial} diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py index d6733412cac9ac..d15f5c7c0475bf 100644 --- a/homeassistant/components/raincloud/switch.py +++ b/homeassistant/components/raincloud/switch.py @@ -83,7 +83,7 @@ def update(self): self._state = self.data.auto_watering @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index b4d8510cc77ae3..e71e8a1f6d2c15 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -225,7 +225,7 @@ def device_info(self) -> dict: } @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/random/sensor.py b/homeassistant/components/random/sensor.py index 58d996bc6ec913..7584fe17405660 100644 --- a/homeassistant/components/random/sensor.py +++ b/homeassistant/components/random/sensor.py @@ -74,7 +74,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the sensor.""" return {ATTR_MAXIMUM: self._maximum, ATTR_MINIMUM: self._minimum} diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 66ced51b77f335..0822cdb1f3a312 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -86,7 +86,7 @@ def __init__(self, coordinator: DataUpdateCoordinator, entry: ConfigEntry) -> No self._state = None @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 7a04fb6a8ae8d4..153b6636cc4d19 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -105,7 +105,7 @@ def state(self): return len(self._subreddit_data) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_SUBREDDIT: self._subreddit, diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 30d57a3d9dcaa2..ea55d56b8df095 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -110,7 +110,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self._times: return {ATTR_STOP_ID: self._stop_id, ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/repetier/sensor.py b/homeassistant/components/repetier/sensor.py index e342b2d341ed97..a2b86792aa76d5 100644 --- a/homeassistant/components/repetier/sensor.py +++ b/homeassistant/components/repetier/sensor.py @@ -66,7 +66,7 @@ def available(self) -> bool: return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return sensor attributes.""" return self._attributes diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 0699d9dc07c5a6..5ff5e87c3e65d6 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -119,7 +119,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index fe74c979396934..2a97472f000ccd 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -194,7 +194,7 @@ def brightness(self): return self._brightness @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self._brightness is None: return {} @@ -256,7 +256,7 @@ def brightness(self): return self._brightness @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if self._brightness is None: return {} diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index c9e8c0dee7576d..649a573c5b4527 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -505,7 +505,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" if not self._event: return None diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index bbfbbf1690e0d8..18ce87e722e0d0 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -111,9 +111,9 @@ def unique_id(self): return self._unique_id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes if self._active_alert is None: return attrs diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index bd5950b81a9fa6..8f827aee7d28ee 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -93,7 +93,7 @@ def unique_id(self): return self._device.id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/ring/entity.py b/homeassistant/components/ring/entity.py index 6eb87cb8f9b023..7a1c8ae7bdfaeb 100644 --- a/homeassistant/components/ring/entity.py +++ b/homeassistant/components/ring/entity.py @@ -38,7 +38,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 0a1cc85230f9fe..276d383943880c 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -180,9 +180,9 @@ def state(self): return self._latest_event["created_at"].isoformat() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes if self._latest_event: attrs["created_at"] = self._latest_event["created_at"] diff --git a/homeassistant/components/ripple/sensor.py b/homeassistant/components/ripple/sensor.py index ab0da77b173b04..97cf8b4a794f65 100644 --- a/homeassistant/components/ripple/sensor.py +++ b/homeassistant/components/ripple/sensor.py @@ -57,7 +57,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/risco/binary_sensor.py b/homeassistant/components/risco/binary_sensor.py index ba01b70686b3ba..ba32429c154b02 100644 --- a/homeassistant/components/risco/binary_sensor.py +++ b/homeassistant/components/risco/binary_sensor.py @@ -60,7 +60,7 @@ def unique_id(self): return binary_sensor_unique_id(self._risco, self._zone_id) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"zone_id": self._zone_id, "bypassed": self._zone.bypassed} diff --git a/homeassistant/components/risco/sensor.py b/homeassistant/components/risco/sensor.py index 43d763a35fa7e2..846444e5fbd966 100644 --- a/homeassistant/components/risco/sensor.py +++ b/homeassistant/components/risco/sensor.py @@ -94,7 +94,7 @@ def state(self): return self._event.time @property - def device_state_attributes(self): + def extra_state_attributes(self): """State attributes.""" if self._event is None: return None diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py index 7041d22f4b82ea..471be52b054afc 100644 --- a/homeassistant/components/rituals_perfume_genie/switch.py +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -66,7 +66,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attributes = { "fan_speed": self._diffuser.data["hub"]["attributes"]["speedc"], diff --git a/homeassistant/components/roomba/braava.py b/homeassistant/components/roomba/braava.py index 1a3d106bf8086a..90298078e4238a 100644 --- a/homeassistant/components/roomba/braava.py +++ b/homeassistant/components/roomba/braava.py @@ -116,9 +116,9 @@ async def async_set_fan_speed(self, fan_speed, **kwargs): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - state_attrs = super().device_state_attributes + state_attrs = super().extra_state_attributes # Get Braava state state = self.vacuum_state diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 7dd045a1137337..9d6a0f5cafc531 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -168,7 +168,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" state = self.vacuum_state diff --git a/homeassistant/components/roomba/roomba.py b/homeassistant/components/roomba/roomba.py index 0a9aec0b60832c..5f960aeaae0e33 100644 --- a/homeassistant/components/roomba/roomba.py +++ b/homeassistant/components/roomba/roomba.py @@ -23,9 +23,9 @@ class RoombaVacuum(IRobotVacuum): """Basic Roomba robot (without carpet boost).""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" - state_attrs = super().device_state_attributes + state_attrs = super().extra_state_attributes # Get bin state bin_raw_state = self.vacuum_state.get("bin", {}) diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index bc06721ae5e38b..ae5e4fc95bc914 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -101,7 +101,7 @@ def old_unique_id(self): return self._id @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 25fa5943bd5fef..7b6e415d4a31fc 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -163,7 +163,7 @@ def unit_of_measurement(self): return POWER_WATT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -247,7 +247,7 @@ def unit_of_measurement(self): return VOLT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -333,7 +333,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @@ -415,7 +415,7 @@ def unit_of_measurement(self): return POWER_WATT @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 26276650752f5a..10ceaa39a38b19 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -191,7 +191,7 @@ def state(self): return self._external_state or super().state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {"battery": self.current_battery} diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py index e0bf23a251405e..02590ccfe8fdbf 100644 --- a/homeassistant/components/serial/sensor.py +++ b/homeassistant/components/serial/sensor.py @@ -241,7 +241,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the entity (if any JSON present).""" return self._attributes diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index 9c86c2622353b9..acd71b7c9e74af 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -86,7 +86,7 @@ def update(self) -> None: self._responsive = status["responsive"] @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return { ATTR_DEVICE_ID: self._device_id, diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 07cfe9ca66fcce..15110f6a0c302c 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -109,7 +109,7 @@ def available(self): return self._state is not None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs @@ -190,7 +190,7 @@ def available(self): return self._data.packages.get(self._tracking_number) is not None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" return self._attrs diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 9684dde45e64de..5b4254eebb7989 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -255,7 +255,7 @@ def low_light(self): return self.sharkiq.get_property_value(Properties.LOW_LIGHT_MISSION) @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return a dictionary of device state attributes specific to sharkiq.""" data = { ATTR_ERROR_CODE: self.error_code, diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 18220fc9e3ac89..385b3b30c36dd0 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -47,7 +47,7 @@ name="Gas", device_class=DEVICE_CLASS_GAS, value=lambda value: value in ["mild", "heavy"], - device_state_attributes=lambda block: {"detected": block.gas}, + extra_state_attributes=lambda block: {"detected": block.gas}, ), ("sensor", "smoke"): BlockAttributeDescription( name="Smoke", device_class=DEVICE_CLASS_SMOKE @@ -95,7 +95,7 @@ icon="mdi:update", value=lambda status, _: status["update"]["has_update"], default_enabled=False, - device_state_attributes=lambda status: { + extra_state_attributes=lambda status: { "latest_stable_version": status["update"]["new_version"], "installed_version": status["update"]["old_version"], }, diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 71ab4703c79033..9457cbaf370f1d 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -150,9 +150,7 @@ class BlockAttributeDescription: available: Optional[Callable[[aioshelly.Block], bool]] = None # Callable (settings, block), return true if entity should be removed removal_condition: Optional[Callable[[dict, aioshelly.Block], bool]] = None - device_state_attributes: Optional[ - Callable[[aioshelly.Block], Optional[dict]] - ] = None + extra_state_attributes: Optional[Callable[[aioshelly.Block], Optional[dict]]] = None @dataclass @@ -165,7 +163,7 @@ class RestAttributeDescription: value: Callable[[dict, Any], Any] = None device_class: Optional[str] = None default_enabled: bool = True - device_state_attributes: Optional[Callable[[dict], Optional[dict]]] = None + extra_state_attributes: Optional[Callable[[dict], Optional[dict]]] = None class ShellyBlockEntity(entity.Entity): @@ -293,12 +291,12 @@ def available(self): return self.description.available(self.block) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - if self.description.device_state_attributes is None: + if self.description.extra_state_attributes is None: return None - return self.description.device_state_attributes(self.block) + return self.description.extra_state_attributes(self.block) class ShellyRestAttributeEntity(update_coordinator.CoordinatorEntity): @@ -369,12 +367,12 @@ def unique_id(self): return f"{self.wrapper.mac}-{self.attribute}" @property - def device_state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" - if self.description.device_state_attributes is None: + if self.description.extra_state_attributes is None: return None - return self.description.device_state_attributes(self.wrapper.device.status) + return self.description.extra_state_attributes(self.wrapper.device.status) class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEntity): diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 10237629223ccd..aac5ec81ec3d3b 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -159,7 +159,7 @@ unit=PERCENTAGE, icon="mdi:progress-wrench", value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1), - device_state_attributes=lambda block: { + extra_state_attributes=lambda block: { "Operational hours": round(block.totalWorkTime / 3600, 1) }, ), diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py index d2a6a28fbe4004..397e05a35cadb5 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -78,7 +78,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 1de3cfeb8a0913..3bf0f084e51452 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -155,6 +155,6 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the last message.""" return self._message_data diff --git a/homeassistant/components/sighthound/image_processing.py b/homeassistant/components/sighthound/image_processing.py index e15fab1aaa3139..fa636eb757f47c 100644 --- a/homeassistant/components/sighthound/image_processing.py +++ b/homeassistant/components/sighthound/image_processing.py @@ -170,7 +170,7 @@ def unit_of_measurement(self): return ATTR_PEOPLE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attributes.""" if not self._last_detection: return {} diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a6d95e1d9af8b3..485284b32937bd 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -634,7 +634,7 @@ def device_info(self): return self._device_info @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attrs diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index 7f484b712c1790..dc4872cb2aab3d 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -137,7 +137,7 @@ def unit_of_measurement(self): return self._unit @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return other details about the sensor state.""" return { "amplitude": self._amp, diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index 9b759327dcacb7..bff9e311844c98 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -86,7 +86,7 @@ def unit_of_measurement(self): return PERCENTAGE @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} @@ -115,7 +115,7 @@ def unit_of_measurement(self): return TEMP_CELSIUS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py index c1c9d76314c901..2acb729d767995 100644 --- a/homeassistant/components/skybell/__init__.py +++ b/homeassistant/components/skybell/__init__.py @@ -82,7 +82,7 @@ def update(self): self._device.refresh() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index 8949a58fa018f8..7e075fba38a371 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -76,9 +76,9 @@ def device_class(self): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - attrs = super().device_state_attributes + attrs = super().extra_state_attributes attrs["event_date"] = self._event.get("createdAt") diff --git a/homeassistant/components/slide/cover.py b/homeassistant/components/slide/cover.py index 470cf9e5a1f0b6..9e925af3391137 100644 --- a/homeassistant/components/slide/cover.py +++ b/homeassistant/components/slide/cover.py @@ -55,7 +55,7 @@ def name(self): return self._name @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_ID: self._id} diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index 94bab40a3b7d27..bc4457e2838d1c 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -196,7 +196,7 @@ def unit_of_measurement(self): return self._sensor.unit @property - def device_state_attributes(self): # Can be remove from 0.99 + def extra_state_attributes(self): # Can be remove from 0.99 """Return the state attributes of the sensor.""" return self._attr diff --git a/homeassistant/components/smart_meter_texas/sensor.py b/homeassistant/components/smart_meter_texas/sensor.py index e65fbdcb53164b..42084fff836eef 100644 --- a/homeassistant/components/smart_meter_texas/sensor.py +++ b/homeassistant/components/smart_meter_texas/sensor.py @@ -65,7 +65,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device specific state attributes.""" attributes = { METER_NUMBER: self.meter.meter, diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 6ce872cdac7ab8..d99cc1d60cf9b0 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -415,7 +415,7 @@ def current_temperature(self): return self._device.status.temperature @property - def device_state_attributes(self): + def extra_state_attributes(self): """ Return device specific state attributes. diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index ddc52ec3f6c9ca..7b837faca1c2b1 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -147,7 +147,7 @@ def device_class(self): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get additional state attributes.""" return self._state_attrs diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index d6b615b47a74f9..55370e99993994 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -57,7 +57,7 @@ def is_locked(self): return self._device.status.lock == ST_STATE_LOCKED @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" state_attrs = {} status = self._device.status.attributes[Attribute.lock] diff --git a/homeassistant/components/smartthings/scene.py b/homeassistant/components/smartthings/scene.py index 11ee6dc83e1853..e3d93c663fa3c6 100644 --- a/homeassistant/components/smartthings/scene.py +++ b/homeassistant/components/smartthings/scene.py @@ -24,7 +24,7 @@ async def async_activate(self, **kwargs: Any) -> None: await self._scene.execute() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Get attributes about the state.""" return { "icon": self._scene.icon, diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 563c16b3ff1230..99b2e80262d052 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -70,7 +70,7 @@ def state(self) -> str: return self._state.status.name.lower() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = self._state return { @@ -96,7 +96,7 @@ def state(self) -> str: return self._state.status.name.lower() @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state = self._state return { diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index c13982ee15db41..ca5e7f7ac23c56 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -235,7 +235,7 @@ def forecast(self) -> List: return data @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return SMHI specific attributes.""" if self.cloudiness: return {ATTR_SMHI_CLOUDINESS: self.cloudiness} diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index e775b4a0e051b2..660b1a70c019a0 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -75,7 +75,7 @@ async def async_update(self): _LOGGER.error("Failed to read signal quality: %s", exc) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the sensor attributes.""" return self._state diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py index ab4b2415034eba..e1c5b7d875b61a 100644 --- a/homeassistant/components/snapcast/media_player.py +++ b/homeassistant/components/snapcast/media_player.py @@ -164,7 +164,7 @@ def source_list(self): return list(self._group.streams_by_name().keys()) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" name = f"{self._group.friendly_name} {GROUP_SUFFIX}" return {"friendly_name": name} @@ -261,7 +261,7 @@ def state(self): return STATE_OFF @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" state_attrs = {} if self.latency is not None: diff --git a/homeassistant/components/sochain/sensor.py b/homeassistant/components/sochain/sensor.py index 8f70447133982a..5acc8e8432af22 100644 --- a/homeassistant/components/sochain/sensor.py +++ b/homeassistant/components/sochain/sensor.py @@ -69,7 +69,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/socialblade/sensor.py b/homeassistant/components/socialblade/sensor.py index 3d53e76a27a125..3d3331f8af2174 100644 --- a/homeassistant/components/socialblade/sensor.py +++ b/homeassistant/components/socialblade/sensor.py @@ -64,7 +64,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._attributes: return self._attributes diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 8609e578e5e371..24932618195378 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -162,7 +162,7 @@ class SolarEdgeDetailsSensor(SolarEdgeSensor): """Representation of an SolarEdge Monitoring API details sensor.""" @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes @@ -182,7 +182,7 @@ def __init__(self, platform_name, sensor_key, data_service): self._json_key = SENSOR_TYPES[self.sensor_key][0] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes.get(self._json_key) @@ -202,7 +202,7 @@ def __init__(self, platform_name, sensor_key, data_service): self._json_key = SENSOR_TYPES[self.sensor_key][0] @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes.get(self._json_key) @@ -232,7 +232,7 @@ def device_class(self): return DEVICE_CLASS_POWER @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self.data_service.attributes.get(self._json_key) diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 59b0a5e88560d8..9d5eca149cf4c7 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -257,7 +257,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._attr: try: diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 8a625846744533..ca489d95cfdc54 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -131,7 +131,7 @@ async def async_update(self) -> None: self._commands = await self.sonarr.commands() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -172,7 +172,7 @@ async def async_update(self) -> None: self._total_free = sum([disk.free for disk in self._disks]) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -217,7 +217,7 @@ async def async_update(self) -> None: self._queue = await self.sonarr.queue() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -258,7 +258,7 @@ async def async_update(self) -> None: self._items = await self.sonarr.series() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -301,7 +301,7 @@ async def async_update(self) -> None: ) @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} @@ -342,7 +342,7 @@ async def async_update(self) -> None: self._total = self._results.total @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" attrs = {} diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 1a9e9ef58df5cd..0b01ff94462178 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1366,7 +1366,7 @@ def remove_from_queue(self, queue_position=0): self.soco.remove_from_queue(queue_position) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return entity specific state attributes.""" attributes = {ATTR_SONOS_GROUP: [e.entity_id for e in self._sonos_group]} diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 83c8192ccb279a..1b07f01e92a7c2 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -440,7 +440,7 @@ def add_zone_slave(self, slaves): self._device.add_zone_slave([slave.device for slave in slaves]) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return entity specific state attributes.""" attributes = {} diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 5607d2570c9797..6f0cb4124fe07c 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -67,7 +67,7 @@ def icon(self): return ICON @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self.coordinator.data: return None diff --git a/homeassistant/components/spotcrime/sensor.py b/homeassistant/components/spotcrime/sensor.py index 30aa80b5e7da5b..e44bd81ed511b6 100644 --- a/homeassistant/components/spotcrime/sensor.py +++ b/homeassistant/components/spotcrime/sensor.py @@ -103,7 +103,7 @@ def state(self): return self._state @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index 670f5e66146bd3..ccec918832ebcd 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -120,7 +120,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index b87695dd159b50..c57f95266ff5b9 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -265,7 +265,7 @@ def __init__(self, player): self._remove_dispatcher = None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device-specific attributes.""" squeezebox_attr = { attr: getattr(self, attr) diff --git a/homeassistant/components/srp_energy/sensor.py b/homeassistant/components/srp_energy/sensor.py index 36a8798b05bfc4..cc39bf0b8980b6 100644 --- a/homeassistant/components/srp_energy/sensor.py +++ b/homeassistant/components/srp_energy/sensor.py @@ -122,7 +122,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if not self.coordinator.data: return None diff --git a/homeassistant/components/starline/device_tracker.py b/homeassistant/components/starline/device_tracker.py index 6f202bbae52c47..59b9f5b4f95d39 100644 --- a/homeassistant/components/starline/device_tracker.py +++ b/homeassistant/components/starline/device_tracker.py @@ -26,7 +26,7 @@ def __init__(self, account: StarlineAccount, device: StarlineDevice): super().__init__(account, device, "location", "Location") @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific attributes.""" return self._account.gps_attrs(self._device) diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index 0b158451fb3dc0..f19fa4896bae4e 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -31,7 +31,7 @@ def available(self): return super().available and self._device.online @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the lock. Possible dictionary keys: diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 8aba1b54269aed..a8782a87892ce8 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -109,7 +109,7 @@ def device_class(self): return self._device_class @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if self._key == "balance": return self._account.balance_attrs(self._device) diff --git a/homeassistant/components/starline/switch.py b/homeassistant/components/starline/switch.py index c50a7bb4973784..b3214390a448d2 100644 --- a/homeassistant/components/starline/switch.py +++ b/homeassistant/components/starline/switch.py @@ -53,7 +53,7 @@ def available(self): return super().available and self._device.online @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the switch.""" if self._key == "ign": return self._account.engine_attrs(self._device) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 11cddc88c878da..3bf70060da9587 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -184,7 +184,7 @@ def should_poll(self): return False @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sensor.""" if not self.is_binary: return { diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index dbe83177537dfd..e62922c67f4b8d 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -194,7 +194,7 @@ def _get_last_online(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attr = {} if self._game is not None: diff --git a/homeassistant/components/stiebel_eltron/climate.py b/homeassistant/components/stiebel_eltron/climate.py index d8c32575b1703a..5ae7a9230f750f 100644 --- a/homeassistant/components/stiebel_eltron/climate.py +++ b/homeassistant/components/stiebel_eltron/climate.py @@ -96,7 +96,7 @@ def update(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {"filter_alarm": self._filter_alarm} diff --git a/homeassistant/components/stookalert/binary_sensor.py b/homeassistant/components/stookalert/binary_sensor.py index a1c36e9a10ebb7..033af78560c341 100644 --- a/homeassistant/components/stookalert/binary_sensor.py +++ b/homeassistant/components/stookalert/binary_sensor.py @@ -57,7 +57,7 @@ def __init__(self, name, api_handler): self._api_handler = api_handler @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the attribute(s) of the sensor.""" state_attr = {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 3bca348429807f..53f6b3e9c14329 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -73,7 +73,7 @@ def unit_of_measurement(self): return VOLUME_LITERS @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._attributes diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py index 8e1d6f89eea196..0817f10ed5c33f 100644 --- a/homeassistant/components/supervisord/sensor.py +++ b/homeassistant/components/supervisord/sensor.py @@ -61,7 +61,7 @@ def available(self): return self._available @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_DESCRIPTION: self._info.get("description"), diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 2a624b580acffd..64e2766978655d 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -151,7 +151,7 @@ def is_on(self) -> bool: return self.available @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: @@ -179,7 +179,7 @@ def is_on(self) -> bool: return False @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: @@ -232,7 +232,7 @@ def is_on(self) -> bool: return self.available @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index e2d3d0708675ea..54e7f4d5773286 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -125,7 +125,7 @@ def state(self) -> Optional[int]: return SureLockStateID(self._state["locking"]["mode"]).name.capitalize() @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attributes = None if self._state: @@ -166,7 +166,7 @@ def device_class(self) -> str: return DEVICE_CLASS_BATTERY @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> Optional[Dict[str, Any]]: """Return state attributes.""" attributes = None if self._state: diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index 61423312b2a073..27071afd112497 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -119,7 +119,7 @@ def state(self): return None @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" attrs = {} diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 2c7fb483eff72a..1a7b97ce4394cc 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -94,7 +94,7 @@ def state(self): ) @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" if self._opendata is None: return diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index 89ba7b5be5edaf..faf230507a2a04 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -86,6 +86,6 @@ def name(self) -> str: return self._name @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes.""" return {"last_run_success": self._last_run_success} diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index 6b4b5026c2f00e..99d50c0c559bfb 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -139,7 +139,7 @@ def current_power_w(self) -> int: return self._device_data.power_consumption @property - def device_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> Dict: """Return the optional state attributes.""" attribs = {} diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index 639ec3ac6cb9d2..bfada33bf38a87 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -121,7 +121,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the device.""" return self._attributes diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 50921944a6d987..88654f02b21d11 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -610,7 +610,7 @@ def device_class(self) -> str: return self._class @property - def device_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> Dict[str, any]: """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 6e89f3d7a84f48..042e46c636ebf0 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -71,7 +71,7 @@ def available(self) -> bool: return bool(self._api.security) @property - def device_state_attributes(self) -> Dict[str, str]: + def extra_state_attributes(self) -> Dict[str, str]: """Return security checks details.""" return self._api.security.status_by_check diff --git a/tests/components/srp_energy/test_sensor.py b/tests/components/srp_energy/test_sensor.py index a93e56b7b931af..069dc9eb64f034 100644 --- a/tests/components/srp_energy/test_sensor.py +++ b/tests/components/srp_energy/test_sensor.py @@ -91,7 +91,7 @@ async def test_srp_entity(hass): assert srp_entity.icon == ICON assert srp_entity.usage == "2.00" assert srp_entity.should_poll is False - assert srp_entity.device_state_attributes[ATTR_ATTRIBUTION] == ATTRIBUTION + assert srp_entity.extra_state_attributes[ATTR_ATTRIBUTION] == ATTRIBUTION assert srp_entity.available is not None await srp_entity.async_added_to_hass() @@ -104,7 +104,7 @@ async def test_srp_entity_no_data(hass): """Test the SrpEntity.""" fake_coordinator = MagicMock(data=False) srp_entity = SrpEntity(fake_coordinator) - assert srp_entity.device_state_attributes is None + assert srp_entity.extra_state_attributes is None async def test_srp_entity_no_coord_data(hass): From 4bafd03dffbca3551608e9a88aa582055829bc1b Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 11 Mar 2021 22:18:16 +0100 Subject: [PATCH 1196/1818] Consistent spelling of "PIN" (#47771) --- homeassistant/components/blink/services.yaml | 4 ++-- homeassistant/components/blink/strings.json | 2 +- homeassistant/components/ecobee/strings.json | 2 +- homeassistant/components/hangouts/strings.json | 4 ++-- homeassistant/components/nest/strings.json | 2 +- homeassistant/components/zwave/lock.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/blink/services.yaml b/homeassistant/components/blink/services.yaml index dc6491e2139393..6ea4e2aa9ac029 100644 --- a/homeassistant/components/blink/services.yaml +++ b/homeassistant/components/blink/services.yaml @@ -21,8 +21,8 @@ save_video: example: "/tmp/video.mp4" send_pin: - description: Send a new pin to blink for 2FA. + description: Send a new PIN to blink for 2FA. fields: pin: - description: Pin received from blink. Leave empty if you only received a verification email. + description: PIN received from blink. Leave empty if you only received a verification email. example: "abc123" diff --git a/homeassistant/components/blink/strings.json b/homeassistant/components/blink/strings.json index db9bdf96273a10..6e438b585906c8 100644 --- a/homeassistant/components/blink/strings.json +++ b/homeassistant/components/blink/strings.json @@ -11,7 +11,7 @@ "2fa": { "title": "Two-factor authentication", "data": { "2fa": "Two-factor code" }, - "description": "Enter the pin sent to your email" + "description": "Enter the PIN sent to your email" } }, "error": { diff --git a/homeassistant/components/ecobee/strings.json b/homeassistant/components/ecobee/strings.json index 78f0708134c8f6..19f379de7d953a 100644 --- a/homeassistant/components/ecobee/strings.json +++ b/homeassistant/components/ecobee/strings.json @@ -10,7 +10,7 @@ }, "authorize": { "title": "Authorize app on ecobee.com", - "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with pin code:\n\n{pin}\n\nThen, press Submit." + "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with PIN code:\n\n{pin}\n\nThen, press Submit." } }, "error": { diff --git a/homeassistant/components/hangouts/strings.json b/homeassistant/components/hangouts/strings.json index bed46e823d93f7..0128363a1ab46f 100644 --- a/homeassistant/components/hangouts/strings.json +++ b/homeassistant/components/hangouts/strings.json @@ -7,7 +7,7 @@ "error": { "invalid_login": "Invalid Login, please try again.", "invalid_2fa": "Invalid 2 Factor Authentication, please try again.", - "invalid_2fa_method": "Invalid 2FA Method (Verify on Phone)." + "invalid_2fa_method": "Invalid 2FA Method (verify on Phone)." }, "step": { "user": { @@ -20,7 +20,7 @@ }, "2fa": { "data": { - "2fa": "2FA Pin" + "2fa": "2FA PIN" }, "title": "2-Factor-Authentication" } diff --git a/homeassistant/components/nest/strings.json b/homeassistant/components/nest/strings.json index 6ce529621aab9b..26ec49c0d75f55 100644 --- a/homeassistant/components/nest/strings.json +++ b/homeassistant/components/nest/strings.json @@ -17,7 +17,7 @@ }, "link": { "title": "Link Nest Account", - "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.", + "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided PIN code below.", "data": { "code": "[%key:common::config_flow::data::pin%]" } diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index 605a64ada5d1b0..e6228a29334f3a 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -95,7 +95,7 @@ "27": "Auto re-lock", "33": "User deleted: ", "112": "Master code changed or User added: ", - "113": "Duplicate Pin-code: ", + "113": "Duplicate PIN code: ", "130": "RF module, power restored", "144": "Unlocked by NFC Tag or Card by user ", "161": "Tamper Alarm: ", From 3ebc262b7fc5d8cb027c8aa4f4b56bf67891a2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Cla=C3=9Fen?= Date: Thu, 11 Mar 2021 22:54:27 +0100 Subject: [PATCH 1197/1818] Upgrade numato-gpio to 0.10.0 (#47539) This adds support for devices sending '\n\r' end-of-line sequences. --- homeassistant/components/numato/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/numato/manifest.json b/homeassistant/components/numato/manifest.json index 4b7dcd9e372eef..6138f401ec2647 100644 --- a/homeassistant/components/numato/manifest.json +++ b/homeassistant/components/numato/manifest.json @@ -2,6 +2,6 @@ "domain": "numato", "name": "Numato USB GPIO Expander", "documentation": "https://www.home-assistant.io/integrations/numato", - "requirements": ["numato-gpio==0.8.0"], + "requirements": ["numato-gpio==0.10.0"], "codeowners": ["@clssn"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5170f3b25749e7..6cdbb1dd65d5b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1008,7 +1008,7 @@ nsw-fuel-api-client==1.0.10 nuheat==0.3.0 # homeassistant.components.numato -numato-gpio==0.8.0 +numato-gpio==0.10.0 # homeassistant.components.iqvia # homeassistant.components.opencv diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2b77de8022bd6a..e2363c528fd49f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -517,7 +517,7 @@ nsw-fuel-api-client==1.0.10 nuheat==0.3.0 # homeassistant.components.numato -numato-gpio==0.8.0 +numato-gpio==0.10.0 # homeassistant.components.iqvia # homeassistant.components.opencv From 7ca5e969cc874258aa8cba2570818d269f3e2cd6 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 11 Mar 2021 16:28:38 -0700 Subject: [PATCH 1198/1818] Fix zwave_js target_temp_low (#47762) --- homeassistant/components/zwave_js/climate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 1a15c45049ba9e..ceb469829496c3 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -287,7 +287,12 @@ def target_temperature_high(self) -> Optional[float]: @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return self.target_temperature + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None + if len(self._current_mode_setpoint_enums) > 1: + return self.target_temperature + return None @property def preset_mode(self) -> Optional[str]: From daab9f9810994d20a4cfcb2f83b07e6160bd74c7 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 11 Mar 2021 17:35:11 -0600 Subject: [PATCH 1199/1818] Bump plexapi to 4.4.1 (#47766) --- 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 49388bdfdb656d..1319e4bbf4960b 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.4.0", + "plexapi==4.4.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index 6cdbb1dd65d5b9..e0260abb04dc2c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1132,7 +1132,7 @@ pillow==8.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2363c528fd49f..fbd8bbd6bc7e77 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -578,7 +578,7 @@ pilight==0.1.1 pillow==8.1.2 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 From 66605b5994451b2e0c5ef0bbab3649e402d6d326 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 12 Mar 2021 00:37:34 +0100 Subject: [PATCH 1200/1818] Upgrade adguardhome to v0.5.0 (#47774) --- homeassistant/components/adguard/__init__.py | 14 +++++++++----- homeassistant/components/adguard/manifest.json | 2 +- homeassistant/components/adguard/sensor.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 6ad7d9579a8496..4015bd31bf2873 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -81,24 +81,28 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool async def add_url(call) -> None: """Service call to add a new filter subscription to AdGuard Home.""" await adguard.filtering.add_url( - call.data.get(CONF_NAME), call.data.get(CONF_URL) + allowlist=False, name=call.data.get(CONF_NAME), url=call.data.get(CONF_URL) ) async def remove_url(call) -> None: """Service call to remove a filter subscription from AdGuard Home.""" - await adguard.filtering.remove_url(call.data.get(CONF_URL)) + await adguard.filtering.remove_url(allowlist=False, url=call.data.get(CONF_URL)) async def enable_url(call) -> None: """Service call to enable a filter subscription in AdGuard Home.""" - await adguard.filtering.enable_url(call.data.get(CONF_URL)) + await adguard.filtering.enable_url(allowlist=False, url=call.data.get(CONF_URL)) async def disable_url(call) -> None: """Service call to disable a filter subscription in AdGuard Home.""" - await adguard.filtering.disable_url(call.data.get(CONF_URL)) + await adguard.filtering.disable_url( + allowlist=False, url=call.data.get(CONF_URL) + ) async def refresh(call) -> None: """Service call to refresh the filter subscriptions in AdGuard Home.""" - await adguard.filtering.refresh(call.data.get(CONF_FORCE)) + await adguard.filtering.refresh( + allowlist=False, force=call.data.get(CONF_FORCE) + ) hass.services.async_register( DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA diff --git a/homeassistant/components/adguard/manifest.json b/homeassistant/components/adguard/manifest.json index 0bcd25569a5a9c..dd23e56136403a 100644 --- a/homeassistant/components/adguard/manifest.json +++ b/homeassistant/components/adguard/manifest.json @@ -3,6 +3,6 @@ "name": "AdGuard Home", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/adguard", - "requirements": ["adguardhome==0.4.2"], + "requirements": ["adguardhome==0.5.0"], "codeowners": ["@frenck"] } diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index edd9fe22ba9ee4..3a93d9fbb353e3 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -232,4 +232,4 @@ def __init__(self, adguard: AdGuardHome) -> None: async def _adguard_update(self) -> None: """Update AdGuard Home entity.""" - self._state = await self.adguard.filtering.rules_count() + self._state = await self.adguard.filtering.rules_count(allowlist=False) diff --git a/requirements_all.txt b/requirements_all.txt index e0260abb04dc2c..140273de629161 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -111,7 +111,7 @@ adb-shell[async]==0.2.1 adext==0.4.1 # homeassistant.components.adguard -adguardhome==0.4.2 +adguardhome==0.5.0 # homeassistant.components.advantage_air advantage_air==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fbd8bbd6bc7e77..a2aaf83b113dbf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -51,7 +51,7 @@ adb-shell[async]==0.2.1 adext==0.4.1 # homeassistant.components.adguard -adguardhome==0.4.2 +adguardhome==0.5.0 # homeassistant.components.advantage_air advantage_air==0.2.1 From 92852b9c10e9454f713f1ffc68fe6c87bf686b27 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 12 Mar 2021 04:03:30 +0100 Subject: [PATCH 1201/1818] Add apply_filter attribute to recorder.purge service (#45826) --- homeassistant/components/recorder/__init__.py | 18 +- homeassistant/components/recorder/purge.py | 75 ++- .../components/recorder/services.yaml | 8 + tests/components/recorder/test_purge.py | 429 +++++++++++++++++- 4 files changed, 522 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 9b84518b6d3183..f8f95fd7cccbc0 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -52,11 +52,13 @@ ATTR_KEEP_DAYS = "keep_days" ATTR_REPACK = "repack" +ATTR_APPLY_FILTER = "apply_filter" SERVICE_PURGE_SCHEMA = vol.Schema( { vol.Optional(ATTR_KEEP_DAYS): cv.positive_int, vol.Optional(ATTR_REPACK, default=False): cv.boolean, + vol.Optional(ATTR_APPLY_FILTER, default=False): cv.boolean, } ) SERVICE_ENABLE_SCHEMA = vol.Schema({}) @@ -227,6 +229,7 @@ class PurgeTask(NamedTuple): keep_days: int repack: bool + apply_filter: bool class WaitTask: @@ -309,8 +312,9 @@ def do_adhoc_purge(self, **kwargs): """Trigger an adhoc purge retaining keep_days worth of data.""" keep_days = kwargs.get(ATTR_KEEP_DAYS, self.keep_days) repack = kwargs.get(ATTR_REPACK) + apply_filter = kwargs.get(ATTR_APPLY_FILTER) - self.queue.put(PurgeTask(keep_days, repack)) + self.queue.put(PurgeTask(keep_days, repack, apply_filter)) def run(self): """Start processing events to save.""" @@ -364,7 +368,9 @@ def notify_hass_started(event): @callback def async_purge(now): """Trigger the purge.""" - self.queue.put(PurgeTask(self.keep_days, repack=False)) + self.queue.put( + PurgeTask(self.keep_days, repack=False, apply_filter=False) + ) # Purge every night at 4:12am self.hass.helpers.event.track_time_change( @@ -425,8 +431,12 @@ def _process_one_event(self, event): """Process one event.""" if isinstance(event, PurgeTask): # Schedule a new purge task if this one didn't finish - if not purge.purge_old_data(self, event.keep_days, event.repack): - self.queue.put(PurgeTask(event.keep_days, event.repack)) + if not purge.purge_old_data( + self, event.keep_days, event.repack, event.apply_filter + ): + self.queue.put( + PurgeTask(event.keep_days, event.repack, event.apply_filter) + ) return if isinstance(event, WaitTask): self._queue_watch.set() diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 3717ed49f300f3..ef626a744c4fab 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -8,6 +8,7 @@ from sqlalchemy.exc import OperationalError, SQLAlchemyError from sqlalchemy.orm.session import Session +from sqlalchemy.sql.expression import distinct import homeassistant.util.dt as dt_util @@ -22,7 +23,9 @@ _LOGGER = logging.getLogger(__name__) -def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: +def purge_old_data( + instance: Recorder, purge_days: int, repack: bool, apply_filter: bool = False +) -> bool: """Purge events and states older than purge_days ago. Cleans up an timeframe of an hour, based on the oldest record. @@ -45,6 +48,9 @@ def purge_old_data(instance: Recorder, purge_days: int, repack: bool) -> bool: # return false, as we are not done yet. _LOGGER.debug("Purging hasn't fully completed yet") return False + if apply_filter and _purge_filtered_data(instance, session) is False: + _LOGGER.debug("Cleanup filtered data hasn't fully completed yet") + return False _purge_old_recorder_runs(instance, session, purge_before) if repack: repack_database(instance) @@ -140,3 +146,70 @@ def _purge_old_recorder_runs( .delete(synchronize_session=False) ) _LOGGER.debug("Deleted %s recorder_runs", deleted_rows) + + +def _purge_filtered_data(instance: Recorder, session: Session) -> bool: + """Remove filtered states and events that shouldn't be in the database.""" + _LOGGER.debug("Cleanup filtered data") + + # Check if excluded entity_ids are in database + excluded_entity_ids: list[str] = [ + entity_id + for (entity_id,) in session.query(distinct(States.entity_id)).all() + if not instance.entity_filter(entity_id) + ] + if len(excluded_entity_ids) > 0: + _purge_filtered_states(session, excluded_entity_ids) + return False + + # Check if excluded event_types are in database + excluded_event_types: list[str] = [ + event_type + for (event_type,) in session.query(distinct(Events.event_type)).all() + if event_type in instance.exclude_t + ] + if len(excluded_event_types) > 0: + _purge_filtered_events(session, excluded_event_types) + return False + + return True + + +def _purge_filtered_states(session: Session, excluded_entity_ids: list[str]) -> None: + """Remove filtered states and linked events.""" + state_ids: list[int] + event_ids: list[int | None] + state_ids, event_ids = zip( + *( + session.query(States.state_id, States.event_id) + .filter(States.entity_id.in_(excluded_entity_ids)) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) + ) + event_ids = [id_ for id_ in event_ids if id_ is not None] + _LOGGER.debug( + "Selected %s state_ids to remove that should be filtered", len(state_ids) + ) + _purge_state_ids(session, state_ids) + _purge_event_ids(session, event_ids) # type: ignore # type of event_ids already narrowed to 'list[int]' + + +def _purge_filtered_events(session: Session, excluded_event_types: list[str]) -> None: + """Remove filtered events and linked states.""" + events: list[Events] = ( + session.query(Events.event_id) + .filter(Events.event_type.in_(excluded_event_types)) + .limit(MAX_ROWS_TO_PURGE) + .all() + ) + event_ids: list[int] = [event.event_id for event in events] + _LOGGER.debug( + "Selected %s event_ids to remove that should be filtered", len(event_ids) + ) + states: list[States] = ( + session.query(States.state_id).filter(States.event_id.in_(event_ids)).all() + ) + state_ids: list[int] = [state.state_id for state in states] + _purge_state_ids(session, state_ids) + _purge_event_ids(session, event_ids) diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index 2be5b0e095e354..2c4f35b5e7a29f 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -25,6 +25,14 @@ purge: selector: boolean: + apply_filter: + name: Apply filter + description: Apply entity_id and event_type filter in addition to time based purge. + example: true + default: false + selector: + boolean: + disable: description: Stop the recording of events and state changes diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 3535a58d33d3d1..db3906595dbe57 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,15 +1,18 @@ """Test data purging.""" -from datetime import timedelta +from datetime import datetime, timedelta import json +from sqlalchemy.orm.session import Session + from homeassistant.components import recorder from homeassistant.components.recorder.models import Events, RecorderRuns, States from homeassistant.components.recorder.purge import purge_old_data from homeassistant.components.recorder.util import session_scope -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.const import EVENT_STATE_CHANGED +from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import dt as dt_util -from .common import async_wait_recording_done +from .common import async_recorder_block_till_done, async_wait_recording_done from .conftest import SetupRecorderInstanceT @@ -154,6 +157,394 @@ async def test_purge_method( assert "Vacuuming SQL DB to free space" in caplog.text +async def test_purge_edge_case( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test states and events are purged even if they occurred shortly before purge_before.""" + + async def _add_db_entries(hass: HomeAssistantType, timestamp: datetime) -> None: + with recorder.session_scope(hass=hass) as session: + session.add( + Events( + event_id=1001, + event_type="EVENT_TEST_PURGE", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + session.add( + States( + entity_id="test.recorder2", + domain="sensor", + state="purgeme", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + event_id=1001, + ) + ) + + instance = await async_setup_recorder_instance(hass, None) + await async_wait_recording_done(hass, instance) + + service_data = {"keep_days": 2} + timestamp = dt_util.utcnow() - timedelta(days=2, minutes=1) + + await _add_db_entries(hass, timestamp) + with session_scope(hass=hass) as session: + states = session.query(States) + assert states.count() == 1 + + events = session.query(Events).filter(Events.event_type == "EVENT_TEST_PURGE") + assert events.count() == 1 + + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert states.count() == 0 + assert events.count() == 0 + + +async def test_purge_filtered_states( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test filtered states are purged.""" + config: ConfigType = {"exclude": {"entities": ["sensor.excluded"]}} + instance = await async_setup_recorder_instance(hass, config) + assert instance.entity_filter("sensor.excluded") is False + + def _add_db_entries(hass: HomeAssistantType) -> None: + with recorder.session_scope(hass=hass) as session: + # Add states and state_changed events that should be purged + for days in range(1, 4): + timestamp = dt_util.utcnow() - timedelta(days=days) + for event_id in range(1000, 1020): + _add_state_and_state_changed_event( + session, + "sensor.excluded", + "purgeme", + timestamp, + event_id * days, + ) + # Add state **without** state_changed event that should be purged + timestamp = dt_util.utcnow() - timedelta(days=1) + session.add( + States( + entity_id="sensor.excluded", + domain="sensor", + state="purgeme", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + ) + ) + # Add states and state_changed events that should be keeped + timestamp = dt_util.utcnow() - timedelta(days=2) + for event_id in range(200, 210): + _add_state_and_state_changed_event( + session, + "sensor.keep", + "keep", + timestamp, + event_id, + ) + # Add states with linked old_state_ids that need to be handled + timestamp = dt_util.utcnow() - timedelta(days=0) + state_1 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=1, + ) + timestamp = dt_util.utcnow() - timedelta(days=4) + state_2 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=2, + ) + state_3 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=62, # keep + ) + session.add_all((state_1, state_2, state_3)) + # Add event that should be keeped + session.add( + Events( + event_id=100, + event_type="EVENT_KEEP", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + + service_data = {"keep_days": 10} + _add_db_entries(hass) + + with session_scope(hass=hass) as session: + states = session.query(States) + assert states.count() == 74 + + events_state_changed = session.query(Events).filter( + Events.event_type == EVENT_STATE_CHANGED + ) + events_keep = session.query(Events).filter(Events.event_type == "EVENT_KEEP") + assert events_state_changed.count() == 70 + assert events_keep.count() == 1 + + # Normal purge doesn't remove excluded entities + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert states.count() == 74 + assert events_state_changed.count() == 70 + assert events_keep.count() == 1 + + # Test with 'apply_filter' = True + service_data["apply_filter"] = True + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert states.count() == 13 + assert events_state_changed.count() == 10 + assert events_keep.count() == 1 + + states_sensor_excluded = session.query(States).filter( + States.entity_id == "sensor.excluded" + ) + assert states_sensor_excluded.count() == 0 + + session.query(States).get(71).old_state_id is None + session.query(States).get(72).old_state_id is None + session.query(States).get(73).old_state_id == 62 # should have been keeped + + +async def test_purge_filtered_events( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test filtered events are purged.""" + config: ConfigType = {"exclude": {"event_types": ["EVENT_PURGE"]}} + instance = await async_setup_recorder_instance(hass, config) + + def _add_db_entries(hass: HomeAssistantType) -> None: + with recorder.session_scope(hass=hass) as session: + # Add events that should be purged + for days in range(1, 4): + timestamp = dt_util.utcnow() - timedelta(days=days) + for event_id in range(1000, 1020): + session.add( + Events( + event_id=event_id * days, + event_type="EVENT_PURGE", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + + # Add states and state_changed events that should be keeped + timestamp = dt_util.utcnow() - timedelta(days=1) + for event_id in range(200, 210): + _add_state_and_state_changed_event( + session, + "sensor.keep", + "keep", + timestamp, + event_id, + ) + + service_data = {"keep_days": 10} + _add_db_entries(hass) + + with session_scope(hass=hass) as session: + events_purge = session.query(Events).filter(Events.event_type == "EVENT_PURGE") + events_keep = session.query(Events).filter( + Events.event_type == EVENT_STATE_CHANGED + ) + states = session.query(States) + + assert events_purge.count() == 60 + assert events_keep.count() == 10 + assert states.count() == 10 + + # Normal purge doesn't remove excluded events + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert events_purge.count() == 60 + assert events_keep.count() == 10 + assert states.count() == 10 + + # Test with 'apply_filter' = True + service_data["apply_filter"] = True + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert events_purge.count() == 0 + assert events_keep.count() == 10 + assert states.count() == 10 + + +async def test_purge_filtered_events_state_changed( + hass: HomeAssistantType, + async_setup_recorder_instance: SetupRecorderInstanceT, +): + """Test filtered state_changed events are purged. This should also remove all states.""" + config: ConfigType = {"exclude": {"event_types": [EVENT_STATE_CHANGED]}} + instance = await async_setup_recorder_instance(hass, config) + # Assert entity_id is NOT excluded + assert instance.entity_filter("sensor.excluded") is True + + def _add_db_entries(hass: HomeAssistantType) -> None: + with recorder.session_scope(hass=hass) as session: + # Add states and state_changed events that should be purged + for days in range(1, 4): + timestamp = dt_util.utcnow() - timedelta(days=days) + for event_id in range(1000, 1020): + _add_state_and_state_changed_event( + session, + "sensor.excluded", + "purgeme", + timestamp, + event_id * days, + ) + # Add events that should be keeped + timestamp = dt_util.utcnow() - timedelta(days=1) + for event_id in range(200, 210): + session.add( + Events( + event_id=event_id, + event_type="EVENT_KEEP", + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) + # Add states with linked old_state_ids that need to be handled + timestamp = dt_util.utcnow() - timedelta(days=0) + state_1 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=1, + ) + timestamp = dt_util.utcnow() - timedelta(days=4) + state_2 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=2, + ) + state_3 = States( + entity_id="sensor.linked_old_state_id", + domain="sensor", + state="keep", + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + old_state_id=62, # keep + ) + session.add_all((state_1, state_2, state_3)) + + service_data = {"keep_days": 10, "apply_filter": True} + _add_db_entries(hass) + + with session_scope(hass=hass) as session: + events_keep = session.query(Events).filter(Events.event_type == "EVENT_KEEP") + events_purge = session.query(Events).filter( + Events.event_type == EVENT_STATE_CHANGED + ) + states = session.query(States) + + assert events_keep.count() == 10 + assert events_purge.count() == 60 + assert states.count() == 63 + + await hass.services.async_call( + recorder.DOMAIN, recorder.SERVICE_PURGE, service_data + ) + await hass.async_block_till_done() + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + await async_recorder_block_till_done(hass, instance) + await async_wait_recording_done(hass, instance) + + assert events_keep.count() == 10 + assert events_purge.count() == 0 + assert states.count() == 3 + + session.query(States).get(61).old_state_id is None + session.query(States).get(62).old_state_id is None + session.query(States).get(63).old_state_id == 62 # should have been keeped + + async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): """Add multiple states to the db for testing.""" utcnow = dt_util.utcnow() @@ -260,3 +651,35 @@ async def _add_test_recorder_runs(hass: HomeAssistantType, instance: recorder.Re end=timestamp + timedelta(days=1), ) ) + + +def _add_state_and_state_changed_event( + session: Session, + entity_id: str, + state: str, + timestamp: datetime, + event_id: int, +) -> None: + """Add state and state_changed event to database for testing.""" + session.add( + States( + entity_id=entity_id, + domain="sensor", + state=state, + attributes="{}", + last_changed=timestamp, + last_updated=timestamp, + created=timestamp, + event_id=event_id, + ) + ) + session.add( + Events( + event_id=event_id, + event_type=EVENT_STATE_CHANGED, + event_data="{}", + origin="LOCAL", + created=timestamp, + time_fired=timestamp, + ) + ) From 9ca0cd546432991384900ef5686e961a57543d1f Mon Sep 17 00:00:00 2001 From: Felipe Martins Diel <41558831+felipediel@users.noreply.github.com> Date: Fri, 12 Mar 2021 02:34:56 -0300 Subject: [PATCH 1202/1818] Bump broadlink from 0.16.0 to 0.17.0 (#47779) --- .../components/broadlink/config_flow.py | 6 +- homeassistant/components/broadlink/const.py | 25 ++- homeassistant/components/broadlink/device.py | 4 +- .../components/broadlink/manifest.json | 2 +- homeassistant/components/broadlink/remote.py | 160 ++++++++++-------- homeassistant/components/broadlink/switch.py | 47 ++--- homeassistant/components/broadlink/updater.py | 61 +++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/broadlink/__init__.py | 13 +- 10 files changed, 156 insertions(+), 166 deletions(-) diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index a309e4eb6034e3..3e21765dbedbce 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -39,11 +39,7 @@ def __init__(self): async def async_set_device(self, device, raise_on_progress=True): """Define a device for the config flow.""" - supported_types = { - device_type - for device_types in DOMAINS_AND_TYPES - for device_type in device_types[1] - } + supported_types = set.union(*DOMAINS_AND_TYPES.values()) if device.type not in supported_types: _LOGGER.error( "Unsupported device: %s. If it worked before, please open " diff --git a/homeassistant/components/broadlink/const.py b/homeassistant/components/broadlink/const.py index b10f7e74ba7622..fd060d23b35cb8 100644 --- a/homeassistant/components/broadlink/const.py +++ b/homeassistant/components/broadlink/const.py @@ -5,11 +5,26 @@ DOMAIN = "broadlink" -DOMAINS_AND_TYPES = ( - (REMOTE_DOMAIN, ("RM2", "RM4")), - (SENSOR_DOMAIN, ("A1", "RM2", "RM4")), - (SWITCH_DOMAIN, ("BG1", "MP1", "RM2", "RM4", "SP1", "SP2", "SP4", "SP4B")), -) +DOMAINS_AND_TYPES = { + REMOTE_DOMAIN: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"}, + SENSOR_DOMAIN: {"A1", "RM4MINI", "RM4PRO", "RMPRO"}, + SWITCH_DOMAIN: { + "BG1", + "MP1", + "RM4MINI", + "RM4PRO", + "RMMINI", + "RMMINIB", + "RMPRO", + "SP1", + "SP2", + "SP2S", + "SP3", + "SP3S", + "SP4", + "SP4B", + }, +} DEFAULT_PORT = 80 DEFAULT_TIMEOUT = 5 diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index be9c7626ac176d..c460040c12bed6 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -22,9 +22,9 @@ _LOGGER = logging.getLogger(__name__) -def get_domains(device_type): +def get_domains(dev_type): """Return the domains available for a device type.""" - return {domain for domain, types in DOMAINS_AND_TYPES if device_type in types} + return {d for d, t in DOMAINS_AND_TYPES.items() if dev_type in t} class BroadlinkDevice: diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json index 0562bc306a5116..8d3b16b4582977 100644 --- a/homeassistant/components/broadlink/manifest.json +++ b/homeassistant/components/broadlink/manifest.json @@ -2,7 +2,7 @@ "domain": "broadlink", "name": "Broadlink", "documentation": "https://www.home-assistant.io/integrations/broadlink", - "requirements": ["broadlink==0.16.0"], + "requirements": ["broadlink==0.17.0"], "codeowners": ["@danielhiversen", "@felipediel"], "config_flow": true } diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 116c97aeb312fd..30043f487b125c 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -26,6 +26,8 @@ DOMAIN as RM_DOMAIN, PLATFORM_SCHEMA, SERVICE_DELETE_COMMAND, + SERVICE_LEARN_COMMAND, + SERVICE_SEND_COMMAND, SUPPORT_DELETE_COMMAND, SUPPORT_LEARN_COMMAND, RemoteEntity, @@ -129,6 +131,7 @@ def __init__(self, device, codes, flags): self._codes = {} self._flags = defaultdict(int) self._state = True + self._lock = asyncio.Lock() @property def name(self): @@ -171,39 +174,44 @@ def device_info(self): "sw_version": self._device.fw_version, } - def get_code(self, command, device): - """Return a code and a boolean indicating a toggle command. + def _extract_codes(self, commands, device=None): + """Extract a list of codes. If the command starts with `b64:`, extract the code from it. - Otherwise, extract the code from the dictionary, using the device - and command as keys. + Otherwise, extract the code from storage, using the command and + device as keys. - You need to change the flag whenever a toggle command is sent - successfully. Use `self._flags[device] ^= 1`. + The codes are returned in sublists. For toggle commands, the + sublist contains two codes that must be sent alternately with + each call. """ - if command.startswith("b64:"): - code, is_toggle_cmd = command[4:], False + code_list = [] + for cmd in commands: + if cmd.startswith("b64:"): + codes = [cmd[4:]] - else: - if device is None: - raise KeyError("You need to specify a device") - - try: - code = self._codes[device][command] - except KeyError as err: - raise KeyError("Command not found") from err - - # For toggle commands, alternate between codes in a list. - if isinstance(code, list): - code = code[self._flags[device]] - is_toggle_cmd = True else: - is_toggle_cmd = False + if device is None: + raise ValueError("You need to specify a device") - try: - return data_packet(code), is_toggle_cmd - except ValueError as err: - raise ValueError("Invalid code") from err + try: + codes = self._codes[device][cmd] + except KeyError as err: + raise ValueError(f"Command not found: {repr(cmd)}") from err + + if isinstance(codes, list): + codes = codes[:] + else: + codes = [codes] + + for idx, code in enumerate(codes): + try: + codes[idx] = data_packet(code) + except ValueError as err: + raise ValueError(f"Invalid code: {repr(code)}") from err + + code_list.append(codes) + return code_list @callback def get_codes(self): @@ -261,44 +269,50 @@ async def async_send_command(self, command, **kwargs): device = kwargs.get(ATTR_DEVICE) repeat = kwargs[ATTR_NUM_REPEATS] delay = kwargs[ATTR_DELAY_SECS] + service = f"{RM_DOMAIN}.{SERVICE_SEND_COMMAND}" if not self._state: _LOGGER.warning( - "remote.send_command canceled: %s entity is turned off", self.entity_id + "%s canceled: %s entity is turned off", service, self.entity_id ) return - should_delay = False + try: + code_list = self._extract_codes(commands, device) + except ValueError as err: + _LOGGER.error("Failed to call %s: %s", service, err) + raise - for _, cmd in product(range(repeat), commands): - if should_delay: + rf_flags = {0xB2, 0xD7} + if not hasattr(self._device.api, "sweep_frequency") and any( + c[0] in rf_flags for codes in code_list for c in codes + ): + err_msg = f"{self.entity_id} doesn't support sending RF commands" + _LOGGER.error("Failed to call %s: %s", service, err_msg) + raise ValueError(err_msg) + + at_least_one_sent = False + for _, codes in product(range(repeat), code_list): + if at_least_one_sent: await asyncio.sleep(delay) - try: - code, is_toggle_cmd = self.get_code(cmd, device) - - except (KeyError, ValueError) as err: - _LOGGER.error("Failed to send '%s': %s", cmd, err) - should_delay = False - continue + if len(codes) > 1: + code = codes[self._flags[device]] + else: + code = codes[0] try: await self._device.async_request(self._device.api.send_data, code) - - except (AuthorizationError, NetworkTimeoutError, OSError) as err: - _LOGGER.error("Failed to send '%s': %s", cmd, err) + except (BroadlinkException, OSError) as err: + _LOGGER.error("Error during %s: %s", service, err) break - except BroadlinkException as err: - _LOGGER.error("Failed to send '%s': %s", cmd, err) - should_delay = False - continue - - should_delay = True - if is_toggle_cmd: + if len(codes) > 1: self._flags[device] ^= 1 + at_least_one_sent = True - self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) + if at_least_one_sent: + self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) async def async_learn_command(self, **kwargs): """Learn a list of commands from a remote.""" @@ -307,39 +321,47 @@ async def async_learn_command(self, **kwargs): command_type = kwargs[ATTR_COMMAND_TYPE] device = kwargs[ATTR_DEVICE] toggle = kwargs[ATTR_ALTERNATIVE] + service = f"{RM_DOMAIN}.{SERVICE_LEARN_COMMAND}" if not self._state: _LOGGER.warning( - "remote.learn_command canceled: %s entity is turned off", self.entity_id + "%s canceled: %s entity is turned off", service, self.entity_id ) return - if command_type == COMMAND_TYPE_IR: - learn_command = self._async_learn_ir_command - else: - learn_command = self._async_learn_rf_command + async with self._lock: + if command_type == COMMAND_TYPE_IR: + learn_command = self._async_learn_ir_command - should_store = False + elif hasattr(self._device.api, "sweep_frequency"): + learn_command = self._async_learn_rf_command - for command in commands: - try: - code = await learn_command(command) - if toggle: - code = [code, await learn_command(command)] + else: + err_msg = f"{self.entity_id} doesn't support learning RF commands" + _LOGGER.error("Failed to call %s: %s", service, err_msg) + raise ValueError(err_msg) - except (AuthorizationError, NetworkTimeoutError, OSError) as err: - _LOGGER.error("Failed to learn '%s': %s", command, err) - break + should_store = False + + for command in commands: + try: + code = await learn_command(command) + if toggle: + code = [code, await learn_command(command)] - except BroadlinkException as err: - _LOGGER.error("Failed to learn '%s': %s", command, err) - continue + except (AuthorizationError, NetworkTimeoutError, OSError) as err: + _LOGGER.error("Failed to learn '%s': %s", command, err) + break + + except BroadlinkException as err: + _LOGGER.error("Failed to learn '%s': %s", command, err) + continue - self._codes.setdefault(device, {}).update({command: code}) - should_store = True + self._codes.setdefault(device, {}).update({command: code}) + should_store = True - if should_store: - await self._code_storage.async_save(self._codes) + if should_store: + await self._code_storage.async_save(self._codes) async def _async_learn_ir_command(self, command): """Learn an infrared command.""" diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index b4cd43ac493b39..0a98530c8069dd 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -109,7 +109,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Broadlink switch.""" device = hass.data[DOMAIN].devices[config_entry.entry_id] - if device.api.type in {"RM2", "RM4"}: + if device.api.type in {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"}: platform_data = hass.data[DOMAIN].platforms.get(SWITCH_DOMAIN, {}) user_defined_switches = platform_data.get(device.api.mac, {}) switches = [ @@ -119,12 +119,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): elif device.api.type == "SP1": switches = [BroadlinkSP1Switch(device)] - elif device.api.type == "SP2": + elif device.api.type in {"SP2", "SP2S", "SP3", "SP3S", "SP4", "SP4B"}: switches = [BroadlinkSP2Switch(device)] - elif device.api.type in {"SP4", "SP4B"}: - switches = [BroadlinkSP4Switch(device)] - elif device.api.type == "BG1": switches = [BroadlinkBG1Slot(device, slot) for slot in range(1, 3)] @@ -143,7 +140,6 @@ def __init__(self, device, command_on, command_off): self._command_on = command_on self._command_off = command_off self._coordinator = device.update_manager.coordinator - self._device_class = None self._state = None @property @@ -174,7 +170,7 @@ def should_poll(self): @property def device_class(self): """Return device class.""" - return self._device_class + return DEVICE_CLASS_SWITCH @property def device_info(self): @@ -254,7 +250,6 @@ class BroadlinkSP1Switch(BroadlinkSwitch): def __init__(self, device): """Initialize the switch.""" super().__init__(device, 1, 0) - self._device_class = DEVICE_CLASS_OUTLET @property def unique_id(self): @@ -277,10 +272,8 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): def __init__(self, device, *args, **kwargs): """Initialize the switch.""" super().__init__(device, *args, **kwargs) - self._state = self._coordinator.data["state"] - self._load_power = self._coordinator.data["load_power"] - if device.api.model == "SC1": - self._device_class = DEVICE_CLASS_SWITCH + self._state = self._coordinator.data["pwr"] + self._load_power = self._coordinator.data.get("power") @property def assumed_state(self): @@ -292,33 +285,12 @@ def current_power_w(self): """Return the current power usage in Watt.""" return self._load_power - @callback - def update_data(self): - """Update data.""" - if self._coordinator.last_update_success: - self._state = self._coordinator.data["state"] - self._load_power = self._coordinator.data["load_power"] - self.async_write_ha_state() - - -class BroadlinkSP4Switch(BroadlinkSP1Switch): - """Representation of a Broadlink SP4 switch.""" - - def __init__(self, device, *args, **kwargs): - """Initialize the switch.""" - super().__init__(device, *args, **kwargs) - self._state = self._coordinator.data["pwr"] - - @property - def assumed_state(self): - """Return True if unable to access real state of the switch.""" - return False - @callback def update_data(self): """Update data.""" if self._coordinator.last_update_success: self._state = self._coordinator.data["pwr"] + self._load_power = self._coordinator.data.get("power") self.async_write_ha_state() @@ -330,7 +302,6 @@ def __init__(self, device, slot): super().__init__(device, 1, 0) self._slot = slot self._state = self._coordinator.data[f"s{slot}"] - self._device_class = DEVICE_CLASS_OUTLET @property def unique_id(self): @@ -374,7 +345,6 @@ def __init__(self, device, slot): super().__init__(device, 1, 0) self._slot = slot self._state = self._coordinator.data[f"pwr{slot}"] - self._device_class = DEVICE_CLASS_OUTLET @property def unique_id(self): @@ -391,6 +361,11 @@ def assumed_state(self): """Return True if unable to access real state of the switch.""" return False + @property + def device_class(self): + """Return device class.""" + return DEVICE_CLASS_OUTLET + @callback def update_data(self): """Update data.""" diff --git a/homeassistant/components/broadlink/updater.py b/homeassistant/components/broadlink/updater.py index c9b273218b59bf..8401dba8c0d840 100644 --- a/homeassistant/components/broadlink/updater.py +++ b/homeassistant/components/broadlink/updater.py @@ -1,17 +1,9 @@ """Support for fetching data from Broadlink devices.""" from abc import ABC, abstractmethod from datetime import timedelta -from functools import partial import logging -import broadlink as blk -from broadlink.exceptions import ( - AuthorizationError, - BroadlinkException, - CommandNotSupportedError, - NetworkTimeoutError, - StorageError, -) +from broadlink.exceptions import AuthorizationError, BroadlinkException from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt @@ -21,17 +13,20 @@ def get_update_manager(device): """Return an update manager for a given Broadlink device.""" - if device.api.model.startswith("RM mini"): - return BroadlinkRMMini3UpdateManager(device) - update_managers = { "A1": BroadlinkA1UpdateManager, "BG1": BroadlinkBG1UpdateManager, "MP1": BroadlinkMP1UpdateManager, - "RM2": BroadlinkRMUpdateManager, - "RM4": BroadlinkRMUpdateManager, + "RM4MINI": BroadlinkRMUpdateManager, + "RM4PRO": BroadlinkRMUpdateManager, + "RMMINI": BroadlinkRMUpdateManager, + "RMMINIB": BroadlinkRMUpdateManager, + "RMPRO": BroadlinkRMUpdateManager, "SP1": BroadlinkSP1UpdateManager, "SP2": BroadlinkSP2UpdateManager, + "SP2S": BroadlinkSP2UpdateManager, + "SP3": BroadlinkSP2UpdateManager, + "SP3S": BroadlinkSP2UpdateManager, "SP4": BroadlinkSP4UpdateManager, "SP4B": BroadlinkSP4UpdateManager, } @@ -114,28 +109,18 @@ async def async_fetch_data(self): return await self.device.async_request(self.device.api.check_power) -class BroadlinkRMMini3UpdateManager(BroadlinkUpdateManager): - """Manages updates for Broadlink RM mini 3 devices.""" +class BroadlinkRMUpdateManager(BroadlinkUpdateManager): + """Manages updates for Broadlink remotes.""" async def async_fetch_data(self): """Fetch data from the device.""" - hello = partial( - blk.discover, - discover_ip_address=self.device.api.host[0], - timeout=self.device.api.timeout, - ) - devices = await self.device.hass.async_add_executor_job(hello) - if not devices: - raise NetworkTimeoutError("The device is offline") - return {} + device = self.device + if hasattr(device.api, "check_sensors"): + return await device.async_request(device.api.check_sensors) -class BroadlinkRMUpdateManager(BroadlinkUpdateManager): - """Manages updates for Broadlink RM2 and RM4 devices.""" - - async def async_fetch_data(self): - """Fetch data from the device.""" - return await self.device.async_request(self.device.api.check_sensors) + await device.async_request(device.api.update) + return {} class BroadlinkSP1UpdateManager(BroadlinkUpdateManager): @@ -151,14 +136,14 @@ class BroadlinkSP2UpdateManager(BroadlinkUpdateManager): async def async_fetch_data(self): """Fetch data from the device.""" + device = self.device + data = {} - data["state"] = await self.device.async_request(self.device.api.check_power) - try: - data["load_power"] = await self.device.async_request( - self.device.api.get_energy - ) - except (CommandNotSupportedError, StorageError): - data["load_power"] = None + data["pwr"] = await device.async_request(device.api.check_power) + + if hasattr(device.api, "get_energy"): + data["power"] = await device.async_request(device.api.get_energy) + return data diff --git a/requirements_all.txt b/requirements_all.txt index 140273de629161..b0fae5c32ee337 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -381,7 +381,7 @@ boto3==1.9.252 bravia-tv==1.0.8 # homeassistant.components.broadlink -broadlink==0.16.0 +broadlink==0.17.0 # homeassistant.components.brother brother==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a2aaf83b113dbf..96f6d6bb4ef1e9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -211,7 +211,7 @@ bond-api==0.1.11 bravia-tv==1.0.8 # homeassistant.components.broadlink -broadlink==0.16.0 +broadlink==0.17.0 # homeassistant.components.brother brother==0.2.1 diff --git a/tests/components/broadlink/__init__.py b/tests/components/broadlink/__init__.py index 7185c605f5ce8c..780887551f235b 100644 --- a/tests/components/broadlink/__init__.py +++ b/tests/components/broadlink/__init__.py @@ -12,7 +12,7 @@ "34ea34befc25", "RM mini 3", "Broadlink", - "RM2", + "RMMINI", 0x2737, 57, 8, @@ -22,7 +22,7 @@ "34ea34b43b5a", "RM mini 3", "Broadlink", - "RM4", + "RMMINIB", 0x5F36, 44017, 10, @@ -32,7 +32,7 @@ "34ea34b43d22", "RM pro", "Broadlink", - "RM2", + "RMPRO", 0x2787, 20025, 7, @@ -42,7 +42,7 @@ "34ea34c43f31", "RM4 pro", "Broadlink", - "RM4", + "RM4PRO", 0x6026, 52, 4, @@ -62,7 +62,7 @@ "34ea34b61d2c", "LB1", "Broadlink", - "SmartBulb", + "LB1", 0x504E, 57, 5, @@ -96,9 +96,6 @@ async def setup_entry(self, hass, mock_api=None, mock_entry=None): with patch( "homeassistant.components.broadlink.device.blk.gendevice", return_value=mock_api, - ), patch( - "homeassistant.components.broadlink.updater.blk.discover", - return_value=[mock_api], ): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() From e2f0b1842780c036ff952446b604e43039298cf8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 19:35:24 -1000 Subject: [PATCH 1203/1818] Adjust insteon fan speed range to valid upper bound (#47765) --- homeassistant/components/insteon/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 596c254dc6573d..00ada3e9a588ba 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -17,7 +17,7 @@ from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (0x00, 0xFF) # off is not included +SPEED_RANGE = (1, 255) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): From 7f60edd7e7360c55fac3c4ea884de3dbdada8ea0 Mon Sep 17 00:00:00 2001 From: James Nimmo Date: Fri, 12 Mar 2021 18:39:44 +1300 Subject: [PATCH 1204/1818] Bump pyIntesisHome to v1.7.6 (#47500) --- homeassistant/components/intesishome/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/intesishome/manifest.json b/homeassistant/components/intesishome/manifest.json index 4131811807af45..d17014cdf0d6aa 100644 --- a/homeassistant/components/intesishome/manifest.json +++ b/homeassistant/components/intesishome/manifest.json @@ -3,5 +3,5 @@ "name": "IntesisHome", "documentation": "https://www.home-assistant.io/integrations/intesishome", "codeowners": ["@jnimmo"], - "requirements": ["pyintesishome==1.7.5"] + "requirements": ["pyintesishome==1.7.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index b0fae5c32ee337..5f4e3a67e5b53d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1444,7 +1444,7 @@ pyicloud==0.10.2 pyinsteon==1.0.9 # homeassistant.components.intesishome -pyintesishome==1.7.5 +pyintesishome==1.7.6 # homeassistant.components.ipma pyipma==2.0.5 From 33c4eb343419a55908f6951d8a37d31e78efdc51 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 19:52:04 -1000 Subject: [PATCH 1205/1818] Log the full exception when the recorder fails to setup (#47770) --- homeassistant/components/recorder/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index f8f95fd7cccbc0..12fb1b38c25b6a 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -400,7 +400,7 @@ def _setup_recorder(self) -> bool: migration.migrate_schema(self) self._setup_run() except Exception as err: # pylint: disable=broad-except - _LOGGER.error( + _LOGGER.exception( "Error during connection setup to %s: %s (retrying in %s seconds)", self.db_url, err, From f4b775b1254095ae4c37e234c9925acb9161d77d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 20:05:03 -1000 Subject: [PATCH 1206/1818] Cleanup homekit and remove aid storage from hass.data (#47488) --- homeassistant/components/homekit/__init__.py | 104 ++++---- homeassistant/components/homekit/const.py | 1 - homeassistant/components/homekit/util.py | 4 +- tests/components/homekit/conftest.py | 4 +- tests/components/homekit/test_homekit.py | 235 ++++++++----------- tests/components/homekit/test_util.py | 4 +- 6 files changed, 147 insertions(+), 205 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 23492b12ccc64d..7c787c7e7be5df 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -34,7 +34,7 @@ SERVICE_RELOAD, ) from homeassistant.core import CoreState, HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady, Unauthorized +from homeassistant.exceptions import Unauthorized from homeassistant.helpers import device_registry, entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import BASE_FILTER_SCHEMA, FILTER_SCHEMA @@ -58,7 +58,6 @@ from .accessories import HomeBridge, HomeDriver, get_accessory from .aidmanager import AccessoryAidStorage from .const import ( - AID_STORAGE, ATTR_INTERGRATION, ATTR_MANUFACTURER, ATTR_MODEL, @@ -241,9 +240,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): port = conf[CONF_PORT] _LOGGER.debug("Begin setup HomeKit for %s", name) - aid_storage = AccessoryAidStorage(hass, entry.entry_id) - - await aid_storage.async_initialize() # ip_address and advertise_ip are yaml only ip_address = conf.get(CONF_IP_ADDRESS) advertise_ip = conf.get(CONF_ADVERTISE_IP) @@ -276,26 +272,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.entry_id, entry.title, ) - zeroconf_instance = await zeroconf.async_get_instance(hass) - - # If the previous instance hasn't cleaned up yet - # we need to wait a bit - try: - await hass.async_add_executor_job(homekit.setup, zeroconf_instance) - except (OSError, AttributeError) as ex: - _LOGGER.warning( - "%s could not be setup because the local port %s is in use", name, port - ) - raise ConfigEntryNotReady from ex - - undo_listener = entry.add_update_listener(_async_update_listener) hass.data[DOMAIN][entry.entry_id] = { - AID_STORAGE: aid_storage, HOMEKIT: homekit, - UNDO_UPDATE_LISTENER: undo_listener, + UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), } + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, homekit.async_stop) + if hass.state == CoreState.running: await homekit.async_start() elif auto_start: @@ -463,6 +447,7 @@ def __init__( self._entry_id = entry_id self._entry_title = entry_title self._homekit_mode = homekit_mode + self.aid_storage = None self.status = STATUS_READY self.bridge = None @@ -470,7 +455,6 @@ def __init__( def setup(self, zeroconf_instance): """Set up bridge and accessory driver.""" - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) ip_addr = self._ip_address or get_local_ip() persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) @@ -503,10 +487,9 @@ def reset_accessories(self, entity_ids): self.driver.config_changed() return - aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE] removed = [] for entity_id in entity_ids: - aid = aid_storage.get_or_allocate_aid_for_entity_id(entity_id) + aid = self.aid_storage.get_or_allocate_aid_for_entity_id(entity_id) if aid not in self.bridge.accessories: continue @@ -531,9 +514,6 @@ def reset_accessories(self, entity_ids): def add_bridge_accessory(self, state): """Try adding accessory to bridge if configured beforehand.""" - if not self._filter(state.entity_id): - return - # The bridge itself counts as an accessory if len(self.bridge.accessories) + 1 >= MAX_DEVICES: _LOGGER.warning( @@ -555,9 +535,7 @@ def add_bridge_accessory(self, state): state.entity_id, ) - aid = self.hass.data[DOMAIN][self._entry_id][ - AID_STORAGE - ].get_or_allocate_aid_for_entity_id(state.entity_id) + aid = self.aid_storage.get_or_allocate_aid_for_entity_id(state.entity_id) conf = self._config.pop(state.entity_id, {}) # If an accessory cannot be created or added due to an exception # of any kind (usually in pyhap) it should not prevent @@ -578,15 +556,10 @@ def remove_bridge_accessory(self, aid): acc = self.bridge.accessories.pop(aid) return acc - async def async_start(self, *args): - """Start the accessory driver.""" - if self.status != STATUS_READY: - return - self.status = STATUS_WAIT - - ent_reg = await entity_registry.async_get_registry(self.hass) - dev_reg = await device_registry.async_get_registry(self.hass) - + async def async_configure_accessories(self): + """Configure accessories for the included states.""" + dev_reg = device_registry.async_get(self.hass) + ent_reg = entity_registry.async_get(self.hass) device_lookup = ent_reg.async_get_device_class_lookup( { (BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING), @@ -597,10 +570,9 @@ async def async_start(self, *args): } ) - bridged_states = [] + entity_states = [] for state in self.hass.states.async_all(): entity_id = state.entity_id - if not self._filter(entity_id): continue @@ -611,17 +583,40 @@ async def async_start(self, *args): ) self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state) - bridged_states.append(state) + entity_states.append(state) + + return entity_states - self._async_register_bridge(dev_reg) - await self._async_start(bridged_states) + async def async_start(self, *args): + """Load storage and start.""" + if self.status != STATUS_READY: + return + self.status = STATUS_WAIT + zc_instance = await zeroconf.async_get_instance(self.hass) + await self.hass.async_add_executor_job(self.setup, zc_instance) + self.aid_storage = AccessoryAidStorage(self.hass, self._entry_id) + await self.aid_storage.async_initialize() + await self._async_create_accessories() + self._async_register_bridge() _LOGGER.debug("Driver start for %s", self._name) await self.driver.async_start() self.status = STATUS_RUNNING + if self.driver.state.paired: + return + + show_setup_message( + self.hass, + self._entry_id, + accessory_friendly_name(self._entry_title, self.driver.accessory), + self.driver.state.pincode, + self.driver.accessory.xhm_uri(), + ) + @callback - def _async_register_bridge(self, dev_reg): + def _async_register_bridge(self): """Register the bridge as a device so homekit_controller and exclude it from discovery.""" + dev_reg = device_registry.async_get(self.hass) formatted_mac = device_registry.format_mac(self.driver.state.mac) # Connections and identifiers are both used here. # @@ -645,8 +640,9 @@ def _async_register_bridge(self, dev_reg): identifiers={identifier}, connections={connection}, manufacturer=MANUFACTURER, - name=self._name, - model=f"Home Assistant HomeKit {hk_mode_name}", + name=accessory_friendly_name(self._entry_title, self.driver.accessory), + model=f"HomeKit {hk_mode_name}", + entry_type="service", ) @callback @@ -663,14 +659,13 @@ def _async_purge_old_bridges(self, dev_reg, identifier, connection): for device_id in devices_to_purge: dev_reg.async_remove_device(device_id) - async def _async_start(self, entity_states): - """Start the accessory.""" + async def _async_create_accessories(self): + """Create the accessories.""" + entity_states = await self.async_configure_accessories() if self._homekit_mode == HOMEKIT_MODE_ACCESSORY: state = entity_states[0] conf = self._config.pop(state.entity_id, {}) acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf) - - self.driver.add_accessory(acc) else: self.bridge = HomeBridge(self.hass, self.driver, self._name) for state in entity_states: @@ -679,15 +674,6 @@ async def _async_start(self, entity_states): await self.hass.async_add_executor_job(self.driver.add_accessory, acc) - if not self.driver.state.paired: - show_setup_message( - self.hass, - self._entry_id, - accessory_friendly_name(self._entry_title, self.driver.accessory), - self.driver.state.pincode, - self.driver.accessory.xhm_uri(), - ) - async def async_stop(self, *args): """Stop the accessory driver.""" if self.status != STATUS_RUNNING: diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 840e9ebe60773e..ff45306b3515b4 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -5,7 +5,6 @@ DEVICE_PRECISION_LEEWAY = 6 DOMAIN = "homekit" HOMEKIT_FILE = ".homekit.state" -AID_STORAGE = "homekit-aid-allocations" HOMEKIT_PAIRING_QR = "homekit-pairing-qr" HOMEKIT_PAIRING_QR_SECRET = "homekit-pairing-qr-secret" HOMEKIT = "homekit" diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 46b893bb96d3b5..10550cf4a119c0 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -487,8 +487,10 @@ def accessory_friendly_name(hass_name, accessory): see both to identify the accessory. """ accessory_mdns_name = accessory.display_name - if hass_name.startswith(accessory_mdns_name): + if hass_name.casefold().startswith(accessory_mdns_name.casefold()): return hass_name + if accessory_mdns_name.casefold().startswith(hass_name.casefold()): + return accessory_mdns_name return f"{hass_name} ({accessory_mdns_name})" diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index 228b5f078376be..469a0a7deb7191 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -14,7 +14,9 @@ def hk_driver(loop): """Return a custom AccessoryDriver instance for HomeKit accessory init.""" with patch("pyhap.accessory_driver.Zeroconf"), patch( "pyhap.accessory_driver.AccessoryEncoder" - ), patch("pyhap.accessory_driver.HAPServer"), patch( + ), patch("pyhap.accessory_driver.HAPServer.async_stop"), patch( + "pyhap.accessory_driver.HAPServer.async_start" + ), patch( "pyhap.accessory_driver.AccessoryDriver.publish" ), patch( "pyhap.accessory_driver.AccessoryDriver.persist" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 4d2fbfe951d27f..6d5f3faae9507f 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,4 +1,5 @@ """Tests for the HomeKit component.""" +import asyncio import os from typing import Dict from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch @@ -23,7 +24,6 @@ ) from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( - AID_STORAGE, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CONF_AUTO_START, @@ -47,7 +47,6 @@ DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, EVENT_HOMEASSISTANT_STARTED, - EVENT_HOMEASSISTANT_STOP, PERCENTAGE, SERVICE_RELOAD, STATE_ON, @@ -98,8 +97,28 @@ def _mock_homekit(hass, entry, homekit_mode, entity_filter=None): ) +def _mock_homekit_bridge(hass, entry): + homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) + homekit.driver = MagicMock() + return homekit + + +def _mock_accessories(accessory_count): + accessories = {} + for idx in range(accessory_count + 1): + accessories[idx + 1000] = MagicMock(async_stop=AsyncMock()) + return accessories + + +def _mock_pyhap_bridge(): + return MagicMock( + aid=1, accessories=_mock_accessories(10), display_name="HomeKit Bridge" + ) + + async def test_setup_min(hass, mock_zeroconf): """Test async_setup with min config options.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, @@ -126,18 +145,16 @@ async def test_setup_min(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True # Test auto start enabled - mock_homekit.reset_mock() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() - - mock_homekit().async_start.assert_called() + assert mock_homekit().async_start.called is True async def test_setup_auto_start_disabled(hass, mock_zeroconf): """Test async_setup with auto start disabled and test service calls.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "Test Name", CONF_PORT: 11111, CONF_IP_ADDRESS: "172.0.0.0"}, @@ -164,7 +181,6 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True # Test auto_start disabled homekit.reset_mock() @@ -237,9 +253,6 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf): ) assert homekit.driver.safe_mode is False - # Test if stop listener is setup - assert hass.bus.async_listeners().get(EVENT_HOMEASSISTANT_STOP) == 1 - async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf): """Test setup with given IP address.""" @@ -321,40 +334,37 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf): async def test_homekit_add_accessory(hass, mock_zeroconf): """Add accessory if config exists and get_acc returns an accessory.""" - + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entry.add_to_hass(hass) - homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit.driver = "driver" - homekit.bridge = mock_bridge = Mock() - homekit.bridge.accessories = range(10) - homekit.async_start = AsyncMock() + homekit = _mock_homekit_bridge(hass, entry) + mock_acc = Mock(category="any") with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - mock_acc = Mock(category="any") + homekit.bridge = _mock_pyhap_bridge() with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, mock_acc, None] state = State("light.demo", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 1403373688, {}) - assert not mock_bridge.add_accessory.called + assert not homekit.bridge.add_accessory.called state = State("demo.test", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 600325356, {}) - assert mock_bridge.add_accessory.called + assert homekit.bridge.add_accessory.called state = State("demo.test_2", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 1467253281, {}) - assert mock_bridge.add_accessory.called + assert homekit.bridge.add_accessory.called @pytest.mark.parametrize("acc_category", [CATEGORY_TELEVISION, CATEGORY_CAMERA]) @@ -362,29 +372,27 @@ async def test_homekit_warn_add_accessory_bridge( hass, acc_category, mock_zeroconf, caplog ): """Test we warn when adding cameras or tvs to a bridge.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entry.add_to_hass(hass) - homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit.driver = "driver" - homekit.bridge = mock_bridge = Mock() - homekit.bridge.accessories = range(10) - homekit.async_start = AsyncMock() + homekit = _mock_homekit_bridge(hass, entry) with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() mock_camera_acc = Mock(category=acc_category) + homekit.bridge = _mock_pyhap_bridge() with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, mock_camera_acc, None] state = State("camera.test", "on") homekit.add_bridge_accessory(state) mock_get_acc.assert_called_with(hass, ANY, ANY, 1508819236, {}) - assert not mock_bridge.add_accessory.called + assert not homekit.bridge.add_accessory.called assert "accessory mode" in caplog.text @@ -396,12 +404,12 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf): homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = "driver" - homekit.bridge = mock_bridge = Mock() - mock_bridge.accessories = {"light.demo": "acc"} + homekit.bridge = _mock_pyhap_bridge() + homekit.bridge.accessories = {"light.demo": "acc"} acc = homekit.remove_bridge_accessory("light.demo") assert acc == "acc" - assert len(mock_bridge.accessories) == 0 + assert len(homekit.bridge.accessories) == 0 async def test_homekit_entity_filter(hass, mock_zeroconf): @@ -413,20 +421,14 @@ async def test_homekit_entity_filter(hass, mock_zeroconf): homekit.bridge = Mock() homekit.bridge.accessories = {} + hass.states.async_set("cover.test", "open") + hass.states.async_set("demo.test", "on") + hass.states.async_set("light.demo", "on") - with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: - mock_get_acc.return_value = None - - homekit.add_bridge_accessory(State("cover.test", "open")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("demo.test", "on")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("light.demo", "light")) - assert mock_get_acc.called is False + filtered_states = await homekit.async_configure_accessories() + assert hass.states.get("cover.test") in filtered_states + assert hass.states.get("demo.test") in filtered_states + assert hass.states.get("light.demo") not in filtered_states async def test_homekit_entity_glob_filter(hass, mock_zeroconf): @@ -441,39 +443,29 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf): homekit.bridge = Mock() homekit.bridge.accessories = {} - with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: - mock_get_acc.return_value = None - - homekit.add_bridge_accessory(State("cover.test", "open")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - - homekit.add_bridge_accessory(State("demo.test", "on")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() + hass.states.async_set("cover.test", "open") + hass.states.async_set("demo.test", "on") + hass.states.async_set("cover.excluded_test", "open") + hass.states.async_set("light.included_test", "on") - homekit.add_bridge_accessory(State("cover.excluded_test", "open")) - assert mock_get_acc.called is False - mock_get_acc.reset_mock() + filtered_states = await homekit.async_configure_accessories() + assert hass.states.get("cover.test") in filtered_states + assert hass.states.get("demo.test") in filtered_states + assert hass.states.get("cover.excluded_test") not in filtered_states + assert hass.states.get("light.included_test") in filtered_states - homekit.add_bridge_accessory(State("light.included_test", "light")) - assert mock_get_acc.called is True - mock_get_acc.reset_mock() - -async def test_homekit_start(hass, hk_driver, device_reg): +async def test_homekit_start(hass, hk_driver, mock_zeroconf, device_reg): """Test HomeKit start method.""" entry = await async_init_integration(hass) - pin = b"123-45-678" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) - homekit.driver.accessory = Accessory(hk_driver, "any") + acc = Accessory(hk_driver, "any") + homekit.driver.accessory = acc connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF") bridge_with_wrong_mac = device_reg.async_get_or_create( @@ -491,8 +483,6 @@ async def test_homekit_start(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( f"{PATH_HOMEKIT}.show_setup_message" ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ) as hk_driver_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -500,9 +490,8 @@ async def test_homekit_start(hass, hk_driver, device_reg): await hass.async_block_till_done() mock_add_acc.assert_any_call(state) mock_setup_msg.assert_called_with( - hass, entry.entry_id, "Mock Title (any)", pin, ANY + hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY ) - hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -526,8 +515,6 @@ async def test_homekit_start(hass, hk_driver, device_reg): with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( f"{PATH_HOMEKIT}.show_setup_message" ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ) as hk_driver_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -545,7 +532,6 @@ async def test_homekit_start(hass, hk_driver, device_reg): async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf): """Test HomeKit start method.""" - pin = b"123-45-678" entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) @@ -565,17 +551,14 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc with patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), patch( f"{PATH_HOMEKIT}.show_setup_message" ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory", - ) as hk_driver_add_acc, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() await hass.async_block_till_done() mock_setup_msg.assert_called_with( - hass, entry.entry_id, "Mock Title (any)", pin, ANY + hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY ) - hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -616,27 +599,23 @@ async def test_homekit_stop(hass): async def test_homekit_reset_accessories(hass, mock_zeroconf): """Test adding too many accessories to HomeKit.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) entity_id = "light.demo" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - homekit.bridge = Mock() - homekit.bridge.accessories = {} - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - f"{PATH_HOMEKIT}.HomeKit.setup" - ), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch( + "pyhap.accessory.Bridge.add_accessory" + ) as mock_add_accessory, patch( "pyhap.accessory_driver.AccessoryDriver.config_changed" ) as hk_driver_config_changed, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ): await async_init_entry(hass, entry) - aid = hass.data[DOMAIN][entry.entry_id][ - AID_STORAGE - ].get_or_allocate_aid_for_entity_id(entity_id) + aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id) homekit.bridge.accessories = {aid: "acc"} homekit.status = STATUS_RUNNING @@ -675,10 +654,8 @@ def _mock_bridge(*_): hass.states.async_set("light.demo3", "on") with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( - f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge - ): + f"{PATH_HOMEKIT}.show_setup_message" + ), patch(f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge): await homekit.async_start() await hass.async_block_till_done() assert "would exceed" in caplog.text @@ -693,9 +670,7 @@ async def test_homekit_finds_linked_batteries( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) - homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") + homekit.bridge = MagicMock() config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -735,17 +710,15 @@ async def test_homekit_finds_linked_batteries( ) hass.states.async_set(light.entity_id, STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ): + with patch(f"{PATH_HOMEKIT}.show_setup_message"), patch( + f"{PATH_HOMEKIT}.get_accessory" + ) as mock_get_acc, patch("pyhap.accessory_driver.AccessoryDriver.async_start"): await homekit.async_start() await hass.async_block_till_done() mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -766,8 +739,6 @@ async def test_homekit_async_get_integration_fails( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") config_entry = MockConfigEntry(domain="test", data={}) @@ -817,7 +788,7 @@ async def test_homekit_async_get_integration_fails( mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -832,6 +803,7 @@ async def test_homekit_async_get_integration_fails( async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): """Test async_setup with imported config.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, source=SOURCE_IMPORT, @@ -861,7 +833,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True # Test auto start enabled mock_homekit.reset_mock() @@ -871,20 +842,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf): mock_homekit().async_start.assert_called() -async def test_raise_config_entry_not_ready(hass, mock_zeroconf): - """Test async_setup when the port is not available.""" - entry = MockConfigEntry( - domain=DOMAIN, - data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, - options={}, - ) - entry.add_to_hass(hass) - - with patch(f"{PATH_HOMEKIT}.HomeKit.setup", side_effect=OSError): - assert not await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): """Test HomeKit uses system zeroconf.""" entry = MockConfigEntry( @@ -917,13 +874,12 @@ async def test_homekit_ignored_missing_devices( hass, hk_driver, device_reg, entity_reg, mock_zeroconf ): """Test HomeKit handles a device in the entity registry but missing from the device registry.""" + await async_setup_component(hass, "persistent_notification", {}) entry = await async_init_integration(hass) homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) - homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") + homekit.bridge = _mock_pyhap_bridge() config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -952,25 +908,28 @@ async def test_homekit_ignored_missing_devices( light = entity_reg.async_get_or_create( "light", "powerwall", "demo", device_id=device_entry.id ) - + before_removal = entity_reg.entities.copy() # Delete the device to make sure we fallback # to using the platform device_reg.async_remove_device(device_entry.id) + # Wait for the entities to be removed + await asyncio.sleep(0) + await asyncio.sleep(0) + # Restore the registry + entity_reg.entities = before_removal hass.states.async_set(light.entity_id, STATE_ON) hass.states.async_set("light.two", STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ): + with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( + f"{PATH_HOMEKIT}.HomeBridge", return_value=homekit.bridge + ), patch("pyhap.accessory_driver.AccessoryDriver.async_start"): await homekit.async_start() - await hass.async_block_till_done() + await hass.async_block_till_done() mock_get_acc.assert_any_call( hass, - hk_driver, + ANY, ANY, ANY, { @@ -990,8 +949,6 @@ async def test_homekit_finds_linked_motion_sensors( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") config_entry = MockConfigEntry(domain="test", data={}) @@ -1032,7 +989,7 @@ async def test_homekit_finds_linked_motion_sensors( mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -1053,7 +1010,6 @@ async def test_homekit_finds_linked_humidity_sensors( homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) homekit.driver = hk_driver - homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") config_entry = MockConfigEntry(domain="test", data={}) @@ -1097,7 +1053,7 @@ async def test_homekit_finds_linked_humidity_sensors( mock_get_acc.assert_called_with( hass, - hk_driver, + ANY, ANY, ANY, { @@ -1111,6 +1067,7 @@ async def test_homekit_finds_linked_humidity_sensors( async def test_reload(hass, mock_zeroconf): """Test we can reload from yaml.""" + await async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, source=SOURCE_IMPORT, @@ -1121,7 +1078,6 @@ async def test_reload(hass, mock_zeroconf): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() - type(homekit).async_start = AsyncMock() assert await async_setup_component( hass, "homekit", {"homekit": {CONF_NAME: "reloadable", CONF_PORT: 12345}} ) @@ -1140,7 +1096,6 @@ async def test_reload(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit().setup.called is True yaml_path = os.path.join( _get_fixtures_base_path(), "fixtures", @@ -1156,7 +1111,6 @@ async def test_reload(hass, mock_zeroconf): "pyhap.accessory_driver.AccessoryDriver.async_start" ): mock_homekit2.return_value = homekit = Mock() - type(homekit).async_start = AsyncMock() await hass.services.async_call( "homekit", SERVICE_RELOAD, @@ -1178,33 +1132,30 @@ async def test_reload(hass, mock_zeroconf): entry.entry_id, entry.title, ) - assert mock_homekit2().setup.called is True def _get_fixtures_base_path(): return os.path.dirname(os.path.dirname(os.path.dirname(__file__))) -async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): +async def test_homekit_start_in_accessory_mode( + hass, hk_driver, mock_zeroconf, device_reg +): """Test HomeKit start method in accessory mode.""" entry = await async_init_integration(hass) - pin = b"123-45-678" - homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver - # pylint: disable=protected-access - homekit._filter = Mock(return_value=True) homekit.driver.accessory = Accessory(hk_driver, "any") hass.states.async_set("light.demo", "on") with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.add_accessory" - ), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch( + f"{PATH_HOMEKIT}.show_setup_message" + ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" ) as hk_driver_start: await homekit.async_start() @@ -1212,7 +1163,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg): await hass.async_block_till_done() mock_add_acc.assert_not_called() mock_setup_msg.assert_called_with( - hass, entry.entry_id, "Mock Title (any)", pin, ANY + hass, entry.entry_id, "Mock Title (demo)", ANY, ANY ) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 9b03d6160027e8..c458d1dc4ef27c 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -294,5 +294,7 @@ async def test_accessory_friendly_name(): accessory = Mock() accessory.display_name = "same" - assert accessory_friendly_name("same", accessory) == "same" + assert accessory_friendly_name("Same", accessory) == "Same" assert accessory_friendly_name("hass title", accessory) == "hass title (same)" + accessory.display_name = "Hass title 123" + assert accessory_friendly_name("hass title", accessory) == "Hass title 123" From fa0c544bf572303e3d16b4dad9fac0d2ae29bce4 Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Fri, 12 Mar 2021 07:15:45 +0100 Subject: [PATCH 1207/1818] Improve Atag integration and bump version to 0.3.5.3 (#47778) Co-authored-by: Paulus Schoutsen --- homeassistant/components/atag/__init__.py | 57 +++++++++---------- homeassistant/components/atag/climate.py | 46 +++++++-------- homeassistant/components/atag/config_flow.py | 14 ++--- homeassistant/components/atag/manifest.json | 2 +- homeassistant/components/atag/sensor.py | 17 +++--- homeassistant/components/atag/strings.json | 1 - homeassistant/components/atag/water_heater.py | 12 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/atag/__init__.py | 54 ++++++++++++------ tests/components/atag/test_climate.py | 49 +++++++--------- tests/components/atag/test_config_flow.py | 53 +++++++---------- tests/components/atag/test_init.py | 17 +----- 13 files changed, 152 insertions(+), 174 deletions(-) diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index c4cffe1c41ad0c..02aab5036b38b5 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -31,17 +31,34 @@ async def async_setup(hass: HomeAssistant, config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Atag integration from a config entry.""" - session = async_get_clientsession(hass) - coordinator = AtagDataUpdateCoordinator(hass, session, entry) + async def _async_update_data(): + """Update data via library.""" + with async_timeout.timeout(20): + try: + await atag.update() + except AtagException as err: + raise UpdateFailed(err) from err + return atag + + atag = AtagOne( + session=async_get_clientsession(hass), **entry.data, device=entry.unique_id + ) + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=DOMAIN.title(), + update_method=_async_update_data, + update_interval=timedelta(seconds=60), + ) + await coordinator.async_refresh() if not coordinator.last_update_success: raise ConfigEntryNotReady - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = coordinator + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator if entry.unique_id is None: - hass.config_entries.async_update_entry(entry, unique_id=coordinator.atag.id) + hass.config_entries.async_update_entry(entry, unique_id=atag.id) for platform in PLATFORMS: hass.async_create_task( @@ -51,28 +68,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -class AtagDataUpdateCoordinator(DataUpdateCoordinator): - """Define an object to hold Atag data.""" - - def __init__(self, hass, session, entry): - """Initialize.""" - self.atag = AtagOne(session=session, **entry.data) - - super().__init__( - hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=30) - ) - - async def _async_update_data(self): - """Update data via library.""" - with async_timeout.timeout(20): - try: - if not await self.atag.update(): - raise UpdateFailed("No data received") - except AtagException as error: - raise UpdateFailed(error) from error - return self.atag.report - - async def async_unload_entry(hass, entry): """Unload Atag config entry.""" unload_ok = all( @@ -91,7 +86,7 @@ async def async_unload_entry(hass, entry): class AtagEntity(CoordinatorEntity): """Defines a base Atag entity.""" - def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None: + def __init__(self, coordinator: DataUpdateCoordinator, atag_id: str) -> None: """Initialize the Atag entity.""" super().__init__(coordinator) @@ -101,8 +96,8 @@ def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None @property def device_info(self) -> dict: """Return info for device registry.""" - device = self.coordinator.atag.id - version = self.coordinator.atag.apiversion + device = self.coordinator.data.id + version = self.coordinator.data.apiversion return { "identifiers": {(DOMAIN, device)}, "name": "Atag Thermostat", @@ -119,4 +114,4 @@ def name(self) -> str: @property def unique_id(self): """Return a unique ID to use for this entity.""" - return f"{self.coordinator.atag.id}-{self._id}" + return f"{self.coordinator.data.id}-{self._id}" diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index ad46fefe8c2bec..a2aa5cf16e476d 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -16,16 +16,14 @@ from . import CLIMATE, DOMAIN, AtagEntity -PRESET_SCHEDULE = "Auto" -PRESET_MANUAL = "Manual" -PRESET_EXTEND = "Extend" -SUPPORT_PRESET = [ - PRESET_MANUAL, - PRESET_SCHEDULE, - PRESET_EXTEND, - PRESET_AWAY, - PRESET_BOOST, -] +PRESET_MAP = { + "Manual": "manual", + "Auto": "automatic", + "Extend": "extend", + PRESET_AWAY: "vacation", + PRESET_BOOST: "fireplace", +} +PRESET_INVERTED = {v: k for k, v in PRESET_MAP.items()} SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE HVAC_MODES = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] @@ -47,8 +45,8 @@ def supported_features(self): @property def hvac_mode(self) -> Optional[str]: """Return hvac operation ie. heat, cool mode.""" - if self.coordinator.atag.climate.hvac_mode in HVAC_MODES: - return self.coordinator.atag.climate.hvac_mode + if self.coordinator.data.climate.hvac_mode in HVAC_MODES: + return self.coordinator.data.climate.hvac_mode return None @property @@ -59,46 +57,46 @@ def hvac_modes(self) -> List[str]: @property def hvac_action(self) -> Optional[str]: """Return the current running hvac operation.""" - if self.coordinator.atag.climate.status: - return CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + is_active = self.coordinator.data.climate.status + return CURRENT_HVAC_HEAT if is_active else CURRENT_HVAC_IDLE @property - def temperature_unit(self): + def temperature_unit(self) -> Optional[str]: """Return the unit of measurement.""" - return self.coordinator.atag.climate.temp_unit + return self.coordinator.data.climate.temp_unit @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" - return self.coordinator.atag.climate.temperature + return self.coordinator.data.climate.temperature @property def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" - return self.coordinator.atag.climate.target_temperature + return self.coordinator.data.climate.target_temperature @property def preset_mode(self) -> Optional[str]: """Return the current preset mode, e.g., auto, manual, fireplace, extend, etc.""" - return self.coordinator.atag.climate.preset_mode + preset = self.coordinator.data.climate.preset_mode + return PRESET_INVERTED.get(preset) @property def preset_modes(self) -> Optional[List[str]]: """Return a list of available preset modes.""" - return SUPPORT_PRESET + return list(PRESET_MAP.keys()) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - await self.coordinator.atag.climate.set_temp(kwargs.get(ATTR_TEMPERATURE)) + await self.coordinator.data.climate.set_temp(kwargs.get(ATTR_TEMPERATURE)) self.async_write_ha_state() async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new target hvac mode.""" - await self.coordinator.atag.climate.set_hvac_mode(hvac_mode) + await self.coordinator.data.climate.set_hvac_mode(hvac_mode) self.async_write_ha_state() async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - await self.coordinator.atag.climate.set_preset_mode(preset_mode) + await self.coordinator.data.climate.set_preset_mode(PRESET_MAP[preset_mode]) self.async_write_ha_state() diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index 865159aa65806c..b1dcedd58dc3cc 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -3,14 +3,13 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_EMAIL, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import DOMAIN # pylint: disable=unused-import DATA_SCHEMA = { vol.Required(CONF_HOST): str, - vol.Optional(CONF_EMAIL): str, vol.Required(CONF_PORT, default=pyatag.const.DEFAULT_PORT): vol.Coerce(int), } @@ -26,15 +25,14 @@ async def async_step_user(self, user_input=None): if not user_input: return await self._show_form() - session = async_get_clientsession(self.hass) + + atag = pyatag.AtagOne(session=async_get_clientsession(self.hass), **user_input) try: - atag = pyatag.AtagOne(session=session, **user_input) - await atag.authorize() - await atag.update(force=True) + await atag.update() - except pyatag.errors.Unauthorized: + except pyatag.Unauthorized: return await self._show_form({"base": "unauthorized"}) - except pyatag.errors.AtagException: + except pyatag.AtagException: return await self._show_form({"base": "cannot_connect"}) await self.async_set_unique_id(atag.id) diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index 5e94afb06d31cb..1154a120f9137e 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -3,6 +3,6 @@ "name": "Atag", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/atag/", - "requirements": ["pyatag==0.3.4.4"], + "requirements": ["pyatag==0.3.5.3"], "codeowners": ["@MatsNL"] } diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index d6abe16ffdb7b3..3123e245711d89 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -26,10 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Initialize sensor platform from config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - entities = [] - for sensor in SENSORS: - entities.append(AtagSensor(coordinator, sensor)) - async_add_entities(entities) + async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS]) class AtagSensor(AtagEntity): @@ -43,32 +40,32 @@ def __init__(self, coordinator, sensor): @property def state(self): """Return the state of the sensor.""" - return self.coordinator.data[self._id].state + return self.coordinator.data.report[self._id].state @property def icon(self): """Return icon.""" - return self.coordinator.data[self._id].icon + return self.coordinator.data.report[self._id].icon @property def device_class(self): """Return deviceclass.""" - if self.coordinator.data[self._id].sensorclass in [ + if self.coordinator.data.report[self._id].sensorclass in [ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, ]: - return self.coordinator.data[self._id].sensorclass + return self.coordinator.data.report[self._id].sensorclass return None @property def unit_of_measurement(self): """Return measure.""" - if self.coordinator.data[self._id].measure in [ + if self.coordinator.data.report[self._id].measure in [ PRESSURE_BAR, TEMP_CELSIUS, TEMP_FAHRENHEIT, PERCENTAGE, TIME_HOURS, ]: - return self.coordinator.data[self._id].measure + return self.coordinator.data.report[self._id].measure return None diff --git a/homeassistant/components/atag/strings.json b/homeassistant/components/atag/strings.json index b06e9188b5b9b4..39ed972524dfc1 100644 --- a/homeassistant/components/atag/strings.json +++ b/homeassistant/components/atag/strings.json @@ -5,7 +5,6 @@ "title": "Connect to the device", "data": { "host": "[%key:common::config_flow::data::host%]", - "email": "[%key:common::config_flow::data::email%]", "port": "[%key:common::config_flow::data::port%]" } } diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index f9c2a4625bb7d1..dac56edf89d599 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -35,12 +35,12 @@ def temperature_unit(self): @property def current_temperature(self): """Return the current temperature.""" - return self.coordinator.atag.dhw.temperature + return self.coordinator.data.dhw.temperature @property def current_operation(self): """Return current operation.""" - operation = self.coordinator.atag.dhw.current_operation + operation = self.coordinator.data.dhw.current_operation return operation if operation in self.operation_list else STATE_OFF @property @@ -50,20 +50,20 @@ def operation_list(self): async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - if await self.coordinator.atag.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)): + if await self.coordinator.data.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)): self.async_write_ha_state() @property def target_temperature(self): """Return the setpoint if water demand, otherwise return base temp (comfort level).""" - return self.coordinator.atag.dhw.target_temperature + return self.coordinator.data.dhw.target_temperature @property def max_temp(self): """Return the maximum temperature.""" - return self.coordinator.atag.dhw.max_temp + return self.coordinator.data.dhw.max_temp @property def min_temp(self): """Return the minimum temperature.""" - return self.coordinator.atag.dhw.min_temp + return self.coordinator.data.dhw.min_temp diff --git a/requirements_all.txt b/requirements_all.txt index 5f4e3a67e5b53d..2a532ee9ae8f6e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1269,7 +1269,7 @@ pyalmond==0.0.2 pyarlo==0.2.4 # homeassistant.components.atag -pyatag==0.3.4.4 +pyatag==0.3.5.3 # homeassistant.components.netatmo pyatmo==4.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 96f6d6bb4ef1e9..1dab1e8837b80b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -667,7 +667,7 @@ pyalmond==0.0.2 pyarlo==0.2.4 # homeassistant.components.atag -pyatag==0.3.4.4 +pyatag==0.3.5.3 # homeassistant.components.netatmo pyatmo==4.2.2 diff --git a/tests/components/atag/__init__.py b/tests/components/atag/__init__.py index 52d53ee99480cb..c41632b9715586 100644 --- a/tests/components/atag/__init__.py +++ b/tests/components/atag/__init__.py @@ -1,7 +1,7 @@ """Tests for the Atag integration.""" -from homeassistant.components.atag import DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON +from homeassistant.components.atag import DOMAIN, AtagException +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -9,12 +9,15 @@ USER_INPUT = { CONF_HOST: "127.0.0.1", - CONF_EMAIL: "atag@domain.com", CONF_PORT: 10000, } UID = "xxxx-xxxx-xxxx_xx-xx-xxx-xxx" -PAIR_REPLY = {"pair_reply": {"status": {"device_id": UID}, "acc_status": 2}} -UPDATE_REPLY = {"update_reply": {"status": {"device_id": UID}, "acc_status": 2}} +AUTHORIZED = 2 +UNAUTHORIZED = 3 +PAIR_REPLY = {"pair_reply": {"status": {"device_id": UID}, "acc_status": AUTHORIZED}} +UPDATE_REPLY = { + "update_reply": {"status": {"device_id": UID}, "acc_status": AUTHORIZED} +} RECEIVE_REPLY = { "retrieve_reply": { "status": {"device_id": UID}, @@ -46,35 +49,52 @@ "dhw_max_set": 65, "dhw_min_set": 40, }, - "acc_status": 2, + "acc_status": AUTHORIZED, } } -async def init_integration( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, - rgbw: bool = False, - skip_setup: bool = False, -) -> MockConfigEntry: - """Set up the Atag integration in Home Assistant.""" - +def mock_connection( + aioclient_mock: AiohttpClientMocker, authorized=True, conn_error=False +) -> None: + """Mock the requests to Atag endpoint.""" + if conn_error: + aioclient_mock.post( + "http://127.0.0.1:10000/pair", + exc=AtagException, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/retrieve", + exc=AtagException, + ) + return + PAIR_REPLY["pair_reply"].update( + {"acc_status": AUTHORIZED if authorized else UNAUTHORIZED} + ) + RECEIVE_REPLY["retrieve_reply"].update( + {"acc_status": AUTHORIZED if authorized else UNAUTHORIZED} + ) aioclient_mock.post( "http://127.0.0.1:10000/retrieve", json=RECEIVE_REPLY, - headers={"Content-Type": CONTENT_TYPE_JSON}, ) aioclient_mock.post( "http://127.0.0.1:10000/update", json=UPDATE_REPLY, - headers={"Content-Type": CONTENT_TYPE_JSON}, ) aioclient_mock.post( "http://127.0.0.1:10000/pair", json=PAIR_REPLY, - headers={"Content-Type": CONTENT_TYPE_JSON}, ) + +async def init_integration( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + skip_setup: bool = False, +) -> MockConfigEntry: + """Set up the Atag integration in Home Assistant.""" + mock_connection(aioclient_mock) entry = MockConfigEntry(domain=DOMAIN, data=USER_INPUT) entry.add_to_hass(hass) diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py index e263ade7c75aa0..3c9a9c3f820a6f 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -1,7 +1,7 @@ """Tests for the Atag climate platform.""" from unittest.mock import PropertyMock, patch -from homeassistant.components.atag import CLIMATE, DOMAIN +from homeassistant.components.atag.climate import CLIMATE, DOMAIN, PRESET_MAP from homeassistant.components.climate import ( ATTR_HVAC_ACTION, ATTR_HVAC_MODE, @@ -11,11 +11,8 @@ SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, ) -from homeassistant.components.climate.const import CURRENT_HVAC_HEAT, PRESET_AWAY -from homeassistant.components.homeassistant import ( - DOMAIN as HA_DOMAIN, - SERVICE_UPDATE_ENTITY, -) +from homeassistant.components.climate.const import CURRENT_HVAC_IDLE, PRESET_AWAY +from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -31,17 +28,13 @@ async def test_climate( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the creation and values of Atag climate device.""" - with patch("pyatag.entities.Climate.status"): - entry = await init_integration(hass, aioclient_mock) - registry = er.async_get(hass) + await init_integration(hass, aioclient_mock) + entity_registry = er.async_get(hass) - assert registry.async_is_registered(CLIMATE_ID) - entry = registry.async_get(CLIMATE_ID) - assert entry.unique_id == f"{UID}-{CLIMATE}" - assert ( - hass.states.get(CLIMATE_ID).attributes[ATTR_HVAC_ACTION] - == CURRENT_HVAC_HEAT - ) + assert entity_registry.async_is_registered(CLIMATE_ID) + entity = entity_registry.async_get(CLIMATE_ID) + assert entity.unique_id == f"{UID}-{CLIMATE}" + assert hass.states.get(CLIMATE_ID).attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE async def test_setting_climate( @@ -67,7 +60,7 @@ async def test_setting_climate( blocking=True, ) await hass.async_block_till_done() - mock_set_preset.assert_called_once_with(PRESET_AWAY) + mock_set_preset.assert_called_once_with(PRESET_MAP[PRESET_AWAY]) with patch("pyatag.entities.Climate.set_hvac_mode") as mock_set_hvac: await hass.services.async_call( @@ -93,18 +86,18 @@ async def test_incorrect_modes( assert hass.states.get(CLIMATE_ID).state == STATE_UNKNOWN -async def test_update_service( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +async def test_update_failed( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, ) -> None: - """Test the updater service is called.""" - await init_integration(hass, aioclient_mock) + """Test data is not destroyed on update failure.""" + entry = await init_integration(hass, aioclient_mock) await async_setup_component(hass, HA_DOMAIN, {}) - with patch("pyatag.AtagOne.update") as updater: - await hass.services.async_call( - HA_DOMAIN, - SERVICE_UPDATE_ENTITY, - {ATTR_ENTITY_ID: CLIMATE_ID}, - blocking=True, - ) + assert hass.states.get(CLIMATE_ID).state == HVAC_MODE_HEAT + coordinator = hass.data[DOMAIN][entry.entry_id] + with patch("pyatag.AtagOne.update", side_effect=TimeoutError) as updater: + await coordinator.async_refresh() await hass.async_block_till_done() updater.assert_called_once() + assert not coordinator.last_update_success + assert coordinator.data.id == UID diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index 81375792c711f4..a92e73ae18e4bc 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -1,24 +1,18 @@ """Tests for the Atag config flow.""" from unittest.mock import PropertyMock, patch -from pyatag import errors - from homeassistant import config_entries, data_entry_flow from homeassistant.components.atag import DOMAIN from homeassistant.core import HomeAssistant -from tests.components.atag import ( - PAIR_REPLY, - RECEIVE_REPLY, - UID, - USER_INPUT, - init_integration, -) +from . import UID, USER_INPUT, init_integration, mock_connection + from tests.test_util.aiohttp import AiohttpClientMocker -async def test_show_form(hass): +async def test_show_form(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): """Test that the form is served with no input.""" + mock_connection(aioclient_mock) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -48,28 +42,30 @@ async def test_adding_second_device( assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY -async def test_connection_error(hass): +async def test_connection_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +): """Test we show user form on Atag connection error.""" - with patch("pyatag.AtagOne.authorize", side_effect=errors.AtagException()): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data=USER_INPUT, - ) + mock_connection(aioclient_mock, conn_error=True) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=USER_INPUT, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "cannot_connect"} -async def test_unauthorized(hass): +async def test_unauthorized(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): """Test we show correct form when Unauthorized error is raised.""" - with patch("pyatag.AtagOne.authorize", side_effect=errors.Unauthorized()): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data=USER_INPUT, - ) + mock_connection(aioclient_mock, authorized=False) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data=USER_INPUT, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "unauthorized"} @@ -79,14 +75,7 @@ async def test_full_flow_implementation( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test registering an integration and finishing flow works.""" - aioclient_mock.post( - "http://127.0.0.1:10000/pair", - json=PAIR_REPLY, - ) - aioclient_mock.post( - "http://127.0.0.1:10000/retrieve", - json=RECEIVE_REPLY, - ) + mock_connection(aioclient_mock) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, diff --git a/tests/components/atag/test_init.py b/tests/components/atag/test_init.py index b86de8a8be5ee5..7b7f3c1e33a416 100644 --- a/tests/components/atag/test_init.py +++ b/tests/components/atag/test_init.py @@ -1,13 +1,11 @@ """Tests for the ATAG integration.""" -from unittest.mock import patch - -import aiohttp from homeassistant.components.atag import DOMAIN from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from homeassistant.core import HomeAssistant -from tests.components.atag import init_integration +from . import init_integration, mock_connection + from tests.test_util.aiohttp import AiohttpClientMocker @@ -15,20 +13,11 @@ async def test_config_entry_not_ready( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test configuration entry not ready on library error.""" - aioclient_mock.post("http://127.0.0.1:10000/retrieve", exc=aiohttp.ClientError) + mock_connection(aioclient_mock, conn_error=True) entry = await init_integration(hass, aioclient_mock) assert entry.state == ENTRY_STATE_SETUP_RETRY -async def test_config_entry_empty_reply( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test configuration entry not ready when library returns False.""" - with patch("pyatag.AtagOne.update", return_value=False): - entry = await init_integration(hass, aioclient_mock) - assert entry.state == ENTRY_STATE_SETUP_RETRY - - async def test_unload_config_entry( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: From 2a22c54fcb5ef299e4d274a537922c14115ce447 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 11 Mar 2021 23:12:26 -0800 Subject: [PATCH 1208/1818] Store the correct context in the trace (#47785) --- homeassistant/components/automation/__init__.py | 10 +++++----- tests/components/automation/test_websocket_api.py | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 24121359ac06b2..862a664976b4e8 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -390,8 +390,12 @@ async def async_trigger(self, run_variables, context=None, skip_condition=False) reason = f' by {run_variables["trigger"]["description"]}' self._logger.debug("Automation triggered%s", reason) + # Create a new context referring to the old context. + parent_id = None if context is None else context.id + trigger_context = Context(parent_id=parent_id) + with trace_automation( - self.hass, self.unique_id, self._raw_config, context + self.hass, self.unique_id, self._raw_config, trigger_context ) as automation_trace: if self._variables: try: @@ -421,10 +425,6 @@ async def async_trigger(self, run_variables, context=None, skip_condition=False) # Prepare tracing the execution of the automation's actions automation_trace.set_action_trace(trace_get()) - # Create a new context referring to the old context. - parent_id = None if context is None else context.id - trigger_context = Context(parent_id=parent_id) - self.async_set_context(trigger_context) event_data = { ATTR_NAME: self._name, diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 106f687f4eed3e..99b9540b06ef97 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -3,6 +3,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import automation, config +from homeassistant.core import Context from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 @@ -52,7 +53,8 @@ def next_id(): client = await hass_ws_client() # Trigger "sun" automation - hass.bus.async_fire("test_event") + context = Context() + hass.bus.async_fire("test_event", context=context) await hass.async_block_till_done() # List traces @@ -73,6 +75,7 @@ def next_id(): response = await client.receive_json() assert response["success"] trace = response["result"] + assert trace["context"]["parent_id"] == context.id assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert trace["action_trace"]["action/0"][0]["error"] From ff94e920e4e436c874f3f8de115792eaeac99b67 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 11 Mar 2021 23:18:09 -0800 Subject: [PATCH 1209/1818] Do not use AsyncTrackStates (#47255) --- homeassistant/components/api/__init__.py | 24 +++++++++++------- homeassistant/helpers/state.py | 10 +++++++- tests/components/api/test_init.py | 20 +++++++++------ tests/helpers/conftest.py | 31 ++++++++++++++++++++++++ tests/helpers/test_frame.py | 27 +++------------------ tests/helpers/test_state.py | 4 +-- 6 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 tests/helpers/conftest.py diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index e40a9332c38459..47c6518f7b118e 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -37,7 +37,6 @@ from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.state import AsyncTrackStates from homeassistant.helpers.system_info import async_get_system_info _LOGGER = logging.getLogger(__name__) @@ -367,20 +366,27 @@ async def post(self, request, domain, service): Returns a list of changed states. """ - hass = request.app["hass"] + hass: ha.HomeAssistant = request.app["hass"] body = await request.text() try: data = json.loads(body) if body else None except ValueError: return self.json_message("Data should be valid JSON.", HTTP_BAD_REQUEST) - with AsyncTrackStates(hass) as changed_states: - try: - await hass.services.async_call( - domain, service, data, blocking=True, context=self.context(request) - ) - except (vol.Invalid, ServiceNotFound) as ex: - raise HTTPBadRequest() from ex + context = self.context(request) + + try: + await hass.services.async_call( + domain, service, data, blocking=True, context=context + ) + except (vol.Invalid, ServiceNotFound) as ex: + raise HTTPBadRequest() from ex + + changed_states = [] + + for state in hass.states.async_all(): + if state.context is context: + changed_states.append(state) return self.json(changed_states) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 87112cd913383d..eda026c8563875 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -22,6 +22,7 @@ from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass import homeassistant.util.dt as dt_util +from .frame import report from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -35,6 +36,9 @@ class AsyncTrackStates: when with-block is exited. Must be run within the event loop. + + Deprecated. Remove after June 2021. + Warning added via `get_changed_since`. """ def __init__(self, hass: HomeAssistantType) -> None: @@ -61,7 +65,11 @@ def __exit__( def get_changed_since( states: Iterable[State], utc_point_in_time: dt.datetime ) -> List[State]: - """Return list of states that have been changed since utc_point_in_time.""" + """Return list of states that have been changed since utc_point_in_time. + + Deprecated. Remove after June 2021. + """ + report("uses deprecated `get_changed_since`") return [state for state in states if state.last_updated >= utc_point_in_time] diff --git a/tests/components/api/test_init.py b/tests/components/api/test_init.py index 678a8096af5129..ffda908a29b193 100644 --- a/tests/components/api/test_init.py +++ b/tests/components/api/test_init.py @@ -270,7 +270,6 @@ def listener(service_call): async def test_api_call_service_with_data(hass, mock_api_client): """Test if the API allows us to call a service.""" - test_value = [] @ha.callback def listener(service_call): @@ -278,17 +277,24 @@ def listener(service_call): Also test if our data came through. """ - if "test" in service_call.data: - test_value.append(1) + hass.states.async_set( + "test.data", + "on", + {"data": service_call.data["test"]}, + context=service_call.context, + ) hass.services.async_register("test_domain", "test_service", listener) - await mock_api_client.post( + resp = await mock_api_client.post( "/api/services/test_domain/test_service", json={"test": 1} ) - - await hass.async_block_till_done() - assert len(test_value) == 1 + data = await resp.json() + assert len(data) == 1 + state = data[0] + assert state["entity_id"] == "test.data" + assert state["state"] == "on" + assert state["attributes"] == {"data": 1} async def test_api_template(hass, mock_api_client): diff --git a/tests/helpers/conftest.py b/tests/helpers/conftest.py new file mode 100644 index 00000000000000..4b3b9bf465d1f0 --- /dev/null +++ b/tests/helpers/conftest.py @@ -0,0 +1,31 @@ +"""Fixtures for helpers.""" +from unittest.mock import Mock, patch + +import pytest + + +@pytest.fixture +def mock_integration_frame(): + """Mock as if we're calling code from inside an integration.""" + correct_frame = Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ) + with patch( + "homeassistant.helpers.frame.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ], + ): + yield correct_frame diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index 7fc46b3699d8ab..b198a16adb1958 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -6,34 +6,13 @@ from homeassistant.helpers import frame -async def test_extract_frame_integration(caplog): +async def test_extract_frame_integration(caplog, mock_integration_frame): """Test extracting the current frame from integration context.""" - correct_frame = Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ) - with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], - ): - found_frame, integration, path = frame.get_integration_frame() + found_frame, integration, path = frame.get_integration_frame() assert integration == "hue" assert path == "homeassistant/components/" - assert found_frame == correct_frame + assert found_frame == mock_integration_frame async def test_extract_frame_integration_with_excluded_intergration(caplog): diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 89b0f3c685088f..aa1d148fbd7ec8 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -25,7 +25,7 @@ from tests.common import async_mock_service -async def test_async_track_states(hass): +async def test_async_track_states(hass, mock_integration_frame): """Test AsyncTrackStates context manager.""" point1 = dt_util.utcnow() point2 = point1 + timedelta(seconds=5) @@ -82,7 +82,7 @@ async def test_call_to_component(hass): ) -async def test_get_changed_since(hass): +async def test_get_changed_since(hass, mock_integration_frame): """Test get_changed_since.""" point1 = dt_util.utcnow() point2 = point1 + timedelta(seconds=5) From 6610e821bcb3217a38abec2d11ccc4d2c2191b82 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Fri, 12 Mar 2021 11:37:39 +0100 Subject: [PATCH 1210/1818] Bump devolo_home_control_api to 0.17.0 (#47790) --- homeassistant/components/devolo_home_control/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/devolo_home_control/manifest.json b/homeassistant/components/devolo_home_control/manifest.json index 0ffa991493cca4..1ae8e9bbb59b09 100644 --- a/homeassistant/components/devolo_home_control/manifest.json +++ b/homeassistant/components/devolo_home_control/manifest.json @@ -2,7 +2,7 @@ "domain": "devolo_home_control", "name": "devolo Home Control", "documentation": "https://www.home-assistant.io/integrations/devolo_home_control", - "requirements": ["devolo-home-control-api==0.16.0"], + "requirements": ["devolo-home-control-api==0.17.0"], "after_dependencies": ["zeroconf"], "config_flow": true, "codeowners": ["@2Fake", "@Shutgun"], diff --git a/requirements_all.txt b/requirements_all.txt index 2a532ee9ae8f6e..bcea3b679b8561 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -476,7 +476,7 @@ deluge-client==1.7.1 denonavr==0.9.10 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.16.0 +devolo-home-control-api==0.17.0 # homeassistant.components.directv directv==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1dab1e8837b80b..1a9c1bd7b9d9cc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -258,7 +258,7 @@ defusedxml==0.6.0 denonavr==0.9.10 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.16.0 +devolo-home-control-api==0.17.0 # homeassistant.components.directv directv==0.4.0 From 40c28aa38a883dac47eab1e9a6e2f539f3d5854f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 12 Mar 2021 00:41:01 -1000 Subject: [PATCH 1211/1818] Ensure homekit reset accessory service can target any entity (#47787) --- homeassistant/components/homekit/services.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/homekit/services.yaml b/homeassistant/components/homekit/services.yaml index 6f9c005ed641da..a6b09a80e7ffe9 100644 --- a/homeassistant/components/homekit/services.yaml +++ b/homeassistant/components/homekit/services.yaml @@ -9,3 +9,5 @@ reload: reset_accessory: description: Reset a HomeKit accessory target: + entity: {} + From 514516baccd8fe6a1a566db633247c2eaef51874 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Mar 2021 13:52:46 +0100 Subject: [PATCH 1212/1818] Remove unused COVER_SCHEMA from gogogate2 cover (#47170) --- homeassistant/components/gogogate2/cover.py | 28 ++++++--------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 053c35c171ad7d..2d97edbfe43bd0 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,8 +1,8 @@ """Support for Gogogate2 garage Doors.""" +import logging from typing import Callable, List, Optional from gogogate2_api.common import AbstractDoor, DoorStatus, get_configured_doors -import voluptuous as vol from homeassistant.components.cover import ( DEVICE_CLASS_GARAGE, @@ -12,14 +12,7 @@ CoverEntity, ) from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_DEVICE, - CONF_IP_ADDRESS, - CONF_PASSWORD, - CONF_USERNAME, -) from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from .common import ( @@ -27,24 +20,19 @@ GoGoGate2Entity, get_data_update_coordinator, ) -from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN - -COVER_SCHEMA = vol.Schema( - { - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Required(CONF_DEVICE, default=DEVICE_TYPE_GOGOGATE2): vol.In( - (DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE) - ), - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - } -) +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) async def async_setup_platform( hass: HomeAssistant, config: dict, add_entities: Callable, discovery_info=None ) -> None: """Convert old style file configs to new style configs.""" + _LOGGER.warning( + "Loading gogogate2 via platform config is deprecated. The configuration" + " has been migrated to a config entry and can be safely removed." + ) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config From bf5028df2bf5d69c97c3f697e32e8563209e5c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Fri, 12 Mar 2021 16:19:14 +0100 Subject: [PATCH 1213/1818] Bump pyatv to 0.7.7 (#47798) * Bump pyatv to 0.7.7 * Change to assume name always exist in config entry --- homeassistant/components/apple_tv/__init__.py | 7 ++++--- homeassistant/components/apple_tv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 75c4d6ccc8affb..6460a501992309 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -181,7 +181,7 @@ def connection_lost(self, _): This is a callback function from pyatv.interface.DeviceListener. """ _LOGGER.warning( - 'Connection lost to Apple TV "%s"', self.config_entry.data.get(CONF_NAME) + 'Connection lost to Apple TV "%s"', self.config_entry.data[CONF_NAME] ) self._connection_was_lost = True self._handle_disconnect() @@ -268,7 +268,7 @@ def _auth_problem(self): """Problem to authenticate occurred that needs intervention.""" _LOGGER.debug("Authentication error, reconfigure integration") - name = self.config_entry.data.get(CONF_NAME) + name = self.config_entry.data[CONF_NAME] identifier = self.config_entry.unique_id self.hass.components.persistent_notification.create( @@ -337,7 +337,8 @@ async def _connect(self, conf): self._connection_attempts = 0 if self._connection_was_lost: _LOGGER.info( - 'Connection was re-established to Apple TV "%s"', self.atv.service.name + 'Connection was re-established to Apple TV "%s"', + self.config_entry.data[CONF_NAME], ) self._connection_was_lost = False diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index 66ae2864dc456a..a60c5db3a06309 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/apple_tv", "requirements": [ - "pyatv==0.7.6" + "pyatv==0.7.7" ], "zeroconf": [ "_mediaremotetv._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index bcea3b679b8561..a6a280a5311f27 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1278,7 +1278,7 @@ pyatmo==4.2.2 pyatome==0.1.1 # homeassistant.components.apple_tv -pyatv==0.7.6 +pyatv==0.7.7 # homeassistant.components.bbox pybbox==0.0.5-alpha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1a9c1bd7b9d9cc..b89ac61932005e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -673,7 +673,7 @@ pyatag==0.3.5.3 pyatmo==4.2.2 # homeassistant.components.apple_tv -pyatv==0.7.6 +pyatv==0.7.7 # homeassistant.components.blackbird pyblackbird==0.5 From 04b335afe9427f4a4b2f9c03d3e403e64c97d4ce Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 12 Mar 2021 09:04:02 -0800 Subject: [PATCH 1214/1818] Allow filtering the logbook by context_id (#47783) --- homeassistant/components/logbook/__init__.py | 15 ++++++ tests/components/logbook/test_init.py | 50 +++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 3b77e6e6409cc2..44c9171f244aa1 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -231,6 +231,12 @@ async def get(self, request, datetime=None): hass = request.app["hass"] entity_matches_only = "entity_matches_only" in request.query + context_id = request.query.get("context_id") + + if entity_ids and context_id: + return self.json_message( + "Can't combine entity with context_id", HTTP_BAD_REQUEST + ) def json_events(): """Fetch events and generate JSON.""" @@ -243,6 +249,7 @@ def json_events(): self.filters, self.entities_filter, entity_matches_only, + context_id, ) ) @@ -413,8 +420,13 @@ def _get_events( filters=None, entities_filter=None, entity_matches_only=False, + context_id=None, ): """Get events for a period of time.""" + assert not ( + entity_ids and context_id + ), "can't pass in both entity_ids and context_id" + entity_attr_cache = EntityAttributeCache(hass) context_lookup = {None: None} @@ -466,6 +478,9 @@ def yield_events(query): filters.entity_filter() | (Events.event_type != EVENT_STATE_CHANGED) ) + if context_id is not None: + query = query.filter(Events.context_id == context_id) + query = query.order_by(Events.time_fired) return list( diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index cd3fb519dedc09..3dab7e6c2fbd18 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1801,17 +1801,52 @@ async def test_empty_config(hass, hass_client): _assert_entry(entries[1], name="blu", entity_id=entity_id) -async def _async_fetch_logbook(client): +async def test_context_filter(hass, hass_client): + """Test we can filter by context.""" + await hass.async_add_executor_job(init_recorder_component, hass) + assert await async_setup_component(hass, "logbook", {}) + await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + entity_id = "switch.blu" + context = ha.Context() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + hass.states.async_set(entity_id, None) + hass.states.async_set(entity_id, "on", context=context) + hass.states.async_set(entity_id, "off") + hass.states.async_set(entity_id, "unknown", context=context) + + await _async_commit_and_wait(hass) + client = await hass_client() + + # Test results + entries = await _async_fetch_logbook(client, {"context_id": context.id}) + + assert len(entries) == 2 + _assert_entry(entries[0], entity_id=entity_id, state="on") + _assert_entry(entries[1], entity_id=entity_id, state="unknown") + + # Test we can't combine context filter with entity_id filter + response = await client.get( + "/api/logbook", params={"context_id": context.id, "entity": entity_id} + ) + assert response.status == 400 + + +async def _async_fetch_logbook(client, params=None): + if params is None: + params = {} # Today time 00:00:00 start = dt_util.utcnow().date() start_date = datetime(start.year, start.month, start.day) - timedelta(hours=24) + if "end_time" not in params: + params["end_time"] = str(start + timedelta(hours=48)) + # Test today entries without filters - end_time = start + timedelta(hours=48) - response = await client.get( - f"/api/logbook/{start_date.isoformat()}?end_time={end_time}" - ) + response = await client.get(f"/api/logbook/{start_date.isoformat()}", params=params) assert response.status == 200 return await response.json() @@ -1825,7 +1860,7 @@ async def _async_commit_and_wait(hass): def _assert_entry( - entry, when=None, name=None, message=None, domain=None, entity_id=None + entry, when=None, name=None, message=None, domain=None, entity_id=None, state=None ): """Assert an entry is what is expected.""" if when: @@ -1843,6 +1878,9 @@ def _assert_entry( if entity_id: assert entity_id == entry["entity_id"] + if state: + assert state == entry["state"] + class MockLazyEventPartialState(ha.Event): """Minimal mock of a Lazy event.""" From 72cb1f54802e8c1943e758fc27585233fea9c91d Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Fri, 12 Mar 2021 18:19:55 +0100 Subject: [PATCH 1215/1818] Add ambient sensors to nut integration (#47411) --- homeassistant/components/nut/const.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index cc70b33f7633b1..b8b315df59e24e 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -1,6 +1,7 @@ """The nut component.""" from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ) @@ -45,7 +46,7 @@ "ups.temperature": [ "UPS Temperature", TEMP_CELSIUS, - "mdi:thermometer", + None, DEVICE_CLASS_TEMPERATURE, ], "ups.load": ["Load", PERCENTAGE, "mdi:gauge", None], @@ -83,13 +84,13 @@ "ups.realpower": [ "Current Real Power", POWER_WATT, - "mdi:flash", + None, DEVICE_CLASS_POWER, ], "ups.realpower.nominal": [ "Nominal Real Power", POWER_WATT, - "mdi:flash", + None, DEVICE_CLASS_POWER, ], "ups.beeper.status": ["Beeper Status", "", "mdi:information-outline", None], @@ -102,7 +103,7 @@ "battery.charge": [ "Battery Charge", PERCENTAGE, - "mdi:gauge", + None, DEVICE_CLASS_BATTERY, ], "battery.charge.low": ["Low Battery Setpoint", PERCENTAGE, "mdi:gauge", None], @@ -139,7 +140,7 @@ "battery.temperature": [ "Battery Temperature", TEMP_CELSIUS, - "mdi:thermometer", + None, DEVICE_CLASS_TEMPERATURE, ], "battery.runtime": ["Battery Runtime", TIME_SECONDS, "mdi:timer-outline", None], @@ -216,6 +217,18 @@ "mdi:flash", None, ], + "ambient.humidity": [ + "Ambient Humidity", + PERCENTAGE, + None, + DEVICE_CLASS_HUMIDITY, + ], + "ambient.temperature": [ + "Ambient Temperature", + TEMP_CELSIUS, + None, + DEVICE_CLASS_TEMPERATURE, + ], } STATE_TYPES = { From 3115bf9aaba476b69b82c75fab0ac1930ff31d26 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Mar 2021 19:04:56 +0100 Subject: [PATCH 1216/1818] Add temperature sensor for gogogate2 wireless door sensor (#47754) * Add temperature sensor for gogogate2 wireless door sensor * Chain sensor generators --- homeassistant/components/gogogate2/common.py | 10 ++- homeassistant/components/gogogate2/cover.py | 4 +- homeassistant/components/gogogate2/sensor.py | 85 +++++++++++++++++-- tests/components/gogogate2/test_sensor.py | 88 ++++++++++++++------ 4 files changed, 152 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 761f92119218f0..404a22e33124b4 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -69,12 +69,13 @@ def __init__( config_entry: ConfigEntry, data_update_coordinator: DeviceDataUpdateCoordinator, door: AbstractDoor, + unique_id: str, ) -> None: """Initialize gogogate2 base entity.""" super().__init__(data_update_coordinator) self._config_entry = config_entry self._door = door - self._unique_id = cover_unique_id(config_entry, door) + self._unique_id = unique_id @property def unique_id(self) -> Optional[str]: @@ -137,6 +138,13 @@ def cover_unique_id(config_entry: ConfigEntry, door: AbstractDoor) -> str: return f"{config_entry.unique_id}_{door.door_id}" +def sensor_unique_id( + config_entry: ConfigEntry, door: AbstractDoor, sensor_type: str +) -> str: + """Generate a cover entity unique id.""" + return f"{config_entry.unique_id}_{door.door_id}_{sensor_type}" + + def get_api(config_data: dict) -> AbstractGateApi: """Get an api object for config data.""" gate_class = GogoGate2Api diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 2d97edbfe43bd0..9a1b53f7bee066 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -18,6 +18,7 @@ from .common import ( DeviceDataUpdateCoordinator, GoGoGate2Entity, + cover_unique_id, get_data_update_coordinator, ) from .const import DOMAIN @@ -66,7 +67,8 @@ def __init__( door: AbstractDoor, ) -> None: """Initialize the object.""" - super().__init__(config_entry, data_update_coordinator, door) + unique_id = cover_unique_id(config_entry, door) + super().__init__(config_entry, data_update_coordinator, door, unique_id) self._api = data_update_coordinator.api self._is_available = True diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index ed53779a95aaf3..eea557639ad353 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -1,14 +1,24 @@ """Support for Gogogate2 garage Doors.""" +from itertools import chain from typing import Callable, List, Optional -from gogogate2_api.common import get_configured_doors +from gogogate2_api.common import AbstractDoor, get_configured_doors from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DEVICE_CLASS_BATTERY +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity -from .common import GoGoGate2Entity, get_data_update_coordinator +from .common import ( + DeviceDataUpdateCoordinator, + GoGoGate2Entity, + get_data_update_coordinator, + sensor_unique_id, +) SENSOR_ID_WIRED = "WIRE" @@ -21,17 +31,33 @@ async def async_setup_entry( """Set up the config entry.""" data_update_coordinator = get_data_update_coordinator(hass, config_entry) - async_add_entities( + sensors = chain( [ - DoorSensor(config_entry, data_update_coordinator, door) + DoorSensorBattery(config_entry, data_update_coordinator, door) for door in get_configured_doors(data_update_coordinator.data) if door.sensorid and door.sensorid != SENSOR_ID_WIRED - ] + ], + [ + DoorSensorTemperature(config_entry, data_update_coordinator, door) + for door in get_configured_doors(data_update_coordinator.data) + if door.sensorid and door.sensorid != SENSOR_ID_WIRED + ], ) + async_add_entities(sensors) + +class DoorSensorBattery(GoGoGate2Entity): + """Battery sensor entity for gogogate2 door sensor.""" -class DoorSensor(GoGoGate2Entity): - """Sensor entity for goggate2.""" + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: DeviceDataUpdateCoordinator, + door: AbstractDoor, + ) -> None: + """Initialize the object.""" + unique_id = sensor_unique_id(config_entry, door, "battery") + super().__init__(config_entry, data_update_coordinator, door, unique_id) @property def name(self): @@ -56,3 +82,46 @@ def extra_state_attributes(self): if door.sensorid is not None: return {"door_id": door.door_id, "sensor_id": door.sensorid} return None + + +class DoorSensorTemperature(GoGoGate2Entity): + """Temperature sensor entity for gogogate2 door sensor.""" + + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: DeviceDataUpdateCoordinator, + door: AbstractDoor, + ) -> None: + """Initialize the object.""" + unique_id = sensor_unique_id(config_entry, door, "temperature") + super().__init__(config_entry, data_update_coordinator, door, unique_id) + + @property + def name(self): + """Return the name of the door.""" + return f"{self._get_door().name} temperature" + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_TEMPERATURE + + @property + def state(self): + """Return the state of the entity.""" + door = self._get_door() + return door.temperature + + @property + def unit_of_measurement(self): + """Return the unit_of_measurement.""" + return TEMP_CELSIUS + + @property + def device_state_attributes(self): + """Return the state attributes.""" + door = self._get_door() + if door.sensorid is not None: + return {"door_id": door.door_id, "sensor_id": door.sensorid} + return None diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py index f8eb9bf88b73ff..020989c003adb7 100644 --- a/tests/components/gogogate2/test_sensor.py +++ b/tests/components/gogogate2/test_sensor.py @@ -20,11 +20,13 @@ from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( ATTR_DEVICE_CLASS, + ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICE, CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_TEMPERATURE, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -34,7 +36,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed -def _mocked_gogogate_sensor_response(battery_level: int): +def _mocked_gogogate_sensor_response(battery_level: int, temperature: float): return GogoGate2InfoResponse( user="user1", gogogatename="gogogatename0", @@ -55,7 +57,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): sensorid="ABCD", camera=False, events=2, - temperature=None, + temperature=temperature, voltage=battery_level, ), door2=GogoGate2Door( @@ -69,7 +71,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): sensorid="WIRE", camera=False, events=0, - temperature=None, + temperature=temperature, voltage=battery_level, ), door3=GogoGate2Door( @@ -83,7 +85,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): sensorid=None, camera=False, events=0, - temperature=None, + temperature=temperature, voltage=battery_level, ), outputs=Outputs(output1=True, output2=False, output3=True), @@ -92,7 +94,7 @@ def _mocked_gogogate_sensor_response(battery_level: int): ) -def _mocked_ismartgate_sensor_response(battery_level: int): +def _mocked_ismartgate_sensor_response(battery_level: int, temperature: float): return ISmartGateInfoResponse( user="user1", ismartgatename="ismartgatename0", @@ -115,7 +117,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int): sensorid="ABCD", camera=False, events=2, - temperature=None, + temperature=temperature, enabled=True, apicode="apicode0", customimage=False, @@ -132,7 +134,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int): sensorid="WIRE", camera=False, events=2, - temperature=None, + temperature=temperature, enabled=True, apicode="apicode0", customimage=False, @@ -149,7 +151,7 @@ def _mocked_ismartgate_sensor_response(battery_level: int): sensorid=None, camera=False, events=0, - temperature=None, + temperature=temperature, enabled=True, apicode="apicode0", customimage=False, @@ -164,16 +166,23 @@ def _mocked_ismartgate_sensor_response(battery_level: int): async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: """Test data update.""" - expected_attributes = { + bat_attributes = { "device_class": "battery", "door_id": 1, "friendly_name": "Door1 battery", "sensor_id": "ABCD", } + temp_attributes = { + "device_class": "temperature", + "door_id": 1, + "friendly_name": "Door1 temperature", + "sensor_id": "ABCD", + "unit_of_measurement": "°C", + } api = MagicMock(GogoGate2Api) api.async_activate.return_value = GogoGate2ActivateResponse(result=True) - api.async_info.return_value = _mocked_gogogate_sensor_response(25) + api.async_info.return_value = _mocked_gogogate_sensor_response(25, 7.0) gogogate2api_mock.return_value = api config_entry = MockConfigEntry( @@ -189,31 +198,40 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: assert hass.states.get("cover.door1") is None assert hass.states.get("cover.door2") is None - assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door3") is None assert hass.states.get("sensor.door1_battery") is None assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door3_battery") is None + assert hass.states.get("sensor.door1_temperature") is None + assert hass.states.get("sensor.door2_temperature") is None + assert hass.states.get("sensor.door3_temperature") is None assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("cover.door1") assert hass.states.get("cover.door2") - assert hass.states.get("cover.door2") + assert hass.states.get("cover.door3") assert hass.states.get("sensor.door1_battery").state == "25" - assert ( - dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes - ) + assert dict(hass.states.get("sensor.door1_battery").attributes) == bat_attributes assert hass.states.get("sensor.door2_battery") is None assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door1_temperature").state == "7.0" + assert ( + dict(hass.states.get("sensor.door1_temperature").attributes) == temp_attributes + ) + assert hass.states.get("sensor.door2_temperature") is None + assert hass.states.get("sensor.door3_temperature") is None - api.async_info.return_value = _mocked_gogogate_sensor_response(40) + api.async_info.return_value = _mocked_gogogate_sensor_response(40, 10.0) async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == "40" + assert hass.states.get("sensor.door1_temperature").state == "10.0" - api.async_info.return_value = _mocked_gogogate_sensor_response(None) + api.async_info.return_value = _mocked_gogogate_sensor_response(None, None) async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == STATE_UNKNOWN + assert hass.states.get("sensor.door1_temperature").state == STATE_UNKNOWN assert await hass.config_entries.async_unload(config_entry.entry_id) assert not hass.states.async_entity_ids(DOMAIN) @@ -222,14 +240,21 @@ async def test_sensor_update(gogogate2api_mock, hass: HomeAssistant) -> None: @patch("homeassistant.components.gogogate2.common.ISmartGateApi") async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: """Test availability.""" - expected_attributes = { + bat_attributes = { "device_class": "battery", "door_id": 1, "friendly_name": "Door1 battery", "sensor_id": "ABCD", } + temp_attributes = { + "device_class": "temperature", + "door_id": 1, + "friendly_name": "Door1 temperature", + "sensor_id": "ABCD", + "unit_of_measurement": "°C", + } - sensor_response = _mocked_ismartgate_sensor_response(35) + sensor_response = _mocked_ismartgate_sensor_response(35, -4.0) api = MagicMock(ISmartGateApi) api.async_info.return_value = sensor_response ismartgateapi_mock.return_value = api @@ -248,34 +273,47 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None: assert hass.states.get("cover.door1") is None assert hass.states.get("cover.door2") is None - assert hass.states.get("cover.door2") is None + assert hass.states.get("cover.door3") is None assert hass.states.get("sensor.door1_battery") is None assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door3_battery") is None assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.states.get("cover.door1") assert hass.states.get("cover.door2") - assert hass.states.get("cover.door2") + assert hass.states.get("cover.door3") assert hass.states.get("sensor.door1_battery").state == "35" assert hass.states.get("sensor.door2_battery") is None - assert hass.states.get("sensor.door2_battery") is None + assert hass.states.get("sensor.door3_battery") is None + assert hass.states.get("sensor.door1_temperature").state == "-4.0" + assert hass.states.get("sensor.door2_temperature") is None + assert hass.states.get("sensor.door3_temperature") is None assert ( hass.states.get("sensor.door1_battery").attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_BATTERY ) + assert ( + hass.states.get("sensor.door1_temperature").attributes[ATTR_DEVICE_CLASS] + == DEVICE_CLASS_TEMPERATURE + ) + assert ( + hass.states.get("sensor.door1_temperature").attributes[ATTR_UNIT_OF_MEASUREMENT] + == "°C" + ) api.async_info.side_effect = Exception("Error") async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == STATE_UNAVAILABLE + assert hass.states.get("sensor.door1_temperature").state == STATE_UNAVAILABLE api.async_info.side_effect = None api.async_info.return_value = sensor_response async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) await hass.async_block_till_done() assert hass.states.get("sensor.door1_battery").state == "35" + assert dict(hass.states.get("sensor.door1_battery").attributes) == bat_attributes assert ( - dict(hass.states.get("sensor.door1_battery").attributes) == expected_attributes + dict(hass.states.get("sensor.door1_temperature").attributes) == temp_attributes ) From 13cd2f52d88adae5b5456ae3f07e6827054b56e4 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 12 Mar 2021 13:49:59 -0500 Subject: [PATCH 1217/1818] Return property_key in zwave_js get_config_parameters websocket (#47808) --- homeassistant/components/zwave_js/api.py | 1 + tests/components/zwave_js/test_api.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 599183eba7e8fd..055115db7b9e47 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -371,6 +371,7 @@ def websocket_get_config_parameters( metadata = zwave_value.metadata result[value_id] = { "property": zwave_value.property_, + "property_key": zwave_value.property_key, "configuration_value_type": zwave_value.configuration_value_type.value, "metadata": { "description": metadata.description, diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index aa085836b65a0d..dd8679ddf73f93 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -73,10 +73,14 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client): assert len(result) == 61 key = "52-112-0-2" assert result[key]["property"] == 2 + assert result[key]["property_key"] is None assert result[key]["metadata"]["type"] == "number" assert result[key]["configuration_value_type"] == "enumerated" assert result[key]["metadata"]["states"] + key = "52-112-0-201-255" + assert result[key]["property_key"] == 255 + # Test getting non-existent node fails await ws_client.send_json( { From 9a98dcf432ae55d000c58729760a195d010b1943 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Fri, 12 Mar 2021 20:03:47 +0100 Subject: [PATCH 1218/1818] Add HomeKit support for new CO / CO2 device class (#47737) --- homeassistant/components/demo/sensor.py | 19 +++++++++++++++++++ .../components/homekit/accessories.py | 6 +++--- homeassistant/components/homekit/const.py | 2 -- .../components/homekit/type_sensors.py | 4 +++- .../homekit/test_get_accessories.py | 16 ++++++++++++++-- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 49930a35377669..9c005d2d7f5d2c 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,6 +1,9 @@ """Demo platform that has a couple of fake sensors.""" from homeassistant.const import ( ATTR_BATTERY_LEVEL, + CONCENTRATION_PARTS_PER_MILLION, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, @@ -31,6 +34,22 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= PERCENTAGE, None, ), + DemoSensor( + "sensor_3", + "Carbon monoxide", + 54, + DEVICE_CLASS_CO, + CONCENTRATION_PARTS_PER_MILLION, + None, + ), + DemoSensor( + "sensor_4", + "Carbon dioxide", + 54, + DEVICE_CLASS_CO2, + CONCENTRATION_PARTS_PER_MILLION, + 14, + ), ] ) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 7e68daf4b62a14..b6ff11aa26d737 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -22,6 +22,8 @@ ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, @@ -54,8 +56,6 @@ CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, - DEVICE_CLASS_CO, - DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, EVENT_HOMEKIT_CHANGED, HK_CHARGING, @@ -167,7 +167,7 @@ def get_accessory(hass, driver, state, aid, config): a_type = "AirQualitySensor" elif device_class == DEVICE_CLASS_CO: a_type = "CarbonMonoxideSensor" - elif device_class == DEVICE_CLASS_CO2 or DEVICE_CLASS_CO2 in state.entity_id: + elif device_class == DEVICE_CLASS_CO2 or "co2" in state.entity_id: a_type = "CarbonDioxideSensor" elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", LIGHT_LUX): a_type = "LightSensor" diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index ff45306b3515b4..abfc6a2aa381fa 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -237,8 +237,6 @@ PROP_VALID_VALUES = "ValidValues" # #### Device Classes #### -DEVICE_CLASS_CO = "co" -DEVICE_CLASS_CO2 = "co2" DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_GARAGE_DOOR = "garage_door" DEVICE_CLASS_GAS = "gas" diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 28c7ea26009769..b6cc4b05125624 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -6,6 +6,8 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, STATE_HOME, STATE_ON, TEMP_CELSIUS, @@ -30,7 +32,6 @@ CHAR_MOTION_DETECTED, CHAR_OCCUPANCY_DETECTED, CHAR_SMOKE_DETECTED, - DEVICE_CLASS_CO2, DEVICE_CLASS_DOOR, DEVICE_CLASS_GARAGE_DOOR, DEVICE_CLASS_GAS, @@ -60,6 +61,7 @@ _LOGGER = logging.getLogger(__name__) BINARY_SENSOR_SERVICE_MAP = { + DEVICE_CLASS_CO: (SERV_CARBON_MONOXIDE_SENSOR, CHAR_CARBON_MONOXIDE_DETECTED, int), DEVICE_CLASS_CO2: (SERV_CARBON_DIOXIDE_SENSOR, CHAR_CARBON_DIOXIDE_DETECTED, int), DEVICE_CLASS_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, int), DEVICE_CLASS_GARAGE_DOOR: (SERV_CONTACT_SENSOR, CHAR_CONTACT_SENSOR_STATE, int), diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 70d594080110bb..1c68ae7d001bb4 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -26,6 +26,8 @@ ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, @@ -186,9 +188,19 @@ def test_type_media_player(type_name, entity_id, state, attrs, config): ("BinarySensor", "person.someone", "home", {}), ("AirQualitySensor", "sensor.air_quality_pm25", "40", {}), ("AirQualitySensor", "sensor.air_quality", "40", {ATTR_DEVICE_CLASS: "pm25"}), - ("CarbonMonoxideSensor", "sensor.airmeter", "2", {ATTR_DEVICE_CLASS: "co"}), + ( + "CarbonMonoxideSensor", + "sensor.co", + "2", + {ATTR_DEVICE_CLASS: DEVICE_CLASS_CO}, + ), ("CarbonDioxideSensor", "sensor.airmeter_co2", "500", {}), - ("CarbonDioxideSensor", "sensor.airmeter", "500", {ATTR_DEVICE_CLASS: "co2"}), + ( + "CarbonDioxideSensor", + "sensor.co2", + "500", + {ATTR_DEVICE_CLASS: DEVICE_CLASS_CO2}, + ), ( "HumiditySensor", "sensor.humidity", From 597bf67f5a8483f1e032e43678103cb33bec561f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 12 Mar 2021 20:55:11 +0100 Subject: [PATCH 1219/1818] UniFi has changed to not report uptime in epoch form (#47492) --- homeassistant/components/unifi/sensor.py | 7 +- tests/components/unifi/test_sensor.py | 326 +++++++++++++---------- 2 files changed, 192 insertions(+), 141 deletions(-) diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index f78ec614da15b0..77e8f5afbe4285 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -3,6 +3,9 @@ Support for bandwidth sensors of network clients. Support for uptime sensors of network clients. """ + +from datetime import datetime, timedelta + from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback @@ -140,8 +143,10 @@ def name(self) -> str: return f"{super().name} {self.TYPE.capitalize()}" @property - def state(self) -> int: + def state(self) -> datetime: """Return the uptime of the client.""" + if self.client.uptime < 1000000000: + return (dt_util.now() - timedelta(seconds=self.client.uptime)).isoformat() return dt_util.utc_from_timestamp(float(self.client.uptime)).isoformat() async def options_updated(self) -> None: diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index db1794e087847d..eec4fba7df903f 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -1,5 +1,7 @@ """UniFi sensor platform tests.""" -from copy import deepcopy + +from datetime import datetime +from unittest.mock import patch from aiounifi.controller import MESSAGE_CLIENT, MESSAGE_CLIENT_REMOVED @@ -13,40 +15,10 @@ DOMAIN as UNIFI_DOMAIN, ) from homeassistant.helpers.dispatcher import async_dispatcher_send +import homeassistant.util.dt as dt_util from .test_controller import setup_unifi_integration -CLIENTS = [ - { - "hostname": "Wired client hostname", - "ip": "10.0.0.1", - "is_wired": True, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:01", - "name": "Wired client name", - "oui": "Producer", - "sw_mac": "00:00:00:00:01:01", - "sw_port": 1, - "wired-rx_bytes": 1234000000, - "wired-tx_bytes": 5678000000, - "uptime": 1600094505, - }, - { - "hostname": "Wireless client hostname", - "ip": "10.0.0.2", - "is_wired": False, - "last_seen": 1562600145, - "mac": "00:00:00:00:00:02", - "name": "Wireless client name", - "oui": "Producer", - "sw_mac": "00:00:00:00:01:01", - "sw_port": 2, - "rx_bytes": 1234000000, - "tx_bytes": 5678000000, - "uptime": 1600094505, - }, -] - async def test_no_clients(hass, aioclient_mock): """Test the update_clients function when no clients are found.""" @@ -62,112 +34,183 @@ async def test_no_clients(hass, aioclient_mock): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 -async def test_sensors(hass, aioclient_mock, mock_unifi_websocket): - """Test the update_items function with some clients.""" +async def test_bandwidth_sensors(hass, aioclient_mock, mock_unifi_websocket): + """Verify that bandwidth sensors are working as expected.""" + wired_client = { + "hostname": "Wired client", + "is_wired": True, + "mac": "00:00:00:00:00:01", + "oui": "Producer", + "wired-rx_bytes": 1234000000, + "wired-tx_bytes": 5678000000, + } + wireless_client = { + "is_wired": False, + "mac": "00:00:00:00:00:02", + "name": "Wireless client", + "oui": "Producer", + "rx_bytes": 2345000000, + "tx_bytes": 6789000000, + } + options = { + CONF_ALLOW_BANDWIDTH_SENSORS: True, + CONF_ALLOW_UPTIME_SENSORS: False, + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + } + config_entry = await setup_unifi_integration( hass, aioclient_mock, - options={ - CONF_ALLOW_BANDWIDTH_SENSORS: True, - CONF_ALLOW_UPTIME_SENSORS: True, - CONF_TRACK_CLIENTS: False, - CONF_TRACK_DEVICES: False, - }, - clients_response=CLIENTS, + options=options, + clients_response=[wired_client, wireless_client], ) - controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 - - wired_client_rx = hass.states.get("sensor.wired_client_name_rx") - assert wired_client_rx.state == "1234.0" - wired_client_tx = hass.states.get("sensor.wired_client_name_tx") - assert wired_client_tx.state == "5678.0" + assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + assert hass.states.get("sensor.wired_client_rx").state == "1234.0" + assert hass.states.get("sensor.wired_client_tx").state == "5678.0" + assert hass.states.get("sensor.wireless_client_rx").state == "2345.0" + assert hass.states.get("sensor.wireless_client_tx").state == "6789.0" - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime.state == "2020-09-14T14:41:45+00:00" + # Verify state update - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx.state == "1234.0" - - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx.state == "5678.0" - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime.state == "2020-09-14T14:41:45+00:00" - - clients = deepcopy(CLIENTS) - clients[0]["is_wired"] = False - clients[1]["rx_bytes"] = 2345000000 - clients[1]["tx_bytes"] = 6789000000 - clients[1]["uptime"] = 1600180860 + wireless_client["rx_bytes"] = 3456000000 + wireless_client["tx_bytes"] = 7891000000 mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT}, - "data": clients, + "data": [wireless_client], } ) await hass.async_block_till_done() - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx.state == "2345.0" + assert hass.states.get("sensor.wireless_client_rx").state == "3456.0" + assert hass.states.get("sensor.wireless_client_tx").state == "7891.0" - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx.state == "6789.0" + # Disable option - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime.state == "2020-09-15T14:41:00+00:00" + options[CONF_ALLOW_BANDWIDTH_SENSORS] = False + hass.config_entries.async_update_entry(config_entry, options=options.copy()) + await hass.async_block_till_done() - hass.config_entries.async_update_entry( - config_entry, - options={ - CONF_ALLOW_BANDWIDTH_SENSORS: False, - CONF_ALLOW_UPTIME_SENSORS: False, - }, - ) + assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 + assert hass.states.get("sensor.wireless_client_rx") is None + assert hass.states.get("sensor.wireless_client_tx") is None + assert hass.states.get("sensor.wired_client_rx") is None + assert hass.states.get("sensor.wired_client_tx") is None + + # Enable option + + options[CONF_ALLOW_BANDWIDTH_SENSORS] = True + hass.config_entries.async_update_entry(config_entry, options=options.copy()) await hass.async_block_till_done() - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx is None + assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + assert hass.states.get("sensor.wireless_client_rx") + assert hass.states.get("sensor.wireless_client_tx") + assert hass.states.get("sensor.wired_client_rx") + assert hass.states.get("sensor.wired_client_tx") - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx is None + # Try to add the sensors again, using a signal - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime is None + clients_connected = {wired_client["mac"], wireless_client["mac"]} + devices_connected = set() - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime is None + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - hass.config_entries.async_update_entry( - config_entry, - options={ - CONF_ALLOW_BANDWIDTH_SENSORS: True, - CONF_ALLOW_UPTIME_SENSORS: True, - }, + async_dispatcher_send( + hass, + controller.signal_update, + clients_connected, + devices_connected, ) await hass.async_block_till_done() - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx.state == "2345.0" + assert len(hass.states.async_all()) == 5 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx.state == "6789.0" - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime.state == "2020-09-15T14:41:00+00:00" +async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): + """Verify that uptime sensors are working as expected.""" + client1 = { + "mac": "00:00:00:00:00:01", + "name": "client1", + "oui": "Producer", + "uptime": 1609506061, + } + client2 = { + "hostname": "Client2", + "mac": "00:00:00:00:00:02", + "oui": "Producer", + "uptime": 60, + } + options = { + CONF_ALLOW_BANDWIDTH_SENSORS: False, + CONF_ALLOW_UPTIME_SENSORS: True, + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + } + + now = datetime(2021, 1, 1, 1, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.now", return_value=now): + config_entry = await setup_unifi_integration( + hass, + aioclient_mock, + options=options, + clients_response=[client1, client2], + ) + + assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T13:01:01+00:00" + assert hass.states.get("sensor.client2_uptime").state == "2021-01-01T00:59:00+00:00" + + # Verify state update + + client1["uptime"] = 1609506062 + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [client1], + } + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T13:01:02+00:00" + + # Disable option + + options[CONF_ALLOW_UPTIME_SENSORS] = False + hass.config_entries.async_update_entry(config_entry, options=options.copy()) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 + assert hass.states.get("sensor.client1_uptime") is None + assert hass.states.get("sensor.client2_uptime") is None + + # Enable option - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime.state == "2020-09-14T14:41:45+00:00" + options[CONF_ALLOW_UPTIME_SENSORS] = True + with patch("homeassistant.util.dt.now", return_value=now): + hass.config_entries.async_update_entry(config_entry, options=options.copy()) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert hass.states.get("sensor.client1_uptime") + assert hass.states.get("sensor.client2_uptime") # Try to add the sensors again, using a signal - clients_connected = set() + + clients_connected = {client1["mac"], client2["mac"]} devices_connected = set() - clients_connected.add(clients[0]["mac"]) - clients_connected.add(clients[1]["mac"]) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] async_dispatcher_send( hass, @@ -175,14 +218,33 @@ async def test_sensors(hass, aioclient_mock, mock_unifi_websocket): clients_connected, devices_connected, ) - await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 + assert len(hass.states.async_all()) == 3 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket): - """Test the remove_items function with some clients.""" + """Verify removing of clients work as expected.""" + wired_client = { + "hostname": "Wired client", + "is_wired": True, + "mac": "00:00:00:00:00:01", + "oui": "Producer", + "wired-rx_bytes": 1234000000, + "wired-tx_bytes": 5678000000, + "uptime": 1600094505, + } + wireless_client = { + "is_wired": False, + "mac": "00:00:00:00:00:02", + "name": "Wireless client", + "oui": "Producer", + "rx_bytes": 2345000000, + "tx_bytes": 6789000000, + "uptime": 60, + } + await setup_unifi_integration( hass, aioclient_mock, @@ -190,51 +252,35 @@ async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket): CONF_ALLOW_BANDWIDTH_SENSORS: True, CONF_ALLOW_UPTIME_SENSORS: True, }, - clients_response=CLIENTS, + clients_response=[wired_client, wireless_client], ) + assert len(hass.states.async_all()) == 9 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 6 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 + assert hass.states.get("sensor.wired_client_rx") + assert hass.states.get("sensor.wired_client_tx") + assert hass.states.get("sensor.wired_client_uptime") + assert hass.states.get("sensor.wireless_client_rx") + assert hass.states.get("sensor.wireless_client_tx") + assert hass.states.get("sensor.wireless_client_uptime") - wired_client_rx = hass.states.get("sensor.wired_client_name_rx") - assert wired_client_rx is not None - wired_client_tx = hass.states.get("sensor.wired_client_name_tx") - assert wired_client_tx is not None - - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime is not None - - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx is not None - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx is not None - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime is not None + # Remove wired client mock_unifi_websocket( data={ "meta": {"message": MESSAGE_CLIENT_REMOVED}, - "data": [CLIENTS[0]], + "data": [wired_client], } ) await hass.async_block_till_done() + assert len(hass.states.async_all()) == 5 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 3 assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 - - wired_client_rx = hass.states.get("sensor.wired_client_name_rx") - assert wired_client_rx is None - wired_client_tx = hass.states.get("sensor.wired_client_name_tx") - assert wired_client_tx is None - - wired_client_uptime = hass.states.get("sensor.wired_client_name_uptime") - assert wired_client_uptime is None - - wireless_client_rx = hass.states.get("sensor.wireless_client_name_rx") - assert wireless_client_rx is not None - wireless_client_tx = hass.states.get("sensor.wireless_client_name_tx") - assert wireless_client_tx is not None - - wireless_client_uptime = hass.states.get("sensor.wireless_client_name_uptime") - assert wireless_client_uptime is not None + assert hass.states.get("sensor.wired_client_rx") is None + assert hass.states.get("sensor.wired_client_tx") is None + assert hass.states.get("sensor.wired_client_uptime") is None + assert hass.states.get("sensor.wireless_client_rx") + assert hass.states.get("sensor.wireless_client_tx") + assert hass.states.get("sensor.wireless_client_uptime") From 6b03c8d1269ec53174f6c094b34326e5c5299afd Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 12 Mar 2021 21:02:15 +0100 Subject: [PATCH 1220/1818] Improve deCONZ init tests (#47825) Use patch.dict rather than deep copy to change DECONZ_WEB_REQUEST --- tests/components/deconz/test_init.py | 46 ++++++++++++---------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 46b563ca00714a..6583372d7bd0df 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,7 +1,6 @@ """Test deCONZ component setup process.""" import asyncio -from copy import deepcopy from unittest.mock import patch from homeassistant.components.deconz import ( @@ -71,15 +70,14 @@ async def test_setup_entry_multiple_gateways(hass, aioclient_mock): config_entry = await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.clear_requests() - data = deepcopy(DECONZ_WEB_REQUEST) - data["config"]["bridgeid"] = "01234E56789B" - config_entry2 = await setup_deconz_integration( - hass, - aioclient_mock, - get_state_response=data, - entry_id="2", - unique_id="01234E56789B", - ) + data = {"config": {"bridgeid": "01234E56789B"}} + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry2 = await setup_deconz_integration( + hass, + aioclient_mock, + entry_id="2", + unique_id="01234E56789B", + ) assert len(hass.data[DECONZ_DOMAIN]) == 2 assert hass.data[DECONZ_DOMAIN][config_entry.unique_id].master @@ -100,15 +98,14 @@ async def test_unload_entry_multiple_gateways(hass, aioclient_mock): config_entry = await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.clear_requests() - data = deepcopy(DECONZ_WEB_REQUEST) - data["config"]["bridgeid"] = "01234E56789B" - config_entry2 = await setup_deconz_integration( - hass, - aioclient_mock, - get_state_response=data, - entry_id="2", - unique_id="01234E56789B", - ) + data = {"config": {"bridgeid": "01234E56789B"}} + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry2 = await setup_deconz_integration( + hass, + aioclient_mock, + entry_id="2", + unique_id="01234E56789B", + ) assert len(hass.data[DECONZ_DOMAIN]) == 2 @@ -154,12 +151,8 @@ async def test_update_group_unique_id(hass): await async_update_group_unique_id(hass, entry) assert entry.data == {CONF_API_KEY: "1", CONF_HOST: "2", CONF_PORT: "3"} - - old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") - assert old_entity.unique_id == f"{new_unique_id}-OLD" - - new_entity = registry.async_get(f"{LIGHT_DOMAIN}.new") - assert new_entity.unique_id == f"{new_unique_id}-NEW" + assert registry.async_get(f"{LIGHT_DOMAIN}.old").unique_id == f"{new_unique_id}-OLD" + assert registry.async_get(f"{LIGHT_DOMAIN}.new").unique_id == f"{new_unique_id}-NEW" async def test_update_group_unique_id_no_legacy_group_id(hass): @@ -184,5 +177,4 @@ async def test_update_group_unique_id_no_legacy_group_id(hass): await async_update_group_unique_id(hass, entry) - old_entity = registry.async_get(f"{LIGHT_DOMAIN}.old") - assert old_entity.unique_id == f"{old_unique_id}-OLD" + assert registry.async_get(f"{LIGHT_DOMAIN}.old").unique_id == f"{old_unique_id}-OLD" From 786cbcc1d631eb1c7135fee5f3a1b2d400482e50 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 12 Mar 2021 21:03:29 +0100 Subject: [PATCH 1221/1818] Introduction of deCONZ websocket fixture (#47812) --- tests/components/deconz/conftest.py | 27 ++++++++++++++ tests/components/deconz/test_gateway.py | 48 +++++++++++++++++-------- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index 5cf122213c4dd3..a7825b80ea2192 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,2 +1,29 @@ """deconz conftest.""" + +from typing import Optional +from unittest.mock import patch + +import pytest + from tests.components.light.conftest import mock_light_profiles # noqa: F401 + + +@pytest.fixture(autouse=True) +def mock_deconz_websocket(): + """No real websocket allowed.""" + with patch("pydeconz.gateway.WSClient") as mock: + + async def make_websocket_call(data: Optional[dict] = None, state: str = ""): + """Generate a websocket call.""" + pydeconz_gateway_session_handler = mock.call_args[0][3] + + if data: + mock.return_value.data = data + await pydeconz_gateway_session_handler(signal="data") + elif state: + mock.return_value.state = state + await pydeconz_gateway_session_handler(signal="state") + else: + raise NotImplementedError + + yield make_websocket_call diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 5c1642ba8f738f..6743ae0696a503 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -4,6 +4,7 @@ from unittest.mock import Mock, patch import pydeconz +from pydeconz.websocket import STATE_RUNNING, STATE_STARTING import pytest from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -29,8 +30,14 @@ ) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import CONN_CLASS_LOCAL_PUSH, SOURCE_SSDP -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONTENT_TYPE_JSON, + STATE_OFF, + STATE_UNAVAILABLE, +) from tests.common import MockConfigEntry @@ -116,8 +123,7 @@ async def setup_deconz_integration( if aioclient_mock: mock_deconz_request(aioclient_mock, config, get_state_response) - with patch("pydeconz.DeconzSession.start", return_value=True): - await hass.config_entries.async_setup(config_entry.entry_id) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() return config_entry @@ -173,21 +179,35 @@ async def test_gateway_setup_fails(hass): assert not hass.data[DECONZ_DOMAIN] -async def test_connection_status_signalling(hass, aioclient_mock): +async def test_connection_status_signalling( + hass, aioclient_mock, mock_deconz_websocket +): """Make sure that connection status triggers a dispatcher send.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "presence", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "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 hass.states.get("binary_sensor.presence").state == STATE_OFF + + await mock_deconz_websocket(state=STATE_STARTING) + await hass.async_block_till_done() - event_call = Mock() - unsub = async_dispatcher_connect(hass, gateway.signal_reachable, event_call) + assert hass.states.get("binary_sensor.presence").state == STATE_UNAVAILABLE - gateway.async_connection_status_callback(False) + await mock_deconz_websocket(state=STATE_RUNNING) await hass.async_block_till_done() - assert gateway.available is False - assert len(event_call.mock_calls) == 1 - - unsub() + assert hass.states.get("binary_sensor.presence").state == STATE_OFF async def test_update_address(hass, aioclient_mock): From 7826f6e3f8048786c3dec1004919f3bd260bdda2 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Fri, 12 Mar 2021 21:52:43 +0100 Subject: [PATCH 1222/1818] Update cloud integration to 0.42.0 (#47818) --- 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 9d27de133097ce..b854cb4578dcda 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.41.0"], + "requirements": ["hass-nabucasa==0.42.0"], "dependencies": ["http", "webhook", "alexa"], "after_dependencies": ["google_assistant"], "codeowners": ["@home-assistant/cloud"] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bcbe614ca2056b..878834c5075b65 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ cryptography==3.3.2 defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 -hass-nabucasa==0.41.0 +hass-nabucasa==0.42.0 home-assistant-frontend==20210302.6 httpx==0.16.1 jinja2>=2.11.3 diff --git a/requirements_all.txt b/requirements_all.txt index a6a280a5311f27..532318d5910c3d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -727,7 +727,7 @@ habitipy==0.2.0 hangups==0.4.11 # homeassistant.components.cloud -hass-nabucasa==0.41.0 +hass-nabucasa==0.42.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b89ac61932005e..b8259e517b6c81 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -388,7 +388,7 @@ habitipy==0.2.0 hangups==0.4.11 # homeassistant.components.cloud -hass-nabucasa==0.41.0 +hass-nabucasa==0.42.0 # homeassistant.components.tasmota hatasmota==0.2.9 From 362e7226e93f7f91199b8fa82b02c64607140430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Kr=C3=B6ner?= Date: Fri, 12 Mar 2021 21:55:13 +0100 Subject: [PATCH 1223/1818] Additional sensors for OpenWeatherMap (#47806) --- .../components/openweathermap/const.py | 19 ++++++++++++++++++- .../weather_update_coordinator.py | 6 +++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 8457ceb65e9dd0..4ab61b486f1391 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -35,6 +35,7 @@ PRESSURE_HPA, SPEED_METERS_PER_SECOND, TEMP_CELSIUS, + UV_INDEX, ) DOMAIN = "openweathermap" @@ -47,6 +48,7 @@ ENTRY_WEATHER_COORDINATOR = "weather_coordinator" ATTR_API_PRECIPITATION = "precipitation" ATTR_API_DATETIME = "datetime" +ATTR_API_DEW_POINT = "dew_point" ATTR_API_WEATHER = "weather" ATTR_API_TEMPERATURE = "temperature" ATTR_API_FEELS_LIKE_TEMPERATURE = "feels_like_temperature" @@ -58,6 +60,7 @@ ATTR_API_CLOUDS = "clouds" ATTR_API_RAIN = "rain" ATTR_API_SNOW = "snow" +ATTR_API_UV_INDEX = "uv_index" ATTR_API_WEATHER_CODE = "weather_code" ATTR_API_FORECAST = "forecast" SENSOR_NAME = "sensor_name" @@ -81,6 +84,7 @@ MONITORED_CONDITIONS = [ ATTR_API_WEATHER, + ATTR_API_DEW_POINT, ATTR_API_TEMPERATURE, ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_WIND_SPEED, @@ -90,6 +94,7 @@ ATTR_API_CLOUDS, ATTR_API_RAIN, ATTR_API_SNOW, + ATTR_API_UV_INDEX, ATTR_API_CONDITION, ATTR_API_WEATHER_CODE, ] @@ -187,6 +192,11 @@ } WEATHER_SENSOR_TYPES = { ATTR_API_WEATHER: {SENSOR_NAME: "Weather"}, + ATTR_API_DEW_POINT: { + SENSOR_NAME: "Dew Point", + SENSOR_UNIT: TEMP_CELSIUS, + SENSOR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + }, ATTR_API_TEMPERATURE: { SENSOR_NAME: "Temperature", SENSOR_UNIT: TEMP_CELSIUS, @@ -215,13 +225,20 @@ ATTR_API_CLOUDS: {SENSOR_NAME: "Cloud coverage", SENSOR_UNIT: PERCENTAGE}, ATTR_API_RAIN: {SENSOR_NAME: "Rain", SENSOR_UNIT: LENGTH_MILLIMETERS}, ATTR_API_SNOW: {SENSOR_NAME: "Snow", SENSOR_UNIT: LENGTH_MILLIMETERS}, + ATTR_API_UV_INDEX: { + SENSOR_NAME: "UV Index", + SENSOR_UNIT: UV_INDEX, + }, ATTR_API_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_API_WEATHER_CODE: {SENSOR_NAME: "Weather Code"}, } FORECAST_SENSOR_TYPES = { ATTR_FORECAST_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_FORECAST_PRECIPITATION: {SENSOR_NAME: "Precipitation"}, - ATTR_FORECAST_PRECIPITATION_PROBABILITY: {SENSOR_NAME: "Precipitation probability"}, + ATTR_FORECAST_PRECIPITATION_PROBABILITY: { + SENSOR_NAME: "Precipitation probability", + SENSOR_UNIT: PERCENTAGE, + }, ATTR_FORECAST_PRESSURE: {SENSOR_NAME: "Pressure"}, ATTR_FORECAST_TEMP: { SENSOR_NAME: "Temperature", diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 07b8c507c873d1..26ada47bef61b3 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -25,6 +25,7 @@ from .const import ( ATTR_API_CLOUDS, ATTR_API_CONDITION, + ATTR_API_DEW_POINT, ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_FORECAST, ATTR_API_HUMIDITY, @@ -32,6 +33,7 @@ ATTR_API_RAIN, ATTR_API_SNOW, ATTR_API_TEMPERATURE, + ATTR_API_UV_INDEX, ATTR_API_WEATHER, ATTR_API_WEATHER_CODE, ATTR_API_WIND_BEARING, @@ -119,6 +121,7 @@ def _convert_weather_response(self, weather_response): ATTR_API_FEELS_LIKE_TEMPERATURE: current_weather.temperature("celsius").get( "feels_like" ), + ATTR_API_DEW_POINT: (round(current_weather.dewpoint / 100, 1)), ATTR_API_PRESSURE: current_weather.pressure.get("press"), ATTR_API_HUMIDITY: current_weather.humidity, ATTR_API_WIND_BEARING: current_weather.wind().get("deg"), @@ -128,6 +131,7 @@ def _convert_weather_response(self, weather_response): ATTR_API_SNOW: self._get_snow(current_weather.snow), ATTR_API_WEATHER: current_weather.detailed_status, ATTR_API_CONDITION: self._get_condition(current_weather.weather_code), + ATTR_API_UV_INDEX: current_weather.uvi, ATTR_API_WEATHER_CODE: current_weather.weather_code, ATTR_API_FORECAST: forecast_weather, } @@ -151,7 +155,7 @@ def _convert_forecast(self, entry): entry.rain, entry.snow ), ATTR_FORECAST_PRECIPITATION_PROBABILITY: ( - entry.precipitation_probability * 100 + round(entry.precipitation_probability * 100) ), ATTR_FORECAST_PRESSURE: entry.pressure.get("press"), ATTR_FORECAST_WIND_SPEED: entry.wind().get("speed"), From 07aeb8d160118fb3d0827ec02f69288debe25b7d Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 12 Mar 2021 21:57:02 +0100 Subject: [PATCH 1224/1818] Fix Netatmo event handling (#47792) --- homeassistant/components/netatmo/webhook.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 5ecc3d41789522..54db95e9aa0a27 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -76,16 +76,18 @@ def async_send_event(hass, event_type, data): {"type": event_type, "data": data}, ) - if event_type not in EVENT_ID_MAP: - return + event_data = { + "type": event_type, + "data": data, + } - data_device_id = data[EVENT_ID_MAP[event_type]] + if event_type in EVENT_ID_MAP: + data_device_id = data[EVENT_ID_MAP[event_type]] + event_data[ATTR_DEVICE_ID] = hass.data[DOMAIN][DATA_DEVICE_IDS].get( + data_device_id + ) hass.bus.async_fire( event_type=NETATMO_EVENT, - event_data={ - "type": event_type, - "data": data, - ATTR_DEVICE_ID: hass.data[DOMAIN][DATA_DEVICE_IDS].get(data_device_id), - }, + event_data=event_data, ) From ed37c316304d0e2b741f7ac03d392fbcc42b85d5 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 11 Mar 2021 07:22:35 -0700 Subject: [PATCH 1225/1818] Write SimpliSafe alarm control panel state after arming/disarming (#47649) * Write SimpliSafe alarm control panel state after arming/disarming * Include locks --- homeassistant/components/simplisafe/alarm_control_panel.py | 3 +++ homeassistant/components/simplisafe/lock.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 7634f1cce86f8c..8f394890ad4eec 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -163,6 +163,7 @@ async def async_alarm_disarm(self, code=None): return self._state = STATE_ALARM_DISARMED + self.async_write_ha_state() async def async_alarm_arm_home(self, code=None): """Send arm home command.""" @@ -178,6 +179,7 @@ async def async_alarm_arm_home(self, code=None): return self._state = STATE_ALARM_ARMED_HOME + self.async_write_ha_state() async def async_alarm_arm_away(self, code=None): """Send arm away command.""" @@ -193,6 +195,7 @@ async def async_alarm_arm_away(self, code=None): return self._state = STATE_ALARM_ARMING + self.async_write_ha_state() @callback def async_update_from_rest_api(self): diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 08ffb82d24fcae..a4d823efe386a6 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -55,6 +55,9 @@ async def async_lock(self, **kwargs): LOGGER.error('Error while locking "%s": %s', self._lock.name, err) return + self._is_locked = True + self.async_write_ha_state() + async def async_unlock(self, **kwargs): """Unlock the lock.""" try: @@ -63,6 +66,9 @@ async def async_unlock(self, **kwargs): LOGGER.error('Error while unlocking "%s": %s', self._lock.name, err) return + self._is_locked = False + self.async_write_ha_state() + @callback def async_update_from_rest_api(self): """Update the entity with the provided REST API data.""" From 544844d8650996925243ddccf69cae4c2e1b9f35 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Thu, 11 Mar 2021 07:49:10 -0500 Subject: [PATCH 1226/1818] Cover Tilt Position Bugfix (#47682) * Report tilt position properly when inverting using tilt_max < tilt_min * Add warning per review comment * Add test for inverted tilt position configuration * Separate non-numeric and out of range warnings per review comment * Fix out of range message and add tests for not numeric and out of range messages --- homeassistant/components/mqtt/cover.py | 15 +++- tests/components/mqtt/test_cover.py | 106 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 7b7f983b1e49a4..e2a16c253292d6 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -280,15 +280,26 @@ def tilt_message_received(msg): payload ) - if payload.isnumeric() and ( + if not payload.isnumeric(): + _LOGGER.warning("Payload '%s' is not numeric", payload) + elif ( self._config[CONF_TILT_MIN] <= int(payload) <= self._config[CONF_TILT_MAX] + or self._config[CONF_TILT_MAX] + <= int(payload) + <= self._config[CONF_TILT_MIN] ): - level = self.find_percentage_in_range(float(payload)) self._tilt_value = level self.async_write_ha_state() + else: + _LOGGER.warning( + "Payload '%s' is out of range, must be between '%s' and '%s' inclusive", + payload, + self._config[CONF_TILT_MIN], + self._config[CONF_TILT_MAX], + ) @callback @log_messages(self.hass, self.entity_id) diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 44144642f40959..d6899d5149ac8b 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1315,6 +1315,112 @@ async def test_tilt_via_topic_altered_range(hass, mqtt_mock): assert current_cover_tilt_position == 50 +async def test_tilt_status_out_of_range_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt out of range warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "60") + + assert ( + "Payload '60' is out of range, must be between '0' and '50' inclusive" + ) in caplog.text + + +async def test_tilt_status_not_numeric_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt not numeric warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "abc") + + assert ("Payload 'abc' is not numeric") in caplog.text + + +async def test_tilt_via_topic_altered_range_inverted(hass, mqtt_mock): + """Test tilt status via MQTT with altered tilt range and inverted tilt position.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 50, + "tilt_max": 0, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "0") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 100 + + async_fire_mqtt_message(hass, "tilt-status-topic", "50") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 0 + + async_fire_mqtt_message(hass, "tilt-status-topic", "25") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 50 + + async def test_tilt_via_topic_template_altered_range(hass, mqtt_mock): """Test tilt status via MQTT and template with altered tilt range.""" assert await async_setup_component( From 726eb69b40e84fb71713631fab59997b3f0f9322 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 11 Mar 2021 16:28:38 -0700 Subject: [PATCH 1227/1818] Fix zwave_js target_temp_low (#47762) --- homeassistant/components/zwave_js/climate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 26e9e730283efe..4f62d5792fc903 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -287,7 +287,12 @@ def target_temperature_high(self) -> Optional[float]: @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return self.target_temperature + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None + if len(self._current_mode_setpoint_enums) > 1: + return self.target_temperature + return None @property def preset_mode(self) -> Optional[str]: From 81336809e830866788c86df941a992f358b54b3f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 11 Mar 2021 19:35:24 -1000 Subject: [PATCH 1228/1818] Adjust insteon fan speed range to valid upper bound (#47765) --- homeassistant/components/insteon/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 2c397188640ea0..f89aeb294fafe3 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -17,7 +17,7 @@ from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (0x00, 0xFF) # off is not included +SPEED_RANGE = (1, 255) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): From 72ef88693a0ccc766b558e9cb3bf359f94f0aed4 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 11 Mar 2021 17:35:11 -0600 Subject: [PATCH 1229/1818] Bump plexapi to 4.4.1 (#47766) --- 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 49388bdfdb656d..1319e4bbf4960b 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.4.0", + "plexapi==4.4.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index 5eb66a3a200e72..05dcf9f233e0e4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1135,7 +1135,7 @@ pillow==8.1.1 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 628b62899cdcd9..4dba304310efbb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -581,7 +581,7 @@ pilight==0.1.1 pillow==8.1.1 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 From 108d9eab1a7c2b1502c5d1fa39b9d717d9900d84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 12 Mar 2021 00:41:01 -1000 Subject: [PATCH 1230/1818] Ensure homekit reset accessory service can target any entity (#47787) --- homeassistant/components/homekit/services.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/homekit/services.yaml b/homeassistant/components/homekit/services.yaml index 6f9c005ed641da..a6b09a80e7ffe9 100644 --- a/homeassistant/components/homekit/services.yaml +++ b/homeassistant/components/homekit/services.yaml @@ -9,3 +9,5 @@ reload: reset_accessory: description: Reset a HomeKit accessory target: + entity: {} + From 926b0d8491a4170f1604d33a56d64876f9a84620 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 12 Mar 2021 21:57:02 +0100 Subject: [PATCH 1231/1818] Fix Netatmo event handling (#47792) --- homeassistant/components/netatmo/webhook.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 1fe7302038eae2..e41f83b8cc0a19 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -77,16 +77,18 @@ async def async_send_event(hass, event_type, data): {"type": event_type, "data": data}, ) - if event_type not in EVENT_ID_MAP: - return + event_data = { + "type": event_type, + "data": data, + } - data_device_id = data[EVENT_ID_MAP[event_type]] + if event_type in EVENT_ID_MAP: + data_device_id = data[EVENT_ID_MAP[event_type]] + event_data[ATTR_DEVICE_ID] = hass.data[DOMAIN][DATA_DEVICE_IDS].get( + data_device_id + ) hass.bus.async_fire( event_type=NETATMO_EVENT, - event_data={ - "type": event_type, - "data": data, - ATTR_DEVICE_ID: hass.data[DOMAIN][DATA_DEVICE_IDS].get(data_device_id), - }, + event_data=event_data, ) From a024343a15043fb88a509dca3be8e2e365806631 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 12 Mar 2021 21:01:15 +0000 Subject: [PATCH 1232/1818] Bumped version to 2021.3.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ae748b3ccc2859..d7bb7ea25b5bdc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) From 2178e27fb4c62271d4872e16838331defed82226 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 12 Mar 2021 23:17:27 +0100 Subject: [PATCH 1233/1818] Fix unclean shutdown of recorder test (#47791) --- tests/components/recorder/test_migrate.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index c29dad2d495fa2..3bde17ab8ef102 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -8,7 +8,8 @@ from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component -from homeassistant.components.recorder import const, migration, models +from homeassistant.components.recorder import RecorderRuns, const, migration, models +import homeassistant.util.dt as dt_util from tests.components.recorder import models_original @@ -51,8 +52,16 @@ async def test_schema_migrate(hass): throwing exceptions. Maintaining a set of assertions based on schema inspection could quickly become quite cumbersome. """ + + def _mock_setup_run(self): + self.run_info = RecorderRuns( + start=self.recording_start, created=dt_util.utcnow() + ) + with patch("sqlalchemy.create_engine", new=create_engine_test), patch( - "homeassistant.components.recorder.Recorder._setup_run" + "homeassistant.components.recorder.Recorder._setup_run", + side_effect=_mock_setup_run, + autospec=True, ) as setup_run: await async_setup_component( hass, "recorder", {"recorder": {"db_url": "sqlite://"}} From 547fd7d352a42194ffc1de74bdc3ef4be35f00f1 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Fri, 12 Mar 2021 21:06:37 -0500 Subject: [PATCH 1234/1818] fix exception on device removal (#47803) --- homeassistant/components/zha/core/gateway.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index c57c726972306a..3a65787cd48243 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -347,7 +347,8 @@ async def _async_remove_device(self, device, entity_refs): remove_tasks = [] for entity_ref in entity_refs: remove_tasks.append(entity_ref.remove_future) - await asyncio.wait(remove_tasks) + if remove_tasks: + await asyncio.wait(remove_tasks) reg_device = self.ha_device_registry.async_get(device.device_id) if reg_device is not None: self.ha_device_registry.async_remove_device(reg_device.id) From eccdf85b2992e279370ceedcfe4172fdb27fc58d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 12 Mar 2021 21:21:24 -0800 Subject: [PATCH 1235/1818] Bump frontend to 20210313.0 (#47844) --- 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 694be0382f7801..a254c7d129af6f 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==20210302.6" + "home-assistant-frontend==20210313.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 878834c5075b65..c604ab5c706b7b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210302.6 +home-assistant-frontend==20210313.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 532318d5910c3d..90e1746e524270 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.6 +home-assistant-frontend==20210313.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b8259e517b6c81..de07ac79a554f1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210302.6 +home-assistant-frontend==20210313.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 30f99177c7bb6a255558821c7f0518139cf0fc37 Mon Sep 17 00:00:00 2001 From: Raj Laud <50647620+rajlaud@users.noreply.github.com> Date: Sat, 13 Mar 2021 01:34:20 -0600 Subject: [PATCH 1236/1818] Fix missing integer cast in squeezebox config flow (#47846) --- homeassistant/components/squeezebox/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/squeezebox/config_flow.py b/homeassistant/components/squeezebox/config_flow.py index 9edff5f9a2abda..cadded67bd1a81 100644 --- a/homeassistant/components/squeezebox/config_flow.py +++ b/homeassistant/components/squeezebox/config_flow.py @@ -80,7 +80,7 @@ def _discovery_callback(server): return self.discovery_info = { CONF_HOST: server.host, - CONF_PORT: server.port, + CONF_PORT: int(server.port), "uuid": server.uuid, } _LOGGER.debug("Discovered server: %s", self.discovery_info) From 02a82d3f00c610f94d3366cc34540bdfa94a2c8e Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 13 Mar 2021 01:53:26 -0800 Subject: [PATCH 1237/1818] Add timeouts in stream tests to prevent possible hangs (#47545) * Add timeouts on recving packets Add a timeout when recving packets from the worker thread in case it hangs. Add an exit condition just in case the while loop goes on forever. * Add a timeout to recorder thread join. * Wait for recorder thread to be invoked in tests Remove the while loop and instead wait for segments to be produced by the background worker thread. * Allow worker to resume before stopping to fix timeouts * Lower test timeout further * Remove test_stream_ended since it is flaky This test doesn't really add additional value on top of other tests. --- tests/components/stream/conftest.py | 1 + tests/components/stream/test_hls.py | 35 +---------------- tests/components/stream/test_recorder.py | 50 +++++++++++++----------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index 1b017667ee6e3e..ead2018b528553 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -32,6 +32,7 @@ def pause(self): def resume(self): """Allow the worker thread to finalize the stream.""" + logging.debug("waking blocked worker") self._event.set() def blocking_finish(self, stream: Stream): diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index b554ee6b20aae1..ab0c21efdfb3d6 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -20,6 +20,8 @@ STREAM_SOURCE = "some-stream-source" SEQUENCE_BYTES = io.BytesIO(b"some-bytes") DURATION = 10 +TEST_TIMEOUT = 5.0 # Lower than 9s home assistant timeout +MAX_ABORT_SEGMENTS = 20 # Abort test to avoid looping forever class HlsClient: @@ -187,39 +189,6 @@ async def test_stream_timeout_after_stop(hass, hass_client, stream_worker_sync): await hass.async_block_till_done() -async def test_stream_ended(hass, stream_worker_sync): - """Test hls stream packets ended.""" - await async_setup_component(hass, "stream", {"stream": {}}) - - stream_worker_sync.pause() - - # Setup demo HLS track - source = generate_h264_video() - stream = create_stream(hass, source) - track = stream.add_provider("hls") - - # Request stream - stream.add_provider("hls") - stream.start() - stream.endpoint_url("hls") - - # Run it dead - while True: - segment = await track.recv() - if segment is None: - break - segments = segment.sequence - # Allow worker to finalize once enough of the stream is been consumed - if segments > 1: - stream_worker_sync.resume() - - assert segments > 1 - assert not track.get_segment() - - # Stop stream, if it hasn't quit already - stream.stop() - - async def test_stream_keepalive(hass): """Test hls stream retries the stream when keepalive=True.""" await async_setup_component(hass, "stream", {"stream": {}}) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 48fe48d3337471..2b44c16243b83b 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -1,10 +1,13 @@ """The tests for hls streams.""" +import asyncio from datetime import timedelta import logging import os import threading +from typing import Deque from unittest.mock import patch +import async_timeout import av import pytest @@ -18,7 +21,8 @@ from tests.common import async_fire_time_changed from tests.components.stream.common import generate_h264_video -TEST_TIMEOUT = 10 +TEST_TIMEOUT = 7.0 # Lower than 9s home assistant timeout +MAX_ABORT_SEGMENTS = 20 # Abort test to avoid looping forever class SaveRecordWorkerSync: @@ -32,23 +36,33 @@ class SaveRecordWorkerSync: def __init__(self): """Initialize SaveRecordWorkerSync.""" self.reset() + self._segments = None - def recorder_save_worker(self, *args, **kwargs): + def recorder_save_worker(self, file_out: str, segments: Deque[Segment]): """Mock method for patch.""" logging.debug("recorder_save_worker thread started") assert self._save_thread is None + self._segments = segments self._save_thread = threading.current_thread() self._save_event.set() - def join(self): + async def get_segments(self): + """Return the recorded video segments.""" + with async_timeout.timeout(TEST_TIMEOUT): + await self._save_event.wait() + return self._segments + + async def join(self): """Verify save worker was invoked and block on shutdown.""" - assert self._save_event.wait(timeout=TEST_TIMEOUT) - self._save_thread.join() + with async_timeout.timeout(TEST_TIMEOUT): + await self._save_event.wait() + self._save_thread.join(timeout=TEST_TIMEOUT) + assert not self._save_thread.is_alive() def reset(self): """Reset callback state for reuse in tests.""" self._save_thread = None - self._save_event = threading.Event() + self._save_event = asyncio.Event() @pytest.fixture() @@ -63,7 +77,7 @@ def record_worker_sync(hass): yield sync -async def test_record_stream(hass, hass_client, stream_worker_sync, record_worker_sync): +async def test_record_stream(hass, hass_client, record_worker_sync): """ Test record stream. @@ -73,29 +87,21 @@ async def test_record_stream(hass, hass_client, stream_worker_sync, record_worke """ await async_setup_component(hass, "stream", {"stream": {}}) - stream_worker_sync.pause() - # Setup demo track source = generate_h264_video() stream = create_stream(hass, source) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") - recorder = stream.add_provider("recorder") - while True: - segment = await recorder.recv() - if not segment: - break - segments = segment.sequence - if segments > 1: - stream_worker_sync.resume() - - stream.stop() - assert segments > 1 + # After stream decoding finishes, the record worker thread starts + segments = await record_worker_sync.get_segments() + assert len(segments) >= 1 # Verify that the save worker was invoked, then block until its # thread completes and is shutdown completely to avoid thread leaks. - record_worker_sync.join() + await record_worker_sync.join() + + stream.stop() async def test_record_lookback( @@ -250,4 +256,4 @@ async def test_record_stream_audio( # Verify that the save worker was invoked, then block until its # thread completes and is shutdown completely to avoid thread leaks. - record_worker_sync.join() + await record_worker_sync.join() From c7b9a0715d2a5ae1d6f7c2752da3d304fa763131 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 13 Mar 2021 06:43:03 -0500 Subject: [PATCH 1238/1818] Fix zwave_js preset supported feature (#47819) --- homeassistant/components/zwave_js/climate.py | 4 +- tests/components/zwave_js/test_climate.py | 82 +------------------- 2 files changed, 7 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index ceb469829496c3..161396830115a3 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -162,7 +162,9 @@ def __init__( add_to_watched_value_ids=True, ) self._set_modes_and_presets() - self._supported_features = SUPPORT_PRESET_MODE + self._supported_features = 0 + if len(self._hvac_presets) > 1: + self._supported_features |= SUPPORT_PRESET_MODE # If any setpoint value exists, we can assume temperature # can be set if any(self._setpoint_values.values()): diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index a31aad19603aca..637fb96f1ba1e0 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -9,7 +9,6 @@ ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, - ATTR_PRESET_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_IDLE, @@ -19,13 +18,10 @@ HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, - PRESET_NONE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, - SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, SUPPORT_FAN_MODE, - SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) @@ -63,51 +59,15 @@ async def test_thermostat_v2( assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.2 assert state.attributes[ATTR_TEMPERATURE] == 22.2 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE assert state.attributes[ATTR_FAN_MODE] == "Auto low" assert state.attributes[ATTR_FAN_STATE] == "Idle / off" assert ( state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_PRESET_MODE - | SUPPORT_TARGET_TEMPERATURE + == SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE | SUPPORT_FAN_MODE ) - # Test setting preset mode - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, - ATTR_PRESET_MODE: PRESET_NONE, - }, - blocking=True, - ) - - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] - assert args["command"] == "node.set_value" - assert args["nodeId"] == 13 - assert args["valueId"] == { - "commandClassName": "Thermostat Mode", - "commandClass": 64, - "endpoint": 1, - "property": "mode", - "propertyName": "mode", - "metadata": { - "type": "number", - "readable": True, - "writeable": True, - "min": 0, - "max": 31, - "label": "Thermostat mode", - "states": {"0": "Off", "1": "Heat", "2": "Cool", "3": "Auto"}, - }, - "value": 1, - } - assert args["value"] == 1 - client.async_send_command_no_wait.reset_mock() # Test setting hvac mode @@ -314,20 +274,6 @@ async def test_thermostat_v2( client.async_send_command_no_wait.reset_mock() - with pytest.raises(ValueError): - # Test setting unknown preset mode - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, - ATTR_PRESET_MODE: "unknown_preset", - }, - blocking=True, - ) - - assert len(client.async_send_command_no_wait.call_args_list) == 0 - # Test setting invalid hvac mode with pytest.raises(ValueError): await hass.services.async_call( @@ -340,18 +286,6 @@ async def test_thermostat_v2( blocking=True, ) - # Test setting invalid preset mode - with pytest.raises(ValueError): - await hass.services.async_call( - CLIMATE_DOMAIN, - SERVICE_SET_PRESET_MODE, - { - ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY, - ATTR_PRESET_MODE: "invalid_mode", - }, - blocking=True, - ) - client.async_send_command_no_wait.reset_mock() # Test setting fan mode @@ -422,11 +356,7 @@ async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integrat assert state.state == HVAC_MODE_HEAT assert state.attributes[ATTR_TEMPERATURE] == 14 assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT] - assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE - ) + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TARGET_TEMPERATURE client.async_send_command_no_wait.reset_mock() @@ -509,11 +439,7 @@ async def test_thermostat_heatit(hass, client, climate_heatit_z_trm3, integratio assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.9 assert state.attributes[ATTR_TEMPERATURE] == 22.5 assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE - assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE - ) + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TARGET_TEMPERATURE async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integration): @@ -530,4 +456,4 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati HVAC_MODE_HEAT, ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None - assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_PRESET_MODE + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 From eddb97b6fdb3d84b67717d850c46b658d390dab3 Mon Sep 17 00:00:00 2001 From: tdorsey Date: Sat, 13 Mar 2021 15:26:48 -0500 Subject: [PATCH 1239/1818] Fix spelling of automatically in roomba/lutron_caseta components (#47856) --- homeassistant/components/lutron_caseta/translations/en.json | 4 ++-- homeassistant/components/roomba/translations/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 96c00d6cb42b05..48b305ac7faffa 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -23,7 +23,7 @@ "host": "Host" }, "description": "Enter the IP address of the device.", - "title": "Automaticlly connect to the bridge" + "title": "Automatically connect to the bridge" } } }, @@ -73,4 +73,4 @@ "release": "\"{subtype}\" released" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 9c373d649aa81e..11c9c6e27fd442 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -15,7 +15,7 @@ "host": "Host" }, "description": "Select a Roomba or Braava.", - "title": "Automaticlly connect to the device" + "title": "Automatically connect to the device" }, "link": { "description": "Press and hold the Home button on {name} until the device generates a sound (about two seconds).", @@ -59,4 +59,4 @@ } } } -} \ No newline at end of file +} From 263023a152f1885ebbeaa67a8f20e61e8ab5a62b Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sat, 13 Mar 2021 20:32:38 +0000 Subject: [PATCH 1240/1818] Update aiolyric to v1.0.6 (#47871) --- homeassistant/components/lyric/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json index 460eb6e2a3d631..6aa028e26366a5 100644 --- a/homeassistant/components/lyric/manifest.json +++ b/homeassistant/components/lyric/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/lyric", "dependencies": ["http"], - "requirements": ["aiolyric==1.0.5"], + "requirements": ["aiolyric==1.0.6"], "codeowners": ["@timmo001"], "quality_scale": "silver", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 90e1746e524270..eb196269990419 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -197,7 +197,7 @@ aiolifx_effects==0.2.2 aiolip==1.1.4 # homeassistant.components.lyric -aiolyric==1.0.5 +aiolyric==1.0.6 # homeassistant.components.keyboard_remote aionotify==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de07ac79a554f1..d0215a056db646 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -119,7 +119,7 @@ aiokafka==0.6.0 aiolip==1.1.4 # homeassistant.components.lyric -aiolyric==1.0.5 +aiolyric==1.0.6 # homeassistant.components.notion aionotion==1.1.0 From 518c86a0ab68c6a96319132ae0f274790aa83aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Sat, 13 Mar 2021 21:39:04 +0100 Subject: [PATCH 1241/1818] Add device_info to Apple TV entities (#47837) --- homeassistant/components/apple_tv/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 6460a501992309..6a78e0b451c088 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -151,6 +151,13 @@ def should_poll(self): """No polling needed for Apple TV.""" return False + @property + def device_info(self): + """Return the device info.""" + return { + "identifiers": {(DOMAIN, self._identifier)}, + } + class AppleTVManager: """Connection and power manager for an Apple TV. From 7d8eb49d883c43d370e768b53ace4d663b720686 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 13 Mar 2021 16:06:07 -0500 Subject: [PATCH 1242/1818] Bump up ZHA dependency (#47873) --- 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 df3c0fcfda91fc..5c66e2cf605437 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.22.0", + "bellows==0.23.0", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index eb196269990419..607dc94f5af68a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.22.0 +bellows==0.23.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d0215a056db646..e53a94f0cd90b5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.22.0 +bellows==0.23.0 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From c53a462b3db6165a9b6ce20acd6c0f5130384bf4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 13 Mar 2021 22:27:05 +0100 Subject: [PATCH 1243/1818] Fix zwave_js preset mode lookup (#47851) --- homeassistant/components/zwave_js/climate.py | 2 +- tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/conftest.py | 14 + tests/components/zwave_js/test_climate.py | 121 +++ .../climate_eurotronic_spirit_z_state.json | 716 ++++++++++++++++++ 5 files changed, 853 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 161396830115a3..9276ec2ed0b3c9 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -304,7 +304,7 @@ def preset_mode(self) -> Optional[str]: return None if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES: return_val: str = self._current_mode.metadata.states.get( - self._current_mode.value + str(self._current_mode.value) ) return return_val return PRESET_NONE diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index ec54e13940424e..f90a2e48ffad97 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -15,6 +15,7 @@ ) CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat" CLIMATE_DANFOSS_LC13_ENTITY = "climate.living_connect_z_thermostat" +CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY = "climate.thermostatic_valve" CLIMATE_FLOOR_THERMOSTAT_ENTITY = "climate.floor_thermostat" CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner" BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index c9f7d35eb138db..fa7df4e16a17f3 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -228,6 +228,12 @@ def climate_danfoss_lc_13_state_fixture(): return json.loads(load_fixture("zwave_js/climate_danfoss_lc_13_state.json")) +@pytest.fixture(name="climate_eurotronic_spirit_z_state", scope="session") +def climate_eurotronic_spirit_z_state_fixture(): + """Load the climate Eurotronic Spirit Z thermostat node state fixture data.""" + return json.loads(load_fixture("zwave_js/climate_eurotronic_spirit_z_state.json")) + + @pytest.fixture(name="climate_heatit_z_trm3_state", scope="session") def climate_heatit_z_trm3_state_fixture(): """Load the climate HEATIT Z-TRM3 thermostat node state fixture data.""" @@ -419,6 +425,14 @@ def climate_danfoss_lc_13_fixture(client, climate_danfoss_lc_13_state): return node +@pytest.fixture(name="climate_eurotronic_spirit_z") +def climate_eurotronic_spirit_z_fixture(client, climate_eurotronic_spirit_z_state): + """Mock a climate radio danfoss LC-13 node.""" + node = Node(client, climate_eurotronic_spirit_z_state) + client.driver.controller.nodes[node.node_id] = node + return node + + @pytest.fixture(name="climate_heatit_z_trm3") def climate_heatit_z_trm3_fixture(client, climate_heatit_z_trm3_state): """Mock a climate radio HEATIT Z-TRM3 node.""" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 637fb96f1ba1e0..c69804b01584b2 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -9,6 +9,7 @@ ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, + ATTR_PRESET_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, CURRENT_HVAC_IDLE, @@ -18,8 +19,10 @@ HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, + PRESET_NONE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, @@ -34,6 +37,7 @@ from .common import ( CLIMATE_DANFOSS_LC13_ENTITY, + CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, CLIMATE_FLOOR_THERMOSTAT_ENTITY, CLIMATE_MAIN_HEAT_ACTIONNER, CLIMATE_RADIO_THERMOSTAT_ENTITY, @@ -457,3 +461,120 @@ async def test_thermostat_srt321_hrt4_zw(hass, client, srt321_hrt4_zw, integrati ] assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 + + +async def test_preset_and_no_setpoint( + hass, client, climate_eurotronic_spirit_z, integration +): + """Test preset without setpoint value.""" + node = climate_eurotronic_spirit_z + + state = hass.states.get(CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY) + assert state + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 22 + + # Test setting preset mode Full power + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, + ATTR_PRESET_MODE: "Full power", + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 8 + assert args["valueId"] == { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": True, + "writeable": True, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "11": "Energy heat", + "15": "Full power", + }, + }, + "value": 1, + } + assert args["value"] == 15 + + client.async_send_command_no_wait.reset_mock() + + # Test Full power preset update from value updated event + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 8, + "args": { + "commandClassName": "Thermostat Mode", + "commandClass": 64, + "endpoint": 0, + "property": "mode", + "propertyName": "mode", + "newValue": 15, + "prevValue": 1, + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY) + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_TEMPERATURE] is None + assert state.attributes[ATTR_PRESET_MODE] == "Full power" + + with pytest.raises(ValueError): + # Test setting invalid preset mode + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, + ATTR_PRESET_MODE: "invalid_preset", + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 0 + + client.async_send_command_no_wait.reset_mock() + + # Restore hvac mode by setting preset None + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, + ATTR_PRESET_MODE: PRESET_NONE, + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 8 + assert args["valueId"]["commandClass"] == 64 + assert args["valueId"]["endpoint"] == 0 + assert args["valueId"]["property"] == "mode" + assert args["value"] == 1 + + client.async_send_command_no_wait.reset_mock() diff --git a/tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json b/tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json new file mode 100644 index 00000000000000..8dff31a52256a8 --- /dev/null +++ b/tests/fixtures/zwave_js/climate_eurotronic_spirit_z_state.json @@ -0,0 +1,716 @@ +{ + "nodeId": 8, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608, + "status": 4, + "ready": true, + "deviceClass": { + "basic": { + "key": 4, + "label": "Routing Slave" + }, + "generic": { + "key": 8, + "label": "Thermostat" + }, + "specific": { + "key": 6, + "label": "Thermostat General V2" + }, + "mandatorySupportedCCs": [ + "Basic", + "Manufacturer Specific", + "Thermostat Mode", + "Thermostat Setpoint", + "Version" + ], + "mandatoryControlCCs": [] + }, + "isListening": false, + "isFrequentListening": true, + "isRouting": true, + "maxBaudRate": 40000, + "isSecure": false, + "version": 4, + "isBeaming": true, + "manufacturerId": 328, + "productId": 1, + "productType": 3, + "firmwareVersion": "0.16", + "zwavePlusVersion": 1, + "nodeType": 0, + "roleType": 7, + "deviceConfig": { + "manufacturerId": 328, + "manufacturer": "Eurotronics", + "label": "Spirit", + "description": "Thermostatic Valve", + "devices": [ + { + "productType": "0x0003", + "productId": "0x0001" + }, + { + "productType": "0x0003", + "productId": "0x0003" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": { + "_map": {} + } + }, + "label": "Spirit", + "neighbors": [ + 1, + 5, + 9, + 10, + 12, + 18, + 20, + 21, + 22 + ], + "interviewAttempts": 1, + "endpoints": [ + { + "nodeId": 8, + "index": 0, + "installerIcon": 4608, + "userIcon": 4608 + } + ], + "values": [ + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 99, + "label": "Target value" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Transition duration" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 99, + "label": "Current value" + }, + "value": 8 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 1, + "metadata": { + "type": "boolean", + "readable": true, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + } + } + }, + { + "endpoint": 0, + "commandClass": 49, + "commandClassName": "Multilevel Sensor", + "property": "Air temperature", + "propertyName": "Air temperature", + "ccVersion": 5, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "unit": "°C", + "label": "Air temperature", + "ccSpecific": { + "sensorType": 1, + "scale": 0 + } + }, + "value": 23.73 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "mode", + "propertyName": "mode", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 0, + "max": 31, + "label": "Thermostat mode", + "states": { + "0": "Off", + "1": "Heat", + "11": "Energy heat", + "15": "Full power" + } + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 64, + "commandClassName": "Thermostat Mode", + "property": "manufacturerData", + "propertyName": "manufacturerData", + "ccVersion": 3, + "metadata": { + "type": "any", + "readable": true, + "writeable": true + } + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyKey": 1, + "propertyName": "setpoint", + "propertyKeyName": "Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 8, + "max": 28, + "unit": "°C", + "ccSpecific": { + "setpointType": 1 + } + }, + "value": 22 + }, + { + "endpoint": 0, + "commandClass": 67, + "commandClassName": "Thermostat Setpoint", + "property": "setpoint", + "propertyKey": 11, + "propertyName": "setpoint", + "propertyKeyName": "Energy Save Heating", + "ccVersion": 3, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "min": 8, + "max": 28, + "unit": "°C", + "ccSpecific": { + "setpointType": 11 + } + }, + "value": 18 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "LCD Invert", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 0, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "LCD-content normal", + "1": "LCD-content inverted (UK Edition)" + }, + "label": "LCD Invert", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "LCD Timeout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 30, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "LCD Timeout", + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Backlight", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Backlight disabled", + "1": "Backlight enabled" + }, + "label": "Backlight", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Battery report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 1, + "default": 1, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "system notification", + "1": "Send battery status unsolicited once a day." + }, + "label": "Battery report", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Measured Temperature report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 50, + "default": 5, + "format": 0, + "allowManualEntry": true, + "label": "Measured Temperature report", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "Valve opening percentage report", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 100, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Valve opening percentage report", + "isFromConfig": true + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Window open detection", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": 0, + "max": 3, + "default": 2, + "format": 0, + "allowManualEntry": false, + "states": { + "0": "Disabled", + "1": "Sensitivity low", + "2": "Sensitivity medium", + "3": "Sensitivity high" + }, + "label": "Window open detection", + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Temperature Offset", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "valueSize": 1, + "min": -128, + "max": 50, + "default": 0, + "format": 0, + "allowManualEntry": true, + "label": "Temperature Offset", + "description": "Measured Temperature offset", + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "alarmType", + "propertyName": "alarmType", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Alarm Type" + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "alarmLevel", + "propertyName": "alarmLevel", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Alarm Level" + } + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "Power Management", + "propertyKey": "Battery maintenance status", + "propertyName": "Power Management", + "propertyKeyName": "Battery maintenance status", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Battery maintenance status", + "states": { + "0": "idle", + "10": "Replace battery soon", + "11": "Replace battery now" + }, + "ccSpecific": { + "notificationType": 8 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 113, + "commandClassName": "Notification", + "property": "System", + "propertyKey": "Hardware status", + "propertyName": "System", + "propertyKeyName": "Hardware status", + "ccVersion": 8, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 255, + "label": "Hardware status", + "states": { + "0": "idle", + "3": "System hardware failure (with failure code)" + }, + "ccSpecific": { + "notificationType": 9 + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Manufacturer ID" + }, + "value": 328 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 65535, + "label": "Product ID" + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 117, + "commandClassName": "Protection", + "property": "local", + "propertyName": "local", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Local protection state", + "states": { + "0": "Unprotected", + "1": "ProtectedBySequence", + "2": "NoOperationPossible" + } + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 128, + "commandClassName": "Battery", + "property": "level", + "propertyName": "level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "min": 0, + "max": 100, + "unit": "%", + "label": "Battery level" + }, + "value": 90 + }, + { + "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": "any", + "readable": true, + "writeable": false, + "label": "Library type" + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version" + }, + "value": "4.61" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions" + }, + "value": [ + "0.16" + ] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "any", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version" + } + } + ] +} From 47114c5f4f2574eb87186ca5ce655908b6544345 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 14 Mar 2021 08:04:49 +0000 Subject: [PATCH 1244/1818] Update service config for lyric (#47857) --- homeassistant/components/lyric/services.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lyric/services.yaml b/homeassistant/components/lyric/services.yaml index b4ea74a9644f3c..69c802d90aaa2c 100644 --- a/homeassistant/components/lyric/services.yaml +++ b/homeassistant/components/lyric/services.yaml @@ -1,9 +1,18 @@ set_hold_time: + name: Set Hold Time description: "Sets the time to hold until" + target: + device: + integration: lyric + entity: + integration: lyric + domain: climate fields: - entity_id: - description: Name(s) of entities to change - example: "climate.thermostat" time_period: + name: Time Period description: Time to hold until + default: "01:00:00" example: "01:00:00" + required: true + selector: + text: From 984f02882bb867143cbf732ebfde0dc1ef85911d Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 14 Mar 2021 08:05:47 +0000 Subject: [PATCH 1245/1818] Add HVAC action to Lyric climate platform (#47876) --- homeassistant/components/lyric/climate.py | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 41e8fa90b671e5..6424083158a37a 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -11,6 +11,10 @@ from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, @@ -41,6 +45,10 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE +LYRIC_HVAC_ACTION_OFF = "EquipmentOff" +LYRIC_HVAC_ACTION_HEAT = "Heat" +LYRIC_HVAC_ACTION_COOL = "Cool" + LYRIC_HVAC_MODE_OFF = "Off" LYRIC_HVAC_MODE_HEAT = "Heat" LYRIC_HVAC_MODE_COOL = "Cool" @@ -60,6 +68,20 @@ LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, } + +HVAC_ACTIONS = { + LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF, + LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT, + LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL, +} + +HVAC_MODES = { + LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF, + LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT, + LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL, + LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, +} + SERVICE_HOLD_TIME = "set_hold_time" ATTR_TIME_PERIOD = "time_period" @@ -152,6 +174,14 @@ def current_temperature(self) -> Optional[float]: """Return the current temperature.""" return self.device.indoorTemperature + @property + def hvac_action(self) -> str: + """Return the current hvac action.""" + action = HVAC_ACTIONS.get(self.device.operationStatus.mode, None) + if action == CURRENT_HVAC_OFF and self.hvac_mode != HVAC_MODE_OFF: + action = CURRENT_HVAC_IDLE + return action + @property def hvac_mode(self) -> str: """Return the hvac mode.""" From 60838cf7edd82c9165dd323325148bd35969ecbc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 14 Mar 2021 10:38:09 +0100 Subject: [PATCH 1246/1818] Verisure: Remove JSONPath, unique IDs, small cleanups (#47870) Co-authored-by: Martin Hjelmare --- homeassistant/components/verisure/__init__.py | 137 +++--------------- .../verisure/alarm_control_panel.py | 14 +- .../components/verisure/binary_sensor.py | 51 ++++--- homeassistant/components/verisure/camera.py | 50 ++++--- .../components/verisure/coordinator.py | 126 ++++++++++++++++ homeassistant/components/verisure/lock.py | 42 ++---- .../components/verisure/manifest.json | 2 +- homeassistant/components/verisure/sensor.py | 121 +++++++--------- homeassistant/components/verisure/switch.py | 32 ++-- requirements_all.txt | 1 - requirements_test_all.txt | 1 - 11 files changed, 287 insertions(+), 290 deletions(-) create mode 100644 homeassistant/components/verisure/coordinator.py diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 00d970f133f6d4..16250915f457ec 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,30 +1,27 @@ """Support for Verisure devices.""" from __future__ import annotations -from datetime import timedelta -from typing import Any, Literal - -from jsonpath import jsonpath -from verisure import ( - Error as VerisureError, - ResponseError as VerisureResponseError, - Session as Verisure, -) +from verisure import Error as VerisureError import voluptuous as vol +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.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, - HTTP_SERVICE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.util import Throttle from .const import ( ATTR_DEVICE_SERIAL, @@ -47,14 +44,15 @@ SERVICE_DISABLE_AUTOLOCK, SERVICE_ENABLE_AUTOLOCK, ) +from .coordinator import VerisureDataUpdateCoordinator PLATFORMS = [ - "sensor", - "switch", - "alarm_control_panel", - "lock", - "camera", - "binary_sensor", + ALARM_CONTROL_PANEL_DOMAIN, + BINARY_SENSOR_DOMAIN, + CAMERA_DOMAIN, + LOCK_DOMAIN, + SENSOR_DOMAIN, + SWITCH_DOMAIN, ] CONFIG_SCHEMA = vol.Schema( @@ -88,18 +86,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Verisure integration.""" - verisure = Verisure(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) - coordinator = VerisureDataUpdateCoordinator( - hass, session=verisure, domain_config=config[DOMAIN] - ) + coordinator = VerisureDataUpdateCoordinator(hass, config=config[DOMAIN]) - if not await hass.async_add_executor_job(coordinator.login): + if not await coordinator.async_login(): LOGGER.error("Login failed") return False - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, lambda event: coordinator.logout() - ) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) await coordinator.async_refresh() if not coordinator.last_update_success: @@ -152,95 +145,3 @@ async def enable_autolock(service): DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA ) return True - - -class VerisureDataUpdateCoordinator(DataUpdateCoordinator): - """A Verisure Data Update Coordinator.""" - - def __init__( - self, hass: HomeAssistant, domain_config: ConfigType, session: Verisure - ) -> None: - """Initialize the Verisure hub.""" - self.imageseries = {} - self.config = domain_config - self.giid = domain_config.get(CONF_GIID) - - self.session = session - - super().__init__( - hass, LOGGER, name=DOMAIN, update_interval=domain_config[CONF_SCAN_INTERVAL] - ) - - def login(self) -> bool: - """Login to Verisure.""" - try: - self.session.login() - except VerisureError as ex: - LOGGER.error("Could not log in to verisure, %s", ex) - return False - if self.giid: - return self.set_giid() - return True - - def logout(self) -> bool: - """Logout from Verisure.""" - try: - self.session.logout() - except VerisureError as ex: - LOGGER.error("Could not log out from verisure, %s", ex) - return False - return True - - def set_giid(self) -> bool: - """Set installation GIID.""" - try: - self.session.set_giid(self.giid) - except VerisureError as ex: - LOGGER.error("Could not set installation GIID, %s", ex) - return False - return True - - async def _async_update_data(self) -> dict: - """Fetch data from Verisure.""" - try: - return await self.hass.async_add_executor_job(self.session.get_overview) - except VerisureResponseError as ex: - LOGGER.error("Could not read overview, %s", ex) - if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable - LOGGER.info("Trying to log in again") - await self.hass.async_add_executor_job(self.login) - return {} - raise - - @Throttle(timedelta(seconds=60)) - def update_smartcam_imageseries(self) -> None: - """Update the image series.""" - self.imageseries = self.session.get_camera_imageseries() - - @Throttle(timedelta(seconds=30)) - def smartcam_capture(self, device_id: str) -> None: - """Capture a new image from a smartcam.""" - self.session.capture_image(device_id) - - def disable_autolock(self, device_id: str) -> None: - """Disable autolock.""" - self.session.set_lock_config(device_id, auto_lock_enabled=False) - - def enable_autolock(self, device_id: str) -> None: - """Enable autolock.""" - self.session.set_lock_config(device_id, auto_lock_enabled=True) - - def get(self, jpath: str, *args) -> list[Any] | Literal[False]: - """Get values from the overview that matches the jsonpath.""" - res = jsonpath(self.data, jpath % args) - return res or [] - - def get_first(self, jpath: str, *args) -> Any | None: - """Get first value from the overview that matches the jsonpath.""" - res = self.get(jpath, *args) - return res[0] if res else None - - def get_image_info(self, jpath: str, *args) -> list[Any] | Literal[False]: - """Get values from the imageseries that matches the jsonpath.""" - res = jsonpath(self.imageseries, jpath % args) - return res or [] diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index d0a93fb45f925e..94fbfe69bd0dbb 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -22,8 +22,8 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( @@ -56,19 +56,19 @@ def name(self) -> str: giid = self.coordinator.config.get(CONF_GIID) if giid is not None: aliass = { - i["giid"]: i["alias"] for i in self.coordinator.session.installations + i["giid"]: i["alias"] for i in self.coordinator.verisure.installations } if giid in aliass: return "{} alarm".format(aliass[giid]) LOGGER.error("Verisure installation giid not found: %s", giid) - return "{} alarm".format(self.coordinator.session.installations[0]["alias"]) + return "{} alarm".format(self.coordinator.verisure.installations[0]["alias"]) @property def state(self) -> str | None: """Return the state of the device.""" - status = self.coordinator.get_first("$.armState.statusType") + status = self.coordinator.data["alarm"]["statusType"] if status == "DISARMED": self._state = STATE_ALARM_DISARMED elif status == "ARMED_HOME": @@ -95,19 +95,19 @@ def code_format(self) -> str: @property def changed_by(self) -> str | None: """Return the last change triggered by.""" - return self.coordinator.get_first("$.armState.name") + return self.coordinator.data["alarm"]["name"] async def _async_set_arm_state(self, state: str, code: str | None = None) -> None: """Send set arm state command.""" arm_state = await self.hass.async_add_executor_job( - self.coordinator.session.set_arm_state, code, state + self.coordinator.verisure.set_arm_state, code, state ) LOGGER.debug("Verisure set arm state %s", state) transaction = {} while "result" not in transaction: await asyncio.sleep(0.5) transaction = await self.hass.async_add_executor_job( - self.coordinator.session.get_arm_state_transaction, + self.coordinator.verisure.get_arm_state_transaction, arm_state["armStateChangeTransactionId"], ) diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index bdefa2af85888f..66eb5031072311 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -5,33 +5,32 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_OPENING, BinarySensorEntity, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_DOOR_WINDOW, DOMAIN, VerisureDataUpdateCoordinator +from . import CONF_DOOR_WINDOW, DOMAIN +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[CoordinatorEntity]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure binary sensors.""" coordinator = hass.data[DOMAIN] - sensors = [VerisureEthernetStatus(coordinator)] + sensors: list[CoordinatorEntity] = [VerisureEthernetStatus(coordinator)] if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)): sensors.extend( [ - VerisureDoorWindowSensor(coordinator, device_label) - for device_label in coordinator.get( - "$.doorWindow.doorWindowDevice[*].deviceLabel" - ) + VerisureDoorWindowSensor(coordinator, serial_number) + for serial_number in coordinator.data["door_window"] ] ) @@ -44,40 +43,40 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure door window sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: """Return the name of the binary sensor.""" - return self.coordinator.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", - self._device_label, - ) + return self.coordinator.data["door_window"][self.serial_number]["area"] + + @property + def unique_id(self) -> str: + """Return the unique ID for this alarm control panel.""" + return f"{self.serial_number}_door_window" + + @property + def device_class(self) -> str: + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_OPENING @property def is_on(self) -> bool: """Return the state of the sensor.""" return ( - self.coordinator.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state", - self._device_label, - ) - == "OPEN" + self.coordinator.data["door_window"][self.serial_number]["state"] == "OPEN" ) @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", - self._device_label, - ) - is not None + super().available + and self.serial_number in self.coordinator.data["door_window"] ) @@ -94,12 +93,12 @@ def name(self) -> str: @property def is_on(self) -> bool: """Return the state of the sensor.""" - return self.coordinator.get_first("$.ethernetConnectedNow") + return self.coordinator.data["ethernet"] @property def available(self) -> bool: """Return True if entity is available.""" - return self.coordinator.get_first("$.ethernetConnectedNow") is not None + return super().available and self.coordinator.data["ethernet"] is not None @property def device_class(self) -> str: diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 4e15b7a88b2fb0..6f22b17b848523 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -8,33 +8,28 @@ from homeassistant.components.camera import Camera from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_SMARTCAM, DOMAIN, LOGGER +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[VerisureSmartcam]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure Camera.""" - coordinator = hass.data[DOMAIN] + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN] if not int(coordinator.config.get(CONF_SMARTCAM, 1)): return - directory_path = hass.config.config_dir - if not os.access(directory_path, os.R_OK): - LOGGER.error("file path %s is not readable", directory_path) - return - + assert hass.config.config_dir add_entities( [ - VerisureSmartcam(hass, coordinator, device_label, directory_path) - for device_label in coordinator.get("$.customerImageCameras[*].deviceLabel") + VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) + for serial_number in coordinator.data["cameras"] ] ) @@ -48,13 +43,13 @@ def __init__( self, hass: HomeAssistant, coordinator: VerisureDataUpdateCoordinator, - device_label: str, + serial_number: str, directory_path: str, ): """Initialize Verisure File Camera component.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number self._directory_path = directory_path self._image = None self._image_id = None @@ -73,21 +68,27 @@ def camera_image(self) -> bytes | None: def check_imagelist(self) -> None: """Check the contents of the image list.""" self.coordinator.update_smartcam_imageseries() - image_ids = self.coordinator.get_image_info( - "$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label - ) - if not image_ids: + + images = self.coordinator.imageseries.get("imageSeries", []) + new_image_id = None + for image in images: + if image["deviceLabel"] == self.serial_number: + new_image_id = image["image"][0]["imageId"] + break + + if not new_image_id: return - new_image_id = image_ids[0] + if new_image_id in ("-1", self._image_id): LOGGER.debug("The image is the same, or loading image_id") return + LOGGER.debug("Download new image %s", new_image_id) new_image_path = os.path.join( self._directory_path, "{}{}".format(new_image_id, ".jpg") ) - self.coordinator.session.download_image( - self._device_label, new_image_id, new_image_path + self.coordinator.verisure.download_image( + self.serial_number, new_image_id, new_image_path ) LOGGER.debug("Old image_id=%s", self._image_id) self.delete_image() @@ -110,6 +111,9 @@ def delete_image(self) -> None: @property def name(self) -> str: """Return the name of this camera.""" - return self.coordinator.get_first( - "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label - ) + return self.coordinator.data["cameras"][self.serial_number]["area"] + + @property + def unique_id(self) -> str: + """Return the unique ID for this camera.""" + return self.serial_number diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py new file mode 100644 index 00000000000000..9de81429c5c9b6 --- /dev/null +++ b/homeassistant/components/verisure/coordinator.py @@ -0,0 +1,126 @@ +"""DataUpdateCoordinator for the Verisure integration.""" +from __future__ import annotations + +from datetime import timedelta + +from verisure import ( + Error as VerisureError, + ResponseError as VerisureResponseError, + Session as Verisure, +) + +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_SERVICE_UNAVAILABLE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util import Throttle + +from .const import CONF_GIID, DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER + + +class VerisureDataUpdateCoordinator(DataUpdateCoordinator): + """A Verisure Data Update Coordinator.""" + + def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: + """Initialize the Verisure hub.""" + self.imageseries = {} + self.config = config + self.giid = config.get(CONF_GIID) + + self.verisure = Verisure( + username=config[CONF_USERNAME], password=config[CONF_PASSWORD] + ) + + super().__init__( + hass, LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL + ) + + async def async_login(self) -> bool: + """Login to Verisure.""" + try: + await self.hass.async_add_executor_job(self.verisure.login) + except VerisureError as ex: + LOGGER.error("Could not log in to verisure, %s", ex) + return False + if self.giid: + return await self.async_set_giid() + return True + + async def async_logout(self) -> bool: + """Logout from Verisure.""" + try: + await self.hass.async_add_executor_job(self.verisure.logout) + except VerisureError as ex: + LOGGER.error("Could not log out from verisure, %s", ex) + return False + return True + + async def async_set_giid(self) -> bool: + """Set installation GIID.""" + try: + await self.hass.async_add_executor_job(self.verisure.set_giid, self.giid) + except VerisureError as ex: + LOGGER.error("Could not set installation GIID, %s", ex) + return False + return True + + async def _async_update_data(self) -> dict: + """Fetch data from Verisure.""" + try: + overview = await self.hass.async_add_executor_job( + self.verisure.get_overview + ) + except VerisureResponseError as ex: + LOGGER.error("Could not read overview, %s", ex) + if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable + LOGGER.info("Trying to log in again") + await self.async_login() + return {} + raise + + # Store data in a way Home Assistant can easily consume it + return { + "alarm": overview["armState"], + "ethernet": overview.get("ethernetConnectedNow"), + "cameras": { + device["deviceLabel"]: device + for device in overview["customerImageCameras"] + }, + "climate": { + device["deviceLabel"]: device for device in overview["climateValues"] + }, + "door_window": { + device["deviceLabel"]: device + for device in overview["doorWindow"]["doorWindowDevice"] + }, + "locks": { + device["deviceLabel"]: device + for device in overview["doorLockStatusList"] + }, + "mice": { + device["deviceLabel"]: device + for device in overview["eventCounts"] + if device["deviceType"] == "MOUSE1" + }, + "smart_plugs": { + device["deviceLabel"]: device for device in overview["smartPlugs"] + }, + } + + @Throttle(timedelta(seconds=60)) + def update_smartcam_imageseries(self) -> None: + """Update the image series.""" + self.imageseries = self.verisure.get_camera_imageseries() + + @Throttle(timedelta(seconds=30)) + def smartcam_capture(self, device_id: str) -> None: + """Capture a new image from a smartcam.""" + self.verisure.capture_image(device_id) + + def disable_autolock(self, device_id: str) -> None: + """Disable autolock.""" + self.verisure.set_lock_config(device_id, auto_lock_enabled=False) + + def enable_autolock(self, device_id: str) -> None: + """Enable autolock.""" + self.verisure.set_lock_config(device_id, auto_lock_enabled=True) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 8fc067308fb996..99118850117c40 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -7,17 +7,16 @@ from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[VerisureDoorlock]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure lock platform.""" @@ -26,10 +25,8 @@ def setup_platform( if int(coordinator.config.get(CONF_LOCKS, 1)): locks.extend( [ - VerisureDoorlock(coordinator, device_label) - for device_label in coordinator.get( - "$.doorLockStatusList[*].deviceLabel" - ) + VerisureDoorlock(coordinator, serial_number) + for serial_number in coordinator.data["locks"] ] ) @@ -42,11 +39,11 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure lock.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number self._state = None self._digits = coordinator.config.get(CONF_CODE_DIGITS) self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE) @@ -54,27 +51,19 @@ def __init__( @property def name(self) -> str: """Return the name of the lock.""" - return self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].area", self._device_label - ) + return self.coordinator.data["locks"][self.serial_number]["area"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')]", self._device_label - ) - is not None + super().available and self.serial_number in self.coordinator.data["locks"] ) @property def changed_by(self) -> str | None: """Last change triggered by.""" - return self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].userString", - self._device_label, - ) + return self.coordinator.data["locks"][self.serial_number].get("userString") @property def code_format(self) -> str: @@ -84,11 +73,10 @@ def code_format(self) -> str: @property def is_locked(self) -> bool: """Return true if lock is locked.""" - status = self.coordinator.get_first( - "$.doorLockStatusList[?(@.deviceLabel=='%s')].lockedState", - self._device_label, + return ( + self.coordinator.data["locks"][self.serial_number]["lockedState"] + == "LOCKED" ) - return status == "LOCKED" async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" @@ -112,9 +100,9 @@ async def async_set_lock_state(self, code: str, state: str) -> None: """Send set lock state command.""" target_state = "lock" if state == STATE_LOCKED else "unlock" lock_state = await self.hass.async_add_executor_job( - self.coordinator.session.set_lock_state, + self.coordinator.verisure.set_lock_state, code, - self._device_label, + self.serial_number, target_state, ) @@ -123,7 +111,7 @@ async def async_set_lock_state(self, code: str, state: str) -> None: attempts = 0 while "result" not in transaction: transaction = await self.hass.async_add_executor_job( - self.coordinator.session.get_lock_state_transaction, + self.coordinator.verisure.get_lock_state_transaction, lock_state["doorLockStateChangeTransactionId"], ) attempts += 1 diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 814b5f148fa049..744f7fb706c6aa 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -2,6 +2,6 @@ "domain": "verisure", "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", - "requirements": ["jsonpath==0.82", "vsure==1.7.2"], + "requirements": ["vsure==1.7.2"], "codeowners": ["@frenck"] } diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 483d03a1bb5231..2a4e47593698d9 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -8,47 +8,43 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[CoordinatorEntity], bool], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure platform.""" coordinator = hass.data[DOMAIN] - sensors = [] + sensors: list[CoordinatorEntity] = [] if int(coordinator.config.get(CONF_THERMOMETERS, 1)): sensors.extend( [ - VerisureThermometer(coordinator, device_label) - for device_label in coordinator.get( - "$.climateValues[?(@.temperature)].deviceLabel" - ) + VerisureThermometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "temperature" in values ] ) if int(coordinator.config.get(CONF_HYDROMETERS, 1)): sensors.extend( [ - VerisureHygrometer(coordinator, device_label) - for device_label in coordinator.get( - "$.climateValues[?(@.humidity)].deviceLabel" - ) + VerisureHygrometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "humidity" in values ] ) if int(coordinator.config.get(CONF_MOUSE, 1)): sensors.extend( [ - VerisureMouseDetection(coordinator, device_label) - for device_label in coordinator.get( - "$.eventCounts[?(@.deviceType=='MOUSE1')].deviceLabel" - ) + VerisureMouseDetection(coordinator, serial_number) + for serial_number in coordinator.data["mice"] ] ) @@ -61,38 +57,35 @@ class VerisureThermometer(CoordinatorEntity, Entity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: - """Return the name of the device.""" - return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label - ) - + " temperature" - ) + """Return the name of the entity.""" + name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return f"{name} Temperature" + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return f"{self.serial_number}_temperature" @property def state(self) -> str | None: - """Return the state of the device.""" - return self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].temperature", self._device_label - ) + """Return the state of the entity.""" + return self.coordinator.data["climate"][self.serial_number]["temperature"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].temperature", - self._device_label, - ) - is not None + super().available + and self.serial_number in self.coordinator.data["climate"] + and "temperature" in self.coordinator.data["climate"][self.serial_number] ) @property @@ -107,37 +100,35 @@ class VerisureHygrometer(CoordinatorEntity, Entity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: - """Return the name of the device.""" - return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].deviceArea", self._device_label - ) - + " humidity" - ) + """Return the name of the entity.""" + name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return f"{name} Humidity" + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return f"{self.serial_number}_humidity" @property def state(self) -> str | None: - """Return the state of the device.""" - return self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label - ) + """Return the state of the entity.""" + return self.coordinator.data["climate"][self.serial_number]["humidity"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.climateValues[?(@.deviceLabel=='%s')].humidity", self._device_label - ) - is not None + super().available + and self.serial_number in self.coordinator.data["climate"] + and "humidity" in self.coordinator.data["climate"][self.serial_number] ) @property @@ -152,37 +143,35 @@ class VerisureMouseDetection(CoordinatorEntity, Entity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_label: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._device_label = device_label + self.serial_number = serial_number @property def name(self) -> str: - """Return the name of the device.""" - return ( - self.coordinator.get_first( - "$.eventCounts[?(@.deviceLabel=='%s')].area", self._device_label - ) - + " mouse" - ) + """Return the name of the entity.""" + name = self.coordinator.data["mice"][self.serial_number]["area"] + return f"{name} Mouse" + + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return f"{self.serial_number}_mice" @property def state(self) -> str | None: """Return the state of the device.""" - return self.coordinator.get_first( - "$.eventCounts[?(@.deviceLabel=='%s')].detections", self._device_label - ) + return self.coordinator.data["mice"][self.serial_number]["detections"] @property def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.eventCounts[?(@.deviceLabel=='%s')]", self._device_label - ) - is not None + super().available + and self.serial_number in self.coordinator.data["mice"] + and "detections" in self.coordinator.data["mice"][self.serial_number] ) @property diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 9329d94331a087..9ce0d3ce5df9a4 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -6,17 +6,16 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import VerisureDataUpdateCoordinator from .const import CONF_SMARTPLUGS, DOMAIN +from .coordinator import VerisureDataUpdateCoordinator def setup_platform( hass: HomeAssistant, config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], + add_entities: Callable[[list[CoordinatorEntity]], None], discovery_info: dict[str, Any] | None = None, ) -> None: """Set up the Verisure switch platform.""" @@ -27,8 +26,8 @@ def setup_platform( add_entities( [ - VerisureSmartplug(coordinator, device_label) - for device_label in coordinator.get("$.smartPlugs[*].deviceLabel") + VerisureSmartplug(coordinator, serial_number) + for serial_number in coordinator.data["smart_plugs"] ] ) @@ -39,20 +38,18 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity): coordinator: VerisureDataUpdateCoordinator def __init__( - self, coordinator: VerisureDataUpdateCoordinator, device_id: str + self, coordinator: VerisureDataUpdateCoordinator, serial_number: str ) -> None: """Initialize the Verisure device.""" super().__init__(coordinator) - self._device_label = device_id + self.serial_number = serial_number self._change_timestamp = 0 self._state = False @property def name(self) -> str: """Return the name or location of the smartplug.""" - return self.coordinator.get_first( - "$.smartPlugs[?(@.deviceLabel == '%s')].area", self._device_label - ) + return self.coordinator.data["smart_plugs"][self.serial_number]["area"] @property def is_on(self) -> bool: @@ -60,10 +57,7 @@ def is_on(self) -> bool: if monotonic() - self._change_timestamp < 10: return self._state self._state = ( - self.coordinator.get_first( - "$.smartPlugs[?(@.deviceLabel == '%s')].currentState", - self._device_label, - ) + self.coordinator.data["smart_plugs"][self.serial_number]["currentState"] == "ON" ) return self._state @@ -72,20 +66,18 @@ def is_on(self) -> bool: def available(self) -> bool: """Return True if entity is available.""" return ( - self.coordinator.get_first( - "$.smartPlugs[?(@.deviceLabel == '%s')]", self._device_label - ) - is not None + super().available + and self.serial_number in self.coordinator.data["smart_plugs"] ) def turn_on(self, **kwargs) -> None: """Set smartplug status on.""" - self.coordinator.session.set_smartplug_state(self._device_label, True) + self.coordinator.verisure.set_smartplug_state(self.serial_number, True) self._state = True self._change_timestamp = monotonic() def turn_off(self, **kwargs) -> None: """Set smartplug status off.""" - self.coordinator.session.set_smartplug_state(self._device_label, False) + self.coordinator.verisure.set_smartplug_state(self.serial_number, False) self._state = False self._change_timestamp = monotonic() diff --git a/requirements_all.txt b/requirements_all.txt index 607dc94f5af68a..9840e88a1ca2ea 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -829,7 +829,6 @@ influxdb==5.2.3 iperf3==0.1.11 # homeassistant.components.rest -# homeassistant.components.verisure jsonpath==0.82 # homeassistant.components.kaiterra diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e53a94f0cd90b5..60a62290db752a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -446,7 +446,6 @@ influxdb-client==1.14.0 influxdb==5.2.3 # homeassistant.components.rest -# homeassistant.components.verisure jsonpath==0.82 # homeassistant.components.konnected From 50b5fc486082d101f0c40ca06bdc0522716af2c3 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 14 Mar 2021 12:32:19 +0100 Subject: [PATCH 1247/1818] Add Xiaomi Miio subdevice lightbulb support (#46660) * Xiaomi Miio: add subdevice lightbulb support * fix tests * process revieuw comments * bump python-miio to 0.5.5 * bump python-miio to 0.5.5 * fix imports --- .../components/xiaomi_miio/__init__.py | 2 +- homeassistant/components/xiaomi_miio/light.py | 66 ++++++++++++++++++- .../components/xiaomi_miio/manifest.json | 2 +- .../components/xiaomi_miio/sensor.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 069520ada7db59..e426322e5dc802 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from miio.gateway import GatewayException +from miio.gateway.gateway import GatewayException from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_TOKEN diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index c6d8b67bb07601..f6cd468ad00dfb 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -7,7 +7,7 @@ from math import ceil from miio import Ceil, DeviceException, PhilipsBulb, PhilipsEyecare, PhilipsMoonlight -from miio.gateway import ( +from miio.gateway.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, GATEWAY_MODEL_AC_V3, @@ -36,6 +36,7 @@ CONF_GATEWAY, CONF_MODEL, DOMAIN, + KEY_COORDINATOR, MODELS_LIGHT, MODELS_LIGHT_BULB, MODELS_LIGHT_CEILING, @@ -52,6 +53,7 @@ SERVICE_SET_SCENE, ) from .device import XiaomiMiioEntity +from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) @@ -148,6 +150,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( XiaomiGatewayLight(gateway, config_entry.title, config_entry.unique_id) ) + # Gateway sub devices + sub_devices = gateway.devices + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] + for sub_device in sub_devices.values(): + if sub_device.device_type == "LightBulb": + entities.append( + XiaomiGatewayBulb(coordinator, sub_device, config_entry) + ) if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: if DATA_KEY not in hass.data: @@ -1042,3 +1052,57 @@ async def async_update(self): self._brightness_pct = state_dict["brightness"] self._rgb = state_dict["rgb"] self._hs = color.color_RGB_to_hs(*self._rgb) + + +class XiaomiGatewayBulb(XiaomiGatewayDevice, LightEntity): + """Representation of Xiaomi Gateway Bulb.""" + + @property + def brightness(self): + """Return the brightness of the light.""" + return round((self._sub_device.status["brightness"] * 255) / 100) + + @property + def color_temp(self): + """Return current color temperature.""" + return self._sub_device.status["color_temp"] + + @property + def is_on(self): + """Return true if light is on.""" + return self._sub_device.status["status"] == "on" + + @property + def min_mireds(self): + """Return min cct.""" + return self._sub_device.status["cct_min"] + + @property + def max_mireds(self): + """Return max cct.""" + return self._sub_device.status["cct_max"] + + @property + def supported_features(self): + """Return the supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + + async def async_turn_on(self, **kwargs): + """Instruct the light to turn on.""" + await self.hass.async_add_executor_job(self._sub_device.on) + + if ATTR_COLOR_TEMP in kwargs: + color_temp = kwargs[ATTR_COLOR_TEMP] + await self.hass.async_add_executor_job( + self._sub_device.set_color_temp, color_temp + ) + + if ATTR_BRIGHTNESS in kwargs: + brightness = round((kwargs[ATTR_BRIGHTNESS] * 100) / 255) + await self.hass.async_add_executor_job( + self._sub_device.set_brightness, brightness + ) + + async def async_turn_off(self, **kwargsf): + """Instruct the light to turn off.""" + await self.hass.async_add_executor_job(self._sub_device.off) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 2536b0e0aa7ab5..6f8069be681b8a 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", "python-miio==0.5.4"], + "requirements": ["construct==2.10.56", "python-miio==0.5.5"], "codeowners": ["@rytilahti", "@syssi", "@starkillerOG"], "zeroconf": ["_miio._udp.local."] } diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 5769c1fb475cf5..cdb38ba95156bd 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -3,7 +3,7 @@ import logging from miio import AirQualityMonitor, DeviceException -from miio.gateway import ( +from miio.gateway.gateway import ( GATEWAY_MODEL_AC_V1, GATEWAY_MODEL_AC_V2, GATEWAY_MODEL_AC_V3, diff --git a/requirements_all.txt b/requirements_all.txt index 9840e88a1ca2ea..d37eb5571fc3e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1795,7 +1795,7 @@ python-juicenet==1.0.1 # python-lirc==1.2.3 # homeassistant.components.xiaomi_miio -python-miio==0.5.4 +python-miio==0.5.5 # homeassistant.components.mpd python-mpd2==3.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 60a62290db752a..564a3b49e09705 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -935,7 +935,7 @@ python-izone==1.1.4 python-juicenet==1.0.1 # homeassistant.components.xiaomi_miio -python-miio==0.5.4 +python-miio==0.5.5 # homeassistant.components.nest python-nest==4.1.0 From fea944bcead3dbac3107f3a07aece8985af36cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Sun, 14 Mar 2021 13:44:07 +0100 Subject: [PATCH 1248/1818] Upgrade Tibber library to 0.16.2 (#47892) --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 652804859dafc1..108f05d5625155 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -2,7 +2,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.16.1"], + "requirements": ["pyTibber==0.16.2"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true diff --git a/requirements_all.txt b/requirements_all.txt index d37eb5571fc3e2..e68c490c12637e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1235,7 +1235,7 @@ pyRFXtrx==0.26.1 # pySwitchmate==0.4.6 # homeassistant.components.tibber -pyTibber==0.16.1 +pyTibber==0.16.2 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 564a3b49e09705..b42db89bded0aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -645,7 +645,7 @@ pyMetno==0.8.1 pyRFXtrx==0.26.1 # homeassistant.components.tibber -pyTibber==0.16.1 +pyTibber==0.16.2 # homeassistant.components.nextbus py_nextbusnext==0.1.4 From 4d61f6f8c2526b9b9e49563ef0e2d2c77c390ea4 Mon Sep 17 00:00:00 2001 From: jugla <59493499+jugla@users.noreply.github.com> Date: Sun, 14 Mar 2021 16:04:08 +0100 Subject: [PATCH 1249/1818] Reduce number of iqair request (#47890) --- homeassistant/components/airvisual/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 6254d533a0fbcd..7450ff2afcf967 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -80,9 +80,9 @@ def async_get_cloud_api_update_interval(hass, api_key, num_consumers): This will shift based on the number of active consumers, thus keeping the user under the monthly API limit. """ - # Assuming 10,000 calls per month and a "smallest possible month" of 28 days; note + # Assuming 10,000 calls per month and a "largest possible month" of 31 days; note # that we give a buffer of 1500 API calls for any drift, restarts, etc.: - minutes_between_api_calls = ceil(1 / (8500 / 28 / 24 / 60 / num_consumers)) + minutes_between_api_calls = ceil(num_consumers * 31 * 24 * 60 / 8500) LOGGER.debug( "Leveling API key usage (%s): %s consumers, %s minutes between updates", From d32c364d7f9e138e0dd9363b34b3cb39f4afcd06 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sun, 14 Mar 2021 18:52:47 +0100 Subject: [PATCH 1250/1818] Update pyhomematic to 0.1.72 (#47906) --- homeassistant/components/homematic/const.py | 4 ++++ homeassistant/components/homematic/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematic/const.py b/homeassistant/components/homematic/const.py index e8fa272b0e57c4..864441c2aa68f2 100644 --- a/homeassistant/components/homematic/const.py +++ b/homeassistant/components/homematic/const.py @@ -60,6 +60,7 @@ "IPWSwitch", "IOSwitchWireless", "IPWIODevice", + "IPSwitchBattery", ], DISCOVER_LIGHTS: [ "Dimmer", @@ -119,6 +120,8 @@ "ValveBox", "IPKeyBlind", "IPKeyBlindTilt", + "IPLanRouter", + "TempModuleSTE2", ], DISCOVER_CLIMATE: [ "Thermostat", @@ -163,6 +166,7 @@ "IPWMotionDection", "IPAlarmSensor", "IPRainSensor", + "IPLanRouter", ], DISCOVER_COVER: [ "Blind", diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json index 36414b606f9990..d81dc97cdb7669 100644 --- a/homeassistant/components/homematic/manifest.json +++ b/homeassistant/components/homematic/manifest.json @@ -2,6 +2,6 @@ "domain": "homematic", "name": "Homematic", "documentation": "https://www.home-assistant.io/integrations/homematic", - "requirements": ["pyhomematic==0.1.71"], + "requirements": ["pyhomematic==0.1.72"], "codeowners": ["@pvizeli", "@danielperna84"] } diff --git a/requirements_all.txt b/requirements_all.txt index e68c490c12637e..a170b00e617add 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1431,7 +1431,7 @@ pyhik==0.2.8 pyhiveapi==0.3.4.4 # homeassistant.components.homematic -pyhomematic==0.1.71 +pyhomematic==0.1.72 # homeassistant.components.homeworks pyhomeworks==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b42db89bded0aa..3104ba205dec5b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -748,7 +748,7 @@ pyhaversion==3.4.2 pyheos==0.7.2 # homeassistant.components.homematic -pyhomematic==0.1.71 +pyhomematic==0.1.72 # homeassistant.components.icloud pyicloud==0.10.2 From 9dabc988fbd8fd1219f947372cb0296952e87273 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 14 Mar 2021 23:48:47 +0000 Subject: [PATCH 1251/1818] Bump frontend to 20210314.0 --- 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 a254c7d129af6f..9d6bf462a48903 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==20210313.0" + "home-assistant-frontend==20210314.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c604ab5c706b7b..8bb7d1cbad5ab6 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210313.0 +home-assistant-frontend==20210314.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a170b00e617add..e3404081073b41 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210313.0 +home-assistant-frontend==20210314.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3104ba205dec5b..f3ee7613da49c4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210313.0 +home-assistant-frontend==20210314.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 61a2460c875b5386c1331cdebce1cdd93b76b00e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Mar 2021 16:46:21 -1000 Subject: [PATCH 1252/1818] Improve error reporting in recorder purge test (#47929) --- homeassistant/components/recorder/models.py | 9 ++++++--- tests/components/recorder/test_purge.py | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 6ed25e64eda1f3..ef7181c9c03ed9 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -207,11 +207,14 @@ class RecorderRuns(Base): # type: ignore def __repr__(self) -> str: """Return string representation of instance for debugging.""" + end = ( + f"'{self.end.isoformat(sep=' ', timespec='seconds')}'" if self.end else None + ) return ( f"" ) diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index db3906595dbe57..e6bc3a99a97989 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -105,6 +105,8 @@ async def test_purge_method( await _add_test_events(hass, instance) await _add_test_states(hass, instance) await _add_test_recorder_runs(hass, instance) + await hass.async_block_till_done() + await async_wait_recording_done(hass, instance) # make sure we start with 6 states with session_scope(hass=hass) as session: @@ -116,9 +118,7 @@ async def test_purge_method( recorder_runs = session.query(RecorderRuns) assert recorder_runs.count() == 7 - - await hass.async_block_till_done() - await async_wait_recording_done(hass, instance) + runs_before_purge = recorder_runs.all() # run purge method - no service data, use defaults await hass.services.async_call("recorder", "purge") @@ -145,7 +145,10 @@ async def test_purge_method( assert events.count() == 2 # now we should only have 3 recorder runs left - assert recorder_runs.count() == 3 + runs = recorder_runs.all() + assert runs[0] == runs_before_purge[0] + assert runs[1] == runs_before_purge[5] + assert runs[2] == runs_before_purge[6] assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all())) From 15aa00d6ccc98904c3382ba0794e4787c0e7c22a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Mar 2021 18:14:46 -1000 Subject: [PATCH 1253/1818] Fix homekit checking for port cleanup too many times (#47836) * Fix homekit checking for port cleanup too many times The loop should have terminated as soon as the port was available * coverage * tweak homekit shutdown wait --- homeassistant/components/homekit/__init__.py | 14 +++++--- tests/components/homekit/test_homekit.py | 35 +++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 7c787c7e7be5df..8b606036a48e49 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -117,6 +117,8 @@ STATUS_STOPPED = 2 STATUS_WAIT = 3 +PORT_CLEANUP_CHECK_INTERVAL_SECS = 1 + def _has_all_unique_names_and_ports(bridges): """Validate that each homekit bridge configured has a unique name.""" @@ -306,12 +308,16 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): if homekit.status == STATUS_RUNNING: await homekit.async_stop() + logged_shutdown_wait = False for _ in range(0, SHUTDOWN_TIMEOUT): - if not await hass.async_add_executor_job( - port_is_available, entry.data[CONF_PORT] - ): + if await hass.async_add_executor_job(port_is_available, entry.data[CONF_PORT]): + break + + if not logged_shutdown_wait: _LOGGER.info("Waiting for the HomeKit server to shutdown") - await asyncio.sleep(1) + logged_shutdown_wait = True + + await asyncio.sleep(PORT_CLEANUP_CHECK_INTERVAL_SECS) hass.data[DOMAIN].pop(entry.entry_id) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 6d5f3faae9507f..5895aae351ef0f 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -9,7 +9,7 @@ import pytest from homeassistant import config as hass_config -from homeassistant.components import zeroconf +from homeassistant.components import homekit as homekit_base, zeroconf from homeassistant.components.binary_sensor import ( DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_MOTION, @@ -1167,3 +1167,36 @@ async def test_homekit_start_in_accessory_mode( ) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING + + +async def test_wait_for_port_to_free(hass, hk_driver, mock_zeroconf, caplog): + """Test we wait for the port to free before declaring unload success.""" + await async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, + options={}, + ) + entry.add_to_hass(hass) + + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + f"{PATH_HOMEKIT}.HomeKit.async_stop" + ), patch(f"{PATH_HOMEKIT}.port_is_available", return_value=True) as port_mock: + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert "Waiting for the HomeKit server to shutdown" not in caplog.text + assert port_mock.called + + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + f"{PATH_HOMEKIT}.HomeKit.async_stop" + ), patch.object(homekit_base, "PORT_CLEANUP_CHECK_INTERVAL_SECS", 0), patch( + f"{PATH_HOMEKIT}.port_is_available", return_value=False + ) as port_mock: + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert "Waiting for the HomeKit server to shutdown" in caplog.text + assert port_mock.called From 8795608ae31fa8e95424371a260e31778bef802a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Mar 2021 19:42:49 -1000 Subject: [PATCH 1254/1818] Add suggested area support to august (#47930) --- homeassistant/components/august/entity.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/august/entity.py b/homeassistant/components/august/entity.py index b6c677a63b6f9a..b2a93948449003 100644 --- a/homeassistant/components/august/entity.py +++ b/homeassistant/components/august/entity.py @@ -5,6 +5,8 @@ from . import DOMAIN from .const import MANUFACTURER +DEVICE_TYPES = ["keypad", "lock", "camera", "doorbell", "door", "bell"] + class AugustEntityMixin(Entity): """Base implementation for August device.""" @@ -31,12 +33,14 @@ def _detail(self): @property def device_info(self): """Return the device_info of the device.""" + name = self._device.device_name return { "identifiers": {(DOMAIN, self._device_id)}, - "name": self._device.device_name, + "name": name, "manufacturer": MANUFACTURER, "sw_version": self._detail.firmware_version, "model": self._detail.model, + "suggested_area": _remove_device_types(name, DEVICE_TYPES), } @callback @@ -56,3 +60,19 @@ async def async_added_to_hass(self): self._device_id, self._update_from_data_and_write_state ) ) + + +def _remove_device_types(name, device_types): + """Strip device types from a string. + + August stores the name as Master Bed Lock + or Master Bed Door. We can come up with a + reasonable suggestion by removing the supported + device types from the string. + """ + lower_name = name.lower() + for device_type in device_types: + device_type_with_space = f" {device_type}" + if lower_name.endswith(device_type_with_space): + lower_name = lower_name[: -len(device_type_with_space)] + return name[: len(lower_name)] From 0be1389cf4b80d2879d35371ceffe944f6ef6659 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Mar 2021 06:44:04 +0100 Subject: [PATCH 1255/1818] Bump accuweather library (#47915) --- homeassistant/components/accuweather/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index b03c0e510180c6..fd91f62ae33a65 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -2,7 +2,7 @@ "domain": "accuweather", "name": "AccuWeather", "documentation": "https://www.home-assistant.io/integrations/accuweather/", - "requirements": ["accuweather==0.1.0"], + "requirements": ["accuweather==0.1.1"], "codeowners": ["@bieniu"], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index e3404081073b41..9d1e9baf5f64ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -96,7 +96,7 @@ WazeRouteCalculator==0.12 abodepy==1.2.0 # homeassistant.components.accuweather -accuweather==0.1.0 +accuweather==0.1.1 # homeassistant.components.bmp280 adafruit-circuitpython-bmp280==3.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f3ee7613da49c4..6b8f17bffd0818 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -42,7 +42,7 @@ WSDiscovery==2.0.0 abodepy==1.2.0 # homeassistant.components.accuweather -accuweather==0.1.0 +accuweather==0.1.1 # homeassistant.components.androidtv adb-shell[async]==0.2.1 From be2be4e867cb57632f6d68094f98bf590ef85d87 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Mar 2021 06:44:13 +0100 Subject: [PATCH 1256/1818] Bump gios library (#47917) --- homeassistant/components/gios/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index ff1f82e6ce3eaa..3f520525a5a0f7 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -3,7 +3,7 @@ "name": "GIOŚ", "documentation": "https://www.home-assistant.io/integrations/gios", "codeowners": ["@bieniu"], - "requirements": ["gios==0.2.0"], + "requirements": ["gios==0.2.1"], "config_flow": true, "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 9d1e9baf5f64ae..834c3e910fa549 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -655,7 +655,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.2.0 +gios==0.2.1 # homeassistant.components.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b8f17bffd0818..aa7afdeb66a224 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -349,7 +349,7 @@ georss_qld_bushfire_alert_client==0.3 getmac==0.8.2 # homeassistant.components.gios -gios==0.2.0 +gios==0.2.1 # homeassistant.components.glances glances_api==0.2.0 From e91be3f9f533222c7fdedaf8955bb12e67875f6b Mon Sep 17 00:00:00 2001 From: unaiur Date: Mon, 15 Mar 2021 06:45:14 +0100 Subject: [PATCH 1257/1818] Upgrade to maxcube-api-0.4.1 (#47910) This new version implements a workaround for a hardware bug that causes a factory reset of the full MAX! service. See https://github.com/hackercowboy/python-maxcube-api/issues/12 for more details. --- homeassistant/components/maxcube/__init__.py | 4 ++-- homeassistant/components/maxcube/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/maxcube/__init__.py b/homeassistant/components/maxcube/__init__.py index ffd156b5e0033f..e38f08809a749a 100644 --- a/homeassistant/components/maxcube/__init__.py +++ b/homeassistant/components/maxcube/__init__.py @@ -4,7 +4,6 @@ from threading import Lock import time -from maxcube.connection import MaxCubeConnection from maxcube.cube import MaxCube import voluptuous as vol @@ -60,7 +59,7 @@ def setup(hass, config): scan_interval = gateway[CONF_SCAN_INTERVAL].total_seconds() try: - cube = MaxCube(MaxCubeConnection(host, port)) + cube = MaxCube(host, port) hass.data[DATA_KEY][host] = MaxCubeHandle(cube, scan_interval) except timeout as ex: _LOGGER.error("Unable to connect to Max!Cube gateway: %s", str(ex)) @@ -86,6 +85,7 @@ class MaxCubeHandle: def __init__(self, cube, scan_interval): """Initialize the Cube Handle.""" self.cube = cube + self.cube.use_persistent_connection = scan_interval <= 300 # seconds self.scan_interval = scan_interval self.mutex = Lock() self._updatets = time.monotonic() diff --git a/homeassistant/components/maxcube/manifest.json b/homeassistant/components/maxcube/manifest.json index e6badb254f7af5..ddc21bd2358f0c 100644 --- a/homeassistant/components/maxcube/manifest.json +++ b/homeassistant/components/maxcube/manifest.json @@ -2,6 +2,6 @@ "domain": "maxcube", "name": "eQ-3 MAX!", "documentation": "https://www.home-assistant.io/integrations/maxcube", - "requirements": ["maxcube-api==0.3.0"], + "requirements": ["maxcube-api==0.4.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 834c3e910fa549..b762b5fd168cc6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -910,7 +910,7 @@ magicseaweed==1.0.3 matrix-client==0.3.2 # homeassistant.components.maxcube -maxcube-api==0.3.0 +maxcube-api==0.4.1 # homeassistant.components.mythicbeastsdns mbddns==0.1.2 From bcadccf7aa5f9d07119e93515ee620bf0352e51c Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 14 Mar 2021 22:49:21 -0700 Subject: [PATCH 1258/1818] Invalidate HLS Stream on nest url refresh failure (#47869) This will ensure that the HLS stream is re-created and fetches a new url. --- homeassistant/components/nest/camera_sdm.py | 3 ++ tests/components/nest/camera_sdm_test.py | 37 ++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index cc2730fad8a14e..dd84b53d7195ed 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -145,6 +145,9 @@ async def _handle_stream_refresh(self, now): _LOGGER.debug("Failed to extend stream: %s", err) # Next attempt to catch a url will get a new one self._stream = None + if self.stream: + self.stream.stop() + self.stream = None return # Update the stream worker with the latest valid url if self.stream: diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py index 57747ad9f599f8..3c0c0fdb4db340 100644 --- a/tests/components/nest/camera_sdm_test.py +++ b/tests/components/nest/camera_sdm_test.py @@ -256,7 +256,10 @@ async def test_refresh_expired_stream_token(hass, auth): # Request a stream for the camera entity to exercise nest cam + camera interaction # and shutdown on url expiration - await camera.async_request_stream(hass, cam.entity_id, "hls") + with patch("homeassistant.components.camera.create_stream") as create_stream: + hls_url = await camera.async_request_stream(hass, "camera.my_camera", fmt="hls") + assert hls_url.startswith("/api/hls/") # Includes access token + assert create_stream.called stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.1.streamingToken" @@ -273,6 +276,13 @@ async def test_refresh_expired_stream_token(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.2.streamingToken" + # HLS stream is not re-created, just the source is updated + with patch("homeassistant.components.camera.create_stream") as create_stream: + hls_url1 = await camera.async_request_stream( + hass, "camera.my_camera", fmt="hls" + ) + assert hls_url == hls_url1 + # Next alarm is well before stream_2_expiration, no change next_update = now + datetime.timedelta(seconds=100) await fire_alarm(hass, next_update) @@ -285,6 +295,13 @@ async def test_refresh_expired_stream_token(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.3.streamingToken" + # HLS stream is still not re-created + with patch("homeassistant.components.camera.create_stream") as create_stream: + hls_url2 = await camera.async_request_stream( + hass, "camera.my_camera", fmt="hls" + ) + assert hls_url == hls_url2 + async def test_stream_response_already_expired(hass, auth): """Test a API response returning an expired stream url.""" @@ -363,12 +380,20 @@ async def test_refresh_expired_stream_failure(hass, auth): make_stream_url_response(expiration=stream_2_expiration, token_num=2), ] await async_setup_camera(hass, DEVICE_TRAITS, auth=auth) + assert await async_setup_component(hass, "stream", {}) assert len(hass.states.async_all()) == 1 cam = hass.states.get("camera.my_camera") assert cam is not None assert cam.state == STATE_IDLE + # Request an HLS stream + with patch("homeassistant.components.camera.create_stream") as create_stream: + + hls_url = await camera.async_request_stream(hass, "camera.my_camera", fmt="hls") + assert hls_url.startswith("/api/hls/") # Includes access token + assert create_stream.called + stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.1.streamingToken" @@ -381,6 +406,16 @@ async def test_refresh_expired_stream_failure(hass, auth): stream_source = await camera.async_get_stream_source(hass, "camera.my_camera") assert stream_source == "rtsp://some/url?auth=g.2.streamingToken" + # Requesting an HLS stream will create an entirely new stream + with patch("homeassistant.components.camera.create_stream") as create_stream: + # The HLS stream endpoint was invalidated, with a new auth token + hls_url2 = await camera.async_request_stream( + hass, "camera.my_camera", fmt="hls" + ) + assert hls_url != hls_url2 + assert hls_url2.startswith("/api/hls/") # Includes access token + assert create_stream.called + async def test_camera_image_from_last_event(hass, auth): """Test an image generated from an event.""" From fbf322523465efd94d744090aefa1f67e0869ae6 Mon Sep 17 00:00:00 2001 From: Brandon Rothweiler Date: Mon, 15 Mar 2021 01:57:39 -0400 Subject: [PATCH 1259/1818] Address review comments and minor fix for Mazda integration (#47702) * Address comments from code review * Fix handling of missing sensor values * Use default timeout for get_vehicles * Fix test_update_auth_failure --- homeassistant/components/mazda/__init__.py | 11 ++-- homeassistant/components/mazda/config_flow.py | 14 +++--- homeassistant/components/mazda/manifest.json | 2 +- homeassistant/components/mazda/sensor.py | 50 +++++++++++-------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/mazda/test_init.py | 29 +++++++---- 7 files changed, 64 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py index 1b1e6584a0eaf2..8731fa256a6131 100644 --- a/homeassistant/components/mazda/__init__.py +++ b/homeassistant/components/mazda/__init__.py @@ -32,6 +32,12 @@ PLATFORMS = ["sensor"] +async def with_timeout(task, timeout_seconds=10): + """Run an async task with a timeout.""" + async with async_timeout.timeout(timeout_seconds): + return await task + + async def async_setup(hass: HomeAssistant, config: dict): """Set up the Mazda Connected Services component.""" hass.data[DOMAIN] = {} @@ -69,11 +75,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_update_data(): """Fetch data from Mazda API.""" - - async def with_timeout(task): - async with async_timeout.timeout(10): - return await task - try: vehicles = await with_timeout(mazda_client.get_vehicles()) diff --git a/homeassistant/components/mazda/config_flow.py b/homeassistant/components/mazda/config_flow.py index 53c08b9bd69ee0..ef0f35e4e8e8a5 100644 --- a/homeassistant/components/mazda/config_flow.py +++ b/homeassistant/components/mazda/config_flow.py @@ -40,15 +40,15 @@ async def async_step_user(self, user_input=None): if user_input is not None: await self.async_set_unique_id(user_input[CONF_EMAIL].lower()) + websession = aiohttp_client.async_get_clientsession(self.hass) + mazda_client = MazdaAPI( + user_input[CONF_EMAIL], + user_input[CONF_PASSWORD], + user_input[CONF_REGION], + websession, + ) try: - websession = aiohttp_client.async_get_clientsession(self.hass) - mazda_client = MazdaAPI( - user_input[CONF_EMAIL], - user_input[CONF_PASSWORD], - user_input[CONF_REGION], - websession, - ) await mazda_client.validate_credentials() except MazdaAuthenticationException: errors["base"] = "invalid_auth" diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index b3826d42318a6b..c3a05a351c304d 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.0.8"], + "requirements": ["pymazda==0.0.9"], "codeowners": ["@bdr99"], "quality_scale": "platinum" } \ No newline at end of file diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py index fa03eb7f4108d0..a05291673e8d9d 100644 --- a/homeassistant/components/mazda/sensor.py +++ b/homeassistant/components/mazda/sensor.py @@ -91,7 +91,13 @@ def state(self): fuel_distance_km = self.coordinator.data[self.index]["status"][ "fuelDistanceRemainingKm" ] - return round(self.hass.config.units.length(fuel_distance_km, LENGTH_KILOMETERS)) + return ( + None + if fuel_distance_km is None + else round( + self.hass.config.units.length(fuel_distance_km, LENGTH_KILOMETERS) + ) + ) class MazdaOdometerSensor(MazdaEntity): @@ -124,7 +130,11 @@ def icon(self): def state(self): """Return the state of the sensor.""" odometer_km = self.coordinator.data[self.index]["status"]["odometerKm"] - return round(self.hass.config.units.length(odometer_km, LENGTH_KILOMETERS)) + return ( + None + if odometer_km is None + else round(self.hass.config.units.length(odometer_km, LENGTH_KILOMETERS)) + ) class MazdaFrontLeftTirePressureSensor(MazdaEntity): @@ -154,11 +164,10 @@ def icon(self): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "frontLeftTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "frontLeftTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) class MazdaFrontRightTirePressureSensor(MazdaEntity): @@ -188,11 +197,10 @@ def icon(self): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "frontRightTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "frontRightTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) class MazdaRearLeftTirePressureSensor(MazdaEntity): @@ -222,11 +230,10 @@ def icon(self): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "rearLeftTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "rearLeftTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) class MazdaRearRightTirePressureSensor(MazdaEntity): @@ -256,8 +263,7 @@ def icon(self): @property def state(self): """Return the state of the sensor.""" - return round( - self.coordinator.data[self.index]["status"]["tirePressure"][ - "rearRightTirePressurePsi" - ] - ) + tire_pressure = self.coordinator.data[self.index]["status"]["tirePressure"][ + "rearRightTirePressurePsi" + ] + return None if tire_pressure is None else round(tire_pressure) diff --git a/requirements_all.txt b/requirements_all.txt index b762b5fd168cc6..b4d6d93bdae665 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1518,7 +1518,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.0.8 +pymazda==0.0.9 # homeassistant.components.mediaroom pymediaroom==0.6.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa7afdeb66a224..7ff9b14e6d74d6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -802,7 +802,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.0.8 +pymazda==0.0.9 # homeassistant.components.melcloud pymelcloud==2.5.2 diff --git a/tests/components/mazda/test_init.py b/tests/components/mazda/test_init.py index d0352682f53cb2..ebd118260bc991 100644 --- a/tests/components/mazda/test_init.py +++ b/tests/components/mazda/test_init.py @@ -1,18 +1,22 @@ """Tests for the Mazda Connected Services integration.""" +from datetime import timedelta +import json from unittest.mock import patch from pymazda import MazdaAuthenticationException, MazdaException -from homeassistant.components.mazda.const import DATA_COORDINATOR, DOMAIN +from homeassistant.components.mazda.const import DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_RETRY, ) from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION from homeassistant.core import HomeAssistant +from homeassistant.util import dt as dt_util -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture from tests.components.mazda import init_integration FIXTURE_USER_INPUT = { @@ -60,10 +64,21 @@ async def test_init_auth_failure(hass: HomeAssistant): async def test_update_auth_failure(hass: HomeAssistant): """Test auth failure during data update.""" + get_vehicles_fixture = json.loads(load_fixture("mazda/get_vehicles.json")) + get_vehicle_status_fixture = json.loads( + load_fixture("mazda/get_vehicle_status.json") + ) + with patch( "homeassistant.components.mazda.MazdaAPI.validate_credentials", return_value=True, - ), patch("homeassistant.components.mazda.MazdaAPI.get_vehicles", return_value={}): + ), patch( + "homeassistant.components.mazda.MazdaAPI.get_vehicles", + return_value=get_vehicles_fixture, + ), patch( + "homeassistant.components.mazda.MazdaAPI.get_vehicle_status", + return_value=get_vehicle_status_fixture, + ): config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT) config_entry.add_to_hass(hass) @@ -74,15 +89,11 @@ async def test_update_auth_failure(hass: HomeAssistant): assert len(entries) == 1 assert entries[0].state == ENTRY_STATE_LOADED - coordinator = hass.data[DOMAIN][config_entry.entry_id][DATA_COORDINATOR] with patch( - "homeassistant.components.mazda.MazdaAPI.validate_credentials", - side_effect=MazdaAuthenticationException("Login failed"), - ), patch( "homeassistant.components.mazda.MazdaAPI.get_vehicles", side_effect=MazdaAuthenticationException("Login failed"), ): - await coordinator.async_refresh() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=61)) await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() @@ -97,4 +108,4 @@ async def test_unload_config_entry(hass: HomeAssistant) -> None: await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() - assert not hass.data.get(DOMAIN) + assert entry.state == ENTRY_STATE_NOT_LOADED From 9ec4c07753f12da47dba351bbe2f1156664d6a63 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Mon, 15 Mar 2021 06:57:56 +0100 Subject: [PATCH 1260/1818] Update openwrt-luci-rpc from 1.1.6 to 1.1.8 (#47848) --- homeassistant/components/luci/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/luci/manifest.json b/homeassistant/components/luci/manifest.json index 3b51aab6e4acbb..95fd6fc35ad84c 100644 --- a/homeassistant/components/luci/manifest.json +++ b/homeassistant/components/luci/manifest.json @@ -2,6 +2,6 @@ "domain": "luci", "name": "OpenWRT (luci)", "documentation": "https://www.home-assistant.io/integrations/luci", - "requirements": ["openwrt-luci-rpc==1.1.6"], + "requirements": ["openwrt-luci-rpc==1.1.8"], "codeowners": ["@mzdrale"] } diff --git a/requirements_all.txt b/requirements_all.txt index b4d6d93bdae665..03fd077d9be636 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1061,7 +1061,7 @@ opensensemap-api==0.1.5 openwebifpy==3.2.7 # homeassistant.components.luci -openwrt-luci-rpc==1.1.6 +openwrt-luci-rpc==1.1.8 # homeassistant.components.ubus openwrt-ubus-rpc==0.0.2 From 7fe3c472e9cc4a494af61893c21d04686c61782a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 15 Mar 2021 02:41:25 -0700 Subject: [PATCH 1261/1818] Improve bad JSON data reporting (#47932) * Improve bad data reporting * Fix tests Co-authored-by: Erik --- homeassistant/util/json.py | 15 +++++++++------ tests/components/websocket_api/test_http.py | 4 ++-- tests/util/test_json.py | 19 +++++++++++++++++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index e906462a250f2e..2ce98ffef77ad9 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -112,12 +112,15 @@ def find_paths_unserializable_data( except (ValueError, TypeError): pass - # We convert states and events to dict so we can find bad data inside it - if isinstance(obj, State): - obj_path += f"(state: {obj.entity_id})" - obj = obj.as_dict() - elif isinstance(obj, Event): - obj_path += f"(event: {obj.event_type})" + # We convert objects with as_dict to their dict values so we can find bad data inside it + if hasattr(obj, "as_dict"): + desc = obj.__class__.__name__ + if isinstance(obj, State): + desc += f": {obj.entity_id}" + elif isinstance(obj, Event): + desc += f": {obj.event_type}" + + obj_path += f"({desc})" obj = obj.as_dict() if isinstance(obj, dict): diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index d3cf4b854f8251..f3952f1dc4b74a 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -67,7 +67,7 @@ def instantiate_handler(*args): async def test_non_json_message(hass, websocket_client, caplog): - """Test trying to serialze non JSON objects.""" + """Test trying to serialize non JSON objects.""" bad_data = object() hass.states.async_set("test_domain.entity", "testing", {"bad": bad_data}) await websocket_client.send_json({"id": 5, "type": "get_states"}) @@ -77,6 +77,6 @@ async def test_non_json_message(hass, websocket_client, caplog): assert msg["type"] == const.TYPE_RESULT assert not msg["success"] assert ( - f"Unable to serialize to JSON. Bad data found at $.result[0](state: test_domain.entity).attributes.bad={bad_data}(" + f"Unable to serialize to JSON. Bad data found at $.result[0](State: test_domain.entity).attributes.bad={bad_data}(" in caplog.text ) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 1cbaaae7d235c3..1d82f5972a3ba0 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -153,7 +153,7 @@ def default(self, o): [State("mock_domain.mock_entity", "on", {"bad": bad_data})], dump=partial(dumps, cls=MockJSONEncoder), ) - == {"$[0](state: mock_domain.mock_entity).attributes.bad": bad_data} + == {"$[0](State: mock_domain.mock_entity).attributes.bad": bad_data} ) assert ( @@ -161,5 +161,20 @@ def default(self, o): [Event("bad_event", {"bad_attribute": bad_data})], dump=partial(dumps, cls=MockJSONEncoder), ) - == {"$[0](event: bad_event).data.bad_attribute": bad_data} + == {"$[0](Event: bad_event).data.bad_attribute": bad_data} + ) + + class BadData: + def __init__(self): + self.bla = bad_data + + def as_dict(self): + return {"bla": self.bla} + + assert ( + find_paths_unserializable_data( + BadData(), + dump=partial(dumps, cls=MockJSONEncoder), + ) + == {"$(BadData).bla": bad_data} ) From 99d1e3e71d80fcfcf395536c2d6c1df1c025e423 Mon Sep 17 00:00:00 2001 From: Drzony Date: Mon, 15 Mar 2021 11:24:07 +0100 Subject: [PATCH 1262/1818] MQTT Light: Use flash attribute in async_turn_off (#47919) --- .../components/mqtt/light/schema_json.py | 26 ++++++++++--------- tests/components/light/common.py | 12 ++++++--- tests/components/mqtt/test_light_json.py | 18 +++++++++++++ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 99c48aa1c8f2a0..8f2d1dda0a77e7 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -341,6 +341,18 @@ def supported_features(self): """Flag supported features.""" return self._supported_features + def _set_flash_and_transition(self, message, **kwargs): + if ATTR_TRANSITION in kwargs: + message["transition"] = kwargs[ATTR_TRANSITION] + + if ATTR_FLASH in kwargs: + flash = kwargs.get(ATTR_FLASH) + + if flash == FLASH_LONG: + message["flash"] = self._flash_times[CONF_FLASH_TIME_LONG] + elif flash == FLASH_SHORT: + message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT] + async def async_turn_on(self, **kwargs): """Turn the device on. @@ -380,16 +392,7 @@ async def async_turn_on(self, **kwargs): self._hs = kwargs[ATTR_HS_COLOR] should_update = True - if ATTR_FLASH in kwargs: - flash = kwargs.get(ATTR_FLASH) - - if flash == FLASH_LONG: - message["flash"] = self._flash_times[CONF_FLASH_TIME_LONG] - elif flash == FLASH_SHORT: - message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT] - - if ATTR_TRANSITION in kwargs: - message["transition"] = kwargs[ATTR_TRANSITION] + self._set_flash_and_transition(message, **kwargs) if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]: brightness_normalized = kwargs[ATTR_BRIGHTNESS] / DEFAULT_BRIGHTNESS_SCALE @@ -449,8 +452,7 @@ async def async_turn_off(self, **kwargs): """ message = {"state": "OFF"} - if ATTR_TRANSITION in kwargs: - message["transition"] = kwargs[ATTR_TRANSITION] + self._set_flash_and_transition(message, **kwargs) mqtt.async_publish( self.hass, diff --git a/tests/components/light/common.py b/tests/components/light/common.py index a9991bf35948c4..20ace3641cdee7 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -111,16 +111,20 @@ async def async_turn_on( @bind_hass -def turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None): +def turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None, flash=None): """Turn all or specified light off.""" - hass.add_job(async_turn_off, hass, entity_id, transition) + hass.add_job(async_turn_off, hass, entity_id, transition, flash) -async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None): +async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL, transition=None, flash=None): """Turn all or specified light off.""" data = { key: value - for key, value in [(ATTR_ENTITY_ID, entity_id), (ATTR_TRANSITION, transition)] + for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_TRANSITION, transition), + (ATTR_FLASH, flash), + ] if value is not None } diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 022df109f3842e..bdb81e5e5e4d77 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -876,6 +876,24 @@ async def test_flash_short_and_long(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_ON + await common.async_turn_off(hass, "light.test", flash="short") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", JsonValidator('{"state": "OFF", "flash": 5}'), 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_off(hass, "light.test", flash="long") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", JsonValidator('{"state": "OFF", "flash": 15}'), 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + async def test_transition(hass, mqtt_mock): """Test for transition time being sent when included.""" From b2efcb3c22c55770ed2c8cd032d30ea3c1822e88 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 15 Mar 2021 12:15:34 +0100 Subject: [PATCH 1263/1818] Support all Xiaomi Miio gateway switches (#46657) * Support all gateway switches * fix checks * process revieuw * Update homeassistant/components/xiaomi_miio/switch.py Co-authored-by: Martin Hjelmare * generilize variable matching * fix styling Co-authored-by: Martin Hjelmare --- .../components/xiaomi_miio/__init__.py | 2 +- .../components/xiaomi_miio/sensor.py | 5 ++ .../components/xiaomi_miio/switch.py | 68 ++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index e426322e5dc802..e194225409a7f2 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -26,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) -GATEWAY_PLATFORMS = ["alarm_control_panel", "sensor", "switch", "light"] +GATEWAY_PLATFORMS = ["alarm_control_panel", "light", "sensor", "switch"] SWITCH_PLATFORMS = ["switch"] FAN_PLATFORMS = ["fan"] LIGHT_PLATFORMS = ["light"] diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index cdb38ba95156bd..e2b645a10ea09c 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -21,9 +21,11 @@ CONF_TOKEN, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, + POWER_WATT, PRESSURE_HPA, TEMP_CELSIUS, ) @@ -77,6 +79,9 @@ class SensorType: "pressure": SensorType( unit=PRESSURE_HPA, icon=None, device_class=DEVICE_CLASS_PRESSURE ), + "load_power": SensorType( + unit=POWER_WATT, icon=None, device_class=DEVICE_CLASS_POWER + ), } diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index fd75e9f00888f9..adb18b0de5a0bb 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -7,7 +7,11 @@ from miio.powerstrip import PowerMode import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity +from homeassistant.components.switch import ( + DEVICE_CLASS_SWITCH, + PLATFORM_SCHEMA, + SwitchEntity, +) from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ENTITY_ID, @@ -25,12 +29,14 @@ CONF_GATEWAY, CONF_MODEL, DOMAIN, + KEY_COORDINATOR, SERVICE_SET_POWER_MODE, SERVICE_SET_POWER_PRICE, SERVICE_SET_WIFI_LED_OFF, SERVICE_SET_WIFI_LED_ON, ) from .device import XiaomiMiioEntity +from .gateway import XiaomiGatewayDevice _LOGGER = logging.getLogger(__name__) @@ -40,6 +46,13 @@ MODEL_POWER_STRIP_V2 = "zimi.powerstrip.v2" MODEL_PLUG_V3 = "chuangmi.plug.v3" +KEY_CHANNEL = "channel" +GATEWAY_SWITCH_VARS = { + "status_ch0": {KEY_CHANNEL: 0}, + "status_ch1": {KEY_CHANNEL: 1}, + "status_ch2": {KEY_CHANNEL: 2}, +} + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, @@ -135,6 +148,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities): model = config_entry.data[CONF_MODEL] unique_id = config_entry.unique_id + if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: + gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY] + # Gateway sub devices + sub_devices = gateway.devices + coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] + for sub_device in sub_devices.values(): + if sub_device.device_type != "Switch": + continue + switch_variables = set(sub_device.status) & set(GATEWAY_SWITCH_VARS) + if switch_variables: + entities.extend( + [ + XiaomiGatewaySwitch( + coordinator, sub_device, config_entry, variable + ) + for variable in switch_variables + ] + ) + if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE or ( config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY and model == "lumi.acpartner.v3" @@ -227,6 +259,40 @@ async def async_service_handler(service): async_add_entities(entities, update_before_add=True) +class XiaomiGatewaySwitch(XiaomiGatewayDevice, SwitchEntity): + """Representation of a XiaomiGatewaySwitch.""" + + def __init__(self, coordinator, sub_device, entry, variable): + """Initialize the XiaomiSensor.""" + super().__init__(coordinator, sub_device, entry) + self._channel = GATEWAY_SWITCH_VARS[variable][KEY_CHANNEL] + self._data_key = f"status_ch{self._channel}" + self._unique_id = f"{sub_device.sid}-ch{self._channel}" + self._name = f"{sub_device.name} ch{self._channel} ({sub_device.sid})" + + @property + def device_class(self): + """Return the device class of this entity.""" + return DEVICE_CLASS_SWITCH + + @property + def is_on(self): + """Return true if switch is on.""" + return self._sub_device.status[self._data_key] == "on" + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self.hass.async_add_executor_job(self._sub_device.on, self._channel) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self.hass.async_add_executor_job(self._sub_device.off, self._channel) + + async def async_toggle(self, **kwargs): + """Toggle the switch.""" + await self.hass.async_add_executor_job(self._sub_device.toggle, self._channel) + + class XiaomiPlugGenericSwitch(XiaomiMiioEntity, SwitchEntity): """Representation of a Xiaomi Plug Generic.""" From 1aa4fd4cc909a5a8258a871d0210b44095650e9a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 15 Mar 2021 12:25:11 +0100 Subject: [PATCH 1264/1818] Make Xiaomi Miio unavailable device independent (#47795) * make unavailable independent * fix data is None * process review comments --- .../components/xiaomi_miio/__init__.py | 24 +++++++++++++------ homeassistant/components/xiaomi_miio/const.py | 2 ++ .../components/xiaomi_miio/gateway.py | 10 +++++++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index e194225409a7f2..ccecc835b43244 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -7,9 +7,10 @@ from homeassistant import config_entries, core from homeassistant.const import CONF_HOST, CONF_TOKEN from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( + ATTR_AVAILABLE, CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, @@ -86,13 +87,22 @@ async def async_setup_gateway_entry( sw_version=gateway_info.firmware_version, ) - async def async_update_data(): + def update_data(): """Fetch data from the subdevice.""" - try: - for sub_device in gateway.gateway_device.devices.values(): - await hass.async_add_executor_job(sub_device.update) - except GatewayException as ex: - raise UpdateFailed("Got exception while fetching the state") from ex + data = {} + for sub_device in gateway.gateway_device.devices.values(): + try: + sub_device.update() + except GatewayException as ex: + _LOGGER.error("Got exception while fetching the state: %s", ex) + data[sub_device.sid] = {ATTR_AVAILABLE: False} + else: + data[sub_device.sid] = {ATTR_AVAILABLE: True} + return data + + async def async_update_data(): + """Fetch data from the subdevice using async_add_executor_job.""" + return await hass.async_add_executor_job(update_data) # Create update coordinator coordinator = DataUpdateCoordinator( diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index 977e390f26bfb9..ddde0c7722909f 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -9,6 +9,8 @@ KEY_COORDINATOR = "coordinator" +ATTR_AVAILABLE = "available" + # Fan Models MODEL_AIRPURIFIER_V1 = "zhimi.airpurifier.v1" MODEL_AIRPURIFIER_V2 = "zhimi.airpurifier.v2" diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index 356b19dc89a1ee..be96f77240a09a 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -6,7 +6,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import ATTR_AVAILABLE, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -89,3 +89,11 @@ def device_info(self): "model": self._sub_device.model, "sw_version": self._sub_device.firmware_version, } + + @property + def available(self): + """Return if entity is available.""" + if self.coordinator.data is None: + return False + + return self.coordinator.data[self._sub_device.sid][ATTR_AVAILABLE] From cfeb8eb06a13e0a01289e383416e8ce6484f8be9 Mon Sep 17 00:00:00 2001 From: Khole Date: Mon, 15 Mar 2021 11:27:10 +0000 Subject: [PATCH 1265/1818] Add Hive config flow (#47300) * Add Hive UI * Fix tests and review updates * Slimmed down config_flow * Fix tests * Updated Services.yaml with extra ui attributes * cleanup config flow * Update config entry * Remove ATTR_AVAILABLE * Fix Re-Auth Test * Added more tests. * Update tests --- .coveragerc | 8 +- homeassistant/components/hive/__init__.py | 195 +++--- .../components/hive/binary_sensor.py | 25 +- homeassistant/components/hive/climate.py | 61 +- homeassistant/components/hive/config_flow.py | 171 ++++++ homeassistant/components/hive/const.py | 20 + homeassistant/components/hive/light.py | 29 +- homeassistant/components/hive/manifest.json | 3 +- homeassistant/components/hive/sensor.py | 32 +- homeassistant/components/hive/services.yaml | 54 +- homeassistant/components/hive/strings.json | 53 ++ homeassistant/components/hive/switch.py | 32 +- .../components/hive/translations/en.json | 53 ++ homeassistant/components/hive/water_heater.py | 64 +- homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/hive/test_config_flow.py | 576 ++++++++++++++++++ 18 files changed, 1173 insertions(+), 209 deletions(-) create mode 100644 homeassistant/components/hive/config_flow.py create mode 100644 homeassistant/components/hive/const.py create mode 100644 homeassistant/components/hive/strings.json create mode 100644 homeassistant/components/hive/translations/en.json create mode 100644 tests/components/hive/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index db940ed642b450..72d6e1e9293305 100644 --- a/.coveragerc +++ b/.coveragerc @@ -386,7 +386,13 @@ omit = homeassistant/components/hikvisioncam/switch.py homeassistant/components/hisense_aehw4a1/* homeassistant/components/hitron_coda/device_tracker.py - homeassistant/components/hive/* + homeassistant/components/hive/__init__.py + homeassistant/components/hive/climate.py + homeassistant/components/hive/binary_sensor.py + homeassistant/components/hive/light.py + homeassistant/components/hive/sensor.py + homeassistant/components/hive/switch.py + homeassistant/components/hive/water_heater.py homeassistant/components/hlk_sw16/__init__.py homeassistant/components/hlk_sw16/switch.py homeassistant/components/home_connect/* diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 331ab37224f37e..040ef7b4674394 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -1,43 +1,26 @@ """Support for the Hive devices and services.""" +import asyncio from functools import wraps import logging -from pyhiveapi import Hive +from aiohttp.web_exceptions import HTTPException +from apyhiveapi import Hive +from apyhiveapi.helper.hive_exceptions import HiveReauthRequired import voluptuous as vol -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_TEMPERATURE, - CONF_PASSWORD, - CONF_SCAN_INTERVAL, - CONF_USERNAME, -) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import async_load_platform +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity import Entity -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN, PLATFORM_LOOKUP, PLATFORMS -ATTR_AVAILABLE = "available" -DOMAIN = "hive" -DATA_HIVE = "data_hive" -SERVICES = ["Heating", "HotWater", "TRV"] -SERVICE_BOOST_HOT_WATER = "boost_hot_water" -SERVICE_BOOST_HEATING = "boost_heating" -ATTR_TIME_PERIOD = "time_period" -ATTR_MODE = "on_off" -DEVICETYPES = { - "binary_sensor": "device_list_binary_sensor", - "climate": "device_list_climate", - "water_heater": "device_list_water_heater", - "light": "device_list_light", - "switch": "device_list_plug", - "sensor": "device_list_sensor", -} +_LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( { @@ -52,101 +35,88 @@ extra=vol.ALLOW_EXTRA, ) -BOOST_HEATING_SCHEMA = vol.Schema( - { - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_TIME_PERIOD): vol.All( - cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60 - ), - vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float), - } -) -BOOST_HOT_WATER_SCHEMA = vol.Schema( - { - vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All( - cv.time_period, cv.positive_timedelta, lambda td: td.total_seconds() // 60 - ), - vol.Required(ATTR_MODE): cv.string, - } -) +async def async_setup(hass, config): + """Hive configuration setup.""" + hass.data[DOMAIN] = {} + + if DOMAIN not in config: + return True + + conf = config[DOMAIN] + + 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={ + CONF_USERNAME: conf[CONF_USERNAME], + CONF_PASSWORD: conf[CONF_PASSWORD], + }, + ) + ) + return True -async def async_setup(hass, config): - """Set up the Hive Component.""" - - async def heating_boost(service): - """Handle the service call.""" - - entity_lookup = hass.data[DOMAIN]["entity_lookup"] - hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID]) - if not hive_id: - # log or raise error - _LOGGER.error("Cannot boost entity id entered") - return - - minutes = service.data[ATTR_TIME_PERIOD] - temperature = service.data[ATTR_TEMPERATURE] - - hive.heating.turn_boost_on(hive_id, minutes, temperature) - - async def hot_water_boost(service): - """Handle the service call.""" - entity_lookup = hass.data[DOMAIN]["entity_lookup"] - hive_id = entity_lookup.get(service.data[ATTR_ENTITY_ID]) - if not hive_id: - # log or raise error - _LOGGER.error("Cannot boost entity id entered") - return - minutes = service.data[ATTR_TIME_PERIOD] - mode = service.data[ATTR_MODE] - - if mode == "on": - hive.hotwater.turn_boost_on(hive_id, minutes) - elif mode == "off": - hive.hotwater.turn_boost_off(hive_id) - - hive = Hive() - - config = {} - config["username"] = config[DOMAIN][CONF_USERNAME] - config["password"] = config[DOMAIN][CONF_PASSWORD] - config["update_interval"] = config[DOMAIN][CONF_SCAN_INTERVAL] - - devices = await hive.session.startSession(config) - - if devices is None: - _LOGGER.error("Hive API initialization failed") +async def async_setup_entry(hass, entry): + """Set up Hive from a config entry.""" + + websession = aiohttp_client.async_get_clientsession(hass) + hive = Hive(websession) + hive_config = dict(entry.data) + + hive_config["options"] = {} + hive_config["options"].update( + {CONF_SCAN_INTERVAL: dict(entry.options).get(CONF_SCAN_INTERVAL, 120)} + ) + hass.data[DOMAIN][entry.entry_id] = hive + + try: + devices = await hive.session.startSession(hive_config) + except HTTPException as error: + _LOGGER.error("Could not connect to the internet: %s", error) + raise ConfigEntryNotReady() from error + except HiveReauthRequired: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": entry.unique_id, + }, + data=entry.data, + ) + ) return False - hass.data[DOMAIN][DATA_HIVE] = hive - hass.data[DOMAIN]["entity_lookup"] = {} - - for ha_type in DEVICETYPES: - devicelist = devices.get(DEVICETYPES[ha_type]) - if devicelist: + for ha_type, hive_type in PLATFORM_LOOKUP.items(): + device_list = devices.get(hive_type) + if device_list: hass.async_create_task( - async_load_platform(hass, ha_type, DOMAIN, devicelist, config) + hass.config_entries.async_forward_entry_setup(entry, ha_type) ) - if ha_type == "climate": - hass.services.async_register( - DOMAIN, - SERVICE_BOOST_HEATING, - heating_boost, - schema=BOOST_HEATING_SCHEMA, - ) - if ha_type == "water_heater": - hass.services.async_register( - DOMAIN, - SERVICE_BOOST_HOT_WATER, - hot_water_boost, - schema=BOOST_HOT_WATER_SCHEMA, - ) return True +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + def refresh_system(func): """Force update all entities after state change.""" @@ -173,6 +143,3 @@ async def async_added_to_hass(self): self.async_on_remove( async_dispatcher_connect(self.hass, DOMAIN, self.async_write_ha_state) ) - if self.device["hiveType"] in SERVICES: - entity_lookup = self.hass.data[DOMAIN]["entity_lookup"] - entity_lookup[self.entity_id] = self.device["hiveID"] diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index eed08c45b3a804..d5f1ca53afde60 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -10,7 +10,8 @@ BinarySensorEntity, ) -from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity +from . import HiveEntity +from .const import ATTR_MODE, DOMAIN DEVICETYPE = { "contactsensor": DEVICE_CLASS_OPENING, @@ -24,13 +25,11 @@ SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Binary Sensor.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("binary_sensor") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("binary_sensor") entities = [] if devices: for dev in devices: @@ -49,7 +48,14 @@ def unique_id(self): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def device_class(self): @@ -72,7 +78,6 @@ def available(self): def extra_state_attributes(self): """Show Device Attributes.""" return { - ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), ATTR_MODE: self.attributes.get(ATTR_MODE), } @@ -84,5 +89,5 @@ def is_on(self): async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.sensor.get_sensor(self.device) + self.device = await self.hive.sensor.getSensor(self.device) self.attributes = self.device.get("attributes", {}) diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index c0b33dbb3ae263..31b4bd273ad749 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,6 +1,8 @@ """Support for the Hive climate devices.""" from datetime import timedelta +import voluptuous as vol + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, @@ -15,8 +17,10 @@ SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.helpers import config_validation as cv, entity_platform -from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ATTR_TIME_PERIOD, DOMAIN, SERVICE_BOOST_HEATING HIVE_TO_HASS_STATE = { "SCHEDULE": HVAC_MODE_AUTO, @@ -45,19 +49,32 @@ SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive thermostat.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("climate") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("climate") entities = [] if devices: for dev in devices: entities.append(HiveClimateEntity(hive, dev)) async_add_entities(entities, True) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_BOOST_HEATING, + { + vol.Required(ATTR_TIME_PERIOD): vol.All( + cv.time_period, + cv.positive_timedelta, + lambda td: td.total_seconds() // 60, + ), + vol.Optional(ATTR_TEMPERATURE, default="25.0"): vol.Coerce(float), + }, + "async_heating_boost", + ) + class HiveClimateEntity(HiveEntity, ClimateEntity): """Hive Climate Device.""" @@ -76,7 +93,14 @@ def unique_id(self): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def supported_features(self): @@ -93,11 +117,6 @@ def available(self): """Return if the device is available.""" return self.device["deviceData"]["online"] - @property - def extra_state_attributes(self): - """Show Device Attributes.""" - return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} - @property def hvac_modes(self): """Return the list of available hvac operation modes. @@ -160,27 +179,31 @@ def preset_modes(self): async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" new_mode = HASS_TO_HIVE_STATE[hvac_mode] - await self.hive.heating.set_mode(self.device, new_mode) + await self.hive.heating.setMode(self.device, new_mode) @refresh_system async def async_set_temperature(self, **kwargs): """Set new target temperature.""" new_temperature = kwargs.get(ATTR_TEMPERATURE) if new_temperature is not None: - await self.hive.heating.set_target_temperature(self.device, new_temperature) + await self.hive.heating.setTargetTemperature(self.device, new_temperature) @refresh_system async def async_set_preset_mode(self, preset_mode): """Set new preset mode.""" if preset_mode == PRESET_NONE and self.preset_mode == PRESET_BOOST: - await self.hive.heating.turn_boost_off(self.device) + await self.hive.heating.turnBoostOff(self.device) elif preset_mode == PRESET_BOOST: curtemp = round(self.current_temperature * 2) / 2 temperature = curtemp + 0.5 - await self.hive.heating.turn_boost_on(self.device, 30, temperature) + await self.hive.heating.turnBoostOn(self.device, 30, temperature) + + @refresh_system + async def async_heating_boost(self, time_period, temperature): + """Handle boost heating service call.""" + await self.hive.heating.turnBoostOn(self.device, time_period, temperature) async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.heating.get_heating(self.device) - self.attributes.update(self.device.get("attributes", {})) + self.device = await self.hive.heating.getHeating(self.device) diff --git a/homeassistant/components/hive/config_flow.py b/homeassistant/components/hive/config_flow.py new file mode 100644 index 00000000000000..ff58e8d96df2fb --- /dev/null +++ b/homeassistant/components/hive/config_flow.py @@ -0,0 +1,171 @@ +"""Config Flow for Hive.""" + +from apyhiveapi import Auth +from apyhiveapi.helper.hive_exceptions import ( + HiveApiError, + HiveInvalid2FACode, + HiveInvalidPassword, + HiveInvalidUsername, +) +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.core import callback + +from .const import ( # pylint:disable=unused-import + CONF_CODE, + CONFIG_ENTRY_VERSION, + DOMAIN, +) + + +class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a Hive config flow.""" + + VERSION = CONFIG_ENTRY_VERSION + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize the config flow.""" + self.hive_auth = None + self.data = {} + self.tokens = {} + self.entry = None + + async def async_step_user(self, user_input=None): + """Prompt user input. Create or edit entry.""" + errors = {} + # Login to Hive with user data. + if user_input is not None: + self.data.update(user_input) + self.hive_auth = Auth( + username=self.data[CONF_USERNAME], password=self.data[CONF_PASSWORD] + ) + + # Get user from existing entry and abort if already setup + self.entry = await self.async_set_unique_id(self.data[CONF_USERNAME]) + if self.context["source"] != config_entries.SOURCE_REAUTH: + self._abort_if_unique_id_configured() + + # Login to the Hive. + try: + self.tokens = await self.hive_auth.login() + except HiveInvalidUsername: + errors["base"] = "invalid_username" + except HiveInvalidPassword: + errors["base"] = "invalid_password" + except HiveApiError: + errors["base"] = "no_internet_available" + + if self.tokens.get("ChallengeName") == "SMS_MFA": + # Complete SMS 2FA. + return await self.async_step_2fa() + + if not errors: + # Complete the entry setup. + try: + return await self.async_setup_hive_entry() + except UnknownHiveError: + errors["base"] = "unknown" + + # Show User Input form. + schema = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} + ) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + async def async_step_2fa(self, user_input=None): + """Handle 2fa step.""" + errors = {} + + if user_input and user_input["2fa"] == "0000": + self.tokens = await self.hive_auth.login() + elif user_input: + try: + self.tokens = await self.hive_auth.sms_2fa( + user_input["2fa"], self.tokens + ) + except HiveInvalid2FACode: + errors["base"] = "invalid_code" + except HiveApiError: + errors["base"] = "no_internet_available" + + if not errors: + try: + return await self.async_setup_hive_entry() + except UnknownHiveError: + errors["base"] = "unknown" + + schema = vol.Schema({vol.Required(CONF_CODE): str}) + return self.async_show_form(step_id="2fa", data_schema=schema, errors=errors) + + async def async_setup_hive_entry(self): + """Finish setup and create the config entry.""" + + if "AuthenticationResult" not in self.tokens: + raise UnknownHiveError + + # Setup the config entry + self.data["tokens"] = self.tokens + if self.context["source"] == config_entries.SOURCE_REAUTH: + self.hass.config_entries.async_update_entry( + self.entry, title=self.data["username"], data=self.data + ) + await self.hass.config_entries.async_reload(self.entry.entry_id) + return self.async_abort(reason="reauth_successful") + return self.async_create_entry(title=self.data["username"], data=self.data) + + async def async_step_reauth(self, user_input=None): + """Re Authenticate a user.""" + data = { + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + } + return await self.async_step_user(data) + + async def async_step_import(self, user_input=None): + """Import user.""" + return await self.async_step_user(user_input) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Hive options callback.""" + return HiveOptionsFlowHandler(config_entry) + + +class HiveOptionsFlowHandler(config_entries.OptionsFlow): + """Config flow options for Hive.""" + + def __init__(self, config_entry): + """Initialize Hive options flow.""" + self.hive = None + self.config_entry = config_entry + self.interval = config_entry.options.get(CONF_SCAN_INTERVAL, 120) + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_user() + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + self.hive = self.hass.data["hive"][self.config_entry.entry_id] + errors = {} + if user_input is not None: + new_interval = user_input.get(CONF_SCAN_INTERVAL) + await self.hive.updateInterval(new_interval) + return self.async_create_entry(title="", data=user_input) + + schema = vol.Schema( + { + vol.Optional(CONF_SCAN_INTERVAL, default=self.interval): vol.All( + vol.Coerce(int), vol.Range(min=30) + ) + } + ) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + +class UnknownHiveError(Exception): + """Catch unknown hive error.""" diff --git a/homeassistant/components/hive/const.py b/homeassistant/components/hive/const.py new file mode 100644 index 00000000000000..ea416fbfe32e00 --- /dev/null +++ b/homeassistant/components/hive/const.py @@ -0,0 +1,20 @@ +"""Constants for Hive.""" +ATTR_MODE = "mode" +ATTR_TIME_PERIOD = "time_period" +ATTR_ONOFF = "on_off" +CONF_CODE = "2fa" +CONFIG_ENTRY_VERSION = 1 +DEFAULT_NAME = "Hive" +DOMAIN = "hive" +PLATFORMS = ["binary_sensor", "climate", "light", "sensor", "switch", "water_heater"] +PLATFORM_LOOKUP = { + "binary_sensor": "binary_sensor", + "climate": "climate", + "light": "light", + "sensor": "sensor", + "switch": "switch", + "water_heater": "water_heater", +} +SERVICE_BOOST_HOT_WATER = "boost_hot_water" +SERVICE_BOOST_HEATING = "boost_heating" +WATER_HEATER_MODES = ["on", "off"] diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 12779ef9d2e34a..46e8c5b579069e 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -12,19 +12,18 @@ ) import homeassistant.util.color as color_util -from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ATTR_MODE, DOMAIN PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Light.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("light") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("light") entities = [] if devices: for dev in devices: @@ -43,7 +42,14 @@ def unique_id(self): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def name(self): @@ -59,7 +65,6 @@ def available(self): def extra_state_attributes(self): """Show Device Attributes.""" return { - ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), ATTR_MODE: self.attributes.get(ATTR_MODE), } @@ -117,14 +122,14 @@ async def async_turn_on(self, **kwargs): saturation = int(get_new_color[1]) new_color = (hue, saturation, 100) - await self.hive.light.turn_on( + await self.hive.light.turnOn( self.device, new_brightness, new_color_temp, new_color ) @refresh_system async def async_turn_off(self, **kwargs): """Instruct the light to turn off.""" - await self.hive.light.turn_off(self.device) + await self.hive.light.turnOff(self.device) @property def supported_features(self): @@ -142,5 +147,5 @@ def supported_features(self): async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.light.get_light(self.device) + self.device = await self.hive.light.getLight(self.device) self.attributes.update(self.device.get("attributes", {})) diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index 27f235949bfb28..f8f40401599af5 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -1,9 +1,10 @@ { "domain": "hive", "name": "Hive", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hive", "requirements": [ - "pyhiveapi==0.3.4.4" + "pyhiveapi==0.3.9" ], "codeowners": [ "@Rendili", diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index fe413a35b2f1f1..53cc643250c332 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -5,7 +5,8 @@ from homeassistant.components.sensor import DEVICE_CLASS_BATTERY from homeassistant.helpers.entity import Entity -from . import ATTR_AVAILABLE, DATA_HIVE, DOMAIN, HiveEntity +from . import HiveEntity +from .const import DOMAIN PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) @@ -14,18 +15,15 @@ } -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Sensor.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("sensor") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("sensor") entities = [] if devices: for dev in devices: - if dev["hiveType"] in DEVICETYPE: - entities.append(HiveSensorEntity(hive, dev)) + entities.append(HiveSensorEntity(hive, dev)) async_add_entities(entities, True) @@ -40,7 +38,14 @@ def unique_id(self): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def available(self): @@ -67,12 +72,7 @@ def state(self): """Return the state of the sensor.""" return self.device["status"]["state"] - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return {ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE)} - async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.sensor.get_sensor(self.device) + self.device = await self.hive.sensor.getSensor(self.device) diff --git a/homeassistant/components/hive/services.yaml b/homeassistant/components/hive/services.yaml index f09baea7655f25..f029af7b0b5184 100644 --- a/homeassistant/components/hive/services.yaml +++ b/homeassistant/components/hive/services.yaml @@ -1,24 +1,62 @@ boost_heating: + name: Boost Heating description: Set the boost mode ON defining the period of time and the desired target temperature for the boost. fields: entity_id: - description: Enter the entity_id for the device required to set the boost mode. - example: "climate.heating" + name: Entity ID + description: Select entity_id to boost. + required: true + example: climate.heating + selector: + entity: + integration: hive + domain: climate time_period: + name: Time Period description: Set the time period for the boost. - example: "01:30:00" + required: true + example: 01:30:00 + selector: + time: temperature: + name: Temperature description: Set the target temperature for the boost period. - example: "20.5" + required: true + example: 20.5 + selector: + number: + min: 7 + max: 35 + step: 0.5 + unit_of_measurement: degrees + mode: slider boost_hot_water: - description: "Set the boost mode ON or OFF defining the period of time for the boost." + name: Boost Hotwater + description: Set the boost mode ON or OFF defining the period of time for the boost. fields: entity_id: - description: Enter the entity_id for the device reuired to set the boost mode. - example: "water_heater.hot_water" + name: Entity ID + description: Select entity_id to boost. + required: true + example: water_heater.hot_water + selector: + entity: + integration: hive + domain: water_heater time_period: + name: Time Period description: Set the time period for the boost. - example: "01:30:00" + required: true + example: 01:30:00 + selector: + time: on_off: + name: Mode description: Set the boost function on or off. + required: true example: "on" + selector: + select: + options: + - "on" + - "off" diff --git a/homeassistant/components/hive/strings.json b/homeassistant/components/hive/strings.json new file mode 100644 index 00000000000000..0a7a587b2dbe62 --- /dev/null +++ b/homeassistant/components/hive/strings.json @@ -0,0 +1,53 @@ +{ + "config": { + "step": { + "user": { + "title": "Hive Login", + "description": "Enter your Hive login information and configuration.", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "scan_interval": "Scan Interval (seconds)" + } + }, + "2fa": { + "title": "Hive Two-factor Authentication.", + "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", + "data": { + "2fa": "Two-factor code" + } + }, + "reauth": { + "title": "Hive Login", + "description": "Re-enter your Hive login information.", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", + "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", + "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", + "no_internet_available": "An internet connection is required to connect to Hive.", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "unknown_entry": "Unable to find existing entry.", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + } + }, + "options": { + "step": { + "user": { + "title": "Options for Hive", + "description": "Update the scan interval to poll for data more often.", + "data": { + "scan_interval": "Scan Interval (seconds)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 821f48dbf974b7..acc2040db00255 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -3,19 +3,18 @@ from homeassistant.components.switch import SwitchEntity -from . import ATTR_AVAILABLE, ATTR_MODE, DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from . import HiveEntity, refresh_system +from .const import ATTR_MODE, DOMAIN PARALLEL_UPDATES = 0 SCAN_INTERVAL = timedelta(seconds=15) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Switch.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("switch") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("switch") entities = [] if devices: for dev in devices: @@ -34,7 +33,15 @@ def unique_id(self): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + if self.device["hiveType"] == "activeplug": + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def name(self): @@ -50,7 +57,6 @@ def available(self): def extra_state_attributes(self): """Show Device Attributes.""" return { - ATTR_AVAILABLE: self.attributes.get(ATTR_AVAILABLE), ATTR_MODE: self.attributes.get(ATTR_MODE), } @@ -67,16 +73,14 @@ def is_on(self): @refresh_system async def async_turn_on(self, **kwargs): """Turn the switch on.""" - if self.device["hiveType"] == "activeplug": - await self.hive.switch.turn_on(self.device) + await self.hive.switch.turnOn(self.device) @refresh_system async def async_turn_off(self, **kwargs): """Turn the device off.""" - if self.device["hiveType"] == "activeplug": - await self.hive.switch.turn_off(self.device) + await self.hive.switch.turnOff(self.device) async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.switch.get_plug(self.device) + self.device = await self.hive.switch.getPlug(self.device) diff --git a/homeassistant/components/hive/translations/en.json b/homeassistant/components/hive/translations/en.json new file mode 100644 index 00000000000000..1d491d64ebf86d --- /dev/null +++ b/homeassistant/components/hive/translations/en.json @@ -0,0 +1,53 @@ +{ + "config": { + "step": { + "user": { + "title": "Hive Login", + "description": "Enter your Hive login information and configuration.", + "data": { + "username": "Username", + "password": "Password", + "scan_interval": "Scan Interval (seconds)" + } + }, + "2fa": { + "title": "Hive Two-factor Authentication.", + "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", + "data": { + "2fa": "Two-factor code" + } + }, + "reauth": { + "title": "Hive Login", + "description": "Re-enter your Hive login information.", + "data": { + "username": "Username", + "password": "Password" + } + } + }, + "error": { + "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", + "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", + "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", + "no_internet_available": "An internet connection is required to connect to Hive.", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Account is already configured", + "unknown_entry": "Unable to find existing entry.", + "reauth_successful": "Re-authentication was successful" + } + }, + "options": { + "step": { + "user": { + "title": "Options for Hive", + "description": "Update the scan interval to poll for data more often.", + "data": { + "scan_interval": "Scan Interval (seconds)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index 56e98a690b8ff4..5d8eb590ea7ebe 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -2,6 +2,8 @@ from datetime import timedelta +import voluptuous as vol + from homeassistant.components.water_heater import ( STATE_ECO, STATE_OFF, @@ -10,8 +12,16 @@ WaterHeaterEntity, ) from homeassistant.const import TEMP_CELSIUS - -from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system +from homeassistant.helpers import config_validation as cv, entity_platform + +from . import HiveEntity, refresh_system +from .const import ( + ATTR_ONOFF, + ATTR_TIME_PERIOD, + DOMAIN, + SERVICE_BOOST_HOT_WATER, + WATER_HEATER_MODES, +) SUPPORT_FLAGS_HEATER = SUPPORT_OPERATION_MODE HOTWATER_NAME = "Hot Water" @@ -32,19 +42,32 @@ SUPPORT_WATER_HEATER = [STATE_ECO, STATE_ON, STATE_OFF] -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Hive Hotwater.""" - if discovery_info is None: - return +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Hive thermostat based on a config entry.""" - hive = hass.data[DOMAIN].get(DATA_HIVE) - devices = hive.devices.get("water_heater") + hive = hass.data[DOMAIN][entry.entry_id] + devices = hive.session.deviceList.get("water_heater") entities = [] if devices: for dev in devices: entities.append(HiveWaterHeater(hive, dev)) async_add_entities(entities, True) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_BOOST_HOT_WATER, + { + vol.Optional(ATTR_TIME_PERIOD, default="00:30:00"): vol.All( + cv.time_period, + cv.positive_timedelta, + lambda td: td.total_seconds() // 60, + ), + vol.Required(ATTR_ONOFF): vol.In(WATER_HEATER_MODES), + }, + "async_hot_water_boost", + ) + class HiveWaterHeater(HiveEntity, WaterHeaterEntity): """Hive Water Heater Device.""" @@ -57,7 +80,14 @@ def unique_id(self): @property def device_info(self): """Return device information.""" - return {"identifiers": {(DOMAIN, self.unique_id)}, "name": self.name} + return { + "identifiers": {(DOMAIN, self.device["device_id"])}, + "name": self.device["device_name"], + "model": self.device["deviceData"]["model"], + "manufacturer": self.device["deviceData"]["manufacturer"], + "sw_version": self.device["deviceData"]["version"], + "via_device": (DOMAIN, self.device["parentDevice"]), + } @property def supported_features(self): @@ -92,20 +122,28 @@ def operation_list(self): @refresh_system async def async_turn_on(self, **kwargs): """Turn on hotwater.""" - await self.hive.hotwater.set_mode(self.device, "MANUAL") + await self.hive.hotwater.setMode(self.device, "MANUAL") @refresh_system async def async_turn_off(self, **kwargs): """Turn on hotwater.""" - await self.hive.hotwater.set_mode(self.device, "OFF") + await self.hive.hotwater.setMode(self.device, "OFF") @refresh_system async def async_set_operation_mode(self, operation_mode): """Set operation mode.""" new_mode = HASS_TO_HIVE_STATE[operation_mode] - await self.hive.hotwater.set_mode(self.device, new_mode) + await self.hive.hotwater.setMode(self.device, new_mode) + + @refresh_system + async def async_hot_water_boost(self, time_period, on_off): + """Handle the service call.""" + if on_off == "on": + await self.hive.hotwater.turnBoostOn(self.device, time_period) + elif on_off == "off": + await self.hive.hotwater.turnBoostOff(self.device) async def async_update(self): """Update all Node data from Hive.""" await self.hive.session.updateData(self.device) - self.device = await self.hive.hotwater.get_hotwater(self.device) + self.device = await self.hive.hotwater.getHotwater(self.device) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a0a846c0d44e4a..057ebe74865411 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -92,6 +92,7 @@ "harmony", "heos", "hisense_aehw4a1", + "hive", "hlk_sw16", "home_connect", "homekit", diff --git a/requirements_all.txt b/requirements_all.txt index 03fd077d9be636..a749acabf9907b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1428,7 +1428,7 @@ pyheos==0.7.2 pyhik==0.2.8 # homeassistant.components.hive -pyhiveapi==0.3.4.4 +pyhiveapi==0.3.9 # homeassistant.components.homematic pyhomematic==0.1.72 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7ff9b14e6d74d6..82425db675b9d2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -747,6 +747,9 @@ pyhaversion==3.4.2 # homeassistant.components.heos pyheos==0.7.2 +# homeassistant.components.hive +pyhiveapi==0.3.9 + # homeassistant.components.homematic pyhomematic==0.1.72 diff --git a/tests/components/hive/test_config_flow.py b/tests/components/hive/test_config_flow.py new file mode 100644 index 00000000000000..dae69eebd96070 --- /dev/null +++ b/tests/components/hive/test_config_flow.py @@ -0,0 +1,576 @@ +"""Test the Hive config flow.""" +from unittest.mock import patch + +from apyhiveapi.helper import hive_exceptions + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.hive.const import CONF_CODE, DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME + +from tests.common import MockConfigEntry + +USERNAME = "username@home-assistant.com" +UPDATED_USERNAME = "updated_username@home-assistant.com" +PASSWORD = "test-password" +UPDATED_PASSWORD = "updated-password" +INCORRECT_PASSWORD = "incoreect-password" +SCAN_INTERVAL = 120 +UPDATED_SCAN_INTERVAL = 60 +MFA_CODE = "1234" +MFA_RESEND_CODE = "0000" +MFA_INVALID_CODE = "HIVE" + + +async def test_import_flow(hass): + """Check import flow.""" + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.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={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_user_flow(hass): + """Test the user flow.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == USERNAME + assert result2["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_user_flow_2fa(hass): + """Test user flow with 2FA.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {CONF_CODE: MFA_CODE} + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"] == USERNAME + assert result3["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_reauth_flow(hass): + """Test the reauth flow.""" + + await setup.async_setup_component(hass, "persistent_notification", {}) + mock_config = MockConfigEntry( + domain=DOMAIN, + unique_id=USERNAME, + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: INCORRECT_PASSWORD, + "tokens": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + }, + ) + mock_config.add_to_hass(hass) + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidPassword(), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "unique_id": mock_config.unique_id, + }, + data=mock_config.data, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_password"} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: UPDATED_PASSWORD, + }, + ) + await hass.async_block_till_done() + + assert mock_config.data.get("username") == USERNAME + assert mock_config.data.get("password") == UPDATED_PASSWORD + assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_option_flow(hass): + """Test config flow options.""" + + entry = MockConfigEntry( + domain=DOMAIN, + title=USERNAME, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + 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"] == "user" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_SCAN_INTERVAL: UPDATED_SCAN_INTERVAL} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_SCAN_INTERVAL] == UPDATED_SCAN_INTERVAL + + +async def test_user_flow_2fa_send_new_code(hass): + """Resend a 2FA code if it didn't arrive.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {CONF_CODE: MFA_RESEND_CODE} + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == CONF_CODE + assert result3["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, + }, + ), patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], {CONF_CODE: MFA_CODE} + ) + await hass.async_block_till_done() + + assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result4["title"] == USERNAME + assert result4["data"] == { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + "tokens": { + "AuthenticationResult": { + "AccessToken": "mock-access-token", + "RefreshToken": "mock-refresh-token", + }, + "ChallengeName": "SUCCESS", + }, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +async def test_abort_if_existing_entry(hass): + """Check flow abort when an entry already exist.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=USERNAME, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + options={CONF_SCAN_INTERVAL: SCAN_INTERVAL}, + ) + config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + data={ + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_user_flow_invalid_username(hass): + """Test user flow with invalid username.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidUsername(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_username"} + + +async def test_user_flow_invalid_password(hass): + """Test user flow with invalid password.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveInvalidPassword(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_password"} + + +async def test_user_flow_no_internet_connection(hass): + """Test user flow with no internet connection.""" + + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + side_effect=hive_exceptions.HiveApiError(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "no_internet_available"} + + +async def test_user_flow_2fa_no_internet_connection(hass): + """Test user flow with no internet connection.""" + + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + side_effect=hive_exceptions.HiveApiError(), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {CONF_CODE: MFA_CODE}, + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == CONF_CODE + assert result3["errors"] == {"base": "no_internet_available"} + + +async def test_user_flow_2fa_invalid_code(hass): + """Test user flow with 2FA.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + assert result2["errors"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + side_effect=hive_exceptions.HiveInvalid2FACode(), + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_CODE: MFA_INVALID_CODE}, + ) + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["step_id"] == CONF_CODE + assert result3["errors"] == {"base": "invalid_code"} + + +async def test_user_flow_unknown_error(hass): + """Test user flow when unknown error occurs.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={"ChallengeName": "FAILED", "InvalidAuthenticationResult": {}}, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + +async def test_user_flow_2fa_unknown_error(hass): + """Test 2fa flow when unknown error occurs.""" + 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"] == {} + + with patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SMS_MFA", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == CONF_CODE + + with patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={"ChallengeName": "FAILED", "InvalidAuthenticationResult": {}}, + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {CONF_CODE: MFA_CODE}, + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result3["errors"] == {"base": "unknown"} From 6b98583bc1f6f0672fc19fe6a224c46282f0f180 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 15 Mar 2021 12:45:36 +0100 Subject: [PATCH 1266/1818] Add tests for Netatmo climate (#46392) * Add tests for Netatmo climate * Add comments and fake webhook events * Split tests * Split tests * Clean up * Fix coveragerc * Fix requirements * Remove freezegun dependency * Move async_block_till_done to * Call async_handle_webhook directly * Use async_handle_webhook directly p2 * Exclude helper.py from * Remove assertion of implementation details * Use the webhook integration handler * Extract function --- .coveragerc | 2 - homeassistant/components/netatmo/climate.py | 11 +- tests/components/netatmo/common.py | 44 ++ tests/components/netatmo/conftest.py | 127 +++++ tests/components/netatmo/test_climate.py | 539 ++++++++++++++++++ tests/fixtures/netatmo/gethomedata.json | 318 +++++++++++ tests/fixtures/netatmo/getstationsdata.json | 600 ++++++++++++++++++++ tests/fixtures/netatmo/homesdata.json | 595 +++++++++++++++++++ tests/fixtures/netatmo/homestatus.json | 113 ++++ 9 files changed, 2343 insertions(+), 6 deletions(-) create mode 100644 tests/components/netatmo/common.py create mode 100644 tests/components/netatmo/conftest.py create mode 100644 tests/components/netatmo/test_climate.py create mode 100644 tests/fixtures/netatmo/gethomedata.json create mode 100644 tests/fixtures/netatmo/getstationsdata.json create mode 100644 tests/fixtures/netatmo/homesdata.json create mode 100644 tests/fixtures/netatmo/homestatus.json diff --git a/.coveragerc b/.coveragerc index 72d6e1e9293305..d406698cbb11a4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -635,8 +635,6 @@ omit = homeassistant/components/netatmo/__init__.py homeassistant/components/netatmo/api.py homeassistant/components/netatmo/camera.py - homeassistant/components/netatmo/climate.py - homeassistant/components/netatmo/const.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/light.py diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index c2a6e484771cdd..3b05e263f023ab 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -2,6 +2,7 @@ import logging from typing import List, Optional +import pyatmo import voluptuous as vol from homeassistant.components.climate import ClimateEntity @@ -251,7 +252,7 @@ async def handle_event(self, event): """Handle webhook events.""" data = event["data"] - if not data.get("home"): + if data.get("home") is None: return home = data["home"] @@ -569,7 +570,9 @@ def _service_set_schedule(self, **kwargs): schedule_id = sid if not schedule_id: - _LOGGER.error("You passed an invalid schedule") + _LOGGER.error( + "%s is not a invalid schedule", kwargs.get(ATTR_SCHEDULE_NAME) + ) return self._data.switch_home_schedule(home_id=self._home_id, schedule_id=schedule_id) @@ -586,7 +589,7 @@ def device_info(self): return {**super().device_info, "suggested_area": self._room_data["name"]} -def interpolate(batterylevel, module_type): +def interpolate(batterylevel: int, module_type: str) -> int: """Interpolate battery level depending on device type.""" na_battery_levels = { NA_THERM: { @@ -628,7 +631,7 @@ def interpolate(batterylevel, module_type): return int(pct) -def get_all_home_ids(home_data): +def get_all_home_ids(home_data: pyatmo.HomeData) -> List[str]: """Get all the home ids returned by NetAtmo API.""" if home_data is None: return [] diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py new file mode 100644 index 00000000000000..c8014a9b2a930b --- /dev/null +++ b/tests/components/netatmo/common.py @@ -0,0 +1,44 @@ +"""Common methods used across tests for Netatmo.""" +import json + +from tests.common import load_fixture + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" +ALL_SCOPES = [ + "read_station", + "read_camera", + "access_camera", + "write_camera", + "read_presence", + "access_presence", + "write_presence", + "read_homecoach", + "read_smokedetector", + "read_thermostat", + "write_thermostat", +] + + +def fake_post_request(**args): + """Return fake data.""" + if "url" not in args: + return "{}" + + endpoint = args["url"].split("/")[-1] + if endpoint in [ + "setpersonsaway", + "setpersonshome", + "setstate", + "setroomthermpoint", + "setthermmode", + "switchhomeschedule", + ]: + return f'{{"{endpoint}": true}}' + + return json.loads(load_fixture(f"netatmo/{endpoint}.json")) + + +def fake_post_request_no_data(**args): + """Fake error during requesting backend data.""" + return "{}" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py new file mode 100644 index 00000000000000..b18b70f323eced --- /dev/null +++ b/tests/components/netatmo/conftest.py @@ -0,0 +1,127 @@ +"""Provide common Netatmo fixtures.""" +from contextlib import contextmanager +from time import time +from unittest.mock import patch + +import pytest + +from .common import ALL_SCOPES, fake_post_request, fake_post_request_no_data + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="config_entry") +async def mock_config_entry_fixture(hass): + """Mock a config entry.""" + mock_entry = MockConfigEntry( + domain="netatmo", + data={ + "auth_implementation": "cloud", + "token": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "expires_at": time() + 1000, + "scope": " ".join(ALL_SCOPES), + }, + }, + options={ + "weather_areas": { + "Home avg": { + "lat_ne": 32.2345678, + "lon_ne": -117.1234567, + "lat_sw": 32.1234567, + "lon_sw": -117.2345678, + "show_on_map": False, + "area_name": "Home avg", + "mode": "avg", + }, + "Home max": { + "lat_ne": 32.2345678, + "lon_ne": -117.1234567, + "lat_sw": 32.1234567, + "lon_sw": -117.2345678, + "show_on_map": True, + "area_name": "Home max", + "mode": "max", + }, + } + }, + ) + mock_entry.add_to_hass(hass) + + return mock_entry + + +@contextmanager +def selected_platforms(platforms=["camera", "climate", "light", "sensor"]): + """Restrict loaded platforms to list given.""" + with patch("homeassistant.components.netatmo.PLATFORMS", platforms), patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ): + mock_auth.return_value.post_request.side_effect = fake_post_request + yield + + +@pytest.fixture(name="entry") +async def mock_entry_fixture(hass, config_entry): + """Mock setup of all platforms.""" + with selected_platforms(): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="sensor_entry") +async def mock_sensor_entry_fixture(hass, config_entry): + """Mock setup of sensor platform.""" + with selected_platforms(["sensor"]): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="camera_entry") +async def mock_camera_entry_fixture(hass, config_entry): + """Mock setup of camera platform.""" + with selected_platforms(["camera"]): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="light_entry") +async def mock_light_entry_fixture(hass, config_entry): + """Mock setup of light platform.""" + with selected_platforms(["light"]): + await hass.config_entries.async_setup(config_entry.entry_id) + return config_entry + + +@pytest.fixture(name="climate_entry") +async def mock_climate_entry_fixture(hass, config_entry): + """Mock setup of climate platform.""" + with selected_platforms(["climate"]): + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + return config_entry + + +@pytest.fixture(name="entry_error") +async def mock_entry_error_fixture(hass, config_entry): + """Mock erroneous setup of platforms.""" + with patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ): + mock_auth.return_value.post_request.side_effect = fake_post_request_no_data + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + return config_entry diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py new file mode 100644 index 00000000000000..e7ad1669769632 --- /dev/null +++ b/tests/components/netatmo/test_climate.py @@ -0,0 +1,539 @@ +"""The tests for the Netatmo climate platform.""" +from unittest.mock import Mock + +import pytest + +from homeassistant.components.climate import ( + DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, +) +from homeassistant.components.netatmo import climate +from homeassistant.components.netatmo.climate import ( + NA_THERM, + NA_VALVE, + PRESET_FROST_GUARD, + PRESET_SCHEDULE, +) +from homeassistant.components.netatmo.const import ( + ATTR_SCHEDULE_NAME, + SERVICE_SET_SCHEDULE, +) +from homeassistant.components.webhook import async_handle_webhook +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID +from homeassistant.util.aiohttp import MockRequest + + +async def simulate_webhook(hass, webhook_id, response): + """Simulate a webhook event.""" + request = MockRequest(content=response, mock_source="test") + await async_handle_webhook(hass, webhook_id, request) + await hass.async_block_till_done() + + +async def test_webhook_event_handling_thermostats(hass, climate_entry): + """Test service and webhook event handling with thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_livingroom = "climate.netatmo_livingroom" + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 12 + + # Test service setting the temperature + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_TEMPERATURE: 21}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat manual set point + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' + b'"rooms": [{ "id": "2746182631", "name": "Livingroom", "type": "livingroom",' + b'"therm_setpoint_mode": "manual", "therm_setpoint_temperature": 21,' + b'"therm_setpoint_end_time": 1612734552}], "modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "manual", "event_type": "set_point",' + b'"temperature": 21, "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "heat" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 21 + + # Test service setting the HVAC mode to "heat" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' + b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' + b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "heat" + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30 + + # Test service setting the HVAC mode to "off" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook turn thermostat off + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' + b'"therm_setpoint_mode": "off"}],"modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "off", "event_type": "set_point",' + b'"push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "off" + + # Test service setting the HVAC mode to "auto" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVAC_MODE_AUTO}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode cancel set point + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b","room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' + b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "home",' + b'"event_type": "cancel_set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + +async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry): + """Test service with frost guard preset for thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_livingroom = "climate.netatmo_livingroom" + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + # Test service setting the preset mode to "frost guard" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: climate_entity_livingroom, + ATTR_PRESET_MODE: PRESET_FROST_GUARD, + }, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode change to "Frost Guard" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"therm_mode": "hg"}, "mode": "hg", "previous_mode": "schedule",' + b'"push_type":"home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Frost Guard" + ) + + # Test service setting the preset mode to "frost guard" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + { + ATTR_ENTITY_ID: climate_entity_livingroom, + ATTR_PRESET_MODE: PRESET_SCHEDULE, + }, + blocking=True, + ) + await hass.async_block_till_done() + + # Test webhook thermostat mode change to "Schedule" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"therm_mode": "schedule"}, "mode": "schedule", "previous_mode": "hg",' + b'"push_type": "home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + +async def test_service_preset_modes_thermostat(hass, climate_entry): + """Test service with preset modes for thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_livingroom = "climate.netatmo_livingroom" + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] + == "Schedule" + ) + + # Test service setting the preset mode to "away" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake webhook thermostat mode change to "Away" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", ' + b'"event_type": "therm_mode","home": {"id": "91763b24c43d3e344f424e8b",' + b'"therm_mode": "away"},"mode": "away","previous_mode": "schedule",' + b'"push_type": "home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "auto" + assert ( + hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" + ) + + # Test service setting the preset mode to "boost" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + await hass.async_block_till_done() + + # TFakeest webhook thermostat mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email":"john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' + b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' + b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' + b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_livingroom).state == "heat" + assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30 + + +async def test_webhook_event_handling_no_data(hass, climate_entry): + """Test service and webhook event handling with erroneous data.""" + # Test webhook without home entry + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"push_type": "home_event_changed"}' + ) + await simulate_webhook(hass, webhook_id, response) + + # Test webhook with different home id + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "3d3e344f491763b24c424e8b",' + b'"room_id": "2746182631", "home": {"id": "3d3e344f491763b24c424e8b",' + b'"name": "MYHOME","country": "DE", "rooms": [], "modules": []}, "mode": "home",' + b'"event_type": "cancel_set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + # Test webhook without room entries + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2746182631", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"name": "MYHOME", "country": "DE", "rooms": [], "modules": []}, "mode": "home",' + b'"event_type": "cancel_set_point","push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + +async def test_service_schedule_thermostats(hass, climate_entry, caplog): + """Test service for selecting Netatmo schedule with thermostats.""" + climate_entity_livingroom = "climate.netatmo_livingroom" + + # Test setting a valid schedule + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert ( + "Setting 91763b24c43d3e344f424e8b schedule to Winter (b1b54a2f45795764f59d50d8)" + in caplog.text + ) + + # Test setting an invalid schedule + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "summer is not a invalid schedule" in caplog.text + + +async def test_service_preset_mode_already_boost_valves(hass, climate_entry): + """Test service with boost preset for valves when already in boost mode.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + assert hass.states.get(climate_entity_entrada).state == "auto" + assert ( + hass.states.get(climate_entity_entrada).attributes["preset_mode"] + == "Frost Guard" + ) + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 + + # Test webhook valve mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' + b'"country": "DE","rooms": [{"id": "2833524037", "name": "Entrada", "type": "lobby",' + b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' + b'"mode": "max","event_type": "set_point","push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + # Test service setting the preset mode to "boost" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + await hass.async_block_till_done() + + # Test webhook valve mode change to "Max" + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b",' + b'"name": "MYHOME","country": "DE","rooms": [{"id": "2833524037", "name": "Entrada",' + b'"type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat" + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30 + + +async def test_service_preset_mode_boost_valves(hass, climate_entry): + """Test service with boost preset for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Test service setting the preset mode to "boost" + assert hass.states.get(climate_entity_entrada).state == "auto" + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake backend response + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' + b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' + b'"country": "DE", "rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' + b'"therm_setpoint_mode": "max","therm_setpoint_end_time": 1612749189}],' + b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' + b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat" + assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30 + + +async def test_service_preset_mode_invalid(hass, climate_entry, caplog): + """Test service with invalid preset.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: "climate.netatmo_cocina", ATTR_PRESET_MODE: "invalid"}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "Preset mode 'invalid' not available" in caplog.text + + +async def test_valves_service_turn_off(hass, climate_entry): + """Test service turn off for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Test turning valve off + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: climate_entity_entrada}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake backend response for valve being turned off + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' + b'"therm_setpoint_mode": "off"}], "modules": [{"id": "12:34:56:00:01:ae","name": "Entrada",' + b'"type": "NRV"}]}, "mode": "off", "event_type": "set_point", "push_type":"display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "off" + + +async def test_valves_service_turn_on(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Test turning valve on + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: climate_entity_entrada}, + blocking=True, + ) + await hass.async_block_till_done() + + # Fake backend response for valve being turned on + response = ( + b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' + b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' + b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' + b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' + b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' + b'"name": "Entrada", "type": "NRV"}]}, "mode": "home", "event_type": "cancel_set_point",' + b'"push_type": "display_change"}' + ) + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "auto" + + +@pytest.mark.parametrize( + "batterylevel, module_type, expected", + [ + (4101, NA_THERM, 100), + (3601, NA_THERM, 80), + (3450, NA_THERM, 65), + (3301, NA_THERM, 50), + (3001, NA_THERM, 20), + (2799, NA_THERM, 0), + (3201, NA_VALVE, 100), + (2701, NA_VALVE, 80), + (2550, NA_VALVE, 65), + (2401, NA_VALVE, 50), + (2201, NA_VALVE, 20), + (2001, NA_VALVE, 0), + ], +) +async def test_interpolate(batterylevel, module_type, expected): + """Test interpolation of battery levels depending on device type.""" + assert climate.interpolate(batterylevel, module_type) == expected + + +async def test_get_all_home_ids(): + """Test extracting all home ids returned by NetAtmo API.""" + # Test with backend returning no data + assert climate.get_all_home_ids(None) == [] + + # Test with fake data + home_data = Mock() + home_data.homes = { + "123": {"id": "123", "name": "Home 1", "modules": [], "therm_schedules": []}, + "987": {"id": "987", "name": "Home 2", "modules": [], "therm_schedules": []}, + } + expected = ["123", "987"] + assert climate.get_all_home_ids(home_data) == expected diff --git a/tests/fixtures/netatmo/gethomedata.json b/tests/fixtures/netatmo/gethomedata.json new file mode 100644 index 00000000000000..db7d6aa438d823 --- /dev/null +++ b/tests/fixtures/netatmo/gethomedata.json @@ -0,0 +1,318 @@ +{ + "body": { + "homes": [ + { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "last_seen": 1557071156, + "out_of_sight": true, + "face": { + "id": "d74fad765b9100ef480720a9", + "version": 1, + "key": "a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7" + }, + "pseudo": "John Doe" + }, + { + "id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "last_seen": 1560600726, + "out_of_sight": true, + "face": { + "id": "d74fad765b9100ef480720a9", + "version": 3, + "key": "a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72" + }, + "pseudo": "Jane Doe" + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff3", + "last_seen": 1560626666, + "out_of_sight": false, + "face": { + "id": "d74fad765b9100ef480720a9", + "version": 1, + "key": "a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8" + }, + "pseudo": "Richard Doe" + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff4", + "last_seen": 1560621666, + "out_of_sight": true, + "face": { + "id": "d0ef44fad765b980720710a9", + "version": 1, + "key": "ab029da89f84a95c2d1730fb67fc40cb2d74b80869ecdf2bb8b72039d2c69928", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d0ef44fad765b980720710a9ab029da89f84a95c2d1730fb67fc40cb2d74b80869ecdf2bb8b72039d2c69928" + } + } + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin" + }, + "cameras": [ + { + "id": "12:34:56:00:f1:62", + "type": "NACamera", + "status": "on", + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTg,,", + "is_local": true, + "sd_status": "on", + "alim_status": "on", + "name": "Hall", + "modules": [ + { + "id": "12:34:56:00:f2:f1", + "type": "NIS", + "battery_percent": 84, + "rf": 68, + "status": "no_news", + "monitoring": "on", + "alim_source": "battery", + "tamper_detection_enabled": true, + "name": "Welcome's Siren" + } + ], + "use_pin_code": false, + "last_setup": 1544828430 + }, + { + "id": "12:34:56:00:a5:a4", + "type": "NOC", + "status": "on", + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTw,,", + "is_local": false, + "sd_status": "on", + "alim_status": "on", + "name": "Garden", + "last_setup": 1563737661, + "light_mode_status": "auto" + } + ], + "smokedetectors": [ + { + "id": "12:34:56:00:8b:a2", + "type": "NSD", + "last_setup": 1567261859, + "name": "Hall" + }, + { + "id": "12:34:56:00:8b:ac", + "type": "NSD", + "last_setup": 1567262759, + "name": "Kitchen" + } + ], + "events": [ + { + "id": "a1b2c3d4e5f6abcdef123456", + "type": "person", + "time": 1560604700, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "video_status": "deleted", + "is_arrival": false, + "message": "John Doe gesehen" + }, + { + "id": "a1b2c3d4e5f6abcdef123457", + "type": "person_away", + "time": 1560602400, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "message": "John Doe hat das Haus verlassen", + "sub_message": "John Doe gilt als abwesend, da das mit diesem Profil verbundene Telefon den Bereich des Hauses verlassen hat." + }, + { + "id": "a1b2c3d4e5f6abcdef123458", + "type": "person", + "time": 1560601200, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "video_status": "deleted", + "is_arrival": false, + "message": "John Doe gesehen" + }, + { + "id": "a1b2c3d4e5f6abcdef123459", + "type": "person", + "time": 1560600100, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "snapshot": { + "id": "d74fad765b9100ef480720a9", + "version": 1, + "key": "a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72" + }, + "video_id": "12345678-36bc-4b9a-9762-5194e707ed51", + "video_status": "available", + "is_arrival": false, + "message": "Jane Doe gesehen" + }, + { + "id": "a1b2c3d4e5f6abcdef12345a", + "type": "person", + "time": 1560603600, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "person_id": "91827375-7e04-5298-83ae-a0cb8372dff3", + "snapshot": { + "id": "532dde8d17554c022ab071b8", + "version": 1, + "key": "9fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28", + "url": "https://netatmocameraimage.blob.core.windows.net/production/532dde8d17554c022ab071b89fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28" + }, + "video_id": "12345678-1234-46cb-ad8f-23d893874099", + "video_status": "available", + "is_arrival": false, + "message": "Bewegung erkannt" + }, + { + "id": "a1b2c3d4e5f6abcdef12345b", + "type": "movement", + "time": 1560506200, + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "category": "human", + "snapshot": { + "id": "532dde8d17554c022ab071b9", + "version": 1, + "key": "8fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28", + "url": "https://netatmocameraimage.blob.core.windows.net/production/532dde8d17554c022ab071b98fbe490fffacf45b8416241946541b031a004a09b6747feb6c38c3ccbc456b28" + }, + "vignette": { + "id": "5dc021b5dea854bd2321707a", + "version": 1, + "key": "58c5a05bd6bd908f6bf368865ef7355231c44215f8eb7ae458c919b2c67b4944", + "url": "https://netatmocameraimage.blob.core.windows.net/production/5dc021b5dea854bd2321707a58c5a05bd6bd908f6bf368865ef7355231c44215f8eb7ae458c919b2c67b4944" + }, + "video_id": "12345678-1234-46cb-ad8f-23d89387409a", + "video_status": "available", + "message": "Bewegung erkannt" + }, + { + "id": "a1b2c3d4e5f6abcdef12345c", + "type": "sound_test", + "time": 1560506210, + "camera_id": "12:34:56:00:8b:a2", + "device_id": "12:34:56:00:8b:a2", + "sub_type": 0, + "message": "Hall: Alarmton erfolgreich getestet" + }, + { + "id": "a1b2c3d4e5f6abcdef12345d", + "type": "wifi_status", + "time": 1560506220, + "camera_id": "12:34:56:00:8b:a2", + "device_id": "12:34:56:00:8b:a2", + "sub_type": 1, + "message": "Hall:WLAN-Verbindung erfolgreich hergestellt" + }, + { + "id": "a1b2c3d4e5f6abcdef12345e", + "type": "outdoor", + "time": 1560643100, + "camera_id": "12:34:56:00:a5:a4", + "device_id": "12:34:56:00:a5:a4", + "video_id": "string", + "video_status": "available", + "event_list": [ + { + "type": "string", + "time": 1560643100, + "offset": 0, + "id": "c81bcf7b-2cfg-4ac9-8455-487ed00c0000", + "message": "Animal détecté", + "snapshot": { + "id": "5715e16849c75xxxx00000000xxxxx", + "version": 1, + "key": "7ac578d05030d0e170643a787ee0a29663dxxx00000xxxxx00000", + "url": "https://netatmocameraimage.blob.core.windows.net/production/1aa" + }, + "vignette": { + "id": "5715e16849c75xxxx00000000xxxxx", + "version": 1, + "key": "7ac578d05030d0e170643a787ee0a29663dxxx00000xxxxx00000", + "url": "https://netatmocameraimage.blob.core.windows.net/production/1aa00000" + } + }, + { + "type": "string", + "time": 1560506222, + "offset": 0, + "id": "c81bcf7b-2cfg-4ac9-8455-487ed00c0000", + "message": "Animal détecté", + "snapshot": { + "filename": "vod\/af74631d-8311-42dc-825b-82e3abeaab09\/events\/c53b-aze7a.jpg" + }, + "vignette": { + "filename": "vod\/af74631d-8311-42dc-825b-82e3abeaab09\/events\/c5.jpg" + } + } + ] + } + ] + }, + { + "id": "91763b24c43d3e344f424e8c", + "persons": [], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin" + }, + "cameras": [ + { + "id": "12:34:56:00:a5:a5", + "type": "NOC", + "status": "on", + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTz,,", + "is_local": true, + "sd_status": "on", + "alim_status": "on", + "name": "Street", + "last_setup": 1563737561, + "light_mode_status": "auto" + } + ], + "smokedetectors": [] + }, + { + "id": "91763b24c43d3e344f424e8d", + "persons": [], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin" + }, + "cameras": [], + "smokedetectors": [] + } + ], + "user": { + "reg_locale": "de-DE", + "lang": "de-DE", + "country": "DE", + "mail": "john@doe.com" + }, + "global_info": { + "show_tags": true + } + }, + "status": "ok", + "time_exec": 0.03621506690979, + "time_server": 1560626960 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/getstationsdata.json b/tests/fixtures/netatmo/getstationsdata.json new file mode 100644 index 00000000000000..2a18c7bd28071c --- /dev/null +++ b/tests/fixtures/netatmo/getstationsdata.json @@ -0,0 +1,600 @@ +{ + "body": { + "devices": [ + { + "_id": "12:34:56:37:11:ca", + "cipher_id": "enc:16:zjiZF/q8jTScXVdDa/kvhUAIUPGeYszaD1ClEf8byAJkRjxc5oth7cAocrMUIApX", + "date_setup": 1544558432, + "last_setup": 1544558432, + "type": "NAMain", + "last_status_store": 1559413181, + "module_name": "NetatmoIndoor", + "firmware": 137, + "last_upgrade": 1544558433, + "wifi_status": 45, + "reachable": true, + "co2_calibrating": false, + "station_name": "MyStation", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 664, + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1559413171, + "Temperature": 24.6, + "CO2": 749, + "Humidity": 36, + "Noise": 37, + "Pressure": 1017.3, + "AbsolutePressure": 939.7, + "min_temp": 23.4, + "max_temp": 25.6, + "date_min_temp": 1559371924, + "date_max_temp": 1559411964, + "temp_trend": "stable", + "pressure_trend": "down" + }, + "modules": [ + { + "_id": "12:34:56:36:fc:de", + "type": "NAModule1", + "module_name": "NetatmoOutdoor", + "data_type": [ + "Temperature", + "Humidity" + ], + "last_setup": 1544558433, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413157, + "Temperature": 28.6, + "Humidity": 24, + "min_temp": 16.9, + "max_temp": 30.3, + "date_min_temp": 1559365579, + "date_max_temp": 1559404698, + "temp_trend": "down" + }, + "firmware": 46, + "last_message": 1559413177, + "last_seen": 1559413157, + "rf_status": 65, + "battery_vp": 5738, + "battery_percent": 87 + }, + { + "_id": "12:34:56:07:bb:3e", + "type": "NAModule4", + "module_name": "Kitchen", + "data_type": [ + "Temperature", + "CO2", + "Humidity" + ], + "last_setup": 1548956696, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413125, + "Temperature": 28, + "CO2": 503, + "Humidity": 26, + "min_temp": 25, + "max_temp": 28, + "date_min_temp": 1559371577, + "date_max_temp": 1559412561, + "temp_trend": "up" + }, + "firmware": 44, + "last_message": 1559413177, + "last_seen": 1559413177, + "rf_status": 73, + "battery_vp": 5687, + "battery_percent": 83 + }, + { + "_id": "12:34:56:07:bb:0e", + "type": "NAModule4", + "module_name": "Livingroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity" + ], + "last_setup": 1548957209, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413093, + "Temperature": 26.4, + "CO2": 451, + "Humidity": 31, + "min_temp": 25.1, + "max_temp": 26.4, + "date_min_temp": 1559365290, + "date_max_temp": 1559413093, + "temp_trend": "stable" + }, + "firmware": 44, + "last_message": 1559413177, + "last_seen": 1559413093, + "rf_status": 84, + "battery_vp": 5626, + "battery_percent": 79 + }, + { + "_id": "12:34:56:03:1b:e4", + "type": "NAModule2", + "module_name": "Garden", + "data_type": [ + "Wind" + ], + "last_setup": 1549193862, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413170, + "WindStrength": 4, + "WindAngle": 217, + "GustStrength": 9, + "GustAngle": 206, + "max_wind_str": 21, + "max_wind_angle": 217, + "date_max_wind_str": 1559386669 + }, + "firmware": 19, + "last_message": 1559413177, + "last_seen": 1559413177, + "rf_status": 59, + "battery_vp": 5689, + "battery_percent": 85 + }, + { + "_id": "12:34:56:05:51:20", + "type": "NAModule3", + "module_name": "Yard", + "data_type": [ + "Rain" + ], + "last_setup": 1549194580, + "reachable": true, + "dashboard_data": { + "time_utc": 1559413170, + "Rain": 0, + "sum_rain_24": 0, + "sum_rain_1": 0 + }, + "firmware": 8, + "last_message": 1559413177, + "last_seen": 1559413170, + "rf_status": 67, + "battery_vp": 5860, + "battery_percent": 93 + } + ] + }, + { + "_id": "12 :34: 56:36:fd:3c", + "station_name": "Valley Road", + "date_setup": 1545897146, + "last_setup": 1545897146, + "type": "NAMain", + "last_status_store": 1581835369, + "firmware": 137, + "last_upgrade": 1545897125, + "wifi_status": 53, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 69, + "city": "Valley", + "country": "AU", + "timezone": "Australia/Hobart", + "location": [ + 148.444226, + -41.721282 + ] + }, + "read_only": true, + "dashboard_data": { + "time_utc": 1581835330, + "Temperature": 22.4, + "CO2": 471, + "Humidity": 46, + "Noise": 47, + "Pressure": 1011.5, + "AbsolutePressure": 1002.8, + "min_temp": 18.1, + "max_temp": 22.5, + "date_max_temp": 1581829891, + "date_min_temp": 1581794878, + "temp_trend": "stable", + "pressure_trend": "stable" + }, + "modules": [ + { + "_id": "12 :34: 56:36:e6:c0", + "type": "NAModule1", + "module_name": "Module", + "data_type": [ + "Temperature", + "Humidity" + ], + "last_setup": 1545897146, + "battery_percent": 22, + "reachable": false, + "firmware": 46, + "last_message": 1572497781, + "last_seen": 1572497742, + "rf_status": 88, + "battery_vp": 4118 + }, + { + "_id": "12:34:56:05:25:6e", + "type": "NAModule3", + "module_name": "Rain Gauge", + "data_type": [ + "Rain" + ], + "last_setup": 1553997427, + "battery_percent": 82, + "reachable": true, + "firmware": 8, + "last_message": 1581835362, + "last_seen": 1581835354, + "rf_status": 78, + "battery_vp": 5594, + "dashboard_data": { + "time_utc": 1581835329, + "Rain": 0, + "sum_rain_1": 0, + "sum_rain_24": 0 + } + } + ] + }, + { + "_id": "12:34:56:32:a7:60", + "home_name": "Ateljen", + "date_setup": 1566714693, + "last_setup": 1566714693, + "type": "NAMain", + "last_status_store": 1588481079, + "module_name": "Indoor", + "firmware": 177, + "last_upgrade": 1566714694, + "wifi_status": 50, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 93, + "city": "Gothenburg", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 11.6136629, + 57.7006827 + ] + }, + "dashboard_data": { + "time_utc": 1588481073, + "Temperature": 18.2, + "CO2": 542, + "Humidity": 45, + "Noise": 45, + "Pressure": 1013, + "AbsolutePressure": 1001.9, + "min_temp": 18.2, + "max_temp": 19.5, + "date_max_temp": 1588456861, + "date_min_temp": 1588479561, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [ + { + "_id": "12:34:56:32:db:06", + "type": "NAModule1", + "last_setup": 1587635819, + "data_type": [ + "Temperature", + "Humidity" + ], + "battery_percent": 100, + "reachable": false, + "firmware": 255, + "last_message": 0, + "last_seen": 0, + "rf_status": 255, + "battery_vp": 65535 + } + ] + }, + { + "_id": "12:34:56:1c:68:2e", + "station_name": "Bol\u00e5s", + "date_setup": 1470935400, + "last_setup": 1470935400, + "type": "NAMain", + "last_status_store": 1588481399, + "module_name": "Inne - Nere", + "firmware": 177, + "last_upgrade": 1470935401, + "wifi_status": 13, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 93, + "city": "Gothenburg", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 11.6136629, + 57.7006827 + ] + }, + "dashboard_data": { + "time_utc": 1588481387, + "Temperature": 20.8, + "CO2": 674, + "Humidity": 41, + "Noise": 34, + "Pressure": 1012.1, + "AbsolutePressure": 1001, + "min_temp": 20.8, + "max_temp": 22.2, + "date_max_temp": 1588456859, + "date_min_temp": 1588480176, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [ + { + "_id": "12:34:56:02:b3:da", + "type": "NAModule3", + "module_name": "Regnm\u00e4tare", + "last_setup": 1470937706, + "data_type": [ + "Rain" + ], + "battery_percent": 81, + "reachable": true, + "firmware": 12, + "last_message": 1588481393, + "last_seen": 1588481386, + "rf_status": 67, + "battery_vp": 5582, + "dashboard_data": { + "time_utc": 1588481386, + "Rain": 0, + "sum_rain_1": 0, + "sum_rain_24": 0.1 + } + }, + { + "_id": "12:34:56:03:76:60", + "type": "NAModule4", + "module_name": "Inne - Uppe", + "last_setup": 1470938089, + "data_type": [ + "Temperature", + "CO2", + "Humidity" + ], + "battery_percent": 14, + "reachable": true, + "firmware": 50, + "last_message": 1588481393, + "last_seen": 1588481374, + "rf_status": 70, + "battery_vp": 4448, + "dashboard_data": { + "time_utc": 1588481374, + "Temperature": 19.6, + "CO2": 696, + "Humidity": 41, + "min_temp": 19.6, + "max_temp": 20.5, + "date_max_temp": 1588456817, + "date_min_temp": 1588481374, + "temp_trend": "stable" + } + }, + { + "_id": "12:34:56:32:db:06", + "type": "NAModule1", + "module_name": "Ute", + "last_setup": 1566326027, + "data_type": [ + "Temperature", + "Humidity" + ], + "battery_percent": 81, + "reachable": true, + "firmware": 50, + "last_message": 1588481393, + "last_seen": 1588481380, + "rf_status": 61, + "battery_vp": 5544, + "dashboard_data": { + "time_utc": 1588481380, + "Temperature": 6.4, + "Humidity": 91, + "min_temp": 3.6, + "max_temp": 6.4, + "date_max_temp": 1588481380, + "date_min_temp": 1588471383, + "temp_trend": "up" + } + } + ] + }, + { + "_id": "12:34:56:1d:68:2e", + "date_setup": 1470935500, + "last_setup": 1470935500, + "type": "NAMain", + "last_status_store": 1588481399, + "module_name": "Basisstation", + "firmware": 177, + "last_upgrade": 1470935401, + "wifi_status": 13, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 93, + "city": "Gothenburg", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 11.6136629, + 57.7006827 + ] + }, + "dashboard_data": { + "time_utc": 1588481387, + "Temperature": 20.8, + "CO2": 674, + "Humidity": 41, + "Noise": 34, + "Pressure": 1012.1, + "AbsolutePressure": 1001, + "min_temp": 20.8, + "max_temp": 22.2, + "date_max_temp": 1588456859, + "date_min_temp": 1588480176, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [] + }, + { + "_id": "12:34:56:58:c8:54", + "date_setup": 1605594014, + "last_setup": 1605594014, + "type": "NAMain", + "last_status_store": 1605878352, + "firmware": 178, + "wifi_status": 47, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure" + ], + "place": { + "altitude": 65, + "city": "Njurunda District", + "country": "SE", + "timezone": "Europe/Stockholm", + "location": [ + 17.123456, + 62.123456 + ] + }, + "station_name": "Njurunda (Indoor)", + "home_id": "5fb36b9ec68fd10c6467ca65", + "home_name": "Njurunda", + "dashboard_data": { + "time_utc": 1605878349, + "Temperature": 19.7, + "CO2": 993, + "Humidity": 40, + "Noise": 40, + "Pressure": 1015.6, + "AbsolutePressure": 1007.8, + "min_temp": 19.7, + "max_temp": 20.4, + "date_max_temp": 1605826917, + "date_min_temp": 1605873207, + "temp_trend": "stable", + "pressure_trend": "up" + }, + "modules": [ + { + "_id": "12:34:56:58:e6:38", + "type": "NAModule1", + "last_setup": 1605594034, + "data_type": [ + "Temperature", + "Humidity" + ], + "battery_percent": 100, + "reachable": true, + "firmware": 50, + "last_message": 1605878347, + "last_seen": 1605878328, + "rf_status": 62, + "battery_vp": 6198, + "dashboard_data": { + "time_utc": 1605878328, + "Temperature": 0.6, + "Humidity": 77, + "min_temp": -2.1, + "max_temp": 1.5, + "date_max_temp": 1605865920, + "date_min_temp": 1605826904, + "temp_trend": "down" + } + } + ] + } + ], + "user": { + "mail": "john@doe.com", + "administrative": { + "lang": "de-DE", + "reg_locale": "de-DE", + "country": "DE", + "unit": 0, + "windunit": 0, + "pressureunit": 0, + "feel_like_algo": 0 + } + } + }, + "status": "ok", + "time_exec": 0.91107702255249, + "time_server": 1559413602 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/homesdata.json b/tests/fixtures/netatmo/homesdata.json new file mode 100644 index 00000000000000..aecab91550cce6 --- /dev/null +++ b/tests/fixtures/netatmo/homesdata.json @@ -0,0 +1,595 @@ +{ + "body": { + "homes": [ + { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "altitude": 112, + "coordinates": [ + 52.516263, + 13.377726 + ], + "country": "DE", + "timezone": "Europe/Berlin", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "module_ids": [ + "12:34:56:00:01:ae" + ] + }, + { + "id": "3688132631", + "name": "Hall", + "type": "custom", + "module_ids": [ + "12:34:56:00:f1:62" + ] + }, + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "module_ids": [ + "12:34:56:03:a5:54" + ] + }, + { + "id": "2940411577", + "name": "Cocina", + "type": "kitchen", + "module_ids": [ + "12:34:56:03:a0:ac" + ] + } + ], + "modules": [ + { + "id": "12:34:56:00:fa:d0", + "type": "NAPlug", + "name": "Thermostat", + "setup_date": 1494963356, + "modules_bridged": [ + "12:34:56:00:01:ae", + "12:34:56:03:a0:ac", + "12:34:56:03:a5:54" + ] + }, + { + "id": "12:34:56:00:01:ae", + "type": "NATherm1", + "name": "Livingroom", + "setup_date": 1494963356, + "room_id": "2746182631", + "bridge": "12:34:56:00:fa:d0" + }, + { + "id": "12:34:56:03:a5:54", + "type": "NRV", + "name": "Valve1", + "setup_date": 1554549767, + "room_id": "2833524037", + "bridge": "12:34:56:00:fa:d0" + }, + { + "id": "12:34:56:03:a0:ac", + "type": "NRV", + "name": "Valve2", + "setup_date": 1554554444, + "room_id": "2940411577", + "bridge": "12:34:56:00:fa:d0" + }, + { + "id": "12:34:56:00:f1:62", + "type": "NACamera", + "name": "Hall", + "setup_date": 1544828430, + "room_id": "3688132631" + } + ], + "therm_schedules": [ + { + "zones": [ + { + "type": 0, + "name": "Comfort", + "rooms_temp": [ + { + "temp": 21, + "room_id": "2746182631" + } + ], + "id": 0 + }, + { + "type": 1, + "name": "Night", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 1 + }, + { + "type": 5, + "name": "Eco", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 4 + } + ], + "timetable": [ + { + "zone_id": 1, + "m_offset": 0 + }, + { + "zone_id": 0, + "m_offset": 360 + }, + { + "zone_id": 4, + "m_offset": 420 + }, + { + "zone_id": 0, + "m_offset": 960 + }, + { + "zone_id": 1, + "m_offset": 1410 + }, + { + "zone_id": 0, + "m_offset": 1800 + }, + { + "zone_id": 4, + "m_offset": 1860 + }, + { + "zone_id": 0, + "m_offset": 2400 + }, + { + "zone_id": 1, + "m_offset": 2850 + }, + { + "zone_id": 0, + "m_offset": 3240 + }, + { + "zone_id": 4, + "m_offset": 3300 + }, + { + "zone_id": 0, + "m_offset": 3840 + }, + { + "zone_id": 1, + "m_offset": 4290 + }, + { + "zone_id": 0, + "m_offset": 4680 + }, + { + "zone_id": 4, + "m_offset": 4740 + }, + { + "zone_id": 0, + "m_offset": 5280 + }, + { + "zone_id": 1, + "m_offset": 5730 + }, + { + "zone_id": 0, + "m_offset": 6120 + }, + { + "zone_id": 4, + "m_offset": 6180 + }, + { + "zone_id": 0, + "m_offset": 6720 + }, + { + "zone_id": 1, + "m_offset": 7170 + }, + { + "zone_id": 0, + "m_offset": 7620 + }, + { + "zone_id": 1, + "m_offset": 8610 + }, + { + "zone_id": 0, + "m_offset": 9060 + }, + { + "zone_id": 1, + "m_offset": 10050 + } + ], + "hg_temp": 7, + "away_temp": 14, + "name": "Default", + "selected": true, + "id": "591b54a2764ff4d50d8b5795", + "type": "therm" + }, + { + "zones": [ + { + "type": 0, + "name": "Comfort", + "rooms_temp": [ + { + "temp": 21, + "room_id": "2746182631" + } + ], + "id": 0 + }, + { + "type": 1, + "name": "Night", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 1 + }, + { + "type": 5, + "name": "Eco", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 4 + } + ], + "timetable": [ + { + "zone_id": 1, + "m_offset": 0 + }, + { + "zone_id": 0, + "m_offset": 360 + }, + { + "zone_id": 4, + "m_offset": 420 + }, + { + "zone_id": 0, + "m_offset": 960 + }, + { + "zone_id": 1, + "m_offset": 1410 + }, + { + "zone_id": 0, + "m_offset": 1800 + }, + { + "zone_id": 4, + "m_offset": 1860 + }, + { + "zone_id": 0, + "m_offset": 2400 + }, + { + "zone_id": 1, + "m_offset": 2850 + }, + { + "zone_id": 0, + "m_offset": 3240 + }, + { + "zone_id": 4, + "m_offset": 3300 + }, + { + "zone_id": 0, + "m_offset": 3840 + }, + { + "zone_id": 1, + "m_offset": 4290 + }, + { + "zone_id": 0, + "m_offset": 4680 + }, + { + "zone_id": 4, + "m_offset": 4740 + }, + { + "zone_id": 0, + "m_offset": 5280 + }, + { + "zone_id": 1, + "m_offset": 5730 + }, + { + "zone_id": 0, + "m_offset": 6120 + }, + { + "zone_id": 4, + "m_offset": 6180 + }, + { + "zone_id": 0, + "m_offset": 6720 + }, + { + "zone_id": 1, + "m_offset": 7170 + }, + { + "zone_id": 0, + "m_offset": 7620 + }, + { + "zone_id": 1, + "m_offset": 8610 + }, + { + "zone_id": 0, + "m_offset": 9060 + }, + { + "zone_id": 1, + "m_offset": 10050 + } + ], + "hg_temp": 7, + "away_temp": 14, + "name": "Winter", + "id": "b1b54a2f45795764f59d50d8", + "type": "therm" + } + ], + "therm_setpoint_default_duration": 120, + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "pseudo": "John Doe", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d7" + }, + { + "id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "pseudo": "Jane Doe", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c24b808a89f8d1730039d2c69928b029d67fc40cb2d7fb69ecdf2bb8b72" + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff3", + "pseudo": "Richard Doe", + "url": "https://netatmocameraimage.blob.core.windows.net/production/d74fad765b9100ef480720a9a4a95c2d1730fb69ecdf2bb8b72039d2c69928b029d67fc40cb2d74b808a89f8" + } + ], + "schedules": [ + { + "zones": [ + { + "type": 0, + "name": "Komfort", + "rooms_temp": [ + { + "temp": 21, + "room_id": "2746182631" + } + ], + "id": 0, + "rooms": [ + { + "id": "2746182631", + "therm_setpoint_temperature": 21 + } + ] + }, + { + "type": 1, + "name": "Nacht", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 1, + "rooms": [ + { + "id": "2746182631", + "therm_setpoint_temperature": 17 + } + ] + }, + { + "type": 5, + "name": "Eco", + "rooms_temp": [ + { + "temp": 17, + "room_id": "2746182631" + } + ], + "id": 4, + "rooms": [ + { + "id": "2746182631", + "therm_setpoint_temperature": 17 + } + ] + } + ], + "timetable": [ + { + "zone_id": 1, + "m_offset": 0 + }, + { + "zone_id": 0, + "m_offset": 360 + }, + { + "zone_id": 4, + "m_offset": 420 + }, + { + "zone_id": 0, + "m_offset": 960 + }, + { + "zone_id": 1, + "m_offset": 1410 + }, + { + "zone_id": 0, + "m_offset": 1800 + }, + { + "zone_id": 4, + "m_offset": 1860 + }, + { + "zone_id": 0, + "m_offset": 2400 + }, + { + "zone_id": 1, + "m_offset": 2850 + }, + { + "zone_id": 0, + "m_offset": 3240 + }, + { + "zone_id": 4, + "m_offset": 3300 + }, + { + "zone_id": 0, + "m_offset": 3840 + }, + { + "zone_id": 1, + "m_offset": 4290 + }, + { + "zone_id": 0, + "m_offset": 4680 + }, + { + "zone_id": 4, + "m_offset": 4740 + }, + { + "zone_id": 0, + "m_offset": 5280 + }, + { + "zone_id": 1, + "m_offset": 5730 + }, + { + "zone_id": 0, + "m_offset": 6120 + }, + { + "zone_id": 4, + "m_offset": 6180 + }, + { + "zone_id": 0, + "m_offset": 6720 + }, + { + "zone_id": 1, + "m_offset": 7170 + }, + { + "zone_id": 0, + "m_offset": 7620 + }, + { + "zone_id": 1, + "m_offset": 8610 + }, + { + "zone_id": 0, + "m_offset": 9060 + }, + { + "zone_id": 1, + "m_offset": 10050 + } + ], + "hg_temp": 7, + "away_temp": 14, + "name": "Default", + "id": "591b54a2764ff4d50d8b5795", + "selected": true, + "type": "therm" + } + ], + "therm_mode": "schedule" + }, + { + "id": "91763b24c43d3e344f424e8c", + "altitude": 112, + "coordinates": [ + 52.516263, + 13.377726 + ], + "country": "DE", + "timezone": "Europe/Berlin", + "therm_setpoint_default_duration": 180, + "therm_mode": "schedule" + } + ], + "user": { + "email": "john@doe.com", + "language": "de-DE", + "locale": "de-DE", + "feel_like_algorithm": 0, + "unit_pressure": 0, + "unit_system": 0, + "unit_wind": 0, + "id": "91763b24c43d3e344f424e8b" + } + }, + "status": "ok", + "time_exec": 0.056135892868042, + "time_server": 1559171003 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/homestatus.json b/tests/fixtures/netatmo/homestatus.json new file mode 100644 index 00000000000000..5d508ea03b0f64 --- /dev/null +++ b/tests/fixtures/netatmo/homestatus.json @@ -0,0 +1,113 @@ +{ + "status": "ok", + "time_server": 1559292039, + "body": { + "home": { + "modules": [ + { + "id": "12:34:56:00:f1:62", + "type": "NACamera", + "monitoring": "on", + "sd_status": 4, + "alim_status": 2, + "locked": false, + "vpn_url": "https://prodvpn-eu-2.netatmo.net/restricted/10.255.123.45/609e27de5699fb18147ab47d06846631/MTRPn_BeWCav5RBq4U1OMDruTW4dkQ0NuMwNDAw11g,,", + "is_local": true + }, + { + "id": "12:34:56:00:fa:d0", + "type": "NAPlug", + "firmware_revision": 174, + "rf_strength": 107, + "wifi_strength": 42 + }, + { + "id": "12:34:56:00:01:ae", + "reachable": true, + "type": "NATherm1", + "firmware_revision": 65, + "rf_strength": 58, + "battery_level": 3793, + "boiler_valve_comfort_boost": false, + "boiler_status": false, + "anticipating": false, + "bridge": "12:34:56:00:fa:d0", + "battery_state": "high" + }, + { + "id": "12:34:56:03:a5:54", + "reachable": true, + "type": "NRV", + "firmware_revision": 79, + "rf_strength": 51, + "battery_level": 3025, + "bridge": "12:34:56:00:fa:d0", + "battery_state": "full" + }, + { + "id": "12:34:56:03:a0:ac", + "reachable": true, + "type": "NRV", + "firmware_revision": 79, + "rf_strength": 59, + "battery_level": 2329, + "bridge": "12:34:56:00:fa:d0", + "battery_state": "full" + } + ], + "rooms": [ + { + "id": "2746182631", + "reachable": true, + "therm_measured_temperature": 19.8, + "therm_setpoint_temperature": 12, + "therm_setpoint_mode": "schedule", + "therm_setpoint_start_time": 1559229567, + "therm_setpoint_end_time": 0 + }, + { + "id": "2940411577", + "reachable": true, + "therm_measured_temperature": 5, + "heating_power_request": 1, + "therm_setpoint_temperature": 7, + "therm_setpoint_mode": "away", + "therm_setpoint_start_time": 0, + "therm_setpoint_end_time": 0, + "anticipating": false, + "open_window": false + }, + { + "id": "2833524037", + "reachable": true, + "therm_measured_temperature": 24.5, + "heating_power_request": 0, + "therm_setpoint_temperature": 7, + "therm_setpoint_mode": "hg", + "therm_setpoint_start_time": 0, + "therm_setpoint_end_time": 0, + "anticipating": false, + "open_window": false + } + ], + "id": "91763b24c43d3e344f424e8b", + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "last_seen": 1557071156, + "out_of_sight": true + }, + { + "id": "91827375-7e04-5298-83ae-a0cb8372dff2", + "last_seen": 1559282761, + "out_of_sight": false + }, + { + "id": "91827376-7e04-5298-83af-a0cb8372dff3", + "last_seen": 1559224132, + "out_of_sight": true + } + ] + } + } +} \ No newline at end of file From 87d62cbbb83bf177b78bb0233234c2ea3d5d49e7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 15 Mar 2021 12:50:19 +0100 Subject: [PATCH 1267/1818] Fix target of WLED services (#47938) --- homeassistant/components/wled/services.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/wled/services.yaml b/homeassistant/components/wled/services.yaml index d6927610a47889..3ade18cb70e242 100644 --- a/homeassistant/components/wled/services.yaml +++ b/homeassistant/components/wled/services.yaml @@ -2,6 +2,9 @@ effect: name: Set effect description: Control the effect settings of WLED. target: + entity: + integration: wled + domain: light fields: effect: name: Effect @@ -48,6 +51,9 @@ preset: name: Set preset description: Set a preset for the WLED device. target: + entity: + integration: wled + domain: light fields: preset: name: Preset ID From b645dabd664a28173d43ff66f915a9820fe5f237 Mon Sep 17 00:00:00 2001 From: Colin O'Dell Date: Mon, 15 Mar 2021 07:53:45 -0400 Subject: [PATCH 1268/1818] Upgrade qnapstats library to 0.3.1 (#47907) Fixes #47674 --- homeassistant/components/qnap/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/qnap/manifest.json b/homeassistant/components/qnap/manifest.json index b83d37f0d3c25b..29750683abf054 100644 --- a/homeassistant/components/qnap/manifest.json +++ b/homeassistant/components/qnap/manifest.json @@ -2,6 +2,6 @@ "domain": "qnap", "name": "QNAP", "documentation": "https://www.home-assistant.io/integrations/qnap", - "requirements": ["qnapstats==0.3.0"], + "requirements": ["qnapstats==0.3.1"], "codeowners": ["@colinodell"] } diff --git a/requirements_all.txt b/requirements_all.txt index a749acabf9907b..68ba25f4659683 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1922,7 +1922,7 @@ pyzbar==0.1.7 pyzerproc==0.4.8 # homeassistant.components.qnap -qnapstats==0.3.0 +qnapstats==0.3.1 # homeassistant.components.quantum_gateway quantum-gateway==0.0.5 From fb34d1a56e7f0329cb21ca97aa2e476b804b14b3 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 15 Mar 2021 12:06:57 +0000 Subject: [PATCH 1269/1818] Clean up Lyric (#47899) --- homeassistant/components/lyric/climate.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 6424083158a37a..4338a6e92cce86 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -68,20 +68,12 @@ LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, } - HVAC_ACTIONS = { LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF, LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT, LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL, } -HVAC_MODES = { - LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF, - LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT, - LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL, - LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL, -} - SERVICE_HOLD_TIME = "set_hold_time" ATTR_TIME_PERIOD = "time_period" From 50b7b1cc7a80690c191ca964410adad268014b21 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Mon, 15 Mar 2021 13:45:13 +0100 Subject: [PATCH 1270/1818] Migrate LCN configuration to ConfigEntry (Part 1) (#44090) --- .coveragerc | 13 +- homeassistant/components/lcn/__init__.py | 232 ++++++++++-------- homeassistant/components/lcn/binary_sensor.py | 109 +++++--- homeassistant/components/lcn/climate.py | 73 +++--- homeassistant/components/lcn/config_flow.py | 97 ++++++++ homeassistant/components/lcn/const.py | 7 + homeassistant/components/lcn/cover.py | 89 ++++--- homeassistant/components/lcn/helpers.py | 185 +++++++++++++- homeassistant/components/lcn/light.py | 79 +++--- homeassistant/components/lcn/manifest.json | 1 + homeassistant/components/lcn/scene.py | 61 +++-- homeassistant/components/lcn/sensor.py | 90 ++++--- homeassistant/components/lcn/services.py | 43 +++- homeassistant/components/lcn/switch.py | 70 +++--- requirements_test_all.txt | 3 + tests/components/lcn/__init__.py | 1 + tests/components/lcn/test_config_flow.py | 92 +++++++ 17 files changed, 890 insertions(+), 355 deletions(-) create mode 100644 homeassistant/components/lcn/config_flow.py create mode 100644 tests/components/lcn/__init__.py create mode 100644 tests/components/lcn/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index d406698cbb11a4..3c6bf16f6ba1b3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -505,7 +505,18 @@ omit = homeassistant/components/lastfm/sensor.py homeassistant/components/launch_library/const.py homeassistant/components/launch_library/sensor.py - homeassistant/components/lcn/* + homeassistant/components/lcn/__init__.py + homeassistant/components/lcn/binary_sensor.py + homeassistant/components/lcn/climate.py + homeassistant/components/lcn/const.py + homeassistant/components/lcn/cover.py + homeassistant/components/lcn/helpers.py + homeassistant/components/lcn/light.py + homeassistant/components/lcn/scene.py + homeassistant/components/lcn/schemas.py + homeassistant/components/lcn/sensor.py + homeassistant/components/lcn/services.py + homeassistant/components/lcn/switch.py homeassistant/components/lg_netcast/media_player.py homeassistant/components/lg_soundbar/media_player.py homeassistant/components/life360/* diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index fe9195584ea9b0..9384fbed29d3ee 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -1,137 +1,162 @@ """Support for LCN devices.""" +import asyncio import logging import pypck +from homeassistant import config_entries from homeassistant.const import ( - CONF_BINARY_SENSORS, - CONF_COVERS, - CONF_HOST, - CONF_LIGHTS, + CONF_IP_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_SENSORS, - CONF_SWITCHES, + CONF_RESOURCE, CONF_USERNAME, ) -from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import Entity -from .const import ( - CONF_CLIMATES, - CONF_CONNECTIONS, - CONF_DIM_MODE, - CONF_SCENES, - CONF_SK_NUM_TRIES, - DATA_LCN, - DOMAIN, -) +from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, CONNECTION, DOMAIN +from .helpers import generate_unique_id, import_lcn_config from .schemas import CONFIG_SCHEMA # noqa: F401 -from .services import ( - DynText, - Led, - LockKeys, - LockRegulator, - OutputAbs, - OutputRel, - OutputToggle, - Pck, - Relays, - SendKeys, - VarAbs, - VarRel, - VarReset, -) +from .services import SERVICES + +PLATFORMS = ["binary_sensor", "climate", "cover", "light", "scene", "sensor", "switch"] _LOGGER = logging.getLogger(__name__) async def async_setup(hass, config): """Set up the LCN component.""" - hass.data[DATA_LCN] = {} - - conf_connections = config[DOMAIN][CONF_CONNECTIONS] - connections = [] - for conf_connection in conf_connections: - connection_name = conf_connection.get(CONF_NAME) - - settings = { - "SK_NUM_TRIES": conf_connection[CONF_SK_NUM_TRIES], - "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[ - conf_connection[CONF_DIM_MODE] - ], - } - - connection = pypck.connection.PchkConnectionManager( - conf_connection[CONF_HOST], - conf_connection[CONF_PORT], - conf_connection[CONF_USERNAME], - conf_connection[CONF_PASSWORD], - settings=settings, - connection_id=connection_name, + if DOMAIN not in config: + return True + + # initialize a config_flow for all LCN configurations read from + # configuration.yaml + config_entries_data = import_lcn_config(config[DOMAIN]) + + for config_entry_data in config_entries_data: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config_entry_data, + ) ) + return True - try: - # establish connection to PCHK server - await hass.async_create_task(connection.async_connect(timeout=15)) - connections.append(connection) - _LOGGER.info('LCN connected to "%s"', connection_name) - except TimeoutError: - _LOGGER.error('Connection to PCHK server "%s" failed', connection_name) - return False - - hass.data[DATA_LCN][CONF_CONNECTIONS] = connections - - # load platforms - for component, conf_key in ( - ("binary_sensor", CONF_BINARY_SENSORS), - ("climate", CONF_CLIMATES), - ("cover", CONF_COVERS), - ("light", CONF_LIGHTS), - ("scene", CONF_SCENES), - ("sensor", CONF_SENSORS), - ("switch", CONF_SWITCHES), - ): - if conf_key in config[DOMAIN]: - hass.async_create_task( - async_load_platform( - hass, component, DOMAIN, config[DOMAIN][conf_key], config - ) - ) - # register service calls - for service_name, service in ( - ("output_abs", OutputAbs), - ("output_rel", OutputRel), - ("output_toggle", OutputToggle), - ("relays", Relays), - ("var_abs", VarAbs), - ("var_reset", VarReset), - ("var_rel", VarRel), - ("lock_regulator", LockRegulator), - ("led", Led), - ("send_keys", SendKeys), - ("lock_keys", LockKeys), - ("dyn_text", DynText), - ("pck", Pck), - ): - hass.services.async_register( - DOMAIN, service_name, service(hass).async_call_service, service.schema +async def async_setup_entry(hass, config_entry): + """Set up a connection to PCHK host from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + if config_entry.entry_id in hass.data[DOMAIN]: + return False + + settings = { + "SK_NUM_TRIES": config_entry.data[CONF_SK_NUM_TRIES], + "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[config_entry.data[CONF_DIM_MODE]], + } + + # connect to PCHK + lcn_connection = pypck.connection.PchkConnectionManager( + config_entry.data[CONF_IP_ADDRESS], + config_entry.data[CONF_PORT], + config_entry.data[CONF_USERNAME], + config_entry.data[CONF_PASSWORD], + settings=settings, + connection_id=config_entry.entry_id, + ) + try: + # establish connection to PCHK server + await lcn_connection.async_connect(timeout=15) + except pypck.connection.PchkAuthenticationError: + _LOGGER.warning('Authentication on PCHK "%s" failed', config_entry.title) + return False + except pypck.connection.PchkLicenseError: + _LOGGER.warning( + 'Maximum number of connections on PCHK "%s" was ' + "reached. An additional license key is required", + config_entry.title, ) + return False + except TimeoutError: + _LOGGER.warning('Connection to PCHK "%s" failed', config_entry.title) + return False + + _LOGGER.debug('LCN connected to "%s"', config_entry.title) + hass.data[DOMAIN][config_entry.entry_id] = { + CONNECTION: lcn_connection, + } + + # remove orphans from entity registry which are in ConfigEntry but were removed + # from configuration.yaml + if config_entry.source == config_entries.SOURCE_IMPORT: + entity_registry = await er.async_get_registry(hass) + entity_registry.async_clear_config_entry(config_entry.entry_id) + + # forward config_entry to components + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + # register service calls + for service_name, service in SERVICES: + if not hass.services.has_service(DOMAIN, service_name): + hass.services.async_register( + DOMAIN, service_name, service(hass).async_call_service, service.schema + ) return True +async def async_unload_entry(hass, config_entry): + """Close connection to PCHK host represented by config_entry.""" + # forward unloading to platforms + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) + + if unload_ok and config_entry.entry_id in hass.data[DOMAIN]: + host = hass.data[DOMAIN].pop(config_entry.entry_id) + await host[CONNECTION].async_close() + + # unregister service calls + if unload_ok and not hass.data[DOMAIN]: # check if this is the last entry to unload + for service_name, _ in SERVICES: + hass.services.async_remove(DOMAIN, service_name) + + return unload_ok + + class LcnEntity(Entity): - """Parent class for all devices associated with the LCN component.""" + """Parent class for all entities associated with the LCN component.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN device.""" self.config = config + self.entry_id = entry_id self.device_connection = device_connection + self._unregister_for_inputs = None self._name = config[CONF_NAME] + @property + def unique_id(self): + """Return a unique ID.""" + unique_device_id = generate_unique_id( + ( + self.device_connection.seg_id, + self.device_connection.addr_id, + self.device_connection.is_group, + ) + ) + return f"{self.entry_id}-{unique_device_id}-{self.config[CONF_RESOURCE]}" + @property def should_poll(self): """Lcn device entity pushes its state to HA.""" @@ -140,7 +165,14 @@ def should_poll(self): async def async_added_to_hass(self): """Run when entity about to be added to hass.""" if not self.device_connection.is_group: - self.device_connection.register_for_inputs(self.input_received) + self._unregister_for_inputs = self.device_connection.register_for_inputs( + self.input_received + ) + + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + if self._unregister_for_inputs is not None: + self._unregister_for_inputs() @property def name(self): diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py index 56a5ea6e6462c6..3bea502cc76063 100644 --- a/homeassistant/components/lcn/binary_sensor.py +++ b/homeassistant/components/lcn/binary_sensor.py @@ -1,49 +1,56 @@ """Support for LCN binary sensors.""" import pypck -from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_ADDRESS, CONF_SOURCE +from homeassistant.components.binary_sensor import ( + DOMAIN as DOMAIN_BINARY_SENSOR, + BinarySensorEntity, +) +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES, CONF_SOURCE from . import LcnEntity -from .const import BINSENSOR_PORTS, CONF_CONNECTIONS, DATA_LCN, SETPOINTS -from .helpers import get_connection - - -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN binary sensor platform.""" - if discovery_info is None: - return - - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) - - if config[CONF_SOURCE] in SETPOINTS: - device = LcnRegulatorLockSensor(config, address_connection) - elif config[CONF_SOURCE] in BINSENSOR_PORTS: - device = LcnBinarySensor(config, address_connection) - else: # in KEYS - device = LcnLockKeysSensor(config, address_connection) - - devices.append(device) +from .const import BINSENSOR_PORTS, CONF_DOMAIN_DATA, SETPOINTS +from .helpers import get_device_connection + + +def create_lcn_binary_sensor_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) + + if entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] in SETPOINTS: + return LcnRegulatorLockSensor( + entity_config, config_entry.entry_id, device_connection + ) + if entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] in BINSENSOR_PORTS: + return LcnBinarySensor(entity_config, config_entry.entry_id, device_connection) + # in KEY + return LcnLockKeysSensor(entity_config, config_entry.entry_id, device_connection) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] + + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_BINARY_SENSOR: + entities.append( + create_lcn_binary_sensor_entity(hass, entity_config, config_entry) + ) - async_add_entities(devices) + async_add_entities(entities) class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity): """Representation of a LCN binary sensor for regulator locks.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN binary sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.setpoint_variable = pypck.lcn_defs.Var[config[CONF_SOURCE]] + self.setpoint_variable = pypck.lcn_defs.Var[ + config[CONF_DOMAIN_DATA][CONF_SOURCE] + ] self._value = None @@ -55,6 +62,14 @@ async def async_added_to_hass(self): self.setpoint_variable ) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler( + self.setpoint_variable + ) + @property def is_on(self): """Return true if the binary sensor is on.""" @@ -75,11 +90,13 @@ def input_received(self, input_obj): class LcnBinarySensor(LcnEntity, BinarySensorEntity): """Representation of a LCN binary sensor for binary sensor ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN binary sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.bin_sensor_port = pypck.lcn_defs.BinSensorPort[config[CONF_SOURCE]] + self.bin_sensor_port = pypck.lcn_defs.BinSensorPort[ + config[CONF_DOMAIN_DATA][CONF_SOURCE] + ] self._value = None @@ -91,6 +108,14 @@ async def async_added_to_hass(self): self.bin_sensor_port ) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler( + self.bin_sensor_port + ) + @property def is_on(self): """Return true if the binary sensor is on.""" @@ -108,11 +133,11 @@ def input_received(self, input_obj): class LcnLockKeysSensor(LcnEntity, BinarySensorEntity): """Representation of a LCN sensor for key locks.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.source = pypck.lcn_defs.Key[config[CONF_SOURCE]] + self.source = pypck.lcn_defs.Key[config[CONF_DOMAIN_DATA][CONF_SOURCE]] self._value = None async def async_added_to_hass(self): @@ -121,6 +146,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.source) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.source) + @property def is_on(self): """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index e3269a51cd62f1..056abcda2b0f3a 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -1,68 +1,76 @@ """Support for LCN climate control.""" - import pypck -from homeassistant.components.climate import ClimateEntity, const +from homeassistant.components.climate import ( + DOMAIN as DOMAIN_CLIMATE, + ClimateEntity, + const, +) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_ADDRESS, + CONF_DOMAIN, + CONF_ENTITIES, CONF_SOURCE, CONF_UNIT_OF_MEASUREMENT, ) from . import LcnEntity from .const import ( - CONF_CONNECTIONS, + CONF_DOMAIN_DATA, CONF_LOCKABLE, CONF_MAX_TEMP, CONF_MIN_TEMP, CONF_SETPOINT, - DATA_LCN, ) -from .helpers import get_connection +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN climate platform.""" - if discovery_info is None: - return +def create_lcn_climate_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) + + return LcnClimate(entity_config, config_entry.entry_id, device_connection) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) - devices.append(LcnClimate(config, address_connection)) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_CLIMATE: + entities.append( + create_lcn_climate_entity(hass, entity_config, config_entry) + ) + + async_add_entities(entities) class LcnClimate(LcnEntity, ClimateEntity): """Representation of a LCN climate device.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize of a LCN climate device.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.variable = pypck.lcn_defs.Var[config[CONF_SOURCE]] - self.setpoint = pypck.lcn_defs.Var[config[CONF_SETPOINT]] - self.unit = pypck.lcn_defs.VarUnit.parse(config[CONF_UNIT_OF_MEASUREMENT]) + self.variable = pypck.lcn_defs.Var[config[CONF_DOMAIN_DATA][CONF_SOURCE]] + self.setpoint = pypck.lcn_defs.Var[config[CONF_DOMAIN_DATA][CONF_SETPOINT]] + self.unit = pypck.lcn_defs.VarUnit.parse( + config[CONF_DOMAIN_DATA][CONF_UNIT_OF_MEASUREMENT] + ) self.regulator_id = pypck.lcn_defs.Var.to_set_point_id(self.setpoint) - self.is_lockable = config[CONF_LOCKABLE] - self._max_temp = config[CONF_MAX_TEMP] - self._min_temp = config[CONF_MIN_TEMP] + self.is_lockable = config[CONF_DOMAIN_DATA][CONF_LOCKABLE] + self._max_temp = config[CONF_DOMAIN_DATA][CONF_MAX_TEMP] + self._min_temp = config[CONF_DOMAIN_DATA][CONF_MIN_TEMP] self._current_temperature = None self._target_temperature = None - self._is_on = None + self._is_on = True async def async_added_to_hass(self): """Run when entity about to be added to hass.""" @@ -71,6 +79,13 @@ async def async_added_to_hass(self): await self.device_connection.activate_status_request_handler(self.variable) await self.device_connection.activate_status_request_handler(self.setpoint) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.variable) + await self.device_connection.cancel_status_request_handler(self.setpoint) + @property def supported_features(self): """Return the list of supported features.""" diff --git a/homeassistant/components/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py new file mode 100644 index 00000000000000..fe353cdb4c54ff --- /dev/null +++ b/homeassistant/components/lcn/config_flow.py @@ -0,0 +1,97 @@ +"""Config flow to configure the LCN integration.""" +import logging + +import pypck + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_HOST, + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) + +from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def get_config_entry(hass, data): + """Check config entries for already configured entries based on the ip address/port.""" + return next( + ( + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.data[CONF_IP_ADDRESS] == data[CONF_IP_ADDRESS] + and entry.data[CONF_PORT] == data[CONF_PORT] + ), + None, + ) + + +async def validate_connection(host_name, data): + """Validate if a connection to LCN can be established.""" + host = data[CONF_IP_ADDRESS] + port = data[CONF_PORT] + username = data[CONF_USERNAME] + password = data[CONF_PASSWORD] + sk_num_tries = data[CONF_SK_NUM_TRIES] + dim_mode = data[CONF_DIM_MODE] + + settings = { + "SK_NUM_TRIES": sk_num_tries, + "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[dim_mode], + } + + _LOGGER.debug("Validating connection parameters to PCHK host '%s'", host_name) + + connection = pypck.connection.PchkConnectionManager( + host, port, username, password, settings=settings + ) + + await connection.async_connect(timeout=5) + + _LOGGER.debug("LCN connection validated") + await connection.async_close() + return data + + +@config_entries.HANDLERS.register(DOMAIN) +class LcnFlowHandler(config_entries.ConfigFlow): + """Handle a LCN config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_import(self, data): + """Import existing configuration from LCN.""" + host_name = data[CONF_HOST] + # validate the imported connection parameters + try: + await validate_connection(host_name, data) + except pypck.connection.PchkAuthenticationError: + _LOGGER.warning('Authentication on PCHK "%s" failed', host_name) + return self.async_abort(reason="authentication_error") + except pypck.connection.PchkLicenseError: + _LOGGER.warning( + 'Maximum number of connections on PCHK "%s" was ' + "reached. An additional license key is required", + host_name, + ) + return self.async_abort(reason="license_error") + except TimeoutError: + _LOGGER.warning('Connection to PCHK "%s" failed', host_name) + return self.async_abort(reason="connection_timeout") + + # check if we already have a host with the same address configured + entry = get_config_entry(self.hass, data) + if entry: + entry.source = config_entries.SOURCE_IMPORT + self.hass.config_entries.async_update_entry(entry, data=data) + return self.async_abort(reason="existing_configuration_updated") + + return self.async_create_entry( + title=f"{host_name}", + data=data, + ) diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index 3dcac6fb55ff1b..4e3e765ace0516 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -14,6 +14,13 @@ DATA_LCN = "lcn" DEFAULT_NAME = "pchk" +CONNECTION = "connection" +CONF_HARDWARE_SERIAL = "hardware_serial" +CONF_SOFTWARE_SERIAL = "software_serial" +CONF_HARDWARE_TYPE = "hardware_type" +CONF_RESOURCE = "resource" +CONF_DOMAIN_DATA = "domain_data" + CONF_CONNECTIONS = "connections" CONF_SK_NUM_TRIES = "sk_num_tries" CONF_OUTPUT = "output" diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 3d7c2a06a3b1ee..bf777ad93f2b0a 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -1,53 +1,54 @@ """Support for LCN covers.""" + import pypck -from homeassistant.components.cover import CoverEntity -from homeassistant.const import CONF_ADDRESS +from homeassistant.components.cover import DOMAIN as DOMAIN_COVER, CoverEntity +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES from . import LcnEntity -from .const import CONF_CONNECTIONS, CONF_MOTOR, CONF_REVERSE_TIME, DATA_LCN -from .helpers import get_connection +from .const import CONF_DOMAIN_DATA, CONF_MOTOR, CONF_REVERSE_TIME +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Setups the LCN cover platform.""" - if discovery_info is None: - return +def create_lcn_cover_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) + + if entity_config[CONF_DOMAIN_DATA][CONF_MOTOR] in "OUTPUTS": + return LcnOutputsCover(entity_config, config_entry.entry_id, device_connection) + # in RELAYS + return LcnRelayCover(entity_config, config_entry.entry_id, device_connection) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) - if config[CONF_MOTOR] == "OUTPUTS": - devices.append(LcnOutputsCover(config, address_connection)) - else: # RELAYS - devices.append(LcnRelayCover(config, address_connection)) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN cover entities from a config entry.""" + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_COVER: + entities.append(create_lcn_cover_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnOutputsCover(LcnEntity, CoverEntity): """Representation of a LCN cover connected to output ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN cover.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) self.output_ids = [ pypck.lcn_defs.OutputPort["OUTPUTUP"].value, pypck.lcn_defs.OutputPort["OUTPUTDOWN"].value, ] - if CONF_REVERSE_TIME in config: + if CONF_REVERSE_TIME in config[CONF_DOMAIN_DATA]: self.reverse_time = pypck.lcn_defs.MotorReverseTime[ - config[CONF_REVERSE_TIME] + config[CONF_DOMAIN_DATA][CONF_REVERSE_TIME] ] else: self.reverse_time = None @@ -59,12 +60,24 @@ def __init__(self, config, device_connection): async def async_added_to_hass(self): """Run when entity about to be added to hass.""" await super().async_added_to_hass() - await self.device_connection.activate_status_request_handler( - pypck.lcn_defs.OutputPort["OUTPUTUP"] - ) - await self.device_connection.activate_status_request_handler( - pypck.lcn_defs.OutputPort["OUTPUTDOWN"] - ) + if not self.device_connection.is_group: + await self.device_connection.activate_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTUP"] + ) + await self.device_connection.activate_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTDOWN"] + ) + + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTUP"] + ) + await self.device_connection.cancel_status_request_handler( + pypck.lcn_defs.OutputPort["OUTPUTDOWN"] + ) @property def is_closed(self): @@ -146,11 +159,11 @@ def input_received(self, input_obj): class LcnRelayCover(LcnEntity, CoverEntity): """Representation of a LCN cover connected to relays.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN cover.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.motor = pypck.lcn_defs.MotorPort[config[CONF_MOTOR]] + self.motor = pypck.lcn_defs.MotorPort[config[CONF_DOMAIN_DATA][CONF_MOTOR]] self.motor_port_onoff = self.motor.value * 2 self.motor_port_updown = self.motor_port_onoff + 1 @@ -164,6 +177,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.motor) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.motor) + @property def is_closed(self): """Return if the cover is closed.""" diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 18342aa1d988d3..3f93ec95a69c3f 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -1,11 +1,42 @@ """Helpers for LCN component.""" import re +import pypck import voluptuous as vol -from homeassistant.const import CONF_NAME +from homeassistant.const import ( + CONF_ADDRESS, + CONF_BINARY_SENSORS, + CONF_COVERS, + CONF_DEVICES, + CONF_DOMAIN, + CONF_ENTITIES, + CONF_HOST, + CONF_IP_ADDRESS, + CONF_LIGHTS, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SENSORS, + CONF_SWITCHES, + CONF_USERNAME, +) -from .const import DEFAULT_NAME +from .const import ( + CONF_CLIMATES, + CONF_CONNECTIONS, + CONF_DIM_MODE, + CONF_DOMAIN_DATA, + CONF_HARDWARE_SERIAL, + CONF_HARDWARE_TYPE, + CONF_RESOURCE, + CONF_SCENES, + CONF_SK_NUM_TRIES, + CONF_SOFTWARE_SERIAL, + CONNECTION, + DEFAULT_NAME, + DOMAIN, +) # Regex for address validation PATTERN_ADDRESS = re.compile( @@ -13,17 +44,145 @@ ) -def get_connection(connections, connection_id=None): - """Return the connection object from list.""" - if connection_id is None: - connection = connections[0] - else: - for connection in connections: - if connection.connection_id == connection_id: - break - else: - raise ValueError("Unknown connection_id.") - return connection +DOMAIN_LOOKUP = { + CONF_BINARY_SENSORS: "binary_sensor", + CONF_CLIMATES: "climate", + CONF_COVERS: "cover", + CONF_LIGHTS: "light", + CONF_SCENES: "scene", + CONF_SENSORS: "sensor", + CONF_SWITCHES: "switch", +} + + +def get_device_connection(hass, address, config_entry): + """Return a lcn device_connection.""" + host_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION] + addr = pypck.lcn_addr.LcnAddr(*address) + return host_connection.get_address_conn(addr) + + +def get_resource(domain_name, domain_data): + """Return the resource for the specified domain_data.""" + if domain_name in ["switch", "light"]: + return domain_data["output"] + if domain_name in ["binary_sensor", "sensor"]: + return domain_data["source"] + if domain_name == "cover": + return domain_data["motor"] + if domain_name == "climate": + return f'{domain_data["source"]}.{domain_data["setpoint"]}' + if domain_name == "scene": + return f'{domain_data["register"]}.{domain_data["scene"]}' + raise ValueError("Unknown domain") + + +def generate_unique_id(address): + """Generate a unique_id from the given parameters.""" + is_group = "g" if address[2] else "m" + return f"{is_group}{address[0]:03d}{address[1]:03d}" + + +def import_lcn_config(lcn_config): + """Convert lcn settings from configuration.yaml to config_entries data. + + Create a list of config_entry data structures like: + + "data": { + "host": "pchk", + "ip_address": "192.168.2.41", + "port": 4114, + "username": "lcn", + "password": "lcn, + "sk_num_tries: 0, + "dim_mode: "STEPS200", + "devices": [ + { + "address": (0, 7, False) + "name": "", + "hardware_serial": -1, + "software_serial": -1, + "hardware_type": -1 + }, ... + ], + "entities": [ + { + "address": (0, 7, False) + "name": "Light_Output1", + "resource": "output1", + "domain": "light", + "domain_data": { + "output": "OUTPUT1", + "dimmable": True, + "transition": 5000.0 + } + }, ... + ] + } + """ + data = {} + for connection in lcn_config[CONF_CONNECTIONS]: + host = { + CONF_HOST: connection[CONF_NAME], + CONF_IP_ADDRESS: connection[CONF_HOST], + CONF_PORT: connection[CONF_PORT], + CONF_USERNAME: connection[CONF_USERNAME], + CONF_PASSWORD: connection[CONF_PASSWORD], + CONF_SK_NUM_TRIES: connection[CONF_SK_NUM_TRIES], + CONF_DIM_MODE: connection[CONF_DIM_MODE], + CONF_DEVICES: [], + CONF_ENTITIES: [], + } + data[connection[CONF_NAME]] = host + + for confkey, domain_config in lcn_config.items(): + if confkey == CONF_CONNECTIONS: + continue + domain = DOMAIN_LOOKUP[confkey] + # loop over entities in configuration.yaml + for domain_data in domain_config: + # remove name and address from domain_data + entity_name = domain_data.pop(CONF_NAME) + address, host_name = domain_data.pop(CONF_ADDRESS) + + if host_name is None: + host_name = DEFAULT_NAME + + # check if we have a new device config + for device_config in data[host_name][CONF_DEVICES]: + if address == device_config[CONF_ADDRESS]: + break + else: # create new device_config + device_config = { + CONF_ADDRESS: address, + CONF_NAME: "", + CONF_HARDWARE_SERIAL: -1, + CONF_SOFTWARE_SERIAL: -1, + CONF_HARDWARE_TYPE: -1, + } + + data[host_name][CONF_DEVICES].append(device_config) + + # insert entity config + resource = get_resource(domain, domain_data).lower() + for entity_config in data[host_name][CONF_ENTITIES]: + if ( + address == entity_config[CONF_ADDRESS] + and resource == entity_config[CONF_RESOURCE] + and domain == entity_config[CONF_DOMAIN] + ): + break + else: # create new entity_config + entity_config = { + CONF_ADDRESS: address, + CONF_NAME: entity_name, + CONF_RESOURCE: resource, + CONF_DOMAIN: domain, + CONF_DOMAIN_DATA: domain_data.copy(), + } + data[host_name][CONF_ENTITIES].append(entity_config) + + return list(data.values()) def has_unique_host_names(hosts): diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 8a76056ff46cc5..8697d8e0319ce7 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -1,68 +1,69 @@ """Support for LCN lights.""" + import pypck from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_TRANSITION, + DOMAIN as DOMAIN_LIGHT, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, LightEntity, ) -from homeassistant.const import CONF_ADDRESS +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES from . import LcnEntity from .const import ( - CONF_CONNECTIONS, CONF_DIMMABLE, + CONF_DOMAIN_DATA, CONF_OUTPUT, CONF_TRANSITION, - DATA_LCN, OUTPUT_PORTS, ) -from .helpers import get_connection +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN light platform.""" - if discovery_info is None: - return +def create_lcn_light_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) + + if entity_config[CONF_DOMAIN_DATA][CONF_OUTPUT] in OUTPUT_PORTS: + return LcnOutputLight(entity_config, config_entry.entry_id, device_connection) + # in RELAY_PORTS + return LcnRelayLight(entity_config, config_entry.entry_id, device_connection) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) - if config[CONF_OUTPUT] in OUTPUT_PORTS: - device = LcnOutputLight(config, address_connection) - else: # in RELAY_PORTS - device = LcnRelayLight(config, address_connection) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN light entities from a config entry.""" + entities = [] - devices.append(device) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_LIGHT: + entities.append(create_lcn_light_entity(hass, entity_config, config_entry)) - async_add_entities(devices) + async_add_entities(entities) class LcnOutputLight(LcnEntity, LightEntity): """Representation of a LCN light for output ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN light.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.OutputPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] - self._transition = pypck.lcn_defs.time_to_ramp_value(config[CONF_TRANSITION]) - self.dimmable = config[CONF_DIMMABLE] + self._transition = pypck.lcn_defs.time_to_ramp_value( + config[CONF_DOMAIN_DATA][CONF_TRANSITION] + ) + self.dimmable = config[CONF_DOMAIN_DATA][CONF_DIMMABLE] self._brightness = 255 - self._is_on = None + self._is_on = False self._is_dimming_to_zero = False async def async_added_to_hass(self): @@ -71,6 +72,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def supported_features(self): """Flag supported features.""" @@ -145,13 +152,13 @@ def input_received(self, input_obj): class LcnRelayLight(LcnEntity, LightEntity): """Representation of a LCN light for relay ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN light.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.RelayPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.RelayPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] - self._is_on = None + self._is_on = False async def async_added_to_hass(self): """Run when entity about to be added to hass.""" @@ -159,6 +166,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def is_on(self): """Return True if entity is on.""" diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index c5077bdf4092ab..5c8be5829e01b2 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -1,6 +1,7 @@ { "domain": "lcn", "name": "LCN", + "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/lcn", "requirements": [ "pypck==0.7.9" diff --git a/homeassistant/components/lcn/scene.py b/homeassistant/components/lcn/scene.py index 1c359607fb2eed..8f770df7668b19 100644 --- a/homeassistant/components/lcn/scene.py +++ b/homeassistant/components/lcn/scene.py @@ -1,72 +1,69 @@ """Support for LCN scenes.""" -from typing import Any import pypck -from homeassistant.components.scene import Scene -from homeassistant.const import CONF_ADDRESS, CONF_SCENE +from homeassistant.components.scene import DOMAIN as DOMAIN_SCENE, Scene +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES, CONF_SCENE from . import LcnEntity from .const import ( - CONF_CONNECTIONS, + CONF_DOMAIN_DATA, CONF_OUTPUTS, CONF_REGISTER, CONF_TRANSITION, - DATA_LCN, OUTPUT_PORTS, ) -from .helpers import get_connection +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN scene platform.""" - if discovery_info is None: - return +def create_lcn_scene_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) + return LcnScene(entity_config, config_entry.entry_id, device_connection) - devices.append(LcnScene(config, address_connection)) - async_add_entities(devices) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] + + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_SCENE: + entities.append(create_lcn_scene_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnScene(LcnEntity, Scene): """Representation of a LCN scene.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN scene.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.register_id = config[CONF_REGISTER] - self.scene_id = config[CONF_SCENE] + self.register_id = config[CONF_DOMAIN_DATA][CONF_REGISTER] + self.scene_id = config[CONF_DOMAIN_DATA][CONF_SCENE] self.output_ports = [] self.relay_ports = [] - for port in config[CONF_OUTPUTS]: + for port in config[CONF_DOMAIN_DATA][CONF_OUTPUTS]: if port in OUTPUT_PORTS: self.output_ports.append(pypck.lcn_defs.OutputPort[port]) else: # in RELEAY_PORTS self.relay_ports.append(pypck.lcn_defs.RelayPort[port]) - if config[CONF_TRANSITION] is None: + if config[CONF_DOMAIN_DATA][CONF_TRANSITION] is None: self.transition = None else: - self.transition = pypck.lcn_defs.time_to_ramp_value(config[CONF_TRANSITION]) - - async def async_added_to_hass(self): - """Run when entity about to be added to hass.""" + self.transition = pypck.lcn_defs.time_to_ramp_value( + config[CONF_DOMAIN_DATA][CONF_TRANSITION] + ) - async def async_activate(self, **kwargs: Any) -> None: + async def async_activate(self, **kwargs): """Activate scene.""" await self.device_connection.activate_scene( self.register_id, diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 11932dccea8f9f..510df46cd1eb91 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -1,55 +1,67 @@ """Support for LCN sensors.""" + import pypck -from homeassistant.const import CONF_ADDRESS, CONF_SOURCE, CONF_UNIT_OF_MEASUREMENT +from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR +from homeassistant.const import ( + CONF_ADDRESS, + CONF_DOMAIN, + CONF_ENTITIES, + CONF_SOURCE, + CONF_UNIT_OF_MEASUREMENT, +) from . import LcnEntity from .const import ( - CONF_CONNECTIONS, - DATA_LCN, + CONF_DOMAIN_DATA, LED_PORTS, S0_INPUTS, SETPOINTS, THRESHOLDS, VARIABLES, ) -from .helpers import get_connection +from .helpers import get_device_connection -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN sensor platform.""" - if discovery_info is None: - return +def create_lcn_sensor_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - device_connection = connection.get_address_conn(addr) + if ( + entity_config[CONF_DOMAIN_DATA][CONF_SOURCE] + in VARIABLES + SETPOINTS + THRESHOLDS + S0_INPUTS + ): + return LcnVariableSensor( + entity_config, config_entry.entry_id, device_connection + ) + # in LED_PORTS + LOGICOP_PORTS + return LcnLedLogicSensor(entity_config, config_entry.entry_id, device_connection) - if config[CONF_SOURCE] in VARIABLES + SETPOINTS + THRESHOLDS + S0_INPUTS: - device = LcnVariableSensor(config, device_connection) - else: # in LED_PORTS + LOGICOP_PORTS - device = LcnLedLogicSensor(config, device_connection) - devices.append(device) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_SENSOR: + entities.append(create_lcn_sensor_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnVariableSensor(LcnEntity): """Representation of a LCN sensor for variables.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.variable = pypck.lcn_defs.Var[config[CONF_SOURCE]] - self.unit = pypck.lcn_defs.VarUnit.parse(config[CONF_UNIT_OF_MEASUREMENT]) + self.variable = pypck.lcn_defs.Var[config[CONF_DOMAIN_DATA][CONF_SOURCE]] + self.unit = pypck.lcn_defs.VarUnit.parse( + config[CONF_DOMAIN_DATA][CONF_UNIT_OF_MEASUREMENT] + ) self._value = None @@ -59,6 +71,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.variable) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.variable) + @property def state(self): """Return the state of the entity.""" @@ -84,14 +102,16 @@ def input_received(self, input_obj): class LcnLedLogicSensor(LcnEntity): """Representation of a LCN sensor for leds and logicops.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN sensor.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - if config[CONF_SOURCE] in LED_PORTS: - self.source = pypck.lcn_defs.LedPort[config[CONF_SOURCE]] + if config[CONF_DOMAIN_DATA][CONF_SOURCE] in LED_PORTS: + self.source = pypck.lcn_defs.LedPort[config[CONF_DOMAIN_DATA][CONF_SOURCE]] else: - self.source = pypck.lcn_defs.LogicOpPort[config[CONF_SOURCE]] + self.source = pypck.lcn_defs.LogicOpPort[ + config[CONF_DOMAIN_DATA][CONF_SOURCE] + ] self._value = None @@ -101,6 +121,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.source) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.source) + @property def state(self): """Return the state of the entity.""" diff --git a/homeassistant/components/lcn/services.py b/homeassistant/components/lcn/services.py index d7d8acf4f298c2..c6c33270264d66 100644 --- a/homeassistant/components/lcn/services.py +++ b/homeassistant/components/lcn/services.py @@ -6,6 +6,7 @@ from homeassistant.const import ( CONF_ADDRESS, CONF_BRIGHTNESS, + CONF_HOST, CONF_STATE, CONF_UNIT_OF_MEASUREMENT, TIME_SECONDS, @@ -13,7 +14,6 @@ import homeassistant.helpers.config_validation as cv from .const import ( - CONF_CONNECTIONS, CONF_KEYS, CONF_LED, CONF_OUTPUT, @@ -28,7 +28,7 @@ CONF_TRANSITION, CONF_VALUE, CONF_VARIABLE, - DATA_LCN, + DOMAIN, LED_PORTS, LED_STATUS, OUTPUT_PORTS, @@ -41,7 +41,7 @@ VARIABLES, ) from .helpers import ( - get_connection, + get_device_connection, is_address, is_key_lock_states_string, is_relays_states_string, @@ -56,18 +56,20 @@ class LcnServiceCall: def __init__(self, hass): """Initialize service call.""" self.hass = hass - self.connections = hass.data[DATA_LCN][CONF_CONNECTIONS] def get_device_connection(self, service): - """Get device connection object.""" - addr, connection_id = service.data[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*addr) - if connection_id is None: - connection = self.connections[0] - else: - connection = get_connection(self.connections, connection_id) + """Get address connection object.""" + address, host_name = service.data[CONF_ADDRESS] - return connection.get_address_conn(addr) + for config_entry in self.hass.config_entries.async_entries(DOMAIN): + if config_entry.data[CONF_HOST] == host_name: + device_connection = get_device_connection( + self.hass, address, config_entry + ) + if device_connection is None: + raise ValueError("Wrong address.") + return device_connection + raise ValueError("Invalid host name.") async def async_call_service(self, service): """Execute service call.""" @@ -392,3 +394,20 @@ async def async_call_service(self, service): pck = service.data[CONF_PCK] device_connection = self.get_device_connection(service) await device_connection.pck(pck) + + +SERVICES = ( + ("output_abs", OutputAbs), + ("output_rel", OutputRel), + ("output_toggle", OutputToggle), + ("relays", Relays), + ("var_abs", VarAbs), + ("var_reset", VarReset), + ("var_rel", VarRel), + ("lock_regulator", LockRegulator), + ("led", Led), + ("send_keys", SendKeys), + ("lock_keys", LockKeys), + ("dyn_text", DynText), + ("pck", Pck), +) diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 5fe624b04bfc0c..1429bf67f7e2d1 100644 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -1,49 +1,49 @@ """Support for LCN switches.""" + import pypck -from homeassistant.components.switch import SwitchEntity -from homeassistant.const import CONF_ADDRESS +from homeassistant.components.switch import DOMAIN as DOMAIN_SWITCH, SwitchEntity +from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES from . import LcnEntity -from .const import CONF_CONNECTIONS, CONF_OUTPUT, DATA_LCN, OUTPUT_PORTS -from .helpers import get_connection +from .const import CONF_DOMAIN_DATA, CONF_OUTPUT, OUTPUT_PORTS +from .helpers import get_device_connection PARALLEL_UPDATES = 0 -async def async_setup_platform( - hass, hass_config, async_add_entities, discovery_info=None -): - """Set up the LCN switch platform.""" - if discovery_info is None: - return +def create_lcn_switch_entity(hass, entity_config, config_entry): + """Set up an entity for this domain.""" + device_connection = get_device_connection( + hass, tuple(entity_config[CONF_ADDRESS]), config_entry + ) + + if entity_config[CONF_DOMAIN_DATA][CONF_OUTPUT] in OUTPUT_PORTS: + return LcnOutputSwitch(entity_config, config_entry.entry_id, device_connection) + # in RELAY_PORTS + return LcnRelaySwitch(entity_config, config_entry.entry_id, device_connection) - devices = [] - for config in discovery_info: - address, connection_id = config[CONF_ADDRESS] - addr = pypck.lcn_addr.LcnAddr(*address) - connections = hass.data[DATA_LCN][CONF_CONNECTIONS] - connection = get_connection(connections, connection_id) - address_connection = connection.get_address_conn(addr) - if config[CONF_OUTPUT] in OUTPUT_PORTS: - device = LcnOutputSwitch(config, address_connection) - else: # in RELAY_PORTS - device = LcnRelaySwitch(config, address_connection) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LCN switch entities from a config entry.""" - devices.append(device) + entities = [] - async_add_entities(devices) + for entity_config in config_entry.data[CONF_ENTITIES]: + if entity_config[CONF_DOMAIN] == DOMAIN_SWITCH: + entities.append(create_lcn_switch_entity(hass, entity_config, config_entry)) + + async_add_entities(entities) class LcnOutputSwitch(LcnEntity, SwitchEntity): """Representation of a LCN switch for output ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN switch.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.OutputPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] self._is_on = None @@ -53,6 +53,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def is_on(self): """Return True if entity is on.""" @@ -87,11 +93,11 @@ def input_received(self, input_obj): class LcnRelaySwitch(LcnEntity, SwitchEntity): """Representation of a LCN switch for relay ports.""" - def __init__(self, config, device_connection): + def __init__(self, config, entry_id, device_connection): """Initialize the LCN switch.""" - super().__init__(config, device_connection) + super().__init__(config, entry_id, device_connection) - self.output = pypck.lcn_defs.RelayPort[config[CONF_OUTPUT]] + self.output = pypck.lcn_defs.RelayPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]] self._is_on = None @@ -101,6 +107,12 @@ async def async_added_to_hass(self): if not self.device_connection.is_group: await self.device_connection.activate_status_request_handler(self.output) + async def async_will_remove_from_hass(self): + """Run when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if not self.device_connection.is_group: + await self.device_connection.cancel_status_request_handler(self.output) + @property def is_on(self): """Return True if entity is on.""" diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 82425db675b9d2..1721ef8c2b5d21 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -863,6 +863,9 @@ pyowm==3.2.0 # homeassistant.components.onewire pyownet==0.10.0.post1 +# homeassistant.components.lcn +pypck==0.7.9 + # homeassistant.components.plaato pyplaato==0.0.15 diff --git a/tests/components/lcn/__init__.py b/tests/components/lcn/__init__.py new file mode 100644 index 00000000000000..6ca398de93fc4c --- /dev/null +++ b/tests/components/lcn/__init__.py @@ -0,0 +1 @@ +"""Tests for LCN.""" diff --git a/tests/components/lcn/test_config_flow.py b/tests/components/lcn/test_config_flow.py new file mode 100644 index 00000000000000..325552f62d3b9b --- /dev/null +++ b/tests/components/lcn/test_config_flow.py @@ -0,0 +1,92 @@ +"""Tests for the LCN config flow.""" +from unittest.mock import patch + +from pypck.connection import PchkAuthenticationError, PchkLicenseError +import pytest + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.lcn.const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN +from homeassistant.const import ( + CONF_HOST, + CONF_IP_ADDRESS, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) + +from tests.common import MockConfigEntry + +IMPORT_DATA = { + CONF_HOST: "pchk", + CONF_IP_ADDRESS: "127.0.0.1", + CONF_PORT: 4114, + CONF_USERNAME: "lcn", + CONF_PASSWORD: "lcn", + CONF_SK_NUM_TRIES: 0, + CONF_DIM_MODE: "STEPS200", +} + + +async def test_step_import(hass): + """Test for import step.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch("pypck.connection.PchkConnectionManager.async_connect"), patch( + "homeassistant.components.lcn.async_setup", return_value=True + ), patch("homeassistant.components.lcn.async_setup_entry", return_value=True): + data = IMPORT_DATA.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "pchk" + assert result["data"] == IMPORT_DATA + + +async def test_step_import_existing_host(hass): + """Test for update of config_entry if imported host already exists.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + # Create config entry and add it to hass + mock_data = IMPORT_DATA.copy() + mock_data.update({CONF_SK_NUM_TRIES: 3, CONF_DIM_MODE: 50}) + mock_entry = MockConfigEntry(domain=DOMAIN, data=mock_data) + mock_entry.add_to_hass(hass) + # Inititalize a config flow with different data but same host address + with patch("pypck.connection.PchkConnectionManager.async_connect"): + imported_data = IMPORT_DATA.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=imported_data + ) + await hass.async_block_till_done() + + # Check if config entry was updated + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "existing_configuration_updated" + assert mock_entry.source == config_entries.SOURCE_IMPORT + assert mock_entry.data == IMPORT_DATA + + +@pytest.mark.parametrize( + "error,reason", + [ + (PchkAuthenticationError, "authentication_error"), + (PchkLicenseError, "license_error"), + (TimeoutError, "connection_timeout"), + ], +) +async def test_step_import_error(hass, error, reason): + """Test for authentication error is handled correctly.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "pypck.connection.PchkConnectionManager.async_connect", side_effect=error + ): + data = IMPORT_DATA.copy() + data.update({CONF_HOST: "pchk"}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=data + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == reason From 48808dc2ada27fa915a19552108a3ce6ef86c00d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 14:43:32 +0100 Subject: [PATCH 1271/1818] Upgrade vsure to 1.7.3 (#47946) --- homeassistant/components/verisure/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 744f7fb706c6aa..05f07e926e0be3 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -2,6 +2,6 @@ "domain": "verisure", "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", - "requirements": ["vsure==1.7.2"], + "requirements": ["vsure==1.7.3"], "codeowners": ["@frenck"] } diff --git a/requirements_all.txt b/requirements_all.txt index 68ba25f4659683..87cde66dd571ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2278,7 +2278,7 @@ volkszaehler==0.2.1 volvooncall==0.8.12 # homeassistant.components.verisure -vsure==1.7.2 +vsure==1.7.3 # homeassistant.components.vasttrafik vtjp==0.1.14 From 37c53e0a047f764828c57704863ca741d40af4df Mon Sep 17 00:00:00 2001 From: David McClosky Date: Mon, 15 Mar 2021 10:07:36 -0400 Subject: [PATCH 1272/1818] Sort supported features in vlc_telnet (#46800) --- .../components/vlc_telnet/media_player.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 68b3c373c7add3..8f557b1e0a4509 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -46,17 +46,17 @@ MAX_VOLUME = 500 SUPPORT_VLC = ( - SUPPORT_PAUSE - | SUPPORT_SEEK - | SUPPORT_VOLUME_SET - | SUPPORT_VOLUME_MUTE - | SUPPORT_PREVIOUS_TRACK + SUPPORT_CLEAR_PLAYLIST | SUPPORT_NEXT_TRACK - | SUPPORT_PLAY_MEDIA - | SUPPORT_STOP - | SUPPORT_CLEAR_PLAYLIST + | SUPPORT_PAUSE | SUPPORT_PLAY + | SUPPORT_PLAY_MEDIA + | SUPPORT_PREVIOUS_TRACK + | SUPPORT_SEEK | SUPPORT_SHUFFLE_SET + | SUPPORT_STOP + | SUPPORT_VOLUME_MUTE + | SUPPORT_VOLUME_SET ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { From b9a26cf5391365d4dfdb6c22aee88c5d577eafc8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 15 Mar 2021 15:08:45 +0100 Subject: [PATCH 1273/1818] Add zwave_js dev docs readme (#47621) --- homeassistant/components/zwave_js/README.md | 49 ++++++++++++++++++ .../docs/running_z_wave_js_server.png | Bin 0 -> 3786 bytes .../zwave_js/docs/z_wave_js_connection.png | Bin 0 -> 10602 bytes 3 files changed, 49 insertions(+) create mode 100644 homeassistant/components/zwave_js/README.md create mode 100644 homeassistant/components/zwave_js/docs/running_z_wave_js_server.png create mode 100644 homeassistant/components/zwave_js/docs/z_wave_js_connection.png diff --git a/homeassistant/components/zwave_js/README.md b/homeassistant/components/zwave_js/README.md new file mode 100644 index 00000000000000..920fc4a6a0b60a --- /dev/null +++ b/homeassistant/components/zwave_js/README.md @@ -0,0 +1,49 @@ +# Z-Wave JS Architecture + +This document describes the architecture of Z-Wave JS in Home Assistant and how the integration is connected all the way to the Z-Wave USB stick controller. + +## Architecture + +### Connection diagram + +![alt text][connection_diagram] + +#### Z-Wave USB stick + +Communicates with devices via the Z-Wave radio and stores device pairing. + +#### Z-Wave JS + +Represents the USB stick serial protocol as devices. + +#### Z-Wave JS Server + +Forward the state of Z-Wave JS over a WebSocket connection. + +#### Z-Wave JS Server Python + +Consumes the WebSocket connection and makes the Z-Wave JS state available in Python. + +#### Z-Wave JS integration + +Represents Z-Wave devices in Home Assistant and allows control. + +#### Home Assistant + +Best home automation platform in the world. + +### Running Z-Wave JS Server + +![alt text][running_zwave_js_server] + +Z-Wave JS Server can be run as a standalone Node app. + +It can also run as part of Z-Wave JS 2 MQTT, which is also a standalone Node app. + +Both apps are available as Home Assistant add-ons. There are also Docker containers etc. + +[connection_diagram]: docs/z_wave_js_connection.png "Connection Diagram" +[//]: # (https://docs.google.com/drawings/d/10yrczSRwV4kjQwzDnCLGoAJkePaB0BMVb1sWZeeDO7U/edit?usp=sharing) + +[running_zwave_js_server]: docs/running_z_wave_js_server.png "Running Z-Wave JS Server" +[//]: # (https://docs.google.com/drawings/d/1YhSVNuss3fa1VFTKQLaACxXg7y6qo742n2oYpdLRs7E/edit?usp=sharing) diff --git a/homeassistant/components/zwave_js/docs/running_z_wave_js_server.png b/homeassistant/components/zwave_js/docs/running_z_wave_js_server.png new file mode 100644 index 0000000000000000000000000000000000000000..53b5bdd3f8f3dc4ae8e1216f011b46dc25d58e17 GIT binary patch literal 3786 zcmcInc{J4h_y2rmn86@3_O;2DHAcqXn8ITyj4g#0M1-0|Wc{dYBa-mgjRx5&%T$cg z*s?^%l0-_jY>_2;EctoP@2~Iq{{6jwyzlFt_dTz3U-$h!_nezRwl?SEk>CLUfX{+t zY6k!?;yz|^!S~NmU$tNRuAh9$!EE0n5J(st-aE0jk9)>ftQ}o)%3i`bI3%QGZYO7Y zUk`oJK7>GWp?G z%dJD-3!XMSe%X4_=X%V8+_2b`Yq##`6HL+ypEM0jzG07Nl`^gbMjVut7r+SW7#b_8 zsCSMo95+7|awkbpShT+Dlc>04Mp2d1#mgjX2NO%1V2Sdg9GjeU?%w{UZ~V9~>fq}EqY9sgdv(X&qOWI#7rgB4TYeRA?U0*4Tp*u>w~nDDx*=JN9XbqXB^!+&KPpfKHTQ<{+p+afyO1NKr+ATIat#x&tL(u)I9408EWIrGfc8k z?TQRrtT5^k#9#S4rnzq%b+H&Ge9kCiXS``jDa20!j0m^@ni+IeCC2w+AVhE_+`DGY z2^7{hxoB(qtil&aX1+pbmvFl6E8mh@Zc$xLLw7j|gt_V5LT%6R!)sJx6B7v+fZPm; zJ>bj&KRMr1>yr~UCx)#Yf6_V2YdMTaSP-~b5^Q15r(w{S2$wKjb_?2)#oAX31o$X| zV^e=%tE=6zI(-GLLx*F+`B~u{e(BULmYb+*M%$+?=SuNgV<9^tNcoA8fLII?=wGBA54)Z3qt=LU z@V6J|n^Ga5zfVfAQR+myUB1L&N@Ak4t_4xI?hS;_E)FkF*R%eS5GX3KsF?`+P4g-p+k`|OwMZ<;(sX4geeMR_vxs0(cI+*iQx+l+ zMzSLdqL1qJC|qwHlgh6be8a|wP!GgSGH6Coe56_7Mg1O?rv`6p?rlOTlJE*6Qz9qY zUcSW-!{%F3$G$5~JEX^}1UXbUSRz>7T)#L-E_YlB2~cPkTHSegfNU~w4b&INRYt}4 zq`iv05B7Lltep}g=lQxC^;yA~O?jMC)9L1S%Y8uBfZjU{B(mTaU!Bo*)%!4iPjNMO zi0dRhR?iC;b7d#nVXN+?PuOoE_IdGjpBT{M9aYcUijAMK`?nNTtWOub(Oc!F!WO1) zMn+H45jZOY`ssO~Srh_T6>?RUaoUJjav+n6Dr}OK6|Y9_NeTSrv3*YyE9DIrGZwN5 zw;;UzI?)Kb(w8QOZ&j1<#TD1%5Nt2$Dus7U8=klM&pZ5l^l;{J#48J2QVNffYK2L}TcE~_-uCqTUq z#rmmlKaCs5pE`xG<9a176njKg`TU|W$Dn=bIau?hVoh4NYTK3n7o{()I$mC|bb>PA ziDQ_|koiRv093Vn-IFNiBks@mj0em+R4<**YX7+Tok{M=3n0*I(nK| z+EF4VQje}}5xJ^8E(hK2Vs)5xR|loOy`IXkE_qF9K2b1CRwZc8F2U)(M z&#yRugEi-(M(=Mt48!G4%x9*zCgimpp(}v9MUakEXuRiHz##2hBM?zi-Mf~1H1jsY z$h@J5U!*C!Y)bSiEIVL)oSsiumMh`&$(Gs#TzVdeM^ITABA<)h^Rw2VIeiQP4X$`8gn%B}V(t%+Y!q{Ar$c#CpMzx32wqDSnkjcHX~ z(h`L-XWm(N0e)Mz<6*Aubw^`I(#$+DL7XG}O4TRi&kk9#hoEGvXpztXduFH1?BV?h z^6B=)VmO}Vl&h6PjNVOXG$Af@`=u*jA6x!5L75ReVZJ4A5b|mg$}2%X49g-r`Y50{Mqs6o+s_GpYJ2?ck-(A2VApB z_$jM#R=q^%wnU0fG_8yomr_eq>cMNRcybPhC;c%$p5rjYqMpPLwlh-0dA2P;f4!Rm zBkw>IWql|x9@3YX6(pHV`fIEzf%$G9&k8;9|JhVA#_KUl$0qX_GP#}yAq$a$8jErm zq=T2LDs!|ZnS^2Y@^om)Xousn36-s%35?M`K8@(hXaT!5`Vx4Rp(9x@AYZJW+!s^g z8X%Q0aMxIh$WFss@)m4r)=WT}S_KZ=!={)0N4NLzG~^$H1HmHfl&C3^c(t*Cq5Dwj zK`1|U`B5l#yRV-+S|4+B%m)EreI%lI?`UaI?nD3WFB$m zHDZv~D9k@>Cu#N3r;3Vcx7|>i3>}kTFKSaJ ztkev0mV~50E+VkXi3MVW{s(x_5D<3SSbCxBqr^KIG7SUU{6QpGsocD+#OyJ&~EgmqEXgP6xS!EQ*32pHkq)hu7C$E;;kq1Y|!rfA=JCI>7Uvp zb;iHe6R8^8Bvv*htFja@Dr|RjiGBIxQgr#m{)l!}=`boU6PH~>H}ODgX$M@8pDilf za0ny9MUB5$F&{e4j8s58aU*oih7dQxRW@i^-R;*h&ZV5Av$c-9G~5xduoa#HI<_f; z^(S%;iE4QQ(E~VhV5!ybIl|ziv$e09J5X`hsERw&1^+TknjJ{Z?P=G_tM$5$FMLe4 zzp<%VDu?ItSnL+xa4oG5p1mOlFQ*E^m`n!q_fTtY#(-(>EZo3Ynb?Yl_;OT{(9mCA z6jwBVG(VgsE%M!K2g-HINbA+*FX-}YhbY(HH%pFVQ&*|HrIwL0P+ChnJc2zS19Li!{n4tQb)zSPaiBZX~~_8m4Qmg1@L*ho*{Q1 ziCZ;t;^g!lBO3Q;s1KBi(K_zc!wKX)sfg&s@D;gdx2Q=ktagWtiW~{BD)iss^kw-q zJ+>ITUq4T~h*~Lh+8dlPr1@U;_h1U_euu_2zn*Du$Ke;Bd`JTwJA2_0D`)T0>vnh} zmViJ^YWC~*@3K}A;@8XqeCdO=B-IPrPFATU2JL+=ab-{Lr;RF>S*_r)1;HJz8l)qx z{|KK}HY2|2DaA(x_S^rgoZK)}GCkk3FkW2mVz|ewtQe2WeqOSpOn0B!i*Sa0(Ew1K^-A5ynN+gMfJ+EXYBY zA_762DF94~9}6*IBmfjb3J}kM@(@6UbJ3mx7$VTNpXPMgYa{$4Y`xoa_r=L}MSz6` zSSss#Iom|^m}#nHDtgui8|{VWisn!eB+<@JED0n3d$9rlC?9F5J*Uc0mJ0j#+QQ7* K^y!IDIR z!^g(KX&YIb*f@?(%kLQb#Ky_1XJ#vjP-0=@q@{-kp^^xQh{x8BUJuTZQBV<+kVPhD zGBB|?cm|3|%e(nUU}9lgIeT;R3O4n92#rn4MAr{3?ePc*GqSMRy84>fy41fNmr+pR z6A}{;l~hpGOv$f&mRlB|S@JYF*E=-EGZ@+P;j@OG$=j(lMRlFDqL(U~`W6lza!Tqi z+uv8U_8VF{3@(1_nplAm5H|Eq=2y3%8{hgx#ATG!j;$V+H^0j%Z!D?r^p8rY>l}&A zC{)!pY#mxq-@#J;bLRp9dRqE`dx-T91suY~rp@#Z=pCo?>vwsa^y9*b6o>C`C9`Uu zdNH@H^81HsE30>##mE*Jri_qp0sw3lYKrm(AuC5S0{DIWgn;HvtYq1zK9=3t4DSU1YJJ_b^bd{qf6ZEMx>$|+h?0m>r? z6_*2&Kp-u}V(W-*AN#oy z=FZ{ewz8~CyijQ_;;n+OCHFA@>fj#HcLnIHoxIy3f$HQ4d*qCl;tJ=`vHm2~qKa`; z7PWnLM%IlX9y?=?qk3!7@ypVN3E+Zfra~}Ryj9`*65=vx|0?U;0{y0Nj&kt1XP+Ld zly-pW^xz%1$|Uw%CNsj{%AL`42$L!o*(~}C2c;F$&-pQm9{?BN_65N?1gR$YWp!S0 z-7}yXOcVafScgQv$Fs=#l|wUXXsu~TfQdtD62moajqLx5!FRMP=u`Zt;JZA*u&wDV z!M4A{Xk!#Ldz)+_(FU|j7vxh<%tC@&Jg-OgxSn2U%wq~W+l%3C7a>Nrty1pRoanJb6iDwyLXj3;l;MGJGb$|k3EYeC0_cUs(p zzsRgkM);DyG*ggx1FlLK?_6p?GkKqAeZ|{jMHOL^HP4VJ#|a}p2#Ps>q{cN%&Nz8t2yrR0dpmU+1rM1sxEo;!Osx7FXGgvl^Gh@XGhoN|__?J!R z)SDJ$L*lP$7fgljnO#~F@s}n0+MBAe!%dd{ab12j13~>SiV(Ip@FzD^0Fkzn9jN4v z5clW8OBTA?d_z7APQlBo@D@Mp385_F6U*Wi!(0&oJQ9pk zF<8`rfR|$l%`~Hl-S*!7L`Vq@ev=>&mAb{7cb+zw10@$RA?!LCXJ#b4sc&PH#6n@4 zG*5RYI&ppi=2U3KgNIexA8^EO_vqpydRZ8bfe=mu$==6>BEhxKnY)R^SB`EbI*-Wa z80W>_fEkC9T$h_E!R!q{h2{Ve-(a_u-h9{D~>F~^s9(9cV=9n*k%7}mm{9@;be z(dKoRRpjl`-1h=Sf8V-(-!yXOsrK!b5w1X=i*0w&b7iq8`a* zO-d+;Uphz>lq9fhrmA3Sma8!5vhjQCfRwAFYOy%1`HrD}cI`|p?HMiMb1;)1LJ^iY z3M7(%Ghr%jBNP9r^MQ6iE!wU9h1ZEJ~0+td)3T8Wgv#08} zrvO&7gnf;0xNgYnio`leq5CvTw-?LD2=Eq&8JWJfMr4`159SE`5ek9zEL<;3}Q@4#$z*>_1=qnIHXGf}}S9fY4=wSF0e=mj%zm1i! zKDGqmjayJMxH|c&3x7v}8(2v=R%#u4X1mWJK5pm&h&uzFaCQK*Uyp0S#kGkJ-70i! z()DHP2J#cliG&3xde#j-Owdgh!m=P z*|6j$EV9M_stbQWhcaH95q-kIjfMS;)R53+zNPr~U5B0u<3{iL%?1Wb_$l*54CarC zlVzNMaNtxSxxR2Gf~_b2O@-4m<97}non5=|dcB*UG7@cScCl1 z50$qlO*A0_u8QV(GZU@Ku@Hsx2CIs!Dk|maTD8Lh$28hmi*;%2xcjxr)mXPYtxJsf z43r3en@vIAsPL7B)N@uH)wPqnLQUk_M6Za2g(B-rm4d%fLpHc0w*c5$C;F750J4&1 z@v7W|lM|(YEn+IMh`s4(y-%4hLvzZ9l3cvu@yYuu{5eiQL(7!9mOi|yO&_#!29z|f z550Yf;3y$dJ(N{P9ltP)BC8=qEu}4KLtpv@gsslK&D(-^vV??R7wl#T*s_lnyW?lOZrX2+ilF9!12Zq-b9=Ee-(A;Tx7H&$}lW@;DU_<1j&cdoCO>ZFD)g(NlFoD1_4 z-8(_ zz(c4nhANA=BXuLqh*PYUOBgBCQ-ih?vDaBbJ9Kg^EV=87OPs{~pnt zuMA&lCJ{_FbrGMyq4BCIA0e#yU+Hg{@UuNK2GRa&!ZY?@iU{W#X_>9*h3XGVBwS4n zMn7#>Zq~Ef#^!H#5p^X;NW*fO-bj(8UrtPoH0%Zp6S1v9#Iu}LWsdpjw+@tzbnFCz zM6cq{@oYTUHcgoxI-}!KTnoamQZ`0bX%ywZI&Vx(n=W4{zn?9ec^?!SBnL6O)~d*x zuV>1{;Ljup=g=Ba^+g)Y>8d*O$y@S~m2_68isfBGzm=CtT znsk^pr45^D$=KgRUK0}C)`9EV4El>b^Cy6NAXCowD?4$S}={4|l za_luLb!LYuq)hy4dqFvXIPbS!==E)nWG4C$KU+RrbWq;dEq8UrfrhD2CUZ$AN=g#6MXj7^-1i*?`0LAE>TnWqoUI9UgZRHDE7BO}`2{xw03#OA%3{ zHmcr+i`0vr6w-(5hqT;u-uI*s z?(;2H-J!)p+86%Xr8@$Xi9@;kBszrtssX%i(l1^24mzHJBH)1N&0r)GJF(mKL-OF4 zk8s%+%f!m19THK;r6Zp>5J+;c@hMzv^SCoL4?NAfv|%}fk^6L*eY`+e zGZQenS8%L&ThiX@ANoQXSc0l8RaJ-@MozIjFZ8jr(%@;}{g(860YQv19x){gzp70} z9~>nQih)3}{|85`4W>-X>;`m-aN@|lC;YOlpzw8|(FZ0sKANHkE#333c*(t;icipF z2hWm{yxPD6dpDtPt5F{Np~WGQP47PT)U728Ps+<39M(&?vZoAQpFA&>!!`$TxGU@r zo1s-X;)rp`Nhw-+7o&V1xl)<;%t+l?I%fKNm5vono(iFGFR%=A0QF|ZF96p1507+K zrLIAaVso4QsuW8Z-zVb1MMu;37eT0X*b6I;aP#<#SPYxX=c|>dUI@gOMt5LSo`jX&690wFAJ*Q{@^)6 z-fI7|hT^cq1kmW*R2S?%^bEZGX}4f0IEYhcSc7lb?31r}*X9%1ZNC}gsJpV7#~%v; z?U&Kl&&tCu0=`~`qH~-#B9eY4KUe>YyX*{}`MFyF6UGtv-Ne391OAHsd-?L}L{YN1 zW*`AHt|Tm`zL@t#$|~KhBug0)N6D#rAl#96vj)q;ZMw{2^EK-S@M#^(a$KYLr!K)N zy?{MH!*HJtFGtmnXXZf+9dnuXkq}s*ntrKv#7mH7;4q(6jgkh{_&x_i4$=e1B$SPR zmiPt}#?}V*S-9pQoR+rEoZ~fL^7@?H#kVPa_s9rZX#f~4k|p)mIRJ_8Q)wy%AJLo} z#)VLFUe3!H^u87c-9Rq_sEW5V-*F9Xrkak3dH3=AEO6tW_MdPX|1m z;;r!ukmpyyntQxLsc z7OKQYL`GWziTw)&{tJEriC#caw%21GH>w` zp>IDQ_sjzBHH<7HE`iQqIe$x4+Kf3Cz zqK2R#o{XI2;%A^L~$_v}n3vi3XIcFk)K-UlbS6nN1l_XU z*Elp_U$!B+s>ctvPZ9XZGhF7oBV$%d41JB2jl`ev{5$dAE0AG+dV1LTY!Dp& zbP4n(h3AFJh;nkOeg*9Pp-63(IM=VflIPCMQbJW{vL_(P;Sp8K|X#pZsCaK>U;M-2cF9 zrqKX|`#&M--vD-C48iK%ezyua{ttNjZ{}cH4|f@Mj4kB@u?qq*4aef-q(9Lq`TfAx zpyu*sex>C=VWL}&by?=CoGvCc3i?m=CK!>YjA(O(>F^Yirq}P_(tl4{bXeXZD4|aK z{3YZwJpw=QUO{TKy0yOo-;&{$%RY@n*JH~O8~bjhoGfIO9+6DXc=ZX`(l(@l<_q8j zZq4~o?uA}rBcxM+*&g1;070`}s2@k%IPA9=+~YCKf<4e(<1ZETlP87U3P7@nj5958 zpiZQWFQP*>?iF5(z2WP-iD0}zU*LxL{WlgCw*Kx*h}k?|{>|PVjO7h-F@9-6B&idU zv;uE($L4=EW!GOuB4JLA>E8C8t6O9D5Y|jT&>{7ScbT@8Rw3a_)Uw>Ne911%6J|N zgPosO)Oli)0uJm@y+?#fRI~(~d4e&L=a1g!zeIBG)F`ybLPyc{;}`W9$Hf}xwVCx7 z2%*`vGF_6&4wI{|WmMVKHfS{t9B@>i8HF9e%Tgj0t{S})deTLS5OjdNCxcK%E zW63y`Xi3UuG2NsvayqeW>Xxc2^i7%c9t2%c1aZUADJ0{sQm9Sj(P&=ccI}Dd=YpB+ znA&?Vr4P>3RJnpDw1Mh1zg5?sIRez>JhaZpKCon%uBVEw0Ub4_TMKRs00Xa*z2w9$ zgr3S$TM5r2#8qkOU$1hw6exPRbdPi^hXna~>6mKa8MfhYtW^7brJOIUE9m7mV=`L5 zh;r3ic&hvMlj0(%LlU&P8wA$9KC986^UeNh$$TgB;5IRl_wueHYAF3TW-5YtMl9$! z9|{1(+fqLL7#?JV1H_C5I$-=$nEnUUZmH%M146K7F#k1F^78nvP9OfXuh-{~?xXzR zX}ShUytR)Fc@7MC+Oq2w%h$zvLy24UDT03FpLW_RDE5{NZV3pZHesQs>D2FR3l?PtFD-^%_L=?`WDs9K$aOOH>pt(9zO2O2 zFpxfK>omW9rO5coo`-qf&##u=30<|CTa61N*ahG{O#uU_KZ^ur(!DjJ>^#KJ@ z&|I9N1gg()v3fh74BkAH zTTFy(Bglkv#}Hx6%3E7;Y>2*%y^BsAHNhx<=fmNS5XCva-r`V;NGQ?=C`*Ih44ds3 z7)5^uyUPAlIbQwpHtK2gOrUTt}%1pkkCX^-D8KaBY;Hw{p^9&U+{_n zQdv#Y_-W}4laYBfhIHB$XWbZT+*N(!k#3oUc8drCNO1I%^FnG-bvB2NA4b6KQ0Qg} zhfDSZf5V*!0JoQEFg|jR*N+y{=ExAum~d@3Mye-0G8s_TCl-=dSd;$*OqGi=TSL6} z?jz>%!iW|-CuAF8^eA@b6fBUztn+=YrpY-&i5hxiRkcLXMCn8z5p;P(o&Cj4Pn|az zCbBHg1BDYm9y;T%)d!7b*Doj5J@* zK}VCQra#Y(6d{T&A7j5o0LVy2EdGYbf8+Z!y;N0r^o6Ny#gn8)(~|f{3Wagwk@=se zE!m6B&tu)6snY?mm;|yn;n;{C`vTDp6bn-alCF=>?*QZh7$rPB;sbubTr|^R$CjdS z7IP8eM(x8x4Lt)8@6&QS!ud&;QE3x-1*m@rf|PdelnZ@973ZjGinnb!koO_5qPS1= zY-IHp7q$vxpGi$+voZ#Xb3j-`@%XycC|?mGk)I<;_Lii~LdG`tibp4z z=E_9MGIB)H60k5&a@G|dCJ5p;Nu3Ujty~yVXH-l3VAI)^l6C&V%_KZ!l*L{%5tbBU z*5{0t^oHXlu~hu*p=o}4WxK|0EKW*RWN=lY_!^{<^hRoNSvzel&e&+zE0d$pLT%iywL z-pBI@06m{X9219>(NvBB#t#IC_ws;~wJjhwM?OQ`DQa=KME-FX5QY|(&WA1X-A%hI zY^jY09Cnbij(sw3M)fdW0PeIj6prB%oldOYX2IM(B#6co%=mapxu+wj!`#uudP|e> zjk9ubbq+P-Z+&QlvoHzXsyk;uYF>Ro9VgQIh;2{$Ayi5WVx8sPS?IA0&7zAiNq3R_ zsfp|x@dz}ZAz4dXWW<4=-tZCxG*z3PnuU2@-zCC8!$#m@8>HY-Dvf5SLgCCIFkI39 zmgFHe`A?U`ZTMLJip4hh52S)P)#+^0o-CI1!W@V62up04{)lQ4cj{Gk=6LY2s3 zajZBg@r#C&U+?dPRmP%mmb6#vpC0(Mv^wq{wW>+^A?PH~X|y^ju$qdekiyH3EaP%Z z!~PX71o(#cd}frLUKil*4#JoDu-Z81kYZl<1KEJkU+{F@MLvKE=R&I!Hl4btV{4BE zGG_bP^8gN39!ZSey_Ybz3RuY53W6={G2s%WIp&`A4>^FFr)*j|X#m)m{CN1s0RDgM&O`|*XP=N>jEeU@TX)LaPX8NWHpGs zJ(_O@#-<|^Ss!sjy4*>Dfe-nng;d^n$zc$O_k;}z2$_;NSqlP`)^cU+giNO-95dhr z_+S(m+3cSH`BU@a4cxT%cE0ROYyv3$V?X`xfZ9RR_D^d~i2fT)|Gn{Uwr|vMeQZk_ zKcZpA73+hug$GK4gFqOScJkXSoFNTiUmY=%YQB4{PSn5I`tg_~VMn&36QY zEp*wUt0FZ{bP&**{d_MexCin|BylI47v2ym;2-7Dx02}b5j32Pz&L)~5rk>n01w`( zCkY(Jw+N(~gQ4ui4({x{kLoB=h0}$>Q$B)vzwx}^lCLkuBb>6#Y@;x8TBgs?*qicF zxQgRFui9axPj;0}aB_gmRb|0$fMzKFW*?obr9UG=KyaipoKocDnur@8==oSXJMHi7`y=^K`QC93d4Ahh<)9Se{_lnwZFz)X|NeNc0ADwl6tbBsgp&Ws5 zL0{0wxEp#e3vp2_x;y5VSdc`=jdy7^&*ym}d?8?) z0akNFJSI86(}z;Y2Ny7c6Vl(eCLcz3;sfxg#=}a$`z<|FdTd!AIr1TYpBEe$f;m~} zAqPF1{J}oB6q10jLojR=aJb8Aa8Gy&U@eG4n?5v%ds&i9&1?~V;d=!xGKciolI;oM zGES#5K*I{lXntZRY80Tyl?tt&adzh7h4q$~X$KcS=pk$kh-Ph6)jFxHlJ5pGb)wwkhcxqwZ+ z%_uC^DanB)aRk9|u8G0IzQwgd? zyfT1elc_3TWa^+;{@dxz@HCwv5C8<=Wk1HdA9S^O7ZOZFZu5W1c4O$@euD1yq<<;+ zZvpXd4E>DL$Z;4+Y#D!=++PFidJyA4!IzH}Pag?MeEvQ7y-CxNcx2Jb<2MBxo5ch1 zN+sPr^GxdqT@Ug1I-HL|7}PVtWeLj4r->jFa}t-m(2Zjm&Ebyf8`vI5SZO`J4YtRJ z<|uIr!}6+rC7*vl52m6^@xw;!40TbS?YMh4esoL6Y{U8ceyMi|LNd8S z&7b`OMLhOXQ8t>eIE5L?*%wD59i$1bdSkWC<->V;D){1FvXbIFwo}3Kx!{aYM;|Mi zgV!)1yLo^oqw>)~whcON3L&UZZ^#38Xsk)LTSf7!mI^IH&2YeIQjJ3t-ZP*$Pn$PB zJgedAYh-?XJ?22Hx(`F7q%iV?oN%@>HVz?{$`tV}y`ZY#bhxP|9HET2)rGq-Z@M=a zNU@i%sot@c^$P5VX#aa%!Hl`^9DL)j>t%$6^_4%#yrb;5xv+_9i>^lmDKXdBbY`}> zvAuw(_XxMMx@Fz}y9U{~!>S5$C_mmQh`3i^K&Pb^_>R^1iSikfew43o*e z5h8LkLYC#injs`ur@$jD6IHJ z7lSsnkKW}OCW4D$crfO(k%$GUgnB4n-cvOS9dw9oNf>OCo(#vWLRX|qlQSEsQFXR8 zm&m+d7_mS@4S4w_4tL!IY(G>&#?BxYW0f=DQL|lLa~SWIeDTd^OP5Go)WVWSr^I)0 zW^v*Nfq_oNIB3+B)~zq*3y9YeY+l~>c8!O&A{^W*^fK{lFtd$RTtlX zIO`OyNXZ+!-2WytLo$2T4fg#B)h%?DZ*3FPPuzhiR^Q!u1brEMuTpH!v_HH6kZZE0 zR9qR}2q?E7Yvc+KE`7V!jtrLa>6_B>WAgF?Twy*0W2zfJT`qeJ`cu@#%io)S-uMfk zh{%A;YyZPGH}?dX8vtMnNG!7#JmG_3Us(=;Hc literal 0 HcmV?d00001 From 5d5a110a20c96989a05497bfa9bd531e03b03276 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 15:11:41 +0100 Subject: [PATCH 1274/1818] None optional hass typing in base entity and notify (#47528) --- .../components/automation/__init__.py | 4 ++-- homeassistant/components/bond/entity.py | 1 - .../components/climacell/config_flow.py | 1 - homeassistant/components/group/__init__.py | 3 --- homeassistant/components/group/cover.py | 1 - homeassistant/components/group/light.py | 1 - .../components/huawei_lte/__init__.py | 1 - homeassistant/components/hyperion/light.py | 1 - homeassistant/components/hyperion/switch.py | 1 - homeassistant/components/notify/__init__.py | 16 ++++++++------- homeassistant/components/number/__init__.py | 1 - homeassistant/components/remote/__init__.py | 3 --- homeassistant/components/scene/__init__.py | 1 - homeassistant/components/switch/light.py | 2 -- homeassistant/components/zwave_js/climate.py | 1 - .../components/zwave_js/config_flow.py | 4 ---- homeassistant/components/zwave_js/entity.py | 2 -- homeassistant/helpers/config_entry_flow.py | 3 --- .../helpers/config_entry_oauth2_flow.py | 2 -- homeassistant/helpers/entity.py | 20 ++++++------------- homeassistant/helpers/restore_state.py | 4 +--- homeassistant/helpers/template.py | 2 -- 22 files changed, 18 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 862a664976b4e8..4555a336cc36ae 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -517,14 +517,14 @@ def log_cb(level, msg, **kwargs): if self._trigger_variables: try: variables = self._trigger_variables.async_render( - cast(HomeAssistant, self.hass), None, limited=True + self.hass, None, limited=True ) except template.TemplateError as err: self._logger.error("Error rendering trigger variables: %s", err) return None return await async_initialize_triggers( - cast(HomeAssistant, self.hass), + self.hass, self._trigger_config, self.async_trigger, DOMAIN, diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index cd120d79d56669..b56c87f692f7e4 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -158,7 +158,6 @@ async def async_added_to_hass(self) -> None: await super().async_added_to_hass() self._update_lock = Lock() self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) - assert self.hass is not None self.async_on_remove( async_track_time_interval( self.hass, self._async_update_if_bpup_not_alive, _FALLBACK_SCAN_INTERVAL diff --git a/homeassistant/components/climacell/config_flow.py b/homeassistant/components/climacell/config_flow.py index 09e02f3f559b7c..7048e4e5c2a7fd 100644 --- a/homeassistant/components/climacell/config_flow.py +++ b/homeassistant/components/climacell/config_flow.py @@ -110,7 +110,6 @@ async def async_step_user( self, user_input: Dict[str, Any] = None ) -> Dict[str, Any]: """Handle the initial step.""" - assert self.hass errors = {} if user_input is not None: await self.async_set_unique_id( diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index f185601ce87e1f..1ce0b3e71d703d 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -395,7 +395,6 @@ def should_poll(self) -> bool: async def async_added_to_hass(self) -> None: """Register listeners.""" - assert self.hass is not None async def _update_at_start(_): await self.async_update() @@ -405,8 +404,6 @@ async def _update_at_start(_): async def async_defer_or_update_ha_state(self) -> None: """Only update once at start.""" - assert self.hass is not None - if self.hass.state != CoreState.running: return diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index da842ee9f007ee..c19e81778a00c2 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -155,7 +155,6 @@ async def async_added_to_hass(self): await self.async_update_supported_features( entity_id, new_state, update_state=False ) - assert self.hass is not None self.async_on_remove( async_track_state_change_event( self.hass, self._entities, self._update_supported_features_event diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 482bdbafdabeb6..cebc697efaee88 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -103,7 +103,6 @@ async def async_state_changed_listener(event): self.async_set_context(event.context) await self.async_defer_or_update_ha_state() - assert self.hass self.async_on_remove( async_track_state_change_event( self.hass, self._entity_ids, async_state_changed_listener diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 341f9c0a118b26..2d33d1a1822f3c 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -636,7 +636,6 @@ async def async_update_options(self, config_entry: ConfigEntry) -> None: async def async_added_to_hass(self) -> None: """Connect to update signals.""" - assert self.hass is not None self._unsub_handlers.append( async_dispatcher_connect(self.hass, UPDATE_SIGNAL, self._async_maybe_update) ) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 838b69cd52b568..89f70c8f24dd69 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -397,7 +397,6 @@ def _update_client(self, _: Optional[Dict[str, Any]] = None) -> None: async def async_added_to_hass(self) -> None: """Register callbacks when entity added to hass.""" - assert self.hass self.async_on_remove( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index e2da80e509327f..f6317a8f396c0a 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -193,7 +193,6 @@ def _update_components(self, _: Optional[Dict[str, Any]] = None) -> None: async def async_added_to_hass(self) -> None: """Register callbacks when entity added to hass.""" - assert self.hass self.async_on_remove( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 7be66dc3c59ce5..5e1493a68eb6d1 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -2,7 +2,7 @@ import asyncio from functools import partial import logging -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, cast import voluptuous as vol @@ -114,7 +114,11 @@ def _async_integration_has_notify_services( class BaseNotificationService: """An abstract class for notification services.""" - hass: Optional[HomeAssistantType] = None + # While not purely typed, it makes typehinting more useful for us + # and removes the need for constant None checks or asserts. + # Ignore types: https://github.com/PyCQA/pylint/issues/3167 + hass: HomeAssistantType = None # type: ignore + # Name => target registered_targets: Dict[str, str] @@ -130,7 +134,9 @@ async def async_send_message(self, message: Any, **kwargs: Any) -> None: kwargs can contain ATTR_TITLE to specify a title. """ - await self.hass.async_add_executor_job(partial(self.send_message, message, **kwargs)) # type: ignore + await self.hass.async_add_executor_job( + partial(self.send_message, message, **kwargs) + ) async def _async_notify_message_service(self, service: ServiceCall) -> None: """Handle sending notification message service calls.""" @@ -175,8 +181,6 @@ async def async_setup( async def async_register_services(self) -> None: """Create or update the notify services.""" - assert self.hass - if hasattr(self, "targets"): stale_targets = set(self.registered_targets) @@ -232,8 +236,6 @@ async def async_register_services(self) -> None: async def async_unregister_services(self) -> None: """Unregister the notify services.""" - assert self.hass - if self.registered_targets: remove_targets = set(self.registered_targets) for remove_target_name in remove_targets: diff --git a/homeassistant/components/number/__init__.py b/homeassistant/components/number/__init__.py index 31a0bcd776280c..933b07e4f58fce 100644 --- a/homeassistant/components/number/__init__.py +++ b/homeassistant/components/number/__init__.py @@ -110,5 +110,4 @@ def set_value(self, value: float) -> None: async def async_set_value(self, value: float) -> None: """Set new value.""" - assert self.hass is not None await self.hass.async_add_executor_job(self.set_value, value) diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 720f7c5ff166c1..12b81402d616ad 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -173,7 +173,6 @@ def send_command(self, command: Iterable[str], **kwargs: Any) -> None: async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send commands to a device.""" - assert self.hass is not None await self.hass.async_add_executor_job( ft.partial(self.send_command, command, **kwargs) ) @@ -184,7 +183,6 @@ def learn_command(self, **kwargs: Any) -> None: async def async_learn_command(self, **kwargs: Any) -> None: """Learn a command from a device.""" - assert self.hass is not None await self.hass.async_add_executor_job(ft.partial(self.learn_command, **kwargs)) def delete_command(self, **kwargs: Any) -> None: @@ -193,7 +191,6 @@ def delete_command(self, **kwargs: Any) -> None: async def async_delete_command(self, **kwargs: Any) -> None: """Delete commands from the database.""" - assert self.hass is not None await self.hass.async_add_executor_job( ft.partial(self.delete_command, **kwargs) ) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 245d21770ee603..4bc63d585be5de 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -104,7 +104,6 @@ def activate(self, **kwargs: Any) -> None: async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" - assert self.hass task = self.hass.async_add_job(ft.partial(self.activate, **kwargs)) if task: await task diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 2650bd61bfb05c..fc8638162e759d 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -120,13 +120,11 @@ async def async_turn_off(self, **kwargs): async def async_added_to_hass(self) -> None: """Register callbacks.""" - assert self.hass is not None self._switch_state = self.hass.states.get(self._switch_entity_id) @callback def async_state_changed_listener(*_: Any) -> None: """Handle child updates.""" - assert self.hass is not None self._switch_state = self.hass.states.get(self._switch_entity_id) self.async_write_ha_state() diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 9276ec2ed0b3c9..3f6b26f536f80c 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -373,7 +373,6 @@ async def async_set_fan_mode(self, fan_mode: str) -> None: async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - assert self.hass hvac_mode: Optional[str] = kwargs.get(ATTR_HVAC_MODE) if hvac_mode is not None: diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 4929f7e78691d0..8ba2909cf20ed9 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -89,7 +89,6 @@ async def async_step_user( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Handle the initial step.""" - assert self.hass # typing if is_hassio(self.hass): # type: ignore # no-untyped-call return await self.async_step_on_supervisor() @@ -266,7 +265,6 @@ async def async_step_start_addon( self, user_input: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Start Z-Wave JS add-on.""" - assert self.hass if not self.start_task: self.start_task = self.hass.async_create_task(self._async_start_addon()) return self.async_show_progress( @@ -289,7 +287,6 @@ async def async_step_start_failed( async def _async_start_addon(self) -> None: """Start the Z-Wave JS add-on.""" - assert self.hass addon_manager: AddonManager = get_addon_manager(self.hass) try: await addon_manager.async_schedule_start_addon() @@ -327,7 +324,6 @@ async def async_step_finish_addon_setup( Get add-on discovery info and server version info. Set unique id and abort if already configured. """ - assert self.hass if not self.ws_address: discovery_info = await self._async_get_addon_discovery_info() self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 7620323d9405b3..34b5940bfead4f 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -46,7 +46,6 @@ def on_value_update(self) -> None: async def async_poll_value(self, refresh_all_values: bool) -> None: """Poll a value.""" - assert self.hass if not refresh_all_values: self.hass.async_create_task( self.info.node.async_poll_value(self.info.primary_value) @@ -75,7 +74,6 @@ async def async_poll_value(self, refresh_all_values: bool) -> None: async def async_added_to_hass(self) -> None: """Call when entity is added.""" - assert self.hass # typing # Add value_changed callbacks. self.async_on_remove( self.info.node.on(EVENT_VALUE_UPDATED, self._value_changed) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 69248186b53a8a..fea188ca336c33 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -59,7 +59,6 @@ async def async_step_confirm( return self.async_abort(reason="no_devices_found") # Cancel the discovered one. - assert self.hass is not None for flow in in_progress: self.hass.config_entries.flow.async_abort(flow["flow_id"]) @@ -91,7 +90,6 @@ async def async_step_import(self, _: Optional[Dict[str, Any]]) -> Dict[str, Any] return self.async_abort(reason="single_instance_allowed") # Cancel other flows. - assert self.hass is not None in_progress = self._async_in_progress() for flow in in_progress: self.hass.config_entries.flow.async_abort(flow["flow_id"]) @@ -144,7 +142,6 @@ async def async_step_user( if user_input is None: return self.async_show_form(step_id="user") - assert self.hass is not None webhook_id = self.hass.components.webhook.async_generate_id() if ( diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 653d07a333e116..691715452ea1de 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -234,7 +234,6 @@ async def async_step_pick_implementation( self, user_input: Optional[dict] = None ) -> dict: """Handle a flow start.""" - assert self.hass implementations = await async_get_implementations(self.hass, self.DOMAIN) if user_input is not None: @@ -318,7 +317,6 @@ async def async_step_discovery( """Handle a flow initialized by discovery.""" await self.async_set_unique_id(self.DOMAIN) - assert self.hass is not None if self.hass.config_entries.async_entries(self.DOMAIN): return self.async_abort(reason="already_configured") diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7afe1b2ad25293..791b1e251cdfdf 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -89,10 +89,13 @@ 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 = None # type: str + entity_id: str = None # type: ignore # Owning hass instance. Will be set by EntityPlatform - hass: Optional[HomeAssistant] = None + # While not purely typed, it makes typehinting more useful for us + # and removes the need for constant None checks or asserts. + # Ignore types: https://github.com/PyCQA/pylint/issues/3167 + hass: HomeAssistant = None # type: ignore # Owning platform instance. Will be set by EntityPlatform platform: Optional[EntityPlatform] = None @@ -391,7 +394,6 @@ def _async_write_ha_state(self) -> None: ) # Overwrite properties that have been set in the config file. - assert self.hass is not None if DATA_CUSTOMIZE in self.hass.data: attr.update(self.hass.data[DATA_CUSTOMIZE].get(self.entity_id)) @@ -432,7 +434,6 @@ def schedule_update_ha_state(self, force_refresh: bool = False) -> None: If state is changed more than once before the ha state change task has been executed, the intermediate state transitions will be missed. """ - assert self.hass is not None self.hass.add_job(self.async_update_ha_state(force_refresh)) # type: ignore @callback @@ -448,7 +449,6 @@ def async_schedule_update_ha_state(self, force_refresh: bool = False) -> None: been executed, the intermediate state transitions will be missed. """ if force_refresh: - assert self.hass is not None self.hass.async_create_task(self.async_update_ha_state(force_refresh)) else: self.async_write_ha_state() @@ -532,7 +532,7 @@ def add_to_platform_start( @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" - self.hass = None + self.hass = None # type: ignore self.platform = None self.parallel_updates = None self._added = False @@ -553,8 +553,6 @@ async def async_remove(self, *, force_remove: bool = False) -> None: If the entity doesn't have a non disabled entry in the entity registry, or if force_remove=True, its state will be removed. """ - assert self.hass is not None - if self.platform and not self._added: raise HomeAssistantError( f"Entity {self.entity_id} async_remove called twice" @@ -597,8 +595,6 @@ async def async_internal_added_to_hass(self) -> None: Not to be extended by integrations. """ - assert self.hass is not None - if self.platform: info = {"domain": self.platform.platform_name} @@ -628,7 +624,6 @@ async def async_internal_will_remove_from_hass(self) -> None: Not to be extended by integrations. """ if self.platform: - assert self.hass is not None self.hass.data[DATA_ENTITY_SOURCE].pop(self.entity_id) async def _async_registry_updated(self, event: Event) -> None: @@ -642,7 +637,6 @@ async def _async_registry_updated(self, event: Event) -> None: if data["action"] != "update": return - assert self.hass is not None ent_reg = await self.hass.helpers.entity_registry.async_get_registry() old = self.registry_entry self.registry_entry = ent_reg.async_get(data["entity_id"]) @@ -717,7 +711,6 @@ def turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - assert self.hass is not None await self.hass.async_add_executor_job(ft.partial(self.turn_on, **kwargs)) def turn_off(self, **kwargs: Any) -> None: @@ -726,7 +719,6 @@ def turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - assert self.hass is not None await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs)) def toggle(self, **kwargs: Any) -> None: diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 4f738887ce376d..51ebc6fa774d75 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -235,7 +235,6 @@ class RestoreEntity(Entity): async def async_internal_added_to_hass(self) -> None: """Register this entity as a restorable entity.""" - assert self.hass is not None _, data = await asyncio.gather( super().async_internal_added_to_hass(), RestoreStateData.async_get_instance(self.hass), @@ -244,7 +243,6 @@ async def async_internal_added_to_hass(self) -> None: async def async_internal_will_remove_from_hass(self) -> None: """Run when entity will be removed from hass.""" - assert self.hass is not None _, data = await asyncio.gather( super().async_internal_will_remove_from_hass(), RestoreStateData.async_get_instance(self.hass), @@ -255,7 +253,7 @@ async def async_get_last_state(self) -> Optional[State]: """Get the entity state from the previous run.""" 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") + _LOGGER.warning("Cannot get last state. Entity not added to hass") # type: ignore[unreachable] return None data = await RestoreStateData.async_get_instance(self.hass) if self.entity_id not in data.last_states: diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 8b3f6ec6e59ba5..7f768354035f72 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -425,8 +425,6 @@ async def async_render_will_timeout( This method must be run in the event loop. """ - assert self.hass - if self.is_static: return False From 8239fb76d14aef5c3298cdad67d67a205c4f65c5 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 15 Mar 2021 16:42:28 +0100 Subject: [PATCH 1275/1818] Bump brother library (#47949) --- homeassistant/components/brother/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 15828e5f05a35f..13933b7bf60460 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -3,7 +3,7 @@ "name": "Brother Printer", "documentation": "https://www.home-assistant.io/integrations/brother", "codeowners": ["@bieniu"], - "requirements": ["brother==0.2.1"], + "requirements": ["brother==0.2.2"], "zeroconf": [{ "type": "_printer._tcp.local.", "name": "brother*" }], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 87cde66dd571ec..d1e27d28076eb8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -384,7 +384,7 @@ bravia-tv==1.0.8 broadlink==0.17.0 # homeassistant.components.brother -brother==0.2.1 +brother==0.2.2 # homeassistant.components.brottsplatskartan brottsplatskartan==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1721ef8c2b5d21..836f9762a067c7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -214,7 +214,7 @@ bravia-tv==1.0.8 broadlink==0.17.0 # homeassistant.components.brother -brother==0.2.1 +brother==0.2.2 # homeassistant.components.bsblan bsblan==0.4.0 From 93c38551d3bf6381cb4d3d5cdc34189fc3de7830 Mon Sep 17 00:00:00 2001 From: Nathan Tilley Date: Mon, 15 Mar 2021 11:20:47 -0500 Subject: [PATCH 1276/1818] Implement Wake On Lan Dummy State (#47719) Co-authored-by: Martin Hjelmare --- CODEOWNERS | 1 + .../components/wake_on_lan/manifest.json | 2 +- .../components/wake_on_lan/switch.py | 23 ++++++++++- tests/components/wake_on_lan/test_switch.py | 41 +++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index bece933d9c8f80..263e5337c588b5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -521,6 +521,7 @@ homeassistant/components/vizio/* @raman325 homeassistant/components/vlc_telnet/* @rodripf @dmcc homeassistant/components/volkszaehler/* @fabaff homeassistant/components/volumio/* @OnFreund +homeassistant/components/wake_on_lan/* @ntilley905 homeassistant/components/waqi/* @andrey-git homeassistant/components/watson_tts/* @rutkai homeassistant/components/weather/* @fabaff diff --git a/homeassistant/components/wake_on_lan/manifest.json b/homeassistant/components/wake_on_lan/manifest.json index c66f87ae26ed07..bcd7ef58c8cd30 100644 --- a/homeassistant/components/wake_on_lan/manifest.json +++ b/homeassistant/components/wake_on_lan/manifest.json @@ -3,5 +3,5 @@ "name": "Wake on LAN", "documentation": "https://www.home-assistant.io/integrations/wake_on_lan", "requirements": ["wakeonlan==1.1.6"], - "codeowners": [] + "codeowners": ["@ntilley905"] } diff --git a/homeassistant/components/wake_on_lan/switch.py b/homeassistant/components/wake_on_lan/switch.py index 491bae782c52ef..eba6897647b595 100644 --- a/homeassistant/components/wake_on_lan/switch.py +++ b/homeassistant/components/wake_on_lan/switch.py @@ -57,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): broadcast_port, ) ], - True, + host is not None, ) @@ -86,6 +86,7 @@ def __init__( Script(hass, off_action, name, domain) if off_action else None ) self._state = False + self._assumed_state = host is None @property def is_on(self): @@ -97,6 +98,16 @@ def name(self): """Return the name of the switch.""" return self._name + @property + def assumed_state(self): + """Return true if no host is provided.""" + return self._assumed_state + + @property + def should_poll(self): + """Return false if assumed state is true.""" + return not self._assumed_state + def turn_on(self, **kwargs): """Turn the device on.""" service_kwargs = {} @@ -114,13 +125,21 @@ def turn_on(self, **kwargs): wakeonlan.send_magic_packet(self._mac_address, **service_kwargs) + if self._assumed_state: + self._state = True + self.async_write_ha_state() + def turn_off(self, **kwargs): """Turn the device off if an off action is present.""" if self._off_script is not None: self._off_script.run(context=self._context) + if self._assumed_state: + self._state = False + self.async_write_ha_state() + def update(self): - """Check if device is on and update the state.""" + """Check if device is on and update the state. Only called if assumed state is false.""" if platform.system().lower() == "windows": ping_cmd = [ "ping", diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index c2e32f77ccffe7..7b41fd4d75c599 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -275,3 +275,44 @@ async def test_invalid_hostname_windows(hass): state = hass.states.get("switch.wake_on_lan") assert STATE_OFF == state.state + + +async def test_no_hostname_state(hass): + """Test that the state updates if we do not pass in a hostname.""" + + assert await async_setup_component( + hass, + switch.DOMAIN, + { + "switch": { + "platform": "wake_on_lan", + "mac": "00-01-02-03-04-05", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_OFF + + with patch.object(subprocess, "call", return_value=0): + + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) + + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_ON + + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) + + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_OFF From 9e05aa2d1f91beb67ddf52266fa6e07081babadf Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Mon, 15 Mar 2021 18:20:10 +0100 Subject: [PATCH 1277/1818] Update state translation strings for water_heater (#46588) --- homeassistant/components/water_heater/strings.json | 11 +++++++++++ .../components/water_heater/translations/en.json | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/homeassistant/components/water_heater/strings.json b/homeassistant/components/water_heater/strings.json index 8f5709ac155cae..71fc1dc9328629 100644 --- a/homeassistant/components/water_heater/strings.json +++ b/homeassistant/components/water_heater/strings.json @@ -4,5 +4,16 @@ "turn_on": "Turn on {entity_name}", "turn_off": "Turn off {entity_name}" } + }, + "state": { + "_": { + "off": "[%key:common::state::off%]", + "eco": "Eco", + "electric": "Electric", + "gas": "Gas", + "high_demand": "High Demand", + "heat_pump": "Heat Pump", + "performance": "Performance" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/en.json b/homeassistant/components/water_heater/translations/en.json index d6abfbb995d3ca..6885fcb198e8af 100644 --- a/homeassistant/components/water_heater/translations/en.json +++ b/homeassistant/components/water_heater/translations/en.json @@ -4,5 +4,16 @@ "turn_off": "Turn off {entity_name}", "turn_on": "Turn on {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "Electric", + "gas": "Gas", + "heat_pump": "Heat Pump", + "high_demand": "High Demand", + "off": "Off", + "performance": "Performance" + } } } \ No newline at end of file From 28c80c11331eaafbac7efc6e39360ddc06954d3e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 15 Mar 2021 08:19:19 -1000 Subject: [PATCH 1278/1818] Ensure recorder purge tests can handle multiple purge cycle (#47956) Since a purge can generate another purge task, we now wait for three recorder queue completions by default. --- tests/components/recorder/common.py | 18 +++++++++++++ tests/components/recorder/test_purge.py | 35 +++++++++++++++---------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index 79f0f1f00d0a45..0bc4cfbfeb9430 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -8,6 +8,8 @@ from tests.common import async_fire_time_changed, fire_time_changed +DEFAULT_PURGE_TASKS = 3 + def wait_recording_done(hass: HomeAssistantType) -> None: """Block till recording is done.""" @@ -42,6 +44,22 @@ async def async_wait_recording_done( await hass.async_block_till_done() +async def async_wait_purge_done( + hass: HomeAssistantType, instance: recorder.Recorder, max: int = None +) -> None: + """Wait for max number of purge events. + + Because a purge may insert another PurgeTask into + the queue after the WaitTask finishes, we need up to + a maximum number of WaitTasks that we will put into the + queue. + """ + if not max: + max = DEFAULT_PURGE_TASKS + for _ in range(max + 1): + await async_wait_recording_done(hass, instance) + + @ha.callback def async_trigger_db_commit(hass: HomeAssistantType) -> None: """Fore the recorder to commit. Async friendly.""" diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index e6bc3a99a97989..bbc16cef82562b 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -12,7 +12,11 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import dt as dt_util -from .common import async_recorder_block_till_done, async_wait_recording_done +from .common import ( + async_recorder_block_till_done, + async_wait_purge_done, + async_wait_recording_done, +) from .conftest import SetupRecorderInstanceT @@ -120,12 +124,15 @@ async def test_purge_method( assert recorder_runs.count() == 7 runs_before_purge = recorder_runs.all() + await hass.async_block_till_done() + await async_wait_purge_done(hass, instance) + # run purge method - no service data, use defaults await hass.services.async_call("recorder", "purge") await hass.async_block_till_done() # Small wait for recorder thread - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) # only purged old events assert states.count() == 4 @@ -136,7 +143,7 @@ async def test_purge_method( await hass.async_block_till_done() # Small wait for recorder thread - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) # we should only have 2 states left after purging assert states.count() == 2 @@ -156,7 +163,7 @@ async def test_purge_method( service_data["repack"] = True await hass.services.async_call("recorder", "purge", service_data=service_data) await hass.async_block_till_done() - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert "Vacuuming SQL DB to free space" in caplog.text @@ -192,7 +199,7 @@ async def _add_db_entries(hass: HomeAssistantType, timestamp: datetime) -> None: ) instance = await async_setup_recorder_instance(hass, None) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) service_data = {"keep_days": 2} timestamp = dt_util.utcnow() - timedelta(days=2, minutes=1) @@ -211,7 +218,7 @@ async def _add_db_entries(hass: HomeAssistantType, timestamp: datetime) -> None: await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert states.count() == 0 assert events.count() == 0 @@ -329,7 +336,7 @@ def _add_db_entries(hass: HomeAssistantType) -> None: await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert states.count() == 74 assert events_state_changed.count() == 70 @@ -343,10 +350,10 @@ def _add_db_entries(hass: HomeAssistantType) -> None: await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert states.count() == 13 assert events_state_changed.count() == 10 @@ -419,7 +426,7 @@ def _add_db_entries(hass: HomeAssistantType) -> None: await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert events_purge.count() == 60 assert events_keep.count() == 10 @@ -433,10 +440,10 @@ def _add_db_entries(hass: HomeAssistantType) -> None: await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert events_purge.count() == 0 assert events_keep.count() == 10 @@ -534,10 +541,10 @@ def _add_db_entries(hass: HomeAssistantType) -> None: await hass.async_block_till_done() await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) await async_recorder_block_till_done(hass, instance) - await async_wait_recording_done(hass, instance) + await async_wait_purge_done(hass, instance) assert events_keep.count() == 10 assert events_purge.count() == 0 From 8b3dccb1b46f5ce8c42db875631986ff2f12d9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 15 Mar 2021 19:31:34 +0100 Subject: [PATCH 1279/1818] Use ClientTimeout for hassio send_command (#47957) --- homeassistant/components/hassio/handler.py | 36 ++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 6bc3cb345a5167..303d6770255588 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -4,7 +4,6 @@ import os import aiohttp -import async_timeout from homeassistant.components.http import ( CONF_SERVER_HOST, @@ -52,7 +51,12 @@ async def _wrapper(*argv, **kwargs): class HassIO: """Small API wrapper for Hass.io.""" - def __init__(self, loop, websession, ip): + def __init__( + self, + loop: asyncio.AbstractEventLoop, + websession: aiohttp.ClientSession, + ip: str, + ) -> None: """Initialize Hass.io API.""" self.loop = loop self.websession = websession @@ -187,20 +191,20 @@ async def send_command(self, command, method="post", payload=None, timeout=10): This method is a coroutine. """ try: - with async_timeout.timeout(timeout): - request = await self.websession.request( - method, - f"http://{self._ip}{command}", - json=payload, - headers={X_HASSIO: os.environ.get("HASSIO_TOKEN", "")}, - ) - - if request.status not in (HTTP_OK, HTTP_BAD_REQUEST): - _LOGGER.error("%s return code %d", command, request.status) - raise HassioAPIError() - - answer = await request.json() - return answer + request = await self.websession.request( + method, + f"http://{self._ip}{command}", + json=payload, + headers={X_HASSIO: os.environ.get("HASSIO_TOKEN", "")}, + timeout=aiohttp.ClientTimeout(total=timeout), + ) + + if request.status not in (HTTP_OK, HTTP_BAD_REQUEST): + _LOGGER.error("%s return code %d", command, request.status) + raise HassioAPIError() + + answer = await request.json() + return answer except asyncio.TimeoutError: _LOGGER.error("Timeout on %s request", command) From 07c197687f0c0748d9ebfe7fd14b4e27cfb5b00c Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 15 Mar 2021 19:42:13 +0100 Subject: [PATCH 1280/1818] improve debug logging (#47858) --- .../components/synology_dsm/__init__.py | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 88654f02b21d11..002538061c87c0 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -191,7 +191,9 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): try: await api.async_setup() except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.debug("Unable to connect to DSM during setup: %s", err) + _LOGGER.debug( + "Unable to connect to DSM '%s' during setup: %s", entry.unique_id, err + ) raise ConfigEntryNotReady from err hass.data.setdefault(DOMAIN, {}) @@ -401,7 +403,8 @@ async def async_setup(self): self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( - "State of Surveillance_station during setup:%s", + "State of Surveillance_station during setup of '%s': %s", + self._entry.unique_id, self._with_surveillance_station, ) @@ -413,9 +416,7 @@ async def async_setup(self): @callback def subscribe(self, api_key, unique_id): """Subscribe an entity to API fetches.""" - _LOGGER.debug( - "Subscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id - ) + _LOGGER.debug("Subscribe new entity: %s", unique_id) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @@ -423,9 +424,7 @@ def subscribe(self, api_key, unique_id): @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" - _LOGGER.debug( - "Unsubscribe new entity - api_key:%s, unique_id:%s", api_key, unique_id - ) + _LOGGER.debug("Unsubscribe entity: %s", unique_id) self._fetching_entities[api_key].remove(unique_id) if len(self._fetching_entities[api_key]) == 0: self._fetching_entities.pop(api_key) @@ -437,7 +436,9 @@ def _async_setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: - _LOGGER.debug("Entities not added yet, fetch all") + _LOGGER.debug( + "Entities not added yet, fetch all for '%s'", self._entry.unique_id + ) return # Determine if we should fetch an API @@ -460,32 +461,47 @@ def _async_setup_api_requests(self): # Reset not used API, information is not reset since it's used in device_info if not self._with_security: - _LOGGER.debug("Disable security api from being updated") + _LOGGER.debug( + "Disable security api from being updated for '%s'", + self._entry.unique_id, + ) self.dsm.reset(self.security) self.security = None if not self._with_storage: - _LOGGER.debug("Disable storage api from being updated") + _LOGGER.debug( + "Disable storage api from being updatedf or '%s'", self._entry.unique_id + ) self.dsm.reset(self.storage) self.storage = None if not self._with_system: - _LOGGER.debug("Disable system api from being updated") + _LOGGER.debug( + "Disable system api from being updated for '%s'", self._entry.unique_id + ) self.dsm.reset(self.system) self.system = None if not self._with_upgrade: - _LOGGER.debug("Disable upgrade api from being updated") + _LOGGER.debug( + "Disable upgrade api from being updated for '%s'", self._entry.unique_id + ) self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: - _LOGGER.debug("Disable utilisation api from being updated") + _LOGGER.debug( + "Disable utilisation api from being updated for '%s'", + self._entry.unique_id, + ) self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: - _LOGGER.debug("Disable surveillance_station api from being updated") + _LOGGER.debug( + "Disable surveillance_station api from being updated for '%s'", + self._entry.unique_id, + ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -496,27 +512,32 @@ def _fetch_device_configuration(self): self.network.update() if self._with_security: - _LOGGER.debug("Enable security api for updates") + _LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id) self.security = self.dsm.security if self._with_storage: - _LOGGER.debug("Enable storage api for updates") + _LOGGER.debug("Enable storage api updates for '%s'", self._entry.unique_id) self.storage = self.dsm.storage if self._with_upgrade: - _LOGGER.debug("Enable upgrade api for updates") + _LOGGER.debug("Enable upgrade api updates for '%s'", self._entry.unique_id) self.upgrade = self.dsm.upgrade if self._with_system: - _LOGGER.debug("Enable system api for updates") + _LOGGER.debug("Enable system api updates for '%s'", self._entry.unique_id) self.system = self.dsm.system if self._with_utilisation: - _LOGGER.debug("Enable utilisation api for updates") + _LOGGER.debug( + "Enable utilisation api updates for '%s'", self._entry.unique_id + ) self.utilisation = self.dsm.utilisation if self._with_surveillance_station: - _LOGGER.debug("Enable surveillance_station api for updates") + _LOGGER.debug( + "Enable surveillance_station api updates for '%s'", + self._entry.unique_id, + ) self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): @@ -524,7 +545,10 @@ async def async_reboot(self): try: await self._hass.async_add_executor_job(self.system.reboot) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.error("Reboot not possible, please try again later") + _LOGGER.error( + "Reboot of '%s' not possible, please try again later", + self._entry.unique_id, + ) _LOGGER.debug("Exception:%s", err) async def async_shutdown(self): @@ -532,7 +556,10 @@ async def async_shutdown(self): try: await self._hass.async_add_executor_job(self.system.shutdown) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: - _LOGGER.error("Shutdown not possible, please try again later") + _LOGGER.error( + "Shutdown of '%s' not possible, please try again later", + self._entry.unique_id, + ) _LOGGER.debug("Exception:%s", err) async def async_unload(self): @@ -540,11 +567,13 @@ async def async_unload(self): try: await self._hass.async_add_executor_job(self.dsm.logout) except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: - _LOGGER.debug("Logout not possible:%s", err) + _LOGGER.debug( + "Logout from '%s' not possible:%s", self._entry.unique_id, err + ) async def async_update(self, now=None): """Update function for updating API information.""" - _LOGGER.debug("Start data update") + _LOGGER.debug("Start data update for '%s'", self._entry.unique_id) self._async_setup_api_requests() try: await self._hass.async_add_executor_job( @@ -554,7 +583,11 @@ async def async_update(self, now=None): _LOGGER.warning( "Connection error during update, fallback by reloading the entry" ) - _LOGGER.debug("Connection error during update with exception: %s", err) + _LOGGER.debug( + "Connection error during update of '%s' with exception: %s", + self._entry.unique_id, + err, + ) await self._hass.config_entries.async_reload(self._entry.entry_id) return From 9f4c2f6260ad73c01f058b35503e39ea10c630b6 Mon Sep 17 00:00:00 2001 From: RadekHvizdos <10856567+RadekHvizdos@users.noreply.github.com> Date: Mon, 15 Mar 2021 20:02:02 +0100 Subject: [PATCH 1281/1818] Add suggested_area to MQTT discovery (#47903) * Add suggested_area to MQTT Discovery This adds suggested_area to MQTT discovery, so that the discovered devices could be automatically added to the proper area. * Add abbreviation for MQTT suggested_area * Remove extra whitespace * Remove extra whitespace #2 * Added tests for MQTT Dicovery of suggested_area * Fix test for MQTT suggested_area * Better tests of MQTT suggested_area Changes made as per feedback from @emontnemery --- homeassistant/components/mqtt/abbreviations.py | 1 + homeassistant/components/mqtt/mixins.py | 5 +++++ tests/components/mqtt/test_common.py | 4 ++++ tests/components/mqtt/test_discovery.py | 3 ++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 8868d487f934be..868e2fdd791354 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -203,4 +203,5 @@ "mf": "manufacturer", "mdl": "model", "sw": "sw_version", + "sa": "suggested_area", } diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 898072de5f9e47..5737dad255c0f9 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -63,6 +63,7 @@ CONF_SW_VERSION = "sw_version" CONF_VIA_DEVICE = "via_device" CONF_DEPRECATED_VIA_HUB = "via_hub" +CONF_SUGGESTED_AREA = "suggested_area" MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema( { @@ -129,6 +130,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_SW_VERSION): cv.string, vol.Optional(CONF_VIA_DEVICE): cv.string, + vol.Optional(CONF_SUGGESTED_AREA): cv.string, } ), validate_device_has_at_least_one_identifier, @@ -491,6 +493,9 @@ def device_info_from_config(config): if CONF_VIA_DEVICE in config: info["via_device"] = (DOMAIN, config[CONF_VIA_DEVICE]) + if CONF_SUGGESTED_AREA in config: + info["suggested_area"] = config[CONF_SUGGESTED_AREA] + return info diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index f1243918e4ee14..43f27373a3e52e 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -20,6 +20,7 @@ "name": "Beer", "model": "Glass", "sw_version": "0.1-beta", + "suggested_area": "default_area", } DEFAULT_CONFIG_DEVICE_INFO_MAC = { @@ -28,6 +29,7 @@ "name": "Beer", "model": "Glass", "sw_version": "0.1-beta", + "suggested_area": "default_area", } @@ -739,6 +741,7 @@ async def help_test_entity_device_info_with_identifier(hass, mqtt_mock, domain, assert device.name == "Beer" assert device.model == "Glass" assert device.sw_version == "0.1-beta" + assert device.suggested_area == "default_area" async def help_test_entity_device_info_with_connection(hass, mqtt_mock, domain, config): @@ -764,6 +767,7 @@ async def help_test_entity_device_info_with_connection(hass, mqtt_mock, domain, assert device.name == "Beer" assert device.model == "Glass" assert device.sw_version == "0.1-beta" + assert device.suggested_area == "default_area" async def help_test_entity_device_info_remove(hass, mqtt_mock, domain, config): diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index fed0dfa54d6f73..d55c8e0eccc73b 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -442,7 +442,8 @@ async def test_discovery_expansion(hass, mqtt_mock, caplog): ' "name":"DiscoveryExpansionTest1 Device",' ' "mdl":"Generic",' ' "sw":"1.2.3.4",' - ' "mf":"None"' + ' "mf":"None",' + ' "sa":"default_area"' " }" "}" ) From 059e9e830727c245ce7f3ac64d065ec3f6501b85 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 20:30:44 +0100 Subject: [PATCH 1282/1818] Add config flow to Verisure (#47880) Co-authored-by: Paulus Schoutsen --- .coveragerc | 9 +- homeassistant/components/verisure/__init__.py | 157 ++++-- .../verisure/alarm_control_panel.py | 35 +- .../components/verisure/binary_sensor.py | 32 +- homeassistant/components/verisure/camera.py | 27 +- .../components/verisure/config_flow.py | 186 +++++++ homeassistant/components/verisure/const.py | 18 +- .../components/verisure/coordinator.py | 34 +- homeassistant/components/verisure/lock.py | 50 +- .../components/verisure/manifest.json | 4 +- homeassistant/components/verisure/sensor.py | 64 +-- .../components/verisure/strings.json | 39 ++ homeassistant/components/verisure/switch.py | 34 +- .../components/verisure/translations/en.json | 39 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/dhcp.py | 4 + requirements_test_all.txt | 3 + tests/components/verisure/__init__.py | 1 + tests/components/verisure/test_config_flow.py | 467 ++++++++++++++++++ 19 files changed, 1000 insertions(+), 204 deletions(-) create mode 100644 homeassistant/components/verisure/config_flow.py create mode 100644 homeassistant/components/verisure/strings.json create mode 100644 homeassistant/components/verisure/translations/en.json create mode 100644 tests/components/verisure/__init__.py create mode 100644 tests/components/verisure/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 3c6bf16f6ba1b3..d6bdfb9b09181e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1064,7 +1064,14 @@ omit = homeassistant/components/velbus/switch.py homeassistant/components/velux/* homeassistant/components/venstar/climate.py - homeassistant/components/verisure/* + homeassistant/components/verisure/__init__.py + homeassistant/components/verisure/alarm_control_panel.py + homeassistant/components/verisure/binary_sensor.py + homeassistant/components/verisure/camera.py + homeassistant/components/verisure/coordinator.py + homeassistant/components/verisure/lock.py + homeassistant/components/verisure/sensor.py + homeassistant/components/verisure/switch.py homeassistant/components/versasense/* homeassistant/components/vesync/__init__.py homeassistant/components/vesync/common.py diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 16250915f457ec..e34bf5c5650030 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,6 +1,10 @@ """Support for Verisure devices.""" from __future__ import annotations +import asyncio +import os +from typing import Any + from verisure import Error as VerisureError import voluptuous as vol @@ -12,34 +16,28 @@ from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( + CONF_EMAIL, CONF_PASSWORD, - CONF_SCAN_INTERVAL, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.storage import STORAGE_DIR from .const import ( ATTR_DEVICE_SERIAL, - CONF_ALARM, CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, - CONF_DOOR_WINDOW, CONF_GIID, - CONF_HYDROMETERS, - CONF_LOCKS, - CONF_MOUSE, - CONF_SMARTCAM, - CONF_SMARTPLUGS, - CONF_THERMOMETERS, - DEFAULT_SCAN_INTERVAL, + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, DOMAIN, LOGGER, - MIN_SCAN_INTERVAL, SERVICE_CAPTURE_SMARTCAM, SERVICE_DISABLE_AUTOLOCK, SERVICE_ENABLE_AUTOLOCK, @@ -56,54 +54,101 @@ ] CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_ALARM, default=True): cv.boolean, - vol.Optional(CONF_CODE_DIGITS, default=4): cv.positive_int, - vol.Optional(CONF_DOOR_WINDOW, default=True): cv.boolean, - vol.Optional(CONF_GIID): cv.string, - vol.Optional(CONF_HYDROMETERS, default=True): cv.boolean, - vol.Optional(CONF_LOCKS, default=True): cv.boolean, - vol.Optional(CONF_DEFAULT_LOCK_CODE): cv.string, - vol.Optional(CONF_MOUSE, default=True): cv.boolean, - vol.Optional(CONF_SMARTPLUGS, default=True): cv.boolean, - vol.Optional(CONF_THERMOMETERS, default=True): cv.boolean, - vol.Optional(CONF_SMARTCAM, default=True): cv.boolean, - vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): ( - vol.All(cv.time_period, vol.Clamp(min=MIN_SCAN_INTERVAL)) - ), - } - ) - }, + vol.All( + cv.deprecated(DOMAIN), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_CODE_DIGITS): cv.positive_int, + vol.Optional(CONF_GIID): cv.string, + vol.Optional(CONF_DEFAULT_LOCK_CODE): cv.string, + }, + extra=vol.ALLOW_EXTRA, + ) + }, + ), extra=vol.ALLOW_EXTRA, ) + DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the Verisure integration.""" - coordinator = VerisureDataUpdateCoordinator(hass, config=config[DOMAIN]) + if DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_EMAIL: config[DOMAIN][CONF_USERNAME], + CONF_PASSWORD: config[DOMAIN][CONF_PASSWORD], + CONF_GIID: config[DOMAIN].get(CONF_GIID), + CONF_LOCK_CODE_DIGITS: config[DOMAIN].get(CONF_CODE_DIGITS), + CONF_LOCK_DEFAULT_CODE: config[DOMAIN].get(CONF_LOCK_DEFAULT_CODE), + }, + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Verisure from a config entry.""" + # Migrate old YAML settings (hidden in the config entry), + # to config entry options. Can be removed after YAML support is gone. + if CONF_LOCK_CODE_DIGITS in entry.data or CONF_DEFAULT_LOCK_CODE in entry.data: + options = entry.options.copy() + + if ( + CONF_LOCK_CODE_DIGITS in entry.data + and CONF_LOCK_CODE_DIGITS not in entry.options + and entry.data[CONF_LOCK_CODE_DIGITS] != DEFAULT_LOCK_CODE_DIGITS + ): + options.update( + { + CONF_LOCK_CODE_DIGITS: entry.data[CONF_LOCK_CODE_DIGITS], + } + ) + + if ( + CONF_DEFAULT_LOCK_CODE in entry.data + and CONF_DEFAULT_LOCK_CODE not in entry.options + ): + options.update( + { + CONF_DEFAULT_LOCK_CODE: entry.data[CONF_DEFAULT_LOCK_CODE], + } + ) + + data = entry.data.copy() + data.pop(CONF_LOCK_CODE_DIGITS, None) + data.pop(CONF_DEFAULT_LOCK_CODE, None) + hass.config_entries.async_update_entry(entry, data=data, options=options) + + # Continue as normal... + coordinator = VerisureDataUpdateCoordinator(hass, entry=entry) if not await coordinator.async_login(): - LOGGER.error("Login failed") + LOGGER.error("Could not login to Verisure, aborting setting up integration") return False hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) await coordinator.async_refresh() if not coordinator.last_update_success: - LOGGER.error("Update failed") - return False + raise ConfigEntryNotReady - hass.data[DOMAIN] = coordinator + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = coordinator + # Set up all platforms for this device/entry. for platform in PLATFORMS: hass.async_create_task( - discovery.async_load_platform(hass, platform, DOMAIN, {}, config) + hass.config_entries.async_forward_entry_setup(entry, platform) ) async def capture_smartcam(service): @@ -145,3 +190,31 @@ async def enable_autolock(service): DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA ) return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Verisure config entry.""" + unload_ok = all( + await asyncio.gather( + *( + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + ) + ) + ) + + if not unload_ok: + return False + + cookie_file = hass.config.path(STORAGE_DIR, f"verisure_{entry.entry_id}") + try: + await hass.async_add_executor_job(os.unlink, cookie_file) + except FileNotFoundError: + pass + + del hass.data[DOMAIN][entry.entry_id] + + if not hass.data[DOMAIN]: + del hass.data[DOMAIN] + + return True diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 94fbfe69bd0dbb..8a2a05e2005c35 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, @@ -12,6 +12,7 @@ SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, @@ -22,22 +23,17 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_ALARM, CONF_GIID, DOMAIN, LOGGER +from .const import CONF_GIID, DOMAIN, LOGGER from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[Entity], bool], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure platform.""" - coordinator = hass.data[DOMAIN] - alarms = [] - if int(coordinator.config.get(CONF_ALARM, 1)): - alarms.append(VerisureAlarm(coordinator)) - add_entities(alarms) + """Set up Verisure alarm control panel from a config entry.""" + async_add_entities([VerisureAlarm(coordinator=hass.data[DOMAIN][entry.entry_id])]) class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): @@ -53,17 +49,12 @@ def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None: @property def name(self) -> str: """Return the name of the device.""" - giid = self.coordinator.config.get(CONF_GIID) - if giid is not None: - aliass = { - i["giid"]: i["alias"] for i in self.coordinator.verisure.installations - } - if giid in aliass: - return "{} alarm".format(aliass[giid]) + return "Verisure Alarm" - LOGGER.error("Verisure installation giid not found: %s", giid) - - return "{} alarm".format(self.coordinator.verisure.installations[0]["alias"]) + @property + def unique_id(self) -> str: + """Return the unique ID for this alarm control panel.""" + return self.coordinator.entry.data[CONF_GIID] @property def state(self) -> str | None: diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 66eb5031072311..4289a6f8ffc14c 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,40 +1,38 @@ """Support for Verisure binary sensors.""" from __future__ import annotations -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_OPENING, BinarySensorEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import CONF_DOOR_WINDOW, DOMAIN +from . import DOMAIN from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[CoordinatorEntity]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure binary sensors.""" - coordinator = hass.data[DOMAIN] + """Set up Verisure sensors based on a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - sensors: list[CoordinatorEntity] = [VerisureEthernetStatus(coordinator)] + sensors: list[Entity] = [VerisureEthernetStatus(coordinator)] - if int(coordinator.config.get(CONF_DOOR_WINDOW, 1)): - sensors.extend( - [ - VerisureDoorWindowSensor(coordinator, serial_number) - for serial_number in coordinator.data["door_window"] - ] - ) + sensors.extend( + VerisureDoorWindowSensor(coordinator, serial_number) + for serial_number in coordinator.data["door_window"] + ) - add_entities(sensors) + async_add_entities(sensors) class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity): diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 6f22b17b848523..ee9fe6577de026 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -3,34 +3,31 @@ import errno import os -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.camera import Camera +from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_SMARTCAM, DOMAIN, LOGGER +from .const import DOMAIN, LOGGER from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[VerisureSmartcam]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure Camera.""" - coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN] - if not int(coordinator.config.get(CONF_SMARTCAM, 1)): - return + """Set up Verisure sensors based on a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] assert hass.config.config_dir - add_entities( - [ - VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) - for serial_number in coordinator.data["cameras"] - ] + async_add_entities( + VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) + for serial_number in coordinator.data["cameras"] ) diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py new file mode 100644 index 00000000000000..ce9c76874f1554 --- /dev/null +++ b/homeassistant/components/verisure/config_flow.py @@ -0,0 +1,186 @@ +"""Config flow for Verisure integration.""" +from __future__ import annotations + +from typing import Any + +from verisure import ( + Error as VerisureError, + LoginError as VerisureLoginError, + ResponseError as VerisureResponseError, + Session as Verisure, +) +import voluptuous as vol + +from homeassistant.config_entries import ( + CONN_CLASS_CLOUD_POLL, + ConfigEntry, + ConfigFlow, + OptionsFlow, +) +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.core import callback + +from .const import ( # pylint:disable=unused-import + CONF_GIID, + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, + DOMAIN, + LOGGER, +) + + +class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Verisure.""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL + + installations: dict[str, str] + email: str + password: str + + # These can be removed after YAML import has been removed. + giid: str | None = None + settings: dict[str, int | str] + + def __init__(self): + """Initialize.""" + self.settings = {} + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> VerisureOptionsFlowHandler: + """Get the options flow for this handler.""" + return VerisureOptionsFlowHandler(config_entry) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Handle the initial step.""" + errors: dict[str, str] = {} + + if user_input is not None: + verisure = Verisure( + username=user_input[CONF_EMAIL], password=user_input[CONF_PASSWORD] + ) + try: + await self.hass.async_add_executor_job(verisure.login) + except VerisureLoginError as ex: + LOGGER.debug("Could not log in to Verisure, %s", ex) + errors["base"] = "invalid_auth" + except (VerisureError, VerisureResponseError) as ex: + LOGGER.debug("Unexpected response from Verisure, %s", ex) + errors["base"] = "unknown" + else: + self.email = user_input[CONF_EMAIL] + self.password = user_input[CONF_PASSWORD] + self.installations = { + inst["giid"]: f"{inst['alias']} ({inst['street']})" + for inst in verisure.installations + } + + return await self.async_step_installation() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + + async def async_step_installation( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Select Verisure installation to add.""" + if len(self.installations) == 1: + user_input = {CONF_GIID: list(self.installations)[0]} + elif self.giid and self.giid in self.installations: + user_input = {CONF_GIID: self.giid} + + if user_input is None: + return self.async_show_form( + step_id="installation", + data_schema=vol.Schema( + {vol.Required(CONF_GIID): vol.In(self.installations)} + ), + ) + + await self.async_set_unique_id(user_input[CONF_GIID]) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=self.installations[user_input[CONF_GIID]], + data={ + CONF_EMAIL: self.email, + CONF_PASSWORD: self.password, + CONF_GIID: user_input[CONF_GIID], + **self.settings, + }, + ) + + async def async_step_import(self, user_input: dict[str, Any]) -> dict[str, Any]: + """Import Verisure YAML configuration.""" + if user_input[CONF_GIID]: + self.giid = user_input[CONF_GIID] + await self.async_set_unique_id(self.giid) + self._abort_if_unique_id_configured() + else: + # The old YAML configuration could handle 1 single Verisure instance. + # Therefore, if we don't know the GIID, we can use the discovery + # without a unique ID logic, to prevent re-import/discovery. + await self._async_handle_discovery_without_unique_id() + + # Settings, later to be converted to config entry options + if user_input[CONF_LOCK_CODE_DIGITS]: + self.settings[CONF_LOCK_CODE_DIGITS] = user_input[CONF_LOCK_CODE_DIGITS] + if user_input[CONF_LOCK_DEFAULT_CODE]: + self.settings[CONF_LOCK_DEFAULT_CODE] = user_input[CONF_LOCK_DEFAULT_CODE] + + return await self.async_step_user(user_input) + + +class VerisureOptionsFlowHandler(OptionsFlow): + """Handle Verisure options.""" + + def __init__(self, entry: ConfigEntry) -> None: + """Initialize Verisure options flow.""" + self.entry = entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Manage Verisure options.""" + errors = {} + + if user_input is not None: + if len(user_input[CONF_LOCK_DEFAULT_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) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_LOCK_CODE_DIGITS, + default=self.entry.options.get( + CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS + ), + ): int, + vol.Optional( + CONF_LOCK_DEFAULT_CODE, + default=self.entry.options.get(CONF_LOCK_DEFAULT_CODE), + ): str, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 89dcfa396aa1b3..3e00eb1ddb3ba6 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -8,21 +8,17 @@ ATTR_DEVICE_SERIAL = "device_serial" -CONF_ALARM = "alarm" -CONF_CODE_DIGITS = "code_digits" -CONF_DOOR_WINDOW = "door_window" CONF_GIID = "giid" -CONF_HYDROMETERS = "hygrometers" -CONF_LOCKS = "locks" -CONF_DEFAULT_LOCK_CODE = "default_lock_code" -CONF_MOUSE = "mouse" -CONF_SMARTPLUGS = "smartplugs" -CONF_THERMOMETERS = "thermometers" -CONF_SMARTCAM = "smartcam" +CONF_LOCK_CODE_DIGITS = "lock_code_digits" +CONF_LOCK_DEFAULT_CODE = "lock_default_code" DEFAULT_SCAN_INTERVAL = timedelta(minutes=1) -MIN_SCAN_INTERVAL = timedelta(minutes=1) +DEFAULT_LOCK_CODE_DIGITS = 4 SERVICE_CAPTURE_SMARTCAM = "capture_smartcam" SERVICE_DISABLE_AUTOLOCK = "disable_autolock" SERVICE_ENABLE_AUTOLOCK = "enable_autolock" + +# Legacy; to remove after YAML removal +CONF_CODE_DIGITS = "code_digits" +CONF_DEFAULT_LOCK_CODE = "default_lock_code" diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index 9de81429c5c9b6..63eb5ac2f683fd 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -9,9 +9,10 @@ Session as Verisure, ) -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_SERVICE_UNAVAILABLE -from homeassistant.core import HomeAssistant -from homeassistant.helpers.typing import ConfigType +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, HTTP_SERVICE_UNAVAILABLE +from homeassistant.core import Event, HomeAssistant +from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import Throttle @@ -21,14 +22,15 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): """A Verisure Data Update Coordinator.""" - def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize the Verisure hub.""" self.imageseries = {} - self.config = config - self.giid = config.get(CONF_GIID) + self.entry = entry self.verisure = Verisure( - username=config[CONF_USERNAME], password=config[CONF_PASSWORD] + username=entry.data[CONF_EMAIL], + password=entry.data[CONF_PASSWORD], + cookieFileName=hass.config.path(STORAGE_DIR, f"verisure_{entry.entry_id}"), ) super().__init__( @@ -42,11 +44,14 @@ async def async_login(self) -> bool: except VerisureError as ex: LOGGER.error("Could not log in to verisure, %s", ex) return False - if self.giid: - return await self.async_set_giid() + + await self.hass.async_add_executor_job( + self.verisure.set_giid, self.entry.data[CONF_GIID] + ) + return True - async def async_logout(self) -> bool: + async def async_logout(self, _event: Event) -> bool: """Logout from Verisure.""" try: await self.hass.async_add_executor_job(self.verisure.logout) @@ -55,15 +60,6 @@ async def async_logout(self) -> bool: return False return True - async def async_set_giid(self) -> bool: - """Set installation GIID.""" - try: - await self.hass.async_add_executor_job(self.verisure.set_giid, self.giid) - except VerisureError as ex: - LOGGER.error("Could not set installation GIID, %s", ex) - return False - return True - async def _async_update_data(self) -> dict: """Fetch data from Verisure.""" try: diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 99118850117c40..816243a454d78f 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -2,35 +2,36 @@ from __future__ import annotations import asyncio -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.lock import LockEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, DOMAIN, LOGGER +from .const import ( + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, + DOMAIN, + LOGGER, +) from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[VerisureDoorlock]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure lock platform.""" - coordinator = hass.data[DOMAIN] - locks = [] - if int(coordinator.config.get(CONF_LOCKS, 1)): - locks.extend( - [ - VerisureDoorlock(coordinator, serial_number) - for serial_number in coordinator.data["locks"] - ] - ) - - add_entities(locks) + """Set up Verisure alarm control panel from a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + VerisureDoorlock(coordinator, serial_number) + for serial_number in coordinator.data["locks"] + ) class VerisureDoorlock(CoordinatorEntity, LockEntity): @@ -45,8 +46,9 @@ def __init__( super().__init__(coordinator) self.serial_number = serial_number self._state = None - self._digits = coordinator.config.get(CONF_CODE_DIGITS) - self._default_lock_code = coordinator.config.get(CONF_DEFAULT_LOCK_CODE) + self._digits = coordinator.entry.options.get( + CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS + ) @property def name(self) -> str: @@ -80,7 +82,9 @@ def is_locked(self) -> bool: async def async_unlock(self, **kwargs) -> None: """Send unlock command.""" - code = kwargs.get(ATTR_CODE, self._default_lock_code) + code = kwargs.get( + ATTR_CODE, self.coordinator.entry.options.get(CONF_LOCK_DEFAULT_CODE) + ) if code is None: LOGGER.error("Code required but none provided") return @@ -89,7 +93,9 @@ async def async_unlock(self, **kwargs) -> None: async def async_lock(self, **kwargs) -> None: """Send lock command.""" - code = kwargs.get(ATTR_CODE, self._default_lock_code) + code = kwargs.get( + ATTR_CODE, self.coordinator.entry.options.get(CONF_LOCK_DEFAULT_CODE) + ) if code is None: LOGGER.error("Code required but none provided") return diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 05f07e926e0be3..074ef4f955ccfa 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -3,5 +3,7 @@ "name": "Verisure", "documentation": "https://www.home-assistant.io/integrations/verisure", "requirements": ["vsure==1.7.3"], - "codeowners": ["@frenck"] + "codeowners": ["@frenck"], + "config_flow": true, + "dhcp": [{ "macaddress": "0023C1*" }] } diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 2a4e47593698d9..37b02161879f5e 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,54 +1,44 @@ """Support for Verisure sensors.""" from __future__ import annotations -from typing import Any, Callable +from typing import Callable, Iterable +from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_HYDROMETERS, CONF_MOUSE, CONF_THERMOMETERS, DOMAIN +from .const import DOMAIN from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[CoordinatorEntity], bool], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure platform.""" - coordinator = hass.data[DOMAIN] - - sensors: list[CoordinatorEntity] = [] - if int(coordinator.config.get(CONF_THERMOMETERS, 1)): - sensors.extend( - [ - VerisureThermometer(coordinator, serial_number) - for serial_number, values in coordinator.data["climate"].items() - if "temperature" in values - ] - ) - - if int(coordinator.config.get(CONF_HYDROMETERS, 1)): - sensors.extend( - [ - VerisureHygrometer(coordinator, serial_number) - for serial_number, values in coordinator.data["climate"].items() - if "humidity" in values - ] - ) - - if int(coordinator.config.get(CONF_MOUSE, 1)): - sensors.extend( - [ - VerisureMouseDetection(coordinator, serial_number) - for serial_number in coordinator.data["mice"] - ] - ) - - add_entities(sensors) + """Set up Verisure sensors based on a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + sensors: list[Entity] = [ + VerisureThermometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "temperature" in values + ] + + sensors.extend( + VerisureHygrometer(coordinator, serial_number) + for serial_number, values in coordinator.data["climate"].items() + if "humidity" in values + ) + + sensors.extend( + VerisureMouseDetection(coordinator, serial_number) + for serial_number in coordinator.data["mice"] + ) + + async_add_entities(sensors) class VerisureThermometer(CoordinatorEntity, Entity): diff --git a/homeassistant/components/verisure/strings.json b/homeassistant/components/verisure/strings.json new file mode 100644 index 00000000000000..0c7f513f8eee42 --- /dev/null +++ b/homeassistant/components/verisure/strings.json @@ -0,0 +1,39 @@ +{ + "config": { + "step": { + "user": { + "data": { + "description": "Sign-in with your Verisure My Pages account.", + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } + }, + "installation": { + "description": "Home Assistant found multiple Verisure installations in your My Pages account. Please, select the installation to add to Home Assistant.", + "data": { + "giid": "Installation" + } + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "lock_code_digits": "Number of digits in PIN code for locks", + "lock_default_code": "Default PIN code for locks, used if none is given" + } + } + }, + "error": { + "code_format_mismatch": "The default PIN code does not match the required number of digits" + } + } +} diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 9ce0d3ce5df9a4..887d052bd81a02 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,33 +2,28 @@ from __future__ import annotations from time import monotonic -from typing import Any, Callable +from typing import Callable, Iterable from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_SMARTPLUGS, DOMAIN +from .const import DOMAIN from .coordinator import VerisureDataUpdateCoordinator -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: dict[str, Any], - add_entities: Callable[[list[CoordinatorEntity]], None], - discovery_info: dict[str, Any] | None = None, + entry: ConfigEntry, + async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up the Verisure switch platform.""" - coordinator = hass.data[DOMAIN] - - if not int(coordinator.config.get(CONF_SMARTPLUGS, 1)): - return - - add_entities( - [ - VerisureSmartplug(coordinator, serial_number) - for serial_number in coordinator.data["smart_plugs"] - ] + """Set up Verisure alarm control panel from a config entry.""" + coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + VerisureSmartplug(coordinator, serial_number) + for serial_number in coordinator.data["smart_plugs"] ) @@ -51,6 +46,11 @@ def name(self) -> str: """Return the name or location of the smartplug.""" return self.coordinator.data["smart_plugs"][self.serial_number]["area"] + @property + def unique_id(self) -> str: + """Return the unique ID for this alarm control panel.""" + return self.serial_number + @property def is_on(self) -> bool: """Return true if on.""" diff --git a/homeassistant/components/verisure/translations/en.json b/homeassistant/components/verisure/translations/en.json new file mode 100644 index 00000000000000..85c7acc167e9c8 --- /dev/null +++ b/homeassistant/components/verisure/translations/en.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "installation": { + "data": { + "giid": "Installation" + }, + "description": "Home Assistant found multiple Verisure installations in your My Pages account. Please, select the installation to add to Home Assistant." + }, + "user": { + "data": { + "description": "Sign-in with your Verisure My Pages account.", + "email": "Email", + "password": "Password" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "The default PIN code does not match the required number of digits" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Number of digits in PIN code for locks", + "lock_default_code": "Default PIN code for locks, used if none is given" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 057ebe74865411..a3bcef9047f26d 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -247,6 +247,7 @@ "upnp", "velbus", "vera", + "verisure", "vesync", "vilfo", "vizio", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 31ee42bc48cce8..b3e10c906210d1 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -153,5 +153,9 @@ "domain": "toon", "hostname": "eneco-*", "macaddress": "74C63B*" + }, + { + "domain": "verisure", + "macaddress": "0023C1*" } ] diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 836f9762a067c7..ed005d34d03b34 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1171,6 +1171,9 @@ uvcclient==0.11.0 # homeassistant.components.vilfo vilfo-api-client==0.3.2 +# homeassistant.components.verisure +vsure==1.7.3 + # homeassistant.components.vultr vultr==0.1.2 diff --git a/tests/components/verisure/__init__.py b/tests/components/verisure/__init__.py new file mode 100644 index 00000000000000..08339e46c6fc40 --- /dev/null +++ b/tests/components/verisure/__init__.py @@ -0,0 +1 @@ +"""Tests for the Verisure integration.""" diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py new file mode 100644 index 00000000000000..97f0f9731b15c3 --- /dev/null +++ b/tests/components/verisure/test_config_flow.py @@ -0,0 +1,467 @@ +"""Test the Verisure config flow.""" +from __future__ import annotations + +from unittest.mock import PropertyMock, patch + +import pytest +from verisure import Error as VerisureError, LoginError as VerisureLoginError + +from homeassistant import config_entries +from homeassistant.components.dhcp import MAC_ADDRESS +from homeassistant.components.verisure.const import ( + CONF_GIID, + CONF_LOCK_CODE_DIGITS, + CONF_LOCK_DEFAULT_CODE, + DEFAULT_LOCK_CODE_DIGITS, + DOMAIN, +) +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +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 + +TEST_INSTALLATIONS = [ + {"giid": "12345", "alias": "ascending", "street": "12345th street"}, + {"giid": "54321", "alias": "descending", "street": "54321th street"}, +] +TEST_INSTALLATION = [TEST_INSTALLATIONS[0]] + + +async def test_full_user_flow_single_installation(hass: HomeAssistant) -> None: + """Test a full user initiated configuration flow with a single installation.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATION + ) + mock_verisure.login.return_value = True + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ascending (12345th street)" + assert result2["data"] == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_full_user_flow_multiple_installations(hass: HomeAssistant) -> None: + """Test a full user initiated configuration flow with multiple installations.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATIONS + ) + mock_verisure.login.return_value = True + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["step_id"] == "installation" + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] is None + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {"giid": "54321"} + ) + await hass.async_block_till_done() + + assert result3["type"] == RESULT_TYPE_CREATE_ENTRY + assert result3["title"] == "descending (54321th street)" + assert result3["data"] == { + CONF_GIID: "54321", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_invalid_login(hass: HomeAssistant) -> None: + """Test a flow with an invalid Verisure My Pages login.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureLoginError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_unknown_error(hass: HomeAssistant) -> None: + """Test a flow with an invalid Verisure My Pages login.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["step_id"] == "user" + assert result2["errors"] == {"base": "unknown"} + + +async def test_dhcp(hass: HomeAssistant) -> None: + """Test that DHCP discovery works.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={MAC_ADDRESS: "01:23:45:67:89:ab"}, + context={"source": config_entries.SOURCE_DHCP}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +@pytest.mark.parametrize( + "input,output", + [ + ( + { + CONF_LOCK_CODE_DIGITS: 5, + CONF_LOCK_DEFAULT_CODE: "12345", + }, + { + CONF_LOCK_CODE_DIGITS: 5, + CONF_LOCK_DEFAULT_CODE: "12345", + }, + ), + ( + { + CONF_LOCK_DEFAULT_CODE: "", + }, + { + CONF_LOCK_DEFAULT_CODE: "", + CONF_LOCK_CODE_DIGITS: DEFAULT_LOCK_CODE_DIGITS, + }, + ), + ], +) +async def test_options_flow( + hass: HomeAssistant, input: dict[str, int | str], output: dict[str, int | str] +) -> None: + """Test options config flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ), patch( + "homeassistant.components.verisure.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=input, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == output + + +async def test_options_flow_code_format_mismatch(hass: HomeAssistant) -> None: + """Test options config flow with a code format mismatch.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ), patch( + "homeassistant.components.verisure.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" + assert result["errors"] == {} + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_LOCK_CODE_DIGITS: 5, + CONF_LOCK_DEFAULT_CODE: "123", + }, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + assert result["errors"] == {"base": "code_format_mismatch"} + + +# +# Below this line are tests that can be removed once the YAML configuration +# has been removed from this integration. +# +@pytest.mark.parametrize( + "giid,installations", + [ + ("12345", TEST_INSTALLATION), + ("12345", TEST_INSTALLATIONS), + (None, TEST_INSTALLATION), + ], +) +async def test_imports( + hass: HomeAssistant, giid: str | None, installations: dict[str, str] +) -> None: + """Test a YAML import with/without known giid on single/multiple installations.""" + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=installations + ) + mock_verisure.login.return_value = True + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: giid, + CONF_LOCK_CODE_DIGITS: 10, + CONF_LOCK_DEFAULT_CODE: "123456", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "ascending (12345th street)" + assert result["data"] == { + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_LOCK_CODE_DIGITS: 10, + CONF_LOCK_DEFAULT_CODE: "123456", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_imports_invalid_login(hass: HomeAssistant) -> None: + """Test a YAML import that results in a invalid login.""" + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureLoginError, + ): + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: None, + CONF_LOCK_CODE_DIGITS: None, + CONF_LOCK_DEFAULT_CODE: None, + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_auth"} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATION + ) + mock_verisure.login.return_value = True + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "SuperS3cr3t!", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ascending (12345th street)" + assert result2["data"] == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_imports_needs_user_installation_choice(hass: HomeAssistant) -> None: + """Test a YAML import that needs to use to decide on the installation.""" + with patch( + "homeassistant.components.verisure.config_flow.Verisure", + ) as mock_verisure: + type(mock_verisure.return_value).installations = PropertyMock( + return_value=TEST_INSTALLATIONS + ) + mock_verisure.login.return_value = True + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: None, + CONF_LOCK_CODE_DIGITS: None, + CONF_LOCK_DEFAULT_CODE: None, + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + + assert result["step_id"] == "installation" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"giid": "12345"} + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "ascending (12345th street)" + assert result2["data"] == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + } + + assert len(mock_verisure.mock_calls) == 2 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize("giid", ["12345", None]) +async def test_import_already_exists(hass: HomeAssistant, giid: str | None) -> None: + """Test that import flow aborts if exists.""" + MockConfigEntry(domain=DOMAIN, data={}, unique_id="12345").add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "SuperS3cr3t!", + CONF_GIID: giid, + }, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" From 40c12997ed5c366901661a5206f027e8ca8217f8 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 15 Mar 2021 20:51:24 +0100 Subject: [PATCH 1283/1818] Add zwave_js sensor humidity device class (#47953) --- homeassistant/components/zwave_js/sensor.py | 18 ++++++++++++------ tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/test_sensor.py | 9 +++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index a6d44cb62d0ddd..d6cc9aabd49fd9 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -14,7 +14,12 @@ DOMAIN as SENSOR_DOMAIN, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -83,11 +88,12 @@ def device_class(self) -> Optional[str]: if self.info.primary_value.metadata.unit == "kWh": return DEVICE_CLASS_ENERGY return DEVICE_CLASS_POWER - if ( - isinstance(self.info.primary_value.property_, str) - and "temperature" in self.info.primary_value.property_.lower() - ): - return DEVICE_CLASS_TEMPERATURE + if isinstance(self.info.primary_value.property_, str): + property_lower = self.info.primary_value.property_.lower() + if "humidity" in property_lower: + return DEVICE_CLASS_HUMIDITY + if "temperature" in property_lower: + return DEVICE_CLASS_TEMPERATURE if self.info.primary_value.metadata.unit == "W": return DEVICE_CLASS_POWER if self.info.primary_value.metadata.unit == "Lux": diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index f90a2e48ffad97..0592f89902fabc 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -1,5 +1,6 @@ """Provide common test tools for Z-Wave JS.""" AIR_TEMPERATURE_SENSOR = "sensor.multisensor_6_air_temperature" +HUMIDITY_SENSOR = "sensor.multisensor_6_humidity" ENERGY_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed_2" POWER_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed" SWITCH_ENTITY = "switch.smart_plug_with_two_usb_ports" diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 398d831e6928a7..7b4cd8bcce64b4 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -1,6 +1,7 @@ """Test the Z-Wave JS sensor platform.""" from homeassistant.const import ( DEVICE_CLASS_ENERGY, + DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ENERGY_KILO_WATT_HOUR, @@ -12,6 +13,7 @@ from .common import ( AIR_TEMPERATURE_SENSOR, ENERGY_SENSOR, + HUMIDITY_SENSOR, NOTIFICATION_MOTION_SENSOR, POWER_SENSOR, ) @@ -26,6 +28,13 @@ async def test_numeric_sensor(hass, multisensor_6, integration): assert state.attributes["unit_of_measurement"] == TEMP_CELSIUS assert state.attributes["device_class"] == DEVICE_CLASS_TEMPERATURE + state = hass.states.get(HUMIDITY_SENSOR) + + assert state + assert state.state == "65.0" + assert state.attributes["unit_of_measurement"] == "%" + assert state.attributes["device_class"] == DEVICE_CLASS_HUMIDITY + async def test_energy_sensors(hass, hank_binary_switch, integration): """Test power and energy sensors.""" From 9fd973d8e8c00adbd31d816cad85af5ec46add6a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 22:50:28 +0100 Subject: [PATCH 1284/1818] Move Verisure services to entity services (#47905) --- homeassistant/components/verisure/__init__.py | 46 ------------------- homeassistant/components/verisure/camera.py | 20 +++++++- homeassistant/components/verisure/const.py | 2 - .../components/verisure/coordinator.py | 8 ---- homeassistant/components/verisure/lock.py | 38 +++++++++++++++ .../components/verisure/services.yaml | 27 +++++++++-- 6 files changed, 79 insertions(+), 62 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index e34bf5c5650030..5f8b2119310cee 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -5,7 +5,6 @@ import os from typing import Any -from verisure import Error as VerisureError import voluptuous as vol from homeassistant.components.alarm_control_panel import ( @@ -29,7 +28,6 @@ from homeassistant.helpers.storage import STORAGE_DIR from .const import ( - ATTR_DEVICE_SERIAL, CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_GIID, @@ -38,9 +36,6 @@ DEFAULT_LOCK_CODE_DIGITS, DOMAIN, LOGGER, - SERVICE_CAPTURE_SMARTCAM, - SERVICE_DISABLE_AUTOLOCK, - SERVICE_ENABLE_AUTOLOCK, ) from .coordinator import VerisureDataUpdateCoordinator @@ -73,9 +68,6 @@ ) -DEVICE_SERIAL_SCHEMA = vol.Schema({vol.Required(ATTR_DEVICE_SERIAL): cv.string}) - - async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the Verisure integration.""" if DOMAIN in config: @@ -151,44 +143,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_forward_entry_setup(entry, platform) ) - async def capture_smartcam(service): - """Capture a new picture from a smartcam.""" - device_id = service.data[ATTR_DEVICE_SERIAL] - try: - await hass.async_add_executor_job(coordinator.smartcam_capture, device_id) - LOGGER.debug("Capturing new image from %s", ATTR_DEVICE_SERIAL) - except VerisureError as ex: - LOGGER.error("Could not capture image, %s", ex) - - hass.services.async_register( - DOMAIN, SERVICE_CAPTURE_SMARTCAM, capture_smartcam, schema=DEVICE_SERIAL_SCHEMA - ) - - async def disable_autolock(service): - """Disable autolock on a doorlock.""" - device_id = service.data[ATTR_DEVICE_SERIAL] - try: - await hass.async_add_executor_job(coordinator.disable_autolock, device_id) - LOGGER.debug("Disabling autolock on%s", ATTR_DEVICE_SERIAL) - except VerisureError as ex: - LOGGER.error("Could not disable autolock, %s", ex) - - hass.services.async_register( - DOMAIN, SERVICE_DISABLE_AUTOLOCK, disable_autolock, schema=DEVICE_SERIAL_SCHEMA - ) - - async def enable_autolock(service): - """Enable autolock on a doorlock.""" - device_id = service.data[ATTR_DEVICE_SERIAL] - try: - await hass.async_add_executor_job(coordinator.enable_autolock, device_id) - LOGGER.debug("Enabling autolock on %s", ATTR_DEVICE_SERIAL) - except VerisureError as ex: - LOGGER.error("Could not enable autolock, %s", ex) - - hass.services.async_register( - DOMAIN, SERVICE_ENABLE_AUTOLOCK, enable_autolock, schema=DEVICE_SERIAL_SCHEMA - ) return True diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index ee9fe6577de026..776211b5cf35ac 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -5,14 +5,17 @@ import os from typing import Callable, Iterable +from verisure import Error as VerisureError + from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, LOGGER +from .const import DOMAIN, LOGGER, SERVICE_CAPTURE_SMARTCAM from .coordinator import VerisureDataUpdateCoordinator @@ -24,6 +27,13 @@ async def async_setup_entry( """Set up Verisure sensors based on a config entry.""" coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + platform = current_platform.get() + platform.async_register_entity_service( + SERVICE_CAPTURE_SMARTCAM, + {}, + VerisureSmartcam.capture_smartcam.__name__, + ) + assert hass.config.config_dir async_add_entities( VerisureSmartcam(hass, coordinator, serial_number, hass.config.config_dir) @@ -114,3 +124,11 @@ def name(self) -> str: def unique_id(self) -> str: """Return the unique ID for this camera.""" return self.serial_number + + def capture_smartcam(self) -> None: + """Capture a new picture from a smartcam.""" + try: + self.coordinator.smartcam_capture(self.serial_number) + LOGGER.debug("Capturing new image from %s", self.serial_number) + except VerisureError as ex: + LOGGER.error("Could not capture image, %s", ex) diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 3e00eb1ddb3ba6..107d146e708977 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -6,8 +6,6 @@ LOGGER = logging.getLogger(__package__) -ATTR_DEVICE_SERIAL = "device_serial" - CONF_GIID = "giid" CONF_LOCK_CODE_DIGITS = "lock_code_digits" CONF_LOCK_DEFAULT_CODE = "lock_default_code" diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index 63eb5ac2f683fd..b118979f586f53 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -112,11 +112,3 @@ def update_smartcam_imageseries(self) -> None: def smartcam_capture(self, device_id: str) -> None: """Capture a new image from a smartcam.""" self.verisure.capture_image(device_id) - - def disable_autolock(self, device_id: str) -> None: - """Disable autolock.""" - self.verisure.set_lock_config(device_id, auto_lock_enabled=False) - - def enable_autolock(self, device_id: str) -> None: - """Enable autolock.""" - self.verisure.set_lock_config(device_id, auto_lock_enabled=True) diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 816243a454d78f..0431d0aee41a46 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -4,11 +4,14 @@ import asyncio from typing import Callable, Iterable +from verisure import Error as VerisureError + from homeassistant.components.lock import LockEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( @@ -17,6 +20,8 @@ DEFAULT_LOCK_CODE_DIGITS, DOMAIN, LOGGER, + SERVICE_DISABLE_AUTOLOCK, + SERVICE_ENABLE_AUTOLOCK, ) from .coordinator import VerisureDataUpdateCoordinator @@ -28,6 +33,19 @@ async def async_setup_entry( ) -> None: """Set up Verisure alarm control panel from a config entry.""" coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + platform = current_platform.get() + platform.async_register_entity_service( + SERVICE_DISABLE_AUTOLOCK, + {}, + VerisureDoorlock.disable_autolock.__name__, + ) + platform.async_register_entity_service( + SERVICE_ENABLE_AUTOLOCK, + {}, + VerisureDoorlock.enable_autolock.__name__, + ) + async_add_entities( VerisureDoorlock(coordinator, serial_number) for serial_number in coordinator.data["locks"] @@ -127,3 +145,23 @@ async def async_set_lock_state(self, code: str, state: str) -> None: await asyncio.sleep(0.5) if transaction["result"] == "OK": self._state = state + + def disable_autolock(self) -> None: + """Disable autolock on a doorlock.""" + try: + self.coordinator.verisure.set_lock_config( + self.serial_number, auto_lock_enabled=False + ) + LOGGER.debug("Disabling autolock on %s", self.serial_number) + except VerisureError as ex: + LOGGER.error("Could not disable autolock, %s", ex) + + def enable_autolock(self) -> None: + """Enable autolock on a doorlock.""" + try: + self.coordinator.verisure.set_lock_config( + self.serial_number, auto_lock_enabled=True + ) + LOGGER.debug("Enabling autolock on %s", self.serial_number) + except VerisureError as ex: + LOGGER.error("Could not enable autolock, %s", ex) diff --git a/homeassistant/components/verisure/services.yaml b/homeassistant/components/verisure/services.yaml index 885b8597549de0..2a4e2a008bee65 100644 --- a/homeassistant/components/verisure/services.yaml +++ b/homeassistant/components/verisure/services.yaml @@ -1,6 +1,23 @@ capture_smartcam: - description: Capture a new image from a smartcam. - fields: - device_serial: - description: The serial number of the smartcam you want to capture an image from. - example: 2DEU AT5Z + name: Capture SmartCam image + description: Capture a new image from a Verisure SmartCam + target: + entity: + integration: verisure + domain: camera + +enable_autolock: + name: Enable autolock + description: Enable autolock of a Verisure Lockguard Smartlock + target: + entity: + integration: verisure + domain: lock + +disable_autolock: + name: Disable autolock + description: Disable autolock of a Verisure Lockguard Smartlock + target: + entity: + integration: verisure + domain: lock From 5f627df6f8d07331e8385f3e24efa0a0e92abdbf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 15 Mar 2021 23:59:41 +0100 Subject: [PATCH 1285/1818] Add devices to Verisure integration (#47913) Co-authored-by: Paulus Schoutsen --- .../verisure/alarm_control_panel.py | 12 ++++- .../components/verisure/binary_sensor.py | 32 +++++++++++- homeassistant/components/verisure/camera.py | 37 +++++++++----- homeassistant/components/verisure/const.py | 14 ++++++ homeassistant/components/verisure/lock.py | 21 +++++++- homeassistant/components/verisure/sensor.py | 49 ++++++++++++++++++- homeassistant/components/verisure/switch.py | 19 +++++-- 7 files changed, 163 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 8a2a05e2005c35..54e36ac65def70 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, @@ -56,6 +56,16 @@ def unique_id(self) -> str: """Return the unique ID for this alarm control panel.""" return self.coordinator.entry.data[CONF_GIID] + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + return { + "name": "Verisure Alarm", + "manufacturer": "Verisure", + "model": "VBox", + "identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])}, + } + @property def state(self) -> str | None: """Return the state of the device.""" diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 4289a6f8ffc14c..864785c7cd0dd7 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Verisure binary sensors.""" from __future__ import annotations -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -13,7 +13,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DOMAIN +from .const import CONF_GIID, DOMAIN from .coordinator import VerisureDataUpdateCoordinator @@ -57,6 +57,19 @@ def unique_id(self) -> str: """Return the unique ID for this alarm control panel.""" return f"{self.serial_number}_door_window" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["door_window"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "Shock Sensor Detector", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" @@ -88,6 +101,21 @@ def name(self) -> str: """Return the name of the binary sensor.""" return "Verisure Ethernet status" + @property + def unique_id(self) -> str: + """Return the unique ID for this binary sensor.""" + return f"{self.coordinator.entry.data[CONF_GIID]}_ethernet" + + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + return { + "name": "Verisure Alarm", + "manufacturer": "Verisure", + "model": "VBox", + "identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])}, + } + @property def is_on(self) -> bool: """Return the state of the sensor.""" diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 776211b5cf35ac..006f9c28ef5475 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -3,7 +3,7 @@ import errno import os -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from verisure import Error as VerisureError @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import current_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, LOGGER, SERVICE_CAPTURE_SMARTCAM +from .const import CONF_GIID, DOMAIN, LOGGER, SERVICE_CAPTURE_SMARTCAM from .coordinator import VerisureDataUpdateCoordinator @@ -62,6 +62,29 @@ def __init__( self._image_id = None hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.delete_image) + @property + def name(self) -> str: + """Return the name of this camera.""" + return self.coordinator.data["cameras"][self.serial_number]["area"] + + @property + def unique_id(self) -> str: + """Return the unique ID for this camera.""" + return self.serial_number + + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["cameras"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "SmartCam", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + def camera_image(self) -> bytes | None: """Return image response.""" self.check_imagelist() @@ -115,16 +138,6 @@ def delete_image(self) -> None: if error.errno != errno.ENOENT: raise - @property - def name(self) -> str: - """Return the name of this camera.""" - return self.coordinator.data["cameras"][self.serial_number]["area"] - - @property - def unique_id(self) -> str: - """Return the unique ID for this camera.""" - return self.serial_number - def capture_smartcam(self) -> None: """Capture a new picture from a smartcam.""" try: diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 107d146e708977..8e39e0594dd9bc 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -17,6 +17,20 @@ SERVICE_DISABLE_AUTOLOCK = "disable_autolock" SERVICE_ENABLE_AUTOLOCK = "enable_autolock" +# Mapping of device types to a human readable name +DEVICE_TYPE_NAME = { + "CAMERAPIR2": "Camera detector", + "HOMEPAD1": "VoiceBox", + "HUMIDITY1": "Climate sensor", + "PIR2": "Camera detector", + "SIREN1": "Siren", + "SMARTCAMERA1": "SmartCam", + "SMOKE2": "Smoke detector", + "SMOKE3": "Smoke detector", + "VOICEBOX1": "VoiceBox", + "WATER1": "Water detector", +} + # Legacy; to remove after YAML removal CONF_CODE_DIGITS = "code_digits" CONF_DEFAULT_LOCK_CODE = "default_lock_code" diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 0431d0aee41a46..d4708c68e882b3 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from verisure import Error as VerisureError @@ -15,6 +15,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( + CONF_GIID, CONF_LOCK_CODE_DIGITS, CONF_LOCK_DEFAULT_CODE, DEFAULT_LOCK_CODE_DIGITS, @@ -73,6 +74,24 @@ def name(self) -> str: """Return the name of the lock.""" return self.coordinator.data["locks"][self.serial_number]["area"] + @property + def unique_id(self) -> str: + """Return the unique ID for this entity.""" + return self.serial_number + + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["locks"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "Lockguard Smartlock", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def available(self) -> bool: """Return True if entity is available.""" diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 37b02161879f5e..614bd0a386db00 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,7 +1,7 @@ """Support for Verisure sensors.""" from __future__ import annotations -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS @@ -9,7 +9,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import CONF_GIID, DEVICE_TYPE_NAME, DOMAIN from .coordinator import VerisureDataUpdateCoordinator @@ -64,6 +64,22 @@ def unique_id(self) -> str: """Return the unique ID for this entity.""" return f"{self.serial_number}_temperature" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + device_type = self.coordinator.data["climate"][self.serial_number].get( + "deviceType" + ) + area = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": DEVICE_TYPE_NAME.get(device_type, device_type), + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def state(self) -> str | None: """Return the state of the entity.""" @@ -107,6 +123,22 @@ def unique_id(self) -> str: """Return the unique ID for this entity.""" return f"{self.serial_number}_humidity" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + device_type = self.coordinator.data["climate"][self.serial_number].get( + "deviceType" + ) + area = self.coordinator.data["climate"][self.serial_number]["deviceArea"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": DEVICE_TYPE_NAME.get(device_type, device_type), + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def state(self) -> str | None: """Return the state of the entity.""" @@ -150,6 +182,19 @@ def unique_id(self) -> str: """Return the unique ID for this entity.""" return f"{self.serial_number}_mice" + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["mice"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "Mouse detector", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def state(self) -> str | None: """Return the state of the device.""" diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 887d052bd81a02..534db9c7a50201 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,7 +2,7 @@ from __future__ import annotations from time import monotonic -from typing import Callable, Iterable +from typing import Any, Callable, Iterable from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -10,7 +10,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .const import CONF_GIID, DOMAIN from .coordinator import VerisureDataUpdateCoordinator @@ -48,9 +48,22 @@ def name(self) -> str: @property def unique_id(self) -> str: - """Return the unique ID for this alarm control panel.""" + """Return the unique ID for this entity.""" return self.serial_number + @property + def device_info(self) -> dict[str, Any]: + """Return device information about this entity.""" + area = self.coordinator.data["smart_plugs"][self.serial_number]["area"] + return { + "name": area, + "suggested_area": area, + "manufacturer": "Verisure", + "model": "SmartPlug", + "identifiers": {(DOMAIN, self.serial_number)}, + "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), + } + @property def is_on(self) -> bool: """Return true if on.""" From f82e59c32a10e093d00401b4daeeaa368548cd9d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 00:51:04 +0100 Subject: [PATCH 1286/1818] Make it possible to list debug traces for a specific automation (#47744) --- homeassistant/components/automation/trace.py | 7 +- .../components/automation/websocket_api.py | 17 +++- homeassistant/helpers/condition.py | 4 +- homeassistant/helpers/script.py | 4 +- homeassistant/helpers/trace.py | 7 +- .../automation/test_websocket_api.py | 86 +++++++++++++------ 6 files changed, 86 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index ebd981f254160a..351ca1ed979d51 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -109,6 +109,7 @@ def as_short_dict(self) -> Dict[str, Any]: trigger = self._variables.get("trigger", {}).get("description") result = { + "automation_id": self._unique_id, "last_action": last_action, "last_condition": last_condition, "run_id": self.run_id, @@ -196,11 +197,9 @@ def get_debug_traces_for_automation(hass, automation_id, summary=False): @callback def get_debug_traces(hass, summary=False): """Return a serializable list of debug traces.""" - traces = {} + traces = [] for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces[automation_id] = get_debug_traces_for_automation( - hass, automation_id, summary - ) + traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) return traces diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index aaebbef7f83674..bb47dd58ff9b27 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -21,7 +21,7 @@ debug_stop, ) -from .trace import get_debug_trace, get_debug_traces +from .trace import get_debug_trace, get_debug_traces, get_debug_traces_for_automation # mypy: allow-untyped-calls, allow-untyped-defs @@ -50,7 +50,7 @@ def async_setup(hass: HomeAssistant) -> None: } ) def websocket_automation_trace_get(hass, connection, msg): - """Get automation traces.""" + """Get an automation trace.""" automation_id = msg["automation_id"] run_id = msg["run_id"] @@ -61,10 +61,19 @@ def websocket_automation_trace_get(hass, connection, msg): @callback @websocket_api.require_admin -@websocket_api.websocket_command({vol.Required("type"): "automation/trace/list"}) +@websocket_api.websocket_command( + {vol.Required("type"): "automation/trace/list", vol.Optional("automation_id"): str} +) def websocket_automation_trace_list(hass, connection, msg): """Summarize automation traces.""" - automation_traces = get_debug_traces(hass, summary=True) + automation_id = msg.get("automation_id") + + if not automation_id: + automation_traces = get_debug_traces(hass, summary=True) + else: + automation_traces = get_debug_traces_for_automation( + hass, automation_id, summary=True + ) connection.send_result(msg["id"], automation_traces) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 71bbaa5f0f4585..10b59645ed0bab 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -77,8 +77,8 @@ def condition_trace_append(variables: TemplateVarsType, path: str) -> TraceElement: """Append a TraceElement to trace[path].""" - trace_element = TraceElement(variables) - trace_append_element(trace_element, path) + trace_element = TraceElement(variables, path) + trace_append_element(trace_element) return trace_element diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index a2df055331dd91..f1732aef31616b 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -137,8 +137,8 @@ def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" - trace_element = TraceElement(variables) - trace_append_element(trace_element, path, ACTION_TRACE_NODE_MAX_LEN) + trace_element = TraceElement(variables, path) + trace_append_element(trace_element, ACTION_TRACE_NODE_MAX_LEN) return trace_element diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index e0c67a1de546d0..a7cd63de479de7 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -11,9 +11,10 @@ class TraceElement: """Container for trace data.""" - def __init__(self, variables: TemplateVarsType): + def __init__(self, variables: TemplateVarsType, path: str): """Container for trace data.""" self._error: Optional[Exception] = None + self.path: str = path self._result: Optional[dict] = None self._timestamp = dt_util.utcnow() @@ -42,7 +43,7 @@ def set_result(self, **kwargs: Any) -> None: def as_dict(self) -> Dict[str, Any]: """Return dictionary version of this TraceElement.""" - result: Dict[str, Any] = {"timestamp": self._timestamp} + result: Dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} if self._variables: result["changed_variables"] = self._variables if self._error is not None: @@ -129,10 +130,10 @@ def trace_path_get() -> str: def trace_append_element( trace_element: TraceElement, - path: str, maxlen: Optional[int] = None, ) -> None: """Append a TraceElement to trace[path].""" + path = trace_element.path trace = trace_cv.get() if trace is None: trace = {} diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 99b9540b06ef97..84c85e224e53f2 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -9,6 +9,20 @@ from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 +def _find_run_id(traces, automation_id): + """Find newest run_id for an automation.""" + for trace in reversed(traces): + if trace["automation_id"] == automation_id: + return trace["run_id"] + + return None + + +def _find_traces_for_automation(traces, automation_id): + """Find traces for an automation.""" + return [trace for trace in traces if trace["automation_id"] == automation_id] + + async def test_get_automation_trace(hass, hass_ws_client): """Test tracing an automation.""" id = 1 @@ -61,7 +75,7 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["sun"][-1]["run_id"] + run_id = _find_run_id(response["result"], "sun") # Get trace await client.send_json( @@ -97,7 +111,7 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["moon"][-1]["run_id"] + run_id = _find_run_id(response["result"], "moon") # Get trace await client.send_json( @@ -134,7 +148,7 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["moon"][-1]["run_id"] + run_id = _find_run_id(response["result"], "moon") # Get trace await client.send_json( @@ -168,7 +182,7 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - run_id = response["result"]["moon"][-1]["run_id"] + run_id = _find_run_id(response["result"], "moon") # Get trace await client.send_json( @@ -237,7 +251,7 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert response["result"] == {} + assert response["result"] == [] # Trigger "sun" and "moon" automation once hass.bus.async_fire("test_event") @@ -248,9 +262,9 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert len(response["result"]["moon"]) == 1 - moon_run_id = response["result"]["moon"][0]["run_id"] - assert len(response["result"]["sun"]) == 1 + assert len(_find_traces_for_automation(response["result"], "moon")) == 1 + moon_run_id = _find_run_id(response["result"], "moon") + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 # Trigger "moon" automation enough times to overflow the number of stored traces for _ in range(automation.trace.STORED_TRACES): @@ -260,13 +274,15 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert len(response["result"]["moon"]) == automation.trace.STORED_TRACES - assert len(response["result"]["sun"]) == 1 - assert int(response["result"]["moon"][0]["run_id"]) == int(moon_run_id) + 1 + moon_traces = _find_traces_for_automation(response["result"], "moon") + assert len(moon_traces) == automation.trace.STORED_TRACES + assert moon_traces[0] + assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1 assert ( - int(response["result"]["moon"][-1]["run_id"]) + int(moon_traces[-1]["run_id"]) == int(moon_run_id) + automation.trace.STORED_TRACES ) + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 async def test_list_automation_traces(hass, hass_ws_client): @@ -315,7 +331,14 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert response["result"] == {} + assert response["result"] == [] + + await client.send_json( + {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] # Trigger "sun" automation hass.bus.async_fire("test_event") @@ -325,8 +348,23 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert "moon" not in response["result"] - assert len(response["result"]["sun"]) == 1 + assert len(response["result"]) == 1 + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + + await client.send_json( + {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + ) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]) == 1 + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + + await client.send_json( + {"id": next_id(), "type": "automation/trace/list", "automation_id": "moon"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -344,9 +382,9 @@ def next_id(): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - assert len(response["result"]["moon"]) == 3 - assert len(response["result"]["sun"]) == 1 - trace = response["result"]["sun"][0] + assert len(_find_traces_for_automation(response["result"], "moon")) == 3 + assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + trace = _find_traces_for_automation(response["result"], "sun")[0] assert trace["last_action"] == "action/0" assert trace["last_condition"] is None assert trace["error"] == "Unable to find service test.automation" @@ -355,7 +393,7 @@ def next_id(): assert trace["trigger"] == "event 'test_event'" assert trace["unique_id"] == "sun" - trace = response["result"]["moon"][0] + trace = _find_traces_for_automation(response["result"], "moon")[0] assert trace["last_action"] == "action/0" assert trace["last_condition"] == "condition/0" assert "error" not in trace @@ -364,7 +402,7 @@ def next_id(): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" - trace = response["result"]["moon"][1] + trace = _find_traces_for_automation(response["result"], "moon")[1] assert trace["last_action"] is None assert trace["last_condition"] == "condition/0" assert "error" not in trace @@ -373,7 +411,7 @@ def next_id(): assert trace["trigger"] == "event 'test_event3'" assert trace["unique_id"] == "moon" - trace = response["result"]["moon"][2] + trace = _find_traces_for_automation(response["result"], "moon")[2] assert trace["last_action"] == "action/0" assert trace["last_condition"] == "condition/0" assert "error" not in trace @@ -396,7 +434,7 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - trace = response["result"][automation_id][-1] + trace = _find_traces_for_automation(response["result"], automation_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -567,7 +605,7 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - trace = response["result"][automation_id][-1] + trace = _find_traces_for_automation(response["result"], automation_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -674,7 +712,7 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json({"id": next_id(), "type": "automation/trace/list"}) response = await client.receive_json() assert response["success"] - trace = response["result"][automation_id][-1] + trace = _find_traces_for_automation(response["result"], automation_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] From c11b85af2f9938a1c3225e2bc3efc9a273e79420 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 16 Mar 2021 00:04:36 +0000 Subject: [PATCH 1287/1818] [ci skip] Translation update --- .../components/abode/translations/he.json | 11 ++ .../components/abode/translations/hu.json | 11 +- .../components/abode/translations/id.json | 35 ++++ .../components/abode/translations/ko.json | 14 +- .../accuweather/translations/he.json | 11 ++ .../accuweather/translations/hu.json | 29 +++ .../accuweather/translations/id.json | 41 ++++ .../accuweather/translations/ko.json | 20 +- .../accuweather/translations/nl.json | 5 + .../accuweather/translations/sensor.hu.json | 9 + .../accuweather/translations/sensor.id.json | 9 + .../accuweather/translations/sensor.ko.json | 9 + .../components/acmeda/translations/hu.json | 7 + .../components/acmeda/translations/id.json | 15 ++ .../components/adguard/translations/bg.json | 4 +- .../components/adguard/translations/ca.json | 2 +- .../components/adguard/translations/cs.json | 4 +- .../components/adguard/translations/da.json | 4 +- .../components/adguard/translations/de.json | 4 +- .../adguard/translations/es-419.json | 4 +- .../components/adguard/translations/es.json | 4 +- .../components/adguard/translations/et.json | 2 +- .../components/adguard/translations/he.json | 1 + .../components/adguard/translations/hu.json | 7 +- .../components/adguard/translations/id.json | 21 +- .../components/adguard/translations/it.json | 2 +- .../components/adguard/translations/ko.json | 6 +- .../components/adguard/translations/lb.json | 4 +- .../components/adguard/translations/nl.json | 4 +- .../components/adguard/translations/no.json | 2 +- .../components/adguard/translations/pl.json | 2 +- .../adguard/translations/pt-BR.json | 4 +- .../components/adguard/translations/pt.json | 2 +- .../components/adguard/translations/sl.json | 4 +- .../components/adguard/translations/sv.json | 4 +- .../components/adguard/translations/uk.json | 4 +- .../adguard/translations/zh-Hant.json | 4 +- .../advantage_air/translations/hu.json | 3 + .../advantage_air/translations/id.json | 20 ++ .../advantage_air/translations/ko.json | 4 +- .../components/aemet/translations/hu.json | 21 ++ .../components/aemet/translations/id.json | 22 +++ .../components/aemet/translations/ko.json | 3 +- .../components/aemet/translations/lb.json | 12 ++ .../components/aemet/translations/nl.json | 1 + .../components/agent_dvr/translations/hu.json | 4 + .../components/agent_dvr/translations/id.json | 20 ++ .../components/airly/translations/de.json | 4 +- .../components/airly/translations/es.json | 4 +- .../components/airly/translations/et.json | 4 +- .../components/airly/translations/fr.json | 4 +- .../components/airly/translations/he.json | 11 ++ .../components/airly/translations/hu.json | 11 +- .../components/airly/translations/id.json | 30 +++ .../components/airly/translations/it.json | 4 +- .../components/airly/translations/ko.json | 9 +- .../components/airly/translations/nl.json | 7 + .../components/airly/translations/no.json | 4 +- .../components/airly/translations/pl.json | 4 +- .../airly/translations/zh-Hans.json | 3 +- .../components/airnow/translations/bg.json | 7 + .../components/airnow/translations/de.json | 1 + .../components/airnow/translations/hu.json | 24 +++ .../components/airnow/translations/id.json | 26 +++ .../components/airnow/translations/ko.json | 11 +- .../components/airvisual/translations/bg.json | 12 ++ .../components/airvisual/translations/he.json | 8 +- .../components/airvisual/translations/hu.json | 27 ++- .../components/airvisual/translations/id.json | 77 ++++++++ .../components/airvisual/translations/ko.json | 21 +- .../components/airvisual/translations/nl.json | 12 +- .../alarm_control_panel/translations/id.json | 39 +++- .../alarm_control_panel/translations/ko.json | 42 ++-- .../translations/zh-Hans.json | 23 +++ .../alarmdecoder/translations/hu.json | 30 +++ .../alarmdecoder/translations/id.json | 74 +++++++ .../alarmdecoder/translations/ko.json | 36 ++-- .../components/almond/translations/ca.json | 2 +- .../components/almond/translations/cs.json | 4 +- .../components/almond/translations/da.json | 4 +- .../components/almond/translations/de.json | 4 +- .../almond/translations/es-419.json | 4 +- .../components/almond/translations/es.json | 4 +- .../components/almond/translations/et.json | 2 +- .../components/almond/translations/fr.json | 4 +- .../components/almond/translations/hu.json | 12 +- .../components/almond/translations/id.json | 19 ++ .../components/almond/translations/it.json | 2 +- .../components/almond/translations/ko.json | 6 +- .../components/almond/translations/lb.json | 4 +- .../components/almond/translations/nl.json | 8 +- .../components/almond/translations/no.json | 2 +- .../components/almond/translations/sl.json | 4 +- .../components/almond/translations/sv.json | 4 +- .../components/almond/translations/uk.json | 4 +- .../almond/translations/zh-Hant.json | 4 +- .../ambiclimate/translations/hu.json | 6 +- .../ambiclimate/translations/id.json | 22 +++ .../ambiclimate/translations/ko.json | 4 +- .../ambient_station/translations/hu.json | 5 +- .../ambient_station/translations/id.json | 20 ++ .../ambient_station/translations/nl.json | 2 +- .../components/apple_tv/translations/hu.json | 13 +- .../components/apple_tv/translations/id.json | 64 ++++++ .../components/apple_tv/translations/ko.json | 45 ++++- .../components/apple_tv/translations/nl.json | 23 +++ .../components/arcam_fmj/translations/hu.json | 10 + .../components/arcam_fmj/translations/id.json | 27 +++ .../components/arcam_fmj/translations/ko.json | 4 +- .../components/arcam_fmj/translations/pt.json | 2 + .../components/asuswrt/translations/bg.json | 17 ++ .../components/asuswrt/translations/hu.json | 33 ++++ .../components/asuswrt/translations/id.json | 45 +++++ .../components/asuswrt/translations/ko.json | 27 ++- .../components/asuswrt/translations/nl.json | 9 + .../components/atag/translations/hu.json | 4 + .../components/atag/translations/id.json | 21 ++ .../components/august/translations/ca.json | 2 +- .../components/august/translations/he.json | 12 ++ .../components/august/translations/hu.json | 16 ++ .../components/august/translations/id.json | 32 +++ .../components/august/translations/nl.json | 4 +- .../august/translations/zh-Hant.json | 2 +- .../components/aurora/translations/hu.json | 2 +- .../components/aurora/translations/id.json | 26 +++ .../components/aurora/translations/ko.json | 12 +- .../components/auth/translations/id.json | 25 ++- .../components/auth/translations/ko.json | 8 +- .../components/auth/translations/zh-Hant.json | 4 +- .../automation/translations/id.json | 4 +- .../components/awair/translations/hu.json | 23 ++- .../components/awair/translations/id.json | 29 +++ .../components/axis/translations/he.json | 11 ++ .../components/axis/translations/hu.json | 9 +- .../components/axis/translations/id.json | 37 ++++ .../components/axis/translations/nl.json | 10 + .../azure_devops/translations/hu.json | 10 +- .../azure_devops/translations/id.json | 32 +++ .../azure_devops/translations/ko.json | 22 ++- .../azure_devops/translations/nl.json | 9 +- .../binary_sensor/translations/id.json | 116 ++++++++++- .../binary_sensor/translations/ko.json | 184 ++++++++++-------- .../binary_sensor/translations/zh-Hans.json | 49 +++-- .../components/blebox/translations/hu.json | 7 +- .../components/blebox/translations/id.json | 24 +++ .../components/blebox/translations/ko.json | 4 +- .../components/blink/translations/en.json | 2 +- .../components/blink/translations/et.json | 2 +- .../components/blink/translations/fr.json | 2 +- .../components/blink/translations/hu.json | 11 +- .../components/blink/translations/id.json | 40 ++++ .../components/blink/translations/ko.json | 2 +- .../components/blink/translations/nl.json | 4 + .../components/blink/translations/no.json | 2 +- .../components/blink/translations/pl.json | 2 +- .../blink/translations/zh-Hant.json | 4 +- .../bmw_connected_drive/translations/bg.json | 12 ++ .../bmw_connected_drive/translations/hu.json | 20 ++ .../bmw_connected_drive/translations/id.json | 30 +++ .../bmw_connected_drive/translations/ko.json | 11 ++ .../components/bond/translations/hu.json | 21 ++ .../components/bond/translations/id.json | 28 +++ .../components/bond/translations/ko.json | 5 +- .../components/bond/translations/nl.json | 4 +- .../components/braviatv/translations/hu.json | 27 ++- .../components/braviatv/translations/id.json | 39 ++++ .../components/braviatv/translations/ko.json | 4 +- .../components/braviatv/translations/nl.json | 8 +- .../components/broadlink/translations/hu.json | 41 +++- .../components/broadlink/translations/id.json | 47 +++++ .../components/broadlink/translations/ko.json | 23 +-- .../components/broadlink/translations/nl.json | 3 +- .../components/brother/translations/hu.json | 2 +- .../components/brother/translations/id.json | 30 +++ .../components/brother/translations/ko.json | 2 +- .../components/bsblan/translations/hu.json | 8 +- .../components/bsblan/translations/id.json | 24 +++ .../components/bsblan/translations/ko.json | 2 +- .../components/calendar/translations/id.json | 4 +- .../components/calendar/translations/ko.json | 2 +- .../components/canary/translations/hu.json | 30 +++ .../components/canary/translations/id.json | 31 +++ .../components/canary/translations/ko.json | 6 +- .../components/cast/translations/ca.json | 23 +++ .../components/cast/translations/de.json | 23 +++ .../components/cast/translations/en.json | 1 + .../components/cast/translations/es.json | 23 +++ .../components/cast/translations/et.json | 23 +++ .../components/cast/translations/fr.json | 23 +++ .../components/cast/translations/hu.json | 29 ++- .../components/cast/translations/id.json | 29 ++- .../components/cast/translations/it.json | 23 +++ .../components/cast/translations/ko.json | 25 ++- .../components/cast/translations/nl.json | 29 ++- .../components/cast/translations/no.json | 23 +++ .../components/cast/translations/pl.json | 23 +++ .../components/cast/translations/pt.json | 7 + .../components/cast/translations/ru.json | 23 +++ .../components/cast/translations/zh-Hans.json | 11 ++ .../components/cast/translations/zh-Hant.json | 23 +++ .../cert_expiry/translations/hu.json | 1 + .../cert_expiry/translations/id.json | 24 +++ .../cert_expiry/translations/nl.json | 2 +- .../components/climacell/translations/bg.json | 16 ++ .../components/climacell/translations/de.json | 3 +- .../components/climacell/translations/es.json | 4 +- .../components/climacell/translations/hu.json | 28 +++ .../components/climacell/translations/id.json | 34 ++++ .../components/climacell/translations/ko.json | 34 ++++ .../components/climacell/translations/nl.json | 6 +- .../components/climacell/translations/pl.json | 34 ++++ .../components/climacell/translations/pt.json | 19 ++ .../climacell/translations/zh-Hans.json | 12 ++ .../components/climate/translations/id.json | 21 +- .../components/climate/translations/ko.json | 14 +- .../climate/translations/zh-Hans.json | 4 +- .../components/cloud/translations/hu.json | 2 + .../components/cloud/translations/id.json | 16 ++ .../components/cloud/translations/ko.json | 16 ++ .../components/cloud/translations/nl.json | 1 + .../cloudflare/translations/hu.json | 5 +- .../cloudflare/translations/id.json | 35 ++++ .../cloudflare/translations/ko.json | 22 ++- .../cloudflare/translations/nl.json | 3 +- .../configurator/translations/id.json | 2 +- .../components/control4/translations/hu.json | 14 ++ .../components/control4/translations/id.json | 31 +++ .../components/control4/translations/nl.json | 10 + .../coolmaster/translations/id.json | 22 +++ .../coronavirus/translations/hu.json | 2 +- .../coronavirus/translations/id.json | 15 ++ .../coronavirus/translations/nl.json | 2 +- .../components/cover/translations/hu.json | 3 +- .../components/cover/translations/id.json | 29 ++- .../components/cover/translations/ko.json | 37 ++-- .../cover/translations/zh-Hans.json | 6 +- .../components/daikin/translations/hu.json | 6 +- .../components/daikin/translations/id.json | 24 +++ .../components/daikin/translations/nl.json | 2 +- .../components/deconz/translations/bg.json | 4 +- .../components/deconz/translations/ca.json | 4 +- .../components/deconz/translations/cs.json | 4 +- .../components/deconz/translations/da.json | 4 +- .../components/deconz/translations/de.json | 4 +- .../deconz/translations/es-419.json | 4 +- .../components/deconz/translations/es.json | 4 +- .../components/deconz/translations/et.json | 2 +- .../components/deconz/translations/fr.json | 4 +- .../components/deconz/translations/hu.json | 41 ++-- .../components/deconz/translations/id.json | 94 ++++++++- .../components/deconz/translations/it.json | 2 +- .../components/deconz/translations/ko.json | 63 +++--- .../components/deconz/translations/lb.json | 4 +- .../components/deconz/translations/nl.json | 4 +- .../components/deconz/translations/no.json | 2 +- .../components/deconz/translations/pt-BR.json | 4 +- .../components/deconz/translations/pt.json | 4 +- .../components/deconz/translations/sl.json | 4 +- .../components/deconz/translations/sv.json | 4 +- .../components/deconz/translations/uk.json | 4 +- .../deconz/translations/zh-Hans.json | 4 +- .../deconz/translations/zh-Hant.json | 4 +- .../components/demo/translations/id.json | 21 ++ .../components/denonavr/translations/de.json | 3 +- .../components/denonavr/translations/hu.json | 8 + .../components/denonavr/translations/id.json | 48 +++++ .../components/denonavr/translations/ko.json | 1 + .../components/denonavr/translations/nl.json | 8 +- .../device_tracker/translations/id.json | 12 +- .../device_tracker/translations/ko.json | 8 +- .../device_tracker/translations/zh-Hans.json | 4 +- .../devolo_home_control/translations/he.json | 3 +- .../devolo_home_control/translations/hu.json | 11 +- .../devolo_home_control/translations/id.json | 20 ++ .../components/dexcom/translations/hu.json | 22 ++- .../components/dexcom/translations/id.json | 32 +++ .../components/dexcom/translations/nl.json | 10 + .../dialogflow/translations/hu.json | 6 +- .../dialogflow/translations/id.json | 17 ++ .../dialogflow/translations/ko.json | 4 +- .../components/directv/translations/hu.json | 3 +- .../components/directv/translations/id.json | 22 +++ .../components/directv/translations/ko.json | 2 +- .../components/directv/translations/nl.json | 6 +- .../components/doorbird/translations/hu.json | 7 +- .../components/doorbird/translations/id.json | 36 ++++ .../components/doorbird/translations/ko.json | 4 +- .../components/doorbird/translations/nl.json | 6 +- .../components/dsmr/translations/fr.json | 4 + .../components/dsmr/translations/id.json | 17 ++ .../components/dsmr/translations/ko.json | 10 + .../components/dunehd/translations/hu.json | 12 +- .../components/dunehd/translations/id.json | 21 ++ .../components/dunehd/translations/ko.json | 2 +- .../components/dunehd/translations/nl.json | 4 +- .../components/eafm/translations/id.json | 17 ++ .../components/eafm/translations/ko.json | 6 +- .../components/eafm/translations/nl.json | 7 + .../components/ebusd/translations/id.json | 6 + .../components/ecobee/translations/ca.json | 2 +- .../components/ecobee/translations/en.json | 2 +- .../components/ecobee/translations/et.json | 2 +- .../components/ecobee/translations/fr.json | 2 +- .../components/ecobee/translations/hu.json | 3 + .../components/ecobee/translations/id.json | 24 +++ .../components/ecobee/translations/ko.json | 2 +- .../components/ecobee/translations/no.json | 2 +- .../components/econet/translations/bg.json | 11 ++ .../components/econet/translations/hu.json | 21 ++ .../components/econet/translations/id.json | 22 +++ .../components/econet/translations/ko.json | 3 +- .../components/elgato/translations/hu.json | 3 +- .../components/elgato/translations/id.json | 25 +++ .../components/elgato/translations/ko.json | 4 +- .../components/elgato/translations/nl.json | 6 +- .../components/elkm1/translations/he.json | 12 ++ .../components/elkm1/translations/hu.json | 6 + .../components/elkm1/translations/id.json | 27 +++ .../components/elkm1/translations/ko.json | 6 +- .../components/elkm1/translations/nl.json | 6 +- .../emulated_roku/translations/hu.json | 7 +- .../emulated_roku/translations/id.json | 20 ++ .../emulated_roku/translations/nl.json | 6 +- .../components/enocean/translations/hu.json | 7 + .../components/enocean/translations/id.json | 25 +++ .../components/enocean/translations/ko.json | 4 +- .../components/epson/translations/hu.json | 2 +- .../components/epson/translations/id.json | 16 ++ .../components/esphome/translations/he.json | 11 ++ .../components/esphome/translations/hu.json | 4 +- .../components/esphome/translations/id.json | 24 ++- .../components/esphome/translations/ko.json | 4 +- .../components/esphome/translations/nl.json | 2 +- .../faa_delays/translations/bg.json | 14 ++ .../faa_delays/translations/de.json | 13 ++ .../faa_delays/translations/es.json | 4 +- .../faa_delays/translations/hu.json | 20 ++ .../faa_delays/translations/id.json | 21 ++ .../faa_delays/translations/ko.json | 21 ++ .../faa_delays/translations/pl.json | 21 ++ .../faa_delays/translations/pt.json | 8 + .../faa_delays/translations/zh-Hans.json | 9 + .../components/fan/translations/id.json | 20 +- .../components/fan/translations/ko.json | 12 +- .../fireservicerota/translations/hu.json | 19 +- .../fireservicerota/translations/id.json | 29 +++ .../fireservicerota/translations/ko.json | 4 +- .../components/firmata/translations/fr.json | 4 + .../components/firmata/translations/hu.json | 7 + .../components/firmata/translations/id.json | 7 + .../flick_electric/translations/de.json | 1 + .../flick_electric/translations/hu.json | 11 +- .../flick_electric/translations/id.json | 23 +++ .../components/flo/translations/hu.json | 14 ++ .../components/flo/translations/id.json | 21 ++ .../components/flume/translations/he.json | 12 ++ .../components/flume/translations/hu.json | 8 + .../components/flume/translations/id.json | 24 +++ .../components/flume/translations/nl.json | 4 +- .../flunearyou/translations/he.json | 11 ++ .../flunearyou/translations/hu.json | 18 ++ .../flunearyou/translations/id.json | 20 ++ .../flunearyou/translations/ko.json | 2 +- .../flunearyou/translations/nl.json | 2 +- .../flunearyou/translations/zh-Hans.json | 7 + .../forked_daapd/translations/de.json | 1 + .../forked_daapd/translations/hu.json | 13 +- .../forked_daapd/translations/id.json | 42 ++++ .../forked_daapd/translations/ko.json | 3 +- .../forked_daapd/translations/nl.json | 4 +- .../components/foscam/translations/bg.json | 14 ++ .../components/foscam/translations/hu.json | 26 +++ .../components/foscam/translations/id.json | 26 +++ .../components/foscam/translations/ko.json | 6 +- .../components/freebox/translations/hu.json | 7 +- .../components/freebox/translations/id.json | 25 +++ .../components/freebox/translations/ko.json | 2 +- .../components/freebox/translations/nl.json | 6 +- .../components/fritzbox/translations/bg.json | 12 ++ .../components/fritzbox/translations/he.json | 18 ++ .../components/fritzbox/translations/hu.json | 17 ++ .../components/fritzbox/translations/id.json | 39 ++++ .../components/fritzbox/translations/ko.json | 7 +- .../components/fritzbox/translations/nl.json | 9 +- .../fritzbox_callmonitor/translations/bg.json | 13 ++ .../fritzbox_callmonitor/translations/hu.json | 26 +++ .../fritzbox_callmonitor/translations/id.json | 41 ++++ .../fritzbox_callmonitor/translations/ko.json | 20 ++ .../fritzbox_callmonitor/translations/nl.json | 20 ++ .../garmin_connect/translations/he.json | 12 ++ .../garmin_connect/translations/hu.json | 6 +- .../garmin_connect/translations/id.json | 23 +++ .../components/gdacs/translations/hu.json | 2 +- .../components/gdacs/translations/id.json | 15 ++ .../components/geofency/translations/hu.json | 6 +- .../components/geofency/translations/id.json | 17 ++ .../components/geofency/translations/ko.json | 4 +- .../geonetnz_quakes/translations/hu.json | 3 + .../geonetnz_quakes/translations/id.json | 16 ++ .../geonetnz_quakes/translations/nl.json | 2 +- .../geonetnz_volcano/translations/hu.json | 3 + .../geonetnz_volcano/translations/id.json | 15 ++ .../geonetnz_volcano/translations/ko.json | 2 +- .../components/gios/translations/hu.json | 6 +- .../components/gios/translations/id.json | 27 +++ .../components/gios/translations/ko.json | 7 +- .../components/gios/translations/nl.json | 4 +- .../components/glances/translations/he.json | 11 ++ .../components/glances/translations/hu.json | 8 +- .../components/glances/translations/id.json | 36 ++++ .../components/glances/translations/ko.json | 2 +- .../components/goalzero/translations/hu.json | 20 ++ .../components/goalzero/translations/id.json | 22 +++ .../components/goalzero/translations/ko.json | 6 +- .../components/gogogate2/translations/hu.json | 4 +- .../components/gogogate2/translations/id.json | 22 +++ .../components/gpslogger/translations/hu.json | 6 +- .../components/gpslogger/translations/id.json | 17 ++ .../components/gpslogger/translations/ko.json | 4 +- .../components/gree/translations/de.json | 2 +- .../components/gree/translations/hu.json | 13 ++ .../components/gree/translations/id.json | 13 ++ .../components/gree/translations/ko.json | 2 +- .../components/group/translations/id.json | 10 +- .../components/guardian/translations/hu.json | 10 + .../components/guardian/translations/id.json | 21 ++ .../components/guardian/translations/nl.json | 4 +- .../components/habitica/translations/bg.json | 11 ++ .../components/habitica/translations/hu.json | 18 ++ .../components/habitica/translations/id.json | 20 ++ .../components/habitica/translations/ko.json | 5 +- .../components/habitica/translations/nl.json | 5 +- .../components/habitica/translations/pt.json | 16 ++ .../components/hangouts/translations/ca.json | 4 +- .../components/hangouts/translations/en.json | 4 +- .../components/hangouts/translations/et.json | 4 +- .../components/hangouts/translations/fr.json | 2 +- .../components/hangouts/translations/hu.json | 6 +- .../components/hangouts/translations/id.json | 21 +- .../components/hangouts/translations/ko.json | 2 +- .../components/hangouts/translations/nl.json | 6 +- .../components/hangouts/translations/no.json | 4 +- .../components/hangouts/translations/pl.json | 2 +- .../hangouts/translations/zh-Hant.json | 8 +- .../components/harmony/translations/hu.json | 7 + .../components/harmony/translations/id.json | 36 ++++ .../components/harmony/translations/nl.json | 4 +- .../components/hassio/translations/af.json | 2 +- .../components/hassio/translations/bg.json | 2 +- .../components/hassio/translations/ca.json | 2 +- .../components/hassio/translations/cs.json | 2 +- .../components/hassio/translations/cy.json | 2 +- .../components/hassio/translations/da.json | 2 +- .../components/hassio/translations/de.json | 2 +- .../components/hassio/translations/el.json | 2 +- .../components/hassio/translations/en.json | 2 +- .../hassio/translations/es-419.json | 2 +- .../components/hassio/translations/es.json | 2 +- .../components/hassio/translations/et.json | 2 +- .../components/hassio/translations/eu.json | 2 +- .../components/hassio/translations/fa.json | 2 +- .../components/hassio/translations/fi.json | 2 +- .../components/hassio/translations/fr.json | 8 +- .../components/hassio/translations/he.json | 2 +- .../components/hassio/translations/hr.json | 2 +- .../components/hassio/translations/hu.json | 6 +- .../components/hassio/translations/hy.json | 2 +- .../components/hassio/translations/id.json | 19 ++ .../components/hassio/translations/is.json | 2 +- .../components/hassio/translations/it.json | 2 +- .../components/hassio/translations/ja.json | 2 +- .../components/hassio/translations/ko.json | 18 +- .../components/hassio/translations/lb.json | 2 +- .../components/hassio/translations/lt.json | 2 +- .../components/hassio/translations/lv.json | 2 +- .../components/hassio/translations/nb.json | 3 - .../components/hassio/translations/nl.json | 3 +- .../components/hassio/translations/nn.json | 2 +- .../components/hassio/translations/no.json | 2 +- .../components/hassio/translations/pl.json | 2 +- .../components/hassio/translations/pt-BR.json | 2 +- .../components/hassio/translations/pt.json | 2 +- .../components/hassio/translations/ro.json | 2 +- .../components/hassio/translations/ru.json | 2 +- .../components/hassio/translations/sk.json | 2 +- .../components/hassio/translations/sl.json | 2 +- .../components/hassio/translations/sv.json | 2 +- .../components/hassio/translations/th.json | 2 +- .../components/hassio/translations/tr.json | 2 +- .../components/hassio/translations/uk.json | 2 +- .../components/hassio/translations/vi.json | 2 +- .../hassio/translations/zh-Hans.json | 2 +- .../hassio/translations/zh-Hant.json | 4 +- .../components/heos/translations/hu.json | 3 + .../components/heos/translations/id.json | 19 ++ .../components/heos/translations/ko.json | 4 +- .../hisense_aehw4a1/translations/hu.json | 4 +- .../hisense_aehw4a1/translations/id.json | 13 ++ .../hisense_aehw4a1/translations/ko.json | 2 +- .../hisense_aehw4a1/translations/nl.json | 4 +- .../components/hive/translations/ca.json | 53 +++++ .../components/hive/translations/de.json | 17 ++ .../components/hive/translations/en.json | 94 ++++----- .../components/hive/translations/et.json | 53 +++++ .../components/hive/translations/fr.json | 53 +++++ .../components/hive/translations/ko.json | 53 +++++ .../components/hive/translations/pt.json | 22 +++ .../components/hive/translations/ru.json | 53 +++++ .../components/hive/translations/zh-Hant.json | 53 +++++ .../components/hlk_sw16/translations/hu.json | 14 ++ .../components/hlk_sw16/translations/id.json | 21 ++ .../home_connect/translations/hu.json | 6 +- .../home_connect/translations/id.json | 16 ++ .../homeassistant/translations/fr.json | 2 +- .../homeassistant/translations/he.json | 7 + .../homeassistant/translations/hu.json | 4 +- .../homeassistant/translations/id.json | 21 ++ .../homeassistant/translations/ko.json | 21 ++ .../homeassistant/translations/nl.json | 2 +- .../components/homekit/translations/he.json | 3 +- .../components/homekit/translations/hu.json | 40 +++- .../components/homekit/translations/id.json | 75 +++++++ .../components/homekit/translations/ko.json | 34 ++-- .../components/homekit/translations/nl.json | 18 +- .../homekit_controller/translations/bg.json | 14 ++ .../homekit_controller/translations/hu.json | 24 ++- .../homekit_controller/translations/id.json | 71 +++++++ .../homekit_controller/translations/ko.json | 22 +-- .../homekit_controller/translations/nl.json | 14 +- .../homematicip_cloud/translations/hu.json | 10 +- .../homematicip_cloud/translations/id.json | 24 +-- .../homematicip_cloud/translations/ko.json | 6 +- .../homematicip_cloud/translations/nl.json | 12 +- .../huawei_lte/translations/he.json | 11 ++ .../huawei_lte/translations/hu.json | 10 +- .../huawei_lte/translations/id.json | 42 ++++ .../huawei_lte/translations/ko.json | 2 +- .../components/hue/translations/hu.json | 18 +- .../components/hue/translations/id.json | 59 +++++- .../components/hue/translations/ko.json | 10 +- .../components/hue/translations/nl.json | 6 +- .../components/hue/translations/zh-Hans.json | 7 + .../huisbaasje/translations/bg.json | 12 ++ .../huisbaasje/translations/hu.json | 21 ++ .../huisbaasje/translations/id.json | 21 ++ .../humidifier/translations/hu.json | 28 +++ .../humidifier/translations/id.json | 28 +++ .../humidifier/translations/ko.json | 22 +-- .../humidifier/translations/nl.json | 7 + .../humidifier/translations/zh-Hans.json | 5 +- .../translations/hu.json | 4 +- .../translations/id.json | 23 +++ .../hvv_departures/translations/he.json | 12 ++ .../hvv_departures/translations/hu.json | 27 +++ .../hvv_departures/translations/id.json | 47 +++++ .../hvv_departures/translations/ko.json | 4 +- .../hvv_departures/translations/nl.json | 28 ++- .../components/hyperion/translations/hu.json | 15 +- .../components/hyperion/translations/id.json | 53 +++++ .../components/hyperion/translations/ko.json | 31 +++ .../components/hyperion/translations/nl.json | 11 +- .../components/iaqualink/translations/he.json | 11 ++ .../components/iaqualink/translations/hu.json | 3 + .../components/iaqualink/translations/id.json | 20 ++ .../components/iaqualink/translations/ko.json | 4 +- .../components/icloud/translations/de.json | 1 + .../components/icloud/translations/he.json | 1 + .../components/icloud/translations/hu.json | 8 +- .../components/icloud/translations/id.json | 46 +++++ .../components/icloud/translations/ko.json | 3 +- .../components/icloud/translations/nl.json | 2 +- .../components/ifttt/translations/hu.json | 6 +- .../components/ifttt/translations/id.json | 17 ++ .../components/ifttt/translations/ko.json | 6 +- .../image_processing/translations/id.json | 2 +- .../input_boolean/translations/id.json | 4 +- .../components/insteon/translations/he.json | 12 ++ .../components/insteon/translations/hu.json | 72 +++++++ .../components/insteon/translations/id.json | 109 +++++++++++ .../components/insteon/translations/ko.json | 57 ++++-- .../components/insteon/translations/nl.json | 6 +- .../components/ios/translations/hu.json | 4 +- .../components/ios/translations/id.json | 4 +- .../components/ios/translations/ko.json | 2 +- .../components/ios/translations/nl.json | 4 +- .../components/ipma/translations/hu.json | 7 +- .../components/ipma/translations/id.json | 24 +++ .../components/ipma/translations/ko.json | 5 + .../components/ipma/translations/nl.json | 9 +- .../components/ipp/translations/hu.json | 7 +- .../components/ipp/translations/id.json | 35 ++++ .../components/ipp/translations/ko.json | 4 +- .../components/ipp/translations/nl.json | 6 +- .../components/iqvia/translations/hu.json | 7 + .../components/iqvia/translations/id.json | 10 +- .../islamic_prayer_times/translations/hu.json | 7 + .../islamic_prayer_times/translations/id.json | 23 +++ .../islamic_prayer_times/translations/ko.json | 2 +- .../components/isy994/translations/hu.json | 11 +- .../components/isy994/translations/id.json | 40 ++++ .../components/isy994/translations/ko.json | 2 +- .../components/isy994/translations/nl.json | 3 +- .../components/izone/translations/hu.json | 4 + .../components/izone/translations/id.json | 13 ++ .../components/izone/translations/ko.json | 2 +- .../components/juicenet/translations/de.json | 2 +- .../components/juicenet/translations/hu.json | 20 ++ .../components/juicenet/translations/id.json | 21 ++ .../components/juicenet/translations/ko.json | 2 +- .../keenetic_ndms2/translations/bg.json | 14 ++ .../keenetic_ndms2/translations/en.json | 1 + .../keenetic_ndms2/translations/hu.json | 21 ++ .../keenetic_ndms2/translations/id.json | 36 ++++ .../keenetic_ndms2/translations/ko.json | 10 +- .../keenetic_ndms2/translations/nl.json | 10 +- .../components/kmtronic/translations/bg.json | 15 ++ .../components/kmtronic/translations/ca.json | 9 + .../components/kmtronic/translations/en.json | 3 +- .../components/kmtronic/translations/es.json | 19 ++ .../components/kmtronic/translations/et.json | 9 + .../components/kmtronic/translations/fr.json | 9 + .../components/kmtronic/translations/hu.json | 21 ++ .../components/kmtronic/translations/id.json | 21 ++ .../components/kmtronic/translations/it.json | 9 + .../components/kmtronic/translations/ko.json | 30 +++ .../components/kmtronic/translations/nl.json | 9 + .../components/kmtronic/translations/no.json | 9 + .../components/kmtronic/translations/pl.json | 9 + .../components/kmtronic/translations/pt.json | 30 +++ .../components/kmtronic/translations/ru.json | 9 + .../kmtronic/translations/zh-Hant.json | 9 + .../components/kodi/translations/hu.json | 36 +++- .../components/kodi/translations/id.json | 50 +++++ .../components/kodi/translations/ko.json | 17 +- .../components/kodi/translations/nl.json | 1 + .../components/konnected/translations/hu.json | 13 ++ .../components/konnected/translations/id.json | 108 ++++++++++ .../components/konnected/translations/ko.json | 4 +- .../components/konnected/translations/nl.json | 2 +- .../components/kulersky/translations/de.json | 2 +- .../components/kulersky/translations/hu.json | 6 +- .../components/kulersky/translations/id.json | 13 ++ .../components/kulersky/translations/ko.json | 2 +- .../components/life360/translations/he.json | 11 ++ .../components/life360/translations/hu.json | 8 +- .../components/life360/translations/id.json | 22 ++- .../components/lifx/translations/hu.json | 4 +- .../components/lifx/translations/id.json | 13 ++ .../components/lifx/translations/ko.json | 4 +- .../components/lifx/translations/nl.json | 4 +- .../components/light/translations/id.json | 22 ++- .../components/light/translations/ko.json | 20 +- .../components/litejet/translations/bg.json | 11 ++ .../components/litejet/translations/hu.json | 15 ++ .../components/litejet/translations/id.json | 19 ++ .../components/litejet/translations/ko.json | 19 ++ .../components/litejet/translations/nl.json | 1 + .../components/litejet/translations/pt.json | 14 ++ .../litterrobot/translations/bg.json | 12 ++ .../litterrobot/translations/es.json | 20 ++ .../litterrobot/translations/hu.json | 20 ++ .../litterrobot/translations/id.json | 20 ++ .../litterrobot/translations/ko.json | 20 ++ .../litterrobot/translations/pt.json | 17 ++ .../components/local_ip/translations/de.json | 2 +- .../components/local_ip/translations/hu.json | 4 + .../components/local_ip/translations/id.json | 17 ++ .../components/local_ip/translations/ko.json | 2 +- .../components/local_ip/translations/nl.json | 2 +- .../components/locative/translations/de.json | 2 +- .../components/locative/translations/hu.json | 8 +- .../components/locative/translations/id.json | 17 ++ .../components/locative/translations/ko.json | 4 +- .../components/locative/translations/nl.json | 2 +- .../components/lock/translations/id.json | 17 +- .../components/lock/translations/ko.json | 14 +- .../components/lock/translations/zh-Hans.json | 10 +- .../logi_circle/translations/hu.json | 8 +- .../logi_circle/translations/id.json | 28 +++ .../logi_circle/translations/ko.json | 2 +- .../logi_circle/translations/nl.json | 2 +- .../components/lovelace/translations/id.json | 10 + .../components/lovelace/translations/ko.json | 10 + .../components/luftdaten/translations/hu.json | 2 + .../components/luftdaten/translations/id.json | 18 ++ .../lutron_caseta/translations/bg.json | 12 ++ .../lutron_caseta/translations/en.json | 2 +- .../lutron_caseta/translations/et.json | 2 +- .../lutron_caseta/translations/he.json | 9 + .../lutron_caseta/translations/hu.json | 29 +++ .../lutron_caseta/translations/id.json | 70 +++++++ .../lutron_caseta/translations/ko.json | 58 +++++- .../lutron_caseta/translations/lb.json | 32 +++ .../lutron_caseta/translations/nl.json | 28 ++- .../components/lyric/translations/hu.json | 16 ++ .../components/lyric/translations/id.json | 16 ++ .../components/mailgun/translations/hu.json | 6 +- .../components/mailgun/translations/id.json | 17 ++ .../components/mailgun/translations/ko.json | 4 +- .../components/mazda/translations/bg.json | 21 ++ .../components/mazda/translations/de.json | 12 +- .../components/mazda/translations/hu.json | 33 ++++ .../components/mazda/translations/id.json | 35 ++++ .../components/mazda/translations/ko.json | 18 +- .../components/mazda/translations/nl.json | 11 +- .../media_player/translations/hu.json | 7 + .../media_player/translations/id.json | 24 ++- .../media_player/translations/ko.json | 17 +- .../media_player/translations/zh-Hans.json | 7 + .../components/melcloud/translations/he.json | 12 ++ .../components/melcloud/translations/hu.json | 5 + .../components/melcloud/translations/id.json | 22 +++ .../components/melcloud/translations/ko.json | 2 +- .../components/met/translations/he.json | 11 ++ .../components/met/translations/hu.json | 3 + .../components/met/translations/id.json | 6 + .../meteo_france/translations/hu.json | 20 +- .../meteo_france/translations/id.json | 36 ++++ .../meteo_france/translations/ko.json | 8 +- .../meteo_france/translations/nl.json | 12 ++ .../components/metoffice/translations/he.json | 11 ++ .../components/metoffice/translations/hu.json | 16 +- .../components/metoffice/translations/id.json | 22 +++ .../components/metoffice/translations/nl.json | 1 + .../components/mikrotik/translations/he.json | 12 ++ .../components/mikrotik/translations/hu.json | 5 +- .../components/mikrotik/translations/id.json | 36 ++++ .../components/mill/translations/hu.json | 3 + .../components/mill/translations/id.json | 18 ++ .../minecraft_server/translations/hu.json | 2 +- .../minecraft_server/translations/id.json | 22 +++ .../mobile_app/translations/ca.json | 3 +- .../mobile_app/translations/en.json | 3 +- .../mobile_app/translations/et.json | 3 +- .../mobile_app/translations/fr.json | 3 +- .../mobile_app/translations/hu.json | 3 +- .../mobile_app/translations/id.json | 18 ++ .../mobile_app/translations/it.json | 3 +- .../mobile_app/translations/ko.json | 12 +- .../mobile_app/translations/nl.json | 3 +- .../mobile_app/translations/no.json | 3 +- .../mobile_app/translations/pl.json | 3 +- .../mobile_app/translations/ru.json | 3 +- .../mobile_app/translations/zh-Hans.json | 8 +- .../mobile_app/translations/zh-Hant.json | 3 +- .../components/monoprice/translations/hu.json | 7 + .../components/monoprice/translations/id.json | 40 ++++ .../components/monoprice/translations/nl.json | 4 +- .../moon/translations/sensor.id.json | 14 ++ .../motion_blinds/translations/bg.json | 11 ++ .../motion_blinds/translations/hu.json | 27 +++ .../motion_blinds/translations/id.json | 37 ++++ .../motion_blinds/translations/ko.json | 16 +- .../components/mqtt/translations/bg.json | 4 +- .../components/mqtt/translations/ca.json | 4 +- .../components/mqtt/translations/cs.json | 4 +- .../components/mqtt/translations/da.json | 4 +- .../components/mqtt/translations/de.json | 4 +- .../components/mqtt/translations/es-419.json | 4 +- .../components/mqtt/translations/es.json | 4 +- .../components/mqtt/translations/et.json | 4 +- .../components/mqtt/translations/fr.json | 4 +- .../components/mqtt/translations/hu.json | 23 ++- .../components/mqtt/translations/id.json | 62 +++++- .../components/mqtt/translations/ko.json | 27 +-- .../components/mqtt/translations/lb.json | 4 +- .../components/mqtt/translations/nl.json | 11 +- .../components/mqtt/translations/no.json | 4 +- .../components/mqtt/translations/pl.json | 2 +- .../components/mqtt/translations/pt-BR.json | 4 +- .../components/mqtt/translations/pt.json | 4 +- .../components/mqtt/translations/ro.json | 2 +- .../components/mqtt/translations/sl.json | 4 +- .../components/mqtt/translations/sv.json | 4 +- .../components/mqtt/translations/uk.json | 4 +- .../components/mqtt/translations/zh-Hans.json | 4 +- .../components/mqtt/translations/zh-Hant.json | 4 +- .../components/mullvad/translations/bg.json | 15 ++ .../components/mullvad/translations/es.json | 12 ++ .../components/mullvad/translations/hu.json | 21 ++ .../components/mullvad/translations/id.json | 22 +++ .../components/mullvad/translations/ko.json | 22 +++ .../components/mullvad/translations/pl.json | 22 +++ .../components/mullvad/translations/pt.json | 21 ++ .../mullvad/translations/zh-Hans.json | 20 ++ .../components/myq/translations/he.json | 12 ++ .../components/myq/translations/hu.json | 8 + .../components/myq/translations/id.json | 21 ++ .../components/mysensors/translations/bg.json | 22 +++ .../components/mysensors/translations/hu.json | 31 +++ .../components/mysensors/translations/id.json | 75 +++++++ .../components/mysensors/translations/ko.json | 65 ++++++- .../components/mysensors/translations/nl.json | 3 + .../components/neato/translations/de.json | 2 +- .../components/neato/translations/he.json | 11 ++ .../components/neato/translations/hu.json | 24 ++- .../components/neato/translations/id.json | 37 ++++ .../components/neato/translations/ko.json | 3 +- .../components/nest/translations/ca.json | 2 +- .../components/nest/translations/en.json | 2 +- .../components/nest/translations/et.json | 2 +- .../components/nest/translations/fr.json | 2 +- .../components/nest/translations/hu.json | 22 ++- .../components/nest/translations/id.json | 40 +++- .../components/nest/translations/ko.json | 17 +- .../components/nest/translations/nl.json | 8 +- .../components/nest/translations/no.json | 2 +- .../components/nest/translations/ru.json | 2 +- .../components/nest/translations/sv.json | 5 + .../components/netatmo/translations/de.json | 22 +++ .../components/netatmo/translations/hu.json | 28 ++- .../components/netatmo/translations/id.json | 64 ++++++ .../components/netatmo/translations/ko.json | 32 ++- .../components/netatmo/translations/nl.json | 7 +- .../components/netatmo/translations/pl.json | 22 +++ .../netatmo/translations/zh-Hans.json | 16 ++ .../components/nexia/translations/he.json | 12 ++ .../components/nexia/translations/hu.json | 11 +- .../components/nexia/translations/id.json | 21 ++ .../components/nexia/translations/ko.json | 2 +- .../components/nexia/translations/nl.json | 2 +- .../nightscout/translations/hu.json | 14 ++ .../nightscout/translations/id.json | 23 +++ .../nightscout/translations/ko.json | 5 +- .../components/notify/translations/hu.json | 2 +- .../components/notify/translations/id.json | 2 +- .../components/notion/translations/he.json | 11 ++ .../components/notion/translations/hu.json | 4 + .../components/notion/translations/id.json | 20 ++ .../components/notion/translations/nl.json | 2 +- .../components/nuheat/translations/he.json | 12 ++ .../components/nuheat/translations/hu.json | 9 +- .../components/nuheat/translations/id.json | 24 +++ .../components/nuheat/translations/ko.json | 4 +- .../components/nuheat/translations/nl.json | 4 +- .../components/nuki/translations/bg.json | 11 ++ .../components/nuki/translations/hu.json | 18 ++ .../components/nuki/translations/id.json | 18 ++ .../components/number/translations/de.json | 3 + .../components/number/translations/hu.json | 8 + .../components/number/translations/id.json | 8 + .../components/number/translations/ko.json | 8 + .../number/translations/zh-Hans.json | 7 + .../components/nut/translations/ca.json | 4 + .../components/nut/translations/de.json | 4 + .../components/nut/translations/et.json | 4 + .../components/nut/translations/fr.json | 4 + .../components/nut/translations/he.json | 12 ++ .../components/nut/translations/hu.json | 13 ++ .../components/nut/translations/id.json | 50 +++++ .../components/nut/translations/it.json | 4 + .../components/nut/translations/ko.json | 6 +- .../components/nut/translations/nl.json | 6 +- .../components/nut/translations/no.json | 4 + .../components/nut/translations/pl.json | 4 + .../components/nut/translations/ru.json | 4 + .../components/nut/translations/zh-Hans.json | 11 ++ .../components/nut/translations/zh-Hant.json | 4 + .../components/nws/translations/he.json | 11 ++ .../components/nws/translations/hu.json | 11 +- .../components/nws/translations/id.json | 23 +++ .../components/nws/translations/ko.json | 2 +- .../components/nws/translations/nl.json | 6 +- .../components/nzbget/translations/hu.json | 35 ++++ .../components/nzbget/translations/id.json | 35 ++++ .../components/nzbget/translations/ko.json | 6 +- .../components/omnilogic/translations/de.json | 9 + .../components/omnilogic/translations/hu.json | 29 +++ .../components/omnilogic/translations/id.json | 29 +++ .../components/omnilogic/translations/ko.json | 2 +- .../onboarding/translations/id.json | 4 +- .../ondilo_ico/translations/de.json | 3 +- .../ondilo_ico/translations/hu.json | 16 ++ .../ondilo_ico/translations/id.json | 17 ++ .../ondilo_ico/translations/ko.json | 3 +- .../components/onewire/translations/de.json | 6 +- .../components/onewire/translations/hu.json | 8 +- .../components/onewire/translations/id.json | 26 +++ .../components/onewire/translations/ko.json | 12 +- .../components/onvif/translations/he.json | 6 + .../components/onvif/translations/hu.json | 20 +- .../components/onvif/translations/id.json | 59 ++++++ .../opentherm_gw/translations/hu.json | 3 +- .../opentherm_gw/translations/id.json | 30 +++ .../components/openuv/translations/hu.json | 3 + .../components/openuv/translations/id.json | 7 +- .../components/openuv/translations/ko.json | 2 +- .../components/openuv/translations/nl.json | 4 +- .../openweathermap/translations/he.json | 11 ++ .../openweathermap/translations/hu.json | 35 ++++ .../openweathermap/translations/id.json | 35 ++++ .../openweathermap/translations/ko.json | 4 +- .../ovo_energy/translations/de.json | 4 +- .../ovo_energy/translations/hu.json | 14 +- .../ovo_energy/translations/id.json | 27 +++ .../ovo_energy/translations/ko.json | 6 +- .../ovo_energy/translations/nl.json | 3 +- .../components/owntracks/translations/hu.json | 3 + .../components/owntracks/translations/id.json | 16 ++ .../components/owntracks/translations/ko.json | 6 +- .../components/ozw/translations/fr.json | 14 +- .../components/ozw/translations/hu.json | 13 +- .../components/ozw/translations/id.json | 41 ++++ .../components/ozw/translations/ko.json | 28 ++- .../components/ozw/translations/nl.json | 7 +- .../components/ozw/translations/zh-Hant.json | 20 +- .../panasonic_viera/translations/hu.json | 20 +- .../panasonic_viera/translations/id.json | 30 +++ .../panasonic_viera/translations/nl.json | 10 +- .../components/person/translations/id.json | 2 +- .../philips_js/translations/bg.json | 8 + .../philips_js/translations/de.json | 1 + .../philips_js/translations/es.json | 4 + .../philips_js/translations/hu.json | 21 ++ .../philips_js/translations/id.json | 26 +++ .../philips_js/translations/it.json | 2 + .../philips_js/translations/ko.json | 8 + .../philips_js/translations/nl.json | 7 + .../philips_js/translations/pl.json | 2 + .../philips_js/translations/zh-Hans.json | 7 + .../components/pi_hole/translations/hu.json | 12 +- .../components/pi_hole/translations/id.json | 29 +++ .../components/pi_hole/translations/ko.json | 1 + .../components/plaato/translations/de.json | 2 +- .../components/plaato/translations/en.json | 2 +- .../components/plaato/translations/et.json | 2 +- .../components/plaato/translations/hu.json | 13 +- .../components/plaato/translations/id.json | 54 +++++ .../components/plaato/translations/ko.json | 40 +++- .../components/plaato/translations/nl.json | 11 ++ .../components/plaato/translations/no.json | 2 +- .../components/plaato/translations/pl.json | 4 +- .../components/plant/translations/id.json | 6 +- .../components/plex/translations/hu.json | 13 +- .../components/plex/translations/id.json | 62 ++++++ .../components/plugwise/translations/hu.json | 22 ++- .../components/plugwise/translations/id.json | 42 ++++ .../components/plugwise/translations/ko.json | 13 +- .../components/plugwise/translations/nl.json | 8 +- .../plum_lightpad/translations/hu.json | 11 ++ .../plum_lightpad/translations/id.json | 18 ++ .../components/point/translations/de.json | 2 +- .../components/point/translations/hu.json | 5 +- .../components/point/translations/id.json | 32 +++ .../components/point/translations/ko.json | 6 +- .../components/point/translations/nl.json | 14 +- .../components/poolsense/translations/de.json | 2 +- .../components/poolsense/translations/hu.json | 13 ++ .../components/poolsense/translations/id.json | 20 ++ .../components/powerwall/translations/bg.json | 11 ++ .../components/powerwall/translations/hu.json | 13 +- .../components/powerwall/translations/id.json | 25 +++ .../components/powerwall/translations/ko.json | 4 +- .../components/powerwall/translations/nl.json | 5 +- .../components/profiler/translations/hu.json | 4 +- .../components/profiler/translations/id.json | 12 ++ .../components/profiler/translations/ko.json | 2 +- .../progettihwsw/translations/hu.json | 26 +++ .../progettihwsw/translations/id.json | 41 ++++ .../progettihwsw/translations/ko.json | 4 +- .../components/ps4/translations/hu.json | 11 +- .../components/ps4/translations/id.json | 41 ++++ .../components/ps4/translations/ko.json | 8 +- .../components/ps4/translations/nl.json | 14 +- .../pvpc_hourly_pricing/translations/hu.json | 7 + .../pvpc_hourly_pricing/translations/id.json | 17 ++ .../pvpc_hourly_pricing/translations/ko.json | 2 +- .../pvpc_hourly_pricing/translations/nl.json | 2 +- .../components/rachio/translations/hu.json | 8 + .../components/rachio/translations/id.json | 30 +++ .../components/rachio/translations/ko.json | 2 +- .../components/rachio/translations/nl.json | 4 +- .../rainmachine/translations/he.json | 11 ++ .../rainmachine/translations/hu.json | 9 +- .../rainmachine/translations/id.json | 30 +++ .../rainmachine/translations/ko.json | 10 + .../recollect_waste/translations/hu.json | 7 + .../recollect_waste/translations/id.json | 28 +++ .../recollect_waste/translations/ko.json | 21 ++ .../components/remote/translations/hu.json | 15 ++ .../components/remote/translations/id.json | 19 +- .../components/remote/translations/ko.json | 14 +- .../remote/translations/zh-Hans.json | 4 +- .../components/rfxtrx/translations/de.json | 3 +- .../components/rfxtrx/translations/fr.json | 10 +- .../components/rfxtrx/translations/hu.json | 19 +- .../components/rfxtrx/translations/id.json | 73 +++++++ .../components/rfxtrx/translations/ko.json | 50 ++++- .../components/rfxtrx/translations/nl.json | 6 + .../components/ring/translations/he.json | 12 ++ .../components/ring/translations/hu.json | 6 +- .../components/ring/translations/id.json | 26 +++ .../components/ring/translations/zh-Hant.json | 4 +- .../components/risco/translations/hu.json | 21 ++ .../components/risco/translations/id.json | 55 ++++++ .../components/risco/translations/ko.json | 23 +-- .../translations/bg.json | 11 ++ .../translations/es.json | 12 ++ .../translations/hu.json | 20 ++ .../translations/id.json | 21 ++ .../translations/ko.json | 21 ++ .../components/roku/translations/hu.json | 16 +- .../components/roku/translations/id.json | 29 +++ .../components/roku/translations/ko.json | 6 +- .../components/roku/translations/nl.json | 8 +- .../components/roomba/translations/bg.json | 12 ++ .../components/roomba/translations/ca.json | 2 +- .../components/roomba/translations/en.json | 2 +- .../components/roomba/translations/et.json | 2 +- .../components/roomba/translations/he.json | 11 ++ .../components/roomba/translations/hu.json | 41 ++++ .../components/roomba/translations/id.json | 62 ++++++ .../components/roomba/translations/ko.json | 23 ++- .../components/roomba/translations/lb.json | 1 + .../components/roomba/translations/nl.json | 12 +- .../components/roon/translations/hu.json | 12 ++ .../components/roon/translations/id.json | 24 +++ .../components/roon/translations/ko.json | 6 +- .../components/rpi_power/translations/de.json | 3 +- .../components/rpi_power/translations/hu.json | 13 ++ .../components/rpi_power/translations/id.json | 14 ++ .../components/rpi_power/translations/ko.json | 4 +- .../ruckus_unleashed/translations/he.json | 16 ++ .../ruckus_unleashed/translations/hu.json | 8 +- .../ruckus_unleashed/translations/id.json | 21 ++ .../components/samsungtv/translations/hu.json | 4 +- .../components/samsungtv/translations/id.json | 25 +++ .../components/samsungtv/translations/ko.json | 4 +- .../components/scene/translations/id.json | 2 +- .../components/script/translations/id.json | 4 +- .../season/translations/sensor.id.json | 16 ++ .../components/sense/translations/he.json | 11 ++ .../components/sense/translations/hu.json | 8 + .../components/sense/translations/id.json | 21 ++ .../components/sense/translations/ko.json | 2 +- .../components/sense/translations/nl.json | 4 +- .../components/sensor/translations/ca.json | 4 + .../components/sensor/translations/en.json | 4 + .../components/sensor/translations/et.json | 4 + .../components/sensor/translations/fr.json | 4 + .../components/sensor/translations/hu.json | 3 +- .../components/sensor/translations/id.json | 40 +++- .../components/sensor/translations/it.json | 4 + .../components/sensor/translations/ko.json | 48 +++-- .../components/sensor/translations/nl.json | 4 + .../components/sensor/translations/no.json | 4 + .../components/sensor/translations/pl.json | 4 + .../components/sensor/translations/ru.json | 4 + .../sensor/translations/zh-Hant.json | 4 + .../components/sentry/translations/hu.json | 5 +- .../components/sentry/translations/id.json | 34 ++++ .../components/sentry/translations/ko.json | 17 +- .../components/sharkiq/translations/hu.json | 29 +++ .../components/sharkiq/translations/id.json | 29 +++ .../components/shelly/translations/bg.json | 10 + .../components/shelly/translations/de.json | 19 +- .../components/shelly/translations/hu.json | 39 +++- .../components/shelly/translations/id.json | 47 +++++ .../components/shelly/translations/ko.json | 25 ++- .../components/shelly/translations/nl.json | 9 +- .../shopping_list/translations/hu.json | 2 +- .../shopping_list/translations/id.json | 14 ++ .../shopping_list/translations/nl.json | 2 +- .../simplisafe/translations/he.json | 11 ++ .../simplisafe/translations/hu.json | 14 +- .../simplisafe/translations/id.json | 45 +++++ .../simplisafe/translations/ko.json | 8 +- .../simplisafe/translations/nl.json | 2 +- .../components/smappee/translations/hu.json | 16 +- .../components/smappee/translations/id.json | 35 ++++ .../components/smappee/translations/ko.json | 16 +- .../components/smappee/translations/nl.json | 1 + .../smart_meter_texas/translations/hu.json | 13 ++ .../smart_meter_texas/translations/id.json | 20 ++ .../components/smarthab/translations/hu.json | 8 + .../components/smarthab/translations/id.json | 19 ++ .../components/smarthab/translations/ko.json | 2 +- .../smartthings/translations/hu.json | 10 +- .../smartthings/translations/id.json | 38 ++++ .../smartthings/translations/ko.json | 12 +- .../smartthings/translations/nl.json | 6 +- .../components/smarttub/translations/bg.json | 14 ++ .../components/smarttub/translations/hu.json | 22 +++ .../components/smarttub/translations/id.json | 22 +++ .../components/smarttub/translations/ko.json | 4 +- .../components/smarttub/translations/nl.json | 1 + .../components/smarttub/translations/pt.json | 20 ++ .../components/smhi/translations/he.json | 11 ++ .../components/smhi/translations/id.json | 18 ++ .../components/sms/translations/hu.json | 15 +- .../components/sms/translations/id.json | 20 ++ .../components/sms/translations/ko.json | 2 +- .../components/solaredge/translations/hu.json | 5 + .../components/solaredge/translations/id.json | 25 +++ .../components/solaredge/translations/ko.json | 4 +- .../components/solarlog/translations/hu.json | 6 +- .../components/solarlog/translations/id.json | 20 ++ .../components/soma/translations/hu.json | 3 +- .../components/soma/translations/id.json | 24 +++ .../components/soma/translations/ko.json | 4 +- .../components/somfy/translations/hu.json | 10 +- .../components/somfy/translations/id.json | 18 ++ .../components/somfy/translations/ko.json | 2 +- .../somfy_mylink/translations/bg.json | 11 ++ .../somfy_mylink/translations/he.json | 9 + .../somfy_mylink/translations/hu.json | 32 +++ .../somfy_mylink/translations/id.json | 53 +++++ .../somfy_mylink/translations/ko.json | 34 +++- .../somfy_mylink/translations/nl.json | 4 + .../components/sonarr/translations/hu.json | 34 +++- .../components/sonarr/translations/id.json | 40 ++++ .../components/sonarr/translations/ko.json | 3 +- .../components/sonarr/translations/nl.json | 12 ++ .../components/songpal/translations/hu.json | 7 +- .../components/songpal/translations/id.json | 22 +++ .../components/sonos/translations/hu.json | 4 +- .../components/sonos/translations/id.json | 6 +- .../components/sonos/translations/ko.json | 4 +- .../components/sonos/translations/nl.json | 4 +- .../speedtestdotnet/translations/de.json | 2 +- .../speedtestdotnet/translations/hu.json | 22 +++ .../speedtestdotnet/translations/id.json | 24 +++ .../speedtestdotnet/translations/ko.json | 4 +- .../speedtestdotnet/translations/nl.json | 11 ++ .../components/spider/translations/hu.json | 20 ++ .../components/spider/translations/id.json | 20 ++ .../components/spider/translations/ko.json | 5 +- .../components/spider/translations/nl.json | 3 +- .../components/spotify/translations/hu.json | 13 +- .../components/spotify/translations/id.json | 27 +++ .../components/spotify/translations/ko.json | 13 +- .../components/spotify/translations/nl.json | 5 + .../squeezebox/translations/hu.json | 25 ++- .../squeezebox/translations/id.json | 31 +++ .../srp_energy/translations/hu.json | 15 +- .../srp_energy/translations/id.json | 24 +++ .../srp_energy/translations/ko.json | 8 +- .../srp_energy/translations/nl.json | 1 + .../components/starline/translations/he.json | 12 ++ .../components/starline/translations/hu.json | 2 +- .../components/starline/translations/id.json | 41 ++++ .../starline/translations/zh-Hant.json | 2 +- .../components/subaru/translations/bg.json | 16 ++ .../components/subaru/translations/hu.json | 38 ++++ .../components/subaru/translations/id.json | 44 +++++ .../components/subaru/translations/ko.json | 44 +++++ .../components/subaru/translations/nl.json | 6 + .../components/sun/translations/id.json | 2 +- .../components/switch/translations/id.json | 19 +- .../components/switch/translations/ko.json | 14 +- .../switch/translations/zh-Hans.json | 2 +- .../components/syncthru/translations/hu.json | 17 ++ .../components/syncthru/translations/id.json | 27 +++ .../components/syncthru/translations/nl.json | 4 +- .../synology_dsm/translations/he.json | 16 ++ .../synology_dsm/translations/hu.json | 27 ++- .../synology_dsm/translations/id.json | 55 ++++++ .../synology_dsm/translations/nl.json | 10 +- .../synology_dsm/translations/zh-Hant.json | 4 +- .../system_health/translations/id.json | 3 + .../components/tado/translations/he.json | 12 ++ .../components/tado/translations/hu.json | 8 + .../components/tado/translations/id.json | 32 +++ .../components/tado/translations/ko.json | 2 +- .../components/tado/translations/nl.json | 2 +- .../components/tag/translations/hu.json | 3 + .../components/tag/translations/id.json | 3 + .../components/tasmota/translations/hu.json | 7 + .../components/tasmota/translations/id.json | 22 +++ .../components/tasmota/translations/ko.json | 17 +- .../tellduslive/translations/hu.json | 10 +- .../tellduslive/translations/id.json | 26 +++ .../tellduslive/translations/ko.json | 4 +- .../tellduslive/translations/nl.json | 2 +- .../components/tesla/translations/he.json | 12 ++ .../components/tesla/translations/hu.json | 9 + .../components/tesla/translations/id.json | 33 ++++ .../components/tesla/translations/nl.json | 2 +- .../components/tibber/translations/hu.json | 10 +- .../components/tibber/translations/id.json | 21 ++ .../components/tile/translations/hu.json | 29 +++ .../components/tile/translations/id.json | 29 +++ .../components/toon/translations/hu.json | 10 + .../components/toon/translations/id.json | 25 +++ .../components/toon/translations/ko.json | 2 +- .../totalconnect/translations/es.json | 3 + .../totalconnect/translations/he.json | 17 ++ .../totalconnect/translations/hu.json | 15 ++ .../totalconnect/translations/id.json | 32 +++ .../totalconnect/translations/ko.json | 17 +- .../totalconnect/translations/nl.json | 10 +- .../components/tplink/translations/hu.json | 8 + .../components/tplink/translations/id.json | 13 ++ .../components/tplink/translations/ko.json | 2 +- .../components/tplink/translations/nl.json | 4 +- .../components/traccar/translations/hu.json | 6 +- .../components/traccar/translations/id.json | 17 ++ .../components/traccar/translations/ko.json | 6 +- .../components/tradfri/translations/hu.json | 5 +- .../components/tradfri/translations/id.json | 7 +- .../components/tradfri/translations/ko.json | 2 +- .../components/tradfri/translations/nl.json | 4 +- .../transmission/translations/he.json | 11 ++ .../transmission/translations/hu.json | 8 +- .../transmission/translations/id.json | 36 ++++ .../transmission/translations/ko.json | 2 +- .../components/tuya/translations/de.json | 1 + .../components/tuya/translations/hu.json | 18 +- .../components/tuya/translations/id.json | 65 +++++++ .../components/tuya/translations/ko.json | 37 +++- .../components/tuya/translations/nl.json | 3 + .../components/tuya/translations/pl.json | 1 + .../twentemilieu/translations/hu.json | 6 + .../twentemilieu/translations/id.json | 22 +++ .../twentemilieu/translations/ko.json | 2 +- .../components/twilio/translations/de.json | 2 +- .../components/twilio/translations/hu.json | 8 +- .../components/twilio/translations/id.json | 17 ++ .../components/twilio/translations/ko.json | 4 +- .../components/twilio/translations/nl.json | 2 +- .../components/twinkly/translations/hu.json | 15 ++ .../components/twinkly/translations/id.json | 19 ++ .../components/twinkly/translations/ko.json | 9 + .../components/unifi/translations/he.json | 11 ++ .../components/unifi/translations/hu.json | 10 +- .../components/unifi/translations/id.json | 69 +++++++ .../components/unifi/translations/ko.json | 7 +- .../components/unifi/translations/lb.json | 1 + .../components/unifi/translations/nl.json | 7 +- .../components/upb/translations/hu.json | 18 ++ .../components/upb/translations/id.json | 23 +++ .../components/upb/translations/ko.json | 4 +- .../components/upb/translations/pt.json | 1 + .../components/upcloud/translations/hu.json | 2 +- .../components/upcloud/translations/id.json | 25 +++ .../components/upcloud/translations/ko.json | 9 + .../updater/translations/zh-Hant.json | 2 +- .../components/upnp/translations/de.json | 1 + .../components/upnp/translations/hu.json | 12 +- .../components/upnp/translations/id.json | 21 ++ .../components/upnp/translations/nl.json | 8 +- .../components/vacuum/translations/id.json | 20 +- .../components/vacuum/translations/ko.json | 12 +- .../components/vacuum/translations/nl.json | 2 +- .../vacuum/translations/zh-Hans.json | 3 +- .../components/velbus/translations/de.json | 2 +- .../components/velbus/translations/hu.json | 11 ++ .../components/velbus/translations/id.json | 20 ++ .../components/vera/translations/id.json | 30 +++ .../components/vera/translations/ko.json | 8 +- .../components/verisure/translations/ca.json | 39 ++++ .../components/verisure/translations/et.json | 39 ++++ .../components/verisure/translations/fr.json | 39 ++++ .../components/verisure/translations/pt.json | 11 ++ .../components/vesync/translations/he.json | 11 ++ .../components/vesync/translations/hu.json | 6 + .../components/vesync/translations/id.json | 19 ++ .../components/vesync/translations/ko.json | 4 +- .../components/vilfo/translations/hu.json | 8 + .../components/vilfo/translations/id.json | 22 +++ .../components/vilfo/translations/ko.json | 2 +- .../components/vilfo/translations/nl.json | 10 +- .../components/vizio/translations/hu.json | 16 +- .../components/vizio/translations/id.json | 54 +++++ .../components/vizio/translations/ko.json | 10 +- .../components/vizio/translations/nl.json | 2 +- .../components/volumio/translations/hu.json | 19 +- .../components/volumio/translations/id.json | 24 +++ .../components/volumio/translations/ko.json | 7 +- .../components/volumio/translations/nl.json | 7 +- .../water_heater/translations/ca.json | 11 ++ .../water_heater/translations/et.json | 11 ++ .../water_heater/translations/fr.json | 11 ++ .../water_heater/translations/id.json | 8 + .../water_heater/translations/ko.json | 19 ++ .../water_heater/translations/pt.json | 5 + .../water_heater/translations/ru.json | 11 ++ .../components/wemo/translations/hu.json | 8 + .../components/wemo/translations/id.json | 13 ++ .../components/wemo/translations/ko.json | 4 +- .../components/wiffi/translations/hu.json | 16 ++ .../components/wiffi/translations/id.json | 25 +++ .../components/wiffi/translations/nl.json | 2 +- .../components/wilight/translations/hu.json | 5 + .../components/wilight/translations/id.json | 16 ++ .../components/wilight/translations/ko.json | 7 +- .../components/withings/translations/hu.json | 13 +- .../components/withings/translations/id.json | 33 ++++ .../components/withings/translations/ko.json | 4 +- .../components/withings/translations/nl.json | 3 +- .../components/wled/translations/hu.json | 2 +- .../components/wled/translations/id.json | 24 +++ .../components/wled/translations/ko.json | 4 +- .../components/wled/translations/nl.json | 4 +- .../components/wolflink/translations/hu.json | 18 ++ .../components/wolflink/translations/id.json | 27 +++ .../components/wolflink/translations/nl.json | 6 + .../wolflink/translations/sensor.de.json | 1 + .../wolflink/translations/sensor.hu.json | 7 + .../wolflink/translations/sensor.id.json | 47 +++++ .../wolflink/translations/sensor.ko.json | 70 ++++++- .../wolflink/translations/sensor.nl.json | 24 ++- .../components/xbox/translations/hu.json | 12 +- .../components/xbox/translations/id.json | 17 ++ .../components/xbox/translations/ko.json | 2 +- .../xiaomi_aqara/translations/de.json | 3 +- .../xiaomi_aqara/translations/hu.json | 11 +- .../xiaomi_aqara/translations/id.json | 43 ++++ .../xiaomi_aqara/translations/ko.json | 9 +- .../xiaomi_aqara/translations/nl.json | 10 +- .../xiaomi_miio/translations/bg.json | 11 ++ .../xiaomi_miio/translations/ca.json | 2 +- .../xiaomi_miio/translations/de.json | 12 +- .../xiaomi_miio/translations/hu.json | 16 +- .../xiaomi_miio/translations/id.json | 42 ++++ .../xiaomi_miio/translations/it.json | 2 +- .../xiaomi_miio/translations/ko.json | 11 +- .../xiaomi_miio/translations/nl.json | 9 +- .../xiaomi_miio/translations/no.json | 2 +- .../xiaomi_miio/translations/pl.json | 2 +- .../xiaomi_miio/translations/ru.json | 2 +- .../xiaomi_miio/translations/zh-Hant.json | 2 +- .../components/yeelight/translations/hu.json | 4 +- .../components/yeelight/translations/id.json | 38 ++++ .../components/yeelight/translations/ko.json | 12 +- .../components/zerproc/translations/id.json | 13 ++ .../components/zerproc/translations/ko.json | 2 +- .../components/zha/translations/hu.json | 9 +- .../components/zha/translations/id.json | 91 +++++++++ .../components/zha/translations/ko.json | 51 ++--- .../components/zha/translations/nl.json | 5 +- .../zodiac/translations/sensor.hu.json | 18 ++ .../zodiac/translations/sensor.id.json | 18 ++ .../zodiac/translations/sensor.ko.json | 24 +-- .../components/zone/translations/id.json | 2 +- .../zoneminder/translations/hu.json | 19 +- .../zoneminder/translations/id.json | 34 ++++ .../zoneminder/translations/ko.json | 10 +- .../components/zwave/translations/hu.json | 3 +- .../components/zwave/translations/id.json | 23 ++- .../components/zwave/translations/ko.json | 2 +- .../components/zwave/translations/nl.json | 6 +- .../components/zwave_js/translations/bg.json | 19 ++ .../components/zwave_js/translations/en.json | 6 + .../components/zwave_js/translations/fr.json | 4 +- .../components/zwave_js/translations/hu.json | 49 +++++ .../components/zwave_js/translations/id.json | 61 ++++++ .../components/zwave_js/translations/ko.json | 35 +++- .../components/zwave_js/translations/nl.json | 7 +- .../zwave_js/translations/zh-Hant.json | 30 +-- 1350 files changed, 17392 insertions(+), 1763 deletions(-) create mode 100644 homeassistant/components/abode/translations/he.json create mode 100644 homeassistant/components/abode/translations/id.json create mode 100644 homeassistant/components/accuweather/translations/he.json create mode 100644 homeassistant/components/accuweather/translations/hu.json create mode 100644 homeassistant/components/accuweather/translations/id.json create mode 100644 homeassistant/components/accuweather/translations/sensor.hu.json create mode 100644 homeassistant/components/accuweather/translations/sensor.id.json create mode 100644 homeassistant/components/accuweather/translations/sensor.ko.json create mode 100644 homeassistant/components/acmeda/translations/hu.json create mode 100644 homeassistant/components/acmeda/translations/id.json create mode 100644 homeassistant/components/advantage_air/translations/id.json create mode 100644 homeassistant/components/aemet/translations/hu.json create mode 100644 homeassistant/components/aemet/translations/id.json create mode 100644 homeassistant/components/aemet/translations/lb.json create mode 100644 homeassistant/components/agent_dvr/translations/id.json create mode 100644 homeassistant/components/airly/translations/he.json create mode 100644 homeassistant/components/airly/translations/id.json create mode 100644 homeassistant/components/airnow/translations/bg.json create mode 100644 homeassistant/components/airnow/translations/hu.json create mode 100644 homeassistant/components/airnow/translations/id.json create mode 100644 homeassistant/components/airvisual/translations/bg.json create mode 100644 homeassistant/components/airvisual/translations/id.json create mode 100644 homeassistant/components/alarmdecoder/translations/id.json create mode 100644 homeassistant/components/almond/translations/id.json create mode 100644 homeassistant/components/ambiclimate/translations/id.json create mode 100644 homeassistant/components/ambient_station/translations/id.json create mode 100644 homeassistant/components/apple_tv/translations/id.json create mode 100644 homeassistant/components/arcam_fmj/translations/id.json create mode 100644 homeassistant/components/asuswrt/translations/bg.json create mode 100644 homeassistant/components/asuswrt/translations/hu.json create mode 100644 homeassistant/components/asuswrt/translations/id.json create mode 100644 homeassistant/components/atag/translations/id.json create mode 100644 homeassistant/components/august/translations/he.json create mode 100644 homeassistant/components/august/translations/id.json create mode 100644 homeassistant/components/aurora/translations/id.json create mode 100644 homeassistant/components/awair/translations/id.json create mode 100644 homeassistant/components/axis/translations/he.json create mode 100644 homeassistant/components/axis/translations/id.json create mode 100644 homeassistant/components/azure_devops/translations/id.json create mode 100644 homeassistant/components/blebox/translations/id.json create mode 100644 homeassistant/components/blink/translations/id.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/bg.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/hu.json create mode 100644 homeassistant/components/bmw_connected_drive/translations/id.json create mode 100644 homeassistant/components/bond/translations/id.json create mode 100644 homeassistant/components/braviatv/translations/id.json create mode 100644 homeassistant/components/broadlink/translations/id.json create mode 100644 homeassistant/components/brother/translations/id.json create mode 100644 homeassistant/components/bsblan/translations/id.json create mode 100644 homeassistant/components/canary/translations/hu.json create mode 100644 homeassistant/components/canary/translations/id.json create mode 100644 homeassistant/components/cert_expiry/translations/id.json create mode 100644 homeassistant/components/climacell/translations/bg.json create mode 100644 homeassistant/components/climacell/translations/hu.json create mode 100644 homeassistant/components/climacell/translations/id.json create mode 100644 homeassistant/components/climacell/translations/ko.json create mode 100644 homeassistant/components/climacell/translations/pl.json create mode 100644 homeassistant/components/climacell/translations/pt.json create mode 100644 homeassistant/components/climacell/translations/zh-Hans.json create mode 100644 homeassistant/components/cloud/translations/id.json create mode 100644 homeassistant/components/cloud/translations/ko.json create mode 100644 homeassistant/components/cloudflare/translations/id.json create mode 100644 homeassistant/components/control4/translations/id.json create mode 100644 homeassistant/components/coolmaster/translations/id.json create mode 100644 homeassistant/components/coronavirus/translations/id.json create mode 100644 homeassistant/components/daikin/translations/id.json create mode 100644 homeassistant/components/demo/translations/id.json create mode 100644 homeassistant/components/denonavr/translations/id.json create mode 100644 homeassistant/components/devolo_home_control/translations/id.json create mode 100644 homeassistant/components/dexcom/translations/id.json create mode 100644 homeassistant/components/dialogflow/translations/id.json create mode 100644 homeassistant/components/directv/translations/id.json create mode 100644 homeassistant/components/doorbird/translations/id.json create mode 100644 homeassistant/components/dsmr/translations/id.json create mode 100644 homeassistant/components/dunehd/translations/id.json create mode 100644 homeassistant/components/eafm/translations/id.json create mode 100644 homeassistant/components/ebusd/translations/id.json create mode 100644 homeassistant/components/ecobee/translations/id.json create mode 100644 homeassistant/components/econet/translations/bg.json create mode 100644 homeassistant/components/econet/translations/hu.json create mode 100644 homeassistant/components/econet/translations/id.json create mode 100644 homeassistant/components/elgato/translations/id.json create mode 100644 homeassistant/components/elkm1/translations/he.json create mode 100644 homeassistant/components/elkm1/translations/id.json create mode 100644 homeassistant/components/emulated_roku/translations/id.json create mode 100644 homeassistant/components/enocean/translations/hu.json create mode 100644 homeassistant/components/enocean/translations/id.json create mode 100644 homeassistant/components/epson/translations/id.json create mode 100644 homeassistant/components/esphome/translations/he.json create mode 100644 homeassistant/components/faa_delays/translations/bg.json create mode 100644 homeassistant/components/faa_delays/translations/hu.json create mode 100644 homeassistant/components/faa_delays/translations/id.json create mode 100644 homeassistant/components/faa_delays/translations/ko.json create mode 100644 homeassistant/components/faa_delays/translations/pl.json create mode 100644 homeassistant/components/faa_delays/translations/pt.json create mode 100644 homeassistant/components/faa_delays/translations/zh-Hans.json create mode 100644 homeassistant/components/fireservicerota/translations/id.json create mode 100644 homeassistant/components/firmata/translations/hu.json create mode 100644 homeassistant/components/firmata/translations/id.json create mode 100644 homeassistant/components/flick_electric/translations/id.json create mode 100644 homeassistant/components/flo/translations/id.json create mode 100644 homeassistant/components/flume/translations/he.json create mode 100644 homeassistant/components/flume/translations/id.json create mode 100644 homeassistant/components/flunearyou/translations/he.json create mode 100644 homeassistant/components/flunearyou/translations/hu.json create mode 100644 homeassistant/components/flunearyou/translations/id.json create mode 100644 homeassistant/components/flunearyou/translations/zh-Hans.json create mode 100644 homeassistant/components/forked_daapd/translations/id.json create mode 100644 homeassistant/components/foscam/translations/bg.json create mode 100644 homeassistant/components/foscam/translations/hu.json create mode 100644 homeassistant/components/foscam/translations/id.json create mode 100644 homeassistant/components/freebox/translations/id.json create mode 100644 homeassistant/components/fritzbox/translations/bg.json create mode 100644 homeassistant/components/fritzbox/translations/he.json create mode 100644 homeassistant/components/fritzbox/translations/id.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/bg.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/hu.json create mode 100644 homeassistant/components/fritzbox_callmonitor/translations/id.json create mode 100644 homeassistant/components/garmin_connect/translations/he.json create mode 100644 homeassistant/components/garmin_connect/translations/id.json create mode 100644 homeassistant/components/gdacs/translations/id.json create mode 100644 homeassistant/components/geofency/translations/id.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/id.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/id.json create mode 100644 homeassistant/components/gios/translations/id.json create mode 100644 homeassistant/components/glances/translations/he.json create mode 100644 homeassistant/components/glances/translations/id.json create mode 100644 homeassistant/components/goalzero/translations/hu.json create mode 100644 homeassistant/components/goalzero/translations/id.json create mode 100644 homeassistant/components/gogogate2/translations/id.json create mode 100644 homeassistant/components/gpslogger/translations/id.json create mode 100644 homeassistant/components/gree/translations/hu.json create mode 100644 homeassistant/components/gree/translations/id.json create mode 100644 homeassistant/components/guardian/translations/id.json create mode 100644 homeassistant/components/habitica/translations/bg.json create mode 100644 homeassistant/components/habitica/translations/hu.json create mode 100644 homeassistant/components/habitica/translations/id.json create mode 100644 homeassistant/components/habitica/translations/pt.json create mode 100644 homeassistant/components/harmony/translations/id.json create mode 100644 homeassistant/components/hassio/translations/id.json delete mode 100644 homeassistant/components/hassio/translations/nb.json create mode 100644 homeassistant/components/heos/translations/id.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/id.json create mode 100644 homeassistant/components/hive/translations/ca.json create mode 100644 homeassistant/components/hive/translations/de.json create mode 100644 homeassistant/components/hive/translations/et.json create mode 100644 homeassistant/components/hive/translations/fr.json create mode 100644 homeassistant/components/hive/translations/ko.json create mode 100644 homeassistant/components/hive/translations/pt.json create mode 100644 homeassistant/components/hive/translations/ru.json create mode 100644 homeassistant/components/hive/translations/zh-Hant.json create mode 100644 homeassistant/components/hlk_sw16/translations/id.json create mode 100644 homeassistant/components/home_connect/translations/id.json create mode 100644 homeassistant/components/homeassistant/translations/he.json create mode 100644 homeassistant/components/homeassistant/translations/id.json create mode 100644 homeassistant/components/homeassistant/translations/ko.json create mode 100644 homeassistant/components/homekit/translations/id.json create mode 100644 homeassistant/components/homekit_controller/translations/id.json create mode 100644 homeassistant/components/huawei_lte/translations/he.json create mode 100644 homeassistant/components/huawei_lte/translations/id.json create mode 100644 homeassistant/components/huisbaasje/translations/bg.json create mode 100644 homeassistant/components/huisbaasje/translations/hu.json create mode 100644 homeassistant/components/huisbaasje/translations/id.json create mode 100644 homeassistant/components/humidifier/translations/hu.json create mode 100644 homeassistant/components/humidifier/translations/id.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/id.json create mode 100644 homeassistant/components/hvv_departures/translations/he.json create mode 100644 homeassistant/components/hvv_departures/translations/hu.json create mode 100644 homeassistant/components/hvv_departures/translations/id.json create mode 100644 homeassistant/components/hyperion/translations/id.json create mode 100644 homeassistant/components/iaqualink/translations/he.json create mode 100644 homeassistant/components/iaqualink/translations/id.json create mode 100644 homeassistant/components/icloud/translations/id.json create mode 100644 homeassistant/components/ifttt/translations/id.json create mode 100644 homeassistant/components/insteon/translations/he.json create mode 100644 homeassistant/components/insteon/translations/hu.json create mode 100644 homeassistant/components/insteon/translations/id.json create mode 100644 homeassistant/components/ipma/translations/id.json create mode 100644 homeassistant/components/ipp/translations/id.json create mode 100644 homeassistant/components/iqvia/translations/hu.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/hu.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/id.json create mode 100644 homeassistant/components/isy994/translations/id.json create mode 100644 homeassistant/components/izone/translations/id.json create mode 100644 homeassistant/components/juicenet/translations/hu.json create mode 100644 homeassistant/components/juicenet/translations/id.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/bg.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/hu.json create mode 100644 homeassistant/components/keenetic_ndms2/translations/id.json create mode 100644 homeassistant/components/kmtronic/translations/bg.json create mode 100644 homeassistant/components/kmtronic/translations/es.json create mode 100644 homeassistant/components/kmtronic/translations/hu.json create mode 100644 homeassistant/components/kmtronic/translations/id.json create mode 100644 homeassistant/components/kmtronic/translations/ko.json create mode 100644 homeassistant/components/kmtronic/translations/pt.json create mode 100644 homeassistant/components/kodi/translations/id.json create mode 100644 homeassistant/components/konnected/translations/id.json create mode 100644 homeassistant/components/kulersky/translations/id.json create mode 100644 homeassistant/components/life360/translations/he.json create mode 100644 homeassistant/components/lifx/translations/id.json create mode 100644 homeassistant/components/litejet/translations/bg.json create mode 100644 homeassistant/components/litejet/translations/hu.json create mode 100644 homeassistant/components/litejet/translations/id.json create mode 100644 homeassistant/components/litejet/translations/ko.json create mode 100644 homeassistant/components/litejet/translations/pt.json create mode 100644 homeassistant/components/litterrobot/translations/bg.json create mode 100644 homeassistant/components/litterrobot/translations/es.json create mode 100644 homeassistant/components/litterrobot/translations/hu.json create mode 100644 homeassistant/components/litterrobot/translations/id.json create mode 100644 homeassistant/components/litterrobot/translations/ko.json create mode 100644 homeassistant/components/litterrobot/translations/pt.json create mode 100644 homeassistant/components/local_ip/translations/id.json create mode 100644 homeassistant/components/locative/translations/id.json create mode 100644 homeassistant/components/logi_circle/translations/id.json create mode 100644 homeassistant/components/lovelace/translations/id.json create mode 100644 homeassistant/components/lovelace/translations/ko.json create mode 100644 homeassistant/components/luftdaten/translations/id.json create mode 100644 homeassistant/components/lutron_caseta/translations/bg.json create mode 100644 homeassistant/components/lutron_caseta/translations/he.json create mode 100644 homeassistant/components/lutron_caseta/translations/hu.json create mode 100644 homeassistant/components/lutron_caseta/translations/id.json create mode 100644 homeassistant/components/lyric/translations/hu.json create mode 100644 homeassistant/components/lyric/translations/id.json create mode 100644 homeassistant/components/mailgun/translations/id.json create mode 100644 homeassistant/components/mazda/translations/bg.json create mode 100644 homeassistant/components/mazda/translations/hu.json create mode 100644 homeassistant/components/mazda/translations/id.json create mode 100644 homeassistant/components/melcloud/translations/he.json create mode 100644 homeassistant/components/melcloud/translations/id.json create mode 100644 homeassistant/components/met/translations/he.json create mode 100644 homeassistant/components/meteo_france/translations/id.json create mode 100644 homeassistant/components/metoffice/translations/he.json create mode 100644 homeassistant/components/metoffice/translations/id.json create mode 100644 homeassistant/components/mikrotik/translations/he.json create mode 100644 homeassistant/components/mikrotik/translations/id.json create mode 100644 homeassistant/components/mill/translations/id.json create mode 100644 homeassistant/components/minecraft_server/translations/id.json create mode 100644 homeassistant/components/mobile_app/translations/id.json create mode 100644 homeassistant/components/monoprice/translations/id.json create mode 100644 homeassistant/components/moon/translations/sensor.id.json create mode 100644 homeassistant/components/motion_blinds/translations/bg.json create mode 100644 homeassistant/components/motion_blinds/translations/hu.json create mode 100644 homeassistant/components/motion_blinds/translations/id.json create mode 100644 homeassistant/components/mullvad/translations/bg.json create mode 100644 homeassistant/components/mullvad/translations/hu.json create mode 100644 homeassistant/components/mullvad/translations/id.json create mode 100644 homeassistant/components/mullvad/translations/ko.json create mode 100644 homeassistant/components/mullvad/translations/pl.json create mode 100644 homeassistant/components/mullvad/translations/pt.json create mode 100644 homeassistant/components/mullvad/translations/zh-Hans.json create mode 100644 homeassistant/components/myq/translations/he.json create mode 100644 homeassistant/components/myq/translations/id.json create mode 100644 homeassistant/components/mysensors/translations/bg.json create mode 100644 homeassistant/components/mysensors/translations/hu.json create mode 100644 homeassistant/components/mysensors/translations/id.json create mode 100644 homeassistant/components/neato/translations/he.json create mode 100644 homeassistant/components/neato/translations/id.json create mode 100644 homeassistant/components/netatmo/translations/id.json create mode 100644 homeassistant/components/netatmo/translations/zh-Hans.json create mode 100644 homeassistant/components/nexia/translations/he.json create mode 100644 homeassistant/components/nexia/translations/id.json create mode 100644 homeassistant/components/nightscout/translations/id.json create mode 100644 homeassistant/components/notion/translations/he.json create mode 100644 homeassistant/components/notion/translations/id.json create mode 100644 homeassistant/components/nuheat/translations/he.json create mode 100644 homeassistant/components/nuheat/translations/id.json create mode 100644 homeassistant/components/nuki/translations/bg.json create mode 100644 homeassistant/components/nuki/translations/hu.json create mode 100644 homeassistant/components/nuki/translations/id.json create mode 100644 homeassistant/components/number/translations/de.json create mode 100644 homeassistant/components/number/translations/hu.json create mode 100644 homeassistant/components/number/translations/id.json create mode 100644 homeassistant/components/number/translations/ko.json create mode 100644 homeassistant/components/number/translations/zh-Hans.json create mode 100644 homeassistant/components/nut/translations/he.json create mode 100644 homeassistant/components/nut/translations/id.json create mode 100644 homeassistant/components/nws/translations/he.json create mode 100644 homeassistant/components/nws/translations/id.json create mode 100644 homeassistant/components/nzbget/translations/hu.json create mode 100644 homeassistant/components/nzbget/translations/id.json create mode 100644 homeassistant/components/omnilogic/translations/hu.json create mode 100644 homeassistant/components/omnilogic/translations/id.json create mode 100644 homeassistant/components/ondilo_ico/translations/hu.json create mode 100644 homeassistant/components/ondilo_ico/translations/id.json create mode 100644 homeassistant/components/onewire/translations/id.json create mode 100644 homeassistant/components/onvif/translations/id.json create mode 100644 homeassistant/components/opentherm_gw/translations/id.json create mode 100644 homeassistant/components/openweathermap/translations/he.json create mode 100644 homeassistant/components/openweathermap/translations/hu.json create mode 100644 homeassistant/components/openweathermap/translations/id.json create mode 100644 homeassistant/components/ovo_energy/translations/id.json create mode 100644 homeassistant/components/owntracks/translations/id.json create mode 100644 homeassistant/components/ozw/translations/id.json create mode 100644 homeassistant/components/panasonic_viera/translations/id.json create mode 100644 homeassistant/components/philips_js/translations/bg.json create mode 100644 homeassistant/components/philips_js/translations/hu.json create mode 100644 homeassistant/components/philips_js/translations/id.json create mode 100644 homeassistant/components/philips_js/translations/zh-Hans.json create mode 100644 homeassistant/components/pi_hole/translations/id.json create mode 100644 homeassistant/components/plaato/translations/id.json create mode 100644 homeassistant/components/plex/translations/id.json create mode 100644 homeassistant/components/plugwise/translations/id.json create mode 100644 homeassistant/components/plum_lightpad/translations/id.json create mode 100644 homeassistant/components/point/translations/id.json create mode 100644 homeassistant/components/poolsense/translations/id.json create mode 100644 homeassistant/components/powerwall/translations/bg.json create mode 100644 homeassistant/components/powerwall/translations/id.json create mode 100644 homeassistant/components/profiler/translations/id.json create mode 100644 homeassistant/components/progettihwsw/translations/id.json create mode 100644 homeassistant/components/ps4/translations/id.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/hu.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/id.json create mode 100644 homeassistant/components/rachio/translations/id.json create mode 100644 homeassistant/components/rainmachine/translations/he.json create mode 100644 homeassistant/components/rainmachine/translations/id.json create mode 100644 homeassistant/components/recollect_waste/translations/id.json create mode 100644 homeassistant/components/rfxtrx/translations/id.json create mode 100644 homeassistant/components/ring/translations/he.json create mode 100644 homeassistant/components/ring/translations/id.json create mode 100644 homeassistant/components/risco/translations/id.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/bg.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/hu.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/id.json create mode 100644 homeassistant/components/rituals_perfume_genie/translations/ko.json create mode 100644 homeassistant/components/roku/translations/id.json create mode 100644 homeassistant/components/roomba/translations/bg.json create mode 100644 homeassistant/components/roomba/translations/he.json create mode 100644 homeassistant/components/roomba/translations/id.json create mode 100644 homeassistant/components/roon/translations/id.json create mode 100644 homeassistant/components/rpi_power/translations/hu.json create mode 100644 homeassistant/components/rpi_power/translations/id.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/he.json create mode 100644 homeassistant/components/ruckus_unleashed/translations/id.json create mode 100644 homeassistant/components/samsungtv/translations/id.json create mode 100644 homeassistant/components/season/translations/sensor.id.json create mode 100644 homeassistant/components/sense/translations/he.json create mode 100644 homeassistant/components/sense/translations/id.json create mode 100644 homeassistant/components/sentry/translations/id.json create mode 100644 homeassistant/components/sharkiq/translations/hu.json create mode 100644 homeassistant/components/sharkiq/translations/id.json create mode 100644 homeassistant/components/shelly/translations/bg.json create mode 100644 homeassistant/components/shelly/translations/id.json create mode 100644 homeassistant/components/shopping_list/translations/id.json create mode 100644 homeassistant/components/simplisafe/translations/he.json create mode 100644 homeassistant/components/simplisafe/translations/id.json create mode 100644 homeassistant/components/smappee/translations/id.json create mode 100644 homeassistant/components/smart_meter_texas/translations/id.json create mode 100644 homeassistant/components/smarthab/translations/id.json create mode 100644 homeassistant/components/smartthings/translations/id.json create mode 100644 homeassistant/components/smarttub/translations/bg.json create mode 100644 homeassistant/components/smarttub/translations/hu.json create mode 100644 homeassistant/components/smarttub/translations/id.json create mode 100644 homeassistant/components/smarttub/translations/pt.json create mode 100644 homeassistant/components/smhi/translations/he.json create mode 100644 homeassistant/components/smhi/translations/id.json create mode 100644 homeassistant/components/sms/translations/id.json create mode 100644 homeassistant/components/solaredge/translations/id.json create mode 100644 homeassistant/components/solarlog/translations/id.json create mode 100644 homeassistant/components/soma/translations/id.json create mode 100644 homeassistant/components/somfy/translations/id.json create mode 100644 homeassistant/components/somfy_mylink/translations/bg.json create mode 100644 homeassistant/components/somfy_mylink/translations/he.json create mode 100644 homeassistant/components/somfy_mylink/translations/hu.json create mode 100644 homeassistant/components/somfy_mylink/translations/id.json create mode 100644 homeassistant/components/sonarr/translations/id.json create mode 100644 homeassistant/components/songpal/translations/id.json create mode 100644 homeassistant/components/speedtestdotnet/translations/hu.json create mode 100644 homeassistant/components/speedtestdotnet/translations/id.json create mode 100644 homeassistant/components/spider/translations/hu.json create mode 100644 homeassistant/components/spider/translations/id.json create mode 100644 homeassistant/components/spotify/translations/id.json create mode 100644 homeassistant/components/squeezebox/translations/id.json create mode 100644 homeassistant/components/srp_energy/translations/id.json create mode 100644 homeassistant/components/starline/translations/he.json create mode 100644 homeassistant/components/starline/translations/id.json create mode 100644 homeassistant/components/subaru/translations/bg.json create mode 100644 homeassistant/components/subaru/translations/hu.json create mode 100644 homeassistant/components/subaru/translations/id.json create mode 100644 homeassistant/components/subaru/translations/ko.json create mode 100644 homeassistant/components/syncthru/translations/id.json create mode 100644 homeassistant/components/synology_dsm/translations/id.json create mode 100644 homeassistant/components/system_health/translations/id.json create mode 100644 homeassistant/components/tado/translations/he.json create mode 100644 homeassistant/components/tado/translations/id.json create mode 100644 homeassistant/components/tag/translations/hu.json create mode 100644 homeassistant/components/tag/translations/id.json create mode 100644 homeassistant/components/tasmota/translations/id.json create mode 100644 homeassistant/components/tellduslive/translations/id.json create mode 100644 homeassistant/components/tesla/translations/he.json create mode 100644 homeassistant/components/tesla/translations/id.json create mode 100644 homeassistant/components/tibber/translations/id.json create mode 100644 homeassistant/components/tile/translations/hu.json create mode 100644 homeassistant/components/tile/translations/id.json create mode 100644 homeassistant/components/toon/translations/hu.json create mode 100644 homeassistant/components/toon/translations/id.json create mode 100644 homeassistant/components/totalconnect/translations/he.json create mode 100644 homeassistant/components/totalconnect/translations/id.json create mode 100644 homeassistant/components/tplink/translations/hu.json create mode 100644 homeassistant/components/tplink/translations/id.json create mode 100644 homeassistant/components/traccar/translations/id.json create mode 100644 homeassistant/components/transmission/translations/he.json create mode 100644 homeassistant/components/transmission/translations/id.json create mode 100644 homeassistant/components/tuya/translations/id.json create mode 100644 homeassistant/components/twentemilieu/translations/id.json create mode 100644 homeassistant/components/twilio/translations/id.json create mode 100644 homeassistant/components/twinkly/translations/hu.json create mode 100644 homeassistant/components/twinkly/translations/id.json create mode 100644 homeassistant/components/unifi/translations/he.json create mode 100644 homeassistant/components/unifi/translations/id.json create mode 100644 homeassistant/components/upb/translations/hu.json create mode 100644 homeassistant/components/upb/translations/id.json create mode 100644 homeassistant/components/upcloud/translations/id.json create mode 100644 homeassistant/components/upnp/translations/id.json create mode 100644 homeassistant/components/velbus/translations/hu.json create mode 100644 homeassistant/components/velbus/translations/id.json create mode 100644 homeassistant/components/vera/translations/id.json create mode 100644 homeassistant/components/verisure/translations/ca.json create mode 100644 homeassistant/components/verisure/translations/et.json create mode 100644 homeassistant/components/verisure/translations/fr.json create mode 100644 homeassistant/components/verisure/translations/pt.json create mode 100644 homeassistant/components/vesync/translations/he.json create mode 100644 homeassistant/components/vesync/translations/id.json create mode 100644 homeassistant/components/vilfo/translations/id.json create mode 100644 homeassistant/components/vizio/translations/id.json create mode 100644 homeassistant/components/volumio/translations/id.json create mode 100644 homeassistant/components/water_heater/translations/id.json create mode 100644 homeassistant/components/water_heater/translations/ko.json create mode 100644 homeassistant/components/wemo/translations/hu.json create mode 100644 homeassistant/components/wemo/translations/id.json create mode 100644 homeassistant/components/wiffi/translations/id.json create mode 100644 homeassistant/components/wilight/translations/id.json create mode 100644 homeassistant/components/withings/translations/id.json create mode 100644 homeassistant/components/wled/translations/id.json create mode 100644 homeassistant/components/wolflink/translations/id.json create mode 100644 homeassistant/components/wolflink/translations/sensor.hu.json create mode 100644 homeassistant/components/wolflink/translations/sensor.id.json create mode 100644 homeassistant/components/xbox/translations/id.json create mode 100644 homeassistant/components/xiaomi_aqara/translations/id.json create mode 100644 homeassistant/components/xiaomi_miio/translations/bg.json create mode 100644 homeassistant/components/xiaomi_miio/translations/id.json create mode 100644 homeassistant/components/yeelight/translations/id.json create mode 100644 homeassistant/components/zerproc/translations/id.json create mode 100644 homeassistant/components/zha/translations/id.json create mode 100644 homeassistant/components/zodiac/translations/sensor.hu.json create mode 100644 homeassistant/components/zodiac/translations/sensor.id.json create mode 100644 homeassistant/components/zoneminder/translations/id.json create mode 100644 homeassistant/components/zwave_js/translations/bg.json create mode 100644 homeassistant/components/zwave_js/translations/hu.json create mode 100644 homeassistant/components/zwave_js/translations/id.json diff --git a/homeassistant/components/abode/translations/he.json b/homeassistant/components/abode/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/abode/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/hu.json b/homeassistant/components/abode/translations/hu.json index 5df508d0f33bfd..260416b07bba48 100644 --- a/homeassistant/components/abode/translations/hu.json +++ b/homeassistant/components/abode/translations/hu.json @@ -1,16 +1,25 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen Abode konfigur\u00e1ci\u00f3 enged\u00e9lyezett." + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_mfa_code": "\u00c9rv\u00e9nytelen MFA k\u00f3d" }, "step": { "mfa": { "data": { "mfa_code": "MFA k\u00f3d (6 jegy\u0171)" + }, + "title": "Add meg az Abode MFA k\u00f3dj\u00e1t" + }, + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" } }, "user": { diff --git a/homeassistant/components/abode/translations/id.json b/homeassistant/components/abode/translations/id.json new file mode 100644 index 00000000000000..2dc79c833b2457 --- /dev/null +++ b/homeassistant/components/abode/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "reauth_successful": "Autentikasi ulang berhasil", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_mfa_code": "Kode MFA tidak valid" + }, + "step": { + "mfa": { + "data": { + "mfa_code": "Kode MFA (6 digit)" + }, + "title": "Masukkan kode MFA Anda untuk Abode" + }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Masukkan informasi masuk Abode Anda" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Masukkan informasi masuk Abode Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/ko.json b/homeassistant/components/abode/translations/ko.json index a9756447adf22f..85d3ef81aeba26 100644 --- a/homeassistant/components/abode/translations/ko.json +++ b/homeassistant/components/abode/translations/ko.json @@ -2,18 +2,26 @@ "config": { "abort": { "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_mfa_code": "MFA \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "mfa": { + "data": { + "mfa_code": "MFA \ucf54\ub4dc (6\uc790\ub9ac)" + }, + "title": "Abode\uc5d0 \ub300\ud55c MFA \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" + }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c" - } + }, + "title": "Abode \ub85c\uadf8\uc778 \uc815\ubcf4 \uc785\ub825\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/accuweather/translations/he.json b/homeassistant/components/accuweather/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/accuweather/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/hu.json b/homeassistant/components/accuweather/translations/hu.json new file mode 100644 index 00000000000000..8a0f7f5a198f95 --- /dev/null +++ b/homeassistant/components/accuweather/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "name": "N\u00e9v" + }, + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "title": "AccuWeather be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/id.json b/homeassistant/components/accuweather/translations/id.json new file mode 100644 index 00000000000000..970b3a026b7bf9 --- /dev/null +++ b/homeassistant/components/accuweather/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid", + "requests_exceeded": "Jumlah permintaan yang diizinkan ke API Accuweather telah terlampaui. Anda harus menunggu atau mengubah Kunci API." + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Jika Anda memerlukan bantuan tentang konfigurasi, baca di sini: https://www.home-assistant.io/integrations/accuweather/\n\nBeberapa sensor tidak diaktifkan secara default. Anda dapat mengaktifkannya di registri entitas setelah konfigurasi integrasi.\nPrakiraan cuaca tidak diaktifkan secara default. Anda dapat mengaktifkannya di opsi integrasi.", + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "forecast": "Prakiraan cuaca" + }, + "description": "Karena keterbatasan versi gratis kunci API AccuWeather, ketika Anda mengaktifkan prakiraan cuaca, pembaruan data akan dilakukan setiap 80 menit, bukan setiap 40 menit.", + "title": "Opsi AccuWeather" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "Keterjangkauan server AccuWeather", + "remaining_requests": "Sisa permintaan yang diizinkan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/ko.json b/homeassistant/components/accuweather/translations/ko.json index 2f0a01e094b062..d992d0bfdd4300 100644 --- a/homeassistant/components/accuweather/translations/ko.json +++ b/homeassistant/components/accuweather/translations/ko.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "requests_exceeded": "Accuweather API\uc5d0 \ud5c8\uc6a9\ub41c \uc694\uccad \uc218\uac00 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uae30\ub2e4\ub9ac\uac70\ub098 API \ud0a4\ub97c \ubcc0\uacbd\ud574\uc57c \ud569\ub2c8\ub2e4." }, "step": { "user": { @@ -15,15 +16,26 @@ "longitude": "\uacbd\ub3c4", "name": "\uc774\ub984" }, - "description": "\uad6c\uc131\uc5d0 \ub300\ud55c \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 \ub2e4\uc74c\uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694:\nhttps://www.home-assistant.io/integrations/accuweather/\n\n\uc77c\ubd80 \uc13c\uc11c\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc5f0\ub3d9 \uad6c\uc131 \ud6c4 \uad6c\uc131\uc694\uc18c \ub808\uc9c0\uc2a4\ud2b8\ub9ac\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\uc77c\uae30\uc608\ubcf4\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc5f0\ub3d9 \uc635\uc158\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "description": "\uad6c\uc131\uc5d0 \ub300\ud55c \ub3c4\uc6c0\uc774 \ud544\uc694\ud55c \uacbd\uc6b0 \ub2e4\uc74c\uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/accuweather/\n\n\uc77c\ubd80 \uc13c\uc11c\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uad6c\uc131 \ud6c4 \uad6c\uc131\uc694\uc18c \ub808\uc9c0\uc2a4\ud2b8\ub9ac\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\uc77c\uae30\uc608\ubcf4\ub294 \uae30\ubcf8\uc801\uc73c\ub85c \ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc635\uc158\uc5d0\uc11c \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "AccuWeather" } } }, "options": { "step": { "user": { - "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4." + "data": { + "forecast": "\ub0a0\uc528 \uc608\ubcf4" + }, + "description": "\ubb34\ub8cc \ubc84\uc804\uc758 AccuWeather API \ud0a4\ub85c \uc77c\uae30\uc608\ubcf4\ub97c \ud65c\uc131\ud654\ud55c \uacbd\uc6b0 \uc81c\ud55c\uc0ac\ud56d\uc73c\ub85c \uc778\ud574 \uc5c5\ub370\uc774\ud2b8\ub294 40 \ubd84\uc774 \uc544\ub2cc 80 \ubd84\ub9c8\ub2e4 \uc218\ud589\ub429\ub2c8\ub2e4.", + "title": "AccuWeather \uc635\uc158" } } + }, + "system_health": { + "info": { + "can_reach_server": "AccuWeather \uc11c\ubc84 \uc5f0\uacb0", + "remaining_requests": "\ub0a8\uc740 \ud5c8\uc6a9 \uc694\uccad \ud69f\uc218" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index 4bf5f9fce45e82..342df3cca78eab 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -31,5 +31,10 @@ "title": "AccuWeather-opties" } } + }, + "system_health": { + "info": { + "can_reach_server": "Kan AccuWeather server bereiken" + } } } \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.hu.json b/homeassistant/components/accuweather/translations/sensor.hu.json new file mode 100644 index 00000000000000..49f2fe41ab30a0 --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.hu.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "Cs\u00f6kken\u0151", + "rising": "Emelked\u0151", + "steady": "\u00c1lland\u00f3" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.id.json b/homeassistant/components/accuweather/translations/sensor.id.json new file mode 100644 index 00000000000000..8ce99bbc8c330a --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.id.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "Turun", + "rising": "Naik", + "steady": "Tetap" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/sensor.ko.json b/homeassistant/components/accuweather/translations/sensor.ko.json new file mode 100644 index 00000000000000..287974fa3fdad6 --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.ko.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "\ud558\uac15", + "rising": "\uc0c1\uc2b9", + "steady": "\uc548\uc815" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/hu.json b/homeassistant/components/acmeda/translations/hu.json new file mode 100644 index 00000000000000..6105977de80fd4 --- /dev/null +++ b/homeassistant/components/acmeda/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/id.json b/homeassistant/components/acmeda/translations/id.json new file mode 100644 index 00000000000000..6e80d134f5a325 --- /dev/null +++ b/homeassistant/components/acmeda/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "step": { + "user": { + "data": { + "id": "ID Host" + }, + "title": "Pilih hub untuk ditambahkan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/bg.json b/homeassistant/components/adguard/translations/bg.json index 1edfc9d8da610f..82c658e7c15b30 100644 --- a/homeassistant/components/adguard/translations/bg.json +++ b/homeassistant/components/adguard/translations/bg.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 AdGuard Home, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430: {addon} ?", - "title": "AdGuard Home \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\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 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 AdGuard Home, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430: {addon} ?", + "title": "AdGuard Home \u0447\u0440\u0435\u0437 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/ca.json b/homeassistant/components/adguard/translations/ca.json index 422fde9479a70b..8c8086813aace7 100644 --- a/homeassistant/components/adguard/translations/ca.json +++ b/homeassistant/components/adguard/translations/ca.json @@ -10,7 +10,7 @@ "step": { "hassio_confirm": { "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb l'AdGuard Home proporcionat pel complement de Hass.io: {addon}?", - "title": "AdGuard Home (complement de Hass.io)" + "title": "AdGuard Home via complement de Hass.io" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/cs.json b/homeassistant/components/adguard/translations/cs.json index 27b9d291fc26f7..00531088a08f12 100644 --- a/homeassistant/components/adguard/translations/cs.json +++ b/homeassistant/components/adguard/translations/cs.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k AddGuard pomoc\u00ed hass.io {addon}?", - "title": "AdGuard prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k AddGuard pomoc\u00ed Supervisor {addon}?", + "title": "AdGuard prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/da.json b/homeassistant/components/adguard/translations/da.json index 927fd03d50a88e..79a1937eba8263 100644 --- a/homeassistant/components/adguard/translations/da.json +++ b/homeassistant/components/adguard/translations/da.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Hass.io-tilf\u00f8jelsen: {addon}?", - "title": "AdGuard Home via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til AdGuard Home leveret af Supervisor-tilf\u00f8jelsen: {addon}?", + "title": "AdGuard Home via Supervisor-tilf\u00f8jelse" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/de.json b/homeassistant/components/adguard/translations/de.json index 67746b3abcf0ca..2731f3f7ebae4b 100644 --- a/homeassistant/components/adguard/translations/de.json +++ b/homeassistant/components/adguard/translations/de.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit AdGuard Home als Hass.io-Add-On hergestellt wird: {addon}?", - "title": "AdGuard Home \u00fcber das Hass.io Add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit AdGuard Home als Supervisor-Add-On hergestellt wird: {addon}?", + "title": "AdGuard Home \u00fcber das Supervisor Add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/es-419.json b/homeassistant/components/adguard/translations/es-419.json index 5efdfae180204f..8fac53b61ab178 100644 --- a/homeassistant/components/adguard/translations/es-419.json +++ b/homeassistant/components/adguard/translations/es-419.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a la p\u00e1gina principal de AdGuard proporcionada por el complemento Hass.io: {addon}?", - "title": "AdGuard Home a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a la p\u00e1gina principal de AdGuard proporcionada por el complemento Supervisor: {addon}?", + "title": "AdGuard Home a trav\u00e9s del complemento Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/es.json b/homeassistant/components/adguard/translations/es.json index a165a9b1c09e7f..3ffdb6b9eb0b89 100644 --- a/homeassistant/components/adguard/translations/es.json +++ b/homeassistant/components/adguard/translations/es.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse al AdGuard Home proporcionado por el complemento Hass.io: {addon} ?", - "title": "AdGuard Home a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse al AdGuard Home proporcionado por el complemento Supervisor: {addon} ?", + "title": "AdGuard Home a trav\u00e9s del complemento Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/et.json b/homeassistant/components/adguard/translations/et.json index 3408d752522293..800b7c37c493d6 100644 --- a/homeassistant/components/adguard/translations/et.json +++ b/homeassistant/components/adguard/translations/et.json @@ -10,7 +10,7 @@ "step": { "hassio_confirm": { "description": "Kas soovid seadistada Home Assistant-i \u00fchenduse AdGuard Home'iga mida pakub Hass.io lisandmoodul: {addon} ?", - "title": "AdGuard Home Hass.io pistikprogrammi kaudu" + "title": "AdGuard Home Hass.io lisandmooduli abil" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/he.json b/homeassistant/components/adguard/translations/he.json index 49c18fac88c046..1471fd6603b017 100644 --- a/homeassistant/components/adguard/translations/he.json +++ b/homeassistant/components/adguard/translations/he.json @@ -4,6 +4,7 @@ "user": { "data": { "host": "Host", + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "port": "\u05e4\u05d5\u05e8\u05d8" } } diff --git a/homeassistant/components/adguard/translations/hu.json b/homeassistant/components/adguard/translations/hu.json index 3f67c7658507cb..3813fae8f3c4c4 100644 --- a/homeassistant/components/adguard/translations/hu.json +++ b/homeassistant/components/adguard/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, @@ -9,7 +12,9 @@ "host": "Hoszt", "password": "Jelsz\u00f3", "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } } } diff --git a/homeassistant/components/adguard/translations/id.json b/homeassistant/components/adguard/translations/id.json index c5d61d91df0498..d2e36cfe5b9938 100644 --- a/homeassistant/components/adguard/translations/id.json +++ b/homeassistant/components/adguard/translations/id.json @@ -1,10 +1,27 @@ { "config": { + "abort": { + "existing_instance_updated": "Memperbarui konfigurasi yang ada.", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke AdGuard Home yang disediakan oleh add-on Supervisor {addon}?", + "title": "AdGuard Home melalui add-on Home Assistant" + }, "user": { "data": { - "password": "Kata sandi" - } + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Siapkan instans AdGuard Home Anda untuk pemantauan dan kontrol." } } } diff --git a/homeassistant/components/adguard/translations/it.json b/homeassistant/components/adguard/translations/it.json index 3df01316aa15da..3758e0935473bf 100644 --- a/homeassistant/components/adguard/translations/it.json +++ b/homeassistant/components/adguard/translations/it.json @@ -9,7 +9,7 @@ }, "step": { "hassio_confirm": { - "description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon}?", + "description": "Vuoi configurare Home Assistant per connettersi ad AdGuard Home fornito dal componente aggiuntivo di Hass.io: {addon}?", "title": "AdGuard Home tramite il componente aggiuntivo di Hass.io" }, "user": { diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index b14e627ca56aab..c60d16e53b19e4 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -2,15 +2,15 @@ "config": { "abort": { "existing_instance_updated": "\uae30\uc874 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c AdGuard Home \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 AdGuard Home" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c AdGuard Home\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 AdGuard Home" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/lb.json b/homeassistant/components/adguard/translations/lb.json index 135451c061f2d1..ae7e6ad99be648 100644 --- a/homeassistant/components/adguard/translations/lb.json +++ b/homeassistant/components/adguard/translations/lb.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam AdGuard Home ze verbannen dee vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", - "title": "AdGuard Home via Hass.io add-on" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam AdGuard Home ze verbannen dee vum Supervisor add-on {addon} bereet gestallt g\u00ebtt?", + "title": "AdGuard Home via Supervisor add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/nl.json b/homeassistant/components/adguard/translations/nl.json index 4c735333932aab..205193be8f8d97 100644 --- a/homeassistant/components/adguard/translations/nl.json +++ b/homeassistant/components/adguard/translations/nl.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Wilt u Home Assistant configureren om verbinding te maken met AdGuard Home van de Hass.io-add-on: {addon}?", - "title": "AdGuard Home via Hass.io add-on" + "description": "Wilt u Home Assistant configureren om verbinding te maken met AdGuard Home van de Supervisor-add-on: {addon}?", + "title": "AdGuard Home via Supervisor add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/no.json b/homeassistant/components/adguard/translations/no.json index 25046c8d38f01b..11c35c5895ef28 100644 --- a/homeassistant/components/adguard/translations/no.json +++ b/homeassistant/components/adguard/translations/no.json @@ -9,7 +9,7 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til AdGuard Home gitt av Hass.io-tillegg {addon}?", + "description": "Vil du konfigurere Home Assistant for \u00e5 koble til AdGuard Home levert av Hass.io-tillegget: {addon} ?", "title": "AdGuard Home via Hass.io-tillegg" }, "user": { diff --git a/homeassistant/components/adguard/translations/pl.json b/homeassistant/components/adguard/translations/pl.json index 41cb2c019dd3d6..f5c433a0bf49a2 100644 --- a/homeassistant/components/adguard/translations/pl.json +++ b/homeassistant/components/adguard/translations/pl.json @@ -9,7 +9,7 @@ }, "step": { "hassio_confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io {addon}?", + "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io: {addon}?", "title": "AdGuard Home przez dodatek Hass.io" }, "user": { diff --git a/homeassistant/components/adguard/translations/pt-BR.json b/homeassistant/components/adguard/translations/pt-BR.json index ae8899bb1a775d..959c7ba3638e10 100644 --- a/homeassistant/components/adguard/translations/pt-BR.json +++ b/homeassistant/components/adguard/translations/pt-BR.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo complemento Hass.io: {addon} ?", - "title": "AdGuard Home via add-on Hass.io" + "description": "Deseja configurar o Home Assistant para se conectar ao AdGuard Home fornecido pelo complemento Supervisor: {addon} ?", + "title": "AdGuard Home via add-on Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/pt.json b/homeassistant/components/adguard/translations/pt.json index 5d8abfc9f56f2f..a7e494936b86be 100644 --- a/homeassistant/components/adguard/translations/pt.json +++ b/homeassistant/components/adguard/translations/pt.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "title": "AdGuard Home via Hass.io add-on" + "title": "AdGuard Home via Supervisor add-on" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/sl.json b/homeassistant/components/adguard/translations/sl.json index 06ad40a17d64ec..34b03263cebfdb 100644 --- a/homeassistant/components/adguard/translations/sl.json +++ b/homeassistant/components/adguard/translations/sl.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u017delite konfigurirati Home Assistant-a za povezavo z AdGuard Home, ki ga ponuja Hass.io add-on {addon} ?", - "title": "AdGuard Home preko dodatka Hass.io" + "description": "\u017delite konfigurirati Home Assistant-a za povezavo z AdGuard Home, ki ga ponuja Supervisor add-on {addon} ?", + "title": "AdGuard Home preko dodatka Supervisor" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/sv.json b/homeassistant/components/adguard/translations/sv.json index 5b9a0f9a969bda..ca6158eaf32b69 100644 --- a/homeassistant/components/adguard/translations/sv.json +++ b/homeassistant/components/adguard/translations/sv.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till AdGuard Home som tillhandah\u00e5lls av Hass.io Add-on: {addon}?", - "title": "AdGuard Home via Hass.io-till\u00e4gget" + "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till AdGuard Home som tillhandah\u00e5lls av Supervisor Add-on: {addon}?", + "title": "AdGuard Home via Supervisor-till\u00e4gget" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/uk.json b/homeassistant/components/adguard/translations/uk.json index 8c24fb0a877495..28d02f25b7e8fa 100644 --- a/homeassistant/components/adguard/translations/uk.json +++ b/homeassistant/components/adguard/translations/uk.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "AdGuard Home (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" }, "user": { "data": { diff --git a/homeassistant/components/adguard/translations/zh-Hant.json b/homeassistant/components/adguard/translations/zh-Hant.json index 8306b2daf70a7f..250b2e0d891d0b 100644 --- a/homeassistant/components/adguard/translations/zh-Hant.json +++ b/homeassistant/components/adguard/translations/zh-Hant.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 AdGuard Home\uff1f", - "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 AdGuard Home" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 AdGuard Home\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 AdGuard Home" }, "user": { "data": { diff --git a/homeassistant/components/advantage_air/translations/hu.json b/homeassistant/components/advantage_air/translations/hu.json index 0da6d0d5304447..e82e88da8d217a 100644 --- a/homeassistant/components/advantage_air/translations/hu.json +++ b/homeassistant/components/advantage_air/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, diff --git a/homeassistant/components/advantage_air/translations/id.json b/homeassistant/components/advantage_air/translations/id.json new file mode 100644 index 00000000000000..7993fa3be1d886 --- /dev/null +++ b/homeassistant/components/advantage_air/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "port": "Port" + }, + "description": "Hubungkan ke API tablet dinding Advantage Air.", + "title": "Hubungkan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/advantage_air/translations/ko.json b/homeassistant/components/advantage_air/translations/ko.json index 444d8d3828550e..9a28cc499bc1eb 100644 --- a/homeassistant/components/advantage_air/translations/ko.json +++ b/homeassistant/components/advantage_air/translations/ko.json @@ -11,7 +11,9 @@ "data": { "ip_address": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" - } + }, + "description": "\ubcbd\uc5d0 \ubd80\ucc29\ub41c Advantage Air \ud0dc\ube14\ub9bf\uc758 API\uc5d0 \uc5f0\uacb0\ud558\uae30", + "title": "\uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/aemet/translations/hu.json b/homeassistant/components/aemet/translations/hu.json new file mode 100644 index 00000000000000..d810691046e54a --- /dev/null +++ b/homeassistant/components/aemet/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "name": "Az integr\u00e1ci\u00f3 neve" + }, + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/id.json b/homeassistant/components/aemet/translations/id.json new file mode 100644 index 00000000000000..fa678cbbbe0b25 --- /dev/null +++ b/homeassistant/components/aemet/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "invalid_api_key": "Kunci API tidak valid" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama integrasi" + }, + "description": "Siapkan integrasi AEMET OpenData. Untuk menghasilkan kunci API, buka https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/ko.json b/homeassistant/components/aemet/translations/ko.json index edfb023a88b060..95c11b018fef02 100644 --- a/homeassistant/components/aemet/translations/ko.json +++ b/homeassistant/components/aemet/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -14,6 +14,7 @@ "longitude": "\uacbd\ub3c4", "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, + "description": "AEMET OpenData \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://opendata.aemet.es/centrodedescargas/altaUsuario \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/aemet/translations/lb.json b/homeassistant/components/aemet/translations/lb.json new file mode 100644 index 00000000000000..5ae5fff8664fdb --- /dev/null +++ b/homeassistant/components/aemet/translations/lb.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Numm vun der Integratioun" + }, + "title": "AEMET OpenData" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/aemet/translations/nl.json b/homeassistant/components/aemet/translations/nl.json index 02415dde1e6423..77589e20490712 100644 --- a/homeassistant/components/aemet/translations/nl.json +++ b/homeassistant/components/aemet/translations/nl.json @@ -14,6 +14,7 @@ "longitude": "Lengtegraad", "name": "Naam van de integratie" }, + "description": "Stel AEMET OpenData-integratie in. Ga naar https://opendata.aemet.es/centrodedescargas/altaUsuario om een API-sleutel te genereren", "title": "AEMET OpenData" } } diff --git a/homeassistant/components/agent_dvr/translations/hu.json b/homeassistant/components/agent_dvr/translations/hu.json index 1d28556ba1ad5a..49968ceea75ed9 100644 --- a/homeassistant/components/agent_dvr/translations/hu.json +++ b/homeassistant/components/agent_dvr/translations/hu.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/id.json b/homeassistant/components/agent_dvr/translations/id.json new file mode 100644 index 00000000000000..f2ee1cc662264b --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Siapkan Agent DVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/de.json b/homeassistant/components/airly/translations/de.json index 8004444fdb9003..b13798c0ae3d26 100644 --- a/homeassistant/components/airly/translations/de.json +++ b/homeassistant/components/airly/translations/de.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Airly-Server erreichen" + "can_reach_server": "Airly-Server erreichen", + "requests_per_day": "Erlaubte Anfragen pro Tag", + "requests_remaining": "Verbleibende erlaubte Anfragen" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/es.json b/homeassistant/components/airly/translations/es.json index a0ed36a71691c7..a96a2f62293b41 100644 --- a/homeassistant/components/airly/translations/es.json +++ b/homeassistant/components/airly/translations/es.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Alcanzar el servidor Airly" + "can_reach_server": "Alcanzar el servidor Airly", + "requests_per_day": "Solicitudes permitidas por d\u00eda", + "requests_remaining": "Solicitudes permitidas restantes" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json index c5c9359c67fd63..6730e131ac2c8d 100644 --- a/homeassistant/components/airly/translations/et.json +++ b/homeassistant/components/airly/translations/et.json @@ -23,8 +23,8 @@ "system_health": { "info": { "can_reach_server": "\u00dchendus Airly serveriga", - "requests_per_day": "Lubatud taotlusi p\u00e4evas", - "requests_remaining": "J\u00e4\u00e4nud lubatud taotlusi" + "requests_per_day": "Lubatud p\u00e4ringuid p\u00e4evas", + "requests_remaining": "Lubatud p\u00e4ringute arv" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/fr.json b/homeassistant/components/airly/translations/fr.json index 98407155f17bfd..a23f455e0b82ce 100644 --- a/homeassistant/components/airly/translations/fr.json +++ b/homeassistant/components/airly/translations/fr.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Acc\u00e8s au serveur Airly" + "can_reach_server": "Acc\u00e8s au serveur Airly", + "requests_per_day": "Demandes autoris\u00e9es par jour", + "requests_remaining": "Demandes autoris\u00e9es restantes" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/he.json b/homeassistant/components/airly/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/airly/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/hu.json b/homeassistant/components/airly/translations/hu.json index b96734935a0245..b9fd0c9e05cc5a 100644 --- a/homeassistant/components/airly/translations/hu.json +++ b/homeassistant/components/airly/translations/hu.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Ezen koordin\u00e1t\u00e1k Airly integr\u00e1ci\u00f3ja m\u00e1r konfigur\u00e1lva van." + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" }, "error": { + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", "wrong_location": "Ezen a ter\u00fcleten nincs Airly m\u00e9r\u0151\u00e1llom\u00e1s." }, "step": { @@ -12,11 +13,17 @@ "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", - "name": "Az integr\u00e1ci\u00f3 neve" + "name": "N\u00e9v" }, "description": "Az Airly leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Api-kulcs l\u00e9trehoz\u00e1s\u00e1hoz nyissa meg a k\u00f6vetkez\u0151 weboldalt: https://developer.airly.eu/register", "title": "Airly" } } + }, + "system_health": { + "info": { + "requests_per_day": "Enged\u00e9lyezett k\u00e9r\u00e9sek naponta", + "requests_remaining": "Fennmarad\u00f3 enged\u00e9lyezett k\u00e9r\u00e9sek" + } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/id.json b/homeassistant/components/airly/translations/id.json new file mode 100644 index 00000000000000..57b4c0d95f9eab --- /dev/null +++ b/homeassistant/components/airly/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "invalid_api_key": "Kunci API tidak valid", + "wrong_location": "Tidak ada stasiun pengukur Airly di daerah ini." + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Siapkan integrasi kualitas udara Airly. Untuk membuat kunci API, buka https://developer.airly.eu/register", + "title": "Airly" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "Keterjangkauan server Airly", + "requests_per_day": "Permintaan yang diizinkan per hari", + "requests_remaining": "Sisa permintaan yang diizinkan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/it.json b/homeassistant/components/airly/translations/it.json index bf6d7a461ceb0e..385b8117437b2c 100644 --- a/homeassistant/components/airly/translations/it.json +++ b/homeassistant/components/airly/translations/it.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Raggiungi il server Airly" + "can_reach_server": "Raggiungi il server Airly", + "requests_per_day": "Richieste consentite al giorno", + "requests_remaining": "Richieste consentite rimanenti" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/ko.json b/homeassistant/components/airly/translations/ko.json index 95981ea5eb1f76..1f9db4a592ed1d 100644 --- a/homeassistant/components/airly/translations/ko.json +++ b/homeassistant/components/airly/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -19,5 +19,12 @@ "title": "Airly" } } + }, + "system_health": { + "info": { + "can_reach_server": "Airly \uc11c\ubc84 \uc5f0\uacb0", + "requests_per_day": "\uc77c\uc77c \ud5c8\uc6a9 \uc694\uccad \ud69f\uc218", + "requests_remaining": "\ub0a8\uc740 \ud5c8\uc6a9 \uc694\uccad \ud69f\uc218" + } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index a7bc9966f637e2..5ea975dfaeffb3 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -19,5 +19,12 @@ "title": "Airly" } } + }, + "system_health": { + "info": { + "can_reach_server": "Kan Airly server bereiken", + "requests_per_day": "Toegestane verzoeken per dag", + "requests_remaining": "Resterende toegestane verzoeken" + } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/no.json b/homeassistant/components/airly/translations/no.json index b38568210ad07b..4c81422d93c90a 100644 --- a/homeassistant/components/airly/translations/no.json +++ b/homeassistant/components/airly/translations/no.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "N\u00e5 Airly-serveren" + "can_reach_server": "N\u00e5 Airly-serveren", + "requests_per_day": "Tillatte foresp\u00f8rsler per dag", + "requests_remaining": "Gjenv\u00e6rende tillatte foresp\u00f8rsler" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/pl.json b/homeassistant/components/airly/translations/pl.json index e36e6f86ec72df..f205a569474497 100644 --- a/homeassistant/components/airly/translations/pl.json +++ b/homeassistant/components/airly/translations/pl.json @@ -22,7 +22,9 @@ }, "system_health": { "info": { - "can_reach_server": "Dost\u0119p do serwera Airly" + "can_reach_server": "Dost\u0119p do serwera Airly", + "requests_per_day": "Dozwolone dzienne \u017c\u0105dania", + "requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144" } } } \ No newline at end of file diff --git a/homeassistant/components/airly/translations/zh-Hans.json b/homeassistant/components/airly/translations/zh-Hans.json index 1a57bfbadf9c0a..0f3c5137d31b43 100644 --- a/homeassistant/components/airly/translations/zh-Hans.json +++ b/homeassistant/components/airly/translations/zh-Hans.json @@ -13,7 +13,8 @@ }, "system_health": { "info": { - "can_reach_server": "\u53ef\u8bbf\u95ee Airly \u670d\u52a1\u5668" + "can_reach_server": "\u53ef\u8bbf\u95ee Airly \u670d\u52a1\u5668", + "requests_per_day": "\u5141\u8bb8\u6bcf\u5929\u8bf7\u6c42" } } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/bg.json b/homeassistant/components/airnow/translations/bg.json new file mode 100644 index 00000000000000..5d274ec2b73c77 --- /dev/null +++ b/homeassistant/components/airnow/translations/bg.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/de.json b/homeassistant/components/airnow/translations/de.json index c98fc6d7415d24..8c2b47c1bd4962 100644 --- a/homeassistant/components/airnow/translations/de.json +++ b/homeassistant/components/airnow/translations/de.json @@ -16,6 +16,7 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad" }, + "description": "Richten Sie die AirNow-Luftqualit\u00e4tsintegration ein. Um den API-Schl\u00fcssel zu generieren, besuchen Sie https://docs.airnowapi.org/account/request/.", "title": "AirNow" } } diff --git a/homeassistant/components/airnow/translations/hu.json b/homeassistant/components/airnow/translations/hu.json new file mode 100644 index 00000000000000..418450f24198c7 --- /dev/null +++ b/homeassistant/components/airnow/translations/hu.json @@ -0,0 +1,24 @@ +{ + "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", + "invalid_location": "Erre a helyre nem tal\u00e1lhat\u00f3 eredm\u00e9ny", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + }, + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/id.json b/homeassistant/components/airnow/translations/id.json new file mode 100644 index 00000000000000..66fdff72fae3b2 --- /dev/null +++ b/homeassistant/components/airnow/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_location": "Tidak ada hasil yang ditemukan untuk lokasi tersebut", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "radius": "Radius Stasiun (mil; opsional)" + }, + "description": "Siapkan integrasi kualitas udara AirNow. Untuk membuat kunci API buka https://docs.airnowapi.org/account/request/", + "title": "AirNow" + } + } + }, + "title": "AirNow" +} \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/ko.json b/homeassistant/components/airnow/translations/ko.json index 6da62dffa2ca5f..adfbf0be8ed5e3 100644 --- a/homeassistant/components/airnow/translations/ko.json +++ b/homeassistant/components/airnow/translations/ko.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_location": "\ud574\ub2f9 \uc704\uce58\uc5d0 \uacb0\uacfc\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -13,9 +14,13 @@ "data": { "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", - "longitude": "\uacbd\ub3c4" - } + "longitude": "\uacbd\ub3c4", + "radius": "\uce21\uc815 \uc2a4\ud14c\uc774\uc158 \ubc18\uacbd (\ub9c8\uc77c; \uc120\ud0dd \uc0ac\ud56d)" + }, + "description": "AirNow \uacf5\uae30 \ud488\uc9c8 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://docs.airnowapi.org/account/request \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", + "title": "AirNow" } } - } + }, + "title": "AirNow" } \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/bg.json b/homeassistant/components/airvisual/translations/bg.json new file mode 100644 index 00000000000000..7e463418576e50 --- /dev/null +++ b/homeassistant/components/airvisual/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "geography_by_name": { + "data": { + "city": "\u0413\u0440\u0430\u0434", + "country": "\u0421\u0442\u0440\u0430\u043d\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/he.json b/homeassistant/components/airvisual/translations/he.json index 7d1a1c696ed07c..e32efda96dcb6a 100644 --- a/homeassistant/components/airvisual/translations/he.json +++ b/homeassistant/components/airvisual/translations/he.json @@ -6,7 +6,13 @@ "step": { "geography": { "data": { - "api_key": "\u05de\u05e4\u05ea\u05d7 API" + "api_key": "\u05de\u05e4\u05ea\u05d7 API", + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + }, + "node_pro": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" } } } diff --git a/homeassistant/components/airvisual/translations/hu.json b/homeassistant/components/airvisual/translations/hu.json index 53ab734e5059b0..89584cd128b7b0 100644 --- a/homeassistant/components/airvisual/translations/hu.json +++ b/homeassistant/components/airvisual/translations/hu.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van vagy a Node/Pro azonos\u00edt\u00f3 m\u00e1r regisztr\u00e1lva van.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "general_error": "Ismeretlen hiba t\u00f6rt\u00e9nt." + "general_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" }, "step": { "geography": { @@ -12,8 +17,23 @@ "longitude": "Hossz\u00fas\u00e1g" } }, + "geography_by_coords": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + } + }, + "geography_by_name": { + "data": { + "api_key": "API kulcs", + "city": "V\u00e1ros", + "country": "Orsz\u00e1g" + } + }, "node_pro": { "data": { + "ip_address": "Hoszt", "password": "Jelsz\u00f3" } }, @@ -21,6 +41,11 @@ "data": { "api_key": "API kulcs" } + }, + "user": { + "data": { + "type": "Integr\u00e1ci\u00f3 t\u00edpusa" + } } } } diff --git a/homeassistant/components/airvisual/translations/id.json b/homeassistant/components/airvisual/translations/id.json new file mode 100644 index 00000000000000..3c689338d9f3e7 --- /dev/null +++ b/homeassistant/components/airvisual/translations/id.json @@ -0,0 +1,77 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi atau ID Node/Pro sudah terdaftar.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "general_error": "Kesalahan yang tidak diharapkan", + "invalid_api_key": "Kunci API tidak valid", + "location_not_found": "Lokasi tidak ditemukan" + }, + "step": { + "geography": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Gunakan API cloud AirVisual untuk memantau lokasi geografis.", + "title": "Konfigurasikan Lokasi Geografi" + }, + "geography_by_coords": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Gunakan API cloud AirVisual untuk memantau satu pasang lintang/bujur.", + "title": "Konfigurasikan Lokasi Geografi" + }, + "geography_by_name": { + "data": { + "api_key": "Kunci API", + "city": "Kota", + "country": "Negara", + "state": "negara bagian" + }, + "description": "Gunakan API cloud AirVisual untuk memantau kota/negara bagian/negara.", + "title": "Konfigurasikan Lokasi Geografi" + }, + "node_pro": { + "data": { + "ip_address": "Host", + "password": "Kata Sandi" + }, + "description": "Pantau unit AirVisual pribadi. Kata sandi dapat diambil dari antarmuka unit.", + "title": "Konfigurasikan AirVisual Node/Pro" + }, + "reauth_confirm": { + "data": { + "api_key": "Kunci API" + }, + "title": "Autentikasi Ulang AirVisual" + }, + "user": { + "data": { + "cloud_api": "Lokasi Geografis", + "node_pro": "AirVisual Node Pro", + "type": "Jenis Integrasi" + }, + "description": "Pilih jenis data AirVisual yang ingin dipantau.", + "title": "Konfigurasikan AirVisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Tampilkan lokasi geografi yang dipantau pada peta" + }, + "title": "Konfigurasikan AirVisual" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/ko.json b/homeassistant/components/airvisual/translations/ko.json index 8cf450e597b29c..3ab3dbcf2867d0 100644 --- a/homeassistant/components/airvisual/translations/ko.json +++ b/homeassistant/components/airvisual/translations/ko.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Node/Pro ID \uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uac70\ub098 \uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured": "Node/Pro ID\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uac70\ub098 \uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "general_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", - "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "location_not_found": "\uc704\uce58\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { "geography": { @@ -24,12 +25,19 @@ "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4" - } + }, + "description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc704\ub3c4/\uacbd\ub3c4\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", + "title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30" }, "geography_by_name": { "data": { - "api_key": "API \ud0a4" - } + "api_key": "API \ud0a4", + "city": "\ub3c4\uc2dc", + "country": "\uad6d\uac00", + "state": "\uc8fc" + }, + "description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub3c4\uc2dc/\uc8fc/\uad6d\uac00\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", + "title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30" }, "node_pro": { "data": { @@ -42,7 +50,8 @@ "reauth_confirm": { "data": { "api_key": "API \ud0a4" - } + }, + "title": "AirVisual \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json index ecf2322c801350..ed81d6568edfff 100644 --- a/homeassistant/components/airvisual/translations/nl.json +++ b/homeassistant/components/airvisual/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten of Node / Pro ID zijn al geregistreerd.", + "already_configured": "Locatie is al geconfigureerd. of Node/Pro IDis al geregistreerd.", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { @@ -25,15 +25,19 @@ "api_key": "API-sleutel", "latitude": "Breedtegraad", "longitude": "Lengtegraad" - } + }, + "description": "Gebruik de AirVisual-cloud-API om een lengte- / breedtegraad te bewaken.", + "title": "Configureer een geografie" }, "geography_by_name": { "data": { "api_key": "API-sleutel", "city": "Stad", - "country": "Land" + "country": "Land", + "state": "staat" }, - "description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken." + "description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken.", + "title": "Configureer een geografie" }, "node_pro": { "data": { diff --git a/homeassistant/components/alarm_control_panel/translations/id.json b/homeassistant/components/alarm_control_panel/translations/id.json index cbc3d31370c9f6..f1676ce8c7561c 100644 --- a/homeassistant/components/alarm_control_panel/translations/id.json +++ b/homeassistant/components/alarm_control_panel/translations/id.json @@ -1,14 +1,37 @@ { + "device_automation": { + "action_type": { + "arm_away": "Aktifkan {entity_name} untuk keluar", + "arm_home": "Aktifkan {entity_name} untuk di rumah", + "arm_night": "Aktifkan {entity_name} untuk malam", + "disarm": "Nonaktifkan {entity_name}", + "trigger": "Picu {entity_name}" + }, + "condition_type": { + "is_armed_away": "{entity_name} diaktifkan untuk keluar", + "is_armed_home": "{entity_name} diaktifkan untuk di rumah", + "is_armed_night": "{entity_name} diaktifkan untuk malam", + "is_disarmed": "{entity_name} dinonaktifkan", + "is_triggered": "{entity_name} dipicu" + }, + "trigger_type": { + "armed_away": "{entity_name} diaktifkan untuk keluar", + "armed_home": "{entity_name} diaktifkan untuk di rumah", + "armed_night": "{entity_name} diaktifkan untuk malam", + "disarmed": "{entity_name} dinonaktifkan", + "triggered": "{entity_name} dipicu" + } + }, "state": { "_": { - "armed": "Bersenjata", - "armed_away": "Armed away", - "armed_custom_bypass": "Armed custom bypass", - "armed_home": "Armed home", - "armed_night": "Armed night", - "arming": "Mempersenjatai", - "disarmed": "Dilucuti", - "disarming": "Melucuti", + "armed": "Diaktifkan", + "armed_away": "Diaktifkan untuk keluar", + "armed_custom_bypass": "Diaktifkan khusus", + "armed_home": "Diaktifkan untuk di rumah", + "armed_night": "Diaktifkan untuk malam", + "arming": "Mengaktifkan", + "disarmed": "Dinonaktifkan", + "disarming": "Dinonaktifkan", "pending": "Tertunda", "triggered": "Terpicu" } diff --git a/homeassistant/components/alarm_control_panel/translations/ko.json b/homeassistant/components/alarm_control_panel/translations/ko.json index f6adb68fe66093..0fd766ba0b9a60 100644 --- a/homeassistant/components/alarm_control_panel/translations/ko.json +++ b/homeassistant/components/alarm_control_panel/translations/ko.json @@ -1,35 +1,35 @@ { "device_automation": { "action_type": { - "arm_away": "{entity_name} \uc678\ucd9c\uacbd\ube44", - "arm_home": "{entity_name} \uc7ac\uc2e4\uacbd\ube44", - "arm_night": "{entity_name} \uc57c\uac04\uacbd\ube44", - "disarm": "{entity_name} \uacbd\ube44\ud574\uc81c", - "trigger": "{entity_name} \ud2b8\ub9ac\uac70" + "arm_away": "{entity_name}\uc744(\ub97c) \uc678\ucd9c\uacbd\ube44\ub85c \uc124\uc815\ud558\uae30", + "arm_home": "{entity_name}\uc744(\ub97c) \uc7ac\uc2e4\uacbd\ube44\ub85c \uc124\uc815\ud558\uae30", + "arm_night": "{entity_name}\uc744(\ub97c) \uc57c\uac04\uacbd\ube44\ub85c \uc124\uc815\ud558\uae30", + "disarm": "{entity_name}\uc744(\ub97c) \uacbd\ube44\ud574\uc81c\ub85c \uc124\uc815\ud558\uae30", + "trigger": "{entity_name}\uc744(\ub97c) \ud2b8\ub9ac\uac70\ud558\uae30" }, "condition_type": { - "is_armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", - "is_armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", - "is_armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", - "is_disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c \uc0c1\ud0dc\uc774\uba74", - "is_triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub418\uc5c8\uc73c\uba74" + "is_armed_away": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", + "is_armed_home": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", + "is_armed_night": "{entity_name}\uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc \uc0c1\ud0dc\uc774\uba74", + "is_disarmed": "{entity_name}\uc774(\uac00) \ud574\uc81c \uc0c1\ud0dc\uc774\uba74", + "is_triggered": "{entity_name}\uc774(\uac00) \ud2b8\ub9ac\uac70 \ub418\uc5c8\uc73c\uba74" }, "trigger_type": { - "armed_away": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", - "armed_home": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", - "armed_night": "{entity_name} \uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub420 \ub54c", - "disarmed": "{entity_name} \uc774(\uac00) \ud574\uc81c\ub420 \ub54c", - "triggered": "{entity_name} \uc774(\uac00) \ud2b8\ub9ac\uac70\ub420 \ub54c" + "armed_away": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "armed_home": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "armed_night": "{entity_name}\uc774(\uac00) \uc57c\uac04 \uacbd\ube44\ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "disarmed": "{entity_name}\uc774(\uac00) \ud574\uc81c\ub418\uc5c8\uc744 \ub54c", + "triggered": "{entity_name}\uc774(\uac00) \ud2b8\ub9ac\uac70\ub418\uc5c8\uc744 \ub54c" } }, "state": { "_": { - "armed": "\uacbd\ube44\uc911", - "armed_away": "\uacbd\ube44\uc911(\uc678\ucd9c)", - "armed_custom_bypass": "\uacbd\ube44\uc911(\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", - "armed_home": "\uacbd\ube44\uc911(\uc7ac\uc2e4)", - "armed_night": "\uacbd\ube44\uc911(\uc57c\uac04)", - "arming": "\uacbd\ube44\uc911", + "armed": "\uacbd\ube44 \uc911", + "armed_away": "\uacbd\ube44 \uc911(\uc678\ucd9c)", + "armed_custom_bypass": "\uacbd\ube44 \uc911 (\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", + "armed_home": "\uacbd\ube44 \uc911(\uc7ac\uc2e4)", + "armed_night": "\uacbd\ube44 \uc911(\uc57c\uac04)", + "arming": "\uacbd\ube44 \uc911", "disarmed": "\ud574\uc81c\ub428", "disarming": "\ud574\uc81c\uc911", "pending": "\ubcf4\ub958\uc911", diff --git a/homeassistant/components/alarm_control_panel/translations/zh-Hans.json b/homeassistant/components/alarm_control_panel/translations/zh-Hans.json index 749674e8e6e62d..fa819e71b4921a 100644 --- a/homeassistant/components/alarm_control_panel/translations/zh-Hans.json +++ b/homeassistant/components/alarm_control_panel/translations/zh-Hans.json @@ -1,4 +1,27 @@ { + "device_automation": { + "action_type": { + "arm_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212", + "arm_home": "{entity_name} \u5728\u5bb6\u8b66\u6212", + "arm_night": "{entity_name} \u591c\u95f4\u8b66\u6212", + "disarm": "\u89e3\u9664 {entity_name} \u8b66\u6212", + "trigger": "\u89e6\u53d1 {entity_name}" + }, + "condition_type": { + "is_armed_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212", + "is_armed_home": "{entity_name} \u5728\u5bb6\u8b66\u6212", + "is_armed_night": "{entity_name} \u591c\u95f4\u8b66\u6212", + "is_disarmed": "{entity_name} \u8b66\u6212\u5df2\u89e3\u9664", + "is_triggered": "{entity_name} \u8b66\u62a5\u5df2\u89e6\u53d1" + }, + "trigger_type": { + "armed_away": "{entity_name} \u79bb\u5bb6\u8b66\u6212", + "armed_home": "{entity_name} \u5728\u5bb6\u8b66\u6212", + "armed_night": "{entity_name} \u591c\u95f4\u8b66\u6212", + "disarmed": "{entity_name} \u8b66\u6212\u89e3\u9664", + "triggered": "{entity_name} \u89e6\u53d1\u8b66\u62a5" + } + }, "state": { "_": { "armed": "\u8b66\u6212", diff --git a/homeassistant/components/alarmdecoder/translations/hu.json b/homeassistant/components/alarmdecoder/translations/hu.json index 2d5f91cf3737ff..8c80adcb3c0f54 100644 --- a/homeassistant/components/alarmdecoder/translations/hu.json +++ b/homeassistant/components/alarmdecoder/translations/hu.json @@ -1,10 +1,40 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "create_entry": { "default": "Sikeres csatlakoz\u00e1s az AlarmDecoderhez." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "protocol": { + "data": { + "host": "Hoszt", + "port": "Port" + } + }, + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "edit_select": "Szerkeszt\u00e9s" + } + }, + "zone_details": { + "data": { + "zone_name": "Z\u00f3na neve" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/id.json b/homeassistant/components/alarmdecoder/translations/id.json new file mode 100644 index 00000000000000..39c8282b36fc18 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/id.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "create_entry": { + "default": "Berhasil terhubung ke AlarmDecoder." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Laju Baud Perangkat", + "device_path": "Jalur Perangkat", + "host": "Host", + "port": "Port" + }, + "title": "Konfigurasikan pengaturan koneksi" + }, + "user": { + "data": { + "protocol": "Protokol" + }, + "title": "Pilih Protokol AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "Bidang di bawah ini harus berupa bilangan bulat.", + "loop_range": "RF Loop harus merupakan bilangan bulat antara 1 dan 4.", + "loop_rfid": "RF Loop tidak dapat digunakan tanpa RF Serial.", + "relay_inclusive": "Relay Address dan Relay Channel saling tergantung dan harus disertakan bersama-sama." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Mode Malam Alternatif", + "auto_bypass": "Diaktifkan Secara Otomatis", + "code_arm_required": "Kode Diperlukan untuk Mengaktifkan" + }, + "title": "Konfigurasikan AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Edit" + }, + "description": "Apa yang ingin diedit?", + "title": "Konfigurasikan AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "Nama Zona", + "zone_relayaddr": "Relay Address", + "zone_relaychan": "Relay Channel", + "zone_rfid": "RF Serial", + "zone_type": "Jenis Zona" + }, + "description": "Masukkan detail untuk zona {zone_number}. Untuk menghapus zona {zone_number}, kosongkan Nama Zona.", + "title": "Konfigurasikan AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Nomor Zona" + }, + "description": "Masukkan nomor zona yang ingin ditambahkan, diedit, atau dihapus.", + "title": "Konfigurasikan AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/ko.json b/homeassistant/components/alarmdecoder/translations/ko.json index 08383d37151b71..cdb63a01bfb4a3 100644 --- a/homeassistant/components/alarmdecoder/translations/ko.json +++ b/homeassistant/components/alarmdecoder/translations/ko.json @@ -12,62 +12,62 @@ "step": { "protocol": { "data": { - "device_baudrate": "\uc7a5\uce58 \uc804\uc1a1 \uc18d\ub3c4", - "device_path": "\uc7a5\uce58 \uacbd\ub85c", + "device_baudrate": "\uae30\uae30 \uc804\uc1a1 \uc18d\ub3c4", + "device_path": "\uae30\uae30 \uacbd\ub85c", "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "title": "\uc5f0\uacb0 \uc124\uc815 \uad6c\uc131" + "title": "\uc5f0\uacb0 \uc124\uc815 \uad6c\uc131\ud558\uae30" }, "user": { "data": { "protocol": "\ud504\ub85c\ud1a0\ucf5c" }, - "title": "AlarmDecoder \ud504\ub85c\ud1a0\ucf5c \uc120\ud0dd" + "title": "AlarmDecoder \ud504\ub85c\ud1a0\ucf5c \uc120\ud0dd\ud558\uae30" } } }, "options": { "error": { "int": "\uc544\ub798 \ud544\ub4dc\ub294 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", - "loop_range": "RF \ub8e8\ud504\ub294 1\uc5d0\uc11c 4 \uc0ac\uc774\uc758 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", - "loop_rfid": "RF \ub8e8\ud504\ub294 RF \uc2dc\ub9ac\uc5bc\uc5c6\uc774 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", - "relay_inclusive": "\ub9b4\ub808\uc774 \uc8fc\uc18c\uc640 \ub9b4\ub808\uc774 \ucc44\ub110\uc740 \uc11c\ub85c \uc758\uc874\uc801\uc774\uba70 \ud568\uaed8 \ud3ec\ud568\ub418\uc5b4\uc57c\ud569\ub2c8\ub2e4." + "loop_range": "RF \ub8e8\ud504\ub294 1\uacfc 4 \uc0ac\uc774\uc758 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", + "loop_rfid": "RF \ub8e8\ud504\ub294 RF \uc2dc\ub9ac\uc5bc \uc5c6\uc73c\uba74 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "relay_inclusive": "\ub9b4\ub808\uc774 \uc8fc\uc18c\uc640 \ub9b4\ub808\uc774 \ucc44\ub110\uc740 \uc0c1\ud638 \uc758\uc874\uc801\uc774\uae30 \ub54c\ubb38\uc5d0 \ud568\uaed8 \ud3ec\ud568\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "step": { "arm_settings": { "data": { "alt_night_mode": "\ub300\uccb4 \uc57c\uac04 \ubaa8\ub4dc", - "auto_bypass": "\uacbd\ube44\uc911 \uc790\ub3d9 \uc6b0\ud68c", + "auto_bypass": "\uacbd\ube44 \uc911 \uc790\ub3d9 \uc6b0\ud68c", "code_arm_required": "\uacbd\ube44\uc5d0 \ud544\uc694\ud55c \ucf54\ub4dc" }, - "title": "AlarmDecoder \uad6c\uc131" + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" }, "init": { "data": { - "edit_select": "\ud3b8\uc9d1" + "edit_select": "\ud3b8\uc9d1\ud558\uae30" }, - "description": "\ubb34\uc5c7\uc744 \ud3b8\uc9d1 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "AlarmDecoder \uad6c\uc131" + "description": "\ubb34\uc5c7\uc744 \ud3b8\uc9d1\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" }, "zone_details": { "data": { "zone_loop": "RF \ub8e8\ud504", - "zone_name": "\uc601\uc5ed \uc774\ub984", + "zone_name": "\uad6c\uc5ed \uc774\ub984", "zone_relayaddr": "\ub9b4\ub808\uc774 \uc8fc\uc18c", "zone_relaychan": "\ub9b4\ub808\uc774 \ucc44\ub110", "zone_rfid": "RF \uc2dc\ub9ac\uc5bc", - "zone_type": "\uc601\uc5ed \uc720\ud615" + "zone_type": "\uad6c\uc5ed \uc720\ud615" }, - "description": "{zone_number} \uc601\uc5ed\uc5d0 \ub300\ud55c \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud569\ub2c8\ub2e4. {zone_number} \uc601\uc5ed\uc744 \uc0ad\uc81c\ud558\ub824\uba74 \uc601\uc5ed \uc774\ub984\uc744 \ube44\uc6cc \ub461\ub2c8\ub2e4.", - "title": "AlarmDecoder \uad6c\uc131" + "description": "\uad6c\uc5ed {zone_number}\uc5d0 \ub300\ud55c \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uad6c\uc5ed {zone_number}\uc744(\ub97c) \uc0ad\uc81c\ud558\ub824\uba74 \uad6c\uc5ed \uc774\ub984\uc744 \ube44\uc6cc\ub450\uc138\uc694.", + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" }, "zone_select": { "data": { "zone_number": "\uad6c\uc5ed \ubc88\ud638" }, - "description": "\ucd94\uac00, \ud3b8\uc9d1 \ub610\ub294 \uc81c\uac70\ud560 \uc601\uc5ed \ubc88\ud638\ub97c \uc785\ub825\ud569\ub2c8\ub2e4.", - "title": "AlarmDecoder \uad6c\uc131" + "description": "\ucd94\uac00, \ud3b8\uc9d1 \ub610\ub294 \uc81c\uac70\ud560 \uad6c\uc5ed \ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "AlarmDecoder \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/almond/translations/ca.json b/homeassistant/components/almond/translations/ca.json index 8ba96e603f5475..3f9ce6353385b1 100644 --- a/homeassistant/components/almond/translations/ca.json +++ b/homeassistant/components/almond/translations/ca.json @@ -9,7 +9,7 @@ "step": { "hassio_confirm": { "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb Almond proporcionat pel complement de Hass.io: {addon}?", - "title": "Almond (complement de Hass.io)" + "title": "Almond via complement de Hass.io" }, "pick_implementation": { "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" diff --git a/homeassistant/components/almond/translations/cs.json b/homeassistant/components/almond/translations/cs.json index 1c667c9d55ea26..dc981403ad23ad 100644 --- a/homeassistant/components/almond/translations/cs.json +++ b/homeassistant/components/almond/translations/cs.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k Almond pomoc\u00ed hass.io {addon}?", - "title": "Almond prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k Almond pomoc\u00ed Supervisor {addon}?", + "title": "Almond prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" }, "pick_implementation": { "title": "Vyberte metodu ov\u011b\u0159en\u00ed" diff --git a/homeassistant/components/almond/translations/da.json b/homeassistant/components/almond/translations/da.json index 37c66ea8efd28a..0e7a804acc6f65 100644 --- a/homeassistant/components/almond/translations/da.json +++ b/homeassistant/components/almond/translations/da.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til Almond leveret af Hass.io-tilf\u00f8jelsen: {addon}?", - "title": "Almond via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til Almond leveret af Supervisor-tilf\u00f8jelsen: {addon}?", + "title": "Almond via Supervisor-tilf\u00f8jelse" }, "pick_implementation": { "title": "V\u00e6lg godkendelsesmetode" diff --git a/homeassistant/components/almond/translations/de.json b/homeassistant/components/almond/translations/de.json index 5eb8c4940aa237..1f69b1c09e4197 100644 --- a/homeassistant/components/almond/translations/de.json +++ b/homeassistant/components/almond/translations/de.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit Almond als Hass.io-Add-On hergestellt wird: {addon}?", - "title": "Almond \u00fcber das Hass.io Add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit Almond als Supervisor-Add-On hergestellt wird: {addon}?", + "title": "Almond \u00fcber das Supervisor Add-on" }, "pick_implementation": { "title": "W\u00e4hle die Authentifizierungsmethode" diff --git a/homeassistant/components/almond/translations/es-419.json b/homeassistant/components/almond/translations/es-419.json index 50a43d67b6d95c..ce1d655d69e7fa 100644 --- a/homeassistant/components/almond/translations/es-419.json +++ b/homeassistant/components/almond/translations/es-419.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Hass.io: {addon}?", - "title": "Almond a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Supervisor: {addon}?", + "title": "Almond a trav\u00e9s del complemento Supervisor" }, "pick_implementation": { "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" diff --git a/homeassistant/components/almond/translations/es.json b/homeassistant/components/almond/translations/es.json index f14d3cd04ee397..4dc5e4ee1c0dcd 100644 --- a/homeassistant/components/almond/translations/es.json +++ b/homeassistant/components/almond/translations/es.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Hass.io: {addon} ?", - "title": "Almond a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a Almond proporcionado por el complemento Supervisor: {addon} ?", + "title": "Almond a trav\u00e9s del complemento Supervisor" }, "pick_implementation": { "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" diff --git a/homeassistant/components/almond/translations/et.json b/homeassistant/components/almond/translations/et.json index 55ab29b2a819d0..c8646d6f09072c 100644 --- a/homeassistant/components/almond/translations/et.json +++ b/homeassistant/components/almond/translations/et.json @@ -9,7 +9,7 @@ "step": { "hassio_confirm": { "description": "Kas soovid seadistada Home Assistant-i \u00fchendust Almondiga mida pakub Hass.io lisandmoodul: {addon} ?", - "title": "Almond Hass.io lisandmooduli kaudu" + "title": "Almond Hass.io lisandmooduli abil" }, "pick_implementation": { "title": "Vali tuvastusmeetod" diff --git a/homeassistant/components/almond/translations/fr.json b/homeassistant/components/almond/translations/fr.json index e4fb8610cd0f18..0e6a8e0be3f6c6 100644 --- a/homeassistant/components/almond/translations/fr.json +++ b/homeassistant/components/almond/translations/fr.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Voulez-vous configurer Home Assistant pour se connecter \u00e0 Almond fourni par le module compl\u00e9mentaire Hass.io: {addon} ?", - "title": "Almonf via le module compl\u00e9mentaire Hass.io" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 Almond fourni par le module compl\u00e9mentaire Hass.io: {addon} ?", + "title": "Amande via le module compl\u00e9mentaire Hass.io" }, "pick_implementation": { "title": "S\u00e9lectionner une m\u00e9thode d'authentification" diff --git a/homeassistant/components/almond/translations/hu.json b/homeassistant/components/almond/translations/hu.json index 7654f66bc28812..568cd7270de260 100644 --- a/homeassistant/components/almond/translations/hu.json +++ b/homeassistant/components/almond/translations/hu.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "cannot_connect": "Nem lehet csatlakozni az Almond szerverhez.", - "missing_configuration": "K\u00e9rj\u00fck, ellen\u0151rizze az Almond be\u00e1ll\u00edt\u00e1s\u00e1nak dokument\u00e1ci\u00f3j\u00e1t." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "hassio_confirm": { - "description": "Be szeretn\u00e9 \u00e1ll\u00edtani a Home Assistant alkalmaz\u00e1st az Almondhoz val\u00f3 csatlakoz\u00e1shoz, amelyet a Hass.io kieg\u00e9sz\u00edt\u0151 biztos\u00edt: {addon} ?", - "title": "Almond a Hass.io kieg\u00e9sz\u00edt\u0151n kereszt\u00fcl" + "description": "Be szeretn\u00e9 \u00e1ll\u00edtani a Home Assistant alkalmaz\u00e1st az Almondhoz val\u00f3 csatlakoz\u00e1shoz, amelyet a Supervisor kieg\u00e9sz\u00edt\u0151 biztos\u00edt: {addon} ?", + "title": "Almond a Supervisor kieg\u00e9sz\u00edt\u0151n kereszt\u00fcl" }, "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/almond/translations/id.json b/homeassistant/components/almond/translations/id.json new file mode 100644 index 00000000000000..21a627132c4723 --- /dev/null +++ b/homeassistant/components/almond/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke Almond yang disediakan oleh add-on Supervisor {addon}?", + "title": "Almond melalui add-on Home Assistant" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/almond/translations/it.json b/homeassistant/components/almond/translations/it.json index ab5d7f86f196c1..7a41f00437b990 100644 --- a/homeassistant/components/almond/translations/it.json +++ b/homeassistant/components/almond/translations/it.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "description": "Vuoi configurare Home Assistant a connettersi ad Almond tramite il componente aggiuntivo Hass.io: {addon} ?", + "description": "Vuoi configurare Home Assistant per connettersi a Almond fornito dal componente aggiuntivo di Hass.io: {addon}?", "title": "Almond tramite il componente aggiuntivo di Hass.io" }, "pick_implementation": { diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index 062ef885c7050e..9fe759a6ba4f5f 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -4,12 +4,12 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c Almond \uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 Almond" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Almond\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 Almond" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" diff --git a/homeassistant/components/almond/translations/lb.json b/homeassistant/components/almond/translations/lb.json index 5a59645cdaa222..0e29d69bbed628 100644 --- a/homeassistant/components/almond/translations/lb.json +++ b/homeassistant/components/almond/translations/lb.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam Almond ze verbannen dee vun der hass.io Erweiderung {addon} bereet gestallt g\u00ebtt?", - "title": "Almond via Hass.io Erweiderung" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam Almond ze verbannen dee vun der Supervisor Erweiderung {addon} bereet gestallt g\u00ebtt?", + "title": "Almond via Supervisor Erweiderung" }, "pick_implementation": { "title": "Wiel Authentifikatiouns Method aus" diff --git a/homeassistant/components/almond/translations/nl.json b/homeassistant/components/almond/translations/nl.json index 26bbc8dea876a3..6d3aaf29e97119 100644 --- a/homeassistant/components/almond/translations/nl.json +++ b/homeassistant/components/almond/translations/nl.json @@ -2,17 +2,17 @@ "config": { "abort": { "cannot_connect": "Kan geen verbinding maken met de Almond-server.", - "missing_configuration": "Raadpleeg de documentatie over het instellen van Almond.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { "hassio_confirm": { - "description": "Wilt u Home Assistant configureren om verbinding te maken met Almond die wordt aangeboden door de hass.io add-on {addon} ?", - "title": "Almond via Hass.io add-on" + "description": "Wilt u Home Assistant configureren om verbinding te maken met Almond die wordt aangeboden door de Supervisor add-on {addon} ?", + "title": "Almond via Supervisor add-on" }, "pick_implementation": { - "title": "Kies de authenticatie methode" + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index 9cd22ca5bc56f9..e6ca8f165897ab 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Hass.io-tillegg: {addon}?", + "description": "Vil du konfigurere Home Assistant for \u00e5 koble til Mandel levert av Hass.io-tillegget: {addon} ?", "title": "Almond via Hass.io-tillegg" }, "pick_implementation": { diff --git a/homeassistant/components/almond/translations/sl.json b/homeassistant/components/almond/translations/sl.json index 573df43876f151..cb20393201f1dc 100644 --- a/homeassistant/components/almond/translations/sl.json +++ b/homeassistant/components/almond/translations/sl.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo z Almondom, ki ga ponuja dodatek Hass.io: {addon} ?", - "title": "Almond prek dodatka Hass.io" + "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo z Almondom, ki ga ponuja dodatek Supervisor: {addon} ?", + "title": "Almond prek dodatka Supervisor" }, "pick_implementation": { "title": "Izberite na\u010din preverjanja pristnosti" diff --git a/homeassistant/components/almond/translations/sv.json b/homeassistant/components/almond/translations/sv.json index 6a7dfdb970c07e..8b20512df9b3f6 100644 --- a/homeassistant/components/almond/translations/sv.json +++ b/homeassistant/components/almond/translations/sv.json @@ -6,8 +6,8 @@ }, "step": { "hassio_confirm": { - "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till Almond som tillhandah\u00e5lls av Hass.io-till\u00e4gget: {addon} ?", - "title": "Almond via Hass.io-till\u00e4gget" + "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till Almond som tillhandah\u00e5lls av Supervisor-till\u00e4gget: {addon} ?", + "title": "Almond via Supervisor-till\u00e4gget" }, "pick_implementation": { "title": "V\u00e4lj autentiseringsmetod" diff --git a/homeassistant/components/almond/translations/uk.json b/homeassistant/components/almond/translations/uk.json index 7f8c12917bbac3..db96ef3d0a3aec 100644 --- a/homeassistant/components/almond/translations/uk.json +++ b/homeassistant/components/almond/translations/uk.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "Almond (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" }, "pick_implementation": { "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0441\u043f\u043e\u0441\u0456\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0446\u0456\u0457" diff --git a/homeassistant/components/almond/translations/zh-Hant.json b/homeassistant/components/almond/translations/zh-Hant.json index 6312d4ecd1895d..c8004ecde4f1d5 100644 --- a/homeassistant/components/almond/translations/zh-Hant.json +++ b/homeassistant/components/almond/translations/zh-Hant.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 Almond\uff1f", - "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 Almond" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6\uff1a{addon} \u9023\u7dda\u81f3 Almond\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 Almond" }, "pick_implementation": { "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" diff --git a/homeassistant/components/ambiclimate/translations/hu.json b/homeassistant/components/ambiclimate/translations/hu.json index 19f706be1c8180..04035f04ccae6a 100644 --- a/homeassistant/components/ambiclimate/translations/hu.json +++ b/homeassistant/components/ambiclimate/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" } } } \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/id.json b/homeassistant/components/ambiclimate/translations/id.json new file mode 100644 index 00000000000000..66c30afcb09386 --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "access_token": "Terjadi kesalahan yang tidak diketahui saat membuat token akses.", + "already_configured": "Akun sudah dikonfigurasi", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "follow_link": "Buka tautan dan autentikasi sebelum menekan Kirim", + "no_token": "Tidak diautentikasi dengan Ambiclimate" + }, + "step": { + "auth": { + "description": "Buka [tautan ini] ({authorization_url}) dan **Izinkan** akses ke akun Ambiclimate Anda, lalu kembali dan tekan **Kirim** di bawah ini.\n(Pastikan URL panggil balik yang ditentukan adalah {cb_url})", + "title": "Autentikasi Ambiclimate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/ko.json b/homeassistant/components/ambiclimate/translations/ko.json index c28affbebb3d52..e47c5041728522 100644 --- a/homeassistant/components/ambiclimate/translations/ko.json +++ b/homeassistant/components/ambiclimate/translations/ko.json @@ -10,11 +10,11 @@ }, "error": { "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", - "no_token": "Ambi Climate \ub85c \uc778\uc99d\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" + "no_token": "Ambi Climate\ub85c \uc778\uc99d\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" }, "step": { "auth": { - "description": "[\ub9c1\ud06c]({authorization_url}) \ub97c \ud074\ub9ad\ud558\uc5ec Ambiclimate \uacc4\uc815\uc5d0 \ub300\ud574 **\ud5c8\uc6a9**\ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n(\ucf5c\ubc31 URL \uc774 {cb_url} \ub85c \uc9c0\uc815\ub418\uc5c8\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", + "description": "[\ub9c1\ud06c]({authorization_url})(\uc744)\ub97c \ud074\ub9ad\ud558\uc5ec Ambiclimate \uacc4\uc815\uc5d0 \ub300\ud574 **\ud5c8\uc6a9**\ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n(\ucf5c\ubc31 URL\uc774 {cb_url}(\uc73c)\ub85c \uc9c0\uc815\ub418\uc5c8\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", "title": "Ambi Climate \uc778\uc99d\ud558\uae30" } } diff --git a/homeassistant/components/ambient_station/translations/hu.json b/homeassistant/components/ambient_station/translations/hu.json index e6b95634827da4..7c7e3a658b90ad 100644 --- a/homeassistant/components/ambient_station/translations/hu.json +++ b/homeassistant/components/ambient_station/translations/hu.json @@ -1,7 +1,10 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "error": { - "invalid_key": "\u00c9rv\u00e9nytelen API kulcs \u00e9s / vagy alkalmaz\u00e1skulcs", + "invalid_key": "\u00c9rv\u00e9nytelen API kulcs", "no_devices": "Nincs a fi\u00f3kodban tal\u00e1lhat\u00f3 eszk\u00f6z" }, "step": { diff --git a/homeassistant/components/ambient_station/translations/id.json b/homeassistant/components/ambient_station/translations/id.json new file mode 100644 index 00000000000000..1b5a1dd0b21eec --- /dev/null +++ b/homeassistant/components/ambient_station/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "invalid_key": "Kunci API tidak valid", + "no_devices": "Tidak ada perangkat yang ditemukan dalam akun" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "app_key": "Kunci Aplikasi" + }, + "title": "Isi informasi Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambient_station/translations/nl.json b/homeassistant/components/ambient_station/translations/nl.json index 02c8f0727f8b52..008bc10e084d5f 100644 --- a/homeassistant/components/ambient_station/translations/nl.json +++ b/homeassistant/components/ambient_station/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Service is al geconfigureerd" }, "error": { - "invalid_key": "Ongeldige API-sleutel en/of applicatiesleutel", + "invalid_key": "Ongeldige API-sleutel", "no_devices": "Geen apparaten gevonden in account" }, "step": { diff --git a/homeassistant/components/apple_tv/translations/hu.json b/homeassistant/components/apple_tv/translations/hu.json index 26c02fabbb4d85..63bf29a73f1aa2 100644 --- a/homeassistant/components/apple_tv/translations/hu.json +++ b/homeassistant/components/apple_tv/translations/hu.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "no_devices_found": "Nincs eszk\u00f6z a h\u00e1l\u00f3zaton", - "unknown": "V\u00e1ratlan hiba" + "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "invalid_auth": "Azonos\u00edt\u00e1s nem siker\u00fclt", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Apple TV: {name}", "step": { "confirm": { "title": "Apple TV sikeresen hozz\u00e1adva" @@ -19,7 +22,7 @@ }, "pair_with_pin": { "data": { - "pin": "PIN K\u00f3d" + "pin": "PIN-k\u00f3d" }, "title": "P\u00e1ros\u00edt\u00e1s" }, diff --git a/homeassistant/components/apple_tv/translations/id.json b/homeassistant/components/apple_tv/translations/id.json new file mode 100644 index 00000000000000..5646b4982422c4 --- /dev/null +++ b/homeassistant/components/apple_tv/translations/id.json @@ -0,0 +1,64 @@ +{ + "config": { + "abort": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "backoff": "Perangkat tidak bisa menerima permintaan pemasangan saat ini (Anda mungkin telah berulang kali memasukkan kode PIN yang salah). Coba lagi nanti.", + "device_did_not_pair": "Tidak ada upaya untuk menyelesaikan proses pemasangan dari sisi perangkat.", + "invalid_config": "Konfigurasi untuk perangkat ini tidak lengkap. Coba tambahkan lagi.", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "invalid_auth": "Autentikasi tidak valid", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "no_usable_service": "Perangkat ditemukan tetapi kami tidak dapat mengidentifikasi berbagai cara untuk membuat koneksi ke perangkat tersebut. Jika Anda terus melihat pesan ini, coba tentukan alamat IP-nya atau mulai ulang Apple TV Anda.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Apple TV: {name}", + "step": { + "confirm": { + "description": "Anda akan menambahkan Apple TV bernama `{name}` ke Home Assistant.\n\n** Untuk menyelesaikan proses, Anda mungkin harus memasukkan kode PIN beberapa kali.**\n\nPerhatikan bahwa Anda *tidak* akan dapat mematikan Apple TV dengan integrasi ini. Hanya pemutar media di Home Assistant yang akan dimatikan!", + "title": "Konfirmasikan menambahkan Apple TV" + }, + "pair_no_pin": { + "description": "Pemasangan diperlukan untuk layanan `{protocol}`. Masukkan PIN {pin} di Apple TV untuk melanjutkan.", + "title": "Memasangkan" + }, + "pair_with_pin": { + "data": { + "pin": "Kode PIN" + }, + "description": "Pemasangan diperlukan untuk protokol `{protocol}`. Masukkan kode PIN yang ditampilkan pada layar. Angka nol di awal harus dihilangkan. Misalnya, masukkan 123 jika kode yang ditampilkan adalah 0123.", + "title": "Memasangkan" + }, + "reconfigure": { + "description": "Apple TV ini mengalami masalah koneksi dan harus dikonfigurasi ulang.", + "title": "Konfigurasi ulang perangkat" + }, + "service_problem": { + "description": "Terjadi masalah saat protokol pemasangan `{protocol}`. Masalah ini akan diabaikan.", + "title": "Gagal menambahkan layanan" + }, + "user": { + "data": { + "device_input": "Perangkat" + }, + "description": "Mulai dengan memasukkan nama perangkat (misalnya Dapur atau Kamar Tidur) atau alamat IP Apple TV yang ingin ditambahkan. Jika ada perangkat yang ditemukan secara otomatis di jaringan Anda, perangkat tersebut akan ditampilkan di bawah ini.\n\nJika Anda tidak dapat melihat perangkat atau mengalami masalah, coba tentukan alamat IP perangkat.\n\n{devices}", + "title": "Siapkan Apple TV baru" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "Jangan nyalakan perangkat saat memulai Home Assistant" + }, + "description": "Konfigurasikan pengaturan umum perangkat" + } + } + }, + "title": "Apple TV" +} \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/ko.json b/homeassistant/components/apple_tv/translations/ko.json index c7e664b0638a0e..278dbec04e40bb 100644 --- a/homeassistant/components/apple_tv/translations/ko.json +++ b/homeassistant/components/apple_tv/translations/ko.json @@ -3,6 +3,9 @@ "abort": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "backoff": "\uae30\uae30\uac00 \ud604\uc7ac \ud398\uc5b4\ub9c1 \uc694\uccad\uc744 \uc218\ub77d\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4(\uc798\ubabb\ub41c PIN \ucf54\ub4dc\ub97c \ub108\ubb34 \ub9ce\uc774 \uc785\ub825\ud588\uc744 \uc218 \uc788\uc74c). \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "device_did_not_pair": "\uae30\uae30\uc5d0\uc11c \ud398\uc5b4\ub9c1 \ud504\ub85c\uc138\uc2a4\ub97c \uc644\ub8cc\ud558\ub824\uace0 \uc2dc\ub3c4\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", + "invalid_config": "\uc774 \uae30\uae30\uc5d0 \ub300\ud55c \uad6c\uc131\uc774 \ubd88\uc644\uc804\ud569\ub2c8\ub2e4. \ub2e4\uc2dc \ucd94\uac00\ud574\uc8fc\uc138\uc694.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, @@ -10,14 +13,52 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "no_usable_service": "\uae30\uae30\ub97c \ucc3e\uc558\uc9c0\ub9cc \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\ub294 \ubc29\ubc95\uc744 \uc2dd\ubcc4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uba54\uc2dc\uc9c0\uac00 \uacc4\uc18d \ud45c\uc2dc\ub418\uba74 \ud574\ub2f9 IP \uc8fc\uc18c\ub97c \uc9c1\uc811 \uc9c0\uc815\ud574\uc8fc\uc2dc\uac70\ub098 Apple TV\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc8fc\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Apple TV: {name}", "step": { + "confirm": { + "description": "Apple TV `{name}`\uc744(\ub97c) Home Assistant\uc5d0 \ucd94\uac00\ud558\ub824\uace0 \ud569\ub2c8\ub2e4.\n\n**\ud504\ub85c\uc138\uc2a4\ub97c \uc644\ub8cc\ud558\ub824\uba74 \uc5ec\ub7ec \uac1c\uc758 PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc57c \ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4.**\n\n\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \ud1b5\ud574 Apple TV\uc758 \uc804\uc6d0\uc740 *\ub04c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4*. Home Assistant\uc758 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\ub9cc \uaebc\uc9d1\ub2c8\ub2e4!", + "title": "Apple TV \ucd94\uac00 \ud655\uc778\ud558\uae30" + }, + "pair_no_pin": { + "description": "`{protocol}` \uc11c\ube44\uc2a4\uc5d0 \ub300\ud55c \ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uacc4\uc18d\ud558\ub824\uba74 Apple TV\uc5d0 PIN {pin}\uc744(\ub97c) \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" + }, "pair_with_pin": { "data": { "pin": "PIN \ucf54\ub4dc" - } + }, + "description": "`{protocol}` \ud504\ub85c\ud1a0\ucf5c\uc5d0 \ub300\ud55c \ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub418\ub294 PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc55e\uc790\ub9ac\uc758 0\uc740 \uc0dd\ub7b5\ub418\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc989, \ud45c\uc2dc\ub41c \ucf54\ub4dc\uac00 0123\uc774\uba74 123\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" + }, + "reconfigure": { + "description": "\uc774 Apple TV\uc5d0 \uc5f0\uacb0 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud558\uc5ec \ub2e4\uc2dc \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\uae30\uae30 \uc7ac\uad6c\uc131" + }, + "service_problem": { + "description": "\ud504\ub85c\ud1a0\ucf5c `{protocol}`\uc744(\ub97c) \ud398\uc5b4\ub9c1\ud558\ub294 \ub3d9\uc548 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\ub294 \ubb34\uc2dc\ub429\ub2c8\ub2e4.", + "title": "\uc11c\ube44\uc2a4\ub97c \ucd94\uac00\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "user": { + "data": { + "device_input": "\uae30\uae30" + }, + "description": "\uba3c\uc800 \ucd94\uac00\ud560 Apple TV\uc758 \uae30\uae30 \uc774\ub984(\uc608: \uc8fc\ubc29 \ub610\ub294 \uce68\uc2e4) \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uc7a5\uce58\uac00 \uc790\ub3d9\uc73c\ub85c \ubc1c\uacac\ub41c \uacbd\uc6b0 \ub2e4\uc74c\uacfc \uac19\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.\n\n\uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uac70\ub098 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud55c \uacbd\uc6b0 \uae30\uae30 IP \uc8fc\uc18c\ub97c \uc9c1\uc811 \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n{devices}", + "title": "\uc0c8\ub85c\uc6b4 Apple TV \uc124\uc815\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "start_off": "Home Assistant\ub97c \uc2dc\uc791\ud560 \ub54c \uae30\uae30\ub97c \ucf1c\uc9c0 \ub9d0\uc544 \uc8fc\uc138\uc694" + }, + "description": "\uc77c\ubc18 \uae30\uae30 \uc124\uc815 \uad6c\uc131" } } - } + }, + "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/apple_tv/translations/nl.json b/homeassistant/components/apple_tv/translations/nl.json index d809ac749b762b..e313e972188798 100644 --- a/homeassistant/components/apple_tv/translations/nl.json +++ b/homeassistant/components/apple_tv/translations/nl.json @@ -13,29 +13,52 @@ "already_configured": "Apparaat is al geconfigureerd", "invalid_auth": "Ongeldige authenticatie", "no_devices_found": "Geen apparaten gevonden op het netwerk", + "no_usable_service": "Er is een apparaat gevonden, maar er kon geen manier worden gevonden om er verbinding mee te maken. Als u dit bericht blijft zien, probeert u het IP-adres in te voeren of uw Apple TV opnieuw op te starten.", "unknown": "Onverwachte fout" }, "flow_title": "Apple TV: {name}", "step": { "confirm": { + "description": "U staat op het punt om de Apple TV met de naam `{name}` toe te voegen aan Home Assistant.\n\n**Om het proces te voltooien, moet u mogelijk meerdere PIN-codes invoeren.**\n\nLet op: u kunt uw Apple TV *niet* uitschakelen met deze integratie. Alleen de mediaspeler in Home Assistant wordt uitgeschakeld!", "title": "Bevestig het toevoegen van Apple TV" }, "pair_no_pin": { + "description": "Koppeling is vereist voor de `{protocol}` service. Voer de PIN {pin} in op uw Apple TV om verder te gaan.", "title": "Koppelen" }, "pair_with_pin": { "data": { "pin": "PIN-code" }, + "description": "Koppelen is vereist voor het `{protocol}` protocol. Voer de PIN-code in die op het scherm wordt getoond. Beginnende nullen moeten worden weggelaten, d.w.z. voer 123 in als de getoonde code 0123 is.", "title": "Koppelen" }, + "reconfigure": { + "description": "Deze Apple TV ondervindt verbindingsproblemen en moet opnieuw worden geconfigureerd.", + "title": "Apparaat herconfiguratie" + }, + "service_problem": { + "description": "Er is een probleem opgetreden tijdens het koppelen van protocol `{protocol}`. Dit wordt genegeerd.", + "title": "Dienst toevoegen mislukt" + }, "user": { "data": { "device_input": "Apparaat" }, + "description": "Begin met het invoeren van de apparaatnaam (bijv. Keuken of Slaapkamer) of het IP-adres van de Apple TV die u wilt toevoegen. Als er automatisch apparaten in uw netwerk zijn gevonden, worden deze hieronder weergegeven.\n\nAls u het apparaat niet kunt zien of problemen ondervindt, probeer dan het IP-adres van het apparaat in te voeren.\n\n{devices}", "title": "Stel een nieuwe Apple TV in" } } }, + "options": { + "step": { + "init": { + "data": { + "start_off": "Schakel het apparaat niet in wanneer u Home Assistant start" + }, + "description": "Algemene apparaatinstellingen configureren" + } + } + }, "title": "Apple TV" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/hu.json b/homeassistant/components/arcam_fmj/translations/hu.json index 563ede561557c5..4af1181a265b2b 100644 --- a/homeassistant/components/arcam_fmj/translations/hu.json +++ b/homeassistant/components/arcam_fmj/translations/hu.json @@ -1,7 +1,17 @@ { "config": { "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/id.json b/homeassistant/components/arcam_fmj/translations/id.json new file mode 100644 index 00000000000000..96b10140948bc5 --- /dev/null +++ b/homeassistant/components/arcam_fmj/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Arcam FMJ di {host}", + "step": { + "confirm": { + "description": "Ingin menambahkan Arcam FMJ `{host}` ke Home Assistant?" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Masukkan nama host atau alamat IP perangkat." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} diminta untuk dinyalakan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ko.json b/homeassistant/components/arcam_fmj/translations/ko.json index 532e5ef4c5f51c..29a2887f7e2e8d 100644 --- a/homeassistant/components/arcam_fmj/translations/ko.json +++ b/homeassistant/components/arcam_fmj/translations/ko.json @@ -8,7 +8,7 @@ "flow_title": "Arcam FMJ: {host}", "step": { "confirm": { - "description": "Home Assistant \uc5d0 Arcam FMJ `{host}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Home Assistant\uc5d0 Arcam FMJ `{host}`\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { @@ -21,7 +21,7 @@ }, "device_automation": { "trigger_type": { - "turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + "turn_on": "{entity_name}\uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pt.json b/homeassistant/components/arcam_fmj/translations/pt.json index 097e3d086d6494..af72dfe96e2c34 100644 --- a/homeassistant/components/arcam_fmj/translations/pt.json +++ b/homeassistant/components/arcam_fmj/translations/pt.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "already_in_progress": "O processo de configura\u00e7\u00e3o j\u00e1 est\u00e1 a decorrer", "cannot_connect": "Falha na liga\u00e7\u00e3o" }, "error": { diff --git a/homeassistant/components/asuswrt/translations/bg.json b/homeassistant/components/asuswrt/translations/bg.json new file mode 100644 index 00000000000000..dbb5f415f92913 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/bg.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435", + "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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/hu.json b/homeassistant/components/asuswrt/translations/hu.json new file mode 100644 index 00000000000000..4f47781a15c56a --- /dev/null +++ b/homeassistant/components/asuswrt/translations/hu.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "ssh_not_file": "Az SSH kulcsf\u00e1jl nem tal\u00e1lhat\u00f3", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "mode": "M\u00f3d", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "title": "AsusWRT Be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/id.json b/homeassistant/components/asuswrt/translations/id.json new file mode 100644 index 00000000000000..aa4eebd1f86c28 --- /dev/null +++ b/homeassistant/components/asuswrt/translations/id.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "pwd_and_ssh": "Hanya berikan kata sandi atau file kunci SSH", + "pwd_or_ssh": "Harap berikan kata sandi atau file kunci SSH", + "ssh_not_file": "File kunci SSH tidak ditemukan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "mode": "Mode", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "protocol": "Protokol komunikasi yang akan digunakan", + "ssh_key": "Jalur ke file kunci SSH Anda (bukan kata sandi)", + "username": "Nama Pengguna" + }, + "description": "Tetapkan parameter yang diperlukan untuk terhubung ke router Anda", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "Tenggang waktu dalam detik untuk perangkat dianggap sebagai keluar", + "dnsmasq": "Lokasi dnsmasq.leases di router file", + "interface": "Antarmuka statistik yang diinginkan (mis. eth0, eth1, dll)", + "require_ip": "Perangkat harus memiliki IP (untuk mode titik akses)", + "track_unknown": "Lacak perangkat yang tidak dikenal/tidak bernama" + }, + "title": "Opsi AsusWRT" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/asuswrt/translations/ko.json b/homeassistant/components/asuswrt/translations/ko.json index de3de06e6b1bf7..108d00adefb63f 100644 --- a/homeassistant/components/asuswrt/translations/ko.json +++ b/homeassistant/components/asuswrt/translations/ko.json @@ -1,11 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "pwd_and_ssh": "\ube44\ubc00\ubc88\ud638 \ub610\ub294 SSH \ud0a4 \ud30c\uc77c\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4", + "pwd_or_ssh": "\ube44\ubc00\ubc88\ud638 \ub610\ub294 SSH \ud0a4 \ud30c\uc77c\uc744 \ub123\uc5b4\uc8fc\uc138\uc694", + "ssh_not_file": "SSH \ud0a4 \ud30c\uc77c\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -16,8 +19,26 @@ "name": "\uc774\ub984", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", + "protocol": "\uc0ac\uc6a9\ud560 \ud1b5\uc2e0 \ud504\ub85c\ud1a0\ucf5c", + "ssh_key": "SSH \ud0a4 \ud30c\uc77c\uc758 \uacbd\ub85c (\ube44\ubc00\ubc88\ud638 \ub300\uccb4)", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "\ub77c\uc6b0\ud130\uc5d0 \uc5f0\uacb0\ud558\ub294 \ub370 \ud544\uc694\ud55c \ub9e4\uac1c \ubcc0\uc218\ub97c \uc124\uc815\ud558\uae30", + "title": "AsusWRT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "consider_home": "\uae30\uae30\uac00 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\uae30 \uc804\uc5d0 \uae30\ub2e4\ub9ac\ub294 \uc2dc\uac04(\ucd08)", + "dnsmasq": "dnsmasq.lease \ud30c\uc77c\uc758 \ub77c\uc6b0\ud130 \uc704\uce58", + "interface": "\ud1b5\uacc4\ub97c \uc6d0\ud558\ub294 \uc778\ud130\ud398\uc774\uc2a4 (\uc608: eth0, eth1 \ub4f1)", + "require_ip": "\uae30\uae30\uc5d0\ub294 IP\uac00 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4 (\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \ubaa8\ub4dc\uc778 \uacbd\uc6b0)", + "track_unknown": "\uc54c \uc218 \uc5c6\uac70\ub098 \uc774\ub984\uc774 \uc5c6\ub294 \uae30\uae30 \ucd94\uc801\ud558\uae30" + }, + "title": "AsusWRT \uc635\uc158" } } } diff --git a/homeassistant/components/asuswrt/translations/nl.json b/homeassistant/components/asuswrt/translations/nl.json index 9d1e76aaf2b5c2..f6f347f771ff0f 100644 --- a/homeassistant/components/asuswrt/translations/nl.json +++ b/homeassistant/components/asuswrt/translations/nl.json @@ -6,6 +6,8 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_host": "Ongeldige hostnaam of IP-adres", + "pwd_and_ssh": "Geef alleen wachtwoord of SSH-sleutelbestand op", + "pwd_or_ssh": "Geef een wachtwoord of SSH-sleutelbestand op", "ssh_not_file": "SSH-sleutelbestand niet gevonden", "unknown": "Onverwachte fout" }, @@ -17,8 +19,11 @@ "name": "Naam", "password": "Wachtwoord", "port": "Poort", + "protocol": "Te gebruiken communicatieprotocol", + "ssh_key": "Pad naar uw SSH-sleutelbestand (in plaats van wachtwoord)", "username": "Gebruikersnaam" }, + "description": "Stel de vereiste parameter in om verbinding te maken met uw router", "title": "AsusWRT" } } @@ -27,6 +32,10 @@ "step": { "init": { "data": { + "consider_home": "Aantal seconden dat wordt gewacht voordat een apparaat als afwezig wordt beschouwd", + "dnsmasq": "De locatie in de router van de dnsmasq.leases-bestanden", + "interface": "De interface waarvan u statistieken wilt (bijv. Eth0, eth1 enz.)", + "require_ip": "Apparaten moeten een IP-adres hebben (voor toegangspuntmodus)", "track_unknown": "Volg onbekende / naamloze apparaten" }, "title": "AsusWRT-opties" diff --git a/homeassistant/components/atag/translations/hu.json b/homeassistant/components/atag/translations/hu.json index 1d28556ba1ad5a..98e947ae64387c 100644 --- a/homeassistant/components/atag/translations/hu.json +++ b/homeassistant/components/atag/translations/hu.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { "data": { + "email": "E-mail", "host": "Hoszt", "port": "Port" } diff --git a/homeassistant/components/atag/translations/id.json b/homeassistant/components/atag/translations/id.json new file mode 100644 index 00000000000000..24732f8c235bda --- /dev/null +++ b/homeassistant/components/atag/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unauthorized": "Pemasangan ditolak, periksa perangkat untuk permintaan autentikasi" + }, + "step": { + "user": { + "data": { + "email": "Email", + "host": "Host", + "port": "Port" + }, + "title": "Hubungkan ke perangkat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/ca.json b/homeassistant/components/august/translations/ca.json index 9d3e3535e46c6d..f530b2cdc5925d 100644 --- a/homeassistant/components/august/translations/ca.json +++ b/homeassistant/components/august/translations/ca.json @@ -25,7 +25,7 @@ "code": "Codi de verificaci\u00f3" }, "description": "Comprova el teu {login_method} ({username}) i introdueix el codi de verificaci\u00f3 a continuaci\u00f3", - "title": "Autenticaci\u00f3 de dos factors" + "title": "Verificaci\u00f3 en dos passos" } } } diff --git a/homeassistant/components/august/translations/he.json b/homeassistant/components/august/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/august/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/august/translations/hu.json b/homeassistant/components/august/translations/hu.json index dee4ed9ee0fa4d..dd2f600435468f 100644 --- a/homeassistant/components/august/translations/hu.json +++ b/homeassistant/components/august/translations/hu.json @@ -1,11 +1,27 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s (m\u00e1sodperc)", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } + }, + "validation": { + "data": { + "code": "Ellen\u0151rz\u0151 k\u00f3d" + }, + "title": "K\u00e9tfaktoros hiteles\u00edt\u00e9s" } } } diff --git a/homeassistant/components/august/translations/id.json b/homeassistant/components/august/translations/id.json new file mode 100644 index 00000000000000..a66c43ce05755f --- /dev/null +++ b/homeassistant/components/august/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "login_method": "Metode Masuk", + "password": "Kata Sandi", + "timeout": "Tenggang waktu (detik)", + "username": "Nama Pengguna" + }, + "description": "Jika Metode Masuk adalah 'email', Nama Pengguna adalah alamat email. Jika Metode Masuk adalah 'telepon', Nama Pengguna adalah nomor telepon dalam format '+NNNNNNNNN'.", + "title": "Siapkan akun August" + }, + "validation": { + "data": { + "code": "Kode verifikasi" + }, + "description": "Periksa {login_method} ({username}) Anda dan masukkan kode verifikasi di bawah ini", + "title": "Autentikasi dua faktor" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index e48d27801ccdc7..41af77b0c7c0fc 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd", + "already_configured": "Account is al geconfigureerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/august/translations/zh-Hant.json b/homeassistant/components/august/translations/zh-Hant.json index 667d881465962f..d2721cd33cc31b 100644 --- a/homeassistant/components/august/translations/zh-Hant.json +++ b/homeassistant/components/august/translations/zh-Hant.json @@ -25,7 +25,7 @@ "code": "\u9a57\u8b49\u78bc" }, "description": "\u8acb\u78ba\u8a8d {login_method} ({username}) \u4e26\u65bc\u4e0b\u65b9\u8f38\u5165\u9a57\u8b49\u78bc", - "title": "\u5169\u6b65\u9a5f\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" } } } diff --git a/homeassistant/components/aurora/translations/hu.json b/homeassistant/components/aurora/translations/hu.json index d5363860cbd0f3..292ed55223547e 100644 --- a/homeassistant/components/aurora/translations/hu.json +++ b/homeassistant/components/aurora/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni" + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { diff --git a/homeassistant/components/aurora/translations/id.json b/homeassistant/components/aurora/translations/id.json new file mode 100644 index 00000000000000..66cf534b7ae22f --- /dev/null +++ b/homeassistant/components/aurora/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "Ambang (%)" + } + } + } + }, + "title": "Sensor Aurora NOAA" +} \ No newline at end of file diff --git a/homeassistant/components/aurora/translations/ko.json b/homeassistant/components/aurora/translations/ko.json index ea10c059f03d32..39f3c4d42818c7 100644 --- a/homeassistant/components/aurora/translations/ko.json +++ b/homeassistant/components/aurora/translations/ko.json @@ -12,5 +12,15 @@ } } } - } + }, + "options": { + "step": { + "init": { + "data": { + "threshold": "\uc784\uacc4\uac12 (%)" + } + } + } + }, + "title": "NOAA Aurora \uc13c\uc11c" } \ No newline at end of file diff --git a/homeassistant/components/auth/translations/id.json b/homeassistant/components/auth/translations/id.json index f6a22386f99bfb..ed7bede5fff458 100644 --- a/homeassistant/components/auth/translations/id.json +++ b/homeassistant/components/auth/translations/id.json @@ -1,13 +1,32 @@ { "mfa_setup": { + "notify": { + "abort": { + "no_available_service": "Tidak ada layanan notifikasi yang tersedia." + }, + "error": { + "invalid_code": "Kode tifak valid, coba lagi." + }, + "step": { + "init": { + "description": "Pilih salah satu layanan notifikasi:", + "title": "Siapkan kata sandi sekali pakai yang dikirimkan oleh komponen notify" + }, + "setup": { + "description": "Kata sandi sekali pakai telah dikirim melalui **notify.{notify_service}**. Masukkan di bawah ini:", + "title": "Verifikasi penyiapan" + } + }, + "title": "Kata Sandi Sekali Pakai Notifikasi" + }, "totp": { "error": { - "invalid_code": "Kode salah, coba lagi. Jika Anda mendapatkan kesalahan ini secara konsisten, pastikan jam pada sistem Home Assistant anda akurat." + "invalid_code": "Kode tidak valid, coba lagi. Jika Anda terus mendapatkan kesalahan yang sama, pastikan jam pada sistem Home Assistant Anda sudah akurat." }, "step": { "init": { - "description": "Untuk mengaktifkan otentikasi dua faktor menggunakan password satu kali berbasis waktu, pindai kode QR dengan aplikasi otentikasi Anda. Jika Anda tidak memilikinya, kami menyarankan [Google Authenticator] (https://support.google.com/accounts/answer/1066447) atau [Authy] (https://authy.com/). \n\n {qr_code} \n \n Setelah memindai kode, masukkan kode enam digit dari aplikasi Anda untuk memverifikasi pengaturan. Jika Anda mengalami masalah saat memindai kode QR, lakukan pengaturan manual dengan kode ** ` {code} ` **.", - "title": "Siapkan otentikasi dua faktor menggunakan TOTP" + "description": "Untuk mengaktifkan autentikasi dua faktor menggunakan kata sandi sekali pakai berbasis waktu, pindai kode QR dengan aplikasi autentikasi Anda. Jika tidak punya, kami menyarankan aplikasi [Google Authenticator] (https://support.google.com/accounts/answer/1066447) atau [Authy] (https://authy.com/). \n\n {qr_code} \n \nSetelah memindai kode, masukkan kode enam digit dari aplikasi Anda untuk memverifikasi penyiapan. Jika mengalami masalah saat memindai kode QR, lakukan penyiapan manual dengan kode **`{code}`**.", + "title": "Siapkan autentikasi dua faktor menggunakan TOTP" } }, "title": "TOTP" diff --git a/homeassistant/components/auth/translations/ko.json b/homeassistant/components/auth/translations/ko.json index 80850bb58b4c64..09af8eb89bfa7e 100644 --- a/homeassistant/components/auth/translations/ko.json +++ b/homeassistant/components/auth/translations/ko.json @@ -5,7 +5,7 @@ "no_available_service": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc54c\ub9bc \uc11c\ube44\uc2a4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." }, "error": { - "invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "invalid_code": "\ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "init": { @@ -13,7 +13,7 @@ "title": "\uc54c\ub9bc \uad6c\uc131\uc694\uc18c\uac00 \uc81c\uacf5\ud558\ub294 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc124\uc815\ud558\uae30" }, "setup": { - "description": "**notify.{notify_service}** \uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574\uc8fc\uc138\uc694:", + "description": "**notify.{notify_service}**\uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574\uc8fc\uc138\uc694:", "title": "\uc124\uc815 \ud655\uc778\ud558\uae30" } }, @@ -21,11 +21,11 @@ }, "totp": { "error": { - "invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." + "invalid_code": "\ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant\uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "step": { "init": { - "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \uad6c\uc131\ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud558\uc5ec \uc124\uc815\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \uad6c\uc131\ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/)\ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud558\uc5ec \uc124\uc815\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131\ud558\uae30" } }, diff --git a/homeassistant/components/auth/translations/zh-Hant.json b/homeassistant/components/auth/translations/zh-Hant.json index 96e7f21ac998c1..8e769cb59830cf 100644 --- a/homeassistant/components/auth/translations/zh-Hant.json +++ b/homeassistant/components/auth/translations/zh-Hant.json @@ -25,8 +25,8 @@ }, "step": { "init": { - "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u96d9\u91cd\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002", - "title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u96d9\u91cd\u9a57\u8b49" + "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u96d9\u91cd\u8a8d\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002", + "title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u96d9\u91cd\u8a8d\u8b49" } }, "title": "TOTP" diff --git a/homeassistant/components/automation/translations/id.json b/homeassistant/components/automation/translations/id.json index eabfe0b64aac4b..58e8497c8b9c0f 100644 --- a/homeassistant/components/automation/translations/id.json +++ b/homeassistant/components/automation/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Otomasi" diff --git a/homeassistant/components/awair/translations/hu.json b/homeassistant/components/awair/translations/hu.json index 436e8b1fb7dd75..53827adf344e78 100644 --- a/homeassistant/components/awair/translations/hu.json +++ b/homeassistant/components/awair/translations/hu.json @@ -1,7 +1,28 @@ { "config": { "abort": { - "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "email": "E-mail" + }, + "description": "Add meg \u00fajra az Awair fejleszt\u0151i hozz\u00e1f\u00e9r\u00e9si tokent." + }, + "user": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "email": "E-mail" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/awair/translations/id.json b/homeassistant/components/awair/translations/id.json new file mode 100644 index 00000000000000..2c6fab90909932 --- /dev/null +++ b/homeassistant/components/awair/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_access_token": "Token akses tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth": { + "data": { + "access_token": "Token Akses", + "email": "Email" + }, + "description": "Masukkan kembali token akses pengembang Awair Anda." + }, + "user": { + "data": { + "access_token": "Token Akses", + "email": "Email" + }, + "description": "Anda harus mendaftar untuk mendapatkan token akses pengembang Awair di: https://developer.getawair.com/onboard/login" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/he.json b/homeassistant/components/axis/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/axis/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/hu.json b/homeassistant/components/axis/translations/hu.json index 659c50e49e7988..972690ede9724a 100644 --- a/homeassistant/components/axis/translations/hu.json +++ b/homeassistant/components/axis/translations/hu.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "flow_title": "Axis eszk\u00f6z: {name} ({host})", "step": { diff --git a/homeassistant/components/axis/translations/id.json b/homeassistant/components/axis/translations/id.json new file mode 100644 index 00000000000000..cdd498a8e6cd3b --- /dev/null +++ b/homeassistant/components/axis/translations/id.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "link_local_address": "Tautan alamat lokal tidak didukung", + "not_axis_device": "Perangkat yang ditemukan bukan perangkat Axis" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "{name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Siapkan perangkat Axis" + } + } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Pilih profil streaming yang akan digunakan" + }, + "title": "Opsi streaming video perangkat Axis" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 345e6622e9384a..8ba305366f9d16 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -23,5 +23,15 @@ "title": "Stel het Axis-apparaat in" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Selecteer stream profiel om te gebruiken" + }, + "title": "Opties voor videostreams van Axis-apparaten" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/hu.json b/homeassistant/components/azure_devops/translations/hu.json index 6bd42409877c9b..460b6132048515 100644 --- a/homeassistant/components/azure_devops/translations/hu.json +++ b/homeassistant/components/azure_devops/translations/hu.json @@ -1,11 +1,19 @@ { "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" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "reauth": { "description": "A(z) {project_url} hiteles\u00edt\u00e9se nem siker\u00fclt. K\u00e9rj\u00fck, adja meg jelenlegi hiteles\u00edt\u0151 adatait." + }, + "user": { + "title": "Azure DevOps Project hozz\u00e1ad\u00e1sa" } } } diff --git a/homeassistant/components/azure_devops/translations/id.json b/homeassistant/components/azure_devops/translations/id.json new file mode 100644 index 00000000000000..42292805b0882d --- /dev/null +++ b/homeassistant/components/azure_devops/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "project_error": "Tidak bisa mendapatkan info proyek." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "data": { + "personal_access_token": "Token Akses Pribadi (PAT)" + }, + "description": "Autentikasi gagal untuk {project_url} . Masukkan kredensial Anda saat ini.", + "title": "Autentikasi ulang" + }, + "user": { + "data": { + "organization": "Organisasi", + "personal_access_token": "Token Akses Pribadi (PAT)", + "project": "Proyek" + }, + "description": "Siapkan instans Azure DevOps untuk mengakses proyek Anda. Token Akses Pribadi hanya diperlukan untuk proyek pribadi.", + "title": "Tambahkan Proyek Azure DevOps" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/ko.json b/homeassistant/components/azure_devops/translations/ko.json index 555c548a14277e..4a8807e5f67605 100644 --- a/homeassistant/components/azure_devops/translations/ko.json +++ b/homeassistant/components/azure_devops/translations/ko.json @@ -6,7 +6,27 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "project_error": "\ud504\ub85c\uc81d\ud2b8 \uc815\ubcf4\ub97c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "data": { + "personal_access_token": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 (PAT)" + }, + "description": "{project_url} \uc5d0 \ub300\ud55c \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\uc7ac\uc778\uc99d" + }, + "user": { + "data": { + "organization": "\uc870\uc9c1", + "personal_access_token": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 (PAT)", + "project": "\ud504\ub85c\uc81d\ud2b8" + }, + "description": "\ud504\ub85c\uc81d\ud2b8\uc5d0 \uc561\uc138\uc2a4\ud560 Azure DevOps \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc740 \uac1c\uc778 \ud504\ub85c\uc81d\ud2b8\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4.", + "title": "Azure DevOps \ud504\ub85c\uc81d\ud2b8 \ucd94\uac00\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index 07dc59e1a56ac3..adf2a84e4e9382 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -10,11 +10,18 @@ "project_error": "Kon geen projectinformatie ophalen." }, "step": { + "reauth": { + "data": { + "personal_access_token": "Persoonlijk toegangstoken (PAT)" + } + }, "user": { "data": { "organization": "Organisatie", + "personal_access_token": "Persoonlijk toegangstoken (PAT)", "project": "Project" - } + }, + "title": "Azure DevOps-project toevoegen" } } } diff --git a/homeassistant/components/binary_sensor/translations/id.json b/homeassistant/components/binary_sensor/translations/id.json index 4ca757da6e5e0f..ac880aa28fa38b 100644 --- a/homeassistant/components/binary_sensor/translations/id.json +++ b/homeassistant/components/binary_sensor/translations/id.json @@ -1,13 +1,107 @@ { + "device_automation": { + "condition_type": { + "is_bat_low": "Baterai {entity_name} hampir habis", + "is_cold": "{entity_name} dingin", + "is_connected": "{entity_name} terhubung", + "is_gas": "{entity_name} mendeteksi gas", + "is_hot": "{entity_name} panas", + "is_light": "{entity_name} mendeteksi cahaya", + "is_locked": "{entity_name} terkunci", + "is_moist": "{entity_name} lembab", + "is_motion": "{entity_name} mendeteksi gerakan", + "is_moving": "{entity_name} bergerak", + "is_no_gas": "{entity_name} tidak mendeteksi gas", + "is_no_light": "{entity_name} tidak mendeteksi cahaya", + "is_no_motion": "{entity_name} tidak mendeteksi gerakan", + "is_no_problem": "{entity_name} tidak mendeteksi masalah", + "is_no_smoke": "{entity_name} tidak mendeteksi asap", + "is_no_sound": "{entity_name} tidak mendeteksi suara", + "is_no_vibration": "{entity_name} tidak mendeteksi getaran", + "is_not_bat_low": "Baterai {entity_name} normal", + "is_not_cold": "{entity_name} tidak dingin", + "is_not_connected": "{entity_name} terputus", + "is_not_hot": "{entity_name} tidak panas", + "is_not_locked": "{entity_name} tidak terkunci", + "is_not_moist": "{entity_name} kering", + "is_not_moving": "{entity_name} tidak bergerak", + "is_not_occupied": "{entity_name} tidak ditempati", + "is_not_open": "{entity_name} tertutup", + "is_not_plugged_in": "{entity_name} dicabut", + "is_not_powered": "{entity_name} tidak ditenagai", + "is_not_present": "{entity_name} tidak ada", + "is_not_unsafe": "{entity_name} aman", + "is_occupied": "{entity_name} ditempati", + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala", + "is_open": "{entity_name} terbuka", + "is_plugged_in": "{entity_name} dicolokkan", + "is_powered": "{entity_name} ditenagai", + "is_present": "{entity_name} ada", + "is_problem": "{entity_name} mendeteksi masalah", + "is_smoke": "{entity_name} mendeteksi asap", + "is_sound": "{entity_name} mendeteksi suara", + "is_unsafe": "{entity_name} tidak aman", + "is_vibration": "{entity_name} mendeteksi getaran" + }, + "trigger_type": { + "bat_low": "Baterai {entity_name} hampir habis", + "cold": "{entity_name} menjadi dingin", + "connected": "{entity_name} terhubung", + "gas": "{entity_name} mulai mendeteksi gas", + "hot": "{entity_name} menjadi panas", + "light": "{entity_name} mulai mendeteksi cahaya", + "locked": "{entity_name} terkunci", + "moist": "{entity_name} menjadi lembab", + "motion": "{entity_name} mulai mendeteksi gerakan", + "moving": "{entity_name} mulai bergerak", + "no_gas": "{entity_name} berhenti mendeteksi gas", + "no_light": "{entity_name} berhenti mendeteksi cahaya", + "no_motion": "{entity_name} berhenti mendeteksi gerakan", + "no_problem": "{entity_name} berhenti mendeteksi masalah", + "no_smoke": "{entity_name} berhenti mendeteksi asap", + "no_sound": "{entity_name} berhenti mendeteksi suara", + "no_vibration": "{entity_name} berhenti mendeteksi getaran", + "not_bat_low": "Baterai {entity_name} normal", + "not_cold": "{entity_name} menjadi tidak dingin", + "not_connected": "{entity_name} terputus", + "not_hot": "{entity_name} menjadi tidak panas", + "not_locked": "{entity_name} tidak terkunci", + "not_moist": "{entity_name} menjadi kering", + "not_moving": "{entity_name} berhenti bergerak", + "not_occupied": "{entity_name} menjadi tidak ditempati", + "not_opened": "{entity_name} tertutup", + "not_plugged_in": "{entity_name} dicabut", + "not_powered": "{entity_name} tidak ditenagai", + "not_present": "{entity_name} tidak ada", + "not_unsafe": "{entity_name} menjadi aman", + "occupied": "{entity_name} menjadi ditempati", + "opened": "{entity_name} terbuka", + "plugged_in": "{entity_name} dicolokkan", + "powered": "{entity_name} ditenagai", + "present": "{entity_name} ada", + "problem": "{entity_name} mulai mendeteksi masalah", + "smoke": "{entity_name} mulai mendeteksi asap", + "sound": "{entity_name} mulai mendeteksi suara", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan", + "unsafe": "{entity_name} menjadi tidak aman", + "vibration": "{entity_name} mulai mendeteksi getaran" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" }, "battery": { "off": "Normal", "on": "Rendah" }, + "battery_charging": { + "off": "Tidak mengisi daya", + "on": "Mengisi daya" + }, "cold": { "off": "Normal", "on": "Dingin" @@ -25,13 +119,17 @@ "on": "Terbuka" }, "gas": { - "off": "Kosong", + "off": "Tidak ada", "on": "Terdeteksi" }, "heat": { "off": "Normal", "on": "Panas" }, + "light": { + "off": "Tidak ada cahaya", + "on": "Cahaya terdeteksi" + }, "lock": { "off": "Terkunci", "on": "Terbuka" @@ -44,6 +142,10 @@ "off": "Tidak ada", "on": "Terdeteksi" }, + "moving": { + "off": "Tidak bergerak", + "on": "Bergerak" + }, "occupancy": { "off": "Tidak ada", "on": "Terdeteksi" @@ -52,13 +154,17 @@ "off": "Tertutup", "on": "Terbuka" }, + "plug": { + "off": "Dicabut", + "on": "Dicolokkan" + }, "presence": { "off": "Keluar", - "on": "Rumah" + "on": "Di Rumah" }, "problem": { "off": "Oke", - "on": "Masalah" + "on": "Bermasalah" }, "safety": { "off": "Aman", diff --git a/homeassistant/components/binary_sensor/translations/ko.json b/homeassistant/components/binary_sensor/translations/ko.json index 0b8ef0b73d5f4a..7a725fc67199b7 100644 --- a/homeassistant/components/binary_sensor/translations/ko.json +++ b/homeassistant/components/binary_sensor/translations/ko.json @@ -1,92 +1,92 @@ { "device_automation": { "condition_type": { - "is_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud558\uba74", - "is_cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc73c\uba74", - "is_connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub418\uc5b4 \uc788\uc73c\uba74", - "is_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uba74", - "is_hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc73c\uba74", - "is_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uba74", - "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", - "is_moist": "{entity_name} \uc774(\uac00) \uc2b5\ud558\uba74", - "is_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uba74", - "is_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uba74", - "is_no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_not_bat_low": "{entity_name} \uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774\uba74", - "is_not_cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uc73c\uba74", - "is_not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc838 \uc788\ub2e4\uba74", - "is_not_hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uc73c\uba74", - "is_not_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74", - "is_not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud558\uba74", - "is_not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc73c\uba74", - "is_not_occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uba74", - "is_not_open": "{entity_name} \uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", - "is_not_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \ubf51\ud600 \uc788\uc73c\uba74", - "is_not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc73c\uba74", - "is_not_present": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74", - "is_not_unsafe": "{entity_name} \uc774(\uac00) \uc548\uc804\ud558\uba74", - "is_occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uc774\uba74", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", - "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", - "is_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud600 \uc788\uc73c\uba74", - "is_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc73c\uba74", - "is_present": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc911\uc774\uba74", - "is_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uba74", - "is_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uba74", - "is_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uba74", - "is_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud558\uc9c0 \uc54a\uc73c\uba74", - "is_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uba74" + "is_bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud558\uba74", + "is_cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc73c\uba74", + "is_connected": "{entity_name}\uc774(\uac00) \uc5f0\uacb0\ub418\uc5b4 \uc788\uc73c\uba74", + "is_gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc73c\uba74", + "is_light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_locked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", + "is_moist": "{entity_name}\uc774(\uac00) \uc2b5\ud558\uba74", + "is_motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uace0 \uc788\uc73c\uba74", + "is_no_gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_no_vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774\uba74", + "is_not_cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uc73c\uba74", + "is_not_connected": "{entity_name}\uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc838 \uc788\uc73c\uba74", + "is_not_hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uc73c\uba74", + "is_not_locked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_moist": "{entity_name}\uc774(\uac00) \uac74\uc870\ud558\uba74", + "is_not_moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uba74", + "is_not_open": "{entity_name}\uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", + "is_not_plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \ubf51\ud600 \uc788\uc73c\uba74", + "is_not_powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc9c0 \uc54a\uc73c\uba74", + "is_not_present": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74", + "is_not_unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud558\uba74", + "is_occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uc774\uba74", + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "is_open": "{entity_name}\uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", + "is_plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud600 \uc788\uc73c\uba74", + "is_powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uace0 \uc788\uc73c\uba74", + "is_present": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc911\uc774\uba74", + "is_problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74", + "is_unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uc73c\uba74", + "is_vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uace0 \uc788\uc73c\uba74" }, "trigger_type": { - "bat_low": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud574\uc9c8 \ub54c", - "cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc544\uc84c\uc744 \ub54c", - "connected": "{entity_name} \uc774(\uac00) \uc5f0\uacb0\ub420 \ub54c", - "gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud560 \ub54c", - "hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc544\uc84c\uc744 \ub54c", - "light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud560 \ub54c", - "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae38 \ub54c", - "moist": "{entity_name} \uc774(\uac00) \uc2b5\ud574\uc9c8 \ub54c", - "motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud560 \ub54c", - "moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc77c \ub54c", - "no_gas": "{entity_name} \uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_light": "{entity_name} \uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_motion": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "no_vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub420 \ub54c", - "not_bat_low": "{entity_name} \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774 \ub420 \ub54c", - "not_cold": "{entity_name} \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uac8c \ub410\uc744 \ub54c", - "not_connected": "{entity_name} \uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc9c8 \ub54c", - "not_hot": "{entity_name} \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uac8c \ub410\uc744 \ub54c", - "not_locked": "{entity_name} \uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub420 \ub54c", - "not_moist": "{entity_name} \uc774(\uac00) \uac74\uc870\ud574\uc9c8 \ub54c", - "not_moving": "{entity_name} \uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc744 \ub54c", - "not_occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uac8c \ub420 \ub54c", - "not_opened": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", - "not_plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \ubf51\ud790 \ub54c", - "not_powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uc744 \ub54c", - "not_present": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc0c1\ud0dc\uac00 \ub420 \ub54c", - "not_unsafe": "{entity_name} \uc740(\ub294) \uc548\uc804\ud574\uc9c8 \ub54c", - "occupied": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub420 \ub54c", - "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9b4 \ub54c", - "plugged_in": "{entity_name} \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud790 \ub54c", - "powered": "{entity_name} \uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub420 \ub54c", - "present": "{entity_name} \uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub420 \ub54c", - "problem": "{entity_name} \uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud560 \ub54c", - "smoke": "{entity_name} \uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud560 \ub54c", - "sound": "{entity_name} \uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud560 \ub54c", - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c", - "unsafe": "{entity_name} \uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uc744 \ub54c", - "vibration": "{entity_name} \uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud560 \ub54c" + "bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubd80\uc871\ud574\uc84c\uc744 \ub54c", + "cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc544\uc84c\uc744 \ub54c", + "connected": "{entity_name}\uc774(\uac00) \uc5f0\uacb0\ub418\uc5c8\uc744 \ub54c", + "gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc544\uc84c\uc744 \ub54c", + "light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "locked": "{entity_name}\uc774(\uac00) \uc7a0\uacbc\uc744 \ub54c", + "moist": "{entity_name}\uc774(\uac00) \uc2b5\ud574\uc84c\uc744 \ub54c", + "motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "no_gas": "{entity_name}\uc774(\uac00) \uac00\uc2a4\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_light": "{entity_name}\uc774(\uac00) \ube5b\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_motion": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "no_vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_bat_low": "{entity_name}\uc758 \ubc30\ud130\ub9ac\uac00 \uc815\uc0c1\uc774 \ub418\uc5c8\uc744 \ub54c", + "not_cold": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub0ae\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_connected": "{entity_name}\uc758 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc84c\uc744 \ub54c", + "not_hot": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ub192\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_locked": "{entity_name}\uc758 \uc7a0\uae08\uc774 \ud574\uc81c\ub418\uc5c8\uc744 \ub54c", + "not_moist": "{entity_name}\uc774(\uac00) \uac74\uc870\ud574\uc84c\uc744 \ub54c", + "not_moving": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc774\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \uc544\ub2c8\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_opened": "{entity_name}\uc774(\uac00) \ub2eb\ud614\uc744 \ub54c", + "not_plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \ubf51\ud614\uc744 \ub54c", + "not_powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "not_present": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uc0c1\ud0dc\uac00 \ub418\uc5c8\uc744 \ub54c", + "not_unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud574\uc84c\uc744 \ub54c", + "occupied": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub418\uc5c8\uc744 \ub54c", + "opened": "{entity_name}\uc774(\uac00) \uc5f4\ub838\uc744 \ub54c", + "plugged_in": "{entity_name}\uc758 \ud50c\ub7ec\uadf8\uac00 \uaf3d\ud614\uc744 \ub54c", + "powered": "{entity_name}\uc5d0 \uc804\uc6d0\uc774 \uacf5\uae09\ub418\uc5c8\uc744 \ub54c", + "present": "{entity_name}\uc774(\uac00) \uc7ac\uc2e4 \uc0c1\ud0dc\uac00 \ub418\uc5c8\uc744 \ub54c", + "problem": "{entity_name}\uc774(\uac00) \ubb38\uc81c\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "smoke": "{entity_name}\uc774(\uac00) \uc5f0\uae30\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "sound": "{entity_name}\uc774(\uac00) \uc18c\ub9ac\ub97c \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c", + "unsafe": "{entity_name}\uc774(\uac00) \uc548\uc804\ud558\uc9c0 \uc54a\uac8c \ub418\uc5c8\uc744 \ub54c", + "vibration": "{entity_name}\uc774(\uac00) \uc9c4\ub3d9\uc744 \uac10\uc9c0\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c" } }, "state": { @@ -98,6 +98,10 @@ "off": "\ubcf4\ud1b5", "on": "\ub0ae\uc74c" }, + "battery_charging": { + "off": "\ucda9\uc804 \uc911\uc774 \uc544\ub2d8", + "on": "\ucda9\uc804 \uc911" + }, "cold": { "off": "\ubcf4\ud1b5", "on": "\uc800\uc628" @@ -122,6 +126,10 @@ "off": "\ubcf4\ud1b5", "on": "\uace0\uc628" }, + "light": { + "off": "\ube5b\uc774 \uc5c6\uc2b4", + "on": "\ube5b\uc744 \uac10\uc9c0\ud568" + }, "lock": { "off": "\uc7a0\uae40", "on": "\ud574\uc81c" @@ -134,6 +142,10 @@ "off": "\uc774\uc0c1\uc5c6\uc74c", "on": "\uac10\uc9c0\ub428" }, + "moving": { + "off": "\uc6c0\uc9c1\uc774\uc9c0 \uc54a\uc74c", + "on": "\uc6c0\uc9c1\uc784" + }, "occupancy": { "off": "\uc774\uc0c1\uc5c6\uc74c", "on": "\uac10\uc9c0\ub428" @@ -142,6 +154,10 @@ "off": "\ub2eb\ud798", "on": "\uc5f4\ub9bc" }, + "plug": { + "off": "\ud50c\ub7ec\uadf8\uac00 \ubf51\ud798", + "on": "\ud50c\ub7ec\uadf8\uac00 \uaf3d\ud798" + }, "presence": { "off": "\uc678\ucd9c", "on": "\uc7ac\uc2e4" diff --git a/homeassistant/components/binary_sensor/translations/zh-Hans.json b/homeassistant/components/binary_sensor/translations/zh-Hans.json index a44e16d78e2250..82cd0d3ccfec98 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hans.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hans.json @@ -25,13 +25,13 @@ "is_not_locked": "{entity_name} \u5df2\u89e3\u9501", "is_not_moist": "{entity_name} \u5e72\u71e5", "is_not_moving": "{entity_name} \u9759\u6b62", - "is_not_occupied": "{entity_name}\u6ca1\u6709\u4eba", + "is_not_occupied": "{entity_name} \u65e0\u4eba", "is_not_open": "{entity_name} \u5df2\u5173\u95ed", "is_not_plugged_in": "{entity_name} \u672a\u63d2\u5165", "is_not_powered": "{entity_name} \u672a\u901a\u7535", "is_not_present": "{entity_name} \u4e0d\u5728\u5bb6", "is_not_unsafe": "{entity_name} \u5b89\u5168", - "is_occupied": "{entity_name}\u6709\u4eba", + "is_occupied": "{entity_name} \u6709\u4eba", "is_off": "{entity_name} \u5df2\u5173\u95ed", "is_on": "{entity_name} \u5df2\u5f00\u542f", "is_open": "{entity_name} \u5df2\u6253\u5f00", @@ -51,15 +51,42 @@ "gas": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u71c3\u6c14\u6cc4\u6f0f", "hot": "{entity_name} \u53d8\u70ed", "light": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u5149\u7ebf", - "locked": "{entity_name}\u5df2\u4e0a\u9501", + "locked": "{entity_name} \u88ab\u9501\u5b9a", + "moist": "{entity_name} \u53d8\u6e7f", "motion": "{entity_name} \u68c0\u6d4b\u5230\u6709\u4eba", - "moving": "{entity_name}\u5f00\u59cb\u79fb\u52a8", - "no_motion": "{entity_name} \u672a\u68c0\u6d4b\u5230\u6709\u4eba", - "not_bat_low": "{entity_name}\u7535\u91cf\u6b63\u5e38", - "not_locked": "{entity_name}\u5df2\u89e3\u9501", - "not_opened": "{entity_name}\u5df2\u5173\u95ed", + "moving": "{entity_name} \u5f00\u59cb\u79fb\u52a8", + "no_gas": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u71c3\u6c14\u6cc4\u6f0f", + "no_light": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u5149\u7ebf", + "no_motion": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u6709\u4eba", + "no_problem": "{entity_name} \u95ee\u9898\u89e3\u9664", + "no_smoke": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u70df\u96fe", + "no_sound": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u58f0\u97f3", + "no_vibration": "{entity_name} \u4e0d\u518d\u68c0\u6d4b\u5230\u632f\u52a8", + "not_bat_low": "{entity_name} \u7535\u6c60\u7535\u91cf\u6b63\u5e38", + "not_cold": "{entity_name} \u4e0d\u51b7\u4e86", + "not_connected": "{entity_name} \u65ad\u5f00", + "not_hot": "{entity_name} \u4e0d\u70ed\u4e86", + "not_locked": "{entity_name} \u89e3\u9501", + "not_moist": "{entity_name} \u53d8\u5e72", + "not_moving": "{entity_name} \u505c\u6b62\u79fb\u52a8", + "not_occupied": "{entity_name} \u4e0d\u518d\u6709\u4eba", + "not_opened": "{entity_name} \u5df2\u5173\u95ed", + "not_plugged_in": "{entity_name} \u88ab\u62d4\u51fa", + "not_powered": "{entity_name} \u6389\u7535", + "not_present": "{entity_name} \u4e0d\u5728\u5bb6", + "not_unsafe": "{entity_name} \u5b89\u5168\u4e86", + "occupied": "{entity_name} \u6709\u4eba", + "opened": "{entity_name} \u88ab\u6253\u5f00", + "plugged_in": "{entity_name} \u88ab\u63d2\u5165", + "powered": "{entity_name} \u4e0a\u7535", + "present": "{entity_name} \u5728\u5bb6", + "problem": "{entity_name} \u53d1\u73b0\u95ee\u9898", + "smoke": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u70df\u96fe", + "sound": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u58f0\u97f3", "turned_off": "{entity_name} \u88ab\u5173\u95ed", - "turned_on": "{entity_name} \u88ab\u6253\u5f00" + "turned_on": "{entity_name} \u88ab\u6253\u5f00", + "unsafe": "{entity_name} \u4e0d\u518d\u5b89\u5168", + "vibration": "{entity_name} \u5f00\u59cb\u68c0\u6d4b\u5230\u632f\u52a8" } }, "state": { @@ -120,8 +147,8 @@ "on": "\u6b63\u5728\u79fb\u52a8" }, "occupancy": { - "off": "\u672a\u89e6\u53d1", - "on": "\u5df2\u89e6\u53d1" + "off": "\u65e0\u4eba", + "on": "\u6709\u4eba" }, "opening": { "off": "\u5173\u95ed", diff --git a/homeassistant/components/blebox/translations/hu.json b/homeassistant/components/blebox/translations/hu.json index 9b0bf1c0ddf0bd..9649d70d976ddf 100644 --- a/homeassistant/components/blebox/translations/hu.json +++ b/homeassistant/components/blebox/translations/hu.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "unsupported_version": "A BleBox eszk\u00f6z elavult firmware-vel rendelkezik. El\u0151sz\u00f6r friss\u00edtse." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", + "unsupported_version": "A BleBox eszk\u00f6z elavult firmware-rel rendelkezik. El\u0151sz\u00f6r friss\u00edtsd." }, "flow_title": "BleBox eszk\u00f6z: {name} ({host})", "step": { diff --git a/homeassistant/components/blebox/translations/id.json b/homeassistant/components/blebox/translations/id.json new file mode 100644 index 00000000000000..2ef604d1bff6fd --- /dev/null +++ b/homeassistant/components/blebox/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "address_already_configured": "Perangkat BleBox sudah dikonfigurasi di {address}.", + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan", + "unsupported_version": "Firmware Perangkat BleBox sudah usang. Tingkatkan terlebih dulu." + }, + "flow_title": "Perangkat BleBox: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Alamat IP", + "port": "Port" + }, + "description": "Siapkan BleBox Anda untuk diintegrasikan dengan Home Assistant.", + "title": "Siapkan perangkat BleBox Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/ko.json b/homeassistant/components/blebox/translations/ko.json index 81c7bf3af481eb..1032e873ae9524 100644 --- a/homeassistant/components/blebox/translations/ko.json +++ b/homeassistant/components/blebox/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "address_already_configured": "BleBox \uae30\uae30\uac00 {address} \ub85c \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "address_already_configured": "BleBox \uae30\uae30\uac00 {address}(\uc73c)\ub85c \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { @@ -16,7 +16,7 @@ "host": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" }, - "description": "Home Assistant \uc5d0 BleBox \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", + "description": "Home Assistant\uc5d0 BleBox \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", "title": "BleBox \uae30\uae30 \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/blink/translations/en.json b/homeassistant/components/blink/translations/en.json index 9a0a2636d3e900..c8c154418dffd0 100644 --- a/homeassistant/components/blink/translations/en.json +++ b/homeassistant/components/blink/translations/en.json @@ -14,7 +14,7 @@ "data": { "2fa": "Two-factor code" }, - "description": "Enter the pin sent to your email", + "description": "Enter the PIN sent to your email", "title": "Two-factor authentication" }, "user": { diff --git a/homeassistant/components/blink/translations/et.json b/homeassistant/components/blink/translations/et.json index 24de0ccbefd2a2..a5cae0eaae2164 100644 --- a/homeassistant/components/blink/translations/et.json +++ b/homeassistant/components/blink/translations/et.json @@ -14,7 +14,7 @@ "data": { "2fa": "2FA kood" }, - "description": "Sisesta E-posti aadressile saadetud PIN-kood", + "description": "Sisesta e-posti aadressile saadetud PIN kood", "title": "Kaheastmeline tuvastamine (2FA)" }, "user": { diff --git a/homeassistant/components/blink/translations/fr.json b/homeassistant/components/blink/translations/fr.json index 83aaad902a151f..23bb7fb91dd8f6 100644 --- a/homeassistant/components/blink/translations/fr.json +++ b/homeassistant/components/blink/translations/fr.json @@ -14,7 +14,7 @@ "data": { "2fa": "Code \u00e0 deux facteurs" }, - "description": "Entrez le code PIN envoy\u00e9 \u00e0 votre e-mail", + "description": "Entrez le NIP envoy\u00e9 \u00e0 votre e-mail", "title": "Authentification \u00e0 deux facteurs" }, "user": { diff --git a/homeassistant/components/blink/translations/hu.json b/homeassistant/components/blink/translations/hu.json index 1150cda9ea93e5..e56b142a5b03d2 100644 --- a/homeassistant/components/blink/translations/hu.json +++ b/homeassistant/components/blink/translations/hu.json @@ -4,10 +4,19 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "2fa": { + "data": { + "2fa": "K\u00e9tfaktoros k\u00f3d" + }, + "description": "Add meg az e-mail c\u00edmedre k\u00fcld\u00f6tt pint", + "title": "K\u00e9tfaktoros hiteles\u00edt\u00e9s" + }, "user": { "data": { "password": "Jelsz\u00f3", diff --git a/homeassistant/components/blink/translations/id.json b/homeassistant/components/blink/translations/id.json new file mode 100644 index 00000000000000..bdbc406bda7001 --- /dev/null +++ b/homeassistant/components/blink/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_access_token": "Token akses tidak valid", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kode autentikasi dua faktor" + }, + "description": "Masukkan PIN yang dikirimkan ke email Anda", + "title": "Autentikasi dua faktor" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Masuk dengan akun Blink" + } + } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "Interval Pindai (detik)" + }, + "description": "Konfigurasikan integrasi Blink", + "title": "Opsi Blink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/ko.json b/homeassistant/components/blink/translations/ko.json index 35ef0cefdef4e9..6d42cdbb2e921e 100644 --- a/homeassistant/components/blink/translations/ko.json +++ b/homeassistant/components/blink/translations/ko.json @@ -14,7 +14,7 @@ "data": { "2fa": "2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc" }, - "description": "\uc774\uba54\uc77c\ub85c \ubcf4\ub0b4\ub4dc\ub9b0 PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "description": "\uc774\uba54\uc77c\ub85c \ubcf4\ub0b4\ub4dc\ub9b0 PIN\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "2\ub2e8\uacc4 \uc778\uc99d" }, "user": { diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json index f1f1ce7888bd12..3160ffe8ddd435 100644 --- a/homeassistant/components/blink/translations/nl.json +++ b/homeassistant/components/blink/translations/nl.json @@ -29,6 +29,10 @@ "options": { "step": { "simple_options": { + "data": { + "scan_interval": "Scaninterval (seconden)" + }, + "description": "Configureer Blink-integratie", "title": "Blink opties" } } diff --git a/homeassistant/components/blink/translations/no.json b/homeassistant/components/blink/translations/no.json index 0b99005c382ed1..90f8fcaa06bfb5 100644 --- a/homeassistant/components/blink/translations/no.json +++ b/homeassistant/components/blink/translations/no.json @@ -14,7 +14,7 @@ "data": { "2fa": "Totrinnsbekreftelse kode" }, - "description": "Skriv inn pin-koden som ble sendt til din e-posten", + "description": "Skriv inn PIN-koden som er sendt til e-posten din", "title": "Totrinnsbekreftelse" }, "user": { diff --git a/homeassistant/components/blink/translations/pl.json b/homeassistant/components/blink/translations/pl.json index 13fe2f1fc934b0..72b6c32e5be748 100644 --- a/homeassistant/components/blink/translations/pl.json +++ b/homeassistant/components/blink/translations/pl.json @@ -14,7 +14,7 @@ "data": { "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" }, - "description": "Wpisz kod PIN wys\u0142any na Tw\u00f3j adres e-mail. Je\u015bli.", + "description": "Wprowad\u017a kod PIN wys\u0142any na Tw\u00f3j adres e-mail.", "title": "Uwierzytelnianie dwusk\u0142adnikowe" }, "user": { diff --git a/homeassistant/components/blink/translations/zh-Hant.json b/homeassistant/components/blink/translations/zh-Hant.json index d2c42bf5531682..6874efb6e316c9 100644 --- a/homeassistant/components/blink/translations/zh-Hant.json +++ b/homeassistant/components/blink/translations/zh-Hant.json @@ -12,10 +12,10 @@ "step": { "2fa": { "data": { - "2fa": "\u96d9\u91cd\u9a57\u8b49\u78bc" + "2fa": "\u96d9\u91cd\u8a8d\u8b49\u78bc" }, "description": "\u8f38\u5165\u90f5\u4ef6\u6240\u6536\u5230 PIN \u78bc", - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "user": { "data": { diff --git a/homeassistant/components/bmw_connected_drive/translations/bg.json b/homeassistant/components/bmw_connected_drive/translations/bg.json new file mode 100644 index 00000000000000..67a484573aa0c1 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/bmw_connected_drive/translations/hu.json b/homeassistant/components/bmw_connected_drive/translations/hu.json new file mode 100644 index 00000000000000..8724f525626c78 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/hu.json @@ -0,0 +1,20 @@ +{ + "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", + "region": "ConnectedDrive R\u00e9gi\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/id.json b/homeassistant/components/bmw_connected_drive/translations/id.json new file mode 100644 index 00000000000000..e49e9202dbe2e5 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "region": "Wilayah ConnectedDrive", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "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)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bmw_connected_drive/translations/ko.json b/homeassistant/components/bmw_connected_drive/translations/ko.json index 9cc079cf1cdfa2..4c9872573bee1a 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ko.json +++ b/homeassistant/components/bmw_connected_drive/translations/ko.json @@ -11,9 +11,20 @@ "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", + "region": "ConnectedDrive \uc9c0\uc5ed", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "\uc77d\uae30 \uc804\uc6a9(\uc13c\uc11c \ubc0f \uc54c\ub9bc\ub9cc \uac00\ub2a5, \uc11c\ube44\uc2a4 \uc2e4\ud589 \ubc0f \uc7a0\uae08 \uc5c6\uc74c)", + "use_location": "\ucc28\ub7c9 \uc704\uce58 \ud3f4\ub9c1\uc5d0 Home Assistant \uc704\uce58\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4(2014\ub144 7\uc6d4 \uc774\uc804\uc5d0 \uc0dd\uc0b0\ub41c i3/i8\uc774 \uc544\ub2cc \ucc28\ub7c9\uc758 \uacbd\uc6b0 \ud544\uc694)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/hu.json b/homeassistant/components/bond/translations/hu.json index 3b2d79a34a77e2..868ef455f5ddc4 100644 --- a/homeassistant/components/bond/translations/hu.json +++ b/homeassistant/components/bond/translations/hu.json @@ -2,6 +2,27 @@ "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", + "old_firmware": "Nem t\u00e1mogatott r\u00e9gi firmware a Bond eszk\u00f6z\u00f6n - k\u00e9rj\u00fck friss\u00edtsd, miel\u0151tt folytatn\u00e1d", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Bond: {name} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" + }, + "description": "Szeretn\u00e9d be\u00e1ll\u00edtani a(z) {name}-t?" + }, + "user": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/bond/translations/id.json b/homeassistant/components/bond/translations/id.json new file mode 100644 index 00000000000000..56c633cf31c7de --- /dev/null +++ b/homeassistant/components/bond/translations/id.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "old_firmware": "Firmware lama yang tidak didukung pada perangkat Bond - tingkatkan versi sebelum melanjutkan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Bond: {name} ({host})", + "step": { + "confirm": { + "data": { + "access_token": "Token Akses" + }, + "description": "Ingin menyiapkan {name}?" + }, + "user": { + "data": { + "access_token": "Token Akses", + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/ko.json b/homeassistant/components/bond/translations/ko.json index b44db53f7c8d5c..33a56559f79c8c 100644 --- a/homeassistant/components/bond/translations/ko.json +++ b/homeassistant/components/bond/translations/ko.json @@ -6,15 +6,16 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "old_firmware": "Bond \uae30\uae30\uc5d0\uc11c \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uc624\ub798\ub41c \ud38c\uc6e8\uc5b4\uc785\ub2c8\ub2e4. \uacc4\uc18d\ud558\uae30 \uc804\uc5d0 \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\uc8fc\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, - "flow_title": "\ubcf8\ub4dc : {bond_id} ( {host} )", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, - "description": "{bond_id} \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index a76c7a69d7f68a..a9086df3559681 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie", + "old_firmware": "Niet-ondersteunde oude firmware op het Bond-apparaat - voer een upgrade uit voordat u doorgaat", "unknown": "Onverwachte fout" }, "flow_title": "Bond: {bond_id} ({host})", @@ -13,7 +14,8 @@ "confirm": { "data": { "access_token": "Toegangstoken" - } + }, + "description": "Wilt u {name} instellen?" }, "user": { "data": { diff --git a/homeassistant/components/braviatv/translations/hu.json b/homeassistant/components/braviatv/translations/hu.json index a87786df1e8fe1..fbb23fdee0432f 100644 --- a/homeassistant/components/braviatv/translations/hu.json +++ b/homeassistant/components/braviatv/translations/hu.json @@ -1,15 +1,36 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_ip_control": "Az IP-vez\u00e9rl\u00e9s le van tiltva a TV-n, vagy a TV nem t\u00e1mogatja." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unsupported_model": "A TV modell nem t\u00e1mogatott." + }, "step": { "authorize": { "data": { - "pin": "PIN k\u00f3d" - } + "pin": "PIN-k\u00f3d" + }, + "title": "Sony Bravia TV enged\u00e9lyez\u00e9se" }, "user": { "data": { "host": "Hoszt" - } + }, + "title": "Sony Bravia TV" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Figyelmen k\u00edv\u00fcl hagyott forr\u00e1sok list\u00e1ja" + }, + "title": "A Sony Bravia TV be\u00e1ll\u00edt\u00e1sai" } } } diff --git a/homeassistant/components/braviatv/translations/id.json b/homeassistant/components/braviatv/translations/id.json new file mode 100644 index 00000000000000..def84dacdbb217 --- /dev/null +++ b/homeassistant/components/braviatv/translations/id.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_ip_control": "Kontrol IP dinonaktifkan di TV Anda atau TV tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "unsupported_model": "Model TV Anda tidak didukung." + }, + "step": { + "authorize": { + "data": { + "pin": "Kode PIN" + }, + "description": "Masukkan kode PIN yang ditampilkan di TV Sony Bravia. \n\nJika kode PIN tidak ditampilkan, Anda harus membatalkan pendaftaran Home Assistant di TV, buka: Pengaturan -> Jaringan -> Pengaturan perangkat jarak jauh -> Batalkan pendaftaran perangkat jarak jauh.", + "title": "Otorisasi TV Sony Bravia" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Siapkan integrasi TV Sony Bravia. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/braviatv \n\nPastikan TV Anda dinyalakan.", + "title": "TV Sony Bravia" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Daftar sumber yang diabaikan" + }, + "title": "Pilihan untuk TV Sony Bravia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index 0bfb6b3f1b2a50..c20c6b2eb7b771 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -6,7 +6,7 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unsupported_model": "\uc774 TV \ubaa8\ub378\uc740 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "step": { @@ -14,7 +14,7 @@ "data": { "pin": "PIN \ucf54\ub4dc" }, - "description": "Sony Bravia TV \uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nPIN \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc9c0 \uc54a\uc73c\uba74 TV \uc5d0\uc11c Home Assistant \ub97c \ub4f1\ub85d \ud574\uc81c\ud558\uc5ec\uc57c \ud569\ub2c8\ub2e4. Settings -> Network -> Remote device settings -> Unregister remote device \ub85c \uc774\ub3d9\ud558\uc5ec \ub4f1\ub85d\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", + "description": "Sony Bravia TV\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nPIN \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc9c0 \uc54a\uc73c\uba74 TV\uc5d0\uc11c Home Assistant\ub97c \ub4f1\ub85d \ud574\uc81c\ud558\uc5ec\uc57c \ud569\ub2c8\ub2e4. Settings -> Network -> Remote device settings -> Unregister remote device\ub85c \uc774\ub3d9\ud558\uc5ec \ub4f1\ub85d\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", "title": "Sony Bravia TV \uc2b9\uc778\ud558\uae30" }, "user": { diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json index b35d7de45cfed7..5354f5761ecca2 100644 --- a/homeassistant/components/braviatv/translations/nl.json +++ b/homeassistant/components/braviatv/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Deze tv is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "no_ip_control": "IP-besturing is uitgeschakeld op uw tv of de tv wordt niet ondersteund." }, "error": { - "cannot_connect": "Geen verbinding, ongeldige host of PIN-code.", - "invalid_host": "Ongeldige hostnaam of IP-adres.", + "cannot_connect": "Kan geen verbinding maken", + "invalid_host": "Ongeldige hostnaam of IP-adres", "unsupported_model": "Uw tv-model wordt niet ondersteund." }, "step": { @@ -19,7 +19,7 @@ }, "user": { "data": { - "host": "Hostnaam of IP-adres van tv" + "host": "Host" }, "description": "Stel Sony Bravia TV-integratie in. Als je problemen hebt met de configuratie ga dan naar: https://www.home-assistant.io/integrations/braviatv \n\nZorg ervoor dat uw tv is ingeschakeld.", "title": "Sony Bravia TV" diff --git a/homeassistant/components/broadlink/translations/hu.json b/homeassistant/components/broadlink/translations/hu.json index 3b2d79a34a77e2..90213e99aecfcc 100644 --- a/homeassistant/components/broadlink/translations/hu.json +++ b/homeassistant/components/broadlink/translations/hu.json @@ -1,7 +1,46 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "{name} ({model} a {host} c\u00edmen)", + "step": { + "auth": { + "title": "Hiteles\u00edt\u00e9s az eszk\u00f6z\u00f6n" + }, + "finish": { + "data": { + "name": "N\u00e9v" + }, + "title": "V\u00e1lassz egy nevet az eszk\u00f6znek" + }, + "reset": { + "description": "{name} ({model} a {host} c\u00edmen) z\u00e1rolva van. A hiteles\u00edt\u00e9shez \u00e9s a konfigur\u00e1ci\u00f3 befejez\u00e9s\u00e9hez fel kell oldani az eszk\u00f6z z\u00e1rol\u00e1s\u00e1t. Utas\u00edt\u00e1sok:\n 1. Nyisd meg a Broadlink alkalmaz\u00e1st.\n 2. Kattints az eszk\u00f6zre.\n 3. Kattints a jobb fels\u0151 sarokban tal\u00e1lhat\u00f3 `...` gombra.\n 4. G\u00f6rgess az oldal alj\u00e1ra.\n 5. Kapcsold ki a z\u00e1rol\u00e1s\u00e1t.", + "title": "Az eszk\u00f6z felold\u00e1sa" + }, + "unlock": { + "data": { + "unlock": "Igen, csin\u00e1ld." + }, + "description": "{name} ({model} a {host} c\u00edmen) z\u00e1rolva van. Ez hiteles\u00edt\u00e9si probl\u00e9m\u00e1khoz vezethet a Home Assistantban. Szeretn\u00e9d feloldani?", + "title": "Az eszk\u00f6z felold\u00e1sa (opcion\u00e1lis)" + }, + "user": { + "data": { + "host": "Hoszt", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s" + }, + "title": "Csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + } } } } \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/id.json b/homeassistant/components/broadlink/translations/id.json new file mode 100644 index 00000000000000..89d9b17a800710 --- /dev/null +++ b/homeassistant/components/broadlink/translations/id.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "not_supported": "Perangkat tidak didukung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "{name} ({model} di {host})", + "step": { + "auth": { + "title": "Autentikasi ke perangkat" + }, + "finish": { + "data": { + "name": "Nama" + }, + "title": "Pilih nama untuk perangkat" + }, + "reset": { + "description": "{name} ({model} di {host}) dikunci. Anda perlu membuka kunci perangkat untuk mengautentikasi dan menyelesaikan konfigurasi. Ikuti petunjuk berikut:\n1. Buka aplikasi Broadlink.\n2. Klik pada perangkat.\n3. Klik `\u2026` di pojok kanan atas.\n4. Gulir ke bagian bawah halaman.\n5. Nonaktifkan kunci.", + "title": "Buka kunci perangkat" + }, + "unlock": { + "data": { + "unlock": "Ya, lakukan." + }, + "description": "{name} ({model} di {host}) dikunci. Hal ini dapat menyebabkan masalah autentikasi di Home Assistant. Apakah Anda ingin membukanya?", + "title": "Buka kunci perangkat (opsional)" + }, + "user": { + "data": { + "host": "Host", + "timeout": "Tenggang waktu" + }, + "title": "Hubungkan ke perangkat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/ko.json b/homeassistant/components/broadlink/translations/ko.json index 13cd17a8475de2..ac3ada0b8314fb 100644 --- a/homeassistant/components/broadlink/translations/ko.json +++ b/homeassistant/components/broadlink/translations/ko.json @@ -4,42 +4,43 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "not_supported": "\uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uc7a5\uce58", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_supported": "\uae30\uae30\uac00 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, - "flow_title": "{name} ({host} \uc758 {model})", + "flow_title": "{name} ({host}\uc758 {model})", "step": { "auth": { - "title": "\uc7a5\uce58\uc5d0 \uc778\uc99d" + "title": "\uae30\uae30\uc5d0 \uc778\uc99d\ud558\uae30" }, "finish": { "data": { "name": "\uc774\ub984" }, - "title": "\uc7a5\uce58 \uc774\ub984\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624" + "title": "\uae30\uae30\uc5d0 \ub300\ud55c \uc774\ub984 \uc120\ud0dd\ud558\uae30" }, "reset": { - "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c" + "description": "{name} ({host}\uc758 {model})\uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc744 \uc778\uc99d\ud558\uace0 \uc644\ub8cc\ud558\ub824\uba74 \uae30\uae30\uc758 \uc7a0\uae08\uc744 \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4. \ub2e4\uc74c\uc744 \ub530\ub77c\uc8fc\uc138\uc694:\n1. Broadlink \uc571\uc744 \uc5f4\uc5b4\uc8fc\uc138\uc694.\n2. \uae30\uae30\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n3. \uc624\ub978\ucabd \uc0c1\ub2e8\uc758 `...`\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n4. \ud398\uc774\uc9c0 \ub9e8 \uc544\ub798\ub85c \uc2a4\ud06c\ub864\ud574\uc8fc\uc138\uc694.\n5. \uc7a0\uae08\uc744 \ud574\uc81c\ud574\uc8fc\uc138\uc694.", + "title": "\uae30\uae30 \uc7a0\uae08 \ud574\uc81c\ud558\uae30" }, "unlock": { "data": { - "unlock": "\uc608" + "unlock": "\uc608, \uc7a0\uae08 \ud574\uc81c\ud558\uaca0\uc2b5\ub2c8\ub2e4." }, - "description": "{name} ({host} \uc758 {model}) \uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant \uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c (\uc635\uc158)" + "description": "{name} ({host}\uc758 {model})\uc774(\uac00) \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant\uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uae30\uae30 \uc7a0\uae08 \ud574\uc81c\ud558\uae30 (\uc120\ud0dd \uc0ac\ud56d)" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "timeout": "\uc81c\ud55c \uc2dc\uac04" }, - "title": "\uc7a5\uce58\uc5d0 \uc5f0\uacb0" + "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index d2db5476555607..138caf9a5b7707 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -31,7 +31,8 @@ }, "user": { "data": { - "host": "Host" + "host": "Host", + "timeout": "Time-out" }, "title": "Verbinding maken met het apparaat" } diff --git a/homeassistant/components/brother/translations/hu.json b/homeassistant/components/brother/translations/hu.json index dd5711cc516928..ae950f58f7271d 100644 --- a/homeassistant/components/brother/translations/hu.json +++ b/homeassistant/components/brother/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez a nyomtat\u00f3 m\u00e1r konfigur\u00e1lva van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "unsupported_model": "Ez a nyomtat\u00f3modell nem t\u00e1mogatott." }, "error": { diff --git a/homeassistant/components/brother/translations/id.json b/homeassistant/components/brother/translations/id.json new file mode 100644 index 00000000000000..5e0b562017ceb8 --- /dev/null +++ b/homeassistant/components/brother/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "unsupported_model": "Model printer ini tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "snmp_error": "Server SNMP dimatikan atau printer tidak didukung.", + "wrong_host": "Nama host atau alamat IP tidak valid." + }, + "flow_title": "Printer Brother: {model} {serial_number}", + "step": { + "user": { + "data": { + "host": "Host", + "type": "Jenis printer" + }, + "description": "Siapkan integrasi printer Brother. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/brother" + }, + "zeroconf_confirm": { + "data": { + "type": "Jenis printer" + }, + "description": "Ingin menambahkan Printer Brother {model} dengan nomor seri `{serial_number}` ke Home Assistant?", + "title": "Perangkat Printer Brother yang Ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/ko.json b/homeassistant/components/brother/translations/ko.json index 47722afdae5df6..2d3b587e475108 100644 --- a/homeassistant/components/brother/translations/ko.json +++ b/homeassistant/components/brother/translations/ko.json @@ -22,7 +22,7 @@ "data": { "type": "\ud504\ub9b0\ud130\uc758 \uc885\ub958" }, - "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \ub85c \ube0c\ub77c\ub354 \ud504\ub9b0\ud130 {model} \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}`\uc758 {model} \ube0c\ub77c\ub354 \ud504\ub9b0\ud130\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c \ube0c\ub77c\ub354 \ud504\ub9b0\ud130" } } diff --git a/homeassistant/components/bsblan/translations/hu.json b/homeassistant/components/bsblan/translations/hu.json index 1d28556ba1ad5a..50d250cc384dd5 100644 --- a/homeassistant/components/bsblan/translations/hu.json +++ b/homeassistant/components/bsblan/translations/hu.json @@ -1,13 +1,19 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "BSB-Lan: {name}", "step": { "user": { "data": { "host": "Hoszt", - "port": "Port" + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } diff --git a/homeassistant/components/bsblan/translations/id.json b/homeassistant/components/bsblan/translations/id.json new file mode 100644 index 00000000000000..6e8ac0bd4cbbf9 --- /dev/null +++ b/homeassistant/components/bsblan/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "BSB-Lan: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "passkey": "String kunci sandi", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Siapkan perangkat BSB-Lan Anda untuk diintegrasikan dengan Home Assistant.", + "title": "Hubungkan ke perangkat BSB-Lan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/ko.json b/homeassistant/components/bsblan/translations/ko.json index 85703c7eeb72c1..65843a258339af 100644 --- a/homeassistant/components/bsblan/translations/ko.json +++ b/homeassistant/components/bsblan/translations/ko.json @@ -16,7 +16,7 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Home Assistant \uc5d0 BSB-Lan \uae30\uae30 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", + "description": "Home Assistant\uc5d0 BSB-Lan \uae30\uae30 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", "title": "BSB-Lan \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/calendar/translations/id.json b/homeassistant/components/calendar/translations/id.json index 383a6ba77a13bf..e48c6e69b98d59 100644 --- a/homeassistant/components/calendar/translations/id.json +++ b/homeassistant/components/calendar/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Kalender" diff --git a/homeassistant/components/calendar/translations/ko.json b/homeassistant/components/calendar/translations/ko.json index af8622be7d772b..fd1672fef56ec7 100644 --- a/homeassistant/components/calendar/translations/ko.json +++ b/homeassistant/components/calendar/translations/ko.json @@ -5,5 +5,5 @@ "on": "\ucf1c\uc9d0" } }, - "title": "\uc77c\uc815" + "title": "\uce98\ub9b0\ub354" } \ No newline at end of file diff --git a/homeassistant/components/canary/translations/hu.json b/homeassistant/components/canary/translations/hu.json new file mode 100644 index 00000000000000..c2c70fdbf22aee --- /dev/null +++ b/homeassistant/components/canary/translations/hu.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Csatlakoz\u00e1s a Canary-hoz" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s (opcion\u00e1lis)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/id.json b/homeassistant/components/canary/translations/id.json new file mode 100644 index 00000000000000..5f092847b4d499 --- /dev/null +++ b/homeassistant/components/canary/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argumen yang diteruskan ke ffmpeg untuk kamera", + "timeout": "Tenggang Waktu Permintaan (detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/ko.json b/homeassistant/components/canary/translations/ko.json index d02344a90272c2..c96049f16ffb6b 100644 --- a/homeassistant/components/canary/translations/ko.json +++ b/homeassistant/components/canary/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -14,7 +14,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "Canary\uc5d0 \uc5f0\uacb0" + "title": "Canary\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "ffmpeg_arguments": "\uce74\uba54\ub77c ffmpeg\uc5d0 \uc804\ub2ec \ub41c \uc778\uc218", + "ffmpeg_arguments": "\uce74\uba54\ub77c\uc5d0 \ub300\ud55c ffmpeg \uc804\ub2ec \uc778\uc218", "timeout": "\uc694\uccad \uc81c\ud55c \uc2dc\uac04 (\ucd08)" } } diff --git a/homeassistant/components/cast/translations/ca.json b/homeassistant/components/cast/translations/ca.json index dc21c371e60fa0..9cb55f4d7314ab 100644 --- a/homeassistant/components/cast/translations/ca.json +++ b/homeassistant/components/cast/translations/ca.json @@ -4,10 +4,33 @@ "no_devices_found": "No s'han trobat dispositius a la xarxa", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, + "error": { + "invalid_known_hosts": "Els amfitrions coneguts han de ser una llista d'amfitrions separats per comes." + }, "step": { + "config": { + "data": { + "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar." + }, + "description": "Introdueix la configuraci\u00f3 de Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Vols comen\u00e7ar la configuraci\u00f3?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Els amfitrions coneguts han de ser una llista d'amfitrions separats per comes." + }, + "step": { + "options": { + "data": { + "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar." + }, + "description": "Introdueix la configuraci\u00f3 de Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/de.json b/homeassistant/components/cast/translations/de.json index 7ff1efb8ee0d81..a59b3421c109a5 100644 --- a/homeassistant/components/cast/translations/de.json +++ b/homeassistant/components/cast/translations/de.json @@ -4,10 +4,33 @@ "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden.", "single_instance_allowed": "Bereits konfiguriert. Es ist nur eine Konfiguration m\u00f6glich." }, + "error": { + "invalid_known_hosts": "Bekannte Hosts m\u00fcssen eine durch Kommata getrennte Liste von Hosts sein." + }, "step": { + "config": { + "data": { + "known_hosts": "Optionale Liste bekannter Hosts, wenn die mDNS-Erkennung nicht funktioniert." + }, + "description": "Bitte die Google Cast-Konfiguration eingeben.", + "title": "Google Cast" + }, "confirm": { "description": "M\u00f6chtest du Google Cast einrichten?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Bekannte Hosts m\u00fcssen eine durch Kommata getrennte Liste von Hosts sein." + }, + "step": { + "options": { + "data": { + "known_hosts": "Optionale Liste bekannter Hosts, wenn die mDNS-Erkennung nicht funktioniert." + }, + "description": "Bitte die Google Cast-Konfiguration eingeben." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index 9ef2f10cda2160..1bfdeb4df8d109 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "no_devices_found": "No devices found on the network", "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { diff --git a/homeassistant/components/cast/translations/es.json b/homeassistant/components/cast/translations/es.json index 520df7ee4cdb26..07b090634e0a68 100644 --- a/homeassistant/components/cast/translations/es.json +++ b/homeassistant/components/cast/translations/es.json @@ -4,10 +4,33 @@ "no_devices_found": "No se encontraron dispositivos en la red", "single_instance_allowed": "Ya est\u00e1 configurado. Solo es posible una \u00fanica configuraci\u00f3n." }, + "error": { + "invalid_known_hosts": "Los hosts conocidos deben ser una lista de hosts separados por comas." + }, "step": { + "config": { + "data": { + "known_hosts": "Lista opcional de hosts conocidos si el descubrimiento mDNS no funciona." + }, + "description": "Introduce la configuraci\u00f3n de Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "\u00bfQuieres iniciar la configuraci\u00f3n?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Los hosts conocidos deben ser una lista de hosts separados por comas." + }, + "step": { + "options": { + "data": { + "known_hosts": "Lista opcional de hosts conocidos si el descubrimiento mDNS no funciona." + }, + "description": "Introduce la configuraci\u00f3n de Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/et.json b/homeassistant/components/cast/translations/et.json index 05287b5a52b9a2..9e126d50af0ab0 100644 --- a/homeassistant/components/cast/translations/et.json +++ b/homeassistant/components/cast/translations/et.json @@ -4,10 +4,33 @@ "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi Google Casti seadet.", "single_instance_allowed": "Vajalik on ainult \u00fcks Google Casti konfiguratsioon." }, + "error": { + "invalid_known_hosts": "Teadaolevad hostid peab olema komaeraldusega hostide loend." + }, "step": { + "config": { + "data": { + "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta." + }, + "description": "Sisesta Google Casti andmed.", + "title": "Google Cast" + }, "confirm": { "description": "Kas soovid seadistada Google Casti?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Teadaolevad hostid peab olema komaeraldusega hostide loend." + }, + "step": { + "options": { + "data": { + "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta." + }, + "description": "Sisesta Google Casti andmed." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/fr.json b/homeassistant/components/cast/translations/fr.json index afa4b094fad414..0acfd327e3e2e7 100644 --- a/homeassistant/components/cast/translations/fr.json +++ b/homeassistant/components/cast/translations/fr.json @@ -4,10 +4,33 @@ "no_devices_found": "Aucun appareil Google Cast trouv\u00e9 sur le r\u00e9seau.", "single_instance_allowed": "Une seule configuration de Google Cast est n\u00e9cessaire." }, + "error": { + "invalid_known_hosts": "Les h\u00f4tes connus doivent \u00eatre une liste d'h\u00f4tes s\u00e9par\u00e9s par des virgules." + }, "step": { + "config": { + "data": { + "known_hosts": "Liste facultative des h\u00f4tes connus si la d\u00e9couverte mDNS ne fonctionne pas." + }, + "description": "Veuillez saisir la configuration de Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Voulez-vous configurer Google Cast?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Les h\u00f4tes connus doivent \u00eatre une liste d'h\u00f4tes s\u00e9par\u00e9s par des virgules." + }, + "step": { + "options": { + "data": { + "known_hosts": "Liste facultative des h\u00f4tes connus si la d\u00e9couverte mDNS ne fonctionne pas." + }, + "description": "Veuillez saisir la configuration de Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/hu.json b/homeassistant/components/cast/translations/hu.json index dc55cd224f8c43..4a6ef76f33cf65 100644 --- a/homeassistant/components/cast/translations/hu.json +++ b/homeassistant/components/cast/translations/hu.json @@ -1,12 +1,35 @@ { "config": { "abort": { - "no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egyetlen Google Cast konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "invalid_known_hosts": "Az ismert hosztoknak vessz\u0151vel elv\u00e1lasztott hosztok list\u00e1j\u00e1nak kell lennie." }, "step": { + "config": { + "data": { + "known_hosts": "Opcion\u00e1lis lista az ismert hosztokr\u00f3l, ha az mDNS felder\u00edt\u00e9s nem m\u0171k\u00f6dik." + }, + "description": "K\u00e9rj\u00fck, add meg a Google Cast konfigur\u00e1ci\u00f3t.", + "title": "Google Cast" + }, "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Google Cast szolg\u00e1ltat\u00e1st?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, + "options": { + "error": { + "invalid_known_hosts": "Az ismert hosztoknak vessz\u0151vel elv\u00e1lasztott hosztok list\u00e1j\u00e1nak kell lennie." + }, + "step": { + "options": { + "data": { + "known_hosts": "Opcion\u00e1lis lista az ismert hosztokr\u00f3l, ha az mDNS felder\u00edt\u00e9s nem m\u0171k\u00f6dik." + }, + "description": "K\u00e9rj\u00fck, add meg a Google Cast konfigur\u00e1ci\u00f3t." } } } diff --git a/homeassistant/components/cast/translations/id.json b/homeassistant/components/cast/translations/id.json index d3e2bb5f360ff9..240ee853609761 100644 --- a/homeassistant/components/cast/translations/id.json +++ b/homeassistant/components/cast/translations/id.json @@ -1,12 +1,35 @@ { "config": { "abort": { - "no_devices_found": "Tidak ada perangkat Google Cast yang ditemukan pada jaringan.", - "single_instance_allowed": "Hanya satu konfigurasi Google Cast yang diperlukan." + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_known_hosts": "Host yang diketahui harus berupa daftar host yang dipisahkan koma." }, "step": { + "config": { + "data": { + "known_hosts": "Daftar opsional host yang diketahui jika penemuan mDNS tidak berfungsi." + }, + "description": "Masukkan konfigurasi Google Cast.", + "title": "Google Cast" + }, "confirm": { - "description": "Apakah Anda ingin menyiapkan Google Cast?" + "description": "Ingin memulai penyiapan?" + } + } + }, + "options": { + "error": { + "invalid_known_hosts": "Host yang diketahui harus berupa daftar host yang dipisahkan koma." + }, + "step": { + "options": { + "data": { + "known_hosts": "Daftar opsional host yang diketahui jika penemuan mDNS tidak berfungsi." + }, + "description": "Masukkan konfigurasi Google Cast." } } } diff --git a/homeassistant/components/cast/translations/it.json b/homeassistant/components/cast/translations/it.json index 0278fe07bfeff9..17abade539edee 100644 --- a/homeassistant/components/cast/translations/it.json +++ b/homeassistant/components/cast/translations/it.json @@ -4,10 +4,33 @@ "no_devices_found": "Nessun dispositivo trovato sulla rete", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, + "error": { + "invalid_known_hosts": "Gli host noti devono essere un elenco di host separato da virgole." + }, "step": { + "config": { + "data": { + "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona." + }, + "description": "Inserisci la configurazione di Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Vuoi iniziare la configurazione?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Gli host noti devono essere indicati sotto forma di un elenco di host separati da virgole." + }, + "step": { + "options": { + "data": { + "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona." + }, + "description": "Inserisci la configurazione di Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index 7011a61f7573ab..3ae6a6885272bb 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -2,12 +2,35 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_known_hosts": "\uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\ub294 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ud638\uc2a4\ud2b8 \ubaa9\ub85d\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "step": { + "config": { + "data": { + "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4." + }, + "description": "Google Cast \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Google Cast" + }, "confirm": { "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "\uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\ub294 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ud638\uc2a4\ud2b8 \ubaa9\ub85d\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, + "step": { + "options": { + "data": { + "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4." + }, + "description": "Google Cast \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/nl.json b/homeassistant/components/cast/translations/nl.json index d42ef3e850c6bb..5c6cfae7c6bc7d 100644 --- a/homeassistant/components/cast/translations/nl.json +++ b/homeassistant/components/cast/translations/nl.json @@ -1,12 +1,35 @@ { "config": { "abort": { - "no_devices_found": "Geen Google Cast-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Google Cast nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "invalid_known_hosts": "Bekende hosts moet een door komma's gescheiden lijst van hosts zijn." }, "step": { + "config": { + "data": { + "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt." + }, + "description": "Voer de Google Cast configuratie in.", + "title": "Google Cast" + }, "confirm": { - "description": "Wilt u Google Cast instellen?" + "description": "Wil je beginnen met instellen?" + } + } + }, + "options": { + "error": { + "invalid_known_hosts": "Bekende hosts moet een door komma's gescheiden lijst van hosts zijn." + }, + "step": { + "options": { + "data": { + "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt." + }, + "description": "Voer de Google Cast configuratie in." } } } diff --git a/homeassistant/components/cast/translations/no.json b/homeassistant/components/cast/translations/no.json index b3d6b5d782e97a..f92a5d649e4ec1 100644 --- a/homeassistant/components/cast/translations/no.json +++ b/homeassistant/components/cast/translations/no.json @@ -4,10 +4,33 @@ "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, + "error": { + "invalid_known_hosts": "Kjente verter m\u00e5 v\u00e6re en kommaseparert liste over verter." + }, "step": { + "config": { + "data": { + "known_hosts": "Valgfri liste over kjente verter hvis mDNS-oppdagelse ikke fungerer." + }, + "description": "Angi Google Cast-konfigurasjonen.", + "title": "Google Cast" + }, "confirm": { "description": "Vil du starte oppsettet?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Kjente verter m\u00e5 v\u00e6re en kommaseparert liste over verter." + }, + "step": { + "options": { + "data": { + "known_hosts": "Valgfri liste over kjente verter hvis mDNS-oppdagelse ikke fungerer." + }, + "description": "Angi Google Cast-konfigurasjonen." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/pl.json b/homeassistant/components/cast/translations/pl.json index a8ee3fa57ac860..e3a74ecd52ec59 100644 --- a/homeassistant/components/cast/translations/pl.json +++ b/homeassistant/components/cast/translations/pl.json @@ -4,10 +4,33 @@ "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, + "error": { + "invalid_known_hosts": "Znane hosty musz\u0105 by\u0107 list\u0105 host\u00f3w oddzielonych przecinkami." + }, "step": { + "config": { + "data": { + "known_hosts": "Opcjonalna lista znanych host\u00f3w, je\u015bli wykrywanie mDNS nie dzia\u0142a." + }, + "description": "Wprowad\u017a konfiguracj\u0119 Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "Znane hosty musz\u0105 by\u0107 list\u0105 host\u00f3w oddzielonych przecinkami." + }, + "step": { + "options": { + "data": { + "known_hosts": "Opcjonalna lista znanych host\u00f3w, je\u015bli wykrywanie mDNS nie dzia\u0142a." + }, + "description": "Wprowad\u017a konfiguracj\u0119 Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/pt.json b/homeassistant/components/cast/translations/pt.json index 9dd9a69a94c50b..32da71ec389d80 100644 --- a/homeassistant/components/cast/translations/pt.json +++ b/homeassistant/components/cast/translations/pt.json @@ -9,5 +9,12 @@ "description": "Deseja configurar o Google Cast?" } } + }, + "options": { + "step": { + "options": { + "description": "Por favor introduza a configura\u00e7\u00e3o do Google Cast" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/ru.json b/homeassistant/components/cast/translations/ru.json index 85a42bf1be546c..e565cedbfadb95 100644 --- a/homeassistant/components/cast/translations/ru.json +++ b/homeassistant/components/cast/translations/ru.json @@ -4,10 +4,33 @@ "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.", "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." }, + "error": { + "invalid_known_hosts": "\u0418\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0445\u043e\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0441\u043f\u0438\u0441\u043a\u043e\u043c, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0437\u0430\u043f\u044f\u0442\u044b\u043c\u0438." + }, "step": { + "config": { + "data": { + "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442." + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Google Cast.", + "title": "Google Cast" + }, "confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" } } + }, + "options": { + "error": { + "invalid_known_hosts": "\u0418\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0445\u043e\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u0441\u043f\u0438\u0441\u043a\u043e\u043c, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u0437\u0430\u043f\u044f\u0442\u044b\u043c\u0438." + }, + "step": { + "options": { + "data": { + "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442." + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Google Cast." + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/zh-Hans.json b/homeassistant/components/cast/translations/zh-Hans.json index 1c2024f8b8114f..0feaac564400ab 100644 --- a/homeassistant/components/cast/translations/zh-Hans.json +++ b/homeassistant/components/cast/translations/zh-Hans.json @@ -5,9 +5,20 @@ "single_instance_allowed": "Google Cast \u53ea\u9700\u8981\u914d\u7f6e\u4e00\u6b21\u3002" }, "step": { + "config": { + "description": "\u8bf7\u786e\u8ba4Goole Cast\u7684\u914d\u7f6e", + "title": "Google Cast" + }, "confirm": { "description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f" } } + }, + "options": { + "step": { + "options": { + "description": "\u8bf7\u786e\u8ba4Goole Cast\u7684\u914d\u7f6e" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 90c98e491dfea4..4a32f61eeffbf3 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -4,10 +4,33 @@ "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e", "single_instance_allowed": "\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" + }, "step": { + "config": { + "data": { + "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002" + }, + "description": "\u8acb\u8f38\u5165 Google Cast \u8a2d\u5b9a\u3002", + "title": "Google Cast" + }, "confirm": { "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" } } + }, + "options": { + "error": { + "invalid_known_hosts": "\u5df2\u77e5\u4e3b\u6a5f\u5fc5\u9808\u4ee5\u9017\u865f\u5206\u4e3b\u6a5f\u5217\u8868\u3002" + }, + "step": { + "options": { + "data": { + "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002" + }, + "description": "\u8acb\u8f38\u5165 Google Cast \u8a2d\u5b9a\u3002" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/hu.json b/homeassistant/components/cert_expiry/translations/hu.json index 5bad24ecb6a1a0..2ae516565e3a34 100644 --- a/homeassistant/components/cert_expiry/translations/hu.json +++ b/homeassistant/components/cert_expiry/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", "import_failed": "Nem siker\u00fclt import\u00e1lni a konfigur\u00e1ci\u00f3t" }, "step": { diff --git a/homeassistant/components/cert_expiry/translations/id.json b/homeassistant/components/cert_expiry/translations/id.json new file mode 100644 index 00000000000000..9fac285fe824d7 --- /dev/null +++ b/homeassistant/components/cert_expiry/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "import_failed": "Impor dari konfigurasi gagal" + }, + "error": { + "connection_refused": "Sambungan ditolak saat menghubungkan ke host", + "connection_timeout": "Tenggang waktu terhubung ke host ini habis", + "resolve_failed": "Host ini tidak dapat ditemukan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama sertifikat", + "port": "Port" + }, + "title": "Tentukan sertifikat yang akan diuji" + } + } + }, + "title": "Informasi Kedaluwarsa Sertifikat" +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/nl.json b/homeassistant/components/cert_expiry/translations/nl.json index d844d28e62fe8a..b29cdcaad71ab7 100644 --- a/homeassistant/components/cert_expiry/translations/nl.json +++ b/homeassistant/components/cert_expiry/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze combinatie van host en poort is al geconfigureerd", + "already_configured": "Service is al geconfigureerd", "import_failed": "Importeren vanuit configuratie is mislukt" }, "error": { diff --git a/homeassistant/components/climacell/translations/bg.json b/homeassistant/components/climacell/translations/bg.json new file mode 100644 index 00000000000000..6b1e4d3cba27ab --- /dev/null +++ b/homeassistant/components/climacell/translations/bg.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430", + "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", + "name": "\u0418\u043c\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json index f18197e1ccad1a..a91e222b3cdc11 100644 --- a/homeassistant/components/climacell/translations/de.json +++ b/homeassistant/components/climacell/translations/de.json @@ -15,5 +15,6 @@ } } } - } + }, + "title": "ClimaCell" } \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/es.json b/homeassistant/components/climacell/translations/es.json index 4c4d8fcc9bb9d8..52fd5d21166da9 100644 --- a/homeassistant/components/climacell/translations/es.json +++ b/homeassistant/components/climacell/translations/es.json @@ -1,7 +1,9 @@ { "config": { "error": { - "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde." + "cannot_connect": "Fallo al conectar", + "rate_limited": "Actualmente la tarifa est\u00e1 limitada, por favor int\u00e9ntelo m\u00e1s tarde.", + "unknown": "Error inesperado" }, "step": { "user": { diff --git a/homeassistant/components/climacell/translations/hu.json b/homeassistant/components/climacell/translations/hu.json new file mode 100644 index 00000000000000..fa0aa2ec0c7305 --- /dev/null +++ b/homeassistant/components/climacell/translations/hu.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "name": "N\u00e9v" + }, + "description": "Ha a Sz\u00e9less\u00e9g \u00e9s Hossz\u00fas\u00e1g nincs megadva, akkor a Home Assistant konfigur\u00e1ci\u00f3j\u00e1ban l\u00e9v\u0151 alap\u00e9rtelmezett \u00e9rt\u00e9keket fogjuk haszn\u00e1lni. Minden el\u0151rejelz\u00e9si t\u00edpushoz l\u00e9trej\u00f6n egy entit\u00e1s, de alap\u00e9rtelmez\u00e9s szerint csak az \u00e1ltalad kiv\u00e1lasztottak lesznek enged\u00e9lyezve." + } + } + }, + "options": { + "step": { + "init": { + "title": "Friss\u00edtse a ClimaCell be\u00e1ll\u00edt\u00e1sokat" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/id.json b/homeassistant/components/climacell/translations/id.json new file mode 100644 index 00000000000000..132f4dcfcb7d3d --- /dev/null +++ b/homeassistant/components/climacell/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid", + "rate_limited": "Saat ini tingkatnya dibatasi, coba lagi nanti.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "description": "Jika Lintang dan Bujur tidak tersedia, nilai default dalam konfigurasi Home Assistant akan digunakan. Entitas akan dibuat untuk setiap jenis prakiraan tetapi hanya yang Anda pilih yang akan diaktifkan secara default." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Jenis Prakiraan", + "timestep": "Jarak Interval Prakiraan NowCast dalam Menit" + }, + "description": "Jika Anda memilih untuk mengaktifkan entitas prakiraan 'nowcast', Anda dapat mengonfigurasi jarak interval prakiraan dalam menit. Jumlah prakiraan yang diberikan tergantung pada nilai interval yang dipilih.", + "title": "Perbarui Opsi ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/ko.json b/homeassistant/components/climacell/translations/ko.json new file mode 100644 index 00000000000000..6fc5a6d7e8bee0 --- /dev/null +++ b/homeassistant/components/climacell/translations/ko.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "rate_limited": "\ud604\uc7ac \uc0ac\uc6a9 \ud69f\uc218\ub97c \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "name": "\uc774\ub984" + }, + "description": "\uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 Home Assistant \uad6c\uc131\uc758 \uae30\ubcf8\uac12\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \uac01 \uc77c\uae30\uc608\ubcf4 \uc720\ud615\uc5d0 \ub300\ud574 \uad6c\uc131\uc694\uc18c\uac00 \uc0dd\uc131\ub418\uc9c0\ub9cc \uae30\ubcf8\uc801\uc73c\ub85c \uc120\ud0dd\ud55c \uad6c\uc131\uc694\uc18c\ub9cc \ud65c\uc131\ud654\ub429\ub2c8\ub2e4." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\uc77c\uae30\uc608\ubcf4 \uc720\ud615", + "timestep": "\ub2e8\uae30\uc608\uce21 \uc77c\uae30\uc608\ubcf4 \uac04 \ucd5c\uc18c \uc2dc\uac04" + }, + "description": "`nowcast` \uc77c\uae30\uc608\ubcf4 \uad6c\uc131\uc694\uc18c\ub97c \uc0ac\uc6a9\ud558\ub3c4\ub85d \uc120\ud0dd\ud55c \uacbd\uc6b0 \uac01 \uc77c\uae30\uc608\ubcf4 \uc0ac\uc774\uc758 \uc2dc\uac04(\ubd84)\uc744 \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc81c\uacf5\ub41c \uc77c\uae30\uc608\ubcf4 \ud69f\uc218\ub294 \uc608\uce21 \uac04 \uc120\ud0dd\ud55c \uc2dc\uac04(\ubd84)\uc5d0 \ub530\ub77c \ub2ec\ub77c\uc9d1\ub2c8\ub2e4.", + "title": "ClimaCell \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/nl.json b/homeassistant/components/climacell/translations/nl.json index 488a43ae24ecd2..f267be34478878 100644 --- a/homeassistant/components/climacell/translations/nl.json +++ b/homeassistant/components/climacell/translations/nl.json @@ -3,6 +3,7 @@ "error": { "cannot_connect": "Kan geen verbinding maken", "invalid_api_key": "Ongeldige API-sleutel", + "rate_limited": "Momenteel is een beperkt aantal aanvragen mogelijk, probeer later opnieuw.", "unknown": "Onverwachte fout" }, "step": { @@ -13,7 +14,7 @@ "longitude": "Lengtegraad", "name": "Naam" }, - "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype maar alleen degene die u selecteert worden standaard ingeschakeld." + "description": "Indien Breedtegraad en Lengtegraad niet worden opgegeven, worden de standaardwaarden in de Home Assistant-configuratie gebruikt. Er wordt een entiteit gemaakt voor elk voorspellingstype, maar alleen degenen die u selecteert worden standaard ingeschakeld." } } }, @@ -21,7 +22,8 @@ "step": { "init": { "data": { - "forecast_types": "Voorspellingstype(n)" + "forecast_types": "Voorspellingstype(n)", + "timestep": "Min. Tussen NowCast-voorspellingen" }, "description": "Als u ervoor kiest om de `nowcast` voorspellingsentiteit in te schakelen, kan u het aantal minuten tussen elke voorspelling configureren. Het aantal voorspellingen hangt af van het aantal gekozen minuten tussen de voorspellingen.", "title": "Update ClimaCell Opties" diff --git a/homeassistant/components/climacell/translations/pl.json b/homeassistant/components/climacell/translations/pl.json new file mode 100644 index 00000000000000..6fc13aadc96c40 --- /dev/null +++ b/homeassistant/components/climacell/translations/pl.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_api_key": "Nieprawid\u0142owy klucz API", + "rate_limited": "Przekroczono limit, spr\u00f3buj ponownie p\u00f3\u017aniej.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "name": "Nazwa" + }, + "description": "Je\u015bli szeroko\u015b\u0107 i d\u0142ugo\u015b\u0107 geograficzna nie zostan\u0105 podane, zostan\u0105 u\u017cyte domy\u015blne warto\u015bci z konfiguracji Home Assistanta. Zostanie utworzona encja dla ka\u017cdego typu prognozy, ale domy\u015blnie w\u0142\u0105czone bed\u0105 tylko te, kt\u00f3re wybierzesz." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Typ(y) prognozy", + "timestep": "Minuty pomi\u0119dzy prognozami" + }, + "description": "Je\u015bli zdecydujesz si\u0119 w\u0142\u0105czy\u0107 encj\u0119 prognozy \u201enowcast\u201d, mo\u017cesz skonfigurowa\u0107 liczb\u0119 minut mi\u0119dzy ka\u017cd\u0105 prognoz\u0105. Liczba dostarczonych prognoz zale\u017cy od liczby minut wybranych mi\u0119dzy prognozami.", + "title": "Opcje aktualizacji ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/pt.json b/homeassistant/components/climacell/translations/pt.json new file mode 100644 index 00000000000000..8e05df2f1b5476 --- /dev/null +++ b/homeassistant/components/climacell/translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_api_key": "Chave de API inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/zh-Hans.json b/homeassistant/components/climacell/translations/zh-Hans.json new file mode 100644 index 00000000000000..315d060bc69a26 --- /dev/null +++ b/homeassistant/components/climacell/translations/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API Key", + "name": "\u540d\u5b57" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climate/translations/id.json b/homeassistant/components/climate/translations/id.json index 4f1ec02379b4f0..bdae7d60067525 100644 --- a/homeassistant/components/climate/translations/id.json +++ b/homeassistant/components/climate/translations/id.json @@ -1,13 +1,28 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "Ubah mode HVAC di {entity_name}", + "set_preset_mode": "Ubah prasetel di {entity_name}" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} disetel ke mode HVAC tertentu", + "is_preset_mode": "{entity_name} disetel ke mode prasetel tertentu" + }, + "trigger_type": { + "current_humidity_changed": "Kelembaban terukur {entity_name} berubah", + "current_temperature_changed": "Suhu terukur {entity_name} berubah", + "hvac_mode_changed": "Mode HVAC {entity_name} berubah" + } + }, "state": { "_": { - "auto": "Auto", - "cool": "Sejuk", + "auto": "Otomatis", + "cool": "Dingin", "dry": "Kering", "fan_only": "Hanya kipas", "heat": "Panas", "heat_cool": "Panas/Dingin", - "off": "Off" + "off": "Mati" } }, "title": "Cuaca" diff --git a/homeassistant/components/climate/translations/ko.json b/homeassistant/components/climate/translations/ko.json index 0923d16604055a..7c7342ef95ccb2 100644 --- a/homeassistant/components/climate/translations/ko.json +++ b/homeassistant/components/climate/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "set_hvac_mode": "{entity_name} \uc758 HVAC \ubaa8\ub4dc \ubcc0\uacbd", - "set_preset_mode": "{entity_name} \uc758 \ud504\ub9ac\uc14b \ubcc0\uacbd" + "set_hvac_mode": "{entity_name}\uc758 HVAC \ubaa8\ub4dc \ubcc0\uacbd\ud558\uae30", + "set_preset_mode": "{entity_name}\uc758 \ud504\ub9ac\uc14b \ubcc0\uacbd\ud558\uae30" }, "condition_type": { - "is_hvac_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 HVAC \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", - "is_preset_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \ud504\ub9ac\uc14b \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" + "is_hvac_mode": "{entity_name}\uc774(\uac00) \ud2b9\uc815 HVAC \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", + "is_preset_mode": "{entity_name}\uc774(\uac00) \ud2b9\uc815 \ud504\ub9ac\uc14b \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" }, "trigger_type": { - "current_humidity_changed": "{entity_name} \uc774(\uac00) \uc2b5\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", - "current_temperature_changed": "{entity_name} \uc774(\uac00) \uc628\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", - "hvac_mode_changed": "{entity_name} HVAC \ubaa8\ub4dc\uac00 \ubcc0\uacbd\ub420 \ub54c" + "current_humidity_changed": "{entity_name}\uc774(\uac00) \uc2b5\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "current_temperature_changed": "{entity_name}\uc774(\uac00) \uc628\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "hvac_mode_changed": "{entity_name}\uc758 HVAC \ubaa8\ub4dc\uac00 \ubcc0\uacbd\ub418\uc5c8\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/climate/translations/zh-Hans.json b/homeassistant/components/climate/translations/zh-Hans.json index 9927cd679ae259..a93125525e1feb 100644 --- a/homeassistant/components/climate/translations/zh-Hans.json +++ b/homeassistant/components/climate/translations/zh-Hans.json @@ -5,8 +5,8 @@ "set_preset_mode": "\u66f4\u6539 {entity_name} \u9884\u8bbe\u6a21\u5f0f" }, "condition_type": { - "is_hvac_mode": "{entity_name} \u88ab\u8bbe\u4e3a\u6307\u5b9a\u7684\u7a7a\u8c03\u6a21\u5f0f", - "is_preset_mode": "{entity_name} \u88ab\u8bbe\u4e3a\u6307\u5b9a\u7684\u9884\u8bbe\u6a21\u5f0f" + "is_hvac_mode": "{entity_name} \u5df2\u8bbe\u4e3a\u6307\u5b9a\u7684\u7a7a\u8c03\u6a21\u5f0f", + "is_preset_mode": "{entity_name} \u5df2\u8bbe\u4e3a\u6307\u5b9a\u7684\u9884\u8bbe\u6a21\u5f0f" }, "trigger_type": { "current_humidity_changed": "{entity_name} \u6d4b\u91cf\u7684\u5ba4\u5185\u6e7f\u5ea6\u53d8\u5316", diff --git a/homeassistant/components/cloud/translations/hu.json b/homeassistant/components/cloud/translations/hu.json index a2bea167b5ea00..8301806831b53a 100644 --- a/homeassistant/components/cloud/translations/hu.json +++ b/homeassistant/components/cloud/translations/hu.json @@ -2,6 +2,8 @@ "system_health": { "info": { "alexa_enabled": "Alexa enged\u00e9lyezve", + "can_reach_cert_server": "Tan\u00fas\u00edtv\u00e1nykiszolg\u00e1l\u00f3 el\u00e9r\u00e9se", + "can_reach_cloud": "Home Assistant Cloud el\u00e9r\u00e9se", "can_reach_cloud_auth": "Hiteles\u00edt\u00e9si kiszolg\u00e1l\u00f3 el\u00e9r\u00e9se", "google_enabled": "Google enged\u00e9lyezve", "logged_in": "Bejelentkezve", diff --git a/homeassistant/components/cloud/translations/id.json b/homeassistant/components/cloud/translations/id.json new file mode 100644 index 00000000000000..1cff542796c3c9 --- /dev/null +++ b/homeassistant/components/cloud/translations/id.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa Diaktifkan", + "can_reach_cert_server": "Keterjangkauan Server Sertifikat", + "can_reach_cloud": "Keterjangkauan Home Assistant Cloud", + "can_reach_cloud_auth": "Keterjangkauan Server Autentikasi", + "google_enabled": "Google Diaktifkan", + "logged_in": "Masuk", + "relayer_connected": "Relayer Terhubung", + "remote_connected": "Terhubung Jarak Jauh", + "remote_enabled": "Kontrol Jarak Jauh Diaktifkan", + "subscription_expiration": "Masa Kedaluwarsa Langganan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/ko.json b/homeassistant/components/cloud/translations/ko.json new file mode 100644 index 00000000000000..94b939502de7a3 --- /dev/null +++ b/homeassistant/components/cloud/translations/ko.json @@ -0,0 +1,16 @@ +{ + "system_health": { + "info": { + "alexa_enabled": "Alexa \ud65c\uc131\ud654", + "can_reach_cert_server": "\uc778\uc99d\uc11c \uc11c\ubc84 \uc5f0\uacb0", + "can_reach_cloud": "Home Assistant Cloud \uc5f0\uacb0", + "can_reach_cloud_auth": "\uc778\uc99d \uc11c\ubc84 \uc5f0\uacb0", + "google_enabled": "Google \uc5b4\uc2dc\uc2a4\ud134\ud2b8 \ud65c\uc131\ud654", + "logged_in": "\ub85c\uadf8\uc778", + "relayer_connected": "\uc911\uacc4\uae30 \uc5f0\uacb0", + "remote_connected": "\uc6d0\uaca9 \uc81c\uc5b4 \uc5f0\uacb0", + "remote_enabled": "\uc6d0\uaca9 \uc81c\uc5b4 \ud65c\uc131\ud654", + "subscription_expiration": "\uad6c\ub3c5 \ub9cc\ub8cc" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloud/translations/nl.json b/homeassistant/components/cloud/translations/nl.json index d9aa78afecb7e2..0ad7a5288228ea 100644 --- a/homeassistant/components/cloud/translations/nl.json +++ b/homeassistant/components/cloud/translations/nl.json @@ -2,6 +2,7 @@ "system_health": { "info": { "alexa_enabled": "Alexa ingeschakeld", + "can_reach_cert_server": "Bereik Certificaatserver", "can_reach_cloud": "Bereik Home Assistant Cloud", "can_reach_cloud_auth": "Bereik authenticatieserver", "google_enabled": "Google ingeschakeld", diff --git a/homeassistant/components/cloudflare/translations/hu.json b/homeassistant/components/cloudflare/translations/hu.json index fa13d00617f0d0..fed6f22d536c78 100644 --- a/homeassistant/components/cloudflare/translations/hu.json +++ b/homeassistant/components/cloudflare/translations/hu.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "unknown": "V\u00e1ratlan hiba" + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_zone": "\u00c9rv\u00e9nytelen z\u00f3na" }, "flow_title": "Cloudflare: {name}", diff --git a/homeassistant/components/cloudflare/translations/id.json b/homeassistant/components/cloudflare/translations/id.json new file mode 100644 index 00000000000000..98286398ea8e79 --- /dev/null +++ b/homeassistant/components/cloudflare/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_zone": "Zona tidak valid" + }, + "flow_title": "Cloudflare: {name}", + "step": { + "records": { + "data": { + "records": "Catatan" + }, + "title": "Pilih Catatan untuk Diperbarui" + }, + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Integrasi ini memerlukan Token API yang dibuat dengan izin Zone:Zone:Read and Zone:DNS:Edit untuk semua zona di akun Anda.", + "title": "Hubungkan ke Cloudflare" + }, + "zone": { + "data": { + "zone": "Zona" + }, + "title": "Pilih Zona yang akan Diperbarui" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cloudflare/translations/ko.json b/homeassistant/components/cloudflare/translations/ko.json index d4f4eee49a8435..4dbe263a138aed 100644 --- a/homeassistant/components/cloudflare/translations/ko.json +++ b/homeassistant/components/cloudflare/translations/ko.json @@ -1,18 +1,34 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_zone": "\uc601\uc5ed\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Cloudflare: {name}", "step": { + "records": { + "data": { + "records": "\ub808\ucf54\ub4dc" + }, + "title": "\uc5c5\ub370\uc774\ud2b8\ud560 \ub808\ucf54\ub4dc \uc120\ud0dd\ud558\uae30" + }, "user": { "data": { "api_token": "API \ud1a0\ud070" - } + }, + "description": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc758 \ubaa8\ub4e0 \uc601\uc5ed\uc5d0 \ub300\ud574 Zone:Zone:Read \ubc0f Zone:DNS:Edit \uad8c\ud55c\uc73c\ub85c \uc0dd\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", + "title": "Cloudflare\uc5d0 \uc5f0\uacb0\ud558\uae30" + }, + "zone": { + "data": { + "zone": "\uc601\uc5ed" + }, + "title": "\uc5c5\ub370\uc774\ud2b8\ud560 \uc601\uc5ed \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/cloudflare/translations/nl.json b/homeassistant/components/cloudflare/translations/nl.json index 35c765d5da7f2e..e4f6c1180e264a 100644 --- a/homeassistant/components/cloudflare/translations/nl.json +++ b/homeassistant/components/cloudflare/translations/nl.json @@ -26,7 +26,8 @@ "zone": { "data": { "zone": "Zone" - } + }, + "title": "Kies de zone die u wilt bijwerken" } } } diff --git a/homeassistant/components/configurator/translations/id.json b/homeassistant/components/configurator/translations/id.json index 759af513228a3a..f345a39417b754 100644 --- a/homeassistant/components/configurator/translations/id.json +++ b/homeassistant/components/configurator/translations/id.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Konfigurasi", + "configure": "Konfigurasikan", "configured": "Terkonfigurasi" } }, diff --git a/homeassistant/components/control4/translations/hu.json b/homeassistant/components/control4/translations/hu.json index 3b2d79a34a77e2..68cb4fe23a9d35 100644 --- a/homeassistant/components/control4/translations/hu.json +++ b/homeassistant/components/control4/translations/hu.json @@ -2,6 +2,20 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "IP c\u00edm", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/control4/translations/id.json b/homeassistant/components/control4/translations/id.json new file mode 100644 index 00000000000000..4b8033c0873f62 --- /dev/null +++ b/homeassistant/components/control4/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Alamat IP", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan detail akun Control4 Anda dan alamat IP pengontrol lokal Anda." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pembaruan dalam detik" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/nl.json b/homeassistant/components/control4/translations/nl.json index 1c4e7de05c995a..d591b4631a4754 100644 --- a/homeassistant/components/control4/translations/nl.json +++ b/homeassistant/components/control4/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -16,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconden tussen updates" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/id.json b/homeassistant/components/coolmaster/translations/id.json new file mode 100644 index 00000000000000..d12c10da25a25d --- /dev/null +++ b/homeassistant/components/coolmaster/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "no_units": "Tidak dapat menemukan perangkat HVAC di host CoolMasterNet." + }, + "step": { + "user": { + "data": { + "cool": "Mendukung mode dingin", + "dry": "Mendukung mode kering", + "fan_only": "Mendukung mode kipas saja", + "heat": "Mendukung mode panas", + "heat_cool": "Mendukung mode panas/dingin otomatis", + "host": "Host", + "off": "Bisa dimatikan" + }, + "title": "Siapkan detail koneksi CoolMasterNet Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/hu.json b/homeassistant/components/coronavirus/translations/hu.json index fcee85c40e8297..631454ec04582a 100644 --- a/homeassistant/components/coronavirus/translations/hu.json +++ b/homeassistant/components/coronavirus/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez az orsz\u00e1g m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/coronavirus/translations/id.json b/homeassistant/components/coronavirus/translations/id.json new file mode 100644 index 00000000000000..e2626d16abb95d --- /dev/null +++ b/homeassistant/components/coronavirus/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "country": "Negara" + }, + "title": "Pilih negara untuk dipantau" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/nl.json b/homeassistant/components/coronavirus/translations/nl.json index d306894f7d03a3..fed3101b38e401 100644 --- a/homeassistant/components/coronavirus/translations/nl.json +++ b/homeassistant/components/coronavirus/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit land is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/cover/translations/hu.json b/homeassistant/components/cover/translations/hu.json index 6d48cca1251304..87bd1c241c667e 100644 --- a/homeassistant/components/cover/translations/hu.json +++ b/homeassistant/components/cover/translations/hu.json @@ -6,7 +6,8 @@ "open": "{entity_name} nyit\u00e1sa", "open_tilt": "{entity_name} d\u00f6nt\u00e9s nyit\u00e1sa", "set_position": "{entity_name} poz\u00edci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa", - "set_tilt_position": "{entity_name} d\u00f6nt\u00e9si poz\u00edci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa" + "set_tilt_position": "{entity_name} d\u00f6nt\u00e9si poz\u00edci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa", + "stop": "{entity_name} meg\u00e1ll\u00edt\u00e1sa" }, "condition_type": { "is_closed": "{entity_name} z\u00e1rva van", diff --git a/homeassistant/components/cover/translations/id.json b/homeassistant/components/cover/translations/id.json index b38fcf86a174e0..d07f2f23ad2992 100644 --- a/homeassistant/components/cover/translations/id.json +++ b/homeassistant/components/cover/translations/id.json @@ -1,9 +1,36 @@ { + "device_automation": { + "action_type": { + "close": "Tutup {entity_name}", + "close_tilt": "Tutup miring {entity_name}", + "open": "Buka {entity_name}", + "open_tilt": "Buka miring {entity_name}", + "set_position": "Tetapkan posisi {entity_name}", + "set_tilt_position": "Setel posisi miring {entity_name}", + "stop": "Hentikan {entity_name}" + }, + "condition_type": { + "is_closed": "{entity_name} tertutup", + "is_closing": "{entity_name} menutup", + "is_open": "{entity_name} terbuka", + "is_opening": "{entity_name} membuka", + "is_position": "Posisi {entity_name} saat ini adalah", + "is_tilt_position": "Posisi miring {entity_name} saat ini adalah" + }, + "trigger_type": { + "closed": "{entity_name} tertutup", + "closing": "{entity_name} menutup", + "opened": "{entity_name} terbuka", + "opening": "{entity_name} membuka", + "position": "Perubahan posisi {entity_name}", + "tilt_position": "Perubahan posisi kemiringan {entity_name}" + } + }, "state": { "_": { "closed": "Tertutup", "closing": "Menutup", - "open": "Buka", + "open": "Terbuka", "opening": "Membuka", "stopped": "Terhenti" } diff --git a/homeassistant/components/cover/translations/ko.json b/homeassistant/components/cover/translations/ko.json index 0a666a8bd82d67..71a48bd532d4bb 100644 --- a/homeassistant/components/cover/translations/ko.json +++ b/homeassistant/components/cover/translations/ko.json @@ -1,28 +1,29 @@ { "device_automation": { "action_type": { - "close": "{entity_name} \ub2eb\uae30", - "close_tilt": "{entity_name} \ub2eb\uae30", - "open": "{entity_name} \uc5f4\uae30", - "open_tilt": "{entity_name} \uc5f4\uae30", - "set_position": "{entity_name} \uac1c\ud3d0 \uc704\uce58 \uc124\uc815\ud558\uae30", - "set_tilt_position": "{entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30 \uc124\uc815\ud558\uae30" + "close": "{entity_name}\uc744(\ub97c) \ub2eb\uae30", + "close_tilt": "{entity_name}\uc744(\ub97c) \ub2eb\uae30", + "open": "{entity_name}\uc744(\ub97c) \uc5f4\uae30", + "open_tilt": "{entity_name}\uc744(\ub97c) \uc5f4\uae30", + "set_position": "{entity_name}\uc758 \uac1c\ud3d0 \uc704\uce58 \uc124\uc815\ud558\uae30", + "set_tilt_position": "{entity_name}\uc758 \uac1c\ud3d0 \uae30\uc6b8\uae30 \uc124\uc815\ud558\uae30", + "stop": "{entity_name}\uc744(\ub97c) \uc815\uc9c0\ud558\uae30" }, "condition_type": { - "is_closed": "{entity_name} \uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", - "is_closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc774\uba74", - "is_open": "{entity_name} \uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", - "is_opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc774\uba74", - "is_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uc704\uce58\uac00 ~ \uc774\uba74", - "is_tilt_position": "\ud604\uc7ac {entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 ~ \uc774\uba74" + "is_closed": "{entity_name}\uc774(\uac00) \ub2eb\ud600 \uc788\uc73c\uba74", + "is_closing": "{entity_name}\uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc774\uba74", + "is_open": "{entity_name}\uc774(\uac00) \uc5f4\ub824 \uc788\uc73c\uba74", + "is_opening": "{entity_name}\uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc774\uba74", + "is_position": "\ud604\uc7ac {entity_name}\uc758 \uac1c\ud3d0 \uc704\uce58\uac00 ~ \uc774\uba74", + "is_tilt_position": "\ud604\uc7ac {entity_name}\uc758 \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 ~ \uc774\uba74" }, "trigger_type": { - "closed": "{entity_name} \uc774(\uac00) \ub2eb\ud790 \ub54c", - "closing": "{entity_name} \uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc77c \ub54c", - "opened": "{entity_name} \uc774(\uac00) \uc5f4\ub9b4 \ub54c", - "opening": "{entity_name} \uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc77c \ub54c", - "position": "{entity_name} \uac1c\ud3d0 \uc704\uce58\uac00 \ubcc0\ud560 \ub54c", - "tilt_position": "{entity_name} \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 \ubcc0\ud560 \ub54c" + "closed": "{entity_name}\uc774(\uac00) \ub2eb\ud614\uc744 \ub54c", + "closing": "{entity_name}\uc774(\uac00) \ub2eb\ud788\ub294 \uc911\uc77c \ub54c", + "opened": "{entity_name}\uc774(\uac00) \uc5f4\ub838\uc744 \ub54c", + "opening": "{entity_name}\uc774(\uac00) \uc5f4\ub9ac\ub294 \uc911\uc77c \ub54c", + "position": "{entity_name}\uc758 \uac1c\ud3d0 \uc704\uce58\uac00 \ubcc0\ud560 \ub54c", + "tilt_position": "{entity_name}\uc758 \uac1c\ud3d0 \uae30\uc6b8\uae30\uac00 \ubcc0\ud560 \ub54c" } }, "state": { diff --git a/homeassistant/components/cover/translations/zh-Hans.json b/homeassistant/components/cover/translations/zh-Hans.json index 04b25ad7cb8a97..765fcbeebe022f 100644 --- a/homeassistant/components/cover/translations/zh-Hans.json +++ b/homeassistant/components/cover/translations/zh-Hans.json @@ -2,8 +2,11 @@ "device_automation": { "action_type": { "close": "\u5173\u95ed {entity_name}", + "close_tilt": "\u5173\u95ed {entity_name}", "open": "\u6253\u5f00 {entity_name}", + "open_tilt": "\u65cb\u5f00 {entity_name}", "set_position": "\u8bbe\u7f6e {entity_name} \u7684\u4f4d\u7f6e", + "set_tilt_position": "\u8bbe\u7f6e {entity_name} \u7684\u503e\u659c\u4f4d\u7f6e", "stop": "\u505c\u6b62 {entity_name}" }, "condition_type": { @@ -19,7 +22,8 @@ "closing": "{entity_name} \u6b63\u5728\u5173\u95ed", "opened": "{entity_name} \u5df2\u6253\u5f00", "opening": "{entity_name} \u6b63\u5728\u6253\u5f00", - "position": "{entity_name} \u7684\u4f4d\u7f6e\u53d8\u5316" + "position": "{entity_name} \u7684\u4f4d\u7f6e\u53d8\u5316", + "tilt_position": "{entity_name} \u7684\u503e\u659c\u4f4d\u7f6e\u53d8\u5316" } }, "state": { diff --git a/homeassistant/components/daikin/translations/hu.json b/homeassistant/components/daikin/translations/hu.json index ef589eb7f6d869..f1cb7eab8f68c6 100644 --- a/homeassistant/components/daikin/translations/hu.json +++ b/homeassistant/components/daikin/translations/hu.json @@ -1,11 +1,13 @@ { "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", - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/id.json b/homeassistant/components/daikin/translations/id.json new file mode 100644 index 00000000000000..8b7cfb5460eb1b --- /dev/null +++ b/homeassistant/components/daikin/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "host": "Host", + "password": "Kata Sandi" + }, + "description": "Masukkan Alamat IP perangkat AC Daikin Anda. \n\nPerhatikan bahwa Kunci API dan Kata Sandi hanya digunakan untuk perangkat BRP072Cxx dan SKYFi.", + "title": "Konfigurasi AC Daikin" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index e4cf54eb365ff9..706a81b5f7fa13 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -16,7 +16,7 @@ "host": "Host", "password": "Wachtwoord" }, - "description": "Voer het IP-adres van uw Daikin AC in.", + "description": "Voer IP-adres van uw Daikin AC in.\n\nLet op dat API-sleutel en Wachtwoord alleen worden gebruikt door respectievelijk BRP072Cxx en SKYFi apparaten.", "title": "Daikin AC instellen" } } diff --git a/homeassistant/components/deconz/translations/bg.json b/homeassistant/components/deconz/translations/bg.json index 0aef2e3ec988fe..24e36ecbe55824 100644 --- a/homeassistant/components/deconz/translations/bg.json +++ b/homeassistant/components/deconz/translations/bg.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee \u0448\u043b\u044e\u0437 ({host})", "step": { "hassio_confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 deCONZ \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 hass.io {addon}?", - "title": "deCONZ Zigbee \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\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 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 deCONZ \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 Supervisor {addon}?", + "title": "deCONZ Zigbee \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f \u0447\u0440\u0435\u0437 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430" }, "link": { "description": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438 deCONZ \u0448\u043b\u044e\u0437\u0430 \u0437\u0430 \u0434\u0430 \u0441\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430 \u0441 Home Assistant.\n\n1. \u041e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 deCONZ Settings -> Gateway -> Advanced\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u0431\u0443\u0442\u043e\u043d\u0430 \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/ca.json b/homeassistant/components/deconz/translations/ca.json index 49374ee123feb0..5957dc88c033c6 100644 --- a/homeassistant/components/deconz/translations/ca.json +++ b/homeassistant/components/deconz/translations/ca.json @@ -14,8 +14,8 @@ "flow_title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee ({host})", "step": { "hassio_confirm": { - "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb la passarel\u00b7la deCONZ proporcionada pel complement de Hass.io: {addon}?", - "title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee (complement de Hass.io)" + "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb la passarel\u00b7la deCONZ proporcionada pel complement de Hass.io {addon}?", + "title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee via complement de Hass.io" }, "link": { "description": "Desbloqueja la teva passarel\u00b7la d'enlla\u00e7 deCONZ per a registrar-te amb Home Assistant.\n\n1. V\u00e9s a la configuraci\u00f3 del sistema deCONZ -> Passarel\u00b7la -> Avan\u00e7at\n2. Prem el bot\u00f3 \"Autenticar applicaci\u00f3\"", diff --git a/homeassistant/components/deconz/translations/cs.json b/homeassistant/components/deconz/translations/cs.json index 52cbd607b7f9a8..7e08a89ec317a2 100644 --- a/homeassistant/components/deconz/translations/cs.json +++ b/homeassistant/components/deconz/translations/cs.json @@ -14,8 +14,8 @@ "flow_title": "Br\u00e1na deCONZ ZigBee ({host})", "step": { "hassio_confirm": { - "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k deCONZ br\u00e1n\u011b pomoc\u00ed hass.io {addon}?", - "title": "deCONZ Zigbee br\u00e1na prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k deCONZ br\u00e1n\u011b pomoc\u00ed Supervisor {addon}?", + "title": "deCONZ Zigbee br\u00e1na prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" }, "link": { "description": "Odemkn\u011bte br\u00e1nu deCONZ pro registraci v Home Assistant.\n\n 1. P\u0159ejd\u011bte na Nastaven\u00ed deCONZ - > Br\u00e1na - > Pokro\u010dil\u00e9\n 2. Stiskn\u011bte tla\u010d\u00edtko \"Ov\u011b\u0159it aplikaci\"", diff --git a/homeassistant/components/deconz/translations/da.json b/homeassistant/components/deconz/translations/da.json index 50cdd242ad01e1..be165a206bf6d6 100644 --- a/homeassistant/components/deconz/translations/da.json +++ b/homeassistant/components/deconz/translations/da.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ-gateway'en leveret af Hass.io-tilf\u00f8jelsen {addon}?", - "title": "deCONZ Zigbee-gateway via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ-gateway'en leveret af Supervisor-tilf\u00f8jelsen {addon}?", + "title": "deCONZ Zigbee-gateway via Supervisor-tilf\u00f8jelse" }, "link": { "description": "L\u00e5s din deCONZ-gateway op for at registrere dig med Home Assistant. \n\n 1. G\u00e5 til deCONZ settings -> Gateway -> Advanced\n 2. Tryk p\u00e5 knappen \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index 75b807b88483e1..a8575d212d649e 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee Gateway", "step": { "hassio_confirm": { - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Hass.io Add-on {addon} bereitgestellt wird?", - "title": "deCONZ Zigbee Gateway \u00fcber das Hass.io Add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Supervisor Add-on {addon} bereitgestellt wird?", + "title": "deCONZ Zigbee Gateway \u00fcber das Supervisor Add-on" }, "link": { "description": "Entsperre dein deCONZ-Gateway, um es bei Home Assistant zu registrieren. \n\n 1. Gehe in die deCONZ-Systemeinstellungen \n 2. Dr\u00fccke die Taste \"Gateway entsperren\"", diff --git a/homeassistant/components/deconz/translations/es-419.json b/homeassistant/components/deconz/translations/es-419.json index e454d080ad7196..e439d1da949506 100644 --- a/homeassistant/components/deconz/translations/es-419.json +++ b/homeassistant/components/deconz/translations/es-419.json @@ -13,8 +13,8 @@ "flow_title": "Puerta de enlace Zigbee deCONZ ({host})", "step": { "hassio_confirm": { - "description": "\u00bfDesea configurar Home Assistant para conectarse a la puerta de enlace deCONZ proporcionada por el complemento hass.io {addon}?", - "title": "deCONZ Zigbee gateway a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar Home Assistant para conectarse a la puerta de enlace deCONZ proporcionada por el complemento Supervisor {addon}?", + "title": "deCONZ Zigbee gateway a trav\u00e9s del complemento Supervisor" }, "link": { "description": "Desbloquee su puerta de enlace deCONZ para registrarse con Home Assistant. \n\n 1. Vaya a Configuraci\u00f3n deCONZ - > Gateway - > Avanzado \n 2. Presione el bot\u00f3n \"Autenticar aplicaci\u00f3n\"", diff --git a/homeassistant/components/deconz/translations/es.json b/homeassistant/components/deconz/translations/es.json index 62ab509e268ede..b237d84fafc1ef 100644 --- a/homeassistant/components/deconz/translations/es.json +++ b/homeassistant/components/deconz/translations/es.json @@ -14,8 +14,8 @@ "flow_title": "pasarela deCONZ Zigbee ({host})", "step": { "hassio_confirm": { - "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de hass.io?", - "title": "Add-on deCONZ Zigbee v\u00eda Hass.io" + "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de Supervisor?", + "title": "Add-on deCONZ Zigbee v\u00eda Supervisor" }, "link": { "description": "Desbloquea tu gateway de deCONZ para registrarte con Home Assistant.\n\n1. Dir\u00edgete a deCONZ Settings -> Gateway -> Advanced\n2. Pulsa el bot\u00f3n \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/et.json b/homeassistant/components/deconz/translations/et.json index 9f6644ea18669b..b949208a664fb8 100644 --- a/homeassistant/components/deconz/translations/et.json +++ b/homeassistant/components/deconz/translations/et.json @@ -15,7 +15,7 @@ "step": { "hassio_confirm": { "description": "Kas soovid seadistada Home Assistant-i \u00fchenduse deCONZ-l\u00fc\u00fcsiga, mida pakub Hass.io lisandmoodul {addon} ?", - "title": "deCONZ Zigbee v\u00e4rav Hass.io pistikprogrammi kaudu" + "title": "deCONZ Zigbee l\u00fc\u00fcs Hass.io lisandmooduli abil" }, "link": { "description": "Home Assistanti registreerumiseks ava deCONZ-i l\u00fc\u00fcs.\n\n 1. Mine deCONZ Settings - > Gateway - > Advanced\n 2. Vajuta nuppu \"Authenticate app\"", diff --git a/homeassistant/components/deconz/translations/fr.json b/homeassistant/components/deconz/translations/fr.json index b64819471a0a0f..d24b592ac10297 100644 --- a/homeassistant/components/deconz/translations/fr.json +++ b/homeassistant/components/deconz/translations/fr.json @@ -14,8 +14,8 @@ "flow_title": "Passerelle deCONZ Zigbee ({host})", "step": { "hassio_confirm": { - "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 la passerelle deCONZ fournie par l'add-on hass.io {addon} ?", - "title": "Passerelle deCONZ Zigbee via l'add-on Hass.io" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 la passerelle deCONZ fournie par le module compl\u00e9mentaire Hass.io {addon} ?", + "title": "Passerelle deCONZ Zigbee via le module compl\u00e9mentaire Hass.io" }, "link": { "description": "D\u00e9verrouillez votre passerelle deCONZ pour vous enregistrer avec Home Assistant. \n\n 1. Acc\u00e9dez aux param\u00e8tres avanc\u00e9s du syst\u00e8me deCONZ \n 2. Cliquez sur \"D\u00e9verrouiller la passerelle\"", diff --git a/homeassistant/components/deconz/translations/hu.json b/homeassistant/components/deconz/translations/hu.json index 4651a7c08e002e..61322087cbf673 100644 --- a/homeassistant/components/deconz/translations/hu.json +++ b/homeassistant/components/deconz/translations/hu.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "Az \u00e1tj\u00e1r\u00f3 konfigur\u00e1ci\u00f3s folyamata m\u00e1r folyamatban van.", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "no_bridges": "Nem tal\u00e1ltam deCONZ bridget", + "no_hardware_available": "Nincs deCONZ-hoz csatlakoztatott r\u00e1di\u00f3hardver", "not_deconz_bridge": "Nem egy deCONZ \u00e1tj\u00e1r\u00f3", "updated_instance": "A deCONZ-p\u00e9ld\u00e1ny \u00faj \u00e1llom\u00e1sc\u00edmmel friss\u00edtve" }, @@ -13,7 +14,7 @@ "flow_title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 ({host})", "step": { "hassio_confirm": { - "title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 a Hass.io kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel" + "title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 a Supervisor kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel" }, "link": { "description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot", @@ -51,30 +52,30 @@ }, "trigger_type": { "remote_awakened": "A k\u00e9sz\u00fcl\u00e9k fel\u00e9bredt", - "remote_button_double_press": "\" {subtype} \" gombra k\u00e9tszer kattintottak", - "remote_button_long_press": "A \" {subtype} \" gomb folyamatosan lenyomva", - "remote_button_long_release": "A \" {subtype} \" gomb hossz\u00fa megnyom\u00e1s ut\u00e1n elengedve", - "remote_button_quadruple_press": "\" {subtype} \" gombra n\u00e9gyszer kattintottak", - "remote_button_quintuple_press": "\" {subtype} \" gombra \u00f6tsz\u00f6r kattintottak", - "remote_button_rotated": "A gomb elforgatva: \" {subtype} \"", - "remote_button_rotation_stopped": "A (z) \" {subtype} \" gomb forg\u00e1sa le\u00e1llt", - "remote_button_short_press": "\" {subtype} \" gomb lenyomva", - "remote_button_short_release": "\"{alt\u00edpus}\" gomb elengedve", - "remote_button_triple_press": "\" {subtype} \" gombra h\u00e1romszor kattintottak", - "remote_double_tap": "Az \" {subtype} \" eszk\u00f6z dupla kattint\u00e1sa", + "remote_button_double_press": "\"{subtype}\" gombra k\u00e9tszer kattintottak", + "remote_button_long_press": "A \"{subtype}\" gomb folyamatosan lenyomva", + "remote_button_long_release": "A \"{subtype}\" gomb hossz\u00fa megnyom\u00e1s ut\u00e1n elengedve", + "remote_button_quadruple_press": "\"{subtype}\" gombra n\u00e9gyszer kattintottak", + "remote_button_quintuple_press": "\"{subtype}\" gombra \u00f6tsz\u00f6r kattintottak", + "remote_button_rotated": "A gomb elforgatva: \"{subtype}\"", + "remote_button_rotation_stopped": "A (z) \"{subtype}\" gomb forg\u00e1sa le\u00e1llt", + "remote_button_short_press": "\"{subtype}\" gomb lenyomva", + "remote_button_short_release": "\"{subtype}\" gomb elengedve", + "remote_button_triple_press": "\"{subtype}\" gombra h\u00e1romszor kattintottak", + "remote_double_tap": "Az \"{subtype}\" eszk\u00f6z dupla kattint\u00e1sa", "remote_double_tap_any_side": "A k\u00e9sz\u00fcl\u00e9k b\u00e1rmelyik oldal\u00e1n dupl\u00e1n koppint.", "remote_falling": "K\u00e9sz\u00fcl\u00e9k szabades\u00e9sben", "remote_flip_180_degrees": "180 fokkal megd\u00f6nt\u00f6tt eszk\u00f6z", "remote_flip_90_degrees": "90 fokkal megd\u00f6nt\u00f6tt eszk\u00f6z", "remote_gyro_activated": "A k\u00e9sz\u00fcl\u00e9k meg lett r\u00e1zva", - "remote_moved": "Az eszk\u00f6z a \" {subtype} \"-lal felfel\u00e9 mozgatva", + "remote_moved": "Az eszk\u00f6z a \"{subtype}\"-lal felfel\u00e9 mozgatva", "remote_moved_any_side": "A k\u00e9sz\u00fcl\u00e9k valamelyik oldal\u00e1val felfel\u00e9 mozogott", - "remote_rotate_from_side_1": "Az eszk\u00f6z a \"1. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_2": "Az eszk\u00f6z a \"2. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_3": "Az eszk\u00f6z a \"3. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_4": "Az eszk\u00f6z a \"4. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_5": "Az eszk\u00f6z a \"5. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", - "remote_rotate_from_side_6": "Az eszk\u00f6z a \"6. oldalr\u00f3l\" a \" {subtype} \" -ra fordult", + "remote_rotate_from_side_1": "Az eszk\u00f6z az \"1. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_2": "Az eszk\u00f6z a \"2. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_3": "Az eszk\u00f6z a \"3. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_4": "Az eszk\u00f6z a \"4. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_5": "Az eszk\u00f6z az \"5. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", + "remote_rotate_from_side_6": "Az eszk\u00f6z a \"6. oldalr\u00f3l\" a \"{subtype}\"-ra fordult", "remote_turned_clockwise": "A k\u00e9sz\u00fcl\u00e9k az \u00f3ramutat\u00f3 j\u00e1r\u00e1s\u00e1val megegyez\u0151en fordult", "remote_turned_counter_clockwise": "A k\u00e9sz\u00fcl\u00e9k az \u00f3ramutat\u00f3 j\u00e1r\u00e1s\u00e1val ellent\u00e9tes ir\u00e1nyban fordult" } diff --git a/homeassistant/components/deconz/translations/id.json b/homeassistant/components/deconz/translations/id.json index 0d46cf7c176b36..d7fb26f8d52081 100644 --- a/homeassistant/components/deconz/translations/id.json +++ b/homeassistant/components/deconz/translations/id.json @@ -2,15 +2,103 @@ "config": { "abort": { "already_configured": "Bridge sudah dikonfigurasi", - "no_bridges": "deCONZ bridges tidak ditemukan" + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "no_bridges": "deCONZ bridge tidak ditemukan", + "no_hardware_available": "Tidak ada perangkat keras radio yang terhubung ke deCONZ", + "not_deconz_bridge": "Bukan bridge deCONZ", + "updated_instance": "Instans deCONZ yang diperbarui dengan alamat host baru" }, "error": { "no_key": "Tidak bisa mendapatkan kunci API" }, + "flow_title": "Gateway Zigbee deCONZ ({host})", "step": { + "hassio_confirm": { + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke gateway deCONZ yang disediakan oleh add-on Supervisor {addon}?", + "title": "Gateway Zigbee deCONZ melalui add-on Supervisor" + }, "link": { - "description": "Buka gerbang deCONZ Anda untuk mendaftar dengan Home Assistant. \n\n 1. Pergi ke pengaturan sistem deCONZ \n 2. Tekan tombol \"Buka Kunci Gateway\"", - "title": "Tautan dengan deCONZ" + "description": "Buka gateway deCONZ Anda untuk mendaftarkan ke Home Assistant. \n\n1. Buka pengaturan sistem deCONZ \n2. Tekan tombol \"Authenticate app\"", + "title": "Tautkan dengan deCONZ" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Port" + } + }, + "user": { + "data": { + "host": "Pilih gateway deCONZ yang ditemukan" + } + } + } + }, + "device_automation": { + "trigger_subtype": { + "both_buttons": "Kedua tombol", + "bottom_buttons": "Tombol bawah", + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "close": "Tutup", + "dim_down": "Redupkan", + "dim_up": "Terangkan", + "left": "Kiri", + "open": "Buka", + "right": "Kanan", + "side_1": "Sisi 1", + "side_2": "Sisi 2", + "side_3": "Sisi 3", + "side_4": "Sisi 4", + "side_5": "Sisi 5", + "side_6": "Sisi 6", + "top_buttons": "Tombol atas", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "remote_awakened": "Perangkat terbangun", + "remote_button_double_press": "Tombol \"{subtype}\" diklik dua kali", + "remote_button_long_press": "Tombol \"{subtype}\" terus ditekan", + "remote_button_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_button_quadruple_press": "Tombol \"{subtype}\" diklik empat kali", + "remote_button_quintuple_press": "Tombol \"{subtype}\" diklik lima kali", + "remote_button_rotated": "Tombol diputar \"{subtype}\"", + "remote_button_rotated_fast": "Tombol diputar cepat \"{subtype}\"", + "remote_button_rotation_stopped": "Pemutaran tombol \"{subtype}\" berhenti", + "remote_button_short_press": "Tombol \"{subtype}\" ditekan", + "remote_button_short_release": "Tombol \"{subtype}\" dilepaskan", + "remote_button_triple_press": "Tombol \"{subtype}\" diklik tiga kali", + "remote_double_tap": "Perangkat \"{subtype}\" diketuk dua kali", + "remote_double_tap_any_side": "Perangkat diketuk dua kali di sisi mana pun", + "remote_falling": "Perangkat jatuh bebas", + "remote_flip_180_degrees": "Perangkat dibalik 180 derajat", + "remote_flip_90_degrees": "Perangkat dibalik 90 derajat", + "remote_gyro_activated": "Perangkat diguncangkan", + "remote_moved": "Perangkat dipindahkan dengan \"{subtype}\" ke atas", + "remote_moved_any_side": "Perangkat dipindahkan dengan sisi mana pun menghadap ke atas", + "remote_rotate_from_side_1": "Perangkat diputar dari \"sisi 1\" ke \"{subtype}\"", + "remote_rotate_from_side_2": "Perangkat diputar dari \"sisi 2\" ke \"{subtype}\"", + "remote_rotate_from_side_3": "Perangkat diputar dari \"sisi 3\" ke \"{subtype}\"", + "remote_rotate_from_side_4": "Perangkat diputar dari \"sisi 4\" ke \"{subtype}\"", + "remote_rotate_from_side_5": "Perangkat diputar dari \"sisi 5\" ke \"{subtype}\"", + "remote_rotate_from_side_6": "Perangkat diputar dari \"sisi 6\" ke \"{subtype}\"", + "remote_turned_clockwise": "Perangkat diputar searah jarum jam", + "remote_turned_counter_clockwise": "Perangkat diputar berlawanan arah jarum jam" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_clip_sensor": "Izinkan sensor CLIP deCONZ", + "allow_deconz_groups": "Izinkan grup lampu deCONZ", + "allow_new_devices": "Izinkan penambahan otomatis perangkat baru" + }, + "description": "Konfigurasikan visibilitas jenis perangkat deCONZ", + "title": "Opsi deCONZ" } } } diff --git a/homeassistant/components/deconz/translations/it.json b/homeassistant/components/deconz/translations/it.json index 00ecd316da761a..fd81ebad8cf6d9 100644 --- a/homeassistant/components/deconz/translations/it.json +++ b/homeassistant/components/deconz/translations/it.json @@ -15,7 +15,7 @@ "step": { "hassio_confirm": { "description": "Vuoi configurare Home Assistant per connettersi al gateway deCONZ fornito dal componente aggiuntivo di Hass.io: {addon}?", - "title": "Gateway Pigmee deCONZ tramite il componente aggiuntivo di Hass.io" + "title": "Gateway deCONZ Zigbee tramite il componente aggiuntivo di Hass.io" }, "link": { "description": "Sblocca il tuo gateway deCONZ per registrarti con Home Assistant.\n\n1. Vai a Impostazioni deCONZ -> Gateway -> Avanzate\n2. Premere il pulsante \"Autentica app\"", diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index bd8aef75dd6cdc..ade0f11b7599f2 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -4,6 +4,7 @@ "already_configured": "\ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", + "no_hardware_available": "deCONZ\uc5d0 \uc5f0\uacb0\ub41c \ubb34\uc120 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "not_deconz_bridge": "deCONZ \ube0c\ub9ac\uc9c0\uac00 \uc544\ub2d9\ub2c8\ub2e4", "updated_instance": "deCONZ \uc778\uc2a4\ud134\uc2a4\ub97c \uc0c8\ub85c\uc6b4 \ud638\uc2a4\ud2b8 \uc8fc\uc18c\ub85c \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4" }, @@ -13,11 +14,11 @@ "flow_title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774 ({host})", "step": { "hassio_confirm": { - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" }, "link": { - "description": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc5b8\ub77d\ud558\uc5ec Home Assistant \uc5d0 \uc5f0\uacb0\ud558\uae30.\n\n1. deCONZ \uc2dc\uc2a4\ud15c \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc138\uc694\n2. \"Authenticate app\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", + "description": "Home Assistant\uc5d0 \ub4f1\ub85d\ud558\ub824\uba74 deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc7a0\uae08 \ud574\uc81c\ud574\uc8fc\uc138\uc694.\n\n 1. deCONZ \uc124\uc815 -> \uac8c\uc774\ud2b8\uc6e8\uc774 -> \uace0\uae09\uc73c\ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694\n 2. \"\uc571 \uc778\uc99d\ud558\uae30\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", "title": "deCONZ \uc5f0\uacb0\ud558\uae30" }, "manual_input": { @@ -58,33 +59,34 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "remote_awakened": "\uae30\uae30 \uc808\uc804 \ubaa8\ub4dc \ud574\uc81c\ub420 \ub54c", - "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_rotated": "\"{subtype}\" \ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\ub420 \ub54c", - "remote_button_rotation_stopped": "\"{subtype}\" \ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\uc744 \uba48\ucd9c \ub54c", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \ub354\ube14 \ud0ed \ub420 \ub54c", - "remote_double_tap_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \ub354\ube14 \ud0ed \ub420 \ub54c", + "remote_awakened": "\uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub418\uc5c8\uc744 \ub54c", + "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub838\uc744 \ub54c", + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_rotated": "\"{subtype}\"(\uc73c)\ub85c \ubc84\ud2bc\uc774 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_button_rotated_fast": "\"{subtype}\"(\uc73c)\ub85c \ubc84\ud2bc\uc774 \ube60\ub974\uac8c \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_button_rotation_stopped": "\"{subtype}\"(\uc73c)\ub85c \ubc84\ud2bc\ud68c\uc804\uc774 \uba48\ucd94\uc5c8\uc744 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\"\uc774(\uac00) \ub354\ube14 \ud0ed \ub418\uc5c8\uc744 \ub54c", + "remote_double_tap_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \ub354\ube14 \ud0ed \ub418\uc5c8\uc744 \ub54c", "remote_falling": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc9c8 \ub54c", - "remote_flip_180_degrees": "\uae30\uae30\uac00 180\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", - "remote_flip_90_degrees": "\uae30\uae30\uac00 90\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", - "remote_gyro_activated": "\uae30\uae30\uac00 \ud754\ub4e4\ub9b4 \ub54c", - "remote_moved": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc77c \ub54c", - "remote_moved_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc77c \ub54c", - "remote_rotate_from_side_1": "\"\uba74 1\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_2": "\"\uba74 2\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_3": "\"\uba74 3\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_4": "\"\uba74 4\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_5": "\"\uba74 5\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_rotate_from_side_6": "\"\uba74 6\" \uc5d0\uc11c \"{subtype}\" \ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_turned_clockwise": "\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "remote_turned_counter_clockwise": "\ubc18\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c" + "remote_flip_180_degrees": "\uae30\uae30\uac00 180\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc84c\uc744 \ub54c", + "remote_flip_90_degrees": "\uae30\uae30\uac00 90\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc84c\uc744 \ub54c", + "remote_gyro_activated": "\uae30\uae30\uac00 \ud754\ub4e4\ub838\uc744 \ub54c", + "remote_moved": "\uae30\uae30\uc758 \"{subtype}\"\uc774(\uac00) \uc704\ub85c \ud5a5\ud55c \ucc44\ub85c \uc6c0\uc9c1\uc600\uc744 \ub54c", + "remote_moved_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \uc704\ub85c \ud5a5\ud55c\ucc44\ub85c \uc6c0\uc9c1\uc600\uc744 \ub54c", + "remote_rotate_from_side_1": "\"\uba74 1\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_2": "\"\uba74 2\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_3": "\"\uba74 3\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_4": "\"\uba74 4\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_5": "\"\uba74 5\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_rotate_from_side_6": "\"\uba74 6\" \uc5d0\uc11c \"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_turned_clockwise": "\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "remote_turned_counter_clockwise": "\ubc18\uc2dc\uacc4 \ubc29\ud5a5\uc73c\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c" } }, "options": { @@ -92,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "deCONZ CLIP \uc13c\uc11c \ud5c8\uc6a9", - "allow_deconz_groups": "deCONZ \ub77c\uc774\ud2b8 \uadf8\ub8f9 \ud5c8\uc6a9" + "allow_deconz_groups": "deCONZ \ub77c\uc774\ud2b8 \uadf8\ub8f9 \ud5c8\uc6a9", + "allow_new_devices": "\uc0c8\ub85c\uc6b4 \uae30\uae30\uc758 \uc790\ub3d9 \ucd94\uac00 \ud5c8\uc6a9\ud558\uae30" }, "description": "deCONZ \uae30\uae30 \uc720\ud615\uc758 \ud45c\uc2dc \uc5ec\ubd80 \uad6c\uc131", "title": "deCONZ \uc635\uc158" diff --git a/homeassistant/components/deconz/translations/lb.json b/homeassistant/components/deconz/translations/lb.json index bb556842194c84..06b8dbacdc53b1 100644 --- a/homeassistant/components/deconz/translations/lb.json +++ b/homeassistant/components/deconz/translations/lb.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mat der deCONZ gateway ze verbannen d\u00e9i vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", - "title": "deCONZ Zigbee gateway via Hass.io add-on" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mat der deCONZ gateway ze verbannen d\u00e9i vum Supervisor add-on {addon} bereet gestallt g\u00ebtt?", + "title": "deCONZ Zigbee gateway via Supervisor add-on" }, "link": { "description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen", diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 2d43ca63bfef62..37833352f91d3e 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee gateway ( {host} )", "step": { "hassio_confirm": { - "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de hass.io add-on {addon}?", - "title": "deCONZ Zigbee Gateway via Hass.io add-on" + "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de Supervisor add-on {addon}?", + "title": "deCONZ Zigbee Gateway via Supervisor add-on" }, "link": { "description": "Ontgrendel je deCONZ gateway om te registreren met Home Assistant.\n\n1. Ga naar deCONZ systeeminstellingen (Instellingen -> Gateway -> Geavanceerd)\n2. Druk op de knop \"Gateway ontgrendelen\"", diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index c1435dbb186a5e..4dcd693b5f46c3 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -14,7 +14,7 @@ "flow_title": "", "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegg {addon} ?", + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til deCONZ gateway levert av Hass.io-tillegget {addon} ?", "title": "deCONZ Zigbee gateway via Hass.io-tillegg" }, "link": { diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index a13c94d82d7939..450fa7707d15a7 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -12,8 +12,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para conectar-se ao gateway deCONZ fornecido pelo add-on hass.io {addon} ?", - "title": "Gateway deCONZ Zigbee via add-on Hass.io" + "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" }, "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\"", diff --git a/homeassistant/components/deconz/translations/pt.json b/homeassistant/components/deconz/translations/pt.json index 725ce07a1b65c8..cc8b4ab19f27d7 100644 --- a/homeassistant/components/deconz/translations/pt.json +++ b/homeassistant/components/deconz/translations/pt.json @@ -11,8 +11,8 @@ }, "step": { "hassio_confirm": { - "description": "Deseja configurar o Home Assistant para se conectar ao gateway deCONZ fornecido pelo addon Hass.io {addon} ?", - "title": "Gateway Zigbee deCONZ via addon Hass.io" + "description": "Deseja configurar o Home Assistant para se conectar ao gateway deCONZ fornecido pelo addon Supervisor {addon} ?", + "title": "Gateway Zigbee deCONZ via addon Supervisor" }, "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\"", diff --git a/homeassistant/components/deconz/translations/sl.json b/homeassistant/components/deconz/translations/sl.json index cf5600c20e4bf6..9e8ed42c07ee66 100644 --- a/homeassistant/components/deconz/translations/sl.json +++ b/homeassistant/components/deconz/translations/sl.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee prehod ({host})", "step": { "hassio_confirm": { - "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s prehodom deCONZ, ki ga ponuja dodatek Hass.io {addon} ?", - "title": "deCONZ Zigbee prehod preko dodatka Hass.io" + "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s prehodom deCONZ, ki ga ponuja dodatek Supervisor {addon} ?", + "title": "deCONZ Zigbee prehod preko dodatka Supervisor" }, "link": { "description": "Odklenite va\u0161 deCONZ gateway za registracijo s Home Assistant-om. \n1. Pojdite v deCONZ sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"", diff --git a/homeassistant/components/deconz/translations/sv.json b/homeassistant/components/deconz/translations/sv.json index 4d709a43af1808..c9814734af09f6 100644 --- a/homeassistant/components/deconz/translations/sv.json +++ b/homeassistant/components/deconz/translations/sv.json @@ -13,8 +13,8 @@ "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { - "description": "Vill du konfigurera Home Assistant att ansluta till den deCONZ-gateway som tillhandah\u00e5lls av Hass.io-till\u00e4gget {addon}?", - "title": "deCONZ Zigbee gateway via Hass.io till\u00e4gg" + "description": "Vill du konfigurera Home Assistant att ansluta till den deCONZ-gateway som tillhandah\u00e5lls av Supervisor-till\u00e4gget {addon}?", + "title": "deCONZ Zigbee gateway via Supervisor till\u00e4gg" }, "link": { "description": "L\u00e5s upp din deCONZ-gateway f\u00f6r att registrera dig med Home Assistant. \n\n 1. G\u00e5 till deCONZ-systeminst\u00e4llningarna \n 2. Tryck p\u00e5 \"L\u00e5s upp gateway\"-knappen", diff --git a/homeassistant/components/deconz/translations/uk.json b/homeassistant/components/deconz/translations/uk.json index b5de362a731ee3..3b09a517385393 100644 --- a/homeassistant/components/deconz/translations/uk.json +++ b/homeassistant/components/deconz/translations/uk.json @@ -14,8 +14,8 @@ "flow_title": "\u0428\u043b\u044e\u0437 Zigbee deCONZ ({host})", "step": { "hassio_confirm": { - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" }, "link": { "description": "\u0420\u043e\u0437\u0431\u043b\u043e\u043a\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u0457 \u0432 Home Assistant: \n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0438 deCONZ - > Gateway - > Advanced.\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", diff --git a/homeassistant/components/deconz/translations/zh-Hans.json b/homeassistant/components/deconz/translations/zh-Hans.json index a85ed6d72ca190..dfe8209fa1c93b 100644 --- a/homeassistant/components/deconz/translations/zh-Hans.json +++ b/homeassistant/components/deconz/translations/zh-Hans.json @@ -21,11 +21,11 @@ "side_3": "\u7b2c 3 \u9762", "side_4": "\u7b2c 4 \u9762", "side_5": "\u7b2c 5 \u9762", - "side_6": "\u7b2c 6 \u9762", - "turn_off": "\u5173\u95ed" + "side_6": "\u7b2c 6 \u9762" }, "trigger_type": { "remote_awakened": "\u8bbe\u5907\u5524\u9192", + "remote_button_rotation_stopped": "\u6309\u94ae \"{subtype}\" \u505c\u6b62\u65cb\u8f6c", "remote_double_tap": "\u8bbe\u5907\u7684\u201c{subtype}\u201d\u88ab\u8f7b\u6572\u4e24\u6b21", "remote_falling": "\u8bbe\u5907\u81ea\u7531\u843d\u4f53", "remote_gyro_activated": "\u8bbe\u5907\u6447\u6643", diff --git a/homeassistant/components/deconz/translations/zh-Hant.json b/homeassistant/components/deconz/translations/zh-Hant.json index 335aa73a67cfb4..c17d2038127a6d 100644 --- a/homeassistant/components/deconz/translations/zh-Hant.json +++ b/homeassistant/components/deconz/translations/zh-Hant.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee \u9598\u9053\u5668\uff08{host}\uff09", "step": { "hassio_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u6574\u5408 {addon} \u4e4b deCONZ \u9598\u9053\u5668\uff1f", - "title": "\u900f\u904e Hass.io \u9644\u52a0\u7d44\u4ef6 deCONZ Zigbee \u9598\u9053\u5668" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u5143\u4ef6 {addon} \u4e4b deCONZ \u9598\u9053\u5668\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 deCONZ Zigbee \u9598\u9053\u5668" }, "link": { "description": "\u89e3\u9664 deCONZ \u9598\u9053\u5668\u9396\u5b9a\uff0c\u4ee5\u65bc Home Assistant \u9032\u884c\u8a3b\u518a\u3002\n\n1. \u9032\u5165 deCONZ \u7cfb\u7d71\u8a2d\u5b9a -> \u9598\u9053\u5668 -> \u9032\u968e\u8a2d\u5b9a\n2. \u6309\u4e0b\u300c\u8a8d\u8b49\u7a0b\u5f0f\uff08Authenticate app\uff09\u300d\u6309\u9215", diff --git a/homeassistant/components/demo/translations/id.json b/homeassistant/components/demo/translations/id.json new file mode 100644 index 00000000000000..8adbeb3e3c4ed8 --- /dev/null +++ b/homeassistant/components/demo/translations/id.json @@ -0,0 +1,21 @@ +{ + "options": { + "step": { + "options_1": { + "data": { + "bool": "Boolean opsional", + "constant": "Konstanta", + "int": "Input numerik" + } + }, + "options_2": { + "data": { + "multi": "Pilihan ganda", + "select": "Pilih salah satu opsi", + "string": "Nilai string" + } + } + } + }, + "title": "Demo" +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json index f52e6303091ad1..e95aeb10b1717d 100644 --- a/homeassistant/components/denonavr/translations/de.json +++ b/homeassistant/components/denonavr/translations/de.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt" + "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", + "cannot_connect": "Verbindung fehlgeschlagen. Bitte versuchen Sie es noch einmal. Trennen Sie ggf. Strom- und Ethernetkabel und verbinden Sie diese erneut." }, "step": { "select": { diff --git a/homeassistant/components/denonavr/translations/hu.json b/homeassistant/components/denonavr/translations/hu.json index aa56cb477417ab..41a1910bd56d01 100644 --- a/homeassistant/components/denonavr/translations/hu.json +++ b/homeassistant/components/denonavr/translations/hu.json @@ -2,10 +2,18 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Nem siker\u00fclt csatlakozni, k\u00e9rj\u00fck, pr\u00f3b\u00e1lja \u00fajra. A h\u00e1l\u00f3zati \u00e9s Ethernet k\u00e1belek kih\u00faz\u00e1sa \u00e9s \u00fajracsatlakoztat\u00e1sa seg\u00edthet" }, "error": { "discovery_error": "Nem siker\u00fclt megtal\u00e1lni a Denon AVR h\u00e1l\u00f3zati er\u0151s\u00edt\u0151t" + }, + "step": { + "user": { + "data": { + "host": "IP c\u00edm" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/id.json b/homeassistant/components/denonavr/translations/id.json new file mode 100644 index 00000000000000..d78f547ef35930 --- /dev/null +++ b/homeassistant/components/denonavr/translations/id.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal menyambungkan, coba lagi. Pemutusan sambungan daya listrik dan kabel ethernet lalu menyambungkannya kembali mungkin dapat membantu", + "not_denonavr_manufacturer": "Bukan Network Receiver Denon AVR, pabrikan yang ditemukan tidak sesuai", + "not_denonavr_missing": "Bukan Network Receiver AVR Denon, informasi penemuan tidak lengkap" + }, + "error": { + "discovery_error": "Gagal menemukan Network Receiver AVR Denon" + }, + "flow_title": "Network Receiver Denon AVR: {name}", + "step": { + "confirm": { + "description": "Konfirmasikan penambahan Receiver", + "title": "Network Receiver Denon AVR" + }, + "select": { + "data": { + "select_host": "Alamat IP Receiver" + }, + "description": "Jalankan penyiapan lagi jika ingin menghubungkan Receiver lainnya", + "title": "Pilih Receiver yang ingin dihubungkan" + }, + "user": { + "data": { + "host": "Alamat IP" + }, + "description": "Hubungkan ke Receiver Anda. Jika alamat IP tidak ditentukan, penemuan otomatis akan digunakan", + "title": "Network Receiver Denon AVR" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_all_sources": "Tampilkan semua sumber", + "zone2": "Siapkan Zona 2", + "zone3": "Siapkan Zona 3" + }, + "description": "Tentukan pengaturan opsional", + "title": "Network Receiver Denon AVR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/ko.json b/homeassistant/components/denonavr/translations/ko.json index 71562ac53a4ec3..c0121a1e2ca885 100644 --- a/homeassistant/components/denonavr/translations/ko.json +++ b/homeassistant/components/denonavr/translations/ko.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc8fc\uc804\uc6d0 \ubc0f \uc774\ub354\ub137 \ucf00\uc774\ube14\uc744 \ubd84\ub9ac\ud55c \ud6c4 \ub2e4\uc2dc \uc5f0\uacb0\ud558\uba74 \ub3c4\uc6c0\uc774 \ub420 \uc218 \uc788\uc2b5\ub2c8\ub2e4", "not_denonavr_manufacturer": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4. \ubc1c\uacac\ub41c \uc81c\uc870\uc0ac\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "not_denonavr_missing": "Denon AVR \ub124\ud2b8\uc6cc\ud06c \ub9ac\uc2dc\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4. \uac80\uc0c9 \uc815\ubcf4\uac00 \uc644\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index 6a00e03765fb3d..d96e81f232217a 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratiestroom is al aan de gang", + "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen" }, "flow_title": "Denon AVR Network Receiver: {name}", "step": { @@ -27,6 +28,11 @@ "options": { "step": { "init": { + "data": { + "show_all_sources": "Toon alle bronnen", + "zone2": "Stel Zone 2 in", + "zone3": "Stel Zone 3 in" + }, "title": "Denon AVR Network Receivers" } } diff --git a/homeassistant/components/device_tracker/translations/id.json b/homeassistant/components/device_tracker/translations/id.json index 99baa5e1a76c54..be5c7e932cea2e 100644 --- a/homeassistant/components/device_tracker/translations/id.json +++ b/homeassistant/components/device_tracker/translations/id.json @@ -1,7 +1,17 @@ { + "device_automation": { + "condition_type": { + "is_home": "{entity_name} ada di rumah", + "is_not_home": "{entity_name} tidak ada di rumah" + }, + "trigger_type": { + "enters": "{entity_name} memasuki zona", + "leaves": "{entity_name} meninggalkan zona" + } + }, "state": { "_": { - "home": "Rumah", + "home": "Di Rumah", "not_home": "Keluar" } }, diff --git a/homeassistant/components/device_tracker/translations/ko.json b/homeassistant/components/device_tracker/translations/ko.json index e3e72d49c89e01..538db691b4ff21 100644 --- a/homeassistant/components/device_tracker/translations/ko.json +++ b/homeassistant/components/device_tracker/translations/ko.json @@ -1,8 +1,12 @@ { "device_automation": { "condition_type": { - "is_home": "{entity_name} \uc774(\uac00) \uc9d1\uc5d0 \uc788\uc73c\uba74", - "is_not_home": "{entity_name} \uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74" + "is_home": "{entity_name}\uc774(\uac00) \uc9d1\uc5d0 \uc788\uc73c\uba74", + "is_not_home": "{entity_name}\uc774(\uac00) \uc678\ucd9c \uc911\uc774\uba74" + }, + "trigger_type": { + "enters": "{entity_name}\uc774(\uac00) \uc9c0\uc5ed\uc5d0 \ub4e4\uc5b4\uac08 \ub54c", + "leaves": "{entity_name}\uc774(\uac00) \uc9c0\uc5ed\uc5d0\uc11c \ub098\uc62c \ub54c" } }, "state": { diff --git a/homeassistant/components/device_tracker/translations/zh-Hans.json b/homeassistant/components/device_tracker/translations/zh-Hans.json index c019a3dcda854d..5d56de9b855633 100644 --- a/homeassistant/components/device_tracker/translations/zh-Hans.json +++ b/homeassistant/components/device_tracker/translations/zh-Hans.json @@ -5,8 +5,8 @@ "is_not_home": "{entity_name} \u4e0d\u5728\u5bb6" }, "trigger_type": { - "enters": "{entity_name} \u8fdb\u5165\u533a\u57df", - "leaves": "{entity_name} \u79bb\u5f00\u533a\u57df" + "enters": "{entity_name} \u8fdb\u5165\u6307\u5b9a\u533a\u57df", + "leaves": "{entity_name} \u79bb\u5f00\u6307\u5b9a\u533a\u57df" } }, "state": { diff --git a/homeassistant/components/devolo_home_control/translations/he.json b/homeassistant/components/devolo_home_control/translations/he.json index 3007c0e968c1dc..ac90b3264eab33 100644 --- a/homeassistant/components/devolo_home_control/translations/he.json +++ b/homeassistant/components/devolo_home_control/translations/he.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" } } } diff --git a/homeassistant/components/devolo_home_control/translations/hu.json b/homeassistant/components/devolo_home_control/translations/hu.json index ff2c2fc87b5159..45b07f0adcb502 100644 --- a/homeassistant/components/devolo_home_control/translations/hu.json +++ b/homeassistant/components/devolo_home_control/translations/hu.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { - "password": "Jelsz\u00f3" + "home_control_url": "Home Control URL", + "mydevolo_url": "mydevolo URL", + "password": "Jelsz\u00f3", + "username": "E-mail / devolo ID" } } } diff --git a/homeassistant/components/devolo_home_control/translations/id.json b/homeassistant/components/devolo_home_control/translations/id.json new file mode 100644 index 00000000000000..8b7ce0171d557b --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "home_control_url": "URL Home Control", + "mydevolo_url": "URL mydevolo", + "password": "Kata Sandi", + "username": "Email/ID devolo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/hu.json b/homeassistant/components/dexcom/translations/hu.json index 7a67a978ae15f7..45f38b22a84a0f 100644 --- a/homeassistant/components/dexcom/translations/hu.json +++ b/homeassistant/components/dexcom/translations/hu.json @@ -4,7 +4,27 @@ "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "server": "Szerver", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "M\u00e9rt\u00e9kegys\u00e9g" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/id.json b/homeassistant/components/dexcom/translations/id.json new file mode 100644 index 00000000000000..2802216e782d76 --- /dev/null +++ b/homeassistant/components/dexcom/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "server": "Server", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial Dexcom Share", + "title": "Siapkan integrasi Dexcom" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "Satuan pengukuran" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/nl.json b/homeassistant/components/dexcom/translations/nl.json index 1dd597d28b4f49..6c5027efaaed94 100644 --- a/homeassistant/components/dexcom/translations/nl.json +++ b/homeassistant/components/dexcom/translations/nl.json @@ -12,9 +12,19 @@ "user": { "data": { "password": "Wachtwoord", + "server": "Server", "username": "Gebruikersnaam" } } } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "Meeteenheid" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/hu.json b/homeassistant/components/dialogflow/translations/hu.json index 04427a1efed1c6..17f38b0262f6e0 100644 --- a/homeassistant/components/dialogflow/translations/hu.json +++ b/homeassistant/components/dialogflow/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Dialogflow webhook integr\u00e1ci\u00f3j\u00e1t] ( {dialogflow_url} ). \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Dialogflow webhook integr\u00e1ci\u00f3j\u00e1t]({dialogflow_url}). \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t]({docs_url})." }, "step": { "user": { diff --git a/homeassistant/components/dialogflow/translations/id.json b/homeassistant/components/dialogflow/translations/id.json new file mode 100644 index 00000000000000..046a04b1dc49bf --- /dev/null +++ b/homeassistant/components/dialogflow/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [integrasi webhook dengan Dialogflow]({dialogflow_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Dialogflow?", + "title": "Siapkan Dialogflow Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/ko.json b/homeassistant/components/dialogflow/translations/ko.json index 2b1be9657b403e..2cd6d208f0089a 100644 --- a/homeassistant/components/dialogflow/translations/ko.json +++ b/homeassistant/components/dialogflow/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/directv/translations/hu.json b/homeassistant/components/directv/translations/hu.json index 5d8fc929b920f2..0309eb358814c0 100644 --- a/homeassistant/components/directv/translations/hu.json +++ b/homeassistant/components/directv/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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" diff --git a/homeassistant/components/directv/translations/id.json b/homeassistant/components/directv/translations/id.json new file mode 100644 index 00000000000000..74f778d6cee28b --- /dev/null +++ b/homeassistant/components/directv/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "Ingin menyiapkan {name}?" + }, + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/ko.json b/homeassistant/components/directv/translations/ko.json index f2526418d08292..ecbde981160fc3 100644 --- a/homeassistant/components/directv/translations/ko.json +++ b/homeassistant/components/directv/translations/ko.json @@ -10,7 +10,7 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { diff --git a/homeassistant/components/directv/translations/nl.json b/homeassistant/components/directv/translations/nl.json index 2024368daf6d91..957095712342a2 100644 --- a/homeassistant/components/directv/translations/nl.json +++ b/homeassistant/components/directv/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "DirecTV-ontvanger is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "DirecTV: {name}", "step": { @@ -18,7 +18,7 @@ }, "user": { "data": { - "host": "Host- of IP-adres" + "host": "Host" } } } diff --git a/homeassistant/components/doorbird/translations/hu.json b/homeassistant/components/doorbird/translations/hu.json index 618368433acc67..3f74783b7ac781 100644 --- a/homeassistant/components/doorbird/translations/hu.json +++ b/homeassistant/components/doorbird/translations/hu.json @@ -1,14 +1,19 @@ { "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", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "DoorBird {name} ({host})", "step": { "user": { "data": { "host": "Hoszt", + "name": "Eszk\u00f6z neve", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/doorbird/translations/id.json b/homeassistant/components/doorbird/translations/id.json new file mode 100644 index 00000000000000..f708780ce311f6 --- /dev/null +++ b/homeassistant/components/doorbird/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "link_local_address": "Tautan alamat lokal tidak didukung", + "not_doorbird_device": "Perangkat ini bukan DoorBird" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "DoorBird {name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama Perangkat", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "Daftar event yang dipisahkan koma." + }, + "description": "Tambahkan nama event yang dipisahkan koma untuk setiap event yang ingin dilacak. Setelah memasukkannya di sini, gunakan aplikasi DoorBird untuk menetapkannya ke event tertentu. Baca dokumentasi di https://www.home-assistant.io/integrations/doorbird/#events. Contoh: somebody_pressed_the_button, motion" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/ko.json b/homeassistant/components/doorbird/translations/ko.json index 819b3b51d1010a..85d00317c2d63b 100644 --- a/homeassistant/components/doorbird/translations/ko.json +++ b/homeassistant/components/doorbird/translations/ko.json @@ -19,7 +19,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "DoorBird \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "DoorBird\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -29,7 +29,7 @@ "data": { "events": "\uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \ubaa9\ub85d." }, - "description": "\ucd94\uc801\ud558\ub824\ub294 \uac01 \uc774\ubca4\ud2b8\uc5d0 \ub300\ud574 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \uc774\ub984\uc744 \ucd94\uac00\ud574\uc8fc\uc138\uc694. \uc5ec\uae30\uc5d0 \uc785\ub825\ud55c \ud6c4 DoorBird \uc571\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud2b9\uc815 \uc774\ubca4\ud2b8\uc5d0 \ud560\ub2f9\ud574\uc8fc\uc138\uc694. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 https://www.home-assistant.io/integrations/doorbird/#event \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694. \uc608: someone_pressed_the_button, motion" + "description": "\ucd94\uc801\ud558\ub824\ub294 \uac01 \uc774\ubca4\ud2b8\uc5d0 \ub300\ud574 \uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \uc774\ubca4\ud2b8 \uc774\ub984\uc744 \ucd94\uac00\ud574\uc8fc\uc138\uc694. \uc5ec\uae30\uc5d0 \uc785\ub825\ud55c \ud6c4 DoorBird \uc571\uc744 \uc0ac\uc6a9\ud558\uc5ec \ud2b9\uc815 \uc774\ubca4\ud2b8\uc5d0 \ud560\ub2f9\ud574\uc8fc\uc138\uc694. \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 https://www.home-assistant.io/integrations/doorbird/#event \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694. \uc608: somebody_pressed_the_button, motion" } } } diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json index 625367484b0ac0..1c43ee2d9c28c5 100644 --- a/homeassistant/components/doorbird/translations/nl.json +++ b/homeassistant/components/doorbird/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Deze DoorBird is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "link_local_address": "Link-lokale adressen worden niet ondersteund", "not_doorbird_device": "Dit apparaat is geen DoorBird" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -14,7 +14,7 @@ "step": { "user": { "data": { - "host": "Host (IP-adres)", + "host": "Host", "name": "Apparaatnaam", "password": "Wachtwoord", "username": "Gebruikersnaam" diff --git a/homeassistant/components/dsmr/translations/fr.json b/homeassistant/components/dsmr/translations/fr.json index cb08a7865b33a4..d156aee8ca0aab 100644 --- a/homeassistant/components/dsmr/translations/fr.json +++ b/homeassistant/components/dsmr/translations/fr.json @@ -3,6 +3,10 @@ "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, + "error": { + "one": "Vide", + "other": "Vide" + }, "step": { "one": "", "other": "Autre" diff --git a/homeassistant/components/dsmr/translations/id.json b/homeassistant/components/dsmr/translations/id.json new file mode 100644 index 00000000000000..fd8299d61eddf8 --- /dev/null +++ b/homeassistant/components/dsmr/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "Interval minimum pembaruan entitas (dalam detik)" + }, + "title": "Opsi DSMR" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/ko.json b/homeassistant/components/dsmr/translations/ko.json index 17dee71d640a46..73837e1b4f2843 100644 --- a/homeassistant/components/dsmr/translations/ko.json +++ b/homeassistant/components/dsmr/translations/ko.json @@ -3,5 +3,15 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" } + }, + "options": { + "step": { + "init": { + "data": { + "time_between_update": "\uad6c\uc131\uc694\uc18c \uc5c6\ub370\uc774\ud2b8 \uac04 \ucd5c\uc18c \uc2dc\uac04 (\ucd08)" + }, + "title": "DSMR \uc635\uc158" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/hu.json b/homeassistant/components/dunehd/translations/hu.json index 44b4442dc31c9e..cf0b593d5460d0 100644 --- a/homeassistant/components/dunehd/translations/hu.json +++ b/homeassistant/components/dunehd/translations/hu.json @@ -4,7 +4,17 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "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", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + }, + "title": "Dune HD" + } } } } \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/id.json b/homeassistant/components/dunehd/translations/id.json new file mode 100644 index 00000000000000..25cb96bedea610 --- /dev/null +++ b/homeassistant/components/dunehd/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid" + }, + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Siapkan integrasi Dune HD. Jika Anda memiliki masalah dengan konfigurasi, buka: https://www.home-assistant.io/integrations/dunehd \n\nPastikan pemutar Anda dinyalakan.", + "title": "Dune HD" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/ko.json b/homeassistant/components/dunehd/translations/ko.json index 45a59b4d75b068..5c7feb27f7eda3 100644 --- a/homeassistant/components/dunehd/translations/ko.json +++ b/homeassistant/components/dunehd/translations/ko.json @@ -6,7 +6,7 @@ "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/dunehd/translations/nl.json b/homeassistant/components/dunehd/translations/nl.json index c8e16770db2407..bb3dd7def476e3 100644 --- a/homeassistant/components/dunehd/translations/nl.json +++ b/homeassistant/components/dunehd/translations/nl.json @@ -12,7 +12,9 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Stel Dune HD integratie in. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/dunehd \n\nZorg ervoor dat uw speler is ingeschakeld.", + "title": "Dune HD" } } } diff --git a/homeassistant/components/eafm/translations/id.json b/homeassistant/components/eafm/translations/id.json new file mode 100644 index 00000000000000..656c9eb51ea404 --- /dev/null +++ b/homeassistant/components/eafm/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_stations": "Tidak ditemukan stasiun pemantau banjir." + }, + "step": { + "user": { + "data": { + "station": "Stasiun" + }, + "description": "Pilih stasiun yang ingin dipantau", + "title": "Lacak stasiun pemantauan banjir" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/ko.json b/homeassistant/components/eafm/translations/ko.json index 36af97756ee705..5c6c0a8aa33c04 100644 --- a/homeassistant/components/eafm/translations/ko.json +++ b/homeassistant/components/eafm/translations/ko.json @@ -2,15 +2,15 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "no_stations": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158\uc774 \uc5c6\uc2b5\ub2c8\ub2e4." + "no_stations": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { "user": { "data": { "station": "\uc2a4\ud14c\uc774\uc158" }, - "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uc2a4\ud14c\uc774\uc158 \uc120\ud0dd", - "title": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158 \ucd94\uc801" + "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uc2a4\ud14c\uc774\uc158 \uc120\ud0dd\ud558\uae30", + "title": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158 \ucd94\uc801\ud558\uae30" } } } diff --git a/homeassistant/components/eafm/translations/nl.json b/homeassistant/components/eafm/translations/nl.json index 8b2702b6708dcb..0973f9ebd1aa6b 100644 --- a/homeassistant/components/eafm/translations/nl.json +++ b/homeassistant/components/eafm/translations/nl.json @@ -2,6 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd" + }, + "step": { + "user": { + "data": { + "station": "Station" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/ebusd/translations/id.json b/homeassistant/components/ebusd/translations/id.json new file mode 100644 index 00000000000000..6b2aaa6e789354 --- /dev/null +++ b/homeassistant/components/ebusd/translations/id.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "Siang", + "night": "Malam" + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/ca.json b/homeassistant/components/ecobee/translations/ca.json index 46d42d0774bbae..99b3f234df23ea 100644 --- a/homeassistant/components/ecobee/translations/ca.json +++ b/homeassistant/components/ecobee/translations/ca.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Autoritza aquesta aplicaci\u00f3 a https://www.ecobee.com/consumerportal/index.html amb el codi pin seg\u00fcent: \n\n {pin} \n \n A continuaci\u00f3, prem Enviar.", + "description": "Autoritza aquesta aplicaci\u00f3 a https://www.ecobee.com/consumerportal/index.html amb el codi PIN: \n\n {pin} \n \n A continuaci\u00f3, prem Envia.", "title": "Autoritzaci\u00f3 de l'aplicaci\u00f3 a ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/en.json b/homeassistant/components/ecobee/translations/en.json index 1dfcc2f6a191f0..8a8beeb63c445f 100644 --- a/homeassistant/components/ecobee/translations/en.json +++ b/homeassistant/components/ecobee/translations/en.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with pin code:\n\n{pin}\n\nThen, press Submit.", + "description": "Please authorize this app at https://www.ecobee.com/consumerportal/index.html with PIN code:\n\n{pin}\n\nThen, press Submit.", "title": "Authorize app on ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/et.json b/homeassistant/components/ecobee/translations/et.json index 46c332a5356803..452cbd578fab84 100644 --- a/homeassistant/components/ecobee/translations/et.json +++ b/homeassistant/components/ecobee/translations/et.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Tuvasta see rakendus aadressil https://www.ecobee.com/consumerportal/index.html koos PIN-koodiga:\n\n {pin}\n\n Seej\u00e4rel vajuta Esita.", + "description": "kinnita see rakendus aadressil https://www.ecobee.com/consumerportal/index.html PIN koodiga:\n\n {pin}\n\n Seej\u00e4rel vajuta Esita.", "title": "Rakenduse tuvastamine saidil ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/fr.json b/homeassistant/components/ecobee/translations/fr.json index cfb307053da59d..acbc909d881144 100644 --- a/homeassistant/components/ecobee/translations/fr.json +++ b/homeassistant/components/ecobee/translations/fr.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Veuillez autoriser cette application \u00e0 https://www.ecobee.com/consumerportal/index.html avec un code PIN :\n\n{pin}\n\nEnsuite, appuyez sur Soumettre.", + "description": "Veuillez autoriser cette application \u00e0 https://www.ecobee.com/consumerportal/index.html avec le code NIP :\n\n{pin}\n\nEnsuite, appuyez sur Soumettre.", "title": "Autoriser l'application sur ecobee.com" }, "user": { diff --git a/homeassistant/components/ecobee/translations/hu.json b/homeassistant/components/ecobee/translations/hu.json index bd620bc9685030..a91478ff03813b 100644 --- a/homeassistant/components/ecobee/translations/hu.json +++ b/homeassistant/components/ecobee/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "pin_request_failed": "Hiba t\u00f6rt\u00e9nt a PIN-k\u00f3d ecobee-t\u0151l t\u00f6rt\u00e9n\u0151 k\u00e9r\u00e9sekor; ellen\u0151rizze, hogy az API-kulcs helyes-e.", "token_request_failed": "Hiba t\u00f6rt\u00e9nt a tokenek ecobee-t\u0151l t\u00f6rt\u00e9n\u0151 ig\u00e9nyl\u00e9se k\u00f6zben; pr\u00f3b\u00e1lkozzon \u00fajra." diff --git a/homeassistant/components/ecobee/translations/id.json b/homeassistant/components/ecobee/translations/id.json new file mode 100644 index 00000000000000..7d23b0ca14165e --- /dev/null +++ b/homeassistant/components/ecobee/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "pin_request_failed": "Terjadi kesalahan saat meminta PIN dari ecobee. Verifikasi apakah kunci API sudah benar.", + "token_request_failed": "Kesalahan saat meminta token dari ecobee. Coba lagi" + }, + "step": { + "authorize": { + "description": "Otorisasi aplikasi ini di https://www.ecobee.com/consumerportal/index.html dengan kode PIN:\n\n{pin}\n\nKemudian, tekan Kirim.", + "title": "Otorisasi aplikasi di ecobee.com" + }, + "user": { + "data": { + "api_key": "Kunci API" + }, + "description": "Masukkan kunci API yang diperoleh dari ecobee.com.", + "title": "Kunci API ecobee" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/ko.json b/homeassistant/components/ecobee/translations/ko.json index 674b087620aaed..45406df54b648c 100644 --- a/homeassistant/components/ecobee/translations/ko.json +++ b/homeassistant/components/ecobee/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "pin_request_failed": "ecobee \ub85c\ubd80\ud130 PIN \uc694\uccad\uc5d0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4; API \ud0a4\uac00 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/ecobee/translations/no.json b/homeassistant/components/ecobee/translations/no.json index f3c2eceee44f38..0492ff76cc6180 100644 --- a/homeassistant/components/ecobee/translations/no.json +++ b/homeassistant/components/ecobee/translations/no.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "Vennligst godkjenn denne appen p\u00e5 [https://www.ecobee.com/consumerportal](https://www.ecobee.com/consumerportal) med pin-kode:\n\n{pin}\n\nTrykk deretter p\u00e5 send.", + "description": "Autoriser denne appen p\u00e5 https://www.ecobee.com/consumerportal/index.html med PIN-kode: \n\n {pin}\n\n Trykk deretter p\u00e5 Send.", "title": "Godkjenn app p\u00e5 ecobee.com" }, "user": { diff --git a/homeassistant/components/econet/translations/bg.json b/homeassistant/components/econet/translations/bg.json new file mode 100644 index 00000000000000..cef3726d759676 --- /dev/null +++ b/homeassistant/components/econet/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/hu.json b/homeassistant/components/econet/translations/hu.json new file mode 100644 index 00000000000000..065c648d4a041f --- /dev/null +++ b/homeassistant/components/econet/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/id.json b/homeassistant/components/econet/translations/id.json new file mode 100644 index 00000000000000..467b58a8d27f50 --- /dev/null +++ b/homeassistant/components/econet/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "title": "Siapkan Akun Rheem EcoNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/econet/translations/ko.json b/homeassistant/components/econet/translations/ko.json index f5c1381b8b164a..40735cdb6d05f9 100644 --- a/homeassistant/components/econet/translations/ko.json +++ b/homeassistant/components/econet/translations/ko.json @@ -14,7 +14,8 @@ "data": { "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "title": "Rheem EcoNet \uacc4\uc815 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/elgato/translations/hu.json b/homeassistant/components/elgato/translations/hu.json index 3c69fd4562a2e4..ef6404bd92d5c0 100644 --- a/homeassistant/components/elgato/translations/hu.json +++ b/homeassistant/components/elgato/translations/hu.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Ez az Elgato Key Light 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" }, + "flow_title": "Elgato Key Light: {serial_number}", "step": { "user": { "data": { diff --git a/homeassistant/components/elgato/translations/id.json b/homeassistant/components/elgato/translations/id.json new file mode 100644 index 00000000000000..b06691b9453cdd --- /dev/null +++ b/homeassistant/components/elgato/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Elgato Key Light: {serial_number}", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Siapkan Elgato Key Light Anda untuk diintegrasikan dengan Home Assistant." + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan Elgato Key Light dengan nomor seri `{serial_number}` ke Home Assistant?", + "title": "Perangkat Elgato Key Light yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/ko.json b/homeassistant/components/elgato/translations/ko.json index f2deb818431160..2d3c7111b2e543 100644 --- a/homeassistant/components/elgato/translations/ko.json +++ b/homeassistant/components/elgato/translations/ko.json @@ -14,10 +14,10 @@ "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "description": "Home Assistant \uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." + "description": "Home Assistant\uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." }, "zeroconf_confirm": { - "description": "Elgato Key Light \uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}`\uc758 Elgato Key Light\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c Elgato Key Light \uae30\uae30" } } diff --git a/homeassistant/components/elgato/translations/nl.json b/homeassistant/components/elgato/translations/nl.json index 81035cc898ffbf..fcda6a7ca84113 100644 --- a/homeassistant/components/elgato/translations/nl.json +++ b/homeassistant/components/elgato/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit Elgato Key Light apparaat is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken" }, "error": { @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "host": "Hostnaam of IP-adres", - "port": "Poortnummer" + "host": "Host", + "port": "Poort" }, "description": "Stel uw Elgato Key Light in om te integreren met Home Assistant." }, diff --git a/homeassistant/components/elkm1/translations/he.json b/homeassistant/components/elkm1/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/elkm1/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/elkm1/translations/hu.json b/homeassistant/components/elkm1/translations/hu.json index dee4ed9ee0fa4d..83862dfb75f243 100644 --- a/homeassistant/components/elkm1/translations/hu.json +++ b/homeassistant/components/elkm1/translations/hu.json @@ -1,9 +1,15 @@ { "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", + "protocol": "Protokoll", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } diff --git a/homeassistant/components/elkm1/translations/id.json b/homeassistant/components/elkm1/translations/id.json new file mode 100644 index 00000000000000..e7ddd3cf9ee4e7 --- /dev/null +++ b/homeassistant/components/elkm1/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "address_already_configured": "ElkM1 dengan alamat ini sudah dikonfigurasi", + "already_configured": "ElkM1 dengan prefiks ini sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "address": "Alamat IP atau domain atau port serial jika terhubung melalui serial.", + "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": "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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/ko.json b/homeassistant/components/elkm1/translations/ko.json index fb8c22ba5b2283..507f741676a35e 100644 --- a/homeassistant/components/elkm1/translations/ko.json +++ b/homeassistant/components/elkm1/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "address_already_configured": "\uc774 \uc8fc\uc18c\ub85c ElkM1 \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_configured": "\uc774 \uc811\ub450\uc0ac\ub85c ElkM1 \uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "address_already_configured": "\uc774 \uc8fc\uc18c\ub85c ElkM1\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured": "\uc774 \uc811\ub450\uc0ac\ub85c ElkM1\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -19,7 +19,7 @@ "temperature_unit": "ElkM1 \uc774 \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704.", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548'\uc5d0 \ub300\ud574 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \ud1b5\uc2e0\uc18d\ub3c4 \ubc14\uc6b0\ub4dc\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", + "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548'\uc5d0 \ub300\ud574 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \uc804\uc1a1 \uc18d\ub3c4\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", "title": "Elk-M1 \uc81c\uc5b4\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/elkm1/translations/nl.json b/homeassistant/components/elkm1/translations/nl.json index 9e7adf71c4b14e..de51e67b2062c9 100644 --- a/homeassistant/components/elkm1/translations/nl.json +++ b/homeassistant/components/elkm1/translations/nl.json @@ -5,7 +5,7 @@ "already_configured": "Een ElkM1 met dit voorvoegsel is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, @@ -13,11 +13,11 @@ "user": { "data": { "address": "Het IP-adres of domein of seri\u00eble poort bij verbinding via serieel.", - "password": "Wachtwoord (alleen beveiligd).", + "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 (alleen beveiligd)." + "username": "Gebruikersnaam" }, "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" diff --git a/homeassistant/components/emulated_roku/translations/hu.json b/homeassistant/components/emulated_roku/translations/hu.json index 3d490ddbfeb125..bccfe3bdcab0d6 100644 --- a/homeassistant/components/emulated_roku/translations/hu.json +++ b/homeassistant/components/emulated_roku/translations/hu.json @@ -1,9 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { - "host_ip": "Hoszt IP", + "host_ip": "Hoszt IP c\u00edm", "listen_port": "Port figyel\u00e9se", "name": "N\u00e9v" }, @@ -11,5 +14,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/id.json b/homeassistant/components/emulated_roku/translations/id.json new file mode 100644 index 00000000000000..9ffcedf5d19791 --- /dev/null +++ b/homeassistant/components/emulated_roku/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "advertise_ip": "Umumkan Alamat IP", + "advertise_port": "Umumkan Port", + "host_ip": "Alamat IP Host", + "name": "Nama", + "upnp_bind_multicast": "Bind multicast (True/False)" + }, + "title": "Tentukan konfigurasi server" + } + } + }, + "title": "Emulasi Roku" +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/nl.json b/homeassistant/components/emulated_roku/translations/nl.json index 54d544faee852c..d4f31b7bb5d151 100644 --- a/homeassistant/components/emulated_roku/translations/nl.json +++ b/homeassistant/components/emulated_roku/translations/nl.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "advertise_ip": "Adverteer IP", - "advertise_port": "Adverterenpoort", + "advertise_ip": "Ip adres ontdekbaar", + "advertise_port": "Adverteer Poort", "host_ip": "Host IP", "listen_port": "Luisterpoort", "name": "Naam", @@ -17,5 +17,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/hu.json b/homeassistant/components/enocean/translations/hu.json new file mode 100644 index 00000000000000..065747fb39df50 --- /dev/null +++ b/homeassistant/components/enocean/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/id.json b/homeassistant/components/enocean/translations/id.json new file mode 100644 index 00000000000000..ccadfe55982ad7 --- /dev/null +++ b/homeassistant/components/enocean/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "invalid_dongle_path": "Jalur dongle tidak valid", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_dongle_path": "Tidak ada dongle valid yang ditemukan untuk jalur ini" + }, + "step": { + "detect": { + "data": { + "path": "Jalur dongle USB" + }, + "title": "Pilih jalur ke dongle ENOcean Anda" + }, + "manual": { + "data": { + "path": "Jalur dongle USB" + }, + "title": "Masukkan jalur ke dongle ENOcean Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/enocean/translations/ko.json b/homeassistant/components/enocean/translations/ko.json index ba109ed58c04ec..a7480a72b3b80b 100644 --- a/homeassistant/components/enocean/translations/ko.json +++ b/homeassistant/components/enocean/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_dongle_path": "\ub3d9\uae00 \uacbd\ub85c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_dongle_path": "\uc774 \uacbd\ub85c\uc5d0 \uc720\ud6a8\ud55c \ub3d9\uae00\uc774 \uc5c6\uc2b5\ub2c8\ub2e4" @@ -18,7 +18,7 @@ "data": { "path": "USB \ub3d9\uae00 \uacbd\ub85c" }, - "title": "ENOcean \ub3d9\uae00 \uacbd\ub85c \uc785\ub825\ud558\uae30" + "title": "ENOcean \ub3d9\uae00 \uacbd\ub85c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/epson/translations/hu.json b/homeassistant/components/epson/translations/hu.json index 5ff60755bfd723..f2a380903ecf04 100644 --- a/homeassistant/components/epson/translations/hu.json +++ b/homeassistant/components/epson/translations/hu.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "host": "Gazdag\u00e9p", + "host": "Hoszt", "name": "N\u00e9v", "port": "Port" } diff --git a/homeassistant/components/epson/translations/id.json b/homeassistant/components/epson/translations/id.json new file mode 100644 index 00000000000000..ba2d36424f951d --- /dev/null +++ b/homeassistant/components/epson/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/he.json b/homeassistant/components/esphome/translations/he.json new file mode 100644 index 00000000000000..648d007cc469fc --- /dev/null +++ b/homeassistant/components/esphome/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "authenticate": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/hu.json b/homeassistant/components/esphome/translations/hu.json index 46cb3228edb830..6c4586fbd558b1 100644 --- a/homeassistant/components/esphome/translations/hu.json +++ b/homeassistant/components/esphome/translations/hu.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Az ESP-t m\u00e1r konfigur\u00e1ltad." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van." }, "error": { "connection_error": "Nem lehet csatlakozni az ESP-hez. K\u00e9rlek gy\u0151z\u0151dj meg r\u00f3la, hogy a YAML f\u00e1jl tartalmaz egy \"api:\" sort.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "resolve_error": "Az ESP c\u00edme nem oldhat\u00f3 fel. Ha a hiba tov\u00e1bbra is fenn\u00e1ll, k\u00e9rlek, \u00e1ll\u00edts be egy statikus IP-c\u00edmet: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, "flow_title": "ESPHome: {name}", diff --git a/homeassistant/components/esphome/translations/id.json b/homeassistant/components/esphome/translations/id.json index 9f6cd01294942b..a39a19e12db3b9 100644 --- a/homeassistant/components/esphome/translations/id.json +++ b/homeassistant/components/esphome/translations/id.json @@ -1,14 +1,32 @@ { "config": { "abort": { - "already_configured": "ESP sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung" }, + "error": { + "connection_error": "Tidak dapat terhubung ke ESP. Pastikan file YAML Anda mengandung baris 'api:'.", + "invalid_auth": "Autentikasi tidak valid", + "resolve_error": "Tidak dapat menemukan alamat ESP. Jika kesalahan ini terus terjadi, atur alamat IP statis: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "flow_title": "ESPHome: {name}", "step": { "authenticate": { "data": { - "password": "Kata kunci" + "password": "Kata Sandi" + }, + "description": "Masukkan kata sandi yang ditetapkan di konfigurasi Anda untuk {name}." + }, + "discovery_confirm": { + "description": "Ingin menambahkan node ESPHome `{name}` ke Home Assistant?", + "title": "Perangkat node ESPHome yang ditemukan" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" }, - "description": "Silakan masukkan kata kunci yang Anda atur di konfigurasi Anda." + "description": "Masukkan pengaturan koneksi node [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/ko.json b/homeassistant/components/esphome/translations/ko.json index 18827f690248bb..3e98bdabeb5cbf 100644 --- a/homeassistant/components/esphome/translations/ko.json +++ b/homeassistant/components/esphome/translations/ko.json @@ -5,7 +5,7 @@ "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4" }, "error": { - "connection_error": "ESP \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:' \ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "connection_error": "ESP\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. YAML \ud30c\uc77c\uc5d0 'api:'\ub97c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "resolve_error": "ESP \uc758 \uc8fc\uc18c\ub97c \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uac00 \uacc4\uc18d \ubc1c\uc0dd\ud558\uba74 \uace0\uc815 IP \uc8fc\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" }, @@ -18,7 +18,7 @@ "description": "{name} \uc758 \uad6c\uc131\uc5d0 \uc124\uc815\ud55c \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." }, "discovery_confirm": { - "description": "Home Assistant \uc5d0 ESPHome node `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Home Assistant\uc5d0 ESPHome node `{name}`\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c ESPHome node" }, "user": { diff --git a/homeassistant/components/esphome/translations/nl.json b/homeassistant/components/esphome/translations/nl.json index f32dbd1723ef97..1aae006feedf3c 100644 --- a/homeassistant/components/esphome/translations/nl.json +++ b/homeassistant/components/esphome/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "ESP is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al begonnen" }, "error": { diff --git a/homeassistant/components/faa_delays/translations/bg.json b/homeassistant/components/faa_delays/translations/bg.json new file mode 100644 index 00000000000000..0995436221b2d2 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "id": "\u041b\u0435\u0442\u0438\u0449\u0435" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/de.json b/homeassistant/components/faa_delays/translations/de.json index 72b837c862ca05..9519c7d44705fc 100644 --- a/homeassistant/components/faa_delays/translations/de.json +++ b/homeassistant/components/faa_delays/translations/de.json @@ -1,8 +1,21 @@ { "config": { + "abort": { + "already_configured": "Dieser Flughafen ist bereits konfiguriert" + }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_airport": "Flughafencode ist ung\u00fcltig", "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "id": "Flughafen" + }, + "description": "Geben Sie einen US-Flughafencode im IATA-Format ein", + "title": "FAA Delays" + } } } } \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/es.json b/homeassistant/components/faa_delays/translations/es.json index 94eca99dda3774..71f7fecef41b6d 100644 --- a/homeassistant/components/faa_delays/translations/es.json +++ b/homeassistant/components/faa_delays/translations/es.json @@ -4,7 +4,9 @@ "already_configured": "Este aeropuerto ya est\u00e1 configurado." }, "error": { - "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido" + "cannot_connect": "Fallo al conectar", + "invalid_airport": "El c\u00f3digo del aeropuerto no es v\u00e1lido", + "unknown": "Error inesperado" }, "step": { "user": { diff --git a/homeassistant/components/faa_delays/translations/hu.json b/homeassistant/components/faa_delays/translations/hu.json new file mode 100644 index 00000000000000..95dfabc213a3e8 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ez a rep\u00fcl\u0151t\u00e9r m\u00e1r konfigur\u00e1lva van." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_airport": "A rep\u00fcl\u0151t\u00e9r k\u00f3dja \u00e9rv\u00e9nytelen", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "id": "Rep\u00fcl\u0151t\u00e9r" + }, + "title": "FAA Delays" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/id.json b/homeassistant/components/faa_delays/translations/id.json new file mode 100644 index 00000000000000..4f4c3a93924472 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Bandara ini sudah dikonfigurasi." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_airport": "Kode bandara tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "id": "Bandara" + }, + "description": "Masukkan Kode Bandara AS dalam Format IATA", + "title": "Penundaan FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/ko.json b/homeassistant/components/faa_delays/translations/ko.json new file mode 100644 index 00000000000000..2d755e5de28afd --- /dev/null +++ b/homeassistant/components/faa_delays/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uacf5\ud56d\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_airport": "\uacf5\ud56d \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "id": "\uacf5\ud56d" + }, + "description": "IATA \ud615\uc2dd\uc758 \ubbf8\uad6d \uacf5\ud56d \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "FAA \ud56d\uacf5 \uc5f0\ucc29 \uc815\ubcf4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/pl.json b/homeassistant/components/faa_delays/translations/pl.json new file mode 100644 index 00000000000000..7073597f5291a6 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "To lotnisko jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_airport": "Kod lotniska jest nieprawid\u0142owy", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "id": "Lotnisko" + }, + "description": "Wprowad\u017a kod lotniska w Stanach w formacie IATA", + "title": "Op\u00f3\u017anienia FAA" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/pt.json b/homeassistant/components/faa_delays/translations/pt.json new file mode 100644 index 00000000000000..49cb628dd85871 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/pt.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/faa_delays/translations/zh-Hans.json b/homeassistant/components/faa_delays/translations/zh-Hans.json new file mode 100644 index 00000000000000..4052f12f524693 --- /dev/null +++ b/homeassistant/components/faa_delays/translations/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_airport": "\u822a\u73ed\u53f7\u65e0\u6548", + "unknown": "\u9884\u671f\u5916\u7684\u9519\u8bef" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fan/translations/id.json b/homeassistant/components/fan/translations/id.json index b5324f36f6a8de..054ec10754c1c7 100644 --- a/homeassistant/components/fan/translations/id.json +++ b/homeassistant/components/fan/translations/id.json @@ -1,9 +1,23 @@ { + "device_automation": { + "action_type": { + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, - "title": "Kipas angin" + "title": "Kipas Angin" } \ No newline at end of file diff --git a/homeassistant/components/fan/translations/ko.json b/homeassistant/components/fan/translations/ko.json index 5f6116d48d23d6..c2157f29e724bc 100644 --- a/homeassistant/components/fan/translations/ko.json +++ b/homeassistant/components/fan/translations/ko.json @@ -1,16 +1,16 @@ { "device_automation": { "action_type": { - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/fireservicerota/translations/hu.json b/homeassistant/components/fireservicerota/translations/hu.json index 63c887ff28140c..8e8432d5df4a25 100644 --- a/homeassistant/components/fireservicerota/translations/hu.json +++ b/homeassistant/components/fireservicerota/translations/hu.json @@ -1,9 +1,26 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { + "reauth": { + "data": { + "password": "Jelsz\u00f3" + } + }, "user": { "data": { - "url": "Weboldal" + "password": "Jelsz\u00f3", + "url": "Weboldal", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } diff --git a/homeassistant/components/fireservicerota/translations/id.json b/homeassistant/components/fireservicerota/translations/id.json new file mode 100644 index 00000000000000..0c4462a1ea7636 --- /dev/null +++ b/homeassistant/components/fireservicerota/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "reauth": { + "data": { + "password": "Kata Sandi" + }, + "description": "Token autentikasi menjadi tidak valid, masuk untuk membuat token lagi." + }, + "user": { + "data": { + "password": "Kata Sandi", + "url": "Situs Web", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/ko.json b/homeassistant/components/fireservicerota/translations/ko.json index f705fd9873c23a..843371ed03561b 100644 --- a/homeassistant/components/fireservicerota/translations/ko.json +++ b/homeassistant/components/fireservicerota/translations/ko.json @@ -14,11 +14,13 @@ "reauth": { "data": { "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "\uc778\uc99d \ud1a0\ud070\uc774 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc0dd\uc131\ud558\ub824\uba74 \ub85c\uadf8\uc778\ud574\uc8fc\uc138\uc694." }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", + "url": "\uc6f9\uc0ac\uc774\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } diff --git a/homeassistant/components/firmata/translations/fr.json b/homeassistant/components/firmata/translations/fr.json index a66d58dce876d5..b3509c9126c1d0 100644 --- a/homeassistant/components/firmata/translations/fr.json +++ b/homeassistant/components/firmata/translations/fr.json @@ -2,6 +2,10 @@ "config": { "abort": { "cannot_connect": "Impossible de se connecter \u00e0 la carte Firmata pendant la configuration" + }, + "step": { + "one": "Vide ", + "other": "Vide" } } } \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/hu.json b/homeassistant/components/firmata/translations/hu.json new file mode 100644 index 00000000000000..563ede561557c5 --- /dev/null +++ b/homeassistant/components/firmata/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/id.json b/homeassistant/components/firmata/translations/id.json new file mode 100644 index 00000000000000..3f10b4aa77c440 --- /dev/null +++ b/homeassistant/components/firmata/translations/id.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/de.json b/homeassistant/components/flick_electric/translations/de.json index 3e3568c45f8e32..5cca1386ffb102 100644 --- a/homeassistant/components/flick_electric/translations/de.json +++ b/homeassistant/components/flick_electric/translations/de.json @@ -12,6 +12,7 @@ "user": { "data": { "client_id": "Client-ID (optional)", + "client_secret": "Client Secret (optional)", "password": "Passwort", "username": "Benutzername" } diff --git a/homeassistant/components/flick_electric/translations/hu.json b/homeassistant/components/flick_electric/translations/hu.json index dee4ed9ee0fa4d..f7ed726e43305f 100644 --- a/homeassistant/components/flick_electric/translations/hu.json +++ b/homeassistant/components/flick_electric/translations/hu.json @@ -1,11 +1,20 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "Flick Bejelentkez\u00e9si Adatok" } } } diff --git a/homeassistant/components/flick_electric/translations/id.json b/homeassistant/components/flick_electric/translations/id.json new file mode 100644 index 00000000000000..8c283cfd56eddb --- /dev/null +++ b/homeassistant/components/flick_electric/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "client_id": "ID Klien (Opsional)", + "client_secret": "Kode Rahasia Klien (Opsional)", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Kredensial Masuk Flick" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/hu.json b/homeassistant/components/flo/translations/hu.json index 3b2d79a34a77e2..0abcc301f0c854 100644 --- a/homeassistant/components/flo/translations/hu.json +++ b/homeassistant/components/flo/translations/hu.json @@ -2,6 +2,20 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/flo/translations/id.json b/homeassistant/components/flo/translations/id.json new file mode 100644 index 00000000000000..ed8fde321061cf --- /dev/null +++ b/homeassistant/components/flo/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/he.json b/homeassistant/components/flume/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/flume/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/flume/translations/hu.json b/homeassistant/components/flume/translations/hu.json index dee4ed9ee0fa4d..cc0c820facf293 100644 --- a/homeassistant/components/flume/translations/hu.json +++ b/homeassistant/components/flume/translations/hu.json @@ -1,5 +1,13 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/flume/translations/id.json b/homeassistant/components/flume/translations/id.json new file mode 100644 index 00000000000000..333afb167e6e82 --- /dev/null +++ b/homeassistant/components/flume/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "client_id": "ID Klien", + "client_secret": "Kode Rahasia Klien", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Untuk mengakses API Flume Personal, Anda harus meminta 'ID Klien' dan 'Kode Rahasia Klien' di https://portal.flumetech.com/settings#token", + "title": "Hubungkan ke Akun Flume Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/nl.json b/homeassistant/components/flume/translations/nl.json index d176eb133656c9..97daf42d11eb26 100644 --- a/homeassistant/components/flume/translations/nl.json +++ b/homeassistant/components/flume/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dit account is al geconfigureerd." + "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/flunearyou/translations/he.json b/homeassistant/components/flunearyou/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/flunearyou/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/hu.json b/homeassistant/components/flunearyou/translations/hu.json new file mode 100644 index 00000000000000..4f8cca2a93946a --- /dev/null +++ b/homeassistant/components/flunearyou/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/id.json b/homeassistant/components/flunearyou/translations/id.json new file mode 100644 index 00000000000000..86afc7bb5fd929 --- /dev/null +++ b/homeassistant/components/flunearyou/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Pantau laporan berbasis pengguna dan CDC berdasarkan data koordinat.", + "title": "Konfigurasikan Flu Near You" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/ko.json b/homeassistant/components/flunearyou/translations/ko.json index f6528e85ccac27..bfe1945fa675bb 100644 --- a/homeassistant/components/flunearyou/translations/ko.json +++ b/homeassistant/components/flunearyou/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index 0ff044abc5e578..bca6e2a8c8bf20 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "unknown": "Onverwachte fout" diff --git a/homeassistant/components/flunearyou/translations/zh-Hans.json b/homeassistant/components/flunearyou/translations/zh-Hans.json new file mode 100644 index 00000000000000..f55159dc2356af --- /dev/null +++ b/homeassistant/components/flunearyou/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\u4f4d\u7f6e\u5b8c\u6210\u914d\u7f6e" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/de.json b/homeassistant/components/forked_daapd/translations/de.json index e90ffc71f90244..fcbcf6b0df0d0a 100644 --- a/homeassistant/components/forked_daapd/translations/de.json +++ b/homeassistant/components/forked_daapd/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { + "forbidden": "Verbindung kann nicht hergestellt werden. Bitte \u00fcberpr\u00fcfen Sie Ihre forked-daapd-Netzwerkberechtigungen.", "unknown_error": "Unbekannter Fehler", "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte Host und Port pr\u00fcfen.", "wrong_password": "Ung\u00fcltiges Passwort", diff --git a/homeassistant/components/forked_daapd/translations/hu.json b/homeassistant/components/forked_daapd/translations/hu.json index a9c13f1ee68c7d..ca90fad3048fd2 100644 --- a/homeassistant/components/forked_daapd/translations/hu.json +++ b/homeassistant/components/forked_daapd/translations/hu.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "forbidden": "Nem tud csatlakozni. K\u00e9rj\u00fck, ellen\u0151rizze a forked-daapd h\u00e1l\u00f3zati enged\u00e9lyeket." + "forbidden": "Nem tud csatlakozni. K\u00e9rj\u00fck, ellen\u0151rizze a forked-daapd h\u00e1l\u00f3zati enged\u00e9lyeket.", + "unknown_error": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/id.json b/homeassistant/components/forked_daapd/translations/id.json new file mode 100644 index 00000000000000..76787e2a19bf3d --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "not_forked_daapd": "Perangkat bukan server forked-daapd." + }, + "error": { + "forbidden": "Tidak dapat terhubung. Periksa izin jaringan forked-daapd Anda.", + "unknown_error": "Kesalahan yang tidak diharapkan", + "websocket_not_enabled": "Websocket server forked-daapd tidak diaktifkan.", + "wrong_host_or_port": "Tidak dapat terhubung. Periksa nilai host dan port.", + "wrong_password": "Kata sandi salah.", + "wrong_server_type": "Integrasi forked-daapd membutuhkan server forked-daapd dengan versi >= 27.0." + }, + "flow_title": "forked-daapd server: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama alias", + "password": "Kata sandi API (kosongkan jika tidak ada kata sandi)", + "port": "Port API" + }, + "title": "Siapkan perangkat forked-daapd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Port untuk kontrol pipa librespot-java (jika digunakan)", + "max_playlists": "Jumlah maksimum daftar putar yang digunakan sebagai sumber", + "tts_pause_time": "Tenggang waktu dalam detik sebelum dan setelah TTS", + "tts_volume": "Volume TTS (bilangan float dalam rentang [0,1])" + }, + "description": "Tentukan berbagai opsi untuk integrasi forked-daapd.", + "title": "Konfigurasikan opsi forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ko.json b/homeassistant/components/forked_daapd/translations/ko.json index 5ae487a4096552..3eaaf15be2c3c4 100644 --- a/homeassistant/components/forked_daapd/translations/ko.json +++ b/homeassistant/components/forked_daapd/translations/ko.json @@ -5,6 +5,7 @@ "not_forked_daapd": "\uae30\uae30\uac00 forked-daapd \uc11c\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4." }, "error": { + "forbidden": "\uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. fork-daapd \ub124\ud2b8\uc6cc\ud06c \uc0ac\uc6a9 \uad8c\ud55c\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694", "unknown_error": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "websocket_not_enabled": "forked-daapd \uc11c\ubc84 \uc6f9\uc18c\ucf13\uc774 \ube44\ud65c\uc131\ud654 \ub418\uc5b4\uc788\uc2b5\ub2c8\ub2e4.", "wrong_host_or_port": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", @@ -34,7 +35,7 @@ "tts_volume": "TTS \ubcfc\ub968 (0~1 \uc758 \uc2e4\uc218\uac12)" }, "description": "forked-daapd \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ub2e4\uc591\ud55c \uc635\uc158\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "forked-daapd \uc635\uc158 \uc124\uc815\ud558\uae30" + "title": "forked-daapd \uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/forked_daapd/translations/nl.json b/homeassistant/components/forked_daapd/translations/nl.json index 5391615fcb1e30..903a716d16e01a 100644 --- a/homeassistant/components/forked_daapd/translations/nl.json +++ b/homeassistant/components/forked_daapd/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "not_forked_daapd": "Apparaat is geen forked-daapd-server." }, "error": { - "unknown_error": "Onbekende fout.", + "unknown_error": "Onverwachte fout", "websocket_not_enabled": "forked-daapd server websocket niet ingeschakeld.", "wrong_host_or_port": "Verbinding mislukt, controleer het host-adres en poort.", "wrong_password": "Onjuist wachtwoord.", diff --git a/homeassistant/components/foscam/translations/bg.json b/homeassistant/components/foscam/translations/bg.json new file mode 100644 index 00000000000000..5c41e03c838396 --- /dev/null +++ b/homeassistant/components/foscam/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "port": "\u041f\u043e\u0440\u0442", + "rtsp_port": "RTSP \u043f\u043e\u0440\u0442", + "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/foscam/translations/hu.json b/homeassistant/components/foscam/translations/hu.json new file mode 100644 index 00000000000000..63ea95210ffa7d --- /dev/null +++ b/homeassistant/components/foscam/translations/hu.json @@ -0,0 +1,26 @@ +{ + "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", + "invalid_response": "\u00c9rv\u00e9nytelen v\u00e1lasz az eszk\u00f6zt\u0151l", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "rtsp_port": "RTSP port", + "stream": "Stream", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/id.json b/homeassistant/components/foscam/translations/id.json new file mode 100644 index 00000000000000..21a7682b92c311 --- /dev/null +++ b/homeassistant/components/foscam/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_response": "Response tidak valid dari perangkat", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "rtsp_port": "Port RTSP", + "stream": "Stream", + "username": "Nama Pengguna" + } + } + } + }, + "title": "Foscam" +} \ No newline at end of file diff --git a/homeassistant/components/foscam/translations/ko.json b/homeassistant/components/foscam/translations/ko.json index bfd8e952671238..ba743f6b27a565 100644 --- a/homeassistant/components/foscam/translations/ko.json +++ b/homeassistant/components/foscam/translations/ko.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_response": "\uae30\uae30\uc5d0\uc11c \uc798\ubabb\ub41c \uc751\ub2f5\uc744 \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -14,9 +15,12 @@ "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", + "rtsp_port": "RTSP \ud3ec\ud2b8", + "stream": "\uc2a4\ud2b8\ub9bc", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } - } + }, + "title": "Foscam" } \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/hu.json b/homeassistant/components/freebox/translations/hu.json index d13f5fa17c8c1d..1f0b848d3b6ecb 100644 --- a/homeassistant/components/freebox/translations/hu.json +++ b/homeassistant/components/freebox/translations/hu.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, k\u00e9rem pr\u00f3b\u00e1lja \u00fajra" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, k\u00e9rem pr\u00f3b\u00e1lja \u00fajra", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/freebox/translations/id.json b/homeassistant/components/freebox/translations/id.json new file mode 100644 index 00000000000000..b03ec248edbdc6 --- /dev/null +++ b/homeassistant/components/freebox/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "register_failed": "Gagal mendaftar, coba lagi.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "link": { + "description": "Klik \"Kirim\", lalu sentuh panah kanan di router untuk mendaftarkan Freebox dengan Home Assistant. \n\n![Lokasi tombol di router](/static/images/config_freebox.png)", + "title": "Tautkan router Freebox" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index a8b9a1edc7aa49..c3bb5a9bd401f1 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -10,7 +10,7 @@ }, "step": { "link": { - "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc758 \uc624\ub978\ucabd \ud654\uc0b4\ud45c\ub97c \ud130\uce58\ud558\uc5ec Home Assistant \uc5d0 Freebox \ub97c \ub4f1\ub85d\ud574\uc8fc\uc138\uc694.\n\n![\ub77c\uc6b0\ud130\uc758 \ubc84\ud2bc \uc704\uce58](/static/images/config_freebox.png)", + "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc758 \uc624\ub978\ucabd \ud654\uc0b4\ud45c\ub97c \ud130\uce58\ud558\uc5ec Home Assistant\uc5d0 Freebox\ub97c \ub4f1\ub85d\ud574\uc8fc\uc138\uc694.\n\n![\ub77c\uc6b0\ud130\uc758 \ubc84\ud2bc \uc704\uce58](/static/images/config_freebox.png)", "title": "Freebox \ub77c\uc6b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/freebox/translations/nl.json b/homeassistant/components/freebox/translations/nl.json index ea41fcfcd6a8e9..7fbd57dd6ff69c 100644 --- a/homeassistant/components/freebox/translations/nl.json +++ b/homeassistant/components/freebox/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "register_failed": "Registratie is mislukt, probeer het opnieuw", - "unknown": "Onbekende fout: probeer het later nog eens" + "unknown": "Onverwachte fout" }, "step": { "link": { diff --git a/homeassistant/components/fritzbox/translations/bg.json b/homeassistant/components/fritzbox/translations/bg.json new file mode 100644 index 00000000000000..ec678d2d76c0e5 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "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/fritzbox/translations/he.json b/homeassistant/components/fritzbox/translations/he.json new file mode 100644 index 00000000000000..035cb07a170a87 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/he.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "confirm": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, + "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/fritzbox/translations/hu.json b/homeassistant/components/fritzbox/translations/hu.json index 6a08b68d863e1b..44b68d5f540ca2 100644 --- a/homeassistant/components/fritzbox/translations/hu.json +++ b/homeassistant/components/fritzbox/translations/hu.json @@ -1,7 +1,24 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "flow_title": "AVM FRITZ!Box: {name}", "step": { "confirm": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}-t?" + }, + "reauth_confirm": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" diff --git a/homeassistant/components/fritzbox/translations/id.json b/homeassistant/components/fritzbox/translations/id.json new file mode 100644 index 00000000000000..8dbd1f71534042 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/id.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "not_supported": "Tersambung ke AVM FRITZ!Box tetapi tidak dapat mengontrol perangkat Smart Home.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Ingin menyiapkan {name}?" + }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Perbarui informasi masuk Anda untuk {name}." + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan informasi AVM FRITZ!Box Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/ko.json b/homeassistant/components/fritzbox/translations/ko.json index dfdcc0ad4eb6f1..21fd6b6afdf63c 100644 --- a/homeassistant/components/fritzbox/translations/ko.json +++ b/homeassistant/components/fritzbox/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "not_supported": "AVM FRITZ!Box \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc2a4\ub9c8\ud2b8 \ud648 \uae30\uae30\ub97c \uc81c\uc5b4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "not_supported": "AVM FRITZ!Box\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc9c0\ub9cc \uc2a4\ub9c8\ud2b8 \ud648 \uae30\uae30\ub97c \uc81c\uc5b4\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -17,13 +17,14 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "{name}\uc5d0 \ub300\ud55c \ub85c\uadf8\uc778 \uc815\ubcf4\ub97c \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694." }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 9bfe2ef6be6ed3..aa4f796f44c43f 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", - "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "no_devices_found": "Geen apparaten gevonden op het netwerk", "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen.", "reauth_successful": "Herauthenticatie was succesvol" @@ -23,11 +23,12 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "description": "Gelieve uw logingegevens voor {name} te actualiseren." }, "user": { "data": { - "host": "Host of IP-adres", + "host": "Host", "password": "Wachtwoord", "username": "Gebruikersnaam" }, diff --git a/homeassistant/components/fritzbox_callmonitor/translations/bg.json b/homeassistant/components/fritzbox_callmonitor/translations/bg.json new file mode 100644 index 00000000000000..fc2115d9ca038b --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/bg.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/hu.json b/homeassistant/components/fritzbox_callmonitor/translations/hu.json new file mode 100644 index 00000000000000..8c2c34775e5e8e --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/hu.json @@ -0,0 +1,26 @@ +{ + "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": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "phonebook": { + "data": { + "phonebook": "Telefonk\u00f6nyv" + } + }, + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/id.json b/homeassistant/components/fritzbox_callmonitor/translations/id.json new file mode 100644 index 00000000000000..43bb4a16b47263 --- /dev/null +++ b/homeassistant/components/fritzbox_callmonitor/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "insufficient_permissions": "Izin pengguna tidak memadai untuk mengakses pengaturan dan buku telepon AVM FRITZ!Box.", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "Pantau panggilan AVM FRITZ!Box: {name}", + "step": { + "phonebook": { + "data": { + "phonebook": "Buku telepon" + } + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "error": { + "malformed_prefixes": "Format prefiks salah, periksa formatnya." + }, + "step": { + "init": { + "data": { + "prefixes": "Prefiks (daftar yang dipisahkan koma)" + }, + "title": "Konfigurasikan Prefiks" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ko.json b/homeassistant/components/fritzbox_callmonitor/translations/ko.json index b8fd442cd03c43..a9bfff9a5e56f9 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ko.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ko.json @@ -2,12 +2,19 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "insufficient_permissions": "AVM FRIZ!Box \uc124\uc815 \ubc0f \uc804\ud654\ubc88\ud638\ubd80\uc5d0 \uc561\uc138\uc2a4\uc5d0 \ud544\uc694\ud55c \uc0ac\uc6a9\uc790\uc758 \uad8c\ud55c\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "AVM FRITZ!Box \uc804\ud654 \ubaa8\ub2c8\ud130\ub9c1: {name}", "step": { + "phonebook": { + "data": { + "phonebook": "\uc804\ud654\ubc88\ud638\ubd80" + } + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", @@ -17,5 +24,18 @@ } } } + }, + "options": { + "error": { + "malformed_prefixes": "\uc811\ub450\uc0ac\uc758 \ud615\uc2dd\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud615\uc2dd\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + }, + "step": { + "init": { + "data": { + "prefixes": "\uc811\ub450\uc0ac (\uc27c\ud45c\ub85c \uad6c\ubd84\ub41c \ubaa9\ub85d)" + }, + "title": "\uc811\ub450\uc0ac \uad6c\uc131\ud558\uae30" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/fritzbox_callmonitor/translations/nl.json b/homeassistant/components/fritzbox_callmonitor/translations/nl.json index 3381ed0d9b2eda..bc706861313f9e 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/nl.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/nl.json @@ -2,12 +2,19 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "insufficient_permissions": "Gebruiker heeft onvoldoende rechten voor toegang tot AVM FRITZ!Box instellingen en de telefoonboeken.", "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { "invalid_auth": "Ongeldige authenticatie" }, + "flow_title": "AVM FRITZ!Box oproepmonitor: {name}", "step": { + "phonebook": { + "data": { + "phonebook": "Telefoonboek" + } + }, "user": { "data": { "host": "Host", @@ -17,5 +24,18 @@ } } } + }, + "options": { + "error": { + "malformed_prefixes": "Voorvoegsels hebben een onjuiste indeling, controleer hun indeling." + }, + "step": { + "init": { + "data": { + "prefixes": "Voorvoegsels (door komma's gescheiden lijst)" + }, + "title": "Configureer voorvoegsels" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/he.json b/homeassistant/components/garmin_connect/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/garmin_connect/translations/hu.json b/homeassistant/components/garmin_connect/translations/hu.json index 2ada884847fc96..ae518acf001c47 100644 --- a/homeassistant/components/garmin_connect/translations/hu.json +++ b/homeassistant/components/garmin_connect/translations/hu.json @@ -4,10 +4,10 @@ "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "too_many_requests": "T\u00fal sok k\u00e9r\u00e9s, pr\u00f3b\u00e1lkozzon k\u00e9s\u0151bb \u00fajra.", - "unknown": "V\u00e1ratlan hiba." + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/id.json b/homeassistant/components/garmin_connect/translations/id.json new file mode 100644 index 00000000000000..27460757234863 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "too_many_requests": "Terlalu banyak permintaan, coba lagi nanti.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial Anda.", + "title": "Garmin Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/hu.json b/homeassistant/components/gdacs/translations/hu.json index 47eca9a7fac33b..fefcabae802009 100644 --- a/homeassistant/components/gdacs/translations/hu.json +++ b/homeassistant/components/gdacs/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A hely m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/id.json b/homeassistant/components/gdacs/translations/id.json new file mode 100644 index 00000000000000..55e1db686a221a --- /dev/null +++ b/homeassistant/components/gdacs/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "radius": "Radius" + }, + "title": "Isi detail filter Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/hu.json b/homeassistant/components/geofency/translations/hu.json index 99dc0feceadb7d..826b943e2f80ad 100644 --- a/homeassistant/components/geofency/translations/hu.json +++ b/homeassistant/components/geofency/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a Geofencyben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3] ( {docs_url} ) linken tal\u00e1lhat\u00f3k." + "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a Geofencyben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3]({docs_url}) linken tal\u00e1lhat\u00f3k." }, "step": { "user": { diff --git a/homeassistant/components/geofency/translations/id.json b/homeassistant/components/geofency/translations/id.json new file mode 100644 index 00000000000000..0e5163b96cd5c2 --- /dev/null +++ b/homeassistant/components/geofency/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di Geofency.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Geofency Webhook?", + "title": "Siapkan Geofency Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/ko.json b/homeassistant/components/geofency/translations/ko.json index fd49c57e249df4..2482ccca454e63 100644 --- a/homeassistant/components/geofency/translations/ko.json +++ b/homeassistant/components/geofency/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_quakes/translations/hu.json b/homeassistant/components/geonetnz_quakes/translations/hu.json index 4a163d24b75925..21a38c18e28344 100644 --- a/homeassistant/components/geonetnz_quakes/translations/hu.json +++ b/homeassistant/components/geonetnz_quakes/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_quakes/translations/id.json b/homeassistant/components/geonetnz_quakes/translations/id.json new file mode 100644 index 00000000000000..7a4e340e230e68 --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "mmi": "MMI", + "radius": "Radius" + }, + "title": "Isi detail filter Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/nl.json b/homeassistant/components/geonetnz_quakes/translations/nl.json index 865860a5adfc4b..74766300e11176 100644 --- a/homeassistant/components/geonetnz_quakes/translations/nl.json +++ b/homeassistant/components/geonetnz_quakes/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_volcano/translations/hu.json b/homeassistant/components/geonetnz_volcano/translations/hu.json index 42de5a1314239b..dadc8142d7efc7 100644 --- a/homeassistant/components/geonetnz_volcano/translations/hu.json +++ b/homeassistant/components/geonetnz_volcano/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/geonetnz_volcano/translations/id.json b/homeassistant/components/geonetnz_volcano/translations/id.json new file mode 100644 index 00000000000000..5dd4414ca62176 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/id.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "radius": "Radius" + }, + "title": "Isi detail filter Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/ko.json b/homeassistant/components/geonetnz_volcano/translations/ko.json index 1aeaf2192889e9..6d743c3a18de4d 100644 --- a/homeassistant/components/geonetnz_volcano/translations/ko.json +++ b/homeassistant/components/geonetnz_volcano/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/gios/translations/hu.json b/homeassistant/components/gios/translations/hu.json index 5702d3b33d265f..b35904e9d7623c 100644 --- a/homeassistant/components/gios/translations/hu.json +++ b/homeassistant/components/gios/translations/hu.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "A GIO\u015a integr\u00e1ci\u00f3 ehhez a m\u00e9r\u0151\u00e1llom\u00e1shoz m\u00e1r konfigur\u00e1lva van." + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem lehet csatlakozni a GIO\u015a szerverhez.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_sensors_data": "\u00c9rv\u00e9nytelen \u00e9rz\u00e9kel\u0151k adatai ehhez a m\u00e9r\u0151\u00e1llom\u00e1shoz.", "wrong_station_id": "A m\u00e9r\u0151\u00e1llom\u00e1s azonos\u00edt\u00f3ja nem megfelel\u0151." }, "step": { "user": { "data": { - "name": "Az integr\u00e1ci\u00f3 neve", + "name": "N\u00e9v", "station_id": "A m\u00e9r\u0151\u00e1llom\u00e1s azonos\u00edt\u00f3ja" }, "description": "A GIO\u015a (lengyel k\u00f6rnyezetv\u00e9delmi f\u0151fel\u00fcgyel\u0151) leveg\u0151min\u0151s\u00e9gi integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha seg\u00edts\u00e9gre van sz\u00fcks\u00e9ged a konfigur\u00e1ci\u00f3val kapcsolatban, l\u00e1togass ide: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/gios/translations/id.json b/homeassistant/components/gios/translations/id.json new file mode 100644 index 00000000000000..b32210c30d5026 --- /dev/null +++ b/homeassistant/components/gios/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_sensors_data": "Data sensor tidak valid untuk stasiun pengukuran ini.", + "wrong_station_id": "ID stasiun pengukuran salah." + }, + "step": { + "user": { + "data": { + "name": "Nama", + "station_id": "ID stasiun pengukuran" + }, + "description": "Siapkan integrasi kualitas udara GIO\u015a (Inspektorat Jenderal Perlindungan Lingkungan Polandia). Jika Anda memerlukan bantuan tentang konfigurasi, baca di sini: https://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a (Inspektorat Jenderal Perlindungan Lingkungan Polandia)" + } + } + }, + "system_health": { + "info": { + "can_reach_server": "Keterjangkauan server GIO\u015a" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/ko.json b/homeassistant/components/gios/translations/ko.json index 7895dafe8ce8d0..e462ef4e3b6565 100644 --- a/homeassistant/components/gios/translations/ko.json +++ b/homeassistant/components/gios/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -18,5 +18,10 @@ "title": "\ud3f4\ub780\ub4dc \ud658\uacbd\uccad (GIO\u015a)" } } + }, + "system_health": { + "info": { + "can_reach_server": "GIO\u015a \uc11c\ubc84 \uc5f0\uacb0" + } } } \ No newline at end of file diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index 09fddb56225f7d..f6ac11f8724ae6 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -4,14 +4,14 @@ "already_configured": "GIO\u015a-integratie voor dit meetstation is al geconfigureerd." }, "error": { - "cannot_connect": "Kan geen verbinding maken met de GIO\u015a-server.", + "cannot_connect": "Kan geen verbinding maken", "invalid_sensors_data": "Ongeldige sensorgegevens voor dit meetstation.", "wrong_station_id": "ID van het meetstation is niet correct." }, "step": { "user": { "data": { - "name": "Naam van de integratie", + "name": "Naam", "station_id": "ID van het meetstation" }, "description": "GIO\u015a (Poolse hoofdinspectie van milieubescherming) luchtkwaliteitintegratie instellen. Als u hulp nodig hebt bij de configuratie, kijk dan hier: https://www.home-assistant.io/integrations/gios", diff --git a/homeassistant/components/glances/translations/he.json b/homeassistant/components/glances/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/glances/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/hu.json b/homeassistant/components/glances/translations/hu.json index 0958efee4ae1bb..d85baecb5ca3ac 100644 --- a/homeassistant/components/glances/translations/hu.json +++ b/homeassistant/components/glances/translations/hu.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Kiszolg\u00e1l\u00f3 m\u00e1r konfigur\u00e1lva van." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem lehet csatlakozni a kiszolg\u00e1l\u00f3hoz", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "wrong_version": "Nem t\u00e1mogatott verzi\u00f3 (2 vagy 3 csak)" }, "step": { @@ -14,9 +14,9 @@ "name": "N\u00e9v", "password": "Jelsz\u00f3", "port": "Port", - "ssl": "Az SSL / TLS haszn\u00e1lat\u00e1val csatlakozzon a Glances rendszerhez", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", - "verify_ssl": "A rendszer tan\u00fas\u00edt\u00e1s\u00e1nak ellen\u0151rz\u00e9se", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se", "version": "Glances API-verzi\u00f3 (2 vagy 3)" }, "title": "Glances Be\u00e1ll\u00edt\u00e1sa" diff --git a/homeassistant/components/glances/translations/id.json b/homeassistant/components/glances/translations/id.json new file mode 100644 index 00000000000000..13127e74322e40 --- /dev/null +++ b/homeassistant/components/glances/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "wrong_version": "Versi tidak didukung (hanya versi 2 atau versi 3)" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL", + "version": "Versi API Glances (2 atau 3)" + }, + "title": "Siapkan Glances" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Frekuensi pembaruan" + }, + "description": "Konfigurasikan opsi untuk Glances" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/ko.json b/homeassistant/components/glances/translations/ko.json index 47f24d2edf12bf..e50206fade52d2 100644 --- a/homeassistant/components/glances/translations/ko.json +++ b/homeassistant/components/glances/translations/ko.json @@ -29,7 +29,7 @@ "data": { "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4" }, - "description": "Glances \uc635\uc158 \uc124\uc815\ud558\uae30" + "description": "Glances\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/goalzero/translations/hu.json b/homeassistant/components/goalzero/translations/hu.json new file mode 100644 index 00000000000000..c876a55301f160 --- /dev/null +++ b/homeassistant/components/goalzero/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "name": "N\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/id.json b/homeassistant/components/goalzero/translations/id.json new file mode 100644 index 00000000000000..63fddf13a8e132 --- /dev/null +++ b/homeassistant/components/goalzero/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_host": "Nama host atau alamat IP tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Pertama, Anda perlu mengunduh aplikasi Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nIkuti petunjuk untuk menghubungkan Yeti Anda ke jaringan Wi-Fi Anda. Kemudian dapatkan IP host dari router Anda. DHCP harus disetel di pengaturan router Anda untuk perangkat host agar IP host tidak berubah. Lihat manual pengguna router Anda.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/ko.json b/homeassistant/components/goalzero/translations/ko.json index f15f5827448670..d51193630026fc 100644 --- a/homeassistant/components/goalzero/translations/ko.json +++ b/homeassistant/components/goalzero/translations/ko.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { @@ -13,7 +13,9 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" - } + }, + "description": "\uba3c\uc800 Goal Zero \uc571\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4. https://www.goalzero.com/product-features/yeti-app/\n\n\uc9c0\uce68\uc5d0 \ub530\ub77c Yeti\ub97c Wifi \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc5d0\uc11c \ud638\uc2a4\ud2b8 IP\ub97c \uac00\uc838\uc640\uc8fc\uc138\uc694. \ud638\uc2a4\ud2b8 IP\uac00 \ubcc0\uacbd\ub418\uc9c0 \uc54a\ub3c4\ub85d \ud558\ub824\uba74 \uae30\uae30\uc5d0 \ub300\ud574 \ub77c\uc6b0\ud130\uc5d0\uc11c DHCP\ub97c \uc54c\ub9de\uac8c \uc124\uc815\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud574\ub2f9 \ub0b4\uc6a9\uc5d0 \ub300\ud574\uc11c\ub294 \ub77c\uc6b0\ud130\uc758 \uc0ac\uc6a9\uc790 \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "Goal Zero Yeti" } } } diff --git a/homeassistant/components/gogogate2/translations/hu.json b/homeassistant/components/gogogate2/translations/hu.json index 952a502a72d189..cdc76a4145aa20 100644 --- a/homeassistant/components/gogogate2/translations/hu.json +++ b/homeassistant/components/gogogate2/translations/hu.json @@ -10,9 +10,11 @@ "step": { "user": { "data": { + "ip_address": "IP c\u00edm", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "A GogoGate2 vagy az iSmartGate be\u00e1ll\u00edt\u00e1sa" } } } diff --git a/homeassistant/components/gogogate2/translations/id.json b/homeassistant/components/gogogate2/translations/id.json new file mode 100644 index 00000000000000..9de61641d41e24 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Berikan informasi yang diperlukan di bawah ini.", + "title": "Siapkan GogoGate2 atau iSmartGate" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/hu.json b/homeassistant/components/gpslogger/translations/hu.json index 4fa3678043ebeb..fe459ca3164956 100644 --- a/homeassistant/components/gpslogger/translations/hu.json +++ b/homeassistant/components/gpslogger/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a GPSLoggerben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3] ( {docs_url} ) linken tal\u00e1lhat\u00f3k." + "default": "Az esem\u00e9ny Home Assistantnek val\u00f3 k\u00fcld\u00e9s\u00e9hez be kell \u00e1ll\u00edtanod a webhook funkci\u00f3t a GPSLoggerben. \n\n Az al\u00e1bbi inform\u00e1ci\u00f3kat haszn\u00e1ld: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Tov\u00e1bbi r\u00e9szletek a [dokument\u00e1ci\u00f3]({docs_url}) linken tal\u00e1lhat\u00f3k." }, "step": { "user": { diff --git a/homeassistant/components/gpslogger/translations/id.json b/homeassistant/components/gpslogger/translations/id.json new file mode 100644 index 00000000000000..3be2d91f1f3641 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di GPSLogger.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan GPSLogger Webhook?", + "title": "Siapkan GPSLogger Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/ko.json b/homeassistant/components/gpslogger/translations/ko.json index e73d72c06b7a07..7a4e8b37c2e111 100644 --- a/homeassistant/components/gpslogger/translations/ko.json +++ b/homeassistant/components/gpslogger/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/gree/translations/de.json b/homeassistant/components/gree/translations/de.json index 96ed09a974f40d..86bc8e3673075f 100644 --- a/homeassistant/components/gree/translations/de.json +++ b/homeassistant/components/gree/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/gree/translations/hu.json b/homeassistant/components/gree/translations/hu.json new file mode 100644 index 00000000000000..6c61530acbebb9 --- /dev/null +++ b/homeassistant/components/gree/translations/hu.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "confirm": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/id.json b/homeassistant/components/gree/translations/id.json new file mode 100644 index 00000000000000..223836a8b40992 --- /dev/null +++ b/homeassistant/components/gree/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gree/translations/ko.json b/homeassistant/components/gree/translations/ko.json index 7011a61f7573ab..e5ae04d6e5c810 100644 --- a/homeassistant/components/gree/translations/ko.json +++ b/homeassistant/components/gree/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/group/translations/id.json b/homeassistant/components/group/translations/id.json index 9a38f0f2de3c15..553cbce0550f0c 100644 --- a/homeassistant/components/group/translations/id.json +++ b/homeassistant/components/group/translations/id.json @@ -2,14 +2,14 @@ "state": { "_": { "closed": "Tertutup", - "home": "Rumah", + "home": "Di Rumah", "locked": "Terkunci", "not_home": "Keluar", - "off": "Off", - "ok": "OK", - "on": "On", + "off": "Mati", + "ok": "Oke", + "on": "Nyala", "open": "Terbuka", - "problem": "Masalah", + "problem": "Bermasalah", "unlocked": "Terbuka" } }, diff --git a/homeassistant/components/guardian/translations/hu.json b/homeassistant/components/guardian/translations/hu.json index 563ede561557c5..bd43ce7672c385 100644 --- a/homeassistant/components/guardian/translations/hu.json +++ b/homeassistant/components/guardian/translations/hu.json @@ -1,7 +1,17 @@ { "config": { "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "ip_address": "IP c\u00edm", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/id.json b/homeassistant/components/guardian/translations/id.json new file mode 100644 index 00000000000000..b5b753210378d9 --- /dev/null +++ b/homeassistant/components/guardian/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "port": "Port" + }, + "description": "Konfigurasikan perangkat Elexa Guardian lokal." + }, + "zeroconf_confirm": { + "description": "Ingin menyiapkan perangkat Guardian ini?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/nl.json b/homeassistant/components/guardian/translations/nl.json index 959a847a7cf337..a33cb9357a9e6b 100644 --- a/homeassistant/components/guardian/translations/nl.json +++ b/homeassistant/components/guardian/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Dit Guardian-apparaat is al geconfigureerd.", - "already_in_progress": "De configuratie van het Guardian-apparaat is al bezig.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" }, "step": { diff --git a/homeassistant/components/habitica/translations/bg.json b/homeassistant/components/habitica/translations/bg.json new file mode 100644 index 00000000000000..02c83a6e9167b4 --- /dev/null +++ b/homeassistant/components/habitica/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/hu.json b/homeassistant/components/habitica/translations/hu.json new file mode 100644 index 00000000000000..4914a1bd27a717 --- /dev/null +++ b/homeassistant/components/habitica/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "invalid_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "api_user": "Habitica API felhaszn\u00e1l\u00f3i azonos\u00edt\u00f3ja", + "url": "URL" + } + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/id.json b/homeassistant/components/habitica/translations/id.json new file mode 100644 index 00000000000000..c7e4c549206ab0 --- /dev/null +++ b/homeassistant/components/habitica/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "api_user": "ID pengguna API Habitica", + "name": "Ganti nama pengguna Habitica. Nama ini akan digunakan untuk panggilan layanan", + "url": "URL" + }, + "description": "Hubungkan profil Habitica Anda untuk memungkinkan pemantauan profil dan tugas pengguna Anda. Perhatikan bahwa api_id dan api_key harus diperoleh dari https://habitica.com/user/settings/api" + } + } + }, + "title": "Habitica" +} \ No newline at end of file diff --git a/homeassistant/components/habitica/translations/ko.json b/homeassistant/components/habitica/translations/ko.json index 3fd04a4477b08f..6b890a320df075 100644 --- a/homeassistant/components/habitica/translations/ko.json +++ b/homeassistant/components/habitica/translations/ko.json @@ -8,8 +8,11 @@ "user": { "data": { "api_key": "API \ud0a4", + "api_user": "Habitica\uc758 API \uc0ac\uc6a9\uc790 ID", + "name": "Habitica\uc758 \uc0ac\uc6a9\uc790 \uc774\ub984\uc744 \uc7ac\uc815\uc758\ud574\uc8fc\uc138\uc694. \uc11c\ube44\uc2a4 \ud638\ucd9c\uc5d0 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", "url": "URL \uc8fc\uc18c" - } + }, + "description": "\uc0ac\uc6a9\uc790\uc758 \ud504\ub85c\ud544 \ubc0f \uc791\uc5c5\uc744 \ubaa8\ub2c8\ud130\ub9c1\ud560 \uc218 \uc788\ub3c4\ub85d \ud558\ub824\uba74 Habitica \ud504\ub85c\ud544\uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694.\n\ucc38\uace0\ub85c api_id \ubc0f api_key\ub294 https://habitica.com/user/settings/api \uc5d0\uc11c \uac00\uc838\uc640\uc57c \ud569\ub2c8\ub2e4." } } }, diff --git a/homeassistant/components/habitica/translations/nl.json b/homeassistant/components/habitica/translations/nl.json index 13a4fd6c7299a9..817ffd8c616d7e 100644 --- a/homeassistant/components/habitica/translations/nl.json +++ b/homeassistant/components/habitica/translations/nl.json @@ -8,8 +8,11 @@ "user": { "data": { "api_key": "API-sleutel", + "api_user": "Habitica's API-gebruikers-ID", + "name": "Vervanging voor de gebruikersnaam van Habitica. Wordt gebruikt voor serviceoproepen", "url": "URL" - } + }, + "description": "Verbind uw Habitica-profiel om het profiel en de taken van uw gebruiker te bewaken. Houd er rekening mee dat api_id en api_key van https://habitica.com/user/settings/api moeten worden gehaald" } } }, diff --git a/homeassistant/components/habitica/translations/pt.json b/homeassistant/components/habitica/translations/pt.json new file mode 100644 index 00000000000000..034099e4828a8f --- /dev/null +++ b/homeassistant/components/habitica/translations/pt.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_credentials": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "API Key", + "url": "" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/ca.json b/homeassistant/components/hangouts/translations/ca.json index 2d1d81bb08db66..b4114312f3e9e9 100644 --- a/homeassistant/components/hangouts/translations/ca.json +++ b/homeassistant/components/hangouts/translations/ca.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "La verificaci\u00f3 en dos passos no \u00e9s v\u00e0lida, torna-ho a provar.", - "invalid_2fa_method": "El m\u00e8tode de verificaci\u00f3 en dos passos no \u00e9s v\u00e0lid (verifica-ho al m\u00f2bil).", + "invalid_2fa_method": "M\u00e8tode 2FA inv\u00e0lid (verifica-ho al m\u00f2bil).", "invalid_login": "L'inici de sessi\u00f3 no \u00e9s v\u00e0lid, torna-ho a provar." }, "step": { "2fa": { "data": { - "2fa": "Pin 2FA" + "2fa": "PIN 2FA" }, "description": "Buit", "title": "Verificaci\u00f3 en dos passos" diff --git a/homeassistant/components/hangouts/translations/en.json b/homeassistant/components/hangouts/translations/en.json index 5de8ac249706f6..b2d7076bd75323 100644 --- a/homeassistant/components/hangouts/translations/en.json +++ b/homeassistant/components/hangouts/translations/en.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "Invalid 2 Factor Authentication, please try again.", - "invalid_2fa_method": "Invalid 2FA Method (Verify on Phone).", + "invalid_2fa_method": "Invalid 2FA Method (verify on Phone).", "invalid_login": "Invalid Login, please try again." }, "step": { "2fa": { "data": { - "2fa": "2FA Pin" + "2fa": "2FA PIN" }, "title": "2-Factor-Authentication" }, diff --git a/homeassistant/components/hangouts/translations/et.json b/homeassistant/components/hangouts/translations/et.json index 6bcc19d2043132..7d6deb2ef53bd6 100644 --- a/homeassistant/components/hangouts/translations/et.json +++ b/homeassistant/components/hangouts/translations/et.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "Vale 2-teguriline autentimine, proovi uuesti.", - "invalid_2fa_method": "Kehtetu 2FA meetod (kontrolli telefoni teel).", + "invalid_2fa_method": "Kehtetu kaheastmelise tuvastuse meetod (kontrolli telefonistl).", "invalid_login": "Vale kasutajanimi, palun proovi uuesti." }, "step": { "2fa": { "data": { - "2fa": "2FA PIN" + "2fa": "Kaheastmelise tuvastuse PIN" }, "description": "", "title": "Kaheastmeline autentimine" diff --git a/homeassistant/components/hangouts/translations/fr.json b/homeassistant/components/hangouts/translations/fr.json index 2e8bec54c34a06..68e652db30956f 100644 --- a/homeassistant/components/hangouts/translations/fr.json +++ b/homeassistant/components/hangouts/translations/fr.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "Code PIN d'authentification \u00e0 2 facteurs" + "2fa": "Code NIP d'authentification \u00e0 2 facteurs" }, "description": "Vide", "title": "Authentification \u00e0 2 facteurs" diff --git a/homeassistant/components/hangouts/translations/hu.json b/homeassistant/components/hangouts/translations/hu.json index 9a9f5b41598069..b81e3fcf0dd201 100644 --- a/homeassistant/components/hangouts/translations/hu.json +++ b/homeassistant/components/hangouts/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "A Google Hangouts m\u00e1r konfigur\u00e1lva van", - "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "invalid_2fa": "\u00c9rv\u00e9nytelen K\u00e9tfaktoros hiteles\u00edt\u00e9s, pr\u00f3b\u00e1ld \u00fajra.", @@ -14,7 +14,6 @@ "data": { "2fa": "2FA Pin" }, - "description": "\u00dcres", "title": "K\u00e9tfaktoros Hiteles\u00edt\u00e9s" }, "user": { @@ -22,7 +21,6 @@ "email": "E-mail", "password": "Jelsz\u00f3" }, - "description": "\u00dcres", "title": "Google Hangouts Bejelentkez\u00e9s" } } diff --git a/homeassistant/components/hangouts/translations/id.json b/homeassistant/components/hangouts/translations/id.json index 1bcfeaeba50ddf..39c68dda2110a5 100644 --- a/homeassistant/components/hangouts/translations/id.json +++ b/homeassistant/components/hangouts/translations/id.json @@ -1,29 +1,30 @@ { "config": { "abort": { - "already_configured": "Google Hangouts sudah dikonfigurasikan", - "unknown": "Kesalahan tidak dikenal terjadi." + "already_configured": "Layanan sudah dikonfigurasi", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "invalid_2fa": "Autentikasi 2 Faktor Tidak Valid, silakan coba lagi.", - "invalid_2fa_method": "Metode 2FA Tidak Sah (Verifikasi di Ponsel).", - "invalid_login": "Login tidak valid, silahkan coba lagi." + "invalid_2fa": "Autentikasi 2 Faktor Tidak Valid, coba lagi.", + "invalid_2fa_method": "Metode 2FA Tidak Valid (Verifikasikan di Ponsel).", + "invalid_login": "Info Masuk Tidak Valid, coba lagi." }, "step": { "2fa": { "data": { - "2fa": "Pin 2FA" + "2fa": "PIN 2FA" }, "description": "Kosong", - "title": "2-Faktor-Otentikasi" + "title": "Autentikasi Dua Faktor" }, "user": { "data": { - "email": "Alamat email", - "password": "Kata sandi" + "authorization_code": "Kode Otorisasi (diperlukan untuk autentikasi manual)", + "email": "Email", + "password": "Kata Sandi" }, "description": "Kosong", - "title": "Google Hangouts Login" + "title": "Info Masuk Google Hangouts" } } } diff --git a/homeassistant/components/hangouts/translations/ko.json b/homeassistant/components/hangouts/translations/ko.json index 3c23effaf4fa93..56c3c577a89542 100644 --- a/homeassistant/components/hangouts/translations/ko.json +++ b/homeassistant/components/hangouts/translations/ko.json @@ -7,7 +7,7 @@ "error": { "invalid_2fa": "2\ub2e8\uacc4 \uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "invalid_2fa_method": "2\ub2e8\uacc4 \uc778\uc99d \ubc29\ubc95\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. (\uc804\ud654\uae30\uc5d0\uc11c \ud655\uc778)", - "invalid_login": "\uc798\ubabb\ub41c \ub85c\uadf8\uc778\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "invalid_login": "\ub85c\uadf8\uc778\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "2fa": { diff --git a/homeassistant/components/hangouts/translations/nl.json b/homeassistant/components/hangouts/translations/nl.json index fac77660251a88..456d2193922615 100644 --- a/homeassistant/components/hangouts/translations/nl.json +++ b/homeassistant/components/hangouts/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts is al geconfigureerd", - "unknown": "Onbekende fout opgetreden." + "already_configured": "Service is al geconfigureerd", + "unknown": "Onverwachte fout" }, "error": { "invalid_2fa": "Ongeldige twee-factor-authenticatie, probeer het opnieuw.", @@ -20,7 +20,7 @@ "user": { "data": { "authorization_code": "Autorisatiecode (vereist voor handmatige authenticatie)", - "email": "E-mailadres", + "email": "E-mail", "password": "Wachtwoord" }, "description": "Leeg", diff --git a/homeassistant/components/hangouts/translations/no.json b/homeassistant/components/hangouts/translations/no.json index fa341509634aa3..d4fe4dbb5a6656 100644 --- a/homeassistant/components/hangouts/translations/no.json +++ b/homeassistant/components/hangouts/translations/no.json @@ -6,13 +6,13 @@ }, "error": { "invalid_2fa": "Ugyldig totrinnsbekreftelse, vennligst pr\u00f8v igjen.", - "invalid_2fa_method": "Ugyldig totrinnsbekreftelse-metode (Bekreft p\u00e5 telefon)", + "invalid_2fa_method": "Ugyldig 2FA-metode (bekreft p\u00e5 telefon).", "invalid_login": "Ugyldig innlogging, vennligst pr\u00f8v igjen." }, "step": { "2fa": { "data": { - "2fa": "Totrinnsbekreftelse Pin" + "2fa": "2FA PIN" }, "description": "", "title": "Totrinnsbekreftelse" diff --git a/homeassistant/components/hangouts/translations/pl.json b/homeassistant/components/hangouts/translations/pl.json index ff60deeece2c47..8fb7e9e64d92fd 100644 --- a/homeassistant/components/hangouts/translations/pl.json +++ b/homeassistant/components/hangouts/translations/pl.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "PIN" + "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" }, "description": "Pusty", "title": "Uwierzytelnianie dwusk\u0142adnikowe" diff --git a/homeassistant/components/hangouts/translations/zh-Hant.json b/homeassistant/components/hangouts/translations/zh-Hant.json index 62a220eaa9462d..678aacc5b62ca7 100644 --- a/homeassistant/components/hangouts/translations/zh-Hant.json +++ b/homeassistant/components/hangouts/translations/zh-Hant.json @@ -5,17 +5,17 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { - "invalid_2fa": "\u96d9\u91cd\u9a57\u8b49\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", - "invalid_2fa_method": "\u8a8d\u8b49\u65b9\u5f0f\u7121\u6548\uff08\u65bc\u96fb\u8a71\u4e0a\u9a57\u8b49\uff09\u3002", + "invalid_2fa": "\u96d9\u91cd\u8a8d\u8b49\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "invalid_2fa_method": "\u5169\u968e\u6bb5\u8a8d\u8b49\u65b9\u5f0f\u7121\u6548\uff08\u65bc\u96fb\u8a71\u4e0a\u9a57\u8b49\uff09\u3002", "invalid_login": "\u767b\u5165\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002" }, "step": { "2fa": { "data": { - "2fa": "\u8a8d\u8b49\u78bc" + "2fa": "\u5169\u968e\u6bb5\u8a8d\u8b49\u78bc" }, "description": "\u7a7a\u767d", - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "user": { "data": { diff --git a/homeassistant/components/harmony/translations/hu.json b/homeassistant/components/harmony/translations/hu.json index cbf055e2fba477..a9cb6ccecee7d7 100644 --- a/homeassistant/components/harmony/translations/hu.json +++ b/homeassistant/components/harmony/translations/hu.json @@ -1,5 +1,12 @@ { "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": { diff --git a/homeassistant/components/harmony/translations/id.json b/homeassistant/components/harmony/translations/id.json new file mode 100644 index 00000000000000..0d2991b1feb106 --- /dev/null +++ b/homeassistant/components/harmony/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "Ingin menyiapkan {name} ({host})?", + "title": "Siapkan Logitech Harmony Hub" + }, + "user": { + "data": { + "host": "Host", + "name": "Nama Hub" + }, + "title": "Siapkan Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "Aktivitas default yang akan dijalankan jika tidak ada yang ditentukan.", + "delay_secs": "Penundaan antara mengirim perintah." + }, + "description": "Sesuaikan Opsi Hub Harmony" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/nl.json b/homeassistant/components/harmony/translations/nl.json index 63d8026d9c2d02..33cbeca8893f71 100644 --- a/homeassistant/components/harmony/translations/nl.json +++ b/homeassistant/components/harmony/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "flow_title": "Logitech Harmony Hub {name}", @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Hostnaam of IP-adres", + "host": "Host", "name": "Naam van hub" }, "title": "Logitech Harmony Hub instellen" diff --git a/homeassistant/components/hassio/translations/af.json b/homeassistant/components/hassio/translations/af.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/af.json +++ b/homeassistant/components/hassio/translations/af.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/bg.json b/homeassistant/components/hassio/translations/bg.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/bg.json +++ b/homeassistant/components/hassio/translations/bg.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ca.json b/homeassistant/components/hassio/translations/ca.json index 19b4316c9ce6b9..d2e712c230d6fe 100644 --- a/homeassistant/components/hassio/translations/ca.json +++ b/homeassistant/components/hassio/translations/ca.json @@ -15,5 +15,5 @@ "version_api": "Versi\u00f3 d'APIs" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/cs.json b/homeassistant/components/hassio/translations/cs.json index cf1a28c3cc2f7a..eb64ed58baac0d 100644 --- a/homeassistant/components/hassio/translations/cs.json +++ b/homeassistant/components/hassio/translations/cs.json @@ -15,5 +15,5 @@ "version_api": "Verze API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/cy.json b/homeassistant/components/hassio/translations/cy.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/cy.json +++ b/homeassistant/components/hassio/translations/cy.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/da.json b/homeassistant/components/hassio/translations/da.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/da.json +++ b/homeassistant/components/hassio/translations/da.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/de.json b/homeassistant/components/hassio/translations/de.json index 939821edb54aa3..a0d02f4a7b90c1 100644 --- a/homeassistant/components/hassio/translations/de.json +++ b/homeassistant/components/hassio/translations/de.json @@ -14,5 +14,5 @@ "version_api": "Versions-API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/el.json b/homeassistant/components/hassio/translations/el.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/el.json +++ b/homeassistant/components/hassio/translations/el.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/en.json b/homeassistant/components/hassio/translations/en.json index 230e0c11feae84..16911be41109b2 100644 --- a/homeassistant/components/hassio/translations/en.json +++ b/homeassistant/components/hassio/translations/en.json @@ -15,5 +15,5 @@ "version_api": "Version API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/es-419.json b/homeassistant/components/hassio/translations/es-419.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/es-419.json +++ b/homeassistant/components/hassio/translations/es-419.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/es.json b/homeassistant/components/hassio/translations/es.json index 5faf32e515f7cc..f3bdf14c4468d2 100644 --- a/homeassistant/components/hassio/translations/es.json +++ b/homeassistant/components/hassio/translations/es.json @@ -15,5 +15,5 @@ "version_api": "Versi\u00f3n del API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/et.json b/homeassistant/components/hassio/translations/et.json index 9e5e776013f0c9..9d3ef08afbed7a 100644 --- a/homeassistant/components/hassio/translations/et.json +++ b/homeassistant/components/hassio/translations/et.json @@ -15,5 +15,5 @@ "version_api": "API versioon" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/eu.json b/homeassistant/components/hassio/translations/eu.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/eu.json +++ b/homeassistant/components/hassio/translations/eu.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fa.json b/homeassistant/components/hassio/translations/fa.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/fa.json +++ b/homeassistant/components/hassio/translations/fa.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fi.json b/homeassistant/components/hassio/translations/fi.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/fi.json +++ b/homeassistant/components/hassio/translations/fi.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/fr.json b/homeassistant/components/hassio/translations/fr.json index cef14b258c452a..e4fe8a63bba3d6 100644 --- a/homeassistant/components/hassio/translations/fr.json +++ b/homeassistant/components/hassio/translations/fr.json @@ -7,13 +7,13 @@ "docker_version": "Version de Docker", "healthy": "Sain", "host_os": "Syst\u00e8me d'exploitation h\u00f4te", - "installed_addons": "Add-ons install\u00e9s", - "supervisor_api": "API du superviseur", - "supervisor_version": "Version du supervisor", + "installed_addons": "Modules compl\u00e9mentaires install\u00e9s", + "supervisor_api": "API du Supervisor", + "supervisor_version": "Version du Supervisor", "supported": "Prise en charge", "update_channel": "Mise \u00e0 jour", "version_api": "Version API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/he.json b/homeassistant/components/hassio/translations/he.json index 981cb51c83ab8b..80c1a0c48eea7d 100644 --- a/homeassistant/components/hassio/translations/he.json +++ b/homeassistant/components/hassio/translations/he.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/hr.json b/homeassistant/components/hassio/translations/hr.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/hr.json +++ b/homeassistant/components/hassio/translations/hr.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/hu.json b/homeassistant/components/hassio/translations/hu.json index 216e8d391b620e..e0fc98408d49ac 100644 --- a/homeassistant/components/hassio/translations/hu.json +++ b/homeassistant/components/hassio/translations/hu.json @@ -7,12 +7,12 @@ "healthy": "Eg\u00e9szs\u00e9ges", "host_os": "Gazdag\u00e9p oper\u00e1ci\u00f3s rendszer", "installed_addons": "Telep\u00edtett kieg\u00e9sz\u00edt\u0151k", - "supervisor_api": "Adminisztr\u00e1tor API", - "supervisor_version": "Adminisztr\u00e1tor verzi\u00f3", + "supervisor_api": "Supervisor API", + "supervisor_version": "Supervisor verzi\u00f3", "supported": "T\u00e1mogatott", "update_channel": "Friss\u00edt\u00e9si csatorna", "version_api": "API verzi\u00f3" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/hy.json b/homeassistant/components/hassio/translations/hy.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/hy.json +++ b/homeassistant/components/hassio/translations/hy.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/id.json b/homeassistant/components/hassio/translations/id.json new file mode 100644 index 00000000000000..b95ffb35d81c97 --- /dev/null +++ b/homeassistant/components/hassio/translations/id.json @@ -0,0 +1,19 @@ +{ + "system_health": { + "info": { + "board": "Board", + "disk_total": "Total Disk", + "disk_used": "Disk Digunakan", + "docker_version": "Versi Docker", + "healthy": "Kesehatan", + "host_os": "Sistem Operasi Host", + "installed_addons": "Add-on yang Diinstal", + "supervisor_api": "API Supervisor", + "supervisor_version": "Versi Supervisor", + "supported": "Didukung", + "update_channel": "Kanal Pembaruan", + "version_api": "API Versi" + } + }, + "title": "Home Assistant Supervisor" +} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/is.json b/homeassistant/components/hassio/translations/is.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/is.json +++ b/homeassistant/components/hassio/translations/is.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/it.json b/homeassistant/components/hassio/translations/it.json index 385a0eedff24a0..7a375d3d78da60 100644 --- a/homeassistant/components/hassio/translations/it.json +++ b/homeassistant/components/hassio/translations/it.json @@ -15,5 +15,5 @@ "version_api": "Versione API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ja.json b/homeassistant/components/hassio/translations/ja.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/ja.json +++ b/homeassistant/components/hassio/translations/ja.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ko.json b/homeassistant/components/hassio/translations/ko.json index 981cb51c83ab8b..0acdd0c77a12f8 100644 --- a/homeassistant/components/hassio/translations/ko.json +++ b/homeassistant/components/hassio/translations/ko.json @@ -1,3 +1,19 @@ { - "title": "Hass.io" + "system_health": { + "info": { + "board": "\ubcf4\ub4dc \uc720\ud615", + "disk_total": "\ub514\uc2a4\ud06c \ucd1d \uc6a9\ub7c9", + "disk_used": "\ub514\uc2a4\ud06c \uc0ac\uc6a9\ub7c9", + "docker_version": "Docker \ubc84\uc804", + "healthy": "\uc2dc\uc2a4\ud15c \uc0c1\ud0dc", + "host_os": "\ud638\uc2a4\ud2b8 \uc6b4\uc601 \uccb4\uc81c", + "installed_addons": "\uc124\uce58\ub41c \ucd94\uac00 \uae30\ub2a5", + "supervisor_api": "Supervisor API", + "supervisor_version": "Supervisor \ubc84\uc804", + "supported": "\uc9c0\uc6d0 \uc5ec\ubd80", + "update_channel": "\uc5c5\ub370\uc774\ud2b8 \ucc44\ub110", + "version_api": "\ubc84\uc804 API" + } + }, + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/lb.json b/homeassistant/components/hassio/translations/lb.json index 54aae5a2c59550..c0d0f42ed94c8d 100644 --- a/homeassistant/components/hassio/translations/lb.json +++ b/homeassistant/components/hassio/translations/lb.json @@ -15,5 +15,5 @@ "version_api": "API Versioun" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/lt.json b/homeassistant/components/hassio/translations/lt.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/lt.json +++ b/homeassistant/components/hassio/translations/lt.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/lv.json b/homeassistant/components/hassio/translations/lv.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/lv.json +++ b/homeassistant/components/hassio/translations/lv.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/nb.json b/homeassistant/components/hassio/translations/nb.json deleted file mode 100644 index d8a4c453015156..00000000000000 --- a/homeassistant/components/hassio/translations/nb.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "" -} \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/nl.json b/homeassistant/components/hassio/translations/nl.json index fca08d49d7c3dc..7224857a10c6a0 100644 --- a/homeassistant/components/hassio/translations/nl.json +++ b/homeassistant/components/hassio/translations/nl.json @@ -1,6 +1,7 @@ { "system_health": { "info": { + "board": "Bord", "disk_total": "Totale schijfruimte", "disk_used": "Gebruikte schijfruimte", "docker_version": "Docker versie", @@ -14,5 +15,5 @@ "version_api": "API Versie" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/nn.json b/homeassistant/components/hassio/translations/nn.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/nn.json +++ b/homeassistant/components/hassio/translations/nn.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/no.json b/homeassistant/components/hassio/translations/no.json index e652f76a12c74f..9f0c5ba89b24ae 100644 --- a/homeassistant/components/hassio/translations/no.json +++ b/homeassistant/components/hassio/translations/no.json @@ -15,5 +15,5 @@ "version_api": "Versjon API" } }, - "title": "" + "title": "Home Assistant veileder" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/pl.json b/homeassistant/components/hassio/translations/pl.json index 10ee7c9d16ca3f..5266d640d7c447 100644 --- a/homeassistant/components/hassio/translations/pl.json +++ b/homeassistant/components/hassio/translations/pl.json @@ -15,5 +15,5 @@ "version_api": "Wersja API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/pt-BR.json b/homeassistant/components/hassio/translations/pt-BR.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/pt-BR.json +++ b/homeassistant/components/hassio/translations/pt-BR.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/pt.json b/homeassistant/components/hassio/translations/pt.json index 06083bae759951..326560409e4eac 100644 --- a/homeassistant/components/hassio/translations/pt.json +++ b/homeassistant/components/hassio/translations/pt.json @@ -13,5 +13,5 @@ "supported": "Suportado" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ro.json b/homeassistant/components/hassio/translations/ro.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/ro.json +++ b/homeassistant/components/hassio/translations/ro.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/ru.json b/homeassistant/components/hassio/translations/ru.json index 4f9b16621fc56f..56c3522ba3c293 100644 --- a/homeassistant/components/hassio/translations/ru.json +++ b/homeassistant/components/hassio/translations/ru.json @@ -15,5 +15,5 @@ "version_api": "\u0412\u0435\u0440\u0441\u0438\u044f API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/sk.json b/homeassistant/components/hassio/translations/sk.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/sk.json +++ b/homeassistant/components/hassio/translations/sk.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/sl.json b/homeassistant/components/hassio/translations/sl.json index eb2f5f7ca8b85e..cfc71ce0832e1d 100644 --- a/homeassistant/components/hassio/translations/sl.json +++ b/homeassistant/components/hassio/translations/sl.json @@ -14,5 +14,5 @@ "version_api": "API razli\u010dica" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/sv.json b/homeassistant/components/hassio/translations/sv.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/sv.json +++ b/homeassistant/components/hassio/translations/sv.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/th.json b/homeassistant/components/hassio/translations/th.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/th.json +++ b/homeassistant/components/hassio/translations/th.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/tr.json b/homeassistant/components/hassio/translations/tr.json index f2c2d52f60df92..06a8d3fd661797 100644 --- a/homeassistant/components/hassio/translations/tr.json +++ b/homeassistant/components/hassio/translations/tr.json @@ -15,5 +15,5 @@ "version_api": "S\u00fcr\u00fcm API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/uk.json b/homeassistant/components/hassio/translations/uk.json index 19a40730897bad..d25ad6e79795a3 100644 --- a/homeassistant/components/hassio/translations/uk.json +++ b/homeassistant/components/hassio/translations/uk.json @@ -15,5 +15,5 @@ "version_api": "\u0412\u0435\u0440\u0441\u0456\u044f API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/vi.json b/homeassistant/components/hassio/translations/vi.json index 981cb51c83ab8b..91588c5529aa44 100644 --- a/homeassistant/components/hassio/translations/vi.json +++ b/homeassistant/components/hassio/translations/vi.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/zh-Hans.json b/homeassistant/components/hassio/translations/zh-Hans.json index 0d74360b8f3385..a48cbeb95a8b0f 100644 --- a/homeassistant/components/hassio/translations/zh-Hans.json +++ b/homeassistant/components/hassio/translations/zh-Hans.json @@ -15,5 +15,5 @@ "version_api": "API \u7248\u672c" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/hassio/translations/zh-Hant.json b/homeassistant/components/hassio/translations/zh-Hant.json index 574b82358d6be3..b8b3a1e7b930ff 100644 --- a/homeassistant/components/hassio/translations/zh-Hant.json +++ b/homeassistant/components/hassio/translations/zh-Hant.json @@ -7,7 +7,7 @@ "docker_version": "Docker \u7248\u672c", "healthy": "\u5065\u5eb7\u5ea6", "host_os": "\u4e3b\u6a5f\u4f5c\u696d\u7cfb\u7d71", - "installed_addons": "\u5df2\u5b89\u88dd Add-on", + "installed_addons": "\u5df2\u5b89\u88dd\u9644\u52a0\u5143\u4ef6", "supervisor_api": "Supervisor API", "supervisor_version": "Supervisor \u7248\u672c", "supported": "\u652f\u63f4", @@ -15,5 +15,5 @@ "version_api": "\u7248\u672c API" } }, - "title": "Hass.io" + "title": "Home Assistant Supervisor" } \ No newline at end of file diff --git a/homeassistant/components/heos/translations/hu.json b/homeassistant/components/heos/translations/hu.json index cf688d6fdeb63f..2fbce1993cd031 100644 --- a/homeassistant/components/heos/translations/hu.json +++ b/homeassistant/components/heos/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, diff --git a/homeassistant/components/heos/translations/id.json b/homeassistant/components/heos/translations/id.json new file mode 100644 index 00000000000000..b7c57b46f66d4e --- /dev/null +++ b/homeassistant/components/heos/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Masukkan nama host atau alamat IP perangkat Heos (sebaiknya yang terhubung melalui kabel ke jaringan).", + "title": "Hubungkan ke Heos" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/ko.json b/homeassistant/components/heos/translations/ko.json index d17cbd0e4b71b5..5e4057ae482843 100644 --- a/homeassistant/components/heos/translations/ko.json +++ b/homeassistant/components/heos/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -12,7 +12,7 @@ "host": "\ud638\uc2a4\ud2b8" }, "description": "Heos \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. (\uc720\uc120 \ub124\ud2b8\uc6cc\ud06c\ub85c \uc5f0\uacb0\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4)", - "title": "Heos \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "Heos\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/hu.json b/homeassistant/components/hisense_aehw4a1/translations/hu.json index 0b21d7c4c3207e..c71972772ebc89 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/hu.json +++ b/homeassistant/components/hisense_aehw4a1/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "A h\u00e1l\u00f3zaton nem tal\u00e1lhat\u00f3 Hisense AEH-W4A1 eszk\u00f6z.", - "single_instance_allowed": "Csak egy konfigur\u00e1ci\u00f3 lehet Hisense AEH-W4A1 eset\u00e9n." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/id.json b/homeassistant/components/hisense_aehw4a1/translations/id.json new file mode 100644 index 00000000000000..eb31eb979c4ce9 --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan Hisense AEH-W4A1?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/ko.json b/homeassistant/components/hisense_aehw4a1/translations/ko.json index 491887280c03cd..dadb6cc79482c7 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ko.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/nl.json b/homeassistant/components/hisense_aehw4a1/translations/nl.json index 14f2445f63e3b0..c1f353558b6aba 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/nl.json +++ b/homeassistant/components/hisense_aehw4a1/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen Hisense AEH-W4A1-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts een enkele configuratie van Hisense AEH-W4A1 is mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/hive/translations/ca.json b/homeassistant/components/hive/translations/ca.json new file mode 100644 index 00000000000000..eacccda82e7ba1 --- /dev/null +++ b/homeassistant/components/hive/translations/ca.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament", + "unknown_entry": "No s'ha pogut trobar l'entrada existent." + }, + "error": { + "invalid_code": "No s'ha pogut iniciar sessi\u00f3 a Hive. El codi de verificaci\u00f3 en dos passos no \u00e9s correcte.", + "invalid_password": "No s'ha pogut iniciar sessi\u00f3 a Hive. Contrasenya incorrecta, torna-ho a provar.", + "invalid_username": "No s'ha pogut iniciar sessi\u00f3 a Hive. L'adre\u00e7a de correu electr\u00f2nic no s'ha reconegut.", + "no_internet_available": "Cal una connexi\u00f3 a Internet per connectar-se al Hive.", + "unknown": "Error inesperat" + }, + "step": { + "2fa": { + "data": { + "2fa": "Codi de verificaci\u00f3 en dos passos" + }, + "description": "Introdueix codi d'autenticaci\u00f3 Hive. \n\n Introdueix el codi 0000 per demanar un altre codi.", + "title": "Verificaci\u00f3 en dos passos de Hive." + }, + "reauth": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Torna a introduir la informaci\u00f3 d'inici de sessi\u00f3 del Hive.", + "title": "Inici de sessi\u00f3 Hive" + }, + "user": { + "data": { + "password": "Contrasenya", + "scan_interval": "Interval d'escaneig (segons)", + "username": "Nom d'usuari" + }, + "description": "Actualitza la informaci\u00f3 i configuraci\u00f3 d'inici de sessi\u00f3.", + "title": "Inici de sessi\u00f3 Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Interval d'escaneig (segons)" + }, + "description": "Actualitza l'interval d'escaneig per sondejar les dades m\u00e9s sovint.", + "title": "Opcions de Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/de.json b/homeassistant/components/hive/translations/de.json new file mode 100644 index 00000000000000..1bd84f7f61cd46 --- /dev/null +++ b/homeassistant/components/hive/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "username": "Benutzername" + } + }, + "user": { + "data": { + "password": "PAsswort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/en.json b/homeassistant/components/hive/translations/en.json index 1d491d64ebf86d..32453da0a0c49c 100644 --- a/homeassistant/components/hive/translations/en.json +++ b/homeassistant/components/hive/translations/en.json @@ -1,53 +1,53 @@ { - "config": { - "step": { - "user": { - "title": "Hive Login", - "description": "Enter your Hive login information and configuration.", - "data": { - "username": "Username", - "password": "Password", - "scan_interval": "Scan Interval (seconds)" + "config": { + "abort": { + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful", + "unknown_entry": "Unable to find existing entry." + }, + "error": { + "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", + "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", + "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", + "no_internet_available": "An internet connection is required to connect to Hive.", + "unknown": "Unexpected error" + }, + "step": { + "2fa": { + "data": { + "2fa": "Two-factor code" + }, + "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", + "title": "Hive Two-factor Authentication." + }, + "reauth": { + "data": { + "password": "Password", + "username": "Username" + }, + "description": "Re-enter your Hive login information.", + "title": "Hive Login" + }, + "user": { + "data": { + "password": "Password", + "scan_interval": "Scan Interval (seconds)", + "username": "Username" + }, + "description": "Enter your Hive login information and configuration.", + "title": "Hive Login" + } } - }, - "2fa": { - "title": "Hive Two-factor Authentication.", - "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", - "data": { - "2fa": "Two-factor code" - } - }, - "reauth": { - "title": "Hive Login", - "description": "Re-enter your Hive login information.", - "data": { - "username": "Username", - "password": "Password" - } - } }, - "error": { - "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", - "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", - "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", - "no_internet_available": "An internet connection is required to connect to Hive.", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Account is already configured", - "unknown_entry": "Unable to find existing entry.", - "reauth_successful": "Re-authentication was successful" - } - }, - "options": { - "step": { - "user": { - "title": "Options for Hive", - "description": "Update the scan interval to poll for data more often.", - "data": { - "scan_interval": "Scan Interval (seconds)" + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scan Interval (seconds)" + }, + "description": "Update the scan interval to poll for data more often.", + "title": "Options for Hive" + } } - } } - } } \ No newline at end of file diff --git a/homeassistant/components/hive/translations/et.json b/homeassistant/components/hive/translations/et.json new file mode 100644 index 00000000000000..5cffcb036d327c --- /dev/null +++ b/homeassistant/components/hive/translations/et.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus", + "unknown_entry": "Olemasolevat kirjet ei leitud." + }, + "error": { + "invalid_code": "Hive sisselogimine nurjus. Kaheastmeline autentimiskood oli vale.", + "invalid_password": "Hive sisselogimine nurjus. Vale parool, proovi uuesti.", + "invalid_username": "Hive sisselogimine nurjus. E-posti aadressi ei tuvastatud.", + "no_internet_available": "Hive-ga \u00fchenduse loomiseks on vajalik Interneti\u00fchendus.", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kaheastmelise tuvastuse kood" + }, + "description": "Sisesta oma Hive autentimiskood. \n\n Uue koodi taotlemiseks sisesta kood 0000.", + "title": "Hive kaheastmeline autentimine." + }, + "reauth": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Sisesta oma Hive sisselogimisandmed uuesti.", + "title": "Hive sisselogimine" + }, + "user": { + "data": { + "password": "Salas\u00f5na", + "scan_interval": "P\u00e4ringute intervall (sekundites)", + "username": "Kasutajanimi" + }, + "description": "Sisesta oma Hive sisselogimisteave ja s\u00e4tted.", + "title": "Hive sisselogimine" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "P\u00e4ringute intervall (sekundites)" + }, + "description": "Muuda k\u00fcsitlemise intervalli p\u00e4ringute tihendamiseks.", + "title": "Hive s\u00e4tted" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/fr.json b/homeassistant/components/hive/translations/fr.json new file mode 100644 index 00000000000000..5868a2bf175166 --- /dev/null +++ b/homeassistant/components/hive/translations/fr.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi", + "unknown_entry": "Impossible de trouver l'entr\u00e9e existante." + }, + "error": { + "invalid_code": "\u00c9chec de la connexion \u00e0 Hive. Votre code d'authentification \u00e0 deux facteurs \u00e9tait incorrect.", + "invalid_password": "\u00c9chec de la connexion \u00e0 Hive. Mot de passe incorrect, veuillez r\u00e9essayer.", + "invalid_username": "\u00c9chec de la connexion \u00e0 Hive. Votre adresse e-mail n'est pas reconnue.", + "no_internet_available": "Une connexion Internet est requise pour se connecter \u00e0 Hive.", + "unknown": "Erreur inattendue" + }, + "step": { + "2fa": { + "data": { + "2fa": "Code \u00e0 deux facteurs" + }, + "description": "Entrez votre code d\u2019authentification Hive. \n \nVeuillez entrer le code 0000 pour demander un autre code.", + "title": "Authentification \u00e0 deux facteurs Hive." + }, + "reauth": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "description": "Entrez \u00e0 nouveau vos informations de connexion Hive.", + "title": "Connexion \u00e0 Hive" + }, + "user": { + "data": { + "password": "Mot de passe", + "scan_interval": "Intervalle de scan (secondes)", + "username": "Nom d'utilisateur" + }, + "description": "Entrez vos informations de connexion et votre configuration Hive.", + "title": "Connexion \u00e0 Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervalle de scan (secondes)" + }, + "description": "Mettez \u00e0 jour l\u2019intervalle d\u2019analyse pour obtenir des donn\u00e9es plus souvent.", + "title": "Options pour Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/ko.json b/homeassistant/components/hive/translations/ko.json new file mode 100644 index 00000000000000..99c8c132229fb0 --- /dev/null +++ b/homeassistant/components/hive/translations/ko.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", + "unknown_entry": "\uae30\uc874 \ud56d\ubaa9\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_code": "Hive\uc5d0 \ub85c\uadf8\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. 2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "invalid_password": "Hive\uc5d0 \ub85c\uadf8\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ube44\ubc00\ubc88\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "invalid_username": "Hive\uc5d0 \ub85c\uadf8\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc778\uc2dd\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "no_internet_available": "Hive\uc5d0 \uc5f0\uacb0\ud558\ub824\uba74 \uc778\ud130\ub137 \uc5f0\uacb0\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "2fa": { + "data": { + "2fa": "2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc" + }, + "description": "Hive \uc778\uc99d \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n\ub2e4\ub978 \ucf54\ub4dc\ub97c \uc694\uccad\ud558\ub824\uba74 \ucf54\ub4dc 0000\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Hive 2\ub2e8\uacc4 \uc778\uc99d." + }, + "reauth": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "Hive \ub85c\uadf8\uc778 \uc815\ubcf4\ub97c \ub2e4\uc2dc \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Hive \ub85c\uadf8\uc778" + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "Hive \ub85c\uadf8\uc778 \uc815\ubcf4 \ubc0f \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Hive \ub85c\uadf8\uc778" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scan Interval (seconds)" + }, + "description": "\ub370\uc774\ud130\ub97c \ub354 \uc790\uc8fc \ud3f4\ub9c1\ud558\ub824\uba74 \uac80\uc0c9 \uac04\uaca9\uc744 \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694.", + "title": "Hive \uc635\uc158" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/pt.json b/homeassistant/components/hive/translations/pt.json new file mode 100644 index 00000000000000..46b7da5620a7d7 --- /dev/null +++ b/homeassistant/components/hive/translations/pt.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" + }, + "step": { + "reauth": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + }, + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/ru.json b/homeassistant/components/hive/translations/ru.json new file mode 100644 index 00000000000000..42df1bf9a244f2 --- /dev/null +++ b/homeassistant/components/hive/translations/ru.json @@ -0,0 +1,53 @@ +{ + "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.", + "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.", + "unknown_entry": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c." + }, + "error": { + "invalid_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Hive. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", + "invalid_password": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Hive. \u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", + "invalid_username": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0439\u0442\u0438 \u0432 Hive. \u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d.", + "no_internet_available": "\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "2fa": { + "data": { + "2fa": "\u041a\u043e\u0434 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 Hive \u0438\u043b\u0438 \u0432\u0432\u0435\u0434\u0438\u0442\u0435 0000, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u0434.", + "title": "\u0414\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f" + }, + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 Hive.", + "title": "Hive" + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 Hive.", + "title": "Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u0427\u0442\u043e\u0431\u044b \u0447\u0430\u0449\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430.", + "title": "Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/zh-Hant.json b/homeassistant/components/hive/translations/zh-Hant.json new file mode 100644 index 00000000000000..0af7e218f6e0bd --- /dev/null +++ b/homeassistant/components/hive/translations/zh-Hant.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f", + "unknown_entry": "\u7121\u6cd5\u627e\u5230\u73fe\u6709\u5be6\u9ad4\u3002" + }, + "error": { + "invalid_code": "Hive \u767b\u5165\u5931\u6557\u3002\u96d9\u91cd\u8a8d\u8b49\u78bc\u4e0d\u6b63\u78ba\u3002", + "invalid_password": "Hive \u767b\u5165\u5931\u6557\u3002\u5bc6\u78bc\u932f\u8aa4\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "invalid_username": "Hive \u767b\u5165\u5931\u6557\u3002\u627e\u4e0d\u5230\u96fb\u5b50\u90f5\u4ef6\u3002", + "no_internet_available": "\u9700\u8981\u7db2\u969b\u7db2\u8def\u9023\u7dda\u4ee5\u9023\u7dda\u81f3 Hive\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u96d9\u91cd\u8a8d\u8b49\u78bc" + }, + "description": "\u8f38\u5165 Hive \u8a8d\u8b49\u78bc\u3002\n \n \u8acb\u8f38\u5165 0000 \u4ee5\u7372\u53d6\u5176\u4ed6\u8a8d\u8b49\u78bc\u3002", + "title": "\u96d9\u91cd\u8a8d\u8b49" + }, + "reauth": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u91cd\u65b0\u8f38\u5165 Hive \u767b\u5165\u8cc7\u8a0a\u3002", + "title": "Hive \u767b\u5165\u8cc7\u8a0a" + }, + "user": { + "data": { + "password": "\u5bc6\u78bc", + "scan_interval": "\u6383\u63cf\u9593\u8ddd\uff08\u79d2\uff09", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8f38\u5165 Hive \u767b\u5165\u8cc7\u8a0a\u8207\u8a2d\u5b9a\u3002", + "title": "Hive \u767b\u5165\u8cc7\u8a0a" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\u6383\u63cf\u9593\u8ddd\uff08\u79d2\uff09" + }, + "description": "\u66f4\u65b0\u6383\u63cf\u9593\u8ddd\u4ee5\u66f4\u983b\u7e41\u7372\u53d6\u66f4\u65b0\u3002", + "title": "Hive \u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/hu.json b/homeassistant/components/hlk_sw16/translations/hu.json index 3b2d79a34a77e2..0abcc301f0c854 100644 --- a/homeassistant/components/hlk_sw16/translations/hu.json +++ b/homeassistant/components/hlk_sw16/translations/hu.json @@ -2,6 +2,20 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/id.json b/homeassistant/components/hlk_sw16/translations/id.json new file mode 100644 index 00000000000000..ed8fde321061cf --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/translations/hu.json b/homeassistant/components/home_connect/translations/hu.json index f02fb97b9df645..aa43f65b520e9e 100644 --- a/homeassistant/components/home_connect/translations/hu.json +++ b/homeassistant/components/home_connect/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/home_connect/translations/id.json b/homeassistant/components/home_connect/translations/id.json new file mode 100644 index 00000000000000..bc6089beb2b819 --- /dev/null +++ b/homeassistant/components/home_connect/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/fr.json b/homeassistant/components/homeassistant/translations/fr.json index 194254a0384696..8d76ff76b79c58 100644 --- a/homeassistant/components/homeassistant/translations/fr.json +++ b/homeassistant/components/homeassistant/translations/fr.json @@ -6,7 +6,7 @@ "dev": "D\u00e9veloppement", "docker": "Docker", "docker_version": "Docker", - "hassio": "Superviseur", + "hassio": "Supervisor", "host_os": "Home Assistant OS", "installation_type": "Type d'installation", "os_name": "Famille du syst\u00e8me d'exploitation", diff --git a/homeassistant/components/homeassistant/translations/he.json b/homeassistant/components/homeassistant/translations/he.json new file mode 100644 index 00000000000000..f45b17b1a13263 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/he.json @@ -0,0 +1,7 @@ +{ + "system_health": { + "info": { + "os_name": "\u05de\u05e9\u05e4\u05d7\u05ea \u05de\u05e2\u05e8\u05db\u05ea \u05d4\u05e4\u05e2\u05dc\u05d4" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/hu.json b/homeassistant/components/homeassistant/translations/hu.json index e202a747ac7532..f6bfe03321e5d7 100644 --- a/homeassistant/components/homeassistant/translations/hu.json +++ b/homeassistant/components/homeassistant/translations/hu.json @@ -6,13 +6,13 @@ "dev": "Fejleszt\u00e9s", "docker": "Docker", "docker_version": "Docker", - "hassio": "Adminisztr\u00e1tor", + "hassio": "Supervisor", "host_os": "Home Assistant OS", "installation_type": "Telep\u00edt\u00e9s t\u00edpusa", "os_name": "Oper\u00e1ci\u00f3s rendszer csal\u00e1d", "os_version": "Oper\u00e1ci\u00f3s rendszer verzi\u00f3ja", "python_version": "Python verzi\u00f3", - "supervisor": "Adminisztr\u00e1tor", + "supervisor": "Supervisor", "timezone": "Id\u0151z\u00f3na", "version": "Verzi\u00f3", "virtualenv": "Virtu\u00e1lis k\u00f6rnyezet" diff --git a/homeassistant/components/homeassistant/translations/id.json b/homeassistant/components/homeassistant/translations/id.json new file mode 100644 index 00000000000000..2ee86bba8157c8 --- /dev/null +++ b/homeassistant/components/homeassistant/translations/id.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "Arsitektur CPU", + "chassis": "Kerangka", + "dev": "Pengembangan", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Supervisor", + "host_os": "Home Assistant OS", + "installation_type": "Jenis Instalasi", + "os_name": "Keluarga Sistem Operasi", + "os_version": "Versi Sistem Operasi", + "python_version": "Versi Python", + "supervisor": "Supervisor", + "timezone": "Zona Waktu", + "version": "Versi", + "virtualenv": "Lingkungan Virtual" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/ko.json b/homeassistant/components/homeassistant/translations/ko.json new file mode 100644 index 00000000000000..801d63fd44992d --- /dev/null +++ b/homeassistant/components/homeassistant/translations/ko.json @@ -0,0 +1,21 @@ +{ + "system_health": { + "info": { + "arch": "CPU \uc544\ud0a4\ud14d\ucc98", + "chassis": "\uc100\uc2dc", + "dev": "\uac1c\ubc1c\uc790 \ubaa8\ub4dc", + "docker": "Docker", + "docker_version": "Docker", + "hassio": "Supervisor", + "host_os": "Home Assistant OS", + "installation_type": "\uc124\uce58 \uc720\ud615", + "os_name": "\uc6b4\uc601 \uccb4\uc81c \uc81c\ud488\uad70", + "os_version": "\uc6b4\uc601 \uccb4\uc81c \ubc84\uc804", + "python_version": "Python \ubc84\uc804", + "supervisor": "Supervisor", + "timezone": "\uc2dc\uac04\ub300", + "version": "\ubc84\uc804", + "virtualenv": "\uac00\uc0c1 \ud658\uacbd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant/translations/nl.json b/homeassistant/components/homeassistant/translations/nl.json index 47b69068ea34ac..b4e33dcd7aad1f 100644 --- a/homeassistant/components/homeassistant/translations/nl.json +++ b/homeassistant/components/homeassistant/translations/nl.json @@ -11,7 +11,7 @@ "installation_type": "Type installatie", "os_name": "Besturingssysteemfamilie", "os_version": "Versie van het besturingssysteem", - "python_version": "Python versie", + "python_version": "Python-versie", "supervisor": "Supervisor", "timezone": "Tijdzone", "version": "Versie", diff --git a/homeassistant/components/homekit/translations/he.json b/homeassistant/components/homekit/translations/he.json index 6acebca0ca47c0..cb5a530b739d2b 100644 --- a/homeassistant/components/homekit/translations/he.json +++ b/homeassistant/components/homekit/translations/he.json @@ -4,7 +4,8 @@ "include_exclude": { "data": { "mode": "\u05de\u05e6\u05d1" - } + }, + "title": "\u05d1\u05d7\u05e8 \u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05e9\u05d9\u05d9\u05db\u05dc\u05dc\u05d5" }, "init": { "data": { diff --git a/homeassistant/components/homekit/translations/hu.json b/homeassistant/components/homekit/translations/hu.json index 4cf1f44c439064..7cc2577cb313ca 100644 --- a/homeassistant/components/homekit/translations/hu.json +++ b/homeassistant/components/homekit/translations/hu.json @@ -1,16 +1,52 @@ { + "config": { + "step": { + "accessory_mode": { + "data": { + "entity_id": "Entit\u00e1s" + }, + "title": "V\u00e1laszd ki a felvenni k\u00edv\u00e1nt entit\u00e1st" + }, + "pairing": { + "title": "HomeKit p\u00e1ros\u00edt\u00e1s" + }, + "user": { + "data": { + "include_domains": "Felvenni k\u00edv\u00e1nt domainek", + "mode": "M\u00f3d" + }, + "title": "Felvenni k\u00edv\u00e1nt domainek kiv\u00e1laszt\u00e1sa" + } + } + }, "options": { "step": { + "advanced": { + "title": "Halad\u00f3 be\u00e1ll\u00edt\u00e1sok" + }, + "cameras": { + "data": { + "camera_copy": "A nat\u00edv H.264 streameket t\u00e1mogat\u00f3 kamer\u00e1k" + }, + "title": "V\u00e1laszd ki a kamera vide\u00f3 kodekj\u00e9t." + }, "include_exclude": { "data": { "entities": "Entit\u00e1sok", "mode": "M\u00f3d" - } + }, + "title": "V\u00e1laszd ki a felvenni k\u00edv\u00e1nt entit\u00e1sokat" }, "init": { "data": { + "include_domains": "Felvenni k\u00edv\u00e1nt domainek", "mode": "M\u00f3d" - } + }, + "title": "V\u00e1laszd ki a felvenni k\u00edv\u00e1nt domaineket." + }, + "yaml": { + "description": "Ez a bejegyz\u00e9s YAML-en kereszt\u00fcl vez\u00e9relhet\u0151", + "title": "HomeKit be\u00e1ll\u00edt\u00e1sok m\u00f3dos\u00edt\u00e1sa" } } } diff --git a/homeassistant/components/homekit/translations/id.json b/homeassistant/components/homekit/translations/id.json new file mode 100644 index 00000000000000..588631a5215ce5 --- /dev/null +++ b/homeassistant/components/homekit/translations/id.json @@ -0,0 +1,75 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Aksesori atau bridge dengan nama atau port yang sama telah dikonfigurasi." + }, + "step": { + "accessory_mode": { + "data": { + "entity_id": "Entitas" + }, + "description": "Pilih entitas yang akan disertakan. Dalam mode aksesori, hanya satu entitas yang disertakan.", + "title": "Pilih entitas yang akan disertakan" + }, + "bridge_mode": { + "data": { + "include_domains": "Domain yang disertakan" + }, + "description": "Pilih domain yang akan disertakan. Semua entitas yang didukung di domain akan disertakan.", + "title": "Pilih domain yang akan disertakan" + }, + "pairing": { + "description": "Untuk menyelesaikan pemasangan ikuti petunjuk di \"Notifikasi\" di bawah \"Pemasangan HomeKit\".", + "title": "Pasangkan HomeKit" + }, + "user": { + "data": { + "auto_start": "Mulai otomatis (nonaktifkan jika menggunakan Z-Wave atau sistem mulai tertunda lainnya)", + "include_domains": "Domain yang disertakan", + "mode": "Mode" + }, + "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 dan kamera.", + "title": "Pilih domain yang akan disertakan" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "Mulai otomatis (nonaktifkan jika Anda memanggil layanan homekit.start secara manual)", + "safe_mode": "Mode Aman (aktifkan hanya jika pemasangan gagal)" + }, + "description": "Pengaturan ini hanya perlu disesuaikan jika HomeKit tidak berfungsi.", + "title": "Konfigurasi Tingkat Lanjut" + }, + "cameras": { + "data": { + "camera_copy": "Kamera yang mendukung aliran H.264 asli" + }, + "description": "Periksa semua kamera yang mendukung streaming H.264 asli. Jika kamera tidak mengeluarkan aliran H.264, sistem akan mentranskode video ke H.264 untuk HomeKit. Proses transcoding membutuhkan CPU kinerja tinggi dan tidak mungkin bekerja pada komputer papan tunggal.", + "title": "Pilih codec video kamera." + }, + "include_exclude": { + "data": { + "entities": "Entitas", + "mode": "Mode" + }, + "description": "Pilih entitas yang akan disertakan. Dalam mode aksesori, hanya satu entitas yang disertakan. Dalam mode \"bridge include\", semua entitas di domain akan disertakan, kecuali entitas tertentu dipilih. Dalam mode \"bridge exclude\", semua entitas di domain akan disertakan, kecuali untuk entitas tertentu yang dipilih. Untuk kinerja terbaik, aksesori HomeKit terpisah diperlukan untuk masing-masing pemutar media, TV, dan kamera.", + "title": "Pilih entitas untuk disertakan" + }, + "init": { + "data": { + "include_domains": "Domain yang disertakan", + "mode": "Mode" + }, + "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." + }, + "yaml": { + "description": "Entri ini dikontrol melalui YAML", + "title": "Sesuaikan Opsi HomeKit" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ko.json b/homeassistant/components/homekit/translations/ko.json index bc8e138fbaf3b6..274898425cb90a 100644 --- a/homeassistant/components/homekit/translations/ko.json +++ b/homeassistant/components/homekit/translations/ko.json @@ -1,16 +1,25 @@ { "config": { "abort": { - "port_name_in_use": "\uc774\ub984\uc774\ub098 \ud3ec\ud2b8\uac00 \uac19\uc740 \ube0c\ub9ac\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "port_name_in_use": "\uc774\ub984\uc774\ub098 \ud3ec\ud2b8\uac00 \uac19\uc740 \ube0c\ub9ac\uc9c0 \ub610\ub294 \uc561\uc138\uc11c\ub9ac\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "\uad6c\uc131\uc694\uc18c" + }, + "description": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \ud3ec\ud568\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c \uc120\ud0dd\ud558\uae30" + }, "bridge_mode": { "data": { "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" - } + }, + "description": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc9c0\uc6d0\ub418\ub294 \ub3c4\uba54\uc778\uc758 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" }, "pairing": { - "description": "{name} \uc774(\uac00) \uc900\ube44\ub418\uba74 \"\uc54c\ub9bc\"\uc5d0\uc11c \"HomeKit \ube0c\ub9ac\uc9c0 \uc124\uc815\"\uc73c\ub85c \ud398\uc5b4\ub9c1\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\"\uc54c\ub9bc\"\uc5d0\uc11c \"HomeKit Pairing\"\uc5d0 \uc788\ub294 \uc548\ub0b4\uc5d0 \ub530\ub77c \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "HomeKit \ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { @@ -19,8 +28,8 @@ "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", "mode": "\ubaa8\ub4dc" }, - "description": "HomeKit \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \ud1b5\ud574 HomeKit \uc758 Home Assistant \uad6c\uc131\uc694\uc18c\uc5d0 \uc561\uc138\uc2a4\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \ubaa8\ub4dc\uc5d0\uc11c HomeKit \ube0c\ub9ac\uc9c0\ub294 \ube0c\ub9ac\uc9c0 \uc790\uccb4\ub97c \ud3ec\ud568\ud558\uc5ec \uc778\uc2a4\ud134\uc2a4\ub2f9 150 \uac1c\uc758 \uc561\uc138\uc11c\ub9ac\ub85c \uc81c\ud55c\ub429\ub2c8\ub2e4. \ucd5c\ub300 \uc561\uc138\uc11c\ub9ac \uac1c\uc218\ubcf4\ub2e4 \ub9ce\uc740 \uc218\uc758 \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub824\ub294 \uacbd\uc6b0, \uc11c\ub85c \ub2e4\ub978 \ub3c4\uba54\uc778\uc5d0 \ub300\ud574 \uc5ec\ub7ec\uac1c\uc758 HomeKit \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc758 \uc790\uc138\ud55c \uad6c\uc131\uc740 YAML \uc744 \ud1b5\ud574\uc11c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ucd5c\uc0c1\uc758 \uc131\ub2a5\uacfc \uc608\uae30\uce58 \uc54a\uc740 \uc0ac\uc6a9 \ubd88\uac00\ub2a5\ud55c \uc0c1\ud0dc\ub97c \ubc29\uc9c0\ud558\ub824\uba74 \uac01\uac01\uc758 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\uc5d0 \ub300\ud574 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c \ubcc4\ub3c4\uc758 HomeKit \uc778\uc2a4\ud134\uc2a4\ub97c \uc0dd\uc131\ud558\uace0 \ud398\uc5b4\ub9c1\ud574\uc8fc\uc138\uc694.", - "title": "HomeKit \ud65c\uc131\ud654\ud558\uae30" + "description": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ub3c4\uba54\uc778\uc5d0\uc11c \uc9c0\uc6d0\ub418\ub294 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \uac01 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\uc5d0 \ub300\ud574 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc758 \uac1c\ubcc4 HomeKit \uc778\uc2a4\ud134\uc2a4\uac00 \uc0dd\uc131\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" } } }, @@ -31,31 +40,34 @@ "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (homekit.start \uc11c\ube44\uc2a4\ub97c \uc218\ub3d9\uc73c\ub85c \ud638\ucd9c\ud558\ub824\uba74 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", "safe_mode": "\uc548\uc804 \ubaa8\ub4dc (\ud398\uc5b4\ub9c1\uc774 \uc2e4\ud328\ud55c \uacbd\uc6b0\uc5d0\ub9cc \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)" }, - "description": "\uc774 \uc124\uc815\uc740 HomeKit \uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "\uace0\uae09 \uad6c\uc131\ud558\uae30" + "description": "\uc774 \uc124\uc815\uc740 HomeKit\uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "title": "\uace0\uae09 \uad6c\uc131" }, "cameras": { "data": { "camera_copy": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \uce74\uba54\ub77c" }, - "description": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \ubaa8\ub4e0 \uce74\uba54\ub77c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \uce74\uba54\ub77c\uac00 H.264 \uc2a4\ud2b8\ub9bc\uc744 \ucd9c\ub825\ud558\uc9c0 \uc54a\uc73c\uba74 \uc2dc\uc2a4\ud15c\uc740 \ube44\ub514\uc624\ub97c HomeKit \uc6a9 H.264 \ud3ec\ub9f7\uc73c\ub85c \ubcc0\ud658\uc2dc\ud0b5\ub2c8\ub2e4. \ud2b8\ub79c\uc2a4\ucf54\ub529 \ubcc0\ud658\uc5d0\ub294 \ub192\uc740 CPU \uc131\ub2a5\uc774 \ud544\uc694\ud558\uba70 Raspberry Pi \uc640 \uac19\uc740 \ub2e8\uc77c \ubcf4\ub4dc \ucef4\ud4e8\ud130\uc5d0\uc11c\ub294 \uc791\ub3d9\ud558\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\ub124\uc774\ud2f0\ube0c H.264 \uc2a4\ud2b8\ub9bc\uc744 \uc9c0\uc6d0\ud558\ub294 \ubaa8\ub4e0 \uce74\uba54\ub77c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \uce74\uba54\ub77c\uac00 H.264 \uc2a4\ud2b8\ub9bc\uc744 \ucd9c\ub825\ud558\uc9c0 \uc54a\uc73c\uba74 \uc2dc\uc2a4\ud15c\uc740 \ube44\ub514\uc624\ub97c HomeKit\uc6a9 H.264 \ud3ec\ub9f7\uc73c\ub85c \ubcc0\ud658\uc2dc\ud0b5\ub2c8\ub2e4. \ud2b8\ub79c\uc2a4\ucf54\ub529 \ubcc0\ud658\uc5d0\ub294 \ub192\uc740 CPU \uc131\ub2a5\uc774 \ud544\uc694\ud558\uba70 Raspberry Pi\uc640 \uac19\uc740 \ub2e8\uc77c \ubcf4\ub4dc \ucef4\ud4e8\ud130\uc5d0\uc11c\ub294 \uc791\ub3d9\ud558\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\uce74\uba54\ub77c \ube44\ub514\uc624 \ucf54\ub371 \uc120\ud0dd\ud558\uae30" }, "include_exclude": { "data": { + "entities": "\uad6c\uc131\uc694\uc18c", "mode": "\ubaa8\ub4dc" - } + }, + "description": "\ud3ec\ud568\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \ud3ec\ud568\ub429\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \ud3ec\ud568 \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ud2b9\uc815 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud558\uc9c0 \uc54a\uc73c\uba74 \ub3c4\uba54\uc778\uc758 \ubaa8\ub4e0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ube0c\ub9ac\uc9c0 \uc81c\uc678 \ubaa8\ub4dc\uc5d0\uc11c\ub294 \uc81c\uc678\ub41c \uad6c\uc131\uc694\uc18c\ub97c \ube80 \ub3c4\uba54\uc778\uc758 \ub098\uba38\uc9c0 \uad6c\uc131\uc694\uc18c\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ucd5c\uc0c1\uc758 \uc131\ub2a5\uc744 \uc704\ud574 \uac01 TV \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uc640 \uce74\uba54\ub77c\ub294 \ubcc4\ub3c4\uc758 HomeKit \uc561\uc138\uc11c\ub9ac\ub85c \uc0dd\uc131\ub429\ub2c8\ub2e4.", + "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" }, "init": { "data": { "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778", "mode": "\ubaa8\ub4dc" }, - "description": "HomeKit \ub294 \ube0c\ub9ac\uc9c0 \ub610\ub294 \uc561\uc138\uc11c\ub9ac\ub97c \ub178\ucd9c\ud558\ub3c4\ub85d \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. TV \uae30\uae30 \ud074\ub798\uc2a4\uac00 \uc788\ub294 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uac00 \uc81c\ub300\ub85c \uc791\ub3d9\ud558\ub824\uba74 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\ub294 HomeKit \uc5d0 \ud3ec\ud568\ub429\ub2c8\ub2e4. \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc5d0 \ud3ec\ud568\ud558\uac70\ub098 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\ube0c\ub9ac\uc9c0 \ub610\ub294 \ub2e8\uc77c \uc561\uc138\uc11c\ub9ac\ub97c \ub178\ucd9c\ud558\uc5ec HomeKit\ub97c \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uc5d0\uc11c\ub294 \ub2e8\uc77c \uad6c\uc131\uc694\uc18c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. TV \uae30\uae30 \ud074\ub798\uc2a4\uac00 \uc788\ub294 \ubbf8\ub514\uc5b4 \ud50c\ub808\uc774\uc5b4\uac00 \uc81c\ub300\ub85c \uc791\ub3d9\ud558\ub824\uba74 \uc561\uc138\uc11c\ub9ac \ubaa8\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\uac00 HomeKit\uc5d0 \ud3ec\ud568\ub418\uba70, \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc5d0 \ud3ec\ud568\ud558\uac70\ub098 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "yaml": { - "description": "\uc774 \ud56d\ubaa9\uc740 YAML \uc744 \ud1b5\ud574 \uc81c\uc5b4\ub429\ub2c8\ub2e4", + "description": "\uc774 \ud56d\ubaa9\uc740 YAML\uc744 \ud1b5\ud574 \uc81c\uc5b4\ub429\ub2c8\ub2e4", "title": "HomeKit \uc635\uc158 \uc870\uc815\ud558\uae30" } } diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index 9013723ac6cd8b..dce21a4b880d63 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -7,10 +7,19 @@ "accessory_mode": { "data": { "entity_id": "Entiteit" - } + }, + "description": "Kies de entiteit die moet worden opgenomen. In de accessoiremodus wordt slechts \u00e9\u00e9n entiteit opgenomen.", + "title": "Selecteer de entiteit die u wilt opnemen" + }, + "bridge_mode": { + "data": { + "include_domains": "Domeinen om op te nemen" + }, + "description": "Kies de domeinen die moeten worden opgenomen. Alle ondersteunde entiteiten in het domein zullen worden opgenomen.", + "title": "Selecteer domeinen die u wilt opnemen" }, "pairing": { - "description": "Zodra de {name} klaar is, is het koppelen beschikbaar in \"Meldingen\" als \"HomeKit Bridge Setup\".", + "description": "Volg de instructies in \"Meldingen\" onder \"HomeKit-koppeling\" om het koppelen te voltooien.", "title": "Koppel HomeKit" }, "user": { @@ -45,11 +54,12 @@ "data": { "entities": "Entiteiten", "mode": "Mode" - } + }, + "title": "Selecteer de entiteiten die u wilt opnemen" }, "init": { "data": { - "include_domains": "Op te nemen domeinen", + "include_domains": "Domeinen om op te nemen", "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_controller/translations/bg.json b/homeassistant/components/homekit_controller/translations/bg.json index 8e2762f9f32885..014398897342a7 100644 --- a/homeassistant/components/homekit_controller/translations/bg.json +++ b/homeassistant/components/homekit_controller/translations/bg.json @@ -34,5 +34,19 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "\u0411\u0443\u0442\u043e\u043d 1", + "button10": "\u0411\u0443\u0442\u043e\u043d 10", + "button2": "\u0411\u0443\u0442\u043e\u043d 2", + "button3": "\u0411\u0443\u0442\u043e\u043d 3", + "button4": "\u0411\u0443\u0442\u043e\u043d 4", + "button5": "\u0411\u0443\u0442\u043e\u043d 5", + "button6": "\u0411\u0443\u0442\u043e\u043d 6", + "button7": "\u0411\u0443\u0442\u043e\u043d 7", + "button8": "\u0411\u0443\u0442\u043e\u043d 8", + "button9": "\u0411\u0443\u0442\u043e\u043d 9" + } + }, "title": "HomeKit \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/hu.json b/homeassistant/components/homekit_controller/translations/hu.json index 49e6fc53231e04..90e2405ed64f8a 100644 --- a/homeassistant/components/homekit_controller/translations/hu.json +++ b/homeassistant/components/homekit_controller/translations/hu.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Nem adhat\u00f3 hozz\u00e1 p\u00e1ros\u00edt\u00e1s, mert az eszk\u00f6z m\u00e1r nem tal\u00e1lhat\u00f3.", "already_configured": "A tartoz\u00e9k m\u00e1r konfigur\u00e1lva van ezzel a vez\u00e9rl\u0151vel.", - "already_in_progress": "Az eszk\u00f6z konfigur\u00e1ci\u00f3ja m\u00e1r folyamatban van.", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "already_paired": "Ez a tartoz\u00e9k m\u00e1r p\u00e1ros\u00edtva van egy m\u00e1sik eszk\u00f6zzel. \u00c1ll\u00edtsa alaphelyzetbe a tartoz\u00e9kot, majd pr\u00f3b\u00e1lkozzon \u00fajra.", "ignored_model": "A HomeKit t\u00e1mogat\u00e1sa e modelln\u00e9l blokkolva van, mivel a szolg\u00e1ltat\u00e1shoz teljes nat\u00edv integr\u00e1ci\u00f3 \u00e9rhet\u0151 el.", "invalid_config_entry": "Ez az eszk\u00f6z k\u00e9szen \u00e1ll a p\u00e1ros\u00edt\u00e1sra, de m\u00e1r van egy \u00fctk\u00f6z\u0151 konfigur\u00e1ci\u00f3s bejegyz\u00e9s a Home Assistant-ben, amelyet el\u0151sz\u00f6r el kell t\u00e1vol\u00edtani.", @@ -30,9 +30,29 @@ "device": "Eszk\u00f6z" }, "description": "V\u00e1lassza ki azt az eszk\u00f6zt, amelyet p\u00e1ros\u00edtani szeretne", - "title": "HomeKit tartoz\u00e9k p\u00e1ros\u00edt\u00e1sa" + "title": "Eszk\u00f6z kiv\u00e1laszt\u00e1sa" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Gomb 1", + "button10": "Gomb 10", + "button2": "Gomb 2", + "button3": "Gomb 3", + "button4": "Gomb 4", + "button5": "Gomb 5", + "button6": "Gomb 6", + "button7": "Gomb 7", + "button8": "Gomb 8", + "button9": "Gomb 9", + "doorbell": "Cseng\u0151" + }, + "trigger_type": { + "double_press": "\"{subtype}\" k\u00e9tszer lenyomva", + "long_press": "\"{subtype}\" lenyomva \u00e9s nyomva tartva", + "single_press": "\"{subtype}\" lenyomva" + } + }, "title": "HomeKit Vez\u00e9rl\u0151" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/id.json b/homeassistant/components/homekit_controller/translations/id.json new file mode 100644 index 00000000000000..49a37d3b3fb683 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/id.json @@ -0,0 +1,71 @@ +{ + "config": { + "abort": { + "accessory_not_found_error": "Tidak dapat menambahkan pemasangan karena perangkat tidak dapat ditemukan lagi.", + "already_configured": "Aksesori sudah dikonfigurasi dengan pengontrol ini.", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "already_paired": "Aksesori ini sudah dipasangkan ke perangkat lain. Setel ulang aksesori dan coba lagi.", + "ignored_model": "Dukungan HomeKit untuk model ini diblokir karena integrasi asli dengan fitur lebih lengkap telah tersedia.", + "invalid_config_entry": "Perangkat ini ditampilkan sebagai siap untuk dipasangkan tetapi sudah ada entri konfigurasi yang bertentangan untuk perangkat tersebut dalam Home Assistant, yang harus dihapus terlebih dulu.", + "invalid_properties": "Properti tidak valid diumumkan oleh perangkat.", + "no_devices": "Tidak ada perangkat yang belum dipasangkan yang dapat ditemukan" + }, + "error": { + "authentication_error": "Kode HomeKit salah. Periksa dan coba lagi.", + "max_peers_error": "Perangkat menolak untuk menambahkan pemasangan karena tidak memiliki penyimpanan pemasangan yang tersedia.", + "pairing_failed": "Terjadi kesalahan yang tidak tertangani saat mencoba memasangkan dengan perangkat ini. Ini mungkin kegagalan sementara atau perangkat Anda mungkin tidak didukung saat ini.", + "unable_to_pair": "Gagal memasangkan, coba lagi.", + "unknown_error": "Perangkat melaporkan kesalahan yang tidak diketahui. Pemasangan gagal." + }, + "flow_title": "{name} lewat HomeKit Accessory Protocol", + "step": { + "busy_error": { + "description": "Batalkan pemasangan di semua pengontrol, atau coba mulai ulang perangkat, lalu lanjutkan untuk melanjutkan pemasangan.", + "title": "Perangkat sudah dipasangkan dengan pengontrol lain" + }, + "max_tries_error": { + "description": "Perangkat telah menerima lebih dari 100 upaya autentikasi yang gagal. Coba mulai ulang perangkat, lalu lanjutkan pemasangan.", + "title": "Upaya autentikasi maksimum terlampaui" + }, + "pair": { + "data": { + "pairing_code": "Kode Pemasangan" + }, + "description": "Pengontrol HomeKit berkomunikasi dengan {name} melalui jaringan area lokal menggunakan koneksi terenkripsi yang aman tanpa pengontrol HomeKit atau iCloud terpisah. Masukkan kode pemasangan HomeKit Anda (dalam format XXX-XX-XXX) untuk menggunakan aksesori ini. Kode ini biasanya ditemukan pada perangkat itu sendiri atau dalam kemasan.", + "title": "Pasangkan dengan perangkat melalui HomeKit Accessory Protocol" + }, + "protocol_error": { + "description": "Perangkat mungkin tidak dalam mode pemasangan dan mungkin memerlukan tombol fisik atau virtual. Pastikan perangkat dalam mode pemasangan atau coba mulai ulang perangkat, lalu lanjutkan pemasangan.", + "title": "Terjadi kesalahan saat berkomunikasi dengan aksesori" + }, + "user": { + "data": { + "device": "Perangkat" + }, + "description": "Pengontrol HomeKit berkomunikasi melalui jaringan area lokal menggunakan koneksi terenkripsi yang aman tanpa pengontrol HomeKit atau iCloud terpisah. Pilih perangkat yang ingin Anda pasangkan:", + "title": "Pemilihan perangkat" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "Tombol 1", + "button10": "Tombol 10", + "button2": "Tombol 2", + "button3": "Tombol 3", + "button4": "Tombol 4", + "button5": "Tombol 5", + "button6": "Tombol 6", + "button7": "Tombol 7", + "button8": "Tombol 8", + "button9": "Tombol 9", + "doorbell": "Bel pintu" + }, + "trigger_type": { + "double_press": "\"{subtype}\" ditekan dua kali", + "long_press": "\"{subtype}\" ditekan dan ditahan", + "single_press": "\"{subtype}\" ditekan" + } + }, + "title": "Pengontrol HomeKit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/ko.json b/homeassistant/components/homekit_controller/translations/ko.json index 7314f43545ed93..c28573ee6ac136 100644 --- a/homeassistant/components/homekit_controller/translations/ko.json +++ b/homeassistant/components/homekit_controller/translations/ko.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "accessory_not_found_error": "\uae30\uae30\ub97c \ub354 \uc774\uc0c1 \ucc3e\uc744 \uc218 \uc5c6\uc73c\ubbc0\ub85c \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "accessory_not_found_error": "\uae30\uae30\ub97c \ub354 \uc774\uc0c1 \ucc3e\uc744 \uc218 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "already_configured": "\uc561\uc138\uc11c\ub9ac\uac00 \ucee8\ud2b8\ub864\ub7ec\uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "already_paired": "\uc774 \uc561\uc138\uc11c\ub9ac\ub294 \uc774\ubbf8 \ub2e4\ub978 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac\ub97c \uc7ac\uc124\uc815\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "ignored_model": "\uc774 \ubaa8\ub378\uc5d0 \ub300\ud55c HomeKit \uc9c0\uc6d0\uc740 \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc81c\uacf5\ud558\ub294 \uae30\ubcf8 \uad6c\uc131\uc694\uc18c\ub85c \uc778\ud574 \ucc28\ub2e8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant \uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.", - "invalid_properties": "\uc7a5\uce58\uc5d0\uc11c\uc120\uc5b8\ud55c \uc798\ubabb\ub41c \uc18d\uc131\uc785\ub2c8\ub2e4.", + "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant\uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.", + "invalid_properties": "\uae30\uae30\uc5d0\uc11c \uc798\ubabb\ub41c \uc18d\uc131\uc744 \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4.", "no_devices": "\ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud55c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { "authentication_error": "HomeKit \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud655\uc778 \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "max_peers_error": "\uae30\uae30\uc5d0 \ube44\uc5b4\uc788\ub294 \ud398\uc5b4\ub9c1 \uc7a5\uc18c\uac00 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1 \ucd94\uac00\ub97c \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "max_peers_error": "\uae30\uae30\uc5d0 \ube44\uc5b4\uc788\ub294 \ud398\uc5b4\ub9c1 \uc7a5\uc18c\uac00 \uc5c6\uc5b4 \ud398\uc5b4\ub9c1\uc744 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "pairing_failed": "\uc774 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\uc744 \uc2dc\ub3c4\ud558\ub294 \uc911 \ucc98\ub9ac\ub418\uc9c0 \uc54a\uc740 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. \uc77c\uc2dc\uc801\uc778 \uc624\ub958\uc774\uac70\ub098 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uae30\uae30 \uc77c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unable_to_pair": "\ud398\uc5b4\ub9c1 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "unknown_error": "\uae30\uae30\uc5d0\uc11c \uc54c \uc218\uc5c6\ub294 \uc624\ub958\ub97c \ubcf4\uace0\ud588\uc2b5\ub2c8\ub2e4. \ud398\uc5b4\ub9c1\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4." @@ -21,7 +21,7 @@ "step": { "busy_error": { "description": "\ubaa8\ub4e0 \ucee8\ud2b8\ub864\ub7ec\uc5d0\uc11c \ud398\uc5b4\ub9c1\uc744 \uc911\ub2e8\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", - "title": "\uae30\uae30\uac00 \uc774\ubbf8 \ub2e4\ub978 \ucee8\ud2b8\ub864\ub7ec\uc640 \ud398\uc774\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4" + "title": "\uae30\uae30\uac00 \uc774\ubbf8 \ub2e4\ub978 \ucee8\ud2b8\ub864\ub7ec\uc640 \ud398\uc774\ub9c1 \ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4" }, "max_tries_error": { "description": "\uae30\uae30\uac00 100\ud68c \uc774\uc0c1\uc758 \uc2e4\ud328\ud55c \uc778\uc99d \uc2dc\ub3c4\ub97c \ubc1b\uc558\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", @@ -31,11 +31,11 @@ "data": { "pairing_code": "\ud398\uc5b4\ub9c1 \ucf54\ub4dc" }, - "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c {name} \uacfc(\uc640) \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc (XX-XX-XXX \ud615\uc2dd) \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774 \ucf54\ub4dc\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uae30\uae30\ub098 \ud3ec\uc7a5 \ubc15\uc2a4\uc5d0 \ud45c\uc2dc\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud504\ub85c\ud1a0\ucf5c\uc744 \ud1b5\ud574 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1 \ud558\uae30" + "description": "HomeKit \ucee8\ud2b8\ub864\ub7ec\ub294 \ubcc4\ub3c4\uc758 HomeKit \ucee8\ud2b8\ub864\ub7ec \ub610\ub294 iCloud \uc5c6\uc774 \uc554\ud638\ud654\ub41c \ubcf4\uc548 \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uc5ec \ub85c\uceec \uc601\uc5ed \ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0\uc11c {name}\uacfc(\uc640) \ud1b5\uc2e0\ud569\ub2c8\ub2e4. \uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc(XX-XX-XXX \ud615\uc2dd)\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774 \ucf54\ub4dc\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \uae30\uae30\ub098 \ud3ec\uc7a5 \ubc15\uc2a4\uc5d0 \ud45c\uc2dc\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud504\ub85c\ud1a0\ucf5c\ub85c \uae30\uae30\uc640 \ud398\uc5b4\ub9c1 \ud558\uae30" }, "protocol_error": { - "description": "\uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\uc9c0 \uc54a\uc744 \uc218 \uc788\uc73c\uba70 \ubb3c\ub9ac\uc801 \ub610\ub294 \uac00\uc0c1 \uc758 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc57c \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", + "description": "\uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\uc9c0 \uc54a\uc744 \uc218 \uc788\uc73c\uba70 \ubb3c\ub9ac\uc801 \ub610\ub294 \uac00\uc0c1\uc758 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc57c \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uae30\uae30\uac00 \ud398\uc5b4\ub9c1 \ubaa8\ub4dc\uc5d0 \uc788\ub294\uc9c0 \ud655\uc778\ud558\uac70\ub098 \uae30\uae30\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ub2e4\uc74c \ud398\uc5b4\ub9c1\uc744 \uacc4\uc18d\ud574\uc8fc\uc138\uc694.", "title": "\uc561\uc138\uc11c\ub9ac\uc640 \ud1b5\uc2e0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "user": { @@ -62,9 +62,9 @@ "doorbell": "\ucd08\uc778\uc885" }, "trigger_type": { - "double_press": "\" {subtype} \"\uc744 \ub450\ubc88 \ub204\ub984", - "long_press": "\" {subtype} \"\uc744 \uae38\uac8c \ub204\ub984", - "single_press": "\"{subtype}\" \uc744 \ud55c\ubc88 \ub204\ub984" + "double_press": "\"{subtype}\"\uc774(\uac00) \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "long_press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub824\uc9c4 \ucc44\ub85c \uc788\uc744 \ub54c", + "single_press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub838\uc744 \ub54c" } }, "title": "HomeKit \ucee8\ud2b8\ub864\ub7ec" diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index ce4279229ab709..46167e9da987af 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -19,19 +19,25 @@ }, "flow_title": "HomeKit-accessoire: {name}", "step": { + "max_tries_error": { + "title": "Maximum aantal authenticatiepogingen overschreden" + }, "pair": { "data": { "pairing_code": "Koppelingscode" }, - "description": "Voer uw HomeKit pairing code (in het formaat XXX-XX-XXX) om dit accessoire te gebruiken", + "description": "HomeKit Controller communiceert met {name} via het lokale netwerk met behulp van een beveiligde versleutelde verbinding zonder een aparte HomeKit-controller of iCloud. Voer uw HomeKit-koppelcode in (in de indeling XXX-XX-XXX) om dit accessoire te gebruiken. Deze code is meestal te vinden op het apparaat zelf of in de verpakking.", "title": "Koppel met HomeKit accessoire" }, + "protocol_error": { + "title": "Fout bij het communiceren met de accessoire" + }, "user": { "data": { "device": "Apparaat" }, - "description": "Selecteer het apparaat waarmee u wilt koppelen", - "title": "Koppel met HomeKit accessoire" + "description": "HomeKit Controller communiceert via het lokale netwerk met behulp van een veilige versleutelde verbinding zonder een aparte HomeKit-controller of iCloud. Selecteer het apparaat dat u wilt koppelen:", + "title": "Apparaat selectie" } } }, @@ -55,5 +61,5 @@ "single_press": "\" {subtype} \" ingedrukt" } }, - "title": "HomeKit Accessoires" + "title": "HomeKit Controller" } \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/hu.json b/homeassistant/components/homematicip_cloud/translations/hu.json index 1ae318c45ad028..eaa8d8834c36d0 100644 --- a/homeassistant/components/homematicip_cloud/translations/hu.json +++ b/homeassistant/components/homematicip_cloud/translations/hu.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "A hozz\u00e1f\u00e9r\u00e9si pontot m\u00e1r konfigur\u00e1ltuk", - "connection_aborted": "Nem siker\u00fclt csatlakozni a HMIP szerverhez", - "unknown": "Unknown error occurred." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "connection_aborted": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { - "invalid_sgtin_or_pin": "\u00c9rv\u00e9nytelen PIN, pr\u00f3b\u00e1lkozz \u00fajra.", + "invalid_sgtin_or_pin": "\u00c9rv\u00e9nytelen PIN-k\u00f3d, pr\u00f3b\u00e1lkozz \u00fajra.", "press_the_button": "Nyomd meg a k\u00e9k gombot.", "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, pr\u00f3b\u00e1ld \u00fajra.", "timeout_button": "K\u00e9k gomb megnyom\u00e1s\u00e1nak id\u0151t\u00fall\u00e9p\u00e9se, pr\u00f3b\u00e1lkozz \u00fajra." @@ -16,7 +16,7 @@ "data": { "hapid": "Hozz\u00e1f\u00e9r\u00e9si pont azonos\u00edt\u00f3ja (SGTIN)", "name": "N\u00e9v (opcion\u00e1lis, minden eszk\u00f6z n\u00e9vel\u0151tagjak\u00e9nt haszn\u00e1latos)", - "pin": "Pin k\u00f3d (opcion\u00e1lis)" + "pin": "PIN-k\u00f3d" }, "title": "V\u00e1lassz HomematicIP hozz\u00e1f\u00e9r\u00e9si pontot" }, diff --git a/homeassistant/components/homematicip_cloud/translations/id.json b/homeassistant/components/homematicip_cloud/translations/id.json index 43525955c3674a..26679d3b37f21c 100644 --- a/homeassistant/components/homematicip_cloud/translations/id.json +++ b/homeassistant/components/homematicip_cloud/translations/id.json @@ -1,28 +1,28 @@ { "config": { "abort": { - "already_configured": "Jalur akses sudah dikonfigurasi", - "connection_aborted": "Tidak dapat terhubung ke server HMIP", - "unknown": "Kesalahan tidak dikenal terjadi." + "already_configured": "Perangkat sudah dikonfigurasi", + "connection_aborted": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "invalid_sgtin_or_pin": "PIN tidak valid, silakan coba lagi.", - "press_the_button": "Silakan tekan tombol biru.", - "register_failed": "Gagal mendaftar, silakan coba lagi.", - "timeout_button": "Batas waktu tekan tombol biru berakhir, silakan coba lagi." + "invalid_sgtin_or_pin": "SGTIN atau Kode PIN tidak valid, coba lagi.", + "press_the_button": "Tekan tombol biru.", + "register_failed": "Gagal mendaftar, coba lagi.", + "timeout_button": "Tenggang waktu penekanan tombol biru berakhir, coba lagi." }, "step": { "init": { "data": { "hapid": "Titik akses ID (SGTIN)", - "name": "Nama (opsional, digunakan sebagai awalan nama untuk semua perangkat)", - "pin": "Kode Pin (opsional)" + "name": "Nama (opsional, digunakan sebagai prefiks nama untuk semua perangkat)", + "pin": "Kode PIN" }, - "title": "Pilih HomematicIP Access point" + "title": "Pilih Access Point HomematicIP" }, "link": { - "description": "Tekan tombol biru pada access point dan tombol submit untuk mendaftarkan HomematicIP dengan rumah asisten.\n\n! [Lokasi tombol di bridge] (/ static/images/config_flows/config_homematicip_cloud.png)", - "title": "Tautkan jalur akses" + "description": "Tekan tombol biru pada access point dan tombol sukirimbmit untuk mendaftarkan HomematicIP dengan Home Assistant.\n\n![Lokasi tombol di bridge](/static/images/config_flows/config_homematicip_cloud.png)", + "title": "Tautkan Titik akses" } } } diff --git a/homeassistant/components/homematicip_cloud/translations/ko.json b/homeassistant/components/homematicip_cloud/translations/ko.json index 6a15b21de84c98..07f6c5c70fc741 100644 --- a/homeassistant/components/homematicip_cloud/translations/ko.json +++ b/homeassistant/components/homematicip_cloud/translations/ko.json @@ -6,7 +6,7 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_sgtin_or_pin": "\uc798\ubabb\ub41c SGTIN \uc774\uac70\ub098 PIN \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "invalid_sgtin_or_pin": "SGTIN \ub610\ub294 PIN \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "press_the_button": "\ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", "register_failed": "\ub4f1\ub85d\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "timeout_button": "\uc815\ud574\uc9c4 \uc2dc\uac04\ub0b4\uc5d0 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub974\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." @@ -15,13 +15,13 @@ "init": { "data": { "hapid": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 ID (SGTIN)", - "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc5b4\ub85c \uc0ac\uc6a9)", + "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc0ac\ub85c \uc0ac\uc6a9)", "pin": "PIN \ucf54\ub4dc" }, "title": "HomematicIP \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc120\ud0dd\ud558\uae30" }, "link": { - "description": "Home Assistant \uc5d0 HomematicIP \ub97c \ub4f1\ub85d\ud558\ub824\uba74 \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc758 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uacfc \ud655\uc778\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n\n![\ube0c\ub9ac\uc9c0\uc758 \ubc84\ud2bc \uc704\uce58 \ubcf4\uae30](/static/images/config_flows/config_homematicip_cloud.png)", + "description": "Home Assistant\uc5d0 HomematicIP\ub97c \ub4f1\ub85d\ud558\ub824\uba74 \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc758 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uacfc \ud655\uc778\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n\n![\ube0c\ub9ac\uc9c0\uc758 \ubc84\ud2bc \uc704\uce58 \ubcf4\uae30](/static/images/config_flows/config_homematicip_cloud.png)", "title": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/homematicip_cloud/translations/nl.json b/homeassistant/components/homematicip_cloud/translations/nl.json index 7127b5c5aae98e..cb65dee7bd1135 100644 --- a/homeassistant/components/homematicip_cloud/translations/nl.json +++ b/homeassistant/components/homematicip_cloud/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Accesspoint is al geconfigureerd", - "connection_aborted": "Kon geen verbinding maken met de HMIP-server", - "unknown": "Er is een onbekende fout opgetreden." + "already_configured": "Apparaat is al geconfigureerd", + "connection_aborted": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" }, "error": { - "invalid_sgtin_or_pin": "Ongeldige PIN-code, probeer het nogmaals.", + "invalid_sgtin_or_pin": "Ongeldige SGTIN of PIN-code, probeer het opnieuw.", "press_the_button": "Druk op de blauwe knop.", "register_failed": "Kan niet registreren, gelieve opnieuw te proberen.", "timeout_button": "Blauwe knop druk op timeout, probeer het opnieuw." @@ -15,8 +15,8 @@ "init": { "data": { "hapid": "Accesspoint ID (SGTIN)", - "name": "Naam (optioneel, gebruikt als naamprefix voor alle apparaten)", - "pin": "Pin-Code (optioneel)" + "name": "Naam (optioneel, gebruikt als naamvoorvoegsel voor alle apparaten)", + "pin": "PIN-code" }, "title": "Kies HomematicIP accesspoint" }, diff --git a/homeassistant/components/huawei_lte/translations/he.json b/homeassistant/components/huawei_lte/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/hu.json b/homeassistant/components/huawei_lte/translations/hu.json index 90814fea5a529b..815794133d2b01 100644 --- a/homeassistant/components/huawei_lte/translations/hu.json +++ b/homeassistant/components/huawei_lte/translations/hu.json @@ -1,20 +1,24 @@ { "config": { "abort": { - "already_configured": "Ez az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "Ez az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "not_huawei_lte": "Nem Huawei LTE eszk\u00f6z" }, "error": { "connection_timeout": "Kapcsolat id\u0151t\u00fall\u00e9p\u00e9se", "incorrect_password": "Hib\u00e1s jelsz\u00f3", "incorrect_username": "Helytelen felhaszn\u00e1l\u00f3n\u00e9v", - "invalid_url": "\u00c9rv\u00e9nytelen URL" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_url": "\u00c9rv\u00e9nytelen URL", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Huawei LTE: {name}", "step": { "user": { "data": { "password": "Jelsz\u00f3", + "url": "URL", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, "title": "Huawei LTE konfigur\u00e1l\u00e1sa" diff --git a/homeassistant/components/huawei_lte/translations/id.json b/homeassistant/components/huawei_lte/translations/id.json new file mode 100644 index 00000000000000..2077b31ccd7d60 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "not_huawei_lte": "Bukan perangkat Huawei LTE" + }, + "error": { + "connection_timeout": "Tenggang waktu terhubung habis", + "incorrect_password": "Kata sandi salah", + "incorrect_username": "Nama pengguna salah", + "invalid_auth": "Autentikasi tidak valid", + "invalid_url": "URL tidak valid", + "login_attempts_exceeded": "Upaya login maksimum telah terlampaui, coba lagi nanti", + "response_error": "Kesalahan tidak dikenal dari perangkat", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Huawei LTE: {name}", + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "url": "URL", + "username": "Nama Pengguna" + }, + "description": "Masukkan detail akses perangkat. Menentukan nama pengguna dan kata sandi bersifat opsional, tetapi memungkinkan dukungan untuk fitur integrasi lainnya. Selain itu, penggunaan koneksi resmi dapat menyebabkan masalah mengakses antarmuka web perangkat dari luar Home Assistant saat integrasi aktif, dan sebaliknya.", + "title": "Konfigurasikan Huawei LTE" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nama layanan notifikasi (perubahan harus dimulai ulang)", + "recipient": "Penerima notifikasi SMS", + "track_new_devices": "Lacak perangkat baru" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index 73274d15bfb90a..1713b81cf7e36b 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -23,7 +23,7 @@ "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant \uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant\uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Huawei LTE \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/hue/translations/hu.json b/homeassistant/components/hue/translations/hu.json index c3e3203a66c1a0..d0aa043b10b44d 100644 --- a/homeassistant/components/hue/translations/hu.json +++ b/homeassistant/components/hue/translations/hu.json @@ -2,14 +2,16 @@ "config": { "abort": { "all_configured": "M\u00e1r minden Philips Hue bridge konfigur\u00e1lt", - "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van", - "cannot_connect": "Nem siker\u00fclt csatlakozni a bridge-hez.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "discover_timeout": "Nem tal\u00e1ltam a Hue bridget", "no_bridges": "Nem tal\u00e1ltam Philips Hue bridget", + "not_hue_bridge": "Nem egy Hue Bridge", "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt" }, "error": { - "linking": "Ismeretlen \u00f6sszekapcsol\u00e1si hiba t\u00f6rt\u00e9nt.", + "linking": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, k\u00e9rem pr\u00f3b\u00e1lja \u00fajra" }, "step": { @@ -22,6 +24,12 @@ "link": { "description": "Nyomja meg a gombot a bridge-en a Philips Hue Home Assistant-ben val\u00f3 regisztr\u00e1l\u00e1s\u00e1hoz.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)", "title": "Kapcsol\u00f3d\u00e1s a hubhoz" + }, + "manual": { + "data": { + "host": "Hoszt" + }, + "title": "A Hue bridge manu\u00e1lis konfigur\u00e1l\u00e1sa" } } }, @@ -29,6 +37,10 @@ "trigger_subtype": { "turn_off": "Kikapcsol\u00e1s", "turn_on": "Bekapcsol\u00e1s" + }, + "trigger_type": { + "remote_button_short_press": "\"{subtype}\" gomb lenyomva", + "remote_button_short_release": "\"{subtype}\" gomb elengedve" } } } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/id.json b/homeassistant/components/hue/translations/id.json index 2af5753f6548b1..c9e0bcd75d4cc2 100644 --- a/homeassistant/components/hue/translations/id.json +++ b/homeassistant/components/hue/translations/id.json @@ -1,27 +1,66 @@ { "config": { "abort": { - "all_configured": "Semua Philips Hue bridges sudah dikonfigurasi", - "already_configured": "Bridge sudah dikonfigurasi", - "cannot_connect": "Tidak dapat terhubung ke bridge", - "discover_timeout": "Tidak dapat menemukan Hue Bridges.", + "all_configured": "Semua bridge Philips Hue sudah dikonfigurasi", + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung", + "discover_timeout": "Tidak dapat menemukan bridge Hue", "no_bridges": "Bridge Philips Hue tidak ditemukan", - "unknown": "Kesalahan tidak dikenal terjadi." + "not_hue_bridge": "Bukan bridge Hue", + "unknown": "Kesalahan yang tidak diharapkan" }, "error": { - "linking": "Terjadi kesalahan tautan tidak dikenal.", - "register_failed": "Gagal mendaftar, silakan coba lagi." + "linking": "Kesalahan yang tidak diharapkan", + "register_failed": "Gagal mendaftar, coba lagi." }, "step": { "init": { "data": { "host": "Host" }, - "title": "Pilih Hue bridge" + "title": "Pilih bridge Hue" }, "link": { - "description": "Tekan tombol di bridge untuk mendaftar Philips Hue dengan Home Assistant.\n\n![Lokasi tombol di bridge](/static/images/config_philips_hue.jpg)", - "title": "Tautan Hub" + "description": "Tekan tombol di bridge untuk mendaftarkan Philips Hue dengan Home Assistant.\n\n![Lokasi tombol di bridge](/static/images/config_philips_hue.jpg)", + "title": "Tautkan Hub" + }, + "manual": { + "data": { + "host": "Host" + }, + "title": "Konfigurasi bridge Hue secara manual" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "dim_down": "Redupkan", + "dim_up": "Terangkan", + "double_buttons_1_3": "Tombol Pertama dan Ketiga", + "double_buttons_2_4": "Tombol Kedua dan Keempat", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "remote_button_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_button_short_press": "Tombol \"{subtype}\" ditekan", + "remote_button_short_release": "Tombol \"{subtype}\" dilepaskan", + "remote_double_button_long_press": "Kedua \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_double_button_short_press": "Kedua \"{subtype}\" dilepas" + } + }, + "options": { + "step": { + "init": { + "data": { + "allow_hue_groups": "Izinkan grup Hue", + "allow_unreachable": "Izinkan bohlam yang tidak dapat dijangkau untuk melaporkan statusnya dengan benar" + } } } } diff --git a/homeassistant/components/hue/translations/ko.json b/homeassistant/components/hue/translations/ko.json index 846ea937515105..30da5cee4849f3 100644 --- a/homeassistant/components/hue/translations/ko.json +++ b/homeassistant/components/hue/translations/ko.json @@ -47,11 +47,11 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_double_button_long_press": "\"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_double_button_short_press": "\"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uc190\uc744 \ub5c4 \ub54c" + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "remote_double_button_long_press": "\ub450 \"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_double_button_short_press": "\ub450 \"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c" } }, "options": { diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index cead9dd21c64db..5cc6a3572e2a61 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "Alle Philips Hue bridges zijn al geconfigureerd", - "already_configured": "Bridge is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", - "cannot_connect": "Kan geen verbinding maken met bridge", + "cannot_connect": "Kan geen verbinding maken", "discover_timeout": "Hue bridges kunnen niet worden gevonden", "no_bridges": "Geen Philips Hue bridges ontdekt", "not_hue_bridge": "Dit is geen Hue bridge", "unknown": "Onverwachte fout" }, "error": { - "linking": "Er is een onbekende verbindingsfout opgetreden.", + "linking": "Onverwachte fout", "register_failed": "Registratie is mislukt, probeer het opnieuw" }, "step": { diff --git a/homeassistant/components/hue/translations/zh-Hans.json b/homeassistant/components/hue/translations/zh-Hans.json index c34bd68aad86e7..1dcc6b8f59b92a 100644 --- a/homeassistant/components/hue/translations/zh-Hans.json +++ b/homeassistant/components/hue/translations/zh-Hans.json @@ -29,6 +29,13 @@ "device_automation": { "trigger_subtype": { "turn_off": "\u5173\u95ed" + }, + "trigger_type": { + "remote_button_long_release": "\"{subtype}\" \u957f\u6309\u540e\u677e\u5f00", + "remote_button_short_press": "\"{subtype}\" \u5355\u51fb", + "remote_button_short_release": "\"{subtype}\" \u677e\u5f00", + "remote_double_button_long_press": "\"{subtype}\" \u4e24\u952e\u540c\u65f6\u957f\u6309\u540e\u677e\u5f00", + "remote_double_button_short_press": "\"{subtype}\" \u4e24\u952e\u540c\u65f6\u677e\u5f00" } } } \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/bg.json b/homeassistant/components/huisbaasje/translations/bg.json new file mode 100644 index 00000000000000..67a484573aa0c1 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/huisbaasje/translations/hu.json b/homeassistant/components/huisbaasje/translations/hu.json new file mode 100644 index 00000000000000..a80b4b9b093307 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "connection_exception": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unauthenticated_exception": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/id.json b/homeassistant/components/huisbaasje/translations/id.json new file mode 100644 index 00000000000000..76e8805524e8a2 --- /dev/null +++ b/homeassistant/components/huisbaasje/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "connection_exception": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unauthenticated_exception": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/hu.json b/homeassistant/components/humidifier/translations/hu.json new file mode 100644 index 00000000000000..7dd723df738511 --- /dev/null +++ b/homeassistant/components/humidifier/translations/hu.json @@ -0,0 +1,28 @@ +{ + "device_automation": { + "action_type": { + "set_humidity": "{entity_name} p\u00e1ratartalom be\u00e1ll\u00edt\u00e1sa", + "set_mode": "{entity_name} m\u00f3d m\u00f3dos\u00edt\u00e1sa", + "toggle": "{entity_name} be/kikapcsol\u00e1sa", + "turn_off": "{entity_name} kikapcsol\u00e1sa", + "turn_on": "{entity_name} bekapcsol\u00e1sa" + }, + "condition_type": { + "is_mode": "A(z) {entity_name} egy adott m\u00f3dra van \u00e1ll\u00edtva", + "is_off": "{entity_name} ki van kapcsolva", + "is_on": "{entity_name} be van kapcsolva" + }, + "trigger_type": { + "target_humidity_changed": "{name} k\u00edv\u00e1nt p\u00e1ratartalom megv\u00e1ltozott", + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + }, + "state": { + "_": { + "off": "Ki", + "on": "Be" + } + }, + "title": "P\u00e1r\u00e1s\u00edt\u00f3" +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/id.json b/homeassistant/components/humidifier/translations/id.json new file mode 100644 index 00000000000000..b06b2bfee45aaa --- /dev/null +++ b/homeassistant/components/humidifier/translations/id.json @@ -0,0 +1,28 @@ +{ + "device_automation": { + "action_type": { + "set_humidity": "Setel kelembaban untuk {entity_name}", + "set_mode": "Ubah mode di {entity_name}", + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_mode": "{entity_name} disetel ke mode tertentu", + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "target_humidity_changed": "Kelembapan target {entity_name} berubah", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, + "state": { + "_": { + "off": "Mati", + "on": "Nyala" + } + }, + "title": "Pelembab" +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/ko.json b/homeassistant/components/humidifier/translations/ko.json index c484a532156559..9c556f246e38a0 100644 --- a/homeassistant/components/humidifier/translations/ko.json +++ b/homeassistant/components/humidifier/translations/ko.json @@ -1,21 +1,21 @@ { "device_automation": { "action_type": { - "set_humidity": "{entity_name} \uc2b5\ub3c4 \uc124\uc815\ud558\uae30", - "set_mode": "{entity_name} \uc758 \uc6b4\uc804 \ubaa8\ub4dc \ubcc0\uacbd", - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "set_humidity": "{entity_name}\uc758 \uc2b5\ub3c4 \uc124\uc815\ud558\uae30", + "set_mode": "{entity_name}\uc758 \uc6b4\uc804 \ubaa8\ub4dc \ubcc0\uacbd", + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_mode": "{entity_name}\uc774(\uac00) \ud2b9\uc815 \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "target_humidity_changed": "{entity_name} \ubaa9\ud45c \uc2b5\ub3c4\uac00 \ubcc0\uacbd\ub420 \ub54c", - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "target_humidity_changed": "{entity_name}\uc758 \ubaa9\ud45c \uc2b5\ub3c4\uac00 \ubcc0\uacbd\ub418\uc5c8\uc744 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/humidifier/translations/nl.json b/homeassistant/components/humidifier/translations/nl.json index 311943bbd23709..915b6f477d4076 100644 --- a/homeassistant/components/humidifier/translations/nl.json +++ b/homeassistant/components/humidifier/translations/nl.json @@ -1,9 +1,16 @@ { "device_automation": { "action_type": { + "set_humidity": "Luchtvochtigheid instellen voor {entity_name}", + "set_mode": "Wijzig modus van {entity_name}", "turn_off": "{entity_name} uitschakelen", "turn_on": "{entity_name} inschakelen" }, + "condition_type": { + "is_mode": "{entity_name} is ingesteld op een specifieke modus", + "is_off": "{entity_name} is uitgeschakeld", + "is_on": "{entity_name} staat aan" + }, "trigger_type": { "target_humidity_changed": "{entity_name} doel luchtvochtigheid gewijzigd", "turned_off": "{entity_name} is uitgeschakeld", diff --git a/homeassistant/components/humidifier/translations/zh-Hans.json b/homeassistant/components/humidifier/translations/zh-Hans.json index 8fa6b0be0dae63..d21c7bf61f79ec 100644 --- a/homeassistant/components/humidifier/translations/zh-Hans.json +++ b/homeassistant/components/humidifier/translations/zh-Hans.json @@ -8,9 +8,12 @@ "turn_on": "\u6253\u5f00 {entity_name}" }, "condition_type": { - "is_off": "{entity_name} \u5df2\u5173\u95ed" + "is_mode": "{entity_name} \u5904\u4e8e\u6307\u5b9a\u6a21\u5f0f", + "is_off": "{entity_name} \u5df2\u5173\u95ed", + "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { + "target_humidity_changed": "{entity_name} \u7684\u8bbe\u5b9a\u6e7f\u5ea6\u53d8\u5316", "turned_off": "{entity_name} \u88ab\u5173\u95ed", "turned_on": "{entity_name} \u88ab\u6253\u5f00" } diff --git a/homeassistant/components/hunterdouglas_powerview/translations/hu.json b/homeassistant/components/hunterdouglas_powerview/translations/hu.json index 61461d1796ccf2..063e0dad3c4428 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/hu.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/hu.json @@ -4,8 +4,8 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", - "unknown": "V\u00e1ratlan hiba" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/hunterdouglas_powerview/translations/id.json b/homeassistant/components/hunterdouglas_powerview/translations/id.json new file mode 100644 index 00000000000000..2d21f87bf67020 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "link": { + "description": "Ingin menyiapkan {name} ({host})?", + "title": "Hubungkan ke PowerView Hub" + }, + "user": { + "data": { + "host": "Alamat IP" + }, + "title": "Hubungkan ke PowerView Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/he.json b/homeassistant/components/hvv_departures/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/hvv_departures/translations/hu.json b/homeassistant/components/hvv_departures/translations/hu.json new file mode 100644 index 00000000000000..91da2d13a7c9d9 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/hu.json @@ -0,0 +1,27 @@ +{ + "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": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/id.json b/homeassistant/components/hvv_departures/translations/id.json new file mode 100644 index 00000000000000..d43b306d2925db --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/id.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_results": "Tidak ada hasil. Coba stasiun/alamat lainnya" + }, + "step": { + "station": { + "data": { + "station": "Stasiun/Alamat" + }, + "title": "Masukkan Stasiun/Alamat" + }, + "station_select": { + "data": { + "station": "Stasiun/Alamat" + }, + "title": "Pilih Stasiun/Alamat" + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke API HVV" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "Pilih jalur", + "offset": "Tenggang (menit)", + "real_time": "Gunakan data waktu nyata" + }, + "description": "Ubah opsi untuk sensor keberangkatan ini", + "title": "Opsi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/ko.json b/homeassistant/components/hvv_departures/translations/ko.json index ea6ef8bc23a885..ca8de2ff5cd5e2 100644 --- a/homeassistant/components/hvv_departures/translations/ko.json +++ b/homeassistant/components/hvv_departures/translations/ko.json @@ -13,7 +13,7 @@ "data": { "station": "\uc2a4\ud14c\uc774\uc158 / \uc8fc\uc18c" }, - "title": "\uc2a4\ud14c\uc774\uc158 / \uc8fc\uc18c \uc785\ub825\ud558\uae30" + "title": "\uc2a4\ud14c\uc774\uc158 / \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "station_select": { "data": { @@ -27,7 +27,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "HVV API \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "HVV API\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, diff --git a/homeassistant/components/hvv_departures/translations/nl.json b/homeassistant/components/hvv_departures/translations/nl.json index 8c80ae5b942271..09c8b5b60e7611 100644 --- a/homeassistant/components/hvv_departures/translations/nl.json +++ b/homeassistant/components/hvv_departures/translations/nl.json @@ -5,9 +5,22 @@ }, "error": { "cannot_connect": "Kon niet verbinden", - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "no_results": "Geen resultaten. Probeer het met een ander station/adres" }, "step": { + "station": { + "data": { + "station": "Station/Adres" + }, + "title": "Voer station/adres in" + }, + "station_select": { + "data": { + "station": "Station/Adres" + }, + "title": "Selecteer Station/Adres" + }, "user": { "data": { "host": "Host", @@ -17,5 +30,18 @@ "title": "Maak verbinding met de HVV API" } } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "Selecteer lijnen", + "offset": "Offset (minuten)", + "real_time": "Gebruik realtime gegevens" + }, + "description": "Wijzig opties voor deze vertreksensor", + "title": "Opties" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/hu.json b/homeassistant/components/hyperion/translations/hu.json index 50ccd9f3b63099..cfe649d9d5ee18 100644 --- a/homeassistant/components/hyperion/translations/hu.json +++ b/homeassistant/components/hyperion/translations/hu.json @@ -1,13 +1,26 @@ { "config": { "abort": { - "reauth_successful": "Az \u00fajb\u00f3li azonos\u00edt\u00e1s sikeres" + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" }, "step": { "auth": { "data": { "create_token": "\u00daj token automatikus l\u00e9trehoz\u00e1sa" } + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } } } } diff --git a/homeassistant/components/hyperion/translations/id.json b/homeassistant/components/hyperion/translations/id.json new file mode 100644 index 00000000000000..c1c2a62e0d9339 --- /dev/null +++ b/homeassistant/components/hyperion/translations/id.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "auth_new_token_not_granted_error": "Token yang baru dibuat tidak disetujui di antarmuka Hyperion", + "auth_new_token_not_work_error": "Gagal mengautentikasi menggunakan token yang baru dibuat", + "auth_required_error": "Gagal menentukan apakah otorisasi diperlukan", + "cannot_connect": "Gagal terhubung", + "no_id": "Instans Hyperion Ambilight tidak melaporkan ID-nya", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_access_token": "Token akses tidak valid" + }, + "step": { + "auth": { + "data": { + "create_token": "Buat token baru secara otomatis", + "token": "Atau berikan token yang sudah ada sebelumnya" + }, + "description": "Konfigurasikan otorisasi ke server Hyperion Ambilight Anda" + }, + "confirm": { + "description": "Apakah Anda ingin menambahkan Hyperion Ambilight ini ke Home Assistant?\n\n**Host:** {host}\n**Port:** {port}\n**ID**: {id}", + "title": "Konfirmasikan penambahan layanan Hyperion Ambilight" + }, + "create_token": { + "description": "Pilih **Kirim** di bawah ini untuk meminta token autentikasi baru. Anda akan diarahkan ke antarmuka Hyperion untuk menyetujui permintaan. Pastikan ID yang ditampilkan adalah \"{auth_id}\"", + "title": "Buat token autentikasi baru secara otomatis" + }, + "create_token_external": { + "title": "Terima token baru di antarmuka Hyperion" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "Prioritas hyperion digunakan untuk warna dan efek" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/ko.json b/homeassistant/components/hyperion/translations/ko.json index 295d418da12e28..94dc9d48a58499 100644 --- a/homeassistant/components/hyperion/translations/ko.json +++ b/homeassistant/components/hyperion/translations/ko.json @@ -3,7 +3,11 @@ "abort": { "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "auth_new_token_not_granted_error": "\uc0c8\ub85c \uc0dd\uc131\ub41c \ud1a0\ud070\uc774 Hyperion UI\uc5d0\uc11c \uc2b9\uc778\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", + "auth_new_token_not_work_error": "\uc0c8\ub85c \uc0dd\uc131\ub41c \ud1a0\ud070\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc778\uc99d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "auth_required_error": "\uc778\uc99d\uc774 \ud544\uc694\ud55c\uc9c0 \ud655\uc778\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "no_id": "Hyperion Amblight \uc778\uc2a4\ud134\uc2a4\uac00 \ud574\ub2f9 ID\ub97c \ubcf4\uace0\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -11,6 +15,24 @@ "invalid_access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "auth": { + "data": { + "create_token": "\uc0c8\ub85c\uc6b4 \ud1a0\ud070\uc744 \uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\uae30", + "token": "\ub610\ub294 \uae30\uc874 \ud1a0\ud070 \uc81c\uacf5\ud558\uae30" + }, + "description": "Hyperion Amblight \uc11c\ubc84\uc5d0 \ub300\ud55c \uad8c\ud55c \uad6c\uc131\ud558\uae30" + }, + "confirm": { + "description": "\uc774 Hyperion Amblight\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n**\ud638\uc2a4\ud2b8**: {host}\n**\ud3ec\ud2b8**: {port}\n**ID**: {id}", + "title": "Hyperion Amblight \uc11c\ube44\uc2a4 \ucd94\uac00 \ud655\uc778\ud558\uae30" + }, + "create_token": { + "description": "\uc544\ub798 **\ud655\uc778**\uc744 \uc120\ud0dd\ud558\uc5ec \uc0c8\ub85c\uc6b4 \uc778\uc99d \ud1a0\ud070\uc744 \uc694\uccad\ud574\uc8fc\uc138\uc694. \uc694\uccad\uc744 \uc2b9\uc778\ud558\ub3c4\ub85d Hyperion UI\ub85c \ub9ac\ub514\ub809\uc158\ub429\ub2c8\ub2e4. \ud45c\uc2dc\ub41c ID\uac00 \"{auth_id}\"\uc778\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694", + "title": "\uc0c8\ub85c\uc6b4 \uc778\uc99d \ud1a0\ud070\uc744 \uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\uae30" + }, + "create_token_external": { + "title": "Hyperion UI\uc5d0\uc11c \uc0c8\ub85c\uc6b4 \ud1a0\ud070 \uc218\ub77d\ud558\uae30" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", @@ -18,5 +40,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "priority": "\uc0c9\uc0c1 \ubc0f \ud6a8\uacfc\uc5d0 \uc0ac\uc6a9\ud560 Hyperion \uc6b0\uc120 \uc21c\uc704" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/nl.json b/homeassistant/components/hyperion/translations/nl.json index 0898272e4a2bab..cb2de7e845dc66 100644 --- a/homeassistant/components/hyperion/translations/nl.json +++ b/homeassistant/components/hyperion/translations/nl.json @@ -7,6 +7,7 @@ "auth_new_token_not_work_error": "Verificatie met nieuw aangemaakt token mislukt", "auth_required_error": "Kan niet bepalen of autorisatie vereist is", "cannot_connect": "Kan geen verbinding maken", + "no_id": "De Hyperion Ambilight instantie heeft zijn id niet gerapporteerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { @@ -16,8 +17,14 @@ "step": { "auth": { "data": { - "create_token": "Maak automatisch een nieuw token aan" - } + "create_token": "Maak automatisch een nieuw token aan", + "token": "Of geef een reeds bestaand token op" + }, + "description": "Configureer autorisatie voor uw Hyperion Ambilight-server" + }, + "confirm": { + "description": "Wilt u deze Hyperion Ambilight toevoegen aan Home Assistant? \n\n ** Host: ** {host}\n ** Poort: ** {port}\n ** ID **: {id}", + "title": "Bevestig de toevoeging van Hyperion Ambilight-service" }, "create_token_external": { "title": "Accepteer nieuwe token in Hyperion UI" diff --git a/homeassistant/components/iaqualink/translations/he.json b/homeassistant/components/iaqualink/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/hu.json b/homeassistant/components/iaqualink/translations/hu.json index 149fee90583d53..dcb7b906ee3beb 100644 --- a/homeassistant/components/iaqualink/translations/hu.json +++ b/homeassistant/components/iaqualink/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, diff --git a/homeassistant/components/iaqualink/translations/id.json b/homeassistant/components/iaqualink/translations/id.json new file mode 100644 index 00000000000000..4591cf11e055e2 --- /dev/null +++ b/homeassistant/components/iaqualink/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan nama pengguna dan kata sandi untuk akun iAqualink Anda.", + "title": "Hubungkan ke iAqualink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/ko.json b/homeassistant/components/iaqualink/translations/ko.json index 1386480fca4d61..ef77daf978b272 100644 --- a/homeassistant/components/iaqualink/translations/ko.json +++ b/homeassistant/components/iaqualink/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -13,7 +13,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "iAqualink \uacc4\uc815\uc758 \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "iAqualink \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "iAqualink\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/icloud/translations/de.json b/homeassistant/components/icloud/translations/de.json index 64a6bcd885c542..7baf9dc3917bf5 100644 --- a/homeassistant/components/icloud/translations/de.json +++ b/homeassistant/components/icloud/translations/de.json @@ -15,6 +15,7 @@ "data": { "password": "Passwort" }, + "description": "Ihr zuvor eingegebenes Passwort f\u00fcr {username} funktioniert nicht mehr. Aktualisieren Sie Ihr Passwort, um diese Integration weiterhin zu verwenden.", "title": "Integration erneut authentifizieren" }, "trusted_device": { diff --git a/homeassistant/components/icloud/translations/he.json b/homeassistant/components/icloud/translations/he.json index 71466dddc39b42..139d7a1e39907f 100644 --- a/homeassistant/components/icloud/translations/he.json +++ b/homeassistant/components/icloud/translations/he.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", "username": "\u05d3\u05d5\u05d0\u05e8 \u05d0\u05dc\u05e7\u05d8\u05e8\u05d5\u05e0\u05d9" } } diff --git a/homeassistant/components/icloud/translations/hu.json b/homeassistant/components/icloud/translations/hu.json index 2e820418e94d51..bb47cdd879b73c 100644 --- a/homeassistant/components/icloud/translations/hu.json +++ b/homeassistant/components/icloud/translations/hu.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "send_verification_code": "Nem siker\u00fclt elk\u00fcldeni az ellen\u0151rz\u0151 k\u00f3dot", "validate_verification_code": "Nem siker\u00fclt ellen\u0151rizni az ellen\u0151rz\u0151 k\u00f3dot, ki kell v\u00e1lasztania egy megb\u00edzhat\u00f3s\u00e1gi eszk\u00f6zt, \u00e9s \u00fajra kell ind\u00edtania az ellen\u0151rz\u00e9st" }, @@ -8,7 +13,8 @@ "reauth": { "data": { "password": "Jelsz\u00f3" - } + }, + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/id.json b/homeassistant/components/icloud/translations/id.json new file mode 100644 index 00000000000000..cd7abc1945dbb6 --- /dev/null +++ b/homeassistant/components/icloud/translations/id.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "no_device": "Tidak ada perangkat Anda yang mengaktifkan \"Temukan iPhone saya\"", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "send_verification_code": "Gagal mengirim kode verifikasi", + "validate_verification_code": "Gagal memverifikasi kode verifikasi Anda, coba lagi" + }, + "step": { + "reauth": { + "data": { + "password": "Kata Sandi" + }, + "description": "Kata sandi yang Anda masukkan sebelumnya untuk {username} tidak lagi berfungsi. Perbarui kata sandi Anda untuk tetap menggunakan integrasi ini.", + "title": "Autentikasi Ulang Integrasi" + }, + "trusted_device": { + "data": { + "trusted_device": "Perangkat tepercaya" + }, + "description": "Pilih perangkat tepercaya Anda", + "title": "Perangkat tepercaya iCloud" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email", + "with_family": "Dengan keluarga" + }, + "description": "Masukkan kredensial Anda", + "title": "Kredensial iCloud" + }, + "verification_code": { + "data": { + "verification_code": "Kode verifikasi" + }, + "description": "Masukkan kode verifikasi yang baru saja diterima dari iCloud", + "title": "Kode verifikasi iCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/ko.json b/homeassistant/components/icloud/translations/ko.json index 5e02fb02993702..52319b888ca9ca 100644 --- a/homeassistant/components/icloud/translations/ko.json +++ b/homeassistant/components/icloud/translations/ko.json @@ -15,7 +15,8 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "\uc774\uc804\uc5d0 \uc785\ub825\ud55c {username}\uc5d0 \ub300\ud55c \ube44\ubc00\ubc88\ud638\uac00 \ub354 \uc774\uc0c1 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uacc4\uc18d \uc0ac\uc6a9\ud558\ub824\uba74 \ube44\ubc00\ubc88\ud638\ub97c \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "trusted_device": { "data": { diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index b150c8d5b16fac..7260f954c3834f 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Account reeds geconfigureerd", + "already_configured": "Account is al geconfigureerd", "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd", "reauth_successful": "Herauthenticatie was succesvol" }, diff --git a/homeassistant/components/ifttt/translations/hu.json b/homeassistant/components/ifttt/translations/hu.json index c3e7007b1a02a5..9898beb3e92cfa 100644 --- a/homeassistant/components/ifttt/translations/hu.json +++ b/homeassistant/components/ifttt/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, akkor az \u201eIFTTT Webhook kisalkalmaz\u00e1s\u201d ( {applet_url} ) \"Webk\u00e9r\u00e9s k\u00e9sz\u00edt\u00e9se\" m\u0171velet\u00e9t kell haszn\u00e1lnia. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3] ( {docs_url} ), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, akkor az [IFTTT Webhook applet]({applet_url}) \"Make a web request\" m\u0171velet\u00e9t kell haszn\u00e1lnia. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3]({docs_url}), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." }, "step": { "user": { diff --git a/homeassistant/components/ifttt/translations/id.json b/homeassistant/components/ifttt/translations/id.json new file mode 100644 index 00000000000000..f997f39a54eadc --- /dev/null +++ b/homeassistant/components/ifttt/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menggunakan tindakan \"Make a web request\" dari [applet IFTTT Webhook]({applet_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan IFTTT?", + "title": "Siapkan Applet IFTTT Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/ko.json b/homeassistant/components/ifttt/translations/ko.json index bc561027fc3451..826a03c733974e 100644 --- a/homeassistant/components/ifttt/translations/ko.json +++ b/homeassistant/components/ifttt/translations/ko.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url})\uc5d0\uc11c \"Make a web request\"\ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "IFTTT \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "IFTTT\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/image_processing/translations/id.json b/homeassistant/components/image_processing/translations/id.json index 19e3a64dcbadd8..c186898fd07a75 100644 --- a/homeassistant/components/image_processing/translations/id.json +++ b/homeassistant/components/image_processing/translations/id.json @@ -1,3 +1,3 @@ { - "title": "Pengolahan gambar" + "title": "Pengolahan citra" } \ No newline at end of file diff --git a/homeassistant/components/input_boolean/translations/id.json b/homeassistant/components/input_boolean/translations/id.json index 4401df1f4530ae..df890baae4a2d9 100644 --- a/homeassistant/components/input_boolean/translations/id.json +++ b/homeassistant/components/input_boolean/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Input boolean" diff --git a/homeassistant/components/insteon/translations/he.json b/homeassistant/components/insteon/translations/he.json new file mode 100644 index 00000000000000..8aabed0bfce040 --- /dev/null +++ b/homeassistant/components/insteon/translations/he.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "change_hub_config": { + "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/insteon/translations/hu.json b/homeassistant/components/insteon/translations/hu.json new file mode 100644 index 00000000000000..06033fb1321c24 --- /dev/null +++ b/homeassistant/components/insteon/translations/hu.json @@ -0,0 +1,72 @@ +{ + "config": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "select_single": "V\u00e1lassz egy lehet\u0151s\u00e9get" + }, + "step": { + "hubv1": { + "data": { + "host": "IP c\u00edm", + "port": "Port" + } + }, + "hubv2": { + "data": { + "host": "IP c\u00edm", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Insteon Hub 2. verzi\u00f3" + }, + "plm": { + "data": { + "device": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" + } + }, + "user": { + "data": { + "modem_type": "Modem t\u00edpusa." + }, + "description": "V\u00e1laszd ki az Insteon modem t\u00edpus\u00e1t.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "add_override": { + "title": "Insteon" + }, + "add_x10": { + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "IP c\u00edm", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Insteon" + }, + "init": { + "title": "Insteon" + }, + "remove_override": { + "title": "Insteon" + }, + "remove_x10": { + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/id.json b/homeassistant/components/insteon/translations/id.json new file mode 100644 index 00000000000000..efae528415015d --- /dev/null +++ b/homeassistant/components/insteon/translations/id.json @@ -0,0 +1,109 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "select_single": "Pilih satu opsi." + }, + "step": { + "hubv1": { + "data": { + "host": "Alamat IP", + "port": "Port" + }, + "description": "Konfigurasikan Insteon Hub Versio 1 (pra-2014).", + "title": "Insteon Hub Versi 1" + }, + "hubv2": { + "data": { + "host": "Alamat IP", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Konfigurasikan Insteon Hub Versi 2.", + "title": "Insteon Hub Versi 2" + }, + "plm": { + "data": { + "device": "Jalur Perangkat USB" + }, + "description": "Konfigurasikan Insteon PowerLink Modem (PLM).", + "title": "Insteon PLM" + }, + "user": { + "data": { + "modem_type": "Jenis modem." + }, + "description": "Pilih jenis modem Insteon.", + "title": "Insteon" + } + } + }, + "options": { + "error": { + "cannot_connect": "Gagal terhubung", + "input_error": "Entri tidak valid, periksa nilai Anda.", + "select_single": "Pilih satu opsi." + }, + "step": { + "add_override": { + "data": { + "address": "Alamat perangkat (yaitu 1a2b3c)", + "cat": "Kategori perangkat (mis. 0x10)", + "subcat": "Subkategori perangkat (mis. 0x0a)" + }, + "description": "Tambahkan penimpaan nilai perangkat.", + "title": "Insteon" + }, + "add_x10": { + "data": { + "housecode": "Kode rumah (a - p)", + "platform": "Platform", + "steps": "Langkah peredup (hanya untuk perangkat ringan, nilai bawaan adalah 22)", + "unitcode": "Unitcode (1 - 16)" + }, + "description": "Ubah kata sandi Insteon Hub.", + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "Alamat IP", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Ubah informasi koneksi Insteon Hub. Anda harus memulai ulang Home Assistant setelah melakukan perubahan ini. Ini tidak mengubah konfigurasi Hub itu sendiri. Untuk mengubah konfigurasi di Hub, gunakan aplikasi Hub.", + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "Tambahkan penimpaan nilai perangkat.", + "add_x10": "Tambahkan perangkat X10.", + "change_hub_config": "Ubah konfigurasi Hub.", + "remove_override": "Hapus penimpaan nilai perangkat.", + "remove_x10": "Hapus perangkat X10." + }, + "description": "Pilih opsi untuk dikonfigurasi.", + "title": "Insteon" + }, + "remove_override": { + "data": { + "address": "Pilih alamat perangkat untuk dihapus" + }, + "description": "Hapus penimpaan nilai perangkat", + "title": "Insteon" + }, + "remove_x10": { + "data": { + "address": "Pilih alamat perangkat untuk dihapus" + }, + "description": "Hapus perangkat X10", + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/ko.json b/homeassistant/components/insteon/translations/ko.json index 1cd65afc9e9c38..559e3351932b08 100644 --- a/homeassistant/components/insteon/translations/ko.json +++ b/homeassistant/components/insteon/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "step": { "hubv1": { @@ -14,7 +14,7 @@ "host": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" }, - "description": "Insteon Hub \ubc84\uc804 1 (2014 \ub144 \uc774\uc804)\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "description": "Insteon Hub \ubc84\uc804 1\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4. (2014\ub144 \uc774\uc804)", "title": "Insteon Hub \ubc84\uc804 1" }, "hubv2": { @@ -30,13 +30,15 @@ "plm": { "data": { "device": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "description": "Insteon PowerLinc Modem (PLM)\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Insteon PLM" }, "user": { "data": { "modem_type": "\ubaa8\ub380 \uc720\ud615." }, - "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", + "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "title": "Insteon" } } @@ -44,20 +46,28 @@ "options": { "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "select_single": "\uc635\uc158 \uc120\ud0dd" + "input_error": "\ud56d\ubaa9\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uac12\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "select_single": "\uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694." }, "step": { "add_override": { "data": { - "cat": "\uc7a5\uce58 \ubc94\uc8fc(\uc608: 0x10)" - } + "address": "\uae30\uae30 \uc8fc\uc18c (\uc608: 1a2b3c)", + "cat": "\uae30\uae30 \ubc94\uc8fc (\uc608: 0x10)", + "subcat": "\uae30\uae30 \ud558\uc704 \ubc94\uc8fc (\uc608: 0x0a)" + }, + "description": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "title": "Insteon" }, "add_x10": { "data": { - "steps": "\ub514\uba38 \ub2e8\uacc4(\ub77c\uc774\ud2b8 \uc7a5\uce58\uc5d0\ub9cc, \uae30\ubcf8\uac12 22)", - "unitcode": "\ub2e8\uc704 \ucf54\ub4dc (1-16)" + "housecode": "\ud558\uc6b0\uc2a4 \ucf54\ub4dc (a - p)", + "platform": "\ud50c\ub7ab\ud3fc", + "steps": "\ubc1d\uae30 \uc870\uc808 \ub2e8\uacc4 (\uc870\uba85 \uae30\uae30 \uc804\uc6a9, \uae30\ubcf8\uac12 22)", + "unitcode": "\uc720\ub2db \ucf54\ub4dc (1-16)" }, - "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4." + "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4.", + "title": "Insteon" }, "change_hub_config": { "data": { @@ -65,29 +75,34 @@ "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "description": "Insteon \ud5c8\ube0c\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4. \ubcc0\uacbd\ud55c \ud6c4\uc5d0\ub294 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4. \ud5c8\ube0c \uc790\uccb4\uc758 \uad6c\uc131\uc740 \ubcc0\uacbd\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ud5c8\ube0c\uc758 \uad6c\uc131\uc744 \ubcc0\uacbd\ud558\ub824\uba74 \ud5c8\ube0c \uc571\uc744 \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694.", + "title": "Insteon" }, "init": { "data": { - "add_override": "\uc7a5\uce58 Override \ucd94\uac00", - "add_x10": "X10 \uc7a5\uce58\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "add_override": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "add_x10": "X10 \uae30\uae30\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", "change_hub_config": "\ud5c8\ube0c \uad6c\uc131\uc744 \ubcc0\uacbd\ud569\ub2c8\ub2e4.", - "remove_override": "\uc7a5\uce58 Override \uc81c\uac70", - "remove_x10": "X10 \uc7a5\uce58\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4." + "remove_override": "\uae30\uae30 \uc7ac\uc815\uc758\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4.", + "remove_x10": "X10 \uae30\uae30\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4." }, - "description": "\uad6c\uc131 \ud560 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "description": "\uad6c\uc131\ud560 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "Insteon" }, "remove_override": { "data": { - "address": "\uc81c\uac70 \ud560 \uc7a5\uce58 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "address": "\uc81c\uac70\ud560 \uae30\uae30\uc758 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, - "description": "\uc7a5\uce58 Override \uc81c\uac70" + "description": "\uae30\uae30 \uc7ac\uc815\uc758 \uc81c\uac70\ud558\uae30", + "title": "Insteon" }, "remove_x10": { "data": { - "address": "\uc81c\uac70 \ud560 \uc7a5\uce58 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + "address": "\uc81c\uac70\ud560 \uae30\uae30\uc758 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" }, - "description": "X10 \uc7a5\uce58 \uc81c\uac70" + "description": "X10 \uae30\uae30 \uc81c\uac70\ud558\uae30", + "title": "Insteon" } } } diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index 98a27fb1139382..20b292878621ea 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -86,7 +86,11 @@ "change_hub_config": "Wijzig de Hub-configuratie.", "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." - } + }, + "title": "Insteon" + }, + "remove_override": { + "title": "Insteon" }, "remove_x10": { "title": "Insteon" diff --git a/homeassistant/components/ios/translations/hu.json b/homeassistant/components/ios/translations/hu.json index f716fd36e9ac69..dda7af8c541fa6 100644 --- a/homeassistant/components/ios/translations/hu.json +++ b/homeassistant/components/ios/translations/hu.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen Home Assistant iOS konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Home Assistant iOS komponenst?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" } } } diff --git a/homeassistant/components/ios/translations/id.json b/homeassistant/components/ios/translations/id.json index 46449f1c0443a0..c8003cc0d3d8b3 100644 --- a/homeassistant/components/ios/translations/id.json +++ b/homeassistant/components/ios/translations/id.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Hanya satu konfigurasi Home Assistant iOS yang diperlukan." + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "step": { "confirm": { - "description": "Apakah Anda ingin mengatur komponen iOS Home Assistant?" + "description": "Ingin memulai penyiapan?" } } } diff --git a/homeassistant/components/ios/translations/ko.json b/homeassistant/components/ios/translations/ko.json index f5da462c1ab564..d7eaa77481f131 100644 --- a/homeassistant/components/ios/translations/ko.json +++ b/homeassistant/components/ios/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/ios/translations/nl.json b/homeassistant/components/ios/translations/nl.json index 0575b4558afe29..78757f9f715dbc 100644 --- a/homeassistant/components/ios/translations/nl.json +++ b/homeassistant/components/ios/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Home Assistant iOS nodig." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { - "description": "Wilt u het Home Assistant iOS component instellen?" + "description": "Wil je beginnen met instellen?" } } } diff --git a/homeassistant/components/ipma/translations/hu.json b/homeassistant/components/ipma/translations/hu.json index 0aeb36b0442767..00ba66d2dd7194 100644 --- a/homeassistant/components/ipma/translations/hu.json +++ b/homeassistant/components/ipma/translations/hu.json @@ -12,8 +12,13 @@ "name": "N\u00e9v" }, "description": "Portug\u00e1l Atmoszf\u00e9ra Int\u00e9zet", - "title": "Hely" + "title": "Elhelyezked\u00e9s" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API v\u00e9gpont el\u00e9rhet\u0151" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/id.json b/homeassistant/components/ipma/translations/id.json new file mode 100644 index 00000000000000..2f7e8324fdfc61 --- /dev/null +++ b/homeassistant/components/ipma/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "error": { + "name_exists": "Nama sudah ada" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur", + "mode": "Mode", + "name": "Nama" + }, + "description": "Instituto Portugu\u00eas do Mar e Atmosfera", + "title": "Lokasi" + } + } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Titik akhir API IPMA dapat dijangkau" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/ko.json b/homeassistant/components/ipma/translations/ko.json index 3fd1a8d8ea9e94..826309a4ee4371 100644 --- a/homeassistant/components/ipma/translations/ko.json +++ b/homeassistant/components/ipma/translations/ko.json @@ -15,5 +15,10 @@ "title": "\uc704\uce58" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API \uc5d4\ub4dc\ud3ec\uc778\ud2b8 \uc5f0\uacb0" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/nl.json b/homeassistant/components/ipma/translations/nl.json index 79248e0d064e40..154aafe10333c3 100644 --- a/homeassistant/components/ipma/translations/nl.json +++ b/homeassistant/components/ipma/translations/nl.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "latitude": "Latitude", - "longitude": "Longitude", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", "mode": "Mode", "name": "Naam" }, @@ -15,5 +15,10 @@ "title": "Locatie" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "IPMA API-eindpunt bereikbaar" + } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/hu.json b/homeassistant/components/ipp/translations/hu.json index 396992156c0f23..8c988eff55117a 100644 --- a/homeassistant/components/ipp/translations/hu.json +++ b/homeassistant/components/ipp/translations/hu.json @@ -14,8 +14,13 @@ "user": { "data": { "host": "Hoszt", - "port": "Port" + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } + }, + "zeroconf_confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}-t?" } } } diff --git a/homeassistant/components/ipp/translations/id.json b/homeassistant/components/ipp/translations/id.json new file mode 100644 index 00000000000000..c2b95751d4bd20 --- /dev/null +++ b/homeassistant/components/ipp/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "connection_upgrade": "Gagal terhubung ke printer karena peningkatan koneksi diperlukan.", + "ipp_error": "Terjadi kesalahan IPP.", + "ipp_version_error": "Versi IPP tidak didukung oleh printer.", + "parse_error": "Gagal mengurai respons dari printer.", + "unique_id_required": "Perangkat tidak memiliki identifikasi unik yang diperlukan untuk ditemukan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "connection_upgrade": "Gagal terhubung ke printer. Coba lagi dengan mencentang opsi SSL/TLS." + }, + "flow_title": "Printer: {name}", + "step": { + "user": { + "data": { + "base_path": "Jalur relatif ke printer", + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Siapkan printer Anda melalui Internet Printing Protocol (IPP) untuk diintegrasikan dengan Home Assistant.", + "title": "Tautkan printer Anda" + }, + "zeroconf_confirm": { + "description": "Ingin menyiapkan {name}?", + "title": "Printer yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/ko.json b/homeassistant/components/ipp/translations/ko.json index 28e79ffe281806..2ae4e08beaf5cd 100644 --- a/homeassistant/components/ipp/translations/ko.json +++ b/homeassistant/components/ipp/translations/ko.json @@ -11,7 +11,7 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. SSL/TLS \uc635\uc158\uc744 \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. SSL/TLS \uc635\uc158\uc744 \ud655\uc778\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "flow_title": "\ud504\ub9b0\ud130: {name}", "step": { @@ -27,7 +27,7 @@ "title": "\ud504\ub9b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "zeroconf_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c \ud504\ub9b0\ud130" } } diff --git a/homeassistant/components/ipp/translations/nl.json b/homeassistant/components/ipp/translations/nl.json index bcbd495be91324..f3d2ee60797909 100644 --- a/homeassistant/components/ipp/translations/nl.json +++ b/homeassistant/components/ipp/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze printer is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", "connection_upgrade": "Kan geen verbinding maken met de printer omdat een upgrade van de verbinding vereist is.", "ipp_error": "Er is een IPP-fout opgetreden.", @@ -18,10 +18,10 @@ "user": { "data": { "base_path": "Relatief pad naar de printer", - "host": "Host- of IP-adres", + "host": "Host", "port": "Poort", "ssl": "Printer ondersteunt communicatie via SSL / TLS", - "verify_ssl": "Printer gebruikt een correct SSL-certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "description": "Stel uw printer in via Internet Printing Protocol (IPP) om te integreren met Home Assistant.", "title": "Koppel uw printer" diff --git a/homeassistant/components/iqvia/translations/hu.json b/homeassistant/components/iqvia/translations/hu.json new file mode 100644 index 00000000000000..f5301e874eae05 --- /dev/null +++ b/homeassistant/components/iqvia/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/iqvia/translations/id.json b/homeassistant/components/iqvia/translations/id.json index a93f9aac26fc74..32f9b77135b9f0 100644 --- a/homeassistant/components/iqvia/translations/id.json +++ b/homeassistant/components/iqvia/translations/id.json @@ -1,10 +1,18 @@ { "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "invalid_zip_code": "Kode pos tidak valid" + }, "step": { "user": { "data": { "zip_code": "Kode Pos" - } + }, + "description": "Isi kode pos AS atau Kanada.", + "title": "IQVIA" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/hu.json b/homeassistant/components/islamic_prayer_times/translations/hu.json new file mode 100644 index 00000000000000..065747fb39df50 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/id.json b/homeassistant/components/islamic_prayer_times/translations/id.json new file mode 100644 index 00000000000000..30eb3497847b81 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin menyiapkan Jadwal Sholat Islam?", + "title": "Siapkan Jadwal Sholat Islam" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Metode perhitungan waktu sholat" + } + } + } + }, + "title": "Jadwal Sholat Islami" +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/ko.json b/homeassistant/components/islamic_prayer_times/translations/ko.json index 240ad6f57dca0f..aa0905b361237a 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ko.json +++ b/homeassistant/components/islamic_prayer_times/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/isy994/translations/hu.json b/homeassistant/components/isy994/translations/hu.json index 6177c39b231a9a..427a51157bf78a 100644 --- a/homeassistant/components/isy994/translations/hu.json +++ b/homeassistant/components/isy994/translations/hu.json @@ -6,15 +6,24 @@ "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Universal Devices ISY994 {name} ({host})", "step": { "user": { "data": { + "host": "URL", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } + }, + "options": { + "step": { + "init": { + "title": "ISY994 Be\u00e1ll\u00edt\u00e1sok" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/id.json b/homeassistant/components/isy994/translations/id.json new file mode 100644 index 00000000000000..fec6d1090b044b --- /dev/null +++ b/homeassistant/components/isy994/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_host": "Entri host tidak dalam format URL lengkap, misalnya, http://192.168.10.100:80", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Universal Devices ISY994 {name} ({host})", + "step": { + "user": { + "data": { + "host": "URL", + "password": "Kata Sandi", + "tls": "Versi TLS dari pengontrol ISY.", + "username": "Nama Pengguna" + }, + "description": "Entri host harus dalam format URL lengkap, misalnya, http://192.168.10.100:80", + "title": "Hubungkan ke ISY994 Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ignore_string": "Abaikan String", + "restore_light_state": "Pulihkan Kecerahan Cahaya", + "sensor_string": "String Sensor Node", + "variable_sensor_string": "String Sensor Variabel" + }, + "description": "Mengatur opsi untuk Integrasi ISY: \n \u2022 String Sensor Node: Setiap perangkat atau folder yang berisi 'String Sensor Node' dalam nama akan diperlakukan sebagai sensor atau sensor biner. \n \u2022 Abaikan String: Setiap perangkat dengan 'Abaikan String' dalam nama akan diabaikan. \n \u2022 String Sensor Variabel: Variabel apa pun yang berisi 'String Sensor Variabel' akan ditambahkan sebagai sensor. \n \u2022 Pulihkan Kecerahan Cahaya: Jika diaktifkan, kecerahan sebelumnya akan dipulihkan saat menyalakan lampu alih-alih bawaan perangkat On-Level.", + "title": "Opsi ISY994" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/ko.json b/homeassistant/components/isy994/translations/ko.json index b8c80f1ee74a90..29829e066b9c01 100644 --- a/homeassistant/components/isy994/translations/ko.json +++ b/homeassistant/components/isy994/translations/ko.json @@ -19,7 +19,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "\ud638\uc2a4\ud2b8 \ud56d\ubaa9\uc740 \uc644\uc804\ud55c URL \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: http://192.168.10.100:80", - "title": "ISY994 \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "ISY994\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, diff --git a/homeassistant/components/isy994/translations/nl.json b/homeassistant/components/isy994/translations/nl.json index d263815158ae98..9fed7b8c99c950 100644 --- a/homeassistant/components/isy994/translations/nl.json +++ b/homeassistant/components/isy994/translations/nl.json @@ -29,7 +29,8 @@ "data": { "ignore_string": "Tekenreeks negeren", "restore_light_state": "Herstel lichthelderheid", - "sensor_string": "Node Sensor String" + "sensor_string": "Node Sensor String", + "variable_sensor_string": "Variabele sensor string" }, "description": "Stel de opties in voor de ISY-integratie:\n \u2022 Node Sensor String: elk apparaat of elke map die 'Node Sensor String' in de naam bevat, wordt behandeld als een sensor of binaire sensor.\n \u2022 Ignore String: elk apparaat met 'Ignore String' in de naam wordt genegeerd.\n \u2022 Variabele sensorreeks: elke variabele die 'Variabele sensorreeks' bevat, wordt als sensor toegevoegd.\n \u2022 Lichthelderheid herstellen: indien ingeschakeld, wordt de vorige helderheid hersteld wanneer u een lamp inschakelt in plaats van het ingebouwde Aan-niveau van het apparaat.", "title": "ISY994-opties" diff --git a/homeassistant/components/izone/translations/hu.json b/homeassistant/components/izone/translations/hu.json index 026093232a3b29..2d474986415595 100644 --- a/homeassistant/components/izone/translations/hu.json +++ b/homeassistant/components/izone/translations/hu.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "confirm": { "description": "Szeretn\u00e9 be\u00e1ll\u00edtani az iZone-t?" diff --git a/homeassistant/components/izone/translations/id.json b/homeassistant/components/izone/translations/id.json new file mode 100644 index 00000000000000..208b59dc9ac1b6 --- /dev/null +++ b/homeassistant/components/izone/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan iZone?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/ko.json b/homeassistant/components/izone/translations/ko.json index b6eae170becdf6..d7173fc2db4fb5 100644 --- a/homeassistant/components/izone/translations/ko.json +++ b/homeassistant/components/izone/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/juicenet/translations/de.json b/homeassistant/components/juicenet/translations/de.json index 7a6b5cff541159..fbdea4c321f0af 100644 --- a/homeassistant/components/juicenet/translations/de.json +++ b/homeassistant/components/juicenet/translations/de.json @@ -13,7 +13,7 @@ "data": { "api_token": "API-Token" }, - "description": "Du ben\u00f6tigst das API-Token von https://home.juice.net/Manage.", + "description": "Sie ben\u00f6tigen das API-Token von https://home.juice.net/Manage.", "title": "Stelle eine Verbindung zu JuiceNet her" } } diff --git a/homeassistant/components/juicenet/translations/hu.json b/homeassistant/components/juicenet/translations/hu.json new file mode 100644 index 00000000000000..f04a8c1e6ca4da --- /dev/null +++ b/homeassistant/components/juicenet/translations/hu.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_token": "API Token" + }, + "title": "Csatlakoz\u00e1s a JuiceNethez" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/id.json b/homeassistant/components/juicenet/translations/id.json new file mode 100644 index 00000000000000..a150b7b7bbfa0d --- /dev/null +++ b/homeassistant/components/juicenet/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_token": "Token API" + }, + "description": "Anda akan memerlukan Token API dari https://home.juice.net/Manage.", + "title": "Hubungkan ke JuiceNet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/ko.json b/homeassistant/components/juicenet/translations/ko.json index 1e1ae6aaa88022..b38ddbd1f6931a 100644 --- a/homeassistant/components/juicenet/translations/ko.json +++ b/homeassistant/components/juicenet/translations/ko.json @@ -14,7 +14,7 @@ "api_token": "API \ud1a0\ud070" }, "description": "https://home.juice.net/Manage \uc758 API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.", - "title": "JuiceNet \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "JuiceNet\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/keenetic_ndms2/translations/bg.json b/homeassistant/components/keenetic_ndms2/translations/bg.json new file mode 100644 index 00000000000000..db122ec078bfdb --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "\u0418\u043c\u0435", + "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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/en.json b/homeassistant/components/keenetic_ndms2/translations/en.json index 5a946751ff4c1b..e95f2f740efd6c 100644 --- a/homeassistant/components/keenetic_ndms2/translations/en.json +++ b/homeassistant/components/keenetic_ndms2/translations/en.json @@ -10,6 +10,7 @@ "user": { "data": { "host": "Host", + "name": "Name", "password": "Password", "port": "Port", "username": "Username" diff --git a/homeassistant/components/keenetic_ndms2/translations/hu.json b/homeassistant/components/keenetic_ndms2/translations/hu.json new file mode 100644 index 00000000000000..72482de8604b6d --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/id.json b/homeassistant/components/keenetic_ndms2/translations/id.json new file mode 100644 index 00000000000000..6a427a875a0c15 --- /dev/null +++ b/homeassistant/components/keenetic_ndms2/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Siapkan Router NDMS2 Keenetik" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "consider_home": "Pertimbangkan interval rumah", + "include_arp": "Gunakan data ARP (diabaikan jika data hotspot digunakan)", + "include_associated": "Gunakan data asosiasi Wi-Fi AP (diabaikan jika data hotspot digunakan)", + "interfaces": "Pilih antarmuka untuk dipindai", + "scan_interval": "Interval pindai", + "try_hotspot": "Gunakan data 'ip hotspot' (paling akurat)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ko.json b/homeassistant/components/keenetic_ndms2/translations/ko.json index 3281ddbe3d4485..8968d9e7709b2c 100644 --- a/homeassistant/components/keenetic_ndms2/translations/ko.json +++ b/homeassistant/components/keenetic_ndms2/translations/ko.json @@ -14,7 +14,8 @@ "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "title": "Keenetic NDMS2 \ub77c\uc6b0\ud130 \uc124\uc815\ud558\uae30" } } }, @@ -22,7 +23,12 @@ "step": { "user": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9" + "consider_home": "\uc7ac\uc2e4 \ucd94\uce21 \uac04\uaca9", + "include_arp": "ARP \ub370\uc774\ud130 \uc0ac\uc6a9\ud558\uae30 (\ud56b\uc2a4\ud31f \ub370\uc774\ud130\uac00 \uc0ac\uc6a9\ub418\ub294 \uacbd\uc6b0 \ubb34\uc2dc\ub428)", + "include_associated": "WiFi AP \uc5f0\uacb0 \ub370\uc774\ud130 \uc0ac\uc6a9\ud558\uae30 (\ud56b\uc2a4\ud31f \ub370\uc774\ud130\uac00 \uc0ac\uc6a9\ub418\ub294 \uacbd\uc6b0 \ubb34\uc2dc\ub428)", + "interfaces": "\uc2a4\uce94\ud560 \uc778\ud130\ud398\uc774\uc2a4\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9", + "try_hotspot": "'ip \ud56b\uc2a4\ud31f' \ub370\uc774\ud130 \uc0ac\uc6a9\ud558\uae30 (\uac00\uc7a5 \uc815\ud655\ud568)" } } } diff --git a/homeassistant/components/keenetic_ndms2/translations/nl.json b/homeassistant/components/keenetic_ndms2/translations/nl.json index c3c08575052fe1..b7c89bb65e9f6f 100644 --- a/homeassistant/components/keenetic_ndms2/translations/nl.json +++ b/homeassistant/components/keenetic_ndms2/translations/nl.json @@ -10,10 +10,12 @@ "user": { "data": { "host": "Host", + "name": "Naam", "password": "Wachtwoord", "port": "Poort", "username": "Gebruikersnaam" - } + }, + "title": "Keenetic NDMS2 Router instellen" } } }, @@ -21,8 +23,12 @@ "step": { "user": { "data": { + "consider_home": "Overweeg thuisinterval", + "include_arp": "Gebruik ARP-gegevens (genegeerd als hotspot-gegevens worden gebruikt)", + "include_associated": "Gebruik WiFi AP-koppelingsgegevens (genegeerd als hotspot-gegevens worden gebruikt)", "interfaces": "Kies interfaces om te scannen", - "scan_interval": "Scaninterval" + "scan_interval": "Scaninterval", + "try_hotspot": "Gebruik 'ip hotspot'-gegevens (meest nauwkeurig)" } } } diff --git a/homeassistant/components/kmtronic/translations/bg.json b/homeassistant/components/kmtronic/translations/bg.json new file mode 100644 index 00000000000000..a84e1c3bfdf358 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/bg.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "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/kmtronic/translations/ca.json b/homeassistant/components/kmtronic/translations/ca.json index df8218bab3e9b3..0847a86a41bcf3 100644 --- a/homeassistant/components/kmtronic/translations/ca.json +++ b/homeassistant/components/kmtronic/translations/ca.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "L\u00f2gica de commutaci\u00f3 inversa (utilitza NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/en.json b/homeassistant/components/kmtronic/translations/en.json index 0a1bde9fb196da..61da788028e29e 100644 --- a/homeassistant/components/kmtronic/translations/en.json +++ b/homeassistant/components/kmtronic/translations/en.json @@ -13,8 +13,7 @@ "data": { "host": "Host", "password": "Password", - "username": "Username", - "reverse": "Reverse switch logic (use NC)" + "username": "Username" } } } diff --git a/homeassistant/components/kmtronic/translations/es.json b/homeassistant/components/kmtronic/translations/es.json new file mode 100644 index 00000000000000..4fb0fc0e0a5c7a --- /dev/null +++ b/homeassistant/components/kmtronic/translations/es.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fallo al conectar", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/et.json b/homeassistant/components/kmtronic/translations/et.json index 0c1715b49320e3..d501e1d1962a9f 100644 --- a/homeassistant/components/kmtronic/translations/et.json +++ b/homeassistant/components/kmtronic/translations/et.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "P\u00f6\u00f6rdl\u00fcliti loogika (kasuta NC-d)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/fr.json b/homeassistant/components/kmtronic/translations/fr.json index 45620fe7795250..7c0050bfad82c8 100644 --- a/homeassistant/components/kmtronic/translations/fr.json +++ b/homeassistant/components/kmtronic/translations/fr.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Logique de commutation inverse (utiliser NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/hu.json b/homeassistant/components/kmtronic/translations/hu.json new file mode 100644 index 00000000000000..0abcc301f0c854 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/hu.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/id.json b/homeassistant/components/kmtronic/translations/id.json new file mode 100644 index 00000000000000..ed8fde321061cf --- /dev/null +++ b/homeassistant/components/kmtronic/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/it.json b/homeassistant/components/kmtronic/translations/it.json index e9356485e087f8..59aeaa25f6f66a 100644 --- a/homeassistant/components/kmtronic/translations/it.json +++ b/homeassistant/components/kmtronic/translations/it.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Invertire la logica dell'interruttore (usare NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/ko.json b/homeassistant/components/kmtronic/translations/ko.json new file mode 100644 index 00000000000000..cf62f34c75568f --- /dev/null +++ b/homeassistant/components/kmtronic/translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "\uc2a4\uc704\uce58 \ubc29\uc2dd \ubcc0\uacbd (\uc0c1\uc2dc\ud3d0\ub85c(NC)\ub85c \uc0ac\uc6a9)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/nl.json b/homeassistant/components/kmtronic/translations/nl.json index 8ad15260b0de5c..9c32e48b20ad10 100644 --- a/homeassistant/components/kmtronic/translations/nl.json +++ b/homeassistant/components/kmtronic/translations/nl.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Omgekeerde schakelaarlogica (gebruik NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/no.json b/homeassistant/components/kmtronic/translations/no.json index 249711bb9120f9..1b192f7895ea09 100644 --- a/homeassistant/components/kmtronic/translations/no.json +++ b/homeassistant/components/kmtronic/translations/no.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Omvendt bryterlogikk (bruk NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pl.json b/homeassistant/components/kmtronic/translations/pl.json index 25dab56796cc15..6b0d06df11be12 100644 --- a/homeassistant/components/kmtronic/translations/pl.json +++ b/homeassistant/components/kmtronic/translations/pl.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Odwr\u00f3\u0107 logik\u0119 prze\u0142\u0105cznika (u\u017cyj NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/pt.json b/homeassistant/components/kmtronic/translations/pt.json new file mode 100644 index 00000000000000..6ff15c6c8d7bf0 --- /dev/null +++ b/homeassistant/components/kmtronic/translations/pt.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Servidor", + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "Inverter l\u00f3gica do interruptor (usar NC)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/ru.json b/homeassistant/components/kmtronic/translations/ru.json index 9e0db9fcf94dd8..7401068638d065 100644 --- a/homeassistant/components/kmtronic/translations/ru.json +++ b/homeassistant/components/kmtronic/translations/ru.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "\u041b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kmtronic/translations/zh-Hant.json b/homeassistant/components/kmtronic/translations/zh-Hant.json index cad7d736a9d26c..5027bc2f5b2612 100644 --- a/homeassistant/components/kmtronic/translations/zh-Hant.json +++ b/homeassistant/components/kmtronic/translations/zh-Hant.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "\u53cd\u5411\u958b\u95dc\u908f\u8f2f\uff08\u4f7f\u7528 NC\uff09" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/hu.json b/homeassistant/components/kodi/translations/hu.json index 3b2d79a34a77e2..64dbfac0c8b2cd 100644 --- a/homeassistant/components/kodi/translations/hu.json +++ b/homeassistant/components/kodi/translations/hu.json @@ -1,7 +1,41 @@ { "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", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Add meg a Kodi felhaszn\u00e1l\u00f3nevet \u00e9s jelsz\u00f3t. Ezek megtal\u00e1lhat\u00f3k a Rendszer/Be\u00e1ll\u00edt\u00e1sok/H\u00e1l\u00f3zat/Szolg\u00e1ltat\u00e1sok r\u00e9szben." + }, + "discovery_confirm": { + "description": "Szeretn\u00e9d hozz\u00e1adni a Kodi (`{name}`)-t a Home Assistant-hoz?", + "title": "Felfedezett Kodi" + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata" + } + }, + "ws_port": { + "data": { + "ws_port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/id.json b/homeassistant/components/kodi/translations/id.json new file mode 100644 index 00000000000000..1a81ab72fabfac --- /dev/null +++ b/homeassistant/components/kodi/translations/id.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_uuid": "Instans Kodi tidak memiliki ID yang unik. Ini kemungkinan besar karena versi Kodi lama (17.x atau sebelumnya). Anda dapat mengonfigurasi integrasi secara manual atau meningkatkan ke versi Kodi yang lebih baru.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan nama pengguna dan kata sandi Kodi Anda. Ini dapat ditemukan di dalam Sistem/Setelan/Jaringan/Layanan." + }, + "discovery_confirm": { + "description": "Ingin menambahkan Kodi (`{name}`) to Home Assistant?", + "title": "Kodi yang ditemukan" + }, + "user": { + "data": { + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL" + }, + "description": "Informasi koneksi Kodi. Pastikan untuk mengaktifkan \"Izinkan kontrol Kodi melalui HTTP\" di Sistem/Pengaturan/Jaringan/Layanan." + }, + "ws_port": { + "data": { + "ws_port": "Port" + }, + "description": "Port WebSocket (kadang-kadang disebut port TCP di Kodi). Untuk terhubung melalui WebSocket, Anda perlu mengaktifkan \"Izinkan program... untuk mengontrol Kodi\" dalam Sistem/Pengaturan/Jaringan/Layanan. Jika WebSocket tidak diaktifkan, hapus port dan biarkan kosong." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} diminta untuk dimatikan", + "turn_on": "{entity_name} diminta untuk dinyalakan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/ko.json b/homeassistant/components/kodi/translations/ko.json index 233cd068a1ee2a..3c5a1a38e963a7 100644 --- a/homeassistant/components/kodi/translations/ko.json +++ b/homeassistant/components/kodi/translations/ko.json @@ -4,6 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_uuid": "Kodi \uc778\uc2a4\ud134\uc2a4\uc5d0 \uace0\uc720\ud55c ID\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774\ub294 \uc624\ub798\ub41c Kodi \ubc84\uc804(17.x \uc774\ud558) \ub54c\ubb38\uc77c \uac00\ub2a5\uc131\uc774 \ub192\uc2b5\ub2c8\ub2e4. \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ud558\uac70\ub098 \ucd5c\uc2e0 Kodi \ubc84\uc804\uc73c\ub85c \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\ubcf4\uc138\uc694.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -18,11 +19,11 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Kodi \uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624. \uc774\ub7ec\ud55c \ub0b4\uc6a9\uc740 \uc2dc\uc2a4\ud15c/\uc124\uc815/\ub124\ud2b8\uc6cc\ud06c/\uc11c\ube44\uc2a4\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "description": "Kodi \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc124\uc815/\uc11c\ube44\uc2a4/\ucee8\ud2b8\ub864/\uc6f9 \uc11c\ubc84\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "discovery_confirm": { - "description": "Kodi (` {name} `)\ub97c Home Assistant\uc5d0 \ucd94\uac00 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Kodi \ubc1c\uacac" + "description": "Home Assistant\uc5d0 Kodi (`{name}`)\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Kodi" }, "user": { "data": { @@ -30,14 +31,20 @@ "port": "\ud3ec\ud2b8", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9" }, - "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4. \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c Kodi \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4\uc785\ub2c8\ub2e4. \uc124\uc815/\uc11c\ube44\uc2a4/\ucee8\ud2b8\ub864/\uc6f9 \uc11c\ubc84\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c \uc6d0\uaca9 \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "ws_port": { "data": { "ws_port": "\ud3ec\ud2b8" }, - "description": "WebSocket \ud3ec\ud2b8 (Kodi\uc5d0\uc11c TCP \ud3ec\ud2b8\ub77c\uace0\ub3c4 \ud568). WebSocket\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub824\uba74 \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"\ud504\ub85c\uadf8\ub7a8\uc774 Kodi\ub97c \uc81c\uc5b4\ud558\ub3c4\ub85d \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud574\uc57c\ud569\ub2c8\ub2e4. WebSocket\uc774 \ud65c\uc131\ud654\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc81c\uac70\ud558\uace0 \ube44\uc6cc \ub461\ub2c8\ub2e4." + "description": "\uc6f9 \uc18c\ucf13 \ud3ec\ud2b8(Kodi\uc5d0\uc11c\ub294 \ud3ec\ud2b8\ub77c\uace0\ub3c4 \ud568)\uc785\ub2c8\ub2e4. \uc6f9 \uc18c\ucf13\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815/\uc11c\ube44\uc2a4/\ucee8\ud2b8\ub864/\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ucee8\ud2b8\ub864\uc5d0\uc11c \"\uc774 \uc2dc\uc2a4\ud15c\uc758/\ub2e4\ub978 \uc2dc\uc2a4\ud15c\uc758 \ud504\ub85c\uadf8\ub7a8\uc5d0 \uc758\ud55c \uc6d0\uaca9 \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud574\uc57c \ud569\ub2c8\ub2e4. \uc6f9 \uc18c\ucf13\uc744 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\ub294 \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc81c\uac70\ud558\uace0 \ube44\uc6cc\ub450\uc138\uc694." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name}\uc774(\uac00) \uaebc\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c", + "turn_on": "{entity_name}\uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 57476791b8fa1a..95d8ea995d5e77 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie", + "no_uuid": "Kodi-instantie heeft geen unieke ID. Dit komt waarschijnlijk door een oude Kodi-versie (17.x of lager). U kunt de integratie handmatig configureren of upgraden naar een recentere Kodi-versie.", "unknown": "Onverwachte fout" }, "error": { diff --git a/homeassistant/components/konnected/translations/hu.json b/homeassistant/components/konnected/translations/hu.json index 1cc44a02646be7..507e5d258f295e 100644 --- a/homeassistant/components/konnected/translations/hu.json +++ b/homeassistant/components/konnected/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { @@ -11,6 +19,11 @@ }, "options": { "step": { + "options_binary": { + "data": { + "name": "N\u00e9v (nem k\u00f6telez\u0151)" + } + }, "options_digital": { "data": { "name": "N\u00e9v (nem k\u00f6telez\u0151)", diff --git a/homeassistant/components/konnected/translations/id.json b/homeassistant/components/konnected/translations/id.json new file mode 100644 index 00000000000000..633e6bba2df496 --- /dev/null +++ b/homeassistant/components/konnected/translations/id.json @@ -0,0 +1,108 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "not_konn_panel": "Bukan perangkat Konnected.io yang dikenali", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "confirm": { + "description": "Model: {model}\nID: {id}\nHost: {host}\nPort: {port}\n\nAnda dapat mengonfigurasi IO dan perilaku panel di pengaturan Panel Alarm Konnected.", + "title": "Perangkat Konnected Siap" + }, + "import_confirm": { + "description": "Panel Alarm Konnected dengan ID {id} telah ditemukan di configuration.yaml. Aliran ini akan memungkinkan Anda mengimpornya menjadi entri konfigurasi.", + "title": "Impor Perangkat Konnected" + }, + "user": { + "data": { + "host": "Alamat IP", + "port": "Port" + }, + "description": "Masukkan informasi host untuk Panel Konnected Anda." + } + } + }, + "options": { + "abort": { + "not_konn_panel": "Bukan perangkat Konnected.io yang dikenali" + }, + "error": { + "bad_host": "URL host API yang Menimpa Tidak Valid" + }, + "step": { + "options_binary": { + "data": { + "inverse": "Balikkan status buka/tutup", + "name": "Nama (opsional)", + "type": "Jenis Sensor Biner" + }, + "description": "Opsi {zone}", + "title": "Konfigurasikan Sensor Biner" + }, + "options_digital": { + "data": { + "name": "Nama (opsional)", + "poll_interval": "Interval Polling (dalam menit) (opsional)", + "type": "Jenis Sensor" + }, + "description": "Opsi {zone}", + "title": "Konfigurasi Sensor Digital" + }, + "options_io": { + "data": { + "1": "Zona 1", + "2": "Zona 2", + "3": "Zona 3", + "4": "Zona 4", + "5": "Zona 5", + "6": "Zona 6", + "7": "Zona 7", + "out": "OUT" + }, + "description": "Ditemukan {model} di {host}. Pilih konfigurasi dasar setiap I/O di bawah ini - tergantung pada I/O yang mungkin memungkinkan sensor biner (kontak terbuka/tutup), sensor digital (dht dan ds18b20), atau sakelar output. Anda dapat mengonfigurasi opsi terperinci dalam langkah berikutnya.", + "title": "Konfigurasikan I/O" + }, + "options_io_ext": { + "data": { + "10": "Zona 10", + "11": "Zona 11", + "12": "Zona 12", + "8": "Zona 8", + "9": "Zona 9", + "alarm1": "ALARM1", + "alarm2_out2": "OUT2/ALARM2", + "out1": "OUT1" + }, + "description": "Pilih konfigurasi I/O lainnya di bawah ini. Anda dapat mengonfigurasi detail opsi pada langkah berikutnya.", + "title": "Konfigurasikan I/O yang Diperluas" + }, + "options_misc": { + "data": { + "api_host": "Ganti URL host API (opsional)", + "blink": "Kedipkan panel LED saat mengirim perubahan status", + "discovery": "Tanggapi permintaan penemuan di jaringan Anda", + "override_api_host": "Timpa URL panel host API Home Assistant bawaan" + }, + "description": "Pilih perilaku yang diinginkan untuk panel Anda", + "title": "Konfigurasikan Lainnya" + }, + "options_switch": { + "data": { + "activation": "Keluaran saat nyala", + "momentary": "Durasi pulsa (milidetik) (opsional)", + "more_states": "Konfigurasikan status tambahan untuk zona ini", + "name": "Nama (opsional)", + "pause": "Jeda di antara pulsa (milidetik) (opsional)", + "repeat": "Waktu pengulangan (-1 = tak terbatas) (opsional)" + }, + "description": "Opsi {zone}: status {state}", + "title": "Konfigurasikan Output yang Dapat Dialihkan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/ko.json b/homeassistant/components/konnected/translations/ko.json index fe5b9a0347aaed..eb7fa3de4e0171 100644 --- a/homeassistant/components/konnected/translations/ko.json +++ b/homeassistant/components/konnected/translations/ko.json @@ -15,7 +15,7 @@ "title": "Konnected \uae30\uae30 \uc900\ube44" }, "import_confirm": { - "description": "Konnected \uc54c\ub78c \ud328\ub110 ID {id} \uac00 configuration.yaml \uc5d0\uc11c \ubc1c\uacac\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \uacfc\uc815\uc744 \ud1b5\ud574 \uad6c\uc131 \ud56d\ubaa9\uc73c\ub85c \uac00\uc838\uc62c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "Konnected \uc54c\ub78c \ud328\ub110 ID {id}\uac00 configuration.yaml\uc5d0\uc11c \ubc1c\uacac\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \uacfc\uc815\uc744 \ud1b5\ud574 \uad6c\uc131 \ud56d\ubaa9\uc73c\ub85c \uac00\uc838\uc62c \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Konnected \uae30\uae30 \uac00\uc838\uc624\uae30" }, "user": { @@ -85,7 +85,7 @@ "data": { "api_host": "API \ud638\uc2a4\ud2b8 URL \uc7ac\uc815\uc758 (\uc120\ud0dd \uc0ac\ud56d)", "blink": "\uc0c1\ud0dc \ubcc0\uacbd\uc744 \ubcf4\ub0bc \ub54c \uae5c\ubc15\uc784 \ud328\ub110 LED \ub97c \ucf2d\ub2c8\ub2e4", - "discovery": "\ub124\ud2b8\uc6cc\ud06c\uc758 \uac80\uc0c9 \uc694\uccad\uc5d0 \uc751\ub2f5", + "discovery": "\ub124\ud2b8\uc6cc\ud06c\uc758 \uac80\uc0c9 \uc694\uccad\uc5d0 \uc751\ub2f5\ud558\uae30", "override_api_host": "\uae30\ubcf8 Home Assistant API \ud638\uc2a4\ud2b8 \ud328\ub110 URL \uc7ac\uc815\uc758" }, "description": "\ud328\ub110\uc5d0 \uc6d0\ud558\ub294 \ub3d9\uc791\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694", diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 9a7f20ac1e1379..75b11338016ca6 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -48,7 +48,7 @@ }, "options_digital": { "data": { - "name": "Naam (optioneel)", + "name": "Naam (optional)", "poll_interval": "Poll interval (minuten) (optioneel)", "type": "Type sensor" }, diff --git a/homeassistant/components/kulersky/translations/de.json b/homeassistant/components/kulersky/translations/de.json index 96ed09a974f40d..86bc8e3673075f 100644 --- a/homeassistant/components/kulersky/translations/de.json +++ b/homeassistant/components/kulersky/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/kulersky/translations/hu.json b/homeassistant/components/kulersky/translations/hu.json index 3d5be90042efea..6c61530acbebb9 100644 --- a/homeassistant/components/kulersky/translations/hu.json +++ b/homeassistant/components/kulersky/translations/hu.json @@ -1,8 +1,12 @@ { "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "confirm": { - "description": "El akarod kezdeni a be\u00e1ll\u00edt\u00e1st?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" } } } diff --git a/homeassistant/components/kulersky/translations/id.json b/homeassistant/components/kulersky/translations/id.json new file mode 100644 index 00000000000000..223836a8b40992 --- /dev/null +++ b/homeassistant/components/kulersky/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kulersky/translations/ko.json b/homeassistant/components/kulersky/translations/ko.json index 7011a61f7573ab..e5ae04d6e5c810 100644 --- a/homeassistant/components/kulersky/translations/ko.json +++ b/homeassistant/components/kulersky/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/life360/translations/he.json b/homeassistant/components/life360/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/life360/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/hu.json b/homeassistant/components/life360/translations/hu.json index 086e3ebf7d2c76..603efee6d9d505 100644 --- a/homeassistant/components/life360/translations/hu.json +++ b/homeassistant/components/life360/translations/hu.json @@ -1,15 +1,17 @@ { "config": { "abort": { - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "create_entry": { - "default": "A speci\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1s\u00e1hoz l\u00e1sd: [Life360 dokument\u00e1ci\u00f3] ( {docs_url} )." + "default": "A speci\u00e1lis be\u00e1ll\u00edt\u00e1sok megad\u00e1s\u00e1hoz l\u00e1sd: [Life360 dokument\u00e1ci\u00f3]({docs_url})." }, "error": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "invalid_username": "\u00c9rv\u00e9nytelen felhaszn\u00e1l\u00f3n\u00e9v", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/life360/translations/id.json b/homeassistant/components/life360/translations/id.json index 2bb7a1cca688ee..21a93366c446b8 100644 --- a/homeassistant/components/life360/translations/id.json +++ b/homeassistant/components/life360/translations/id.json @@ -1,7 +1,27 @@ { "config": { + "abort": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "create_entry": { + "default": "Untuk mengatur opsi tingkat lanjut, baca [dokumentasi Life360]({docs_url})." + }, "error": { - "invalid_username": "Nama pengguna tidak valid" + "already_configured": "Akun sudah dikonfigurasi", + "invalid_auth": "Autentikasi tidak valid", + "invalid_username": "Nama pengguna tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Untuk mengatur opsi tingkat lanjut, baca [dokumentasi Life360]({docs_url}).\nAnda mungkin ingin melakukannya sebelum menambahkan akun.", + "title": "Info Akun Life360" + } } } } \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/hu.json b/homeassistant/components/lifx/translations/hu.json index 0b6cdb39fd4994..f706dcefa962b3 100644 --- a/homeassistant/components/lifx/translations/hu.json +++ b/homeassistant/components/lifx/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nem tal\u00e1lhat\u00f3k LIFX eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egyetlen LIFX konfigur\u00e1ci\u00f3 lehets\u00e9ges." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/id.json b/homeassistant/components/lifx/translations/id.json new file mode 100644 index 00000000000000..03b2b387c6f12b --- /dev/null +++ b/homeassistant/components/lifx/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan LIFX?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/translations/ko.json b/homeassistant/components/lifx/translations/ko.json index 34bec9c3aee4fe..4d388cbeda289d 100644 --- a/homeassistant/components/lifx/translations/ko.json +++ b/homeassistant/components/lifx/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "LIFX \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "LIFX\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/lifx/translations/nl.json b/homeassistant/components/lifx/translations/nl.json index 60efcdffa463ca..0e0a6190f0d783 100644 --- a/homeassistant/components/lifx/translations/nl.json +++ b/homeassistant/components/lifx/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen LIFX-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts een enkele configuratie van LIFX is mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/light/translations/id.json b/homeassistant/components/light/translations/id.json index bc2ba732df2bd2..25c636ac1c7e56 100644 --- a/homeassistant/components/light/translations/id.json +++ b/homeassistant/components/light/translations/id.json @@ -1,8 +1,26 @@ { + "device_automation": { + "action_type": { + "brightness_decrease": "Kurangi kecerahan {entity_name}", + "brightness_increase": "Tingkatkan kecerahan {entity_name}", + "flash": "Lampu kilat {entity_name}", + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Lampu" diff --git a/homeassistant/components/light/translations/ko.json b/homeassistant/components/light/translations/ko.json index 33afda2d06d5b3..5413ecd567e682 100644 --- a/homeassistant/components/light/translations/ko.json +++ b/homeassistant/components/light/translations/ko.json @@ -1,20 +1,20 @@ { "device_automation": { "action_type": { - "brightness_decrease": "{entity_name} \uc744(\ub97c) \uc5b4\ub461\uac8c \ud558\uae30", - "brightness_increase": "{entity_name} \uc744(\ub97c) \ubc1d\uac8c \ud558\uae30", - "flash": "{entity_name} \ud50c\ub798\uc2dc", - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "brightness_decrease": "{entity_name}\uc744(\ub97c) \uc5b4\ub461\uac8c \ud558\uae30", + "brightness_increase": "{entity_name}\uc744(\ub97c) \ubc1d\uac8c \ud558\uae30", + "flash": "{entity_name}\uc744(\ub97c) \uae5c\ube61\uc774\uae30", + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/litejet/translations/bg.json b/homeassistant/components/litejet/translations/bg.json new file mode 100644 index 00000000000000..4983c9a14b265d --- /dev/null +++ b/homeassistant/components/litejet/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/hu.json b/homeassistant/components/litejet/translations/hu.json new file mode 100644 index 00000000000000..6d895624c30dd9 --- /dev/null +++ b/homeassistant/components/litejet/translations/hu.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "title": "Csatlakoz\u00e1s a LiteJet-hez" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/id.json b/homeassistant/components/litejet/translations/id.json new file mode 100644 index 00000000000000..690692ca4ccba7 --- /dev/null +++ b/homeassistant/components/litejet/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "open_failed": "Tidak dapat membuka port serial yang ditentukan." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "description": "Hubungkan port RS232-2 LiteJet ke komputer Anda dan masukkan jalur ke perangkat port serial.\n\nLiteJet MCP harus dikonfigurasi untuk baud 19,2 K, bit data 8,bit stop 1, tanpa paritas, dan untuk mengirimkan 'CR' setelah setiap respons.", + "title": "Hubungkan ke LiteJet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/ko.json b/homeassistant/components/litejet/translations/ko.json new file mode 100644 index 00000000000000..829918dc55712e --- /dev/null +++ b/homeassistant/components/litejet/translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "open_failed": "\uc9c0\uc815\ud55c \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\ub97c \uc5f4 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "port": "\ud3ec\ud2b8" + }, + "description": "LiteJet\uc758 RS232-2 \ud3ec\ud2b8\ub97c \ucef4\ud4e8\ud130\uc5d0 \uc5f0\uacb0\ud558\uace0 \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8 \uc7a5\uce58\uc758 \uacbd\ub85c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nLiteJet MCP\ub294 19.2 K \uc804\uc1a1\uc18d\ub3c4, 8 \ub370\uc774\ud130 \ube44\ud2b8, 1 \uc815\uc9c0 \ube44\ud2b8, \ud328\ub9ac\ud2f0 \uc5c6\uc74c\uc73c\ub85c \uad6c\uc131\ub418\uc5b4\uc57c \ud558\uba70 \uac01 \uc751\ub2f5 \ud6c4\uc5d0 'CR'\uc744 \uc804\uc1a1\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "Litejet\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litejet/translations/nl.json b/homeassistant/components/litejet/translations/nl.json index f16f25a3987326..a96f8de6171158 100644 --- a/homeassistant/components/litejet/translations/nl.json +++ b/homeassistant/components/litejet/translations/nl.json @@ -11,6 +11,7 @@ "data": { "port": "Poort" }, + "description": "Verbind de RS232-2 poort van de LiteJet met uw computer en voer het pad naar het seri\u00eble poortapparaat in.\n\nDe LiteJet MCP moet worden geconfigureerd voor 19,2 K baud, 8 databits, 1 stopbit, geen pariteit, en om een 'CR' uit te zenden na elk antwoord.", "title": "Maak verbinding met LiteJet" } } diff --git a/homeassistant/components/litejet/translations/pt.json b/homeassistant/components/litejet/translations/pt.json new file mode 100644 index 00000000000000..8b09f9a245f3d6 --- /dev/null +++ b/homeassistant/components/litejet/translations/pt.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel." + }, + "step": { + "user": { + "data": { + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/bg.json b/homeassistant/components/litterrobot/translations/bg.json new file mode 100644 index 00000000000000..67a484573aa0c1 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/litterrobot/translations/es.json b/homeassistant/components/litterrobot/translations/es.json new file mode 100644 index 00000000000000..12a48f17c3229b --- /dev/null +++ b/homeassistant/components/litterrobot/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fallo al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/hu.json b/homeassistant/components/litterrobot/translations/hu.json new file mode 100644 index 00000000000000..fd8db27da5efd0 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/hu.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/id.json b/homeassistant/components/litterrobot/translations/id.json new file mode 100644 index 00000000000000..4a84db42a14f2a --- /dev/null +++ b/homeassistant/components/litterrobot/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/ko.json b/homeassistant/components/litterrobot/translations/ko.json new file mode 100644 index 00000000000000..94261de9637607 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/litterrobot/translations/pt.json b/homeassistant/components/litterrobot/translations/pt.json new file mode 100644 index 00000000000000..565b9f6c0e8410 --- /dev/null +++ b/homeassistant/components/litterrobot/translations/pt.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/de.json b/homeassistant/components/local_ip/translations/de.json index 9e2a6eda5c6a27..2d31f90139d1e3 100644 --- a/homeassistant/components/local_ip/translations/de.json +++ b/homeassistant/components/local_ip/translations/de.json @@ -8,7 +8,7 @@ "data": { "name": "Sensorname" }, - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Lokale IP-Adresse" } } diff --git a/homeassistant/components/local_ip/translations/hu.json b/homeassistant/components/local_ip/translations/hu.json index 09b894742a6a9e..b692e87f92222f 100644 --- a/homeassistant/components/local_ip/translations/hu.json +++ b/homeassistant/components/local_ip/translations/hu.json @@ -1,10 +1,14 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "user": { "data": { "name": "\u00c9rz\u00e9kel\u0151 neve" }, + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "Helyi IP c\u00edm" } } diff --git a/homeassistant/components/local_ip/translations/id.json b/homeassistant/components/local_ip/translations/id.json new file mode 100644 index 00000000000000..a7d8993baa6fe1 --- /dev/null +++ b/homeassistant/components/local_ip/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "data": { + "name": "Nama Sensor" + }, + "description": "Ingin memulai penyiapan?", + "title": "Alamat IP Lokal" + } + } + }, + "title": "Alamat IP Lokal" +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/ko.json b/homeassistant/components/local_ip/translations/ko.json index 3b543f87f79ead..f7248ab0f6f83f 100644 --- a/homeassistant/components/local_ip/translations/ko.json +++ b/homeassistant/components/local_ip/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/local_ip/translations/nl.json b/homeassistant/components/local_ip/translations/nl.json index 57547adedd85c9..b8b033d2e73442 100644 --- a/homeassistant/components/local_ip/translations/nl.json +++ b/homeassistant/components/local_ip/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van lokaal IP toegestaan." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/de.json b/homeassistant/components/locative/translations/de.json index a6dcf4150d0ff7..2df9f889e85ab9 100644 --- a/homeassistant/components/locative/translations/de.json +++ b/homeassistant/components/locative/translations/de.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Locative Webhook einrichten" } } diff --git a/homeassistant/components/locative/translations/hu.json b/homeassistant/components/locative/translations/hu.json index 983ffacfa7df6a..8dc03e9c37a0c6 100644 --- a/homeassistant/components/locative/translations/hu.json +++ b/homeassistant/components/locative/translations/hu.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha helyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Locative alkalmaz\u00e1sban. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "Ha helyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Locative alkalmaz\u00e1sban. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t]({docs_url})." }, "step": { "user": { - "description": "Biztosan be szeretn\u00e9d \u00e1ll\u00edtani a Locative Webhook-ot?", + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "Locative Webhook be\u00e1ll\u00edt\u00e1sa" } } diff --git a/homeassistant/components/locative/translations/id.json b/homeassistant/components/locative/translations/id.json new file mode 100644 index 00000000000000..71aea5cc63cff7 --- /dev/null +++ b/homeassistant/components/locative/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim lokasi ke Home Assistant, Anda harus mengatur fitur webhook di aplikasi Locative.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) untuk detail lebih lanjut." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?", + "title": "Siapkan Locative Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/ko.json b/homeassistant/components/locative/translations/ko.json index 5930e7edf1ba33..da063335ff8ef4 100644 --- a/homeassistant/components/locative/translations/ko.json +++ b/homeassistant/components/locative/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative\uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/locative/translations/nl.json b/homeassistant/components/locative/translations/nl.json index 16cbbc77277af2..d66a1262b5dbc0 100644 --- a/homeassistant/components/locative/translations/nl.json +++ b/homeassistant/components/locative/translations/nl.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Weet u zeker dat u de Locative Webhook wilt instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Locative Webhook in" } } diff --git a/homeassistant/components/lock/translations/id.json b/homeassistant/components/lock/translations/id.json index da11e3422f15ee..d8778868651d52 100644 --- a/homeassistant/components/lock/translations/id.json +++ b/homeassistant/components/lock/translations/id.json @@ -1,8 +1,23 @@ { + "device_automation": { + "action_type": { + "lock": "Kunci {entity_name}", + "open": "Buka {entity_name}", + "unlock": "Buka kunci {entity_name}" + }, + "condition_type": { + "is_locked": "{entity_name} terkunci", + "is_unlocked": "{entity_name} tidak terkunci" + }, + "trigger_type": { + "locked": "{entity_name} terkunci", + "unlocked": "{entity_name} tidak terkunci" + } + }, "state": { "_": { "locked": "Terkunci", - "unlocked": "Terbuka" + "unlocked": "Tidak Terkunci" } }, "title": "Kunci" diff --git a/homeassistant/components/lock/translations/ko.json b/homeassistant/components/lock/translations/ko.json index 6f04e2b41101be..f89a1d1b54f701 100644 --- a/homeassistant/components/lock/translations/ko.json +++ b/homeassistant/components/lock/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "lock": "{entity_name} \uc7a0\uae08", - "open": "{entity_name} \uc5f4\uae30", - "unlock": "{entity_name} \uc7a0\uae08 \ud574\uc81c" + "lock": "{entity_name}\uc744(\ub97c) \uc7a0\uadf8\uae30", + "open": "{entity_name}\uc744(\ub97c) \uc5f4\uae30", + "unlock": "{entity_name}\uc744(\ub97c) \uc7a0\uae08 \ud574\uc81c\ud558\uae30" }, "condition_type": { - "is_locked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", - "is_unlocked": "{entity_name} \uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74" + "is_locked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc73c\uba74", + "is_unlocked": "{entity_name}\uc774(\uac00) \uc7a0\uaca8\uc788\uc9c0 \uc54a\uc73c\uba74" }, "trigger_type": { - "locked": "{entity_name} \uc774(\uac00) \uc7a0\uae38 \ub54c", - "unlocked": "{entity_name} \uc774(\uac00) \uc7a0\uae08\uc774 \ud574\uc81c\ub420 \ub54c" + "locked": "{entity_name}\uc774(\uac00) \uc7a0\uacbc\uc744 \ub54c", + "unlocked": "{entity_name}\uc774(\uac00) \uc7a0\uae08\uc774 \ud574\uc81c\ub418\uc5c8\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/lock/translations/zh-Hans.json b/homeassistant/components/lock/translations/zh-Hans.json index 4999c52f8e00c8..7e2a771971813b 100644 --- a/homeassistant/components/lock/translations/zh-Hans.json +++ b/homeassistant/components/lock/translations/zh-Hans.json @@ -1,13 +1,13 @@ { "device_automation": { "action_type": { - "lock": "\u4e0a\u9501{entity_name}", - "open": "\u5f00\u542f{entity_name}", - "unlock": "\u89e3\u9501{entity_name}" + "lock": "\u9501\u5b9a {entity_name}", + "open": "\u5f00\u542f {entity_name}", + "unlock": "\u89e3\u9501 {entity_name}" }, "condition_type": { - "is_locked": "{entity_name}\u5df2\u4e0a\u9501", - "is_unlocked": "{entity_name}\u5df2\u89e3\u9501" + "is_locked": "{entity_name} \u5df2\u9501\u5b9a", + "is_unlocked": "{entity_name} \u5df2\u89e3\u9501" }, "trigger_type": { "locked": "{entity_name} \u88ab\u9501\u5b9a", diff --git a/homeassistant/components/logi_circle/translations/hu.json b/homeassistant/components/logi_circle/translations/hu.json index 04c8229f5ff941..9c788350de4962 100644 --- a/homeassistant/components/logi_circle/translations/hu.json +++ b/homeassistant/components/logi_circle/translations/hu.json @@ -1,7 +1,13 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, "error": { - "follow_link": "K\u00e9rlek, k\u00f6vesd a hivatkoz\u00e1st \u00e9s hiteles\u00edtsd magad miel\u0151tt megnyomod a K\u00fcld\u00e9s gombot" + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "follow_link": "K\u00e9rlek, k\u00f6vesd a hivatkoz\u00e1st \u00e9s hiteles\u00edtsd magad miel\u0151tt megnyomod a K\u00fcld\u00e9s gombot", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "user": { diff --git a/homeassistant/components/logi_circle/translations/id.json b/homeassistant/components/logi_circle/translations/id.json new file mode 100644 index 00000000000000..3cbfdd03978f27 --- /dev/null +++ b/homeassistant/components/logi_circle/translations/id.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "external_error": "Eksepsi terjadi dari alur lain.", + "external_setup": "Logi Circle berhasil dikonfigurasi dari alur lain.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "error": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "follow_link": "Ikuti tautan dan autentikasi sebelum menekan Kirim.", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "auth": { + "description": "Buka tautan di bawah ini dan **Terima** akses ke akun Logi Circle Anda, lalu kembali dan tekan **Kirim** di bawah ini.\n\n[Tautan] ({authorization_url})", + "title": "Autentikasi dengan Logi Circle" + }, + "user": { + "data": { + "flow_impl": "Penyedia" + }, + "description": "Pilih melalui penyedia autentikasi mana yang ingin Anda autentikasi dengan Logi Circle.", + "title": "Penyedia Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/logi_circle/translations/ko.json b/homeassistant/components/logi_circle/translations/ko.json index 2300bbb27c6623..733c95f95e0338 100644 --- a/homeassistant/components/logi_circle/translations/ko.json +++ b/homeassistant/components/logi_circle/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "external_error": "\ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc608\uc678\uc0ac\ud56d\uc774 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "external_setup": "Logi Circle \uc774 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "external_setup": "Logi Circle\uc774 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." }, "error": { diff --git a/homeassistant/components/logi_circle/translations/nl.json b/homeassistant/components/logi_circle/translations/nl.json index 36970feb48b560..8c4d81d120eb64 100644 --- a/homeassistant/components/logi_circle/translations/nl.json +++ b/homeassistant/components/logi_circle/translations/nl.json @@ -13,7 +13,7 @@ }, "step": { "auth": { - "description": "Volg de onderstaande link en Accepteer toegang tot uw Logi Circle-account, kom dan terug en druk hieronder op Verzenden . \n\n [Link] ({authorization_url})", + "description": "Volg de onderstaande link en **Accepteer** toegang tot uw Logi Circle-account, kom dan terug en druk hieronder op **Verzenden** . \n\n [Link] ({authorization_url})", "title": "Authenticeren met Logi Circle" }, "user": { diff --git a/homeassistant/components/lovelace/translations/id.json b/homeassistant/components/lovelace/translations/id.json new file mode 100644 index 00000000000000..d945bc04f2235f --- /dev/null +++ b/homeassistant/components/lovelace/translations/id.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "Dasbor", + "mode": "Mode", + "resources": "Sumber Daya", + "views": "Tampilan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lovelace/translations/ko.json b/homeassistant/components/lovelace/translations/ko.json new file mode 100644 index 00000000000000..48a26a1371c9ef --- /dev/null +++ b/homeassistant/components/lovelace/translations/ko.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "dashboards": "\ub300\uc2dc\ubcf4\ub4dc", + "mode": "\ubaa8\ub4dc", + "resources": "\ub9ac\uc18c\uc2a4", + "views": "\ubdf0" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/luftdaten/translations/hu.json b/homeassistant/components/luftdaten/translations/hu.json index 4385c09d6efad0..2fa90c23ca8d25 100644 --- a/homeassistant/components/luftdaten/translations/hu.json +++ b/homeassistant/components/luftdaten/translations/hu.json @@ -1,6 +1,8 @@ { "config": { "error": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_sensor": "Az \u00e9rz\u00e9kel\u0151 nem el\u00e9rhet\u0151 vagy \u00e9rv\u00e9nytelen" }, "step": { diff --git a/homeassistant/components/luftdaten/translations/id.json b/homeassistant/components/luftdaten/translations/id.json new file mode 100644 index 00000000000000..96ec6d5f20ff10 --- /dev/null +++ b/homeassistant/components/luftdaten/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "already_configured": "Layanan sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_sensor": "Sensor tidak tersedia atau tidak valid" + }, + "step": { + "user": { + "data": { + "show_on_map": "Tampilkan di peta", + "station_id": "ID Sensor Luftdaten" + }, + "title": "Konfigurasikan Luftdaten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/bg.json b/homeassistant/components/lutron_caseta/translations/bg.json new file mode 100644 index 00000000000000..ba9f144cb0aa50 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/bg.json @@ -0,0 +1,12 @@ +{ + "device_automation": { + "trigger_subtype": { + "button_1": "\u041f\u044a\u0440\u0432\u0438 \u0431\u0443\u0442\u043e\u043d", + "button_2": "\u0412\u0442\u043e\u0440\u0438 \u0431\u0443\u0442\u043e\u043d", + "button_3": "\u0422\u0440\u0435\u0442\u0438 \u0431\u0443\u0442\u043e\u043d", + "button_4": "\u0427\u0435\u0442\u0432\u044a\u0440\u0442\u0438 \u0431\u0443\u0442\u043e\u043d", + "off": "\u0418\u0437\u043a\u043b.", + "on": "\u0412\u043a\u043b." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 48b305ac7faffa..2088cd5232766f 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -73,4 +73,4 @@ "release": "\"{subtype}\" released" } } -} +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/et.json b/homeassistant/components/lutron_caseta/translations/et.json index 81fee6d5b4afcc..5e57dd63b8b471 100644 --- a/homeassistant/components/lutron_caseta/translations/et.json +++ b/homeassistant/components/lutron_caseta/translations/et.json @@ -23,7 +23,7 @@ "host": "" }, "description": "Sisesta seadme IP-aadress.", - "title": "\u00dchendu sillaga automaatselt" + "title": "\u00dchenda sillaga automaatselt" } } }, diff --git a/homeassistant/components/lutron_caseta/translations/he.json b/homeassistant/components/lutron_caseta/translations/he.json new file mode 100644 index 00000000000000..7b55b0743fba7b --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/he.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "description": "\u05d4\u05d6\u05df \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4- IP \u05e9\u05dc \u05d4\u05de\u05db\u05e9\u05d9\u05e8." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/hu.json b/homeassistant/components/lutron_caseta/translations/hu.json new file mode 100644 index 00000000000000..bf949ddf21c9ed --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + }, + "description": "Add meg az eszk\u00f6z IP-c\u00edm\u00e9t." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Els\u0151 gomb", + "button_2": "M\u00e1sodik gomb", + "button_3": "Harmadik gomb", + "button_4": "Negyedik gomb", + "off": "Ki", + "on": "Be" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/id.json b/homeassistant/components/lutron_caseta/translations/id.json new file mode 100644 index 00000000000000..b14e9ad1c23a10 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/id.json @@ -0,0 +1,70 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "not_lutron_device": "Perangkat yang ditemukan bukan perangkat Lutron" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", + "step": { + "import_failed": { + "description": "Tidak dapat menyiapkan bridge (host: {host} ) yang diimpor dari configuration.yaml.", + "title": "Gagal mengimpor konfigurasi bridge Cas\u00e9ta." + }, + "link": { + "description": "Untuk memasangkan dengan {name} ({host}), setelah mengirimkan formulir ini, tekan tombol hitam di bagian belakang bridge.", + "title": "Pasangkan dengan bridge" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Masukkan alamat IP perangkat.", + "title": "Sambungkan secara otomatis ke bridge" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "close_1": "Tutup 1", + "close_2": "Tutup 2", + "close_3": "Tutup 3", + "close_4": "Tutup 4", + "close_all": "Tutup semua", + "group_1_button_1": "Tombol pertama Grup Pertama", + "group_1_button_2": "Tombol kedua Grup Pertama", + "group_2_button_1": "Tombol pertama Grup Kedua", + "group_2_button_2": "Tombol kedua Grup Kedua", + "lower": "Rendah", + "lower_1": "Rendah 1", + "lower_2": "Rendah 2", + "lower_3": "Rendah 3", + "lower_4": "Rendah 4", + "lower_all": "Rendahkan semua", + "off": "Mati", + "on": "Nyala", + "open_1": "Buka 1", + "open_2": "Buka 2", + "open_3": "Buka 3", + "open_4": "Buka 4", + "open_all": "Buka semua", + "stop": "Hentikan (favorit)", + "stop_1": "Hentikan 1", + "stop_2": "Hentikan 2", + "stop_3": "Hentikan 3", + "stop_4": "Hentikan 4", + "stop_all": "Hentikan semuanya" + }, + "trigger_type": { + "press": "\"{subtype}\" ditekan", + "release": "\"{subtype}\" dilepas" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ko.json b/homeassistant/components/lutron_caseta/translations/ko.json index af7fed5829c6be..862bfab8cfd734 100644 --- a/homeassistant/components/lutron_caseta/translations/ko.json +++ b/homeassistant/components/lutron_caseta/translations/ko.json @@ -2,21 +2,75 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "not_lutron_device": "\ubc1c\uacac\ub41c \uae30\uae30\ub294 Lutron \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Lutron Cas\u00e9ta: {name} ({host})", "step": { "import_failed": { "description": "configuration.yaml \uc5d0\uc11c \uac00\uc838\uc628 \ube0c\ub9ac\uc9c0 (\ud638\uc2a4\ud2b8:{host}) \ub97c \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "title": "Cas\u00e9ta \ube0c\ub9ac\uc9c0 \uad6c\uc131\uc744 \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." }, + "link": { + "description": "{name} ({host})\uacfc(\uc640) \ud398\uc5b4\ub9c1\ud558\ub824\uba74 \uc774 \uc591\uc2dd\uc744 \uc81c\ucd9c\ud55c \ud6c4 \ube0c\ub9ac\uc9c0 \ub4a4\ucabd\uc5d0 \uc788\ub294 \uac80\uc740\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", + "title": "\ube0c\ub9ac\uc9c0\uc640 \ud398\uc5b4\ub9c1\ud558\uae30" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\uae30\uae30\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ube0c\ub9ac\uc9c0\uc5d0 \uc790\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "\uccab \ubc88\uc9f8 \ubc84\ud2bc", + "button_2": "\ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "button_3": "\uc138 \ubc88\uc9f8 \ubc84\ud2bc", + "button_4": "\ub124 \ubc88\uc9f8 \ubc84\ud2bc", + "close_1": "1 \ub2eb\uae30", + "close_2": "2 \ub2eb\uae30", + "close_3": "3 \ub2eb\uae30", + "close_4": "4 \ub2eb\uae30", + "close_all": "\ubaa8\ub450 \ub2eb\uae30", + "group_1_button_1": "\uccab \ubc88\uc9f8 \uadf8\ub8f9 \uccab \ubc88\uc9f8 \ubc84\ud2bc", + "group_1_button_2": "\uccab \ubc88\uc9f8 \uadf8\ub8f9 \ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "group_2_button_1": "\ub450 \ubc88\uc9f8 \uadf8\ub8f9 \uccab \ubc88\uc9f8 \ubc84\ud2bc", + "group_2_button_2": "\ub450 \ubc88\uc9f8 \uadf8\ub8f9 \ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "lower": "\ub0ae\ucd94\uae30", + "lower_1": "1 \ub0ae\ucd94\uae30", + "lower_2": "2 \ub0ae\ucd94\uae30", + "lower_3": "3 \ub0ae\ucd94\uae30", + "lower_4": "4 \ub0ae\ucd94\uae30", + "lower_all": "\ubaa8\ub450 \ub0ae\ucd94\uae30", + "off": "\ub044\uae30", + "on": "\ucf1c\uae30", + "open_1": "1 \uc5f4\uae30", + "open_2": "2 \uc5f4\uae30", + "open_3": "3 \uc5f4\uae30", + "open_4": "4 \uc5f4\uae30", + "open_all": "\ubaa8\ub450 \uc5f4\uae30", + "raise": "\ub192\uc774\uae30", + "raise_1": "1 \uc62c\ub9ac\uae30", + "raise_2": "2 \uc62c\ub9ac\uae30", + "raise_3": "3 \uc62c\ub9ac\uae30", + "raise_4": "4 \uc62c\ub9ac\uae30", + "raise_all": "\ubaa8\ub450 \uc62c\ub9ac\uae30", + "stop": "\uc911\uc9c0 (\uc990\uaca8 \ucc3e\uae30)", + "stop_1": "1 \uc911\uc9c0", + "stop_2": "2 \uc911\uc9c0", + "stop_3": "3 \uc911\uc9c0", + "stop_4": "4 \uc911\uc9c0", + "stop_all": "\ubaa8\ub450 \uc911\uc9c0" + }, + "trigger_type": { + "press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub838\uc744 \ub54c", + "release": "\"{subtype}\"\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/lb.json b/homeassistant/components/lutron_caseta/translations/lb.json index e78f88397976f4..6a390bc5a1f6d1 100644 --- a/homeassistant/components/lutron_caseta/translations/lb.json +++ b/homeassistant/components/lutron_caseta/translations/lb.json @@ -13,5 +13,37 @@ "title": "Feeler beim import vun der Cas\u00e9ta Bridge Konfiguratioun" } } + }, + "device_automation": { + "trigger_subtype": { + "lower": "Erofsetzen", + "lower_1": "1 erofsetzen", + "lower_2": "2 erofsetzen", + "lower_3": "3 erofsetzen", + "lower_4": "4 erofsetzen", + "lower_all": "All erofsetzen", + "off": "Aus", + "on": "Un", + "open_1": "1 opmaachen", + "open_2": "2 opmaachen", + "open_3": "3 opmaachen", + "open_4": "4 opmaachen", + "open_all": "All opmaachen", + "raise": "Unhiewen", + "raise_1": "1 unhiewen", + "raise_2": "2 unhiewen", + "raise_3": "3 unhiewen", + "raise_4": "4 unhiewen", + "raise_all": "All unhiewen", + "stop_1": "Stop 1", + "stop_2": "Stop 2", + "stop_3": "Stop 3", + "stop_4": "Stop 4", + "stop_all": "All stoppen" + }, + "trigger_type": { + "press": "\"{subtype}\" gedr\u00e9ckt", + "release": "\"{subtype}\" lassgelooss" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index 17e6fc47fd8966..d74a18622d0071 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Kon niet verbinden" + "cannot_connect": "Kon niet verbinden", + "not_lutron_device": "Ontdekt apparaat is geen Lutron-apparaat" }, "error": { "cannot_connect": "Kon niet verbinden" @@ -10,13 +11,18 @@ "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { "import_failed": { - "description": "Kan bridge (host: {host} ) niet instellen, ge\u00efmporteerd uit configuration.yaml." + "description": "Kan bridge (host: {host} ) niet instellen, ge\u00efmporteerd uit configuration.yaml.", + "title": "Het importeren van de Cas\u00e9ta bridge configuratie is mislukt." + }, + "link": { + "title": "Koppel met de bridge" }, "user": { "data": { "host": "Host" }, - "description": "Voer het IP-adres van het apparaat in." + "description": "Voer het IP-adres van het apparaat in.", + "title": "Automatisch verbinding maken met de bridge" } } }, @@ -35,17 +41,31 @@ "group_1_button_2": "Eerste Groep tweede knop", "group_2_button_1": "Tweede Groep eerste knop", "group_2_button_2": "Tweede Groep tweede knop", + "lower": "Verlagen", + "lower_1": "Verlagen 1", + "lower_2": "Verlagen 2", + "lower_3": "Verlagen 3", + "lower_4": "Verlaag 4", + "lower_all": "Verlaag alles", "off": "Uit", "on": "Aan", "open_1": "Open 1", "open_2": "Open 2", "open_3": "Open 3", "open_4": "Open 4", + "open_all": "Open alle", + "raise": "Verhogen", + "raise_1": "Verhogen 1", + "raise_2": "Verhogen 2", + "raise_3": "Verhogen 3", + "raise_4": "Verhogen 4", + "raise_all": "Verhoog alles", "stop": "Stop (favoriet)", "stop_1": "Stop 1", "stop_2": "Stop 2", "stop_3": "Stop 3", - "stop_4": "Stop 4" + "stop_4": "Stop 4", + "stop_all": "Stop alles" }, "trigger_type": { "press": "\" {subtype} \" ingedrukt", diff --git a/homeassistant/components/lyric/translations/hu.json b/homeassistant/components/lyric/translations/hu.json new file mode 100644 index 00000000000000..cae1f6d20c0393 --- /dev/null +++ b/homeassistant/components/lyric/translations/hu.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/id.json b/homeassistant/components/lyric/translations/id.json new file mode 100644 index 00000000000000..876fe2f8c39c0c --- /dev/null +++ b/homeassistant/components/lyric/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/hu.json b/homeassistant/components/mailgun/translations/hu.json index 51bbe6ef04cc6c..14c2293734cd73 100644 --- a/homeassistant/components/mailgun/translations/hu.json +++ b/homeassistant/components/mailgun/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant programnak, be kell \u00e1ll\u00edtania a [Webhooks Mailgun-al] ( {mailgun_url} ) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3] ( {docs_url} ), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant programnak, be kell \u00e1ll\u00edtania a [Webhooks with Mailgun]({mailgun_url}) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/json \n\n L\u00e1sd [a dokument\u00e1ci\u00f3]({docs_url}), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/translations/id.json b/homeassistant/components/mailgun/translations/id.json new file mode 100644 index 00000000000000..b58deb171bef01 --- /dev/null +++ b/homeassistant/components/mailgun/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [Webhooks dengan Mailgun]({mailgun_url}).\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Mailgun?", + "title": "Siapkan Mailgun Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/ko.json b/homeassistant/components/mailgun/translations/ko.json index b757a27f4a0608..1be1721d78f45a 100644 --- a/homeassistant/components/mailgun/translations/ko.json +++ b/homeassistant/components/mailgun/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/mazda/translations/bg.json b/homeassistant/components/mazda/translations/bg.json new file mode 100644 index 00000000000000..6f3c5a54f3f3b8 --- /dev/null +++ b/homeassistant/components/mazda/translations/bg.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "reauth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + } + }, + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "region": "\u0420\u0435\u0433\u0438\u043e\u043d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/de.json b/homeassistant/components/mazda/translations/de.json index 4e23becb8af642..9050ee9f00cf04 100644 --- a/homeassistant/components/mazda/translations/de.json +++ b/homeassistant/components/mazda/translations/de.json @@ -5,6 +5,7 @@ "reauth_successful": "Die erneute Authentifizierung war erfolgreich" }, "error": { + "account_locked": "Konto gesperrt. Bitte versuchen Sie es sp\u00e4ter erneut.", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" @@ -15,15 +16,20 @@ "email": "E-Mail", "password": "Passwort", "region": "Region" - } + }, + "description": "Die Authentifizierung f\u00fcr Mazda Connected Services ist fehlgeschlagen. Bitte geben Sie Ihre aktuellen Anmeldedaten ein.", + "title": "Mazda Connected Services - Authentifizierung fehlgeschlagen" }, "user": { "data": { "email": "E-Mail", "password": "Passwort", "region": "Region" - } + }, + "description": "Bitte geben Sie die E-Mail-Adresse und das Passwort ein, die Sie f\u00fcr die Anmeldung bei der MyMazda Mobile App verwenden.", + "title": "Mazda Connected Services - Konto hinzuf\u00fcgen" } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/hu.json b/homeassistant/components/mazda/translations/hu.json new file mode 100644 index 00000000000000..1b9c6893ed547a --- /dev/null +++ b/homeassistant/components/mazda/translations/hu.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "account_locked": "Fi\u00f3k z\u00e1rolva. K\u00e9rlek, pr\u00f3b\u00e1ld \u00fajra k\u00e9s\u0151bb.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3", + "region": "R\u00e9gi\u00f3" + }, + "title": "Mazda Connected Services - A hiteles\u00edt\u00e9s sikertelen" + }, + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3", + "region": "R\u00e9gi\u00f3" + }, + "title": "Mazda Connected Services - Fi\u00f3k hozz\u00e1ad\u00e1sa" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/id.json b/homeassistant/components/mazda/translations/id.json new file mode 100644 index 00000000000000..0a6e81e8454718 --- /dev/null +++ b/homeassistant/components/mazda/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "account_locked": "Akun terkunci. Coba lagi nanti.", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth": { + "data": { + "email": "Email", + "password": "Kata Sandi", + "region": "Wilayah" + }, + "description": "Autentikasi gagal untuk Mazda Connected Services. Masukkan kredensial Anda saat ini.", + "title": "Mazda Connected Services - Autentikasi Gagal" + }, + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi", + "region": "Wilayah" + }, + "description": "Masukkan alamat email dan kata sandi yang digunakan untuk masuk ke aplikasi seluler MyMazda.", + "title": "Mazda Connected Services - Tambahkan Akun" + } + } + }, + "title": "Mazda Connected Services" +} \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/ko.json b/homeassistant/components/mazda/translations/ko.json index 31495b0d8e324a..aa9dcf99d14499 100644 --- a/homeassistant/components/mazda/translations/ko.json +++ b/homeassistant/components/mazda/translations/ko.json @@ -5,6 +5,7 @@ "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "account_locked": "\uacc4\uc815\uc774 \uc7a0\uacbc\uc2b5\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" @@ -13,15 +14,22 @@ "reauth": { "data": { "email": "\uc774\uba54\uc77c", - "password": "\ube44\ubc00\ubc88\ud638" - } + "password": "\ube44\ubc00\ubc88\ud638", + "region": "\uc9c0\uc5ed" + }, + "description": "Mazda Connected Services\uc5d0 \ub300\ud55c \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Mazda Connected Services - \uc778\uc99d \uc2e4\ud328" }, "user": { "data": { "email": "\uc774\uba54\uc77c", - "password": "\ube44\ubc00\ubc88\ud638" - } + "password": "\ube44\ubc00\ubc88\ud638", + "region": "\uc9c0\uc5ed" + }, + "description": "MyMazda \ubaa8\ubc14\uc77c \uc571\uc5d0 \ub85c\uadf8\uc778\ud558\uae30 \uc704\ud574 \uc0ac\uc6a9\ud558\ub294 \uc774\uba54\uc77c \uc8fc\uc18c\uc640 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Mazda Connected Services - \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } - } + }, + "title": "Mazda Connected Services" } \ No newline at end of file diff --git a/homeassistant/components/mazda/translations/nl.json b/homeassistant/components/mazda/translations/nl.json index 3198bfb4192e5f..64975c9b14b4f5 100644 --- a/homeassistant/components/mazda/translations/nl.json +++ b/homeassistant/components/mazda/translations/nl.json @@ -14,15 +14,20 @@ "reauth": { "data": { "email": "E-mail", - "password": "Wachtwoord" - } + "password": "Wachtwoord", + "region": "Regio" + }, + "description": "Verificatie mislukt voor Mazda Connected Services. Voer uw huidige inloggegevens in.", + "title": "Mazda Connected Services - Authenticatie mislukt" }, "user": { "data": { "email": "E-mail", "password": "Wachtwoord", "region": "Regio" - } + }, + "description": "Voer het e-mailadres en wachtwoord in dat u gebruikt om in te loggen op de MyMazda mobiele app.", + "title": "Mazda Connected Services - Account toevoegen" } } }, diff --git a/homeassistant/components/media_player/translations/hu.json b/homeassistant/components/media_player/translations/hu.json index 0eae14fdd986b8..83b5dc4e12281b 100644 --- a/homeassistant/components/media_player/translations/hu.json +++ b/homeassistant/components/media_player/translations/hu.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} be van kapcsolva", "is_paused": "{entity_name} sz\u00fcneteltetve van", "is_playing": "{entity_name} lej\u00e1tszik" + }, + "trigger_type": { + "idle": "{entity_name} t\u00e9tlenn\u00e9 v\u00e1lik", + "paused": "{entity_name} sz\u00fcneteltetve van", + "playing": "{entity_name} megkezdi a lej\u00e1tsz\u00e1st", + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" } }, "state": { diff --git a/homeassistant/components/media_player/translations/id.json b/homeassistant/components/media_player/translations/id.json index bcf12d72542e8d..e759f88a15a36c 100644 --- a/homeassistant/components/media_player/translations/id.json +++ b/homeassistant/components/media_player/translations/id.json @@ -1,11 +1,27 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} siaga", + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala", + "is_paused": "{entity_name} dijeda", + "is_playing": "{entity_name} sedang memutar" + }, + "trigger_type": { + "idle": "{entity_name} menjadi siaga", + "paused": "{entity_name} dijeda", + "playing": "{entity_name} mulai memutar", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "idle": "Diam", - "off": "Off", - "on": "On", + "idle": "Siaga", + "off": "Mati", + "on": "Nyala", "paused": "Jeda", - "playing": "Memainkan", + "playing": "Memutar", "standby": "Siaga" } }, diff --git a/homeassistant/components/media_player/translations/ko.json b/homeassistant/components/media_player/translations/ko.json index e727e744d7368e..213b61ef6b9f18 100644 --- a/homeassistant/components/media_player/translations/ko.json +++ b/homeassistant/components/media_player/translations/ko.json @@ -1,11 +1,18 @@ { "device_automation": { "condition_type": { - "is_idle": "{entity_name} \uc774(\uac00) \uc720\ud734 \uc0c1\ud0dc\uc774\uba74", - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", - "is_paused": "{entity_name} \uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub418\uc5b4 \uc788\uc73c\uba74", - "is_playing": "{entity_name} \uc774(\uac00) \uc7ac\uc0dd \uc911\uc774\uba74" + "is_idle": "{entity_name}\uc774(\uac00) \ub300\uae30 \uc0c1\ud0dc\uc774\uba74", + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74", + "is_paused": "{entity_name}\uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub418\uc5b4 \uc788\uc73c\uba74", + "is_playing": "{entity_name}\uc774(\uac00) \uc7ac\uc0dd \uc911\uc774\uba74" + }, + "trigger_type": { + "idle": "{entity_name}\uc774(\uac00) \ub300\uae30 \uc0c1\ud0dc\uac00 \ub420 \ub54c", + "paused": "{entity_name}\uc774(\uac00) \uc77c\uc2dc\uc911\uc9c0\ub420 \ub54c", + "playing": "{entity_name}\uc774(\uac00) \uc7ac\uc0dd\uc744 \uc2dc\uc791\ud560 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/media_player/translations/zh-Hans.json b/homeassistant/components/media_player/translations/zh-Hans.json index af8579075be2c3..0fa034898c39c0 100644 --- a/homeassistant/components/media_player/translations/zh-Hans.json +++ b/homeassistant/components/media_player/translations/zh-Hans.json @@ -6,6 +6,13 @@ "is_on": "{entity_name} \u5df2\u5f00\u542f", "is_paused": "{entity_name} \u5df2\u6682\u505c", "is_playing": "{entity_name} \u6b63\u5728\u64ad\u653e" + }, + "trigger_type": { + "idle": "{entity_name} \u7a7a\u95f2", + "paused": "{entity_name} \u6682\u505c", + "playing": "{entity_name} \u5f00\u59cb\u64ad\u653e", + "turned_off": "{entity_name} \u88ab\u5173\u95ed", + "turned_on": "{entity_name} \u88ab\u6253\u5f00" } }, "state": { diff --git a/homeassistant/components/melcloud/translations/he.json b/homeassistant/components/melcloud/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/melcloud/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/melcloud/translations/hu.json b/homeassistant/components/melcloud/translations/hu.json index 62699ecb4683b9..7f81269c70011c 100644 --- a/homeassistant/components/melcloud/translations/hu.json +++ b/homeassistant/components/melcloud/translations/hu.json @@ -1,5 +1,10 @@ { "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/melcloud/translations/id.json b/homeassistant/components/melcloud/translations/id.json new file mode 100644 index 00000000000000..d2847541537ff4 --- /dev/null +++ b/homeassistant/components/melcloud/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Integrasi MELCloud sudah dikonfigurasi untuk email ini. Token akses telah disegarkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "description": "Hubungkan menggunakan akun MELCloud Anda.", + "title": "Hubungkan ke MELCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/ko.json b/homeassistant/components/melcloud/translations/ko.json index 2e1f1b535e1f38..ce984e26906c6e 100644 --- a/homeassistant/components/melcloud/translations/ko.json +++ b/homeassistant/components/melcloud/translations/ko.json @@ -15,7 +15,7 @@ "username": "\uc774\uba54\uc77c" }, "description": "MELCloud \uacc4\uc815\uc73c\ub85c \uc5f0\uacb0\ud558\uc138\uc694.", - "title": "MELCloud \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "MELCloud\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/met/translations/he.json b/homeassistant/components/met/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/met/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/hu.json b/homeassistant/components/met/translations/hu.json index 4e3ccf87ab76a1..b9141541a93f51 100644 --- a/homeassistant/components/met/translations/hu.json +++ b/homeassistant/components/met/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/met/translations/id.json b/homeassistant/components/met/translations/id.json index 12854e4ed619ff..639ed5086ce23e 100644 --- a/homeassistant/components/met/translations/id.json +++ b/homeassistant/components/met/translations/id.json @@ -1,11 +1,17 @@ { "config": { + "error": { + "already_configured": "Layanan sudah dikonfigurasi" + }, "step": { "user": { "data": { "elevation": "Ketinggian", + "latitude": "Lintang", + "longitude": "Bujur", "name": "Nama" }, + "description": "Meteorologisk institutt", "title": "Lokasi" } } diff --git a/homeassistant/components/meteo_france/translations/hu.json b/homeassistant/components/meteo_france/translations/hu.json index dc74eafa409fde..112f70b6ea6dc4 100644 --- a/homeassistant/components/meteo_france/translations/hu.json +++ b/homeassistant/components/meteo_france/translations/hu.json @@ -1,13 +1,20 @@ { "config": { "abort": { - "already_configured": "A v\u00e1ros m\u00e1r konfigur\u00e1lva van", - "unknown": "Ismeretlen hiba: k\u00e9rj\u00fck, pr\u00f3b\u00e1lja \u00fajra k\u00e9s\u0151bb" + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "empty": "Nincs eredm\u00e9ny a v\u00e1roskeres\u00e9sben: ellen\u0151rizze a v\u00e1ros mez\u0151t" }, "step": { + "cities": { + "data": { + "city": "V\u00e1ros" + }, + "description": "V\u00e1laszd ki a v\u00e1rost a list\u00e1b\u00f3l", + "title": "M\u00e9t\u00e9o-France" + }, "user": { "data": { "city": "V\u00e1ros" @@ -16,5 +23,14 @@ "title": "M\u00e9t\u00e9o-France" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "El\u0151rejelz\u00e9si m\u00f3d" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/id.json b/homeassistant/components/meteo_france/translations/id.json new file mode 100644 index 00000000000000..07d8450e873b33 --- /dev/null +++ b/homeassistant/components/meteo_france/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "empty": "Tidak ada hasil dalam penelusuran kota: periksa bidang isian kota" + }, + "step": { + "cities": { + "data": { + "city": "Kota" + }, + "description": "Pilih kota Anda dari daftar", + "title": "M\u00e9t\u00e9o-France" + }, + "user": { + "data": { + "city": "Kota" + }, + "description": "Masukkan kode pos (hanya untuk Prancis, disarankan) atau nama kota", + "title": "M\u00e9t\u00e9o-France" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Mode prakiraan" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/ko.json b/homeassistant/components/meteo_france/translations/ko.json index ec48103bbff8bc..83cda0e4dcf394 100644 --- a/homeassistant/components/meteo_france/translations/ko.json +++ b/homeassistant/components/meteo_france/translations/ko.json @@ -1,14 +1,18 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "empty": "\ub3c4\uc2dc \uac80\uc0c9 \uacb0\uacfc \uc5c6\uc74c: \ub3c4\uc2dc \ud544\ub4dc\ub97c \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + "empty": "\ub3c4\uc2dc \uac80\uc0c9 \uacb0\uacfc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \ub3c4\uc2dc \ud544\ub4dc\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "step": { "cities": { + "data": { + "city": "\ub3c4\uc2dc" + }, + "description": "\ubaa9\ub85d\uc5d0\uc11c \ub3c4\uc2dc\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", "title": "\ud504\ub791\uc2a4 \uae30\uc0c1\uccad (M\u00e9t\u00e9o-France)" }, "user": { diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index 61925da4cd3df4..e828fa6e09f77a 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -6,6 +6,9 @@ }, "step": { "cities": { + "data": { + "city": "Stad" + }, "title": "M\u00e9t\u00e9o-France" }, "user": { @@ -16,5 +19,14 @@ "title": "M\u00e9t\u00e9o-France" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Voorspellingsmodus" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/he.json b/homeassistant/components/metoffice/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/metoffice/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/hu.json b/homeassistant/components/metoffice/translations/hu.json index 3b2d79a34a77e2..350e6f92f32910 100644 --- a/homeassistant/components/metoffice/translations/hu.json +++ b/homeassistant/components/metoffice/translations/hu.json @@ -1,7 +1,21 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + }, + "title": "Csatlakoz\u00e1s a UK Met Office-hoz" + } } } } \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/id.json b/homeassistant/components/metoffice/translations/id.json new file mode 100644 index 00000000000000..d9bb784a99fd4c --- /dev/null +++ b/homeassistant/components/metoffice/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur" + }, + "description": "Lintang dan bujur akan digunakan untuk menemukan stasiun cuaca terdekat.", + "title": "Hubungkan ke the UK Met Office" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/nl.json b/homeassistant/components/metoffice/translations/nl.json index 1b3063459c461b..a6ba36f07aff18 100644 --- a/homeassistant/components/metoffice/translations/nl.json +++ b/homeassistant/components/metoffice/translations/nl.json @@ -14,6 +14,7 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad" }, + "description": "De lengte- en breedtegraad worden gebruikt om het dichtstbijzijnde weerstation te vinden.", "title": "Maak verbinding met het UK Met Office" } } diff --git a/homeassistant/components/mikrotik/translations/he.json b/homeassistant/components/mikrotik/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/mikrotik/translations/hu.json b/homeassistant/components/mikrotik/translations/hu.json index 67a2e8d8fc3455..248884f9687ab8 100644 --- a/homeassistant/components/mikrotik/translations/hu.json +++ b/homeassistant/components/mikrotik/translations/hu.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "A Mikrotik m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "cannot_connect": "A kapcsolat sikertelen", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "name_exists": "A n\u00e9v m\u00e1r l\u00e9tezik" }, "step": { diff --git a/homeassistant/components/mikrotik/translations/id.json b/homeassistant/components/mikrotik/translations/id.json new file mode 100644 index 00000000000000..3ef0dacb763217 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "name_exists": "Nama sudah ada" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna", + "verify_ssl": "Gunakan SSL" + }, + "title": "Siapkan Router Mikrotik" + } + } + }, + "options": { + "step": { + "device_tracker": { + "data": { + "arp_ping": "Aktifkan ping ARP", + "detection_time": "Pertimbangkan interval rumah", + "force_dhcp": "Paksa pemindaian menggunakan DHCP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/hu.json b/homeassistant/components/mill/translations/hu.json index 387a73041d92f8..74a6f9abbac214 100644 --- a/homeassistant/components/mill/translations/hu.json +++ b/homeassistant/components/mill/translations/hu.json @@ -3,6 +3,9 @@ "abort": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/mill/translations/id.json b/homeassistant/components/mill/translations/id.json new file mode 100644 index 00000000000000..ab929d3d7c80c2 --- /dev/null +++ b/homeassistant/components/mill/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/hu.json b/homeassistant/components/minecraft_server/translations/hu.json index 7a8958bd7c6764..247c1ffc1c378d 100644 --- a/homeassistant/components/minecraft_server/translations/hu.json +++ b/homeassistant/components/minecraft_server/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Kiszolg\u00e1l\u00f3 m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "error": { "cannot_connect": "Nem siker\u00fclt csatlakozni a szerverhez. K\u00e9rj\u00fck, ellen\u0151rizze a gazdag\u00e9pet \u00e9s a portot, majd pr\u00f3b\u00e1lkozzon \u00fajra. Gondoskodjon arr\u00f3l, hogy a szerveren legal\u00e1bb a Minecraft 1.7-es verzi\u00f3j\u00e1t futtassa." diff --git a/homeassistant/components/minecraft_server/translations/id.json b/homeassistant/components/minecraft_server/translations/id.json new file mode 100644 index 00000000000000..fffb0865b241bc --- /dev/null +++ b/homeassistant/components/minecraft_server/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 Minecraft dengan versi minimal 1.7 di server Anda.", + "invalid_ip": "Alamat IP tidak valid (alamat MAC tidak dapat ditentukan). Perbaiki, lalu coba lagi.", + "invalid_port": "Port harus berada dalam rentang dari 1024 hingga 65535. Perbaiki, lalu coba lagi." + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Siapkan instans Minecraft Server Anda untuk pemantauan.", + "title": "Tautkan Server Minecraft Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/ca.json b/homeassistant/components/mobile_app/translations/ca.json index a36fd1ca13ab8b..70709d1be64611 100644 --- a/homeassistant/components/mobile_app/translations/ca.json +++ b/homeassistant/components/mobile_app/translations/ca.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Envia una notificaci\u00f3" } - } + }, + "title": "Aplicaci\u00f3 m\u00f2bil" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/en.json b/homeassistant/components/mobile_app/translations/en.json index 34631f86afa558..0b564a38174e31 100644 --- a/homeassistant/components/mobile_app/translations/en.json +++ b/homeassistant/components/mobile_app/translations/en.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Send a notification" } - } + }, + "title": "Mobile App" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/et.json b/homeassistant/components/mobile_app/translations/et.json index 27f5fce2bdf991..e5b4ea8009c826 100644 --- a/homeassistant/components/mobile_app/translations/et.json +++ b/homeassistant/components/mobile_app/translations/et.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Saada teavitus" } - } + }, + "title": "Mobiilirakendus" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/fr.json b/homeassistant/components/mobile_app/translations/fr.json index f4b0f590e48527..f888b8062f8298 100644 --- a/homeassistant/components/mobile_app/translations/fr.json +++ b/homeassistant/components/mobile_app/translations/fr.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Envoyer une notification" } - } + }, + "title": "Application mobile" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/hu.json b/homeassistant/components/mobile_app/translations/hu.json index 301075e0ad4c4f..90690e2545b9f1 100644 --- a/homeassistant/components/mobile_app/translations/hu.json +++ b/homeassistant/components/mobile_app/translations/hu.json @@ -13,5 +13,6 @@ "action_type": { "notify": "\u00c9rtes\u00edt\u00e9s k\u00fcld\u00e9se" } - } + }, + "title": "Mobil alkalmaz\u00e1s" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/id.json b/homeassistant/components/mobile_app/translations/id.json new file mode 100644 index 00000000000000..d346ca76eabc9d --- /dev/null +++ b/homeassistant/components/mobile_app/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "install_app": "Buka aplikasi seluler untuk menyiapkan integrasi dengan Home Assistant. Baca [dokumentasi]({apps_url}) tentang daftar aplikasi yang kompatibel." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan komponen Aplikasi Seluler?" + } + } + }, + "device_automation": { + "action_type": { + "notify": "Kirim notifikasi" + } + }, + "title": "Aplikasi Seluler" +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/it.json b/homeassistant/components/mobile_app/translations/it.json index f5ba52b1a53dde..3784a158930fa5 100644 --- a/homeassistant/components/mobile_app/translations/it.json +++ b/homeassistant/components/mobile_app/translations/it.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Invia una notifica" } - } + }, + "title": "App per dispositivi mobili" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/ko.json b/homeassistant/components/mobile_app/translations/ko.json index 03478e1cf2a264..7f99e1f2100c7e 100644 --- a/homeassistant/components/mobile_app/translations/ko.json +++ b/homeassistant/components/mobile_app/translations/ko.json @@ -1,12 +1,18 @@ { "config": { "abort": { - "install_app": "\ubaa8\ubc14\uc77c \uc571\uc744 \uc5f4\uc5b4 Home Assistant \uc640 \uc5f0\ub3d9\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694. \ud638\ud658\ub418\ub294 \uc571 \ubaa9\ub85d\uc740 [\uc548\ub0b4]({apps_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "install_app": "Mobile App\uc744 \uc5f4\uc5b4 Home Assistant\uc640 \uc5f0\ub3d9\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694. \ud638\ud658\ub418\ub294 \uc571 \ubaa9\ub85d\uc740 [\uad00\ub828 \ubb38\uc11c]({apps_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "confirm": { - "description": "\ubaa8\ubc14\uc77c \uc571 \ucef4\ud3ec\ub10c\ud2b8\uc758 \uc124\uc815\uc744 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Mobile App \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } - } + }, + "device_automation": { + "action_type": { + "notify": "\uc54c\ub9bc \ubcf4\ub0b4\uae30" + } + }, + "title": "Mobile App" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/nl.json b/homeassistant/components/mobile_app/translations/nl.json index 17a20705cd4cf2..e7bfb6150a5019 100644 --- a/homeassistant/components/mobile_app/translations/nl.json +++ b/homeassistant/components/mobile_app/translations/nl.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Stuur een notificatie" } - } + }, + "title": "Mobiele app" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/no.json b/homeassistant/components/mobile_app/translations/no.json index 65d465723b1edf..0f6f6dbb0fd8b8 100644 --- a/homeassistant/components/mobile_app/translations/no.json +++ b/homeassistant/components/mobile_app/translations/no.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Send et varsel" } - } + }, + "title": "Mobilapp" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/pl.json b/homeassistant/components/mobile_app/translations/pl.json index cd083447634632..00a4631684c084 100644 --- a/homeassistant/components/mobile_app/translations/pl.json +++ b/homeassistant/components/mobile_app/translations/pl.json @@ -13,5 +13,6 @@ "action_type": { "notify": "wy\u015blij powiadomienie" } - } + }, + "title": "Aplikacja mobilna" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/ru.json b/homeassistant/components/mobile_app/translations/ru.json index fc4496ba1d86c3..65b6cc15c6588d 100644 --- a/homeassistant/components/mobile_app/translations/ru.json +++ b/homeassistant/components/mobile_app/translations/ru.json @@ -13,5 +13,6 @@ "action_type": { "notify": "\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435" } - } + }, + "title": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/zh-Hans.json b/homeassistant/components/mobile_app/translations/zh-Hans.json index b48ca1e4263bb4..6a884b156bc90e 100644 --- a/homeassistant/components/mobile_app/translations/zh-Hans.json +++ b/homeassistant/components/mobile_app/translations/zh-Hans.json @@ -8,5 +8,11 @@ "description": "\u60a8\u60f3\u8981\u914d\u7f6e\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u7ec4\u4ef6\u5417\uff1f" } } - } + }, + "device_automation": { + "action_type": { + "notify": "\u63a8\u9001\u901a\u77e5" + } + }, + "title": "\u79fb\u52a8\u5e94\u7528" } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/zh-Hant.json b/homeassistant/components/mobile_app/translations/zh-Hant.json index d54afd94f54bab..2793794b8b6e77 100644 --- a/homeassistant/components/mobile_app/translations/zh-Hant.json +++ b/homeassistant/components/mobile_app/translations/zh-Hant.json @@ -13,5 +13,6 @@ "action_type": { "notify": "\u50b3\u9001\u901a\u77e5" } - } + }, + "title": "\u624b\u6a5f App" } \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/hu.json b/homeassistant/components/monoprice/translations/hu.json index 892b8b2cd91240..a845f8621608ca 100644 --- a/homeassistant/components/monoprice/translations/hu.json +++ b/homeassistant/components/monoprice/translations/hu.json @@ -1,5 +1,12 @@ { "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": { diff --git a/homeassistant/components/monoprice/translations/id.json b/homeassistant/components/monoprice/translations/id.json new file mode 100644 index 00000000000000..bf4269c492ea64 --- /dev/null +++ b/homeassistant/components/monoprice/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "port": "Port", + "source_1": "Nama sumber #1", + "source_2": "Nama sumber #2", + "source_3": "Nama sumber #3", + "source_4": "Nama sumber #4", + "source_5": "Nama sumber #5", + "source_6": "Nama sumber #6" + }, + "title": "Hubungkan ke perangkat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nama sumber #1", + "source_2": "Nama sumber #2", + "source_3": "Nama sumber #3", + "source_4": "Nama sumber #4", + "source_5": "Nama sumber #5", + "source_6": "Nama sumber #6" + }, + "title": "Konfigurasikan sumber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/nl.json b/homeassistant/components/monoprice/translations/nl.json index 74bc677dbe8728..28ecedab3d39d1 100644 --- a/homeassistant/components/monoprice/translations/nl.json +++ b/homeassistant/components/monoprice/translations/nl.json @@ -4,13 +4,13 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "port": "Seri\u00eble poort", + "port": "Poort", "source_1": "Naam van bron #1", "source_2": "Naam van bron #2", "source_3": "Naam van bron #3", diff --git a/homeassistant/components/moon/translations/sensor.id.json b/homeassistant/components/moon/translations/sensor.id.json new file mode 100644 index 00000000000000..197bc609813242 --- /dev/null +++ b/homeassistant/components/moon/translations/sensor.id.json @@ -0,0 +1,14 @@ +{ + "state": { + "moon__phase": { + "first_quarter": "Seperempat pertama", + "full_moon": "Bulan purnama", + "last_quarter": "Seperempat ketiga", + "new_moon": "Bulan baru", + "waning_crescent": "Bulan sabit tua", + "waning_gibbous": "Bulan cembung tua", + "waxing_crescent": "Bulan sabit muda", + "waxing_gibbous": "Bulan cembung muda" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/bg.json b/homeassistant/components/motion_blinds/translations/bg.json new file mode 100644 index 00000000000000..39f706036fd2a5 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "select": { + "data": { + "select_ip": "IP \u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/hu.json b/homeassistant/components/motion_blinds/translations/hu.json new file mode 100644 index 00000000000000..541cefd2110734 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/hu.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "connection_error": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "connect": { + "data": { + "api_key": "API kulcs" + } + }, + "select": { + "data": { + "select_ip": "IP c\u00edm" + } + }, + "user": { + "data": { + "api_key": "API kulcs", + "host": "IP c\u00edm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/id.json b/homeassistant/components/motion_blinds/translations/id.json new file mode 100644 index 00000000000000..9248531a751c36 --- /dev/null +++ b/homeassistant/components/motion_blinds/translations/id.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "connection_error": "Gagal terhubung" + }, + "error": { + "discovery_error": "Gagal menemukan Motion Gateway" + }, + "flow_title": "Motion Blinds", + "step": { + "connect": { + "data": { + "api_key": "Kunci API" + }, + "description": "Anda akan memerlukan Kunci API 16 karakter, baca petunjuknya di https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "Motion Blinds" + }, + "select": { + "data": { + "select_ip": "Alamat IP" + }, + "description": "Jalankan penyiapan lagi jika ingin menghubungkan Motion Gateway lainnya", + "title": "Pilih Motion Gateway yang ingin dihubungkan" + }, + "user": { + "data": { + "api_key": "Kunci API", + "host": "Alamat IP" + }, + "description": "Hubungkan ke Motion Gateway Anda, jika alamat IP tidak disetel, penemuan otomatis akan digunakan", + "title": "Motion Blinds" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/motion_blinds/translations/ko.json b/homeassistant/components/motion_blinds/translations/ko.json index 9d2f0eead3d390..69ed2cd7b35ee2 100644 --- a/homeassistant/components/motion_blinds/translations/ko.json +++ b/homeassistant/components/motion_blinds/translations/ko.json @@ -5,22 +5,32 @@ "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "error": { + "discovery_error": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "Motion Blinds", "step": { "connect": { "data": { "api_key": "API \ud0a4" - } + }, + "description": "16\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API Key\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "Motion Blinds" }, "select": { "data": { "select_ip": "IP \uc8fc\uc18c" - } + }, + "description": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ucd94\uac00 \uc5f0\uacb0\ud558\ub824\uba74 \uc124\uc815\uc744 \ub2e4\uc2dc \uc2e4\ud589\ud574\uc8fc\uc138\uc694", + "title": "\uc5f0\uacb0\ud560 Motion \uac8c\uc774\ud2b8\uc6e8\uc774 \uc120\ud0dd\ud558\uae30" }, "user": { "data": { "api_key": "API \ud0a4", "host": "IP \uc8fc\uc18c" - } + }, + "description": "Motion \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", + "title": "Motion Blinds" } } } diff --git a/homeassistant/components/mqtt/translations/bg.json b/homeassistant/components/mqtt/translations/bg.json index 7a470b7427264e..96343a7f87abf7 100644 --- a/homeassistant/components/mqtt/translations/bg.json +++ b/homeassistant/components/mqtt/translations/bg.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e\u0442\u043e \u043e\u0442\u043a\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\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 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 MQTT \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 {addon}?", - "title": "MQTT \u0431\u0440\u043e\u043a\u0435\u0440 \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\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 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0436\u0435 \u0441 MQTT \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 {addon}?", + "title": "MQTT \u0431\u0440\u043e\u043a\u0435\u0440 \u0447\u0440\u0435\u0437 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430" } } } diff --git a/homeassistant/components/mqtt/translations/ca.json b/homeassistant/components/mqtt/translations/ca.json index f72ee30cdcfc3a..23b7cd5dfa9f1b 100644 --- a/homeassistant/components/mqtt/translations/ca.json +++ b/homeassistant/components/mqtt/translations/ca.json @@ -21,8 +21,8 @@ "data": { "discovery": "Habilitar descobriment autom\u00e0tic" }, - "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb el broker MQTT proporcionat pel complement de Hass.io: {addon}?", - "title": "Broker MQTT a trav\u00e9s del complement de Hass.io" + "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb el broker MQTT proporcionat pel complement de Hass.io {addon}?", + "title": "Broker MQTT via complement de Hass.io" } } }, diff --git a/homeassistant/components/mqtt/translations/cs.json b/homeassistant/components/mqtt/translations/cs.json index 60c323d9051a91..9876e2509b3773 100644 --- a/homeassistant/components/mqtt/translations/cs.json +++ b/homeassistant/components/mqtt/translations/cs.json @@ -21,8 +21,8 @@ "data": { "discovery": "Povolit automatick\u00e9 vyhled\u00e1v\u00e1n\u00ed za\u0159\u00edzen\u00ed" }, - "description": "Chcete nakonfigurovat Home Assistant pro p\u0159ipojen\u00ed k MQTT poskytovan\u00e9mu dopl\u0148kem {addon} z Hass.io?", - "title": "MQTT Broker prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" + "description": "Chcete nakonfigurovat Home Assistant pro p\u0159ipojen\u00ed k MQTT poskytovan\u00e9mu dopl\u0148kem {addon} z Supervisor?", + "title": "MQTT Broker prost\u0159ednictv\u00edm dopl\u0148ku Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/da.json b/homeassistant/components/mqtt/translations/da.json index 7ff0f2b0a7020a..9b853a2dae2ea3 100644 --- a/homeassistant/components/mqtt/translations/da.json +++ b/homeassistant/components/mqtt/translations/da.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktiv\u00e9r opdagelse" }, - "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT-brokeren, der leveres af hass.io-tilf\u00f8jelsen {addon}?", - "title": "MQTT-broker via Hass.io-tilf\u00f8jelse" + "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til MQTT-brokeren, der leveres af Supervisor-tilf\u00f8jelsen {addon}?", + "title": "MQTT-broker via Supervisor-tilf\u00f8jelse" } } }, diff --git a/homeassistant/components/mqtt/translations/de.json b/homeassistant/components/mqtt/translations/de.json index 3346abfd53e22a..4b57249eb382e3 100644 --- a/homeassistant/components/mqtt/translations/de.json +++ b/homeassistant/components/mqtt/translations/de.json @@ -21,8 +21,8 @@ "data": { "discovery": "Suche aktivieren" }, - "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem MQTT-Broker herstellt, der vom Hass.io Add-on {addon} bereitgestellt wird?", - "title": "MQTT Broker per Hass.io add-on" + "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem MQTT-Broker herstellt, der vom Supervisor Add-on {addon} bereitgestellt wird?", + "title": "MQTT Broker per Supervisor add-on" } } }, diff --git a/homeassistant/components/mqtt/translations/es-419.json b/homeassistant/components/mqtt/translations/es-419.json index d2ddc6691d158e..a69be795f77089 100644 --- a/homeassistant/components/mqtt/translations/es-419.json +++ b/homeassistant/components/mqtt/translations/es-419.json @@ -21,8 +21,8 @@ "data": { "discovery": "Habilitar descubrimiento" }, - "description": "\u00bfDesea configurar el Asistente del Hogar para que se conecte al broker MQTT proporcionado por el complemento hass.io {addon}?", - "title": "MQTT Broker a trav\u00e9s del complemento Hass.io" + "description": "\u00bfDesea configurar el Asistente del Hogar para que se conecte al broker MQTT proporcionado por el complemento Supervisor {addon}?", + "title": "MQTT Broker a trav\u00e9s del complemento Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/es.json b/homeassistant/components/mqtt/translations/es.json index d36cfbc9694b24..70107efa269121 100644 --- a/homeassistant/components/mqtt/translations/es.json +++ b/homeassistant/components/mqtt/translations/es.json @@ -21,8 +21,8 @@ "data": { "discovery": "Habilitar descubrimiento" }, - "description": "\u00bfQuieres configurar Home Assistant para conectar con el broker de MQTT proporcionado por el complemento Hass.io {addon}?", - "title": "MQTT Broker a trav\u00e9s del complemento Hass.io" + "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" } } }, diff --git a/homeassistant/components/mqtt/translations/et.json b/homeassistant/components/mqtt/translations/et.json index d2b863cab46292..f28b1f4f94ed52 100644 --- a/homeassistant/components/mqtt/translations/et.json +++ b/homeassistant/components/mqtt/translations/et.json @@ -21,8 +21,8 @@ "data": { "discovery": "Luba automaatne avastamine" }, - "description": "Kas soovite seadistada Home Assistanti \u00fchenduse loomiseks Hass.io lisandmooduli {addon} pakutava MQTT vahendajaga?", - "title": "MQTT vahendaja Hass.io pistikprogrammi kaudu" + "description": "Kas soovid seadistada Home Assistanti \u00fchenduse loomiseks Hass.io lisandmooduli {addon} pakutava MQTT vahendajaga?", + "title": "MQTT vahendaja Hass.io lisandmooduli abil" } } }, diff --git a/homeassistant/components/mqtt/translations/fr.json b/homeassistant/components/mqtt/translations/fr.json index 574db2d2faf57f..6ee3788725d863 100644 --- a/homeassistant/components/mqtt/translations/fr.json +++ b/homeassistant/components/mqtt/translations/fr.json @@ -21,8 +21,8 @@ "data": { "discovery": "Activer la d\u00e9couverte" }, - "description": "Vous voulez configurer Home Assistant pour vous connecter au broker MQTT fourni par l\u2019Add-on hass.io {addon} ?", - "title": "MQTT Broker via le module compl\u00e9mentaire Hass.io" + "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte au courtier MQTT fourni par le module compl\u00e9mentaire Hass.io {addon} ?", + "title": "Courtier MQTT via le module compl\u00e9mentaire Hass.io" } } }, diff --git a/homeassistant/components/mqtt/translations/hu.json b/homeassistant/components/mqtt/translations/hu.json index 8cc6aa0857fcb2..f265789d777dda 100644 --- a/homeassistant/components/mqtt/translations/hu.json +++ b/homeassistant/components/mqtt/translations/hu.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen MQTT konfigur\u00e1ci\u00f3 megengedett." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni a br\u00f3kerhez." + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "broker": { @@ -21,8 +21,8 @@ "data": { "discovery": "Felfedez\u00e9s enged\u00e9lyez\u00e9se" }, - "description": "Be szeretn\u00e9d konfigru\u00e1lni, hogy a Home Assistant a(z) {addon} Hass.io add-on \u00e1ltal biztos\u00edtott MQTT br\u00f3kerhez csatlakozzon?", - "title": "MQTT Br\u00f3ker a Hass.io b\u0151v\u00edtm\u00e9nnyel" + "description": "Be szeretn\u00e9d konfigru\u00e1lni, hogy a Home Assistant a(z) {addon} Supervisor add-on \u00e1ltal biztos\u00edtott MQTT br\u00f3kerhez csatlakozzon?", + "title": "MQTT Br\u00f3ker a Supervisor b\u0151v\u00edtm\u00e9nnyel" } } }, @@ -47,5 +47,20 @@ "button_short_release": "\"{subtype}\" felengedve", "button_triple_press": "\"{subtype}\" tripla kattint\u00e1s" } + }, + "options": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "broker": { + "data": { + "broker": "Br\u00f3ker", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/id.json b/homeassistant/components/mqtt/translations/id.json index e21052a501fe07..2a3171456c86bc 100644 --- a/homeassistant/components/mqtt/translations/id.json +++ b/homeassistant/components/mqtt/translations/id.json @@ -1,20 +1,72 @@ { "config": { "abort": { - "single_instance_allowed": "Hanya satu konfigurasi MQTT yang diizinkan." + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "error": { - "cannot_connect": "Tidak dapat terhubung ke broker." + "cannot_connect": "Gagal terhubung" }, "step": { "broker": { "data": { "broker": "Broker", - "password": "Kata sandi", + "discovery": "Aktifkan penemuan", + "password": "Kata Sandi", "port": "Port", - "username": "Nama pengguna" + "username": "Nama Pengguna" }, - "description": "Harap masukkan informasi koneksi dari broker MQTT Anda." + "description": "Masukkan informasi koneksi broker MQTT Anda." + }, + "hassio_confirm": { + "data": { + "discovery": "Aktifkan penemuan" + }, + "description": "Ingin mengonfigurasi Home Assistant untuk terhubung ke broker MQTT yang disediakan oleh add-on Supervisor {addon}?", + "title": "MQTT Broker via add-on Supervisor" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "button_5": "Tombol kelima", + "button_6": "Tombol keenam", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "button_double_press": "\"{subtype}\" diklik dua kali", + "button_long_press": "\"{subtype}\" terus ditekan", + "button_long_release": "\"{subtype}\" dilepaskan setelah ditekan lama", + "button_quadruple_press": "\"{subtype}\" diklik empat kali", + "button_quintuple_press": "\"{subtype}\" diklik lima kali", + "button_short_press": "\"{subtype}\" ditekan", + "button_short_release": "\"{subtype}\" dilepas", + "button_triple_press": "\"{subtype}\" diklik tiga kali" + } + }, + "options": { + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "broker": { + "data": { + "broker": "Broker", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "description": "Masukkan informasi koneksi broker MQTT Anda." + }, + "options": { + "data": { + "discovery": "Aktifkan penemuan" + }, + "description": "Pilih opsi MQTT." } } } diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index fd79863fadf288..f10c9bbf7e9b7e 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -21,8 +21,8 @@ "data": { "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654" }, - "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \uc560\ub4dc\uc628\uc758 MQTT \ube0c\ub85c\ucee4" + "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 MQTT \ube0c\ub85c\ucee4" } } }, @@ -38,14 +38,14 @@ "turn_on": "\ucf1c\uae30" }, "trigger_type": { - "button_double_press": "\"{subtype}\" \uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", - "button_long_press": "\"{subtype}\" \uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", - "button_long_release": "\"{subtype}\" \uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "button_quadruple_press": "\"{subtype}\" \uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", - "button_quintuple_press": "\"{subtype}\" \uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", - "button_short_press": "\"{subtype}\" \uc774 \ub20c\ub9b4 \ub54c", - "button_short_release": "\"{subtype}\" \uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "button_triple_press": "\"{subtype}\" \uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c" + "button_double_press": "\"{subtype}\"\uc774(\uac00) \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "button_long_press": "\"{subtype}\"\uc774(\uac00) \uacc4\uc18d \ub20c\ub838\uc744 \ub54c", + "button_long_release": "\"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "button_quadruple_press": "\"{subtype}\"\uc774(\uac00) \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c", + "button_quintuple_press": "\"{subtype}\"\uc774(\uac00) \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c", + "button_short_press": "\"{subtype}\"\uc774(\uac00) \ub20c\ub838\uc744 \ub54c", + "button_short_release": "\"{subtype}\"\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "button_triple_press": "\"{subtype}\"\uc774(\uac00) \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c" } }, "options": { @@ -66,12 +66,13 @@ }, "options": { "data": { + "birth_enable": "Birth \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654\ud558\uae30", "birth_payload": "Birth \uba54\uc2dc\uc9c0 \ud398\uc774\ub85c\ub4dc", "birth_qos": "Birth \uba54\uc2dc\uc9c0 QoS", "birth_retain": "Birth \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", "birth_topic": "Birth \uba54\uc2dc\uc9c0 \ud1a0\ud53d", - "discovery": "\uc7a5\uce58 \uac80\uc0c9 \ud65c\uc131\ud654", - "will_enable": "Will \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654", + "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654\ud558\uae30", + "will_enable": "Will \uba54\uc2dc\uc9c0 \ud65c\uc131\ud654\ud558\uae30", "will_payload": "Will \uba54\uc2dc\uc9c0 \ud398\uc774\ub85c\ub4dc", "will_qos": "Will \uba54\uc2dc\uc9c0 QoS", "will_retain": "Will \uba54\uc2dc\uc9c0 \ub9ac\ud14c\uc778", diff --git a/homeassistant/components/mqtt/translations/lb.json b/homeassistant/components/mqtt/translations/lb.json index 88a2664cd37a3f..fd9cd351858067 100644 --- a/homeassistant/components/mqtt/translations/lb.json +++ b/homeassistant/components/mqtt/translations/lb.json @@ -21,8 +21,8 @@ "data": { "discovery": "Entdeckung aktiv\u00e9ieren" }, - "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam MQTT broker ze verbannen dee vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", - "title": "MQTT Broker via Hass.io add-on" + "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam MQTT broker ze verbannen dee vum Supervisor add-on {addon} bereet gestallt g\u00ebtt?", + "title": "MQTT Broker via Supervisor add-on" } } }, diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 3b3ebf9fe3b9a5..8395b1db7e96b8 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van MQTT is toegestaan." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { - "cannot_connect": "Kan geen verbinding maken met de broker." + "cannot_connect": "Kan geen verbinding maken" }, "step": { "broker": { @@ -21,8 +21,8 @@ "data": { "discovery": "Detectie inschakelen" }, - "description": "Wilt u Home Assistant configureren om verbinding te maken met de MQTT-broker die wordt aangeboden door de hass.io add-on {addon} ?", - "title": "MQTTT Broker via Hass.io add-on" + "description": "Wilt u Home Assistant configureren om verbinding te maken met de MQTT-broker die wordt aangeboden door de Supervisor add-on {addon} ?", + "title": "MQTT Broker via Supervisor add-on" } } }, @@ -66,7 +66,8 @@ "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", "birth_topic": "Birth message onderwerp" - } + }, + "description": "Selecteer MQTT-opties." } } } diff --git a/homeassistant/components/mqtt/translations/no.json b/homeassistant/components/mqtt/translations/no.json index 12c72603a1fa32..586c62dac6a36c 100644 --- a/homeassistant/components/mqtt/translations/no.json +++ b/homeassistant/components/mqtt/translations/no.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktiver oppdagelse" }, - "description": "Vil du konfigurere Home Assistant til \u00e5 koble til en MQTT megler som er levert av Hass.io-tillegg {addon}?", - "title": "MQTT megler via Hass.io-tillegg" + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til MQTT-megleren levert av Hass.io-tillegget {addon} ?", + "title": "MQTT Megler via Hass.io-tillegg" } } }, diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index 08b1d2f1974d56..287f0165d96554 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -22,7 +22,7 @@ "discovery": "W\u0142\u0105cz wykrywanie" }, "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z po\u015brednikiem MQTT dostarczonym przez dodatek Hass.io {addon}?", - "title": "Po\u015brednik MQTT za po\u015brednictwem dodatku Hass.io" + "title": "Po\u015brednik MQTT przez dodatek Hass.io" } } }, diff --git a/homeassistant/components/mqtt/translations/pt-BR.json b/homeassistant/components/mqtt/translations/pt-BR.json index de739963cae894..ef9fad14440547 100644 --- a/homeassistant/components/mqtt/translations/pt-BR.json +++ b/homeassistant/components/mqtt/translations/pt-BR.json @@ -21,8 +21,8 @@ "data": { "discovery": "Ativar descoberta" }, - "description": "Deseja configurar o Home Assistant para se conectar ao broker MQTT fornecido pelo complemento hass.io {addon}?", - "title": "MQTT Broker via add-on Hass.io" + "description": "Deseja configurar o Home Assistant para se conectar ao broker MQTT fornecido pelo complemento Supervisor {addon}?", + "title": "MQTT Broker via add-on Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/pt.json b/homeassistant/components/mqtt/translations/pt.json index 606997038b20e2..209c33cf1657bd 100644 --- a/homeassistant/components/mqtt/translations/pt.json +++ b/homeassistant/components/mqtt/translations/pt.json @@ -21,8 +21,8 @@ "data": { "discovery": "Ativar descoberta" }, - "description": "Deseja configurar o Home Assistant para se ligar ao broker MQTT fornecido pelo add-on hass.io {addon}?", - "title": "MQTT Broker atrav\u00e9s do add-on Hass.io" + "description": "Deseja configurar o Home Assistant para se ligar ao broker MQTT fornecido pelo add-on Supervisor {addon}?", + "title": "MQTT Broker atrav\u00e9s do add-on Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/ro.json b/homeassistant/components/mqtt/translations/ro.json index 2292b58d01da67..a98818be937a8f 100644 --- a/homeassistant/components/mqtt/translations/ro.json +++ b/homeassistant/components/mqtt/translations/ro.json @@ -22,7 +22,7 @@ "discovery": "Activa\u021bi descoperirea" }, "description": "Dori\u021bi s\u0103 configura\u021bi Home Assistant pentru a se conecta la brokerul MQTT furnizat de addon-ul {addon} ?", - "title": "MQTT Broker, prin intermediul Hass.io add-on" + "title": "MQTT Broker, prin intermediul Supervisor add-on" } } } diff --git a/homeassistant/components/mqtt/translations/sl.json b/homeassistant/components/mqtt/translations/sl.json index afd2f3b80007cd..9f16209d524bb3 100644 --- a/homeassistant/components/mqtt/translations/sl.json +++ b/homeassistant/components/mqtt/translations/sl.json @@ -21,8 +21,8 @@ "data": { "discovery": "Omogo\u010di odkrivanje" }, - "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s posrednikom MQTT, ki ga ponuja dodatek Hass.io {addon} ?", - "title": "MQTT Broker prek dodatka Hass.io" + "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s posrednikom MQTT, ki ga ponuja dodatek Supervisor {addon} ?", + "title": "MQTT Broker prek dodatka Supervisor" } } }, diff --git a/homeassistant/components/mqtt/translations/sv.json b/homeassistant/components/mqtt/translations/sv.json index c74979bb6bba2f..b3088ca49a9799 100644 --- a/homeassistant/components/mqtt/translations/sv.json +++ b/homeassistant/components/mqtt/translations/sv.json @@ -21,8 +21,8 @@ "data": { "discovery": "Aktivera uppt\u00e4ckt" }, - "description": "Vill du konfigurera Home Assistant att ansluta till den MQTT-broker som tillhandah\u00e5lls av Hass.io-till\u00e4gget \"{addon}\"?", - "title": "MQTT Broker via Hass.io till\u00e4gg" + "description": "Vill du konfigurera Home Assistant att ansluta till den MQTT-broker som tillhandah\u00e5lls av Supervisor-till\u00e4gget \"{addon}\"?", + "title": "MQTT Broker via Supervisor till\u00e4gg" } } }, diff --git a/homeassistant/components/mqtt/translations/uk.json b/homeassistant/components/mqtt/translations/uk.json index f871db4aa9d965..b8cbab32b14fd4 100644 --- a/homeassistant/components/mqtt/translations/uk.json +++ b/homeassistant/components/mqtt/translations/uk.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u0414\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0438 \u0410\u0432\u0442\u043e\u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043d\u044f \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432" }, - "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io \"{addon}\")?", - "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Hass.io)" + "description": "\u0412\u0438 \u0432\u043f\u0435\u0432\u043d\u0435\u043d\u0456, \u0449\u043e \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0440\u043e\u043a\u0435\u0440\u0430 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor \"{addon}\")?", + "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT (\u0434\u043e\u0434\u0430\u0442\u043e\u043a \u0434\u043b\u044f Supervisor)" } } }, diff --git a/homeassistant/components/mqtt/translations/zh-Hans.json b/homeassistant/components/mqtt/translations/zh-Hans.json index 63ceded5654159..97356ed44d4202 100644 --- a/homeassistant/components/mqtt/translations/zh-Hans.json +++ b/homeassistant/components/mqtt/translations/zh-Hans.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u542f\u7528\u53d1\u73b0" }, - "description": "\u662f\u5426\u8981\u914d\u7f6e Home Assistant \u8fde\u63a5\u5230 Hass.io \u52a0\u8f7d\u9879 {addon} \u63d0\u4f9b\u7684 MQTT \u670d\u52a1\u5668\uff1f", - "title": "\u6765\u81ea Hass.io \u52a0\u8f7d\u9879\u7684 MQTT \u670d\u52a1\u5668" + "description": "\u662f\u5426\u8981\u914d\u7f6e Home Assistant \u8fde\u63a5\u5230 Supervisor \u52a0\u8f7d\u9879 {addon} \u63d0\u4f9b\u7684 MQTT \u670d\u52a1\u5668\uff1f", + "title": "\u6765\u81ea Supervisor \u52a0\u8f7d\u9879\u7684 MQTT \u670d\u52a1\u5668" } } }, diff --git a/homeassistant/components/mqtt/translations/zh-Hant.json b/homeassistant/components/mqtt/translations/zh-Hant.json index bfb27361889f38..807de2e2c09138 100644 --- a/homeassistant/components/mqtt/translations/zh-Hant.json +++ b/homeassistant/components/mqtt/translations/zh-Hant.json @@ -21,8 +21,8 @@ "data": { "discovery": "\u958b\u555f\u641c\u5c0b" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u6574\u5408 {addon} \u4e4b MQTT broker\uff1f", - "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 MQTT Broker" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u5143\u4ef6 {addon} \u4e4b MQTT broker\uff1f", + "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u5143\u4ef6 MQTT Broker" } } }, diff --git a/homeassistant/components/mullvad/translations/bg.json b/homeassistant/components/mullvad/translations/bg.json new file mode 100644 index 00000000000000..a84e1c3bfdf358 --- /dev/null +++ b/homeassistant/components/mullvad/translations/bg.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "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/mullvad/translations/es.json b/homeassistant/components/mullvad/translations/es.json index d6a17561c3d354..579726b061e520 100644 --- a/homeassistant/components/mullvad/translations/es.json +++ b/homeassistant/components/mullvad/translations/es.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Fallo al conectar", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, "step": { "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + }, "description": "\u00bfConfigurar la integraci\u00f3n VPN de Mullvad?" } } diff --git a/homeassistant/components/mullvad/translations/hu.json b/homeassistant/components/mullvad/translations/hu.json new file mode 100644 index 00000000000000..0abcc301f0c854 --- /dev/null +++ b/homeassistant/components/mullvad/translations/hu.json @@ -0,0 +1,21 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/id.json b/homeassistant/components/mullvad/translations/id.json new file mode 100644 index 00000000000000..a5409549f192d5 --- /dev/null +++ b/homeassistant/components/mullvad/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Siapkan integrasi VPN Mullvad?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/ko.json b/homeassistant/components/mullvad/translations/ko.json new file mode 100644 index 00000000000000..fd9134b977c4f2 --- /dev/null +++ b/homeassistant/components/mullvad/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "Mullvad VPN \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/pl.json b/homeassistant/components/mullvad/translations/pl.json new file mode 100644 index 00000000000000..f5aca4e092c9a3 --- /dev/null +++ b/homeassistant/components/mullvad/translations/pl.json @@ -0,0 +1,22 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Skonfigurowa\u0107 integracj\u0119 Mullvad VPN?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/pt.json b/homeassistant/components/mullvad/translations/pt.json new file mode 100644 index 00000000000000..561c8d77287efa --- /dev/null +++ b/homeassistant/components/mullvad/translations/pt.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "host": "Servidor", + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mullvad/translations/zh-Hans.json b/homeassistant/components/mullvad/translations/zh-Hans.json new file mode 100644 index 00000000000000..acb02a7d0f6303 --- /dev/null +++ b/homeassistant/components/mullvad/translations/zh-Hans.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u5b8c\u6210\u914d\u7f6e" + }, + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "invalid_auth": "\u9a8c\u8bc1\u5931\u8d25", + "unknown": "\u9884\u671f\u5916\u7684\u9519\u8bef" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u7801", + "username": "\u7528\u6237\u540d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/he.json b/homeassistant/components/myq/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/myq/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/myq/translations/hu.json b/homeassistant/components/myq/translations/hu.json index dee4ed9ee0fa4d..9c5b90e744753a 100644 --- a/homeassistant/components/myq/translations/hu.json +++ b/homeassistant/components/myq/translations/hu.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/myq/translations/id.json b/homeassistant/components/myq/translations/id.json new file mode 100644 index 00000000000000..2cc790d15e0da8 --- /dev/null +++ b/homeassistant/components/myq/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke MyQ Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/bg.json b/homeassistant/components/mysensors/translations/bg.json new file mode 100644 index 00000000000000..854e88b38b9f3c --- /dev/null +++ b/homeassistant/components/mysensors/translations/bg.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "gw_serial": { + "data": { + "device": "\u0421\u0435\u0440\u0438\u0435\u043d \u043f\u043e\u0440\u0442" + } + }, + "gw_tcp": { + "data": { + "tcp_port": "\u043f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/hu.json b/homeassistant/components/mysensors/translations/hu.json new file mode 100644 index 00000000000000..5faaf7aea56794 --- /dev/null +++ b/homeassistant/components/mysensors/translations/hu.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_device": "\u00c9rv\u00e9nytelen eszk\u00f6z", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "gw_serial": { + "data": { + "version": "MySensors verzi\u00f3" + } + }, + "gw_tcp": { + "data": { + "tcp_port": "port", + "version": "MySensors verzi\u00f3" + } + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/id.json b/homeassistant/components/mysensors/translations/id.json new file mode 100644 index 00000000000000..e982250b09c03e --- /dev/null +++ b/homeassistant/components/mysensors/translations/id.json @@ -0,0 +1,75 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "duplicate_persistence_file": "File persistensi sudah digunakan", + "duplicate_topic": "Topik sudah digunakan", + "invalid_auth": "Autentikasi tidak valid", + "invalid_device": "Perangkat tidak valid", + "invalid_ip": "Alamat IP tidak valid", + "invalid_persistence_file": "File persistensi tidak valid", + "invalid_port": "Nomor port tidak valid", + "invalid_serial": "Port serial tidak valid", + "invalid_subscribe_topic": "Topik langganan tidak valid", + "invalid_version": "Versi MySensors tidak valid", + "not_a_number": "Masukkan angka", + "port_out_of_range": "Nilai port minimal 1 dan maksimal 65535", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "duplicate_persistence_file": "File persistensi sudah digunakan", + "duplicate_topic": "Topik sudah digunakan", + "invalid_auth": "Autentikasi tidak valid", + "invalid_device": "Perangkat tidak valid", + "invalid_ip": "Alamat IP tidak valid", + "invalid_persistence_file": "File persistensi tidak valid", + "invalid_port": "Nomor port tidak valid", + "invalid_serial": "Port serial tidak valid", + "invalid_subscribe_topic": "Topik langganan tidak valid", + "invalid_version": "Versi MySensors tidak valid", + "not_a_number": "Masukkan angka", + "port_out_of_range": "Nilai port minimal 1 dan maksimal 65535", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "file persistensi (kosongkan untuk dihasilkan otomatis)", + "retain": "mqtt retain", + "topic_in_prefix": "prefiks untuk topik input (topic_in_prefix)", + "topic_out_prefix": "prefiks untuk topik output (topic_out_prefix)", + "version": "Versi MySensors" + }, + "description": "Penyiapan gateway MQTT" + }, + "gw_serial": { + "data": { + "baud_rate": "tingkat baud", + "device": "Port serial", + "persistence_file": "file persistensi (kosongkan untuk dihasilkan otomatis)", + "version": "Versi MySensors" + }, + "description": "Penyiapan gateway serial" + }, + "gw_tcp": { + "data": { + "device": "Alamat IP gateway", + "persistence_file": "file persistensi (kosongkan untuk dihasilkan otomatis)", + "tcp_port": "port", + "version": "Versi MySensors" + }, + "description": "Pengaturan gateway Ethernet" + }, + "user": { + "data": { + "gateway_type": "Jenis gateway" + }, + "description": "Pilih metode koneksi ke gateway" + } + } + }, + "title": "MySensors" +} \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/ko.json b/homeassistant/components/mysensors/translations/ko.json index bb38f94bc9204c..e57f60aafbffa6 100644 --- a/homeassistant/components/mysensors/translations/ko.json +++ b/homeassistant/components/mysensors/translations/ko.json @@ -3,14 +3,77 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "duplicate_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", + "duplicate_topic": "\ud1a0\ud53d\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_device": "\uae30\uae30\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_ip": "IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_port": "\ud3ec\ud2b8 \ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_publish_topic": "\ubc1c\ud589 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_serial": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_subscribe_topic": "\uad6c\ub3c5 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_version": "MySensors \ubc84\uc804\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_a_number": "\uc22b\uc790\ub85c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "port_out_of_range": "\ud3ec\ud2b8 \ubc88\ud638\ub294 1 \uc774\uc0c1 65535 \uc774\ud558\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", + "same_topic": "\uad6c\ub3c5 \ubc0f \ubc1c\ud589 \ud1a0\ud53d\uc740 \ub3d9\uc77c\ud569\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "duplicate_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", + "duplicate_topic": "\ud1a0\ud53d\uc774 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_device": "\uae30\uae30\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_ip": "IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_port": "\ud3ec\ud2b8 \ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_publish_topic": "\ubc1c\ud589 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_serial": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_subscribe_topic": "\uad6c\ub3c5 \ud1a0\ud53d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_version": "MySensors \ubc84\uc804\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_a_number": "\uc22b\uc790\ub85c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "port_out_of_range": "\ud3ec\ud2b8 \ubc88\ud638\ub294 1 \uc774\uc0c1 65535 \uc774\ud558\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", + "same_topic": "\uad6c\ub3c5 \ubc0f \ubc1c\ud589 \ud1a0\ud53d\uc740 \ub3d9\uc77c\ud569\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "gw_mqtt": { + "data": { + "persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c(\uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "retain": "mqtt retain", + "topic_in_prefix": "\uc785\ub825 \ud1a0\ud53d\uc758 \uc811\ub450\uc0ac (topic_in_prefix)", + "topic_out_prefix": "\ucd9c\ub825 \ud1a0\ud53d\uc758 \uc811\ub450\uc0ac (topic_out_prefix)", + "version": "MySensors \ubc84\uc804" + }, + "description": "MQTT \uac8c\uc774\ud2b8\uc6e8\uc774 \uc124\uc815" + }, + "gw_serial": { + "data": { + "baud_rate": "\uc804\uc1a1 \uc18d\ub3c4", + "device": "\uc2dc\ub9ac\uc5bc \ud3ec\ud2b8", + "persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c(\uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "version": "MySensors \ubc84\uc804" + }, + "description": "\uc2dc\ub9ac\uc5bc \uac8c\uc774\ud2b8\uc6e8\uc774 \uc124\uc815" + }, + "gw_tcp": { + "data": { + "device": "\uac8c\uc774\ud2b8\uc6e8\uc774\uc758 IP \uc8fc\uc18c", + "persistence_file": "\uc9c0\uc18d\uc131 \ud30c\uc77c(\uc790\ub3d9\uc73c\ub85c \uc0dd\uc131\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "tcp_port": "\ud3ec\ud2b8", + "version": "MySensors \ubc84\uc804" + }, + "description": "\uc774\ub354\ub137 \uac8c\uc774\ud2b8\uc6e8\uc774 \uc124\uc815" + }, + "user": { + "data": { + "gateway_type": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc720\ud615" + }, + "description": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc5f0\uacb0 \ubc29\ubc95\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694" + } } - } + }, + "title": "MySensors" } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/nl.json b/homeassistant/components/mysensors/translations/nl.json index e41f67c7730e11..49ddf987cef645 100644 --- a/homeassistant/components/mysensors/translations/nl.json +++ b/homeassistant/components/mysensors/translations/nl.json @@ -3,13 +3,16 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", + "duplicate_persistence_file": "Persistentiebestand al in gebruik", "duplicate_topic": "Topic is al in gebruik", "invalid_auth": "Ongeldige authenticatie", "invalid_device": "Ongeldig apparaat", "invalid_ip": "Ongeldig IP-adres", + "invalid_persistence_file": "Ongeldig persistentiebestand", "invalid_port": "Ongeldig poortnummer", "invalid_publish_topic": "Ongeldig publiceer topic", "invalid_serial": "Ongeldige seri\u00eble poort", + "invalid_subscribe_topic": "Ongeldig abonneeronderwerp", "invalid_version": "Ongeldige MySensors-versie", "not_a_number": "Voer een nummer in", "port_out_of_range": "Poortnummer moet minimaal 1 en maximaal 65535 zijn", diff --git a/homeassistant/components/neato/translations/de.json b/homeassistant/components/neato/translations/de.json index 4c2fc456873181..272191a42216d4 100644 --- a/homeassistant/components/neato/translations/de.json +++ b/homeassistant/components/neato/translations/de.json @@ -20,7 +20,7 @@ "title": "W\u00e4hle die Authentifizierungsmethode" }, "reauth_confirm": { - "title": "M\u00f6chtest du mit der Einrichtung beginnen?" + "title": "M\u00f6chten Sie mit der Einrichtung beginnen?" }, "user": { "data": { diff --git a/homeassistant/components/neato/translations/he.json b/homeassistant/components/neato/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/neato/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/hu.json b/homeassistant/components/neato/translations/hu.json index f2fd30f323efe4..3cb6ffd33641b1 100644 --- a/homeassistant/components/neato/translations/hu.json +++ b/homeassistant/components/neato/translations/hu.json @@ -1,15 +1,26 @@ { "config": { "abort": { - "already_configured": "M\u00e1r konfigur\u00e1lva van", - "reauth_successful": "Az \u00fajb\u00f3li azonos\u00edt\u00e1s sikeres" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" }, "create_entry": { - "default": "L\u00e1sd: [Neato dokument\u00e1ci\u00f3] ( {docs_url} )." + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + }, "reauth_confirm": { - "title": "El akarja kezdeni a be\u00e1ll\u00edt\u00e1st?" + "title": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" }, "user": { "data": { @@ -17,9 +28,10 @@ "username": "Felhaszn\u00e1l\u00f3n\u00e9v", "vendor": "Sz\u00e1ll\u00edt\u00f3" }, - "description": "L\u00e1sd: [Neato dokument\u00e1ci\u00f3] ( {docs_url} ).", + "description": "L\u00e1sd: [Neato dokument\u00e1ci\u00f3]({docs_url}).", "title": "Neato Fi\u00f3kinform\u00e1ci\u00f3" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/id.json b/homeassistant/components/neato/translations/id.json new file mode 100644 index 00000000000000..17eee515787ba2 --- /dev/null +++ b/homeassistant/components/neato/translations/id.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "invalid_auth": "Autentikasi tidak valid", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "title": "Ingin memulai penyiapan?" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna", + "vendor": "Vendor" + }, + "description": "Baca [dokumentasi Neato]({docs_url}).", + "title": "Info Akun Neato" + } + } + }, + "title": "Neato Botvac" +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ko.json b/homeassistant/components/neato/translations/ko.json index 359aeefcc78552..d08000871ea8f5 100644 --- a/homeassistant/components/neato/translations/ko.json +++ b/homeassistant/components/neato/translations/ko.json @@ -32,5 +32,6 @@ "title": "Neato \uacc4\uc815 \uc815\ubcf4" } } - } + }, + "title": "Neato Botvac" } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/ca.json b/homeassistant/components/nest/translations/ca.json index 08d8cb974546e9..bc19d5c2c7c1d5 100644 --- a/homeassistant/components/nest/translations/ca.json +++ b/homeassistant/components/nest/translations/ca.json @@ -30,7 +30,7 @@ "data": { "code": "Codi PIN" }, - "description": "Per enlla\u00e7ar el teu compte de Nest, [autoritza el teu compte]({url}). \n\nDespr\u00e9s de l'autoritzaci\u00f3, copia i enganxa el codi pin que es mostra a sota.", + "description": "Per enlla\u00e7ar el teu compte de Nest, [autoritza el teu compte]({url}). \n\nDespr\u00e9s de l'autoritzaci\u00f3, copia i enganxa el codi PIN que es mostra a sota.", "title": "Enlla\u00e7 amb el compte de Nest" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/en.json b/homeassistant/components/nest/translations/en.json index f45c9ea489fc5c..d7b000d921fe27 100644 --- a/homeassistant/components/nest/translations/en.json +++ b/homeassistant/components/nest/translations/en.json @@ -30,7 +30,7 @@ "data": { "code": "PIN Code" }, - "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.", + "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided PIN code below.", "title": "Link Nest Account" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/et.json b/homeassistant/components/nest/translations/et.json index 7d22dfd96bf21c..1319bc2ca4b794 100644 --- a/homeassistant/components/nest/translations/et.json +++ b/homeassistant/components/nest/translations/et.json @@ -30,7 +30,7 @@ "data": { "code": "PIN kood" }, - "description": "Nest-i konto linkimiseks [authorize your account] ({url}).\n\nP\u00e4rast autoriseerimist kopeeri allolev PIN kood.", + "description": "Nest-i konto linkimiseks [authorize your account] ({url}).\n\nP\u00e4rast autoriseerimist kopeeri ja kleebi allolev PIN kood.", "title": "Lingi Nesti konto" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/fr.json b/homeassistant/components/nest/translations/fr.json index 2830bf5da8771e..ce716fb3083bc0 100644 --- a/homeassistant/components/nest/translations/fr.json +++ b/homeassistant/components/nest/translations/fr.json @@ -30,7 +30,7 @@ "data": { "code": "Code PIN" }, - "description": "Pour associer votre compte Nest, [autorisez votre compte]({url}). \n\n Apr\u00e8s autorisation, copiez-collez le code PIN fourni ci-dessous.", + "description": "Pour associer votre compte Nest, [autorisez votre compte]({url}). \n\n Apr\u00e8s autorisation, copiez-collez le code NIP fourni ci-dessous.", "title": "Lier un compte Nest" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/hu.json b/homeassistant/components/nest/translations/hu.json index 47334c4aa622dd..9400ea7875c6cf 100644 --- a/homeassistant/components/nest/translations/hu.json +++ b/homeassistant/components/nest/translations/hu.json @@ -3,34 +3,42 @@ "abort": { "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "reauth_successful": "Az \u00fajb\u00f3li azonos\u00edt\u00e1s sikeres" + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" }, "error": { "internal_error": "Bels\u0151 hiba t\u00f6rt\u00e9nt a k\u00f3d valid\u00e1l\u00e1s\u00e1n\u00e1l", - "invalid_pin": "\u00c9rv\u00e9nytelen ", + "invalid_pin": "\u00c9rv\u00e9nytelen PIN-k\u00f3d", "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n.", - "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "init": { "data": { "flow_impl": "Szolg\u00e1ltat\u00f3" }, - "description": "V\u00e1laszd ki, hogy melyik hiteles\u00edt\u00e9si szolg\u00e1ltat\u00f3n\u00e1l szeretn\u00e9d hiteles\u00edteni a Nestet.", + "description": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert", "title": "Hiteles\u00edt\u00e9si Szolg\u00e1ltat\u00f3" }, "link": { "data": { "code": "PIN-k\u00f3d" }, - "description": "A Nest-fi\u00f3k \u00f6sszekapcsol\u00e1s\u00e1hoz [enged\u00e9lyezze fi\u00f3kj\u00e1t] ( {url} ). \n\n Az enged\u00e9lyez\u00e9s ut\u00e1n m\u00e1solja be az al\u00e1bbi PIN k\u00f3dot.", + "description": "A Nest-fi\u00f3k \u00f6sszekapcsol\u00e1s\u00e1hoz [enged\u00e9lyezze fi\u00f3kj\u00e1t]({url}). \n\nAz enged\u00e9lyez\u00e9s ut\u00e1n m\u00e1solja be az PIN k\u00f3dot.", "title": "Nest fi\u00f3k \u00f6sszekapcsol\u00e1sa" }, + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + }, "reauth_confirm": { - "title": "Integr\u00e1ci\u00f3 \u00fajb\u00f3li azonos\u00edt\u00e1sa" + "description": "A Nest integr\u00e1ci\u00f3nak \u00fajra kell hiteles\u00edtenie a fi\u00f3kodat", + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } }, diff --git a/homeassistant/components/nest/translations/id.json b/homeassistant/components/nest/translations/id.json index 4fc229c0d5308f..757c53e2866221 100644 --- a/homeassistant/components/nest/translations/id.json +++ b/homeassistant/components/nest/translations/id.json @@ -2,28 +2,52 @@ "config": { "abort": { "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", - "authorize_url_timeout": "Waktu tunggu menghasilkan otorisasi url telah habis." + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "reauth_successful": "Autentikasi ulang berhasil", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" }, "error": { - "internal_error": "Kesalahan Internal memvalidasi kode", - "timeout": "Waktu tunggu memvalidasi kode telah habis.", - "unknown": "Error tidak diketahui saat memvalidasi kode" + "internal_error": "Kesalahan internal saat memvalidasi kode", + "invalid_pin": "Invalid Kode PIN", + "timeout": "Tenggang waktu memvalidasi kode telah habis.", + "unknown": "Kesalahan yang tidak diharapkan" }, "step": { "init": { "data": { "flow_impl": "Penyedia" }, - "description": "Pilih melalui penyedia autentikasi mana yang ingin Anda autentikasi dengan Nest.", - "title": "Penyedia Otentikasi" + "description": "Pilih Metode Autentikasi", + "title": "Penyedia Autentikasi" }, "link": { "data": { "code": "Kode PIN" }, - "description": "Untuk menautkan akun Nest Anda, [beri kuasa akun Anda] ( {url} ). \n\n Setelah otorisasi, salin-tempel kode pin yang disediakan di bawah ini.", - "title": "Hubungkan Akun Nest" + "description": "Untuk menautkan akun Nest Anda, [otorisasi akun Anda]({url}).\n\nSetelah otorisasi, salin dan tempel kode PIN yang disediakan di bawah ini.", + "title": "Tautkan Akun Nest" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi Nest perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "Gerakan terdeteksi", + "camera_person": "Orang terdeteksi", + "camera_sound": "Suara terdeteksi", + "doorbell_chime": "Bel pintu ditekan" + } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/ko.json b/homeassistant/components/nest/translations/ko.json index f5a0fcf39d1c77..f8d6de2244a859 100644 --- a/homeassistant/components/nest/translations/ko.json +++ b/homeassistant/components/nest/translations/ko.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." }, "create_entry": { @@ -30,15 +30,24 @@ "data": { "code": "PIN \ucf54\ub4dc" }, - "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74, [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url}) \uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4, \uc544\ub798\uc758 PIN \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec\ub123\uc73c\uc138\uc694.", + "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74 [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url})\uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4 \uc544\ub798\uc5d0 \uc81c\uacf5\ub41c PIN \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec \ub123\uc5b4\uc8fc\uc138\uc694.", "title": "Nest \uacc4\uc815 \uc5f0\uacb0\ud558\uae30" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "reauth_confirm": { - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "Nest \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "\uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "camera_person": "\uc0ac\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "camera_sound": "\uc18c\ub9ac\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "doorbell_chime": "\ucd08\uc778\uc885\uc774 \ub20c\ub838\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/nest/translations/nl.json b/homeassistant/components/nest/translations/nl.json index 387b1effcb0014..b4a965f4955749 100644 --- a/homeassistant/components/nest/translations/nl.json +++ b/homeassistant/components/nest/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "authorize_url_fail": "Onbekende fout bij het genereren van een autoriseer-URL.", - "authorize_url_timeout": "Toestemming voor het genereren van autoriseer-url.", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "reauth_successful": "Herauthenticatie was succesvol", @@ -16,19 +16,19 @@ "internal_error": "Interne foutvalidatiecode", "invalid_pin": "Ongeldige PIN-code", "timeout": "Time-out validatie van code", - "unknown": "Onbekende foutvalidatiecode" + "unknown": "Onverwachte fout" }, "step": { "init": { "data": { "flow_impl": "Leverancier" }, - "description": "Kies met welke authenticatieleverancier u wilt verifi\u00ebren met Nest.", + "description": "Kies een authenticatie methode", "title": "Authenticatieleverancier" }, "link": { "data": { - "code": "Pincode" + "code": "PIN-code" }, "description": "Als je je Nest-account wilt koppelen, [autoriseer je account] ( {url} ). \n\nNa autorisatie, kopieer en plak de voorziene pincode hieronder.", "title": "Koppel Nest-account" diff --git a/homeassistant/components/nest/translations/no.json b/homeassistant/components/nest/translations/no.json index 14166f7d9a6e17..d6b6c89bcaa14b 100644 --- a/homeassistant/components/nest/translations/no.json +++ b/homeassistant/components/nest/translations/no.json @@ -30,7 +30,7 @@ "data": { "code": "PIN kode" }, - "description": "For \u00e5 koble din Nest-konto m\u00e5 du [bekrefte kontoen din]({url}). \n\nEtter bekreftelse, kopier og lim inn den oppgitte PIN koden nedenfor.", + "description": "For \u00e5 koble til Nest-kontoen din, [autoriser kontoen din] ( {url} ). \n\n Etter autorisasjon, kopier og lim inn den angitte PIN-koden nedenfor.", "title": "Koble til Nest konto" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/ru.json b/homeassistant/components/nest/translations/ru.json index 4f2e8952566ff2..07ac5246cbfa1e 100644 --- a/homeassistant/components/nest/translations/ru.json +++ b/homeassistant/components/nest/translations/ru.json @@ -30,7 +30,7 @@ "data": { "code": "PIN-\u043a\u043e\u0434" }, - "description": "[\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c]({url}), \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Nest. \n\n \u041f\u043e\u0441\u043b\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u0430\u0433\u0430\u0435\u043c\u044b\u0439 \u043f\u0438\u043d-\u043a\u043e\u0434.", + "description": "[\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0439\u0442\u0435\u0441\u044c]({url}), \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Nest. \n\n \u041f\u043e\u0441\u043b\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u0430\u0433\u0430\u0435\u043c\u044b\u0439 PIN-\u043a\u043e\u0434.", "title": "\u041f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Nest" }, "pick_implementation": { diff --git a/homeassistant/components/nest/translations/sv.json b/homeassistant/components/nest/translations/sv.json index 0abe4d75fac17d..cddb9e2fe79a47 100644 --- a/homeassistant/components/nest/translations/sv.json +++ b/homeassistant/components/nest/translations/sv.json @@ -25,5 +25,10 @@ "title": "L\u00e4nka Nest-konto" } } + }, + "device_automation": { + "trigger_type": { + "camera_motion": "R\u00f6relse uppt\u00e4ckt" + } } } \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/de.json b/homeassistant/components/netatmo/translations/de.json index 0be425d1e31cf9..247e9e8b9312ea 100644 --- a/homeassistant/components/netatmo/translations/de.json +++ b/homeassistant/components/netatmo/translations/de.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "Abwesenheit", + "hg": "Frostw\u00e4chter", + "schedule": "Zeitplan" + }, + "trigger_type": { + "alarm_started": "{entity_name} hat einen Alarm erkannt", + "animal": "{entity_name} hat ein Tier erkannt", + "cancel_set_point": "{entity_name} hat seinen Zeitplan wieder aufgenommen", + "human": "{entity_name} hat einen Menschen erkannt", + "movement": "{entity_name} hat eine Bewegung erkannt", + "outdoor": "{entity_name} hat ein Ereignis im Freien erkannt", + "person": "{entity_name} hat eine Person erkannt", + "person_away": "{entity_name} hat erkannt, dass eine Person gegangen ist", + "set_point": "Solltemperatur {entity_name} manuell eingestellt", + "therm_mode": "{entity_name} wechselte zu \"{subtype}\"", + "turned_off": "{entity_name} ausgeschaltet", + "turned_on": "{entity_name} eingeschaltet", + "vehicle": "{entity_name} hat ein Fahrzeug erkannt" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index cae1f6d20c0393..b3140a6f115df5 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -2,7 +2,9 @@ "config": { "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", - "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "create_entry": { "default": "Sikeres hiteles\u00edt\u00e9s" @@ -12,5 +14,29 @@ "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } + }, + "device_automation": { + "trigger_subtype": { + "away": "t\u00e1vol" + }, + "trigger_type": { + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "A ter\u00fclet neve" + } + }, + "public_weather_areas": { + "data": { + "new_area": "Ter\u00fclet neve", + "weather_areas": "Id\u0151j\u00e1r\u00e1si ter\u00fcletek" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/id.json b/homeassistant/components/netatmo/translations/id.json new file mode 100644 index 00000000000000..6812d45816b492 --- /dev/null +++ b/homeassistant/components/netatmo/translations/id.json @@ -0,0 +1,64 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + }, + "device_automation": { + "trigger_subtype": { + "away": "keluar", + "schedule": "jadwal" + }, + "trigger_type": { + "alarm_started": "{entity_name} mendeteksi alarm", + "animal": "{entity_name} mendeteksi binatang", + "cancel_set_point": "{entity_name} telah melanjutkan jadwalnya", + "human": "{entity_name} mendeteksi manusia", + "movement": "{entity_name} mendeteksi gerakan", + "outdoor": "{entity_name} mendeteksi peristiwa luar ruangan", + "person": "{entity_name} mendeteksi seseorang", + "person_away": "{entity_name} mendeteksi seseorang telah pergi", + "set_point": "Suhu target {entity_name} disetel secara manual", + "therm_mode": "{entity_name} beralih ke \"{subtype}\"", + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan", + "vehicle": "{entity_name} mendeteksi kendaraan" + } + }, + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "Nama area", + "lat_ne": "Lintang Pojok Timur Laut", + "lat_sw": "Lintang Pojok Barat Daya", + "lon_ne": "Bujur Pojok Timur Laut", + "lon_sw": "Bujur Pojok Barat Daya", + "mode": "Perhitungan", + "show_on_map": "Tampilkan di peta" + }, + "description": "Konfigurasikan sensor cuaca publik untuk suatu area.", + "title": "Sensor cuaca publik Netatmo" + }, + "public_weather_areas": { + "data": { + "new_area": "Nama area", + "weather_areas": "Area cuaca" + }, + "description": "Konfigurasikan sensor cuaca publik.", + "title": "Sensor cuaca publik Netatmo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/ko.json b/homeassistant/components/netatmo/translations/ko.json index 320df4665156a4..fee370ce219f1e 100644 --- a/homeassistant/components/netatmo/translations/ko.json +++ b/homeassistant/components/netatmo/translations/ko.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "\uc678\ucd9c", + "hg": "\ub3d9\ud30c \ubc29\uc9c0", + "schedule": "\uc77c\uc815" + }, + "trigger_type": { + "alarm_started": "{entity_name}\uc774(\uac00) \uc54c\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "animal": "{entity_name}\uc774(\uac00) \ub3d9\ubb3c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "cancel_set_point": "{entity_name}\uc774(\uac00) \uc77c\uc815\uc744 \uc7ac\uac1c\ud560 \ub54c", + "human": "{entity_name}\uc774(\uac00) \uc0ac\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "movement": "{entity_name}\uc774(\uac00) \uc6c0\uc9c1\uc784\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "outdoor": "{entity_name}\uc774(\uac00) \uc2e4\uc678\uc758 \uc774\ubca4\ud2b8\ub97c \uac10\uc9c0\ud588\uc744 \ub54c", + "person": "{entity_name}\uc774(\uac00) \uc0ac\ub78c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "person_away": "{entity_name}\uc774(\uac00) \uc0ac\ub78c\uc774 \ub5a0\ub0ac\uc74c\uc744 \uac10\uc9c0\ud588\uc744 \ub54c", + "set_point": "{entity_name}\uc758 \ubaa9\ud45c \uc628\ub3c4\uac00 \uc218\ub3d9\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc744 \ub54c", + "therm_mode": "{entity_name}\uc774(\uac00) {subtype}(\uc73c)\ub85c \uc804\ud658\ub418\uc5c8\uc744 \ub54c", + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c", + "vehicle": "{entity_name}\uc774(\uac00) \ucc28\ub7c9\uc744 \uac10\uc9c0\ud588\uc744 \ub54c" + } + }, "options": { "step": { "public_weather": { @@ -27,16 +49,16 @@ "mode": "\uacc4\uc0b0\ud558\uae30", "show_on_map": "\uc9c0\ub3c4\uc5d0 \ud45c\uc2dc\ud558\uae30" }, - "description": "\uc9c0\uc5ed\uc5d0 \ub300\ud55c \uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", - "title": "Netamo \uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c" + "description": "\uc9c0\uc5ed\uc5d0 \ub300\ud55c \uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Netamo \uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c" }, "public_weather_areas": { "data": { "new_area": "\uc9c0\uc5ed \uc774\ub984", "weather_areas": "\ub0a0\uc528 \uc9c0\uc5ed" }, - "description": "\uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", - "title": "Netamo \uacf5\uc6a9 \ub0a0\uc528 \uc13c\uc11c" + "description": "\uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Netamo \uacf5\uacf5 \uae30\uc0c1 \uc13c\uc11c" } } } diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 0bdc3170a5a5b6..53ff8bb5cb5938 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -48,10 +48,13 @@ "lon_sw": "Lengtegraad Zuidwestelijke hoek", "mode": "Berekening", "show_on_map": "Toon op kaart" - } + }, + "description": "Configureer een openbare weersensor voor een gebied.", + "title": "Netatmo openbare weersensor" }, "public_weather_areas": { - "description": "Configureer openbare weersensoren." + "description": "Configureer openbare weersensoren.", + "title": "Netatmo openbare weersensor" } } } diff --git a/homeassistant/components/netatmo/translations/pl.json b/homeassistant/components/netatmo/translations/pl.json index b41689a9f8cdaa..449e09bfa3a08c 100644 --- a/homeassistant/components/netatmo/translations/pl.json +++ b/homeassistant/components/netatmo/translations/pl.json @@ -15,6 +15,28 @@ } } }, + "device_automation": { + "trigger_subtype": { + "away": "poza domem", + "hg": "ochrona przed mrozem", + "schedule": "harmonogram" + }, + "trigger_type": { + "alarm_started": "{entity_name} wykryje alarm", + "animal": "{entity_name} wykryje zwierz\u0119", + "cancel_set_point": "{entity_name} wznowi sw\u00f3j harmonogram", + "human": "{entity_name} wykryje cz\u0142owieka", + "movement": "{entity_name} wykryje ruch", + "outdoor": "{entity_name} wykryje zdarzenie zewn\u0119trzne", + "person": "{entity_name} wykryje osob\u0119", + "person_away": "{entity_name} wykryje, \u017ce osoba wysz\u0142a", + "set_point": "temperatura docelowa {entity_name} zosta\u0142a ustawiona r\u0119cznie", + "therm_mode": "{entity_name} prze\u0142\u0105czy\u0142(a) si\u0119 na \u201e{subtype}\u201d", + "turned_off": "{entity_name} zostanie wy\u0142\u0105czony", + "turned_on": "{entity_name} zostanie w\u0142\u0105czony", + "vehicle": "{entity_name} wykryje pojazd" + } + }, "options": { "step": { "public_weather": { diff --git a/homeassistant/components/netatmo/translations/zh-Hans.json b/homeassistant/components/netatmo/translations/zh-Hans.json new file mode 100644 index 00000000000000..0e8f01ed8f724a --- /dev/null +++ b/homeassistant/components/netatmo/translations/zh-Hans.json @@ -0,0 +1,16 @@ +{ + "device_automation": { + "trigger_subtype": { + "away": "\u79bb\u5f00", + "schedule": "\u65e5\u7a0b" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u95f9\u949f", + "animal": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u52a8\u7269", + "human": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u4eba", + "outdoor": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u51fa\u95e8\u4e8b\u4ef6", + "person": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u4eba", + "person_away": "{entity_name} \u68c0\u6d4b\u5230\u4e00\u4e2a\u4eba\u79bb\u5f00\u4e86" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/he.json b/homeassistant/components/nexia/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/nexia/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/nexia/translations/hu.json b/homeassistant/components/nexia/translations/hu.json index dee4ed9ee0fa4d..7dedf459484fa1 100644 --- a/homeassistant/components/nexia/translations/hu.json +++ b/homeassistant/components/nexia/translations/hu.json @@ -1,11 +1,20 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "Csatlakoz\u00e1s a mynexia.com-hoz" } } } diff --git a/homeassistant/components/nexia/translations/id.json b/homeassistant/components/nexia/translations/id.json new file mode 100644 index 00000000000000..e6900bdffa1757 --- /dev/null +++ b/homeassistant/components/nexia/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/ko.json b/homeassistant/components/nexia/translations/ko.json index 170411f5f7390f..4bd1589e4e8d80 100644 --- a/homeassistant/components/nexia/translations/ko.json +++ b/homeassistant/components/nexia/translations/ko.json @@ -14,7 +14,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "mynexia.com \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "mynexia.com\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json index d718c78d7af78c..a9b4d99883ef4e 100644 --- a/homeassistant/components/nexia/translations/nl.json +++ b/homeassistant/components/nexia/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze nexia-woning is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", diff --git a/homeassistant/components/nightscout/translations/hu.json b/homeassistant/components/nightscout/translations/hu.json index 3b2d79a34a77e2..459a879e82cc63 100644 --- a/homeassistant/components/nightscout/translations/hu.json +++ b/homeassistant/components/nightscout/translations/hu.json @@ -2,6 +2,20 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "url": "URL" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/id.json b/homeassistant/components/nightscout/translations/id.json new file mode 100644 index 00000000000000..75496084bc4fa2 --- /dev/null +++ b/homeassistant/components/nightscout/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "url": "URL" + }, + "description": "- URL: alamat instans nightscout Anda, misalnya https://myhomeassistant.duckdns.org:5423\n- Kunci API (Opsional): Hanya gunakan jika instans Anda dilindungi (auth_default_roles != dapat dibaca).", + "title": "Masukkan informasi server Nightscout Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/ko.json b/homeassistant/components/nightscout/translations/ko.json index 0408a1f61abfb6..1146e6926e3f46 100644 --- a/homeassistant/components/nightscout/translations/ko.json +++ b/homeassistant/components/nightscout/translations/ko.json @@ -8,12 +8,15 @@ "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Nightscout", "step": { "user": { "data": { "api_key": "API \ud0a4", "url": "URL \uc8fc\uc18c" - } + }, + "description": "- URL: Nightscout \uc778\uc2a4\ud134\uc2a4\uc758 \uc8fc\uc18c. \uc608: https://myhomeassistant.duckdns.org:5423\n- API \ud0a4 (\uc120\ud0dd \uc0ac\ud56d): \uc778\uc2a4\ud134\uc2a4\uac00 \ubcf4\ud638\ub41c \uacbd\uc6b0\uc5d0\ub9cc \uc0ac\uc6a9\ud574\uc8fc\uc138\uc694 (auth_default_roles != readable).", + "title": "Nightscout \uc11c\ubc84 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/notify/translations/hu.json b/homeassistant/components/notify/translations/hu.json index b5c88047f66c42..18413724b53e7e 100644 --- a/homeassistant/components/notify/translations/hu.json +++ b/homeassistant/components/notify/translations/hu.json @@ -1,3 +1,3 @@ { - "title": "\u00c9rtes\u00edt" + "title": "\u00c9rtes\u00edt\u00e9sek" } \ No newline at end of file diff --git a/homeassistant/components/notify/translations/id.json b/homeassistant/components/notify/translations/id.json index 723b49fe6af4db..0ee3a77f9c13da 100644 --- a/homeassistant/components/notify/translations/id.json +++ b/homeassistant/components/notify/translations/id.json @@ -1,3 +1,3 @@ { - "title": "Pemberitahuan" + "title": "Notifikasi" } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/he.json b/homeassistant/components/notion/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/notion/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/hu.json b/homeassistant/components/notion/translations/hu.json index 6bf20925535f06..b4d57f83bb3e54 100644 --- a/homeassistant/components/notion/translations/hu.json +++ b/homeassistant/components/notion/translations/hu.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "no_devices": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a fi\u00f3kban" }, "step": { diff --git a/homeassistant/components/notion/translations/id.json b/homeassistant/components/notion/translations/id.json new file mode 100644 index 00000000000000..35ee7a295442ee --- /dev/null +++ b/homeassistant/components/notion/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "no_devices": "Tidak ada perangkat yang ditemukan di akun" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Isi informasi Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/nl.json b/homeassistant/components/notion/translations/nl.json index 4b6597725efa92..64894303cdf9ab 100644 --- a/homeassistant/components/notion/translations/nl.json +++ b/homeassistant/components/notion/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze gebruikersnaam is al in gebruik." + "already_configured": "Account is al geconfigureerd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/nuheat/translations/he.json b/homeassistant/components/nuheat/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/nuheat/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/nuheat/translations/hu.json b/homeassistant/components/nuheat/translations/hu.json index 8c523b72e04ce1..e6e7174e325229 100644 --- a/homeassistant/components/nuheat/translations/hu.json +++ b/homeassistant/components/nuheat/translations/hu.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "cannot_connect": "A csatlakoz\u00e1s nem siker\u00fclt, pr\u00f3b\u00e1lkozzon \u00fajra", - "invalid_thermostat": "A termoszt\u00e1t sorozatsz\u00e1ma \u00e9rv\u00e9nytelen." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_thermostat": "A termoszt\u00e1t sorozatsz\u00e1ma \u00e9rv\u00e9nytelen.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/nuheat/translations/id.json b/homeassistant/components/nuheat/translations/id.json new file mode 100644 index 00000000000000..69041c7755d40c --- /dev/null +++ b/homeassistant/components/nuheat/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "invalid_thermostat": "Nomor seri termostat tidak valid.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "serial_number": "Nomor seri termostat.", + "username": "Nama Pengguna" + }, + "description": "Anda harus mendapatkan nomor seri atau ID numerik termostat Anda dengan masuk ke https://MyNuHeat.com dan memilih termostat Anda.", + "title": "Hubungkan ke NuHeat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/ko.json b/homeassistant/components/nuheat/translations/ko.json index a533cd69093f80..b926d1f5269ee4 100644 --- a/homeassistant/components/nuheat/translations/ko.json +++ b/homeassistant/components/nuheat/translations/ko.json @@ -16,8 +16,8 @@ "serial_number": "\uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "https://MyNuHeat.com \uc5d0 \ub85c\uadf8\uc778\ud558\uace0 \uc628\ub3c4 \uc870\uc808\uae30\ub97c \uc120\ud0dd\ud558\uc5ec \uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638 \ub610\ub294 \ub610\ub294 ID \ub97c \uc5bb\uc5b4\uc57c \ud569\ub2c8\ub2e4.", - "title": "NuHeat \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "https://MyNuHeat.com \uc5d0 \ub85c\uadf8\uc778\ud558\uace0 \uc628\ub3c4 \uc870\uc808\uae30\ub97c \uc120\ud0dd\ud558\uc5ec \uc628\ub3c4 \uc870\uc808\uae30\uc758 \uc2dc\ub9ac\uc5bc \ubc88\ud638 \ub610\ub294 \ub610\ub294 ID\ub97c \uc5bb\uc5b4\uc57c \ud569\ub2c8\ub2e4.", + "title": "NuHeat\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/nuheat/translations/nl.json b/homeassistant/components/nuheat/translations/nl.json index edf3ad17ff45c4..d7672832db051b 100644 --- a/homeassistant/components/nuheat/translations/nl.json +++ b/homeassistant/components/nuheat/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "De thermostaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "invalid_thermostat": "Het serienummer van de thermostaat is ongeldig.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/nuki/translations/bg.json b/homeassistant/components/nuki/translations/bg.json new file mode 100644 index 00000000000000..4983c9a14b265d --- /dev/null +++ b/homeassistant/components/nuki/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/hu.json b/homeassistant/components/nuki/translations/hu.json new file mode 100644 index 00000000000000..4f0b1a29738370 --- /dev/null +++ b/homeassistant/components/nuki/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port", + "token": "Hozz\u00e1f\u00e9r\u00e9si token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuki/translations/id.json b/homeassistant/components/nuki/translations/id.json new file mode 100644 index 00000000000000..d9e5e1de2c31af --- /dev/null +++ b/homeassistant/components/nuki/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "token": "Token Akses" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/de.json b/homeassistant/components/number/translations/de.json new file mode 100644 index 00000000000000..5005eb5a010e62 --- /dev/null +++ b/homeassistant/components/number/translations/de.json @@ -0,0 +1,3 @@ +{ + "title": "Nummer" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/hu.json b/homeassistant/components/number/translations/hu.json new file mode 100644 index 00000000000000..296b9b750a7077 --- /dev/null +++ b/homeassistant/components/number/translations/hu.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name} \u00e9rt\u00e9k\u00e9nek be\u00e1ll\u00edt\u00e1sa" + } + }, + "title": "Sz\u00e1m" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/id.json b/homeassistant/components/number/translations/id.json new file mode 100644 index 00000000000000..6e928cb51cf18d --- /dev/null +++ b/homeassistant/components/number/translations/id.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "Tetapkan nilai untuk {entity_name}" + } + }, + "title": "Bilangan" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/ko.json b/homeassistant/components/number/translations/ko.json new file mode 100644 index 00000000000000..9c642931408745 --- /dev/null +++ b/homeassistant/components/number/translations/ko.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "set_value": "{entity_name}\uc758 \uac12 \uc124\uc815\ud558\uae30" + } + }, + "title": "\uc22b\uc790" +} \ No newline at end of file diff --git a/homeassistant/components/number/translations/zh-Hans.json b/homeassistant/components/number/translations/zh-Hans.json new file mode 100644 index 00000000000000..de9720ed77acaa --- /dev/null +++ b/homeassistant/components/number/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "action_type": { + "set_value": "\u8bbe\u7f6e {entity_name} \u7684\u503c" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ca.json b/homeassistant/components/nut/translations/ca.json index fa7b728e115272..fc8b5b719a029f 100644 --- a/homeassistant/components/nut/translations/ca.json +++ b/homeassistant/components/nut/translations/ca.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/de.json b/homeassistant/components/nut/translations/de.json index 50d37fa8ec4bc8..990f21523b6093 100644 --- a/homeassistant/components/nut/translations/de.json +++ b/homeassistant/components/nut/translations/de.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "unknown": "Unerwarteter Fehler" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/et.json b/homeassistant/components/nut/translations/et.json index 27745bfeeae378..83d793eb22c4ce 100644 --- a/homeassistant/components/nut/translations/et.json +++ b/homeassistant/components/nut/translations/et.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u00dchendus nurjus", + "unknown": "Tundmatu viga" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/fr.json b/homeassistant/components/nut/translations/fr.json index d91bd1e4070f9b..35739689425f84 100644 --- a/homeassistant/components/nut/translations/fr.json +++ b/homeassistant/components/nut/translations/fr.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u00c9chec de connexion", + "unknown": "Erreur inattendue" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/he.json b/homeassistant/components/nut/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/nut/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/nut/translations/hu.json b/homeassistant/components/nut/translations/hu.json index 1ca56c7684f069..a7bad455dc3837 100644 --- a/homeassistant/components/nut/translations/hu.json +++ b/homeassistant/components/nut/translations/hu.json @@ -1,5 +1,12 @@ { "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": { @@ -10,5 +17,11 @@ } } } + }, + "options": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/id.json b/homeassistant/components/nut/translations/id.json new file mode 100644 index 00000000000000..fc23e34fe8e0d8 --- /dev/null +++ b/homeassistant/components/nut/translations/id.json @@ -0,0 +1,50 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "resources": { + "data": { + "resources": "Sumber Daya" + }, + "title": "Pilih Sumber Daya untuk Dipantau" + }, + "ups": { + "data": { + "alias": "Alias", + "resources": "Sumber Daya" + }, + "title": "Pilih UPS untuk Dipantau" + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke server NUT" + } + } + }, + "options": { + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "init": { + "data": { + "resources": "Sumber Daya", + "scan_interval": "Interval Pindai (detik)" + }, + "description": "Pilih Sumber Daya Sensor." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/it.json b/homeassistant/components/nut/translations/it.json index 440cb421504a47..cb8949fca641b0 100644 --- a/homeassistant/components/nut/translations/it.json +++ b/homeassistant/components/nut/translations/it.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/ko.json b/homeassistant/components/nut/translations/ko.json index 0fb8339ddfce3c..a5680f4ed48887 100644 --- a/homeassistant/components/nut/translations/ko.json +++ b/homeassistant/components/nut/translations/ko.json @@ -33,13 +33,17 @@ } }, "options": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, "step": { "init": { "data": { "resources": "\ub9ac\uc18c\uc2a4", "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, - "description": "\uc13c\uc11c \ub9ac\uc18c\uc2a4 \uc120\ud0dd" + "description": "\uc13c\uc11c \ub9ac\uc18c\uc2a4\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/nut/translations/nl.json b/homeassistant/components/nut/translations/nl.json index 5e4acf3574d986..d90b75b4bccbe6 100644 --- a/homeassistant/components/nut/translations/nl.json +++ b/homeassistant/components/nut/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Verbinden mislukt", + "unknown": "Onverwachte fout" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/no.json b/homeassistant/components/nut/translations/no.json index 0af64ad4fa46c1..887d3b6d30af7a 100644 --- a/homeassistant/components/nut/translations/no.json +++ b/homeassistant/components/nut/translations/no.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Tilkobling mislyktes", + "unknown": "Uventet feil" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 240b32bfe192c9..2686a98e69779f 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/ru.json b/homeassistant/components/nut/translations/ru.json index 7a3f1c9b47e0fb..5422704c96dbf5 100644 --- a/homeassistant/components/nut/translations/ru.json +++ b/homeassistant/components/nut/translations/ru.json @@ -33,6 +33,10 @@ } }, "options": { + "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": { "init": { "data": { diff --git a/homeassistant/components/nut/translations/zh-Hans.json b/homeassistant/components/nut/translations/zh-Hans.json index a5f4ff11f09ef8..91522c7f609f39 100644 --- a/homeassistant/components/nut/translations/zh-Hans.json +++ b/homeassistant/components/nut/translations/zh-Hans.json @@ -1,11 +1,22 @@ { "config": { "step": { + "resources": { + "data": { + "resources": "\u8d44\u6e90" + } + }, "user": { "data": { "username": "\u7528\u6237\u540d" } } } + }, + "options": { + "error": { + "cannot_connect": "\u8fde\u63a5\u5931\u8d25", + "unknown": "\u4e0d\u5728\u9884\u671f\u5185\u7684\u9519\u8bef" + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/zh-Hant.json b/homeassistant/components/nut/translations/zh-Hant.json index 7c65e836f9e067..822d2e785f22ce 100644 --- a/homeassistant/components/nut/translations/zh-Hant.json +++ b/homeassistant/components/nut/translations/zh-Hant.json @@ -33,6 +33,10 @@ } }, "options": { + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, "step": { "init": { "data": { diff --git a/homeassistant/components/nws/translations/he.json b/homeassistant/components/nws/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/nws/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/hu.json b/homeassistant/components/nws/translations/hu.json index 5f4f7bb8bee402..1d674cacc7e0c1 100644 --- a/homeassistant/components/nws/translations/hu.json +++ b/homeassistant/components/nws/translations/hu.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { - "api_key": "API kulcs" + "api_key": "API kulcs", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" } } } diff --git a/homeassistant/components/nws/translations/id.json b/homeassistant/components/nws/translations/id.json new file mode 100644 index 00000000000000..3c92ebaed64031 --- /dev/null +++ b/homeassistant/components/nws/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "latitude": "Lintang", + "longitude": "Bujur", + "station": "Kode stasiun METAR" + }, + "description": "Jika kode stasiun METAR tidak ditentukan, informasi lintang dan bujur akan digunakan untuk menemukan stasiun terdekat. Untuk saat ini, Kunci API bisa berupa nilai sebarang. Disarankan untuk menggunakan alamat email yang valid.", + "title": "Hubungkan ke National Weather Service" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/ko.json b/homeassistant/components/nws/translations/ko.json index 9fbdf026558856..6cf4ca0c9c592e 100644 --- a/homeassistant/components/nws/translations/ko.json +++ b/homeassistant/components/nws/translations/ko.json @@ -15,7 +15,7 @@ "longitude": "\uacbd\ub3c4", "station": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc" }, - "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\uac00 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uac00\uc7a5 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294 \ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \ud604\uc7ac API Key \ub294 \uc544\ubb34 \ud0a4\ub098 \ub123\uc5b4\ub3c4 \uc0c1\uad00 \uc5c6\uc2b5\ub2c8\ub2e4\ub9cc, \uc62c\ubc14\ub978 \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uad8c\uc7a5\ud569\ub2c8\ub2e4.", + "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\uac00 \uc9c0\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc704\ub3c4 \ubc0f \uacbd\ub3c4\uac00 \uac00\uc7a5 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294 \ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4. \ud604\uc7ac API Key\ub294 \uc544\ubb34 \ud0a4\ub098 \ub123\uc5b4\ub3c4 \uc0c1\uad00\uc5c6\uc2b5\ub2c8\ub2e4\ub9cc, \uc62c\ubc14\ub978 \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4.", "title": "\ubbf8\uad6d \uae30\uc0c1\uccad\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index b74e6db96a21a7..c8e10dfba10ec7 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Service is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "api_key": "API-sleutel (e-mail)", + "api_key": "API-sleutel", "latitude": "Breedtegraad", "longitude": "Lengtegraad", "station": "METAR-zendercode" diff --git a/homeassistant/components/nzbget/translations/hu.json b/homeassistant/components/nzbget/translations/hu.json new file mode 100644 index 00000000000000..9eee6cc3be60dc --- /dev/null +++ b/homeassistant/components/nzbget/translations/hu.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "Hoszt", + "name": "N\u00e9v", + "password": "Jelsz\u00f3", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "title": "Csatlakoz\u00e1s az NZBGet-hez" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Friss\u00edt\u00e9si gyakoris\u00e1g (m\u00e1sodpercben)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/id.json b/homeassistant/components/nzbget/translations/id.json new file mode 100644 index 00000000000000..af096f4ef5fd53 --- /dev/null +++ b/homeassistant/components/nzbget/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Hubungkan ke NZBGet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Frekuensi pembaruan (dalam detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/ko.json b/homeassistant/components/nzbget/translations/ko.json index 58d38b66361e08..0de5bbb3dd80e0 100644 --- a/homeassistant/components/nzbget/translations/ko.json +++ b/homeassistant/components/nzbget/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -19,7 +19,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, - "title": "NZBGet\uc5d0 \uc5f0\uacb0" + "title": "NZBGet\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -27,7 +27,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08)" + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4 (\ucd08)" } } } diff --git a/homeassistant/components/omnilogic/translations/de.json b/homeassistant/components/omnilogic/translations/de.json index 4378d39912d90c..85de80d3dfab85 100644 --- a/homeassistant/components/omnilogic/translations/de.json +++ b/homeassistant/components/omnilogic/translations/de.json @@ -16,5 +16,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Abfrageintervall (in Sekunden)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/hu.json b/homeassistant/components/omnilogic/translations/hu.json new file mode 100644 index 00000000000000..129bb041b42fbd --- /dev/null +++ b/homeassistant/components/omnilogic/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Lek\u00e9rdez\u00e9si id\u0151k\u00f6z (m\u00e1sodpercben)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/id.json b/homeassistant/components/omnilogic/translations/id.json new file mode 100644 index 00000000000000..ed19cc68cf8609 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Interval polling (dalam detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/ko.json b/homeassistant/components/omnilogic/translations/ko.json index 74786104624fe1..d2a5f58abeb84e 100644 --- a/homeassistant/components/omnilogic/translations/ko.json +++ b/homeassistant/components/omnilogic/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/onboarding/translations/id.json b/homeassistant/components/onboarding/translations/id.json index 33e8a88a9ae0cd..472630cacf6de8 100644 --- a/homeassistant/components/onboarding/translations/id.json +++ b/homeassistant/components/onboarding/translations/id.json @@ -1,5 +1,7 @@ { "area": { - "kitchen": "Dapur" + "bedroom": "Kamar Tidur", + "kitchen": "Dapur", + "living_room": "Ruang Keluarga" } } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/de.json b/homeassistant/components/ondilo_ico/translations/de.json index 5bab6ed132bf52..ad11cefde66ec9 100644 --- a/homeassistant/components/ondilo_ico/translations/de.json +++ b/homeassistant/components/ondilo_ico/translations/de.json @@ -12,5 +12,6 @@ "title": "W\u00e4hle die Authentifizierungsmethode" } } - } + }, + "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/hu.json b/homeassistant/components/ondilo_ico/translations/hu.json new file mode 100644 index 00000000000000..cae1f6d20c0393 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/hu.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/id.json b/homeassistant/components/ondilo_ico/translations/id.json new file mode 100644 index 00000000000000..1227a6d6689dc4 --- /dev/null +++ b/homeassistant/components/ondilo_ico/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + }, + "title": "Ondilo ICO" +} \ No newline at end of file diff --git a/homeassistant/components/ondilo_ico/translations/ko.json b/homeassistant/components/ondilo_ico/translations/ko.json index fa000ea1c06d1b..88f3d678171bde 100644 --- a/homeassistant/components/ondilo_ico/translations/ko.json +++ b/homeassistant/components/ondilo_ico/translations/ko.json @@ -12,5 +12,6 @@ "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } - } + }, + "title": "Ondilo ICO" } \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/de.json b/homeassistant/components/onewire/translations/de.json index d3ed8137da3473..2b0630db22c85e 100644 --- a/homeassistant/components/onewire/translations/de.json +++ b/homeassistant/components/onewire/translations/de.json @@ -12,12 +12,14 @@ "data": { "host": "Host", "port": "Port" - } + }, + "title": "owserver-Details einstellen" }, "user": { "data": { "type": "Verbindungstyp" - } + }, + "title": "1-Wire einrichten" } } } diff --git a/homeassistant/components/onewire/translations/hu.json b/homeassistant/components/onewire/translations/hu.json index 8ac8f6d3b03c42..662475dde2c085 100644 --- a/homeassistant/components/onewire/translations/hu.json +++ b/homeassistant/components/onewire/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_path": "A k\u00f6nyvt\u00e1r nem tal\u00e1lhat\u00f3." @@ -7,14 +10,15 @@ "step": { "owserver": { "data": { - "host": "Gazdag\u00e9p", + "host": "Hoszt", "port": "Port" } }, "user": { "data": { "type": "Kapcsolat t\u00edpusa" - } + }, + "title": "A 1-Wire be\u00e1ll\u00edt\u00e1sa" } } } diff --git a/homeassistant/components/onewire/translations/id.json b/homeassistant/components/onewire/translations/id.json new file mode 100644 index 00000000000000..5de8e2eee3e6c3 --- /dev/null +++ b/homeassistant/components/onewire/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_path": "Direktori tidak ditemukan." + }, + "step": { + "owserver": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Tetapkan detail owserver" + }, + "user": { + "data": { + "type": "Jenis koneksi" + }, + "title": "Siapkan 1-Wire" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onewire/translations/ko.json b/homeassistant/components/onewire/translations/ko.json index 871482b766b53b..038be16108ab12 100644 --- a/homeassistant/components/onewire/translations/ko.json +++ b/homeassistant/components/onewire/translations/ko.json @@ -4,14 +4,22 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_path": "\ub514\ub809\ud130\ub9ac\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "step": { "owserver": { "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" - } + }, + "title": "owserver \uc138\ubd80 \uc815\ubcf4 \uc124\uc815\ud558\uae30" + }, + "user": { + "data": { + "type": "\uc5f0\uacb0 \uc720\ud615" + }, + "title": "1-Wire \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/onvif/translations/he.json b/homeassistant/components/onvif/translations/he.json index a3203bfedb3380..ecfa1afaab23d5 100644 --- a/homeassistant/components/onvif/translations/he.json +++ b/homeassistant/components/onvif/translations/he.json @@ -1,6 +1,12 @@ { "config": { "step": { + "auth": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, "configure_profile": { "data": { "include": "\u05e6\u05d5\u05e8 \u05d9\u05e9\u05d5\u05ea \u05de\u05e6\u05dc\u05de\u05d4" diff --git a/homeassistant/components/onvif/translations/hu.json b/homeassistant/components/onvif/translations/hu.json index c9c3f1379847f0..61a6cfb056ef46 100644 --- a/homeassistant/components/onvif/translations/hu.json +++ b/homeassistant/components/onvif/translations/hu.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "no_h264": "Nem voltak el\u00e9rhet\u0151 H264 streamek. Ellen\u0151rizd a profil konfigur\u00e1ci\u00f3j\u00e1t a k\u00e9sz\u00fcl\u00e9ken.", + "no_mac": "Nem siker\u00fclt konfigur\u00e1lni az egyedi azonos\u00edt\u00f3t az ONVIF eszk\u00f6zh\u00f6z.", + "onvif_error": "Hiba t\u00f6rt\u00e9nt az ONVIF eszk\u00f6z be\u00e1ll\u00edt\u00e1sakor. Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt ellen\u0151rizd a napl\u00f3kat." + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, @@ -8,7 +15,8 @@ "data": { "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + }, + "title": "Hiteles\u00edt\u00e9s konfigur\u00e1l\u00e1sa" }, "configure_profile": { "data": { @@ -17,11 +25,19 @@ "description": "L\u00e9trehozza a(z) {profile} f\u00e9nyk\u00e9pez\u0151g\u00e9p entit\u00e1s\u00e1t {resolution} felbont\u00e1ssal?", "title": "Profilok konfigur\u00e1l\u00e1sa" }, + "device": { + "data": { + "host": "V\u00e1laszd ki a felfedezett ONVIF eszk\u00f6zt" + }, + "title": "ONVIF eszk\u00f6z kiv\u00e1laszt\u00e1sa" + }, "manual_input": { "data": { "host": "Hoszt", + "name": "N\u00e9v", "port": "Port" - } + }, + "title": "ONVIF eszk\u00f6z konfigur\u00e1l\u00e1sa" }, "user": { "description": "A k\u00fcld\u00e9s gombra kattintva olyan ONVIF-eszk\u00f6z\u00f6ket keres\u00fcnk a h\u00e1l\u00f3zat\u00e1ban, amelyek t\u00e1mogatj\u00e1k az S profilt.\n\nEgyes gy\u00e1rt\u00f3k alap\u00e9rtelmez\u00e9s szerint elkezdt\u00e9k letiltani az ONVIF-et. Ellen\u0151rizze, hogy az ONVIF enged\u00e9lyezve van-e a kamera konfigur\u00e1ci\u00f3j\u00e1ban." diff --git a/homeassistant/components/onvif/translations/id.json b/homeassistant/components/onvif/translations/id.json new file mode 100644 index 00000000000000..3ed50ae63c40de --- /dev/null +++ b/homeassistant/components/onvif/translations/id.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "no_h264": "Tidak ada aliran H264 yang tersedia. Periksa konfigurasi profil di perangkat Anda.", + "no_mac": "Tidak dapat mengonfigurasi ID unik untuk perangkat ONVIF.", + "onvif_error": "Terjadi kesalahan saat menyiapkan perangkat ONVIF. Periksa log untuk informasi lebih lanjut." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "auth": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Konfigurasikan autentikasi" + }, + "configure_profile": { + "data": { + "include": "Buat entitas kamera" + }, + "description": "Buat entitas kamera untuk {profile} dengan {resolution}?", + "title": "Konfigurasikan Profil" + }, + "device": { + "data": { + "host": "Pilih perangkat ONVIF yang ditemukan" + }, + "title": "Pilih perangkat ONVIF" + }, + "manual_input": { + "data": { + "host": "Host", + "name": "Nama", + "port": "Port" + }, + "title": "Konfigurasikan perangkat ONVIF" + }, + "user": { + "description": "Dengan mengklik kirim, kami akan mencari perangkat ONVIF pada jaringan Anda yang mendukung Profil S.\n\nBeberapa produsen mulai menonaktifkan ONVIF secara default. Pastikan ONVIF diaktifkan dalam konfigurasi kamera Anda.", + "title": "Penyiapan perangkat ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Argumen FFMPEG ekstra", + "rtsp_transport": "Mekanisme transport RTSP" + }, + "title": "Opsi Perangkat ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/hu.json b/homeassistant/components/opentherm_gw/translations/hu.json index c3dd3f1020679e..b8f51f4bb20c63 100644 --- a/homeassistant/components/opentherm_gw/translations/hu.json +++ b/homeassistant/components/opentherm_gw/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "error": { - "already_configured": "Az \u00e1tj\u00e1r\u00f3 m\u00e1r konfigur\u00e1lva van", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "id_exists": "Az \u00e1tj\u00e1r\u00f3 azonos\u00edt\u00f3ja m\u00e1r l\u00e9tezik" }, "step": { diff --git a/homeassistant/components/opentherm_gw/translations/id.json b/homeassistant/components/opentherm_gw/translations/id.json new file mode 100644 index 00000000000000..7c7624c3dfe07b --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "id_exists": "ID gateway sudah ada" + }, + "step": { + "init": { + "data": { + "device": "Jalur atau URL", + "id": "ID", + "name": "Nama" + }, + "title": "Gateway OpenTherm" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "floor_temperature": "Suhu Lantai", + "precision": "Tingkat Presisi" + }, + "description": "Pilihan untuk Gateway OpenTherm" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/hu.json b/homeassistant/components/openuv/translations/hu.json index 3a6f6a8ae9183a..b5c0e5ec6081b9 100644 --- a/homeassistant/components/openuv/translations/hu.json +++ b/homeassistant/components/openuv/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, "error": { "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" }, diff --git a/homeassistant/components/openuv/translations/id.json b/homeassistant/components/openuv/translations/id.json index 4d0edd93ea9625..5075ec2c965b8f 100644 --- a/homeassistant/components/openuv/translations/id.json +++ b/homeassistant/components/openuv/translations/id.json @@ -1,15 +1,18 @@ { "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, "error": { "invalid_api_key": "Kunci API tidak valid" }, "step": { "user": { "data": { - "api_key": "Kunci API OpenUV", + "api_key": "Kunci API", "elevation": "Ketinggian", "latitude": "Lintang", - "longitude": "Garis bujur" + "longitude": "Bujur" }, "title": "Isi informasi Anda" } diff --git a/homeassistant/components/openuv/translations/ko.json b/homeassistant/components/openuv/translations/ko.json index ee211d3cbd53e6..114be1df692360 100644 --- a/homeassistant/components/openuv/translations/ko.json +++ b/homeassistant/components/openuv/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/openuv/translations/nl.json b/homeassistant/components/openuv/translations/nl.json index 118e8f05141ebf..d7287b99ddf5c6 100644 --- a/homeassistant/components/openuv/translations/nl.json +++ b/homeassistant/components/openuv/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "invalid_api_key": "Ongeldige API-sleutel" @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "api_key": "OpenUV API-Sleutel", + "api_key": "API-sleutel", "elevation": "Hoogte", "latitude": "Breedtegraad", "longitude": "Lengtegraad" diff --git a/homeassistant/components/openweathermap/translations/he.json b/homeassistant/components/openweathermap/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/openweathermap/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/hu.json b/homeassistant/components/openweathermap/translations/hu.json new file mode 100644 index 00000000000000..2fd2f0acc7a354 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/hu.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs" + }, + "step": { + "user": { + "data": { + "api_key": "API kulcs", + "language": "Nyelv", + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g", + "mode": "M\u00f3d", + "name": "Az integr\u00e1ci\u00f3 neve" + }, + "description": "Az OpenWeatherMap integr\u00e1ci\u00f3 be\u00e1ll\u00edt\u00e1sa. Az API kulcs l\u00e9trehoz\u00e1s\u00e1hoz menj az https://openweathermap.org/appid oldalra", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Nyelv", + "mode": "M\u00f3d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/id.json b/homeassistant/components/openweathermap/translations/id.json new file mode 100644 index 00000000000000..61d2713f42a094 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_api_key": "Kunci API tidak valid" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "language": "Bahasa", + "latitude": "Lintang", + "longitude": "Bujur", + "mode": "Mode", + "name": "Nama integrasi" + }, + "description": "Siapkan integrasi OpenWeatherMap. Untuk membuat kunci API, buka https://openweathermap.org/appid", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Bahasa", + "mode": "Mode" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/ko.json b/homeassistant/components/openweathermap/translations/ko.json index 1514ada24ec40f..d2f5d9aa1230b9 100644 --- a/homeassistant/components/openweathermap/translations/ko.json +++ b/homeassistant/components/openweathermap/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -17,7 +17,7 @@ "mode": "\ubaa8\ub4dc", "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc774\ub984" }, - "description": "OpenWeatherMap \ud1b5\ud569\uc744 \uc124\uc815\ud558\uc138\uc694. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid\ub85c \uc774\ub3d9\ud558\uc2ed\uc2dc\uc624.", + "description": "OpenWeatherMap \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid \ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/ovo_energy/translations/de.json b/homeassistant/components/ovo_energy/translations/de.json index 761f6a7d247561..a86f39a614cf2b 100644 --- a/homeassistant/components/ovo_energy/translations/de.json +++ b/homeassistant/components/ovo_energy/translations/de.json @@ -10,7 +10,9 @@ "reauth": { "data": { "password": "Passwort" - } + }, + "description": "Die Authentifizierung f\u00fcr OVO Energy ist fehlgeschlagen. Bitte geben Sie Ihre aktuellen Anmeldedaten ein.", + "title": "Erneute Authentifizierung" }, "user": { "data": { diff --git a/homeassistant/components/ovo_energy/translations/hu.json b/homeassistant/components/ovo_energy/translations/hu.json index c4091388b35acb..7bfd337cce5d05 100644 --- a/homeassistant/components/ovo_energy/translations/hu.json +++ b/homeassistant/components/ovo_energy/translations/hu.json @@ -2,11 +2,23 @@ "config": { "error": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { + "data": { + "password": "Jelsz\u00f3" + }, "title": "\u00dajrahiteles\u00edt\u00e9s" + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "OVO Energy azonos\u00edt\u00f3 megad\u00e1sa" } } } diff --git a/homeassistant/components/ovo_energy/translations/id.json b/homeassistant/components/ovo_energy/translations/id.json new file mode 100644 index 00000000000000..05c38f244e7db7 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "error": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "OVO Energy: {username}", + "step": { + "reauth": { + "data": { + "password": "Kata Sandi" + }, + "description": "Autentikasi gagal untuk OVO Energy. Masukkan kredensial Anda saat ini.", + "title": "Autentikasi ulang" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Siapkan instans OVO Energy untuk mengakses data penggunaan energi Anda.", + "title": "Tambahkan Akun OVO Energy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/ko.json b/homeassistant/components/ovo_energy/translations/ko.json index 07ef8d8e166a11..273c146e805910 100644 --- a/homeassistant/components/ovo_energy/translations/ko.json +++ b/homeassistant/components/ovo_energy/translations/ko.json @@ -5,17 +5,21 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, + "flow_title": "OVO Energy: {username}", "step": { "reauth": { "data": { "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "OVO Energy\uc5d0 \ub300\ud55c \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\uc7ac\uc778\uc99d" }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, + "description": "\uc5d0\ub108\uc9c0 \uc0ac\uc6a9\ub7c9\uc5d0 \uc561\uc138\uc2a4 \ud558\ub824\uba74 OVO Energy \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "OVO Energy \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/ovo_energy/translations/nl.json b/homeassistant/components/ovo_energy/translations/nl.json index 7a2b5b757bbc37..d3909b0e44ed2b 100644 --- a/homeassistant/components/ovo_energy/translations/nl.json +++ b/homeassistant/components/ovo_energy/translations/nl.json @@ -18,7 +18,8 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "Voeg OVO Energie Account toe" } } } diff --git a/homeassistant/components/owntracks/translations/hu.json b/homeassistant/components/owntracks/translations/hu.json index b6bb7593906f33..f103fc9bbe1cd6 100644 --- a/homeassistant/components/owntracks/translations/hu.json +++ b/homeassistant/components/owntracks/translations/hu.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "create_entry": { "default": "\n\nAndroidon, nyisd meg [az OwnTracks appot]({android_url}), menj a preferences -> connectionre. V\u00e1ltoztasd meg a al\u00e1bbi be\u00e1ll\u00edt\u00e1sokat:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\niOS-en, nyisd meg [az OwnTracks appot]({ios_url}), kattints az (i) ikonra bal oldalon fel\u00fcl -> settings. V\u00e1ltoztasd meg az al\u00e1bbi be\u00e1ll\u00edt\u00e1sokat:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret}\n\nN\u00e9zd meg [a dokument\u00e1ci\u00f3t]({docs_url}) tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt." }, diff --git a/homeassistant/components/owntracks/translations/id.json b/homeassistant/components/owntracks/translations/id.json new file mode 100644 index 00000000000000..890afaa099cb46 --- /dev/null +++ b/homeassistant/components/owntracks/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "\n\nDi Android, buka [aplikasi OwnTracks]({android_url}), buka preferensi -> koneksi. Ubah setelan berikut ini:\n - Mode: HTTP Pribadi\n - Host: {webhook_url}\n - Identifikasi:\n - Nama pengguna: `''`\n - ID Perangkat: `''`\n\nDi iOS, buka [aplikasi OwnTracks]({ios_url}), ketuk ikon (i) di pojok kiri atas -> pengaturan. Ubah setelan berikut ini:\n - Mode: HTTP\n - URL: {webhook_url}\n - Aktifkan autentikasi\n - UserID: `''`\n\n{secret}\n\nLihat [dokumentasi]({docs_url}) untuk informasi lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan OwnTracks?", + "title": "Siapkan OwnTracks" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/ko.json b/homeassistant/components/owntracks/translations/ko.json index 6e558e54627473..a8dd94b7e165c0 100644 --- a/homeassistant/components/owntracks/translations/ko.json +++ b/homeassistant/components/owntracks/translations/ko.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { - "default": "\n\nAndroid \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url}) \uc744 \uc5f4\uace0 preferences -> connection \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url}) \uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "\n\nAndroid\uc778 \uacbd\uc6b0, [OwnTracks \uc571]({android_url})\uc744 \uc5f4\uace0 preferences -> connection\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\niOS \uc778 \uacbd\uc6b0, [OwnTracks \uc571]({ios_url})\uc744 \uc5f4\uace0 \uc67c\ucabd \uc0c1\ub2e8\uc758 (i) \uc544\uc774\ucf58\uc744 \ud0ed\ud558\uc5ec \uc124\uc815\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \ub2e4\uc74c\uacfc \uac19\uc774 \uc124\uc815\ud574\uc8fc\uc138\uc694:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret} \n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "OwnTracks \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "OwnTracks\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "OwnTracks \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/ozw/translations/fr.json b/homeassistant/components/ozw/translations/fr.json index bf4ba5c699541a..5e408b7b807215 100644 --- a/homeassistant/components/ozw/translations/fr.json +++ b/homeassistant/components/ozw/translations/fr.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "addon_info_failed": "Impossible d\u2019obtenir des informations de l'add-on OpenZWave.", - "addon_install_failed": "\u00c9chec de l\u2019installation de l'add-on OpenZWave.", + "addon_info_failed": "Impossible d'obtenir les informations sur le module compl\u00e9mentaire OpenZWave.", + "addon_install_failed": "\u00c9chec de l'installation du module compl\u00e9mentaire OpenZWave.", "addon_set_config_failed": "\u00c9chec de la configuration OpenZWave.", "already_configured": "Cet appareil est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "La configuration est d\u00e9j\u00e0 en cours", @@ -10,23 +10,23 @@ "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "error": { - "addon_start_failed": "\u00c9chec du d\u00e9marrage de l'add-on OpenZWave. V\u00e9rifiez la configuration." + "addon_start_failed": "\u00c9chec du d\u00e9marrage du module compl\u00e9mentaire OpenZWave. V\u00e9rifiez la configuration." }, "progress": { "install_addon": "Veuillez patienter pendant que l'installation du module OpenZWave se termine. Cela peut prendre plusieurs minutes." }, "step": { "hassio_confirm": { - "title": "Configurer l\u2019int\u00e9gration OpenZWave avec l\u2019add-on OpenZWave" + "title": "Configurer l'int\u00e9gration d'OpenZWave avec le module compl\u00e9mentaire OpenZWave" }, "install_addon": { "title": "L'installation du module compl\u00e9mentaire OpenZWave a commenc\u00e9" }, "on_supervisor": { "data": { - "use_addon": "Utiliser l'add-on OpenZWave Supervisor" + "use_addon": "Utilisez le module compl\u00e9mentaire OpenZWave du Supervisor" }, - "description": "Souhaitez-vous utiliser l'add-on OpenZWave Supervisor ?", + "description": "Voulez-vous utiliser le module compl\u00e9mentaire OpenZWave du Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "Cl\u00e9 r\u00e9seau", "usb_path": "Chemin du p\u00e9riph\u00e9rique USB" }, - "title": "Entrez dans la configuration de l'add-on OpenZWave" + "title": "Entrez dans la configuration du module compl\u00e9mentaire OpenZWave" } } } diff --git a/homeassistant/components/ozw/translations/hu.json b/homeassistant/components/ozw/translations/hu.json index e4c864d9fd3491..6c2c6c22f55e0b 100644 --- a/homeassistant/components/ozw/translations/hu.json +++ b/homeassistant/components/ozw/translations/hu.json @@ -4,7 +4,9 @@ "addon_info_failed": "Nem siker\u00fclt bet\u00f6lteni az OpenZWave kieg\u00e9sz\u00edt\u0151 inform\u00e1ci\u00f3kat.", "addon_install_failed": "Nem siker\u00fclt telep\u00edteni az OpenZWave b\u0151v\u00edtm\u00e9nyt.", "addon_set_config_failed": "Nem siker\u00fclt be\u00e1ll\u00edtani az OpenZWave konfigur\u00e1ci\u00f3t.", - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "addon_start_failed": "Nem siker\u00fclt elind\u00edtani az OpenZWave b\u0151v\u00edtm\u00e9nyt. Ellen\u0151rizze a konfigur\u00e1ci\u00f3t." @@ -12,14 +14,15 @@ "step": { "on_supervisor": { "data": { - "use_addon": "Haszn\u00e1lja az OpenZWave adminisztr\u00e1tori b\u0151v\u00edtm\u00e9nyt" + "use_addon": "Haszn\u00e1ld az OpenZWave Supervisor b\u0151v\u00edtm\u00e9nyt" }, - "description": "Szeretn\u00e9 haszn\u00e1lni az OpenZWave adminisztr\u00e1tori b\u0151v\u00edtm\u00e9nyt?", - "title": "V\u00e1lassza ki a csatlakoz\u00e1si m\u00f3dot" + "description": "Szeretn\u00e9d haszn\u00e1lni az OpenZWave Supervisor b\u0151v\u00edtm\u00e9nyt?", + "title": "V\u00e1laszd ki a csatlakoz\u00e1si m\u00f3dot" }, "start_addon": { "data": { - "network_key": "H\u00e1l\u00f3zati kulcs" + "network_key": "H\u00e1l\u00f3zati kulcs", + "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" } } } diff --git a/homeassistant/components/ozw/translations/id.json b/homeassistant/components/ozw/translations/id.json new file mode 100644 index 00000000000000..ef47e12f12d37c --- /dev/null +++ b/homeassistant/components/ozw/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "addon_info_failed": "Gagal mendapatkan info add-on OpenZWave.", + "addon_install_failed": "Gagal menginstal add-on OpenZWave.", + "addon_set_config_failed": "Gagal menyetel konfigurasi OpenZWave.", + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "mqtt_required": "Integrasi MQTT belum disiapkan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "addon_start_failed": "Gagal memulai add-on OpenZWave. Periksa konfigurasi." + }, + "progress": { + "install_addon": "Harap tunggu hingga penginstalan add-on OpenZWave selesai. Ini bisa memakan waktu beberapa saat." + }, + "step": { + "hassio_confirm": { + "title": "Siapkan integrasi OpenZWave dengan add-on OpenZWave" + }, + "install_addon": { + "title": "Instalasi add-on OpenZWave telah dimulai" + }, + "on_supervisor": { + "data": { + "use_addon": "Gunakan add-on Supervisor OpenZWave" + }, + "description": "Ingin menggunakan add-on Supervisor OpenZWave?", + "title": "Pilih metode koneksi" + }, + "start_addon": { + "data": { + "network_key": "Kunci Jaringan", + "usb_path": "Jalur Perangkat USB" + }, + "title": "Masukkan konfigurasi add-on OpenZWave" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/ko.json b/homeassistant/components/ozw/translations/ko.json index ba37dccdd68050..f118db7f72c43f 100644 --- a/homeassistant/components/ozw/translations/ko.json +++ b/homeassistant/components/ozw/translations/ko.json @@ -1,16 +1,40 @@ { "config": { "abort": { + "addon_info_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_set_config_failed": "OpenZWave \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "mqtt_required": "MQTT \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "addon_start_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + }, + "progress": { + "install_addon": "Openzwave \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { + "hassio_confirm": { + "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c OpenZWave \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + }, + "install_addon": { + "title": "Openzwave \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "on_supervisor": { + "data": { + "use_addon": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + }, + "description": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, "start_addon": { "data": { + "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index 80ef72a061e2f4..3026427e8f17ab 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -13,10 +13,15 @@ "install_addon": { "title": "De OpenZWave add-on installatie is gestart" }, + "on_supervisor": { + "title": "Selecteer een verbindingsmethode" + }, "start_addon": { "data": { + "network_key": "Netwerksleutel", "usb_path": "USB-apparaatpad" - } + }, + "title": "Voer de OpenZWave add-on configuratie in" } } } diff --git a/homeassistant/components/ozw/translations/zh-Hant.json b/homeassistant/components/ozw/translations/zh-Hant.json index f9ed5469da0579..37ab2ea9c9e5ff 100644 --- a/homeassistant/components/ozw/translations/zh-Hant.json +++ b/homeassistant/components/ozw/translations/zh-Hant.json @@ -1,32 +1,32 @@ { "config": { "abort": { - "addon_info_failed": "\u53d6\u5f97 OpenZWave add-on \u8cc7\u8a0a\u5931\u6557\u3002", - "addon_install_failed": "OpenZWave add-on \u5b89\u88dd\u5931\u6557\u3002", - "addon_set_config_failed": "OpenZWave add-on \u8a2d\u5b9a\u5931\u6557\u3002", + "addon_info_failed": "\u53d6\u5f97 OpenZWave \u9644\u52a0\u5143\u4ef6\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_install_failed": "OpenZWave \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5931\u6557\u3002", + "addon_set_config_failed": "OpenZWave a\u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\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" }, "error": { - "addon_start_failed": "OpenZWave add-on \u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002" + "addon_start_failed": "OpenZWave \u9644\u52a0\u5143\u4ef6\u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 OpenZWave add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 OpenZWave \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "hassio_confirm": { - "title": "\u4ee5 OpenZWave add-on \u8a2d\u5b9a OpenZwave \u6574\u5408" + "title": "\u4ee5 OpenZWave \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a OpenZwave \u6574\u5408" }, "install_addon": { - "title": "OpenZWave add-on \u5b89\u88dd\u5df2\u555f\u52d5" + "title": "OpenZWave \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5df2\u555f\u52d5" }, "on_supervisor": { "data": { - "use_addon": "\u4f7f\u7528 OpenZWave Supervisor add-on" + "use_addon": "\u4f7f\u7528 OpenZWave Supervisor \u9644\u52a0\u5143\u4ef6" }, - "description": "\u662f\u5426\u8981\u4f7f\u7528 OpenZWave Supervisor add-on\uff1f", + "description": "\u662f\u5426\u8981\u4f7f\u7528 OpenZWave Supervisor \u9644\u52a0\u5143\u4ef6\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "\u7db2\u8def\u5bc6\u9470", "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" }, - "title": "\u8acb\u8f38\u5165 OpenZWave \u8a2d\u5b9a\u3002" + "title": "\u8acb\u8f38\u5165 OpenZWave \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a\u3002" } } } diff --git a/homeassistant/components/panasonic_viera/translations/hu.json b/homeassistant/components/panasonic_viera/translations/hu.json index fbf7f49be6a51d..cfc0be387d00bf 100644 --- a/homeassistant/components/panasonic_viera/translations/hu.json +++ b/homeassistant/components/panasonic_viera/translations/hu.json @@ -1,16 +1,28 @@ { "config": { "abort": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_pin_code": "A megadott PIN-k\u00f3d \u00e9rv\u00e9nytelen volt" }, "step": { + "pairing": { + "data": { + "pin": "PIN-k\u00f3d" + }, + "description": "Add meg a TV-k\u00e9sz\u00fcl\u00e9ken megjelen\u0151 PIN-k\u00f3dot", + "title": "P\u00e1ros\u00edt\u00e1s" + }, "user": { "data": { - "host": "IP c\u00edm" - } + "host": "IP c\u00edm", + "name": "N\u00e9v" + }, + "description": "Add meg a Panasonic Viera TV-hez tartoz\u00f3 IP c\u00edmet" } } } diff --git a/homeassistant/components/panasonic_viera/translations/id.json b/homeassistant/components/panasonic_viera/translations/id.json new file mode 100644 index 00000000000000..4f9c6e3d432481 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_pin_code": "Kode PIN Anda masukkan tidak valid" + }, + "step": { + "pairing": { + "data": { + "pin": "Kode PIN" + }, + "description": "Masukkan Kode PIN yang ditampilkan di TV Anda", + "title": "Memasangkan" + }, + "user": { + "data": { + "host": "Alamat IP", + "name": "Nama" + }, + "description": "Masukkan Alamat IP TV Panasonic Viera Anda", + "title": "Siapkan TV Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/nl.json b/homeassistant/components/panasonic_viera/translations/nl.json index 96757370bed841..fa63892730e7ca 100644 --- a/homeassistant/components/panasonic_viera/translations/nl.json +++ b/homeassistant/components/panasonic_viera/translations/nl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_configured": "Deze Panasonic Viera TV is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken", - "unknown": "Er is een onbekende fout opgetreden. Controleer de logs voor meer informatie." + "unknown": "Onverwachte fout" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "invalid_pin_code": "De ingevoerde pincode is ongeldig" + "invalid_pin_code": "De PIN-code die u hebt ingevoerd is ongeldig" }, "step": { "pairing": { "data": { - "pin": "PIN" + "pin": "PIN-code" }, "description": "Voer de PIN-code in die op uw TV wordt weergegeven", "title": "Koppelen" @@ -22,7 +22,7 @@ "host": "IP-adres", "name": "Naam" }, - "description": "Voer het IP-adres van uw Panasonic Viera TV in", + "description": "Voer de IP-adres van uw Panasonic Viera TV in.", "title": "Uw tv instellen" } } diff --git a/homeassistant/components/person/translations/id.json b/homeassistant/components/person/translations/id.json index 2be3be8476a683..1535bb2bd50c4c 100644 --- a/homeassistant/components/person/translations/id.json +++ b/homeassistant/components/person/translations/id.json @@ -1,7 +1,7 @@ { "state": { "_": { - "home": "Di rumah", + "home": "Di Rumah", "not_home": "Keluar" } }, diff --git a/homeassistant/components/philips_js/translations/bg.json b/homeassistant/components/philips_js/translations/bg.json new file mode 100644 index 00000000000000..eb502f5a135e22 --- /dev/null +++ b/homeassistant/components/philips_js/translations/bg.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_pin": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u041f\u0418\u041d", + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json index f59a17bce49668..8b5d376ead70d9 100644 --- a/homeassistant/components/philips_js/translations/de.json +++ b/homeassistant/components/philips_js/translations/de.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_pin": "Ung\u00fcltige PIN", + "pairing_failure": "Fehler beim Koppeln: {error_id}", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/philips_js/translations/es.json b/homeassistant/components/philips_js/translations/es.json index d4476f29981a13..66d3b85c3bdd00 100644 --- a/homeassistant/components/philips_js/translations/es.json +++ b/homeassistant/components/philips_js/translations/es.json @@ -1,5 +1,9 @@ { "config": { + "error": { + "invalid_pin": "PIN no v\u00e1lido", + "pairing_failure": "No se ha podido emparejar: {error_id}" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/hu.json b/homeassistant/components/philips_js/translations/hu.json new file mode 100644 index 00000000000000..0065a78ae0cb6e --- /dev/null +++ b/homeassistant/components/philips_js/translations/hu.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_pin": "\u00c9rv\u00e9nytelen PIN", + "pairing_failure": "Nem lehet p\u00e1ros\u00edtani: {error_id}", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "api_version": "API Verzi\u00f3", + "host": "Hoszt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/id.json b/homeassistant/components/philips_js/translations/id.json new file mode 100644 index 00000000000000..633cfdd633e697 --- /dev/null +++ b/homeassistant/components/philips_js/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_pin": "PIN tidak valid", + "pairing_failure": "Tidak dapat memasangkan: {error_id}", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_version": "Versi API", + "host": "Host" + } + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Perangkat diminta untuk dinyalakan" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/it.json b/homeassistant/components/philips_js/translations/it.json index 216248d8eac4ce..03b5340a6bc5ba 100644 --- a/homeassistant/components/philips_js/translations/it.json +++ b/homeassistant/components/philips_js/translations/it.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Impossibile connettersi", + "invalid_pin": "PIN non valido", + "pairing_failure": "Impossibile eseguire l'associazione: {error_id}", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index 85281856809b4a..d698b01af1a9b6 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -5,14 +5,22 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_pin": "PIN\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "pairing_failure": "\ud398\uc5b4\ub9c1\uc744 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4: {error_id}", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { + "api_version": "API \ubc84\uc804", "host": "\ud638\uc2a4\ud2b8" } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "\uae30\uae30\uac00 \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/nl.json b/homeassistant/components/philips_js/translations/nl.json index 23cd7e47043f32..108d158f1c5eac 100644 --- a/homeassistant/components/philips_js/translations/nl.json +++ b/homeassistant/components/philips_js/translations/nl.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Kan geen verbinding maken", + "invalid_pin": "Ongeldige pincode", + "pairing_failure": "Kan niet koppelen: {error_id}", "unknown": "Onverwachte fout" }, "step": { @@ -15,5 +17,10 @@ } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Apparaat wordt gevraagd om in te schakelen" + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/pl.json b/homeassistant/components/philips_js/translations/pl.json index 27c088350c4383..4fc1fbc5269bf0 100644 --- a/homeassistant/components/philips_js/translations/pl.json +++ b/homeassistant/components/philips_js/translations/pl.json @@ -5,6 +5,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_pin": "Nieprawid\u0142owy kod PIN", + "pairing_failure": "Nie mo\u017cna sparowa\u0107: {error_id}", "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { diff --git a/homeassistant/components/philips_js/translations/zh-Hans.json b/homeassistant/components/philips_js/translations/zh-Hans.json new file mode 100644 index 00000000000000..1353d8d122507b --- /dev/null +++ b/homeassistant/components/philips_js/translations/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_pin": "\u65e0\u6548\u7684PIN\u7801" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/hu.json b/homeassistant/components/pi_hole/translations/hu.json index f1bd9a106bc930..104f711d8d563d 100644 --- a/homeassistant/components/pi_hole/translations/hu.json +++ b/homeassistant/components/pi_hole/translations/hu.json @@ -7,10 +7,20 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { + "api_key": { + "data": { + "api_key": "API kulcs" + } + }, "user": { "data": { + "api_key": "API kulcs", "host": "Hoszt", - "port": "Port" + "location": "Elhelyezked\u00e9s", + "name": "N\u00e9v", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } } } diff --git a/homeassistant/components/pi_hole/translations/id.json b/homeassistant/components/pi_hole/translations/id.json new file mode 100644 index 00000000000000..c38c0f5c9bbad7 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "api_key": { + "data": { + "api_key": "Kunci API" + } + }, + "user": { + "data": { + "api_key": "Kunci API", + "host": "Host", + "location": "Lokasi", + "name": "Nama", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "statistics_only": "Hanya Statistik", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/ko.json b/homeassistant/components/pi_hole/translations/ko.json index 7261742b2a64ec..d79878d8a42ce5 100644 --- a/homeassistant/components/pi_hole/translations/ko.json +++ b/homeassistant/components/pi_hole/translations/ko.json @@ -20,6 +20,7 @@ "name": "\uc774\ub984", "port": "\ud3ec\ud2b8", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", + "statistics_only": "\ud1b5\uacc4 \uc804\uc6a9", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" } } diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index eaf68b507f9f83..20cba7e9ebca62 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -10,7 +10,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Plaato Webhook einrichten" } } diff --git a/homeassistant/components/plaato/translations/en.json b/homeassistant/components/plaato/translations/en.json index 64d41d0091ecd4..1217eb53d6eac8 100644 --- a/homeassistant/components/plaato/translations/en.json +++ b/homeassistant/components/plaato/translations/en.json @@ -9,7 +9,7 @@ "default": "Your Plaato {device_type} with name **{device_name}** was successfully setup!" }, "error": { - "invalid_webhook_device": "You have selected a device that doesn't not support sending data to a webhook. It is only available for the Airlock", + "invalid_webhook_device": "You have selected a device that does not support sending data to a webhook. It is only available for the Airlock", "no_api_method": "You need to add an auth token or select webhook", "no_auth_token": "You need to add an auth token" }, diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json index 66a7b252a87c64..fb4325581dd37e 100644 --- a/homeassistant/components/plaato/translations/et.json +++ b/homeassistant/components/plaato/translations/et.json @@ -9,7 +9,7 @@ "default": "{device_type} Plaato seade nimega **{device_name}** on edukalt seadistatud!" }, "error": { - "invalid_webhook_device": "Oled valinud seadme, mis ei toeta andmete saatmist veebihaagile. See on saadaval ainult Airlocki jaoks", + "invalid_webhook_device": "Oled valinud seadme mis ei toeta andmete saatmist veebihaagile. See on saadaval ainult Airlocki jaoks", "no_api_method": "Pead lisama autentimisloa v\u00f5i valima veebihaagi", "no_auth_token": "Pead lisama autentimisloa" }, diff --git a/homeassistant/components/plaato/translations/hu.json b/homeassistant/components/plaato/translations/hu.json index 76229e86224903..aee8ceb07cdfe1 100644 --- a/homeassistant/components/plaato/translations/hu.json +++ b/homeassistant/components/plaato/translations/hu.json @@ -1,7 +1,18 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Plaato Airlock-ban. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "A Plaato {device_type} **{device_name}** n\u00e9vvel sikeresen telep\u00edtve lett!" + }, + "step": { + "user": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", + "title": "A Plaato eszk\u00f6z\u00f6k be\u00e1ll\u00edt\u00e1sa" + } } } } \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/id.json b/homeassistant/components/plaato/translations/id.json new file mode 100644 index 00000000000000..989bb38bcaf0f5 --- /dev/null +++ b/homeassistant/components/plaato/translations/id.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "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." + }, + "create_entry": { + "default": "Plaato {device_type} dengan nama **{device_name}** berhasil disiapkan!" + }, + "error": { + "invalid_webhook_device": "Anda telah memilih perangkat yang tidak mendukung pengiriman data ke webhook. Ini hanya tersedia untuk Airlock", + "no_api_method": "Anda perlu menambahkan token auth atau memilih webhook", + "no_auth_token": "Anda perlu menambahkan token autentikasi" + }, + "step": { + "api_method": { + "data": { + "token": "Tempel Token Auth di sini", + "use_webhook": "Gunakan webhook" + }, + "description": "Untuk dapat melakukan kueri API diperlukan 'auth_token'. Nilai token dapat diperoleh dengan mengikuti [petunjuk ini](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\nPerangkat yang dipilih: **{device_type}** \n\nJika Anda lebih memilih untuk menggunakan metode webhook bawaan (hanya Airlock), centang centang kotak di bawah ini dan kosongkan nilai Auth Token'", + "title": "Pilih metode API" + }, + "user": { + "data": { + "device_name": "Nama perangkat Anda", + "device_type": "Jenis perangkat Plaato" + }, + "description": "Ingin memulai penyiapan?", + "title": "Siapkan perangkat Plaato" + }, + "webhook": { + "description": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di Plaato Airlock.\n\nIsi info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nBaca [dokumentasi]({docs_url}) tentang detail lebih lanjut.", + "title": "Webhook untuk digunakan" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Interval pembaruan (menit)" + }, + "description": "Atur interval pembaruan (menit)", + "title": "Opsi untuk Plaato" + }, + "webhook": { + "description": "Info webhook:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n", + "title": "Opsi untuk Plaato Airlock" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/ko.json b/homeassistant/components/plaato/translations/ko.json index 753653c88b2f9d..1cb999c4729189 100644 --- a/homeassistant/components/plaato/translations/ko.json +++ b/homeassistant/components/plaato/translations/ko.json @@ -2,16 +2,52 @@ "config": { "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "**{device_name}** \uc758 Plaato {device_type} \uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" + "default": "**{device_name}**\uc758 Plaato {device_type}\uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" + }, + "error": { + "invalid_webhook_device": "\uc6f9 \ud6c5\uc73c\ub85c \ub370\uc774\ud130 \uc804\uc1a1\uc744 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \uae30\uae30\ub97c \uc120\ud0dd\ud588\uc2b5\ub2c8\ub2e4. Airlock\uc5d0\uc11c\ub9cc \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4", + "no_api_method": "\uc778\uc99d \ud1a0\ud070\uc744 \ucd94\uac00\ud558\uac70\ub098 \uc6f9 \ud6c5\uc744 \uc120\ud0dd\ud574\uc57c \ud569\ub2c8\ub2e4", + "no_auth_token": "\uc778\uc99d \ud1a0\ud070\uc744 \ucd94\uac00\ud574\uc57c \ud569\ub2c8\ub2e4" }, "step": { + "api_method": { + "data": { + "token": "\uc5ec\uae30\uc5d0 \uc778\uc99d \ud1a0\ud070\uc744 \ubd99\uc5ec \ub123\uc5b4\uc8fc\uc138\uc694", + "use_webhook": "\uc6f9 \ud6c5 \uc0ac\uc6a9\ud558\uae30" + }, + "description": "API\ub97c \ucffc\ub9ac \ud558\ub824\uba74 [\uc548\ub0b4 \uc9c0\uce68](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\uc5d0 \ub530\ub77c \uc5bb\uc744 \uc218 \uc788\ub294 'auth_token'\uc774 \ud544\uc694\ud569\ub2c8\ub2e4\n\n\uc120\ud0dd\ud55c \uae30\uae30: **{device_type}**\n\n\ub0b4\uc7a5\ub41c \uc6f9 \ud6c5 \ubc29\uc2dd(Airlock \uc804\uc6a9)\uc744 \uc0ac\uc6a9\ud558\ub824\ub294 \uacbd\uc6b0 \uc544\ub798 \ud655\uc778\ub780\uc744 \uc120\ud0dd\ud558\uace0 Auth Token\uc744 \ube44\uc6cc\ub450\uc138\uc694", + "title": "API \ubc29\uc2dd \uc120\ud0dd\ud558\uae30" + }, "user": { + "data": { + "device_name": "\uae30\uae30 \uc774\ub984", + "device_type": "Plaato \uae30\uae30 \uc720\ud615" + }, "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Plaato \uae30\uae30 \uc124\uc815\ud558\uae30" + }, + "webhook": { + "description": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Plaato Airlock\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4.\n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "\uc0ac\uc6a9\ud560 \uc6f9 \ud6c5" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ubd84)" + }, + "description": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 \uc124\uc815\ud558\uae30 (\ubd84)", + "title": "Plaato \uc635\uc158" + }, + "webhook": { + "description": "\uc6f9 \ud6c5 \uc815\ubcf4:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n", + "title": "Plaato Airlock \uc635\uc158" } } } diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index c0a9d1e04fba45..6453668680a8f4 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -9,6 +9,8 @@ "default": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." }, "error": { + "invalid_webhook_device": "U heeft een apparaat geselecteerd dat het verzenden van gegevens naar een webhook niet ondersteunt. Het is alleen beschikbaar voor de Airlock", + "no_api_method": "U moet een verificatie token toevoegen of selecteer een webhook", "no_auth_token": "U moet een verificatie token toevoegen" }, "step": { @@ -26,15 +28,24 @@ }, "description": "Weet u zeker dat u de Plaato-airlock wilt instellen?", "title": "Stel de Plaato Webhook in" + }, + "webhook": { + "description": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ({docs_url}) voor meer informatie.", + "title": "Webhook om te gebruiken" } } }, "options": { "step": { "user": { + "data": { + "update_interval": "Update-interval (minuten)" + }, + "description": "Stel het update-interval in (minuten)", "title": "Opties voor Plaato" }, "webhook": { + "description": "Webhook-informatie: \n\n - URL: ' {webhook_url} '\n - Methode: POST\n\n", "title": "Opties voor Plaato Airlock" } } diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 8efbf07945f2ee..7039662468ef2b 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -9,7 +9,7 @@ "default": "Plaato {device_type} med navnet **{device_name}** ble konfigurert!" }, "error": { - "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er kun tilgjengelig for Airlock", + "invalid_webhook_device": "Du har valgt en enhet som ikke st\u00f8tter sending av data til en webhook. Den er bare tilgjengelig for luftsluse", "no_api_method": "Du m\u00e5 legge til et godkjenningstoken eller velge webhook", "no_auth_token": "Du m\u00e5 legge til et godkjenningstoken" }, diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index 57df32c3f4eddf..ddfb779ea2e7d9 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty webhook" }, "create_entry": { - "default": "Tw\u00f3j {device_type} Plaato o nazwie *{device_name}* zosta\u0142o pomy\u015blnie skonfigurowane!" + "default": "Tw\u00f3j {device_type} Plaato o nazwie **{device_name}** zosta\u0142o pomy\u015blnie skonfigurowane!" }, "error": { "invalid_webhook_device": "Wybra\u0142e\u015b urz\u0105dzenie, kt\u00f3re nie obs\u0142uguje wysy\u0142ania danych do webhooka. Opcja dost\u0119pna tylko w areometrze Airlock", @@ -19,7 +19,7 @@ "token": "Wklej token autoryzacji", "use_webhook": "U\u017cyj webhook" }, - "description": "Aby m\u00f3c przesy\u0142a\u0107 zapytania do API, wymagany jest \u201etoken autoryzacji\u201d, kt\u00f3ry mo\u017cna uzyska\u0107, post\u0119puj\u0105c zgodnie z [t\u0105] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrukcj\u0105\n\nWybrane urz\u0105dzenie: *{device_type}* \n\nJe\u015bli wolisz u\u017cywa\u0107 wbudowanej metody webhook (tylko areomierz Airlock), zaznacz poni\u017csze pole i pozostaw token autoryzacji pusty", + "description": "Aby m\u00f3c przesy\u0142a\u0107 zapytania do API, wymagany jest \u201etoken autoryzacji\u201d, kt\u00f3ry mo\u017cna uzyska\u0107, post\u0119puj\u0105c zgodnie z [t\u0105] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instrukcj\u0105\n\nWybrane urz\u0105dzenie: **{device_type}** \n\nJe\u015bli wolisz u\u017cywa\u0107 wbudowanej metody webhook (tylko areomierz Airlock), zaznacz poni\u017csze pole i pozostaw token autoryzacji pusty", "title": "Wybierz metod\u0119 API" }, "user": { diff --git a/homeassistant/components/plant/translations/id.json b/homeassistant/components/plant/translations/id.json index 519964be2787af..5378edc405f116 100644 --- a/homeassistant/components/plant/translations/id.json +++ b/homeassistant/components/plant/translations/id.json @@ -1,9 +1,9 @@ { "state": { "_": { - "ok": "OK", - "problem": "Masalah" + "ok": "Oke", + "problem": "Bermasalah" } }, - "title": "Tanaman" + "title": "Monitor Tanaman" } \ No newline at end of file diff --git a/homeassistant/components/plex/translations/hu.json b/homeassistant/components/plex/translations/hu.json index cfccf5c83e6949..9168f070609f48 100644 --- a/homeassistant/components/plex/translations/hu.json +++ b/homeassistant/components/plex/translations/hu.json @@ -3,26 +3,30 @@ "abort": { "all_configured": "Az \u00f6sszes \u00f6sszekapcsolt szerver m\u00e1r konfigur\u00e1lva van", "already_configured": "Ez a Plex szerver m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A Plex konfigur\u00e1l\u00e1sa folyamatban van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", "token_request_timeout": "Token k\u00e9r\u00e9sre sz\u00e1nt id\u0151 lej\u00e1rt", - "unknown": "Ismeretlen okb\u00f3l nem siker\u00fclt" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "faulty_credentials": "A hiteles\u00edt\u00e9s sikertelen", "no_servers": "Nincs szerver csatlakoztatva a fi\u00f3khoz", "not_found": "A Plex szerver nem tal\u00e1lhat\u00f3" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { "host": "Hoszt", "port": "Port", - "ssl": "Haszn\u00e1ljon SSL-t" + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "token": "Token (opcion\u00e1lis)", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } }, "select_server": { "data": { - "server": "szerver" + "server": "Szerver" }, "description": "T\u00f6bb szerver el\u00e9rhet\u0151, v\u00e1lasszon egyet:", "title": "Plex-kiszolg\u00e1l\u00f3 kiv\u00e1laszt\u00e1sa" @@ -39,6 +43,7 @@ "step": { "plex_mp_settings": { "data": { + "ignore_plex_web_clients": "Plex Web kliensek figyelmen k\u00edv\u00fcl hagy\u00e1sa", "use_episode_art": "Haszn\u00e1lja az epiz\u00f3d bor\u00edt\u00f3j\u00e1t" }, "description": "Plex media lej\u00e1tsz\u00f3k be\u00e1ll\u00edt\u00e1sai" diff --git a/homeassistant/components/plex/translations/id.json b/homeassistant/components/plex/translations/id.json new file mode 100644 index 00000000000000..7c596835e03c07 --- /dev/null +++ b/homeassistant/components/plex/translations/id.json @@ -0,0 +1,62 @@ +{ + "config": { + "abort": { + "all_configured": "Semua server tertaut sudah dikonfigurasi", + "already_configured": "Server Plex ini sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "reauth_successful": "Autentikasi ulang berhasil", + "token_request_timeout": "Tenggang waktu pengambilan token habis", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "faulty_credentials": "Otorisasi gagal, verifikasi Token", + "host_or_token": "Harus menyediakan setidaknya satu Host atau Token", + "no_servers": "Tidak ada server yang ditautkan ke akun Plex", + "not_found": "Server Plex tidak ditemukan", + "ssl_error": "Masalah sertifikat SSL" + }, + "flow_title": "{name} ({host})", + "step": { + "manual_setup": { + "data": { + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "token": "Token (Opsional)", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Konfigurasi Plex Manual" + }, + "select_server": { + "data": { + "server": "Server" + }, + "description": "Beberapa server tersedia, pilih satu:", + "title": "Pilih server Plex" + }, + "user": { + "description": "Lanjutkan [plex.tv](https://plex.tv) untuk menautkan server Plex.", + "title": "Server Media Plex" + }, + "user_advanced": { + "data": { + "setup_method": "Metode penyiapan" + }, + "title": "Server Media Plex" + } + } + }, + "options": { + "step": { + "plex_mp_settings": { + "data": { + "ignore_new_shared_users": "Abaikan pengguna baru yang dikelola/berbagi", + "ignore_plex_web_clients": "Abaikan klien Plex Web", + "monitored_users": "Pengguna yang dipantau", + "use_episode_art": "Gunakan sampul episode" + }, + "description": "Opsi untuk Pemutar Media Plex" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/hu.json b/homeassistant/components/plugwise/translations/hu.json index 1dcdb7fe5afb52..d6d9012c21ac35 100644 --- a/homeassistant/components/plugwise/translations/hu.json +++ b/homeassistant/components/plugwise/translations/hu.json @@ -1,13 +1,31 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Smile: {name}", "step": { "user": { "data": { "flow_type": "Kapcsolat t\u00edpusa" - } + }, + "description": "Term\u00e9k:", + "title": "Plugwise t\u00edpus" }, "user_gateway": { - "description": "K\u00e9rj\u00fck, adja meg" + "data": { + "host": "IP c\u00edm", + "password": "Smile azonos\u00edt\u00f3", + "port": "Port", + "username": "Smile Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "K\u00e9rj\u00fck, adja meg", + "title": "Csatlakoz\u00e1s a Smile-hoz" } } } diff --git a/homeassistant/components/plugwise/translations/id.json b/homeassistant/components/plugwise/translations/id.json new file mode 100644 index 00000000000000..9047bf477bd7c7 --- /dev/null +++ b/homeassistant/components/plugwise/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Smile: {name}", + "step": { + "user": { + "data": { + "flow_type": "Jenis koneksi" + }, + "description": "Produk:", + "title": "Jenis Plugwise" + }, + "user_gateway": { + "data": { + "host": "Alamat IP", + "password": "ID Smile", + "port": "Port", + "username": "Nama Pengguna Smile" + }, + "description": "Masukkan", + "title": "Hubungkan ke Smile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval Pindai (detik)" + }, + "description": "Sesuaikan Opsi Plugwise" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/ko.json b/homeassistant/components/plugwise/translations/ko.json index 7af503f0a660d2..7d856d1fe2873d 100644 --- a/homeassistant/components/plugwise/translations/ko.json +++ b/homeassistant/components/plugwise/translations/ko.json @@ -11,14 +11,21 @@ "flow_title": "Smile: {name}", "step": { "user": { + "data": { + "flow_type": "\uc5f0\uacb0 \uc720\ud615" + }, "description": "\uc81c\ud488:", "title": "Plugwise \uc720\ud615" }, "user_gateway": { "data": { "host": "IP \uc8fc\uc18c", - "port": "\ud3ec\ud2b8" - } + "password": "Smile ID", + "port": "\ud3ec\ud2b8", + "username": "Smile \uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "\uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "Smile\uc5d0 \uc5f0\uacb0\ud558\uae30" } } }, @@ -28,7 +35,7 @@ "data": { "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, - "description": "Plugwise \uc635\uc158 \uc870\uc815" + "description": "Plugwise \uc635\uc158 \uc870\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 001bbbe6d5e57f..a432b8bbfd8659 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -4,8 +4,8 @@ "already_configured": "De service is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", - "invalid_auth": "Ongeldige authenticatie, controleer de 8 karakters van uw Smile-ID", + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { @@ -20,8 +20,10 @@ "data": { "host": "IP-adres", "password": "Smile-ID", - "port": "Poort" + "port": "Poort", + "username": "Smile Gebruikersnaam" }, + "description": "Voer in", "title": "Maak verbinding met de Smile" } } diff --git a/homeassistant/components/plum_lightpad/translations/hu.json b/homeassistant/components/plum_lightpad/translations/hu.json index 436e8b1fb7dd75..3a82e0a241ed2a 100644 --- a/homeassistant/components/plum_lightpad/translations/hu.json +++ b/homeassistant/components/plum_lightpad/translations/hu.json @@ -2,6 +2,17 @@ "config": { "abort": { "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/id.json b/homeassistant/components/plum_lightpad/translations/id.json new file mode 100644 index 00000000000000..0d2f98d1faa927 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/de.json b/homeassistant/components/point/translations/de.json index 343c02055d5deb..c36c7a44b51aa7 100644 --- a/homeassistant/components/point/translations/de.json +++ b/homeassistant/components/point/translations/de.json @@ -24,7 +24,7 @@ "data": { "flow_impl": "Anbieter" }, - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "W\u00e4hle die Authentifizierungsmethode" } } diff --git a/homeassistant/components/point/translations/hu.json b/homeassistant/components/point/translations/hu.json index c31e2c55e6aff3..7f4346a6ea9e62 100644 --- a/homeassistant/components/point/translations/hu.json +++ b/homeassistant/components/point/translations/hu.json @@ -4,7 +4,8 @@ "already_setup": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", - "no_flows": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + "no_flows": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." }, "create_entry": { "default": "Sikeres hiteles\u00edt\u00e9s" @@ -15,7 +16,7 @@ }, "step": { "auth": { - "description": "K\u00e9rlek k\u00f6vesd az al\u00e1bbi linket \u00e9s a Fogadd el a hozz\u00e1f\u00e9r\u00e9st a Minut fi\u00f3kj\u00e1hoz, majd t\u00e9rj vissza \u00e9s nyomd meg a K\u00fcld\u00e9s gombot. \n\n [Link] ( {authorization_url} )", + "description": "K\u00e9rlek k\u00f6vesd az al\u00e1bbi linket \u00e9s a **Fogadd el** a hozz\u00e1f\u00e9r\u00e9st a Minut fi\u00f3kj\u00e1hoz, majd t\u00e9rj vissza \u00e9s nyomd meg a **K\u00fcld\u00e9s ** gombot. \n\n [Link]({authorization_url})", "title": "Point hiteles\u00edt\u00e9se" }, "user": { diff --git a/homeassistant/components/point/translations/id.json b/homeassistant/components/point/translations/id.json new file mode 100644 index 00000000000000..868321d74693f8 --- /dev/null +++ b/homeassistant/components/point/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_setup": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "external_setup": "Point berhasil dikonfigurasi dari alur konfigurasi lainnya.", + "no_flows": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "error": { + "follow_link": "Buka tautan dan autentikasi sebelum menekan Kirim", + "no_token": "Token akses tidak valid" + }, + "step": { + "auth": { + "description": "Buka tautan di bawah ini dan **Terima** akses ke akun Minut Anda, lalu kembali dan tekan tombol **Kirim** di bawah ini.\n\n[Tautan] ({authorization_url})", + "title": "Autentikasi Point" + }, + "user": { + "data": { + "flow_impl": "Penyedia" + }, + "description": "Ingin memulai penyiapan?", + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/ko.json b/homeassistant/components/point/translations/ko.json index f4ca6002036819..5813dbba137264 100644 --- a/homeassistant/components/point/translations/ko.json +++ b/homeassistant/components/point/translations/ko.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_setup": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "already_setup": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "external_setup": "Point \uac00 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "external_setup": "Point\uac00 \ub2e4\ub978 \uad6c\uc131 \ub2e8\uacc4\uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "no_flows": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." }, diff --git a/homeassistant/components/point/translations/nl.json b/homeassistant/components/point/translations/nl.json index 94763a412a0e72..8447ac6bbb2c35 100644 --- a/homeassistant/components/point/translations/nl.json +++ b/homeassistant/components/point/translations/nl.json @@ -1,31 +1,31 @@ { "config": { "abort": { - "already_setup": "U kunt alleen een Point-account configureren.", + "already_setup": "Al geconfigureerd. Slechts een enkele configuratie mogelijk.", "authorize_url_fail": "Onbekende fout bij het genereren van een autoriseer-URL.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "external_setup": "Punt succesvol geconfigureerd vanuit een andere stroom.", - "no_flows": "U moet Point configureren voordat u zich ermee kunt verifi\u00ebren. [Gelieve de instructies te lezen](https://www.home-assistant.io/components/nest/).", + "no_flows": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "create_entry": { - "default": "Succesvol geverifieerd met Minut voor uw Point appara(a)t(en)" + "default": "Succesvol geauthenticeerd" }, "error": { "follow_link": "Volg de link en verifieer voordat je op Verzenden klikt", - "no_token": "Niet geverifieerd met Minut" + "no_token": "Ongeldig toegangstoken" }, "step": { "auth": { - "description": "Ga naar onderstaande link en Accepteer toegang tot je Minut account, kom dan hier terug en klik op Verzenden hier onder.\n\n[Link]({authorization_url})", + "description": "Ga naar onderstaande link en **Accepteer** toegang tot je Minut account, kom dan hier terug en klik op **Verzenden** hier onder.\n\n[Link]({authorization_url})", "title": "Verificatie Point" }, "user": { "data": { "flow_impl": "Leverancier" }, - "description": "Kies met welke authenticatieprovider u wilt authenticeren met Point.", - "title": "Authenticatieleverancier" + "description": "Wil je beginnen met instellen?", + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/poolsense/translations/de.json b/homeassistant/components/poolsense/translations/de.json index 5869da61c9c40f..6b64ec2eef1a45 100644 --- a/homeassistant/components/poolsense/translations/de.json +++ b/homeassistant/components/poolsense/translations/de.json @@ -12,7 +12,7 @@ "email": "E-Mail", "password": "Passwort" }, - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } } diff --git a/homeassistant/components/poolsense/translations/hu.json b/homeassistant/components/poolsense/translations/hu.json index 3b2d79a34a77e2..80562b34e2830f 100644 --- a/homeassistant/components/poolsense/translations/hu.json +++ b/homeassistant/components/poolsense/translations/hu.json @@ -2,6 +2,19 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + }, + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", + "title": "PoolSense" + } } } } \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/id.json b/homeassistant/components/poolsense/translations/id.json new file mode 100644 index 00000000000000..6e40f5f0925367 --- /dev/null +++ b/homeassistant/components/poolsense/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "description": "Ingin memulai penyiapan?", + "title": "PoolSense" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/bg.json b/homeassistant/components/powerwall/translations/bg.json new file mode 100644 index 00000000000000..cef3726d759676 --- /dev/null +++ b/homeassistant/components/powerwall/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/hu.json b/homeassistant/components/powerwall/translations/hu.json index 7cc0ceafac116a..a2b8af1337003c 100644 --- a/homeassistant/components/powerwall/translations/hu.json +++ b/homeassistant/components/powerwall/translations/hu.json @@ -1,9 +1,20 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { - "ip_address": "IP-c\u00edm" + "ip_address": "IP c\u00edm", + "password": "Jelsz\u00f3" } } } diff --git a/homeassistant/components/powerwall/translations/id.json b/homeassistant/components/powerwall/translations/id.json new file mode 100644 index 00000000000000..a5ae5f5e9794be --- /dev/null +++ b/homeassistant/components/powerwall/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "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": "Tesla Powerwall ({ip_address})", + "step": { + "user": { + "data": { + "ip_address": "Alamat IP", + "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": "Hubungkan ke powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ko.json b/homeassistant/components/powerwall/translations/ko.json index 11c638fa9bd50d..d1dd99bbb7a103 100644 --- a/homeassistant/components/powerwall/translations/ko.json +++ b/homeassistant/components/powerwall/translations/ko.json @@ -10,13 +10,15 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "wrong_version": "Powerwall \uc774 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ubc84\uc804\uc758 \uc18c\ud504\ud2b8\uc6e8\uc5b4\ub97c \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4. \uc774 \ubb38\uc81c\ub97c \ud574\uacb0\ud558\ub824\uba74 \uc5c5\uadf8\ub808\uc774\ub4dc\ud558\uac70\ub098 \uc774 \ub0b4\uc6a9\uc744 \uc54c\ub824\uc8fc\uc138\uc694." }, + "flow_title": "Tesla Powerwall ({ip_address})", "step": { "user": { "data": { "ip_address": "IP \uc8fc\uc18c", "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "powerwall \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "\ube44\ubc00\ubc88\ud638\ub294 \uc77c\ubc18\uc801\uc73c\ub85c \ubc31\uc5c5 \uac8c\uc774\ud2b8\uc6e8\uc774 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc758 \ub9c8\uc9c0\ub9c9 5\uc790\ub9ac\uc774\uba70 Tesla \uc571 \ub610\ub294 \ubc31\uc5c5 \uac8c\uc774\ud2b8\uc6e8\uc774 2\uc758 \ub3c4\uc5b4 \ub0b4\ubd80\uc5d0 \uc788\ub294 \ub9c8\uc9c0\ub9c9 5\uc790\ub9ac \ube44\ubc00\ubc88\ud638\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "powerwall\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json index c4ae2616f46e5f..5149f391669388 100644 --- a/homeassistant/components/powerwall/translations/nl.json +++ b/homeassistant/components/powerwall/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "De powerwall is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "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." @@ -17,6 +17,7 @@ "ip_address": "IP-adres", "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": "Maak verbinding met de powerwall" } } diff --git a/homeassistant/components/profiler/translations/hu.json b/homeassistant/components/profiler/translations/hu.json index bbdd2e5b536f4d..c5d28903888626 100644 --- a/homeassistant/components/profiler/translations/hu.json +++ b/homeassistant/components/profiler/translations/hu.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "M\u00e1r konfigur\u00e1lva. Csak egyetlen konfigur\u00e1ci\u00f3 lehets\u00e9ges." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "user": { - "description": "El akarja kezdeni a be\u00e1ll\u00edt\u00e1st?" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" } } } diff --git a/homeassistant/components/profiler/translations/id.json b/homeassistant/components/profiler/translations/id.json new file mode 100644 index 00000000000000..3521bb95412b03 --- /dev/null +++ b/homeassistant/components/profiler/translations/id.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/ko.json b/homeassistant/components/profiler/translations/ko.json index 0c38052b826296..251c9777b6c18f 100644 --- a/homeassistant/components/profiler/translations/ko.json +++ b/homeassistant/components/profiler/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/progettihwsw/translations/hu.json b/homeassistant/components/progettihwsw/translations/hu.json index 3b2d79a34a77e2..f6f6e2c15b7849 100644 --- a/homeassistant/components/progettihwsw/translations/hu.json +++ b/homeassistant/components/progettihwsw/translations/hu.json @@ -2,6 +2,32 @@ "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": { + "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" + } + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/id.json b/homeassistant/components/progettihwsw/translations/id.json new file mode 100644 index 00000000000000..0aa69cad9588a1 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "Relai 1", + "relay_10": "Relai 10", + "relay_11": "Relai 11", + "relay_12": "Relai 12", + "relay_13": "Relai 13", + "relay_14": "Relai 14", + "relay_15": "Relai 15", + "relay_16": "Relai 16", + "relay_2": "Relai 2", + "relay_3": "Relai 3", + "relay_4": "Relai 4", + "relay_5": "Relai 5", + "relay_6": "Relai 6", + "relay_7": "Relai 7", + "relay_8": "Relai 8", + "relay_9": "Relai 9" + }, + "title": "Siapkan relai" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Siapkan papan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/ko.json b/homeassistant/components/progettihwsw/translations/ko.json index ab21d8427bd50f..9324783f5cbe2d 100644 --- a/homeassistant/components/progettihwsw/translations/ko.json +++ b/homeassistant/components/progettihwsw/translations/ko.json @@ -27,14 +27,14 @@ "relay_8": "\ub9b4\ub808\uc774 8", "relay_9": "\ub9b4\ub808\uc774 9" }, - "title": "\ub9b4\ub808\uc774 \uc124\uc815" + "title": "\ub9b4\ub808\uc774 \uc124\uc815\ud558\uae30" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "title": "\ubcf4\ub4dc \uc124\uc815" + "title": "\ubcf4\ub4dc \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/ps4/translations/hu.json b/homeassistant/components/ps4/translations/hu.json index c3bcabb0d3a111..bb677b21700a77 100644 --- a/homeassistant/components/ps4/translations/hu.json +++ b/homeassistant/components/ps4/translations/hu.json @@ -1,8 +1,12 @@ { "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": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "login_failed": "Nem siker\u00fclt p\u00e1ros\u00edtani a PlayStation 4-gyel. Ellen\u0151rizze, hogy a helyes-e." + "login_failed": "Nem siker\u00fclt p\u00e1ros\u00edtani a PlayStation 4-gyel. Ellen\u0151rizze, hogy a PIN-k\u00f3d helyes-e." }, "step": { "creds": { @@ -10,14 +14,15 @@ }, "link": { "data": { - "code": "PIN", - "ip_address": "IP-c\u00edm", + "code": "PIN-k\u00f3d", + "ip_address": "IP c\u00edm", "name": "N\u00e9v", "region": "R\u00e9gi\u00f3" } }, "mode": { "data": { + "ip_address": "IP c\u00edm (Hagyd \u00fcresen az Automatikus Felder\u00edt\u00e9s haszn\u00e1lat\u00e1hoz).", "mode": "Konfigur\u00e1ci\u00f3s m\u00f3d" }, "title": "PlayStation 4" diff --git a/homeassistant/components/ps4/translations/id.json b/homeassistant/components/ps4/translations/id.json new file mode 100644 index 00000000000000..aab31564e162cf --- /dev/null +++ b/homeassistant/components/ps4/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "credential_error": "Terjadi kesalahan saat mengambil kredensial.", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "port_987_bind_error": "Tidak dapat mengaitkan ke port 987. Baca [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut.", + "port_997_bind_error": "Tidak dapat mengaitkan ke port 997. Baca [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "credential_timeout": "Tenggang waktu layanan kredensial habis. Tekan kirim untuk memulai kembali.", + "login_failed": "Gagal memasangkan dengan PlayStation 4. Pastikan Kode PIN sudah benar.", + "no_ipaddress": "Masukkan Alamat IP perangkat PlayStation 4 yang dikonfigurasi." + }, + "step": { + "creds": { + "description": "Kredensial diperlukan. Tekan 'Kirim' dan kemudian di Aplikasi Layar ke-2 PS4, segarkan perangkat dan pilih perangkat 'Home-Assistant' untuk melanjutkan.", + "title": "PlayStation 4" + }, + "link": { + "data": { + "code": "Kode PIN", + "ip_address": "Alamat IP", + "name": "Nama", + "region": "Wilayah" + }, + "description": "Masukkan informasi PlayStation 4 Anda. Untuk Kode PIN, buka 'Pengaturan' di konsol PlayStation 4 Anda. Kemudian buka 'Pengaturan Koneksi Aplikasi Seluler' dan pilih 'Tambah Perangkat'. Masukkan Kode PIN yang ditampilkan. Lihat [dokumentasi] (https://www.home-assistant.io/components/ps4/) untuk info lebih lanjut.", + "title": "PlayStation 4" + }, + "mode": { + "data": { + "ip_address": "Alamat IP (Kosongkan jika menggunakan Penemuan Otomatis).", + "mode": "Mode Konfigurasi" + }, + "description": "Pilih mode untuk konfigurasi. Alamat IP dapat dikosongkan jika memilih Penemuan Otomatis karena perangkat akan ditemukan secara otomatis.", + "title": "PlayStation 4" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/ko.json b/homeassistant/components/ps4/translations/ko.json index 76762a0bec6b4d..129927a9babd25 100644 --- a/homeassistant/components/ps4/translations/ko.json +++ b/homeassistant/components/ps4/translations/ko.json @@ -4,8 +4,8 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "credential_error": "\uc790\uaca9 \uc99d\uba85\uc744 \uac00\uc838\uc624\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "port_987_bind_error": "\ud3ec\ud2b8 987 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "port_987_bind_error": "987 \ud3ec\ud2b8\uc5d0 \ubc14\uc778\ub529\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "port_997_bind_error": "997 \ud3ec\ud2b8\uc5d0 \ubc14\uc778\ub529\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -15,7 +15,7 @@ }, "step": { "creds": { - "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c \uace0\uce68\ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c\uace0\uce68 \ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "title": "PlayStation 4" }, "link": { @@ -25,7 +25,7 @@ "name": "\uc774\ub984", "region": "\uc9c0\uc5ed" }, - "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. PIN \ucf54\ub4dc\ub97c \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815' \uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30' \ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "description": "PlayStation 4 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. PIN \ucf54\ub4dc\ub97c \ud655\uc778\ud558\ub824\uba74, PlayStation 4 \ucf58\uc194\uc5d0\uc11c '\uc124\uc815'\uc73c\ub85c \uc774\ub3d9\ud55c \ub4a4 '\ubaa8\ubc14\uc77c \uc571 \uc811\uc18d \uc124\uc815'\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec '\uae30\uae30 \ub4f1\ub85d\ud558\uae30'\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ud654\uba74\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc(\uc744)\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. \ucd94\uac00 \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/components/ps4/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "title": "PlayStation 4" }, "mode": { diff --git a/homeassistant/components/ps4/translations/nl.json b/homeassistant/components/ps4/translations/nl.json index 326917e49608b5..db59c7ab30ab5b 100644 --- a/homeassistant/components/ps4/translations/nl.json +++ b/homeassistant/components/ps4/translations/nl.json @@ -3,15 +3,15 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "credential_error": "Fout bij ophalen van inloggegevens.", - "no_devices_found": "Geen PlayStation 4 apparaten gevonden op het netwerk.", + "no_devices_found": "Geen apparaten gevonden op het netwerk", "port_987_bind_error": "Kon niet binden aan poort 987. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor meer informatie.", "port_997_bind_error": "Kon niet binden aan poort 997. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor aanvullende informatie." }, "error": { "cannot_connect": "Kan geen verbinding maken", "credential_timeout": "Time-out van inlog service. Druk op Submit om opnieuw te starten.", - "login_failed": "Kan niet koppelen met PlayStation 4. Controleer of de pincode juist is.", - "no_ipaddress": "Voer het IP-adres in van de PlayStation 4 die je wilt configureren." + "login_failed": "Kan Playstation 4 niet koppelen. Controleer of de PIN-code correct is.", + "no_ipaddress": "Voer het IP-adres in van de PlayStation 4 die u wilt configureren." }, "step": { "creds": { @@ -20,20 +20,20 @@ }, "link": { "data": { - "code": "PIN", + "code": "PIN-code", "ip_address": "IP-adres", "name": "Naam", "region": "Regio" }, - "description": "Voer je PlayStation 4-informatie in. Ga voor 'PIN' naar 'Instellingen' op je PlayStation 4-console. Navigeer vervolgens naar 'Verbindingsinstellingen mobiele app' en selecteer 'Apparaat toevoegen'. Voer de pincode in die wordt weergegeven. Raadpleeg de [documentatie] (https://www.home-assistant.io/components/ps4/) voor meer informatie.", + "description": "Voer je PlayStation 4-informatie in. Voor PIN-code navigeer je naar 'Instellingen' op je PlayStation 4-console. Ga vervolgens naar 'Verbindingsinstellingen mobiele app' en selecteer 'Apparaat toevoegen'. Voer de PIN-code in die wordt weergegeven. Raadpleeg de [documentatie](https://www.home-assistant.io/components/ps4/) voor meer informatie.", "title": "PlayStation 4" }, "mode": { "data": { - "ip_address": "IP-adres (leeg laten als u Auto Discovery gebruikt).", + "ip_address": "IP-adres (leeg laten indien Auto Discovery wordt gebruikt).", "mode": "Configuratiemodus" }, - "description": "Selecteer modus voor configuratie. Het veld IP-adres kan leeg blijven als Auto Discovery wordt geselecteerd, omdat apparaten automatisch worden gedetecteerd.", + "description": "Selecteer modus voor configuratie. Het veld IP-adres kan leeg worden gelaten als Auto Discovery wordt geselecteerd, omdat apparaten dan automatisch worden ontdekt.", "title": "PlayStation 4" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/hu.json b/homeassistant/components/pvpc_hourly_pricing/translations/hu.json new file mode 100644 index 00000000000000..f5301e874eae05 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/id.json b/homeassistant/components/pvpc_hourly_pricing/translations/id.json new file mode 100644 index 00000000000000..8601c31fda0c28 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "data": { + "name": "Nama Sensor", + "tariff": "Tarif kontrak (1, 2, atau 3 periode)" + }, + "description": "Sensor ini menggunakan API resmi untuk mendapatkan [harga listrik per jam (PVPC)](https://www.esios.ree.es/es/pvpc) di Spanyol.\nUntuk penjelasan yang lebih tepat, kunjungi [dokumen integrasi](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nPilih tarif kontrak berdasarkan jumlah periode penagihan per hari:\n- 1 periode: normal\n- 2 periode: diskriminasi (tarif per malam)\n- 3 periode: mobil listrik (tarif per malam 3 periode)", + "title": "Pemilihan tarif" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json index f1f225ae525771..c44d121796104b 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ko.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ko.json @@ -9,7 +9,7 @@ "name": "\uc13c\uc11c \uc774\ub984", "tariff": "\uacc4\uc57d \uc694\uae08\uc81c (1, 2 \ub610\ub294 3 \uad6c\uac04)" }, - "description": "\uc774 \uc13c\uc11c\ub294 \uacf5\uc2dd API \ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2a4\ud398\uc778\uc758 [\uc2dc\uac04\ub2f9 \uc804\uae30 \uc694\uae08 (PVPC)](https://www.esios.ree.es/es/pvpc) \uc744 \uac00\uc838\uc635\ub2c8\ub2e4.\n\ubcf4\ub2e4 \uc790\uc138\ud55c \uc124\uba85\uc740 [\uc548\ub0b4](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.\n\n1\uc77c\ub2f9 \uccad\uad6c \uad6c\uac04\uc5d0 \ub530\ub77c \uacc4\uc57d \uc694\uae08\uc81c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.\n - 1 \uad6c\uac04: \uc77c\ubc18 \uc694\uae08\uc81c\n - 2 \uad6c\uac04: \ucc28\ub4f1 \uc694\uae08\uc81c (\uc57c\uac04 \uc694\uae08) \n - 3 \uad6c\uac04: \uc804\uae30\uc790\ub3d9\ucc28 (3 \uad6c\uac04 \uc57c\uac04 \uc694\uae08)", + "description": "\uc774 \uc13c\uc11c\ub294 \uacf5\uc2dd API\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2a4\ud398\uc778\uc758 [\uc2dc\uac04\ub2f9 \uc804\uae30 \uc694\uae08 (PVPC)](https://www.esios.ree.es/es/pvpc) \uc744 \uac00\uc838\uc635\ub2c8\ub2e4.\n\ubcf4\ub2e4 \uc790\uc138\ud55c \uc124\uba85\uc740 [\uad00\ub828 \ubb38\uc11c](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.\n\n1\uc77c\ub2f9 \uccad\uad6c \uad6c\uac04\uc5d0 \ub530\ub77c \uacc4\uc57d \uc694\uae08\uc81c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.\n - 1 \uad6c\uac04: \uc77c\ubc18 \uc694\uae08\uc81c\n - 2 \uad6c\uac04: \ucc28\ub4f1 \uc694\uae08\uc81c (\uc57c\uac04 \uc694\uae08) \n - 3 \uad6c\uac04: \uc804\uae30\uc790\ub3d9\ucc28 (3 \uad6c\uac04 \uc57c\uac04 \uc694\uae08)", "title": "\uc694\uae08\uc81c \uc120\ud0dd" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json index 3abffdf5bc0f24..5048ed498dfa7c 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Integratie is al geconfigureerd met een bestaande sensor met dat tarief" + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/rachio/translations/hu.json b/homeassistant/components/rachio/translations/hu.json index 5f4f7bb8bee402..570dd27b5d99ac 100644 --- a/homeassistant/components/rachio/translations/hu.json +++ b/homeassistant/components/rachio/translations/hu.json @@ -1,5 +1,13 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/rachio/translations/id.json b/homeassistant/components/rachio/translations/id.json new file mode 100644 index 00000000000000..6630273424833d --- /dev/null +++ b/homeassistant/components/rachio/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API" + }, + "description": "Anda akan memerlukan Kunci API dari https://app.rach.io/. Buka Settings, lalu klik 'GET API KEY'.", + "title": "Hubungkan ke perangkat Rachio Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "Durasi dalam menit yang akan dijalankan saat mengaktifkan sakelar zona" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/ko.json b/homeassistant/components/rachio/translations/ko.json index 298ef476745461..203dba17303e0b 100644 --- a/homeassistant/components/rachio/translations/ko.json +++ b/homeassistant/components/rachio/translations/ko.json @@ -13,7 +13,7 @@ "data": { "api_key": "API \ud0a4" }, - "description": "https://app.rach.io/ \uc758 API \ud0a4\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. Settings \ub85c \uc774\ub3d9\ud55c \ub2e4\uc74c 'GET API KEY ' \ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", + "description": "https://app.rach.io/ \uc758 API \ud0a4\uac00 \ud544\uc694\ud569\ub2c8\ub2e4. Settings\ub85c \uc774\ub3d9\ud55c \ub2e4\uc74c 'GET API KEY '\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", "title": "Rachio \uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/rachio/translations/nl.json b/homeassistant/components/rachio/translations/nl.json index 2173c768185bc4..6a94ac2dcd41ec 100644 --- a/homeassistant/components/rachio/translations/nl.json +++ b/homeassistant/components/rachio/translations/nl.json @@ -4,14 +4,14 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "api_key": "De API-sleutel voor het Rachio-account." + "api_key": "API-sleutel" }, "description": "U heeft de API-sleutel nodig van https://app.rach.io/. Selecteer 'Accountinstellingen en klik vervolgens op' GET API KEY '.", "title": "Maak verbinding met uw Rachio-apparaat" diff --git a/homeassistant/components/rainmachine/translations/he.json b/homeassistant/components/rainmachine/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/rainmachine/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/hu.json b/homeassistant/components/rainmachine/translations/hu.json index 44e24519ca2e48..48718980e2efe4 100644 --- a/homeassistant/components/rainmachine/translations/hu.json +++ b/homeassistant/components/rainmachine/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { @@ -16,7 +22,8 @@ "init": { "data": { "zone_run_time": "Alap\u00e9rtelmezett z\u00f3nafut\u00e1si id\u0151 (m\u00e1sodpercben)" - } + }, + "title": "RainMachine konfigur\u00e1l\u00e1sa" } } } diff --git a/homeassistant/components/rainmachine/translations/id.json b/homeassistant/components/rainmachine/translations/id.json new file mode 100644 index 00000000000000..482ffb752785fc --- /dev/null +++ b/homeassistant/components/rainmachine/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "ip_address": "Nama Host atau Alamat IP", + "password": "Kata Sandi", + "port": "Port" + }, + "title": "Isi informasi Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "Waktu berjalan zona default (dalam detik)" + }, + "title": "Konfigurasikan RainMachine" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/ko.json b/homeassistant/components/rainmachine/translations/ko.json index 08ccfd1f5b9a84..0e38f4c4dfab07 100644 --- a/homeassistant/components/rainmachine/translations/ko.json +++ b/homeassistant/components/rainmachine/translations/ko.json @@ -16,5 +16,15 @@ "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30" } } + }, + "options": { + "step": { + "init": { + "data": { + "zone_run_time": "\uae30\ubcf8 \uc9c0\uc5ed \uc2e4\ud589 \uc2dc\uac04 (\ucd08)" + }, + "title": "RainMachine \uad6c\uc131\ud558\uae30" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/hu.json b/homeassistant/components/recollect_waste/translations/hu.json index 112c8cb83858b9..3222f50be02af5 100644 --- a/homeassistant/components/recollect_waste/translations/hu.json +++ b/homeassistant/components/recollect_waste/translations/hu.json @@ -14,5 +14,12 @@ } } } + }, + "options": { + "step": { + "init": { + "title": "Recollect Waste konfigur\u00e1l\u00e1sa" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/id.json b/homeassistant/components/recollect_waste/translations/id.json new file mode 100644 index 00000000000000..1c0656810dd7ec --- /dev/null +++ b/homeassistant/components/recollect_waste/translations/id.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_place_or_service_id": "Place atau Service ID tidak valid" + }, + "step": { + "user": { + "data": { + "place_id": "Place ID", + "service_id": "Service ID" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "Gunakan nama alias untuk jenis pengambilan (jika memungkinkan)" + }, + "title": "Konfigurasikan Recollect Waste" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/recollect_waste/translations/ko.json b/homeassistant/components/recollect_waste/translations/ko.json index 17dee71d640a46..422843c67f232b 100644 --- a/homeassistant/components/recollect_waste/translations/ko.json +++ b/homeassistant/components/recollect_waste/translations/ko.json @@ -2,6 +2,27 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_place_or_service_id": "\uc7a5\uc18c \ub610\ub294 \uc11c\ube44\uc2a4 ID\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "place_id": "\uc7a5\uc18c ID", + "service_id": "\uc11c\ube44\uc2a4 ID" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "\uc218\uac70 \uc720\ud615\uc5d0 \uce5c\uc219\ud55c \uc774\ub984\uc744 \uc0ac\uc6a9\ud558\uae30 (\uac00\ub2a5\ud55c \uacbd\uc6b0)" + }, + "title": "Recollect Waste \uad6c\uc131\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/remote/translations/hu.json b/homeassistant/components/remote/translations/hu.json index fa0bf3fee904ad..39ce5f17a12ef9 100644 --- a/homeassistant/components/remote/translations/hu.json +++ b/homeassistant/components/remote/translations/hu.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "{entity_name} be/kikapcsol\u00e1sa", + "turn_off": "{entity_name} kikapcsol\u00e1sa", + "turn_on": "{entity_name} bekapcsol\u00e1sa" + }, + "condition_type": { + "is_off": "{entity_name} ki van kapcsolva", + "is_on": "{entity_name} be van kapcsolva" + }, + "trigger_type": { + "turned_off": "{entity_name} ki lett kapcsolva", + "turned_on": "{entity_name} be lett kapcsolva" + } + }, "state": { "_": { "off": "Ki", diff --git a/homeassistant/components/remote/translations/id.json b/homeassistant/components/remote/translations/id.json index e824cafff4eceb..09552be40d49d6 100644 --- a/homeassistant/components/remote/translations/id.json +++ b/homeassistant/components/remote/translations/id.json @@ -1,8 +1,23 @@ { + "device_automation": { + "action_type": { + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Daring" diff --git a/homeassistant/components/remote/translations/ko.json b/homeassistant/components/remote/translations/ko.json index bd055e21f5bb73..a89c4b36f35d7d 100644 --- a/homeassistant/components/remote/translations/ko.json +++ b/homeassistant/components/remote/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774 \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774 \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uaebc\uc9d0", - "turned_on": "{entity_name} \ucf1c\uc9d0" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/remote/translations/zh-Hans.json b/homeassistant/components/remote/translations/zh-Hans.json index ba1344fbb744af..f6c509d4a0861f 100644 --- a/homeassistant/components/remote/translations/zh-Hans.json +++ b/homeassistant/components/remote/translations/zh-Hans.json @@ -1,7 +1,9 @@ { "device_automation": { "action_type": { - "turn_off": "\u5173\u95ed {entity_name}" + "toggle": "\u5207\u6362 {entity_name} \u5f00\u5173", + "turn_off": "\u5173\u95ed {entity_name}", + "turn_on": "\u6253\u5f00 {entity_name}" }, "condition_type": { "is_off": "{entity_name} \u5df2\u5173\u95ed", diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index b1e4197c0f1a2c..2315e739909074 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -50,7 +50,8 @@ "data": { "off_delay": "Ausschaltverz\u00f6gerung", "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", - "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll" + "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll", + "venetian_blind_mode": "Jalousie-Modus" } } } diff --git a/homeassistant/components/rfxtrx/translations/fr.json b/homeassistant/components/rfxtrx/translations/fr.json index c0df7233458983..8794b3913f190c 100644 --- a/homeassistant/components/rfxtrx/translations/fr.json +++ b/homeassistant/components/rfxtrx/translations/fr.json @@ -5,9 +5,13 @@ "cannot_connect": "\u00c9chec de connexion" }, "error": { - "cannot_connect": "\u00c9chec de connexion" + "cannot_connect": "\u00c9chec de connexion", + "one": "Vide", + "other": "Vide" }, "step": { + "one": "Vide", + "other": "Vide", "setup_network": { "data": { "host": "H\u00f4te", @@ -35,6 +39,7 @@ } } }, + "one": "Vide", "options": { "error": { "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9", @@ -70,5 +75,6 @@ "title": "Configurer les options de l'appareil" } } - } + }, + "other": "Vide" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/hu.json b/homeassistant/components/rfxtrx/translations/hu.json index 4e04ab16ce2c60..20ef3db6171884 100644 --- a/homeassistant/components/rfxtrx/translations/hu.json +++ b/homeassistant/components/rfxtrx/translations/hu.json @@ -1,9 +1,19 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { + "setup_network": { + "data": { + "host": "Hoszt", + "port": "Port" + } + }, "setup_serial": { "data": { "device": "Eszk\u00f6z kiv\u00e1laszt\u00e1sa" @@ -11,13 +21,18 @@ "title": "Eszk\u00f6z" }, "setup_serial_manual_path": { + "data": { + "device": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" + }, "title": "El\u00e9r\u00e9si \u00fat" } } }, "options": { "error": { - "invalid_event_code": "\u00c9rv\u00e9nytelen esem\u00e9nyk\u00f3d" + "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "invalid_event_code": "\u00c9rv\u00e9nytelen esem\u00e9nyk\u00f3d", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "prompt_options": { diff --git a/homeassistant/components/rfxtrx/translations/id.json b/homeassistant/components/rfxtrx/translations/id.json new file mode 100644 index 00000000000000..9836d252c6818c --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/id.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "setup_network": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Pilih alamat koneksi" + }, + "setup_serial": { + "data": { + "device": "Pilih perangkat" + }, + "title": "Perangkat" + }, + "setup_serial_manual_path": { + "data": { + "device": "Jalur Perangkat USB" + }, + "title": "Jalur" + }, + "user": { + "data": { + "type": "Jenis koneksi" + }, + "title": "Pilih jenis koneksi" + } + } + }, + "options": { + "error": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "invalid_event_code": "Kode event tidak valid", + "invalid_input_2262_off": "Masukan tidak valid untuk perintah mematikan", + "invalid_input_2262_on": "Masukan tidak valid untuk perintah menyalakan", + "invalid_input_off_delay": "Input tidak valid untuk penundaan mematikan", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Aktifkan penambahan otomatis", + "debug": "Aktifkan debugging", + "device": "Pilih perangkat untuk dikonfigurasi", + "event_code": "Masukkan kode event untuk ditambahkan", + "remove_device": "Pilih perangkat yang akan dihapus" + }, + "title": "Opsi Rfxtrx" + }, + "set_device_options": { + "data": { + "command_off": "Nilai bit data untuk perintah mematikan", + "command_on": "Nilai bit data untuk perintah menyalakan", + "data_bit": "Jumlah bit data", + "fire_event": "Aktifkan event perangkat", + "off_delay": "Penundaan mematikan", + "off_delay_enabled": "Aktifkan penundaan mematikan", + "replace_device": "Pilih perangkat yang akan diganti", + "signal_repetitions": "Jumlah pengulangan sinyal" + }, + "title": "Konfigurasi opsi perangkat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/ko.json b/homeassistant/components/rfxtrx/translations/ko.json index e8c83a7bfd70e3..891926083dd479 100644 --- a/homeassistant/components/rfxtrx/translations/ko.json +++ b/homeassistant/components/rfxtrx/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "already_configured": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -12,19 +12,63 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" - } + }, + "title": "\uc5f0\uacb0 \uc8fc\uc18c \uc120\ud0dd\ud558\uae30" + }, + "setup_serial": { + "data": { + "device": "\uae30\uae30 \uc120\ud0dd\ud558\uae30" + }, + "title": "\uae30\uae30" }, "setup_serial_manual_path": { "data": { "device": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "title": "\uacbd\ub85c" + }, + "user": { + "data": { + "type": "\uc5f0\uacb0 \uc720\ud615" + }, + "title": "\uc5f0\uacb0 \uc720\ud615 \uc120\ud0dd\ud558\uae30" } } }, "options": { "error": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_event_code": "\uc774\ubca4\ud2b8 \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_input_2262_off": "\ub044\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \uc785\ub825\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_input_2262_on": "\ucf1c\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \uc785\ub825\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_input_off_delay": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc\uc5d0 \ub300\ud55c \uc785\ub825\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "\uc790\ub3d9 \ucd94\uac00 \ud65c\uc131\ud654\ud558\uae30", + "debug": "\ub514\ubc84\uae45 \ud65c\uc131\ud654\ud558\uae30", + "device": "\uad6c\uc131\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30", + "event_code": "\ucd94\uac00\ud560 \uc774\ubca4\ud2b8 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "remove_device": "\uc0ad\uc81c\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30" + }, + "title": "Rfxtrx \uc635\uc158" + }, + "set_device_options": { + "data": { + "command_off": "\ub044\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \ub370\uc774\ud130 \ube44\ud2b8 \uac12", + "command_on": "\ucf1c\uae30 \uba85\ub839\uc5d0 \ub300\ud55c \ub370\uc774\ud130 \ube44\ud2b8 \uac12", + "data_bit": "\ub370\uc774\ud130 \ube44\ud2b8 \uc218", + "fire_event": "\uae30\uae30 \uc774\ubca4\ud2b8 \ud65c\uc131\ud654\ud558\uae30", + "off_delay": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc", + "off_delay_enabled": "\uc790\ub3d9 \uaebc\uc9c4 \uc0c1\ud0dc(Off Delay) \ud65c\uc131\ud654\ud558\uae30", + "replace_device": "\uad50\uccb4\ud560 \uae30\uae30 \uc120\ud0dd\ud558\uae30", + "signal_repetitions": "\uc2e0\ud638 \ubc18\ubcf5 \ud69f\uc218", + "venetian_blind_mode": "\ubca0\ub124\uc2dc\uc548 \ube14\ub77c\uc778\ub4dc \ubaa8\ub4dc" + }, + "title": "\uae30\uae30 \uc635\uc158 \uad6c\uc131\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 0b6e8997b18299..81b77f5ced55d9 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -49,6 +49,12 @@ } }, "set_device_options": { + "data": { + "data_bit": "Aantal databits", + "fire_event": "Schakel apparaatgebeurtenis in", + "off_delay": "Uitschakelvertraging", + "off_delay_enabled": "Schakel uitschakelvertraging in" + }, "title": "Configureer apparaatopties" } } diff --git a/homeassistant/components/ring/translations/he.json b/homeassistant/components/ring/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/ring/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/ring/translations/hu.json b/homeassistant/components/ring/translations/hu.json index fba6b944222a3b..8e6e94eb2d63eb 100644 --- a/homeassistant/components/ring/translations/hu.json +++ b/homeassistant/components/ring/translations/hu.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { - "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s.", - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "2fa": { diff --git a/homeassistant/components/ring/translations/id.json b/homeassistant/components/ring/translations/id.json new file mode 100644 index 00000000000000..198833299384fe --- /dev/null +++ b/homeassistant/components/ring/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kode autentikasi dua faktor" + }, + "title": "Autentikasi dua faktor" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Masuk dengan akun Ring" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/zh-Hant.json b/homeassistant/components/ring/translations/zh-Hant.json index 5eb31bfa7927d4..9f3c91e2a7c490 100644 --- a/homeassistant/components/ring/translations/zh-Hant.json +++ b/homeassistant/components/ring/translations/zh-Hant.json @@ -10,9 +10,9 @@ "step": { "2fa": { "data": { - "2fa": "\u96d9\u91cd\u9a57\u8b49\u78bc" + "2fa": "\u96d9\u91cd\u8a8d\u8b49\u78bc" }, - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "user": { "data": { diff --git a/homeassistant/components/risco/translations/hu.json b/homeassistant/components/risco/translations/hu.json index 3b2d79a34a77e2..ee57b9488dc073 100644 --- a/homeassistant/components/risco/translations/hu.json +++ b/homeassistant/components/risco/translations/hu.json @@ -2,6 +2,27 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "pin": "PIN-k\u00f3d", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + } } } } \ No newline at end of file diff --git a/homeassistant/components/risco/translations/id.json b/homeassistant/components/risco/translations/id.json new file mode 100644 index 00000000000000..eef1f00ffa6c65 --- /dev/null +++ b/homeassistant/components/risco/translations/id.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "pin": "Kode PIN", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "Diaktifkan untuk Keluar", + "armed_custom_bypass": "Diaktifkan Khusus", + "armed_home": "Diaktifkan untuk Di Rumah", + "armed_night": "Diaktifkan untuk Malam" + }, + "description": "Pilih status mana yang akan disetel pada Risco ketika mengaktifkan alarm Home Assistant", + "title": "Petakan status Home Assistant ke status Risco" + }, + "init": { + "data": { + "code_arm_required": "Membutuhkan Kode PIN untuk mengaktifkan", + "code_disarm_required": "Membutuhkan Kode PIN untuk menonaktifkan", + "scan_interval": "Seberapa sering untuk mengambil dari Risco (dalam detik)" + }, + "title": "Konfigurasi opsi" + }, + "risco_to_ha": { + "data": { + "A": "Grup A", + "B": "Grup B", + "C": "Grup C", + "D": "Grup D", + "arm": "Diaktifkan (Keluar)", + "partial_arm": "Diaktifkan Sebagian (Di Rumah)" + }, + "description": "Pilih status mana untuk masing-masing status yang akan dilaporkan oleh Home Assistant ke Risco", + "title": "Petakan status Risco ke status Home Assistant" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/ko.json b/homeassistant/components/risco/translations/ko.json index f3065256e7fa39..6ceaedbc6dcdd0 100644 --- a/homeassistant/components/risco/translations/ko.json +++ b/homeassistant/components/risco/translations/ko.json @@ -22,20 +22,21 @@ "step": { "ha_to_risco": { "data": { - "armed_away": "\uacbd\ube44\uc911(\uc678\ucd9c)", - "armed_custom_bypass": "\uacbd\ube44\uc911(\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", - "armed_home": "\uc9d1\uc548 \uacbd\ube44\uc911", - "armed_night": "\uc57c\uac04 \uacbd\ube44\uc911" + "armed_away": "\uacbd\ube44 \uc911(\uc678\ucd9c)", + "armed_custom_bypass": "\uacbd\ube44 \uc911 (\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", + "armed_home": "\uacbd\ube44 \uc911 (\uc7ac\uc2e4)", + "armed_night": "\uc57c\uac04 \uacbd\ube44 \uc911" }, - "description": "Home Assistant \uc54c\ub78c\uc744 \ud65c\uc131\ud654 \ud560 \ub54c Risco \uc54c\ub78c\uc758 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", - "title": "Home Assistant \uc0c1\ud0dc\ub97c Risco \uc0c1\ud0dc\ub85c \ub9e4\ud551" + "description": "Home Assistant \uc54c\ub78c\uc744 \uc124\uc815\ud560 \ub54c Risco \uc54c\ub78c\uc744 \uc124\uc815\ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uae30", + "title": "Home Assistant \uc0c1\ud0dc\ub97c Risco \uc0c1\ud0dc\uc5d0 \ub9e4\ud551\ud558\uae30" }, "init": { "data": { "code_arm_required": "\uc124\uc815\ud558\ub824\uba74 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4", "code_disarm_required": "\ud574\uc81c\ud558\ub824\uba74 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud569\ub2c8\ub2e4", "scan_interval": "Risco\ub97c \ud3f4\ub9c1\ud558\ub294 \ube48\ub3c4 (\ucd08)" - } + }, + "title": "\uc635\uc158 \uad6c\uc131\ud558\uae30" }, "risco_to_ha": { "data": { @@ -43,11 +44,11 @@ "B": "\uadf8\ub8f9 B", "C": "\uadf8\ub8f9 C", "D": "\uadf8\ub8f9 D", - "arm": "\uacbd\ube44\uc911(\uc678\ucd9c)", - "partial_arm": "\ubd80\ubd84 \uacbd\ube44 \uc124\uc815 (\uc7ac\uc2e4)" + "arm": "\uacbd\ube44 \uc911 (\uc678\ucd9c)", + "partial_arm": "\ubd80\ubd84 \uacbd\ube44 \uc911 (\uc7ac\uc2e4)" }, - "description": "Risco\uc5d0\uc11c\ubcf4\uace0\ud558\ub294 \ubaa8\ub4e0 \uc0c1\ud0dc\uc5d0 \ub300\ud574 Home Assistant \uc54c\ub78c\uc774 \ubcf4\uace0 \ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4.", - "title": "Risco \uc0c1\ud0dc\ub97c \ud648 \uc5b4\uc2dc\uc2a4\ud134\ud2b8 \uc0c1\ud0dc\uc5d0 \ub9e4\ud551" + "description": "Risco\uac00 \ubcf4\uace0\ud558\ub294 \ubaa8\ub4e0 \uc0c1\ud0dc\uc5d0 \ub300\ud574 Home Assistant \uc54c\ub78c\uc774 \ubcf4\uace0\ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uae30", + "title": "Risco \uc0c1\ud0dc\ub97c Home Assistant \uc0c1\ud0dc\uc5d0 \ub9e4\ud551\ud558\uae30" } } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/bg.json b/homeassistant/components/rituals_perfume_genie/translations/bg.json new file mode 100644 index 00000000000000..cef3726d759676 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/es.json b/homeassistant/components/rituals_perfume_genie/translations/es.json index bc74ecfd7ea20d..bdb1933eaf7176 100644 --- a/homeassistant/components/rituals_perfume_genie/translations/es.json +++ b/homeassistant/components/rituals_perfume_genie/translations/es.json @@ -1,7 +1,19 @@ { "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, "step": { "user": { + "data": { + "email": "email", + "password": "Contrase\u00f1a" + }, "title": "Con\u00e9ctese a su cuenta de Rituals" } } diff --git a/homeassistant/components/rituals_perfume_genie/translations/hu.json b/homeassistant/components/rituals_perfume_genie/translations/hu.json new file mode 100644 index 00000000000000..4ecaf2ba0d0b0d --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/hu.json @@ -0,0 +1,20 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/id.json b/homeassistant/components/rituals_perfume_genie/translations/id.json new file mode 100644 index 00000000000000..91d931005cf91f --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "title": "Hubungkan ke akun Ritual Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rituals_perfume_genie/translations/ko.json b/homeassistant/components/rituals_perfume_genie/translations/ko.json new file mode 100644 index 00000000000000..489e4f5b806475 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + }, + "title": "Rituals \uacc4\uc815\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/hu.json b/homeassistant/components/roku/translations/hu.json index 8b4e9f9d54d114..5485d9e00ce465 100644 --- a/homeassistant/components/roku/translations/hu.json +++ b/homeassistant/components/roku/translations/hu.json @@ -2,12 +2,26 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "unknown": "V\u00e1ratlan hiba" + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "data": { + "one": "Egy", + "other": "Egy\u00e9b" + }, + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a(z) {name}-t?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}-t?", + "title": "Roku" + }, "user": { "data": { "host": "Hoszt" diff --git a/homeassistant/components/roku/translations/id.json b/homeassistant/components/roku/translations/id.json new file mode 100644 index 00000000000000..0e60de9b61f790 --- /dev/null +++ b/homeassistant/components/roku/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Roku: {name}", + "step": { + "discovery_confirm": { + "description": "Ingin menyiapkan {name}?", + "title": "Roku" + }, + "ssdp_confirm": { + "description": "Ingin menyiapkan {name}?", + "title": "Roku" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Masukkan informasi Roku Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ko.json b/homeassistant/components/roku/translations/ko.json index 19f4c16785fb08..cb127234601533 100644 --- a/homeassistant/components/roku/translations/ko.json +++ b/homeassistant/components/roku/translations/ko.json @@ -10,8 +10,12 @@ }, "flow_title": "Roku: {name}", "step": { + "discovery_confirm": { + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Roku" + }, "ssdp_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "{name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Roku" }, "user": { diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json index d892d2c78d2f2e..daecee2f1dc4e7 100644 --- a/homeassistant/components/roku/translations/nl.json +++ b/homeassistant/components/roku/translations/nl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Roku-apparaat is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Kan geen verbinding maken" }, "flow_title": "Roku: {name}", "step": { @@ -15,7 +15,7 @@ "one": "Een", "other": "Ander" }, - "description": "Wilt u {naam} instellen?", + "description": "Wilt u {name} instellen?", "title": "Roku" }, "ssdp_confirm": { @@ -28,7 +28,7 @@ }, "user": { "data": { - "host": "Host- of IP-adres" + "host": "Host" }, "description": "Voer uw Roku-informatie in." } diff --git a/homeassistant/components/roomba/translations/bg.json b/homeassistant/components/roomba/translations/bg.json new file mode 100644 index 00000000000000..10f778472e6321 --- /dev/null +++ b/homeassistant/components/roomba/translations/bg.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "link_manual": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + }, + "title": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index b2fe68c876c482..d5c81db15ca3db 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -15,7 +15,7 @@ "host": "Amfitri\u00f3" }, "description": "Selecciona un/a Roomba o Braava.", - "title": "Connecta't al dispositiu autom\u00e0ticament" + "title": "Connexi\u00f3 autom\u00e0tica amb el dispositiu" }, "link": { "description": "Mant\u00e9 premut el bot\u00f3 d'inici a {name} fins que el dispositiu emeti un so (aproximadament dos segons).", diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 11c9c6e27fd442..ddbf5a10a788b2 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -59,4 +59,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/et.json b/homeassistant/components/roomba/translations/et.json index e038257c12dea6..5718e8b6a53f64 100644 --- a/homeassistant/components/roomba/translations/et.json +++ b/homeassistant/components/roomba/translations/et.json @@ -15,7 +15,7 @@ "host": "Host" }, "description": "Vali Roomba v\u00f5i Braava seade.", - "title": "\u00dchendu seadmega automaatselt" + "title": "\u00dchenda seadmega automaatselt" }, "link": { "description": "Vajuta ja hoia all seadme {name} nuppu Home kuni seade teeb piiksu (umbes kaks sekundit).", diff --git a/homeassistant/components/roomba/translations/he.json b/homeassistant/components/roomba/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/roomba/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/hu.json b/homeassistant/components/roomba/translations/hu.json index 357ca74746dfa5..8f7c2c97884ab7 100644 --- a/homeassistant/components/roomba/translations/hu.json +++ b/homeassistant/components/roomba/translations/hu.json @@ -1,10 +1,51 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "not_irobot_device": "A felfedezett eszk\u00f6z nem iRobot eszk\u00f6z" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "iRobot {name} ({host})", "step": { + "init": { + "data": { + "host": "Hoszt" + }, + "title": "Automatikus csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + }, + "link_manual": { + "data": { + "password": "Jelsz\u00f3" + }, + "title": "Jelsz\u00f3 megad\u00e1sa" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Hoszt" + }, + "title": "Manu\u00e1lis csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + }, "user": { "data": { + "blid": "BLID", + "delay": "K\u00e9sleltet\u00e9s", "host": "Hoszt", "password": "Jelsz\u00f3" + }, + "title": "Csatlakoz\u00e1s az eszk\u00f6zh\u00f6z" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Folyamatos", + "delay": "K\u00e9sleltet\u00e9s" } } } diff --git a/homeassistant/components/roomba/translations/id.json b/homeassistant/components/roomba/translations/id.json new file mode 100644 index 00000000000000..3afe75ae09da8e --- /dev/null +++ b/homeassistant/components/roomba/translations/id.json @@ -0,0 +1,62 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "not_irobot_device": "Perangkat yang ditemukan bukan perangkat iRobot" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "iRobot {name} ({host})", + "step": { + "init": { + "data": { + "host": "Host" + }, + "description": "Pilih Roomba atau Braava.", + "title": "Sambungkan secara otomatis ke perangkat" + }, + "link": { + "description": "Tekan dan tahan tombol Home pada {name} hingga perangkat mengeluarkan suara (sekitar dua detik).", + "title": "Ambil Kata Sandi" + }, + "link_manual": { + "data": { + "password": "Kata Sandi" + }, + "description": "Kata sandi tidak dapat diambil dari perangkat secara otomatis. Ikuti langkah-langkah yang diuraikan dalam dokumentasi di: {auth_help_url}", + "title": "Masukkan Kata Sandi" + }, + "manual": { + "data": { + "blid": "BLID", + "host": "Host" + }, + "description": "Tidak ada Roomba atau Braava yang ditemukan di jaringan Anda. BLID adalah bagian dari nama host perangkat setelah `iRobot-`. Ikuti langkah-langkah yang diuraikan dalam dokumentasi di: {auth_help_url}", + "title": "Hubungkan ke perangkat secara manual" + }, + "user": { + "data": { + "blid": "BLID", + "continuous": "Terus menerus", + "delay": "Tunda", + "host": "Host", + "password": "Kata Sandi" + }, + "description": "Saat ini proses mengambil BLID dan kata sandi merupakan proses manual. Iikuti langkah-langkah yang diuraikan dalam dokumentasi di: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Hubungkan ke perangkat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Terus menerus", + "delay": "Tunda" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/ko.json b/homeassistant/components/roomba/translations/ko.json index d9c661a20dd2a9..5066225100bb9d 100644 --- a/homeassistant/components/roomba/translations/ko.json +++ b/homeassistant/components/roomba/translations/ko.json @@ -2,26 +2,39 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "not_irobot_device": "\ubc1c\uacac\ub41c \uae30\uae30\ub294 \uc544\uc774\ub85c\ubd07 \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "\uc544\uc774\ub85c\ubd07: {name} ({host})", "step": { "init": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\ub8f8\ubc14 \ub610\ub294 \ube0c\ub77c\ubc14\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "\uae30\uae30\uc5d0 \uc790\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" + }, + "link": { + "description": "\uae30\uae30\uc5d0\uc11c \uc18c\ub9ac\uac00 \ub0a0 \ub54c\uae4c\uc9c0 {name}\uc758 \ud648 \ubc84\ud2bc\uc744 \uae38\uac8c \ub20c\ub7ec\uc8fc\uc138\uc694 (\uc57d 2\ucd08).", + "title": "\ube44\ubc00\ubc88\ud638 \uac00\uc838\uc624\uae30" }, "link_manual": { "data": { "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "\uae30\uae30\uc5d0\uc11c \ube44\ubc00\ubc88\ud638\ub97c \uc790\ub3d9\uc73c\ub85c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 {auth_help_url} \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694.", + "title": "\ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "manual": { "data": { + "blid": "BLID", "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ub8f8\ubc14 \ub610\ub294 \ube0c\ub77c\ubc14\uac00 \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. BLID\ub294 `iRobot-` \ub4a4\uc758 \uae30\uae30 \ud638\uc2a4\ud2b8 \uc774\ub984 \ubd80\ubd84\uc785\ub2c8\ub2e4. \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 {auth_help_url} \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694.", + "title": "\uae30\uae30\uc5d0 \uc218\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud558\uae30" }, "user": { "data": { @@ -31,7 +44,7 @@ "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "\ud604\uc7ac BLID \ubc0f \ube44\ubc00\ubc88\ud638\ub294 \uc218\ub3d9\uc73c\ub85c \uac00\uc838\uc640\uc57c\ud569\ub2c8\ub2e4. \ub2e4\uc74c \ubb38\uc11c\uc5d0 \uc124\uba85\ub41c \uc808\ucc28\ub97c \ub530\ub77c \uc124\uc815\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "description": "\ud604\uc7ac BLID \ubc0f \ube44\ubc00\ubc88\ud638\ub294 \uc218\ub3d9\uc73c\ub85c \uac00\uc838\uc640\uc57c\ud569\ub2c8\ub2e4. \ub2e4\uc74c \uad00\ub828 \ubb38\uc11c\uc5d0 \ub098\uc640 \uc788\ub294 \ub2e8\uacc4\ub97c \ub530\ub77c\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index 500aa4fbee6a46..5578a7710dba71 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Feeler beim verbannen" }, + "flow_title": "iRobot {name} ({host})", "step": { "init": { "data": { diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json index 0177adaea1ffe0..f26b28d22486ef 100644 --- a/homeassistant/components/roomba/translations/nl.json +++ b/homeassistant/components/roomba/translations/nl.json @@ -6,9 +6,9 @@ "not_irobot_device": "Het gevonden apparaat is geen iRobot-apparaat" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Kan geen verbinding maken" }, - "flow_title": "iRobot {naam} ({host})", + "flow_title": "iRobot {name} ({host})", "step": { "init": { "data": { @@ -18,7 +18,7 @@ "title": "Automatisch verbinding maken met het apparaat" }, "link": { - "description": "Houd de Home-knop op {naam} ingedrukt totdat het apparaat een geluid genereert (ongeveer twee seconden).", + "description": "Houd de Home-knop op {name} ingedrukt totdat het apparaat een geluid genereert (ongeveer twee seconden).", "title": "Wachtwoord opvragen" }, "link_manual": { @@ -32,14 +32,16 @@ "data": { "blid": "BLID", "host": "Host" - } + }, + "description": "Er is geen Roomba of Braava ontdekt op uw netwerk. De BLID is het gedeelte van de hostnaam van het apparaat na `iRobot-`. Volg de stappen die worden beschreven in de documentatie op: {auth_help_url}", + "title": "Handmatig verbinding maken met het apparaat" }, "user": { "data": { "blid": "BLID", "continuous": "Doorlopend", "delay": "Vertraging", - "host": "Hostnaam of IP-adres", + "host": "Host", "password": "Wachtwoord" }, "description": "Het ophalen van de BLID en het wachtwoord is momenteel een handmatig proces. Volg de stappen in de documentatie op: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", diff --git a/homeassistant/components/roon/translations/hu.json b/homeassistant/components/roon/translations/hu.json index 3b2d79a34a77e2..6ea0aa2b25c3ab 100644 --- a/homeassistant/components/roon/translations/hu.json +++ b/homeassistant/components/roon/translations/hu.json @@ -2,6 +2,18 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "duplicate_entry": "Ez a hoszt m\u00e1r konfigur\u00e1lva van.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/roon/translations/id.json b/homeassistant/components/roon/translations/id.json new file mode 100644 index 00000000000000..bfd70955ac86b8 --- /dev/null +++ b/homeassistant/components/roon/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "duplicate_entry": "Host ini telah ditambahkan.", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "link": { + "description": "Anda harus mengotorisasi Home Assistant di Roon. Setelah Anda mengeklik kirim, buka aplikasi Roon Core, buka Pengaturan dan aktifkan HomeAssistant pada tab Ekstensi.", + "title": "Otorisasi HomeAssistant di Roon" + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Tidak dapat menemukan server Roon, masukkan Nama Host atau IP Anda." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/ko.json b/homeassistant/components/roon/translations/ko.json index 7c051d49dc7046..b1e0fee4089211 100644 --- a/homeassistant/components/roon/translations/ko.json +++ b/homeassistant/components/roon/translations/ko.json @@ -10,14 +10,14 @@ }, "step": { "link": { - "description": "Roon\uc5d0\uc11c \ud648 \uc5b4\uc2dc\uc2a4\ud134\ud2b8\ub97c \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4. \uc81c\ucd9c\uc744 \ud074\ub9ad \ud55c \ud6c4 Roon Core \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \uc124\uc815\uc744 \uc5f4\uace0 \ud655\uc7a5 \ud0ed\uc5d0\uc11c HomeAssistant\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4.", - "title": "Roon\uc5d0\uc11c HomeAssistant \uc778\uc99d" + "description": "Roon\uc5d0\uc11c Home Assistant\ub97c \uc778\uc99d\ud574\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4. \ud655\uc778\uc744 \ud074\ub9ad\ud55c \ud6c4 Roon Core \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc73c\ub85c \uc774\ub3d9\ud558\uc5ec \uc124\uc815\uc744 \uc5f4\uace0 \ud655\uc7a5 \ud0ed\uc5d0\uc11c Home Assistant\ub97c \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694.", + "title": "Roon\uc5d0\uc11c HomeAssistant \uc778\uc99d\ud558\uae30" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uba85\uc774\ub098 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." + "description": "Roon \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/rpi_power/translations/de.json b/homeassistant/components/rpi_power/translations/de.json index 9f3851f0c2b304..1a00c87b985362 100644 --- a/homeassistant/components/rpi_power/translations/de.json +++ b/homeassistant/components/rpi_power/translations/de.json @@ -1,11 +1,12 @@ { "config": { "abort": { + "no_devices_found": "Die f\u00fcr diese Komponente ben\u00f6tigte Systemklasse konnte nicht gefunden werden. Stellen Sie sicher, dass Ihr Kernel aktuell ist und die Hardware unterst\u00fctzt wird", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "step": { "confirm": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } }, diff --git a/homeassistant/components/rpi_power/translations/hu.json b/homeassistant/components/rpi_power/translations/hu.json new file mode 100644 index 00000000000000..2d1c0811286072 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/hu.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "confirm": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, + "title": "Raspberry Pi Power Supply Checker" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/id.json b/homeassistant/components/rpi_power/translations/id.json new file mode 100644 index 00000000000000..f9fcfa6c97a626 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/id.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak dapat menemukan kelas sistem yang diperlukan untuk komponen ini, pastikan kernel Anda cukup terbaru dan perangkat kerasnya didukung", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + }, + "title": "Pemeriksa Catu Daya Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/ko.json b/homeassistant/components/rpi_power/translations/ko.json index 445c0c34e6811a..e8423b7d7339f1 100644 --- a/homeassistant/components/rpi_power/translations/ko.json +++ b/homeassistant/components/rpi_power/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\uc774 \uad6c\uc131 \uc694\uc18c\uc5d0 \ud544\uc694\ud55c \uc2dc\uc2a4\ud15c \ud074\ub798\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee4\ub110\uc774 \ucd5c\uc2e0\uc774\uace0 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc9c0\uc6d0\ub418\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "no_devices_found": "\uc774 \uad6c\uc131 \uc694\uc18c\uc5d0 \ud544\uc694\ud55c \uc2dc\uc2a4\ud15c \ud074\ub798\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee4\ub110\uc774 \ucd5c\uc2e0 \uc0c1\ud0dc\uc774\uace0 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc9c0\uc6d0\ub418\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/ruckus_unleashed/translations/he.json b/homeassistant/components/ruckus_unleashed/translations/he.json new file mode 100644 index 00000000000000..6ef580c7d8d482 --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/he.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4" + }, + "step": { + "user": { + "data": { + "host": "\u05de\u05d0\u05e8\u05d7", + "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/ruckus_unleashed/translations/hu.json b/homeassistant/components/ruckus_unleashed/translations/hu.json index c1a23478ac4d6a..0abcc301f0c854 100644 --- a/homeassistant/components/ruckus_unleashed/translations/hu.json +++ b/homeassistant/components/ruckus_unleashed/translations/hu.json @@ -1,13 +1,17 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "unknown": "V\u00e1ratlan hiba" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { "data": { - "host": "Gazdag\u00e9p", + "host": "Hoszt", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/ruckus_unleashed/translations/id.json b/homeassistant/components/ruckus_unleashed/translations/id.json new file mode 100644 index 00000000000000..ed8fde321061cf --- /dev/null +++ b/homeassistant/components/ruckus_unleashed/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index ca42aff331ad2e..5c517c78d69682 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Ez a Samsung TV m\u00e1r konfigur\u00e1lva van.", - "already_in_progress": "A Samsung TV konfigur\u00e1l\u00e1sa m\u00e1r folyamatban van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "auth_missing": "A Home Assistant nem jogosult csatlakozni ehhez a Samsung TV-hez. Ellen\u0151rizd a TV be\u00e1ll\u00edt\u00e1sait a Home Assistant enged\u00e9lyez\u00e9s\u00e9hez.", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "not_supported": "Ez a Samsung TV k\u00e9sz\u00fcl\u00e9k jelenleg nem t\u00e1mogatott." diff --git a/homeassistant/components/samsungtv/translations/id.json b/homeassistant/components/samsungtv/translations/id.json new file mode 100644 index 00000000000000..7d0f5982a657c5 --- /dev/null +++ b/homeassistant/components/samsungtv/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "auth_missing": "Home Assistant tidak diizinkan untuk tersambung ke TV Samsung ini. Periksa setelan TV Anda untuk mengotorisasi Home Assistant.", + "cannot_connect": "Gagal terhubung", + "not_supported": "Perangkat TV Samsung ini saat ini tidak didukung." + }, + "flow_title": "TV Samsung: {model}", + "step": { + "confirm": { + "description": "Apakah Anda ingin menyiapkan TV Samsung {model}? Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi. Konfigurasi manual untuk TV ini akan ditimpa.", + "title": "TV Samsung" + }, + "user": { + "data": { + "host": "Host", + "name": "Nama" + }, + "description": "Masukkan informasi TV Samsung Anda. Jika Anda belum pernah menyambungkan Home Assistant sebelumnya, Anda akan melihat dialog di TV yang meminta otorisasi." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 14e35a7ff2e850..5686c684e1f15d 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -3,14 +3,14 @@ "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", - "auth_missing": "Home Assistant \uac00 \ud574\ub2f9 \uc0bc\uc131 TV \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant \ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694.", + "auth_missing": "Home Assistant\uac00 \ud574\ub2f9 \uc0bc\uc131 TV\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc788\ub294 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. TV \uc124\uc815\uc744 \ud655\uc778\ud558\uc5ec Home Assistant\ub97c \uc2b9\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "not_supported": "\uc774 \uc0bc\uc131 TV \ubaa8\ub378\uc740 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "flow_title": "\uc0bc\uc131 TV: {model}", "step": { "confirm": { - "description": "\uc0bc\uc131 TV {model} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant \ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV \uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV \uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc744 \ub36e\uc5b4\uc501\ub2c8\ub2e4.", + "description": "{model} \uc0bc\uc131 TV\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? Home Assistant\ub97c \uc5f0\uacb0\ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4. \uc774 TV\uc758 \uc218\ub3d9\uc73c\ub85c \uad6c\uc131\ub41c \ub0b4\uc6a9\uc740 \ub36e\uc5b4\uc50c\uc6cc\uc9d1\ub2c8\ub2e4.", "title": "\uc0bc\uc131 TV" }, "user": { diff --git a/homeassistant/components/scene/translations/id.json b/homeassistant/components/scene/translations/id.json index 65f2adf73253bc..827c0c81f3801f 100644 --- a/homeassistant/components/scene/translations/id.json +++ b/homeassistant/components/scene/translations/id.json @@ -1,3 +1,3 @@ { - "title": "Adegan" + "title": "Scene" } \ No newline at end of file diff --git a/homeassistant/components/script/translations/id.json b/homeassistant/components/script/translations/id.json index 8b23be94861f1e..cac38736ea7d1e 100644 --- a/homeassistant/components/script/translations/id.json +++ b/homeassistant/components/script/translations/id.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Skrip" diff --git a/homeassistant/components/season/translations/sensor.id.json b/homeassistant/components/season/translations/sensor.id.json new file mode 100644 index 00000000000000..fd28dca59012c7 --- /dev/null +++ b/homeassistant/components/season/translations/sensor.id.json @@ -0,0 +1,16 @@ +{ + "state": { + "season__season": { + "autumn": "Musim gugur", + "spring": "Musim semi", + "summer": "Musim panas", + "winter": "Musim dingin" + }, + "season__season__": { + "autumn": "Musim gugur", + "spring": "Musim semi", + "summer": "Musim panas", + "winter": "Musim dingin" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/he.json b/homeassistant/components/sense/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/sense/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/hu.json b/homeassistant/components/sense/translations/hu.json index 0085d9ea9c4b7d..4ecaf2ba0d0b0d 100644 --- a/homeassistant/components/sense/translations/hu.json +++ b/homeassistant/components/sense/translations/hu.json @@ -1,5 +1,13 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/sense/translations/id.json b/homeassistant/components/sense/translations/id.json new file mode 100644 index 00000000000000..8d0d996e510279 --- /dev/null +++ b/homeassistant/components/sense/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "title": "Hubungkan ke Sense Energy Monitor Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/ko.json b/homeassistant/components/sense/translations/ko.json index 517ad7af17d2cb..269d8a76feaa6e 100644 --- a/homeassistant/components/sense/translations/ko.json +++ b/homeassistant/components/sense/translations/ko.json @@ -14,7 +14,7 @@ "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "Sense Energy Monitor \uc5d0 \uc5f0\uacb0\ud558\uae30" + "title": "Sense Energy Monitor\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/sense/translations/nl.json b/homeassistant/components/sense/translations/nl.json index ee9e61b5a38b35..df64e83da16028 100644 --- a/homeassistant/components/sense/translations/nl.json +++ b/homeassistant/components/sense/translations/nl.json @@ -4,14 +4,14 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "email": "E-mailadres", + "email": "E-mail", "password": "Wachtwoord" }, "title": "Maak verbinding met uw Sense Energy Monitor" diff --git a/homeassistant/components/sensor/translations/ca.json b/homeassistant/components/sensor/translations/ca.json index b351aed3049c03..e0cfb5faa50334 100644 --- a/homeassistant/components/sensor/translations/ca.json +++ b/homeassistant/components/sensor/translations/ca.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Nivell de bateria actual de {entity_name}", + "is_carbon_dioxide": "Concentraci\u00f3 actual de di\u00f2xid de carboni de {entity_name}", + "is_carbon_monoxide": "Concentraci\u00f3 actual de mon\u00f2xid de carboni de {entity_name}", "is_current": "Intensitat actual de {entity_name}", "is_energy": "Energia actual de {entity_name}", "is_humidity": "Humitat actual de {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "Canvia el nivell de bateria de {entity_name}", + "carbon_dioxide": "Canvia la concentraci\u00f3 de di\u00f2xid de carboni de {entity_name}", + "carbon_monoxide": "Canvia la concentraci\u00f3 de mon\u00f2xid de carboni de {entity_name}", "current": "Canvia la intensitat de {entity_name}", "energy": "Canvia l'energia de {entity_name}", "humidity": "Canvia la humitat de {entity_name}", diff --git a/homeassistant/components/sensor/translations/en.json b/homeassistant/components/sensor/translations/en.json index ae0c32df57445c..e32ae845c1c7bd 100644 --- a/homeassistant/components/sensor/translations/en.json +++ b/homeassistant/components/sensor/translations/en.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Current {entity_name} battery level", + "is_carbon_dioxide": "Current {entity_name} carbon dioxide concentration level", + "is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level", "is_current": "Current {entity_name} current", "is_energy": "Current {entity_name} energy", "is_humidity": "Current {entity_name} humidity", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} battery level changes", + "carbon_dioxide": "{entity_name} carbon dioxide concentration changes", + "carbon_monoxide": "{entity_name} carbon monoxide concentration changes", "current": "{entity_name} current changes", "energy": "{entity_name} energy changes", "humidity": "{entity_name} humidity changes", diff --git a/homeassistant/components/sensor/translations/et.json b/homeassistant/components/sensor/translations/et.json index 450f5b60537c93..55b1fa48a8f0b9 100644 --- a/homeassistant/components/sensor/translations/et.json +++ b/homeassistant/components/sensor/translations/et.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Praegune {entity_name} aku tase", + "is_carbon_dioxide": "{entity_name} praegune s\u00fcsihappegaasi tase", + "is_carbon_monoxide": "{entity_name} praegune vingugaasi tase", "is_current": "Praegune {entity_name} voolutugevus", "is_energy": "Praegune {entity_name} v\u00f5imsus", "is_humidity": "Praegune {entity_name} niiskus", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} aku tase muutub", + "carbon_dioxide": "{entity_name} s\u00fcsihappegaasi tase muutus", + "carbon_monoxide": "{entity_name} vingugaasi tase muutus", "current": "{entity_name} voolutugevus muutub", "energy": "{entity_name} v\u00f5imsus muutub", "humidity": "{entity_name} niiskus muutub", diff --git a/homeassistant/components/sensor/translations/fr.json b/homeassistant/components/sensor/translations/fr.json index 4705d28b5c3219..ef88b7aceccb15 100644 --- a/homeassistant/components/sensor/translations/fr.json +++ b/homeassistant/components/sensor/translations/fr.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Niveau de la batterie de {entity_name}", + "is_carbon_dioxide": "Niveau actuel de concentration de dioxyde de carbone {entity_name}", + "is_carbon_monoxide": "Niveau actuel de concentration de monoxyde de carbone {entity_name}", "is_current": "Courant actuel pour {entity_name}", "is_energy": "\u00c9nergie actuelle pour {entity_name}", "is_humidity": "Humidit\u00e9 de {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} modification du niveau de batterie", + "carbon_dioxide": "{entity_name} changements de concentration de dioxyde de carbone", + "carbon_monoxide": "{entity_name} changements de concentration de monoxyde de carbone", "current": "{entity_name} changement de courant", "energy": "{entity_name} changement d'\u00e9nergie", "humidity": "{entity_name} modification de l'humidit\u00e9", diff --git a/homeassistant/components/sensor/translations/hu.json b/homeassistant/components/sensor/translations/hu.json index 7be96984451bc6..9c49de27a7bba7 100644 --- a/homeassistant/components/sensor/translations/hu.json +++ b/homeassistant/components/sensor/translations/hu.json @@ -20,7 +20,8 @@ "signal_strength": "{entity_name} jeler\u0151ss\u00e9ge v\u00e1ltozik", "temperature": "{entity_name} h\u0151m\u00e9rs\u00e9klete v\u00e1ltozik", "timestamp": "{entity_name} id\u0151b\u00e9lyege v\u00e1ltozik", - "value": "{entity_name} \u00e9rt\u00e9ke v\u00e1ltozik" + "value": "{entity_name} \u00e9rt\u00e9ke v\u00e1ltozik", + "voltage": "{entity_name} fesz\u00fclts\u00e9ge v\u00e1ltozik" } }, "state": { diff --git a/homeassistant/components/sensor/translations/id.json b/homeassistant/components/sensor/translations/id.json index e2d0cdb057dfd5..d43c2304428ebc 100644 --- a/homeassistant/components/sensor/translations/id.json +++ b/homeassistant/components/sensor/translations/id.json @@ -1,8 +1,44 @@ { + "device_automation": { + "condition_type": { + "is_battery_level": "Level baterai {entity_name} saat ini", + "is_carbon_dioxide": "Level konsentasi karbondioksida {entity_name} saat ini", + "is_carbon_monoxide": "Level konsentasi karbonmonoksida {entity_name} saat ini", + "is_current": "Arus {entity_name} saat ini", + "is_energy": "Energi {entity_name} saat ini", + "is_humidity": "Kelembaban {entity_name} saat ini", + "is_illuminance": "Pencahayaan {entity_name} saat ini", + "is_power": "Daya {entity_name} saat ini", + "is_power_factor": "Faktor daya {entity_name} saat ini", + "is_pressure": "Tekanan {entity_name} saat ini", + "is_signal_strength": "Kekuatan sinyal {entity_name} saat ini", + "is_temperature": "Suhu {entity_name} saat ini", + "is_timestamp": "Stempel waktu {entity_name} saat ini", + "is_value": "Nilai {entity_name} saat ini", + "is_voltage": "Tegangan {entity_name} saat ini" + }, + "trigger_type": { + "battery_level": "Perubahan level baterai {entity_name}", + "carbon_dioxide": "Perubahan konsentrasi karbondioksida {entity_name}", + "carbon_monoxide": "Perubahan konsentrasi karbonmonoksida {entity_name}", + "current": "Perubahan arus {entity_name}", + "energy": "Perubahan energi {entity_name}", + "humidity": "Perubahan kelembaban {entity_name}", + "illuminance": "Perubahan pencahayaan {entity_name}", + "power": "Perubahan daya {entity_name}", + "power_factor": "Perubahan faktor daya {entity_name}", + "pressure": "Perubahan tekanan {entity_name}", + "signal_strength": "Perubahan kekuatan sinyal {entity_name}", + "temperature": "Perubahan suhu {entity_name}", + "timestamp": "Perubahan stempel waktu {entity_name}", + "value": "Perubahan nilai {entity_name}", + "voltage": "Perubahan tegangan {entity_name}" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Sensor" diff --git a/homeassistant/components/sensor/translations/it.json b/homeassistant/components/sensor/translations/it.json index 84a8b2773a50e5..c8b4a2f9b9cd17 100644 --- a/homeassistant/components/sensor/translations/it.json +++ b/homeassistant/components/sensor/translations/it.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Livello della batteria attuale di {entity_name}", + "is_carbon_dioxide": "Livello di concentrazione di anidride carbonica attuale in {entity_name}", + "is_carbon_monoxide": "Livello attuale di concentrazione di monossido di carbonio in {entity_name}", "is_current": "Corrente attuale di {entity_name}", "is_energy": "Energia attuale di {entity_name}", "is_humidity": "Umidit\u00e0 attuale di {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "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}", "humidity": "variazioni di umidit\u00e0 di {entity_name} ", diff --git a/homeassistant/components/sensor/translations/ko.json b/homeassistant/components/sensor/translations/ko.json index 92fcd5d37a2673..d8e99874822c50 100644 --- a/homeassistant/components/sensor/translations/ko.json +++ b/homeassistant/components/sensor/translations/ko.json @@ -1,26 +1,38 @@ { "device_automation": { "condition_type": { - "is_battery_level": "\ud604\uc7ac {entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 ~ \uc774\uba74", - "is_humidity": "\ud604\uc7ac {entity_name} \uc2b5\ub3c4\uac00 ~ \uc774\uba74", - "is_illuminance": "\ud604\uc7ac {entity_name} \uc870\ub3c4\uac00 ~ \uc774\uba74", - "is_power": "\ud604\uc7ac {entity_name} \uc18c\ube44 \uc804\ub825\uc774 ~ \uc774\uba74", - "is_pressure": "\ud604\uc7ac {entity_name} \uc555\ub825\uc774 ~ \uc774\uba74", - "is_signal_strength": "\ud604\uc7ac {entity_name} \uc2e0\ud638 \uac15\ub3c4\uac00 ~ \uc774\uba74", - "is_temperature": "\ud604\uc7ac {entity_name} \uc628\ub3c4\uac00 ~ \uc774\uba74", - "is_timestamp": "\ud604\uc7ac {entity_name} \uc2dc\uac01\uc774 ~ \uc774\uba74", - "is_value": "\ud604\uc7ac {entity_name} \uac12\uc774 ~ \uc774\uba74" + "is_battery_level": "\ud604\uc7ac {entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 ~ \uc774\uba74", + "is_carbon_dioxide": "\ud604\uc7ac {entity_name}\uc758 \uc774\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4 \uc218\uc900\uc774 ~ \uc774\uba74", + "is_carbon_monoxide": "\ud604\uc7ac {entity_name}\uc758 \uc77c\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4 \uc218\uc900\uc774 ~ \uc774\uba74", + "is_current": "\ud604\uc7ac {entity_name}\uc758 \uc804\ub958\uac00 ~ \uc774\uba74", + "is_energy": "\ud604\uc7ac {entity_name}\uc758 \uc5d0\ub108\uc9c0\uac00 ~ \uc774\uba74", + "is_humidity": "\ud604\uc7ac {entity_name}\uc758 \uc2b5\ub3c4\uac00 ~ \uc774\uba74", + "is_illuminance": "\ud604\uc7ac {entity_name}\uc758 \uc870\ub3c4\uac00 ~ \uc774\uba74", + "is_power": "\ud604\uc7ac {entity_name}\uc758 \uc18c\ube44 \uc804\ub825\uc774 ~ \uc774\uba74", + "is_power_factor": "\ud604\uc7ac {entity_name}\uc758 \uc5ed\ub960\uc774 ~ \uc774\uba74", + "is_pressure": "\ud604\uc7ac {entity_name}\uc758 \uc555\ub825\uc774 ~ \uc774\uba74", + "is_signal_strength": "\ud604\uc7ac {entity_name}\uc758 \uc2e0\ud638 \uac15\ub3c4\uac00 ~ \uc774\uba74", + "is_temperature": "\ud604\uc7ac {entity_name}\uc758 \uc628\ub3c4\uac00 ~ \uc774\uba74", + "is_timestamp": "\ud604\uc7ac {entity_name}\uc758 \uc2dc\uac01\uc774 ~ \uc774\uba74", + "is_value": "\ud604\uc7ac {entity_name}\uc758 \uac12\uc774 ~ \uc774\uba74", + "is_voltage": "\ud604\uc7ac {entity_name}\uc758 \uc804\uc555\uc774 ~ \uc774\uba74" }, "trigger_type": { - "battery_level": "{entity_name} \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubc14\ub014 \ub54c", - "humidity": "{entity_name} \uc2b5\ub3c4\uac00 \ubc14\ub014 \ub54c", - "illuminance": "{entity_name} \uc870\ub3c4\uac00 \ubc14\ub014 \ub54c", - "power": "{entity_name} \uc18c\ube44 \uc804\ub825\uc774 \ubc14\ub014 \ub54c", - "pressure": "{entity_name} \uc555\ub825\uc774 \ubc14\ub014 \ub54c", - "signal_strength": "{entity_name} \uc2e0\ud638 \uac15\ub3c4\uac00 \ubc14\ub014 \ub54c", - "temperature": "{entity_name} \uc628\ub3c4\uac00 \ubc14\ub014 \ub54c", - "timestamp": "{entity_name} \uc2dc\uac01\uc774 \ubc14\ub014 \ub54c", - "value": "{entity_name} \uac12\uc774 \ubc14\ub014 \ub54c" + "battery_level": "{entity_name}\uc758 \ubc30\ud130\ub9ac \uc794\ub7c9\uc774 \ubcc0\ud560 \ub54c", + "carbon_dioxide": "{entity_name}\uc758 \uc774\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "carbon_monoxide": "{entity_name}\uc758 \uc77c\uc0b0\ud654\ud0c4\uc18c \ub18d\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "current": "{entity_name}\uc758 \uc804\ub958\uac00 \ubcc0\ud560 \ub54c", + "energy": "{entity_name}\uc758 \uc5d0\ub108\uc9c0\uac00 \ubcc0\ud560 \ub54c", + "humidity": "{entity_name}\uc758 \uc2b5\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "illuminance": "{entity_name}\uc758 \uc870\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "power": "{entity_name}\uc758 \uc18c\ube44 \uc804\ub825\uc774 \ubcc0\ud560 \ub54c", + "power_factor": "{entity_name}\uc758 \uc5ed\ub960\uc774 \ubcc0\ud560 \ub54c", + "pressure": "{entity_name}\uc758 \uc555\ub825\uc774 \ubcc0\ud560 \ub54c", + "signal_strength": "{entity_name}\uc758 \uc2e0\ud638 \uac15\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "temperature": "{entity_name}\uc758 \uc628\ub3c4\uac00 \ubcc0\ud560 \ub54c", + "timestamp": "{entity_name}\uc758 \uc2dc\uac01\uc774 \ubcc0\ud560 \ub54c", + "value": "{entity_name}\uc758 \uac12\uc774 \ubcc0\ud560 \ub54c", + "voltage": "{entity_name}\uc758 \uc804\uc555\uc774 \ubcc0\ud560 \ub54c" } }, "state": { diff --git a/homeassistant/components/sensor/translations/nl.json b/homeassistant/components/sensor/translations/nl.json index 869599296d583f..94eb0374adfe6a 100644 --- a/homeassistant/components/sensor/translations/nl.json +++ b/homeassistant/components/sensor/translations/nl.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Huidige batterijniveau {entity_name}", + "is_carbon_dioxide": "Huidig niveau {entity_name} kooldioxideconcentratie", + "is_carbon_monoxide": "Huidig niveau {entity_name} koolmonoxideconcentratie", "is_current": "Huidige {entity_name} stroom", "is_energy": "Huidige {entity_name} energie", "is_humidity": "Huidige {entity_name} vochtigheidsgraad", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} batterijniveau gewijzigd", + "carbon_dioxide": "{entity_name} kooldioxideconcentratie gewijzigd", + "carbon_monoxide": "{entity_name} koolmonoxideconcentratie gewijzigd", "current": "{entity_name} huidige wijzigingen", "energy": "{entity_name} energieveranderingen", "humidity": "{entity_name} vochtigheidsgraad gewijzigd", diff --git a/homeassistant/components/sensor/translations/no.json b/homeassistant/components/sensor/translations/no.json index b3e8dc199f6ee4..3662356d15ecfe 100644 --- a/homeassistant/components/sensor/translations/no.json +++ b/homeassistant/components/sensor/translations/no.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Gjeldende {entity_name} batteriniv\u00e5", + "is_carbon_dioxide": "Gjeldende {entity_name} karbondioksidkonsentrasjonsniv\u00e5", + "is_carbon_monoxide": "Gjeldende {entity_name} karbonmonoksid konsentrasjonsniv\u00e5", "is_current": "Gjeldende {entity_name} str\u00f8m", "is_energy": "Gjeldende {entity_name} effekt", "is_humidity": "Gjeldende {entity_name} fuktighet", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} batteriniv\u00e5 endres", + "carbon_dioxide": "{entity_name} endringer i konsentrasjonen av karbondioksid", + "carbon_monoxide": "{entity_name} endringer i konsentrasjonen av karbonmonoksid", "current": "{entity_name} gjeldende endringer", "energy": "{entity_name} effektendringer", "humidity": "{entity_name} fuktighets endringer", diff --git a/homeassistant/components/sensor/translations/pl.json b/homeassistant/components/sensor/translations/pl.json index a8fb604cc530cd..a2baa380174467 100644 --- a/homeassistant/components/sensor/translations/pl.json +++ b/homeassistant/components/sensor/translations/pl.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "obecny poziom na\u0142adowania baterii {entity_name}", + "is_carbon_dioxide": "Bie\u017c\u0105cy poziom st\u0119\u017cenia dwutlenku w\u0119gla w {entity_name}", + "is_carbon_monoxide": "Bie\u017c\u0105cy poziom st\u0119\u017cenia tlenku w\u0119gla w {entity_name}", "is_current": "obecne nat\u0119\u017cenie pr\u0105du {entity_name}", "is_energy": "obecna energia {entity_name}", "is_humidity": "obecna wilgotno\u015b\u0107 {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "zmieni si\u0119 poziom baterii {entity_name}", + "carbon_dioxide": "Zmiana st\u0119\u017cenie dwutlenku w\u0119gla w {entity_name}", + "carbon_monoxide": "Zmiana st\u0119\u017cenia tlenku w\u0119gla w {entity_name}", "current": "zmieni si\u0119 nat\u0119\u017cenie pr\u0105du w {entity_name}", "energy": "zmieni si\u0119 energia {entity_name}", "humidity": "zmieni si\u0119 wilgotno\u015b\u0107 {entity_name}", diff --git a/homeassistant/components/sensor/translations/ru.json b/homeassistant/components/sensor/translations/ru.json index ae84c843bc3f22..ae0a0997dd6721 100644 --- a/homeassistant/components/sensor/translations/ru.json +++ b/homeassistant/components/sensor/translations/ru.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", + "is_carbon_dioxide": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0443\u0440\u043e\u0432\u043d\u044f \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0433\u043b\u0435\u043a\u0438\u0441\u043b\u043e\u0433\u043e \u0433\u0430\u0437\u0430", + "is_carbon_monoxide": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0443\u0440\u043e\u0432\u043d\u044f \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0433\u0430\u0440\u043d\u043e\u0433\u043e \u0433\u0430\u0437\u0430", "is_current": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u043b\u044b \u0442\u043e\u043a\u0430", "is_energy": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u0438", "is_humidity": "{entity_name} \u0438\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", + "carbon_dioxide": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", + "carbon_monoxide": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", "current": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0441\u0438\u043b\u044b \u0442\u043e\u043a\u0430", "energy": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043c\u043e\u0449\u043d\u043e\u0441\u0442\u0438", "humidity": "{entity_name} \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435", diff --git a/homeassistant/components/sensor/translations/zh-Hant.json b/homeassistant/components/sensor/translations/zh-Hant.json index 56350e501ef585..ff84d8b2790a40 100644 --- a/homeassistant/components/sensor/translations/zh-Hant.json +++ b/homeassistant/components/sensor/translations/zh-Hant.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "\u76ee\u524d{entity_name}\u96fb\u91cf", + "is_carbon_dioxide": "\u76ee\u524d {entity_name} \u4e8c\u6c27\u5316\u78b3\u6fc3\u5ea6\u72c0\u614b", + "is_carbon_monoxide": "\u76ee\u524d {entity_name} \u4e00\u6c27\u5316\u78b3\u6fc3\u5ea6\u72c0\u614b", "is_current": "\u76ee\u524d{entity_name}\u96fb\u6d41", "is_energy": "\u76ee\u524d{entity_name}\u96fb\u529b", "is_humidity": "\u76ee\u524d{entity_name}\u6fd5\u5ea6", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "{entity_name}\u96fb\u91cf\u8b8a\u66f4", + "carbon_dioxide": "{entity_name} \u4e8c\u6c27\u5316\u78b3\u6fc3\u5ea6\u8b8a\u5316", + "carbon_monoxide": "{entity_name} \u4e00\u6c27\u5316\u78b3\u6fc3\u5ea6\u8b8a\u5316", "current": "\u76ee\u524d{entity_name}\u96fb\u6d41\u8b8a\u66f4", "energy": "\u76ee\u524d{entity_name}\u96fb\u529b\u8b8a\u66f4", "humidity": "{entity_name}\u6fd5\u5ea6\u8b8a\u66f4", diff --git a/homeassistant/components/sentry/translations/hu.json b/homeassistant/components/sentry/translations/hu.json index 64ee672a02f91b..055c881717724e 100644 --- a/homeassistant/components/sentry/translations/hu.json +++ b/homeassistant/components/sentry/translations/hu.json @@ -1,8 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "error": { "bad_dsn": "\u00c9rv\u00e9nytelen DSN", - "unknown": "V\u00e1ratlan hiba" + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { "user": { diff --git a/homeassistant/components/sentry/translations/id.json b/homeassistant/components/sentry/translations/id.json new file mode 100644 index 00000000000000..fbd94fa656505e --- /dev/null +++ b/homeassistant/components/sentry/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "bad_dsn": "DSN tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "dsn": "DSN" + }, + "description": "Masukkan DSN Sentry Anda", + "title": "Sentry" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Nama opsional untuk lingkungan.", + "event_custom_components": "Kirim event dari komponen khusus", + "event_handled": "Kirim event yang ditangani", + "event_third_party_packages": "Kirim event dari paket pihak ketiga", + "tracing": "Aktifkan pelacakan kinerja", + "tracing_sample_rate": "Laju sampel pelacakan; antara 0,0 dan 1,0 (1,0 = 100%)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/ko.json b/homeassistant/components/sentry/translations/ko.json index 6c00ffea2eff2e..92e26b30c42e93 100644 --- a/homeassistant/components/sentry/translations/ko.json +++ b/homeassistant/components/sentry/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "bad_dsn": "DSN \uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -9,6 +9,9 @@ }, "step": { "user": { + "data": { + "dsn": "DSN" + }, "description": "Sentry DSN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "Sentry" } @@ -18,14 +21,14 @@ "step": { "init": { "data": { - "environment": "\ud658\uacbd\uc758 \uc120\ud0dd\uc801 \uba85\uce6d", + "environment": "\ud658\uacbd\uc5d0 \ub300\ud55c \uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d)", "event_custom_components": "\uc0ac\uc6a9\uc790 \uc9c0\uc815 \uad6c\uc131 \uc694\uc18c\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", "event_handled": "\ucc98\ub9ac\ub41c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", - "event_third_party_packages": "\uc368\ub4dc\ud30c\ud2f0 \ud328\ud0a4\uc9c0\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", - "logging_event_level": "Log level Sentry\ub294 \ub2e4\uc74c\uc5d0 \ub300\ud55c \uc774\ubca4\ud2b8\ub97c \ub4f1\ub85d\ud569\ub2c8\ub2e4.", - "logging_level": "Log level sentry\ub294 \ub2e4\uc74c\uc5d0 \ub300\ud55c \ub85c\uadf8\ub97c \ube0c\ub808\ub4dc \ud06c\ub7fc\uc73c\ub85c \uae30\ub85d\ud569\ub2c8\ub2e4.", - "tracing": "\uc131\ub2a5 \ucd94\uc801 \ud65c\uc131\ud654", - "tracing_sample_rate": "\uc0d8\ud50c\ub9c1 \uc18d\ub3c4 \ucd94\uc801; 0.0\uc5d0\uc11c 1.0 \uc0ac\uc774 (1.0 = 100 %)" + "event_third_party_packages": "\uc11c\ub4dc \ud30c\ud2f0 \ud328\ud0a4\uc9c0\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", + "logging_event_level": "\ub85c\uadf8 \ub808\ubca8 Sentry\ub294 \ub2e4\uc74c \ub85c\uadf8 \uc218\uc900\uc5d0 \ub300\ud55c \uc774\ubca4\ud2b8\ub97c \ub4f1\ub85d\ud569\ub2c8\ub2e4", + "logging_level": "\ub85c\uadf8 \ub808\ubca8 Sentry\ub294 \ub2e4\uc74c \ub85c\uadf8 \uc218\uc900\uc5d0 \ub300\ud55c \ub85c\uadf8\ub97c \ube0c\ub808\ub4dc\ud06c\ub7fc\uc73c\ub85c \uae30\ub85d\ud569\ub2c8\ub2e4", + "tracing": "\uc131\ub2a5 \ucd94\uc801 \ud65c\uc131\ud654\ud558\uae30", + "tracing_sample_rate": "\ucd94\uc801 \uc0d8\ud50c \uc18d\ub3c4; 0.0\uc5d0\uc11c 1.0 \uc0ac\uc774 (1.0 = 100%)" } } } diff --git a/homeassistant/components/sharkiq/translations/hu.json b/homeassistant/components/sharkiq/translations/hu.json new file mode 100644 index 00000000000000..b765ad68a3ff43 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "reauth": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/id.json b/homeassistant/components/sharkiq/translations/id.json new file mode 100644 index 00000000000000..e3d8b4a8ed24c5 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "reauth_successful": "Autentikasi ulang berhasil", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "reauth": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/bg.json b/homeassistant/components/shelly/translations/bg.json new file mode 100644 index 00000000000000..c856929a5e1f4a --- /dev/null +++ b/homeassistant/components/shelly/translations/bg.json @@ -0,0 +1,10 @@ +{ + "device_automation": { + "trigger_subtype": { + "button": "\u0411\u0443\u0442\u043e\u043d", + "button1": "\u041f\u044a\u0440\u0432\u0438 \u0431\u0443\u0442\u043e\u043d", + "button2": "\u0412\u0442\u043e\u0440\u0438 \u0431\u0443\u0442\u043e\u043d", + "button3": "\u0422\u0440\u0435\u0442\u0438 \u0431\u0443\u0442\u043e\u043d" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/de.json b/homeassistant/components/shelly/translations/de.json index 4764936a41bd26..9d78d362c99c75 100644 --- a/homeassistant/components/shelly/translations/de.json +++ b/homeassistant/components/shelly/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "unsupported_firmware": "Das Ger\u00e4t verwendet eine nicht unterst\u00fctzte Firmware-Version." }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", @@ -23,5 +24,21 @@ "description": "Vor der Einrichtung m\u00fcssen batteriebetriebene Ger\u00e4te durch Dr\u00fccken der Taste am Ger\u00e4t aufgeweckt werden." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "Taste", + "button1": "Erste Taste", + "button2": "Zweite Taste", + "button3": "Dritte Taste" + }, + "trigger_type": { + "double": "{subtype} zweifach bet\u00e4tigt", + "long": "{subtype} gehalten", + "long_single": "{subtype} gehalten und dann einfach bet\u00e4tigt", + "single": "{subtype} einfach bet\u00e4tigt", + "single_long": "{subtype} einfach bet\u00e4tigt und dann gehalten", + "triple": "{subtype} dreifach bet\u00e4tigt" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/hu.json b/homeassistant/components/shelly/translations/hu.json index 3b2d79a34a77e2..2c8f468aaed7f8 100644 --- a/homeassistant/components/shelly/translations/hu.json +++ b/homeassistant/components/shelly/translations/hu.json @@ -1,7 +1,44 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "unsupported_firmware": "Az eszk\u00f6z nem t\u00e1mogatott firmware verzi\u00f3t haszn\u00e1l." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "{name}", + "step": { + "credentials": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "user": { + "data": { + "host": "Hoszt" + }, + "description": "A be\u00e1ll\u00edt\u00e1s el\u0151tt az akkumul\u00e1toros eszk\u00f6z\u00f6ket fel kell \u00e9breszteni, most egy rajta l\u00e9v\u0151 gombbal fel\u00e9bresztheted az eszk\u00f6zt." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "Gomb", + "button1": "Els\u0151 gomb", + "button2": "M\u00e1sodik gomb", + "button3": "Harmadik gomb" + }, + "trigger_type": { + "double": "{subtype} dupla kattint\u00e1s", + "long": "{subtype} hosszan nyomva", + "long_single": "{subtype} hosszan nyomva, majd egy kattint\u00e1s", + "single": "{subtype} egy kattint\u00e1s", + "single_long": "{subtype} egy kattint\u00e1s, majd hosszan nyomva", + "triple": "{subtype} tripla kattint\u00e1s" } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/id.json b/homeassistant/components/shelly/translations/id.json new file mode 100644 index 00000000000000..606ee473805dad --- /dev/null +++ b/homeassistant/components/shelly/translations/id.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "unsupported_firmware": "Perangkat menggunakan versi firmware yang tidak didukung." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "{name}", + "step": { + "confirm_discovery": { + "description": "Ingin menyiapkan {model} di {host}?\n\nPerangkat bertenaga baterai yang dilindungi kata sandi harus dibangunkan sebelum melanjutkan penyiapan.\nPerangkat bertenaga baterai yang tidak dilindungi kata sandi akan ditambahkan ketika perangkat bangun. Anda dapat membangunkan perangkat secara manual menggunakan tombol di atasnya sekarang juga atau menunggu pembaruan data berikutnya dari perangkat." + }, + "credentials": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Sebelum menyiapkan, perangkat bertenaga baterai harus dibangunkan. Anda dapat membangunkan perangkat menggunakan tombol di atasnya sekarang." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button": "Tombol", + "button1": "Tombol pertama", + "button2": "Tombol kedua", + "button3": "Tombol ketiga" + }, + "trigger_type": { + "double": "{subtype} diklik dua kali", + "long": "{subtype} diklik lama", + "long_single": "{subtype} diklik lama kemudian diklik sekali", + "single": "{subtype} diklik sekali", + "single_long": "{subtype} diklik sekali kemudian diklik lama", + "triple": "{subtype} diklik tiga kali" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/ko.json b/homeassistant/components/shelly/translations/ko.json index 914c9a46bd8030..d422b6de86bc8d 100644 --- a/homeassistant/components/shelly/translations/ko.json +++ b/homeassistant/components/shelly/translations/ko.json @@ -2,14 +2,18 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "unsupported_firmware": "\uc774 \uc7a5\uce58\ub294 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud38c\uc6e8\uc5b4 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4." + "unsupported_firmware": "\uae30\uae30\uac00 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud38c\uc6e8\uc5b4 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "{name}", "step": { + "confirm_discovery": { + "description": "{host}\uc5d0\uc11c {model}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub41c \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uc124\uc815\ud558\uae30 \uc804\uc5d0 \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4.\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub418\uc9c0 \uc54a\ub294 \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub420 \ub54c \ucd94\uac00\ub418\uba70, \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc218\ub3d9\uc73c\ub85c \uae30\uae30\ub97c \uc808\uc804 \ud574\uc81c\uc2dc\ud0a4\uac70\ub098 \uae30\uae30\uc5d0\uc11c \ub2e4\uc74c \ub370\uc774\ud130\ub97c \uc5c5\ub370\uc774\ud2b8\ud560 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, "credentials": { "data": { "password": "\ube44\ubc00\ubc88\ud638", @@ -19,8 +23,25 @@ "user": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "\uc124\uc815\ud558\uae30 \uc804\uc5d0 \ubc30\ud130\ub9ac\ub85c \uc791\ub3d9\ub418\ub294 \uae30\uae30\ub294 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub418\uc5b4 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." } } + }, + "device_automation": { + "trigger_subtype": { + "button": "\ubc84\ud2bc", + "button1": "\uccab \ubc88\uc9f8 \ubc84\ud2bc", + "button2": "\ub450 \ubc88\uc9f8 \ubc84\ud2bc", + "button3": "\uc138 \ubc88\uc9f8 \ubc84\ud2bc" + }, + "trigger_type": { + "double": "\"{subtype}\"\uc774(\uac00) \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "long": "\"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\uc744 \ub54c", + "long_single": "\"{subtype}\"\uc774(\uac00) \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc9e7\uac8c \ub20c\ub838\uc744 \ub54c", + "single": "\"{subtype}\"\uc774(\uac00) \uc9e7\uac8c \ub20c\ub838\uc744 \ub54c", + "single_long": "\"{subtype}\"\uc774(\uac00) \uc9e7\uac8c \ub20c\ub838\ub2e4\uac00 \uae38\uac8c \ub20c\ub838\uc744 \ub54c", + "triple": "\"{subtype}\"\uc774(\uac00) \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c" + } } } \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json index c486b9c6bfe7a6..571fb9b4339752 100644 --- a/homeassistant/components/shelly/translations/nl.json +++ b/homeassistant/components/shelly/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "unsupported_firmware": "Het apparaat gebruikt een niet-ondersteunde firmwareversie." }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -22,7 +23,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Slapende (op batterij werkende) apparaten moeten wakker zijn wanneer deze apparaten opgezet worden. Je kunt deze apparaten nu wakker maken door op de knop erop te drukken" } } }, @@ -35,6 +37,9 @@ }, "trigger_type": { "double": "{subtype} dubbel geklikt", + "long": "{subtype} lang geklikt", + "long_single": "{subtype} lang geklikt en daarna \u00e9\u00e9n keer geklikt", + "single": "{subtype} enkel geklikt", "single_long": "{subtype} een keer geklikt en daarna lang geklikt", "triple": "{subtype} driemaal geklikt" } diff --git a/homeassistant/components/shopping_list/translations/hu.json b/homeassistant/components/shopping_list/translations/hu.json index 4a093bea379c31..5f092963da3d66 100644 --- a/homeassistant/components/shopping_list/translations/hu.json +++ b/homeassistant/components/shopping_list/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "A bev\u00e1s\u00e1rl\u00f3lista m\u00e1r konfigur\u00e1lva van." + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/id.json b/homeassistant/components/shopping_list/translations/id.json new file mode 100644 index 00000000000000..0efa42d0782433 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/id.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "step": { + "user": { + "description": "Ingin mengonfigurasi daftar belanja?", + "title": "Daftar Belanja" + } + } + }, + "title": "Daftar Belanja" +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/nl.json b/homeassistant/components/shopping_list/translations/nl.json index de6045dd81bc29..e8de5fbae1dd42 100644 --- a/homeassistant/components/shopping_list/translations/nl.json +++ b/homeassistant/components/shopping_list/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "De Shopping List is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/simplisafe/translations/he.json b/homeassistant/components/simplisafe/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/simplisafe/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/hu.json b/homeassistant/components/simplisafe/translations/hu.json index 7b989246de1738..8a2deedc5341af 100644 --- a/homeassistant/components/simplisafe/translations/hu.json +++ b/homeassistant/components/simplisafe/translations/hu.json @@ -1,11 +1,23 @@ { "config": { + "abort": { + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { - "identifier_exists": "Fi\u00f3k m\u00e1r regisztr\u00e1lva van" + "identifier_exists": "Fi\u00f3k m\u00e1r regisztr\u00e1lva van", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "reauth_confirm": { + "data": { + "password": "Jelsz\u00f3" + }, + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { + "code": "K\u00f3d (a Home Assistant felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9n haszn\u00e1latos)", "password": "Jelsz\u00f3", "username": "E-mail" }, diff --git a/homeassistant/components/simplisafe/translations/id.json b/homeassistant/components/simplisafe/translations/id.json new file mode 100644 index 00000000000000..512d6a38405f5d --- /dev/null +++ b/homeassistant/components/simplisafe/translations/id.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "already_configured": "Akun SimpliSafe ini sudah digunakan.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "identifier_exists": "Akun sudah terdaftar", + "invalid_auth": "Autentikasi tidak valid", + "still_awaiting_mfa": "Masih menunggu pengeklikan dari email MFA", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "mfa": { + "description": "Periksa email Anda untuk mendapatkan tautan dari SimpliSafe. Setelah memverifikasi tautan, kembali ke sini untuk menyelesaikan instalasi integrasi.", + "title": "Autentikasi Multi-Faktor SimpliSafe" + }, + "reauth_confirm": { + "data": { + "password": "Kata Sandi" + }, + "description": "Token akses Anda telah kedaluwarsa atau dicabut. Masukkan kata sandi Anda untuk menautkan kembali akun Anda.", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "code": "Kode (digunakan di antarmuka Home Assistant)", + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Isi informasi Anda." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "code": "Kode (digunakan di antarmuka Home Assistant)" + }, + "title": "Konfigurasikan SimpliSafe" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/ko.json b/homeassistant/components/simplisafe/translations/ko.json index c5c1b057ea87c0..194fa6cecf48b2 100644 --- a/homeassistant/components/simplisafe/translations/ko.json +++ b/homeassistant/components/simplisafe/translations/ko.json @@ -7,14 +7,20 @@ "error": { "identifier_exists": "\uacc4\uc815\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "still_awaiting_mfa": "\uc544\uc9c1 \ub2e4\ub2e8\uacc4 \uc778\uc99d(MFA) \uc774\uba54\uc77c\uc758 \ub9c1\ud06c \ud074\ub9ad\uc744 \uae30\ub2e4\ub9ac\uace0\uc788\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "mfa": { + "description": "\uc774\uba54\uc77c\uc5d0\uc11c SimpliSafe\uc758 \ub9c1\ud06c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694. \ub9c1\ud06c\ub97c \ud655\uc778\ud55c \ud6c4 \uc5ec\uae30\ub85c \ub3cc\uc544\uc640 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc758 \uc124\uce58\ub97c \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", + "title": "SimpliSafe \ub2e4\ub2e8\uacc4 \uc778\uc99d" + }, "reauth_confirm": { "data": { "password": "\ube44\ubc00\ubc88\ud638" }, - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ub9cc\ub8cc\ub418\uc5c8\uac70\ub098 \ud574\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uacc4\uc815\uc744 \ub2e4\uc2dc \uc5f0\uacb0\ud558\ub824\uba74 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index d3196c591cb192..29b9d9ab70bbb8 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -21,7 +21,7 @@ "data": { "code": "Code (gebruikt in Home Assistant)", "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "title": "Vul uw gegevens in" } diff --git a/homeassistant/components/smappee/translations/hu.json b/homeassistant/components/smappee/translations/hu.json index 4258cfb0912ffb..15bfd4dc5d2175 100644 --- a/homeassistant/components/smappee/translations/hu.json +++ b/homeassistant/components/smappee/translations/hu.json @@ -2,7 +2,21 @@ "config": { "abort": { "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." + }, + "flow_title": "Smappee: {name}", + "step": { + "local": { + "data": { + "host": "Hoszt" + } + }, + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/id.json b/homeassistant/components/smappee/translations/id.json new file mode 100644 index 00000000000000..b72200c34ca7da --- /dev/null +++ b/homeassistant/components/smappee/translations/id.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "already_configured_local_device": "Perangkat lokal sudah dikonfigurasi. Hapus perangkat tersebut terlebih dahulu sebelum mengonfigurasi perangkat awan.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "cannot_connect": "Gagal terhubung", + "invalid_mdns": "Perangkat tidak didukung untuk integrasi Smappee.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})" + }, + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "Lingkungan" + }, + "description": "Siapkan Smappee Anda untuk diintegrasikan dengan Home Assistant." + }, + "local": { + "data": { + "host": "Host" + }, + "description": "Masukkan host untuk memulai integrasi lokal Smappee" + }, + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan perangkat Smappee dengan nomor seri `{serialnumber}` ke Home Assistant?", + "title": "Peranti Smappee yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/ko.json b/homeassistant/components/smappee/translations/ko.json index 8509b65ca09fa5..b2f0e5880c59a2 100644 --- a/homeassistant/components/smappee/translations/ko.json +++ b/homeassistant/components/smappee/translations/ko.json @@ -2,19 +2,33 @@ "config": { "abort": { "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_configured_local_device": "\ub85c\uceec \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \ud074\ub77c\uc6b0\ub4dc \uae30\uae30\ub97c \uad6c\uc131\ud558\uae30 \uc804\uc5d0 \uc774\ub7ec\ud55c \uae30\uae30\ub97c \uba3c\uc800 \uc81c\uac70\ud574\uc8fc\uc138\uc694.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_mdns": "Smappee \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \uae30\uae30\uc785\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, + "flow_title": "Smappee: {name}", "step": { + "environment": { + "data": { + "environment": "\ud658\uacbd" + }, + "description": "Home Assistant\uc5d0 Smappee \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." + }, "local": { "data": { "host": "\ud638\uc2a4\ud2b8" - } + }, + "description": "Smappee \ub85c\uceec \uc5f0\ub3d9\uc744 \uc2dc\uc791\ud560 \ud638\uc2a4\ud2b8\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "zeroconf_confirm": { + "description": "\uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serialnumber}`\uc758 Smappee \uae30\uae30\ub97c Home Assistant\uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Smpappee \uae30\uae30" } } } diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index 10a4fe2efab23e..373ff6ecd2e879 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -9,6 +9,7 @@ "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, + "flow_title": "Smappee: {name}", "step": { "local": { "data": { diff --git a/homeassistant/components/smart_meter_texas/translations/hu.json b/homeassistant/components/smart_meter_texas/translations/hu.json index 3b2d79a34a77e2..fd8db27da5efd0 100644 --- a/homeassistant/components/smart_meter_texas/translations/hu.json +++ b/homeassistant/components/smart_meter_texas/translations/hu.json @@ -2,6 +2,19 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/id.json b/homeassistant/components/smart_meter_texas/translations/id.json new file mode 100644 index 00000000000000..4a84db42a14f2a --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/hu.json b/homeassistant/components/smarthab/translations/hu.json index b40828cc764d8a..222c95bba16b57 100644 --- a/homeassistant/components/smarthab/translations/hu.json +++ b/homeassistant/components/smarthab/translations/hu.json @@ -1,7 +1,15 @@ { "config": { + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + }, "description": "Technikai okokb\u00f3l ne felejtsen el m\u00e1sodlagos fi\u00f3kot haszn\u00e1lni a Home Assistant be\u00e1ll\u00edt\u00e1s\u00e1hoz. A SmartHab alkalmaz\u00e1sb\u00f3l l\u00e9trehozhat egyet." } } diff --git a/homeassistant/components/smarthab/translations/id.json b/homeassistant/components/smarthab/translations/id.json new file mode 100644 index 00000000000000..7a776eac304f53 --- /dev/null +++ b/homeassistant/components/smarthab/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentikasi tidak valid", + "service": "Terjadi kesalahan saat mencoba menjangkau SmartHab. Layanan mungkin sedang mengalami gangguan. Periksa koneksi Anda.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "description": "Untuk alasan teknis, pastikan untuk menggunakan akun sekunder khusus untuk penyiapan Home Assistant Anda. Anda dapat membuatnya dari aplikasi SmartHab.", + "title": "Siapkan SmartHab" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/translations/ko.json b/homeassistant/components/smarthab/translations/ko.json index d39931cbc034e9..1641555b412f48 100644 --- a/homeassistant/components/smarthab/translations/ko.json +++ b/homeassistant/components/smarthab/translations/ko.json @@ -11,7 +11,7 @@ "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "\uae30\uc220\uc801\uc778 \uc774\uc720\ub85c Home Assistant \uc124\uc815\uacfc \uad00\ub828\ub41c \ubcf4\uc870 \uacc4\uc815\uc744 \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. SmartHab \uc751\uc6a9 \ud504\ub85c\uadf8\ub7a8\uc5d0\uc11c \uc0dd\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uc220\uc801\uc778 \uc774\uc720\ub85c Home Assistant \uc124\uc815\uacfc \uad00\ub828\ub41c \ubcf4\uc870 \uacc4\uc815\uc744 \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. SmartHab \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0\uc11c \uc0dd\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "SmartHab \uc124\uce58\ud558\uae30" } } diff --git a/homeassistant/components/smartthings/translations/hu.json b/homeassistant/components/smartthings/translations/hu.json index 5cbe1d086bc084..17c0a1a1b042f5 100644 --- a/homeassistant/components/smartthings/translations/hu.json +++ b/homeassistant/components/smartthings/translations/hu.json @@ -12,11 +12,15 @@ "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" }, - "description": "K\u00e9rj\u00fck, adjon meg egy SmartThings [Szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si tokent] ( {token_url} ), amelyet az [utas\u00edt\u00e1sok] ( {component_url} ) alapj\u00e1n hoztak l\u00e9tre. Ezt haszn\u00e1ljuk a Home Assistant integr\u00e1ci\u00f3j\u00e1nak l\u00e9trehoz\u00e1s\u00e1hoz a SmartThings-fi\u00f3kban." + "description": "K\u00e9rj\u00fck, adjon meg egy SmartThings [Szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si tokent]({token_url}), amelyet az [utas\u00edt\u00e1sok]({component_url}) alapj\u00e1n hoztak l\u00e9tre. Ezt haszn\u00e1ljuk a Home Assistant integr\u00e1ci\u00f3j\u00e1nak l\u00e9trehoz\u00e1s\u00e1hoz a SmartThings-fi\u00f3kban." + }, + "select_location": { + "data": { + "location_id": "Elhelyezked\u00e9s" + } }, "user": { - "description": "K\u00e9rlek add meg a SmartThings [Personal Access Tokent]({token_url}), amit az [instrukci\u00f3k] ({component_url}) alapj\u00e1n hozt\u00e1l l\u00e9tre.", - "title": "Adja meg a szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si Tokent" + "title": "Callback URL meger\u0151s\u00edt\u00e9se" } } } diff --git a/homeassistant/components/smartthings/translations/id.json b/homeassistant/components/smartthings/translations/id.json new file mode 100644 index 00000000000000..77846487d85b5c --- /dev/null +++ b/homeassistant/components/smartthings/translations/id.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "invalid_webhook_url": "Home Assistant tidak dikonfigurasi dengan benar untuk menerima pembaruan dari SmartThings. URL webhook tidak valid:\n> {webhook_url}\n\nPerbarui konfigurasi Anda sesuai [petunjuk]({component_url}), kemudian mulai ulang Home Assistant, dan coba kembali.", + "no_available_locations": "Tidak ada SmartThings Location untuk disiapkan di Home Assistant." + }, + "error": { + "app_setup_error": "Tidak dapat menyiapkan SmartApp. Coba lagi.", + "token_forbidden": "Token tidak memiliki cakupan OAuth yang diperlukan.", + "token_invalid_format": "Token harus dalam format UID/GUID", + "token_unauthorized": "Token tidak valid atau tidak lagi diotorisasi.", + "webhook_error": "SmartThings tidak dapat memvalidasi URL webhook. Pastikan URL webhook dapat dijangkau dari internet, lalu coba lagi." + }, + "step": { + "authorize": { + "title": "Otorisasi Home Assistant" + }, + "pat": { + "data": { + "access_token": "Token Akses" + }, + "description": "Masukkan [Token Akses Pribadi]({token_url}) SmartThings yang telah dibuat sesuai [petunjuk]({component_url}). Ini akan digunakan untuk membuat integrasi Home Assistant dalam akun SmartThings Anda.", + "title": "Masukkan Token Akses Pribadi" + }, + "select_location": { + "data": { + "location_id": "Lokasi" + }, + "description": "Pilih SmartThings Location yang ingin ditambahkan ke Home Assistant. Kami akan membuka jendela baru dan meminta Anda untuk masuk dan mengotorisasi instalasi integrasi Home Assistant ke Location yang dipilih.", + "title": "Pilih Location" + }, + "user": { + "description": "SmartThings akan dikonfigurasi untuk mengirim pembaruan push ke Home Assistant di:\n > {webhook_url} \n\nJika ini tidak benar, perbarui konfigurasi Anda, mulai ulang Home Assistant, dan coba lagi.", + "title": "Konfirmasikan URL Panggilan Balik" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/ko.json b/homeassistant/components/smartthings/translations/ko.json index 0ee2813665181f..dc02f25451b637 100644 --- a/homeassistant/components/smartthings/translations/ko.json +++ b/homeassistant/components/smartthings/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "invalid_webhook_url": "Home Assistant \uac00 SmartThings \uc5d0\uc11c \uc5c5\ub370\uc774\ud2b8\ub97c \uc218\uc2e0\ud558\ub3c4\ub85d \uc62c\ubc14\ub974\uac8c \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc6f9 \ud6c5 URL \uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4:\n> {webhook_url} \n\n[\uc548\ub0b4]({component_url}) \ub97c \ucc38\uace0\ud558\uc5ec \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant \ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "no_available_locations": "Home Assistant \uc5d0\uc11c \uc124\uc815\ud560 \uc218 \uc788\ub294 SmartThings \uc704\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." + "invalid_webhook_url": "Home Assistant\uac00 SmartThings\uc5d0\uc11c \uc5c5\ub370\uc774\ud2b8\ub97c \uc218\uc2e0\ud558\ub3c4\ub85d \uc62c\ubc14\ub974\uac8c \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc6f9 \ud6c5 URL\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4:\n> {webhook_url} \n\n[\uc548\ub0b4]({component_url})\ub97c \ucc38\uace0\ud558\uc5ec \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "no_available_locations": "Home Assistant\uc5d0\uc11c \uc124\uc815\ud560 \uc218 \uc788\ub294 SmartThings \uc704\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." }, "error": { "app_setup_error": "SmartApp \uc744 \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", @@ -19,18 +19,18 @@ "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, - "description": "[\uc548\ub0b4]({component_url}) \uc5d0 \ub530\ub77c \uc0dd\uc131\ub41c SmartThings [\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070]({token_url}) \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. SmartThings \uacc4\uc815\uc5d0\uc11c Home Assistant \uc5f0\ub3d9\uc744 \ub9cc\ub4dc\ub294\ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4.", - "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 \uc785\ub825\ud558\uae30" + "description": "[\uc548\ub0b4]({component_url})\uc5d0 \ub530\ub77c \uc0dd\uc131\ub41c SmartThings [\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070]({token_url})\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. SmartThings \uacc4\uc815\uc5d0\uc11c Home Assistant \uc5f0\ub3d9\uc744 \ub9cc\ub4dc\ub294\ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4.", + "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "select_location": { "data": { "location_id": "\uc704\uce58" }, - "description": "Home Assistant \uc5d0 \ucd94\uac00\ud558\ub824\ub294 SmartThings \uc704\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc0c8\ub86d\uac8c \uc5f4\ub9b0 \ub85c\uadf8\uc778 \ucc3d\uc5d0\uc11c \ub85c\uadf8\uc778\uc744 \ud558\uba74 \uc120\ud0dd\ud55c \uc704\uce58\uc5d0 Home Assistant \uc5f0\ub3d9\uc744 \uc2b9\uc778\ud558\ub77c\ub294 \uba54\uc2dc\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.", + "description": "Home Assistant\uc5d0 \ucd94\uac00\ud558\ub824\ub294 SmartThings \uc704\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc0c8\ub86d\uac8c \uc5f4\ub9b0 \ub85c\uadf8\uc778 \ucc3d\uc5d0\uc11c \ub85c\uadf8\uc778\uc744 \ud558\uba74 \uc120\ud0dd\ud55c \uc704\uce58\uc5d0 Home Assistant \uc5f0\ub3d9\uc744 \uc2b9\uc778\ud558\ub77c\ub294 \uba54\uc2dc\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.", "title": "\uc704\uce58 \uc120\ud0dd\ud558\uae30" }, "user": { - "description": "SmartThings \ub294 \uc544\ub798\uc758 \uc6f9 \ud6c5 \uc8fc\uc18c\ub85c Home Assistant \uc5d0 \ud478\uc2dc \uc5c5\ub370\uc774\ud2b8\ub97c \ubcf4\ub0b4\ub3c4\ub85d \uad6c\uc131\ub429\ub2c8\ub2e4. \n > {webhook_url} \n\n\uc774 \uad6c\uc131\uc774 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc73c\uba74 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant \ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "description": "SmartThings\ub294 \uc544\ub798\uc758 \uc6f9 \ud6c5 \uc8fc\uc18c\ub85c Home Assistant\uc5d0 \ud478\uc2dc \uc5c5\ub370\uc774\ud2b8\ub97c \ubcf4\ub0b4\ub3c4\ub85d \uad6c\uc131\ub429\ub2c8\ub2e4. \n > {webhook_url} \n\n\uc774 \uad6c\uc131\uc774 \uc62c\ubc14\ub974\uc9c0 \uc54a\ub2e4\uba74 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "title": "\ucf5c\ubc31 URL \ud655\uc778\ud558\uae30" } } diff --git a/homeassistant/components/smartthings/translations/nl.json b/homeassistant/components/smartthings/translations/nl.json index a77ad40f0caa98..6c141b624d28e3 100644 --- a/homeassistant/components/smartthings/translations/nl.json +++ b/homeassistant/components/smartthings/translations/nl.json @@ -9,7 +9,7 @@ "token_forbidden": "Het token heeft niet de vereiste OAuth-scopes.", "token_invalid_format": "Het token moet de UID/GUID-indeling hebben", "token_unauthorized": "Het token is ongeldig of niet langer geautoriseerd.", - "webhook_error": "SmartThings kon het in 'base_url` geconfigureerde endpoint niet goedkeuren. Lees de componentvereisten door." + "webhook_error": "SmartThings kan de webhook URL niet valideren. Zorg ervoor dat de webhook URL bereikbaar is vanaf het internet en probeer het opnieuw." }, "step": { "authorize": { @@ -30,8 +30,8 @@ "title": "Locatie selecteren" }, "user": { - "description": "Voer een SmartThings [Personal Access Token]({token_url}) in die is aangemaakt volgens de [instructies]({component_url}).", - "title": "Persoonlijk toegangstoken invoeren" + "description": "SmartThings zal worden geconfigureerd om push updates te sturen naar Home Assistant op:\n> {webhook_url}\n\nAls dit niet correct is, werk dan uw configuratie bij, start Home Assistant opnieuw op en probeer het opnieuw.", + "title": "Bevestig Callback URL" } } } diff --git a/homeassistant/components/smarttub/translations/bg.json b/homeassistant/components/smarttub/translations/bg.json new file mode 100644 index 00000000000000..05ef3ed780e750 --- /dev/null +++ b/homeassistant/components/smarttub/translations/bg.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u0430" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/hu.json b/homeassistant/components/smarttub/translations/hu.json new file mode 100644 index 00000000000000..666ff85e321b32 --- /dev/null +++ b/homeassistant/components/smarttub/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + }, + "description": "Add meg SmartTub e-mail c\u00edmet \u00e9s jelsz\u00f3t a bejelentkez\u00e9shez", + "title": "Bejelentkez\u00e9s" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/id.json b/homeassistant/components/smarttub/translations/id.json new file mode 100644 index 00000000000000..c1de3aa0453058 --- /dev/null +++ b/homeassistant/components/smarttub/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Kata Sandi" + }, + "description": "Masukkan alamat email dan kata sandi SmartTub Anda untuk masuk", + "title": "Masuk" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smarttub/translations/ko.json b/homeassistant/components/smarttub/translations/ko.json index fab7e511034b46..2ab844cd967f39 100644 --- a/homeassistant/components/smarttub/translations/ko.json +++ b/homeassistant/components/smarttub/translations/ko.json @@ -13,7 +13,9 @@ "data": { "email": "\uc774\uba54\uc77c", "password": "\ube44\ubc00\ubc88\ud638" - } + }, + "description": "\ub85c\uadf8\uc778\ud558\ub824\uba74 SmartTub \uc774\uba54\uc77c \uc8fc\uc18c\uc640 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\ub85c\uadf8\uc778" } } } diff --git a/homeassistant/components/smarttub/translations/nl.json b/homeassistant/components/smarttub/translations/nl.json index a5f20db8a32a99..7ef935d8cee182 100644 --- a/homeassistant/components/smarttub/translations/nl.json +++ b/homeassistant/components/smarttub/translations/nl.json @@ -14,6 +14,7 @@ "email": "E-mail", "password": "Wachtwoord" }, + "description": "Voer uw SmartTub-e-mailadres en wachtwoord in om in te loggen", "title": "Inloggen" } } diff --git a/homeassistant/components/smarttub/translations/pt.json b/homeassistant/components/smarttub/translations/pt.json new file mode 100644 index 00000000000000..414ca7ddf82f11 --- /dev/null +++ b/homeassistant/components/smarttub/translations/pt.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "email": "Email", + "password": "Palavra-passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/he.json b/homeassistant/components/smhi/translations/he.json new file mode 100644 index 00000000000000..4c49313d97741a --- /dev/null +++ b/homeassistant/components/smhi/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/id.json b/homeassistant/components/smhi/translations/id.json new file mode 100644 index 00000000000000..8d5d95f183ec83 --- /dev/null +++ b/homeassistant/components/smhi/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "name_exists": "Nama sudah ada", + "wrong_location": "Hanya untuk lokasi di Swedia" + }, + "step": { + "user": { + "data": { + "latitude": "Lintang", + "longitude": "Bujur", + "name": "Nama" + }, + "title": "Lokasi di Swedia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/hu.json b/homeassistant/components/sms/translations/hu.json index 3b2d79a34a77e2..6fa524b18ab936 100644 --- a/homeassistant/components/sms/translations/hu.json +++ b/homeassistant/components/sms/translations/hu.json @@ -1,7 +1,20 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "device": "Eszk\u00f6z" + }, + "title": "Csatlakoz\u00e1s a modemhez" + } } } } \ No newline at end of file diff --git a/homeassistant/components/sms/translations/id.json b/homeassistant/components/sms/translations/id.json new file mode 100644 index 00000000000000..63ebb088521c66 --- /dev/null +++ b/homeassistant/components/sms/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "device": "Perangkat" + }, + "title": "Hubungkan ke modem" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/ko.json b/homeassistant/components/sms/translations/ko.json index 13c043ef6353e9..5ead95c1a27ea2 100644 --- a/homeassistant/components/sms/translations/ko.json +++ b/homeassistant/components/sms/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/solaredge/translations/hu.json b/homeassistant/components/solaredge/translations/hu.json index 31890269925447..8479c90f595b6f 100644 --- a/homeassistant/components/solaredge/translations/hu.json +++ b/homeassistant/components/solaredge/translations/hu.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs", "site_not_active": "Az oldal nem akt\u00edv" }, "step": { diff --git a/homeassistant/components/solaredge/translations/id.json b/homeassistant/components/solaredge/translations/id.json new file mode 100644 index 00000000000000..41c94755af7480 --- /dev/null +++ b/homeassistant/components/solaredge/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "site_exists": "Nilai site_id ini sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "could_not_connect": "Tidak dapat terhubung ke API solaredge", + "invalid_api_key": "Kunci API tidak valid", + "site_exists": "Nilai site_id ini sudah dikonfigurasi", + "site_not_active": "Situs tidak aktif" + }, + "step": { + "user": { + "data": { + "api_key": "Kunci API", + "name": "Nama instalasi ini", + "site_id": "Nilai site_id SolarEdge" + }, + "title": "Tentukan parameter API untuk instalasi ini" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solaredge/translations/ko.json b/homeassistant/components/solaredge/translations/ko.json index 8544cdd143dfff..ce3ed2a767d847 100644 --- a/homeassistant/components/solaredge/translations/ko.json +++ b/homeassistant/components/solaredge/translations/ko.json @@ -6,8 +6,10 @@ }, "error": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "could_not_connect": "SolarEdge API\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "invalid_api_key": "API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "site_exists": "\uc774 site_id \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "site_exists": "\uc774 site_id \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "site_not_active": "\uc0ac\uc774\ud2b8\uac00 \ud65c\uc131\ud654\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/hu.json b/homeassistant/components/solarlog/translations/hu.json index 3fa8a9620a017f..dd0ea8033ae16a 100644 --- a/homeassistant/components/solarlog/translations/hu.json +++ b/homeassistant/components/solarlog/translations/hu.json @@ -1,7 +1,11 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/id.json b/homeassistant/components/solarlog/translations/id.json new file mode 100644 index 00000000000000..3ce222f9c2a879 --- /dev/null +++ b/homeassistant/components/solarlog/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Prefiks yang akan digunakan untuk sensor Solar-Log Anda" + }, + "title": "Tentukan koneksi Solar-Log Anda" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/hu.json b/homeassistant/components/soma/translations/hu.json index 82ec28ff4d7905..d013cb49fdf945 100644 --- a/homeassistant/components/soma/translations/hu.json +++ b/homeassistant/components/soma/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", "connection_error": "Nem siker\u00fclt csatlakozni a SOMA Connecthez.", "missing_configuration": "A Soma \u00f6sszetev\u0151 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t.", "result_error": "A SOMA Connect hiba\u00e1llapottal v\u00e1laszolt." @@ -15,7 +16,7 @@ "port": "Port" }, "description": "K\u00e9rj\u00fck, adja meg a SOMA Connect csatlakoz\u00e1si be\u00e1ll\u00edt\u00e1sait.", - "title": "SOMA csatlakoz\u00e1s" + "title": "SOMA Connect" } } } diff --git a/homeassistant/components/soma/translations/id.json b/homeassistant/components/soma/translations/id.json new file mode 100644 index 00000000000000..d512bd467976e9 --- /dev/null +++ b/homeassistant/components/soma/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_setup": "Anda hanya dapat mengonfigurasi satu akun Soma.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "connection_error": "Gagal menyambungkan ke SOMA Connect.", + "missing_configuration": "Komponen Soma tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "result_error": "SOMA Connect merespons dengan status kesalahan." + }, + "create_entry": { + "default": "Berhasil mengautentikasi dengan Soma." + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "description": "Masukkan pengaturan koneksi SOMA Connect Anda.", + "title": "SOMA Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/ko.json b/homeassistant/components/soma/translations/ko.json index 83c2f01ff8bbb9..13a6fb03e83da4 100644 --- a/homeassistant/components/soma/translations/ko.json +++ b/homeassistant/components/soma/translations/ko.json @@ -3,12 +3,12 @@ "abort": { "already_setup": "\ud558\ub098\uc758 Soma \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "connection_error": "SOMA Connect \uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "connection_error": "SOMA Connect\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Soma \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "result_error": "SOMA Connect \uac00 \uc624\ub958 \uc0c1\ud0dc\ub85c \uc751\ub2f5\ud588\uc2b5\ub2c8\ub2e4." }, "create_entry": { - "default": "Soma \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "Soma\ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { "user": { diff --git a/homeassistant/components/somfy/translations/hu.json b/homeassistant/components/somfy/translations/hu.json index 86927570c85836..ce4e94b3399566 100644 --- a/homeassistant/components/somfy/translations/hu.json +++ b/homeassistant/components/somfy/translations/hu.json @@ -1,11 +1,17 @@ { "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" }, "step": { "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/somfy/translations/id.json b/homeassistant/components/somfy/translations/id.json new file mode 100644 index 00000000000000..2d229de00d579f --- /dev/null +++ b/homeassistant/components/somfy/translations/id.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/ko.json b/homeassistant/components/somfy/translations/ko.json index 8b4f4ff752f67d..568c8d051163da 100644 --- a/homeassistant/components/somfy/translations/ko.json +++ b/homeassistant/components/somfy/translations/ko.json @@ -4,7 +4,7 @@ "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/somfy_mylink/translations/bg.json b/homeassistant/components/somfy_mylink/translations/bg.json new file mode 100644 index 00000000000000..4983c9a14b265d --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u041f\u043e\u0440\u0442" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/he.json b/homeassistant/components/somfy_mylink/translations/he.json new file mode 100644 index 00000000000000..9af5985ac45883 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/he.json @@ -0,0 +1,9 @@ +{ + "options": { + "step": { + "init": { + "title": "\u05e7\u05d1\u05d9\u05e2\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc MyLink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/hu.json b/homeassistant/components/somfy_mylink/translations/hu.json new file mode 100644 index 00000000000000..1ef6d25d34c65c --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/hu.json @@ -0,0 +1,32 @@ +{ + "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } + }, + "options": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "init": { + "title": "Mylink be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/id.json b/homeassistant/components/somfy_mylink/translations/id.json new file mode 100644 index 00000000000000..0203ae421e2ce6 --- /dev/null +++ b/homeassistant/components/somfy_mylink/translations/id.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Somfy MyLink {mac} ({ip})", + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port", + "system_id": "ID Sistem" + }, + "description": "ID Sistem dapat diperoleh di aplikasi MyLink di bawah bagian Integrasi dengan memilih layanan non-Cloud." + } + } + }, + "options": { + "abort": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "entity_config": { + "data": { + "reverse": "Penutup dibalik" + }, + "description": "Konfigurasikan opsi untuk `{entity_id}`", + "title": "Konfigurasikan Entitas" + }, + "init": { + "data": { + "default_reverse": "Status pembalikan baku untuk penutup yang belum dikonfigurasi", + "entity_id": "Konfigurasikan entitas tertentu.", + "target_id": "Konfigurasikan opsi untuk penutup." + }, + "title": "Konfigurasikan Opsi MyLink" + }, + "target_config": { + "data": { + "reverse": "Penutup dibalik" + }, + "description": "Konfigurasikan opsi untuk `{target_name}`", + "title": "Konfigurasikan Cover MyLink" + } + } + }, + "title": "Somfy MyLink" +} \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/ko.json b/homeassistant/components/somfy_mylink/translations/ko.json index 4d4a78ee1f01c3..b099d3d6ed842d 100644 --- a/homeassistant/components/somfy_mylink/translations/ko.json +++ b/homeassistant/components/somfy_mylink/translations/ko.json @@ -8,18 +8,46 @@ "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "Somfy MyLink: {mac} ({ip})", "step": { "user": { "data": { "host": "\ud638\uc2a4\ud2b8", - "port": "\ud3ec\ud2b8" - } + "port": "\ud3ec\ud2b8", + "system_id": "\uc2dc\uc2a4\ud15c ID" + }, + "description": "\uc2dc\uc2a4\ud15c ID\ub294 MyLink \uc571\uc758 Integration\uc5d0\uc11c non-Cloud service\ub97c \uc120\ud0dd\ud558\uc5ec \uc5bb\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." } } }, "options": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "entity_config": { + "data": { + "reverse": "\uc5ec\ub2eb\uc774\uac00 \ubc18\uc804\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "description": "`{entity_id}`\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30", + "title": "\uad6c\uc131\uc694\uc18c \uad6c\uc131\ud558\uae30" + }, + "init": { + "data": { + "default_reverse": "\uad6c\uc131\ub418\uc9c0 \uc54a\uc740 \uc5ec\ub2eb\uc774\uc5d0 \ub300\ud55c \uae30\ubcf8 \ubc18\uc804 \uc0c1\ud0dc", + "entity_id": "\ud2b9\uc815 \uad6c\uc131\uc694\uc18c\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "target_id": "\uc5ec\ub2eb\uc774\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" + }, + "title": "MyLink \uc635\uc158 \uad6c\uc131\ud558\uae30" + }, + "target_config": { + "data": { + "reverse": "\uc5ec\ub2eb\uc774\uac00 \ubc18\uc804\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "description": "`{target_name}`\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30", + "title": "MyLink \uc5ec\ub2eb\uc774 \uad6c\uc131\ud558\uae30" + } } - } + }, + "title": "Somfy MyLink" } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index b0ae5c9d3ad6ff..1e9d5a58f89d66 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -33,6 +33,10 @@ "entity_id": "Configureer een specifieke entiteit." }, "title": "Configureer MyLink-opties" + }, + "target_config": { + "description": "Configureer opties voor ' {target_name} '", + "title": "Configureer MyLink Cover" } } }, diff --git a/homeassistant/components/sonarr/translations/hu.json b/homeassistant/components/sonarr/translations/hu.json index f5301e874eae05..e3fa0b5ff21928 100644 --- a/homeassistant/components/sonarr/translations/hu.json +++ b/homeassistant/components/sonarr/translations/hu.json @@ -1,7 +1,39 @@ { "config": { "abort": { - "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "flow_title": "Sonarr: {name}", + "step": { + "reauth_confirm": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, + "user": { + "data": { + "api_key": "API kulcs", + "base_path": "El\u00e9r\u00e9si \u00fat az API-hoz", + "host": "Hoszt", + "port": "Port", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "A megjelen\u00edteni k\u00edv\u00e1nt k\u00f6vetkez\u0151 napok sz\u00e1ma", + "wanted_max_items": "A megjelen\u00edteni k\u00edv\u00e1nt elemek maxim\u00e1lis sz\u00e1ma" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/id.json b/homeassistant/components/sonarr/translations/id.json new file mode 100644 index 00000000000000..ffaf1d226047f6 --- /dev/null +++ b/homeassistant/components/sonarr/translations/id.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "Sonarr: {name}", + "step": { + "reauth_confirm": { + "description": "Integrasi Sonarr perlu diautentikasi ulang secara manual dengan API Sonarr yang dihosting di: {host}", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "api_key": "Kunci API", + "base_path": "Jalur ke API", + "host": "Host", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "verify_ssl": "Verifikasi sertifikat SSL" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Jumlah hari mendatang untuk ditampilkan", + "wanted_max_items": "Jumlah maksimal item yang diinginkan untuk ditampilkan" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/ko.json b/homeassistant/components/sonarr/translations/ko.json index 17e3592d509260..fbff72e46f20ae 100644 --- a/homeassistant/components/sonarr/translations/ko.json +++ b/homeassistant/components/sonarr/translations/ko.json @@ -12,7 +12,8 @@ "flow_title": "Sonarr: {name}", "step": { "reauth_confirm": { - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "Sonarr \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 {host}\uc5d0\uc11c \ud638\uc2a4\ud305\ub418\ub294 Sonarr API\ub85c \uc218\ub3d9\uc73c\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/sonarr/translations/nl.json b/homeassistant/components/sonarr/translations/nl.json index 08ef9bb2ecea61..3203f3ac1281fb 100644 --- a/homeassistant/components/sonarr/translations/nl.json +++ b/homeassistant/components/sonarr/translations/nl.json @@ -9,6 +9,7 @@ "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie" }, + "flow_title": "Sonarr: {name}", "step": { "reauth_confirm": { "description": "De Sonarr-integratie moet handmatig opnieuw worden geverifieerd met de Sonarr-API die wordt gehost op: {host}", @@ -17,6 +18,7 @@ "user": { "data": { "api_key": "API-sleutel", + "base_path": "Pad naar API", "host": "Host", "port": "Poort", "ssl": "Maakt gebruik van een SSL-certificaat", @@ -24,5 +26,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Aantal komende dagen om weer te geven", + "wanted_max_items": "Maximaal aantal gewenste items dat moet worden weergegeven" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/songpal/translations/hu.json b/homeassistant/components/songpal/translations/hu.json index cd4c501ecf79f1..aa55862c0aad27 100644 --- a/homeassistant/components/songpal/translations/hu.json +++ b/homeassistant/components/songpal/translations/hu.json @@ -1,12 +1,17 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "not_songpal_device": "Nem Songpal eszk\u00f6z" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "Sony Songpal {name} ({host})", "step": { + "init": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a {name}({host})-t?" + }, "user": { "data": { "endpoint": "V\u00e9gpont" diff --git a/homeassistant/components/songpal/translations/id.json b/homeassistant/components/songpal/translations/id.json new file mode 100644 index 00000000000000..2b8149661bc4a2 --- /dev/null +++ b/homeassistant/components/songpal/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "not_songpal_device": "Bukan perangkat Songpal" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "Sony Songpal {name} ({host})", + "step": { + "init": { + "description": "Ingin menyiapkan {name} ({host})?" + }, + "user": { + "data": { + "endpoint": "Titik Akhir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/translations/hu.json b/homeassistant/components/sonos/translations/hu.json index aa10087a884e9a..2123ec520f7474 100644 --- a/homeassistant/components/sonos/translations/hu.json +++ b/homeassistant/components/sonos/translations/hu.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nem tal\u00e1lhat\u00f3k Sonos eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egyetlen Sonos konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/id.json b/homeassistant/components/sonos/translations/id.json index ef88cab58142db..145e2775e4a1ff 100644 --- a/homeassistant/components/sonos/translations/id.json +++ b/homeassistant/components/sonos/translations/id.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "Tidak ada perangkat Sonos yang ditemukan pada jaringan.", - "single_instance_allowed": "Hanya satu konfigurasi Sonos yang diperlukan." + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." }, "step": { "confirm": { - "description": "Apakah Anda ingin mengatur Sonos?" + "description": "Ingin menyiapkan Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/ko.json b/homeassistant/components/sonos/translations/ko.json index ba85f8df170222..f85f3c5cab402f 100644 --- a/homeassistant/components/sonos/translations/ko.json +++ b/homeassistant/components/sonos/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "Sonos \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Sonos\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/sonos/translations/nl.json b/homeassistant/components/sonos/translations/nl.json index e52111fc50f6e2..42298f0b4f7dc1 100644 --- a/homeassistant/components/sonos/translations/nl.json +++ b/homeassistant/components/sonos/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen Sonos-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Sonos nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/speedtestdotnet/translations/de.json b/homeassistant/components/speedtestdotnet/translations/de.json index 3b5ef0b26e17ad..f2635c19f033cb 100644 --- a/homeassistant/components/speedtestdotnet/translations/de.json +++ b/homeassistant/components/speedtestdotnet/translations/de.json @@ -6,7 +6,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?" + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?" } } }, diff --git a/homeassistant/components/speedtestdotnet/translations/hu.json b/homeassistant/components/speedtestdotnet/translations/hu.json new file mode 100644 index 00000000000000..ec08c711e1d374 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "wrong_server_id": "A szerver azonos\u00edt\u00f3 \u00e9rv\u00e9nytelen" + }, + "step": { + "user": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "server_name": "V\u00e1laszd ki a teszt szervert" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/id.json b/homeassistant/components/speedtestdotnet/translations/id.json new file mode 100644 index 00000000000000..24e78609380bdc --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan.", + "wrong_server_id": "ID server tidak valid" + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Nonaktifkan pembaruan otomatis", + "scan_interval": "Frekuensi pembaruan (menit)", + "server_name": "Pilih server uji" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/ko.json b/homeassistant/components/speedtestdotnet/translations/ko.json index 2951d72d2018a4..e3b652083174f6 100644 --- a/homeassistant/components/speedtestdotnet/translations/ko.json +++ b/homeassistant/components/speedtestdotnet/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", - "wrong_server_id": "\uc11c\ubc84 ID \uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "wrong_server_id": "\uc11c\ubc84 ID\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { "user": { diff --git a/homeassistant/components/speedtestdotnet/translations/nl.json b/homeassistant/components/speedtestdotnet/translations/nl.json index 1fe99195f7a0a0..5de8460fd77e14 100644 --- a/homeassistant/components/speedtestdotnet/translations/nl.json +++ b/homeassistant/components/speedtestdotnet/translations/nl.json @@ -9,5 +9,16 @@ "description": "Wil je beginnen met instellen?" } } + }, + "options": { + "step": { + "init": { + "data": { + "manual": "Automatische updaten uitschakelen", + "scan_interval": "Update frequentie (minuten)", + "server_name": "Selecteer testserver" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/spider/translations/hu.json b/homeassistant/components/spider/translations/hu.json new file mode 100644 index 00000000000000..9639cfe6367d56 --- /dev/null +++ b/homeassistant/components/spider/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Bejelentkez\u00e9s mijn.ithodaalderop.nl fi\u00f3kkal" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/id.json b/homeassistant/components/spider/translations/id.json new file mode 100644 index 00000000000000..2ea038fdcdd76b --- /dev/null +++ b/homeassistant/components/spider/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Masuk dengan akun mijn.ithodaalderop.nl" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/ko.json b/homeassistant/components/spider/translations/ko.json index 9e9ed5b0f3026f..5c72b30726af9c 100644 --- a/homeassistant/components/spider/translations/ko.json +++ b/homeassistant/components/spider/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -12,7 +12,8 @@ "data": { "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" - } + }, + "title": "mijn.ithodaalderop.nl \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uae30" } } } diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json index bc7683ac0a4165..373d203aed7e64 100644 --- a/homeassistant/components/spider/translations/nl.json +++ b/homeassistant/components/spider/translations/nl.json @@ -12,7 +12,8 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "Aanmelden met mijn.ithodaalderop.nl account" } } } diff --git a/homeassistant/components/spotify/translations/hu.json b/homeassistant/components/spotify/translations/hu.json index fb0dc0f8a1f826..060aeffe8bdfb0 100644 --- a/homeassistant/components/spotify/translations/hu.json +++ b/homeassistant/components/spotify/translations/hu.json @@ -2,15 +2,24 @@ "config": { "abort": { "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "missing_configuration": "A Spotify integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t." + "missing_configuration": "A Spotify integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." }, "create_entry": { "default": "A Spotify sikeresen hiteles\u00edtett." }, "step": { "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + }, + "reauth_confirm": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "A Spotify API v\u00e9gpont el\u00e9rhet\u0151" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/id.json b/homeassistant/components/spotify/translations/id.json new file mode 100644 index 00000000000000..f75f4159a96e05 --- /dev/null +++ b/homeassistant/components/spotify/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Integrasi Spotify tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "reauth_account_mismatch": "Akun Spotify yang digunakan untuk autentikasi tidak cocok dengan akun yang memerlukan autentikasi ulang." + }, + "create_entry": { + "default": "Berhasil mengautentikasi dengan Spotify." + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "reauth_confirm": { + "description": "Integrasi Spotify perlu diautentikasi ulang ke Spotify untuk akun: {account}", + "title": "Autentikasi Ulang Integrasi" + } + } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Titik akhir API Spotify dapat dijangkau" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/ko.json b/homeassistant/components/spotify/translations/ko.json index 22a338a8d7cc95..926231a1de3171 100644 --- a/homeassistant/components/spotify/translations/ko.json +++ b/homeassistant/components/spotify/translations/ko.json @@ -4,19 +4,24 @@ "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Spotify \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", - "reauth_account_mismatch": "\uc778\uc99d\ub41c Spotify \uacc4\uc815\uc740 \uc7ac\uc778\uc99d\uc774 \ud544\uc694\ud55c \uacc4\uc815\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + "reauth_account_mismatch": "\uc778\uc99d \ubc1b\uc740 Spotify \uacc4\uc815\uc774 \uc7ac\uc778\uc99d\ud574\uc57c \ud558\ub294 \uacc4\uc815\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "create_entry": { - "default": "Spotify \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "Spotify\ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "reauth_confirm": { - "description": "Spotify \ud1b5\ud569\uc740 \uacc4\uc815 {account} \ub300\ud574 Spotify\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4.", - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "description": "Spotify \uad6c\uc131\uc694\uc18c\ub294 {account} \uacc4\uc815\uc5d0 \ub300\ud574 Spotify\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API \uc5d4\ub4dc \ud3ec\uc778\ud2b8 \uc5f0\uacb0" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index 46b18857fe8b6a..afccb637145f80 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -18,5 +18,10 @@ "title": "Verifieer de integratie opnieuw" } } + }, + "system_health": { + "info": { + "api_endpoint_reachable": "Spotify API-eindpunt is bereikbaar" + } } } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/hu.json b/homeassistant/components/squeezebox/translations/hu.json index 3b2d79a34a77e2..216badd15c68ac 100644 --- a/homeassistant/components/squeezebox/translations/hu.json +++ b/homeassistant/components/squeezebox/translations/hu.json @@ -1,7 +1,30 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_server_found": "Nem tal\u00e1lhat\u00f3 LMS szerver." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "no_server_found": "Nem siker\u00fclt automatikusan felfedezni a szervert.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "flow_title": "Logitech Squeezebox: {host}", + "step": { + "edit": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/id.json b/homeassistant/components/squeezebox/translations/id.json new file mode 100644 index 00000000000000..764c356ba8475c --- /dev/null +++ b/homeassistant/components/squeezebox/translations/id.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_server_found": "Tidak ada server LMS yang ditemukan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_server_found": "Tidak dapat menemukan server secara otomatis.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Logitech Squeezebox: {host}", + "step": { + "edit": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Edit informasi koneksi" + }, + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/hu.json b/homeassistant/components/srp_energy/translations/hu.json index f46e17923ad735..0c3bdf29389f37 100644 --- a/homeassistant/components/srp_energy/translations/hu.json +++ b/homeassistant/components/srp_energy/translations/hu.json @@ -1,11 +1,22 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { - "id": "A fi\u00f3k azonos\u00edt\u00f3ja" + "id": "A fi\u00f3k azonos\u00edt\u00f3ja", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/id.json b/homeassistant/components/srp_energy/translations/id.json new file mode 100644 index 00000000000000..fefcbff2ecb6b5 --- /dev/null +++ b/homeassistant/components/srp_energy/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_account": "ID akun harus terdiri dari 9 angka", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "id": "ID akun", + "is_tou": "Dalam Paket Time of Use", + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + }, + "title": "SRP Energy" +} \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/ko.json b/homeassistant/components/srp_energy/translations/ko.json index 4b6af62638af11..b329cf1f2b1536 100644 --- a/homeassistant/components/srp_energy/translations/ko.json +++ b/homeassistant/components/srp_energy/translations/ko.json @@ -1,20 +1,24 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_account": "\uacc4\uc815 ID\ub294 9\uc790\ub9ac \uc22b\uc790\uc5ec\uc57c \ud569\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { "user": { "data": { + "id": "\uacc4\uc815 ID", + "is_tou": "\uacc4\uc2dc\ubcc4 \uc694\uae08\uc81c", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" } } } - } + }, + "title": "SRP Energy" } \ No newline at end of file diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json index cd06c36b66158c..d1df1796f3a643 100644 --- a/homeassistant/components/srp_energy/translations/nl.json +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "id": "Account ID", "password": "Wachtwoord", "username": "Gebruikersnaam" } diff --git a/homeassistant/components/starline/translations/he.json b/homeassistant/components/starline/translations/he.json new file mode 100644 index 00000000000000..535427982322a0 --- /dev/null +++ b/homeassistant/components/starline/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "auth_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/starline/translations/hu.json b/homeassistant/components/starline/translations/hu.json index 9d544eb0337890..71895e80ad5b38 100644 --- a/homeassistant/components/starline/translations/hu.json +++ b/homeassistant/components/starline/translations/hu.json @@ -11,7 +11,7 @@ "app_id": "App ID", "app_secret": "Titok" }, - "description": "Alkalmaz\u00e1s azonos\u00edt\u00f3ja \u00e9s titkos k\u00f3dja a StarLine fejleszt\u0151i fi\u00f3kb\u00f3l ", + "description": "Alkalmaz\u00e1s azonos\u00edt\u00f3ja \u00e9s titkos k\u00f3dja a [StarLine fejleszt\u0151i fi\u00f3kb\u00f3l](https://my.starline.ru/developer)", "title": "Alkalmaz\u00e1si hiteles\u00edt\u0151 adatok" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/id.json b/homeassistant/components/starline/translations/id.json new file mode 100644 index 00000000000000..5a0afdba7ef11d --- /dev/null +++ b/homeassistant/components/starline/translations/id.json @@ -0,0 +1,41 @@ +{ + "config": { + "error": { + "error_auth_app": "ID aplikasi atau kode rahasia salah", + "error_auth_mfa": "Kode salah", + "error_auth_user": "Nama pengguna atau kata sandi salah" + }, + "step": { + "auth_app": { + "data": { + "app_id": "ID Aplikasi", + "app_secret": "Kode Rahasia" + }, + "description": "ID Aplikasi dan kode rahasia dari [akun pengembang StarLine] (https://my.starline.ru/developer)", + "title": "Kredensial aplikasi" + }, + "auth_captcha": { + "data": { + "captcha_code": "Kode dari gambar" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "Kode SMS" + }, + "description": "Masukkan kode yang dikirimkan ke ponsel {phone_number}", + "title": "Autentikasi Dua Faktor" + }, + "auth_user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Email dan kata sandi akun StarLine", + "title": "Kredensial pengguna" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/zh-Hant.json b/homeassistant/components/starline/translations/zh-Hant.json index 81a65ac0405a01..722c5daaad4a3a 100644 --- a/homeassistant/components/starline/translations/zh-Hant.json +++ b/homeassistant/components/starline/translations/zh-Hant.json @@ -26,7 +26,7 @@ "mfa_code": "\u7c21\u8a0a\u5bc6\u78bc" }, "description": "\u8f38\u5165\u50b3\u9001\u81f3 {phone_number} \u7684\u9a57\u8b49\u78bc", - "title": "\u96d9\u91cd\u9a57\u8b49" + "title": "\u96d9\u91cd\u8a8d\u8b49" }, "auth_user": { "data": { diff --git a/homeassistant/components/subaru/translations/bg.json b/homeassistant/components/subaru/translations/bg.json new file mode 100644 index 00000000000000..c3dc8345ecd9e8 --- /dev/null +++ b/homeassistant/components/subaru/translations/bg.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "user": { + "data": { + "country": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0434\u044a\u0440\u0436\u0430\u0432\u0430", + "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/subaru/translations/hu.json b/homeassistant/components/subaru/translations/hu.json new file mode 100644 index 00000000000000..d92ca24b7a1e06 --- /dev/null +++ b/homeassistant/components/subaru/translations/hu.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "bad_pin_format": "A PIN-nek 4 sz\u00e1mjegy\u0171nek kell lennie", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "incorrect_pin": "Helytelen PIN", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "title": "Subaru Starlink konfigur\u00e1ci\u00f3" + }, + "user": { + "data": { + "country": "V\u00e1lassz orsz\u00e1got", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "Subaru Starlink konfigur\u00e1ci\u00f3" + } + } + }, + "options": { + "step": { + "init": { + "title": "Subaru Starlink be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/id.json b/homeassistant/components/subaru/translations/id.json new file mode 100644 index 00000000000000..1ae1506fe099ce --- /dev/null +++ b/homeassistant/components/subaru/translations/id.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "bad_pin_format": "PIN harus terdiri dari 4 angka", + "cannot_connect": "Gagal terhubung", + "incorrect_pin": "PIN salah", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "Masukkan PIN MySubaru Anda\nCATATAN: Semua kendaraan dalam akun harus memiliki PIN yang sama", + "title": "Konfigurasi Subaru Starlink" + }, + "user": { + "data": { + "country": "Pilih negara", + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial MySubaru Anda\nCATATAN: Penyiapan awal mungkin memerlukan waktu hingga 30 detik", + "title": "Konfigurasi Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "Aktifkan polling kendaraan" + }, + "description": "Ketika diaktifkan, polling kendaraan akan mengirim perintah jarak jauh ke kendaraan Anda setiap 2 jam untuk mendapatkan data sensor baru. Tanpa polling kendaraan, data sensor baru hanya diterima ketika kendaraan mengirimkan data secara otomatis (umumnya setelah mesin dimatikan).", + "title": "Opsi Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/ko.json b/homeassistant/components/subaru/translations/ko.json new file mode 100644 index 00000000000000..8fe12309812498 --- /dev/null +++ b/homeassistant/components/subaru/translations/ko.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "bad_pin_format": "PIN\uc740 4\uc790\ub9ac\uc5ec\uc57c \ud569\ub2c8\ub2e4", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "incorrect_pin": "PIN\uc774 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "description": "MySubaru PIN\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694\n\ucc38\uace0: \uacc4\uc815\uc758 \ubaa8\ub4e0 \ucc28\ub7c9\uc740 \ub3d9\uc77c\ud55c PIN \uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", + "title": "Subaru Starlink \uad6c\uc131" + }, + "user": { + "data": { + "country": "\uad6d\uac00 \uc120\ud0dd\ud558\uae30", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "MySubaru \uc790\uaca9 \uc99d\uba85\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694\n\ucc38\uace0: \ucd08\uae30 \uc124\uc815\uc5d0\ub294 \ucd5c\ub300 30\ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4", + "title": "Subaru Starlink \uad6c\uc131" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "\ucc28\ub7c9 \ud3f4\ub9c1 \ud65c\uc131\ud654\ud558\uae30" + }, + "description": "\ud65c\uc131\ud654\ub418\uba74 \ucc28\ub7c9 \ud3f4\ub9c1\uc740 \ucc28\ub7c9\uc5d0 2\uc2dc\uac04\ub9c8\ub2e4 \uc6d0\uaca9 \uba85\ub839\uc744 \uc804\uc1a1\ud558\uc5ec \uc0c8\ub85c\uc6b4 \uc13c\uc11c \ub370\uc774\ud130\ub97c \ubc1b\uc544\uc635\ub2c8\ub2e4. \ucc28\ub7c9 \ud3f4\ub9c1\uc774 \uc5c6\uc73c\uba74 \uc0c8\ub85c\uc6b4 \uc13c\uc11c \ub370\uc774\ud130\ub294 \ucc28\ub7c9\uc774 \uc790\ub3d9\uc73c\ub85c \ub370\uc774\ud130\ub97c \ubcf4\ub0bc \ub54c\ub9cc \uc218\uc2e0\ub429\ub2c8\ub2e4(\uc77c\ubc18\uc801\uc73c\ub85c \uc5d4\uc9c4\uc774 \uaebc\uc9c4 \ud6c4).", + "title": "Subaru Starlink \uc635\uc158" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/nl.json b/homeassistant/components/subaru/translations/nl.json index 5a9bd4119ff09a..339c990ca0bb68 100644 --- a/homeassistant/components/subaru/translations/nl.json +++ b/homeassistant/components/subaru/translations/nl.json @@ -16,6 +16,7 @@ "data": { "pin": "PIN" }, + "description": "Voer uw MySubaru-pincode in\n OPMERKING: Alle voertuigen in een account moeten dezelfde pincode hebben", "title": "Subaru Starlink Configuratie" }, "user": { @@ -24,6 +25,7 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, + "description": "Voer uw MySubaru inloggegevens in\nOPMERKING: De eerste installatie kan tot 30 seconden duren", "title": "Subaru Starlink-configuratie" } } @@ -31,6 +33,10 @@ "options": { "step": { "init": { + "data": { + "update_enabled": "Voertuigpeiling inschakelen" + }, + "description": "Wanneer deze optie is ingeschakeld, zal voertuigpeiling om de 2 uur een opdracht op afstand naar uw voertuig sturen om nieuwe sensorgegevens te verkrijgen. Zonder voertuigpeiling worden nieuwe sensorgegevens alleen ontvangen wanneer het voertuig automatisch gegevens doorstuurt (normaal gesproken na het uitschakelen van de motor).", "title": "Subaru Starlink-opties" } } diff --git a/homeassistant/components/sun/translations/id.json b/homeassistant/components/sun/translations/id.json index 374da4e0db9293..df6c960e67d0e0 100644 --- a/homeassistant/components/sun/translations/id.json +++ b/homeassistant/components/sun/translations/id.json @@ -2,7 +2,7 @@ "state": { "_": { "above_horizon": "Terbit", - "below_horizon": "Tenggelam" + "below_horizon": "Terbenam" } }, "title": "Matahari" diff --git a/homeassistant/components/switch/translations/id.json b/homeassistant/components/switch/translations/id.json index 891b1b00681ce2..070d272aa4344a 100644 --- a/homeassistant/components/switch/translations/id.json +++ b/homeassistant/components/switch/translations/id.json @@ -1,8 +1,23 @@ { + "device_automation": { + "action_type": { + "toggle": "Nyala/matikan {entity_name}", + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} mati", + "is_on": "{entity_name} nyala" + }, + "trigger_type": { + "turned_off": "{entity_name} dimatikan", + "turned_on": "{entity_name} dinyalakan" + } + }, "state": { "_": { - "off": "Off", - "on": "On" + "off": "Mati", + "on": "Nyala" } }, "title": "Sakelar" diff --git a/homeassistant/components/switch/translations/ko.json b/homeassistant/components/switch/translations/ko.json index 1779f3e1f648b3..6a3417efeb67d7 100644 --- a/homeassistant/components/switch/translations/ko.json +++ b/homeassistant/components/switch/translations/ko.json @@ -1,17 +1,17 @@ { "device_automation": { "action_type": { - "toggle": "{entity_name} \ud1a0\uae00", - "turn_off": "{entity_name} \ub044\uae30", - "turn_on": "{entity_name} \ucf1c\uae30" + "toggle": "{entity_name}\uc744(\ub97c) \ud1a0\uae00\ud558\uae30", + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" }, "condition_type": { - "is_off": "{entity_name} \uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", - "is_on": "{entity_name} \uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" + "is_off": "{entity_name}\uc774(\uac00) \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name}\uc774(\uac00) \ucf1c\uc838 \uc788\uc73c\uba74" }, "trigger_type": { - "turned_off": "{entity_name} \uc774(\uac00) \uaebc\uc9c8 \ub54c", - "turned_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c8 \ub54c" + "turned_off": "{entity_name}\uc774(\uac00) \uaebc\uc84c\uc744 \ub54c", + "turned_on": "{entity_name}\uc774(\uac00) \ucf1c\uc84c\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/switch/translations/zh-Hans.json b/homeassistant/components/switch/translations/zh-Hans.json index a18455aec6a862..afe9f58db8f9d8 100644 --- a/homeassistant/components/switch/translations/zh-Hans.json +++ b/homeassistant/components/switch/translations/zh-Hans.json @@ -7,7 +7,7 @@ }, "condition_type": { "is_off": "{entity_name} \u5df2\u5173\u95ed", - "is_on": "{entity_name} \u5df2\u5f00\u542f" + "is_on": "{entity_name} \u5df2\u6253\u5f00" }, "trigger_type": { "turned_off": "{entity_name} \u88ab\u5173\u95ed", diff --git a/homeassistant/components/syncthru/translations/hu.json b/homeassistant/components/syncthru/translations/hu.json index 3b2d79a34a77e2..227b759ffaf163 100644 --- a/homeassistant/components/syncthru/translations/hu.json +++ b/homeassistant/components/syncthru/translations/hu.json @@ -2,6 +2,23 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_url": "\u00c9rv\u00e9nytelen URL" + }, + "step": { + "confirm": { + "data": { + "name": "N\u00e9v", + "url": "Webes fel\u00fclet URL-je" + } + }, + "user": { + "data": { + "name": "N\u00e9v", + "url": "Webes fel\u00fclet URL-je" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/id.json b/homeassistant/components/syncthru/translations/id.json new file mode 100644 index 00000000000000..54d5e6f5c969e2 --- /dev/null +++ b/homeassistant/components/syncthru/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "invalid_url": "URL tidak valid", + "syncthru_not_supported": "Perangkat tidak mendukung SyncThru", + "unknown_state": "Status printer tidak diketahui, verifikasi URL dan konektivitas jaringan" + }, + "flow_title": "Printer Samsung SyncThru: {name}", + "step": { + "confirm": { + "data": { + "name": "Nama", + "url": "URL antarmuka web" + } + }, + "user": { + "data": { + "name": "Nama", + "url": "URL antarmuka web" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index 799e19ea37104f..e569d1d6322dc2 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -4,13 +4,15 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "invalid_url": "Ongeldige URL", "unknown_state": "Printerstatus onbekend, controleer URL en netwerkconnectiviteit" }, "flow_title": "Samsung SyncThru Printer: {name}", "step": { "confirm": { "data": { - "name": "Naam" + "name": "Naam", + "url": "Webinterface URL" } }, "user": { diff --git a/homeassistant/components/synology_dsm/translations/he.json b/homeassistant/components/synology_dsm/translations/he.json index 98b3a2214d746f..8135fba13e030e 100644 --- a/homeassistant/components/synology_dsm/translations/he.json +++ b/homeassistant/components/synology_dsm/translations/he.json @@ -1,4 +1,20 @@ { + "config": { + "step": { + "link": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + }, + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4", + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/synology_dsm/translations/hu.json b/homeassistant/components/synology_dsm/translations/hu.json index 29e520de432d6e..c26fd349f06646 100644 --- a/homeassistant/components/synology_dsm/translations/hu.json +++ b/homeassistant/components/synology_dsm/translations/hu.json @@ -1,23 +1,40 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "cannot_connect": "Sikertelen csatlakoz\u00e1s" + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, + "flow_title": "Synology DSM {name} ({host})", "step": { + "2sa": { + "data": { + "otp_code": "K\u00f3d" + } + }, "link": { "data": { "password": "Jelsz\u00f3", "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "title": "Synology DSM" }, "user": { "data": { "host": "Hoszt", "password": "Jelsz\u00f3", "port": "Port", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v" - } + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v", + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" + }, + "title": "Synology DSM" } } } diff --git a/homeassistant/components/synology_dsm/translations/id.json b/homeassistant/components/synology_dsm/translations/id.json new file mode 100644 index 00000000000000..e614c2578d4c1a --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/id.json @@ -0,0 +1,55 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "missing_data": "Data tidak tersedia: coba lagi nanti atau konfigurasikan lainnya", + "otp_failed": "Autentikasi dua langkah gagal, coba lagi dengan kode sandi baru", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "2sa": { + "data": { + "otp_code": "Kode" + }, + "title": "Synology DSM: autentikasi dua langkah" + }, + "link": { + "data": { + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "description": "Ingin menyiapkan {name} ({host})?", + "title": "Synology DSM" + }, + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Synology DSM" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pemindaian dalam menit", + "timeout": "Tenggang waktu (detik)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index d4932064a60057..be8d7d4534851e 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken", @@ -21,8 +21,8 @@ "link": { "data": { "password": "Wachtwoord", - "port": "Poort (optioneel)", - "ssl": "Gebruik SSL/TLS om verbinding te maken met uw NAS", + "port": "Poort", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Controleer het SSL-certificaat" }, @@ -33,8 +33,8 @@ "data": { "host": "Host", "password": "Wachtwoord", - "port": "Poort (optioneel)", - "ssl": "Gebruik SSL/TLS om verbinding te maken met uw NAS", + "port": "Poort", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", "verify_ssl": "Controleer het SSL-certificaat" }, diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index d5e78faf91c950..19a231ccd60526 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -7,7 +7,7 @@ "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "missing_data": "\u7f3a\u5c11\u8cc7\u6599\uff1a\u8acb\u7a0d\u5f8c\u91cd\u8a66\u6216\u4f7f\u7528\u5176\u4ed6\u8a2d\u5b9a", - "otp_failed": "\u5169\u6b65\u9a5f\u9a57\u8b49\u5931\u6557\uff0c\u8acb\u91cd\u65b0\u53d6\u5f97\u4ee3\u78bc\u5f8c\u91cd\u8a66", + "otp_failed": "\u96d9\u91cd\u8a8d\u8b49\u5931\u6557\uff0c\u8acb\u91cd\u65b0\u53d6\u5f97\u4ee3\u78bc\u5f8c\u91cd\u8a66", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "\u7fa4\u6689 DSM {name} ({host})", @@ -16,7 +16,7 @@ "data": { "otp_code": "\u4ee3\u78bc" }, - "title": "Synology DSM\uff1a\u96d9\u91cd\u9a57\u8b49" + "title": "Synology DSM\uff1a\u96d9\u91cd\u8a8d\u8b49" }, "link": { "data": { diff --git a/homeassistant/components/system_health/translations/id.json b/homeassistant/components/system_health/translations/id.json new file mode 100644 index 00000000000000..309a6dd38d08d9 --- /dev/null +++ b/homeassistant/components/system_health/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Kesehatan Sistem" +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/he.json b/homeassistant/components/tado/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/tado/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/tado/translations/hu.json b/homeassistant/components/tado/translations/hu.json index dee4ed9ee0fa4d..fd8db27da5efd0 100644 --- a/homeassistant/components/tado/translations/hu.json +++ b/homeassistant/components/tado/translations/hu.json @@ -1,5 +1,13 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tado/translations/id.json b/homeassistant/components/tado/translations/id.json new file mode 100644 index 00000000000000..bdbfa19cb6d6ab --- /dev/null +++ b/homeassistant/components/tado/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "no_homes": "Tidak ada rumah yang ditautkan ke akun Tado ini.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Hubungkan ke akun Tado Anda" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Aktifkan mode alternatif." + }, + "title": "Sesuaikan opsi Tado." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/ko.json b/homeassistant/components/tado/translations/ko.json index 8290e32c4c868a..29603d8f6d2bd0 100644 --- a/homeassistant/components/tado/translations/ko.json +++ b/homeassistant/components/tado/translations/ko.json @@ -25,7 +25,7 @@ "data": { "fallback": "\ub300\uccb4 \ubaa8\ub4dc\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4." }, - "description": "\uc601\uc5ed\uc744 \uc218\ub3d9\uc73c\ub85c \uc804\ud658\ud558\uba74 \ub300\uccb4 \ubaa8\ub4dc\ub294 \ub2e4\uc74c \uc77c\uc815\uc744 \uc2a4\ub9c8\ud2b8 \uc77c\uc815\uc73c\ub85c \uc804\ud658\ud569\ub2c8\ub2e4.", + "description": "\ub300\uccb4 \ubaa8\ub4dc\ub294 \uc9c0\uc5ed\uc744 \uc218\ub3d9\uc73c\ub85c \uc870\uc815\ud55c \ud6c4 \ub2e4\uc74c \uc77c\uc815 \uc804\ud658\uc2dc \uc2a4\ub9c8\ud2b8 \uc77c\uc815\uc73c\ub85c \uc804\ud658\ub429\ub2c8\ub2e4.", "title": "Tado \uc635\uc158 \uc870\uc815\ud558\uae30" } } diff --git a/homeassistant/components/tado/translations/nl.json b/homeassistant/components/tado/translations/nl.json index 3cdadf0f54e1c6..3b6d914b71c49b 100644 --- a/homeassistant/components/tado/translations/nl.json +++ b/homeassistant/components/tado/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "no_homes": "Er zijn geen huizen gekoppeld aan dit tado-account.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/tag/translations/hu.json b/homeassistant/components/tag/translations/hu.json new file mode 100644 index 00000000000000..edea7ba32ef265 --- /dev/null +++ b/homeassistant/components/tag/translations/hu.json @@ -0,0 +1,3 @@ +{ + "title": "C\u00edmke" +} \ No newline at end of file diff --git a/homeassistant/components/tag/translations/id.json b/homeassistant/components/tag/translations/id.json new file mode 100644 index 00000000000000..fdac700612daf4 --- /dev/null +++ b/homeassistant/components/tag/translations/id.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/hu.json b/homeassistant/components/tasmota/translations/hu.json index c76efd0e898325..4461f2a2b71a44 100644 --- a/homeassistant/components/tasmota/translations/hu.json +++ b/homeassistant/components/tasmota/translations/hu.json @@ -1,8 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "step": { "config": { + "description": "Add meg a Tasmota konfigur\u00e1ci\u00f3t.", "title": "Tasmota" + }, + "confirm": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Tasmota-t?" } } } diff --git a/homeassistant/components/tasmota/translations/id.json b/homeassistant/components/tasmota/translations/id.json new file mode 100644 index 00000000000000..a11acf50390f10 --- /dev/null +++ b/homeassistant/components/tasmota/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_discovery_topic": "Prefiks topik penemuan tidak valid." + }, + "step": { + "config": { + "data": { + "discovery_prefix": "Prefiks topik penemuan" + }, + "description": "Masukkan konfigurasi Tasmota.", + "title": "Tasmota" + }, + "confirm": { + "description": "Ingin menyiapkan Tasmota?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/ko.json b/homeassistant/components/tasmota/translations/ko.json index c6e52d209e7bfc..45cac13f622034 100644 --- a/homeassistant/components/tasmota/translations/ko.json +++ b/homeassistant/components/tasmota/translations/ko.json @@ -1,7 +1,22 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_discovery_topic": "\uac80\uc0c9 \ud1a0\ud53d \uc811\ub450\uc0ac\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "config": { + "data": { + "discovery_prefix": "\uac80\uc0c9 \ud1a0\ud53d \uc811\ub450\uc0ac" + }, + "description": "Tasmota \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "Tasmota" + }, + "confirm": { + "description": "Tasmota\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/hu.json b/homeassistant/components/tellduslive/translations/hu.json index 748189e6427009..2e59375369dbd3 100644 --- a/homeassistant/components/tellduslive/translations/hu.json +++ b/homeassistant/components/tellduslive/translations/hu.json @@ -1,16 +1,20 @@ { "config": { "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van", "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", - "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt" + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "user": { "data": { "host": "Hoszt" }, - "description": "\u00dcres", "title": "V\u00e1lassz v\u00e9gpontot." } } diff --git a/homeassistant/components/tellduslive/translations/id.json b/homeassistant/components/tellduslive/translations/id.json new file mode 100644 index 00000000000000..1a405e794fefb4 --- /dev/null +++ b/homeassistant/components/tellduslive/translations/id.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi", + "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "unknown": "Kesalahan yang tidak diharapkan", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "auth": { + "description": "Untuk menautkan akun TelldusLive Anda:\n 1. Klik tautan di bawah ini\n 2. Masuk ke Telldus Live\n 3. Otorisasi **{app_name}** (klik **Yes**).\n 4. Kembali ke sini dan klik **KIRIM**.\n\n[Akun Link TelldusLive] ({auth_url})", + "title": "Autentikasi ke TelldusLive" + }, + "user": { + "data": { + "host": "Host" + }, + "title": "Pilih titik akhir." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/ko.json b/homeassistant/components/tellduslive/translations/ko.json index d29dd50484414c..6107245c088db2 100644 --- a/homeassistant/components/tellduslive/translations/ko.json +++ b/homeassistant/components/tellduslive/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", "unknown_authorize_url_generation": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." @@ -12,7 +12,7 @@ }, "step": { "auth": { - "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **\ud655\uc778**\uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", + "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live\uc5d0 \ub85c\uadf8\uc778\ud574\uc8fc\uc138\uc694\n 3. **{app_name}**\uc744(\ub97c) \uc778\uc99d\ud574\uc8fc\uc138\uc694 (**Yes**\ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **\ud655\uc778**\uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", "title": "TelldusLive \uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/tellduslive/translations/nl.json b/homeassistant/components/tellduslive/translations/nl.json index 4eb6d40a14267f..c34911553ab6c4 100644 --- a/homeassistant/components/tellduslive/translations/nl.json +++ b/homeassistant/components/tellduslive/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Service is al geconfigureerd", "authorize_url_fail": "Onbekende fout bij het genereren van een autorisatie url.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "unknown": "Onbekende fout opgetreden", + "unknown": "Onverwachte fout", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." }, "error": { diff --git a/homeassistant/components/tesla/translations/he.json b/homeassistant/components/tesla/translations/he.json new file mode 100644 index 00000000000000..ac90b3264eab33 --- /dev/null +++ b/homeassistant/components/tesla/translations/he.json @@ -0,0 +1,12 @@ +{ + "config": { + "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/tesla/translations/hu.json b/homeassistant/components/tesla/translations/hu.json index c6553d4a595a35..a4622ce7efa15a 100644 --- a/homeassistant/components/tesla/translations/hu.json +++ b/homeassistant/components/tesla/translations/hu.json @@ -1,5 +1,14 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/tesla/translations/id.json b/homeassistant/components/tesla/translations/id.json new file mode 100644 index 00000000000000..681504d0d42a6d --- /dev/null +++ b/homeassistant/components/tesla/translations/id.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "already_configured": "Akun sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "description": "Masukkan informasi Anda.", + "title": "Tesla - Konfigurasi" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "enable_wake_on_start": "Paksa mobil bangun saat dinyalakan", + "scan_interval": "Interval pemindaian dalam detik" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/nl.json b/homeassistant/components/tesla/translations/nl.json index f6289de6d9d6d9..5655a641f96ee7 100644 --- a/homeassistant/components/tesla/translations/nl.json +++ b/homeassistant/components/tesla/translations/nl.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "description": "Vul alstublieft uw gegevens in.", "title": "Tesla - Configuratie" diff --git a/homeassistant/components/tibber/translations/hu.json b/homeassistant/components/tibber/translations/hu.json index 08a622fd238c01..6ad5902284518d 100644 --- a/homeassistant/components/tibber/translations/hu.json +++ b/homeassistant/components/tibber/translations/hu.json @@ -1,14 +1,20 @@ { "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token", + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a Tibberhez val\u00f3 csatlakoz\u00e1skor" }, "step": { "user": { "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" - } + }, + "description": "Add meg a hozz\u00e1f\u00e9r\u00e9si tokent a https://developer.tibber.com/settings/accesstoken c\u00edmr\u0151l", + "title": "Tibber" } } } diff --git a/homeassistant/components/tibber/translations/id.json b/homeassistant/components/tibber/translations/id.json new file mode 100644 index 00000000000000..479cf83f8c7617 --- /dev/null +++ b/homeassistant/components/tibber/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Layanan sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_access_token": "Token akses tidak valid", + "timeout": "Tenggang waktu terhubung ke Tibber habis" + }, + "step": { + "user": { + "data": { + "access_token": "Token Akses" + }, + "description": "Masukkan token akses Anda dari https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/hu.json b/homeassistant/components/tile/translations/hu.json new file mode 100644 index 00000000000000..c4a6e63030ce9b --- /dev/null +++ b/homeassistant/components/tile/translations/hu.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" + }, + "title": "Tile konfigur\u00e1l\u00e1sa" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Inakt\u00edv Tile-ok megjelen\u00edt\u00e9se" + }, + "title": "Tile konfigur\u00e1l\u00e1sa" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tile/translations/id.json b/homeassistant/components/tile/translations/id.json new file mode 100644 index 00000000000000..5b5c710594d56f --- /dev/null +++ b/homeassistant/components/tile/translations/id.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Konfigurasi Tile" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Tampilkan Tile yang tidak aktif" + }, + "title": "Konfigurasi Tile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/hu.json b/homeassistant/components/toon/translations/hu.json new file mode 100644 index 00000000000000..cd832522870c8c --- /dev/null +++ b/homeassistant/components/toon/translations/hu.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz.", + "unknown_authorize_url_generation": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/id.json b/homeassistant/components/toon/translations/id.json new file mode 100644 index 00000000000000..6e9d4a76683ad0 --- /dev/null +++ b/homeassistant/components/toon/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Perjanjian yang dipilih sudah dikonfigurasi.", + "authorize_url_fail": "Kesalahan tidak dikenal terjadi ketika menghasilkan URL otorisasi.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_agreements": "Akun ini tidak memiliki tampilan Toon.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})", + "unknown_authorize_url_generation": "Kesalahan tidak dikenal ketika menghasilkan URL otorisasi." + }, + "step": { + "agreement": { + "data": { + "agreement": "Persetujuan" + }, + "description": "Pilih alamat persetujuan yang ingin ditambahkan.", + "title": "Pilih persetujuan Anda" + }, + "pick_implementation": { + "title": "Pilih penyewa Anda untuk diautentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/ko.json b/homeassistant/components/toon/translations/ko.json index faed1fe74d77d7..e36adba2ffb560 100644 --- a/homeassistant/components/toon/translations/ko.json +++ b/homeassistant/components/toon/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uc120\ud0dd\ub41c \uc57d\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "authorize_url_fail": "\uc778\uc99d URL \uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "authorize_url_fail": "\uc778\uc99d URL\uc744 \uc0dd\uc131\ud558\ub294 \ub3d9\uc548 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", "no_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 090d9271dee5a0..85797fa901ee1a 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -9,6 +9,9 @@ }, "step": { "locations": { + "data": { + "location": "Localizaci\u00f3n" + }, "description": "Ingrese el c\u00f3digo de usuario para este usuario en esta ubicaci\u00f3n", "title": "C\u00f3digos de usuario de ubicaci\u00f3n" }, diff --git a/homeassistant/components/totalconnect/translations/he.json b/homeassistant/components/totalconnect/translations/he.json new file mode 100644 index 00000000000000..ed07845a182fec --- /dev/null +++ b/homeassistant/components/totalconnect/translations/he.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "locations": { + "data": { + "location": "\u05de\u05d9\u05e7\u05d5\u05dd" + } + }, + "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/totalconnect/translations/hu.json b/homeassistant/components/totalconnect/translations/hu.json index dee4ed9ee0fa4d..6002f05663576d 100644 --- a/homeassistant/components/totalconnect/translations/hu.json +++ b/homeassistant/components/totalconnect/translations/hu.json @@ -1,6 +1,21 @@ { "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { + "locations": { + "data": { + "location": "Elhelyezked\u00e9s" + } + }, + "reauth_confirm": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "password": "Jelsz\u00f3", diff --git a/homeassistant/components/totalconnect/translations/id.json b/homeassistant/components/totalconnect/translations/id.json new file mode 100644 index 00000000000000..c1bdf6649948a2 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/id.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Akun sudah dikonfigurasi", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "invalid_auth": "Autentikasi tidak valid", + "usercode": "Kode pengguna tidak valid untuk pengguna ini di lokasi ini" + }, + "step": { + "locations": { + "data": { + "location": "Lokasi" + }, + "description": "Masukkan kode pengguna untuk pengguna ini di lokasi ini", + "title": "Lokasi Kode Pengguna" + }, + "reauth_confirm": { + "description": "Total Connect perlu mengautentikasi ulang akun Anda", + "title": "Autentikasi Ulang Integrasi" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/ko.json b/homeassistant/components/totalconnect/translations/ko.json index c074472b8f4c6d..354522154b59de 100644 --- a/homeassistant/components/totalconnect/translations/ko.json +++ b/homeassistant/components/totalconnect/translations/ko.json @@ -1,12 +1,25 @@ { "config": { "abort": { - "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "usercode": "\uc774 \uc704\uce58\uc758 \ud574\ub2f9 \uc0ac\uc6a9\uc790\uc5d0 \ub300\ud55c \uc0ac\uc6a9\uc790 \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "step": { + "locations": { + "data": { + "location": "\uc704\uce58" + }, + "description": "\uc774 \uc704\uce58\uc758 \ud574\ub2f9 \uc0ac\uc6a9\uc790\uc5d0 \ub300\ud55c \uc0ac\uc6a9\uc790 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\uc704\uce58 \uc0ac\uc6a9\uc790 \ucf54\ub4dc" + }, + "reauth_confirm": { + "description": "Total Connect\ub294 \uacc4\uc815\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4", + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" + }, "user": { "data": { "password": "\ube44\ubc00\ubc88\ud638", diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 94d8e3ac01ee9a..de20d40bee6a1e 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -1,19 +1,23 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd", + "already_configured": "Account is al geconfigureerd", "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "invalid_auth": "Ongeldige authenticatie" + "invalid_auth": "Ongeldige authenticatie", + "usercode": "Gebruikerscode niet geldig voor deze gebruiker op deze locatie" }, "step": { "locations": { "data": { "location": "Locatie" - } + }, + "description": "Voer de gebruikerscode voor deze gebruiker op deze locatie in", + "title": "Locatie gebruikerscodes" }, "reauth_confirm": { + "description": "Total Connect moet uw account opnieuw verifi\u00ebren", "title": "Verifieer de integratie opnieuw" }, "user": { diff --git a/homeassistant/components/tplink/translations/hu.json b/homeassistant/components/tplink/translations/hu.json new file mode 100644 index 00000000000000..ab799e90c74001 --- /dev/null +++ b/homeassistant/components/tplink/translations/hu.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/id.json b/homeassistant/components/tplink/translations/id.json new file mode 100644 index 00000000000000..66d510de4edc6c --- /dev/null +++ b/homeassistant/components/tplink/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan perangkat cerdas TP-Link?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/ko.json b/homeassistant/components/tplink/translations/ko.json index e1ff7eff3722ca..a1bfb59ca0719b 100644 --- a/homeassistant/components/tplink/translations/ko.json +++ b/homeassistant/components/tplink/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/nl.json b/homeassistant/components/tplink/translations/nl.json index f6a8dbbe02a2e5..362645d9f1955b 100644 --- a/homeassistant/components/tplink/translations/nl.json +++ b/homeassistant/components/tplink/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen TP-Link apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie is nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/traccar/translations/hu.json b/homeassistant/components/traccar/translations/hu.json index a14c446e673fae..c4fc027d059c67 100644 --- a/homeassistant/components/traccar/translations/hu.json +++ b/homeassistant/components/traccar/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Traccar-ban. \n\n Haszn\u00e1lja a k\u00f6vetkez\u0151 URL-t: \" {webhook_url} \" \n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t] ( {docs_url} )." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a webhook funkci\u00f3t a Traccar-ban. \n\n Haszn\u00e1lja a k\u00f6vetkez\u0151 URL-t: `{webhook_url}`\n\n Tov\u00e1bbi r\u00e9szletek\u00e9rt l\u00e1sd a [dokument\u00e1ci\u00f3t]({docs_url})." } } } \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/id.json b/homeassistant/components/traccar/translations/id.json new file mode 100644 index 00000000000000..573b73570c2310 --- /dev/null +++ b/homeassistant/components/traccar/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan fitur webhook di Traccar.\n\nGunakan URL berikut: {webhook_url}`\n\nBaca [dokumentasi]({docs_url}) untuk detail lebih lanjut." + }, + "step": { + "user": { + "description": "Yakin ingin menyiapkan Traccar?", + "title": "Siapkan Traccar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/ko.json b/homeassistant/components/traccar/translations/ko.json index 04e13a9aa6f91a..2af5079ae2de61 100644 --- a/homeassistant/components/traccar/translations/ko.json +++ b/homeassistant/components/traccar/translations/ko.json @@ -1,15 +1,15 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar \uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "Traccar \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Traccar\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Traccar \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/tradfri/translations/hu.json b/homeassistant/components/tradfri/translations/hu.json index 8be065fe7971a6..3bc4ec90e77dd0 100644 --- a/homeassistant/components/tradfri/translations/hu.json +++ b/homeassistant/components/tradfri/translations/hu.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van." }, "error": { - "cannot_connect": "Nem siker\u00fclt csatlakozni a gatewayhez.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_key": "Nem siker\u00fclt regisztr\u00e1lni a megadott kulcs seg\u00edts\u00e9g\u00e9vel. Ha ez t\u00f6bbsz\u00f6r megt\u00f6rt\u00e9nik, pr\u00f3b\u00e1lja meg \u00fajraind\u00edtani a gatewayt.", "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n." }, diff --git a/homeassistant/components/tradfri/translations/id.json b/homeassistant/components/tradfri/translations/id.json index 0671b162e1c758..8ff5fe257ebbe5 100644 --- a/homeassistant/components/tradfri/translations/id.json +++ b/homeassistant/components/tradfri/translations/id.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge sudah dikonfigurasi" + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung" }, "error": { - "cannot_connect": "Tidak dapat terhubung ke gateway.", + "cannot_connect": "Gagal terhubung", "invalid_key": "Gagal mendaftar dengan kunci yang disediakan. Jika ini terus terjadi, coba mulai ulang gateway.", "timeout": "Waktu tunggu memvalidasi kode telah habis." }, @@ -12,7 +13,7 @@ "auth": { "data": { "host": "Host", - "security_code": "Kode keamanan" + "security_code": "Kode Keamanan" }, "description": "Anda dapat menemukan kode keamanan di belakang gateway Anda.", "title": "Masukkan kode keamanan" diff --git a/homeassistant/components/tradfri/translations/ko.json b/homeassistant/components/tradfri/translations/ko.json index 067a10c6490b33..307ba39ceaf6bf 100644 --- a/homeassistant/components/tradfri/translations/ko.json +++ b/homeassistant/components/tradfri/translations/ko.json @@ -16,7 +16,7 @@ "security_code": "\ubcf4\uc548 \ucf54\ub4dc" }, "description": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ub4b7\uba74\uc5d0\uc11c \ubcf4\uc548 \ucf54\ub4dc\ub97c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "\ubcf4\uc548 \ucf54\ub4dc \uc785\ub825\ud558\uae30" + "title": "\ubcf4\uc548 \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/tradfri/translations/nl.json b/homeassistant/components/tradfri/translations/nl.json index 1d0453704d0638..874b6cff81c8e8 100644 --- a/homeassistant/components/tradfri/translations/nl.json +++ b/homeassistant/components/tradfri/translations/nl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "Bridge configuratie is al in volle gang." }, "error": { - "cannot_connect": "Kan geen verbinding maken met bridge", + "cannot_connect": "Kan geen verbinding maken", "invalid_key": "Mislukt om te registreren met de meegeleverde sleutel. Als dit blijft gebeuren, probeer dan de gateway opnieuw op te starten.", "timeout": "Time-out bij validatie van code" }, diff --git a/homeassistant/components/transmission/translations/he.json b/homeassistant/components/transmission/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/transmission/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/hu.json b/homeassistant/components/transmission/translations/hu.json index f2fd2ca79e5f62..22d4e18df5e925 100644 --- a/homeassistant/components/transmission/translations/hu.json +++ b/homeassistant/components/transmission/translations/hu.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "cannot_connect": "Nem lehet csatlakozni az \u00e1llom\u00e1shoz", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "name_exists": "A n\u00e9v m\u00e1r l\u00e9tezik" }, "step": { @@ -21,6 +25,8 @@ "step": { "init": { "data": { + "limit": "Limit", + "order": "Sorrend", "scan_interval": "Friss\u00edt\u00e9si gyakoris\u00e1g" } } diff --git a/homeassistant/components/transmission/translations/id.json b/homeassistant/components/transmission/translations/id.json new file mode 100644 index 00000000000000..a96524f51658f0 --- /dev/null +++ b/homeassistant/components/transmission/translations/id.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "name_exists": "Nama sudah ada" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nama", + "password": "Kata Sandi", + "port": "Port", + "username": "Nama Pengguna" + }, + "title": "Siapkan Klien Transmission" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "limit": "Batas", + "order": "Urutan", + "scan_interval": "Frekuensi pembaruan" + }, + "title": "Konfigurasikan opsi untuk Transmission" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/ko.json b/homeassistant/components/transmission/translations/ko.json index 002e374e54d179..898a371035043f 100644 --- a/homeassistant/components/transmission/translations/ko.json +++ b/homeassistant/components/transmission/translations/ko.json @@ -29,7 +29,7 @@ "order": "\uc21c\uc11c", "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4" }, - "title": "Transmission \uc635\uc158 \uc124\uc815\ud558\uae30" + "title": "Transmission\uc5d0 \ub300\ud55c \uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index 67a61f81a1c5b3..cf5d3dfffe8857 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -12,6 +12,7 @@ "step": { "user": { "data": { + "country_code": "L\u00e4ndercode Ihres Kontos (z. B. 1 f\u00fcr USA oder 86 f\u00fcr China)", "password": "Passwort", "username": "Benutzername" }, diff --git a/homeassistant/components/tuya/translations/hu.json b/homeassistant/components/tuya/translations/hu.json index b128be67087541..b45148f80b8d3d 100644 --- a/homeassistant/components/tuya/translations/hu.json +++ b/homeassistant/components/tuya/translations/hu.json @@ -2,11 +2,11 @@ "config": { "abort": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "flow_title": "Tuya konfigur\u00e1ci\u00f3", "step": { @@ -27,7 +27,7 @@ "cannot_connect": "A kapcsol\u00f3d\u00e1s nem siker\u00fclt" }, "error": { - "dev_multi_type": "A konfigur\u00e1land\u00f3 eszk\u00f6z\u00f6knek azonos t\u00edpus\u00faaknak kell lennie", + "dev_multi_type": "T\u00f6bb kiv\u00e1lasztott konfigur\u00e1land\u00f3 eszk\u00f6znek azonos t\u00edpus\u00fanak kell lennie", "dev_not_config": "Ez az eszk\u00f6zt\u00edpus nem konfigur\u00e1lhat\u00f3", "dev_not_found": "Eszk\u00f6z nem tal\u00e1lhat\u00f3" }, @@ -45,18 +45,18 @@ "tuya_max_coltemp": "Az eszk\u00f6z \u00e1ltal megadott maxim\u00e1lis sz\u00ednh\u0151m\u00e9rs\u00e9klet", "unit_of_measurement": "Az eszk\u00f6z \u00e1ltal haszn\u00e1lt h\u0151m\u00e9rs\u00e9kleti egys\u00e9g" }, - "description": "Konfigur\u00e1lja a(z) {device_type} eszk\u00f6zt \" {device_name} {device_type} \" megjelen\u00edtett inform\u00e1ci\u00f3inak be\u00e1ll\u00edt\u00e1s\u00e1hoz", - "title": "Konfigur\u00e1lja a Tuya eszk\u00f6zt" + "description": "Konfigur\u00e1l\u00e1si lehet\u0151s\u00e9gek a(z) {device_type} t\u00edpus\u00fa `{device_name}` eszk\u00f6z megjelen\u00edtett inform\u00e1ci\u00f3inak be\u00e1ll\u00edt\u00e1s\u00e1hoz", + "title": "Tuya eszk\u00f6z konfigur\u00e1l\u00e1sa" }, "init": { "data": { "discovery_interval": "Felfedez\u0151 eszk\u00f6z lek\u00e9rdez\u00e9si intervalluma m\u00e1sodpercben", - "list_devices": "V\u00e1lassza ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6z\u00f6ket, vagy hagyja \u00fcresen a konfigur\u00e1ci\u00f3 ment\u00e9s\u00e9hez", - "query_device": "V\u00e1lassza ki azt az eszk\u00f6zt, amely a lek\u00e9rdez\u00e9si m\u00f3dszert haszn\u00e1lja a gyorsabb \u00e1llapotfriss\u00edt\u00e9shez", + "list_devices": "V\u00e1laszd ki a konfigur\u00e1lni k\u00edv\u00e1nt eszk\u00f6z\u00f6ket, vagy hagyd \u00fcresen a konfigur\u00e1ci\u00f3 ment\u00e9s\u00e9hez", + "query_device": "V\u00e1laszd ki azt az eszk\u00f6zt, amely a lek\u00e9rdez\u00e9si m\u00f3dszert haszn\u00e1lja a gyorsabb \u00e1llapotfriss\u00edt\u00e9shez", "query_interval": "Eszk\u00f6z lek\u00e9rdez\u00e9si id\u0151k\u00f6ze m\u00e1sodpercben" }, - "description": "Ne \u00e1ll\u00edtsa t\u00fal alacsonyra a lek\u00e9rdez\u00e9si intervallum \u00e9rt\u00e9keit, k\u00fcl\u00f6nben a h\u00edv\u00e1sok nem fognak hiba\u00fczenetet gener\u00e1lni a napl\u00f3ban", - "title": "Konfigur\u00e1lja a Tuya be\u00e1ll\u00edt\u00e1sokat" + "description": "Ne \u00e1ll\u00edtsd t\u00fal alacsonyra a lek\u00e9rdez\u00e9si intervallum \u00e9rt\u00e9keit, k\u00fcl\u00f6nben a h\u00edv\u00e1sok nem fognak hiba\u00fczenetet gener\u00e1lni a napl\u00f3ban", + "title": "Tuya be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" } } } diff --git a/homeassistant/components/tuya/translations/id.json b/homeassistant/components/tuya/translations/id.json new file mode 100644 index 00000000000000..bb338e12752879 --- /dev/null +++ b/homeassistant/components/tuya/translations/id.json @@ -0,0 +1,65 @@ +{ + "config": { + "abort": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "Konfigurasi Tuya", + "step": { + "user": { + "data": { + "country_code": "Kode negara akun Anda (mis., 1 untuk AS atau 86 untuk China)", + "password": "Kata Sandi", + "platform": "Aplikasi tempat akun Anda mendaftar", + "username": "Nama Pengguna" + }, + "description": "Masukkan kredensial Tuya Anda.", + "title": "Tuya" + } + } + }, + "options": { + "abort": { + "cannot_connect": "Gagal terhubung" + }, + "error": { + "dev_multi_type": "Untuk konfigurasi sekaligus, beberapa perangkat yang dipilih harus berjenis sama", + "dev_not_config": "Jenis perangkat tidak dapat dikonfigurasi", + "dev_not_found": "Perangkat tidak ditemukan" + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "Rentang kecerahan yang digunakan oleh perangkat", + "curr_temp_divider": "Pembagi nilai suhu saat ini (0 = gunakan bawaan)", + "max_kelvin": "Suhu warna maksimal yang didukung dalam Kelvin", + "max_temp": "Suhu target maksimal (gunakan min dan maks = 0 untuk bawaan)", + "min_kelvin": "Suhu warna minimal yang didukung dalam Kelvin", + "min_temp": "Suhu target minimal (gunakan min dan maks = 0 untuk bawaan)", + "set_temp_divided": "Gunakan nilai suhu terbagi untuk mengirimkan perintah mengatur suhu", + "support_color": "Paksa dukungan warna", + "temp_divider": "Pembagi nilai suhu (0 = gunakan bawaan)", + "temp_step_override": "Langkah Suhu Target", + "tuya_max_coltemp": "Suhu warna maksimal yang dilaporkan oleh perangkat", + "unit_of_measurement": "Satuan suhu yang digunakan oleh perangkat" + }, + "description": "Konfigurasikan opsi untuk menyesuaikan informasi yang ditampilkan untuk perangkat {device_type} `{device_name}`", + "title": "Konfigurasi Perangkat Tuya" + }, + "init": { + "data": { + "discovery_interval": "Interval polling penemuan perangkat dalam detik", + "list_devices": "Pilih perangkat yang akan dikonfigurasi atau biarkan kosong untuk menyimpan konfigurasi", + "query_device": "Pilih perangkat yang akan menggunakan metode kueri untuk pembaruan status lebih cepat", + "query_interval": "Interval polling perangkat kueri dalam detik" + }, + "description": "Jangan atur nilai interval polling terlalu rendah karena panggilan akan gagal menghasilkan pesan kesalahan dalam log", + "title": "Konfigurasikan Opsi Tuya" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index 81dd2689b0c08f..3e3fbc68bee4f9 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -25,6 +25,41 @@ "options": { "abort": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "dev_multi_type": "\uc120\ud0dd\ud55c \uc5ec\ub7ec \uae30\uae30\ub97c \uad6c\uc131\ud558\ub824\uba74 \uc720\ud615\uc774 \ub3d9\uc77c\ud574\uc57c \ud569\ub2c8\ub2e4", + "dev_not_config": "\uae30\uae30 \uc720\ud615\uc744 \uad6c\uc131\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", + "dev_not_found": "\uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \ubc1d\uae30 \ubc94\uc704", + "curr_temp_divider": "\ud604\uc7ac \uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", + "max_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\ub300 \uc0c9\uc628\ub3c4", + "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "min_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\uc18c \uc0c9\uc628\ub3c4", + "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "set_temp_divided": "\uc124\uc815 \uc628\ub3c4 \uba85\ub839\uc5d0 \ubd84\ud560\ub41c \uc628\ub3c4 \uac12 \uc0ac\uc6a9\ud558\uae30", + "support_color": "\uc0c9\uc0c1 \uc9c0\uc6d0 \uac15\uc81c \uc801\uc6a9\ud558\uae30", + "temp_divider": "\uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", + "temp_step_override": "\ud76c\ub9dd \uc628\ub3c4 \ub2e8\uacc4", + "tuya_max_coltemp": "\uae30\uae30\uc5d0\uc11c \ubcf4\uace0\ud55c \ucd5c\ub300 \uc0c9\uc628\ub3c4", + "unit_of_measurement": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704" + }, + "description": "{device_type} `{device_name}` \uae30\uae30\uc5d0 \ub300\ud574 \ud45c\uc2dc\ub418\ub294 \uc815\ubcf4\ub97c \uc870\uc815\ud558\ub294 \uc635\uc158 \uad6c\uc131\ud558\uae30", + "title": "Tuya \uae30\uae30 \uad6c\uc131\ud558\uae30" + }, + "init": { + "data": { + "discovery_interval": "\uae30\uae30 \uac80\uc0c9 \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)", + "list_devices": "\uad6c\uc131\uc744 \uc800\uc7a5\ud558\ub824\uba74 \uad6c\uc131\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud558\uac70\ub098 \ube44\uc6cc \ub450\uc138\uc694", + "query_device": "\ube60\ub978 \uc0c1\ud0dc \uc5c5\ub370\uc774\ud2b8\ub97c \uc704\ud574 \ucffc\ub9ac \ubc29\ubc95\uc744 \uc0ac\uc6a9\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", + "query_interval": "\uae30\uae30 \ucffc\ub9ac \ud3f4\ub9c1 \uac04\uaca9 (\ucd08)" + }, + "description": "\ud3f4\ub9c1 \uac04\uaca9 \uac12\uc744 \ub108\ubb34 \ub0ae\uac8c \uc124\uc815\ud558\uc9c0 \ub9d0\uc544 \uc8fc\uc138\uc694. \uadf8\ub807\uc9c0 \uc54a\uc73c\uba74 \ud638\ucd9c\uc5d0 \uc2e4\ud328\ud558\uace0 \ub85c\uadf8\uc5d0 \uc624\ub958 \uba54\uc2dc\uc9c0\uac00 \uc0dd\uc131\ub429\ub2c8\ub2e4.", + "title": "Tuya \uc635\uc158 \uad6c\uc131\ud558\uae30" + } } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 46a0228843bdb6..55fb320c17e08a 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -31,6 +31,9 @@ }, "step": { "device": { + "data": { + "temp_step_override": "Doeltemperatuur stap" + }, "title": "Configureer Tuya Apparaat" }, "init": { diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index 742ac600c62d9c..92ced00e733d17 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -43,6 +43,7 @@ "set_temp_divided": "U\u017cyj podzielonej warto\u015bci temperatury dla polecenia ustawienia temperatury", "support_color": "Wymu\u015b obs\u0142ug\u0119 kolor\u00f3w", "temp_divider": "Dzielnik warto\u015bci temperatury (0 = u\u017cyj warto\u015bci domy\u015blnej)", + "temp_step_override": "Krok docelowej temperatury", "tuya_max_coltemp": "Maksymalna temperatura barwy raportowana przez urz\u0105dzenie", "unit_of_measurement": "Jednostka temperatury u\u017cywana przez urz\u0105dzenie" }, diff --git a/homeassistant/components/twentemilieu/translations/hu.json b/homeassistant/components/twentemilieu/translations/hu.json index 8f88f82f2e5271..df83a29ec22c8c 100644 --- a/homeassistant/components/twentemilieu/translations/hu.json +++ b/homeassistant/components/twentemilieu/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "A hely m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/twentemilieu/translations/id.json b/homeassistant/components/twentemilieu/translations/id.json new file mode 100644 index 00000000000000..38746dfd12f0c6 --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Lokasi sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_address": "Alamat tidak ditemukan di area layanan Twente Milieu." + }, + "step": { + "user": { + "data": { + "house_letter": "Abjad rumah/tambahan", + "house_number": "Nomor rumah", + "post_code": "Kode pos" + }, + "description": "Siapkan Twente Milieu untuk memberikan informasi pengumpulan sampah di alamat Anda.", + "title": "Twente Milieu" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/ko.json b/homeassistant/components/twentemilieu/translations/ko.json index e6c19d40d0622c..a27df565f1b425 100644 --- a/homeassistant/components/twentemilieu/translations/ko.json +++ b/homeassistant/components/twentemilieu/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc704\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/twilio/translations/de.json b/homeassistant/components/twilio/translations/de.json index 61df22c10f86c7..d9f071e8ff7ad0 100644 --- a/homeassistant/components/twilio/translations/de.json +++ b/homeassistant/components/twilio/translations/de.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "M\u00f6chtest du mit der Einrichtung beginnen?", + "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Twilio-Webhook einrichten" } } diff --git a/homeassistant/components/twilio/translations/hu.json b/homeassistant/components/twilio/translations/hu.json index 913e3d2a7a287c..cd60890dab37c9 100644 --- a/homeassistant/components/twilio/translations/hu.json +++ b/homeassistant/components/twilio/translations/hu.json @@ -1,11 +1,15 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "webhook_not_internet_accessible": "A Home Assistant rendszerednek el\u00e9rhet\u0151nek kell lennie az internetr\u0151l a webhook \u00fczenetek fogad\u00e1s\u00e1hoz." + }, "create_entry": { - "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Webhooks Twilio-val] ( {twilio_url} ) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: \" {webhook_url} \"\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/x-www-form-urlencoded \n\n L\u00e1sd [a dokument\u00e1ci\u00f3] ( {docs_url} ), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." + "default": "Ha esem\u00e9nyeket szeretne k\u00fcldeni a Home Assistant alkalmaz\u00e1snak, be kell \u00e1ll\u00edtania a [Webhooks Twilio-val]({twilio_url}) alkalmaz\u00e1st. \n\n T\u00f6ltse ki a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3kat: \n\n - URL: `{webhook_url}`\n - M\u00f3dszer: POST\n - Tartalom t\u00edpusa: application/x-www-form-urlencoded \n\n L\u00e1sd [a dokument\u00e1ci\u00f3]({docs_url}), hogyan konfigur\u00e1lhatja az automatiz\u00e1l\u00e1sokat a bej\u00f6v\u0151 adatok kezel\u00e9s\u00e9re." }, "step": { "user": { - "description": "Biztosan be szeretn\u00e9d \u00e1ll\u00edtani a Twilio-t?", + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "A Twilio Webhook be\u00e1ll\u00edt\u00e1sa" } } diff --git a/homeassistant/components/twilio/translations/id.json b/homeassistant/components/twilio/translations/id.json new file mode 100644 index 00000000000000..be16b1d4802cdf --- /dev/null +++ b/homeassistant/components/twilio/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "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." + }, + "create_entry": { + "default": "Untuk mengirim event ke Home Assistant, Anda harus menyiapkan [Webhooks dengan Twilio]({twilio_url}).\n\nIsikan info berikut:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content-Type: application/x-www-form-urlencoded\n\nBaca [dokumentasi]({docs_url}) tentang cara mengonfigurasi otomasi untuk menangani data masuk." + }, + "step": { + "user": { + "description": "Ingin memulai penyiapan?", + "title": "Siapkan Twilio Webhook" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/ko.json b/homeassistant/components/twilio/translations/ko.json index 72165dfb798842..4feeacf1a20074 100644 --- a/homeassistant/components/twilio/translations/ko.json +++ b/homeassistant/components/twilio/translations/ko.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." + "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/twilio/translations/nl.json b/homeassistant/components/twilio/translations/nl.json index 55db4ef5e48dce..0d5d33a727e350 100644 --- a/homeassistant/components/twilio/translations/nl.json +++ b/homeassistant/components/twilio/translations/nl.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Weet u zeker dat u Twilio wilt instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Twilio Webhook in" } } diff --git a/homeassistant/components/twinkly/translations/hu.json b/homeassistant/components/twinkly/translations/hu.json new file mode 100644 index 00000000000000..190d7e469d56f7 --- /dev/null +++ b/homeassistant/components/twinkly/translations/hu.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "device_exists": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "title": "Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/id.json b/homeassistant/components/twinkly/translations/id.json new file mode 100644 index 00000000000000..b4a5ba6cbfaef3 --- /dev/null +++ b/homeassistant/components/twinkly/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "device_exists": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "host": "Host (atau alamat IP) perangkat twinkly Anda" + }, + "description": "Siapkan string led Twinkly Anda", + "title": "Twinkly" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twinkly/translations/ko.json b/homeassistant/components/twinkly/translations/ko.json index 207037cba606dc..b3b23991e3d5e9 100644 --- a/homeassistant/components/twinkly/translations/ko.json +++ b/homeassistant/components/twinkly/translations/ko.json @@ -5,6 +5,15 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "Twinkly \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 (\ub610\ub294 IP \uc8fc\uc18c)" + }, + "description": "Twinkly LED \uc904 \uc870\uba85 \uc124\uc815\ud558\uae30", + "title": "Twinkly" + } } } } \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/he.json b/homeassistant/components/unifi/translations/he.json new file mode 100644 index 00000000000000..3007c0e968c1dc --- /dev/null +++ b/homeassistant/components/unifi/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "\u05e1\u05d9\u05e1\u05de\u05d4" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/hu.json b/homeassistant/components/unifi/translations/hu.json index 2a7a43d42e9565..4602193850f226 100644 --- a/homeassistant/components/unifi/translations/hu.json +++ b/homeassistant/components/unifi/translations/hu.json @@ -1,9 +1,14 @@ { "config": { + "abort": { + "configuration_updated": "A konfigur\u00e1ci\u00f3 friss\u00edtve.", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, "error": { "faulty_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "service_unavailable": "Sikertelen csatlakoz\u00e1s" }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { @@ -12,7 +17,7 @@ "port": "Port", "site": "Site azonos\u00edt\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v", - "verify_ssl": "Vez\u00e9rl\u0151 megfelel\u0151 tan\u00fas\u00edtv\u00e1nnyal" + "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" }, "title": "UniFi vez\u00e9rl\u0151 be\u00e1ll\u00edt\u00e1sa" } @@ -23,6 +28,9 @@ "client_control": { "description": "Konfigur\u00e1lja a klienseket\n\n Hozzon l\u00e9tre kapcsol\u00f3kat azokhoz a sorsz\u00e1mokhoz, amelyeknek vez\u00e9relni k\u00edv\u00e1nja a h\u00e1l\u00f3zati hozz\u00e1f\u00e9r\u00e9st." }, + "simple_options": { + "description": "UniFi integr\u00e1ci\u00f3 konfigur\u00e1l\u00e1sa" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "S\u00e1vsz\u00e9less\u00e9g-haszn\u00e1lati \u00e9rz\u00e9kel\u0151k l\u00e9trehoz\u00e1sa a h\u00e1l\u00f3zati \u00fcgyfelek sz\u00e1m\u00e1ra" diff --git a/homeassistant/components/unifi/translations/id.json b/homeassistant/components/unifi/translations/id.json new file mode 100644 index 00000000000000..7a707b28aa02f8 --- /dev/null +++ b/homeassistant/components/unifi/translations/id.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "already_configured": "Situs Controller sudah dikonfigurasi", + "configuration_updated": "Konfigurasi diperbarui.", + "reauth_successful": "Autentikasi ulang berhasil" + }, + "error": { + "faulty_credentials": "Autentikasi tidak valid", + "service_unavailable": "Gagal terhubung", + "unknown_client_mac": "Tidak ada klien yang tersedia di alamat MAC tersebut" + }, + "flow_title": "UniFi Network {site} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "password": "Kata Sandi", + "port": "Port", + "site": "ID Site", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Siapkan UniFi Controller" + } + } + }, + "options": { + "step": { + "client_control": { + "data": { + "block_client": "Klien yang dikontrol akses jaringan", + "dpi_restrictions": "Izinkan kontrol grup pembatasan DPI", + "poe_clients": "Izinkan kontrol POE klien" + }, + "description": "Konfigurasikan kontrol klien \n\nBuat sakelar untuk nomor seri yang ingin dikontrol akses jaringannya.", + "title": "Opsi UniFi 2/3" + }, + "device_tracker": { + "data": { + "detection_time": "Tenggang waktu dalam detik dari terakhir terlihat hingga dianggap sebagai keluar", + "ignore_wired_bug": "Nonaktifkan bug logika kabel UniFi", + "ssid_filter": "Pilih SSID untuk melacak klien nirkabel", + "track_clients": "Lacak klien jaringan", + "track_devices": "Lacak perangkat jaringan (perangkat Ubiquiti)", + "track_wired_clients": "Sertakan klien jaringan berkabel" + }, + "description": "Konfigurasikan pelacakan perangkat", + "title": "Opsi UniFi 1/3" + }, + "simple_options": { + "data": { + "block_client": "Klien yang dikontrol akses jaringan", + "track_clients": "Lacak klien jaringan", + "track_devices": "Lacak perangkat jaringan (perangkat Ubiquiti)" + }, + "description": "Konfigurasikan integrasi UniFi" + }, + "statistics_sensors": { + "data": { + "allow_bandwidth_sensors": "Sensor penggunaan bandwidth untuk klien jaringan", + "allow_uptime_sensors": "Sensor waktu kerja untuk klien jaringan" + }, + "description": "Konfigurasikan sensor statistik", + "title": "Opsi UniFi 3/3" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index 454feec0922794..f5ada3df99a9fa 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\ucee8\ud2b8\ub864\ub7ec \uc0ac\uc774\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "configuration_updated": "\uad6c\uc131\uc774 \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" }, "error": { @@ -9,6 +10,7 @@ "service_unavailable": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown_client_mac": "\ud574\ub2f9 MAC \uc8fc\uc18c\uc5d0\uc11c \uc0ac\uc6a9 \uac00\ub2a5\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." }, + "flow_title": "UniFi \ub124\ud2b8\uc6cc\ud06c: {site} ({host})", "step": { "user": { "data": { @@ -28,7 +30,8 @@ "client_control": { "data": { "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", - "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9" + "dpi_restrictions": "DPI \uc81c\ud55c \uadf8\ub8f9\uc758 \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30", + "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30" }, "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4\ub97c \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", "title": "UniFi \uc635\uc158 2/3" @@ -56,7 +59,7 @@ "statistics_sensors": { "data": { "allow_bandwidth_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ub300\uc5ed\ud3ed \uc0ac\uc6a9\ub7c9 \uc13c\uc11c", - "allow_uptime_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8\ub97c \uc704\ud55c \uac00\ub3d9 \uc2dc\uac04 \uc13c\uc11c" + "allow_uptime_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0 \ub300\ud55c \uac00\ub3d9 \uc2dc\uac04 \uc13c\uc11c" }, "description": "\ud1b5\uacc4 \uc13c\uc11c \uad6c\uc131", "title": "UniFi \uc635\uc158 3/3" diff --git a/homeassistant/components/unifi/translations/lb.json b/homeassistant/components/unifi/translations/lb.json index 1e4870e6ab8d43..5c939a0105a47e 100644 --- a/homeassistant/components/unifi/translations/lb.json +++ b/homeassistant/components/unifi/translations/lb.json @@ -8,6 +8,7 @@ "service_unavailable": "Feeler beim verbannen", "unknown_client_mac": "Kee Cliwent mat der MAC Adress disponibel" }, + "flow_title": "UniFi Network {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 7f0baf4a3be8ea..e5e5e3a1dfba9a 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -6,8 +6,8 @@ "reauth_successful": "Herauthenticatie was succesvol" }, "error": { - "faulty_credentials": "Foutieve gebruikersgegevens", - "service_unavailable": "Geen service beschikbaar", + "faulty_credentials": "Ongeldige authenticatie", + "service_unavailable": "Kan geen verbinding maken", "unknown_client_mac": "Geen client beschikbaar op dat MAC-adres" }, "flow_title": "UniFi Netwerk {site} ({host})", @@ -19,7 +19,7 @@ "port": "Poort", "site": "Site ID", "username": "Gebruikersnaam", - "verify_ssl": "Controller gebruik van het juiste certificaat" + "verify_ssl": "SSL-certificaat verifi\u00ebren" }, "title": "Stel de UniFi-controller in" } @@ -30,6 +30,7 @@ "client_control": { "data": { "block_client": "Cli\u00ebnten met netwerktoegang", + "dpi_restrictions": "Sta controle van DPI-beperkingsgroepen toe", "poe_clients": "Sta POE-controle van gebruikers toe" }, "description": "Configureer clientbesturingen \n\n Maak schakelaars voor serienummers waarvoor u de netwerktoegang wilt beheren.", diff --git a/homeassistant/components/upb/translations/hu.json b/homeassistant/components/upb/translations/hu.json new file mode 100644 index 00000000000000..b09f497a0e4235 --- /dev/null +++ b/homeassistant/components/upb/translations/hu.json @@ -0,0 +1,18 @@ +{ + "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": { + "protocol": "Protokoll" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/id.json b/homeassistant/components/upb/translations/id.json new file mode 100644 index 00000000000000..3e875de7b19727 --- /dev/null +++ b/homeassistant/components/upb/translations/id.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_upb_file": "File ekspor UPB UPStart tidak ada atau tidak valid, periksa nama dan jalur berkas.", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "address": "Alamat (lihat deskripsi di atas)", + "file_path": "Jalur dan nama file ekspor UPStart UPB.", + "protocol": "Protokol" + }, + "description": "Hubungkan Universal Powerline Bus Powerline Interface Module (UPB PIM). String alamat harus dalam format 'address[:port]' untuk 'tcp'. Nilai port ini opsional dan nilai bakunya adalah 2101. Contoh: '192.168.1.42'. Untuk protokol serial, alamat harus dalam format 'tty[:baud]'. Nilai baud ini opsional dan nilai bakunya adalah 4800. Contoh: '/dev/ttyS1'.", + "title": "Hubungkan ke UPB PIM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/ko.json b/homeassistant/components/upb/translations/ko.json index da357f7a136816..aedb6e06c41f1a 100644 --- a/homeassistant/components/upb/translations/ko.json +++ b/homeassistant/components/upb/translations/ko.json @@ -15,8 +15,8 @@ "file_path": "UPStart UPB \ub0b4\ubcf4\ub0b4\uae30 \ud30c\uc77c\uc758 \uacbd\ub85c \ubc0f \uc774\ub984", "protocol": "\ud504\ub85c\ud1a0\ucf5c" }, - "description": "\ubc94\uc6a9 \ud30c\uc6cc\ub77c\uc778 \ubc84\uc2a4 \ud30c\uc6cc\ub77c\uc778 \uc778\ud130\ud398\uc774\uc2a4 \ubaa8\ub4c8 (UPB PIM) \uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694. \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tcp' \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 'address[:port]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 2101 \uc785\ub2c8\ub2e4. \uc608: '192.168.1.42'. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tty[:baud]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \ubcf4(baud)\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 4800 \uc785\ub2c8\ub2e4. \uc608: '/dev/ttyS1'.", - "title": "UPB PIM \uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "\ubc94\uc6a9 \ud30c\uc6cc\ub77c\uc778 \ubc84\uc2a4 \ud30c\uc6cc\ub77c\uc778 \uc778\ud130\ud398\uc774\uc2a4 \ubaa8\ub4c8 (UPB PIM) \uc744 \uc5f0\uacb0\ud574\uc8fc\uc138\uc694. \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tcp' \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 'address[:port]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 2101 \uc785\ub2c8\ub2e4. \uc608: '192.168.1.42'. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 'tty[:baud]' \ud615\uc2dd\uc785\ub2c8\ub2e4. \uc804\uc1a1 \uc18d\ub3c4\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 4800 \uc785\ub2c8\ub2e4. \uc608: '/dev/ttyS1'.", + "title": "UPB PIM\uc5d0 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/upb/translations/pt.json b/homeassistant/components/upb/translations/pt.json index ae100e458450fc..657ce03e544fea 100644 --- a/homeassistant/components/upb/translations/pt.json +++ b/homeassistant/components/upb/translations/pt.json @@ -4,6 +4,7 @@ "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" }, "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", "unknown": "Erro inesperado" } } diff --git a/homeassistant/components/upcloud/translations/hu.json b/homeassistant/components/upcloud/translations/hu.json index 7a7de0633a71d5..2bb28c6c3bcbd5 100644 --- a/homeassistant/components/upcloud/translations/hu.json +++ b/homeassistant/components/upcloud/translations/hu.json @@ -2,7 +2,7 @@ "config": { "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", - "invalid_auth": "\u00c9rv\u00e9nytelen autentik\u00e1ci\u00f3" + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "step": { "user": { diff --git a/homeassistant/components/upcloud/translations/id.json b/homeassistant/components/upcloud/translations/id.json new file mode 100644 index 00000000000000..4ff6a8c7d92c23 --- /dev/null +++ b/homeassistant/components/upcloud/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Interval pembaruan (dalam detik, minimal 30)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/ko.json b/homeassistant/components/upcloud/translations/ko.json index 04360a9a8f79d8..073236bd80bcc5 100644 --- a/homeassistant/components/upcloud/translations/ko.json +++ b/homeassistant/components/upcloud/translations/ko.json @@ -12,5 +12,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/updater/translations/zh-Hant.json b/homeassistant/components/updater/translations/zh-Hant.json index 31188faa135654..23c1b069fc186c 100644 --- a/homeassistant/components/updater/translations/zh-Hant.json +++ b/homeassistant/components/updater/translations/zh-Hant.json @@ -1,3 +1,3 @@ { - "title": "\u66f4\u65b0\u7248\u672c" + "title": "\u66f4\u65b0\u5668" } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/de.json b/homeassistant/components/upnp/translations/de.json index 972f8da40751dd..8a5f4fefadffcc 100644 --- a/homeassistant/components/upnp/translations/de.json +++ b/homeassistant/components/upnp/translations/de.json @@ -16,6 +16,7 @@ }, "user": { "data": { + "scan_interval": "Aktualisierungsintervall (Sekunden, mindestens 30)", "usn": "Ger\u00e4t" } } diff --git a/homeassistant/components/upnp/translations/hu.json b/homeassistant/components/upnp/translations/hu.json index 66320386a89dbf..8b50de71f746a3 100644 --- a/homeassistant/components/upnp/translations/hu.json +++ b/homeassistant/components/upnp/translations/hu.json @@ -1,12 +1,20 @@ { "config": { "abort": { - "already_configured": "Az UPnP / IGD m\u00e1r konfigur\u00e1l\u00e1sra ker\u00fclt", - "no_devices_found": "Nincsenek UPnPIGD eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { "one": "hiba", "other": "" + }, + "flow_title": "UPnP/IGD: {name}", + "step": { + "user": { + "data": { + "usn": "Eszk\u00f6z" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/id.json b/homeassistant/components/upnp/translations/id.json new file mode 100644 index 00000000000000..463e61f271c6e4 --- /dev/null +++ b/homeassistant/components/upnp/translations/id.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "incomplete_discovery": "Proses penemuan tidak selesai", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "flow_title": "UPnP/IGD: {name}", + "step": { + "ssdp_confirm": { + "description": "Ingin menyiapkan perangkat UPnP/IGD ini?" + }, + "user": { + "data": { + "scan_interval": "Interval pembaruan (dalam detik, minimal 30)", + "usn": "Perangkat" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index 3d2c628fcbb55f..cd4192e257cfdf 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "UPnP/IGD is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "incomplete_discovery": "Onvolledige ontdekking", - "no_devices_found": "Geen UPnP/IGD apparaten gevonden op het netwerk." + "no_devices_found": "Geen apparaten gevonden op het netwerk" }, "error": { "one": "Een", @@ -11,6 +11,10 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { + "init": { + "one": "Leeg", + "other": "Leeg" + }, "ssdp_confirm": { "description": "Wilt u [%%] instellen?" }, diff --git a/homeassistant/components/vacuum/translations/id.json b/homeassistant/components/vacuum/translations/id.json index a9827363d5ec19..5fc888515fe6f4 100644 --- a/homeassistant/components/vacuum/translations/id.json +++ b/homeassistant/components/vacuum/translations/id.json @@ -1,14 +1,28 @@ { + "device_automation": { + "action_type": { + "clean": "Perintahkan {entity_name} untuk bersih-bersih", + "dock": "Kembalikan {entity_name} ke dok" + }, + "condition_type": { + "is_cleaning": "{entity_name} sedang membersihkan", + "is_docked": "{entity_name} berlabuh di dok" + }, + "trigger_type": { + "cleaning": "{entity_name} mulai membersihkan", + "docked": "{entity_name} berlabuh" + } + }, "state": { "_": { "cleaning": "Membersihkan", "docked": "Berlabuh", "error": "Kesalahan", "idle": "Siaga", - "off": "Padam", + "off": "Mati", "on": "Nyala", - "paused": "Dijeda", - "returning": "Kembali ke dock" + "paused": "Jeda", + "returning": "Kembali ke dok" } }, "title": "Vakum" diff --git a/homeassistant/components/vacuum/translations/ko.json b/homeassistant/components/vacuum/translations/ko.json index 0e59d5157eb750..52e1e1bd36b68b 100644 --- a/homeassistant/components/vacuum/translations/ko.json +++ b/homeassistant/components/vacuum/translations/ko.json @@ -1,16 +1,16 @@ { "device_automation": { "action_type": { - "clean": "{entity_name} \uc744(\ub97c) \uccad\uc18c\uc2dc\ud0a4\uae30", - "dock": "{entity_name} \uc744(\ub97c) \ucda9\uc804\uc2a4\ud14c\uc774\uc158\uc73c\ub85c \ubcf5\uadc0\uc2dc\ud0a4\uae30" + "clean": "{entity_name}\uc744(\ub97c) \uccad\uc18c\uc2dc\ud0a4\uae30", + "dock": "{entity_name}\uc744(\ub97c) \ucda9\uc804\uc2a4\ud14c\uc774\uc158\uc73c\ub85c \ubcf5\uadc0\uc2dc\ud0a4\uae30" }, "condition_type": { - "is_cleaning": "{entity_name} \uc774(\uac00) \uccad\uc18c \uc911\uc774\uba74", - "is_docked": "{entity_name} \uc774(\uac00) \ub3c4\ud0b9\ub418\uc5b4\uc788\uc73c\uba74" + "is_cleaning": "{entity_name}\uc774(\uac00) \uccad\uc18c \uc911\uc774\uba74", + "is_docked": "{entity_name}\uc774(\uac00) \ucda9\uc804 \uc2a4\ud14c\uc774\uc158\uc5d0 \uc788\uc73c\uba74" }, "trigger_type": { - "cleaning": "{entity_name} \uc774(\uac00) \uccad\uc18c\ub97c \uc2dc\uc791\ud560 \ub54c", - "docked": "{entity_name} \uc774(\uac00) \ub3c4\ud0b9\ub420 \ub54c" + "cleaning": "{entity_name}\uc774(\uac00) \uccad\uc18c\ud558\uae30 \uc2dc\uc791\ud588\uc744 \ub54c", + "docked": "{entity_name}\uc774(\uac00) \ucda9\uc804 \uc2a4\ud14c\uc774\uc158\uc5d0 \ubcf5\uadc0\ud588\uc744 \ub54c" } }, "state": { diff --git a/homeassistant/components/vacuum/translations/nl.json b/homeassistant/components/vacuum/translations/nl.json index 3fbb0ae50beaef..1de347f9875ef0 100644 --- a/homeassistant/components/vacuum/translations/nl.json +++ b/homeassistant/components/vacuum/translations/nl.json @@ -25,5 +25,5 @@ "returning": "Terugkeren naar dock" } }, - "title": "Stofzuigen" + "title": "Stofzuiger" } \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/zh-Hans.json b/homeassistant/components/vacuum/translations/zh-Hans.json index 9e252236d0a4c3..1e4be0ebe0b0bb 100644 --- a/homeassistant/components/vacuum/translations/zh-Hans.json +++ b/homeassistant/components/vacuum/translations/zh-Hans.json @@ -1,7 +1,8 @@ { "device_automation": { "action_type": { - "clean": "\u4f7f {entity_name} \u5f00\u59cb\u6e05\u626b" + "clean": "\u4f7f {entity_name} \u5f00\u59cb\u6e05\u626b", + "dock": "\u4f7f {entity_name} \u8fd4\u56de\u5e95\u5ea7" }, "condition_type": { "is_cleaning": "{entity_name} \u6b63\u5728\u6e05\u626b", diff --git a/homeassistant/components/velbus/translations/de.json b/homeassistant/components/velbus/translations/de.json index 9bbb23b1bcd85c..c6c452953ca3a5 100644 --- a/homeassistant/components/velbus/translations/de.json +++ b/homeassistant/components/velbus/translations/de.json @@ -11,7 +11,7 @@ "user": { "data": { "name": "Der Name f\u00fcr diese Velbus-Verbindung", - "port": "Verbindungs details" + "port": "Verbindungsdetails" }, "title": "Definieren des Velbus-Verbindungstyps" } diff --git a/homeassistant/components/velbus/translations/hu.json b/homeassistant/components/velbus/translations/hu.json new file mode 100644 index 00000000000000..414ee7e60c6a1d --- /dev/null +++ b/homeassistant/components/velbus/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/id.json b/homeassistant/components/velbus/translations/id.json new file mode 100644 index 00000000000000..69a05411dc43b7 --- /dev/null +++ b/homeassistant/components/velbus/translations/id.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "step": { + "user": { + "data": { + "name": "Nama untuk koneksi velbus ini", + "port": "String koneksi" + }, + "title": "Tentukan jenis koneksi velbus" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/id.json b/homeassistant/components/vera/translations/id.json new file mode 100644 index 00000000000000..435fc722dba8b4 --- /dev/null +++ b/homeassistant/components/vera/translations/id.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "cannot_connect": "Tidak dapat terhubung ke pengontrol dengan URL {base_url}" + }, + "step": { + "user": { + "data": { + "exclude": "ID perangkat Vera yang akan dikecualikan dari Home Assistant.", + "lights": "ID perangkat sakelar Vera yang diperlakukan sebagai lampu di Home Assistant", + "vera_controller_url": "URL Pengontrol" + }, + "description": "Tentukan URL pengontrol Vera di bawah. Ini akan terlihat seperti ini: http://192.168.1.161:3480.", + "title": "Siapkan pengontrol Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "ID perangkat Vera yang akan dikecualikan dari Home Assistant.", + "lights": "ID perangkat sakelar Vera yang diperlakukan sebagai lampu di Home Assistant" + }, + "description": "Lihat dokumentasi Vera untuk informasi tentang parameter opsional: https://www.home-assistant.io/integrations/vera/. Catatan: Setiap perubahan yang dilakukan di sini membutuhkan memulai ulang Home Assistant. Untuk menghapus nilai, ketikkan karakter spasi.", + "title": "Opsi pengontrol Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/ko.json b/homeassistant/components/vera/translations/ko.json index e8658528488d63..0990435cb4aa56 100644 --- a/homeassistant/components/vera/translations/ko.json +++ b/homeassistant/components/vera/translations/ko.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "exclude": "Home Assistant \uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", - "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant \uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4.", + "exclude": "Home Assistant\uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", + "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant\uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4.", "vera_controller_url": "\ucee8\ud2b8\ub864\ub7ec URL" }, "description": "\uc544\ub798\uc5d0 Vera \ucee8\ud2b8\ub864\ub7ec URL \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. http://192.168.1.161:3480 \uacfc \uac19\uc740 \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.", @@ -19,8 +19,8 @@ "step": { "init": { "data": { - "exclude": "Home Assistant \uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", - "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant \uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4." + "exclude": "Home Assistant\uc5d0\uc11c \uc81c\uc678\ud560 Vera \uae30\uae30 ID.", + "lights": "Vera \uc2a4\uc704\uce58 \uae30\uae30 ID \ub294 Home Assistant\uc5d0\uc11c \uc870\uba85\uc73c\ub85c \ucde8\uae09\ub429\ub2c8\ub2e4." }, "description": "\ub9e4\uac1c \ubcc0\uc218 \uc120\ud0dd\uc0ac\ud56d\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 vera \uc124\uba85\uc11c\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.home-assistant.io/integrations/vera/. \ucc38\uace0: \uc5ec\uae30\uc5d0\uc11c \ubcc0\uacbd\ud558\uba74 Home Assistant \uc11c\ubc84\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4. \uac12\uc744 \uc9c0\uc6b0\ub824\uba74 \uc785\ub825\ub780\uc744 \uacf5\ubc31\uc73c\ub85c \ub450\uc138\uc694.", "title": "Vera \ucee8\ud2b8\ub864\ub7ec \uc635\uc158" diff --git a/homeassistant/components/verisure/translations/ca.json b/homeassistant/components/verisure/translations/ca.json new file mode 100644 index 00000000000000..4df484912ce865 --- /dev/null +++ b/homeassistant/components/verisure/translations/ca.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" + }, + "step": { + "installation": { + "data": { + "giid": "Instal\u00b7laci\u00f3" + }, + "description": "Home Assistant ha trobat diverses instal\u00b7lacions Verisure al teu compte de My Pages. Selecciona la instal\u00b7laci\u00f3 a afegir a Home Assistant." + }, + "user": { + "data": { + "description": "Inicia sessi\u00f3 amb el compte de Verisure My Pages.", + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "El codi PIN predeterminat no coincideix amb el nombre de d\u00edgits correcte" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Nombre de d\u00edgits del codi PIN dels panys", + "lock_default_code": "Codi PIN dels panys predeterminat, s'utilitza si no se n'indica cap" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/et.json b/homeassistant/components/verisure/translations/et.json new file mode 100644 index 00000000000000..d893f470a9a6c7 --- /dev/null +++ b/homeassistant/components/verisure/translations/et.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud" + }, + "error": { + "invalid_auth": "Vigane autentimine", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "installation": { + "data": { + "giid": "Paigaldus" + }, + "description": "Home Assistant leidis kontolt Minu lehed mitu Verisure paigaldust. Vali Home Assistantile lisatav paigaldus." + }, + "user": { + "data": { + "description": "Logi sisse oma Verisure My Pages kontoga.", + "email": "E-posti aadress", + "password": "Salas\u00f5na" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Vaikimisi PIN-koodi numbrite arv on vale" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Lukkude PIN-koodi numbrite arv", + "lock_default_code": "Lukkude VAIKIMISI PIN-kood, mida kasutatakse juhul kui seda polem\u00e4\u00e4ratud" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/fr.json b/homeassistant/components/verisure/translations/fr.json new file mode 100644 index 00000000000000..df2640e10ba7b1 --- /dev/null +++ b/homeassistant/components/verisure/translations/fr.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "installation": { + "data": { + "giid": "Installation" + }, + "description": "Home Assistant a trouv\u00e9 plusieurs installations Verisure dans votre compte My Pages. Veuillez s\u00e9lectionner l'installation \u00e0 ajouter \u00e0 Home Assistant." + }, + "user": { + "data": { + "description": "Connectez-vous avec votre compte Verisure My Pages.", + "email": "Email", + "password": "Mot de passe" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Le code NIP par d\u00e9faut ne correspond pas au nombre de chiffres requis" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Nombre de chiffres du code NIP pour les serrures", + "lock_default_code": "Code NIP par d\u00e9faut pour les serrures, utilis\u00e9 si aucun n'est indiqu\u00e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pt.json b/homeassistant/components/verisure/translations/pt.json new file mode 100644 index 00000000000000..b6634945535019 --- /dev/null +++ b/homeassistant/components/verisure/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/he.json b/homeassistant/components/vesync/translations/he.json new file mode 100644 index 00000000000000..6f4191da70d538 --- /dev/null +++ b/homeassistant/components/vesync/translations/he.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/hu.json b/homeassistant/components/vesync/translations/hu.json index 10607c5a136286..91956aff45227d 100644 --- a/homeassistant/components/vesync/translations/hu.json +++ b/homeassistant/components/vesync/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vesync/translations/id.json b/homeassistant/components/vesync/translations/id.json new file mode 100644 index 00000000000000..968f854184991d --- /dev/null +++ b/homeassistant/components/vesync/translations/id.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "invalid_auth": "Autentikasi tidak valid" + }, + "step": { + "user": { + "data": { + "password": "Kata Sandi", + "username": "Email" + }, + "title": "Masukkan Nama Pengguna dan Kata Sandi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/ko.json b/homeassistant/components/vesync/translations/ko.json index d11e9f9459d698..a3f1fc1316a91b 100644 --- a/homeassistant/components/vesync/translations/ko.json +++ b/homeassistant/components/vesync/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -12,7 +12,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c" }, - "title": "\uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud558\uae30" + "title": "\uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/vilfo/translations/hu.json b/homeassistant/components/vilfo/translations/hu.json index a75149507fcc80..34db9cf7cc9934 100644 --- a/homeassistant/components/vilfo/translations/hu.json +++ b/homeassistant/components/vilfo/translations/hu.json @@ -1,5 +1,13 @@ { "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/vilfo/translations/id.json b/homeassistant/components/vilfo/translations/id.json new file mode 100644 index 00000000000000..3871670a1cd92c --- /dev/null +++ b/homeassistant/components/vilfo/translations/id.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "user": { + "data": { + "access_token": "Token Akses", + "host": "Host" + }, + "description": "Siapkan integrasi Router Vilfo. Anda memerlukan nama host/IP Router Vilfo dan token akses API. Untuk informasi lebih lanjut tentang integrasi ini dan cara mendapatkan data tersebut, kunjungi: https://www.home-assistant.io/integrations/vilfo", + "title": "Hubungkan ke Router Vilfo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/ko.json b/homeassistant/components/vilfo/translations/ko.json index 4b79130c750290..980ee36d7a5324 100644 --- a/homeassistant/components/vilfo/translations/ko.json +++ b/homeassistant/components/vilfo/translations/ko.json @@ -14,7 +14,7 @@ "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070", "host": "\ud638\uc2a4\ud2b8" }, - "description": "Vilfo \ub77c\uc6b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. Vilfo \ub77c\uc6b0\ud130 \ud638\uc2a4\ud2b8 \uc774\ub984 / IP \uc640 API \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ucd94\uac00 \uc815\ubcf4\uc640 \uc138\ubd80 \uc0ac\ud56d\uc740 https://www.home-assistant.io/integrations/vilfo \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "description": "Vilfo \ub77c\uc6b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. Vilfo \ub77c\uc6b0\ud130 \ud638\uc2a4\ud2b8 \uc774\ub984/IP \uc640 API \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ucd94\uac00 \uc815\ubcf4\uc640 \uc138\ubd80 \uc0ac\ud56d\uc740 https://www.home-assistant.io/integrations/vilfo \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", "title": "Vilfo \ub77c\uc6b0\ud130\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/vilfo/translations/nl.json b/homeassistant/components/vilfo/translations/nl.json index d4b117e2b70a15..304871cc145fb7 100644 --- a/homeassistant/components/vilfo/translations/nl.json +++ b/homeassistant/components/vilfo/translations/nl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_configured": "Deze Vilfo Router is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kon niet verbinden. Controleer de door u verstrekte informatie en probeer het opnieuw.", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", - "unknown": "Er is een onverwachte fout opgetreden tijdens het instellen van de integratie." + "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "access_token": "Toegangstoken voor de Vilfo Router API", - "host": "Router hostnaam of IP-adres" + "access_token": "Toegangstoken", + "host": "Host" }, "description": "Stel de Vilfo Router-integratie in. U heeft de hostnaam/IP van uw Vilfo Router en een API-toegangstoken nodig. Voor meer informatie over deze integratie en hoe u die details kunt verkrijgen, gaat u naar: https://www.home-assistant.io/integrations/vilfo", "title": "Maak verbinding met de Vilfo Router" diff --git a/homeassistant/components/vizio/translations/hu.json b/homeassistant/components/vizio/translations/hu.json index 7401b751d274d7..6f0962509f5fa7 100644 --- a/homeassistant/components/vizio/translations/hu.json +++ b/homeassistant/components/vizio/translations/hu.json @@ -2,9 +2,21 @@ "config": { "abort": { "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", "updated_entry": "Ez a bejegyz\u00e9s m\u00e1r be van \u00e1ll\u00edtva, de a konfigur\u00e1ci\u00f3ban defini\u00e1lt n\u00e9v, appok \u00e9s/vagy be\u00e1ll\u00edt\u00e1sok nem egyeznek meg a kor\u00e1bban import\u00e1lt konfigur\u00e1ci\u00f3val, \u00edgy a konfigur\u00e1ci\u00f3s bejegyz\u00e9s ennek megfelel\u0151en friss\u00fclt." }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { + "pair_tv": { + "data": { + "pin": "PIN-k\u00f3d" + } + }, + "pairing_complete": { + "description": "A VIZIO SmartCast Eszk\u00f6z mostant\u00f3l csatlakozik a Home Assistant-hoz." + }, "user": { "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", @@ -12,7 +24,7 @@ "host": "Hoszt", "name": "N\u00e9v" }, - "title": "A Vizio SmartCast Client be\u00e1ll\u00edt\u00e1sa" + "title": "VIZIO SmartCast Eszk\u00f6z" } } }, @@ -22,7 +34,7 @@ "data": { "volume_step": "Hanger\u0151 l\u00e9p\u00e9s nagys\u00e1ga" }, - "title": "Friss\u00edtse a Vizo SmartCast be\u00e1ll\u00edt\u00e1sokat" + "title": "VIZIO SmartCast Eszk\u00f6z be\u00e1ll\u00edt\u00e1sok friss\u00edt\u00e9se" } } } diff --git a/homeassistant/components/vizio/translations/id.json b/homeassistant/components/vizio/translations/id.json new file mode 100644 index 00000000000000..19f9b41449c97a --- /dev/null +++ b/homeassistant/components/vizio/translations/id.json @@ -0,0 +1,54 @@ +{ + "config": { + "abort": { + "already_configured_device": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung", + "updated_entry": "Entri ini telah disiapkan tetapi nama, aplikasi, dan/atau opsi yang ditentukan dalam konfigurasi tidak cocok dengan konfigurasi yang diimpor sebelumnya, oleh karena itu entri konfigurasi telah diperbarui." + }, + "error": { + "cannot_connect": "Gagal terhubung", + "complete_pairing_failed": "Tidak dapat menyelesaikan pemasangan. Pastikan PIN yang diberikan benar dan TV masih menyala dan terhubung ke jaringan sebelum mengirim ulang.", + "existing_config_entry_found": "Entri konfigurasi Perangkat VIZIO SmartCast yang ada dengan nomor seri yang sama telah dikonfigurasi. Anda harus menghapus entri yang ada untuk mengonfigurasi entri ini." + }, + "step": { + "pair_tv": { + "data": { + "pin": "Kode PIN" + }, + "description": "TV Anda harus menampilkan kode. Masukkan kode tersebut ke dalam formulir dan kemudian lanjutkan ke langkah berikutnya untuk menyelesaikan pemasangan.", + "title": "Selesaikan Proses Pemasangan" + }, + "pairing_complete": { + "description": "Perangkat VIZIO SmartCast sekarang terhubung ke Home Assistant.", + "title": "Pemasangan Selesai" + }, + "pairing_complete_import": { + "description": "Perangkat VIZIO SmartCast sekarang terhubung ke Home Assistant. \n\nToken Akses adalah '**{access_token}**'.", + "title": "Pemasangan Selesai" + }, + "user": { + "data": { + "access_token": "Token Akses", + "device_class": "Jenis Perangkat", + "host": "Host", + "name": "Nama" + }, + "description": "Token Akses hanya diperlukan untuk TV. Jika Anda mengkonfigurasi TV dan belum memiliki Token Akses , biarkan kosong untuk melakukan proses pemasangan.", + "title": "Perangkat VIZIO SmartCast" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "Aplikasi untuk Disertakan atau Dikecualikan", + "include_or_exclude": "Sertakan atau Kecualikan Aplikasi?", + "volume_step": "Langkah Volume" + }, + "description": "Jika memiliki Smart TV, Anda dapat memfilter daftar sumber secara opsional dengan memilih aplikasi mana yang akan disertakan atau dikecualikan dalam daftar sumber Anda.", + "title": "Perbarui Opsi Perangkat VIZIO SmartCast" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index 037c85d7c4eab0..9310adfa4b96b6 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -7,8 +7,8 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc81c\ucd9c\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN \uc774 \uc62c\ubc14\ub978\uc9c0, TV \uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", - "existing_config_entry_found": "\uc77c\ub828 \ubc88\ud638\uac00 \ub3d9\uc77c\ud55c \uae30\uc874 VIZIO SmartCast \uae30\uae30 \uad6c\uc131 \ud56d\ubaa9\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \ud56d\ubaa9\uc744 \uad6c\uc131\ud558\ub824\uba74 \uae30\uc874 \ud56d\ubaa9\uc744 \uc0ad\uc81c\ud574\uc57c\ud569\ub2c8\ub2e4." + "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN\uc774 \uc62c\ubc14\ub978\uc9c0, TV\uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "existing_config_entry_found": "\uc2dc\ub9ac\uc5bc \ubc88\ud638\uac00 \ub3d9\uc77c\ud55c VIZIO SmartCast \uae30\uae30 \uad6c\uc131 \ud56d\ubaa9\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \ud56d\ubaa9\uc744 \uad6c\uc131\ud558\ub824\uba74 \uae30\uc874 \ud56d\ubaa9\uc744 \uc0ad\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4." }, "step": { "pair_tv": { @@ -19,11 +19,11 @@ "title": "\ud398\uc5b4\ub9c1 \uacfc\uc815 \ub05d\ub0b4\uae30" }, "pairing_complete": { - "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "title": "\ud398\uc5b4\ub9c1 \uc644\ub8cc" }, "pairing_complete_import": { - "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant \uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \n\n\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 '**{access_token}**' \uc785\ub2c8\ub2e4.", + "description": "VIZIO SmartCast \uae30\uae30\uac00 Home Assistant\uc5d0 \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \n\n\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 '**{access_token}**' \uc785\ub2c8\ub2e4.", "title": "\ud398\uc5b4\ub9c1 \uc644\ub8cc" }, "user": { @@ -46,7 +46,7 @@ "include_or_exclude": "\uc571\uc744 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "volume_step": "\ubcfc\ub968 \ub2e8\uacc4 \ud06c\uae30" }, - "description": "\uc2a4\ub9c8\ud2b8 TV \uac00 \uc788\ub294 \uacbd\uc6b0 \uc120\ud0dd\uc0ac\ud56d\uc73c\ub85c \uc18c\uc2a4 \ubaa9\ub85d\uc5d0 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud560 \uc571\uc744 \uc120\ud0dd\ud558\uc5ec \uc18c\uc2a4 \ubaa9\ub85d\uc744 \ud544\ud130\ub9c1\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uc2a4\ub9c8\ud2b8 TV\uac00 \uc788\ub294 \uacbd\uc6b0 \uc120\ud0dd\uc0ac\ud56d\uc73c\ub85c \uc18c\uc2a4 \ubaa9\ub85d\uc5d0 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud560 \uc571\uc744 \uc120\ud0dd\ud558\uc5ec \uc18c\uc2a4 \ubaa9\ub85d\uc744 \ud544\ud130\ub9c1\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "VIZIO SmartCast \uae30\uae30 \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" } } diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 48fd831d61c045..4472a9cb9c0de9 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -12,7 +12,7 @@ "step": { "pair_tv": { "data": { - "pin": "PIN" + "pin": "PIN-code" }, "description": "Uw TV zou een code moeten weergeven. Voer die code in het formulier in en ga dan door naar de volgende stap om de koppeling te voltooien.", "title": "Voltooi het koppelingsproces" diff --git a/homeassistant/components/volumio/translations/hu.json b/homeassistant/components/volumio/translations/hu.json index 3b2d79a34a77e2..e58f0666039ab9 100644 --- a/homeassistant/components/volumio/translations/hu.json +++ b/homeassistant/components/volumio/translations/hu.json @@ -1,7 +1,24 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "cannot_connect": "Nem lehet csatlakozni a felfedezett Volumi\u00f3hoz" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "discovery_confirm": { + "description": "Szeretn\u00e9d hozz\u00e1adni a Volumio (`{name}`)-t a Home Assistant-hoz?", + "title": "Felfedezett Volumio" + }, + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/id.json b/homeassistant/components/volumio/translations/id.json new file mode 100644 index 00000000000000..210c6eca87d439 --- /dev/null +++ b/homeassistant/components/volumio/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Tidak dapat terhubung ke Volumio yang ditemukan" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "discovery_confirm": { + "description": "Ingin menambahkan Volumio (`{name}`) ke Home Assistant?", + "title": "Volumio yang ditemukan" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/ko.json b/homeassistant/components/volumio/translations/ko.json index 2c630e533ff590..75ff87b41c85df 100644 --- a/homeassistant/components/volumio/translations/ko.json +++ b/homeassistant/components/volumio/translations/ko.json @@ -1,13 +1,18 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "cannot_connect": "\ubc1c\uacac\ub41c Volumio\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "discovery_confirm": { + "description": "Home Assistant\uc5d0 Volumio (`{name}`)\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\ubc1c\uacac\ub41c Volumio" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", diff --git a/homeassistant/components/volumio/translations/nl.json b/homeassistant/components/volumio/translations/nl.json index 9e11dbad82ba3b..96538422fe0c85 100644 --- a/homeassistant/components/volumio/translations/nl.json +++ b/homeassistant/components/volumio/translations/nl.json @@ -1,13 +1,18 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "cannot_connect": "Kan geen verbinding maken met Volumio" }, "error": { "cannot_connect": "Kan geen verbinding maken", "unknown": "Onverwachte fout" }, "step": { + "discovery_confirm": { + "description": "Wilt u Volumio (`{name}`) toevoegen aan Home Assistant?", + "title": "Volumio ontdekt" + }, "user": { "data": { "host": "Host", diff --git a/homeassistant/components/water_heater/translations/ca.json b/homeassistant/components/water_heater/translations/ca.json index 8c1de7124f5b39..022b5e887d8c6e 100644 --- a/homeassistant/components/water_heater/translations/ca.json +++ b/homeassistant/components/water_heater/translations/ca.json @@ -4,5 +4,16 @@ "turn_off": "Apaga {entity_name}", "turn_on": "Engega {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "El\u00e8ctric", + "gas": "Gas", + "heat_pump": "Bomba de calor", + "high_demand": "Alta demanda", + "off": "OFF", + "performance": "Rendiment" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/et.json b/homeassistant/components/water_heater/translations/et.json index fbb3d11424fcdd..cba6b8274d998f 100644 --- a/homeassistant/components/water_heater/translations/et.json +++ b/homeassistant/components/water_heater/translations/et.json @@ -4,5 +4,16 @@ "turn_off": "L\u00fclita {entity_name} v\u00e4lja", "turn_on": "L\u00fclita {entity_name} sisse" } + }, + "state": { + "_": { + "eco": "\u00d6ko", + "electric": "Elektriline", + "gas": "Gaas", + "heat_pump": "Soojuspump", + "high_demand": "Suur n\u00f5udlus", + "off": "V\u00e4lja l\u00fclitatud", + "performance": "J\u00f5udlus" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/fr.json b/homeassistant/components/water_heater/translations/fr.json index ac72ffab883618..84c9b730b2268c 100644 --- a/homeassistant/components/water_heater/translations/fr.json +++ b/homeassistant/components/water_heater/translations/fr.json @@ -4,5 +4,16 @@ "turn_off": "\u00c9teindre {entity_name}", "turn_on": "Allumer {entity_name}" } + }, + "state": { + "_": { + "eco": "\u00c9co", + "electric": "\u00c9lectrique", + "gas": "Gaz", + "heat_pump": "Pompe \u00e0 chaleur", + "high_demand": "Demande \u00e9lev\u00e9e", + "off": "Inactif", + "performance": "Performance" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/id.json b/homeassistant/components/water_heater/translations/id.json new file mode 100644 index 00000000000000..591f96ffc4f4e1 --- /dev/null +++ b/homeassistant/components/water_heater/translations/id.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "Matikan {entity_name}", + "turn_on": "Nyalakan {entity_name}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/ko.json b/homeassistant/components/water_heater/translations/ko.json new file mode 100644 index 00000000000000..8c591c1991823b --- /dev/null +++ b/homeassistant/components/water_heater/translations/ko.json @@ -0,0 +1,19 @@ +{ + "device_automation": { + "action_type": { + "turn_off": "{entity_name}\uc744(\ub97c) \ub044\uae30", + "turn_on": "{entity_name}\uc744(\ub97c) \ucf1c\uae30" + } + }, + "state": { + "_": { + "eco": "\uc808\uc57d", + "electric": "\uc804\uae30", + "gas": "\uac00\uc2a4", + "heat_pump": "\ud788\ud2b8 \ud38c\ud504", + "high_demand": "\uace0\uc131\ub2a5", + "off": "\uaebc\uc9d0", + "performance": "\uace0\ud6a8\uc728" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pt.json b/homeassistant/components/water_heater/translations/pt.json index 2278e7701aadcc..072a81b682c47f 100644 --- a/homeassistant/components/water_heater/translations/pt.json +++ b/homeassistant/components/water_heater/translations/pt.json @@ -4,5 +4,10 @@ "turn_off": "Desligar {entity_name}", "turn_on": "Ligar {entity_name}" } + }, + "state": { + "_": { + "off": "Desligado" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/ru.json b/homeassistant/components/water_heater/translations/ru.json index 7c703da39d5f36..b3491f82e5eb9e 100644 --- a/homeassistant/components/water_heater/translations/ru.json +++ b/homeassistant/components/water_heater/translations/ru.json @@ -4,5 +4,16 @@ "turn_off": "{entity_name}: \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c", "turn_on": "{entity_name}: \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c" } + }, + "state": { + "_": { + "eco": "\u042d\u043a\u043e", + "electric": "\u042d\u043b\u0435\u043a\u0442\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0439", + "gas": "\u0413\u0430\u0437", + "heat_pump": "\u0422\u0435\u043f\u043b\u043e\u0432\u043e\u0439 \u043d\u0430\u0441\u043e\u0441", + "high_demand": "\u0411\u043e\u043b\u044c\u0448\u0430\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430", + "off": "\u0412\u044b\u043a\u043b", + "performance": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c" + } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/hu.json b/homeassistant/components/wemo/translations/hu.json new file mode 100644 index 00000000000000..ab799e90c74001 --- /dev/null +++ b/homeassistant/components/wemo/translations/hu.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/id.json b/homeassistant/components/wemo/translations/id.json new file mode 100644 index 00000000000000..af0b3128cb9155 --- /dev/null +++ b/homeassistant/components/wemo/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin menyiapkan Wemo?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/ko.json b/homeassistant/components/wemo/translations/ko.json index 5673c049422e5d..704b11262615ac 100644 --- a/homeassistant/components/wemo/translations/ko.json +++ b/homeassistant/components/wemo/translations/ko.json @@ -2,11 +2,11 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { - "description": "Wemo \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + "description": "Wemo\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/wiffi/translations/hu.json b/homeassistant/components/wiffi/translations/hu.json index 21320afea78209..c623f6ddabae25 100644 --- a/homeassistant/components/wiffi/translations/hu.json +++ b/homeassistant/components/wiffi/translations/hu.json @@ -2,6 +2,22 @@ "config": { "abort": { "start_server_failed": "A szerver ind\u00edt\u00e1sa nem siker\u00fclt." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s (perc)" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/id.json b/homeassistant/components/wiffi/translations/id.json new file mode 100644 index 00000000000000..0022f83b0a1b38 --- /dev/null +++ b/homeassistant/components/wiffi/translations/id.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "addr_in_use": "Port server sudah digunakan.", + "start_server_failed": "Gagal memulai server." + }, + "step": { + "user": { + "data": { + "port": "Port" + }, + "title": "Siapkan server TCP untuk perangkat WIFFI" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Tenggang waktu (menit)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/nl.json b/homeassistant/components/wiffi/translations/nl.json index af14d1942a78dc..966f9a18e416a9 100644 --- a/homeassistant/components/wiffi/translations/nl.json +++ b/homeassistant/components/wiffi/translations/nl.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "Server poort" + "port": "Poort" }, "title": "TCP-server instellen voor WIFFI-apparaten" } diff --git a/homeassistant/components/wilight/translations/hu.json b/homeassistant/components/wilight/translations/hu.json index 3b2d79a34a77e2..26cdaf6a025c85 100644 --- a/homeassistant/components/wilight/translations/hu.json +++ b/homeassistant/components/wilight/translations/hu.json @@ -2,6 +2,11 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "step": { + "confirm": { + "title": "WiLight" + } } } } \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/id.json b/homeassistant/components/wilight/translations/id.json new file mode 100644 index 00000000000000..dae7b0bd16ac3c --- /dev/null +++ b/homeassistant/components/wilight/translations/id.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "not_supported_device": "Perangkat WiLight ini saat ini tidak didukung.", + "not_wilight_device": "Perangkat ini bukan perangkat WiLight" + }, + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "description": "Apakah Anda ingin menyiapkan WiLight {name}?\n\nIni mendukung: {components}", + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/ko.json b/homeassistant/components/wilight/translations/ko.json index e18250811fcc05..1b53a1ba544524 100644 --- a/homeassistant/components/wilight/translations/ko.json +++ b/homeassistant/components/wilight/translations/ko.json @@ -2,11 +2,14 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "not_wilight_device": "\uc774 \uc7a5\uce58\ub294 WiLight\uac00 \uc544\ub2d9\ub2c8\ub2e4." + "not_supported_device": "\uc774 WiLight\ub294 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", + "not_wilight_device": "\uc774 \uae30\uae30\ub294 WiLight\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, + "flow_title": "WiLight: {name}", "step": { "confirm": { - "description": "WiLight {name} \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? \n\n \uc9c0\uc6d0 : {components}" + "description": "WiLight {name}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\uc9c0\uc6d0 \uae30\uae30: {components}", + "title": "WiLight" } } } diff --git a/homeassistant/components/withings/translations/hu.json b/homeassistant/components/withings/translations/hu.json index 1486048adfc919..d39410b6d1732e 100644 --- a/homeassistant/components/withings/translations/hu.json +++ b/homeassistant/components/withings/translations/hu.json @@ -2,15 +2,19 @@ "config": { "abort": { "already_configured": "A profil konfigur\u00e1ci\u00f3ja friss\u00edtve.", - "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n.", - "missing_configuration": "A Withings integr\u00e1ci\u00f3 nincs konfigur\u00e1lva. K\u00e9rj\u00fck, k\u00f6vesse a dokument\u00e1ci\u00f3t." + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "no_url_available": "Nincs el\u00e9rhet\u0151 URL. A hib\u00e1r\u00f3l tov\u00e1bbi inform\u00e1ci\u00f3t [a s\u00fag\u00f3ban]({docs_url}) tal\u00e1lsz." }, "create_entry": { "default": "A Withings sikeresen hiteles\u00edtett." }, + "error": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, "step": { "pick_implementation": { - "title": "V\u00e1lassza ki a hiteles\u00edt\u00e9si m\u00f3dszert" + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" }, "profile": { "data": { @@ -18,6 +22,9 @@ }, "description": "Melyik profilt v\u00e1lasztottad ki a Withings weboldalon? Fontos, hogy a profilok egyeznek, k\u00fcl\u00f6nben az adatok helytelen c\u00edmk\u00e9vel lesznek ell\u00e1tva.", "title": "Felhaszn\u00e1l\u00f3i profil." + }, + "reauth": { + "title": "Integr\u00e1ci\u00f3 \u00fajrahiteles\u00edt\u00e9se" } } } diff --git a/homeassistant/components/withings/translations/id.json b/homeassistant/components/withings/translations/id.json new file mode 100644 index 00000000000000..e254e61d91e26b --- /dev/null +++ b/homeassistant/components/withings/translations/id.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Konfigurasi diperbarui untuk profil.", + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "no_url_available": "Tidak ada URL yang tersedia. Untuk informasi tentang kesalahan ini, [lihat bagian bantuan]({docs_url})" + }, + "create_entry": { + "default": "Berhasil mengautentikasi dengan Withings." + }, + "error": { + "already_configured": "Akun sudah dikonfigurasi" + }, + "flow_title": "Withings: {profile}", + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + }, + "profile": { + "data": { + "profile": "Nama Profil" + }, + "description": "Berikan nama profil unik untuk data ini. Umumnya namanya sama dengan nama profil yang Anda pilih di langkah sebelumnya.", + "title": "Profil Pengguna." + }, + "reauth": { + "description": "Profil \"{profile}\" perlu diautentikasi ulang untuk terus menerima data Withings.", + "title": "Autentikasi Ulang Integrasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/ko.json b/homeassistant/components/withings/translations/ko.json index 38ed96dca67941..4823061de41e4b 100644 --- a/homeassistant/components/withings/translations/ko.json +++ b/homeassistant/components/withings/translations/ko.json @@ -7,7 +7,7 @@ "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "create_entry": { - "default": "Withings \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "default": "Withings\ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -26,7 +26,7 @@ }, "reauth": { "description": "Withings \ub370\uc774\ud130\ub97c \uacc4\uc18d \uc218\uc2e0\ud558\ub824\uba74 \"{profile}\" \ud504\ub85c\ud544\uc744 \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c \ud569\ub2c8\ub2e4.", - "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d" + "title": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc7ac\uc778\uc99d\ud558\uae30" } } } diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 21b2d6b11e9be0..5b099b952e5c14 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -12,6 +12,7 @@ "error": { "already_configured": "Account is al geconfigureerd" }, + "flow_title": "Withings: {profile}", "step": { "pick_implementation": { "title": "Kies Authenticatiemethode" @@ -24,7 +25,7 @@ "title": "Gebruikersprofiel." }, "reauth": { - "title": "Profiel opnieuw verifi\u00ebren" + "title": "Verifieer de integratie opnieuw" } } } diff --git a/homeassistant/components/wled/translations/hu.json b/homeassistant/components/wled/translations/hu.json index b89bd72f704691..0d2c85e477d7ef 100644 --- a/homeassistant/components/wled/translations/hu.json +++ b/homeassistant/components/wled/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez a WLED eszk\u00f6z m\u00e1r konfigur\u00e1lva van.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "error": { diff --git a/homeassistant/components/wled/translations/id.json b/homeassistant/components/wled/translations/id.json new file mode 100644 index 00000000000000..6437dfaf83e142 --- /dev/null +++ b/homeassistant/components/wled/translations/id.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "flow_title": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Siapkan WLED Anda untuk diintegrasikan dengan Home Assistant." + }, + "zeroconf_confirm": { + "description": "Ingin menambahkan WLED `{name}` ke Home Assistant?", + "title": "Peranti WLED yang ditemukan" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/ko.json b/homeassistant/components/wled/translations/ko.json index d1945707b6d0a0..69f30eb7516a3e 100644 --- a/homeassistant/components/wled/translations/ko.json +++ b/homeassistant/components/wled/translations/ko.json @@ -13,10 +13,10 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Home Assistant \uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." + "description": "Home Assistant\uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." }, "zeroconf_confirm": { - "description": "Home Assistant \uc5d0 WLED `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Home Assistant\uc5d0 WLED `{name}`\uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\ubc1c\uacac\ub41c WLED \uae30\uae30" } } diff --git a/homeassistant/components/wled/translations/nl.json b/homeassistant/components/wled/translations/nl.json index 329716e2cd5850..3e7b16a7f4a3c6 100644 --- a/homeassistant/components/wled/translations/nl.json +++ b/homeassistant/components/wled/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit WLED-apparaat is al geconfigureerd.", + "already_configured": "Apparaat is al geconfigureerd", "cannot_connect": "Kan geen verbinding maken" }, "error": { @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Hostnaam of IP-adres" + "host": "Host" }, "description": "Stel uw WLED-integratie in met Home Assistant." }, diff --git a/homeassistant/components/wolflink/translations/hu.json b/homeassistant/components/wolflink/translations/hu.json index 3b2d79a34a77e2..c7bb483155de36 100644 --- a/homeassistant/components/wolflink/translations/hu.json +++ b/homeassistant/components/wolflink/translations/hu.json @@ -2,6 +2,24 @@ "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", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "device": { + "data": { + "device_name": "Eszk\u00f6z" + } + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/id.json b/homeassistant/components/wolflink/translations/id.json new file mode 100644 index 00000000000000..64d692efa7d4cb --- /dev/null +++ b/homeassistant/components/wolflink/translations/id.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Autentikasi tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "step": { + "device": { + "data": { + "device_name": "Perangkat" + }, + "title": "Pilih perangkat WOLF" + }, + "user": { + "data": { + "password": "Kata Sandi", + "username": "Nama Pengguna" + }, + "title": "Koneksi WOLF SmartSet" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/nl.json b/homeassistant/components/wolflink/translations/nl.json index 7fb1b867cdd8da..d3cde252467534 100644 --- a/homeassistant/components/wolflink/translations/nl.json +++ b/homeassistant/components/wolflink/translations/nl.json @@ -4,10 +4,16 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { + "device": { + "data": { + "device_name": "Apparaat" + } + }, "user": { "data": { "password": "Wachtwoord", diff --git a/homeassistant/components/wolflink/translations/sensor.de.json b/homeassistant/components/wolflink/translations/sensor.de.json index 9680716cd19e2f..17c365e88c4c58 100644 --- a/homeassistant/components/wolflink/translations/sensor.de.json +++ b/homeassistant/components/wolflink/translations/sensor.de.json @@ -1,6 +1,7 @@ { "state": { "wolflink__state": { + "partymodus": "Party-Modus", "permanent": "Permanent", "solarbetrieb": "Solarmodus", "sparbetrieb": "Sparmodus", diff --git a/homeassistant/components/wolflink/translations/sensor.hu.json b/homeassistant/components/wolflink/translations/sensor.hu.json new file mode 100644 index 00000000000000..2d8cdda9315872 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.hu.json @@ -0,0 +1,7 @@ +{ + "state": { + "wolflink__state": { + "permanent": "\u00c1lland\u00f3" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.id.json b/homeassistant/components/wolflink/translations/sensor.id.json new file mode 100644 index 00000000000000..12b755a9c24846 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.id.json @@ -0,0 +1,47 @@ +{ + "state": { + "wolflink__state": { + "1_x_warmwasser": "1 x DHW", + "aktiviert": "Diaktifkan", + "antilegionellenfunktion": "Fungsi Anti-legionella", + "aus": "Dinonaktifkan", + "auto": "Otomatis", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "Otomatis MATI", + "automatik_ein": "Otomatis NYALA", + "bereit_keine_ladung": "Siap, tidak memuat", + "betrieb_ohne_brenner": "Bekerja tanpa pembakar", + "cooling": "Mendinginkan", + "deaktiviert": "Tidak aktif", + "dhw_prior": "DHWPrior", + "eco": "Eco", + "ein": "Diaktifkan", + "externe_deaktivierung": "Penonaktifan eksternal", + "fernschalter_ein": "Kontrol jarak jauh diaktifkan", + "heizung": "Memanaskan", + "initialisierung": "Inisialisasi", + "kalibration": "Kalibrasi", + "kalibration_heizbetrieb": "Kalibrasi mode pemanasan", + "kalibration_kombibetrieb": "Kalibrasi mode kombi", + "kalibration_warmwasserbetrieb": "Kalibrasi DHW", + "kaskadenbetrieb": "Operasi bertingkat", + "kombibetrieb": "Mode kombi", + "mindest_kombizeit": "Waktu kombi minimum", + "nur_heizgerat": "Hanya boiler", + "parallelbetrieb": "Mode paralel", + "partymodus": "Mode pesta", + "permanent": "Permanen", + "permanentbetrieb": "Mode permanen", + "reduzierter_betrieb": "Mode terbatas", + "schornsteinfeger": "Uji emisi", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "solarbetrieb": "Mode surya", + "sparbetrieb": "Mode ekonomi", + "sparen": "Ekonomi", + "urlaubsmodus": "Mode liburan", + "ventilprufung": "Uji katup" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.ko.json b/homeassistant/components/wolflink/translations/sensor.ko.json index 71fde05a07a0ad..2597560be1bf71 100644 --- a/homeassistant/components/wolflink/translations/sensor.ko.json +++ b/homeassistant/components/wolflink/translations/sensor.ko.json @@ -8,14 +8,80 @@ "aktiviert": "\ud65c\uc131\ud654", "antilegionellenfunktion": "\ud56d \ub808\uc9c0\uc624\ub12c\ub77c\uade0 \uae30\ub2a5", "at_abschaltung": "OT \ub044\uae30", - "at_frostschutz": "OT \ube59\uacb0 \ubcf4\ud638", + "at_frostschutz": "OT \ub3d9\ud30c \ubc29\uc9c0", "aus": "\ube44\ud65c\uc131\ud654", "auto": "\uc790\ub3d9", "auto_off_cool": "\ub0c9\ubc29 \uc790\ub3d9 \uaebc\uc9d0", "auto_on_cool": "\ub0c9\ubc29 \uc790\ub3d9 \ucf1c\uc9d0", "automatik_aus": "\uc790\ub3d9 \uaebc\uc9d0", "automatik_ein": "\uc790\ub3d9 \ucf1c\uc9d0", - "permanent": "\uc601\uad6c\uc801" + "bereit_keine_ladung": "\uc900\ube44\ub428, \ub85c\ub4dc\ub418\uc9c0\ub294 \uc54a\uc74c", + "betrieb_ohne_brenner": "\ubc84\ub108 \uc5c6\uc774 \uc791\ub3d9", + "cooling": "\ub0c9\ubc29", + "deaktiviert": "\ube44\ud65c\uc131", + "dhw_prior": "DHW \uc6b0\uc120", + "eco": "\uc808\uc57d", + "ein": "\ud65c\uc131\ud654", + "estrichtrocknung": "\uc7a5\uae30\uac04 \uc81c\uc2b5", + "externe_deaktivierung": "\uc678\ubd80 \ube44\ud65c\uc131\ud654", + "fernschalter_ein": "\uc6d0\uaca9 \uc81c\uc5b4 \ud65c\uc131\ud654", + "frost_heizkreis": "\ub09c\ubc29 \ud68c\ub85c \ub3d9\ud30c", + "frost_warmwasser": "DHW \ub3d9\ud30c", + "frostschutz": "\ub3d9\ud30c \ubc29\uc9c0", + "gasdruck": "\uac00\uc2a4 \uc555\ub825", + "glt_betrieb": "BMS \ubaa8\ub4dc", + "gradienten_uberwachung": "\uae30\uc6b8\uae30 \ubaa8\ub2c8\ud130\ub9c1", + "heizbetrieb": "\ub09c\ubc29 \ubaa8\ub4dc", + "heizgerat_mit_speicher": "\uc2e4\ub9b0\ub354 \ubcf4\uc77c\ub7ec", + "heizung": "\ub09c\ubc29", + "initialisierung": "\ucd08\uae30\ud654", + "kalibration": "\ubcf4\uc815", + "kalibration_heizbetrieb": "\ub09c\ubc29 \ubaa8\ub4dc \ubcf4\uc815", + "kalibration_kombibetrieb": "\ucf64\ube44 \ubaa8\ub4dc \ubcf4\uc815", + "kalibration_warmwasserbetrieb": "DHW \ubcf4\uc815", + "kaskadenbetrieb": "\uce90\uc2a4\ucf00\uc774\ub4dc \uc6b4\uc804", + "kombibetrieb": "\ucf64\ube44 \ubaa8\ub4dc", + "kombigerat": "\ucf64\ube44 \ubcf4\uc77c\ub7ec", + "kombigerat_mit_solareinbindung": "\ud0dc\uc591\uc5f4 \ud1b5\ud569\ud615 \ucf64\ube44 \ubcf4\uc77c\ub7ec", + "mindest_kombizeit": "\ucd5c\uc18c \ucf64\ube44 \uc2dc\uac04", + "nachlauf_heizkreispumpe": "\ub09c\ubc29 \ud68c\ub85c \ud38c\ud504 \uc6b4\uc804", + "nachspulen": "\ud50c\ub7ec\uc2dc \ud6c4", + "nur_heizgerat": "\ubcf4\uc77c\ub7ec\ub9cc", + "parallelbetrieb": "\ubcd1\ub82c \ubaa8\ub4dc", + "partymodus": "\ud30c\ud2f0 \ubaa8\ub4dc", + "perm_cooling": "\uc601\uad6c \ub0c9\ubc29", + "permanent": "\uc601\uad6c", + "permanentbetrieb": "\uc601\uad6c \ubaa8\ub4dc", + "reduzierter_betrieb": "\uc81c\ud55c \ubaa8\ub4dc", + "rt_abschaltung": "RT \ub044\uae30", + "rt_frostschutz": "RT \ub3d9\ud30c \ubcf4\ud638", + "ruhekontakt": "\uc0c1\uc2dc \ud3d0\uc1c4(NC)", + "schornsteinfeger": "\ubc30\uae30 \ud14c\uc2a4\ud2b8", + "smart_grid": "\uc2a4\ub9c8\ud2b8\uadf8\ub9ac\ub4dc", + "smart_home": "\uc2a4\ub9c8\ud2b8\ud648", + "softstart": "\uc18c\ud504\ud2b8 \uc2a4\ud0c0\ud2b8", + "solarbetrieb": "\ud0dc\uc591\uc5f4 \ubaa8\ub4dc", + "sparbetrieb": "\uc808\uc57d \ubaa8\ub4dc", + "sparen": "\uc808\uc57d", + "spreizung_hoch": "\ub108\ubb34 \ub113\uc740 dT", + "spreizung_kf": "KF \ud655\uc0b0", + "stabilisierung": "\uc548\uc815\ud654", + "standby": "\uc900\ube44\uc911", + "start": "\uc2dc\uc791", + "storung": "\uc624\ub958", + "taktsperre": "\uc21c\ud658 \ubc29\uc9c0", + "telefonfernschalter": "\uc804\ud654 \uc6d0\uaca9 \uc2a4\uc704\uce58", + "test": "\ud14c\uc2a4\ud2b8", + "tpw": "TPW", + "urlaubsmodus": "\ud734\uc77c \ubaa8\ub4dc", + "ventilprufung": "\ubc38\ube0c \ud14c\uc2a4\ud2b8", + "vorspulen": "\uc785\uc218\uad6c \uccad\uc18c", + "warmwasser": "DHW", + "warmwasser_schnellstart": "DHW \ube60\ub978 \uc2dc\uc791", + "warmwasserbetrieb": "DHW \ubaa8\ub4dc", + "warmwassernachlauf": "DHW \uc6b4\uc804", + "warmwasservorrang": "DHW \uc6b0\uc120\uc21c\uc704", + "zunden": "\uc810\ud654" } } } \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index ae205d79aef701..02e8e3f8c2eaee 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -1,6 +1,15 @@ { "state": { "wolflink__state": { + "1_x_warmwasser": "1 x DHW", + "auto": "Auto", + "automatik_aus": "Automatisch UIT", + "automatik_ein": "Automatisch AAN", + "cooling": "Koelen", + "deaktiviert": "Inactief", + "dhw_prior": "DHWPrior", + "eco": "Eco", + "ein": "Ingeschakeld", "frost_warmwasser": "DHW vorst", "frostschutz": "Vorstbescherming", "gasdruck": "Gasdruk", @@ -11,7 +20,18 @@ "initialisierung": "Initialisatie", "kalibration": "Kalibratie", "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "kalibration_warmwasserbetrieb": "DHW-kalibratie", + "kombibetrieb": "Combi-modus", + "kombigerat": "Combiketel", + "nur_heizgerat": "Alleen ketel", + "partymodus": "Feestmodus", "permanent": "Permanent", + "permanentbetrieb": "Permanente modus", + "reduzierter_betrieb": "Beperkte modus", + "schornsteinfeger": "Emissietest", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "sparbetrieb": "Spaarstand", "standby": "Stand-by", "start": "Start", "storung": "Fout", @@ -19,7 +39,9 @@ "tpw": "TPW", "urlaubsmodus": "Vakantiemodus", "ventilprufung": "Kleptest", - "warmwasser": "DHW" + "warmwasser": "DHW", + "warmwasserbetrieb": "DHW-modus", + "zunden": "Ontsteking" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/hu.json b/homeassistant/components/xbox/translations/hu.json index 19f706be1c8180..b35b1b8e2fcde2 100644 --- a/homeassistant/components/xbox/translations/hu.json +++ b/homeassistant/components/xbox/translations/hu.json @@ -1,7 +1,17 @@ { "config": { + "abort": { + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t.", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, "create_entry": { - "default": "Sikeres autentik\u00e1ci\u00f3" + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/id.json b/homeassistant/components/xbox/translations/id.json new file mode 100644 index 00000000000000..ed8106b014430a --- /dev/null +++ b/homeassistant/components/xbox/translations/id.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Tenggang waktu pembuatan URL otorisasi habis.", + "missing_configuration": "Komponen tidak dikonfigurasi. Ikuti petunjuk dalam dokumentasi.", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "create_entry": { + "default": "Berhasil diautentikasi" + }, + "step": { + "pick_implementation": { + "title": "Pilih Metode Autentikasi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/ko.json b/homeassistant/components/xbox/translations/ko.json index 7314d9e3c5c4ea..0765928f8c9f64 100644 --- a/homeassistant/components/xbox/translations/ko.json +++ b/homeassistant/components/xbox/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index 6b0e25dfcd5206..61e865ef01648a 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -14,7 +14,8 @@ "select": { "data": { "select_ip": "IP-Adresse" - } + }, + "description": "F\u00fchre das Setup erneut aus, wenn du zus\u00e4tzliche Gateways verbinden m\u00f6chtest" }, "user": { "data": { diff --git a/homeassistant/components/xiaomi_aqara/translations/hu.json b/homeassistant/components/xiaomi_aqara/translations/hu.json index 1a69e20c6b1edf..295fcef83feb83 100644 --- a/homeassistant/components/xiaomi_aqara/translations/hu.json +++ b/homeassistant/components/xiaomi_aqara/translations/hu.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1ci\u00f3s folyamat m\u00e1r fut" + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "not_xiaomi_aqara": "Nem egy Xiaomi Aqara Gateway, a felfedezett eszk\u00f6z nem egyezett az ismert \u00e1tj\u00e1r\u00f3kkal" }, "error": { "discovery_error": "Nem siker\u00fclt felfedezni a Xiaomi Aqara K\u00f6zponti egys\u00e9get, pr\u00f3b\u00e1lja meg interf\u00e9szk\u00e9nt haszn\u00e1lni a HomeAssistant futtat\u00f3 eszk\u00f6z IP-j\u00e9t", - "invalid_host": " , l\u00e1sd: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_host": "\u00c9rv\u00e9nytelen hosztn\u00e9v vagy IP-c\u00edm, l\u00e1sd: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", "invalid_interface": "\u00c9rv\u00e9nytelen h\u00e1l\u00f3zati interf\u00e9sz", "invalid_key": "\u00c9rv\u00e9nytelen kulcs", "invalid_mac": "\u00c9rv\u00e9nytelen Mac-c\u00edm" @@ -17,7 +18,7 @@ "data": { "select_ip": "IP c\u00edm" }, - "description": "Futtassa \u00fajra a be\u00e1ll\u00edt\u00e1st, ha egy m\u00e1sik K\u00f6zponti egys\u00e9get szeretne csatlakoztatni", + "description": "Futtassa \u00fajra a be\u00e1ll\u00edt\u00e1st, ha egy m\u00e1sik k\u00f6zponti egys\u00e9get szeretne csatlakoztatni", "title": "V\u00e1lassza ki a csatlakoztatni k\u00edv\u00e1nt Xiaomi Aqara K\u00f6zponti egys\u00e9get" }, "settings": { @@ -25,7 +26,7 @@ "key": "K\u00f6zponti egys\u00e9g kulcsa", "name": "K\u00f6zponti egys\u00e9g neve" }, - "description": "A kulcs (jelsz\u00f3) az al\u00e1bbi oktat\u00f3anyag seg\u00edts\u00e9g\u00e9vel t\u00f6lthet\u0151 le: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Ha a kulcs nincs megadva, csak az \u00e9rz\u00e9kel\u0151k f\u00e9rhetnek hozz\u00e1", + "description": "A kulcs (jelsz\u00f3) az al\u00e1bbi oktat\u00f3anyag seg\u00edts\u00e9g\u00e9vel t\u00f6lthet\u0151 le: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Ha a kulcs nincs megadva, csak az \u00e9rz\u00e9kel\u0151k lesznek hozz\u00e1f\u00e9rhet\u0151k", "title": "Xiaomi Aqara k\u00f6zponti egys\u00e9g, opcion\u00e1lis be\u00e1ll\u00edt\u00e1sok" }, "user": { @@ -34,7 +35,7 @@ "interface": "A haszn\u00e1lni k\u00edv\u00e1nt h\u00e1l\u00f3zati interf\u00e9sz", "mac": "Mac-c\u00edm (opcion\u00e1lis)" }, - "description": "Csatlakozzon a Xiaomi Aqara k\u00f6zponti egys\u00e9ghez, ha az IP- \u00e9s a mac-c\u00edm \u00fcresen marad, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja", + "description": "Csatlakozzon a Xiaomi Aqara k\u00f6zponti egys\u00e9ghez, ha az IP- \u00e9s a MAC-c\u00edm \u00fcresen marad, akkor az automatikus felder\u00edt\u00e9st haszn\u00e1lja", "title": "Xiaomi Aqara k\u00f6zponti egys\u00e9g" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/id.json b/homeassistant/components/xiaomi_aqara/translations/id.json new file mode 100644 index 00000000000000..5a2acfa330a709 --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/id.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "not_xiaomi_aqara": "Bukan Gateway Xiaomi Aqara, perangkat yang ditemukan tidak sesuai dengan gateway yang dikenal" + }, + "error": { + "discovery_error": "Gagal menemukan Xiaomi Aqara Gateway, coba gunakan IP perangkat yang menjalankan HomeAssistant sebagai antarmuka", + "invalid_host": "Nama host atau alamat IP tidak valid, lihat https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_interface": "Antarmuka jaringan tidak valid", + "invalid_key": "Kunci gateway tidak valid", + "invalid_mac": "Alamat MAC Tidak Valid" + }, + "flow_title": "Xiaomi Aqara Gateway: {name}", + "step": { + "select": { + "data": { + "select_ip": "Alamat IP" + }, + "description": "Jalankan penyiapan lagi jika Anda ingin menghubungkan gateway lainnya", + "title": "Pilih Gateway Xiaomi Aqara yang ingin dihubungkan" + }, + "settings": { + "data": { + "key": "Kunci gateway Anda", + "name": "Nama Gateway" + }, + "description": "Kunci (kata sandi) dapat diambil menggunakan tutorial ini: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Jika kunci tidak disediakan, hanya sensor yang akan dapat diakses", + "title": "Xiaomi Aqara Gateway, pengaturan opsional" + }, + "user": { + "data": { + "host": "Alamat IP (opsional)", + "interface": "Antarmuka jaringan yang akan digunakan", + "mac": "Alamat MAC (opsional)" + }, + "description": "Hubungkan ke Xiaomi Aqara Gateway Anda, jika alamat IP dan MAC dibiarkan kosong, penemuan otomatis digunakan", + "title": "Xiaomi Aqara Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index 7c15bc572e56e5..131975c1d8c413 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -7,9 +7,10 @@ }, "error": { "discovery_error": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \ubc1c\uacac\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. HomeAssistant \ub97c \uc778\ud130\ud398\uc774\uc2a4\ub85c \uc0ac\uc6a9\ud558\ub294 \uae30\uae30\uc758 IP \ub85c \uc2dc\ub3c4\ud574\ubcf4\uc138\uc694.", - "invalid_host": "\ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "invalid_host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694", "invalid_interface": "\ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "invalid_key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "invalid_key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "invalid_mac": "Mac \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "flow_title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774: {name}", "step": { @@ -25,14 +26,14 @@ "key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4", "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984" }, - "description": "\ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\uc740 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. \ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\ub294 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774 \ucd94\uac00 \uc124\uc815\ud558\uae30" }, "user": { "data": { "host": "IP \uc8fc\uc18c (\uc120\ud0dd \uc0ac\ud56d)", "interface": "\uc0ac\uc6a9\ud560 \ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4", - "mac": "Mac \uc8fc\uc18c(\uc120\ud0dd \uc0ac\ud56d)" + "mac": "Mac \uc8fc\uc18c (\uc120\ud0dd \uc0ac\ud56d)" }, "description": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud569\ub2c8\ub2e4. IP \uc8fc\uc18c \ubc0f MAC \uc8fc\uc18c\ub97c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774" diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index 81f984a5a0583d..0e5283c485e9ac 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -2,10 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom is al aan de gang" + "already_in_progress": "De configuratiestroom is al aan de gang", + "not_xiaomi_aqara": "Geen Xiaomi Aqara Gateway, ontdekt apparaat kwam niet overeen met bekende gateways" }, "error": { "invalid_host": "Ongeldige hostnaam of IP-adres, zie https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_interface": "Ongeldige netwerkinterface", + "invalid_key": "Ongeldige gatewaysleutel", "invalid_mac": "Ongeldig MAC-adres" }, "flow_title": "Xiaomi Aqara Gateway: {name}", @@ -18,11 +21,16 @@ "title": "Selecteer de Xiaomi Aqara Gateway waarmee u verbinding wilt maken" }, "settings": { + "data": { + "key": "De sleutel van uw gateway", + "name": "Naam van de Gateway" + }, "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk" }, "user": { "data": { "host": "IP-adres (optioneel)", + "interface": "De netwerkinterface die moet worden gebruikt", "mac": "MAC-adres (optioneel)" }, "description": "Maak verbinding met uw Xiaomi Aqara Gateway, als de IP- en mac-adressen leeg worden gelaten, wordt automatische detectie gebruikt", diff --git a/homeassistant/components/xiaomi_miio/translations/bg.json b/homeassistant/components/xiaomi_miio/translations/bg.json new file mode 100644 index 00000000000000..bd5387fe8f9bb5 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/bg.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "device": { + "data": { + "host": "IP \u0430\u0434\u0440\u0435\u0441" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index 170d14fc6dcaba..f8dee0efe6921b 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -18,7 +18,7 @@ "name": "Nom del dispositiu", "token": "Token d'API" }, - "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", + "description": "Necessitar\u00e0s el Token d'API de 32 car\u00e0cters, consulta les instruccions a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token. Tingues en compte que aquest Token d'API \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", "title": "Connexi\u00f3 amb un dispositiu Xiaomi Miio o una passarel\u00b7la de Xiaomi" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index 7cf11a1085e2a1..2817d18b57819e 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -6,16 +6,20 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", - "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus." + "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hle ein Ger\u00e4t aus.", + "unknown_device": "Das Ger\u00e4temodell ist nicht bekannt und das Ger\u00e4t kann nicht mithilfe des Assistenten eingerichtet werden." }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { "host": "IP-Adresse", + "model": "Ger\u00e4temodell (optional)", "name": "Name des Ger\u00e4ts", "token": "API-Token" - } + }, + "description": "Sie ben\u00f6tigen den 32 Zeichen langen API-Token, siehe https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token f\u00fcr eine Anleitung. Dieser unterscheidet sich vom API-Token, den die Xiaomi Aqara-Integration nutzt.", + "title": "Herstellen einer Verbindung mit einem Xiaomi Miio-Ger\u00e4t oder Xiaomi Gateway" }, "gateway": { "data": { @@ -23,14 +27,14 @@ "name": "Name des Gateways", "token": "API-Token" }, - "description": "Du ben\u00f6tigst den 32 Zeichen langen API-Token. Anweisungen findest du unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "description": "Sie ben\u00f6tigen den 32 Zeichen langen API-Token. Anweisungen finden Sie unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", "title": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, "user": { "data": { "gateway": "Stelle eine Verbindung zu einem Xiaomi Gateway her" }, - "description": "W\u00e4hle aus, mit welchem Ger\u00e4t du eine Verbindung herstellen m\u00f6chtest.", + "description": "W\u00e4hlen Sie aus, mit welchem Ger\u00e4t Sie eine Verbindung herstellen m\u00f6chten.", "title": "Xiaomi Miio" } } diff --git a/homeassistant/components/xiaomi_miio/translations/hu.json b/homeassistant/components/xiaomi_miio/translations/hu.json index beb5c06c098773..e5cf4501608804 100644 --- a/homeassistant/components/xiaomi_miio/translations/hu.json +++ b/homeassistant/components/xiaomi_miio/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "already_in_progress": "A konfigur\u00e1ci\u00f3s folyamat m\u00e1r fut" + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van." }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s", @@ -10,20 +10,30 @@ }, "flow_title": "Xiaomi Miio: {name}", "step": { + "device": { + "data": { + "host": "IP c\u00edm", + "model": "Eszk\u00f6z modell (opcion\u00e1lis)", + "name": "Eszk\u00f6z neve", + "token": "API Token" + }, + "description": "Sz\u00fcks\u00e9ged lesz a 32 karakteres API Tokenre, k\u00f6vesd a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token oldal instrukci\u00f3it. Vedd figyelembe, hogy ez az API Token k\u00fcl\u00f6nb\u00f6zik a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", + "title": "Csatlakoz\u00e1s Xiaomi Miio eszk\u00f6zh\u00f6z vagy Xiaomi Gateway-hez" + }, "gateway": { "data": { "host": "IP c\u00edm", "name": "K\u00f6zponti egys\u00e9g neve", "token": "API Token" }, - "description": "Sz\u00fcks\u00e9ge lesz az API Tokenre, tov\u00e1bbi inforaciok: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token", + "description": "Sz\u00fcks\u00e9ge lesz az API Tokenre, tov\u00e1bbi inforaciok: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token. K\u00e9rj\u00fck, vegye figyelembe, hogy ez az API Token k\u00fcl\u00f6nb\u00f6zik a Xiaomi Aqara integr\u00e1ci\u00f3 \u00e1ltal haszn\u00e1lt kulcst\u00f3l.", "title": "Csatlakozzon egy Xiaomi K\u00f6zponti egys\u00e9ghez" }, "user": { "data": { "gateway": "Csatlakozzon egy Xiaomi K\u00f6zponti egys\u00e9ghez" }, - "description": "V\u00e1lassza ki, melyik k\u00e9sz\u00fcl\u00e9khez szeretne csatlakozni. ", + "description": "V\u00e1lassza ki, melyik k\u00e9sz\u00fcl\u00e9khez szeretne csatlakozni.", "title": "Xiaomi Miio" } } diff --git a/homeassistant/components/xiaomi_miio/translations/id.json b/homeassistant/components/xiaomi_miio/translations/id.json new file mode 100644 index 00000000000000..d55e19980a7166 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/id.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung" + }, + "error": { + "cannot_connect": "Gagal terhubung", + "no_device_selected": "Tidak ada perangkat yang dipilih, pilih satu perangkat.", + "unknown_device": "Model perangkat tidak diketahui, tidak dapat menyiapkan perangkat menggunakan alur konfigurasi." + }, + "flow_title": "Xiaomi Miio: {name}", + "step": { + "device": { + "data": { + "host": "Alamat IP", + "model": "Model perangkat (Opsional)", + "name": "Nama perangkat", + "token": "Token API" + }, + "description": "Anda akan membutuhkan Token API 32 karakter, lihat https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token untuk mendapatkan petunjuknya. Perhatikan bahwa Token API ini berbeda dari kunci yang digunakan oleh integrasi Xiaomi Aqara.", + "title": "Hubungkan ke Perangkat Xiaomi Miio atau Xiaomi Gateway" + }, + "gateway": { + "data": { + "host": "Alamat IP", + "name": "Nama Gateway", + "token": "Token API" + }, + "description": "Anda akan membutuhkan Token API 32 karakter, lihat https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token untuk mendapatkan petunjuknya. Perhatikan bahwa Token API ini berbeda dari kunci yang digunakan oleh integrasi Xiaomi Aqara.", + "title": "Hubungkan ke Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Hubungkan ke Xiaomi Gateway" + }, + "description": "Pilih perangkat mana yang ingin disambungkan.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index aa48ba7cfa8022..7eec7d7e42439d 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -18,7 +18,7 @@ "name": "Nome del dispositivo", "token": "Token API" }, - "description": "Avrai bisogno dei 32 caratteri Token API , vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni. Tieni presente che questa Token API \u00e8 diversa dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", + "description": "Avrai bisogno dei 32 caratteri della Token API, vedi https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni. Tieni presente che questa Token API \u00e8 diversa dalla chiave utilizzata dall'integrazione Xiaomi Aqara.", "title": "Connettiti a un dispositivo Xiaomi Miio o Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json index 7e594fde247af6..03043f929571dc 100644 --- a/homeassistant/components/xiaomi_miio/translations/ko.json +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -6,15 +6,20 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "no_device_selected": "\uc120\ud0dd\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." + "no_device_selected": "\uc120\ud0dd\ub41c \uae30\uae30\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "unknown_device": "\uae30\uae30\uc758 \ubaa8\ub378\uc744 \uc54c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ud750\ub984\uc5d0\uc11c \uae30\uae30\ub97c \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "flow_title": "Xiaomi Miio: {name}", "step": { "device": { "data": { "host": "IP \uc8fc\uc18c", + "model": "\uae30\uae30 \ubaa8\ub378 (\uc120\ud0dd \uc0ac\ud56d)", + "name": "\uae30\uae30 \uc774\ub984", "token": "API \ud1a0\ud070" - } + }, + "description": "32\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", + "title": "Xiaomi Miio \uae30\uae30 \ub610\ub294 Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" }, "gateway": { "data": { @@ -22,7 +27,7 @@ "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984", "token": "API \ud1a0\ud070" }, - "description": "32 \uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", + "description": "32\uac1c\uc758 \ubb38\uc790\uc5f4\ub85c \uad6c\uc131\ub41c API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694. \ucc38\uace0\ub85c \uc774 API \ud1a0\ud070\uc740 Xiaomi Aqara \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0\uc11c \uc0ac\uc6a9\ub418\ub294 \ud0a4\uc640 \ub2e4\ub985\ub2c8\ub2e4.", "title": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 66209e61ee67f5..394d43fc26164d 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -2,11 +2,12 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor dit Xiaomi Miio-apparaat is al bezig." + "already_in_progress": "De configuratiestroom is al aan de gang" }, "error": { "cannot_connect": "Kan geen verbinding maken", - "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" + "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft", + "unknown_device": "Het apparaatmodel is niet bekend, niet in staat om het apparaat in te stellen met config flow." }, "flow_title": "Xiaomi Miio: {name}", "step": { @@ -16,7 +17,9 @@ "model": "Apparaatmodel (Optioneel)", "name": "Naam van het apparaat", "token": "API-token" - } + }, + "description": "U hebt de 32 karakter API-token nodig, zie https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token voor instructies. Let op, deze API-token is anders dan de sleutel die wordt gebruikt door de Xiaomi Aqara integratie.", + "title": "Verbinding maken met een Xiaomi Miio-apparaat of Xiaomi Gateway" }, "gateway": { "data": { diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 0a6cf433d87740..74a398a9ba6948 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -18,7 +18,7 @@ "name": "Navnet p\u00e5 enheten", "token": "API-token" }, - "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", + "description": "Du trenger 32 tegn API-token , se https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instruksjoner. V\u00e6r oppmerksom p\u00e5 at denne API-token er forskjellig fra n\u00f8kkelen som brukes av Xiaomi Aqara-integrasjonen.", "title": "Koble til en Xiaomi Miio-enhet eller Xiaomi Gateway" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 80528b71370146..8b7105b673694c 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -18,7 +18,7 @@ "name": "Nazwa urz\u0105dzenia", "token": "Token API" }, - "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara.", + "description": "B\u0119dziesz potrzebowa\u0107 tokenu API (32 znaki), odwied\u017a https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token, aby uzyska\u0107 instrukcje. Zauwa\u017c i\u017c jest to inny token ni\u017c w integracji Xiaomi Aqara.", "title": "Po\u0142\u0105czenie z bramk\u0105 Xiaomi b\u0105d\u017a innym urz\u0105dzeniem Xiaomi Miio" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index 5c5064ac347398..b17291746b3e8f 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -18,7 +18,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", "token": "\u0422\u043e\u043a\u0435\u043d API" }, - "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", + "description": "\u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 32-\u0445 \u0437\u043d\u0430\u0447\u043d\u044b\u0439 \u0422\u043e\u043a\u0435\u043d API. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c: \nhttps://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token.\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0442\u043e\u043a\u0435\u043d \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043a\u043b\u044e\u0447\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Xiaomi Aqara.", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Xiaomi Miio \u0438\u043b\u0438 \u0448\u043b\u044e\u0437\u0443 Xiaomi" }, "gateway": { diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 3b0a89b7485e62..db1d825cea878f 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -18,7 +18,7 @@ "name": "\u88dd\u7f6e\u540d\u7a31", "token": "API \u6b0a\u6756" }, - "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", + "description": "\u5c07\u9700\u8981\u8f38\u5165 32 \u4f4d\u5b57\u5143 API \u6b0a\u6756\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \u4ee5\u7372\u5f97\u7372\u53d6\u6b0a\u6756\u7684\u6559\u5b78\u3002\u8acb\u6ce8\u610f\uff1a\u6b64 API \u6b0a\u6756\u8207 Xiaomi Aqara \u6574\u5408\u6240\u4f7f\u7528\u4e4b\u6b0a\u6756\u4e0d\u540c\u3002", "title": "\u9023\u7dda\u81f3\u5c0f\u7c73 MIIO \u88dd\u7f6e\u6216\u5c0f\u7c73\u7db2\u95dc" }, "gateway": { diff --git a/homeassistant/components/yeelight/translations/hu.json b/homeassistant/components/yeelight/translations/hu.json index 10a03cebd21cc0..ac463142359233 100644 --- a/homeassistant/components/yeelight/translations/hu.json +++ b/homeassistant/components/yeelight/translations/hu.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "no_devices_found": "Nincs eszk\u00f6z a h\u00e1l\u00f3zaton" + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" }, "error": { "cannot_connect": "Sikertelen csatlakoz\u00e1s" @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Gazdag\u00e9p" + "host": "Hoszt" }, "description": "Ha a gazdag\u00e9pet \u00fcresen hagyja, felder\u00edt\u00e9sre ker\u00fcl automatikusan." } diff --git a/homeassistant/components/yeelight/translations/id.json b/homeassistant/components/yeelight/translations/id.json new file mode 100644 index 00000000000000..0c81739095d52a --- /dev/null +++ b/homeassistant/components/yeelight/translations/id.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan" + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "pick_device": { + "data": { + "device": "Perangkat" + } + }, + "user": { + "data": { + "host": "Host" + }, + "description": "Jika host dibiarkan kosong, proses penemuan akan digunakan untuk menemukan perangkat." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Model (Opsional)", + "nightlight_switch": "Gunakan Sakelar Lampu Malam", + "save_on_change": "Simpan Status Saat Berubah", + "transition": "Waktu Transisi (milidetik)", + "use_music_mode": "Aktifkan Mode Musik" + }, + "description": "Jika model dibiarkan kosong, model akan dideteksi secara otomatis." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/ko.json b/homeassistant/components/yeelight/translations/ko.json index 1d6974aaa61e20..4abb8fcbbfff15 100644 --- a/homeassistant/components/yeelight/translations/ko.json +++ b/homeassistant/components/yeelight/translations/ko.json @@ -10,14 +10,14 @@ "step": { "pick_device": { "data": { - "device": "\uc7a5\uce58" + "device": "\uae30\uae30" } }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "\ud638\uc2a4\ud2b8\ub97c \ube44\uc6cc\ub450\uba74 \uc7a5\uce58\ub97c \ucc3e\ub294 \ub370 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4." + "description": "\ud638\uc2a4\ud2b8\ub97c \ube44\uc6cc \ub450\uba74 \uae30\uae30\ub97c \ucc3e\ub294 \ub370 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4" } } }, @@ -25,11 +25,11 @@ "step": { "init": { "data": { - "model": "\ubaa8\ub378(\uc120\ud0dd \uc0ac\ud56d)", - "nightlight_switch": "\uc57c\uac04 \uc870\uba85 \uc2a4\uc704\uce58 \uc0ac\uc6a9", - "save_on_change": "\ubcc0\uacbd\uc2dc \uc0c1\ud0dc \uc800\uc7a5", + "model": "\ubaa8\ub378 (\uc120\ud0dd \uc0ac\ud56d)", + "nightlight_switch": "\uc57c\uac04 \uc870\uba85 \uc804\ud658 \uc0ac\uc6a9\ud558\uae30", + "save_on_change": "\ubcc0\uacbd \uc2dc \uc0c1\ud0dc\ub97c \uc800\uc7a5\ud558\uae30", "transition": "\uc804\ud658 \uc2dc\uac04(ms)", - "use_music_mode": "\uc74c\uc545 \ubaa8\ub4dc \ud65c\uc131\ud654" + "use_music_mode": "\uc74c\uc545 \ubaa8\ub4dc \ud65c\uc131\ud654\ud558\uae30" }, "description": "\ubaa8\ub378\uc744 \ube44\uc6cc \ub450\uba74 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub429\ub2c8\ub2e4." } diff --git a/homeassistant/components/zerproc/translations/id.json b/homeassistant/components/zerproc/translations/id.json new file mode 100644 index 00000000000000..223836a8b40992 --- /dev/null +++ b/homeassistant/components/zerproc/translations/id.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Tidak ada perangkat yang ditemukan di jaringan", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "step": { + "confirm": { + "description": "Ingin memulai penyiapan?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/ko.json b/homeassistant/components/zerproc/translations/ko.json index 7011a61f7573ab..e5ae04d6e5c810 100644 --- a/homeassistant/components/zerproc/translations/ko.json +++ b/homeassistant/components/zerproc/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/zha/translations/hu.json b/homeassistant/components/zha/translations/hu.json index 935663ed9e49a4..844f2dd7191119 100644 --- a/homeassistant/components/zha/translations/hu.json +++ b/homeassistant/components/zha/translations/hu.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Csak egyetlen ZHA konfigur\u00e1ci\u00f3 megengedett." + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { - "cannot_connect": "Nem lehet csatlakozni a ZHA eszk\u00f6zh\u00f6z." + "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { "port_config": { @@ -17,5 +17,10 @@ "title": "ZHA" } } + }, + "device_automation": { + "trigger_type": { + "device_offline": "Eszk\u00f6z offline" + } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/id.json b/homeassistant/components/zha/translations/id.json new file mode 100644 index 00000000000000..5baf04e13149e5 --- /dev/null +++ b/homeassistant/components/zha/translations/id.json @@ -0,0 +1,91 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "cannot_connect": "Gagal terhubung" + }, + "step": { + "pick_radio": { + "data": { + "radio_type": "Jenis Radio" + }, + "description": "Pilih jenis radio Zigbee Anda", + "title": "Jenis Radio" + }, + "port_config": { + "data": { + "baudrate": "kecepatan port", + "flow_control": "kontrol data flow", + "path": "Jalur perangkat serial" + }, + "description": "Masukkan pengaturan khusus port", + "title": "Setelan" + }, + "user": { + "data": { + "path": "Jalur Perangkat Serial" + }, + "description": "Pilih port serial untuk radio Zigbee", + "title": "ZHA" + } + } + }, + "device_automation": { + "action_type": { + "squawk": "Squawk", + "warn": "Peringatkan" + }, + "trigger_subtype": { + "both_buttons": "Kedua tombol", + "button_1": "Tombol pertama", + "button_2": "Tombol kedua", + "button_3": "Tombol ketiga", + "button_4": "Tombol keempat", + "button_5": "Tombol kelima", + "button_6": "Tombol keenam", + "close": "Tutup", + "dim_down": "Redupkan", + "dim_up": "Terangkan", + "face_1": "dengan wajah 1 diaktifkan", + "face_2": "dengan wajah 2 diaktifkan", + "face_3": "dengan wajah 3 diaktifkan", + "face_4": "dengan wajah 4 diaktifkan", + "face_5": "dengan wajah 5 diaktifkan", + "face_6": "dengan wajah 6 diaktifkan", + "face_any": "Dengan wajah apa pun/yang ditentukan diaktifkan", + "left": "Kiri", + "open": "Buka", + "right": "Kanan", + "turn_off": "Matikan", + "turn_on": "Nyalakan" + }, + "trigger_type": { + "device_dropped": "Perangkat dijatuhkan", + "device_flipped": "Perangkat dibalik \"{subtype}\"", + "device_knocked": "Perangkat diketuk \"{subtype}\"", + "device_offline": "Perangkat offline", + "device_rotated": "Perangkat diputar \"{subtype}\"", + "device_shaken": "Perangkat diguncangkan", + "device_slid": "Perangkat diluncurkan \"{subtype}\"", + "device_tilted": "Perangkat dimiringkan", + "remote_button_alt_double_press": "Tombol \"{subtype}\" diklik dua kali (Mode alternatif)", + "remote_button_alt_long_press": "Tombol \"{subtype}\" terus ditekan (Mode alternatif)", + "remote_button_alt_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama (Mode alternatif)", + "remote_button_alt_quadruple_press": "Tombol \"{subtype}\" diklik empat kali (Mode alternatif)", + "remote_button_alt_quintuple_press": "Tombol \"{subtype}\" diklik lima kali (Mode alternatif)", + "remote_button_alt_short_press": "Tombol \"{subtype}\" ditekan (Mode alternatif)", + "remote_button_alt_short_release": "Tombol \"{subtype}\" dilepaskan (Mode alternatif)", + "remote_button_alt_triple_press": "Tombol \"{subtype}\" diklik tiga kali (Mode alternatif)", + "remote_button_double_press": "Tombol \"{subtype}\" diklik dua kali", + "remote_button_long_press": "Tombol \"{subtype}\" terus ditekan", + "remote_button_long_release": "Tombol \"{subtype}\" dilepaskan setelah ditekan lama", + "remote_button_quadruple_press": "Tombol \"{subtype}\" diklik empat kali", + "remote_button_quintuple_press": "Tombol \"{subtype}\" diklik lima kali", + "remote_button_short_press": "Tombol \"{subtype}\" ditekan", + "remote_button_short_release": "Tombol \"{subtype}\" dilepaskan", + "remote_button_triple_press": "Tombol \"{subtype}\" diklik tiga kali" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/ko.json b/homeassistant/components/zha/translations/ko.json index 639cc84d86f1e3..4ec9790b357424 100644 --- a/homeassistant/components/zha/translations/ko.json +++ b/homeassistant/components/zha/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" @@ -34,8 +34,8 @@ }, "device_automation": { "action_type": { - "squawk": "\ube44\uc0c1", - "warn": "\uacbd\uace0" + "squawk": "\uc2a4\ucffc\ud06c \ud558\uae30", + "warn": "\uacbd\uace0\ud558\uae30" }, "trigger_subtype": { "both_buttons": "\ub450 \uac1c", @@ -63,28 +63,29 @@ }, "trigger_type": { "device_dropped": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc84c\uc744 \ub54c", - "device_flipped": "\"{subtype}\" \uae30\uae30\uac00 \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", - "device_knocked": "\"{subtype}\" \uae30\uae30\uac00 \ub450\ub4dc\ub824\uc9c8 \ub54c", - "device_rotated": "\"{subtype}\" \uae30\uae30\uac00 \ud68c\uc804\ub420 \ub54c", - "device_shaken": "\uae30\uae30\uac00 \ud754\ub4e4\ub9b4 \ub54c", - "device_slid": "\"{subtype}\" \uae30\uae30\uac00 \ubbf8\ub044\ub7ec\uc9c8 \ub54c", - "device_tilted": "\uae30\uae30\uac00 \uae30\uc6b8\uc5b4\uc9c8 \ub54c", - "remote_button_alt_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c (\ub300\uccb4\ubaa8\ub4dc)", - "remote_button_alt_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_alt_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", - "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub824\uc9c8 \ub54c", - "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub9b4 \ub54c", - "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", - "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c" + "device_flipped": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ub4a4\uc9d1\uc5b4\uc84c\uc744 \ub54c", + "device_knocked": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ub450\ub4dc\ub824\uc84c\uc744 \ub54c", + "device_offline": "\uae30\uae30\uac00 \uc624\ud504\ub77c\uc778\uc774 \ub418\uc5c8\uc744 \ub54c", + "device_rotated": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ud68c\uc804\ub418\uc5c8\uc744 \ub54c", + "device_shaken": "\uae30\uae30\uac00 \ud754\ub4e4\ub838\uc744 \ub54c", + "device_slid": "\"{subtype}\"(\uc73c)\ub85c \uae30\uae30\uac00 \ubbf8\ub044\ub7ec\uc84c\uc744 \ub54c", + "device_tilted": "\uae30\uae30\uac00 \uae30\uc6b8\uc5b4\uc84c\uc744 \ub54c", + "remote_button_alt_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c (\ub300\uccb4\ubaa8\ub4dc)", + "remote_button_alt_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_alt_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c (\ub300\uccb4 \ubaa8\ub4dc)", + "remote_button_double_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub450 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_long_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uacc4\uc18d \ub20c\ub838\uc744 \ub54c", + "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \ub5bc\uc600\uc744 \ub54c", + "remote_button_quadruple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub124 \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_quintuple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub2e4\uc12f \ubc88 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub838\uc744 \ub54c", + "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5bc\uc5c8\uc744 \ub54c", + "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub838\uc744 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/nl.json b/homeassistant/components/zha/translations/nl.json index 9a7a83e19d3284..ddf208c85771c6 100644 --- a/homeassistant/components/zha/translations/nl.json +++ b/homeassistant/components/zha/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van ZHA is toegestaan." + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "error": { - "cannot_connect": "Kan geen verbinding maken met ZHA apparaat." + "cannot_connect": "Kan geen verbinding maken" }, "step": { "pick_radio": { @@ -65,6 +65,7 @@ "device_dropped": "Apparaat gevallen", "device_flipped": "Apparaat omgedraaid \"{subtype}\"", "device_knocked": "Apparaat klopte \"{subtype}\"", + "device_offline": "Apparaat offline", "device_rotated": "Apparaat gedraaid \" {subtype} \"", "device_shaken": "Apparaat geschud", "device_slid": "Apparaat geschoven \"{subtype}\"\".", diff --git a/homeassistant/components/zodiac/translations/sensor.hu.json b/homeassistant/components/zodiac/translations/sensor.hu.json new file mode 100644 index 00000000000000..8897339b74f87b --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.hu.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "V\u00edz\u00f6nt\u0151", + "aries": "Kos", + "cancer": "R\u00e1k", + "capricorn": "Bak", + "gemini": "Ikrek", + "leo": "Oroszl\u00e1n", + "libra": "M\u00e9rleg", + "pisces": "Halak", + "sagittarius": "Nyilas", + "scorpio": "Skorpi\u00f3", + "taurus": "Bika", + "virgo": "Sz\u0171z" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.id.json b/homeassistant/components/zodiac/translations/sensor.id.json new file mode 100644 index 00000000000000..cd671e146ed712 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.id.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Aquarius", + "aries": "Aries", + "cancer": "Cancer", + "capricorn": "Capricorn", + "gemini": "Gemini", + "leo": "Leo", + "libra": "Libra", + "pisces": "Pisces", + "sagittarius": "Sagittarius", + "scorpio": "Scorpio", + "taurus": "Taurus", + "virgo": "Virgo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.ko.json b/homeassistant/components/zodiac/translations/sensor.ko.json index 0a9fc83cdeacf9..88221b2f34e883 100644 --- a/homeassistant/components/zodiac/translations/sensor.ko.json +++ b/homeassistant/components/zodiac/translations/sensor.ko.json @@ -1,18 +1,18 @@ { "state": { "zodiac__sign": { - "aquarius": "\ubb3c\ubcd1 \uc790\ub9ac", - "aries": "\uc591 \uc790\ub9ac", - "cancer": "\uac8c \uc790\ub9ac", - "capricorn": "\uc5fc\uc18c \uc790\ub9ac", - "gemini": "\uc30d\ub465\uc774 \uc790\ub9ac", - "leo": "\uc0ac\uc790 \uc790\ub9ac", - "libra": "\ucc9c\uce6d \uc790\ub9ac", - "pisces": "\ubb3c\uace0\uae30 \uc790\ub9ac", - "sagittarius": "\uad81\uc218 \uc790\ub9ac", - "scorpio": "\uc804\uac08 \uc790\ub9ac", - "taurus": "\ud669\uc18c \uc790\ub9ac", - "virgo": "\ucc98\ub140 \uc790\ub9ac" + "aquarius": "\ubb3c\ubcd1\uc790\ub9ac", + "aries": "\uc591\uc790\ub9ac", + "cancer": "\uac8c\uc790\ub9ac", + "capricorn": "\uc5fc\uc18c\uc790\ub9ac", + "gemini": "\uc30d\ub465\uc774\uc790\ub9ac", + "leo": "\uc0ac\uc790\uc790\ub9ac", + "libra": "\ucc9c\uce6d\uc790\ub9ac", + "pisces": "\ubb3c\uace0\uae30\uc790\ub9ac", + "sagittarius": "\uad81\uc218\uc790\ub9ac", + "scorpio": "\uc804\uac08\uc790\ub9ac", + "taurus": "\ud669\uc18c\uc790\ub9ac", + "virgo": "\ucc98\ub140\uc790\ub9ac" } } } \ No newline at end of file diff --git a/homeassistant/components/zone/translations/id.json b/homeassistant/components/zone/translations/id.json index b84710dc408bd6..aa05923b561029 100644 --- a/homeassistant/components/zone/translations/id.json +++ b/homeassistant/components/zone/translations/id.json @@ -8,7 +8,7 @@ "data": { "icon": "Ikon", "latitude": "Lintang", - "longitude": "Garis bujur", + "longitude": "Bujur", "name": "Nama", "passive": "Pasif", "radius": "Radius" diff --git a/homeassistant/components/zoneminder/translations/hu.json b/homeassistant/components/zoneminder/translations/hu.json index f1f99fa2f7c44e..a40d92992519d6 100644 --- a/homeassistant/components/zoneminder/translations/hu.json +++ b/homeassistant/components/zoneminder/translations/hu.json @@ -1,13 +1,28 @@ { "config": { "abort": { - "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez." + "auth_fail": "\u00c9rv\u00e9nytelen felhaszn\u00e1l\u00f3n\u00e9v vagy jelsz\u00f3", + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" }, "create_entry": { "default": "ZoneMinder szerver hozz\u00e1adva." }, "error": { - "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez." + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "connection_error": "Nem siker\u00fclt csatlakozni a ZoneMinder szerverhez.", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "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/zoneminder/translations/id.json b/homeassistant/components/zoneminder/translations/id.json new file mode 100644 index 00000000000000..25f1d98fc26d0d --- /dev/null +++ b/homeassistant/components/zoneminder/translations/id.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Nama pengguna atau kata sandi salah.", + "cannot_connect": "Gagal terhubung", + "connection_error": "Gagal terhubung ke server ZoneMinder.", + "invalid_auth": "Autentikasi tidak valid" + }, + "create_entry": { + "default": "Server ZoneMinder ditambahkan." + }, + "error": { + "auth_fail": "Nama pengguna atau kata sandi salah.", + "cannot_connect": "Gagal terhubung", + "connection_error": "Gagal terhubung ke server ZoneMinder.", + "invalid_auth": "Autentikasi tidak valid" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host dan Port (mis. 10.10.0.4:8010)", + "password": "Kata Sandi", + "path": "Jalur ZM", + "path_zms": "Jalur ZMS", + "ssl": "Menggunakan sertifikat SSL", + "username": "Nama Pengguna", + "verify_ssl": "Verifikasi sertifikat SSL" + }, + "title": "Tambahkan Server ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/ko.json b/homeassistant/components/zoneminder/translations/ko.json index e03da9ed8fa9f8..bee0b7b6465af7 100644 --- a/homeassistant/components/zoneminder/translations/ko.json +++ b/homeassistant/components/zoneminder/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "auth_fail": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ub610\ub294 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -10,7 +10,7 @@ "default": "ZoneMinder \uc11c\ubc84\uac00 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "auth_fail": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ub610\ub294 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" @@ -19,15 +19,15 @@ "step": { "user": { "data": { - "host": "\ud638\uc2a4\ud2b8 \ubc0f \ud3ec\ud2b8(\uc608: 10.10.0.4:8010)", + "host": "\ud638\uc2a4\ud2b8 \ubc0f \ud3ec\ud2b8 (\uc608: 10.10.0.4:8010)", "password": "\ube44\ubc00\ubc88\ud638", - "path": "ZMS \uacbd\ub85c", + "path": "ZM \uacbd\ub85c", "path_zms": "ZMS \uacbd\ub85c", "ssl": "SSL \uc778\uc99d\uc11c \uc0ac\uc6a9", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" }, - "title": "ZoneMinder \uc11c\ubc84\ub97c \ucd94\uac00\ud558\uc138\uc694." + "title": "ZoneMinder \uc11c\ubc84\ub97c \ucd94\uac00\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/zwave/translations/hu.json b/homeassistant/components/zwave/translations/hu.json index 240e7fe776c13f..68a19863b53f25 100644 --- a/homeassistant/components/zwave/translations/hu.json +++ b/homeassistant/components/zwave/translations/hu.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "A Z-Wave m\u00e1r konfigur\u00e1lva van" + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." }, "error": { "option_error": "A Z-Wave \u00e9rv\u00e9nyes\u00edt\u00e9s sikertelen. Az USB-meghajt\u00f3 el\u00e9r\u00e9si \u00fatj\u00e1t helyesen adtad meg?" diff --git a/homeassistant/components/zwave/translations/id.json b/homeassistant/components/zwave/translations/id.json index 76c9c148b1eb71..99bd62703263e8 100644 --- a/homeassistant/components/zwave/translations/id.json +++ b/homeassistant/components/zwave/translations/id.json @@ -1,4 +1,23 @@ { + "config": { + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi", + "single_instance_allowed": "Sudah dikonfigurasi. Hanya satu konfigurasi yang diizinkan." + }, + "error": { + "option_error": "Validasi Z-Wave gagal. Apakah jalur ke stik USB sudah benar?" + }, + "step": { + "user": { + "data": { + "network_key": "Kunci Jaringan (biarkan kosong untuk dibuat secara otomatis)", + "usb_path": "Jalur Perangkat USB" + }, + "description": "Integrasi ini tidak lagi dipertahankan. Untuk instalasi baru, gunakan Z-Wave JS sebagai gantinya.\n\nBaca https://www.home-assistant.io/docs/z-wave/installation/ untuk informasi tentang variabel konfigurasi", + "title": "Siapkan Z-Wave" + } + } + }, "state": { "_": { "dead": "Mati", @@ -7,8 +26,8 @@ "sleeping": "Tidur" }, "query_stage": { - "dead": "Mati ({query_stage})", - "initializing": "Inisialisasi ( {query_stage} )" + "dead": "Mati", + "initializing": "Inisialisasi" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/ko.json b/homeassistant/components/zwave/translations/ko.json index 84c7b4ee4e3ece..674476ac7598c1 100644 --- a/homeassistant/components/zwave/translations/ko.json +++ b/homeassistant/components/zwave/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "option_error": "Z-Wave \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. USB \uc2a4\ud2f1\uc758 \uacbd\ub85c\uac00 \uc815\ud655\ud569\ub2c8\uae4c?" diff --git a/homeassistant/components/zwave/translations/nl.json b/homeassistant/components/zwave/translations/nl.json index 50e81003d27d26..a366d1d50df8c8 100644 --- a/homeassistant/components/zwave/translations/nl.json +++ b/homeassistant/components/zwave/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Z-Wave is al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { @@ -11,9 +11,9 @@ "user": { "data": { "network_key": "Netwerksleutel (laat leeg om automatisch te genereren)", - "usb_path": "USB-pad" + "usb_path": "USB-apparaatpad" }, - "description": "Zie https://www.home-assistant.io/docs/z-wave/installation/ voor informatie over de configuratievariabelen", + "description": "Deze integratie wordt niet langer onderhouden. Voor nieuwe installaties, gebruik Z-Wave JS in plaats daarvan.\n\nZie https://www.home-assistant.io/docs/z-wave/installation/ voor informatie over de configuratievariabelen", "title": "Stel Z-Wave in" } } diff --git a/homeassistant/components/zwave_js/translations/bg.json b/homeassistant/components/zwave_js/translations/bg.json new file mode 100644 index 00000000000000..abf89f00513fb6 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/bg.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" + }, + "step": { + "manual": { + "data": { + "url": "URL" + } + }, + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/en.json b/homeassistant/components/zwave_js/translations/en.json index 101942dc717bad..5be980d52cb07a 100644 --- a/homeassistant/components/zwave_js/translations/en.json +++ b/homeassistant/components/zwave_js/translations/en.json @@ -4,6 +4,7 @@ "addon_get_discovery_info_failed": "Failed to get Z-Wave JS add-on discovery info.", "addon_info_failed": "Failed to get Z-Wave JS add-on info.", "addon_install_failed": "Failed to install the Z-Wave JS add-on.", + "addon_missing_discovery_info": "Missing Z-Wave JS add-on discovery info.", "addon_set_config_failed": "Failed to set Z-Wave JS configuration.", "addon_start_failed": "Failed to start the Z-Wave JS add-on.", "already_configured": "Device is already configured", @@ -48,6 +49,11 @@ }, "start_addon": { "title": "The Z-Wave JS add-on is starting." + }, + "user": { + "data": { + "url": "URL" + } } } }, diff --git a/homeassistant/components/zwave_js/translations/fr.json b/homeassistant/components/zwave_js/translations/fr.json index 9cc8bf822b87d4..ce9f2f8b501dd8 100644 --- a/homeassistant/components/zwave_js/translations/fr.json +++ b/homeassistant/components/zwave_js/translations/fr.json @@ -42,9 +42,9 @@ }, "on_supervisor": { "data": { - "use_addon": "Utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor" + "use_addon": "Utiliser le module compl\u00e9mentaire Z-Wave JS du Supervisor" }, - "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS Supervisor?", + "description": "Voulez-vous utiliser le module compl\u00e9mentaire Z-Wave JS du Supervisor?", "title": "S\u00e9lectionner la m\u00e9thode de connexion" }, "start_addon": { diff --git a/homeassistant/components/zwave_js/translations/hu.json b/homeassistant/components/zwave_js/translations/hu.json new file mode 100644 index 00000000000000..6732251f3a0a10 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/hu.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "addon_start_failed": "Nem siker\u00fclt elind\u00edtani a Z-Wave JS b\u0151v\u00edtm\u00e9nyt.", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_ws_url": "\u00c9rv\u00e9nytelen websocket URL", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "progress": { + "start_addon": "V\u00e1rj am\u00edg a Z-Wave JS b\u0151v\u00edtm\u00e9ny elindul. Ez eltarthat n\u00e9h\u00e1ny m\u00e1sodpercig." + }, + "step": { + "configure_addon": { + "data": { + "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" + } + }, + "install_addon": { + "title": "Elkezd\u0151d\u00f6tt a Z-Wave JS b\u0151v\u00edtm\u00e9ny telep\u00edt\u00e9se" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Haszn\u00e1ld a Z-Wave JS Supervisor b\u0151v\u00edtm\u00e9nyt" + }, + "description": "Szeretn\u00e9d haszn\u00e1lni az Z-Wave JS Supervisor b\u0151v\u00edtm\u00e9nyt?", + "title": "V\u00e1laszd ki a csatlakoz\u00e1si m\u00f3dot" + }, + "start_addon": { + "title": "Indul a Z-Wave JS b\u0151v\u00edtm\u00e9ny." + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/id.json b/homeassistant/components/zwave_js/translations/id.json new file mode 100644 index 00000000000000..e8ea9381544c78 --- /dev/null +++ b/homeassistant/components/zwave_js/translations/id.json @@ -0,0 +1,61 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Gagal mendapatkan info penemuan add-on Z-Wave JS.", + "addon_info_failed": "Gagal mendapatkan info add-on Z-Wave JS.", + "addon_install_failed": "Gagal menginstal add-on Z-Wave JS.", + "addon_missing_discovery_info": "Info penemuan add-on Z-Wave JS tidak ada.", + "addon_set_config_failed": "Gagal menyetel konfigurasi Z-Wave JS.", + "addon_start_failed": "Gagal memulai add-on Z-Wave JS.", + "already_configured": "Perangkat sudah dikonfigurasi", + "already_in_progress": "Alur konfigurasi sedang berlangsung", + "cannot_connect": "Gagal terhubung" + }, + "error": { + "addon_start_failed": "Gagal memulai add-on Z-Wave JS. Periksa konfigurasi.", + "cannot_connect": "Gagal terhubung", + "invalid_ws_url": "URL websocket tidak valid", + "unknown": "Kesalahan yang tidak diharapkan" + }, + "progress": { + "install_addon": "Harap tunggu hingga penginstalan add-on Z-Wave JS selesai. Ini bisa memakan waktu beberapa saat.", + "start_addon": "Harap tunggu hingga add-on Z-Wave JS selesai. Ini mungkin perlu waktu beberapa saat." + }, + "step": { + "configure_addon": { + "data": { + "network_key": "Kunci Jaringan", + "usb_path": "Jalur Perangkat USB" + }, + "title": "Masukkan konfigurasi add-on Z-Wave JS" + }, + "hassio_confirm": { + "title": "Siapkan integrasi Z-Wave JS dengan add-on Z-Wave JS" + }, + "install_addon": { + "title": "Instalasi add-on Z-Wave JS telah dimulai" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Gunakan add-on Supervisor Z-Wave JS" + }, + "description": "Ingin menggunakan add-on Supervisor Z-Wave JS?", + "title": "Pilih metode koneksi" + }, + "start_addon": { + "title": "Add-on Z-Wave JS sedang dimulai." + }, + "user": { + "data": { + "url": "URL" + } + } + } + }, + "title": "Z-Wave JS" +} \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json index 9c86a064151093..72d81531551fe6 100644 --- a/homeassistant/components/zwave_js/translations/ko.json +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -1,30 +1,61 @@ { "config": { "abort": { + "addon_get_discovery_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_missing_discovery_info": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "addon_set_config_failed": "Z-Wave JS \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_ws_url": "\uc6f9 \uc18c\ucf13 URL \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "progress": { + "install_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "start_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc2dc\uc791\uc774 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, "step": { "configure_addon": { "data": { + "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" - } + }, + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + }, + "hassio_confirm": { + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Z-Wave JS \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + }, + "install_addon": { + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "manual": { "data": { "url": "URL \uc8fc\uc18c" } }, + "on_supervisor": { + "data": { + "use_addon": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + }, + "description": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "start_addon": { + "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." + }, "user": { "data": { "url": "URL \uc8fc\uc18c" } } } - } + }, + "title": "Z-Wave JS" } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/nl.json b/homeassistant/components/zwave_js/translations/nl.json index c15cfd26f31c78..f50c9c8cebae46 100644 --- a/homeassistant/components/zwave_js/translations/nl.json +++ b/homeassistant/components/zwave_js/translations/nl.json @@ -6,6 +6,7 @@ "addon_install_failed": "Kan de Z-Wave JS add-on niet installeren.", "addon_missing_discovery_info": "De Z-Wave JS addon mist ontdekkings informatie", "addon_set_config_failed": "Instellen van de Z-Wave JS configuratie is mislukt.", + "addon_start_failed": "Kan de Z-Wave JS add-on niet starten.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken" @@ -17,7 +18,8 @@ "unknown": "Onverwachte fout" }, "progress": { - "install_addon": "Een ogenblik geduld terwijl de installatie van de Z-Wave JS add-on is voltooid. Dit kan enkele minuten duren." + "install_addon": "Een ogenblik geduld terwijl de installatie van de Z-Wave JS add-on is voltooid. Dit kan enkele minuten duren.", + "start_addon": "Wacht alstublieft terwijl de Z-Wave JS add-on start voltooid is. Dit kan enkele seconden duren." }, "step": { "configure_addon": { @@ -45,6 +47,9 @@ "description": "Wilt u de Z-Wave JS Supervisor add-on gebruiken?", "title": "Selecteer verbindingsmethode" }, + "start_addon": { + "title": "De add-on Z-Wave JS wordt gestart." + }, "user": { "data": { "url": "URL" diff --git a/homeassistant/components/zwave_js/translations/zh-Hant.json b/homeassistant/components/zwave_js/translations/zh-Hant.json index f1495b1aedadb9..10b003f71e8cb3 100644 --- a/homeassistant/components/zwave_js/translations/zh-Hant.json +++ b/homeassistant/components/zwave_js/translations/zh-Hant.json @@ -1,25 +1,25 @@ { "config": { "abort": { - "addon_get_discovery_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u5931\u6557\u3002", - "addon_info_failed": "\u53d6\u5f97 Z-Wave JS add-on \u8cc7\u8a0a\u5931\u6557\u3002", - "addon_install_failed": "Z-Wave JS add-on \u5b89\u88dd\u5931\u6557\u3002", - "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS add-on \u63a2\u7d22\u8cc7\u8a0a\u3002", - "addon_set_config_failed": "Z-Wave JS add-on \u8a2d\u5b9a\u5931\u6557\u3002", - "addon_start_failed": "Z-Wave JS add-on \u555f\u59cb\u5931\u6557\u3002", + "addon_get_discovery_info_failed": "\u53d6\u5f97 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u63a2\u7d22\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_info_failed": "\u53d6\u5f97 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8cc7\u8a0a\u5931\u6557\u3002", + "addon_install_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5931\u6557\u3002", + "addon_missing_discovery_info": "\u7f3a\u5c11 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u63a2\u7d22\u8cc7\u8a0a\u3002", + "addon_set_config_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a\u5931\u6557\u3002", + "addon_start_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u5931\u6557\u3002", "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "error": { - "addon_start_failed": "Z-Wave JS add-on \u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002", + "addon_start_failed": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u52d5\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5b9a\u3002", "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_ws_url": "Websocket URL \u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "progress": { - "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", - "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS add-on \u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" + "install_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002", + "start_addon": "\u8acb\u7a0d\u7b49 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u5b8c\u6210\uff0c\u53ef\u80fd\u6703\u9700\u8981\u5e7e\u5206\u9418\u3002" }, "step": { "configure_addon": { @@ -27,13 +27,13 @@ "network_key": "\u7db2\u8def\u5bc6\u9470", "usb_path": "USB \u88dd\u7f6e\u8def\u5f91" }, - "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u8a2d\u5b9a" + "title": "\u8f38\u5165 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a" }, "hassio_confirm": { - "title": "\u4ee5 Z-Wave JS add-on \u8a2d\u5b9a Z-Wave JS \u6574\u5408" + "title": "\u4ee5 Z-Wave JS \u9644\u52a0\u5143\u4ef6\u8a2d\u5b9a Z-Wave JS \u6574\u5408" }, "install_addon": { - "title": "Z-Wave JS add-on \u5b89\u88dd\u5df2\u555f\u52d5" + "title": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u5b89\u88dd\u5df2\u555f\u52d5" }, "manual": { "data": { @@ -42,13 +42,13 @@ }, "on_supervisor": { "data": { - "use_addon": "\u4f7f\u7528 Z-Wave JS Supervisor add-on" + "use_addon": "\u4f7f\u7528 Z-Wave JS Supervisor \u9644\u52a0\u5143\u4ef6" }, - "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor add-on\uff1f", + "description": "\u662f\u5426\u8981\u4f7f\u7528 Z-Wave JS Supervisor \u9644\u52a0\u5143\u4ef6\uff1f", "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" }, "start_addon": { - "title": "Z-Wave JS add-on \u555f\u59cb\u4e2d\u3002" + "title": "Z-Wave JS \u9644\u52a0\u5143\u4ef6\u555f\u59cb\u4e2d\u3002" }, "user": { "data": { From 0e368df02381587c12120b490c27284f008eca87 Mon Sep 17 00:00:00 2001 From: SoCalix <48040807+Socalix@users.noreply.github.com> Date: Mon, 15 Mar 2021 20:07:54 -0500 Subject: [PATCH 1288/1818] Fix xmpp notify for muc rooms (#46715) --- homeassistant/components/xmpp/notify.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index 68a041a28877c3..2abd3ffa245c4d 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -165,7 +165,7 @@ async def start(self, event): if message: self.send_text_message() - self.disconnect(wait=True) + self.disconnect() async def send_file(self, timeout=None): """Send file via XMPP. @@ -174,7 +174,7 @@ async def send_file(self, timeout=None): HTTP Upload (XEP_0363) """ if room: - self.plugin["xep_0045"].join_muc(room, sender, wait=True) + self.plugin["xep_0045"].join_muc(room, sender) try: # Uploading with XEP_0363 @@ -335,7 +335,7 @@ def send_text_message(self): try: if room: _LOGGER.debug("Joining room %s", room) - self.plugin["xep_0045"].join_muc(room, sender, wait=True) + self.plugin["xep_0045"].join_muc(room, sender) self.send_message(mto=room, mbody=message, mtype="groupchat") else: for recipient in recipients: From 2230b03888a3d71d280c983f49d12e6a875088a5 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Tue, 16 Mar 2021 08:13:03 +0100 Subject: [PATCH 1289/1818] Add voltage device class to devolo Home Control (#47967) --- homeassistant/components/devolo_home_control/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index e78b4eabeacc78..9fa3bdcf8093c5 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -5,6 +5,7 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE @@ -20,6 +21,7 @@ "humidity": DEVICE_CLASS_HUMIDITY, "current": DEVICE_CLASS_POWER, "total": DEVICE_CLASS_POWER, + "voltage": DEVICE_CLASS_VOLTAGE, } From 354c0a7fd119c20120653e4244fcddc67c6ca706 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 16 Mar 2021 08:41:41 +0100 Subject: [PATCH 1290/1818] Add reauthentication to Verisure (#47972) * Add reauthentication to Verisure * Update translations * Correct translation step key * Address pylint warning * = is not : --- homeassistant/components/verisure/__init__.py | 9 +- .../components/verisure/config_flow.py | 52 +++++++- .../components/verisure/strings.json | 10 +- .../components/verisure/translations/en.json | 10 +- tests/components/verisure/test_config_flow.py | 121 ++++++++++++++++++ 5 files changed, 196 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 5f8b2119310cee..1a727daac73834 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -15,7 +15,7 @@ from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, ConfigEntry from homeassistant.const import ( CONF_EMAIL, CONF_PASSWORD, @@ -35,7 +35,6 @@ CONF_LOCK_DEFAULT_CODE, DEFAULT_LOCK_CODE_DIGITS, DOMAIN, - LOGGER, ) from .coordinator import VerisureDataUpdateCoordinator @@ -125,7 +124,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = VerisureDataUpdateCoordinator(hass, entry=entry) if not await coordinator.async_login(): - LOGGER.error("Could not login to Verisure, aborting setting up integration") + await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_REAUTH}, + data={"entry": entry}, + ) return False hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout) diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py index ce9c76874f1554..f05571d338c48f 100644 --- a/homeassistant/components/verisure/config_flow.py +++ b/homeassistant/components/verisure/config_flow.py @@ -36,8 +36,9 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL - installations: dict[str, str] email: str + entry: ConfigEntry + installations: dict[str, str] password: str # These can be removed after YAML import has been removed. @@ -123,6 +124,55 @@ async def async_step_installation( }, ) + async def async_step_reauth(self, data: dict[str, Any]) -> dict[str, Any]: + """Handle initiation of re-authentication with Verisure.""" + self.entry = data["entry"] + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: + """Handle re-authentication with Verisure.""" + errors: dict[str, str] = {} + + if user_input is not None: + verisure = Verisure( + username=user_input[CONF_EMAIL], password=user_input[CONF_PASSWORD] + ) + try: + await self.hass.async_add_executor_job(verisure.login) + except VerisureLoginError as ex: + LOGGER.debug("Could not log in to Verisure, %s", ex) + errors["base"] = "invalid_auth" + except (VerisureError, VerisureResponseError) as ex: + LOGGER.debug("Unexpected response from Verisure, %s", ex) + errors["base"] = "unknown" + else: + data = self.entry.data.copy() + self.hass.config_entries.async_update_entry( + self.entry, + data={ + **data, + CONF_EMAIL: user_input[CONF_EMAIL], + CONF_PASSWORD: user_input[CONF_PASSWORD], + }, + ) + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry.entry_id) + ) + return self.async_abort(reason="reauth_successful") + + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema( + { + vol.Required(CONF_EMAIL, default=self.entry.data[CONF_EMAIL]): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + async def async_step_import(self, user_input: dict[str, Any]) -> dict[str, Any]: """Import Verisure YAML configuration.""" if user_input[CONF_GIID]: diff --git a/homeassistant/components/verisure/strings.json b/homeassistant/components/verisure/strings.json index 0c7f513f8eee42..5170bff5faa4f1 100644 --- a/homeassistant/components/verisure/strings.json +++ b/homeassistant/components/verisure/strings.json @@ -13,6 +13,13 @@ "data": { "giid": "Installation" } + }, + "reauth_confirm": { + "data": { + "description": "Re-authenticate with your Verisure My Pages account.", + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { @@ -20,7 +27,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "options": { diff --git a/homeassistant/components/verisure/translations/en.json b/homeassistant/components/verisure/translations/en.json index 85c7acc167e9c8..57f73c3772b9f3 100644 --- a/homeassistant/components/verisure/translations/en.json +++ b/homeassistant/components/verisure/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account is already configured" + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { "invalid_auth": "Invalid authentication", @@ -14,6 +15,13 @@ }, "description": "Home Assistant found multiple Verisure installations in your My Pages account. Please, select the installation to add to Home Assistant." }, + "reauth_confirm": { + "data": { + "description": "Re-authenticate with your Verisure My Pages account.", + "email": "Email", + "password": "Password" + } + }, "user": { "data": { "description": "Sign-in with your Verisure My Pages account.", diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py index 97f0f9731b15c3..c53c418c72b48b 100644 --- a/tests/components/verisure/test_config_flow.py +++ b/tests/components/verisure/test_config_flow.py @@ -190,6 +190,127 @@ async def test_dhcp(hass: HomeAssistant) -> None: assert result["step_id"] == "user" +async def test_reauth_flow(hass: HomeAssistant) -> None: + """Test a reauthentication flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} + ) + assert result["step_id"] == "reauth_confirm" + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + return_value=True, + ) as mock_verisure, patch( + "homeassistant.components.verisure.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.verisure.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "correct horse battery staple", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_ABORT + assert result2["reason"] == "reauth_successful" + assert entry.data == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "correct horse battery staple", + } + + assert len(mock_verisure.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: + """Test a reauthentication flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureLoginError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "WrOngP4ssw0rd!", + }, + ) + await hass.async_block_till_done() + + assert result2["step_id"] == "reauth_confirm" + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_reauth_flow_unknown_error(hass: HomeAssistant) -> None: + """Test a reauthentication flow, with an unknown error happening.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_GIID: "12345", + CONF_PASSWORD: "SuperS3cr3t!", + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data={"entry": entry} + ) + + with patch( + "homeassistant.components.verisure.config_flow.Verisure.login", + side_effect=VerisureError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "WrOngP4ssw0rd!", + }, + ) + await hass.async_block_till_done() + + assert result2["step_id"] == "reauth_confirm" + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "unknown"} + + @pytest.mark.parametrize( "input,output", [ From 1cde1074c91aaf9bb3c1dffcb4d711de5338b919 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 08:49:16 +0100 Subject: [PATCH 1291/1818] Correct trace for choose and repeat script actions (#47973) * Correct trace for choose and repeat script actions * only choose-wrap the choices * Update tests Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/script.py | 25 ++++++++++++++----------- tests/helpers/test_script.py | 21 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index f1732aef31616b..51bf7b14927323 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -640,6 +640,7 @@ def traced_test_conditions(hass, variables): result = traced_test_conditions(self._hass, self._variables) return result + @trace_path("repeat") async def _async_repeat_step(self): """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") @@ -658,7 +659,7 @@ def set_repeat_var(iteration, count=None): async def async_run_sequence(iteration, extra_msg=""): self._log("Repeating %s: Iteration %i%s", description, iteration, extra_msg) - with trace_path(str(self._step)): + with trace_path("sequence"): await self._async_run_script(script) if CONF_COUNT in repeat: @@ -724,19 +725,21 @@ async def _async_choose_step(self) -> None: # pylint: disable=protected-access choose_data = await self._script._async_get_choose_data(self._step) - for idx, (conditions, script) in enumerate(choose_data["choices"]): - with trace_path(str(idx)): - try: - if self._test_conditions(conditions, "choose"): - trace_set_result(choice=idx) - await self._async_run_script(script) - return - except exceptions.ConditionError as ex: - _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) + with trace_path("choose"): + for idx, (conditions, script) in enumerate(choose_data["choices"]): + with trace_path(str(idx)): + try: + if self._test_conditions(conditions, "choose"): + trace_set_result(choice=idx) + with trace_path("sequence"): + await self._async_run_script(script) + return + except exceptions.ConditionError as ex: + _LOGGER.warning("Error in 'choose' evaluation:\n%s", ex) if choose_data["default"]: trace_set_result(choice="default") - with trace_path("default"): + with trace_path(["default", "sequence"]): await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 7cb4b627a945f3..f6246299074904 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1236,7 +1236,7 @@ async def test_repeat_count(hass, caplog, count): assert_action_trace( { "0": [{}], - "0/0/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + "0/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), } ) @@ -1578,6 +1578,10 @@ async def test_choose(hass, caplog, var, result): }, } ) + + # Prepare tracing + trace.trace_get() + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(MappingProxyType({"var": var}), Context()) @@ -1590,6 +1594,21 @@ async def test_choose(hass, caplog, var, result): expected_choice = "default" assert f"{alias}: {expected_choice}: Executing step {aliases[var]}" in caplog.text + expected_trace = {"0": [{}]} + if var >= 1: + expected_trace["0/choose/0"] = [{}] + expected_trace["0/choose/0/conditions/0"] = [{}] + if var >= 2: + expected_trace["0/choose/1"] = [{}] + expected_trace["0/choose/1/conditions/0"] = [{}] + if var == 1: + expected_trace["0/choose/0/sequence/0"] = [{}] + if var == 2: + expected_trace["0/choose/1/sequence/0"] = [{}] + if var == 3: + expected_trace["0/default/sequence/0"] = [{}] + assert_action_trace(expected_trace) + @pytest.mark.parametrize( "action", From 333e6a215a021c87dea472cee756c6931f08de3f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 08:51:00 +0100 Subject: [PATCH 1292/1818] Add execute_script WS API (#47964) * Add execute_script WS API * Improve tests --- .../components/websocket_api/commands.py | 35 +++++++++++++---- .../components/websocket_api/test_commands.py | 39 +++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 53531cf9ba9931..f85281d10c1b22 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -26,19 +26,20 @@ @callback def async_register_commands(hass, async_reg): """Register commands.""" - async_reg(hass, handle_subscribe_events) - async_reg(hass, handle_unsubscribe_events) async_reg(hass, handle_call_service) - async_reg(hass, handle_get_states) - async_reg(hass, handle_get_services) + async_reg(hass, handle_entity_source) + async_reg(hass, handle_execute_script) async_reg(hass, handle_get_config) + async_reg(hass, handle_get_services) + async_reg(hass, handle_get_states) + async_reg(hass, handle_manifest_get) + async_reg(hass, handle_manifest_list) async_reg(hass, handle_ping) async_reg(hass, handle_render_template) - async_reg(hass, handle_manifest_list) - async_reg(hass, handle_manifest_get) - async_reg(hass, handle_entity_source) + async_reg(hass, handle_subscribe_events) async_reg(hass, handle_subscribe_trigger) async_reg(hass, handle_test_condition) + async_reg(hass, handle_unsubscribe_events) def pong_message(iden): @@ -420,3 +421,23 @@ async def handle_test_condition(hass, connection, msg): connection.send_result( msg["id"], {"result": check_condition(hass, msg.get("variables"))} ) + + +@decorators.websocket_command( + { + vol.Required("type"): "execute_script", + vol.Required("sequence"): cv.SCRIPT_SCHEMA, + } +) +@decorators.require_admin +@decorators.async_response +async def handle_execute_script(hass, connection, msg): + """Handle execute script command.""" + # Circular dep + # pylint: disable=import-outside-toplevel + from homeassistant.helpers.script import Script + + context = connection.context(msg) + script_obj = Script(hass, msg["sequence"], f"{const.DOMAIN} script", const.DOMAIN) + await script_obj.async_run(context=context) + connection.send_message(messages.result_message(msg["id"], {"context": context})) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index f596db63c5ea70..a83f9509d01b5e 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -44,6 +44,7 @@ async def test_call_service(hass, websocket_client): assert call.domain == "domain_test" assert call.service == "test_service" assert call.data == {"hello": "world"} + assert call.context.as_dict() == msg["result"]["context"] async def test_call_service_target(hass, websocket_client): @@ -79,6 +80,7 @@ async def test_call_service_target(hass, websocket_client): "entity_id": ["entity.one", "entity.two"], "device_id": ["deviceid"], } + assert call.context.as_dict() == msg["result"]["context"] async def test_call_service_target_template(hass, websocket_client): @@ -985,3 +987,40 @@ async def test_test_condition(hass, websocket_client): assert msg["type"] == const.TYPE_RESULT assert msg["success"] assert msg["result"]["result"] is True + + +async def test_execute_script(hass, websocket_client): + """Test testing a condition.""" + calls = async_mock_service(hass, "domain_test", "test_service") + + await websocket_client.send_json( + { + "id": 5, + "type": "execute_script", + "sequence": [ + { + "service": "domain_test.test_service", + "data": {"hello": "world"}, + } + ], + } + ) + + await hass.async_block_till_done() + await hass.async_block_till_done() + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + + await hass.async_block_till_done() + await hass.async_block_till_done() + + assert len(calls) == 1 + call = calls[0] + + assert call.domain == "domain_test" + assert call.service == "test_service" + assert call.data == {"hello": "world"} + assert call.context.as_dict() == msg["result"]["context"] From 5f2326fb5756b85255cec27d3b2172c44bc66058 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 12:51:39 +0100 Subject: [PATCH 1293/1818] Add support for light color modes (#47720) * Add support for light color modes * Update tests * Update comments * Fix bugs, add tests * Suppress lint errors * Don't suppress brightness when state is ambiguous * Improve reproduce_state + add tests * Add comment * Change COLOR_MODE_* constants, rename COLOR_MODE_DIMMER to COLOR_MODE_BRIGHTNESS * Fix tests * Tweaks --- homeassistant/components/light/__init__.py | 271 +++++++++- .../components/light/reproduce_state.py | 47 +- tests/components/kulersky/test_light.py | 11 + tests/components/light/test_init.py | 479 ++++++++++++++++++ .../components/light/test_reproduce_state.py | 82 ++- tests/components/yeelight/test_light.py | 22 +- tests/components/zerproc/test_light.py | 11 + .../custom_components/test/light.py | 13 + 8 files changed, 897 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 693eb1a573ad3f..56fd5841388d47 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -6,7 +6,7 @@ from datetime import timedelta import logging import os -from typing import Dict, List, Optional, Tuple, cast +from typing import Dict, List, Optional, Set, Tuple, cast import voluptuous as vol @@ -38,19 +38,49 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}" # Bitfield of features supported by the light entity -SUPPORT_BRIGHTNESS = 1 -SUPPORT_COLOR_TEMP = 2 +SUPPORT_BRIGHTNESS = 1 # Deprecated, replaced by color modes +SUPPORT_COLOR_TEMP = 2 # Deprecated, replaced by color modes SUPPORT_EFFECT = 4 SUPPORT_FLASH = 8 -SUPPORT_COLOR = 16 +SUPPORT_COLOR = 16 # Deprecated, replaced by color modes SUPPORT_TRANSITION = 32 -SUPPORT_WHITE_VALUE = 128 +SUPPORT_WHITE_VALUE = 128 # Deprecated, replaced by color modes + +# Color mode of the light +ATTR_COLOR_MODE = "color_mode" +# List of color modes supported by the light +ATTR_SUPPORTED_COLOR_MODES = "supported_color_modes" +# Possible color modes +COLOR_MODE_UNKNOWN = "unknown" # Ambiguous color mode +COLOR_MODE_ONOFF = "onoff" # Must be the only supported mode +COLOR_MODE_BRIGHTNESS = "brightness" # Must be the only supported mode +COLOR_MODE_COLOR_TEMP = "color_temp" +COLOR_MODE_HS = "hs" +COLOR_MODE_XY = "xy" +COLOR_MODE_RGB = "rgb" +COLOR_MODE_RGBW = "rgbw" +COLOR_MODE_RGBWW = "rgbww" + +VALID_COLOR_MODES = { + COLOR_MODE_ONOFF, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_XY, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, + COLOR_MODE_RGBWW, +} +COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF} +COLOR_MODES_COLOR = {COLOR_MODE_HS, COLOR_MODE_RGB, COLOR_MODE_XY} # Float that represents transition time in seconds to make change. ATTR_TRANSITION = "transition" # Lists holding color values ATTR_RGB_COLOR = "rgb_color" +ATTR_RGBW_COLOR = "rgbw_color" +ATTR_RGBWW_COLOR = "rgbww_color" ATTR_XY_COLOR = "xy_color" ATTR_HS_COLOR = "hs_color" ATTR_COLOR_TEMP = "color_temp" @@ -104,7 +134,13 @@ vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT, vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All( - vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple) + vol.ExactSequence((cv.byte,) * 3), vol.Coerce(tuple) + ), + vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All( + vol.ExactSequence((cv.byte,) * 4), vol.Coerce(tuple) + ), + vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All( + vol.ExactSequence((cv.byte,) * 5), vol.Coerce(tuple) ), vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All( vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple) @@ -166,14 +202,6 @@ def preprocess_turn_on_alternatives(hass, params): if brightness_pct is not None: params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100) - xy_color = params.pop(ATTR_XY_COLOR, None) - if xy_color is not None: - params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) - - rgb_color = params.pop(ATTR_RGB_COLOR, None) - if rgb_color is not None: - params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) - def filter_turn_off_params(params): """Filter out params not used in turn off.""" @@ -228,6 +256,52 @@ async def async_handle_light_on_service(light, call): if ATTR_PROFILE not in params: profiles.apply_default(light.entity_id, params) + supported_color_modes = light.supported_color_modes + # Backwards compatibility: if an RGBWW color is specified, convert to RGB + W + # for legacy lights + if ATTR_RGBW_COLOR in params: + legacy_supported_color_modes = ( + light._light_internal_supported_color_modes # pylint: disable=protected-access + ) + if ( + COLOR_MODE_RGBW in legacy_supported_color_modes + and not supported_color_modes + ): + rgbw_color = params.pop(ATTR_RGBW_COLOR) + params[ATTR_RGB_COLOR] = rgbw_color[0:3] + params[ATTR_WHITE_VALUE] = rgbw_color[3] + + # If a color is specified, convert to the color space supported by the light + # Backwards compatibility: Fall back to hs color if light.supported_color_modes + # is not implemented + if not supported_color_modes: + if (rgb_color := params.pop(ATTR_RGB_COLOR, None)) is not None: + params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) + elif (xy_color := params.pop(ATTR_XY_COLOR, None)) is not None: + params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) + elif ATTR_HS_COLOR in params and COLOR_MODE_HS not in supported_color_modes: + hs_color = params.pop(ATTR_HS_COLOR) + if COLOR_MODE_RGB in supported_color_modes: + params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) + elif COLOR_MODE_XY in supported_color_modes: + params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + elif ATTR_RGB_COLOR in params and COLOR_MODE_RGB not in supported_color_modes: + rgb_color = params.pop(ATTR_RGB_COLOR) + if COLOR_MODE_HS in supported_color_modes: + params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) + elif COLOR_MODE_XY in supported_color_modes: + params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) + elif ATTR_XY_COLOR in params and COLOR_MODE_XY not in supported_color_modes: + xy_color = params.pop(ATTR_XY_COLOR) + if COLOR_MODE_HS in supported_color_modes: + params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) + elif COLOR_MODE_RGB in supported_color_modes: + params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color) + + # Remove deprecated white value if the light supports color mode + if supported_color_modes: + params.pop(ATTR_WHITE_VALUE, None) + # Zero brightness: Light will be turned off if params.get(ATTR_BRIGHTNESS) == 0: await light.async_turn_off(**filter_turn_off_params(params)) @@ -411,11 +485,83 @@ def brightness(self) -> Optional[int]: """Return the brightness of this light between 0..255.""" return None + @property + def color_mode(self) -> Optional[str]: + """Return the color mode of the light.""" + return None + + @property + def _light_internal_color_mode(self) -> str: + """Return the color mode of the light with backwards compatibility.""" + color_mode = self.color_mode + + if color_mode is None: + # Backwards compatibility for color_mode added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + supported = self._light_internal_supported_color_modes + + if ( + COLOR_MODE_RGBW in supported + and self.white_value is not None + and self.hs_color is not None + ): + return COLOR_MODE_RGBW + if COLOR_MODE_HS in supported and self.hs_color is not None: + return COLOR_MODE_HS + if COLOR_MODE_COLOR_TEMP in supported and self.color_temp is not None: + return COLOR_MODE_COLOR_TEMP + if COLOR_MODE_BRIGHTNESS in supported and self.brightness is not None: + return COLOR_MODE_BRIGHTNESS + if COLOR_MODE_ONOFF in supported: + return COLOR_MODE_ONOFF + return COLOR_MODE_UNKNOWN + + return color_mode + @property def hs_color(self) -> Optional[Tuple[float, float]]: """Return the hue and saturation color value [float, float].""" return None + @property + def xy_color(self) -> Optional[Tuple[float, float]]: + """Return the xy color value [float, float].""" + return None + + @property + def rgb_color(self) -> Optional[Tuple[int, int, int]]: + """Return the rgb color value [int, int, int].""" + return None + + @property + def rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + """Return the rgbw color value [int, int, int, int].""" + return None + + @property + def _light_internal_rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + """Return the rgbw color value [int, int, int, int].""" + rgbw_color = self.rgbw_color + if ( + rgbw_color is None + and self.hs_color is not None + and self.white_value is not None + ): + # Backwards compatibility for rgbw_color added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + r, g, b = color_util.color_hs_to_RGB( # pylint: disable=invalid-name + *self.hs_color + ) + w = self.white_value # pylint: disable=invalid-name + rgbw_color = (r, g, b, w) + + return rgbw_color + + @property + def rgbww_color(self) -> Optional[Tuple[int, int, int, int, int]]: + """Return the rgbww color value [int, int, int, int, int].""" + return None + @property def color_temp(self) -> Optional[int]: """Return the CT color value in mireds.""" @@ -463,6 +609,29 @@ def capability_attributes(self): if supported_features & SUPPORT_EFFECT: data[ATTR_EFFECT_LIST] = self.effect_list + data[ATTR_SUPPORTED_COLOR_MODES] = sorted( + list(self._light_internal_supported_color_modes) + ) + + return data + + def _light_internal_convert_color(self, color_mode: str) -> dict: + data: Dict[str, Tuple] = {} + if color_mode == COLOR_MODE_HS and self.hs_color: + hs_color = self.hs_color + data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) + data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) + data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + elif color_mode == COLOR_MODE_XY and self.xy_color: + xy_color = self.xy_color + data[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color) + data[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color) + data[ATTR_XY_COLOR] = (round(xy_color[0], 6), round(xy_color[1], 6)) + elif color_mode == COLOR_MODE_RGB and self.rgb_color: + rgb_color = self.rgb_color + data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) + data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3]) + data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) return data @property @@ -473,27 +642,85 @@ def state_attributes(self): data = {} supported_features = self.supported_features + color_mode = self._light_internal_color_mode + + if color_mode not in self._light_internal_supported_color_modes: + # Increase severity to warning in 2021.6, reject in 2021.10 + _LOGGER.debug( + "%s: set to unsupported color_mode: %s, supported_color_modes: %s", + self.entity_id, + color_mode, + self._light_internal_supported_color_modes, + ) - if supported_features & SUPPORT_BRIGHTNESS: + data[ATTR_COLOR_MODE] = color_mode + + if color_mode in COLOR_MODES_BRIGHTNESS: + data[ATTR_BRIGHTNESS] = self.brightness + elif supported_features & SUPPORT_BRIGHTNESS: + # Backwards compatibility for ambiguous / incomplete states + # Add warning in 2021.6, remove in 2021.10 data[ATTR_BRIGHTNESS] = self.brightness - if supported_features & SUPPORT_COLOR_TEMP: + if color_mode == COLOR_MODE_COLOR_TEMP: data[ATTR_COLOR_TEMP] = self.color_temp - if supported_features & SUPPORT_COLOR and self.hs_color: - hs_color = self.hs_color - data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) - data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) - data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + if color_mode in COLOR_MODES_COLOR: + data.update(self._light_internal_convert_color(color_mode)) - if supported_features & SUPPORT_WHITE_VALUE: + if color_mode == COLOR_MODE_RGBW: + data[ATTR_RGBW_COLOR] = self._light_internal_rgbw_color + + if color_mode == COLOR_MODE_RGBWW: + data[ATTR_RGBWW_COLOR] = self.rgbww_color + + if supported_features & SUPPORT_COLOR_TEMP and not self.supported_color_modes: + # Backwards compatibility + # Add warning in 2021.6, remove in 2021.10 + data[ATTR_COLOR_TEMP] = self.color_temp + + if supported_features & SUPPORT_WHITE_VALUE and not self.supported_color_modes: + # Backwards compatibility + # Add warning in 2021.6, remove in 2021.10 data[ATTR_WHITE_VALUE] = self.white_value + if self.hs_color is not None: + data.update(self._light_internal_convert_color(COLOR_MODE_HS)) if supported_features & SUPPORT_EFFECT: data[ATTR_EFFECT] = self.effect return {key: val for key, val in data.items() if val is not None} + @property + def _light_internal_supported_color_modes(self) -> Set: + """Calculate supported color modes with backwards compatibility.""" + supported_color_modes = self.supported_color_modes + + if supported_color_modes is None: + # Backwards compatibility for supported_color_modes added in 2021.4 + # Add warning in 2021.6, remove in 2021.10 + supported_features = self.supported_features + supported_color_modes = set() + + if supported_features & SUPPORT_COLOR_TEMP: + supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + if supported_features & SUPPORT_COLOR: + supported_color_modes.add(COLOR_MODE_HS) + if supported_features & SUPPORT_WHITE_VALUE: + supported_color_modes.add(COLOR_MODE_RGBW) + if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes: + supported_color_modes = {COLOR_MODE_BRIGHTNESS} + + if not supported_color_modes: + supported_color_modes = {COLOR_MODE_ONOFF} + + return supported_color_modes + + @property + def supported_color_modes(self) -> Optional[Set]: + """Flag supported color modes.""" + return None + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index a7939beb91e16b..863790cff71d04 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -17,6 +17,7 @@ from . import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, + ATTR_COLOR_MODE, ATTR_COLOR_NAME, ATTR_COLOR_TEMP, ATTR_EFFECT, @@ -25,9 +26,18 @@ ATTR_KELVIN, ATTR_PROFILE, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, + COLOR_MODE_RGBWW, + COLOR_MODE_UNKNOWN, + COLOR_MODE_XY, DOMAIN, ) @@ -48,6 +58,8 @@ ATTR_HS_COLOR, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, ATTR_XY_COLOR, # The following color attributes are deprecated ATTR_PROFILE, @@ -55,6 +67,15 @@ ATTR_KELVIN, ] +COLOR_MODE_TO_ATTRIBUTE = { + COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP, + COLOR_MODE_HS: ATTR_HS_COLOR, + COLOR_MODE_RGB: ATTR_RGB_COLOR, + COLOR_MODE_RGBW: ATTR_RGBW_COLOR, + COLOR_MODE_RGBWW: ATTR_RGBWW_COLOR, + COLOR_MODE_XY: ATTR_XY_COLOR, +} + DEPRECATED_GROUP = [ ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, @@ -114,11 +135,29 @@ async def _async_reproduce_state( if attr in state.attributes: service_data[attr] = state.attributes[attr] - for color_attr in COLOR_GROUP: - # Choose the first color that is specified - if color_attr in state.attributes: + if ( + state.attributes.get(ATTR_COLOR_MODE, COLOR_MODE_UNKNOWN) + != COLOR_MODE_UNKNOWN + ): + # Remove deprecated white value if we got a valid color mode + service_data.pop(ATTR_WHITE_VALUE, None) + color_mode = state.attributes[ATTR_COLOR_MODE] + if color_attr := COLOR_MODE_TO_ATTRIBUTE.get(color_mode): + if color_attr not in state.attributes: + _LOGGER.warning( + "Color mode %s specified but attribute %s missing for: %s", + color_mode, + color_attr, + state.entity_id, + ) + return service_data[color_attr] = state.attributes[color_attr] - break + else: + # Fall back to Choosing the first color that is specified + for color_attr in COLOR_GROUP: + if color_attr in state.attributes: + service_data[color_attr] = state.attributes[color_attr] + break elif state.state == STATE_OFF: service = SERVICE_TURN_OFF diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index 9dd13fad18b32a..c08dd10d5970ad 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -8,10 +8,15 @@ from homeassistant.components.kulersky.light import DOMAIN from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, ATTR_HS_COLOR, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, + ATTR_SUPPORTED_COLOR_MODES, ATTR_WHITE_VALUE, ATTR_XY_COLOR, + COLOR_MODE_HS, + COLOR_MODE_RGBW, SCAN_INTERVAL, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, @@ -65,6 +70,7 @@ async def test_init(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, @@ -168,6 +174,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, @@ -183,6 +190,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_UNAVAILABLE assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, @@ -198,12 +206,15 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_ON assert state.attributes == { ATTR_FRIENDLY_NAME: "Bedroom", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE, + ATTR_COLOR_MODE: COLOR_MODE_RGBW, ATTR_BRIGHTNESS: 200, ATTR_HS_COLOR: (200, 60), ATTR_RGB_COLOR: (102, 203, 255), + ATTR_RGBW_COLOR: (102, 203, 255, 240), ATTR_WHITE_VALUE: 240, ATTR_XY_COLOR: (0.184, 0.261), } diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 10f475a580dab0..3adb146a225152 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -915,3 +915,482 @@ async def test_profile_load_optional_hs_color(hass): "invalid_no_brightness_no_color_no_transition", ): assert invalid_profile_name not in profiles.data + + +@pytest.mark.parametrize("light_state", (STATE_ON, STATE_OFF)) +async def test_light_backwards_compatibility_supported_color_modes(hass, light_state): + """Test supported_color_modes if not implemented by the entity.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_0", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_1", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_2", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_3", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_4", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_5", light_state)) + platform.ENTITIES.append(platform.MockLight("Test_6", light_state)) + + entity0 = platform.ENTITIES[0] + + entity1 = platform.ENTITIES[1] + entity1.supported_features = light.SUPPORT_BRIGHTNESS + + entity2 = platform.ENTITIES[2] + entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP + + entity3 = platform.ENTITIES[3] + entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR + + entity4 = platform.ENTITIES[4] + entity4.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + + entity5 = platform.ENTITIES[5] + entity5.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP + ) + + entity6 = platform.ENTITIES[6] + entity6.supported_features = ( + light.SUPPORT_BRIGHTNESS + | light.SUPPORT_COLOR + | light.SUPPORT_COLOR_TEMP + | light.SUPPORT_WHITE_VALUE + ) + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_ONOFF + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_BRIGHTNESS] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_COLOR_TEMP] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity4.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity5.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_HS, + ] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + state = hass.states.get(entity6.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + if light_state == STATE_OFF: + assert "color_mode" not in state.attributes + else: + assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN + + +async def test_light_backwards_compatibility_color_mode(hass): + """Test color_mode if not implemented by the entity.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_2", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_3", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_4", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_5", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_6", STATE_ON)) + + entity0 = platform.ENTITIES[0] + + entity1 = platform.ENTITIES[1] + entity1.supported_features = light.SUPPORT_BRIGHTNESS + entity1.brightness = 100 + + entity2 = platform.ENTITIES[2] + entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP + entity2.color_temp = 100 + + entity3 = platform.ENTITIES[3] + entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR + entity3.hs_color = (240, 100) + + entity4 = platform.ENTITIES[4] + entity4.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + entity4.hs_color = (240, 100) + entity4.white_value = 100 + + entity5 = platform.ENTITIES[5] + entity5.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP + ) + entity5.hs_color = (240, 100) + entity5.color_temp = 100 + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + assert state.attributes["color_mode"] == light.COLOR_MODE_ONOFF + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_BRIGHTNESS] + assert state.attributes["color_mode"] == light.COLOR_MODE_BRIGHTNESS + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_COLOR_TEMP] + assert state.attributes["color_mode"] == light.COLOR_MODE_COLOR_TEMP + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + + state = hass.states.get(entity4.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + assert state.attributes["color_mode"] == light.COLOR_MODE_RGBW + + state = hass.states.get(entity5.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_HS, + ] + # hs color prioritized over color_temp, light should report mode COLOR_MODE_HS + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + + +async def test_light_service_call_rgbw(hass): + """Test backwards compatibility for rgbw functionality in service calls.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_legacy_white_value", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) + + entity0 = platform.ENTITIES[0] + entity0.supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGBW} + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGBW, + ] + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_RGBW] + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [entity0.entity_id, entity1.entity_id], + "brightness_pct": 100, + "rgbw_color": (10, 20, 30, 40), + }, + blocking=True, + ) + + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (210.0, 66.667), "white_value": 40} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 255, "rgbw_color": (10, 20, 30, 40)} + + +async def test_light_state_rgbw(hass): + """Test rgbw color conversion in state updates.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_legacy_white_value", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) + + entity0 = platform.ENTITIES[0] + legacy_supported_features = ( + light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE + ) + entity0.supported_features = legacy_supported_features + entity0.hs_color = (210.0, 66.667) + entity0.rgb_color = "Invalid" # Should be ignored + entity0.rgbww_color = "Invalid" # Should be ignored + entity0.white_value = 40 + entity0.xy_color = "Invalid" # Should be ignored + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGBW} + entity1.color_mode = light.COLOR_MODE_RGBW + entity1.hs_color = "Invalid" # Should be ignored + entity1.rgb_color = "Invalid" # Should be ignored + entity1.rgbw_color = (1, 2, 3, 4) + entity1.rgbww_color = "Invalid" # Should be ignored + entity1.white_value = "Invalid" # Should be ignored + entity1.xy_color = "Invalid" # Should be ignored + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes == { + "color_mode": light.COLOR_MODE_RGBW, + "friendly_name": "Test_legacy_white_value", + "supported_color_modes": [light.COLOR_MODE_HS, light.COLOR_MODE_RGBW], + "supported_features": legacy_supported_features, + "hs_color": (210.0, 66.667), + "rgb_color": (84, 169, 255), + "rgbw_color": (84, 169, 255, 40), + "white_value": 40, + "xy_color": (0.173, 0.207), + } + + state = hass.states.get(entity1.entity_id) + assert state.attributes == { + "color_mode": light.COLOR_MODE_RGBW, + "friendly_name": "Test_rgbw", + "supported_color_modes": [light.COLOR_MODE_RGBW], + "supported_features": 0, + "rgbw_color": (1, 2, 3, 4), + } + + +async def test_light_service_call_color_conversion(hass): + """Test color conversion in service calls.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_all", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) + + entity0 = platform.ENTITIES[0] + entity0.supported_color_modes = {light.COLOR_MODE_HS} + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGB} + + entity2 = platform.ENTITIES[2] + entity2.supported_color_modes = {light.COLOR_MODE_XY} + + entity3 = platform.ENTITIES[3] + entity3.supported_color_modes = { + light.COLOR_MODE_HS, + light.COLOR_MODE_RGB, + light.COLOR_MODE_XY, + } + + entity4 = platform.ENTITIES[4] + entity4.supported_features = light.SUPPORT_COLOR + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_RGB] + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_XY] + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [ + light.COLOR_MODE_HS, + light.COLOR_MODE_RGB, + light.COLOR_MODE_XY, + ] + + state = hass.states.get(entity4.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS] + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + entity2.entity_id, + entity3.entity_id, + entity4.entity_id, + ], + "brightness_pct": 100, + "hs_color": (240, 100), + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (240.0, 100.0)} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 255, "rgb_color": (0, 0, 255)} + _, data = entity2.last_call("turn_on") + assert data == {"brightness": 255, "xy_color": (0.136, 0.04)} + _, data = entity3.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (240.0, 100.0)} + _, data = entity4.last_call("turn_on") + assert data == {"brightness": 255, "hs_color": (240.0, 100.0)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + entity2.entity_id, + entity3.entity_id, + entity4.entity_id, + ], + "brightness_pct": 50, + "rgb_color": (128, 0, 0), + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (0.0, 100.0)} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 128, "rgb_color": (128, 0, 0)} + _, data = entity2.last_call("turn_on") + assert data == {"brightness": 128, "xy_color": (0.701, 0.299)} + _, data = entity3.last_call("turn_on") + assert data == {"brightness": 128, "rgb_color": (128, 0, 0)} + _, data = entity4.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (0.0, 100.0)} + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": [ + entity0.entity_id, + entity1.entity_id, + entity2.entity_id, + entity3.entity_id, + entity4.entity_id, + ], + "brightness_pct": 50, + "xy_color": (0.1, 0.8), + }, + blocking=True, + ) + _, data = entity0.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (125.176, 100.0)} + _, data = entity1.last_call("turn_on") + assert data == {"brightness": 128, "rgb_color": (0, 255, 22)} + _, data = entity2.last_call("turn_on") + assert data == {"brightness": 128, "xy_color": (0.1, 0.8)} + _, data = entity3.last_call("turn_on") + assert data == {"brightness": 128, "xy_color": (0.1, 0.8)} + _, data = entity4.last_call("turn_on") + assert data == {"brightness": 128, "hs_color": (125.176, 100.0)} + + +async def test_light_state_color_conversion(hass): + """Test color conversion in state updates.""" + platform = getattr(hass.components, "test.light") + platform.init(empty=True) + + platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) + + entity0 = platform.ENTITIES[0] + entity0.supported_color_modes = {light.COLOR_MODE_HS} + entity0.color_mode = light.COLOR_MODE_HS + entity0.hs_color = (240, 100) + entity0.rgb_color = "Invalid" # Should be ignored + entity0.xy_color = "Invalid" # Should be ignored + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = {light.COLOR_MODE_RGB} + entity1.color_mode = light.COLOR_MODE_RGB + entity1.hs_color = "Invalid" # Should be ignored + entity1.rgb_color = (128, 0, 0) + entity1.xy_color = "Invalid" # Should be ignored + + entity2 = platform.ENTITIES[2] + entity2.supported_color_modes = {light.COLOR_MODE_XY} + entity2.color_mode = light.COLOR_MODE_XY + entity2.hs_color = "Invalid" # Should be ignored + entity2.rgb_color = "Invalid" # Should be ignored + entity2.xy_color = (0.1, 0.8) + + entity3 = platform.ENTITIES[3] + entity3.hs_color = (240, 100) + entity3.supported_features = light.SUPPORT_COLOR + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + state = hass.states.get(entity0.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + assert state.attributes["hs_color"] == (240, 100) + assert state.attributes["rgb_color"] == (0, 0, 255) + assert state.attributes["xy_color"] == (0.136, 0.04) + + state = hass.states.get(entity1.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_RGB + assert state.attributes["hs_color"] == (0.0, 100.0) + assert state.attributes["rgb_color"] == (128, 0, 0) + assert state.attributes["xy_color"] == (0.701, 0.299) + + state = hass.states.get(entity2.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_XY + assert state.attributes["hs_color"] == (125.176, 100.0) + assert state.attributes["rgb_color"] == (0, 255, 22) + assert state.attributes["xy_color"] == (0.1, 0.8) + + state = hass.states.get(entity3.entity_id) + assert state.attributes["color_mode"] == light.COLOR_MODE_HS + assert state.attributes["hs_color"] == (240, 100) + assert state.attributes["rgb_color"] == (0, 0, 255) + assert state.attributes["xy_color"] == (0.136, 0.04) diff --git a/tests/components/light/test_reproduce_state.py b/tests/components/light/test_reproduce_state.py index e96f4ff4528276..815b8831d37402 100644 --- a/tests/components/light/test_reproduce_state.py +++ b/tests/components/light/test_reproduce_state.py @@ -1,4 +1,7 @@ """Test reproduce state for Light.""" +import pytest + +from homeassistant.components import light from homeassistant.components.light.reproduce_state import DEPRECATION_WARNING from homeassistant.core import State @@ -15,6 +18,8 @@ VALID_KELVIN = {"kelvin": 4000} VALID_PROFILE = {"profile": "relax"} VALID_RGB_COLOR = {"rgb_color": (255, 63, 111)} +VALID_RGBW_COLOR = {"rgbw_color": (255, 63, 111, 10)} +VALID_RGBWW_COLOR = {"rgbww_color": (255, 63, 111, 10, 20)} VALID_XY_COLOR = {"xy_color": (0.59, 0.274)} @@ -91,51 +96,51 @@ async def test_reproducing_states(hass, caplog): expected_calls = [] - expected_off = VALID_BRIGHTNESS + expected_off = dict(VALID_BRIGHTNESS) expected_off["entity_id"] = "light.entity_off" expected_calls.append(expected_off) - expected_bright = VALID_WHITE_VALUE + expected_bright = dict(VALID_WHITE_VALUE) expected_bright["entity_id"] = "light.entity_bright" expected_calls.append(expected_bright) - expected_white = VALID_FLASH + expected_white = dict(VALID_FLASH) expected_white["entity_id"] = "light.entity_white" expected_calls.append(expected_white) - expected_flash = VALID_EFFECT + expected_flash = dict(VALID_EFFECT) expected_flash["entity_id"] = "light.entity_flash" expected_calls.append(expected_flash) - expected_effect = VALID_TRANSITION + expected_effect = dict(VALID_TRANSITION) expected_effect["entity_id"] = "light.entity_effect" expected_calls.append(expected_effect) - expected_trans = VALID_COLOR_NAME + expected_trans = dict(VALID_COLOR_NAME) expected_trans["entity_id"] = "light.entity_trans" expected_calls.append(expected_trans) - expected_name = VALID_COLOR_TEMP + expected_name = dict(VALID_COLOR_TEMP) expected_name["entity_id"] = "light.entity_name" expected_calls.append(expected_name) - expected_temp = VALID_HS_COLOR + expected_temp = dict(VALID_HS_COLOR) expected_temp["entity_id"] = "light.entity_temp" expected_calls.append(expected_temp) - expected_hs = VALID_KELVIN + expected_hs = dict(VALID_KELVIN) expected_hs["entity_id"] = "light.entity_hs" expected_calls.append(expected_hs) - expected_kelvin = VALID_PROFILE + expected_kelvin = dict(VALID_PROFILE) expected_kelvin["entity_id"] = "light.entity_kelvin" expected_calls.append(expected_kelvin) - expected_profile = VALID_RGB_COLOR + expected_profile = dict(VALID_RGB_COLOR) expected_profile["entity_id"] = "light.entity_profile" expected_calls.append(expected_profile) - expected_rgb = VALID_XY_COLOR + expected_rgb = dict(VALID_XY_COLOR) expected_rgb["entity_id"] = "light.entity_rgb" expected_calls.append(expected_rgb) @@ -156,6 +161,59 @@ async def test_reproducing_states(hass, caplog): assert turn_off_calls[0].data == {"entity_id": "light.entity_xy"} +@pytest.mark.parametrize( + "color_mode", + ( + light.COLOR_MODE_COLOR_TEMP, + light.COLOR_MODE_BRIGHTNESS, + light.COLOR_MODE_HS, + light.COLOR_MODE_ONOFF, + light.COLOR_MODE_RGB, + light.COLOR_MODE_RGBW, + light.COLOR_MODE_RGBWW, + light.COLOR_MODE_UNKNOWN, + light.COLOR_MODE_XY, + ), +) +async def test_filter_color_modes(hass, caplog, color_mode): + """Test filtering of parameters according to color mode.""" + hass.states.async_set("light.entity", "off", {}) + all_colors = { + **VALID_WHITE_VALUE, + **VALID_COLOR_NAME, + **VALID_COLOR_TEMP, + **VALID_HS_COLOR, + **VALID_KELVIN, + **VALID_RGB_COLOR, + **VALID_RGBW_COLOR, + **VALID_RGBWW_COLOR, + **VALID_XY_COLOR, + } + + turn_on_calls = async_mock_service(hass, "light", "turn_on") + + await hass.helpers.state.async_reproduce_state( + [State("light.entity", "on", {**all_colors, "color_mode": color_mode})] + ) + + expected_map = { + light.COLOR_MODE_COLOR_TEMP: VALID_COLOR_TEMP, + light.COLOR_MODE_BRIGHTNESS: {}, + light.COLOR_MODE_HS: VALID_HS_COLOR, + light.COLOR_MODE_ONOFF: {}, + light.COLOR_MODE_RGB: VALID_RGB_COLOR, + light.COLOR_MODE_RGBW: VALID_RGBW_COLOR, + light.COLOR_MODE_RGBWW: VALID_RGBWW_COLOR, + light.COLOR_MODE_UNKNOWN: {**VALID_HS_COLOR, **VALID_WHITE_VALUE}, + light.COLOR_MODE_XY: VALID_XY_COLOR, + } + expected = expected_map[color_mode] + + assert len(turn_on_calls) == 1 + assert turn_on_calls[0].domain == "light" + assert dict(turn_on_calls[0].data) == {"entity_id": "light.entity", **expected} + + async def test_deprecation_warning(hass, caplog): """Test deprecation warning.""" hass.states.async_set("light.entity_off", "off", {}) diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 90a8d1f9e6ee5e..b6ce2fa4fafa11 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -430,6 +430,8 @@ async def _async_test( "effect_list": YEELIGHT_MONO_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": bright, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) @@ -441,6 +443,8 @@ async def _async_test( "effect_list": YEELIGHT_MONO_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": bright, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) @@ -463,8 +467,14 @@ async def _async_test( "hs_color": hs_color, "rgb_color": rgb_color, "xy_color": xy_color, + "color_mode": "hs", + "supported_color_modes": ["color_temp", "hs"], + }, + { + "supported_features": 0, + "color_mode": "onoff", + "supported_color_modes": ["onoff"], }, - {"supported_features": 0}, ) # WhiteTemp @@ -483,11 +493,15 @@ async def _async_test( ), "brightness": current_brightness, "color_temp": ct, + "color_mode": "color_temp", + "supported_color_modes": ["color_temp"], }, { "effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": nl_br, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) @@ -512,11 +526,15 @@ async def _async_test( ), "brightness": current_brightness, "color_temp": ct, + "color_mode": "color_temp", + "supported_color_modes": ["color_temp"], }, { "effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, "brightness": nl_br, + "color_mode": "brightness", + "supported_color_modes": ["brightness"], }, ) await _async_test( @@ -532,6 +550,8 @@ async def _async_test( "hs_color": bg_hs_color, "rgb_color": bg_rgb_color, "xy_color": bg_xy_color, + "color_mode": "hs", + "supported_color_modes": ["color_temp", "hs"], }, name=f"{UNIQUE_NAME} ambilight", entity_id=f"{ENTITY_LIGHT}_ambilight", diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 1dc608e18df590..9fac50859868e3 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -7,9 +7,12 @@ from homeassistant import setup from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, ATTR_HS_COLOR, ATTR_RGB_COLOR, + ATTR_SUPPORTED_COLOR_MODES, ATTR_XY_COLOR, + COLOR_MODE_HS, SCAN_INTERVAL, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, @@ -96,6 +99,7 @@ async def test_init(hass, mock_entry): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -104,8 +108,10 @@ async def test_init(hass, mock_entry): assert state.state == STATE_ON assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-33445566", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", + ATTR_COLOR_MODE: COLOR_MODE_HS, ATTR_BRIGHTNESS: 255, ATTR_HS_COLOR: (221.176, 100.0), ATTR_RGB_COLOR: (0, 80, 255), @@ -272,6 +278,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -290,6 +297,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_UNAVAILABLE assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -307,6 +315,7 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_OFF assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", } @@ -324,8 +333,10 @@ async def test_light_update(hass, mock_light): assert state.state == STATE_ON assert state.attributes == { ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS], ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR, ATTR_ICON: "mdi:string-lights", + ATTR_COLOR_MODE: COLOR_MODE_HS, ATTR_BRIGHTNESS: 220, ATTR_HS_COLOR: (261.429, 31.818), ATTR_RGB_COLOR: (202, 173, 255), diff --git a/tests/testing_config/custom_components/test/light.py b/tests/testing_config/custom_components/test/light.py index 863412fe747daa..84008d90c27f4e 100644 --- a/tests/testing_config/custom_components/test/light.py +++ b/tests/testing_config/custom_components/test/light.py @@ -37,4 +37,17 @@ class MockLight(MockToggleEntity, LightEntity): """Mock light class.""" brightness = None + supported_color_modes = None supported_features = 0 + + color_mode = None + + hs_color = None + xy_color = None + rgb_color = None + rgbw_color = None + rgbww_color = None + + color_temp = None + + white_value = None From 9647eeb2e08822da190e47208e57b4133facd5cf Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 14:21:05 +0100 Subject: [PATCH 1294/1818] Add custom JSONEncoder for automation traces (#47942) * Add custom JSONEncoder for automation traces * Add tests * Update default case to include type * Tweak * Refactor * Tweak * Lint * Update websocket_api.py --- homeassistant/components/automation/trace.py | 18 ++++++++ .../components/automation/websocket_api.py | 12 +++++- tests/components/automation/test_trace.py | 42 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/components/automation/test_trace.py diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 351ca1ed979d51..4aac3d327b87aa 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -2,11 +2,13 @@ from collections import OrderedDict from contextlib import contextmanager import datetime as dt +from datetime import timedelta from itertools import count import logging from typing import Any, Awaitable, Callable, Deque, Dict, Optional from homeassistant.core import Context, HomeAssistant, callback +from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder from homeassistant.helpers.trace import TraceElement, trace_id_set from homeassistant.helpers.typing import TemplateVarsType from homeassistant.util import dt as dt_util @@ -203,3 +205,19 @@ def get_debug_traces(hass, summary=False): traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) return traces + + +class TraceJSONEncoder(HAJSONEncoder): + """JSONEncoder that supports Home Assistant objects and falls back to repr(o).""" + + def default(self, o: Any) -> Any: + """Convert certain objects. + + Fall back to repr(o). + """ + if isinstance(o, timedelta): + return {"__type": str(type(o)), "total_seconds": o.total_seconds()} + try: + return super().default(o) + except TypeError: + return {"__type": str(type(o)), "repr": repr(o)} diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index bb47dd58ff9b27..eba56f94e7d182 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -1,4 +1,6 @@ """Websocket API for automation.""" +import json + import voluptuous as vol from homeassistant.components import websocket_api @@ -21,7 +23,12 @@ debug_stop, ) -from .trace import get_debug_trace, get_debug_traces, get_debug_traces_for_automation +from .trace import ( + TraceJSONEncoder, + get_debug_trace, + get_debug_traces, + get_debug_traces_for_automation, +) # mypy: allow-untyped-calls, allow-untyped-defs @@ -55,8 +62,9 @@ def websocket_automation_trace_get(hass, connection, msg): run_id = msg["run_id"] trace = get_debug_trace(hass, automation_id, run_id) + message = websocket_api.messages.result_message(msg["id"], trace) - connection.send_result(msg["id"], trace) + connection.send_message(json.dumps(message, cls=TraceJSONEncoder, allow_nan=False)) @callback diff --git a/tests/components/automation/test_trace.py b/tests/components/automation/test_trace.py new file mode 100644 index 00000000000000..818f1ee176840f --- /dev/null +++ b/tests/components/automation/test_trace.py @@ -0,0 +1,42 @@ +"""Test Automation trace helpers.""" +from datetime import timedelta + +from homeassistant import core +from homeassistant.components import automation +from homeassistant.util import dt as dt_util + + +def test_json_encoder(hass): + """Test the Trace JSON Encoder.""" + ha_json_enc = automation.trace.TraceJSONEncoder() + state = core.State("test.test", "hello") + + # Test serializing a datetime + now = dt_util.utcnow() + assert ha_json_enc.default(now) == now.isoformat() + + # Test serializing a timedelta + data = timedelta( + days=50, + seconds=27, + microseconds=10, + milliseconds=29000, + minutes=5, + hours=8, + weeks=2, + ) + assert ha_json_enc.default(data) == { + "__type": str(type(data)), + "total_seconds": data.total_seconds(), + } + + # Test serializing a set() + data = {"milk", "beer"} + assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + + # Test serializong object which implements as_dict + assert ha_json_enc.default(state) == state.as_dict() + + # Default method falls back to repr(o) + o = object() + assert ha_json_enc.default(o) == {"__type": str(type(o)), "repr": repr(o)} From 673ebe291192c0d6ad2de82ab30897b8b535694a Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 16 Mar 2021 10:02:26 -0400 Subject: [PATCH 1295/1818] Guard extra call in ZHA lights (#47832) * add flag to prevent sending an on command * fix condition * add constant for default transition * make groups work with new force on flag * reorder light entity creation * rearrange logic * update test * remove failed attempt at group light flag * fix flag --- homeassistant/components/zha/light.py | 19 +++++++++++++++++-- tests/components/zha/test_light.py | 8 +++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index e7d9be62374970..f81b931a49dbd7 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -65,6 +65,8 @@ CAPABILITIES_COLOR_XY = 0x08 CAPABILITIES_COLOR_TEMP = 0x10 +DEFAULT_TRANSITION = 1 + UPDATE_COLORLOOP_ACTION = 0x1 UPDATE_COLORLOOP_DIRECTION = 0x2 UPDATE_COLORLOOP_TIME = 0x4 @@ -114,6 +116,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BaseLight(LogMixin, light.LightEntity): """Operations common to all light entities.""" + _FORCE_ON = False + def __init__(self, *args, **kwargs): """Initialize the light.""" super().__init__(*args, **kwargs) @@ -201,7 +205,7 @@ def supported_features(self): async def async_turn_on(self, **kwargs): """Turn the entity on.""" transition = kwargs.get(light.ATTR_TRANSITION) - duration = transition * 10 if transition else 1 + duration = transition * 10 if transition else DEFAULT_TRANSITION brightness = kwargs.get(light.ATTR_BRIGHTNESS) effect = kwargs.get(light.ATTR_EFFECT) flash = kwargs.get(light.ATTR_FLASH) @@ -228,7 +232,7 @@ async def async_turn_on(self, **kwargs): if level: self._brightness = level - if brightness is None or brightness: + if brightness is None or (self._FORCE_ON and brightness): # since some lights don't always turn on with move_to_level_with_on_off, # we should call the on command on the on_off cluster if brightness is not 0. result = await self._on_off_channel.on() @@ -512,6 +516,17 @@ class HueLight(Light): _REFRESH_INTERVAL = (3, 5) +@STRICT_MATCH( + channel_names=CHANNEL_ON_OFF, + aux_channels={CHANNEL_COLOR, CHANNEL_LEVEL}, + manufacturers="Jasco", +) +class ForceOnLight(Light): + """Representation of a light which does not respect move_to_level_with_on_off.""" + + _FORCE_ON = True + + @GROUP_MATCH() class LightGroup(BaseLight, ZhaGroupEntity): """Representation of a light group.""" diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 0a9a492a148957..021f4f09dd9c8a 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -392,13 +392,11 @@ async def async_test_level_on_off_from_hass( await hass.services.async_call( DOMAIN, "turn_on", {"entity_id": entity_id, "brightness": 10}, blocking=True ) - assert on_off_cluster.request.call_count == 1 - assert on_off_cluster.request.await_count == 1 + # the onoff cluster is now not used when brightness is present by default + assert on_off_cluster.request.call_count == 0 + assert on_off_cluster.request.await_count == 0 assert level_cluster.request.call_count == 1 assert level_cluster.request.await_count == 1 - assert on_off_cluster.request.call_args == call( - False, ON, (), expect_reply=True, manufacturer=None, tries=1, tsn=None - ) assert level_cluster.request.call_args == call( False, 4, From 4dc0cdbb5fee2660fb89ecc143c25f5c3aa94802 Mon Sep 17 00:00:00 2001 From: Antoine Meillet Date: Tue, 16 Mar 2021 16:11:51 +0100 Subject: [PATCH 1296/1818] Ignore STATE_UNKNOWN in prometheus (#47840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Giving a value of 0 by default can lead to erroneous data being exported. For example, if a MQTT temperature sensor is in `STATE_UNKNOWN` (which can happen after a HASS restart), a temperature of 0°C will be exported. Some user might prefer no value rather than a wrong one. --- homeassistant/components/prometheus/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index bbab9af83e82d6..b253daf559ec36 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -29,6 +29,7 @@ PERCENTAGE, STATE_ON, STATE_UNAVAILABLE, + STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) @@ -154,9 +155,11 @@ def handle_event(self, event): if not self._filter(state.entity_id): return + ignored_states = (STATE_UNAVAILABLE, STATE_UNKNOWN) + handler = f"_handle_{domain}" - if hasattr(self, handler) and state.state != STATE_UNAVAILABLE: + if hasattr(self, handler) and state.state not in ignored_states: getattr(self, handler)(state) labels = self._labels(state) @@ -168,9 +171,9 @@ def handle_event(self, event): entity_available = self._metric( "entity_available", self.prometheus_cli.Gauge, - "Entity is available (not in the unavailable state)", + "Entity is available (not in the unavailable or unknown state)", ) - entity_available.labels(**labels).set(float(state.state != STATE_UNAVAILABLE)) + entity_available.labels(**labels).set(float(state.state not in ignored_states)) last_updated_time_seconds = self._metric( "last_updated_time_seconds", From f695155af58cbe615fc234e0029fd276dbcb9922 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 16 Mar 2021 16:30:04 +0100 Subject: [PATCH 1297/1818] Add device classes to Verisure sensors (#47990) --- .../components/verisure/alarm_control_panel.py | 6 +++--- .../components/verisure/binary_sensor.py | 14 +++++++------- homeassistant/components/verisure/camera.py | 4 ++-- homeassistant/components/verisure/lock.py | 2 +- homeassistant/components/verisure/sensor.py | 16 +++++++++++++++- homeassistant/components/verisure/switch.py | 2 +- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 54e36ac65def70..761feb0d2cb77b 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -48,12 +48,12 @@ def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None: @property def name(self) -> str: - """Return the name of the device.""" + """Return the name of the entity.""" return "Verisure Alarm" @property def unique_id(self) -> str: - """Return the unique ID for this alarm control panel.""" + """Return the unique ID for this entity.""" return self.coordinator.entry.data[CONF_GIID] @property @@ -68,7 +68,7 @@ def device_info(self) -> dict[str, Any]: @property def state(self) -> str | None: - """Return the state of the device.""" + """Return the state of the entity.""" status = self.coordinator.data["alarm"]["statusType"] if status == "DISARMED": self._state = STATE_ALARM_DISARMED diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 864785c7cd0dd7..3363178efe28d8 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -22,7 +22,7 @@ async def async_setup_entry( entry: ConfigEntry, async_add_entities: Callable[[Iterable[Entity]], None], ) -> None: - """Set up Verisure sensors based on a config entry.""" + """Set up Verisure binary sensors based on a config entry.""" coordinator: VerisureDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] sensors: list[Entity] = [VerisureEthernetStatus(coordinator)] @@ -49,12 +49,12 @@ def __init__( @property def name(self) -> str: - """Return the name of the binary sensor.""" + """Return the name of this entity.""" return self.coordinator.data["door_window"][self.serial_number]["area"] @property def unique_id(self) -> str: - """Return the unique ID for this alarm control panel.""" + """Return the unique ID for this entity.""" return f"{self.serial_number}_door_window" @property @@ -72,7 +72,7 @@ def device_info(self) -> dict[str, Any]: @property def device_class(self) -> str: - """Return the class of this device, from component DEVICE_CLASSES.""" + """Return the class of this entity.""" return DEVICE_CLASS_OPENING @property @@ -98,12 +98,12 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity): @property def name(self) -> str: - """Return the name of the binary sensor.""" + """Return the name of this entity.""" return "Verisure Ethernet status" @property def unique_id(self) -> str: - """Return the unique ID for this binary sensor.""" + """Return the unique ID for this entity.""" return f"{self.coordinator.entry.data[CONF_GIID]}_ethernet" @property @@ -128,5 +128,5 @@ def available(self) -> bool: @property def device_class(self) -> str: - """Return the class of this device, from component DEVICE_CLASSES.""" + """Return the class of this entity.""" return DEVICE_CLASS_CONNECTIVITY diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index 006f9c28ef5475..c97d7f8c76c4b9 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -64,12 +64,12 @@ def __init__( @property def name(self) -> str: - """Return the name of this camera.""" + """Return the name of this entity.""" return self.coordinator.data["cameras"][self.serial_number]["area"] @property def unique_id(self) -> str: - """Return the unique ID for this camera.""" + """Return the unique ID for this entity.""" return self.serial_number @property diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index d4708c68e882b3..eeec7e53a0a73a 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -71,7 +71,7 @@ def __init__( @property def name(self) -> str: - """Return the name of the lock.""" + """Return the name of this entity.""" return self.coordinator.data["locks"][self.serial_number]["area"] @property diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 614bd0a386db00..f7f2212149bec1 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -3,6 +3,10 @@ from typing import Any, Callable, Iterable +from homeassistant.components.sensor import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.core import HomeAssistant @@ -64,6 +68,11 @@ def unique_id(self) -> str: """Return the unique ID for this entity.""" return f"{self.serial_number}_temperature" + @property + def device_class(self) -> str: + """Return the class of this entity.""" + return DEVICE_CLASS_TEMPERATURE + @property def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" @@ -123,6 +132,11 @@ def unique_id(self) -> str: """Return the unique ID for this entity.""" return f"{self.serial_number}_humidity" + @property + def device_class(self) -> str: + """Return the class of this entity.""" + return DEVICE_CLASS_HUMIDITY + @property def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" @@ -197,7 +211,7 @@ def device_info(self) -> dict[str, Any]: @property def state(self) -> str | None: - """Return the state of the device.""" + """Return the state of the entity.""" return self.coordinator.data["mice"][self.serial_number]["detections"] @property diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 534db9c7a50201..f55db8ce4282fb 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -43,7 +43,7 @@ def __init__( @property def name(self) -> str: - """Return the name or location of the smartplug.""" + """Return the name of this entity.""" return self.coordinator.data["smart_plugs"][self.serial_number]["area"] @property From 7ed9e5b2c203b8968c32983c342d34e07cdea059 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 16 Mar 2021 16:43:11 +0100 Subject: [PATCH 1298/1818] Update xknx to 0.17.3 (#47996) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index f8ed51f364a38b..7f14c17839c4ad 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.2"], + "requirements": ["xknx==0.17.3"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index d1e27d28076eb8..c07215ade321fa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2329,7 +2329,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.2 +xknx==0.17.3 # homeassistant.components.bluesound # homeassistant.components.rest From 389891d13d18cd69e34b819b4bb6fa06f52d3067 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 16 Mar 2021 17:12:51 +0100 Subject: [PATCH 1299/1818] Improve JSONEncoder test coverage (#47935) --- tests/helpers/test_json.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 8ae516b2bbe6b3..55d566f5eddae4 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -11,11 +11,17 @@ def test_json_encoder(hass): ha_json_enc = JSONEncoder() state = core.State("test.test", "hello") + # Test serializing a datetime + now = dt_util.utcnow() + assert ha_json_enc.default(now) == now.isoformat() + + # Test serializing a set() + data = {"milk", "beer"} + assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + + # Test serializing an object which implements as_dict assert ha_json_enc.default(state) == state.as_dict() # Default method raises TypeError if non HA object with pytest.raises(TypeError): ha_json_enc.default(1) - - now = dt_util.utcnow() - assert ha_json_enc.default(now) == now.isoformat() From 0097169dd36dde9471062c0f0cfb523b7276ec52 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Tue, 16 Mar 2021 17:14:07 +0100 Subject: [PATCH 1300/1818] Add aliases to actions in automation blueprints (#47940) --- .../automation/blueprints/motion_light.yaml | 12 ++++++++---- .../automation/blueprints/notify_leaving_zone.yaml | 9 +++++---- tests/components/automation/test_blueprint.py | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/automation/blueprints/motion_light.yaml b/homeassistant/components/automation/blueprints/motion_light.yaml index c11d22d974eb9e..54a4a4f0643a05 100644 --- a/homeassistant/components/automation/blueprints/motion_light.yaml +++ b/homeassistant/components/automation/blueprints/motion_light.yaml @@ -38,13 +38,17 @@ trigger: to: "on" action: - - service: light.turn_on + - alias: "Turn on the light" + service: light.turn_on target: !input light_target - - wait_for_trigger: + - alias: "Wait until there is no motion from device" + wait_for_trigger: platform: state entity_id: !input motion_entity from: "on" to: "off" - - delay: !input no_motion_wait - - service: light.turn_off + - alias: "Wait the number of seconds that has been set" + delay: !input no_motion_wait + - alias: "Turn off the light" + service: light.turn_off target: !input light_target diff --git a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml index d3a70d773eef7a..71abf8f865cd63 100644 --- a/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml +++ b/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml @@ -37,7 +37,8 @@ condition: value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" action: - domain: mobile_app - type: notify - device_id: !input notify_device - message: "{{ person_name }} has left {{ zone_state }}" + - alias: "Notify that a person has left the zone" + domain: mobile_app + type: notify + device_id: !input notify_device + message: "{{ person_name }} has left {{ zone_state }}" diff --git a/tests/components/automation/test_blueprint.py b/tests/components/automation/test_blueprint.py index 1a6ccec7a8e119..747e162fe46d21 100644 --- a/tests/components/automation/test_blueprint.py +++ b/tests/components/automation/test_blueprint.py @@ -84,6 +84,7 @@ def set_person_state(state, extra={}): _hass, config, variables, _context = mock_call_action.mock_calls[0][1] message_tpl = config.pop("message") assert config == { + "alias": "Notify that a person has left the zone", "domain": "mobile_app", "type": "notify", "device_id": "abcdefgh", From bbd98e196b70a08800a23b954ed81b62a7eabf75 Mon Sep 17 00:00:00 2001 From: Ron Heft Date: Tue, 16 Mar 2021 12:15:22 -0400 Subject: [PATCH 1301/1818] Fix withings InvalidParamsException (#47975) * Bump withings_api to 2.3.1 (fixes #47329) * Fix NotifyAppli calls to be compatible with withings_api 2.3.1 * Fix errors with withings_api 2.2+ using pydantic * Bump withings_api to 2.3.2 --- homeassistant/components/withings/__init__.py | 8 +++----- homeassistant/components/withings/common.py | 4 ++-- homeassistant/components/withings/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index c6f420d172acf5..5efa22e6a8617d 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -4,12 +4,12 @@ For more details about this platform, please refer to the documentation at """ import asyncio -from typing import Optional, cast +from typing import Optional from aiohttp.web import Request, Response import voluptuous as vol from withings_api import WithingsAuth -from withings_api.common import NotifyAppli, enum_or_raise +from withings_api.common import NotifyAppli from homeassistant.components import webhook from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -195,9 +195,7 @@ async def async_webhook_handler( return json_message_response("Parameter appli not provided", message_code=20) try: - appli = cast( - NotifyAppli, enum_or_raise(int(params.getone("appli")), NotifyAppli) - ) + appli = NotifyAppli(int(params.getone("appli"))) except ValueError: return json_message_response("Invalid appli provided", message_code=21) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index e2e0b06e34203d..54a0a3c94eecff 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -681,7 +681,7 @@ async def _async_subscribe_webhook(self) -> None: ) # Determine what subscriptions need to be created. - ignored_applis = frozenset({NotifyAppli.USER}) + ignored_applis = frozenset({NotifyAppli.USER, NotifyAppli.UNKNOWN}) to_add_applis = frozenset( [ appli @@ -846,7 +846,7 @@ def get_sleep_summary() -> SleepGetSummaryResponse: data = serie.data for field in GetSleepSummaryField: - raw_values[field].append(data._asdict()[field.value]) + raw_values[field].append(dict(data)[field.value]) values: Dict[GetSleepSummaryField, float] = {} diff --git a/homeassistant/components/withings/manifest.json b/homeassistant/components/withings/manifest.json index ec981ff691c4d4..6b2918722ba012 100644 --- a/homeassistant/components/withings/manifest.json +++ b/homeassistant/components/withings/manifest.json @@ -3,7 +3,7 @@ "name": "Withings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/withings", - "requirements": ["withings-api==2.1.6"], + "requirements": ["withings-api==2.3.2"], "dependencies": ["http", "webhook"], "codeowners": ["@vangorra"] } diff --git a/requirements_all.txt b/requirements_all.txt index c07215ade321fa..ca653fb6e1b28d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2311,7 +2311,7 @@ wiffi==1.0.1 wirelesstagpy==0.4.1 # homeassistant.components.withings -withings-api==2.1.6 +withings-api==2.3.2 # homeassistant.components.wled wled==0.4.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ed005d34d03b34..7210c651177d00 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1187,7 +1187,7 @@ watchdog==1.0.2 wiffi==1.0.1 # homeassistant.components.withings -withings-api==2.1.6 +withings-api==2.3.2 # homeassistant.components.wled wled==0.4.4 From 6a66ccef1b5036409e612facbf508c0a8160321c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 16 Mar 2021 12:21:08 -0400 Subject: [PATCH 1302/1818] Bump up ZHA dependencies (#47997) --- 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 5c66e2cf605437..5eee54a5c71605 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.23.0", + "bellows==0.23.1", "pyserial==3.5", "pyserial-asyncio==0.5", "zha-quirks==0.0.54", diff --git a/requirements_all.txt b/requirements_all.txt index ca653fb6e1b28d..a626f812fb8a0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -340,7 +340,7 @@ beautifulsoup4==4.9.3 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.23.0 +bellows==0.23.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7210c651177d00..7889a5af96b0ee 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -193,7 +193,7 @@ azure-eventhub==5.1.0 base36==0.1.1 # homeassistant.components.zha -bellows==0.23.0 +bellows==0.23.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.15 From 73c6728e98e2e853afa9bd80b3a689c2d56e49e3 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 16 Mar 2021 09:38:09 -0700 Subject: [PATCH 1303/1818] Add binary_sensor entities for SmartTub reminders (#47583) --- .../components/smarttub/binary_sensor.py | 63 ++++++++++++++++++- homeassistant/components/smarttub/climate.py | 2 +- homeassistant/components/smarttub/const.py | 5 +- .../components/smarttub/controller.py | 5 +- tests/components/smarttub/conftest.py | 8 +++ .../components/smarttub/test_binary_sensor.py | 22 ++++++- 6 files changed, 96 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py index 52dbfd71a37b1b..04a6bb9b72a778 100644 --- a/homeassistant/components/smarttub/binary_sensor.py +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -1,23 +1,38 @@ """Platform for binary sensor integration.""" +from datetime import datetime, timedelta import logging +from smarttub import SpaReminder + from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_PROBLEM, BinarySensorEntity, ) -from .const import DOMAIN, SMARTTUB_CONTROLLER -from .entity import SmartTubSensorBase +from .const import ATTR_REMINDERS, DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubEntity, SmartTubSensorBase _LOGGER = logging.getLogger(__name__) +# whether the reminder has been snoozed (bool) +ATTR_REMINDER_SNOOZED = "snoozed" +# the date at which the reminder will be activated +ATTR_REMINDER_DATE = "date" + async def async_setup_entry(hass, entry, async_add_entities): """Set up binary sensor entities for the binary sensors in the tub.""" controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] - entities = [SmartTubOnline(controller.coordinator, spa) for spa in controller.spas] + entities = [] + for spa in controller.spas: + entities.append(SmartTubOnline(controller.coordinator, spa)) + entities.extend( + SmartTubReminder(controller.coordinator, spa, reminder) + for reminder in controller.coordinator.data[spa.id][ATTR_REMINDERS].values() + ) async_add_entities(entities) @@ -38,3 +53,45 @@ def is_on(self) -> bool: def device_class(self) -> str: """Return the device class for this entity.""" return DEVICE_CLASS_CONNECTIVITY + + +class SmartTubReminder(SmartTubEntity, BinarySensorEntity): + """Reminders for maintenance actions.""" + + def __init__(self, coordinator, spa, reminder): + """Initialize the entity.""" + super().__init__( + coordinator, + spa, + f"{reminder.name.title()} Reminder", + ) + self.reminder_id = reminder.id + + @property + def unique_id(self): + """Return a unique id for this sensor.""" + return f"{self.spa.id}-reminder-{self.reminder_id}" + + @property + def reminder(self) -> SpaReminder: + """Return the underlying SpaReminder object for this entity.""" + return self.coordinator.data[self.spa.id]["reminders"][self.reminder_id] + + @property + def is_on(self) -> bool: + """Return whether the specified maintenance action needs to be taken.""" + return self.reminder.remaining_days == 0 + + @property + def device_state_attributes(self): + """Return the state attributes.""" + when = datetime.now() + timedelta(days=self.reminder.remaining_days) + return { + ATTR_REMINDER_SNOOZED: self.reminder.snoozed, + ATTR_REMINDER_DATE: when.date().isoformat(), + } + + @property + def device_class(self) -> str: + """Return the device class for this entity.""" + return DEVICE_CLASS_PROBLEM diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 3e18bc12672458..be564c84a94ea8 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -54,7 +54,7 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): def __init__(self, coordinator, spa): """Initialize the entity.""" - super().__init__(coordinator, spa, "thermostat") + super().__init__(coordinator, spa, "Thermostat") @property def temperature_unit(self): diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 082d46cb26a447..23bd8bd8ec0da3 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -21,6 +21,7 @@ # default to 50% brightness DEFAULT_LIGHT_BRIGHTNESS = 128 -ATTR_STATUS = "status" -ATTR_PUMPS = "pumps" ATTR_LIGHTS = "lights" +ATTR_PUMPS = "pumps" +ATTR_REMINDERS = "reminders" +ATTR_STATUS = "status" diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index e5246446665db6..8139c72ab6e954 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -18,6 +18,7 @@ from .const import ( ATTR_LIGHTS, ATTR_PUMPS, + ATTR_REMINDERS, ATTR_STATUS, DOMAIN, POLLING_TIMEOUT, @@ -93,15 +94,17 @@ async def async_update_data(self): return data async def _get_spa_data(self, spa): - status, pumps, lights = await asyncio.gather( + status, pumps, lights, reminders = await asyncio.gather( spa.get_status(), spa.get_pumps(), spa.get_lights(), + spa.get_reminders(), ) return { ATTR_STATUS: status, ATTR_PUMPS: {pump.id: pump for pump in pumps}, ATTR_LIGHTS: {light.zone: light for light in lights}, + ATTR_REMINDERS: {reminder.id: reminder for reminder in reminders}, } async def async_register_devices(self, entry): diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index b7c90b5ad3e2a7..7f23c2355bc7b2 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -105,6 +105,14 @@ def mock_spa(): mock_spa.get_lights.return_value = [mock_light_off, mock_light_on] + mock_filter_reminder = create_autospec(smarttub.SpaReminder, instance=True) + mock_filter_reminder.id = "FILTER01" + mock_filter_reminder.name = "MyFilter" + mock_filter_reminder.remaining_days = 2 + mock_filter_reminder.snoozed = False + + mock_spa.get_reminders.return_value = [mock_filter_reminder] + return mock_spa diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py index b2624369e96884..8229372e90447d 100644 --- a/tests/components/smarttub/test_binary_sensor.py +++ b/tests/components/smarttub/test_binary_sensor.py @@ -1,13 +1,31 @@ """Test the SmartTub binary sensor platform.""" +from datetime import date, timedelta -from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY, STATE_ON +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + STATE_OFF, + STATE_ON, +) async def test_binary_sensors(spa, setup_entry, hass): - """Test the binary sensors.""" + """Test simple binary sensors.""" entity_id = f"binary_sensor.{spa.brand}_{spa.model}_online" state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_ON assert state.attributes.get("device_class") == DEVICE_CLASS_CONNECTIVITY + + +async def test_reminders(spa, setup_entry, hass): + """Test the reminder sensor.""" + + entity_id = f"binary_sensor.{spa.brand}_{spa.model}_myfilter_reminder" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + assert date.fromisoformat(state.attributes["date"]) <= date.today() + timedelta( + days=2 + ) + assert state.attributes["snoozed"] is False From 55db855f91270ce8e8ebfa98cc287e7902fa90a4 Mon Sep 17 00:00:00 2001 From: billsq Date: Tue, 16 Mar 2021 12:54:13 -0700 Subject: [PATCH 1304/1818] Add support for Xiaomi Air Purifier Pro H (#47601) --- homeassistant/components/xiaomi_miio/const.py | 7 ++++++- homeassistant/components/xiaomi_miio/fan.py | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index ddde0c7722909f..e4084542e13251 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -27,6 +27,7 @@ MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" +MODEL_AIRPURIFIER_PROH = "zhimi.airpurifier.va1" MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" @@ -35,7 +36,11 @@ MODEL_AIRFRESH_VA2 = "zhimi.airfresh.va2" -MODELS_PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] +MODELS_PURIFIER_MIOT = [ + MODEL_AIRPURIFIER_3, + MODEL_AIRPURIFIER_3H, + MODEL_AIRPURIFIER_PROH, +] MODELS_HUMIDIFIER_MIOT = [MODEL_AIRHUMIDIFIER_CA4] MODELS_FAN_MIIO = [ MODEL_AIRPURIFIER_V1, diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index cc8ee74feb0bd1..e20b1429bc6f0e 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -62,8 +62,6 @@ MODEL_AIRHUMIDIFIER_CA4, MODEL_AIRHUMIDIFIER_CB1, MODEL_AIRPURIFIER_2S, - MODEL_AIRPURIFIER_3, - MODEL_AIRPURIFIER_3H, MODEL_AIRPURIFIER_PRO, MODEL_AIRPURIFIER_PRO_V7, MODEL_AIRPURIFIER_V3, @@ -786,7 +784,7 @@ def __init__(self, name, device, entry, unique_id): self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S self._speed_list = OPERATION_MODES_AIRPURIFIER_2S - elif self._model == MODEL_AIRPURIFIER_3 or self._model == MODEL_AIRPURIFIER_3H: + elif self._model in MODELS_PURIFIER_MIOT: self._device_features = FEATURE_FLAGS_AIRPURIFIER_3 self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_3 self._speed_list = OPERATION_MODES_AIRPURIFIER_3 From 14d3e29e649cc9a923154e31f6e45259bd1d1e96 Mon Sep 17 00:00:00 2001 From: Kevin Eifinger Date: Tue, 16 Mar 2021 20:56:49 +0100 Subject: [PATCH 1305/1818] Add missing "pin" field in step "pair" for philips_js (#47802) --- homeassistant/components/philips_js/strings.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/philips_js/strings.json b/homeassistant/components/philips_js/strings.json index df65d453f2b0cf..5c8f08eff6a261 100644 --- a/homeassistant/components/philips_js/strings.json +++ b/homeassistant/components/philips_js/strings.json @@ -6,6 +6,13 @@ "host": "[%key:common::config_flow::data::host%]", "api_version": "API Version" } + }, + "pair": { + "title": "Pair", + "description": "Enter the PIN displayed on your TV", + "data":{ + "pin": "[%key:common::config_flow::data::pin%]" + } } }, "error": { From f3c74948c3a4d2b1048fd980eba9d240db472504 Mon Sep 17 00:00:00 2001 From: chpego <38792705+chpego@users.noreply.github.com> Date: Tue, 16 Mar 2021 21:02:05 +0100 Subject: [PATCH 1306/1818] Upgrade youtube_dl to version 2021.03.14 (#48000) --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 48589b37bd214b..35a5b0981843b0 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -2,7 +2,7 @@ "domain": "media_extractor", "name": "Media Extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", - "requirements": ["youtube_dl==2021.02.22"], + "requirements": ["youtube_dl==2021.03.14"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index a626f812fb8a0d..5a92aebdd51821 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2351,7 +2351,7 @@ yeelight==0.5.4 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2021.02.22 +youtube_dl==2021.03.14 # homeassistant.components.onvif zeep[async]==4.0.0 From f86e7535e0dbb37159f13f08c1cc0e39ce6976ad Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Tue, 16 Mar 2021 13:16:07 -0700 Subject: [PATCH 1307/1818] Add location details to deprecation warning (#47155) --- homeassistant/helpers/config_validation.py | 21 +++++-- tests/helpers/test_config_validation.py | 72 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 422f940e98e09e..b06e9974125753 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -746,12 +746,21 @@ def deprecated( def validator(config: Dict) -> Dict: """Check if key is in config and log warning.""" if key in config: - KeywordStyleAdapter(logging.getLogger(module_name)).warning( - warning, - key=key, - replacement_key=replacement_key, - ) - + try: + KeywordStyleAdapter(logging.getLogger(module_name)).warning( + warning.replace( + "'{key}' option", + f"'{key}' option near {config.__config_file__}:{config.__line__}", # type: ignore + ), + key=key, + replacement_key=replacement_key, + ) + except AttributeError: + KeywordStyleAdapter(logging.getLogger(module_name)).warning( + warning, + key=key, + replacement_key=replacement_key, + ) value = config[key] if replacement_key: config.pop(key) diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index d0ae86f8f7edb8..f58fd1b22f92ae 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,4 +1,5 @@ """Test config validators.""" +from collections import OrderedDict from datetime import date, datetime, timedelta import enum import os @@ -799,6 +800,77 @@ def test_deprecated_cant_find_module(): ) +def test_deprecated_logger_with_config_attributes(caplog): + """Test if the logger outputs the correct message if the line and file attribute is available in config.""" + file: str = "configuration.yaml" + line: int = 54 + replacement = f"'mars' option near {file}:{line} is deprecated" + config = OrderedDict([("mars", "blah")]) + setattr(config, "__config_file__", file) + setattr(config, "__line__", line) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + +def test_deprecated_logger_with_one_config_attribute(caplog): + """Test if the logger outputs the correct message if only one of line and file attribute is available in config.""" + file: str = "configuration.yaml" + line: int = 54 + replacement = f"'mars' option near {file}:{line} is deprecated" + config = OrderedDict([("mars", "blah")]) + setattr(config, "__config_file__", file) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement not in caplog.text + assert ( + "The 'mars' option is deprecated, please replace it with 'jupiter'" + ) in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + config = OrderedDict([("mars", "blah")]) + setattr(config, "__line__", line) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement not in caplog.text + assert ( + "The 'mars' option is deprecated, please replace it with 'jupiter'" + ) in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + +def test_deprecated_logger_without_config_attributes(caplog): + """Test if the logger outputs the correct message if the line and file attribute is not available in config.""" + file: str = "configuration.yaml" + line: int = 54 + replacement = f"'mars' option near {file}:{line} is deprecated" + config = OrderedDict([("mars", "blah")]) + + cv.deprecated("mars", replacement_key="jupiter", default=False)(config) + + assert len(caplog.records) == 1 + assert replacement not in caplog.text + assert ( + "The 'mars' option is deprecated, please replace it with 'jupiter'" + ) in caplog.text + + caplog.clear() + assert len(caplog.records) == 0 + + def test_key_dependency(): """Test key_dependency validator.""" schema = vol.Schema(cv.key_dependency("beer", "soda")) From d39aa9f80b82bc7137e71dab8cc6957cd3c6a714 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 16 Mar 2021 21:24:01 +0100 Subject: [PATCH 1308/1818] Bump philips_js with backported fixes (#47959) --- homeassistant/components/philips_js/manifest.json | 2 +- homeassistant/components/philips_js/media_player.py | 6 ------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index 9ed1cedbf05ef2..ad591ad330bdc4 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -3,7 +3,7 @@ "name": "Philips TV", "documentation": "https://www.home-assistant.io/integrations/philips_js", "requirements": [ - "ha-philipsjs==2.3.1" + "ha-philipsjs==2.3.2" ], "codeowners": [ "@elupus" diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 38521989567a95..f3b2fe651e231a 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -370,9 +370,6 @@ async def async_browse_media_channels(self, expanded): media_content_type=MEDIA_TYPE_CHANNEL, can_play=True, can_expand=False, - thumbnail=self.get_browse_image_url( - MEDIA_TYPE_APP, channel_id, media_image_id=None - ), ) for channel_id, channel in self._tv.channels.items() ] @@ -410,9 +407,6 @@ def get_name(channel): media_content_type=MEDIA_TYPE_CHANNEL, can_play=True, can_expand=False, - thumbnail=self.get_browse_image_url( - MEDIA_TYPE_APP, channel, media_image_id=None - ), ) for channel in favorites ] diff --git a/requirements_all.txt b/requirements_all.txt index 5a92aebdd51821..1f0cc6ab7ca7f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -718,7 +718,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.1 +ha-philipsjs==2.3.2 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7889a5af96b0ee..7dd403f6bd1a7e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -379,7 +379,7 @@ guppy3==3.1.0 ha-ffmpeg==3.0.2 # homeassistant.components.philips_js -ha-philipsjs==2.3.1 +ha-philipsjs==2.3.2 # homeassistant.components.habitica habitipy==0.2.0 From a4075d9e1144041a3d45801d1dd9902666a69915 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 16 Mar 2021 21:33:56 +0100 Subject: [PATCH 1309/1818] KNX sensor: float no longer valid for `type` (#48005) --- homeassistant/components/knx/__init__.py | 3 ++- homeassistant/components/knx/schema.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 99198fbaa99352..e05c18e5d5ce3e 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -49,6 +49,7 @@ WeatherSchema, ga_validator, ia_validator, + sensor_type_validator, ) _LOGGER = logging.getLogger(__name__) @@ -156,7 +157,7 @@ [ga_validator], ), vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, - vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), + vol.Required(SERVICE_KNX_ATTR_TYPE): sensor_type_validator, } ), vol.Schema( diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index c57539b501fbf5..376e86cf90cf51 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -47,6 +47,8 @@ cv.matches_regex(r"^(init|expire|every)( \d*)?$"), ) +sensor_type_validator = vol.Any(int, str) + ############## # CONNECTION @@ -255,7 +257,7 @@ class ExposeSchema: SCHEMA = vol.Schema( { - vol.Required(CONF_KNX_EXPOSE_TYPE): vol.Any(int, float, str), + vol.Required(CONF_KNX_EXPOSE_TYPE): sensor_type_validator, vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, @@ -417,7 +419,7 @@ class SensorSchema: vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_ALWAYS_CALLBACK, default=False): cv.boolean, vol.Required(CONF_STATE_ADDRESS): ga_validator, - vol.Required(CONF_TYPE): vol.Any(int, float, str), + vol.Required(CONF_TYPE): sensor_type_validator, } ) From f1c274b3dd1bdb328acf9bf36168bdb34c139345 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Mar 2021 14:37:26 -0700 Subject: [PATCH 1310/1818] Add run_id to automation logbook event (#47980) --- .../components/automation/__init__.py | 10 +--- .../components/automation/logbook.py | 9 ++-- homeassistant/components/automation/trace.py | 4 +- .../components/automation/websocket_api.py | 30 +++++++++++ tests/components/automation/test_logbook.py | 54 +++++++++++++++++++ .../automation/test_websocket_api.py | 23 ++++++++ 6 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 tests/components/automation/test_logbook.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4555a336cc36ae..b769b0329cce7c 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -281,6 +281,8 @@ def state_attributes(self): } if self.action_script.supports_max: attrs[ATTR_MAX] = self.action_script.max_runs + if self._id is not None: + attrs[CONF_ID] = self._id return attrs @property @@ -534,14 +536,6 @@ def log_cb(level, msg, **kwargs): variables, ) - @property - def extra_state_attributes(self): - """Return automation attributes.""" - if self._id is None: - return None - - return {CONF_ID: self._id} - async def _async_process_config( hass: HomeAssistant, diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py index 3c9671af18f24e..b5dbada1b7a098 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -1,26 +1,29 @@ """Describe logbook events.""" +from homeassistant.components.logbook import LazyEventPartialState from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from . import ATTR_SOURCE, DOMAIN, EVENT_AUTOMATION_TRIGGERED @callback -def async_describe_events(hass, async_describe_event): # type: ignore +def async_describe_events(hass: HomeAssistant, async_describe_event): # type: ignore """Describe logbook events.""" @callback - def async_describe_logbook_event(event): # type: ignore + def async_describe_logbook_event(event: LazyEventPartialState): # type: ignore """Describe a logbook event.""" data = event.data message = "has been triggered" if ATTR_SOURCE in data: message = f"{message} by {data[ATTR_SOURCE]}" + return { "name": data.get(ATTR_NAME), "message": message, "source": data.get(ATTR_SOURCE), "entity_id": data.get(ATTR_ENTITY_ID), + "context_id": event.context_id, } async_describe_event( diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 4aac3d327b87aa..68cca5a4a411ec 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -38,7 +38,7 @@ def __init__( self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None self._config: Dict[str, Any] = config - self._context: Context = context + self.context: Context = context self._error: Optional[Exception] = None self._state: str = "running" self.run_id: str = str(next(self._run_ids)) @@ -88,7 +88,7 @@ def as_dict(self) -> Dict[str, Any]: "action_trace": action_traces, "condition_trace": condition_traces, "config": self._config, - "context": self._context, + "context": self.context, "variables": self._variables, } ) diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/automation/websocket_api.py index eba56f94e7d182..7bd1b57d064b4a 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/automation/websocket_api.py @@ -24,6 +24,7 @@ ) from .trace import ( + DATA_AUTOMATION_TRACE, TraceJSONEncoder, get_debug_trace, get_debug_traces, @@ -38,6 +39,7 @@ def async_setup(hass: HomeAssistant) -> None: """Set up the websocket API.""" websocket_api.async_register_command(hass, websocket_automation_trace_get) websocket_api.async_register_command(hass, websocket_automation_trace_list) + websocket_api.async_register_command(hass, websocket_automation_trace_contexts) websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) @@ -86,6 +88,34 @@ def websocket_automation_trace_list(hass, connection, msg): connection.send_result(msg["id"], automation_traces) +@callback +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required("type"): "automation/trace/contexts", + vol.Optional("automation_id"): str, + } +) +def websocket_automation_trace_contexts(hass, connection, msg): + """Retrieve contexts we have traces for.""" + automation_id = msg.get("automation_id") + + if automation_id is not None: + values = { + automation_id: hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}) + } + else: + values = hass.data[DATA_AUTOMATION_TRACE] + + contexts = { + trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} + for automation_id, traces in values.items() + for trace in traces.values() + } + + connection.send_result(msg["id"], contexts) + + @callback @websocket_api.require_admin @websocket_api.websocket_command( diff --git a/tests/components/automation/test_logbook.py b/tests/components/automation/test_logbook.py new file mode 100644 index 00000000000000..e13ebdc17a111f --- /dev/null +++ b/tests/components/automation/test_logbook.py @@ -0,0 +1,54 @@ +"""Test automation logbook.""" +from homeassistant.components import automation, logbook +from homeassistant.core import Context +from homeassistant.setup import async_setup_component + +from tests.components.logbook.test_init import MockLazyEventPartialState + + +async def test_humanify_automation_trigger_event(hass): + """Test humanifying Shelly click event.""" + hass.config.components.add("recorder") + assert await async_setup_component(hass, "automation", {}) + assert await async_setup_component(hass, "logbook", {}) + entity_attr_cache = logbook.EntityAttributeCache(hass) + context = Context() + + event1, event2 = list( + logbook.humanify( + hass, + [ + MockLazyEventPartialState( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + "source": "state change of input_boolean.yo", + }, + context=context, + ), + MockLazyEventPartialState( + automation.EVENT_AUTOMATION_TRIGGERED, + { + "name": "Bla", + "entity_id": "automation.bla", + }, + context=context, + ), + ], + entity_attr_cache, + {}, + ) + ) + + assert event1["name"] == "Bla" + assert event1["message"] == "has been triggered by state change of input_boolean.yo" + assert event1["source"] == "state change of input_boolean.yo" + assert event1["context_id"] == context.id + assert event1["entity_id"] == "automation.bla" + + assert event2["name"] == "Bla" + assert event2["message"] == "has been triggered" + assert event2["source"] is None + assert event2["context_id"] == context.id + assert event2["entity_id"] == "automation.bla" diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index 84c85e224e53f2..ab69e89416abfa 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -65,6 +65,7 @@ def next_id(): await async_setup_component(hass, "config", {}) client = await hass_ws_client() + contexts = {} # Trigger "sun" automation context = Context() @@ -102,6 +103,10 @@ def next_id(): assert trace["trigger"] == "event 'test_event'" assert trace["unique_id"] == "sun" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -139,6 +144,10 @@ def next_id(): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with failing condition hass.bus.async_fire("test_event3") @@ -173,6 +182,10 @@ def next_id(): assert trace["trigger"] == "event 'test_event3'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } # Trigger "moon" automation, with passing condition hass.bus.async_fire("test_event2") @@ -210,6 +223,16 @@ def next_id(): assert trace["trigger"] == "event 'test_event2'" assert trace["unique_id"] == "moon" assert trace["variables"] + contexts[trace["context"]["id"]] = { + "run_id": trace["run_id"], + "automation_id": trace["automation_id"], + } + + # Check contexts + await client.send_json({"id": next_id(), "type": "automation/trace/contexts"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == contexts async def test_automation_trace_overflow(hass, hass_ws_client): From d49a4365735fbb6af09a6f0a02fe9f7655985484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Tue, 16 Mar 2021 21:38:16 +0000 Subject: [PATCH 1311/1818] Delay ZHA group updates to ensure all members are updated first (#46861) * Delay ZHA group updates to ensure all members are updated first After turning off a group, when the first device reports "off", the other devices may still be "on". If HA processes the group state update quickly enough, the group will see that some devices are on, so the state of the group will revert back to "on", and then "off" when the remaining devices all report "off". That would cause the UI toggle to go back and forward quickly, and automations that trigger with "state: on" to fire when the user turns the group off. This PR fixes that by delaying the group state update, giving time for all the devices to report their states first. * Fix zha group tests * Reorder sleeping. * Update tests/components/zha/common.py Co-authored-by: Alexei Chetroi --- homeassistant/components/zha/entity.py | 7 ++++++- tests/components/zha/common.py | 9 +++++++++ tests/components/zha/test_fan.py | 22 ++++++++++++++++------ tests/components/zha/test_light.py | 25 +++++++++++++++---------- tests/components/zha/test_switch.py | 17 +++++++++++------ 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index c19bad21455dae..9a573e92aa4380 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -32,6 +32,7 @@ _LOGGER = logging.getLogger(__name__) ENTITY_SUFFIX = "entity_suffix" +UPDATE_GROUP_FROM_CHILD_DELAY = 0.2 class BaseZhaEntity(LogMixin, entity.Entity): @@ -267,7 +268,11 @@ def send_removed_signal(): @callback def async_state_changed_listener(self, event: Event): """Handle child updates.""" - self.async_schedule_update_ha_state(True) + # Delay to ensure that we get updates from all members before updating the group + self.hass.loop.call_later( + UPDATE_GROUP_FROM_CHILD_DELAY, + lambda: self.async_schedule_update_ha_state(True), + ) async def async_will_remove_from_hass(self) -> None: """Handle removal from Home Assistant.""" diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 234ca0c9ba547b..eeffa3fb911226 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,4 +1,5 @@ """Common test objects.""" +import asyncio import time from unittest.mock import AsyncMock, Mock @@ -237,3 +238,11 @@ async def async_test_rejoin(hass, zigpy_device, clusters, report_counts, ep_id=1 assert cluster.bind.await_count == 1 assert cluster.configure_reporting.call_count == reports assert cluster.configure_reporting.await_count == reports + + +async def async_wait_for_updates(hass): + """Wait until all scheduled updates are executed.""" + await hass.async_block_till_done() + await asyncio.sleep(0) + await asyncio.sleep(0) + await hass.async_block_till_done() diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 81a441a4101dcc..eed0e0b691e222 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -50,6 +50,8 @@ send_attributes_report, ) +from tests.components.zha.common import async_wait_for_updates + IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8" IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e8" @@ -246,6 +248,10 @@ async def async_set_preset_mode(hass, entity_id, preset_mode=None): "zigpy.zcl.clusters.hvac.Fan.write_attributes", new=AsyncMock(return_value=zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]), ) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinator): """Test the fan entity for a ZHA group.""" zha_gateway = get_zha_gateway(hass) @@ -283,13 +289,13 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato dev2_fan_cluster = device_fan_2.device.endpoints[1].fan await async_enable_traffic(hass, [device_fan_1, device_fan_2], enabled=False) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the fans were created and that they are unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_fan_1, device_fan_2]) - + await async_wait_for_updates(hass) # test that the fan group entity was created and is off assert hass.states.get(entity_id).state == STATE_OFF @@ -338,13 +344,13 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato assert hass.states.get(entity_id).state == STATE_OFF await send_attributes_report(hass, dev2_fan_cluster, {0: 2}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group fan is speed medium assert hass.states.get(entity_id).state == STATE_ON await send_attributes_report(hass, dev2_fan_cluster, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group fan is now off assert hass.states.get(entity_id).state == STATE_OFF @@ -354,6 +360,10 @@ async def test_zha_group_fan_entity(hass, device_fan_1, device_fan_2, coordinato "zigpy.zcl.clusters.hvac.Fan.write_attributes", new=AsyncMock(side_effect=ZigbeeException), ) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_fan_entity_failure_state( hass, device_fan_1, device_fan_2, coordinator, caplog ): @@ -390,13 +400,13 @@ async def test_zha_group_fan_entity_failure_state( group_fan_cluster = zha_group.endpoint[hvac.Fan.cluster_id] await async_enable_traffic(hass, [device_fan_1, device_fan_2], enabled=False) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the fans were created and that they are unavailable assert hass.states.get(entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_fan_1, device_fan_2]) - + await async_wait_for_updates(hass) # test that the fan group entity was created and is off assert hass.states.get(entity_id).state == STATE_OFF diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 021f4f09dd9c8a..fe367a3969bcbe 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -25,6 +25,7 @@ ) from tests.common import async_fire_time_changed +from tests.components.zha.common import async_wait_for_updates ON = 1 OFF = 0 @@ -309,7 +310,7 @@ async def async_test_on_from_light(hass, cluster, entity_id): """Test on off functionality from the light.""" # turn on at light await send_attributes_report(hass, cluster, {1: -1, 0: 1, 2: 2}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert hass.states.get(entity_id).state == STATE_ON @@ -466,6 +467,10 @@ async def async_test_flash_from_hass(hass, cluster, entity_id, flash): "zigpy.zcl.clusters.general.OnOff.request", new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), ) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_light_entity( hass, device_light_1, device_light_2, device_light_3, coordinator ): @@ -522,13 +527,13 @@ async def test_zha_group_light_entity( await async_enable_traffic( hass, [device_light_1, device_light_2, device_light_3], enabled=False ) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and that they are unavailable assert hass.states.get(group_entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_light_1, device_light_2, device_light_3]) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and are off assert hass.states.get(group_entity_id).state == STATE_OFF @@ -580,7 +585,7 @@ async def test_zha_group_light_entity( assert hass.states.get(group_entity_id).state == STATE_ON await send_attributes_report(hass, dev2_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now off assert hass.states.get(device_1_entity_id).state == STATE_OFF @@ -588,7 +593,7 @@ async def test_zha_group_light_entity( assert hass.states.get(group_entity_id).state == STATE_OFF await send_attributes_report(hass, dev1_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now back on assert hass.states.get(device_1_entity_id).state == STATE_ON @@ -597,7 +602,7 @@ async def test_zha_group_light_entity( # turn it off to test a new member add being tracked await send_attributes_report(hass, dev1_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert hass.states.get(device_1_entity_id).state == STATE_OFF assert hass.states.get(device_2_entity_id).state == STATE_OFF assert hass.states.get(group_entity_id).state == STATE_OFF @@ -605,7 +610,7 @@ async def test_zha_group_light_entity( # add a new member and test that his state is also tracked await zha_group.async_add_members([GroupMember(device_light_3.ieee, 1)]) await send_attributes_report(hass, dev3_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert device_3_entity_id in zha_group.member_entity_ids assert len(zha_group.members) == 3 @@ -629,14 +634,14 @@ async def test_zha_group_light_entity( # add a member back and ensure that the group entity was created again await zha_group.async_add_members([GroupMember(device_light_3.ieee, 1)]) await send_attributes_report(hass, dev3_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert len(zha_group.members) == 2 assert hass.states.get(group_entity_id).state == STATE_ON # add a 3rd member and ensure we still have an entity and we track the new one await send_attributes_report(hass, dev1_cluster_on_off, {0: 0}) await send_attributes_report(hass, dev3_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert hass.states.get(group_entity_id).state == STATE_OFF # this will test that _reprobe_group is used correctly @@ -644,7 +649,7 @@ async def test_zha_group_light_entity( [GroupMember(device_light_2.ieee, 1), GroupMember(coordinator.ieee, 1)] ) await send_attributes_report(hass, dev2_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) assert len(zha_group.members) == 4 assert hass.states.get(group_entity_id).state == STATE_ON diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index da3037f720d832..4cec0753c684ab 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -20,6 +20,7 @@ ) from tests.common import mock_coro +from tests.components.zha.common import async_wait_for_updates ON = 1 OFF = 0 @@ -160,6 +161,10 @@ async def test_switch(hass, zha_device_joined_restored, zigpy_device): await async_test_rejoin(hass, zigpy_device, [cluster], (1,)) +@patch( + "homeassistant.components.zha.entity.UPDATE_GROUP_FROM_CHILD_DELAY", + new=0, +) async def test_zha_group_switch_entity( hass, device_switch_1, device_switch_2, coordinator ): @@ -195,14 +200,14 @@ async def test_zha_group_switch_entity( dev2_cluster_on_off = device_switch_2.device.endpoints[1].on_off await async_enable_traffic(hass, [device_switch_1, device_switch_2], enabled=False) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and that they are off assert hass.states.get(entity_id).state == STATE_UNAVAILABLE # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_switch_1, device_switch_2]) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that the lights were created and are off assert hass.states.get(entity_id).state == STATE_OFF @@ -240,25 +245,25 @@ async def test_zha_group_switch_entity( # test some of the group logic to make sure we key off states correctly await send_attributes_report(hass, dev1_cluster_on_off, {0: 1}) await send_attributes_report(hass, dev2_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is on assert hass.states.get(entity_id).state == STATE_ON await send_attributes_report(hass, dev1_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is still on assert hass.states.get(entity_id).state == STATE_ON await send_attributes_report(hass, dev2_cluster_on_off, {0: 0}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now off assert hass.states.get(entity_id).state == STATE_OFF await send_attributes_report(hass, dev1_cluster_on_off, {0: 1}) - await hass.async_block_till_done() + await async_wait_for_updates(hass) # test that group light is now back on assert hass.states.get(entity_id).state == STATE_ON From f605a3c149af0d61695b62058a380d908f733089 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 16 Mar 2021 13:22:07 -1000 Subject: [PATCH 1312/1818] Remove YAML support from August (#47615) --- homeassistant/components/august/__init__.py | 181 +++--------------- .../components/august/config_flow.py | 166 +++++++++------- homeassistant/components/august/gateway.py | 31 +-- homeassistant/components/august/manifest.json | 1 - homeassistant/components/august/strings.json | 10 +- .../components/august/translations/en.json | 10 +- tests/components/august/mocks.py | 19 +- tests/components/august/test_config_flow.py | 76 +++++++- tests/components/august/test_init.py | 50 +---- 9 files changed, 242 insertions(+), 302 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 23419f4df2d143..73f1cc6a1b509b 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -4,169 +4,25 @@ import logging from aiohttp import ClientError, ClientResponseError -from august.authenticator import ValidationResult from august.exceptions import AugustApiAIOHTTPError -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_USERNAME, - HTTP_UNAUTHORIZED, -) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, HTTP_UNAUTHORIZED from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError -import homeassistant.helpers.config_validation as cv from .activity import ActivityStream -from .const import ( - CONF_ACCESS_TOKEN_CACHE_FILE, - CONF_INSTALL_ID, - CONF_LOGIN_METHOD, - DATA_AUGUST, - DEFAULT_AUGUST_CONFIG_FILE, - DEFAULT_NAME, - DEFAULT_TIMEOUT, - DOMAIN, - LOGIN_METHODS, - MIN_TIME_BETWEEN_DETAIL_UPDATES, - PLATFORMS, - VERIFICATION_CODE_KEY, -) +from .const import DATA_AUGUST, DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .gateway import AugustGateway from .subscriber import AugustSubscriberMixin _LOGGER = logging.getLogger(__name__) -TWO_FA_REVALIDATE = "verify_configurator" - -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(DOMAIN), - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_INSTALL_ID): cv.string, - vol.Optional( - CONF_TIMEOUT, default=DEFAULT_TIMEOUT - ): cv.positive_int, - } - ) - }, - ), - extra=vol.ALLOW_EXTRA, -) - - -async def async_request_validation(hass, config_entry, august_gateway): - """Request a new verification code from the user.""" - - # - # In the future this should start a new config flow - # instead of using the legacy configurator - # - _LOGGER.error("Access token is no longer valid") - configurator = hass.components.configurator - entry_id = config_entry.entry_id - - async def async_august_configuration_validation_callback(data): - code = data.get(VERIFICATION_CODE_KEY) - result = await august_gateway.authenticator.async_validate_verification_code( - code - ) - - if result == ValidationResult.INVALID_VERIFICATION_CODE: - configurator.async_notify_errors( - hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE], - "Invalid verification code, please make sure you are using the latest code and try again.", - ) - elif result == ValidationResult.VALIDATED: - return await async_setup_august(hass, config_entry, august_gateway) - - return False - - if TWO_FA_REVALIDATE not in hass.data[DOMAIN][entry_id]: - await august_gateway.authenticator.async_send_verification_code() - - entry_data = config_entry.data - login_method = entry_data.get(CONF_LOGIN_METHOD) - username = entry_data.get(CONF_USERNAME) - - hass.data[DOMAIN][entry_id][TWO_FA_REVALIDATE] = configurator.async_request_config( - f"{DEFAULT_NAME} ({username})", - async_august_configuration_validation_callback, - description=( - "August must be re-verified. " - f"Please check your {login_method} ({username}) " - "and enter the verification code below" - ), - submit_caption="Verify", - fields=[ - {"id": VERIFICATION_CODE_KEY, "name": "Verification code", "type": "string"} - ], - ) - return - - -async def async_setup_august(hass, config_entry, august_gateway): - """Set up the August component.""" - - entry_id = config_entry.entry_id - hass.data[DOMAIN].setdefault(entry_id, {}) - - try: - await august_gateway.async_authenticate() - except RequireValidation: - await async_request_validation(hass, config_entry, august_gateway) - raise - - # We still use the configurator to get a new 2fa code - # when needed since config_flow doesn't have a way - # to re-request if it expires - if TWO_FA_REVALIDATE in hass.data[DOMAIN][entry_id]: - hass.components.configurator.async_request_done( - hass.data[DOMAIN][entry_id].pop(TWO_FA_REVALIDATE) - ) - - hass.data[DOMAIN][entry_id][DATA_AUGUST] = AugustData(hass, august_gateway) - - await hass.data[DOMAIN][entry_id][DATA_AUGUST].async_setup() - - for platform in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) - - return True - async def async_setup(hass: HomeAssistant, config: dict): """Set up the August component from YAML.""" - - conf = config.get(DOMAIN) hass.data.setdefault(DOMAIN, {}) - - if not conf: - return True - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_LOGIN_METHOD: conf.get(CONF_LOGIN_METHOD), - CONF_USERNAME: conf.get(CONF_USERNAME), - CONF_PASSWORD: conf.get(CONF_PASSWORD), - CONF_INSTALL_ID: conf.get(CONF_INSTALL_ID), - CONF_ACCESS_TOKEN_CACHE_FILE: DEFAULT_AUGUST_CONFIG_FILE, - }, - ) - ) return True @@ -184,11 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return False raise ConfigEntryNotReady from err - except InvalidAuth: + except (RequireValidation, InvalidAuth): _async_start_reauth(hass, entry) return False - except RequireValidation: - return False except (CannotConnect, asyncio.TimeoutError) as err: raise ConfigEntryNotReady from err @@ -221,6 +75,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unload_ok +async def async_setup_august(hass, config_entry, august_gateway): + """Set up the August component.""" + + if CONF_PASSWORD in config_entry.data: + # We no longer need to store passwords since we do not + # support YAML anymore + config_data = config_entry.data.copy() + del config_data[CONF_PASSWORD] + hass.config_entries.async_update_entry(config_entry, data=config_data) + + await august_gateway.async_authenticate() + + data = hass.data[DOMAIN][config_entry.entry_id] = { + DATA_AUGUST: AugustData(hass, august_gateway) + } + await data[DATA_AUGUST].async_setup() + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) + + return True + + class AugustData(AugustSubscriberMixin): """August data object.""" diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index f595479c0cfd65..29bb41947a065d 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -5,14 +5,9 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME - -from .const import ( - CONF_LOGIN_METHOD, - DEFAULT_TIMEOUT, - LOGIN_METHODS, - VERIFICATION_CODE_KEY, -) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +from .const import CONF_LOGIN_METHOD, LOGIN_METHODS, VERIFICATION_CODE_KEY from .const import DOMAIN # pylint:disable=unused-import from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .gateway import AugustGateway @@ -68,61 +63,48 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Store an AugustGateway().""" self._august_gateway = None - self.user_auth_details = {} + self._user_auth_details = {} self._needs_reset = False + self._mode = None super().__init__() async def async_step_user(self, user_input=None): """Handle the initial step.""" - if self._august_gateway is None: - self._august_gateway = AugustGateway(self.hass) + self._august_gateway = AugustGateway(self.hass) + return await self.async_step_user_validate() + + async def async_step_user_validate(self, user_input=None): + """Handle authentication.""" errors = {} if user_input is not None: - combined_inputs = {**self.user_auth_details, **user_input} - await self._august_gateway.async_setup(combined_inputs) - if self._needs_reset: - self._needs_reset = False - await self._august_gateway.async_reset_authentication() - - try: - info = await async_validate_input( - combined_inputs, - self._august_gateway, - ) - except CannotConnect: - errors["base"] = "cannot_connect" - except InvalidAuth: - errors["base"] = "invalid_auth" - except RequireValidation: - self.user_auth_details.update(user_input) - - return await self.async_step_validation() - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - if not errors: - self.user_auth_details.update(user_input) - - existing_entry = await self.async_set_unique_id( - combined_inputs[CONF_USERNAME] - ) - if existing_entry: - self.hass.config_entries.async_update_entry( - existing_entry, data=info["data"] - ) - await self.hass.config_entries.async_reload(existing_entry.entry_id) - return self.async_abort(reason="reauth_successful") - return self.async_create_entry(title=info["title"], data=info["data"]) + result = await self._async_auth_or_validate(user_input, errors) + if result is not None: + return result return self.async_show_form( - step_id="user", data_schema=self._async_build_schema(), errors=errors + step_id="user_validate", + data_schema=vol.Schema( + { + vol.Required( + CONF_LOGIN_METHOD, + default=self._user_auth_details.get(CONF_LOGIN_METHOD, "phone"), + ): vol.In(LOGIN_METHODS), + vol.Required( + CONF_USERNAME, + default=self._user_auth_details.get(CONF_USERNAME), + ): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, ) async def async_step_validation(self, user_input=None): """Handle validation (2fa) step.""" if user_input: - return await self.async_step_user({**self.user_auth_details, **user_input}) + if self._mode == "reauth": + return await self.async_step_reauth_validate(user_input) + return await self.async_step_user_validate(user_input) return self.async_show_form( step_id="validation", @@ -130,34 +112,70 @@ async def async_step_validation(self, user_input=None): {vol.Required(VERIFICATION_CODE_KEY): vol.All(str, vol.Strip)} ), description_placeholders={ - CONF_USERNAME: self.user_auth_details.get(CONF_USERNAME), - CONF_LOGIN_METHOD: self.user_auth_details.get(CONF_LOGIN_METHOD), + CONF_USERNAME: self._user_auth_details[CONF_USERNAME], + CONF_LOGIN_METHOD: self._user_auth_details[CONF_LOGIN_METHOD], }, ) - async def async_step_import(self, user_input): - """Handle import.""" - await self.async_set_unique_id(user_input[CONF_USERNAME]) - self._abort_if_unique_id_configured() - - return await self.async_step_user(user_input) - async def async_step_reauth(self, data): """Handle configuration by re-auth.""" - self.user_auth_details = dict(data) + self._user_auth_details = dict(data) + self._mode = "reauth" self._needs_reset = True - return await self.async_step_user() - - def _async_build_schema(self): - """Generate the config flow schema.""" - base_schema = { - vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int), - } - for key in self.user_auth_details: - if key == CONF_PASSWORD or key not in base_schema: - continue - del base_schema[key] - return vol.Schema(base_schema) + self._august_gateway = AugustGateway(self.hass) + return await self.async_step_reauth_validate() + + async def async_step_reauth_validate(self, user_input=None): + """Handle reauth and validation.""" + errors = {} + if user_input is not None: + result = await self._async_auth_or_validate(user_input, errors) + if result is not None: + 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_USERNAME: self._user_auth_details[CONF_USERNAME], + }, + ) + + async def _async_auth_or_validate(self, user_input, errors): + self._user_auth_details.update(user_input) + await self._august_gateway.async_setup(self._user_auth_details) + if self._needs_reset: + self._needs_reset = False + await self._august_gateway.async_reset_authentication() + try: + info = await async_validate_input( + self._user_auth_details, + self._august_gateway, + ) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except RequireValidation: + return await self.async_step_validation() + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if errors: + return None + + existing_entry = await self.async_set_unique_id( + self._user_auth_details[CONF_USERNAME] + ) + if not existing_entry: + return self.async_create_entry(title=info["title"], data=info["data"]) + + self.hass.config_entries.async_update_entry(existing_entry, data=info["data"]) + await self.hass.config_entries.async_reload(existing_entry.entry_id) + return self.async_abort(reason="reauth_successful") diff --git a/homeassistant/components/august/gateway.py b/homeassistant/components/august/gateway.py index b72bb52e710925..f71541e82fcb35 100644 --- a/homeassistant/components/august/gateway.py +++ b/homeassistant/components/august/gateway.py @@ -21,6 +21,7 @@ CONF_INSTALL_ID, CONF_LOGIN_METHOD, DEFAULT_AUGUST_CONFIG_FILE, + DEFAULT_TIMEOUT, VERIFICATION_CODE_KEY, ) from .exceptions import CannotConnect, InvalidAuth, RequireValidation @@ -52,9 +53,7 @@ def config_entry(self): return { CONF_LOGIN_METHOD: self._config[CONF_LOGIN_METHOD], CONF_USERNAME: self._config[CONF_USERNAME], - CONF_PASSWORD: self._config[CONF_PASSWORD], CONF_INSTALL_ID: self._config.get(CONF_INSTALL_ID), - CONF_TIMEOUT: self._config.get(CONF_TIMEOUT), CONF_ACCESS_TOKEN_CACHE_FILE: self._access_token_cache_file, } @@ -70,14 +69,15 @@ async def async_setup(self, conf): self._config = conf self.api = ApiAsync( - self._aiohttp_session, timeout=self._config.get(CONF_TIMEOUT) + self._aiohttp_session, + timeout=self._config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), ) self.authenticator = AuthenticatorAsync( self.api, self._config[CONF_LOGIN_METHOD], self._config[CONF_USERNAME], - self._config[CONF_PASSWORD], + self._config.get(CONF_PASSWORD, ""), install_id=self._config.get(CONF_INSTALL_ID), access_token_cache_file=self._hass.config.path( self._access_token_cache_file @@ -128,14 +128,15 @@ def _reset_authentication(self): async def async_refresh_access_token_if_needed(self): """Refresh the august access token if needed.""" - if self.authenticator.should_refresh(): - async with self._token_refresh_lock: - refreshed_authentication = ( - await self.authenticator.async_refresh_access_token(force=False) - ) - _LOGGER.info( - "Refreshed august access token. The old token expired at %s, and the new token expires at %s", - self.authentication.access_token_expires, - refreshed_authentication.access_token_expires, - ) - self.authentication = refreshed_authentication + if not self.authenticator.should_refresh(): + return + async with self._token_refresh_lock: + refreshed_authentication = ( + await self.authenticator.async_refresh_access_token(force=False) + ) + _LOGGER.info( + "Refreshed august access token. The old token expired at %s, and the new token expires at %s", + self.authentication.access_token_expires, + refreshed_authentication.access_token_expires, + ) + self.authentication = refreshed_authentication diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index dcdfb0a0497a65..91733b6822e07c 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -3,7 +3,6 @@ "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", "requirements": ["py-august==0.25.2"], - "dependencies": ["configurator"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/homeassistant/components/august/strings.json b/homeassistant/components/august/strings.json index 998d870e629e03..7939fb1d25f3b3 100644 --- a/homeassistant/components/august/strings.json +++ b/homeassistant/components/august/strings.json @@ -17,15 +17,21 @@ }, "description": "Please check your {login_method} ({username}) and enter the verification code below" }, - "user": { + "user_validate": { "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", "data": { - "timeout": "Timeout (seconds)", "password": "[%key:common::config_flow::data::password%]", "username": "[%key:common::config_flow::data::username%]", "login_method": "Login Method" }, "title": "Setup an August account" + }, + "reauth_validate": { + "description": "Enter the password for {username}.", + "data": { + "password": "[%key:common::config_flow::data::password%]" + }, + "title": "Reauthenticate an August account" } } } diff --git a/homeassistant/components/august/translations/en.json b/homeassistant/components/august/translations/en.json index c6c19321d8a5a0..f2ceef78d48653 100644 --- a/homeassistant/components/august/translations/en.json +++ b/homeassistant/components/august/translations/en.json @@ -10,11 +10,17 @@ "unknown": "Unexpected error" }, "step": { - "user": { + "reauth_validate": { + "data": { + "password": "Password" + }, + "description": "Enter the password for {username}.", + "title": "Reauthenticate an August account" + }, + "user_validate": { "data": { "login_method": "Login Method", "password": "Password", - "timeout": "Timeout (seconds)", "username": "Username" }, "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index e02e1ec59dd502..928b753af52cbc 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -22,15 +22,10 @@ from august.doorbell import Doorbell, DoorbellDetail from august.lock import Lock, LockDetail -from homeassistant.components.august import ( - CONF_LOGIN_METHOD, - CONF_PASSWORD, - CONF_USERNAME, - DOMAIN, -) -from homeassistant.setup import async_setup_component +from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from tests.common import load_fixture +from tests.common import MockConfigEntry, load_fixture def _mock_get_config(): @@ -61,7 +56,13 @@ async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock): ) ) api_mock.return_value = api_instance - assert await async_setup_component(hass, DOMAIN, _mock_get_config()) + entry = MockConfigEntry( + domain=DOMAIN, + data=_mock_get_config()[DOMAIN], + options={}, + ) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() return True diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index c1e7c9bb3c5544..205c5c70689e28 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -54,9 +54,7 @@ async def test_form(hass): assert result2["data"] == { CONF_LOGIN_METHOD: "email", CONF_USERNAME: "my@email.tld", - CONF_PASSWORD: "test-password", CONF_INSTALL_ID: None, - CONF_TIMEOUT: 10, CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", } assert len(mock_setup.mock_calls) == 1 @@ -215,9 +213,7 @@ async def test_form_needs_validate(hass): assert result4["data"] == { CONF_LOGIN_METHOD: "email", CONF_USERNAME: "my@email.tld", - CONF_PASSWORD: "test-password", CONF_INSTALL_ID: None, - CONF_TIMEOUT: 10, CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", } assert len(mock_setup.mock_calls) == 1 @@ -268,3 +264,75 @@ async def test_form_reauth(hass): assert result2["reason"] == "reauth_successful" assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_reauth_with_2fa(hass): + """Test reauthenticate with 2fa.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_LOGIN_METHOD: "email", + CONF_USERNAME: "my@email.tld", + CONF_PASSWORD: "test-password", + CONF_INSTALL_ID: None, + CONF_TIMEOUT: 10, + CONF_ACCESS_TOKEN_CACHE_FILE: ".my@email.tld.august.conf", + }, + unique_id="my@email.tld", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "reauth"}, data=entry.data + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + side_effect=RequireValidation, + ), patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_PASSWORD: "new-test-password", + }, + ) + await hass.async_block_till_done() + + assert len(mock_send_verification_code.mock_calls) == 1 + assert result2["type"] == "form" + assert result2["errors"] is None + assert result2["step_id"] == "validation" + + # Try with the CORRECT verification code and we setup + with patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", + return_value=ValidationResult.VALIDATED, + ) as mock_validate_verification_code, patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, patch( + "homeassistant.components.august.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.august.async_setup_entry", return_value=True + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {VERIFICATION_CODE_KEY: "correct"}, + ) + await hass.async_block_till_done() + + assert len(mock_validate_verification_code.mock_calls) == 1 + assert len(mock_send_verification_code.mock_calls) == 0 + assert result3["type"] == "abort" + assert result3["reason"] == "reauth_successful" + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index e881ac09c975d9..d0116d2c586b0f 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -7,13 +7,7 @@ from august.exceptions import AugustApiAIOHTTPError from homeassistant import setup -from homeassistant.components.august.const import ( - CONF_ACCESS_TOKEN_CACHE_FILE, - CONF_INSTALL_ID, - CONF_LOGIN_METHOD, - DEFAULT_AUGUST_CONFIG_FILE, - DOMAIN, -) +from homeassistant.components.august.const import DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_SETUP_ERROR, @@ -21,16 +15,12 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_PASSWORD, - CONF_TIMEOUT, - CONF_USERNAME, SERVICE_LOCK, SERVICE_UNLOCK, STATE_LOCKED, STATE_ON, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry from tests.components.august.mocks import ( @@ -149,35 +139,6 @@ async def test_lock_has_doorsense(hass): assert binary_sensor_missing_doorsense_id_name_open is None -async def test_set_up_from_yaml(hass): - """Test to make sure config is imported from yaml.""" - - await setup.async_setup_component(hass, "persistent_notification", {}) - with patch( - "homeassistant.components.august.async_setup_august", - return_value=True, - ) as mock_setup_august, patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ): - assert await async_setup_component(hass, DOMAIN, _mock_get_config()) - await hass.async_block_till_done() - assert len(mock_setup_august.mock_calls) == 1 - call = mock_setup_august.call_args - args, _ = call - imported_config_entry = args[1] - # The import must use DEFAULT_AUGUST_CONFIG_FILE so they - # do not loose their token when config is migrated - assert imported_config_entry.data == { - CONF_ACCESS_TOKEN_CACHE_FILE: DEFAULT_AUGUST_CONFIG_FILE, - CONF_INSTALL_ID: None, - CONF_LOGIN_METHOD: "email", - CONF_PASSWORD: "mocked_password", - CONF_TIMEOUT: None, - CONF_USERNAME: "mocked_username", - } - - async def test_auth_fails(hass): """Config entry state is ENTRY_STATE_SETUP_ERROR when auth fails.""" @@ -201,7 +162,7 @@ async def test_auth_fails(hass): flows = hass.config_entries.flow.async_progress() - assert flows[0]["step_id"] == "user" + assert flows[0]["step_id"] == "reauth_validate" async def test_bad_password(hass): @@ -229,7 +190,7 @@ async def test_bad_password(hass): flows = hass.config_entries.flow.async_progress() - assert flows[0]["step_id"] == "user" + assert flows[0]["step_id"] == "reauth_validate" async def test_http_failure(hass): @@ -279,7 +240,7 @@ async def test_unknown_auth_state(hass): flows = hass.config_entries.flow.async_progress() - assert flows[0]["step_id"] == "user" + assert flows[0]["step_id"] == "reauth_validate" async def test_requires_validation_state(hass): @@ -305,4 +266,5 @@ async def test_requires_validation_state(hass): assert config_entry.state == ENTRY_STATE_SETUP_ERROR - assert hass.config_entries.flow.async_progress() == [] + assert len(hass.config_entries.flow.async_progress()) == 1 + assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth" From d21d9951ba1663905325f80ea8c9ef2019eaaa6e Mon Sep 17 00:00:00 2001 From: Kevin Worrel <37058192+dieselrabbit@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:32:02 -0700 Subject: [PATCH 1313/1818] Add Pentair ScreenLogic integration (#47933) Co-authored-by: J. Nick Koston --- .coveragerc | 5 + CODEOWNERS | 1 + .../components/screenlogic/__init__.py | 202 ++++++++++++++ .../components/screenlogic/binary_sensor.py | 54 ++++ .../components/screenlogic/config_flow.py | 218 +++++++++++++++ homeassistant/components/screenlogic/const.py | 7 + .../components/screenlogic/manifest.json | 11 + .../components/screenlogic/sensor.py | 107 ++++++++ .../components/screenlogic/strings.json | 39 +++ .../components/screenlogic/switch.py | 63 +++++ .../screenlogic/translations/en.json | 39 +++ .../components/screenlogic/water_heater.py | 128 +++++++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/dhcp.py | 5 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/screenlogic/__init__.py | 1 + .../screenlogic/test_config_flow.py | 249 ++++++++++++++++++ 18 files changed, 1136 insertions(+) create mode 100644 homeassistant/components/screenlogic/__init__.py create mode 100644 homeassistant/components/screenlogic/binary_sensor.py create mode 100644 homeassistant/components/screenlogic/config_flow.py create mode 100644 homeassistant/components/screenlogic/const.py create mode 100644 homeassistant/components/screenlogic/manifest.json create mode 100644 homeassistant/components/screenlogic/sensor.py create mode 100644 homeassistant/components/screenlogic/strings.json create mode 100644 homeassistant/components/screenlogic/switch.py create mode 100644 homeassistant/components/screenlogic/translations/en.json create mode 100644 homeassistant/components/screenlogic/water_heater.py create mode 100644 tests/components/screenlogic/__init__.py create mode 100644 tests/components/screenlogic/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index d6bdfb9b09181e..8f609ec5b22235 100644 --- a/.coveragerc +++ b/.coveragerc @@ -842,6 +842,11 @@ omit = 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/sensor.py + homeassistant/components/screenlogic/switch.py + homeassistant/components/screenlogic/water_heater.py homeassistant/components/scsgate/* homeassistant/components/scsgate/cover.py homeassistant/components/sendgrid/notify.py diff --git a/CODEOWNERS b/CODEOWNERS index 263e5337c588b5..1b0f2747dee204 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -401,6 +401,7 @@ homeassistant/components/samsungtv/* @escoand homeassistant/components/scene/* @home-assistant/core homeassistant/components/schluter/* @prairieapps homeassistant/components/scrape/* @fabaff +homeassistant/components/screenlogic/* @dieselrabbit homeassistant/components/script/* @home-assistant/core homeassistant/components/search/* @home-assistant/core homeassistant/components/sense/* @kbickar diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py new file mode 100644 index 00000000000000..720b59f80b9626 --- /dev/null +++ b/homeassistant/components/screenlogic/__init__.py @@ -0,0 +1,202 @@ +"""The Screenlogic integration.""" +import asyncio +from collections import defaultdict +from datetime import timedelta +import logging + +from screenlogicpy import ScreenLogicError, ScreenLogicGateway +from screenlogicpy.const import ( + CONTROLLER_HARDWARE, + SL_GATEWAY_IP, + SL_GATEWAY_NAME, + SL_GATEWAY_PORT, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .config_flow import async_discover_gateways_by_unique_id, name_for_mac +from .const import DEFAULT_SCAN_INTERVAL, DISCOVERED_GATEWAYS, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["switch", "sensor", "binary_sensor", "water_heater"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Screenlogic component.""" + domain_data = hass.data[DOMAIN] = {} + domain_data[DISCOVERED_GATEWAYS] = await async_discover_gateways_by_unique_id(hass) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Screenlogic from a config entry.""" + mac = entry.unique_id + # Attempt to re-discover named gateway to follow IP changes + discovered_gateways = hass.data[DOMAIN][DISCOVERED_GATEWAYS] + if mac in discovered_gateways: + connect_info = discovered_gateways[mac] + else: + _LOGGER.warning("Gateway rediscovery failed.") + # Static connection defined or fallback from discovery + connect_info = { + SL_GATEWAY_NAME: name_for_mac(mac), + SL_GATEWAY_IP: entry.data[CONF_IP_ADDRESS], + SL_GATEWAY_PORT: entry.data[CONF_PORT], + } + + try: + gateway = ScreenLogicGateway(**connect_info) + except ScreenLogicError as ex: + _LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex) + raise ConfigEntryNotReady from ex + except AttributeError as ex: + _LOGGER.exception( + "Unexpected error while connecting to the gateway %s", connect_info + ) + raise ConfigEntryNotReady from ex + + coordinator = ScreenlogicDataUpdateCoordinator( + hass, config_entry=entry, gateway=gateway + ) + + device_data = defaultdict(list) + + await coordinator.async_refresh() + + for circuit in coordinator.data["circuits"]: + device_data["switch"].append(circuit) + + for sensor in coordinator.data["sensors"]: + if sensor == "chem_alarm": + device_data["binary_sensor"].append(sensor) + else: + if coordinator.data["sensors"][sensor]["value"] != 0: + device_data["sensor"].append(sensor) + + for pump in coordinator.data["pumps"]: + if ( + coordinator.data["pumps"][pump]["data"] != 0 + and "currentWatts" in coordinator.data["pumps"][pump] + ): + device_data["pump"].append(pump) + + for body in coordinator.data["bodies"]: + device_data["water_heater"].append(body) + + hass.data[DOMAIN][entry.entry_id] = { + "coordinator": coordinator, + "devices": device_data, + "listener": entry.add_update_listener(async_update_listener), + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + hass.data[DOMAIN][entry.entry_id]["listener"]() + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + +class ScreenlogicDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage the data update for the Screenlogic component.""" + + def __init__(self, hass, *, config_entry, gateway): + """Initialize the Screenlogic Data Update Coordinator.""" + self.config_entry = config_entry + self.gateway = gateway + self.screenlogic_data = {} + interval = timedelta( + seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + ) + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=interval, + ) + + async def _async_update_data(self): + """Fetch data from the Screenlogic gateway.""" + try: + await self.hass.async_add_executor_job(self.gateway.update) + return self.gateway.get_data() + except ScreenLogicError as error: + raise UpdateFailed(error) from error + + +class ScreenlogicEntity(CoordinatorEntity): + """Base class for all ScreenLogic entities.""" + + def __init__(self, coordinator, datakey): + """Initialize of the entity.""" + super().__init__(coordinator) + self._data_key = datakey + + @property + def mac(self): + """Mac address.""" + return self.coordinator.config_entry.unique_id + + @property + def unique_id(self): + """Entity Unique ID.""" + return f"{self.mac}_{self._data_key}" + + @property + def config_data(self): + """Shortcut for config data.""" + return self.coordinator.data["config"] + + @property + def gateway(self): + """Return the gateway.""" + return self.coordinator.gateway + + @property + def gateway_name(self): + """Return the configured name of the gateway.""" + return self.gateway.name + + @property + def device_info(self): + """Return device information for the controller.""" + controller_type = self.config_data["controler_type"] + hardware_type = self.config_data["hardware_type"] + return { + "connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)}, + "name": self.gateway_name, + "manufacturer": "Pentair", + "model": CONTROLLER_HARDWARE[controller_type][hardware_type], + } diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py new file mode 100644 index 00000000000000..fa8d63ee5e6bdb --- /dev/null +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -0,0 +1,54 @@ +"""Support for a ScreenLogic Binary Sensor.""" +import logging + +from screenlogicpy.const import ON_OFF + +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for binary_sensor in data["devices"]["binary_sensor"]: + entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor)) + async_add_entities(entities, True) + + +class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): + """Representation of a ScreenLogic binary sensor entity.""" + + @property + def name(self): + """Return the sensor name.""" + return f"{self.gateway_name} {self.sensor['name']}" + + @property + def device_class(self): + """Return the device class.""" + device_class = self.sensor.get("hass_type") + if device_class in DEVICE_CLASSES: + return device_class + return None + + @property + def is_on(self) -> bool: + """Determine if the sensor is on.""" + return self.sensor["value"] == ON_OFF.ON + + @property + def sensor(self): + """Shortcut to access the sensor data.""" + return self.sensor_data[self._data_key] + + @property + def sensor_data(self): + """Shortcut to access the sensors data.""" + return self.coordinator.data["sensors"] diff --git a/homeassistant/components/screenlogic/config_flow.py b/homeassistant/components/screenlogic/config_flow.py new file mode 100644 index 00000000000000..865c0fdbbf4a9f --- /dev/null +++ b/homeassistant/components/screenlogic/config_flow.py @@ -0,0 +1,218 @@ +"""Config flow for ScreenLogic.""" +import logging + +from screenlogicpy import ScreenLogicError, discover +from screenlogicpy.const import SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWAY_PORT +from screenlogicpy.requests import login +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import format_mac + +from .const import DEFAULT_SCAN_INTERVAL, MIN_SCAN_INTERVAL +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +GATEWAY_SELECT_KEY = "selected_gateway" +GATEWAY_MANUAL_ENTRY = "manual" + +PENTAIR_OUI = "00-C0-33" + + +async def async_discover_gateways_by_unique_id(hass): + """Discover gateways and return a dict of them by unique id.""" + discovered_gateways = {} + try: + hosts = await hass.async_add_executor_job(discover) + _LOGGER.debug("Discovered hosts: %s", hosts) + except ScreenLogicError as ex: + _LOGGER.debug(ex) + return discovered_gateways + + for host in hosts: + mac = _extract_mac_from_name(host[SL_GATEWAY_NAME]) + discovered_gateways[mac] = host + + _LOGGER.debug("Discovered gateways: %s", discovered_gateways) + return discovered_gateways + + +def _extract_mac_from_name(name): + return format_mac(f"{PENTAIR_OUI}-{name.split(':')[1].strip()}") + + +def short_mac(mac): + """Short version of the mac as seen in the app.""" + return "-".join(mac.split(":")[3:]).upper() + + +def name_for_mac(mac): + """Derive the gateway name from the mac.""" + return f"Pentair: {short_mac(mac)}" + + +async def async_get_mac_address(hass, ip_address, port): + """Connect to a screenlogic gateway and return the mac address.""" + connected_socket = await hass.async_add_executor_job( + login.create_socket, + ip_address, + port, + ) + if not connected_socket: + raise ScreenLogicError("Unknown socket error") + return await hass.async_add_executor_job(login.gateway_connect, connected_socket) + + +class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow to setup screen logic devices.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize ScreenLogic ConfigFlow.""" + self.discovered_gateways = {} + self.discovered_ip = None + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for ScreenLogic.""" + return ScreenLogicOptionsFlowHandler(config_entry) + + async def async_step_user(self, user_input=None): + """Handle the start of the config flow.""" + self.discovered_gateways = await async_discover_gateways_by_unique_id(self.hass) + return await self.async_step_gateway_select() + + async def async_step_dhcp(self, dhcp_discovery): + """Handle dhcp discovery.""" + mac = _extract_mac_from_name(dhcp_discovery[HOSTNAME]) + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured( + updates={CONF_IP_ADDRESS: dhcp_discovery[IP_ADDRESS]} + ) + self.discovered_ip = dhcp_discovery[IP_ADDRESS] + self.context["title_placeholders"] = {"name": dhcp_discovery[HOSTNAME]} + return await self.async_step_gateway_entry() + + async def async_step_gateway_select(self, user_input=None): + """Handle the selection of a discovered ScreenLogic gateway.""" + existing = self._async_current_ids() + unconfigured_gateways = { + mac: gateway[SL_GATEWAY_NAME] + for mac, gateway in self.discovered_gateways.items() + if mac not in existing + } + + if not unconfigured_gateways: + return await self.async_step_gateway_entry() + + errors = {} + if user_input is not None: + if user_input[GATEWAY_SELECT_KEY] == GATEWAY_MANUAL_ENTRY: + return await self.async_step_gateway_entry() + + mac = user_input[GATEWAY_SELECT_KEY] + selected_gateway = self.discovered_gateways[mac] + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name_for_mac(mac), + data={ + CONF_IP_ADDRESS: selected_gateway[SL_GATEWAY_IP], + CONF_PORT: selected_gateway[SL_GATEWAY_PORT], + }, + ) + + return self.async_show_form( + step_id="gateway_select", + data_schema=vol.Schema( + { + vol.Required(GATEWAY_SELECT_KEY): vol.In( + { + **unconfigured_gateways, + GATEWAY_MANUAL_ENTRY: "Manually configure a ScreenLogic gateway", + } + ) + } + ), + errors=errors, + description_placeholders={}, + ) + + async def async_step_gateway_entry(self, user_input=None): + """Handle the manual entry of a ScreenLogic gateway.""" + errors = {} + ip_address = self.discovered_ip + port = 80 + + if user_input is not None: + ip_address = user_input[CONF_IP_ADDRESS] + port = user_input[CONF_PORT] + try: + mac = format_mac( + await async_get_mac_address(self.hass, ip_address, port) + ) + except ScreenLogicError as ex: + _LOGGER.debug(ex) + errors[CONF_IP_ADDRESS] = "cannot_connect" + + if not errors: + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=name_for_mac(mac), + data={ + CONF_IP_ADDRESS: ip_address, + CONF_PORT: port, + }, + ) + + return self.async_show_form( + step_id="gateway_entry", + data_schema=vol.Schema( + { + vol.Required(CONF_IP_ADDRESS, default=ip_address): str, + vol.Required(CONF_PORT, default=port): int, + } + ), + errors=errors, + description_placeholders={}, + ) + + +class ScreenLogicOptionsFlowHandler(config_entries.OptionsFlow): + """Handles the options for the ScreenLogic integration.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Init the screen logic 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=self.config_entry.title, data=user_input + ) + + current_interval = self.config_entry.options.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required( + CONF_SCAN_INTERVAL, + default=current_interval, + ): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL)) + } + ), + description_placeholders={"gateway_name": self.config_entry.title}, + ) diff --git a/homeassistant/components/screenlogic/const.py b/homeassistant/components/screenlogic/const.py new file mode 100644 index 00000000000000..d777dc6ddc5031 --- /dev/null +++ b/homeassistant/components/screenlogic/const.py @@ -0,0 +1,7 @@ +"""Constants for the ScreenLogic integration.""" + +DOMAIN = "screenlogic" +DEFAULT_SCAN_INTERVAL = 30 +MIN_SCAN_INTERVAL = 10 + +DISCOVERED_GATEWAYS = "_discovered_gateways" diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json new file mode 100644 index 00000000000000..2ff3f2c683d9be --- /dev/null +++ b/homeassistant/components/screenlogic/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "screenlogic", + "name": "Pentair ScreenLogic", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/screenlogic", + "requirements": ["screenlogicpy==0.1.2"], + "codeowners": [ + "@dieselrabbit" + ], + "dhcp": [{"hostname":"pentair: *","macaddress":"00C033*"}] +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py new file mode 100644 index 00000000000000..f4f86cdd2aa5fb --- /dev/null +++ b/homeassistant/components/screenlogic/sensor.py @@ -0,0 +1,107 @@ +"""Support for a ScreenLogic Sensor.""" +import logging + +from homeassistant.components.sensor import DEVICE_CLASSES + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM") + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + # Generic sensors + for sensor in data["devices"]["sensor"]: + entities.append(ScreenLogicSensor(coordinator, sensor)) + for pump in data["devices"]["pump"]: + for pump_key in PUMP_SENSORS: + entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key)) + + async_add_entities(entities, True) + + +class ScreenLogicSensor(ScreenlogicEntity): + """Representation of a ScreenLogic sensor entity.""" + + @property + def name(self): + """Name of the sensor.""" + return f"{self.gateway_name} {self.sensor['name']}" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.sensor.get("unit") + + @property + def device_class(self): + """Device class of the sensor.""" + device_class = self.sensor.get("hass_type") + if device_class in DEVICE_CLASSES: + return device_class + return None + + @property + def state(self): + """State of the sensor.""" + value = self.sensor["value"] + return (value - 1) if "supply" in self._data_key else value + + @property + def sensor(self): + """Shortcut to access the sensor data.""" + return self.sensor_data[self._data_key] + + @property + def sensor_data(self): + """Shortcut to access the sensors data.""" + return self.coordinator.data["sensors"] + + +class ScreenLogicPumpSensor(ScreenlogicEntity): + """Representation of a ScreenLogic pump sensor entity.""" + + def __init__(self, coordinator, pump, key): + """Initialize of the pump sensor.""" + super().__init__(coordinator, f"{key}_{pump}") + self._pump_id = pump + self._key = key + + @property + def name(self): + """Return the pump sensor name.""" + return f"{self.gateway_name} {self.pump_sensor['name']}" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self.pump_sensor.get("unit") + + @property + def device_class(self): + """Return the device class.""" + device_class = self.pump_sensor.get("hass_type") + if device_class in DEVICE_CLASSES: + return device_class + return None + + @property + def state(self): + """State of the pump sensor.""" + return self.pump_sensor["value"] + + @property + def pump_sensor(self): + """Shortcut to access the pump sensor data.""" + return self.pumps_data[self._pump_id][self._key] + + @property + def pumps_data(self): + """Shortcut to access the pump data.""" + return self.coordinator.data["pumps"] diff --git a/homeassistant/components/screenlogic/strings.json b/homeassistant/components/screenlogic/strings.json new file mode 100644 index 00000000000000..155eeb3043e1ac --- /dev/null +++ b/homeassistant/components/screenlogic/strings.json @@ -0,0 +1,39 @@ +{ + "config": { + "flow_title": "ScreenLogic {name}", + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "step": { + "gateway_entry": { + "title": "ScreenLogic", + "description": "Enter your ScreenLogic Gateway information.", + "data": { + "ip_address": "[%key:common::config_flow::data::ip%]", + "port": "[%key:common::config_flow::data::port%]" + } + }, + "gateway_select": { + "title": "ScreenLogic", + "description": "The following ScreenLogic gateways were discovered. Please select one to configure, or choose to manually configure a ScreenLogic gateway.", + "data": { + "selected_gateway": "Gateway" + } + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options":{ + "step": { + "init": { + "title": "ScreenLogic", + "description": "Specify settings for {gateway_name}", + "data": { + "scan_interval": "Seconds between scans" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py new file mode 100644 index 00000000000000..aa1e643681ee55 --- /dev/null +++ b/homeassistant/components/screenlogic/switch.py @@ -0,0 +1,63 @@ +"""Support for a ScreenLogic 'circuit' switch.""" +import logging + +from screenlogicpy.const import ON_OFF + +from homeassistant.components.switch import SwitchEntity + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for switch in data["devices"]["switch"]: + entities.append(ScreenLogicSwitch(coordinator, switch)) + async_add_entities(entities, True) + + +class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity): + """ScreenLogic switch entity.""" + + @property + def name(self): + """Get the name of the switch.""" + return f"{self.gateway_name} {self.circuit['name']}" + + @property + def is_on(self) -> bool: + """Get whether the switch is in on state.""" + return self.circuit["value"] == 1 + + async def async_turn_on(self, **kwargs) -> None: + """Send the ON command.""" + return await self._async_set_circuit(ON_OFF.ON) + + async def async_turn_off(self, **kwargs) -> None: + """Send the OFF command.""" + return await self._async_set_circuit(ON_OFF.OFF) + + async def _async_set_circuit(self, circuit_value) -> None: + if await self.hass.async_add_executor_job( + self.gateway.set_circuit, self._data_key, circuit_value + ): + _LOGGER.info("screenlogic turn %s %s", circuit_value, self._data_key) + await self.coordinator.async_request_refresh() + else: + _LOGGER.info("screenlogic turn %s %s error", circuit_value, self._data_key) + + @property + def circuit(self): + """Shortcut to access the circuit.""" + return self.circuits_data[self._data_key] + + @property + def circuits_data(self): + """Shortcut to access the circuits data.""" + return self.coordinator.data["circuits"] diff --git a/homeassistant/components/screenlogic/translations/en.json b/homeassistant/components/screenlogic/translations/en.json new file mode 100644 index 00000000000000..2572fdf38fa190 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/en.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP Address", + "port": "Port" + }, + "description": "Enter your ScreenLogic Gateway information.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "The following ScreenLogic gateways were discovered. Please select one to configure, or choose to manually configure a ScreenLogic gateway.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconds between scans" + }, + "description": "Specify settings for {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/water_heater.py b/homeassistant/components/screenlogic/water_heater.py new file mode 100644 index 00000000000000..2a0a8e82c8003d --- /dev/null +++ b/homeassistant/components/screenlogic/water_heater.py @@ -0,0 +1,128 @@ +"""Support for a ScreenLogic Water Heater.""" +import logging + +from screenlogicpy.const import HEAT_MODE + +from homeassistant.components.water_heater import ( + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + WaterHeaterEntity, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE + +HEAT_MODE_NAMES = HEAT_MODE.Names + +MODE_NAME_TO_MODE_NUM = { + HEAT_MODE_NAMES[num]: num for num in range(len(HEAT_MODE_NAMES)) +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for body in data["devices"]["water_heater"]: + entities.append(ScreenLogicWaterHeater(coordinator, body)) + async_add_entities(entities, True) + + +class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity): + """Represents the heating functions for a body of water.""" + + @property + def name(self) -> str: + """Name of the water heater.""" + ent_name = self.body["heat_status"]["name"] + return f"{self.gateway_name} {ent_name}" + + @property + def state(self) -> str: + """State of the water heater.""" + return HEAT_MODE.GetFriendlyName(self.body["heat_status"]["value"]) + + @property + def min_temp(self) -> float: + """Minimum allowed temperature.""" + return self.body["min_set_point"]["value"] + + @property + def max_temp(self) -> float: + """Maximum allowed temperature.""" + return self.body["max_set_point"]["value"] + + @property + def current_temperature(self) -> float: + """Return water temperature.""" + return self.body["last_temperature"]["value"] + + @property + def target_temperature(self) -> float: + """Target temperature.""" + return self.body["heat_set_point"]["value"] + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement.""" + if self.config_data["is_celcius"]["value"] == 1: + return TEMP_CELSIUS + return TEMP_FAHRENHEIT + + @property + def current_operation(self) -> str: + """Return operation.""" + return HEAT_MODE_NAMES[self.body["heat_mode"]["value"]] + + @property + def operation_list(self): + """All available operations.""" + supported_heat_modes = [HEAT_MODE.OFF] + # Is solar listed as available equipment? + if self.coordinator.data["config"]["equipment_flags"] & 0x1: + supported_heat_modes.extend([HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERED]) + supported_heat_modes.append(HEAT_MODE.HEATER) + + return [HEAT_MODE_NAMES[mode_num] for mode_num in supported_heat_modes] + + @property + def supported_features(self): + """Supported features of the water heater.""" + return SUPPORTED_FEATURES + + async def async_set_temperature(self, **kwargs) -> None: + """Change the setpoint of the heater.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if await self.hass.async_add_executor_job( + self.gateway.set_heat_temp, int(self._data_key), int(temperature) + ): + await self.coordinator.async_request_refresh() + else: + _LOGGER.error("screenlogic set_temperature error") + + async def async_set_operation_mode(self, operation_mode) -> None: + """Set the operation mode.""" + mode = MODE_NAME_TO_MODE_NUM[operation_mode] + if await self.hass.async_add_executor_job( + self.gateway.set_heat_mode, int(self._data_key), int(mode) + ): + await self.coordinator.async_request_refresh() + else: + _LOGGER.error("screenlogic set_operation_mode error") + + @property + def body(self): + """Shortcut to access body data.""" + return self.bodies_data[self._data_key] + + @property + def bodies_data(self): + """Shortcut to access bodies data.""" + return self.coordinator.data["bodies"] diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a3bcef9047f26d..b6799f59a0408a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -195,6 +195,7 @@ "rpi_power", "ruckus_unleashed", "samsungtv", + "screenlogic", "sense", "sentry", "sharkiq", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b3e10c906210d1..b5d419662ffb70 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -109,6 +109,11 @@ "hostname": "irobot-*", "macaddress": "501479*" }, + { + "domain": "screenlogic", + "hostname": "pentair: *", + "macaddress": "00C033*" + }, { "domain": "sense", "hostname": "sense-*", diff --git a/requirements_all.txt b/requirements_all.txt index 1f0cc6ab7ca7f3..a3788d7493a54b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2008,6 +2008,9 @@ scapy==2.4.4 # homeassistant.components.deutsche_bahn schiene==0.23 +# homeassistant.components.screenlogic +screenlogicpy==0.1.2 + # homeassistant.components.scsgate scsgate==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7dd403f6bd1a7e..216a7e0c0b26cb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1039,6 +1039,9 @@ samsungtvws==1.6.0 # homeassistant.components.dhcp scapy==2.4.4 +# homeassistant.components.screenlogic +screenlogicpy==0.1.2 + # homeassistant.components.emulated_kasa # homeassistant.components.sense sense_energy==0.9.0 diff --git a/tests/components/screenlogic/__init__.py b/tests/components/screenlogic/__init__.py new file mode 100644 index 00000000000000..ad2b82960f0662 --- /dev/null +++ b/tests/components/screenlogic/__init__.py @@ -0,0 +1 @@ +"""Tests for the Screenlogic integration.""" diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py new file mode 100644 index 00000000000000..6d2c1ee2595d52 --- /dev/null +++ b/tests/components/screenlogic/test_config_flow.py @@ -0,0 +1,249 @@ +"""Test the Pentair ScreenLogic config flow.""" +from unittest.mock import patch + +from screenlogicpy import ScreenLogicError +from screenlogicpy.const import ( + SL_GATEWAY_IP, + SL_GATEWAY_NAME, + SL_GATEWAY_PORT, + SL_GATEWAY_SUBTYPE, + SL_GATEWAY_TYPE, +) + +from homeassistant import config_entries, setup +from homeassistant.components.dhcp import HOSTNAME, IP_ADDRESS +from homeassistant.components.screenlogic.config_flow import ( + GATEWAY_MANUAL_ENTRY, + GATEWAY_SELECT_KEY, +) +from homeassistant.components.screenlogic.const import ( + DEFAULT_SCAN_INTERVAL, + DOMAIN, + MIN_SCAN_INTERVAL, +) +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL + +from tests.common import MockConfigEntry + + +async def test_flow_discovery(hass): + """Test the flow works with basic discovery.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + return_value=[ + { + SL_GATEWAY_IP: "1.1.1.1", + SL_GATEWAY_PORT: 80, + SL_GATEWAY_TYPE: 12, + SL_GATEWAY_SUBTYPE: 2, + SL_GATEWAY_NAME: "Pentair: 01-01-01", + }, + ], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_select" + + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={GATEWAY_SELECT_KEY: "00:c0:33:01:01:01"} + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "Pentair: 01-01-01" + assert result2["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_flow_discover_none(hass): + """Test when nothing is discovered.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + return_value=[], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_entry" + + +async def test_flow_discover_error(hass): + """Test when discovery errors.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + side_effect=ScreenLogicError("Fake error"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_entry" + + +async def test_dhcp(hass): + """Test DHCP discovery flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "dhcp"}, + data={ + HOSTNAME: "Pentair: 01-01-01", + IP_ADDRESS: "1.1.1.1", + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "gateway_entry" + + +async def test_form_manual_entry(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + with patch( + "homeassistant.components.screenlogic.config_flow.discover", + return_value=[ + { + SL_GATEWAY_IP: "1.1.1.1", + SL_GATEWAY_PORT: 80, + SL_GATEWAY_TYPE: 12, + SL_GATEWAY_SUBTYPE: 2, + SL_GATEWAY_NAME: "Pentair: 01-01-01", + }, + ], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + assert result["step_id"] == "gateway_select" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={GATEWAY_SELECT_KEY: GATEWAY_MANUAL_ENTRY} + ) + + assert result2["type"] == "form" + assert result2["errors"] == {} + assert result2["step_id"] == "gateway_entry" + + with patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=True, + ), patch( + "homeassistant.components.screenlogic.config_flow.login.gateway_connect", + return_value="00-C0-33-01-01-01", + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "Pentair: 01-01-01" + assert result3["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + + +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( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=None, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + +async def test_option_flow(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_SCAN_INTERVAL: 15}, + ) + assert result["type"] == "create_entry" + assert result["data"] == {CONF_SCAN_INTERVAL: 15} + + +async def test_option_flow_defaults(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == "create_entry" + assert result["data"] == { + CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, + } + + +async def test_option_flow_input_floor(hass): + """Test config flow options.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry.add_to_hass(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1} + ) + assert result["type"] == "create_entry" + assert result["data"] == { + CONF_SCAN_INTERVAL: MIN_SCAN_INTERVAL, + } From 529b23d8afe6cd79952232de5d858c249256cb1a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 16 Mar 2021 23:09:35 +0000 Subject: [PATCH 1314/1818] Bump frontend to 20210316.0 --- 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 9d6bf462a48903..ed97d0718e7ae2 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==20210314.0" + "home-assistant-frontend==20210316.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8bb7d1cbad5ab6..a8ad4ff85f900d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210314.0 +home-assistant-frontend==20210316.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index a3788d7493a54b..e685876c09d91b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210314.0 +home-assistant-frontend==20210316.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 216a7e0c0b26cb..00e6121b4854be 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210314.0 +home-assistant-frontend==20210316.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 4306c8fbb44de71f3870a3bb4327d9f486a8a18d Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 17 Mar 2021 00:03:55 +0000 Subject: [PATCH 1315/1818] [ci skip] Translation update --- .../components/adguard/translations/ru.json | 2 +- .../components/aemet/translations/de.json | 7 ++- .../components/airly/translations/nl.json | 4 +- .../components/almond/translations/no.json | 2 +- .../ambiclimate/translations/nl.json | 2 +- .../components/asuswrt/translations/de.json | 19 ++++++- .../components/asuswrt/translations/ru.json | 2 +- .../components/august/translations/en.json | 10 ++++ .../components/august/translations/ru.json | 2 +- .../components/awair/translations/nl.json | 3 +- .../components/axis/translations/nl.json | 4 +- .../components/axis/translations/ru.json | 2 +- .../components/blink/translations/it.json | 2 +- .../components/blink/translations/ru.json | 2 +- .../bmw_connected_drive/translations/ru.json | 2 +- .../components/bond/translations/de.json | 5 ++ .../components/bsblan/translations/ru.json | 2 +- .../components/canary/translations/ru.json | 2 +- .../components/cast/translations/pt.json | 3 ++ .../cert_expiry/translations/nl.json | 4 +- .../components/climacell/translations/de.json | 16 +++++- .../cloudflare/translations/nl.json | 1 + .../components/control4/translations/de.json | 10 ++++ .../components/control4/translations/ru.json | 2 +- .../components/deconz/translations/nl.json | 4 +- .../components/denonavr/translations/nl.json | 11 +++- .../components/dexcom/translations/de.json | 1 + .../components/dexcom/translations/ru.json | 2 +- .../components/doorbird/translations/ru.json | 2 +- .../components/elkm1/translations/ru.json | 2 +- .../components/enocean/translations/nl.json | 17 ++++++ .../fireservicerota/translations/ru.json | 2 +- .../flick_electric/translations/ru.json | 2 +- .../components/flo/translations/ru.json | 2 +- .../components/flume/translations/ru.json | 2 +- .../forked_daapd/translations/nl.json | 1 + .../components/foscam/translations/de.json | 1 + .../components/foscam/translations/ru.json | 2 +- .../components/fritzbox/translations/ru.json | 6 +-- .../fritzbox_callmonitor/translations/ru.json | 2 +- .../garmin_connect/translations/ru.json | 2 +- .../components/glances/translations/nl.json | 8 +-- .../components/glances/translations/ru.json | 2 +- .../components/gogogate2/translations/ru.json | 2 +- .../components/hangouts/translations/it.json | 2 +- .../components/hassio/translations/it.json | 4 +- .../components/hive/translations/de.json | 42 +++++++++++++-- .../components/hive/translations/it.json | 53 +++++++++++++++++++ .../components/hive/translations/nl.json | 49 +++++++++++++++++ .../components/hive/translations/no.json | 53 +++++++++++++++++++ .../components/hive/translations/pl.json | 53 +++++++++++++++++++ .../components/hive/translations/pt.json | 3 ++ .../components/hive/translations/ru.json | 4 +- .../components/hlk_sw16/translations/ru.json | 2 +- .../homeassistant/translations/it.json | 4 +- .../components/homekit/translations/nl.json | 1 + .../homekit_controller/translations/nl.json | 4 +- .../huawei_lte/translations/nl.json | 3 +- .../huawei_lte/translations/ru.json | 6 +-- .../components/hue/translations/nl.json | 2 +- .../huisbaasje/translations/ru.json | 2 +- .../humidifier/translations/de.json | 7 +++ .../humidifier/translations/nl.json | 4 +- .../hvv_departures/translations/ru.json | 2 +- .../components/iaqualink/translations/nl.json | 2 +- .../components/iaqualink/translations/ru.json | 4 +- .../components/insteon/translations/ru.json | 4 +- .../components/isy994/translations/ru.json | 2 +- .../components/izone/translations/nl.json | 4 +- .../keenetic_ndms2/translations/de.json | 9 ++++ .../keenetic_ndms2/translations/ru.json | 2 +- .../components/kmtronic/translations/ru.json | 2 +- .../components/kodi/translations/ru.json | 2 +- .../components/life360/translations/ru.json | 4 +- .../components/litejet/translations/de.json | 7 ++- .../litterrobot/translations/pt.json | 3 ++ .../litterrobot/translations/ru.json | 2 +- .../components/mikrotik/translations/ru.json | 2 +- .../components/mill/translations/ru.json | 2 +- .../components/mqtt/translations/ru.json | 4 +- .../components/mullvad/translations/de.json | 3 +- .../components/mullvad/translations/ru.json | 2 +- .../components/myq/translations/ru.json | 2 +- .../components/mysensors/translations/de.json | 40 +++++++++++++- .../components/neato/translations/ru.json | 2 +- .../components/nest/translations/it.json | 2 +- .../components/netatmo/translations/de.json | 1 + .../components/nexia/translations/ru.json | 2 +- .../components/notion/translations/nl.json | 2 +- .../components/notion/translations/ru.json | 2 +- .../components/nuheat/translations/ru.json | 2 +- .../components/number/translations/de.json | 5 ++ .../components/nut/translations/pt.json | 6 +++ .../components/nut/translations/ru.json | 2 +- .../components/nzbget/translations/ru.json | 2 +- .../components/omnilogic/translations/ru.json | 2 +- .../components/onvif/translations/ru.json | 2 +- .../ovo_energy/translations/ru.json | 2 +- .../philips_js/translations/ca.json | 7 +++ .../philips_js/translations/de.json | 10 ++++ .../philips_js/translations/en.json | 7 +++ .../philips_js/translations/ko.json | 5 ++ .../philips_js/translations/pt.json | 14 +++++ .../components/plaato/translations/de.json | 28 ++++++++++ .../components/plaato/translations/nl.json | 4 +- .../components/plex/translations/nl.json | 8 +-- .../components/plugwise/translations/ru.json | 2 +- .../components/rfxtrx/translations/nl.json | 13 +++-- .../components/ring/translations/ru.json | 2 +- .../components/risco/translations/ru.json | 2 +- .../ruckus_unleashed/translations/ru.json | 2 +- .../components/sensor/translations/de.json | 4 ++ .../components/sharkiq/translations/ru.json | 4 +- .../components/shelly/translations/ru.json | 2 +- .../smart_meter_texas/translations/ru.json | 2 +- .../components/smarttub/translations/de.json | 4 +- .../components/solaredge/translations/nl.json | 2 +- .../components/solarlog/translations/nl.json | 4 +- .../components/somfy/translations/nl.json | 4 +- .../components/spider/translations/ru.json | 2 +- .../squeezebox/translations/nl.json | 4 +- .../squeezebox/translations/ru.json | 2 +- .../srp_energy/translations/ru.json | 2 +- .../components/starline/translations/ru.json | 4 +- .../components/subaru/translations/de.json | 7 ++- .../components/subaru/translations/it.json | 4 +- .../components/subaru/translations/pt.json | 21 ++++++++ .../components/subaru/translations/ru.json | 2 +- .../components/syncthru/translations/nl.json | 1 + .../synology_dsm/translations/ru.json | 4 +- .../components/tado/translations/ru.json | 2 +- .../components/tasmota/translations/nl.json | 3 ++ .../components/toon/translations/nl.json | 12 +++++ .../totalconnect/translations/de.json | 1 + .../totalconnect/translations/pt.json | 11 +++- .../totalconnect/translations/ru.json | 2 +- .../components/tradfri/translations/nl.json | 2 +- .../transmission/translations/nl.json | 6 ++- .../transmission/translations/ru.json | 2 +- .../components/tuya/translations/nl.json | 3 ++ .../components/tuya/translations/ru.json | 2 +- .../components/unifi/translations/ru.json | 2 +- .../components/upcloud/translations/nl.json | 9 ++++ .../components/upcloud/translations/ru.json | 2 +- .../components/verisure/translations/ca.json | 12 ++++- .../components/verisure/translations/de.json | 44 +++++++++++++++ .../components/verisure/translations/et.json | 10 +++- .../components/verisure/translations/it.json | 47 ++++++++++++++++ .../components/verisure/translations/ko.json | 47 ++++++++++++++++ .../components/verisure/translations/nl.json | 46 ++++++++++++++++ .../components/verisure/translations/no.json | 47 ++++++++++++++++ .../components/verisure/translations/pl.json | 47 ++++++++++++++++ .../components/verisure/translations/pt.json | 17 +++++- .../components/verisure/translations/ru.json | 47 ++++++++++++++++ .../verisure/translations/zh-Hant.json | 47 ++++++++++++++++ .../components/vesync/translations/nl.json | 2 +- .../water_heater/translations/de.json | 11 ++++ .../water_heater/translations/it.json | 11 ++++ .../water_heater/translations/nl.json | 10 ++++ .../water_heater/translations/no.json | 11 ++++ .../water_heater/translations/pl.json | 11 ++++ .../water_heater/translations/pt.json | 5 ++ .../water_heater/translations/zh-Hant.json | 11 ++++ .../components/wemo/translations/nl.json | 4 +- .../components/withings/translations/nl.json | 1 + .../components/wolflink/translations/ru.json | 2 +- .../xiaomi_aqara/translations/de.json | 6 +++ .../xiaomi_aqara/translations/nl.json | 4 +- .../zoneminder/translations/ru.json | 6 +-- .../components/zwave_js/translations/de.json | 26 ++++++++- 170 files changed, 1233 insertions(+), 160 deletions(-) create mode 100644 homeassistant/components/hive/translations/it.json create mode 100644 homeassistant/components/hive/translations/nl.json create mode 100644 homeassistant/components/hive/translations/no.json create mode 100644 homeassistant/components/hive/translations/pl.json create mode 100644 homeassistant/components/philips_js/translations/pt.json create mode 100644 homeassistant/components/subaru/translations/pt.json create mode 100644 homeassistant/components/verisure/translations/de.json create mode 100644 homeassistant/components/verisure/translations/it.json create mode 100644 homeassistant/components/verisure/translations/ko.json create mode 100644 homeassistant/components/verisure/translations/nl.json create mode 100644 homeassistant/components/verisure/translations/no.json create mode 100644 homeassistant/components/verisure/translations/pl.json create mode 100644 homeassistant/components/verisure/translations/ru.json create mode 100644 homeassistant/components/verisure/translations/zh-Hant.json diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index 5e8483047f830c..97dc6505c3bf58 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -18,7 +18,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "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": "\u041b\u043e\u0433\u0438\u043d", + "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" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard Home." diff --git a/homeassistant/components/aemet/translations/de.json b/homeassistant/components/aemet/translations/de.json index d7254aea92f600..d5312805722964 100644 --- a/homeassistant/components/aemet/translations/de.json +++ b/homeassistant/components/aemet/translations/de.json @@ -11,8 +11,11 @@ "data": { "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", - "longitude": "L\u00e4ngengrad" - } + "longitude": "L\u00e4ngengrad", + "name": "Name der Integration" + }, + "description": "Richte die AEMET OpenData Integration ein. Um den API-Schl\u00fcssel zu generieren, besuche https://opendata.aemet.es/centrodedescargas/altaUsuario", + "title": "[void]" } } } diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index 5ea975dfaeffb3..e89f769cd83123 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -10,10 +10,10 @@ "step": { "user": { "data": { - "api_key": "Airly API-sleutel", + "api_key": "API-sleutel", "latitude": "Breedtegraad", "longitude": "Lengtegraad", - "name": "Naam van de integratie" + "name": "Naam" }, "description": "Airly-integratie van luchtkwaliteit instellen. Ga naar https://developer.airly.eu/register om de API-sleutel te genereren", "title": "Airly" diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index e6ca8f165897ab..84a57a42ff71a0 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -8,7 +8,7 @@ }, "step": { "hassio_confirm": { - "description": "Vil du konfigurere Home Assistant for \u00e5 koble til Mandel levert av Hass.io-tillegget: {addon} ?", + "description": "Vil du konfigurere Home Assistant til \u00e5 koble til Almond levert av Supervisor-tillegg: {addon}?", "title": "Almond via Hass.io-tillegg" }, "pick_implementation": { diff --git a/homeassistant/components/ambiclimate/translations/nl.json b/homeassistant/components/ambiclimate/translations/nl.json index 1d7652a370ecb7..4e6c5ebb202d78 100644 --- a/homeassistant/components/ambiclimate/translations/nl.json +++ b/homeassistant/components/ambiclimate/translations/nl.json @@ -6,7 +6,7 @@ "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen." }, "create_entry": { - "default": "Succesvol geverifieerd met Ambiclimate" + "default": "Succesvol geauthenticeerd" }, "error": { "follow_link": "Gelieve de link te volgen en te verifi\u00ebren voordat u op Verzenden drukt.", diff --git a/homeassistant/components/asuswrt/translations/de.json b/homeassistant/components/asuswrt/translations/de.json index 433bf17b8143a9..36699d9575379a 100644 --- a/homeassistant/components/asuswrt/translations/de.json +++ b/homeassistant/components/asuswrt/translations/de.json @@ -6,6 +6,9 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_host": "Ung\u00fcltiger Hostname oder IP-Adresse", + "pwd_and_ssh": "Nur Passwort oder SSH-Schl\u00fcsseldatei angeben", + "pwd_or_ssh": "Bitte Passwort oder SSH-Schl\u00fcsseldatei angeben", + "ssh_not_file": "SSH-Schl\u00fcsseldatei nicht gefunden", "unknown": "Unerwarteter Fehler" }, "step": { @@ -16,8 +19,22 @@ "name": "Name", "password": "Passwort", "port": "Port", + "protocol": "Zu verwendendes Kommunikationsprotokoll", + "ssh_key": "Pfad zu deiner SSH-Schl\u00fcsseldatei (anstelle des Passworts)", "username": "Benutzername" - } + }, + "title": "" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "interface": "Schnittstelle, von der du Statistiken haben m\u00f6chtest (z.B. eth0, eth1 usw.)", + "require_ip": "Ger\u00e4te m\u00fcssen IP haben (f\u00fcr Zugangspunkt-Modus)" + }, + "title": "AsusWRT Optionen" } } } diff --git a/homeassistant/components/asuswrt/translations/ru.json b/homeassistant/components/asuswrt/translations/ru.json index 236f7642c12a64..a2090b1faf6374 100644 --- a/homeassistant/components/asuswrt/translations/ru.json +++ b/homeassistant/components/asuswrt/translations/ru.json @@ -21,7 +21,7 @@ "port": "\u041f\u043e\u0440\u0442", "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441\u0432\u044f\u0437\u0438", "ssh_key": "\u041f\u0443\u0442\u044c \u0444\u0430\u0439\u043b\u0443 \u043a\u043b\u044e\u0447\u0435\u0439 SSH (\u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0430\u0440\u043e\u043b\u044f)", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0440\u043e\u0443\u0442\u0435\u0440\u0443.", "title": "AsusWRT" diff --git a/homeassistant/components/august/translations/en.json b/homeassistant/components/august/translations/en.json index f2ceef78d48653..0b8d1511244f3a 100644 --- a/homeassistant/components/august/translations/en.json +++ b/homeassistant/components/august/translations/en.json @@ -17,6 +17,16 @@ "description": "Enter the password for {username}.", "title": "Reauthenticate an August account" }, + "user": { + "data": { + "login_method": "Login Method", + "password": "Password", + "timeout": "Timeout (seconds)", + "username": "Username" + }, + "description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.", + "title": "Setup an August account" + }, "user_validate": { "data": { "login_method": "Login Method", diff --git a/homeassistant/components/august/translations/ru.json b/homeassistant/components/august/translations/ru.json index 97dba8fc758e55..277fd6abec28e8 100644 --- a/homeassistant/components/august/translations/ru.json +++ b/homeassistant/components/august/translations/ru.json @@ -15,7 +15,7 @@ "login_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 '+NNNNNNNNN'.", "title": "August" diff --git a/homeassistant/components/awair/translations/nl.json b/homeassistant/components/awair/translations/nl.json index 5d20aed2fdb878..d41b85cc09b1fb 100644 --- a/homeassistant/components/awair/translations/nl.json +++ b/homeassistant/components/awair/translations/nl.json @@ -21,7 +21,8 @@ "data": { "access_token": "Toegangstoken", "email": "E-mail" - } + }, + "description": "U moet zich registreren voor een Awair-toegangstoken voor ontwikkelaars op: https://developer.getawair.com/onboard/login" } } } diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 8ba305366f9d16..3b41c1184ba915 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -7,11 +7,11 @@ }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie" }, - "flow_title": "Axis apparaat: {name} ({host})", + "flow_title": "{name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index 1bf3e369b65a3d..f5c5e79a32fe8a 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -18,7 +18,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Axis" } diff --git a/homeassistant/components/blink/translations/it.json b/homeassistant/components/blink/translations/it.json index bdb0ba3f6b4350..6dee5d9c02fe52 100644 --- a/homeassistant/components/blink/translations/it.json +++ b/homeassistant/components/blink/translations/it.json @@ -14,7 +14,7 @@ "data": { "2fa": "Codice a due fattori" }, - "description": "Inserisci il pin inviato alla tua email", + "description": "Inserisci il PIN inviato alla tua email", "title": "Autenticazione a due fattori" }, "user": { diff --git a/homeassistant/components/blink/translations/ru.json b/homeassistant/components/blink/translations/ru.json index 0835ab5ac0af2e..fa68ee2dad46fa 100644 --- a/homeassistant/components/blink/translations/ru.json +++ b/homeassistant/components/blink/translations/ru.json @@ -20,7 +20,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Blink" } diff --git a/homeassistant/components/bmw_connected_drive/translations/ru.json b/homeassistant/components/bmw_connected_drive/translations/ru.json index 9ac76bbea9e35d..8ab4e4e1207000 100644 --- a/homeassistant/components/bmw_connected_drive/translations/ru.json +++ b/homeassistant/components/bmw_connected_drive/translations/ru.json @@ -12,7 +12,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "region": "\u0420\u0435\u0433\u0438\u043e\u043d ConnectedDrive", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/bond/translations/de.json b/homeassistant/components/bond/translations/de.json index 14f86a30bb285f..1c1c7375a28b06 100644 --- a/homeassistant/components/bond/translations/de.json +++ b/homeassistant/components/bond/translations/de.json @@ -9,6 +9,11 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "confirm": { + "data": { + "access_token": "Zugangstoken" + } + }, "user": { "data": { "access_token": "Zugriffstoken", diff --git a/homeassistant/components/bsblan/translations/ru.json b/homeassistant/components/bsblan/translations/ru.json index 76aa715a9de7c7..8291a20d3078a0 100644 --- a/homeassistant/components/bsblan/translations/ru.json +++ b/homeassistant/components/bsblan/translations/ru.json @@ -14,7 +14,7 @@ "passkey": "\u041f\u0430\u0440\u043e\u043b\u044c", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 BSB-Lan.", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/canary/translations/ru.json b/homeassistant/components/canary/translations/ru.json index 146863cf768ba2..51052d0d68d4ba 100644 --- a/homeassistant/components/canary/translations/ru.json +++ b/homeassistant/components/canary/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Canary" } diff --git a/homeassistant/components/cast/translations/pt.json b/homeassistant/components/cast/translations/pt.json index 32da71ec389d80..2a5b62a9de190d 100644 --- a/homeassistant/components/cast/translations/pt.json +++ b/homeassistant/components/cast/translations/pt.json @@ -5,6 +5,9 @@ "single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do Google Cast \u00e9 necess\u00e1ria." }, "step": { + "config": { + "title": "Google Cast" + }, "confirm": { "description": "Deseja configurar o Google Cast?" } diff --git a/homeassistant/components/cert_expiry/translations/nl.json b/homeassistant/components/cert_expiry/translations/nl.json index b29cdcaad71ab7..e3cd3d7983beb0 100644 --- a/homeassistant/components/cert_expiry/translations/nl.json +++ b/homeassistant/components/cert_expiry/translations/nl.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "host": "De hostnaam van het certificaat", + "host": "Host", "name": "De naam van het certificaat", - "port": "De poort van het certificaat" + "port": "Poort" }, "title": "Het certificaat defini\u00ebren dat moet worden getest" } diff --git a/homeassistant/components/climacell/translations/de.json b/homeassistant/components/climacell/translations/de.json index a91e222b3cdc11..7ec41d017337c0 100644 --- a/homeassistant/components/climacell/translations/de.json +++ b/homeassistant/components/climacell/translations/de.json @@ -3,6 +3,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "rate_limited": "Aktuelle Aktualisierungsrate gedrosselt, bitte versuche es sp\u00e4ter erneut.", "unknown": "Unerwarteter Fehler" }, "step": { @@ -12,7 +13,20 @@ "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad", "name": "Name" - } + }, + "description": "Wenn Breitengrad und L\u00e4ngengrad nicht angegeben werden, werden die Standardwerte in der Home Assistant-Konfiguration verwendet. F\u00fcr jeden Vorhersagetyp wird eine Entit\u00e4t erstellt, aber nur die von Ihnen ausgew\u00e4hlten werden standardm\u00e4\u00dfig aktiviert." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "Vorhersage Arten", + "timestep": "Minuten zwischen den Kurzvorhersagen" + }, + "description": "Wenn du die Vorhersage-Entitit\u00e4t \"Kurzvorhersage\" aktivierst, kannst du die Anzahl der Minuten zwischen den einzelnen Vorhersagen konfigurieren. Die Anzahl der bereitgestellten Vorhersagen h\u00e4ngt von der Anzahl der zwischen den Vorhersagen gew\u00e4hlten Minuten ab.", + "title": "Aktualisiere ClimaCell-Optionen" } } }, diff --git a/homeassistant/components/cloudflare/translations/nl.json b/homeassistant/components/cloudflare/translations/nl.json index e4f6c1180e264a..94697419ff1e90 100644 --- a/homeassistant/components/cloudflare/translations/nl.json +++ b/homeassistant/components/cloudflare/translations/nl.json @@ -21,6 +21,7 @@ "data": { "api_token": "API-token" }, + "description": "Voor deze integratie is een API-token vereist dat is gemaakt met Zone:Zone:Lezen en Zone:DNS:Bewerk machtigingen voor alle zones in uw account.", "title": "Verbinden met Cloudflare" }, "zone": { diff --git a/homeassistant/components/control4/translations/de.json b/homeassistant/components/control4/translations/de.json index 399b8d424911e1..e50e2499320d4b 100644 --- a/homeassistant/components/control4/translations/de.json +++ b/homeassistant/components/control4/translations/de.json @@ -14,6 +14,16 @@ "host": "IP-Addresse", "password": "Passwort", "username": "Benutzername" + }, + "description": "Bitte gib deine Control4-Kontodaten und die IP-Adresse deiner lokalen Steuerung ein." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunden zwischen Updates" } } } diff --git a/homeassistant/components/control4/translations/ru.json b/homeassistant/components/control4/translations/ru.json index 3882f03cb32c8c..41a033c037643b 100644 --- a/homeassistant/components/control4/translations/ru.json +++ b/homeassistant/components/control4/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Control4 \u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0412\u0430\u0448\u0435\u0433\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430." } diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 37833352f91d3e..833050eaf921fc 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Bridge is al geconfigureerd", - "already_in_progress": "Configuratiestroom voor bridge wordt al ingesteld.", + "already_in_progress": "De configuratiestroom is al aan de gang", "no_bridges": "Geen deCONZ apparaten ontdekt", "no_hardware_available": "Geen radiohardware aangesloten op deCONZ", "not_deconz_bridge": "Dit is geen deCONZ bridge", @@ -14,7 +14,7 @@ "flow_title": "deCONZ Zigbee gateway ( {host} )", "step": { "hassio_confirm": { - "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de Supervisor add-on {addon}?", + "description": "Wilt u Home Assistant configureren om verbinding te maken met de deCONZ gateway van de Supervisor add-on {addon}?", "title": "deCONZ Zigbee Gateway via Supervisor add-on" }, "link": { diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index d96e81f232217a..a109df0f1259fa 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -3,11 +3,16 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", - "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen" + "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen", + "not_denonavr_missing": "Geen Denon AVR netwerkontvanger, zoekinformatie niet compleet" + }, + "error": { + "discovery_error": "Kan een Denon AVR netwerkontvanger niet vinden" }, "flow_title": "Denon AVR Network Receiver: {name}", "step": { "confirm": { + "description": "Bevestig het toevoegen van de ontvanger", "title": "Denon AVR Network Receivers" }, "select": { @@ -21,7 +26,8 @@ "data": { "host": "IP-adres" }, - "description": "Maak verbinding met uw ontvanger. Als het IP-adres niet is ingesteld, wordt automatische detectie gebruikt" + "description": "Maak verbinding met uw ontvanger. Als het IP-adres niet is ingesteld, wordt automatische detectie gebruikt", + "title": "Denon AVR Netwerk Ontvangers" } } }, @@ -33,6 +39,7 @@ "zone2": "Stel Zone 2 in", "zone3": "Stel Zone 3 in" }, + "description": "Optionele instellingen opgeven", "title": "Denon AVR Network Receivers" } } diff --git a/homeassistant/components/dexcom/translations/de.json b/homeassistant/components/dexcom/translations/de.json index d567dd6b611167..64cdc05a081086 100644 --- a/homeassistant/components/dexcom/translations/de.json +++ b/homeassistant/components/dexcom/translations/de.json @@ -12,6 +12,7 @@ "user": { "data": { "password": "Passwort", + "server": "Server", "username": "Benutzername" } } diff --git a/homeassistant/components/dexcom/translations/ru.json b/homeassistant/components/dexcom/translations/ru.json index aa90d6d998dac6..08543cf103ed04 100644 --- a/homeassistant/components/dexcom/translations/ru.json +++ b/homeassistant/components/dexcom/translations/ru.json @@ -13,7 +13,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "server": "\u0421\u0435\u0440\u0432\u0435\u0440", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", "title": "Dexcom" diff --git a/homeassistant/components/doorbird/translations/ru.json b/homeassistant/components/doorbird/translations/ru.json index 5e376ee56d3086..4d5695a3ab2367 100644 --- a/homeassistant/components/doorbird/translations/ru.json +++ b/homeassistant/components/doorbird/translations/ru.json @@ -17,7 +17,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a DoorBird" } diff --git a/homeassistant/components/elkm1/translations/ru.json b/homeassistant/components/elkm1/translations/ru.json index 48a950f1cca17e..954722ecf5267e 100644 --- a/homeassistant/components/elkm1/translations/ru.json +++ b/homeassistant/components/elkm1/translations/ru.json @@ -17,7 +17,7 @@ "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": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "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" diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json index 79aaec23123a61..27e4091552a8bd 100644 --- a/homeassistant/components/enocean/translations/nl.json +++ b/homeassistant/components/enocean/translations/nl.json @@ -1,7 +1,24 @@ { "config": { "abort": { + "invalid_dongle_path": "Ongeldig dongle-pad", "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "error": { + "invalid_dongle_path": "Geen geldige dongle gevonden voor dit pad" + }, + "step": { + "detect": { + "data": { + "path": "USB dongle pad" + }, + "title": "Selecteer het pad naar uw ENOcean-dongle" + }, + "manual": { + "data": { + "path": "USB-dongle-pad" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/fireservicerota/translations/ru.json b/homeassistant/components/fireservicerota/translations/ru.json index 3955172e02da95..046a65081ec61b 100644 --- a/homeassistant/components/fireservicerota/translations/ru.json +++ b/homeassistant/components/fireservicerota/translations/ru.json @@ -21,7 +21,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "url": "\u0412\u0435\u0431-\u0441\u0430\u0439\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/flick_electric/translations/ru.json b/homeassistant/components/flick_electric/translations/ru.json index bcabe2f2157353..08bfc3ffb02ab9 100644 --- a/homeassistant/components/flick_electric/translations/ru.json +++ b/homeassistant/components/flick_electric/translations/ru.json @@ -14,7 +14,7 @@ "client_id": "ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Flick Electric" } diff --git a/homeassistant/components/flo/translations/ru.json b/homeassistant/components/flo/translations/ru.json index 9e0db9fcf94dd8..9b02cafd466691 100644 --- a/homeassistant/components/flo/translations/ru.json +++ b/homeassistant/components/flo/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/flume/translations/ru.json b/homeassistant/components/flume/translations/ru.json index e4be913abcdfcd..757ec6e5226e33 100644 --- a/homeassistant/components/flume/translations/ru.json +++ b/homeassistant/components/flume/translations/ru.json @@ -14,7 +14,7 @@ "client_id": "ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430", "client_secret": "\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u043c\u0443 API Flume, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c 'ID \u043a\u043b\u0438\u0435\u043d\u0442\u0430' \u0438 '\u0421\u0435\u043a\u0440\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430' \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 https://portal.flumetech.com/settings#token.", "title": "Flume" diff --git a/homeassistant/components/forked_daapd/translations/nl.json b/homeassistant/components/forked_daapd/translations/nl.json index 903a716d16e01a..7eec6a34571ed8 100644 --- a/homeassistant/components/forked_daapd/translations/nl.json +++ b/homeassistant/components/forked_daapd/translations/nl.json @@ -5,6 +5,7 @@ "not_forked_daapd": "Apparaat is geen forked-daapd-server." }, "error": { + "forbidden": "Niet in staat te verbinden. Controleer alstublieft uw forked-daapd netwerkrechten.", "unknown_error": "Onverwachte fout", "websocket_not_enabled": "forked-daapd server websocket niet ingeschakeld.", "wrong_host_or_port": "Verbinding mislukt, controleer het host-adres en poort.", diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json index 603be1847cc6b4..e3011f61615a42 100644 --- a/homeassistant/components/foscam/translations/de.json +++ b/homeassistant/components/foscam/translations/de.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_response": "Ung\u00fcltige Antwort vom Ger\u00e4t", "unknown": "Unerwarteter Fehler" }, "step": { diff --git a/homeassistant/components/foscam/translations/ru.json b/homeassistant/components/foscam/translations/ru.json index f78f64af69aa6d..8e8404c501e19e 100644 --- a/homeassistant/components/foscam/translations/ru.json +++ b/homeassistant/components/foscam/translations/ru.json @@ -17,7 +17,7 @@ "port": "\u041f\u043e\u0440\u0442", "rtsp_port": "\u041f\u043e\u0440\u0442 RTSP", "stream": "\u041f\u043e\u0442\u043e\u043a", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 8cd77671bd8d97..adbdfa13d6bc9c 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -15,14 +15,14 @@ "confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "reauth_confirm": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0434\u043b\u044f {name}." }, @@ -30,7 +30,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AVM FRITZ!Box." } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ru.json b/homeassistant/components/fritzbox_callmonitor/translations/ru.json index f1bcb18a2f6818..38448ac8c59828 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ru.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ru.json @@ -20,7 +20,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/garmin_connect/translations/ru.json b/homeassistant/components/garmin_connect/translations/ru.json index 49dd5c5b3bc148..066c337309f7c6 100644 --- a/homeassistant/components/garmin_connect/translations/ru.json +++ b/homeassistant/components/garmin_connect/translations/ru.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0412\u0430\u0448\u0438 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", "title": "Garmin Connect" diff --git a/homeassistant/components/glances/translations/nl.json b/homeassistant/components/glances/translations/nl.json index c2f2b9d473a5bc..6cb7fb445bbc8c 100644 --- a/homeassistant/components/glances/translations/nl.json +++ b/homeassistant/components/glances/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kan geen verbinding maken met host", + "cannot_connect": "Kan geen verbinding maken", "wrong_version": "Versie niet ondersteund (alleen 2 of 3)" }, "step": { @@ -14,9 +14,9 @@ "name": "Naam", "password": "Wachtwoord", "port": "Poort", - "ssl": "Gebruik SSL / TLS om verbinding te maken met het Glances-systeem", + "ssl": "Gebruik een SSL-certificaat", "username": "Gebruikersnaam", - "verify_ssl": "Controleer de certificering van het systeem", + "verify_ssl": "SSL-certificaat verifi\u00ebren", "version": "Glances API-versie (2 of 3)" }, "title": "Glances instellen" diff --git a/homeassistant/components/glances/translations/ru.json b/homeassistant/components/glances/translations/ru.json index 0dc8c72dc9f55e..aecffe204c8087 100644 --- a/homeassistant/components/glances/translations/ru.json +++ b/homeassistant/components/glances/translations/ru.json @@ -15,7 +15,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "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": "\u041b\u043e\u0433\u0438\u043d", + "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", "version": "\u0412\u0435\u0440\u0441\u0438\u044f API Glances (2 \u0438\u043b\u0438 3)" }, diff --git a/homeassistant/components/gogogate2/translations/ru.json b/homeassistant/components/gogogate2/translations/ru.json index 43e9f7a1b2ffe3..4efa554fc913db 100644 --- a/homeassistant/components/gogogate2/translations/ru.json +++ b/homeassistant/components/gogogate2/translations/ru.json @@ -12,7 +12,7 @@ "data": { "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 GogoGate2.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 GogoGate2 \u0438\u043b\u0438 iSmartGate" diff --git a/homeassistant/components/hangouts/translations/it.json b/homeassistant/components/hangouts/translations/it.json index 4831d51ef12c2b..3e89327ca30f0b 100644 --- a/homeassistant/components/hangouts/translations/it.json +++ b/homeassistant/components/hangouts/translations/it.json @@ -12,7 +12,7 @@ "step": { "2fa": { "data": { - "2fa": "2FA Pin" + "2fa": "2FA PIN" }, "description": "Vuoto", "title": "Autenticazione a due fattori" diff --git a/homeassistant/components/hassio/translations/it.json b/homeassistant/components/hassio/translations/it.json index 7a375d3d78da60..86d573cba40abf 100644 --- a/homeassistant/components/hassio/translations/it.json +++ b/homeassistant/components/hassio/translations/it.json @@ -8,8 +8,8 @@ "healthy": "Integrit\u00e0", "host_os": "Sistema Operativo Host", "installed_addons": "Componenti aggiuntivi installati", - "supervisor_api": "API Supervisore", - "supervisor_version": "Versione Supervisore", + "supervisor_api": "API Supervisor", + "supervisor_version": "Versione Supervisor", "supported": "Supportato", "update_channel": "Canale di aggiornamento", "version_api": "Versione API" diff --git a/homeassistant/components/hive/translations/de.json b/homeassistant/components/hive/translations/de.json index 1bd84f7f61cd46..bd5876bb023d26 100644 --- a/homeassistant/components/hive/translations/de.json +++ b/homeassistant/components/hive/translations/de.json @@ -1,16 +1,52 @@ { "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich", + "unknown_entry": "Vorhandener Eintrag kann nicht gefunden werden." + }, + "error": { + "invalid_code": "Anmeldung bei Hive fehlgeschlagen. Dein Zwei-Faktor-Authentifizierungscode war falsch.", + "invalid_password": "Anmeldung bei Hive fehlgeschlagen. Falsches Passwort, bitte versuche es erneut.", + "invalid_username": "Die Anmeldung bei Hive ist fehlgeschlagen. Deine E-Mail-Adresse wird nicht erkannt.", + "no_internet_available": "F\u00fcr die Verbindung mit Hive ist eine Internetverbindung erforderlich.", + "unknown": "Unerwarteter Fehler" + }, "step": { + "2fa": { + "data": { + "2fa": "Zwei-Faktor Authentifizierungscode" + }, + "description": "Gib deinen Hive-Authentifizierungscode ein. \n \nBitte gib den Code 0000 ein, um einen anderen Code anzufordern.", + "title": "Hive Zwei-Faktor-Authentifizierung." + }, "reauth": { "data": { + "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Gebe deine Hive Anmeldeinformationen erneut ein.", + "title": "Hive Anmeldung" }, "user": { "data": { - "password": "PAsswort", + "password": "Passwort", + "scan_interval": "Scanintervall (Sekunden)", "username": "Benutzername" - } + }, + "description": "Gebe deine Anmeldeinformationen und -konfiguration f\u00fcr Hive ein", + "title": "Hive Anmeldung" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scanintervall (Sekunden)" + }, + "description": "Aktualisiere den Scanintervall, um Daten \u00f6fters abzufragen.", + "title": "Optionen f\u00fcr Hive" } } } diff --git a/homeassistant/components/hive/translations/it.json b/homeassistant/components/hive/translations/it.json new file mode 100644 index 00000000000000..fd79ca35b79787 --- /dev/null +++ b/homeassistant/components/hive/translations/it.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente", + "unknown_entry": "Impossibile trovare la voce esistente." + }, + "error": { + "invalid_code": "Impossibile accedere a Hive. Il codice di autenticazione a due fattori non era corretto.", + "invalid_password": "Impossibile accedere a Hive. Password errata, riprova.", + "invalid_username": "Impossibile accedere a Hive. Il tuo indirizzo email non \u00e8 riconosciuto.", + "no_internet_available": "\u00c8 necessaria una connessione Internet per connettersi a Hive.", + "unknown": "Errore imprevisto" + }, + "step": { + "2fa": { + "data": { + "2fa": "Codice a due fattori" + }, + "description": "Inserisci il tuo codice di autenticazione Hive. \n\n Inserisci il codice 0000 per richiedere un altro codice.", + "title": "Autenticazione a due fattori di Hive." + }, + "reauth": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Inserisci nuovamente le tue informazioni di accesso a Hive.", + "title": "Accesso Hive" + }, + "user": { + "data": { + "password": "Password", + "scan_interval": "Intervallo di scansione (secondi)", + "username": "Nome utente" + }, + "description": "Immettere le informazioni di accesso e la configurazione di Hive.", + "title": "Accesso Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervallo di scansione (secondi)" + }, + "description": "Aggiorna l'intervallo di scansione per eseguire la verifica ciclica dei dati pi\u00f9 spesso.", + "title": "Opzioni per Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/nl.json b/homeassistant/components/hive/translations/nl.json new file mode 100644 index 00000000000000..96ea799c0fe8ee --- /dev/null +++ b/homeassistant/components/hive/translations/nl.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "unknown_entry": "Kan bestaand item niet vinden." + }, + "error": { + "invalid_code": "Aanmelden bij Hive is mislukt. Uw tweefactorauthenticatiecode was onjuist.", + "invalid_password": "Aanmelden bij Hive is mislukt. Onjuist wachtwoord, probeer het opnieuw.", + "invalid_username": "Aanmelden bij Hive is mislukt. Uw e-mailadres wordt niet herkend.", + "no_internet_available": "Een internetverbinding is vereist om verbinding te maken met Hive.", + "unknown": "Onverwachte fout" + }, + "step": { + "2fa": { + "data": { + "2fa": "Tweefactorauthenticatiecode" + }, + "title": "Hive tweefactorauthenticatie" + }, + "reauth": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "Voer uw Hive-aanmeldingsgegevens opnieuw in.", + "title": "Hive-aanmelding" + }, + "user": { + "data": { + "password": "Wachtwoord", + "scan_interval": "Scaninterval (seconden)", + "username": "Gebruikersnaam" + }, + "title": "Hive-aanmelding" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scaninterval (seconden)" + }, + "title": "Opties voor Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/no.json b/homeassistant/components/hive/translations/no.json new file mode 100644 index 00000000000000..c5213aafeee9a7 --- /dev/null +++ b/homeassistant/components/hive/translations/no.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket", + "unknown_entry": "Kunne ikke finne eksisterende oppf\u00f8ring." + }, + "error": { + "invalid_code": "Kunne ikke logge p\u00e5 Hive. Tofaktorautentiseringskoden din var feil.", + "invalid_password": "Kunne ikke logge p\u00e5 Hive. Feil passord. Vennligst pr\u00f8v igjen.", + "invalid_username": "Kunne ikke logge p\u00e5 Hive. E-postadressen din blir ikke gjenkjent.", + "no_internet_available": "Det kreves en internettforbindelse for \u00e5 koble til Hive.", + "unknown": "Uventet feil" + }, + "step": { + "2fa": { + "data": { + "2fa": "Totrinnsbekreftelse kode" + }, + "description": "Skriv inn din Hive-godkjenningskode. \n\n Vennligst skriv inn kode 0000 for \u00e5 be om en annen kode.", + "title": "Hive Totrinnsbekreftelse autentisering." + }, + "reauth": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Skriv innloggingsinformasjonen for Hive p\u00e5 nytt.", + "title": "Hive-p\u00e5logging" + }, + "user": { + "data": { + "password": "Passord", + "scan_interval": "Skanneintervall (sekunder)", + "username": "Brukernavn" + }, + "description": "Skriv inn inn innloggingsinformasjonen og konfigurasjonen for Hive.", + "title": "Hive-p\u00e5logging" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Skanneintervall (sekunder)" + }, + "description": "Oppdater skanneintervallet for \u00e5 avstemme etter data oftere.", + "title": "Alternativer for Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/pl.json b/homeassistant/components/hive/translations/pl.json new file mode 100644 index 00000000000000..0c61fa74febead --- /dev/null +++ b/homeassistant/components/hive/translations/pl.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119", + "unknown_entry": "Nie mo\u017cna znale\u017a\u0107 istniej\u0105cego wpisu." + }, + "error": { + "invalid_code": "Nie uda\u0142o si\u0119 zalogowa\u0107 do Hive. Tw\u00f3j kod uwierzytelniania dwusk\u0142adnikowego by\u0142 nieprawid\u0142owy.", + "invalid_password": "Nie uda\u0142o si\u0119 zalogowa\u0107 do Hive. Nieprawid\u0142owe has\u0142o, spr\u00f3buj ponownie.", + "invalid_username": "Nie uda\u0142o si\u0119 zalogowa\u0107 do Hive. Tw\u00f3j adres e-mail nie zosta\u0142 rozpoznany.", + "no_internet_available": "Do po\u0142\u0105czenia z Hive wymagane jest po\u0142\u0105czenie z internetem.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "2fa": { + "data": { + "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" + }, + "description": "Wprowad\u017a sw\u00f3j kod uwierzytelniaj\u0105cy Hive. \n\nWprowad\u017a kod 0000, aby poprosi\u0107 o kolejny kod.", + "title": "Uwierzytelnianie dwusk\u0142adnikowe Hive" + }, + "reauth": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Wprowad\u017a ponownie swoje dane logowania do Hive.", + "title": "Login Hive" + }, + "user": { + "data": { + "password": "Has\u0142o", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Wprowad\u017a dane logowania i konfiguracj\u0119 Hive.", + "title": "Login Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)" + }, + "description": "Zaktualizuj cz\u0119stotliwo\u015b\u0107 skanowania, aby cz\u0119\u015bciej sondowa\u0107 dane.", + "title": "Opcje Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hive/translations/pt.json b/homeassistant/components/hive/translations/pt.json index 46b7da5620a7d7..8397b5cb0a457e 100644 --- a/homeassistant/components/hive/translations/pt.json +++ b/homeassistant/components/hive/translations/pt.json @@ -4,6 +4,9 @@ "already_configured": "Conta j\u00e1 configurada", "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, + "error": { + "unknown": "Erro inesperado" + }, "step": { "reauth": { "data": { diff --git a/homeassistant/components/hive/translations/ru.json b/homeassistant/components/hive/translations/ru.json index 42df1bf9a244f2..02736871d240be 100644 --- a/homeassistant/components/hive/translations/ru.json +++ b/homeassistant/components/hive/translations/ru.json @@ -23,7 +23,7 @@ "reauth": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 Hive.", "title": "Hive" @@ -32,7 +32,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 Hive.", "title": "Hive" diff --git a/homeassistant/components/hlk_sw16/translations/ru.json b/homeassistant/components/hlk_sw16/translations/ru.json index 9e0db9fcf94dd8..9b02cafd466691 100644 --- a/homeassistant/components/hlk_sw16/translations/ru.json +++ b/homeassistant/components/hlk_sw16/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/homeassistant/translations/it.json b/homeassistant/components/homeassistant/translations/it.json index f3168807715245..66b8f8a1d14c85 100644 --- a/homeassistant/components/homeassistant/translations/it.json +++ b/homeassistant/components/homeassistant/translations/it.json @@ -6,13 +6,13 @@ "dev": "Sviluppo", "docker": "Docker", "docker_version": "Docker", - "hassio": "Supervisore", + "hassio": "Supervisor", "host_os": "Sistema Operativo di Home Assistant", "installation_type": "Tipo di installazione", "os_name": "Famiglia del Sistema Operativo", "os_version": "Versione del Sistema Operativo", "python_version": "Versione Python", - "supervisor": "Supervisore", + "supervisor": "Supervisor", "timezone": "Fuso orario", "version": "Versione", "virtualenv": "Ambiente virtuale" diff --git a/homeassistant/components/homekit/translations/nl.json b/homeassistant/components/homekit/translations/nl.json index dce21a4b880d63..3ba77f095a55e6 100644 --- a/homeassistant/components/homekit/translations/nl.json +++ b/homeassistant/components/homekit/translations/nl.json @@ -55,6 +55,7 @@ "entities": "Entiteiten", "mode": "Mode" }, + "description": "Kies de entiteiten die u wilt opnemen. In de accessoiremodus is slechts een enkele entiteit inbegrepen. In de bridge-include-modus worden alle entiteiten in het domein opgenomen, tenzij specifieke entiteiten zijn geselecteerd. In de bridge-uitsluitingsmodus worden alle entiteiten in het domein opgenomen, behalve de uitgesloten entiteiten. Voor de beste prestaties is elke tv-mediaspeler en camera een apart HomeKit-accessoire.", "title": "Selecteer de entiteiten die u wilt opnemen" }, "init": { diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index 46167e9da987af..5c23159e21f690 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "accessory_not_found_error": "Kan geen koppeling toevoegen omdat het apparaat niet langer kan worden gevonden.", "already_configured": "Accessoire is al geconfigureerd met deze controller.", - "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", + "already_in_progress": "De configuratiestroom is al aan de gang", "already_paired": "Dit accessoire is al gekoppeld aan een ander apparaat. Reset het accessoire en probeer het opnieuw.", "ignored_model": "HomeKit-ondersteuning voor dit model is geblokkeerd omdat er een meer functie volledige native integratie beschikbaar is.", "invalid_config_entry": "Dit apparaat geeft aan dat het gereed is om te koppelen, maar er is al een conflicterend configuratie-item voor in de Home Assistant dat eerst moet worden verwijderd.", @@ -17,7 +17,7 @@ "unable_to_pair": "Kan niet koppelen, probeer het opnieuw.", "unknown_error": "Apparaat meldde een onbekende fout. Koppelen mislukt." }, - "flow_title": "HomeKit-accessoire: {name}", + "flow_title": "{name} via HomeKit-accessoireprotocol", "step": { "max_tries_error": { "title": "Maximum aantal authenticatiepogingen overschreden" diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index d420093996c768..c1584b56330d19 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dit apparaat is reeds geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "Dit apparaat wordt al geconfigureerd", "not_huawei_lte": "Geen Huawei LTE-apparaat" }, @@ -15,6 +15,7 @@ "response_error": "Onbekende fout van het apparaat", "unknown": "Onverwachte fout" }, + "flow_title": "Huawei LTE: {name}", "step": { "user": { "data": { diff --git a/homeassistant/components/huawei_lte/translations/ru.json b/homeassistant/components/huawei_lte/translations/ru.json index c2ec20fb2598da..d3f95e3fbf1bbe 100644 --- a/homeassistant/components/huawei_lte/translations/ru.json +++ b/homeassistant/components/huawei_lte/translations/ru.json @@ -8,7 +8,7 @@ "error": { "connection_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", "incorrect_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", - "incorrect_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", + "incorrect_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", "invalid_url": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441.", "login_attempts_exceeded": "\u041f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u043f\u044b\u0442\u043e\u043a \u0432\u0445\u043e\u0434\u0430, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", @@ -21,9 +21,9 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "url": "URL-\u0430\u0434\u0440\u0435\u0441", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u043e \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0421 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437 Home Assistant, \u043a\u043e\u0433\u0434\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430, \u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442.", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043d\u043e \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0421 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u0438\u0437 Home Assistant, \u043a\u043e\u0433\u0434\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u0430, \u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442.", "title": "Huawei LTE" } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index 5cc6a3572e2a61..c2ed895dc8c228 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "Alle Philips Hue bridges zijn al geconfigureerd", "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor het apparaat is al in volle gang.", + "already_in_progress": "De configuratiestroom is al aan de gang", "cannot_connect": "Kan geen verbinding maken", "discover_timeout": "Hue bridges kunnen niet worden gevonden", "no_bridges": "Geen Philips Hue bridges ontdekt", diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json index a598320115dbcc..995e340c396b39 100644 --- a/homeassistant/components/huisbaasje/translations/ru.json +++ b/homeassistant/components/huisbaasje/translations/ru.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/humidifier/translations/de.json b/homeassistant/components/humidifier/translations/de.json index 24d8b01353e2e1..a9bd89055ef042 100644 --- a/homeassistant/components/humidifier/translations/de.json +++ b/homeassistant/components/humidifier/translations/de.json @@ -1,12 +1,19 @@ { "device_automation": { "action_type": { + "set_humidity": "Luftfeuchtigkeit f\u00fcr {entity_name} einstellen", "set_mode": "Wechsele Modus auf {entity_name}", "toggle": "{entity_name} umschalten", "turn_off": "Schalte {entity_name} aus", "turn_on": "Schalte {entity_name} an" }, + "condition_type": { + "is_mode": "{entity_name} ist auf einen bestimmten Modus festgelegt", + "is_off": "{entity_name} ist ausgeschaltet", + "is_on": "{entity_name} ist eingeschaltet" + }, "trigger_type": { + "target_humidity_changed": "{entity_name} Soll-Luftfeuchtigkeit ge\u00e4ndert", "turned_off": "{entity_name} ausgeschaltet", "turned_on": "{entity_name} eingeschaltet" } diff --git a/homeassistant/components/humidifier/translations/nl.json b/homeassistant/components/humidifier/translations/nl.json index 915b6f477d4076..9505a6a0838619 100644 --- a/homeassistant/components/humidifier/translations/nl.json +++ b/homeassistant/components/humidifier/translations/nl.json @@ -3,6 +3,7 @@ "action_type": { "set_humidity": "Luchtvochtigheid instellen voor {entity_name}", "set_mode": "Wijzig modus van {entity_name}", + "toggle": "Schakel {entity_name}", "turn_off": "{entity_name} uitschakelen", "turn_on": "{entity_name} inschakelen" }, @@ -22,5 +23,6 @@ "off": "Uit", "on": "Aan" } - } + }, + "title": "Luchtbevochtiger" } \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/ru.json b/homeassistant/components/hvv_departures/translations/ru.json index 6ae277150334b1..55e60b23c32c15 100644 --- a/homeassistant/components/hvv_departures/translations/ru.json +++ b/homeassistant/components/hvv_departures/translations/ru.json @@ -25,7 +25,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a API HVV" } diff --git a/homeassistant/components/iaqualink/translations/nl.json b/homeassistant/components/iaqualink/translations/nl.json index fae8693ce4cc06..fc5b00694a1df0 100644 --- a/homeassistant/components/iaqualink/translations/nl.json +++ b/homeassistant/components/iaqualink/translations/nl.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "Gebruikersnaam/E-mailadres" + "username": "Gebruikersnaam" }, "description": "Voer de gebruikersnaam en het wachtwoord voor uw iAqualink-account in.", "title": "Verbinding maken met iAqualink" diff --git a/homeassistant/components/iaqualink/translations/ru.json b/homeassistant/components/iaqualink/translations/ru.json index 27531c65d9e19f..b7c9779e11ae50 100644 --- a/homeassistant/components/iaqualink/translations/ru.json +++ b/homeassistant/components/iaqualink/translations/ru.json @@ -10,9 +10,9 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 iAqualink.", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 iAqualink.", "title": "Jandy iAqualink" } } diff --git a/homeassistant/components/insteon/translations/ru.json b/homeassistant/components/insteon/translations/ru.json index dec25f1fe4bc7c..69b5354fe8739a 100644 --- a/homeassistant/components/insteon/translations/ru.json +++ b/homeassistant/components/insteon/translations/ru.json @@ -22,7 +22,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Insteon Hub \u0432\u0435\u0440\u0441\u0438\u0438 2", "title": "Insteon Hub. \u0412\u0435\u0440\u0441\u0438\u044f 2" @@ -74,7 +74,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0418\u0437\u043c\u0435\u043d\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 Insteon Hub. \u041f\u043e\u0441\u043b\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Home Assistant. \u042d\u0442\u043e \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0441\u0430\u043c\u043e\u0433\u043e \u0445\u0430\u0431\u0430. \u0427\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0445\u0430\u0431\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Hub.", "title": "Insteon" diff --git a/homeassistant/components/isy994/translations/ru.json b/homeassistant/components/isy994/translations/ru.json index cbf88574e1e066..50aa75cab37e36 100644 --- a/homeassistant/components/isy994/translations/ru.json +++ b/homeassistant/components/isy994/translations/ru.json @@ -16,7 +16,7 @@ "host": "URL-\u0430\u0434\u0440\u0435\u0441", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "tls": "\u0412\u0435\u0440\u0441\u0438\u044f TLS \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 URL-\u0430\u0434\u0440\u0435\u0441 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 'http://192.168.10.100:80').", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/izone/translations/nl.json b/homeassistant/components/izone/translations/nl.json index 22d1a3c4963671..b70bb738df02d2 100644 --- a/homeassistant/components/izone/translations/nl.json +++ b/homeassistant/components/izone/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen iZone-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van iZone nodig." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json index 71ce0154639b81..5994df16940c15 100644 --- a/homeassistant/components/keenetic_ndms2/translations/de.json +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Scanintervall" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/keenetic_ndms2/translations/ru.json b/homeassistant/components/keenetic_ndms2/translations/ru.json index bfd7f6407e75b0..6f99453888e4a1 100644 --- a/homeassistant/components/keenetic_ndms2/translations/ru.json +++ b/homeassistant/components/keenetic_ndms2/translations/ru.json @@ -13,7 +13,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 Keenetic NDMS2" } diff --git a/homeassistant/components/kmtronic/translations/ru.json b/homeassistant/components/kmtronic/translations/ru.json index 7401068638d065..219af38fae9d8c 100644 --- a/homeassistant/components/kmtronic/translations/ru.json +++ b/homeassistant/components/kmtronic/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/kodi/translations/ru.json b/homeassistant/components/kodi/translations/ru.json index 50742417f28f99..b6f7443f061fbb 100644 --- a/homeassistant/components/kodi/translations/ru.json +++ b/homeassistant/components/kodi/translations/ru.json @@ -17,7 +17,7 @@ "credentials": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c Kodi. \u0418\u0445 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438, \u043f\u0435\u0440\u0435\u0439\u0434\u044f \u0432 \"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\" - \"\u0421\u043b\u0443\u0436\u0431\u044b\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\"." }, diff --git a/homeassistant/components/life360/translations/ru.json b/homeassistant/components/life360/translations/ru.json index 5b5934fbb427da..b7bc71989879cb 100644 --- a/homeassistant/components/life360/translations/ru.json +++ b/homeassistant/components/life360/translations/ru.json @@ -10,14 +10,14 @@ "error": { "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.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", - "invalid_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", + "invalid_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\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": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.", "title": "Life360" diff --git a/homeassistant/components/litejet/translations/de.json b/homeassistant/components/litejet/translations/de.json index 492314e5cc6d32..ff528dd79b2187 100644 --- a/homeassistant/components/litejet/translations/de.json +++ b/homeassistant/components/litejet/translations/de.json @@ -3,11 +3,16 @@ "abort": { "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "error": { + "open_failed": "Die angegebene serielle Schnittstelle kann nicht ge\u00f6ffnet werden." + }, "step": { "user": { "data": { "port": "Port" - } + }, + "description": "Verbinde den RS232-2-Anschluss des LiteJet mit deinen Computer und gib den Pfad zum Ger\u00e4t der seriellen Schnittstelle ein.\n\nDer LiteJet MCP muss f\u00fcr 19,2 K Baud, 8 Datenbits, 1 Stoppbit, keine Parit\u00e4t und zur \u00dcbertragung eines 'CR' nach jeder Antwort konfiguriert werden.", + "title": "Verbinde zu LiteJet" } } } diff --git a/homeassistant/components/litterrobot/translations/pt.json b/homeassistant/components/litterrobot/translations/pt.json index 565b9f6c0e8410..7953cf5625ced6 100644 --- a/homeassistant/components/litterrobot/translations/pt.json +++ b/homeassistant/components/litterrobot/translations/pt.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, "error": { "cannot_connect": "Falha na liga\u00e7\u00e3o", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", diff --git a/homeassistant/components/litterrobot/translations/ru.json b/homeassistant/components/litterrobot/translations/ru.json index 3f4677a050e6df..aef0fdff54e603 100644 --- a/homeassistant/components/litterrobot/translations/ru.json +++ b/homeassistant/components/litterrobot/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/mikrotik/translations/ru.json b/homeassistant/components/mikrotik/translations/ru.json index 21391f12b1c05d..06e9d647545905 100644 --- a/homeassistant/components/mikrotik/translations/ru.json +++ b/homeassistant/components/mikrotik/translations/ru.json @@ -15,7 +15,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "verify_ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL" }, "title": "MikroTik" diff --git a/homeassistant/components/mill/translations/ru.json b/homeassistant/components/mill/translations/ru.json index db2d4651c2d58d..eac6c63c559db7 100644 --- a/homeassistant/components/mill/translations/ru.json +++ b/homeassistant/components/mill/translations/ru.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/mqtt/translations/ru.json b/homeassistant/components/mqtt/translations/ru.json index 7cc7a84b28c682..4357a0902c6ec6 100644 --- a/homeassistant/components/mqtt/translations/ru.json +++ b/homeassistant/components/mqtt/translations/ru.json @@ -13,7 +13,7 @@ "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "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 \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT." }, @@ -60,7 +60,7 @@ "broker": "\u0411\u0440\u043e\u043a\u0435\u0440", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "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 \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT." }, diff --git a/homeassistant/components/mullvad/translations/de.json b/homeassistant/components/mullvad/translations/de.json index 625c7372347a61..6014a9155c81b8 100644 --- a/homeassistant/components/mullvad/translations/de.json +++ b/homeassistant/components/mullvad/translations/de.json @@ -14,7 +14,8 @@ "host": "Host", "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Mullvad VPN Integration einrichten?" } } } diff --git a/homeassistant/components/mullvad/translations/ru.json b/homeassistant/components/mullvad/translations/ru.json index ff34530e4a9b48..af2ecd321f00d6 100644 --- a/homeassistant/components/mullvad/translations/ru.json +++ b/homeassistant/components/mullvad/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Mullvad VPN." } diff --git a/homeassistant/components/myq/translations/ru.json b/homeassistant/components/myq/translations/ru.json index c3b113f148f562..c88db7d696094a 100644 --- a/homeassistant/components/myq/translations/ru.json +++ b/homeassistant/components/myq/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "MyQ" } diff --git a/homeassistant/components/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json index 189226f29d5e90..98a1ca9cd32869 100644 --- a/homeassistant/components/mysensors/translations/de.json +++ b/homeassistant/components/mysensors/translations/de.json @@ -4,13 +4,51 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_device": "Ung\u00fcltiges Ger\u00e4t", + "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_version": "Ung\u00fcltige MySensors Version", + "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" }, "error": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "invalid_device": "Ung\u00fcltiges Ger\u00e4t", + "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_version": "Ung\u00fcltige MySensors Version", + "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" + }, + "step": { + "gw_mqtt": { + "data": { + "version": "MySensors Version" + }, + "description": "MQTT-Gateway einrichten" + }, + "gw_serial": { + "data": { + "baud_rate": "Baudrate", + "device": "Serielle Schnittstelle", + "version": "MySensors Version" + }, + "description": "Einrichtung des seriellen Gateways" + }, + "gw_tcp": { + "data": { + "device": "IP-Adresse des Gateways", + "version": "MySensors Version" + }, + "description": "Einrichtung des Ethernet-Gateways" + }, + "user": { + "data": { + "gateway_type": "Gateway-Typ" + }, + "description": "Verbindungsmethode zum Gateway w\u00e4hlen" + } } - } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ru.json b/homeassistant/components/neato/translations/ru.json index ea1be16d7ac1c1..25bb616a63837f 100644 --- a/homeassistant/components/neato/translations/ru.json +++ b/homeassistant/components/neato/translations/ru.json @@ -25,7 +25,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f", "vendor": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c" }, "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({docs_url}) \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.", diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 84c040499468c9..c6e62db314d7fa 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -30,7 +30,7 @@ "data": { "code": "Codice PIN" }, - "description": "Per collegare l'account Nido, [autorizzare l'account]({url}).\n\nDopo l'autorizzazione, copia-incolla il codice PIN fornito di seguito.", + "description": "Per collegare il tuo account Nest, [autorizza il tuo account] ({url}). \n\nDopo l'autorizzazione, copia e incolla il codice PIN fornito di seguito.", "title": "Collega un account Nest" }, "pick_implementation": { diff --git a/homeassistant/components/netatmo/translations/de.json b/homeassistant/components/netatmo/translations/de.json index 247e9e8b9312ea..dccb58577487e5 100644 --- a/homeassistant/components/netatmo/translations/de.json +++ b/homeassistant/components/netatmo/translations/de.json @@ -52,6 +52,7 @@ }, "public_weather_areas": { "data": { + "new_area": "Bereichsname", "weather_areas": "Wettergebiete" }, "description": "Konfiguriere \u00f6ffentliche Wettersensoren.", diff --git a/homeassistant/components/nexia/translations/ru.json b/homeassistant/components/nexia/translations/ru.json index f1c7b5b8ced785..74ec08ec2cfef4 100644 --- a/homeassistant/components/nexia/translations/ru.json +++ b/homeassistant/components/nexia/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a mynexia.com" } diff --git a/homeassistant/components/notion/translations/nl.json b/homeassistant/components/notion/translations/nl.json index 64894303cdf9ab..acb42046c9053e 100644 --- a/homeassistant/components/notion/translations/nl.json +++ b/homeassistant/components/notion/translations/nl.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "Gebruikersnaam/E-mailadres" + "username": "Gebruikersnaam" }, "title": "Vul uw gegevens informatie" } diff --git a/homeassistant/components/notion/translations/ru.json b/homeassistant/components/notion/translations/ru.json index 737539424b02b8..4b9a45bbf3f8d2 100644 --- a/homeassistant/components/notion/translations/ru.json +++ b/homeassistant/components/notion/translations/ru.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Notion" } diff --git a/homeassistant/components/nuheat/translations/ru.json b/homeassistant/components/nuheat/translations/ru.json index 099f6c3f1fccaa..7c90b8615640e0 100644 --- a/homeassistant/components/nuheat/translations/ru.json +++ b/homeassistant/components/nuheat/translations/ru.json @@ -14,7 +14,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "serial_number": "\u0421\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0438\u043b\u0438 ID \u0412\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430, \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 https://MyNuHeat.com.", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" diff --git a/homeassistant/components/number/translations/de.json b/homeassistant/components/number/translations/de.json index 5005eb5a010e62..3ef9a0358a461e 100644 --- a/homeassistant/components/number/translations/de.json +++ b/homeassistant/components/number/translations/de.json @@ -1,3 +1,8 @@ { + "device_automation": { + "action_type": { + "set_value": "Wert f\u00fcr {entity_name} setzen" + } + }, "title": "Nummer" } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pt.json b/homeassistant/components/nut/translations/pt.json index a856ef0aeede81..f5e8690e3839c6 100644 --- a/homeassistant/components/nut/translations/pt.json +++ b/homeassistant/components/nut/translations/pt.json @@ -22,5 +22,11 @@ } } } + }, + "options": { + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "unknown": "Erro inesperado" + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ru.json b/homeassistant/components/nut/translations/ru.json index 5422704c96dbf5..071a7d0f09c82c 100644 --- a/homeassistant/components/nut/translations/ru.json +++ b/homeassistant/components/nut/translations/ru.json @@ -26,7 +26,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 NUT" } diff --git a/homeassistant/components/nzbget/translations/ru.json b/homeassistant/components/nzbget/translations/ru.json index e4f0a44fbcc58a..4c5d73795270d8 100644 --- a/homeassistant/components/nzbget/translations/ru.json +++ b/homeassistant/components/nzbget/translations/ru.json @@ -16,7 +16,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "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": "\u041b\u043e\u0433\u0438\u043d", + "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" }, "title": "NZBGet" diff --git a/homeassistant/components/omnilogic/translations/ru.json b/homeassistant/components/omnilogic/translations/ru.json index 828f053083096a..5b00efefa1a209 100644 --- a/homeassistant/components/omnilogic/translations/ru.json +++ b/homeassistant/components/omnilogic/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/onvif/translations/ru.json b/homeassistant/components/onvif/translations/ru.json index 823dd8eb7fd2dd..6b486bb62e2b68 100644 --- a/homeassistant/components/onvif/translations/ru.json +++ b/homeassistant/components/onvif/translations/ru.json @@ -14,7 +14,7 @@ "auth": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" }, diff --git a/homeassistant/components/ovo_energy/translations/ru.json b/homeassistant/components/ovo_energy/translations/ru.json index 47a94f6a24aad7..89eb632102f585 100644 --- a/homeassistant/components/ovo_energy/translations/ru.json +++ b/homeassistant/components/ovo_energy/translations/ru.json @@ -17,7 +17,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 OVO Energy.", "title": "OVO Energy" diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index 980bb6800e1719..b12d8e7a848081 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -10,6 +10,13 @@ "unknown": "Error inesperat" }, "step": { + "pair": { + "data": { + "pin": "Codi PIN" + }, + "description": "Introdueix el PIN que apareix a la pantalla del televisor", + "title": "Emparellar" + }, "user": { "data": { "api_version": "Versi\u00f3 de l'API", diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json index 8b5d376ead70d9..d0c9932b3cd268 100644 --- a/homeassistant/components/philips_js/translations/de.json +++ b/homeassistant/components/philips_js/translations/de.json @@ -10,6 +10,11 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "pair": { + "data": { + "pin": "PIN-Code" + } + }, "user": { "data": { "api_version": "API-Version", @@ -17,5 +22,10 @@ } } } + }, + "device_automation": { + "trigger_type": { + "turn_on": "Ger\u00e4t wird zum Einschalten aufgefordert" + } } } \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/en.json b/homeassistant/components/philips_js/translations/en.json index 65d4f417b9f9ec..ea254a3873d79f 100644 --- a/homeassistant/components/philips_js/translations/en.json +++ b/homeassistant/components/philips_js/translations/en.json @@ -10,6 +10,13 @@ "unknown": "Unexpected error" }, "step": { + "pair": { + "data": { + "pin": "PIN Code" + }, + "description": "Enter the PIN displayed on your TV", + "title": "Pair" + }, "user": { "data": { "api_version": "API Version", diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index d698b01af1a9b6..9a7c530ce52101 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -10,6 +10,11 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "pair": { + "data": { + "pin": "PIN \ucf54\ub4dc" + } + }, "user": { "data": { "api_version": "API \ubc84\uc804", diff --git a/homeassistant/components/philips_js/translations/pt.json b/homeassistant/components/philips_js/translations/pt.json new file mode 100644 index 00000000000000..4646fcae7dc081 --- /dev/null +++ b/homeassistant/components/philips_js/translations/pt.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_pin": "PIN inv\u00e1lido" + }, + "step": { + "pair": { + "data": { + "pin": "C\u00f3digo PIN" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index 20cba7e9ebca62..e3e94ddf848c94 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -9,9 +9,37 @@ "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, "step": { + "api_method": { + "data": { + "use_webhook": "Webhook verwenden" + }, + "title": "API-Methode ausw\u00e4hlen" + }, "user": { + "data": { + "device_name": "Benenne dein Ger\u00e4t", + "device_type": "Art des Plaato-Ger\u00e4ts" + }, "description": "M\u00f6chten Sie mit der Einrichtung beginnen?", "title": "Plaato Webhook einrichten" + }, + "webhook": { + "description": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." + } + } + }, + "options": { + "step": { + "user": { + "data": { + "update_interval": "Aktualisierungsintervall (Minuten)" + }, + "description": "Aktualisierungsintervall einrichten (Minuten)", + "title": "Optionen f\u00fcr Plaato" + }, + "webhook": { + "description": "Webhook-Informationen:\n\n- URL: `{webhook_url}`\n- Methode: POST\n\n", + "title": "Optionen f\u00fcr Plaato Airlock" } } } diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 6453668680a8f4..6c09818594c62a 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -6,7 +6,7 @@ "webhook_not_internet_accessible": "Uw Home Assistant-instantie moet toegankelijk zijn via internet om webhook-berichten te ontvangen." }, "create_entry": { - "default": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ( {docs_url} ) voor meer informatie." + "default": "Uw Plaato {device_type} met naam **{device_name}** is succesvol ingesteld!" }, "error": { "invalid_webhook_device": "U heeft een apparaat geselecteerd dat het verzenden van gegevens naar een webhook niet ondersteunt. Het is alleen beschikbaar voor de Airlock", @@ -26,7 +26,7 @@ "device_name": "Geef uw apparaat een naam", "device_type": "Type Plaato-apparaat" }, - "description": "Weet u zeker dat u de Plaato-airlock wilt instellen?", + "description": "Wil je beginnen met instellen?", "title": "Stel de Plaato Webhook in" }, "webhook": { diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index 6c89b0b8d5fe5f..a196555e7ea56d 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -3,15 +3,15 @@ "abort": { "all_configured": "Alle gekoppelde servers zijn al geconfigureerd", "already_configured": "Deze Plex-server is al geconfigureerd", - "already_in_progress": "Plex wordt geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "reauth_successful": "Herauthenticatie was succesvol", "token_request_timeout": "Time-out verkrijgen van token", - "unknown": "Mislukt om onbekende reden" + "unknown": "Onverwachte fout" }, "error": { - "faulty_credentials": "Autorisatie mislukt", + "faulty_credentials": "Autorisatie mislukt, controleer token", "host_or_token": "Moet ten minste \u00e9\u00e9n host of token verstrekken.", - "no_servers": "Geen servers gekoppeld aan account", + "no_servers": "Geen servers gekoppeld aan Plex account", "not_found": "Plex-server niet gevonden", "ssl_error": "SSL-certificaatprobleem" }, diff --git a/homeassistant/components/plugwise/translations/ru.json b/homeassistant/components/plugwise/translations/ru.json index 9df460e8919634..f027ebfc772472 100644 --- a/homeassistant/components/plugwise/translations/ru.json +++ b/homeassistant/components/plugwise/translations/ru.json @@ -22,7 +22,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "password": "Smile ID", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d Smile" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Smile" }, "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435:", "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Smile" diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 81b77f5ced55d9..d727fc9c1b8228 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -39,21 +39,28 @@ "error": { "already_configured_device": "Apparaat is al geconfigureerd", "invalid_event_code": "Ongeldige gebeurteniscode", + "invalid_input_off_delay": "Ongeldige invoer voor uitschakelvertraging", "unknown": "Onverwachte fout" }, "step": { "prompt_options": { "data": { "automatic_add": "Schakel automatisch toevoegen in", - "debug": "Foutopsporing inschakelen" - } + "debug": "Foutopsporing inschakelen", + "device": "Selecteer het apparaat om te configureren", + "event_code": "Voer de gebeurteniscode in om toe te voegen", + "remove_device": "Apparaat selecteren dat u wilt verwijderen" + }, + "title": "Rfxtrx-opties" }, "set_device_options": { "data": { "data_bit": "Aantal databits", "fire_event": "Schakel apparaatgebeurtenis in", "off_delay": "Uitschakelvertraging", - "off_delay_enabled": "Schakel uitschakelvertraging in" + "off_delay_enabled": "Schakel uitschakelvertraging in", + "replace_device": "Selecteer apparaat dat u wilt vervangen", + "signal_repetitions": "Aantal signaalherhalingen" }, "title": "Configureer apparaatopties" } diff --git a/homeassistant/components/ring/translations/ru.json b/homeassistant/components/ring/translations/ru.json index 636d83f2e02701..bc07db24007d4c 100644 --- a/homeassistant/components/ring/translations/ru.json +++ b/homeassistant/components/ring/translations/ru.json @@ -17,7 +17,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Ring" } diff --git a/homeassistant/components/risco/translations/ru.json b/homeassistant/components/risco/translations/ru.json index a507bb84e530d4..200c38fb213d67 100644 --- a/homeassistant/components/risco/translations/ru.json +++ b/homeassistant/components/risco/translations/ru.json @@ -13,7 +13,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "pin": "PIN-\u043a\u043e\u0434", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/ruckus_unleashed/translations/ru.json b/homeassistant/components/ruckus_unleashed/translations/ru.json index 9e0db9fcf94dd8..9b02cafd466691 100644 --- a/homeassistant/components/ruckus_unleashed/translations/ru.json +++ b/homeassistant/components/ruckus_unleashed/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/sensor/translations/de.json b/homeassistant/components/sensor/translations/de.json index 0f72b344982e06..bb7c197f0e82a8 100644 --- a/homeassistant/components/sensor/translations/de.json +++ b/homeassistant/components/sensor/translations/de.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "{entity_name} Batteriestand", + "is_carbon_dioxide": "Aktuelle {entity_name} Kohlenstoffdioxid-Konzentration", + "is_carbon_monoxide": "Aktuelle {entity_name} Kohlenstoffmonoxid-Konzentration", "is_humidity": "{entity_name} Feuchtigkeit", "is_illuminance": "Aktuelle {entity_name} Helligkeit", "is_power": "Aktuelle {entity_name} Leistung", @@ -13,6 +15,8 @@ }, "trigger_type": { "battery_level": "{entity_name} Batteriestatus\u00e4nderungen", + "carbon_dioxide": "{entity_name} Kohlenstoffdioxid-Konzentrations\u00e4nderung", + "carbon_monoxide": "{entity_name} Kohlenstoffmonoxid-Konzentrations\u00e4nderung", "humidity": "{entity_name} Feuchtigkeits\u00e4nderungen", "illuminance": "{entity_name} Helligkeits\u00e4nderungen", "power": "{entity_name} Leistungs\u00e4nderungen", diff --git a/homeassistant/components/sharkiq/translations/ru.json b/homeassistant/components/sharkiq/translations/ru.json index 60ce7d454d655f..0e91c64c7d41e6 100644 --- a/homeassistant/components/sharkiq/translations/ru.json +++ b/homeassistant/components/sharkiq/translations/ru.json @@ -15,13 +15,13 @@ "reauth": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } }, "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index a570cb7f9fb3ef..9996e347e96a91 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -17,7 +17,7 @@ "credentials": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } }, "user": { diff --git a/homeassistant/components/smart_meter_texas/translations/ru.json b/homeassistant/components/smart_meter_texas/translations/ru.json index 3f4677a050e6df..aef0fdff54e603 100644 --- a/homeassistant/components/smart_meter_texas/translations/ru.json +++ b/homeassistant/components/smart_meter_texas/translations/ru.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/smarttub/translations/de.json b/homeassistant/components/smarttub/translations/de.json index fbb3411a6c5b26..8e608193b81f71 100644 --- a/homeassistant/components/smarttub/translations/de.json +++ b/homeassistant/components/smarttub/translations/de.json @@ -13,7 +13,9 @@ "data": { "email": "E-Mail", "password": "Passwort" - } + }, + "description": "Gib deine SmartTub E-Mail-Adresse und Passwort f\u00fcr die Anmeldung ein", + "title": "Anmeldung" } } } diff --git a/homeassistant/components/solaredge/translations/nl.json b/homeassistant/components/solaredge/translations/nl.json index 3fe28971f29e81..24e1716dd571ba 100644 --- a/homeassistant/components/solaredge/translations/nl.json +++ b/homeassistant/components/solaredge/translations/nl.json @@ -14,7 +14,7 @@ "step": { "user": { "data": { - "api_key": "De API-sleutel voor deze site", + "api_key": "API-sleutel", "name": "De naam van deze installatie", "site_id": "De SolarEdge site-id" }, diff --git a/homeassistant/components/solarlog/translations/nl.json b/homeassistant/components/solarlog/translations/nl.json index 8ccf5e626c7632..6e526802d8ab9d 100644 --- a/homeassistant/components/solarlog/translations/nl.json +++ b/homeassistant/components/solarlog/translations/nl.json @@ -5,12 +5,12 @@ }, "error": { "already_configured": "Apparaat is al geconfigureerd", - "cannot_connect": "Verbinding mislukt, controleer het host-adres" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "user": { "data": { - "host": "De hostnaam of het IP-adres van uw Solar-Log apparaat", + "host": "Host", "name": "Het voorvoegsel dat moet worden gebruikt voor uw Solar-Log sensoren" }, "title": "Definieer uw Solar-Log verbinding" diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index b7f077f2c7342f..3fe61e5eaab19c 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -2,12 +2,12 @@ "config": { "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "Het Somfy-component is niet geconfigureerd. Gelieve de documentatie te volgen.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geverifieerd met Somfy." + "default": "Succesvol geauthenticeerd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/spider/translations/ru.json b/homeassistant/components/spider/translations/ru.json index 1b1a175cce526f..08d70e4406539e 100644 --- a/homeassistant/components/spider/translations/ru.json +++ b/homeassistant/components/spider/translations/ru.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u0412\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 mijn.ithodaalderop.nl" } diff --git a/homeassistant/components/squeezebox/translations/nl.json b/homeassistant/components/squeezebox/translations/nl.json index d023a3e96d19d3..f60fc640db2b8f 100644 --- a/homeassistant/components/squeezebox/translations/nl.json +++ b/homeassistant/components/squeezebox/translations/nl.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "no_server_found": "Geen LMS server gevonden." }, "error": { "cannot_connect": "Kon niet verbinden", "invalid_auth": "Ongeldige authenticatie", + "no_server_found": "Kan server niet automatisch vinden.", "unknown": "Onverwachte fout" }, "flow_title": "Logitech Squeezebox: {host}", diff --git a/homeassistant/components/squeezebox/translations/ru.json b/homeassistant/components/squeezebox/translations/ru.json index fb07471d116e10..3b144adc04eb8b 100644 --- a/homeassistant/components/squeezebox/translations/ru.json +++ b/homeassistant/components/squeezebox/translations/ru.json @@ -17,7 +17,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438" }, diff --git a/homeassistant/components/srp_energy/translations/ru.json b/homeassistant/components/srp_energy/translations/ru.json index 125f3a5addcff4..a492fa7dfb233e 100644 --- a/homeassistant/components/srp_energy/translations/ru.json +++ b/homeassistant/components/srp_energy/translations/ru.json @@ -15,7 +15,7 @@ "id": "ID \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430", "is_tou": "\u041f\u043b\u0430\u043d \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/starline/translations/ru.json b/homeassistant/components/starline/translations/ru.json index ea6833f58423d9..a89fc3b15d5d5c 100644 --- a/homeassistant/components/starline/translations/ru.json +++ b/homeassistant/components/starline/translations/ru.json @@ -3,7 +3,7 @@ "error": { "error_auth_app": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043e\u0434.", "error_auth_mfa": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434.", - "error_auth_user": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c." + "error_auth_user": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c." }, "step": { "auth_app": { @@ -31,7 +31,7 @@ "auth_user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 StarLine", "title": "\u0423\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" diff --git a/homeassistant/components/subaru/translations/de.json b/homeassistant/components/subaru/translations/de.json index 1c162d61e994fd..9c4a0bdf535efe 100644 --- a/homeassistant/components/subaru/translations/de.json +++ b/homeassistant/components/subaru/translations/de.json @@ -15,13 +15,16 @@ "pin": { "data": { "pin": "PIN" - } + }, + "description": "Bitte gib deinen MySubaru-PIN ein\nHINWEIS: Alle Fahrzeuge im Konto m\u00fcssen dieselbe PIN haben" }, "user": { "data": { + "country": "Land ausw\u00e4hlen", "password": "Passwort", "username": "Benutzername" - } + }, + "description": "Bitte gib deine MySubaru-Anmeldedaten ein\nHINWEIS: Die Ersteinrichtung kann bis zu 30 Sekunden dauern" } } } diff --git a/homeassistant/components/subaru/translations/it.json b/homeassistant/components/subaru/translations/it.json index 6dbb0702f46682..c585834f12ba98 100644 --- a/homeassistant/components/subaru/translations/it.json +++ b/homeassistant/components/subaru/translations/it.json @@ -34,9 +34,9 @@ "step": { "init": { "data": { - "update_enabled": "Abilita l'interrogazione del veicolo" + "update_enabled": "Abilita la verifica ciclica del veicolo" }, - "description": "Quando abilitata, l'interrogazione del veicolo invier\u00e0 un comando remoto al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo, i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", + "description": "Quando abilitata, la verifica ciclica del veicolo invier\u00e0 un comando remoto d'interrogazione al tuo veicolo ogni 2 ore per ottenere nuovi dati del sensore. Senza l'interrogazione del veicolo i nuovi dati del sensore verranno ricevuti solo quando il veicolo invier\u00e0 automaticamente i dati (normalmente dopo lo spegnimento del motore).", "title": "Opzioni Subaru Starlink" } } diff --git a/homeassistant/components/subaru/translations/pt.json b/homeassistant/components/subaru/translations/pt.json new file mode 100644 index 00000000000000..7f3e1ec8e3b6fb --- /dev/null +++ b/homeassistant/components/subaru/translations/pt.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada", + "cannot_connect": "Falha na liga\u00e7\u00e3o" + }, + "error": { + "cannot_connect": "Falha na liga\u00e7\u00e3o", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/ru.json b/homeassistant/components/subaru/translations/ru.json index 7e3fbce6e38cd3..c414ebb385fece 100644 --- a/homeassistant/components/subaru/translations/ru.json +++ b/homeassistant/components/subaru/translations/ru.json @@ -23,7 +23,7 @@ "data": { "country": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0443", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0443\u0447\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 MySubaru.\n\u041f\u0435\u0440\u0432\u043e\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u0434\u043e 30 \u0441\u0435\u043a\u0443\u043d\u0434.", "title": "Subaru Starlink" diff --git a/homeassistant/components/syncthru/translations/nl.json b/homeassistant/components/syncthru/translations/nl.json index e569d1d6322dc2..d86e9fa2087968 100644 --- a/homeassistant/components/syncthru/translations/nl.json +++ b/homeassistant/components/syncthru/translations/nl.json @@ -5,6 +5,7 @@ }, "error": { "invalid_url": "Ongeldige URL", + "syncthru_not_supported": "Apparaat ondersteunt SyncThru niet", "unknown_state": "Printerstatus onbekend, controleer URL en netwerkconnectiviteit" }, "flow_title": "Samsung SyncThru Printer: {name}", diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index 8c48b8c3fc7418..98a7522b27d4c2 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -23,7 +23,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "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": "\u041b\u043e\u0433\u0438\u043d", + "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" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?", @@ -35,7 +35,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "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": "\u041b\u043e\u0433\u0438\u043d", + "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" }, "title": "Synology DSM" diff --git a/homeassistant/components/tado/translations/ru.json b/homeassistant/components/tado/translations/ru.json index 75c83e8582bfcf..18e9ddff67b914 100644 --- a/homeassistant/components/tado/translations/ru.json +++ b/homeassistant/components/tado/translations/ru.json @@ -13,7 +13,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Tado" } diff --git a/homeassistant/components/tasmota/translations/nl.json b/homeassistant/components/tasmota/translations/nl.json index 3b0cc5c1ca8e21..561439362c748c 100644 --- a/homeassistant/components/tasmota/translations/nl.json +++ b/homeassistant/components/tasmota/translations/nl.json @@ -5,6 +5,9 @@ }, "step": { "config": { + "data": { + "discovery_prefix": "Discovery-onderwerpvoorvoegsel" + }, "description": "Vul de Tasmota gegevens in", "title": "Tasmota" }, diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index a0cda915172de3..69ae8aa127f724 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -8,6 +8,18 @@ "no_agreements": "Dit account heeft geen Toon schermen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout [check the help section] ( {docs_url} )", "unknown_authorize_url_generation": "Onbekende fout bij het genereren van een autorisatie-URL." + }, + "step": { + "agreement": { + "data": { + "agreement": "Overeenkomst" + }, + "description": "Selecteer het overeenkomstadres dat u wilt toevoegen.", + "title": "Kies uw overeenkomst" + }, + "pick_implementation": { + "title": "Kies uw tenant om mee te authenticeren" + } } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/de.json b/homeassistant/components/totalconnect/translations/de.json index 3fb5bb8f3e1f08..57dbaf77364eae 100644 --- a/homeassistant/components/totalconnect/translations/de.json +++ b/homeassistant/components/totalconnect/translations/de.json @@ -14,6 +14,7 @@ } }, "reauth_confirm": { + "description": "Total Connect muss dein Konto neu authentifizieren", "title": "Integration erneut authentifizieren" }, "user": { diff --git a/homeassistant/components/totalconnect/translations/pt.json b/homeassistant/components/totalconnect/translations/pt.json index 3c17682089a267..11ac82622676ff 100644 --- a/homeassistant/components/totalconnect/translations/pt.json +++ b/homeassistant/components/totalconnect/translations/pt.json @@ -1,12 +1,21 @@ { "config": { "abort": { - "already_configured": "Conta j\u00e1 configurada" + "already_configured": "Conta j\u00e1 configurada", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida" }, "step": { + "locations": { + "data": { + "location": "Localiza\u00e7\u00e3o" + } + }, + "reauth_confirm": { + "title": "Reautenticar integra\u00e7\u00e3o" + }, "user": { "data": { "password": "Palavra-passe", diff --git a/homeassistant/components/totalconnect/translations/ru.json b/homeassistant/components/totalconnect/translations/ru.json index 0f067541dec673..a32f92b7b580d9 100644 --- a/homeassistant/components/totalconnect/translations/ru.json +++ b/homeassistant/components/totalconnect/translations/ru.json @@ -23,7 +23,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Total Connect" } diff --git a/homeassistant/components/tradfri/translations/nl.json b/homeassistant/components/tradfri/translations/nl.json index 874b6cff81c8e8..e70e515c4d2a50 100644 --- a/homeassistant/components/tradfri/translations/nl.json +++ b/homeassistant/components/tradfri/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "Bridge configuratie is al in volle gang." + "already_in_progress": "De configuratiestroom is al aan de gang" }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/transmission/translations/nl.json b/homeassistant/components/transmission/translations/nl.json index df9a4590e663b0..fcc1e05e7abe7a 100644 --- a/homeassistant/components/transmission/translations/nl.json +++ b/homeassistant/components/transmission/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Kan geen verbinding maken met host", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "name_exists": "Naam bestaat al" }, @@ -25,6 +25,8 @@ "step": { "init": { "data": { + "limit": "Limiet", + "order": "Bestel", "scan_interval": "Update frequentie" }, "title": "Configureer de opties voor Transmission" diff --git a/homeassistant/components/transmission/translations/ru.json b/homeassistant/components/transmission/translations/ru.json index 6b326bc123c6a5..a83e19da4001e9 100644 --- a/homeassistant/components/transmission/translations/ru.json +++ b/homeassistant/components/transmission/translations/ru.json @@ -15,7 +15,7 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "Transmission" } diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 55fb320c17e08a..c9f681c5ecbc77 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -27,11 +27,14 @@ "cannot_connect": "Kan geen verbinding maken" }, "error": { + "dev_multi_type": "Meerdere geselecteerde apparaten om te configureren moeten van hetzelfde type zijn", + "dev_not_config": "Apparaattype kan niet worden geconfigureerd", "dev_not_found": "Apparaat niet gevonden" }, "step": { "device": { "data": { + "brightness_range_mode": "Helderheidsbereik gebruikt door apparaat", "temp_step_override": "Doeltemperatuur stap" }, "title": "Configureer Tuya Apparaat" diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index 4babc23f2ec8aa..f40071ba400b39 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -15,7 +15,7 @@ "country_code": "\u041a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044f)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0430\u043a\u043a\u0430\u0443\u043d\u0442", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Tuya.", "title": "Tuya" diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 5810204db41f07..769287bb9751da 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -18,7 +18,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "site": "ID \u0441\u0430\u0439\u0442\u0430", - "username": "\u041b\u043e\u0433\u0438\u043d", + "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" }, "title": "UniFi Controller" diff --git a/homeassistant/components/upcloud/translations/nl.json b/homeassistant/components/upcloud/translations/nl.json index 312b117208c1cd..f9ba7d2f696a3b 100644 --- a/homeassistant/components/upcloud/translations/nl.json +++ b/homeassistant/components/upcloud/translations/nl.json @@ -12,5 +12,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Update-interval in seconden, minimaal 30" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/upcloud/translations/ru.json b/homeassistant/components/upcloud/translations/ru.json index c64a69965b22d1..8b0377e9c34c72 100644 --- a/homeassistant/components/upcloud/translations/ru.json +++ b/homeassistant/components/upcloud/translations/ru.json @@ -8,7 +8,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" } } } diff --git a/homeassistant/components/verisure/translations/ca.json b/homeassistant/components/verisure/translations/ca.json index 4df484912ce865..0ddcf9513f4753 100644 --- a/homeassistant/components/verisure/translations/ca.json +++ b/homeassistant/components/verisure/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El compte ja ha estat configurat" + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", @@ -12,7 +13,14 @@ "data": { "giid": "Instal\u00b7laci\u00f3" }, - "description": "Home Assistant ha trobat diverses instal\u00b7lacions Verisure al teu compte de My Pages. Selecciona la instal\u00b7laci\u00f3 a afegir a Home Assistant." + "description": "Home Assistant ha trobat diverses instal\u00b7lacions Verisure al compte de My Pages. Selecciona la instal\u00b7laci\u00f3 a afegir a Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Torna a autenticar-te amb el compte de Verisure My Pages.", + "email": "Correu electr\u00f2nic", + "password": "Contrasenya" + } }, "user": { "data": { diff --git a/homeassistant/components/verisure/translations/de.json b/homeassistant/components/verisure/translations/de.json new file mode 100644 index 00000000000000..f9edd2e7b16d0c --- /dev/null +++ b/homeassistant/components/verisure/translations/de.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "Konto wurde bereits konfiguriert", + "reauth_successful": "Die erneute Authentifizierung war erfolgreich" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "installation": { + "data": { + "giid": "Installation" + } + }, + "reauth_confirm": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + }, + "user": { + "data": { + "email": "E-Mail", + "password": "Passwort" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Der Standard-PIN-Code stimmt nicht mit der erforderlichen Anzahl von Ziffern \u00fcberein" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Anzahl der Ziffern im PIN-Code f\u00fcr Schl\u00f6sser", + "lock_default_code": "Standard-PIN-Code f\u00fcr Schl\u00f6sser, wird verwendet wenn keiner angegeben wird" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/et.json b/homeassistant/components/verisure/translations/et.json index d893f470a9a6c7..78a2c987ef2ee3 100644 --- a/homeassistant/components/verisure/translations/et.json +++ b/homeassistant/components/verisure/translations/et.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Kasutaja on juba seadistatud" + "already_configured": "Kasutaja on juba seadistatud", + "reauth_successful": "Taastuvastamine \u00f5nnestus" }, "error": { "invalid_auth": "Vigane autentimine", @@ -14,6 +15,13 @@ }, "description": "Home Assistant leidis kontolt Minu lehed mitu Verisure paigaldust. Vali Home Assistantile lisatav paigaldus." }, + "reauth_confirm": { + "data": { + "description": "Taastuvasta oma Verisure My Pages'i kontoga.", + "email": "E-posti aadress", + "password": "Salas\u00f5na" + } + }, "user": { "data": { "description": "Logi sisse oma Verisure My Pages kontoga.", diff --git a/homeassistant/components/verisure/translations/it.json b/homeassistant/components/verisure/translations/it.json new file mode 100644 index 00000000000000..1c90871db71353 --- /dev/null +++ b/homeassistant/components/verisure/translations/it.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La nuova autenticazione \u00e8 stata eseguita correttamente" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "installation": { + "data": { + "giid": "Installazione" + }, + "description": "Home Assistant ha trovato pi\u00f9 installazioni Verisure nel tuo account My Pages. Per favore, seleziona l'installazione da aggiungere a Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Autenticati nuovamente con il tuo account Verisure My Pages.", + "email": "E-mail", + "password": "Password" + } + }, + "user": { + "data": { + "description": "Accedi con il tuo account Verisure My Pages.", + "email": "E-mail", + "password": "Password" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Il codice PIN predefinito non corrisponde al numero di cifre richiesto" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Numero di cifre nel codice PIN per le serrature", + "lock_default_code": "Codice PIN predefinito per le serrature, utilizzato se non ne viene fornito nessuno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/ko.json b/homeassistant/components/verisure/translations/ko.json new file mode 100644 index 00000000000000..470aed32b19655 --- /dev/null +++ b/homeassistant/components/verisure/translations/ko.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "reauth_successful": "\uc7ac\uc778\uc99d\uc5d0 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "installation": { + "data": { + "giid": "\uc124\uce58" + }, + "description": "Home Assistant\uac00 My Pages \uacc4\uc815\uc5d0\uc11c \uc5ec\ub7ec \uac00\uc9c0 Verisure \uc124\uce58\ub97c \ubc1c\uacac\ud588\uc2b5\ub2c8\ub2e4. Home Assistant\uc5d0 \ucd94\uac00\ud560 \uc124\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694." + }, + "reauth_confirm": { + "data": { + "description": "Verisure My Pages \uacc4\uc815\uc73c\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc8fc\uc138\uc694.", + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + }, + "user": { + "data": { + "description": "Verisure My Pages \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uae30.", + "email": "\uc774\uba54\uc77c", + "password": "\ube44\ubc00\ubc88\ud638" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "\uae30\ubcf8 PIN \ucf54\ub4dc\uac00 \ud544\uc694\ud55c \uc790\ub9bf\uc218\uc640 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "\uc7a0\uae08\uc7a5\uce58\uc6a9 PIN \ucf54\ub4dc\uc758 \uc790\ub9bf\uc218", + "lock_default_code": "\uc7a0\uae08\uc7a5\uce58\uc5d0 \ub300\ud55c \uae30\ubcf8 PIN \ucf54\ub4dc (\uc81c\uacf5\ub41c PIN\uc774 \uc5c6\ub294 \uacbd\uc6b0 \uc0ac\uc6a9)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/nl.json b/homeassistant/components/verisure/translations/nl.json new file mode 100644 index 00000000000000..806231dbe4aaaa --- /dev/null +++ b/homeassistant/components/verisure/translations/nl.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd" + }, + "error": { + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "installation": { + "data": { + "giid": "Installatie" + }, + "description": "Home Assistant heeft meerdere Verisure-installaties gevonden in uw My Pages-account. Selecteer de installatie om toe te voegen aan Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Verifieer opnieuw met uw Verisure My Pages-account.", + "email": "E-mail", + "password": "Wachtwoord" + } + }, + "user": { + "data": { + "description": "Aanmelden met Verisure My Pages-account.", + "email": "E-mail", + "password": "Wachtwoord" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "De standaard pincode komt niet overeen met het vereiste aantal cijfers" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Aantal cijfers in pincode voor vergrendelingen", + "lock_default_code": "Standaard pincode voor vergrendelingen, gebruikt als er geen wordt gegeven" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/no.json b/homeassistant/components/verisure/translations/no.json new file mode 100644 index 00000000000000..0bd5529c51de59 --- /dev/null +++ b/homeassistant/components/verisure/translations/no.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Godkjenning p\u00e5 nytt var vellykket" + }, + "error": { + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "installation": { + "data": { + "giid": "Installasjon" + }, + "description": "Home Assistant fant flere Verisure-installasjoner i Mine sider-kontoen din. Velg installasjonen du vil legge til i Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Autentiser p\u00e5 nytt med Verisure Mine sider-kontoen din.", + "email": "E-post", + "password": "Passord" + } + }, + "user": { + "data": { + "description": "Logg p\u00e5 med Verisure Mine sider-kontoen din.", + "email": "E-post", + "password": "Passord" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Standard PIN-kode samsvarer ikke med det n\u00f8dvendige antallet sifre" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Antall sifre i PIN-kode for l\u00e5ser", + "lock_default_code": "Standard PIN-kode for l\u00e5ser, brukes hvis ingen er oppgitt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pl.json b/homeassistant/components/verisure/translations/pl.json new file mode 100644 index 00000000000000..baaf61f60c3e41 --- /dev/null +++ b/homeassistant/components/verisure/translations/pl.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "installation": { + "data": { + "giid": "Instalacja" + }, + "description": "Home Assistant znalaz\u0142 wiele instalacji Verisure na Twoim koncie. Wybierz instalacj\u0119, kt\u00f3r\u0105 chcesz doda\u0107 do Home Assistanta." + }, + "reauth_confirm": { + "data": { + "description": "Ponownie uwierzytelnij za pomoc\u0105 konta Verisure.", + "email": "Adres e-mail", + "password": "Has\u0142o" + } + }, + "user": { + "data": { + "description": "Zaloguj si\u0119 na swoje konto Verisure.", + "email": "Adres e-mail", + "password": "Has\u0142o" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Domy\u015blny kod PIN nie odpowiada wymaganej liczbie cyfr" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Liczba cyfr w kodzie PIN dla zamk\u00f3w", + "lock_default_code": "Domy\u015blny kod PIN dla zamk\u00f3w. U\u017cywany, je\u015bli nie podano \u017cadnego." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/pt.json b/homeassistant/components/verisure/translations/pt.json index b6634945535019..a60db2b4ea0b41 100644 --- a/homeassistant/components/verisure/translations/pt.json +++ b/homeassistant/components/verisure/translations/pt.json @@ -1,11 +1,26 @@ { "config": { "abort": { - "already_configured": "Conta j\u00e1 configurada" + "already_configured": "Conta j\u00e1 configurada", + "reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida" }, "error": { "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" + }, + "step": { + "reauth_confirm": { + "data": { + "email": "Email", + "password": "Palavra-passe" + } + }, + "user": { + "data": { + "email": "Email", + "password": "Palavra-passe" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/ru.json b/homeassistant/components/verisure/translations/ru.json new file mode 100644 index 00000000000000..430b8d773d0bc3 --- /dev/null +++ b/homeassistant/components/verisure/translations/ru.json @@ -0,0 +1,47 @@ +{ + "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.", + "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": { + "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." + }, + "step": { + "installation": { + "data": { + "giid": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430" + }, + "description": "Home Assistant \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043e\u043a Verisure \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u00ab\u041c\u043e\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b\u00bb \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Verisure.", + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + } + }, + "user": { + "data": { + "description": "\u0412\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Verisure.", + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "PIN-\u043a\u043e\u0434 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u043c\u043e\u043c\u0443 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u0446\u0438\u0444\u0440." + }, + "step": { + "init": { + "data": { + "lock_code_digits": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0446\u0438\u0444\u0440 \u0432 PIN-\u043a\u043e\u0434\u0435 \u0434\u043b\u044f \u0437\u0430\u043c\u043a\u043e\u0432", + "lock_default_code": "PIN-\u043a\u043e\u0434 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0434\u043b\u044f \u0437\u0430\u043c\u043a\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439, \u0435\u0441\u043b\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043e" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/zh-Hant.json b/homeassistant/components/verisure/translations/zh-Hant.json new file mode 100644 index 00000000000000..29410e390fe09c --- /dev/null +++ b/homeassistant/components/verisure/translations/zh-Hant.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "installation": { + "data": { + "giid": "\u5b89\u88dd" + }, + "description": "Home Assistant \u65bc My Pages \u5e33\u865f\u4e2d\u627e\u5230\u591a\u500b Verisure \u5b89\u88dd\u3002\u8acb\u9078\u64c7\u6240\u8981\u65b0\u589e\u81f3 Home Assistant \u7684\u9805\u76ee\u3002" + }, + "reauth_confirm": { + "data": { + "description": "\u91cd\u65b0\u8a8d\u8b49 Verisure My Pages \u5e33\u865f\u3002", + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + } + }, + "user": { + "data": { + "description": "\u4ee5 Verisure My Pages \u5e33\u865f\u767b\u5165", + "email": "\u96fb\u5b50\u90f5\u4ef6", + "password": "\u5bc6\u78bc" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "\u9810\u8a2d PIN \u78bc\u8207\u6240\u9700\u6578\u5b57\u6578\u4e0d\u7b26\u5408" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "\u9580\u9396 PIN \u78bc\u6578\u5b57\u6578", + "lock_default_code": "\u9810\u8a2d\u9580\u9396 PIN \u78bc\uff0c\u65bc\u672a\u63d0\u4f9b\u6642\u4f7f\u7528" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vesync/translations/nl.json b/homeassistant/components/vesync/translations/nl.json index 36c7f315bcc3a0..ab330237afd95b 100644 --- a/homeassistant/components/vesync/translations/nl.json +++ b/homeassistant/components/vesync/translations/nl.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Wachtwoord", - "username": "E-mailadres" + "username": "E-mail" }, "title": "Voer gebruikersnaam en wachtwoord in" } diff --git a/homeassistant/components/water_heater/translations/de.json b/homeassistant/components/water_heater/translations/de.json index 9169018a5d4b4e..86f6115835136c 100644 --- a/homeassistant/components/water_heater/translations/de.json +++ b/homeassistant/components/water_heater/translations/de.json @@ -4,5 +4,16 @@ "turn_off": "{entity_name} ausschalten", "turn_on": "{entity_name} einschalten" } + }, + "state": { + "_": { + "eco": "Sparmodus", + "electric": "Elektrisch", + "gas": "Gas", + "heat_pump": "W\u00e4rmepumpe", + "high_demand": "Hoher Bedarf", + "off": "Aus", + "performance": "Leistung" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/it.json b/homeassistant/components/water_heater/translations/it.json index 86458a54272f50..88c53f67e3a920 100644 --- a/homeassistant/components/water_heater/translations/it.json +++ b/homeassistant/components/water_heater/translations/it.json @@ -4,5 +4,16 @@ "turn_off": "Disattiva {entity_name}", "turn_on": "Attiva {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "Elettrico", + "gas": "Gas", + "heat_pump": "Pompa di calore", + "high_demand": "Forte richiesta", + "off": "Spento", + "performance": "Prestazione" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/nl.json b/homeassistant/components/water_heater/translations/nl.json index 8b832b52c1c154..f70a5efc3e7f60 100644 --- a/homeassistant/components/water_heater/translations/nl.json +++ b/homeassistant/components/water_heater/translations/nl.json @@ -4,5 +4,15 @@ "turn_off": "Schakel {entity_name} uit", "turn_on": "Schakel {entity_name} in" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "Elektriciteit", + "gas": "Gas", + "heat_pump": "Warmtepomp", + "off": "Uit", + "performance": "Prestaties" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/no.json b/homeassistant/components/water_heater/translations/no.json index 6455919518f60e..2cbb96b23e7769 100644 --- a/homeassistant/components/water_heater/translations/no.json +++ b/homeassistant/components/water_heater/translations/no.json @@ -4,5 +4,16 @@ "turn_off": "Sl\u00e5 av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" } + }, + "state": { + "_": { + "eco": "\u00d8ko", + "electric": "Elektrisk", + "gas": "Gass", + "heat_pump": "Varmepumpe", + "high_demand": "H\u00f8y ettersp\u00f8rsel", + "off": "Av", + "performance": "Ytelse" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pl.json b/homeassistant/components/water_heater/translations/pl.json index d6deb99da020d4..bb314c855106f8 100644 --- a/homeassistant/components/water_heater/translations/pl.json +++ b/homeassistant/components/water_heater/translations/pl.json @@ -4,5 +4,16 @@ "turn_off": "wy\u0142\u0105cz {entity_name}", "turn_on": "w\u0142\u0105cz {entity_name}" } + }, + "state": { + "_": { + "eco": "ekonomiczny", + "electric": "elektryczny", + "gas": "gaz", + "heat_pump": "pompa ciep\u0142a", + "high_demand": "du\u017ce zapotrzebowanie", + "off": "wy\u0142.", + "performance": "wydajno\u015b\u0107" + } } } \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/pt.json b/homeassistant/components/water_heater/translations/pt.json index 072a81b682c47f..97cc30fe1350a8 100644 --- a/homeassistant/components/water_heater/translations/pt.json +++ b/homeassistant/components/water_heater/translations/pt.json @@ -7,6 +7,11 @@ }, "state": { "_": { + "eco": "Eco", + "electric": "El\u00e9trico", + "gas": "G\u00e1s", + "heat_pump": "Bomba de calor", + "high_demand": "Necessidade alta", "off": "Desligado" } } diff --git a/homeassistant/components/water_heater/translations/zh-Hant.json b/homeassistant/components/water_heater/translations/zh-Hant.json index 11e88cb1faeb20..5bb7aa129b5179 100644 --- a/homeassistant/components/water_heater/translations/zh-Hant.json +++ b/homeassistant/components/water_heater/translations/zh-Hant.json @@ -4,5 +4,16 @@ "turn_off": "\u95dc\u9589{entity_name}", "turn_on": "\u958b\u555f{entity_name}" } + }, + "state": { + "_": { + "eco": "\u7bc0\u80fd", + "electric": "\u96fb\u529b", + "gas": "\u74e6\u65af", + "heat_pump": "\u6696\u6c23", + "high_demand": "\u9ad8\u7528\u91cf", + "off": "\u95dc\u9589", + "performance": "\u6548\u80fd" + } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/nl.json b/homeassistant/components/wemo/translations/nl.json index 6146072623a95d..e4087863726c0d 100644 --- a/homeassistant/components/wemo/translations/nl.json +++ b/homeassistant/components/wemo/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Geen Wemo-apparaten gevonden op het netwerk.", - "single_instance_allowed": "Slechts een enkele configuratie van Wemo is mogelijk." + "no_devices_found": "Geen apparaten gevonden op het netwerk", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "step": { "confirm": { diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 5b099b952e5c14..9f7810a8378c9c 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -25,6 +25,7 @@ "title": "Gebruikersprofiel." }, "reauth": { + "description": "Het \"{profile}\" profiel moet opnieuw worden geverifieerd om Withings gegevens te kunnen blijven ontvangen.", "title": "Verifieer de integratie opnieuw" } } diff --git a/homeassistant/components/wolflink/translations/ru.json b/homeassistant/components/wolflink/translations/ru.json index 0b105ad922afee..8a430c9a63dedf 100644 --- a/homeassistant/components/wolflink/translations/ru.json +++ b/homeassistant/components/wolflink/translations/ru.json @@ -18,7 +18,7 @@ "user": { "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "username": "\u041b\u043e\u0433\u0438\u043d" + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" }, "title": "WOLF SmartSet" } diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json index 61e865ef01648a..1effe228de6a70 100644 --- a/homeassistant/components/xiaomi_aqara/translations/de.json +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -17,6 +17,12 @@ }, "description": "F\u00fchre das Setup erneut aus, wenn du zus\u00e4tzliche Gateways verbinden m\u00f6chtest" }, + "settings": { + "data": { + "name": "Name des Gateways" + }, + "title": "Xiaomi Aqara Gateway, optionale Einstellungen" + }, "user": { "data": { "host": "IP-Adresse", diff --git a/homeassistant/components/xiaomi_aqara/translations/nl.json b/homeassistant/components/xiaomi_aqara/translations/nl.json index 0e5283c485e9ac..a356ed36e1bbaf 100644 --- a/homeassistant/components/xiaomi_aqara/translations/nl.json +++ b/homeassistant/components/xiaomi_aqara/translations/nl.json @@ -6,6 +6,7 @@ "not_xiaomi_aqara": "Geen Xiaomi Aqara Gateway, ontdekt apparaat kwam niet overeen met bekende gateways" }, "error": { + "discovery_error": "Het is niet gelukt om een Xiaomi Aqara Gateway te vinden, probeer het IP van het apparaat waarop HomeAssistant draait als interface te gebruiken", "invalid_host": "Ongeldige hostnaam of IP-adres, zie https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", "invalid_interface": "Ongeldige netwerkinterface", "invalid_key": "Ongeldige gatewaysleutel", @@ -25,7 +26,8 @@ "key": "De sleutel van uw gateway", "name": "Naam van de Gateway" }, - "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk" + "description": "De sleutel (wachtwoord) kan worden opgehaald met behulp van deze tutorial: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz. Als de sleutel niet wordt meegeleverd, zijn alleen sensoren toegankelijk", + "title": "Xiaomi Aqara Gateway, optionele instellingen" }, "user": { "data": { diff --git a/homeassistant/components/zoneminder/translations/ru.json b/homeassistant/components/zoneminder/translations/ru.json index bee720ee09a4da..f520f0e29bd27b 100644 --- a/homeassistant/components/zoneminder/translations/ru.json +++ b/homeassistant/components/zoneminder/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." @@ -10,7 +10,7 @@ "default": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440 ZoneMinder." }, "error": { - "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." @@ -24,7 +24,7 @@ "path": "\u041f\u0443\u0442\u044c \u043a ZM", "path_zms": "\u041f\u0443\u0442\u044c \u043a ZMS", "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": "\u041b\u043e\u0433\u0438\u043d", + "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" }, "title": "ZoneMinder" diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index 9ff130605efdd1..30254e2a523958 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,25 +1,49 @@ { "config": { "abort": { + "addon_install_failed": "Installation des Z-Wave JS Add-Ons fehlgeschlagen.", + "addon_start_failed": "Starten des Z-Wave JS Add-ons fehlgeschlagen.", "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "cannot_connect": "Verbindung fehlgeschlagen" }, "error": { + "addon_start_failed": "Fehler beim Starten des Z-Wave JS Add-Ons. \u00dcberpr\u00fcfe die Konfiguration.", "cannot_connect": "Verbindung fehlgeschlagen", "unknown": "Unerwarteter Fehler" }, + "progress": { + "install_addon": "Bitte warte, w\u00e4hrend die Installation des Z-Wave JS Add-ons abgeschlossen wird. Dies kann einige Minuten dauern.", + "start_addon": "Bitte warte, w\u00e4hrend der Start des Z-Wave JS Add-ons abgeschlossen wird. Dies kann einige Sekunden dauern." + }, "step": { "configure_addon": { "data": { "usb_path": "USB-Ger\u00e4te-Pfad" - } + }, + "title": "Gib die Konfiguration des Z-Wave JS Add-ons ein" + }, + "hassio_confirm": { + "title": "Einrichten der Z-Wave JS Integration mit dem Z-Wave JS Add-on" + }, + "install_addon": { + "title": "Die Installation des Z-Wave-JS-Add-ons hat begonnen" }, "manual": { "data": { "url": "URL" } }, + "on_supervisor": { + "data": { + "use_addon": "Verwende das Z-Wave JS Supervisor Add-on" + }, + "description": "M\u00f6chtest du das Z-Wave JS Supervisor Add-on verwenden?", + "title": "Verbindungstyp ausw\u00e4hlen" + }, + "start_addon": { + "title": "Z-Wave JS Add-on wird gestartet." + }, "user": { "data": { "url": "URL" From 470b0b8b876f71955148103ff5bf2d5fe603d1a9 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 17 Mar 2021 06:40:20 +0100 Subject: [PATCH 1316/1818] Fix historic attributes for input_datetime (#45208) --- homeassistant/components/history/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 1e22e45a89249d..dd98ba4ef252a8 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -71,6 +71,7 @@ NEED_ATTRIBUTE_DOMAINS = { "climate", "humidifier", + "input_datetime", "thermostat", "water_heater", } From bdc8a2878f2afba60df683c91a8db93de94ed7d1 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Wed, 17 Mar 2021 01:23:54 -0600 Subject: [PATCH 1317/1818] Fix issue with setting sleep mode during DST (#48001) --- homeassistant/components/litterrobot/hub.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 943ef5bfe37a6c..1847f37ed6490a 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -113,9 +113,12 @@ def parse_time_at_default_timezone(time_str: str) -> Optional[time]: if parsed_time is None: return None - return time( - hour=parsed_time.hour, - minute=parsed_time.minute, - second=parsed_time.second, - tzinfo=dt_util.DEFAULT_TIME_ZONE, + return ( + dt_util.start_of_local_day() + .replace( + hour=parsed_time.hour, + minute=parsed_time.minute, + second=parsed_time.second, + ) + .timetz() ) From ecf93e09e8f6bb8a0a8898243ef0091402700aa4 Mon Sep 17 00:00:00 2001 From: Jim Ekman Date: Wed, 17 Mar 2021 08:50:34 +0100 Subject: [PATCH 1318/1818] Add support for percentage based fan model in esphome (#46712) --- homeassistant/components/esphome/fan.py | 37 +++++++++++++++---- .../components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 8cf28d6b2aa231..bac35fdb93f8a0 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -1,4 +1,5 @@ """Support for ESPHome fans.""" +import math from typing import Optional from aioesphomeapi import FanDirection, FanInfo, FanSpeed, FanState @@ -16,6 +17,8 @@ from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, ) from . import ( @@ -62,6 +65,11 @@ def _static_info(self) -> FanInfo: def _state(self) -> Optional[FanState]: return super()._state + @property + def _supports_speed_levels(self) -> bool: + api_version = self._client.api_version + return api_version.major == 1 and api_version.minor > 3 + async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" if percentage == 0: @@ -70,10 +78,17 @@ async def async_set_percentage(self, percentage: int) -> None: data = {"key": self._static_info.key, "state": True} if percentage is not None: - named_speed = percentage_to_ordered_list_item( - ORDERED_NAMED_FAN_SPEEDS, percentage - ) - data["speed"] = named_speed + if self._supports_speed_levels: + data["speed_level"] = math.ceil( + percentage_to_ranged_value( + (1, self._static_info.supported_speed_levels), percentage + ) + ) + else: + named_speed = percentage_to_ordered_list_item( + ORDERED_NAMED_FAN_SPEEDS, percentage + ) + data["speed"] = named_speed await self._client.fan_command(**data) async def async_turn_on( @@ -115,14 +130,22 @@ def percentage(self) -> Optional[int]: """Return the current speed percentage.""" if not self._static_info.supports_speed: return None - return ordered_list_item_to_percentage( - ORDERED_NAMED_FAN_SPEEDS, self._state.speed + + if not self._supports_speed_levels: + return ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, self._state.speed + ) + + return ranged_value_to_percentage( + (1, self._static_info.supported_speed_levels), self._state.speed_level ) @property def speed_count(self) -> int: """Return the number of speeds the fan supports.""" - return len(ORDERED_NAMED_FAN_SPEEDS) + if not self._supports_speed_levels: + return len(ORDERED_NAMED_FAN_SPEEDS) + return self._static_info.supported_speed_levels @esphome_state_property def oscillating(self) -> None: diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 17ff2ca96ba30e..e3c609c9fad522 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==2.6.5"], + "requirements": ["aioesphomeapi==2.6.6"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter"], "after_dependencies": ["zeroconf", "tag"] diff --git a/requirements_all.txt b/requirements_all.txt index e685876c09d91b..0398f053ec3bae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -154,7 +154,7 @@ aiodns==2.0.0 aioeafm==0.1.2 # homeassistant.components.esphome -aioesphomeapi==2.6.5 +aioesphomeapi==2.6.6 # homeassistant.components.flo aioflo==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 00e6121b4854be..47c10b5700c536 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -91,7 +91,7 @@ aiodns==2.0.0 aioeafm==0.1.2 # homeassistant.components.esphome -aioesphomeapi==2.6.5 +aioesphomeapi==2.6.6 # homeassistant.components.flo aioflo==0.4.1 From 4f3b7f917ff47a7d9c01a1d0ffe3ed51027b9b34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Mar 2021 09:52:17 +0100 Subject: [PATCH 1319/1818] Bump codecov/codecov-action from v1.2.2 to v1.3.1 (#48020) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from v1.2.2 to v1.3.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1.2.2...fcebab03f26c7530a22baa63f06b3e0515f0c7cd) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f395a4b3c425eb..afee814b432701 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -739,4 +739,4 @@ jobs: coverage report --fail-under=94 coverage xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.2.2 + uses: codecov/codecov-action@v1.3.1 From 6f7f4955a359e5c711d505366ca58f8ecb58435f Mon Sep 17 00:00:00 2001 From: Nathan Tilley Date: Wed, 17 Mar 2021 06:11:39 -0400 Subject: [PATCH 1320/1818] Add Logger Check Before Adding Another (#47954) --- script/setup | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/setup b/script/setup index 46865ecfcb6547..f827c3a373f5a3 100755 --- a/script/setup +++ b/script/setup @@ -28,9 +28,11 @@ python3 -m pip install -e . --constraint homeassistant/package_constraints.txt hass --script ensure_config -c config +if ! grep -R "logger" config/configuration.yaml >> /dev/null;then echo " logger: default: info logs: homeassistant.components.cloud: debug " >> config/configuration.yaml +fi \ No newline at end of file From 009e44ed9b7e2c2fad662cfea23e6490d0e24a42 Mon Sep 17 00:00:00 2001 From: sycx2 Date: Wed, 17 Mar 2021 11:30:44 +0100 Subject: [PATCH 1321/1818] Rewrite tests for Template Light (#41163) --- tests/components/template/test_light.py | 1721 ++++++++++++----------- 1 file changed, 882 insertions(+), 839 deletions(-) diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 1b99e1cec0a198..b3a4b2a1aa4b0d 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -4,17 +4,23 @@ import pytest from homeassistant import setup +import homeassistant.components.light as light from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ) -from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE -from homeassistant.core import callback +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) -from tests.common import assert_setup_component, get_test_home_assistant -from tests.components.light import common +from tests.common import assert_setup_component, async_mock_service _LOGGER = logging.getLogger(__name__) @@ -22,284 +28,619 @@ _STATE_AVAILABILITY_BOOLEAN = "availability_boolean.state" -class TestTemplateLight: - """Test the Template light.""" - - hass = None - calls = None - # pylint: disable=invalid-name - - def setup_method(self, method): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.calls = [] - - @callback - def record_call(service): - """Track function calls.""" - self.calls.append(service) - - self.hass.services.register("test", "automation", record_call) - - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - - def test_template_state_invalid(self): - """Test template state with render error.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.test['big.fat...']}}", - "turn_on": { - "service": "light.turn_on", +@pytest.fixture(name="calls") +def fixture_calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_template_state_invalid(hass): + """Test template state with render error.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{states.test['big.fat...']}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "turn_off": { - "service": "light.turn_off", + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + +async def test_template_state_text(hass): + """Test the state text of a template.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{ states.light.test_state.state }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF - - def test_template_state_text(self): - """Test the state text of a template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ states.light.test_state.state }}", - "turn_on": { - "service": "light.turn_on", + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_ON + + state = hass.states.async_set("light.test_state", STATE_OFF) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + +@pytest.mark.parametrize( + "expected_state,template", + [(STATE_ON, "{{ 1 == 1 }}"), (STATE_OFF, "{{ 1 == 2 }}")], +) +async def test_template_state_boolean(hass, expected_state, template): + """Test the setting of the state with boolean on.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": template, + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "turn_off": { - "service": "light.turn_off", + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == expected_state + + +async def test_template_syntax_error(hass): + """Test templating syntax error.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{%- if false -%}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +async def test_invalid_name_does_not_create(hass): + """Test invalid name.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "bad name here": { + "value_template": "{{ 1== 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, - } + }, + } + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +async def test_invalid_light_does_not_create(hass): + """Test invalid light.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "switches": {"test_template_light": "Invalid"}, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +async def test_no_lights_does_not_create(hass): + """Test if there are no lights no creation.""" + with assert_setup_component(0, light.DOMAIN): + assert await setup.async_setup_component( + hass, "light", {"light": {"platform": "template"}} + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.async_all() == [] + + +@pytest.mark.parametrize( + "missing_key, count", [("value_template", 1), ("turn_on", 0), ("turn_off", 0)] +) +async def test_missing_key(hass, missing_key, count): + """Test missing template.""" + light_config = { + "light": { + "platform": "template", + "lights": { + "light_one": { + "value_template": "{{ 1== 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, + }, + } + }, + } + } + + del light_config["light"]["lights"]["light_one"][missing_key] + with assert_setup_component(count, light.DOMAIN): + assert await setup.async_setup_component(hass, "light", light_config) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + if count: + assert hass.states.async_all() != [] + else: + assert hass.states.async_all() == [] + + +async def test_on_action(hass, calls): + """Test on action.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{states.light.test_state.state}}", + "turn_on": {"service": "test.automation"}, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, }, } }, - ) + } + }, + ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - state = self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() + hass.states.async_set("light.test_state", STATE_OFF) + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_ON + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF - state = self.hass.states.set("light.test_state", STATE_OFF) - self.hass.block_till_done() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + assert len(calls) == 1 - @pytest.mark.parametrize( - "expected_state,template", - [(STATE_ON, "{{ 1 == 1 }}"), (STATE_OFF, "{{ 1 == 2 }}")], - ) - def test_template_state_boolean(self, expected_state, template): - """Test the setting of the state with boolean on.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": template, - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } + +async def test_on_action_optimistic(hass, calls): + """Test on action with optimistic state.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "turn_on": {"service": "test.automation"}, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, }, } }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == expected_state - - def test_template_syntax_error(self): - """Test templating syntax error.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{%- if false -%}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + hass.states.async_set("light.test_state", STATE_OFF) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) + + state = hass.states.get("light.test_template_light") + assert len(calls) == 1 + assert state.state == STATE_ON + + +async def test_off_action(hass, calls): + """Test off action.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{states.light.test_state.state}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": {"service": "test.automation"}, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, }, } }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_invalid_name_does_not_create(self): - """Test invalid name.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "bad name here": { - "value_template": "{{ 1== 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_ON + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) + + assert len(calls) == 1 + + +async def test_off_action_optimistic(hass, calls): + """Test off action with optimistic state.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": {"service": "test.automation"}, + "set_level": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "brightness": "{{brightness}}", + }, }, } }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_invalid_light_does_not_create(self): - """Test invalid light.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "switches": {"test_template_light": "Invalid"}, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.test_template_light"}, + blocking=True, + ) + + assert len(calls) == 1 + state = hass.states.get("light.test_template_light") + assert state.state == STATE_OFF + + +async def test_white_value_action_no_template(hass, calls): + """Test setting white value with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_white_value": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "white_value": "{{white_value}}", + }, + }, } }, - ) + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("white_value") is None + + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_WHITE_VALUE: 124}, + blocking=True, + ) + + assert len(calls) == 1 + assert calls[0].data["white_value"] == 124 + + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("white_value") == 124 - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - assert self.hass.states.all() == [] +@pytest.mark.parametrize( + "expected_white_value,template", + [ + (255, "{{255}}"), + (None, "{{256}}"), + (None, "{{x - 12}}"), + (None, "{{ none }}"), + (None, ""), + ], +) +async def test_white_value_template(hass, expected_white_value, template): + """Test the template for the white value.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_white_value": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "white_value": "{{white_value}}", + }, + }, + "white_value_template": template, + } + }, + } + }, + ) - def test_no_lights_does_not_create(self): - """Test if there are no lights no creation.""" - with assert_setup_component(0, "light"): - assert setup.setup_component( - self.hass, "light", {"light": {"platform": "template"}} - ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("white_value") == expected_white_value - assert self.hass.states.all() == [] - @pytest.mark.parametrize( - "missing_key, count", [("value_template", 1), ("turn_on", 0), ("turn_off", 0)] - ) - def test_missing_key(self, missing_key, count): - """Test missing template.""" - light = { +async def test_level_action_no_template(hass, calls): + """Test setting brightness with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { "light": { "platform": "template", "lights": { - "light_one": { - "value_template": "{{ 1== 1}}", + "test_template_light": { + "value_template": "{{1 == 1}}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -309,84 +650,66 @@ def test_missing_key(self, missing_key, count): "entity_id": "light.test_state", }, "set_level": { - "service": "light.turn_on", + "service": "test.automation", "data_template": { - "entity_id": "light.test_state", + "entity_id": "test.test_state", "brightness": "{{brightness}}", }, }, } }, } - } - - del light["light"]["lights"]["light_one"][missing_key] - with assert_setup_component(count, "light"): - assert setup.setup_component(self.hass, "light", light) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - if count: - assert self.hass.states.all() != [] - else: - assert self.hass.states.all() == [] - - def test_on_action(self): - """Test on action.""" - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{states.light.test_state.state}}", - "turn_on": {"service": "test.automation"}, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + assert state.attributes.get("brightness") is None - self.hass.states.set("light.test_state", STATE_OFF) - self.hass.block_till_done() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_BRIGHTNESS: 124}, + blocking=True, + ) - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + assert len(calls) == 1 + assert calls[0].data["brightness"] == 124 - common.turn_on(self.hass, "light.test_template_light") - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + _LOGGER.info(str(state.attributes)) + assert state is not None + assert state.attributes.get("brightness") == 124 - assert len(self.calls) == 1 - def test_on_action_optimistic(self): - """Test on action with optimistic state.""" - assert setup.setup_component( - self.hass, - "light", +@pytest.mark.parametrize( + "expected_level,template", + [ + (255, "{{255}}"), + (None, "{{256}}"), + (None, "{{x - 12}}"), + (None, "{{ none }}"), + (None, ""), + ], +) +async def test_level_template(hass, expected_level, template): + """Test the template for the level.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "turn_on": {"service": "test.automation"}, + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, "turn_off": { "service": "light.turn_off", "entity_id": "light.test_state", @@ -398,88 +721,151 @@ def test_on_action_optimistic(self): "brightness": "{{brightness}}", }, }, + "level_template": template, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - self.hass.states.set("light.test_state", STATE_OFF) - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF - - common.turn_on(self.hass, "light.test_template_light") - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert len(self.calls) == 1 - assert state.state == STATE_ON + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - def test_off_action(self): - """Test off action.""" - assert setup.setup_component( - self.hass, - "light", + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("brightness") == expected_level + + +@pytest.mark.parametrize( + "expected_temp,template", + [ + (500, "{{500}}"), + (None, "{{501}}"), + (None, "{{x - 12}}"), + (None, "None"), + (None, "{{ none }}"), + (None, ""), + ], +) +async def test_temperature_template(hass, expected_temp, template): + """Test the template for the temperature.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{states.light.test_state.state}}", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", }, - "turn_off": {"service": "test.automation"}, - "set_level": { + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_temperature": { "service": "light.turn_on", "data_template": { "entity_id": "light.test_state", - "brightness": "{{brightness}}", + "color_temp": "{{color_temp}}", }, }, + "temperature_template": template, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("color_temp") == expected_temp + + +async def test_temperature_action_no_template(hass, calls): + """Test setting temperature with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_temperature": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "color_temp": "{{color_temp}}", + }, + }, + } + }, + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("color_template") is None - self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_COLOR_TEMP: 345}, + blocking=True, + ) - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_ON + assert len(calls) == 1 + assert calls[0].data["color_temp"] == 345 - common.turn_off(self.hass, "light.test_template_light") - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") + _LOGGER.info(str(state.attributes)) + assert state is not None + assert state.attributes.get("color_temp") == 345 - assert len(self.calls) == 1 - def test_off_action_optimistic(self): - """Test off action with optimistic state.""" - assert setup.setup_component( - self.hass, - "light", +async def test_friendly_name(hass): + """Test the accessibility of the friendly_name attribute.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", }, - "turn_off": {"service": "test.automation"}, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, "set_level": { "service": "light.turn_on", "data_template": { @@ -493,31 +879,29 @@ def test_off_action_optimistic(self): }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF + state = hass.states.get("light.test_template_light") + assert state is not None - common.turn_off(self.hass, "light.test_template_light") - self.hass.block_till_done() + assert state.attributes.get("friendly_name") == "Template light" - assert len(self.calls) == 1 - state = self.hass.states.get("light.test_template_light") - assert state.state == STATE_OFF - def test_white_value_action_no_template(self): - """Test setting white value with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", +async def test_icon_template(hass): + """Test icon template.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{1 == 1}}", + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -526,99 +910,50 @@ def test_white_value_action_no_template(self): "service": "light.turn_off", "entity_id": "light.test_state", }, - "set_white_value": { - "service": "test.automation", + "set_level": { + "service": "light.turn_on", "data_template": { - "entity_id": "test.test_state", - "white_value": "{{white_value}}", + "entity_id": "light.test_state", + "brightness": "{{brightness}}", }, }, + "icon_template": "{% if states.light.test_state.state %}" + "mdi:check" + "{% endif %}", } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("white_value") is None + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + assert state.attributes.get("icon") == "" - common.turn_on( - self.hass, "light.test_template_light", **{ATTR_WHITE_VALUE: 124} - ) - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data["white_value"] == 124 - - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("white_value") == 124 - - @pytest.mark.parametrize( - "expected_white_value,template", - [ - (255, "{{255}}"), - (None, "{{256}}"), - (None, "{{x - 12}}"), - (None, "{{ none }}"), - (None, ""), - ], - ) - def test_white_value_template(self, expected_white_value, template): - """Test the template for the white value.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_white_value": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "white_value": "{{white_value}}", - }, - }, - "white_value_template": template, - } - }, - } - }, - ) + state = hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + state = hass.states.get("light.test_template_light") - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("white_value") == expected_white_value + assert state.attributes["icon"] == "mdi:check" - def test_level_action_no_template(self): - """Test setting brightness with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", + +async def test_entity_picture_template(hass): + """Test entity_picture template.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{1 == 1}}", + "friendly_name": "Template light", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -628,343 +963,131 @@ def test_level_action_no_template(self): "entity_id": "light.test_state", }, "set_level": { - "service": "test.automation", + "service": "light.turn_on", "data_template": { - "entity_id": "test.test_state", + "entity_id": "light.test_state", "brightness": "{{brightness}}", }, }, + "entity_picture_template": "{% if states.light.test_state.state %}" + "/local/light.png" + "{% endif %}", } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("brightness") is None - - common.turn_on(self.hass, "light.test_template_light", **{ATTR_BRIGHTNESS: 124}) - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data["brightness"] == 124 - - state = self.hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert state.attributes.get("brightness") == 124 - - @pytest.mark.parametrize( - "expected_level,template", - [ - (255, "{{255}}"), - (None, "{{256}}"), - (None, "{{x - 12}}"), - (None, "{{ none }}"), - (None, ""), - ], - ) - def test_level_template(self, expected_level, template): - """Test the template for the level.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "level_template": template, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("brightness") == expected_level - - @pytest.mark.parametrize( - "expected_temp,template", - [ - (500, "{{500}}"), - (None, "{{501}}"), - (None, "{{x - 12}}"), - (None, "None"), - (None, "{{ none }}"), - (None, ""), - ], - ) - def test_temperature_template(self, expected_temp, template): - """Test the template for the temperature.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_temperature": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "color_temp": "{{color_temp}}", - }, - }, - "temperature_template": template, - } - }, - } - }, - ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("color_temp") == expected_temp + state = hass.states.get("light.test_template_light") + assert state.attributes.get("entity_picture") == "" - def test_temperature_action_no_template(self): - """Test setting temperature with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{1 == 1}}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_temperature": { + state = hass.states.async_set("light.test_state", STATE_ON) + await hass.async_block_till_done() + + state = hass.states.get("light.test_template_light") + + assert state.attributes["entity_picture"] == "/local/light.png" + + +async def test_color_action_no_template(hass, calls): + """Test setting color with optimistic template.""" + assert await setup.async_setup_component( + hass, + light.DOMAIN, + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_color": [ + { "service": "test.automation", "data_template": { "entity_id": "test.test_state", - "color_temp": "{{color_temp}}", + "h": "{{h}}", + "s": "{{s}}", }, }, - } - }, - } - }, - ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("color_template") is None - - common.turn_on(self.hass, "light.test_template_light", **{ATTR_COLOR_TEMP: 345}) - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data["color_temp"] == 345 - - state = self.hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert state.attributes.get("color_temp") == 345 - - def test_friendly_name(self): - """Test the accessibility of the friendly_name attribute.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state is not None - - assert state.attributes.get("friendly_name") == "Template light" - - def test_icon_template(self): - """Test icon template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, - }, - "icon_template": "{% if states.light.test_state.state %}" - "mdi:check" - "{% endif %}", - } - }, - } - }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("icon") == "" - - state = self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - - assert state.attributes["icon"] == "mdi:check" - - def test_entity_picture_template(self): - """Test entity_picture template.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "friendly_name": "Template light", - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_level": { - "service": "light.turn_on", - "data_template": { - "entity_id": "light.test_state", - "brightness": "{{brightness}}", - }, + { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "s": "{{s}}", + "h": "{{h}}", }, - "entity_picture_template": "{% if states.light.test_state.state %}" - "/local/light.png" - "{% endif %}", - } - }, + }, + ], } }, - ) - - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("entity_picture") == "" - - state = self.hass.states.set("light.test_state", STATE_ON) - self.hass.block_till_done() + } + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() - state = self.hass.states.get("light.test_template_light") + state = hass.states.get("light.test_template_light") + assert state.attributes.get("hs_color") is None - assert state.attributes["entity_picture"] == "/local/light.png" + await hass.services.async_call( + light.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.test_template_light", ATTR_HS_COLOR: (40, 50)}, + blocking=True, + ) - def test_color_action_no_template(self): - """Test setting color with optimistic template.""" - assert setup.setup_component( - self.hass, - "light", + assert len(calls) == 2 + assert calls[0].data["h"] == 40 + assert calls[0].data["s"] == 50 + assert calls[1].data["h"] == 40 + assert calls[1].data["s"] == 50 + + state = hass.states.get("light.test_template_light") + _LOGGER.info(str(state.attributes)) + assert state is not None + assert calls[0].data["h"] == 40 + assert calls[0].data["s"] == 50 + assert calls[1].data["h"] == 40 + assert calls[1].data["s"] == 50 + + +@pytest.mark.parametrize( + "expected_hs,template", + [ + ((360, 100), "{{(360, 100)}}"), + ((359.9, 99.9), "{{(359.9, 99.9)}}"), + (None, "{{(361, 100)}}"), + (None, "{{(360, 101)}}"), + (None, "{{x - 12}}"), + (None, ""), + (None, "{{ none }}"), + ], +) +async def test_color_template(hass, expected_hs, template): + """Test the template for the color.""" + with assert_setup_component(1, light.DOMAIN): + assert await setup.async_setup_component( + hass, + light.DOMAIN, { "light": { "platform": "template", "lights": { "test_template_light": { - "value_template": "{{1 == 1}}", + "value_template": "{{ 1 == 1 }}", "turn_on": { "service": "light.turn_on", "entity_id": "light.test_state", @@ -975,112 +1098,32 @@ def test_color_action_no_template(self): }, "set_color": [ { - "service": "test.automation", - "data_template": { - "entity_id": "test.test_state", - "h": "{{h}}", - "s": "{{s}}", - }, - }, - { - "service": "test.automation", + "service": "input_number.set_value", "data_template": { - "entity_id": "test.test_state", - "s": "{{s}}", - "h": "{{h}}", + "entity_id": "input_number.h", + "color_temp": "{{h}}", }, - }, + } ], + "color_template": template, } }, } }, ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - - state = self.hass.states.get("light.test_template_light") - assert state.attributes.get("hs_color") is None - - common.turn_on( - self.hass, "light.test_template_light", **{ATTR_HS_COLOR: (40, 50)} - ) - self.hass.block_till_done() - assert len(self.calls) == 2 - assert self.calls[0].data["h"] == 40 - assert self.calls[0].data["s"] == 50 - assert self.calls[1].data["h"] == 40 - assert self.calls[1].data["s"] == 50 - - state = self.hass.states.get("light.test_template_light") - _LOGGER.info(str(state.attributes)) - assert state is not None - assert self.calls[0].data["h"] == 40 - assert self.calls[0].data["s"] == 50 - assert self.calls[1].data["h"] == 40 - assert self.calls[1].data["s"] == 50 - - @pytest.mark.parametrize( - "expected_hs,template", - [ - ((360, 100), "{{(360, 100)}}"), - ((359.9, 99.9), "{{(359.9, 99.9)}}"), - (None, "{{(361, 100)}}"), - (None, "{{(360, 101)}}"), - (None, "{{x - 12}}"), - (None, ""), - (None, "{{ none }}"), - ], - ) - def test_color_template(self, expected_hs, template): - """Test the template for the color.""" - with assert_setup_component(1, "light"): - assert setup.setup_component( - self.hass, - "light", - { - "light": { - "platform": "template", - "lights": { - "test_template_light": { - "value_template": "{{ 1 == 1 }}", - "turn_on": { - "service": "light.turn_on", - "entity_id": "light.test_state", - }, - "turn_off": { - "service": "light.turn_off", - "entity_id": "light.test_state", - }, - "set_color": [ - { - "service": "input_number.set_value", - "data_template": { - "entity_id": "input_number.h", - "color_temp": "{{h}}", - }, - } - ], - "color_template": template, - } - }, - } - }, - ) - self.hass.block_till_done() - self.hass.start() - self.hass.block_till_done() - state = self.hass.states.get("light.test_template_light") - assert state is not None - assert state.attributes.get("hs_color") == expected_hs + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + state = hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("hs_color") == expected_hs async def test_available_template_with_entities(hass): """Test availability templates with values from other entities.""" await setup.async_setup_component( hass, - "light", + light.DOMAIN, { "light": { "platform": "template", @@ -1130,7 +1173,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap """Test that an invalid availability keeps the device available.""" await setup.async_setup_component( hass, - "light", + light.DOMAIN, { "light": { "platform": "template", @@ -1170,7 +1213,7 @@ async def test_unique_id(hass): """Test unique_id option only creates one light per id.""" await setup.async_setup_component( hass, - "light", + light.DOMAIN, { "light": { "platform": "template", From a1ff45cbf29abefd283d86bdf870663757a3b28e Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 14:32:13 +0100 Subject: [PATCH 1322/1818] Update metadata license string (#46899) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index dbdb61b5fcf9e2..d5f8b9da16b69a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -license = Apache License 2.0 +license = Apache-2.0 license_file = LICENSE.md platforms = any description = Open-source home automation platform running on Python 3. From 6a24ec7a3097213024cc3fd4d377430980e9ed69 Mon Sep 17 00:00:00 2001 From: schiermi Date: Wed, 17 Mar 2021 15:32:18 +0100 Subject: [PATCH 1323/1818] Fix workday sensor to honor timezone (#47927) --- homeassistant/components/workday/binary_sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 44f30cf95381e1..7a4140359002ba 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -1,5 +1,5 @@ """Sensor to indicate whether the current day is a workday.""" -from datetime import datetime, timedelta +from datetime import timedelta import logging from typing import Any @@ -9,6 +9,7 @@ from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME, WEEKDAYS import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt _LOGGER = logging.getLogger(__name__) @@ -77,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensor_name = config[CONF_NAME] workdays = config[CONF_WORKDAYS] - year = (get_date(datetime.today()) + timedelta(days=days_offset)).year + year = (get_date(dt.now()) + timedelta(days=days_offset)).year obj_holidays = getattr(holidays, country)(years=year) if province: @@ -185,7 +186,7 @@ async def async_update(self): self._state = False # Get ISO day of the week (1 = Monday, 7 = Sunday) - date = get_date(datetime.today()) + timedelta(days=self._days_offset) + date = get_date(dt.now()) + timedelta(days=self._days_offset) day = date.isoweekday() - 1 day_of_week = day_to_string(day) From 9011a54e7f0c8b3a10fc648b9a2bdd8660efb42b Mon Sep 17 00:00:00 2001 From: Matthew Donoughe Date: Wed, 17 Mar 2021 10:32:44 -0400 Subject: [PATCH 1324/1818] Switch history tests to pytest (#42318) Co-authored-by: Martin Hjelmare --- tests/components/history/conftest.py | 49 + tests/components/history/test_init.py | 1405 ++++++++++++------------- 2 files changed, 733 insertions(+), 721 deletions(-) create mode 100644 tests/components/history/conftest.py diff --git a/tests/components/history/conftest.py b/tests/components/history/conftest.py new file mode 100644 index 00000000000000..35829ece721dc2 --- /dev/null +++ b/tests/components/history/conftest.py @@ -0,0 +1,49 @@ +"""Fixtures for history tests.""" +import pytest + +from homeassistant.components import history +from homeassistant.components.recorder.const import DATA_INSTANCE +from homeassistant.setup import setup_component + +from tests.common import get_test_home_assistant, init_recorder_component + + +@pytest.fixture +def hass_recorder(): + """Home Assistant fixture with in-memory recorder.""" + hass = get_test_home_assistant() + + def setup_recorder(config=None): + """Set up with params.""" + init_recorder_component(hass, config) + hass.start() + hass.block_till_done() + hass.data[DATA_INSTANCE].block_till_done() + return hass + + yield setup_recorder + hass.stop() + + +@pytest.fixture +def hass_history(hass_recorder): + """Home Assistant fixture with history.""" + hass = hass_recorder() + + config = history.CONFIG_SCHEMA( + { + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_DOMAINS: ["media_player"], + history.CONF_ENTITIES: ["thermostat.test"], + }, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], + }, + } + } + ) + assert setup_component(hass, history.DOMAIN, config) + + yield hass diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 87e18305b8ab2a..f4d1c817858731 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -3,760 +3,723 @@ from copy import copy from datetime import timedelta import json -import unittest from unittest.mock import patch, sentinel +import pytest + from homeassistant.components import history, recorder from homeassistant.components.recorder.models import process_timestamp import homeassistant.core as ha from homeassistant.helpers.json import JSONEncoder -from homeassistant.setup import async_setup_component, setup_component +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.common import ( - get_test_home_assistant, - init_recorder_component, - mock_state_change_event, -) +from tests.common import init_recorder_component, mock_state_change_event from tests.components.recorder.common import trigger_db_commit, wait_recording_done -class TestComponentHistory(unittest.TestCase): - """Test History component.""" - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - - def tear_down_cleanup(self): - """Stop everything that was started.""" - self.hass.stop() - - def init_recorder(self): - """Initialize the recorder.""" - init_recorder_component(self.hass) - self.hass.start() - wait_recording_done(self.hass) - - def test_setup(self): - """Test setup method of history.""" - config = history.CONFIG_SCHEMA( - { - # ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["media_player"], - history.CONF_ENTITIES: ["thermostat.test"], - }, - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - }, - } - } - ) - self.init_recorder() - assert setup_component(self.hass, history.DOMAIN, config) - - def test_get_states(self): - """Test getting states at a specific point in time.""" - self.test_setup() - states = [] - - now = dt_util.utcnow() - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=now - ): - for i in range(5): - state = ha.State( - "test.point_in_time_{}".format(i % 5), - f"State {i}", - {"attribute_test": i}, - ) - - mock_state_change_event(self.hass, state) - - states.append(state) - - wait_recording_done(self.hass) - - future = now + timedelta(seconds=1) - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=future - ): - for i in range(5): - state = ha.State( - "test.point_in_time_{}".format(i % 5), - f"State {i}", - {"attribute_test": i}, - ) - - mock_state_change_event(self.hass, state) - - wait_recording_done(self.hass) - - # Get states returns everything before POINT - for state1, state2 in zip( - states, - sorted( - history.get_states(self.hass, future), key=lambda state: state.entity_id - ), - ): - assert state1 == state2 - - # Test get_state here because we have a DB setup - assert states[0] == history.get_state(self.hass, future, states[0].entity_id) - - time_before_recorder_ran = now - timedelta(days=1000) - assert history.get_states(self.hass, time_before_recorder_ran) == [] - - assert history.get_state(self.hass, time_before_recorder_ran, "demo.id") is None - - def test_state_changes_during_period(self): - """Test state change during period.""" - self.test_setup() - entity_id = "media_player.test" - - def set_state(state): - """Set the state.""" - self.hass.states.set(entity_id, state) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - point = start + timedelta(seconds=1) - end = point + timedelta(seconds=1) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("idle") - set_state("YouTube") - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point - ): - states = [ - set_state("idle"), - set_state("Netflix"), - set_state("Plex"), - set_state("YouTube"), - ] - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=end - ): - set_state("Netflix") - set_state("Plex") - - hist = history.state_changes_during_period(self.hass, start, end, entity_id) - - assert states == hist[entity_id] - - def test_get_last_state_changes(self): - """Test number of state changes.""" - self.test_setup() - entity_id = "sensor.test" - - def set_state(state): - """Set the state.""" - self.hass.states.set(entity_id, state) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - timedelta(minutes=2) - point = start + timedelta(minutes=1) - point2 = point + timedelta(minutes=1) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("1") - - states = [] - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point - ): - states.append(set_state("2")) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point2 - ): - states.append(set_state("3")) - - hist = history.get_last_state_changes(self.hass, 2, entity_id) - - assert states == hist[entity_id] - - def test_ensure_state_can_be_copied(self): - """Ensure a state can pass though copy(). - - The filter integration uses copy() on states - from history. - """ - self.test_setup() - entity_id = "sensor.test" - - def set_state(state): - """Set the state.""" - self.hass.states.set(entity_id, state) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - timedelta(minutes=2) - point = start + timedelta(minutes=1) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("1") - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=point - ): - set_state("2") - - hist = history.get_last_state_changes(self.hass, 2, entity_id) - - assert copy(hist[entity_id][0]) == hist[entity_id][0] - assert copy(hist[entity_id][1]) == hist[entity_id][1] - - def test_get_significant_states(self): - """Test that only significant states are returned. - - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - hist = history.get_significant_states( - self.hass, zero, four, filters=history.Filters() - ) - assert states == hist +@pytest.mark.usefixtures("hass_history") +def test_setup(): + """Test setup method of history.""" + # Verification occurs in the fixture + pass - def test_get_significant_states_minimal_response(self): - """Test that only significant states are returned. - When minimal responses is set only the first and - last states return a complete state. +def test_get_states(hass_history): + """Test getting states at a specific point in time.""" + hass = hass_history + states = [] - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - hist = history.get_significant_states( - self.hass, zero, four, filters=history.Filters(), minimal_response=True - ) + now = dt_util.utcnow() + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=now): + for i in range(5): + state = ha.State( + "test.point_in_time_{}".format(i % 5), + f"State {i}", + {"attribute_test": i}, + ) - # The second media_player.test state is reduced - # down to last_changed and state when minimal_response - # is set. We use JSONEncoder to make sure that are - # pre-encoded last_changed is always the same as what - # will happen with encoding a native state - input_state = states["media_player.test"][1] - orig_last_changed = json.dumps( - process_timestamp(input_state.last_changed), - cls=JSONEncoder, - ).replace('"', "") - orig_state = input_state.state - states["media_player.test"][1] = { - "last_changed": orig_last_changed, - "state": orig_state, - } + mock_state_change_event(hass, state) - assert states == hist - - def test_get_significant_states_with_initial(self): - """Test that only significant states are returned. - - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - one = zero + timedelta(seconds=1) - one_and_half = zero + timedelta(seconds=1.5) - for entity_id in states: - if entity_id == "media_player.test": - states[entity_id] = states[entity_id][1:] - for state in states[entity_id]: - if state.last_changed == one: - state.last_changed = one_and_half - - hist = history.get_significant_states( - self.hass, - one_and_half, - four, - filters=history.Filters(), - include_start_time_state=True, - ) - assert states == hist - - def test_get_significant_states_without_initial(self): - """Test that only significant states are returned. - - We should get back every thermostat change that - includes an attribute change, but only the state updates for - media player (attribute changes are not significant and not returned). - """ - zero, four, states = self.record_states() - one = zero + timedelta(seconds=1) - one_and_half = zero + timedelta(seconds=1.5) - for entity_id in states: - states[entity_id] = list( - filter(lambda s: s.last_changed != one, states[entity_id]) + states.append(state) + + wait_recording_done(hass) + + future = now + timedelta(seconds=1) + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=future): + for i in range(5): + state = ha.State( + "test.point_in_time_{}".format(i % 5), + f"State {i}", + {"attribute_test": i}, ) - del states["media_player.test2"] - - hist = history.get_significant_states( - self.hass, - one_and_half, - four, - filters=history.Filters(), - include_start_time_state=False, - ) - assert states == hist - - def test_get_significant_states_entity_id(self): - """Test that only significant states are returned for one entity.""" - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - hist = history.get_significant_states( - self.hass, zero, four, ["media_player.test"], filters=history.Filters() - ) - assert states == hist - - def test_get_significant_states_multiple_entity_ids(self): - """Test that only significant states are returned for one entity.""" - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - hist = history.get_significant_states( - self.hass, - zero, - four, - ["media_player.test", "thermostat.test"], - filters=history.Filters(), - ) - assert states == hist - - def test_get_significant_states_exclude_domain(self): - """Test if significant states are returned when excluding domains. - - We should get back every thermostat change that includes an attribute - change, but no media player changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]} - }, - } + + mock_state_change_event(hass, state) + + wait_recording_done(hass) + + # Get states returns everything before POINT + for state1, state2 in zip( + states, + sorted(history.get_states(hass, future), key=lambda state: state.entity_id), + ): + assert state1 == state2 + + # Test get_state here because we have a DB setup + assert states[0] == history.get_state(hass, future, states[0].entity_id) + + time_before_recorder_ran = now - timedelta(days=1000) + assert history.get_states(hass, time_before_recorder_ran) == [] + + assert history.get_state(hass, time_before_recorder_ran, "demo.id") is None + + +def test_state_changes_during_period(hass_history): + """Test state change during period.""" + hass = hass_history + entity_id = "media_player.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() + point = start + timedelta(seconds=1) + end = point + timedelta(seconds=1) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("idle") + set_state("YouTube") + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + states = [ + set_state("idle"), + set_state("Netflix"), + set_state("Plex"), + set_state("YouTube"), + ] + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=end): + set_state("Netflix") + set_state("Plex") + + hist = history.state_changes_during_period(hass, start, end, entity_id) + + assert states == hist[entity_id] + + +def test_get_last_state_changes(hass_history): + """Test number of state changes.""" + hass = hass_history + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + point2 = point + timedelta(minutes=1) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("1") + + states = [] + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + states.append(set_state("2")) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point2): + states.append(set_state("3")) + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert states == hist[entity_id] + + +def test_ensure_state_can_be_copied(hass_history): + """Ensure a state can pass though copy(). + + The filter integration uses copy() on states + from history. + """ + hass = hass_history + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("1") + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=point): + set_state("2") + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert copy(hist[entity_id][0]) == hist[entity_id][0] + assert copy(hist[entity_id][1]) == hist[entity_id][1] + + +def test_get_significant_states(hass_history): + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + hist = history.get_significant_states(hass, zero, four, filters=history.Filters()) + assert states == hist + + +def test_get_significant_states_minimal_response(hass_history): + """Test that only significant states are returned. + + When minimal responses is set only the first and + last states return a complete state. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + hist = history.get_significant_states( + hass, zero, four, filters=history.Filters(), minimal_response=True + ) + + # The second media_player.test state is reduced + # down to last_changed and state when minimal_response + # is set. We use JSONEncoder to make sure that are + # pre-encoded last_changed is always the same as what + # will happen with encoding a native state + input_state = states["media_player.test"][1] + orig_last_changed = json.dumps( + process_timestamp(input_state.last_changed), + cls=JSONEncoder, + ).replace('"', "") + orig_state = input_state.state + states["media_player.test"][1] = { + "last_changed": orig_last_changed, + "state": orig_state, + } + + assert states == hist + + +def test_get_significant_states_with_initial(hass_history): + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + one = zero + timedelta(seconds=1) + one_and_half = zero + timedelta(seconds=1.5) + for entity_id in states: + if entity_id == "media_player.test": + states[entity_id] = states[entity_id][1:] + for state in states[entity_id]: + if state.last_changed == one: + state.last_changed = one_and_half + + hist = history.get_significant_states( + hass, + one_and_half, + four, + filters=history.Filters(), + include_start_time_state=True, + ) + assert states == hist + + +def test_get_significant_states_without_initial(hass_history): + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_history + zero, four, states = record_states(hass) + one = zero + timedelta(seconds=1) + one_and_half = zero + timedelta(seconds=1.5) + for entity_id in states: + states[entity_id] = list( + filter(lambda s: s.last_changed != one, states[entity_id]) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_exclude_entity(self): - """Test if significant states are returned when excluding entities. - - We should get back every thermostat and script changes, but no media - player changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} + del states["media_player.test2"] + + hist = history.get_significant_states( + hass, + one_and_half, + four, + filters=history.Filters(), + include_start_time_state=False, + ) + assert states == hist + + +def test_get_significant_states_entity_id(hass_history): + """Test that only significant states are returned for one entity.""" + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + hist = history.get_significant_states( + hass, zero, four, ["media_player.test"], filters=history.Filters() + ) + assert states == hist + + +def test_get_significant_states_multiple_entity_ids(hass_history): + """Test that only significant states are returned for one entity.""" + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + hist = history.get_significant_states( + hass, + zero, + four, + ["media_player.test", "thermostat.test"], + filters=history.Filters(), + ) + assert states == hist + + +def test_get_significant_states_exclude_domain(hass_history): + """Test if significant states are returned when excluding domains. + + We should get back every thermostat change that includes an attribute + change, but no media player changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_exclude_entity(hass_history): + """Test if significant states are returned when excluding entities. + + We should get back every thermostat and script changes, but no media + player changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_exclude(hass_history): + """Test significant states when excluding entities and domains. + + We should not get back every thermostat and media player test changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["thermostat.test"] + del states["thermostat.test2"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], + } + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_exclude_include_entity(hass_history): + """Test significant states when excluding domains and include entities. + + We should not get back every thermostat and media player test changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_ENTITIES: ["media_player.test", "thermostat.test"] }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_exclude(self): - """Test significant states when excluding entities and domains. - - We should not get back every thermostat and media player test changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["thermostat.test"] - del states["thermostat.test2"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - } + history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["thermostat"]}, + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_domain(hass_history): + """Test if significant states are returned when including domains. + + We should get back every thermostat and script changes, but no media + player changes. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_DOMAINS: ["thermostat", "script"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_entity(hass_history): + """Test if significant states are returned when including entities. + + We should only get back changes of the media_player.test entity. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include(hass_history): + """Test significant states when including domains and entities. + + We should only get back changes of the media_player.test entity and the + thermostat domain. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], + } + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_exclude_domain(hass_history): + """Test if significant states when excluding and including domains. + + We should not get back any changes since we include only the + media_player domain but also exclude it. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_DOMAINS: ["media_player"]}, + history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]}, + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_exclude_entity(hass_history): + """Test if significant states when excluding and including domains. + + We should not get back any changes since we include only + media_player.test but also exclude it. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]}, + history.CONF_EXCLUDE: {history.CONF_ENTITIES: ["media_player.test"]}, + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_include_exclude(hass_history): + """Test if significant states when in/excluding domains and entities. + + We should only get back changes of the media_player.test2 entity. + """ + hass = hass_history + zero, four, states = record_states(hass) + del states["media_player.test"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["script.can_cancel_this_one"] + + config = history.CONFIG_SCHEMA( + { + ha.DOMAIN: {}, + history.DOMAIN: { + history.CONF_INCLUDE: { + history.CONF_DOMAINS: ["media_player"], + history.CONF_ENTITIES: ["thermostat.test"], }, - } - ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_exclude_include_entity(self): - """Test significant states when excluding domains and include entities. - - We should not get back every thermostat and media player test changes. - """ - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_ENTITIES: ["media_player.test", "thermostat.test"] - }, - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["thermostat"]}, + history.CONF_EXCLUDE: { + history.CONF_DOMAINS: ["thermostat"], + history.CONF_ENTITIES: ["media_player.test"], }, - } + }, + } + ) + check_significant_states(hass, zero, four, states, config) + + +def test_get_significant_states_are_ordered(hass_history): + """Test order of results from get_significant_states. + + When entity ids are given, the results should be returned with the data + in the same order. + """ + hass = hass_history + zero, four, _states = record_states(hass) + entity_ids = ["media_player.test", "media_player.test2"] + hist = history.get_significant_states( + hass, zero, four, entity_ids, filters=history.Filters() + ) + assert list(hist.keys()) == entity_ids + entity_ids = ["media_player.test2", "media_player.test"] + hist = history.get_significant_states( + hass, zero, four, entity_ids, filters=history.Filters() + ) + assert list(hist.keys()) == entity_ids + + +def test_get_significant_states_only(hass_history): + """Test significant states when significant_states_only is set.""" + hass = hass_history + entity_id = "sensor.test" + + def set_state(state, **kwargs): + """Set the state.""" + hass.states.set(entity_id, state, **kwargs) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=4) + points = [] + for i in range(1, 4): + points.append(start + timedelta(minutes=i)) + + states = [] + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=start): + set_state("123", attributes={"attribute": 10.64}) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=points[0] + ): + # Attributes are different, state not + states.append(set_state("123", attributes={"attribute": 21.42})) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=points[1] + ): + # state is different, attributes not + states.append(set_state("32", attributes={"attribute": 21.42})) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=points[2] + ): + # everything is different + states.append(set_state("412", attributes={"attribute": 54.23})) + + hist = history.get_significant_states(hass, start, significant_changes_only=True) + + assert len(hist[entity_id]) == 2 + assert states[0] not in hist[entity_id] + assert states[1] in hist[entity_id] + assert states[2] in hist[entity_id] + + hist = history.get_significant_states(hass, start, significant_changes_only=False) + + assert len(hist[entity_id]) == 3 + assert states == hist[entity_id] + + +def check_significant_states(hass, zero, four, states, config): + """Check if significant states are retrieved.""" + filters = history.Filters() + exclude = config[history.DOMAIN].get(history.CONF_EXCLUDE) + if exclude: + filters.excluded_entities = exclude.get(history.CONF_ENTITIES, []) + filters.excluded_domains = exclude.get(history.CONF_DOMAINS, []) + include = config[history.DOMAIN].get(history.CONF_INCLUDE) + if include: + filters.included_entities = include.get(history.CONF_ENTITIES, []) + filters.included_domains = include.get(history.CONF_DOMAINS, []) + + hist = history.get_significant_states(hass, zero, four, filters=filters) + assert states == hist + + +def record_states(hass): + """Record some test states. + + We inject a bunch of state updates from media player, zone and + thermostat. + """ + mp = "media_player.test" + mp2 = "media_player.test2" + mp3 = "media_player.test3" + therm = "thermostat.test" + therm2 = "thermostat.test2" + zone = "zone.home" + script_c = "script.can_cancel_this_one" + + def set_state(entity_id, state, **kwargs): + """Set the state.""" + hass.states.set(entity_id, state, **kwargs) + wait_recording_done(hass) + return hass.states.get(entity_id) + + zero = dt_util.utcnow() + one = zero + timedelta(seconds=1) + two = one + timedelta(seconds=1) + three = two + timedelta(seconds=1) + four = three + timedelta(seconds=1) + + states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): + states[mp].append( + set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_domain(self): - """Test if significant states are returned when including domains. - - We should get back every thermostat and script changes, but no media - player changes. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["thermostat", "script"] - } - }, - } + states[mp].append( + set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_entity(self): - """Test if significant states are returned when including entities. - - We should only get back changes of the media_player.test entity. - """ - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_ENTITIES: ["media_player.test"]} - }, - } + states[mp2].append( + set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include(self): - """Test significant states when including domains and entities. - - We should only get back changes of the media_player.test entity and the - thermostat domain. - """ - zero, four, states = self.record_states() - del states["media_player.test2"] - del states["media_player.test3"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - } - }, - } + states[mp3].append( + set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_exclude_domain(self): - """Test if significant states when excluding and including domains. - - We should not get back any changes since we include only the - media_player domain but also exclude it. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: {history.CONF_DOMAINS: ["media_player"]}, - history.CONF_EXCLUDE: {history.CONF_DOMAINS: ["media_player"]}, - }, - } + states[therm].append( + set_state(therm, 20, attributes={"current_temperature": 19.5}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_exclude_entity(self): - """Test if significant states when excluding and including domains. - - We should not get back any changes since we include only - media_player.test but also exclude it. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["media_player.test2"] - del states["media_player.test3"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_ENTITIES: ["media_player.test"] - }, - history.CONF_EXCLUDE: { - history.CONF_ENTITIES: ["media_player.test"] - }, - }, - } + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): + # This state will be skipped only different in time + set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) + # This state will be skipped because domain is excluded + set_state(zone, "zoning") + states[script_c].append( + set_state(script_c, "off", attributes={"can_cancel": True}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_include_exclude(self): - """Test if significant states when in/excluding domains and entities. - - We should only get back changes of the media_player.test2 entity. - """ - zero, four, states = self.record_states() - del states["media_player.test"] - del states["thermostat.test"] - del states["thermostat.test2"] - del states["script.can_cancel_this_one"] - - config = history.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - history.DOMAIN: { - history.CONF_INCLUDE: { - history.CONF_DOMAINS: ["media_player"], - history.CONF_ENTITIES: ["thermostat.test"], - }, - history.CONF_EXCLUDE: { - history.CONF_DOMAINS: ["thermostat"], - history.CONF_ENTITIES: ["media_player.test"], - }, - }, - } + states[therm].append( + set_state(therm, 21, attributes={"current_temperature": 19.8}) ) - self.check_significant_states(zero, four, states, config) - - def test_get_significant_states_are_ordered(self): - """Test order of results from get_significant_states. - - When entity ids are given, the results should be returned with the data - in the same order. - """ - zero, four, states = self.record_states() - entity_ids = ["media_player.test", "media_player.test2"] - hist = history.get_significant_states( - self.hass, zero, four, entity_ids, filters=history.Filters() + states[therm2].append( + set_state(therm2, 20, attributes={"current_temperature": 19}) ) - assert list(hist.keys()) == entity_ids - entity_ids = ["media_player.test2", "media_player.test"] - hist = history.get_significant_states( - self.hass, zero, four, entity_ids, filters=history.Filters() + + with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=three): + states[mp].append( + set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) ) - assert list(hist.keys()) == entity_ids - - def test_get_significant_states_only(self): - """Test significant states when significant_states_only is set.""" - self.test_setup() - entity_id = "sensor.test" - - def set_state(state, **kwargs): - """Set the state.""" - self.hass.states.set(entity_id, state, **kwargs) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) - - states = [] - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=start - ): - set_state("123", attributes={"attribute": 10.64}) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[0] - ): - # Attributes are different, state not - states.append(set_state("123", attributes={"attribute": 21.42})) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[1] - ): - # state is different, attributes not - states.append(set_state("32", attributes={"attribute": 21.42})) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=points[2] - ): - # everything is different - states.append(set_state("412", attributes={"attribute": 54.23})) - - hist = history.get_significant_states( - self.hass, start, significant_changes_only=True + states[mp3].append( + set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) ) - - assert len(hist[entity_id]) == 2 - assert states[0] not in hist[entity_id] - assert states[1] in hist[entity_id] - assert states[2] in hist[entity_id] - - hist = history.get_significant_states( - self.hass, start, significant_changes_only=False + # Attributes changed even though state is the same + states[therm].append( + set_state(therm, 21, attributes={"current_temperature": 20}) ) - assert len(hist[entity_id]) == 3 - assert states == hist[entity_id] - - def check_significant_states(self, zero, four, states, config): - """Check if significant states are retrieved.""" - filters = history.Filters() - exclude = config[history.DOMAIN].get(history.CONF_EXCLUDE) - if exclude: - filters.excluded_entities = exclude.get(history.CONF_ENTITIES, []) - filters.excluded_domains = exclude.get(history.CONF_DOMAINS, []) - include = config[history.DOMAIN].get(history.CONF_INCLUDE) - if include: - filters.included_entities = include.get(history.CONF_ENTITIES, []) - filters.included_domains = include.get(history.CONF_DOMAINS, []) - - hist = history.get_significant_states(self.hass, zero, four, filters=filters) - assert states == hist - - def record_states(self): - """Record some test states. - - We inject a bunch of state updates from media player, zone and - thermostat. - """ - self.test_setup() - mp = "media_player.test" - mp2 = "media_player.test2" - mp3 = "media_player.test3" - therm = "thermostat.test" - therm2 = "thermostat.test2" - zone = "zone.home" - script_c = "script.can_cancel_this_one" - - def set_state(entity_id, state, **kwargs): - """Set the state.""" - self.hass.states.set(entity_id, state, **kwargs) - wait_recording_done(self.hass) - return self.hass.states.get(entity_id) - - zero = dt_util.utcnow() - one = zero + timedelta(seconds=1) - two = one + timedelta(seconds=1) - three = two + timedelta(seconds=1) - four = three + timedelta(seconds=1) - - states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=one - ): - states[mp].append( - set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) - ) - states[mp].append( - set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) - ) - states[mp2].append( - set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) - ) - states[mp3].append( - set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) - ) - states[therm].append( - set_state(therm, 20, attributes={"current_temperature": 19.5}) - ) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=two - ): - # This state will be skipped only different in time - set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) - # This state will be skipped because domain is excluded - set_state(zone, "zoning") - states[script_c].append( - set_state(script_c, "off", attributes={"can_cancel": True}) - ) - states[therm].append( - set_state(therm, 21, attributes={"current_temperature": 19.8}) - ) - states[therm2].append( - set_state(therm2, 20, attributes={"current_temperature": 19}) - ) - - with patch( - "homeassistant.components.recorder.dt_util.utcnow", return_value=three - ): - states[mp].append( - set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) - ) - states[mp3].append( - set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) - ) - # Attributes changed even though state is the same - states[therm].append( - set_state(therm, 21, attributes={"current_temperature": 20}) - ) - - return zero, four, states + return zero, four, states async def test_fetch_period_api(hass, hass_client): From e55702d63528f52d5324cf24d233399b47607c2b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 17:34:55 +0100 Subject: [PATCH 1325/1818] Update typing 01 (#48013) --- homeassistant/__main__.py | 5 +- homeassistant/bootstrap.py | 26 +++-- homeassistant/config.py | 54 ++++----- homeassistant/config_entries.py | 104 ++++++++--------- homeassistant/core.py | 185 +++++++++++++++---------------- homeassistant/data_entry_flow.py | 72 ++++++------ homeassistant/exceptions.py | 16 +-- homeassistant/loader.py | 116 +++++++++---------- homeassistant/requirements.py | 18 +-- homeassistant/runner.py | 10 +- homeassistant/setup.py | 10 +- 11 files changed, 303 insertions(+), 313 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 840e0bed24d5f1..d8256e2ef92aa0 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,11 +1,12 @@ """Start Home Assistant.""" +from __future__ import annotations + import argparse import os import platform import subprocess import sys import threading -from typing import List from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ @@ -206,7 +207,7 @@ def closefds_osx(min_fd: int, max_fd: int) -> None: pass -def cmdline() -> List[str]: +def cmdline() -> list[str]: """Collect path and arguments to re-execute the current hass instance.""" if os.path.basename(sys.argv[0]) == "__main__.py": modulepath = os.path.dirname(sys.argv[0]) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 6d334ac8953d39..8b191ae3a4705f 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,4 +1,6 @@ """Provide methods to bootstrap a Home Assistant instance.""" +from __future__ import annotations + import asyncio import contextlib from datetime import datetime @@ -8,7 +10,7 @@ import sys import threading from time import monotonic -from typing import TYPE_CHECKING, Any, Dict, Optional, Set +from typing import TYPE_CHECKING, Any import voluptuous as vol import yarl @@ -75,7 +77,7 @@ async def async_setup_hass( runtime_config: "RuntimeConfig", -) -> Optional[core.HomeAssistant]: +) -> core.HomeAssistant | None: """Set up Home Assistant.""" hass = core.HomeAssistant() hass.config.config_dir = runtime_config.config_dir @@ -188,7 +190,7 @@ def open_hass_ui(hass: core.HomeAssistant) -> None: async def async_from_config_dict( config: ConfigType, hass: core.HomeAssistant -) -> Optional[core.HomeAssistant]: +) -> core.HomeAssistant | None: """Try to configure Home Assistant from a configuration dictionary. Dynamically loads required components and its dependencies. @@ -255,8 +257,8 @@ async def async_from_config_dict( def async_enable_logging( hass: core.HomeAssistant, verbose: bool = False, - log_rotate_days: Optional[int] = None, - log_file: Optional[str] = None, + log_rotate_days: int | None = None, + log_file: str | None = None, log_no_color: bool = False, ) -> None: """Set up the logging. @@ -362,7 +364,7 @@ async def async_mount_local_lib_path(config_dir: str) -> str: @core.callback -def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]: +def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]: """Get domains of components to set up.""" # Filter out the repeating and common config section [homeassistant] domains = {key.split(" ")[0] for key in config if key != core.DOMAIN} @@ -379,7 +381,7 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]: async def _async_log_pending_setups( - hass: core.HomeAssistant, domains: Set[str], setup_started: Dict[str, datetime] + hass: core.HomeAssistant, domains: set[str], setup_started: dict[str, datetime] ) -> None: """Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL.""" while True: @@ -396,9 +398,9 @@ async def _async_log_pending_setups( async def async_setup_multi_components( hass: core.HomeAssistant, - domains: Set[str], - config: Dict[str, Any], - setup_started: Dict[str, datetime], + domains: set[str], + config: dict[str, Any], + setup_started: dict[str, datetime], ) -> None: """Set up multiple domains. Log on failure.""" futures = { @@ -422,7 +424,7 @@ async def async_setup_multi_components( async def _async_set_up_integrations( - hass: core.HomeAssistant, config: Dict[str, Any] + hass: core.HomeAssistant, config: dict[str, Any] ) -> None: """Set up all the integrations.""" setup_started = hass.data[DATA_SETUP_STARTED] = {} @@ -430,7 +432,7 @@ 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] = {} + integration_cache: dict[str, loader.Integration] = {} to_resolve = domains_to_setup while to_resolve: old_to_resolve = to_resolve diff --git a/homeassistant/config.py b/homeassistant/config.py index cfc1390a37ba2c..362c93d04fa863 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1,4 +1,6 @@ """Module to help with parsing and generating configuration files.""" +from __future__ import annotations + from collections import OrderedDict import logging import os @@ -6,7 +8,7 @@ import re import shutil from types import ModuleType -from typing import Any, Callable, Dict, Optional, Sequence, Set, Tuple, Union +from typing import Any, Callable, Sequence from awesomeversion import AwesomeVersion import voluptuous as vol @@ -114,14 +116,14 @@ def _no_duplicate_auth_provider( - configs: Sequence[Dict[str, Any]] -) -> Sequence[Dict[str, Any]]: + configs: Sequence[dict[str, Any]] +) -> Sequence[dict[str, Any]]: """No duplicate auth provider config allowed in a list. Each type of auth provider can only have one config without optional id. Unique id is required if same type of auth provider used multiple times. """ - config_keys: Set[Tuple[str, Optional[str]]] = set() + config_keys: set[tuple[str, str | None]] = set() for config in configs: key = (config[CONF_TYPE], config.get(CONF_ID)) if key in config_keys: @@ -135,8 +137,8 @@ def _no_duplicate_auth_provider( def _no_duplicate_auth_mfa_module( - configs: Sequence[Dict[str, Any]] -) -> Sequence[Dict[str, Any]]: + configs: Sequence[dict[str, Any]] +) -> Sequence[dict[str, Any]]: """No duplicate auth mfa module item allowed in a list. Each type of mfa module can only have one config without optional id. @@ -144,7 +146,7 @@ def _no_duplicate_auth_mfa_module( times. Note: this is different than auth provider """ - config_keys: Set[str] = set() + config_keys: set[str] = set() for config in configs: key = config.get(CONF_ID, config[CONF_TYPE]) if key in config_keys: @@ -313,7 +315,7 @@ def _write_default_config(config_dir: str) -> bool: return False -async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: +async def async_hass_config_yaml(hass: HomeAssistant) -> dict: """Load YAML from a Home Assistant configuration file. This function allow a component inside the asyncio loop to reload its @@ -337,8 +339,8 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: def load_yaml_config_file( - config_path: str, secrets: Optional[Secrets] = None -) -> Dict[Any, Any]: + config_path: str, secrets: Secrets | None = None +) -> dict[Any, Any]: """Parse a YAML configuration file. Raises FileNotFoundError or HomeAssistantError. @@ -421,9 +423,9 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None: def async_log_exception( ex: Exception, domain: str, - config: Dict, + config: dict, hass: HomeAssistant, - link: Optional[str] = None, + link: str | None = None, ) -> None: """Log an error for configuration validation. @@ -437,8 +439,8 @@ def async_log_exception( @callback def _format_config_error( - ex: Exception, domain: str, config: Dict, link: Optional[str] = None -) -> Tuple[str, bool]: + ex: Exception, domain: str, config: dict, link: str | None = None +) -> tuple[str, bool]: """Generate log exception for configuration validation. This method must be run in the event loop. @@ -474,7 +476,7 @@ def _format_config_error( return message, is_friendly -async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> None: +async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> None: """Process the [homeassistant] section from the configuration. This method is a coroutine. @@ -603,7 +605,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non ) -def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> None: +def _log_pkg_error(package: str, component: str, config: dict, message: str) -> None: """Log an error while merging packages.""" message = f"Package {package} setup failed. Integration {component} {message}" @@ -616,7 +618,7 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> _LOGGER.error(message) -def _identify_config_schema(module: ModuleType) -> Optional[str]: +def _identify_config_schema(module: ModuleType) -> str | None: """Extract the schema and identify list or dict based.""" if not isinstance(module.CONFIG_SCHEMA, vol.Schema): # type: ignore return None @@ -664,9 +666,9 @@ def _identify_config_schema(module: ModuleType) -> Optional[str]: return None -def _recursive_merge(conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]: +def _recursive_merge(conf: dict[str, Any], package: dict[str, Any]) -> bool | str: """Merge package into conf, recursively.""" - error: Union[bool, str] = False + error: bool | str = False for key, pack_conf in package.items(): if isinstance(pack_conf, dict): if not pack_conf: @@ -688,10 +690,10 @@ def _recursive_merge(conf: Dict[str, Any], package: Dict[str, Any]) -> Union[boo async def merge_packages_config( hass: HomeAssistant, - config: Dict, - packages: Dict[str, Any], + config: dict, + packages: dict[str, Any], _log_pkg_error: Callable = _log_pkg_error, -) -> Dict: +) -> dict: """Merge packages into the top-level configuration. Mutate config.""" PACKAGES_CONFIG_SCHEMA(packages) for pack_name, pack_conf in packages.items(): @@ -754,7 +756,7 @@ async def merge_packages_config( async def async_process_component_config( hass: HomeAssistant, config: ConfigType, integration: Integration -) -> Optional[ConfigType]: +) -> ConfigType | None: """Check component configuration and return processed configuration. Returns None on error. @@ -879,13 +881,13 @@ async def async_process_component_config( @callback -def config_without_domain(config: Dict, domain: str) -> Dict: +def config_without_domain(config: dict, domain: str) -> dict: """Return a config with all configuration for a domain removed.""" filter_keys = extract_domain_configs(config, domain) return {key: value for key, value in config.items() if key not in filter_keys} -async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]: +async def async_check_ha_config_file(hass: HomeAssistant) -> str | None: """Check if Home Assistant configuration file is valid. This method is a coroutine. @@ -902,7 +904,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]: @callback def async_notify_setup_error( - hass: HomeAssistant, component: str, display_link: Optional[str] = None + hass: HomeAssistant, component: str, display_link: str | None = None ) -> None: """Print a persistent notification. diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index c105be42ba3e92..a18e207a7305ca 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -5,7 +5,7 @@ import functools import logging from types import MappingProxyType, MethodType -from typing import Any, Callable, Dict, List, Optional, Set, Union, cast +from typing import Any, Callable, Optional, cast import weakref import attr @@ -143,11 +143,11 @@ def __init__( source: str, connection_class: str, system_options: dict, - options: Optional[dict] = None, - unique_id: Optional[str] = None, - entry_id: Optional[str] = None, + options: dict | None = None, + unique_id: str | None = None, + entry_id: str | None = None, state: str = ENTRY_STATE_NOT_LOADED, - disabled_by: Optional[str] = None, + disabled_by: str | None = None, ) -> None: """Initialize a config entry.""" # Unique id of the config entry @@ -190,18 +190,18 @@ def __init__( self.supports_unload = False # Listeners to call on update - self.update_listeners: List[ - Union[weakref.ReferenceType[UpdateListenerType], weakref.WeakMethod] + self.update_listeners: list[ + weakref.ReferenceType[UpdateListenerType] | weakref.WeakMethod ] = [] # Function to cancel a scheduled retry - self._async_cancel_retry_setup: Optional[Callable[[], Any]] = None + self._async_cancel_retry_setup: Callable[[], Any] | None = None async def async_setup( self, hass: HomeAssistant, *, - integration: Optional[loader.Integration] = None, + integration: loader.Integration | None = None, tries: int = 0, ) -> None: """Set up an entry.""" @@ -295,7 +295,7 @@ async def setup_again(now: Any) -> None: self.state = ENTRY_STATE_SETUP_ERROR async def async_unload( - self, hass: HomeAssistant, *, integration: Optional[loader.Integration] = None + self, hass: HomeAssistant, *, integration: loader.Integration | None = None ) -> bool: """Unload an entry. @@ -442,7 +442,7 @@ def add_update_listener(self, listener: UpdateListenerType) -> CALLBACK_TYPE: return lambda: self.update_listeners.remove(weak_listener) - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this entry.""" return { "entry_id": self.entry_id, @@ -471,8 +471,8 @@ def __init__( self._hass_config = hass_config async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: data_entry_flow.FlowHandler, result: dict[str, Any] + ) -> dict[str, Any]: """Finish a config flow and add an entry.""" flow = cast(ConfigFlow, flow) @@ -542,7 +542,7 @@ async def async_finish_flow( return result async def async_create_flow( - self, handler_key: Any, *, context: Optional[Dict] = None, data: Any = None + self, handler_key: Any, *, context: dict | None = None, data: Any = None ) -> ConfigFlow: """Create a flow for specified handler. @@ -619,14 +619,14 @@ def __init__(self, hass: HomeAssistant, hass_config: dict) -> None: self.flow = ConfigEntriesFlowManager(hass, self, hass_config) self.options = OptionsFlowManager(hass) self._hass_config = hass_config - self._entries: List[ConfigEntry] = [] + self._entries: list[ConfigEntry] = [] self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) EntityRegistryDisabledHandler(hass).async_setup() @callback - def async_domains(self) -> List[str]: + def async_domains(self) -> list[str]: """Return domains for which we have entries.""" - seen: Set[str] = set() + seen: set[str] = set() result = [] for entry in self._entries: @@ -637,7 +637,7 @@ def async_domains(self) -> List[str]: return result @callback - def async_get_entry(self, entry_id: str) -> Optional[ConfigEntry]: + def async_get_entry(self, entry_id: str) -> ConfigEntry | None: """Return entry with matching entry_id.""" for entry in self._entries: if entry_id == entry.entry_id: @@ -645,7 +645,7 @@ def async_get_entry(self, entry_id: str) -> Optional[ConfigEntry]: return None @callback - def async_entries(self, domain: Optional[str] = None) -> List[ConfigEntry]: + def async_entries(self, domain: str | None = None) -> list[ConfigEntry]: """Return all entries or entries for a specific domain.""" if domain is None: return list(self._entries) @@ -657,7 +657,7 @@ async def async_add(self, entry: ConfigEntry) -> None: await self.async_setup(entry.entry_id) self._async_schedule_save() - async def async_remove(self, entry_id: str) -> Dict[str, Any]: + async def async_remove(self, entry_id: str) -> dict[str, Any]: """Remove an entry.""" entry = self.async_get_entry(entry_id) @@ -789,7 +789,7 @@ async def async_reload(self, entry_id: str) -> bool: return await self.async_setup(entry_id) async def async_set_disabled_by( - self, entry_id: str, disabled_by: Optional[str] + self, entry_id: str, disabled_by: str | None ) -> bool: """Disable an entry. @@ -829,11 +829,11 @@ def async_update_entry( self, entry: ConfigEntry, *, - unique_id: Union[str, dict, None, UndefinedType] = UNDEFINED, - title: Union[str, dict, UndefinedType] = UNDEFINED, - data: Union[dict, UndefinedType] = UNDEFINED, - options: Union[dict, UndefinedType] = UNDEFINED, - system_options: Union[dict, UndefinedType] = UNDEFINED, + unique_id: str | dict | None | UndefinedType = UNDEFINED, + title: str | dict | UndefinedType = UNDEFINED, + data: dict | UndefinedType = UNDEFINED, + options: dict | UndefinedType = UNDEFINED, + system_options: dict | UndefinedType = UNDEFINED, ) -> bool: """Update a config entry. @@ -918,12 +918,12 @@ def _async_schedule_save(self) -> None: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, List[Dict[str, Any]]]: + def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data to save.""" return {"entries": [entry.as_dict() for entry in self._entries]} -async def _old_conf_migrator(old_config: Dict[str, Any]) -> Dict[str, Any]: +async def _old_conf_migrator(old_config: dict[str, Any]) -> dict[str, Any]: """Migrate the pre-0.73 config format to the latest version.""" return {"entries": old_config} @@ -931,7 +931,7 @@ async def _old_conf_migrator(old_config: Dict[str, Any]) -> Dict[str, Any]: class ConfigFlow(data_entry_flow.FlowHandler): """Base class for config flows with some helpers.""" - def __init_subclass__(cls, domain: Optional[str] = None, **kwargs: Any) -> None: + def __init_subclass__(cls, domain: str | None = None, **kwargs: Any) -> None: """Initialize a subclass, register if possible.""" super().__init_subclass__(**kwargs) # type: ignore if domain is not None: @@ -940,7 +940,7 @@ def __init_subclass__(cls, domain: Optional[str] = None, **kwargs: Any) -> None: CONNECTION_CLASS = CONN_CLASS_UNKNOWN @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique ID if available.""" if not self.context: return None @@ -956,7 +956,7 @@ def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: @callback def _abort_if_unique_id_configured( self, - updates: Optional[Dict[Any, Any]] = None, + updates: dict[Any, Any] | None = None, reload_on_update: bool = True, ) -> None: """Abort if the unique ID is already configured.""" @@ -983,8 +983,8 @@ def _abort_if_unique_id_configured( raise data_entry_flow.AbortFlow("already_configured") async def async_set_unique_id( - self, unique_id: Optional[str] = None, *, raise_on_progress: bool = True - ) -> Optional[ConfigEntry]: + self, unique_id: str | None = None, *, raise_on_progress: bool = True + ) -> ConfigEntry | None: """Set a unique ID for the config flow. Returns optionally existing config entry with same ID. @@ -1020,7 +1020,7 @@ def _set_confirm_only( self.context["confirm_only"] = True @callback - def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEntry]: + def _async_current_entries(self, include_ignore: bool = False) -> list[ConfigEntry]: """Return current entries. If the flow is user initiated, filter out ignored entries unless include_ignore is True. @@ -1033,7 +1033,7 @@ def _async_current_entries(self, include_ignore: bool = False) -> List[ConfigEnt return [entry for entry in config_entries if entry.source != SOURCE_IGNORE] @callback - def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: + def _async_current_ids(self, include_ignore: bool = True) -> set[str | None]: """Return current unique IDs.""" return { entry.unique_id @@ -1042,7 +1042,7 @@ def _async_current_ids(self, include_ignore: bool = True) -> Set[Optional[str]]: } @callback - def _async_in_progress(self) -> List[Dict]: + def _async_in_progress(self) -> list[dict]: """Return other in progress flows for current domain.""" return [ flw @@ -1050,18 +1050,18 @@ def _async_in_progress(self) -> List[Dict]: if flw["handler"] == self.handler and flw["flow_id"] != self.flow_id ] - async def async_step_ignore(self, user_input: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_ignore(self, user_input: dict[str, Any]) -> dict[str, Any]: """Ignore this config flow.""" await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False) return self.async_create_entry(title=user_input["title"], data={}) - async def async_step_unignore(self, user_input: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_unignore(self, user_input: dict[str, Any]) -> dict[str, Any]: """Rediscover a config entry by it's unique_id.""" return self.async_abort(reason="not_implemented") async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" return self.async_abort(reason="not_implemented") @@ -1090,16 +1090,16 @@ async def _async_handle_discovery_without_unique_id(self) -> None: raise data_entry_flow.AbortFlow("already_in_progress") async def async_step_discovery( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" await self._async_handle_discovery_without_unique_id() return await self.async_step_user() @callback def async_abort( - self, *, reason: str, description_placeholders: Optional[Dict] = None - ) -> Dict[str, Any]: + self, *, reason: str, description_placeholders: dict | None = None + ) -> dict[str, Any]: """Abort the config flow.""" # Remove reauth notification if no reauth flows are in progress if self.source == SOURCE_REAUTH and not any( @@ -1130,8 +1130,8 @@ async def async_create_flow( self, handler_key: Any, *, - context: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> OptionsFlow: """Create an options flow for a config entry. @@ -1147,8 +1147,8 @@ async def async_create_flow( return cast(OptionsFlow, HANDLERS[entry.domain].async_get_options_flow(entry)) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: data_entry_flow.FlowHandler, result: dict[str, Any] + ) -> dict[str, Any]: """Finish an options flow and update options for configuration entry. Flow.handler and entry_id is the same thing to map flow with entry. @@ -1184,7 +1184,7 @@ def update(self, *, disable_new_entities: bool) -> None: """Update properties.""" self.disable_new_entities = disable_new_entities - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this config entries system options.""" return {"disable_new_entities": self.disable_new_entities} @@ -1195,9 +1195,9 @@ class EntityRegistryDisabledHandler: def __init__(self, hass: HomeAssistant) -> None: """Initialize the handler.""" self.hass = hass - self.registry: Optional[entity_registry.EntityRegistry] = None - self.changed: Set[str] = set() - self._remove_call_later: Optional[Callable[[], None]] = None + self.registry: entity_registry.EntityRegistry | None = None + self.changed: set[str] = set() + self._remove_call_later: Callable[[], None] | None = None @callback def async_setup(self) -> None: diff --git a/homeassistant/core.py b/homeassistant/core.py index b7bb645be3619d..4e0c3c48283814 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -4,6 +4,8 @@ Home Assistant is a Home Automation framework for observing the state of entities and react to changes. """ +from __future__ import annotations + import asyncio import datetime import enum @@ -22,15 +24,10 @@ Callable, Collection, Coroutine, - Dict, Iterable, - List, Mapping, Optional, - Set, - Tuple, TypeVar, - Union, cast, ) @@ -119,7 +116,7 @@ _LOGGER = logging.getLogger(__name__) -def split_entity_id(entity_id: str) -> List[str]: +def split_entity_id(entity_id: str) -> list[str]: """Split a state entity ID into domain and object ID.""" return entity_id.split(".", 1) @@ -237,7 +234,7 @@ def __init__(self) -> None: self.state: CoreState = CoreState.not_running self.exit_code: int = 0 # If not None, use to signal end-of-loop - self._stopped: Optional[asyncio.Event] = None + self._stopped: asyncio.Event | None = None # Timeout handler for Core/Helper namespace self.timeout: TimeoutManager = TimeoutManager() @@ -342,7 +339,7 @@ def add_job(self, target: Callable[..., Any], *args: Any) -> None: @callback def async_add_job( self, target: Callable[..., Any], *args: Any - ) -> Optional[asyncio.Future]: + ) -> asyncio.Future | None: """Add a job from within the event loop. This method must be run in the event loop. @@ -359,9 +356,7 @@ def async_add_job( return self.async_add_hass_job(HassJob(target), *args) @callback - def async_add_hass_job( - self, hassjob: HassJob, *args: Any - ) -> Optional[asyncio.Future]: + def async_add_hass_job(self, hassjob: HassJob, *args: Any) -> asyncio.Future | None: """Add a HassJob from within the event loop. This method must be run in the event loop. @@ -423,9 +418,7 @@ def async_stop_track_tasks(self) -> None: self._track_task = False @callback - def async_run_hass_job( - self, hassjob: HassJob, *args: Any - ) -> Optional[asyncio.Future]: + def async_run_hass_job(self, hassjob: HassJob, *args: Any) -> asyncio.Future | None: """Run a HassJob from within the event loop. This method must be run in the event loop. @@ -441,8 +434,8 @@ def async_run_hass_job( @callback def async_run_job( - self, target: Callable[..., Union[None, Awaitable]], *args: Any - ) -> Optional[asyncio.Future]: + self, target: Callable[..., None | Awaitable], *args: Any + ) -> asyncio.Future | None: """Run a job from within the event loop. This method must be run in the event loop. @@ -465,7 +458,7 @@ async def async_block_till_done(self) -> None: """Block until all pending work is done.""" # To flush out any call_soon_threadsafe await asyncio.sleep(0) - start_time: Optional[float] = None + start_time: float | None = None while self._pending_tasks: pending = [task for task in self._pending_tasks if not task.done()] @@ -582,10 +575,10 @@ class Context: """The context that triggered something.""" user_id: str = attr.ib(default=None) - parent_id: Optional[str] = attr.ib(default=None) + parent_id: str | None = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) - def as_dict(self) -> Dict[str, Optional[str]]: + def as_dict(self) -> dict[str, str | None]: """Return a dictionary representation of the context.""" return {"id": self.id, "parent_id": self.parent_id, "user_id": self.user_id} @@ -610,10 +603,10 @@ class Event: def __init__( self, event_type: str, - data: Optional[Dict[str, Any]] = None, + data: dict[str, Any] | None = None, origin: EventOrigin = EventOrigin.local, - time_fired: Optional[datetime.datetime] = None, - context: Optional[Context] = None, + time_fired: datetime.datetime | None = None, + context: Context | None = None, ) -> None: """Initialize a new event.""" self.event_type = event_type @@ -627,7 +620,7 @@ def __hash__(self) -> int: # The only event type that shares context are the TIME_CHANGED return hash((self.event_type, self.context.id, self.time_fired)) - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Create a dict representation of this Event. Async friendly. @@ -664,11 +657,11 @@ class EventBus: def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" - self._listeners: Dict[str, List[Tuple[HassJob, Optional[Callable]]]] = {} + self._listeners: dict[str, list[tuple[HassJob, Callable | None]]] = {} self._hass = hass @callback - def async_listeners(self) -> Dict[str, int]: + def async_listeners(self) -> dict[str, int]: """Return dictionary with events and the number of listeners. This method must be run in the event loop. @@ -676,16 +669,16 @@ def async_listeners(self) -> Dict[str, int]: return {key: len(self._listeners[key]) for key in self._listeners} @property - def listeners(self) -> Dict[str, int]: + def listeners(self) -> dict[str, int]: """Return dictionary with events and the number of listeners.""" return run_callback_threadsafe(self._hass.loop, self.async_listeners).result() def fire( self, event_type: str, - event_data: Optional[Dict] = None, + event_data: dict | None = None, origin: EventOrigin = EventOrigin.local, - context: Optional[Context] = None, + context: Context | None = None, ) -> None: """Fire an event.""" self._hass.loop.call_soon_threadsafe( @@ -696,10 +689,10 @@ def fire( def async_fire( self, event_type: str, - event_data: Optional[Dict[str, Any]] = None, + event_data: dict[str, Any] | None = None, origin: EventOrigin = EventOrigin.local, - context: Optional[Context] = None, - time_fired: Optional[datetime.datetime] = None, + context: Context | None = None, + time_fired: datetime.datetime | None = None, ) -> None: """Fire an event. @@ -751,7 +744,7 @@ def async_listen( self, event_type: str, listener: Callable, - event_filter: Optional[Callable] = None, + event_filter: Callable | None = None, ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. @@ -772,7 +765,7 @@ def async_listen( @callback def _async_listen_filterable_job( - self, event_type: str, filterable_job: Tuple[HassJob, Optional[Callable]] + self, event_type: str, filterable_job: tuple[HassJob, Callable | None] ) -> CALLBACK_TYPE: self._listeners.setdefault(event_type, []).append(filterable_job) @@ -811,7 +804,7 @@ def async_listen_once(self, event_type: str, listener: Callable) -> CALLBACK_TYP This method must be run in the event loop. """ - filterable_job: Optional[Tuple[HassJob, Optional[Callable]]] = None + filterable_job: tuple[HassJob, Callable | None] | None = None @callback def _onetime_listener(event: Event) -> None: @@ -835,7 +828,7 @@ def _onetime_listener(event: Event) -> None: @callback def _async_remove_listener( - self, event_type: str, filterable_job: Tuple[HassJob, Optional[Callable]] + self, event_type: str, filterable_job: tuple[HassJob, Callable | None] ) -> None: """Remove a listener of a specific event_type. @@ -884,11 +877,11 @@ def __init__( self, entity_id: str, state: str, - attributes: Optional[Mapping[str, Any]] = None, - last_changed: Optional[datetime.datetime] = None, - last_updated: Optional[datetime.datetime] = None, - context: Optional[Context] = None, - validate_entity_id: Optional[bool] = True, + attributes: Mapping[str, Any] | None = None, + last_changed: datetime.datetime | None = None, + last_updated: datetime.datetime | None = None, + context: Context | None = None, + validate_entity_id: bool | None = True, ) -> None: """Initialize a new state.""" state = str(state) @@ -912,7 +905,7 @@ def __init__( 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: Optional[Dict[str, Collection[Any]]] = None + self._as_dict: dict[str, Collection[Any]] | None = None @property def name(self) -> str: @@ -921,7 +914,7 @@ def name(self) -> str: "_", " " ) - def as_dict(self) -> Dict: + def as_dict(self) -> dict: """Return a dict representation of the State. Async friendly. @@ -946,7 +939,7 @@ def as_dict(self) -> Dict: return self._as_dict @classmethod - def from_dict(cls, json_dict: Dict) -> Any: + def from_dict(cls, json_dict: dict) -> Any: """Initialize a state from a dict. Async friendly. @@ -1004,12 +997,12 @@ class StateMachine: def __init__(self, bus: EventBus, loop: asyncio.events.AbstractEventLoop) -> None: """Initialize state machine.""" - self._states: Dict[str, State] = {} - self._reservations: Set[str] = set() + self._states: dict[str, State] = {} + self._reservations: set[str] = set() self._bus = bus self._loop = loop - def entity_ids(self, domain_filter: Optional[str] = None) -> List[str]: + def entity_ids(self, domain_filter: str | None = None) -> list[str]: """List of entity ids that are being tracked.""" future = run_callback_threadsafe( self._loop, self.async_entity_ids, domain_filter @@ -1018,8 +1011,8 @@ def entity_ids(self, domain_filter: Optional[str] = None) -> List[str]: @callback def async_entity_ids( - self, domain_filter: Optional[Union[str, Iterable]] = None - ) -> List[str]: + self, domain_filter: str | Iterable | None = None + ) -> list[str]: """List of entity ids that are being tracked. This method must be run in the event loop. @@ -1038,7 +1031,7 @@ def async_entity_ids( @callback def async_entity_ids_count( - self, domain_filter: Optional[Union[str, Iterable]] = None + self, domain_filter: str | Iterable | None = None ) -> int: """Count the entity ids that are being tracked. @@ -1054,16 +1047,14 @@ def async_entity_ids_count( [None for state in self._states.values() if state.domain in domain_filter] ) - def all(self, domain_filter: Optional[Union[str, Iterable]] = None) -> List[State]: + def all(self, domain_filter: str | Iterable | None = None) -> list[State]: """Create a list of all states.""" return run_callback_threadsafe( self._loop, self.async_all, domain_filter ).result() @callback - def async_all( - self, domain_filter: Optional[Union[str, Iterable]] = None - ) -> List[State]: + def async_all(self, domain_filter: str | Iterable | None = None) -> list[State]: """Create a list of all states matching the filter. This method must be run in the event loop. @@ -1078,7 +1069,7 @@ def async_all( state for state in self._states.values() if state.domain in domain_filter ] - def get(self, entity_id: str) -> Optional[State]: + def get(self, entity_id: str) -> State | None: """Retrieve state of entity_id or None if not found. Async friendly. @@ -1103,7 +1094,7 @@ def remove(self, entity_id: str) -> bool: ).result() @callback - def async_remove(self, entity_id: str, context: Optional[Context] = None) -> bool: + def async_remove(self, entity_id: str, context: Context | None = None) -> bool: """Remove the state of an entity. Returns boolean to indicate if an entity was removed. @@ -1131,9 +1122,9 @@ def set( self, entity_id: str, new_state: str, - attributes: Optional[Mapping[str, Any]] = None, + attributes: Mapping[str, Any] | None = None, force_update: bool = False, - context: Optional[Context] = None, + context: Context | None = None, ) -> None: """Set the state of an entity, add entity if it does not exist. @@ -1180,9 +1171,9 @@ def async_set( self, entity_id: str, new_state: str, - attributes: Optional[Mapping[str, Any]] = None, + attributes: Mapping[str, Any] | None = None, force_update: bool = False, - context: Optional[Context] = None, + context: Context | None = None, ) -> None: """Set the state of an entity, add entity if it does not exist. @@ -1241,8 +1232,8 @@ class Service: def __init__( self, func: Callable, - schema: Optional[vol.Schema], - context: Optional[Context] = None, + schema: vol.Schema | None, + context: Context | None = None, ) -> None: """Initialize a service.""" self.job = HassJob(func) @@ -1258,8 +1249,8 @@ def __init__( self, domain: str, service: str, - data: Optional[Dict] = None, - context: Optional[Context] = None, + data: dict | None = None, + context: Context | None = None, ) -> None: """Initialize a service call.""" self.domain = domain.lower() @@ -1283,16 +1274,16 @@ class ServiceRegistry: def __init__(self, hass: HomeAssistant) -> None: """Initialize a service registry.""" - self._services: Dict[str, Dict[str, Service]] = {} + self._services: dict[str, dict[str, Service]] = {} self._hass = hass @property - def services(self) -> Dict[str, Dict[str, Service]]: + def services(self) -> dict[str, dict[str, Service]]: """Return dictionary with per domain a list of available services.""" return run_callback_threadsafe(self._hass.loop, self.async_services).result() @callback - def async_services(self) -> Dict[str, Dict[str, Service]]: + def async_services(self) -> dict[str, dict[str, Service]]: """Return dictionary with per domain a list of available services. This method must be run in the event loop. @@ -1311,7 +1302,7 @@ def register( domain: str, service: str, service_func: Callable, - schema: Optional[vol.Schema] = None, + schema: vol.Schema | None = None, ) -> None: """ Register a service. @@ -1328,7 +1319,7 @@ def async_register( domain: str, service: str, service_func: Callable, - schema: Optional[vol.Schema] = None, + schema: vol.Schema | None = None, ) -> None: """ Register a service. @@ -1382,12 +1373,12 @@ def call( self, domain: str, service: str, - service_data: Optional[Dict] = None, + service_data: dict | None = None, blocking: bool = False, - context: Optional[Context] = None, - limit: Optional[float] = SERVICE_CALL_LIMIT, - target: Optional[Dict] = None, - ) -> Optional[bool]: + context: Context | None = None, + limit: float | None = SERVICE_CALL_LIMIT, + target: dict | None = None, + ) -> bool | None: """ Call a service. @@ -1404,12 +1395,12 @@ async def async_call( self, domain: str, service: str, - service_data: Optional[Dict] = None, + service_data: dict | None = None, blocking: bool = False, - context: Optional[Context] = None, - limit: Optional[float] = SERVICE_CALL_LIMIT, - target: Optional[Dict] = None, - ) -> Optional[bool]: + context: Context | None = None, + limit: float | None = SERVICE_CALL_LIMIT, + target: dict | None = None, + ) -> bool | None: """ Call a service. @@ -1497,7 +1488,7 @@ async def async_call( return False def _run_service_in_background( - self, coro_or_task: Union[Coroutine, asyncio.Task], service_call: ServiceCall + self, coro_or_task: Coroutine | asyncio.Task, service_call: ServiceCall ) -> None: """Run service call in background, catching and logging any exceptions.""" @@ -1542,8 +1533,8 @@ def __init__(self, hass: HomeAssistant) -> None: self.location_name: str = "Home" self.time_zone: datetime.tzinfo = dt_util.UTC self.units: UnitSystem = METRIC_SYSTEM - self.internal_url: Optional[str] = None - self.external_url: Optional[str] = None + self.internal_url: str | None = None + self.external_url: str | None = None self.config_source: str = "default" @@ -1551,22 +1542,22 @@ def __init__(self, hass: HomeAssistant) -> None: self.skip_pip: bool = False # List of loaded components - self.components: Set[str] = set() + self.components: set[str] = set() # API (HTTP) server configuration, see components.http.ApiConfig - self.api: Optional[Any] = None + self.api: Any | None = None # Directory that holds the configuration - self.config_dir: Optional[str] = None + self.config_dir: str | None = None # List of allowed external dirs to access - self.allowlist_external_dirs: Set[str] = set() + self.allowlist_external_dirs: set[str] = set() # List of allowed external URLs that integrations may use - self.allowlist_external_urls: Set[str] = set() + self.allowlist_external_urls: set[str] = set() # Dictionary of Media folders that integrations may use - self.media_dirs: Dict[str, str] = {} + self.media_dirs: dict[str, str] = {} # If Home Assistant is running in safe mode self.safe_mode: bool = False @@ -1574,7 +1565,7 @@ def __init__(self, hass: HomeAssistant) -> None: # Use legacy template behavior self.legacy_templates: bool = False - def distance(self, lat: float, lon: float) -> Optional[float]: + def distance(self, lat: float, lon: float) -> float | None: """Calculate distance from Home Assistant. Async friendly. @@ -1625,7 +1616,7 @@ def is_allowed_path(self, path: str) -> bool: return False - def as_dict(self) -> Dict: + def as_dict(self) -> dict: """Create a dictionary representation of the configuration. Async friendly. @@ -1670,15 +1661,15 @@ def _update( self, *, source: str, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - elevation: Optional[int] = None, - unit_system: Optional[str] = None, - location_name: Optional[str] = None, - time_zone: Optional[str] = None, + latitude: float | None = None, + longitude: float | None = None, + elevation: int | None = None, + unit_system: str | None = None, + location_name: str | None = None, + time_zone: str | None = None, # pylint: disable=dangerous-default-value # _UNDEFs not modified - external_url: Optional[Union[str, dict]] = _UNDEF, - internal_url: Optional[Union[str, dict]] = _UNDEF, + external_url: str | dict | None = _UNDEF, + internal_url: str | dict | None = _UNDEF, ) -> None: """Update the configuration from a dictionary.""" self.config_source = source diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 15ad417c6ad70b..f16c1859fcda8c 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -4,7 +4,7 @@ import abc import asyncio from types import MappingProxyType -from typing import Any, Dict, List, Optional +from typing import Any import uuid import voluptuous as vol @@ -43,7 +43,7 @@ class UnknownStep(FlowError): class AbortFlow(FlowError): """Exception to indicate a flow needs to be aborted.""" - def __init__(self, reason: str, description_placeholders: Optional[Dict] = None): + def __init__(self, reason: str, description_placeholders: dict | None = None): """Initialize an abort flow exception.""" super().__init__(f"Flow aborted: {reason}") self.reason = reason @@ -59,8 +59,8 @@ def __init__( ) -> None: """Initialize the flow manager.""" self.hass = hass - self._initializing: Dict[str, List[asyncio.Future]] = {} - self._progress: Dict[str, Any] = {} + self._initializing: dict[str, list[asyncio.Future]] = {} + self._progress: dict[str, Any] = {} async def async_wait_init_flow_finish(self, handler: str) -> None: """Wait till all flows in progress are initialized.""" @@ -76,8 +76,8 @@ async def async_create_flow( self, handler_key: Any, *, - context: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> FlowHandler: """Create a flow for specified handler. @@ -86,17 +86,17 @@ async def async_create_flow( @abc.abstractmethod async def async_finish_flow( - self, flow: "FlowHandler", result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: "FlowHandler", result: dict[str, Any] + ) -> dict[str, Any]: """Finish a config flow and add an entry.""" async def async_post_init( - self, flow: "FlowHandler", result: Dict[str, Any] + self, flow: "FlowHandler", result: dict[str, Any] ) -> None: """Entry has finished executing its first step asynchronously.""" @callback - def async_progress(self) -> List[Dict]: + def async_progress(self) -> list[dict]: """Return the flows in progress.""" return [ { @@ -110,7 +110,7 @@ def async_progress(self) -> List[Dict]: ] async def async_init( - self, handler: str, *, context: Optional[Dict] = None, data: Any = None + self, handler: str, *, context: dict | None = None, data: Any = None ) -> Any: """Start a configuration flow.""" if context is None: @@ -142,7 +142,7 @@ async def async_init( return result async def async_configure( - self, flow_id: str, user_input: Optional[Dict] = None + self, flow_id: str, user_input: dict | None = None ) -> Any: """Continue a configuration flow.""" flow = self._progress.get(flow_id) @@ -198,9 +198,9 @@ async def _async_handle_step( self, flow: Any, step_id: str, - user_input: Optional[Dict], - step_done: Optional[asyncio.Future] = None, - ) -> Dict: + user_input: dict | None, + step_done: asyncio.Future | None = None, + ) -> dict: """Handle a step of a flow.""" method = f"async_step_{step_id}" @@ -213,7 +213,7 @@ async def _async_handle_step( ) try: - result: Dict = await getattr(flow, method)(user_input) + result: dict = await getattr(flow, method)(user_input) except AbortFlow as err: result = _create_abort_data( flow.flow_id, flow.handler, err.reason, err.description_placeholders @@ -265,13 +265,13 @@ class FlowHandler: """Handle the configuration flow of a component.""" # Set by flow manager - cur_step: Optional[Dict[str, str]] = None + cur_step: dict[str, str] | None = None # Ignore types: https://github.com/PyCQA/pylint/issues/3167 flow_id: str = None # type: ignore hass: HomeAssistant = None # type: ignore handler: str = None # type: ignore # Ensure the attribute has a subscriptable, but immutable, default value. - context: Dict = MappingProxyType({}) # type: ignore + context: dict = MappingProxyType({}) # type: ignore # Set by _async_create_flow callback init_step = "init" @@ -280,7 +280,7 @@ class FlowHandler: VERSION = 1 @property - def source(self) -> Optional[str]: + def source(self) -> str | None: """Source that initialized the flow.""" if not hasattr(self, "context"): return None @@ -301,9 +301,9 @@ def async_show_form( *, step_id: str, data_schema: vol.Schema = None, - errors: Optional[Dict] = None, - description_placeholders: Optional[Dict] = None, - ) -> Dict[str, Any]: + errors: dict | None = None, + description_placeholders: dict | None = None, + ) -> dict[str, Any]: """Return the definition of a form to gather user input.""" return { "type": RESULT_TYPE_FORM, @@ -320,10 +320,10 @@ def async_create_entry( self, *, title: str, - data: Dict, - description: Optional[str] = None, - description_placeholders: Optional[Dict] = None, - ) -> Dict[str, Any]: + data: dict, + description: str | None = None, + description_placeholders: dict | None = None, + ) -> dict[str, Any]: """Finish config flow and create a config entry.""" return { "version": self.VERSION, @@ -338,8 +338,8 @@ def async_create_entry( @callback def async_abort( - self, *, reason: str, description_placeholders: Optional[Dict] = None - ) -> Dict[str, Any]: + self, *, reason: str, description_placeholders: dict | None = None + ) -> dict[str, Any]: """Abort the config flow.""" return _create_abort_data( self.flow_id, self.handler, reason, description_placeholders @@ -347,8 +347,8 @@ def async_abort( @callback def async_external_step( - self, *, step_id: str, url: str, description_placeholders: Optional[Dict] = None - ) -> Dict[str, Any]: + self, *, step_id: str, url: str, description_placeholders: dict | None = None + ) -> dict[str, Any]: """Return the definition of an external step for the user to take.""" return { "type": RESULT_TYPE_EXTERNAL_STEP, @@ -360,7 +360,7 @@ def async_external_step( } @callback - def async_external_step_done(self, *, next_step_id: str) -> Dict[str, Any]: + def async_external_step_done(self, *, next_step_id: str) -> dict[str, Any]: """Return the definition of an external step for the user to take.""" return { "type": RESULT_TYPE_EXTERNAL_STEP_DONE, @@ -375,8 +375,8 @@ def async_show_progress( *, step_id: str, progress_action: str, - description_placeholders: Optional[Dict] = None, - ) -> Dict[str, Any]: + description_placeholders: dict | None = None, + ) -> dict[str, Any]: """Show a progress message to the user, without user input allowed.""" return { "type": RESULT_TYPE_SHOW_PROGRESS, @@ -388,7 +388,7 @@ def async_show_progress( } @callback - def async_show_progress_done(self, *, next_step_id: str) -> Dict[str, Any]: + def async_show_progress_done(self, *, next_step_id: str) -> dict[str, Any]: """Mark the progress done.""" return { "type": RESULT_TYPE_SHOW_PROGRESS_DONE, @@ -403,8 +403,8 @@ def _create_abort_data( flow_id: str, handler: str, reason: str, - description_placeholders: Optional[Dict] = None, -) -> Dict[str, Any]: + description_placeholders: dict | None = None, +) -> dict[str, Any]: """Return the definition of an external step for the user to take.""" return { "type": RESULT_TYPE_ABORT, diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 9a7b3da0f1d836..499d4052849fe7 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,5 +1,7 @@ """The exceptions used by Home Assistant.""" -from typing import TYPE_CHECKING, Generator, Optional, Sequence +from __future__ import annotations + +from typing import TYPE_CHECKING, Generator, Sequence import attr @@ -113,12 +115,12 @@ class Unauthorized(HomeAssistantError): def __init__( self, - context: Optional["Context"] = None, - user_id: Optional[str] = None, - entity_id: Optional[str] = None, - config_entry_id: Optional[str] = None, - perm_category: Optional[str] = None, - permission: Optional[str] = None, + context: "Context" | None = None, + user_id: str | None = None, + entity_id: str | None = None, + config_entry_id: str | None = None, + perm_category: str | None = None, + permission: str | None = None, ) -> None: """Unauthorized error.""" super().__init__(self.__class__.__name__) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 2ae279da79ea27..f6cb24698ece12 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -14,19 +14,7 @@ import pathlib import sys from types import ModuleType -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - List, - Optional, - Set, - TypedDict, - TypeVar, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Callable, Dict, TypedDict, TypeVar, cast from awesomeversion import AwesomeVersion, AwesomeVersionStrategy @@ -86,21 +74,21 @@ class Manifest(TypedDict, total=False): name: str disabled: str domain: str - dependencies: List[str] - after_dependencies: List[str] - requirements: List[str] + dependencies: list[str] + after_dependencies: list[str] + requirements: list[str] config_flow: bool documentation: str issue_tracker: str quality_scale: str - mqtt: List[str] - ssdp: List[Dict[str, str]] - zeroconf: List[Union[str, Dict[str, str]]] - dhcp: List[Dict[str, str]] - homekit: Dict[str, List[str]] + mqtt: list[str] + ssdp: list[dict[str, str]] + zeroconf: list[str | dict[str, str]] + dhcp: list[dict[str, str]] + homekit: dict[str, list[str]] is_built_in: bool version: str - codeowners: List[str] + codeowners: list[str] def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: @@ -116,7 +104,7 @@ def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: async def _async_get_custom_components( hass: "HomeAssistant", -) -> Dict[str, Integration]: +) -> dict[str, Integration]: """Return list of custom integrations.""" if hass.config.safe_mode: return {} @@ -126,7 +114,7 @@ async def _async_get_custom_components( except ImportError: return {} - def get_sub_directories(paths: List[str]) -> List[pathlib.Path]: + def get_sub_directories(paths: list[str]) -> list[pathlib.Path]: """Return all sub directories in a set of paths.""" return [ entry @@ -157,7 +145,7 @@ def get_sub_directories(paths: List[str]) -> List[pathlib.Path]: async def async_get_custom_components( hass: "HomeAssistant", -) -> Dict[str, Integration]: +) -> dict[str, Integration]: """Return cached list of custom integrations.""" reg_or_evt = hass.data.get(DATA_CUSTOM_COMPONENTS) @@ -177,12 +165,12 @@ async def async_get_custom_components( return cast(Dict[str, "Integration"], reg_or_evt) -async def async_get_config_flows(hass: HomeAssistant) -> Set[str]: +async def async_get_config_flows(hass: HomeAssistant) -> set[str]: """Return cached list of config flows.""" # pylint: disable=import-outside-toplevel from homeassistant.generated.config_flows import FLOWS - flows: Set[str] = set() + flows: set[str] = set() flows.update(FLOWS) integrations = await async_get_custom_components(hass) @@ -197,9 +185,9 @@ async def async_get_config_flows(hass: HomeAssistant) -> Set[str]: return flows -async def async_get_zeroconf(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]]: +async def async_get_zeroconf(hass: HomeAssistant) -> dict[str, list[dict[str, str]]]: """Return cached list of zeroconf types.""" - zeroconf: Dict[str, List[Dict[str, str]]] = ZEROCONF.copy() + zeroconf: dict[str, list[dict[str, str]]] = ZEROCONF.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -220,9 +208,9 @@ async def async_get_zeroconf(hass: HomeAssistant) -> Dict[str, List[Dict[str, st return zeroconf -async def async_get_dhcp(hass: HomeAssistant) -> List[Dict[str, str]]: +async def async_get_dhcp(hass: HomeAssistant) -> list[dict[str, str]]: """Return cached list of dhcp types.""" - dhcp: List[Dict[str, str]] = DHCP.copy() + dhcp: list[dict[str, str]] = DHCP.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -234,10 +222,10 @@ async def async_get_dhcp(hass: HomeAssistant) -> List[Dict[str, str]]: return dhcp -async def async_get_homekit(hass: HomeAssistant) -> Dict[str, str]: +async def async_get_homekit(hass: HomeAssistant) -> dict[str, str]: """Return cached list of homekit models.""" - homekit: Dict[str, str] = HOMEKIT.copy() + homekit: dict[str, str] = HOMEKIT.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -253,10 +241,10 @@ async def async_get_homekit(hass: HomeAssistant) -> Dict[str, str]: return homekit -async def async_get_ssdp(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]]: +async def async_get_ssdp(hass: HomeAssistant) -> dict[str, list[dict[str, str]]]: """Return cached list of ssdp mappings.""" - ssdp: Dict[str, List[Dict[str, str]]] = SSDP.copy() + ssdp: dict[str, list[dict[str, str]]] = SSDP.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -268,10 +256,10 @@ async def async_get_ssdp(hass: HomeAssistant) -> Dict[str, List[Dict[str, str]]] return ssdp -async def async_get_mqtt(hass: HomeAssistant) -> Dict[str, List[str]]: +async def async_get_mqtt(hass: HomeAssistant) -> dict[str, list[str]]: """Return cached list of MQTT mappings.""" - mqtt: Dict[str, List[str]] = MQTT.copy() + mqtt: dict[str, list[str]] = MQTT.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -289,7 +277,7 @@ class Integration: @classmethod def resolve_from_root( cls, hass: "HomeAssistant", root_module: ModuleType, domain: str - ) -> Optional[Integration]: + ) -> Integration | None: """Resolve an integration from a root module.""" for base in root_module.__path__: # type: ignore manifest_path = pathlib.Path(base) / domain / "manifest.json" @@ -312,9 +300,7 @@ def resolve_from_root( return None @classmethod - def resolve_legacy( - cls, hass: "HomeAssistant", domain: str - ) -> Optional[Integration]: + def resolve_legacy(cls, hass: "HomeAssistant", domain: str) -> Integration | None: """Resolve legacy component. Will create a stub manifest. @@ -346,8 +332,8 @@ def __init__( manifest["is_built_in"] = self.is_built_in if self.dependencies: - self._all_dependencies_resolved: Optional[bool] = None - self._all_dependencies: Optional[Set[str]] = None + self._all_dependencies_resolved: bool | None = None + self._all_dependencies: set[str] | None = None else: self._all_dependencies_resolved = True self._all_dependencies = set() @@ -360,7 +346,7 @@ def name(self) -> str: return self.manifest["name"] @property - def disabled(self) -> Optional[str]: + def disabled(self) -> str | None: """Return reason integration is disabled.""" return self.manifest.get("disabled") @@ -370,17 +356,17 @@ def domain(self) -> str: return self.manifest["domain"] @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: """Return dependencies.""" return self.manifest.get("dependencies", []) @property - def after_dependencies(self) -> List[str]: + def after_dependencies(self) -> list[str]: """Return after_dependencies.""" return self.manifest.get("after_dependencies", []) @property - def requirements(self) -> List[str]: + def requirements(self) -> list[str]: """Return requirements.""" return self.manifest.get("requirements", []) @@ -390,42 +376,42 @@ def config_flow(self) -> bool: return self.manifest.get("config_flow") or False @property - def documentation(self) -> Optional[str]: + def documentation(self) -> str | None: """Return documentation.""" return self.manifest.get("documentation") @property - def issue_tracker(self) -> Optional[str]: + def issue_tracker(self) -> str | None: """Return issue tracker link.""" return self.manifest.get("issue_tracker") @property - def quality_scale(self) -> Optional[str]: + def quality_scale(self) -> str | None: """Return Integration Quality Scale.""" return self.manifest.get("quality_scale") @property - def mqtt(self) -> Optional[List[str]]: + def mqtt(self) -> list[str] | None: """Return Integration MQTT entries.""" return self.manifest.get("mqtt") @property - def ssdp(self) -> Optional[List[Dict[str, str]]]: + def ssdp(self) -> list[dict[str, str]] | None: """Return Integration SSDP entries.""" return self.manifest.get("ssdp") @property - def zeroconf(self) -> Optional[List[Union[str, Dict[str, str]]]]: + def zeroconf(self) -> list[str | dict[str, str]] | None: """Return Integration zeroconf entries.""" return self.manifest.get("zeroconf") @property - def dhcp(self) -> Optional[List[Dict[str, str]]]: + def dhcp(self) -> list[dict[str, str]] | None: """Return Integration dhcp entries.""" return self.manifest.get("dhcp") @property - def homekit(self) -> Optional[Dict[str, List[str]]]: + def homekit(self) -> dict[str, list[str]] | None: """Return Integration homekit entries.""" return self.manifest.get("homekit") @@ -435,14 +421,14 @@ def is_built_in(self) -> bool: return self.pkg_path.startswith(PACKAGE_BUILTIN) @property - def version(self) -> Optional[AwesomeVersion]: + def version(self) -> AwesomeVersion | None: """Return the version of the integration.""" if "version" not in self.manifest: return None return AwesomeVersion(self.manifest["version"]) @property - def all_dependencies(self) -> Set[str]: + def all_dependencies(self) -> set[str]: """Return all dependencies including sub-dependencies.""" if self._all_dependencies is None: raise RuntimeError("Dependencies not resolved!") @@ -516,7 +502,7 @@ async def async_get_integration(hass: "HomeAssistant", domain: str) -> Integrati raise IntegrationNotFound(domain) cache = hass.data[DATA_INTEGRATIONS] = {} - int_or_evt: Union[Integration, asyncio.Event, None] = cache.get(domain, _UNDEF) + int_or_evt: Integration | asyncio.Event | None = cache.get(domain, _UNDEF) if isinstance(int_or_evt, asyncio.Event): await int_or_evt.wait() @@ -593,8 +579,8 @@ def __init__(self, from_domain: str, to_domain: str) -> None: def _load_file( - hass: "HomeAssistant", comp_or_platform: str, base_paths: List[str] -) -> Optional[ModuleType]: + hass: "HomeAssistant", comp_or_platform: str, base_paths: list[str] +) -> ModuleType | None: """Try to load specified file. Looks in config dir first, then built-in components. @@ -683,7 +669,7 @@ def __getattr__(self, comp_name: str) -> ModuleWrapper: integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name) if isinstance(integration, Integration): - component: Optional[ModuleType] = integration.get_component() + component: ModuleType | None = integration.get_component() else: # Fallback to importing old-school component = _load_file(self._hass, comp_name, _lookup_path(self._hass)) @@ -721,9 +707,9 @@ async def _async_component_dependencies( hass: "HomeAssistant", start_domain: str, integration: Integration, - loaded: Set[str], - loading: Set[str], -) -> Set[str]: + loaded: set[str], + loading: set[str], +) -> set[str]: """Recursive function to get component dependencies. Async friendly. @@ -773,7 +759,7 @@ def _async_mount_config_dir(hass: HomeAssistant) -> bool: return True -def _lookup_path(hass: HomeAssistant) -> List[str]: +def _lookup_path(hass: HomeAssistant) -> list[str]: """Return the lookup paths for legacy lookups.""" if hass.config.safe_mode: return [PACKAGE_BUILTIN] diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index 6ea2074ddf4259..f073fd13df8d91 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -1,7 +1,9 @@ """Module to handle installing requirements.""" +from __future__ import annotations + import asyncio import os -from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast +from typing import Any, Iterable, cast from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -15,7 +17,7 @@ DATA_PKG_CACHE = "pkg_cache" DATA_INTEGRATIONS_WITH_REQS = "integrations_with_reqs" CONSTRAINT_FILE = "package_constraints.txt" -DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { +DISCOVERY_INTEGRATIONS: dict[str, Iterable[str]] = { "dhcp": ("dhcp",), "mqtt": ("mqtt",), "ssdp": ("ssdp",), @@ -26,7 +28,7 @@ class RequirementsNotFound(HomeAssistantError): """Raised when a component is not found.""" - def __init__(self, domain: str, requirements: List[str]) -> None: + def __init__(self, domain: str, requirements: list[str]) -> None: """Initialize a component not found error.""" super().__init__(f"Requirements for {domain} not found: {requirements}.") self.domain = domain @@ -34,7 +36,7 @@ def __init__(self, domain: str, requirements: List[str]) -> None: async def async_get_integration_with_requirements( - hass: HomeAssistant, domain: str, done: Optional[Set[str]] = None + hass: HomeAssistant, domain: str, done: set[str] | None = None ) -> Integration: """Get an integration with all requirements installed, including the dependencies. @@ -56,7 +58,7 @@ async def async_get_integration_with_requirements( if cache is None: cache = hass.data[DATA_INTEGRATIONS_WITH_REQS] = {} - int_or_evt: Union[Integration, asyncio.Event, None, UndefinedType] = cache.get( + int_or_evt: Integration | asyncio.Event | None | UndefinedType = cache.get( domain, UNDEFINED ) @@ -108,7 +110,7 @@ async def async_get_integration_with_requirements( async def async_process_requirements( - hass: HomeAssistant, name: str, requirements: List[str] + hass: HomeAssistant, name: str, requirements: list[str] ) -> None: """Install the requirements for a component or platform. @@ -126,7 +128,7 @@ async def async_process_requirements( if pkg_util.is_installed(req): continue - def _install(req: str, kwargs: Dict[str, Any]) -> bool: + def _install(req: str, kwargs: dict[str, Any]) -> bool: """Install requirement.""" return pkg_util.install_package(req, **kwargs) @@ -136,7 +138,7 @@ def _install(req: str, kwargs: Dict[str, Any]) -> bool: raise RequirementsNotFound(name, [req]) -def pip_kwargs(config_dir: Optional[str]) -> Dict[str, Any]: +def pip_kwargs(config_dir: str | None) -> dict[str, Any]: """Return keyword arguments for PIP install.""" is_docker = pkg_util.is_docker_env() kwargs = { diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 6f13a47be8164a..5adddb5f6efdd3 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -1,9 +1,11 @@ """Run Home Assistant.""" +from __future__ import annotations + import asyncio from concurrent.futures import ThreadPoolExecutor import dataclasses import logging -from typing import Any, Dict, Optional +from typing import Any from homeassistant import bootstrap from homeassistant.core import callback @@ -34,8 +36,8 @@ class RuntimeConfig: verbose: bool = False - log_rotate_days: Optional[int] = None - log_file: Optional[str] = None + log_rotate_days: int | None = None + log_file: str | None = None log_no_color: bool = False debug: bool = False @@ -83,7 +85,7 @@ def close() -> None: @callback -def _async_loop_exception_handler(_: Any, context: Dict[str, Any]) -> None: +def _async_loop_exception_handler(_: Any, context: dict[str, Any]) -> None: """Handle all exception inside the core loop.""" kwargs = {} exception = context.get("exception") diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 4060b0410d6601..4c5e10a254bf52 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -1,9 +1,11 @@ """All methods needed to bootstrap a Home Assistant instance.""" +from __future__ import annotations + import asyncio import logging.handlers from timeit import default_timer as timer from types import ModuleType -from typing import Awaitable, Callable, Optional, Set +from typing import Awaitable, Callable from homeassistant import config as conf_util, core, loader, requirements from homeassistant.config import async_notify_setup_error @@ -26,7 +28,7 @@ @core.callback -def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: Set[str]) -> None: +def async_set_domains_to_be_loaded(hass: core.HomeAssistant, domains: set[str]) -> None: """Set domains that are going to be loaded from the config. This will allow us to properly handle after_dependencies. @@ -133,7 +135,7 @@ async def _async_setup_component( This method is a coroutine. """ - def log_error(msg: str, link: Optional[str] = None) -> None: + def log_error(msg: str, link: str | None = None) -> None: """Log helper.""" _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) @@ -268,7 +270,7 @@ def log_error(msg: str, link: Optional[str] = None) -> None: async def async_prepare_setup_platform( hass: core.HomeAssistant, hass_config: ConfigType, domain: str, platform_name: str -) -> Optional[ModuleType]: +) -> ModuleType | None: """Load a platform and makes sure dependencies are setup. This method is a coroutine. From 86d3baa34e4c296f112b4bbc9540f4b0461379aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Kr=C3=B6ner?= Date: Wed, 17 Mar 2021 17:39:47 +0100 Subject: [PATCH 1326/1818] Improve OWM Precipitation sensors (#47945) --- .../components/openweathermap/const.py | 8 +++- .../weather_update_coordinator.py | 37 +++++++++++++------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index 4ab61b486f1391..36d38ff4688f9c 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -47,6 +47,7 @@ ENTRY_NAME = "name" ENTRY_WEATHER_COORDINATOR = "weather_coordinator" ATTR_API_PRECIPITATION = "precipitation" +ATTR_API_PRECIPITATION_KIND = "precipitation_kind" ATTR_API_DATETIME = "datetime" ATTR_API_DEW_POINT = "dew_point" ATTR_API_WEATHER = "weather" @@ -94,6 +95,7 @@ ATTR_API_CLOUDS, ATTR_API_RAIN, ATTR_API_SNOW, + ATTR_API_PRECIPITATION_KIND, ATTR_API_UV_INDEX, ATTR_API_CONDITION, ATTR_API_WEATHER_CODE, @@ -225,6 +227,7 @@ ATTR_API_CLOUDS: {SENSOR_NAME: "Cloud coverage", SENSOR_UNIT: PERCENTAGE}, ATTR_API_RAIN: {SENSOR_NAME: "Rain", SENSOR_UNIT: LENGTH_MILLIMETERS}, ATTR_API_SNOW: {SENSOR_NAME: "Snow", SENSOR_UNIT: LENGTH_MILLIMETERS}, + ATTR_API_PRECIPITATION_KIND: {SENSOR_NAME: "Precipitation kind"}, ATTR_API_UV_INDEX: { SENSOR_NAME: "UV Index", SENSOR_UNIT: UV_INDEX, @@ -234,7 +237,10 @@ } FORECAST_SENSOR_TYPES = { ATTR_FORECAST_CONDITION: {SENSOR_NAME: "Condition"}, - ATTR_FORECAST_PRECIPITATION: {SENSOR_NAME: "Precipitation"}, + ATTR_FORECAST_PRECIPITATION: { + SENSOR_NAME: "Precipitation", + SENSOR_UNIT: LENGTH_MILLIMETERS, + }, ATTR_FORECAST_PRECIPITATION_PROBABILITY: { SENSOR_NAME: "Precipitation probability", SENSOR_UNIT: PERCENTAGE, diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 26ada47bef61b3..51e475eb754557 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -29,6 +29,7 @@ ATTR_API_FEELS_LIKE_TEMPERATURE, ATTR_API_FORECAST, ATTR_API_HUMIDITY, + ATTR_API_PRECIPITATION_KIND, ATTR_API_PRESSURE, ATTR_API_RAIN, ATTR_API_SNOW, @@ -129,6 +130,9 @@ def _convert_weather_response(self, weather_response): ATTR_API_CLOUDS: current_weather.clouds, ATTR_API_RAIN: self._get_rain(current_weather.rain), ATTR_API_SNOW: self._get_snow(current_weather.snow), + ATTR_API_PRECIPITATION_KIND: self._calc_precipitation_kind( + current_weather.rain, current_weather.snow + ), ATTR_API_WEATHER: current_weather.detailed_status, ATTR_API_CONDITION: self._get_condition(current_weather.weather_code), ATTR_API_UV_INDEX: current_weather.uvi, @@ -178,36 +182,45 @@ def _convert_forecast(self, entry): def _get_rain(rain): """Get rain data from weather data.""" if "all" in rain: - return round(rain["all"], 0) + return round(rain["all"], 2) if "1h" in rain: - return round(rain["1h"], 0) - return "not raining" + return round(rain["1h"], 2) + return 0 @staticmethod def _get_snow(snow): """Get snow data from weather data.""" if snow: if "all" in snow: - return round(snow["all"], 0) + return round(snow["all"], 2) if "1h" in snow: - return round(snow["1h"], 0) - return "not snowing" - return "not snowing" + return round(snow["1h"], 2) + return 0 @staticmethod def _calc_precipitation(rain, snow): """Calculate the precipitation.""" rain_value = 0 - if WeatherUpdateCoordinator._get_rain(rain) != "not raining": + if WeatherUpdateCoordinator._get_rain(rain) != 0: rain_value = WeatherUpdateCoordinator._get_rain(rain) snow_value = 0 - if WeatherUpdateCoordinator._get_snow(snow) != "not snowing": + if WeatherUpdateCoordinator._get_snow(snow) != 0: snow_value = WeatherUpdateCoordinator._get_snow(snow) - if round(rain_value + snow_value, 1) == 0: - return None - return round(rain_value + snow_value, 1) + return round(rain_value + snow_value, 2) + + @staticmethod + def _calc_precipitation_kind(rain, snow): + """Determine the precipitation kind.""" + if WeatherUpdateCoordinator._get_rain(rain) != 0: + if WeatherUpdateCoordinator._get_snow(snow) != 0: + return "Snow and Rain" + return "Rain" + + if WeatherUpdateCoordinator._get_snow(snow) != 0: + return "Snow" + return "None" def _get_condition(self, weather_code, timestamp=None): """Get weather condition from weather data.""" From 6fb2e63e49f01d9eaf09c7b21122981e462e4e99 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 18:34:19 +0100 Subject: [PATCH 1327/1818] Update typing 02 (#48014) --- homeassistant/helpers/__init__.py | 6 +- homeassistant/helpers/aiohttp_client.py | 10 +- homeassistant/helpers/area_registry.py | 14 +- homeassistant/helpers/check_config.py | 12 +- homeassistant/helpers/collection.py | 24 +-- homeassistant/helpers/condition.py | 60 +++--- homeassistant/helpers/config_entry_flow.py | 22 ++- .../helpers/config_entry_oauth2_flow.py | 24 +-- homeassistant/helpers/config_validation.py | 72 +++---- homeassistant/helpers/data_entry_flow.py | 9 +- homeassistant/helpers/debounce.py | 12 +- homeassistant/helpers/deprecation.py | 10 +- homeassistant/helpers/device_registry.py | 176 +++++++++--------- homeassistant/helpers/discovery.py | 14 +- homeassistant/helpers/entity.py | 56 +++--- homeassistant/helpers/entity_component.py | 32 ++-- homeassistant/helpers/entity_platform.py | 36 ++-- homeassistant/helpers/entity_registry.py | 129 ++++++------- homeassistant/helpers/entity_values.py | 16 +- homeassistant/helpers/entityfilter.py | 22 ++- homeassistant/helpers/event.py | 131 ++++++------- homeassistant/helpers/frame.py | 10 +- homeassistant/helpers/httpx_client.py | 6 +- homeassistant/helpers/icon.py | 6 +- homeassistant/helpers/instance_id.py | 5 +- homeassistant/helpers/intent.py | 32 ++-- homeassistant/helpers/location.py | 11 +- homeassistant/helpers/logging.py | 8 +- homeassistant/helpers/network.py | 6 +- homeassistant/helpers/ratelimit.py | 14 +- homeassistant/helpers/reload.py | 13 +- homeassistant/helpers/restore_state.py | 14 +- homeassistant/helpers/script.py | 69 +++---- homeassistant/helpers/script_variables.py | 12 +- homeassistant/helpers/service.py | 57 +++--- homeassistant/helpers/significant_change.py | 24 ++- homeassistant/helpers/singleton.py | 6 +- homeassistant/helpers/state.py | 28 +-- homeassistant/helpers/storage.py | 22 ++- homeassistant/helpers/sun.py | 16 +- homeassistant/helpers/system_info.py | 6 +- homeassistant/helpers/temperature.py | 7 +- homeassistant/helpers/template.py | 38 ++-- homeassistant/helpers/trace.py | 36 ++-- homeassistant/helpers/translation.py | 56 +++--- homeassistant/helpers/trigger.py | 14 +- homeassistant/helpers/update_coordinator.py | 20 +- 47 files changed, 717 insertions(+), 706 deletions(-) diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 6f22be8d323d3e..195c18381465bf 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -1,6 +1,8 @@ """Helper methods for components within Home Assistant.""" +from __future__ import annotations + import re -from typing import TYPE_CHECKING, Any, Iterable, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Iterable, Sequence from homeassistant.const import CONF_PLATFORM @@ -8,7 +10,7 @@ from .typing import ConfigType -def config_per_platform(config: "ConfigType", domain: str) -> Iterable[Tuple[Any, Any]]: +def config_per_platform(config: "ConfigType", domain: str) -> Iterable[tuple[Any, Any]]: """Break a component config into different platforms. For example, will find 'switch', 'switch 2', 'switch 3', .. etc diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 3e1e45e5981159..2a362028d51c26 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -1,8 +1,10 @@ """Helper for aiohttp webclient stuff.""" +from __future__ import annotations + import asyncio from ssl import SSLContext import sys -from typing import Any, Awaitable, Optional, Union, cast +from typing import Any, Awaitable, cast import aiohttp from aiohttp import web @@ -87,7 +89,7 @@ async def async_aiohttp_proxy_web( web_coro: Awaitable[aiohttp.ClientResponse], buffer_size: int = 102400, timeout: int = 10, -) -> Optional[web.StreamResponse]: +) -> web.StreamResponse | None: """Stream websession request to aiohttp web response.""" try: with async_timeout.timeout(timeout): @@ -118,7 +120,7 @@ async def async_aiohttp_proxy_stream( hass: HomeAssistantType, request: web.BaseRequest, stream: aiohttp.StreamReader, - content_type: Optional[str], + content_type: str | None, buffer_size: int = 102400, timeout: int = 10, ) -> web.StreamResponse: @@ -175,7 +177,7 @@ def _async_get_connector( return cast(aiohttp.BaseConnector, hass.data[key]) if verify_ssl: - ssl_context: Union[bool, SSLContext] = ssl_util.client_context() + ssl_context: bool | SSLContext = ssl_util.client_context() else: ssl_context = False diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 164207a8b2ad91..c181fadcfd3504 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,6 +1,8 @@ """Provide a way to connect devices to one physical location.""" +from __future__ import annotations + from collections import OrderedDict -from typing import Container, Dict, Iterable, List, MutableMapping, Optional, cast +from typing import Container, Iterable, MutableMapping, cast import attr @@ -26,7 +28,7 @@ class AreaEntry: name: str = attr.ib() normalized_name: str = attr.ib() - id: Optional[str] = attr.ib(default=None) + id: str | None = attr.ib(default=None) def generate_id(self, existing_ids: Container[str]) -> None: """Initialize ID.""" @@ -46,15 +48,15 @@ def __init__(self, hass: HomeAssistantType) -> None: self.hass = hass self.areas: MutableMapping[str, AreaEntry] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) - self._normalized_name_area_idx: Dict[str, str] = {} + self._normalized_name_area_idx: dict[str, str] = {} @callback - def async_get_area(self, area_id: str) -> Optional[AreaEntry]: + def async_get_area(self, area_id: str) -> AreaEntry | None: """Get area by id.""" return self.areas.get(area_id) @callback - def async_get_area_by_name(self, name: str) -> Optional[AreaEntry]: + def async_get_area_by_name(self, name: str) -> AreaEntry | None: """Get area by name.""" normalized_name = normalize_area_name(name) if normalized_name not in self._normalized_name_area_idx: @@ -171,7 +173,7 @@ def async_schedule_save(self) -> None: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, List[Dict[str, Optional[str]]]]: + def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]: """Return data of area registry to store in a file.""" data = {} diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 5dd7623ecc8ec8..a486c8bcc14792 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -5,7 +5,7 @@ import logging import os from pathlib import Path -from typing import List, NamedTuple, Optional +from typing import NamedTuple import voluptuous as vol @@ -35,8 +35,8 @@ class CheckConfigError(NamedTuple): """Configuration check error.""" message: str - domain: Optional[str] - config: Optional[ConfigType] + domain: str | None + config: ConfigType | None class HomeAssistantConfig(OrderedDict): @@ -45,13 +45,13 @@ class HomeAssistantConfig(OrderedDict): def __init__(self) -> None: """Initialize HA config.""" super().__init__() - self.errors: List[CheckConfigError] = [] + self.errors: list[CheckConfigError] = [] def add_error( self, message: str, - domain: Optional[str] = None, - config: Optional[ConfigType] = None, + domain: str | None = None, + config: ConfigType | None = None, ) -> HomeAssistantConfig: """Add a single error.""" self.errors.append(CheckConfigError(str(message), domain, config)) diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index abeef0f0d680a1..0c74ac413e7e46 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -1,9 +1,11 @@ """Helper to deal with YAML + storage.""" +from __future__ import annotations + from abc import ABC, abstractmethod import asyncio from dataclasses import dataclass import logging -from typing import Any, Awaitable, Callable, Dict, Iterable, List, Optional, cast +from typing import Any, Awaitable, Callable, Iterable, Optional, cast import voluptuous as vol from voluptuous.humanize import humanize_error @@ -72,9 +74,9 @@ class IDManager: def __init__(self) -> None: """Initiate the ID manager.""" - self.collections: List[Dict[str, Any]] = [] + self.collections: list[dict[str, Any]] = [] - def add_collection(self, collection: Dict[str, Any]) -> None: + def add_collection(self, collection: dict[str, Any]) -> None: """Add a collection to check for ID usage.""" self.collections.append(collection) @@ -98,17 +100,17 @@ def generate_id(self, suggestion: str) -> str: class ObservableCollection(ABC): """Base collection type that can be observed.""" - def __init__(self, logger: logging.Logger, id_manager: Optional[IDManager] = None): + def __init__(self, logger: logging.Logger, id_manager: IDManager | None = None): """Initialize the base collection.""" self.logger = logger self.id_manager = id_manager or IDManager() - self.data: Dict[str, dict] = {} - self.listeners: List[ChangeListener] = [] + self.data: dict[str, dict] = {} + self.listeners: list[ChangeListener] = [] self.id_manager.add_collection(self.data) @callback - def async_items(self) -> List[dict]: + def async_items(self) -> list[dict]: """Return list of items in collection.""" return list(self.data.values()) @@ -134,7 +136,7 @@ async def notify_changes(self, change_sets: Iterable[CollectionChangeSet]) -> No class YamlCollection(ObservableCollection): """Offer a collection based on static data.""" - async def async_load(self, data: List[dict]) -> None: + async def async_load(self, data: list[dict]) -> None: """Load the YAML collection. Overrides existing data.""" old_ids = set(self.data) @@ -171,7 +173,7 @@ def __init__( self, store: Store, logger: logging.Logger, - id_manager: Optional[IDManager] = None, + id_manager: IDManager | None = None, ): """Initialize the storage collection.""" super().__init__(logger, id_manager) @@ -182,7 +184,7 @@ def hass(self) -> HomeAssistant: """Home Assistant object.""" return self.store.hass - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data.""" return cast(Optional[dict], await self.store.async_load()) @@ -274,7 +276,7 @@ class IDLessCollection(ObservableCollection): counter = 0 - async def async_load(self, data: List[dict]) -> None: + async def async_load(self, data: list[dict]) -> None: """Load the collection. Overrides existing data.""" await self.notify_changes( [ diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 10b59645ed0bab..ac2fa91afe6412 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -1,4 +1,6 @@ """Offer reusable conditions.""" +from __future__ import annotations + import asyncio from collections import deque from contextlib import contextmanager @@ -7,7 +9,7 @@ import logging import re import sys -from typing import Any, Callable, Container, Generator, List, Optional, Set, Union, cast +from typing import Any, Callable, Container, Generator, cast from homeassistant.components import zone as zone_cmp from homeassistant.components.device_automation import ( @@ -124,7 +126,7 @@ def wrapper(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: async def async_from_config( hass: HomeAssistant, - config: Union[ConfigType, Template], + config: ConfigType | Template, config_validation: bool = True, ) -> ConditionCheckerType: """Turn a condition configuration into a method. @@ -267,10 +269,10 @@ def if_not_condition( def numeric_state( hass: HomeAssistant, - entity: Union[None, str, State], - below: Optional[Union[float, str]] = None, - above: Optional[Union[float, str]] = None, - value_template: Optional[Template] = None, + entity: None | str | State, + below: float | str | None = None, + above: float | str | None = None, + value_template: Template | None = None, variables: TemplateVarsType = None, ) -> bool: """Test a numeric state condition.""" @@ -288,12 +290,12 @@ def numeric_state( def async_numeric_state( hass: HomeAssistant, - entity: Union[None, str, State], - below: Optional[Union[float, str]] = None, - above: Optional[Union[float, str]] = None, - value_template: Optional[Template] = None, + entity: None | str | State, + below: float | str | None = None, + above: float | str | None = None, + value_template: Template | None = None, variables: TemplateVarsType = None, - attribute: Optional[str] = None, + attribute: str | None = None, ) -> bool: """Test a numeric state condition.""" if entity is None: @@ -456,10 +458,10 @@ def if_numeric_state( def state( hass: HomeAssistant, - entity: Union[None, str, State], + entity: None | str | State, req_state: Any, - for_period: Optional[timedelta] = None, - attribute: Optional[str] = None, + for_period: timedelta | None = None, + attribute: str | None = None, ) -> bool: """Test if state matches requirements. @@ -526,7 +528,7 @@ def state_from_config( if config_validation: config = cv.STATE_CONDITION_SCHEMA(config) entity_ids = config.get(CONF_ENTITY_ID, []) - req_states: Union[str, List[str]] = config.get(CONF_STATE, []) + req_states: str | list[str] = config.get(CONF_STATE, []) for_period = config.get("for") attribute = config.get(CONF_ATTRIBUTE) @@ -560,10 +562,10 @@ def if_state(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: def sun( hass: HomeAssistant, - before: Optional[str] = None, - after: Optional[str] = None, - before_offset: Optional[timedelta] = None, - after_offset: Optional[timedelta] = None, + before: str | None = None, + after: str | None = None, + before_offset: timedelta | None = None, + after_offset: timedelta | None = None, ) -> bool: """Test if current time matches sun requirements.""" utcnow = dt_util.utcnow() @@ -673,9 +675,9 @@ def template_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool def time( hass: HomeAssistant, - before: Optional[Union[dt_util.dt.time, str]] = None, - after: Optional[Union[dt_util.dt.time, str]] = None, - weekday: Union[None, str, Container[str]] = None, + before: dt_util.dt.time | str | None = None, + after: dt_util.dt.time | str | None = None, + weekday: None | str | Container[str] = None, ) -> bool: """Test if local time condition matches. @@ -752,8 +754,8 @@ def time_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool: def zone( hass: HomeAssistant, - zone_ent: Union[None, str, State], - entity: Union[None, str, State], + zone_ent: None | str | State, + entity: None | str | State, ) -> bool: """Test if zone-condition matches. @@ -858,8 +860,8 @@ async def async_device_from_config( async def async_validate_condition_config( - hass: HomeAssistant, config: Union[ConfigType, Template] -) -> Union[ConfigType, Template]: + hass: HomeAssistant, config: ConfigType | Template +) -> ConfigType | Template: """Validate config.""" if isinstance(config, Template): return config @@ -884,9 +886,9 @@ async def async_validate_condition_config( @callback -def async_extract_entities(config: Union[ConfigType, Template]) -> Set[str]: +def async_extract_entities(config: ConfigType | Template) -> set[str]: """Extract entities from a condition.""" - referenced: Set[str] = set() + referenced: set[str] = set() to_process = deque([config]) while to_process: @@ -912,7 +914,7 @@ def async_extract_entities(config: Union[ConfigType, Template]) -> Set[str]: @callback -def async_extract_devices(config: Union[ConfigType, Template]) -> Set[str]: +def async_extract_devices(config: ConfigType | Template) -> set[str]: """Extract devices from a condition.""" referenced = set() to_process = deque([config]) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index fea188ca336c33..8d0178caa8e1b3 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -1,5 +1,7 @@ """Helpers for data entry flows for config entries.""" -from typing import Any, Awaitable, Callable, Dict, Optional, Union +from __future__ import annotations + +from typing import Any, Awaitable, Callable, Union from homeassistant import config_entries @@ -27,8 +29,8 @@ def __init__( self.CONNECTION_CLASS = connection_class # pylint: disable=invalid-name async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -38,8 +40,8 @@ async def async_step_user( return await self.async_step_confirm() async def async_step_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm setup.""" if user_input is None: self._set_confirm_only() @@ -68,8 +70,8 @@ async def async_step_confirm( return self.async_create_entry(title=self._title, data={}) async def async_step_discovery( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -84,7 +86,7 @@ async def async_step_discovery( async_step_homekit = async_step_discovery async_step_dhcp = async_step_discovery - async def async_step_import(self, _: Optional[Dict[str, Any]]) -> Dict[str, Any]: + async def async_step_import(self, _: dict[str, Any] | None) -> dict[str, Any]: """Handle a flow initialized by import.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -133,8 +135,8 @@ def __init__( self._allow_multiple = allow_multiple async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a user initiated set up flow to create a webhook.""" if not self._allow_multiple and self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 691715452ea1de..a949685c7b118b 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -5,12 +5,14 @@ - OAuth2 implementation that works with local provided client ID/secret """ +from __future__ import annotations + from abc import ABC, ABCMeta, abstractmethod import asyncio import logging import secrets import time -from typing import Any, Awaitable, Callable, Dict, Optional, cast +from typing import Any, Awaitable, Callable, Dict, cast from aiohttp import client, web import async_timeout @@ -231,7 +233,7 @@ def extra_authorize_data(self) -> dict: return {} async def async_step_pick_implementation( - self, user_input: Optional[dict] = None + self, user_input: dict | None = None ) -> dict: """Handle a flow start.""" implementations = await async_get_implementations(self.hass, self.DOMAIN) @@ -260,8 +262,8 @@ async def async_step_pick_implementation( ) async def async_step_auth( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Create an entry for auth.""" # Flow has been triggered by external data if user_input: @@ -286,8 +288,8 @@ async def async_step_auth( return self.async_external_step(step_id="auth", url=url) async def async_step_creation( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Create config entry from external data.""" token = await self.flow_impl.async_resolve_external_data(self.external_data) # Force int for non-compliant oauth2 providers @@ -312,8 +314,8 @@ async def async_oauth_create_entry(self, data: dict) -> dict: return self.async_create_entry(title=self.flow_impl.name, data=data) async def async_step_discovery( - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" await self.async_set_unique_id(self.DOMAIN) @@ -354,7 +356,7 @@ def async_register_implementation( async def async_get_implementations( hass: HomeAssistant, domain: str -) -> Dict[str, AbstractOAuth2Implementation]: +) -> dict[str, AbstractOAuth2Implementation]: """Return OAuth2 implementations for specified domain.""" registered = cast( Dict[str, AbstractOAuth2Implementation], @@ -392,7 +394,7 @@ def async_add_implementation_provider( hass: HomeAssistant, provider_domain: str, async_provide_implementation: Callable[ - [HomeAssistant, str], Awaitable[Optional[AbstractOAuth2Implementation]] + [HomeAssistant, str], Awaitable[AbstractOAuth2Implementation | None] ], ) -> None: """Add an implementation provider. @@ -516,7 +518,7 @@ def _encode_jwt(hass: HomeAssistant, data: dict) -> str: @callback -def _decode_jwt(hass: HomeAssistant, encoded: str) -> Optional[dict]: +def _decode_jwt(hass: HomeAssistant, encoded: str) -> dict | None: """JWT encode data.""" secret = cast(str, hass.data.get(DATA_JWT_SECRET)) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index b06e9974125753..7f2f1550cfe7a0 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,4 +1,6 @@ """Helpers for config validation using voluptuous.""" +from __future__ import annotations + from datetime import ( date as date_sys, datetime as datetime_sys, @@ -12,19 +14,7 @@ import os import re from socket import _GLOBAL_DEFAULT_TIMEOUT # type: ignore # private, not in typeshed -from typing import ( - Any, - Callable, - Dict, - Hashable, - List, - Optional, - Pattern, - Type, - TypeVar, - Union, - cast, -) +from typing import Any, Callable, Dict, Hashable, Pattern, TypeVar, cast from urllib.parse import urlparse from uuid import UUID @@ -131,7 +121,7 @@ def path(value: Any) -> str: def has_at_least_one_key(*keys: str) -> Callable: """Validate that at least one key exists.""" - def validate(obj: Dict) -> Dict: + def validate(obj: dict) -> dict: """Test keys exist in dict.""" if not isinstance(obj, dict): raise vol.Invalid("expected dictionary") @@ -144,10 +134,10 @@ def validate(obj: Dict) -> Dict: return validate -def has_at_most_one_key(*keys: str) -> Callable[[Dict], Dict]: +def has_at_most_one_key(*keys: str) -> Callable[[dict], dict]: """Validate that zero keys exist or one key exists.""" - def validate(obj: Dict) -> Dict: + def validate(obj: dict) -> dict: """Test zero keys exist or one key exists in dict.""" if not isinstance(obj, dict): raise vol.Invalid("expected dictionary") @@ -253,7 +243,7 @@ def isdir(value: Any) -> str: return dir_in -def ensure_list(value: Union[T, List[T], None]) -> List[T]: +def ensure_list(value: T | list[T] | None) -> list[T]: """Wrap value in list if it is not one.""" if value is None: return [] @@ -269,7 +259,7 @@ def entity_id(value: Any) -> str: raise vol.Invalid(f"Entity ID {value} is an invalid entity ID") -def entity_ids(value: Union[str, List]) -> List[str]: +def entity_ids(value: str | list) -> list[str]: """Validate Entity IDs.""" if value is None: raise vol.Invalid("Entity IDs can not be None") @@ -284,7 +274,7 @@ def entity_ids(value: Union[str, List]) -> List[str]: ) -def entity_domain(domain: Union[str, List[str]]) -> Callable[[Any], str]: +def entity_domain(domain: str | list[str]) -> Callable[[Any], str]: """Validate that entity belong to domain.""" ent_domain = entities_domain(domain) @@ -298,9 +288,7 @@ def validate(value: str) -> str: return validate -def entities_domain( - domain: Union[str, List[str]] -) -> Callable[[Union[str, List]], List[str]]: +def entities_domain(domain: str | list[str]) -> Callable[[str | list], list[str]]: """Validate that entities belong to domain.""" if isinstance(domain, str): @@ -312,7 +300,7 @@ def check_invalid(val: str) -> bool: def check_invalid(val: str) -> bool: return val not in domain - def validate(values: Union[str, List]) -> List[str]: + def validate(values: str | list) -> list[str]: """Test if entity domain is domain.""" values = entity_ids(values) for ent_id in values: @@ -325,7 +313,7 @@ def validate(values: Union[str, List]) -> List[str]: return validate -def enum(enumClass: Type[Enum]) -> vol.All: +def enum(enumClass: type[Enum]) -> vol.All: """Create validator for specified enum.""" return vol.All(vol.In(enumClass.__members__), enumClass.__getitem__) @@ -423,7 +411,7 @@ def time_period_str(value: str) -> timedelta: return offset -def time_period_seconds(value: Union[float, str]) -> timedelta: +def time_period_seconds(value: float | str) -> timedelta: """Validate and transform seconds to a time offset.""" try: return timedelta(seconds=float(value)) @@ -450,7 +438,7 @@ def positive_timedelta(value: timedelta) -> timedelta: positive_time_period = vol.All(time_period, positive_timedelta) -def remove_falsy(value: List[T]) -> List[T]: +def remove_falsy(value: list[T]) -> list[T]: """Remove falsy values from a list.""" return [v for v in value if v] @@ -477,7 +465,7 @@ def slug(value: Any) -> str: def schema_with_slug_keys( - value_schema: Union[T, Callable], *, slug_validator: Callable[[Any], str] = slug + value_schema: T | Callable, *, slug_validator: Callable[[Any], str] = slug ) -> Callable: """Ensure dicts have slugs as keys. @@ -486,7 +474,7 @@ def schema_with_slug_keys( """ schema = vol.Schema({str: value_schema}) - def verify(value: Dict) -> Dict: + def verify(value: dict) -> dict: """Validate all keys are slugs and then the value_schema.""" if not isinstance(value, dict): raise vol.Invalid("expected dictionary") @@ -547,7 +535,7 @@ def temperature_unit(value: Any) -> str: ) -def template(value: Optional[Any]) -> template_helper.Template: +def template(value: Any | None) -> template_helper.Template: """Validate a jinja2 template.""" if value is None: raise vol.Invalid("template value is None") @@ -563,7 +551,7 @@ def template(value: Optional[Any]) -> template_helper.Template: raise vol.Invalid(f"invalid template ({ex})") from ex -def dynamic_template(value: Optional[Any]) -> template_helper.Template: +def dynamic_template(value: Any | None) -> template_helper.Template: """Validate a dynamic (non static) jinja2 template.""" if value is None: raise vol.Invalid("template value is None") @@ -632,7 +620,7 @@ def time_zone(value: str) -> str: weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)]) -def socket_timeout(value: Optional[Any]) -> object: +def socket_timeout(value: Any | None) -> object: """Validate timeout float > 0.0. None coerced to socket._GLOBAL_DEFAULT_TIMEOUT bare object. @@ -681,7 +669,7 @@ def uuid4_hex(value: Any) -> str: return result.hex -def ensure_list_csv(value: Any) -> List: +def ensure_list_csv(value: Any) -> list: """Ensure that input is a list or make one from comma-separated string.""" if isinstance(value, str): return [member.strip() for member in value.split(",")] @@ -709,9 +697,9 @@ def __call__(self, selected: list) -> list: def deprecated( key: str, - replacement_key: Optional[str] = None, - default: Optional[Any] = None, -) -> Callable[[Dict], Dict]: + replacement_key: str | None = None, + default: Any | None = None, +) -> Callable[[dict], dict]: """ Log key as deprecated and provide a replacement (if exists). @@ -743,7 +731,7 @@ def deprecated( " please remove it from your configuration" ) - def validator(config: Dict) -> Dict: + def validator(config: dict) -> dict: """Check if key is in config and log warning.""" if key in config: try: @@ -781,14 +769,14 @@ def validator(config: Dict) -> Dict: def key_value_schemas( - key: str, value_schemas: Dict[str, vol.Schema] -) -> Callable[[Any], Dict[str, Any]]: + key: str, value_schemas: dict[str, vol.Schema] +) -> Callable[[Any], dict[str, Any]]: """Create a validator that validates based on a value for specific key. This gives better error messages. """ - def key_value_validator(value: Any) -> Dict[str, Any]: + def key_value_validator(value: Any) -> dict[str, Any]: if not isinstance(value, dict): raise vol.Invalid("Expected a dictionary") @@ -809,10 +797,10 @@ def key_value_validator(value: Any) -> Dict[str, Any]: def key_dependency( key: Hashable, dependency: Hashable -) -> Callable[[Dict[Hashable, Any]], Dict[Hashable, Any]]: +) -> Callable[[dict[Hashable, Any]], dict[Hashable, Any]]: """Validate that all dependencies exist for key.""" - def validator(value: Dict[Hashable, Any]) -> Dict[Hashable, Any]: + def validator(value: dict[Hashable, Any]) -> dict[Hashable, Any]: """Test dependencies.""" if not isinstance(value, dict): raise vol.Invalid("key dependencies require a dict") @@ -1247,7 +1235,7 @@ def determine_script_action(action: dict) -> str: return SCRIPT_ACTION_CALL_SERVICE -ACTION_TYPE_SCHEMAS: Dict[str, Callable[[Any], dict]] = { +ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { SCRIPT_ACTION_CALL_SERVICE: SERVICE_SCHEMA, SCRIPT_ACTION_DELAY: _SCRIPT_DELAY_SCHEMA, SCRIPT_ACTION_WAIT_TEMPLATE: _SCRIPT_WAIT_TEMPLATE_SCHEMA, diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index e686dd2ae4b910..00d12d3ab907dc 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -1,6 +1,7 @@ """Helpers for the data entry flow.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any from aiohttp import web import voluptuous as vol @@ -20,7 +21,7 @@ def __init__(self, flow_mgr: data_entry_flow.FlowManager) -> None: self._flow_mgr = flow_mgr # pylint: disable=no-self-use - def _prepare_result_json(self, result: Dict[str, Any]) -> Dict[str, Any]: + def _prepare_result_json(self, result: dict[str, Any]) -> dict[str, Any]: """Convert result to JSON.""" if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() @@ -58,7 +59,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): extra=vol.ALLOW_EXTRA, ) ) - async def post(self, request: web.Request, data: Dict[str, Any]) -> web.Response: + async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle a POST request.""" if isinstance(data["handler"], list): handler = tuple(data["handler"]) @@ -99,7 +100,7 @@ async def get(self, request: web.Request, flow_id: str) -> web.Response: @RequestDataValidator(vol.Schema(dict), allow_empty=True) async def post( - self, request: web.Request, flow_id: str, data: Dict[str, Any] + self, request: web.Request, flow_id: str, data: dict[str, Any] ) -> web.Response: """Handle a POST request.""" try: diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 23727c2a00fe77..705f48bbd708a9 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -1,7 +1,9 @@ """Debounce helper.""" +from __future__ import annotations + import asyncio from logging import Logger -from typing import Any, Awaitable, Callable, Optional +from typing import Any, Awaitable, Callable from homeassistant.core import HassJob, HomeAssistant, callback @@ -16,7 +18,7 @@ def __init__( *, cooldown: float, immediate: bool, - function: Optional[Callable[..., Awaitable[Any]]] = None, + function: Callable[..., Awaitable[Any]] | None = None, ): """Initialize debounce. @@ -29,13 +31,13 @@ def __init__( self._function = function self.cooldown = cooldown self.immediate = immediate - self._timer_task: Optional[asyncio.TimerHandle] = None + self._timer_task: asyncio.TimerHandle | None = None self._execute_at_end_of_timer: bool = False self._execute_lock = asyncio.Lock() - self._job: Optional[HassJob] = None if function is None else HassJob(function) + self._job: HassJob | None = None if function is None else HassJob(function) @property - def function(self) -> Optional[Callable[..., Awaitable[Any]]]: + def function(self) -> Callable[..., Awaitable[Any]] | None: """Return the function being wrapped by the Debouncer.""" return self._function diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 7478a7fede98ed..38b1dfca437d57 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -1,8 +1,10 @@ """Deprecation helpers for Home Assistant.""" +from __future__ import annotations + import functools import inspect import logging -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from ..helpers.frame import MissingIntegrationFrame, get_integration_frame @@ -49,8 +51,8 @@ def func_wrapper(self: Callable) -> Any: def get_deprecated( - config: Dict[str, Any], new_name: str, old_name: str, default: Optional[Any] = None -) -> Optional[Any]: + config: dict[str, Any], new_name: str, old_name: str, default: Any | None = None +) -> Any | None: """Allow an old config name to be deprecated with a replacement. If the new config isn't found, but the old one is, the old value is used @@ -85,7 +87,7 @@ def deprecated_decorator(func: Callable) -> Callable: """Decorate function as deprecated.""" @functools.wraps(func) - def deprecated_func(*args: tuple, **kwargs: Dict[str, Any]) -> Any: + def deprecated_func(*args: tuple, **kwargs: dict[str, Any]) -> Any: """Wrap for the original function.""" logger = logging.getLogger(func.__module__) try: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index d311538f27f23a..4018ba6204c597 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,8 +1,10 @@ """Provide a way to connect entities belonging to one device.""" +from __future__ import annotations + from collections import OrderedDict import logging import time -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, cast import attr @@ -50,21 +52,21 @@ class DeviceEntry: """Device Registry Entry.""" - config_entries: Set[str] = attr.ib(converter=set, factory=set) - connections: Set[Tuple[str, str]] = attr.ib(converter=set, factory=set) - identifiers: Set[Tuple[str, str]] = attr.ib(converter=set, factory=set) - manufacturer: Optional[str] = attr.ib(default=None) - model: Optional[str] = attr.ib(default=None) - name: Optional[str] = attr.ib(default=None) - sw_version: Optional[str] = attr.ib(default=None) - via_device_id: Optional[str] = attr.ib(default=None) - area_id: Optional[str] = attr.ib(default=None) - name_by_user: Optional[str] = attr.ib(default=None) - entry_type: Optional[str] = attr.ib(default=None) + config_entries: set[str] = attr.ib(converter=set, factory=set) + connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set) + identifiers: set[tuple[str, str]] = attr.ib(converter=set, factory=set) + manufacturer: str | None = attr.ib(default=None) + model: str | None = attr.ib(default=None) + name: str | None = attr.ib(default=None) + sw_version: str | None = attr.ib(default=None) + via_device_id: str | None = attr.ib(default=None) + area_id: str | None = attr.ib(default=None) + name_by_user: str | None = attr.ib(default=None) + entry_type: str | None = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) # This value is not stored, just used to keep track of events to fire. is_new: bool = attr.ib(default=False) - disabled_by: Optional[str] = attr.ib( + disabled_by: str | None = attr.ib( default=None, validator=attr.validators.in_( ( @@ -75,7 +77,7 @@ class DeviceEntry: ) ), ) - suggested_area: Optional[str] = attr.ib(default=None) + suggested_area: str | None = attr.ib(default=None) @property def disabled(self) -> bool: @@ -87,17 +89,17 @@ def disabled(self) -> bool: class DeletedDeviceEntry: """Deleted Device Registry Entry.""" - config_entries: Set[str] = attr.ib() - connections: Set[Tuple[str, str]] = attr.ib() - identifiers: Set[Tuple[str, str]] = attr.ib() + config_entries: set[str] = attr.ib() + connections: set[tuple[str, str]] = attr.ib() + identifiers: set[tuple[str, str]] = attr.ib() id: str = attr.ib() - orphaned_timestamp: Optional[float] = attr.ib() + orphaned_timestamp: float | None = attr.ib() def to_device_entry( self, config_entry_id: str, - connections: Set[Tuple[str, str]], - identifiers: Set[Tuple[str, str]], + connections: set[tuple[str, str]], + identifiers: set[tuple[str, str]], ) -> DeviceEntry: """Create DeviceEntry from DeletedDeviceEntry.""" return DeviceEntry( @@ -133,9 +135,9 @@ def format_mac(mac: str) -> str: class DeviceRegistry: """Class to hold a registry of devices.""" - devices: Dict[str, DeviceEntry] - deleted_devices: Dict[str, DeletedDeviceEntry] - _devices_index: Dict[str, Dict[str, Dict[Tuple[str, str], str]]] + devices: dict[str, DeviceEntry] + deleted_devices: dict[str, DeletedDeviceEntry] + _devices_index: dict[str, dict[str, dict[tuple[str, str], str]]] def __init__(self, hass: HomeAssistantType) -> None: """Initialize the device registry.""" @@ -144,16 +146,16 @@ def __init__(self, hass: HomeAssistantType) -> None: self._clear_index() @callback - def async_get(self, device_id: str) -> Optional[DeviceEntry]: + def async_get(self, device_id: str) -> DeviceEntry | None: """Get device.""" return self.devices.get(device_id) @callback def async_get_device( self, - identifiers: Set[Tuple[str, str]], - connections: Optional[Set[Tuple[str, str]]] = None, - ) -> Optional[DeviceEntry]: + identifiers: set[tuple[str, str]], + connections: set[tuple[str, str]] | None = None, + ) -> DeviceEntry | None: """Check if device is registered.""" device_id = self._async_get_device_id_from_index( REGISTERED_DEVICE, identifiers, connections @@ -164,9 +166,9 @@ def async_get_device( def _async_get_deleted_device( self, - identifiers: Set[Tuple[str, str]], - connections: Optional[Set[Tuple[str, str]]], - ) -> Optional[DeletedDeviceEntry]: + identifiers: set[tuple[str, str]], + connections: set[tuple[str, str]] | None, + ) -> DeletedDeviceEntry | None: """Check if device is deleted.""" device_id = self._async_get_device_id_from_index( DELETED_DEVICE, identifiers, connections @@ -178,9 +180,9 @@ def _async_get_deleted_device( def _async_get_device_id_from_index( self, index: str, - identifiers: Set[Tuple[str, str]], - connections: Optional[Set[Tuple[str, str]]], - ) -> Optional[str]: + identifiers: set[tuple[str, str]], + connections: set[tuple[str, str]] | None, + ) -> str | None: """Check if device has previously been registered.""" devices_index = self._devices_index[index] for identifier in identifiers: @@ -193,7 +195,7 @@ def _async_get_device_id_from_index( return devices_index[IDX_CONNECTIONS][connection] return None - def _add_device(self, device: Union[DeviceEntry, DeletedDeviceEntry]) -> None: + def _add_device(self, device: DeviceEntry | DeletedDeviceEntry) -> None: """Add a device and index it.""" if isinstance(device, DeletedDeviceEntry): devices_index = self._devices_index[DELETED_DEVICE] @@ -204,7 +206,7 @@ def _add_device(self, device: Union[DeviceEntry, DeletedDeviceEntry]) -> None: _add_device_to_index(devices_index, device) - def _remove_device(self, device: Union[DeviceEntry, DeletedDeviceEntry]) -> None: + def _remove_device(self, device: DeviceEntry | DeletedDeviceEntry) -> None: """Remove a device and remove it from the index.""" if isinstance(device, DeletedDeviceEntry): devices_index = self._devices_index[DELETED_DEVICE] @@ -243,21 +245,21 @@ def async_get_or_create( self, *, config_entry_id: str, - connections: Optional[Set[Tuple[str, str]]] = None, - identifiers: Optional[Set[Tuple[str, str]]] = None, - manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - model: Union[str, None, UndefinedType] = UNDEFINED, - name: Union[str, None, UndefinedType] = UNDEFINED, - default_manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - default_model: Union[str, None, UndefinedType] = UNDEFINED, - default_name: Union[str, None, UndefinedType] = UNDEFINED, - sw_version: Union[str, None, UndefinedType] = UNDEFINED, - entry_type: Union[str, None, UndefinedType] = UNDEFINED, - via_device: Optional[Tuple[str, str]] = None, + connections: set[tuple[str, str]] | None = None, + identifiers: set[tuple[str, str]] | None = None, + manufacturer: str | None | UndefinedType = UNDEFINED, + model: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + default_manufacturer: str | None | UndefinedType = UNDEFINED, + default_model: str | None | UndefinedType = UNDEFINED, + default_name: str | None | UndefinedType = UNDEFINED, + sw_version: str | None | UndefinedType = UNDEFINED, + entry_type: str | None | UndefinedType = UNDEFINED, + via_device: tuple[str, str] | None = None, # To disable a device if it gets created - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - suggested_area: Union[str, None, UndefinedType] = UNDEFINED, - ) -> Optional[DeviceEntry]: + disabled_by: str | None | UndefinedType = UNDEFINED, + suggested_area: str | None | UndefinedType = UNDEFINED, + ) -> DeviceEntry | None: """Get device. Create if it doesn't exist.""" if not identifiers and not connections: return None @@ -294,7 +296,7 @@ def async_get_or_create( if via_device is not None: via = self.async_get_device({via_device}) - via_device_id: Union[str, UndefinedType] = via.id if via else UNDEFINED + via_device_id: str | UndefinedType = via.id if via else UNDEFINED else: via_device_id = UNDEFINED @@ -318,18 +320,18 @@ def async_update_device( self, device_id: str, *, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - model: Union[str, None, UndefinedType] = UNDEFINED, - name: Union[str, None, UndefinedType] = UNDEFINED, - name_by_user: Union[str, None, UndefinedType] = UNDEFINED, - new_identifiers: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - sw_version: Union[str, None, UndefinedType] = UNDEFINED, - via_device_id: Union[str, None, UndefinedType] = UNDEFINED, - remove_config_entry_id: Union[str, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - suggested_area: Union[str, None, UndefinedType] = UNDEFINED, - ) -> Optional[DeviceEntry]: + area_id: str | None | UndefinedType = UNDEFINED, + manufacturer: str | None | UndefinedType = UNDEFINED, + model: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + name_by_user: str | None | UndefinedType = UNDEFINED, + new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + sw_version: str | None | UndefinedType = UNDEFINED, + via_device_id: str | None | UndefinedType = UNDEFINED, + remove_config_entry_id: str | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, + suggested_area: str | None | UndefinedType = UNDEFINED, + ) -> DeviceEntry | None: """Update properties of a device.""" return self._async_update_device( device_id, @@ -351,26 +353,26 @@ def _async_update_device( self, device_id: str, *, - add_config_entry_id: Union[str, UndefinedType] = UNDEFINED, - remove_config_entry_id: Union[str, UndefinedType] = UNDEFINED, - merge_connections: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - merge_identifiers: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - new_identifiers: Union[Set[Tuple[str, str]], UndefinedType] = UNDEFINED, - manufacturer: Union[str, None, UndefinedType] = UNDEFINED, - model: Union[str, None, UndefinedType] = UNDEFINED, - name: Union[str, None, UndefinedType] = UNDEFINED, - sw_version: Union[str, None, UndefinedType] = UNDEFINED, - entry_type: Union[str, None, UndefinedType] = UNDEFINED, - via_device_id: Union[str, None, UndefinedType] = UNDEFINED, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - name_by_user: Union[str, None, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - suggested_area: Union[str, None, UndefinedType] = UNDEFINED, - ) -> Optional[DeviceEntry]: + add_config_entry_id: str | UndefinedType = UNDEFINED, + remove_config_entry_id: str | UndefinedType = UNDEFINED, + merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED, + merge_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + new_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, + manufacturer: str | None | UndefinedType = UNDEFINED, + model: str | None | UndefinedType = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + sw_version: str | None | UndefinedType = UNDEFINED, + entry_type: str | None | UndefinedType = UNDEFINED, + via_device_id: str | None | UndefinedType = UNDEFINED, + area_id: str | None | UndefinedType = UNDEFINED, + name_by_user: str | None | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, + suggested_area: str | None | UndefinedType = UNDEFINED, + ) -> DeviceEntry | None: """Update device attributes.""" old = self.devices[device_id] - changes: Dict[str, Any] = {} + changes: dict[str, Any] = {} config_entries = old.config_entries @@ -529,7 +531,7 @@ def async_schedule_save(self) -> None: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, List[Dict[str, Any]]]: + def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data of device registry to store in a file.""" data = {} @@ -637,7 +639,7 @@ async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: @callback -def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> List[DeviceEntry]: +def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> list[DeviceEntry]: """Return entries that match an area.""" return [device for device in registry.devices.values() if device.area_id == area_id] @@ -645,7 +647,7 @@ def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> List[Devic @callback def async_entries_for_config_entry( registry: DeviceRegistry, config_entry_id: str -) -> List[DeviceEntry]: +) -> list[DeviceEntry]: """Return entries that match a config entry.""" return [ device @@ -769,7 +771,7 @@ async def startup_clean(event: Event) -> None: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, startup_clean) -def _normalize_connections(connections: Set[Tuple[str, str]]) -> Set[Tuple[str, str]]: +def _normalize_connections(connections: set[tuple[str, str]]) -> set[tuple[str, str]]: """Normalize connections to ensure we can match mac addresses.""" return { (key, format_mac(value)) if key == CONNECTION_NETWORK_MAC else (key, value) @@ -778,8 +780,8 @@ def _normalize_connections(connections: Set[Tuple[str, str]]) -> Set[Tuple[str, def _add_device_to_index( - devices_index: Dict[str, Dict[Tuple[str, str], str]], - device: Union[DeviceEntry, DeletedDeviceEntry], + devices_index: dict[str, dict[tuple[str, str], str]], + device: DeviceEntry | DeletedDeviceEntry, ) -> None: """Add a device to the index.""" for identifier in device.identifiers: @@ -789,8 +791,8 @@ def _add_device_to_index( def _remove_device_from_index( - devices_index: Dict[str, Dict[Tuple[str, str], str]], - device: Union[DeviceEntry, DeletedDeviceEntry], + devices_index: dict[str, dict[tuple[str, str], str]], + device: DeviceEntry | DeletedDeviceEntry, ) -> None: """Remove a device from the index.""" for identifier in device.identifiers: diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 7ee72759d65dfd..53dbca867d7a54 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,7 +5,9 @@ - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ -from typing import Any, Callable, Dict, Optional, TypedDict +from __future__ import annotations + +from typing import Any, Callable, TypedDict from homeassistant import core, setup from homeassistant.core import CALLBACK_TYPE @@ -26,8 +28,8 @@ class DiscoveryDict(TypedDict): """Discovery data.""" service: str - platform: Optional[str] - discovered: Optional[DiscoveryInfoType] + platform: str | None + discovered: DiscoveryInfoType | None @core.callback @@ -76,8 +78,8 @@ def discover( async def async_discover( hass: core.HomeAssistant, service: str, - discovered: Optional[DiscoveryInfoType], - component: Optional[str], + discovered: DiscoveryInfoType | None, + component: str | None, hass_config: ConfigType, ) -> None: """Fire discovery event. Can ensure a component is loaded.""" @@ -97,7 +99,7 @@ async def async_discover( def async_listen_platform( hass: core.HomeAssistant, component: str, - callback: Callable[[str, Optional[Dict[str, Any]]], Any], + callback: Callable[[str, dict[str, Any] | None], Any], ) -> None: """Register a platform loader listener. diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 791b1e251cdfdf..894ae6c822595a 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -1,11 +1,13 @@ """An abstract class for entities.""" +from __future__ import annotations + from abc import ABC import asyncio from datetime import datetime, timedelta import functools as ft import logging from timeit import default_timer as timer -from typing import Any, Awaitable, Dict, Iterable, List, Optional +from typing import Any, Awaitable, Iterable from homeassistant.config import DATA_CUSTOMIZE from homeassistant.const import ( @@ -42,16 +44,16 @@ @callback @bind_hass -def entity_sources(hass: HomeAssistant) -> Dict[str, Dict[str, str]]: +def entity_sources(hass: HomeAssistant) -> dict[str, dict[str, str]]: """Get the entity sources.""" return hass.data.get(DATA_ENTITY_SOURCE, {}) def generate_entity_id( entity_id_format: str, - name: Optional[str], - current_ids: Optional[List[str]] = None, - hass: Optional[HomeAssistant] = None, + name: str | None, + current_ids: list[str] | None = None, + hass: HomeAssistant | None = None, ) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" return async_generate_entity_id(entity_id_format, name, current_ids, hass) @@ -60,9 +62,9 @@ def generate_entity_id( @callback def async_generate_entity_id( entity_id_format: str, - name: Optional[str], - current_ids: Optional[Iterable[str]] = None, - hass: Optional[HomeAssistant] = None, + name: str | None, + current_ids: Iterable[str] | None = None, + hass: HomeAssistant | None = None, ) -> str: """Generate a unique entity ID based on given entity IDs or used IDs.""" name = (name or DEVICE_DEFAULT_NAME).lower() @@ -98,7 +100,7 @@ class Entity(ABC): hass: HomeAssistant = None # type: ignore # Owning platform instance. Will be set by EntityPlatform - platform: Optional[EntityPlatform] = None + platform: EntityPlatform | None = None # If we reported if this entity was slow _slow_reported = False @@ -110,17 +112,17 @@ class Entity(ABC): _update_staged = False # Process updates in parallel - parallel_updates: Optional[asyncio.Semaphore] = None + parallel_updates: asyncio.Semaphore | None = None # Entry in the entity registry - registry_entry: Optional[RegistryEntry] = None + registry_entry: RegistryEntry | None = None # Hold list for functions to call on remove. - _on_remove: Optional[List[CALLBACK_TYPE]] = None + _on_remove: list[CALLBACK_TYPE] | None = None # Context - _context: Optional[Context] = None - _context_set: Optional[datetime] = None + _context: Context | None = None + _context_set: datetime | None = None # If entity is added to an entity platform _added = False @@ -134,12 +136,12 @@ def should_poll(self) -> bool: return True @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return None @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return None @@ -149,7 +151,7 @@ def state(self) -> StateType: return STATE_UNKNOWN @property - def capability_attributes(self) -> Optional[Dict[str, Any]]: + def capability_attributes(self) -> dict[str, Any] | None: """Return the capability attributes. Attributes that explain the capabilities of an entity. @@ -160,7 +162,7 @@ def capability_attributes(self) -> Optional[Dict[str, Any]]: return None @property - def state_attributes(self) -> Optional[Dict[str, Any]]: + def state_attributes(self) -> dict[str, Any] | None: """Return the state attributes. Implemented by component base class, should not be extended by integrations. @@ -169,7 +171,7 @@ def state_attributes(self) -> Optional[Dict[str, Any]]: return None @property - def device_state_attributes(self) -> Optional[Dict[str, Any]]: + def device_state_attributes(self) -> dict[str, Any] | None: """Return entity specific state attributes. This method is deprecated, platform classes should implement @@ -178,7 +180,7 @@ def device_state_attributes(self) -> Optional[Dict[str, Any]]: return None @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return entity specific state attributes. Implemented by platform classes. Convention for attribute names @@ -187,7 +189,7 @@ def extra_state_attributes(self) -> Optional[Dict[str, Any]]: return None @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Return device specific attributes. Implemented by platform classes. @@ -195,22 +197,22 @@ def device_info(self) -> Optional[Dict[str, Any]]: return None @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return None @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" return None @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon to use in the frontend, if any.""" return None @property - def entity_picture(self) -> Optional[str]: + def entity_picture(self) -> str | None: """Return the entity picture to use in the frontend, if any.""" return None @@ -234,7 +236,7 @@ def force_update(self) -> bool: return False @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int | None: """Flag supported features.""" return None @@ -516,7 +518,7 @@ def add_to_platform_start( self, hass: HomeAssistant, platform: EntityPlatform, - parallel_updates: Optional[asyncio.Semaphore], + parallel_updates: asyncio.Semaphore | None, ) -> None: """Start adding an entity to a platform.""" if self._added: diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 6fb8696d8450a1..17131665240466 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,10 +1,12 @@ """Helpers for components that manage entities.""" +from __future__ import annotations + import asyncio from datetime import timedelta from itertools import chain import logging from types import ModuleType -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Callable, Iterable import voluptuous as vol @@ -76,10 +78,10 @@ def __init__( self.domain = domain self.scan_interval = scan_interval - self.config: Optional[ConfigType] = None + self.config: ConfigType | None = None - self._platforms: Dict[ - Union[str, Tuple[str, Optional[timedelta], Optional[str]]], EntityPlatform + self._platforms: dict[ + str | tuple[str, timedelta | None, str | None], EntityPlatform ] = {domain: self._async_init_entity_platform(domain, None)} self.async_add_entities = self._platforms[domain].async_add_entities self.add_entities = self._platforms[domain].add_entities @@ -93,7 +95,7 @@ def entities(self) -> Iterable[entity.Entity]: platform.entities.values() for platform in self._platforms.values() ) - def get_entity(self, entity_id: str) -> Optional[entity.Entity]: + def get_entity(self, entity_id: str) -> entity.Entity | None: """Get an entity.""" for platform in self._platforms.values(): entity_obj = platform.entities.get(entity_id) @@ -125,7 +127,7 @@ async def async_setup(self, config: ConfigType) -> None: # Generic discovery listener for loading platform dynamically # Refer to: homeassistant.helpers.discovery.async_load_platform() async def component_platform_discovered( - platform: str, info: Optional[Dict[str, Any]] + platform: str, info: dict[str, Any] | None ) -> None: """Handle the loading of a platform.""" await self.async_setup_platform(platform, {}, info) @@ -176,7 +178,7 @@ async def async_unload_entry(self, config_entry: ConfigEntry) -> bool: async def async_extract_from_service( self, service_call: ServiceCall, expand_group: bool = True - ) -> List[entity.Entity]: + ) -> list[entity.Entity]: """Extract all known and available entities from a service call. Will return an empty list if entities specified but unknown. @@ -191,9 +193,9 @@ async def async_extract_from_service( def async_register_entity_service( self, name: str, - schema: Union[Dict[str, Any], vol.Schema], - func: Union[str, Callable[..., Any]], - required_features: Optional[List[int]] = None, + schema: dict[str, Any] | vol.Schema, + func: str | Callable[..., Any], + required_features: list[int] | None = None, ) -> None: """Register an entity service.""" if isinstance(schema, dict): @@ -211,7 +213,7 @@ async def async_setup_platform( self, platform_type: str, platform_config: ConfigType, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up a platform for this component.""" if self.config is None: @@ -274,7 +276,7 @@ async def async_remove_entity(self, entity_id: str) -> None: async def async_prepare_reload( self, *, skip_reset: bool = False - ) -> Optional[ConfigType]: + ) -> ConfigType | None: """Prepare reloading this entity component. This method must be run in the event loop. @@ -303,9 +305,9 @@ async def async_prepare_reload( def _async_init_entity_platform( self, platform_type: str, - platform: Optional[ModuleType], - scan_interval: Optional[timedelta] = None, - entity_namespace: Optional[str] = None, + platform: ModuleType | None, + scan_interval: timedelta | None = None, + entity_namespace: str | None = None, ) -> EntityPlatform: """Initialize an entity platform.""" if scan_interval is None: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index d860a8a33902be..382ebf8055ec60 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta from logging import Logger from types import ModuleType -from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Iterable, List, Optional +from typing import TYPE_CHECKING, Callable, Coroutine, Iterable from homeassistant import config_entries from homeassistant.const import ATTR_RESTORED, DEVICE_DEFAULT_NAME @@ -49,9 +49,9 @@ def __init__( logger: Logger, domain: str, platform_name: str, - platform: Optional[ModuleType], + platform: ModuleType | None, scan_interval: timedelta, - entity_namespace: Optional[str], + entity_namespace: str | None, ): """Initialize the entity platform.""" self.hass = hass @@ -61,18 +61,18 @@ def __init__( self.platform = platform self.scan_interval = scan_interval self.entity_namespace = entity_namespace - self.config_entry: Optional[config_entries.ConfigEntry] = None - self.entities: Dict[str, Entity] = {} - self._tasks: List[asyncio.Future] = [] + self.config_entry: config_entries.ConfigEntry | None = None + self.entities: dict[str, Entity] = {} + self._tasks: list[asyncio.Future] = [] # Stop tracking tasks after setup is completed self._setup_complete = False # Method to cancel the state change listener - self._async_unsub_polling: Optional[CALLBACK_TYPE] = None + self._async_unsub_polling: CALLBACK_TYPE | None = None # Method to cancel the retry of setup - self._async_cancel_retry_setup: Optional[CALLBACK_TYPE] = None - self._process_updates: Optional[asyncio.Lock] = None + self._async_cancel_retry_setup: CALLBACK_TYPE | None = None + self._process_updates: asyncio.Lock | None = None - self.parallel_updates: Optional[asyncio.Semaphore] = None + self.parallel_updates: asyncio.Semaphore | None = None # Platform is None for the EntityComponent "catch-all" EntityPlatform # which powers entity_component.add_entities @@ -89,7 +89,7 @@ def __repr__(self) -> str: @callback def _get_parallel_updates_semaphore( self, entity_has_async_update: bool - ) -> Optional[asyncio.Semaphore]: + ) -> asyncio.Semaphore | None: """Get or create a semaphore for parallel updates. Semaphore will be created on demand because we base it off if update method is async or not. @@ -364,7 +364,7 @@ async def _async_add_entity( # type: ignore[no-untyped-def] return requested_entity_id = None - suggested_object_id: Optional[str] = None + suggested_object_id: str | None = None # Get entity_id from unique ID registration if entity.unique_id is not None: @@ -378,7 +378,7 @@ async def _async_add_entity( # type: ignore[no-untyped-def] suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" if self.config_entry is not None: - config_entry_id: Optional[str] = self.config_entry.entry_id + config_entry_id: str | None = self.config_entry.entry_id else: config_entry_id = None @@ -408,7 +408,7 @@ async def _async_add_entity( # type: ignore[no-untyped-def] if device: device_id = device.id - disabled_by: Optional[str] = None + disabled_by: str | None = None if not entity.entity_registry_enabled_default: disabled_by = DISABLED_INTEGRATION @@ -550,7 +550,7 @@ async def async_remove_entity(self, entity_id: str) -> None: async def async_extract_from_service( self, service_call: ServiceCall, expand_group: bool = True - ) -> List[Entity]: + ) -> list[Entity]: """Extract all known and available entities from a service call. Will return an empty list if entities specified but unknown. @@ -621,7 +621,7 @@ async def _update_entity_states(self, now: datetime) -> None: await asyncio.gather(*tasks) -current_platform: ContextVar[Optional[EntityPlatform]] = ContextVar( +current_platform: ContextVar[EntityPlatform | None] = ContextVar( "current_platform", default=None ) @@ -629,7 +629,7 @@ async def _update_entity_states(self, now: datetime) -> None: @callback def async_get_platforms( hass: HomeAssistantType, integration_name: str -) -> List[EntityPlatform]: +) -> list[EntityPlatform]: """Find existing platforms.""" if ( DATA_ENTITY_PLATFORM not in hass.data @@ -637,6 +637,6 @@ def async_get_platforms( ): return [] - platforms: List[EntityPlatform] = hass.data[DATA_ENTITY_PLATFORM][integration_name] + platforms: list[EntityPlatform] = hass.data[DATA_ENTITY_PLATFORM][integration_name] return platforms diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 36b010c82a034a..3d33b73271dc4f 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -7,20 +7,11 @@ registered. Registering a new entity while a timer is in progress resets the timer. """ +from __future__ import annotations + from collections import OrderedDict import logging -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Iterable, - List, - Optional, - Tuple, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Callable, Iterable, cast import attr @@ -80,12 +71,12 @@ class RegistryEntry: entity_id: str = attr.ib() unique_id: str = attr.ib() platform: str = attr.ib() - name: Optional[str] = attr.ib(default=None) - icon: Optional[str] = attr.ib(default=None) - device_id: Optional[str] = attr.ib(default=None) - area_id: Optional[str] = attr.ib(default=None) - config_entry_id: Optional[str] = attr.ib(default=None) - disabled_by: Optional[str] = attr.ib( + name: str | None = attr.ib(default=None) + icon: str | None = attr.ib(default=None) + device_id: str | None = attr.ib(default=None) + area_id: str | None = attr.ib(default=None) + config_entry_id: str | None = attr.ib(default=None) + disabled_by: str | None = attr.ib( default=None, validator=attr.validators.in_( ( @@ -98,13 +89,13 @@ class RegistryEntry: ) ), ) - capabilities: Optional[Dict[str, Any]] = attr.ib(default=None) + capabilities: dict[str, Any] | None = attr.ib(default=None) supported_features: int = attr.ib(default=0) - device_class: Optional[str] = attr.ib(default=None) - unit_of_measurement: Optional[str] = attr.ib(default=None) + device_class: str | None = attr.ib(default=None) + unit_of_measurement: str | None = attr.ib(default=None) # As set by integration - original_name: Optional[str] = attr.ib(default=None) - original_icon: Optional[str] = attr.ib(default=None) + original_name: str | None = attr.ib(default=None) + original_icon: str | None = attr.ib(default=None) domain: str = attr.ib(init=False, repr=False) @domain.default @@ -120,7 +111,7 @@ def disabled(self) -> bool: @callback def write_unavailable_state(self, hass: HomeAssistantType) -> None: """Write the unavailable state to the state machine.""" - attrs: Dict[str, Any] = {ATTR_RESTORED: True} + attrs: dict[str, Any] = {ATTR_RESTORED: True} if self.capabilities is not None: attrs.update(self.capabilities) @@ -151,8 +142,8 @@ class EntityRegistry: def __init__(self, hass: HomeAssistantType): """Initialize the registry.""" self.hass = hass - self.entities: Dict[str, RegistryEntry] - self._index: Dict[Tuple[str, str, str], str] = {} + self.entities: dict[str, RegistryEntry] + self._index: dict[tuple[str, str, str], str] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self.hass.bus.async_listen( EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified @@ -161,7 +152,7 @@ def __init__(self, hass: HomeAssistantType): @callback def async_get_device_class_lookup(self, domain_device_classes: set) -> dict: """Return a lookup for the device class by domain.""" - lookup: Dict[str, Dict[Tuple[Any, Any], str]] = {} + lookup: dict[str, dict[tuple[Any, Any], str]] = {} for entity in self.entities.values(): if not entity.device_id: continue @@ -180,14 +171,14 @@ def async_is_registered(self, entity_id: str) -> bool: return entity_id in self.entities @callback - def async_get(self, entity_id: str) -> Optional[RegistryEntry]: + def async_get(self, entity_id: str) -> RegistryEntry | None: """Get EntityEntry for an entity_id.""" return self.entities.get(entity_id) @callback def async_get_entity_id( self, domain: str, platform: str, unique_id: str - ) -> Optional[str]: + ) -> str | None: """Check if an entity_id is currently registered.""" return self._index.get((domain, platform, unique_id)) @@ -196,7 +187,7 @@ def async_generate_entity_id( self, domain: str, suggested_object_id: str, - known_object_ids: Optional[Iterable[str]] = None, + known_object_ids: Iterable[str] | None = None, ) -> str: """Generate an entity ID that does not conflict. @@ -226,20 +217,20 @@ def async_get_or_create( unique_id: str, *, # To influence entity ID generation - suggested_object_id: Optional[str] = None, - known_object_ids: Optional[Iterable[str]] = None, + suggested_object_id: str | None = None, + known_object_ids: Iterable[str] | None = None, # To disable an entity if it gets created - disabled_by: Optional[str] = None, + disabled_by: str | None = None, # Data that we want entry to have - config_entry: Optional["ConfigEntry"] = None, - device_id: Optional[str] = None, - area_id: Optional[str] = None, - capabilities: Optional[Dict[str, Any]] = None, - supported_features: Optional[int] = None, - device_class: Optional[str] = None, - unit_of_measurement: Optional[str] = None, - original_name: Optional[str] = None, - original_icon: Optional[str] = None, + config_entry: "ConfigEntry" | None = None, + device_id: str | None = None, + area_id: str | None = None, + capabilities: dict[str, Any] | None = None, + supported_features: int | None = None, + device_class: str | None = None, + unit_of_measurement: str | None = None, + original_name: str | None = None, + original_icon: str | None = None, ) -> RegistryEntry: """Get entity. Create if it doesn't exist.""" config_entry_id = None @@ -363,12 +354,12 @@ def async_update_entity( self, entity_id: str, *, - name: Union[str, None, UndefinedType] = UNDEFINED, - icon: Union[str, None, UndefinedType] = UNDEFINED, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - new_entity_id: Union[str, UndefinedType] = UNDEFINED, - new_unique_id: Union[str, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + icon: str | None | UndefinedType = UNDEFINED, + area_id: str | None | UndefinedType = UNDEFINED, + new_entity_id: str | UndefinedType = UNDEFINED, + new_unique_id: str | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Update properties of an entity.""" return self._async_update_entity( @@ -386,20 +377,20 @@ def _async_update_entity( self, entity_id: str, *, - name: Union[str, None, UndefinedType] = UNDEFINED, - icon: Union[str, None, UndefinedType] = UNDEFINED, - config_entry_id: Union[str, None, UndefinedType] = UNDEFINED, - new_entity_id: Union[str, UndefinedType] = UNDEFINED, - device_id: Union[str, None, UndefinedType] = UNDEFINED, - area_id: Union[str, None, UndefinedType] = UNDEFINED, - new_unique_id: Union[str, UndefinedType] = UNDEFINED, - disabled_by: Union[str, None, UndefinedType] = UNDEFINED, - capabilities: Union[Dict[str, Any], None, UndefinedType] = UNDEFINED, - supported_features: Union[int, UndefinedType] = UNDEFINED, - device_class: Union[str, None, UndefinedType] = UNDEFINED, - unit_of_measurement: Union[str, None, UndefinedType] = UNDEFINED, - original_name: Union[str, None, UndefinedType] = UNDEFINED, - original_icon: Union[str, None, UndefinedType] = UNDEFINED, + name: str | None | UndefinedType = UNDEFINED, + icon: str | None | UndefinedType = UNDEFINED, + config_entry_id: str | None | UndefinedType = UNDEFINED, + new_entity_id: str | UndefinedType = UNDEFINED, + device_id: str | None | UndefinedType = UNDEFINED, + area_id: str | None | UndefinedType = UNDEFINED, + new_unique_id: str | UndefinedType = UNDEFINED, + disabled_by: str | None | UndefinedType = UNDEFINED, + capabilities: dict[str, Any] | None | UndefinedType = UNDEFINED, + supported_features: int | UndefinedType = UNDEFINED, + device_class: str | None | UndefinedType = UNDEFINED, + unit_of_measurement: str | None | UndefinedType = UNDEFINED, + original_name: str | None | UndefinedType = UNDEFINED, + original_icon: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] @@ -479,7 +470,7 @@ async def async_load(self) -> None: old_conf_load_func=load_yaml, old_conf_migrate_func=_async_migrate, ) - entities: Dict[str, RegistryEntry] = OrderedDict() + entities: dict[str, RegistryEntry] = OrderedDict() if data is not None: for entity in data["entities"]: @@ -516,7 +507,7 @@ def async_schedule_save(self) -> None: self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback - def _data_to_save(self) -> Dict[str, Any]: + def _data_to_save(self) -> dict[str, Any]: """Return data of entity registry to store in a file.""" data = {} @@ -605,7 +596,7 @@ async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: @callback def async_entries_for_device( registry: EntityRegistry, device_id: str, include_disabled_entities: bool = False -) -> List[RegistryEntry]: +) -> list[RegistryEntry]: """Return entries that match a device.""" return [ entry @@ -618,7 +609,7 @@ def async_entries_for_device( @callback def async_entries_for_area( registry: EntityRegistry, area_id: str -) -> List[RegistryEntry]: +) -> list[RegistryEntry]: """Return entries that match an area.""" return [entry for entry in registry.entities.values() if entry.area_id == area_id] @@ -626,7 +617,7 @@ def async_entries_for_area( @callback def async_entries_for_config_entry( registry: EntityRegistry, config_entry_id: str -) -> List[RegistryEntry]: +) -> list[RegistryEntry]: """Return entries that match a config entry.""" return [ entry @@ -665,7 +656,7 @@ def async_config_entry_disabled_by_changed( ) -async def _async_migrate(entities: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: +async def _async_migrate(entities: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: """Migrate the YAML config file to storage helper format.""" return { "entities": [ @@ -721,7 +712,7 @@ def _write_unavailable_states(_: Event) -> None: async def async_migrate_entries( hass: HomeAssistantType, config_entry_id: str, - entry_callback: Callable[[RegistryEntry], Optional[dict]], + entry_callback: Callable[[RegistryEntry], dict | None], ) -> None: """Migrator of unique IDs.""" ent_reg = await async_get_registry(hass) diff --git a/homeassistant/helpers/entity_values.py b/homeassistant/helpers/entity_values.py index 7f44e8b27684d1..57dbb34c560a05 100644 --- a/homeassistant/helpers/entity_values.py +++ b/homeassistant/helpers/entity_values.py @@ -1,8 +1,10 @@ """A class to hold entity values.""" +from __future__ import annotations + from collections import OrderedDict import fnmatch import re -from typing import Any, Dict, Optional, Pattern +from typing import Any, Pattern from homeassistant.core import split_entity_id @@ -14,17 +16,17 @@ class EntityValues: def __init__( self, - exact: Optional[Dict[str, Dict[str, str]]] = None, - domain: Optional[Dict[str, Dict[str, str]]] = None, - glob: Optional[Dict[str, Dict[str, str]]] = None, + exact: dict[str, dict[str, str]] | None = None, + domain: dict[str, dict[str, str]] | None = None, + glob: dict[str, dict[str, str]] | None = None, ) -> None: """Initialize an EntityConfigDict.""" - self._cache: Dict[str, Dict[str, str]] = {} + self._cache: dict[str, dict[str, str]] = {} self._exact = exact self._domain = domain if glob is None: - compiled: Optional[Dict[Pattern[str], Any]] = None + compiled: dict[Pattern[str], Any] | None = None else: compiled = OrderedDict() for key, value in glob.items(): @@ -32,7 +34,7 @@ def __init__( self._glob = compiled - def get(self, entity_id: str) -> Dict[str, str]: + def get(self, entity_id: str) -> dict[str, str]: """Get config for an entity id.""" if entity_id in self._cache: return self._cache[entity_id] diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index 608fae0242ef98..ebde309de144e9 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -1,7 +1,9 @@ """Helper class to implement include/exclude of entities and domains.""" +from __future__ import annotations + import fnmatch import re -from typing import Callable, Dict, List, Pattern +from typing import Callable, Pattern import voluptuous as vol @@ -19,7 +21,7 @@ CONF_ENTITY_GLOBS = "entity_globs" -def convert_filter(config: Dict[str, List[str]]) -> Callable[[str], bool]: +def convert_filter(config: dict[str, list[str]]) -> Callable[[str], bool]: """Convert the filter schema into a filter.""" filt = generate_filter( config[CONF_INCLUDE_DOMAINS], @@ -57,7 +59,7 @@ def convert_filter(config: Dict[str, List[str]]) -> Callable[[str], bool]: def convert_include_exclude_filter( - config: Dict[str, Dict[str, List[str]]] + config: dict[str, dict[str, list[str]]] ) -> Callable[[str], bool]: """Convert the include exclude filter schema into a filter.""" include = config[CONF_INCLUDE] @@ -107,7 +109,7 @@ def _glob_to_re(glob: str) -> Pattern[str]: return re.compile(fnmatch.translate(glob)) -def _test_against_patterns(patterns: List[Pattern[str]], entity_id: str) -> bool: +def _test_against_patterns(patterns: list[Pattern[str]], entity_id: str) -> bool: """Test entity against list of patterns, true if any match.""" for pattern in patterns: if pattern.match(entity_id): @@ -119,12 +121,12 @@ def _test_against_patterns(patterns: List[Pattern[str]], entity_id: str) -> bool # It's safe since we don't modify it. And None causes typing warnings # pylint: disable=dangerous-default-value def generate_filter( - include_domains: List[str], - include_entities: List[str], - exclude_domains: List[str], - exclude_entities: List[str], - include_entity_globs: List[str] = [], - exclude_entity_globs: List[str] = [], + include_domains: list[str], + include_entities: list[str], + exclude_domains: list[str], + exclude_entities: list[str], + include_entity_globs: list[str] = [], + exclude_entity_globs: list[str] = [], ) -> Callable[[str], bool]: """Return a function that will filter entities based on the args.""" include_d = set(include_domains) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index f496c7088a40da..2a3ee75ce7561b 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,4 +1,6 @@ """Helpers for listening to events.""" +from __future__ import annotations + import asyncio import copy from dataclasses import dataclass @@ -6,18 +8,7 @@ import functools as ft import logging import time -from typing import ( - Any, - Awaitable, - Callable, - Dict, - Iterable, - List, - Optional, - Set, - Tuple, - Union, -) +from typing import Any, Awaitable, Callable, Iterable, List import attr @@ -79,8 +70,8 @@ class TrackStates: """ all_states: bool - entities: Set - domains: Set + entities: set + domains: set @dataclass @@ -94,7 +85,7 @@ class TrackTemplate: template: Template variables: TemplateVarsType - rate_limit: Optional[timedelta] = None + rate_limit: timedelta | None = None @dataclass @@ -146,10 +137,10 @@ def remove() -> None: @bind_hass def async_track_state_change( hass: HomeAssistant, - entity_ids: Union[str, Iterable[str]], + entity_ids: str | Iterable[str], action: Callable[[str, State, State], None], - from_state: Union[None, str, Iterable[str]] = None, - to_state: Union[None, str, Iterable[str]] = None, + from_state: None | str | Iterable[str] = None, + to_state: None | str | Iterable[str] = None, ) -> CALLBACK_TYPE: """Track specific state changes. @@ -240,7 +231,7 @@ def state_change_listener(event: Event) -> None: @bind_hass def async_track_state_change_event( hass: HomeAssistant, - entity_ids: Union[str, Iterable[str]], + entity_ids: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track specific state change events indexed by entity_id. @@ -337,7 +328,7 @@ def _async_remove_indexed_listeners( @bind_hass def async_track_entity_registry_updated_event( hass: HomeAssistant, - entity_ids: Union[str, Iterable[str]], + entity_ids: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track specific entity registry updated events indexed by entity_id. @@ -402,7 +393,7 @@ def remove_listener() -> None: @callback def _async_dispatch_domain_event( - hass: HomeAssistant, event: Event, callbacks: Dict[str, List] + hass: HomeAssistant, event: Event, callbacks: dict[str, list] ) -> None: domain = split_entity_id(event.data["entity_id"])[0] @@ -423,7 +414,7 @@ def _async_dispatch_domain_event( @bind_hass def async_track_state_added_domain( hass: HomeAssistant, - domains: Union[str, Iterable[str]], + domains: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track state change events when an entity is added to domains.""" @@ -476,7 +467,7 @@ def remove_listener() -> None: @bind_hass def async_track_state_removed_domain( hass: HomeAssistant, - domains: Union[str, Iterable[str]], + domains: str | Iterable[str], action: Callable[[Event], Any], ) -> Callable[[], None]: """Track state change events when an entity is removed from domains.""" @@ -527,7 +518,7 @@ def remove_listener() -> None: @callback -def _async_string_to_lower_list(instr: Union[str, Iterable[str]]) -> List[str]: +def _async_string_to_lower_list(instr: str | Iterable[str]) -> list[str]: if isinstance(instr, str): return [instr.lower()] @@ -546,7 +537,7 @@ def __init__( """Handle removal / refresh of tracker init.""" self.hass = hass self._action = action - self._listeners: Dict[str, Callable] = {} + self._listeners: dict[str, Callable] = {} self._last_track_states: TrackStates = track_states @callback @@ -569,7 +560,7 @@ def async_setup(self) -> None: self._setup_entities_listener(track_states.domains, track_states.entities) @property - def listeners(self) -> Dict: + def listeners(self) -> dict: """State changes that will cause a re-render.""" track_states = self._last_track_states return { @@ -628,7 +619,7 @@ def _cancel_listener(self, listener_name: str) -> None: self._listeners.pop(listener_name)() @callback - def _setup_entities_listener(self, domains: Set, entities: Set) -> None: + def _setup_entities_listener(self, domains: set, entities: set) -> None: if domains: entities = entities.copy() entities.update(self.hass.states.async_entity_ids(domains)) @@ -642,7 +633,7 @@ def _setup_entities_listener(self, domains: Set, entities: Set) -> None: ) @callback - def _setup_domains_listener(self, domains: Set) -> None: + def _setup_domains_listener(self, domains: set) -> None: if not domains: return @@ -691,8 +682,8 @@ def async_track_state_change_filtered( def async_track_template( hass: HomeAssistant, template: Template, - action: Callable[[str, Optional[State], Optional[State]], None], - variables: Optional[TemplateVarsType] = None, + action: Callable[[str, State | None, State | None], None], + variables: TemplateVarsType | None = None, ) -> Callable[[], None]: """Add a listener that fires when a a template evaluates to 'true'. @@ -734,7 +725,7 @@ def async_track_template( @callback def _template_changed_listener( - event: Event, updates: List[TrackTemplateResult] + event: Event, updates: list[TrackTemplateResult] ) -> None: """Check if condition is correct and run action.""" track_result = updates.pop() @@ -792,12 +783,12 @@ def __init__( track_template_.template.hass = hass self._track_templates = track_templates - self._last_result: Dict[Template, Union[str, TemplateError]] = {} + self._last_result: dict[Template, str | TemplateError] = {} self._rate_limit = KeyedRateLimit(hass) - self._info: Dict[Template, RenderInfo] = {} - self._track_state_changes: Optional[_TrackStateChangeFiltered] = None - self._time_listeners: Dict[Template, Callable] = {} + self._info: dict[Template, RenderInfo] = {} + self._track_state_changes: _TrackStateChangeFiltered | None = None + self._time_listeners: dict[Template, Callable] = {} def async_setup(self, raise_on_template_error: bool) -> None: """Activation of template tracking.""" @@ -826,7 +817,7 @@ def async_setup(self, raise_on_template_error: bool) -> None: ) @property - def listeners(self) -> Dict: + def listeners(self) -> dict: """State changes that will cause a re-render.""" assert self._track_state_changes return { @@ -882,8 +873,8 @@ def _render_template_if_ready( self, track_template_: TrackTemplate, now: datetime, - event: Optional[Event], - ) -> Union[bool, TrackTemplateResult]: + event: Event | None, + ) -> bool | TrackTemplateResult: """Re-render the template if conditions match. Returns False if the template was not be re-rendered @@ -927,7 +918,7 @@ def _render_template_if_ready( ) try: - result: Union[str, TemplateError] = info.result() + result: str | TemplateError = info.result() except TemplateError as ex: result = ex @@ -945,9 +936,9 @@ def _render_template_if_ready( @callback def _refresh( self, - event: Optional[Event], - track_templates: Optional[Iterable[TrackTemplate]] = None, - replayed: Optional[bool] = False, + event: Event | None, + track_templates: Iterable[TrackTemplate] | None = None, + replayed: bool | None = False, ) -> None: """Refresh the template. @@ -1076,16 +1067,16 @@ def async_track_same_state( hass: HomeAssistant, period: timedelta, action: Callable[..., None], - async_check_same_func: Callable[[str, Optional[State], Optional[State]], bool], - entity_ids: Union[str, Iterable[str]] = MATCH_ALL, + async_check_same_func: Callable[[str, State | None, State | None], bool], + entity_ids: str | Iterable[str] = MATCH_ALL, ) -> CALLBACK_TYPE: """Track the state of entities for a period and run an action. If async_check_func is None it use the state of orig_value. Without entity_ids we track all state changes. """ - async_remove_state_for_cancel: Optional[CALLBACK_TYPE] = None - async_remove_state_for_listener: Optional[CALLBACK_TYPE] = None + async_remove_state_for_cancel: CALLBACK_TYPE | None = None + async_remove_state_for_listener: CALLBACK_TYPE | None = None job = HassJob(action) @@ -1113,8 +1104,8 @@ def state_for_listener(now: Any) -> None: def state_for_cancel_listener(event: Event) -> None: """Fire on changes and cancel for listener if changed.""" entity: str = event.data["entity_id"] - from_state: Optional[State] = event.data.get("old_state") - to_state: Optional[State] = event.data.get("new_state") + from_state: State | None = event.data.get("old_state") + to_state: State | None = event.data.get("new_state") if not async_check_same_func(entity, from_state, to_state): clear_listener() @@ -1144,7 +1135,7 @@ def state_for_cancel_listener(event: Event) -> None: @bind_hass def async_track_point_in_time( hass: HomeAssistant, - action: Union[HassJob, Callable[..., None]], + action: HassJob | Callable[..., None], point_in_time: datetime, ) -> CALLBACK_TYPE: """Add a listener that fires once after a specific point in time.""" @@ -1165,7 +1156,7 @@ def utc_converter(utc_now: datetime) -> None: @bind_hass def async_track_point_in_utc_time( hass: HomeAssistant, - action: Union[HassJob, Callable[..., None]], + action: HassJob | Callable[..., None], point_in_time: datetime, ) -> CALLBACK_TYPE: """Add a listener that fires once after a specific point in UTC time.""" @@ -1176,7 +1167,7 @@ def async_track_point_in_utc_time( # having to figure out how to call the action every time its called. job = action if isinstance(action, HassJob) else HassJob(action) - cancel_callback: Optional[asyncio.TimerHandle] = None + cancel_callback: asyncio.TimerHandle | None = None @callback def run_action() -> None: @@ -1217,7 +1208,7 @@ def unsub_point_in_time_listener() -> None: @callback @bind_hass def async_call_later( - hass: HomeAssistant, delay: float, action: Union[HassJob, Callable[..., None]] + hass: HomeAssistant, delay: float, action: HassJob | Callable[..., None] ) -> CALLBACK_TYPE: """Add a listener that is called in .""" return async_track_point_in_utc_time( @@ -1232,7 +1223,7 @@ def async_call_later( @bind_hass def async_track_time_interval( hass: HomeAssistant, - action: Callable[..., Union[None, Awaitable]], + action: Callable[..., None | Awaitable], interval: timedelta, ) -> CALLBACK_TYPE: """Add a listener that fires repetitively at every timedelta interval.""" @@ -1276,9 +1267,9 @@ class SunListener: hass: HomeAssistant = attr.ib() job: HassJob = attr.ib() event: str = attr.ib() - offset: Optional[timedelta] = attr.ib() - _unsub_sun: Optional[CALLBACK_TYPE] = attr.ib(default=None) - _unsub_config: Optional[CALLBACK_TYPE] = attr.ib(default=None) + offset: timedelta | None = attr.ib() + _unsub_sun: CALLBACK_TYPE | None = attr.ib(default=None) + _unsub_config: CALLBACK_TYPE | None = attr.ib(default=None) @callback def async_attach(self) -> None: @@ -1332,7 +1323,7 @@ def _handle_config_event(self, _event: Any) -> None: @callback @bind_hass def async_track_sunrise( - hass: HomeAssistant, action: Callable[..., None], offset: Optional[timedelta] = None + hass: HomeAssistant, action: Callable[..., None], offset: timedelta | None = None ) -> CALLBACK_TYPE: """Add a listener that will fire a specified offset from sunrise daily.""" listener = SunListener(hass, HassJob(action), SUN_EVENT_SUNRISE, offset) @@ -1346,7 +1337,7 @@ def async_track_sunrise( @callback @bind_hass def async_track_sunset( - hass: HomeAssistant, action: Callable[..., None], offset: Optional[timedelta] = None + hass: HomeAssistant, action: Callable[..., None], offset: timedelta | None = None ) -> CALLBACK_TYPE: """Add a listener that will fire a specified offset from sunset daily.""" listener = SunListener(hass, HassJob(action), SUN_EVENT_SUNSET, offset) @@ -1365,9 +1356,9 @@ def async_track_sunset( def async_track_utc_time_change( hass: HomeAssistant, action: Callable[..., None], - hour: Optional[Any] = None, - minute: Optional[Any] = None, - second: Optional[Any] = None, + hour: Any | None = None, + minute: Any | None = None, + second: Any | None = None, local: bool = False, ) -> CALLBACK_TYPE: """Add a listener that will fire if time matches a pattern.""" @@ -1394,7 +1385,7 @@ def calculate_next(now: datetime) -> datetime: localized_now, matching_seconds, matching_minutes, matching_hours ) - time_listener: Optional[CALLBACK_TYPE] = None + time_listener: CALLBACK_TYPE | None = None @callback def pattern_time_change_listener(_: datetime) -> None: @@ -1431,9 +1422,9 @@ def unsub_pattern_time_change_listener() -> None: def async_track_time_change( hass: HomeAssistant, action: Callable[..., None], - hour: Optional[Any] = None, - minute: Optional[Any] = None, - second: Optional[Any] = None, + hour: Any | None = None, + minute: Any | None = None, + second: Any | None = None, ) -> CALLBACK_TYPE: """Add a listener that will fire if UTC time matches a pattern.""" return async_track_utc_time_change(hass, action, hour, minute, second, local=True) @@ -1442,9 +1433,7 @@ def async_track_time_change( track_time_change = threaded_listener_factory(async_track_time_change) -def process_state_match( - parameter: Union[None, str, Iterable[str]] -) -> Callable[[str], bool]: +def process_state_match(parameter: None | str | Iterable[str]) -> Callable[[str], bool]: """Convert parameter to function that matches input against parameter.""" if parameter is None or parameter == MATCH_ALL: return lambda _: True @@ -1459,7 +1448,7 @@ def process_state_match( @callback def _entities_domains_from_render_infos( render_infos: Iterable[RenderInfo], -) -> Tuple[Set, Set]: +) -> tuple[set, set]: """Combine from multiple RenderInfo.""" entities = set() domains = set() @@ -1520,7 +1509,7 @@ def _event_triggers_rerender(event: Event, info: RenderInfo) -> bool: @callback def _rate_limit_for_event( event: Event, info: RenderInfo, track_template_: TrackTemplate -) -> Optional[timedelta]: +) -> timedelta | None: """Determine the rate limit for an event.""" entity_id = event.data.get(ATTR_ENTITY_ID) @@ -1532,7 +1521,7 @@ def _rate_limit_for_event( if track_template_.rate_limit is not None: return track_template_.rate_limit - rate_limit: Optional[timedelta] = info.rate_limit + rate_limit: timedelta | None = info.rate_limit return rate_limit diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index a0517338ec89fb..f10e8f4c25c4a5 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -1,9 +1,11 @@ """Provide frame helper for finding the current frame context.""" +from __future__ import annotations + import asyncio import functools import logging from traceback import FrameSummary, extract_stack -from typing import Any, Callable, Optional, Tuple, TypeVar, cast +from typing import Any, Callable, TypeVar, cast from homeassistant.exceptions import HomeAssistantError @@ -13,8 +15,8 @@ def get_integration_frame( - exclude_integrations: Optional[set] = None, -) -> Tuple[FrameSummary, str, str]: + exclude_integrations: set | None = None, +) -> tuple[FrameSummary, str, str]: """Return the frame, integration and integration path of the current stack frame.""" found_frame = None if not exclude_integrations: @@ -64,7 +66,7 @@ def report(what: str) -> None: def report_integration( - what: str, integration_frame: Tuple[FrameSummary, str, str] + what: str, integration_frame: tuple[FrameSummary, str, str] ) -> None: """Report incorrect usage in an integration. diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index b86223964b3231..dfac0694a074a1 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -1,6 +1,8 @@ """Helper for httpx.""" +from __future__ import annotations + import sys -from typing import Any, Callable, Optional +from typing import Any, Callable import httpx @@ -29,7 +31,7 @@ def get_async_client( """ key = DATA_ASYNC_CLIENT if verify_ssl else DATA_ASYNC_CLIENT_NOVERIFY - client: Optional[httpx.AsyncClient] = hass.data.get(key) + client: httpx.AsyncClient | None = hass.data.get(key) if client is None: client = hass.data[key] = create_async_httpx_client(hass, verify_ssl) diff --git a/homeassistant/helpers/icon.py b/homeassistant/helpers/icon.py index dd64e9c92f11d6..628dc9d341e396 100644 --- a/homeassistant/helpers/icon.py +++ b/homeassistant/helpers/icon.py @@ -1,9 +1,9 @@ """Icon helper methods.""" -from typing import Optional +from __future__ import annotations def icon_for_battery_level( - battery_level: Optional[int] = None, charging: bool = False + battery_level: int | None = None, charging: bool = False ) -> str: """Return a battery icon valid identifier.""" icon = "mdi:battery" @@ -20,7 +20,7 @@ def icon_for_battery_level( return icon -def icon_for_signal_level(signal_level: Optional[int] = None) -> str: +def icon_for_signal_level(signal_level: int | None = None) -> str: """Return a signal icon valid identifier.""" if signal_level is None or signal_level == 0: return "mdi:signal-cellular-outline" diff --git a/homeassistant/helpers/instance_id.py b/homeassistant/helpers/instance_id.py index 5feca605099770..5b6f645c55a353 100644 --- a/homeassistant/helpers/instance_id.py +++ b/homeassistant/helpers/instance_id.py @@ -1,5 +1,6 @@ """Helper to create a unique instance ID.""" -from typing import Dict, Optional +from __future__ import annotations + import uuid from homeassistant.core import HomeAssistant @@ -17,7 +18,7 @@ async def async_get(hass: HomeAssistant) -> str: """Get unique ID for the hass instance.""" store = storage.Store(hass, DATA_VERSION, DATA_KEY, True) - data: Optional[Dict[str, str]] = await storage.async_migrator( # type: ignore + data: dict[str, str] | None = await storage.async_migrator( # type: ignore hass, hass.config.path(LEGACY_UUID_FILE), store, diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 1c5d56ccbd12b4..2bc2ff0f837f59 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -3,7 +3,7 @@ import logging import re -from typing import Any, Callable, Dict, Iterable, Optional +from typing import Any, Callable, Dict, Iterable import voluptuous as vol @@ -52,9 +52,9 @@ async def async_handle( hass: HomeAssistantType, platform: str, intent_type: str, - slots: Optional[_SlotsType] = None, - text_input: Optional[str] = None, - context: Optional[Context] = None, + slots: _SlotsType | None = None, + text_input: str | None = None, + context: Context | None = None, ) -> IntentResponse: """Handle an intent.""" handler: IntentHandler = hass.data.get(DATA_KEY, {}).get(intent_type) @@ -103,7 +103,7 @@ class IntentUnexpectedError(IntentError): @callback @bind_hass def async_match_state( - hass: HomeAssistantType, name: str, states: Optional[Iterable[State]] = None + hass: HomeAssistantType, name: str, states: Iterable[State] | None = None ) -> State: """Find a state that matches the name.""" if states is None: @@ -127,10 +127,10 @@ def async_test_feature(state: State, feature: int, feature_name: str) -> None: class IntentHandler: """Intent handler registration.""" - intent_type: Optional[str] = None - slot_schema: Optional[vol.Schema] = None - _slot_schema: Optional[vol.Schema] = None - platforms: Optional[Iterable[str]] = [] + intent_type: str | None = None + slot_schema: vol.Schema | None = None + _slot_schema: vol.Schema | None = None + platforms: Iterable[str] | None = [] @callback def async_can_handle(self, intent_obj: Intent) -> bool: @@ -163,7 +163,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} - {self.intent_type}>" -def _fuzzymatch(name: str, items: Iterable[T], key: Callable[[T], str]) -> Optional[T]: +def _fuzzymatch(name: str, items: Iterable[T], key: Callable[[T], str]) -> T | None: """Fuzzy matching function.""" matches = [] pattern = ".*?".join(name) @@ -226,7 +226,7 @@ def __init__( platform: str, intent_type: str, slots: _SlotsType, - text_input: Optional[str], + text_input: str | None, context: Context, ) -> None: """Initialize an intent.""" @@ -246,15 +246,15 @@ def create_response(self) -> IntentResponse: class IntentResponse: """Response to an intent.""" - def __init__(self, intent: Optional[Intent] = None) -> None: + def __init__(self, intent: Intent | None = None) -> None: """Initialize an IntentResponse.""" self.intent = intent - self.speech: Dict[str, Dict[str, Any]] = {} - self.card: Dict[str, Dict[str, str]] = {} + self.speech: dict[str, dict[str, Any]] = {} + self.card: dict[str, dict[str, str]] = {} @callback def async_set_speech( - self, speech: str, speech_type: str = "plain", extra_data: Optional[Any] = None + self, speech: str, speech_type: str = "plain", extra_data: Any | None = None ) -> None: """Set speech response.""" self.speech[speech_type] = {"speech": speech, "extra_data": extra_data} @@ -267,6 +267,6 @@ def async_set_card( self.card[card_type] = {"title": title, "content": content} @callback - def as_dict(self) -> Dict[str, Dict[str, Dict[str, Any]]]: + 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} diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 19058bc3e7f0ab..4e02b40abbb4ec 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -1,7 +1,8 @@ """Location helpers for Home Assistant.""" +from __future__ import annotations import logging -from typing import Optional, Sequence +from typing import Sequence import voluptuous as vol @@ -25,9 +26,7 @@ def has_location(state: State) -> bool: ) -def closest( - latitude: float, longitude: float, states: Sequence[State] -) -> Optional[State]: +def closest(latitude: float, longitude: float, states: Sequence[State]) -> State | None: """Return closest state to point. Async friendly. @@ -50,8 +49,8 @@ def closest( def find_coordinates( - hass: HomeAssistantType, entity_id: str, recursion_history: Optional[list] = None -) -> Optional[str]: + hass: HomeAssistantType, entity_id: str, recursion_history: list | None = None +) -> str | None: """Find the gps coordinates of the entity in the form of '90.000,180.000'.""" entity_state = hass.states.get(entity_id) diff --git a/homeassistant/helpers/logging.py b/homeassistant/helpers/logging.py index 49b9bfcffec6f6..a32d13ce513359 100644 --- a/homeassistant/helpers/logging.py +++ b/homeassistant/helpers/logging.py @@ -1,7 +1,9 @@ """Helpers for logging allowing more advanced logging styles to be used.""" +from __future__ import annotations + import inspect import logging -from typing import Any, Mapping, MutableMapping, Optional, Tuple +from typing import Any, Mapping, MutableMapping class KeywordMessage: @@ -26,7 +28,7 @@ class KeywordStyleAdapter(logging.LoggerAdapter): """Represents an adapter wrapping the logger allowing KeywordMessages.""" def __init__( - self, logger: logging.Logger, extra: Optional[Mapping[str, Any]] = None + self, logger: logging.Logger, extra: Mapping[str, Any] | None = None ) -> None: """Initialize a new StyleAdapter for the provided logger.""" super().__init__(logger, extra or {}) @@ -41,7 +43,7 @@ def log(self, level: int, msg: Any, *args: Any, **kwargs: Any) -> None: def process( self, msg: Any, kwargs: MutableMapping[str, Any] - ) -> Tuple[Any, MutableMapping[str, Any]]: + ) -> tuple[Any, MutableMapping[str, Any]]: """Process the keyword args in preparation for logging.""" return ( msg, diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 21f69dc539a6ba..6278bf8f855f2d 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -1,6 +1,8 @@ """Network helpers.""" +from __future__ import annotations + from ipaddress import ip_address -from typing import Optional, cast +from typing import cast import yarl @@ -117,7 +119,7 @@ def get_url( raise NoURLAvailableError -def _get_request_host() -> Optional[str]: +def _get_request_host() -> str | None: """Get the host address of the current request.""" request = http.current_request.get() if request is None: diff --git a/homeassistant/helpers/ratelimit.py b/homeassistant/helpers/ratelimit.py index 40f10e69d2512d..fa671c6627f915 100644 --- a/homeassistant/helpers/ratelimit.py +++ b/homeassistant/helpers/ratelimit.py @@ -1,8 +1,10 @@ """Ratelimit helper.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Callable, Dict, Hashable, Optional +from typing import Any, Callable, Hashable from homeassistant.core import HomeAssistant, callback import homeassistant.util.dt as dt_util @@ -19,8 +21,8 @@ def __init__( ): """Initialize ratelimit tracker.""" self.hass = hass - self._last_triggered: Dict[Hashable, datetime] = {} - self._rate_limit_timers: Dict[Hashable, asyncio.TimerHandle] = {} + self._last_triggered: dict[Hashable, datetime] = {} + self._rate_limit_timers: dict[Hashable, asyncio.TimerHandle] = {} @callback def async_has_timer(self, key: Hashable) -> bool: @@ -30,7 +32,7 @@ def async_has_timer(self, key: Hashable) -> bool: return key in self._rate_limit_timers @callback - def async_triggered(self, key: Hashable, now: Optional[datetime] = None) -> None: + def async_triggered(self, key: Hashable, now: datetime | None = None) -> None: """Call when the action we are tracking was triggered.""" self.async_cancel_timer(key) self._last_triggered[key] = now or dt_util.utcnow() @@ -54,11 +56,11 @@ def async_remove(self) -> None: def async_schedule_action( self, key: Hashable, - rate_limit: Optional[timedelta], + rate_limit: timedelta | None, now: datetime, action: Callable, *args: Any, - ) -> Optional[datetime]: + ) -> datetime | None: """Check rate limits and schedule an action if we hit the limit. If the rate limit is hit: diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index 4a768a7932080f..b53ed554e861bf 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -1,8 +1,9 @@ """Class to reload platforms.""" +from __future__ import annotations import asyncio import logging -from typing import Dict, Iterable, List, Optional +from typing import Iterable from homeassistant import config as conf_util from homeassistant.const import SERVICE_RELOAD @@ -61,7 +62,7 @@ async def _resetup_platform( if not conf: return - root_config: Dict = {integration_platform: []} + root_config: dict = {integration_platform: []} # Extract only the config for template, ignore the rest. for p_type, p_config in config_per_platform(conf, integration_platform): if p_type != integration_name: @@ -101,7 +102,7 @@ async def _async_setup_platform( hass: HomeAssistantType, integration_name: str, integration_platform: str, - platform_configs: List[Dict], + platform_configs: list[dict], ) -> None: """Platform for the first time when new configuration is added.""" if integration_platform not in hass.data: @@ -119,7 +120,7 @@ async def _async_setup_platform( async def _async_reconfig_platform( - platform: EntityPlatform, platform_configs: List[Dict] + platform: EntityPlatform, platform_configs: list[dict] ) -> None: """Reconfigure an already loaded platform.""" await platform.async_reset() @@ -129,7 +130,7 @@ async def _async_reconfig_platform( async def async_integration_yaml_config( hass: HomeAssistantType, integration_name: str -) -> Optional[ConfigType]: +) -> ConfigType | None: """Fetch the latest yaml configuration for an integration.""" integration = await async_get_integration(hass, integration_name) @@ -141,7 +142,7 @@ async def async_integration_yaml_config( @callback def async_get_platform_without_config_entry( hass: HomeAssistantType, integration_name: str, integration_platform_name: str -) -> Optional[EntityPlatform]: +) -> EntityPlatform | None: """Find an existing platform that is not a config entry.""" for integration_platform in async_get_platforms(hass, integration_name): if integration_platform.config_entry is not None: diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 51ebc6fa774d75..3350ed7a073cc4 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -4,7 +4,7 @@ import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, cast from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import ( @@ -45,12 +45,12 @@ def __init__(self, state: State, last_seen: datetime) -> None: self.state = state self.last_seen = last_seen - def as_dict(self) -> Dict[str, Any]: + 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} @classmethod - def from_dict(cls, json_dict: Dict) -> StoredState: + def from_dict(cls, json_dict: dict) -> StoredState: """Initialize a stored state from a dict.""" last_seen = json_dict["last_seen"] @@ -106,11 +106,11 @@ def __init__(self, hass: HomeAssistant) -> None: self.store: Store = Store( hass, STORAGE_VERSION, STORAGE_KEY, encoder=JSONEncoder ) - self.last_states: Dict[str, StoredState] = {} - self.entity_ids: Set[str] = set() + self.last_states: dict[str, StoredState] = {} + self.entity_ids: set[str] = set() @callback - def async_get_stored_states(self) -> List[StoredState]: + def async_get_stored_states(self) -> list[StoredState]: """Get the set of states which should be stored. This includes the states of all registered entities, as well as the @@ -249,7 +249,7 @@ async def async_internal_will_remove_from_hass(self) -> None: ) data.async_restore_entity_removed(self.entity_id) - async def async_get_last_state(self) -> Optional[State]: + async def async_get_last_state(self) -> State | None: """Get the entity state from the previous run.""" if self.hass is None or self.entity_id is None: # Return None if this entity isn't added to hass yet diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 51bf7b14927323..be6be32da1eafb 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,4 +1,6 @@ """Helpers to execute scripts.""" +from __future__ import annotations + import asyncio from contextlib import asynccontextmanager from datetime import datetime, timedelta @@ -6,18 +8,7 @@ import itertools import logging from types import MappingProxyType -from typing import ( - Any, - Callable, - Dict, - List, - Optional, - Sequence, - Set, - Tuple, - Union, - cast, -) +from typing import Any, Callable, Dict, Sequence, Union, cast import async_timeout import voluptuous as vol @@ -232,8 +223,8 @@ def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA): async def async_validate_actions_config( - hass: HomeAssistant, actions: List[ConfigType] -) -> List[ConfigType]: + hass: HomeAssistant, actions: list[ConfigType] +) -> list[ConfigType]: """Validate a list of actions.""" return await asyncio.gather( *[async_validate_action_config(hass, action) for action in actions] @@ -300,8 +291,8 @@ def __init__( self, hass: HomeAssistant, script: "Script", - variables: Dict[str, Any], - context: Optional[Context], + variables: dict[str, Any], + context: Context | None, log_exceptions: bool, ) -> None: self._hass = hass @@ -310,7 +301,7 @@ def __init__( self._context = context self._log_exceptions = log_exceptions self._step = -1 - self._action: Optional[Dict[str, Any]] = None + self._action: dict[str, Any] | None = None self._stop = asyncio.Event() self._stopped = asyncio.Event() @@ -890,7 +881,7 @@ async def _async_stop_scripts_at_shutdown(hass, event): _VarsType = Union[Dict[str, Any], MappingProxyType] -def _referenced_extract_ids(data: Dict[str, Any], key: str, found: Set[str]) -> None: +def _referenced_extract_ids(data: dict[str, Any], key: str, found: set[str]) -> None: """Extract referenced IDs.""" if not data: return @@ -913,20 +904,20 @@ class Script: def __init__( self, hass: HomeAssistant, - sequence: Sequence[Dict[str, Any]], + sequence: Sequence[dict[str, Any]], name: str, domain: str, *, # Used in "Running " log message - running_description: Optional[str] = None, - change_listener: Optional[Callable[..., Any]] = None, + running_description: str | None = None, + change_listener: Callable[..., Any] | None = None, script_mode: str = DEFAULT_SCRIPT_MODE, max_runs: int = DEFAULT_MAX, max_exceeded: str = DEFAULT_MAX_EXCEEDED, - logger: Optional[logging.Logger] = None, + logger: logging.Logger | None = None, log_exceptions: bool = True, top_level: bool = True, - variables: Optional[ScriptVariables] = None, + variables: ScriptVariables | None = None, ) -> None: """Initialize the script.""" all_scripts = hass.data.get(DATA_SCRIPTS) @@ -959,25 +950,25 @@ def __init__( self._log_exceptions = log_exceptions self.last_action = None - self.last_triggered: Optional[datetime] = None + self.last_triggered: datetime | None = None - self._runs: List[_ScriptRun] = [] + self._runs: list[_ScriptRun] = [] self.max_runs = max_runs self._max_exceeded = max_exceeded if script_mode == SCRIPT_MODE_QUEUED: self._queue_lck = asyncio.Lock() - self._config_cache: Dict[Set[Tuple], Callable[..., bool]] = {} - self._repeat_script: Dict[int, Script] = {} - self._choose_data: Dict[int, Dict[str, Any]] = {} - self._referenced_entities: Optional[Set[str]] = None - self._referenced_devices: Optional[Set[str]] = None + self._config_cache: dict[set[tuple], Callable[..., bool]] = {} + self._repeat_script: dict[int, Script] = {} + self._choose_data: dict[int, dict[str, Any]] = {} + self._referenced_entities: set[str] | None = None + self._referenced_devices: set[str] | None = None self.variables = variables self._variables_dynamic = template.is_complex(variables) if self._variables_dynamic: template.attach(hass, variables) @property - def change_listener(self) -> Optional[Callable[..., Any]]: + def change_listener(self) -> Callable[..., Any] | None: """Return the change_listener.""" return self._change_listener @@ -991,13 +982,13 @@ def change_listener(self, change_listener: Callable[..., Any]) -> None: ): self._change_listener_job = HassJob(change_listener) - def _set_logger(self, logger: Optional[logging.Logger] = None) -> None: + def _set_logger(self, logger: logging.Logger | None = None) -> None: if logger: self._logger = logger else: self._logger = logging.getLogger(f"{__name__}.{slugify(self.name)}") - def update_logger(self, logger: Optional[logging.Logger] = None) -> None: + def update_logger(self, logger: logging.Logger | None = None) -> None: """Update logger.""" self._set_logger(logger) for script in self._repeat_script.values(): @@ -1038,7 +1029,7 @@ def referenced_devices(self): if self._referenced_devices is not None: return self._referenced_devices - referenced: Set[str] = set() + referenced: set[str] = set() for step in self.sequence: action = cv.determine_script_action(step) @@ -1067,7 +1058,7 @@ def referenced_entities(self): if self._referenced_entities is not None: return self._referenced_entities - referenced: Set[str] = set() + referenced: set[str] = set() for step in self.sequence: action = cv.determine_script_action(step) @@ -1091,7 +1082,7 @@ def referenced_entities(self): return referenced def run( - self, variables: Optional[_VarsType] = None, context: Optional[Context] = None + self, variables: _VarsType | None = None, context: Context | None = None ) -> None: """Run script.""" asyncio.run_coroutine_threadsafe( @@ -1100,9 +1091,9 @@ def run( async def async_run( self, - run_variables: Optional[_VarsType] = None, - context: Optional[Context] = None, - started_action: Optional[Callable[..., Any]] = None, + run_variables: _VarsType | None = None, + context: Context | None = None, + started_action: Callable[..., Any] | None = None, ) -> None: """Run script.""" if context is None: diff --git a/homeassistant/helpers/script_variables.py b/homeassistant/helpers/script_variables.py index 818263c9dd56b8..86a700bc62bf1c 100644 --- a/homeassistant/helpers/script_variables.py +++ b/homeassistant/helpers/script_variables.py @@ -1,5 +1,7 @@ """Script variables.""" -from typing import Any, Dict, Mapping, Optional +from __future__ import annotations + +from typing import Any, Mapping from homeassistant.core import HomeAssistant, callback @@ -9,20 +11,20 @@ class ScriptVariables: """Class to hold and render script variables.""" - def __init__(self, variables: Dict[str, Any]): + def __init__(self, variables: dict[str, Any]): """Initialize script variables.""" self.variables = variables - self._has_template: Optional[bool] = None + self._has_template: bool | None = None @callback def async_render( self, hass: HomeAssistant, - run_variables: Optional[Mapping[str, Any]], + run_variables: Mapping[str, Any] | None, *, render_as_defaults: bool = True, limited: bool = False, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Render script variables. The run variables are used to compute the static variables. diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 85595998a54206..f1ab245d38a28c 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -10,14 +10,9 @@ Any, Awaitable, Callable, - Dict, Iterable, - List, - Optional, - Set, Tuple, TypedDict, - Union, cast, ) @@ -79,8 +74,8 @@ class ServiceParams(TypedDict): domain: str service: str - service_data: Dict[str, Any] - target: Optional[Dict] + service_data: dict[str, Any] + target: dict | None @dataclasses.dataclass @@ -88,17 +83,17 @@ class SelectedEntities: """Class to hold the selected entities.""" # Entities that were explicitly mentioned. - referenced: Set[str] = dataclasses.field(default_factory=set) + referenced: set[str] = dataclasses.field(default_factory=set) # Entities that were referenced via device/area ID. # Should not trigger a warning when they don't exist. - indirectly_referenced: Set[str] = dataclasses.field(default_factory=set) + indirectly_referenced: set[str] = dataclasses.field(default_factory=set) # Referenced items that could not be found. - missing_devices: Set[str] = dataclasses.field(default_factory=set) - missing_areas: Set[str] = dataclasses.field(default_factory=set) + missing_devices: set[str] = dataclasses.field(default_factory=set) + missing_areas: set[str] = dataclasses.field(default_factory=set) - def log_missing(self, missing_entities: Set[str]) -> None: + def log_missing(self, missing_entities: set[str]) -> None: """Log about missing items.""" parts = [] for label, items in ( @@ -137,7 +132,7 @@ async def async_call_from_config( blocking: bool = False, variables: TemplateVarsType = None, validate_config: bool = True, - context: Optional[ha.Context] = None, + context: ha.Context | None = None, ) -> None: """Call a service based on a config hash.""" try: @@ -235,7 +230,7 @@ def async_prepare_call_from_config( @bind_hass def extract_entity_ids( hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True -) -> Set[str]: +) -> set[str]: """Extract a list of entity ids from a service call. Will convert group entity ids to the entity ids it represents. @@ -251,7 +246,7 @@ async def async_extract_entities( entities: Iterable[Entity], service_call: ha.ServiceCall, expand_group: bool = True, -) -> List[Entity]: +) -> list[Entity]: """Extract a list of entity objects from a service call. Will convert group entity ids to the entity ids it represents. @@ -287,7 +282,7 @@ async def async_extract_entities( @bind_hass async def async_extract_entity_ids( hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True -) -> Set[str]: +) -> set[str]: """Extract a set of entity ids from a service call. Will convert group entity ids to the entity ids it represents. @@ -408,7 +403,7 @@ def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JS def _load_services_files( hass: HomeAssistantType, integrations: Iterable[Integration] -) -> List[JSON_TYPE]: +) -> list[JSON_TYPE]: """Load service files for multiple intergrations.""" return [_load_services_file(hass, integration) for integration in integrations] @@ -416,7 +411,7 @@ def _load_services_files( @bind_hass async def async_get_all_descriptions( hass: HomeAssistantType, -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: """Return descriptions (i.e. user documentation) for all service calls.""" descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) format_cache_key = "{}.{}".format @@ -448,7 +443,7 @@ async def async_get_all_descriptions( loaded[domain] = content # Build response - descriptions: Dict[str, Dict[str, Any]] = {} + descriptions: dict[str, dict[str, Any]] = {} for domain in services: descriptions[domain] = {} @@ -483,7 +478,7 @@ async def async_get_all_descriptions( @ha.callback @bind_hass def async_set_service_schema( - hass: HomeAssistantType, domain: str, service: str, schema: Dict[str, Any] + hass: HomeAssistantType, domain: str, service: str, schema: dict[str, Any] ) -> None: """Register a description for a service.""" hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) @@ -504,9 +499,9 @@ def async_set_service_schema( async def entity_service_call( hass: HomeAssistantType, platforms: Iterable["EntityPlatform"], - func: Union[str, Callable[..., Any]], + func: str | Callable[..., Any], call: ha.ServiceCall, - required_features: Optional[Iterable[int]] = None, + required_features: Iterable[int] | None = None, ) -> None: """Handle an entity service call. @@ -516,17 +511,17 @@ async def entity_service_call( user = await hass.auth.async_get_user(call.context.user_id) if user is None: raise UnknownUser(context=call.context) - entity_perms: Optional[ + entity_perms: None | ( Callable[[str, str], bool] - ] = user.permissions.check_entity + ) = user.permissions.check_entity else: entity_perms = None target_all_entities = call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL if target_all_entities: - referenced: Optional[SelectedEntities] = None - all_referenced: Optional[Set[str]] = None + referenced: SelectedEntities | None = None + all_referenced: set[str] | None = None else: # A set of entities we're trying to target. referenced = await async_extract_referenced_entity_ids(hass, call, True) @@ -534,7 +529,7 @@ async def entity_service_call( # If the service function is a string, we'll pass it the service call data if isinstance(func, str): - data: Union[Dict, ha.ServiceCall] = { + data: dict | ha.ServiceCall = { key: val for key, val in call.data.items() if key not in cv.ENTITY_SERVICE_FIELDS @@ -546,7 +541,7 @@ async def entity_service_call( # Check the permissions # A list with entities to call the service on. - entity_candidates: List["Entity"] = [] + entity_candidates: list["Entity"] = [] if entity_perms is None: for platform in platforms: @@ -662,8 +657,8 @@ async def entity_service_call( async def _handle_entity_call( hass: HomeAssistantType, entity: Entity, - func: Union[str, Callable[..., Any]], - data: Union[Dict, ha.ServiceCall], + func: str | Callable[..., Any], + data: dict | ha.ServiceCall, context: ha.Context, ) -> None: """Handle calling service method.""" @@ -693,7 +688,7 @@ def async_register_admin_service( hass: HomeAssistantType, domain: str, service: str, - service_func: Callable[[ha.ServiceCall], Optional[Awaitable]], + service_func: Callable[[ha.ServiceCall], Awaitable | None], schema: vol.Schema = vol.Schema({}, extra=vol.PREVENT_EXTRA), ) -> None: """Register a service that requires admin access.""" diff --git a/homeassistant/helpers/significant_change.py b/homeassistant/helpers/significant_change.py index a7be57693ba230..b34df0075a32e4 100644 --- a/homeassistant/helpers/significant_change.py +++ b/homeassistant/helpers/significant_change.py @@ -29,7 +29,7 @@ async def async_check_significant_change( from __future__ import annotations from types import MappingProxyType -from typing import Any, Callable, Dict, Optional, Tuple, Union +from typing import Any, Callable, Optional, Union from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State, callback @@ -66,7 +66,7 @@ async def async_check_significant_change( async def create_checker( hass: HomeAssistant, _domain: str, - extra_significant_check: Optional[ExtraCheckTypeFunc] = None, + extra_significant_check: ExtraCheckTypeFunc | None = None, ) -> SignificantlyChangedChecker: """Create a significantly changed checker for a domain.""" await _initialize(hass) @@ -90,15 +90,15 @@ async def process_platform( await async_process_integration_platforms(hass, PLATFORM, process_platform) -def either_one_none(val1: Optional[Any], val2: Optional[Any]) -> bool: +def either_one_none(val1: Any | None, val2: Any | None) -> bool: """Test if exactly one value is None.""" return (val1 is None and val2 is not None) or (val1 is not None and val2 is None) def check_numeric_changed( - val1: Optional[Union[int, float]], - val2: Optional[Union[int, float]], - change: Union[int, float], + val1: int | float | None, + val2: int | float | None, + change: int | float, ) -> bool: """Check if two numeric values have changed.""" if val1 is None and val2 is None: @@ -125,22 +125,22 @@ class SignificantlyChangedChecker: def __init__( self, hass: HomeAssistant, - extra_significant_check: Optional[ExtraCheckTypeFunc] = None, + extra_significant_check: ExtraCheckTypeFunc | None = None, ) -> None: """Test if an entity has significantly changed.""" self.hass = hass - self.last_approved_entities: Dict[str, Tuple[State, Any]] = {} + self.last_approved_entities: dict[str, tuple[State, Any]] = {} self.extra_significant_check = extra_significant_check @callback def async_is_significant_change( - self, new_state: State, *, extra_arg: Optional[Any] = None + self, new_state: State, *, extra_arg: Any | None = None ) -> bool: """Return if this was a significant change. Extra kwargs are passed to the extra significant checker. """ - old_data: Optional[Tuple[State, Any]] = self.last_approved_entities.get( + old_data: tuple[State, Any] | None = self.last_approved_entities.get( new_state.entity_id ) @@ -164,9 +164,7 @@ def async_is_significant_change( self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg) return True - functions: Optional[Dict[str, CheckTypeFunc]] = self.hass.data.get( - DATA_FUNCTIONS - ) + functions: dict[str, CheckTypeFunc] | None = self.hass.data.get(DATA_FUNCTIONS) if functions is None: raise RuntimeError("Significant Change not initialized") diff --git a/homeassistant/helpers/singleton.py b/homeassistant/helpers/singleton.py index ab4d12dc1cc3b5..a48ea5d64f0783 100644 --- a/homeassistant/helpers/singleton.py +++ b/homeassistant/helpers/singleton.py @@ -1,7 +1,9 @@ """Helper to help coordinating calls.""" +from __future__ import annotations + import asyncio import functools -from typing import Callable, Optional, TypeVar, cast +from typing import Callable, TypeVar, cast from homeassistant.core import HomeAssistant from homeassistant.loader import bind_hass @@ -24,7 +26,7 @@ def wrapper(func: FUNC) -> FUNC: @bind_hass @functools.wraps(func) def wrapped(hass: HomeAssistant) -> T: - obj: Optional[T] = hass.data.get(data_key) + obj: T | None = hass.data.get(data_key) if obj is None: obj = hass.data[data_key] = func(hass) return obj diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index eda026c8563875..32026f3d2d6cff 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -1,10 +1,12 @@ """Helpers that help with state related things.""" +from __future__ import annotations + import asyncio from collections import defaultdict import datetime as dt import logging from types import ModuleType, TracebackType -from typing import Any, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Iterable from homeassistant.components.sun import STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON from homeassistant.const import ( @@ -44,19 +46,19 @@ class AsyncTrackStates: def __init__(self, hass: HomeAssistantType) -> None: """Initialize a TrackStates block.""" self.hass = hass - self.states: List[State] = [] + self.states: list[State] = [] # pylint: disable=attribute-defined-outside-init - def __enter__(self) -> List[State]: + def __enter__(self) -> list[State]: """Record time from which to track changes.""" self.now = dt_util.utcnow() return self.states def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: """Add changes states to changes list.""" self.states.extend(get_changed_since(self.hass.states.async_all(), self.now)) @@ -64,7 +66,7 @@ def __exit__( def get_changed_since( states: Iterable[State], utc_point_in_time: dt.datetime -) -> List[State]: +) -> list[State]: """Return list of states that have been changed since utc_point_in_time. Deprecated. Remove after June 2021. @@ -76,21 +78,21 @@ def get_changed_since( @bind_hass async def async_reproduce_state( hass: HomeAssistantType, - states: Union[State, Iterable[State]], + states: State | Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a list of states on multiple domains.""" if isinstance(states, State): states = [states] - to_call: Dict[str, List[State]] = defaultdict(list) + to_call: dict[str, list[State]] = defaultdict(list) for state in states: to_call[state.domain].append(state) - async def worker(domain: str, states_by_domain: List[State]) -> None: + async def worker(domain: str, states_by_domain: list[State]) -> None: try: integration = await async_get_integration(hass, domain) except IntegrationNotFound: @@ -100,7 +102,7 @@ async def worker(domain: str, states_by_domain: List[State]) -> None: return try: - platform: Optional[ModuleType] = integration.get_platform("reproduce_state") + platform: ModuleType | None = integration.get_platform("reproduce_state") except ImportError: _LOGGER.warning("Integration %s does not support reproduce state", domain) return diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 2bc13fbdf44190..c1138b54f79bb5 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -1,9 +1,11 @@ """Helper to help store data.""" +from __future__ import annotations + import asyncio from json import JSONEncoder import logging import os -from typing import Any, Callable, Dict, List, Optional, Type, Union +from typing import Any, Callable from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE from homeassistant.core import CALLBACK_TYPE, CoreState, HomeAssistant, callback @@ -71,18 +73,18 @@ def __init__( key: str, private: bool = False, *, - encoder: Optional[Type[JSONEncoder]] = None, + encoder: type[JSONEncoder] | None = None, ): """Initialize storage class.""" self.version = version self.key = key self.hass = hass self._private = private - self._data: Optional[Dict[str, Any]] = None - self._unsub_delay_listener: Optional[CALLBACK_TYPE] = None - self._unsub_final_write_listener: Optional[CALLBACK_TYPE] = None + self._data: dict[str, Any] | None = None + self._unsub_delay_listener: CALLBACK_TYPE | None = None + self._unsub_final_write_listener: CALLBACK_TYPE | None = None self._write_lock = asyncio.Lock() - self._load_task: Optional[asyncio.Future] = None + self._load_task: asyncio.Future | None = None self._encoder = encoder @property @@ -90,7 +92,7 @@ def path(self): """Return the config path.""" return self.hass.config.path(STORAGE_DIR, self.key) - async def async_load(self) -> Union[Dict, List, None]: + async def async_load(self) -> dict | list | None: """Load data. If the expected version does not match the given version, the migrate @@ -140,7 +142,7 @@ async def _async_load_data(self): return stored - async def async_save(self, data: Union[Dict, List]) -> None: + async def async_save(self, data: dict | list) -> None: """Save data.""" self._data = {"version": self.version, "key": self.key, "data": data} @@ -151,7 +153,7 @@ async def async_save(self, data: Union[Dict, List]) -> None: await self._async_handle_write_data() @callback - def async_delay_save(self, data_func: Callable[[], Dict], delay: float = 0) -> None: + def async_delay_save(self, data_func: Callable[[], dict], delay: float = 0) -> None: """Save data with an optional delay.""" self._data = {"version": self.version, "key": self.key, "data_func": data_func} @@ -224,7 +226,7 @@ async def _async_handle_write_data(self, *_args): except (json_util.SerializationError, json_util.WriteError) as err: _LOGGER.error("Error writing config for %s: %s", self.key, err) - def _write_data(self, path: str, data: Dict) -> None: + def _write_data(self, path: str, data: dict) -> None: """Write the data.""" if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index ce0f0598318c06..ded1b3a7123787 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -2,7 +2,7 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.core import callback @@ -44,8 +44,8 @@ def get_astral_location(hass: HomeAssistantType) -> astral.Location: def get_astral_event_next( hass: HomeAssistantType, event: str, - utc_point_in_time: Optional[datetime.datetime] = None, - offset: Optional[datetime.timedelta] = None, + utc_point_in_time: datetime.datetime | None = None, + offset: datetime.timedelta | None = None, ) -> datetime.datetime: """Calculate the next specified solar event.""" location = get_astral_location(hass) @@ -56,8 +56,8 @@ def get_astral_event_next( def get_location_astral_event_next( location: "astral.Location", event: str, - utc_point_in_time: Optional[datetime.datetime] = None, - offset: Optional[datetime.timedelta] = None, + utc_point_in_time: datetime.datetime | None = None, + offset: datetime.timedelta | None = None, ) -> datetime.datetime: """Calculate the next specified solar event.""" from astral import AstralError # pylint: disable=import-outside-toplevel @@ -91,8 +91,8 @@ def get_location_astral_event_next( def get_astral_event_date( hass: HomeAssistantType, event: str, - date: Union[datetime.date, datetime.datetime, None] = None, -) -> Optional[datetime.datetime]: + date: datetime.date | datetime.datetime | None = None, +) -> datetime.datetime | None: """Calculate the astral event time for the specified date.""" from astral import AstralError # pylint: disable=import-outside-toplevel @@ -114,7 +114,7 @@ def get_astral_event_date( @callback @bind_hass def is_up( - hass: HomeAssistantType, utc_point_in_time: Optional[datetime.datetime] = None + hass: HomeAssistantType, utc_point_in_time: datetime.datetime | None = None ) -> bool: """Calculate if the sun is currently up.""" if utc_point_in_time is None: diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index 9c2c4b181adeec..b9894ca18fa8a4 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -1,7 +1,9 @@ """Helper to gather system info.""" +from __future__ import annotations + import os import platform -from typing import Any, Dict +from typing import Any from homeassistant.const import __version__ as current_version from homeassistant.loader import bind_hass @@ -11,7 +13,7 @@ @bind_hass -async def async_get_system_info(hass: HomeAssistantType) -> Dict[str, Any]: +async def async_get_system_info(hass: HomeAssistantType) -> dict[str, Any]: """Return info about the system.""" info_object = { "installation_type": "Unknown", diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py index 18ca8355159199..e0f089e93b9d8d 100644 --- a/homeassistant/helpers/temperature.py +++ b/homeassistant/helpers/temperature.py @@ -1,6 +1,7 @@ """Temperature helpers for Home Assistant.""" +from __future__ import annotations + from numbers import Number -from typing import Optional from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS from homeassistant.core import HomeAssistant @@ -8,8 +9,8 @@ def display_temp( - hass: HomeAssistant, temperature: Optional[float], unit: str, precision: float -) -> Optional[float]: + hass: HomeAssistant, temperature: float | None, unit: str, precision: float +) -> float | None: """Convert temperature into preferred units/precision for display.""" temperature_unit = unit ha_unit = hass.config.units.temperature_unit diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 7f768354035f72..e97526458042c5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -13,7 +13,7 @@ from operator import attrgetter import random import re -from typing import Any, Dict, Generator, Iterable, Optional, Type, Union, cast +from typing import Any, Generator, Iterable, cast from urllib.parse import urlencode as urllib_urlencode import weakref @@ -125,7 +125,7 @@ def is_template_string(maybe_template: str) -> bool: class ResultWrapper: """Result wrapper class to store render result.""" - render_result: Optional[str] + render_result: str | None def gen_result_wrapper(kls): @@ -134,7 +134,7 @@ def gen_result_wrapper(kls): class Wrapper(kls, ResultWrapper): """Wrapper of a kls that can store render_result.""" - def __init__(self, *args: tuple, render_result: Optional[str] = None) -> None: + def __init__(self, *args: tuple, render_result: str | None = None) -> None: super().__init__(*args) self.render_result = render_result @@ -156,15 +156,13 @@ class TupleWrapper(tuple, ResultWrapper): # This is all magic to be allowed to subclass a tuple. - def __new__( - cls, value: tuple, *, render_result: Optional[str] = None - ) -> TupleWrapper: + def __new__(cls, value: tuple, *, render_result: str | None = None) -> TupleWrapper: """Create a new tuple class.""" return super().__new__(cls, tuple(value)) # pylint: disable=super-init-not-called - def __init__(self, value: tuple, *, render_result: Optional[str] = None): + def __init__(self, value: tuple, *, render_result: str | None = None): """Initialize a new tuple class.""" self.render_result = render_result @@ -176,7 +174,7 @@ def __str__(self) -> str: return self.render_result -RESULT_WRAPPERS: Dict[Type, Type] = { +RESULT_WRAPPERS: dict[type, type] = { kls: gen_result_wrapper(kls) # type: ignore[no-untyped-call] for kls in (list, dict, set) } @@ -200,15 +198,15 @@ def __init__(self, template): # Will be set sensibly once frozen. self.filter_lifecycle = _true self.filter = _true - self._result: Optional[str] = None + self._result: str | None = None self.is_static = False - self.exception: Optional[TemplateError] = None + self.exception: TemplateError | None = None self.all_states = False self.all_states_lifecycle = False self.domains = set() self.domains_lifecycle = set() self.entities = set() - self.rate_limit: Optional[timedelta] = None + self.rate_limit: timedelta | None = None self.has_time = False def __repr__(self) -> str: @@ -294,7 +292,7 @@ def __init__(self, template, hass=None): self.template: str = template.strip() self._compiled_code = None - self._compiled: Optional[Template] = None + self._compiled: Template | None = None self.hass = hass self.is_static = not is_template_string(template) self._limited = None @@ -304,7 +302,7 @@ def _env(self) -> TemplateEnvironment: if self.hass is None: return _NO_HASS_ENV wanted_env = _ENVIRONMENT_LIMITED if self._limited else _ENVIRONMENT - ret: Optional[TemplateEnvironment] = self.hass.data.get(wanted_env) + ret: TemplateEnvironment | None = self.hass.data.get(wanted_env) if ret is None: ret = self.hass.data[wanted_env] = TemplateEnvironment(self.hass, self._limited) # type: ignore[no-untyped-call] return ret @@ -776,7 +774,7 @@ def _collect_state(hass: HomeAssistantType, entity_id: str) -> None: entity_collect.entities.add(entity_id) -def _state_generator(hass: HomeAssistantType, domain: Optional[str]) -> Generator: +def _state_generator(hass: HomeAssistantType, domain: str | None) -> Generator: """State generator for a domain or all states.""" for state in sorted(hass.states.async_all(domain), key=attrgetter("entity_id")): yield TemplateState(hass, state, collect=False) @@ -784,20 +782,20 @@ def _state_generator(hass: HomeAssistantType, domain: Optional[str]) -> Generato def _get_state_if_valid( hass: HomeAssistantType, entity_id: str -) -> Optional[TemplateState]: +) -> 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 return _get_template_state_from_state(hass, entity_id, state) -def _get_state(hass: HomeAssistantType, entity_id: str) -> Optional[TemplateState]: +def _get_state(hass: HomeAssistantType, entity_id: str) -> TemplateState | None: return _get_template_state_from_state(hass, entity_id, hass.states.get(entity_id)) def _get_template_state_from_state( - hass: HomeAssistantType, entity_id: str, state: Optional[State] -) -> Optional[TemplateState]: + hass: HomeAssistantType, entity_id: str, state: State | None +) -> TemplateState | None: if state is None: # Only need to collect if none, if not none collect first actual # access to the state properties in the state wrapper. @@ -808,7 +806,7 @@ def _get_template_state_from_state( def _resolve_state( hass: HomeAssistantType, entity_id_or_state: Any -) -> Union[State, TemplateState, None]: +) -> State | TemplateState | None: """Return state or entity_id if given.""" if isinstance(entity_id_or_state, State): return entity_id_or_state @@ -817,7 +815,7 @@ def _resolve_state( return None -def result_as_boolean(template_result: Optional[str]) -> bool: +def result_as_boolean(template_result: str | None) -> bool: """Convert the template result to a boolean. True/not 0/'1'/'true'/'yes'/'on'/'enable' are considered truthy diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index a7cd63de479de7..2e5f1dd8c5407a 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -1,8 +1,10 @@ """Helpers for script and condition tracing.""" +from __future__ import annotations + from collections import deque from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Deque, Dict, Generator, List, Optional, Tuple, Union, cast +from typing import Any, Deque, Generator, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util @@ -13,9 +15,9 @@ class TraceElement: def __init__(self, variables: TemplateVarsType, path: str): """Container for trace data.""" - self._error: Optional[Exception] = None + self._error: Exception | None = None self.path: str = path - self._result: Optional[dict] = None + self._result: dict | None = None self._timestamp = dt_util.utcnow() if variables is None: @@ -41,9 +43,9 @@ def set_result(self, **kwargs: Any) -> None: """Set result.""" self._result = {**kwargs} - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this TraceElement.""" - result: Dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} + result: dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} if self._variables: result["changed_variables"] = self._variables if self._error is not None: @@ -55,31 +57,31 @@ def as_dict(self) -> Dict[str, Any]: # Context variables for tracing # Current trace -trace_cv: ContextVar[Optional[Dict[str, Deque[TraceElement]]]] = ContextVar( +trace_cv: ContextVar[dict[str, Deque[TraceElement]] | None] = ContextVar( "trace_cv", default=None ) # Stack of TraceElements -trace_stack_cv: ContextVar[Optional[List[TraceElement]]] = ContextVar( +trace_stack_cv: ContextVar[list[TraceElement] | None] = ContextVar( "trace_stack_cv", default=None ) # Current location in config tree -trace_path_stack_cv: ContextVar[Optional[List[str]]] = ContextVar( +trace_path_stack_cv: ContextVar[list[str] | None] = ContextVar( "trace_path_stack_cv", default=None ) # Copy of last variables -variables_cv: ContextVar[Optional[Any]] = ContextVar("variables_cv", default=None) +variables_cv: ContextVar[Any | None] = ContextVar("variables_cv", default=None) # Automation ID + Run ID -trace_id_cv: ContextVar[Optional[Tuple[str, str]]] = ContextVar( +trace_id_cv: ContextVar[tuple[str, str] | None] = ContextVar( "trace_id_cv", default=None ) -def trace_id_set(trace_id: Tuple[str, str]) -> None: +def trace_id_set(trace_id: tuple[str, str]) -> None: """Set id of the current trace.""" trace_id_cv.set(trace_id) -def trace_id_get() -> Optional[Tuple[str, str]]: +def trace_id_get() -> tuple[str, str] | None: """Get id if the current trace.""" return trace_id_cv.get() @@ -99,13 +101,13 @@ def trace_stack_pop(trace_stack_var: ContextVar) -> None: trace_stack.pop() -def trace_stack_top(trace_stack_var: ContextVar) -> Optional[Any]: +def trace_stack_top(trace_stack_var: ContextVar) -> Any | None: """Return the element at the top of a trace stack.""" trace_stack = trace_stack_var.get() return trace_stack[-1] if trace_stack else None -def trace_path_push(suffix: Union[str, List[str]]) -> int: +def trace_path_push(suffix: str | list[str]) -> int: """Go deeper in the config tree.""" if isinstance(suffix, str): suffix = [suffix] @@ -130,7 +132,7 @@ def trace_path_get() -> str: def trace_append_element( trace_element: TraceElement, - maxlen: Optional[int] = None, + maxlen: int | None = None, ) -> None: """Append a TraceElement to trace[path].""" path = trace_element.path @@ -143,7 +145,7 @@ def trace_append_element( trace[path].append(trace_element) -def trace_get(clear: bool = True) -> Optional[Dict[str, Deque[TraceElement]]]: +def trace_get(clear: bool = True) -> dict[str, Deque[TraceElement]] | None: """Return the current trace.""" if clear: trace_clear() @@ -165,7 +167,7 @@ def trace_set_result(**kwargs: Any) -> None: @contextmanager -def trace_path(suffix: Union[str, List[str]]) -> Generator: +def trace_path(suffix: str | list[str]) -> Generator: """Go deeper in the config tree.""" count = trace_path_push(suffix) try: diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index bd229d79111911..da7613d86a8d97 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -1,8 +1,10 @@ """Translation string lookup helpers.""" +from __future__ import annotations + import asyncio from collections import ChainMap import logging -from typing import Any, Dict, List, Optional, Set +from typing import Any from homeassistant.core import callback from homeassistant.loader import ( @@ -24,7 +26,7 @@ LOCALE_EN = "en" -def recursive_flatten(prefix: Any, data: Dict) -> Dict[str, Any]: +def recursive_flatten(prefix: Any, data: dict) -> dict[str, Any]: """Return a flattened representation of dict data.""" output = {} for key, value in data.items(): @@ -38,7 +40,7 @@ def recursive_flatten(prefix: Any, data: Dict) -> Dict[str, Any]: @callback def component_translation_path( component: str, language: str, integration: Integration -) -> Optional[str]: +) -> str | None: """Return the translation json file location for a component. For component: @@ -69,8 +71,8 @@ def component_translation_path( def load_translations_files( - translation_files: Dict[str, str] -) -> Dict[str, Dict[str, Any]]: + translation_files: dict[str, str] +) -> dict[str, dict[str, Any]]: """Load and parse translation.json files.""" loaded = {} for component, translation_file in translation_files.items(): @@ -90,13 +92,13 @@ def load_translations_files( def _merge_resources( - translation_strings: Dict[str, Dict[str, Any]], - components: Set[str], + translation_strings: dict[str, dict[str, Any]], + components: set[str], category: str, -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: """Build and merge the resources response for the given components and platforms.""" # Build response - resources: Dict[str, Dict[str, Any]] = {} + resources: dict[str, dict[str, Any]] = {} for component in components: if "." not in component: domain = component @@ -131,10 +133,10 @@ def _merge_resources( def _build_resources( - translation_strings: Dict[str, Dict[str, Any]], - components: Set[str], + translation_strings: dict[str, dict[str, Any]], + components: set[str], category: str, -) -> Dict[str, Dict[str, Any]]: +) -> dict[str, dict[str, Any]]: """Build the resources response for the given components.""" # Build response return { @@ -146,8 +148,8 @@ def _build_resources( async def async_get_component_strings( - hass: HomeAssistantType, language: str, components: Set[str] -) -> Dict[str, Any]: + hass: HomeAssistantType, language: str, components: set[str] +) -> dict[str, Any]: """Load translations.""" domains = list({loaded.split(".")[-1] for loaded in components}) integrations = dict( @@ -160,7 +162,7 @@ async def async_get_component_strings( ) ) - translations: Dict[str, Any] = {} + translations: dict[str, Any] = {} # Determine paths of missing components/platforms files_to_load = {} @@ -205,15 +207,15 @@ class _TranslationCache: def __init__(self, hass: HomeAssistantType) -> None: """Initialize the cache.""" self.hass = hass - self.loaded: Dict[str, Set[str]] = {} - self.cache: Dict[str, Dict[str, Dict[str, Any]]] = {} + self.loaded: dict[str, set[str]] = {} + self.cache: dict[str, dict[str, dict[str, Any]]] = {} async def async_fetch( self, language: str, category: str, - components: Set, - ) -> List[Dict[str, Dict[str, Any]]]: + components: set, + ) -> list[dict[str, dict[str, Any]]]: """Load resources into the cache.""" components_to_load = components - self.loaded.setdefault(language, set()) @@ -224,7 +226,7 @@ async def async_fetch( return [cached.get(component, {}).get(category, {}) for component in components] - async def _async_load(self, language: str, components: Set) -> None: + async def _async_load(self, language: str, components: set) -> None: """Populate the cache for a given set of components.""" _LOGGER.debug( "Cache miss for %s: %s", @@ -247,12 +249,12 @@ async def _async_load(self, language: str, components: Set) -> None: def _build_category_cache( self, language: str, - components: Set, - translation_strings: Dict[str, Dict[str, Any]], + components: set, + translation_strings: dict[str, dict[str, Any]], ) -> None: """Extract resources into the cache.""" cached = self.cache.setdefault(language, {}) - categories: Set[str] = set() + categories: set[str] = set() for resource in translation_strings.values(): categories.update(resource) @@ -263,7 +265,7 @@ def _build_category_cache( new_resources = resource_func(translation_strings, components, category) for component, resource in new_resources.items(): - category_cache: Dict[str, Any] = cached.setdefault( + category_cache: dict[str, Any] = cached.setdefault( component, {} ).setdefault(category, {}) @@ -283,9 +285,9 @@ async def async_get_translations( hass: HomeAssistantType, language: str, category: str, - integration: Optional[str] = None, - config_flow: Optional[bool] = None, -) -> Dict[str, Any]: + integration: str | None = None, + config_flow: bool | None = None, +) -> dict[str, Any]: """Return all backend translations. If integration specified, load it for that one. diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 58ac71a515e1cb..ef6bf6ca5df5b5 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -1,8 +1,10 @@ """Triggers.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable import voluptuous as vol @@ -39,8 +41,8 @@ async def _async_get_trigger_platform( async def async_validate_trigger_config( - hass: HomeAssistantType, trigger_config: List[ConfigType] -) -> List[ConfigType]: + hass: HomeAssistantType, trigger_config: list[ConfigType] +) -> list[ConfigType]: """Validate triggers.""" config = [] for conf in trigger_config: @@ -55,14 +57,14 @@ async def async_validate_trigger_config( async def async_initialize_triggers( hass: HomeAssistantType, - trigger_config: List[ConfigType], + trigger_config: list[ConfigType], action: Callable, domain: str, name: str, log_cb: Callable, home_assistant_start: bool = False, - variables: Optional[Union[Dict[str, Any], MappingProxyType]] = None, -) -> Optional[CALLBACK_TYPE]: + variables: dict[str, Any] | MappingProxyType | None = None, +) -> CALLBACK_TYPE | None: """Initialize triggers.""" info = { "domain": domain, diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 8ba355e648974a..5b892ea79c477c 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -1,9 +1,11 @@ """Helpers to help coordinate updates.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging from time import monotonic -from typing import Any, Awaitable, Callable, Generic, List, Optional, TypeVar +from typing import Any, Awaitable, Callable, Generic, TypeVar import urllib.error import aiohttp @@ -37,9 +39,9 @@ def __init__( logger: logging.Logger, *, name: str, - update_interval: Optional[timedelta] = None, - update_method: Optional[Callable[[], Awaitable[T]]] = None, - request_refresh_debouncer: Optional[Debouncer] = None, + update_interval: timedelta | None = None, + update_method: Callable[[], Awaitable[T]] | None = None, + request_refresh_debouncer: Debouncer | None = None, ): """Initialize global data updater.""" self.hass = hass @@ -48,12 +50,12 @@ def __init__( self.update_method = update_method self.update_interval = update_interval - self.data: Optional[T] = None + self.data: T | None = None - self._listeners: List[CALLBACK_TYPE] = [] + self._listeners: list[CALLBACK_TYPE] = [] self._job = HassJob(self._handle_refresh_interval) - self._unsub_refresh: Optional[CALLBACK_TYPE] = None - self._request_refresh_task: Optional[asyncio.TimerHandle] = None + self._unsub_refresh: CALLBACK_TYPE | None = None + self._request_refresh_task: asyncio.TimerHandle | None = None self.last_update_success = True if request_refresh_debouncer is None: @@ -132,7 +134,7 @@ async def async_request_refresh(self) -> None: """ await self._debounced_refresh.async_call() - async def _async_update_data(self) -> Optional[T]: + async def _async_update_data(self) -> T | None: """Fetch the latest data from the source.""" if self.update_method is None: raise NotImplementedError("Update method not implemented") From fabd73f08bdc6be81bdb30b39efa3ea7e91a4768 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 21:46:07 +0100 Subject: [PATCH 1328/1818] Update typing 03 (#48015) --- homeassistant/auth/__init__.py | 78 +++++++++---------- homeassistant/auth/auth_store.py | 68 ++++++++-------- homeassistant/auth/mfa_modules/__init__.py | 14 ++-- .../auth/mfa_modules/insecure_example.py | 8 +- homeassistant/auth/mfa_modules/notify.py | 42 +++++----- homeassistant/auth/mfa_modules/totp.py | 22 +++--- homeassistant/auth/models.py | 34 ++++---- homeassistant/auth/permissions/__init__.py | 6 +- homeassistant/auth/permissions/entities.py | 12 +-- homeassistant/auth/permissions/merge.py | 14 ++-- homeassistant/auth/permissions/util.py | 10 ++- homeassistant/auth/providers/__init__.py | 42 +++++----- homeassistant/auth/providers/command_line.py | 17 ++-- homeassistant/auth/providers/homeassistant.py | 22 +++--- .../auth/providers/insecure_example.py | 14 ++-- .../auth/providers/legacy_api_password.py | 12 +-- .../auth/providers/trusted_networks.py | 20 ++--- homeassistant/scripts/__init__.py | 8 +- homeassistant/scripts/benchmark/__init__.py | 6 +- homeassistant/scripts/check_config.py | 16 ++-- homeassistant/util/__init__.py | 23 ++---- homeassistant/util/aiohttp.py | 10 ++- homeassistant/util/color.py | 57 +++++++------- homeassistant/util/distance.py | 8 +- homeassistant/util/dt.py | 34 ++++---- homeassistant/util/json.py | 16 ++-- homeassistant/util/location.py | 18 +++-- homeassistant/util/logging.py | 8 +- homeassistant/util/network.py | 11 +-- homeassistant/util/package.py | 11 +-- homeassistant/util/percentage.py | 15 ++-- homeassistant/util/pil.py | 6 +- homeassistant/util/ruamel_yaml.py | 8 +- homeassistant/util/timeout.py | 58 +++++++------- homeassistant/util/unit_system.py | 11 +-- homeassistant/util/yaml/input.py | 11 +-- homeassistant/util/yaml/loader.py | 26 +++---- 37 files changed, 417 insertions(+), 379 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 7d6f94dda85c0f..3830419c537545 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -4,7 +4,7 @@ import asyncio from collections import OrderedDict from datetime import timedelta -from typing import Any, Dict, List, Optional, Tuple, cast +from typing import Any, Dict, Optional, Tuple, cast import jwt @@ -36,8 +36,8 @@ class InvalidProvider(Exception): async def auth_manager_from_config( hass: HomeAssistant, - provider_configs: List[Dict[str, Any]], - module_configs: List[Dict[str, Any]], + provider_configs: list[dict[str, Any]], + module_configs: list[dict[str, Any]], ) -> AuthManager: """Initialize an auth manager from config. @@ -87,8 +87,8 @@ async def async_create_flow( self, handler_key: Any, *, - context: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> data_entry_flow.FlowHandler: """Create a login flow.""" auth_provider = self.auth_manager.get_auth_provider(*handler_key) @@ -97,8 +97,8 @@ async def async_create_flow( return await auth_provider.async_login_flow(context) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: Dict[str, Any] - ) -> Dict[str, Any]: + self, flow: data_entry_flow.FlowHandler, result: dict[str, Any] + ) -> dict[str, Any]: """Return a user as result of login flow.""" flow = cast(LoginFlow, flow) @@ -157,22 +157,22 @@ def __init__( self.login_flow = AuthManagerFlowManager(hass, self) @property - def auth_providers(self) -> List[AuthProvider]: + def auth_providers(self) -> list[AuthProvider]: """Return a list of available auth providers.""" return list(self._providers.values()) @property - def auth_mfa_modules(self) -> List[MultiFactorAuthModule]: + def auth_mfa_modules(self) -> list[MultiFactorAuthModule]: """Return a list of available auth modules.""" return list(self._mfa_modules.values()) def get_auth_provider( - self, provider_type: str, provider_id: Optional[str] - ) -> Optional[AuthProvider]: + self, provider_type: str, provider_id: str | None + ) -> AuthProvider | None: """Return an auth provider, None if not found.""" return self._providers.get((provider_type, provider_id)) - def get_auth_providers(self, provider_type: str) -> List[AuthProvider]: + def get_auth_providers(self, provider_type: str) -> list[AuthProvider]: """Return a List of auth provider of one type, Empty if not found.""" return [ provider @@ -180,30 +180,30 @@ def get_auth_providers(self, provider_type: str) -> List[AuthProvider]: if p_type == provider_type ] - def get_auth_mfa_module(self, module_id: str) -> Optional[MultiFactorAuthModule]: + def get_auth_mfa_module(self, module_id: str) -> MultiFactorAuthModule | None: """Return a multi-factor auth module, None if not found.""" return self._mfa_modules.get(module_id) - async def async_get_users(self) -> List[models.User]: + async def async_get_users(self) -> list[models.User]: """Retrieve all users.""" return await self._store.async_get_users() - async def async_get_user(self, user_id: str) -> Optional[models.User]: + async def async_get_user(self, user_id: str) -> models.User | None: """Retrieve a user.""" return await self._store.async_get_user(user_id) - async def async_get_owner(self) -> Optional[models.User]: + async def async_get_owner(self) -> models.User | None: """Retrieve the owner.""" users = await self.async_get_users() return next((user for user in users if user.is_owner), None) - async def async_get_group(self, group_id: str) -> Optional[models.Group]: + async def async_get_group(self, group_id: str) -> models.Group | None: """Retrieve all groups.""" return await self._store.async_get_group(group_id) async def async_get_user_by_credentials( self, credentials: models.Credentials - ) -> Optional[models.User]: + ) -> models.User | None: """Get a user by credential, return None if not found.""" for user in await self.async_get_users(): for creds in user.credentials: @@ -213,7 +213,7 @@ async def async_get_user_by_credentials( return None async def async_create_system_user( - self, name: str, group_ids: Optional[List[str]] = None + self, name: str, group_ids: list[str] | None = None ) -> models.User: """Create a system user.""" user = await self._store.async_create_user( @@ -225,10 +225,10 @@ async def async_create_system_user( return user async def async_create_user( - self, name: str, group_ids: Optional[List[str]] = None + self, name: str, group_ids: list[str] | None = None ) -> models.User: """Create a user.""" - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "name": name, "is_active": True, "group_ids": group_ids or [], @@ -294,12 +294,12 @@ async def async_remove_user(self, user: models.User) -> None: async def async_update_user( self, user: models.User, - name: Optional[str] = None, - is_active: Optional[bool] = None, - group_ids: Optional[List[str]] = None, + name: str | None = None, + is_active: bool | None = None, + group_ids: list[str] | None = None, ) -> None: """Update a user.""" - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} if name is not None: kwargs["name"] = name if group_ids is not None: @@ -362,9 +362,9 @@ async def async_disable_user_mfa( await module.async_depose_user(user.id) - async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]: + async def async_get_enabled_mfa(self, user: models.User) -> dict[str, str]: """List enabled mfa modules for user.""" - modules: Dict[str, str] = OrderedDict() + modules: dict[str, str] = OrderedDict() for module_id, module in self._mfa_modules.items(): if await module.async_is_user_setup(user.id): modules[module_id] = module.name @@ -373,12 +373,12 @@ async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]: async def async_create_refresh_token( self, user: models.User, - client_id: Optional[str] = None, - client_name: Optional[str] = None, - client_icon: Optional[str] = None, - token_type: Optional[str] = None, + client_id: str | None = None, + client_name: str | None = None, + client_icon: str | None = None, + token_type: str | None = None, access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, - credential: Optional[models.Credentials] = None, + credential: models.Credentials | None = None, ) -> models.RefreshToken: """Create a new refresh token for a user.""" if not user.is_active: @@ -432,13 +432,13 @@ async def async_create_refresh_token( async def async_get_refresh_token( self, token_id: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by id.""" return await self._store.async_get_refresh_token(token_id) async def async_get_refresh_token_by_token( self, token: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by token.""" return await self._store.async_get_refresh_token_by_token(token) @@ -450,7 +450,7 @@ async def async_remove_refresh_token( @callback def async_create_access_token( - self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: models.RefreshToken, remote_ip: str | None = None ) -> str: """Create a new access token.""" self.async_validate_refresh_token(refresh_token, remote_ip) @@ -471,7 +471,7 @@ def async_create_access_token( @callback def _async_resolve_provider( self, refresh_token: models.RefreshToken - ) -> Optional[AuthProvider]: + ) -> AuthProvider | None: """Get the auth provider for the given refresh token. Raises an exception if the expected provider is no longer available or return @@ -492,7 +492,7 @@ def _async_resolve_provider( @callback def async_validate_refresh_token( - self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: models.RefreshToken, remote_ip: str | None = None ) -> None: """Validate that a refresh token is usable. @@ -504,7 +504,7 @@ def async_validate_refresh_token( async def async_validate_access_token( self, token: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Return refresh token if an access token is valid.""" try: unverif_claims = jwt.decode(token, verify=False) @@ -535,7 +535,7 @@ async def async_validate_access_token( @callback def _async_get_auth_provider( self, credentials: models.Credentials - ) -> Optional[AuthProvider]: + ) -> AuthProvider | None: """Get auth provider from a set of credentials.""" auth_provider_key = ( credentials.auth_provider_type, diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index 724f1c86722572..0b360668ad4504 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -1,10 +1,12 @@ """Storage for auth models.""" +from __future__ import annotations + import asyncio from collections import OrderedDict from datetime import timedelta import hmac from logging import getLogger -from typing import Any, Dict, List, Optional +from typing import Any from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.core import HomeAssistant, callback @@ -34,15 +36,15 @@ class AuthStore: def __init__(self, hass: HomeAssistant) -> None: """Initialize the auth store.""" self.hass = hass - self._users: Optional[Dict[str, models.User]] = None - self._groups: Optional[Dict[str, models.Group]] = None - self._perm_lookup: Optional[PermissionLookup] = None + self._users: dict[str, models.User] | None = None + self._groups: dict[str, models.Group] | None = None + self._perm_lookup: PermissionLookup | None = None self._store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) self._lock = asyncio.Lock() - async def async_get_groups(self) -> List[models.Group]: + async def async_get_groups(self) -> list[models.Group]: """Retrieve all users.""" if self._groups is None: await self._async_load() @@ -50,7 +52,7 @@ async def async_get_groups(self) -> List[models.Group]: return list(self._groups.values()) - async def async_get_group(self, group_id: str) -> Optional[models.Group]: + async def async_get_group(self, group_id: str) -> models.Group | None: """Retrieve all users.""" if self._groups is None: await self._async_load() @@ -58,7 +60,7 @@ async def async_get_group(self, group_id: str) -> Optional[models.Group]: return self._groups.get(group_id) - async def async_get_users(self) -> List[models.User]: + async def async_get_users(self) -> list[models.User]: """Retrieve all users.""" if self._users is None: await self._async_load() @@ -66,7 +68,7 @@ async def async_get_users(self) -> List[models.User]: return list(self._users.values()) - async def async_get_user(self, user_id: str) -> Optional[models.User]: + async def async_get_user(self, user_id: str) -> models.User | None: """Retrieve a user by id.""" if self._users is None: await self._async_load() @@ -76,12 +78,12 @@ async def async_get_user(self, user_id: str) -> Optional[models.User]: async def async_create_user( self, - name: Optional[str], - is_owner: Optional[bool] = None, - is_active: Optional[bool] = None, - system_generated: Optional[bool] = None, - credentials: Optional[models.Credentials] = None, - group_ids: Optional[List[str]] = None, + name: str | None, + is_owner: bool | None = None, + is_active: bool | None = None, + system_generated: bool | None = None, + credentials: models.Credentials | None = None, + group_ids: list[str] | None = None, ) -> models.User: """Create a new user.""" if self._users is None: @@ -97,7 +99,7 @@ async def async_create_user( raise ValueError(f"Invalid group specified {group_id}") groups.append(group) - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "name": name, # Until we get group management, we just put everyone in the # same group. @@ -146,9 +148,9 @@ async def async_remove_user(self, user: models.User) -> None: async def async_update_user( self, user: models.User, - name: Optional[str] = None, - is_active: Optional[bool] = None, - group_ids: Optional[List[str]] = None, + name: str | None = None, + is_active: bool | None = None, + group_ids: list[str] | None = None, ) -> None: """Update a user.""" assert self._groups is not None @@ -203,15 +205,15 @@ async def async_remove_credentials(self, credentials: models.Credentials) -> Non async def async_create_refresh_token( self, user: models.User, - client_id: Optional[str] = None, - client_name: Optional[str] = None, - client_icon: Optional[str] = None, + client_id: str | None = None, + client_name: str | None = None, + client_icon: str | None = None, token_type: str = models.TOKEN_TYPE_NORMAL, access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, - credential: Optional[models.Credentials] = None, + credential: models.Credentials | None = None, ) -> models.RefreshToken: """Create a new token for a user.""" - kwargs: Dict[str, Any] = { + kwargs: dict[str, Any] = { "user": user, "client_id": client_id, "token_type": token_type, @@ -244,7 +246,7 @@ async def async_remove_refresh_token( async def async_get_refresh_token( self, token_id: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by id.""" if self._users is None: await self._async_load() @@ -259,7 +261,7 @@ async def async_get_refresh_token( async def async_get_refresh_token_by_token( self, token: str - ) -> Optional[models.RefreshToken]: + ) -> models.RefreshToken | None: """Get refresh token by token.""" if self._users is None: await self._async_load() @@ -276,7 +278,7 @@ async def async_get_refresh_token_by_token( @callback def async_log_refresh_token_usage( - self, refresh_token: models.RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: models.RefreshToken, remote_ip: str | None = None ) -> None: """Update refresh token last used information.""" refresh_token.last_used_at = dt_util.utcnow() @@ -309,9 +311,9 @@ async def _async_load_task(self) -> None: self._set_defaults() return - users: Dict[str, models.User] = OrderedDict() - groups: Dict[str, models.Group] = OrderedDict() - credentials: Dict[str, models.Credentials] = OrderedDict() + users: dict[str, models.User] = OrderedDict() + groups: dict[str, models.Group] = OrderedDict() + credentials: dict[str, models.Credentials] = OrderedDict() # Soft-migrating data as we load. We are going to make sure we have a # read only group and an admin group. There are two states that we can @@ -328,7 +330,7 @@ async def _async_load_task(self) -> None: # was added. for group_dict in data.get("groups", []): - policy: Optional[PolicyType] = None + policy: PolicyType | None = None if group_dict["id"] == GROUP_ID_ADMIN: has_admin_group = True @@ -489,7 +491,7 @@ def _async_schedule_save(self) -> None: self._store.async_delay_save(self._data_to_save, 1) @callback - def _data_to_save(self) -> Dict: + def _data_to_save(self) -> dict: """Return the data to store.""" assert self._users is not None assert self._groups is not None @@ -508,7 +510,7 @@ def _data_to_save(self) -> Dict: groups = [] for group in self._groups.values(): - g_dict: Dict[str, Any] = { + g_dict: dict[str, Any] = { "id": group.id, # Name not read for sys groups. Kept here for backwards compat "name": group.name, @@ -567,7 +569,7 @@ def _set_defaults(self) -> None: """Set default values for auth store.""" self._users = OrderedDict() - groups: Dict[str, models.Group] = OrderedDict() + groups: dict[str, models.Group] = OrderedDict() admin_group = _system_admin_group() groups[admin_group.id] = admin_group user_group = _system_user_group() diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index f29f5f8fcc2063..d6989b6416fc9a 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -4,7 +4,7 @@ import importlib import logging import types -from typing import Any, Dict, Optional +from typing import Any import voluptuous as vol from voluptuous.humanize import humanize_error @@ -38,7 +38,7 @@ class MultiFactorAuthModule: DEFAULT_TITLE = "Unnamed auth module" MAX_RETRY_TIME = 3 - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize an auth module.""" self.hass = hass self.config = config @@ -87,7 +87,7 @@ async def async_is_user_setup(self, user_id: str) -> bool: """Return whether user is setup.""" raise NotImplementedError - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" raise NotImplementedError @@ -104,14 +104,14 @@ def __init__( self._user_id = user_id async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input is None. Return self.async_create_entry(data={'result': result}) if finish. """ - errors: Dict[str, str] = {} + errors: dict[str, str] = {} if user_input: result = await self._auth_module.async_setup_user(self._user_id, user_input) @@ -125,7 +125,7 @@ async def async_step_init( async def auth_mfa_module_from_config( - hass: HomeAssistant, config: Dict[str, Any] + hass: HomeAssistant, config: dict[str, Any] ) -> MultiFactorAuthModule: """Initialize an auth module from a config.""" module_name = config[CONF_TYPE] diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index ddceeaae826740..c25f70ca31b4be 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -1,5 +1,7 @@ """Example auth module.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -28,7 +30,7 @@ class InsecureExampleModule(MultiFactorAuthModule): DEFAULT_TITLE = "Insecure Personal Identify Number" - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) self._data = config["data"] @@ -80,7 +82,7 @@ async def async_is_user_setup(self, user_id: str) -> bool: return True return False - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" for data in self._data: if data["user_id"] == user_id: diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index c4e5800821e5be..76a5676d562c87 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -2,10 +2,12 @@ Sending HOTP through notify service """ +from __future__ import annotations + import asyncio from collections import OrderedDict import logging -from typing import Any, Dict, List, Optional +from typing import Any, Dict import attr import voluptuous as vol @@ -79,8 +81,8 @@ class NotifySetting: secret: str = attr.ib(factory=_generate_secret) # not persistent counter: int = attr.ib(factory=_generate_random) # not persistent - notify_service: Optional[str] = attr.ib(default=None) - target: Optional[str] = attr.ib(default=None) + notify_service: str | None = attr.ib(default=None) + target: str | None = attr.ib(default=None) _UsersDict = Dict[str, NotifySetting] @@ -92,10 +94,10 @@ class NotifyAuthModule(MultiFactorAuthModule): DEFAULT_TITLE = "Notify One-Time Password" - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) - self._user_settings: Optional[_UsersDict] = None + self._user_settings: _UsersDict | None = None self._user_store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) @@ -146,7 +148,7 @@ async def _async_save(self) -> None: ) @callback - def aync_get_available_notify_services(self) -> List[str]: + def aync_get_available_notify_services(self) -> list[str]: """Return list of notify services.""" unordered_services = set() @@ -198,7 +200,7 @@ async def async_is_user_setup(self, user_id: str) -> bool: return user_id in self._user_settings - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" if self._user_settings is None: await self._async_load() @@ -258,7 +260,7 @@ async def async_notify_user(self, user_id: str, code: str) -> None: ) async def async_notify( - self, code: str, notify_service: str, target: Optional[str] = None + self, code: str, notify_service: str, target: str | None = None ) -> None: """Send code by notify service.""" data = {"message": self._message_template.format(code)} @@ -276,23 +278,23 @@ def __init__( auth_module: NotifyAuthModule, setup_schema: vol.Schema, user_id: str, - available_notify_services: List[str], + available_notify_services: list[str], ) -> None: """Initialize the setup flow.""" super().__init__(auth_module, setup_schema, user_id) # to fix typing complaint self._auth_module: NotifyAuthModule = auth_module self._available_notify_services = available_notify_services - self._secret: Optional[str] = None - self._count: Optional[int] = None - self._notify_service: Optional[str] = None - self._target: Optional[str] = None + self._secret: str | None = None + self._count: int | None = None + self._notify_service: str | None = None + self._target: str | None = None async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Let user select available notify services.""" - errors: Dict[str, str] = {} + errors: dict[str, str] = {} hass = self._auth_module.hass if user_input: @@ -306,7 +308,7 @@ async def async_step_init( if not self._available_notify_services: return self.async_abort(reason="no_available_service") - schema: Dict[str, Any] = OrderedDict() + schema: dict[str, Any] = OrderedDict() schema["notify_service"] = vol.In(self._available_notify_services) schema["target"] = vol.Optional(str) @@ -315,10 +317,10 @@ async def async_step_init( ) async def async_step_setup( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Verify user can receive one-time password.""" - errors: Dict[str, str] = {} + errors: dict[str, str] = {} hass = self._auth_module.hass if user_input: diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 4a6faef96c009f..d20c84655463af 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -1,7 +1,9 @@ """Time-based One Time Password auth module.""" +from __future__ import annotations + import asyncio from io import BytesIO -from typing import Any, Dict, Optional, Tuple +from typing import Any import voluptuous as vol @@ -50,7 +52,7 @@ def _generate_qr_code(data: str) -> str: ) -def _generate_secret_and_qr_code(username: str) -> Tuple[str, str, str]: +def _generate_secret_and_qr_code(username: str) -> tuple[str, str, str]: """Generate a secret, url, and QR code.""" import pyotp # pylint: disable=import-outside-toplevel @@ -69,10 +71,10 @@ class TotpAuthModule(MultiFactorAuthModule): DEFAULT_TITLE = "Time-based One Time Password" MAX_RETRY_TIME = 5 - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) - self._users: Optional[Dict[str, str]] = None + self._users: dict[str, str] | None = None self._user_store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) @@ -100,7 +102,7 @@ async def _async_save(self) -> None: """Save data.""" await self._user_store.async_save({STORAGE_USERS: self._users}) - def _add_ota_secret(self, user_id: str, secret: Optional[str] = None) -> str: + def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str: """Create a ota_secret for user.""" import pyotp # pylint: disable=import-outside-toplevel @@ -145,7 +147,7 @@ async def async_is_user_setup(self, user_id: str) -> bool: return user_id in self._users # type: ignore - async def async_validate(self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" if self._users is None: await self._async_load() @@ -181,13 +183,13 @@ def __init__( # to fix typing complaint self._auth_module: TotpAuthModule = auth_module self._user = user - self._ota_secret: Optional[str] = None + self._ota_secret: str | None = None self._url = None # type Optional[str] self._image = None # type Optional[str] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -195,7 +197,7 @@ async def async_step_init( """ import pyotp # pylint: disable=import-outside-toplevel - errors: Dict[str, str] = {} + errors: dict[str, str] = {} if user_input: verified = await self.hass.async_add_executor_job( diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index 4cc67b2ebd4b71..a8a70b8fc3b7c8 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -1,7 +1,9 @@ """Auth models.""" +from __future__ import annotations + from datetime import datetime, timedelta import secrets -from typing import Dict, List, NamedTuple, Optional +from typing import NamedTuple import uuid import attr @@ -21,7 +23,7 @@ class Group: """A group.""" - name: Optional[str] = attr.ib() + name: str | None = attr.ib() policy: perm_mdl.PolicyType = attr.ib() id: str = attr.ib(factory=lambda: uuid.uuid4().hex) system_generated: bool = attr.ib(default=False) @@ -31,24 +33,24 @@ class Group: class User: """A user.""" - name: Optional[str] = attr.ib() + name: str | None = attr.ib() perm_lookup: perm_mdl.PermissionLookup = attr.ib(eq=False, order=False) id: str = attr.ib(factory=lambda: uuid.uuid4().hex) is_owner: bool = attr.ib(default=False) is_active: bool = attr.ib(default=False) system_generated: bool = attr.ib(default=False) - groups: List[Group] = attr.ib(factory=list, eq=False, order=False) + groups: list[Group] = attr.ib(factory=list, eq=False, order=False) # List of credentials of a user. - credentials: List["Credentials"] = attr.ib(factory=list, eq=False, order=False) + credentials: list["Credentials"] = attr.ib(factory=list, eq=False, order=False) # Tokens associated with a user. - refresh_tokens: Dict[str, "RefreshToken"] = attr.ib( + refresh_tokens: dict[str, "RefreshToken"] = attr.ib( factory=dict, eq=False, order=False ) - _permissions: Optional[perm_mdl.PolicyPermissions] = attr.ib( + _permissions: perm_mdl.PolicyPermissions | None = attr.ib( init=False, eq=False, order=False, @@ -89,10 +91,10 @@ class RefreshToken: """RefreshToken for a user to grant new access tokens.""" user: User = attr.ib() - client_id: Optional[str] = attr.ib() + client_id: str | None = attr.ib() access_token_expiration: timedelta = attr.ib() - client_name: Optional[str] = attr.ib(default=None) - client_icon: Optional[str] = attr.ib(default=None) + client_name: str | None = attr.ib(default=None) + client_icon: str | None = attr.ib(default=None) token_type: str = attr.ib( default=TOKEN_TYPE_NORMAL, validator=attr.validators.in_( @@ -104,12 +106,12 @@ class RefreshToken: token: str = attr.ib(factory=lambda: secrets.token_hex(64)) jwt_key: str = attr.ib(factory=lambda: secrets.token_hex(64)) - last_used_at: Optional[datetime] = attr.ib(default=None) - last_used_ip: Optional[str] = attr.ib(default=None) + last_used_at: datetime | None = attr.ib(default=None) + last_used_ip: str | None = attr.ib(default=None) - credential: Optional["Credentials"] = attr.ib(default=None) + credential: "Credentials" | None = attr.ib(default=None) - version: Optional[str] = attr.ib(default=__version__) + version: str | None = attr.ib(default=__version__) @attr.s(slots=True) @@ -117,7 +119,7 @@ class Credentials: """Credentials for a user on an auth provider.""" auth_provider_type: str = attr.ib() - auth_provider_id: Optional[str] = attr.ib() + auth_provider_id: str | None = attr.ib() # Allow the auth provider to store data to represent their auth. data: dict = attr.ib() @@ -129,5 +131,5 @@ class Credentials: class UserMeta(NamedTuple): """User metadata.""" - name: Optional[str] + name: str | None is_active: bool diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py index 2f887d21b02543..28ff3f638d409c 100644 --- a/homeassistant/auth/permissions/__init__.py +++ b/homeassistant/auth/permissions/__init__.py @@ -1,6 +1,8 @@ """Permissions for Home Assistant.""" +from __future__ import annotations + import logging -from typing import Any, Callable, Optional +from typing import Any, Callable import voluptuous as vol @@ -19,7 +21,7 @@ class AbstractPermissions: """Default permissions class.""" - _cached_entity_func: Optional[Callable[[str, str], bool]] = None + _cached_entity_func: Callable[[str, str], bool] | None = None def _entity_func(self) -> Callable[[str, str], bool]: """Return a function that can test entity access.""" diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py index be30c7bf69aef5..f19590a6349de1 100644 --- a/homeassistant/auth/permissions/entities.py +++ b/homeassistant/auth/permissions/entities.py @@ -1,6 +1,8 @@ """Entity permissions.""" +from __future__ import annotations + from collections import OrderedDict -from typing import Callable, Optional +from typing import Callable import voluptuous as vol @@ -43,14 +45,14 @@ def _lookup_domain( perm_lookup: PermissionLookup, domains_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permissions by domain.""" return domains_dict.get(entity_id.split(".", 1)[0]) def _lookup_area( perm_lookup: PermissionLookup, area_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permissions by area.""" entity_entry = perm_lookup.entity_registry.async_get(entity_id) @@ -67,7 +69,7 @@ def _lookup_area( def _lookup_device( perm_lookup: PermissionLookup, devices_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permissions by device.""" entity_entry = perm_lookup.entity_registry.async_get(entity_id) @@ -79,7 +81,7 @@ def _lookup_device( def _lookup_entity_id( perm_lookup: PermissionLookup, entities_dict: SubCategoryDict, entity_id: str -) -> Optional[ValueType]: +) -> ValueType | None: """Look up entity permission by entity id.""" return entities_dict.get(entity_id) diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py index fad98b3f22a3f7..121d87f78484cb 100644 --- a/homeassistant/auth/permissions/merge.py +++ b/homeassistant/auth/permissions/merge.py @@ -1,13 +1,15 @@ """Merging of policies.""" -from typing import Dict, List, Set, cast +from __future__ import annotations + +from typing import cast from .types import CategoryType, PolicyType -def merge_policies(policies: List[PolicyType]) -> PolicyType: +def merge_policies(policies: list[PolicyType]) -> PolicyType: """Merge policies.""" - new_policy: Dict[str, CategoryType] = {} - seen: Set[str] = set() + new_policy: dict[str, CategoryType] = {} + seen: set[str] = set() for policy in policies: for category in policy: if category in seen: @@ -20,7 +22,7 @@ def merge_policies(policies: List[PolicyType]) -> PolicyType: return new_policy -def _merge_policies(sources: List[CategoryType]) -> CategoryType: +def _merge_policies(sources: list[CategoryType]) -> CategoryType: """Merge a policy.""" # When merging policies, the most permissive wins. # This means we order it like this: @@ -34,7 +36,7 @@ def _merge_policies(sources: List[CategoryType]) -> CategoryType: # merge each key in the source. policy: CategoryType = None - seen: Set[str] = set() + seen: set[str] = set() for source in sources: if source is None: continue diff --git a/homeassistant/auth/permissions/util.py b/homeassistant/auth/permissions/util.py index 11bbd878eb23d1..e95e0080b50a83 100644 --- a/homeassistant/auth/permissions/util.py +++ b/homeassistant/auth/permissions/util.py @@ -1,6 +1,8 @@ """Helpers to deal with permissions.""" +from __future__ import annotations + from functools import wraps -from typing import Callable, Dict, List, Optional, cast +from typing import Callable, Dict, Optional, cast from .const import SUBCAT_ALL from .models import PermissionLookup @@ -45,7 +47,7 @@ def apply_policy_allow_all(entity_id: str, key: str) -> bool: assert isinstance(policy, dict) - funcs: List[Callable[[str, str], Optional[bool]]] = [] + funcs: list[Callable[[str, str], bool | None]] = [] for key, lookup_func in subcategories.items(): lookup_value = policy.get(key) @@ -80,10 +82,10 @@ def apply_policy_funcs(object_id: str, key: str) -> bool: def _gen_dict_test_func( perm_lookup: PermissionLookup, lookup_func: LookupFunc, lookup_dict: SubCategoryDict -) -> Callable[[str, str], Optional[bool]]: +) -> Callable[[str, str], bool | None]: """Generate a lookup function.""" - def test_value(object_id: str, key: str) -> Optional[bool]: + def test_value(object_id: str, key: str) -> bool | None: """Test if permission is allowed based on the keys.""" schema: ValueType = lookup_func(perm_lookup, lookup_dict, object_id) diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 2afe1333c6a345..6e188be1ffc940 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -4,7 +4,7 @@ import importlib import logging import types -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol from voluptuous.humanize import humanize_error @@ -42,7 +42,7 @@ class AuthProvider: DEFAULT_TITLE = "Unnamed auth provider" def __init__( - self, hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] + self, hass: HomeAssistant, store: AuthStore, config: dict[str, Any] ) -> None: """Initialize an auth provider.""" self.hass = hass @@ -50,7 +50,7 @@ def __init__( self.config = config @property - def id(self) -> Optional[str]: + def id(self) -> str | None: """Return id of the auth provider. Optional, can be None. @@ -72,7 +72,7 @@ def support_mfa(self) -> bool: """Return whether multi-factor auth supported by the auth provider.""" return True - async def async_credentials(self) -> List[Credentials]: + async def async_credentials(self) -> list[Credentials]: """Return all credentials of this provider.""" users = await self.store.async_get_users() return [ @@ -86,7 +86,7 @@ async def async_credentials(self) -> List[Credentials]: ] @callback - def async_create_credentials(self, data: Dict[str, str]) -> Credentials: + def async_create_credentials(self, data: dict[str, str]) -> Credentials: """Create credentials.""" return Credentials( auth_provider_type=self.type, auth_provider_id=self.id, data=data @@ -94,7 +94,7 @@ def async_create_credentials(self, data: Dict[str, str]) -> Credentials: # Implement by extending class - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return the data flow for logging in with auth provider. Auth provider should extend LoginFlow and return an instance. @@ -102,7 +102,7 @@ async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: raise NotImplementedError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" raise NotImplementedError @@ -121,7 +121,7 @@ async def async_initialize(self) -> None: @callback def async_validate_refresh_token( - self, refresh_token: RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: RefreshToken, remote_ip: str | None = None ) -> None: """Verify a refresh token is still valid. @@ -131,7 +131,7 @@ def async_validate_refresh_token( async def auth_provider_from_config( - hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] + hass: HomeAssistant, store: AuthStore, config: dict[str, Any] ) -> AuthProvider: """Initialize an auth provider from a config.""" provider_name = config[CONF_TYPE] @@ -188,17 +188,17 @@ class LoginFlow(data_entry_flow.FlowHandler): def __init__(self, auth_provider: AuthProvider) -> None: """Initialize the login flow.""" self._auth_provider = auth_provider - self._auth_module_id: Optional[str] = None + self._auth_module_id: str | None = None self._auth_manager = auth_provider.hass.auth - self.available_mfa_modules: Dict[str, str] = {} + self.available_mfa_modules: dict[str, str] = {} self.created_at = dt_util.utcnow() self.invalid_mfa_times = 0 - self.user: Optional[User] = None - self.credential: Optional[Credentials] = None + self.user: User | None = None + self.credential: Credentials | None = None async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the first step of login flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -207,8 +207,8 @@ async def async_step_init( raise NotImplementedError async def async_step_select_mfa_module( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of select mfa module.""" errors = {} @@ -232,8 +232,8 @@ async def async_step_select_mfa_module( ) async def async_step_mfa( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of mfa validation.""" assert self.credential assert self.user @@ -273,7 +273,7 @@ async def async_step_mfa( if not errors: return await self.async_finish(self.credential) - description_placeholders: Dict[str, Optional[str]] = { + description_placeholders: dict[str, str | None] = { "mfa_module_name": auth_module.name, "mfa_module_id": auth_module.id, } @@ -285,6 +285,6 @@ async def async_step_mfa( errors=errors, ) - async def async_finish(self, flow_result: Any) -> Dict: + async def async_finish(self, flow_result: Any) -> dict: """Handle the pass of login flow.""" return self.async_create_entry(title=self._auth_provider.name, data=flow_result) diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index b2b192219790cd..47a56d87097c42 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -1,10 +1,11 @@ """Auth provider that validates credentials via an external command.""" +from __future__ import annotations import asyncio.subprocess import collections import logging import os -from typing import Any, Dict, Optional, cast +from typing import Any, cast import voluptuous as vol @@ -51,9 +52,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: attributes provided by external programs. """ super().__init__(*args, **kwargs) - self._user_meta: Dict[str, Dict[str, Any]] = {} + self._user_meta: dict[str, dict[str, Any]] = {} - async def async_login_flow(self, context: Optional[dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return CommandLineLoginFlow(self) @@ -82,7 +83,7 @@ async def async_validate_login(self, username: str, password: str) -> None: raise InvalidAuthError if self.config[CONF_META]: - meta: Dict[str, str] = {} + meta: dict[str, str] = {} for _line in stdout.splitlines(): try: line = _line.decode().lstrip() @@ -99,7 +100,7 @@ async def async_validate_login(self, username: str, password: str) -> None: self._user_meta[username] = meta async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" username = flow_result["username"] @@ -125,8 +126,8 @@ class CommandLineLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -143,7 +144,7 @@ async def async_step_init( user_input.pop("password") return await self.async_finish(user_input) - schema: Dict[str, type] = collections.OrderedDict() + schema: dict[str, type] = collections.OrderedDict() schema["username"] = str schema["password"] = str diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index c66ffa7332e4a8..54d82013a7580e 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -5,7 +5,7 @@ import base64 from collections import OrderedDict import logging -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, cast import bcrypt import voluptuous as vol @@ -21,7 +21,7 @@ STORAGE_KEY = "auth_provider.homeassistant" -def _disallow_id(conf: Dict[str, Any]) -> Dict[str, Any]: +def _disallow_id(conf: dict[str, Any]) -> dict[str, Any]: """Disallow ID in config.""" if CONF_ID in conf: raise vol.Invalid("ID is not allowed for the homeassistant auth provider.") @@ -62,7 +62,7 @@ def __init__(self, hass: HomeAssistant) -> None: self._store = hass.helpers.storage.Store( STORAGE_VERSION, STORAGE_KEY, private=True ) - self._data: Optional[Dict[str, Any]] = None + self._data: dict[str, Any] | None = None # Legacy mode will allow usernames to start/end with whitespace # and will compare usernames case-insensitive. # Remove in 2020 or when we launch 1.0. @@ -83,7 +83,7 @@ async def async_load(self) -> None: if data is None: data = {"users": []} - seen: Set[str] = set() + seen: set[str] = set() for user in data["users"]: username = user["username"] @@ -121,7 +121,7 @@ async def async_load(self) -> None: self._data = data @property - def users(self) -> List[Dict[str, str]]: + def users(self) -> list[dict[str, str]]: """Return users.""" return self._data["users"] # type: ignore @@ -220,7 +220,7 @@ class HassAuthProvider(AuthProvider): def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize an Home Assistant auth provider.""" super().__init__(*args, **kwargs) - self.data: Optional[Data] = None + self.data: Data | None = None self._init_lock = asyncio.Lock() async def async_initialize(self) -> None: @@ -233,7 +233,7 @@ async def async_initialize(self) -> None: await data.async_load() self.data = data - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return HassLoginFlow(self) @@ -277,7 +277,7 @@ async def async_change_password(self, username: str, new_password: str) -> None: await self.data.async_save() async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" if self.data is None: @@ -318,8 +318,8 @@ class HassLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -335,7 +335,7 @@ async def async_step_init( user_input.pop("password") return await self.async_finish(user_input) - schema: Dict[str, type] = OrderedDict() + schema: dict[str, type] = OrderedDict() schema["username"] = str schema["password"] = str diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 70014a236cdae2..c938a6fac81539 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -1,7 +1,9 @@ """Example auth provider.""" +from __future__ import annotations + from collections import OrderedDict import hmac -from typing import Any, Dict, Optional, cast +from typing import Any, cast import voluptuous as vol @@ -33,7 +35,7 @@ class InvalidAuthError(HomeAssistantError): class ExampleAuthProvider(AuthProvider): """Example auth provider based on hardcoded usernames and passwords.""" - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return ExampleLoginFlow(self) @@ -60,7 +62,7 @@ def async_validate_login(self, username: str, password: str) -> None: raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" username = flow_result["username"] @@ -94,8 +96,8 @@ class ExampleLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} @@ -111,7 +113,7 @@ async def async_step_init( user_input.pop("password") return await self.async_finish(user_input) - schema: Dict[str, type] = OrderedDict() + schema: dict[str, type] = OrderedDict() schema["username"] = str schema["password"] = str diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index ba96fa285f1213..522751c70d6988 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -3,8 +3,10 @@ It will be removed when auth system production ready """ +from __future__ import annotations + import hmac -from typing import Any, Dict, Optional, cast +from typing import Any, cast import voluptuous as vol @@ -40,7 +42,7 @@ def api_password(self) -> str: """Return api_password.""" return str(self.config[CONF_API_PASSWORD]) - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" return LegacyLoginFlow(self) @@ -55,7 +57,7 @@ def async_validate_login(self, password: str) -> None: raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Return credentials for this login.""" credentials = await self.async_credentials() @@ -79,8 +81,8 @@ class LegacyLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" errors = {} diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index c45cca4bd1a2d4..85b43d89f3fb40 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -3,6 +3,8 @@ It shows list of users if access from trusted network. Abort login flow if not access from trusted network. """ +from __future__ import annotations + from ipaddress import ( IPv4Address, IPv4Network, @@ -11,7 +13,7 @@ ip_address, ip_network, ) -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, Dict, List, Union, cast import voluptuous as vol @@ -68,12 +70,12 @@ class TrustedNetworksAuthProvider(AuthProvider): DEFAULT_TITLE = "Trusted Networks" @property - def trusted_networks(self) -> List[IPNetwork]: + def trusted_networks(self) -> list[IPNetwork]: """Return trusted networks.""" return cast(List[IPNetwork], self.config[CONF_TRUSTED_NETWORKS]) @property - def trusted_users(self) -> Dict[IPNetwork, Any]: + def trusted_users(self) -> dict[IPNetwork, Any]: """Return trusted users per network.""" return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS]) @@ -82,7 +84,7 @@ def support_mfa(self) -> bool: """Trusted Networks auth provider does not support MFA.""" return False - async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: dict | None) -> LoginFlow: """Return a flow to login.""" assert context is not None ip_addr = cast(IPAddress, context.get("ip_address")) @@ -125,7 +127,7 @@ async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: ) async def async_get_or_create_credentials( - self, flow_result: Dict[str, str] + self, flow_result: dict[str, str] ) -> Credentials: """Get credentials based on the flow result.""" user_id = flow_result["user"] @@ -169,7 +171,7 @@ def async_validate_access(self, ip_addr: IPAddress) -> None: @callback def async_validate_refresh_token( - self, refresh_token: RefreshToken, remote_ip: Optional[str] = None + self, refresh_token: RefreshToken, remote_ip: str | None = None ) -> None: """Verify a refresh token is still valid.""" if remote_ip is None: @@ -186,7 +188,7 @@ def __init__( self, auth_provider: TrustedNetworksAuthProvider, ip_addr: IPAddress, - available_users: Dict[str, Optional[str]], + available_users: dict[str, str | None], allow_bypass_login: bool, ) -> None: """Initialize the login flow.""" @@ -196,8 +198,8 @@ def __init__( self._allow_bypass_login = allow_bypass_login async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Handle the step of the form.""" try: cast( diff --git a/homeassistant/scripts/__init__.py b/homeassistant/scripts/__init__.py index 8042e546884019..9aa07b94dc8952 100644 --- a/homeassistant/scripts/__init__.py +++ b/homeassistant/scripts/__init__.py @@ -1,11 +1,13 @@ """Home Assistant command line scripts.""" +from __future__ import annotations + import argparse import asyncio import importlib import logging import os import sys -from typing import List, Optional, Sequence, Text +from typing import Sequence from homeassistant import runner from homeassistant.bootstrap import async_mount_local_lib_path @@ -16,7 +18,7 @@ # mypy: allow-untyped-defs, no-warn-return-any -def run(args: List) -> int: +def run(args: list) -> int: """Run a script.""" scripts = [] path = os.path.dirname(__file__) @@ -65,7 +67,7 @@ def run(args: List) -> int: return script.run(args[1:]) # type: ignore -def extract_config_dir(args: Optional[Sequence[Text]] = None) -> str: +def extract_config_dir(args: Sequence[str] | None = None) -> str: """Extract the config dir from the arguments or get the default.""" parser = argparse.ArgumentParser(add_help=False) parser.add_argument("-c", "--config", default=None) diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index 3f590362504953..2acefbce12891a 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -1,4 +1,6 @@ """Script to run benchmarks.""" +from __future__ import annotations + import argparse import asyncio import collections @@ -7,7 +9,7 @@ import json import logging from timeit import default_timer as timer -from typing import Callable, Dict, TypeVar +from typing import Callable, TypeVar from homeassistant import core from homeassistant.components.websocket_api.const import JSON_DUMP @@ -21,7 +23,7 @@ CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name -BENCHMARKS: Dict[str, Callable] = {} +BENCHMARKS: dict[str, Callable] = {} def run(args): diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 4fc8383782cc86..40ce9a521df988 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -1,4 +1,6 @@ """Script to check the configuration file.""" +from __future__ import annotations + import argparse import asyncio from collections import OrderedDict @@ -6,7 +8,7 @@ from glob import glob import logging import os -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable from unittest.mock import patch from homeassistant import core @@ -22,13 +24,13 @@ _LOGGER = logging.getLogger(__name__) # pylint: disable=protected-access -MOCKS: Dict[str, Tuple[str, Callable]] = { +MOCKS: dict[str, tuple[str, Callable]] = { "load": ("homeassistant.util.yaml.loader.load_yaml", yaml_loader.load_yaml), "load*": ("homeassistant.config.load_yaml", yaml_loader.load_yaml), "secrets": ("homeassistant.util.yaml.loader.secret_yaml", yaml_loader.secret_yaml), } -PATCHES: Dict[str, Any] = {} +PATCHES: dict[str, Any] = {} C_HEAD = "bold" ERROR_STR = "General Errors" @@ -48,7 +50,7 @@ def color(the_color, *args, reset=None): raise ValueError(f"Invalid color {k!s} in {the_color}") from k -def run(script_args: List) -> int: +def run(script_args: list) -> int: """Handle check config commandline script.""" parser = argparse.ArgumentParser(description="Check Home Assistant configuration.") parser.add_argument("--script", choices=["check_config"]) @@ -83,7 +85,7 @@ def run(script_args: List) -> int: res = check(config_dir, args.secrets) - domain_info: List[str] = [] + domain_info: list[str] = [] if args.info: domain_info = args.info.split(",") @@ -123,7 +125,7 @@ def run(script_args: List) -> int: dump_dict(res["components"].get(domain)) if args.secrets: - flatsecret: Dict[str, str] = {} + flatsecret: dict[str, str] = {} for sfn, sdict in res["secret_cache"].items(): sss = [] @@ -149,7 +151,7 @@ def run(script_args: List) -> int: def check(config_dir, secrets=False): """Perform a check by mocking hass load functions.""" logging.getLogger("homeassistant.loader").setLevel(logging.CRITICAL) - res: Dict[str, Any] = { + res: dict[str, Any] = { "yaml_files": OrderedDict(), # yaml_files loaded "secrets": OrderedDict(), # secret cache and secrets loaded "except": OrderedDict(), # exceptions raised (with config) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 281a7d5308c302..79ceeada0c28aa 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -1,4 +1,6 @@ """Helper methods for various modules.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import enum @@ -9,16 +11,7 @@ import string import threading from types import MappingProxyType -from typing import ( - Any, - Callable, - Coroutine, - Iterable, - KeysView, - Optional, - TypeVar, - Union, -) +from typing import Any, Callable, Coroutine, Iterable, KeysView, TypeVar import slugify as unicode_slug @@ -106,8 +99,8 @@ def repr_helper(inp: Any) -> str: def convert( - value: Optional[T], to_type: Callable[[T], U], default: Optional[U] = None -) -> Optional[U]: + value: T | None, to_type: Callable[[T], U], default: U | None = None +) -> U | None: """Convert value to to_type, returns default if fails.""" try: return default if value is None else to_type(value) @@ -117,7 +110,7 @@ def convert( def ensure_unique_string( - preferred_string: str, current_strings: Union[Iterable[str], KeysView[str]] + preferred_string: str, current_strings: Iterable[str] | KeysView[str] ) -> str: """Return a string that is not present in current_strings. @@ -213,7 +206,7 @@ class Throttle: """ def __init__( - self, min_time: timedelta, limit_no_throttle: Optional[timedelta] = None + self, min_time: timedelta, limit_no_throttle: timedelta | None = None ) -> None: """Initialize the throttle.""" self.min_time = min_time @@ -253,7 +246,7 @@ def throttled_value() -> None: # type: ignore ) @wraps(method) - def wrapper(*args: Any, **kwargs: Any) -> Union[Callable, Coroutine]: + def wrapper(*args: Any, **kwargs: Any) -> Callable | Coroutine: """Wrap that allows wrapped to be called only once per min_time. If we cannot acquire the lock, it is running so return None. diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index f2c761282bc281..7d14ec252d9bf3 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -1,7 +1,9 @@ """Utilities to help with aiohttp.""" +from __future__ import annotations + import io import json -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import parse_qsl from multidict import CIMultiDict, MultiDict @@ -26,7 +28,7 @@ async def read(self, byte_count: int = -1) -> bytes: class MockRequest: """Mock an aiohttp request.""" - mock_source: Optional[str] = None + mock_source: str | None = None def __init__( self, @@ -34,8 +36,8 @@ def __init__( mock_source: str, method: str = "GET", status: int = HTTP_OK, - headers: Optional[Dict[str, str]] = None, - query_string: Optional[str] = None, + headers: dict[str, str] | None = None, + query_string: str | None = None, url: str = "", ) -> None: """Initialize a request.""" diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 36bf47aaf968db..f41461aada5b57 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -1,7 +1,8 @@ """Color util methods.""" +from __future__ import annotations + import colorsys import math -from typing import List, Optional, Tuple import attr @@ -183,7 +184,7 @@ class GamutType: blue: XYPoint = attr.ib() -def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: +def color_name_to_rgb(color_name: str) -> tuple[int, int, int]: """Convert color name to RGB hex value.""" # COLORS map has no spaces in it, so make the color_name have no # spaces in it as well for matching purposes @@ -198,8 +199,8 @@ def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: def color_RGB_to_xy( - iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None -) -> Tuple[float, float]: + iR: int, iG: int, iB: int, Gamut: GamutType | None = None +) -> tuple[float, float]: """Convert from RGB color to XY color.""" return color_RGB_to_xy_brightness(iR, iG, iB, Gamut)[:2] @@ -208,8 +209,8 @@ def color_RGB_to_xy( # http://www.developers.meethue.com/documentation/color-conversions-rgb-xy # License: Code is given as is. Use at your own risk and discretion. def color_RGB_to_xy_brightness( - iR: int, iG: int, iB: int, Gamut: Optional[GamutType] = None -) -> Tuple[float, float, int]: + iR: int, iG: int, iB: int, Gamut: GamutType | None = None +) -> tuple[float, float, int]: """Convert from RGB color to XY color.""" if iR + iG + iB == 0: return 0.0, 0.0, 0 @@ -248,8 +249,8 @@ def color_RGB_to_xy_brightness( def color_xy_to_RGB( - vX: float, vY: float, Gamut: Optional[GamutType] = None -) -> Tuple[int, int, int]: + vX: float, vY: float, Gamut: GamutType | None = None +) -> tuple[int, int, int]: """Convert from XY to a normalized RGB.""" return color_xy_brightness_to_RGB(vX, vY, 255, Gamut) @@ -257,8 +258,8 @@ def color_xy_to_RGB( # Converted to Python from Obj-C, original source from: # http://www.developers.meethue.com/documentation/color-conversions-rgb-xy def color_xy_brightness_to_RGB( - vX: float, vY: float, ibrightness: int, Gamut: Optional[GamutType] = None -) -> Tuple[int, int, int]: + vX: float, vY: float, ibrightness: int, Gamut: GamutType | None = None +) -> tuple[int, int, int]: """Convert from XYZ to RGB.""" if Gamut: if not check_point_in_lamps_reach((vX, vY), Gamut): @@ -304,7 +305,7 @@ def color_xy_brightness_to_RGB( return (ir, ig, ib) -def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> Tuple[int, int, int]: +def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> tuple[int, int, int]: """Convert a hsb into its rgb representation.""" if fS == 0.0: fV = int(fB * 255) @@ -345,7 +346,7 @@ def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> Tuple[int, int, int]: return (r, g, b) -def color_RGB_to_hsv(iR: float, iG: float, iB: float) -> Tuple[float, float, float]: +def color_RGB_to_hsv(iR: float, iG: float, iB: float) -> tuple[float, float, float]: """Convert an rgb color to its hsv representation. Hue is scaled 0-360 @@ -356,12 +357,12 @@ def color_RGB_to_hsv(iR: float, iG: float, iB: float) -> Tuple[float, float, flo return round(fHSV[0] * 360, 3), round(fHSV[1] * 100, 3), round(fHSV[2] * 100, 3) -def color_RGB_to_hs(iR: float, iG: float, iB: float) -> Tuple[float, float]: +def color_RGB_to_hs(iR: float, iG: float, iB: float) -> tuple[float, float]: """Convert an rgb color to its hs representation.""" return color_RGB_to_hsv(iR, iG, iB)[:2] -def color_hsv_to_RGB(iH: float, iS: float, iV: float) -> Tuple[int, int, int]: +def color_hsv_to_RGB(iH: float, iS: float, iV: float) -> tuple[int, int, int]: """Convert an hsv color into its rgb representation. Hue is scaled 0-360 @@ -372,27 +373,27 @@ def color_hsv_to_RGB(iH: float, iS: float, iV: float) -> Tuple[int, int, int]: return (int(fRGB[0] * 255), int(fRGB[1] * 255), int(fRGB[2] * 255)) -def color_hs_to_RGB(iH: float, iS: float) -> Tuple[int, int, int]: +def color_hs_to_RGB(iH: float, iS: float) -> tuple[int, int, int]: """Convert an hsv color into its rgb representation.""" return color_hsv_to_RGB(iH, iS, 100) def color_xy_to_hs( - vX: float, vY: float, Gamut: Optional[GamutType] = None -) -> Tuple[float, float]: + vX: float, vY: float, Gamut: GamutType | None = None +) -> tuple[float, float]: """Convert an xy color to its hs representation.""" h, s, _ = color_RGB_to_hsv(*color_xy_to_RGB(vX, vY, Gamut)) return h, s def color_hs_to_xy( - iH: float, iS: float, Gamut: Optional[GamutType] = None -) -> Tuple[float, float]: + iH: float, iS: float, Gamut: GamutType | None = None +) -> tuple[float, float]: """Convert an hs color to its xy representation.""" return color_RGB_to_xy(*color_hs_to_RGB(iH, iS), Gamut) -def _match_max_scale(input_colors: Tuple, output_colors: Tuple) -> Tuple: +def _match_max_scale(input_colors: tuple, output_colors: tuple) -> tuple: """Match the maximum value of the output to the input.""" max_in = max(input_colors) max_out = max(output_colors) @@ -403,7 +404,7 @@ def _match_max_scale(input_colors: Tuple, output_colors: Tuple) -> Tuple: return tuple(int(round(i * factor)) for i in output_colors) -def color_rgb_to_rgbw(r: int, g: int, b: int) -> Tuple[int, int, int, int]: +def color_rgb_to_rgbw(r: int, g: int, b: int) -> tuple[int, int, int, int]: """Convert an rgb color to an rgbw representation.""" # Calculate the white channel as the minimum of input rgb channels. # Subtract the white portion from the remaining rgb channels. @@ -415,7 +416,7 @@ def color_rgb_to_rgbw(r: int, g: int, b: int) -> Tuple[int, int, int, int]: return _match_max_scale((r, g, b), rgbw) # type: ignore -def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> Tuple[int, int, int]: +def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> tuple[int, int, int]: """Convert an rgbw color to an rgb representation.""" # Add the white channel back into the rgb channels. rgb = (r + w, g + w, b + w) @@ -430,7 +431,7 @@ def color_rgb_to_hex(r: int, g: int, b: int) -> str: return "{:02x}{:02x}{:02x}".format(round(r), round(g), round(b)) -def rgb_hex_to_rgb_list(hex_string: str) -> List[int]: +def rgb_hex_to_rgb_list(hex_string: str) -> list[int]: """Return an RGB color value list from a hex color string.""" return [ int(hex_string[i : i + len(hex_string) // 3], 16) @@ -438,14 +439,14 @@ def rgb_hex_to_rgb_list(hex_string: str) -> List[int]: ] -def color_temperature_to_hs(color_temperature_kelvin: float) -> Tuple[float, float]: +def color_temperature_to_hs(color_temperature_kelvin: float) -> tuple[float, float]: """Return an hs color from a color temperature in Kelvin.""" return color_RGB_to_hs(*color_temperature_to_rgb(color_temperature_kelvin)) def color_temperature_to_rgb( color_temperature_kelvin: float, -) -> Tuple[float, float, float]: +) -> tuple[float, float, float]: """ Return an RGB color from a color temperature in Kelvin. @@ -555,8 +556,8 @@ def get_closest_point_to_line(A: XYPoint, B: XYPoint, P: XYPoint) -> XYPoint: def get_closest_point_to_point( - xy_tuple: Tuple[float, float], Gamut: GamutType -) -> Tuple[float, float]: + xy_tuple: tuple[float, float], Gamut: GamutType +) -> tuple[float, float]: """ Get the closest matching color within the gamut of the light. @@ -592,7 +593,7 @@ def get_closest_point_to_point( return (cx, cy) -def check_point_in_lamps_reach(p: Tuple[float, float], Gamut: GamutType) -> bool: +def check_point_in_lamps_reach(p: tuple[float, float], Gamut: GamutType) -> bool: """Check if the provided XYPoint can be recreated by a Hue lamp.""" v1 = XYPoint(Gamut.green.x - Gamut.red.x, Gamut.green.y - Gamut.red.y) v2 = XYPoint(Gamut.blue.x - Gamut.red.x, Gamut.blue.y - Gamut.red.y) diff --git a/homeassistant/util/distance.py b/homeassistant/util/distance.py index 0e0a060c49cbca..592c7c3145ea3b 100644 --- a/homeassistant/util/distance.py +++ b/homeassistant/util/distance.py @@ -1,6 +1,8 @@ """Distance util functions.""" +from __future__ import annotations + from numbers import Number -from typing import Callable, Dict +from typing import Callable from homeassistant.const import ( LENGTH, @@ -26,7 +28,7 @@ LENGTH_YARD, ] -TO_METERS: Dict[str, Callable[[float], float]] = { +TO_METERS: dict[str, Callable[[float], float]] = { LENGTH_METERS: lambda meters: meters, LENGTH_MILES: lambda miles: miles * 1609.344, LENGTH_YARD: lambda yards: yards * 0.9144, @@ -37,7 +39,7 @@ LENGTH_MILLIMETERS: lambda millimeters: millimeters * 0.001, } -METERS_TO: Dict[str, Callable[[float], float]] = { +METERS_TO: dict[str, Callable[[float], float]] = { LENGTH_METERS: lambda meters: meters, LENGTH_MILES: lambda meters: meters * 0.000621371, LENGTH_YARD: lambda meters: meters * 1.09361, diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index a4d5fd81c4f229..a659d0add38b1f 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,7 +1,9 @@ """Helper methods to handle the time in Home Assistant.""" +from __future__ import annotations + import datetime as dt import re -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, cast import ciso8601 import pytz @@ -40,7 +42,7 @@ def set_default_time_zone(time_zone: dt.tzinfo) -> None: DEFAULT_TIME_ZONE = time_zone -def get_time_zone(time_zone_str: str) -> Optional[dt.tzinfo]: +def get_time_zone(time_zone_str: str) -> dt.tzinfo | None: """Get time zone from string. Return None if unable to determine. Async friendly. @@ -56,7 +58,7 @@ def utcnow() -> dt.datetime: return dt.datetime.now(NATIVE_UTC) -def now(time_zone: Optional[dt.tzinfo] = None) -> dt.datetime: +def now(time_zone: dt.tzinfo | None = None) -> dt.datetime: """Get now in specified time zone.""" return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE) @@ -77,7 +79,7 @@ def as_utc(dattim: dt.datetime) -> dt.datetime: def as_timestamp(dt_value: dt.datetime) -> float: """Convert a date/time into a unix time (seconds since 1970).""" if hasattr(dt_value, "timestamp"): - parsed_dt: Optional[dt.datetime] = dt_value + parsed_dt: dt.datetime | None = dt_value else: parsed_dt = parse_datetime(str(dt_value)) if parsed_dt is None: @@ -100,9 +102,7 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime: return UTC.localize(dt.datetime.utcfromtimestamp(timestamp)) -def start_of_local_day( - dt_or_d: Union[dt.date, dt.datetime, None] = None -) -> dt.datetime: +def start_of_local_day(dt_or_d: dt.date | dt.datetime | None = None) -> dt.datetime: """Return local datetime object of start of day from date or datetime.""" if dt_or_d is None: date: dt.date = now().date() @@ -119,7 +119,7 @@ def start_of_local_day( # Copyright (c) Django Software Foundation and individual contributors. # All rights reserved. # https://github.com/django/django/blob/master/LICENSE -def parse_datetime(dt_str: str) -> Optional[dt.datetime]: +def parse_datetime(dt_str: str) -> dt.datetime | None: """Parse a string and return a datetime.datetime. This function supports time zone offsets. When the input contains one, @@ -134,12 +134,12 @@ def parse_datetime(dt_str: str) -> Optional[dt.datetime]: match = DATETIME_RE.match(dt_str) if not match: return None - kws: Dict[str, Any] = match.groupdict() + kws: dict[str, Any] = match.groupdict() if kws["microsecond"]: kws["microsecond"] = kws["microsecond"].ljust(6, "0") tzinfo_str = kws.pop("tzinfo") - tzinfo: Optional[dt.tzinfo] = None + tzinfo: dt.tzinfo | None = None if tzinfo_str == "Z": tzinfo = UTC elif tzinfo_str is not None: @@ -154,7 +154,7 @@ def parse_datetime(dt_str: str) -> Optional[dt.datetime]: return dt.datetime(**kws) -def parse_date(dt_str: str) -> Optional[dt.date]: +def parse_date(dt_str: str) -> dt.date | None: """Convert a date string to a date object.""" try: return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date() @@ -162,7 +162,7 @@ def parse_date(dt_str: str) -> Optional[dt.date]: return None -def parse_time(time_str: str) -> Optional[dt.time]: +def parse_time(time_str: str) -> dt.time | None: """Parse a time string (00:20:00) into Time object. Return None if invalid. @@ -213,7 +213,7 @@ def formatn(number: int, unit: str) -> str: return formatn(rounded_delta, selected_unit) -def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> List[int]: +def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> list[int]: """Parse the time expression part and return a list of times to match.""" if parameter is None or parameter == MATCH_ALL: res = list(range(min_value, max_value + 1)) @@ -241,9 +241,9 @@ def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> Lis def find_next_time_expression_time( now: dt.datetime, # pylint: disable=redefined-outer-name - seconds: List[int], - minutes: List[int], - hours: List[int], + seconds: list[int], + minutes: list[int], + hours: list[int], ) -> dt.datetime: """Find the next datetime from now for which the time expression matches. @@ -257,7 +257,7 @@ def find_next_time_expression_time( if not seconds or not minutes or not hours: raise ValueError("Cannot find a next time: Time expression never matches!") - def _lower_bound(arr: List[int], cmp: int) -> Optional[int]: + def _lower_bound(arr: list[int], cmp: int) -> int | None: """Return the first value in arr greater or equal to cmp. Return None if no such value exists. diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 2ce98ffef77ad9..fac008d9f0fd2c 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -1,10 +1,12 @@ """JSON utility functions.""" +from __future__ import annotations + from collections import deque import json import logging import os import tempfile -from typing import Any, Callable, Dict, List, Optional, Type, Union +from typing import Any, Callable from homeassistant.core import Event, State from homeassistant.exceptions import HomeAssistantError @@ -20,9 +22,7 @@ class WriteError(HomeAssistantError): """Error writing the data.""" -def load_json( - filename: str, default: Union[List, Dict, None] = None -) -> Union[List, Dict]: +def load_json(filename: str, default: list | dict | None = None) -> list | dict: """Load JSON data from a file and return as dict or list. Defaults to returning empty dict if file is not found. @@ -44,10 +44,10 @@ def load_json( def save_json( filename: str, - data: Union[List, Dict], + data: list | dict, private: bool = False, *, - encoder: Optional[Type[json.JSONEncoder]] = None, + encoder: type[json.JSONEncoder] | None = None, ) -> None: """Save JSON data to a file. @@ -85,7 +85,7 @@ def save_json( _LOGGER.error("JSON replacement cleanup failed: %s", err) -def format_unserializable_data(data: Dict[str, Any]) -> str: +def format_unserializable_data(data: dict[str, Any]) -> str: """Format output of find_paths in a friendly way. Format is comma separated: =() @@ -95,7 +95,7 @@ def format_unserializable_data(data: Dict[str, Any]) -> str: def find_paths_unserializable_data( bad_data: Any, *, dump: Callable[[Any], str] = json.dumps -) -> Dict[str, Any]: +) -> dict[str, Any]: """Find the paths to unserializable data. This method is slow! Only use for error handling. diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index 85db07e2d4260b..c22f5213130b59 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -3,10 +3,12 @@ detect_location_info and elevation are mocked by default during tests. """ +from __future__ import annotations + import asyncio import collections import math -from typing import Any, Dict, Optional, Tuple +from typing import Any import aiohttp @@ -47,7 +49,7 @@ async def async_detect_location_info( session: aiohttp.ClientSession, -) -> Optional[LocationInfo]: +) -> LocationInfo | None: """Detect location information.""" data = await _get_ipapi(session) @@ -63,8 +65,8 @@ async def async_detect_location_info( def distance( - lat1: Optional[float], lon1: Optional[float], lat2: float, lon2: float -) -> Optional[float]: + lat1: float | None, lon1: float | None, lat2: float, lon2: float +) -> float | None: """Calculate the distance in meters between two points. Async friendly. @@ -81,8 +83,8 @@ def distance( # Source: https://github.com/maurycyp/vincenty # License: https://github.com/maurycyp/vincenty/blob/master/LICENSE def vincenty( - point1: Tuple[float, float], point2: Tuple[float, float], miles: bool = False -) -> Optional[float]: + point1: tuple[float, float], point2: tuple[float, float], miles: bool = False +) -> float | None: """ Vincenty formula (inverse method) to calculate the distance. @@ -162,7 +164,7 @@ def vincenty( return round(s, 6) -async def _get_ipapi(session: aiohttp.ClientSession) -> Optional[Dict[str, Any]]: +async def _get_ipapi(session: aiohttp.ClientSession) -> dict[str, Any] | None: """Query ipapi.co for location data.""" try: resp = await session.get(IPAPI, timeout=5) @@ -192,7 +194,7 @@ async def _get_ipapi(session: aiohttp.ClientSession) -> Optional[Dict[str, Any]] } -async def _get_ip_api(session: aiohttp.ClientSession) -> Optional[Dict[str, Any]]: +async def _get_ip_api(session: aiohttp.ClientSession) -> dict[str, Any] | None: """Query ip-api.com for location data.""" try: resp = await session.get(IP_API, timeout=5) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 423685fe9d47f2..5653523b677b45 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -1,4 +1,6 @@ """Logging utilities.""" +from __future__ import annotations + import asyncio from functools import partial, wraps import inspect @@ -6,7 +8,7 @@ import logging.handlers import queue import traceback -from typing import Any, Awaitable, Callable, Coroutine, Union, cast, overload +from typing import Any, Awaitable, Callable, Coroutine, cast, overload from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.core import HomeAssistant, callback @@ -115,7 +117,7 @@ def catch_log_exception( def catch_log_exception( func: Callable[..., Any], format_err: Callable[..., Any], *args: Any -) -> Union[Callable[..., None], Callable[..., Awaitable[None]]]: +) -> Callable[..., None] | Callable[..., Awaitable[None]]: """Decorate a callback to catch and log exceptions.""" # Check for partials to properly determine if coroutine function @@ -123,7 +125,7 @@ def catch_log_exception( while isinstance(check_func, partial): check_func = check_func.func - wrapper_func: Union[Callable[..., None], Callable[..., Awaitable[None]]] + wrapper_func: Callable[..., None] | Callable[..., Awaitable[None]] if asyncio.iscoroutinefunction(check_func): async_func = cast(Callable[..., Awaitable[None]], func) diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py index 94b43ad78033b5..c36e7f3793ad74 100644 --- a/homeassistant/util/network.py +++ b/homeassistant/util/network.py @@ -1,6 +1,7 @@ """Network utilities.""" +from __future__ import annotations + from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network -from typing import Union import yarl @@ -23,22 +24,22 @@ LINK_LOCAL_NETWORK = ip_network("169.254.0.0/16") -def is_loopback(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_loopback(address: IPv4Address | IPv6Address) -> bool: """Check if an address is a loopback address.""" return any(address in network for network in LOOPBACK_NETWORKS) -def is_private(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_private(address: IPv4Address | IPv6Address) -> bool: """Check if an address is a private address.""" return any(address in network for network in PRIVATE_NETWORKS) -def is_link_local(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_link_local(address: IPv4Address | IPv6Address) -> bool: """Check if an address is link local.""" return address in LINK_LOCAL_NETWORK -def is_local(address: Union[IPv4Address, IPv6Address]) -> bool: +def is_local(address: IPv4Address | IPv6Address) -> bool: """Check if an address is loopback or private.""" return is_loopback(address) or is_private(address) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 34628b4ca4d30c..99afcd0fcf8713 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,4 +1,6 @@ """Helpers to install PyPi packages.""" +from __future__ import annotations + import asyncio from importlib.metadata import PackageNotFoundError, version import logging @@ -6,7 +8,6 @@ from pathlib import Path from subprocess import PIPE, Popen import sys -from typing import Optional from urllib.parse import urlparse import pkg_resources @@ -59,10 +60,10 @@ def is_installed(package: str) -> bool: def install_package( package: str, upgrade: bool = True, - target: Optional[str] = None, - constraints: Optional[str] = None, - find_links: Optional[str] = None, - no_cache_dir: Optional[bool] = False, + target: str | None = None, + constraints: str | None = None, + find_links: str | None = None, + no_cache_dir: bool | None = False, ) -> bool: """Install a package on PyPi. Accepts pip compatible package strings. diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index 949af7dbb3235c..c257ca2268c549 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -1,9 +1,8 @@ """Percentage util functions.""" +from __future__ import annotations -from typing import List, Tuple - -def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: +def ordered_list_item_to_percentage(ordered_list: list[str], item: str) -> int: """Determine the percentage of an item in an ordered list. When using this utility for fan speeds, do not include "off" @@ -26,7 +25,7 @@ def ordered_list_item_to_percentage(ordered_list: List[str], item: str) -> int: return (list_position * 100) // list_len -def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> str: +def percentage_to_ordered_list_item(ordered_list: list[str], percentage: int) -> str: """Find the item that most closely matches the percentage in an ordered list. When using this utility for fan speeds, do not include "off" @@ -54,7 +53,7 @@ def percentage_to_ordered_list_item(ordered_list: List[str], percentage: int) -> def ranged_value_to_percentage( - low_high_range: Tuple[float, float], value: float + low_high_range: tuple[float, float], value: float ) -> int: """Given a range of low and high values convert a single value to a percentage. @@ -71,7 +70,7 @@ def ranged_value_to_percentage( def percentage_to_ranged_value( - low_high_range: Tuple[float, float], percentage: int + low_high_range: tuple[float, float], percentage: int ) -> float: """Given a range of low and high values convert a percentage to a single value. @@ -87,11 +86,11 @@ def percentage_to_ranged_value( return states_in_range(low_high_range) * percentage / 100 -def states_in_range(low_high_range: Tuple[float, float]) -> float: +def states_in_range(low_high_range: tuple[float, float]) -> float: """Given a range of low and high values return how many states exist.""" return low_high_range[1] - low_high_range[0] + 1 -def int_states_in_range(low_high_range: Tuple[float, float]) -> int: +def int_states_in_range(low_high_range: tuple[float, float]) -> int: """Given a range of low and high values return how many integer states exist.""" return int(states_in_range(low_high_range)) diff --git a/homeassistant/util/pil.py b/homeassistant/util/pil.py index 80c11c9c4104c0..7caeac15458aa5 100644 --- a/homeassistant/util/pil.py +++ b/homeassistant/util/pil.py @@ -2,18 +2,18 @@ Can only be used by integrations that have pillow in their requirements. """ -from typing import Tuple +from __future__ import annotations from PIL import ImageDraw def draw_box( draw: ImageDraw, - box: Tuple[float, float, float, float], + box: tuple[float, float, float, float], img_width: int, img_height: int, text: str = "", - color: Tuple[int, int, int] = (255, 255, 0), + color: tuple[int, int, int] = (255, 255, 0), ) -> None: """ Draw a bounding box on and image. diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index 496ca377936cd4..91aad5f381cb77 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -1,9 +1,11 @@ """ruamel.yaml utility functions.""" +from __future__ import annotations + from collections import OrderedDict import logging import os from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result -from typing import Dict, List, Optional, Union +from typing import Dict, List, Union import ruamel.yaml from ruamel.yaml import YAML # type: ignore @@ -22,7 +24,7 @@ class ExtSafeConstructor(SafeConstructor): """Extended SafeConstructor.""" - name: Optional[str] = None + name: str | None = None class UnsupportedYamlError(HomeAssistantError): @@ -77,7 +79,7 @@ def yaml_to_object(data: str) -> JSON_TYPE: """Create object from yaml string.""" yaml = YAML(typ="rt") try: - result: Union[List, Dict, str] = yaml.load(data) + result: list | dict | str = yaml.load(data) return result except YAMLError as exc: _LOGGER.error("YAML error: %s", exc) diff --git a/homeassistant/util/timeout.py b/homeassistant/util/timeout.py index d8fc3e48fe6c26..64208d775ea015 100644 --- a/homeassistant/util/timeout.py +++ b/homeassistant/util/timeout.py @@ -8,7 +8,7 @@ import asyncio import enum from types import TracebackType -from typing import Any, Dict, List, Optional, Type, Union +from typing import Any from .async_ import run_callback_threadsafe @@ -38,10 +38,10 @@ async def __aenter__(self) -> _GlobalFreezeContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._exit() return None @@ -51,10 +51,10 @@ def __enter__(self) -> _GlobalFreezeContext: def __exit__( # pylint: disable=useless-return self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._loop.call_soon_threadsafe(self._exit) return None @@ -106,10 +106,10 @@ async def __aenter__(self) -> _ZoneFreezeContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._exit() return None @@ -119,10 +119,10 @@ def __enter__(self) -> _ZoneFreezeContext: def __exit__( # pylint: disable=useless-return self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._loop.call_soon_threadsafe(self._exit) return None @@ -155,8 +155,8 @@ def __init__( self._manager: TimeoutManager = manager self._task: asyncio.Task[Any] = task self._time_left: float = timeout - self._expiration_time: Optional[float] = None - self._timeout_handler: Optional[asyncio.Handle] = None + self._expiration_time: float | None = None + self._timeout_handler: asyncio.Handle | None = None self._wait_zone: asyncio.Event = asyncio.Event() self._state: _State = _State.INIT self._cool_down: float = cool_down @@ -169,10 +169,10 @@ async def __aenter__(self) -> _GlobalTaskContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._stop_timer() self._manager.global_tasks.remove(self) @@ -263,8 +263,8 @@ def __init__( self._task: asyncio.Task[Any] = task self._state: _State = _State.INIT self._time_left: float = timeout - self._expiration_time: Optional[float] = None - self._timeout_handler: Optional[asyncio.Handle] = None + self._expiration_time: float | None = None + self._timeout_handler: asyncio.Handle | None = None @property def state(self) -> _State: @@ -283,10 +283,10 @@ async def __aenter__(self) -> _ZoneTaskContext: async def __aexit__( self, - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_val: BaseException, exc_tb: TracebackType, - ) -> Optional[bool]: + ) -> bool | None: self._zone.exit_task(self) self._stop_timer() @@ -344,8 +344,8 @@ def __init__(self, manager: TimeoutManager, zone: str) -> None: """Initialize internal timeout context manager.""" self._manager: TimeoutManager = manager self._zone: str = zone - self._tasks: List[_ZoneTaskContext] = [] - self._freezes: List[_ZoneFreezeContext] = [] + self._tasks: list[_ZoneTaskContext] = [] + self._freezes: list[_ZoneFreezeContext] = [] def __repr__(self) -> str: """Representation of a zone.""" @@ -418,9 +418,9 @@ class TimeoutManager: def __init__(self) -> None: """Initialize TimeoutManager.""" self._loop: asyncio.AbstractEventLoop = asyncio.get_running_loop() - self._zones: Dict[str, _ZoneTimeoutManager] = {} - self._globals: List[_GlobalTaskContext] = [] - self._freezes: List[_GlobalFreezeContext] = [] + self._zones: dict[str, _ZoneTimeoutManager] = {} + self._globals: list[_GlobalTaskContext] = [] + self._freezes: list[_GlobalFreezeContext] = [] @property def zones_done(self) -> bool: @@ -433,17 +433,17 @@ def freezes_done(self) -> bool: return not self._freezes @property - def zones(self) -> Dict[str, _ZoneTimeoutManager]: + def zones(self) -> dict[str, _ZoneTimeoutManager]: """Return all Zones.""" return self._zones @property - def global_tasks(self) -> List[_GlobalTaskContext]: + def global_tasks(self) -> list[_GlobalTaskContext]: """Return all global Tasks.""" return self._globals @property - def global_freezes(self) -> List[_GlobalFreezeContext]: + def global_freezes(self) -> list[_GlobalFreezeContext]: """Return all global Freezes.""" return self._freezes @@ -459,12 +459,12 @@ def drop_zone(self, zone_name: str) -> None: def async_timeout( self, timeout: float, zone_name: str = ZONE_GLOBAL, cool_down: float = 0 - ) -> Union[_ZoneTaskContext, _GlobalTaskContext]: + ) -> _ZoneTaskContext | _GlobalTaskContext: """Timeout based on a zone. For using as Async Context Manager. """ - current_task: Optional[asyncio.Task[Any]] = asyncio.current_task() + current_task: asyncio.Task[Any] | None = asyncio.current_task() assert current_task # Global Zone @@ -483,7 +483,7 @@ def async_timeout( def async_freeze( self, zone_name: str = ZONE_GLOBAL - ) -> Union[_ZoneFreezeContext, _GlobalFreezeContext]: + ) -> _ZoneFreezeContext | _GlobalFreezeContext: """Freeze all timer until job is done. For using as Async Context Manager. @@ -502,7 +502,7 @@ def async_freeze( def freeze( self, zone_name: str = ZONE_GLOBAL - ) -> Union[_ZoneFreezeContext, _GlobalFreezeContext]: + ) -> _ZoneFreezeContext | _GlobalFreezeContext: """Freeze all timer until job is done. For using as Context Manager. diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 5cba1bfeb19cf9..b5c8c38425a003 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -1,6 +1,7 @@ """Unit system helper class and methods.""" +from __future__ import annotations + from numbers import Number -from typing import Dict, Optional from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, @@ -109,7 +110,7 @@ def temperature(self, temperature: float, from_unit: str) -> float: return temperature_util.convert(temperature, from_unit, self.temperature_unit) - def length(self, length: Optional[float], from_unit: str) -> float: + def length(self, length: float | None, from_unit: str) -> float: """Convert the given length to this unit system.""" if not isinstance(length, Number): raise TypeError(f"{length!s} is not a numeric value.") @@ -119,7 +120,7 @@ def length(self, length: Optional[float], from_unit: str) -> float: length, from_unit, self.length_unit ) - def pressure(self, pressure: Optional[float], from_unit: str) -> float: + def pressure(self, pressure: float | None, from_unit: str) -> float: """Convert the given pressure to this unit system.""" if not isinstance(pressure, Number): raise TypeError(f"{pressure!s} is not a numeric value.") @@ -129,7 +130,7 @@ def pressure(self, pressure: Optional[float], from_unit: str) -> float: pressure, from_unit, self.pressure_unit ) - def volume(self, volume: Optional[float], from_unit: str) -> float: + def volume(self, volume: float | None, from_unit: str) -> float: """Convert the given volume to this unit system.""" if not isinstance(volume, Number): raise TypeError(f"{volume!s} is not a numeric value.") @@ -137,7 +138,7 @@ def volume(self, volume: Optional[float], from_unit: str) -> float: # type ignore: https://github.com/python/mypy/issues/7207 return volume_util.convert(volume, from_unit, self.volume_unit) # type: ignore - def as_dict(self) -> Dict[str, str]: + def as_dict(self) -> dict[str, str]: """Convert the unit system to a dictionary.""" return { LENGTH: self.length_unit, diff --git a/homeassistant/util/yaml/input.py b/homeassistant/util/yaml/input.py index 6282509fae2e1f..ab5948db605f52 100644 --- a/homeassistant/util/yaml/input.py +++ b/homeassistant/util/yaml/input.py @@ -1,6 +1,7 @@ """Deal with YAML input.""" +from __future__ import annotations -from typing import Any, Dict, Set +from typing import Any from .objects import Input @@ -14,14 +15,14 @@ def __init__(self, input_name: str) -> None: self.input = input -def extract_inputs(obj: Any) -> Set[str]: +def extract_inputs(obj: Any) -> set[str]: """Extract input from a structure.""" - found: Set[str] = set() + found: set[str] = set() _extract_inputs(obj, found) return found -def _extract_inputs(obj: Any, found: Set[str]) -> None: +def _extract_inputs(obj: Any, found: set[str]) -> None: """Extract input from a structure.""" if isinstance(obj, Input): found.add(obj.name) @@ -38,7 +39,7 @@ def _extract_inputs(obj: Any, found: Set[str]) -> None: return -def substitute(obj: Any, substitutions: Dict[str, Any]) -> Any: +def substitute(obj: Any, substitutions: dict[str, Any]) -> Any: """Substitute values.""" if isinstance(obj, Input): if obj.name not in substitutions: diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index b4699ed95d2602..386f14ac15736f 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -1,10 +1,12 @@ """Custom loader.""" +from __future__ import annotations + from collections import OrderedDict import fnmatch import logging import os from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, TextIO, TypeVar, Union, overload +from typing import Any, Dict, Iterator, List, TextIO, TypeVar, Union, overload import yaml @@ -27,7 +29,7 @@ class Secrets: def __init__(self, config_dir: Path): """Initialize secrets.""" self.config_dir = config_dir - self._cache: Dict[Path, Dict[str, str]] = {} + self._cache: dict[Path, dict[str, str]] = {} def get(self, requester_path: str, secret: str) -> str: """Return the value of a secret.""" @@ -55,7 +57,7 @@ def get(self, requester_path: str, secret: str) -> str: raise HomeAssistantError(f"Secret {secret} not defined") - def _load_secret_yaml(self, secret_dir: Path) -> Dict[str, str]: + def _load_secret_yaml(self, secret_dir: Path) -> dict[str, str]: """Load the secrets yaml from path.""" secret_path = secret_dir / SECRET_YAML @@ -90,7 +92,7 @@ def _load_secret_yaml(self, secret_dir: Path) -> Dict[str, str]: class SafeLineLoader(yaml.SafeLoader): """Loader class that keeps track of line numbers.""" - def __init__(self, stream: Any, secrets: Optional[Secrets] = None) -> None: + def __init__(self, stream: Any, secrets: Secrets | None = None) -> None: """Initialize a safe line loader.""" super().__init__(stream) self.secrets = secrets @@ -103,7 +105,7 @@ def compose_node(self, parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: return node -def load_yaml(fname: str, secrets: Optional[Secrets] = None) -> JSON_TYPE: +def load_yaml(fname: str, secrets: Secrets | None = None) -> JSON_TYPE: """Load a YAML file.""" try: with open(fname, encoding="utf-8") as conf_file: @@ -113,9 +115,7 @@ def load_yaml(fname: str, secrets: Optional[Secrets] = None) -> JSON_TYPE: raise HomeAssistantError(exc) from exc -def parse_yaml( - content: Union[str, TextIO], secrets: Optional[Secrets] = None -) -> JSON_TYPE: +def parse_yaml(content: str | TextIO, secrets: Secrets | None = None) -> JSON_TYPE: """Load a YAML file.""" try: # If configuration file is empty YAML returns None @@ -131,14 +131,14 @@ def parse_yaml( @overload def _add_reference( - obj: Union[list, NodeListClass], loader: SafeLineLoader, node: yaml.nodes.Node + obj: list | NodeListClass, loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeListClass: ... @overload def _add_reference( - obj: Union[str, NodeStrClass], loader: SafeLineLoader, node: yaml.nodes.Node + obj: str | NodeStrClass, loader: SafeLineLoader, node: yaml.nodes.Node ) -> NodeStrClass: ... @@ -223,7 +223,7 @@ def _include_dir_merge_named_yaml( def _include_dir_list_yaml( loader: SafeLineLoader, node: yaml.nodes.Node -) -> List[JSON_TYPE]: +) -> list[JSON_TYPE]: """Load multiple files from directory as a list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) return [ @@ -238,7 +238,7 @@ def _include_dir_merge_list_yaml( ) -> JSON_TYPE: """Load multiple files from directory as a merged list.""" loc: str = os.path.join(os.path.dirname(loader.name), node.value) - merged_list: List[JSON_TYPE] = [] + merged_list: list[JSON_TYPE] = [] for fname in _find_files(loc, "*.yaml"): if os.path.basename(fname) == SECRET_YAML: continue @@ -253,7 +253,7 @@ def _ordered_dict(loader: SafeLineLoader, node: yaml.nodes.MappingNode) -> Order loader.flatten_mapping(node) nodes = loader.construct_pairs(node) - seen: Dict = {} + seen: dict = {} for (key, _), (child_node, _) in zip(nodes, node.value): line = child_node.start_mark.line From dd56cc801060291d75184390bb0a522f3e546bf4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Mar 2021 11:47:42 -1000 Subject: [PATCH 1329/1818] Fix rest sensor data misalignment with multiple sensors (#48043) If there were multiple rest data sources, the index needed to be incremented by type instead of by data source/type --- homeassistant/components/rest/__init__.py | 9 ++- tests/components/rest/test_init.py | 75 +++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index 233a12f44c8b3a..26e8fde57e0e36 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -77,6 +77,7 @@ async def _async_process_config(hass, config) -> bool: refresh_tasks = [] load_tasks = [] + platform_idxs = {} for rest_idx, conf in enumerate(config[DOMAIN]): scan_interval = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) resource_template = conf.get(CONF_RESOURCE_TEMPLATE) @@ -91,7 +92,13 @@ async def _async_process_config(hass, config) -> bool: if platform_domain not in conf: continue - for platform_idx, platform_conf in enumerate(conf[platform_domain]): + for platform_conf in conf[platform_domain]: + if platform_domain not in platform_idxs: + platform_idxs[platform_domain] = 0 + else: + platform_idxs[platform_domain] += 1 + platform_idx = platform_idxs[platform_domain] + hass.data[DOMAIN][platform_domain][platform_idx] = platform_conf load = discovery.async_load_platform( diff --git a/tests/components/rest/test_init.py b/tests/components/rest/test_init.py index 19a5651e9898f5..2902addca0c800 100644 --- a/tests/components/rest/test_init.py +++ b/tests/components/rest/test_init.py @@ -338,3 +338,78 @@ async def test_reload_fails_to_read_configuration(hass): def _get_fixtures_base_path(): return path.dirname(path.dirname(path.dirname(__file__))) + + +@respx.mock +async def test_multiple_rest_endpoints(hass): + """Test multiple rest endpoints.""" + + respx.get("http://date.jsontest.com").respond( + status_code=200, + json={ + "date": "03-17-2021", + "milliseconds_since_epoch": 1616008268573, + "time": "07:11:08 PM", + }, + ) + + respx.get("http://time.jsontest.com").respond( + status_code=200, + json={ + "date": "03-17-2021", + "milliseconds_since_epoch": 1616008299665, + "time": "07:11:39 PM", + }, + ) + respx.get("http://localhost").respond( + status_code=200, + json={ + "value": "1", + }, + ) + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + { + "resource": "http://date.jsontest.com", + "sensor": [ + { + "name": "JSON Date", + "value_template": "{{ value_json.date }}", + }, + { + "name": "JSON Date Time", + "value_template": "{{ value_json.time }}", + }, + ], + }, + { + "resource": "http://time.jsontest.com", + "sensor": [ + { + "name": "JSON Time", + "value_template": "{{ value_json.time }}", + }, + ], + }, + { + "resource": "http://localhost", + "binary_sensor": [ + { + "name": "Binary Sensor", + "value_template": "{{ value_json.value }}", + }, + ], + }, + ] + }, + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 4 + + assert hass.states.get("sensor.json_date").state == "03-17-2021" + assert hass.states.get("sensor.json_date_time").state == "07:11:08 PM" + assert hass.states.get("sensor.json_time").state == "07:11:39 PM" + assert hass.states.get("binary_sensor.binary_sensor").state == "on" From 02619ca2cd8bc4c7250eabbee28f83d123005698 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 17 Mar 2021 18:50:21 -0300 Subject: [PATCH 1330/1818] Add service schema for ESPHome api services (#47426) --- homeassistant/components/esphome/__init__.py | 73 +++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index dee0813007cec2..650b630e34c878 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -22,6 +22,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, + CONF_MODE, CONF_PASSWORD, CONF_PORT, EVENT_HOMEASSISTANT_STOP, @@ -35,6 +36,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.json import JSONEncoder +from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.storage import Store from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -308,17 +310,63 @@ async def _register_service( ): service_name = f"{entry_data.device_info.name}_{service.name}" schema = {} + fields = {} + for arg in service.args: - schema[vol.Required(arg.name)] = { - UserServiceArgType.BOOL: cv.boolean, - UserServiceArgType.INT: vol.Coerce(int), - UserServiceArgType.FLOAT: vol.Coerce(float), - UserServiceArgType.STRING: cv.string, - UserServiceArgType.BOOL_ARRAY: [cv.boolean], - UserServiceArgType.INT_ARRAY: [vol.Coerce(int)], - UserServiceArgType.FLOAT_ARRAY: [vol.Coerce(float)], - UserServiceArgType.STRING_ARRAY: [cv.string], + metadata = { + UserServiceArgType.BOOL: { + "validator": cv.boolean, + "example": "False", + "selector": {"boolean": None}, + }, + UserServiceArgType.INT: { + "validator": vol.Coerce(int), + "example": "42", + "selector": {"number": {CONF_MODE: "box"}}, + }, + UserServiceArgType.FLOAT: { + "validator": vol.Coerce(float), + "example": "12.3", + "selector": {"number": {CONF_MODE: "box", "step": 1e-3}}, + }, + UserServiceArgType.STRING: { + "validator": cv.string, + "example": "Example text", + "selector": {"text": None}, + }, + UserServiceArgType.BOOL_ARRAY: { + "validator": [cv.boolean], + "description": "A list of boolean values.", + "example": "[True, False]", + "selector": {"object": {}}, + }, + UserServiceArgType.INT_ARRAY: { + "validator": [vol.Coerce(int)], + "description": "A list of integer values.", + "example": "[42, 34]", + "selector": {"object": {}}, + }, + UserServiceArgType.FLOAT_ARRAY: { + "validator": [vol.Coerce(float)], + "description": "A list of floating point numbers.", + "example": "[ 12.3, 34.5 ]", + "selector": {"object": {}}, + }, + UserServiceArgType.STRING_ARRAY: { + "validator": [cv.string], + "description": "A list of strings.", + "example": "['Example text', 'Another example']", + "selector": {"object": {}}, + }, }[arg.type_] + schema[vol.Required(arg.name)] = metadata["validator"] + fields[arg.name] = { + "name": arg.name, + "required": True, + "description": metadata.get("description"), + "example": metadata["example"], + "selector": metadata["selector"], + } async def execute_service(call): await entry_data.client.execute_service(service, call.data) @@ -327,6 +375,13 @@ async def execute_service(call): DOMAIN, service_name, execute_service, vol.Schema(schema) ) + service_desc = { + "description": f"Calls the service {service.name} of the node {entry_data.device_info.name}", + "fields": fields, + } + + async_set_service_schema(hass, DOMAIN, service_name, service_desc) + async def _setup_services( hass: HomeAssistantType, entry_data: RuntimeEntryData, services: List[UserService] From 76199c0eb23ab7612152cfb17b656c68a787f195 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:34:25 +0100 Subject: [PATCH 1331/1818] Update typing 04 (#48037) --- .../components/acmeda/config_flow.py | 5 +-- homeassistant/components/acmeda/hub.py | 5 +-- .../alarm_control_panel/device_action.py | 6 ++-- .../alarm_control_panel/device_condition.py | 4 +-- .../alarm_control_panel/device_trigger.py | 4 +-- .../alarm_control_panel/reproduce_state.py | 12 ++++--- .../components/alert/reproduce_state.py | 12 ++++--- .../components/alexa/capabilities.py | 7 ++-- homeassistant/components/alexa/entities.py | 8 +++-- .../components/alexa/state_report.py | 7 ++-- homeassistant/components/almond/__init__.py | 5 +-- .../components/arcam_fmj/device_trigger.py | 4 +-- .../arris_tg2492lg/device_tracker.py | 4 +-- .../components/asuswrt/device_tracker.py | 6 ++-- homeassistant/components/asuswrt/router.py | 24 +++++++------ homeassistant/components/asuswrt/sensor.py | 9 ++--- homeassistant/components/atag/climate.py | 18 +++++----- homeassistant/components/auth/__init__.py | 5 +-- .../components/automation/__init__.py | 26 +++++++------- .../components/automation/reproduce_state.py | 12 ++++--- homeassistant/components/automation/trace.py | 32 +++++++++-------- homeassistant/components/awair/__init__.py | 5 +-- homeassistant/components/awair/config_flow.py | 7 ++-- homeassistant/components/awair/sensor.py | 11 +++--- .../components/azure_devops/__init__.py | 6 ++-- .../components/azure_devops/sensor.py | 5 +-- .../components/azure_event_hub/__init__.py | 6 ++-- .../components/bbox/device_tracker.py | 5 +-- .../binary_sensor/device_condition.py | 6 ++-- .../binary_sensor/significant_change.py | 6 ++-- .../components/blueprint/importer.py | 7 ++-- homeassistant/components/blueprint/models.py | 18 +++++----- .../components/blueprint/websocket_api.py | 8 ++--- .../bluetooth_tracker/device_tracker.py | 13 +++---- homeassistant/components/bond/config_flow.py | 18 +++++----- homeassistant/components/bond/cover.py | 14 ++++---- homeassistant/components/bond/entity.py | 14 ++++---- homeassistant/components/bond/fan.py | 24 +++++++------ homeassistant/components/bond/light.py | 36 ++++++++++--------- homeassistant/components/bond/switch.py | 10 +++--- homeassistant/components/bond/utils.py | 36 ++++++++++--------- homeassistant/components/bsblan/climate.py | 14 ++++---- .../components/bsblan/config_flow.py | 16 +++++---- homeassistant/components/buienradar/camera.py | 11 +++--- 44 files changed, 282 insertions(+), 229 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index f421fa9ca25329..b8913e31f2adb7 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Rollease Acmeda Automate Pulse Hub.""" +from __future__ import annotations + import asyncio -from typing import Dict, Optional import aiopulse import async_timeout @@ -19,7 +20,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the config flow.""" - self.discovered_hubs: Optional[Dict[str, aiopulse.Hub]] = None + self.discovered_hubs: dict[str, aiopulse.Hub] | None = None async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" diff --git a/homeassistant/components/acmeda/hub.py b/homeassistant/components/acmeda/hub.py index 0b74b874dcc838..e156ee5cb7812e 100644 --- a/homeassistant/components/acmeda/hub.py +++ b/homeassistant/components/acmeda/hub.py @@ -1,6 +1,7 @@ """Code to handle a Pulse Hub.""" +from __future__ import annotations + import asyncio -from typing import Optional import aiopulse @@ -17,7 +18,7 @@ def __init__(self, hass, config_entry): """Initialize the system.""" self.config_entry = config_entry self.hass = hass - self.api: Optional[aiopulse.Hub] = None + self.api: aiopulse.Hub | None = None self.tasks = [] self.current_rollers = {} self.cleanup_callbacks = [] diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 0dc16fdcf42275..67637550db2710 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Alarm control panel.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -41,7 +41,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -109,7 +109,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/alarm_control_panel/device_condition.py b/homeassistant/components/alarm_control_panel/device_condition.py index e5b3ec6aeee960..3817cf37b451fb 100644 --- a/homeassistant/components/alarm_control_panel/device_condition.py +++ b/homeassistant/components/alarm_control_panel/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Alarm control panel.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -58,7 +58,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index 5669340c2ce5f2..9ab28e3e863662 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Alarm control panel.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -48,7 +48,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/alarm_control_panel/reproduce_state.py b/homeassistant/components/alarm_control_panel/reproduce_state.py index 9e7d8e6f1a76a1..3021d4421d969e 100644 --- a/homeassistant/components/alarm_control_panel/reproduce_state.py +++ b/homeassistant/components/alarm_control_panel/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Alarm control panel state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -39,8 +41,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -83,8 +85,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Alarm control panel states.""" await asyncio.gather( diff --git a/homeassistant/components/alert/reproduce_state.py b/homeassistant/components/alert/reproduce_state.py index 7645b642d59500..de40649854eda4 100644 --- a/homeassistant/components/alert/reproduce_state.py +++ b/homeassistant/components/alert/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Alert state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -61,8 +63,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Alert states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index acfba91a933aeb..69acf95e207333 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1,6 +1,7 @@ """Alexa capabilities.""" +from __future__ import annotations + import logging -from typing import List, Optional from homeassistant.components import ( cover, @@ -72,7 +73,7 @@ class AlexaCapability: supported_locales = {"en-US"} - def __init__(self, entity: State, instance: Optional[str] = None): + def __init__(self, entity: State, instance: str | None = None): """Initialize an Alexa capability.""" self.entity = entity self.instance = instance @@ -82,7 +83,7 @@ def name(self) -> str: raise NotImplementedError @staticmethod - def properties_supported() -> List[dict]: + def properties_supported() -> list[dict]: """Return what properties this entity supports.""" return [] diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index c05d9641b9ae63..71f4e41a810ef2 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -1,6 +1,8 @@ """Alexa entity adapters.""" +from __future__ import annotations + import logging -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING from homeassistant.components import ( alarm_control_panel, @@ -300,7 +302,7 @@ def get_interface(self, capability) -> AlexaCapability: Raises _UnsupportedInterface. """ - def interfaces(self) -> List[AlexaCapability]: + def interfaces(self) -> list[AlexaCapability]: """Return a list of supported interfaces. Used for discovery. The list should contain AlexaInterface instances. @@ -353,7 +355,7 @@ def serialize_discovery(self): @callback -def async_get_entities(hass, config) -> List[AlexaEntity]: +def async_get_entities(hass, config) -> list[AlexaEntity]: """Return all entities that are supported by Alexa.""" entities = [] for state in hass.states.async_all(): diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index c34dc34f0dd224..712a08ac6b9ea7 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -1,8 +1,9 @@ """Alexa state report code.""" +from __future__ import annotations + import asyncio import json import logging -from typing import Optional import aiohttp import async_timeout @@ -45,8 +46,8 @@ def extra_significant_check( async def async_entity_state_listener( changed_entity: str, - old_state: Optional[State], - new_state: Optional[State], + old_state: State | None, + new_state: State | None, ): if not hass.is_running: return diff --git a/homeassistant/components/almond/__init__.py b/homeassistant/components/almond/__init__.py index b9f75ff8c6b4fc..554a4aa47bcf8a 100644 --- a/homeassistant/components/almond/__init__.py +++ b/homeassistant/components/almond/__init__.py @@ -1,9 +1,10 @@ """Support for Almond.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import time -from typing import Optional from aiohttp import ClientError, ClientSession import async_timeout @@ -281,7 +282,7 @@ async def async_set_onboarding(self, shown): return True async def async_process( - self, text: str, context: Context, conversation_id: Optional[str] = None + self, text: str, context: Context, conversation_id: str | None = None ) -> intent.IntentResponse: """Process a sentence.""" response = await self.api.async_converse_text(text, conversation_id) diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index c03a082c149f44..060c56e59534b1 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Arcam FMJ Receiver control.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Arcam FMJ Receiver control devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/arris_tg2492lg/device_tracker.py b/homeassistant/components/arris_tg2492lg/device_tracker.py index e63bef9c108564..1011d76f8aa128 100644 --- a/homeassistant/components/arris_tg2492lg/device_tracker.py +++ b/homeassistant/components/arris_tg2492lg/device_tracker.py @@ -1,5 +1,5 @@ """Support for Arris TG2492LG router.""" -from typing import List +from __future__ import annotations from arris_tg2492lg import ConnectBox, Device import voluptuous as vol @@ -36,7 +36,7 @@ class ArrisDeviceScanner(DeviceScanner): def __init__(self, connect_box: ConnectBox): """Initialize the scanner.""" self.connect_box = connect_box - self.last_results: List[Device] = [] + self.last_results: list[Device] = [] def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index f5cb9b934e30ab..1ef68f9d65d1c5 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -1,5 +1,5 @@ """Support for ASUSWRT routers.""" -from typing import Dict +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -103,12 +103,12 @@ def icon(self) -> str: return self._icon @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 4c92ee2ef6771b..550e4c8fc169b4 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -1,7 +1,9 @@ """Represent the AsusWrt router.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Any, Dict, Optional +from typing import Any from aioasuswrt.asuswrt import AsusWrt @@ -74,7 +76,7 @@ async def _get_connected_devices(self): async def _get_bytes(self): """Fetch byte information from the router.""" - ret_dict: Dict[str, Any] = {} + ret_dict: dict[str, Any] = {} try: datas = await self._api.async_get_bytes_total() except OSError as exc: @@ -87,7 +89,7 @@ async def _get_bytes(self): async def _get_rates(self): """Fetch rates information from the router.""" - ret_dict: Dict[str, Any] = {} + ret_dict: dict[str, Any] = {} try: rates = await self._api.async_get_current_transfer_rates() except OSError as exc: @@ -194,12 +196,12 @@ def __init__(self, hass: HomeAssistantType, entry: ConfigEntry) -> None: self._protocol = entry.data[CONF_PROTOCOL] self._host = entry.data[CONF_HOST] - self._devices: Dict[str, Any] = {} + self._devices: dict[str, Any] = {} self._connected_devices = 0 self._connect_error = False self._sensors_data_handler: AsusWrtSensorDataHandler = None - self._sensors_coordinator: Dict[str, Any] = {} + self._sensors_coordinator: dict[str, Any] = {} self._on_close = [] @@ -245,7 +247,7 @@ async def setup(self) -> None: async_track_time_interval(self.hass, self.update_all, SCAN_INTERVAL) ) - async def update_all(self, now: Optional[datetime] = None) -> None: + async def update_all(self, now: datetime | None = None) -> None: """Update all AsusWrt platforms.""" await self.update_devices() @@ -353,7 +355,7 @@ def async_on_close(self, func: CALLBACK_TYPE) -> None: """Add a function to call when router is closed.""" self._on_close.append(func) - def update_options(self, new_options: Dict) -> bool: + def update_options(self, new_options: dict) -> bool: """Update router options.""" req_reload = False for name, new_opt in new_options.items(): @@ -367,7 +369,7 @@ def update_options(self, new_options: Dict) -> bool: return req_reload @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device information.""" return { "identifiers": {(DOMAIN, "AsusWRT")}, @@ -392,12 +394,12 @@ def host(self) -> str: return self._host @property - def devices(self) -> Dict[str, Any]: + def devices(self) -> dict[str, Any]: """Return devices.""" return self._devices @property - def sensors_coordinator(self) -> Dict[str, Any]: + def sensors_coordinator(self) -> dict[str, Any]: """Return sensors coordinators.""" return self._sensors_coordinator @@ -407,7 +409,7 @@ def api(self) -> AsusWrt: return self._api -def get_api(conf: Dict, options: Optional[Dict] = None) -> AsusWrt: +def get_api(conf: dict, options: dict | None = None) -> AsusWrt: """Get the AsusWrt API.""" opt = options or {} diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 0cd427d3b6406f..7dc8208ee679a3 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -1,7 +1,8 @@ """Asuswrt status sensors.""" +from __future__ import annotations + import logging from numbers import Number -from typing import Dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND @@ -104,7 +105,7 @@ def __init__( coordinator: DataUpdateCoordinator, router: AsusWrtRouter, sensor_type: str, - sensor: Dict[str, any], + sensor: dict[str, any], ) -> None: """Initialize a AsusWrt sensor.""" super().__init__(coordinator) @@ -159,11 +160,11 @@ def device_class(self) -> str: return self._device_class @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return {"hostname": self._router.host} @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return self._router.device_info diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index a2aa5cf16e476d..da7e6a14a73ef6 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -1,5 +1,5 @@ """Initialization of ATAG One climate platform.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -43,46 +43,46 @@ def supported_features(self): return SUPPORT_FLAGS @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return hvac operation ie. heat, cool mode.""" if self.coordinator.data.climate.hvac_mode in HVAC_MODES: return self.coordinator.data.climate.hvac_mode return None @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return HVAC_MODES @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" is_active = self.coordinator.data.climate.status return CURRENT_HVAC_HEAT if is_active else CURRENT_HVAC_IDLE @property - def temperature_unit(self) -> Optional[str]: + def temperature_unit(self) -> str | None: """Return the unit of measurement.""" return self.coordinator.data.climate.temp_unit @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.coordinator.data.climate.temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self.coordinator.data.climate.target_temperature @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, manual, fireplace, extend, etc.""" preset = self.coordinator.data.climate.preset_mode return PRESET_INVERTED.get(preset) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return list(PRESET_MAP.keys()) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 4ddf82cc022108..7381be5e9de613 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -114,8 +114,9 @@ } """ +from __future__ import annotations + from datetime import timedelta -from typing import Union import uuid from aiohttp import web @@ -183,7 +184,7 @@ @bind_hass def create_auth_code( - hass, client_id: str, credential_or_user: Union[Credentials, User] + hass, client_id: str, credential_or_user: Credentials | User ) -> str: """Create an authorization code to fetch tokens.""" return hass.data[DOMAIN](client_id, credential_or_user) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index b769b0329cce7c..425ffe17979a60 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,6 +1,8 @@ """Allow to set up simple automation rules via the config file.""" +from __future__ import annotations + import logging -from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Union, cast +from typing import Any, Awaitable, Callable, Dict, cast import voluptuous as vol from voluptuous.humanize import humanize_error @@ -109,7 +111,7 @@ def is_on(hass, entity_id): @callback -def automations_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: +def automations_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all automations that reference the entity.""" if DOMAIN not in hass.data: return [] @@ -124,7 +126,7 @@ def automations_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def entities_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: +def entities_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all entities in a scene.""" if DOMAIN not in hass.data: return [] @@ -140,7 +142,7 @@ def entities_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def automations_with_device(hass: HomeAssistant, device_id: str) -> List[str]: +def automations_with_device(hass: HomeAssistant, device_id: str) -> list[str]: """Return all automations that reference the device.""" if DOMAIN not in hass.data: return [] @@ -155,7 +157,7 @@ def automations_with_device(hass: HomeAssistant, device_id: str) -> List[str]: @callback -def devices_in_automation(hass: HomeAssistant, entity_id: str) -> List[str]: +def devices_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all devices in a scene.""" if DOMAIN not in hass.data: return [] @@ -249,8 +251,8 @@ def __init__( self.action_script.change_listener = self.async_write_ha_state self._initial_state = initial_state self._is_enabled = False - self._referenced_entities: Optional[Set[str]] = None - self._referenced_devices: Optional[Set[str]] = None + self._referenced_entities: set[str] | None = None + self._referenced_devices: set[str] | None = None self._logger = LOGGER self._variables: ScriptVariables = variables self._trigger_variables: ScriptVariables = trigger_variables @@ -509,7 +511,7 @@ async def async_disable(self, stop_actions=DEFAULT_STOP_ACTIONS): async def _async_attach_triggers( self, home_assistant_start: bool - ) -> Optional[Callable[[], None]]: + ) -> Callable[[], None] | None: """Set up the triggers.""" def log_cb(level, msg, **kwargs): @@ -539,7 +541,7 @@ def log_cb(level, msg, **kwargs): async def _async_process_config( hass: HomeAssistant, - config: Dict[str, Any], + config: dict[str, Any], component: EntityComponent, ) -> bool: """Process config and add automations. @@ -550,7 +552,7 @@ async def _async_process_config( blueprints_used = False for config_key in extract_domain_configs(config, DOMAIN): - conf: List[Union[Dict[str, Any], blueprint.BlueprintInputs]] = config[ # type: ignore + conf: list[dict[str, Any] | blueprint.BlueprintInputs] = config[ # type: ignore config_key ] @@ -680,7 +682,7 @@ def if_action(variables=None): @callback -def _trigger_extract_device(trigger_conf: dict) -> Optional[str]: +def _trigger_extract_device(trigger_conf: dict) -> str | None: """Extract devices from a trigger config.""" if trigger_conf[CONF_PLATFORM] != "device": return None @@ -689,7 +691,7 @@ def _trigger_extract_device(trigger_conf: dict) -> Optional[str]: @callback -def _trigger_extract_entities(trigger_conf: dict) -> List[str]: +def _trigger_extract_entities(trigger_conf: dict) -> list[str]: """Extract entities from a trigger config.""" if trigger_conf[CONF_PLATFORM] in ("state", "numeric_state"): return trigger_conf[CONF_ENTITY_ID] diff --git a/homeassistant/components/automation/reproduce_state.py b/homeassistant/components/automation/reproduce_state.py index bcd0cc4e58570a..efe83960f4193f 100644 --- a/homeassistant/components/automation/reproduce_state.py +++ b/homeassistant/components/automation/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Automation state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Automation states.""" await asyncio.gather( diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 68cca5a4a411ec..79fa4c844bc4e4 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -1,11 +1,13 @@ """Trace support for automation.""" +from __future__ import annotations + from collections import OrderedDict from contextlib import contextmanager import datetime as dt from datetime import timedelta from itertools import count import logging -from typing import Any, Awaitable, Callable, Deque, Dict, Optional +from typing import Any, Awaitable, Callable, Deque from homeassistant.core import Context, HomeAssistant, callback from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder @@ -30,28 +32,28 @@ class AutomationTrace: def __init__( self, - unique_id: Optional[str], - config: Dict[str, Any], + unique_id: str | None, + config: dict[str, Any], context: Context, ): """Container for automation trace.""" - self._action_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._condition_trace: Optional[Dict[str, Deque[TraceElement]]] = None - self._config: Dict[str, Any] = config + self._action_trace: dict[str, Deque[TraceElement]] | None = None + self._condition_trace: dict[str, Deque[TraceElement]] | None = None + self._config: dict[str, Any] = config self.context: Context = context - self._error: Optional[Exception] = None + self._error: Exception | None = None self._state: str = "running" self.run_id: str = str(next(self._run_ids)) - self._timestamp_finish: Optional[dt.datetime] = None + self._timestamp_finish: dt.datetime | None = None self._timestamp_start: dt.datetime = dt_util.utcnow() - self._unique_id: Optional[str] = unique_id - self._variables: Optional[Dict[str, Any]] = None + self._unique_id: str | None = unique_id + self._variables: dict[str, Any] | None = None - def set_action_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: """Set action trace.""" self._action_trace = trace - def set_condition_trace(self, trace: Dict[str, Deque[TraceElement]]) -> None: + def set_condition_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: """Set condition trace.""" self._condition_trace = trace @@ -59,7 +61,7 @@ def set_error(self, ex: Exception) -> None: """Set error.""" self._error = ex - def set_variables(self, variables: Dict[str, Any]) -> None: + def set_variables(self, variables: dict[str, Any]) -> None: """Set variables.""" self._variables = variables @@ -68,7 +70,7 @@ def finished(self) -> None: self._timestamp_finish = dt_util.utcnow() self._state = "stopped" - def as_dict(self) -> Dict[str, Any]: + def as_dict(self) -> dict[str, Any]: """Return dictionary version of this AutomationTrace.""" result = self.as_short_dict() @@ -96,7 +98,7 @@ def as_dict(self) -> Dict[str, Any]: result["error"] = str(self._error) return result - def as_short_dict(self) -> Dict[str, Any]: + def as_short_dict(self) -> dict[str, Any]: """Return a brief dictionary version of this AutomationTrace.""" last_action = None diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py index 56af5d2b66293e..eefc7445d36e13 100644 --- a/homeassistant/components/awair/__init__.py +++ b/homeassistant/components/awair/__init__.py @@ -1,7 +1,8 @@ """The awair component.""" +from __future__ import annotations from asyncio import gather -from typing import Any, Optional +from typing import Any from async_timeout import timeout from python_awair import Awair @@ -70,7 +71,7 @@ def __init__(self, hass, config_entry, session) -> None: super().__init__(hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - async def _async_update_data(self) -> Optional[Any]: + async def _async_update_data(self) -> Any | None: """Update data via Awair client library.""" with timeout(API_TIMEOUT): try: diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index c28ac55f21660d..76c7cbca3a9b70 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -1,6 +1,5 @@ """Config flow for Awair.""" - -from typing import Optional +from __future__ import annotations from python_awair import Awair from python_awair.exceptions import AuthError, AwairError @@ -36,7 +35,7 @@ async def async_step_import(self, conf: dict): data={CONF_ACCESS_TOKEN: conf[CONF_ACCESS_TOKEN]}, ) - async def async_step_user(self, user_input: Optional[dict] = None): + async def async_step_user(self, user_input: dict | None = None): """Handle a flow initialized by the user.""" errors = {} @@ -61,7 +60,7 @@ async def async_step_user(self, user_input: Optional[dict] = None): errors=errors, ) - async def async_step_reauth(self, user_input: Optional[dict] = None): + async def async_step_reauth(self, user_input: dict | None = None): """Handle re-auth if token invalid.""" errors = {} diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index d685b3ec17b984..81dc0562fc45c2 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -1,6 +1,7 @@ """Support for Awair sensors.""" +from __future__ import annotations -from typing import Callable, List, Optional +from typing import Callable from python_awair.devices import AwairDevice import voluptuous as vol @@ -55,13 +56,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigType, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ): """Set up Awair sensor entity based on a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] sensors = [] - data: List[AwairResult] = coordinator.data.values() + data: list[AwairResult] = coordinator.data.values() for result in data: if result.air_data: sensors.append(AwairSensor(API_SCORE, result.device, coordinator)) @@ -228,9 +229,9 @@ def device_info(self) -> dict: return info @property - def _air_data(self) -> Optional[AwairResult]: + def _air_data(self) -> AwairResult | None: """Return the latest data for our device, or None.""" - result: Optional[AwairResult] = self.coordinator.data.get(self._device.uuid) + result: AwairResult | None = self.coordinator.data.get(self._device.uuid) if result: return result.air_data diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index f72a4c44918d48..b856dc5aa003a7 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -1,6 +1,8 @@ """Support for Azure DevOps.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from aioazuredevops.client import DevOpsClient import aiohttp @@ -114,7 +116,7 @@ class AzureDevOpsDeviceEntity(AzureDevOpsEntity): """Defines a Azure DevOps device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Azure DevOps instance.""" return { "identifiers": { diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index a26f0c65f9c472..1d30bfcb9f90da 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -1,7 +1,8 @@ """Support for Azure DevOps sensors.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List from aioazuredevops.builds import DevOpsBuild from aioazuredevops.client import DevOpsClient @@ -39,7 +40,7 @@ async def async_setup_entry( sensors = [] try: - builds: List[DevOpsBuild] = await client.get_builds( + builds: list[DevOpsBuild] = await client.get_builds( organization, project, BUILDS_QUERY ) except aiohttp.ClientError as exception: diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index 3b44c6423be779..0473c4ff5a7559 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -1,9 +1,11 @@ """Support for Azure Event Hubs.""" +from __future__ import annotations + import asyncio import json import logging import time -from typing import Any, Dict +from typing import Any from azure.eventhub import EventData from azure.eventhub.aio import EventHubProducerClient, EventHubSharedKeyCredential @@ -95,7 +97,7 @@ class AzureEventHub: def __init__( self, hass: HomeAssistant, - client_args: Dict[str, Any], + client_args: dict[str, Any], conn_str_client: bool, entities_filter: vol.Schema, send_interval: int, diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index 8097c11eb89b92..130d315197b5d4 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -1,8 +1,9 @@ """Support for French FAI Bouygues Bbox routers.""" +from __future__ import annotations + from collections import namedtuple from datetime import timedelta import logging -from typing import List import pybbox import voluptuous as vol @@ -47,7 +48,7 @@ def __init__(self, config): self.host = config[CONF_HOST] """Initialize the scanner.""" - self.last_results: List[Device] = [] + self.last_results: list[Device] = [] self.success_init = self._update_info() _LOGGER.info("Scanner initialized") diff --git a/homeassistant/components/binary_sensor/device_condition.py b/homeassistant/components/binary_sensor/device_condition.py index 999a62b3a80e31..8c5066342005aa 100644 --- a/homeassistant/components/binary_sensor/device_condition.py +++ b/homeassistant/components/binary_sensor/device_condition.py @@ -1,5 +1,5 @@ """Implement device conditions for binary sensor.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -205,9 +205,9 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" - conditions: List[Dict[str, str]] = [] + conditions: list[dict[str, str]] = [] entity_registry = await async_get_registry(hass) entries = [ entry diff --git a/homeassistant/components/binary_sensor/significant_change.py b/homeassistant/components/binary_sensor/significant_change.py index bc2dba04f09c88..8421483ba0cb8f 100644 --- a/homeassistant/components/binary_sensor/significant_change.py +++ b/homeassistant/components/binary_sensor/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Binary Sensor state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,7 +14,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if old_state != new_state: return True diff --git a/homeassistant/components/blueprint/importer.py b/homeassistant/components/blueprint/importer.py index 217851df980ade..99dffb114e1149 100644 --- a/homeassistant/components/blueprint/importer.py +++ b/homeassistant/components/blueprint/importer.py @@ -1,8 +1,9 @@ """Import logic for blueprint.""" +from __future__ import annotations + from dataclasses import dataclass import html import re -from typing import Optional import voluptuous as vol import yarl @@ -93,7 +94,7 @@ def _get_community_post_import_url(url: str) -> str: def _extract_blueprint_from_community_topic( url: str, topic: dict, -) -> Optional[ImportedBlueprint]: +) -> ImportedBlueprint | None: """Extract a blueprint from a community post JSON. Async friendly. @@ -136,7 +137,7 @@ def _extract_blueprint_from_community_topic( async def fetch_blueprint_from_community_post( hass: HomeAssistant, url: str -) -> Optional[ImportedBlueprint]: +) -> ImportedBlueprint | None: """Get blueprints from a community post url. Method can raise aiohttp client exceptions, vol.Invalid. diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py index 84931a04310d69..797f9bd1512b95 100644 --- a/homeassistant/components/blueprint/models.py +++ b/homeassistant/components/blueprint/models.py @@ -1,9 +1,11 @@ """Blueprint models.""" +from __future__ import annotations + import asyncio import logging import pathlib import shutil -from typing import Any, Dict, List, Optional, Union +from typing import Any from awesomeversion import AwesomeVersion import voluptuous as vol @@ -49,8 +51,8 @@ def __init__( self, data: dict, *, - path: Optional[str] = None, - expected_domain: Optional[str] = None, + path: str | None = None, + expected_domain: str | None = None, ) -> None: """Initialize a blueprint.""" try: @@ -95,7 +97,7 @@ def metadata(self) -> dict: """Return blueprint metadata.""" return self.data[CONF_BLUEPRINT] - def update_metadata(self, *, source_url: Optional[str] = None) -> None: + def update_metadata(self, *, source_url: str | None = None) -> None: """Update metadata.""" if source_url is not None: self.data[CONF_BLUEPRINT][CONF_SOURCE_URL] = source_url @@ -105,7 +107,7 @@ def yaml(self) -> str: return yaml.dump(self.data) @callback - def validate(self) -> Optional[List[str]]: + def validate(self) -> list[str] | None: """Test if the Home Assistant installation supports this blueprint. Return list of errors if not valid. @@ -126,7 +128,7 @@ class BlueprintInputs: """Inputs for a blueprint.""" def __init__( - self, blueprint: Blueprint, config_with_inputs: Dict[str, Any] + self, blueprint: Blueprint, config_with_inputs: dict[str, Any] ) -> None: """Instantiate a blueprint inputs object.""" self.blueprint = blueprint @@ -218,7 +220,7 @@ def _load_blueprint(self, blueprint_path) -> Blueprint: blueprint_data, expected_domain=self.domain, path=blueprint_path ) - def _load_blueprints(self) -> Dict[str, Union[Blueprint, BlueprintException]]: + def _load_blueprints(self) -> dict[str, Blueprint | BlueprintException]: """Load all the blueprints.""" blueprint_folder = pathlib.Path( self.hass.config.path(BLUEPRINT_FOLDER, self.domain) @@ -243,7 +245,7 @@ def _load_blueprints(self) -> Dict[str, Union[Blueprint, BlueprintException]]: async def async_get_blueprints( self, - ) -> Dict[str, Union[Blueprint, BlueprintException]]: + ) -> dict[str, Blueprint | BlueprintException]: """Get all the blueprints.""" async with self._load_lock: return await self.hass.async_add_executor_job(self._load_blueprints) diff --git a/homeassistant/components/blueprint/websocket_api.py b/homeassistant/components/blueprint/websocket_api.py index 05ae28166962af..b8a4c214a2e5d4 100644 --- a/homeassistant/components/blueprint/websocket_api.py +++ b/homeassistant/components/blueprint/websocket_api.py @@ -1,5 +1,5 @@ """Websocket API for blueprint.""" -from typing import Dict, Optional +from __future__ import annotations import async_timeout import voluptuous as vol @@ -33,7 +33,7 @@ def async_setup(hass: HomeAssistant): ) async def ws_list_blueprints(hass, connection, msg): """List available blueprints.""" - domain_blueprints: Optional[Dict[str, models.DomainBlueprints]] = hass.data.get( + domain_blueprints: dict[str, models.DomainBlueprints] | None = hass.data.get( DOMAIN, {} ) results = {} @@ -102,7 +102,7 @@ async def ws_save_blueprint(hass, connection, msg): path = msg["path"] domain = msg["domain"] - domain_blueprints: Optional[Dict[str, models.DomainBlueprints]] = hass.data.get( + domain_blueprints: dict[str, models.DomainBlueprints] | None = hass.data.get( DOMAIN, {} ) @@ -149,7 +149,7 @@ async def ws_delete_blueprint(hass, connection, msg): path = msg["path"] domain = msg["domain"] - domain_blueprints: Optional[Dict[str, models.DomainBlueprints]] = hass.data.get( + domain_blueprints: dict[str, models.DomainBlueprints] | None = hass.data.get( DOMAIN, {} ) diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 9bc2e630ba0825..f00bd672892c03 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -1,7 +1,8 @@ """Tracking for bluetooth devices.""" +from __future__ import annotations + import asyncio import logging -from typing import List, Optional, Set, Tuple import bluetooth # pylint: disable=import-error from bt_proximity import BluetoothRSSI @@ -50,7 +51,7 @@ def is_bluetooth_device(device) -> bool: return device.mac and device.mac[:3].upper() == BT_PREFIX -def discover_devices(device_id: int) -> List[Tuple[str, str]]: +def discover_devices(device_id: int) -> list[tuple[str, str]]: """Discover Bluetooth devices.""" result = bluetooth.discover_devices( duration=8, @@ -79,7 +80,7 @@ async def see_device( ) -async def get_tracking_devices(hass: HomeAssistantType) -> Tuple[Set[str], Set[str]]: +async def get_tracking_devices(hass: HomeAssistantType) -> tuple[set[str], set[str]]: """ Load all known devices. @@ -90,17 +91,17 @@ async def get_tracking_devices(hass: HomeAssistantType) -> Tuple[Set[str], Set[s devices = await async_load_config(yaml_path, hass, 0) bluetooth_devices = [device for device in devices if is_bluetooth_device(device)] - devices_to_track: Set[str] = { + devices_to_track: set[str] = { device.mac[3:] for device in bluetooth_devices if device.track } - devices_to_not_track: Set[str] = { + devices_to_not_track: set[str] = { device.mac[3:] for device in bluetooth_devices if not device.track } return devices_to_track, devices_to_not_track -def lookup_name(mac: str) -> Optional[str]: +def lookup_name(mac: str) -> str | None: """Lookup a Bluetooth device name.""" _LOGGER.debug("Scanning %s", mac) return bluetooth.lookup_name(mac, timeout=5) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index af889b803b5e1e..71ad54ee35c75e 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Bond integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional, Tuple +from typing import Any from aiohttp import ClientConnectionError, ClientResponseError from bond_api import Bond @@ -28,7 +30,7 @@ TOKEN_SCHEMA = vol.Schema({}) -async def _validate_input(data: Dict[str, Any]) -> Tuple[str, str]: +async def _validate_input(data: dict[str, Any]) -> tuple[str, str]: """Validate the user input allows us to connect.""" bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) @@ -60,7 +62,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize config flow.""" - self._discovered: Dict[str, str] = {} + self._discovered: dict[str, str] = {} async def _async_try_automatic_configure(self) -> None: """Try to auto configure the device. @@ -83,7 +85,7 @@ async def _async_try_automatic_configure(self) -> None: _, hub_name = await _validate_input(self._discovered) self._discovered[CONF_NAME] = hub_name - async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> Dict[str, Any]: # type: ignore + async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> dict[str, Any]: # type: ignore """Handle a flow initialized by zeroconf discovery.""" name: str = discovery_info[CONF_NAME] host: str = discovery_info[CONF_HOST] @@ -106,8 +108,8 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> Dict[s return await self.async_step_confirm() async def async_step_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle confirmation flow for discovered bond hub.""" errors = {} if user_input is not None: @@ -147,8 +149,8 @@ async def async_step_confirm( ) async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 0c73bdbc8f9262..60dcc4ec1f0803 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -1,5 +1,7 @@ """Support for Bond covers.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType @@ -16,14 +18,14 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond cover devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - covers: List[Entity] = [ + covers: list[Entity] = [ BondCover(hub, device, bpup_subs) for device in hub.devices if device.type == DeviceType.MOTORIZED_SHADES @@ -41,19 +43,19 @@ def __init__( """Create HA entity representing Bond cover.""" super().__init__(hub, device, bpup_subs) - self._closed: Optional[bool] = None + self._closed: bool | None = None def _apply_state(self, state: dict) -> None: cover_open = state.get("open") self._closed = True if cover_open == 0 else False if cover_open == 1 else None @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Get device class.""" return DEVICE_CLASS_SHADE @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" return self._closed diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index b56c87f692f7e4..a676d99e9ad9d6 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -1,9 +1,11 @@ """An abstract class common to all Bond entities.""" +from __future__ import annotations + from abc import abstractmethod from asyncio import Lock, TimeoutError as AsyncIOTimeoutError from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any from aiohttp import ClientError from bond_api import BPUPSubscriptions @@ -29,7 +31,7 @@ def __init__( hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions, - sub_device: Optional[str] = None, + sub_device: str | None = None, ): """Initialize entity with API and device info.""" self._hub = hub @@ -38,11 +40,11 @@ def __init__( self._sub_device = sub_device self._available = True self._bpup_subs = bpup_subs - self._update_lock: Optional[Lock] = None + self._update_lock: Lock | None = None self._initialized = False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Get unique ID for the entity.""" hub_id = self._hub.bond_id device_id = self._device_id @@ -50,7 +52,7 @@ def unique_id(self) -> Optional[str]: return f"{hub_id}_{device_id}{sub_device_id}" @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Get entity name.""" if self._sub_device: sub_device_name = self._sub_device.replace("_", " ").title() @@ -63,7 +65,7 @@ def should_poll(self) -> bool: return False @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Get a an HA device representing this Bond controlled device.""" device_info = { ATTR_NAME: self.name, diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 1c94a6f3e9a7a8..817cf0f99a2c25 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -1,7 +1,9 @@ """Support for Bond fans.""" +from __future__ import annotations + import logging import math -from typing import Any, Callable, List, Optional, Tuple +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType, Direction @@ -31,14 +33,14 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond fan devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - fans: List[Entity] = [ + fans: list[Entity] = [ BondFan(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) @@ -54,9 +56,9 @@ def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscription """Create HA entity representing Bond fan.""" super().__init__(hub, device, bpup_subs) - self._power: Optional[bool] = None - self._speed: Optional[int] = None - self._direction: Optional[int] = None + self._power: bool | None = None + self._speed: int | None = None + self._direction: int | None = None def _apply_state(self, state: dict) -> None: self._power = state.get("power") @@ -75,7 +77,7 @@ def supported_features(self) -> int: return features @property - def _speed_range(self) -> Tuple[int, int]: + def _speed_range(self) -> tuple[int, int]: """Return the range of speeds.""" return (1, self._device.props.get("max_speed", 3)) @@ -92,7 +94,7 @@ def speed_count(self) -> int: return int_states_in_range(self._speed_range) @property - def current_direction(self) -> Optional[str]: + def current_direction(self) -> str | None: """Return fan rotation direction.""" direction = None if self._direction == Direction.FORWARD: @@ -125,9 +127,9 @@ async def async_set_percentage(self, percentage: int) -> None: async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn on the fan.""" diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index d2b06012ed342f..8faab26f785715 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -1,6 +1,8 @@ """Support for Bond lights.""" +from __future__ import annotations + import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType @@ -24,14 +26,14 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond light devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - fan_lights: List[Entity] = [ + fan_lights: list[Entity] = [ BondLight(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fan(device.type) @@ -39,31 +41,31 @@ async def async_setup_entry( and not (device.supports_up_light() and device.supports_down_light()) ] - fan_up_lights: List[Entity] = [ + fan_up_lights: list[Entity] = [ BondUpLight(hub, device, bpup_subs, "up_light") for device in hub.devices if DeviceType.is_fan(device.type) and device.supports_up_light() ] - fan_down_lights: List[Entity] = [ + fan_down_lights: list[Entity] = [ BondDownLight(hub, device, bpup_subs, "down_light") for device in hub.devices if DeviceType.is_fan(device.type) and device.supports_down_light() ] - fireplaces: List[Entity] = [ + fireplaces: list[Entity] = [ BondFireplace(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_fireplace(device.type) ] - fp_lights: List[Entity] = [ + fp_lights: list[Entity] = [ BondLight(hub, device, bpup_subs, "light") for device in hub.devices if DeviceType.is_fireplace(device.type) and device.supports_light() ] - lights: List[Entity] = [ + lights: list[Entity] = [ BondLight(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_light(device.type) @@ -83,11 +85,11 @@ def __init__( hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions, - sub_device: Optional[str] = None, + sub_device: str | None = None, ): """Create HA entity representing Bond light.""" super().__init__(hub, device, bpup_subs, sub_device) - self._light: Optional[int] = None + self._light: int | None = None @property def is_on(self) -> bool: @@ -108,11 +110,11 @@ def __init__( hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscriptions, - sub_device: Optional[str] = None, + sub_device: str | None = None, ): """Create HA entity representing Bond light.""" super().__init__(hub, device, bpup_subs, sub_device) - self._brightness: Optional[int] = None + self._brightness: int | None = None def _apply_state(self, state: dict) -> None: self._light = state.get("light") @@ -126,7 +128,7 @@ def supported_features(self) -> int: return 0 @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" brightness_value = ( round(self._brightness * 255 / 100) if self._brightness else None @@ -194,9 +196,9 @@ def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscription """Create HA entity representing Bond fireplace.""" super().__init__(hub, device, bpup_subs) - self._power: Optional[bool] = None + self._power: bool | None = None # Bond flame level, 0-100 - self._flame: Optional[int] = None + self._flame: int | None = None def _apply_state(self, state: dict) -> None: self._power = state.get("power") @@ -230,11 +232,11 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self._hub.bond.action(self._device.device_id, Action.turn_off()) @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the flame of this fireplace converted to HA brightness between 0..255.""" return round(self._flame * 255 / 100) if self._flame else None @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Show fireplace icon for the entity.""" return "mdi:fireplace" if self._power == 1 else "mdi:fireplace-off" diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index abbc2e2b44c4ed..23e99d6af30aaf 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -1,5 +1,7 @@ """Support for Bond generic devices.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable from bond_api import Action, BPUPSubscriptions, DeviceType @@ -16,14 +18,14 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Bond generic devices.""" data = hass.data[DOMAIN][entry.entry_id] hub: BondHub = data[HUB] bpup_subs: BPUPSubscriptions = data[BPUP_SUBS] - switches: List[Entity] = [ + switches: list[Entity] = [ BondSwitch(hub, device, bpup_subs) for device in hub.devices if DeviceType.is_generic(device.type) @@ -39,7 +41,7 @@ def __init__(self, hub: BondHub, device: BondDevice, bpup_subs: BPUPSubscription """Create HA entity representing Bond generic device (switch).""" super().__init__(hub, device, bpup_subs) - self._power: Optional[bool] = None + self._power: bool | None = None def _apply_state(self, state: dict) -> None: self._power = state.get("power") diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 28580ae415e42e..e3d951b81371a7 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,7 +1,9 @@ """Reusable utilities for the Bond component.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, cast from aiohttp import ClientResponseError from bond_api import Action, Bond @@ -15,7 +17,7 @@ class BondDevice: """Helper device class to hold ID and attributes together.""" def __init__( - self, device_id: str, attrs: Dict[str, Any], props: Dict[str, Any] + self, device_id: str, attrs: dict[str, Any], props: dict[str, Any] ) -> None: """Create a helper device from ID and attributes returned by API.""" self.device_id = device_id @@ -41,17 +43,17 @@ def type(self) -> str: return cast(str, self._attrs["type"]) @property - def location(self) -> Optional[str]: + def location(self) -> str | None: """Get the location of this device.""" return self._attrs.get("location") @property - def template(self) -> Optional[str]: + def template(self) -> str | None: """Return this model template.""" return self._attrs.get("template") @property - def branding_profile(self) -> Optional[str]: + def branding_profile(self) -> str | None: """Return this branding profile.""" return self.props.get("branding_profile") @@ -60,9 +62,9 @@ def trust_state(self) -> bool: """Check if Trust State is turned on.""" return self.props.get("trust_state", False) - def _has_any_action(self, actions: Set[str]) -> bool: + def _has_any_action(self, actions: set[str]) -> bool: """Check to see if the device supports any of the actions.""" - supported_actions: List[str] = self._attrs["actions"] + supported_actions: list[str] = self._attrs["actions"] for action in supported_actions: if action in actions: return True @@ -101,11 +103,11 @@ class BondHub: def __init__(self, bond: Bond): """Initialize Bond Hub.""" self.bond: Bond = bond - self._bridge: Dict[str, Any] = {} - self._version: Dict[str, Any] = {} - self._devices: List[BondDevice] = [] + self._bridge: dict[str, Any] = {} + self._version: dict[str, Any] = {} + self._devices: list[BondDevice] = [] - async def setup(self, max_devices: Optional[int] = None) -> None: + async def setup(self, max_devices: int | None = None) -> None: """Read hub version information.""" self._version = await self.bond.version() _LOGGER.debug("Bond reported the following version info: %s", self._version) @@ -131,18 +133,18 @@ async def setup(self, max_devices: Optional[int] = None) -> None: _LOGGER.debug("Bond reported the following bridge info: %s", self._bridge) @property - def bond_id(self) -> Optional[str]: + def bond_id(self) -> str | None: """Return unique Bond ID for this hub.""" # Old firmwares are missing the bondid return self._version.get("bondid") @property - def target(self) -> Optional[str]: + def target(self) -> str | None: """Return this hub target.""" return self._version.get("target") @property - def model(self) -> Optional[str]: + def model(self) -> str | None: """Return this hub model.""" return self._version.get("model") @@ -159,19 +161,19 @@ def name(self) -> str: return cast(str, self._bridge["name"]) @property - def location(self) -> Optional[str]: + def location(self) -> str | None: """Get the location of this bridge.""" if not self.is_bridge and self._devices: return self._devices[0].location return self._bridge.get("location") @property - def fw_ver(self) -> Optional[str]: + def fw_ver(self) -> str | None: """Return this hub firmware version.""" return self._version.get("fw_ver") @property - def devices(self) -> List[BondDevice]: + def devices(self) -> list[BondDevice]: """Return a list of all devices controlled by this hub.""" return self._devices diff --git a/homeassistant/components/bsblan/climate.py b/homeassistant/components/bsblan/climate.py index a97c13c3424098..4d83fb04dbe872 100644 --- a/homeassistant/components/bsblan/climate.py +++ b/homeassistant/components/bsblan/climate.py @@ -1,7 +1,9 @@ """BSBLAN platform to control a compatible Climate Device.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from bsblan import BSBLan, BSBLanError, Info, State @@ -74,7 +76,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up BSBLan device based on a config entry.""" bsblan: BSBLan = hass.data[DOMAIN][entry.entry_id][DATA_BSBLAN_CLIENT] @@ -92,10 +94,10 @@ def __init__( info: Info, ): """Initialize BSBLan climate device.""" - self._current_temperature: Optional[float] = None + self._current_temperature: float | None = None self._available = True - self._hvac_mode: Optional[str] = None - self._target_temperature: Optional[float] = None + self._hvac_mode: str | None = None + self._target_temperature: float | None = None self._temperature_unit = None self._preset_mode = None self._store_hvac_mode = None @@ -229,7 +231,7 @@ async def async_update(self) -> None: self._temperature_unit = state.current_temperature.unit @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this BSBLan device.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self._info.device_identification)}, diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index dee04e6ef85056..8ea597d238650f 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -1,6 +1,8 @@ """Config flow for BSB-Lan integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from bsblan import BSBLan, BSBLanError, Info import voluptuous as vol @@ -26,8 +28,8 @@ class BSBLanFlowHandler(ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -59,7 +61,7 @@ async def async_step_user( }, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -78,9 +80,9 @@ def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: async def _get_bsblan_info( self, host: str, - username: Optional[str], - password: Optional[str], - passkey: Optional[str], + username: str | None, + password: str | None, + passkey: str | None, port: int, ) -> Info: """Get device information from an BSBLan device.""" diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index 44f86589b2707f..92f25b7ffc6798 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -1,8 +1,9 @@ """Provide animated GIF loops of Buienradar imagery.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging -from typing import Optional import aiohttp import voluptuous as vol @@ -85,13 +86,13 @@ def __init__(self, name: str, dimension: int, delta: float, country: str): # invariant: this condition is private to and owned by this instance. self._condition = asyncio.Condition() - self._last_image: Optional[bytes] = None + self._last_image: bytes | None = None # value of the last seen last modified header - self._last_modified: Optional[str] = None + self._last_modified: str | None = None # loading status self._loading = False # deadline for image refresh - self.delta after last successful load - self._deadline: Optional[datetime] = None + self._deadline: datetime | None = None self._unique_id = f"{self._dimension}_{self._country}" @@ -140,7 +141,7 @@ async def __retrieve_radar_image(self) -> bool: _LOGGER.error("Failed to fetch image, %s", type(err)) return False - async def async_camera_image(self) -> Optional[bytes]: + async def async_camera_image(self) -> bytes | None: """ Return a still image response from the camera. From 7c0734bdd5a38fc192f3faf865ad731d7687ac0d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:43:55 +0100 Subject: [PATCH 1332/1818] Update typing 05 (#48038) --- homeassistant/components/calendar/__init__.py | 6 ++- .../components/canary/alarm_control_panel.py | 6 ++- homeassistant/components/canary/camera.py | 6 ++- .../components/canary/config_flow.py | 16 ++++---- homeassistant/components/canary/sensor.py | 6 ++- .../components/cast/home_assistant_cast.py | 8 ++-- homeassistant/components/cast/media_player.py | 17 ++++---- .../components/cert_expiry/__init__.py | 5 ++- .../components/climacell/__init__.py | 12 +++--- .../components/climacell/config_flow.py | 16 ++++---- homeassistant/components/climacell/weather.py | 24 +++++------ homeassistant/components/climate/__init__.py | 40 ++++++++++--------- .../components/climate/device_action.py | 6 +-- .../components/climate/device_condition.py | 4 +- .../components/climate/device_trigger.py | 4 +- .../components/climate/reproduce_state.py | 12 +++--- homeassistant/components/cloud/client.py | 18 +++++---- homeassistant/components/cloud/prefs.py | 9 +++-- homeassistant/components/cloud/stt.py | 14 +++---- homeassistant/components/cloud/utils.py | 6 ++- .../components/cloudflare/__init__.py | 5 ++- .../components/cloudflare/config_flow.py | 15 +++---- homeassistant/components/comfoconnect/fan.py | 5 ++- .../components/conversation/agent.py | 5 ++- .../components/conversation/default_agent.py | 5 ++- homeassistant/components/counter/__init__.py | 27 ++++++------- .../components/counter/reproduce_state.py | 12 +++--- .../components/cover/device_action.py | 6 +-- .../components/cover/device_condition.py | 8 ++-- .../components/cover/device_trigger.py | 4 +- .../components/cover/reproduce_state.py | 12 +++--- homeassistant/components/debugpy/__init__.py | 5 ++- homeassistant/components/deconz/climate.py | 4 +- homeassistant/components/deconz/logbook.py | 5 ++- homeassistant/components/demo/fan.py | 18 ++++----- homeassistant/components/demo/geo_location.py | 11 ++--- homeassistant/components/demo/stt.py | 14 +++---- .../components/device_automation/__init__.py | 6 ++- .../device_automation/toggle_entity.py | 16 ++++---- .../components/device_tracker/config_entry.py | 4 +- .../device_tracker/device_condition.py | 4 +- .../device_tracker/device_trigger.py | 4 +- .../components/device_tracker/legacy.py | 10 +++-- .../components/devolo_home_control/climate.py | 8 ++-- homeassistant/components/directv/__init__.py | 8 ++-- .../components/directv/config_flow.py | 16 ++++---- .../components/directv/media_player.py | 8 ++-- homeassistant/components/directv/remote.py | 6 ++- .../components/dlna_dmr/media_player.py | 5 ++- homeassistant/components/dsmr/config_flow.py | 6 ++- homeassistant/components/dsmr/sensor.py | 5 ++- homeassistant/components/dynalite/__init__.py | 9 +++-- homeassistant/components/dynalite/bridge.py | 13 +++--- .../components/dynalite/config_flow.py | 6 ++- .../components/dynalite/convert_config.py | 15 +++---- .../components/dynalite/dynalitebase.py | 6 ++- homeassistant/components/dyson/fan.py | 15 +++---- 57 files changed, 315 insertions(+), 251 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 478dafb3423653..81ce49e9c9411b 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -1,8 +1,10 @@ """Support for Google Calendar event device sensors.""" +from __future__ import annotations + from datetime import timedelta import logging import re -from typing import Dict, List, cast +from typing import cast from aiohttp import web @@ -218,7 +220,7 @@ def __init__(self, component: EntityComponent) -> None: async def get(self, request: web.Request) -> web.Response: """Retrieve calendar list.""" hass = request.app["hass"] - calendar_list: List[Dict[str, str]] = [] + calendar_list: list[dict[str, str]] = [] for entity in self.component.entities: state = hass.states.get(entity.entity_id) diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index d0beece8df2c9b..933e6708e22f7f 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -1,5 +1,7 @@ """Support for Canary alarm.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from canary.api import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, LOCATION_MODE_NIGHT @@ -27,7 +29,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Canary alarm control panels based on a config entry.""" coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 0493a964cc45fb..703ae2edc8a2c4 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -1,7 +1,9 @@ """Support for Canary camera.""" +from __future__ import annotations + import asyncio from datetime import timedelta -from typing import Callable, List +from typing import Callable from haffmpeg.camera import CameraMjpeg from haffmpeg.tools import IMAGE_JPEG, ImageFrame @@ -44,7 +46,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Canary sensors based on a config entry.""" coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/canary/config_flow.py b/homeassistant/components/canary/config_flow.py index dc2822d836a2ae..7b6b5b3f322362 100644 --- a/homeassistant/components/canary/config_flow.py +++ b/homeassistant/components/canary/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Canary.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from canary.api import Api from requests import ConnectTimeout, HTTPError @@ -17,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) -def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -45,14 +47,14 @@ def async_get_options_flow(config_entry): return CanaryOptionsFlowHandler(config_entry) async def async_step_import( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by configuration file.""" return await self.async_step_user(user_input) async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -100,7 +102,7 @@ def __init__(self, config_entry): """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Optional[ConfigType] = None): + async def async_step_init(self, user_input: ConfigType | None = None): """Manage Canary options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 2d5a7885fbf6ab..87e40d268bddda 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -1,5 +1,7 @@ """Support for Canary sensors.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from canary.api import SensorType @@ -54,7 +56,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Canary sensors based on a config entry.""" coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index 3edc1ce2cde59d..bb0354bb68e17f 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -1,5 +1,5 @@ """Home Assistant Cast integration for Cast.""" -from typing import Optional +from __future__ import annotations from pychromecast.controllers.homeassistant import HomeAssistantController import voluptuous as vol @@ -20,8 +20,8 @@ async def async_setup_ha_cast( hass: core.HomeAssistant, entry: config_entries.ConfigEntry ): """Set up Home Assistant Cast.""" - user_id: Optional[str] = entry.data.get("user_id") - user: Optional[auth.models.User] = None + user_id: str | None = entry.data.get("user_id") + user: auth.models.User | None = None if user_id is not None: user = await hass.auth.async_get_user(user_id) @@ -78,7 +78,7 @@ async def async_remove_user( hass: core.HomeAssistant, entry: config_entries.ConfigEntry ): """Remove Home Assistant Cast user.""" - user_id: Optional[str] = entry.data.get("user_id") + user_id: str | None = entry.data.get("user_id") if user_id is not None: user = await hass.auth.async_get_user(user_id) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index fbb084378305b8..2a1aeaacaa6fa6 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -1,10 +1,11 @@ """Provide functionality to interact with Cast devices on the network.""" +from __future__ import annotations + import asyncio from datetime import timedelta import functools as ft import json import logging -from typing import Optional import pychromecast from pychromecast.controllers.homeassistant import HomeAssistantController @@ -195,7 +196,7 @@ def __init__(self, cast_info: ChromecastInfo): self._cast_info = cast_info self.services = cast_info.services - self._chromecast: Optional[pychromecast.Chromecast] = None + self._chromecast: pychromecast.Chromecast | None = None self.cast_status = None self.media_status = None self.media_status_received = None @@ -203,8 +204,8 @@ def __init__(self, cast_info: ChromecastInfo): self.mz_media_status_received = {} self.mz_mgr = None self._available = False - self._status_listener: Optional[CastStatusListener] = None - self._hass_cast_controller: Optional[HomeAssistantController] = None + self._status_listener: CastStatusListener | None = None + self._hass_cast_controller: HomeAssistantController | None = None self._add_remove_handler = None self._cast_view_remove_handler = None @@ -783,7 +784,7 @@ def media_position_updated_at(self): return media_status_recevied @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._cast_info.uuid @@ -805,7 +806,7 @@ def _handle_signal_show_view( controller: HomeAssistantController, entity_id: str, view_path: str, - url_path: Optional[str], + url_path: str | None, ): """Handle a show view signal.""" if entity_id != self.entity_id: @@ -827,9 +828,9 @@ def __init__(self, hass, cast_info: ChromecastInfo): self.hass = hass self._cast_info = cast_info self.services = cast_info.services - self._chromecast: Optional[pychromecast.Chromecast] = None + self._chromecast: pychromecast.Chromecast | None = None self.mz_mgr = None - self._status_listener: Optional[CastStatusListener] = None + self._status_listener: CastStatusListener | None = None self._add_remove_handler = None self._del_remove_handler = None diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py index d01e38a2e2cbb3..2d4be924a17938 100644 --- a/homeassistant/components/cert_expiry/__init__.py +++ b/homeassistant/components/cert_expiry/__init__.py @@ -1,7 +1,8 @@ """The cert_expiry component.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Optional from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT @@ -71,7 +72,7 @@ def __init__(self, hass, host, port): update_interval=SCAN_INTERVAL, ) - async def _async_update_data(self) -> Optional[datetime]: + async def _async_update_data(self) -> datetime | None: """Fetch certificate.""" try: timestamp = await get_cert_expiry_timestamp(self.hass, self.host, self.port) diff --git a/homeassistant/components/climacell/__init__.py b/homeassistant/components/climacell/__init__.py index c3c3f702780f13..96d7735a03c026 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -1,9 +1,11 @@ """The ClimaCell integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging from math import ceil -from typing import Any, Dict, Optional, Union +from typing import Any from pyclimacell import ClimaCell from pyclimacell.const import ( @@ -169,7 +171,7 @@ def __init__( update_interval=update_interval, ) - async def _async_update_data(self) -> Dict[str, Any]: + async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" data = {FORECASTS: {}} try: @@ -217,8 +219,8 @@ def __init__( @staticmethod def _get_cc_value( - weather_dict: Dict[str, Any], key: str - ) -> Optional[Union[int, float, str]]: + weather_dict: dict[str, Any], key: str + ) -> int | float | str | None: """Return property from weather_dict.""" items = weather_dict.get(key, {}) # Handle cases where value returned is a list. @@ -252,7 +254,7 @@ def attribution(self): return ATTRIBUTION @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.data[CONF_API_KEY])}, diff --git a/homeassistant/components/climacell/config_flow.py b/homeassistant/components/climacell/config_flow.py index 7048e4e5c2a7fd..cf3bccd3392416 100644 --- a/homeassistant/components/climacell/config_flow.py +++ b/homeassistant/components/climacell/config_flow.py @@ -1,6 +1,8 @@ """Config flow for ClimaCell integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from pyclimacell import ClimaCell from pyclimacell.const import REALTIME @@ -25,7 +27,7 @@ def _get_config_schema( - hass: core.HomeAssistant, input_dict: Dict[str, Any] = None + hass: core.HomeAssistant, input_dict: dict[str, Any] = None ) -> vol.Schema: """ Return schema defaults for init step based on user input/config dict. @@ -57,7 +59,7 @@ def _get_config_schema( ) -def _get_unique_id(hass: HomeAssistantType, input_dict: Dict[str, Any]): +def _get_unique_id(hass: HomeAssistantType, input_dict: dict[str, Any]): """Return unique ID from config data.""" return ( f"{input_dict[CONF_API_KEY]}" @@ -74,8 +76,8 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: self._config_entry = config_entry async def async_step_init( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Manage the ClimaCell options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -107,8 +109,8 @@ def async_get_options_flow( return ClimaCellOptionsConfigFlow(config_entry) async def async_step_user( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index e5a24197d6bad1..e6485f90936c92 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -1,7 +1,9 @@ """Weather component that handles meteorological data for your location.""" +from __future__ import annotations + from datetime import datetime import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, @@ -64,9 +66,7 @@ _LOGGER = logging.getLogger(__name__) -def _translate_condition( - condition: Optional[str], sun_is_up: bool = True -) -> Optional[str]: +def _translate_condition(condition: str | None, sun_is_up: bool = True) -> str | None: """Translate ClimaCell condition into an HA condition.""" if not condition: return None @@ -82,13 +82,13 @@ def _forecast_dict( forecast_dt: datetime, use_datetime: bool, condition: str, - precipitation: Optional[float], - precipitation_probability: Optional[float], - temp: Optional[float], - temp_low: Optional[float], - wind_direction: Optional[float], - wind_speed: Optional[float], -) -> Dict[str, Any]: + precipitation: float | None, + precipitation_probability: float | None, + temp: float | None, + temp_low: float | None, + wind_direction: float | None, + wind_speed: float | None, +) -> dict[str, Any]: """Return formatted Forecast dict from ClimaCell forecast data.""" if use_datetime: translated_condition = _translate_condition(condition, is_up(hass, forecast_dt)) @@ -120,7 +120,7 @@ def _forecast_dict( async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 32dfaa0e8fb4a5..93041a6bb33b4b 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -1,9 +1,11 @@ """Provides functionality to interact with climate devices.""" +from __future__ import annotations + from abc import abstractmethod from datetime import timedelta import functools as ft import logging -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol @@ -180,7 +182,7 @@ def precision(self) -> float: return PRECISION_WHOLE @property - def capability_attributes(self) -> Optional[Dict[str, Any]]: + def capability_attributes(self) -> dict[str, Any] | None: """Return the capability attributes.""" supported_features = self.supported_features data = { @@ -212,7 +214,7 @@ def capability_attributes(self) -> Optional[Dict[str, Any]]: return data @property - def state_attributes(self) -> Dict[str, Any]: + def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" supported_features = self.supported_features data = { @@ -275,12 +277,12 @@ def temperature_unit(self) -> str: raise NotImplementedError() @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return None @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return None @@ -294,14 +296,14 @@ def hvac_mode(self) -> str: @property @abstractmethod - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. """ @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. @@ -309,22 +311,22 @@ def hvac_action(self) -> Optional[str]: return None @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return None @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return None @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach. Requires SUPPORT_TARGET_TEMPERATURE_RANGE. @@ -332,7 +334,7 @@ def target_temperature_high(self) -> Optional[float]: raise NotImplementedError @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach. Requires SUPPORT_TARGET_TEMPERATURE_RANGE. @@ -340,7 +342,7 @@ def target_temperature_low(self) -> Optional[float]: raise NotImplementedError @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -348,7 +350,7 @@ def preset_mode(self) -> Optional[str]: raise NotImplementedError @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. @@ -356,7 +358,7 @@ def preset_modes(self) -> Optional[List[str]]: raise NotImplementedError @property - def is_aux_heat(self) -> Optional[bool]: + def is_aux_heat(self) -> bool | None: """Return true if aux heater. Requires SUPPORT_AUX_HEAT. @@ -364,7 +366,7 @@ def is_aux_heat(self) -> Optional[bool]: raise NotImplementedError @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting. Requires SUPPORT_FAN_MODE. @@ -372,7 +374,7 @@ def fan_mode(self) -> Optional[str]: raise NotImplementedError @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes. Requires SUPPORT_FAN_MODE. @@ -380,7 +382,7 @@ def fan_modes(self) -> Optional[List[str]]: raise NotImplementedError @property - def swing_mode(self) -> Optional[str]: + def swing_mode(self) -> str | None: """Return the swing setting. Requires SUPPORT_SWING_MODE. @@ -388,7 +390,7 @@ def swing_mode(self) -> Optional[str]: raise NotImplementedError @property - def swing_modes(self) -> Optional[List[str]]: + def swing_modes(self) -> list[str] | None: """Return the list of available swing modes. Requires SUPPORT_SWING_MODE. diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 3f2b8dc23f2c89..18123ab11f7bd5 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Climate.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -38,7 +38,7 @@ ACTION_SCHEMA = vol.Any(SET_HVAC_MODE_SCHEMA, SET_PRESET_MODE_SCHEMA) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Climate devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -76,7 +76,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 423efdf8196c4d..10f3b5069b93cc 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Climate.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -42,7 +42,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Climate devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index 84a7c35162a792..bee019fa6da6bd 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Climate.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -58,7 +58,7 @@ TRIGGER_SCHEMA = vol.Any(HVAC_MODE_TRIGGER_SCHEMA, CURRENT_TRIGGER_SCHEMA) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Climate devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index 1217d5fde4cd08..831a67c3882955 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -1,6 +1,8 @@ """Module that groups code required to handle state restore for component.""" +from __future__ import annotations + import asyncio -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import Context, State @@ -29,8 +31,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" @@ -76,8 +78,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index 155a39e49b6aa4..fda2014c9c3d69 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -1,8 +1,10 @@ """Interface implementation for cloud client.""" +from __future__ import annotations + import asyncio import logging from pathlib import Path -from typing import Any, Dict +from typing import Any import aiohttp from hass_nabucasa.client import CloudClient as Interface @@ -32,8 +34,8 @@ def __init__( hass: HomeAssistantType, prefs: CloudPreferences, websession: aiohttp.ClientSession, - alexa_user_config: Dict[str, Any], - google_user_config: Dict[str, Any], + alexa_user_config: dict[str, Any], + google_user_config: dict[str, Any], ): """Initialize client interface to Cloud.""" self._hass = hass @@ -70,7 +72,7 @@ def aiohttp_runner(self) -> aiohttp.web.AppRunner: return self._hass.http.runner @property - def cloudhooks(self) -> Dict[str, Dict[str, str]]: + def cloudhooks(self) -> dict[str, dict[str, str]]: """Return list of cloudhooks.""" return self._prefs.cloudhooks @@ -164,7 +166,7 @@ def dispatcher_message(self, identifier: str, data: Any = None) -> None: if identifier.startswith("remote_"): async_dispatcher_send(self._hass, DISPATCHER_REMOTE_UPDATE, data) - async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_alexa_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: """Process cloud alexa message to client.""" cloud_user = await self._prefs.get_cloud_user() aconfig = await self.get_alexa_config() @@ -176,7 +178,7 @@ async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: enabled=self._prefs.alexa_enabled, ) - async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_google_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: """Process cloud google message to client.""" if not self._prefs.google_enabled: return ga.turned_off_response(payload) @@ -187,7 +189,7 @@ async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: self._hass, gconf, gconf.cloud_user, payload, gc.SOURCE_CLOUD ) - async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]: + async def async_webhook_message(self, payload: dict[Any, Any]) -> dict[Any, Any]: """Process cloud webhook message to client.""" cloudhook_id = payload["cloudhook_id"] @@ -221,6 +223,6 @@ async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any] "headers": {"Content-Type": response.content_type}, } - async def async_cloudhooks_update(self, data: Dict[str, Dict[str, str]]) -> None: + async def async_cloudhooks_update(self, data: dict[str, dict[str, str]]) -> None: """Update local list of cloudhooks.""" await self._prefs.async_update(cloudhooks=data) diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index a15eafc4d081d8..2b15a620b83d9a 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -1,6 +1,7 @@ """Preference management for cloud.""" +from __future__ import annotations + from ipaddress import ip_address -from typing import List, Optional from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.models import User @@ -234,7 +235,7 @@ def alexa_report_state(self): return self._prefs.get(PREF_ALEXA_REPORT_STATE, DEFAULT_ALEXA_REPORT_STATE) @property - def alexa_default_expose(self) -> Optional[List[str]]: + def alexa_default_expose(self) -> list[str] | None: """Return array of entity domains that are exposed by default to Alexa. Can return None, in which case for backwards should be interpreted as allow all domains. @@ -272,7 +273,7 @@ def google_local_webhook_id(self): return self._prefs[PREF_GOOGLE_LOCAL_WEBHOOK_ID] @property - def google_default_expose(self) -> Optional[List[str]]: + def google_default_expose(self) -> list[str] | None: """Return array of entity domains that are exposed by default to Google. Can return None, in which case for backwards should be interpreted as allow all domains. @@ -302,7 +303,7 @@ async def get_cloud_user(self) -> str: await self.async_update(cloud_user=user.id) return user.id - async def _load_cloud_user(self) -> Optional[User]: + async def _load_cloud_user(self) -> User | None: """Load cloud user if available.""" user_id = self._prefs.get(PREF_CLOUD_USER) diff --git a/homeassistant/components/cloud/stt.py b/homeassistant/components/cloud/stt.py index 6c069ce16d777d..80578a8d721de6 100644 --- a/homeassistant/components/cloud/stt.py +++ b/homeassistant/components/cloud/stt.py @@ -1,5 +1,5 @@ """Support for the cloud for speech to text service.""" -from typing import List +from __future__ import annotations from aiohttp import StreamReader from hass_nabucasa import Cloud @@ -56,32 +56,32 @@ def __init__(self, cloud: Cloud) -> None: self.cloud = cloud @property - def supported_languages(self) -> List[str]: + def supported_languages(self) -> list[str]: """Return a list of supported languages.""" return SUPPORT_LANGUAGES @property - def supported_formats(self) -> List[AudioFormats]: + def supported_formats(self) -> list[AudioFormats]: """Return a list of supported formats.""" return [AudioFormats.WAV, AudioFormats.OGG] @property - def supported_codecs(self) -> List[AudioCodecs]: + def supported_codecs(self) -> list[AudioCodecs]: """Return a list of supported codecs.""" return [AudioCodecs.PCM, AudioCodecs.OPUS] @property - def supported_bit_rates(self) -> List[AudioBitRates]: + def supported_bit_rates(self) -> list[AudioBitRates]: """Return a list of supported bitrates.""" return [AudioBitRates.BITRATE_16] @property - def supported_sample_rates(self) -> List[AudioSampleRates]: + def supported_sample_rates(self) -> list[AudioSampleRates]: """Return a list of supported samplerates.""" return [AudioSampleRates.SAMPLERATE_16000] @property - def supported_channels(self) -> List[AudioChannels]: + def supported_channels(self) -> list[AudioChannels]: """Return a list of supported channels.""" return [AudioChannels.CHANNEL_MONO] diff --git a/homeassistant/components/cloud/utils.py b/homeassistant/components/cloud/utils.py index 36599b42ad336a..57f84b057f78d5 100644 --- a/homeassistant/components/cloud/utils.py +++ b/homeassistant/components/cloud/utils.py @@ -1,10 +1,12 @@ """Helper functions for cloud components.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from aiohttp import payload, web -def aiohttp_serialize_response(response: web.Response) -> Dict[str, Any]: +def aiohttp_serialize_response(response: web.Response) -> dict[str, Any]: """Serialize an aiohttp response to a dictionary.""" body = response.body diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py index 446890887c10cb..abef32a4c5c1a6 100644 --- a/homeassistant/components/cloudflare/__init__.py +++ b/homeassistant/components/cloudflare/__init__.py @@ -1,7 +1,8 @@ """Update the IP addresses of your Cloudflare DNS records.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Dict from pycfdns import CloudflareUpdater from pycfdns.exceptions import ( @@ -51,7 +52,7 @@ ) -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the component.""" hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/cloudflare/config_flow.py b/homeassistant/components/cloudflare/config_flow.py index 066fff9f704848..f530652fe90447 100644 --- a/homeassistant/components/cloudflare/config_flow.py +++ b/homeassistant/components/cloudflare/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Cloudflare integration.""" +from __future__ import annotations + import logging -from typing import Dict, List, Optional from pycfdns import CloudflareUpdater from pycfdns.exceptions import ( @@ -30,7 +31,7 @@ ) -def _zone_schema(zones: Optional[List] = None): +def _zone_schema(zones: list | None = None): """Zone selection schema.""" zones_list = [] @@ -40,7 +41,7 @@ def _zone_schema(zones: Optional[List] = None): return vol.Schema({vol.Required(CONF_ZONE): vol.In(zones_list)}) -def _records_schema(records: Optional[List] = None): +def _records_schema(records: list | None = None): """Zone records selection schema.""" records_dict = {} @@ -50,7 +51,7 @@ def _records_schema(records: Optional[List] = None): return vol.Schema({vol.Required(CONF_RECORDS): cv.multi_select(records_dict)}) -async def validate_input(hass: HomeAssistant, data: Dict): +async def validate_input(hass: HomeAssistant, data: dict): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -92,7 +93,7 @@ def __init__(self): self.zones = None self.records = None - async def async_step_user(self, user_input: Optional[Dict] = None): + async def async_step_user(self, user_input: dict | None = None): """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -113,7 +114,7 @@ async def async_step_user(self, user_input: Optional[Dict] = None): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_zone(self, user_input: Optional[Dict] = None): + async def async_step_zone(self, user_input: dict | None = None): """Handle the picking the zone.""" errors = {} @@ -133,7 +134,7 @@ async def async_step_zone(self, user_input: Optional[Dict] = None): errors=errors, ) - async def async_step_records(self, user_input: Optional[Dict] = None): + async def async_step_records(self, user_input: dict | None = None): """Handle the picking the zone records.""" errors = {} diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index d248bf74ac4938..53bc242ba2ff03 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,7 +1,8 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" +from __future__ import annotations + import logging import math -from typing import Optional from pycomfoconnect import ( CMD_FAN_MODE_AWAY, @@ -96,7 +97,7 @@ def supported_features(self) -> int: return SUPPORT_SET_SPEED @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" speed = self._ccb.data.get(SENSOR_FAN_SPEED_MODE) if speed is None: diff --git a/homeassistant/components/conversation/agent.py b/homeassistant/components/conversation/agent.py index c9c2ab46cf9fd3..56cf4aecdeaebc 100644 --- a/homeassistant/components/conversation/agent.py +++ b/homeassistant/components/conversation/agent.py @@ -1,6 +1,7 @@ """Agent foundation for conversation integration.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Optional from homeassistant.core import Context from homeassistant.helpers import intent @@ -24,6 +25,6 @@ async def async_set_onboarding(self, shown): @abstractmethod async def async_process( - self, text: str, context: Context, conversation_id: Optional[str] = None + self, text: str, context: Context, conversation_id: str | None = None ) -> intent.IntentResponse: """Process a sentence.""" diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index d2dcf13a62aece..a98f685ea1d447 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -1,6 +1,7 @@ """Standard conversastion implementation for Home Assistant.""" +from __future__ import annotations + import re -from typing import Optional from homeassistant import core, setup from homeassistant.components.cover.intent import INTENT_CLOSE_COVER, INTENT_OPEN_COVER @@ -112,7 +113,7 @@ def register_utterances(self, component): async_register(self.hass, intent_type, sentences) async def async_process( - self, text: str, context: core.Context, conversation_id: Optional[str] = None + self, text: str, context: core.Context, conversation_id: str | None = None ) -> intent.IntentResponse: """Process a sentence.""" intents = self.hass.data[DOMAIN] diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index 868a74cc7b76ab..a4e04825ef65c9 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -from typing import Dict, Optional import voluptuous as vol @@ -156,16 +155,16 @@ class CounterStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: Dict) -> Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: Dict) -> Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return {**data, **update_data} @@ -174,14 +173,14 @@ async def _update_data(self, data: dict, update_data: Dict) -> Dict: class Counter(RestoreEntity): """Representation of a counter.""" - def __init__(self, config: Dict): + def __init__(self, config: dict): """Initialize a counter.""" - self._config: Dict = config - self._state: Optional[int] = config[CONF_INITIAL] + self._config: dict = config + self._state: int | None = config[CONF_INITIAL] self.editable: bool = True @classmethod - def from_yaml(cls, config: Dict) -> Counter: + def from_yaml(cls, config: dict) -> Counter: """Create counter instance from yaml config.""" counter = cls(config) counter.editable = False @@ -194,22 +193,22 @@ def should_poll(self) -> bool: return False @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return name of the counter.""" return self._config.get(CONF_NAME) @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon to be used for this entity.""" return self._config.get(CONF_ICON) @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return the current value of the counter.""" return self._state @property - def state_attributes(self) -> Dict: + def state_attributes(self) -> dict: """Return the state attributes.""" ret = { ATTR_EDITABLE: self.editable, @@ -223,7 +222,7 @@ def state_attributes(self) -> Dict: return ret @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique id of the entity.""" return self._config[CONF_ID] @@ -276,7 +275,7 @@ def async_configure(self, **kwargs) -> None: self._state = self.compute_next_state(new_state) self.async_write_ha_state() - async def async_update_config(self, config: Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Change the counter's settings WS CRUD.""" self._config = config self._state = self.compute_next_state(self._state) diff --git a/homeassistant/components/counter/reproduce_state.py b/homeassistant/components/counter/reproduce_state.py index b2dd63adedc912..5d51a074e67887 100644 --- a/homeassistant/components/counter/reproduce_state.py +++ b/homeassistant/components/counter/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Counter state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -70,8 +72,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Counter states.""" await asyncio.gather( diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 490ce162d9a369..4f92e7f09bdd51 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Cover.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -58,7 +58,7 @@ ACTION_SCHEMA = vol.Any(CMD_ACTION_SCHEMA, POSITION_ACTION_SCHEMA) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Cover devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -162,7 +162,7 @@ async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> di async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/cover/device_condition.py b/homeassistant/components/cover/device_condition.py index 0bcec2a6e43d0e..2943f589f7b8d8 100644 --- a/homeassistant/components/cover/device_condition.py +++ b/homeassistant/components/cover/device_condition.py @@ -1,5 +1,7 @@ """Provides device automations for Cover.""" -from typing import Any, Dict, List +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -65,10 +67,10 @@ CONDITION_SCHEMA = vol.Any(POSITION_CONDITION_SCHEMA, STATE_CONDITION_SCHEMA) -async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device conditions for Cover devices.""" registry = await entity_registry.async_get_registry(hass) - conditions: List[Dict[str, Any]] = [] + conditions: list[dict[str, Any]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 764cc173e5fa13..119bfc835f586c 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Cover.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -65,7 +65,7 @@ TRIGGER_SCHEMA = vol.Any(POSITION_TRIGGER_SCHEMA, STATE_TRIGGER_SCHEMA) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Cover devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/cover/reproduce_state.py b/homeassistant/components/cover/reproduce_state.py index 2a12172bdab882..5a053947ec9728 100644 --- a/homeassistant/components/cover/reproduce_state.py +++ b/homeassistant/components/cover/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Cover state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, @@ -36,8 +38,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -117,8 +119,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Cover states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/debugpy/__init__.py b/homeassistant/components/debugpy/__init__.py index caa691b23695af..98f08827c239e0 100644 --- a/homeassistant/components/debugpy/__init__.py +++ b/homeassistant/components/debugpy/__init__.py @@ -1,8 +1,9 @@ """The Remote Python Debugger integration.""" +from __future__ import annotations + from asyncio import Event import logging from threading import Thread -from typing import Optional import debugpy import voluptuous as vol @@ -40,7 +41,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: conf = config[DOMAIN] async def debug_start( - call: Optional[ServiceCall] = None, *, wait: bool = True + call: ServiceCall | None = None, *, wait: bool = True ) -> None: """Start the debugger.""" debugpy.listen((conf[CONF_HOST], conf[CONF_PORT])) diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index dad3f3adf67667..49f0cc4d1499bd 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -1,5 +1,5 @@ """Support for deCONZ climate devices.""" -from typing import Optional +from __future__ import annotations from pydeconz.sensor import Thermostat @@ -195,7 +195,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: # Preset control @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return preset mode.""" return DECONZ_TO_PRESET_MODE.get(self._device.preset) diff --git a/homeassistant/components/deconz/logbook.py b/homeassistant/components/deconz/logbook.py index 85982244364fc3..e6f3e9362cd3b3 100644 --- a/homeassistant/components/deconz/logbook.py +++ b/homeassistant/components/deconz/logbook.py @@ -1,6 +1,7 @@ """Describe deCONZ logbook events.""" +from __future__ import annotations -from typing import Callable, Optional +from typing import Callable from homeassistant.const import ATTR_DEVICE_ID, CONF_EVENT from homeassistant.core import HomeAssistant, callback @@ -125,7 +126,7 @@ def async_describe_events( @callback def async_describe_deconz_event(event: Event) -> dict: """Describe deCONZ logbook event.""" - deconz_event: Optional[DeconzEvent] = _get_deconz_event_from_device_id( + deconz_event: DeconzEvent | None = _get_deconz_event_from_device_id( hass, event.data[ATTR_DEVICE_ID] ) diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 77d6f39a794247..c406ffdb2143ec 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -1,5 +1,5 @@ """Demo fan platform that has a fake fan.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.fan import ( SPEED_HIGH, @@ -110,8 +110,8 @@ def __init__( unique_id: str, name: str, supported_features: int, - preset_modes: Optional[List[str]], - speed_list: Optional[List[str]], + preset_modes: list[str] | None, + speed_list: list[str] | None, ) -> None: """Initialize the entity.""" self.hass = hass @@ -211,7 +211,7 @@ class DemoPercentageFan(BaseDemoFan, FanEntity): """A demonstration fan component that uses percentages.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed.""" return self._percentage @@ -227,12 +227,12 @@ def set_percentage(self, percentage: int) -> None: self.schedule_update_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite.""" return self._preset_mode @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return self._preset_modes @@ -271,7 +271,7 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): """An async demonstration fan component that uses percentages.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed.""" return self._percentage @@ -287,12 +287,12 @@ async def async_set_percentage(self, percentage: int) -> None: self.async_write_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite.""" return self._preset_mode @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return self._preset_modes diff --git a/homeassistant/components/demo/geo_location.py b/homeassistant/components/demo/geo_location.py index 76dea52e846671..f288a2fd340b88 100644 --- a/homeassistant/components/demo/geo_location.py +++ b/homeassistant/components/demo/geo_location.py @@ -1,9 +1,10 @@ """Demo platform for the geolocation component.""" +from __future__ import annotations + from datetime import timedelta import logging from math import cos, pi, radians, sin import random -from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import LENGTH_KILOMETERS @@ -117,7 +118,7 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the event.""" return self._name @@ -127,17 +128,17 @@ def should_poll(self): return False @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/demo/stt.py b/homeassistant/components/demo/stt.py index e0367fad6a920c..0497e2335d3d02 100644 --- a/homeassistant/components/demo/stt.py +++ b/homeassistant/components/demo/stt.py @@ -1,5 +1,5 @@ """Support for the demo for speech to text service.""" -from typing import List +from __future__ import annotations from aiohttp import StreamReader @@ -25,32 +25,32 @@ class DemoProvider(Provider): """Demo speech API provider.""" @property - def supported_languages(self) -> List[str]: + def supported_languages(self) -> list[str]: """Return a list of supported languages.""" return SUPPORT_LANGUAGES @property - def supported_formats(self) -> List[AudioFormats]: + def supported_formats(self) -> list[AudioFormats]: """Return a list of supported formats.""" return [AudioFormats.WAV] @property - def supported_codecs(self) -> List[AudioCodecs]: + def supported_codecs(self) -> list[AudioCodecs]: """Return a list of supported codecs.""" return [AudioCodecs.PCM] @property - def supported_bit_rates(self) -> List[AudioBitRates]: + def supported_bit_rates(self) -> list[AudioBitRates]: """Return a list of supported bit rates.""" return [AudioBitRates.BITRATE_16] @property - def supported_sample_rates(self) -> List[AudioSampleRates]: + def supported_sample_rates(self) -> list[AudioSampleRates]: """Return a list of supported sample rates.""" return [AudioSampleRates.SAMPLERATE_16000, AudioSampleRates.SAMPLERATE_44100] @property - def supported_channels(self) -> List[AudioChannels]: + def supported_channels(self) -> list[AudioChannels]: """Return a list of supported channels.""" return [AudioChannels.CHANNEL_STEREO] diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index bba02147c71173..4741dbdb7f5572 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -1,8 +1,10 @@ """Helpers for device automations.""" +from __future__ import annotations + import asyncio from functools import wraps from types import ModuleType -from typing import Any, List, MutableMapping +from typing import Any, MutableMapping import voluptuous as vol import voluptuous_serialize @@ -116,7 +118,7 @@ async def _async_get_device_automations(hass, automation_type, device_id): ) domains = set() - automations: List[MutableMapping[str, Any]] = [] + automations: list[MutableMapping[str, Any]] = [] device = device_registry.async_get(device_id) if device is None: diff --git a/homeassistant/components/device_automation/toggle_entity.py b/homeassistant/components/device_automation/toggle_entity.py index 61c50da68681ce..ec7b25b9c24e4f 100644 --- a/homeassistant/components/device_automation/toggle_entity.py +++ b/homeassistant/components/device_automation/toggle_entity.py @@ -1,5 +1,7 @@ """Device automation helpers for toggle entity.""" -from typing import Any, Dict, List +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -170,10 +172,10 @@ async def async_attach_trigger( async def _async_get_automations( - hass: HomeAssistant, device_id: str, automation_templates: List[dict], domain: str -) -> List[dict]: + hass: HomeAssistant, device_id: str, automation_templates: list[dict], domain: str +) -> list[dict]: """List device automations.""" - automations: List[Dict[str, Any]] = [] + automations: list[dict[str, Any]] = [] entity_registry = await hass.helpers.entity_registry.async_get_registry() entries = [ @@ -198,21 +200,21 @@ async def _async_get_automations( async def async_get_actions( hass: HomeAssistant, device_id: str, domain: str -) -> List[dict]: +) -> list[dict]: """List device actions.""" return await _async_get_automations(hass, device_id, ENTITY_ACTIONS, domain) async def async_get_conditions( hass: HomeAssistant, device_id: str, domain: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await _async_get_automations(hass, device_id, ENTITY_CONDITIONS, domain) async def async_get_triggers( hass: HomeAssistant, device_id: str, domain: str -) -> List[dict]: +) -> list[dict]: """List device triggers.""" return await _async_get_automations(hass, device_id, ENTITY_TRIGGERS, domain) diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 16e7d022c92c54..0d8970e087bea0 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -1,5 +1,5 @@ """Code to set up a device tracker platform using a config entry.""" -from typing import Optional +from __future__ import annotations from homeassistant.components import zone from homeassistant.const import ( @@ -18,7 +18,7 @@ async def async_setup_entry(hass, entry): """Set up an entry.""" - component: Optional[EntityComponent] = hass.data.get(DOMAIN) + component: EntityComponent | None = hass.data.get(DOMAIN) if component is None: component = hass.data[DOMAIN] = EntityComponent(LOGGER, DOMAIN, hass) diff --git a/homeassistant/components/device_tracker/device_condition.py b/homeassistant/components/device_tracker/device_condition.py index 9c102bfa745e9b..0260a4bbd3abf0 100644 --- a/homeassistant/components/device_tracker/device_condition.py +++ b/homeassistant/components/device_tracker/device_condition.py @@ -1,5 +1,5 @@ """Provides device automations for Device tracker.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Device tracker devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 2c92304a246827..49b77024a1ed6d 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Device Tracker.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Device Tracker devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index e947ae7b828c04..1d3e428b46a460 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -1,9 +1,11 @@ """Legacy device tracker classes.""" +from __future__ import annotations + import asyncio from datetime import timedelta import hashlib from types import ModuleType -from typing import Any, Callable, Dict, List, Optional, Sequence +from typing import Any, Callable, Sequence import attr import voluptuous as vol @@ -205,7 +207,7 @@ class DeviceTrackerPlatform: name: str = attr.ib() platform: ModuleType = attr.ib() - config: Dict = attr.ib() + config: dict = attr.ib() @property def type(self): @@ -285,7 +287,7 @@ async def async_extract_config(hass, config): async def async_create_platform_type( hass, config, p_type, p_config -) -> Optional[DeviceTrackerPlatform]: +) -> DeviceTrackerPlatform | None: """Determine type of platform.""" platform = await async_prepare_setup_platform(hass, config, DOMAIN, p_type) @@ -786,7 +788,7 @@ class DeviceScanner: hass: HomeAssistantType = None - def scan_devices(self) -> List[str]: + def scan_devices(self) -> list[str]: """Scan for devices.""" raise NotImplementedError() diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index d333a5c7609350..7ad375bf44de41 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -1,5 +1,5 @@ """Platform for climate integration.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.climate import ( ATTR_TEMPERATURE, @@ -45,7 +45,7 @@ class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntit """Representation of a climate/thermostat device within devolo Home Control.""" @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" if hasattr(self._device_instance, "multi_level_sensor_property"): return next( @@ -60,7 +60,7 @@ def current_temperature(self) -> Optional[float]: return None @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the target temperature.""" return self._value @@ -75,7 +75,7 @@ def hvac_mode(self) -> str: return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_HEAT] diff --git a/homeassistant/components/directv/__init__.py b/homeassistant/components/directv/__init__.py index 34b8c5cd30c65e..7ede8fae9469ff 100644 --- a/homeassistant/components/directv/__init__.py +++ b/homeassistant/components/directv/__init__.py @@ -1,7 +1,9 @@ """The DirecTV integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta -from typing import Any, Dict +from typing import Any from directv import DIRECTV, DIRECTVError @@ -28,7 +30,7 @@ SCAN_INTERVAL = timedelta(seconds=30) -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the DirecTV component.""" hass.data.setdefault(DOMAIN, {}) return True @@ -87,7 +89,7 @@ def name(self) -> str: return self._name @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this DirecTV receiver.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self._device_id)}, diff --git a/homeassistant/components/directv/config_flow.py b/homeassistant/components/directv/config_flow.py index fed13c63dc8f43..401ec3b39b69bc 100644 --- a/homeassistant/components/directv/config_flow.py +++ b/homeassistant/components/directv/config_flow.py @@ -1,6 +1,8 @@ """Config flow for DirecTV.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from directv import DIRECTV, DIRECTVError @@ -25,7 +27,7 @@ ERROR_UNKNOWN = "unknown" -async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -48,8 +50,8 @@ def __init__(self): self.discovery_info = {} async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -71,7 +73,7 @@ async def async_step_user( async def async_step_ssdp( self, discovery_info: DiscoveryInfoType - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle SSDP discovery.""" host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname receiver_id = None @@ -104,7 +106,7 @@ async def async_step_ssdp( async def async_step_ssdp_confirm( self, user_input: ConfigType = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a confirmation flow initiated by SSDP.""" if user_input is None: return self.async_show_form( @@ -118,7 +120,7 @@ async def async_step_ssdp_confirm( data=self.discovery_info, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index ee290ba5f2c4d6..4004592e5dcc0e 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -1,6 +1,8 @@ """Support for the DirecTV receivers.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable from directv import DIRECTV @@ -64,7 +66,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List, bool], None], + async_add_entities: Callable[[list, bool], None], ) -> bool: """Set up the DirecTV config entry.""" dtv = hass.data[DOMAIN][entry.entry_id] @@ -141,7 +143,7 @@ def name(self): return self._name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" return DEVICE_CLASS_RECEIVER diff --git a/homeassistant/components/directv/remote.py b/homeassistant/components/directv/remote.py index 64695ae3813592..b35580928acb55 100644 --- a/homeassistant/components/directv/remote.py +++ b/homeassistant/components/directv/remote.py @@ -1,7 +1,9 @@ """Support for the DIRECTV remote.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Iterable, List +from typing import Any, Callable, Iterable from directv import DIRECTV, DIRECTVError @@ -20,7 +22,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List, bool], None], + async_add_entities: Callable[[list, bool], None], ) -> bool: """Load DirecTV remote based on a config entry.""" dtv = hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index f8af118caedbe6..db0f60d14bcd62 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -1,9 +1,10 @@ """Support for DLNA DMR (Device Media Renderer).""" +from __future__ import annotations + import asyncio from datetime import timedelta import functools import logging -from typing import Optional import aiohttp from async_upnp_client import UpnpFactory @@ -116,7 +117,7 @@ async def async_start_event_handler( server_host: str, server_port: int, requester, - callback_url_override: Optional[str] = None, + callback_url_override: str | None = None, ): """Register notify view.""" hass_data = hass.data[DLNA_DMR_DATA] diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index f0899598351f1e..95edb87e947187 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -1,8 +1,10 @@ """Config flow for DSMR integration.""" +from __future__ import annotations + import asyncio from functools import partial import logging -from typing import Any, Dict, Optional +from typing import Any from async_timeout import timeout from dsmr_parser import obis_references as obis_ref @@ -133,7 +135,7 @@ def _abort_if_host_port_configured( self, port: str, host: str = None, - updates: Optional[Dict[Any, Any]] = None, + updates: dict[Any, Any] | None = None, reload_on_update: bool = True, ): """Test if host and port are already configured.""" diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index aea12a863f068a..0d2c55051e818b 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -1,10 +1,11 @@ """Support for Dutch Smart Meter (also known as Smartmeter or P1 port).""" +from __future__ import annotations + import asyncio from asyncio import CancelledError from datetime import timedelta from functools import partial import logging -from typing import Dict from dsmr_parser import obis_references as obis_ref from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader @@ -363,7 +364,7 @@ def unique_id(self) -> str: return self._unique_id @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._device_serial)}, diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index e19ab3198969dd..92392e4b51a89d 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -1,7 +1,8 @@ """Support for the Dynalite networks.""" +from __future__ import annotations import asyncio -from typing import Any, Dict, Union +from typing import Any import voluptuous as vol @@ -54,7 +55,7 @@ ) -def num_string(value: Union[int, str]) -> str: +def num_string(value: int | str) -> str: """Test if value is a string of digits, aka an integer.""" new_value = str(value) if new_value.isdigit(): @@ -105,7 +106,7 @@ def num_string(value: Union[int, str]) -> str: TEMPLATE_SCHEMA = vol.Schema({str: TEMPLATE_DATA_SCHEMA}) -def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: +def validate_area(config: dict[str, Any]) -> dict[str, Any]: """Validate that template parameters are only used if area is using the relevant template.""" conf_set = set() for template in DEFAULT_TEMPLATES: @@ -178,7 +179,7 @@ def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: ) -async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool: +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the Dynalite platform.""" conf = config.get(DOMAIN) LOGGER.debug("Setting up dynalite component config = %s", conf) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index e024ba11802882..71cecee8d43011 100644 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -1,6 +1,7 @@ """Code to handle a Dynalite bridge.""" +from __future__ import annotations -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from dynalite_devices_lib.dynalite_devices import ( CONF_AREA as dyn_CONF_AREA, @@ -23,7 +24,7 @@ class DynaliteBridge: """Manages a single Dynalite bridge.""" - def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: + def __init__(self, hass: HomeAssistant, config: dict[str, Any]) -> None: """Initialize the system based on host parameter.""" self.hass = hass self.area = {} @@ -44,12 +45,12 @@ async def async_setup(self) -> bool: LOGGER.debug("Setting up bridge - host %s", self.host) return await self.dynalite_devices.async_setup() - def reload_config(self, config: Dict[str, Any]) -> None: + def reload_config(self, config: dict[str, Any]) -> None: """Reconfigure a bridge when config changes.""" LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config) self.dynalite_devices.configure(convert_config(config)) - def update_signal(self, device: Optional[DynaliteBaseDevice] = None) -> str: + def update_signal(self, device: DynaliteBaseDevice | None = None) -> str: """Create signal to use to trigger entity update.""" if device: signal = f"dynalite-update-{self.host}-{device.unique_id}" @@ -58,7 +59,7 @@ def update_signal(self, device: Optional[DynaliteBaseDevice] = None) -> str: return signal @callback - def update_device(self, device: Optional[DynaliteBaseDevice] = None) -> None: + def update_device(self, device: DynaliteBaseDevice | None = None) -> None: """Call when a device or all devices should be updated.""" if not device: # This is used to signal connection or disconnection, so all devices may become available or not. @@ -98,7 +99,7 @@ def register_add_devices(self, platform: str, async_add_devices: Callable) -> No if platform in self.waiting_devices: self.async_add_devices[platform](self.waiting_devices[platform]) - def add_devices_when_registered(self, devices: List[DynaliteBaseDevice]) -> None: + def add_devices_when_registered(self, devices: list[DynaliteBaseDevice]) -> None: """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" for platform in PLATFORMS: platform_devices = [ diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index 4c5b2ceb7d88a4..2bd2b142252dbf 100644 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure Dynalite hub.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homeassistant import config_entries from homeassistant.const import CONF_HOST @@ -18,7 +20,7 @@ def __init__(self) -> None: """Initialize the Dynalite flow.""" self.host = None - async def async_step_import(self, import_info: Dict[str, Any]) -> Any: + async def async_step_import(self, import_info: dict[str, Any]) -> Any: """Import a new bridge as a config entry.""" LOGGER.debug("Starting async_step_import - %s", import_info) host = import_info[CONF_HOST] diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index 6a85147a2e0ad2..89a7f32b47ab07 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -1,6 +1,7 @@ """Convert the HA config to the dynalite config.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any from dynalite_devices_lib import const as dyn_const @@ -62,7 +63,7 @@ def convert_with_map(config, conf_map): return result -def convert_channel(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_channel(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for a channel.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, @@ -72,7 +73,7 @@ def convert_channel(config: Dict[str, Any]) -> Dict[str, Any]: return convert_with_map(config, my_map) -def convert_preset(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_preset(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for a preset.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, @@ -82,7 +83,7 @@ def convert_preset(config: Dict[str, Any]) -> Dict[str, Any]: return convert_with_map(config, my_map) -def convert_area(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_area(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for an area.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, @@ -114,12 +115,12 @@ def convert_area(config: Dict[str, Any]) -> Dict[str, Any]: return result -def convert_default(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_default(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for the platform defaults.""" return convert_with_map(config, {CONF_FADE: dyn_const.CONF_FADE}) -def convert_template(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_template(config: dict[str, Any]) -> dict[str, Any]: """Convert the config for a template.""" my_map = { CONF_ROOM_ON: dyn_const.CONF_ROOM_ON, @@ -135,7 +136,7 @@ def convert_template(config: Dict[str, Any]) -> Dict[str, Any]: return convert_with_map(config, my_map) -def convert_config(config: Dict[str, Any]) -> Dict[str, Any]: +def convert_config(config: dict[str, Any]) -> dict[str, Any]: """Convert a config dict by replacing component consts with library consts.""" my_map = { CONF_NAME: dyn_const.CONF_NAME, diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py index 31879c5c118f6b..2cc28002a2c145 100644 --- a/homeassistant/components/dynalite/dynalitebase.py +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -1,5 +1,7 @@ """Support for the Dynalite devices as entities.""" -from typing import Any, Callable, Dict +from __future__ import annotations + +from typing import Any, Callable from homeassistant.components.dynalite.bridge import DynaliteBridge from homeassistant.config_entries import ConfigEntry @@ -58,7 +60,7 @@ def available(self) -> bool: return self._device.available @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Device info for this entity.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 5b69092ac5d907..e646babb944c32 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -1,7 +1,8 @@ """Support for Dyson Pure Cool link fan.""" +from __future__ import annotations + import logging import math -from typing import Optional from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation from libpurecool.dyson_pure_cool import DysonPureCool @@ -243,9 +244,9 @@ def service_set_dyson_speed(self, dyson_speed: str) -> None: def turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -325,9 +326,9 @@ def __init__(self, device): def turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" From 91df3fa9049955517319e9e2fe175faab57433e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:49:01 +0100 Subject: [PATCH 1333/1818] Update typing 06 (#48039) --- .../components/ecoal_boiler/switch.py | 4 +- homeassistant/components/ecobee/climate.py | 7 ++-- homeassistant/components/esphome/__init__.py | 20 +++++---- .../components/esphome/binary_sensor.py | 6 +-- homeassistant/components/esphome/camera.py | 9 ++-- homeassistant/components/esphome/climate.py | 18 ++++---- .../components/esphome/config_flow.py | 11 ++--- homeassistant/components/esphome/cover.py | 10 ++--- .../components/esphome/entry_data.py | 28 +++++++------ homeassistant/components/esphome/fan.py | 15 +++---- homeassistant/components/esphome/light.py | 18 ++++---- homeassistant/components/esphome/sensor.py | 11 ++--- homeassistant/components/esphome/switch.py | 6 +-- homeassistant/components/essent/sensor.py | 5 ++- homeassistant/components/everlights/light.py | 5 ++- homeassistant/components/evohome/__init__.py | 20 +++++---- homeassistant/components/evohome/climate.py | 19 +++++---- .../components/evohome/water_heater.py | 5 ++- homeassistant/components/fan/__init__.py | 41 ++++++++++--------- homeassistant/components/fan/device_action.py | 6 +-- .../components/fan/device_condition.py | 4 +- .../components/fan/device_trigger.py | 4 +- .../components/fan/reproduce_state.py | 12 +++--- homeassistant/components/ffmpeg/__init__.py | 5 ++- homeassistant/components/fibaro/__init__.py | 5 ++- homeassistant/components/filter/sensor.py | 13 +++--- homeassistant/components/firmata/entity.py | 4 +- homeassistant/components/firmata/light.py | 4 +- homeassistant/components/flexit/climate.py | 5 ++- homeassistant/components/flo/binary_sensor.py | 5 +-- homeassistant/components/flo/device.py | 8 ++-- homeassistant/components/flo/entity.py | 5 ++- homeassistant/components/flo/sensor.py | 27 ++++++------ homeassistant/components/flo/switch.py | 5 +-- .../components/freebox/device_tracker.py | 9 ++-- homeassistant/components/freebox/router.py | 28 +++++++------ homeassistant/components/freebox/sensor.py | 19 +++++---- homeassistant/components/freebox/switch.py | 5 ++- homeassistant/components/fronius/sensor.py | 5 ++- homeassistant/components/frontend/__init__.py | 16 ++++---- 40 files changed, 241 insertions(+), 211 deletions(-) diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index 57a8d420c437c6..995a49554e6486 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -1,5 +1,5 @@ """Allows to configuration ecoal (esterownik.pl) pumps as switches.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.switch import SwitchEntity @@ -40,7 +40,7 @@ def __init__(self, ecoal_contr, name, state_attr): self._state = None @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the switch.""" return self._name diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index c3224410993022..47c2ff969ecf73 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -1,6 +1,7 @@ """Support for Ecobee Thermostats.""" +from __future__ import annotations + import collections -from typing import Optional import voluptuous as vol @@ -406,7 +407,7 @@ def has_humidifier_control(self): ) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the desired humidity set point.""" if self.has_humidifier_control: return self.thermostat["runtime"]["desiredHumidity"] @@ -484,7 +485,7 @@ def hvac_modes(self): return self._operation_list @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return self.thermostat["runtime"]["actualHumidity"] diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 650b630e34c878..d895fc9216d7ae 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -1,9 +1,11 @@ """Support for esphome devices.""" +from __future__ import annotations + import asyncio import functools import logging import math -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from aioesphomeapi import ( APIClient, @@ -155,7 +157,7 @@ async def send_home_assistant_state_event(event: Event) -> None: await cli.send_home_assistant_state(entity_id, new_state.state) async def _send_home_assistant_state( - entity_id: str, new_state: Optional[State] + entity_id: str, new_state: State | None ) -> None: """Forward Home Assistant states to ESPHome.""" await cli.send_home_assistant_state(entity_id, new_state.state) @@ -384,7 +386,7 @@ async def execute_service(call): async def _setup_services( - hass: HomeAssistantType, entry_data: RuntimeEntryData, services: List[UserService] + hass: HomeAssistantType, entry_data: RuntimeEntryData, services: list[UserService] ): old_services = entry_data.services.copy() to_unregister = [] @@ -461,7 +463,7 @@ async def platform_async_setup_entry( entry_data.state[component_key] = {} @callback - def async_list_entities(infos: List[EntityInfo]): + def async_list_entities(infos: list[EntityInfo]): """Update entities of this platform when entities are listed.""" old_infos = entry_data.info[component_key] new_infos = {} @@ -535,7 +537,7 @@ def _wrapper(self): class EsphomeEnumMapper: """Helper class to convert between hass and esphome enum values.""" - def __init__(self, func: Callable[[], Dict[int, str]]): + def __init__(self, func: Callable[[], dict[int, str]]): """Construct a EsphomeEnumMapper.""" self._func = func @@ -549,7 +551,7 @@ def from_hass(self, value: str) -> int: return inverse[value] -def esphome_map_enum(func: Callable[[], Dict[int, str]]): +def esphome_map_enum(func: Callable[[], dict[int, str]]): """Map esphome int enum values to hass string constants. This class has to be used as a decorator. This ensures the aioesphomeapi @@ -621,7 +623,7 @@ def _client(self) -> APIClient: return self._entry_data.client @property - def _state(self) -> Optional[EntityState]: + def _state(self) -> EntityState | None: try: return self._entry_data.state[self._component_key][self._key] except KeyError: @@ -640,14 +642,14 @@ def available(self) -> bool: return self._entry_data.available @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique id identifying the entity.""" if not self._static_info.unique_id: return None return self._static_info.unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device registry information for this entity.""" return { "connections": {(dr.CONNECTION_NETWORK_MAC, self._device_info.mac_address)} diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py index d605a48410b89f..28cc47691f56b8 100644 --- a/homeassistant/components/esphome/binary_sensor.py +++ b/homeassistant/components/esphome/binary_sensor.py @@ -1,5 +1,5 @@ """Support for ESPHome binary sensors.""" -from typing import Optional +from __future__ import annotations from aioesphomeapi import BinarySensorInfo, BinarySensorState @@ -29,11 +29,11 @@ def _static_info(self) -> BinarySensorInfo: return super()._static_info @property - def _state(self) -> Optional[BinarySensorState]: + def _state(self) -> BinarySensorState | None: return super()._state @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" if self._static_info.is_status_binary_sensor: # Status binary sensors indicated connected state. diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py index 5b8f4f0d7e6633..c868d7b320a735 100644 --- a/homeassistant/components/esphome/camera.py +++ b/homeassistant/components/esphome/camera.py @@ -1,6 +1,7 @@ """Support for ESPHome cameras.""" +from __future__ import annotations + import asyncio -from typing import Optional from aioesphomeapi import CameraInfo, CameraState @@ -42,7 +43,7 @@ def _static_info(self) -> CameraInfo: return super()._static_info @property - def _state(self) -> Optional[CameraState]: + def _state(self) -> CameraState | None: return super()._state async def async_added_to_hass(self) -> None: @@ -67,7 +68,7 @@ async def _on_state_update(self) -> None: async with self._image_cond: self._image_cond.notify_all() - async def async_camera_image(self) -> Optional[bytes]: + async def async_camera_image(self) -> bytes | None: """Return single camera image bytes.""" if not self.available: return None @@ -78,7 +79,7 @@ async def async_camera_image(self) -> Optional[bytes]: return None return self._state.image[:] - async def _async_camera_stream_image(self) -> Optional[bytes]: + async def _async_camera_stream_image(self) -> bytes | None: """Return a single camera image in a stream.""" if not self.available: return None diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index fe171d24fb938a..5d21d495ec2cdb 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -1,5 +1,5 @@ """Support for ESPHome climate devices.""" -from typing import List, Optional +from __future__ import annotations from aioesphomeapi import ( ClimateAction, @@ -134,7 +134,7 @@ def _static_info(self) -> ClimateInfo: return super()._static_info @property - def _state(self) -> Optional[ClimateState]: + def _state(self) -> ClimateState | None: return super()._state @property @@ -153,7 +153,7 @@ def temperature_unit(self) -> str: return TEMP_CELSIUS @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available operation modes.""" return [ _climate_modes.from_esphome(mode) @@ -217,12 +217,12 @@ def supported_features(self) -> int: # pylint: disable=invalid-overridden-method @esphome_state_property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" return _climate_modes.from_esphome(self._state.mode) @esphome_state_property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return current action.""" # HA has no support feature field for hvac_action if not self._static_info.supports_action: @@ -245,22 +245,22 @@ def swing_mode(self): return _swing_modes.from_esphome(self._state.swing_mode) @esphome_state_property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._state.current_temperature @esphome_state_property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._state.target_temperature @esphome_state_property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._state.target_temperature_low @esphome_state_property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._state.target_temperature_high diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index a84aa2959eac8c..45a33a0dc24fca 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -1,6 +1,7 @@ """Config flow to configure esphome component.""" +from __future__ import annotations + from collections import OrderedDict -from typing import Optional from aioesphomeapi import APIClient, APIConnectionError import voluptuous as vol @@ -23,12 +24,12 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._host: Optional[str] = None - self._port: Optional[int] = None - self._password: Optional[str] = None + self._host: str | None = None + self._port: int | None = None + self._password: str | None = None async def async_step_user( - self, user_input: Optional[ConfigType] = None, error: Optional[str] = None + self, user_input: ConfigType | None = None, error: str | None = None ): # pylint: disable=arguments-differ """Handle a flow initialized by the user.""" if user_input is not None: diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 4c42fe0d522a1a..294689d075a199 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -1,5 +1,5 @@ """Support for ESPHome covers.""" -from typing import Optional +from __future__ import annotations from aioesphomeapi import CoverInfo, CoverOperation, CoverState @@ -64,14 +64,14 @@ def assumed_state(self) -> bool: return self._static_info.assumed_state @property - def _state(self) -> Optional[CoverState]: + def _state(self) -> CoverState | None: return super()._state # https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property # pylint: disable=invalid-overridden-method @esphome_state_property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" # Check closed state with api version due to a protocol change return self._state.is_closed(self._client.api_version) @@ -87,14 +87,14 @@ def is_closing(self) -> bool: return self._state.current_operation == CoverOperation.IS_CLOSING @esphome_state_property - def current_cover_position(self) -> Optional[int]: + def current_cover_position(self) -> int | None: """Return current position of cover. 0 is closed, 100 is open.""" if not self._static_info.supports_position: return None return round(self._state.position * 100.0) @esphome_state_property - def current_cover_tilt_position(self) -> Optional[float]: + def current_cover_tilt_position(self) -> float | None: """Return current position of cover tilt. 0 is closed, 100 is open.""" if not self._static_info.supports_tilt: return None diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 58b20d18e12f42..f308239fa23415 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -1,6 +1,8 @@ """Runtime entry data for ESPHome stored in hass.data.""" +from __future__ import annotations + import asyncio -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Any, Callable from aioesphomeapi import ( COMPONENT_TYPE_TO_INFO, @@ -52,22 +54,22 @@ class RuntimeEntryData: entry_id: str = attr.ib() client: "APIClient" = attr.ib() store: Store = attr.ib() - reconnect_task: Optional[asyncio.Task] = attr.ib(default=None) - state: Dict[str, Dict[str, Any]] = attr.ib(factory=dict) - info: Dict[str, Dict[str, Any]] = attr.ib(factory=dict) + reconnect_task: asyncio.Task | None = attr.ib(default=None) + state: dict[str, dict[str, Any]] = attr.ib(factory=dict) + info: dict[str, dict[str, Any]] = attr.ib(factory=dict) # A second list of EntityInfo objects # This is necessary for when an entity is being removed. HA requires # some static info to be accessible during removal (unique_id, maybe others) # If an entity can't find anything in the info array, it will look for info here. - old_info: Dict[str, Dict[str, Any]] = attr.ib(factory=dict) + old_info: dict[str, dict[str, Any]] = attr.ib(factory=dict) - services: Dict[int, "UserService"] = attr.ib(factory=dict) + services: dict[int, "UserService"] = attr.ib(factory=dict) available: bool = attr.ib(default=False) - device_info: Optional[DeviceInfo] = attr.ib(default=None) - cleanup_callbacks: List[Callable[[], None]] = attr.ib(factory=list) - disconnect_callbacks: List[Callable[[], None]] = attr.ib(factory=list) - loaded_platforms: Set[str] = attr.ib(factory=set) + device_info: DeviceInfo | None = attr.ib(default=None) + cleanup_callbacks: list[Callable[[], None]] = attr.ib(factory=list) + disconnect_callbacks: list[Callable[[], None]] = attr.ib(factory=list) + loaded_platforms: set[str] = attr.ib(factory=set) platform_load_lock: asyncio.Lock = attr.ib(factory=asyncio.Lock) @callback @@ -87,7 +89,7 @@ def async_remove_entity( async_dispatcher_send(hass, signal) async def _ensure_platforms_loaded( - self, hass: HomeAssistantType, entry: ConfigEntry, platforms: Set[str] + self, hass: HomeAssistantType, entry: ConfigEntry, platforms: set[str] ): async with self.platform_load_lock: needed = platforms - self.loaded_platforms @@ -101,7 +103,7 @@ async def _ensure_platforms_loaded( self.loaded_platforms |= needed async def async_update_static_infos( - self, hass: HomeAssistantType, entry: ConfigEntry, infos: List[EntityInfo] + self, hass: HomeAssistantType, entry: ConfigEntry, infos: list[EntityInfo] ) -> None: """Distribute an update of static infos to all platforms.""" # First, load all platforms @@ -129,7 +131,7 @@ def async_update_device_state(self, hass: HomeAssistantType) -> None: signal = f"esphome_{self.entry_id}_on_device_update" async_dispatcher_send(hass, signal) - async def async_load_from_store(self) -> Tuple[List[EntityInfo], List[UserService]]: + async def async_load_from_store(self) -> tuple[list[EntityInfo], list[UserService]]: """Load the retained data from store and return de-serialized data.""" restored = await self.store.async_load() if restored is None: diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index bac35fdb93f8a0..5d7cf24f2c5183 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -1,6 +1,7 @@ """Support for ESPHome fans.""" +from __future__ import annotations + import math -from typing import Optional from aioesphomeapi import FanDirection, FanInfo, FanSpeed, FanState @@ -62,7 +63,7 @@ def _static_info(self) -> FanInfo: return super()._static_info @property - def _state(self) -> Optional[FanState]: + def _state(self) -> FanState | None: return super()._state @property @@ -93,9 +94,9 @@ async def async_set_percentage(self, percentage: int) -> None: async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -121,12 +122,12 @@ async def async_set_direction(self, direction: str): # pylint: disable=invalid-overridden-method @esphome_state_property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the entity is on.""" return self._state.state @esphome_state_property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if not self._static_info.supports_speed: return None diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index adbb36188fd9d1..29fd969d479e44 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -1,5 +1,5 @@ """Support for ESPHome lights.""" -from typing import List, Optional, Tuple +from __future__ import annotations from aioesphomeapi import LightInfo, LightState @@ -54,14 +54,14 @@ def _static_info(self) -> LightInfo: return super()._static_info @property - def _state(self) -> Optional[LightState]: + def _state(self) -> LightState | None: return super()._state # https://github.com/PyCQA/pylint/issues/3150 for all @esphome_state_property # pylint: disable=invalid-overridden-method @esphome_state_property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the switch is on.""" return self._state.state @@ -96,29 +96,29 @@ async def async_turn_off(self, **kwargs) -> None: await self._client.light_command(**data) @esphome_state_property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" return round(self._state.brightness * 255) @esphome_state_property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" return color_util.color_RGB_to_hs( self._state.red * 255, self._state.green * 255, self._state.blue * 255 ) @esphome_state_property - def color_temp(self) -> Optional[float]: + def color_temp(self) -> float | None: """Return the CT color value in mireds.""" return self._state.color_temperature @esphome_state_property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" return round(self._state.white * 255) @esphome_state_property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect.""" return self._state.effect @@ -140,7 +140,7 @@ def supported_features(self) -> int: return flags @property - def effect_list(self) -> List[str]: + def effect_list(self) -> list[str]: """Return the list of supported effects.""" return self._static_info.effects diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index fe9922cf0edcb1..17799e4484fb84 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -1,6 +1,7 @@ """Support for esphome sensors.""" +from __future__ import annotations + import math -from typing import Optional from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState import voluptuous as vol @@ -49,7 +50,7 @@ def _static_info(self) -> SensorInfo: return super()._static_info @property - def _state(self) -> Optional[SensorState]: + def _state(self) -> SensorState | None: return super()._state @property @@ -65,7 +66,7 @@ def force_update(self) -> bool: return self._static_info.force_update @esphome_state_property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the entity.""" if math.isnan(self._state.state): return None @@ -96,7 +97,7 @@ def _static_info(self) -> TextSensorInfo: return super()._static_info @property - def _state(self) -> Optional[TextSensorState]: + def _state(self) -> TextSensorState | None: return super()._state @property @@ -105,7 +106,7 @@ def icon(self) -> str: return self._static_info.icon @esphome_state_property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the entity.""" if self._state.missing_state: return None diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index 3f8ca90d6c5a34..992f014e829f4f 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -1,5 +1,5 @@ """Support for ESPHome switches.""" -from typing import Optional +from __future__ import annotations from aioesphomeapi import SwitchInfo, SwitchState @@ -33,7 +33,7 @@ def _static_info(self) -> SwitchInfo: return super()._static_info @property - def _state(self) -> Optional[SwitchState]: + def _state(self) -> SwitchState | None: return super()._state @property @@ -49,7 +49,7 @@ def assumed_state(self) -> bool: # https://github.com/PyCQA/pylint/issues/3150 for @esphome_state_property # pylint: disable=invalid-overridden-method @esphome_state_property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if the switch is on.""" return self._state.state diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 3ac7af315b72ab..26f7c930241436 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -1,6 +1,7 @@ """Support for Essent API.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional from pyessent import PyEssent import voluptuous as vol @@ -94,7 +95,7 @@ def __init__(self, essent_base, meter, meter_type, tariff, unit): self._unit = unit @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self._meter}-{self._type}-{self._tariff}" diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py index 243bd2913b1c06..e451b83c1fa534 100644 --- a/homeassistant/components/everlights/light.py +++ b/homeassistant/components/everlights/light.py @@ -1,7 +1,8 @@ """Support for EverLights lights.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Tuple import pyeverlights import voluptuous as vol @@ -38,7 +39,7 @@ def color_rgb_to_int(red: int, green: int, blue: int) -> int: return red * 256 * 256 + green * 256 + blue -def color_int_to_rgb(value: int) -> Tuple[int, int, int]: +def color_int_to_rgb(value: int) -> tuple[int, int, int]: """Return an RGB tuple from an integer.""" return (value >> 16, (value >> 8) & 0xFF, value & 0xFF) diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index 006fbb1610d820..8c83308a8b7673 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -2,10 +2,12 @@ Such systems include evohome, Round Thermostat, and others. """ +from __future__ import annotations + from datetime import datetime as dt, timedelta import logging import re -from typing import Any, Dict, Optional, Tuple +from typing import Any import aiohttp.client_exceptions import evohomeasync @@ -114,7 +116,7 @@ def convert_until(status_dict: dict, until_key: str) -> None: status_dict[until_key] = dt_util.as_local(dt_utc_naive).isoformat() -def convert_dict(dictionary: Dict[str, Any]) -> Dict[str, Any]: +def convert_dict(dictionary: dict[str, Any]) -> dict[str, Any]: """Recursively convert a dict's keys to snake_case.""" def convert_key(key: str) -> str: @@ -176,7 +178,7 @@ def _handle_exception(err) -> bool: async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Create a (EMEA/EU-based) Honeywell TCC system.""" - async def load_auth_tokens(store) -> Tuple[Dict, Optional[Dict]]: + async def load_auth_tokens(store) -> tuple[dict, dict | None]: app_storage = await store.async_load() tokens = dict(app_storage if app_storage else {}) @@ -435,7 +437,7 @@ async def call_client_api(self, api_function, update_state=True) -> Any: async def _update_v1_api_temps(self, *args, **kwargs) -> None: """Get the latest high-precision temperatures of the default Location.""" - def get_session_id(client_v1) -> Optional[str]: + def get_session_id(client_v1) -> str | None: user_data = client_v1.user_data if client_v1 else None return user_data.get("sessionId") if user_data else None @@ -520,7 +522,7 @@ def __init__(self, evo_broker, evo_device) -> None: self._supported_features = None self._device_state_attrs = {} - async def async_refresh(self, payload: Optional[dict] = None) -> None: + async def async_refresh(self, payload: dict | None = None) -> None: """Process any signals.""" if payload is None: self.async_schedule_update_ha_state(force_refresh=True) @@ -546,7 +548,7 @@ def should_poll(self) -> bool: return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @@ -556,7 +558,7 @@ def name(self) -> str: return self._name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the evohome-specific state attributes.""" status = self._device_state_attrs if "systemModeStatus" in status: @@ -606,7 +608,7 @@ def __init__(self, evo_broker, evo_device) -> None: self._setpoints = {} @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature of a Zone.""" if ( self._evo_broker.temps @@ -618,7 +620,7 @@ def current_temperature(self) -> Optional[float]: return self._evo_device.temperatureStatus["temperature"] @property - def setpoints(self) -> Dict[str, Any]: + def setpoints(self) -> dict[str, Any]: """Return the current/next setpoints from the schedule. Only Zones & DHW controllers (but not the TCS) can have schedules. diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index e99cae5e22e5d3..f291fcd9cb3084 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -1,7 +1,8 @@ """Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems.""" +from __future__ import annotations + from datetime import datetime as dt import logging -from typing import List, Optional from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -129,12 +130,12 @@ def __init__(self, evo_broker, evo_device) -> None: self._preset_modes = None @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return a list of available hvac operation modes.""" return list(HA_HVAC_TO_TCS) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return self._preset_modes @@ -203,7 +204,7 @@ def target_temperature(self) -> float: return self._evo_device.setpointStatus["targetHeatTemperature"] @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" if self._evo_tcs.systemModeStatus["mode"] in [EVO_AWAY, EVO_HEATOFF]: return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) @@ -268,7 +269,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: self._evo_device.cancel_temp_override() ) - async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None: + async def async_set_preset_mode(self, preset_mode: str | None) -> None: """Set the preset mode; if None, then revert to following the schedule.""" evo_preset_mode = HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW) @@ -347,7 +348,7 @@ async def async_tcs_svc_request(self, service: dict, data: dict) -> None: await self._set_tcs_mode(mode, until=until) - async def _set_tcs_mode(self, mode: str, until: Optional[dt] = None) -> None: + async def _set_tcs_mode(self, mode: str, until: dt | None = None) -> None: """Set a Controller to any of its native EVO_* operating modes.""" until = dt_util.as_utc(until) if until else None await self._evo_broker.call_client_api( @@ -361,7 +362,7 @@ def hvac_mode(self) -> str: return HVAC_MODE_OFF if tcs_mode == EVO_HEATOFF else HVAC_MODE_HEAT @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the average current temperature of the heating Zones. Controllers do not have a current temp, but one is expected by HA. @@ -374,7 +375,7 @@ def current_temperature(self) -> Optional[float]: return round(sum(temps) / len(temps), 1) if temps else None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) @@ -396,7 +397,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set an operating mode for a Controller.""" await self._set_tcs_mode(HA_HVAC_TO_TCS.get(hvac_mode)) - async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None: + async def async_set_preset_mode(self, preset_mode: str | None) -> None: """Set the preset mode; if None, then revert to 'Auto' mode.""" await self._set_tcs_mode(HA_PRESET_TO_TCS.get(preset_mode, EVO_AUTO)) diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 846c8c09155320..4e05c5534616e2 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -1,6 +1,7 @@ """Support for WaterHeater devices of (EMEA/EU) Honeywell TCC systems.""" +from __future__ import annotations + import logging -from typing import List from homeassistant.components.water_heater import ( SUPPORT_AWAY_MODE, @@ -70,7 +71,7 @@ def current_operation(self) -> str: return EVO_STATE_TO_HA[self._evo_device.stateStatus["state"]] @property - def operation_list(self) -> List[str]: + def operation_list(self) -> list[str]: """Return the list of available operations.""" return list(HA_STATE_TO_EVO) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 7a50997d76dfa1..e707de5f543cf2 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -1,9 +1,10 @@ """Provides functionality to interact with fans.""" +from __future__ import annotations + from datetime import timedelta import functools as ft import logging import math -from typing import List, Optional import voluptuous as vol @@ -274,16 +275,16 @@ async def async_set_percentage(self, percentage: int) -> None: else: await self.async_set_speed(self.percentage_to_speed(percentage)) - async def async_increase_speed(self, percentage_step: Optional[int] = None) -> None: + async def async_increase_speed(self, percentage_step: int | None = None) -> None: """Increase the speed of the fan.""" await self._async_adjust_speed(1, percentage_step) - async def async_decrease_speed(self, percentage_step: Optional[int] = None) -> None: + async def async_decrease_speed(self, percentage_step: int | None = None) -> None: """Decrease the speed of the fan.""" await self._async_adjust_speed(-1, percentage_step) async def _async_adjust_speed( - self, modifier: int, percentage_step: Optional[int] + self, modifier: int, percentage_step: int | None ) -> None: """Increase or decrease the speed of the fan.""" current_percentage = self.percentage or 0 @@ -338,9 +339,9 @@ async def async_set_direction(self, direction: str): # pylint: disable=arguments-differ def turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -348,9 +349,9 @@ def turn_on( async def async_turn_on_compat( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan. @@ -387,9 +388,9 @@ async def async_turn_on_compat( # pylint: disable=arguments-differ async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" @@ -441,7 +442,7 @@ def _implemented_speed(self): ) @property - def speed(self) -> Optional[str]: + def speed(self) -> str | None: """Return the current speed.""" if self._implemented_preset_mode: preset_mode = self.preset_mode @@ -455,7 +456,7 @@ def speed(self) -> Optional[str]: return None @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed as a percentage.""" if not self._implemented_preset_mode: if self.speed in self.preset_modes: @@ -488,7 +489,7 @@ def speed_list(self) -> list: return speeds @property - def current_direction(self) -> Optional[str]: + def current_direction(self) -> str | None: """Return the current direction of the fan.""" return None @@ -616,7 +617,7 @@ def supported_features(self) -> int: return 0 @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., auto, smart, interval, favorite. Requires SUPPORT_SET_SPEED. @@ -627,7 +628,7 @@ def preset_mode(self) -> Optional[str]: return None @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_SET_SPEED. @@ -635,7 +636,7 @@ def preset_modes(self) -> Optional[List[str]]: return preset_modes_from_speed_list(self.speed_list) -def speed_list_without_preset_modes(speed_list: List): +def speed_list_without_preset_modes(speed_list: list): """Filter out non-speeds from the speed list. The goal is to get the speeds in a list from lowest to @@ -659,7 +660,7 @@ def speed_list_without_preset_modes(speed_list: List): return [speed for speed in speed_list if speed.lower() not in _NOT_SPEEDS_FILTER] -def preset_modes_from_speed_list(speed_list: List): +def preset_modes_from_speed_list(speed_list: list): """Filter out non-preset modes from the speed list. The goal is to return only preset modes. diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index a5d35d741b640e..b42c8145470059 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Fan.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Fan devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -59,7 +59,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/fan/device_condition.py b/homeassistant/components/fan/device_condition.py index d3a8aa5c395485..9aa9620ef720e2 100644 --- a/homeassistant/components/fan/device_condition.py +++ b/homeassistant/components/fan/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Fan.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Fan devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index 95f4b429a243d4..f109cfef1170e4 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Fan.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -31,7 +31,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Fan devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index b5f0fca47b7a6b..fc4d942231bf61 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -1,8 +1,10 @@ """Reproduce an Fan state.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -44,8 +46,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -98,8 +100,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Fan states.""" await asyncio.gather( diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 4b67f06ba23b9a..4bf8de91edc184 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -1,7 +1,8 @@ """Support for FFmpeg.""" +from __future__ import annotations + import asyncio import re -from typing import Optional from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame import voluptuous as vol @@ -93,7 +94,7 @@ async def async_get_image( hass: HomeAssistantType, input_source: str, output_format: str = IMAGE_JPEG, - extra_cmd: Optional[str] = None, + extra_cmd: str | None = None, ): """Get an image from a frame of an RTSP stream.""" manager = hass.data[DATA_FFMPEG] diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 9ce284d51ab992..964c111284053b 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -1,7 +1,8 @@ """Support for the Fibaro devices.""" +from __future__ import annotations + from collections import defaultdict import logging -from typing import Optional from fiblary3.client.v4.client import Client as FibaroClient, StateHandler import voluptuous as vol @@ -496,7 +497,7 @@ def unique_id(self) -> str: return self.fibaro_device.unique_id_str @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the device.""" return self._name diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index f4be797d12ba53..d74491dd9c6eee 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -1,4 +1,6 @@ """Allows the creation of a sensor that filters state property.""" +from __future__ import annotations + from collections import Counter, deque from copy import copy from datetime import timedelta @@ -6,7 +8,6 @@ import logging from numbers import Number import statistics -from typing import Optional import voluptuous as vol @@ -394,8 +395,8 @@ def __init__( self, name, window_size: int = 1, - precision: Optional[int] = None, - entity: Optional[str] = None, + precision: int | None = None, + entity: str | None = None, ): """Initialize common attributes. @@ -463,9 +464,9 @@ class RangeFilter(Filter): def __init__( self, entity, - precision: Optional[int] = DEFAULT_PRECISION, - lower_bound: Optional[float] = None, - upper_bound: Optional[float] = None, + precision: int | None = DEFAULT_PRECISION, + lower_bound: float | None = None, + upper_bound: float | None = None, ): """Initialize Filter. diff --git a/homeassistant/components/firmata/entity.py b/homeassistant/components/firmata/entity.py index 50ab58b904608a..8f843d29272d5e 100644 --- a/homeassistant/components/firmata/entity.py +++ b/homeassistant/components/firmata/entity.py @@ -1,5 +1,5 @@ """Entity for Firmata devices.""" -from typing import Type +from __future__ import annotations from homeassistant.config_entries import ConfigEntry @@ -32,7 +32,7 @@ class FirmataPinEntity(FirmataEntity): def __init__( self, - api: Type[FirmataBoardPin], + api: type[FirmataBoardPin], config_entry: ConfigEntry, name: str, pin: FirmataPinType, diff --git a/homeassistant/components/firmata/light.py b/homeassistant/components/firmata/light.py index e95b51014136b0..3c50a559e51b8c 100644 --- a/homeassistant/components/firmata/light.py +++ b/homeassistant/components/firmata/light.py @@ -1,7 +1,7 @@ """Support for Firmata light output.""" +from __future__ import annotations import logging -from typing import Type from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -55,7 +55,7 @@ class FirmataLight(FirmataPinEntity, LightEntity): def __init__( self, - api: Type[FirmataBoardPin], + api: type[FirmataBoardPin], config_entry: ConfigEntry, name: str, pin: FirmataPinType, diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index ef3997968982e4..cf4662b98666e0 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -1,6 +1,7 @@ """Platform for Flexit AC units with CI66 Modbus adapter.""" +from __future__ import annotations + import logging -from typing import List from pyflexit.pyflexit import pyflexit import voluptuous as vol @@ -135,7 +136,7 @@ def hvac_mode(self): return self._current_operation @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index 96909b3bd9c5f9..bd623aa38bb1bd 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -1,6 +1,5 @@ """Support for Flo Water Monitor binary sensors.""" - -from typing import List +from __future__ import annotations from homeassistant.components.binary_sensor import ( DEVICE_CLASS_PROBLEM, @@ -14,7 +13,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Flo sensors from config entry.""" - devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ + devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] entities = [] diff --git a/homeassistant/components/flo/device.py b/homeassistant/components/flo/device.py index 49e0e9913767bd..50e0ccda87f47a 100644 --- a/homeassistant/components/flo/device.py +++ b/homeassistant/components/flo/device.py @@ -1,7 +1,9 @@ """Flo device object.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta -from typing import Any, Dict, Optional +from typing import Any from aioflo.api import API from aioflo.errors import RequestError @@ -26,8 +28,8 @@ def __init__( self._flo_location_id: str = location_id self._flo_device_id: str = device_id self._manufacturer: str = "Flo by Moen" - self._device_information: Optional[Dict[str, Any]] = None - self._water_usage: Optional[Dict[str, Any]] = None + self._device_information: dict[str, Any] | None = None + self._water_usage: dict[str, Any] | None = None super().__init__( hass, LOGGER, diff --git a/homeassistant/components/flo/entity.py b/homeassistant/components/flo/entity.py index 35c6e022dcf0da..878c4188815d52 100644 --- a/homeassistant/components/flo/entity.py +++ b/homeassistant/components/flo/entity.py @@ -1,6 +1,7 @@ """Base entity class for Flo entities.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import Entity @@ -36,7 +37,7 @@ def unique_id(self) -> str: return self._unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return a device description for device registry.""" return { "identifiers": {(FLO_DOMAIN, self._device.id)}, diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index d88f2b950fc47f..18ee067af5a906 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -1,6 +1,5 @@ """Support for Flo Water Monitor sensors.""" - -from typing import List, Optional +from __future__ import annotations from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -31,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Flo sensors from config entry.""" - devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ + devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] entities = [] @@ -71,7 +70,7 @@ def icon(self) -> str: return WATER_ICON @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current daily usage.""" if self._device.consumption_today is None: return None @@ -92,7 +91,7 @@ def __init__(self, device): self._state: str = None @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the current system mode.""" if not self._device.current_system_mode: return None @@ -113,7 +112,7 @@ def icon(self) -> str: return GAUGE_ICON @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current flow rate.""" if self._device.current_flow_rate is None: return None @@ -134,7 +133,7 @@ def __init__(self, name, device): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current temperature.""" if self._device.temperature is None: return None @@ -146,7 +145,7 @@ def unit_of_measurement(self) -> str: return TEMP_FAHRENHEIT @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_TEMPERATURE @@ -160,7 +159,7 @@ def __init__(self, device): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current humidity.""" if self._device.humidity is None: return None @@ -172,7 +171,7 @@ def unit_of_measurement(self) -> str: return PERCENTAGE @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_HUMIDITY @@ -186,7 +185,7 @@ def __init__(self, device): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current water pressure.""" if self._device.current_psi is None: return None @@ -198,7 +197,7 @@ def unit_of_measurement(self) -> str: return PRESSURE_PSI @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_PRESSURE @@ -212,7 +211,7 @@ def __init__(self, device): self._state: float = None @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the current battery level.""" return self._device.battery_level @@ -222,6 +221,6 @@ def unit_of_measurement(self) -> str: return PERCENTAGE @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class for this sensor.""" return DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index 895212c56a0b12..e5f00a6125fb52 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -1,6 +1,5 @@ """Switch representing the shutoff valve for the Flo by Moen integration.""" - -from typing import List +from __future__ import annotations from aioflo.location import SLEEP_MINUTE_OPTIONS, SYSTEM_MODE_HOME, SYSTEM_REVERT_MODES import voluptuous as vol @@ -23,7 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Flo switches from config entry.""" - devices: List[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ + devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] entities = [] diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index 8b1b214c33a48a..6510a29bbfc77e 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -1,6 +1,7 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +from __future__ import annotations + from datetime import datetime -from typing import Dict from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -52,7 +53,7 @@ def add_entities(router, async_add_entities, tracked): class FreeboxDevice(ScannerEntity): """Representation of a Freebox device.""" - def __init__(self, router: FreeboxRouter, device: Dict[str, any]) -> None: + def __init__(self, router: FreeboxRouter, device: dict[str, any]) -> None: """Initialize a Freebox device.""" self._router = router self._name = device["primary_name"].strip() or DEFAULT_DEVICE_NAME @@ -105,12 +106,12 @@ def icon(self) -> str: return self._icon @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index a38992320eb7a2..fbeca869d1dd0c 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,9 +1,11 @@ """Represent the Freebox router and its devices and sensors.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging import os from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any from freebox_api import Freepybox from freebox_api.api.wifi import Wifi @@ -60,11 +62,11 @@ def __init__(self, hass: HomeAssistantType, entry: ConfigEntry) -> None: self._sw_v = None self._attrs = {} - self.devices: Dict[str, Dict[str, Any]] = {} - self.disks: Dict[int, Dict[str, Any]] = {} - self.sensors_temperature: Dict[str, int] = {} - self.sensors_connection: Dict[str, float] = {} - self.call_list: List[Dict[str, Any]] = [] + self.devices: dict[str, dict[str, Any]] = {} + self.disks: dict[int, dict[str, Any]] = {} + self.sensors_temperature: dict[str, int] = {} + self.sensors_connection: dict[str, float] = {} + self.call_list: list[dict[str, Any]] = [] self._unsub_dispatcher = None self.listeners = [] @@ -91,7 +93,7 @@ async def setup(self) -> None: self.hass, self.update_all, SCAN_INTERVAL ) - async def update_all(self, now: Optional[datetime] = None) -> None: + async def update_all(self, now: datetime | None = None) -> None: """Update all Freebox platforms.""" await self.update_device_trackers() await self.update_sensors() @@ -99,7 +101,7 @@ async def update_all(self, now: Optional[datetime] = None) -> None: async def update_device_trackers(self) -> None: """Update Freebox devices.""" new_device = False - fbx_devices: [Dict[str, Any]] = await self._api.lan.get_hosts_list() + fbx_devices: [dict[str, Any]] = await self._api.lan.get_hosts_list() # Adds the Freebox itself fbx_devices.append( @@ -129,7 +131,7 @@ async def update_device_trackers(self) -> None: async def update_sensors(self) -> None: """Update Freebox sensors.""" # System sensors - syst_datas: Dict[str, Any] = await self._api.system.get_config() + syst_datas: dict[str, Any] = await self._api.system.get_config() # According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree. # Name and id of sensors may vary under Freebox devices. @@ -137,7 +139,7 @@ async def update_sensors(self) -> None: self.sensors_temperature[sensor["name"]] = sensor["value"] # Connection sensors - connection_datas: Dict[str, Any] = await self._api.connection.get_status() + connection_datas: dict[str, Any] = await self._api.connection.get_status() for sensor_key in CONNECTION_SENSORS: self.sensors_connection[sensor_key] = connection_datas[sensor_key] @@ -161,7 +163,7 @@ async def update_sensors(self) -> None: async def _update_disks_sensors(self) -> None: """Update Freebox disks.""" # None at first request - fbx_disks: [Dict[str, Any]] = await self._api.storage.get_disks() or [] + fbx_disks: [dict[str, Any]] = await self._api.storage.get_disks() or [] for fbx_disk in fbx_disks: self.disks[fbx_disk["id"]] = fbx_disk @@ -178,7 +180,7 @@ async def close(self) -> None: self._api = None @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device information.""" return { "connections": {(CONNECTION_NETWORK_MAC, self.mac)}, @@ -204,7 +206,7 @@ def signal_sensor_update(self) -> str: return f"{DOMAIN}-{self._host}-sensor-update" @property - def sensors(self) -> Dict[str, Any]: + def sensors(self) -> dict[str, Any]: """Return sensors.""" return {**self.sensors_temperature, **self.sensors_connection} diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index d014d85f6dca1c..9f125ff69bc63b 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,6 +1,7 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" +from __future__ import annotations + import logging -from typing import Dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND @@ -77,7 +78,7 @@ class FreeboxSensor(Entity): """Representation of a Freebox sensor.""" def __init__( - self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any] + self, router: FreeboxRouter, sensor_type: str, sensor: dict[str, any] ) -> None: """Initialize a Freebox sensor.""" self._state = None @@ -129,7 +130,7 @@ def device_class(self) -> str: return self._device_class @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return self._router.device_info @@ -160,7 +161,7 @@ class FreeboxCallSensor(FreeboxSensor): """Representation of a Freebox call sensor.""" def __init__( - self, router: FreeboxRouter, sensor_type: str, sensor: Dict[str, any] + self, router: FreeboxRouter, sensor_type: str, sensor: dict[str, any] ) -> None: """Initialize a Freebox call sensor.""" super().__init__(router, sensor_type, sensor) @@ -180,7 +181,7 @@ def async_update_state(self) -> None: self._state = len(self._call_list_for_type) @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return device specific state attributes.""" return { dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"] @@ -194,10 +195,10 @@ class FreeboxDiskSensor(FreeboxSensor): def __init__( self, router: FreeboxRouter, - disk: Dict[str, any], - partition: Dict[str, any], + disk: dict[str, any], + partition: dict[str, any], sensor_type: str, - sensor: Dict[str, any], + sensor: dict[str, any], ) -> None: """Initialize a Freebox disk sensor.""" super().__init__(router, sensor_type, sensor) @@ -207,7 +208,7 @@ def __init__( self._unique_id = f"{self._router.mac} {sensor_type} {self._disk['id']} {self._partition['id']}" @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._disk["id"])}, diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index b1cfc93eb53b90..a15a86f46d879b 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -1,6 +1,7 @@ """Support for Freebox Delta, Revolution and Mini 4K.""" +from __future__ import annotations + import logging -from typing import Dict from freebox_api.exceptions import InsufficientPermissionsError @@ -48,7 +49,7 @@ def is_on(self) -> bool: return self._state @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return self._router.device_info diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 02ff760e574f3b..7e578701e2caa5 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -1,8 +1,9 @@ """Support for Fronius devices.""" +from __future__ import annotations + import copy from datetime import timedelta import logging -from typing import Dict from pyfronius import Fronius import voluptuous as vol @@ -195,7 +196,7 @@ async def async_update(self): for sensor in self._registered_sensors: sensor.async_schedule_update_ha_state(True) - async def _update(self) -> Dict: + async def _update(self) -> dict: """Return values of interest.""" async def register(self, sensor): diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index bb503ee86733df..0176987274e717 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -1,10 +1,12 @@ """Handle the frontend for Home Assistant.""" +from __future__ import annotations + import json import logging import mimetypes import os import pathlib -from typing import Any, Dict, Optional, Set, Tuple +from typing import Any from aiohttp import hdrs, web, web_urldispatcher import jinja2 @@ -119,19 +121,19 @@ class Panel: """Abstract class for panels.""" # Name of the webcomponent - component_name: Optional[str] = None + component_name: str | None = None # Icon to show in the sidebar - sidebar_icon: Optional[str] = None + sidebar_icon: str | None = None # Title to show in the sidebar - sidebar_title: Optional[str] = None + sidebar_title: str | None = None # Url to show the panel in the frontend - frontend_url_path: Optional[str] = None + frontend_url_path: str | None = None # Config to pass to the webcomponent - config: Optional[Dict[str, Any]] = None + config: dict[str, Any] | None = None # If the panel should only be visible to admins require_admin = False @@ -443,7 +445,7 @@ def url_for(self, **kwargs: str) -> URL: async def resolve( self, request: web.Request - ) -> Tuple[Optional[web_urldispatcher.UrlMappingMatchInfo], Set[str]]: + ) -> tuple[web_urldispatcher.UrlMappingMatchInfo | None, set[str]]: """Resolve resource. Return (UrlMappingMatchInfo, allowed_methods) pair. From f625e324dd52f51d7e049920d88c2086909010dc Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 18 Mar 2021 00:07:07 +0000 Subject: [PATCH 1334/1818] [ci skip] Translation update --- .../components/adguard/translations/ko.json | 4 +- .../components/almond/translations/ko.json | 4 +- .../components/august/translations/ca.json | 16 +++++++ .../components/august/translations/de.json | 16 +++++++ .../components/august/translations/et.json | 16 +++++++ .../components/august/translations/hu.json | 15 ++++++ .../components/august/translations/ko.json | 16 +++++++ .../components/august/translations/no.json | 16 +++++++ .../components/august/translations/ru.json | 16 +++++++ .../august/translations/zh-Hant.json | 16 +++++++ .../azure_devops/translations/ko.json | 2 +- .../bmw_connected_drive/translations/de.json | 1 + .../components/braviatv/translations/ko.json | 4 +- .../components/deconz/translations/ko.json | 4 +- .../components/dexcom/translations/nl.json | 4 +- .../dialogflow/translations/ko.json | 2 +- .../faa_delays/translations/hu.json | 1 + .../components/flume/translations/ko.json | 2 +- .../fritzbox_callmonitor/translations/ko.json | 2 +- .../components/geofency/translations/ko.json | 2 +- .../components/goalzero/translations/de.json | 3 +- .../components/gpslogger/translations/ko.json | 2 +- .../components/hassio/translations/ko.json | 2 +- .../components/hive/translations/hu.json | 48 +++++++++++++++++++ .../huawei_lte/translations/ko.json | 2 +- .../components/hue/translations/nl.json | 12 ++++- .../components/ifttt/translations/ko.json | 2 +- .../input_boolean/translations/ko.json | 2 +- .../input_datetime/translations/ko.json | 2 +- .../input_number/translations/ko.json | 2 +- .../input_select/translations/ko.json | 2 +- .../input_text/translations/ko.json | 2 +- .../components/kodi/translations/de.json | 1 + .../components/locative/translations/ko.json | 2 +- .../components/lock/translations/ko.json | 2 +- .../lutron_caseta/translations/de.json | 21 +++++++- .../lutron_caseta/translations/hu.json | 3 +- .../components/mailgun/translations/ko.json | 2 +- .../motion_blinds/translations/de.json | 8 +++- .../components/mqtt/translations/ko.json | 4 +- .../components/mysensors/translations/hu.json | 8 ++++ .../components/netatmo/translations/hu.json | 13 ++++- .../ovo_energy/translations/ko.json | 2 +- .../components/ozw/translations/de.json | 13 ++++- .../components/ozw/translations/ko.json | 18 +++---- .../panasonic_viera/translations/ko.json | 4 +- .../philips_js/translations/ca.json | 4 +- .../philips_js/translations/de.json | 3 +- .../philips_js/translations/et.json | 7 +++ .../philips_js/translations/hu.json | 7 +++ .../philips_js/translations/ko.json | 4 +- .../philips_js/translations/no.json | 7 +++ .../philips_js/translations/ru.json | 7 +++ .../philips_js/translations/zh-Hant.json | 7 +++ .../components/pi_hole/translations/hu.json | 1 + .../components/plaato/translations/de.json | 5 ++ .../components/plaato/translations/hu.json | 4 ++ .../components/plaato/translations/ko.json | 2 +- .../components/plex/translations/ko.json | 2 +- .../components/plugwise/translations/nl.json | 1 + .../components/rfxtrx/translations/de.json | 17 ++++++- .../components/samsungtv/translations/ko.json | 2 +- .../screenlogic/translations/ca.json | 39 +++++++++++++++ .../screenlogic/translations/de.json | 39 +++++++++++++++ .../screenlogic/translations/et.json | 39 +++++++++++++++ .../screenlogic/translations/hu.json | 38 +++++++++++++++ .../screenlogic/translations/ko.json | 39 +++++++++++++++ .../screenlogic/translations/no.json | 39 +++++++++++++++ .../screenlogic/translations/ru.json | 39 +++++++++++++++ .../screenlogic/translations/zh-Hant.json | 39 +++++++++++++++ .../components/sensor/translations/hu.json | 4 ++ .../components/smarthab/translations/nl.json | 4 +- .../somfy_mylink/translations/de.json | 7 ++- .../somfy_mylink/translations/hu.json | 10 ++++ .../components/sonarr/translations/de.json | 1 + .../components/traccar/translations/ko.json | 2 +- .../components/tuya/translations/de.json | 14 ++++++ .../components/tuya/translations/ko.json | 4 +- .../components/twilio/translations/ko.json | 2 +- .../components/unifi/translations/de.json | 1 + .../components/unifi/translations/ko.json | 6 +-- .../components/upcloud/translations/ko.json | 2 +- .../components/upnp/translations/ko.json | 2 +- .../components/verisure/translations/hu.json | 46 ++++++++++++++++++ .../components/vizio/translations/ko.json | 4 +- .../water_heater/translations/hu.json | 11 +++++ .../xiaomi_aqara/translations/ko.json | 2 +- .../components/zwave_js/translations/de.json | 7 ++- .../components/zwave_js/translations/ko.json | 28 +++++------ 89 files changed, 802 insertions(+), 87 deletions(-) create mode 100644 homeassistant/components/hive/translations/hu.json create mode 100644 homeassistant/components/screenlogic/translations/ca.json create mode 100644 homeassistant/components/screenlogic/translations/de.json create mode 100644 homeassistant/components/screenlogic/translations/et.json create mode 100644 homeassistant/components/screenlogic/translations/hu.json create mode 100644 homeassistant/components/screenlogic/translations/ko.json create mode 100644 homeassistant/components/screenlogic/translations/no.json create mode 100644 homeassistant/components/screenlogic/translations/ru.json create mode 100644 homeassistant/components/screenlogic/translations/zh-Hant.json create mode 100644 homeassistant/components/verisure/translations/hu.json diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index c60d16e53b19e4..8564ba19f3c8ce 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -9,8 +9,8 @@ }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c AdGuard Home\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 AdGuard Home" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c AdGuard Home\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 AdGuard Home" }, "user": { "data": { diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index 9fe759a6ba4f5f..cd9d4d67874f7d 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -8,8 +8,8 @@ }, "step": { "hassio_confirm": { - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Almond\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 Almond" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc73c\ub85c Almond\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 Almond" }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" diff --git a/homeassistant/components/august/translations/ca.json b/homeassistant/components/august/translations/ca.json index f530b2cdc5925d..8faa12e27573f3 100644 --- a/homeassistant/components/august/translations/ca.json +++ b/homeassistant/components/august/translations/ca.json @@ -10,6 +10,13 @@ "unknown": "Error inesperat" }, "step": { + "reauth_validate": { + "data": { + "password": "Contrasenya" + }, + "description": "Introdueix la contrasenya per a {username}.", + "title": "Torna a autenticar compte d'August" + }, "user": { "data": { "login_method": "M\u00e8tode d'inici de sessi\u00f3", @@ -20,6 +27,15 @@ "description": "Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'email', el nom d'usuari \u00e9s l'adre\u00e7a de correu electr\u00f2nic. Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'phone', el nom d'usuari \u00e9s el n\u00famero de tel\u00e8fon en el format \"+NNNNNNNNN\".", "title": "Configuraci\u00f3 de compte August" }, + "user_validate": { + "data": { + "login_method": "M\u00e8tode d'inici de sessi\u00f3", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'email', el nom d'usuari \u00e9s l'adre\u00e7a de correu electr\u00f2nic. Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'phone', el nom d'usuari \u00e9s el n\u00famero de tel\u00e8fon en format \"+NNNNNNNNN\".", + "title": "Configuraci\u00f3 de compte August" + }, "validation": { "data": { "code": "Codi de verificaci\u00f3" diff --git a/homeassistant/components/august/translations/de.json b/homeassistant/components/august/translations/de.json index 3a5bd70f1af12f..ef525fb665df3a 100644 --- a/homeassistant/components/august/translations/de.json +++ b/homeassistant/components/august/translations/de.json @@ -10,6 +10,13 @@ "unknown": "Unerwarteter Fehler" }, "step": { + "reauth_validate": { + "data": { + "password": "Passwort" + }, + "description": "Gib das Passwort f\u00fcr {username} ein.", + "title": "August-Konto erneut authentifizieren" + }, "user": { "data": { "login_method": "Anmeldemethode", @@ -20,6 +27,15 @@ "description": "Wenn die Anmeldemethode \"E-Mail\" lautet, ist Benutzername die E-Mail-Adresse. Wenn die Anmeldemethode \"Telefon\" ist, ist Benutzername die Telefonnummer im Format \"+ NNNNNNNNN\".", "title": "Richten Sie ein August-Konto ein" }, + "user_validate": { + "data": { + "login_method": "Anmeldemethode", + "password": "Passwort", + "username": "Benutzername" + }, + "description": "Wenn die Anmeldemethode \"E-Mail\" lautet, ist Benutzername die E-Mail-Adresse. Wenn die Anmeldemethode \"Telefon\" ist, ist Benutzername die Telefonnummer im Format \"+ NNNNNNNNN\".", + "title": "Richte ein August-Konto ein" + }, "validation": { "data": { "code": "Verifizierungs-Code" diff --git a/homeassistant/components/august/translations/et.json b/homeassistant/components/august/translations/et.json index 0b455b06d00552..69cd9e66ce380b 100644 --- a/homeassistant/components/august/translations/et.json +++ b/homeassistant/components/august/translations/et.json @@ -10,6 +10,13 @@ "unknown": "Tundmatu viga" }, "step": { + "reauth_validate": { + "data": { + "password": "Salas\u00f5na" + }, + "description": "Sisesta kasutaja {username} salas\u00f5na.", + "title": "Autendi Augusti konto uuesti" + }, "user": { "data": { "login_method": "Sisselogimismeetod", @@ -20,6 +27,15 @@ "description": "Kui sisselogimismeetod on \"e-post\" on kasutajanimi e-posti aadress. Kui sisselogimismeetod on \"telefon\" on kasutajanimi telefoninumber vormingus \"+NNNNNNNNN\".", "title": "Seadista Augusti sidumise konto" }, + "user_validate": { + "data": { + "login_method": "Sisselogimismeetod", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "description": "Kui sisselogimismeetod on \"e-post\" on kasutajanimi e-posti aadress. Kui sisselogimismeetod on \"telefon\" on kasutajanimi telefoninumber vormingus \"+NNNNNNNNN\".", + "title": "Seadista Augusti sidumise konto" + }, "validation": { "data": { "code": "Kinnituskood" diff --git a/homeassistant/components/august/translations/hu.json b/homeassistant/components/august/translations/hu.json index dd2f600435468f..1bced4e1036cd1 100644 --- a/homeassistant/components/august/translations/hu.json +++ b/homeassistant/components/august/translations/hu.json @@ -10,6 +10,13 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "reauth_validate": { + "data": { + "password": "Jelsz\u00f3" + }, + "description": "Add meg a(z) {username} jelszav\u00e1t.", + "title": "August fi\u00f3k \u00fajrahiteles\u00edt\u00e9se" + }, "user": { "data": { "password": "Jelsz\u00f3", @@ -17,6 +24,14 @@ "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } }, + "user_validate": { + "data": { + "login_method": "Bejelentkez\u00e9si m\u00f3d", + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "title": "August fi\u00f3k be\u00e1ll\u00edt\u00e1sa" + }, "validation": { "data": { "code": "Ellen\u0151rz\u0151 k\u00f3d" diff --git a/homeassistant/components/august/translations/ko.json b/homeassistant/components/august/translations/ko.json index e7aed3d4c2c24d..f3bc64a706c67e 100644 --- a/homeassistant/components/august/translations/ko.json +++ b/homeassistant/components/august/translations/ko.json @@ -10,6 +10,13 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "reauth_validate": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638" + }, + "description": "{username}\uc758 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "August \uacc4\uc815 \uc7ac\uc778\uc99d\ud558\uae30" + }, "user": { "data": { "login_method": "\ub85c\uadf8\uc778 \ubc29\ubc95", @@ -20,6 +27,15 @@ "description": "\ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc774\uba54\uc77c'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc804\ud654\ubc88\ud638'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 '+NNNNNNNNN' \ud615\uc2dd\uc758 \uc804\ud654\ubc88\ud638\uc785\ub2c8\ub2e4.", "title": "August \uacc4\uc815 \uc124\uc815\ud558\uae30" }, + "user_validate": { + "data": { + "login_method": "\ub85c\uadf8\uc778 \ubc29\ubc95", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "\ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc774\uba54\uc77c'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc804\ud654\ubc88\ud638'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 '+NNNNNNNNN' \ud615\uc2dd\uc758 \uc804\ud654\ubc88\ud638\uc785\ub2c8\ub2e4.", + "title": "August \uacc4\uc815 \uc124\uc815\ud558\uae30" + }, "validation": { "data": { "code": "\uc778\uc99d \ucf54\ub4dc" diff --git a/homeassistant/components/august/translations/no.json b/homeassistant/components/august/translations/no.json index ae314897e74d3f..d90e7f8080a1a7 100644 --- a/homeassistant/components/august/translations/no.json +++ b/homeassistant/components/august/translations/no.json @@ -10,6 +10,13 @@ "unknown": "Uventet feil" }, "step": { + "reauth_validate": { + "data": { + "password": "Passord" + }, + "description": "Skriv inn passordet for {username} .", + "title": "Godkjenn en August-konto p\u00e5 nytt" + }, "user": { "data": { "login_method": "P\u00e5loggingsmetode", @@ -20,6 +27,15 @@ "description": "Hvis p\u00e5loggingsmetoden er 'e-post', er brukernavnet e-postadressen. Hvis p\u00e5loggingsmetoden er 'telefon', er brukernavn telefonnummeret i formatet '+ NNNNNNNNN'.", "title": "Sett opp en August konto" }, + "user_validate": { + "data": { + "login_method": "P\u00e5loggingsmetode", + "password": "Passord", + "username": "Brukernavn" + }, + "description": "Hvis p\u00e5loggingsmetoden er 'e-post', er brukernavnet e-postadressen. Hvis p\u00e5loggingsmetoden er 'telefon', er brukernavn telefonnummeret i formatet '+ NNNNNNNNN'.", + "title": "Sett opp en August konto" + }, "validation": { "data": { "code": "Bekreftelseskode" diff --git a/homeassistant/components/august/translations/ru.json b/homeassistant/components/august/translations/ru.json index 277fd6abec28e8..0263ef6ee18a57 100644 --- a/homeassistant/components/august/translations/ru.json +++ b/homeassistant/components/august/translations/ru.json @@ -10,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": "\u0412\u0432\u0435\u0434\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 \u043f\u0440\u043e\u0444\u0438\u043b\u044f" + }, "user": { "data": { "login_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", @@ -20,6 +27,15 @@ "description": "\u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 '+NNNNNNNNN'.", "title": "August" }, + "user_validate": { + "data": { + "login_method": "\u0421\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + "description": "\u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'email', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043e 'phone', \u0442\u043e \u043b\u043e\u0433\u0438\u043d\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 '+NNNNNNNNN'.", + "title": "August" + }, "validation": { "data": { "code": "\u041a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f" diff --git a/homeassistant/components/august/translations/zh-Hant.json b/homeassistant/components/august/translations/zh-Hant.json index d2721cd33cc31b..ab157e3da3c08a 100644 --- a/homeassistant/components/august/translations/zh-Hant.json +++ b/homeassistant/components/august/translations/zh-Hant.json @@ -10,6 +10,13 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "reauth_validate": { + "data": { + "password": "\u5bc6\u78bc" + }, + "description": "\u8f38\u5165{username} \u5bc6\u78bc", + "title": "\u91cd\u65b0\u8a8d\u8b49 August \u5e33\u865f" + }, "user": { "data": { "login_method": "\u767b\u5165\u65b9\u5f0f", @@ -20,6 +27,15 @@ "description": "\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u90f5\u4ef6\u300cemail\u300d\u3001\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u96fb\u5b50\u90f5\u4ef6\u4f4d\u5740\u3002\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u96fb\u8a71\u300cphone\u300d\u3001\u5247\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u5305\u542b\u570b\u78bc\u4e4b\u96fb\u8a71\u865f\u78bc\uff0c\u5982\u300c+NNNNNNNNN\u300d\u3002", "title": "\u8a2d\u5b9a August \u5e33\u865f" }, + "user_validate": { + "data": { + "login_method": "\u767b\u5165\u65b9\u5f0f", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u90f5\u4ef6\u300cemail\u300d\u3001\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u96fb\u5b50\u90f5\u4ef6\u4f4d\u5740\u3002\u5047\u5982\u767b\u5165\u65b9\u5f0f\u70ba\u96fb\u8a71\u300cphone\u300d\u3001\u5247\u4f7f\u7528\u8005\u540d\u7a31\u70ba\u5305\u542b\u570b\u78bc\u4e4b\u96fb\u8a71\u865f\u78bc\uff0c\u5982\u300c+NNNNNNNNN\u300d\u3002", + "title": "\u8a2d\u5b9a August \u5e33\u865f" + }, "validation": { "data": { "code": "\u9a57\u8b49\u78bc" diff --git a/homeassistant/components/azure_devops/translations/ko.json b/homeassistant/components/azure_devops/translations/ko.json index 4a8807e5f67605..cdb67cf77dfd61 100644 --- a/homeassistant/components/azure_devops/translations/ko.json +++ b/homeassistant/components/azure_devops/translations/ko.json @@ -24,7 +24,7 @@ "personal_access_token": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 (PAT)", "project": "\ud504\ub85c\uc81d\ud2b8" }, - "description": "\ud504\ub85c\uc81d\ud2b8\uc5d0 \uc561\uc138\uc2a4\ud560 Azure DevOps \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc740 \uac1c\uc778 \ud504\ub85c\uc81d\ud2b8\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4.", + "description": "\ud504\ub85c\uc81d\ud2b8\uc5d0 \uc811\uadfc\ud560 Azure DevOps \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc740 \uac1c\uc778 \ud504\ub85c\uc81d\ud2b8\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4.", "title": "Azure DevOps \ud504\ub85c\uc81d\ud2b8 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/bmw_connected_drive/translations/de.json b/homeassistant/components/bmw_connected_drive/translations/de.json index 12a870b4cc9181..d274719d7d0d0d 100644 --- a/homeassistant/components/bmw_connected_drive/translations/de.json +++ b/homeassistant/components/bmw_connected_drive/translations/de.json @@ -11,6 +11,7 @@ "user": { "data": { "password": "Passwort", + "region": "ConnectedDrive Region", "username": "Benutzername" } } diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index c20c6b2eb7b771..7bad0f0047c558 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "no_ip_control": "TV \uc5d0\uc11c IP \uc81c\uc5b4\uac00 \ube44\ud65c\uc131\ud654\ub418\uc5c8\uac70\ub098 TV \uac00 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." + "no_ip_control": "TV\uc5d0\uc11c IP \uc81c\uc5b4\uac00 \ube44\ud65c\uc131\ud654\ub418\uc5c8\uac70\ub098 TV\uac00 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", @@ -21,7 +21,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Sony Bravia TV \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/braviatv \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\nTV \uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "description": "Sony Bravia TV \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00 \uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/braviatv \ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n\nTV\uac00 \ucf1c\uc838 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index ade0f11b7599f2..811b2400ddd490 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -14,8 +14,8 @@ "flow_title": "deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774 ({host})", "step": { "hassio_confirm": { - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" }, "link": { "description": "Home Assistant\uc5d0 \ub4f1\ub85d\ud558\ub824\uba74 deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc7a0\uae08 \ud574\uc81c\ud574\uc8fc\uc138\uc694.\n\n 1. deCONZ \uc124\uc815 -> \uac8c\uc774\ud2b8\uc6e8\uc774 -> \uace0\uae09\uc73c\ub85c \uc774\ub3d9\ud574\uc8fc\uc138\uc694\n 2. \"\uc571 \uc778\uc99d\ud558\uae30\" \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694", diff --git a/homeassistant/components/dexcom/translations/nl.json b/homeassistant/components/dexcom/translations/nl.json index 6c5027efaaed94..9be09aff0c8269 100644 --- a/homeassistant/components/dexcom/translations/nl.json +++ b/homeassistant/components/dexcom/translations/nl.json @@ -14,7 +14,9 @@ "password": "Wachtwoord", "server": "Server", "username": "Gebruikersnaam" - } + }, + "description": "Voer Dexcom Share-gegevens in", + "title": "Dexcom integratie instellen" } } }, diff --git a/homeassistant/components/dialogflow/translations/ko.json b/homeassistant/components/dialogflow/translations/ko.json index 2cd6d208f0089a..e0414018787777 100644 --- a/homeassistant/components/dialogflow/translations/ko.json +++ b/homeassistant/components/dialogflow/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow \uc6f9 \ud6c5]({dialogflow_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/faa_delays/translations/hu.json b/homeassistant/components/faa_delays/translations/hu.json index 95dfabc213a3e8..c511f42a726bc9 100644 --- a/homeassistant/components/faa_delays/translations/hu.json +++ b/homeassistant/components/faa_delays/translations/hu.json @@ -13,6 +13,7 @@ "data": { "id": "Rep\u00fcl\u0151t\u00e9r" }, + "description": "Amerikai rep\u00fcl\u0151t\u00e9ri k\u00f3d be\u00edr\u00e1sa IATA form\u00e1tumban", "title": "FAA Delays" } } diff --git a/homeassistant/components/flume/translations/ko.json b/homeassistant/components/flume/translations/ko.json index b700854ab5708c..c82f0a990d8509 100644 --- a/homeassistant/components/flume/translations/ko.json +++ b/homeassistant/components/flume/translations/ko.json @@ -16,7 +16,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "Flume Personal API \uc5d0 \uc561\uc138\uc2a4 \ud558\ub824\uba74 https://portal.flumetech.com/settings#token \uc5d0\uc11c '\ud074\ub77c\uc774\uc5b8\ud2b8 ID'\ubc0f '\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf'\uc744 \uc694\uccad\ud574\uc57c \ud569\ub2c8\ub2e4.", + "description": "Flume Personal API \uc5d0 \uc811\uadfc\ud558\ub824\uba74 https://portal.flumetech.com/settings#token \uc5d0\uc11c '\ud074\ub77c\uc774\uc5b8\ud2b8 ID'\ubc0f '\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf'\uc744 \uc694\uccad\ud574\uc57c \ud569\ub2c8\ub2e4.", "title": "Flume \uacc4\uc815\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/fritzbox_callmonitor/translations/ko.json b/homeassistant/components/fritzbox_callmonitor/translations/ko.json index a9bfff9a5e56f9..51f4ccec35aa73 100644 --- a/homeassistant/components/fritzbox_callmonitor/translations/ko.json +++ b/homeassistant/components/fritzbox_callmonitor/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "insufficient_permissions": "AVM FRIZ!Box \uc124\uc815 \ubc0f \uc804\ud654\ubc88\ud638\ubd80\uc5d0 \uc561\uc138\uc2a4\uc5d0 \ud544\uc694\ud55c \uc0ac\uc6a9\uc790\uc758 \uad8c\ud55c\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4.", + "insufficient_permissions": "AVM FRIZ!Box \uc124\uc815 \ubc0f \uc804\ud654\ubc88\ud638\ubd80\uc5d0 \ud544\uc694\ud55c \uc0ac\uc6a9\uc790 \uc811\uadfc \uad8c\ud55c\uc774 \ubd80\uc871\ud569\ub2c8\ub2e4.", "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { diff --git a/homeassistant/components/geofency/translations/ko.json b/homeassistant/components/geofency/translations/ko.json index 2482ccca454e63..b9303110e358c7 100644 --- a/homeassistant/components/geofency/translations/ko.json +++ b/homeassistant/components/geofency/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/goalzero/translations/de.json b/homeassistant/components/goalzero/translations/de.json index 7d8962cdb1164c..3916b987981764 100644 --- a/homeassistant/components/goalzero/translations/de.json +++ b/homeassistant/components/goalzero/translations/de.json @@ -13,7 +13,8 @@ "data": { "host": "Host", "name": "Name" - } + }, + "description": "Zun\u00e4chst musst du die Goal Zero App herunterladen: https://www.goalzero.com/product-features/yeti-app/\n\nFolge den Anweisungen, um deinen Yeti mit deinem Wifi-Netzwerk zu verbinden. Bekomme dann die Host-IP von deinem Router. DHCP muss in den Router-Einstellungen f\u00fcr das Ger\u00e4t richtig eingerichtet werden, um sicherzustellen, dass sich die Host-IP nicht \u00e4ndert. Schaue hierzu im Benutzerhandbuch deines Routers nach." } } } diff --git a/homeassistant/components/gpslogger/translations/ko.json b/homeassistant/components/gpslogger/translations/ko.json index 7a4e8b37c2e111..3d32cb44736b3f 100644 --- a/homeassistant/components/gpslogger/translations/ko.json +++ b/homeassistant/components/gpslogger/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/hassio/translations/ko.json b/homeassistant/components/hassio/translations/ko.json index 0acdd0c77a12f8..aba9a665f703e3 100644 --- a/homeassistant/components/hassio/translations/ko.json +++ b/homeassistant/components/hassio/translations/ko.json @@ -7,7 +7,7 @@ "docker_version": "Docker \ubc84\uc804", "healthy": "\uc2dc\uc2a4\ud15c \uc0c1\ud0dc", "host_os": "\ud638\uc2a4\ud2b8 \uc6b4\uc601 \uccb4\uc81c", - "installed_addons": "\uc124\uce58\ub41c \ucd94\uac00 \uae30\ub2a5", + "installed_addons": "\uc124\uce58\ub41c \uc560\ub4dc\uc628", "supervisor_api": "Supervisor API", "supervisor_version": "Supervisor \ubc84\uc804", "supported": "\uc9c0\uc6d0 \uc5ec\ubd80", diff --git a/homeassistant/components/hive/translations/hu.json b/homeassistant/components/hive/translations/hu.json new file mode 100644 index 00000000000000..80c6a7e40f11c9 --- /dev/null +++ b/homeassistant/components/hive/translations/hu.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", + "unknown_entry": "Nem tal\u00e1lhat\u00f3 megl\u00e9v\u0151 bejegyz\u00e9s." + }, + "error": { + "invalid_code": "Nem siker\u00fclt bejelentkezni a Hive-ba. A k\u00e9tfaktoros hiteles\u00edt\u00e9si k\u00f3d helytelen volt.", + "invalid_password": "Nem siker\u00fclt bejelentkezni a Hive-ba. Helytelen jelsz\u00f3, pr\u00f3b\u00e1lkozz \u00fajra.", + "invalid_username": "Nem siker\u00fclt bejelentkezni a Hive-ba. Az email c\u00edmedet nem siker\u00fclt felismerni.", + "no_internet_available": "A Hive-hoz val\u00f3 csatlakoz\u00e1shoz internetkapcsolat sz\u00fcks\u00e9ges.", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "2fa": { + "data": { + "2fa": "K\u00e9tfaktoros k\u00f3d" + }, + "description": "Add meg a Hive hiteles\u00edt\u00e9si k\u00f3dj\u00e1t. \n \n\u00cdrd be a 0000 k\u00f3dot m\u00e1sik k\u00f3d k\u00e9r\u00e9s\u00e9hez.", + "title": "Hive k\u00e9tfaktoros hiteles\u00edt\u00e9s." + }, + "reauth": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Add meg \u00fajra a Hive bejelentkez\u00e9si adatait.", + "title": "Hive Bejelentkez\u00e9s" + }, + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + }, + "description": "Add meg a Hive bejelentkez\u00e9si adatait \u00e9s konfigur\u00e1ci\u00f3j\u00e1t.", + "title": "Hive Bejelentkez\u00e9s" + } + } + }, + "options": { + "step": { + "user": { + "title": "Hive be\u00e1ll\u00edt\u00e1sok" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index 1713b81cf7e36b..ab108964ed88f6 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -23,7 +23,7 @@ "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant\uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uae30 \uc811\uadfc\uc5d0 \ub300\ud55c \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant\uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc811\uadfc\ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Huawei LTE \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index c2ed895dc8c228..d4047ec35ecc19 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -28,7 +28,8 @@ "manual": { "data": { "host": "Host" - } + }, + "title": "Handmatig een Hue bridge configureren" } } }, @@ -52,5 +53,14 @@ "remote_double_button_long_press": "Beide \"{subtype}\" losgelaten na lang indrukken", "remote_double_button_short_press": "Beide \"{subtype}\" losgelaten" } + }, + "options": { + "step": { + "init": { + "data": { + "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/ko.json b/homeassistant/components/ifttt/translations/ko.json index 826a03c733974e..8f01109da76801 100644 --- a/homeassistant/components/ifttt/translations/ko.json +++ b/homeassistant/components/ifttt/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf]({applet_url})\uc5d0\uc11c \"Make a web request\"\ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/input_boolean/translations/ko.json b/homeassistant/components/input_boolean/translations/ko.json index 712051d04a4729..5d7d597f79063e 100644 --- a/homeassistant/components/input_boolean/translations/ko.json +++ b/homeassistant/components/input_boolean/translations/ko.json @@ -5,5 +5,5 @@ "on": "\ucf1c\uc9d0" } }, - "title": "\ub17c\ub9ac\uc785\ub825" + "title": "\ub17c\ub9ac \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/ko.json b/homeassistant/components/input_datetime/translations/ko.json index a5984527e151bc..3f6a71e23d787f 100644 --- a/homeassistant/components/input_datetime/translations/ko.json +++ b/homeassistant/components/input_datetime/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\ub0a0\uc9dc / \uc2dc\uac04\uc785\ub825" + "title": "\ub0a0\uc9dc/\uc2dc\uac04 \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/ko.json b/homeassistant/components/input_number/translations/ko.json index 68960733dd8e8b..635e1e26b5ef96 100644 --- a/homeassistant/components/input_number/translations/ko.json +++ b/homeassistant/components/input_number/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\uc22b\uc790\uc785\ub825" + "title": "\uc22b\uc790 \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_select/translations/ko.json b/homeassistant/components/input_select/translations/ko.json index 5620635d90325c..59b3f59f143b57 100644 --- a/homeassistant/components/input_select/translations/ko.json +++ b/homeassistant/components/input_select/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\uc120\ud0dd\uc785\ub825" + "title": "\uc120\ud0dd \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/input_text/translations/ko.json b/homeassistant/components/input_text/translations/ko.json index 6f8e3a04f2a854..43e792dd2ad17a 100644 --- a/homeassistant/components/input_text/translations/ko.json +++ b/homeassistant/components/input_text/translations/ko.json @@ -1,3 +1,3 @@ { - "title": "\ubb38\uc790\uc785\ub825" + "title": "\ubb38\uc790 \uc785\ub825" } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/de.json b/homeassistant/components/kodi/translations/de.json index 1d229e5a428ef1..15fd212fdbd922 100644 --- a/homeassistant/components/kodi/translations/de.json +++ b/homeassistant/components/kodi/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Ger\u00e4t ist bereits konfiguriert", "cannot_connect": "Verbindung fehlgeschlagen", "invalid_auth": "Ung\u00fcltige Authentifizierung", + "no_uuid": "Die Kodi-Instanz hat keine eindeutige ID. Dies ist h\u00f6chstwahrscheinlich auf eine alte Kodi-Version (17.x oder niedriger) zur\u00fcckzuf\u00fchren. Du kannst die Integration manuell konfigurieren oder auf eine neuere Kodi-Version aktualisieren.", "unknown": "Unerwarteter Fehler" }, "error": { diff --git a/homeassistant/components/locative/translations/ko.json b/homeassistant/components/locative/translations/ko.json index da063335ff8ef4..50652f76fc543c 100644 --- a/homeassistant/components/locative/translations/ko.json +++ b/homeassistant/components/locative/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative\uc571\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/lock/translations/ko.json b/homeassistant/components/lock/translations/ko.json index f89a1d1b54f701..b8db32ad51372f 100644 --- a/homeassistant/components/lock/translations/ko.json +++ b/homeassistant/components/lock/translations/ko.json @@ -20,5 +20,5 @@ "unlocked": "\ud574\uc81c" } }, - "title": "\uc7a0\uae40" + "title": "\uc7a0\uae08\uc7a5\uce58" } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index b6aacf2d0ef02e..ce771f52acd009 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -2,17 +2,34 @@ "config": { "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "cannot_connect": "Verbindung fehlgeschlagen" + "cannot_connect": "Verbindung fehlgeschlagen", + "not_lutron_device": "Erkanntes Ger\u00e4t ist kein Lutron-Ger\u00e4t" }, "error": { "cannot_connect": "Verbindung fehlgeschlagen" }, + "flow_title": "Lutron Cas\u00e9ta {name} ({host})", "step": { + "link": { + "title": "Mit der Bridge verbinden" + }, "user": { "data": { "host": "Host" - } + }, + "description": "Gib die IP-Adresse des Ger\u00e4ts ein.", + "title": "Automatisch mit der Bridge verbinden" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Erste Taste", + "button_2": "Zweite Taste", + "button_3": "Dritte Taste", + "button_4": "Vierte Taste", + "off": "Aus", + "on": "An" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/hu.json b/homeassistant/components/lutron_caseta/translations/hu.json index bf949ddf21c9ed..921f5e83409094 100644 --- a/homeassistant/components/lutron_caseta/translations/hu.json +++ b/homeassistant/components/lutron_caseta/translations/hu.json @@ -23,7 +23,8 @@ "button_3": "Harmadik gomb", "button_4": "Negyedik gomb", "off": "Ki", - "on": "Be" + "on": "Be", + "stop_all": "Az \u00f6sszes le\u00e1ll\u00edt\u00e1sa" } } } \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/ko.json b/homeassistant/components/mailgun/translations/ko.json index 1be1721d78f45a..2a296303d58586 100644 --- a/homeassistant/components/mailgun/translations/ko.json +++ b/homeassistant/components/mailgun/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun \uc6f9 \ud6c5]({mailgun_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/motion_blinds/translations/de.json b/homeassistant/components/motion_blinds/translations/de.json index c1a7ac0bc8d40d..01eba9c7ecd61c 100644 --- a/homeassistant/components/motion_blinds/translations/de.json +++ b/homeassistant/components/motion_blinds/translations/de.json @@ -10,12 +10,16 @@ "connect": { "data": { "api_key": "API-Schl\u00fcssel" - } + }, + "description": "Ein 16-Zeichen-API-Schl\u00fcssel wird ben\u00f6tigt, siehe https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key", + "title": "Motion Jalousien" }, "select": { "data": { "select_ip": "IP-Adresse" - } + }, + "description": "F\u00fchre das Setup erneut aus, wenn du weitere Motion Gateways verbinden m\u00f6chtest", + "title": "W\u00e4hle das Motion Gateway aus, zu dem du eine Verbindung herstellen m\u00f6chten" }, "user": { "data": { diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index f10c9bbf7e9b7e..e7631c5805d374 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -21,8 +21,8 @@ "data": { "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654" }, - "description": "Hass.io {addon} \ucd94\uac00 \uae30\ub2a5\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hass.io \ucd94\uac00 \uae30\ub2a5\uc758 MQTT \ube0c\ub85c\ucee4" + "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant\ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Hass.io \uc560\ub4dc\uc628\uc758 MQTT \ube0c\ub85c\ucee4" } } }, diff --git a/homeassistant/components/mysensors/translations/hu.json b/homeassistant/components/mysensors/translations/hu.json index 5faaf7aea56794..7d4df1f12da2c0 100644 --- a/homeassistant/components/mysensors/translations/hu.json +++ b/homeassistant/components/mysensors/translations/hu.json @@ -11,9 +11,17 @@ "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "cannot_connect": "Sikertelen csatlakoz\u00e1s", "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "invalid_serial": "\u00c9rv\u00e9nytelen soros port", + "invalid_version": "\u00c9rv\u00e9nytelen MySensors verzi\u00f3", + "port_out_of_range": "A portsz\u00e1mnak legal\u00e1bb 1-nek \u00e9s legfeljebb 65535-nek kell lennie", "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "gw_mqtt": { + "data": { + "version": "MySensors verzi\u00f3" + } + }, "gw_serial": { "data": { "version": "MySensors verzi\u00f3" diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index b3140a6f115df5..b4979396eeb081 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -20,8 +20,19 @@ "away": "t\u00e1vol" }, "trigger_type": { + "alarm_started": "{entity_name} riaszt\u00e1st \u00e9szlelt", + "animal": "{entity_name} \u00e9szlelt egy \u00e1llatot", + "cancel_set_point": "{entity_name} folytatta \u00fctemez\u00e9s\u00e9t", + "human": "{entity_name} embert \u00e9szlelt", + "movement": "{entity_name} mozg\u00e1st \u00e9szlelt", + "outdoor": "{entity_name} k\u00fclt\u00e9ri esem\u00e9nyt \u00e9szlelt", + "person": "{entity_name} szem\u00e9lyt \u00e9szlelt", + "person_away": "{entity_name} \u00e9szlelte, hogy egy szem\u00e9ly t\u00e1vozott", + "set_point": "A(z) {entity_name} c\u00e9lh\u0151m\u00e9rs\u00e9klet manu\u00e1lisan lett be\u00e1ll\u00edtva", + "therm_mode": "{entity_name} \u00e1tv\u00e1ltott erre: \"{subtype}\"", "turned_off": "{entity_name} ki lett kapcsolva", - "turned_on": "{entity_name} be lett kapcsolva" + "turned_on": "{entity_name} be lett kapcsolva", + "vehicle": "{entity_name} j\u00e1rm\u0171vet \u00e9szlelt" } }, "options": { diff --git a/homeassistant/components/ovo_energy/translations/ko.json b/homeassistant/components/ovo_energy/translations/ko.json index 273c146e805910..4ca904725b4559 100644 --- a/homeassistant/components/ovo_energy/translations/ko.json +++ b/homeassistant/components/ovo_energy/translations/ko.json @@ -19,7 +19,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uc5d0\ub108\uc9c0 \uc0ac\uc6a9\ub7c9\uc5d0 \uc561\uc138\uc2a4 \ud558\ub824\uba74 OVO Energy \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "description": "\uc5d0\ub108\uc9c0 \uc0ac\uc6a9\ub7c9\uc5d0 \uc811\uadfc\ud558\ub824\uba74 OVO Energy \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "OVO Energy \uacc4\uc815 \ucd94\uac00\ud558\uae30" } } diff --git a/homeassistant/components/ozw/translations/de.json b/homeassistant/components/ozw/translations/de.json index afa26fb7e03040..c58c55c49adb0b 100644 --- a/homeassistant/components/ozw/translations/de.json +++ b/homeassistant/components/ozw/translations/de.json @@ -1,11 +1,17 @@ { "config": { "abort": { + "addon_info_failed": "Fehler beim Abrufen von OpenZWave Add-on Informationen.", + "addon_install_failed": "Installation des OpenZWave Add-ons fehlgeschlagen.", + "addon_set_config_failed": "Setzen der OpenZWave Konfiguration fehlgeschlagen.", "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", "mqtt_required": "Die MQTT-Integration ist nicht eingerichtet", "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, + "error": { + "addon_start_failed": "Fehler beim Starten des OpenZWave Add-ons. \u00dcberpr\u00fcfe die Konfiguration." + }, "progress": { "install_addon": "Bitte warten, bis die Installation des OpenZWave-Add-Ons abgeschlossen ist. Dies kann einige Minuten dauern." }, @@ -17,13 +23,18 @@ "title": "Die Installation des OpenZWave-Add-On wurde gestartet" }, "on_supervisor": { + "data": { + "use_addon": "Verwende das OpenZWave Supervisor Add-on" + }, + "description": "M\u00f6chtest du das OpenZWave Supervisor Add-on verwenden?", "title": "Verbindungstyp ausw\u00e4hlen" }, "start_addon": { "data": { "network_key": "Netzwerk-Schl\u00fcssel", "usb_path": "USB-Ger\u00e4te-Pfad" - } + }, + "title": "Gib die Konfiguration des OpenZWave Add-ons ein" } } } diff --git a/homeassistant/components/ozw/translations/ko.json b/homeassistant/components/ozw/translations/ko.json index f118db7f72c43f..f6dddf5c96abdd 100644 --- a/homeassistant/components/ozw/translations/ko.json +++ b/homeassistant/components/ozw/translations/ko.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "addon_info_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_install_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_info_failed": "OpenZWave \uc560\ub4dc\uc628\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "OpenZWave \uc560\ub4dc\uc628\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "addon_set_config_failed": "OpenZWave \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", @@ -10,23 +10,23 @@ "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { - "addon_start_failed": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." + "addon_start_failed": "OpenZWave \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "progress": { - "install_addon": "Openzwave \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "install_addon": "Openzwave \uc560\ub4dc\uc628\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "hassio_confirm": { - "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c OpenZWave \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + "title": "OpenZWave \uc560\ub4dc\uc628\uc73c\ub85c OpenZWave \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" }, "install_addon": { - "title": "Openzwave \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "title": "Openzwave \uc560\ub4dc\uc628 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "on_supervisor": { "data": { - "use_addon": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + "use_addon": "OpenZWave Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uae30" }, - "description": "OpenZWave Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "OpenZWave Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "start_addon": { @@ -34,7 +34,7 @@ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" }, - "title": "OpenZWave \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "OpenZWave \uc560\ub4dc\uc628\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" } } } diff --git a/homeassistant/components/panasonic_viera/translations/ko.json b/homeassistant/components/panasonic_viera/translations/ko.json index 0f3252a4ab13dd..4eb136c11494a7 100644 --- a/homeassistant/components/panasonic_viera/translations/ko.json +++ b/homeassistant/components/panasonic_viera/translations/ko.json @@ -14,7 +14,7 @@ "data": { "pin": "PIN \ucf54\ub4dc" }, - "description": "TV \uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "description": "TV\uc5d0 \ud45c\uc2dc\ub41c PIN \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "\ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { @@ -22,7 +22,7 @@ "host": "IP \uc8fc\uc18c", "name": "\uc774\ub984" }, - "description": "Panasonic Viera TV \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "description": "Panasonic Viera TV\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "TV \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/philips_js/translations/ca.json b/homeassistant/components/philips_js/translations/ca.json index b12d8e7a848081..b94faccd615c58 100644 --- a/homeassistant/components/philips_js/translations/ca.json +++ b/homeassistant/components/philips_js/translations/ca.json @@ -14,8 +14,8 @@ "data": { "pin": "Codi PIN" }, - "description": "Introdueix el PIN que apareix a la pantalla del televisor", - "title": "Emparellar" + "description": "Introdueix el PIN que es mostra al televisor", + "title": "Vinculaci\u00f3" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/de.json b/homeassistant/components/philips_js/translations/de.json index d0c9932b3cd268..6288e9fb5c4bae 100644 --- a/homeassistant/components/philips_js/translations/de.json +++ b/homeassistant/components/philips_js/translations/de.json @@ -13,7 +13,8 @@ "pair": { "data": { "pin": "PIN-Code" - } + }, + "description": "Gib die auf deinem Fernseher angezeigten PIN ein" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/et.json b/homeassistant/components/philips_js/translations/et.json index 9953df9c272940..4a5bf9fe6e9f6f 100644 --- a/homeassistant/components/philips_js/translations/et.json +++ b/homeassistant/components/philips_js/translations/et.json @@ -10,6 +10,13 @@ "unknown": "Ootamatu t\u00f5rge" }, "step": { + "pair": { + "data": { + "pin": "PIN kood" + }, + "description": "Sisesta teleris kuvatav PIN-kood", + "title": "Paarita" + }, "user": { "data": { "api_version": "API versioon", diff --git a/homeassistant/components/philips_js/translations/hu.json b/homeassistant/components/philips_js/translations/hu.json index 0065a78ae0cb6e..f7ce3f708b0ab2 100644 --- a/homeassistant/components/philips_js/translations/hu.json +++ b/homeassistant/components/philips_js/translations/hu.json @@ -10,6 +10,13 @@ "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" }, "step": { + "pair": { + "data": { + "pin": "PIN-k\u00f3d" + }, + "description": "\u00cdrd be a t\u00e9v\u00e9n megjelen\u0151 PIN-k\u00f3dot", + "title": "P\u00e1ros\u00edt\u00e1s" + }, "user": { "data": { "api_version": "API Verzi\u00f3", diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index 9a7c530ce52101..a76fd70418c24d 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -13,7 +13,9 @@ "pair": { "data": { "pin": "PIN \ucf54\ub4dc" - } + }, + "description": "TV\uc5d0 \ud45c\uc2dc\ub41c PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\ud398\uc5b4\ub9c1" }, "user": { "data": { diff --git a/homeassistant/components/philips_js/translations/no.json b/homeassistant/components/philips_js/translations/no.json index a9c647a644be22..5b1df7c8e8bb00 100644 --- a/homeassistant/components/philips_js/translations/no.json +++ b/homeassistant/components/philips_js/translations/no.json @@ -10,6 +10,13 @@ "unknown": "Uventet feil" }, "step": { + "pair": { + "data": { + "pin": "PIN kode" + }, + "description": "Angi PIN-koden som vises p\u00e5 TV-en", + "title": "Par" + }, "user": { "data": { "api_version": "API-versjon", diff --git a/homeassistant/components/philips_js/translations/ru.json b/homeassistant/components/philips_js/translations/ru.json index 83511ff246aae0..df3dfd4b6f61b9 100644 --- a/homeassistant/components/philips_js/translations/ru.json +++ b/homeassistant/components/philips_js/translations/ru.json @@ -10,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": { + "pair": { + "data": { + "pin": "PIN-\u043a\u043e\u0434" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 PIN-\u043a\u043e\u0434, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430", + "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435" + }, "user": { "data": { "api_version": "\u0412\u0435\u0440\u0441\u0438\u044f API", diff --git a/homeassistant/components/philips_js/translations/zh-Hant.json b/homeassistant/components/philips_js/translations/zh-Hant.json index 13bfd52e980148..7ae9c8893d557a 100644 --- a/homeassistant/components/philips_js/translations/zh-Hant.json +++ b/homeassistant/components/philips_js/translations/zh-Hant.json @@ -10,6 +10,13 @@ "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { + "pair": { + "data": { + "pin": "PIN \u78bc" + }, + "description": "\u8f38\u5165\u96fb\u8996\u986f\u793a\u4e4b PIN \u78bc", + "title": "\u914d\u5c0d" + }, "user": { "data": { "api_version": "API \u7248\u672c", diff --git a/homeassistant/components/pi_hole/translations/hu.json b/homeassistant/components/pi_hole/translations/hu.json index 104f711d8d563d..a8f8563da41f0b 100644 --- a/homeassistant/components/pi_hole/translations/hu.json +++ b/homeassistant/components/pi_hole/translations/hu.json @@ -20,6 +20,7 @@ "name": "N\u00e9v", "port": "Port", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", + "statistics_only": "Csak statisztik\u00e1k", "verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" } } diff --git a/homeassistant/components/plaato/translations/de.json b/homeassistant/components/plaato/translations/de.json index e3e94ddf848c94..9a092ef4fa6230 100644 --- a/homeassistant/components/plaato/translations/de.json +++ b/homeassistant/components/plaato/translations/de.json @@ -8,6 +8,11 @@ "create_entry": { "default": "Um Ereignisse an Home Assistant zu senden, muss das Webhook Feature in Plaato Airlock konfiguriert werden.\n\n F\u00fcge die folgenden Informationen ein: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n \n Weitere Informationen finden sich in der [Dokumentation]({docs_url})." }, + "error": { + "invalid_webhook_device": "Du hast ein Ger\u00e4t gew\u00e4hlt, das das Senden von Daten an einen Webhook nicht unterst\u00fctzt. Es ist nur f\u00fcr die Airlock verf\u00fcgbar", + "no_api_method": "Du musst ein Authentifizierungstoken hinzuf\u00fcgen oder ein Webhook ausw\u00e4hlen", + "no_auth_token": "Du musst ein Authentifizierungstoken hinzuf\u00fcgen" + }, "step": { "api_method": { "data": { diff --git a/homeassistant/components/plaato/translations/hu.json b/homeassistant/components/plaato/translations/hu.json index aee8ceb07cdfe1..8347b5d2f98ce8 100644 --- a/homeassistant/components/plaato/translations/hu.json +++ b/homeassistant/components/plaato/translations/hu.json @@ -10,6 +10,10 @@ }, "step": { "user": { + "data": { + "device_name": "Eszk\u00f6z neve", + "device_type": "A Plaato eszk\u00f6z t\u00edpusa" + }, "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", "title": "A Plaato eszk\u00f6z\u00f6k be\u00e1ll\u00edt\u00e1sa" } diff --git a/homeassistant/components/plaato/translations/ko.json b/homeassistant/components/plaato/translations/ko.json index 1cb999c4729189..fb75bbb7d7cbcc 100644 --- a/homeassistant/components/plaato/translations/ko.json +++ b/homeassistant/components/plaato/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "**{device_name}**\uc758 Plaato {device_type}\uc774(\uac00) \uc131\uacf5\uc801\uc73c\ub85c \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4!" diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index df7285334678d0..d6b0c6a23419c5 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -35,7 +35,7 @@ "title": "Plex \uc11c\ubc84 \uc120\ud0dd\ud558\uae30" }, "user": { - "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv) \ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", + "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv)\ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", "title": "Plex \ubbf8\ub514\uc5b4 \uc11c\ubc84" }, "user_advanced": { diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index a432b8bbfd8659..efd679a1ce349b 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -8,6 +8,7 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Glimlach: {name}", "step": { "user": { "data": { diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json index 2315e739909074..0f3837f3a595de 100644 --- a/homeassistant/components/rfxtrx/translations/de.json +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -38,19 +38,32 @@ "options": { "error": { "already_configured_device": "Ger\u00e4t ist bereits konfiguriert", + "invalid_event_code": "Ung\u00fcltiger Ereigniscode", + "invalid_input_2262_off": "Ung\u00fcltige Eingabe f\u00fcr Befehl \"aus\"", + "invalid_input_2262_on": "Ung\u00fcltige Eingabe f\u00fcr Befehl \"an\"", + "invalid_input_off_delay": "Ung\u00fcltige Eingabe f\u00fcr Ausschaltverz\u00f6gerung", "unknown": "Unerwarteter Fehler" }, "step": { "prompt_options": { "data": { - "debug": "Debugging aktivieren" - } + "automatic_add": "Automatisches Hinzuf\u00fcgen aktivieren", + "debug": "Debugging aktivieren", + "device": "Zu konfigurierendes Ger\u00e4t ausw\u00e4hlen", + "remove_device": "Zu l\u00f6schendes Ger\u00e4t ausw\u00e4hlen" + }, + "title": "Rfxtrx Optionen" }, "set_device_options": { "data": { + "command_off": "Datenbitwert f\u00fcr den Befehl \"aus\"", + "command_on": "Datenbitwert f\u00fcr den Befehl \"ein\"", + "data_bit": "Anzahl der Datenbits", + "fire_event": "Ger\u00e4teereignis aktivieren", "off_delay": "Ausschaltverz\u00f6gerung", "off_delay_enabled": "Ausschaltverz\u00f6gerung aktivieren", "replace_device": "W\u00e4hle ein Ger\u00e4t aus, das ersetzt werden soll", + "signal_repetitions": "Anzahl der Signalwiederholungen", "venetian_blind_mode": "Jalousie-Modus" } } diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 5686c684e1f15d..7efb88bf7eb3c8 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -18,7 +18,7 @@ "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" }, - "description": "\uc0bc\uc131 TV \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. Home Assistant \ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV \uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4." + "description": "\uc0bc\uc131 TV \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. Home Assistant\ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV\uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4." } } } diff --git a/homeassistant/components/screenlogic/translations/ca.json b/homeassistant/components/screenlogic/translations/ca.json new file mode 100644 index 00000000000000..68bfad1ff94578 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/ca.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Adre\u00e7a IP", + "port": "Port" + }, + "description": "Introdueix la informaci\u00f3 de la passarel\u00b7la ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Passarel\u00b7la" + }, + "description": "S'han descobert les seg\u00fcents passarel\u00b7les ScreenLogic. Tria'n una per configurar-la o escull configurar manualment una passarel\u00b7la ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segons entre escanejos" + }, + "description": "Especifica la configuraci\u00f3 de {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/de.json b/homeassistant/components/screenlogic/translations/de.json new file mode 100644 index 00000000000000..6afe42e37ee1b6 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/de.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP-Adresse", + "port": "Port" + }, + "description": "Gib deine ScreenLogic Gateway-Informationen ein.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "" + }, + "description": "Die folgenden ScreenLogic-Gateways wurden erkannt. Bitte w\u00e4hle eines aus, um es zu konfigurieren oder w\u00e4hle ein ScreenLogic-Gateway zum manuellen Konfigurieren.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunden zwischen den Scans" + }, + "description": "Einstellungen f\u00fcr {gateway_name} angeben", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/et.json b/homeassistant/components/screenlogic/translations/et.json new file mode 100644 index 00000000000000..cf2cf19418fcbd --- /dev/null +++ b/homeassistant/components/screenlogic/translations/et.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP aadress", + "port": "Port" + }, + "description": "Sisesta oma ScreenLogic Gateway teave.", + "title": "" + }, + "gateway_select": { + "data": { + "selected_gateway": "L\u00fc\u00fcs" + }, + "description": "Avastati j\u00e4rgmised ScreenLogicu l\u00fc\u00fcsid. Vali seadistatav l\u00fc\u00fcs v\u00f5i seadista ScreenLogicu l\u00fc\u00fcs k\u00e4sitsi.", + "title": "" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "P\u00e4ringute vahe sekundites" + }, + "description": "M\u00e4\u00e4ra {gateway_name} s\u00e4tted", + "title": "" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/hu.json b/homeassistant/components/screenlogic/translations/hu.json new file mode 100644 index 00000000000000..59e48fda273298 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/hu.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP c\u00edm", + "port": "Port" + }, + "description": "Add meg a ScreenLogic Gateway adatait.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Szkennel\u00e9sek k\u00f6z\u00f6tti m\u00e1sodpercek" + }, + "description": "{gateway_name} be\u00e1ll\u00edt\u00e1sainak megad\u00e1sa", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/ko.json b/homeassistant/components/screenlogic/translations/ko.json new file mode 100644 index 00000000000000..3a8d457c603a99 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/ko.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8" + }, + "description": "ScreenLogic \uac8c\uc774\ud2b8\uc6e8\uc774 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\uac8c\uc774\ud2b8\uc6e8\uc774" + }, + "description": "\ub2e4\uc74c ScreenLogic \uac8c\uc774\ud2b8\uc6e8\uc774\uac00 \uac80\uc0c9\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uad6c\uc131\ud558\uac70\ub098 \uc218\ub3d9\uc73c\ub85c ScreenLogic \uac8c\uc774\ud2b8\uc6e8\uc774\ub85c \uad6c\uc131\ud560 \uac8c\uc774\ud2b8\uc6e8\uc774\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + }, + "description": "{gateway_name}\uc5d0 \ub300\ud55c \uc124\uc815 \uc9c0\uc815\ud558\uae30", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/no.json b/homeassistant/components/screenlogic/translations/no.json new file mode 100644 index 00000000000000..0ca4827514a0f5 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/no.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP adresse", + "port": "Port" + }, + "description": "Skriv inn din ScreenLogic Gateway-informasjon.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "F\u00f8lgende ScreenLogic-gateways ble oppdaget. Velg en \u00e5 konfigurere, eller velg \u00e5 konfigurere en ScreenLogic gateway manuelt.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Sekunder mellom skanninger" + }, + "description": "Angi innstillinger for {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/ru.json b/homeassistant/components/screenlogic/translations/ru.json new file mode 100644 index 00000000000000..a657b7360c790d --- /dev/null +++ b/homeassistant/components/screenlogic/translations/ru.json @@ -0,0 +1,39 @@ +{ + "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." + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0448\u043b\u044e\u0437\u0435 ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\u0428\u043b\u044e\u0437" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0434\u0438\u043d \u0438\u0437 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0448\u043b\u044e\u0437\u043e\u0432 ScreenLogic \u0438\u043b\u0438 \u0443\u043a\u0430\u0436\u0438\u0442\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + }, + "description": "\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/zh-Hant.json b/homeassistant/components/screenlogic/translations/zh-Hant.json new file mode 100644 index 00000000000000..40ca94fd779a3d --- /dev/null +++ b/homeassistant/components/screenlogic/translations/zh-Hant.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP \u4f4d\u5740", + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u8f38\u5165 ScreenLogic \u9598\u9053\u5668\u8cc7\u8a0a\u3002", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\u9598\u9053\u5668" + }, + "description": "\u641c\u5c0b\u5230\u4ee5\u4e0b ScreenLogic \u9598\u9053\u5668\uff0c\u8acb\u9078\u64c7\u6240\u8981\u8a2d\u5b9a\u7684\u9598\u9053\u5668\u3001\u6216\u9032\u884c\u624b\u52d5\u8a2d\u5b9a\u3002", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u6383\u63cf\u9593\u9694\u79d2\u6578" + }, + "description": "{gateway_name} \u7279\u5b9a\u8a2d\u5b9a", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/hu.json b/homeassistant/components/sensor/translations/hu.json index 9c49de27a7bba7..98e817fc1644d6 100644 --- a/homeassistant/components/sensor/translations/hu.json +++ b/homeassistant/components/sensor/translations/hu.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "{entity_name} aktu\u00e1lis akku szintje", + "is_carbon_dioxide": "Jelenlegi {entity_name} sz\u00e9n-dioxid koncentr\u00e1ci\u00f3 szint", + "is_carbon_monoxide": "Jelenlegi {entity_name} sz\u00e9n-monoxid koncentr\u00e1ci\u00f3 szint", "is_humidity": "{entity_name} aktu\u00e1lis p\u00e1ratartalma", "is_illuminance": "{entity_name} aktu\u00e1lis megvil\u00e1g\u00edt\u00e1sa", "is_power": "{entity_name} aktu\u00e1lis teljes\u00edtm\u00e9nye", @@ -13,6 +15,8 @@ }, "trigger_type": { "battery_level": "{entity_name} akku szintje v\u00e1ltozik", + "carbon_dioxide": "{entity_name} sz\u00e9n-dioxid koncentr\u00e1ci\u00f3ja megv\u00e1ltozik", + "carbon_monoxide": "{entity_name} sz\u00e9n-monoxid koncentr\u00e1ci\u00f3ja megv\u00e1ltozik", "humidity": "{entity_name} p\u00e1ratartalma v\u00e1ltozik", "illuminance": "{entity_name} megvil\u00e1g\u00edt\u00e1sa v\u00e1ltozik", "power": "{entity_name} teljes\u00edtm\u00e9nye v\u00e1ltozik", diff --git a/homeassistant/components/smarthab/translations/nl.json b/homeassistant/components/smarthab/translations/nl.json index 7f5fc7fe27c64d..31a02ae2b9771d 100644 --- a/homeassistant/components/smarthab/translations/nl.json +++ b/homeassistant/components/smarthab/translations/nl.json @@ -10,7 +10,9 @@ "data": { "email": "E-mail", "password": "Wachtwoord" - } + }, + "description": "Om technische redenen moet u een tweede account gebruiken dat specifiek is voor uw Home Assistant-installatie. U kunt er een aanmaken vanuit de SmartHab-toepassing.", + "title": "Stel SmartHab in" } } } diff --git a/homeassistant/components/somfy_mylink/translations/de.json b/homeassistant/components/somfy_mylink/translations/de.json index 522e185af5de0b..4382ccd4a0c0f1 100644 --- a/homeassistant/components/somfy_mylink/translations/de.json +++ b/homeassistant/components/somfy_mylink/translations/de.json @@ -25,9 +25,13 @@ }, "step": { "entity_config": { + "description": "Optionen f\u00fcr `{entity_id}` konfigurieren", "title": "Entit\u00e4t konfigurieren" }, "init": { + "data": { + "entity_id": "Konfiguriere eine bestimmte Entit\u00e4t." + }, "title": "MyLink-Optionen konfigurieren" }, "target_config": { @@ -35,5 +39,6 @@ "title": "MyLink-Cover konfigurieren" } } - } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/somfy_mylink/translations/hu.json b/homeassistant/components/somfy_mylink/translations/hu.json index 1ef6d25d34c65c..08d0db14866e8f 100644 --- a/homeassistant/components/somfy_mylink/translations/hu.json +++ b/homeassistant/components/somfy_mylink/translations/hu.json @@ -23,8 +23,18 @@ "cannot_connect": "Sikertelen csatlakoz\u00e1s" }, "step": { + "entity_config": { + "title": "Entit\u00e1s konfigur\u00e1l\u00e1sa" + }, "init": { + "data": { + "target_id": "Az \u00e1rny\u00e9kol\u00f3 be\u00e1ll\u00edt\u00e1sainak konfigur\u00e1l\u00e1sa." + }, "title": "Mylink be\u00e1ll\u00edt\u00e1sok konfigur\u00e1l\u00e1sa" + }, + "target_config": { + "description": "A(z) `{target_name}` be\u00e1ll\u00edt\u00e1sainak konfigur\u00e1l\u00e1sa", + "title": "MyLink \u00e1rny\u00e9kol\u00f3 konfigur\u00e1l\u00e1sa" } } }, diff --git a/homeassistant/components/sonarr/translations/de.json b/homeassistant/components/sonarr/translations/de.json index 19a37dbcc4f306..939c5eed1c82fc 100644 --- a/homeassistant/components/sonarr/translations/de.json +++ b/homeassistant/components/sonarr/translations/de.json @@ -12,6 +12,7 @@ "flow_title": "Sonarr: {name}", "step": { "reauth_confirm": { + "description": "Die Sonarr-Integration muss manuell mit der Sonarr-API, die unter {host} gehostet wird, neu authentifiziert werden", "title": "Integration erneut authentifizieren" }, "user": { diff --git a/homeassistant/components/traccar/translations/ko.json b/homeassistant/components/traccar/translations/ko.json index 2af5079ae2de61..aa5e2a65736734 100644 --- a/homeassistant/components/traccar/translations/ko.json +++ b/homeassistant/components/traccar/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar\uc5d0\uc11c \uc6f9 \ud6c5\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c\uc758 URL \uc8fc\uc18c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4: `{webhook_url}`\n \n\uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/tuya/translations/de.json b/homeassistant/components/tuya/translations/de.json index cf5d3dfffe8857..6650dc754b3068 100644 --- a/homeassistant/components/tuya/translations/de.json +++ b/homeassistant/components/tuya/translations/de.json @@ -28,6 +28,20 @@ "error": { "dev_not_config": "Ger\u00e4tetyp nicht konfigurierbar", "dev_not_found": "Ger\u00e4t nicht gefunden" + }, + "step": { + "device": { + "data": { + "brightness_range_mode": "Vom Ger\u00e4t genutzter Helligkeitsbereich", + "max_kelvin": "Maximal unterst\u00fctzte Farbtemperatur in Kelvin", + "max_temp": "Maximale Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", + "min_kelvin": "Minimale unterst\u00fctzte Farbtemperatur in Kelvin", + "min_temp": "Minimal Solltemperatur (f\u00fcr Voreinstellung min und max = 0 verwenden)", + "support_color": "Farbunterst\u00fctzung erzwingen", + "tuya_max_coltemp": "Vom Ger\u00e4t gemeldete maximale Farbtemperatur", + "unit_of_measurement": "Vom Ger\u00e4t verwendete Temperatureinheit" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index 3e3fbc68bee4f9..afa2541e7b9e0d 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -37,9 +37,9 @@ "brightness_range_mode": "\uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \ubc1d\uae30 \ubc94\uc704", "curr_temp_divider": "\ud604\uc7ac \uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", "max_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\ub300 \uc0c9\uc628\ub3c4", - "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "max_temp": "\ucd5c\ub300 \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", "min_kelvin": "\uce98\ube48 \ub2e8\uc704\uc758 \ucd5c\uc18c \uc0c9\uc628\ub3c4", - "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc18c\uac12 \ubc0f \ucd5c\ub300\uac12 = 0)", + "min_temp": "\ucd5c\uc18c \ubaa9\ud45c \uc628\ub3c4 (\uae30\ubcf8\uac12\uc758 \uacbd\uc6b0 \ucd5c\uc19f\uac12 \ubc0f \ucd5c\ub313\uac12 = 0)", "set_temp_divided": "\uc124\uc815 \uc628\ub3c4 \uba85\ub839\uc5d0 \ubd84\ud560\ub41c \uc628\ub3c4 \uac12 \uc0ac\uc6a9\ud558\uae30", "support_color": "\uc0c9\uc0c1 \uc9c0\uc6d0 \uac15\uc81c \uc801\uc6a9\ud558\uae30", "temp_divider": "\uc628\ub3c4 \uac12 \ubd84\ud560 (0 = \uae30\ubcf8\uac12 \uc0ac\uc6a9)", diff --git a/homeassistant/components/twilio/translations/ko.json b/homeassistant/components/twilio/translations/ko.json index 4feeacf1a20074..aeb7c09474dec1 100644 --- a/homeassistant/components/twilio/translations/ko.json +++ b/homeassistant/components/twilio/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." + "webhook_not_internet_accessible": "\uc6f9 \ud6c5 \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4." }, "create_entry": { "default": "Home Assistant\ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio \uc6f9 \ud6c5]({twilio_url})\uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694:\n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \nHome Assistant\ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uad00\ub828 \ubb38\uc11c]({docs_url})\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index 05dd66fe56c798..b1d3e495f94250 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -10,6 +10,7 @@ "service_unavailable": "Verbindung fehlgeschlagen", "unknown_client_mac": "Unter dieser MAC-Adresse ist kein Client verf\u00fcgbar." }, + "flow_title": "UniFi Netzwerk {site} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index f5ada3df99a9fa..3a13b420097a39 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -29,11 +29,11 @@ "step": { "client_control": { "data": { - "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", + "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc811\uadfc \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", "dpi_restrictions": "DPI \uc81c\ud55c \uadf8\ub8f9\uc758 \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30", "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9\ud558\uae30" }, - "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4\ub97c \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", + "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc811\uadfc\uc744 \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", "title": "UniFi \uc635\uc158 2/3" }, "device_tracker": { @@ -50,7 +50,7 @@ }, "simple_options": { "data": { - "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", + "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc811\uadfc \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", "track_clients": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ucd94\uc801 \ub300\uc0c1", "track_devices": "\ub124\ud2b8\uc6cc\ud06c \uae30\uae30 \ucd94\uc801 (Ubiquiti \uae30\uae30)" }, diff --git a/homeassistant/components/upcloud/translations/ko.json b/homeassistant/components/upcloud/translations/ko.json index 073236bd80bcc5..24b4ab0a446a73 100644 --- a/homeassistant/components/upcloud/translations/ko.json +++ b/homeassistant/components/upcloud/translations/ko.json @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)" + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc19f\uac12 30)" } } } diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index 7dd2e5c685c6e6..ab80ceb9caa82e 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -12,7 +12,7 @@ }, "user": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)", + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc19f\uac12 30)", "usn": "\uae30\uae30" } } diff --git a/homeassistant/components/verisure/translations/hu.json b/homeassistant/components/verisure/translations/hu.json new file mode 100644 index 00000000000000..85e53003566483 --- /dev/null +++ b/homeassistant/components/verisure/translations/hu.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", + "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt" + }, + "step": { + "installation": { + "data": { + "giid": "Telep\u00edt\u00e9s" + } + }, + "reauth_confirm": { + "data": { + "description": "Hiteles\u00edts \u00fajra a Verisure My Pages fi\u00f3koddal.", + "email": "E-mail", + "password": "Jelsz\u00f3" + } + }, + "user": { + "data": { + "description": "Jelentkezz be a Verisure My Pages fi\u00f3koddal.", + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "Az alap\u00e9rtelmezett PIN-k\u00f3d nem egyezik meg a sz\u00fcks\u00e9ges sz\u00e1mjegyek sz\u00e1m\u00e1val" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "Sz\u00e1mjegyek sz\u00e1ma a z\u00e1rak PIN-k\u00f3dj\u00e1ban", + "lock_default_code": "Alap\u00e9rtelmezett PIN-k\u00f3d z\u00e1rakhoz, akkor haszn\u00e1latos, ha nincs m\u00e1s megadva" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index 9310adfa4b96b6..cc86e020a1c844 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -15,7 +15,7 @@ "data": { "pin": "PIN \ucf54\ub4dc" }, - "description": "TV \uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc785\ub825\ub780\uc5d0 \uc785\ub825\ud55c \ud6c4 \ub2e4\uc74c \ub2e8\uacc4\ub97c \uacc4\uc18d\ud558\uc5ec \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", + "description": "TV\uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc785\ub825\ub780\uc5d0 \uc785\ub825\ud55c \ud6c4 \ub2e4\uc74c \ub2e8\uacc4\ub97c \uacc4\uc18d\ud558\uc5ec \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "\ud398\uc5b4\ub9c1 \uacfc\uc815 \ub05d\ub0b4\uae30" }, "pairing_complete": { @@ -33,7 +33,7 @@ "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" }, - "description": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 TV \uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4. TV \ub97c \uad6c\uc131\ud558\uace0 \uc788\uace0 \uc544\uc9c1 \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc5c6\ub294 \uacbd\uc6b0 \ud398\uc5b4\ub9c1 \uacfc\uc815\uc744 \uc9c4\ud589\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694.", + "description": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc740 TV\uc5d0\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4. TV\ub97c \uad6c\uc131\ud558\uace0 \uc788\uace0 \uc544\uc9c1 \uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc5c6\ub294 \uacbd\uc6b0 \ud398\uc5b4\ub9c1 \uacfc\uc815\uc744 \uc9c4\ud589\ud558\ub824\uba74 \ube44\uc6cc\ub450\uc138\uc694.", "title": "VIZIO SmartCast \uae30\uae30" } } diff --git a/homeassistant/components/water_heater/translations/hu.json b/homeassistant/components/water_heater/translations/hu.json index c3c47030acb4fe..82f88d1f0dec88 100644 --- a/homeassistant/components/water_heater/translations/hu.json +++ b/homeassistant/components/water_heater/translations/hu.json @@ -4,5 +4,16 @@ "turn_off": "{entity_name} kikapcsol\u00e1sa", "turn_on": "{entity_name} bekapcsol\u00e1sa" } + }, + "state": { + "_": { + "eco": "Takar\u00e9kos", + "electric": "Elektromos", + "gas": "G\u00e1z", + "heat_pump": "H\u0151szivatty\u00fa", + "high_demand": "Magas ig\u00e9nybev\u00e9tel", + "off": "Ki", + "performance": "Teljes\u00edtm\u00e9ny" + } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index 131975c1d8c413..dd8a9ae5ede0aa 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -26,7 +26,7 @@ "key": "\uac8c\uc774\ud2b8\uc6e8\uc774 \ud0a4", "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984" }, - "description": "\ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\ub294 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz", + "description": "\ud0a4\uac00 \uc81c\uacf5\ub418\uc9c0 \uc54a\uc73c\uba74 \uc13c\uc11c\uc5d0\ub9cc \uc811\uadfc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud0a4(\ube44\ubc00\ubc88\ud638)\ub97c \uc5bb\ub294 \ubc29\ubc95\uc740 \ub2e4\uc74c\uc758 \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774 \ucd94\uac00 \uc124\uc815\ud558\uae30" }, "user": { diff --git a/homeassistant/components/zwave_js/translations/de.json b/homeassistant/components/zwave_js/translations/de.json index 30254e2a523958..ae9293cf9261a8 100644 --- a/homeassistant/components/zwave_js/translations/de.json +++ b/homeassistant/components/zwave_js/translations/de.json @@ -1,7 +1,9 @@ { "config": { "abort": { + "addon_info_failed": "Fehler beim Abrufen von Z-Wave JS Add-on Informationen.", "addon_install_failed": "Installation des Z-Wave JS Add-Ons fehlgeschlagen.", + "addon_set_config_failed": "Setzen der Z-Wave JS Konfiguration fehlgeschlagen", "addon_start_failed": "Starten des Z-Wave JS Add-ons fehlgeschlagen.", "already_configured": "Ger\u00e4t ist bereits konfiguriert", "already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt", @@ -10,6 +12,7 @@ "error": { "addon_start_failed": "Fehler beim Starten des Z-Wave JS Add-Ons. \u00dcberpr\u00fcfe die Konfiguration.", "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_ws_url": "Ung\u00fcltige Websocket-URL", "unknown": "Unerwarteter Fehler" }, "progress": { @@ -19,6 +22,7 @@ "step": { "configure_addon": { "data": { + "network_key": "Netzwerk-Schl\u00fcssel", "usb_path": "USB-Ger\u00e4te-Pfad" }, "title": "Gib die Konfiguration des Z-Wave JS Add-ons ein" @@ -50,5 +54,6 @@ } } } - } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/zwave_js/translations/ko.json b/homeassistant/components/zwave_js/translations/ko.json index 72d81531551fe6..22149af7496bcc 100644 --- a/homeassistant/components/zwave_js/translations/ko.json +++ b/homeassistant/components/zwave_js/translations/ko.json @@ -1,25 +1,25 @@ { "config": { "abort": { - "addon_get_discovery_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_info_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_install_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_missing_discovery_info": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uac80\uc0c9 \uc815\ubcf4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "addon_get_discovery_info_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uac80\uc0c9 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_info_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uc815\ubcf4\ub97c \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_install_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc124\uce58\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_missing_discovery_info": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uac80\uc0c9 \uc815\ubcf4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", "addon_set_config_failed": "Z-Wave JS \uad6c\uc131\uc744 \uc124\uc815\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", - "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "addon_start_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "error": { - "addon_start_failed": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "addon_start_failed": "Z-Wave JS \uc560\ub4dc\uc628\uc744 \uc2dc\uc791\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uad6c\uc131 \ub0b4\uc6a9\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_ws_url": "\uc6f9 \uc18c\ucf13 URL \uc8fc\uc18c\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "progress": { - "install_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "start_addon": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc2dc\uc791\uc774 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "install_addon": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uc124\uce58\uac00 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ubd84 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "start_addon": "Z-Wave JS \uc560\ub4dc\uc628 \uc2dc\uc791\uc774 \uc644\ub8cc\ub418\ub294 \ub3d9\uc548 \uc7a0\uc2dc \uae30\ub2e4\ub824\uc8fc\uc138\uc694. \uba87 \ucd08 \uc815\ub3c4 \uac78\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "configure_addon": { @@ -27,13 +27,13 @@ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4", "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" }, - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "Z-Wave JS \uc560\ub4dc\uc628\uc758 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" }, "hassio_confirm": { - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc73c\ub85c Z-Wave JS \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" + "title": "Z-Wave JS \uc560\ub4dc\uc628\uc73c\ub85c Z-Wave JS \ud1b5\ud569 \uad6c\uc131\uc694\uc18c \uc124\uc815\ud558\uae30" }, "install_addon": { - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "title": "Z-Wave JS \uc560\ub4dc\uc628 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "manual": { "data": { @@ -42,13 +42,13 @@ }, "on_supervisor": { "data": { - "use_addon": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uae30" + "use_addon": "Z-Wave JS Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uae30" }, - "description": "Z-Wave JS Supervisor \ucd94\uac00 \uae30\ub2a5\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "description": "Z-Wave JS Supervisor \uc560\ub4dc\uc628\uc744 \uc0ac\uc6a9\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "\uc5f0\uacb0 \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "start_addon": { - "title": "Z-Wave JS \ucd94\uac00 \uae30\ub2a5\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." + "title": "Z-Wave JS \uc560\ub4dc\uc628\uc774 \uc2dc\uc791\ud558\ub294 \uc911\uc785\ub2c8\ub2e4." }, "user": { "data": { From f785cc7d9a4c5cf885b6c5cda0141d3b3c1f4cd8 Mon Sep 17 00:00:00 2001 From: Tobias Haber Date: Thu, 18 Mar 2021 04:59:06 +0100 Subject: [PATCH 1335/1818] Google has deprecated a comma separated list for modes changed it to array (#48029) --- homeassistant/components/google_assistant/trait.py | 2 +- tests/components/google_assistant/__init__.py | 10 +++++++++- tests/components/google_assistant/test_trait.py | 10 +++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index be7ceb98ad31a2..7babc5e4836192 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -763,7 +763,7 @@ def sync_attributes(self): mode in modes for mode in ("heatcool", "heat", "cool") ): modes.append("on") - response["availableThermostatModes"] = ",".join(modes) + response["availableThermostatModes"] = modes return response diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 4bef45cf0ee733..9047e175ae6f8c 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -276,7 +276,15 @@ def should_2fa(self, state): "type": "action.devices.types.THERMOSTAT", "willReportState": False, "attributes": { - "availableThermostatModes": "off,heat,cool,heatcool,auto,dry,fan-only", + "availableThermostatModes": [ + "off", + "heat", + "cool", + "heatcool", + "auto", + "dry", + "fan-only", + ], "thermostatTemperatureUnit": "C", }, }, diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 43e9c30f91a8ba..fd62d225aace40 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -713,7 +713,7 @@ async def test_temperature_setting_climate_onoff(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,cool,heat,heatcool,on", + "availableThermostatModes": ["off", "cool", "heat", "heatcool", "on"], "thermostatTemperatureUnit": "F", } assert trt.can_execute(trait.COMMAND_THERMOSTAT_SET_MODE, {}) @@ -752,7 +752,7 @@ async def test_temperature_setting_climate_no_modes(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "heat", + "availableThermostatModes": ["heat"], "thermostatTemperatureUnit": "C", } @@ -788,7 +788,7 @@ async def test_temperature_setting_climate_range(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,cool,heat,auto,on", + "availableThermostatModes": ["off", "cool", "heat", "auto", "on"], "thermostatTemperatureUnit": "F", } assert trt.query_attributes() == { @@ -862,7 +862,7 @@ async def test_temperature_setting_climate_setpoint(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,cool,on", + "availableThermostatModes": ["off", "cool", "on"], "thermostatTemperatureUnit": "C", } assert trt.query_attributes() == { @@ -920,7 +920,7 @@ async def test_temperature_setting_climate_setpoint_auto(hass): BASIC_CONFIG, ) assert trt.sync_attributes() == { - "availableThermostatModes": "off,heatcool,on", + "availableThermostatModes": ["off", "heatcool", "on"], "thermostatTemperatureUnit": "C", } assert trt.query_attributes() == { From 6fb0e493357a683ee4f77c3c636fa61767355f8f Mon Sep 17 00:00:00 2001 From: corneyl Date: Thu, 18 Mar 2021 04:59:48 +0100 Subject: [PATCH 1336/1818] Upgraded aiopylgtv to v0.4.0 (#47014) Co-authored-by: Paulus Schoutsen --- homeassistant/components/webostv/__init__.py | 33 ++++++++++++- .../components/webostv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/webostv/test_media_player.py | 47 ++++++++++++++++++- 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 3a117955bee9c3..34e32dce16386b 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1,8 +1,11 @@ """Support for LG webOS Smart TV.""" import asyncio +import json import logging +import os from aiopylgtv import PyLGTVCmdException, PyLGTVPairException, WebOsClient +from sqlitedict import SqliteDict import voluptuous as vol from websockets.exceptions import ConnectionClosed @@ -101,13 +104,41 @@ async def async_service_handler(service): return True +def convert_client_keys(config_file): + """In case the config file contains JSON, convert it to a Sqlite config file.""" + # Return early if config file is non-existing + if not os.path.isfile(config_file): + return + + # Try to parse the file as being JSON + with open(config_file) as json_file: + try: + json_conf = json.load(json_file) + except (json.JSONDecodeError, UnicodeDecodeError): + json_conf = None + + # If the file contains JSON, convert it to an Sqlite DB + if json_conf: + _LOGGER.warning("LG webOS TV client-key file is being migrated to Sqlite!") + + # Clean the JSON file + os.remove(config_file) + + # Write the data to the Sqlite DB + with SqliteDict(config_file) as conf: + for host, key in json_conf.items(): + conf[host] = key + conf.commit() + + async def async_setup_tv(hass, config, conf): """Set up a LG WebOS TV based on host parameter.""" host = conf[CONF_HOST] config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + await hass.async_add_executor_job(convert_client_keys, config_file) - client = WebOsClient(host, config_file) + client = await WebOsClient.create(host, config_file) hass.data[DOMAIN][host] = {"client": client} if client.is_registered(): diff --git a/homeassistant/components/webostv/manifest.json b/homeassistant/components/webostv/manifest.json index acdee1d9ca9ee7..7773e9c4963329 100644 --- a/homeassistant/components/webostv/manifest.json +++ b/homeassistant/components/webostv/manifest.json @@ -2,7 +2,7 @@ "domain": "webostv", "name": "LG webOS Smart TV", "documentation": "https://www.home-assistant.io/integrations/webostv", - "requirements": ["aiopylgtv==0.3.3"], + "requirements": ["aiopylgtv==0.4.0"], "dependencies": ["configurator"], "codeowners": ["@bendavid"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0398f053ec3bae..40a3e8545be2e3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -215,7 +215,7 @@ aiopvapi==1.6.14 aiopvpc==2.0.2 # homeassistant.components.webostv -aiopylgtv==0.3.3 +aiopylgtv==0.4.0 # homeassistant.components.recollect_waste aiorecollect==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47c10b5700c536..5a17b05864d982 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aiopvapi==1.6.14 aiopvpc==2.0.2 # homeassistant.components.webostv -aiopylgtv==0.3.3 +aiopylgtv==0.4.0 # homeassistant.components.recollect_waste aiorecollect==1.0.1 diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 3015fcccf9f480..716c496d88a87f 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -1,8 +1,10 @@ """The tests for the LG webOS media player platform.""" - +import json +import os from unittest.mock import patch import pytest +from sqlitedict import SqliteDict from homeassistant.components import media_player from homeassistant.components.media_player.const import ( @@ -16,6 +18,7 @@ DOMAIN, SERVICE_BUTTON, SERVICE_COMMAND, + WEBOSTV_CONFIG_FILE, ) from homeassistant.const import ( ATTR_COMMAND, @@ -36,6 +39,7 @@ def client_fixture(): with patch( "homeassistant.components.webostv.WebOsClient", autospec=True ) as mock_client_class: + mock_client_class.create.return_value = mock_client_class.return_value client = mock_client_class.return_value client.software_info = {"device_id": "a1:b1:c1:d1:e1:f1"} client.client_key = "0123456789" @@ -52,6 +56,13 @@ async def setup_webostv(hass): await hass.async_block_till_done() +@pytest.fixture +def cleanup_config(hass): + """Test cleanup, remove the config file.""" + yield + os.remove(hass.config.path(WEBOSTV_CONFIG_FILE)) + + async def test_mute(hass, client): """Test simple service call.""" @@ -128,3 +139,37 @@ async def test_command_with_optional_arg(hass, client): client.request.assert_called_with( "test", payload={"target": "https://www.google.com"} ) + + +async def test_migrate_keyfile_to_sqlite(hass, client, cleanup_config): + """Test migration from JSON key-file to Sqlite based one.""" + key = "3d5b1aeeb98e" + # Create config file with JSON content + config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + with open(config_file, "w+") as file: + json.dump({"host": key}, file) + + # Run the component setup + await setup_webostv(hass) + + # Assert that the config file is a Sqlite database which contains the key + with SqliteDict(config_file) as conf: + assert conf.get("host") == key + + +async def test_dont_migrate_sqlite_keyfile(hass, client, cleanup_config): + """Test that migration is not performed and setup still succeeds when config file is already an Sqlite DB.""" + key = "3d5b1aeeb98e" + + # Create config file with Sqlite DB + config_file = hass.config.path(WEBOSTV_CONFIG_FILE) + with SqliteDict(config_file) as conf: + conf["host"] = key + conf.commit() + + # Run the component setup + await setup_webostv(hass) + + # Assert that the config file is still an Sqlite database and setup didn't fail + with SqliteDict(config_file) as conf: + assert conf.get("host") == key From 08db262972c33c7a466b93b9dc90f460a255fb33 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Mar 2021 18:27:21 -1000 Subject: [PATCH 1337/1818] Add a service to reload config entries that can easily be called though automations (#46762) --- .../components/homeassistant/__init__.py | 39 ++++- .../components/homeassistant/services.yaml | 16 ++ homeassistant/helpers/service.py | 158 ++++++++++-------- tests/components/homeassistant/test_init.py | 62 +++++++ tests/helpers/test_service.py | 25 +++ 5 files changed, 228 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index c2ee40b7d43f25..309f98e6095632 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -21,15 +21,30 @@ import homeassistant.core as ha from homeassistant.exceptions import HomeAssistantError, Unauthorized, UnknownUser from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.service import async_extract_referenced_entity_ids +from homeassistant.helpers.service import ( + async_extract_config_entry_ids, + async_extract_referenced_entity_ids, +) + +ATTR_ENTRY_ID = "entry_id" _LOGGER = logging.getLogger(__name__) DOMAIN = ha.DOMAIN SERVICE_RELOAD_CORE_CONFIG = "reload_core_config" +SERVICE_RELOAD_CONFIG_ENTRY = "reload_config_entry" SERVICE_CHECK_CONFIG = "check_config" SERVICE_UPDATE_ENTITY = "update_entity" SERVICE_SET_LOCATION = "set_location" SCHEMA_UPDATE_ENTITY = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) +SCHEMA_RELOAD_CONFIG_ENTRY = vol.All( + vol.Schema( + { + vol.Optional(ATTR_ENTRY_ID): str, + **cv.ENTITY_SERVICE_FIELDS, + }, + ), + cv.has_at_least_one_key(ATTR_ENTRY_ID, *cv.ENTITY_SERVICE_FIELDS), +) async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: @@ -203,4 +218,26 @@ async def async_set_location(call): vol.Schema({ATTR_LATITUDE: cv.latitude, ATTR_LONGITUDE: cv.longitude}), ) + async def async_handle_reload_config_entry(call): + """Service handler for reloading a config entry.""" + reload_entries = set() + if ATTR_ENTRY_ID in call.data: + reload_entries.add(call.data[ATTR_ENTRY_ID]) + reload_entries.update(await async_extract_config_entry_ids(hass, call)) + if not reload_entries: + raise ValueError("There were no matching config entries to reload") + await asyncio.gather( + *[ + hass.config_entries.async_reload(config_entry_id) + for config_entry_id in reload_entries + ] + ) + + hass.helpers.service.async_register_admin_service( + ha.DOMAIN, + SERVICE_RELOAD_CONFIG_ENTRY, + async_handle_reload_config_entry, + schema=SCHEMA_RELOAD_CONFIG_ENTRY, + ) + return True diff --git a/homeassistant/components/homeassistant/services.yaml b/homeassistant/components/homeassistant/services.yaml index 38814d9f902062..251ee171b6af0b 100644 --- a/homeassistant/components/homeassistant/services.yaml +++ b/homeassistant/components/homeassistant/services.yaml @@ -58,3 +58,19 @@ update_entity: description: Force one or more entities to update its data target: entity: {} + +reload_config_entry: + name: Reload config entry + description: Reload a config entry that matches a target. + target: + entity: {} + device: {} + fields: + entry_id: + advanced: true + name: Config entry id + description: A configuration entry id + required: false + example: 8955375327824e14ba89e4b29cc3ec9a + selector: + text: diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index f1ab245d38a28c..f43b85575b88d2 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -11,9 +11,9 @@ Awaitable, Callable, Iterable, - Tuple, + Optional, TypedDict, - cast, + Union, ) import voluptuous as vol @@ -78,6 +78,29 @@ class ServiceParams(TypedDict): target: dict | None +class ServiceTargetSelector: + """Class to hold a target selector for a service.""" + + def __init__(self, service_call: ha.ServiceCall): + """Extract ids from service call data.""" + entity_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_ENTITY_ID) + device_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_DEVICE_ID) + area_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_AREA_ID) + + self.entity_ids = ( + set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() + ) + self.device_ids = ( + set(cv.ensure_list(device_ids)) if _has_match(device_ids) else set() + ) + self.area_ids = set(cv.ensure_list(area_ids)) if _has_match(area_ids) else set() + + @property + def has_any_selector(self) -> bool: + """Determine if any selectors are present.""" + return bool(self.entity_ids or self.device_ids or self.area_ids) + + @dataclasses.dataclass class SelectedEntities: """Class to hold the selected entities.""" @@ -93,6 +116,9 @@ class SelectedEntities: missing_devices: set[str] = dataclasses.field(default_factory=set) missing_areas: set[str] = dataclasses.field(default_factory=set) + # Referenced devices + referenced_devices: set[str] = dataclasses.field(default_factory=set) + def log_missing(self, missing_entities: set[str]) -> None: """Log about missing items.""" parts = [] @@ -293,98 +319,88 @@ async def async_extract_entity_ids( return referenced.referenced | referenced.indirectly_referenced +def _has_match(ids: Optional[Union[str, list]]) -> bool: + """Check if ids can match anything.""" + return ids not in (None, ENTITY_MATCH_NONE) + + @bind_hass async def async_extract_referenced_entity_ids( hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True ) -> SelectedEntities: """Extract referenced entity IDs from a service call.""" - entity_ids = service_call.data.get(ATTR_ENTITY_ID) - device_ids = service_call.data.get(ATTR_DEVICE_ID) - area_ids = service_call.data.get(ATTR_AREA_ID) - - selects_entity_ids = entity_ids not in (None, ENTITY_MATCH_NONE) - selects_device_ids = device_ids not in (None, ENTITY_MATCH_NONE) - selects_area_ids = area_ids not in (None, ENTITY_MATCH_NONE) - + selector = ServiceTargetSelector(service_call) selected = SelectedEntities() - if not selects_entity_ids and not selects_device_ids and not selects_area_ids: + if not selector.has_any_selector: return selected - if selects_entity_ids: - assert entity_ids is not None + entity_ids = selector.entity_ids + if expand_group: + entity_ids = hass.components.group.expand_entity_ids(entity_ids) - # Entity ID attr can be a list or a string - if isinstance(entity_ids, str): - entity_ids = [entity_ids] + selected.referenced.update(entity_ids) - if expand_group: - entity_ids = hass.components.group.expand_entity_ids(entity_ids) - - selected.referenced.update(entity_ids) - - if not selects_device_ids and not selects_area_ids: + if not selector.device_ids and not selector.area_ids: return selected - area_reg, dev_reg, ent_reg = cast( - Tuple[ - area_registry.AreaRegistry, - device_registry.DeviceRegistry, - entity_registry.EntityRegistry, - ], - await asyncio.gather( - area_registry.async_get_registry(hass), - device_registry.async_get_registry(hass), - entity_registry.async_get_registry(hass), - ), - ) - - picked_devices = set() - - if selects_device_ids: - if isinstance(device_ids, str): - picked_devices = {device_ids} - else: - assert isinstance(device_ids, list) - picked_devices = set(device_ids) - - for device_id in picked_devices: - if device_id not in dev_reg.devices: - selected.missing_devices.add(device_id) + ent_reg = entity_registry.async_get(hass) + dev_reg = device_registry.async_get(hass) + area_reg = area_registry.async_get(hass) - if selects_area_ids: - assert area_ids is not None + for device_id in selector.device_ids: + if device_id not in dev_reg.devices: + selected.missing_devices.add(device_id) - if isinstance(area_ids, str): - area_lookup = {area_ids} - else: - area_lookup = set(area_ids) - - for area_id in area_lookup: - if area_id not in area_reg.areas: - selected.missing_areas.add(area_id) - continue + for area_id in selector.area_ids: + if area_id not in area_reg.areas: + selected.missing_areas.add(area_id) - # Find entities tied to an area - for entity_entry in ent_reg.entities.values(): - if entity_entry.area_id in area_lookup: - selected.indirectly_referenced.add(entity_entry.entity_id) + # Find devices for this area + selected.referenced_devices.update(selector.device_ids) + for device_entry in dev_reg.devices.values(): + if device_entry.area_id in selector.area_ids: + selected.referenced_devices.add(device_entry.id) - # Find devices for this area - for device_entry in dev_reg.devices.values(): - if device_entry.area_id in area_lookup: - picked_devices.add(device_entry.id) - - if not picked_devices: + if not selector.area_ids and not selected.referenced_devices: return selected - for entity_entry in ent_reg.entities.values(): - if not entity_entry.area_id and entity_entry.device_id in picked_devices: - selected.indirectly_referenced.add(entity_entry.entity_id) + for ent_entry in ent_reg.entities.values(): + if ent_entry.area_id in selector.area_ids or ( + not ent_entry.area_id and ent_entry.device_id in selected.referenced_devices + ): + selected.indirectly_referenced.add(ent_entry.entity_id) return selected +@bind_hass +async def async_extract_config_entry_ids( + hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True +) -> set: + """Extract referenced config entry ids from a service call.""" + referenced = await async_extract_referenced_entity_ids( + hass, service_call, expand_group + ) + ent_reg = entity_registry.async_get(hass) + dev_reg = device_registry.async_get(hass) + config_entry_ids: set[str] = set() + + # Some devices may have no entities + for device_id in referenced.referenced_devices: + if device_id in dev_reg.devices: + device = dev_reg.async_get(device_id) + if device is not None: + config_entry_ids.update(device.config_entries) + + for entity_id in referenced.referenced | referenced.indirectly_referenced: + entry = ent_reg.async_get(entity_id) + if entry is not None and entry.config_entry_id is not None: + config_entry_ids.add(entry.config_entry_id) + + return config_entry_ids + + def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JSON_TYPE: """Load services file for an integration.""" try: diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index ef830c7ee77837..51646ae71391ee 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -11,6 +11,7 @@ from homeassistant import config import homeassistant.components as comps from homeassistant.components.homeassistant import ( + ATTR_ENTRY_ID, SERVICE_CHECK_CONFIG, SERVICE_RELOAD_CORE_CONFIG, SERVICE_SET_LOCATION, @@ -34,9 +35,11 @@ from homeassistant.setup import async_setup_component from tests.common import ( + MockConfigEntry, async_capture_events, async_mock_service, get_test_home_assistant, + mock_registry, mock_service, patch_yaml_files, ) @@ -385,3 +388,62 @@ async def test_not_allowing_recursion(hass, caplog): f"Called service homeassistant.{service} with invalid entities homeassistant.light" in caplog.text ), service + + +async def test_reload_config_entry_by_entity_id(hass): + """Test being able to reload a config entry by entity_id.""" + await async_setup_component(hass, "homeassistant", {}) + entity_reg = mock_registry(hass) + entry1 = MockConfigEntry(domain="mockdomain") + entry1.add_to_hass(hass) + entry2 = MockConfigEntry(domain="mockdomain") + entry2.add_to_hass(hass) + reg_entity1 = entity_reg.async_get_or_create( + "binary_sensor", "powerwall", "battery_charging", config_entry=entry1 + ) + reg_entity2 = entity_reg.async_get_or_create( + "binary_sensor", "powerwall", "battery_status", config_entry=entry2 + ) + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=None, + ) as mock_reload: + await hass.services.async_call( + "homeassistant", + "reload_config_entry", + {"entity_id": f"{reg_entity1.entity_id},{reg_entity2.entity_id}"}, + blocking=True, + ) + + assert len(mock_reload.mock_calls) == 2 + assert {mock_reload.mock_calls[0][1][0], mock_reload.mock_calls[1][1][0]} == { + entry1.entry_id, + entry2.entry_id, + } + + with pytest.raises(ValueError): + await hass.services.async_call( + "homeassistant", + "reload_config_entry", + {"entity_id": "unknown.entity_id"}, + blocking=True, + ) + + +async def test_reload_config_entry_by_entry_id(hass): + """Test being able to reload a config entry by config entry id.""" + await async_setup_component(hass, "homeassistant", {}) + + with patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=None, + ) as mock_reload: + await hass.services.async_call( + "homeassistant", + "reload_config_entry", + {ATTR_ENTRY_ID: "8955375327824e14ba89e4b29cc3ec9a"}, + blocking=True, + ) + + assert len(mock_reload.mock_calls) == 1 + assert mock_reload.mock_calls[0][1][0] == "8955375327824e14ba89e4b29cc3ec9a" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 92cbd5514e6cd3..7a084fed9dd6b0 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -1015,3 +1015,28 @@ async def test_async_extract_entities_warn_referenced(hass, caplog): "Unable to find referenced areas non-existent-area, devices non-existent-device, entities non.existent" in caplog.text ) + + +async def test_async_extract_config_entry_ids(hass): + """Test we can find devices that have no entities.""" + + device_no_entities = dev_reg.DeviceEntry( + id="device-no-entities", config_entries={"abc"} + ) + + call = ha.ServiceCall( + "homeassistant", + "reload_config_entry", + { + "device_id": "device-no-entities", + }, + ) + + mock_device_registry( + hass, + { + device_no_entities.id: device_no_entities, + }, + ) + + assert await service.async_extract_config_entry_ids(hass, call) == {"abc"} From 9e1a6610dc4f46922b2f2571c6f547b182de52e5 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 08:02:55 +0100 Subject: [PATCH 1338/1818] Update typing 07 (#48057) --- .../components/garmin_connect/sensor.py | 6 ++- .../components/gdacs/geo_location.py | 13 ++++--- homeassistant/components/gdacs/sensor.py | 7 ++-- .../components/geniushub/__init__.py | 16 ++++---- homeassistant/components/geniushub/climate.py | 10 ++--- homeassistant/components/geniushub/sensor.py | 6 ++- .../components/geniushub/water_heater.py | 4 +- .../geo_json_events/geo_location.py | 11 +++--- .../components/geo_location/__init__.py | 9 +++-- .../geonetnz_quakes/geo_location.py | 13 ++++--- .../components/geonetnz_quakes/sensor.py | 5 ++- .../components/geonetnz_volcano/__init__.py | 7 ++-- .../components/geonetnz_volcano/sensor.py | 5 ++- homeassistant/components/gogogate2/common.py | 14 ++++--- homeassistant/components/gogogate2/cover.py | 6 ++- homeassistant/components/gogogate2/sensor.py | 6 ++- .../components/google_assistant/__init__.py | 6 ++- .../components/google_assistant/helpers.py | 13 ++++--- .../components/google_assistant/trait.py | 5 ++- .../components/google_pubsub/__init__.py | 6 ++- homeassistant/components/gree/bridge.py | 5 ++- homeassistant/components/gree/climate.py | 11 +++--- homeassistant/components/gree/switch.py | 4 +- homeassistant/components/group/__init__.py | 24 ++++++------ homeassistant/components/group/cover.py | 12 +++--- homeassistant/components/group/light.py | 38 ++++++++++--------- .../components/group/reproduce_state.py | 8 ++-- homeassistant/components/gtfs/sensor.py | 14 ++++--- homeassistant/components/guardian/__init__.py | 5 ++- .../components/guardian/binary_sensor.py | 14 ++++--- homeassistant/components/guardian/sensor.py | 18 +++++---- homeassistant/components/guardian/switch.py | 6 ++- 32 files changed, 185 insertions(+), 142 deletions(-) diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index d2a6419cc75d11..ba43a894cfe38a 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -1,6 +1,8 @@ """Platform for Garmin Connect integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from garminconnect import ( GarminConnectAuthenticationError, @@ -136,7 +138,7 @@ def extra_state_attributes(self): return attributes @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information.""" return { "identifiers": {(DOMAIN, self._unique_id)}, diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index a2178bb78e07d8..7e3dc7484bb94f 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -1,6 +1,7 @@ """Geolocation support for GDACS Feed.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import ( @@ -169,7 +170,7 @@ def _update_from_feed(self, feed_entry): self._version = feed_entry.version @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID containing latitude/longitude and external id.""" return f"{self._integration_id}_{self._external_id}" @@ -186,22 +187,22 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._title @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index c3642181427903..7ab63fd6c6ab58 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -1,6 +1,7 @@ """Feed Entity Manager Sensor support for GDACS Feed.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -109,12 +110,12 @@ def state(self): return self._total @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID containing latitude/longitude.""" return self._config_unique_id @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return f"GDACS ({self._config_title})" diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 15a2c8e7686e3e..f1d2a1d47c1bd8 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -1,7 +1,9 @@ """Support for a Genius Hub system.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any import aiohttp from geniushubclient import GeniusHub @@ -218,12 +220,12 @@ async def async_added_to_hass(self) -> None: """Set up a listener when this entity is added to HA.""" self.async_on_remove(async_dispatcher_connect(self.hass, DOMAIN, self._refresh)) - async def _refresh(self, payload: Optional[dict] = None) -> None: + async def _refresh(self, payload: dict | None = None) -> None: """Process any signals.""" self.async_schedule_update_ha_state(force_refresh=True) @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @@ -250,7 +252,7 @@ def __init__(self, broker, device) -> None: self._last_comms = self._state_attr = None @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" attrs = {} attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"] @@ -285,7 +287,7 @@ def __init__(self, broker, zone) -> None: self._zone = zone self._unique_id = f"{broker.hub_uid}_zone_{zone.id}" - async def _refresh(self, payload: Optional[dict] = None) -> None: + async def _refresh(self, payload: dict | None = None) -> None: """Process any signals.""" if payload is None: self.async_schedule_update_ha_state(force_refresh=True) @@ -317,7 +319,7 @@ def name(self) -> str: return self._zone.name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" status = {k: v for k, v in self._zone.data.items() if k in GH_ZONE_ATTRS} return {"status": status} @@ -333,7 +335,7 @@ def __init__(self, broker, zone) -> None: self._max_temp = self._min_temp = self._supported_features = None @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._zone.data.get("temperature") diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py index 70d08dc2d1fa69..089fd96483555e 100644 --- a/homeassistant/components/geniushub/climate.py +++ b/homeassistant/components/geniushub/climate.py @@ -1,5 +1,5 @@ """Support for Genius Hub climate devices.""" -from typing import List, Optional +from __future__ import annotations from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -67,12 +67,12 @@ def hvac_mode(self) -> str: return GH_HVAC_TO_HA.get(self._zone.data["mode"], HVAC_MODE_HEAT) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(HA_HVAC_TO_GH) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if "_state" in self._zone.data: # only for v3 API if not self._zone.data["_state"].get("bIsActive"): @@ -83,12 +83,12 @@ def hvac_action(self) -> Optional[str]: return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return GH_PRESET_TO_HA.get(self._zone.data["mode"]) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" if "occupied" in self._zone.data: # if has a movement sensor return [PRESET_ACTIVITY, PRESET_BOOST] diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 9d87fb46cb0ddb..10c53994529286 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -1,6 +1,8 @@ """Support for Genius Hub sensor devices.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Dict +from typing import Any from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -106,7 +108,7 @@ def state(self) -> str: return len(self._issues) @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" return {f"{self._level}_list": self._issues} diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index 51fdce4a6d7c59..bb775432d8e15d 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -1,5 +1,5 @@ """Support for Genius Hub water_heater devices.""" -from typing import List +from __future__ import annotations from homeassistant.components.water_heater import ( SUPPORT_OPERATION_MODE, @@ -61,7 +61,7 @@ def __init__(self, broker, zone) -> None: self._supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @property - def operation_list(self) -> List[str]: + def operation_list(self) -> list[str]: """Return the list of available operation modes.""" return list(HA_OPMODE_TO_GH) diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index feb0b98b3facce..cfd58124c16e8b 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -1,7 +1,8 @@ """Support for generic GeoJSON events.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from geojson_client.generic_feed import GenericFeedManager import voluptuous as vol @@ -176,22 +177,22 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index 6142fa222095d7..2041c147b2b138 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -1,7 +1,8 @@ """Support for Geolocation.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.helpers.config_validation import ( # noqa: F401 @@ -60,17 +61,17 @@ def source(self) -> str: raise NotImplementedError @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return None @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return None @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return None diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 62086f059f42e1..1643264fd751e7 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -1,6 +1,7 @@ """Geolocation support for GeoNet NZ Quakes Feeds.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.components.geo_location import GeolocationEvent from homeassistant.const import ( @@ -142,7 +143,7 @@ def _update_from_feed(self, feed_entry): self._time = feed_entry.time @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID containing latitude/longitude and external id.""" return f"{self._integration_id}_{self._external_id}" @@ -157,22 +158,22 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._title @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index e54767570247d8..565e022f0366a7 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -1,6 +1,7 @@ """Feed Entity Manager Sensor support for GeoNet NZ Quakes Feeds.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -115,7 +116,7 @@ def unique_id(self) -> str: return self._config_unique_id @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return f"GeoNet NZ Quakes ({self._config_title})" diff --git a/homeassistant/components/geonetnz_volcano/__init__.py b/homeassistant/components/geonetnz_volcano/__init__.py index e2c6cb77083f49..c3db7770499f95 100644 --- a/homeassistant/components/geonetnz_volcano/__init__.py +++ b/homeassistant/components/geonetnz_volcano/__init__.py @@ -1,8 +1,9 @@ """The GeoNet NZ Volcano integration.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import logging -from typing import Optional from aio_geojson_geonetnz_volcano import GeonetnzVolcanoFeedManager import voluptuous as vol @@ -172,11 +173,11 @@ def get_entry(self, external_id): """Get feed entry by external id.""" return self._feed_manager.feed_entries.get(external_id) - def last_update(self) -> Optional[datetime]: + def last_update(self) -> datetime | None: """Return the last update of this feed.""" return self._feed_manager.last_update - def last_update_successful(self) -> Optional[datetime]: + def last_update_successful(self) -> datetime | None: """Return the last successful update of this feed.""" return self._feed_manager.last_update_successful diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 7044b3c5609830..2ec4940f88d8ed 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -1,6 +1,7 @@ """Feed Entity Manager Sensor support for GeoNet NZ Volcano Feeds.""" +from __future__ import annotations + import logging -from typing import Optional from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -139,7 +140,7 @@ def icon(self): return DEFAULT_ICON @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return f"Volcano {self._title}" diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 404a22e33124b4..e8b17184bbe559 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -1,7 +1,9 @@ """Common code for GogoGate2 component.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Awaitable, Callable, NamedTuple, Optional +from typing import Awaitable, Callable, NamedTuple from gogogate2_api import AbstractGateApi, GogoGate2Api, ISmartGateApi from gogogate2_api.common import AbstractDoor, get_door_by_id @@ -30,8 +32,8 @@ class StateData(NamedTuple): """State data for a cover entity.""" config_unique_id: str - unique_id: Optional[str] - door: Optional[AbstractDoor] + unique_id: str | None + door: AbstractDoor | None class DeviceDataUpdateCoordinator(DataUpdateCoordinator): @@ -45,8 +47,8 @@ def __init__( *, name: str, update_interval: timedelta, - update_method: Optional[Callable[[], Awaitable]] = None, - request_refresh_debouncer: Optional[Debouncer] = None, + update_method: Callable[[], Awaitable] | None = None, + request_refresh_debouncer: Debouncer | None = None, ): """Initialize the data update coordinator.""" DataUpdateCoordinator.__init__( @@ -78,7 +80,7 @@ def __init__( self._unique_id = unique_id @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 9a1b53f7bee066..62302e8f6697eb 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,6 +1,8 @@ """Support for Gogogate2 garage Doors.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable from gogogate2_api.common import AbstractDoor, DoorStatus, get_configured_doors @@ -44,7 +46,7 @@ async def async_setup_platform( async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], Optional[bool]], None], + async_add_entities: Callable[[list[Entity], bool | None], None], ) -> None: """Set up the config entry.""" data_update_coordinator = get_data_update_coordinator(hass, config_entry) diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index eea557639ad353..2bdb1b3170c0cb 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -1,6 +1,8 @@ """Support for Gogogate2 garage Doors.""" +from __future__ import annotations + from itertools import chain -from typing import Callable, List, Optional +from typing import Callable from gogogate2_api.common import AbstractDoor, get_configured_doors @@ -26,7 +28,7 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], Optional[bool]], None], + async_add_entities: Callable[[list[Entity], bool | None], None], ) -> None: """Set up the config entry.""" data_update_coordinator = get_data_update_coordinator(hass, config_entry) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index 00c09242517e8c..7793ed4d65974a 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -1,6 +1,8 @@ """Support for Actions on Google Assistant Smart Home Control.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any import voluptuous as vol @@ -87,7 +89,7 @@ def _check_report_state(data): CONFIG_SCHEMA = vol.Schema({DOMAIN: GOOGLE_ASSISTANT_SCHEMA}, extra=vol.ALLOW_EXTRA) -async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): +async def async_setup(hass: HomeAssistant, yaml_config: dict[str, Any]): """Activate Google Actions component.""" config = yaml_config.get(DOMAIN, {}) diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index b4900d83b64d12..9b133ad6a30854 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -1,10 +1,11 @@ """Helper classes for Google Assistant integration.""" +from __future__ import annotations + from abc import ABC, abstractmethod from asyncio import gather from collections.abc import Mapping import logging import pprint -from typing import Dict, List, Optional, Tuple from aiohttp.web import json_response @@ -44,7 +45,7 @@ async def _get_entity_and_device( hass, entity_id -) -> Optional[Tuple[RegistryEntry, DeviceEntry]]: +) -> tuple[RegistryEntry, DeviceEntry] | None: """Fetch the entity and device entries for a entity_id.""" dev_reg, ent_reg = await gather( hass.helpers.device_registry.async_get_registry(), @@ -58,7 +59,7 @@ async def _get_entity_and_device( return entity_entry, device_entry -async def _get_area(hass, entity_entry, device_entry) -> Optional[AreaEntry]: +async def _get_area(hass, entity_entry, device_entry) -> AreaEntry | None: """Calculate the area for an entity.""" if entity_entry and entity_entry.area_id: area_id = entity_entry.area_id @@ -71,7 +72,7 @@ async def _get_area(hass, entity_entry, device_entry) -> Optional[AreaEntry]: return area_reg.areas.get(area_id) -async def _get_device_info(device_entry) -> Optional[Dict[str, str]]: +async def _get_device_info(device_entry) -> dict[str, str] | None: """Retrieve the device info for a device.""" if not device_entry: return None @@ -344,7 +345,7 @@ def __init__( user_id: str, source: str, request_id: str, - devices: Optional[List[dict]], + devices: list[dict] | None, ): """Initialize the request data.""" self.config = config @@ -578,7 +579,7 @@ def deep_update(target, source): @callback -def async_get_entities(hass, config) -> List[GoogleEntity]: +def async_get_entities(hass, config) -> list[GoogleEntity]: """Return all entities that are supported by Google.""" entities = [] for state in hass.states.async_all(): diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 7babc5e4836192..8f01482aa45977 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1,6 +1,7 @@ """Implement the Google Smart Home traits.""" +from __future__ import annotations + import logging -from typing import List, Optional from homeassistant.components import ( alarm_control_panel, @@ -153,7 +154,7 @@ def _google_temp_unit(units): return "C" -def _next_selected(items: List[str], selected: Optional[str]) -> Optional[str]: +def _next_selected(items: list[str], selected: str | None) -> str | None: """Return the next item in a item list starting at given value. If selected is missing in items, None is returned diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index c832c87318c336..365c118e99e7f1 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -1,9 +1,11 @@ """Support for Google Cloud Pub/Sub.""" +from __future__ import annotations + import datetime import json import logging import os -from typing import Any, Dict +from typing import Any from google.cloud import pubsub_v1 import voluptuous as vol @@ -37,7 +39,7 @@ ) -def setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): +def setup(hass: HomeAssistant, yaml_config: dict[str, Any]): """Activate Google Pub/Sub component.""" config = yaml_config[DOMAIN] diff --git a/homeassistant/components/gree/bridge.py b/homeassistant/components/gree/bridge.py index 3fbf4a21fb39d7..af523f385aa3e1 100644 --- a/homeassistant/components/gree/bridge.py +++ b/homeassistant/components/gree/bridge.py @@ -1,7 +1,8 @@ """Helper and wrapper classes for Gree module.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List from greeclimate.device import Device, DeviceInfo from greeclimate.discovery import Discovery @@ -86,7 +87,7 @@ async def try_bind_device(device_info: DeviceInfo) -> Device: return device @staticmethod - async def find_devices() -> List[DeviceInfo]: + async def find_devices() -> list[DeviceInfo]: """Gather a list of device infos from the local network.""" return await Discovery.search_devices() diff --git a/homeassistant/components/gree/climate.py b/homeassistant/components/gree/climate.py index 8d0170fbe50328..a5ef39be071d4c 100644 --- a/homeassistant/components/gree/climate.py +++ b/homeassistant/components/gree/climate.py @@ -1,6 +1,7 @@ """Support for interface with a Gree climate systems.""" +from __future__ import annotations + import logging -from typing import List from greeclimate.device import ( FanSpeed, @@ -234,7 +235,7 @@ async def async_turn_off(self) -> None: self.async_write_ha_state() @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the HVAC modes support by the device.""" modes = [*HVAC_MODES_REVERSE] modes.append(HVAC_MODE_OFF) @@ -282,7 +283,7 @@ async def async_set_preset_mode(self, preset_mode): self.async_write_ha_state() @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return the preset modes support by the device.""" return PRESET_MODES @@ -302,7 +303,7 @@ async def async_set_fan_mode(self, fan_mode): self.async_write_ha_state() @property - def fan_modes(self) -> List[str]: + def fan_modes(self) -> list[str]: """Return the fan modes support by the device.""" return [*FAN_MODES_REVERSE] @@ -342,7 +343,7 @@ async def async_set_swing_mode(self, swing_mode): self.async_write_ha_state() @property - def swing_modes(self) -> List[str]: + def swing_modes(self) -> list[str]: """Return the swing modes currently supported for this device.""" return SWING_MODES diff --git a/homeassistant/components/gree/switch.py b/homeassistant/components/gree/switch.py index fa1f1550e83d81..12c94ddec61893 100644 --- a/homeassistant/components/gree/switch.py +++ b/homeassistant/components/gree/switch.py @@ -1,5 +1,5 @@ """Support for interface with a Gree climate systems.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.switch import DEVICE_CLASS_SWITCH, SwitchEntity from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC @@ -38,7 +38,7 @@ def unique_id(self) -> str: return f"{self._mac}-panel-light" @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon for the device.""" return "mdi:lightbulb" diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 1ce0b3e71d703d..96822b9f993b47 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -1,9 +1,11 @@ """Provide the functionality to group entities.""" +from __future__ import annotations + from abc import abstractmethod import asyncio from contextvars import ContextVar import logging -from typing import Any, Dict, Iterable, List, Optional, Set, cast +from typing import Any, Iterable, List, cast import voluptuous as vol @@ -91,16 +93,16 @@ def _conf_preprocess(value): class GroupIntegrationRegistry: """Class to hold a registry of integrations.""" - on_off_mapping: Dict[str, str] = {STATE_ON: STATE_OFF} - off_on_mapping: Dict[str, str] = {STATE_OFF: STATE_ON} - on_states_by_domain: Dict[str, Set] = {} - exclude_domains: Set = set() + on_off_mapping: dict[str, str] = {STATE_ON: STATE_OFF} + off_on_mapping: dict[str, str] = {STATE_OFF: STATE_ON} + on_states_by_domain: dict[str, set] = {} + exclude_domains: set = set() def exclude_domain(self) -> None: """Exclude the current domain.""" self.exclude_domains.add(current_domain.get()) - def on_off_states(self, on_states: Set, off_state: str) -> None: + def on_off_states(self, on_states: set, off_state: str) -> None: """Register on and off states for the current domain.""" for on_state in on_states: if on_state not in self.on_off_mapping: @@ -128,12 +130,12 @@ def is_on(hass, entity_id): @bind_hass -def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> List[str]: +def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> list[str]: """Return entity_ids with group entity ids replaced by their members. Async friendly. """ - found_ids: List[str] = [] + found_ids: list[str] = [] for entity_id in entity_ids: if not isinstance(entity_id, str) or entity_id in ( ENTITY_MATCH_NONE, @@ -171,8 +173,8 @@ def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> Lis @bind_hass def get_entity_ids( - hass: HomeAssistantType, entity_id: str, domain_filter: Optional[str] = None -) -> List[str]: + hass: HomeAssistantType, entity_id: str, domain_filter: str | None = None +) -> list[str]: """Get members of this group. Async friendly. @@ -192,7 +194,7 @@ def get_entity_ids( @bind_hass -def groups_with_entity(hass: HomeAssistantType, entity_id: str) -> List[str]: +def groups_with_entity(hass: HomeAssistantType, entity_id: str) -> list[str]: """Get all groups that contain this entity. Async friendly. diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index c19e81778a00c2..5e8d18b28e2371 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -1,5 +1,5 @@ """This platform allows several cover to be grouped into one cover.""" -from typing import Dict, Optional, Set +from __future__ import annotations import voluptuous as vol @@ -76,18 +76,18 @@ def __init__(self, name, entities): self._is_closed = False self._is_closing = False self._is_opening = False - self._cover_position: Optional[int] = 100 + self._cover_position: int | None = 100 self._tilt_position = None self._supported_features = 0 self._assumed_state = True self._entities = entities - self._covers: Dict[str, Set[str]] = { + self._covers: dict[str, set[str]] = { KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set(), } - self._tilts: Dict[str, Set[str]] = { + self._tilts: dict[str, set[str]] = { KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set(), @@ -102,7 +102,7 @@ async def _update_supported_features_event(self, event): async def async_update_supported_features( self, entity_id: str, - new_state: Optional[State], + new_state: State | None, update_state: bool = True, ) -> None: """Update dictionaries with supported features.""" @@ -197,7 +197,7 @@ def is_closing(self): return self._is_closing @property - def current_cover_position(self) -> Optional[int]: + def current_cover_position(self) -> int | None: """Return current position for all covers.""" return self._cover_position diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index cebc697efaee88..a1cf47411fc06a 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -1,8 +1,10 @@ """This platform allows several lights to be grouped into one light.""" +from __future__ import annotations + import asyncio from collections import Counter import itertools -from typing import Any, Callable, Iterator, List, Optional, Tuple, cast +from typing import Any, Callable, Iterator, cast import voluptuous as vol @@ -78,21 +80,21 @@ async def async_setup_platform( class LightGroup(GroupEntity, light.LightEntity): """Representation of a light group.""" - def __init__(self, name: str, entity_ids: List[str]) -> None: + def __init__(self, name: str, entity_ids: list[str]) -> None: """Initialize a light group.""" self._name = name self._entity_ids = entity_ids self._is_on = False self._available = False self._icon = "mdi:lightbulb-group" - self._brightness: Optional[int] = None - self._hs_color: Optional[Tuple[float, float]] = None - self._color_temp: Optional[int] = None + self._brightness: int | None = None + self._hs_color: tuple[float, float] | None = None + self._color_temp: int | None = None self._min_mireds: int = 154 self._max_mireds: int = 500 - self._white_value: Optional[int] = None - self._effect_list: Optional[List[str]] = None - self._effect: Optional[str] = None + self._white_value: int | None = None + self._effect_list: list[str] | None = None + self._effect: str | None = None self._supported_features: int = 0 async def async_added_to_hass(self) -> None: @@ -136,17 +138,17 @@ def icon(self): return self._icon @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light group between 0..255.""" return self._brightness @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the HS color value [float, float].""" return self._hs_color @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" return self._color_temp @@ -161,17 +163,17 @@ def max_mireds(self) -> int: return self._max_mireds @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light group between 0..255.""" return self._white_value @property - def effect_list(self) -> Optional[List[str]]: + def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" return self._effect_list @property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect.""" return self._effect @@ -288,7 +290,7 @@ async def async_turn_off(self, **kwargs): async def async_update(self): """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) + states: list[State] = list(filter(None, all_states)) on_states = [state for state in states if state.state == STATE_ON] self._is_on = len(on_states) > 0 @@ -331,7 +333,7 @@ async def async_update(self): self._supported_features &= SUPPORT_GROUP_LIGHT -def _find_state_attributes(states: List[State], key: str) -> Iterator[Any]: +def _find_state_attributes(states: list[State], key: str) -> Iterator[Any]: """Find attributes with matching key from states.""" for state in states: value = state.attributes.get(key) @@ -350,9 +352,9 @@ def _mean_tuple(*args): def _reduce_attribute( - states: List[State], + states: list[State], key: str, - default: Optional[Any] = None, + default: Any | None = None, reduce: Callable[..., Any] = _mean_int, ) -> Any: """Find the first attribute matching key from states. diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index adeb0cfee0aee7..f91b1e119af573 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -1,5 +1,7 @@ """Module that groups code required to handle state restore for component.""" -from typing import Any, Dict, Iterable, Optional +from __future__ import annotations + +from typing import Any, Iterable from homeassistant.core import Context, State from homeassistant.helpers.state import async_reproduce_state @@ -12,8 +14,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" states_copy = [] diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 23f9e2a80218db..737348548a8b1e 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -1,9 +1,11 @@ """Support for GTFS (Google/General Transport Format Schema).""" +from __future__ import annotations + import datetime import logging import os import threading -from typing import Any, Callable, Optional +from typing import Any, Callable import pygtfs from sqlalchemy.sql import text @@ -484,7 +486,7 @@ def setup_platform( hass: HomeAssistantType, config: ConfigType, add_entities: Callable[[list], None], - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the GTFS sensor.""" gtfs_dir = hass.config.path(DEFAULT_PATH) @@ -523,7 +525,7 @@ class GTFSDepartureSensor(Entity): def __init__( self, gtfs: Any, - name: Optional[Any], + name: Any | None, origin: Any, destination: Any, offset: cv.time_period, @@ -540,7 +542,7 @@ def __init__( self._available = False self._icon = ICON self._name = "" - self._state: Optional[str] = None + self._state: str | None = None self._attributes = {} self._agency = None @@ -559,7 +561,7 @@ def name(self) -> str: return self._name @property - def state(self) -> Optional[str]: # type: ignore + def state(self) -> str | None: # type: ignore """Return the state of the sensor.""" return self._state @@ -811,7 +813,7 @@ def dict_for_table(resource: Any) -> dict: col: getattr(resource, col) for col in resource.__table__.columns.keys() } - def append_keys(self, resource: dict, prefix: Optional[str] = None) -> None: + def append_keys(self, resource: dict, prefix: str | None = None) -> None: """Properly format key val pairs to append to attributes.""" for attr, val in resource.items(): if val == "" or val is None or attr == "feed_id": diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 98f35d34e14df1..ebb5e71e1cbb6b 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -1,6 +1,7 @@ """The Elexa Guardian integration.""" +from __future__ import annotations + import asyncio -from typing import Dict from aioguardian import Client @@ -314,7 +315,7 @@ class ValveControllerEntity(GuardianEntity): def __init__( self, entry: ConfigEntry, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], kind: str, name: str, device_class: str, diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index d8d0498304d082..e8c736eabe5535 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -1,5 +1,7 @@ """Binary sensors for the Elexa Guardian integration.""" -from typing import Callable, Dict, Optional +from __future__ import annotations + +from typing import Callable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -122,8 +124,8 @@ def __init__( coordinator: DataUpdateCoordinator, kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], + device_class: str | None, + icon: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinator, kind, name, device_class, icon) @@ -155,11 +157,11 @@ class ValveControllerBinarySensor(ValveControllerEntity, BinarySensorEntity): def __init__( self, entry: ConfigEntry, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], + device_class: str | None, + icon: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinators, kind, name, device_class, icon) diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 160246b2014915..9b778128d19512 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -1,5 +1,7 @@ """Sensors for the Elexa Guardian integration.""" -from typing import Callable, Dict, Optional +from __future__ import annotations + +from typing import Callable from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -117,9 +119,9 @@ def __init__( coordinator: DataUpdateCoordinator, kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], - unit: Optional[str], + device_class: str | None, + icon: str | None, + unit: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinator, kind, name, device_class, icon) @@ -157,12 +159,12 @@ class ValveControllerSensor(ValveControllerEntity): def __init__( self, entry: ConfigEntry, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], kind: str, name: str, - device_class: Optional[str], - icon: Optional[str], - unit: Optional[str], + device_class: str | None, + icon: str | None, + unit: str | None, ) -> None: """Initialize.""" super().__init__(entry, coordinators, kind, name, device_class, icon) diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py index 20a38ea5ce72ca..c574f283bdd946 100644 --- a/homeassistant/components/guardian/switch.py +++ b/homeassistant/components/guardian/switch.py @@ -1,5 +1,7 @@ """Switches for the Elexa Guardian integration.""" -from typing import Callable, Dict +from __future__ import annotations + +from typing import Callable from aioguardian import Client from aioguardian.errors import GuardianError @@ -84,7 +86,7 @@ def __init__( self, entry: ConfigEntry, client: Client, - coordinators: Dict[str, DataUpdateCoordinator], + coordinators: dict[str, DataUpdateCoordinator], ): """Initialize.""" super().__init__( From 5cdd945f440c39d98979f156b88d9ecf29c13011 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 09:25:40 +0100 Subject: [PATCH 1339/1818] Update typing 08 (#48058) --- .../components/habitica/config_flow.py | 7 +-- homeassistant/components/hassio/__init__.py | 14 ++--- .../components/hassio/binary_sensor.py | 6 ++- homeassistant/components/hassio/entity.py | 14 ++--- homeassistant/components/hassio/http.py | 9 ++-- homeassistant/components/hassio/ingress.py | 13 +++-- homeassistant/components/hassio/sensor.py | 6 ++- homeassistant/components/heatmiser/climate.py | 5 +- homeassistant/components/heos/__init__.py | 5 +- .../components/here_travel_time/sensor.py | 16 +++--- homeassistant/components/history/__init__.py | 6 ++- .../components/homeassistant/scene.py | 8 +-- .../homeassistant/triggers/state.py | 10 ++-- .../components/homekit_controller/__init__.py | 6 ++- .../homekit_controller/device_trigger.py | 4 +- .../homekit_controller/humidifier.py | 14 ++--- .../homematicip_cloud/alarm_control_panel.py | 6 ++- .../homematicip_cloud/binary_sensor.py | 16 +++--- .../components/homematicip_cloud/climate.py | 24 +++++---- .../homematicip_cloud/config_flow.py | 12 +++-- .../components/homematicip_cloud/cover.py | 10 ++-- .../homematicip_cloud/generic_entity.py | 16 +++--- .../components/homematicip_cloud/light.py | 8 +-- .../components/homematicip_cloud/sensor.py | 12 +++-- .../components/homematicip_cloud/services.py | 5 +- .../components/homematicip_cloud/switch.py | 6 ++- homeassistant/components/honeywell/climate.py | 32 ++++++------ homeassistant/components/http/__init__.py | 12 +++-- homeassistant/components/http/ban.py | 9 ++-- homeassistant/components/http/view.py | 14 ++--- homeassistant/components/http/web_runner.py | 11 ++-- .../components/huawei_lte/__init__.py | 25 ++++----- .../components/huawei_lte/binary_sensor.py | 11 ++-- .../components/huawei_lte/config_flow.py | 30 +++++------ .../components/huawei_lte/device_tracker.py | 21 ++++---- homeassistant/components/huawei_lte/notify.py | 10 ++-- homeassistant/components/huawei_lte/sensor.py | 33 ++++++------ homeassistant/components/huawei_lte/switch.py | 9 ++-- homeassistant/components/hue/config_flow.py | 18 ++++--- .../components/humidifier/__init__.py | 14 ++--- .../components/humidifier/device_action.py | 6 +-- .../components/humidifier/device_condition.py | 4 +- .../components/humidifier/device_trigger.py | 4 +- .../components/humidifier/reproduce_state.py | 12 +++-- homeassistant/components/hyperion/__init__.py | 15 +++--- .../components/hyperion/config_flow.py | 52 +++++++++---------- homeassistant/components/hyperion/light.py | 46 ++++++++-------- homeassistant/components/hyperion/switch.py | 5 +- 48 files changed, 355 insertions(+), 296 deletions(-) diff --git a/homeassistant/components/habitica/config_flow.py b/homeassistant/components/habitica/config_flow.py index 6e3311ea9b5b59..3a77aa3a5b6276 100644 --- a/homeassistant/components/habitica/config_flow.py +++ b/homeassistant/components/habitica/config_flow.py @@ -1,6 +1,7 @@ """Config flow for habitica integration.""" +from __future__ import annotations + import logging -from typing import Dict from aiohttp import ClientResponseError from habitipy.aio import HabitipyAsync @@ -25,8 +26,8 @@ async def validate_input( - hass: core.HomeAssistant, data: Dict[str, str] -) -> Dict[str, str]: + hass: core.HomeAssistant, data: dict[str, str] +) -> dict[str, str]: """Validate the user input allows us to connect.""" websession = async_get_clientsession(hass) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index c09927fa7d2f38..9fcb73884b318a 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -1,9 +1,11 @@ """Support for Hass.io.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import os -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol @@ -236,7 +238,7 @@ async def async_set_addon_options( @bind_hass async def async_get_addon_discovery_info( hass: HomeAssistantType, slug: str -) -> Optional[dict]: +) -> dict | None: """Return discovery data for an add-on.""" hassio = hass.data[DOMAIN] data = await hassio.retrieve_discovery_messages() @@ -545,7 +547,7 @@ async def async_unload_entry( @callback def async_register_addons_in_dev_reg( - entry_id: str, dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] + entry_id: str, dev_reg: DeviceRegistry, addons: list[dict[str, Any]] ) -> None: """Register addons in the device registry.""" for addon in addons: @@ -564,7 +566,7 @@ def async_register_addons_in_dev_reg( @callback def async_register_os_in_dev_reg( - entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any] + entry_id: str, dev_reg: DeviceRegistry, os_dict: dict[str, Any] ) -> None: """Register OS in the device registry.""" params = { @@ -581,7 +583,7 @@ def async_register_os_in_dev_reg( @callback def async_remove_addons_from_dev_reg( - dev_reg: DeviceRegistry, addons: List[Dict[str, Any]] + dev_reg: DeviceRegistry, addons: list[dict[str, Any]] ) -> None: """Remove addons from the device registry.""" for addon_slug in addons: @@ -607,7 +609,7 @@ def __init__( self.dev_reg = dev_reg self.is_hass_os = "hassos" in get_info(self.hass) - async def _async_update_data(self) -> Dict[str, Any]: + async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" new_data = {} addon_data = get_supervisor_info(self.hass) diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py index 2208f3fb580867..b6faf566807349 100644 --- a/homeassistant/components/hassio/binary_sensor.py +++ b/homeassistant/components/hassio/binary_sensor.py @@ -1,5 +1,7 @@ """Binary sensor platform for Hass.io addons.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -14,7 +16,7 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Binary sensor set up for Hass.io config entry.""" coordinator = hass.data[ADDONS_COORDINATOR] diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py index daadeb514a2e90..5f35235bb5ded3 100644 --- a/homeassistant/components/hassio/entity.py +++ b/homeassistant/components/hassio/entity.py @@ -1,5 +1,7 @@ """Base for Hass.io entities.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homeassistant.const import ATTR_NAME from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -14,7 +16,7 @@ class HassioAddonEntity(CoordinatorEntity): def __init__( self, coordinator: HassioDataUpdateCoordinator, - addon: Dict[str, Any], + addon: dict[str, Any], attribute_name: str, sensor_name: str, ) -> None: @@ -27,7 +29,7 @@ def __init__( super().__init__(coordinator) @property - def addon_info(self) -> Dict[str, Any]: + def addon_info(self) -> dict[str, Any]: """Return add-on info.""" return self.coordinator.data[self._data_key][self.addon_slug] @@ -47,7 +49,7 @@ def unique_id(self) -> str: return f"{self.addon_slug}_{self.attribute_name}" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return {"identifiers": {(DOMAIN, self.addon_slug)}} @@ -68,7 +70,7 @@ def __init__( super().__init__(coordinator) @property - def os_info(self) -> Dict[str, Any]: + def os_info(self) -> dict[str, Any]: """Return OS info.""" return self.coordinator.data[self._data_key] @@ -88,6 +90,6 @@ def unique_id(self) -> str: return f"home_assistant_os_{self.attribute_name}" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return {"identifiers": {(DOMAIN, "OS")}} diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index 2aa05ae6ab429e..e1bd1cb095c53e 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -1,9 +1,10 @@ """HTTP Support for Hass.io.""" +from __future__ import annotations + import asyncio import logging import os import re -from typing import Dict, Union import aiohttp from aiohttp import web @@ -57,7 +58,7 @@ def __init__(self, host: str, websession: aiohttp.ClientSession): async def _handle( self, request: web.Request, path: str - ) -> Union[web.Response, web.StreamResponse]: + ) -> web.Response | web.StreamResponse: """Route data to Hass.io.""" hass = request.app["hass"] if _need_auth(hass, path) and not request[KEY_AUTHENTICATED]: @@ -71,7 +72,7 @@ async def _handle( async def _command_proxy( self, path: str, request: web.Request - ) -> Union[web.Response, web.StreamResponse]: + ) -> web.Response | web.StreamResponse: """Return a client request with proxy origin for Hass.io supervisor. This method is a coroutine. @@ -131,7 +132,7 @@ async def _command_proxy( raise HTTPBadGateway() -def _init_header(request: web.Request) -> Dict[str, str]: +def _init_header(request: web.Request) -> dict[str, str]: """Create initial header.""" headers = { X_HASSIO: os.environ.get("HASSIO_TOKEN", ""), diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index c69d2078468fd7..1f0a49ae497ee0 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -1,9 +1,10 @@ """Hass.io Add-on ingress service.""" +from __future__ import annotations + import asyncio from ipaddress import ip_address import logging import os -from typing import Dict, Union import aiohttp from aiohttp import hdrs, web @@ -46,7 +47,7 @@ def _create_url(self, token: str, path: str) -> str: async def _handle( self, request: web.Request, token: str, path: str - ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: + ) -> web.Response | web.StreamResponse | web.WebSocketResponse: """Route data to Hass.io ingress service.""" try: # Websocket @@ -114,7 +115,7 @@ async def _handle_websocket( async def _handle_request( self, request: web.Request, token: str, path: str - ) -> Union[web.Response, web.StreamResponse]: + ) -> web.Response | web.StreamResponse: """Ingress route for request.""" url = self._create_url(token, path) data = await request.read() @@ -159,9 +160,7 @@ async def _handle_request( return response -def _init_header( - request: web.Request, token: str -) -> Union[CIMultiDict, Dict[str, str]]: +def _init_header(request: web.Request, token: str) -> CIMultiDict | dict[str, str]: """Create initial header.""" headers = {} @@ -208,7 +207,7 @@ def _init_header( return headers -def _response_header(response: aiohttp.ClientResponse) -> Dict[str, str]: +def _response_header(response: aiohttp.ClientResponse) -> dict[str, str]: """Create response header.""" headers = {} diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index 711a2a4630005f..ae2bd20d6edd45 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -1,5 +1,7 @@ """Sensor platform for Hass.io addons.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -13,7 +15,7 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Sensor set up for Hass.io config entry.""" coordinator = hass.data[ADDONS_COORDINATOR] diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index b3f3363818c3be..c8e1db0a10f7c7 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -1,6 +1,7 @@ """Support for the PRT Heatmiser themostats using the V3 protocol.""" +from __future__ import annotations + import logging -from typing import List from heatmiserV3 import connection, heatmiser import voluptuous as vol @@ -103,7 +104,7 @@ def hvac_mode(self) -> str: return self._hvac_mode @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index 11020d1166e1d0..a71d0d2de50a03 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -1,8 +1,9 @@ """Denon HEOS Media Player.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Dict from pyheos import Heos, HeosError, const as heos_const import voluptuous as vol @@ -191,7 +192,7 @@ async def _heos_event(self, event): # Update players self._hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_HEOS_UPDATED) - def update_ids(self, mapped_ids: Dict[int, int]): + def update_ids(self, mapped_ids: dict[int, int]): """Update the IDs in the device and entity registry.""" # mapped_ids contains the mapped IDs (new:old) for new_id, old_id in mapped_ids.items(): diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e9b3f4ff9a4d96..9a6b0724caab26 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -1,7 +1,9 @@ """Support for HERE travel time sensors.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Callable, Dict, Optional, Union +from typing import Callable import herepy import voluptuous as vol @@ -143,9 +145,9 @@ async def async_setup_platform( hass: HomeAssistant, - config: Dict[str, Union[str, bool]], + config: dict[str, str | bool], async_add_entities: Callable, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the HERE travel time platform.""" api_key = config[CONF_API_KEY] @@ -255,7 +257,7 @@ def delayed_sensor_update(event): ) @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" if self._here_data.traffic_mode: if self._here_data.traffic_time is not None: @@ -273,7 +275,7 @@ def name(self) -> str: @property def extra_state_attributes( self, - ) -> Optional[Dict[str, Union[None, float, str, bool]]]: + ) -> dict[str, None | float | str | bool] | None: """Return the state attributes.""" if self._here_data.base_time is None: return None @@ -324,7 +326,7 @@ async def async_update(self) -> None: await self.hass.async_add_executor_job(self._here_data.update) - async def _get_location_from_entity(self, entity_id: str) -> Optional[str]: + async def _get_location_from_entity(self, entity_id: str) -> str | None: """Get the location from the entity state or attributes.""" entity = self.hass.states.get(entity_id) @@ -480,7 +482,7 @@ def update(self) -> None: self.destination_name = waypoint[1]["mappedRoadName"] @staticmethod - def _build_hass_attribution(source_attribution: Dict) -> Optional[str]: + def _build_hass_attribution(source_attribution: dict) -> str | None: """Build a hass frontend ready string out of the sourceAttribution.""" suppliers = source_attribution.get("supplier") if suppliers is not None: diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index dd98ba4ef252a8..fbce4909e13871 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -1,11 +1,13 @@ """Provide pre-made queries on top of the recorder component.""" +from __future__ import annotations + from collections import defaultdict from datetime import datetime as dt, timedelta from itertools import groupby import json import logging import time -from typing import Iterable, Optional, cast +from typing import Iterable, cast from aiohttp import web from sqlalchemy import and_, bindparam, func, not_, or_ @@ -462,7 +464,7 @@ def __init__(self, filters, use_include_order): self.use_include_order = use_include_order async def get( - self, request: web.Request, datetime: Optional[str] = None + self, request: web.Request, datetime: str | None = None ) -> web.Response: """Return history over a period of time.""" datetime_ = None diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index cca2c601493cd6..3173d2d8c32abf 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -1,7 +1,9 @@ """Allow users to set and activate scenes.""" +from __future__ import annotations + from collections import namedtuple import logging -from typing import Any, List +from typing import Any import voluptuous as vol @@ -118,7 +120,7 @@ def _ensure_no_intersection(value): @callback -def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: +def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all scenes that reference the entity.""" if DATA_PLATFORM not in hass.data: return [] @@ -133,7 +135,7 @@ def scenes_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def entities_in_scene(hass: HomeAssistant, entity_id: str) -> List[str]: +def entities_in_scene(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all entities in a scene.""" if DATA_PLATFORM not in hass.data: return [] diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 8a03905d98d197..df8e3c419e18e3 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -1,7 +1,9 @@ """Offer state listening automation rules.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any import voluptuous as vol @@ -79,7 +81,7 @@ async def async_attach_trigger( template.attach(hass, time_delta) match_all = from_state == MATCH_ALL and to_state == MATCH_ALL unsub_track_same = {} - period: Dict[str, timedelta] = {} + period: dict[str, timedelta] = {} match_from_state = process_state_match(from_state) match_to_state = process_state_match(to_state) attribute = config.get(CONF_ATTRIBUTE) @@ -93,8 +95,8 @@ async def async_attach_trigger( def state_automation_listener(event: Event): """Listen for state changes and calls action.""" entity: str = event.data["entity_id"] - from_s: Optional[State] = event.data.get("old_state") - to_s: Optional[State] = event.data.get("new_state") + from_s: State | None = event.data.get("old_state") + to_s: State | None = event.data.get("new_state") if from_s is None: old_value = None diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 0a8f376fb3387b..d7b28036426d8e 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -1,5 +1,7 @@ """Support for Homekit device discovery.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any import aiohomekit from aiohomekit.model import Accessory @@ -77,7 +79,7 @@ async def async_will_remove_from_hass(self): signal_remove() self._signals.clear() - async def async_put_characteristics(self, characteristics: Dict[str, Any]): + async def async_put_characteristics(self, characteristics: dict[str, Any]): """ Write characteristics to the device. diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index b2e668915d7188..4de5ce66a09c20 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for homekit devices.""" -from typing import List +from __future__ import annotations from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics.const import InputEventValues @@ -226,7 +226,7 @@ def async_fire_triggers(conn, events): source.fire(iid, ev) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for homekit devices.""" if device_id not in hass.data.get(TRIGGERS, {}): diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index e4bed25d618fb6..227174d00e98d7 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -1,5 +1,5 @@ """Support for HomeKit Controller humidifier.""" -from typing import List, Optional +from __future__ import annotations from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes @@ -69,14 +69,14 @@ async def async_turn_off(self, **kwargs): await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return self.service.value( CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD ) @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. Requires SUPPORT_MODES. @@ -87,7 +87,7 @@ def mode(self) -> Optional[str]: return MODE_AUTO if mode == 1 else MODE_NORMAL @property - def available_modes(self) -> Optional[List[str]]: + def available_modes(self) -> list[str] | None: """Return a list of available modes. Requires SUPPORT_MODES. @@ -175,14 +175,14 @@ async def async_turn_off(self, **kwargs): await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return self.service.value( CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD ) @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. Requires SUPPORT_MODES. @@ -193,7 +193,7 @@ def mode(self) -> Optional[str]: return MODE_AUTO if mode == 1 else MODE_NORMAL @property - def available_modes(self) -> Optional[List[str]]: + def available_modes(self) -> list[str] | None: """Return a list of available modes. Requires SUPPORT_MODES. diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index 51cec6ac0cd87a..7fa5e197aa8247 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -1,6 +1,8 @@ """Support for HomematicIP Cloud alarm control panel.""" +from __future__ import annotations + import logging -from typing import Any, Dict +from typing import Any from homematicip.functionalHomes import SecurityAndAlarmHome @@ -44,7 +46,7 @@ def __init__(self, hap: HomematicipHAP) -> None: _LOGGER.info("Setting up %s", self.name) @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return { "identifiers": {(HMIPC_DOMAIN, f"ACP {self._home.id}")}, diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 0d21967b24215f..4fcf1f67dd4f16 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud binary sensor.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncAccelerationSensor, @@ -166,7 +168,7 @@ def name(self) -> str: return name if not self._home.name else f"{self._home.name} {name}" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" # Adds a sensor to the existing HAP device return { @@ -210,7 +212,7 @@ def is_on(self) -> bool: return self._device.accelerationSensorTriggered @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the acceleration sensor.""" state_attr = super().extra_state_attributes @@ -285,7 +287,7 @@ def device_class(self) -> str: return DEVICE_CLASS_DOOR @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the Shutter Contact.""" state_attr = super().extra_state_attributes @@ -412,7 +414,7 @@ def is_on(self) -> bool: return self._device.sunshine @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the illuminance sensor.""" state_attr = super().extra_state_attributes @@ -482,7 +484,7 @@ def available(self) -> bool: return True @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the security zone group.""" state_attr = super().extra_state_attributes @@ -526,7 +528,7 @@ def __init__(self, hap: HomematicipHAP, device) -> None: super().__init__(hap, device, post="Sensors") @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the security group.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index c09bd2cc53e056..5cdadf4d5f1e0c 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud climate devices.""" -from typing import Any, Dict, List, Optional, Union +from __future__ import annotations + +from typing import Any from homematicip.aio.device import AsyncHeatingThermostat, AsyncHeatingThermostatCompact from homematicip.aio.group import AsyncHeatingGroup @@ -71,7 +73,7 @@ def __init__(self, hap: HomematicipHAP, device: AsyncHeatingGroup) -> None: self._simple_heating = self._first_radiator_thermostat @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" return { "identifiers": {(HMIPC_DOMAIN, self._device.id)}, @@ -121,7 +123,7 @@ def hvac_mode(self) -> str: return HVAC_MODE_AUTO @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" if self._disabled_by_cooling_mode and not self._has_switch: return [HVAC_MODE_OFF] @@ -133,7 +135,7 @@ def hvac_modes(self) -> List[str]: ) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """ Return the current hvac_action. @@ -151,7 +153,7 @@ def hvac_action(self) -> Optional[str]: return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" if self._device.boostMode: return PRESET_BOOST @@ -174,7 +176,7 @@ def preset_mode(self) -> Optional[str]: ) @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return a list of available preset modes incl. hmip profiles.""" # Boost is only available if a radiator thermostat is in the room, # and heat mode is enabled. @@ -237,7 +239,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: await self._device.set_active_profile(profile_idx) @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the access point.""" state_attr = super().extra_state_attributes @@ -259,7 +261,7 @@ def _indoor_climate(self) -> IndoorClimateHome: return self._home.get_functionalHome(IndoorClimateHome) @property - def _device_profiles(self) -> List[str]: + def _device_profiles(self) -> list[str]: """Return the relevant profiles.""" return [ profile @@ -270,7 +272,7 @@ def _device_profiles(self) -> List[str]: ] @property - def _device_profile_names(self) -> List[str]: + def _device_profile_names(self) -> list[str]: """Return a collection of profile names.""" return [profile.name for profile in self._device_profiles] @@ -298,7 +300,7 @@ def _disabled_by_cooling_mode(self) -> bool: ) @property - def _relevant_profile_group(self) -> List[str]: + def _relevant_profile_group(self) -> list[str]: """Return the relevant profile groups.""" if self._disabled_by_cooling_mode: return [] @@ -322,7 +324,7 @@ def _has_radiator_thermostat(self) -> bool: @property def _first_radiator_thermostat( self, - ) -> Optional[Union[AsyncHeatingThermostat, AsyncHeatingThermostatCompact]]: + ) -> AsyncHeatingThermostat | AsyncHeatingThermostatCompact | None: """Return the first radiator thermostat from the hmip heating group.""" for device in self._device.devices: if isinstance( diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py index b6b78948894c8c..d90d8d7023b8a3 100644 --- a/homeassistant/components/homematicip_cloud/config_flow.py +++ b/homeassistant/components/homematicip_cloud/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure the HomematicIP Cloud component.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -27,11 +29,11 @@ def __init__(self) -> None: """Initialize HomematicIP Cloud config flow.""" self.auth = None - async def async_step_user(self, user_input=None) -> Dict[str, Any]: + async def async_step_user(self, user_input=None) -> dict[str, Any]: """Handle a flow initialized by the user.""" return await self.async_step_init(user_input) - async def async_step_init(self, user_input=None) -> Dict[str, Any]: + async def async_step_init(self, user_input=None) -> dict[str, Any]: """Handle a flow start.""" errors = {} @@ -62,7 +64,7 @@ async def async_step_init(self, user_input=None) -> Dict[str, Any]: errors=errors, ) - async def async_step_link(self, user_input=None) -> Dict[str, Any]: + async def async_step_link(self, user_input=None) -> dict[str, Any]: """Attempt to link with the HomematicIP Cloud access point.""" errors = {} @@ -84,7 +86,7 @@ async def async_step_link(self, user_input=None) -> Dict[str, Any]: return self.async_show_form(step_id="link", errors=errors) - async def async_step_import(self, import_info) -> Dict[str, Any]: + async def async_step_import(self, import_info) -> dict[str, Any]: """Import a new access point as a config entry.""" hapid = import_info[HMIPC_HAPID].replace("-", "").upper() authtoken = import_info[HMIPC_AUTHTOKEN] diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index 29a06c558fe893..aa1be11758eeb4 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -1,5 +1,5 @@ """Support for HomematicIP Cloud cover devices.""" -from typing import Optional +from __future__ import annotations from homematicip.aio.device import ( AsyncBlindModule, @@ -95,7 +95,7 @@ async def async_set_cover_tilt_position(self, **kwargs) -> None: ) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self._device.primaryShadingLevel is not None: return self._device.primaryShadingLevel == HMIP_COVER_CLOSED @@ -168,7 +168,7 @@ async def async_set_cover_position(self, **kwargs) -> None: await self._device.set_shutter_level(level, self._channel) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self._device.functionalChannels[self._channel].shutterLevel is not None: return ( @@ -265,7 +265,7 @@ def current_cover_position(self) -> int: return door_state_to_position.get(self._device.doorState) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" return self._device.doorState == DoorState.CLOSED @@ -305,7 +305,7 @@ def current_cover_tilt_position(self) -> int: return None @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed.""" if self._device.shutterLevel is not None: return self._device.shutterLevel == HMIP_COVER_CLOSED diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index 345446e78b2eef..856e47a1dee168 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -1,6 +1,8 @@ """Generic entity for the HomematicIP Cloud component.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from homematicip.aio.device import AsyncDevice from homematicip.aio.group import AsyncGroup @@ -74,9 +76,9 @@ def __init__( self, hap: HomematicipHAP, device, - post: Optional[str] = None, - channel: Optional[int] = None, - is_multi_channel: Optional[bool] = False, + post: str | None = None, + channel: int | None = None, + is_multi_channel: bool | None = False, ) -> None: """Initialize the generic entity.""" self._hap = hap @@ -90,7 +92,7 @@ def __init__( _LOGGER.info("Setting up %s (%s)", self.name, self._device.modelType) @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device specific attributes.""" # Only physical devices should be HA devices. if isinstance(self._device, AsyncDevice): @@ -223,7 +225,7 @@ def unique_id(self) -> str: return unique_id @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon.""" for attr, icon in DEVICE_ATTRIBUTE_ICONS.items(): if getattr(self._device, attr, None): @@ -232,7 +234,7 @@ def icon(self) -> Optional[str]: return None @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the generic entity.""" state_attr = {} diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index c453cf516dca6d..5732ea1bf9670a 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud lights.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncBrandDimmer, @@ -90,7 +92,7 @@ class HomematicipLightMeasuring(HomematicipLight): """Representation of the HomematicIP measuring light.""" @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the light.""" state_attr = super().extra_state_attributes @@ -206,7 +208,7 @@ def hs_color(self) -> tuple: return self._color_switcher.get(simple_rgb_color, [0.0, 0.0]) @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the notification light sensor.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 9f7c7517c3a7a2..6f4acc6137a3f0 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud sensors.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncBrandSwitchMeasuring, @@ -222,7 +224,7 @@ def unit_of_measurement(self) -> str: return TEMP_CELSIUS @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the windspeed sensor.""" state_attr = super().extra_state_attributes @@ -259,7 +261,7 @@ def unit_of_measurement(self) -> str: return LIGHT_LUX @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the wind speed sensor.""" state_attr = super().extra_state_attributes @@ -312,7 +314,7 @@ def unit_of_measurement(self) -> str: return SPEED_KILOMETERS_PER_HOUR @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the wind speed sensor.""" state_attr = super().extra_state_attributes @@ -354,7 +356,7 @@ def state(self) -> int: return self._device.leftRightCounterDelta @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the delta counter.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/homematicip_cloud/services.py b/homeassistant/components/homematicip_cloud/services.py index 7c92ac5e7211c1..aa82e72e284c97 100644 --- a/homeassistant/components/homematicip_cloud/services.py +++ b/homeassistant/components/homematicip_cloud/services.py @@ -1,7 +1,8 @@ """Support for HomematicIP Cloud devices.""" +from __future__ import annotations + import logging from pathlib import Path -from typing import Optional from homematicip.aio.device import AsyncSwitchMeasuring from homematicip.aio.group import AsyncHeatingGroup @@ -342,7 +343,7 @@ async def _async_reset_energy_counter( await device.reset_energy_counter() -def _get_home(hass: HomeAssistantType, hapid: str) -> Optional[AsyncHome]: +def _get_home(hass: HomeAssistantType, hapid: str) -> AsyncHome | None: """Return a HmIP home.""" hap = hass.data[HMIPC_DOMAIN].get(hapid) if hap: diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index e75615bf3361d6..8172d64d35734d 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -1,5 +1,7 @@ """Support for HomematicIP Cloud switches.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homematicip.aio.device import ( AsyncBrandSwitchMeasuring, @@ -141,7 +143,7 @@ def available(self) -> bool: return True @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the switch-group.""" state_attr = super().extra_state_attributes diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index a825916628dcd6..8053ad85502274 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -1,7 +1,9 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" +from __future__ import annotations + import datetime import logging -from typing import Any, Dict, List, Optional +from typing import Any import requests import somecomfort @@ -192,12 +194,12 @@ def __init__( self._supported_features |= SUPPORT_FAN_MODE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the honeywell, if any.""" return self._device.name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device specific state attributes.""" data = {} data[ATTR_FAN_ACTION] = "running" if self._device.fan_running else "idle" @@ -235,7 +237,7 @@ def temperature_unit(self) -> str: return TEMP_CELSIUS if self._device.temperature_unit == "C" else TEMP_FAHRENHEIT @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return self._device.current_humidity @@ -245,24 +247,24 @@ def hvac_mode(self) -> str: return HW_MODE_TO_HVAC_MODE[self._device.system_mode] @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(self._hvac_mode_map) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if self.hvac_mode == HVAC_MODE_OFF: return None return HW_MODE_TO_HA_HVAC_ACTION[self._device.equipment_output_status] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._device.current_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_COOL: return self._device.setpoint_cool @@ -271,41 +273,41 @@ def target_temperature(self) -> Optional[float]: return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_HEAT_COOL: return self._device.setpoint_cool return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_HEAT_COOL: return self._device.setpoint_heat return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return PRESET_AWAY if self._away else None @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return [PRESET_NONE, PRESET_AWAY] @property - def is_aux_heat(self) -> Optional[str]: + def is_aux_heat(self) -> str | None: """Return true if aux heater.""" return self._device.system_mode == "emheat" @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return HW_FAN_MODE_TO_HA[self._device.fan_mode] @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return list(self._fan_mode_map) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 993d466ae18ed7..5f57b4b77b8afc 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -1,10 +1,12 @@ """Support to serve the Home Assistant API as WSGI application.""" +from __future__ import annotations + from contextvars import ContextVar from ipaddress import ip_network import logging import os import ssl -from typing import Dict, Optional, cast +from typing import Optional, cast from aiohttp import web from aiohttp.web_exceptions import HTTPMovedPermanently @@ -102,7 +104,7 @@ @bind_hass -async def async_get_last_config(hass: HomeAssistant) -> Optional[dict]: +async def async_get_last_config(hass: HomeAssistant) -> dict | None: """Return the last known working config.""" store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) return cast(Optional[dict], await store.async_load()) @@ -115,7 +117,7 @@ def __init__( self, local_ip: str, host: str, - port: Optional[int] = SERVER_PORT, + port: int | None = SERVER_PORT, use_ssl: bool = False, ) -> None: """Initialize a new API config object.""" @@ -379,7 +381,7 @@ async def stop(self): async def start_http_server_and_save_config( - hass: HomeAssistant, conf: Dict, server: HomeAssistantHTTP + hass: HomeAssistant, conf: dict, server: HomeAssistantHTTP ) -> None: """Startup the http server and save the config.""" await server.start() # type: ignore @@ -395,6 +397,6 @@ async def start_http_server_and_save_config( await store.async_save(conf) -current_request: ContextVar[Optional[web.Request]] = ContextVar( +current_request: ContextVar[web.Request | None] = ContextVar( "current_request", default=None ) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 2e51dc35d8803a..009a4c7afb5d4a 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -1,10 +1,11 @@ """Ban logic for HTTP component.""" +from __future__ import annotations + from collections import defaultdict from datetime import datetime from ipaddress import ip_address import logging from socket import gethostbyaddr, herror -from typing import List, Optional from aiohttp.web import middleware from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized @@ -178,15 +179,15 @@ async def process_success_login(request): class IpBan: """Represents banned IP address.""" - def __init__(self, ip_ban: str, banned_at: Optional[datetime] = None) -> None: + def __init__(self, ip_ban: str, 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() -async def async_load_ip_bans_config(hass: HomeAssistant, path: str) -> List[IpBan]: +async def async_load_ip_bans_config(hass: HomeAssistant, path: str) -> list[IpBan]: """Load list of banned IPs from config file.""" - ip_list: List[IpBan] = [] + ip_list: list[IpBan] = [] try: list_ = await hass.async_add_executor_job(load_yaml_config_file, path) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 354159f13bef7b..b4dbb845638dce 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -1,8 +1,10 @@ """Support for views.""" +from __future__ import annotations + import asyncio import json import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from aiohttp import web from aiohttp.typedefs import LooseHeaders @@ -26,8 +28,8 @@ class HomeAssistantView: """Base view for all views.""" - url: Optional[str] = None - extra_urls: List[str] = [] + url: str | None = None + extra_urls: list[str] = [] # Views inheriting from this class can override this requires_auth = True cors_allowed = False @@ -45,7 +47,7 @@ def context(request: web.Request) -> Context: def json( result: Any, status_code: int = HTTP_OK, - headers: Optional[LooseHeaders] = None, + headers: LooseHeaders | None = None, ) -> web.Response: """Return a JSON response.""" try: @@ -66,8 +68,8 @@ def json_message( self, message: str, status_code: int = HTTP_OK, - message_code: Optional[str] = None, - headers: Optional[LooseHeaders] = None, + message_code: str | None = None, + headers: LooseHeaders | None = None, ) -> web.Response: """Return a JSON message response.""" data = {"message": message} diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index c30ba32b780544..74410026f948a2 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -1,7 +1,8 @@ """HomeAssistant specific aiohttp Site.""" +from __future__ import annotations + import asyncio from ssl import SSLContext -from typing import List, Optional, Union from aiohttp import web from yarl import URL @@ -25,14 +26,14 @@ class HomeAssistantTCPSite(web.BaseSite): def __init__( self, runner: "web.BaseRunner", - host: Union[None, str, List[str]], + host: None | str | list[str], port: int, *, shutdown_timeout: float = 10.0, - ssl_context: Optional[SSLContext] = None, + ssl_context: SSLContext | None = None, backlog: int = 128, - reuse_address: Optional[bool] = None, - reuse_port: Optional[bool] = None, + reuse_address: bool | None = None, + reuse_port: bool | None = None, ) -> None: # noqa: D107 super().__init__( runner, diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 2d33d1a1822f3c..d82e7e03e40a8c 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -1,4 +1,5 @@ """Support for Huawei LTE routers.""" +from __future__ import annotations from collections import defaultdict from datetime import timedelta @@ -6,7 +7,7 @@ import ipaddress import logging import time -from typing import Any, Callable, Dict, List, Set, Tuple, cast +from typing import Any, Callable, cast from urllib.parse import urlparse import attr @@ -138,13 +139,13 @@ class Router: mac: str = attr.ib() signal_update: CALLBACK_TYPE = attr.ib() - data: Dict[str, Any] = attr.ib(init=False, factory=dict) - subscriptions: Dict[str, Set[str]] = attr.ib( + data: dict[str, Any] = attr.ib(init=False, factory=dict) + subscriptions: dict[str, set[str]] = attr.ib( init=False, factory=lambda: defaultdict(set, ((x, {"initial_scan"}) for x in ALL_KEYS)), ) - inflight_gets: Set[str] = attr.ib(init=False, factory=set) - unload_handlers: List[CALLBACK_TYPE] = attr.ib(init=False, factory=list) + inflight_gets: set[str] = attr.ib(init=False, factory=set) + unload_handlers: list[CALLBACK_TYPE] = attr.ib(init=False, factory=list) client: Client suspended = attr.ib(init=False, default=False) notify_last_attempt: float = attr.ib(init=False, default=-1) @@ -167,7 +168,7 @@ def device_name(self) -> str: return DEFAULT_DEVICE_NAME @property - def device_identifiers(self) -> Set[Tuple[str, str]]: + def device_identifiers(self) -> set[tuple[str, str]]: """Get router identifiers for device registry.""" try: return {(DOMAIN, self.data[KEY_DEVICE_INFORMATION]["SerialNumber"])} @@ -175,7 +176,7 @@ def device_identifiers(self) -> Set[Tuple[str, str]]: return set() @property - def device_connections(self) -> Set[Tuple[str, str]]: + def device_connections(self) -> set[tuple[str, str]]: """Get router connections for device registry.""" return {(dr.CONNECTION_NETWORK_MAC, self.mac)} if self.mac else set() @@ -304,8 +305,8 @@ class HuaweiLteData: hass_config: dict = attr.ib() # Our YAML config, keyed by router URL - config: Dict[str, Dict[str, Any]] = attr.ib() - routers: Dict[str, Router] = attr.ib(init=False, factory=dict) + config: dict[str, dict[str, Any]] = attr.ib() + routers: dict[str, Router] = attr.ib(init=False, factory=dict) async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool: @@ -484,7 +485,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: logging.getLogger("dicttoxml").setLevel(logging.WARNING) # Arrange our YAML config to dict with normalized URLs as keys - domain_config: Dict[str, Dict[str, Any]] = {} + domain_config: dict[str, dict[str, Any]] = {} if DOMAIN not in hass.data: hass.data[DOMAIN] = HuaweiLteData(hass_config=config, config=domain_config) for router_config in config.get(DOMAIN, []): @@ -588,7 +589,7 @@ class HuaweiLteBaseEntity(Entity): router: Router = attr.ib() _available: bool = attr.ib(init=False, default=True) - _unsub_handlers: List[Callable] = attr.ib(init=False, factory=list) + _unsub_handlers: list[Callable] = attr.ib(init=False, factory=list) @property def _entity_name(self) -> str: @@ -620,7 +621,7 @@ def should_poll(self) -> bool: return False @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Get info for matching with parent router.""" return { "identifiers": self.router.device_identifiers, diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index 525dd3352e7266..833a632b0dba26 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -1,7 +1,8 @@ """Support for Huawei LTE binary sensors.""" +from __future__ import annotations import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable import attr from huawei_lte_api.enums.cradle import ConnectionStatusEnum @@ -29,11 +30,11 @@ async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - entities: List[Entity] = [] + entities: list[Entity] = [] if router.data.get(KEY_MONITORING_STATUS): entities.append(HuaweiLteMobileConnectionBinarySensor(router)) @@ -53,7 +54,7 @@ class HuaweiLteBaseBinarySensor(HuaweiLteBaseEntity, BinarySensorEntity): key: str item: str - _raw_state: Optional[str] = attr.ib(init=False, default=None) + _raw_state: str | None = attr.ib(init=False, default=None) @property def entity_registry_enabled_default(self) -> bool: @@ -142,7 +143,7 @@ def entity_registry_enabled_default(self) -> bool: return True @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Get additional attributes related to connection status.""" attributes = {} if self._raw_state in CONNECTION_STATE_ATTRIBUTES: diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index e38b873a5bb3c1..80f44e87319fa9 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -3,7 +3,7 @@ from collections import OrderedDict import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from huawei_lte_api.AuthorizedConnection import AuthorizedConnection @@ -55,9 +55,9 @@ def async_get_options_flow( async def _async_show_user_form( self, - user_input: Optional[Dict[str, Any]] = None, - errors: Optional[Dict[str, str]] = None, - ) -> Dict[str, Any]: + user_input: dict[str, Any] | None = None, + errors: dict[str, str] | None = None, + ) -> dict[str, Any]: if user_input is None: user_input = {} return self.async_show_form( @@ -94,12 +94,12 @@ async def _async_show_user_form( ) async def async_step_import( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle import initiated config flow.""" return await self.async_step_user(user_input) - def _already_configured(self, user_input: Dict[str, Any]) -> bool: + def _already_configured(self, user_input: dict[str, Any]) -> bool: """See if we already have a router matching user input configured.""" existing_urls = { url_normalize(entry.data[CONF_URL], default_scheme="http") @@ -108,8 +108,8 @@ def _already_configured(self, user_input: Dict[str, Any]) -> bool: return user_input[CONF_URL] in existing_urls async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle user initiated config flow.""" if user_input is None: return await self._async_show_user_form() @@ -129,7 +129,7 @@ async def async_step_user( if self._already_configured(user_input): return self.async_abort(reason="already_configured") - conn: Optional[Connection] = None + conn: Connection | None = None def logout() -> None: if isinstance(conn, AuthorizedConnection): @@ -138,7 +138,7 @@ def logout() -> None: except Exception: # pylint: disable=broad-except _LOGGER.debug("Could not logout", exc_info=True) - def try_connect(user_input: Dict[str, Any]) -> Connection: + def try_connect(user_input: dict[str, Any]) -> Connection: """Try connecting with given credentials.""" username = user_input.get(CONF_USERNAME) password = user_input.get(CONF_PASSWORD) @@ -222,8 +222,8 @@ def get_router_title(conn: Connection) -> str: return self.async_create_entry(title=title, data=user_input) async def async_step_ssdp( # type: ignore # mypy says signature incompatible with supertype, but it's the same? - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle SSDP initiated config flow.""" await self.async_set_unique_id(discovery_info[ssdp.ATTR_UPNP_UDN]) self._abort_if_unique_id_configured() @@ -263,8 +263,8 @@ def __init__(self, config_entry: config_entries.ConfigEntry): self.config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle options flow.""" # Recipients are persisted as a list, but handled as comma separated string in UI diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 3e889263b76fd0..b042c0c2912eff 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -1,8 +1,9 @@ """Support for device tracking of Huawei LTE routers.""" +from __future__ import annotations import logging import re -from typing import Any, Callable, Dict, List, Optional, Set, cast +from typing import Any, Callable, cast import attr from stringcase import snakecase @@ -31,7 +32,7 @@ async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" @@ -46,9 +47,9 @@ async def async_setup_entry( return # Initialize already tracked entities - tracked: Set[str] = set() + tracked: set[str] = set() registry = await entity_registry.async_get_registry(hass) - known_entities: List[Entity] = [] + known_entities: list[Entity] = [] for entity in registry.entities.values(): if ( entity.domain == DEVICE_TRACKER_DOMAIN @@ -82,8 +83,8 @@ async def _async_maybe_add_new_entities(url: str) -> None: def async_add_new_entities( hass: HomeAssistantType, router_url: str, - async_add_entities: Callable[[List[Entity], bool], None], - tracked: Set[str], + async_add_entities: Callable[[list[Entity], bool], None], + tracked: set[str], ) -> None: """Add new entities that are not already being tracked.""" router = hass.data[DOMAIN].routers[router_url] @@ -93,7 +94,7 @@ def async_add_new_entities( _LOGGER.debug("%s[%s][%s] not in data", KEY_WLAN_HOST_LIST, "Hosts", "Host") return - new_entities: List[Entity] = [] + new_entities: list[Entity] = [] for host in (x for x in hosts if x.get("MacAddress")): entity = HuaweiLteScannerEntity(router, host["MacAddress"]) if entity.unique_id in tracked: @@ -125,8 +126,8 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): mac: str = attr.ib() _is_connected: bool = attr.ib(init=False, default=False) - _hostname: Optional[str] = attr.ib(init=False, default=None) - _extra_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) + _hostname: str | None = attr.ib(init=False, default=None) + _extra_state_attributes: dict[str, Any] = attr.ib(init=False, factory=dict) def __attrs_post_init__(self) -> None: """Initialize internal state.""" @@ -151,7 +152,7 @@ def is_connected(self) -> bool: return self._is_connected @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Get additional attributes related to entity state.""" return self._extra_state_attributes diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index ef354fefaf37de..ea7b5d9f6ab8b2 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -3,7 +3,7 @@ import logging import time -from typing import Any, Dict, List, Optional +from typing import Any import attr from huawei_lte_api.exceptions import ResponseErrorException @@ -20,9 +20,9 @@ async def async_get_service( hass: HomeAssistantType, - config: Dict[str, Any], - discovery_info: Optional[Dict[str, Any]] = None, -) -> Optional[HuaweiLteSmsNotificationService]: + config: dict[str, Any], + discovery_info: dict[str, Any] | None = None, +) -> HuaweiLteSmsNotificationService | None: """Get the notification service.""" if discovery_info is None: return None @@ -38,7 +38,7 @@ class HuaweiLteSmsNotificationService(BaseNotificationService): """Huawei LTE router SMS notification service.""" router: Router = attr.ib() - default_targets: List[str] = attr.ib() + default_targets: list[str] = attr.ib() def send_message(self, message: str = "", **kwargs: Any) -> None: """Send message to target numbers.""" diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index 3815ac831b5546..c0773fdf80849f 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -1,9 +1,10 @@ """Support for Huawei LTE sensors.""" +from __future__ import annotations from bisect import bisect import logging import re -from typing import Callable, Dict, List, NamedTuple, Optional, Pattern, Tuple, Union +from typing import Callable, NamedTuple, Pattern import attr @@ -45,17 +46,17 @@ class SensorMeta(NamedTuple): """Metadata for defining sensors.""" - name: Optional[str] = None - device_class: Optional[str] = None - icon: Union[str, Callable[[StateType], str], None] = None - unit: Optional[str] = None + name: str | None = None + device_class: str | None = None + icon: str | Callable[[StateType], str] | None = None + unit: str | None = None enabled_default: bool = False - include: Optional[Pattern[str]] = None - exclude: Optional[Pattern[str]] = None - formatter: Optional[Callable[[str], Tuple[StateType, Optional[str]]]] = None + include: Pattern[str] | None = None + exclude: Pattern[str] | None = None + formatter: Callable[[str], tuple[StateType, str | None]] | None = None -SENSOR_META: Dict[Union[str, Tuple[str, str]], SensorMeta] = { +SENSOR_META: dict[str | tuple[str, str], SensorMeta] = { KEY_DEVICE_INFORMATION: SensorMeta( include=re.compile(r"^WanIP.*Address$", re.IGNORECASE) ), @@ -329,11 +330,11 @@ class SensorMeta(NamedTuple): async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - sensors: List[Entity] = [] + sensors: list[Entity] = [] for key in SENSOR_KEYS: items = router.data.get(key) if not items: @@ -354,7 +355,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -def format_default(value: StateType) -> Tuple[StateType, Optional[str]]: +def format_default(value: StateType) -> tuple[StateType, str | None]: """Format value.""" unit = None if value is not None: @@ -380,7 +381,7 @@ class HuaweiLteSensor(HuaweiLteBaseEntity): meta: SensorMeta = attr.ib() _state: StateType = attr.ib(init=False, default=STATE_UNKNOWN) - _unit: Optional[str] = attr.ib(init=False) + _unit: str | None = attr.ib(init=False) async def async_added_to_hass(self) -> None: """Subscribe to needed data on add.""" @@ -406,17 +407,17 @@ def state(self) -> StateType: return self._state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return sensor device class.""" return self.meta.device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return sensor's unit of measurement.""" return self.meta.unit or self._unit @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return icon for sensor.""" icon = self.meta.icon if callable(icon): diff --git a/homeassistant/components/huawei_lte/switch.py b/homeassistant/components/huawei_lte/switch.py index 4dfa1e32df25d3..9279226e8eccb5 100644 --- a/homeassistant/components/huawei_lte/switch.py +++ b/homeassistant/components/huawei_lte/switch.py @@ -1,7 +1,8 @@ """Support for Huawei LTE switches.""" +from __future__ import annotations import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable import attr @@ -24,11 +25,11 @@ async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - switches: List[Entity] = [] + switches: list[Entity] = [] if router.data.get(KEY_DIALUP_MOBILE_DATASWITCH): switches.append(HuaweiLteMobileDataSwitch(router)) @@ -42,7 +43,7 @@ class HuaweiLteBaseSwitch(HuaweiLteBaseEntity, SwitchEntity): key: str item: str - _raw_state: Optional[str] = attr.ib(init=False, default=None) + _raw_state: str | None = attr.ib(init=False, default=None) def _turn(self, state: bool) -> None: raise NotImplementedError diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 95e4a1ad7f21a8..3c9fabf3c9ee13 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure Philips Hue.""" +from __future__ import annotations + import asyncio -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse import aiohue @@ -44,8 +46,8 @@ def async_get_options_flow(config_entry): def __init__(self): """Initialize the Hue flow.""" - self.bridge: Optional[aiohue.Bridge] = None - self.discovered_bridges: Optional[Dict[str, aiohue.Bridge]] = None + self.bridge: aiohue.Bridge | None = None + self.discovered_bridges: dict[str, aiohue.Bridge] | None = None async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" @@ -53,7 +55,7 @@ async def async_step_user(self, user_input=None): return await self.async_step_init(user_input) @core.callback - def _async_get_bridge(self, host: str, bridge_id: Optional[str] = None): + def _async_get_bridge(self, host: str, bridge_id: str | None = None): """Return a bridge object.""" if bridge_id is not None: bridge_id = normalize_bridge_id(bridge_id) @@ -114,8 +116,8 @@ async def async_step_init(self, user_input=None): ) async def async_step_manual( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle manual bridge setup.""" if user_input is None: return self.async_show_form( @@ -249,8 +251,8 @@ def __init__(self, config_entry): self.config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Manage Hue options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 1763e169d50bc6..01a9fc2ada0dda 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -1,7 +1,9 @@ """Provides functionality to interact with humidifier devices.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Dict, List, Optional +from typing import Any import voluptuous as vol @@ -100,7 +102,7 @@ class HumidifierEntity(ToggleEntity): """Representation of a humidifier device.""" @property - def capability_attributes(self) -> Dict[str, Any]: + def capability_attributes(self) -> dict[str, Any]: """Return capability attributes.""" supported_features = self.supported_features or 0 data = { @@ -114,7 +116,7 @@ def capability_attributes(self) -> Dict[str, Any]: return data @property - def state_attributes(self) -> Dict[str, Any]: + def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" supported_features = self.supported_features or 0 data = {} @@ -128,12 +130,12 @@ def state_attributes(self) -> Dict[str, Any]: return data @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return None @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the current mode, e.g., home, auto, baby. Requires SUPPORT_MODES. @@ -141,7 +143,7 @@ def mode(self) -> Optional[str]: raise NotImplementedError @property - def available_modes(self) -> Optional[List[str]]: + def available_modes(self) -> list[str] | None: """Return a list of available modes. Requires SUPPORT_MODES. diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index c702a7c2a2de31..a68b4d771ef295 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for Humidifier.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -40,7 +40,7 @@ ACTION_SCHEMA = vol.Any(SET_HUMIDITY_SCHEMA, SET_MODE_SCHEMA, ONOFF_SCHEMA) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) @@ -79,7 +79,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 86a049d838b730..137fd6af73da6b 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Humidifier.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -38,7 +38,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) conditions = await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/humidifier/device_trigger.py b/homeassistant/components/humidifier/device_trigger.py index 6bc9682f79a41b..d0f462f6b0ff52 100644 --- a/homeassistant/components/humidifier/device_trigger.py +++ b/homeassistant/components/humidifier/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Climate.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -48,7 +48,7 @@ TRIGGER_SCHEMA = vol.Any(TARGET_TRIGGER_SCHEMA, TOGGLE_TRIGGER_SCHEMA) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) triggers = await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/humidifier/reproduce_state.py b/homeassistant/components/humidifier/reproduce_state.py index f6fff75203e894..b20edacec460d3 100644 --- a/homeassistant/components/humidifier/reproduce_state.py +++ b/homeassistant/components/humidifier/reproduce_state.py @@ -1,7 +1,9 @@ """Module that groups code required to handle state restore for component.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_MODE, @@ -22,8 +24,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" cur_state = hass.states.get(state.entity_id) @@ -82,8 +84,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index e5e6cb4494a9cb..ad6990c6ab4476 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -1,8 +1,9 @@ """The Hyperion component.""" +from __future__ import annotations import asyncio import logging -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, cast +from typing import Any, Callable, cast from awesomeversion import AwesomeVersion from hyperion import client, const as hyperion_const @@ -70,7 +71,7 @@ def get_hyperion_unique_id(server_id: str, instance: int, name: str) -> str: return f"{server_id}_{instance}_{name}" -def split_hyperion_unique_id(unique_id: str) -> Optional[Tuple[str, int, str]]: +def split_hyperion_unique_id(unique_id: str) -> tuple[str, int, str] | None: """Split a unique_id into a (server_id, instance, type) tuple.""" data = tuple(unique_id.split("_", 2)) if len(data) != 3: @@ -92,7 +93,7 @@ def create_hyperion_client( async def async_create_connect_hyperion_client( *args: Any, **kwargs: Any, -) -> Optional[client.HyperionClient]: +) -> client.HyperionClient | None: """Create and connect a Hyperion Client.""" hyperion_client = create_hyperion_client(*args, **kwargs) @@ -207,17 +208,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b CONF_ON_UNLOAD: [], } - async def async_instances_to_clients(response: Dict[str, Any]) -> None: + async def async_instances_to_clients(response: dict[str, Any]) -> None: """Convert instances to Hyperion clients.""" if not response or hyperion_const.KEY_DATA not in response: return await async_instances_to_clients_raw(response[hyperion_const.KEY_DATA]) - async def async_instances_to_clients_raw(instances: List[Dict[str, Any]]) -> None: + async def async_instances_to_clients_raw(instances: list[dict[str, Any]]) -> None: """Convert instances to Hyperion clients.""" registry = await async_get_registry(hass) - running_instances: Set[int] = set() - stopped_instances: Set[int] = set() + running_instances: set[int] = set() + stopped_instances: set[int] = set() existing_instances = hass.data[DOMAIN][config_entry.entry_id][ CONF_INSTANCE_CLIENTS ] diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 8d02028dc38b37..bea8971cfa691c 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -3,7 +3,7 @@ import asyncio import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from hyperion import client, const @@ -111,9 +111,9 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Instantiate config flow.""" - self._data: Dict[str, Any] = {} - self._request_token_task: Optional[asyncio.Task] = None - self._auth_id: Optional[str] = None + self._data: dict[str, Any] = {} + self._request_token_task: asyncio.Task | None = None + self._auth_id: str | None = None self._require_confirm: bool = False self._port_ui: int = const.DEFAULT_PORT_UI @@ -128,7 +128,7 @@ def _create_client(self, raw_connection: bool = False) -> client.HyperionClient: async def _advance_to_auth_step_if_necessary( self, hyperion_client: client.HyperionClient - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Determine if auth is required.""" auth_resp = await hyperion_client.async_is_auth_required() @@ -143,7 +143,7 @@ async def _advance_to_auth_step_if_necessary( async def async_step_reauth( self, config_data: ConfigType, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a reauthentication flow.""" self._data = dict(config_data) async with self._create_client(raw_connection=True) as hyperion_client: @@ -152,8 +152,8 @@ async def async_step_reauth( return await self._advance_to_auth_step_if_necessary(hyperion_client) async def async_step_ssdp( # type: ignore[override] - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Handle a flow initiated by SSDP.""" # Sample data provided by SSDP: { # 'ssdp_location': 'http://192.168.0.1:8090/description.xml', @@ -223,8 +223,8 @@ async def async_step_ssdp( # type: ignore[override] async def async_step_user( self, - user_input: Optional[ConfigType] = None, - ) -> Dict[str, Any]: + user_input: ConfigType | None = None, + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" errors = {} if user_input: @@ -262,7 +262,7 @@ async def _cancel_request_token_task(self) -> None: async def _request_token_task_func(self, auth_id: str) -> None: """Send an async_request_token request.""" - auth_resp: Optional[Dict[str, Any]] = None + auth_resp: dict[str, Any] | None = None async with self._create_client(raw_connection=True) as hyperion_client: if hyperion_client: # The Hyperion-py client has a default timeout of 3 minutes on this request. @@ -283,7 +283,7 @@ def _get_hyperion_url(self) -> str: # used to open a URL, that the user already knows the address of). return f"http://{self._data[CONF_HOST]}:{self._port_ui}" - async def _can_login(self) -> Optional[bool]: + async def _can_login(self) -> bool | None: """Verify login details.""" async with self._create_client(raw_connection=True) as hyperion_client: if not hyperion_client: @@ -296,8 +296,8 @@ async def _can_login(self) -> Optional[bool]: async def async_step_auth( self, - user_input: Optional[ConfigType] = None, - ) -> Dict[str, Any]: + user_input: ConfigType | None = None, + ) -> dict[str, Any]: """Handle the auth step of a flow.""" errors = {} if user_input: @@ -325,8 +325,8 @@ async def async_step_auth( ) async def async_step_create_token( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Send a request for a new token.""" if user_input is None: self._auth_id = client.generate_random_auth_id() @@ -351,8 +351,8 @@ async def async_step_create_token( ) async def async_step_create_token_external( - self, auth_resp: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, auth_resp: ConfigType | None = None + ) -> dict[str, Any]: """Handle completion of the request for a new token.""" if auth_resp is not None and client.ResponseOK(auth_resp): token = auth_resp.get(const.KEY_INFO, {}).get(const.KEY_TOKEN) @@ -364,8 +364,8 @@ async def async_step_create_token_external( return self.async_external_step_done(next_step_id="create_token_fail") async def async_step_create_token_success( - self, _: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, _: ConfigType | None = None + ) -> dict[str, Any]: """Create an entry after successful token creation.""" # Clean-up the request task. await self._cancel_request_token_task() @@ -380,16 +380,16 @@ async def async_step_create_token_success( return await self.async_step_confirm() async def async_step_create_token_fail( - self, _: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, _: ConfigType | None = None + ) -> dict[str, Any]: """Show an error on the auth form.""" # Clean-up the request task. await self._cancel_request_token_task() return self.async_abort(reason="auth_new_token_not_granted_error") async def async_step_confirm( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Get final confirmation before entry creation.""" if user_input is None and self._require_confirm: return self.async_show_form( @@ -440,8 +440,8 @@ def __init__(self, config_entry: ConfigEntry): self._config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 89f70c8f24dd69..d322362e95985e 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -4,7 +4,7 @@ import functools import logging from types import MappingProxyType -from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple +from typing import Any, Callable, Mapping, Sequence from hyperion import client, const @@ -63,7 +63,7 @@ DEFAULT_NAME = "Hyperion" DEFAULT_PORT = const.DEFAULT_PORT_JSON DEFAULT_HDMI_PRIORITY = 880 -DEFAULT_EFFECT_LIST: List[str] = [] +DEFAULT_EFFECT_LIST: list[str] = [] SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT @@ -142,12 +142,12 @@ def __init__( self._rgb_color: Sequence[int] = DEFAULT_COLOR self._effect: str = KEY_EFFECT_SOLID - self._static_effect_list: List[str] = [KEY_EFFECT_SOLID] + self._static_effect_list: list[str] = [KEY_EFFECT_SOLID] if self._support_external_effects: self._static_effect_list += list(const.KEY_COMPONENTID_EXTERNAL_SOURCES) - self._effect_list: List[str] = self._static_effect_list[:] + self._effect_list: list[str] = self._static_effect_list[:] - self._client_callbacks: Mapping[str, Callable[[Dict[str, Any]], None]] = { + self._client_callbacks: Mapping[str, Callable[[dict[str, Any]], None]] = { f"{const.KEY_ADJUSTMENT}-{const.KEY_UPDATE}": self._update_adjustment, f"{const.KEY_COMPONENTS}-{const.KEY_UPDATE}": self._update_components, f"{const.KEY_EFFECTS}-{const.KEY_UPDATE}": self._update_effect_list, @@ -176,7 +176,7 @@ def brightness(self) -> int: return self._brightness @property - def hs_color(self) -> Tuple[float, float]: + def hs_color(self) -> tuple[float, float]: """Return last color value set.""" return color_util.color_RGB_to_hs(*self._rgb_color) @@ -196,7 +196,7 @@ def effect(self) -> str: return self._effect @property - def effect_list(self) -> List[str]: + def effect_list(self) -> list[str]: """Return the list of supported effects.""" return self._effect_list @@ -305,9 +305,9 @@ async def async_turn_on(self, **kwargs: Any) -> None: def _set_internal_state( self, - brightness: Optional[int] = None, - rgb_color: Optional[Sequence[int]] = None, - effect: Optional[str] = None, + brightness: int | None = None, + rgb_color: Sequence[int] | None = None, + effect: str | None = None, ) -> None: """Set the internal state.""" if brightness is not None: @@ -318,12 +318,12 @@ def _set_internal_state( self._effect = effect @callback - def _update_components(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_components(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion components.""" self.async_write_ha_state() @callback - def _update_adjustment(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_adjustment(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion adjustments.""" if self._client.adjustment: brightness_pct = self._client.adjustment[0].get( @@ -337,7 +337,7 @@ def _update_adjustment(self, _: Optional[Dict[str, Any]] = None) -> None: self.async_write_ha_state() @callback - def _update_priorities(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_priorities(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion priorities.""" priority = self._get_priority_entry_that_dictates_state() if priority and self._allow_priority_update(priority): @@ -361,11 +361,11 @@ def _update_priorities(self, _: Optional[Dict[str, Any]] = None) -> None: self.async_write_ha_state() @callback - def _update_effect_list(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_effect_list(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion effects.""" if not self._client.effects: return - effect_list: List[str] = [] + effect_list: list[str] = [] for effect in self._client.effects or []: if const.KEY_NAME in effect: effect_list.append(effect[const.KEY_NAME]) @@ -391,7 +391,7 @@ def _update_full_state(self) -> None: ) @callback - def _update_client(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_client(self, _: dict[str, Any] | None = None) -> None: """Update client connection state.""" self.async_write_ha_state() @@ -419,18 +419,18 @@ def _support_external_effects(self) -> bool: """Whether or not to support setting external effects from the light entity.""" return True - def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: + def _get_priority_entry_that_dictates_state(self) -> dict[str, Any] | None: """Get the relevant Hyperion priority entry to consider.""" # Return the visible priority (whether or not it is the HA priority). # Explicit type specifier to ensure this works when the underlying (typed) # library is installed along with the tests. Casts would trigger a # redundant-cast warning in this case. - priority: Optional[Dict[str, Any]] = self._client.visible_priority + priority: dict[str, Any] | None = self._client.visible_priority return priority # pylint: disable=no-self-use - def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: + def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool: """Determine whether to allow a priority to update internal state.""" return True @@ -525,7 +525,7 @@ def _support_external_effects(self) -> bool: """Whether or not to support setting external effects from the light entity.""" return False - def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: + def _get_priority_entry_that_dictates_state(self) -> dict[str, Any] | None: """Get the relevant Hyperion priority entry to consider.""" # Return the active priority (if any) at the configured HA priority. for candidate in self._client.priorities or []: @@ -537,12 +537,12 @@ def _get_priority_entry_that_dictates_state(self) -> Optional[Dict[str, Any]]: # Explicit type specifier to ensure this works when the underlying # (typed) library is installed along with the tests. Casts would trigger # a redundant-cast warning in this case. - output: Dict[str, Any] = candidate + output: dict[str, Any] = candidate return output return None @classmethod - def _is_priority_entry_black(cls, priority: Optional[Dict[str, Any]]) -> bool: + def _is_priority_entry_black(cls, priority: dict[str, Any] | None) -> bool: """Determine if a given priority entry is the color black.""" if not priority: return False @@ -552,7 +552,7 @@ def _is_priority_entry_black(cls, priority: Optional[Dict[str, Any]]) -> bool: return True return False - def _allow_priority_update(self, priority: Optional[Dict[str, Any]] = None) -> bool: + def _allow_priority_update(self, priority: dict[str, Any] | None = None) -> bool: """Determine whether to allow a Hyperion priority to update entity attributes.""" # Black is treated as 'off' (and Home Assistant does not support selecting black # from the color selector). Do not set our internal attributes if the priority is diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index f6317a8f396c0a..4a4f8d4da135dd 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -1,7 +1,8 @@ """Switch platform for Hyperion.""" +from __future__ import annotations import functools -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from hyperion import client from hyperion.const import ( @@ -187,7 +188,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self._async_send_set_component(False) @callback - def _update_components(self, _: Optional[Dict[str, Any]] = None) -> None: + def _update_components(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion components.""" self.async_write_ha_state() From 333f5da03674f679f7da548da1cc412ee399be77 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 09:51:42 +0100 Subject: [PATCH 1340/1818] Use websocket fixture in deCONZ binary sensor tests (#47820) Localize test data Improve asserts --- tests/components/deconz/test_binary_sensor.py | 196 ++++++++++-------- 1 file changed, 108 insertions(+), 88 deletions(-) diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 39809ecc231dbf..9d4c86ead6c9f8 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -1,5 +1,6 @@ """deCONZ binary sensor platform tests.""" -from copy import deepcopy + +from unittest.mock import patch from homeassistant.components.binary_sensor import ( DEVICE_CLASS_MOTION, @@ -11,7 +12,6 @@ CONF_MASTER_GATEWAY, DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import SERVICE_DEVICE_REFRESH from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.helpers import entity_registry as er @@ -23,46 +23,6 @@ setup_deconz_integration, ) -SENSORS = { - "1": { - "id": "Presence sensor id", - "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": { - "id": "Temperature sensor id", - "name": "Temperature sensor", - "type": "ZHATemperature", - "state": {"temperature": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "CLIP presence sensor id", - "name": "CLIP presence sensor", - "type": "CLIPPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "id": "Vibration sensor id", - "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", - }, -} - async def test_no_binary_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" @@ -70,14 +30,47 @@ async def test_no_binary_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_binary_sensors(hass, aioclient_mock): +async def test_binary_sensors(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of binary sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + 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) assert len(hass.states.async_all()) == 3 presence_sensor = hass.states.get("binary_sensor.presence_sensor") @@ -89,14 +82,14 @@ async def test_binary_sensors(hass, aioclient_mock): assert vibration_sensor.state == STATE_ON assert vibration_sensor.attributes["device_class"] == DEVICE_CLASS_VIBRATION - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"presence": True}, } - gateway.api.event_handler(state_changed_event) + 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 @@ -107,25 +100,39 @@ async def test_binary_sensors(hass, aioclient_mock): 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_allow_clip_sensor(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_CLIP_SENSOR: True}, - get_state_response=data, - ) + data = { + "sensors": { + "1": { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "CLIP presence sensor", + "type": "CLIPPresence", + "state": {"presence": False}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} + ) - assert len(hass.states.async_all()) == 4 + assert len(hass.states.async_all()) == 2 assert hass.states.get("binary_sensor.presence_sensor").state == STATE_OFF - assert hass.states.get("binary_sensor.temperature_sensor") is None assert hass.states.get("binary_sensor.clip_presence_sensor").state == STATE_OFF - assert hass.states.get("binary_sensor.vibration_sensor").state == STATE_ON # Disallow clip sensors @@ -134,8 +141,8 @@ async def test_allow_clip_sensor(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 3 - assert hass.states.get("binary_sensor.clip_presence_sensor") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("binary_sensor.clip_presence_sensor") # Allow clip sensors @@ -144,48 +151,65 @@ async def test_allow_clip_sensor(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 4 + assert len(hass.states.async_all()) == 2 assert hass.states.get("binary_sensor.clip_presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor(hass, aioclient_mock): +async def test_add_new_binary_sensor(hass, aioclient_mock, mock_deconz_websocket): """Test that adding a new binary sensor works.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) - assert len(hass.states.async_all()) == 0 - - state_added_event = { + event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", - "sensor": deepcopy(SENSORS["1"]), + "sensor": { + "id": "Presence sensor id", + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, } - gateway.api.event_handler(state_added_event) + + await setup_deconz_integration(hass, aioclient_mock) + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 assert hass.states.get("binary_sensor.presence_sensor").state == STATE_OFF -async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): +async def test_add_new_binary_sensor_ignored( + hass, aioclient_mock, mock_deconz_websocket +): """Test that adding a new binary sensor is not allowed.""" + sensor = { + "name": "Presence sensor", + "type": "ZHAPresence", + "state": {"presence": False}, + "config": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + } + event_added_sensor = { + "t": "event", + "e": "added", + "r": "sensors", + "id": "1", + "sensor": sensor, + } + config_entry = await setup_deconz_integration( hass, aioclient_mock, options={CONF_MASTER_GATEWAY: True, CONF_ALLOW_NEW_DEVICES: False}, ) - gateway = get_gateway_from_config_entry(hass, config_entry) + assert len(hass.states.async_all()) == 0 - state_added_event = { - "t": "event", - "e": "added", - "r": "sensors", - "id": "1", - "sensor": deepcopy(SENSORS["1"]), - } - gateway.api.event_handler(state_added_event) + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 @@ -197,11 +221,7 @@ async def test_add_new_binary_sensor_ignored(hass, aioclient_mock): ) aioclient_mock.clear_requests() - data = { - "groups": {}, - "lights": {}, - "sensors": {"1": deepcopy(SENSORS["1"])}, - } + data = {"groups": {}, "lights": {}, "sensors": {"1": sensor}} mock_deconz_request(aioclient_mock, config_entry.data, data) await hass.services.async_call(DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH) From fea0e39fa0d8c5650eadb2de765e47e163650838 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 17 Mar 2021 22:55:38 -1000 Subject: [PATCH 1341/1818] Reduce rest setup code (#48062) - Switch to storing each platform config/rest data in a list --- homeassistant/components/rest/__init__.py | 24 ++++++++--------------- homeassistant/components/rest/const.py | 2 ++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index 26e8fde57e0e36..8b9390bb1c922d 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -33,7 +33,7 @@ from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_IDX +from .const import COORDINATOR, DOMAIN, PLATFORM_IDX, REST, REST_DATA, REST_IDX from .data import RestData from .schema import CONFIG_SCHEMA # noqa: F401 @@ -67,7 +67,7 @@ async def reload_service_handler(service): @callback def _async_setup_shared_data(hass: HomeAssistant): """Create shared data for platform config and rest coordinators.""" - hass.data[DOMAIN] = {platform: {} for platform in COORDINATOR_AWARE_PLATFORMS} + hass.data[DOMAIN] = {key: [] for key in [REST_DATA, *COORDINATOR_AWARE_PLATFORMS]} async def _async_process_config(hass, config) -> bool: @@ -77,29 +77,21 @@ async def _async_process_config(hass, config) -> bool: refresh_tasks = [] load_tasks = [] - platform_idxs = {} for rest_idx, conf in enumerate(config[DOMAIN]): scan_interval = conf.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) resource_template = conf.get(CONF_RESOURCE_TEMPLATE) rest = create_rest_data_from_config(hass, conf) - coordinator = _wrap_rest_in_coordinator( - hass, rest, resource_template, scan_interval - ) + coordinator = _rest_coordinator(hass, rest, resource_template, scan_interval) refresh_tasks.append(coordinator.async_refresh()) - hass.data[DOMAIN][rest_idx] = {REST: rest, COORDINATOR: coordinator} + hass.data[DOMAIN][REST_DATA].append({REST: rest, COORDINATOR: coordinator}) for platform_domain in COORDINATOR_AWARE_PLATFORMS: if platform_domain not in conf: continue for platform_conf in conf[platform_domain]: - if platform_domain not in platform_idxs: - platform_idxs[platform_domain] = 0 - else: - platform_idxs[platform_domain] += 1 - platform_idx = platform_idxs[platform_domain] - - hass.data[DOMAIN][platform_domain][platform_idx] = platform_conf + hass.data[DOMAIN][platform_domain].append(platform_conf) + platform_idx = len(hass.data[DOMAIN][platform_domain]) - 1 load = discovery.async_load_platform( hass, @@ -121,7 +113,7 @@ async def _async_process_config(hass, config) -> bool: async def async_get_config_and_coordinator(hass, platform_domain, discovery_info): """Get the config and coordinator for the platform from discovery.""" - shared_data = hass.data[DOMAIN][discovery_info[REST_IDX]] + shared_data = hass.data[DOMAIN][REST_DATA][discovery_info[REST_IDX]] conf = hass.data[DOMAIN][platform_domain][discovery_info[PLATFORM_IDX]] coordinator = shared_data[COORDINATOR] rest = shared_data[REST] @@ -130,7 +122,7 @@ async def async_get_config_and_coordinator(hass, platform_domain, discovery_info return conf, coordinator, rest -def _wrap_rest_in_coordinator(hass, rest, resource_template, update_interval): +def _rest_coordinator(hass, rest, resource_template, update_interval): """Wrap a DataUpdateCoordinator around the rest object.""" if resource_template: diff --git a/homeassistant/components/rest/const.py b/homeassistant/components/rest/const.py index 31216b65968b71..5fd32d8fba774f 100644 --- a/homeassistant/components/rest/const.py +++ b/homeassistant/components/rest/const.py @@ -17,4 +17,6 @@ COORDINATOR = "coordinator" REST = "rest" +REST_DATA = "rest_data" + METHODS = ["POST", "GET"] From 2ab640aaefdfccc1ecafcc28f18aa71236bc93c8 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 09:57:27 +0100 Subject: [PATCH 1342/1818] Use websocket fixture in deCONZ climate tests (#47821) Localize test data Improve asserts --- tests/components/deconz/test_climate.py | 486 +++++++++++++----------- 1 file changed, 261 insertions(+), 225 deletions(-) diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index 5577a2d0414542..92ca38fdf4d5ef 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -1,6 +1,6 @@ """deCONZ climate platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest @@ -27,14 +27,18 @@ HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, + PRESET_BOOST, PRESET_COMFORT, + PRESET_ECO, ) from homeassistant.components.deconz.climate import ( DECONZ_FAN_SMART, + DECONZ_PRESET_AUTO, + DECONZ_PRESET_COMPLEX, + DECONZ_PRESET_HOLIDAY, DECONZ_PRESET_MANUAL, ) from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, @@ -48,31 +52,6 @@ setup_deconz_integration, ) -SENSORS = { - "1": { - "id": "Thermostat id", - "name": "Thermostat", - "type": "ZHAThermostat", - "state": {"on": True, "temperature": 2260, "valve": 30}, - "config": { - "battery": 100, - "heatsetpoint": 2200, - "mode": "auto", - "offset": 10, - "reachable": True, - }, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "CLIP thermostat id", - "name": "CLIP thermostat", - "type": "CLIPThermostat", - "state": {"on": True, "temperature": 2260, "valve": 30}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, -} - async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no climate entities.""" @@ -80,48 +59,47 @@ async def test_no_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_simple_climate_device(hass, aioclient_mock): +async def test_simple_climate_device(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of climate entities. This is a simple water heater that only supports setting temperature and on and off. """ - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 59, - "displayflipped": None, - "heatsetpoint": 2100, - "locked": True, - "mountingmode": None, - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "6130553ac247174809bae47144ee23f8", - "lastseen": "2020-11-29T19:31Z", - "manufacturername": "Danfoss", - "modelid": "eTRV0100", - "name": "thermostat", - "state": { - "errorcode": None, - "lastupdated": "2020-11-29T19:28:40.665", - "mountingmodeactive": False, - "on": True, - "temperature": 2102, - "valve": 24, - "windowopen": "Closed", - }, - "swversion": "01.02.0008 01.02", - "type": "ZHAThermostat", - "uniqueid": "14:b4:57:ff:fe:d5:4e:77-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 59, + "displayflipped": None, + "heatsetpoint": 2100, + "locked": True, + "mountingmode": None, + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "6130553ac247174809bae47144ee23f8", + "lastseen": "2020-11-29T19:31Z", + "manufacturername": "Danfoss", + "modelid": "eTRV0100", + "name": "thermostat", + "state": { + "errorcode": None, + "lastupdated": "2020-11-29T19:28:40.665", + "mountingmodeactive": False, + "on": True, + "temperature": 2102, + "valve": 24, + "windowopen": "Closed", + }, + "swversion": "01.02.0008 01.02", + "type": "ZHAThermostat", + "uniqueid": "14:b4:57:ff:fe:d5:4e:77-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.thermostat") @@ -137,28 +115,28 @@ async def test_simple_climate_device(hass, aioclient_mock): # Event signals thermostat configured off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == STATE_OFF # Event signals thermostat state on - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == HVAC_MODE_HEAT @@ -198,14 +176,29 @@ async def test_simple_climate_device(hass, aioclient_mock): ) -async def test_climate_device_without_cooling_support(hass, aioclient_mock): +async def test_climate_device_without_cooling_support( + hass, aioclient_mock, mock_deconz_websocket +): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "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) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.thermostat") @@ -224,21 +217,21 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): # Event signals thermostat configured off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "config": {"mode": "off"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == STATE_OFF # Event signals thermostat state on - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", @@ -246,21 +239,21 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): "config": {"mode": "other"}, "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == HVAC_MODE_HEAT # Event signals thermostat state off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == STATE_OFF @@ -336,7 +329,7 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE @@ -345,40 +338,41 @@ async def test_climate_device_without_cooling_support(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_climate_device_with_cooling_support(hass, aioclient_mock): +async def test_climate_device_with_cooling_support( + hass, aioclient_mock, mock_deconz_websocket +): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 25, - "coolsetpoint": None, - "fanmode": None, - "heatsetpoint": 2222, - "mode": "heat", - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "074549903686a77a12ef0f06c499b1ef", - "lastseen": "2020-11-27T13:45Z", - "manufacturername": "Zen Within", - "modelid": "Zen-01", - "name": "Zen-01", - "state": { - "lastupdated": "2020-11-27T13:42:40.863", - "on": False, - "temperature": 2320, - }, - "type": "ZHAThermostat", - "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 25, + "coolsetpoint": None, + "fanmode": None, + "heatsetpoint": 2222, + "mode": "heat", + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "074549903686a77a12ef0f06c499b1ef", + "lastseen": "2020-11-27T13:45Z", + "manufacturername": "Zen Within", + "modelid": "Zen-01", + "name": "Zen-01", + "state": { + "lastupdated": "2020-11-27T13:42:40.863", + "on": False, + "temperature": 2320, + }, + "type": "ZHAThermostat", + "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.zen_01") @@ -395,14 +389,14 @@ async def test_climate_device_with_cooling_support(hass, aioclient_mock): # Event signals thermostat state cool - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"mode": "cool"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").state == HVAC_MODE_COOL @@ -422,40 +416,41 @@ async def test_climate_device_with_cooling_support(hass, aioclient_mock): assert aioclient_mock.mock_calls[1][2] == {"coolsetpoint": 2000.0} -async def test_climate_device_with_fan_support(hass, aioclient_mock): +async def test_climate_device_with_fan_support( + hass, aioclient_mock, mock_deconz_websocket +): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 25, - "coolsetpoint": None, - "fanmode": "auto", - "heatsetpoint": 2222, - "mode": "heat", - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "074549903686a77a12ef0f06c499b1ef", - "lastseen": "2020-11-27T13:45Z", - "manufacturername": "Zen Within", - "modelid": "Zen-01", - "name": "Zen-01", - "state": { - "lastupdated": "2020-11-27T13:42:40.863", - "on": False, - "temperature": 2320, - }, - "type": "ZHAThermostat", - "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 25, + "coolsetpoint": None, + "fanmode": "auto", + "heatsetpoint": 2222, + "mode": "heat", + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "074549903686a77a12ef0f06c499b1ef", + "lastseen": "2020-11-27T13:45Z", + "manufacturername": "Zen Within", + "modelid": "Zen-01", + "name": "Zen-01", + "state": { + "lastupdated": "2020-11-27T13:42:40.863", + "on": False, + "temperature": 2320, + }, + "type": "ZHAThermostat", + "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 climate_thermostat = hass.states.get("climate.zen_01") @@ -473,21 +468,21 @@ async def test_climate_device_with_fan_support(hass, aioclient_mock): # Event signals fan mode defaults to off - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"fanmode": "unsupported"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_OFF # Event signals unsupported fan mode - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", @@ -495,21 +490,21 @@ async def test_climate_device_with_fan_support(hass, aioclient_mock): "config": {"fanmode": "unsupported"}, "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON # Event signals unsupported fan mode - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"fanmode": "unsupported"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["fan_mode"] == FAN_ON @@ -549,41 +544,40 @@ async def test_climate_device_with_fan_support(hass, aioclient_mock): ) -async def test_climate_device_with_preset(hass, aioclient_mock): +async def test_climate_device_with_preset(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "config": { - "battery": 25, - "coolsetpoint": None, - "fanmode": None, - "heatsetpoint": 2222, - "mode": "heat", - "preset": "auto", - "offset": 0, - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "074549903686a77a12ef0f06c499b1ef", - "lastseen": "2020-11-27T13:45Z", - "manufacturername": "Zen Within", - "modelid": "Zen-01", - "name": "Zen-01", - "state": { - "lastupdated": "2020-11-27T13:42:40.863", - "on": False, - "temperature": 2320, - }, - "type": "ZHAThermostat", - "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + data = { + "sensors": { + "0": { + "config": { + "battery": 25, + "coolsetpoint": None, + "fanmode": None, + "heatsetpoint": 2222, + "mode": "heat", + "preset": "auto", + "offset": 0, + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "074549903686a77a12ef0f06c499b1ef", + "lastseen": "2020-11-27T13:45Z", + "manufacturername": "Zen Within", + "modelid": "Zen-01", + "name": "Zen-01", + "state": { + "lastupdated": "2020-11-27T13:42:40.863", + "on": False, + "temperature": 2320, + }, + "type": "ZHAThermostat", + "uniqueid": "00:24:46:00:00:11:6f:56-01-0201", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 @@ -591,27 +585,27 @@ async def test_climate_device_with_preset(hass, aioclient_mock): assert climate_zen_01.state == HVAC_MODE_HEAT assert climate_zen_01.attributes["current_temperature"] == 23.2 assert climate_zen_01.attributes["temperature"] == 22.2 - assert climate_zen_01.attributes["preset_mode"] == "auto" + assert climate_zen_01.attributes["preset_mode"] == DECONZ_PRESET_AUTO assert climate_zen_01.attributes["preset_modes"] == [ - "auto", - "boost", - "comfort", - "complex", - "eco", - "holiday", - "manual", + DECONZ_PRESET_AUTO, + PRESET_BOOST, + PRESET_COMFORT, + DECONZ_PRESET_COMPLEX, + PRESET_ECO, + DECONZ_PRESET_HOLIDAY, + DECONZ_PRESET_MANUAL, ] # Event signals deCONZ preset - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"preset": "manual"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert ( @@ -621,14 +615,14 @@ async def test_climate_device_with_preset(hass, aioclient_mock): # Event signals unknown preset - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "0", "config": {"preset": "unsupported"}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.zen_01").attributes["preset_mode"] is None @@ -670,19 +664,36 @@ async def test_climate_device_with_preset(hass, aioclient_mock): async def test_clip_climate_device(hass, aioclient_mock): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_CLIP_SENSOR: True}, - get_state_response=data, - ) + data = { + "sensors": { + "1": { + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "CLIP thermostat", + "type": "CLIPThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} + ) assert len(hass.states.async_all()) == 3 - assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - assert hass.states.get("sensor.thermostat") is None - assert hass.states.get("sensor.thermostat_battery_level").state == "100" assert hass.states.get("climate.clip_thermostat").state == HVAC_MODE_HEAT # Disallow clip sensors @@ -693,7 +704,7 @@ async def test_clip_climate_device(hass, aioclient_mock): await hass.async_block_till_done() assert len(hass.states.async_all()) == 2 - assert hass.states.get("climate.clip_thermostat") is None + assert not hass.states.get("climate.clip_thermostat") # Allow clip sensors @@ -706,45 +717,70 @@ async def test_clip_climate_device(hass, aioclient_mock): assert hass.states.get("climate.clip_thermostat").state == HVAC_MODE_HEAT -async def test_verify_state_update(hass, aioclient_mock): +async def test_verify_state_update(hass, aioclient_mock, mock_deconz_websocket): """Test that state update properly.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "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 hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert hass.states.get("climate.thermostat").state == HVAC_MODE_AUTO - assert gateway.api.sensors["1"].changed_keys == {"state", "r", "t", "on", "e", "id"} -async def test_add_new_climate_device(hass, aioclient_mock): +async def test_add_new_climate_device(hass, aioclient_mock, mock_deconz_websocket): """Test that adding a new climate device works.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) - assert len(hass.states.async_all()) == 0 - - state_added_event = { + event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", - "sensor": deepcopy(SENSORS["1"]), + "sensor": { + "id": "Thermostat id", + "name": "Thermostat", + "type": "ZHAThermostat", + "state": {"on": True, "temperature": 2260, "valve": 30}, + "config": { + "battery": 100, + "heatsetpoint": 2200, + "mode": "auto", + "offset": 10, + "reachable": True, + }, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, } - gateway.api.event_handler(state_added_event) + + await setup_deconz_integration(hass, aioclient_mock) + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 2 From 283b4abe6719da72d99c72b45700a2b22c065ab1 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 10:02:00 +0100 Subject: [PATCH 1343/1818] Update typing 09 (#48059) --- .../components/iaqualink/__init__.py | 6 +++-- homeassistant/components/iaqualink/climate.py | 7 ++--- .../components/iaqualink/config_flow.py | 6 ++--- homeassistant/components/iaqualink/sensor.py | 8 +++--- homeassistant/components/icloud/account.py | 13 +++++----- .../components/icloud/device_tracker.py | 6 ++--- homeassistant/components/icloud/sensor.py | 6 ++--- .../components/ign_sismologia/geo_location.py | 11 ++++---- homeassistant/components/image/__init__.py | 9 ++++--- .../components/incomfort/__init__.py | 7 ++--- .../components/incomfort/binary_sensor.py | 6 +++-- homeassistant/components/incomfort/climate.py | 12 +++++---- homeassistant/components/incomfort/sensor.py | 12 +++++---- .../components/incomfort/water_heater.py | 6 +++-- homeassistant/components/influxdb/__init__.py | 16 +++++++----- homeassistant/components/influxdb/sensor.py | 5 ++-- .../components/input_boolean/__init__.py | 13 +++++----- .../input_boolean/reproduce_state.py | 12 +++++---- .../components/input_datetime/__init__.py | 15 +++++------ .../input_datetime/reproduce_state.py | 12 +++++---- .../components/input_number/__init__.py | 15 +++++------ .../input_number/reproduce_state.py | 12 +++++---- .../components/input_select/__init__.py | 17 ++++++------ .../input_select/reproduce_state.py | 12 +++++---- .../components/input_text/__init__.py | 15 +++++------ .../components/input_text/reproduce_state.py | 12 +++++---- homeassistant/components/insteon/climate.py | 22 ++++++++-------- homeassistant/components/insteon/schemas.py | 5 ++-- homeassistant/components/ipp/__init__.py | 8 +++--- homeassistant/components/ipp/config_flow.py | 16 +++++++----- homeassistant/components/ipp/sensor.py | 16 +++++++----- homeassistant/components/isy994/__init__.py | 5 ++-- .../components/isy994/binary_sensor.py | 6 +++-- homeassistant/components/isy994/climate.py | 22 +++++++++------- homeassistant/components/isy994/fan.py | 8 +++--- homeassistant/components/isy994/helpers.py | 26 ++++++++++--------- homeassistant/components/isy994/light.py | 6 +++-- homeassistant/components/isy994/sensor.py | 8 +++--- homeassistant/components/izone/climate.py | 16 ++++++------ 39 files changed, 239 insertions(+), 196 deletions(-) diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index d0667aab72afbd..0435645d87cd15 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -1,8 +1,10 @@ """Component to embed Aqualink devices.""" +from __future__ import annotations + import asyncio from functools import wraps import logging -from typing import Any, Dict +from typing import Any import aiohttp.client_exceptions from iaqualink import ( @@ -234,7 +236,7 @@ def available(self) -> bool: return self.dev.system.online @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device info.""" return { "identifiers": {(DOMAIN, self.unique_id)}, diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index 2c26b2bc363072..73988c4e523895 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -1,6 +1,7 @@ """Support for Aqualink Thermostats.""" +from __future__ import annotations + import logging -from typing import List, Optional from iaqualink import AqualinkHeater, AqualinkPump, AqualinkSensor, AqualinkState from iaqualink.const import ( @@ -53,7 +54,7 @@ def supported_features(self) -> int: return SUPPORT_TARGET_TEMPERATURE @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of supported HVAC modes.""" return CLIMATE_SUPPORTED_MODES @@ -119,7 +120,7 @@ def sensor(self) -> AqualinkSensor: return self.dev.system.devices[sensor] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" if self.sensor.state != "": return float(self.sensor.state) diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index c083aee7c1c076..df8880a0e8f336 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -1,5 +1,5 @@ """Config flow to configure zone component.""" -from typing import Optional +from __future__ import annotations from iaqualink import AqualinkClient, AqualinkLoginException import voluptuous as vol @@ -18,7 +18,7 @@ class AqualinkFlowHandler(config_entries.ConfigFlow): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - async def async_step_user(self, user_input: Optional[ConfigType] = None): + async def async_step_user(self, user_input: ConfigType | None = None): """Handle a flow start.""" # Supporting a single account. entries = self.hass.config_entries.async_entries(DOMAIN) @@ -46,6 +46,6 @@ async def async_step_user(self, user_input: Optional[ConfigType] = None): errors=errors, ) - async def async_step_import(self, user_input: Optional[ConfigType] = None): + async def async_step_import(self, user_input: ConfigType | None = None): """Occurs when an entry is setup through config.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 50f2a83221199f..565cfcca6671a6 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -1,5 +1,5 @@ """Support for Aqualink temperature sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.sensor import DOMAIN from homeassistant.config_entries import ConfigEntry @@ -31,7 +31,7 @@ def name(self) -> str: return self.dev.label @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the measurement unit for the sensor.""" if self.dev.name.endswith("_temp"): if self.dev.system.temp_unit == "F": @@ -40,7 +40,7 @@ def unit_of_measurement(self) -> Optional[str]: return None @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" if self.dev.state == "": return None @@ -52,7 +52,7 @@ def state(self) -> Optional[str]: return state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of the sensor.""" if self.dev.name.endswith("_temp"): return DEVICE_CLASS_TEMPERATURE diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 4221cf635ba585..5c7f448668de88 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -1,8 +1,9 @@ """iCloud account.""" +from __future__ import annotations + from datetime import timedelta import logging import operator -from typing import Dict, Optional from pyicloud import PyiCloudService from pyicloud.exceptions import ( @@ -95,7 +96,7 @@ def __init__( self._icloud_dir = icloud_dir - self.api: Optional[PyiCloudService] = None + self.api: PyiCloudService | None = None self._owner_fullname = None self._family_members_fullname = {} self._devices = {} @@ -345,7 +346,7 @@ def owner_fullname(self) -> str: return self._owner_fullname @property - def family_members_fullname(self) -> Dict[str, str]: + def family_members_fullname(self) -> dict[str, str]: """Return the account family members fullname.""" return self._family_members_fullname @@ -355,7 +356,7 @@ def fetch_interval(self) -> int: return self._fetch_interval @property - def devices(self) -> Dict[str, any]: + def devices(self) -> dict[str, any]: """Return the account devices.""" return self._devices @@ -496,11 +497,11 @@ def battery_status(self) -> str: return self._battery_status @property - def location(self) -> Dict[str, any]: + def location(self) -> dict[str, any]: """Return the Apple device location.""" return self._location @property - def state_attributes(self) -> Dict[str, any]: + def state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 77fcf0a3039ee7..3dbc10bcf1b30a 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -1,5 +1,5 @@ """Support for tracking for iCloud devices.""" -from typing import Dict +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity @@ -108,12 +108,12 @@ def icon(self) -> str: return icon_for_icloud_device(self._device) @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the device state attributes.""" return self._device.state_attributes @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index 9a7c568112db62..e75c9aa3854b29 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -1,5 +1,5 @@ """Support for iCloud sensors.""" -from typing import Dict +from __future__ import annotations from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE @@ -91,12 +91,12 @@ def icon(self) -> str: ) @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return default attributes for the iCloud device entity.""" return self._device.state_attributes @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index fc9bdcbe87e352..314a7bdea31a60 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -1,7 +1,8 @@ """Support for IGN Sismologia (Earthquakes) Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from georss_ign_sismologia_client import IgnSismologiaFeedManager import voluptuous as vol @@ -207,7 +208,7 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" if self._magnitude and self._region: return f"M {self._magnitude:.1f} - {self._region}" @@ -218,17 +219,17 @@ def name(self) -> Optional[str]: return self._title @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index c68df580643f38..37b3bd7ff6ad51 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -1,10 +1,11 @@ """The Picture integration.""" +from __future__ import annotations + import asyncio import logging import pathlib import secrets import shutil -import typing from PIL import Image, ImageOps, UnidentifiedImageError from aiohttp import hdrs, web @@ -69,7 +70,7 @@ def __init__(self, hass: HomeAssistant, image_dir: pathlib.Path) -> None: self.async_add_listener(self._change_listener) self.image_dir = image_dir - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" data = self.CREATE_SCHEMA(dict(data)) uploaded_file: FileField = data["file"] @@ -117,11 +118,11 @@ def _move_data(self, data): return media_file.stat().st_size @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_ID] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" return {**data, **self.UPDATE_SCHEMA(update_data)} diff --git a/homeassistant/components/incomfort/__init__.py b/homeassistant/components/incomfort/__init__.py index cec550d24d95e2..cea7244919b180 100644 --- a/homeassistant/components/incomfort/__init__.py +++ b/homeassistant/components/incomfort/__init__.py @@ -1,6 +1,7 @@ """Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway.""" +from __future__ import annotations + import logging -from typing import Optional from aiohttp import ClientResponseError from incomfortclient import Gateway as InComfortGateway @@ -68,12 +69,12 @@ def __init__(self) -> None: self._unique_id = self._name = None @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the sensor.""" return self._name diff --git a/homeassistant/components/incomfort/binary_sensor.py b/homeassistant/components/incomfort/binary_sensor.py index 86cdf2d8687832..cc8d2e24a0aee5 100644 --- a/homeassistant/components/incomfort/binary_sensor.py +++ b/homeassistant/components/incomfort/binary_sensor.py @@ -1,5 +1,7 @@ """Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, @@ -40,6 +42,6 @@ def is_on(self) -> bool: return self._heater.status["is_failed"] @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the device state attributes.""" return {"fault_code": self._heater.status["fault_code"]} diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index c3db86a79ac4d0..e44090a0b48ca0 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -1,5 +1,7 @@ """Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway.""" -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import Any from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( @@ -39,7 +41,7 @@ def __init__(self, client, heater, room) -> None: self._room = room @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" return {"status": self._room.status} @@ -54,17 +56,17 @@ def hvac_mode(self) -> str: return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_HEAT] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._room.room_temp @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._room.setpoint diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 0672d19b2a9ec2..8cb07fce14b772 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -1,5 +1,7 @@ """Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( @@ -57,17 +59,17 @@ def __init__(self, client, heater, name) -> None: self._unit_of_measurement = None @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" return self._heater.status[self._state_attr] @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of the sensor.""" return self._unit_of_measurement @@ -95,6 +97,6 @@ def __init__(self, client, heater, name) -> None: self._unit_of_measurement = TEMP_CELSIUS @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the device state attributes.""" return {self._attr: self._heater.status[self._attr]} diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index b3db66bd93db84..84ed0212d3bfd0 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -1,7 +1,9 @@ """Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict +from typing import Any from aiohttp import ClientResponseError @@ -50,7 +52,7 @@ def icon(self) -> str: return "mdi:thermometer-lines" @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the device state attributes.""" return {k: v for k, v in self._heater.status.items() if k in HEATER_ATTRS} diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index e327f34d128561..df7a8fda786bae 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,11 +1,13 @@ """Support for sending data to an Influx database.""" +from __future__ import annotations + from dataclasses import dataclass import logging import math import queue import threading import time -from typing import Any, Callable, Dict, List +from typing import Any, Callable from influxdb import InfluxDBClient, exceptions from influxdb_client import InfluxDBClient as InfluxDBClientV2 @@ -100,7 +102,7 @@ _LOGGER = logging.getLogger(__name__) -def create_influx_url(conf: Dict) -> Dict: +def create_influx_url(conf: dict) -> dict: """Build URL used from config inputs and default when necessary.""" if conf[CONF_API_VERSION] == API_VERSION_2: if CONF_SSL not in conf: @@ -125,7 +127,7 @@ def create_influx_url(conf: Dict) -> Dict: return conf -def validate_version_specific_config(conf: Dict) -> Dict: +def validate_version_specific_config(conf: dict) -> dict: """Ensure correct config fields are provided based on API version used.""" if conf[CONF_API_VERSION] == API_VERSION_2: if CONF_TOKEN not in conf: @@ -193,7 +195,7 @@ def validate_version_specific_config(conf: Dict) -> Dict: ) -def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: +def _generate_event_to_json(conf: dict) -> Callable[[dict], str]: """Build event to json converter and add to config.""" entity_filter = convert_include_exclude_filter(conf) tags = conf.get(CONF_TAGS) @@ -208,7 +210,7 @@ def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: conf[CONF_COMPONENT_CONFIG_GLOB], ) - def event_to_json(event: Dict) -> str: + def event_to_json(event: dict) -> str: """Convert event into json in format Influx expects.""" state = event.data.get(EVENT_NEW_STATE) if ( @@ -319,9 +321,9 @@ def event_to_json(event: Dict) -> str: class InfluxClient: """An InfluxDB client wrapper for V1 or V2.""" - data_repositories: List[str] + data_repositories: list[str] write: Callable[[str], None] - query: Callable[[str, str], List[Any]] + query: Callable[[str, str], list[Any]] close: Callable[[], None] diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index ff9f6f931532f2..cfabd3ee0995ff 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -1,6 +1,7 @@ """InfluxDB component which allows you to get data from an Influx database.""" +from __future__ import annotations + import logging -from typing import Dict import voluptuous as vol @@ -67,7 +68,7 @@ def _merge_connection_config_into_query(conf, query): query[key] = conf[key] -def validate_query_format_for_version(conf: Dict) -> Dict: +def validate_query_format_for_version(conf: dict) -> dict: """Ensure queries are provided in correct format based on API version.""" if conf[CONF_API_VERSION] == API_VERSION_2: if CONF_QUERIES_FLUX not in conf: diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index fbfe4cd0454a1b..f1e4ebd57dca9d 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -62,16 +61,16 @@ class InputBooleanStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return {**data, **update_data} @@ -145,14 +144,14 @@ async def reload_service_handler(service_call: ServiceCallType) -> None: class InputBoolean(ToggleEntity, RestoreEntity): """Representation of a boolean input.""" - def __init__(self, config: typing.Optional[dict]): + def __init__(self, config: dict | None): """Initialize a boolean input.""" self._config = config self.editable = True self._state = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputBoolean: + def from_yaml(cls, config: dict) -> InputBoolean: """Return entity instance initialized from yaml storage.""" input_bool = cls(config) input_bool.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -209,7 +208,7 @@ async def async_turn_off(self, **kwargs): self._state = False self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_boolean/reproduce_state.py b/homeassistant/components/input_boolean/reproduce_state.py index d01e931c5cceb6..8e8edcfd11b5e9 100644 --- a/homeassistant/components/input_boolean/reproduce_state.py +++ b/homeassistant/components/input_boolean/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an input boolean state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -22,8 +24,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce input boolean states.""" cur_state = hass.states.get(state.entity_id) @@ -56,8 +58,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index adefa36639a719..8273652be43b58 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -3,7 +3,6 @@ import datetime as py_datetime import logging -import typing import voluptuous as vol @@ -178,16 +177,16 @@ class DateTimeStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, has_date_or_time)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return has_date_or_time({**data, **update_data}) @@ -196,7 +195,7 @@ async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dic class InputDatetime(RestoreEntity): """Representation of a datetime input.""" - def __init__(self, config: typing.Dict) -> None: + def __init__(self, config: dict) -> None: """Initialize a select input.""" self._config = config self.editable = True @@ -230,7 +229,7 @@ def __init__(self, config: typing.Dict) -> None: ) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputDatetime: + def from_yaml(cls, config: dict) -> InputDatetime: """Return entity instance initialized from yaml storage.""" input_dt = cls(config) input_dt.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -360,7 +359,7 @@ def state_attributes(self): return attrs @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id of the entity.""" return self._config[CONF_ID] @@ -394,7 +393,7 @@ def async_set_datetime(self, date=None, time=None, datetime=None, timestamp=None ) self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_datetime/reproduce_state.py b/homeassistant/components/input_datetime/reproduce_state.py index cc906ac50b3886..a537a921f391c6 100644 --- a/homeassistant/components/input_datetime/reproduce_state.py +++ b/homeassistant/components/input_datetime/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Input datetime state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -35,8 +37,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -80,8 +82,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input datetime states.""" await asyncio.gather( diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index b68e6fff45da9b..5f73dec01923af 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -179,16 +178,16 @@ class NumberStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_number)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return _cv_input_number({**data, **update_data}) @@ -197,14 +196,14 @@ async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dic class InputNumber(RestoreEntity): """Representation of a slider.""" - def __init__(self, config: typing.Dict): + def __init__(self, config: dict): """Initialize an input number.""" self._config = config self.editable = True self._current_value = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputNumber: + def from_yaml(cls, config: dict) -> InputNumber: """Return entity instance initialized from yaml storage.""" input_num = cls(config) input_num.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -252,7 +251,7 @@ def unit_of_measurement(self): return self._config.get(CONF_UNIT_OF_MEASUREMENT) @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id of the entity.""" return self._config[CONF_ID] @@ -303,7 +302,7 @@ async def async_decrement(self): """Decrement value.""" await self.async_set_value(max(self._current_value - self._step, self._minimum)) - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config # just in case min/max values changed diff --git a/homeassistant/components/input_number/reproduce_state.py b/homeassistant/components/input_number/reproduce_state.py index 5a6324c43334eb..1f9af63e01b79b 100644 --- a/homeassistant/components/input_number/reproduce_state.py +++ b/homeassistant/components/input_number/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Input number state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable import voluptuous as vol @@ -18,8 +20,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -56,8 +58,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input number states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 5c10c33421aefe..f14d23124e7bbd 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -184,16 +183,16 @@ class InputSelectStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_select)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return _cv_input_select({**data, **update_data}) @@ -202,14 +201,14 @@ async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dic class InputSelect(RestoreEntity): """Representation of a select input.""" - def __init__(self, config: typing.Dict): + def __init__(self, config: dict): """Initialize a select input.""" self._config = config self.editable = True self._current_option = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputSelect: + def from_yaml(cls, config: dict) -> InputSelect: """Return entity instance initialized from yaml storage.""" input_select = cls(config) input_select.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -244,7 +243,7 @@ def icon(self): return self._config.get(CONF_ICON) @property - def _options(self) -> typing.List[str]: + def _options(self) -> list[str]: """Return a list of selection options.""" return self._config[CONF_OPTIONS] @@ -259,7 +258,7 @@ def state_attributes(self): return {ATTR_OPTIONS: self._config[ATTR_OPTIONS], ATTR_EDITABLE: self.editable} @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id for the entity.""" return self._config[CONF_ID] @@ -315,7 +314,7 @@ def async_set_options(self, options): self._config[CONF_OPTIONS] = options self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_select/reproduce_state.py b/homeassistant/components/input_select/reproduce_state.py index bf6877387409e4..beaee1097505a6 100644 --- a/homeassistant/components/input_select/reproduce_state.py +++ b/homeassistant/components/input_select/reproduce_state.py @@ -1,8 +1,10 @@ """Reproduce an Input select state.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -25,8 +27,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -71,8 +73,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input select states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 3f8c1d6a13e5a3..d4e0fc705f2d5e 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations import logging -import typing import voluptuous as vol @@ -173,16 +172,16 @@ class InputTextStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_text)) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return self.CREATE_SCHEMA(data) @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return _cv_input_text({**data, **update_data}) @@ -191,14 +190,14 @@ async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dic class InputText(RestoreEntity): """Represent a text box.""" - def __init__(self, config: typing.Dict): + def __init__(self, config: dict): """Initialize a text input.""" self._config = config self.editable = True self._current_value = config.get(CONF_INITIAL) @classmethod - def from_yaml(cls, config: typing.Dict) -> InputText: + def from_yaml(cls, config: dict) -> InputText: """Return entity instance initialized from yaml storage.""" input_text = cls(config) input_text.entity_id = f"{DOMAIN}.{config[CONF_ID]}" @@ -241,7 +240,7 @@ def unit_of_measurement(self): return self._config.get(CONF_UNIT_OF_MEASUREMENT) @property - def unique_id(self) -> typing.Optional[str]: + def unique_id(self) -> str | None: """Return unique id for the entity.""" return self._config[CONF_ID] @@ -282,7 +281,7 @@ async def async_set_value(self, value): self._current_value = value self.async_write_ha_state() - async def async_update_config(self, config: typing.Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self.async_write_ha_state() diff --git a/homeassistant/components/input_text/reproduce_state.py b/homeassistant/components/input_text/reproduce_state.py index abd28195d8d629..62e1a1fbd680a7 100644 --- a/homeassistant/components/input_text/reproduce_state.py +++ b/homeassistant/components/input_text/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Input text state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -16,8 +18,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -44,8 +46,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Input text states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 536c30bc6b9a77..7e034311a82936 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -1,5 +1,5 @@ """Support for Insteon thermostat.""" -from typing import List, Optional +from __future__ import annotations from pyinsteon.constants import ThermostatMode from pyinsteon.operating_flag import CELSIUS @@ -97,7 +97,7 @@ def temperature_unit(self) -> str: return TEMP_FAHRENHEIT @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" return self._insteon_device.groups[HUMIDITY].value @@ -107,17 +107,17 @@ def hvac_mode(self) -> str: return HVAC_MODES[self._insteon_device.groups[SYSTEM_MODE].value] @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(HVAC_MODES.values()) @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._insteon_device.groups[TEMPERATURE].value @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.HEAT: return self._insteon_device.groups[HEAT_SET_POINT].value @@ -126,31 +126,31 @@ def target_temperature(self) -> Optional[float]: return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO: return self._insteon_device.groups[COOL_SET_POINT].value return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO: return self._insteon_device.groups[HEAT_SET_POINT].value return None @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return FAN_MODES[self._insteon_device.groups[FAN_MODE].value] @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return list(FAN_MODES.values()) @property - def target_humidity(self) -> Optional[int]: + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" high = self._insteon_device.groups[HUMIDITY_HIGH].value low = self._insteon_device.groups[HUMIDITY_LOW].value @@ -163,7 +163,7 @@ def min_humidity(self) -> int: return 1 @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 8698a358b21abe..c43df24b4cb03e 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -1,6 +1,7 @@ """Schemas used by insteon component.""" +from __future__ import annotations + from binascii import Error as HexError, unhexlify -from typing import Dict from pyinsteon.address import Address from pyinsteon.constants import HC_LOOKUP @@ -51,7 +52,7 @@ ) -def set_default_port(schema: Dict) -> Dict: +def set_default_port(schema: dict) -> dict: """Set the default port based on the Hub version.""" # If the ip_port is found do nothing # If it is not found the set the default diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index c36b0cb7959961..6e522a999c0bd0 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -1,8 +1,10 @@ """The Internet Printing Protocol (IPP) integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from pyipp import IPP, IPPError, Printer as IPPPrinter @@ -39,7 +41,7 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the IPP component.""" hass.data.setdefault(DOMAIN, {}) return True @@ -166,7 +168,7 @@ def entity_registry_enabled_default(self) -> bool: return self._enabled_default @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this IPP device.""" if self._device_id is None: return None diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index 3815dcf8f69061..d167e79cba5db7 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure the IPP integration.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from pyipp import ( IPP, @@ -30,7 +32,7 @@ _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -61,8 +63,8 @@ def __init__(self): self.discovery_info = {} async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -98,7 +100,7 @@ async def async_step_user( return self.async_create_entry(title=user_input[CONF_HOST], data=user_input) - async def async_step_zeroconf(self, discovery_info: ConfigType) -> Dict[str, Any]: + async def async_step_zeroconf(self, discovery_info: ConfigType) -> dict[str, Any]: """Handle zeroconf discovery.""" port = discovery_info[CONF_PORT] zctype = discovery_info["type"] @@ -166,7 +168,7 @@ async def async_step_zeroconf(self, discovery_info: ConfigType) -> Dict[str, Any async def async_step_zeroconf_confirm( self, user_input: ConfigType = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a confirmation flow initiated by zeroconf.""" if user_input is None: return self.async_show_form( @@ -180,7 +182,7 @@ async def async_step_zeroconf_confirm( data=self.discovery_info, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index b24e05f2720544..1ff98df7a5fb2b 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -1,6 +1,8 @@ """Support for IPP sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_LOCATION, DEVICE_CLASS_TIMESTAMP, PERCENTAGE @@ -26,7 +28,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up IPP sensor based on a config entry.""" coordinator: IPPDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -63,7 +65,7 @@ def __init__( icon: str, key: str, name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ) -> None: """Initialize IPP sensor.""" self._unit_of_measurement = unit_of_measurement @@ -117,7 +119,7 @@ def __init__( ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_MARKER_HIGH_LEVEL: self.coordinator.data.markers[ @@ -132,7 +134,7 @@ def extra_state_attributes(self) -> Optional[Dict[str, Any]]: } @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return the state of the sensor.""" level = self.coordinator.data.markers[self.marker_index].level @@ -160,7 +162,7 @@ def __init__( ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_INFO: self.coordinator.data.info.printer_info, @@ -202,6 +204,6 @@ def state(self) -> str: return uptime.replace(microsecond=0).isoformat() @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_TIMESTAMP diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index d07fef97eddf76..a2648d0dbc449b 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -1,7 +1,8 @@ """Support the ISY-994 controllers.""" +from __future__ import annotations + import asyncio from functools import partial -from typing import Optional from urllib.parse import urlparse from pyisy import ISY @@ -67,7 +68,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the isy994 integration from YAML.""" - isy_config: Optional[ConfigType] = config.get(DOMAIN) + isy_config: ConfigType | None = config.get(DOMAIN) hass.data.setdefault(DOMAIN, {}) if not isy_config: diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 807f99734b703c..3b0bb9fd1444ca 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -1,6 +1,8 @@ """Support for ISY994 binary sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Callable, Union +from typing import Callable from pyisy.constants import ( CMD_OFF, @@ -173,7 +175,7 @@ async def async_setup_entry( async_add_entities(devices) -def _detect_device_type_and_class(node: Union[Group, Node]) -> (str, str): +def _detect_device_type_and_class(node: Group | Node) -> (str, str): try: device_type = node.type except AttributeError: diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index bb98c3d31bfb9d..2c9aa52b3a7406 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -1,5 +1,7 @@ """Support for Insteon Thermostats via ISY994 Platform.""" -from typing import Callable, List, Optional +from __future__ import annotations + +from typing import Callable from pyisy.constants import ( CMD_CLIMATE_FAN_SETTING, @@ -114,7 +116,7 @@ def temperature_unit(self) -> str: return TEMP_FAHRENHEIT @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity.""" humidity = self._node.aux_properties.get(PROP_HUMIDITY) if not humidity: @@ -122,7 +124,7 @@ def current_humidity(self) -> Optional[int]: return int(humidity.value) @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return hvac operation ie. heat, cool mode.""" hvac_mode = self._node.aux_properties.get(CMD_CLIMATE_MODE) if not hvac_mode: @@ -140,12 +142,12 @@ def hvac_mode(self) -> Optional[str]: return UOM_TO_STATES[uom].get(hvac_mode.value) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return ISY_HVAC_MODES @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" hvac_action = self._node.aux_properties.get(PROP_HEAT_COOL_STATE) if not hvac_action: @@ -153,19 +155,19 @@ def hvac_action(self) -> Optional[str]: return UOM_TO_STATES[UOM_HVAC_ACTIONS].get(hvac_action.value) @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return convert_isy_value_to_hass( self._node.status, self._uom, self._node.prec, 1 ) @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return 1.0 @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self.hvac_mode == HVAC_MODE_COOL: return self.target_temperature_high @@ -174,7 +176,7 @@ def target_temperature(self) -> Optional[float]: return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" target = self._node.aux_properties.get(PROP_SETPOINT_COOL) if not target: @@ -182,7 +184,7 @@ def target_temperature_high(self) -> Optional[float]: return convert_isy_value_to_hass(target.value, target.uom, target.prec, 1) @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" target = self._node.aux_properties.get(PROP_SETPOINT_HEAT) if not target: diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index 43323cc5546d7b..183d4b31d3b55e 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -1,6 +1,8 @@ """Support for ISY994 fans.""" +from __future__ import annotations + import math -from typing import Callable, Optional +from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_INSTEON @@ -43,7 +45,7 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): """Representation of an ISY994 fan device.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None @@ -97,7 +99,7 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity): """Representation of an ISY994 fan program.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._node.status == ISY_VALUE_UNKNOWN: return None diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index fecf18f789b536..780e24843bd0a5 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -1,5 +1,7 @@ """Sorting helpers for ISY994 device classifications.""" -from typing import Any, List, Optional, Union +from __future__ import annotations + +from typing import Any from pyisy.constants import ( ISY_VALUE_UNKNOWN, @@ -56,7 +58,7 @@ def _check_for_node_def( - hass_isy_data: dict, node: Union[Group, Node], single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: str = None ) -> bool: """Check if the node matches the node_def_id for any platforms. @@ -79,7 +81,7 @@ def _check_for_node_def( def _check_for_insteon_type( - hass_isy_data: dict, node: Union[Group, Node], single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: str = None ) -> bool: """Check if the node matches the Insteon type for any platforms. @@ -144,7 +146,7 @@ def _check_for_insteon_type( def _check_for_zwave_cat( - hass_isy_data: dict, node: Union[Group, Node], single_platform: str = None + hass_isy_data: dict, node: Group | Node, single_platform: str = None ) -> bool: """Check if the node matches the ISY Z-Wave Category for any platforms. @@ -174,7 +176,7 @@ def _check_for_zwave_cat( def _check_for_uom_id( hass_isy_data: dict, - node: Union[Group, Node], + node: Group | Node, single_platform: str = None, uom_list: list = None, ) -> bool: @@ -209,7 +211,7 @@ def _check_for_uom_id( def _check_for_states_in_uom( hass_isy_data: dict, - node: Union[Group, Node], + node: Group | Node, single_platform: str = None, states_list: list = None, ) -> bool: @@ -244,7 +246,7 @@ def _check_for_states_in_uom( return False -def _is_sensor_a_binary_sensor(hass_isy_data: dict, node: Union[Group, Node]) -> bool: +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): return True @@ -364,7 +366,7 @@ def _categorize_variables( async def migrate_old_unique_ids( - hass: HomeAssistantType, platform: str, devices: Optional[List[Any]] + hass: HomeAssistantType, platform: str, devices: list[Any] | None ) -> None: """Migrate to new controller-specific unique ids.""" registry = await async_get_registry(hass) @@ -396,11 +398,11 @@ async def migrate_old_unique_ids( def convert_isy_value_to_hass( - value: Union[int, float, None], + value: int | float | None, uom: str, - precision: Union[int, str], - fallback_precision: Optional[int] = None, -) -> Union[float, int]: + precision: int | str, + fallback_precision: int | None = None, +) -> float | int: """Fix ISY Reported Values. ISY provides float values as an integer and precision component. diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 04800a3f2118cc..7f35e96acaf57b 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -1,5 +1,7 @@ """Support for ISY994 lights.""" -from typing import Callable, Dict +from __future__ import annotations + +from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN @@ -98,7 +100,7 @@ def turn_on(self, brightness=None, **kwargs) -> None: _LOGGER.debug("Unable to turn on light") @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return the light attributes.""" attribs = super().extra_state_attributes attribs[ATTR_LAST_BRIGHTNESS] = self._last_brightness diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 2f393227df509c..4d3c4c72763755 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -1,5 +1,7 @@ """Support for ISY994 sensors.""" -from typing import Callable, Dict, Union +from __future__ import annotations + +from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN @@ -47,7 +49,7 @@ class ISYSensorEntity(ISYNodeEntity): """Representation of an ISY994 sensor device.""" @property - def raw_unit_of_measurement(self) -> Union[dict, str]: + def raw_unit_of_measurement(self) -> dict | str: """Get the raw unit of measurement for the ISY994 sensor device.""" uom = self._node.uom @@ -117,7 +119,7 @@ def state(self): 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: """Get the state attributes for the device.""" return { "init_value": convert_isy_value_to_hass( diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 53651683725508..18c8b5e9a2c13b 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -1,7 +1,7 @@ """Support for the iZone HVAC.""" +from __future__ import annotations import logging -from typing import List, Optional from pizone import Controller, Zone import voluptuous as vol @@ -324,7 +324,7 @@ def hvac_mode(self) -> str: @property @_return_on_connection_error([]) - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available operation modes.""" if self._controller.free_air: return [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] @@ -346,7 +346,7 @@ def preset_modes(self): @property @_return_on_connection_error() - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" if self._controller.mode == Controller.Mode.FREE_AIR: return self._controller.temp_supply @@ -364,7 +364,7 @@ def control_zone_name(self): return zone.name @property - def control_zone_setpoint(self) -> Optional[float]: + def control_zone_setpoint(self) -> float | None: """Return the temperature setpoint of the zone that currently controls the AC unit (if target temp not set by controller).""" if self._supported_features & SUPPORT_TARGET_TEMPERATURE: return None @@ -376,7 +376,7 @@ def control_zone_setpoint(self) -> Optional[float]: @property @_return_on_connection_error() - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach (either from control zone or master unit).""" if self._supported_features & SUPPORT_TARGET_TEMPERATURE: return self._controller.temp_setpoint @@ -388,17 +388,17 @@ def supply_temperature(self) -> float: return self._controller.temp_supply @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return 0.5 @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return _IZONE_FAN_TO_HA[self._controller.fan] @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return list(self._fan_to_pizone) From a57d340037a227b04d3c7884611355470a6f93a7 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 10:07:11 +0100 Subject: [PATCH 1344/1818] Use websocket fixture in deCONZ cover tests (#47822) Localize test data Improve asserts --- tests/components/deconz/test_cover.py | 160 ++++++++++++-------------- 1 file changed, 76 insertions(+), 84 deletions(-) diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index e48d44fb61e386..c252b00a228c19 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -1,8 +1,9 @@ """deCONZ cover platform tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, ATTR_POSITION, ATTR_TILT_POSITION, @@ -16,7 +17,6 @@ SERVICE_STOP_COVER, SERVICE_STOP_COVER_TILT, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import ( ATTR_ENTITY_ID, STATE_CLOSED, @@ -30,48 +30,6 @@ setup_deconz_integration, ) -COVERS = { - "1": { - "id": "Level controllable cover id", - "name": "Level controllable cover", - "type": "Level controllable output", - "state": {"bri": 254, "on": False, "reachable": True}, - "modelid": "Not zigbee spec", - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Window covering device id", - "name": "Window covering device", - "type": "Window covering device", - "state": {"lift": 100, "open": False, "reachable": True}, - "modelid": "lumi.curtain", - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "Unsupported cover id", - "name": "Unsupported cover", - "type": "Not a cover", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "id": "deconz old brightness cover id", - "name": "deconz old brightness cover", - "type": "Level controllable output", - "state": {"bri": 255, "on": False, "reachable": True}, - "modelid": "Not zigbee spec", - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "id": "Window covering controller id", - "name": "Window covering controller", - "type": "Window covering controller", - "state": {"bri": 253, "on": True, "reachable": True}, - "modelid": "Motor controller", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - async def test_no_covers(hass, aioclient_mock): """Test that no cover entities are created.""" @@ -79,32 +37,66 @@ async def test_no_covers(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_cover(hass, aioclient_mock): +async def test_cover(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported cover entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(COVERS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Level controllable cover", + "type": "Level controllable output", + "state": {"bri": 254, "on": False, "reachable": True}, + "modelid": "Not zigbee spec", + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Window covering device", + "type": "Window covering device", + "state": {"lift": 100, "open": False, "reachable": True}, + "modelid": "lumi.curtain", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "Unsupported cover", + "type": "Not a cover", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "deconz old brightness cover", + "type": "Level controllable output", + "state": {"bri": 255, "on": False, "reachable": True}, + "modelid": "Not zigbee spec", + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "5": { + "name": "Window covering controller", + "type": "Window covering controller", + "state": {"bri": 253, "on": True, "reachable": True}, + "modelid": "Motor controller", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 5 assert hass.states.get("cover.level_controllable_cover").state == STATE_OPEN assert hass.states.get("cover.window_covering_device").state == STATE_CLOSED - assert hass.states.get("cover.unsupported_cover") is None + assert not hass.states.get("cover.unsupported_cover") assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN assert hass.states.get("cover.window_covering_controller").state == STATE_CLOSED # Event signals cover is closed - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("cover.level_controllable_cover").state == STATE_CLOSED @@ -200,24 +192,24 @@ async def test_cover(hass, aioclient_mock): # Test that a reported cover position of 255 (deconz-rest-api < 2.05.73) is interpreted correctly. assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "4", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() deconz_old_brightness_cover = hass.states.get("cover.deconz_old_brightness_cover") assert deconz_old_brightness_cover.state == STATE_CLOSED - assert deconz_old_brightness_cover.attributes["current_position"] == 0 + assert deconz_old_brightness_cover.attributes[ATTR_CURRENT_POSITION] == 0 await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 5 + assert len(states) == 5 for state in states: assert state.state == STATE_UNAVAILABLE @@ -228,36 +220,36 @@ async def test_cover(hass, aioclient_mock): async def test_tilt_cover(hass, aioclient_mock): """Test that tilting a cover works.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "87269755b9b3a046485fdae8d96b252c", - "lastannounced": None, - "lastseen": "2020-08-01T16:22:05Z", - "manufacturername": "AXIS", - "modelid": "Gear", - "name": "Covering device", - "state": { - "bri": 0, - "lift": 0, - "on": False, - "open": True, - "reachable": True, - "tilt": 0, - }, - "swversion": "100-5.3.5.1122", - "type": "Window covering device", - "uniqueid": "00:24:46:00:00:12:34:56-01", + data = { + "lights": { + "0": { + "etag": "87269755b9b3a046485fdae8d96b252c", + "lastannounced": None, + "lastseen": "2020-08-01T16:22:05Z", + "manufacturername": "AXIS", + "modelid": "Gear", + "name": "Covering device", + "state": { + "bri": 0, + "lift": 0, + "on": False, + "open": True, + "reachable": True, + "tilt": 0, + }, + "swversion": "100-5.3.5.1122", + "type": "Window covering device", + "uniqueid": "00:24:46:00:00:12:34:56-01", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 - entity = hass.states.get("cover.covering_device") - assert entity.state == STATE_OPEN - assert entity.attributes[ATTR_CURRENT_TILT_POSITION] == 100 + covering_device = hass.states.get("cover.covering_device") + assert covering_device.state == STATE_OPEN + assert covering_device.attributes[ATTR_CURRENT_TILT_POSITION] == 100 # Verify service calls for tilting cover From 7ff9610e6761128036b2b8b3811a09964b124759 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 10:44:31 +0100 Subject: [PATCH 1345/1818] Use websocket fixture in deCONZ event related tests (#47823) Localize test data Improve asserts --- tests/components/deconz/test_deconz_event.py | 193 +++++++++++------- .../components/deconz/test_device_trigger.py | 76 ++++--- tests/components/deconz/test_logbook.py | 135 ++++++------ 3 files changed, 237 insertions(+), 167 deletions(-) diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 1212d72a6ee6ea..8a2e6a1d465c25 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -1,125 +1,164 @@ """Test deCONZ remote events.""" -from copy import deepcopy +from unittest.mock import patch +from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.helpers.device_registry import async_entries_for_config_entry from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration from tests.common import async_capture_events -SENSORS = { - "1": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "2": { - "id": "Switch 2 id", - "name": "Switch 2", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "3": { - "id": "Switch 3 id", - "name": "Switch 3", - "type": "ZHASwitch", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "4": { - "id": "Switch 4 id", - "name": "Switch 4", - "type": "ZHASwitch", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, - "5": { - "id": "ZHA remote 1 id", - "name": "ZHA remote 1", - "type": "ZHASwitch", - "state": {"angle": 0, "buttonevent": 1000, "xy": [0.0, 0.0]}, - "config": {"group": "4,5,6", "reachable": True, "on": True}, - "uniqueid": "00:00:00:00:00:00:00:05-00", - }, -} - - -async def test_deconz_events(hass, aioclient_mock): + +async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of deconz events.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "2": { + "name": "Switch 2", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "3": { + "name": "Switch 3", + "type": "ZHASwitch", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "4": { + "name": "Switch 4", + "type": "ZHASwitch", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + "5": { + "name": "ZHA remote 1", + "type": "ZHASwitch", + "state": {"angle": 0, "buttonevent": 1000, "xy": [0.0, 0.0]}, + "config": {"group": "4,5,6", "reachable": True, "on": True}, + "uniqueid": "00:00:00:00:00:00:00:05-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() assert len(hass.states.async_all()) == 3 - assert len(gateway.events) == 5 - assert hass.states.get("sensor.switch_1") is None - assert hass.states.get("sensor.switch_1_battery_level") is None - assert hass.states.get("sensor.switch_2") is None + # 5 switches + 2 additional devices for deconz service and host + 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" - events = async_capture_events(hass, CONF_DECONZ_EVENT) + captured_events = async_capture_events(hass, CONF_DECONZ_EVENT) - gateway.api.sensors["1"].update({"state": {"buttonevent": 2000}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"buttonevent": 2000}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 1 - assert events[0].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:01")} + ) + + assert len(captured_events) == 1 + assert captured_events[0].data == { "id": "switch_1", "unique_id": "00:00:00:00:00:00:00:01", "event": 2000, - "device_id": gateway.events[0].device_id, + "device_id": device.id, } - gateway.api.sensors["3"].update({"state": {"buttonevent": 2000}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "3", + "state": {"buttonevent": 2000}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 2 - assert events[1].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:03")} + ) + + assert len(captured_events) == 2 + assert captured_events[1].data == { "id": "switch_3", "unique_id": "00:00:00:00:00:00:00:03", "event": 2000, "gesture": 1, - "device_id": gateway.events[2].device_id, + "device_id": device.id, } - gateway.api.sensors["4"].update({"state": {"gesture": 0}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "4", + "state": {"gesture": 0}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 3 - assert events[2].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:04")} + ) + + assert len(captured_events) == 3 + assert captured_events[2].data == { "id": "switch_4", "unique_id": "00:00:00:00:00:00:00:04", "event": 1000, "gesture": 0, - "device_id": gateway.events[3].device_id, + "device_id": device.id, } - gateway.api.sensors["5"].update( - {"state": {"buttonevent": 6002, "angle": 110, "xy": [0.5982, 0.3897]}} - ) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "5", + "state": {"buttonevent": 6002, "angle": 110, "xy": [0.5982, 0.3897]}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() - assert len(events) == 4 - assert events[3].data == { + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "00:00:00:00:00:00:00:05")} + ) + + assert len(captured_events) == 4 + assert captured_events[3].data == { "id": "zha_remote_1", "unique_id": "00:00:00:00:00:00:00:05", "event": 6002, "angle": 110, "xy": [0.5982, 0.3897], - "device_id": gateway.events[4].device_id, + "device_id": device.id, } await hass.config_entries.async_unload(config_entry.entry_id) @@ -128,9 +167,7 @@ async def test_deconz_events(hass, aioclient_mock): assert len(hass.states.async_all()) == 3 for state in states: assert state.state == STATE_UNAVAILABLE - assert len(gateway.events) == 0 await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() assert len(hass.states.async_all()) == 0 - assert len(gateway.events) == 0 diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 20fd79e9e8cf24..7a39b6fe48f125 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -1,11 +1,10 @@ """deCONZ device automation tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.deconz import device_trigger from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.device_trigger import CONF_SUBTYPE -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -38,7 +37,7 @@ "name": "TRÅDFRI on/off switch ", "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, "swversion": "1.4.018", - CONF_TYPE: "ZHASwitch", + "type": "ZHASwitch", "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", } } @@ -46,60 +45,86 @@ async def test_get_triggers(hass, aioclient_mock): """Test triggers work.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data + data = { + "sensors": { + "1": { + "config": { + "alert": "none", + "battery": 60, + "group": "10", + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1b355c0b6d2af28febd7ca9165881952", + "manufacturername": "IKEA of Sweden", + "mode": 1, + "modelid": "TRADFRI on/off switch", + "name": "TRÅDFRI on/off switch ", + "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, + "swversion": "1.4.018", + "type": "ZHASwitch", + "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} ) - gateway = get_gateway_from_config_entry(hass, config_entry) - device_id = gateway.events[0].device_id - triggers = await async_get_device_automations(hass, "trigger", device_id) + + assert device_trigger._get_deconz_event_from_device_id(hass, device.id) + + triggers = await async_get_device_automations(hass, "trigger", device.id) expected_triggers = [ { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_SHORT_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_ON, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_ON, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_RELEASE, CONF_SUBTYPE: device_trigger.CONF_TURN_ON, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_SHORT_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_OFF, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_PRESS, CONF_SUBTYPE: device_trigger.CONF_TURN_OFF, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: DECONZ_DOMAIN, CONF_PLATFORM: "device", CONF_TYPE: device_trigger.CONF_LONG_RELEASE, CONF_SUBTYPE: device_trigger.CONF_TURN_OFF, }, { - CONF_DEVICE_ID: device_id, + CONF_DEVICE_ID: device.id, CONF_DOMAIN: SENSOR_DOMAIN, ATTR_ENTITY_ID: "sensor.tradfri_on_off_switch_battery_level", CONF_PLATFORM: "device", @@ -110,27 +135,14 @@ async def test_get_triggers(hass, aioclient_mock): assert_lists_same(triggers, expected_triggers) -async def test_helper_successful(hass, aioclient_mock): - """Verify trigger helper.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) - device_id = gateway.events[0].device_id - deconz_event = device_trigger._get_deconz_event_from_device_id(hass, device_id) - assert deconz_event == gateway.events[0] - - async def test_helper_no_match(hass, aioclient_mock): """Verify trigger helper returns None when no event could be matched.""" await setup_deconz_integration(hass, aioclient_mock) deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert deconz_event is None + assert not deconz_event async def test_helper_no_gateway_exist(hass): """Verify trigger helper returns None when no gateway exist.""" deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert deconz_event is None + assert not deconz_event diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 5886a29a8bfd04..e8e5244a2224a1 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -1,13 +1,13 @@ """The tests for deCONZ logbook.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components import logbook -from homeassistant.components.deconz.const import CONF_GESTURE +from homeassistant.components.deconz.const import CONF_GESTURE, DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_UNIQUE_ID from homeassistant.setup import async_setup_component +from homeassistant.util import slugify from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -16,47 +16,68 @@ async def test_humanifying_deconz_event(hass, aioclient_mock): """Test humanifying deCONZ event.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "1": { - "id": "Hue remote id", - "name": "Hue remote", - "type": "ZHASwitch", - "modelid": "RWL021", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "2": { - "id": "Xiaomi cube id", - "name": "Xiaomi cube", - "type": "ZHASwitch", - "modelid": "lumi.sensor_cube", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "3": { - "id": "faulty", - "name": "Faulty event", - "type": "ZHASwitch", - "state": {}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "2": { + "name": "Hue remote", + "type": "ZHASwitch", + "modelid": "RWL021", + "state": {"buttonevent": 1000}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "3": { + "name": "Xiaomi cube", + "type": "ZHASwitch", + "modelid": "lumi.sensor_cube", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "4": { + "name": "Faulty event", + "type": "ZHASwitch", + "state": {}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + + switch_event_id = slugify(data["sensors"]["1"]["name"]) + switch_serial = data["sensors"]["1"]["uniqueid"].split("-", 1)[0] + switch_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, switch_serial)} + ) + + hue_remote_event_id = slugify(data["sensors"]["2"]["name"]) + hue_remote_serial = data["sensors"]["2"]["uniqueid"].split("-", 1)[0] + hue_remote_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, hue_remote_serial)} + ) + + xiaomi_cube_event_id = slugify(data["sensors"]["3"]["name"]) + xiaomi_cube_serial = data["sensors"]["3"]["uniqueid"].split("-", 1)[0] + xiaomi_cube_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, xiaomi_cube_serial)} + ) + + faulty_event_id = slugify(data["sensors"]["4"]["name"]) + faulty_serial = data["sensors"]["4"]["uniqueid"].split("-", 1)[0] + faulty_entry = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, faulty_serial)} ) - gateway = get_gateway_from_config_entry(hass, config_entry) hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) @@ -70,50 +91,50 @@ async def test_humanifying_deconz_event(hass, aioclient_mock): MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[0].device_id, + CONF_DEVICE_ID: switch_entry.id, CONF_EVENT: 2000, - CONF_ID: gateway.events[0].event_id, - CONF_UNIQUE_ID: gateway.events[0].serial, + CONF_ID: switch_event_id, + CONF_UNIQUE_ID: switch_serial, }, ), # Event with matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[1].device_id, + CONF_DEVICE_ID: hue_remote_entry.id, CONF_EVENT: 2001, - CONF_ID: gateway.events[1].event_id, - CONF_UNIQUE_ID: gateway.events[1].serial, + CONF_ID: hue_remote_event_id, + CONF_UNIQUE_ID: hue_remote_serial, }, ), # Gesture with matching device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_DEVICE_ID: xiaomi_cube_entry.id, CONF_GESTURE: 1, - CONF_ID: gateway.events[2].event_id, - CONF_UNIQUE_ID: gateway.events[2].serial, + CONF_ID: xiaomi_cube_event_id, + CONF_UNIQUE_ID: xiaomi_cube_serial, }, ), # Unsupported device trigger MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[2].device_id, + CONF_DEVICE_ID: xiaomi_cube_entry.id, CONF_GESTURE: "unsupported_gesture", - CONF_ID: gateway.events[2].event_id, - CONF_UNIQUE_ID: gateway.events[2].serial, + CONF_ID: xiaomi_cube_event_id, + CONF_UNIQUE_ID: xiaomi_cube_serial, }, ), # Unknown event MockLazyEventPartialState( CONF_DECONZ_EVENT, { - CONF_DEVICE_ID: gateway.events[3].device_id, + CONF_DEVICE_ID: faulty_entry.id, "unknown_event": None, - CONF_ID: gateway.events[3].event_id, - CONF_UNIQUE_ID: gateway.events[3].serial, + CONF_ID: faulty_event_id, + CONF_UNIQUE_ID: faulty_serial, }, ), ], From 7350215b4e9f95cb697d67eaa095a92679225900 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 10:49:48 +0100 Subject: [PATCH 1346/1818] Use websocket fixture in deCONZ fan tests (#47824) Localize test data Improve asserts --- tests/components/deconz/test_fan.py | 75 ++++++++++++++--------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index c6acbb7f6aa343..930645689f8b08 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -1,10 +1,9 @@ """deCONZ fan platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.fan import ( ATTR_SPEED, DOMAIN as FAN_DOMAIN, @@ -24,26 +23,6 @@ setup_deconz_integration, ) -FANS = { - "1": { - "etag": "432f3de28965052961a99e3c5494daf4", - "hascolor": False, - "manufacturername": "King Of Fans, Inc.", - "modelid": "HDC52EastwindFan", - "name": "Ceiling fan", - "state": { - "alert": "none", - "bri": 254, - "on": False, - "reachable": True, - "speed": 4, - }, - "swversion": "0000000F", - "type": "Fan", - "uniqueid": "00:22:a3:00:00:27:8b:81-01", - } -} - async def test_no_fans(hass, aioclient_mock): """Test that no fan entities are created.""" @@ -51,35 +30,51 @@ async def test_no_fans(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_fans(hass, aioclient_mock): +async def test_fans(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported fan entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(FANS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "etag": "432f3de28965052961a99e3c5494daf4", + "hascolor": False, + "manufacturername": "King Of Fans, Inc.", + "modelid": "HDC52EastwindFan", + "name": "Ceiling fan", + "state": { + "alert": "none", + "bri": 254, + "on": False, + "reachable": True, + "speed": 4, + }, + "swversion": "0000000F", + "type": "Fan", + "uniqueid": "00:22:a3:00:00:27:8b:81-01", + } + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 # Light and fan - assert hass.states.get("fan.ceiling_fan") + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH # Test states - assert hass.states.get("fan.ceiling_fan").state == STATE_ON - assert hass.states.get("fan.ceiling_fan").attributes["speed"] == SPEED_HIGH - - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"speed": 0}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("fan.ceiling_fan").state == STATE_OFF - assert hass.states.get("fan.ceiling_fan").attributes["speed"] == SPEED_OFF + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_OFF # Test service calls @@ -157,23 +152,23 @@ async def test_fans(hass, aioclient_mock): # Events with an unsupported speed gets converted to default speed "medium" - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"speed": 3}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("fan.ceiling_fan").state == STATE_ON - assert hass.states.get("fan.ceiling_fan").attributes["speed"] == SPEED_MEDIUM + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_MEDIUM await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE From 3f2d3bd1b2710e14ca5bb58f4386a23a0ae933d2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:01:32 +0100 Subject: [PATCH 1347/1818] Use websocket fixture in deCONZ light tests (#47826) Localize test data Improve asserts# --- tests/components/deconz/test_light.py | 528 ++++++++++++++------------ 1 file changed, 285 insertions(+), 243 deletions(-) diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index c7f7fab1868aab..84c7a9b1078696 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1,11 +1,10 @@ """deCONZ light platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -36,75 +35,6 @@ setup_deconz_integration, ) -GROUPS = { - "1": { - "id": "Light group id", - "name": "Light group", - "type": "LightGroup", - "state": {"all_on": False, "any_on": True}, - "action": {}, - "scenes": [], - "lights": ["1", "2"], - }, - "2": { - "id": "Empty group id", - "name": "Empty group", - "type": "LightGroup", - "state": {}, - "action": {}, - "scenes": [], - "lights": [], - }, -} - -LIGHTS = { - "1": { - "id": "RGB light id", - "name": "RGB light", - "state": { - "on": True, - "bri": 255, - "colormode": "xy", - "effect": "colorloop", - "xy": (500, 500), - "reachable": True, - }, - "type": "Extended color light", - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "ctmax": 454, - "ctmin": 155, - "id": "Tunable white light id", - "name": "Tunable white light", - "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, - "type": "Tunable white light", - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "On off switch id", - "name": "On off switch", - "type": "On/Off plug-in unit", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "name": "On off light", - "state": {"on": True, "reachable": True}, - "type": "On and Off light", - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "ctmax": 1000, - "ctmin": 0, - "id": "Tunable white light with bad maxmin values id", - "name": "Tunable white light with bad maxmin values", - "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, - "type": "Tunable white light", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - async def test_no_lights_or_groups(hass, aioclient_mock): """Test that no lights or groups entities are created.""" @@ -112,15 +42,75 @@ async def test_no_lights_or_groups(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_lights_and_groups(hass, aioclient_mock): +async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket): """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [], + "lights": ["1", "2"], + }, + "2": { + "id": "Empty group id", + "name": "Empty group", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [], + "lights": [], + }, + }, + "lights": { + "1": { + "name": "RGB light", + "state": { + "on": True, + "bri": 255, + "colormode": "xy", + "effect": "colorloop", + "xy": (500, 500), + "reachable": True, + }, + "type": "Extended color light", + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "ctmax": 454, + "ctmin": 155, + "name": "Tunable white light", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "On off switch", + "type": "On/Off plug-in unit", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "On off light", + "state": {"on": True, "reachable": True}, + "type": "On and Off light", + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "5": { + "ctmax": 1000, + "ctmin": 0, + "name": "Tunable white light with bad maxmin values", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 6 @@ -151,25 +141,23 @@ async def test_lights_and_groups(hass, aioclient_mock): assert on_off_light.state == STATE_ON assert on_off_light.attributes[ATTR_SUPPORTED_FEATURES] == 0 - light_group = hass.states.get("light.light_group") - assert light_group.state == STATE_ON - assert light_group.attributes["all_on"] is False + assert hass.states.get("light.light_group").state == STATE_ON + assert hass.states.get("light.light_group").attributes["all_on"] is False empty_group = hass.states.get("light.empty_group") assert empty_group is None - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() - rgb_light = hass.states.get("light.rgb_light") - assert rgb_light.state == STATE_OFF + assert hass.states.get("light.rgb_light").state == STATE_OFF # Verify service calls @@ -231,14 +219,14 @@ async def test_lights_and_groups(hass, aioclient_mock): ) assert len(aioclient_mock.mock_calls) == 3 # Not called - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() # Service turn off light with short flashing @@ -272,7 +260,7 @@ async def test_lights_and_groups(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 6 + assert len(states) == 6 for state in states: assert state.state == STATE_UNAVAILABLE @@ -283,28 +271,56 @@ async def test_lights_and_groups(hass, aioclient_mock): async def test_disable_light_groups(hass, aioclient_mock): """Test disallowing light groups work.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_DECONZ_GROUPS: False}, - get_state_response=data, - ) - - assert len(hass.states.async_all()) == 5 - assert hass.states.get("light.rgb_light") + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [], + "lights": ["1"], + }, + "2": { + "id": "Empty group id", + "name": "Empty group", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [], + "lights": [], + }, + }, + "lights": { + "1": { + "ctmax": 454, + "ctmin": 155, + "name": "Tunable white light", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, + aioclient_mock, + options={CONF_ALLOW_DECONZ_GROUPS: False}, + ) + + assert len(hass.states.async_all()) == 1 assert hass.states.get("light.tunable_white_light") - assert hass.states.get("light.light_group") is None - assert hass.states.get("light.empty_group") is None + assert not hass.states.get("light.light_group") + assert not hass.states.get("light.empty_group") hass.config_entries.async_update_entry( config_entry, options={CONF_ALLOW_DECONZ_GROUPS: True} ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_all()) == 2 assert hass.states.get("light.light_group") hass.config_entries.async_update_entry( @@ -312,62 +328,96 @@ async def test_disable_light_groups(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 5 - assert hass.states.get("light.light_group") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("light.light_group") async def test_configuration_tool(hass, aioclient_mock): - """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "26839cb118f5bf7ba1f2108256644010", - "hascolor": False, - "lastannounced": None, - "lastseen": "2020-11-22T11:27Z", - "manufacturername": "dresden elektronik", - "modelid": "ConBee II", - "name": "Configuration tool 1", - "state": {"reachable": True}, - "swversion": "0x264a0700", - "type": "Configuration tool", - "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", + """Test that configuration tool is not created.""" + data = { + "lights": { + "0": { + "etag": "26839cb118f5bf7ba1f2108256644010", + "hascolor": False, + "lastannounced": None, + "lastseen": "2020-11-22T11:27Z", + "manufacturername": "dresden elektronik", + "modelid": "ConBee II", + "name": "Configuration tool 1", + "state": {"reachable": True}, + "swversion": "0x264a0700", + "type": "Configuration tool", + "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 +async def test_ikea_default_transition_time(hass, aioclient_mock): + """Verify that service calls to IKEA lights always extend with transition tinme 0 if absent.""" + data = { + "lights": { + "1": { + "manufacturername": "IKEA", + "name": "Dimmable light", + "state": {"on": True, "bri": 255, "reachable": True}, + "type": "Dimmable light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.dimmable_light", ATTR_BRIGHTNESS: 100}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == { + "bri": 100, + "on": True, + "transitiontime": 0, + } + + async def test_lidl_christmas_light(hass, aioclient_mock): """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "87a89542bf9b9d0aa8134919056844f8", - "hascolor": True, - "lastannounced": None, - "lastseen": "2020-12-05T22:57Z", - "manufacturername": "_TZE200_s8gkrkxk", - "modelid": "TS0601", - "name": "xmas light", - "state": { - "bri": 25, - "colormode": "hs", - "effect": "none", - "hue": 53691, - "on": True, - "reachable": True, - "sat": 141, - }, - "swversion": None, - "type": "Color dimmable light", - "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", + data = { + "lights": { + "0": { + "etag": "87a89542bf9b9d0aa8134919056844f8", + "hascolor": True, + "lastannounced": None, + "lastseen": "2020-12-05T22:57Z", + "manufacturername": "_TZE200_s8gkrkxk", + "modelid": "TS0601", + "name": "xmas light", + "state": { + "bri": 25, + "colormode": "hs", + "effect": "none", + "hue": 53691, + "on": True, + "reachable": True, + "sat": 141, + }, + "swversion": None, + "type": "Color dimmable light", + "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state") @@ -385,100 +435,98 @@ async def test_lidl_christmas_light(hass, aioclient_mock): assert hass.states.get("light.xmas_light") -async def test_non_color_light_reports_color(hass, aioclient_mock): +async def test_non_color_light_reports_color( + hass, aioclient_mock, mock_deconz_websocket +): """Verify hs_color does not crash when a group gets updated with a bad color value. After calling a scene color temp light of certain manufacturers report color temp in color space. """ - data = deepcopy(DECONZ_WEB_REQUEST) - - data["groups"] = { - "0": { - "action": { - "alert": "none", - "bri": 127, - "colormode": "hs", - "ct": 0, - "effect": "none", - "hue": 0, - "on": True, - "sat": 127, - "scene": None, - "xy": [0, 0], - }, - "devicemembership": [], - "etag": "81e42cf1b47affb72fa72bc2e25ba8bf", - "id": "0", - "lights": ["0", "1"], - "name": "All", - "scenes": [], - "state": {"all_on": False, "any_on": True}, - "type": "LightGroup", - } - } - - data["lights"] = { - "0": { - "ctmax": 500, - "ctmin": 153, - "etag": "026bcfe544ad76c7534e5ca8ed39047c", - "hascolor": True, - "manufacturername": "dresden elektronik", - "modelid": "FLS-PP3", - "name": "Light 1", - "pointsymbol": {}, - "state": { - "alert": None, - "bri": 111, - "colormode": "ct", - "ct": 307, - "effect": None, + data = { + "groups": { + "0": { + "action": { + "alert": "none", + "bri": 127, + "colormode": "hs", + "ct": 0, + "effect": "none", + "hue": 0, + "on": True, + "sat": 127, + "scene": None, + "xy": [0, 0], + }, + "devicemembership": [], + "etag": "81e42cf1b47affb72fa72bc2e25ba8bf", + "lights": ["0", "1"], + "name": "All", + "scenes": [], + "state": {"all_on": False, "any_on": True}, + "type": "LightGroup", + } + }, + "lights": { + "0": { + "ctmax": 500, + "ctmin": 153, + "etag": "026bcfe544ad76c7534e5ca8ed39047c", "hascolor": True, - "hue": 7998, - "on": False, - "reachable": True, - "sat": 172, - "xy": [0.421253, 0.39921], + "manufacturername": "dresden elektronik", + "modelid": "FLS-PP3", + "name": "Light 1", + "pointsymbol": {}, + "state": { + "alert": None, + "bri": 111, + "colormode": "ct", + "ct": 307, + "effect": None, + "hascolor": True, + "hue": 7998, + "on": False, + "reachable": True, + "sat": 172, + "xy": [0.421253, 0.39921], + }, + "swversion": "020C.201000A0", + "type": "Extended color light", + "uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A", }, - "swversion": "020C.201000A0", - "type": "Extended color light", - "uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A", - }, - "1": { - "colorcapabilities": 0, - "ctmax": 65535, - "ctmin": 0, - "etag": "9dd510cd474791481f189d2a68a3c7f1", - "hascolor": True, - "lastannounced": "2020-12-17T17:44:38Z", - "lastseen": "2021-01-11T18:36Z", - "manufacturername": "IKEA of Sweden", - "modelid": "TRADFRI bulb E27 WS opal 1000lm", - "name": "Küchenlicht", - "state": { - "alert": "none", - "bri": 156, - "colormode": "ct", - "ct": 250, - "on": True, - "reachable": True, + "1": { + "colorcapabilities": 0, + "ctmax": 65535, + "ctmin": 0, + "etag": "9dd510cd474791481f189d2a68a3c7f1", + "hascolor": True, + "lastannounced": "2020-12-17T17:44:38Z", + "lastseen": "2021-01-11T18:36Z", + "manufacturername": "IKEA of Sweden", + "modelid": "TRADFRI bulb E27 WS opal 1000lm", + "name": "Küchenlicht", + "state": { + "alert": "none", + "bri": 156, + "colormode": "ct", + "ct": 250, + "on": True, + "reachable": True, + }, + "swversion": "2.0.022", + "type": "Color temperature light", + "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, - "swversion": "2.0.022", - "type": "Color temperature light", - "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 3 assert hass.states.get("light.all").attributes[ATTR_COLOR_TEMP] == 307 # Updating a scene will return a faulty color value for a non-color light causing an exception in hs_color - state_changed_event = { + event_changed_light = { "e": "changed", "id": "1", "r": "lights", @@ -493,7 +541,7 @@ async def test_non_color_light_reports_color(hass, aioclient_mock): "t": "event", "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() # Bug is fixed if we reach this point, but device won't have neither color temp nor color @@ -504,9 +552,8 @@ async def test_non_color_light_reports_color(hass, aioclient_mock): async def test_verify_group_supported_features(hass, aioclient_mock): """Test that group supported features reflect what included lights support.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy( - { + data = { + "groups": { "1": { "id": "Group1", "name": "group", @@ -516,19 +563,15 @@ async def test_verify_group_supported_features(hass, aioclient_mock): "scenes": [], "lights": ["1", "2", "3"], }, - } - ) - data["lights"] = deepcopy( - { + }, + "lights": { "1": { - "id": "light1", "name": "Dimmable light", "state": {"on": True, "bri": 255, "reachable": True}, "type": "Light", "uniqueid": "00:00:00:00:00:00:00:01-00", }, "2": { - "id": "light2", "name": "Color light", "state": { "on": True, @@ -544,18 +587,17 @@ async def test_verify_group_supported_features(hass, aioclient_mock): "3": { "ctmax": 454, "ctmin": 155, - "id": "light3", "name": "Tunable light", "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, "type": "Tunable white light", "uniqueid": "00:00:00:00:00:00:00:03-00", }, - } - ) - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 4 - group = hass.states.get("light.group") - assert group.state == STATE_ON - assert group.attributes[ATTR_SUPPORTED_FEATURES] == 63 + assert hass.states.get("light.group").state == STATE_ON + assert hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES] == 63 From a21d0cadf8795a44649dcade50744250296d3246 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:06:44 +0100 Subject: [PATCH 1348/1818] Use websocket fixture in deCONZ lock tests (#47827) Localize test data Improve asserts --- tests/components/deconz/test_lock.py | 55 ++++++++++++---------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/tests/components/deconz/test_lock.py b/tests/components/deconz/test_lock.py index a6b4caaec19553..1b4bf3c160c8e6 100644 --- a/tests/components/deconz/test_lock.py +++ b/tests/components/deconz/test_lock.py @@ -1,8 +1,7 @@ """deCONZ lock platform tests.""" -from copy import deepcopy +from unittest.mock import patch -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.lock import ( DOMAIN as LOCK_DOMAIN, SERVICE_LOCK, @@ -21,22 +20,6 @@ setup_deconz_integration, ) -LOCKS = { - "1": { - "etag": "5c2ec06cde4bd654aef3a555fcd8ad12", - "hascolor": False, - "lastannounced": None, - "lastseen": "2020-08-22T15:29:03Z", - "manufacturername": "Danalock", - "modelid": "V3-BTZB", - "name": "Door lock", - "state": {"alert": "none", "on": False, "reachable": True}, - "swversion": "19042019", - "type": "Door Lock", - "uniqueid": "00:00:00:00:00:00:00:00-00", - } -} - async def test_no_locks(hass, aioclient_mock): """Test that no lock entities are created.""" @@ -44,29 +27,39 @@ async def test_no_locks(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_locks(hass, aioclient_mock): +async def test_locks(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported lock entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(LOCKS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "etag": "5c2ec06cde4bd654aef3a555fcd8ad12", + "hascolor": False, + "lastannounced": None, + "lastseen": "2020-08-22T15:29:03Z", + "manufacturername": "Danalock", + "modelid": "V3-BTZB", + "name": "Door lock", + "state": {"alert": "none", "on": False, "reachable": True}, + "swversion": "19042019", + "type": "Door Lock", + "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) assert len(hass.states.async_all()) == 1 assert hass.states.get("lock.door_lock").state == STATE_UNLOCKED - door_lock = hass.states.get("lock.door_lock") - assert door_lock.state == STATE_UNLOCKED - - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() assert hass.states.get("lock.door_lock").state == STATE_LOCKED @@ -98,7 +91,7 @@ async def test_locks(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 1 + assert len(states) == 1 for state in states: assert state.state == STATE_UNAVAILABLE From a4b2dff58d9c59c101e3cf0b3812b1d1e8197b50 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:41:27 +0100 Subject: [PATCH 1349/1818] Use websocket fixture in deCONZ sensor tests (#47830) Localize test data Improve asserts --- tests/components/deconz/test_sensor.py | 634 +++++++++++++------------ 1 file changed, 333 insertions(+), 301 deletions(-) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 1a52194633512a..e9a35337aef837 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -1,86 +1,21 @@ """deCONZ sensor platform tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.components.deconz.sensor import ATTR_DAYLIGHT from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( + ATTR_DEVICE_CLASS, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration -SENSORS = { - "1": { - "id": "Light sensor id", - "name": "Light level sensor", - "type": "ZHALightLevel", - "state": {"lightlevel": 30000, "dark": False}, - "config": {"on": True, "reachable": True, "temperature": 10}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Presence sensor id", - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "id": "Switch 2 id", - "name": "Switch 2", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "id": "Daylight sensor id", - "name": "Daylight sensor", - "type": "Daylight", - "state": {"daylight": True, "status": 130}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, - "6": { - "id": "Power sensor id", - "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", - }, - "7": { - "id": "Consumption id", - "name": "Consumption sensor", - "type": "ZHAConsumption", - "state": {"consumption": 2, "power": 6}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:06-00", - }, - "8": { - "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", - }, -} - async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" @@ -88,76 +23,138 @@ async def test_no_sensors(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_sensors(hass, aioclient_mock): +async def test_sensors(hass, aioclient_mock, mock_deconz_websocket): """Test successful creation of sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + 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": "Daylight sensor", + "type": "Daylight", + "state": {"daylight": True, "status": 130}, + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + "6": { + "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", + }, + "7": { + "name": "Consumption sensor", + "type": "ZHAConsumption", + "state": {"consumption": 2, "power": 6}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:06-00", + }, + "8": { + "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()) == 5 light_level_sensor = hass.states.get("sensor.light_level_sensor") assert light_level_sensor.state == "999.8" - assert light_level_sensor.attributes["device_class"] == DEVICE_CLASS_ILLUMINANCE + assert light_level_sensor.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_ILLUMINANCE + assert light_level_sensor.attributes[ATTR_DAYLIGHT] == 6955 - assert hass.states.get("sensor.presence_sensor") is None - assert hass.states.get("sensor.switch_1") is None - assert hass.states.get("sensor.switch_1_battery_level") is None - assert hass.states.get("sensor.switch_2") is None + 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") 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["device_class"] == DEVICE_CLASS_BATTERY + assert switch_2_battery_level.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_BATTERY - assert hass.states.get("sensor.daylight_sensor") is None + 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["device_class"] == DEVICE_CLASS_POWER + assert power_sensor.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER consumption_sensor = hass.states.get("sensor.consumption_sensor") assert consumption_sensor.state == "0.002" - assert "device_class" not in consumption_sensor.attributes + assert ATTR_DEVICE_CLASS not in consumption_sensor.attributes - assert hass.states.get("sensor.clip_light_level_sensor") is None + assert not hass.states.get("sensor.clip_light_level_sensor") # Event signals new light level - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "1", "state": {"lightlevel": 2000}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_sensor) assert hass.states.get("sensor.light_level_sensor").state == "1.6" # Event signals new battery level - state_changed_event = { + event_changed_sensor = { "t": "event", "e": "changed", "r": "sensors", "id": "4", "config": {"battery": 75}, } - gateway.api.event_handler(state_changed_event) - await hass.async_block_till_done() + await mock_deconz_websocket(data=event_changed_sensor) assert hass.states.get("sensor.switch_2_battery_level").state == "75" + # Unload entry + await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 5 + assert len(states) == 5 for state in states: assert state.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 @@ -165,16 +162,33 @@ async def test_sensors(hass, aioclient_mock): async def test_allow_clip_sensors(hass, aioclient_mock): """Test that CLIP sensors can be allowed.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = deepcopy(SENSORS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_CLIP_SENSOR: True}, - get_state_response=data, - ) + data = { + "sensors": { + "1": { + "name": "Light level sensor", + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "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:01-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, + aioclient_mock, + options={CONF_ALLOW_CLIP_SENSOR: True}, + ) - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_all()) == 2 assert hass.states.get("sensor.clip_light_level_sensor").state == "999.8" # Disallow clip sensors @@ -184,8 +198,8 @@ async def test_allow_clip_sensors(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 5 - assert hass.states.get("sensor.clip_light_level_sensor") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("sensor.clip_light_level_sensor") # Allow clip sensors @@ -194,52 +208,70 @@ async def test_allow_clip_sensors(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 6 - assert hass.states.get("sensor.clip_light_level_sensor") + assert len(hass.states.async_all()) == 2 + assert hass.states.get("sensor.clip_light_level_sensor").state == "999.8" -async def test_add_new_sensor(hass, aioclient_mock): +async def test_add_new_sensor(hass, aioclient_mock, mock_deconz_websocket): """Test that adding a new sensor works.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) - assert len(hass.states.async_all()) == 0 - - state_added_event = { + event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", - "sensor": deepcopy(SENSORS["1"]), + "sensor": { + "id": "Light sensor id", + "name": "Light level sensor", + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"on": True, "reachable": True, "temperature": 10}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, } - gateway.api.event_handler(state_added_event) + + await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 0 + + await mock_deconz_websocket(data=event_added_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 assert hass.states.get("sensor.light_level_sensor").state == "999.8" -async def test_add_battery_later(hass, aioclient_mock): +async def test_add_battery_later(hass, aioclient_mock, mock_deconz_websocket): """Test that a sensor without an initial battery state creates a battery sensor once state exist.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = {"1": deepcopy(SENSORS["3"])} - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) - remote = gateway.api.sensors["1"] + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + "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 len(gateway.events) == 1 - assert len(remote._callbacks) == 2 # Event and battery tracker + assert not hass.states.get("sensor.switch_1_battery_level") - remote.update({"config": {"battery": 50}}) + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "config": {"battery": 50}, + } + await mock_deconz_websocket(data=event_changed_sensor) await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 - assert len(gateway.events) == 1 - assert len(remote._callbacks) == 2 # Event and battery entity - assert hass.states.get("sensor.switch_1_battery_level") + assert hass.states.get("sensor.switch_1_battery_level").state == "50" async def test_special_danfoss_battery_creation(hass, aioclient_mock): @@ -248,131 +280,133 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): Normally there should only be one battery sensor per device from deCONZ. With specific Danfoss devices each endpoint can report its own battery state. """ - data = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "1": { - "config": { - "battery": 70, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, - }, - "ep": 1, - "etag": "982d9acc38bee5b251e24a9be26558e4", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:07.994", - "on": False, - "temperature": 2307, - }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-01-0201", - }, - "2": { - "config": { - "battery": 86, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, - }, - "ep": 2, - "etag": "62f12749f9f51c950086aff37dd02b61", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:22.399", - "on": False, - "temperature": 2316, - }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-02-0201", - }, - "3": { - "config": { - "battery": 86, - "heatsetpoint": 2350, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, + data = { + "sensors": { + "1": { + "config": { + "battery": 70, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 1, + "etag": "982d9acc38bee5b251e24a9be26558e4", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:07.994", + "on": False, + "temperature": 2307, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-01-0201", }, - "ep": 3, - "etag": "f50061174bb7f18a3d95789bab8b646d", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:25.466", - "on": False, - "temperature": 2337, + "2": { + "config": { + "battery": 86, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 2, + "etag": "62f12749f9f51c950086aff37dd02b61", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:22.399", + "on": False, + "temperature": 2316, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-02-0201", }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-03-0201", - }, - "4": { - "config": { - "battery": 85, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, + "3": { + "config": { + "battery": 86, + "heatsetpoint": 2350, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 3, + "etag": "f50061174bb7f18a3d95789bab8b646d", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:25.466", + "on": False, + "temperature": 2337, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-03-0201", }, - "ep": 4, - "etag": "eea97adf8ce1b971b8b6a3a31793f96b", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": { - "lastupdated": "2021-02-15T12:23:41.939", - "on": False, - "temperature": 2333, + "4": { + "config": { + "battery": 85, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 4, + "etag": "eea97adf8ce1b971b8b6a3a31793f96b", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": { + "lastupdated": "2021-02-15T12:23:41.939", + "on": False, + "temperature": 2333, + }, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-04-0201", }, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-04-0201", - }, - "5": { - "config": { - "battery": 83, - "heatsetpoint": 2300, - "offset": 0, - "on": True, - "reachable": True, - "schedule": {}, - "schedule_on": False, + "5": { + "config": { + "battery": 83, + "heatsetpoint": 2300, + "offset": 0, + "on": True, + "reachable": True, + "schedule": {}, + "schedule_on": False, + }, + "ep": 5, + "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", + "lastseen": "2021-02-15T12:23Z", + "manufacturername": "Danfoss", + "modelid": "0x8030", + "name": "0x8030", + "state": {"lastupdated": "none", "on": False, "temperature": 2325}, + "swversion": "YYYYMMDD", + "type": "ZHAThermostat", + "uniqueid": "58:8e:81:ff:fe:00:11:22-05-0201", }, - "ep": 5, - "etag": "1f7cd1a5d66dc27ac5eb44b8c47362fb", - "lastseen": "2021-02-15T12:23Z", - "manufacturername": "Danfoss", - "modelid": "0x8030", - "name": "0x8030", - "state": {"lastupdated": "none", "on": False, "temperature": 2325}, - "swversion": "YYYYMMDD", - "type": "ZHAThermostat", - "uniqueid": "58:8e:81:ff:fe:00:11:22-05-0201", - }, + } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 10 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 5 @@ -380,77 +414,75 @@ async def test_special_danfoss_battery_creation(hass, aioclient_mock): async def test_air_quality_sensor(hass, aioclient_mock): """Test successful creation of air quality sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - 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", + 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", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 - - air_quality = hass.states.get("sensor.air_quality") - assert air_quality.state == "poor" + assert hass.states.get("sensor.air_quality").state == "poor" async def test_time_sensor(hass, aioclient_mock): """Test successful creation of time sensor entities.""" - data = deepcopy(DECONZ_WEB_REQUEST) - 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", + 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", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 - - time = hass.states.get("sensor.time") - assert time.state == "2020-11-19T08:07:08Z" - - time_battery = hass.states.get("sensor.time_battery_level") - assert time_battery.state == "40" + 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 = deepcopy(DECONZ_WEB_REQUEST) - data["sensors"] = { - "0": {"type": "not supported", "name": "name", "state": {}, "config": {}} + data = { + "sensors": { + "0": {"type": "not supported", "name": "name", "state": {}, "config": {}} + } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 1 - - unsupported_sensor = hass.states.get("sensor.name") - assert unsupported_sensor.state == "unknown" + assert hass.states.get("sensor.name").state == STATE_UNKNOWN From ad5dbebc03e6047f8afb3fa9802eab325545346f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:44:52 +0100 Subject: [PATCH 1350/1818] Use websocket fixture in deCONZ switch tests (#47831) Localize test data Improve asserts --- tests/components/deconz/test_switch.py | 131 +++++++++++-------------- 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 6aafac1bd4220a..cffdf07ae2bc25 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -1,8 +1,7 @@ """deCONZ switch platform tests.""" -from copy import deepcopy +from unittest.mock import patch -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -16,54 +15,6 @@ setup_deconz_integration, ) -POWER_PLUGS = { - "1": { - "id": "On off switch id", - "name": "On off switch", - "type": "On/Off plug-in unit", - "state": {"on": True, "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Smart plug id", - "name": "Smart plug", - "type": "Smart plug", - "state": {"on": False, "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "Unsupported switch id", - "name": "Unsupported switch", - "type": "Not a switch", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "4": { - "id": "On off relay id", - "name": "On off relay", - "state": {"on": True, "reachable": True}, - "type": "On/Off light", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - -SIRENS = { - "1": { - "id": "Warning device id", - "name": "Warning device", - "type": "Warning device", - "state": {"alert": "lselect", "reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "id": "Unsupported switch id", - "name": "Unsupported switch", - "type": "Not a switch", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, -} - async def test_no_switches(hass, aioclient_mock): """Test that no switch entities are created.""" @@ -71,14 +22,38 @@ async def test_no_switches(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_power_plugs(hass, aioclient_mock): +async def test_power_plugs(hass, aioclient_mock, mock_deconz_websocket): """Test that all supported switch entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(POWER_PLUGS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "On off switch", + "type": "On/Off plug-in unit", + "state": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Smart plug", + "type": "Smart plug", + "state": {"on": False, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "Unsupported switch", + "type": "Not a switch", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "4": { + "name": "On off relay", + "state": {"on": True, "reachable": True}, + "type": "On/Off light", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 4 assert hass.states.get("switch.on_off_switch").state == STATE_ON @@ -86,14 +61,15 @@ async def test_power_plugs(hass, aioclient_mock): assert hass.states.get("switch.on_off_relay").state == STATE_ON assert hass.states.get("switch.unsupported_switch") is None - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() assert hass.states.get("switch.on_off_switch").state == STATE_OFF @@ -124,7 +100,7 @@ async def test_power_plugs(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 4 + assert len(states) == 4 for state in states: assert state.state == STATE_UNAVAILABLE @@ -133,27 +109,40 @@ async def test_power_plugs(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_sirens(hass, aioclient_mock): +async def test_sirens(hass, aioclient_mock, mock_deconz_websocket): """Test that siren entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(SIRENS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Warning device", + "type": "Warning device", + "state": {"alert": "lselect", "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "name": "Unsupported switch", + "type": "Not a switch", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 2 assert hass.states.get("switch.warning_device").state == STATE_ON - assert hass.states.get("switch.unsupported_switch") is None + assert not hass.states.get("switch.unsupported_switch") - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"alert": None}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() assert hass.states.get("switch.warning_device").state == STATE_OFF @@ -184,7 +173,7 @@ async def test_sirens(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 2 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE From 8b8a54b36773831cf3fc56fed98863fe9d995a71 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 18 Mar 2021 11:57:56 +0100 Subject: [PATCH 1351/1818] Improve deCONZ services and scenes tests (#47829) Use patch.dict rather than deep copy to change DECONZ_WEB_REQUEST --- tests/components/deconz/test_scene.py | 34 +++-- tests/components/deconz/test_services.py | 170 ++++++++++++----------- 2 files changed, 105 insertions(+), 99 deletions(-) diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index 229111bf9ae215..189eb1e6eb7290 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -1,6 +1,6 @@ """deCONZ scene platform tests.""" -from copy import deepcopy +from unittest.mock import patch from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, SERVICE_TURN_ON from homeassistant.const import ATTR_ENTITY_ID @@ -11,18 +11,6 @@ setup_deconz_integration, ) -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": [], - } -} - async def test_no_scenes(hass, aioclient_mock): """Test that scenes can be loaded without scenes being available.""" @@ -32,11 +20,21 @@ async def test_no_scenes(hass, aioclient_mock): async def test_scenes(hass, aioclient_mock): """Test that scenes works.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + 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 assert hass.states.get("scene.light_group_scene") diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 35279572113c92..a631327351f83c 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -1,5 +1,4 @@ """deCONZ service tests.""" -from copy import deepcopy from unittest.mock import Mock, patch import pytest @@ -9,7 +8,6 @@ CONF_BRIDGE_ID, DOMAIN as DECONZ_DOMAIN, ) -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.deconz.services import ( DECONZ_SERVICES, SERVICE_CONFIGURE_DEVICE, @@ -33,50 +31,6 @@ setup_deconz_integration, ) -GROUP = { - "1": { - "id": "Group 1 id", - "name": "Group 1 name", - "type": "LightGroup", - "state": {}, - "action": {}, - "scenes": [{"id": "1", "name": "Scene 1"}], - "lights": ["1"], - } -} - -LIGHT = { - "1": { - "id": "Light 1 id", - "name": "Light 1 name", - "state": {"reachable": True}, - "type": "Light", - "uniqueid": "00:00:00:00:00:00:00:01-00", - } -} - -SENSOR = { - "1": { - "id": "Sensor 1 id", - "name": "Sensor 1 name", - "type": "ZHALightLevel", - "state": {"lightlevel": 30000, "dark": False}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - } -} - -SWITCH = { - "1": { - "id": "Switch 1 id", - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000, "gesture": 1}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, -} - async def test_service_setup(hass): """Verify service setup works.""" @@ -140,10 +94,19 @@ async def test_configure_service_with_field(hass, aioclient_mock): async def test_configure_service_with_entity(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Test", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway.deconz_ids["light.test"] = "/lights/1" data = { SERVICE_ENTITY: "light.test", SERVICE_DATA: {"on": True, "attr1": 10, "attr2": 20}, @@ -159,10 +122,19 @@ async def test_configure_service_with_entity(hass, aioclient_mock): async def test_configure_service_with_entity_and_field(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" - config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "lights": { + "1": { + "name": "Test", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway.deconz_ids["light.test"] = "/lights/1" data = { SERVICE_ENTITY: "light.test", SERVICE_FIELD: "/state", @@ -193,57 +165,93 @@ async def test_configure_service_with_faulty_field(hass, aioclient_mock): async def test_configure_service_with_faulty_entity(hass, aioclient_mock): """Test that service invokes pydeconz with the correct path and data.""" await setup_deconz_integration(hass, aioclient_mock) + aioclient_mock.clear_requests() data = { SERVICE_ENTITY: "light.nonexisting", SERVICE_DATA: {}, } - with patch("pydeconz.DeconzSession.request", return_value=Mock(True)) as put_state: - await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data - ) - await hass.async_block_till_done() - put_state.assert_not_called() + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data + ) + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 0 async def test_service_refresh_devices(hass, aioclient_mock): """Test that service can refresh devices.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + + assert len(hass.states.async_all()) == 0 + aioclient_mock.clear_requests() - data = {CONF_BRIDGE_ID: BRIDGEID} + data = { + "groups": { + "1": { + "id": "Group 1 id", + "name": "Group 1 name", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene 1"}], + "lights": ["1"], + } + }, + "lights": { + "1": { + "name": "Light 1 name", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + "sensors": { + "1": { + "name": "Sensor 1 name", + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + } + }, + } - mock_deconz_request( - aioclient_mock, - config_entry.data, - {"groups": GROUP, "lights": LIGHT, "sensors": SENSOR}, - ) + mock_deconz_request(aioclient_mock, config_entry.data, data) await hass.services.async_call( - DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data=data + DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data={CONF_BRIDGE_ID: BRIDGEID} ) await hass.async_block_till_done() - assert gateway.deconz_ids == { - "light.group_1_name": "/groups/1", - "light.light_1_name": "/lights/1", - "scene.group_1_name_scene_1": "/groups/1/scenes/1", - "sensor.sensor_1_name": "/sensors/1", - } + assert len(hass.states.async_all()) == 4 async def test_remove_orphaned_entries_service(hass, aioclient_mock): """Test service works and also don't remove more than expected.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = deepcopy(LIGHT) - data["sensors"] = deepcopy(SWITCH) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - - data = {CONF_BRIDGE_ID: BRIDGEID} + data = { + "lights": { + "1": { + "name": "Light 1 name", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000, "gesture": 1}, + "config": {"battery": 100}, + "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) device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( @@ -279,7 +287,7 @@ async def test_remove_orphaned_entries_service(hass, aioclient_mock): await hass.services.async_call( DECONZ_DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES, - service_data=data, + service_data={CONF_BRIDGE_ID: BRIDGEID}, ) await hass.async_block_till_done() From c8950870a2413c21457fe86ce62cb537095f01ed Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:25:00 +0100 Subject: [PATCH 1352/1818] Propagate RFLink 'send_command' event (#43588) * propagate send_command event * propagate send_command event --- homeassistant/components/rflink/__init__.py | 11 ++++++ tests/components/rflink/test_init.py | 44 +++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 68783c3426af3d..18f02d66a31f5d 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -67,6 +67,7 @@ SIGNAL_AVAILABILITY = "rflink_device_available" SIGNAL_HANDLE_EVENT = "rflink_handle_event_{}" +SIGNAL_EVENT = "rflink_event" TMP_ENTITY = "tmp.{}" @@ -140,6 +141,15 @@ async def async_send_command(call): ) ): _LOGGER.error("Failed Rflink command for %s", str(call.data)) + else: + async_dispatcher_send( + hass, + SIGNAL_EVENT, + { + EVENT_KEY_ID: call.data.get(CONF_DEVICE_ID), + EVENT_KEY_COMMAND: call.data.get(CONF_COMMAND), + }, + ) hass.services.async_register( DOMAIN, SERVICE_SEND_COMMAND, async_send_command, schema=SEND_COMMAND_SCHEMA @@ -293,6 +303,7 @@ async def connect(): _LOGGER.info("Connected to Rflink") hass.async_create_task(connect()) + async_dispatcher_connect(hass, SIGNAL_EVENT, event_callback) return True diff --git a/tests/components/rflink/test_init.py b/tests/components/rflink/test_init.py index 7ba90286e62d04..233170d8cd2c03 100644 --- a/tests/components/rflink/test_init.py +++ b/tests/components/rflink/test_init.py @@ -196,6 +196,50 @@ async def test_send_command_invalid_arguments(hass, monkeypatch): assert not success, "send command should not succeed for unknown command" +async def test_send_command_event_propagation(hass, monkeypatch): + """Test event propagation for send_command service.""" + domain = "light" + config = { + "rflink": {"port": "/dev/ttyABC0"}, + domain: { + "platform": "rflink", + "devices": { + "protocol_0_1": {"name": "test1"}, + }, + }, + } + + # setup mocking rflink module + _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) + + # default value = 'off' + assert hass.states.get(f"{domain}.test1").state == "off" + + hass.async_create_task( + hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "on"}, + ) + ) + await hass.async_block_till_done() + assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_1" + assert protocol.send_command_ack.call_args_list[0][0][1] == "on" + assert hass.states.get(f"{domain}.test1").state == "on" + + hass.async_create_task( + hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "alloff"}, + ) + ) + await hass.async_block_till_done() + assert protocol.send_command_ack.call_args_list[1][0][0] == "protocol_0_1" + assert protocol.send_command_ack.call_args_list[1][0][1] == "alloff" + assert hass.states.get(f"{domain}.test1").state == "off" + + async def test_reconnecting_after_disconnect(hass, monkeypatch): """An unexpected disconnect should cause a reconnect.""" domain = "sensor" From 00dca88024fa5cf6b25fa9220c17d4e16e5dd25a Mon Sep 17 00:00:00 2001 From: Andreas <28764847+andreas-amlabs@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:32:08 +0100 Subject: [PATCH 1353/1818] Amcrest add support for CrossLineDetection (#44582) Co-authored-by: andreas-amlabs --- 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 649258c42c7ab5..0824021f31e82b 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -38,6 +38,8 @@ BINARY_SENSOR_MOTION_DETECTED = "motion_detected" BINARY_SENSOR_MOTION_DETECTED_POLLED = "motion_detected_polled" BINARY_SENSOR_ONLINE = "online" +BINARY_SENSOR_CROSSLINE_DETECTED = "crossline_detected" +BINARY_SENSOR_CROSSLINE_DETECTED_POLLED = "crossline_detected_polled" BINARY_POLLED_SENSORS = [ BINARY_SENSOR_AUDIO_DETECTED_POLLED, BINARY_SENSOR_MOTION_DETECTED_POLLED, @@ -45,11 +47,18 @@ ] _AUDIO_DETECTED_PARAMS = ("Audio Detected", DEVICE_CLASS_SOUND, "AudioMutation") _MOTION_DETECTED_PARAMS = ("Motion Detected", DEVICE_CLASS_MOTION, "VideoMotion") +_CROSSLINE_DETECTED_PARAMS = ( + "CrossLine Detected", + DEVICE_CLASS_MOTION, + "CrossLineDetection", +) BINARY_SENSORS = { BINARY_SENSOR_AUDIO_DETECTED: _AUDIO_DETECTED_PARAMS, BINARY_SENSOR_AUDIO_DETECTED_POLLED: _AUDIO_DETECTED_PARAMS, BINARY_SENSOR_MOTION_DETECTED: _MOTION_DETECTED_PARAMS, BINARY_SENSOR_MOTION_DETECTED_POLLED: _MOTION_DETECTED_PARAMS, + BINARY_SENSOR_CROSSLINE_DETECTED: _CROSSLINE_DETECTED_PARAMS, + BINARY_SENSOR_CROSSLINE_DETECTED_POLLED: _CROSSLINE_DETECTED_PARAMS, BINARY_SENSOR_ONLINE: ("Online", DEVICE_CLASS_CONNECTIVITY, None), } BINARY_SENSORS = { @@ -58,6 +67,7 @@ } _EXCLUSIVE_OPTIONS = [ {BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSOR_MOTION_DETECTED_POLLED}, + {BINARY_SENSOR_CROSSLINE_DETECTED, BINARY_SENSOR_CROSSLINE_DETECTED_POLLED}, ] _UPDATE_MSG = "Updating %s binary sensor" From 25a13d1554a318f53c0fef9b680fe12aacd6bfcd Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:07:04 +0100 Subject: [PATCH 1354/1818] Update typing 10 (#48071) --- .../components/jewish_calendar/__init__.py | 6 +-- .../components/keenetic_ndms2/config_flow.py | 4 +- .../keenetic_ndms2/device_tracker.py | 11 ++-- .../components/keenetic_ndms2/router.py | 14 ++--- homeassistant/components/knx/__init__.py | 5 +- homeassistant/components/knx/binary_sensor.py | 6 ++- homeassistant/components/knx/climate.py | 10 ++-- homeassistant/components/knx/expose.py | 6 +-- homeassistant/components/knx/factory.py | 4 +- homeassistant/components/knx/fan.py | 12 +++-- homeassistant/components/knx/notify.py | 4 +- .../components/kodi/device_trigger.py | 4 +- homeassistant/components/kulersky/light.py | 6 ++- .../components/launch_library/sensor.py | 7 +-- homeassistant/components/light/__init__.py | 52 +++++++++---------- .../components/light/device_action.py | 4 +- .../components/light/device_condition.py | 4 +- .../components/light/device_trigger.py | 4 +- .../components/light/reproduce_state.py | 14 ++--- .../components/light/significant_change.py | 6 ++- .../components/litejet/config_flow.py | 8 +-- homeassistant/components/litterrobot/hub.py | 6 ++- .../components/litterrobot/sensor.py | 4 +- homeassistant/components/locative/__init__.py | 5 +- .../components/lock/device_action.py | 6 +-- .../components/lock/device_condition.py | 4 +- .../components/lock/device_trigger.py | 4 +- .../components/lock/reproduce_state.py | 12 +++-- .../components/lock/significant_change.py | 6 ++- .../components/lovelace/resources.py | 8 +-- .../lutron_caseta/device_trigger.py | 4 +- homeassistant/components/lutron_caseta/fan.py | 5 +- homeassistant/components/lyric/__init__.py | 8 +-- homeassistant/components/lyric/climate.py | 17 +++--- .../components/media_player/__init__.py | 25 +++++---- .../media_player/device_condition.py | 4 +- .../components/media_player/device_trigger.py | 4 +- .../media_player/reproduce_state.py | 12 +++-- .../components/media_source/__init__.py | 5 +- .../components/media_source/local_source.py | 7 +-- .../components/media_source/models.py | 9 ++-- homeassistant/components/melcloud/__init__.py | 8 +-- homeassistant/components/melcloud/climate.py | 34 ++++++------ .../components/melcloud/config_flow.py | 7 +-- .../components/melcloud/water_heater.py | 14 ++--- homeassistant/components/met/config_flow.py | 8 +-- .../components/minecraft_server/__init__.py | 5 +- .../components/minecraft_server/helpers.py | 5 +- .../components/minecraft_server/sensor.py | 6 ++- homeassistant/components/minio/__init__.py | 5 +- .../components/minio/minio_helper.py | 8 +-- .../components/mobile_app/device_action.py | 6 +-- .../components/mobile_app/helpers.py | 22 ++++---- .../components/mobile_app/http_api.py | 5 +- homeassistant/components/mobile_app/util.py | 8 +-- .../components/modbus/binary_sensor.py | 4 +- homeassistant/components/modbus/climate.py | 10 ++-- homeassistant/components/modbus/cover.py | 14 ++--- homeassistant/components/modbus/sensor.py | 8 +-- homeassistant/components/modbus/switch.py | 12 +++-- homeassistant/components/mqtt/__init__.py | 12 +++-- .../components/mqtt/device_trigger.py | 10 ++-- homeassistant/components/mqtt/mixins.py | 5 +- homeassistant/components/mqtt/models.py | 8 +-- homeassistant/components/mqtt/sensor.py | 5 +- homeassistant/components/mqtt/subscription.py | 10 ++-- .../components/mysensors/__init__.py | 26 +++++----- .../components/mysensors/config_flow.py | 34 ++++++------ homeassistant/components/mysensors/const.py | 28 +++++----- homeassistant/components/mysensors/device.py | 8 +-- homeassistant/components/mysensors/gateway.py | 24 +++++---- homeassistant/components/mysensors/handler.py | 8 +-- homeassistant/components/mysensors/helpers.py | 32 ++++++------ 73 files changed, 412 insertions(+), 333 deletions(-) diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py index d1474c3cf5fd37..35c1505561dba0 100644 --- a/homeassistant/components/jewish_calendar/__init__.py +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -1,5 +1,5 @@ """The jewish_calendar component.""" -from typing import Optional +from __future__ import annotations import hdate import voluptuous as vol @@ -78,8 +78,8 @@ def get_unique_prefix( location: hdate.Location, language: str, - candle_lighting_offset: Optional[int], - havdalah_offset: Optional[int], + candle_lighting_offset: int | None, + havdalah_offset: int | None, ) -> str: """Create a prefix for unique ids.""" config_properties = [ diff --git a/homeassistant/components/keenetic_ndms2/config_flow.py b/homeassistant/components/keenetic_ndms2/config_flow.py index 9338cb05935e90..a832f68e01762a 100644 --- a/homeassistant/components/keenetic_ndms2/config_flow.py +++ b/homeassistant/components/keenetic_ndms2/config_flow.py @@ -1,5 +1,5 @@ """Config flow for Keenetic NDMS2.""" -from typing import List +from __future__ import annotations from ndms2_client import Client, ConnectionException, InterfaceInfo, TelnetConnection import voluptuous as vol @@ -103,7 +103,7 @@ async def async_step_init(self, _user_input=None): ROUTER ] - interfaces: List[InterfaceInfo] = await self.hass.async_add_executor_job( + interfaces: list[InterfaceInfo] = await self.hass.async_add_executor_job( router.client.get_interfaces ) diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index a8b0f943dd6f58..461814b19170d8 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -1,7 +1,8 @@ """Support for Keenetic routers as device tracker.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List, Optional, Set from ndms2_client import Device import voluptuous as vol @@ -57,8 +58,8 @@ async def async_get_scanner(hass: HomeAssistant, config): """Import legacy configuration from YAML.""" scanner_config = config[DEVICE_TRACKER_DOMAIN] - scan_interval: Optional[timedelta] = scanner_config.get(CONF_SCAN_INTERVAL) - consider_home: Optional[timedelta] = scanner_config.get(CONF_CONSIDER_HOME) + scan_interval: timedelta | None = scanner_config.get(CONF_SCAN_INTERVAL) + consider_home: timedelta | None = scanner_config.get(CONF_CONSIDER_HOME) host: str = scanner_config[CONF_HOST] hass.data[DOMAIN][f"imported_options_{host}"] = { @@ -139,9 +140,9 @@ def update_from_router(): @callback -def update_items(router: KeeneticRouter, async_add_entities, tracked: Set[str]): +def update_items(router: KeeneticRouter, async_add_entities, tracked: set[str]): """Update tracked device state from the hub.""" - new_tracked: List[KeeneticTracker] = [] + new_tracked: list[KeeneticTracker] = [] for mac, device in router.last_devices.items(): if mac not in tracked: tracked.add(mac) diff --git a/homeassistant/components/keenetic_ndms2/router.py b/homeassistant/components/keenetic_ndms2/router.py index 340b25ff725530..0066b49223b4d7 100644 --- a/homeassistant/components/keenetic_ndms2/router.py +++ b/homeassistant/components/keenetic_ndms2/router.py @@ -1,7 +1,9 @@ """The Keenetic Client class.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, Dict, Optional +from typing import Callable from ndms2_client import Client, ConnectionException, Device, TelnetConnection from ndms2_client.client import RouterInfo @@ -39,11 +41,11 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry): """Initialize the Client.""" self.hass = hass self.config_entry = config_entry - self._last_devices: Dict[str, Device] = {} - self._router_info: Optional[RouterInfo] = None - self._connection: Optional[TelnetConnection] = None - self._client: Optional[Client] = None - self._cancel_periodic_update: Optional[Callable] = None + self._last_devices: dict[str, Device] = {} + self._router_info: RouterInfo | None = None + self._connection: TelnetConnection | None = None + self._client: Client | None = None + self._cancel_periodic_update: Callable | None = None self._available = False self._progress = None diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index e05c18e5d5ce3e..348eac8f40e84a 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -1,7 +1,8 @@ """Support KNX devices.""" +from __future__ import annotations + import asyncio import logging -from typing import Union import voluptuous as vol from xknx import XKNX @@ -466,7 +467,7 @@ async def service_send_to_knx_bus(self, call): attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) - payload: Union[DPTBinary, DPTArray] + payload: DPTBinary | DPTArray if attr_type is not None: transcoder = DPTBase.parse_transcoder(attr_type) if transcoder is None: diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index ecb79664afdd9b..6ee37abee181ca 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,5 +1,7 @@ """Support for KNX/IP binary sensors.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from xknx.devices import BinarySensor as XknxBinarySensor @@ -38,7 +40,7 @@ def is_on(self): return self._device.is_on() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return device specific state attributes.""" if self._device.counter is not None: return {ATTR_COUNTER: self._device.counter} diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 565c41298a39b1..e90371e3282a49 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,5 +1,5 @@ """Support for KNX/IP climate devices.""" -from typing import List, Optional +from __future__ import annotations from xknx.devices import Climate as XknxClimate from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode @@ -88,7 +88,7 @@ async def async_set_temperature(self, **kwargs) -> None: self.async_write_ha_state() @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF @@ -100,7 +100,7 @@ def hvac_mode(self) -> Optional[str]: return HVAC_MODE_HEAT @property - def hvac_modes(self) -> Optional[List[str]]: + def hvac_modes(self) -> list[str] | None: """Return the list of available operation/controller modes.""" _controller_modes = [ CONTROLLER_MODES.get(controller_mode.value) @@ -131,7 +131,7 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: self.async_write_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -141,7 +141,7 @@ def preset_mode(self) -> Optional[str]: return None @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 3a010810862308..8bdd3d1d1d14db 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -1,5 +1,5 @@ """Exposures to KNX bus.""" -from typing import Union +from __future__ import annotations from xknx import XKNX from xknx.devices import DateTime, ExposeSensor @@ -22,7 +22,7 @@ @callback def create_knx_exposure( hass: HomeAssistant, xknx: XKNX, config: ConfigType -) -> Union["KNXExposeSensor", "KNXExposeTime"]: +) -> KNXExposeSensor | KNXExposeTime: """Create exposures from config.""" address = config[KNX_ADDRESS] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) @@ -30,7 +30,7 @@ def create_knx_exposure( expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) - exposure: Union["KNXExposeSensor", "KNXExposeTime"] + exposure: KNXExposeSensor | KNXExposeTime if expose_type.lower() in ["time", "date", "datetime"]: exposure = KNXExposeTime(xknx, expose_type, address) else: diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 893ac1e55e32ef..0543119cadf695 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -1,5 +1,5 @@ """Factory function to initialize KNX devices from config.""" -from typing import Optional, Tuple +from __future__ import annotations from xknx import XKNX from xknx.devices import ( @@ -95,7 +95,7 @@ def _create_cover(knx_module: XKNX, config: ConfigType) -> XknxCover: def _create_light_color( color: str, config: ConfigType -) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]: +) -> tuple[str | None, str | None, str | None, str | None]: """Load color configuration from configuration structure.""" if "individual_colors" in config and color in config["individual_colors"]: sub_config = config["individual_colors"][color] diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index 43d1cd7d6f20d8..2d9f48fe8049c1 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -1,6 +1,8 @@ """Support for KNX/IP fans.""" +from __future__ import annotations + import math -from typing import Any, Optional +from typing import Any from xknx.devices import Fan as XknxFan from xknx.devices.fan import FanSpeedMode @@ -58,7 +60,7 @@ def supported_features(self) -> int: return flags @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed as a percentage.""" if self._device.current_speed is None: return None @@ -78,9 +80,9 @@ def speed_count(self) -> int: async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs, ) -> None: """Turn on the fan.""" diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 7210795bd71ab8..9d6ed35e36b9cb 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,5 +1,5 @@ """Support for KNX/IP notification services.""" -from typing import List +from __future__ import annotations from xknx.devices import Notification as XknxNotification @@ -22,7 +22,7 @@ async def async_get_service(hass, config, discovery_info=None): class KNXNotificationService(BaseNotificationService): """Implement demo notification service.""" - def __init__(self, devices: List[XknxNotification]): + def __init__(self, devices: list[XknxNotification]): """Initialize the service.""" self.devices = devices diff --git a/homeassistant/components/kodi/device_trigger.py b/homeassistant/components/kodi/device_trigger.py index 314f73a927e452..3454fc122ed4bf 100644 --- a/homeassistant/components/kodi/device_trigger.py +++ b/homeassistant/components/kodi/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Kodi.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -29,7 +29,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Kodi devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 9098975d500133..599f99a83e84d9 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -1,7 +1,9 @@ """Kuler Sky light platform.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, List +from typing import Callable import pykulersky @@ -34,7 +36,7 @@ async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Kuler sky light devices.""" if DOMAIN not in hass.data: diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 366cd4e7d442c3..7663bf86fc51d9 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -1,7 +1,8 @@ """A sensor platform that give you information about the next space launch.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from pylaunches import PyLaunches, PyLaunchesException import voluptuous as vol @@ -64,7 +65,7 @@ def name(self) -> str: return self._name @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" if self.next_launch: return self.next_launch.name @@ -76,7 +77,7 @@ def icon(self) -> str: return "mdi:rocket" @property - def extra_state_attributes(self) -> Optional[dict]: + def extra_state_attributes(self) -> dict | None: """Return attributes for the sensor.""" if self.next_launch: return { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 56fd5841388d47..c2e2fdbeaa9449 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -6,7 +6,7 @@ from datetime import timedelta import logging import os -from typing import Dict, List, Optional, Set, Tuple, cast +from typing import cast import voluptuous as vol @@ -364,11 +364,11 @@ class Profile: """Representation of a profile.""" name: str - color_x: Optional[float] = dataclasses.field(repr=False) - color_y: Optional[float] = dataclasses.field(repr=False) - brightness: Optional[int] - transition: Optional[int] = None - hs_color: Optional[Tuple[float, float]] = dataclasses.field(init=False) + color_x: float | None = dataclasses.field(repr=False) + color_y: float | None = dataclasses.field(repr=False) + brightness: int | None + transition: int | None = None + hs_color: tuple[float, float] | None = dataclasses.field(init=False) SCHEMA = vol.Schema( # pylint: disable=invalid-name vol.Any( @@ -403,7 +403,7 @@ def __post_init__(self) -> None: ) @classmethod - def from_csv_row(cls, csv_row: List[str]) -> Profile: + def from_csv_row(cls, csv_row: list[str]) -> Profile: """Create profile from a CSV row tuple.""" return cls(*cls.SCHEMA(csv_row)) @@ -414,9 +414,9 @@ class Profiles: def __init__(self, hass: HomeAssistantType): """Initialize profiles.""" self.hass = hass - self.data: Dict[str, Profile] = {} + self.data: dict[str, Profile] = {} - def _load_profile_data(self) -> Dict[str, Profile]: + def _load_profile_data(self) -> dict[str, Profile]: """Load built-in profiles and custom profiles.""" profile_paths = [ os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), @@ -453,7 +453,7 @@ async def async_initialize(self) -> None: self.data = await self.hass.async_add_executor_job(self._load_profile_data) @callback - def apply_default(self, entity_id: str, params: Dict) -> None: + def apply_default(self, entity_id: str, params: dict) -> None: """Return the default turn-on profile for the given light.""" for _entity_id in (entity_id, "group.all_lights"): name = f"{_entity_id}.default" @@ -462,7 +462,7 @@ def apply_default(self, entity_id: str, params: Dict) -> None: return @callback - def apply_profile(self, name: str, params: Dict) -> None: + def apply_profile(self, name: str, params: dict) -> None: """Apply a profile.""" profile = self.data.get(name) @@ -481,12 +481,12 @@ class LightEntity(ToggleEntity): """Representation of a light.""" @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" return None @property - def color_mode(self) -> Optional[str]: + def color_mode(self) -> str | None: """Return the color mode of the light.""" return None @@ -519,27 +519,27 @@ def _light_internal_color_mode(self) -> str: return color_mode @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" return None @property - def xy_color(self) -> Optional[Tuple[float, float]]: + def xy_color(self) -> tuple[float, float] | None: """Return the xy color value [float, float].""" return None @property - def rgb_color(self) -> Optional[Tuple[int, int, int]]: + def rgb_color(self) -> tuple[int, int, int] | None: """Return the rgb color value [int, int, int].""" return None @property - def rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + def rgbw_color(self) -> tuple[int, int, int, int] | None: """Return the rgbw color value [int, int, int, int].""" return None @property - def _light_internal_rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: + def _light_internal_rgbw_color(self) -> tuple[int, int, int, int] | None: """Return the rgbw color value [int, int, int, int].""" rgbw_color = self.rgbw_color if ( @@ -558,12 +558,12 @@ def _light_internal_rgbw_color(self) -> Optional[Tuple[int, int, int, int]]: return rgbw_color @property - def rgbww_color(self) -> Optional[Tuple[int, int, int, int, int]]: + def rgbww_color(self) -> tuple[int, int, int, int, int] | None: """Return the rgbww color value [int, int, int, int, int].""" return None @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" return None @@ -582,17 +582,17 @@ def max_mireds(self) -> int: return 500 @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" return None @property - def effect_list(self) -> Optional[List[str]]: + def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" return None @property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect.""" return None @@ -616,7 +616,7 @@ def capability_attributes(self): return data def _light_internal_convert_color(self, color_mode: str) -> dict: - data: Dict[str, Tuple] = {} + data: dict[str, tuple] = {} if color_mode == COLOR_MODE_HS and self.hs_color: hs_color = self.hs_color data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) @@ -692,7 +692,7 @@ def state_attributes(self): return {key: val for key, val in data.items() if val is not None} @property - def _light_internal_supported_color_modes(self) -> Set: + def _light_internal_supported_color_modes(self) -> set: """Calculate supported color modes with backwards compatibility.""" supported_color_modes = self.supported_color_modes @@ -717,7 +717,7 @@ def _light_internal_supported_color_modes(self) -> Set: return supported_color_modes @property - def supported_color_modes(self) -> Optional[Set]: + def supported_color_modes(self) -> set | None: """Flag supported color modes.""" return None diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index d499bc0c2a2738..4c37647f1683b3 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for lights.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -78,7 +78,7 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/device_condition.py b/homeassistant/components/light/device_condition.py index 1d9323907f2048..7396ddeea3165c 100644 --- a/homeassistant/components/light/device_condition.py +++ b/homeassistant/components/light/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for lights.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ def async_condition_from_config( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/device_trigger.py b/homeassistant/components/light/device_trigger.py index 066d1f4c0205b7..e1b1412483167e 100644 --- a/homeassistant/components/light/device_trigger.py +++ b/homeassistant/components/light/device_trigger.py @@ -1,5 +1,5 @@ """Provides device trigger for lights.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index 863790cff71d04..13a014426a07a0 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -1,8 +1,10 @@ """Reproduce an Light state.""" +from __future__ import annotations + import asyncio import logging from types import MappingProxyType -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -95,8 +97,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -123,7 +125,7 @@ async def _async_reproduce_state( ): return - service_data: Dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id} + service_data: dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id} if reproduce_options is not None and ATTR_TRANSITION in reproduce_options: service_data[ATTR_TRANSITION] = reproduce_options[ATTR_TRANSITION] @@ -171,8 +173,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Light states.""" await asyncio.gather( diff --git a/homeassistant/components/light/significant_change.py b/homeassistant/components/light/significant_change.py index a0bd5203101d42..9e0f10fae474bf 100644 --- a/homeassistant/components/light/significant_change.py +++ b/homeassistant/components/light/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Light state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.significant_change import ( @@ -24,7 +26,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if old_state != new_state: return True diff --git a/homeassistant/components/litejet/config_flow.py b/homeassistant/components/litejet/config_flow.py index e1c7d8ab7b93bb..124b229c78600f 100644 --- a/homeassistant/components/litejet/config_flow.py +++ b/homeassistant/components/litejet/config_flow.py @@ -1,6 +1,8 @@ """Config flow for the LiteJet lighting system.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any import pylitejet from serial import SerialException @@ -18,8 +20,8 @@ class LiteJetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """LiteJet config flow.""" async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Create a LiteJet config entry based upon user input.""" if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 1847f37ed6490a..86c3aff5462cab 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -1,8 +1,10 @@ """A wrapper 'hub' for the Litter-Robot API and base entity for common attributes.""" +from __future__ import annotations + from datetime import time, timedelta import logging from types import MethodType -from typing import Any, Optional +from typing import Any import pylitterbot from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException @@ -106,7 +108,7 @@ async def async_call_later_callback(*_) -> None: async_call_later(self.hass, REFRESH_WAIT_TIME, async_call_later_callback) @staticmethod - def parse_time_at_default_timezone(time_str: str) -> Optional[time]: + def parse_time_at_default_timezone(time_str: str) -> time | None: """Parse a time string and add default timezone.""" parsed_time = dt_util.parse_time(time_str) diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 8900c6c54ca80b..8ae512fa801f47 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,5 +1,5 @@ """Support for Litter-Robot sensors.""" -from typing import Optional +from __future__ import annotations from pylitterbot.robot import Robot @@ -10,7 +10,7 @@ from .hub import LitterRobotEntity, LitterRobotHub -def icon_for_gauge_level(gauge_level: Optional[int] = None, offset: int = 0) -> str: +def icon_for_gauge_level(gauge_level: int | None = None, offset: int = 0) -> str: """Return a gauge icon valid identifier.""" if gauge_level is None or gauge_level <= 0 + offset: return "mdi:gauge-empty" diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index 28af822ae63412..bb2a19c6380191 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -1,6 +1,7 @@ """Support for Locative.""" +from __future__ import annotations + import logging -from typing import Dict from aiohttp import web import voluptuous as vol @@ -34,7 +35,7 @@ def _id(value: str) -> str: return value.replace("-", "") -def _validate_test_mode(obj: Dict) -> Dict: +def _validate_test_mode(obj: dict) -> dict: """Validate that id is provided outside of test mode.""" if ATTR_ID not in obj and obj[ATTR_TRIGGER] != "test": raise vol.Invalid("Location id not specified") diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index efdb5e352cfe31..639947f3b88a99 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Lock.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -30,7 +30,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Lock devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -75,7 +75,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/lock/device_condition.py b/homeassistant/components/lock/device_condition.py index a25018dc70981d..0fae680f8293f1 100644 --- a/homeassistant/components/lock/device_condition.py +++ b/homeassistant/components/lock/device_condition.py @@ -1,5 +1,5 @@ """Provides device automations for Lock.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -30,7 +30,7 @@ ) -async def async_get_conditions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device conditions for Lock devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 05d5041ca65001..3e5bee49a22695 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Lock.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -31,7 +31,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Lock devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/lock/reproduce_state.py b/homeassistant/components/lock/reproduce_state.py index 812b9bf04df9b1..0d575964b2bd3e 100644 --- a/homeassistant/components/lock/reproduce_state.py +++ b/homeassistant/components/lock/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Lock state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Lock states.""" await asyncio.gather( diff --git a/homeassistant/components/lock/significant_change.py b/homeassistant/components/lock/significant_change.py index 59a3b1a95c5a63..172bf2559c5a6d 100644 --- a/homeassistant/components/lock/significant_change.py +++ b/homeassistant/components/lock/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Lock state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,7 +14,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if old_state != new_state: return True diff --git a/homeassistant/components/lovelace/resources.py b/homeassistant/components/lovelace/resources.py index 0a3e36892d5015..6a97d5c4192136 100644 --- a/homeassistant/components/lovelace/resources.py +++ b/homeassistant/components/lovelace/resources.py @@ -1,6 +1,8 @@ """Lovelace resources support.""" +from __future__ import annotations + import logging -from typing import List, Optional, cast +from typing import Optional, cast import uuid import voluptuous as vol @@ -38,7 +40,7 @@ async def async_get_info(self): return {"resources": len(self.async_items() or [])} @callback - def async_items(self) -> List[dict]: + def async_items(self) -> list[dict]: """Return list of items in collection.""" return self.data @@ -66,7 +68,7 @@ async def async_get_info(self): return {"resources": len(self.async_items() or [])} - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data.""" data = await self.store.async_load() diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index 86ee5e46b51235..230301c12f2ef8 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for lutron caseta.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -225,7 +225,7 @@ async def async_validate_trigger_config(hass: HomeAssistant, config: ConfigType) return schema(config) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for lutron caseta devices.""" triggers = [] diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index de2b2a2ae8ca01..edca88c10fcc49 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -1,6 +1,7 @@ """Support for Lutron Caseta fans.""" +from __future__ import annotations + import logging -from typing import Optional from pylutron_caseta import FAN_HIGH, FAN_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_OFF @@ -42,7 +43,7 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """Representation of a Lutron Caseta fan. Including Fan Speed.""" @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._device["fan_speed"] is None: return None diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index 8b23d6f85cf7a0..8536fa03e8a985 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -1,8 +1,10 @@ """The Honeywell Lyric integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict, Optional +from typing import Any from aiolyric import Lyric from aiolyric.objects.device import LyricDevice @@ -147,7 +149,7 @@ def __init__( device: LyricDevice, key: str, name: str, - icon: Optional[str], + icon: str | None, ) -> None: """Initialize the Honeywell Lyric entity.""" super().__init__(coordinator) @@ -190,7 +192,7 @@ class LyricDeviceEntity(LyricEntity): """Defines a Honeywell Lyric device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Honeywell Lyric instance.""" return { "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac_id)}, diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 4338a6e92cce86..0e3672f952ed8a 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -1,7 +1,8 @@ """Support for Honeywell Lyric climate platform.""" +from __future__ import annotations + import logging from time import gmtime, strftime, time -from typing import List, Optional from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation @@ -162,7 +163,7 @@ def temperature_unit(self) -> str: return self._temperature_unit @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.device.indoorTemperature @@ -180,12 +181,12 @@ def hvac_mode(self) -> str: return HVAC_MODES[self.device.changeableValues.mode] @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """List of available hvac modes.""" return self._hvac_modes @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" device = self.device if not device.hasDualSetpointStatus: @@ -193,7 +194,7 @@ def target_temperature(self) -> Optional[float]: return None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the upper bound temperature we try to reach.""" device = self.device if device.hasDualSetpointStatus: @@ -201,7 +202,7 @@ def target_temperature_low(self) -> Optional[float]: return None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the upper bound temperature we try to reach.""" device = self.device if device.hasDualSetpointStatus: @@ -209,12 +210,12 @@ def target_temperature_high(self) -> Optional[float]: return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return current preset mode.""" return self.device.changeableValues.thermostatSetpointStatus @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return preset modes.""" return [ PRESET_NO_HOLD, diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 87ecff7a54cd4b..1e0a9b9f6bb3ac 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -9,7 +9,6 @@ import hashlib import logging import secrets -from typing import List, Optional, Tuple from urllib.parse import urlparse from aiohttp import web @@ -355,7 +354,7 @@ async def async_unload_entry(hass, entry): class MediaPlayerEntity(Entity): """ABC for media player entities.""" - _access_token: Optional[str] = None + _access_token: str | None = None # Implement these for your media player @property @@ -439,8 +438,8 @@ async def async_get_browse_image( self, media_content_type: str, media_content_id: str, - media_image_id: Optional[str] = None, - ) -> Tuple[Optional[str], Optional[str]]: + media_image_id: str | None = None, + ) -> tuple[str | None, str | None]: """ Optionally fetch internally accessible image for media browser. @@ -851,8 +850,8 @@ def state_attributes(self): async def async_browse_media( self, - media_content_type: Optional[str] = None, - media_content_id: Optional[str] = None, + media_content_type: str | None = None, + media_content_id: str | None = None, ) -> BrowseMedia: """Return a BrowseMedia instance. @@ -914,7 +913,7 @@ def get_browse_image_url( self, media_content_type: str, media_content_id: str, - media_image_id: Optional[str] = None, + media_image_id: str | None = None, ) -> str: """Generate an url for a media browser image.""" url_path = ( @@ -947,8 +946,8 @@ async def get( self, request: web.Request, entity_id: str, - media_content_type: Optional[str] = None, - media_content_id: Optional[str] = None, + media_content_type: str | None = None, + media_content_id: str | None = None, ) -> web.Response: """Start a get request.""" player = self.component.get_entity(entity_id) @@ -1047,7 +1046,7 @@ async def websocket_browse_media(hass, connection, msg): To use, media_player integrations can implement MediaPlayerEntity.async_browse_media() """ component = hass.data[DOMAIN] - player: Optional[MediaPlayerDevice] = component.get_entity(msg["entity_id"]) + player: MediaPlayerDevice | None = component.get_entity(msg["entity_id"]) if player is None: connection.send_error(msg["id"], "entity_not_found", "Entity not found") @@ -1119,9 +1118,9 @@ def __init__( title: str, can_play: bool, can_expand: bool, - children: Optional[List["BrowseMedia"]] = None, - children_media_class: Optional[str] = None, - thumbnail: Optional[str] = None, + children: list[BrowseMedia] | None = None, + children_media_class: str | None = None, + thumbnail: str | None = None, ): """Initialize browse media item.""" self.media_class = media_class diff --git a/homeassistant/components/media_player/device_condition.py b/homeassistant/components/media_player/device_condition.py index 6faa6521b707b3..0e6e0f96c403b3 100644 --- a/homeassistant/components/media_player/device_condition.py +++ b/homeassistant/components/media_player/device_condition.py @@ -1,5 +1,5 @@ """Provides device automations for Media player.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -35,7 +35,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Media player devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index 6db5f16cf01a60..03c165412e92cd 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Media player.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -34,7 +34,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Media player entities.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index 64955d1913ba30..1707109197f83f 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -1,6 +1,8 @@ """Module that groups code required to handle state restore for component.""" +from __future__ import annotations + import asyncio -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( SERVICE_MEDIA_PAUSE, @@ -40,8 +42,8 @@ async def _async_reproduce_states( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" @@ -104,8 +106,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" await asyncio.gather( diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 3dff949d5dd6ad..6aa01403a5f1f6 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -1,6 +1,7 @@ """The media_source integration.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional import voluptuous as vol @@ -54,7 +55,7 @@ async def _process_media_source_platform(hass, domain, platform): @callback def _get_media_item( - hass: HomeAssistant, media_content_id: Optional[str] + hass: HomeAssistant, media_content_id: str | None ) -> models.MediaSourceItem: """Return media item.""" if media_content_id: diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index fa62ba48c5f1d1..fb5e9094dfb21c 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -1,7 +1,8 @@ """Local Media Source Implementation.""" +from __future__ import annotations + import mimetypes from pathlib import Path -from typing import Tuple from aiohttp import web @@ -40,7 +41,7 @@ def async_full_path(self, source_dir_id, location) -> Path: return Path(self.hass.config.media_dirs[source_dir_id], location) @callback - def async_parse_identifier(self, item: MediaSourceItem) -> Tuple[str, str]: + def async_parse_identifier(self, item: MediaSourceItem) -> tuple[str, str]: """Parse identifier.""" if not item.identifier: # Empty source_dir_id and location @@ -69,7 +70,7 @@ async def async_resolve_media(self, item: MediaSourceItem) -> str: return PlayMedia(f"/media/{item.identifier}", mime_type) async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES + self, item: MediaSourceItem, media_types: tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" try: diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index 98b817344d9caf..aa17fff320efe1 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -3,7 +3,6 @@ from abc import ABC from dataclasses import dataclass -from typing import List, Optional, Tuple from homeassistant.components.media_player import BrowseMedia from homeassistant.components.media_player.const import ( @@ -28,9 +27,9 @@ class PlayMedia: class BrowseMediaSource(BrowseMedia): """Represent a browsable media file.""" - children: Optional[List["BrowseMediaSource"]] + children: list[BrowseMediaSource] | None - def __init__(self, *, domain: Optional[str], identifier: Optional[str], **kwargs): + def __init__(self, *, domain: str | None, identifier: str | None, **kwargs): """Initialize media source browse media.""" media_content_id = f"{URI_SCHEME}{domain or ''}" if identifier: @@ -47,7 +46,7 @@ class MediaSourceItem: """A parsed media item.""" hass: HomeAssistant - domain: Optional[str] + domain: str | None identifier: str async def async_browse(self) -> BrowseMediaSource: @@ -118,7 +117,7 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: raise NotImplementedError async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] + self, item: MediaSourceItem, media_types: tuple[str] ) -> BrowseMediaSource: """Browse media.""" raise NotImplementedError diff --git a/homeassistant/components/melcloud/__init__.py b/homeassistant/components/melcloud/__init__.py index 0e81d6101b3059..0f48db96bf846f 100644 --- a/homeassistant/components/melcloud/__init__.py +++ b/homeassistant/components/melcloud/__init__.py @@ -1,8 +1,10 @@ """The MELCloud Climate integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict, List +from typing import Any from aiohttp import ClientConnectionError from async_timeout import timeout @@ -101,7 +103,7 @@ async def async_update(self, **kwargs): _LOGGER.warning("Connection failed for %s", self.name) self._available = False - async def async_set(self, properties: Dict[str, Any]): + async def async_set(self, properties: dict[str, Any]): """Write state changes to the MELCloud API.""" try: await self.device.set(properties) @@ -142,7 +144,7 @@ def device_info(self): return _device_info -async def mel_devices_setup(hass, token) -> List[MelCloudDevice]: +async def mel_devices_setup(hass, token) -> list[MelCloudDevice]: """Query connected devices from MELCloud.""" session = hass.helpers.aiohttp_client.async_get_clientsession() try: diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index 1abb86cf5e523c..8e45cc3d9a4c18 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -1,6 +1,8 @@ """Platform for climate integration.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Any from pymelcloud import DEVICE_TYPE_ATA, DEVICE_TYPE_ATW, AtaDevice, AtwDevice import pymelcloud.ata_device as ata @@ -114,7 +116,7 @@ def device_info(self): return self.api.device_info @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return self._base_device.temperature_increment @@ -128,7 +130,7 @@ def __init__(self, device: MelCloudDevice, ata_device: AtaDevice) -> None: self._device = ata_device @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self.api.device.serial}-{self.api.device.mac}" @@ -138,7 +140,7 @@ def name(self): return self._name @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes with device specific additions.""" attr = {} @@ -190,19 +192,19 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: await self._device.set(props) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_OFF] + [ ATA_HVAC_MODE_LOOKUP.get(mode) for mode in self._device.operation_modes ] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._device.room_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._device.target_temperature @@ -213,7 +215,7 @@ async def async_set_temperature(self, **kwargs) -> None: ) @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" return self._device.fan_speed @@ -222,7 +224,7 @@ async def async_set_fan_mode(self, fan_mode: str) -> None: await self._device.set({"fan_speed": fan_mode}) @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" return self._device.fan_speeds @@ -243,7 +245,7 @@ async def async_set_vane_vertical(self, position: str) -> None: await self._device.set({ata.PROPERTY_VANE_VERTICAL: position}) @property - def swing_mode(self) -> Optional[str]: + def swing_mode(self) -> str | None: """Return vertical vane position or mode.""" return self._device.vane_vertical @@ -252,7 +254,7 @@ async def async_set_swing_mode(self, swing_mode) -> None: await self.async_set_vane_vertical(swing_mode) @property - def swing_modes(self) -> Optional[str]: + def swing_modes(self) -> str | None: """Return a list of available vertical vane positions and modes.""" return self._device.vane_vertical_positions @@ -300,7 +302,7 @@ def __init__( self._zone = atw_zone @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self.api.device.serial}-{self._zone.zone_index}" @@ -310,7 +312,7 @@ def name(self) -> str: return f"{self._name} {self._zone.name}" @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the optional state attributes with device specific additions.""" data = { ATTR_STATUS: ATW_ZONE_HVAC_MODE_LOOKUP.get( @@ -351,17 +353,17 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: await self._device.set(props) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [self.hvac_mode] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._zone.room_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._zone.target_temperature diff --git a/homeassistant/components/melcloud/config_flow.py b/homeassistant/components/melcloud/config_flow.py index 41ce24989a5d00..a487a446d9b12e 100644 --- a/homeassistant/components/melcloud/config_flow.py +++ b/homeassistant/components/melcloud/config_flow.py @@ -1,6 +1,7 @@ """Config flow for the MELCloud platform.""" +from __future__ import annotations + import asyncio -from typing import Optional from aiohttp import ClientError, ClientResponseError from async_timeout import timeout @@ -37,8 +38,8 @@ async def _create_client( self, username: str, *, - password: Optional[str] = None, - token: Optional[str] = None, + password: str | None = None, + token: str | None = None, ): """Create client.""" if password is None and token is None: diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index 3474fc07540c98..e01d78a527068e 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -1,5 +1,5 @@ """Platform for water_heater integration.""" -from typing import List, Optional +from __future__ import annotations from pymelcloud import DEVICE_TYPE_ATW, AtwDevice from pymelcloud.atw_device import ( @@ -49,7 +49,7 @@ async def async_update(self): await self._api.async_update() @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return f"{self._api.device.serial}" @@ -83,17 +83,17 @@ def temperature_unit(self) -> str: return TEMP_CELSIUS @property - def current_operation(self) -> Optional[str]: + def current_operation(self) -> str | None: """Return current operation as reported by pymelcloud.""" return self._device.operation_mode @property - def operation_list(self) -> List[str]: + def operation_list(self) -> list[str]: """Return the list of available operation modes as reported by pymelcloud.""" return self._device.operation_modes @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._device.tank_temperature @@ -122,11 +122,11 @@ def supported_features(self): return SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @property - def min_temp(self) -> Optional[float]: + def min_temp(self) -> float | None: """Return the minimum temperature.""" return self._device.target_tank_temperature_min @property - def max_temp(self) -> Optional[float]: + def max_temp(self) -> float | None: """Return the maximum temperature.""" return self._device.target_tank_temperature_max diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index 6b3d6735f46604..b9d50ba59a58e5 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure Met component.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -73,9 +75,7 @@ async def _show_config_form( errors=self._errors, ) - async def async_step_import( - self, user_input: Optional[Dict] = None - ) -> Dict[str, Any]: + async def async_step_import(self, user_input: dict | None = None) -> dict[str, Any]: """Handle configuration by yaml file.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 0e7096881f5051..f76e8e8467edb4 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -1,9 +1,10 @@ """The Minecraft Server integration.""" +from __future__ import annotations import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Dict +from typing import Any from mcstatus.server import MinecraftServer as MCStatus @@ -260,7 +261,7 @@ def unique_id(self) -> str: return self._unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information.""" return self._device_info diff --git a/homeassistant/components/minecraft_server/helpers.py b/homeassistant/components/minecraft_server/helpers.py index 7f9380cdec24c0..f6409ce525de80 100644 --- a/homeassistant/components/minecraft_server/helpers.py +++ b/homeassistant/components/minecraft_server/helpers.py @@ -1,6 +1,7 @@ """Helper functions for the Minecraft Server integration.""" +from __future__ import annotations -from typing import Any, Dict +from typing import Any import aiodns @@ -10,7 +11,7 @@ from .const import SRV_RECORD_PREFIX -async def async_check_srv_record(hass: HomeAssistantType, host: str) -> Dict[str, Any]: +async def async_check_srv_record(hass: HomeAssistantType, host: str) -> dict[str, Any]: """Check if the given host is a valid Minecraft SRV record.""" # Check if 'host' is a valid SRV record. return_value = None diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 8cc626390dd914..4cc9e9746bbc48 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -1,5 +1,7 @@ """The Minecraft Server sensor platform.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.const import TIME_MILLISECONDS @@ -151,7 +153,7 @@ async def async_update(self) -> None: self._extra_state_attributes = extra_state_attributes @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return players list in device state attributes.""" return self._extra_state_attributes diff --git a/homeassistant/components/minio/__init__.py b/homeassistant/components/minio/__init__.py index 178058986cc099..6e7174b60ee34f 100644 --- a/homeassistant/components/minio/__init__.py +++ b/homeassistant/components/minio/__init__.py @@ -1,9 +1,10 @@ """Minio component.""" +from __future__ import annotations + import logging import os from queue import Queue import threading -from typing import List import voluptuous as vol @@ -230,7 +231,7 @@ def __init__( bucket_name: str, prefix: str, suffix: str, - events: List[str], + events: list[str], ): """Create Listener.""" self._queue = queue diff --git a/homeassistant/components/minio/minio_helper.py b/homeassistant/components/minio/minio_helper.py index 2aaba9d408528c..f2d860675525c2 100644 --- a/homeassistant/components/minio/minio_helper.py +++ b/homeassistant/components/minio/minio_helper.py @@ -1,4 +1,6 @@ """Minio helper methods.""" +from __future__ import annotations + from collections.abc import Iterable import json import logging @@ -6,7 +8,7 @@ import re import threading import time -from typing import Iterator, List +from typing import Iterator from urllib.parse import unquote from minio import Minio @@ -38,7 +40,7 @@ def create_minio_client( def get_minio_notification_response( - minio_client, bucket_name: str, prefix: str, suffix: str, events: List[str] + minio_client, bucket_name: str, prefix: str, suffix: str, events: list[str] ): """Start listening to minio events. Copied from minio-py.""" query = {"prefix": prefix, "suffix": suffix, "events": events} @@ -87,7 +89,7 @@ def __init__( bucket_name: str, prefix: str, suffix: str, - events: List[str], + events: list[str], ): """Copy over all Minio client options.""" super().__init__() diff --git a/homeassistant/components/mobile_app/device_action.py b/homeassistant/components/mobile_app/device_action.py index 2592d4b486b176..33a7510da2149a 100644 --- a/homeassistant/components/mobile_app/device_action.py +++ b/homeassistant/components/mobile_app/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for Mobile App.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -22,7 +22,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Mobile App devices.""" webhook_id = webhook_id_from_device_id(hass, device_id) @@ -33,7 +33,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" webhook_id = webhook_id_from_device_id(hass, config[CONF_DEVICE_ID]) diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index fed322df464f5f..63d638cd9e557f 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -1,7 +1,9 @@ """Helpers for mobile_app.""" +from __future__ import annotations + import json import logging -from typing import Callable, Dict, Tuple +from typing import Callable from aiohttp.web import Response, json_response from nacl.encoding import Base64Encoder @@ -36,7 +38,7 @@ _LOGGER = logging.getLogger(__name__) -def setup_decrypt() -> Tuple[int, Callable]: +def setup_decrypt() -> tuple[int, Callable]: """Return decryption function and length of key. Async friendly. @@ -49,7 +51,7 @@ def decrypt(ciphertext, key): return (SecretBox.KEY_SIZE, decrypt) -def setup_encrypt() -> Tuple[int, Callable]: +def setup_encrypt() -> tuple[int, Callable]: """Return encryption function and length of key. Async friendly. @@ -62,7 +64,7 @@ def encrypt(ciphertext, key): return (SecretBox.KEY_SIZE, encrypt) -def _decrypt_payload(key: str, ciphertext: str) -> Dict[str, str]: +def _decrypt_payload(key: str, ciphertext: str) -> dict[str, str]: """Decrypt encrypted payload.""" try: keylen, decrypt = setup_decrypt() @@ -88,12 +90,12 @@ def _decrypt_payload(key: str, ciphertext: str) -> Dict[str, str]: return None -def registration_context(registration: Dict) -> Context: +def registration_context(registration: dict) -> Context: """Generate a context from a request.""" return Context(user_id=registration[CONF_USER_ID]) -def empty_okay_response(headers: Dict = None, status: int = HTTP_OK) -> Response: +def empty_okay_response(headers: dict = None, status: int = HTTP_OK) -> Response: """Return a Response with empty JSON object and a 200.""" return Response( text="{}", status=status, content_type=CONTENT_TYPE_JSON, headers=headers @@ -121,7 +123,7 @@ def supports_encryption() -> bool: return False -def safe_registration(registration: Dict) -> Dict: +def safe_registration(registration: dict) -> dict: """Return a registration without sensitive values.""" # Sensitive values: webhook_id, secret, cloudhook_url return { @@ -137,7 +139,7 @@ def safe_registration(registration: Dict) -> Dict: } -def savable_state(hass: HomeAssistantType) -> Dict: +def savable_state(hass: HomeAssistantType) -> dict: """Return a clean object containing things that should be saved.""" return { DATA_DELETED_IDS: hass.data[DOMAIN][DATA_DELETED_IDS], @@ -145,7 +147,7 @@ def savable_state(hass: HomeAssistantType) -> Dict: def webhook_response( - data, *, registration: Dict, status: int = HTTP_OK, headers: Dict = None + data, *, registration: dict, status: int = HTTP_OK, headers: dict = None ) -> Response: """Return a encrypted response if registration supports it.""" data = json.dumps(data, cls=JSONEncoder) @@ -165,7 +167,7 @@ def webhook_response( ) -def device_info(registration: Dict) -> Dict: +def device_info(registration: dict) -> dict: """Return the device info for this registration.""" return { "identifiers": {(DOMAIN, registration[ATTR_DEVICE_ID])}, diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 4bd8d0cd76b07f..5583b7c58d1fc4 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -1,6 +1,7 @@ """Provides an HTTP API for mobile_app.""" +from __future__ import annotations + import secrets -from typing import Dict from aiohttp.web import Request, Response import emoji @@ -58,7 +59,7 @@ class RegistrationsView(HomeAssistantView): extra=vol.REMOVE_EXTRA, ) ) - async def post(self, request: Request, data: Dict) -> Response: + async def post(self, request: Request, data: dict) -> Response: """Handle the POST request for registration.""" hass = request.app["hass"] diff --git a/homeassistant/components/mobile_app/util.py b/homeassistant/components/mobile_app/util.py index 60dfe242e0430e..b0a3f52e3949c7 100644 --- a/homeassistant/components/mobile_app/util.py +++ b/homeassistant/components/mobile_app/util.py @@ -1,5 +1,7 @@ """Mobile app utility functions.""" -from typing import TYPE_CHECKING, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from homeassistant.core import callback @@ -18,7 +20,7 @@ @callback -def webhook_id_from_device_id(hass, device_id: str) -> Optional[str]: +def webhook_id_from_device_id(hass, device_id: str) -> str | None: """Get webhook ID from device ID.""" if DOMAIN not in hass.data: return None @@ -39,7 +41,7 @@ def supports_push(hass, webhook_id: str) -> bool: @callback -def get_notify_service(hass, webhook_id: str) -> Optional[str]: +def get_notify_service(hass, webhook_id: str) -> str | None: """Return the notify service for this webhook ID.""" notify_service: "MobileAppNotificationService" = hass.data[DOMAIN][DATA_NOTIFY] diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 8e91945d0732f8..16cd191bba7dc8 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Modbus Coil and Discrete Input sensors.""" -from typing import Optional +from __future__ import annotations from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -94,7 +94,7 @@ def is_on(self): return self._value @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 45cfbf5eb57134..d32f2ae3cf56fe 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -1,8 +1,10 @@ """Support for Generic Modbus Thermostats.""" +from __future__ import annotations + from datetime import timedelta import logging import struct -from typing import Any, Dict, Optional +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -57,7 +59,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ): """Read configuration and create Modbus climate.""" if discovery_info is None: @@ -108,7 +110,7 @@ class ModbusThermostat(ClimateEntity): def __init__( self, hub: ModbusHub, - config: Dict[str, Any], + config: dict[str, Any], ): """Initialize the modbus thermostat.""" self._hub: ModbusHub = hub @@ -232,7 +234,7 @@ def _update(self): self.schedule_update_ha_state() - def _read_register(self, register_type, register) -> Optional[float]: + def _read_register(self, register_type, register) -> float | None: """Read register using the Modbus hub slave.""" try: if register_type == CALL_TYPE_REGISTER_INPUT: diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index 09a465a2cdd7c1..a8003676640f22 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -1,6 +1,8 @@ """Support for Modbus covers.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Dict, Optional +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -41,7 +43,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities, - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ): """Read configuration and create Modbus cover.""" if discovery_info is None: @@ -61,7 +63,7 @@ class ModbusCover(CoverEntity, RestoreEntity): def __init__( self, hub: ModbusHub, - config: Dict[str, Any], + config: dict[str, Any], ): """Initialize the modbus cover.""" self._hub: ModbusHub = hub @@ -108,7 +110,7 @@ async def async_added_to_hass(self): ) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class @@ -178,7 +180,7 @@ def _update(self): self.schedule_update_ha_state() - def _read_status_register(self) -> Optional[int]: + def _read_status_register(self) -> int | None: """Read status register using the Modbus hub slave.""" try: if self._status_register_type == CALL_TYPE_REGISTER_INPUT: @@ -212,7 +214,7 @@ def _write_register(self, value): self._available = True - def _read_coil(self) -> Optional[bool]: + def _read_coil(self) -> bool | None: """Read coil using the Modbus hub slave.""" try: result = self._hub.read_coils(self._slave, self._coil, 1) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 656e5e2986d0af..d0fad973802164 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,7 +1,9 @@ """Support for Modbus Register sensors.""" +from __future__ import annotations + import logging import struct -from typing import Any, Optional, Union +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -44,7 +46,7 @@ _LOGGER = logging.getLogger(__name__) -def number(value: Any) -> Union[int, float]: +def number(value: Any) -> int | float: """Coerce a value to number without losing precision.""" if isinstance(value, int): return value @@ -217,7 +219,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 36fbef084281f6..bf2233e2407e34 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -1,7 +1,9 @@ """Support for Modbus switches.""" +from __future__ import annotations + from abc import ABC import logging -from typing import Any, Dict, Optional +from typing import Any from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse @@ -99,7 +101,7 @@ async def async_setup_platform( class ModbusBaseSwitch(ToggleEntity, RestoreEntity, ABC): """Base class representing a Modbus switch.""" - def __init__(self, hub: ModbusHub, config: Dict[str, Any]): + def __init__(self, hub: ModbusHub, config: dict[str, Any]): """Initialize the switch.""" self._hub: ModbusHub = hub self._name = config[CONF_NAME] @@ -133,7 +135,7 @@ def available(self) -> bool: class ModbusCoilSwitch(ModbusBaseSwitch, SwitchEntity): """Representation of a Modbus coil switch.""" - def __init__(self, hub: ModbusHub, config: Dict[str, Any]): + def __init__(self, hub: ModbusHub, config: dict[str, Any]): """Initialize the coil switch.""" super().__init__(hub, config) self._coil = config[CALL_TYPE_COIL] @@ -184,7 +186,7 @@ def _write_coil(self, coil, value): class ModbusRegisterSwitch(ModbusBaseSwitch, SwitchEntity): """Representation of a Modbus register switch.""" - def __init__(self, hub: ModbusHub, config: Dict[str, Any]): + def __init__(self, hub: ModbusHub, config: dict[str, Any]): """Initialize the register switch.""" super().__init__(hub, config) self._register = config[CONF_REGISTER] @@ -238,7 +240,7 @@ def update(self): value, ) - def _read_register(self) -> Optional[int]: + def _read_register(self) -> int | None: try: if self._register_type == CALL_TYPE_REGISTER_INPUT: result = self._hub.read_input_registers( diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 503083b0067299..ce2d413e1b69c7 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1,4 +1,6 @@ """Support for MQTT message handling.""" +from __future__ import annotations + import asyncio from functools import lru_cache, partial, wraps import inspect @@ -8,7 +10,7 @@ import os import ssl import time -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable, Union import uuid import attr @@ -310,7 +312,7 @@ async def async_subscribe( topic: str, msg_callback: MessageCallbackType, qos: int = DEFAULT_QOS, - encoding: Optional[str] = "utf-8", + encoding: str | None = "utf-8", ): """Subscribe to an MQTT topic. @@ -385,7 +387,7 @@ async def _async_setup_discovery( async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Start the MQTT protocol service.""" - conf: Optional[ConfigType] = config.get(DOMAIN) + conf: ConfigType | None = config.get(DOMAIN) websocket_api.async_register_command(hass, websocket_subscribe) websocket_api.async_register_command(hass, websocket_remove_device) @@ -552,7 +554,7 @@ def __init__( self.hass = hass self.config_entry = config_entry self.conf = conf - self.subscriptions: List[Subscription] = [] + self.subscriptions: list[Subscription] = [] self.connected = False self._ha_started = asyncio.Event() self._last_subscribe = time.time() @@ -730,7 +732,7 @@ async def async_subscribe( topic: str, msg_callback: MessageCallbackType, qos: int, - encoding: Optional[str] = None, + encoding: str | None = None, ) -> Callable[[], None]: """Set up a subscription to a topic with the provided qos. diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index d6e2ee0fc65721..1e4981d7d6f8c8 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -1,6 +1,8 @@ """Provides device automations for MQTT.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable import attr import voluptuous as vol @@ -86,7 +88,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() trigger: "Trigger" = attr.ib() - remove: Optional[CALLBACK_TYPE] = attr.ib(default=None) + remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): """Attach MQTT trigger.""" @@ -126,7 +128,7 @@ class Trigger: topic: str = attr.ib() type: str = attr.ib() value_template: str = attr.ib() - trigger_instances: List[TriggerInstance] = attr.ib(factory=list) + trigger_instances: list[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): """Add MQTT trigger.""" @@ -285,7 +287,7 @@ async def async_device_removed(hass: HomeAssistant, device_id: str): ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for MQTT devices.""" triggers = [] diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 5737dad255c0f9..332632f4e0f20a 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1,8 +1,9 @@ """MQTT component mixins and helpers.""" +from __future__ import annotations + from abc import abstractmethod import json import logging -from typing import Optional import voluptuous as vol @@ -502,7 +503,7 @@ def device_info_from_config(config): class MqttEntityDeviceInfo(Entity): """Mixin used for mqtt platforms that support the device registry.""" - def __init__(self, device_config: Optional[ConfigType], config_entry=None) -> None: + def __init__(self, device_config: ConfigType | None, config_entry=None) -> None: """Initialize the device mixin.""" self._device_config = device_config self._config_entry = config_entry diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 202f457372c10c..7cdafeef98de86 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -1,6 +1,8 @@ """Modesl used by multiple MQTT modules.""" +from __future__ import annotations + import datetime as dt -from typing import Callable, Optional, Union +from typing import Callable, Union import attr @@ -15,8 +17,8 @@ class Message: payload: PublishPayloadType = attr.ib() qos: int = attr.ib() retain: bool = attr.ib() - subscribed_topic: Optional[str] = attr.ib(default=None) - timestamp: Optional[dt.datetime] = attr.ib(default=None) + subscribed_topic: str | None = attr.ib(default=None) + timestamp: dt.datetime | None = attr.ib(default=None) MessageCallbackType = Callable[[Message], None] diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index d32f4c423831c9..b20595922cda7a 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -1,7 +1,8 @@ """Support for MQTT sensors.""" +from __future__ import annotations + from datetime import timedelta import functools -from typing import Optional import voluptuous as vol @@ -166,7 +167,7 @@ def state(self): return self._state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._config.get(CONF_DEVICE_CLASS) diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py index 5c2efabc2669a7..e6c99c09fd5a44 100644 --- a/homeassistant/components/mqtt/subscription.py +++ b/homeassistant/components/mqtt/subscription.py @@ -1,5 +1,7 @@ """Helper to handle a set of topics to subscribe to.""" -from typing import Any, Callable, Dict, Optional +from __future__ import annotations + +from typing import Any, Callable import attr @@ -19,7 +21,7 @@ class EntitySubscription: hass: HomeAssistantType = attr.ib() topic: str = attr.ib() message_callback: MessageCallbackType = attr.ib() - unsubscribe_callback: Optional[Callable[[], None]] = attr.ib() + unsubscribe_callback: Callable[[], None] | None = attr.ib() qos: int = attr.ib(default=0) encoding: str = attr.ib(default="utf-8") @@ -62,8 +64,8 @@ def _should_resubscribe(self, other): @bind_hass async def async_subscribe_topics( hass: HomeAssistantType, - new_state: Optional[Dict[str, EntitySubscription]], - topics: Dict[str, Any], + new_state: dict[str, EntitySubscription] | None, + topics: dict[str, Any], ): """(Re)Subscribe to a set of MQTT topics. diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 7dd118099f764b..c9ad496762dce0 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,8 +1,10 @@ """Connect to a MySensors gateway via pymysensors API.""" +from __future__ import annotations + import asyncio from functools import partial import logging -from typing import Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Callable from mysensors import BaseAsyncGateway import voluptuous as vol @@ -265,13 +267,13 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo def setup_mysensors_platform( hass: HomeAssistant, domain: str, # hass platform name - discovery_info: Dict[str, List[DevId]], - device_class: Union[Type[MySensorsDevice], Dict[SensorType, Type[MySensorsEntity]]], - device_args: Optional[ - Tuple - ] = None, # extra arguments that will be given to the entity constructor - async_add_entities: Optional[Callable] = None, -) -> Optional[List[MySensorsDevice]]: + discovery_info: dict[str, list[DevId]], + device_class: type[MySensorsDevice] | dict[SensorType, type[MySensorsEntity]], + device_args: ( + None | tuple + ) = None, # extra arguments that will be given to the entity constructor + async_add_entities: Callable | None = None, +) -> list[MySensorsDevice] | None: """Set up a MySensors platform. Sets up a bunch of instances of a single platform that is supported by this integration. @@ -281,10 +283,10 @@ def setup_mysensors_platform( """ if device_args is None: device_args = () - new_devices: List[MySensorsDevice] = [] - new_dev_ids: List[DevId] = discovery_info[ATTR_DEVICES] + new_devices: list[MySensorsDevice] = [] + new_dev_ids: list[DevId] = discovery_info[ATTR_DEVICES] for dev_id in new_dev_ids: - devices: Dict[DevId, MySensorsDevice] = get_mysensors_devices(hass, domain) + devices: dict[DevId, MySensorsDevice] = get_mysensors_devices(hass, domain) if dev_id in devices: _LOGGER.debug( "Skipping setup of %s for platform %s as it already exists", @@ -293,7 +295,7 @@ def setup_mysensors_platform( ) continue gateway_id, node_id, child_id, value_type = dev_id - gateway: Optional[BaseAsyncGateway] = get_mysensors_gateway(hass, gateway_id) + gateway: BaseAsyncGateway | None = get_mysensors_gateway(hass, gateway_id) if not gateway: _LOGGER.warning("Skipping setup of %s, no gateway found", dev_id) continue diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 7bf30e4cab5b47..3a37799106a991 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -1,7 +1,9 @@ """Config flow for MySensors.""" +from __future__ import annotations + import logging import os -from typing import Any, Dict, Optional +from typing import Any from awesomeversion import ( AwesomeVersion, @@ -42,7 +44,7 @@ _LOGGER = logging.getLogger(__name__) -def _get_schema_common(user_input: Dict[str, str]) -> dict: +def _get_schema_common(user_input: dict[str, str]) -> dict: """Create a schema with options common to all gateway types.""" schema = { vol.Required( @@ -57,7 +59,7 @@ def _get_schema_common(user_input: Dict[str, str]) -> dict: return schema -def _validate_version(version: str) -> Dict[str, str]: +def _validate_version(version: str) -> dict[str, str]: """Validate a version string from the user.""" version_okay = False try: @@ -75,7 +77,7 @@ def _validate_version(version: str) -> Dict[str, str]: def _is_same_device( - gw_type: ConfGatewayType, user_input: Dict[str, str], entry: ConfigEntry + gw_type: ConfGatewayType, user_input: dict[str, str], entry: ConfigEntry ): """Check if another ConfigDevice is actually the same as user_input. @@ -102,9 +104,9 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Set up config flow.""" - self._gw_type: Optional[str] = None + self._gw_type: str | None = None - async def async_step_import(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_import(self, user_input: dict[str, str] | None = None): """Import a config entry. This method is called by async_setup and it has already @@ -124,12 +126,12 @@ async def async_step_import(self, user_input: Optional[Dict[str, str]] = None): else: user_input[CONF_GATEWAY_TYPE] = CONF_GATEWAY_TYPE_SERIAL - result: Dict[str, Any] = await self.async_step_user(user_input=user_input) + result: dict[str, Any] = await self.async_step_user(user_input=user_input) if result["type"] == "form": return self.async_abort(reason=next(iter(result["errors"].values()))) return result - async def async_step_user(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_user(self, user_input: dict[str, str] | None = None): """Create a config entry from frontend user input.""" schema = {vol.Required(CONF_GATEWAY_TYPE): vol.In(CONF_GATEWAY_TYPE_ALL)} schema = vol.Schema(schema) @@ -146,7 +148,7 @@ async def async_step_user(self, user_input: Optional[Dict[str, str]] = None): return self.async_show_form(step_id="user", data_schema=schema) - async def async_step_gw_serial(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_gw_serial(self, user_input: dict[str, str] | None = None): """Create config entry for a serial gateway.""" errors = {} if user_input is not None: @@ -175,7 +177,7 @@ async def async_step_gw_serial(self, user_input: Optional[Dict[str, str]] = None step_id="gw_serial", data_schema=schema, errors=errors ) - async def async_step_gw_tcp(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_gw_tcp(self, user_input: dict[str, str] | None = None): """Create a config entry for a tcp gateway.""" errors = {} if user_input is not None: @@ -213,7 +215,7 @@ def _check_topic_exists(self, topic: str) -> bool: return True return False - async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): + async def async_step_gw_mqtt(self, user_input: dict[str, str] | None = None): """Create a config entry for a mqtt gateway.""" errors = {} if user_input is not None: @@ -269,8 +271,8 @@ async def async_step_gw_mqtt(self, user_input: Optional[Dict[str, str]] = None): @callback def _async_create_entry( - self, user_input: Optional[Dict[str, str]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, str] | None = None + ) -> dict[str, Any]: """Create the config entry.""" return self.async_create_entry( title=f"{user_input[CONF_DEVICE]}", @@ -283,9 +285,9 @@ def _normalize_persistence_file(self, path: str) -> str: async def validate_common( self, gw_type: ConfGatewayType, - errors: Dict[str, str], - user_input: Optional[Dict[str, str]] = None, - ) -> Dict[str, str]: + errors: dict[str, str], + user_input: dict[str, str] | None = None, + ) -> dict[str, str]: """Validate parameters common to all gateway types.""" if user_input is not None: errors.update(_validate_version(user_input.get(CONF_VERSION))) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 1b8fa5e24e8d7d..7a9027d9b724eb 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -1,6 +1,8 @@ """MySensors constants.""" +from __future__ import annotations + from collections import defaultdict -from typing import Dict, List, Literal, Set, Tuple +from typing import Literal, Tuple ATTR_DEVICES: str = "devices" ATTR_GATEWAY_ID: str = "gateway_id" @@ -21,7 +23,7 @@ CONF_GATEWAY_TYPE_SERIAL: ConfGatewayType = "Serial" CONF_GATEWAY_TYPE_TCP: ConfGatewayType = "TCP" CONF_GATEWAY_TYPE_MQTT: ConfGatewayType = "MQTT" -CONF_GATEWAY_TYPE_ALL: List[str] = [ +CONF_GATEWAY_TYPE_ALL: list[str] = [ CONF_GATEWAY_TYPE_MQTT, CONF_GATEWAY_TYPE_SERIAL, CONF_GATEWAY_TYPE_TCP, @@ -62,7 +64,7 @@ # The MySensors integration brings these together by creating an entity for every v_type of every child_id of every node. # The DevId tuple perfectly captures this. -BINARY_SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { +BINARY_SENSOR_TYPES: dict[SensorType, set[ValueType]] = { "S_DOOR": {"V_TRIPPED"}, "S_MOTION": {"V_TRIPPED"}, "S_SMOKE": {"V_TRIPPED"}, @@ -73,23 +75,23 @@ "S_MOISTURE": {"V_TRIPPED"}, } -CLIMATE_TYPES: Dict[SensorType, Set[ValueType]] = {"S_HVAC": {"V_HVAC_FLOW_STATE"}} +CLIMATE_TYPES: dict[SensorType, set[ValueType]] = {"S_HVAC": {"V_HVAC_FLOW_STATE"}} -COVER_TYPES: Dict[SensorType, Set[ValueType]] = { +COVER_TYPES: dict[SensorType, set[ValueType]] = { "S_COVER": {"V_DIMMER", "V_PERCENTAGE", "V_LIGHT", "V_STATUS"} } -DEVICE_TRACKER_TYPES: Dict[SensorType, Set[ValueType]] = {"S_GPS": {"V_POSITION"}} +DEVICE_TRACKER_TYPES: dict[SensorType, set[ValueType]] = {"S_GPS": {"V_POSITION"}} -LIGHT_TYPES: Dict[SensorType, Set[ValueType]] = { +LIGHT_TYPES: dict[SensorType, set[ValueType]] = { "S_DIMMER": {"V_DIMMER", "V_PERCENTAGE"}, "S_RGB_LIGHT": {"V_RGB"}, "S_RGBW_LIGHT": {"V_RGBW"}, } -NOTIFY_TYPES: Dict[SensorType, Set[ValueType]] = {"S_INFO": {"V_TEXT"}} +NOTIFY_TYPES: dict[SensorType, set[ValueType]] = {"S_INFO": {"V_TEXT"}} -SENSOR_TYPES: Dict[SensorType, Set[ValueType]] = { +SENSOR_TYPES: dict[SensorType, set[ValueType]] = { "S_SOUND": {"V_LEVEL"}, "S_VIBRATION": {"V_LEVEL"}, "S_MOISTURE": {"V_LEVEL"}, @@ -117,7 +119,7 @@ "S_DUST": {"V_DUST_LEVEL", "V_LEVEL"}, } -SWITCH_TYPES: Dict[SensorType, Set[ValueType]] = { +SWITCH_TYPES: dict[SensorType, set[ValueType]] = { "S_LIGHT": {"V_LIGHT"}, "S_BINARY": {"V_STATUS"}, "S_DOOR": {"V_ARMED"}, @@ -134,7 +136,7 @@ } -PLATFORM_TYPES: Dict[str, Dict[SensorType, Set[ValueType]]] = { +PLATFORM_TYPES: dict[str, dict[SensorType, set[ValueType]]] = { "binary_sensor": BINARY_SENSOR_TYPES, "climate": CLIMATE_TYPES, "cover": COVER_TYPES, @@ -145,13 +147,13 @@ "switch": SWITCH_TYPES, } -FLAT_PLATFORM_TYPES: Dict[Tuple[str, SensorType], Set[ValueType]] = { +FLAT_PLATFORM_TYPES: dict[tuple[str, SensorType], set[ValueType]] = { (platform, s_type_name): v_type_name for platform, platform_types in PLATFORM_TYPES.items() for s_type_name, v_type_name in platform_types.items() } -TYPE_TO_PLATFORMS: Dict[SensorType, List[str]] = defaultdict(list) +TYPE_TO_PLATFORMS: dict[SensorType, list[str]] = defaultdict(list) for platform, platform_types in PLATFORM_TYPES.items(): for s_type_name in platform_types: diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index d5ae6c0c156638..4e770f70bf0c98 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -1,7 +1,9 @@ """Handle MySensors devices.""" +from __future__ import annotations + from functools import partial import logging -from typing import Any, Dict, Optional +from typing import Any from mysensors import BaseAsyncGateway, Sensor from mysensors.sensor import ChildSensor @@ -107,7 +109,7 @@ def unique_id(self) -> str: return f"{self.gateway_id}-{self.node_id}-{self.child_id}-{self.value_type}" @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Return a dict that allows home assistant to puzzle all entities belonging to a node together.""" return { "identifiers": {(DOMAIN, f"{self.gateway_id}-{self.node_id}")}, @@ -196,7 +198,7 @@ async def update(): self.hass.loop.call_later(UPDATE_DELAY, delayed_update) -def get_mysensors_devices(hass, domain: str) -> Dict[DevId, MySensorsDevice]: +def get_mysensors_devices(hass, domain: str) -> dict[DevId, MySensorsDevice]: """Return MySensors devices for a hass platform name.""" if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] = {} diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index a7f3a053d3fc60..6cf8e7d7383176 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -1,10 +1,12 @@ """Handle MySensors gateways.""" +from __future__ import annotations + import asyncio from collections import defaultdict import logging import socket import sys -from typing import Any, Callable, Coroutine, Dict, Optional +from typing import Any, Callable, Coroutine import async_timeout from mysensors import BaseAsyncGateway, Message, Sensor, mysensors @@ -63,7 +65,7 @@ def is_socket_address(value): raise vol.Invalid("Device is not a valid domain name or ip address") from err -async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bool: +async def try_connect(hass: HomeAssistantType, user_input: dict[str, str]) -> bool: """Try to connect to a gateway and report if it worked.""" if user_input[CONF_DEVICE] == MQTT_COMPONENT: return True # dont validate mqtt. mqtt gateways dont send ready messages :( @@ -73,7 +75,7 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo def on_conn_made(_: BaseAsyncGateway) -> None: gateway_ready.set() - gateway: Optional[BaseAsyncGateway] = await _get_gateway( + gateway: BaseAsyncGateway | None = await _get_gateway( hass, device=user_input[CONF_DEVICE], version=user_input[CONF_VERSION], @@ -110,7 +112,7 @@ def on_conn_made(_: BaseAsyncGateway) -> None: def get_mysensors_gateway( hass: HomeAssistantType, gateway_id: GatewayId -) -> Optional[BaseAsyncGateway]: +) -> BaseAsyncGateway | None: """Return the Gateway for a given GatewayId.""" if MYSENSORS_GATEWAYS not in hass.data[DOMAIN]: hass.data[DOMAIN][MYSENSORS_GATEWAYS] = {} @@ -120,7 +122,7 @@ def get_mysensors_gateway( async def setup_gateway( hass: HomeAssistantType, entry: ConfigEntry -) -> Optional[BaseAsyncGateway]: +) -> BaseAsyncGateway | None: """Set up the Gateway for the given ConfigEntry.""" ready_gateway = await _get_gateway( @@ -145,14 +147,14 @@ async def _get_gateway( device: str, version: str, event_callback: Callable[[Message], None], - persistence_file: Optional[str] = None, - baud_rate: Optional[int] = None, - tcp_port: Optional[int] = None, - topic_in_prefix: Optional[str] = None, - topic_out_prefix: Optional[str] = None, + persistence_file: str | None = None, + baud_rate: int | None = None, + tcp_port: int | None = None, + topic_in_prefix: str | None = None, + topic_out_prefix: str | None = None, retain: bool = False, persistence: bool = True, # old persistence option has been deprecated. kwarg is here so we can run try_connect() without persistence -) -> Optional[BaseAsyncGateway]: +) -> BaseAsyncGateway | None: """Return gateway after setup of the gateway.""" if persistence_file is not None: diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index a47c9174b23bf8..d21140701f97c2 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -1,5 +1,5 @@ """Handle MySensors messages.""" -from typing import Dict, List +from __future__ import annotations from mysensors import Message @@ -70,16 +70,16 @@ async def handle_sketch_version( @callback def _handle_child_update( - hass: HomeAssistantType, gateway_id: GatewayId, validated: Dict[str, List[DevId]] + hass: HomeAssistantType, gateway_id: GatewayId, validated: dict[str, list[DevId]] ): """Handle a child update.""" - signals: List[str] = [] + signals: list[str] = [] # Update all platforms for the device via dispatcher. # Add/update entity for validated children. for platform, dev_ids in validated.items(): devices = get_mysensors_devices(hass, platform) - new_dev_ids: List[DevId] = [] + new_dev_ids: list[DevId] = [] for dev_id in dev_ids: if dev_id in devices: signals.append(CHILD_CALLBACK.format(*dev_id)) diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 0b8dc361158fd4..0d18b243520d54 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -1,8 +1,10 @@ """Helper functions for mysensors package.""" +from __future__ import annotations + from collections import defaultdict from enum import IntEnum import logging -from typing import Callable, DefaultDict, Dict, List, Optional, Set, Union +from typing import Callable, DefaultDict from mysensors import BaseAsyncGateway, Message from mysensors.sensor import ChildSensor @@ -35,7 +37,7 @@ async def on_unload( - hass: HomeAssistantType, entry: Union[ConfigEntry, GatewayId], fnct: Callable + hass: HomeAssistantType, entry: ConfigEntry | GatewayId, fnct: Callable ) -> None: """Register a callback to be called when entry is unloaded. @@ -53,7 +55,7 @@ async def on_unload( @callback def discover_mysensors_platform( - hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: List[DevId] + hass: HomeAssistant, gateway_id: GatewayId, platform: str, new_devices: list[DevId] ) -> None: """Discover a MySensors platform.""" _LOGGER.debug("Discovering platform %s with devIds: %s", platform, new_devices) @@ -150,7 +152,7 @@ def invalid_msg( ) -def validate_set_msg(gateway_id: GatewayId, msg: Message) -> Dict[str, List[DevId]]: +def validate_set_msg(gateway_id: GatewayId, msg: Message) -> dict[str, list[DevId]]: """Validate a set message.""" if not validate_node(msg.gateway, msg.node_id): return {} @@ -171,34 +173,34 @@ def validate_child( gateway: BaseAsyncGateway, node_id: int, child: ChildSensor, - value_type: Optional[int] = None, -) -> DefaultDict[str, List[DevId]]: + value_type: int | None = None, +) -> DefaultDict[str, list[DevId]]: """Validate a child. Returns a dict mapping hass platform names to list of DevId.""" - validated: DefaultDict[str, List[DevId]] = defaultdict(list) + validated: DefaultDict[str, list[DevId]] = defaultdict(list) pres: IntEnum = gateway.const.Presentation set_req: IntEnum = gateway.const.SetReq - child_type_name: Optional[SensorType] = next( + child_type_name: SensorType | None = next( (member.name for member in pres if member.value == child.type), None ) - value_types: Set[int] = {value_type} if value_type else {*child.values} - value_type_names: Set[ValueType] = { + value_types: set[int] = {value_type} if value_type else {*child.values} + value_type_names: set[ValueType] = { member.name for member in set_req if member.value in value_types } - platforms: List[str] = TYPE_TO_PLATFORMS.get(child_type_name, []) + platforms: list[str] = TYPE_TO_PLATFORMS.get(child_type_name, []) if not platforms: _LOGGER.warning("Child type %s is not supported", child.type) return validated for platform in platforms: - platform_v_names: Set[ValueType] = FLAT_PLATFORM_TYPES[ + platform_v_names: set[ValueType] = FLAT_PLATFORM_TYPES[ platform, child_type_name ] - v_names: Set[ValueType] = platform_v_names & value_type_names + v_names: set[ValueType] = platform_v_names & value_type_names if not v_names: - child_value_names: Set[ValueType] = { + child_value_names: set[ValueType] = { member.name for member in set_req if member.value in child.values } - v_names: Set[ValueType] = platform_v_names & child_value_names + v_names: set[ValueType] = platform_v_names & child_value_names for v_name in v_names: child_schema_gen = SCHEMAS.get((platform, v_name), default_schema) From 99f9f8dec031d6e8d5f3f5443950d7980fceb739 Mon Sep 17 00:00:00 2001 From: Raj Laud <50647620+rajlaud@users.noreply.github.com> Date: Thu, 18 Mar 2021 07:07:35 -0500 Subject: [PATCH 1355/1818] Allow hdmi_cec to recover from lost connection to adapter without restart (#40714) * Only update CecDevice state when there is new data * Replace CecDevice with CecEntity * Support for losing and reconnecting to pycec TcpAdapter * Register listener in async_added_to_hass * Rename hdmi_cec watchdog * Only update CecDevice state when there is new data * Fix flake8 docstring error * Fix linter error * Bump pycec version to 0.5.0 * Bump pycec version to 0.5.1 * Fixe merge mistake Co-authored-by: Erik Montnemery --- homeassistant/components/hdmi_cec/__init__.py | 31 +++++++++++++++++-- .../components/hdmi_cec/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 2f821c1d3a7ee5..d92342c1fb0974 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -1,6 +1,6 @@ """Support for HDMI CEC.""" from collections import defaultdict -from functools import reduce +from functools import partial, reduce import logging import multiprocessing @@ -38,9 +38,10 @@ STATE_ON, STATE_PAUSED, STATE_PLAYING, + STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery +from homeassistant.helpers import discovery, event import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -162,6 +163,9 @@ extra=vol.ALLOW_EXTRA, ) +WATCHDOG_INTERVAL = 120 +EVENT_HDMI_CEC_UNAVAILABLE = "hdmi_cec_unavailable" + def pad_physical_address(addr): """Right-pad a physical address.""" @@ -210,6 +214,18 @@ def setup(hass: HomeAssistant, base_config): adapter = CecAdapter(name=display_name[:12], activate_source=False) hdmi_network = HDMINetwork(adapter, loop=loop) + def _adapter_watchdog(now=None): + _LOGGER.debug("Reached _adapter_watchdog") + event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog) + if not adapter.initialized: + _LOGGER.info("Adapter not initialized. Trying to restart.") + hass.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE) + adapter.init() + + hdmi_network.set_initialized_callback( + partial(event.async_call_later, hass, WATCHDOG_INTERVAL, _adapter_watchdog) + ) + def _volume(call): """Increase/decrease volume and mute/unmute system.""" mute_key_mapping = { @@ -327,7 +343,7 @@ def _new_device(device): def _shutdown(call): hdmi_network.stop() - def _start_cec(event): + def _start_cec(callback_event): """Register services and start HDMI network to watch for devices.""" hass.services.register( DOMAIN, SERVICE_SEND_COMMAND, _tx, SERVICE_SEND_COMMAND_SCHEMA @@ -364,6 +380,12 @@ def __init__(self, device, logical) -> None: self._logical_address = logical self.entity_id = "%s.%d" % (DOMAIN, self._logical_address) + def _hdmi_cec_unavailable(self, callback_event): + # Change state to unavailable. Without this, entity would remain in + # its last state, since the state changes are pushed. + self._state = STATE_UNAVAILABLE + self.schedule_update_ha_state(False) + def update(self): """Update device status.""" device = self._device @@ -383,6 +405,9 @@ def update(self): async def async_added_to_hass(self): """Register HDMI callbacks after initialization.""" self._device.set_update_callback(self._update) + self.hass.bus.async_listen( + EVENT_HDMI_CEC_UNAVAILABLE, self._hdmi_cec_unavailable + ) def _update(self, device=None): """Device status changed, schedule an update.""" diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index 90ed7cc33592e1..4f6975f52df83f 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -2,6 +2,6 @@ "domain": "hdmi_cec", "name": "HDMI-CEC", "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", - "requirements": ["pyCEC==0.4.14"], + "requirements": ["pyCEC==0.5.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 40a3e8545be2e3..963b262ec6064b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1216,7 +1216,7 @@ py-zabbix==1.1.7 py17track==2.2.2 # homeassistant.components.hdmi_cec -pyCEC==0.4.14 +pyCEC==0.5.1 # homeassistant.components.control4 pyControl4==0.0.6 From 3d2b81a401ba2aaf160517fdbccead44b3bd394c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:21:46 +0100 Subject: [PATCH 1356/1818] Update typing 11 (#48072) --- homeassistant/components/neato/config_flow.py | 9 ++++----- homeassistant/components/nest/camera_sdm.py | 4 ++-- homeassistant/components/nest/climate_sdm.py | 5 ++--- homeassistant/components/nest/config_flow.py | 4 ++-- .../components/nest/device_trigger.py | 6 +++--- homeassistant/components/nest/sensor_sdm.py | 6 +++--- homeassistant/components/netatmo/climate.py | 13 ++++++------ .../components/netatmo/data_handler.py | 8 +++++--- .../components/netatmo/device_trigger.py | 4 ++-- .../components/netatmo/media_source.py | 7 ++++--- .../components/netatmo/netatmo_entity_base.py | 7 ++++--- homeassistant/components/nightscout/sensor.py | 6 ++++-- homeassistant/components/notify/__init__.py | 6 ++++-- .../components/nsw_fuel_station/sensor.py | 5 +++-- .../geo_location.py | 11 +++++----- homeassistant/components/number/__init__.py | 6 ++++-- .../components/number/device_action.py | 10 ++++++---- .../components/number/reproduce_state.py | 12 ++++++----- homeassistant/components/nws/__init__.py | 8 +++++--- .../components/nzbget/config_flow.py | 16 ++++++++------- homeassistant/components/nzbget/sensor.py | 8 +++++--- homeassistant/components/nzbget/switch.py | 6 ++++-- .../components/onewire/onewire_entities.py | 20 ++++++++++--------- .../components/onkyo/media_player.py | 5 +++-- .../components/onvif/binary_sensor.py | 4 ++-- homeassistant/components/onvif/config_flow.py | 5 +++-- homeassistant/components/onvif/device.py | 7 ++++--- homeassistant/components/onvif/event.py | 14 +++++++------ homeassistant/components/onvif/models.py | 6 ++++-- homeassistant/components/onvif/sensor.py | 8 ++++---- .../components/ovo_energy/__init__.py | 6 ++++-- homeassistant/components/ozw/climate.py | 9 +++++---- .../persistent_notification/__init__.py | 8 +++++--- homeassistant/components/person/__init__.py | 12 ++++++----- .../components/person/significant_change.py | 6 ++++-- .../components/philips_js/__init__.py | 14 ++++++------- .../components/philips_js/config_flow.py | 12 ++++++----- .../components/philips_js/device_trigger.py | 6 +++--- .../components/philips_js/media_player.py | 14 +++++++------ .../components/ping/binary_sensor.py | 6 ++++-- homeassistant/components/plaato/sensor.py | 4 ++-- homeassistant/components/plugwise/gateway.py | 4 ++-- .../components/plum_lightpad/config_flow.py | 12 ++++++----- .../components/plum_lightpad/light.py | 6 ++++-- .../components/pvpc_hourly_pricing/sensor.py | 5 +++-- 45 files changed, 206 insertions(+), 154 deletions(-) diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 5f7487fe5ccb79..3f7f7831f54b46 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Neato Botvac.""" +from __future__ import annotations + import logging -from typing import Optional import voluptuous as vol @@ -24,7 +25,7 @@ def logger(self) -> logging.Logger: """Return logger.""" return logging.getLogger(__name__) - async def async_step_user(self, user_input: Optional[dict] = None) -> dict: + async def async_step_user(self, user_input: dict | None = None) -> dict: """Create an entry for the flow.""" current_entries = self._async_current_entries() if current_entries and CONF_TOKEN in current_entries[0].data: @@ -37,9 +38,7 @@ async def async_step_reauth(self, data) -> dict: """Perform reauth upon migration of old entries.""" return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm( - self, user_input: Optional[dict] = None - ) -> dict: + async def async_step_reauth_confirm(self, user_input: dict | None = None) -> dict: """Confirm reauth upon migration of old entries.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index dd84b53d7195ed..ce6ff897a2fac0 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -1,8 +1,8 @@ """Support for Google Nest SDM Cameras.""" +from __future__ import annotations import datetime import logging -from typing import Optional from google_nest_sdm.camera_traits import ( CameraEventImageTrait, @@ -74,7 +74,7 @@ def should_poll(self) -> bool: return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API "name" field is a unique device identifier. return f"{self._device.name}-camera" diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index b0c64329ffdf34..169f9d23957b61 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -1,6 +1,5 @@ """Support for Google Nest SDM climate devices.""" - -from typing import Optional +from __future__ import annotations from google_nest_sdm.device import Device from google_nest_sdm.device_traits import FanTrait, TemperatureTrait @@ -111,7 +110,7 @@ def should_poll(self) -> bool: return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API "name" field is a unique device identifier. return self._device.name diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index 36b0da239a98ad..fd5eef34c7d388 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -11,12 +11,12 @@ new api via NestFlowHandler.register_sdm_api, the custom methods just invoke the AbstractOAuth2FlowHandler methods. """ +from __future__ import annotations import asyncio from collections import OrderedDict import logging import os -from typing import Dict import async_timeout import voluptuous as vol @@ -98,7 +98,7 @@ def logger(self) -> logging.Logger: return logging.getLogger(__name__) @property - def extra_authorize_data(self) -> Dict[str, str]: + def extra_authorize_data(self) -> dict[str, str]: """Extra data that needs to be appended to the authorize url.""" return { "scope": " ".join(SDM_SCOPES), diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index ee16ca1166f874..bd2b59c6cfe25b 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Nest.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -39,7 +39,7 @@ async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str: async def async_get_device_trigger_types( hass: HomeAssistant, nest_device_id: str -) -> List[str]: +) -> list[str]: """List event triggers supported for a Nest device.""" # All devices should have already been loaded so any failures here are # "shouldn't happen" cases @@ -58,7 +58,7 @@ async def async_get_device_trigger_types( return trigger_types -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for a Nest device.""" nest_device_id = await async_get_nest_device_id(hass, device_id) if not nest_device_id: diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 52490f41f8691e..946be2e95b016c 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -1,7 +1,7 @@ """Support for Google Nest SDM sensors.""" +from __future__ import annotations import logging -from typing import Optional from google_nest_sdm.device import Device from google_nest_sdm.device_traits import HumidityTrait, TemperatureTrait @@ -67,7 +67,7 @@ def should_poll(self) -> bool: return False @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API "name" field is a unique device identifier. return f"{self._device.name}-{self.device_class}" @@ -113,7 +113,7 @@ class HumiditySensor(SensorBase): """Representation of a Humidity Sensor.""" @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" # The API returns the identifier under the name field. return f"{self._device.name}-humidity" diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 3b05e263f023ab..6558717b847d7d 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -1,6 +1,7 @@ """Support for Netatmo Smart thermostats.""" +from __future__ import annotations + import logging -from typing import List, Optional import pyatmo import voluptuous as vol @@ -320,7 +321,7 @@ def target_temperature(self): return self._target_temperature @property - def target_temperature_step(self) -> Optional[float]: + def target_temperature_step(self) -> float | None: """Return the supported step of target temperature.""" return PRECISION_HALVES @@ -335,7 +336,7 @@ def hvac_modes(self): return self._operation_list @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if self._model == NA_THERM and self._boilerstatus is not None: return CURRENT_HVAC_MAP_NETATMO[self._boilerstatus] @@ -400,12 +401,12 @@ def set_preset_mode(self, preset_mode: str) -> None: self.async_write_ha_state() @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" return self._preset @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return SUPPORT_PRESET @@ -631,7 +632,7 @@ def interpolate(batterylevel: int, module_type: str) -> int: return int(pct) -def get_all_home_ids(home_data: pyatmo.HomeData) -> List[str]: +def get_all_home_ids(home_data: pyatmo.HomeData) -> list[str]: """Get all the home ids returned by NetAtmo API.""" if home_data is None: return [] diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index be0120bd1a0a31..358ae79edd2b4b 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -1,11 +1,13 @@ """The Netatmo data handler.""" +from __future__ import annotations + from collections import deque from datetime import timedelta from functools import partial from itertools import islice import logging from time import time -from typing import Deque, Dict, List +from typing import Deque import pyatmo @@ -55,8 +57,8 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry): """Initialize self.""" self.hass = hass self._auth = hass.data[DOMAIN][entry.entry_id][AUTH] - self.listeners: List[CALLBACK_TYPE] = [] - self._data_classes: Dict = {} + self.listeners: list[CALLBACK_TYPE] = [] + self._data_classes: dict = {} self.data = {} self._queue: Deque = deque() self._webhook: bool = False diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index 38601e981db7cc..3893fbed4c72aa 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Netatmo.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -82,7 +82,7 @@ async def async_validate_trigger_config(hass, config): return config -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Netatmo devices.""" registry = await entity_registry.async_get_registry(hass) device_registry = await hass.helpers.device_registry.async_get_registry() diff --git a/homeassistant/components/netatmo/media_source.py b/homeassistant/components/netatmo/media_source.py index 6375c46d394f51..db00df5129f257 100644 --- a/homeassistant/components/netatmo/media_source.py +++ b/homeassistant/components/netatmo/media_source.py @@ -1,8 +1,9 @@ """Netatmo Media Source Implementation.""" +from __future__ import annotations + import datetime as dt import logging import re -from typing import Optional, Tuple from homeassistant.components.media_player.const import ( MEDIA_CLASS_DIRECTORY, @@ -53,7 +54,7 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: return PlayMedia(url, MIME_TYPE) async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES + self, item: MediaSourceItem, media_types: tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" try: @@ -156,7 +157,7 @@ def remove_html_tags(text): @callback def async_parse_identifier( item: MediaSourceItem, -) -> Tuple[str, str, Optional[int]]: +) -> tuple[str, str, int | None]: """Parse identifier.""" if not item.identifier: return "events", "", None diff --git a/homeassistant/components/netatmo/netatmo_entity_base.py b/homeassistant/components/netatmo/netatmo_entity_base.py index 1845cbe76e9621..e41b873bdc4d41 100644 --- a/homeassistant/components/netatmo/netatmo_entity_base.py +++ b/homeassistant/components/netatmo/netatmo_entity_base.py @@ -1,6 +1,7 @@ """Base class for Netatmo entities.""" +from __future__ import annotations + import logging -from typing import Dict, List from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.entity import Entity @@ -17,8 +18,8 @@ class NetatmoBase(Entity): def __init__(self, data_handler: NetatmoDataHandler) -> None: """Set up Netatmo entity base.""" self.data_handler = data_handler - self._data_classes: List[Dict] = [] - self._listeners: List[CALLBACK_TYPE] = [] + self._data_classes: list[dict] = [] + self._listeners: list[CALLBACK_TYPE] = [] self._device_name = None self._id = None diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index 53f13f3b69bf24..d190da48a16ca9 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -1,8 +1,10 @@ """Support for Nightscout sensors.""" +from __future__ import annotations + from asyncio import TimeoutError as AsyncIOTimeoutError from datetime import timedelta import logging -from typing import Callable, List +from typing import Callable from aiohttp import ClientError from py_nightscout import Api as NightscoutAPI @@ -24,7 +26,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the Glucose Sensor.""" api = hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 5e1493a68eb6d1..f85f07a0bd5cf8 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -1,8 +1,10 @@ """Provides functionality to notify people.""" +from __future__ import annotations + import asyncio from functools import partial import logging -from typing import Any, Dict, cast +from typing import Any, cast import voluptuous as vol @@ -120,7 +122,7 @@ class BaseNotificationService: hass: HomeAssistantType = None # type: ignore # Name => target - registered_targets: Dict[str, str] + registered_targets: dict[str, str] def send_message(self, message, **kwargs): """Send a message. diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 0bf82c1d162708..92a071ec439925 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -1,7 +1,8 @@ """Sensor platform to display the current fuel prices at a NSW fuel station.""" +from __future__ import annotations + import datetime import logging -from typing import Optional from nsw_fuel import FuelCheckClient, FuelCheckError import voluptuous as vol @@ -159,7 +160,7 @@ def name(self) -> str: return f"{self._station_data.get_station_name()} {self._fuel_type}" @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the state of the sensor.""" price_info = self._station_data.for_fuel_type(self._fuel_type) if price_info: diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py index 0467b9cc353e02..08e62e6c6a3768 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py +++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py @@ -1,7 +1,8 @@ """Support for NSW Rural Fire Service Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from aio_geojson_nsw_rfs_incidents import NswRuralFireServiceIncidentsFeedManager import voluptuous as vol @@ -259,22 +260,22 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/number/__init__.py b/homeassistant/components/number/__init__.py index 933b07e4f58fce..e61398f6582f3d 100644 --- a/homeassistant/components/number/__init__.py +++ b/homeassistant/components/number/__init__.py @@ -1,8 +1,10 @@ """Component to allow numeric input for platforms.""" +from __future__ import annotations + from abc import abstractmethod from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any import voluptuous as vol @@ -66,7 +68,7 @@ class NumberEntity(Entity): """Representation of a Number entity.""" @property - def capability_attributes(self) -> Dict[str, Any]: + def capability_attributes(self) -> dict[str, Any]: """Return capability attributes.""" return { ATTR_MIN: self.min_value, diff --git a/homeassistant/components/number/device_action.py b/homeassistant/components/number/device_action.py index c22ba720e374e8..1a26226962cce8 100644 --- a/homeassistant/components/number/device_action.py +++ b/homeassistant/components/number/device_action.py @@ -1,5 +1,7 @@ """Provides device actions for Number.""" -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import Any import voluptuous as vol @@ -27,10 +29,10 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Number.""" registry = await entity_registry.async_get_registry(hass) - actions: List[Dict[str, Any]] = [] + actions: list[dict[str, Any]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): @@ -50,7 +52,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/number/reproduce_state.py b/homeassistant/components/number/reproduce_state.py index 611744e3191eaa..4364dffe1e8531 100644 --- a/homeassistant/components/number/reproduce_state.py +++ b/homeassistant/components/number/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce a Number entity state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -16,8 +18,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -50,8 +52,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce multiple Number states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 83fca527d4376c..569a8adf83b709 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -1,8 +1,10 @@ """The National Weather Service integration.""" +from __future__ import annotations + import asyncio import datetime import logging -from typing import Awaitable, Callable, Optional +from typing import Awaitable, Callable from pynws import SimpleNWS @@ -58,8 +60,8 @@ def __init__( name: str, update_interval: datetime.timedelta, failed_update_interval: datetime.timedelta, - update_method: Optional[Callable[[], Awaitable]] = None, - request_refresh_debouncer: Optional[debounce.Debouncer] = None, + update_method: Callable[[], Awaitable] | None = None, + request_refresh_debouncer: debounce.Debouncer | None = None, ): """Initialize NWS coordinator.""" super().__init__( diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index f593eeb0729c4c..eb81540e39e7d7 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -1,6 +1,8 @@ """Config flow for NZBGet.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any import voluptuous as vol @@ -31,7 +33,7 @@ _LOGGER = logging.getLogger(__name__) -def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -63,8 +65,8 @@ def async_get_options_flow(config_entry): return NZBGetOptionsFlowHandler(config_entry) async def async_step_import( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by configuration file.""" if CONF_SCAN_INTERVAL in user_input: user_input[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL].seconds @@ -72,8 +74,8 @@ async def async_step_import( return await self.async_step_user(user_input) async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -127,7 +129,7 @@ def __init__(self, config_entry): """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Optional[ConfigType] = None): + async def async_step_init(self, user_input: ConfigType | None = None): """Manage NZBGet options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index b4133e7550d985..a8870db52a3552 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -1,7 +1,9 @@ """Monitor the NZBGet API.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, List, Optional +from typing import Callable from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -41,7 +43,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up NZBGet sensor based on a config entry.""" coordinator: NZBGetDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ @@ -74,7 +76,7 @@ def __init__( entry_name: str, sensor_type: str, sensor_name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ): """Initialize a new NZBGet sensor.""" self._sensor_type = sensor_type diff --git a/homeassistant/components/nzbget/switch.py b/homeassistant/components/nzbget/switch.py index c4ceaab5ded353..4f0eae17c23dc1 100644 --- a/homeassistant/components/nzbget/switch.py +++ b/homeassistant/components/nzbget/switch.py @@ -1,5 +1,7 @@ """Support for NZBGet switches.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -15,7 +17,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up NZBGet sensor based on a config entry.""" coordinator: NZBGetDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index 724783b5686e8e..e8c013094f7c9b 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -1,6 +1,8 @@ """Support for 1-Wire entities.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from pyownet import protocol @@ -43,32 +45,32 @@ def __init__( self._unique_id = unique_id or device_file @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" return self._device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit the value is expressed in.""" return self._unit_of_measurement @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return {"device_file": self._device_file, "raw_value": self._value_raw} @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Return device specific attributes.""" return self._device_info @@ -85,9 +87,9 @@ def __init__( self, device_id: str, device_name: str, - device_info: Dict[str, Any], + device_info: dict[str, Any], entity_path: str, - entity_specs: Dict[str, Any], + entity_specs: dict[str, Any], owproxy: protocol._Proxy, ): """Initialize the sensor.""" diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 3d882884aa2be3..2e4b6eff6da399 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -1,6 +1,7 @@ """Support for Onkyo Receivers.""" +from __future__ import annotations + import logging -from typing import List import eiscp from eiscp import eISCP @@ -56,7 +57,7 @@ | SUPPORT_PLAY_MEDIA ) -KNOWN_HOSTS: List[str] = [] +KNOWN_HOSTS: list[str] = [] DEFAULT_SOURCES = { "tv": "TV", "bd": "Bluray", diff --git a/homeassistant/components/onvif/binary_sensor.py b/homeassistant/components/onvif/binary_sensor.py index 9b5469ee0d0cb0..680f92efd2bc5d 100644 --- a/homeassistant/components/onvif/binary_sensor.py +++ b/homeassistant/components/onvif/binary_sensor.py @@ -1,5 +1,5 @@ """Support for ONVIF binary sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback @@ -55,7 +55,7 @@ def name(self) -> str: return self.device.events.get_uid(self.uid).name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return self.device.events.get_uid(self.uid).device_class diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 273640ab6b5371..6c6e155a0462f4 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -1,6 +1,7 @@ """Config flow for ONVIF.""" +from __future__ import annotations + from pprint import pformat -from typing import List from urllib.parse import urlparse from onvif.exceptions import ONVIFError @@ -36,7 +37,7 @@ CONF_MANUAL_INPUT = "Manually configure ONVIF device" -def wsdiscovery() -> List[Service]: +def wsdiscovery() -> list[Service]: """Get ONVIF Profile S devices from network.""" discovery = WSDiscovery(ttl=4) discovery.start() diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index c0851cbe32f04a..07be1fbfd03b41 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -1,8 +1,9 @@ """ONVIF device abstraction.""" +from __future__ import annotations + import asyncio import datetime as dt import os -from typing import List from httpx import RequestError import onvif @@ -50,7 +51,7 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry = None): self.info: DeviceInfo = DeviceInfo() self.capabilities: Capabilities = Capabilities() - self.profiles: List[Profile] = [] + self.profiles: list[Profile] = [] self.max_resolution: int = 0 self._dt_diff_seconds: int = 0 @@ -262,7 +263,7 @@ async def async_get_capabilities(self): return Capabilities(snapshot, pullpoint, ptz) - async def async_get_profiles(self) -> List[Profile]: + async def async_get_profiles(self) -> list[Profile]: """Obtain media profiles for this device.""" media_service = self.device.create_media_service() result = await media_service.GetProfiles() diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index eaf23236042be6..064f0dcfa0fc7a 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -1,7 +1,9 @@ """ONVIF event abstraction.""" +from __future__ import annotations + import asyncio import datetime as dt -from typing import Callable, Dict, List, Optional, Set +from typing import Callable from httpx import RemoteProtocolError, TransportError from onvif import ONVIFCamera, ONVIFService @@ -34,14 +36,14 @@ def __init__(self, hass: HomeAssistant, device: ONVIFCamera, unique_id: str): self.started: bool = False self._subscription: ONVIFService = None - self._events: Dict[str, Event] = {} - self._listeners: List[CALLBACK_TYPE] = [] - self._unsub_refresh: Optional[CALLBACK_TYPE] = None + self._events: dict[str, Event] = {} + self._listeners: list[CALLBACK_TYPE] = [] + self._unsub_refresh: CALLBACK_TYPE | None = None super().__init__() @property - def platforms(self) -> Set[str]: + def platforms(self) -> set[str]: """Return platforms to setup.""" return {event.platform for event in self._events.values()} @@ -229,6 +231,6 @@ def get_uid(self, uid) -> Event: """Retrieve event for given id.""" return self._events[uid] - def get_platform(self, platform) -> List[Event]: + def get_platform(self, platform) -> list[Event]: """Retrieve events for given platform.""" return [event for event in self._events.values() if event.platform == platform] diff --git a/homeassistant/components/onvif/models.py b/homeassistant/components/onvif/models.py index 2a129d3bc44427..feda891f7729b8 100644 --- a/homeassistant/components/onvif/models.py +++ b/homeassistant/components/onvif/models.py @@ -1,6 +1,8 @@ """ONVIF models.""" +from __future__ import annotations + from dataclasses import dataclass -from typing import Any, List +from typing import Any @dataclass @@ -37,7 +39,7 @@ class PTZ: continuous: bool relative: bool absolute: bool - presets: List[str] = None + presets: list[str] = None @dataclass diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index b1d7ff7986cc46..3895e8a4abbf96 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -1,5 +1,5 @@ """Support for ONVIF binary sensors.""" -from typing import Optional, Union +from __future__ import annotations from homeassistant.core import callback @@ -43,7 +43,7 @@ def __init__(self, uid, device): super().__init__(device) @property - def state(self) -> Union[None, str, int, float]: + def state(self) -> None | str | int | float: """Return the state of the entity.""" return self.device.events.get_uid(self.uid).value @@ -53,12 +53,12 @@ def name(self): return self.device.events.get_uid(self.uid).name @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return self.device.events.get_uid(self.uid).device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" return self.device.events.get_uid(self.uid).unit_of_measurement diff --git a/homeassistant/components/ovo_energy/__init__.py b/homeassistant/components/ovo_energy/__init__.py index 0130ba30c3046c..7bf79f47991209 100644 --- a/homeassistant/components/ovo_energy/__init__.py +++ b/homeassistant/components/ovo_energy/__init__.py @@ -1,7 +1,9 @@ """Support for OVO Energy.""" +from __future__ import annotations + from datetime import datetime, timedelta import logging -from typing import Any, Dict +from typing import Any import aiohttp import async_timeout @@ -148,7 +150,7 @@ class OVOEnergyDeviceEntity(OVOEnergyEntity): """Defines a OVO Energy device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this OVO Energy instance.""" return { "identifiers": {(DOMAIN, self._client.account_id)}, diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py index a6532af4d2624b..e403c4f5517b71 100644 --- a/homeassistant/components/ozw/climate.py +++ b/homeassistant/components/ozw/climate.py @@ -1,7 +1,8 @@ """Support for Z-Wave climate devices.""" +from __future__ import annotations + from enum import IntEnum import logging -from typing import Optional, Tuple from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( @@ -239,12 +240,12 @@ def target_temperature(self): return self._current_mode_setpoint_values[0].value @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._current_mode_setpoint_values[0].value @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._current_mode_setpoint_values[1].value @@ -333,7 +334,7 @@ def supported_features(self): support |= SUPPORT_PRESET_MODE return support - def _get_current_mode_setpoint_values(self) -> Tuple: + def _get_current_mode_setpoint_values(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" if not self.values.mode: setpoint_names = ("setpoint_heating",) diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 589cc97baeafd9..05d52cf7830e3f 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -1,7 +1,9 @@ """Support for displaying persistent notifications.""" +from __future__ import annotations + from collections import OrderedDict import logging -from typing import Any, Mapping, MutableMapping, Optional +from typing import Any, Mapping, MutableMapping import voluptuous as vol @@ -71,8 +73,8 @@ def dismiss(hass, notification_id): def async_create( hass: HomeAssistant, message: str, - title: Optional[str] = None, - notification_id: Optional[str] = None, + title: str | None = None, + notification_id: str | None = None, ) -> None: """Generate a notification.""" data = { diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index d3e17d904eae86..cf6403b7334208 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -1,6 +1,8 @@ """Support for tracking people.""" +from __future__ import annotations + import logging -from typing import List, Optional, cast +from typing import cast import voluptuous as vol @@ -171,7 +173,7 @@ def __init__( super().__init__(store, logger, id_manager) self.yaml_collection = yaml_collection - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data. A past bug caused onboarding to create invalid person objects. @@ -257,7 +259,7 @@ async def _validate_user_id(self, user_id): raise ValueError("User already taken") -async def filter_yaml_data(hass: HomeAssistantType, persons: List[dict]) -> List[dict]: +async def filter_yaml_data(hass: HomeAssistantType, persons: list[dict]) -> list[dict]: """Validate YAML data that we can't validate via schema.""" filtered = [] person_invalid_user = [] @@ -380,7 +382,7 @@ def name(self): return self._config[CONF_NAME] @property - def entity_picture(self) -> Optional[str]: + def entity_picture(self) -> str | None: """Return entity picture.""" return self._config.get(CONF_PICTURE) @@ -522,7 +524,7 @@ def ws_list_person( ) -def _get_latest(prev: Optional[State], curr: State): +def _get_latest(prev: State | None, curr: State): """Get latest state.""" if prev is None or curr.last_updated > prev.last_updated: return curr diff --git a/homeassistant/components/person/significant_change.py b/homeassistant/components/person/significant_change.py index d9c1ec6cc2347a..680b9194144aa6 100644 --- a/homeassistant/components/person/significant_change.py +++ b/homeassistant/components/person/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Person state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,7 +14,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" if new_state != old_state: diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 088e29e4e26b6e..7be5efeaf2fd7b 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -1,8 +1,10 @@ """The Philips TV integration.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from haphilipsjs import ConnectionFailure, PhilipsTV @@ -77,14 +79,14 @@ class PluggableAction: def __init__(self, update: Callable[[], None]): """Initialize.""" self._update = update - self._actions: Dict[Any, AutomationActionType] = {} + self._actions: dict[Any, AutomationActionType] = {} def __bool__(self): """Return if we have something attached.""" return bool(self._actions) @callback - def async_attach(self, action: AutomationActionType, variables: Dict[str, Any]): + def async_attach(self, action: AutomationActionType, variables: dict[str, Any]): """Attach a device trigger for turn on.""" @callback @@ -99,9 +101,7 @@ def _remove(): return _remove - async def async_run( - self, hass: HomeAssistantType, context: Optional[Context] = None - ): + async def async_run(self, hass: HomeAssistantType, context: Context | None = None): """Run all turn on triggers.""" for job, variables in self._actions.values(): hass.async_run_hass_job(job, variables, context) @@ -113,7 +113,7 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): def __init__(self, hass, api: PhilipsTV) -> None: """Set up the coordinator.""" self.api = api - self._notify_future: Optional[asyncio.Task] = None + self._notify_future: asyncio.Task | None = None @callback def _update_listeners(): diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index 39150be9ce8562..7b06558f3fef02 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Philips TV integration.""" +from __future__ import annotations + import platform -from typing import Any, Dict, Optional, Tuple +from typing import Any from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol @@ -25,7 +27,7 @@ async def validate_input( hass: core.HomeAssistant, host: str, api_version: int -) -> Tuple[Dict, PhilipsTV]: +) -> tuple[dict, PhilipsTV]: """Validate the user input allows us to connect.""" hub = PhilipsTV(host, api_version) @@ -48,7 +50,7 @@ def __init__(self) -> None: """Initialize flow.""" super().__init__() self._current = {} - self._hub: Optional[PhilipsTV] = None + self._hub: PhilipsTV | None = None self._pair_state: Any = None async def async_step_import(self, conf: dict) -> dict: @@ -72,7 +74,7 @@ async def _async_create_current(self): data=self._current, ) - async def async_step_pair(self, user_input: Optional[dict] = None) -> dict: + async def async_step_pair(self, user_input: dict | None = None) -> dict: """Attempt to pair with device.""" assert self._hub @@ -123,7 +125,7 @@ async def async_step_pair(self, user_input: Optional[dict] = None) -> dict: self._current[CONF_PASSWORD] = password return await self._async_create_current() - async def async_step_user(self, user_input: Optional[dict] = None) -> dict: + async def async_step_user(self, user_input: dict | None = None) -> dict: """Handle the initial step.""" errors = {} if user_input: diff --git a/homeassistant/components/philips_js/device_trigger.py b/homeassistant/components/philips_js/device_trigger.py index 2a60a1664bcddf..615d758735eb4b 100644 --- a/homeassistant/components/philips_js/device_trigger.py +++ b/homeassistant/components/philips_js/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for control of device.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -23,7 +23,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for device.""" triggers = [] triggers.append( @@ -43,7 +43,7 @@ async def async_attach_trigger( config: ConfigType, action: AutomationActionType, automation_info: dict, -) -> Optional[CALLBACK_TYPE]: +) -> CALLBACK_TYPE | None: """Attach a trigger.""" registry: DeviceRegistry = await async_get_registry(hass) if config[CONF_TYPE] == TRIGGER_TYPE_TURN_ON: diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index f3b2fe651e231a..83adf61ed1e4c3 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,5 +1,7 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from haphilipsjs import ConnectionFailure import voluptuous as vol @@ -125,7 +127,7 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): def __init__( self, coordinator: PhilipsTVDataUpdateCoordinator, - system: Dict[str, Any], + system: dict[str, Any], unique_id: str, ): """Initialize the Philips TV.""" @@ -137,10 +139,10 @@ def __init__( self._system = system self._unique_id = unique_id self._state = STATE_OFF - self._media_content_type: Optional[str] = None - self._media_content_id: Optional[str] = None - self._media_title: Optional[str] = None - self._media_channel: Optional[str] = None + self._media_content_type: str | None = None + self._media_content_id: str | None = None + self._media_title: str | None = None + self._media_channel: str | None = None super().__init__(coordinator) self._update_from_coordinator() diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index a91f7235254a86..e660b5af38dbf2 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -1,11 +1,13 @@ """Tracks the latency of a host by sending ICMP echo requests (ping).""" +from __future__ import annotations + import asyncio from datetime import timedelta from functools import partial import logging import re import sys -from typing import Any, Dict +from typing import Any from icmplib import SocketPermissionError, ping as icmp_ping import voluptuous as vol @@ -105,7 +107,7 @@ def is_on(self) -> bool: return self._ping.available @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the ICMP checo request.""" if self._ping.data is not False: return { diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index ae767add18b9d2..c2c59d347f36ba 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -1,5 +1,5 @@ """Support for Plaato Airlock sensors.""" -from typing import Optional +from __future__ import annotations from pyplaato.models.device import PlaatoDevice from pyplaato.plaato import PlaatoKeg @@ -63,7 +63,7 @@ class PlaatoSensor(PlaatoEntity): """Representation of a Plaato Sensor.""" @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" if self._coordinator is not None: if self._sensor_type == PlaatoKeg.Pins.TEMPERATURE: diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 280de59898b80d..4e0e31810cb131 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -1,9 +1,9 @@ """Plugwise platform for Home Assistant Core.""" +from __future__ import annotations import asyncio from datetime import timedelta import logging -from typing import Dict import async_timeout from plugwise.exceptions import ( @@ -201,7 +201,7 @@ def name(self): return self._name @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" device_information = { "identifiers": {(DOMAIN, self._dev_id)}, diff --git a/homeassistant/components/plum_lightpad/config_flow.py b/homeassistant/components/plum_lightpad/config_flow.py index c6261307d135fb..561f071bc7bc33 100644 --- a/homeassistant/components/plum_lightpad/config_flow.py +++ b/homeassistant/components/plum_lightpad/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Plum Lightpad.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from aiohttp import ContentTypeError from requests.exceptions import ConnectTimeout, HTTPError @@ -34,8 +36,8 @@ def _show_form(self, errors=None): ) async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initialized by the user or redirected to by import.""" if not user_input: return self._show_form() @@ -58,7 +60,7 @@ async def async_step_user( ) async def async_step_import( - self, import_config: Optional[ConfigType] - ) -> Dict[str, Any]: + self, import_config: ConfigType | None + ) -> dict[str, Any]: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index feacec4492b8b3..90558eb252380c 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -1,6 +1,8 @@ """Support for Plum Lightpad lights.""" +from __future__ import annotations + import asyncio -from typing import Callable, List +from typing import Callable from plumlightpad import Plum @@ -23,7 +25,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity]], None], + async_add_entities: Callable[[list[Entity]], None], ) -> None: """Set up Plum Lightpad dimmer lights and glow rings.""" diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index b3486f9d53458d..3aca4db67e72ce 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -1,7 +1,8 @@ """Sensor to collect the reference daily prices of electricity ('PVPC') in Spain.""" +from __future__ import annotations + import logging from random import randint -from typing import Optional from aiopvpc import PVPCData @@ -92,7 +93,7 @@ async def async_added_to_hass(self): self.update_current_price(dt_util.utcnow()) @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return a unique ID.""" return self._unique_id From 6cd6ad69040311df103da7f23ac2d35389677880 Mon Sep 17 00:00:00 2001 From: Berni Moses Date: Thu, 18 Mar 2021 14:06:17 +0100 Subject: [PATCH 1357/1818] Ignore not implemented lg_soundbar source/equaliser. (#45868) --- homeassistant/components/lg_soundbar/media_player.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index c2d196196f9997..78bf7e050ef45a 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -159,7 +159,8 @@ def sound_mode_list(self): """Return the available sound modes.""" modes = [] for equaliser in self._equalisers: - modes.append(temescal.equalisers[equaliser]) + if equaliser < len(temescal.equalisers): + modes.append(temescal.equalisers[equaliser]) return sorted(modes) @property @@ -174,7 +175,8 @@ def source_list(self): """List of available input sources.""" sources = [] for function in self._functions: - sources.append(temescal.functions[function]) + if function < len(temescal.functions): + sources.append(temescal.functions[function]) return sorted(sources) @property From a3cd1854f6fe0cdf83aab56de0bc5932aed8ed74 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:31:38 +0100 Subject: [PATCH 1358/1818] Update typing 12 (#48073) --- .../components/qld_bushfire/geo_location.py | 11 ++--- homeassistant/components/rachio/device.py | 5 ++- .../components/recollect_waste/__init__.py | 5 ++- .../components/recollect_waste/config_flow.py | 4 +- .../components/recollect_waste/sensor.py | 8 ++-- homeassistant/components/recorder/__init__.py | 12 ++--- homeassistant/components/remote/__init__.py | 10 +++-- .../components/remote/device_action.py | 4 +- .../components/remote/device_condition.py | 4 +- .../components/remote/device_trigger.py | 4 +- .../components/remote/reproduce_state.py | 12 ++--- homeassistant/components/ring/__init__.py | 5 ++- homeassistant/components/roku/__init__.py | 8 ++-- homeassistant/components/roku/config_flow.py | 20 ++++----- homeassistant/components/roku/media_player.py | 7 +-- homeassistant/components/roku/remote.py | 8 ++-- homeassistant/components/route53/__init__.py | 5 ++- .../components/rpi_power/config_flow.py | 8 ++-- .../ruckus_unleashed/device_tracker.py | 4 +- homeassistant/components/scene/__init__.py | 6 ++- homeassistant/components/script/__init__.py | 11 ++--- .../components/sensor/device_condition.py | 6 +-- .../components/sensor/significant_change.py | 8 ++-- homeassistant/components/sentry/__init__.py | 7 +-- .../components/sentry/config_flow.py | 10 ++--- .../components/sharkiq/config_flow.py | 6 +-- .../components/sharkiq/update_coordinator.py | 8 ++-- homeassistant/components/sharkiq/vacuum.py | 22 +++++----- .../components/shelly/device_trigger.py | 4 +- homeassistant/components/shelly/entity.py | 26 ++++++----- homeassistant/components/shelly/light.py | 8 ++-- homeassistant/components/shelly/utils.py | 6 +-- .../components/smartthings/binary_sensor.py | 6 ++- .../components/smartthings/climate.py | 8 ++-- homeassistant/components/smartthings/cover.py | 6 ++- homeassistant/components/smartthings/fan.py | 6 ++- homeassistant/components/smartthings/light.py | 6 ++- homeassistant/components/smartthings/lock.py | 6 ++- .../components/smartthings/sensor.py | 6 ++- .../components/smartthings/switch.py | 6 ++- homeassistant/components/smhi/weather.py | 7 +-- homeassistant/components/somfy/api.py | 5 ++- homeassistant/components/somfy/climate.py | 9 ++-- homeassistant/components/sonarr/__init__.py | 8 ++-- .../components/sonarr/config_flow.py | 24 +++++----- homeassistant/components/sonarr/sensor.py | 24 +++++----- .../components/songpal/config_flow.py | 5 ++- .../components/spotify/config_flow.py | 16 ++++--- .../components/spotify/media_player.py | 44 ++++++++++--------- homeassistant/components/starline/account.py | 20 +++++---- .../components/starline/config_flow.py | 12 ++--- homeassistant/components/starline/entity.py | 6 ++- homeassistant/components/stream/core.py | 6 ++- homeassistant/components/stream/recorder.py | 6 ++- homeassistant/components/stt/__init__.py | 25 ++++++----- homeassistant/components/supla/__init__.py | 5 ++- .../components/surepetcare/__init__.py | 8 ++-- .../components/surepetcare/binary_sensor.py | 16 ++++--- .../components/surepetcare/sensor.py | 18 ++++---- .../components/switch/device_action.py | 4 +- .../components/switch/device_condition.py | 4 +- .../components/switch/device_trigger.py | 4 +- homeassistant/components/switch/light.py | 8 ++-- .../components/switch/reproduce_state.py | 12 ++--- .../components/switch/significant_change.py | 6 ++- homeassistant/components/switchbot/switch.py | 6 ++- .../components/switcher_kis/__init__.py | 7 +-- .../components/switcher_kis/switch.py | 14 +++--- homeassistant/components/syncthru/__init__.py | 6 +-- .../components/synology_dsm/__init__.py | 13 +++--- .../components/synology_dsm/binary_sensor.py | 4 +- .../components/synology_dsm/camera.py | 5 ++- .../components/synology_dsm/sensor.py | 5 ++- .../components/synology_dsm/switch.py | 7 +-- .../components/system_health/__init__.py | 20 +++++---- 75 files changed, 399 insertions(+), 312 deletions(-) diff --git a/homeassistant/components/qld_bushfire/geo_location.py b/homeassistant/components/qld_bushfire/geo_location.py index 0887e6b7cdd0f6..669e9d1e884cfa 100644 --- a/homeassistant/components/qld_bushfire/geo_location.py +++ b/homeassistant/components/qld_bushfire/geo_location.py @@ -1,7 +1,8 @@ """Support for Queensland Bushfire Alert Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from georss_qld_bushfire_alert_client import QldBushfireAlertFeedManager import voluptuous as vol @@ -209,22 +210,22 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py index c9de7eea7d44bb..a6ed596db04216 100644 --- a/homeassistant/components/rachio/device.py +++ b/homeassistant/components/rachio/device.py @@ -1,6 +1,7 @@ """Adapter to wrap the rachiopy api for home assistant.""" +from __future__ import annotations + import logging -from typing import Optional import voluptuous as vol @@ -239,7 +240,7 @@ def list_zones(self, include_disabled=False) -> list: # Only enabled zones return [z for z in self._zones if z[KEY_ENABLED]] - def get_zone(self, zone_id) -> Optional[dict]: + def get_zone(self, zone_id) -> dict | None: """Return the zone with the given ID.""" for zone in self.list_zones(include_disabled=True): if zone[KEY_ID] == zone_id: diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py index 0d6961831d80d1..f37cabdd69803a 100644 --- a/homeassistant/components/recollect_waste/__init__.py +++ b/homeassistant/components/recollect_waste/__init__.py @@ -1,7 +1,8 @@ """The ReCollect Waste integration.""" +from __future__ import annotations + import asyncio from datetime import date, timedelta -from typing import List from aiorecollect.client import Client, PickupEvent from aiorecollect.errors import RecollectError @@ -35,7 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data[CONF_PLACE_ID], entry.data[CONF_SERVICE_ID], session=session ) - async def async_get_pickup_events() -> List[PickupEvent]: + async def async_get_pickup_events() -> list[PickupEvent]: """Get the next pickup.""" try: return await client.async_get_pickup_events( diff --git a/homeassistant/components/recollect_waste/config_flow.py b/homeassistant/components/recollect_waste/config_flow.py index 8e208f57cc6c78..37dca6a064ea98 100644 --- a/homeassistant/components/recollect_waste/config_flow.py +++ b/homeassistant/components/recollect_waste/config_flow.py @@ -1,5 +1,5 @@ """Config flow for ReCollect Waste integration.""" -from typing import Optional +from __future__ import annotations from aiorecollect.client import Client from aiorecollect.errors import RecollectError @@ -83,7 +83,7 @@ def __init__(self, entry: config_entries.ConfigEntry): """Initialize.""" self._entry = entry - async def async_step_init(self, user_input: Optional[dict] = None): + async def async_step_init(self, user_input: dict | None = None): """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 0822cdb1f3a312..b6a99f4ebea198 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -1,5 +1,7 @@ """Support for ReCollect Waste sensors.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from aiorecollect.client import PickupType import voluptuous as vol @@ -36,8 +38,8 @@ @callback def async_get_pickup_type_names( - entry: ConfigEntry, pickup_types: List[PickupType] -) -> List[str]: + entry: ConfigEntry, pickup_types: list[PickupType] +) -> list[str]: """Return proper pickup type names from their associated objects.""" return [ t.friendly_name diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 12fb1b38c25b6a..cff8119356f0bc 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,4 +1,6 @@ """Support for recording details.""" +from __future__ import annotations + import asyncio import concurrent.futures from datetime import datetime @@ -7,7 +9,7 @@ import sqlite3 import threading import time -from typing import Any, Callable, List, NamedTuple, Optional +from typing import Any, Callable, NamedTuple from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select from sqlalchemy.orm import scoped_session, sessionmaker @@ -125,7 +127,7 @@ ) -def run_information(hass, point_in_time: Optional[datetime] = None): +def run_information(hass, point_in_time: datetime | None = None): """Return information about current run. There is also the run that covers point_in_time. @@ -138,7 +140,7 @@ def run_information(hass, point_in_time: Optional[datetime] = None): return run_information_with_session(session, point_in_time) -def run_information_from_instance(hass, point_in_time: Optional[datetime] = None): +def run_information_from_instance(hass, point_in_time: datetime | None = None): """Return information about current run from the existing instance. Does not query the database for older runs. @@ -149,7 +151,7 @@ def run_information_from_instance(hass, point_in_time: Optional[datetime] = None return ins.run_info -def run_information_with_session(session, point_in_time: Optional[datetime] = None): +def run_information_with_session(session, point_in_time: datetime | None = None): """Return information about current run from the database.""" recorder_runs = RecorderRuns @@ -249,7 +251,7 @@ def __init__( db_max_retries: int, db_retry_wait: int, entity_filter: Callable[[str], bool], - exclude_t: List[str], + exclude_t: list[str], db_integrity_check: bool, ) -> None: """Initialize the recorder.""" diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 12b81402d616ad..fe220dd46a8349 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -1,8 +1,10 @@ """Support to interface with universal remote control devices.""" +from __future__ import annotations + from datetime import timedelta import functools as ft import logging -from typing import Any, Dict, Iterable, List, Optional, cast +from typing import Any, Iterable, cast import voluptuous as vol @@ -147,17 +149,17 @@ def supported_features(self) -> int: return 0 @property - def current_activity(self) -> Optional[str]: + def current_activity(self) -> str | None: """Active activity.""" return None @property - def activity_list(self) -> Optional[List[str]]: + def activity_list(self) -> list[str] | None: """List of available activities.""" return None @property - def state_attributes(self) -> Optional[Dict[str, Any]]: + def state_attributes(self) -> dict[str, Any] | None: """Return optional state attributes.""" if not self.supported_features & SUPPORT_ACTIVITY: return None diff --git a/homeassistant/components/remote/device_action.py b/homeassistant/components/remote/device_action.py index aa819f3eb46812..aa34eb33224c20 100644 --- a/homeassistant/components/remote/device_action.py +++ b/homeassistant/components/remote/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for remotes.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -25,6 +25,6 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" return await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/device_condition.py b/homeassistant/components/remote/device_condition.py index 06c7bec89d48fe..ed200fd5579c03 100644 --- a/homeassistant/components/remote/device_condition.py +++ b/homeassistant/components/remote/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for remotes.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ def async_condition_from_config( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/device_trigger.py b/homeassistant/components/remote/device_trigger.py index 5919e8c61ba43c..d8437604f6dc0d 100644 --- a/homeassistant/components/remote/device_trigger.py +++ b/homeassistant/components/remote/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for remotes.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/remote/reproduce_state.py b/homeassistant/components/remote/reproduce_state.py index 4e1f426c57b0ee..b42a0bdc611a71 100644 --- a/homeassistant/components/remote/reproduce_state.py +++ b/homeassistant/components/remote/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Remote state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Remote states.""" await asyncio.gather( diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index a4d005d3c44a8f..f5211ac54c03c4 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -1,10 +1,11 @@ """Support for Ring Doorbell/Chimes.""" +from __future__ import annotations + import asyncio from datetime import timedelta from functools import partial import logging from pathlib import Path -from typing import Optional from oauthlib.oauth2 import AccessDeniedError import requests @@ -187,7 +188,7 @@ def async_remove_listener(self, update_callback): self._unsub_interval() self._unsub_interval = None - async def async_refresh_all(self, _now: Optional[int] = None) -> None: + async def async_refresh_all(self, _now: int | None = None) -> None: """Time to update.""" if not self.listeners: return diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index 06e8d0ae848306..174bd71b77ac15 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -1,8 +1,10 @@ """Support for Roku.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from rokuecp import Roku, RokuConnectionError, RokuError from rokuecp.models import Device @@ -38,7 +40,7 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: """Set up the Roku integration.""" hass.data.setdefault(DOMAIN, {}) return True @@ -151,7 +153,7 @@ def name(self) -> str: return self._name @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this Roku device.""" if self._device_id is None: return None diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index b086d7a931130c..87ccd20cbda677 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Roku.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from urllib.parse import urlparse from rokuecp import Roku, RokuError @@ -27,7 +29,7 @@ _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistantType, data: Dict) -> Dict: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -53,7 +55,7 @@ def __init__(self): self.discovery_info = {} @callback - def _show_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the form to the user.""" return self.async_show_form( step_id="user", @@ -61,9 +63,7 @@ def _show_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: errors=errors or {}, ) - async def async_step_user( - self, user_input: Optional[Dict] = None - ) -> Dict[str, Any]: + async def async_step_user(self, user_input: dict | None = None) -> dict[str, Any]: """Handle a flow initialized by the user.""" if not user_input: return self._show_form() @@ -115,8 +115,8 @@ async def async_step_homekit(self, discovery_info): return await self.async_step_discovery_confirm() async def async_step_ssdp( - self, discovery_info: Optional[Dict] = None - ) -> Dict[str, Any]: + self, discovery_info: dict | None = None + ) -> dict[str, Any]: """Handle a flow initialized by discovery.""" host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname name = discovery_info[ATTR_UPNP_FRIENDLY_NAME] @@ -141,8 +141,8 @@ async def async_step_ssdp( return await self.async_step_discovery_confirm() async def async_step_discovery_confirm( - self, user_input: Optional[Dict] = None - ) -> Dict[str, Any]: + self, user_input: dict | None = None + ) -> dict[str, Any]: """Handle user-confirmation of discovered device.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index e50c28d0a43e52..6fee53595ac823 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -1,6 +1,7 @@ """Support for the Roku media player.""" +from __future__ import annotations + import logging -from typing import List, Optional import voluptuous as vol @@ -100,7 +101,7 @@ def unique_id(self) -> str: return self._unique_id @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" if self.coordinator.data.info.device_type == "tv": return DEVICE_CLASS_TV @@ -230,7 +231,7 @@ def source(self) -> str: return None @property - def source_list(self) -> List: + def source_list(self) -> list: """List of available input sources.""" return ["Home"] + sorted(app.name for app in self.coordinator.data.apps) diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 3fcd2ee1a34032..da57866757866d 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -1,5 +1,7 @@ """Support for the Roku remote.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry @@ -12,7 +14,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List, bool], None], + async_add_entities: Callable[[list, bool], None], ) -> bool: """Load Roku remote based on a config entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] @@ -56,7 +58,7 @@ async def async_turn_off(self, **kwargs) -> None: 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) -> None: """Send a command to one device.""" num_repeats = kwargs[ATTR_NUM_REPEATS] diff --git a/homeassistant/components/route53/__init__.py b/homeassistant/components/route53/__init__.py index 1061b7979ba0a9..a2b6a854c62e10 100644 --- a/homeassistant/components/route53/__init__.py +++ b/homeassistant/components/route53/__init__.py @@ -1,7 +1,8 @@ """Update the IP addresses of your Route53 DNS records.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import List import boto3 import requests @@ -77,7 +78,7 @@ def _update_route53( aws_secret_access_key: str, zone: str, domain: str, - records: List[str], + records: list[str], ttl: int, ): _LOGGER.debug("Starting update for zone %s", zone) diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py index 9924ebf0440cf2..b635972f43fe59 100644 --- a/homeassistant/components/rpi_power/config_flow.py +++ b/homeassistant/components/rpi_power/config_flow.py @@ -1,5 +1,7 @@ """Config flow for Raspberry Pi Power Supply Checker.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from rpi_bad_power import new_under_voltage @@ -31,8 +33,8 @@ def __init__(self) -> None: ) async def async_step_onboarding( - self, data: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, data: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a flow initialized by onboarding.""" has_devices = await self._discovery_function(self.hass) diff --git a/homeassistant/components/ruckus_unleashed/device_tracker.py b/homeassistant/components/ruckus_unleashed/device_tracker.py index 955e0581393f48..140aa3a8692b38 100644 --- a/homeassistant/components/ruckus_unleashed/device_tracker.py +++ b/homeassistant/components/ruckus_unleashed/device_tracker.py @@ -1,5 +1,5 @@ """Support for Ruckus Unleashed devices.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER from homeassistant.components.device_tracker.config_entry import ScannerEntity @@ -115,7 +115,7 @@ def source_type(self) -> str: return SOURCE_TYPE_ROUTER @property - def device_info(self) -> Optional[dict]: + def device_info(self) -> dict | None: """Return the device information.""" if self.is_connected: return { diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 4bc63d585be5de..e11934c61c3804 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -1,8 +1,10 @@ """Allow users to set and activate scenes.""" +from __future__ import annotations + import functools as ft import importlib import logging -from typing import Any, Optional +from typing import Any import voluptuous as vol @@ -94,7 +96,7 @@ def should_poll(self) -> bool: return False @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the scene.""" return STATE diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index e408be47f656a9..ec0bee73528359 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -1,7 +1,8 @@ """Support for scripts.""" +from __future__ import annotations + import asyncio import logging -from typing import List import voluptuous as vol @@ -89,7 +90,7 @@ def is_on(hass, entity_id): @callback -def scripts_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: +def scripts_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all scripts that reference the entity.""" if DOMAIN not in hass.data: return [] @@ -104,7 +105,7 @@ def scripts_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def entities_in_script(hass: HomeAssistant, entity_id: str) -> List[str]: +def entities_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all entities in script.""" if DOMAIN not in hass.data: return [] @@ -120,7 +121,7 @@ def entities_in_script(hass: HomeAssistant, entity_id: str) -> List[str]: @callback -def scripts_with_device(hass: HomeAssistant, device_id: str) -> List[str]: +def scripts_with_device(hass: HomeAssistant, device_id: str) -> list[str]: """Return all scripts that reference the device.""" if DOMAIN not in hass.data: return [] @@ -135,7 +136,7 @@ def scripts_with_device(hass: HomeAssistant, device_id: str) -> List[str]: @callback -def devices_in_script(hass: HomeAssistant, entity_id: str) -> List[str]: +def devices_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: """Return all devices in script.""" if DOMAIN not in hass.data: return [] diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index e2efac7b141f69..fea79530485104 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for sensors.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -109,9 +109,9 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" - conditions: List[Dict[str, str]] = [] + conditions: list[dict[str, str]] = [] entity_registry = await async_get_registry(hass) entries = [ entry diff --git a/homeassistant/components/sensor/significant_change.py b/homeassistant/components/sensor/significant_change.py index 2c281c0a046acd..cda80991242957 100644 --- a/homeassistant/components/sensor/significant_change.py +++ b/homeassistant/components/sensor/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant sensor state changes.""" -from typing import Any, Optional, Union +from __future__ import annotations + +from typing import Any from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -19,7 +21,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" device_class = new_attrs.get(ATTR_DEVICE_CLASS) @@ -28,7 +30,7 @@ def async_check_significant_change( if device_class == DEVICE_CLASS_TEMPERATURE: if new_attrs.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_FAHRENHEIT: - change: Union[float, int] = 1 + change: float | int = 1 else: change = 0.5 diff --git a/homeassistant/components/sentry/__init__.py b/homeassistant/components/sentry/__init__.py index 6be02b9ba5edba..c58d7bcd1a8e29 100644 --- a/homeassistant/components/sentry/__init__.py +++ b/homeassistant/components/sentry/__init__.py @@ -1,6 +1,7 @@ """The sentry integration.""" +from __future__ import annotations + import re -from typing import Dict, Union import sentry_sdk from sentry_sdk.integrations.aiohttp import AioHttpIntegration @@ -126,8 +127,8 @@ def process_before_send( options, channel: str, huuid: str, - system_info: Dict[str, Union[bool, str]], - custom_components: Dict[str, Integration], + system_info: dict[str, bool | str], + custom_components: dict[str, Integration], event, hint, ): diff --git a/homeassistant/components/sentry/config_flow.py b/homeassistant/components/sentry/config_flow.py index a308423f40ba46..66707c82ec94eb 100644 --- a/homeassistant/components/sentry/config_flow.py +++ b/homeassistant/components/sentry/config_flow.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any, Dict, Optional +from typing import Any from sentry_sdk.utils import BadDsn, Dsn import voluptuous as vol @@ -47,8 +47,8 @@ def async_get_options_flow( return SentryOptionsFlow(config_entry) async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a user config flow.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -78,8 +78,8 @@ def __init__(self, config_entry: config_entries.ConfigEntry): self.config_entry = config_entry async def async_step_init( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Manage Sentry options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/sharkiq/config_flow.py b/homeassistant/components/sharkiq/config_flow.py index 9d2e80b8ec60e8..06a25a8de567f6 100644 --- a/homeassistant/components/sharkiq/config_flow.py +++ b/homeassistant/components/sharkiq/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Shark IQ integration.""" +from __future__ import annotations import asyncio -from typing import Dict, Optional import aiohttp import async_timeout @@ -62,7 +62,7 @@ async def _async_validate_input(self, user_input): errors["base"] = "unknown" return info, errors - async def async_step_user(self, user_input: Optional[Dict] = None): + async def async_step_user(self, user_input: dict | None = None): """Handle the initial step.""" errors = {} if user_input is not None: @@ -76,7 +76,7 @@ async def async_step_user(self, user_input: Optional[Dict] = None): step_id="user", data_schema=SHARKIQ_SCHEMA, errors=errors ) - async def async_step_reauth(self, user_input: Optional[dict] = None): + async def async_step_reauth(self, user_input: dict | None = None): """Handle re-auth if login is invalid.""" errors = {} diff --git a/homeassistant/components/sharkiq/update_coordinator.py b/homeassistant/components/sharkiq/update_coordinator.py index 8675058de8612c..5374e873668188 100644 --- a/homeassistant/components/sharkiq/update_coordinator.py +++ b/homeassistant/components/sharkiq/update_coordinator.py @@ -1,7 +1,7 @@ """Data update coordinator for shark iq vacuums.""" +from __future__ import annotations import asyncio -from typing import Dict, List, Set from async_timeout import timeout from sharkiqpy import ( @@ -27,11 +27,11 @@ def __init__( hass: HomeAssistant, config_entry: ConfigEntry, ayla_api: AylaApi, - shark_vacs: List[SharkIqVacuum], + shark_vacs: list[SharkIqVacuum], ) -> None: """Set up the SharkIqUpdateCoordinator class.""" self.ayla_api = ayla_api - self.shark_vacs: Dict[str, SharkIqVacuum] = { + self.shark_vacs: dict[str, SharkIqVacuum] = { sharkiq.serial_number: sharkiq for sharkiq in shark_vacs } self._config_entry = config_entry @@ -40,7 +40,7 @@ def __init__( super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) @property - def online_dsns(self) -> Set[str]: + def online_dsns(self) -> set[str]: """Get the set of all online DSNs.""" return self._online_dsns diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 5b4254eebb7989..7e33d355729327 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -1,8 +1,8 @@ """Shark IQ Wrapper.""" - +from __future__ import annotations import logging -from typing import Dict, Iterable, Optional +from typing import Iterable from sharkiqpy import OperatingModes, PowerModes, Properties, SharkIqVacuum @@ -118,7 +118,7 @@ def model(self) -> str: return self.sharkiq.oem_model_number @property - def device_info(self) -> Dict: + def device_info(self) -> dict: """Device info dictionary.""" return { "identifiers": {(DOMAIN, self.serial_number)}, @@ -136,30 +136,30 @@ def supported_features(self) -> int: return SUPPORT_SHARKIQ @property - def is_docked(self) -> Optional[bool]: + def is_docked(self) -> bool | None: """Is vacuum docked.""" return self.sharkiq.get_property_value(Properties.DOCKED_STATUS) @property - def error_code(self) -> Optional[int]: + def error_code(self) -> int | None: """Return the last observed error code (or None).""" return self.sharkiq.error_code @property - def error_message(self) -> Optional[str]: + def error_message(self) -> str | None: """Return the last observed error message (or None).""" if not self.error_code: return None return self.sharkiq.error_text @property - def operating_mode(self) -> Optional[str]: + def operating_mode(self) -> str | None: """Operating mode..""" op_mode = self.sharkiq.get_property_value(Properties.OPERATING_MODE) return OPERATING_STATE_MAP.get(op_mode) @property - def recharging_to_resume(self) -> Optional[int]: + def recharging_to_resume(self) -> int | None: """Return True if vacuum set to recharge and resume cleaning.""" return self.sharkiq.get_property_value(Properties.RECHARGING_TO_RESUME) @@ -240,12 +240,12 @@ def fan_speed_list(self): # Various attributes we want to expose @property - def recharge_resume(self) -> Optional[bool]: + def recharge_resume(self) -> bool | None: """Recharge and resume mode active.""" return self.sharkiq.get_property_value(Properties.RECHARGE_RESUME) @property - def rssi(self) -> Optional[int]: + def rssi(self) -> int | None: """Get the WiFi RSSI.""" return self.sharkiq.get_property_value(Properties.RSSI) @@ -255,7 +255,7 @@ def low_light(self): return self.sharkiq.get_property_value(Properties.LOW_LIGHT_MISSION) @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return a dictionary of device state attributes specific to sharkiq.""" data = { ATTR_ERROR_CODE: self.error_code, diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index 9d4851c92a4eb9..deec98a4915f56 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for Shelly.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -60,7 +60,7 @@ async def async_validate_trigger_config(hass, config): ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Shelly devices.""" triggers = [] diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 9457cbaf370f1d..292a6050f9a6e3 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -1,7 +1,9 @@ """Shelly entity helper.""" +from __future__ import annotations + from dataclasses import dataclass import logging -from typing import Any, Callable, Optional, Union +from typing import Any, Callable import aioshelly @@ -142,15 +144,15 @@ class BlockAttributeDescription: name: str # Callable = lambda attr_info: unit - icon: Optional[str] = None - unit: Union[None, str, Callable[[dict], str]] = None + icon: str | None = None + unit: None | str | Callable[[dict], str] = None value: Callable[[Any], Any] = lambda val: val - device_class: Optional[str] = None + device_class: str | None = None default_enabled: bool = True - available: Optional[Callable[[aioshelly.Block], bool]] = None + available: Callable[[aioshelly.Block], bool] | None = None # Callable (settings, block), return true if entity should be removed - removal_condition: Optional[Callable[[dict, aioshelly.Block], bool]] = None - extra_state_attributes: Optional[Callable[[aioshelly.Block], Optional[dict]]] = None + removal_condition: Callable[[dict, aioshelly.Block], bool] | None = None + extra_state_attributes: Callable[[aioshelly.Block], dict | None] | None = None @dataclass @@ -158,12 +160,12 @@ class RestAttributeDescription: """Class to describe a REST sensor.""" name: str - icon: Optional[str] = None - unit: Optional[str] = None + icon: str | None = None + unit: str | None = None value: Callable[[dict, Any], Any] = None - device_class: Optional[str] = None + device_class: str | None = None default_enabled: bool = True - extra_state_attributes: Optional[Callable[[dict], Optional[dict]]] = None + extra_state_attributes: Callable[[dict], dict | None] | None = None class ShellyBlockEntity(entity.Entity): @@ -385,7 +387,7 @@ def __init__( block: aioshelly.Block, attribute: str, description: BlockAttributeDescription, - entry: Optional[ConfigEntry] = None, + entry: ConfigEntry | None = None, ) -> None: """Initialize the sleeping sensor.""" self.last_state = None diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 0379bfec1cfd48..a9e137968758a9 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -1,5 +1,5 @@ """Light for Shelly.""" -from typing import Optional, Tuple +from __future__ import annotations from aioshelly import Block @@ -96,7 +96,7 @@ def is_on(self) -> bool: return self.block.output @property - def mode(self) -> Optional[str]: + def mode(self) -> str | None: """Return the color mode of the light.""" if self.mode_result: return self.mode_result["mode"] @@ -138,7 +138,7 @@ def white_value(self) -> int: return int(white) @property - def hs_color(self) -> Tuple[float, float]: + def hs_color(self) -> tuple[float, float]: """Return the hue and saturation color value of light.""" if self.mode == "white": return color_RGB_to_hs(255, 255, 255) @@ -154,7 +154,7 @@ def hs_color(self) -> Tuple[float, float]: return color_RGB_to_hs(red, green, blue) @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the CT color value in mireds.""" if self.mode == "color": return None diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 27152997ef70a7..126491f65c1303 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -1,8 +1,8 @@ """Shelly helpers functions.""" +from __future__ import annotations from datetime import timedelta import logging -from typing import List, Optional, Tuple import aioshelly @@ -67,7 +67,7 @@ def get_number_of_channels(device: aioshelly.Device, block: aioshelly.Block) -> def get_entity_name( device: aioshelly.Device, block: aioshelly.Block, - description: Optional[str] = None, + description: str | None = None, ) -> str: """Naming for switch and sensors.""" channel_name = get_device_channel_name(device, block) @@ -143,7 +143,7 @@ def get_device_uptime(status: dict, last_uptime: str) -> str: def get_input_triggers( device: aioshelly.Device, block: aioshelly.Block -) -> List[Tuple[str, str]]: +) -> list[tuple[str, str]]: """Return list of input triggers for block.""" if "inputEvent" not in block.sensor_ids or "inputEventCnt" not in block.sensor_ids: return [] diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 41e915d5c955f4..dd4c1e2928c5de 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -1,5 +1,7 @@ """Support for binary sensors through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -52,7 +54,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" return [ capability for capability in CAPABILITY_TO_ATTRIB if capability in capabilities diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index d99cc1d60cf9b0..76c168fbc381ee 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,8 +1,10 @@ """Support for climate devices through the SmartThings cloud API.""" +from __future__ import annotations + import asyncio from collections.abc import Iterable import logging -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Attribute, Capability @@ -103,7 +105,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" supported = [ Capability.air_conditioner_mode, @@ -274,7 +276,7 @@ def fan_modes(self): return self._device.status.supported_thermostat_fan_modes @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" return OPERATING_STATE_TO_ACTION.get( self._device.status.thermostat_operating_state diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index 7b837faca1c2b1..8fff4ebbdfa51b 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -1,5 +1,7 @@ """Support for covers through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -46,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" min_required = [ Capability.door_control, diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index ec133a1f6aa93b..167f3a38edf7bd 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -1,6 +1,8 @@ """Support for fans through the SmartThings cloud API.""" +from __future__ import annotations + import math -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Capability @@ -29,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" supported = [Capability.switch, Capability.fan_speed] # Must have switch and fan_speed diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 1e4161abd0f756..de678f255fa6e4 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -1,6 +1,8 @@ """Support for lights through the SmartThings cloud API.""" +from __future__ import annotations + import asyncio -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Capability @@ -34,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" supported = [ Capability.switch, diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index 55370e99993994..2cd0b283cca861 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -1,5 +1,7 @@ """Support for locks through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -31,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" if Capability.lock in capabilities: return [Capability.lock] diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 835c4168f07f7f..4f924786e49cbd 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -1,6 +1,8 @@ """Support for sensors through the SmartThings cloud API.""" +from __future__ import annotations + from collections import namedtuple -from typing import Optional, Sequence +from typing import Sequence from pysmartthings import Attribute, Capability @@ -297,7 +299,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" return [ capability for capability in CAPABILITY_TO_SENSORS if capability in capabilities diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index ff70648ddcf0d6..d8bcd4554154b6 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -1,5 +1,7 @@ """Support for switches through the SmartThings cloud API.""" -from typing import Optional, Sequence +from __future__ import annotations + +from typing import Sequence from pysmartthings import Attribute, Capability @@ -21,7 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: +def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: """Return all capabilities supported if minimum required are present.""" # Must be able to be turned on/off. if Capability.switch in capabilities: diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index ca5e7f7ac23c56..86cdf72e65c487 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -1,8 +1,9 @@ """Support for the Swedish weather institute weather service.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Dict, List import aiohttp import async_timeout @@ -210,7 +211,7 @@ def attribution(self) -> str: return "Swedish weather institute (SMHI)" @property - def forecast(self) -> List: + def forecast(self) -> list: """Return the forecast.""" if self._forecasts is None or len(self._forecasts) < 2: return None @@ -235,7 +236,7 @@ def forecast(self) -> List: return data @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return SMHI specific attributes.""" if self.cloudiness: return {ATTR_SMHI_CLOUDINESS: self.cloudiness} diff --git a/homeassistant/components/somfy/api.py b/homeassistant/components/somfy/api.py index a679af06d730db..43db2c29060982 100644 --- a/homeassistant/components/somfy/api.py +++ b/homeassistant/components/somfy/api.py @@ -1,6 +1,7 @@ """API for Somfy bound to Home Assistant OAuth.""" +from __future__ import annotations + from asyncio import run_coroutine_threadsafe -from typing import Dict, Union from pymfy.api import somfy_api @@ -27,7 +28,7 @@ def __init__( def refresh_tokens( self, - ) -> Dict[str, Union[str, int]]: + ) -> dict[str, str | int]: """Refresh and return new Somfy tokens using Home Assistant OAuth2 session.""" run_coroutine_threadsafe( self.session.async_ensure_token_valid(), self.hass.loop diff --git a/homeassistant/components/somfy/climate.py b/homeassistant/components/somfy/climate.py index 99d6dca06eec74..66602aea3e6acd 100644 --- a/homeassistant/components/somfy/climate.py +++ b/homeassistant/components/somfy/climate.py @@ -1,6 +1,5 @@ """Support for Somfy Thermostat.""" - -from typing import List, Optional +from __future__ import annotations from pymfy.api.devices.category import Category from pymfy.api.devices.thermostat import ( @@ -125,7 +124,7 @@ def hvac_mode(self) -> str: return HVAC_MODES_MAPPING.get(self._climate.get_hvac_state()) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. HEAT and COOL mode are exclusive. End user has to enable a mode manually within the Somfy application. @@ -144,13 +143,13 @@ def set_hvac_mode(self, hvac_mode: str) -> None: ) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" mode = self._climate.get_target_mode() return PRESETS_MAPPING.get(mode) @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return list(PRESETS_MAPPING.values()) diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 636653dad00d17..946d9b1e047604 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -1,8 +1,10 @@ """The Sonarr component.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from sonarr import Sonarr, SonarrAccessRestricted, SonarrError @@ -40,7 +42,7 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: """Set up the Sonarr component.""" hass.data.setdefault(DOMAIN, {}) return True @@ -164,7 +166,7 @@ def entity_registry_enabled_default(self) -> bool: return self._enabled_default @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about the application.""" if self._device_id is None: return None diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index fc11790356a431..5329371bde8a93 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Sonarr.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from sonarr import Sonarr, SonarrAccessRestricted, SonarrError import voluptuous as vol @@ -33,7 +35,7 @@ _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: +async def validate_input(hass: HomeAssistantType, data: dict) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -73,9 +75,7 @@ def async_get_options_flow(config_entry): """Get the options flow for this handler.""" return SonarrOptionsFlowHandler(config_entry) - async def async_step_reauth( - self, data: Optional[ConfigType] = None - ) -> Dict[str, Any]: + async def async_step_reauth(self, data: ConfigType | None = None) -> dict[str, Any]: """Handle configuration by re-auth.""" self._reauth = True self._entry_data = dict(data) @@ -84,8 +84,8 @@ async def async_step_reauth( return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form( @@ -98,8 +98,8 @@ async def async_step_reauth_confirm( return await self.async_step_user() async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" errors = {} @@ -138,7 +138,7 @@ async def async_step_user( async def _async_reauth_update_entry( self, entry_id: str, data: dict - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Update existing config entry.""" entry = self.hass.config_entries.async_get_entry(entry_id) self.hass.config_entries.async_update_entry(entry, data=data) @@ -146,7 +146,7 @@ async def _async_reauth_update_entry( return self.async_abort(reason="reauth_successful") - def _get_user_data_schema(self) -> Dict[str, Any]: + def _get_user_data_schema(self) -> dict[str, Any]: """Get the data schema to display user form.""" if self._reauth: return {vol.Required(CONF_API_KEY): str} @@ -174,7 +174,7 @@ def __init__(self, config_entry): """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: Optional[ConfigType] = None): + async def async_step_init(self, user_input: ConfigType | None = None): """Manage Sonarr options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index ca489d95cfdc54..017d9b0f0d00af 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -1,7 +1,9 @@ """Support for Sonarr sensors.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from sonarr import Sonarr, SonarrConnectionError, SonarrError @@ -20,7 +22,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Sonarr sensors based on a config entry.""" options = entry.options @@ -75,7 +77,7 @@ def __init__( icon: str, key: str, name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ) -> None: """Initialize Sonarr sensor.""" self._unit_of_measurement = unit_of_measurement @@ -131,7 +133,7 @@ async def async_update(self) -> None: self._commands = await self.sonarr.commands() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -172,7 +174,7 @@ async def async_update(self) -> None: self._total_free = sum([disk.free for disk in self._disks]) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -217,7 +219,7 @@ async def async_update(self) -> None: self._queue = await self.sonarr.queue() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -258,7 +260,7 @@ async def async_update(self) -> None: self._items = await self.sonarr.series() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -301,7 +303,7 @@ async def async_update(self) -> None: ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -323,7 +325,7 @@ def __init__(self, sonarr: Sonarr, entry_id: str, max_items: int = 10) -> None: """Initialize Sonarr Wanted sensor.""" self._max_items = max_items self._results = None - self._total: Optional[int] = None + self._total: int | None = None super().__init__( sonarr=sonarr, @@ -342,7 +344,7 @@ async def async_update(self) -> None: self._total = self._results.total @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" attrs = {} @@ -354,6 +356,6 @@ def extra_state_attributes(self) -> Optional[Dict[str, Any]]: return attrs @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return the state of the sensor.""" return self._total diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index aaa9302cac25e2..b93a2b10bc02d9 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -1,6 +1,7 @@ """Config flow to configure songpal component.""" +from __future__ import annotations + import logging -from typing import Optional from urllib.parse import urlparse from songpal import Device, SongpalException @@ -34,7 +35,7 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize the flow.""" - self.conf: Optional[SongpalConfig] = None + self.conf: SongpalConfig | None = None async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" diff --git a/homeassistant/components/spotify/config_flow.py b/homeassistant/components/spotify/config_flow.py index afad75f0f39b39..d0fb73e18bdb3e 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -1,6 +1,8 @@ """Config flow for Spotify.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from spotipy import Spotify import voluptuous as vol @@ -24,7 +26,7 @@ class SpotifyFlowHandler( def __init__(self) -> None: """Instantiate config flow.""" super().__init__() - self.entry: Optional[Dict[str, Any]] = None + self.entry: dict[str, Any] | None = None @property def logger(self) -> logging.Logger: @@ -32,11 +34,11 @@ def logger(self) -> logging.Logger: return logging.getLogger(__name__) @property - def extra_authorize_data(self) -> Dict[str, Any]: + def extra_authorize_data(self) -> dict[str, Any]: """Extra data that needs to be appended to the authorize url.""" return {"scope": ",".join(SPOTIFY_SCOPES)} - async def async_oauth_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> dict[str, Any]: """Create an entry for Spotify.""" spotify = Spotify(auth=data["token"]["access_token"]) @@ -58,7 +60,7 @@ async def async_oauth_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any] return self.async_create_entry(title=name, data=data) - async def async_step_reauth(self, entry: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_reauth(self, entry: dict[str, Any]) -> dict[str, Any]: """Perform reauth upon migration of old entries.""" if entry: self.entry = entry @@ -73,8 +75,8 @@ async def async_step_reauth(self, entry: Dict[str, Any]) -> Dict[str, Any]: return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index e4450e7a3068a1..84c7d2b41ed184 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -1,9 +1,11 @@ """Support for interacting with Spotify Connect.""" +from __future__ import annotations + from asyncio import run_coroutine_threadsafe import datetime as dt from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable import requests from spotipy import Spotify, SpotifyException @@ -185,7 +187,7 @@ class UnknownMediaType(BrowseError): async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Spotify based on a config entry.""" spotify = SpotifyMediaPlayer( @@ -237,9 +239,9 @@ def __init__( SPOTIFY_SCOPES ) - self._currently_playing: Optional[dict] = {} - self._devices: Optional[List[dict]] = [] - self._playlist: Optional[dict] = None + self._currently_playing: dict | None = {} + self._devices: list[dict] | None = [] + self._playlist: dict | None = None self._spotify: Spotify = None self.player_available = False @@ -265,7 +267,7 @@ def unique_id(self) -> str: return self._id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" if self._me is not None: model = self._me["product"] @@ -278,7 +280,7 @@ def device_info(self) -> Dict[str, Any]: } @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the playback state.""" if not self._currently_playing: return STATE_IDLE @@ -287,44 +289,44 @@ def state(self) -> Optional[str]: return STATE_PAUSED @property - def volume_level(self) -> Optional[float]: + def volume_level(self) -> float | None: """Return the device volume.""" return self._currently_playing.get("device", {}).get("volume_percent", 0) / 100 @property - def media_content_id(self) -> Optional[str]: + def media_content_id(self) -> str | None: """Return the media URL.""" item = self._currently_playing.get("item") or {} return item.get("uri") @property - def media_content_type(self) -> Optional[str]: + def media_content_type(self) -> str | None: """Return the media type.""" return MEDIA_TYPE_MUSIC @property - def media_duration(self) -> Optional[int]: + def media_duration(self) -> int | None: """Duration of current playing media in seconds.""" if self._currently_playing.get("item") is None: return None return self._currently_playing["item"]["duration_ms"] / 1000 @property - def media_position(self) -> Optional[str]: + def media_position(self) -> str | None: """Position of current playing media in seconds.""" if not self._currently_playing: return None return self._currently_playing["progress_ms"] / 1000 @property - def media_position_updated_at(self) -> Optional[dt.datetime]: + def media_position_updated_at(self) -> dt.datetime | None: """When was the position of the current playing media valid.""" if not self._currently_playing: return None return utc_from_timestamp(self._currently_playing["timestamp"] / 1000) @property - def media_image_url(self) -> Optional[str]: + def media_image_url(self) -> str | None: """Return the media image URL.""" if ( self._currently_playing.get("item") is None @@ -339,13 +341,13 @@ def media_image_remotely_accessible(self) -> bool: return False @property - def media_title(self) -> Optional[str]: + def media_title(self) -> str | None: """Return the media title.""" item = self._currently_playing.get("item") or {} return item.get("name") @property - def media_artist(self) -> Optional[str]: + def media_artist(self) -> str | None: """Return the media artist.""" if self._currently_playing.get("item") is None: return None @@ -354,14 +356,14 @@ def media_artist(self) -> Optional[str]: ) @property - def media_album_name(self) -> Optional[str]: + def media_album_name(self) -> str | None: """Return the media album.""" if self._currently_playing.get("item") is None: return None return self._currently_playing["item"]["album"]["name"] @property - def media_track(self) -> Optional[int]: + def media_track(self) -> int | None: """Track number of current playing media, music track only.""" item = self._currently_playing.get("item") or {} return item.get("track_number") @@ -374,12 +376,12 @@ def media_playlist(self): return self._playlist["name"] @property - def source(self) -> Optional[str]: + def source(self) -> str | None: """Return the current playback device.""" return self._currently_playing.get("device", {}).get("name") @property - def source_list(self) -> Optional[List[str]]: + def source_list(self) -> list[str] | None: """Return a list of source devices.""" if not self._devices: return None @@ -391,7 +393,7 @@ def shuffle(self) -> bool: return bool(self._currently_playing.get("shuffle_state")) @property - def repeat(self) -> Optional[str]: + def repeat(self) -> str | None: """Return current repeat mode.""" repeat_state = self._currently_playing.get("repeat_state") return REPEAT_MODE_MAPPING_TO_HA.get(repeat_state) diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 7452253019b23a..8d967dc2ea7114 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -1,6 +1,8 @@ """StarLine Account.""" +from __future__ import annotations + from datetime import datetime, timedelta -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable from starline import StarlineApi, StarlineDevice @@ -29,8 +31,8 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry): self._config_entry: ConfigEntry = config_entry self._update_interval: int = DEFAULT_SCAN_INTERVAL self._update_obd_interval: int = DEFAULT_SCAN_OBD_INTERVAL - self._unsubscribe_auto_updater: Optional[Callable] = None - self._unsubscribe_auto_obd_updater: Optional[Callable] = None + self._unsubscribe_auto_updater: Callable | None = None + self._unsubscribe_auto_obd_updater: Callable | None = None self._api: StarlineApi = StarlineApi( config_entry.data[DATA_USER_ID], config_entry.data[DATA_SLNET_TOKEN] ) @@ -123,7 +125,7 @@ def unload(self): self._unsubscribe_auto_obd_updater = None @staticmethod - def device_info(device: StarlineDevice) -> Dict[str, Any]: + def device_info(device: StarlineDevice) -> dict[str, Any]: """Device information for entities.""" return { "identifiers": {(DOMAIN, device.device_id)}, @@ -134,7 +136,7 @@ def device_info(device: StarlineDevice) -> Dict[str, Any]: } @staticmethod - def gps_attrs(device: StarlineDevice) -> Dict[str, Any]: + def gps_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for device tracker.""" return { "updated": datetime.utcfromtimestamp(device.position["ts"]).isoformat(), @@ -142,7 +144,7 @@ def gps_attrs(device: StarlineDevice) -> Dict[str, Any]: } @staticmethod - def balance_attrs(device: StarlineDevice) -> Dict[str, Any]: + def balance_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for balance sensor.""" return { "operator": device.balance.get("operator"), @@ -151,7 +153,7 @@ def balance_attrs(device: StarlineDevice) -> Dict[str, Any]: } @staticmethod - def gsm_attrs(device: StarlineDevice) -> Dict[str, Any]: + def gsm_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for GSM sensor.""" return { "raw": device.gsm_level, @@ -161,7 +163,7 @@ def gsm_attrs(device: StarlineDevice) -> Dict[str, Any]: } @staticmethod - def engine_attrs(device: StarlineDevice) -> Dict[str, Any]: + def engine_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for engine switch.""" return { "autostart": device.car_state.get("r_start"), @@ -169,6 +171,6 @@ def engine_attrs(device: StarlineDevice) -> Dict[str, Any]: } @staticmethod - def errors_attrs(device: StarlineDevice) -> Dict[str, Any]: + def errors_attrs(device: StarlineDevice) -> dict[str, Any]: """Attributes for errors sensor.""" return {"errors": device.errors.get("errors")} diff --git a/homeassistant/components/starline/config_flow.py b/homeassistant/components/starline/config_flow.py index d6e8d6f98ead9f..5e62127cc7a97d 100644 --- a/homeassistant/components/starline/config_flow.py +++ b/homeassistant/components/starline/config_flow.py @@ -1,5 +1,5 @@ """Config flow to configure StarLine component.""" -from typing import Optional +from __future__ import annotations from starline import StarlineAuth import voluptuous as vol @@ -32,11 +32,11 @@ class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._app_id: Optional[str] = None - self._app_secret: Optional[str] = None - self._username: Optional[str] = None - self._password: Optional[str] = None - self._mfa_code: Optional[str] = None + self._app_id: str | None = None + self._app_secret: str | None = None + self._username: str | None = None + self._password: str | None = None + self._mfa_code: str | None = None self._app_code = None self._app_token = None diff --git a/homeassistant/components/starline/entity.py b/homeassistant/components/starline/entity.py index 5db4d369f5e742..9b81481b9d1c30 100644 --- a/homeassistant/components/starline/entity.py +++ b/homeassistant/components/starline/entity.py @@ -1,5 +1,7 @@ """StarLine base entity.""" -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable from homeassistant.helpers.entity import Entity @@ -17,7 +19,7 @@ def __init__( self._device = device self._key = key self._name = name - self._unsubscribe_api: Optional[Callable] = None + self._unsubscribe_api: Callable | None = None @property def should_poll(self): diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 17d4516344a4f0..076eb3596d7a33 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -1,8 +1,10 @@ """Provides core stream functionality.""" +from __future__ import annotations + import asyncio from collections import deque import io -from typing import Any, Callable, List +from typing import Any, Callable from aiohttp import web import attr @@ -104,7 +106,7 @@ def idle(self) -> bool: return self._idle_timer.idle @property - def segments(self) -> List[int]: + def segments(self) -> list[int]: """Return current sequence from segments.""" return [s.sequence for s in self._segments] diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index f61211340efb20..01a8ca9ea6b49a 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -1,8 +1,10 @@ """Provide functionality to record stream.""" +from __future__ import annotations + import logging import os import threading -from typing import Deque, List +from typing import Deque import av @@ -115,7 +117,7 @@ def name(self) -> str: """Return provider name.""" return "recorder" - def prepend(self, segments: List[Segment]) -> None: + def prepend(self, segments: list[Segment]) -> None: """Prepend segments to existing list.""" self._segments.extendleft(reversed(segments)) diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index 0ad621f070703a..5c45e5e3d44571 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -1,8 +1,9 @@ """Provide functionality to STT.""" +from __future__ import annotations + from abc import ABC, abstractmethod import asyncio import logging -from typing import Dict, List, Optional from aiohttp import StreamReader, web from aiohttp.hdrs import istr @@ -96,44 +97,44 @@ class SpeechMetadata: class SpeechResult: """Result of audio Speech.""" - text: Optional[str] = attr.ib() + text: str | None = attr.ib() result: SpeechResultState = attr.ib() class Provider(ABC): """Represent a single STT provider.""" - hass: Optional[HomeAssistantType] = None - name: Optional[str] = None + hass: HomeAssistantType | None = None + name: str | None = None @property @abstractmethod - def supported_languages(self) -> List[str]: + def supported_languages(self) -> list[str]: """Return a list of supported languages.""" @property @abstractmethod - def supported_formats(self) -> List[AudioFormats]: + def supported_formats(self) -> list[AudioFormats]: """Return a list of supported formats.""" @property @abstractmethod - def supported_codecs(self) -> List[AudioCodecs]: + def supported_codecs(self) -> list[AudioCodecs]: """Return a list of supported codecs.""" @property @abstractmethod - def supported_bit_rates(self) -> List[AudioBitRates]: + def supported_bit_rates(self) -> list[AudioBitRates]: """Return a list of supported bit rates.""" @property @abstractmethod - def supported_sample_rates(self) -> List[AudioSampleRates]: + def supported_sample_rates(self) -> list[AudioSampleRates]: """Return a list of supported sample rates.""" @property @abstractmethod - def supported_channels(self) -> List[AudioChannels]: + def supported_channels(self) -> list[AudioChannels]: """Return a list of supported channels.""" @abstractmethod @@ -167,12 +168,12 @@ class SpeechToTextView(HomeAssistantView): url = "/api/stt/{provider}" name = "api:stt:provider" - def __init__(self, providers: Dict[str, Provider]) -> None: + def __init__(self, providers: dict[str, Provider]) -> None: """Initialize a tts view.""" self.providers = providers @staticmethod - def _metadata_from_header(request: web.Request) -> Optional[SpeechMetadata]: + def _metadata_from_header(request: web.Request) -> SpeechMetadata | None: """Extract metadata from header. X-Speech-Content: format=wav; codec=pcm; sample_rate=16000; bit_rate=16; channel=1; language=de_de diff --git a/homeassistant/components/supla/__init__.py b/homeassistant/components/supla/__init__.py index 084811c8fa05fc..5ebd6d6ca481d0 100644 --- a/homeassistant/components/supla/__init__.py +++ b/homeassistant/components/supla/__init__.py @@ -1,7 +1,8 @@ """Support for Supla devices.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional import async_timeout from asyncpysupla import SuplaAPI @@ -180,7 +181,7 @@ def unique_id(self) -> str: ) @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the device.""" return self.channel_data["caption"] diff --git a/homeassistant/components/surepetcare/__init__.py b/homeassistant/components/surepetcare/__init__.py index 8ba6809ee0502a..4a65931d3f04aa 100644 --- a/homeassistant/components/surepetcare/__init__.py +++ b/homeassistant/components/surepetcare/__init__.py @@ -1,6 +1,8 @@ """Support for Sure Petcare cat/pet flaps.""" +from __future__ import annotations + import logging -from typing import Any, Dict, List +from typing import Any from surepy import ( MESTART_RESOURCE, @@ -185,12 +187,12 @@ async def handle_set_lock_state(call): class SurePetcareAPI: """Define a generic Sure Petcare object.""" - def __init__(self, hass, surepy: SurePetcare, ids: List[Dict[str, Any]]) -> None: + def __init__(self, hass, surepy: SurePetcare, ids: list[dict[str, Any]]) -> None: """Initialize the Sure Petcare object.""" self.hass = hass self.surepy = surepy self.ids = ids - self.states: Dict[str, Any] = {} + self.states: dict[str, Any] = {} async def async_update(self, arg: Any = None) -> None: """Refresh Sure Petcare data.""" diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 64e2766978655d..e96a5eaf35e591 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -1,7 +1,9 @@ """Support for Sure PetCare Flaps/Pets binary sensors.""" +from __future__ import annotations + from datetime import datetime import logging -from typing import Any, Dict, Optional +from typing import Any from surepy import SureLocationID, SurepyProduct @@ -71,8 +73,8 @@ def __init__( self._device_class = device_class self._spc: SurePetcareAPI = spc - self._spc_data: Dict[str, Any] = self._spc.states[self._sure_type].get(self._id) - self._state: Dict[str, Any] = {} + self._spc_data: dict[str, Any] = self._spc.states[self._sure_type].get(self._id) + self._state: dict[str, Any] = {} # cover special case where a device has no name set if "name" in self._spc_data: @@ -85,7 +87,7 @@ def __init__( self._async_unsub_dispatcher_connect = None @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if entity is on/unlocked.""" return bool(self._state) @@ -151,7 +153,7 @@ def is_on(self) -> bool: return self.available @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: @@ -179,7 +181,7 @@ def is_on(self) -> bool: return False @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: @@ -232,7 +234,7 @@ def is_on(self) -> bool: return self.available @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index 54e7f4d5773286..92f90faff2c86a 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -1,6 +1,8 @@ """Support for Sure PetCare Flaps/Pets sensors.""" +from __future__ import annotations + import logging -from typing import Any, Dict, Optional +from typing import Any from surepy import SureLockStateID, SurepyProduct @@ -62,8 +64,8 @@ def __init__(self, _id: int, sure_type: SurepyProduct, spc: SurePetcareAPI): self._sure_type = sure_type self._spc = spc - self._spc_data: Dict[str, Any] = self._spc.states[self._sure_type].get(self._id) - self._state: Dict[str, Any] = {} + self._spc_data: dict[str, Any] = self._spc.states[self._sure_type].get(self._id) + self._state: dict[str, Any] = {} self._name = ( f"{self._sure_type.name.capitalize()} " @@ -120,12 +122,12 @@ class Flap(SurePetcareSensor): """Sure Petcare Flap.""" @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return battery level in percent.""" return SureLockStateID(self._state["locking"]["mode"]).name.capitalize() @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attributes = None if self._state: @@ -143,9 +145,9 @@ def name(self) -> str: return f"{self._name} Battery Level" @property - def state(self) -> Optional[int]: + def state(self) -> int | None: """Return battery level in percent.""" - battery_percent: Optional[int] + battery_percent: int | None try: per_battery_voltage = self._state["battery"] / 4 voltage_diff = per_battery_voltage - SURE_BATT_VOLTAGE_LOW @@ -166,7 +168,7 @@ def device_class(self) -> str: return DEVICE_CLASS_BATTERY @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return state attributes.""" attributes = None if self._state: diff --git a/homeassistant/components/switch/device_action.py b/homeassistant/components/switch/device_action.py index a50131f094c281..0f3890d329ff3a 100644 --- a/homeassistant/components/switch/device_action.py +++ b/homeassistant/components/switch/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for switches.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -25,6 +25,6 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" return await toggle_entity.async_get_actions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/device_condition.py b/homeassistant/components/switch/device_condition.py index c928deef01abe6..15c2e54d193254 100644 --- a/homeassistant/components/switch/device_condition.py +++ b/homeassistant/components/switch/device_condition.py @@ -1,5 +1,5 @@ """Provides device conditions for switches.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ def async_condition_from_config( async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions.""" return await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/device_trigger.py b/homeassistant/components/switch/device_trigger.py index cb5d5f7aa0e3c4..15b700d9eb59ef 100644 --- a/homeassistant/components/switch/device_trigger.py +++ b/homeassistant/components/switch/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for switches.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ async def async_attach_trigger( ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers.""" return await toggle_entity.async_get_triggers(hass, device_id, DOMAIN) diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index fc8638162e759d..4ab030bc8e4641 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -1,5 +1,7 @@ """Light support for switch entities.""" -from typing import Any, Callable, Optional, Sequence, cast +from __future__ import annotations + +from typing import Any, Callable, Sequence, cast import voluptuous as vol @@ -38,7 +40,7 @@ async def async_setup_platform( hass: HomeAssistantType, config: ConfigType, async_add_entities: Callable[[Sequence[Entity]], None], - discovery_info: Optional[DiscoveryInfoType] = None, + discovery_info: DiscoveryInfoType | None = None, ) -> None: """Initialize Light Switch platform.""" @@ -65,7 +67,7 @@ def __init__(self, name: str, switch_entity_id: str, unique_id: str) -> None: self._name = name self._switch_entity_id = switch_entity_id self._unique_id = unique_id - self._switch_state: Optional[State] = None + self._switch_state: State | None = None @property def name(self) -> str: diff --git a/homeassistant/components/switch/reproduce_state.py b/homeassistant/components/switch/reproduce_state.py index 0527f558f35673..5a90af4181c0b4 100644 --- a/homeassistant/components/switch/reproduce_state.py +++ b/homeassistant/components/switch/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Switch state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -24,8 +26,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -60,8 +62,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Switch states.""" await asyncio.gather( diff --git a/homeassistant/components/switch/significant_change.py b/homeassistant/components/switch/significant_change.py index f4dcddc3f340dc..231085a3eeff8b 100644 --- a/homeassistant/components/switch/significant_change.py +++ b/homeassistant/components/switch/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant Switch state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.core import HomeAssistant, callback @@ -12,6 +14,6 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" return old_state != new_state diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index faf230507a2a04..cff1a0d0edc4d5 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -1,5 +1,7 @@ """Support for Switchbot.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any # pylint: disable=import-error import switchbot @@ -86,6 +88,6 @@ def name(self) -> str: return self._name @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes.""" return {"last_run_success": self._last_run_success} diff --git a/homeassistant/components/switcher_kis/__init__.py b/homeassistant/components/switcher_kis/__init__.py index d081b3331c7244..8d39182dcc32f4 100644 --- a/homeassistant/components/switcher_kis/__init__.py +++ b/homeassistant/components/switcher_kis/__init__.py @@ -1,8 +1,9 @@ """Home Assistant Switcher Component.""" +from __future__ import annotations + from asyncio import QueueEmpty, TimeoutError as Asyncio_TimeoutError, wait_for from datetime import datetime, timedelta import logging -from typing import Dict, Optional from aioswitcher.bridge import SwitcherV2Bridge import voluptuous as vol @@ -45,7 +46,7 @@ ) -async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: """Set up the switcher component.""" phone_id = config[DOMAIN][CONF_PHONE_ID] device_id = config[DOMAIN][CONF_DEVICE_ID] @@ -72,7 +73,7 @@ async def async_stop_bridge(event: EventType) -> None: hass.async_create_task(async_load_platform(hass, SWITCH_DOMAIN, DOMAIN, {}, config)) @callback - def device_updates(timestamp: Optional[datetime]) -> None: + def device_updates(timestamp: datetime | None) -> None: """Use for updating the device data from the queue.""" if v2bridge.running: try: diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index 99d50c0c559bfb..61297142716cb9 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -1,5 +1,7 @@ """Home Assistant Switcher Component Switch platform.""" -from typing import Callable, Dict +from __future__ import annotations + +from typing import Callable from aioswitcher.api import SwitcherV2Api from aioswitcher.api.messages import SwitcherV2ControlResponseMSG @@ -52,9 +54,9 @@ async def async_setup_platform( hass: HomeAssistantType, - config: Dict, + config: dict, async_add_entities: Callable, - discovery_info: Dict, + discovery_info: dict, ) -> None: """Set up the switcher platform for the switch component.""" if discovery_info is None: @@ -139,7 +141,7 @@ def current_power_w(self) -> int: return self._device_data.power_consumption @property - def extra_state_attributes(self) -> Dict: + def extra_state_attributes(self) -> dict: """Return the optional state attributes.""" attribs = {} @@ -173,11 +175,11 @@ async def async_update_data(self, device_data: SwitcherV2Device) -> None: self._state = self._device_data.state self.async_write_ha_state() - async def async_turn_on(self, **kwargs: Dict) -> None: + async def async_turn_on(self, **kwargs: dict) -> None: """Turn the entity on.""" await self._control_device(True) - async def async_turn_off(self, **kwargs: Dict) -> None: + async def async_turn_off(self, **kwargs: dict) -> None: """Turn the entity off.""" await self._control_device(False) diff --git a/homeassistant/components/syncthru/__init__.py b/homeassistant/components/syncthru/__init__.py index 83d32eb9b47e83..293680151ffc08 100644 --- a/homeassistant/components/syncthru/__init__.py +++ b/homeassistant/components/syncthru/__init__.py @@ -1,7 +1,7 @@ """The syncthru component.""" +from __future__ import annotations import logging -from typing import Set, Tuple from pysyncthru import SyncThru @@ -65,12 +65,12 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo return True -def device_identifiers(printer: SyncThru) -> Set[Tuple[str, str]]: +def device_identifiers(printer: SyncThru) -> set[tuple[str, str]]: """Get device identifiers for device registry.""" return {(DOMAIN, printer.serial_number())} -def device_connections(printer: SyncThru) -> Set[Tuple[str, str]]: +def device_connections(printer: SyncThru) -> set[tuple[str, str]]: """Get device connections for device registry.""" connections = set() try: diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 002538061c87c0..673cb6717fcdf9 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -1,8 +1,9 @@ """The Synology DSM component.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Dict import async_timeout from synology_dsm import SynologyDSM @@ -599,7 +600,7 @@ def __init__( self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], coordinator: DataUpdateCoordinator, ): """Initialize the Synology DSM entity.""" @@ -643,12 +644,12 @@ def device_class(self) -> str: return self._class @property - def extra_state_attributes(self) -> Dict[str, any]: + def extra_state_attributes(self) -> dict[str, any]: """Return the state attributes.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._api.information.serial)}, @@ -676,7 +677,7 @@ def __init__( self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], coordinator: DataUpdateCoordinator, device_id: str = None, ): @@ -718,7 +719,7 @@ def available(self) -> bool: return bool(self._api.storage) @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._api.information.serial, self._device_id)}, diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 042e46c636ebf0..fb8ed5a23cdb5a 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Synology DSM binary sensors.""" -from typing import Dict +from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -71,7 +71,7 @@ def available(self) -> bool: return bool(self._api.security) @property - def extra_state_attributes(self) -> Dict[str, str]: + def extra_state_attributes(self) -> dict[str, str]: """Return security checks details.""" return self._api.security.status_by_check diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index c0e0ded72ed57b..67052543569e37 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -1,6 +1,7 @@ """Support for Synology DSM cameras.""" +from __future__ import annotations + import logging -from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.exceptions import ( @@ -79,7 +80,7 @@ def camera_data(self): return self.coordinator.data["cameras"][self._camera_id] @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": { diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 79350ce89d3578..8cfab92975c43f 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -1,6 +1,7 @@ """Support for Synology DSM sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -145,7 +146,7 @@ def __init__( self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], coordinator: DataUpdateCoordinator, ): """Initialize the Synology SynoDSMInfoSensor entity.""" diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 998f74adf2a4a8..f9883b0c9162f2 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -1,6 +1,7 @@ """Support for Synology DSM switch.""" +from __future__ import annotations + import logging -from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation @@ -49,7 +50,7 @@ def __init__( self, api: SynoApi, entity_type: str, - entity_info: Dict[str, str], + entity_info: dict[str, str], version: str, coordinator: DataUpdateCoordinator, ): @@ -95,7 +96,7 @@ def available(self) -> bool: return bool(self._api.surveillance_station) @property - def device_info(self) -> Dict[str, any]: + def device_info(self) -> dict[str, any]: """Return the device information.""" return { "identifiers": { diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index c53cd9da1a5453..ea87a2af832473 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -1,9 +1,11 @@ """Support for System health .""" +from __future__ import annotations + import asyncio import dataclasses from datetime import datetime import logging -from typing import Awaitable, Callable, Dict, Optional +from typing import Awaitable, Callable import aiohttp import async_timeout @@ -27,7 +29,7 @@ def async_register_info( hass: HomeAssistant, domain: str, - info_callback: Callable[[HomeAssistant], Dict], + info_callback: Callable[[HomeAssistant], dict], ): """Register an info callback. @@ -89,10 +91,10 @@ def _format_value(val): @websocket_api.async_response @websocket_api.websocket_command({vol.Required("type"): "system_health/info"}) async def handle_info( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: Dict + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict ): """Handle an info request via a subscription.""" - registrations: Dict[str, SystemHealthRegistration] = hass.data[DOMAIN] + registrations: dict[str, SystemHealthRegistration] = hass.data[DOMAIN] data = {} pending_info = {} @@ -187,14 +189,14 @@ class SystemHealthRegistration: hass: HomeAssistant domain: str - info_callback: Optional[Callable[[HomeAssistant], Awaitable[Dict]]] = None - manage_url: Optional[str] = None + info_callback: Callable[[HomeAssistant], Awaitable[dict]] | None = None + manage_url: str | None = None @callback def async_register_info( self, - info_callback: Callable[[HomeAssistant], Awaitable[Dict]], - manage_url: Optional[str] = None, + info_callback: Callable[[HomeAssistant], Awaitable[dict]], + manage_url: str | None = None, ): """Register an info callback.""" self.info_callback = info_callback @@ -203,7 +205,7 @@ def async_register_info( async def async_check_can_reach_url( - hass: HomeAssistant, url: str, more_info: Optional[str] = None + hass: HomeAssistant, url: str, more_info: str | None = None ) -> str: """Test if the url can be reached.""" session = aiohttp_client.async_get_clientsession(hass) From b67b9b94f9261deaa423a16acba344e59d66fe0a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 14:43:52 +0100 Subject: [PATCH 1359/1818] Update typing 13 (#48077) --- homeassistant/components/tag/__init__.py | 9 +++-- .../components/tasmota/device_trigger.py | 10 +++-- homeassistant/components/tasmota/sensor.py | 4 +- homeassistant/components/template/sensor.py | 4 +- .../components/template/template_entity.py | 19 +++++----- homeassistant/components/tesla/climate.py | 7 ++-- .../components/tesla/device_tracker.py | 6 +-- homeassistant/components/tesla/sensor.py | 8 ++-- homeassistant/components/timer/__init__.py | 19 +++++----- .../components/timer/reproduce_state.py | 12 +++--- .../components/toon/binary_sensor.py | 4 +- homeassistant/components/toon/climate.py | 18 +++++---- homeassistant/components/toon/config_flow.py | 20 +++++----- homeassistant/components/toon/coordinator.py | 7 ++-- homeassistant/components/toon/models.py | 20 +++++----- homeassistant/components/toon/oauth2.py | 6 ++- homeassistant/components/toon/sensor.py | 8 ++-- homeassistant/components/tplink/common.py | 5 ++- homeassistant/components/tplink/light.py | 8 ++-- .../components/transmission/sensor.py | 4 +- homeassistant/components/tts/__init__.py | 10 +++-- homeassistant/components/tuya/fan.py | 5 ++- homeassistant/components/twinkly/light.py | 9 +++-- homeassistant/components/unifi/controller.py | 5 ++- .../components/universal/media_player.py | 5 ++- .../components/upc_connect/device_tracker.py | 7 ++-- homeassistant/components/upcloud/__init__.py | 11 +++--- homeassistant/components/upnp/config_flow.py | 12 +++--- homeassistant/components/upnp/device.py | 4 +- homeassistant/components/upnp/sensor.py | 8 ++-- .../usgs_earthquakes_feed/geo_location.py | 11 +++--- homeassistant/components/uvc/camera.py | 5 ++- .../components/vacuum/device_action.py | 6 +-- .../components/vacuum/device_condition.py | 4 +- .../components/vacuum/device_trigger.py | 4 +- .../components/vacuum/reproduce_state.py | 12 +++--- homeassistant/components/vera/__init__.py | 10 +++-- .../components/vera/binary_sensor.py | 8 ++-- homeassistant/components/vera/climate.py | 20 +++++----- homeassistant/components/vera/common.py | 10 +++-- homeassistant/components/vera/config_flow.py | 12 +++--- homeassistant/components/vera/cover.py | 6 ++- homeassistant/components/vera/light.py | 10 +++-- homeassistant/components/vera/lock.py | 12 +++--- homeassistant/components/vera/scene.py | 8 ++-- homeassistant/components/vera/sensor.py | 8 ++-- homeassistant/components/vera/switch.py | 8 ++-- homeassistant/components/vizio/__init__.py | 6 ++- homeassistant/components/vizio/config_flow.py | 38 ++++++++++--------- .../components/vizio/media_player.py | 30 ++++++++------- .../components/volumio/config_flow.py | 11 +++--- 51 files changed, 287 insertions(+), 226 deletions(-) diff --git a/homeassistant/components/tag/__init__.py b/homeassistant/components/tag/__init__.py index 6c385181aaad2d..6dcf2ec9a4df8c 100644 --- a/homeassistant/components/tag/__init__.py +++ b/homeassistant/components/tag/__init__.py @@ -1,6 +1,7 @@ """The Tag integration.""" +from __future__ import annotations + import logging -import typing import uuid import voluptuous as vol @@ -63,7 +64,7 @@ class TagStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: typing.Dict) -> typing.Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" data = self.CREATE_SCHEMA(data) if not data[TAG_ID]: @@ -74,11 +75,11 @@ async def _process_create_data(self, data: typing.Dict) -> typing.Dict: return data @callback - def _get_suggested_id(self, info: typing.Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[TAG_ID] - async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" data = {**data, **self.UPDATE_SCHEMA(update_data)} # make last_scanned JSON serializeable diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 463b1c65a98a1c..0cbb6e10f1fd03 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -1,6 +1,8 @@ """Provides device automations for Tasmota.""" +from __future__ import annotations + import logging -from typing import Callable, List, Optional +from typing import Callable import attr from hatasmota.trigger import TasmotaTrigger @@ -47,7 +49,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() trigger: "Trigger" = attr.ib() - remove: Optional[CALLBACK_TYPE] = attr.ib(default=None) + remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): """Attach event trigger.""" @@ -85,7 +87,7 @@ class Trigger: subtype: str = attr.ib() tasmota_trigger: TasmotaTrigger = attr.ib() type: str = attr.ib() - trigger_instances: List[TriggerInstance] = attr.ib(factory=list) + trigger_instances: list[TriggerInstance] = attr.ib(factory=list) async def add_trigger(self, action, automation_info): """Add Tasmota trigger.""" @@ -238,7 +240,7 @@ async def async_remove_triggers(hass: HomeAssistant, device_id: str): device_trigger.remove_update_signal() -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for a Tasmota device.""" triggers = [] diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 0387e835522622..9d7195a98e5b4f 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -1,5 +1,5 @@ """Support for Tasmota sensors.""" -from typing import Optional +from __future__ import annotations from hatasmota import const as hc, status_sensor @@ -163,7 +163,7 @@ def state_updated(self, state, **kwargs): self.async_write_ha_state() @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" class_or_icon = SENSOR_DEVICE_CLASS_ICON_MAP.get( self._tasmota_entity.quantity, {} diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index c67e3a275a3ce0..073924c51b6b75 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -1,5 +1,5 @@ """Allows the creation of a sensor that breaks out state_attributes.""" -from typing import Optional +from __future__ import annotations import voluptuous as vol @@ -165,7 +165,7 @@ def state(self): return self._state @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" return self._device_class diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 0ae540edf9714d..f8909206dec2a4 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -1,7 +1,8 @@ """TemplateEntity utility class.""" +from __future__ import annotations import logging -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable import voluptuous as vol @@ -30,8 +31,8 @@ def __init__( attribute: str, template: Template, validator: Callable[[Any], Any] = None, - on_update: Optional[Callable[[Any], None]] = None, - none_on_template_error: Optional[bool] = False, + on_update: Callable[[Any], None] | None = None, + none_on_template_error: bool | None = False, ): """Template attribute.""" self._entity = entity @@ -61,10 +62,10 @@ def _default_update(self, result): @callback def handle_result( self, - event: Optional[Event], + event: Event | None, template: Template, - last_result: Union[str, None, TemplateError], - result: Union[str, TemplateError], + last_result: str | None | TemplateError, + result: str | TemplateError, ) -> None: """Handle a template result event callback.""" if isinstance(result, TemplateError): @@ -189,7 +190,7 @@ def add_template_attribute( attribute: str, template: Template, validator: Callable[[Any], Any] = None, - on_update: Optional[Callable[[Any], None]] = None, + on_update: Callable[[Any], None] | None = None, none_on_template_error: bool = False, ) -> None: """ @@ -219,8 +220,8 @@ def add_template_attribute( @callback def _handle_results( self, - event: Optional[Event], - updates: List[TrackTemplateResult], + event: Event | None, + updates: list[TrackTemplateResult], ) -> None: """Call back the results to the attributes.""" if event: diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index 4c7ed850749dc0..81639bc3fe46c7 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -1,6 +1,7 @@ """Support for Tesla HVAC system.""" +from __future__ import annotations + import logging -from typing import List, Optional from teslajsonpy.exceptions import UnknownPresetMode @@ -103,7 +104,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: _LOGGER.error("%s", ex.message) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp. Requires SUPPORT_PRESET_MODE. @@ -111,7 +112,7 @@ def preset_mode(self) -> Optional[str]: return self.tesla_device.preset_mode @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes. Requires SUPPORT_PRESET_MODE. diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py index 16e8ce6dbe2d3b..6813b3769e79f1 100644 --- a/homeassistant/components/tesla/device_tracker.py +++ b/homeassistant/components/tesla/device_tracker.py @@ -1,5 +1,5 @@ """Support for tracking Tesla cars.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.device_tracker import SOURCE_TYPE_GPS from homeassistant.components.device_tracker.config_entry import TrackerEntity @@ -25,13 +25,13 @@ class TeslaDeviceEntity(TeslaDevice, TrackerEntity): """A class representing a Tesla device.""" @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of the device.""" location = self.tesla_device.get_location() return self.tesla_device.get_location().get("latitude") if location else None @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of the device.""" location = self.tesla_device.get_location() return self.tesla_device.get_location().get("longitude") if location else None diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 9094a7d6b013e0..40bf68bfa156af 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -1,5 +1,5 @@ """Support for the Tesla sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.sensor import DEVICE_CLASSES from homeassistant.const import ( @@ -39,7 +39,7 @@ def __init__(self, tesla_device, coordinator, sensor_type=None): self._unique_id = f"{super().unique_id}_{self.type}" @property - def state(self) -> Optional[float]: + def state(self) -> float | None: """Return the state of the sensor.""" if self.tesla_device.type == "temperature sensor": if self.type == "outside": @@ -58,7 +58,7 @@ def state(self) -> Optional[float]: return self.tesla_device.get_value() @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit_of_measurement of the device.""" units = self.tesla_device.measurement if units == "F": @@ -72,7 +72,7 @@ def unit_of_measurement(self) -> Optional[str]: return units @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device_class of the device.""" return ( self.tesla_device.device_class diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index b0ff60bbcaef29..05955b46b5cb48 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -3,7 +3,6 @@ from datetime import datetime, timedelta import logging -from typing import Dict, Optional import voluptuous as vol @@ -165,7 +164,7 @@ class TimerStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: Dict) -> Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" data = self.CREATE_SCHEMA(data) # make duration JSON serializeable @@ -173,11 +172,11 @@ async def _process_create_data(self, data: Dict) -> Dict: return data @callback - def _get_suggested_id(self, info: Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return info[CONF_NAME] - async def _update_data(self, data: dict, update_data: Dict) -> Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" data = {**data, **self.UPDATE_SCHEMA(update_data)} # make duration JSON serializeable @@ -189,18 +188,18 @@ async def _update_data(self, data: dict, update_data: Dict) -> Dict: class Timer(RestoreEntity): """Representation of a timer.""" - def __init__(self, config: Dict): + def __init__(self, config: dict): """Initialize a timer.""" self._config: dict = config self.editable: bool = True self._state: str = STATUS_IDLE self._duration = cv.time_period_str(config[CONF_DURATION]) - self._remaining: Optional[timedelta] = None - self._end: Optional[datetime] = None + self._remaining: timedelta | None = None + self._end: datetime | None = None self._listener = None @classmethod - def from_yaml(cls, config: Dict) -> Timer: + def from_yaml(cls, config: dict) -> Timer: """Return entity instance initialized from yaml storage.""" timer = cls(config) timer.entity_id = ENTITY_ID_FORMAT.format(config[CONF_ID]) @@ -247,7 +246,7 @@ def state_attributes(self): return attrs @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique id for the entity.""" return self._config[CONF_ID] @@ -348,7 +347,7 @@ def _async_finished(self, time): self.hass.bus.async_fire(EVENT_TIMER_FINISHED, {"entity_id": self.entity_id}) self.async_write_ha_state() - async def async_update_config(self, config: Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" self._config = config self._duration = cv.time_period_str(config[CONF_DURATION]) diff --git a/homeassistant/components/timer/reproduce_state.py b/homeassistant/components/timer/reproduce_state.py index 71abb0bfd71a5e..377f8a1dda22c3 100644 --- a/homeassistant/components/timer/reproduce_state.py +++ b/homeassistant/components/timer/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Timer state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import Context, State @@ -27,8 +29,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -69,8 +71,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Timer states.""" await asyncio.gather( diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index fe14435f2aba5c..6651806a21c7c5 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Toon binary sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry @@ -84,7 +84,7 @@ def device_class(self) -> str: return BINARY_SENSOR_ENTITIES[self.key][ATTR_DEVICE_CLASS] @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return the status of the binary sensor.""" section = getattr( self.coordinator.data, BINARY_SENSOR_ENTITIES[self.key][ATTR_SECTION] diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index 99b76600dce973..db2bed47f51615 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -1,5 +1,7 @@ """Support for Toon thermostat.""" -from typing import Any, Dict, List, Optional +from __future__ import annotations + +from typing import Any from toonapi import ( ACTIVE_STATE_AWAY, @@ -61,12 +63,12 @@ def hvac_mode(self) -> str: return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return [HVAC_MODE_HEAT] @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation.""" if self.coordinator.data.thermostat.heating: return CURRENT_HVAC_HEAT @@ -78,7 +80,7 @@ def temperature_unit(self) -> str: return TEMP_CELSIUS @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" mapping = { ACTIVE_STATE_AWAY: PRESET_AWAY, @@ -89,17 +91,17 @@ def preset_mode(self) -> Optional[str]: return mapping.get(self.coordinator.data.thermostat.active_state) @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return a list of available preset modes.""" return [PRESET_AWAY, PRESET_COMFORT, PRESET_HOME, PRESET_SLEEP] @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.coordinator.data.thermostat.current_display_temperature @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self.coordinator.data.thermostat.current_setpoint @@ -114,7 +116,7 @@ def max_temp(self) -> float: return DEFAULT_MAX_TEMP @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return the current state of the burner.""" return {"heating_type": self.coordinator.data.agreement.heating_type} diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index 1e1739e85dfb52..bc673d2d181b57 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure the Toon component.""" +from __future__ import annotations + import logging -from typing import Any, Dict, List, Optional +from typing import Any from toonapi import Agreement, Toon, ToonError import voluptuous as vol @@ -19,15 +21,15 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): DOMAIN = DOMAIN VERSION = 2 - agreements: Optional[List[Agreement]] = None - data: Optional[Dict[str, Any]] = None + agreements: list[Agreement] | None = None + data: dict[str, Any] | None = None @property def logger(self) -> logging.Logger: """Return logger.""" return logging.getLogger(__name__) - async def async_oauth_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> dict[str, Any]: """Test connection and load up agreements.""" self.data = data @@ -46,8 +48,8 @@ async def async_oauth_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any] return await self.async_step_agreement() async def async_step_import( - self, config: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, config: dict[str, Any] | None = None + ) -> dict[str, Any]: """Start a configuration flow based on imported data. This step is merely here to trigger "discovery" when the `toon` @@ -63,8 +65,8 @@ async def async_step_import( return await self.async_step_user() async def async_step_agreement( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Select Toon agreement to add.""" if len(self.agreements) == 1: return await self._create_entry(self.agreements[0]) @@ -85,7 +87,7 @@ async def async_step_agreement( agreement_index = agreements_list.index(user_input[CONF_AGREEMENT]) return await self._create_entry(self.agreements[agreement_index]) - async def _create_entry(self, agreement: Agreement) -> Dict[str, Any]: + async def _create_entry(self, agreement: Agreement) -> dict[str, Any]: if CONF_MIGRATE in self.context: await self.hass.config_entries.async_remove(self.context[CONF_MIGRATE]) diff --git a/homeassistant/components/toon/coordinator.py b/homeassistant/components/toon/coordinator.py index 359cb5b0ffb098..069bd58d922e42 100644 --- a/homeassistant/components/toon/coordinator.py +++ b/homeassistant/components/toon/coordinator.py @@ -1,7 +1,8 @@ """Provides the Toon DataUpdateCoordinator.""" +from __future__ import annotations + import logging import secrets -from typing import Optional from toonapi import Status, Toon, ToonError @@ -50,7 +51,7 @@ def update_listeners(self) -> None: for update_callback in self._listeners: update_callback() - async def register_webhook(self, event: Optional[Event] = None) -> None: + async def register_webhook(self, event: Event | None = None) -> None: """Register a webhook with Toon to get live updates.""" if CONF_WEBHOOK_ID not in self.entry.data: data = {**self.entry.data, CONF_WEBHOOK_ID: secrets.token_hex()} @@ -124,7 +125,7 @@ async def handle_webhook( except ToonError as err: _LOGGER.error("Could not process data received from Toon webhook - %s", err) - async def unregister_webhook(self, event: Optional[Event] = None) -> None: + async def unregister_webhook(self, event: Event | None = None) -> None: """Remove / Unregister webhook for toon.""" _LOGGER.debug( "Unregistering Toon webhook (%s)", self.entry.data[CONF_WEBHOOK_ID] diff --git a/homeassistant/components/toon/models.py b/homeassistant/components/toon/models.py index edcbed369a387e..8aee2fe27e1660 100644 --- a/homeassistant/components/toon/models.py +++ b/homeassistant/components/toon/models.py @@ -1,5 +1,7 @@ """DataUpdate Coordinator, and base Entity and Device models for Toon.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -31,7 +33,7 @@ def name(self) -> str: return self._name @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the mdi icon of the entity.""" return self._icon @@ -45,7 +47,7 @@ class ToonDisplayDeviceEntity(ToonEntity): """Defines a Toon display device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this thermostat.""" agreement = self.coordinator.data.agreement model = agreement.display_hardware_version.rpartition("/")[0] @@ -63,7 +65,7 @@ class ToonElectricityMeterDeviceEntity(ToonEntity): """Defines a Electricity Meter device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -77,7 +79,7 @@ class ToonGasMeterDeviceEntity(ToonEntity): """Defines a Gas Meter device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -91,7 +93,7 @@ class ToonWaterMeterDeviceEntity(ToonEntity): """Defines a Water Meter device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -105,7 +107,7 @@ class ToonSolarDeviceEntity(ToonEntity): """Defines a Solar Device device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -119,7 +121,7 @@ class ToonBoilerModuleDeviceEntity(ToonEntity): """Defines a Boiler Module device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { @@ -134,7 +136,7 @@ class ToonBoilerDeviceEntity(ToonEntity): """Defines a Boiler device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this entity.""" agreement_id = self.coordinator.data.agreement.agreement_id return { diff --git a/homeassistant/components/toon/oauth2.py b/homeassistant/components/toon/oauth2.py index e3a83583ac6f2e..7539224ebba2b2 100644 --- a/homeassistant/components/toon/oauth2.py +++ b/homeassistant/components/toon/oauth2.py @@ -1,5 +1,7 @@ """OAuth2 implementations for Toon.""" -from typing import Any, Optional, cast +from __future__ import annotations + +from typing import Any, cast from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow @@ -55,7 +57,7 @@ def __init__( client_secret: str, name: str, tenant_id: str, - issuer: Optional[str] = None, + issuer: str | None = None, ): """Local Toon Oauth Implementation.""" self._name = name diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 52d3a68f2c1b8e..583683e53aeebd 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,5 +1,5 @@ """Support for Toon sensors.""" -from typing import Optional +from __future__ import annotations from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -132,7 +132,7 @@ def unique_id(self) -> str: return f"{DOMAIN}_{agreement_id}_sensor_{self.key}" @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the sensor.""" section = getattr( self.coordinator.data, SENSOR_ENTITIES[self.key][ATTR_SECTION] @@ -140,12 +140,12 @@ def state(self) -> Optional[str]: return getattr(section, SENSOR_ENTITIES[self.key][ATTR_MEASUREMENT]) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return SENSOR_ENTITIES[self.key][ATTR_UNIT_OF_MEASUREMENT] @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class.""" return SENSOR_ENTITIES[self.key][ATTR_DEVICE_CLASS] diff --git a/homeassistant/components/tplink/common.py b/homeassistant/components/tplink/common.py index 1aca4bf7edc0d8..b9318cf3fdd217 100644 --- a/homeassistant/components/tplink/common.py +++ b/homeassistant/components/tplink/common.py @@ -1,6 +1,7 @@ """Common code for tplink.""" +from __future__ import annotations + import logging -from typing import List from pyHS100 import ( Discover, @@ -30,7 +31,7 @@ class SmartDevices: """Hold different kinds of devices.""" def __init__( - self, lights: List[SmartDevice] = None, switches: List[SmartDevice] = None + self, lights: list[SmartDevice] = None, switches: list[SmartDevice] = None ): """Initialize device holder.""" self._lights = lights or [] diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 31b2319ead044a..3cb7e663058053 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -1,9 +1,11 @@ """Support for TPLink lights.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import time -from typing import Any, Dict, NamedTuple, Tuple, cast +from typing import Any, NamedTuple, cast from pyHS100 import SmartBulb, SmartDeviceException @@ -88,7 +90,7 @@ class LightState(NamedTuple): state: bool brightness: int color_temp: float - hs: Tuple[int, int] + hs: tuple[int, int] def to_param(self): """Return a version that we can send to the bulb.""" @@ -109,7 +111,7 @@ def to_param(self): class LightFeatures(NamedTuple): """Light features.""" - sysinfo: Dict[str, Any] + sysinfo: dict[str, Any] mac: str alias: str model: str diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index d2b52771124b63..6a59b3a3d618f3 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,5 +1,5 @@ """Support for monitoring the Transmission BitTorrent client API.""" -from typing import List +from __future__ import annotations from transmissionrpc.torrent import Torrent @@ -168,7 +168,7 @@ def update(self): self._state = len(torrents) -def _filter_torrents(torrents: List[Torrent], statuses=None) -> List[Torrent]: +def _filter_torrents(torrents: list[Torrent], statuses=None) -> list[Torrent]: return [ torrent for torrent in torrents diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index f9b07a98595bc3..3ec9c0645aa7da 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -1,4 +1,6 @@ """Provide functionality for TTS.""" +from __future__ import annotations + import asyncio import functools as ft import hashlib @@ -7,7 +9,7 @@ import mimetypes import os import re -from typing import Dict, Optional, cast +from typing import cast from aiohttp import web import mutagen @@ -243,7 +245,7 @@ async def async_clear_cache_handle(service): return True -def _hash_options(options: Dict) -> str: +def _hash_options(options: dict) -> str: """Hashes an options dictionary.""" opts_hash = hashlib.blake2s(digest_size=5) for key, value in sorted(options.items()): @@ -512,8 +514,8 @@ def write_tags(filename, data, provider, message, language, options): class Provider: """Represent a single TTS provider.""" - hass: Optional[HomeAssistantType] = None - name: Optional[str] = None + hass: HomeAssistantType | None = None + name: str | None = None @property def default_language(self): diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index cb6f96358c9a26..ab361c6ac31d5e 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,6 +1,7 @@ """Support for Tuya fans.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional from homeassistant.components.fan import ( DOMAIN as SENSOR_DOMAIN, @@ -124,7 +125,7 @@ def is_on(self): return self._tuya.state() @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed.""" if not self.is_on: return 0 diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 8de51d19d51869..ece3e0b048cb58 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -1,8 +1,9 @@ """The Twinkly light component.""" +from __future__ import annotations import asyncio import logging -from typing import Any, Dict, Optional +from typing import Any from aiohttp import ClientError @@ -84,7 +85,7 @@ def available(self) -> bool: return self._is_available @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Id of the device.""" return self._id @@ -104,7 +105,7 @@ def icon(self) -> str: return "mdi:string-lights" @property - def device_info(self) -> Optional[Dict[str, Any]]: + def device_info(self) -> dict[str, Any] | None: """Get device specific attributes.""" return ( { @@ -123,7 +124,7 @@ def is_on(self) -> bool: return self._is_on @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of the light.""" return self._brightness diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 9096620f0eda16..dc56cd9d9e3d9d 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -1,8 +1,9 @@ """UniFi Controller abstraction.""" +from __future__ import annotations + import asyncio from datetime import datetime, timedelta import ssl -from typing import Optional from aiohttp import CookieJar import aiounifi @@ -385,7 +386,7 @@ async def async_setup(self): @callback def async_heartbeat( - self, unique_id: str, heartbeat_expire_time: Optional[datetime] = None + self, unique_id: str, heartbeat_expire_time: datetime | None = None ) -> None: """Signal when a device has fresh home state.""" if heartbeat_expire_time is not None: diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index c814abf5a82873..f6e770c126f254 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -1,6 +1,7 @@ """Combination of multiple media players for a universal controller.""" +from __future__ import annotations + from copy import copy -from typing import Optional import voluptuous as vol @@ -270,7 +271,7 @@ def should_poll(self): return False @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device.""" return self._device_class diff --git a/homeassistant/components/upc_connect/device_tracker.py b/homeassistant/components/upc_connect/device_tracker.py index d68311a879341a..2a2a1b3798bb86 100644 --- a/homeassistant/components/upc_connect/device_tracker.py +++ b/homeassistant/components/upc_connect/device_tracker.py @@ -1,6 +1,7 @@ """Support for UPC ConnectBox router.""" +from __future__ import annotations + import logging -from typing import List, Optional from connect_box import ConnectBox from connect_box.exceptions import ConnectBoxError, ConnectBoxLoginError @@ -57,7 +58,7 @@ def __init__(self, connect_box: ConnectBox): """Initialize the scanner.""" self.connect_box: ConnectBox = connect_box - async def async_scan_devices(self) -> List[str]: + async def async_scan_devices(self) -> list[str]: """Scan for new devices and return a list with found device IDs.""" try: await self.connect_box.async_get_devices() @@ -66,7 +67,7 @@ async def async_scan_devices(self) -> List[str]: return [device.mac for device in self.connect_box.devices] - async def async_get_device_name(self, device: str) -> Optional[str]: + async def async_get_device_name(self, device: str) -> str | None: """Get the device name (the name of the wireless device not used).""" for connected_device in self.connect_box.devices: if ( diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index eba61058ca0bcb..0f463aec666ebe 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -1,9 +1,10 @@ """Support for UpCloud.""" +from __future__ import annotations import dataclasses from datetime import timedelta import logging -from typing import Dict, List +from typing import Dict import requests.exceptions import upcloud_api @@ -91,7 +92,7 @@ def __init__( hass, _LOGGER, name=f"{username}@UpCloud", update_interval=update_interval ) self.cloud_manager = cloud_manager - self.unsub_handlers: List[CALLBACK_TYPE] = [] + self.unsub_handlers: list[CALLBACK_TYPE] = [] async def async_update_config(self, config_entry: ConfigEntry) -> None: """Handle config update.""" @@ -99,7 +100,7 @@ async def async_update_config(self, config_entry: ConfigEntry) -> None: seconds=config_entry.options[CONF_SCAN_INTERVAL] ) - async def _async_update_data(self) -> Dict[str, upcloud_api.Server]: + async def _async_update_data(self) -> dict[str, upcloud_api.Server]: return { x.uuid: x for x in await self.hass.async_add_executor_job( @@ -112,10 +113,10 @@ async def _async_update_data(self) -> Dict[str, upcloud_api.Server]: class UpCloudHassData: """Home Assistant UpCloud runtime data.""" - coordinators: Dict[str, UpCloudDataUpdateCoordinator] = dataclasses.field( + coordinators: dict[str, UpCloudDataUpdateCoordinator] = dataclasses.field( default_factory=dict ) - scan_interval_migrations: Dict[str, int] = dataclasses.field(default_factory=dict) + scan_interval_migrations: dict[str, int] = dataclasses.field(default_factory=dict) async def async_setup(hass: HomeAssistantType, config) -> bool: diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index e1101c3713c21c..1cbaf9318572bb 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,6 +1,8 @@ """Config flow for UPNP.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Mapping, Optional +from typing import Any, Mapping import voluptuous as vol @@ -55,7 +57,7 @@ def __init__(self) -> None: self._discoveries: Mapping = None async def async_step_user( - self, user_input: Optional[Mapping] = None + self, user_input: Mapping | None = None ) -> Mapping[str, Any]: """Handle a flow start.""" _LOGGER.debug("async_step_user: user_input: %s", user_input) @@ -111,9 +113,7 @@ async def async_step_user( data_schema=data_schema, ) - async def async_step_import( - self, import_info: Optional[Mapping] - ) -> Mapping[str, Any]: + async def async_step_import(self, import_info: Mapping | None) -> Mapping[str, Any]: """Import a new UPnP/IGD device as a config entry. This flow is triggered by `async_setup`. If no device has been @@ -204,7 +204,7 @@ async def async_step_ssdp(self, discovery_info: Mapping) -> Mapping[str, Any]: return await self.async_step_ssdp_confirm() async def async_step_ssdp_confirm( - self, user_input: Optional[Mapping] = None + self, user_input: Mapping | None = None ) -> Mapping[str, Any]: """Confirm integration via SSDP.""" _LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index a06ca254c87914..034496ec02817d 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -3,7 +3,7 @@ import asyncio from ipaddress import IPv4Address -from typing import List, Mapping +from typing import Mapping from urllib.parse import urlparse from async_upnp_client import UpnpFactory @@ -42,7 +42,7 @@ def __init__(self, igd_device): self._igd_device: IgdDevice = igd_device @classmethod - async def async_discover(cls, hass: HomeAssistantType) -> List[Mapping]: + async def async_discover(cls, hass: HomeAssistantType) -> list[Mapping]: """Discover UPnP/IGD devices.""" _LOGGER.debug("Discovering UPnP/IGD devices") local_ip = None diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 97d3c1a702c178..76bb1d023b7ff2 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -1,6 +1,8 @@ """Support for UPnP/IGD Sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Mapping, Optional +from typing import Any, Mapping from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_BYTES, DATA_RATE_KIBIBYTES_PER_SECOND @@ -176,7 +178,7 @@ class RawUpnpSensor(UpnpSensor): """Representation of a UPnP/IGD sensor.""" @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the device.""" device_value_key = self._sensor_type["device_value_key"] value = self.coordinator.data[device_value_key] @@ -214,7 +216,7 @@ def _has_overflowed(self, current_value) -> bool: return current_value < self._last_value @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the device.""" # Can't calculate any derivative if we have only one value. device_value_key = self._sensor_type["device_value_key"] diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 364e0599b75dbb..eefa2ed1d0d627 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -1,7 +1,8 @@ """Support for U.S. Geological Survey Earthquake Hazards Program Feeds.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Optional from geojson_client.usgs_earthquake_hazards_program_feed import ( UsgsEarthquakeHazardsProgramFeedManager, @@ -255,22 +256,22 @@ def source(self) -> str: return SOURCE @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Return the name of the entity.""" return self._name @property - def distance(self) -> Optional[float]: + def distance(self) -> float | None: """Return distance value of this external event.""" return self._distance @property - def latitude(self) -> Optional[float]: + def latitude(self) -> float | None: """Return latitude value of this external event.""" return self._latitude @property - def longitude(self) -> Optional[float]: + def longitude(self) -> float | None: """Return longitude value of this external event.""" return self._longitude diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 82f59a40131d3a..94181de37c4bca 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -1,8 +1,9 @@ """Support for Ubiquiti's UVC cameras.""" +from __future__ import annotations + from datetime import datetime import logging import re -from typing import Optional import requests from uvcclient import camera as uvc_camera, nvr @@ -255,7 +256,7 @@ def update(self): self._caminfo = self._nvr.get_camera(self._uuid) -def timestamp_ms_to_date(epoch_ms: int) -> Optional[datetime]: +def timestamp_ms_to_date(epoch_ms: int) -> datetime | None: """Convert millisecond timestamp to datetime.""" if epoch_ms: return datetime.fromtimestamp(epoch_ms / 1000) diff --git a/homeassistant/components/vacuum/device_action.py b/homeassistant/components/vacuum/device_action.py index ed25289da10ede..2308882469e6c9 100644 --- a/homeassistant/components/vacuum/device_action.py +++ b/homeassistant/components/vacuum/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Vacuum.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -26,7 +26,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Vacuum devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -57,7 +57,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/vacuum/device_condition.py b/homeassistant/components/vacuum/device_condition.py index cb17505f6e1022..4803ebdb988543 100644 --- a/homeassistant/components/vacuum/device_condition.py +++ b/homeassistant/components/vacuum/device_condition.py @@ -1,5 +1,5 @@ """Provide the device automations for Vacuum.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -30,7 +30,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for Vacuum devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 21a2ae5e8c2b4a..8023a3865a7421 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -1,5 +1,5 @@ """Provides device automations for Vacuum.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -29,7 +29,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for Vacuum devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/homeassistant/components/vacuum/reproduce_state.py b/homeassistant/components/vacuum/reproduce_state.py index 48aa9615f1ef16..38958bd47908a1 100644 --- a/homeassistant/components/vacuum/reproduce_state.py +++ b/homeassistant/components/vacuum/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Vacuum state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -44,8 +46,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -99,8 +101,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Vacuum states.""" # Reproduce states in parallel. diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index ff8dc8b96d1f2f..929e4424d804e9 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -1,8 +1,10 @@ """Support for Vera devices.""" +from __future__ import annotations + import asyncio from collections import defaultdict import logging -from typing import Any, Dict, Generic, List, Optional, Type, TypeVar +from typing import Any, Generic, TypeVar import pyvera as veraApi from requests.exceptions import RequestException @@ -172,7 +174,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return True -def map_vera_device(vera_device: veraApi.VeraDevice, remap: List[int]) -> str: +def map_vera_device(vera_device: veraApi.VeraDevice, remap: list[int]) -> str: """Map vera classes to Home Assistant types.""" type_map = { @@ -187,7 +189,7 @@ def map_vera_device(vera_device: veraApi.VeraDevice, remap: List[int]) -> str: veraApi.VeraSwitch: "switch", } - def map_special_case(instance_class: Type, entity_type: str) -> str: + def map_special_case(instance_class: type, entity_type: str) -> str: if instance_class is veraApi.VeraSwitch and vera_device.device_id in remap: return "light" return entity_type @@ -248,7 +250,7 @@ def should_poll(self) -> bool: return self.vera_device.should_poll @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" attr = {} diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 00d4fb3a75827d..816234bb6025b7 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -1,5 +1,7 @@ """Support for Vera binary sensors.""" -from typing import Callable, List, Optional +from __future__ import annotations + +from typing import Callable import pyvera as veraApi @@ -19,7 +21,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -44,7 +46,7 @@ def __init__( self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return true if sensor is on.""" return self._state diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 9abfc485268d1f..5027becb71fb19 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -1,5 +1,7 @@ """Support for Vera thermostats.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -36,7 +38,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -60,7 +62,7 @@ def __init__( self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int | None: """Return the list of supported features.""" return SUPPORT_FLAGS @@ -80,7 +82,7 @@ def hvac_mode(self) -> str: return HVAC_MODE_OFF @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. @@ -88,7 +90,7 @@ def hvac_modes(self) -> List[str]: return SUPPORT_HVAC @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" mode = self.vera_device.get_fan_mode() if mode == "ContinuousOn": @@ -96,7 +98,7 @@ def fan_mode(self) -> Optional[str]: return FAN_AUTO @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return a list of available fan modes.""" return FAN_OPERATION_LIST @@ -110,7 +112,7 @@ def set_fan_mode(self, fan_mode) -> None: self.schedule_update_ha_state() @property - def current_power_w(self) -> Optional[float]: + def current_power_w(self) -> float | None: """Return the current power usage in W.""" power = self.vera_device.power if power: @@ -127,7 +129,7 @@ def temperature_unit(self) -> str: return TEMP_CELSIUS @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self.vera_device.get_current_temperature() @@ -137,7 +139,7 @@ def operation(self) -> str: return self.vera_device.get_hvac_mode() @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self.vera_device.get_current_goal_temperature() diff --git a/homeassistant/components/vera/common.py b/homeassistant/components/vera/common.py index fce6475f930c0a..fcc501c20945c2 100644 --- a/homeassistant/components/vera/common.py +++ b/homeassistant/components/vera/common.py @@ -1,5 +1,7 @@ """Common vera code.""" -from typing import DefaultDict, List, NamedTuple, Set +from __future__ import annotations + +from typing import DefaultDict, NamedTuple import pyvera as pv @@ -15,12 +17,12 @@ class ControllerData(NamedTuple): """Controller data.""" controller: pv.VeraController - devices: DefaultDict[str, List[pv.VeraDevice]] - scenes: List[pv.VeraScene] + devices: DefaultDict[str, list[pv.VeraDevice]] + scenes: list[pv.VeraScene] config_entry: ConfigEntry -def get_configured_platforms(controller_data: ControllerData) -> Set[str]: +def get_configured_platforms(controller_data: ControllerData) -> set[str]: """Get configured platforms for a controller.""" platforms = [] for platform in controller_data.devices: diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index e62b21c82ea7b8..a5450cd4a651bf 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -1,7 +1,9 @@ """Config flow for Vera.""" +from __future__ import annotations + import logging import re -from typing import Any, List +from typing import Any import pyvera as pv from requests.exceptions import RequestException @@ -19,22 +21,22 @@ _LOGGER = logging.getLogger(__name__) -def fix_device_id_list(data: List[Any]) -> List[int]: +def fix_device_id_list(data: list[Any]) -> list[int]: """Fix the id list by converting it to a supported int list.""" return str_to_int_list(list_to_str(data)) -def str_to_int_list(data: str) -> List[int]: +def str_to_int_list(data: str) -> list[int]: """Convert a string to an int list.""" return [int(s) for s in LIST_REGEX.split(data) if len(s) > 0] -def list_to_str(data: List[Any]) -> str: +def list_to_str(data: list[Any]) -> str: """Convert an int list to a string.""" return " ".join([str(i) for i in data]) -def new_options(lights: List[int], exclude: List[int]) -> dict: +def new_options(lights: list[int], exclude: list[int]) -> dict: """Create a standard options object.""" return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude} diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 43f68fba786884..cf3dd4a3d135b2 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -1,5 +1,7 @@ """Support for Vera cover - curtains, rollershutters etc.""" -from typing import Any, Callable, List +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -20,7 +22,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 30c4e93a2ba7d3..7fcb726efcc912 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -1,5 +1,7 @@ """Support for Vera lights.""" -from typing import Any, Callable, List, Optional, Tuple +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -24,7 +26,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -51,12 +53,12 @@ def __init__( self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of the light.""" return self._brightness @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the color of the light.""" return self._color diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index f98319b6ccf0f4..eada4b205502bf 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -1,5 +1,7 @@ """Support for Vera locks.""" -from typing import Any, Callable, Dict, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -23,7 +25,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -56,12 +58,12 @@ def unlock(self, **kwargs: Any) -> None: self._state = STATE_UNLOCKED @property - def is_locked(self) -> Optional[bool]: + def is_locked(self) -> bool | None: """Return true if device is on.""" return self._state == STATE_LOCKED @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Who unlocked the lock and did a low battery alert fire. Reports on the previous poll cycle. @@ -78,7 +80,7 @@ def extra_state_attributes(self) -> Optional[Dict[str, Any]]: return data @property - def changed_by(self) -> Optional[str]: + def changed_by(self) -> str | None: """Who unlocked the lock. Reports on the previous poll cycle. diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 4ecbe8c724e1a2..c6eb983a8f78d0 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,5 +1,7 @@ """Support for Vera scenes.""" -from typing import Any, Callable, Dict, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -16,7 +18,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -53,6 +55,6 @@ def name(self) -> str: return self._name @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the scene.""" return {"vera_scene_id": self.vera_scene.vera_scene_id} diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index 007290807e691f..d52d49c2546775 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -1,6 +1,8 @@ """Support for Vera sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Callable, List, Optional, cast +from typing import Callable, cast import pyvera as veraApi @@ -20,7 +22,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -52,7 +54,7 @@ def state(self) -> str: return self.current_value @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index f567893e5b0f5e..c779a3c8cfc9e6 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -1,5 +1,7 @@ """Support for Vera switches.""" -from typing import Any, Callable, List, Optional +from __future__ import annotations + +from typing import Any, Callable import pyvera as veraApi @@ -20,7 +22,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" controller_data = get_controller_data(hass, entry) @@ -57,7 +59,7 @@ def turn_off(self, **kwargs: Any) -> None: self.schedule_update_ha_state() @property - def current_power_w(self) -> Optional[float]: + def current_power_w(self) -> float | None: """Return the current power usage in W.""" power = self.vera_device.power if power: diff --git a/homeassistant/components/vizio/__init__.py b/homeassistant/components/vizio/__init__.py index a7a9c404f74d9c..3719ada27aef5b 100644 --- a/homeassistant/components/vizio/__init__.py +++ b/homeassistant/components/vizio/__init__.py @@ -1,8 +1,10 @@ """The vizio component.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict, List +from typing import Any from pyvizio.const import APPS from pyvizio.util import gen_apps_list_from_url @@ -116,7 +118,7 @@ def __init__(self, hass: HomeAssistantType) -> None: ) self.data = APPS - async def _async_update_data(self) -> List[Dict[str, Any]]: + async def _async_update_data(self) -> list[dict[str, Any]]: """Update data via library.""" data = await gen_apps_list_from_url(session=async_get_clientsession(self.hass)) if not data: diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 3f57cdb81facee..c6632868ae3b39 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -1,8 +1,10 @@ """Config flow for Vizio.""" +from __future__ import annotations + import copy import logging import socket -from typing import Any, Dict, Optional +from typing import Any from pyvizio import VizioAsync, async_guess_device_type from pyvizio.const import APP_HOME @@ -48,7 +50,7 @@ _LOGGER = logging.getLogger(__name__) -def _get_config_schema(input_dict: Dict[str, Any] = None) -> vol.Schema: +def _get_config_schema(input_dict: dict[str, Any] = None) -> vol.Schema: """ Return schema defaults for init step based on user input/config dict. @@ -76,7 +78,7 @@ def _get_config_schema(input_dict: Dict[str, Any] = None) -> vol.Schema: ) -def _get_pairing_schema(input_dict: Dict[str, Any] = None) -> vol.Schema: +def _get_pairing_schema(input_dict: dict[str, Any] = None) -> vol.Schema: """ Return schema defaults for pairing data based on user input. @@ -108,8 +110,8 @@ def __init__(self, config_entry: ConfigEntry) -> None: self.config_entry = config_entry async def async_step_init( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Manage the vizio options.""" if user_input is not None: if user_input.get(CONF_APPS_TO_INCLUDE_OR_EXCLUDE): @@ -191,7 +193,7 @@ def __init__(self) -> None: self._data = None self._apps = {} - async def _create_entry(self, input_dict: Dict[str, Any]) -> Dict[str, Any]: + async def _create_entry(self, input_dict: dict[str, Any]) -> dict[str, Any]: """Create vizio config entry.""" # Remove extra keys that will not be used by entry setup input_dict.pop(CONF_APPS_TO_INCLUDE_OR_EXCLUDE, None) @@ -203,8 +205,8 @@ async def _create_entry(self, input_dict: Dict[str, Any]) -> Dict[str, Any]: return self.async_create_entry(title=input_dict[CONF_NAME], data=input_dict) async def async_step_user( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """Handle a flow initialized by the user.""" errors = {} @@ -276,7 +278,7 @@ async def async_step_user( return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - async def async_step_import(self, import_config: Dict[str, Any]) -> Dict[str, Any]: + async def async_step_import(self, import_config: dict[str, Any]) -> dict[str, Any]: """Import a config entry from configuration.yaml.""" # Check if new config entry matches any existing config entries for entry in self.hass.config_entries.async_entries(DOMAIN): @@ -339,8 +341,8 @@ async def async_step_import(self, import_config: Dict[str, Any]) -> Dict[str, An return await self.async_step_user(user_input=import_config) async def async_step_zeroconf( - self, discovery_info: Optional[DiscoveryInfoType] = None - ) -> Dict[str, Any]: + self, discovery_info: DiscoveryInfoType | None = None + ) -> dict[str, Any]: """Handle zeroconf discovery.""" # If host already has port, no need to add it again if ":" not in discovery_info[CONF_HOST]: @@ -376,8 +378,8 @@ async def async_step_zeroconf( return await self.async_step_user(user_input=discovery_info) async def async_step_pair_tv( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """ Start pairing process for TV. @@ -442,7 +444,7 @@ async def async_step_pair_tv( errors=errors, ) - async def _pairing_complete(self, step_id: str) -> Dict[str, Any]: + async def _pairing_complete(self, step_id: str) -> dict[str, Any]: """Handle config flow completion.""" if not self._must_show_form: return await self._create_entry(self._data) @@ -455,8 +457,8 @@ async def _pairing_complete(self, step_id: str) -> Dict[str, Any]: ) async def async_step_pairing_complete( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """ Complete non-import sourced config flow. @@ -465,8 +467,8 @@ async def async_step_pairing_complete( return await self._pairing_complete("pairing_complete") async def async_step_pairing_complete_import( - self, user_input: Dict[str, Any] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] = None + ) -> dict[str, Any]: """ Complete import sourced config flow. diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 53c8a2bba88b8f..fc955d48158b9e 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -1,7 +1,9 @@ """Vizio SmartCast Device support.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable from pyvizio import VizioAsync from pyvizio.api.apps import find_app_name @@ -64,7 +66,7 @@ async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up a Vizio media player entry.""" host = config_entry.data[CONF_HOST] @@ -166,7 +168,7 @@ def __init__( self._model = None self._sw_version = None - def _apps_list(self, apps: List[str]) -> List[str]: + def _apps_list(self, apps: list[str]) -> list[str]: """Return process apps list based on configured filters.""" if self._conf_apps.get(CONF_INCLUDE): return [app for app in apps if app in self._conf_apps[CONF_INCLUDE]] @@ -274,7 +276,7 @@ async def async_update(self) -> None: if self._current_app == NO_APP_RUNNING: self._current_app = None - def _get_additional_app_names(self) -> List[Dict[str, Any]]: + def _get_additional_app_names(self) -> list[dict[str, Any]]: """Return list of additional apps that were included in configuration.yaml.""" return [ additional_app["name"] for additional_app in self._additional_app_configs @@ -296,7 +298,7 @@ async def _async_update_options(self, config_entry: ConfigEntry) -> None: self._conf_apps.update(config_entry.options.get(CONF_APPS, {})) async def async_update_setting( - self, setting_type: str, setting_name: str, new_value: Union[int, str] + self, setting_type: str, setting_name: str, new_value: int | str ) -> None: """Update a setting when update_setting service is called.""" await self._device.set_setting( @@ -340,7 +342,7 @@ def available(self) -> bool: return self._available @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the state of the device.""" return self._state @@ -355,7 +357,7 @@ def icon(self) -> str: return self._icon @property - def volume_level(self) -> Optional[float]: + def volume_level(self) -> float | None: """Return the volume level of the device.""" return self._volume_level @@ -365,7 +367,7 @@ def is_volume_muted(self): return self._is_volume_muted @property - def source(self) -> Optional[str]: + def source(self) -> str | None: """Return current input of the device.""" if self._current_app is not None and self._current_input in INPUT_APPS: return self._current_app @@ -373,7 +375,7 @@ def source(self) -> Optional[str]: return self._current_input @property - def source_list(self) -> List[str]: + def source_list(self) -> list[str]: """Return list of available inputs of the device.""" # If Smartcast app is in input list, and the app list has been retrieved, # show the combination with , otherwise just return inputs @@ -395,7 +397,7 @@ def source_list(self) -> List[str]: return self._available_inputs @property - def app_id(self) -> Optional[str]: + def app_id(self) -> str | None: """Return the ID of the current app if it is unknown by pyvizio.""" if self._current_app_config and self.app_name == UNKNOWN_APP: return { @@ -407,7 +409,7 @@ def app_id(self) -> Optional[str]: return None @property - def app_name(self) -> Optional[str]: + def app_name(self) -> str | None: """Return the friendly name of the current app.""" return self._current_app @@ -422,7 +424,7 @@ def unique_id(self) -> str: return self._config_entry.unique_id @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device registry information.""" return { "identifiers": {(DOMAIN, self._config_entry.unique_id)}, @@ -438,12 +440,12 @@ def device_class(self) -> str: return self._device_class @property - def sound_mode(self) -> Optional[str]: + def sound_mode(self) -> str | None: """Name of the current sound mode.""" return self._current_sound_mode @property - def sound_mode_list(self) -> Optional[List[str]]: + def sound_mode_list(self) -> list[str] | None: """List of available sound modes.""" return self._available_sound_modes diff --git a/homeassistant/components/volumio/config_flow.py b/homeassistant/components/volumio/config_flow.py index 950a161a5c3ece..a0ed860f061edd 100644 --- a/homeassistant/components/volumio/config_flow.py +++ b/homeassistant/components/volumio/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Volumio integration.""" +from __future__ import annotations + import logging -from typing import Optional from pyvolumio import CannotConnectError, Volumio import voluptuous as vol @@ -39,10 +40,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._host: Optional[str] = None - self._port: Optional[int] = None - self._name: Optional[str] = None - self._uuid: Optional[str] = None + self._host: str | None = None + self._port: int | None = None + self._name: str | None = None + self._uuid: str | None = None @callback def _async_get_entry(self): From 7d196abc4a638c52f2f165018952788c8a79efd4 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 18 Mar 2021 14:55:39 +0100 Subject: [PATCH 1360/1818] Add tests for Netatmo oauth2 api (#46375) * Add Netatmo tests for api * Update tests/components/netatmo/test_api.py * Update .coveragerc Co-authored-by: Erik Montnemery --- .coveragerc | 1 - tests/components/netatmo/test_api.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/components/netatmo/test_api.py diff --git a/.coveragerc b/.coveragerc index 8f609ec5b22235..92d55e83527ee7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,6 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/__init__.py - homeassistant/components/netatmo/api.py homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py diff --git a/tests/components/netatmo/test_api.py b/tests/components/netatmo/test_api.py new file mode 100644 index 00000000000000..76d16d10515aa3 --- /dev/null +++ b/tests/components/netatmo/test_api.py @@ -0,0 +1,14 @@ +"""The tests for the Netatmo oauth2 api.""" +from unittest.mock import patch + +from homeassistant.components.netatmo import api + + +async def test_api(hass, config_entry): + """Test auth instantiation.""" + with patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as fake_implementation: + auth = api.ConfigEntryNetatmoAuth(hass, config_entry, fake_implementation) + + assert isinstance(auth, api.ConfigEntryNetatmoAuth) From dcca29ef68e74cfcbca1c7bf7309e1041a8b7f90 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 15:08:35 +0100 Subject: [PATCH 1361/1818] Update typing 14 (#48078) --- .../components/water_heater/device_action.py | 6 +- .../water_heater/reproduce_state.py | 12 ++-- .../components/websocket_api/__init__.py | 10 +-- .../components/websocket_api/connection.py | 8 ++- .../components/websocket_api/http.py | 5 +- .../components/websocket_api/messages.py | 9 +-- homeassistant/components/wemo/entity.py | 12 ++-- homeassistant/components/wilight/fan.py | 5 +- homeassistant/components/withings/__init__.py | 5 +- .../components/withings/binary_sensor.py | 6 +- homeassistant/components/withings/common.py | 52 ++++++++-------- .../components/withings/config_flow.py | 5 +- homeassistant/components/withings/sensor.py | 8 ++- homeassistant/components/wled/__init__.py | 6 +- homeassistant/components/wled/config_flow.py | 22 ++++--- homeassistant/components/wled/light.py | 34 +++++----- homeassistant/components/wled/sensor.py | 16 ++--- homeassistant/components/wled/switch.py | 12 ++-- .../components/wunderground/sensor.py | 26 ++++---- homeassistant/components/xbox/__init__.py | 19 +++--- homeassistant/components/xbox/base_sensor.py | 4 +- .../components/xbox/binary_sensor.py | 7 ++- homeassistant/components/xbox/browse_media.py | 8 +-- homeassistant/components/xbox/media_player.py | 6 +- homeassistant/components/xbox/media_source.py | 11 ++-- homeassistant/components/xbox/sensor.py | 7 ++- homeassistant/components/yeelight/__init__.py | 7 ++- homeassistant/components/zerproc/light.py | 10 +-- homeassistant/components/zha/climate.py | 23 +++---- homeassistant/components/zha/config_flow.py | 6 +- .../components/zha/core/channels/__init__.py | 32 +++++----- .../components/zha/core/channels/base.py | 5 +- .../components/zha/core/channels/general.py | 28 +++++---- .../zha/core/channels/homeautomation.py | 8 ++- .../components/zha/core/channels/hvac.py | 28 +++++---- .../components/zha/core/channels/lighting.py | 12 ++-- .../zha/core/channels/smartenergy.py | 6 +- homeassistant/components/zha/core/const.py | 5 +- .../components/zha/core/decorators.py | 8 ++- homeassistant/components/zha/core/device.py | 6 +- .../components/zha/core/discovery.py | 13 ++-- homeassistant/components/zha/core/gateway.py | 8 +-- homeassistant/components/zha/core/group.py | 28 +++++---- homeassistant/components/zha/core/helpers.py | 13 ++-- .../components/zha/core/registries.py | 46 +++++++------- homeassistant/components/zha/core/store.py | 10 +-- homeassistant/components/zha/cover.py | 11 ++-- homeassistant/components/zha/device_action.py | 4 +- homeassistant/components/zha/entity.py | 21 ++++--- homeassistant/components/zha/fan.py | 23 +++---- homeassistant/components/zha/light.py | 32 +++++----- homeassistant/components/zha/sensor.py | 22 ++++--- homeassistant/components/zha/switch.py | 8 ++- homeassistant/components/zone/__init__.py | 28 ++++----- homeassistant/components/zwave/climate.py | 13 ++-- homeassistant/components/zwave_js/__init__.py | 6 +- homeassistant/components/zwave_js/addon.py | 8 +-- homeassistant/components/zwave_js/api.py | 5 +- .../components/zwave_js/binary_sensor.py | 25 ++++---- homeassistant/components/zwave_js/climate.py | 56 +++++++++-------- .../components/zwave_js/config_flow.py | 62 ++++++++++--------- homeassistant/components/zwave_js/cover.py | 20 +++--- .../components/zwave_js/discovery.py | 35 ++++++----- homeassistant/components/zwave_js/entity.py | 16 ++--- homeassistant/components/zwave_js/fan.py | 18 +++--- homeassistant/components/zwave_js/helpers.py | 8 ++- homeassistant/components/zwave_js/light.py | 24 +++---- homeassistant/components/zwave_js/lock.py | 16 ++--- homeassistant/components/zwave_js/migrate.py | 5 +- homeassistant/components/zwave_js/number.py | 10 +-- homeassistant/components/zwave_js/sensor.py | 17 ++--- homeassistant/components/zwave_js/services.py | 8 +-- homeassistant/components/zwave_js/switch.py | 11 ++-- 73 files changed, 614 insertions(+), 521 deletions(-) diff --git a/homeassistant/components/water_heater/device_action.py b/homeassistant/components/water_heater/device_action.py index 991929580d1a91..f138c777d44e34 100644 --- a/homeassistant/components/water_heater/device_action.py +++ b/homeassistant/components/water_heater/device_action.py @@ -1,5 +1,5 @@ """Provides device automations for Water Heater.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -28,7 +28,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Water Heater devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -58,7 +58,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" config = ACTION_SCHEMA(config) diff --git a/homeassistant/components/water_heater/reproduce_state.py b/homeassistant/components/water_heater/reproduce_state.py index 77cdba93f96a34..4675cdb8621def 100644 --- a/homeassistant/components/water_heater/reproduce_state.py +++ b/homeassistant/components/water_heater/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an Water heater state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -47,8 +49,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -124,8 +126,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce Water heater states.""" await asyncio.gather( diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index 43f5c807770a9c..e7b10e18889e94 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -1,5 +1,7 @@ """WebSocket based API for Home Assistant.""" -from typing import Optional, Union, cast +from __future__ import annotations + +from typing import cast import voluptuous as vol @@ -43,9 +45,9 @@ @callback def async_register_command( hass: HomeAssistant, - command_or_handler: Union[str, const.WebSocketCommandHandler], - handler: Optional[const.WebSocketCommandHandler] = None, - schema: Optional[vol.Schema] = None, + command_or_handler: str | const.WebSocketCommandHandler, + handler: const.WebSocketCommandHandler | None = None, + schema: vol.Schema | None = None, ) -> None: """Register a websocket command.""" # pylint: disable=protected-access diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index 108d4de5ada4e3..dd1bb33369385b 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -1,6 +1,8 @@ """Connection session.""" +from __future__ import annotations + import asyncio -from typing import Any, Callable, Dict, Hashable, Optional +from typing import Any, Callable, Hashable import voluptuous as vol @@ -26,7 +28,7 @@ def __init__(self, logger, hass, send_message, user, refresh_token): else: self.refresh_token_id = None - self.subscriptions: Dict[Hashable, Callable[[], Any]] = {} + self.subscriptions: dict[Hashable, Callable[[], Any]] = {} self.last_id = 0 def context(self, msg): @@ -37,7 +39,7 @@ def context(self, msg): return Context(user_id=user.id) @callback - def send_result(self, msg_id: int, result: Optional[Any] = None) -> None: + def send_result(self, msg_id: int, result: Any | None = None) -> None: """Send a result message.""" self.send_message(messages.result_message(msg_id, result)) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index daa8529e8bd35b..982bead296a8a5 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -1,8 +1,9 @@ """View to accept incoming websocket connection.""" +from __future__ import annotations + import asyncio from contextlib import suppress import logging -from typing import Optional from aiohttp import WSMsgType, web import async_timeout @@ -57,7 +58,7 @@ def __init__(self, hass, request): """Initialize an active connection.""" self.hass = hass self.request = request - self.wsock: Optional[web.WebSocketResponse] = None + self.wsock: web.WebSocketResponse | None = None self._to_write: asyncio.Queue = asyncio.Queue(maxsize=MAX_PENDING_MSG) self._handle_task = None self._writer_task = None diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index f68beff5924feb..736a7ad59f0634 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -1,8 +1,9 @@ """Message templates for websocket commands.""" +from __future__ import annotations from functools import lru_cache import logging -from typing import Any, Dict +from typing import Any import voluptuous as vol @@ -32,12 +33,12 @@ IDEN_JSON_TEMPLATE = '"__IDEN__"' -def result_message(iden: int, result: Any = None) -> Dict: +def result_message(iden: int, result: Any = None) -> dict: """Return a success result message.""" return {"id": iden, "type": const.TYPE_RESULT, "success": True, "result": result} -def error_message(iden: int, code: str, message: str) -> Dict: +def error_message(iden: int, code: str, message: str) -> dict: """Return an error result message.""" return { "id": iden, @@ -47,7 +48,7 @@ def error_message(iden: int, code: str, message: str) -> Dict: } -def event_message(iden: JSON_TYPE, event: Any) -> Dict: +def event_message(iden: JSON_TYPE, event: Any) -> dict: """Return an event message.""" return {"id": iden, "type": "event", "event": event} diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index d9b4b0548f86fd..d10707f4590478 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -1,8 +1,10 @@ """Classes shared among Wemo entities.""" +from __future__ import annotations + import asyncio import contextlib import logging -from typing import Any, Dict, Generator, Optional +from typing import Any, Generator import async_timeout from pywemo import WeMoDevice @@ -19,7 +21,7 @@ class ExceptionHandlerStatus: """Exit status from the _wemo_exception_handler context manager.""" # An exception if one was raised in the _wemo_exception_handler. - exception: Optional[Exception] = None + exception: Exception | None = None @property def success(self) -> bool: @@ -68,7 +70,7 @@ def _wemo_exception_handler( _LOGGER.info("Reconnected to %s", self.name) self._available = True - def _update(self, force_update: Optional[bool] = True): + def _update(self, force_update: bool | None = True): """Update the device state.""" raise NotImplementedError() @@ -99,7 +101,7 @@ async def async_update(self) -> None: self._available = False async def _async_locked_update( - self, force_update: bool, timeout: Optional[async_timeout.timeout] = None + self, force_update: bool, timeout: async_timeout.timeout | None = None ) -> None: """Try updating within an async lock.""" async with self._update_lock: @@ -124,7 +126,7 @@ def unique_id(self) -> str: return self.wemo.serialnumber @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return the device info.""" return { "name": self.name, diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index 35727b19927d89..49402ecb911215 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -1,6 +1,5 @@ """Support for WiLight Fan.""" - -from typing import Optional +from __future__ import annotations from pywilight.const import ( FAN_V1, @@ -79,7 +78,7 @@ def is_on(self): return self._status.get("direction", WL_DIRECTION_OFF) != WL_DIRECTION_OFF @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if "direction" in self._status: if self._status["direction"] == WL_DIRECTION_OFF: diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 5efa22e6a8617d..7ccff020c0dd43 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -3,8 +3,9 @@ For more details about this platform, please refer to the documentation at """ +from __future__ import annotations + import asyncio -from typing import Optional from aiohttp.web import Request, Response import voluptuous as vol @@ -175,7 +176,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_webhook_handler( hass: HomeAssistant, webhook_id: str, request: Request -) -> Optional[Response]: +) -> Response | None: """Handle webhooks calls.""" # Handle http head calls to the path. # When creating a notify subscription, Withings will check that the endpoint is running by sending a HEAD request. diff --git a/homeassistant/components/withings/binary_sensor.py b/homeassistant/components/withings/binary_sensor.py index 136a12a0e1d235..a7d0a80e8e32a6 100644 --- a/homeassistant/components/withings/binary_sensor.py +++ b/homeassistant/components/withings/binary_sensor.py @@ -1,5 +1,7 @@ """Sensors flow for Withings.""" -from typing import Callable, List +from __future__ import annotations + +from typing import Callable from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OCCUPANCY, @@ -16,7 +18,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" entities = await async_create_entities( diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 54a0a3c94eecff..96be4fd3063df2 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1,4 +1,6 @@ """Common code for Withings.""" +from __future__ import annotations + import asyncio from dataclasses import dataclass import datetime @@ -6,7 +8,7 @@ from enum import Enum, IntEnum import logging import re -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict from aiohttp.web import Response import requests @@ -86,7 +88,7 @@ class WithingsAttribute: measute_type: Enum friendly_name: str unit_of_measurement: str - icon: Optional[str] + icon: str | None platform: str enabled_by_default: bool update_type: UpdateType @@ -461,12 +463,12 @@ class StateData: ), ] -WITHINGS_MEASUREMENTS_MAP: Dict[Measurement, WithingsAttribute] = { +WITHINGS_MEASUREMENTS_MAP: dict[Measurement, WithingsAttribute] = { attr.measurement: attr for attr in WITHINGS_ATTRIBUTES } -WITHINGS_MEASURE_TYPE_MAP: Dict[ - Union[NotifyAppli, GetSleepSummaryField, MeasureType], WithingsAttribute +WITHINGS_MEASURE_TYPE_MAP: dict[ + NotifyAppli | GetSleepSummaryField | MeasureType, WithingsAttribute ] = {attr.measute_type: attr for attr in WITHINGS_ATTRIBUTES} @@ -486,8 +488,8 @@ def __init__( self.session = OAuth2Session(hass, config_entry, implementation) def _request( - self, path: str, params: Dict[str, Any], method: str = "GET" - ) -> Dict[str, Any]: + self, path: str, params: dict[str, Any], method: str = "GET" + ) -> dict[str, Any]: """Perform an async request.""" asyncio.run_coroutine_threadsafe( self.session.async_ensure_token_valid(), self._hass.loop @@ -524,7 +526,7 @@ def __init__(self, hass: HomeAssistant, user_id: int) -> None: """Initialize the object.""" self._hass = hass self._user_id = user_id - self._listeners: List[CALLBACK_TYPE] = [] + self._listeners: list[CALLBACK_TYPE] = [] self.data: MeasurementData = {} def async_add_listener(self, listener: CALLBACK_TYPE) -> Callable[[], None]: @@ -573,10 +575,8 @@ def __init__( self._notify_unsubscribe_delay = datetime.timedelta(seconds=1) self._is_available = True - self._cancel_interval_update_interval: Optional[CALLBACK_TYPE] = None - self._cancel_configure_webhook_subscribe_interval: Optional[ - CALLBACK_TYPE - ] = None + self._cancel_interval_update_interval: CALLBACK_TYPE | None = None + self._cancel_configure_webhook_subscribe_interval: CALLBACK_TYPE | None = None self._api_notification_id = f"withings_{self._user_id}" self.subscription_update_coordinator = DataUpdateCoordinator( @@ -600,7 +600,7 @@ def __init__( self.webhook_update_coordinator = WebhookUpdateCoordinator( self._hass, self._user_id ) - self._cancel_subscription_update: Optional[Callable[[], None]] = None + self._cancel_subscription_update: Callable[[], None] | None = None self._subscribe_webhook_run_count = 0 @property @@ -728,7 +728,7 @@ async def _async_unsubscribe_webhook(self) -> None: self._api.notify_revoke, profile.callbackurl, profile.appli ) - async def async_get_all_data(self) -> Optional[Dict[MeasureType, Any]]: + async def async_get_all_data(self) -> dict[MeasureType, Any] | None: """Update all withings data.""" try: return await self._do_retry(self._async_get_all_data) @@ -764,14 +764,14 @@ async def async_get_all_data(self) -> Optional[Dict[MeasureType, Any]]: raise exception - async def _async_get_all_data(self) -> Optional[Dict[MeasureType, Any]]: + async def _async_get_all_data(self) -> dict[MeasureType, Any] | None: _LOGGER.info("Updating all withings data") return { **await self.async_get_measures(), **await self.async_get_sleep_summary(), } - async def async_get_measures(self) -> Dict[MeasureType, Any]: + async def async_get_measures(self) -> dict[MeasureType, Any]: """Get the measures data.""" _LOGGER.debug("Updating withings measures") @@ -794,7 +794,7 @@ async def async_get_measures(self) -> Dict[MeasureType, Any]: for measure in group.measures } - async def async_get_sleep_summary(self) -> Dict[MeasureType, Any]: + async def async_get_sleep_summary(self) -> dict[MeasureType, Any]: """Get the sleep summary data.""" _LOGGER.debug("Updating withing sleep summary") now = dt.utcnow() @@ -837,7 +837,7 @@ def get_sleep_summary() -> SleepGetSummaryResponse: response = await self._hass.async_add_executor_job(get_sleep_summary) # Set the default to empty lists. - raw_values: Dict[GetSleepSummaryField, List[int]] = { + raw_values: dict[GetSleepSummaryField, list[int]] = { field: [] for field in GetSleepSummaryField } @@ -848,9 +848,9 @@ def get_sleep_summary() -> SleepGetSummaryResponse: for field in GetSleepSummaryField: raw_values[field].append(dict(data)[field.value]) - values: Dict[GetSleepSummaryField, float] = {} + values: dict[GetSleepSummaryField, float] = {} - def average(data: List[int]) -> float: + def average(data: list[int]) -> float: return sum(data) / len(data) def set_value(field: GetSleepSummaryField, func: Callable) -> None: @@ -907,7 +907,7 @@ def get_attribute_unique_id(attribute: WithingsAttribute, user_id: int) -> str: async def async_get_entity_id( hass: HomeAssistant, attribute: WithingsAttribute, user_id: int -) -> Optional[str]: +) -> str | None: """Get an entity id for a user's attribute.""" entity_registry: EntityRegistry = ( await hass.helpers.entity_registry.async_get_registry() @@ -936,7 +936,7 @@ def __init__(self, data_manager: DataManager, attribute: WithingsAttribute) -> N self._user_id = self._data_manager.user_id self._name = f"Withings {self._attribute.measurement.value} {self._profile}" self._unique_id = get_attribute_unique_id(self._attribute, self._user_id) - self._state_data: Optional[Any] = None + self._state_data: Any | None = None @property def should_poll(self) -> bool: @@ -1053,7 +1053,7 @@ async def async_get_data_manager( def get_data_manager_by_webhook_id( hass: HomeAssistant, webhook_id: str -) -> Optional[DataManager]: +) -> DataManager | None: """Get a data manager by it's webhook id.""" return next( iter( @@ -1067,7 +1067,7 @@ def get_data_manager_by_webhook_id( ) -def get_all_data_managers(hass: HomeAssistant) -> Tuple[DataManager, ...]: +def get_all_data_managers(hass: HomeAssistant) -> tuple[DataManager, ...]: """Get all configured data managers.""" return tuple( config_entry_data[const.DATA_MANAGER] @@ -1086,7 +1086,7 @@ async def async_create_entities( entry: ConfigEntry, create_func: Callable[[DataManager, WithingsAttribute], Entity], platform: str, -) -> List[Entity]: +) -> list[Entity]: """Create withings entities from config entry.""" data_manager = await async_get_data_manager(hass, entry) @@ -1096,7 +1096,7 @@ async def async_create_entities( ] -def get_platform_attributes(platform: str) -> Tuple[WithingsAttribute, ...]: +def get_platform_attributes(platform: str) -> tuple[WithingsAttribute, ...]: """Get withings attributes used for a specific platform.""" return tuple( attribute for attribute in WITHINGS_ATTRIBUTES if attribute.platform == platform diff --git a/homeassistant/components/withings/config_flow.py b/homeassistant/components/withings/config_flow.py index ddf51741c622d3..0cdc523266c3a7 100644 --- a/homeassistant/components/withings/config_flow.py +++ b/homeassistant/components/withings/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Withings.""" +from __future__ import annotations + import logging -from typing import Dict, Union import voluptuous as vol from withings_api.common import AuthScope @@ -19,7 +20,7 @@ class WithingsFlowHandler( DOMAIN = const.DOMAIN CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL # Temporarily holds authorization data during the profile step. - _current_data: Dict[str, Union[None, str, int]] = {} + _current_data: dict[str, None | str | int] = {} @property def logger(self) -> logging.Logger: diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index a55d83d717bfdd..a7a7947dec5bf0 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -1,5 +1,7 @@ """Sensors flow for Withings.""" -from typing import Callable, List, Union +from __future__ import annotations + +from typing import Callable from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry @@ -12,7 +14,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" @@ -30,6 +32,6 @@ class WithingsHealthSensor(BaseWithingsSensor): """Implementation of a Withings sensor.""" @property - def state(self) -> Union[None, str, int, float]: + def state(self) -> None | str | int | float: """Return the state of the entity.""" return self._state_data diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index 72fee5f078c960..2ec029314153bc 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -1,8 +1,10 @@ """Support for WLED.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Any, Dict +from typing import Any from wled import WLED, Device as WLEDDevice, WLEDConnectionError, WLEDError @@ -185,7 +187,7 @@ class WLEDDeviceEntity(WLEDEntity): """Defines a WLED device entity.""" @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return device information about this WLED device.""" return { ATTR_IDENTIFIERS: {(DOMAIN, self.coordinator.data.info.mac_address)}, diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index 5915447f2f54f1..75e59c880864df 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -1,5 +1,7 @@ """Config flow to configure the WLED integration.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any import voluptuous as vol from wled import WLED, WLEDConnectionError @@ -23,14 +25,14 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL async def async_step_user( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle a flow initiated by the user.""" return await self._handle_config_flow(user_input) async def async_step_zeroconf( - self, user_input: Optional[ConfigType] = None - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None + ) -> dict[str, Any]: """Handle zeroconf discovery.""" if user_input is None: return self.async_abort(reason="cannot_connect") @@ -53,13 +55,13 @@ async def async_step_zeroconf( async def async_step_zeroconf_confirm( self, user_input: ConfigType = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Handle a flow initiated by zeroconf.""" return await self._handle_config_flow(user_input) async def _handle_config_flow( - self, user_input: Optional[ConfigType] = None, prepare: bool = False - ) -> Dict[str, Any]: + self, user_input: ConfigType | None = None, prepare: bool = False + ) -> dict[str, Any]: """Config flow handler for WLED.""" source = self.context.get("source") @@ -100,7 +102,7 @@ async def _handle_config_flow( data={CONF_HOST: user_input[CONF_HOST], CONF_MAC: user_input[CONF_MAC]}, ) - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_setup_form(self, errors: dict | None = None) -> dict[str, Any]: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -108,7 +110,7 @@ def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: errors=errors or {}, ) - def _show_confirm_dialog(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + def _show_confirm_dialog(self, errors: dict | None = None) -> dict[str, Any]: """Show the confirm dialog to the user.""" name = self.context.get(CONF_NAME) return self.async_show_form( diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index d25c0fc9064089..df412ba4c368fe 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -1,6 +1,8 @@ """Support for LED lights.""" +from __future__ import annotations + from functools import partial -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable import voluptuous as vol @@ -51,7 +53,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up WLED light based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -115,7 +117,7 @@ def supported_features(self) -> int: return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" return self.coordinator.data.state.brightness @@ -188,7 +190,7 @@ def available(self) -> bool: return super().available @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" playlist = self.coordinator.data.state.playlist if playlist == -1: @@ -209,18 +211,18 @@ def extra_state_attributes(self) -> Optional[Dict[str, Any]]: } @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hue and saturation color value [float, float].""" color = self.coordinator.data.state.segments[self._segment].color_primary return color_util.color_RGB_to_hs(*color[:3]) @property - def effect(self) -> Optional[str]: + def effect(self) -> str | None: """Return the current effect of the light.""" return self.coordinator.data.state.segments[self._segment].effect.name @property - def brightness(self) -> Optional[int]: + def brightness(self) -> int | None: """Return the brightness of this light between 1..255.""" state = self.coordinator.data.state @@ -234,7 +236,7 @@ def brightness(self) -> Optional[int]: return state.segments[self._segment].brightness @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" color = self.coordinator.data.state.segments[self._segment].color_primary return color[-1] if self._rgbw else None @@ -256,7 +258,7 @@ def supported_features(self) -> int: return flags @property - def effect_list(self) -> List[str]: + def effect_list(self) -> list[str]: """Return the list of supported effects.""" return [effect.name for effect in self.coordinator.data.effects] @@ -357,11 +359,11 @@ async def async_turn_on(self, **kwargs: Any) -> None: @wled_exception_handler async def async_effect( self, - effect: Optional[Union[int, str]] = None, - intensity: Optional[int] = None, - palette: Optional[Union[int, str]] = None, - reverse: Optional[bool] = None, - speed: Optional[int] = None, + effect: int | str | None = None, + intensity: int | None = None, + palette: int | str | None = None, + reverse: bool | None = None, + speed: int | None = None, ) -> None: """Set the effect of a WLED light.""" data = {ATTR_SEGMENT_ID: self._segment} @@ -398,7 +400,7 @@ async def async_preset( def async_update_segments( entry: ConfigEntry, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDSegmentLight], + current: dict[int, WLEDSegmentLight], async_add_entities, ) -> None: """Update segments.""" @@ -438,7 +440,7 @@ def async_update_segments( async def async_remove_entity( index: int, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDSegmentLight], + current: dict[int, WLEDSegmentLight], ) -> None: """Remove WLED segment light from Home Assistant.""" entity = current[index] diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index da002e1e8f0cfa..4bfca885613cb0 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -1,6 +1,8 @@ """Support for WLED sensors.""" +from __future__ import annotations + from datetime import timedelta -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable from homeassistant.components.sensor import DEVICE_CLASS_CURRENT from homeassistant.config_entries import ConfigEntry @@ -22,7 +24,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up WLED sensor based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -52,7 +54,7 @@ def __init__( icon: str, key: str, name: str, - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, ) -> None: """Initialize WLED sensor.""" self._unit_of_measurement = unit_of_measurement @@ -92,7 +94,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_LED_COUNT: self.coordinator.data.info.leds.count, @@ -105,7 +107,7 @@ def state(self) -> int: return self.coordinator.data.info.leds.power @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_CURRENT @@ -131,7 +133,7 @@ def state(self) -> str: return uptime.replace(microsecond=0).isoformat() @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_TIMESTAMP @@ -199,7 +201,7 @@ def state(self) -> int: return self.coordinator.data.info.wifi.rssi @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this sensor.""" return DEVICE_CLASS_SIGNAL_STRENGTH diff --git a/homeassistant/components/wled/switch.py b/homeassistant/components/wled/switch.py index e58b32425cbd61..5902cd246a0181 100644 --- a/homeassistant/components/wled/switch.py +++ b/homeassistant/components/wled/switch.py @@ -1,5 +1,7 @@ """Support for WLED switches.""" -from typing import Any, Callable, Dict, List, Optional +from __future__ import annotations + +from typing import Any, Callable from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -21,7 +23,7 @@ async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up WLED switch based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -72,7 +74,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return { ATTR_DURATION: self.coordinator.data.state.nightlight.duration, @@ -110,7 +112,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} @@ -144,7 +146,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator): ) @property - def extra_state_attributes(self) -> Optional[Dict[str, Any]]: + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" return {ATTR_UDP_PORT: self.coordinator.data.info.udp_port} diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 1f4332c35e7946..9e4b764f34c765 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -1,9 +1,11 @@ """Support for WUnderground weather service.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging import re -from typing import Any, Callable, Optional, Union +from typing import Any, Callable import aiohttp import async_timeout @@ -64,10 +66,10 @@ class WUSensorConfig: def __init__( self, - friendly_name: Union[str, Callable], + friendly_name: str | Callable, feature: str, value: Callable[["WUndergroundData"], Any], - unit_of_measurement: Optional[str] = None, + unit_of_measurement: str | None = None, entity_picture=None, icon: str = "mdi:gauge", extra_state_attributes=None, @@ -99,10 +101,10 @@ class WUCurrentConditionsSensorConfig(WUSensorConfig): def __init__( self, - friendly_name: Union[str, Callable], + friendly_name: str | Callable, field: str, - icon: Optional[str] = "mdi:gauge", - unit_of_measurement: Optional[str] = None, + icon: str | None = "mdi:gauge", + unit_of_measurement: str | None = None, device_class=None, ): """Initialize current conditions sensor configuration. @@ -131,9 +133,7 @@ def __init__( class WUDailyTextForecastSensorConfig(WUSensorConfig): """Helper for defining sensor configurations for daily text forecasts.""" - def __init__( - self, period: int, field: str, unit_of_measurement: Optional[str] = None - ): + def __init__(self, period: int, field: str, unit_of_measurement: str | None = None): """Initialize daily text forecast sensor configuration. :param period: forecast period number @@ -166,8 +166,8 @@ def __init__( friendly_name: str, period: int, field: str, - wu_unit: Optional[str] = None, - ha_unit: Optional[str] = None, + wu_unit: str | None = None, + ha_unit: str | None = None, icon=None, device_class=None, ): @@ -273,7 +273,7 @@ class WUAlmanacSensorConfig(WUSensorConfig): def __init__( self, - friendly_name: Union[str, Callable], + friendly_name: str | Callable, field: str, value_type: str, wu_unit: str, @@ -303,7 +303,7 @@ def __init__( class WUAlertsSensorConfig(WUSensorConfig): """Helper for defining field configuration for alerts.""" - def __init__(self, friendly_name: Union[str, Callable]): + def __init__(self, friendly_name: str | Callable): """Initialiize alerts sensor configuration. :param friendly_name: Friendly name diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 1d921f5fd18218..dd35828680205f 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -1,9 +1,10 @@ """The xbox integration.""" +from __future__ import annotations + import asyncio from dataclasses import dataclass from datetime import timedelta import logging -from typing import Dict, Optional import voluptuous as vol from xbox.webapi.api.client import XboxLiveClient @@ -133,7 +134,7 @@ class ConsoleData: """Xbox console status data.""" status: SmartglassConsoleStatus - app_details: Optional[Product] + app_details: Product | None @dataclass @@ -149,7 +150,7 @@ class PresenceData: in_game: bool in_multiplayer: bool gamer_score: str - gold_tenure: Optional[str] + gold_tenure: str | None account_tier: str @@ -157,8 +158,8 @@ class PresenceData: class XboxData: """Xbox dataclass for update coordinator.""" - consoles: Dict[str, ConsoleData] - presence: Dict[str, PresenceData] + consoles: dict[str, ConsoleData] + presence: dict[str, PresenceData] class XboxUpdateCoordinator(DataUpdateCoordinator): @@ -184,9 +185,9 @@ def __init__( async def _async_update_data(self) -> XboxData: """Fetch the latest console status.""" # Update Console Status - new_console_data: Dict[str, ConsoleData] = {} + new_console_data: dict[str, ConsoleData] = {} for console in self.consoles.result: - current_state: Optional[ConsoleData] = self.data.consoles.get(console.id) + current_state: ConsoleData | None = self.data.consoles.get(console.id) status: SmartglassConsoleStatus = ( await self.client.smartglass.get_console_status(console.id) ) @@ -198,7 +199,7 @@ async def _async_update_data(self) -> XboxData: ) # Setup focus app - app_details: Optional[Product] = None + app_details: Product | None = None if current_state is not None: app_details = current_state.app_details @@ -246,7 +247,7 @@ async def _async_update_data(self) -> XboxData: def _build_presence_data(person: Person) -> PresenceData: """Build presence data from a person.""" - active_app: Optional[PresenceDetail] = None + active_app: PresenceDetail | None = None try: active_app = next( presence for presence in person.presence_details if presence.is_primary diff --git a/homeassistant/components/xbox/base_sensor.py b/homeassistant/components/xbox/base_sensor.py index d19fbfb918d6ea..c149ce74c3238e 100644 --- a/homeassistant/components/xbox/base_sensor.py +++ b/homeassistant/components/xbox/base_sensor.py @@ -1,5 +1,5 @@ """Base Sensor for the Xbox Integration.""" -from typing import Optional +from __future__ import annotations from yarl import URL @@ -24,7 +24,7 @@ def unique_id(self) -> str: return f"{self.xuid}_{self.attribute}" @property - def data(self) -> Optional[PresenceData]: + def data(self) -> PresenceData | None: """Return coordinator data for this console.""" return self.coordinator.data.presence.get(self.xuid) diff --git a/homeassistant/components/xbox/binary_sensor.py b/homeassistant/components/xbox/binary_sensor.py index 109b2839b682c7..98e06257146314 100644 --- a/homeassistant/components/xbox/binary_sensor.py +++ b/homeassistant/components/xbox/binary_sensor.py @@ -1,6 +1,7 @@ """Xbox friends binary sensors.""" +from __future__ import annotations + from functools import partial -from typing import Dict, List from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback @@ -44,7 +45,7 @@ def is_on(self) -> bool: @callback def async_update_friends( coordinator: XboxUpdateCoordinator, - current: Dict[str, List[XboxBinarySensorEntity]], + current: dict[str, list[XboxBinarySensorEntity]], async_add_entities, ) -> None: """Update friends.""" @@ -73,7 +74,7 @@ def async_update_friends( async def async_remove_entities( xuid: str, coordinator: XboxUpdateCoordinator, - current: Dict[str, XboxBinarySensorEntity], + current: dict[str, XboxBinarySensorEntity], ) -> None: """Remove friend sensors from Home Assistant.""" registry = await async_get_entity_registry(coordinator.hass) diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index 2aebb07df1bddb..0c3eec95c6fad1 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -1,5 +1,5 @@ """Support for media browsing.""" -from typing import Dict, List, Optional +from __future__ import annotations from xbox.webapi.api.client import XboxLiveClient from xbox.webapi.api.provider.catalog.const import HOME_APP_IDS, SYSTEM_PFN_ID_MAP @@ -41,7 +41,7 @@ async def build_item_response( tv_configured: bool, media_content_type: str, media_content_id: str, -) -> Optional[BrowseMedia]: +) -> BrowseMedia | None: """Create response payload for the provided media query.""" apps: InstalledPackagesList = await client.smartglass.get_installed_apps(device_id) @@ -149,7 +149,7 @@ async def build_item_response( ) -def item_payload(item: InstalledPackage, images: Dict[str, List[Image]]): +def item_payload(item: InstalledPackage, images: dict[str, list[Image]]): """Create response payload for a single media item.""" thumbnail = None image = _find_media_image(images.get(item.one_store_product_id, [])) @@ -169,7 +169,7 @@ def item_payload(item: InstalledPackage, images: Dict[str, List[Image]]): ) -def _find_media_image(images: List[Image]) -> Optional[Image]: +def _find_media_image(images: list[Image]) -> Image | None: purpose_order = ["Poster", "Tile", "Logo", "BoxArt"] for purpose in purpose_order: for image in images: diff --git a/homeassistant/components/xbox/media_player.py b/homeassistant/components/xbox/media_player.py index 19e8a90d48e2d0..e57f3971042ce9 100644 --- a/homeassistant/components/xbox/media_player.py +++ b/homeassistant/components/xbox/media_player.py @@ -1,6 +1,8 @@ """Xbox Media Player Support.""" +from __future__ import annotations + import re -from typing import List, Optional +from typing import List from xbox.webapi.api.client import XboxLiveClient from xbox.webapi.api.provider.catalog.models import Image @@ -231,7 +233,7 @@ def device_info(self): } -def _find_media_image(images=List[Image]) -> Optional[Image]: +def _find_media_image(images=List[Image]) -> Image | None: purpose_order = ["FeaturePromotionalSquareArt", "Tile", "Logo", "BoxArt"] for purpose in purpose_order: for image in images: diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 53346f3750d559..1a459580d661e9 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -1,6 +1,7 @@ """Xbox Media Source Implementation.""" +from __future__ import annotations + from dataclasses import dataclass -from typing import List, Tuple from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module from xbox.webapi.api.client import XboxLiveClient @@ -50,7 +51,7 @@ async def async_get_media_source(hass: HomeAssistantType): @callback def async_parse_identifier( item: MediaSourceItem, -) -> Tuple[str, str, str]: +) -> tuple[str, str, str]: """Parse identifier.""" identifier = item.identifier or "" start = ["", "", ""] @@ -87,7 +88,7 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: return PlayMedia(url, MIME_TYPE_MAP[kind]) async def async_browse_media( - self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES + self, item: MediaSourceItem, media_types: tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" title, category, _ = async_parse_identifier(item) @@ -136,7 +137,7 @@ async def _build_media_items(self, title, category): title_id, _, thumbnail = title.split("#", 2) owner, kind = category.split("#", 1) - items: List[XboxMediaItem] = [] + items: list[XboxMediaItem] = [] try: if kind == "gameclips": if owner == "my": @@ -206,7 +207,7 @@ async def _build_media_items(self, title, category): ) -def _build_game_item(item: InstalledPackage, images: List[Image]): +def _build_game_item(item: InstalledPackage, images: list[Image]): """Build individual game.""" thumbnail = "" image = _find_media_image(images.get(item.one_store_product_id, [])) diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index e0ab661b2d982b..88bf112b7282bb 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -1,6 +1,7 @@ """Xbox friends binary sensors.""" +from __future__ import annotations + from functools import partial -from typing import Dict, List from homeassistant.core import callback from homeassistant.helpers.entity_registry import ( @@ -43,7 +44,7 @@ def state(self): @callback def async_update_friends( coordinator: XboxUpdateCoordinator, - current: Dict[str, List[XboxSensorEntity]], + current: dict[str, list[XboxSensorEntity]], async_add_entities, ) -> None: """Update friends.""" @@ -72,7 +73,7 @@ def async_update_friends( async def async_remove_entities( xuid: str, coordinator: XboxUpdateCoordinator, - current: Dict[str, XboxSensorEntity], + current: dict[str, XboxSensorEntity], ) -> None: """Remove friend sensors from Home Assistant.""" registry = await async_get_entity_registry(coordinator.hass) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index a86af10fe1c51f..c1e0c555e02269 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -1,8 +1,9 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" +from __future__ import annotations + import asyncio from datetime import timedelta import logging -from typing import Optional import voluptuous as vol from yeelight import Bulb, BulbException, discover_bulbs @@ -181,7 +182,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Yeelight from a config entry.""" - async def _initialize(host: str, capabilities: Optional[dict] = None) -> None: + async def _initialize(host: str, capabilities: dict | None = None) -> None: remove_dispatcher = async_dispatcher_connect( hass, DEVICE_INITIALIZED.format(host), @@ -593,7 +594,7 @@ async def _async_get_device( hass: HomeAssistant, host: str, entry: ConfigEntry, - capabilities: Optional[dict], + capabilities: dict | None, ) -> YeelightDevice: # Get model from config and capabilities model = entry.options.get(CONF_MODEL) diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index bf9f917f230d5b..16fe5607925301 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -1,7 +1,9 @@ """Zerproc light platform.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import Callable, List, Optional +from typing import Callable import pyzerproc @@ -29,7 +31,7 @@ DISCOVERY_INTERVAL = timedelta(seconds=60) -async def discover_entities(hass: HomeAssistant) -> List[Entity]: +async def discover_entities(hass: HomeAssistant) -> list[Entity]: """Attempt to discover new lights.""" lights = await pyzerproc.discover() @@ -49,7 +51,7 @@ async def discover_entities(hass: HomeAssistant) -> List[Entity]: async def async_setup_entry( hass: HomeAssistantType, config_entry: ConfigEntry, - async_add_entities: Callable[[List[Entity], bool], None], + async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Zerproc light devices.""" warned = False @@ -122,7 +124,7 @@ def device_info(self): } @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon to use in the frontend.""" return "mdi:string-lights" diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index f947012d3afaad..e2c4faa8539e20 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -4,11 +4,12 @@ For more details on this platform, please refer to the documentation at https://home-assistant.io/components/zha.climate/ """ +from __future__ import annotations + from datetime import datetime, timedelta import enum import functools from random import randint -from typing import List, Optional, Tuple from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -212,7 +213,7 @@ def extra_state_attributes(self): return data @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return current FAN mode.""" if self._thrm.running_state is None: return FAN_AUTO @@ -224,14 +225,14 @@ def fan_mode(self) -> Optional[str]: return FAN_AUTO @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return supported FAN modes.""" if not self._fan: return None return [FAN_AUTO, FAN_ON] @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current HVAC action.""" if ( self._thrm.pi_heating_demand is None @@ -241,7 +242,7 @@ def hvac_action(self) -> Optional[str]: return self._pi_demand_action @property - def _rm_rs_action(self) -> Optional[str]: + def _rm_rs_action(self) -> str | None: """Return the current HVAC action based on running mode and running state.""" running_mode = self._thrm.running_mode @@ -260,7 +261,7 @@ def _rm_rs_action(self) -> Optional[str]: return CURRENT_HVAC_OFF @property - def _pi_demand_action(self) -> Optional[str]: + def _pi_demand_action(self) -> str | None: """Return the current HVAC action based on pi_demands.""" heating_demand = self._thrm.pi_heating_demand @@ -275,12 +276,12 @@ def _pi_demand_action(self) -> Optional[str]: return CURRENT_HVAC_OFF @property - def hvac_mode(self) -> Optional[str]: + def hvac_mode(self) -> str | None: """Return HVAC operation mode.""" return SYSTEM_MODE_2_HVAC.get(self._thrm.system_mode) @property - def hvac_modes(self) -> Tuple[str, ...]: + def hvac_modes(self) -> tuple[str, ...]: """Return the list of available HVAC operation modes.""" return SEQ_OF_OPERATION.get(self._thrm.ctrl_seqe_of_oper, (HVAC_MODE_OFF,)) @@ -290,12 +291,12 @@ def precision(self): return PRECISION_TENTHS @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return current preset mode.""" return self._preset @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return supported preset modes.""" return self._presets @@ -566,7 +567,7 @@ class ZenWithinThermostat(Thermostat): """Zen Within Thermostat implementation.""" @property - def _rm_rs_action(self) -> Optional[str]: + def _rm_rs_action(self) -> str | None: """Return the current HVAC action based on running mode and running state.""" running_state = self._thrm.running_state diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index f59f53c79950f8..9835a9a393738c 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -1,6 +1,8 @@ """Config flow for ZHA.""" +from __future__ import annotations + import os -from typing import Any, Dict, Optional +from typing import Any import serial.tools.list_ports import voluptuous as vol @@ -127,7 +129,7 @@ async def async_step_port_config(self, user_input=None): ) -async def detect_radios(dev_path: str) -> Optional[Dict[str, Any]]: +async def detect_radios(dev_path: str) -> dict[str, Any] | None: """Probe all radio types on the device port.""" for radio in RadioType: dev_config = radio.controller.SCHEMA_DEVICE({CONF_DEVICE_PATH: dev_path}) diff --git a/homeassistant/components/zha/core/channels/__init__.py b/homeassistant/components/zha/core/channels/__init__.py index d3de5f7004df64..289f1c36d4df18 100644 --- a/homeassistant/components/zha/core/channels/__init__.py +++ b/homeassistant/components/zha/core/channels/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict import zigpy.zcl.clusters.closures @@ -40,7 +40,7 @@ class Channels: def __init__(self, zha_device: zha_typing.ZhaDeviceType) -> None: """Initialize instance.""" - self._pools: List[zha_typing.ChannelPoolType] = [] + self._pools: list[zha_typing.ChannelPoolType] = [] self._power_config = None self._identify = None self._semaphore = asyncio.Semaphore(3) @@ -49,7 +49,7 @@ def __init__(self, zha_device: zha_typing.ZhaDeviceType) -> None: self._zha_device = zha_device @property - def pools(self) -> List[ChannelPool]: + def pools(self) -> list[ChannelPool]: """Return channel pools list.""" return self._pools @@ -96,7 +96,7 @@ def unique_id(self): return self._unique_id @property - def zigbee_signature(self) -> Dict[int, Dict[str, Any]]: + def zigbee_signature(self) -> dict[int, dict[str, Any]]: """Get the zigbee signatures for the pools in channels.""" return { signature[0]: signature[1] @@ -137,7 +137,7 @@ def async_new_entity( component: str, entity_class: zha_typing.CALLABLE_T, unique_id: str, - channels: List[zha_typing.ChannelType], + channels: list[zha_typing.ChannelType], ): """Signal new entity addition.""" if self.zha_device.status == zha_core_device.DeviceStatus.INITIALIZED: @@ -153,7 +153,7 @@ def async_send_signal(self, signal: str, *args: Any) -> None: async_dispatcher_send(self.zha_device.hass, signal, *args) @callback - def zha_send_event(self, event_data: Dict[str, Union[str, int]]) -> None: + def zha_send_event(self, event_data: dict[str, str | int]) -> None: """Relay events to hass.""" self.zha_device.hass.bus.async_fire( "zha_event", @@ -175,7 +175,7 @@ def __init__(self, channels: Channels, ep_id: int): self._channels: Channels = channels self._claimed_channels: ChannelsDict = {} self._id: int = ep_id - self._client_channels: Dict[str, zha_typing.ClientChannelType] = {} + self._client_channels: dict[str, zha_typing.ClientChannelType] = {} self._unique_id: str = f"{channels.unique_id}-{ep_id}" @property @@ -189,7 +189,7 @@ def claimed_channels(self) -> ChannelsDict: return self._claimed_channels @property - def client_channels(self) -> Dict[str, zha_typing.ClientChannelType]: + def client_channels(self) -> dict[str, zha_typing.ClientChannelType]: """Return a dict of client channels.""" return self._client_channels @@ -214,12 +214,12 @@ def is_mains_powered(self) -> bool: return self._channels.zha_device.is_mains_powered @property - def manufacturer(self) -> Optional[str]: + def manufacturer(self) -> str | None: """Return device manufacturer.""" return self._channels.zha_device.manufacturer @property - def manufacturer_code(self) -> Optional[int]: + def manufacturer_code(self) -> int | None: """Return device manufacturer.""" return self._channels.zha_device.manufacturer_code @@ -229,7 +229,7 @@ def hass(self): return self._channels.zha_device.hass @property - def model(self) -> Optional[str]: + def model(self) -> str | None: """Return device model.""" return self._channels.zha_device.model @@ -244,7 +244,7 @@ def unique_id(self): return self._unique_id @property - def zigbee_signature(self) -> Tuple[int, Dict[str, Any]]: + def zigbee_signature(self) -> tuple[int, dict[str, Any]]: """Get the zigbee signature for the endpoint this pool represents.""" return ( self.endpoint.endpoint_id, @@ -342,7 +342,7 @@ def async_new_entity( component: str, entity_class: zha_typing.CALLABLE_T, unique_id: str, - channels: List[zha_typing.ChannelType], + channels: list[zha_typing.ChannelType], ): """Signal new entity addition.""" self._channels.async_new_entity(component, entity_class, unique_id, channels) @@ -353,19 +353,19 @@ def async_send_signal(self, signal: str, *args: Any) -> None: self._channels.async_send_signal(signal, *args) @callback - def claim_channels(self, channels: List[zha_typing.ChannelType]) -> None: + def claim_channels(self, channels: list[zha_typing.ChannelType]) -> None: """Claim a channel.""" self.claimed_channels.update({ch.id: ch for ch in channels}) @callback - def unclaimed_channels(self) -> List[zha_typing.ChannelType]: + def unclaimed_channels(self) -> list[zha_typing.ChannelType]: """Return a list of available (unclaimed) channels.""" claimed = set(self.claimed_channels) available = set(self.all_channels) return [self.all_channels[chan_id] for chan_id in (available - claimed)] @callback - def zha_send_event(self, event_data: Dict[str, Union[str, int]]) -> None: + def zha_send_event(self, event_data: dict[str, str | int]) -> None: """Relay events to hass.""" self._channels.zha_send_event( { diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index 14774c09550e33..bc93459dbad419 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -1,10 +1,11 @@ """Base classes for channels.""" +from __future__ import annotations import asyncio from enum import Enum from functools import wraps import logging -from typing import Any, Union +from typing import Any import zigpy.exceptions @@ -238,7 +239,7 @@ def zdo_command(self, *args, **kwargs): """Handle ZDO commands on this cluster.""" @callback - def zha_send_event(self, command: str, args: Union[int, dict]) -> None: + def zha_send_event(self, command: str, args: int | dict) -> None: """Relay events to hass.""" self._ch_pool.zha_send_event( { diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index 1c1ab28ba5d5b6..626596e1a3e6b7 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -1,6 +1,8 @@ """General channels module for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio -from typing import Any, Coroutine, List, Optional +from typing import Any, Coroutine import zigpy.exceptions import zigpy.zcl.clusters.general as general @@ -44,42 +46,42 @@ class AnalogOutput(ZigbeeChannel): REPORT_CONFIG = [{"attr": "present_value", "config": REPORT_CONFIG_DEFAULT}] @property - def present_value(self) -> Optional[float]: + def present_value(self) -> float | None: """Return cached value of present_value.""" return self.cluster.get("present_value") @property - def min_present_value(self) -> Optional[float]: + def min_present_value(self) -> float | None: """Return cached value of min_present_value.""" return self.cluster.get("min_present_value") @property - def max_present_value(self) -> Optional[float]: + def max_present_value(self) -> float | None: """Return cached value of max_present_value.""" return self.cluster.get("max_present_value") @property - def resolution(self) -> Optional[float]: + def resolution(self) -> float | None: """Return cached value of resolution.""" return self.cluster.get("resolution") @property - def relinquish_default(self) -> Optional[float]: + def relinquish_default(self) -> float | None: """Return cached value of relinquish_default.""" return self.cluster.get("relinquish_default") @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Return cached value of description.""" return self.cluster.get("description") @property - def engineering_units(self) -> Optional[int]: + def engineering_units(self) -> int | None: """Return cached value of engineering_units.""" return self.cluster.get("engineering_units") @property - def application_type(self) -> Optional[int]: + def application_type(self) -> int | None: """Return cached value of application_type.""" return self.cluster.get("application_type") @@ -215,7 +217,7 @@ class LevelControlChannel(ZigbeeChannel): REPORT_CONFIG = ({"attr": "current_level", "config": REPORT_CONFIG_ASAP},) @property - def current_level(self) -> Optional[int]: + def current_level(self) -> int | None: """Return cached value of the current_level attribute.""" return self.cluster.get("current_level") @@ -293,7 +295,7 @@ def __init__( self._off_listener = None @property - def on_off(self) -> Optional[bool]: + def on_off(self) -> bool | None: """Return cached value of on/off attribute.""" return self.cluster.get("on_off") @@ -367,7 +369,7 @@ class Ota(ZigbeeChannel): @callback def cluster_command( - self, tsn: int, command_id: int, args: Optional[List[Any]] + self, tsn: int, command_id: int, args: list[Any] | None ) -> None: """Handle OTA commands.""" cmd_name = self.cluster.server_commands.get(command_id, [command_id])[0] @@ -402,7 +404,7 @@ async def async_configure_channel_specific(self) -> None: @callback def cluster_command( - self, tsn: int, command_id: int, args: Optional[List[Any]] + self, tsn: int, command_id: int, args: list[Any] | None ) -> None: """Handle commands received to this cluster.""" cmd_name = self.cluster.client_commands.get(command_id, [command_id])[0] diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index 5b3a4778fcd842..989cc17f97de0b 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -1,5 +1,7 @@ """Home automation channels module for Zigbee Home Automation.""" -from typing import Coroutine, Optional +from __future__ import annotations + +from typing import Coroutine import zigpy.zcl.clusters.homeautomation as homeautomation @@ -76,14 +78,14 @@ def async_initialize_channel_specific(self, from_cache: bool) -> Coroutine: ) @property - def divisor(self) -> Optional[int]: + def divisor(self) -> int | None: """Return active power divisor.""" return self.cluster.get( "ac_power_divisor", self.cluster.get("power_divisor", 1) ) @property - def multiplier(self) -> Optional[int]: + def multiplier(self) -> int | None: """Return active power divisor.""" return self.cluster.get( "ac_power_multiplier", self.cluster.get("power_multiplier", 1) diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index e02f6c01836b3e..76f9c0b4e807b1 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -4,9 +4,11 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ +from __future__ import annotations + import asyncio from collections import namedtuple -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any from zigpy.exceptions import ZigbeeException import zigpy.zcl.clusters.hvac as hvac @@ -44,7 +46,7 @@ class FanChannel(ZigbeeChannel): REPORT_CONFIG = ({"attr": "fan_mode", "config": REPORT_CONFIG_OP},) @property - def fan_mode(self) -> Optional[int]: + def fan_mode(self) -> int | None: """Return current fan mode.""" return self.cluster.get("fan_mode") @@ -198,22 +200,22 @@ def min_heat_setpoint_limit(self) -> int: return self._min_heat_setpoint_limit @property - def local_temp(self) -> Optional[int]: + def local_temp(self) -> int | None: """Thermostat temperature.""" return self._local_temp @property - def occupancy(self) -> Optional[int]: + def occupancy(self) -> int | None: """Is occupancy detected.""" return self._occupancy @property - def occupied_cooling_setpoint(self) -> Optional[int]: + def occupied_cooling_setpoint(self) -> int | None: """Temperature when room is occupied.""" return self._occupied_cooling_setpoint @property - def occupied_heating_setpoint(self) -> Optional[int]: + def occupied_heating_setpoint(self) -> int | None: """Temperature when room is occupied.""" return self._occupied_heating_setpoint @@ -228,27 +230,27 @@ def pi_heating_demand(self) -> int: return self._pi_heating_demand @property - def running_mode(self) -> Optional[int]: + def running_mode(self) -> int | None: """Thermostat running mode.""" return self._running_mode @property - def running_state(self) -> Optional[int]: + def running_state(self) -> int | None: """Thermostat running state, state of heat, cool, fan relays.""" return self._running_state @property - def system_mode(self) -> Optional[int]: + def system_mode(self) -> int | None: """System mode.""" return self._system_mode @property - def unoccupied_cooling_setpoint(self) -> Optional[int]: + def unoccupied_cooling_setpoint(self) -> int | None: """Temperature when room is not occupied.""" return self._unoccupied_cooling_setpoint @property - def unoccupied_heating_setpoint(self) -> Optional[int]: + def unoccupied_heating_setpoint(self) -> int | None: """Temperature when room is not occupied.""" return self._unoccupied_heating_setpoint @@ -309,7 +311,7 @@ async def configure_reporting(self): chunk, rest = rest[:4], rest[4:] def _configure_reporting_status( - self, attrs: Dict[Union[int, str], Tuple], res: Union[List, Tuple] + self, attrs: dict[int | str, tuple], res: list | tuple ) -> None: """Parse configure reporting result.""" if not isinstance(res, list): @@ -405,7 +407,7 @@ async def async_set_cooling_setpoint( self.debug("set cooling setpoint to %s", temperature) return True - async def get_occupancy(self) -> Optional[bool]: + async def get_occupancy(self) -> bool | None: """Get unreportable occupancy attribute.""" try: res, fail = await self.cluster.read_attributes(["occupancy"]) diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py index c8827e20e01a90..000549b059363b 100644 --- a/homeassistant/components/zha/core/channels/lighting.py +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -1,5 +1,7 @@ """Lighting channels module for Zigbee Home Automation.""" -from typing import Coroutine, Optional +from __future__ import annotations + +from typing import Coroutine import zigpy.zcl.clusters.lighting as lighting @@ -46,22 +48,22 @@ def color_capabilities(self) -> int: return self.CAPABILITIES_COLOR_XY @property - def color_loop_active(self) -> Optional[int]: + def color_loop_active(self) -> int | None: """Return cached value of the color_loop_active attribute.""" return self.cluster.get("color_loop_active") @property - def color_temperature(self) -> Optional[int]: + def color_temperature(self) -> int | None: """Return cached value of color temperature.""" return self.cluster.get("color_temperature") @property - def current_x(self) -> Optional[int]: + def current_x(self) -> int | None: """Return cached value of the current_x attribute.""" return self.cluster.get("current_x") @property - def current_y(self) -> Optional[int]: + def current_y(self) -> int | None: """Return cached value of the current_y attribute.""" return self.cluster.get("current_y") diff --git a/homeassistant/components/zha/core/channels/smartenergy.py b/homeassistant/components/zha/core/channels/smartenergy.py index 32e4902799e338..a815c75c8b36ed 100644 --- a/homeassistant/components/zha/core/channels/smartenergy.py +++ b/homeassistant/components/zha/core/channels/smartenergy.py @@ -1,5 +1,7 @@ """Smart energy channels module for Zigbee Home Automation.""" -from typing import Coroutine, Union +from __future__ import annotations + +from typing import Coroutine import zigpy.zcl.clusters.smartenergy as smartenergy @@ -139,7 +141,7 @@ async def fetch_config(self, from_cache: bool) -> None: else: self._format_spec = "{:0" + str(width) + "." + str(r_digits) + "f}" - def formatter_function(self, value: int) -> Union[int, float]: + def formatter_function(self, value: int) -> int | float: """Return formatted value for display.""" value = value * self.multiplier / self.divisor if self.unit_of_measurement == POWER_WATT: diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 672fafdd98f08b..45453ba75454de 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -1,7 +1,8 @@ """All constants related to the ZHA component.""" +from __future__ import annotations + import enum import logging -from typing import List import bellows.zigbee.application from zigpy.config import CONF_DEVICE_PATH # noqa: F401 # pylint: disable=unused-import @@ -203,7 +204,7 @@ class RadioType(enum.Enum): ) @classmethod - def list(cls) -> List[str]: + def list(cls) -> list[str]: """Return a list of descriptions.""" return [e.description for e in RadioType] diff --git a/homeassistant/components/zha/core/decorators.py b/homeassistant/components/zha/core/decorators.py index c416548dbe97e4..c3eec07e980cec 100644 --- a/homeassistant/components/zha/core/decorators.py +++ b/homeassistant/components/zha/core/decorators.py @@ -1,5 +1,7 @@ """Decorators for ZHA core registries.""" -from typing import Callable, TypeVar, Union +from __future__ import annotations + +from typing import Callable, TypeVar CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name @@ -8,7 +10,7 @@ class DictRegistry(dict): """Dict Registry of items.""" def register( - self, name: Union[int, str], item: Union[str, CALLABLE_T] = None + self, name: int | str, item: str | CALLABLE_T = None ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Return decorator to register item with a specific name.""" @@ -26,7 +28,7 @@ def decorator(channel: CALLABLE_T) -> CALLABLE_T: class SetRegistry(set): """Set Registry of items.""" - def register(self, name: Union[int, str]) -> Callable[[CALLABLE_T], CALLABLE_T]: + def register(self, name: int | str) -> Callable[[CALLABLE_T], CALLABLE_T]: """Return decorator to register item with a specific name.""" def decorator(channel: CALLABLE_T) -> CALLABLE_T: diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 4f93a7a95d68f0..65605b2f7a3ff6 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -1,11 +1,13 @@ """Device for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio from datetime import timedelta from enum import Enum import logging import random import time -from typing import Any, Dict +from typing import Any from zigpy import types import zigpy.exceptions @@ -275,7 +277,7 @@ def available(self, new_availability: bool) -> None: self._available = new_availability @property - def zigbee_signature(self) -> Dict[str, Any]: + def zigbee_signature(self) -> dict[str, Any]: """Get zigbee signature for this device.""" return { ATTR_NODE_DESCRIPTOR: str(self._zigpy_device.node_desc), diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index ba970570ecbd1c..338796acffe0a0 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -1,8 +1,9 @@ """Device discovery functions for Zigbee Home Automation.""" +from __future__ import annotations from collections import Counter import logging -from typing import Callable, List, Tuple +from typing import Callable from homeassistant import const as ha_const from homeassistant.core import callback @@ -34,10 +35,10 @@ @callback async def async_add_entities( _async_add_entities: Callable, - entities: List[ - Tuple[ + entities: list[ + tuple[ zha_typing.ZhaEntityType, - Tuple[str, zha_typing.ZhaDeviceType, List[zha_typing.ChannelType]], + tuple[str, zha_typing.ZhaDeviceType, list[zha_typing.ChannelType]], ] ], update_before_add: bool = True, @@ -235,9 +236,9 @@ def discover_group_entities(self, group: zha_typing.ZhaGroupType) -> None: @staticmethod def determine_entity_domains( hass: HomeAssistantType, group: zha_typing.ZhaGroupType - ) -> List[str]: + ) -> list[str]: """Determine the entity domains for this group.""" - entity_domains: List[str] = [] + entity_domains: list[str] = [] zha_gateway = hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY] all_domain_occurrences = [] for member in group.members: diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 3a65787cd48243..d8baf89efcfec2 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -1,4 +1,5 @@ """Virtual gateway for Zigbee Home Automation.""" +from __future__ import annotations import asyncio import collections @@ -9,7 +10,6 @@ import os import time import traceback -from typing import List, Optional from serial import SerialException from zigpy.config import CONF_DEVICE @@ -378,12 +378,12 @@ def get_device(self, ieee): """Return ZHADevice for given ieee.""" return self._devices.get(ieee) - def get_group(self, group_id: str) -> Optional[ZhaGroupType]: + def get_group(self, group_id: str) -> ZhaGroupType | None: """Return Group for given group id.""" return self.groups.get(group_id) @callback - def async_get_group_by_name(self, group_name: str) -> Optional[ZhaGroupType]: + def async_get_group_by_name(self, group_name: str) -> ZhaGroupType | None: """Get ZHA group by name.""" for group in self.groups.values(): if group.name == group_name: @@ -620,7 +620,7 @@ async def _async_device_rejoined(self, zha_device): zha_device.update_available(True) async def async_create_zigpy_group( - self, name: str, members: List[GroupMember] + self, name: str, members: list[GroupMember] ) -> ZhaGroupType: """Create a new Zigpy Zigbee group.""" # we start with two to fill any gaps from a user removing existing groups diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index 59277a394b3ba9..beaebbe8767031 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -1,8 +1,10 @@ """Group for Zigbee Home Automation.""" +from __future__ import annotations + import asyncio import collections import logging -from typing import Any, Dict, List +from typing import Any import zigpy.exceptions @@ -58,16 +60,16 @@ def device(self) -> ZhaDeviceType: return self._zha_device @property - def member_info(self) -> Dict[str, Any]: + def member_info(self) -> dict[str, Any]: """Get ZHA group info.""" - member_info: Dict[str, Any] = {} + member_info: dict[str, Any] = {} member_info["endpoint_id"] = self.endpoint_id member_info["device"] = self.device.zha_device_info member_info["entities"] = self.associated_entities return member_info @property - def associated_entities(self) -> List[GroupEntityReference]: + def associated_entities(self) -> list[GroupEntityReference]: """Return the list of entities that were derived from this endpoint.""" ha_entity_registry = self.device.gateway.ha_entity_registry zha_device_registry = self.device.gateway.device_registry @@ -136,7 +138,7 @@ def endpoint(self) -> ZigpyEndpointType: return self._zigpy_group.endpoint @property - def members(self) -> List[ZHAGroupMember]: + def members(self) -> list[ZHAGroupMember]: """Return the ZHA devices that are members of this group.""" return [ ZHAGroupMember( @@ -146,7 +148,7 @@ def members(self) -> List[ZHAGroupMember]: if member_ieee in self._zha_gateway.devices ] - async def async_add_members(self, members: List[GroupMember]) -> None: + async def async_add_members(self, members: list[GroupMember]) -> None: """Add members to this group.""" if len(members) > 1: tasks = [] @@ -162,7 +164,7 @@ async def async_add_members(self, members: List[GroupMember]) -> None: members[0].ieee ].async_add_endpoint_to_group(members[0].endpoint_id, self.group_id) - async def async_remove_members(self, members: List[GroupMember]) -> None: + async def async_remove_members(self, members: list[GroupMember]) -> None: """Remove members from this group.""" if len(members) > 1: tasks = [] @@ -181,18 +183,18 @@ async def async_remove_members(self, members: List[GroupMember]) -> None: ].async_remove_endpoint_from_group(members[0].endpoint_id, self.group_id) @property - def member_entity_ids(self) -> List[str]: + def member_entity_ids(self) -> list[str]: """Return the ZHA entity ids for all entities for the members of this group.""" - all_entity_ids: List[str] = [] + all_entity_ids: list[str] = [] for member in self.members: entity_references = member.associated_entities for entity_reference in entity_references: all_entity_ids.append(entity_reference["entity_id"]) return all_entity_ids - def get_domain_entity_ids(self, domain) -> List[str]: + def get_domain_entity_ids(self, domain) -> list[str]: """Return entity ids from the entity domain for this group.""" - domain_entity_ids: List[str] = [] + domain_entity_ids: list[str] = [] for member in self.members: if member.device.is_coordinator: continue @@ -207,9 +209,9 @@ def get_domain_entity_ids(self, domain) -> List[str]: return domain_entity_ids @property - def group_info(self) -> Dict[str, Any]: + def group_info(self) -> dict[str, Any]: """Get ZHA group info.""" - group_info: Dict[str, Any] = {} + group_info: dict[str, Any] = {} group_info["group_id"] = self.group_id group_info["name"] = self.name group_info["members"] = [member.member_info for member in self.members] diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 47911fc1078498..cf3d040f020538 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -4,6 +4,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ +from __future__ import annotations import asyncio import binascii @@ -13,7 +14,7 @@ import logging from random import uniform import re -from typing import Any, Callable, Iterator, List, Optional, Tuple +from typing import Any, Callable, Iterator import voluptuous as vol import zigpy.exceptions @@ -67,7 +68,7 @@ async def safe_read( async def get_matched_clusters( source_zha_device: ZhaDeviceType, target_zha_device: ZhaDeviceType -) -> List[BindingPair]: +) -> list[BindingPair]: """Get matched input/output cluster pairs for 2 devices.""" source_clusters = source_zha_device.async_get_std_clusters() target_clusters = target_zha_device.async_get_std_clusters() @@ -131,7 +132,7 @@ async def async_get_zha_device(hass, device_id): return zha_gateway.devices[ieee] -def find_state_attributes(states: List[State], key: str) -> Iterator[Any]: +def find_state_attributes(states: list[State], key: str) -> Iterator[Any]: """Find attributes with matching key from states.""" for state in states: value = state.attributes.get(key) @@ -150,9 +151,9 @@ def mean_tuple(*args): def reduce_attribute( - states: List[State], + states: list[State], key: str, - default: Optional[Any] = None, + default: Any | None = None, reduce: Callable[..., Any] = mean_int, ) -> Any: """Find the first attribute matching key from states. @@ -280,7 +281,7 @@ def convert_install_code(value: str) -> bytes: ) -def qr_to_install_code(qr_code: str) -> Tuple[zigpy.types.EUI64, bytes]: +def qr_to_install_code(qr_code: str) -> tuple[zigpy.types.EUI64, bytes]: """Try to parse the QR code. if successful, return a tuple of a EUI64 address and install code. diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 66a9a70e752317..2f9ed57745a26e 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -1,6 +1,8 @@ """Mapping registries for Zigbee Home Automation.""" +from __future__ import annotations + import collections -from typing import Callable, Dict, List, Set, Tuple, Union +from typing import Callable, Dict import attr import zigpy.profiles.zha @@ -134,19 +136,19 @@ def set_or_callable(value): class MatchRule: """Match a ZHA Entity to a channel name or generic id.""" - channel_names: Union[Callable, Set[str], str] = attr.ib( + channel_names: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - generic_ids: Union[Callable, Set[str], str] = attr.ib( + generic_ids: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - manufacturers: Union[Callable, Set[str], str] = attr.ib( + manufacturers: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - models: Union[Callable, Set[str], str] = attr.ib( + models: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) - aux_channels: Union[Callable, Set[str], str] = attr.ib( + aux_channels: Callable | set[str] | str = attr.ib( factory=frozenset, converter=set_or_callable ) @@ -176,7 +178,7 @@ def weight(self) -> int: weight += 1 * len(self.aux_channels) return weight - def claim_channels(self, channel_pool: List[ChannelType]) -> List[ChannelType]: + def claim_channels(self, channel_pool: list[ChannelType]) -> list[ChannelType]: """Return a list of channels this rule matches + aux channels.""" claimed = [] if isinstance(self.channel_names, frozenset): @@ -189,15 +191,15 @@ def claim_channels(self, channel_pool: List[ChannelType]) -> List[ChannelType]: claimed.extend([ch for ch in channel_pool if ch.name in self.aux_channels]) return claimed - def strict_matched(self, manufacturer: str, model: str, channels: List) -> bool: + def strict_matched(self, manufacturer: str, model: str, channels: list) -> bool: """Return True if this device matches the criteria.""" return all(self._matched(manufacturer, model, channels)) - def loose_matched(self, manufacturer: str, model: str, channels: List) -> bool: + def loose_matched(self, manufacturer: str, model: str, channels: list) -> bool: """Return True if this device matches the criteria.""" return any(self._matched(manufacturer, model, channels)) - def _matched(self, manufacturer: str, model: str, channels: List) -> list: + def _matched(self, manufacturer: str, model: str, channels: list) -> list: """Return a list of field matches.""" if not any(attr.asdict(self).values()): return [False] @@ -245,9 +247,9 @@ def get_entity( component: str, manufacturer: str, model: str, - channels: List[ChannelType], + channels: list[ChannelType], default: CALLABLE_T = None, - ) -> Tuple[CALLABLE_T, List[ChannelType]]: + ) -> tuple[CALLABLE_T, list[ChannelType]]: """Match a ZHA Channels to a ZHA Entity class.""" matches = self._strict_registry[component] for match in sorted(matches, key=lambda x: x.weight, reverse=True): @@ -264,11 +266,11 @@ def get_group_entity(self, component: str) -> CALLABLE_T: def strict_match( self, component: str, - channel_names: Union[Callable, Set[str], str] = None, - generic_ids: Union[Callable, Set[str], str] = None, - manufacturers: Union[Callable, Set[str], str] = None, - models: Union[Callable, Set[str], str] = None, - aux_channels: Union[Callable, Set[str], str] = None, + channel_names: Callable | set[str] | str = None, + generic_ids: Callable | set[str] | str = None, + manufacturers: Callable | set[str] | str = None, + models: Callable | set[str] | str = None, + aux_channels: Callable | set[str] | str = None, ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a strict match rule.""" @@ -289,11 +291,11 @@ def decorator(zha_ent: CALLABLE_T) -> CALLABLE_T: def loose_match( self, component: str, - channel_names: Union[Callable, Set[str], str] = None, - generic_ids: Union[Callable, Set[str], str] = None, - manufacturers: Union[Callable, Set[str], str] = None, - models: Union[Callable, Set[str], str] = None, - aux_channels: Union[Callable, Set[str], str] = None, + channel_names: Callable | set[str] | str = None, + generic_ids: Callable | set[str] | str = None, + manufacturers: Callable | set[str] | str = None, + models: Callable | set[str] | str = None, + aux_channels: Callable | set[str] | str = None, ) -> Callable[[CALLABLE_T], CALLABLE_T]: """Decorate a loose match rule.""" diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 2db803258bc129..43b41153657e8e 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -1,8 +1,10 @@ """Data storage helper for ZHA.""" +from __future__ import annotations + from collections import OrderedDict import datetime import time -from typing import MutableMapping, Optional, cast +from typing import MutableMapping, cast import attr @@ -24,9 +26,9 @@ class ZhaDeviceEntry: """Zha Device storage Entry.""" - name: Optional[str] = attr.ib(default=None) - ieee: Optional[str] = attr.ib(default=None) - last_seen: Optional[float] = attr.ib(default=None) + name: str | None = attr.ib(default=None) + ieee: str | None = attr.ib(default=None) + last_seen: float | None = attr.ib(default=None) class ZhaStorage: diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index e202def46c5ab7..5530cd3e3f5176 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -1,8 +1,9 @@ """Support for ZHA covers.""" +from __future__ import annotations + import asyncio import functools import logging -from typing import List, Optional from zigpy.zcl.foundation import Status @@ -180,7 +181,7 @@ def __init__( self, unique_id: str, zha_device: ZhaDeviceType, - channels: List[ChannelType], + channels: list[ChannelType], **kwargs, ): """Initialize the ZHA light.""" @@ -199,12 +200,12 @@ def current_cover_position(self): return self._position @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_SHADE @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return True if shade is closed.""" if self._is_open is None: return None @@ -289,7 +290,7 @@ class KeenVent(Shade): """Keen vent cover.""" @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_DAMPER diff --git a/homeassistant/components/zha/device_action.py b/homeassistant/components/zha/device_action.py index 4636393919052b..9d419b164357f0 100644 --- a/homeassistant/components/zha/device_action.py +++ b/homeassistant/components/zha/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for ZHA devices.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -54,7 +54,7 @@ async def async_call_action_from_config( ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" try: zha_device = await async_get_zha_device(hass, device_id) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 9a573e92aa4380..445151899ee0bf 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -1,9 +1,10 @@ """Entity for Zigbee Home Automation.""" +from __future__ import annotations import asyncio import functools import logging -from typing import Any, Awaitable, Dict, List, Optional +from typing import Any, Awaitable from homeassistant.const import ATTR_NAME from homeassistant.core import CALLBACK_TYPE, Event, callback @@ -45,9 +46,9 @@ def __init__(self, unique_id: str, zha_device: ZhaDeviceType, **kwargs): self._should_poll: bool = False self._unique_id: str = unique_id self._state: Any = None - self._extra_state_attributes: Dict[str, Any] = {} + self._extra_state_attributes: dict[str, Any] = {} self._zha_device: ZhaDeviceType = zha_device - self._unsubs: List[CALLABLE_T] = [] + self._unsubs: list[CALLABLE_T] = [] self.remove_future: Awaitable[None] = None @property @@ -66,7 +67,7 @@ def zha_device(self) -> ZhaDeviceType: return self._zha_device @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return device specific state attributes.""" return self._extra_state_attributes @@ -81,7 +82,7 @@ def should_poll(self) -> bool: return self._should_poll @property - def device_info(self) -> Dict[str, Any]: + def device_info(self) -> dict[str, Any]: """Return a device description for device registry.""" zha_device_info = self._zha_device.device_info ieee = zha_device_info["ieee"] @@ -143,7 +144,7 @@ def __init__( self, unique_id: str, zha_device: ZhaDeviceType, - channels: List[ChannelType], + channels: list[ChannelType], **kwargs, ): """Init ZHA entity.""" @@ -152,7 +153,7 @@ def __init__( ch_names = [ch.cluster.ep_attribute for ch in channels] ch_names = ", ".join(sorted(ch_names)) self._name: str = f"{zha_device.name} {ieeetail} {ch_names}" - self.cluster_channels: Dict[str, ChannelType] = {} + self.cluster_channels: dict[str, ChannelType] = {} for channel in channels: self.cluster_channels[channel.name] = channel @@ -217,7 +218,7 @@ class ZhaGroupEntity(BaseZhaEntity): """A base class for ZHA group entities.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a light group.""" super().__init__(unique_id, zha_device, **kwargs) @@ -225,8 +226,8 @@ def __init__( self._group = zha_device.gateway.groups.get(group_id) self._name = f"{self._group.name}_zha_group_0x{group_id:04x}" self._group_id: int = group_id - self._entity_ids: List[str] = entity_ids - self._async_unsub_state_changed: Optional[CALLBACK_TYPE] = None + self._entity_ids: list[str] = entity_ids + self._async_unsub_state_changed: CALLBACK_TYPE | None = None self._handled_group_membership = False @property diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index ed041f8c6c01f6..3c50261b56578b 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -1,8 +1,9 @@ """Fans on Zigbee Home Automation networks.""" +from __future__ import annotations + from abc import abstractmethod import functools import math -from typing import List, Optional from zigpy.exceptions import ZigbeeException import zigpy.zcl.clusters.hvac as hvac @@ -77,7 +78,7 @@ class BaseFan(FanEntity): """Base representation of a ZHA fan.""" @property - def preset_modes(self) -> List[str]: + def preset_modes(self) -> list[str]: """Return the available preset modes.""" return PRESET_MODES @@ -103,7 +104,7 @@ async def async_turn_off(self, **kwargs) -> None: """Turn the entity off.""" await self.async_set_percentage(0) - async def async_set_percentage(self, percentage: Optional[int]) -> None: + async def async_set_percentage(self, percentage: int | None) -> None: """Set the speed percenage of the fan.""" fan_mode = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) await self._async_set_fan_mode(fan_mode) @@ -142,7 +143,7 @@ async def async_added_to_hass(self): ) @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if ( self._fan_channel.fan_mode is None @@ -154,7 +155,7 @@ def percentage(self) -> Optional[int]: return ranged_value_to_percentage(SPEED_RANGE, self._fan_channel.fan_mode) @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" return PRESET_MODES_TO_NAME.get(self._fan_channel.fan_mode) @@ -174,7 +175,7 @@ class FanGroup(BaseFan, ZhaGroupEntity): """Representation of a fan group.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a fan group.""" super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) @@ -185,12 +186,12 @@ def __init__( self._preset_mode = None @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" return self._percentage @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode.""" return self._preset_mode @@ -205,11 +206,11 @@ async def _async_set_fan_mode(self, fan_mode: int) -> None: async def async_update(self): """Attempt to retrieve on off state from the fan.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) - percentage_states: List[State] = [ + states: list[State] = list(filter(None, all_states)) + percentage_states: list[State] = [ state for state in states if state.attributes.get(ATTR_PERCENTAGE) ] - preset_mode_states: List[State] = [ + preset_mode_states: list[State] = [ state for state in states if state.attributes.get(ATTR_PRESET_MODE) ] self._available = any(state.state != STATE_UNAVAILABLE for state in states) diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index f81b931a49dbd7..72807458d266ac 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -1,4 +1,6 @@ """Lights on Zigbee Home Automation networks.""" +from __future__ import annotations + from collections import Counter from datetime import timedelta import enum @@ -6,7 +8,7 @@ import itertools import logging import random -from typing import Any, Dict, List, Optional, Tuple +from typing import Any from zigpy.zcl.clusters.general import Identify, LevelControl, OnOff from zigpy.zcl.clusters.lighting import Color @@ -122,15 +124,15 @@ def __init__(self, *args, **kwargs): """Initialize the light.""" super().__init__(*args, **kwargs) self._available: bool = False - self._brightness: Optional[int] = None - self._off_brightness: Optional[int] = None - self._hs_color: Optional[Tuple[float, float]] = None - self._color_temp: Optional[int] = None - self._min_mireds: Optional[int] = 153 - self._max_mireds: Optional[int] = 500 - self._white_value: Optional[int] = None - self._effect_list: Optional[List[str]] = None - self._effect: Optional[str] = None + self._brightness: int | None = None + self._off_brightness: int | None = None + self._hs_color: tuple[float, float] | None = None + self._color_temp: int | None = None + self._min_mireds: int | None = 153 + self._max_mireds: int | None = 500 + self._white_value: int | None = None + self._effect_list: list[str] | None = None + self._effect: str | None = None self._supported_features: int = 0 self._state: bool = False self._on_off_channel = None @@ -139,7 +141,7 @@ def __init__(self, *args, **kwargs): self._identify_channel = None @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return state attributes.""" attributes = {"off_brightness": self._off_brightness} return attributes @@ -348,8 +350,8 @@ def __init__(self, unique_id, zha_device: ZhaDeviceType, channels, **kwargs): self._color_channel = self.cluster_channels.get(CHANNEL_COLOR) self._identify_channel = self.zha_device.channels.identify_ch if self._color_channel: - self._min_mireds: Optional[int] = self._color_channel.min_mireds - self._max_mireds: Optional[int] = self._color_channel.max_mireds + self._min_mireds: int | None = self._color_channel.min_mireds + self._max_mireds: int | None = self._color_channel.max_mireds self._cancel_refresh_handle = None effect_list = [] @@ -532,7 +534,7 @@ class LightGroup(BaseLight, ZhaGroupEntity): """Representation of a light group.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a light group.""" super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) @@ -569,7 +571,7 @@ async def async_turn_off(self, **kwargs): async def async_update(self) -> None: """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) + states: list[State] = list(filter(None, all_states)) on_states = [state for state in states if state.state == STATE_ON] self._state = len(on_states) > 0 diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 926fd4555e111f..425a41a1340a7c 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -1,7 +1,9 @@ """Sensors on Zigbee Home Automation networks.""" +from __future__ import annotations + import functools import numbers -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable from homeassistant.components.sensor import ( DEVICE_CLASS_BATTERY, @@ -90,18 +92,18 @@ async def async_setup_entry( class Sensor(ZhaEntity): """Base ZHA sensor.""" - SENSOR_ATTR: Optional[Union[int, str]] = None + SENSOR_ATTR: int | str | None = None _decimals: int = 1 - _device_class: Optional[str] = None + _device_class: str | None = None _divisor: int = 1 _multiplier: int = 1 - _unit: Optional[str] = None + _unit: str | None = None def __init__( self, unique_id: str, zha_device: ZhaDeviceType, - channels: List[ChannelType], + channels: list[ChannelType], **kwargs, ): """Init this sensor.""" @@ -121,7 +123,7 @@ def device_class(self) -> str: return self._device_class @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity.""" return self._unit @@ -139,7 +141,7 @@ def async_set_state(self, attr_id: int, attr_name: str, value: Any) -> None: """Handle state update from channel.""" self.async_write_ha_state() - def formatter(self, value: int) -> Union[int, float]: + def formatter(self, value: int) -> int | float: """Numeric pass-through formatter.""" if self._decimals > 0: return round( @@ -178,7 +180,7 @@ def formatter(value: int) -> int: return value @property - def extra_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> dict[str, Any]: """Return device state attrs for battery sensors.""" state_attrs = {} battery_size = self._channel.cluster.get("battery_size") @@ -208,7 +210,7 @@ def should_poll(self) -> bool: """Return True if HA needs to poll for state changes.""" return True - def formatter(self, value: int) -> Union[int, float]: + def formatter(self, value: int) -> int | float: """Return 'normalized' value.""" value = value * self._channel.multiplier / self._channel.divisor if value < 100 and self._channel.divisor > 1: @@ -254,7 +256,7 @@ class SmartEnergyMetering(Sensor): SENSOR_ATTR = "instantaneous_demand" _device_class = DEVICE_CLASS_POWER - def formatter(self, value: int) -> Union[int, float]: + def formatter(self, value: int) -> int | float: """Pass through channel formatter.""" return self._channel.formatter_function(value) diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 60db9ec0a08b8b..75254f631b9d7f 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -1,6 +1,8 @@ """Switches on Zigbee Home Automation networks.""" +from __future__ import annotations + import functools -from typing import Any, List +from typing import Any from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import Status @@ -113,7 +115,7 @@ class SwitchGroup(BaseSwitch, ZhaGroupEntity): """Representation of a switch group.""" def __init__( - self, entity_ids: List[str], unique_id: str, group_id: int, zha_device, **kwargs + self, entity_ids: list[str], unique_id: str, group_id: int, zha_device, **kwargs ) -> None: """Initialize a switch group.""" super().__init__(entity_ids, unique_id, group_id, zha_device, **kwargs) @@ -124,7 +126,7 @@ def __init__( async def async_update(self) -> None: """Query all members and determine the light group state.""" all_states = [self.hass.states.get(x) for x in self._entity_ids] - states: List[State] = list(filter(None, all_states)) + states: list[State] = list(filter(None, all_states)) on_states = [state for state in states if state.state == STATE_ON] self._state = len(on_states) > 0 diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index e1d48cbe1ff5ce..7e329127d03215 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, cast import voluptuous as vol @@ -92,7 +92,7 @@ def empty_value(value: Any) -> Any: @bind_hass def async_active_zone( hass: HomeAssistant, latitude: float, longitude: float, radius: int = 0 -) -> Optional[State]: +) -> State | None: """Find the active zone for given latitude, longitude. This method must be run in the event loop. @@ -161,22 +161,22 @@ class ZoneStorageCollection(collection.StorageCollection): CREATE_SCHEMA = vol.Schema(CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS) - async def _process_create_data(self, data: Dict) -> Dict: + async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" return cast(Dict, self.CREATE_SCHEMA(data)) @callback - def _get_suggested_id(self, info: Dict) -> str: + def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" return cast(str, info[CONF_NAME]) - async def _update_data(self, data: dict, update_data: Dict) -> Dict: + async def _update_data(self, data: dict, update_data: dict) -> dict: """Return a new updated data object.""" update_data = self.UPDATE_SCHEMA(update_data) return {**data, **update_data} -async def async_setup(hass: HomeAssistant, config: Dict) -> bool: +async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up configured zones as well as Home Assistant zone if necessary.""" component = entity_component.EntityComponent(_LOGGER, DOMAIN, hass) id_manager = collection.IDManager() @@ -240,7 +240,7 @@ async def core_config_updated(_: Event) -> None: @callback -def _home_conf(hass: HomeAssistant) -> Dict: +def _home_conf(hass: HomeAssistant) -> dict: """Return the home zone config.""" return { CONF_NAME: hass.config.location_name, @@ -279,15 +279,15 @@ async def async_unload_entry( class Zone(entity.Entity): """Representation of a Zone.""" - def __init__(self, config: Dict): + def __init__(self, config: dict): """Initialize the zone.""" self._config = config self.editable = True - self._attrs: Optional[Dict] = None + self._attrs: dict | None = None self._generate_attrs() @classmethod - def from_yaml(cls, config: Dict) -> Zone: + def from_yaml(cls, config: dict) -> Zone: """Return entity instance initialized from yaml storage.""" zone = cls(config) zone.editable = False @@ -305,17 +305,17 @@ def name(self) -> str: return cast(str, self._config[CONF_NAME]) @property - def unique_id(self) -> Optional[str]: + def unique_id(self) -> str | None: """Return unique ID.""" return self._config.get(CONF_ID) @property - def icon(self) -> Optional[str]: + def icon(self) -> str | None: """Return the icon if any.""" return self._config.get(CONF_ICON) @property - def state_attributes(self) -> Optional[Dict]: + def state_attributes(self) -> dict | None: """Return the state attributes of the zone.""" return self._attrs @@ -324,7 +324,7 @@ def should_poll(self) -> bool: """Zone does not poll.""" return False - async def async_update_config(self, config: Dict) -> None: + async def async_update_config(self, config: dict) -> None: """Handle when the config is updated.""" if self._config == config: return diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 20eb52c2c194b6..ac8c47af9a3228 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -1,7 +1,8 @@ """Support for Z-Wave climate devices.""" # Because we do not compile openzwave on CI +from __future__ import annotations + import logging -from typing import Optional, Tuple from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -191,7 +192,7 @@ def _mode(self) -> None: """Return thermostat mode Z-Wave value.""" raise NotImplementedError() - def _current_mode_setpoints(self) -> Tuple: + def _current_mode_setpoints(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" raise NotImplementedError() @@ -483,12 +484,12 @@ def target_temperature(self): return self._target_temperature @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" return self._target_temperature_range[0] @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" return self._target_temperature_range[1] @@ -590,7 +591,7 @@ def _mode(self) -> None: """Return thermostat mode Z-Wave value.""" return self.values.mode - def _current_mode_setpoints(self) -> Tuple: + def _current_mode_setpoints(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" return (self.values.primary,) @@ -606,7 +607,7 @@ def _mode(self) -> None: """Return thermostat mode Z-Wave value.""" return self.values.primary - def _current_mode_setpoints(self) -> Tuple: + def _current_mode_setpoints(self) -> tuple: """Return a tuple of current setpoint Z-Wave value(s).""" current_mode = str(self.values.primary.data).lower() setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ()) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 2d4db96088661c..64dcf9614b2a5d 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,6 +1,8 @@ """The Z-Wave JS integration.""" +from __future__ import annotations + import asyncio -from typing import Callable, List +from typing import Callable from async_timeout import timeout from zwave_js_server.client import Client as ZwaveClient @@ -226,7 +228,7 @@ def async_on_notification(notification: Notification) -> None: entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False - unsubscribe_callbacks: List[Callable] = [] + unsubscribe_callbacks: list[Callable] = [] entry_hass_data[DATA_CLIENT] = client entry_hass_data[DATA_UNSUBSCRIBE] = unsubscribe_callbacks diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index 818e46a34aa001..0c2fdb17944303 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -3,7 +3,7 @@ import asyncio from functools import partial -from typing import Any, Callable, Optional, TypeVar, cast +from typing import Any, Callable, TypeVar, cast from homeassistant.components.hassio import ( async_create_snapshot, @@ -66,9 +66,9 @@ class AddonManager: def __init__(self, hass: HomeAssistant) -> None: """Set up the add-on manager.""" self._hass = hass - self._install_task: Optional[asyncio.Task] = None - self._start_task: Optional[asyncio.Task] = None - self._update_task: Optional[asyncio.Task] = None + self._install_task: asyncio.Task | None = None + self._start_task: asyncio.Task | None = None + self._update_task: asyncio.Task | None = None def task_in_progress(self) -> bool: """Return True if any of the add-on tasks are in progress.""" diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 055115db7b9e47..087b94d0060ffa 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -1,7 +1,8 @@ """Websocket API for Z-Wave JS.""" +from __future__ import annotations + import dataclasses import json -from typing import Dict from aiohttp import hdrs, web, web_exceptions import voluptuous as vol @@ -399,7 +400,7 @@ def convert_log_level_to_enum(value: str) -> LogLevel: return LogLevel[value.upper()] -def filename_is_present_if_logging_to_file(obj: Dict) -> Dict: +def filename_is_present_if_logging_to_file(obj: dict) -> dict: """Validate that filename is provided if log_to_file is True.""" if obj.get(LOG_TO_FILE, False) and FILENAME not in obj: raise vol.Invalid("`filename` must be provided if logging to file") diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 8d266c83f2249a..47c374405b1a59 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -1,7 +1,8 @@ """Representation of Z-Wave binary sensors.""" +from __future__ import annotations import logging -from typing import Callable, List, Optional, TypedDict +from typing import Callable, TypedDict from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import CommandClass @@ -56,14 +57,14 @@ class NotificationSensorMapping(TypedDict, total=False): """Represent a notification sensor mapping dict type.""" type: int # required - states: List[str] + states: list[str] device_class: str enabled: bool # Mappings for Notification sensors # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json -NOTIFICATION_SENSOR_MAPPINGS: List[NotificationSensorMapping] = [ +NOTIFICATION_SENSOR_MAPPINGS: list[NotificationSensorMapping] = [ { # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected "type": NOTIFICATION_SMOKE_ALARM, @@ -201,13 +202,13 @@ class PropertySensorMapping(TypedDict, total=False): """Represent a property sensor mapping dict type.""" property_name: str # required - on_states: List[str] # required + on_states: list[str] # required device_class: str enabled: bool # Mappings for property sensors -PROPERTY_SENSOR_MAPPINGS: List[PropertySensorMapping] = [ +PROPERTY_SENSOR_MAPPINGS: list[PropertySensorMapping] = [ { "property_name": PROPERTY_DOOR_STATUS, "on_states": ["open"], @@ -226,7 +227,7 @@ async def async_setup_entry( @callback def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Binary Sensor.""" - entities: List[BinarySensorEntity] = [] + entities: list[BinarySensorEntity] = [] if info.platform_hint == "notification": # Get all sensors from Notification CC states @@ -268,14 +269,14 @@ def __init__( self._name = self.generate_name(include_value_name=True) @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return if the sensor is on or off.""" if self.info.primary_value.value is None: return None return bool(self.info.primary_value.value) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return device class.""" if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY @@ -314,14 +315,14 @@ def __init__( self._mapping_info = self._get_sensor_mapping() @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return if the sensor is on or off.""" if self.info.primary_value.value is None: return None return int(self.info.primary_value.value) == int(self.state_key) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return device class.""" return self._mapping_info.get("device_class") @@ -365,14 +366,14 @@ def __init__( self._name = self.generate_name(include_value_name=True) @property - def is_on(self) -> Optional[bool]: + def is_on(self) -> bool | None: """Return if the sensor is on or off.""" if self.info.primary_value.value is None: return None return self.info.primary_value.value in self._mapping_info["on_states"] @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return device class.""" return self._mapping_info.get("device_class") diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 3f6b26f536f80c..78d76423378b95 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -1,5 +1,7 @@ """Representation of Z-Wave thermostats.""" -from typing import Any, Callable, Dict, List, Optional, cast +from __future__ import annotations + +from typing import Any, Callable, cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ( @@ -50,7 +52,7 @@ # Map Z-Wave HVAC Mode to Home Assistant value # Note: We treat "auto" as "heat_cool" as most Z-Wave devices # report auto_changeover as auto without schedule support. -ZW_HVAC_MODE_MAP: Dict[int, str] = { +ZW_HVAC_MODE_MAP: dict[int, str] = { ThermostatMode.OFF: HVAC_MODE_OFF, ThermostatMode.HEAT: HVAC_MODE_HEAT, ThermostatMode.COOL: HVAC_MODE_COOL, @@ -67,7 +69,7 @@ ThermostatMode.FULL_POWER: HVAC_MODE_HEAT, } -HVAC_CURRENT_MAP: Dict[int, str] = { +HVAC_CURRENT_MAP: dict[int, str] = { ThermostatOperatingState.IDLE: CURRENT_HVAC_IDLE, ThermostatOperatingState.PENDING_HEAT: CURRENT_HVAC_IDLE, ThermostatOperatingState.HEATING: CURRENT_HVAC_HEAT, @@ -94,7 +96,7 @@ async def async_setup_entry( @callback def async_add_climate(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Climate.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZWaveClimate(config_entry, client, info)) async_add_entities(entities) @@ -116,14 +118,14 @@ def __init__( ) -> None: """Initialize lock.""" super().__init__(config_entry, client, info) - self._hvac_modes: Dict[str, Optional[int]] = {} - self._hvac_presets: Dict[str, Optional[int]] = {} - self._unit_value: Optional[ZwaveValue] = None + self._hvac_modes: dict[str, int | None] = {} + self._hvac_presets: dict[str, int | None] = {} + self._unit_value: ZwaveValue | None = None self._current_mode = self.get_zwave_value( THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE ) - self._setpoint_values: Dict[ThermostatSetpointType, ZwaveValue] = {} + self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue] = {} for enum in ThermostatSetpointType: self._setpoint_values[enum] = self.get_zwave_value( THERMOSTAT_SETPOINT_PROPERTY, @@ -184,8 +186,8 @@ def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue: def _set_modes_and_presets(self) -> None: """Convert Z-Wave Thermostat modes into Home Assistant modes and presets.""" - all_modes: Dict[str, Optional[int]] = {} - all_presets: Dict[str, Optional[int]] = {PRESET_NONE: None} + all_modes: dict[str, int | None] = {} + all_presets: dict[str, int | None] = {PRESET_NONE: None} # Z-Wave uses one list for both modes and presets. # Iterate over all Z-Wave ThermostatModes and extract the hvac modes and presets. @@ -208,7 +210,7 @@ def _set_modes_and_presets(self) -> None: self._hvac_presets = all_presets @property - def _current_mode_setpoint_enums(self) -> List[Optional[ThermostatSetpointType]]: + def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType | None]: """Return the list of enums that are relevant to the current thermostat mode.""" if self._current_mode is None: # Thermostat(valve) with no support for setting a mode is considered heating-only @@ -238,12 +240,12 @@ def hvac_mode(self) -> str: return ZW_HVAC_MODE_MAP.get(int(self._current_mode.value), HVAC_MODE_HEAT_COOL) @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes.""" return list(self._hvac_modes) @property - def hvac_action(self) -> Optional[str]: + def hvac_action(self) -> str | None: """Return the current running hvac operation if supported.""" if not self._operating_state: return None @@ -253,17 +255,17 @@ def hvac_action(self) -> Optional[str]: return HVAC_CURRENT_MAP.get(int(self._operating_state.value)) @property - def current_humidity(self) -> Optional[int]: + def current_humidity(self) -> int | None: """Return the current humidity level.""" return self._current_humidity.value if self._current_humidity else None @property - def current_temperature(self) -> Optional[float]: + def current_temperature(self) -> float | None: """Return the current temperature.""" return self._current_temp.value if self._current_temp else None @property - def target_temperature(self) -> Optional[float]: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -275,7 +277,7 @@ def target_temperature(self) -> Optional[float]: return temp.value if temp else None @property - def target_temperature_high(self) -> Optional[float]: + def target_temperature_high(self) -> float | None: """Return the highbound target temperature we try to reach.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -287,7 +289,7 @@ def target_temperature_high(self) -> Optional[float]: return temp.value if temp else None @property - def target_temperature_low(self) -> Optional[float]: + def target_temperature_low(self) -> float | None: """Return the lowbound target temperature we try to reach.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -297,7 +299,7 @@ def target_temperature_low(self) -> Optional[float]: return None @property - def preset_mode(self) -> Optional[str]: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" if self._current_mode and self._current_mode.value is None: # guard missing value @@ -310,12 +312,12 @@ def preset_mode(self) -> Optional[str]: return PRESET_NONE @property - def preset_modes(self) -> Optional[List[str]]: + def preset_modes(self) -> list[str] | None: """Return a list of available preset modes.""" return list(self._hvac_presets) @property - def fan_mode(self) -> Optional[str]: + def fan_mode(self) -> str | None: """Return the fan setting.""" if ( self._fan_mode @@ -326,14 +328,14 @@ def fan_mode(self) -> Optional[str]: return None @property - def fan_modes(self) -> Optional[List[str]]: + def fan_modes(self) -> list[str] | None: """Return the list of available fan modes.""" if self._fan_mode and self._fan_mode.metadata.states: return list(self._fan_mode.metadata.states.values()) return None @property - def extra_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> dict[str, str] | None: """Return the optional state attributes.""" if ( self._fan_state @@ -373,7 +375,7 @@ async def async_set_fan_mode(self, fan_mode: str) -> None: async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - hvac_mode: Optional[str] = kwargs.get(ATTR_HVAC_MODE) + hvac_mode: str | None = kwargs.get(ATTR_HVAC_MODE) if hvac_mode is not None: await self.async_set_hvac_mode(hvac_mode) @@ -381,7 +383,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None: setpoint: ZwaveValue = self._setpoint_value( self._current_mode_setpoint_enums[0] ) - target_temp: Optional[float] = kwargs.get(ATTR_TEMPERATURE) + target_temp: float | None = kwargs.get(ATTR_TEMPERATURE) if target_temp is not None: await self.info.node.async_set_value(setpoint, target_temp) elif len(self._current_mode_setpoint_enums) == 2: @@ -391,8 +393,8 @@ async def async_set_temperature(self, **kwargs: Any) -> None: setpoint_high: ZwaveValue = self._setpoint_value( self._current_mode_setpoint_enums[1] ) - target_temp_low: Optional[float] = kwargs.get(ATTR_TARGET_TEMP_LOW) - target_temp_high: Optional[float] = kwargs.get(ATTR_TARGET_TEMP_HIGH) + target_temp_low: float | None = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high: float | None = kwargs.get(ATTR_TARGET_TEMP_HIGH) if target_temp_low is not None: await self.info.node.async_set_value(setpoint_low, target_temp_low) if target_temp_high is not None: diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 8ba2909cf20ed9..75e948004cb4a8 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -1,7 +1,9 @@ """Config flow for Z-Wave JS integration.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Optional, cast +from typing import Any, cast import aiohttp from async_timeout import timeout @@ -76,18 +78,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Set up flow instance.""" - self.network_key: Optional[str] = None - self.usb_path: Optional[str] = None + self.network_key: str | None = None + self.usb_path: str | None = None self.use_addon = False - self.ws_address: Optional[str] = None + self.ws_address: str | None = None # If we install the add-on we should uninstall it on entry remove. self.integration_created_addon = False - self.install_task: Optional[asyncio.Task] = None - self.start_task: Optional[asyncio.Task] = None + self.install_task: asyncio.Task | None = None + self.start_task: asyncio.Task | None = None async def async_step_user( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle the initial step.""" if is_hassio(self.hass): # type: ignore # no-untyped-call return await self.async_step_on_supervisor() @@ -95,8 +97,8 @@ async def async_step_user( return await self.async_step_manual() async def async_step_manual( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle a manual configuration.""" if user_input is None: return self.async_show_form( @@ -133,8 +135,8 @@ async def async_step_manual( ) async def async_step_hassio( # type: ignore # override - self, discovery_info: Dict[str, Any] - ) -> Dict[str, Any]: + self, discovery_info: dict[str, Any] + ) -> dict[str, Any]: """Receive configuration from add-on discovery info. This flow is triggered by the Z-Wave JS add-on. @@ -151,8 +153,8 @@ async def async_step_hassio( # type: ignore # override return await self.async_step_hassio_confirm() async def async_step_hassio_confirm( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Confirm the add-on discovery.""" if user_input is not None: return await self.async_step_on_supervisor( @@ -162,7 +164,7 @@ async def async_step_hassio_confirm( return self.async_show_form(step_id="hassio_confirm") @callback - def _async_create_entry_from_vars(self) -> Dict[str, Any]: + def _async_create_entry_from_vars(self) -> dict[str, Any]: """Return a config entry for the flow.""" return self.async_create_entry( title=TITLE, @@ -176,8 +178,8 @@ def _async_create_entry_from_vars(self) -> Dict[str, Any]: ) async def async_step_on_supervisor( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle logic when on Supervisor host.""" if user_input is None: return self.async_show_form( @@ -200,8 +202,8 @@ async def async_step_on_supervisor( return await self.async_step_install_addon() async def async_step_install_addon( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Install Z-Wave JS add-on.""" if not self.install_task: self.install_task = self.hass.async_create_task(self._async_install_addon()) @@ -220,18 +222,18 @@ async def async_step_install_addon( return self.async_show_progress_done(next_step_id="configure_addon") async def async_step_install_failed( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Add-on installation failed.""" return self.async_abort(reason="addon_install_failed") async def async_step_configure_addon( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Ask for config for Z-Wave JS add-on.""" addon_config = await self._async_get_addon_config() - errors: Dict[str, str] = {} + errors: dict[str, str] = {} if user_input is not None: self.network_key = user_input[CONF_NETWORK_KEY] @@ -262,8 +264,8 @@ async def async_step_configure_addon( ) async def async_step_start_addon( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Start Z-Wave JS add-on.""" if not self.start_task: self.start_task = self.hass.async_create_task(self._async_start_addon()) @@ -280,8 +282,8 @@ async def async_step_start_addon( return self.async_show_progress_done(next_step_id="finish_addon_setup") async def async_step_start_failed( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Add-on start failed.""" return self.async_abort(reason="addon_start_failed") @@ -317,8 +319,8 @@ async def _async_start_addon(self) -> None: ) async def async_step_finish_addon_setup( - self, user_input: Optional[Dict[str, Any]] = None - ) -> Dict[str, Any]: + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Prepare info needed to complete the config entry. Get add-on discovery info and server version info. diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index ff77bdb408dbc1..25c69335ed1c23 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -1,6 +1,8 @@ """Support for Z-Wave cover devices.""" +from __future__ import annotations + import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.value import Value as ZwaveValue @@ -42,7 +44,7 @@ async def async_setup_entry( @callback def async_add_cover(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave cover.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "motorized_barrier": entities.append(ZwaveMotorizedBarrier(config_entry, client, info)) else: @@ -72,7 +74,7 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity): """Representation of a Z-Wave Cover device.""" @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return true if cover is closed.""" if self.info.primary_value.value is None: # guard missing value @@ -80,7 +82,7 @@ def is_closed(self) -> Optional[bool]: return bool(self.info.primary_value.value == 0) @property - def current_cover_position(self) -> Optional[int]: + def current_cover_position(self) -> int | None: """Return the current position of cover where 0 means closed and 100 is fully open.""" if self.info.primary_value.value is None: # guard missing value @@ -130,31 +132,31 @@ def __init__( ) @property - def supported_features(self) -> Optional[int]: + def supported_features(self) -> int | None: """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" return DEVICE_CLASS_GARAGE @property - def is_opening(self) -> Optional[bool]: + def is_opening(self) -> bool | None: """Return if the cover is opening or not.""" if self.info.primary_value.value is None: return None return bool(self.info.primary_value.value == BARRIER_STATE_OPENING) @property - def is_closing(self) -> Optional[bool]: + def is_closing(self) -> bool | None: """Return if the cover is closing or not.""" if self.info.primary_value.value is None: return None return bool(self.info.primary_value.value == BARRIER_STATE_CLOSING) @property - def is_closed(self) -> Optional[bool]: + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" if self.info.primary_value.value is None: return None diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index f27325b87d2864..5f1f04274baa3b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -1,7 +1,8 @@ """Map Z-Wave nodes and values to Home Assistant entities.""" +from __future__ import annotations from dataclasses import dataclass -from typing import Generator, List, Optional, Set, Union +from typing import Generator from zwave_js_server.const import CommandClass from zwave_js_server.model.device_class import DeviceClassItem @@ -22,7 +23,7 @@ class ZwaveDiscoveryInfo: # the home assistant platform for which an entity should be created platform: str # hint for the platform about this discovered entity - platform_hint: Optional[str] = "" + platform_hint: str | None = "" @dataclass @@ -35,13 +36,13 @@ class ZWaveValueDiscoverySchema: """ # [optional] the value's command class must match ANY of these values - command_class: Optional[Set[int]] = None + command_class: set[int] | None = None # [optional] the value's endpoint must match ANY of these values - endpoint: Optional[Set[int]] = None + endpoint: set[int] | None = None # [optional] the value's property must match ANY of these values - property: Optional[Set[Union[str, int]]] = None + property: set[str | int] | None = None # [optional] the value's metadata_type must match ANY of these values - type: Optional[Set[str]] = None + type: set[str] | None = None @dataclass @@ -58,25 +59,25 @@ class ZWaveDiscoverySchema: # primary value belonging to this discovery scheme primary_value: ZWaveValueDiscoverySchema # [optional] hint for platform - hint: Optional[str] = None + hint: str | None = None # [optional] the node's manufacturer_id must match ANY of these values - manufacturer_id: Optional[Set[int]] = None + manufacturer_id: set[int] | None = None # [optional] the node's product_id must match ANY of these values - product_id: Optional[Set[int]] = None + product_id: set[int] | None = None # [optional] the node's product_type must match ANY of these values - product_type: Optional[Set[int]] = None + product_type: set[int] | None = None # [optional] the node's firmware_version must match ANY of these values - firmware_version: Optional[Set[str]] = None + firmware_version: set[str] | None = None # [optional] the node's basic device class must match ANY of these values - device_class_basic: Optional[Set[Union[str, int]]] = None + device_class_basic: set[str | int] | None = None # [optional] the node's generic device class must match ANY of these values - device_class_generic: Optional[Set[Union[str, int]]] = None + device_class_generic: set[str | int] | None = None # [optional] the node's specific device class must match ANY of these values - device_class_specific: Optional[Set[Union[str, int]]] = None + device_class_specific: set[str | int] | None = None # [optional] additional values that ALL need to be present on the node for this scheme to pass - required_values: Optional[List[ZWaveValueDiscoverySchema]] = None + required_values: list[ZWaveValueDiscoverySchema] | None = None # [optional] additional values that MAY NOT be present on the node for this scheme to pass - absent_values: Optional[List[ZWaveValueDiscoverySchema]] = None + absent_values: list[ZWaveValueDiscoverySchema] | None = None # [optional] bool to specify if this primary value may be discovered by multiple platforms allow_multi: bool = False @@ -487,7 +488,7 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool: @callback def check_device_class( - device_class: DeviceClassItem, required_value: Optional[Set[Union[str, int]]] + device_class: DeviceClassItem, required_value: set[str | int] | None ) -> bool: """Check if device class id or label matches.""" if required_value is None: diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 34b5940bfead4f..854fbe45039474 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,7 +1,7 @@ """Generic Z-Wave Entity Class.""" +from __future__ import annotations import logging -from typing import List, Optional, Union from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.value import Value as ZwaveValue, get_value_id @@ -97,8 +97,8 @@ def device_info(self) -> dict: def generate_name( self, include_value_name: bool = False, - alternate_value_name: Optional[str] = None, - additional_info: Optional[List[str]] = None, + alternate_value_name: str | None = None, + additional_info: list[str] | None = None, ) -> str: """Generate entity name.""" if additional_info is None: @@ -167,13 +167,13 @@ def _value_changed(self, event_data: dict) -> None: @callback def get_zwave_value( self, - value_property: Union[str, int], - command_class: Optional[int] = None, - endpoint: Optional[int] = None, - value_property_key: Optional[int] = None, + value_property: str | int, + command_class: int | None = None, + endpoint: int | None = None, + value_property_key: int | None = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, - ) -> Optional[ZwaveValue]: + ) -> ZwaveValue | None: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted return_value = None diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index ea17fbe4cff843..100e400f9f70ae 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -1,6 +1,8 @@ """Support for Z-Wave fans.""" +from __future__ import annotations + import math -from typing import Any, Callable, List, Optional +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient @@ -36,7 +38,7 @@ async def async_setup_entry( @callback def async_add_fan(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave fan.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZwaveFan(config_entry, client, info)) async_add_entities(entities) @@ -52,7 +54,7 @@ def async_add_fan(info: ZwaveDiscoveryInfo) -> None: class ZwaveFan(ZWaveBaseEntity, FanEntity): """Representation of a Z-Wave fan.""" - async def async_set_percentage(self, percentage: Optional[int]) -> None: + async def async_set_percentage(self, percentage: int | None) -> None: """Set the speed percentage of the fan.""" target_value = self.get_zwave_value("targetValue") @@ -68,9 +70,9 @@ async def async_set_percentage(self, percentage: Optional[int]) -> None: async def async_turn_on( self, - speed: Optional[str] = None, - percentage: Optional[int] = None, - preset_mode: Optional[str] = None, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, **kwargs: Any, ) -> None: """Turn the device on.""" @@ -82,7 +84,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self.info.node.async_set_value(target_value, 0) @property - def is_on(self) -> Optional[bool]: # type: ignore + def is_on(self) -> bool | None: # type: ignore """Return true if device is on (speed above 0).""" if self.info.primary_value.value is None: # guard missing value @@ -90,7 +92,7 @@ def is_on(self) -> Optional[bool]: # type: ignore return bool(self.info.primary_value.value > 0) @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self.info.primary_value.value is None: # guard missing value diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 16baeb816c2d0b..d535a22394cd94 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -1,5 +1,7 @@ """Helper functions for Z-Wave JS integration.""" -from typing import List, Tuple, cast +from __future__ import annotations + +from typing import cast from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.node import Node as ZwaveNode @@ -19,13 +21,13 @@ def get_unique_id(home_id: str, value_id: str) -> str: @callback -def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]: +def get_device_id(client: ZwaveClient, node: ZwaveNode) -> tuple[str, str]: """Get device registry identifier for Z-Wave node.""" return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}") @callback -def get_home_and_node_id_from_device_id(device_id: Tuple[str, str]) -> List[str]: +def get_home_and_node_id_from_device_id(device_id: tuple[str, str]) -> list[str]: """ Get home ID and node ID for Z-Wave device registry entry. diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index d66821c9ba3797..6b42c0ef08ebb0 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -1,6 +1,8 @@ """Support for Z-Wave lights.""" +from __future__ import annotations + import logging -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ColorComponent, CommandClass @@ -85,9 +87,9 @@ def __init__( self._supports_color = False self._supports_white_value = False self._supports_color_temp = False - self._hs_color: Optional[Tuple[float, float]] = None - self._white_value: Optional[int] = None - self._color_temp: Optional[int] = None + self._hs_color: tuple[float, float] | None = None + self._white_value: int | None = None + self._color_temp: int | None = None self._min_mireds = 153 # 6500K as a safe default self._max_mireds = 370 # 2700K as a safe default self._supported_features = SUPPORT_BRIGHTNESS @@ -126,17 +128,17 @@ def is_on(self) -> bool: return self.brightness > 0 @property - def hs_color(self) -> Optional[Tuple[float, float]]: + def hs_color(self) -> tuple[float, float] | None: """Return the hs color.""" return self._hs_color @property - def white_value(self) -> Optional[int]: + def white_value(self) -> int | None: """Return the white value of this light between 0..255.""" return self._white_value @property - def color_temp(self) -> Optional[int]: + def color_temp(self) -> int | None: """Return the color temperature.""" return self._color_temp @@ -220,7 +222,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION)) - async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None: + async def _async_set_colors(self, colors: dict[ColorComponent, int]) -> None: """Set (multiple) defined colors to given value(s).""" # prefer the (new) combined color property # https://github.com/zwave-js/node-zwave-js/pull/1782 @@ -258,7 +260,7 @@ async def _async_set_color(self, color: ColorComponent, new_value: int) -> None: await self.info.node.async_set_value(target_zwave_value, new_value) async def _async_set_brightness( - self, brightness: Optional[int], transition: Optional[int] = None + self, brightness: int | None, transition: int | None = None ) -> None: """Set new brightness to light.""" if brightness is None: @@ -273,9 +275,7 @@ async def _async_set_brightness( # setting a value requires setting targetValue await self.info.node.async_set_value(self._target_value, zwave_brightness) - async def _async_set_transition_duration( - self, duration: Optional[int] = None - ) -> None: + async def _async_set_transition_duration(self, duration: int | None = None) -> None: """Set the transition time for the brightness value.""" if self._dimming_duration is None: return diff --git a/homeassistant/components/zwave_js/lock.py b/homeassistant/components/zwave_js/lock.py index 6f2a1a72c7d93d..0647885345b678 100644 --- a/homeassistant/components/zwave_js/lock.py +++ b/homeassistant/components/zwave_js/lock.py @@ -1,6 +1,8 @@ """Representation of Z-Wave locks.""" +from __future__ import annotations + import logging -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient @@ -28,7 +30,7 @@ LOGGER = logging.getLogger(__name__) -STATE_TO_ZWAVE_MAP: Dict[int, Dict[str, Union[int, bool]]] = { +STATE_TO_ZWAVE_MAP: dict[int, dict[str, int | bool]] = { CommandClass.DOOR_LOCK: { STATE_UNLOCKED: DoorLockMode.UNSECURED, STATE_LOCKED: DoorLockMode.SECURED, @@ -52,7 +54,7 @@ async def async_setup_entry( @callback def async_add_lock(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Lock.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZWaveLock(config_entry, client, info)) async_add_entities(entities) @@ -88,7 +90,7 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity): """Representation of a Z-Wave lock.""" @property - def is_locked(self) -> Optional[bool]: + def is_locked(self) -> bool | None: """Return true if the lock is locked.""" if self.info.primary_value.value is None: # guard missing value @@ -100,7 +102,7 @@ def is_locked(self) -> Optional[bool]: ) == int(self.info.primary_value.value) async def _set_lock_state( - self, target_state: str, **kwargs: Dict[str, Any] + self, target_state: str, **kwargs: dict[str, Any] ) -> None: """Set the lock state.""" target_value: ZwaveValue = self.get_zwave_value( @@ -112,11 +114,11 @@ async def _set_lock_state( STATE_TO_ZWAVE_MAP[self.info.primary_value.command_class][target_state], ) - async def async_lock(self, **kwargs: Dict[str, Any]) -> None: + async def async_lock(self, **kwargs: dict[str, Any]) -> None: """Lock the lock.""" await self._set_lock_state(STATE_LOCKED) - async def async_unlock(self, **kwargs: Dict[str, Any]) -> None: + async def async_unlock(self, **kwargs: dict[str, Any]) -> None: """Unlock the lock.""" await self._set_lock_state(STATE_UNLOCKED) diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index 49c18073de5897..016aa3066d7f76 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -1,6 +1,7 @@ """Functions used to migrate unique IDs for Z-Wave JS entities.""" +from __future__ import annotations + import logging -from typing import List from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.model.value import Value as ZwaveValue @@ -85,7 +86,7 @@ def async_migrate_discovered_value( @callback -def get_old_value_ids(value: ZwaveValue) -> List[str]: +def get_old_value_ids(value: ZwaveValue) -> list[str]: """Get old value IDs so we can migrate entity unique ID.""" value_ids = [] diff --git a/homeassistant/components/zwave_js/number.py b/homeassistant/components/zwave_js/number.py index 8f8e894cda211d..f418ee3d35b4a1 100644 --- a/homeassistant/components/zwave_js/number.py +++ b/homeassistant/components/zwave_js/number.py @@ -1,5 +1,7 @@ """Support for Z-Wave controls using the number platform.""" -from typing import Callable, List, Optional +from __future__ import annotations + +from typing import Callable from zwave_js_server.client import Client as ZwaveClient @@ -22,7 +24,7 @@ async def async_setup_entry( @callback def async_add_number(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave number entity.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] entities.append(ZwaveNumberEntity(config_entry, client, info)) async_add_entities(entities) @@ -66,14 +68,14 @@ def max_value(self) -> float: return float(self.info.primary_value.metadata.max) @property - def value(self) -> Optional[float]: # type: ignore + def value(self) -> float | None: # type: ignore """Return the entity value.""" if self.info.primary_value.value is None: return None return float(self.info.primary_value.value) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity, if any.""" if self.info.primary_value.metadata.unit is None: return None diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index d6cc9aabd49fd9..ada6f6b5d063d5 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -1,7 +1,8 @@ """Representation of Z-Wave sensors.""" +from __future__ import annotations import logging -from typing import Callable, Dict, List, Optional +from typing import Callable from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import CommandClass @@ -39,7 +40,7 @@ async def async_setup_entry( @callback def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Sensor.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "string_sensor": entities.append(ZWaveStringSensor(config_entry, client, info)) @@ -80,7 +81,7 @@ def __init__( self._name = self.generate_name(include_value_name=True) @property - def device_class(self) -> Optional[str]: + def device_class(self) -> str | None: """Return the device class of the sensor.""" if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY @@ -122,14 +123,14 @@ class ZWaveStringSensor(ZwaveSensorBase): """Representation of a Z-Wave String sensor.""" @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return state of the sensor.""" if self.info.primary_value.value is None: return None return str(self.info.primary_value.value) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return unit of measurement the value is expressed in.""" if self.info.primary_value.metadata.unit is None: return None @@ -161,7 +162,7 @@ def state(self) -> float: return round(float(self.info.primary_value.value), 2) @property - def unit_of_measurement(self) -> Optional[str]: + def unit_of_measurement(self) -> str | None: """Return unit of measurement the value is expressed in.""" if self.info.primary_value.metadata.unit is None: return None @@ -191,7 +192,7 @@ def __init__( ) @property - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return state of the sensor.""" if self.info.primary_value.value is None: return None @@ -205,7 +206,7 @@ def state(self) -> Optional[str]: ) @property - def extra_state_attributes(self) -> Optional[Dict[str, str]]: + def extra_state_attributes(self) -> dict[str, str] | None: """Return the device specific state attributes.""" # add the value's int value as property for multi-value (list) items return {"value": self.info.primary_value.value} diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index c971891b35bedc..1944e4b3dd0f5e 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -1,7 +1,7 @@ """Methods and classes related to executing Z-Wave commands and publishing these to hass.""" +from __future__ import annotations import logging -from typing import Dict, Set, Union import voluptuous as vol from zwave_js_server.model.node import Node as ZwaveNode @@ -20,8 +20,8 @@ def parameter_name_does_not_need_bitmask( - val: Dict[str, Union[int, str]] -) -> Dict[str, Union[int, str]]: + val: dict[str, int | str] +) -> dict[str, int | str]: """Validate that if a parameter name is provided, bitmask is not as well.""" if isinstance(val[const.ATTR_CONFIG_PARAMETER], str) and ( val.get(const.ATTR_CONFIG_PARAMETER_BITMASK) @@ -88,7 +88,7 @@ def async_register(self) -> None: async def async_set_config_parameter(self, service: ServiceCall) -> None: """Set a config value on a node.""" - nodes: Set[ZwaveNode] = set() + nodes: set[ZwaveNode] = set() if ATTR_ENTITY_ID in service.data: nodes |= { async_get_node_from_entity_id(self._hass, entity_id) diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index a325e9821f7374..e64ea57703d81b 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -1,7 +1,8 @@ """Representation of Z-Wave switches.""" +from __future__ import annotations import logging -from typing import Any, Callable, List, Optional +from typing import Any, Callable from zwave_js_server.client import Client as ZwaveClient @@ -30,7 +31,7 @@ async def async_setup_entry( @callback def async_add_switch(info: ZwaveDiscoveryInfo) -> None: """Add Z-Wave Switch.""" - entities: List[ZWaveBaseEntity] = [] + entities: list[ZWaveBaseEntity] = [] if info.platform_hint == "barrier_event_signaling_state": entities.append( ZWaveBarrierEventSignalingSwitch(config_entry, client, info) @@ -53,7 +54,7 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity): """Representation of a Z-Wave switch.""" @property - def is_on(self) -> Optional[bool]: # type: ignore + def is_on(self) -> bool | None: # type: ignore """Return a boolean for the state of the switch.""" if self.info.primary_value.value is None: # guard missing value @@ -85,7 +86,7 @@ def __init__( """Initialize a ZWaveBarrierEventSignalingSwitch entity.""" super().__init__(config_entry, client, info) self._name = self.generate_name(include_value_name=True) - self._state: Optional[bool] = None + self._state: bool | None = None self._update_state() @@ -100,7 +101,7 @@ def name(self) -> str: return self._name @property - def is_on(self) -> Optional[bool]: # type: ignore + def is_on(self) -> bool | None: # type: ignore """Return a boolean for the state of the switch.""" return self._state From 54d1e9985feb4c7b9418414cd0e4c4fa9e9fe18b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 15:13:22 +0100 Subject: [PATCH 1362/1818] Update typing 15 (#48079) --- tests/common.py | 6 ++- tests/components/apache_kafka/test_init.py | 6 ++- tests/components/bond/common.py | 24 ++++++------ tests/components/bond/test_config_flow.py | 8 ++-- tests/components/bond/test_fan.py | 7 ++-- tests/components/cast/test_media_player.py | 9 +++-- tests/components/climate/test_init.py | 5 ++- tests/components/cloudflare/__init__.py | 5 ++- tests/components/deconz/conftest.py | 4 +- tests/components/directv/test_media_player.py | 25 ++++++------ tests/components/dyson/common.py | 6 +-- tests/components/dyson/test_climate.py | 5 +-- tests/components/dyson/test_fan.py | 4 +- tests/components/dyson/test_sensor.py | 7 ++-- tests/components/heos/conftest.py | 12 +++--- tests/components/homekit/test_homekit.py | 5 ++- tests/components/hyperion/__init__.py | 26 ++++++------- tests/components/hyperion/test_config_flow.py | 12 +++--- tests/components/hyperion/test_light.py | 5 ++- tests/components/influxdb/test_sensor.py | 7 ++-- tests/components/litterrobot/conftest.py | 7 ++-- .../components/mysensors/test_config_flow.py | 13 ++++--- tests/components/mysensors/test_init.py | 5 ++- .../components/seventeentrack/test_sensor.py | 5 ++- tests/components/sharkiq/test_vacuum.py | 12 +++--- tests/components/shell_command/test_init.py | 4 +- tests/components/slack/test_notify.py | 5 ++- tests/components/switcher_kis/conftest.py | 5 ++- tests/components/tplink/test_init.py | 11 ++---- tests/components/twinkly/test_twinkly.py | 5 ++- tests/components/unifi/conftest.py | 5 ++- tests/components/vera/common.py | 20 +++++----- tests/components/vera/test_sensor.py | 6 ++- tests/components/vizio/test_media_player.py | 26 +++++++------ tests/components/withings/common.py | 39 ++++++++----------- 35 files changed, 190 insertions(+), 166 deletions(-) diff --git a/tests/common.py b/tests/common.py index df8f8fd2feaa9e..451eee5f99770c 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,4 +1,6 @@ """Test the helper method for writing tests.""" +from __future__ import annotations + import asyncio import collections from collections import OrderedDict @@ -14,7 +16,7 @@ import time from time import monotonic import types -from typing import Any, Awaitable, Collection, Optional +from typing import Any, Awaitable, Collection from unittest.mock import AsyncMock, Mock, patch import uuid @@ -197,7 +199,7 @@ async def async_wait_for_task_count(self, max_remaining_tasks: int = 0) -> None: """ # To flush out any call_soon_threadsafe await asyncio.sleep(0) - start_time: Optional[float] = None + start_time: float | None = None while len(self._pending_tasks) > max_remaining_tasks: pending = [ diff --git a/tests/components/apache_kafka/test_init.py b/tests/components/apache_kafka/test_init.py index 7e793bce96af35..c4285a0cc652d7 100644 --- a/tests/components/apache_kafka/test_init.py +++ b/tests/components/apache_kafka/test_init.py @@ -1,7 +1,9 @@ """The tests for the Apache Kafka component.""" +from __future__ import annotations + from asyncio import AbstractEventLoop from dataclasses import dataclass -from typing import Callable, Type +from typing import Callable from unittest.mock import patch import pytest @@ -31,7 +33,7 @@ class FilterTest: class MockKafkaClient: """Mock of the Apache Kafka client for testing.""" - init: Callable[[Type[AbstractEventLoop], str, str], None] + init: Callable[[type[AbstractEventLoop], str, str], None] start: Callable[[], None] send_and_wait: Callable[[str, str], None] diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 061dc23797e603..d8adf134293311 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -1,8 +1,10 @@ """Common methods used across tests for Bond.""" +from __future__ import annotations + from asyncio import TimeoutError as AsyncIOTimeoutError from contextlib import nullcontext from datetime import timedelta -from typing import Any, Dict, Optional +from typing import Any from unittest.mock import MagicMock, patch from homeassistant import core @@ -54,14 +56,14 @@ async def setup_bond_entity( async def setup_platform( hass: core.HomeAssistant, platform: str, - discovered_device: Dict[str, Any], + discovered_device: dict[str, Any], *, bond_device_id: str = "bond-device-id", - bond_version: Dict[str, Any] = None, - props: Dict[str, Any] = None, - state: Dict[str, Any] = None, - bridge: Dict[str, Any] = None, - token: Dict[str, Any] = None, + bond_version: dict[str, Any] = None, + props: dict[str, Any] = None, + state: dict[str, Any] = None, + bridge: dict[str, Any] = None, + token: dict[str, Any] = None, ): """Set up the specified Bond platform.""" mock_entry = MockConfigEntry( @@ -89,7 +91,7 @@ async def setup_platform( def patch_bond_version( - enabled: bool = True, return_value: Optional[dict] = None, side_effect=None + enabled: bool = True, return_value: dict | None = None, side_effect=None ): """Patch Bond API version endpoint.""" if not enabled: @@ -106,7 +108,7 @@ def patch_bond_version( def patch_bond_bridge( - enabled: bool = True, return_value: Optional[dict] = None, side_effect=None + enabled: bool = True, return_value: dict | None = None, side_effect=None ): """Patch Bond API bridge endpoint.""" if not enabled: @@ -127,7 +129,7 @@ def patch_bond_bridge( def patch_bond_token( - enabled: bool = True, return_value: Optional[dict] = None, side_effect=None + enabled: bool = True, return_value: dict | None = None, side_effect=None ): """Patch Bond API token endpoint.""" if not enabled: @@ -203,7 +205,7 @@ def patch_bond_device_state(return_value=None, side_effect=None): async def help_test_entity_available( - hass: core.HomeAssistant, domain: str, device: Dict[str, Any], entity_id: str + hass: core.HomeAssistant, domain: str, device: dict[str, Any], entity_id: str ): """Run common test to verify available property.""" await setup_platform(hass, domain, device) diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 39fd1a2db5dcb0..b77c316f15252a 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -1,5 +1,7 @@ """Test the Bond config flow.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from unittest.mock import Mock, patch from aiohttp import ClientConnectionError, ClientResponseError @@ -334,8 +336,8 @@ async def _help_test_form_unexpected_error( hass: core.HomeAssistant, *, source: str, - initial_input: Dict[str, Any] = None, - user_input: Dict[str, Any], + initial_input: dict[str, Any] = None, + user_input: dict[str, Any], error: Exception, ): """Test we handle unexpected error gracefully.""" diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index ca3bc9ac7e796c..bd5994f51824e4 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -1,6 +1,7 @@ """Tests for the Bond fan device.""" +from __future__ import annotations + from datetime import timedelta -from typing import Optional from bond_api import Action, DeviceType, Direction @@ -44,8 +45,8 @@ def ceiling_fan(name: str): async def turn_fan_on( hass: core.HomeAssistant, fan_id: str, - speed: Optional[str] = None, - percentage: Optional[int] = None, + speed: str | None = None, + percentage: int | None = None, ) -> None: """Turn the fan on at the specified speed.""" service_data = {ATTR_ENTITY_ID: fan_id} diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index a3d5676a878251..8a6f84580d2208 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -1,7 +1,8 @@ """The tests for the Cast Media player platform.""" # pylint: disable=protected-access +from __future__ import annotations + import json -from typing import Optional from unittest.mock import ANY, MagicMock, Mock, patch from uuid import UUID @@ -50,14 +51,14 @@ def get_fake_chromecast(info: ChromecastInfo): def get_fake_chromecast_info( - host="192.168.178.42", port=8009, uuid: Optional[UUID] = FakeUUID + host="192.168.178.42", port=8009, uuid: UUID | None = FakeUUID ): """Generate a Fake ChromecastInfo with the specified arguments.""" @attr.s(slots=True, frozen=True, eq=False) class ExtendedChromecastInfo(ChromecastInfo): - host: Optional[str] = attr.ib(default=None) - port: Optional[int] = attr.ib(default=0) + host: str | None = attr.ib(default=None) + port: int | None = attr.ib(default=0) def __eq__(self, other): if isinstance(other, ChromecastInfo): diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 8113c1e343a3d0..9473e61a165a4f 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1,5 +1,6 @@ """The tests for the climate component.""" -from typing import List +from __future__ import annotations + from unittest.mock import MagicMock import pytest @@ -58,7 +59,7 @@ def hvac_mode(self) -> str: return HVAC_MODE_HEAT @property - def hvac_modes(self) -> List[str]: + def hvac_modes(self) -> list[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. diff --git a/tests/components/cloudflare/__init__.py b/tests/components/cloudflare/__init__.py index c72a9cd84b0126..60ce0f055d50f7 100644 --- a/tests/components/cloudflare/__init__.py +++ b/tests/components/cloudflare/__init__.py @@ -1,5 +1,6 @@ """Tests for the Cloudflare integration.""" -from typing import List +from __future__ import annotations + from unittest.mock import AsyncMock, patch from pycfdns import CFRecord @@ -71,7 +72,7 @@ async def init_integration( def _get_mock_cfupdate( zone: str = MOCK_ZONE, zone_id: str = MOCK_ZONE_ID, - records: List = MOCK_ZONE_RECORDS, + records: list = MOCK_ZONE_RECORDS, ): client = AsyncMock() diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index a7825b80ea2192..7b2c691bcae7ea 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,6 +1,6 @@ """deconz conftest.""" +from __future__ import annotations -from typing import Optional from unittest.mock import patch import pytest @@ -13,7 +13,7 @@ def mock_deconz_websocket(): """No real websocket allowed.""" with patch("pydeconz.gateway.WSClient") as mock: - async def make_websocket_call(data: Optional[dict] = None, state: str = ""): + async def make_websocket_call(data: dict | None = None, state: str = ""): """Generate a websocket call.""" pydeconz_gateway_session_handler = mock.call_args[0][3] diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index aeaa245161dcba..8e7fad62c8943f 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -1,6 +1,7 @@ """The tests for the DirecTV Media player platform.""" +from __future__ import annotations + from datetime import datetime, timedelta -from typing import Optional from unittest.mock import patch from pytest import fixture @@ -77,24 +78,20 @@ def mock_now() -> datetime: return dt_util.utcnow() -async def async_turn_on( - hass: HomeAssistantType, entity_id: Optional[str] = None -) -> None: +async def async_turn_on(hass: HomeAssistantType, entity_id: str | None = None) -> None: """Turn on specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} await hass.services.async_call(MP_DOMAIN, SERVICE_TURN_ON, data) -async def async_turn_off( - hass: HomeAssistantType, entity_id: Optional[str] = None -) -> None: +async def async_turn_off(hass: HomeAssistantType, entity_id: str | None = None) -> None: """Turn off specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} await hass.services.async_call(MP_DOMAIN, SERVICE_TURN_OFF, data) async def async_media_pause( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -102,7 +99,7 @@ async def async_media_pause( async def async_media_play( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for play/pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -110,7 +107,7 @@ async def async_media_play( async def async_media_stop( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for stop.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -118,7 +115,7 @@ async def async_media_stop( async def async_media_next_track( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for next track.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -126,7 +123,7 @@ async def async_media_next_track( async def async_media_previous_track( - hass: HomeAssistantType, entity_id: Optional[str] = None + hass: HomeAssistantType, entity_id: str | None = None ) -> None: """Send the media player the command for prev track.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} @@ -137,8 +134,8 @@ async def async_play_media( hass: HomeAssistantType, media_type: str, media_id: str, - entity_id: Optional[str] = None, - enqueue: Optional[str] = None, + entity_id: str | None = None, + enqueue: str | None = None, ) -> None: """Send the media player the command for playing media.""" data = {ATTR_MEDIA_CONTENT_TYPE: media_type, ATTR_MEDIA_CONTENT_ID: media_id} diff --git a/tests/components/dyson/common.py b/tests/components/dyson/common.py index b26c48d55f8378..4fde47183d2b6a 100644 --- a/tests/components/dyson/common.py +++ b/tests/components/dyson/common.py @@ -1,6 +1,6 @@ """Common utils for Dyson tests.""" +from __future__ import annotations -from typing import Optional, Type from unittest import mock from unittest.mock import MagicMock @@ -37,7 +37,7 @@ @callback -def async_get_basic_device(spec: Type[DysonDevice]) -> DysonDevice: +def async_get_basic_device(spec: type[DysonDevice]) -> DysonDevice: """Return a basic device with common fields filled out.""" device = MagicMock(spec=spec) device.serial = SERIAL @@ -88,7 +88,7 @@ def async_get_purecool_device() -> DysonPureCool: async def async_update_device( - hass: HomeAssistant, device: DysonDevice, state_type: Optional[Type] = None + hass: HomeAssistant, device: DysonDevice, state_type: type | None = None ) -> None: """Update the device using callback function.""" callbacks = [args[0][0] for args in device.add_message_listener.call_args_list] diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index 90d91092dca702..2591b90f596e8e 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -1,6 +1,5 @@ """Test the Dyson fan component.""" - -from typing import Type +from __future__ import annotations from libpurecool.const import ( AutoMode, @@ -74,7 +73,7 @@ @callback -def async_get_device(spec: Type[DysonDevice]) -> DysonDevice: +def async_get_device(spec: type[DysonDevice]) -> DysonDevice: """Return a Dyson climate device.""" device = async_get_basic_device(spec) device.state.heat_target = 2900 diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index eb83f319c1f4cb..67149ff7f2e382 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -1,5 +1,5 @@ """Test the Dyson fan component.""" -from typing import Type +from __future__ import annotations from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation from libpurecool.dyson_pure_cool import DysonPureCool, DysonPureCoolLink @@ -67,7 +67,7 @@ @callback -def async_get_device(spec: Type[DysonPureCoolLink]) -> DysonPureCoolLink: +def async_get_device(spec: type[DysonPureCoolLink]) -> DysonPureCoolLink: """Return a Dyson fan device.""" if spec == DysonPureCoolLink: return async_get_purecoollink_device() diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index a27aeeb99e171d..5bd6fd85c3a3df 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -1,5 +1,6 @@ """Test the Dyson sensor(s) component.""" -from typing import List, Type +from __future__ import annotations + from unittest.mock import patch from libpurecool.dyson_pure_cool import DysonPureCool @@ -79,7 +80,7 @@ def _async_assign_values( @callback -def async_get_device(spec: Type[DysonPureCoolLink], combi=False) -> DysonPureCoolLink: +def async_get_device(spec: type[DysonPureCoolLink], combi=False) -> DysonPureCoolLink: """Return a device of the given type.""" device = async_get_basic_device(spec) _async_assign_values(device, combi=combi) @@ -113,7 +114,7 @@ def _async_get_entity_id(sensor_type: str) -> str: indirect=["device"], ) async def test_sensors( - hass: HomeAssistant, device: DysonPureCoolLink, sensors: List[str] + hass: HomeAssistant, device: DysonPureCoolLink, sensors: list[str] ) -> None: """Test the sensors.""" # Temperature is given by the device in kelvin diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index fa7615e2de8ba9..2c48b7fe8e1b13 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -1,5 +1,7 @@ """Configuration for HEOS tests.""" -from typing import Dict, Sequence +from __future__ import annotations + +from typing import Sequence from unittest.mock import Mock, patch as patch from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const @@ -86,7 +88,7 @@ def player_fixture(quick_selects): @pytest.fixture(name="favorites") -def favorites_fixture() -> Dict[int, HeosSource]: +def favorites_fixture() -> dict[int, HeosSource]: """Create favorites fixture.""" station = Mock(HeosSource) station.type = const.TYPE_STATION @@ -131,7 +133,7 @@ def discovery_data_fixture() -> dict: @pytest.fixture(name="quick_selects") -def quick_selects_fixture() -> Dict[int, str]: +def quick_selects_fixture() -> dict[int, str]: """Create a dict of quick selects for testing.""" return { 1: "Quick Select 1", @@ -153,12 +155,12 @@ def playlists_fixture() -> Sequence[HeosSource]: @pytest.fixture(name="change_data") -def change_data_fixture() -> Dict: +def change_data_fixture() -> dict: """Create player change data for testing.""" return {const.DATA_MAPPED_IDS: {}, const.DATA_NEW: []} @pytest.fixture(name="change_data_mapped_ids") -def change_data_mapped_ids_fixture() -> Dict: +def change_data_mapped_ids_fixture() -> dict: """Create player change data for testing.""" return {const.DATA_MAPPED_IDS: {101: 1}, const.DATA_NEW: []} diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 5895aae351ef0f..fd7d74aeaba5c1 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,7 +1,8 @@ """Tests for the HomeKit component.""" +from __future__ import annotations + import asyncio import os -from typing import Dict from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch from pyhap.accessory import Accessory @@ -863,7 +864,7 @@ async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): await hass.async_block_till_done() -def _write_data(path: str, data: Dict) -> None: +def _write_data(path: str, data: dict) -> None: """Write the data.""" if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index 954d9abc129d75..e811a4dde32016 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations from types import TracebackType -from typing import Any, Dict, Optional, Type +from typing import Any from unittest.mock import AsyncMock, Mock, patch from hyperion import const @@ -30,25 +30,25 @@ TEST_TOKEN = "sekr1t" TEST_CONFIG_ENTRY_ID = "74565ad414754616000674c87bdc876c" -TEST_CONFIG_ENTRY_OPTIONS: Dict[str, Any] = {CONF_PRIORITY: TEST_PRIORITY} +TEST_CONFIG_ENTRY_OPTIONS: dict[str, Any] = {CONF_PRIORITY: TEST_PRIORITY} -TEST_INSTANCE_1: Dict[str, Any] = { +TEST_INSTANCE_1: dict[str, Any] = { "friendly_name": "Test instance 1", "instance": 1, "running": True, } -TEST_INSTANCE_2: Dict[str, Any] = { +TEST_INSTANCE_2: dict[str, Any] = { "friendly_name": "Test instance 2", "instance": 2, "running": True, } -TEST_INSTANCE_3: Dict[str, Any] = { +TEST_INSTANCE_3: dict[str, Any] = { "friendly_name": "Test instance 3", "instance": 3, "running": True, } -TEST_AUTH_REQUIRED_RESP: Dict[str, Any] = { +TEST_AUTH_REQUIRED_RESP: dict[str, Any] = { "command": "authorize-tokenRequired", "info": { "required": True, @@ -66,16 +66,16 @@ class AsyncContextManagerMock(Mock): """An async context manager mock for Hyperion.""" - async def __aenter__(self) -> Optional[AsyncContextManagerMock]: + async def __aenter__(self) -> AsyncContextManagerMock | None: """Enter context manager and connect the client.""" result = await self.async_client_connect() return self if result else None async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: TracebackType | None, ) -> None: """Leave context manager and disconnect the client.""" await self.async_client_disconnect() @@ -118,7 +118,7 @@ def create_mock_client() -> Mock: def add_test_config_entry( - hass: HomeAssistantType, data: Optional[Dict[str, Any]] = None + hass: HomeAssistantType, data: dict[str, Any] | None = None ) -> ConfigEntry: """Add a test config entry.""" config_entry: MockConfigEntry = MockConfigEntry( # type: ignore[no-untyped-call] @@ -139,8 +139,8 @@ def add_test_config_entry( async def setup_test_config_entry( hass: HomeAssistantType, - config_entry: Optional[ConfigEntry] = None, - hyperion_client: Optional[Mock] = None, + config_entry: ConfigEntry | None = None, + hyperion_client: Mock | None = None, ) -> ConfigEntry: """Add a test Hyperion entity to hass.""" config_entry = config_entry or add_test_config_entry(hass) diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index beb642792c9866..15bca12b03f1e7 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -1,5 +1,7 @@ """Tests for the Hyperion config flow.""" -from typing import Any, Dict, Optional +from __future__ import annotations + +from typing import Any from unittest.mock import AsyncMock, patch from hyperion import const @@ -40,7 +42,7 @@ from tests.common import MockConfigEntry TEST_IP_ADDRESS = "192.168.0.1" -TEST_HOST_PORT: Dict[str, Any] = { +TEST_HOST_PORT: dict[str, Any] = { CONF_HOST: TEST_HOST, CONF_PORT: TEST_PORT, } @@ -122,7 +124,7 @@ async def _create_mock_entry(hass: HomeAssistantType) -> MockConfigEntry: async def _init_flow( hass: HomeAssistantType, source: str = SOURCE_USER, - data: Optional[Dict[str, Any]] = None, + data: dict[str, Any] | None = None, ) -> Any: """Initialize a flow.""" data = data or {} @@ -133,7 +135,7 @@ async def _init_flow( async def _configure_flow( - hass: HomeAssistantType, result: Dict, user_input: Optional[Dict[str, Any]] = None + hass: HomeAssistantType, result: dict, user_input: dict[str, Any] | None = None ) -> Any: """Provide input to a flow.""" user_input = user_input or {} @@ -528,7 +530,7 @@ async def test_ssdp_failure_bad_port_json(hass: HomeAssistantType) -> None: """Check an SSDP flow with bad json port.""" client = create_mock_client() - bad_data: Dict[str, Any] = {**TEST_SSDP_SERVICE_INFO} + bad_data: dict[str, Any] = {**TEST_SSDP_SERVICE_INFO} bad_data["ports"]["jsonServer"] = "not_a_port" with patch( diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 94f41aa4fd005e..e83f50599391e8 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -1,5 +1,6 @@ """Tests for the Hyperion integration.""" -from typing import Optional +from __future__ import annotations + from unittest.mock import AsyncMock, Mock, call, patch from hyperion import const @@ -56,7 +57,7 @@ def _get_config_entry_from_unique_id( hass: HomeAssistantType, unique_id: str -) -> Optional[ConfigEntry]: +) -> ConfigEntry | None: for entry in hass.config_entries.async_entries(domain=DOMAIN): if TEST_SYSINFO_ID == entry.unique_id: return entry diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index 57983d14abab86..55172011f4fb4f 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -1,7 +1,8 @@ """The tests for the InfluxDB sensor.""" +from __future__ import annotations + from dataclasses import dataclass from datetime import timedelta -from typing import Dict, List, Type from unittest.mock import MagicMock, patch from influxdb.exceptions import InfluxDBClientError, InfluxDBServerError @@ -55,14 +56,14 @@ class Record: """Record in a Table.""" - values: Dict + values: dict @dataclass class Table: """Table in an Influx 2 resultset.""" - records: List[Type[Record]] + records: list[type[Record]] @pytest.fixture(name="mock_client") diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index aadf7d810aa5d2..11ed66fcb5297c 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,5 +1,6 @@ """Configure pytest for Litter-Robot tests.""" -from typing import Optional +from __future__ import annotations + from unittest.mock import AsyncMock, MagicMock, patch import pylitterbot @@ -13,7 +14,7 @@ from tests.common import MockConfigEntry -def create_mock_robot(unit_status_code: Optional[str] = None): +def create_mock_robot(unit_status_code: str | None = None): """Create a mock Litter-Robot device.""" if not ( unit_status_code @@ -32,7 +33,7 @@ def create_mock_robot(unit_status_code: Optional[str] = None): return robot -def create_mock_account(unit_status_code: Optional[str] = None): +def create_mock_account(unit_status_code: str | None = None): """Create a mock Litter-Robot account.""" account = MagicMock(spec=pylitterbot.Account) account.connect = AsyncMock() diff --git a/tests/components/mysensors/test_config_flow.py b/tests/components/mysensors/test_config_flow.py index 5fd9e3e7ea1f0b..e4c4016d11ac12 100644 --- a/tests/components/mysensors/test_config_flow.py +++ b/tests/components/mysensors/test_config_flow.py @@ -1,5 +1,6 @@ """Test the MySensors config flow.""" -from typing import Dict, Optional, Tuple +from __future__ import annotations + from unittest.mock import patch import pytest @@ -349,7 +350,7 @@ async def test_config_invalid( hass: HomeAssistantType, gateway_type: ConfGatewayType, expected_step_id: str, - user_input: Dict[str, any], + user_input: dict[str, any], err_field, err_string, ): @@ -420,7 +421,7 @@ async def test_config_invalid( }, ], ) -async def test_import(hass: HomeAssistantType, user_input: Dict): +async def test_import(hass: HomeAssistantType, user_input: dict): """Test importing a gateway.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -712,9 +713,9 @@ async def test_import(hass: HomeAssistantType, user_input: Dict): ) async def test_duplicate( hass: HomeAssistantType, - first_input: Dict, - second_input: Dict, - expected_result: Optional[Tuple[str, str]], + first_input: dict, + second_input: dict, + expected_result: tuple[str, str] | None, ): """Test duplicate detection.""" await setup.async_setup_component(hass, "persistent_notification", {}) diff --git a/tests/components/mysensors/test_init.py b/tests/components/mysensors/test_init.py index 2775b73efd6299..c85c627df9ff04 100644 --- a/tests/components/mysensors/test_init.py +++ b/tests/components/mysensors/test_init.py @@ -1,5 +1,6 @@ """Test function in __init__.py.""" -from typing import Dict +from __future__ import annotations + from unittest.mock import patch import pytest @@ -229,7 +230,7 @@ async def test_import( config: ConfigType, expected_calls: int, expected_to_succeed: bool, - expected_config_flow_user_input: Dict[str, any], + expected_config_flow_user_input: dict[str, any], ): """Test importing a gateway.""" with patch("sys.platform", "win32"), patch( diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 6519b435c0ad48..65e40d02afff42 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -1,6 +1,7 @@ """Tests for the seventeentrack sensor.""" +from __future__ import annotations + import datetime -from typing import Union from unittest.mock import MagicMock, patch from py17track.package import Package @@ -100,7 +101,7 @@ async def login(self, email: str, password: str) -> bool: return self.__class__.login_result async def packages( - self, package_state: Union[int, str] = "", show_archived: bool = False + self, package_state: int | str = "", show_archived: bool = False ) -> list: """Packages mock.""" return self.__class__.package_list[:] diff --git a/tests/components/sharkiq/test_vacuum.py b/tests/components/sharkiq/test_vacuum.py index fb35d9d4cd2b32..b36359ed31aceb 100644 --- a/tests/components/sharkiq/test_vacuum.py +++ b/tests/components/sharkiq/test_vacuum.py @@ -1,7 +1,9 @@ """Test the Shark IQ vacuum entity.""" +from __future__ import annotations + from copy import deepcopy import enum -from typing import Any, Iterable, List, Optional +from typing import Any, Iterable from unittest.mock import patch import pytest @@ -80,11 +82,11 @@ class MockAyla(AylaApi): async def async_sign_in(self): """Instead of signing in, just return.""" - async def async_list_devices(self) -> List[dict]: + async def async_list_devices(self) -> list[dict]: """Return the device list.""" return [SHARK_DEVICE_DICT] - async def async_get_devices(self, update: bool = True) -> List[SharkIqVacuum]: + async def async_get_devices(self, update: bool = True) -> list[SharkIqVacuum]: """Get the list of devices.""" shark = MockShark(self, SHARK_DEVICE_DICT) shark.properties_full = deepcopy(SHARK_PROPERTIES_DICT) @@ -98,7 +100,7 @@ async def async_request(self, http_method: str, url: str, **kwargs): class MockShark(SharkIqVacuum): """Mocked SharkIqVacuum that won't hit the API.""" - async def async_update(self, property_list: Optional[Iterable[str]] = None): + async def async_update(self, property_list: Iterable[str] | None = None): """Don't do anything.""" def set_property_value(self, property_name, value): @@ -224,7 +226,7 @@ async def test_locate(hass): ) @patch("sharkiqpy.ayla_api.AylaApi", MockAyla) async def test_coordinator_updates( - hass: HomeAssistant, side_effect: Optional[Exception], success: bool + hass: HomeAssistant, side_effect: Exception | None, success: bool ) -> None: """Test the update coordinator update functions.""" coordinator = hass.data[DOMAIN][ENTRY_ID] diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 928c186bc1147b..d4581ae1fc74cf 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -1,8 +1,8 @@ """The tests for the Shell command component.""" +from __future__ import annotations import os import tempfile -from typing import Tuple from unittest.mock import MagicMock, patch from homeassistant.components import shell_command @@ -12,7 +12,7 @@ def mock_process_creator(error: bool = False): """Mock a coroutine that creates a process when yielded.""" - async def communicate() -> Tuple[bytes, bytes]: + async def communicate() -> tuple[bytes, bytes]: """Mock a coroutine that runs a process when yielded. Returns a tuple of (stdout, stderr). diff --git a/tests/components/slack/test_notify.py b/tests/components/slack/test_notify.py index 9fc6784a09e0eb..6c353cf8fc679a 100644 --- a/tests/components/slack/test_notify.py +++ b/tests/components/slack/test_notify.py @@ -1,7 +1,8 @@ """Test slack notifications.""" +from __future__ import annotations + import copy import logging -from typing import List from unittest.mock import AsyncMock, Mock, patch from _pytest.logging import LogCaptureFixture @@ -39,7 +40,7 @@ } -def filter_log_records(caplog: LogCaptureFixture) -> List[logging.LogRecord]: +def filter_log_records(caplog: LogCaptureFixture) -> list[logging.LogRecord]: """Filter all unrelated log records.""" return [ rec for rec in caplog.records if rec.name.endswith(f"{DOMAIN}.{notify.DOMAIN}") diff --git a/tests/components/switcher_kis/conftest.py b/tests/components/switcher_kis/conftest.py index e7303a20ea5619..fda5f39922d4fd 100644 --- a/tests/components/switcher_kis/conftest.py +++ b/tests/components/switcher_kis/conftest.py @@ -1,8 +1,9 @@ """Common fixtures and objects for the Switcher integration tests.""" +from __future__ import annotations from asyncio import Queue from datetime import datetime -from typing import Any, Generator, Optional +from typing import Any, Generator from unittest.mock import AsyncMock, patch from pytest import fixture @@ -56,7 +57,7 @@ def state(self) -> str: return DUMMY_DEVICE_STATE @property - def remaining_time(self) -> Optional[str]: + def remaining_time(self) -> str | None: """Return the time left to auto-off.""" return DUMMY_REMAINING_TIME diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 8dbe7481339eb4..49309a6ecefbc5 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -1,5 +1,7 @@ """Tests for the TP-Link component.""" -from typing import Any, Dict +from __future__ import annotations + +from typing import Any from unittest.mock import MagicMock, patch from pyHS100 import SmartBulb, SmartDevice, SmartDeviceException, SmartPlug @@ -88,25 +90,20 @@ class UnknownSmartDevice(SmartDevice): @property def has_emeter(self) -> bool: """Do nothing.""" - pass def turn_off(self) -> None: """Do nothing.""" - pass def turn_on(self) -> None: """Do nothing.""" - pass @property def is_on(self) -> bool: """Do nothing.""" - pass @property - def state_information(self) -> Dict[str, Any]: + def state_information(self) -> dict[str, Any]: """Do nothing.""" - pass async def test_configuring_devices_from_multiple_sources(hass): diff --git a/tests/components/twinkly/test_twinkly.py b/tests/components/twinkly/test_twinkly.py index d4afe02c11ba6e..fcbbdb035c7a67 100644 --- a/tests/components/twinkly/test_twinkly.py +++ b/tests/components/twinkly/test_twinkly.py @@ -1,5 +1,6 @@ """Tests for the integration of a twinly device.""" -from typing import Tuple +from __future__ import annotations + from unittest.mock import patch from homeassistant.components.twinkly.const import ( @@ -190,7 +191,7 @@ async def test_unload(hass: HomeAssistant): async def _create_entries( hass: HomeAssistant, client=None -) -> Tuple[RegistryEntry, DeviceEntry, ClientMock]: +) -> tuple[RegistryEntry, DeviceEntry, ClientMock]: client = ClientMock() if client is None else client def get_client_mock(client, _): diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index 83dc99fdaf87cb..81af3f7243f6ed 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -1,5 +1,6 @@ """Fixtures for UniFi methods.""" -from typing import Optional +from __future__ import annotations + from unittest.mock import patch from aiounifi.websocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA @@ -11,7 +12,7 @@ def mock_unifi_websocket(): """No real websocket allowed.""" with patch("aiounifi.controller.WSClient") as mock: - def make_websocket_call(data: Optional[dict] = None, state: str = ""): + def make_websocket_call(data: dict | None = None, state: str = ""): """Generate a websocket call.""" if data: mock.return_value.data = data diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index 43ef154b5884dd..e666d51389731e 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -1,6 +1,8 @@ """Common code for tests.""" +from __future__ import annotations + from enum import Enum -from typing import Callable, Dict, NamedTuple, Tuple +from typing import Callable, NamedTuple from unittest.mock import MagicMock import pyvera as pv @@ -29,7 +31,7 @@ class ControllerData(NamedTuple): class ComponentData(NamedTuple): """Test data about the vera component.""" - controller_data: Tuple[ControllerData] + controller_data: tuple[ControllerData] class ConfigSource(Enum): @@ -43,12 +45,12 @@ class ConfigSource(Enum): class ControllerConfig(NamedTuple): """Test config for mocking a vera controller.""" - config: Dict - options: Dict + config: dict + options: dict config_source: ConfigSource serial_number: str - devices: Tuple[pv.VeraDevice, ...] - scenes: Tuple[pv.VeraScene, ...] + devices: tuple[pv.VeraDevice, ...] + scenes: tuple[pv.VeraScene, ...] setup_callback: SetupCallback legacy_entity_unique_id: bool @@ -58,8 +60,8 @@ def new_simple_controller_config( options: dict = None, config_source=ConfigSource.CONFIG_FLOW, serial_number="1111", - devices: Tuple[pv.VeraDevice, ...] = (), - scenes: Tuple[pv.VeraScene, ...] = (), + devices: tuple[pv.VeraDevice, ...] = (), + scenes: tuple[pv.VeraScene, ...] = (), setup_callback: SetupCallback = None, legacy_entity_unique_id=False, ) -> ControllerConfig: @@ -87,7 +89,7 @@ async def configure_component( self, hass: HomeAssistant, controller_config: ControllerConfig = None, - controller_configs: Tuple[ControllerConfig] = (), + controller_configs: tuple[ControllerConfig] = (), ) -> ComponentData: """Configure the component with multiple specific mock data.""" configs = list(controller_configs) diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index 62639df3a35ae4..566a3db482e3ec 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -1,5 +1,7 @@ """Vera tests.""" -from typing import Any, Callable, Tuple +from __future__ import annotations + +from typing import Any, Callable from unittest.mock import MagicMock import pyvera as pv @@ -15,7 +17,7 @@ async def run_sensor_test( vera_component_factory: ComponentFactory, category: int, class_property: str, - assert_states: Tuple[Tuple[Any, Any]], + assert_states: tuple[tuple[Any, Any]], assert_unit_of_measurement: str = None, setup_callback: Callable[[pv.VeraController], None] = None, ) -> None: diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 6d0ba2781e65b4..48a1b5b464fd6c 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -1,7 +1,9 @@ """Tests for Vizio config flow.""" +from __future__ import annotations + from contextlib import asynccontextmanager from datetime import timedelta -from typing import Any, Dict, List, Optional +from typing import Any from unittest.mock import call, patch import pytest @@ -87,7 +89,7 @@ async def _add_config_entry_to_hass( await hass.async_block_till_done() -def _get_ha_power_state(vizio_power_state: Optional[bool]) -> str: +def _get_ha_power_state(vizio_power_state: bool | None) -> str: """Return HA power state given Vizio power state.""" if vizio_power_state: return STATE_ON @@ -98,7 +100,7 @@ def _get_ha_power_state(vizio_power_state: Optional[bool]) -> str: return STATE_UNAVAILABLE -def _assert_sources_and_volume(attr: Dict[str, Any], vizio_device_class: str) -> None: +def _assert_sources_and_volume(attr: dict[str, Any], vizio_device_class: str) -> None: """Assert source list, source, and volume level based on attr dict and device class.""" assert attr["source_list"] == INPUT_LIST assert attr["source"] == CURRENT_INPUT @@ -111,7 +113,7 @@ def _assert_sources_and_volume(attr: Dict[str, Any], vizio_device_class: str) -> def _get_attr_and_assert_base_attr( hass: HomeAssistantType, device_class: str, power_state: str -) -> Dict[str, Any]: +) -> dict[str, Any]: """Return entity attributes after asserting name, device class, and power state.""" attr = hass.states.get(ENTITY_ID).attributes assert attr["friendly_name"] == NAME @@ -123,7 +125,7 @@ def _get_attr_and_assert_base_attr( @asynccontextmanager async def _cm_for_test_setup_without_apps( - all_settings: Dict[str, Any], vizio_power_state: Optional[bool] + all_settings: dict[str, Any], vizio_power_state: bool | None ) -> None: """Context manager to setup test for Vizio devices without including app specific patches.""" with patch( @@ -140,7 +142,7 @@ async def _cm_for_test_setup_without_apps( async def _test_setup_tv( - hass: HomeAssistantType, vizio_power_state: Optional[bool] + hass: HomeAssistantType, vizio_power_state: bool | None ) -> None: """Test Vizio TV entity setup.""" ha_power_state = _get_ha_power_state(vizio_power_state) @@ -164,7 +166,7 @@ async def _test_setup_tv( async def _test_setup_speaker( - hass: HomeAssistantType, vizio_power_state: Optional[bool] + hass: HomeAssistantType, vizio_power_state: bool | None ) -> None: """Test Vizio Speaker entity setup.""" ha_power_state = _get_ha_power_state(vizio_power_state) @@ -201,7 +203,7 @@ async def _test_setup_speaker( @asynccontextmanager async def _cm_for_test_setup_tv_with_apps( - hass: HomeAssistantType, device_config: Dict[str, Any], app_config: Dict[str, Any] + hass: HomeAssistantType, device_config: dict[str, Any], app_config: dict[str, Any] ) -> None: """Context manager to setup test for Vizio TV with support for apps.""" config_entry = MockConfigEntry( @@ -229,7 +231,7 @@ async def _cm_for_test_setup_tv_with_apps( def _assert_source_list_with_apps( - list_to_test: List[str], attr: Dict[str, Any] + list_to_test: list[str], attr: dict[str, Any] ) -> None: """Assert source list matches list_to_test after removing INPUT_APPS from list.""" for app_to_remove in INPUT_APPS: @@ -244,7 +246,7 @@ async def _test_service( domain: str, vizio_func_name: str, ha_service_name: str, - additional_service_data: Optional[Dict[str, Any]], + additional_service_data: dict[str, Any] | None, *args, **kwargs, ) -> None: @@ -460,8 +462,8 @@ async def test_options_update( async def _test_update_availability_switch( hass: HomeAssistantType, - initial_power_state: Optional[bool], - final_power_state: Optional[bool], + initial_power_state: bool | None, + final_power_state: bool | None, caplog: pytest.fixture, ) -> None: now = dt_util.utcnow() diff --git a/tests/components/withings/common.py b/tests/components/withings/common.py index 80e6d07654c8da..4d3552bf6626c3 100644 --- a/tests/components/withings/common.py +++ b/tests/components/withings/common.py @@ -1,6 +1,7 @@ """Common data for for the withings component tests.""" +from __future__ import annotations + from dataclasses import dataclass -from typing import List, Optional, Tuple, Union from unittest.mock import MagicMock from urllib.parse import urlparse @@ -49,27 +50,21 @@ class ProfileConfig: profile: str user_id: int - api_response_user_get_device: Union[UserGetDeviceResponse, Exception] - api_response_measure_get_meas: Union[MeasureGetMeasResponse, Exception] - api_response_sleep_get_summary: Union[SleepGetSummaryResponse, Exception] - api_response_notify_list: Union[NotifyListResponse, Exception] - api_response_notify_revoke: Optional[Exception] + api_response_user_get_device: UserGetDeviceResponse | Exception + api_response_measure_get_meas: MeasureGetMeasResponse | Exception + api_response_sleep_get_summary: SleepGetSummaryResponse | Exception + api_response_notify_list: NotifyListResponse | Exception + api_response_notify_revoke: Exception | None def new_profile_config( profile: str, user_id: int, - api_response_user_get_device: Optional[ - Union[UserGetDeviceResponse, Exception] - ] = None, - api_response_measure_get_meas: Optional[ - Union[MeasureGetMeasResponse, Exception] - ] = None, - api_response_sleep_get_summary: Optional[ - Union[SleepGetSummaryResponse, Exception] - ] = None, - api_response_notify_list: Optional[Union[NotifyListResponse, Exception]] = None, - api_response_notify_revoke: Optional[Exception] = None, + api_response_user_get_device: UserGetDeviceResponse | Exception | None = None, + api_response_measure_get_meas: MeasureGetMeasResponse | Exception | None = None, + api_response_sleep_get_summary: SleepGetSummaryResponse | Exception | None = None, + api_response_notify_list: NotifyListResponse | Exception | None = None, + api_response_notify_revoke: Exception | None = None, ) -> ProfileConfig: """Create a new profile config immutable object.""" return ProfileConfig( @@ -118,13 +113,13 @@ def __init__( self._aioclient_mock = aioclient_mock self._client_id = None self._client_secret = None - self._profile_configs: Tuple[ProfileConfig, ...] = () + self._profile_configs: tuple[ProfileConfig, ...] = () async def configure_component( self, client_id: str = "my_client_id", client_secret: str = "my_client_secret", - profile_configs: Tuple[ProfileConfig, ...] = (), + profile_configs: tuple[ProfileConfig, ...] = (), ) -> None: """Configure the wihings component.""" self._client_id = client_id @@ -294,7 +289,7 @@ async def unload(self, profile: ProfileConfig) -> None: def get_config_entries_for_user_id( hass: HomeAssistant, user_id: int -) -> Tuple[ConfigEntry]: +) -> tuple[ConfigEntry]: """Get a list of config entries that apply to a specific withings user.""" return tuple( [ @@ -305,7 +300,7 @@ def get_config_entries_for_user_id( ) -def async_get_flow_for_user_id(hass: HomeAssistant, user_id: int) -> List[dict]: +def async_get_flow_for_user_id(hass: HomeAssistant, user_id: int) -> list[dict]: """Get a flow for a user id.""" return [ flow @@ -316,7 +311,7 @@ def async_get_flow_for_user_id(hass: HomeAssistant, user_id: int) -> List[dict]: def get_data_manager_by_user_id( hass: HomeAssistant, user_id: int -) -> Optional[DataManager]: +) -> DataManager | None: """Get a data manager by the user id.""" return next( iter( From 53687c766da66c6b9e86c3bc9fa33b7739269e8e Mon Sep 17 00:00:00 2001 From: elyobelyob Date: Thu, 18 Mar 2021 16:02:38 +0000 Subject: [PATCH 1363/1818] Add URL input for Prowl (#46427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen --- homeassistant/components/prowl/notify.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/prowl/notify.py b/homeassistant/components/prowl/notify.py index 5d2efe766077b8..725c3b9de303a8 100644 --- a/homeassistant/components/prowl/notify.py +++ b/homeassistant/components/prowl/notify.py @@ -48,6 +48,8 @@ async def async_send_message(self, message, **kwargs): "description": message, "priority": data["priority"] if data and "priority" in data else 0, } + if data.get("url"): + payload["url"] = data["url"] _LOGGER.debug("Attempting call Prowl service at %s", url) session = async_get_clientsession(self._hass) From 9fca001eed7e22456a334f9a16fea7a14b02b652 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:12:33 -0400 Subject: [PATCH 1364/1818] Bump zwave-js-server-python to 0.22.0 (#48085) --- .../components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/test_api.py | 16 ++--- tests/components/zwave_js/test_climate.py | 50 +++++++-------- tests/components/zwave_js/test_cover.py | 52 ++++++++-------- tests/components/zwave_js/test_fan.py | 22 +++---- tests/components/zwave_js/test_light.py | 62 +++++++++---------- tests/components/zwave_js/test_lock.py | 24 +++---- tests/components/zwave_js/test_number.py | 6 +- tests/components/zwave_js/test_services.py | 34 +++++----- tests/components/zwave_js/test_switch.py | 14 ++--- 12 files changed, 140 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index de0f2cc0a6f146..7c97836c3b8ec0 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.21.1"], + "requirements": ["zwave-js-server-python==0.22.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["http", "websocket_api"] } diff --git a/requirements_all.txt b/requirements_all.txt index 963b262ec6064b..b1581b797bd894 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2396,4 +2396,4 @@ zigpy==0.33.0 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.1 +zwave-js-server-python==0.22.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a17b05864d982..9c0222a12d6dfe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1239,4 +1239,4 @@ zigpy-znp==0.4.0 zigpy==0.33.0 # homeassistant.components.zwave_js -zwave-js-server-python==0.21.1 +zwave-js-server-python==0.22.0 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index dd8679ddf73f93..75bde222111bc9 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -229,7 +229,7 @@ async def test_set_config_parameter( entry = integration ws_client = await hass_ws_client(hass) - client.async_send_command.return_value = {"success": True} + client.async_send_command_no_wait.return_value = None await ws_client.send_json( { @@ -244,10 +244,10 @@ async def test_set_config_parameter( ) msg = await ws_client.receive_json() - assert msg["result"] + assert msg["success"] - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -275,7 +275,7 @@ async def test_set_config_parameter( } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() with patch( "homeassistant.components.zwave_js.api.async_set_config_parameter", @@ -295,7 +295,7 @@ async def test_set_config_parameter( msg = await ws_client.receive_json() - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 assert not msg["success"] assert msg["error"]["code"] == "not_supported" assert msg["error"]["message"] == "test" @@ -315,7 +315,7 @@ async def test_set_config_parameter( msg = await ws_client.receive_json() - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 assert not msg["success"] assert msg["error"]["code"] == "not_found" assert msg["error"]["message"] == "test" @@ -335,7 +335,7 @@ async def test_set_config_parameter( msg = await ws_client.receive_json() - assert len(client.async_send_command.call_args_list) == 0 + assert len(client.async_send_command_no_wait.call_args_list) == 0 assert not msg["success"] assert msg["error"]["code"] == "unknown_error" assert msg["error"]["message"] == "test" diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index c69804b01584b2..636b6b3f9f1794 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -72,7 +72,7 @@ async def test_thermostat_v2( | SUPPORT_FAN_MODE ) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting hvac mode await hass.services.async_call( @@ -85,8 +85,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -108,7 +108,7 @@ async def test_thermostat_v2( } assert args["value"] == 2 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting temperature await hass.services.async_call( @@ -122,8 +122,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + args = client.async_send_command.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -144,7 +144,7 @@ async def test_thermostat_v2( "value": 1, } assert args["value"] == 2 - args = client.async_send_command_no_wait.call_args_list[1][0][0] + args = client.async_send_command.call_args_list[1][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -166,7 +166,7 @@ async def test_thermostat_v2( } assert args["value"] == 77 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test cool mode update from value updated event event = Event( @@ -217,7 +217,7 @@ async def test_thermostat_v2( assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 22.8 assert state.attributes[ATTR_TARGET_TEMP_LOW] == 22.2 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting temperature with heat_cool await hass.services.async_call( @@ -231,8 +231,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + args = client.async_send_command.call_args_list[0][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -254,7 +254,7 @@ async def test_thermostat_v2( } assert args["value"] == 77 - args = client.async_send_command_no_wait.call_args_list[1][0][0] + args = client.async_send_command.call_args_list[1][0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -276,7 +276,7 @@ async def test_thermostat_v2( } assert args["value"] == 86 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting invalid hvac mode with pytest.raises(ValueError): @@ -290,7 +290,7 @@ async def test_thermostat_v2( blocking=True, ) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting fan mode await hass.services.async_call( @@ -303,8 +303,8 @@ async def test_thermostat_v2( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 13 assert args["valueId"] == { @@ -327,7 +327,7 @@ async def test_thermostat_v2( } assert args["value"] == 1 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting invalid fan mode with pytest.raises(ValueError): @@ -485,8 +485,8 @@ async def test_preset_and_no_setpoint( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 8 assert args["valueId"] == { @@ -514,7 +514,7 @@ async def test_preset_and_no_setpoint( } assert args["value"] == 15 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test Full power preset update from value updated event event = Event( @@ -553,9 +553,9 @@ async def test_preset_and_no_setpoint( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 0 + assert len(client.async_send_command.call_args_list) == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Restore hvac mode by setting preset None await hass.services.async_call( @@ -568,8 +568,8 @@ async def test_preset_and_no_setpoint( blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 8 assert args["valueId"]["commandClass"] == 64 @@ -577,4 +577,4 @@ async def test_preset_and_no_setpoint( assert args["valueId"]["property"] == "mode" assert args["value"] == 1 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index e6118f9b37d1ea..2378453e31a265 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -38,8 +38,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -60,7 +60,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 50 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting position await hass.services.async_call( @@ -70,8 +70,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -92,7 +92,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test opening await hass.services.async_call( @@ -102,8 +102,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -124,7 +124,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test stop after opening await hass.services.async_call( "cover", @@ -133,8 +133,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - open_args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] assert open_args["command"] == "node.set_value" assert open_args["nodeId"] == 6 assert open_args["valueId"] == { @@ -153,7 +153,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not open_args["value"] - close_args = client.async_send_command_no_wait.call_args_list[1][0][0] + close_args = client.async_send_command.call_args_list[1][0][0] assert close_args["command"] == "node.set_value" assert close_args["nodeId"] == 6 assert close_args["valueId"] == { @@ -191,7 +191,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): }, ) node.receive_event(event) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() state = hass.states.get(WINDOW_COVER_ENTITY) assert state.state == "open" @@ -203,8 +203,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): {"entity_id": WINDOW_COVER_ENTITY}, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 6 assert args["valueId"] == { @@ -225,7 +225,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test stop after closing await hass.services.async_call( @@ -235,8 +235,8 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 2 - open_args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert len(client.async_send_command.call_args_list) == 2 + open_args = client.async_send_command.call_args_list[0][0][0] assert open_args["command"] == "node.set_value" assert open_args["nodeId"] == 6 assert open_args["valueId"] == { @@ -255,7 +255,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not open_args["value"] - close_args = client.async_send_command_no_wait.call_args_list[1][0][0] + close_args = client.async_send_command.call_args_list[1][0][0] assert close_args["command"] == "node.set_value" assert close_args["nodeId"] == 6 assert close_args["valueId"] == { @@ -274,7 +274,7 @@ async def test_window_cover(hass, client, chain_actuator_zws12, integration): } assert not close_args["value"] - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() event = Event( type="value updated", @@ -314,8 +314,8 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): DOMAIN, SERVICE_OPEN_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 255 @@ -341,15 +341,15 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): state = hass.states.get(GDC_COVER_ENTITY) assert state.state == STATE_CLOSED - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test close await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": GDC_COVER_ENTITY}, blocking=True ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 0 @@ -375,7 +375,7 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration): state = hass.states.get(GDC_COVER_ENTITY) assert state.state == STATE_CLOSED - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Barrier sends an opening state event = Event( diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index 0ee007aab35213..5bd856c664a02c 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -23,8 +23,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -45,7 +45,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 66 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test setting unknown speed with pytest.raises(ValueError): @@ -56,7 +56,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turn on no speed await hass.services.async_call( @@ -66,8 +66,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -88,7 +88,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 255 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning off await hass.services.async_call( @@ -98,8 +98,8 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 17 assert args["valueId"] == { @@ -120,7 +120,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test speed update from value updated event event = Event( @@ -146,7 +146,7 @@ async def test_fan(hass, client, in_wall_smart_fan_control, integration): assert state.state == "on" assert state.attributes[ATTR_SPEED] == "high" - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() event = Event( type="value updated", diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index c16e2474980029..9440f5967edd42 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -38,8 +38,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { @@ -60,7 +60,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): } assert args["value"] == 255 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test brightness update from value updated event event = Event( @@ -95,9 +95,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 + assert len(client.async_send_command.call_args_list) == 1 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with brightness await hass.services.async_call( @@ -107,8 +107,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { @@ -129,7 +129,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): } assert args["value"] == 50 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with rgb color await hass.services.async_call( @@ -139,8 +139,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 - warm_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 255 + assert len(client.async_send_command.call_args_list) == 6 + warm_args = client.async_send_command.call_args_list[0][0][0] # red 255 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" @@ -151,7 +151,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert warm_args["valueId"]["propertyName"] == "targetColor" assert warm_args["value"] == 255 - cold_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 76 + cold_args = client.async_send_command.call_args_list[1][0][0] # green 76 assert cold_args["command"] == "node.set_value" assert cold_args["nodeId"] == 39 assert cold_args["valueId"]["commandClassName"] == "Color Switch" @@ -161,7 +161,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert cold_args["valueId"]["property"] == "targetColor" assert cold_args["valueId"]["propertyName"] == "targetColor" assert cold_args["value"] == 76 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 255 + red_args = client.async_send_command.call_args_list[2][0][0] # blue 255 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -171,9 +171,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 255 - green_args = client.async_send_command_no_wait.call_args_list[3][0][ - 0 - ] # warm white 0 + green_args = client.async_send_command.call_args_list[3][0][0] # warm white 0 assert green_args["command"] == "node.set_value" assert green_args["nodeId"] == 39 assert green_args["valueId"]["commandClassName"] == "Color Switch" @@ -183,9 +181,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert green_args["valueId"]["property"] == "targetColor" assert green_args["valueId"]["propertyName"] == "targetColor" assert green_args["value"] == 0 - blue_args = client.async_send_command_no_wait.call_args_list[4][0][ - 0 - ] # cold white 0 + blue_args = client.async_send_command.call_args_list[4][0][0] # cold white 0 assert blue_args["command"] == "node.set_value" assert blue_args["nodeId"] == 39 assert blue_args["valueId"]["commandClassName"] == "Color Switch" @@ -236,7 +232,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with same rgb color await hass.services.async_call( @@ -246,9 +242,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 + assert len(client.async_send_command.call_args_list) == 6 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on with color temp await hass.services.async_call( @@ -258,8 +254,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 - red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0 + assert len(client.async_send_command.call_args_list) == 6 + red_args = client.async_send_command.call_args_list[0][0][0] # red 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -269,7 +265,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 0 + red_args = client.async_send_command.call_args_list[1][0][0] # green 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -279,7 +275,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 0 + red_args = client.async_send_command.call_args_list[2][0][0] # blue 0 assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -289,9 +285,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["property"] == "targetColor" assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 0 - warm_args = client.async_send_command_no_wait.call_args_list[3][0][ - 0 - ] # warm white 0 + warm_args = client.async_send_command.call_args_list[3][0][0] # warm white 0 assert warm_args["command"] == "node.set_value" assert warm_args["nodeId"] == 39 assert warm_args["valueId"]["commandClassName"] == "Color Switch" @@ -301,7 +295,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert warm_args["valueId"]["property"] == "targetColor" assert warm_args["valueId"]["propertyName"] == "targetColor" assert warm_args["value"] == 20 - red_args = client.async_send_command_no_wait.call_args_list[4][0][0] # cold white + red_args = client.async_send_command.call_args_list[4][0][0] # cold white assert red_args["command"] == "node.set_value" assert red_args["nodeId"] == 39 assert red_args["valueId"]["commandClassName"] == "Color Switch" @@ -312,7 +306,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert red_args["valueId"]["propertyName"] == "targetColor" assert red_args["value"] == 235 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test color temp update from value updated event red_event = Event( @@ -368,9 +362,9 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 6 + assert len(client.async_send_command.call_args_list) == 6 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning off await hass.services.async_call( @@ -380,8 +374,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 39 assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_lock.py b/tests/components/zwave_js/test_lock.py index e4032cf42ed038..9ddc7abdd88eaa 100644 --- a/tests/components/zwave_js/test_lock.py +++ b/tests/components/zwave_js/test_lock.py @@ -33,8 +33,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -64,7 +64,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == 255 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test locked update from value updated event event = Event( @@ -88,7 +88,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): assert hass.states.get(SCHLAGE_BE469_LOCK_ENTITY).state == STATE_LOCKED - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test unlocking await hass.services.async_call( @@ -98,8 +98,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -129,7 +129,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == 0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test set usercode service await hass.services.async_call( @@ -143,8 +143,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { @@ -167,7 +167,7 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): } assert args["value"] == "1234" - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test clear usercode await hass.services.async_call( @@ -177,8 +177,8 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 20 assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py index 136e62c540590f..b7d83068bea220 100644 --- a/tests/components/zwave_js/test_number.py +++ b/tests/components/zwave_js/test_number.py @@ -20,8 +20,8 @@ async def test_number(hass, client, aeotec_radiator_thermostat, integration): blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 4 assert args["valueId"] == { @@ -43,7 +43,7 @@ async def test_number(hass, client, aeotec_radiator_thermostat, integration): } assert args["value"] == 30.0 - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test value update from value updated event event = Event( diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index d0bf08c1b7ab09..585e03402ebac3 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -39,8 +39,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -68,7 +68,7 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting parameter by property name await hass.services.async_call( @@ -82,8 +82,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -111,7 +111,7 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): } assert args["value"] == 1 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting parameter by property name and state label await hass.services.async_call( @@ -125,8 +125,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -154,7 +154,7 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): } assert args["value"] == 2 - client.async_send_command.reset_mock() + client.async_send_command_no_wait.reset_mock() # Test setting parameter by property and bitmask await hass.services.async_call( @@ -169,8 +169,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration): blocking=True, ) - assert len(client.async_send_command.call_args_list) == 1 - args = client.async_send_command.call_args[0][0] + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { @@ -302,15 +302,15 @@ async def test_poll_value( ): """Test the poll_value service.""" # Test polling the primary value - client.async_send_command_no_wait.return_value = {"result": 2} + client.async_send_command.return_value = {"result": 2} await hass.services.async_call( DOMAIN, SERVICE_REFRESH_VALUE, {ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY}, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.poll_value" assert args["nodeId"] == 26 assert args["valueId"] == { @@ -339,10 +339,10 @@ async def test_poll_value( "ccVersion": 2, } - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test polling all watched values - client.async_send_command_no_wait.return_value = {"result": 2} + client.async_send_command.return_value = {"result": 2} await hass.services.async_call( DOMAIN, SERVICE_REFRESH_VALUE, @@ -352,7 +352,7 @@ async def test_poll_value( }, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 8 + assert len(client.async_send_command.call_args_list) == 8 # Test polling against an invalid entity raises ValueError with pytest.raises(ValueError): diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index dceaa17a816c67..ea6e27d9b725f4 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -21,7 +21,7 @@ async def test_switch(hass, hank_binary_switch, integration, client): "switch", "turn_on", {"entity_id": SWITCH_ENTITY}, blocking=True ) - args = client.async_send_command_no_wait.call_args[0][0] + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 32 assert args["valueId"] == { @@ -68,7 +68,7 @@ async def test_switch(hass, hank_binary_switch, integration, client): "switch", "turn_off", {"entity_id": SWITCH_ENTITY}, blocking=True ) - args = client.async_send_command_no_wait.call_args[0][0] + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 32 assert args["valueId"] == { @@ -102,8 +102,8 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): DOMAIN, SERVICE_TURN_OFF, {"entity_id": entity}, blocking=True ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 0 @@ -134,7 +134,7 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): state = hass.states.get(entity) assert state.state == STATE_OFF - client.async_send_command_no_wait.reset_mock() + client.async_send_command.reset_mock() # Test turning on await hass.services.async_call( @@ -143,8 +143,8 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client): # Note: the valueId's value is still 255 because we never # received an updated value - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args[0][0] + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 12 assert args["value"] == 255 From 5174f63fd85fb752ce378a5c0615b2f035695918 Mon Sep 17 00:00:00 2001 From: Dan Klaffenbach Date: Thu, 18 Mar 2021 18:19:28 +0100 Subject: [PATCH 1365/1818] Add definitions for grouping media players (#41193) * Add definitions for grouping media players See https://github.com/home-assistant/architecture/issues/364 * Fix Google Assistant tests * Define sync versions of async_join_players/async_unjoin * Don't use async API in synchronous test methods * Fix tests and make pylint happy The method name `unjoin` is used by another component, so let's use `unjoin_player` instead. * Fix emulated_hue tests The new media player entity in the `demo` component requires a tiny adjustment in the emulated_hue tests. * Use "target:" in service description * Also use "name:" in service descriptions Co-authored-by: Franck Nijhof --- homeassistant/components/demo/media_player.py | 27 ++++++++++-- .../components/media_player/__init__.py | 42 +++++++++++++++++++ .../components/media_player/const.py | 4 ++ .../components/media_player/services.yaml | 25 ++++++++++- tests/components/demo/test_media_player.py | 36 ++++++++++++++++ tests/components/emulated_hue/test_hue_api.py | 1 + tests/components/google_assistant/__init__.py | 13 ++++++ 7 files changed, 143 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index 2ea360ebb81c4c..ea315707deae93 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -6,6 +6,7 @@ MEDIA_TYPE_TVSHOW, REPEAT_MODE_OFF, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_GROUPING, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -40,6 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "Bedroom", "kxopViU98Xo", "Epic sax guy 10 hours", 360000 ), DemoMusicPlayer(), + DemoMusicPlayer("Kitchen"), DemoTVShowPlayer(), ] ) @@ -73,6 +75,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST + | SUPPORT_GROUPING | SUPPORT_PLAY | SUPPORT_SHUFFLE_SET | SUPPORT_REPEAT_SET @@ -291,7 +294,7 @@ def media_pause(self): class DemoMusicPlayer(AbstractDemoPlayer): - """A Demo media player that only supports YouTube.""" + """A Demo media player.""" # We only implement the methods that we support @@ -318,12 +321,18 @@ class DemoMusicPlayer(AbstractDemoPlayer): ), ] - def __init__(self): + def __init__(self, name="Walkman"): """Initialize the demo device.""" - super().__init__("Walkman") + super().__init__(name) self._cur_track = 0 + self._group_members = [] self._repeat = REPEAT_MODE_OFF + @property + def group_members(self): + """List of players which are currently grouped together.""" + return self._group_members + @property def media_content_id(self): """Return the content ID of current playing media.""" @@ -398,6 +407,18 @@ def set_repeat(self, repeat): self._repeat = repeat self.schedule_update_ha_state() + def join_players(self, group_members): + """Join `group_members` as a player group with the current player.""" + self._group_members = [ + self.entity_id, + ] + group_members + self.schedule_update_ha_state() + + def unjoin_player(self): + """Remove this player from any group.""" + self._group_members = [] + self.schedule_update_ha_state() + class DemoTVShowPlayer(AbstractDemoPlayer): """A Demo media player that only supports YouTube.""" diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 1e0a9b9f6bb3ac..8e3ffe5dd0d45e 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -64,6 +64,7 @@ from .const import ( ATTR_APP_ID, ATTR_APP_NAME, + ATTR_GROUP_MEMBERS, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, ATTR_MEDIA_ALBUM_ARTIST, @@ -94,11 +95,14 @@ MEDIA_CLASS_DIRECTORY, REPEAT_MODES, SERVICE_CLEAR_PLAYLIST, + SERVICE_JOIN, SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, + SERVICE_UNJOIN, SUPPORT_BROWSE_MEDIA, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_GROUPING, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -299,6 +303,12 @@ async def async_setup(hass, config): "async_media_seek", [SUPPORT_SEEK], ) + component.async_register_entity_service( + SERVICE_JOIN, + {vol.Required(ATTR_GROUP_MEMBERS): list}, + "async_join_players", + [SUPPORT_GROUPING], + ) component.async_register_entity_service( SERVICE_SELECT_SOURCE, {vol.Required(ATTR_INPUT_SOURCE): cv.string}, @@ -330,6 +340,9 @@ async def async_setup(hass, config): "async_set_shuffle", [SUPPORT_SHUFFLE_SET], ) + component.async_register_entity_service( + SERVICE_UNJOIN, {}, "async_unjoin_player", [SUPPORT_GROUPING] + ) component.async_register_entity_service( SERVICE_REPEAT_SET, @@ -537,6 +550,11 @@ def repeat(self): """Return current repeat mode.""" return None + @property + def group_members(self): + """List of members which are currently grouped together.""" + return None + @property def supported_features(self): """Flag media player features that are supported.""" @@ -738,6 +756,11 @@ def support_shuffle_set(self): """Boolean if shuffle is supported.""" return bool(self.supported_features & SUPPORT_SHUFFLE_SET) + @property + def support_grouping(self): + """Boolean if player grouping is supported.""" + return bool(self.supported_features & SUPPORT_GROUPING) + async def async_toggle(self): """Toggle the power on the media player.""" if hasattr(self, "toggle"): @@ -846,6 +869,9 @@ def state_attributes(self): if self.media_image_remotely_accessible: state_attr["entity_picture_local"] = self.media_image_local + if self.support_grouping: + state_attr[ATTR_GROUP_MEMBERS] = self.group_members + return state_attr async def async_browse_media( @@ -860,6 +886,22 @@ async def async_browse_media( """ raise NotImplementedError() + def join_players(self, group_members): + """Join `group_members` as a player group with the current player.""" + raise NotImplementedError() + + async def async_join_players(self, group_members): + """Join `group_members` as a player group with the current player.""" + await self.hass.async_add_executor_job(self.join_players, group_members) + + def unjoin_player(self): + """Remove this player from any group.""" + raise NotImplementedError() + + async def async_unjoin_player(self): + """Remove this player from any group.""" + await self.hass.async_add_executor_job(self.unjoin_player) + async def _async_fetch_image_from_cache(self, url): """Fetch image. diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index 87ccca75d36e18..67f4331aa60c43 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -2,6 +2,7 @@ ATTR_APP_ID = "app_id" ATTR_APP_NAME = "app_name" +ATTR_GROUP_MEMBERS = "group_members" ATTR_INPUT_SOURCE = "source" ATTR_INPUT_SOURCE_LIST = "source_list" ATTR_MEDIA_ALBUM_ARTIST = "media_album_artist" @@ -75,9 +76,11 @@ MEDIA_TYPE_VIDEO = "video" SERVICE_CLEAR_PLAYLIST = "clear_playlist" +SERVICE_JOIN = "join" SERVICE_PLAY_MEDIA = "play_media" SERVICE_SELECT_SOUND_MODE = "select_sound_mode" SERVICE_SELECT_SOURCE = "select_source" +SERVICE_UNJOIN = "unjoin" REPEAT_MODE_ALL = "all" REPEAT_MODE_OFF = "off" @@ -103,3 +106,4 @@ SUPPORT_SELECT_SOUND_MODE = 65536 SUPPORT_BROWSE_MEDIA = 131072 SUPPORT_REPEAT_SET = 262144 +SUPPORT_GROUPING = 524288 diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index eaca8483be188e..e2a260dc80f556 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -118,8 +118,8 @@ play_media: media_content_type: name: Content type description: - The type of the content to play. Like image, music, tvshow, - video, episode, channel or playlist. + The type of the content to play. Like image, music, tvshow, video, + episode, channel or playlist. required: true example: "music" selector: @@ -184,3 +184,24 @@ repeat_set: - "off" - "all" - "one" + +join: + description: + Group players together. Only works on platforms with support for player + groups. + name: Join + target: + fields: + group_members: + description: + The players which will be synced with the target player. + example: + - "media_player.multiroom_player2" + - "media_player.multiroom_player3" + +unjoin: + description: + Unjoin the player from a group. Only works on platforms with support for + player groups. + name: Unjoin + target: diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index a32a99bbc63312..4ed41ce9c83f6e 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -442,3 +442,39 @@ def detach(self): req = await client.get(state.attributes.get(ATTR_ENTITY_PICTURE)) assert req.status == 200 assert await req.text() == fake_picture_data + + +async def test_grouping(hass): + """Test the join/unjoin services.""" + walkman = "media_player.walkman" + kitchen = "media_player.kitchen" + + assert await async_setup_component( + hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} + ) + await hass.async_block_till_done() + state = hass.states.get(walkman) + assert state.attributes.get(mp.ATTR_GROUP_MEMBERS) == [] + + await hass.services.async_call( + mp.DOMAIN, + mp.SERVICE_JOIN, + { + ATTR_ENTITY_ID: walkman, + mp.ATTR_GROUP_MEMBERS: [ + kitchen, + ], + }, + blocking=True, + ) + state = hass.states.get(walkman) + assert state.attributes.get(mp.ATTR_GROUP_MEMBERS) == [walkman, kitchen] + + await hass.services.async_call( + mp.DOMAIN, + mp.SERVICE_UNJOIN, + {ATTR_ENTITY_ID: walkman}, + blocking=True, + ) + state = hass.states.get(walkman) + assert state.attributes.get(mp.ATTR_GROUP_MEMBERS) == [] diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index e3f965616f903b..3c322c0b613a87 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -90,6 +90,7 @@ "21": "humidifier.hygrostat", "22": "scene.light_on", "23": "scene.light_off", + "24": "media_player.kitchen", } ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()} diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 9047e175ae6f8c..0fe89d0fa7b07d 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -192,6 +192,19 @@ def should_2fa(self, state): "type": "action.devices.types.SETTOP", "willReportState": False, }, + { + "id": "media_player.kitchen", + "name": {"name": "Kitchen"}, + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.Volume", + "action.devices.traits.Modes", + "action.devices.traits.TransportControl", + "action.devices.traits.MediaState", + ], + "type": "action.devices.types.SETTOP", + "willReportState": False, + }, { "id": "media_player.living_room", "name": {"name": "Living Room"}, From 98d7e6b898fdafc1cb535f6dad14de617bea4679 Mon Sep 17 00:00:00 2001 From: bestlibre Date: Thu, 18 Mar 2021 19:30:38 +0100 Subject: [PATCH 1366/1818] Add images support to matrix notify (#37625) Co-authored-by: Paulus Schoutsen --- homeassistant/components/matrix/__init__.py | 47 +++++++++++++++++-- homeassistant/components/matrix/notify.py | 6 ++- homeassistant/components/matrix/services.yaml | 3 ++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index c89de5552d5bae..62af53079e831b 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -1,12 +1,13 @@ """The Matrix bot component.""" from functools import partial import logging +import mimetypes import os from matrix_client.client import MatrixClient, MatrixRequestError import voluptuous as vol -from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TARGET +from homeassistant.components.notify import ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -31,8 +32,12 @@ CONF_WORD = "word" CONF_EXPRESSION = "expression" +DEFAULT_CONTENT_TYPE = "application/octet-stream" + EVENT_MATRIX_COMMAND = "matrix_command" +ATTR_IMAGES = "images" # optional images + COMMAND_SCHEMA = vol.All( vol.Schema( { @@ -67,6 +72,9 @@ SERVICE_SCHEMA_SEND_MESSAGE = vol.Schema( { vol.Required(ATTR_MESSAGE): cv.string, + vol.Optional(ATTR_DATA): { + vol.Optional(ATTR_IMAGES): vol.All(cv.ensure_list, [cv.string]), + }, vol.Required(ATTR_TARGET): vol.All(cv.ensure_list, [cv.string]), } ) @@ -336,13 +344,20 @@ def _login_by_password(self): return _client - def _send_message(self, message, target_rooms): - """Send the message to the Matrix server.""" + def _send_image(self, img, target_rooms): + _LOGGER.debug("Uploading file from path, %s", img) + if not self.hass.config.is_allowed_path(img): + _LOGGER.error("Path not allowed: %s", img) + return + with open(img, "rb") as upfile: + imgfile = upfile.read() + content_type = mimetypes.guess_type(img)[0] + mxc = self._client.upload(imgfile, content_type) for target_room in target_rooms: try: room = self._join_or_get_room(target_room) - _LOGGER.debug(room.send_text(message)) + room.send_image(mxc, img) except MatrixRequestError as ex: _LOGGER.error( "Unable to deliver message to room '%s': %d, %s", @@ -351,6 +366,28 @@ def _send_message(self, message, target_rooms): ex.content, ) + def _send_message(self, message, data, target_rooms): + """Send the message to the Matrix server.""" + for target_room in target_rooms: + try: + room = self._join_or_get_room(target_room) + if message is not None: + _LOGGER.debug(room.send_text(message)) + except MatrixRequestError as ex: + _LOGGER.error( + "Unable to deliver message to room '%s': %d, %s", + target_room, + ex.code, + ex.content, + ) + if data is not None: + for img in data.get(ATTR_IMAGES, []): + self._send_image(img, target_rooms) + def handle_send_message(self, service): """Handle the send_message service.""" - self._send_message(service.data[ATTR_MESSAGE], service.data[ATTR_TARGET]) + self._send_message( + service.data.get(ATTR_MESSAGE), + service.data.get(ATTR_DATA), + service.data[ATTR_TARGET], + ) diff --git a/homeassistant/components/matrix/notify.py b/homeassistant/components/matrix/notify.py index 0965783bf4d858..8643d7511bc4d2 100644 --- a/homeassistant/components/matrix/notify.py +++ b/homeassistant/components/matrix/notify.py @@ -2,6 +2,7 @@ import voluptuous as vol from homeassistant.components.notify import ( + ATTR_DATA, ATTR_MESSAGE, ATTR_TARGET, PLATFORM_SCHEMA, @@ -31,9 +32,10 @@ def __init__(self, default_room): def send_message(self, message="", **kwargs): """Send the message to the Matrix server.""" target_rooms = kwargs.get(ATTR_TARGET) or [self._default_room] - service_data = {ATTR_TARGET: target_rooms, ATTR_MESSAGE: message} - + data = kwargs.get(ATTR_DATA) + if data is not None: + service_data[ATTR_DATA] = data return self.hass.services.call( DOMAIN, SERVICE_SEND_MESSAGE, service_data=service_data ) diff --git a/homeassistant/components/matrix/services.yaml b/homeassistant/components/matrix/services.yaml index f8b0c53bda633c..fe99bf6365a657 100644 --- a/homeassistant/components/matrix/services.yaml +++ b/homeassistant/components/matrix/services.yaml @@ -7,3 +7,6 @@ send_message: target: description: A list of room(s) to send the message to. example: "#hasstest:matrix.org" + data: + description: Extended information of notification. Supports list of images. Optional. + example: "{'images': ['/tmp/test.jpg']}" From 7b717bc43724ff34c0c2b5252a93e711081917d8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 18 Mar 2021 21:14:06 +0100 Subject: [PATCH 1367/1818] Correct trace for repeat script actions (#48031) --- homeassistant/helpers/script.py | 12 ++++++++---- homeassistant/helpers/trace.py | 28 ++++++++++++++++++++++++++-- tests/helpers/test_script.py | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index be6be32da1eafb..ac56ce2beb421f 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -72,6 +72,7 @@ from .trace import ( TraceElement, + async_trace_path, trace_append_element, trace_id_get, trace_path, @@ -613,11 +614,14 @@ async def _async_condition_step(self): if not check: raise _StopScript - def _test_conditions(self, conditions, name): + def _test_conditions(self, conditions, name, condition_path=None): + if condition_path is None: + condition_path = name + @trace_condition_function def traced_test_conditions(hass, variables): try: - with trace_path("conditions"): + with trace_path(condition_path): for idx, cond in enumerate(conditions): with trace_path(str(idx)): if not cond(hass, variables): @@ -631,7 +635,7 @@ def traced_test_conditions(hass, variables): result = traced_test_conditions(self._hass, self._variables) return result - @trace_path("repeat") + @async_trace_path("repeat") async def _async_repeat_step(self): """Repeat a sequence.""" description = self._action.get(CONF_ALIAS, "sequence") @@ -720,7 +724,7 @@ async def _async_choose_step(self) -> None: for idx, (conditions, script) in enumerate(choose_data["choices"]): with trace_path(str(idx)): try: - if self._test_conditions(conditions, "choose"): + if self._test_conditions(conditions, "choose", "conditions"): trace_set_result(choice=idx) with trace_path("sequence"): await self._async_run_script(script) diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 2e5f1dd8c5407a..d6de845248f174 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -4,7 +4,8 @@ from collections import deque from contextlib import contextmanager from contextvars import ContextVar -from typing import Any, Deque, Generator, cast +from functools import wraps +from typing import Any, Callable, Deque, Generator, cast from homeassistant.helpers.typing import TemplateVarsType import homeassistant.util.dt as dt_util @@ -168,9 +169,32 @@ def trace_set_result(**kwargs: Any) -> None: @contextmanager def trace_path(suffix: str | list[str]) -> Generator: - """Go deeper in the config tree.""" + """Go deeper in the config tree. + + Can not be used as a decorator on couroutine functions. + """ count = trace_path_push(suffix) try: yield finally: trace_path_pop(count) + + +def async_trace_path(suffix: str | list[str]) -> Callable: + """Go deeper in the config tree. + + To be used as a decorator on coroutine functions. + """ + + def _trace_path_decorator(func: Callable) -> Callable: + """Decorate a coroutine function.""" + + @wraps(func) + async def async_wrapper(*args: Any) -> None: + """Catch and log exception.""" + with trace_path(suffix): + await func(*args) + + return async_wrapper + + return _trace_path_decorator diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index f6246299074904..1f47aa9dbbaf2f 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1236,7 +1236,7 @@ async def test_repeat_count(hass, caplog, count): assert_action_trace( { "0": [{}], - "0/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + "0/repeat/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), } ) From 0f5efca76b6cbec9a0237b2229d5e01894e7431b Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 18 Mar 2021 21:26:20 +0100 Subject: [PATCH 1368/1818] Fix Shelly sleeping device initialization after reconfiguration (#48076) --- homeassistant/components/shelly/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 52dae7f164dbef..93b75b139a90e6 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -75,6 +75,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): dev_reg = await device_registry.async_get_registry(hass) identifier = (DOMAIN, entry.unique_id) device_entry = dev_reg.async_get_device(identifiers={identifier}, connections=set()) + if entry.entry_id not in device_entry.config_entries: + device_entry = None sleep_period = entry.data.get("sleep_period") From 4cb77181920b25f668c75a19e0dd91eb703efb1b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 22:58:19 +0100 Subject: [PATCH 1369/1818] Update typing 16 (#48087) --- homeassistant/auth/models.py | 2 +- homeassistant/bootstrap.py | 2 +- homeassistant/components/alexa/entities.py | 2 +- homeassistant/components/cast/helpers.py | 6 ++--- .../components/esphome/entry_data.py | 2 +- .../components/here_travel_time/sensor.py | 2 +- homeassistant/components/izone/climate.py | 2 +- .../components/lovelace/dashboard.py | 4 +++- homeassistant/components/mobile_app/util.py | 2 +- .../components/mqtt/device_trigger.py | 2 +- homeassistant/components/sharkiq/vacuum.py | 2 +- .../components/system_health/__init__.py | 2 +- .../components/tasmota/device_trigger.py | 2 +- .../components/transmission/__init__.py | 23 +++++++++---------- .../components/transmission/sensor.py | 2 +- homeassistant/components/wiffi/__init__.py | 2 +- homeassistant/components/zeroconf/__init__.py | 4 +++- homeassistant/config_entries.py | 2 +- homeassistant/core.py | 6 ++--- homeassistant/data_entry_flow.py | 6 ++--- homeassistant/helpers/__init__.py | 4 ++-- homeassistant/helpers/device_registry.py | 2 +- homeassistant/helpers/entity_registry.py | 2 +- homeassistant/helpers/script.py | 2 +- homeassistant/helpers/selector.py | 4 +++- homeassistant/helpers/service.py | 23 ++++++------------- homeassistant/loader.py | 18 +++++++-------- script/hassfest/codeowners.py | 8 +++---- script/hassfest/config_flow.py | 9 ++++---- script/hassfest/coverage.py | 5 ++-- script/hassfest/dependencies.py | 15 ++++++------ script/hassfest/dhcp.py | 9 ++++---- script/hassfest/json.py | 5 ++-- script/hassfest/manifest.py | 5 ++-- script/hassfest/model.py | 22 ++++++++++-------- script/hassfest/mqtt.py | 9 ++++---- script/hassfest/requirements.py | 9 ++++---- script/hassfest/services.py | 5 ++-- script/hassfest/ssdp.py | 9 ++++---- script/hassfest/translations.py | 5 ++-- script/hassfest/zeroconf.py | 9 ++++---- script/scaffold/model.py | 9 ++++---- .../integration/device_action.py | 6 ++--- .../integration/device_condition.py | 4 ++-- .../integration/device_trigger.py | 4 ++-- .../integration/reproduce_state.py | 12 ++++++---- .../integration/significant_change.py | 6 +++-- script/translations/download.py | 5 ++-- tests/common.py | 4 ++-- tests/components/vera/common.py | 2 +- tests/components/vera/test_binary_sensor.py | 2 +- tests/components/vera/test_climate.py | 4 ++-- tests/components/vera/test_cover.py | 2 +- tests/components/vera/test_init.py | 18 +++++++-------- tests/components/vera/test_light.py | 2 +- tests/components/vera/test_lock.py | 2 +- tests/components/vera/test_scene.py | 2 +- tests/components/vera/test_sensor.py | 4 ++-- tests/components/vera/test_switch.py | 2 +- 59 files changed, 180 insertions(+), 166 deletions(-) diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index a8a70b8fc3b7c8..aaef7e02174918 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -43,7 +43,7 @@ class User: groups: list[Group] = attr.ib(factory=list, eq=False, order=False) # List of credentials of a user. - credentials: list["Credentials"] = attr.ib(factory=list, eq=False, order=False) + credentials: list[Credentials] = attr.ib(factory=list, eq=False, order=False) # Tokens associated with a user. refresh_tokens: dict[str, "RefreshToken"] = attr.ib( diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 8b191ae3a4705f..d19ddaf4f5d6e1 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -76,7 +76,7 @@ async def async_setup_hass( - runtime_config: "RuntimeConfig", + runtime_config: RuntimeConfig, ) -> core.HomeAssistant | None: """Set up Home Assistant.""" hass = core.HomeAssistant() diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index 71f4e41a810ef2..95c40cd7cef005 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -253,7 +253,7 @@ class AlexaEntity: The API handlers should manipulate entities only through this interface. """ - def __init__(self, hass: HomeAssistant, config: "AbstractConfig", entity: State): + def __init__(self, hass: HomeAssistant, config: AbstractConfig, entity: State): """Initialize Alexa Entity.""" self.hass = hass self.config = config diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index 91382c695914f2..71caa6490d852e 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -15,13 +15,13 @@ class ChromecastInfo: This also has the same attributes as the mDNS fields by zeroconf. """ - services: Optional[set] = attr.ib() - uuid: Optional[str] = attr.ib( + services: set | None = attr.ib() + uuid: str | None = attr.ib( converter=attr.converters.optional(str), default=None ) # always convert UUID to string if not None _manufacturer = attr.ib(type=Optional[str], default=None) model_name: str = attr.ib(default="") - friendly_name: Optional[str] = attr.ib(default=None) + friendly_name: str | None = attr.ib(default=None) is_audio_group = attr.ib(type=Optional[bool], default=False) is_dynamic_group = attr.ib(type=Optional[bool], default=None) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index f308239fa23415..0923c84acd70f4 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -52,7 +52,7 @@ class RuntimeEntryData: """Store runtime data for esphome config entries.""" entry_id: str = attr.ib() - client: "APIClient" = attr.ib() + client: APIClient = attr.ib() store: Store = attr.ib() reconnect_task: asyncio.Task | None = attr.ib(default=None) state: dict[str, dict[str, Any]] = attr.ib(factory=dict) diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 9a6b0724caab26..976c591f81041e 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -225,7 +225,7 @@ def __init__( destination: str, origin_entity_id: str, destination_entity_id: str, - here_data: "HERETravelTimeData", + here_data: HERETravelTimeData, ) -> None: """Initialize the sensor.""" self._name = name diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index 18c8b5e9a2c13b..d509896e841b4e 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -78,7 +78,7 @@ async def async_setup_entry( @callback def init_controller(ctrl: Controller): """Register the controller device and the containing zones.""" - conf = hass.data.get(DATA_CONFIG) # type: ConfigType + conf: ConfigType = hass.data.get(DATA_CONFIG) # Filter out any entities excluded in the config file if conf and ctrl.device_uid in conf[CONF_EXCLUDE]: diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index c6f4726724bcb6..93b127259d20aa 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -1,4 +1,6 @@ """Lovelace dashboard support.""" +from __future__ import annotations + from abc import ABC, abstractmethod import logging import os @@ -231,7 +233,7 @@ def __init__(self, hass): _LOGGER, ) - async def _async_load_data(self) -> Optional[dict]: + async def _async_load_data(self) -> dict | None: """Load the data.""" data = await self.store.async_load() diff --git a/homeassistant/components/mobile_app/util.py b/homeassistant/components/mobile_app/util.py index b0a3f52e3949c7..cd4b7c22939310 100644 --- a/homeassistant/components/mobile_app/util.py +++ b/homeassistant/components/mobile_app/util.py @@ -43,7 +43,7 @@ def supports_push(hass, webhook_id: str) -> bool: @callback def get_notify_service(hass, webhook_id: str) -> str | None: """Return the notify service for this webhook ID.""" - notify_service: "MobileAppNotificationService" = hass.data[DOMAIN][DATA_NOTIFY] + notify_service: MobileAppNotificationService = hass.data[DOMAIN][DATA_NOTIFY] for target_service, target_webhook_id in notify_service.registered_targets.items(): if target_webhook_id == webhook_id: diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 1e4981d7d6f8c8..12a98905ba5690 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -87,7 +87,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() - trigger: "Trigger" = attr.ib() + trigger: Trigger = attr.ib() remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 7e33d355729327..eed41fb14387c8 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -69,7 +69,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Shark IQ vacuum cleaner.""" coordinator: SharkIqUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - devices: Iterable["SharkIqVacuum"] = coordinator.shark_vacs.values() + devices: Iterable[SharkIqVacuum] = coordinator.shark_vacs.values() device_names = [d.name for d in devices] _LOGGER.debug( "Found %d Shark IQ device(s): %s", diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index ea87a2af832473..a5756ce9ddcaf4 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -60,7 +60,7 @@ async def _register_system_health_platform(hass, integration_domain, platform): async def get_integration_info( - hass: HomeAssistant, registration: "SystemHealthRegistration" + hass: HomeAssistant, registration: SystemHealthRegistration ): """Get integration system health.""" try: diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 0cbb6e10f1fd03..139e9be816a976 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -48,7 +48,7 @@ class TriggerInstance: action: AutomationActionType = attr.ib() automation_info: dict = attr.ib() - trigger: "Trigger" = attr.ib() + trigger: Trigger = attr.ib() remove: CALLBACK_TYPE | None = attr.ib(default=None) async def async_attach_trigger(self): diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 5a37cc4d771719..cb4bcceeeeaac1 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -3,7 +3,6 @@ from datetime import timedelta import logging -from typing import List import transmissionrpc from transmissionrpc.error import TransmissionError @@ -173,8 +172,8 @@ def __init__(self, hass, config_entry): """Initialize the Transmission RPC API.""" self.hass = hass self.config_entry = config_entry - self.tm_api = None # type: transmissionrpc.Client - self._tm_data = None # type: TransmissionData + self.tm_api: transmissionrpc.Client = None + self._tm_data: TransmissionData = None self.unsub_timer = None @property @@ -345,14 +344,14 @@ def __init__(self, hass, config, api: transmissionrpc.Client): """Initialize the Transmission RPC API.""" self.hass = hass self.config = config - self.data = None # type: transmissionrpc.Session - self.available = True # type: bool - self._all_torrents = [] # type: List[transmissionrpc.Torrent] - self._api = api # type: transmissionrpc.Client - self._completed_torrents = [] # type: List[transmissionrpc.Torrent] - self._session = None # type: transmissionrpc.Session - self._started_torrents = [] # type: List[transmissionrpc.Torrent] - self._torrents = [] # type: List[transmissionrpc.Torrent] + self.data: transmissionrpc.Session = None + self.available: bool = True + self._all_torrents: list[transmissionrpc.Torrent] = [] + self._api: transmissionrpc.Client = api + self._completed_torrents: list[transmissionrpc.Torrent] = [] + self._session: transmissionrpc.Session = None + self._started_torrents: list[transmissionrpc.Torrent] = [] + self._torrents: list[transmissionrpc.Torrent] = [] @property def host(self): @@ -365,7 +364,7 @@ def signal_update(self): return f"{DATA_UPDATED}-{self.host}" @property - def torrents(self) -> List[transmissionrpc.Torrent]: + def torrents(self) -> list[transmissionrpc.Torrent]: """Get the list of torrents.""" return self._torrents diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 6a59b3a3d618f3..cd9e57c50d74ca 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -43,7 +43,7 @@ class TransmissionSensor(Entity): def __init__(self, tm_client, client_name, sensor_name, sub_type=None): """Initialize the sensor.""" - self._tm_client = tm_client # type: TransmissionClient + self._tm_client: TransmissionClient = tm_client self._client_name = client_name self._name = sensor_name self._sub_type = sub_type diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index d93bd54437b5ea..3b85a7ff0f8b71 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -74,7 +74,7 @@ async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Unload a config entry.""" - api: "WiffiIntegrationApi" = hass.data[DOMAIN][config_entry.entry_id] + api: WiffiIntegrationApi = hass.data[DOMAIN][config_entry.entry_id] await api.server.close_server() unload_ok = all( diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index fb2d097d0630ac..d9c8dc5286026a 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,4 +1,6 @@ """Support for exposing Home Assistant via Zeroconf.""" +from __future__ import annotations + import fnmatch from functools import partial import ipaddress @@ -107,7 +109,7 @@ def _stop_zeroconf(_): class HaServiceBrowser(ServiceBrowser): """ServiceBrowser that only consumes DNSPointer records.""" - def update_record(self, zc: "Zeroconf", now: float, record: DNSRecord) -> None: + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: """Pre-Filter update_record to DNSPointers for the configured type.""" # diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index a18e207a7305ca..157acb545c1dd8 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -463,7 +463,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): """Manage all the config entry flows that are in progress.""" def __init__( - self, hass: HomeAssistant, config_entries: "ConfigEntries", hass_config: dict + self, hass: HomeAssistant, config_entries: ConfigEntries, hass_config: dict ): """Initialize the config entry flow manager.""" super().__init__(hass) diff --git a/homeassistant/core.py b/homeassistant/core.py index 4e0c3c48283814..64edf5742ce049 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -214,9 +214,9 @@ def __str__(self) -> str: # pylint: disable=invalid-str-returned class HomeAssistant: """Root object of the Home Assistant home automation.""" - auth: "AuthManager" - http: "HomeAssistantHTTP" = None # type: ignore - config_entries: "ConfigEntries" = None # type: ignore + auth: AuthManager + http: HomeAssistantHTTP = None # type: ignore + config_entries: ConfigEntries = None # type: ignore def __init__(self) -> None: """Initialize new Home Assistant object.""" diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index f16c1859fcda8c..b149493373feea 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -86,13 +86,11 @@ async def async_create_flow( @abc.abstractmethod async def async_finish_flow( - self, flow: "FlowHandler", result: dict[str, Any] + self, flow: FlowHandler, result: dict[str, Any] ) -> dict[str, Any]: """Finish a config flow and add an entry.""" - async def async_post_init( - self, flow: "FlowHandler", result: dict[str, Any] - ) -> None: + async def async_post_init(self, flow: FlowHandler, result: dict[str, Any]) -> None: """Entry has finished executing its first step asynchronously.""" @callback diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 195c18381465bf..a1964c432fc0df 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -10,7 +10,7 @@ from .typing import ConfigType -def config_per_platform(config: "ConfigType", domain: str) -> Iterable[tuple[Any, Any]]: +def config_per_platform(config: ConfigType, domain: str) -> Iterable[tuple[Any, Any]]: """Break a component config into different platforms. For example, will find 'switch', 'switch 2', 'switch 3', .. etc @@ -34,7 +34,7 @@ def config_per_platform(config: "ConfigType", domain: str) -> Iterable[tuple[Any yield platform, item -def extract_domain_configs(config: "ConfigType", domain: str) -> Sequence[str]: +def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: """Extract keys from config for given domain name. Async friendly. diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 4018ba6204c597..fac937c4afb78c 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -658,7 +658,7 @@ def async_entries_for_config_entry( @callback def async_config_entry_disabled_by_changed( - registry: DeviceRegistry, config_entry: "ConfigEntry" + registry: DeviceRegistry, config_entry: ConfigEntry ) -> None: """Handle a config entry being disabled or enabled. diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 3d33b73271dc4f..265dcd9d8781ee 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -628,7 +628,7 @@ def async_entries_for_config_entry( @callback def async_config_entry_disabled_by_changed( - registry: EntityRegistry, config_entry: "ConfigEntry" + registry: EntityRegistry, config_entry: ConfigEntry ) -> None: """Handle a config entry being disabled or enabled. diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index ac56ce2beb421f..1824bb9e2d6dbe 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -291,7 +291,7 @@ class _ScriptRun: def __init__( self, hass: HomeAssistant, - script: "Script", + script: Script, variables: dict[str, Any], context: Context | None, log_exceptions: bool, diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 34befe9c37b2eb..6de8c58d279f15 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -1,4 +1,6 @@ """Selectors for Home Assistant.""" +from __future__ import annotations + from typing import Any, Callable, Dict, cast import voluptuous as vol @@ -9,7 +11,7 @@ SELECTORS = decorator.Registry() -def validate_selector(config: Any) -> Dict: +def validate_selector(config: Any) -> dict: """Validate a selector.""" if not isinstance(config, dict): raise vol.Invalid("Expected a dictionary") diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index f43b85575b88d2..d227422f6d5c80 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -5,16 +5,7 @@ import dataclasses from functools import partial, wraps import logging -from typing import ( - TYPE_CHECKING, - Any, - Awaitable, - Callable, - Iterable, - Optional, - TypedDict, - Union, -) +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Iterable, TypedDict import voluptuous as vol @@ -83,9 +74,9 @@ class ServiceTargetSelector: def __init__(self, service_call: ha.ServiceCall): """Extract ids from service call data.""" - entity_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_ENTITY_ID) - device_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_DEVICE_ID) - area_ids: Optional[Union[str, list]] = service_call.data.get(ATTR_AREA_ID) + entity_ids: str | list | None = service_call.data.get(ATTR_ENTITY_ID) + device_ids: str | list | None = service_call.data.get(ATTR_DEVICE_ID) + area_ids: str | list | None = service_call.data.get(ATTR_AREA_ID) self.entity_ids = ( set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() @@ -319,7 +310,7 @@ async def async_extract_entity_ids( return referenced.referenced | referenced.indirectly_referenced -def _has_match(ids: Optional[Union[str, list]]) -> bool: +def _has_match(ids: str | list | None) -> bool: """Check if ids can match anything.""" return ids not in (None, ENTITY_MATCH_NONE) @@ -514,7 +505,7 @@ def async_set_service_schema( @bind_hass async def entity_service_call( hass: HomeAssistantType, - platforms: Iterable["EntityPlatform"], + platforms: Iterable[EntityPlatform], func: str | Callable[..., Any], call: ha.ServiceCall, required_features: Iterable[int] | None = None, @@ -557,7 +548,7 @@ async def entity_service_call( # Check the permissions # A list with entities to call the service on. - entity_candidates: list["Entity"] = [] + entity_candidates: list[Entity] = [] if entity_perms is None: for platform in platforms: diff --git a/homeassistant/loader.py b/homeassistant/loader.py index f6cb24698ece12..b3ef4fff271b4e 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -103,7 +103,7 @@ def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: async def _async_get_custom_components( - hass: "HomeAssistant", + hass: HomeAssistant, ) -> dict[str, Integration]: """Return list of custom integrations.""" if hass.config.safe_mode: @@ -144,7 +144,7 @@ def get_sub_directories(paths: list[str]) -> list[pathlib.Path]: async def async_get_custom_components( - hass: "HomeAssistant", + hass: HomeAssistant, ) -> dict[str, Integration]: """Return cached list of custom integrations.""" reg_or_evt = hass.data.get(DATA_CUSTOM_COMPONENTS) @@ -276,7 +276,7 @@ class Integration: @classmethod def resolve_from_root( - cls, hass: "HomeAssistant", root_module: ModuleType, domain: str + cls, hass: HomeAssistant, root_module: ModuleType, domain: str ) -> Integration | None: """Resolve an integration from a root module.""" for base in root_module.__path__: # type: ignore @@ -300,7 +300,7 @@ def resolve_from_root( return None @classmethod - def resolve_legacy(cls, hass: "HomeAssistant", domain: str) -> Integration | None: + def resolve_legacy(cls, hass: HomeAssistant, domain: str) -> Integration | None: """Resolve legacy component. Will create a stub manifest. @@ -319,7 +319,7 @@ def resolve_legacy(cls, hass: "HomeAssistant", domain: str) -> Integration | Non def __init__( self, - hass: "HomeAssistant", + hass: HomeAssistant, pkg_path: str, file_path: pathlib.Path, manifest: Manifest, @@ -494,7 +494,7 @@ def __repr__(self) -> str: return f"" -async def async_get_integration(hass: "HomeAssistant", domain: str) -> Integration: +async def async_get_integration(hass: HomeAssistant, domain: str) -> Integration: """Get an integration.""" cache = hass.data.get(DATA_INTEGRATIONS) if cache is None: @@ -579,7 +579,7 @@ def __init__(self, from_domain: str, to_domain: str) -> None: def _load_file( - hass: "HomeAssistant", comp_or_platform: str, base_paths: list[str] + hass: HomeAssistant, comp_or_platform: str, base_paths: list[str] ) -> ModuleType | None: """Try to load specified file. @@ -640,7 +640,7 @@ def _load_file( class ModuleWrapper: """Class to wrap a Python module and auto fill in hass argument.""" - def __init__(self, hass: "HomeAssistant", module: ModuleType) -> None: + def __init__(self, hass: HomeAssistant, module: ModuleType) -> None: """Initialize the module wrapper.""" self._hass = hass self._module = module @@ -704,7 +704,7 @@ def bind_hass(func: CALLABLE_T) -> CALLABLE_T: async def _async_component_dependencies( - hass: "HomeAssistant", + hass: HomeAssistant, start_domain: str, integration: Integration, loaded: set[str], diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py index eba657f64cf19d..81c3c883965480 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -1,5 +1,5 @@ """Generate CODEOWNERS.""" -from typing import Dict +from __future__ import annotations from .model import Config, Integration @@ -33,7 +33,7 @@ """ -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Generate CODEOWNERS.""" parts = [BASE] @@ -61,7 +61,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return "\n".join(parts) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate CODEOWNERS.""" codeowners_path = config.root / "CODEOWNERS" config.cache["codeowners"] = content = generate_and_validate(integrations) @@ -79,7 +79,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate CODEOWNERS.""" codeowners_path = config.root / "CODEOWNERS" with open(str(codeowners_path), "w") as fp: diff --git a/script/hassfest/config_flow.py b/script/hassfest/config_flow.py index 9ae0daee1b02d5..e4d1be7bc4694e 100644 --- a/script/hassfest/config_flow.py +++ b/script/hassfest/config_flow.py @@ -1,6 +1,7 @@ """Generate config flow file.""" +from __future__ import annotations + import json -from typing import Dict from .model import Config, Integration @@ -90,7 +91,7 @@ def validate_integration(config: Config, integration: Integration): ) -def generate_and_validate(integrations: Dict[str, Integration], config: Config): +def generate_and_validate(integrations: dict[str, Integration], config: Config): """Validate and generate config flow data.""" domains = [] @@ -117,7 +118,7 @@ def generate_and_validate(integrations: Dict[str, Integration], config: Config): return BASE.format(json.dumps(domains, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate config flow file.""" config_flow_path = config.root / "homeassistant/generated/config_flows.py" config.cache["config_flow"] = content = generate_and_validate(integrations, config) @@ -136,7 +137,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate config flow file.""" config_flow_path = config.root / "homeassistant/generated/config_flows.py" with open(str(config_flow_path), "w") as fp: diff --git a/script/hassfest/coverage.py b/script/hassfest/coverage.py index c561c5d9f167e5..1a0c684cfab124 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -1,6 +1,7 @@ """Validate coverage files.""" +from __future__ import annotations + from pathlib import Path -from typing import Dict from .model import Config, Integration @@ -69,7 +70,7 @@ } -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate coverage.""" coverage_path = config.root / ".coveragerc" diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index b13d792904243e..ed780ed067b1d3 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -1,7 +1,8 @@ """Validate dependencies.""" +from __future__ import annotations + import ast from pathlib import Path -from typing import Dict, Set from homeassistant.requirements import DISCOVERY_INTEGRATIONS @@ -14,7 +15,7 @@ class ImportCollector(ast.NodeVisitor): def __init__(self, integration: Integration): """Initialize the import collector.""" self.integration = integration - self.referenced: Dict[Path, Set[str]] = {} + self.referenced: dict[Path, set[str]] = {} # Current file or dir we're inspecting self._cur_fil_dir = None @@ -156,7 +157,7 @@ def visit_Attribute(self, node): } -def calc_allowed_references(integration: Integration) -> Set[str]: +def calc_allowed_references(integration: Integration) -> set[str]: """Return a set of allowed references.""" allowed_references = ( ALLOWED_USED_COMPONENTS @@ -173,9 +174,9 @@ def calc_allowed_references(integration: Integration) -> Set[str]: def find_non_referenced_integrations( - integrations: Dict[str, Integration], + integrations: dict[str, Integration], integration: Integration, - references: Dict[Path, Set[str]], + references: dict[Path, set[str]], ): """Find intergrations that are not allowed to be referenced.""" allowed_references = calc_allowed_references(integration) @@ -221,7 +222,7 @@ def find_non_referenced_integrations( def validate_dependencies( - integrations: Dict[str, Integration], integration: Integration + integrations: dict[str, Integration], integration: Integration ): """Validate all dependencies.""" # Some integrations are allowed to have violations. @@ -244,7 +245,7 @@ def validate_dependencies( ) -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle dependencies for integrations.""" # check for non-existing dependencies for integration in integrations.values(): diff --git a/script/hassfest/dhcp.py b/script/hassfest/dhcp.py index fbf695a9f7378c..a3abe80063e797 100644 --- a/script/hassfest/dhcp.py +++ b/script/hassfest/dhcp.py @@ -1,6 +1,7 @@ """Generate dhcp file.""" +from __future__ import annotations + import json -from typing import Dict, List from .model import Config, Integration @@ -16,7 +17,7 @@ """.strip() -def generate_and_validate(integrations: List[Dict[str, str]]): +def generate_and_validate(integrations: list[dict[str, str]]): """Validate and generate dhcp data.""" match_list = [] @@ -37,7 +38,7 @@ def generate_and_validate(integrations: List[Dict[str, str]]): return BASE.format(json.dumps(match_list, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate dhcp file.""" dhcp_path = config.root / "homeassistant/generated/dhcp.py" config.cache["dhcp"] = content = generate_and_validate(integrations) @@ -56,7 +57,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate dhcp file.""" dhcp_path = config.root / "homeassistant/generated/dhcp.py" with open(str(dhcp_path), "w") as fp: diff --git a/script/hassfest/json.py b/script/hassfest/json.py index 39c1f6f13a926e..49ebb05bbeafc2 100644 --- a/script/hassfest/json.py +++ b/script/hassfest/json.py @@ -1,6 +1,7 @@ """Validate integration JSON files.""" +from __future__ import annotations + import json -from typing import Dict from .model import Integration @@ -20,7 +21,7 @@ def validate_json_files(integration: Integration): return -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle JSON files inside integrations.""" if not config.specific_integrations: return diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index c6f8f71f2d9389..55bfa717a3fb64 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -1,5 +1,6 @@ """Manifest validation.""" -from typing import Dict +from __future__ import annotations + from urllib.parse import urlparse import voluptuous as vol @@ -145,7 +146,7 @@ def validate_manifest(integration: Integration): validate_version(integration) -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle all integrations manifests.""" for integration in integrations.values(): if integration.manifest: diff --git a/script/hassfest/model.py b/script/hassfest/model.py index 0750ef10b6c475..c5b8dbff618753 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -1,8 +1,10 @@ """Models for manifest validator.""" +from __future__ import annotations + import importlib import json import pathlib -from typing import Any, Dict, List, Optional +from typing import Any import attr @@ -24,12 +26,12 @@ def __str__(self) -> str: class Config: """Config for the run.""" - specific_integrations: Optional[pathlib.Path] = attr.ib() + specific_integrations: pathlib.Path | None = attr.ib() root: pathlib.Path = attr.ib() action: str = attr.ib() requirements: bool = attr.ib() - errors: List[Error] = attr.ib(factory=list) - cache: Dict[str, Any] = attr.ib(factory=dict) + errors: list[Error] = attr.ib(factory=list) + cache: dict[str, Any] = attr.ib(factory=dict) def add_error(self, *args, **kwargs): """Add an error.""" @@ -65,9 +67,9 @@ def load_dir(cls, path: pathlib.Path): return integrations path: pathlib.Path = attr.ib() - manifest: Optional[dict] = attr.ib(default=None) - errors: List[Error] = attr.ib(factory=list) - warnings: List[Error] = attr.ib(factory=list) + manifest: dict | None = attr.ib(default=None) + errors: list[Error] = attr.ib(factory=list) + warnings: list[Error] = attr.ib(factory=list) @property def domain(self) -> str: @@ -80,17 +82,17 @@ def core(self) -> bool: return self.path.as_posix().startswith("homeassistant/components") @property - def disabled(self) -> Optional[str]: + def disabled(self) -> str | None: """List of disabled.""" return self.manifest.get("disabled") @property - def requirements(self) -> List[str]: + def requirements(self) -> list[str]: """List of requirements.""" return self.manifest.get("requirements", []) @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: """List of dependencies.""" return self.manifest.get("dependencies", []) diff --git a/script/hassfest/mqtt.py b/script/hassfest/mqtt.py index fdc16895d8cd50..718df4ac827e9e 100644 --- a/script/hassfest/mqtt.py +++ b/script/hassfest/mqtt.py @@ -1,7 +1,8 @@ """Generate MQTT file.""" +from __future__ import annotations + from collections import defaultdict import json -from typing import Dict from .model import Config, Integration @@ -17,7 +18,7 @@ """.strip() -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Validate and generate MQTT data.""" data = defaultdict(list) @@ -39,7 +40,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return BASE.format(json.dumps(data, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate MQTT file.""" mqtt_path = config.root / "homeassistant/generated/mqtt.py" config.cache["mqtt"] = content = generate_and_validate(integrations) @@ -57,7 +58,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate MQTT file.""" mqtt_path = config.root / "homeassistant/generated/mqtt.py" with open(str(mqtt_path), "w") as fp: diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index b51cbff7185408..fa5a36c655914e 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -1,4 +1,6 @@ """Validate requirements.""" +from __future__ import annotations + from collections import deque import json import operator @@ -6,7 +8,6 @@ import re import subprocess import sys -from typing import Dict, Set from stdlib_list import stdlib_list from tqdm import tqdm @@ -58,7 +59,7 @@ def normalize_package_name(requirement: str) -> str: return package -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Handle requirements for integrations.""" ensure_cache() @@ -153,7 +154,7 @@ def ensure_cache(): PIPDEPTREE_CACHE = cache -def get_requirements(integration: Integration, packages: Set[str]) -> Set[str]: +def get_requirements(integration: Integration, packages: set[str]) -> set[str]: """Return all (recursively) requirements for an integration.""" ensure_cache() @@ -184,7 +185,7 @@ def get_requirements(integration: Integration, packages: Set[str]) -> Set[str]: return all_requirements -def install_requirements(integration: Integration, requirements: Set[str]) -> bool: +def install_requirements(integration: Integration, requirements: set[str]) -> bool: """Install integration requirements. Return True if successful. diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 62e9b2f88a1ef1..9577d134ccc148 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -1,7 +1,8 @@ """Validate dependencies.""" +from __future__ import annotations + import pathlib import re -from typing import Dict import voluptuous as vol from voluptuous.humanize import humanize_error @@ -93,7 +94,7 @@ def validate_services(integration: Integration): ) -def validate(integrations: Dict[str, Integration], config): +def validate(integrations: dict[str, Integration], config): """Handle dependencies for integrations.""" # check services.yaml is cool for integration in integrations.values(): diff --git a/script/hassfest/ssdp.py b/script/hassfest/ssdp.py index c9b3b8931187ca..c71d5432adf70e 100644 --- a/script/hassfest/ssdp.py +++ b/script/hassfest/ssdp.py @@ -1,7 +1,8 @@ """Generate ssdp file.""" +from __future__ import annotations + from collections import OrderedDict, defaultdict import json -from typing import Dict from .model import Config, Integration @@ -22,7 +23,7 @@ def sort_dict(value): return OrderedDict((key, value[key]) for key in sorted(value)) -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Validate and generate ssdp data.""" data = defaultdict(list) @@ -44,7 +45,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return BASE.format(json.dumps(data, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate ssdp file.""" ssdp_path = config.root / "homeassistant/generated/ssdp.py" config.cache["ssdp"] = content = generate_and_validate(integrations) @@ -62,7 +63,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate ssdp file.""" ssdp_path = config.root / "homeassistant/generated/ssdp.py" with open(str(ssdp_path), "w") as fp: diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 75886eedc6f836..b7a6341d25c087 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -1,9 +1,10 @@ """Validate integration translation files.""" +from __future__ import annotations + from functools import partial from itertools import chain import json import re -from typing import Dict import voluptuous as vol from voluptuous.humanize import humanize_error @@ -295,7 +296,7 @@ def validate_translation_file(config: Config, integration: Integration, all_stri ) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Handle JSON files inside integrations.""" if config.specific_integrations: all_strings = None diff --git a/script/hassfest/zeroconf.py b/script/hassfest/zeroconf.py index 61162b02761026..907c6aacefff73 100644 --- a/script/hassfest/zeroconf.py +++ b/script/hassfest/zeroconf.py @@ -1,7 +1,8 @@ """Generate zeroconf file.""" +from __future__ import annotations + from collections import OrderedDict, defaultdict import json -from typing import Dict from .model import Config, Integration @@ -19,7 +20,7 @@ """.strip() -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: dict[str, Integration]): """Validate and generate zeroconf data.""" service_type_dict = defaultdict(list) homekit_dict = {} @@ -89,7 +90,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): return BASE.format(json.dumps(zeroconf, indent=4), json.dumps(homekit, indent=4)) -def validate(integrations: Dict[str, Integration], config: Config): +def validate(integrations: dict[str, Integration], config: Config): """Validate zeroconf file.""" zeroconf_path = config.root / "homeassistant/generated/zeroconf.py" config.cache["zeroconf"] = content = generate_and_validate(integrations) @@ -108,7 +109,7 @@ def validate(integrations: Dict[str, Integration], config: Config): return -def generate(integrations: Dict[str, Integration], config: Config): +def generate(integrations: dict[str, Integration], config: Config): """Generate zeroconf file.""" zeroconf_path = config.root / "homeassistant/generated/zeroconf.py" with open(str(zeroconf_path), "w") as fp: diff --git a/script/scaffold/model.py b/script/scaffold/model.py index bfbcfa52544958..f9c71072a1b401 100644 --- a/script/scaffold/model.py +++ b/script/scaffold/model.py @@ -1,7 +1,8 @@ """Models for scaffolding.""" +from __future__ import annotations + import json from pathlib import Path -from typing import Set import attr @@ -21,9 +22,9 @@ class Info: discoverable: str = attr.ib(default=None) oauth2: str = attr.ib(default=None) - files_added: Set[Path] = attr.ib(factory=set) - tests_added: Set[Path] = attr.ib(factory=set) - examples_added: Set[Path] = attr.ib(factory=set) + files_added: set[Path] = attr.ib(factory=set) + tests_added: set[Path] = attr.ib(factory=set) + examples_added: set[Path] = attr.ib(factory=set) @property def integration_dir(self) -> Path: diff --git a/script/scaffold/templates/device_action/integration/device_action.py b/script/scaffold/templates/device_action/integration/device_action.py index 27a27cb95ee0c2..3bd1c0b91b320b 100644 --- a/script/scaffold/templates/device_action/integration/device_action.py +++ b/script/scaffold/templates/device_action/integration/device_action.py @@ -1,5 +1,5 @@ """Provides device actions for NEW_NAME.""" -from typing import List, Optional +from __future__ import annotations import voluptuous as vol @@ -29,7 +29,7 @@ ) -async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] @@ -69,7 +69,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: async def async_call_action_from_config( - hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context] + hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} diff --git a/script/scaffold/templates/device_condition/integration/device_condition.py b/script/scaffold/templates/device_condition/integration/device_condition.py index 6ad89332f8ec8b..413812828e5c00 100644 --- a/script/scaffold/templates/device_condition/integration/device_condition.py +++ b/script/scaffold/templates/device_condition/integration/device_condition.py @@ -1,5 +1,5 @@ """Provide the device conditions for NEW_NAME.""" -from typing import Dict, List +from __future__ import annotations import voluptuous as vol @@ -33,7 +33,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str -) -> List[Dict[str, str]]: +) -> list[dict[str, str]]: """List device conditions for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] diff --git a/script/scaffold/templates/device_trigger/integration/device_trigger.py b/script/scaffold/templates/device_trigger/integration/device_trigger.py index 2fa59d4eac8294..ca430368544ea6 100644 --- a/script/scaffold/templates/device_trigger/integration/device_trigger.py +++ b/script/scaffold/templates/device_trigger/integration/device_trigger.py @@ -1,5 +1,5 @@ """Provides device triggers for NEW_NAME.""" -from typing import List +from __future__ import annotations import voluptuous as vol @@ -32,7 +32,7 @@ ) -async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: """List device triggers for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(hass) triggers = [] diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index bb2ee7aee963f2..2031109a6bdecb 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -1,7 +1,9 @@ """Reproduce an NEW_NAME state.""" +from __future__ import annotations + import asyncio import logging -from typing import Any, Dict, Iterable, Optional +from typing import Any, Iterable from homeassistant.const import ( ATTR_ENTITY_ID, @@ -25,8 +27,8 @@ async def _async_reproduce_state( hass: HomeAssistantType, state: State, *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce a single state.""" cur_state = hass.states.get(state.entity_id) @@ -70,8 +72,8 @@ async def async_reproduce_states( hass: HomeAssistantType, states: Iterable[State], *, - context: Optional[Context] = None, - reproduce_options: Optional[Dict[str, Any]] = None, + context: Context | None = None, + reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce NEW_NAME states.""" # TODO pick one and remove other one diff --git a/script/scaffold/templates/significant_change/integration/significant_change.py b/script/scaffold/templates/significant_change/integration/significant_change.py index 23a00c603ac296..8e6022f171bc73 100644 --- a/script/scaffold/templates/significant_change/integration/significant_change.py +++ b/script/scaffold/templates/significant_change/integration/significant_change.py @@ -1,5 +1,7 @@ """Helper to test significant NEW_NAME state changes.""" -from typing import Any, Optional +from __future__ import annotations + +from typing import Any from homeassistant.const import ATTR_DEVICE_CLASS from homeassistant.core import HomeAssistant, callback @@ -13,7 +15,7 @@ def async_check_significant_change( new_state: str, new_attrs: dict, **kwargs: Any, -) -> Optional[bool]: +) -> bool | None: """Test if state significantly changed.""" device_class = new_attrs.get(ATTR_DEVICE_CLASS) diff --git a/script/translations/download.py b/script/translations/download.py index 7fc4c3365ccac9..eab9c40370ee59 100755 --- a/script/translations/download.py +++ b/script/translations/download.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 """Merge all translation sources into a single JSON file.""" +from __future__ import annotations + import json import os import pathlib import re import subprocess -from typing import Dict, List, Union from .const import CLI_2_DOCKER_IMAGE, CORE_PROJECT_ID, INTEGRATIONS_DIR from .error import ExitApp @@ -51,7 +52,7 @@ def run_download_docker(): raise ExitApp("Failed to download translations") -def save_json(filename: str, data: Union[List, Dict]): +def save_json(filename: str, data: list | dict): """Save JSON data to a file. Returns True on success. diff --git a/tests/common.py b/tests/common.py index 451eee5f99770c..f1449721c29ba3 100644 --- a/tests/common.py +++ b/tests/common.py @@ -202,9 +202,9 @@ async def async_wait_for_task_count(self, max_remaining_tasks: int = 0) -> None: start_time: float | None = None while len(self._pending_tasks) > max_remaining_tasks: - pending = [ + pending: Collection[Awaitable[Any]] = [ task for task in self._pending_tasks if not task.done() - ] # type: Collection[Awaitable[Any]] + ] self._pending_tasks.clear() if len(pending) > max_remaining_tasks: remaining_pending = await self._await_count_and_log_pending( diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index e666d51389731e..ae3c0a1a1dedba 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -118,7 +118,7 @@ async def _configure_component( if controller_config.legacy_entity_unique_id: component_config[CONF_LEGACY_UNIQUE_ID] = True - controller = MagicMock(spec=pv.VeraController) # type: pv.VeraController + controller: pv.VeraController = MagicMock(spec=pv.VeraController) controller.base_url = component_config.get(CONF_CONTROLLER) controller.register = MagicMock() controller.start = MagicMock() diff --git a/tests/components/vera/test_binary_sensor.py b/tests/components/vera/test_binary_sensor.py index b3b8d2d6ae194c..58b8ed7f4614dc 100644 --- a/tests/components/vera/test_binary_sensor.py +++ b/tests/components/vera/test_binary_sensor.py @@ -12,7 +12,7 @@ async def test_binary_sensor( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device.device_id = 1 vera_device.comm_failure = False vera_device.vera_device_id = vera_device.device_id diff --git a/tests/components/vera/test_climate.py b/tests/components/vera/test_climate.py index 5ec39f07953710..4b301a1c96d96d 100644 --- a/tests/components/vera/test_climate.py +++ b/tests/components/vera/test_climate.py @@ -20,7 +20,7 @@ async def test_climate( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat + vera_device: pv.VeraThermostat = MagicMock(spec=pv.VeraThermostat) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False @@ -131,7 +131,7 @@ async def test_climate_f( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat + vera_device: pv.VeraThermostat = MagicMock(spec=pv.VeraThermostat) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_cover.py b/tests/components/vera/test_cover.py index cfc33fb2dcfcc5..15b95b55e39847 100644 --- a/tests/components/vera/test_cover.py +++ b/tests/components/vera/test_cover.py @@ -12,7 +12,7 @@ async def test_cover( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraCurtain) # type: pv.VeraCurtain + vera_device: pv.VeraCurtain = MagicMock(spec=pv.VeraCurtain) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index 85735d0320e5b6..c828ef55fcd574 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -24,7 +24,7 @@ async def test_init( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" @@ -51,7 +51,7 @@ async def test_init_from_file( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" @@ -78,14 +78,14 @@ async def test_multiple_controllers_with_legacy_one( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test multiple controllers with one legacy controller.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" vera_device1.is_tripped = False entity1_id = "binary_sensor.first_dev_1" - vera_device2 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device2: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device2.device_id = 2 vera_device2.vera_device_id = vera_device2.device_id vera_device2.name = "second_dev" @@ -133,7 +133,7 @@ async def test_unload( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = vera_device1.device_id vera_device1.name = "first_dev" @@ -187,21 +187,21 @@ async def test_exclude_and_light_ids( hass: HomeAssistant, vera_component_factory: ComponentFactory, options ) -> None: """Test device exclusion, marking switches as lights and fixing the data type.""" - vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device1.device_id = 1 vera_device1.vera_device_id = 1 vera_device1.name = "dev1" vera_device1.is_tripped = False entity_id1 = "binary_sensor.dev1_1" - vera_device2 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device2: pv.VeraBinarySensor = MagicMock(spec=pv.VeraBinarySensor) vera_device2.device_id = 2 vera_device2.vera_device_id = 2 vera_device2.name = "dev2" vera_device2.is_tripped = False entity_id2 = "binary_sensor.dev2_2" - vera_device3 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device3: pv.VeraSwitch = MagicMock(spec=pv.VeraSwitch) vera_device3.device_id = 3 vera_device3.vera_device_id = 3 vera_device3.name = "dev3" @@ -210,7 +210,7 @@ async def test_exclude_and_light_ids( entity_id3 = "switch.dev3_3" - vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device4: pv.VeraSwitch = MagicMock(spec=pv.VeraSwitch) vera_device4.device_id = 4 vera_device4.vera_device_id = 4 vera_device4.name = "dev4" diff --git a/tests/components/vera/test_light.py b/tests/components/vera/test_light.py index ad5ad7e0259952..c96199e59894ff 100644 --- a/tests/components/vera/test_light.py +++ b/tests/components/vera/test_light.py @@ -13,7 +13,7 @@ async def test_light( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraDimmer) # type: pv.VeraDimmer + vera_device: pv.VeraDimmer = MagicMock(spec=pv.VeraDimmer) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_lock.py b/tests/components/vera/test_lock.py index 171f799f87bae6..644d58b9fc5a52 100644 --- a/tests/components/vera/test_lock.py +++ b/tests/components/vera/test_lock.py @@ -13,7 +13,7 @@ async def test_lock( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraLock) # type: pv.VeraLock + vera_device: pv.VeraLock = MagicMock(spec=pv.VeraLock) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_scene.py b/tests/components/vera/test_scene.py index 2d4b7375498157..b23d220e74e2b8 100644 --- a/tests/components/vera/test_scene.py +++ b/tests/components/vera/test_scene.py @@ -12,7 +12,7 @@ async def test_scene( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_scene = MagicMock(spec=pv.VeraScene) # type: pv.VeraScene + vera_scene: pv.VeraScene = MagicMock(spec=pv.VeraScene) vera_scene.scene_id = 1 vera_scene.vera_scene_id = vera_scene.scene_id vera_scene.name = "dev1" diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index 566a3db482e3ec..a2086f9f5e0000 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -22,7 +22,7 @@ async def run_sensor_test( setup_callback: Callable[[pv.VeraController], None] = None, ) -> None: """Test generic sensor.""" - vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor + vera_device: pv.VeraSensor = MagicMock(spec=pv.VeraSensor) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False @@ -178,7 +178,7 @@ async def test_scene_controller_sensor( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor + vera_device: pv.VeraSensor = MagicMock(spec=pv.VeraSensor) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False diff --git a/tests/components/vera/test_switch.py b/tests/components/vera/test_switch.py index ac90edc9ded216..2ffb472aeeb897 100644 --- a/tests/components/vera/test_switch.py +++ b/tests/components/vera/test_switch.py @@ -12,7 +12,7 @@ async def test_switch( hass: HomeAssistant, vera_component_factory: ComponentFactory ) -> None: """Test function.""" - vera_device = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device: pv.VeraSwitch = MagicMock(spec=pv.VeraSwitch) vera_device.device_id = 1 vera_device.vera_device_id = vera_device.device_id vera_device.comm_failure = False From 314a5518f1b0043c74581970dceac20547b8615b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 18 Mar 2021 23:20:25 +0100 Subject: [PATCH 1370/1818] Add python-typing-update to pre-commit-config (#48088) --- .pre-commit-config.yaml | 13 +++++++++++++ requirements_test_pre_commit.txt | 1 + 2 files changed, 14 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ab91245b9ee86..ee4b524610019c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,6 +67,19 @@ repos: hooks: - id: prettier stages: [manual] + - repo: https://github.com/cdce8p/python-typing-update + rev: v0.3.0 + hooks: + # Run `python-typing-update` hook manually from time to time + # to update python typing syntax. + # Will require manual work, before submitting changes! + - id: python-typing-update + stages: [manual] + args: + - --py38-plus + - --force + - --keep-updates + files: ^(homeassistant|tests|script)/.+\.py$ - repo: local hooks: # Run mypy through our wrapper script in order to get the possible diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 07f1efbc6946ef..24c8bf3a7ff5c6 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -7,5 +7,6 @@ flake8-docstrings==1.5.0 flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 +python-typing-update==0.3.0 pyupgrade==2.7.2 yamllint==1.24.2 From 7def36746718fed0f6906fc4c58a80af8ef9e22c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 19 Mar 2021 00:03:27 +0100 Subject: [PATCH 1371/1818] Update pyupgrade to v2.10.1 (#48089) --- .pre-commit-config.yaml | 2 +- requirements_test_pre_commit.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee4b524610019c..6629065005a7a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.7.2 + rev: v2.10.1 hooks: - id: pyupgrade args: [--py38-plus] diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 24c8bf3a7ff5c6..fd5329a8ca1c5b 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -8,5 +8,5 @@ flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 python-typing-update==0.3.0 -pyupgrade==2.7.2 +pyupgrade==2.10.1 yamllint==1.24.2 From 8a37b616bf16f77eefe9392edb1356263f89c9ec Mon Sep 17 00:00:00 2001 From: Martidjen Date: Fri, 19 Mar 2021 00:47:59 +0100 Subject: [PATCH 1372/1818] Add Opentherm Gateway current and setpoint precision (#47484) Co-authored-by: Martin Hjelmare --- .../components/opentherm_gw/__init__.py | 13 +++ .../components/opentherm_gw/climate.py | 24 +++-- .../components/opentherm_gw/config_flow.py | 15 ++- .../components/opentherm_gw/const.py | 2 + .../components/opentherm_gw/strings.json | 3 +- .../opentherm_gw/test_config_flow.py | 92 ++++++++++++++++++- 6 files changed, 134 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index cc08ec3da6997f..8686997e7481a6 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -39,6 +39,8 @@ CONF_CLIMATE, CONF_FLOOR_TEMP, CONF_PRECISION, + CONF_READ_PRECISION, + CONF_SET_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW, DOMAIN, @@ -94,6 +96,17 @@ async def async_setup_entry(hass, config_entry): gateway = OpenThermGatewayDevice(hass, config_entry) hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] = gateway + if config_entry.options.get(CONF_PRECISION): + migrate_options = dict(config_entry.options) + migrate_options.update( + { + CONF_READ_PRECISION: config_entry.options[CONF_PRECISION], + CONF_SET_PRECISION: config_entry.options[CONF_PRECISION], + } + ) + del migrate_options[CONF_PRECISION] + hass.config_entries.async_update_entry(config_entry, options=migrate_options) + config_entry.add_update_listener(options_updated) # Schedule directly on the loop to avoid blocking HA startup. diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 8ec536e73317bb..1253fe4c6d22a7 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -28,7 +28,13 @@ from homeassistant.helpers.entity import async_generate_entity_id from . import DOMAIN -from .const import CONF_FLOOR_TEMP, CONF_PRECISION, DATA_GATEWAYS, DATA_OPENTHERM_GW +from .const import ( + CONF_FLOOR_TEMP, + CONF_READ_PRECISION, + CONF_SET_PRECISION, + DATA_GATEWAYS, + DATA_OPENTHERM_GW, +) _LOGGER = logging.getLogger(__name__) @@ -61,7 +67,8 @@ def __init__(self, gw_dev, options): ) self.friendly_name = gw_dev.name self.floor_temp = options.get(CONF_FLOOR_TEMP, DEFAULT_FLOOR_TEMP) - self.temp_precision = options.get(CONF_PRECISION) + self.temp_read_precision = options.get(CONF_READ_PRECISION) + self.temp_set_precision = options.get(CONF_SET_PRECISION) self._available = False self._current_operation = None self._current_temperature = None @@ -79,7 +86,8 @@ def __init__(self, gw_dev, options): def update_options(self, entry): """Update climate entity options.""" self.floor_temp = entry.options[CONF_FLOOR_TEMP] - self.temp_precision = entry.options[CONF_PRECISION] + self.temp_read_precision = entry.options[CONF_READ_PRECISION] + self.temp_set_precision = entry.options[CONF_SET_PRECISION] self.async_write_ha_state() async def async_added_to_hass(self): @@ -178,8 +186,8 @@ def unique_id(self): @property def precision(self): """Return the precision of the system.""" - if self.temp_precision is not None and self.temp_precision != 0: - return self.temp_precision + if self.temp_read_precision: + return self.temp_read_precision if self.hass.config.units.temperature_unit == TEMP_CELSIUS: return PRECISION_HALVES return PRECISION_WHOLE @@ -234,7 +242,11 @@ def target_temperature(self): @property def target_temperature_step(self): """Return the supported step of target temperature.""" - return self.precision + if self.temp_set_precision: + return self.temp_set_precision + if self.hass.config.units.temperature_unit == TEMP_CELSIUS: + return PRECISION_HALVES + return PRECISION_WHOLE @property def preset_mode(self): diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 8da530bebda5a7..8081d3bf96cdf9 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -19,7 +19,7 @@ import homeassistant.helpers.config_validation as cv from . import DOMAIN -from .const import CONF_FLOOR_TEMP, CONF_PRECISION +from .const import CONF_FLOOR_TEMP, CONF_READ_PRECISION, CONF_SET_PRECISION class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -121,8 +121,17 @@ async def async_step_init(self, user_input=None): data_schema=vol.Schema( { vol.Optional( - CONF_PRECISION, - default=self.config_entry.options.get(CONF_PRECISION, 0), + CONF_READ_PRECISION, + default=self.config_entry.options.get(CONF_READ_PRECISION, 0), + ): vol.All( + vol.Coerce(float), + vol.In( + [0, PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE] + ), + ), + vol.Optional( + CONF_SET_PRECISION, + default=self.config_entry.options.get(CONF_SET_PRECISION, 0), ): vol.All( vol.Coerce(float), vol.In( diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py index 2c3e2f7071d831..1a129d1dfbdfcc 100644 --- a/homeassistant/components/opentherm_gw/const.py +++ b/homeassistant/components/opentherm_gw/const.py @@ -19,6 +19,8 @@ CONF_CLIMATE = "climate" CONF_FLOOR_TEMP = "floor_temperature" CONF_PRECISION = "precision" +CONF_READ_PRECISION = "read_precision" +CONF_SET_PRECISION = "set_precision" DATA_GATEWAYS = "gateways" DATA_OPENTHERM_GW = "opentherm_gw" diff --git a/homeassistant/components/opentherm_gw/strings.json b/homeassistant/components/opentherm_gw/strings.json index 306529e7be1329..937917608faaa0 100644 --- a/homeassistant/components/opentherm_gw/strings.json +++ b/homeassistant/components/opentherm_gw/strings.json @@ -22,7 +22,8 @@ "description": "Options for the OpenTherm Gateway", "data": { "floor_temperature": "Floor Temperature", - "precision": "Precision" + "read_precision": "Read Precision", + "set_precision": "Set Precision" } } } diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 4d811b9f9859bc..43da10b19cf02c 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -9,9 +9,17 @@ from homeassistant.components.opentherm_gw.const import ( CONF_FLOOR_TEMP, CONF_PRECISION, + CONF_READ_PRECISION, + CONF_SET_PRECISION, DOMAIN, ) -from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES +from homeassistant.const import ( + CONF_DEVICE, + CONF_ID, + CONF_NAME, + PRECISION_HALVES, + PRECISION_TENTHS, +) from tests.common import MockConfigEntry @@ -167,6 +175,48 @@ async def test_form_connection_error(hass): assert len(mock_connect.mock_calls) == 1 +async def test_options_migration(hass): + """Test migration of precision option after update.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Mock Gateway", + data={ + CONF_NAME: "Test Entry 1", + CONF_DEVICE: "/dev/ttyUSB0", + CONF_ID: "test_entry_1", + }, + options={ + CONF_FLOOR_TEMP: True, + CONF_PRECISION: PRECISION_TENTHS, + }, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.connect_and_subscribe", + return_value=True, + ), patch("homeassistant.components.opentherm_gw.async_setup", return_value=True): + 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, context={"source": config_entries.SOURCE_USER}, data=None + ) + + 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={}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_READ_PRECISION] == PRECISION_TENTHS + assert result["data"][CONF_SET_PRECISION] == PRECISION_TENTHS + assert result["data"][CONF_FLOOR_TEMP] is True + + async def test_options_form(hass): """Test the options form.""" entry = MockConfigEntry( @@ -181,6 +231,14 @@ async def test_options_form(hass): ) entry.add_to_hass(hass) + with patch( + "homeassistant.components.opentherm_gw.async_setup", return_value=True + ), patch( + "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True + ): + 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, context={"source": "test"}, data=None ) @@ -189,11 +247,16 @@ async def test_options_form(hass): result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={CONF_FLOOR_TEMP: True, CONF_PRECISION: PRECISION_HALVES}, + user_input={ + CONF_FLOOR_TEMP: True, + CONF_READ_PRECISION: PRECISION_HALVES, + CONF_SET_PRECISION: PRECISION_HALVES, + }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_READ_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_SET_PRECISION] == PRECISION_HALVES assert result["data"][CONF_FLOOR_TEMP] is True result = await hass.config_entries.options.async_init( @@ -201,9 +264,28 @@ async def test_options_form(hass): ) result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_PRECISION: 0} + result["flow_id"], user_input={CONF_READ_PRECISION: 0} ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"][CONF_PRECISION] == 0.0 + assert result["data"][CONF_READ_PRECISION] == 0.0 + assert result["data"][CONF_SET_PRECISION] == PRECISION_HALVES assert result["data"][CONF_FLOOR_TEMP] is True + + result = await hass.config_entries.options.async_init( + entry.entry_id, context={"source": "test"}, data=None + ) + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_FLOOR_TEMP: False, + CONF_READ_PRECISION: PRECISION_TENTHS, + CONF_SET_PRECISION: PRECISION_HALVES, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_READ_PRECISION] == PRECISION_TENTHS + assert result["data"][CONF_SET_PRECISION] == PRECISION_HALVES + assert result["data"][CONF_FLOOR_TEMP] is False From d77a28b8a1a9a9beacc306f8f1eed8797819ee66 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 19 Mar 2021 00:03:03 +0000 Subject: [PATCH 1373/1818] [ci skip] Translation update --- .../components/azure_devops/translations/nl.json | 6 +++++- .../components/control4/translations/nl.json | 3 ++- homeassistant/components/demo/translations/nl.json | 1 + .../components/enocean/translations/nl.json | 3 ++- homeassistant/components/hue/translations/nl.json | 1 + .../components/meteo_france/translations/nl.json | 4 ++++ .../components/netatmo/translations/nl.json | 4 ++++ .../components/opentherm_gw/translations/en.json | 4 +++- .../components/poolsense/translations/nl.json | 3 ++- .../components/simplisafe/translations/nl.json | 5 +++++ .../components/wolflink/translations/nl.json | 6 ++++-- .../wolflink/translations/sensor.nl.json | 14 ++++++++++++++ 12 files changed, 47 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/azure_devops/translations/nl.json b/homeassistant/components/azure_devops/translations/nl.json index adf2a84e4e9382..971af5b8d588b7 100644 --- a/homeassistant/components/azure_devops/translations/nl.json +++ b/homeassistant/components/azure_devops/translations/nl.json @@ -9,11 +9,14 @@ "invalid_auth": "Ongeldige authenticatie", "project_error": "Kon geen projectinformatie ophalen." }, + "flow_title": "Azure DevOps: {project_url}", "step": { "reauth": { "data": { "personal_access_token": "Persoonlijk toegangstoken (PAT)" - } + }, + "description": "Authenticatie mislukt voor {project_url}. Voer uw huidige inloggegevens in.", + "title": "Herauthenticatie" }, "user": { "data": { @@ -21,6 +24,7 @@ "personal_access_token": "Persoonlijk toegangstoken (PAT)", "project": "Project" }, + "description": "Stel een Azure DevOps instantie in om toegang te krijgen tot uw project. Een persoonlijke toegangstoken is alleen nodig voor een priv\u00e9project.", "title": "Azure DevOps-project toevoegen" } } diff --git a/homeassistant/components/control4/translations/nl.json b/homeassistant/components/control4/translations/nl.json index d591b4631a4754..f13dd5e7f64c7c 100644 --- a/homeassistant/components/control4/translations/nl.json +++ b/homeassistant/components/control4/translations/nl.json @@ -14,7 +14,8 @@ "host": "IP-adres", "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "description": "Voer uw Control4-accountgegevens en het IP-adres van uw lokale controller in." } } }, diff --git a/homeassistant/components/demo/translations/nl.json b/homeassistant/components/demo/translations/nl.json index ac10172933f14e..8e7c97f7c3f1b8 100644 --- a/homeassistant/components/demo/translations/nl.json +++ b/homeassistant/components/demo/translations/nl.json @@ -10,6 +10,7 @@ "options_1": { "data": { "bool": "Optioneel Boolean", + "constant": "Constant", "int": "Numerieke invoer" } }, diff --git a/homeassistant/components/enocean/translations/nl.json b/homeassistant/components/enocean/translations/nl.json index 27e4091552a8bd..c7dd4985133711 100644 --- a/homeassistant/components/enocean/translations/nl.json +++ b/homeassistant/components/enocean/translations/nl.json @@ -17,7 +17,8 @@ "manual": { "data": { "path": "USB-dongle-pad" - } + }, + "title": "Voer het pad naar uw ENOcean dongle in" } } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index d4047ec35ecc19..0938c18e1ea421 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -58,6 +58,7 @@ "step": { "init": { "data": { + "allow_hue_groups": "Sta Hue-groepen toe", "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden" } } diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index e828fa6e09f77a..c8f7258e100c7a 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -4,11 +4,15 @@ "already_configured": "Stad al geconfigureerd", "unknown": "Onbekende fout: probeer het later nog eens" }, + "error": { + "empty": "Geen resultaat bij het zoeken naar een stad: controleer de invoer: stad" + }, "step": { "cities": { "data": { "city": "Stad" }, + "description": "Kies uw stad uit de lijst", "title": "M\u00e9t\u00e9o-France" }, "user": { diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index 53ff8bb5cb5938..a0f9682fd7461d 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -53,6 +53,10 @@ "title": "Netatmo openbare weersensor" }, "public_weather_areas": { + "data": { + "new_area": "Naam van het gebied", + "weather_areas": "Weersgebieden" + }, "description": "Configureer openbare weersensoren.", "title": "Netatmo openbare weersensor" } diff --git a/homeassistant/components/opentherm_gw/translations/en.json b/homeassistant/components/opentherm_gw/translations/en.json index 9d74a168bae7eb..0743e53e015010 100644 --- a/homeassistant/components/opentherm_gw/translations/en.json +++ b/homeassistant/components/opentherm_gw/translations/en.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Floor Temperature", - "precision": "Precision" + "precision": "Precision", + "read_precision": "Read Precision", + "set_precision": "Set Precision" }, "description": "Options for the OpenTherm Gateway" } diff --git a/homeassistant/components/poolsense/translations/nl.json b/homeassistant/components/poolsense/translations/nl.json index 38ef34d5afc9c8..f88d14e297a9e7 100644 --- a/homeassistant/components/poolsense/translations/nl.json +++ b/homeassistant/components/poolsense/translations/nl.json @@ -12,7 +12,8 @@ "email": "E-mail", "password": "Wachtwoord" }, - "description": "Wil je beginnen met instellen?" + "description": "Wil je beginnen met instellen?", + "title": "PoolSense" } } } diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index 29b9d9ab70bbb8..7a6d9c5c4e32bb 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -7,9 +7,14 @@ "error": { "identifier_exists": "Account bestaat al", "invalid_auth": "Ongeldige authenticatie", + "still_awaiting_mfa": "Wacht nog steeds op MFA-e-mailklik", "unknown": "Onverwachte fout" }, "step": { + "mfa": { + "description": "Controleer uw e-mail voor een link van SimpliSafe. Nadat u de link hebt geverifieerd, gaat u hier terug om de installatie van de integratie te voltooien.", + "title": "SimpliSafe Multi-Factor Authenticatie" + }, "reauth_confirm": { "data": { "password": "Wachtwoord" diff --git a/homeassistant/components/wolflink/translations/nl.json b/homeassistant/components/wolflink/translations/nl.json index d3cde252467534..069ed92896244f 100644 --- a/homeassistant/components/wolflink/translations/nl.json +++ b/homeassistant/components/wolflink/translations/nl.json @@ -12,13 +12,15 @@ "device": { "data": { "device_name": "Apparaat" - } + }, + "title": "Selecteer WOLF-apparaat" }, "user": { "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "WOLF SmartSet-verbinding" } } } diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index 02e8e3f8c2eaee..4056e4762ec728 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -2,14 +2,27 @@ "state": { "wolflink__state": { "1_x_warmwasser": "1 x DHW", + "abgasklappe": "Rookgasklep", + "aktiviert": "Geactiveerd", + "antilegionellenfunktion": "Anti-legionella functie", + "at_abschaltung": "OT afsluiten", + "at_frostschutz": "OT vorstbescherming", + "aus": "Uitgeschakeld", "auto": "Auto", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", "automatik_aus": "Automatisch UIT", "automatik_ein": "Automatisch AAN", + "bereit_keine_ladung": "Klaar, niet laden", + "betrieb_ohne_brenner": "Werken zonder brander", "cooling": "Koelen", "deaktiviert": "Inactief", "dhw_prior": "DHWPrior", "eco": "Eco", "ein": "Ingeschakeld", + "estrichtrocknung": "Dekvloer drogen", + "externe_deaktivierung": "Externe uitschakeling", + "fernschalter_ein": "Op afstand bedienen ingeschakeld", "frost_warmwasser": "DHW vorst", "frostschutz": "Vorstbescherming", "gasdruck": "Gasdruk", @@ -20,6 +33,7 @@ "initialisierung": "Initialisatie", "kalibration": "Kalibratie", "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", + "kalibration_kombibetrieb": "Kalibratie van de combimodus", "kalibration_warmwasserbetrieb": "DHW-kalibratie", "kombibetrieb": "Combi-modus", "kombigerat": "Combiketel", From 2f159577074e98c472ca29f8c456b5f00cfd5f60 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Mar 2021 09:57:11 +0100 Subject: [PATCH 1374/1818] Refactor Netatmo test (#48097) * Refactor webhook simulate * Update test_climate.py --- tests/components/netatmo/common.py | 20 ++ tests/components/netatmo/test_climate.py | 409 +++++++++++++++-------- 2 files changed, 290 insertions(+), 139 deletions(-) diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index c8014a9b2a930b..b952d6fe790749 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -1,6 +1,9 @@ """Common methods used across tests for Netatmo.""" import json +from homeassistant.components.webhook import async_handle_webhook +from homeassistant.util.aiohttp import MockRequest + from tests.common import load_fixture CLIENT_ID = "1234" @@ -19,6 +22,13 @@ "write_thermostat", ] +COMMON_RESPONSE = { + "user_id": "91763b24c43d3e344f424e8d", + "home_id": "91763b24c43d3e344f424e8b", + "home_name": "MYHOME", + "user": {"id": "91763b24c43d3e344f424e8b", "email": "john@doe.com"}, +} + def fake_post_request(**args): """Return fake data.""" @@ -42,3 +52,13 @@ def fake_post_request(**args): def fake_post_request_no_data(**args): """Fake error during requesting backend data.""" return "{}" + + +async def simulate_webhook(hass, webhook_id, response): + """Simulate a webhook event.""" + request = MockRequest( + content=bytes(json.dumps({**COMMON_RESPONSE, **response}), "utf-8"), + mock_source="test", + ) + await async_handle_webhook(hass, webhook_id, request) + await hass.async_block_till_done() diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index e7ad1669769632..910fb32a7cf601 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -31,16 +31,9 @@ ATTR_SCHEDULE_NAME, SERVICE_SET_SCHEDULE, ) -from homeassistant.components.webhook import async_handle_webhook from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID -from homeassistant.util.aiohttp import MockRequest - -async def simulate_webhook(hass, webhook_id, response): - """Simulate a webhook event.""" - request = MockRequest(content=response, mock_source="test") - await async_handle_webhook(hass, webhook_id, request) - await hass.async_block_till_done() +from .common import simulate_webhook async def test_webhook_event_handling_thermostats(hass, climate_entry): @@ -65,16 +58,31 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat manual set point - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' - b'"rooms": [{ "id": "2746182631", "name": "Livingroom", "type": "livingroom",' - b'"therm_setpoint_mode": "manual", "therm_setpoint_temperature": 21,' - b'"therm_setpoint_end_time": 1612734552}], "modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "manual", "event_type": "set_point",' - b'"temperature": 21, "push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "manual", + "therm_setpoint_temperature": 21, + "therm_setpoint_end_time": 1612734552, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "manual", + "event_type": "set_point", + "temperature": 21, + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" @@ -94,15 +102,29 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' - b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' - b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" @@ -118,15 +140,27 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook turn thermostat off - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' - b'"therm_setpoint_mode": "off"}],"modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "off", "event_type": "set_point",' - b'"push_type": "display_change"}' - ) + response = { + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "off", + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "off", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "off" @@ -141,15 +175,28 @@ async def test_webhook_event_handling_thermostats(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode cancel set point - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b","room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2746182631","name": "Livingroom","type": "livingroom",' - b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Livingroom", "type": "NATherm1"}]}, "mode": "home",' - b'"event_type": "cancel_set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "home", + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -183,13 +230,13 @@ async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode change to "Frost Guard" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"therm_mode": "hg"}, "mode": "hg", "previous_mode": "schedule",' - b'"push_type":"home_event_changed"}' - ) + response = { + "event_type": "therm_mode", + "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "hg"}, + "mode": "hg", + "previous_mode": "schedule", + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -211,13 +258,13 @@ async def test_service_preset_mode_frost_guard_thermostat(hass, climate_entry): await hass.async_block_till_done() # Test webhook thermostat mode change to "Schedule" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"therm_mode": "schedule"}, "mode": "schedule", "previous_mode": "hg",' - b'"push_type": "home_event_changed"}' - ) + response = { + "event_type": "therm_mode", + "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "schedule"}, + "mode": "schedule", + "previous_mode": "hg", + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -248,13 +295,13 @@ async def test_service_preset_modes_thermostat(hass, climate_entry): await hass.async_block_till_done() # Fake webhook thermostat mode change to "Away" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b","user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", ' - b'"event_type": "therm_mode","home": {"id": "91763b24c43d3e344f424e8b",' - b'"therm_mode": "away"},"mode": "away","previous_mode": "schedule",' - b'"push_type": "home_event_changed"}' - ) + response = { + "event_type": "therm_mode", + "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "away"}, + "mode": "away", + "previous_mode": "schedule", + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" @@ -271,16 +318,30 @@ async def test_service_preset_modes_thermostat(hass, climate_entry): ) await hass.async_block_till_done() - # TFakeest webhook thermostat mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email":"john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2746182631",' - b'"home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE",' - b'"rooms": [{"id": "2746182631", "name": "Livingroom", "type": "livingroom",' - b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + # Test webhook thermostat mode change to "Max" + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" @@ -292,31 +353,42 @@ async def test_webhook_event_handling_no_data(hass, climate_entry): # Test webhook without home entry webhook_id = climate_entry.data[CONF_WEBHOOK_ID] - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"push_type": "home_event_changed"}' - ) + response = { + "push_type": "home_event_changed", + } await simulate_webhook(hass, webhook_id, response) # Test webhook with different home id - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "3d3e344f491763b24c424e8b",' - b'"room_id": "2746182631", "home": {"id": "3d3e344f491763b24c424e8b",' - b'"name": "MYHOME","country": "DE", "rooms": [], "modules": []}, "mode": "home",' - b'"event_type": "cancel_set_point", "push_type": "display_change"}' - ) + response = { + "home_id": "3d3e344f491763b24c424e8b", + "room_id": "2746182631", + "home": { + "id": "3d3e344f491763b24c424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [], + "modules": [], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) # Test webhook without room entries - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2746182631", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"name": "MYHOME", "country": "DE", "rooms": [], "modules": []}, "mode": "home",' - b'"event_type": "cancel_set_point","push_type": "display_change"}' - ) + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [], + "modules": [], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) @@ -363,15 +435,27 @@ async def test_service_preset_mode_already_boost_valves(hass, climate_entry): assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 # Test webhook valve mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' - b'"country": "DE","rooms": [{"id": "2833524037", "name": "Entrada", "type": "lobby",' - b'"therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' - b'"mode": "max","event_type": "set_point","push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) # Test service setting the preset mode to "boost" @@ -384,15 +468,27 @@ async def test_service_preset_mode_already_boost_valves(hass, climate_entry): await hass.async_block_till_done() # Test webhook valve mode change to "Max" - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b",' - b'"name": "MYHOME","country": "DE","rooms": [{"id": "2833524037", "name": "Entrada",' - b'"type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat" @@ -417,15 +513,27 @@ async def test_service_preset_mode_boost_valves(hass, climate_entry): await hass.async_block_till_done() # Fake backend response - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b",' - b'"room_id": "2833524037", "home": {"id": "91763b24c43d3e344f424e8b", "name": "MYHOME",' - b'"country": "DE", "rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' - b'"therm_setpoint_mode": "max","therm_setpoint_end_time": 1612749189}],' - b'"modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}]},' - b'"mode": "max", "event_type": "set_point", "push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat" @@ -460,14 +568,26 @@ async def test_valves_service_turn_off(hass, climate_entry): await hass.async_block_till_done() # Fake backend response for valve being turned off - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' - b'"therm_setpoint_mode": "off"}], "modules": [{"id": "12:34:56:00:01:ae","name": "Entrada",' - b'"type": "NRV"}]}, "mode": "off", "event_type": "set_point", "push_type":"display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "off", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "off", + "event_type": "set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "off" @@ -488,15 +608,26 @@ async def test_valves_service_turn_on(hass, climate_entry): await hass.async_block_till_done() # Fake backend response for valve being turned on - response = ( - b'{"user_id": "91763b24c43d3e344f424e8b", "user": {"id": "91763b24c43d3e344f424e8b",' - b'"email": "john@doe.com"}, "home_id": "91763b24c43d3e344f424e8b", "room_id": "2833524037",' - b'"home": {"id": "91763b24c43d3e344f424e8b","name": "MYHOME","country": "DE",' - b'"rooms": [{"id": "2833524037","name": "Entrada","type": "lobby",' - b'"therm_setpoint_mode": "home"}], "modules": [{"id": "12:34:56:00:01:ae",' - b'"name": "Entrada", "type": "NRV"}]}, "mode": "home", "event_type": "cancel_set_point",' - b'"push_type": "display_change"}' - ) + response = { + "room_id": "2833524037", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "auto" From 987c2d1612a69422fb0cf6e94cf8ed5b69b6fbd0 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:12:55 +0100 Subject: [PATCH 1375/1818] Type check KNX integration expose (#48055) --- homeassistant/components/knx/__init__.py | 2 +- homeassistant/components/knx/expose.py | 64 +++++++++++++++--------- homeassistant/components/knx/schema.py | 18 ++++++- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 348eac8f40e84a..c252572e28e642 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -195,7 +195,7 @@ ) SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any( - ExposeSchema.SCHEMA.extend( + ExposeSchema.EXPOSE_SENSOR_SCHEMA.extend( { vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, } diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 8bdd3d1d1d14db..3d28c394140a38 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -1,6 +1,8 @@ """Exposures to KNX bus.""" from __future__ import annotations +from typing import Callable + from xknx import XKNX from xknx.devices import DateTime, ExposeSensor @@ -11,9 +13,14 @@ STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import callback from homeassistant.helpers.event import async_track_state_change_event -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import ( + ConfigType, + EventType, + HomeAssistantType, + StateType, +) from .const import KNX_ADDRESS from .schema import ExposeSchema @@ -21,19 +28,22 @@ @callback def create_knx_exposure( - hass: HomeAssistant, xknx: XKNX, config: ConfigType + hass: HomeAssistantType, xknx: XKNX, config: ConfigType ) -> KNXExposeSensor | KNXExposeTime: """Create exposures from config.""" address = config[KNX_ADDRESS] + expose_type = config[ExposeSchema.CONF_KNX_EXPOSE_TYPE] attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) - entity_id = config.get(CONF_ENTITY_ID) - expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) exposure: KNXExposeSensor | KNXExposeTime - if expose_type.lower() in ["time", "date", "datetime"]: + if ( + isinstance(expose_type, str) + and expose_type.lower() in ExposeSchema.EXPOSE_TIME_TYPES + ): exposure = KNXExposeTime(xknx, expose_type, address) else: + entity_id = config[CONF_ENTITY_ID] exposure = KNXExposeSensor( hass, xknx, @@ -43,14 +53,22 @@ def create_knx_exposure( default, address, ) - exposure.async_register() return exposure class KNXExposeSensor: """Object to Expose Home Assistant entity to KNX bus.""" - def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, address): + def __init__( + self, + hass: HomeAssistantType, + xknx: XKNX, + expose_type: int | str, + entity_id: str, + attribute: str | None, + default: StateType, + address: str, + ) -> None: """Initialize of Expose class.""" self.hass = hass self.xknx = xknx @@ -59,17 +77,17 @@ def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, addre self.expose_attribute = attribute self.expose_default = default self.address = address - self.device = None - self._remove_listener = None + self._remove_listener: Callable[[], None] | None = None + self.device: ExposeSensor = self.async_register() @callback - def async_register(self): + def async_register(self) -> ExposeSensor: """Register listener.""" if self.expose_attribute is not None: _name = self.entity_id + "__" + self.expose_attribute else: _name = self.entity_id - self.device = ExposeSensor( + device = ExposeSensor( self.xknx, name=_name, group_address=self.address, @@ -78,6 +96,7 @@ def async_register(self): self._remove_listener = async_track_state_change_event( self.hass, [self.entity_id], self._async_entity_changed ) + return device @callback def shutdown(self) -> None: @@ -85,10 +104,9 @@ def shutdown(self) -> None: if self._remove_listener is not None: self._remove_listener() self._remove_listener = None - if self.device is not None: - self.device.shutdown() + self.device.shutdown() - async def _async_entity_changed(self, event): + async def _async_entity_changed(self, event: EventType) -> None: """Handle entity change.""" new_state = event.data.get("new_state") if new_state is None: @@ -110,8 +128,9 @@ async def _async_entity_changed(self, event): return await self._async_set_knx_value(new_attribute) - async def _async_set_knx_value(self, value): + async def _async_set_knx_value(self, value: StateType) -> None: """Set new value on xknx ExposeSensor.""" + assert self.device is not None if value is None: if self.expose_default is None: return @@ -129,17 +148,17 @@ async def _async_set_knx_value(self, value): class KNXExposeTime: """Object to Expose Time/Date object to KNX bus.""" - def __init__(self, xknx: XKNX, expose_type: str, address: str): + def __init__(self, xknx: XKNX, expose_type: str, address: str) -> None: """Initialize of Expose class.""" self.xknx = xknx self.expose_type = expose_type self.address = address - self.device = None + self.device: DateTime = self.async_register() @callback - def async_register(self): + def async_register(self) -> DateTime: """Register listener.""" - self.device = DateTime( + return DateTime( self.xknx, name=self.expose_type.capitalize(), broadcast_type=self.expose_type.upper(), @@ -148,7 +167,6 @@ def async_register(self): ) @callback - def shutdown(self): + def shutdown(self) -> None: """Prepare for deletion.""" - if self.device is not None: - self.device.shutdown() + self.device.shutdown() diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 376e86cf90cf51..0e290311f28665 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -254,16 +254,30 @@ class ExposeSchema: CONF_KNX_EXPOSE_TYPE = CONF_TYPE CONF_KNX_EXPOSE_ATTRIBUTE = "attribute" CONF_KNX_EXPOSE_DEFAULT = "default" + EXPOSE_TIME_TYPES = [ + "time", + "date", + "datetime", + ] - SCHEMA = vol.Schema( + EXPOSE_TIME_SCHEMA = vol.Schema( + { + vol.Required(CONF_KNX_EXPOSE_TYPE): vol.All( + cv.string, str.lower, vol.In(EXPOSE_TIME_TYPES) + ), + vol.Required(KNX_ADDRESS): ga_validator, + } + ) + EXPOSE_SENSOR_SCHEMA = vol.Schema( { vol.Required(CONF_KNX_EXPOSE_TYPE): sensor_type_validator, vol.Required(KNX_ADDRESS): ga_validator, - vol.Optional(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, } ) + SCHEMA = vol.Any(EXPOSE_TIME_SCHEMA, EXPOSE_SENSOR_SCHEMA) class FanSchema: From 943ce8afaff83a69ab05410a68e5321d58609d93 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:16:27 +0100 Subject: [PATCH 1376/1818] Type check KNX integration weather, notify and scene (#48051) --- homeassistant/components/knx/notify.py | 23 +++++++++++----- homeassistant/components/knx/scene.py | 20 +++++++++++--- homeassistant/components/knx/weather.py | 35 ++++++++++++++++++------- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 9d6ed35e36b9cb..77b89b86c4d314 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,14 +1,25 @@ """Support for KNX/IP notification services.""" from __future__ import annotations +from typing import Any + from xknx.devices import Notification as XknxNotification from homeassistant.components.notify import BaseNotificationService +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN -async def async_get_service(hass, config, discovery_info=None): +async def async_get_service( + hass: HomeAssistantType, + config: ConfigType, + discovery_info: DiscoveryInfoType | None = None, +) -> KNXNotificationService | None: """Get the KNX notification service.""" notification_devices = [] for device in hass.data[DOMAIN].xknx.devices: @@ -22,31 +33,31 @@ async def async_get_service(hass, config, discovery_info=None): class KNXNotificationService(BaseNotificationService): """Implement demo notification service.""" - def __init__(self, devices: list[XknxNotification]): + def __init__(self, devices: list[XknxNotification]) -> None: """Initialize the service.""" self.devices = devices @property - def targets(self): + def targets(self) -> dict[str, str]: """Return a dictionary of registered targets.""" ret = {} for device in self.devices: ret[device.name] = device.name return ret - async def async_send_message(self, message="", **kwargs): + async def async_send_message(self, message: str = "", **kwargs: Any) -> None: """Send a notification to knx bus.""" if "target" in kwargs: await self._async_send_to_device(message, kwargs["target"]) else: await self._async_send_to_all_devices(message) - async def _async_send_to_all_devices(self, message): + async def _async_send_to_all_devices(self, message: str) -> None: """Send a notification to knx bus to all connected devices.""" for device in self.devices: await device.set(message) - async def _async_send_to_device(self, message, names): + async def _async_send_to_device(self, message: str, names: str) -> None: """Send a notification to knx bus to device with given names.""" for device in self.devices: if device.name in names: diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index 6c76fdbd199e2b..bc235da4f35357 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -1,15 +1,28 @@ """Support for KNX scenes.""" -from typing import Any +from __future__ import annotations + +from typing import Any, Callable, Iterable from xknx.devices import Scene as XknxScene from homeassistant.components.scene import Scene +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up the scenes for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -21,8 +34,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXScene(KnxEntity, Scene): """Representation of a KNX scene.""" - def __init__(self, device: XknxScene): + def __init__(self, device: XknxScene) -> None: """Init KNX scene.""" + self._device: XknxScene super().__init__(device) async def async_activate(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py index 031af9f5af014f..c022867284e842 100644 --- a/homeassistant/components/knx/weather.py +++ b/homeassistant/components/knx/weather.py @@ -1,16 +1,30 @@ """Support for KNX/IP weather station.""" +from __future__ import annotations + +from typing import Callable, Iterable from xknx.devices import Weather as XknxWeather from homeassistant.components.weather import WeatherEntity from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the scenes for KNX platform.""" +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up weather entities for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxWeather): @@ -21,22 +35,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXWeather(KnxEntity, WeatherEntity): """Representation of a KNX weather device.""" - def __init__(self, device: XknxWeather): + def __init__(self, device: XknxWeather) -> None: """Initialize of a KNX sensor.""" + self._device: XknxWeather super().__init__(device) @property - def temperature(self): + def temperature(self) -> float | None: """Return current temperature.""" return self._device.temperature @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return temperature unit.""" return TEMP_CELSIUS @property - def pressure(self): + def pressure(self) -> float | None: """Return current air pressure.""" # KNX returns pA - HA requires hPa return ( @@ -46,22 +61,22 @@ def pressure(self): ) @property - def condition(self): + def condition(self) -> str: """Return current weather condition.""" return self._device.ha_current_state().value @property - def humidity(self): + def humidity(self) -> float | None: """Return current humidity.""" return self._device.humidity @property - def wind_bearing(self): + def wind_bearing(self) -> int | None: """Return current wind bearing in degrees.""" return self._device.wind_bearing @property - def wind_speed(self): + def wind_speed(self) -> float | None: """Return current wind speed in km/h.""" # KNX only supports wind speed in m/s return ( From fb1e76db8c84b06b541df659a9dae4a8dee4cf78 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:21:06 +0100 Subject: [PATCH 1377/1818] Type check KNX integration light (#48053) * type check light * review changes --- homeassistant/components/knx/light.py | 62 +++++++++++++++++---------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index 50d067bf29ac57..940a9333696551 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -1,4 +1,8 @@ """Support for KNX/IP lights.""" +from __future__ import annotations + +from typing import Any, Callable, Iterable + from xknx.devices import Light as XknxLight from homeassistant.components.light import ( @@ -12,17 +16,29 @@ SUPPORT_WHITE_VALUE, LightEntity, ) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) import homeassistant.util.color as color_util from .const import DOMAIN from .knx_entity import KnxEntity +from .schema import LightSchema DEFAULT_COLOR = (0.0, 0.0) DEFAULT_BRIGHTNESS = 255 DEFAULT_WHITE_VALUE = 255 -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up lights for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -34,12 +50,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXLight(KnxEntity, LightEntity): """Representation of a KNX light.""" - def __init__(self, device: XknxLight): + def __init__(self, device: XknxLight) -> None: """Initialize of KNX light.""" + self._device: XknxLight super().__init__(device) - self._min_kelvin = device.min_kelvin - self._max_kelvin = device.max_kelvin + self._min_kelvin = device.min_kelvin or LightSchema.DEFAULT_MIN_KELVIN + self._max_kelvin = device.max_kelvin or LightSchema.DEFAULT_MAX_KELVIN self._min_mireds = color_util.color_temperature_kelvin_to_mired( self._max_kelvin ) @@ -48,7 +65,7 @@ def __init__(self, device: XknxLight): ) @property - def brightness(self): + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" if self._device.supports_brightness: return self._device.current_brightness @@ -58,31 +75,31 @@ def brightness(self): return None @property - def hs_color(self): + def hs_color(self) -> tuple[float, float] | None: """Return the HS color value.""" - rgb = None + rgb: tuple[int, int, int] | None = None if self._device.supports_rgbw or self._device.supports_color: rgb, _ = self._device.current_color return color_util.color_RGB_to_hs(*rgb) if rgb else None @property - def _hsv_color(self): + def _hsv_color(self) -> tuple[float, float, float] | None: """Return the HSV color value.""" - rgb = None + rgb: tuple[int, int, int] | None = None if self._device.supports_rgbw or self._device.supports_color: rgb, _ = self._device.current_color return color_util.color_RGB_to_hsv(*rgb) if rgb else None @property - def white_value(self): + def white_value(self) -> int | None: """Return the white value.""" - white = None + white: int | None = None if self._device.supports_rgbw: _, white = self._device.current_color return white @property - def color_temp(self): + def color_temp(self) -> int | None: """Return the color temperature in mireds.""" if self._device.supports_color_temperature: kelvin = self._device.current_color_temperature @@ -101,32 +118,32 @@ def color_temp(self): return None @property - def min_mireds(self): + def min_mireds(self) -> int: """Return the coldest color temp this light supports in mireds.""" return self._min_mireds @property - def max_mireds(self): + def max_mireds(self) -> int: """Return the warmest color temp this light supports in mireds.""" return self._max_mireds @property - def effect_list(self): + def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" return None @property - def effect(self): + def effect(self) -> str | None: """Return the current effect.""" return None @property - def is_on(self): + def is_on(self) -> bool: """Return true if light is on.""" - return self._device.state + return bool(self._device.state) @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" flags = 0 if self._device.supports_brightness: @@ -142,7 +159,7 @@ def supported_features(self): flags |= SUPPORT_COLOR_TEMP return flags - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the light on.""" brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color) @@ -183,7 +200,8 @@ async def async_turn_on(self, **kwargs): hs_color = DEFAULT_COLOR if white_value is None and self._device.supports_rgbw: white_value = DEFAULT_WHITE_VALUE - rgb = color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255) + hsv_color = hs_color + (brightness * 100 / 255,) + rgb = color_util.color_hsv_to_RGB(*hsv_color) await self._device.set_color(rgb, white_value) if update_color_temp: @@ -200,6 +218,6 @@ async def async_turn_on(self, **kwargs): ) await self._device.set_tunable_white(relative_ct) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" await self._device.set_off() From e522b311ce883eb3a1c04fadd2c968454882e552 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:22:18 +0100 Subject: [PATCH 1378/1818] Type check KNX integration binary_sensor, sensor and switch (#48050) --- homeassistant/components/knx/binary_sensor.py | 22 ++++++++++--- homeassistant/components/knx/sensor.py | 26 ++++++++++++--- homeassistant/components/knx/switch.py | 32 ++++++++++++++----- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index 6ee37abee181ca..df5a367098d0ac 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,17 +1,28 @@ """Support for KNX/IP binary sensors.""" from __future__ import annotations -from typing import Any +from typing import Any, Callable, Iterable from xknx.devices import BinarySensor as XknxBinarySensor from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import ATTR_COUNTER, DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up binary sensor(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -23,19 +34,20 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXBinarySensor(KnxEntity, BinarySensorEntity): """Representation of a KNX binary sensor.""" - def __init__(self, device: XknxBinarySensor): + def __init__(self, device: XknxBinarySensor) -> None: """Initialize of KNX binary sensor.""" + self._device: XknxBinarySensor super().__init__(device) @property - def device_class(self): + def device_class(self) -> str | None: """Return the class of this sensor.""" if self._device.device_class in DEVICE_CLASSES: return self._device.device_class return None @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary sensor is on.""" return self._device.is_on() diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index 2409d7a6425eef..f3155eebf015b3 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -1,14 +1,29 @@ """Support for KNX/IP sensors.""" +from __future__ import annotations + +from typing import Callable, Iterable + from xknx.devices import Sensor as XknxSensor from homeassistant.components.sensor import DEVICE_CLASSES from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, + StateType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up sensor(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -20,22 +35,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXSensor(KnxEntity, Entity): """Representation of a KNX sensor.""" - def __init__(self, device: XknxSensor): + def __init__(self, device: XknxSensor) -> None: """Initialize of a KNX sensor.""" + self._device: XknxSensor super().__init__(device) @property - def state(self): + def state(self) -> StateType: """Return the state of the sensor.""" return self._device.resolve_state() @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return self._device.unit_of_measurement() @property - def device_class(self): + def device_class(self) -> str | None: """Return the device class of the sensor.""" device_class = self._device.ha_device_class() if device_class in DEVICE_CLASSES: diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index ae3048e2d23adc..40b028267baf37 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -1,13 +1,28 @@ """Support for KNX/IP switches.""" +from __future__ import annotations + +from typing import Any, Callable, Iterable + from xknx.devices import Switch as XknxSwitch from homeassistant.components.switch import SwitchEntity - -from . import DOMAIN +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) + +from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up switch(es) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -19,19 +34,20 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXSwitch(KnxEntity, SwitchEntity): """Representation of a KNX switch.""" - def __init__(self, device: XknxSwitch): + def __init__(self, device: XknxSwitch) -> None: """Initialize of KNX switch.""" + self._device: XknxSwitch super().__init__(device) @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" - return self._device.state + return bool(self._device.state) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" await self._device.set_on() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self._device.set_off() From 66b537c0e36c68f8072dbbc3a4259b6d69e6160e Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 10:23:50 +0100 Subject: [PATCH 1379/1818] Type check KNX integration factory and schema (#48045) these are used non-optional anyway get them per config[] notation --- homeassistant/components/knx/factory.py | 8 ++++---- homeassistant/components/knx/schema.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 0543119cadf695..827ec83a8e18f4 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -264,9 +264,9 @@ def _create_climate(knx_module: XKNX, config: ConfigType) -> XknxClimate: max_temp=config.get(ClimateSchema.CONF_MAX_TEMP), mode=climate_mode, on_off_invert=config[ClimateSchema.CONF_ON_OFF_INVERT], - create_temperature_sensors=config.get( + create_temperature_sensors=config[ ClimateSchema.CONF_CREATE_TEMPERATURE_SENSORS - ), + ], ) @@ -277,7 +277,7 @@ def _create_switch(knx_module: XKNX, config: ConfigType) -> XknxSwitch: name=config[CONF_NAME], group_address=config[KNX_ADDRESS], group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS), - invert=config.get(SwitchSchema.CONF_INVERT), + invert=config[SwitchSchema.CONF_INVERT], ) @@ -320,7 +320,7 @@ def _create_binary_sensor(knx_module: XKNX, config: ConfigType) -> XknxBinarySen knx_module, name=device_name, group_address_state=config[BinarySensorSchema.CONF_STATE_ADDRESS], - invert=config.get(BinarySensorSchema.CONF_INVERT), + invert=config[BinarySensorSchema.CONF_INVERT], sync_state=config[BinarySensorSchema.CONF_SYNC_STATE], device_class=config.get(CONF_DEVICE_CLASS), ignore_internal_state=config[BinarySensorSchema.CONF_IGNORE_INTERNAL_STATE], diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 0e290311f28665..6ff9d295b751eb 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -30,13 +30,13 @@ ################## ga_validator = vol.Any( - cv.matches_regex(GroupAddress.ADDRESS_RE), + cv.matches_regex(GroupAddress.ADDRESS_RE.pattern), vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), msg="value does not match pattern for KNX group address '
//', '
/' or '' (eg.'1/2/3', '9/234', '123')", ) ia_validator = vol.Any( - cv.matches_regex(IndividualAddress.ADDRESS_RE), + cv.matches_regex(IndividualAddress.ADDRESS_RE.pattern), vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)), msg="value does not match pattern for KNX individual address '..' (eg.'1.1.100')", ) @@ -96,12 +96,12 @@ class BinarySensorSchema: vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean, + vol.Optional(CONF_INVERT, default=False): cv.boolean, vol.Required(CONF_STATE_ADDRESS): ga_validator, vol.Optional(CONF_CONTEXT_TIMEOUT): vol.All( vol.Coerce(float), vol.Range(min=0, max=10) ), vol.Optional(CONF_DEVICE_CLASS): cv.string, - vol.Optional(CONF_INVERT): cv.boolean, vol.Optional(CONF_RESET_AFTER): cv.positive_float, } ), @@ -448,9 +448,9 @@ class SwitchSchema: SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_INVERT, default=False): cv.boolean, vol.Required(KNX_ADDRESS): ga_validator, vol.Optional(CONF_STATE_ADDRESS): ga_validator, - vol.Optional(CONF_INVERT): cv.boolean, } ) From 7858b599449d1aa90c4f6a10c62c098a2e66deb7 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 19 Mar 2021 10:25:17 +0100 Subject: [PATCH 1380/1818] Use device class voltage in NUT integration (#48096) --- homeassistant/components/nut/const.py | 36 +++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index b8b315df59e24e..890ac3697dd6b4 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -4,6 +4,7 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, ) from homeassistant.const import ( ELECTRICAL_CURRENT_AMPERE, @@ -120,10 +121,15 @@ None, ], "battery.charger.status": ["Charging Status", "", "mdi:information-outline", None], - "battery.voltage": ["Battery Voltage", VOLT, "mdi:flash", None], - "battery.voltage.nominal": ["Nominal Battery Voltage", VOLT, "mdi:flash", None], - "battery.voltage.low": ["Low Battery Voltage", VOLT, "mdi:flash", None], - "battery.voltage.high": ["High Battery Voltage", VOLT, "mdi:flash", None], + "battery.voltage": ["Battery Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "battery.voltage.nominal": [ + "Nominal Battery Voltage", + VOLT, + None, + DEVICE_CLASS_VOLTAGE, + ], + "battery.voltage.low": ["Low Battery Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "battery.voltage.high": ["High Battery Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], "battery.capacity": ["Battery Capacity", "Ah", "mdi:flash", None], "battery.current": [ "Battery Current", @@ -178,16 +184,21 @@ "mdi:information-outline", None, ], - "input.transfer.low": ["Low Voltage Transfer", VOLT, "mdi:flash", None], - "input.transfer.high": ["High Voltage Transfer", VOLT, "mdi:flash", None], + "input.transfer.low": ["Low Voltage Transfer", VOLT, None, DEVICE_CLASS_VOLTAGE], + "input.transfer.high": ["High Voltage Transfer", VOLT, None, DEVICE_CLASS_VOLTAGE], "input.transfer.reason": [ "Voltage Transfer Reason", "", "mdi:information-outline", None, ], - "input.voltage": ["Input Voltage", VOLT, "mdi:flash", None], - "input.voltage.nominal": ["Nominal Input Voltage", VOLT, "mdi:flash", None], + "input.voltage": ["Input Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "input.voltage.nominal": [ + "Nominal Input Voltage", + VOLT, + None, + DEVICE_CLASS_VOLTAGE, + ], "input.frequency": ["Input Line Frequency", FREQUENCY_HERTZ, "mdi:flash", None], "input.frequency.nominal": [ "Nominal Input Line Frequency", @@ -208,8 +219,13 @@ "mdi:flash", None, ], - "output.voltage": ["Output Voltage", VOLT, "mdi:flash", None], - "output.voltage.nominal": ["Nominal Output Voltage", VOLT, "mdi:flash", None], + "output.voltage": ["Output Voltage", VOLT, None, DEVICE_CLASS_VOLTAGE], + "output.voltage.nominal": [ + "Nominal Output Voltage", + VOLT, + None, + DEVICE_CLASS_VOLTAGE, + ], "output.frequency": ["Output Frequency", FREQUENCY_HERTZ, "mdi:flash", None], "output.frequency.nominal": [ "Nominal Output Frequency", From aaafe399a1804410d1d5a227950cd19cba3aa8cc Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Mar 2021 12:19:27 +0100 Subject: [PATCH 1381/1818] Add tests for Netatmo light (#46381) * Add tests for Netatmo light * Improve docstring * Register the camera data class for the light platform * Remove freezegun dependency * Update tests * Update tests/components/netatmo/test_light.py Co-authored-by: Erik Montnemery * Deduplicate webhook test data * Mock pytest to verify it is called * Don't test internals * Rename * Assert light still on with erroneous event data Co-authored-by: Erik Montnemery --- .coveragerc | 1 - homeassistant/components/netatmo/light.py | 6 ++ tests/components/netatmo/conftest.py | 6 ++ tests/components/netatmo/test_light.py | 79 +++++++++++++++++++++++ tests/fixtures/netatmo/ping.json | 4 ++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/components/netatmo/test_light.py create mode 100644 tests/fixtures/netatmo/ping.json diff --git a/.coveragerc b/.coveragerc index 92d55e83527ee7..71dbda8ce401a4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -647,7 +647,6 @@ omit = homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py - homeassistant/components/netatmo/light.py homeassistant/components/netatmo/netatmo_entity_base.py homeassistant/components/netatmo/sensor.py homeassistant/components/netatmo/webhook.py diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index eed12f048c85e3..08744c462e8402 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -31,6 +31,10 @@ async def async_setup_entry(hass, entry, async_add_entities): 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 + ) + if CAMERA_DATA_CLASS_NAME not in data_handler.data: raise PlatformNotReady @@ -64,6 +68,8 @@ async def get_entities(): async_add_entities(await get_entities(), True) + await data_handler.unregister_data_class(CAMERA_DATA_CLASS_NAME, None) + class NetatmoLight(NetatmoBase, LightEntity): """Representation of a Netatmo Presence camera light.""" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index b18b70f323eced..4e59eaacb8271c 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -81,6 +81,8 @@ async def mock_sensor_entry_fixture(hass, config_entry): """Mock setup of sensor platform.""" with selected_platforms(["sensor"]): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry @@ -89,6 +91,8 @@ async def mock_camera_entry_fixture(hass, config_entry): """Mock setup of camera platform.""" with selected_platforms(["camera"]): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry @@ -97,6 +101,8 @@ async def mock_light_entry_fixture(hass, config_entry): """Mock setup of light platform.""" with selected_platforms(["light"]): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py new file mode 100644 index 00000000000000..8f2f371a15e2f4 --- /dev/null +++ b/tests/components/netatmo/test_light.py @@ -0,0 +1,79 @@ +"""The tests for Netatmo light.""" +from unittest.mock import patch + +from homeassistant.components.light import ( + DOMAIN as LIGHT_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ATTR_ENTITY_ID, CONF_WEBHOOK_ID + +from .common import simulate_webhook + + +async def test_light_setup_and_services(hass, light_entry): + """Test setup and services.""" + webhook_id = light_entry.data[CONF_WEBHOOK_ID] + + # Fake webhook activation + webhook_data = { + "push_type": "webhook_activation", + } + await simulate_webhook(hass, webhook_id, webhook_data) + await hass.async_block_till_done() + + light_entity = "light.netatmo_garden" + assert hass.states.get(light_entity).state == "unavailable" + + # Trigger light mode change + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "camera_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + "sub_type": "on", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(light_entity).state == "on" + + # Trigger light mode change with erroneous webhook data + response = { + "user_id": "91763b24c43d3e344f424e8d", + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(light_entity).state == "on" + + # Test turning light off + with patch("pyatmo.camera.CameraData.set_state") as mock_set_state: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: light_entity}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_state.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", + camera_id="12:34:56:00:a5:a4", + floodlight="auto", + ) + + # Test turning light on + with patch("pyatmo.camera.CameraData.set_state") as mock_set_state: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: light_entity}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_state.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", + camera_id="12:34:56:00:a5:a4", + floodlight="on", + ) diff --git a/tests/fixtures/netatmo/ping.json b/tests/fixtures/netatmo/ping.json new file mode 100644 index 00000000000000..784975de5b0139 --- /dev/null +++ b/tests/fixtures/netatmo/ping.json @@ -0,0 +1,4 @@ +{ + "local_url": "http://192.168.0.123/678460a0d47e5618699fb31169e2b47d", + "product_name": "Welcome Netatmo" +} \ No newline at end of file From 4ee4d674d8931927eae5222e3bf8dd6e26f3c6e5 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 19 Mar 2021 12:19:57 +0100 Subject: [PATCH 1382/1818] Add tests for Netatmo camera (#46380) * Add test for Netatmo camera * Improve docstrings * Remove light tests * Remove freezegun from tests * Update camera tests * Remove freezegun dependency * Update tests/components/netatmo/test_camera.py Co-authored-by: Erik Montnemery * Update tests/components/netatmo/test_camera.py Co-authored-by: Erik Montnemery * Deduplication of the fake webhook payload * Mock pyatmo instead of checking the logs * Clean up * Further deduplication * Assert function arguments * Rename mocked functions * Update .coveragerc Co-authored-by: Erik Montnemery --- .coveragerc | 1 - homeassistant/components/netatmo/camera.py | 2 +- tests/components/netatmo/conftest.py | 2 + tests/components/netatmo/test_camera.py | 207 +++++++++++++++++++++ 4 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 tests/components/netatmo/test_camera.py diff --git a/.coveragerc b/.coveragerc index 71dbda8ce401a4..48fb4ce756fa4f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,6 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/__init__.py - homeassistant/components/netatmo/camera.py homeassistant/components/netatmo/data_handler.py homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/netatmo_entity_base.py diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index b02d77e45fb07a..5445231282cced 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -350,7 +350,7 @@ def _service_set_person_away(self, **kwargs): def _service_set_camera_light(self, **kwargs): """Service to set light mode.""" mode = kwargs.get(ATTR_CAMERA_LIGHT_MODE) - _LOGGER.debug("Turn camera '%s' %s", self._name, mode) + _LOGGER.debug("Turn %s camera light for '%s'", mode, self._name) self._data.set_state( home_id=self._home_id, camera_id=self._id, diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index 4e59eaacb8271c..e0138dcc4d7db3 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -73,6 +73,8 @@ async def mock_entry_fixture(hass, config_entry): """Mock setup of all platforms.""" with selected_platforms(): await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() return config_entry diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py new file mode 100644 index 00000000000000..a58f2f3e1aae4f --- /dev/null +++ b/tests/components/netatmo/test_camera.py @@ -0,0 +1,207 @@ +"""The tests for Netatmo camera.""" +from unittest.mock import patch + +from homeassistant.components import camera +from homeassistant.components.camera import STATE_STREAMING +from homeassistant.components.netatmo.const import ( + SERVICE_SET_CAMERA_LIGHT, + SERVICE_SET_PERSON_AWAY, + SERVICE_SET_PERSONS_HOME, +) +from homeassistant.const import CONF_WEBHOOK_ID + +from .common import simulate_webhook + + +async def test_setup_component_with_webhook(hass, camera_entry): + """Test setup with webhook.""" + webhook_id = camera_entry.data[CONF_WEBHOOK_ID] + await hass.async_block_till_done() + + camera_entity_indoor = "camera.netatmo_hall" + camera_entity_outdoor = "camera.netatmo_garden" + assert hass.states.get(camera_entity_indoor).state == "streaming" + response = { + "event_type": "off", + "device_id": "12:34:56:00:f1:62", + "camera_id": "12:34:56:00:f1:62", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NACamera-off", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "idle" + + response = { + "event_type": "on", + "device_id": "12:34:56:00:f1:62", + "camera_id": "12:34:56:00:f1:62", + "event_id": "646227f1dc0dfa000ec5f350", + "push_type": "NACamera-on", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "streaming" + + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "camera_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + "sub_type": "on", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "streaming" + assert hass.states.get(camera_entity_outdoor).attributes["light_state"] == "on" + + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "camera_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + "sub_type": "auto", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_outdoor).attributes["light_state"] == "auto" + + response = { + "event_type": "light_mode", + "device_id": "12:34:56:00:a5:a4", + "event_id": "601dce1560abca1ebad9b723", + "push_type": "NOC-light_mode", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(camera_entity_indoor).state == "streaming" + assert hass.states.get(camera_entity_outdoor).attributes["light_state"] == "auto" + + +IMAGE_BYTES_FROM_STREAM = b"test stream image bytes" + + +async def test_camera_image_local(hass, camera_entry, requests_mock): + """Test retrieval or local camera image.""" + await hass.async_block_till_done() + + uri = "http://192.168.0.123/678460a0d47e5618699fb31169e2b47d" + stream_uri = uri + "/live/files/high/index.m3u8" + camera_entity_indoor = "camera.netatmo_hall" + cam = hass.states.get(camera_entity_indoor) + + assert cam is not None + assert cam.state == STATE_STREAMING + + stream_source = await camera.async_get_stream_source(hass, camera_entity_indoor) + assert stream_source == stream_uri + + requests_mock.get( + uri + "/live/snapshot_720.jpg", + content=IMAGE_BYTES_FROM_STREAM, + ) + image = await camera.async_get_image(hass, camera_entity_indoor) + assert image.content == IMAGE_BYTES_FROM_STREAM + + +async def test_camera_image_vpn(hass, camera_entry, requests_mock): + """Test retrieval of remote camera image.""" + await hass.async_block_till_done() + + uri = ( + "https://prodvpn-eu-2.netatmo.net/restricted/10.255.248.91/" + "6d278460699e56180d47ab47169efb31/MpEylTU2MDYzNjRVD-LJxUnIndumKzLboeAwMDqTTw,," + ) + stream_uri = uri + "/live/files/high/index.m3u8" + camera_entity_indoor = "camera.netatmo_garden" + cam = hass.states.get(camera_entity_indoor) + + assert cam is not None + assert cam.state == STATE_STREAMING + + stream_source = await camera.async_get_stream_source(hass, camera_entity_indoor) + assert stream_source == stream_uri + + requests_mock.get( + uri + "/live/snapshot_720.jpg", + content=IMAGE_BYTES_FROM_STREAM, + ) + image = await camera.async_get_image(hass, camera_entity_indoor) + assert image.content == IMAGE_BYTES_FROM_STREAM + + +async def test_service_set_person_away(hass, camera_entry): + """Test service to set person as away.""" + await hass.async_block_till_done() + + data = { + "entity_id": "camera.netatmo_hall", + "person": "Richard Doe", + } + + with patch("pyatmo.camera.CameraData.set_persons_away") as mock_set_persons_away: + await hass.services.async_call( + "netatmo", SERVICE_SET_PERSON_AWAY, service_data=data + ) + await hass.async_block_till_done() + mock_set_persons_away.assert_called_once_with( + person_id="91827376-7e04-5298-83af-a0cb8372dff3", + home_id="91763b24c43d3e344f424e8b", + ) + + data = { + "entity_id": "camera.netatmo_hall", + } + + with patch("pyatmo.camera.CameraData.set_persons_away") as mock_set_persons_away: + await hass.services.async_call( + "netatmo", SERVICE_SET_PERSON_AWAY, service_data=data + ) + await hass.async_block_till_done() + mock_set_persons_away.assert_called_once_with( + person_id=None, + home_id="91763b24c43d3e344f424e8b", + ) + + +async def test_service_set_persons_home(hass, camera_entry): + """Test service to set persons as home.""" + await hass.async_block_till_done() + + data = { + "entity_id": "camera.netatmo_hall", + "persons": "John Doe", + } + + with patch("pyatmo.camera.CameraData.set_persons_home") as mock_set_persons_home: + await hass.services.async_call( + "netatmo", SERVICE_SET_PERSONS_HOME, service_data=data + ) + await hass.async_block_till_done() + mock_set_persons_home.assert_called_once_with( + person_ids=["91827374-7e04-5298-83ad-a0cb8372dff1"], + home_id="91763b24c43d3e344f424e8b", + ) + + +async def test_service_set_camera_light(hass, camera_entry): + """Test service to set the outdoor camera light mode.""" + await hass.async_block_till_done() + + data = { + "entity_id": "camera.netatmo_garden", + "camera_light_mode": "on", + } + + with patch("pyatmo.camera.CameraData.set_state") as mock_set_state: + await hass.services.async_call( + "netatmo", SERVICE_SET_CAMERA_LIGHT, service_data=data + ) + await hass.async_block_till_done() + mock_set_state.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", + camera_id="12:34:56:00:a5:a4", + floodlight="on", + ) From 993261e7f5da3de48153beaed975c61867b55351 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Fri, 19 Mar 2021 12:34:06 +0100 Subject: [PATCH 1383/1818] Add "timestamp" attribute to seventeentrack (#47960) * bump py17track to 3.0.1 * Make aiohttp ClientSession optional as introduced in py17track v3.0.0 (https://github.com/bachya/py17track/releases/tag/3.0.0) * Update manifest.json * add new attribute timestamp introduced in 3.1.0 * Update requirements.txt * Update requirements_all.txt * Update requirements.txt * Update requirements_test_all.txt * Update sensor.py * Update sensor.py * Update manifest.json * provide timezone configuration user config to pre-define timezone of package status timestamps * Update requirements_all.txt * Update requirements_test_all.txt * linting * use hass.config.time_zone * Update sensor.py * Update test_sensor.py * Update test_sensor.py * black * Update manifest.json * adjust changes to session param * added test against latest dev branch * make isort happy * make black happy * make flake8 happy * make black happy * bump to 3.2.1 * 3.2.1 * Update typing 15 --- .../components/seventeentrack/manifest.json | 2 +- .../components/seventeentrack/sensor.py | 25 +++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/seventeentrack/test_sensor.py | 128 ++++++++++++++++-- 5 files changed, 139 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/seventeentrack/manifest.json b/homeassistant/components/seventeentrack/manifest.json index 2cec9dea954add..427882de91a557 100644 --- a/homeassistant/components/seventeentrack/manifest.json +++ b/homeassistant/components/seventeentrack/manifest.json @@ -2,6 +2,6 @@ "domain": "seventeentrack", "name": "17TRACK", "documentation": "https://www.home-assistant.io/integrations/seventeentrack", - "requirements": ["py17track==2.2.2"], + "requirements": ["py17track==3.2.1"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 15110f6a0c302c..223844dc9c70cb 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -24,6 +24,7 @@ ATTR_DESTINATION_COUNTRY = "destination_country" ATTR_INFO_TEXT = "info_text" +ATTR_TIMESTAMP = "timestamp" ATTR_ORIGIN_COUNTRY = "origin_country" ATTR_PACKAGES = "packages" ATTR_PACKAGE_TYPE = "package_type" @@ -65,9 +66,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Configure the platform and add the sensors.""" - websession = aiohttp_client.async_get_clientsession(hass) + session = aiohttp_client.async_get_clientsession(hass) - client = SeventeenTrackClient(websession) + client = SeventeenTrackClient(session=session) try: login_result = await client.profile.login( @@ -89,6 +90,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= scan_interval, config[CONF_SHOW_ARCHIVED], config[CONF_SHOW_DELIVERED], + str(hass.config.time_zone), ) await data.async_update() @@ -151,6 +153,7 @@ async def async_update(self): { ATTR_FRIENDLY_NAME: package.friendly_name, ATTR_INFO_TEXT: package.info_text, + ATTR_TIMESTAMP: package.timestamp, ATTR_STATUS: package.status, ATTR_LOCATION: package.location, ATTR_TRACKING_NUMBER: package.tracking_number, @@ -172,6 +175,7 @@ def __init__(self, data, package): ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION, ATTR_DESTINATION_COUNTRY: package.destination_country, ATTR_INFO_TEXT: package.info_text, + ATTR_TIMESTAMP: package.timestamp, ATTR_LOCATION: package.location, ATTR_ORIGIN_COUNTRY: package.origin_country, ATTR_PACKAGE_TYPE: package.package_type, @@ -237,7 +241,11 @@ async def async_update(self): return self._attrs.update( - {ATTR_INFO_TEXT: package.info_text, ATTR_LOCATION: package.location} + { + ATTR_INFO_TEXT: package.info_text, + ATTR_TIMESTAMP: package.timestamp, + ATTR_LOCATION: package.location, + } ) self._state = package.status self._friendly_name = package.friendly_name @@ -277,7 +285,13 @@ class SeventeenTrackData: """Define a data handler for 17track.net.""" def __init__( - self, client, async_add_entities, scan_interval, show_archived, show_delivered + self, + client, + async_add_entities, + scan_interval, + show_archived, + show_delivered, + timezone, ): """Initialize.""" self._async_add_entities = async_add_entities @@ -287,6 +301,7 @@ def __init__( self.account_id = client.profile.account_id self.packages = {} self.show_delivered = show_delivered + self.timezone = timezone self.summary = {} self.async_update = Throttle(self._scan_interval)(self._async_update) @@ -297,7 +312,7 @@ async def _async_update(self): try: packages = await self._client.profile.packages( - show_archived=self._show_archived + show_archived=self._show_archived, tz=self.timezone ) _LOGGER.debug("New package data received: %s", packages) diff --git a/requirements_all.txt b/requirements_all.txt index b1581b797bd894..b7b07acc71ae1c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1213,7 +1213,7 @@ py-schluter==0.1.7 py-zabbix==1.1.7 # homeassistant.components.seventeentrack -py17track==2.2.2 +py17track==3.2.1 # homeassistant.components.hdmi_cec pyCEC==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c0222a12d6dfe..4a5dbc85efe8f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -629,7 +629,7 @@ py-melissa-climate==2.1.4 py-nightscout==1.2.2 # homeassistant.components.seventeentrack -py17track==2.2.2 +py17track==3.2.1 # homeassistant.components.control4 pyControl4==0.0.6 diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 65e40d02afff42..d40d2cd499b16d 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -71,7 +71,7 @@ class ClientMock: """Mock the py17track client to inject the ProfileMock.""" - def __init__(self, websession) -> None: + def __init__(self, session) -> None: """Mock the profile.""" self.profile = ProfileMock() @@ -101,7 +101,10 @@ async def login(self, email: str, password: str) -> bool: return self.__class__.login_result async def packages( - self, package_state: int | str = "", show_archived: bool = False + self, + package_state: int | str = "", + show_archived: bool = False, + tz: str = "UTC", ) -> list: """Packages mock.""" return self.__class__.package_list[:] @@ -169,7 +172,14 @@ async def test_invalid_config(hass): async def test_add_package(hass): """Ensure package is added correctly when user add a new package.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -178,7 +188,14 @@ async def test_add_package(hass): assert len(hass.states.async_entity_ids()) == 1 package2 = Package( - "789", 206, "friendly name 2", "info text 2", "location 2", 206, 2 + "789", + 206, + "friendly name 2", + "info text 2", + "location 2", + "2020-08-10 14:25", + 206, + 2, ) ProfileMock.package_list = [package, package2] @@ -191,10 +208,24 @@ async def test_add_package(hass): async def test_remove_package(hass): """Ensure entity is not there anymore if package is not there.""" package1 = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) package2 = Package( - "789", 206, "friendly name 2", "info text 2", "location 2", 206, 2 + "789", + 206, + "friendly name 2", + "info text 2", + "location 2", + "2020-08-10 14:25", + 206, + 2, ) ProfileMock.package_list = [package1, package2] @@ -217,7 +248,14 @@ async def test_remove_package(hass): async def test_friendly_name_changed(hass): """Test friendly name change.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -227,7 +265,14 @@ async def test_friendly_name_changed(hass): assert len(hass.states.async_entity_ids()) == 1 package = Package( - "456", 206, "friendly name 2", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 2", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -244,7 +289,15 @@ async def test_friendly_name_changed(hass): async def test_delivered_not_shown(hass): """Ensure delivered packages are not shown.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2, 40 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + 40, ) ProfileMock.package_list = [package] @@ -259,7 +312,15 @@ async def test_delivered_not_shown(hass): async def test_delivered_shown(hass): """Ensure delivered packages are show when user choose to show them.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2, 40 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + 40, ) ProfileMock.package_list = [package] @@ -274,7 +335,14 @@ async def test_delivered_shown(hass): async def test_becomes_delivered_not_shown_notification(hass): """Ensure notification is triggered when package becomes delivered.""" package = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, ) ProfileMock.package_list = [package] @@ -284,7 +352,15 @@ async def test_becomes_delivered_not_shown_notification(hass): assert len(hass.states.async_entity_ids()) == 1 package_delivered = Package( - "456", 206, "friendly name 1", "info text 1", "location 1", 206, 2, 40 + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + 40, ) ProfileMock.package_list = [package_delivered] @@ -310,3 +386,31 @@ async def test_summary_correctly_updated(hass): assert len(hass.states.async_entity_ids()) == 7 for state in hass.states.async_all(): assert state.state == "1" + + +async def test_utc_timestamp(hass): + """Ensure package timestamp is converted correctly from HA-defined time zone to UTC.""" + package = Package( + "456", + 206, + "friendly name 1", + "info text 1", + "location 1", + "2020-08-10 10:32", + 206, + 2, + tz="Asia/Jakarta", + ) + ProfileMock.package_list = [package] + + await _setup_seventeentrack(hass) + assert hass.states.get("sensor.seventeentrack_package_456") is not None + assert len(hass.states.async_entity_ids()) == 1 + assert ( + str( + hass.states.get("sensor.seventeentrack_package_456").attributes.get( + "timestamp" + ) + ) + == "2020-08-10 03:32:00+00:00" + ) From b1626f00917d1041d138dd8d495d1702c964d15d Mon Sep 17 00:00:00 2001 From: Michael Cicogna <44257895+miccico@users.noreply.github.com> Date: Fri, 19 Mar 2021 12:36:03 +0100 Subject: [PATCH 1384/1818] Fix Homematic transition function on light devices with multiple channels (#45725) * Update light.py Fix Transition function on devices with multiple channels * Update light.py fix Flake8 Warning W293 blank line contains whitespace --- homeassistant/components/homematic/light.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index 11731e2ae5feec..036034bf801846 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -9,6 +9,7 @@ SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, + SUPPORT_TRANSITION, LightEntity, ) @@ -53,7 +54,8 @@ def is_on(self): @property def supported_features(self): """Flag supported features.""" - features = SUPPORT_BRIGHTNESS + features = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + if "COLOR" in self._hmdevice.WRITENODE: features |= SUPPORT_COLOR if "PROGRAM" in self._hmdevice.WRITENODE: @@ -95,7 +97,7 @@ def effect(self): def turn_on(self, **kwargs): """Turn the light on and/or change color or color effect settings.""" if ATTR_TRANSITION in kwargs: - self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION]) + self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION], self._channel) if ATTR_BRIGHTNESS in kwargs and self._state == "LEVEL": percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255 @@ -123,6 +125,9 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): """Turn the light off.""" + if ATTR_TRANSITION in kwargs: + self._hmdevice.setValue("RAMP_TIME", kwargs[ATTR_TRANSITION], self._channel) + self._hmdevice.off(self._channel) def _init_data_struct(self): From 703c073e53217692b8d6f86ceb7a8478986f6fd2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 19 Mar 2021 13:30:16 +0100 Subject: [PATCH 1385/1818] Improve websocket debug log --- homeassistant/components/websocket_api/http.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 982bead296a8a5..27af0424f3cfec 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -74,11 +74,11 @@ async def _writer(self): if message is None: break - self._logger.debug("Sending %s", message) - if not isinstance(message, str): message = message_to_json(message) + self._logger.debug("Sending %s", message) + await self.wsock.send_str(message) # Clean up the peaker checker when we shut down the writer From 6e0c0afde2811659052d56588dc86b074de0da1b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Mar 2021 13:36:44 +0100 Subject: [PATCH 1386/1818] Upgrade RPi.GPIO to 0.7.1a4 (#48106) --- homeassistant/components/bmp280/manifest.json | 2 +- homeassistant/components/mcp23017/manifest.json | 2 +- homeassistant/components/rpi_gpio/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bmp280/manifest.json b/homeassistant/components/bmp280/manifest.json index dbd7989671896e..e22c275ed76ac6 100644 --- a/homeassistant/components/bmp280/manifest.json +++ b/homeassistant/components/bmp280/manifest.json @@ -3,6 +3,6 @@ "name": "Bosch BMP280 Environmental Sensor", "documentation": "https://www.home-assistant.io/integrations/bmp280", "codeowners": ["@belidzs"], - "requirements": ["adafruit-circuitpython-bmp280==3.1.1", "RPi.GPIO==0.7.0"], + "requirements": ["adafruit-circuitpython-bmp280==3.1.1", "RPi.GPIO==0.7.1a4"], "quality_scale": "silver" } diff --git a/homeassistant/components/mcp23017/manifest.json b/homeassistant/components/mcp23017/manifest.json index aeda638710e364..7460529f8fee06 100644 --- a/homeassistant/components/mcp23017/manifest.json +++ b/homeassistant/components/mcp23017/manifest.json @@ -3,7 +3,7 @@ "name": "MCP23017 I/O Expander", "documentation": "https://www.home-assistant.io/integrations/mcp23017", "requirements": [ - "RPi.GPIO==0.7.0", + "RPi.GPIO==0.7.1a4", "adafruit-circuitpython-mcp230xx==2.2.2" ], "codeowners": ["@jardiamj"] diff --git a/homeassistant/components/rpi_gpio/manifest.json b/homeassistant/components/rpi_gpio/manifest.json index 523d98dfdb752a..1a73c736d04c30 100644 --- a/homeassistant/components/rpi_gpio/manifest.json +++ b/homeassistant/components/rpi_gpio/manifest.json @@ -2,6 +2,6 @@ "domain": "rpi_gpio", "name": "Raspberry Pi GPIO", "documentation": "https://www.home-assistant.io/integrations/rpi_gpio", - "requirements": ["RPi.GPIO==0.7.0"], + "requirements": ["RPi.GPIO==0.7.1a4"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index b7b07acc71ae1c..6a114b0e972a80 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -72,7 +72,7 @@ PyXiaomiGateway==0.13.4 # homeassistant.components.bmp280 # homeassistant.components.mcp23017 # homeassistant.components.rpi_gpio -# RPi.GPIO==0.7.0 +# RPi.GPIO==0.7.1a4 # homeassistant.components.remember_the_milk RtmAPI==0.7.2 From fa5ce70af1f6cbc219adf49ae15730abf090482f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 19 Mar 2021 13:37:22 +0100 Subject: [PATCH 1387/1818] Improve test coverage of deCONZ config flow (#48091) --- tests/components/deconz/test_config_flow.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 19f544fabc9dd6..8f9a597e00c8b5 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -442,6 +442,18 @@ async def test_flow_ssdp_discovery(hass, aioclient_mock): } +async def test_flow_ssdp_bad_discovery(hass, aioclient_mock): + """Test that SSDP discovery aborts if manufacturer URL is wrong.""" + result = await hass.config_entries.flow.async_init( + DECONZ_DOMAIN, + data={ATTR_UPNP_MANUFACTURER_URL: "other"}, + context={"source": SOURCE_SSDP}, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "not_deconz_bridge" + + async def test_ssdp_discovery_update_configuration(hass, aioclient_mock): """Test if a discovered bridge is configured but updates with new attributes.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) From 8a56dbf58710e8a3bbcfa83ce92c427a6c793fbe Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Mar 2021 13:41:09 +0100 Subject: [PATCH 1388/1818] Add flake8 comprehensions checks to pre-commit & CI (#48111) --- .pre-commit-config.yaml | 1 + homeassistant/components/denon/media_player.py | 2 +- homeassistant/components/emulated_hue/hue_api.py | 8 +++----- homeassistant/components/flux_led/light.py | 2 +- homeassistant/components/light/__init__.py | 2 +- homeassistant/components/nad/media_player.py | 2 +- homeassistant/components/sms/gateway.py | 4 ++-- homeassistant/components/uk_transport/sensor.py | 4 +--- homeassistant/components/unifi/config_flow.py | 2 +- homeassistant/components/webostv/media_player.py | 2 +- homeassistant/util/dt.py | 2 +- requirements_test_pre_commit.txt | 1 + tests/common.py | 2 +- tests/components/automation/test_trace.py | 2 +- tests/components/group/test_init.py | 2 +- tests/components/zwave/test_init.py | 8 ++++---- tests/helpers/test_json.py | 2 +- tests/util/test_dt.py | 8 ++++---- 18 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6629065005a7a7..031d0659aadea0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,6 +32,7 @@ repos: # default yet due to https://github.com/plinss/flake8-noqa/issues/1 # - flake8-noqa==1.1.0 - pydocstyle==5.1.1 + - flake8-comprehensions==3.4.0 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.7.0 diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index b909dc7c07008c..82427f8aa16e33 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -230,7 +230,7 @@ def is_volume_muted(self): @property def source_list(self): """Return the list of available input sources.""" - return sorted(list(self._source_list)) + return sorted(self._source_list) @property def media_title(self): diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 1630405a73ec75..647d9db13356a8 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -391,11 +391,9 @@ async def put(self, request, username, entity_number): return self.json_message("Bad request", HTTP_BAD_REQUEST) if HUE_API_STATE_XY in request_json: try: - parsed[STATE_XY] = tuple( - ( - float(request_json[HUE_API_STATE_XY][0]), - float(request_json[HUE_API_STATE_XY][1]), - ) + parsed[STATE_XY] = ( + float(request_json[HUE_API_STATE_XY][0]), + float(request_json[HUE_API_STATE_XY][1]), ) except ValueError: _LOGGER.error("Unable to parse data (2): %s", request_json) diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 4bfd0c0a26c77a..2f8d2cc553694e 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -98,7 +98,7 @@ TRANSITION_JUMP = "jump" TRANSITION_STROBE = "strobe" -FLUX_EFFECT_LIST = sorted(list(EFFECT_MAP)) + [EFFECT_RANDOM] +FLUX_EFFECT_LIST = sorted(EFFECT_MAP) + [EFFECT_RANDOM] CUSTOM_EFFECT_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c2e2fdbeaa9449..7934874db0b7a9 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -610,7 +610,7 @@ def capability_attributes(self): data[ATTR_EFFECT_LIST] = self.effect_list data[ATTR_SUPPORTED_COLOR_MODES] = sorted( - list(self._light_internal_supported_color_modes) + self._light_internal_supported_color_modes ) return data diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index 782b8735e3ae21..e7f83c66efac93 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -163,7 +163,7 @@ def source(self): @property def source_list(self): """List of available input sources.""" - return sorted(list(self._reverse_mapping)) + return sorted(self._reverse_mapping) @property def available(self): diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index e28b3947e3745b..51667ef8f776e3 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -43,7 +43,7 @@ def sms_callback(self, state_machine, callback_type, callback_data): ) entries = self.get_and_delete_all_sms(state_machine) _LOGGER.debug("SMS entries:%s", entries) - data = list() + data = [] for entry in entries: decoded_entry = gammu.DecodeSMS(entry) @@ -78,7 +78,7 @@ def get_and_delete_all_sms(self, state_machine, force=False): start_remaining = remaining # Get all sms start = True - entries = list() + entries = [] all_parts = -1 all_parts_arrived = False _LOGGER.debug("Start remaining:%i", start_remaining) diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index 80b97b186be030..f5e4b4373c0de0 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -32,9 +32,7 @@ _QUERY_SCHEME = vol.Schema( { - vol.Required(CONF_MODE): vol.All( - cv.ensure_list, [vol.In(list(["bus", "train"]))] - ), + vol.Required(CONF_MODE): vol.All(cv.ensure_list, [vol.In(["bus", "train"])]), vol.Required(CONF_ORIGIN): cv.string, vol.Required(CONF_DESTINATION): cv.string, } diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 6d8c37e8b04b58..094bae0588197b 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -319,7 +319,7 @@ async def async_step_device_tracker(self, user_input=None): if "name" in wlan } ) - ssid_filter = {ssid: ssid for ssid in sorted(list(ssids))} + ssid_filter = {ssid: ssid for ssid in sorted(ssids)} return self.async_show_form( step_id="device_tracker", diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 0fc442b37e98f1..75c1bdd2bcc0ee 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -271,7 +271,7 @@ def source(self): @property def source_list(self): """List of available input sources.""" - return sorted(list(self._source_list)) + return sorted(self._source_list) @property def media_content_type(self): diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index a659d0add38b1f..aae01c56bf3ed6 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -227,7 +227,7 @@ def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> lis elif not hasattr(parameter, "__iter__"): res = [int(parameter)] else: - res = list(sorted(int(x) for x in parameter)) + res = sorted(int(x) for x in parameter) for val in res: if val < min_value or val > max_value: diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index fd5329a8ca1c5b..be881f09f7cae6 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -3,6 +3,7 @@ bandit==1.7.0 black==20.8b1 codespell==2.0.0 +flake8-comprehensions==3.4.0 flake8-docstrings==1.5.0 flake8==3.8.4 isort==5.7.0 diff --git a/tests/common.py b/tests/common.py index f1449721c29ba3..5f8626afb4eb40 100644 --- a/tests/common.py +++ b/tests/common.py @@ -766,7 +766,7 @@ def add_to_manager(self, manager): def patch_yaml_files(files_dict, endswith=True): """Patch load_yaml with a dictionary of yaml files.""" # match using endswith, start search with longest string - matchlist = sorted(list(files_dict.keys()), key=len) if endswith else [] + matchlist = sorted(files_dict.keys(), key=len) if endswith else [] def mock_open_f(fname, **_): """Mock open() in the yaml module, used by load_yaml.""" diff --git a/tests/components/automation/test_trace.py b/tests/components/automation/test_trace.py index 818f1ee176840f..612a0ccfcab654 100644 --- a/tests/components/automation/test_trace.py +++ b/tests/components/automation/test_trace.py @@ -32,7 +32,7 @@ def test_json_encoder(hass): # Test serializing a set() data = {"milk", "beer"} - assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + assert sorted(ha_json_enc.default(data)) == sorted(data) # Test serializong object which implements as_dict assert ha_json_enc.default(state) == state.as_dict() diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 627e3c5bbe00d0..d68fdd7f717c54 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -579,7 +579,7 @@ async def test_service_group_set_group_remove_group(hass): assert group_state.attributes[group.ATTR_AUTO] assert group_state.attributes["friendly_name"] == "Test2" assert group_state.attributes["icon"] == "mdi:camera" - assert sorted(list(group_state.attributes["entity_id"])) == sorted( + assert sorted(group_state.attributes["entity_id"]) == sorted( ["test.entity_bla1", "test.entity_id2"] ) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index e76754a9872cb9..e857675c545dbc 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -861,7 +861,7 @@ async def test_entity_discovery( assert values.primary is value_class.primary assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, None, None], key=lambda a: id(a) ) @@ -885,7 +885,7 @@ async def test_entity_discovery( assert values.secondary is value_class.secondary assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, value_class.secondary, None], key=lambda a: id(a) ) @@ -902,7 +902,7 @@ async def test_entity_discovery( assert values.optional is value_class.optional assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, value_class.secondary, value_class.optional], key=lambda a: id(a), ) @@ -961,7 +961,7 @@ async def test_entity_existing_values( assert values.secondary is value_class.secondary assert values.optional is value_class.optional assert len(list(values)) == 3 - assert sorted(list(values), key=lambda a: id(a)) == sorted( + assert sorted(values, key=lambda a: id(a)) == sorted( [value_class.primary, value_class.secondary, value_class.optional], key=lambda a: id(a), ) diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 55d566f5eddae4..1a68f2b8da59d7 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -17,7 +17,7 @@ def test_json_encoder(hass): # Test serializing a set() data = {"milk", "beer"} - assert sorted(ha_json_enc.default(data)) == sorted(list(data)) + assert sorted(ha_json_enc.default(data)) == sorted(data) # Test serializing an object which implements as_dict assert ha_json_enc.default(state) == state.as_dict() diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 7c4ca77fd79d77..1327bc51f8a97a 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -177,14 +177,14 @@ def test_get_age(): def test_parse_time_expression(): """Test parse_time_expression.""" - assert [x for x in range(60)] == dt_util.parse_time_expression("*", 0, 59) - assert [x for x in range(60)] == dt_util.parse_time_expression(None, 0, 59) + assert list(range(60)) == dt_util.parse_time_expression("*", 0, 59) + assert list(range(60)) == dt_util.parse_time_expression(None, 0, 59) - assert [x for x in range(0, 60, 5)] == dt_util.parse_time_expression("/5", 0, 59) + assert list(range(0, 60, 5)) == dt_util.parse_time_expression("/5", 0, 59) assert [1, 2, 3] == dt_util.parse_time_expression([2, 1, 3], 0, 59) - assert [x for x in range(24)] == dt_util.parse_time_expression("*", 0, 23) + assert list(range(24)) == dt_util.parse_time_expression("*", 0, 23) assert [42] == dt_util.parse_time_expression(42, 0, 59) assert [42] == dt_util.parse_time_expression("42", 0, 59) From 3742f175ad327a63684789531ee4a4bbad655123 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 19 Mar 2021 14:27:26 +0100 Subject: [PATCH 1389/1818] Add missing oauth2 error abort reason (#48112) --- homeassistant/strings.json | 1 + script/scaffold/generate.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/homeassistant/strings.json b/homeassistant/strings.json index b8e7dee299615a..31693c5bba183a 100644 --- a/homeassistant/strings.json +++ b/homeassistant/strings.json @@ -67,6 +67,7 @@ "already_in_progress": "Configuration flow is already in progress", "no_devices_found": "No devices found on the network", "webhook_not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive webhook messages.", + "oauth2_error": "Received invalid token data.", "oauth2_missing_configuration": "The component is not configured. Please follow the documentation.", "oauth2_authorize_url_timeout": "Timeout generating authorize URL.", "oauth2_no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", diff --git a/script/scaffold/generate.py b/script/scaffold/generate.py index 4cf1624eb4b821..10de17e45ee13e 100644 --- a/script/scaffold/generate.py +++ b/script/scaffold/generate.py @@ -163,6 +163,9 @@ def _custom_tasks(template, info) -> None: } }, "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]", "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", From c820dd4cb55ab4349f9135a7ba1b1c059536719b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 19 Mar 2021 09:26:36 -0500 Subject: [PATCH 1390/1818] Have pylint warn when user visible log messages do not start with capital letter or end with a period (#48064) Co-authored-by: Martin Hjelmare --- homeassistant/components/abode/config_flow.py | 2 +- .../components/actiontec/device_tracker.py | 2 +- .../aemet/weather_update_coordinator.py | 12 +-- homeassistant/components/amcrest/camera.py | 2 +- homeassistant/components/awair/sensor.py | 2 +- homeassistant/components/axis/device.py | 2 +- .../components/azure_devops/__init__.py | 2 +- .../components/bbox/device_tracker.py | 2 +- homeassistant/components/bloomsky/__init__.py | 2 +- homeassistant/components/buienradar/sensor.py | 6 +- homeassistant/components/buienradar/util.py | 2 +- .../components/cisco_ios/device_tracker.py | 5 +- homeassistant/components/decora/light.py | 2 +- .../components/downloader/__init__.py | 2 +- homeassistant/components/duckdns/__init__.py | 2 +- .../eddystone_temperature/sensor.py | 2 +- .../components/envisalink/__init__.py | 8 +- homeassistant/components/fan/__init__.py | 4 +- homeassistant/components/fints/sensor.py | 4 +- homeassistant/components/folder/sensor.py | 2 +- .../components/folder_watcher/__init__.py | 2 +- homeassistant/components/foscam/camera.py | 2 +- .../components/foscam/config_flow.py | 8 +- homeassistant/components/gogogate2/cover.py | 4 +- .../components/growatt_server/sensor.py | 2 +- .../components/gstreamer/media_player.py | 2 +- homeassistant/components/habitica/sensor.py | 8 +- .../components/hangouts/hangouts_bot.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 2 +- .../components/hitron_coda/device_tracker.py | 4 +- .../components/homeassistant/__init__.py | 3 +- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/homekit/img_util.py | 2 +- .../components/homekit_controller/climate.py | 8 +- .../components/horizon/media_player.py | 4 +- .../components/huawei_lte/__init__.py | 6 +- homeassistant/components/hue/bridge.py | 2 +- .../components/huisbaasje/__init__.py | 2 +- homeassistant/components/icloud/account.py | 4 +- .../components/insteon/config_flow.py | 4 +- homeassistant/components/isy994/__init__.py | 2 +- .../components/isy994/config_flow.py | 2 +- homeassistant/components/keba/__init__.py | 4 +- .../components/keenetic_ndms2/router.py | 2 +- homeassistant/components/kiwi/lock.py | 2 +- homeassistant/components/konnected/panel.py | 2 +- homeassistant/components/lifx_legacy/light.py | 2 +- .../components/meraki/device_tracker.py | 2 +- .../components/meteo_france/__init__.py | 4 +- .../components/meteo_france/weather.py | 2 +- .../components/motion_blinds/gateway.py | 2 +- homeassistant/components/mqtt/cover.py | 2 +- homeassistant/components/mystrom/light.py | 2 +- homeassistant/components/netatmo/__init__.py | 2 +- homeassistant/components/netatmo/climate.py | 4 +- .../components/nissan_leaf/__init__.py | 2 +- .../components/nmap_tracker/device_tracker.py | 2 +- homeassistant/components/onvif/config_flow.py | 2 +- homeassistant/components/onvif/device.py | 2 +- homeassistant/components/onvif/event.py | 2 +- homeassistant/components/orvibo/switch.py | 2 +- .../components/panasonic_viera/__init__.py | 2 +- homeassistant/components/plex/server.py | 6 +- .../components/plum_lightpad/__init__.py | 2 +- .../components/poolsense/__init__.py | 2 +- homeassistant/components/pushsafer/notify.py | 4 +- .../components/radiotherm/climate.py | 2 +- .../components/recollect_waste/sensor.py | 4 +- homeassistant/components/recorder/util.py | 4 +- homeassistant/components/rflink/__init__.py | 2 +- homeassistant/components/rfxtrx/__init__.py | 2 +- .../components/screenlogic/__init__.py | 2 +- .../components/screenlogic/switch.py | 4 +- .../components/screenlogic/water_heater.py | 4 +- homeassistant/components/skybeacon/sensor.py | 2 +- homeassistant/components/smarty/__init__.py | 6 +- .../components/somfy_mylink/__init__.py | 2 +- .../components/sonos/media_player.py | 2 +- homeassistant/components/starline/account.py | 2 +- .../components/subaru/config_flow.py | 2 +- homeassistant/components/syncthru/sensor.py | 2 +- .../components/system_health/__init__.py | 2 +- .../components/systemmonitor/sensor.py | 2 +- .../components/telegram_bot/webhooks.py | 2 +- .../components/tensorflow/image_processing.py | 2 +- homeassistant/components/twitter/notify.py | 2 +- homeassistant/components/upcloud/__init__.py | 2 +- homeassistant/components/vizio/config_flow.py | 3 +- homeassistant/components/webhook/__init__.py | 2 +- .../components/webostv/media_player.py | 2 +- homeassistant/components/wemo/__init__.py | 4 +- .../components/wirelesstag/__init__.py | 2 +- .../components/xiaomi_miio/switch.py | 2 +- .../components/xiaomi_miio/vacuum.py | 2 +- homeassistant/components/xs1/__init__.py | 4 +- homeassistant/components/zabbix/sensor.py | 2 +- homeassistant/components/zeroconf/usage.py | 2 +- homeassistant/components/zwave/__init__.py | 8 +- homeassistant/components/zwave_js/__init__.py | 2 +- homeassistant/components/zwave_js/migrate.py | 4 +- homeassistant/core.py | 6 +- homeassistant/helpers/template.py | 2 +- homeassistant/util/yaml/loader.py | 2 +- pylint/plugins/hass_logger.py | 85 +++++++++++++++++++ pyproject.toml | 2 + tests/components/duckdns/test_init.py | 4 +- tests/components/mqtt/test_cover.py | 2 +- 107 files changed, 243 insertions(+), 157 deletions(-) create mode 100644 pylint/plugins/hass_logger.py diff --git a/homeassistant/components/abode/config_flow.py b/homeassistant/components/abode/config_flow.py index 76c23f7f705ac5..d1e66b3a3dc68f 100644 --- a/homeassistant/components/abode/config_flow.py +++ b/homeassistant/components/abode/config_flow.py @@ -163,7 +163,7 @@ async def async_step_reauth_confirm(self, user_input=None): async def async_step_import(self, import_config): """Import a config entry from configuration.yaml.""" if self._async_current_entries(): - LOGGER.warning("Already configured. Only a single configuration possible.") + LOGGER.warning("Already configured; Only a single configuration possible") return self.async_abort(reason="single_instance_allowed") self._polling = import_config.get(CONF_POLLING, False) diff --git a/homeassistant/components/actiontec/device_tracker.py b/homeassistant/components/actiontec/device_tracker.py index e3fdeaf35f28fe..c88ed546b9d060 100644 --- a/homeassistant/components/actiontec/device_tracker.py +++ b/homeassistant/components/actiontec/device_tracker.py @@ -53,7 +53,7 @@ def __init__(self, config): self.last_results = [] data = self.get_actiontec_data() self.success_init = data is not None - _LOGGER.info("canner initialized") + _LOGGER.info("Scanner initialized") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" diff --git a/homeassistant/components/aemet/weather_update_coordinator.py b/homeassistant/components/aemet/weather_update_coordinator.py index 1a70baa6765a82..ab098dae17c1d7 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -90,7 +90,7 @@ def format_condition(condition: str) -> str: for key, value in CONDITIONS_MAP.items(): if condition in value: return key - _LOGGER.error('condition "%s" not found in CONDITIONS_MAP', condition) + _LOGGER.error('Condition "%s" not found in CONDITIONS_MAP', condition) return condition @@ -175,14 +175,14 @@ def _get_weather_town(self): ) if self._town: _LOGGER.debug( - "town found for coordinates [%s, %s]: %s", + "Town found for coordinates [%s, %s]: %s", self._latitude, self._longitude, self._town, ) if not self._town: _LOGGER.error( - "town not found for coordinates [%s, %s]", + "Town not found for coordinates [%s, %s]", self._latitude, self._longitude, ) @@ -197,7 +197,7 @@ def _get_weather_and_forecast(self): daily = self._aemet.get_specific_forecast_town_daily(self._town[AEMET_ATTR_ID]) if not daily: _LOGGER.error( - 'error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID] + 'Error fetching daily data for town "%s"', self._town[AEMET_ATTR_ID] ) hourly = self._aemet.get_specific_forecast_town_hourly( @@ -205,7 +205,7 @@ def _get_weather_and_forecast(self): ) if not hourly: _LOGGER.error( - 'error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID] + 'Error fetching hourly data for town "%s"', self._town[AEMET_ATTR_ID] ) station = None @@ -215,7 +215,7 @@ def _get_weather_and_forecast(self): ) if not station: _LOGGER.error( - 'error fetching data for station "%s"', + 'Error fetching data for station "%s"', self._station[AEMET_ATTR_IDEMA], ) diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 046da7b270d838..f57b9e62bae4f3 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -197,7 +197,7 @@ async def async_camera_image(self): # and before initiating shapshot. while self._snapshot_task: self._check_snapshot_ok() - _LOGGER.debug("Waiting for previous snapshot from %s ...", self._name) + _LOGGER.debug("Waiting for previous snapshot from %s", self._name) await self._snapshot_task self._check_snapshot_ok() # Run snapshot command in separate Task that can't be cancelled so diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 81dc0562fc45c2..33ecaf4666090f 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -42,7 +42,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import Awair configuration from YAML.""" LOGGER.warning( - "Loading Awair via platform setup is deprecated. Please remove it from your configuration." + "Loading Awair via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index bd7b5e442ad498..8c2a43c44ed078 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -310,7 +310,7 @@ async def get_device(hass, host, port, username, password): return device except axis.Unauthorized as err: - LOGGER.warning("Connected to device at %s but not registered.", host) + LOGGER.warning("Connected to device at %s but not registered", host) raise AuthenticationRequired from err except (asyncio.TimeoutError, axis.RequestError) as err: diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index b856dc5aa003a7..e10bb4df88672a 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -102,7 +102,7 @@ async def async_update(self) -> None: else: if self._available: _LOGGER.debug( - "An error occurred while updating Azure DevOps sensor.", + "An error occurred while updating Azure DevOps sensor", exc_info=True, ) self._available = False diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index 130d315197b5d4..9dac635dd2f748 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -75,7 +75,7 @@ def _update_info(self): Returns boolean if scanning successful. """ - _LOGGER.info("Scanning...") + _LOGGER.info("Scanning") box = pybbox.Bbox(ip=self.host) result = box.get_all_connected_devices() diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 76ed9cdd12a0ab..fa8d3160dc8d51 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -60,7 +60,7 @@ def __init__(self, api_key, is_metric): self._endpoint_argument = "unit=intl" if is_metric else "" self.devices = {} self.is_metric = is_metric - _LOGGER.debug("Initial BloomSky device load...") + _LOGGER.debug("Initial BloomSky device load") self.refresh_devices() @Throttle(MIN_TIME_BETWEEN_UPDATES) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index ae57ed3c43cf02..fbfa23f1e74fea 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -309,7 +309,7 @@ def _load_data(self, data): try: condition = data.get(FORECAST)[fcday].get(CONDITION) except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) + _LOGGER.warning("No forecast for fcday=%s", fcday) return False if condition: @@ -339,7 +339,7 @@ def _load_data(self, data): self._state = round(self._state * 3.6, 1) return True except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) + _LOGGER.warning("No forecast for fcday=%s", fcday) return False # update all other sensors @@ -347,7 +347,7 @@ def _load_data(self, data): self._state = data.get(FORECAST)[fcday].get(self.type[:-3]) return True except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) + _LOGGER.warning("No forecast for fcday=%s", fcday) return False if self.type == SYMBOL or self.type.startswith(CONDITION): diff --git a/homeassistant/components/buienradar/util.py b/homeassistant/components/buienradar/util.py index b4f2314eee5b5f..83c511713d0164 100644 --- a/homeassistant/components/buienradar/util.py +++ b/homeassistant/components/buienradar/util.py @@ -82,7 +82,7 @@ async def schedule_update(self, minute=1): async def get_data(self, url): """Load data from specified url.""" - _LOGGER.debug("Calling url: %s...", url) + _LOGGER.debug("Calling url: %s", url) result = {SUCCESS: False, MESSAGE: None} resp = None try: diff --git a/homeassistant/components/cisco_ios/device_tracker.py b/homeassistant/components/cisco_ios/device_tracker.py index 8bf2b77fa2502e..0c77fc6fd7ee12 100644 --- a/homeassistant/components/cisco_ios/device_tracker.py +++ b/homeassistant/components/cisco_ios/device_tracker.py @@ -47,7 +47,7 @@ def __init__(self, config): self.last_results = {} self.success_init = self._update_info() - _LOGGER.info("cisco_ios scanner initialized") + _LOGGER.info("Initialized cisco_ios scanner") def get_device_name(self, device): """Get the firmware doesn't save the name of the wireless device.""" @@ -131,8 +131,7 @@ def _get_arp_data(self): return devices_result.decode("utf-8") except pxssh.ExceptionPxssh as px_e: - _LOGGER.error("pxssh failed on login") - _LOGGER.error(px_e) + _LOGGER.error("Failed to login via pxssh: %s", px_e) return None diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 96126bfcc988ff..45c42c4bb1c2e2 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -62,7 +62,7 @@ def wrapper_retry(device, *args, **kwargs): return method(device, *args, **kwargs) except (decora.decoraException, AttributeError, BTLEException): _LOGGER.warning( - "Decora connect error for device %s. Reconnecting...", + "Decora connect error for device %s. Reconnecting", device.name, ) # pylint: disable=protected-access diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 3856df696ad00d..89aa4a465cfeff 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -80,7 +80,7 @@ def do_download(): if req.status_code != HTTP_OK: _LOGGER.warning( - "downloading '%s' failed, status_code=%d", url, req.status_code + "Downloading '%s' failed, status_code=%d", url, req.status_code ) hass.bus.fire( f"{DOMAIN}_{DOWNLOAD_FAILED_EVENT}", diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index 3e08d0e7b349e5..76353415d4f5f3 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -103,7 +103,7 @@ async def _update_duckdns(session, domain, token, *, txt=_SENTINEL, clear=False) def async_track_time_interval_backoff(hass, action, intervals) -> CALLBACK_TYPE: """Add a listener that fires repetitively at every timedelta interval.""" if not iscoroutinefunction: - _LOGGER.error("action needs to be a coroutine and return True/False") + _LOGGER.error("Action needs to be a coroutine and return True/False") return if not isinstance(intervals, (list, tuple)): diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index 1d6ff61bf59c98..f8a7254b6fadf7 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -179,7 +179,7 @@ def process_packet(self, namespace, instance, temperature): def stop(self): """Signal runner to stop and join thread.""" if self.scanning: - _LOGGER.debug("Stopping...") + _LOGGER.debug("Stopping") self.scanner.stop() _LOGGER.debug("Stopped") self.scanning = False diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index 73e20eea92cfc7..75d4bff3dd183b 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -144,9 +144,7 @@ def login_fail_callback(data): @callback def connection_fail_callback(data): """Network failure callback.""" - _LOGGER.error( - "Could not establish a connection with the Envisalink- retrying..." - ) + _LOGGER.error("Could not establish a connection with the Envisalink- retrying") if not sync_connect.done(): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink) sync_connect.set_result(True) @@ -162,13 +160,13 @@ def connection_success_callback(data): @callback def zones_updated_callback(data): """Handle zone timer updates.""" - _LOGGER.debug("Envisalink sent a zone update event. Updating zones...") + _LOGGER.debug("Envisalink sent a zone update event. Updating zones") async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data) @callback def alarm_data_updated_callback(data): """Handle non-alarm based info updates.""" - _LOGGER.debug("Envisalink sent new alarm info. Updating alarms...") + _LOGGER.debug("Envisalink sent new alarm info. Updating alarms") async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data) @callback diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index e707de5f543cf2..85b31c31576b8c 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -230,7 +230,7 @@ def set_speed(self, speed: str) -> None: async def async_set_speed_deprecated(self, speed: str): """Set the speed of the fan.""" _LOGGER.warning( - "fan.set_speed is deprecated, use fan.set_percentage or fan.set_preset_mode instead." + "The fan.set_speed service is deprecated, use fan.set_percentage or fan.set_preset_mode instead" ) await self.async_set_speed(speed) @@ -368,7 +368,7 @@ async def async_turn_on_compat( percentage = None elif speed is not None: _LOGGER.warning( - "Calling fan.turn_on with the speed argument is deprecated, use percentage or preset_mode instead." + "Calling fan.turn_on with the speed argument is deprecated, use percentage or preset_mode instead" ) if speed in self.preset_modes: preset_mode = speed diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 4ccd2b6f848bcb..96d6ecc2166954 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -75,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for account in balance_accounts: if config[CONF_ACCOUNTS] and account.iban not in account_config: - _LOGGER.info("skipping account %s for bank %s", account.iban, fints_name) + _LOGGER.info("Skipping account %s for bank %s", account.iban, fints_name) continue account_name = account_config.get(account.iban) @@ -87,7 +87,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for account in holdings_accounts: if config[CONF_HOLDINGS] and account.accountnumber not in holdings_config: _LOGGER.info( - "skipping holdings %s for bank %s", account.accountnumber, fints_name + "Skipping holdings %s for bank %s", account.accountnumber, fints_name ) continue diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index cfbfd670d0522a..a6dccf576026ad 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -45,7 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): path = config.get(CONF_FOLDER_PATHS) if not hass.config.is_allowed_path(path): - _LOGGER.error("folder %s is not valid or allowed", path) + _LOGGER.error("Folder %s is not valid or allowed", path) else: folder = Folder(path, config.get(CONF_FILTER)) add_entities([folder], True) diff --git a/homeassistant/components/folder_watcher/__init__.py b/homeassistant/components/folder_watcher/__init__.py index d99e4928cc5d5d..7d3fd5e77a7a87 100644 --- a/homeassistant/components/folder_watcher/__init__.py +++ b/homeassistant/components/folder_watcher/__init__.py @@ -43,7 +43,7 @@ def setup(hass, config): path = watcher[CONF_FOLDER] patterns = watcher[CONF_PATTERNS] if not hass.config.is_allowed_path(path): - _LOGGER.error("folder %s is not valid or allowed", path) + _LOGGER.error("Folder %s is not valid or allowed", path) return False Watcher(path, patterns, hass) diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index d600546c3b04ed..ea20f0a07fb0ab 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -68,7 +68,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a Foscam IP Camera.""" LOGGER.warning( - "Loading foscam via platform config is deprecated, it will be automatically imported. Please remove it afterwards." + "Loading foscam via platform config is deprecated, it will be automatically imported; Please remove it afterwards" ) config_new = { diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index bfd13c730f8b1f..5ec36c97fa0768 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -127,16 +127,16 @@ async def async_step_import(self, import_config): return await self._validate_and_create(import_config) except CannotConnect: - LOGGER.error("Error importing foscam platform config: cannot connect.") + LOGGER.error("Error importing foscam platform config: cannot connect") return self.async_abort(reason="cannot_connect") except InvalidAuth: - LOGGER.error("Error importing foscam platform config: invalid auth.") + LOGGER.error("Error importing foscam platform config: invalid auth") return self.async_abort(reason="invalid_auth") except InvalidResponse: LOGGER.exception( - "Error importing foscam platform config: invalid response from camera." + "Error importing foscam platform config: invalid response from camera" ) return self.async_abort(reason="invalid_response") @@ -145,7 +145,7 @@ async def async_step_import(self, import_config): except Exception: # pylint: disable=broad-except LOGGER.exception( - "Error importing foscam platform config: unexpected exception." + "Error importing foscam platform config: unexpected exception" ) return self.async_abort(reason="unknown") diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 62302e8f6697eb..05fcb639e4725f 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -33,8 +33,8 @@ async def async_setup_platform( ) -> None: """Convert old style file configs to new style configs.""" _LOGGER.warning( - "Loading gogogate2 via platform config is deprecated. The configuration" - " has been migrated to a config entry and can be safely removed." + "Loading gogogate2 via platform config is deprecated; The configuration" + " has been migrated to a config entry and can be safely removed" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index e6ed422db0f33e..50d269207de161 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -399,7 +399,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = STORAGE_SENSOR_TYPES else: _LOGGER.debug( - "Device type %s was found but is not supported right now.", + "Device type %s was found but is not supported right now", device["deviceType"], ) diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index ea211ccd748add..c927b04de25269 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -82,7 +82,7 @@ def set_volume_level(self, volume): def play_media(self, media_type, media_id, **kwargs): """Play media.""" if media_type != MEDIA_TYPE_MUSIC: - _LOGGER.error("invalid media type") + _LOGGER.error("Invalid media type") return self._player.queue(media_id) diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index 4d61a86de75d2f..dd17243cc9c536 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -96,8 +96,8 @@ async def update(self): except ClientResponseError as error: if error.status == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning( - "Sensor data update for %s has too many API requests." - " Skipping the update.", + "Sensor data update for %s has too many API requests;" + " Skipping the update", DOMAIN, ) else: @@ -113,8 +113,8 @@ async def update(self): except ClientResponseError as error: if error.status == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning( - "Sensor data update for %s has too many API requests." - " Skipping the update.", + "Sensor data update for %s has too many API requests;" + " Skipping the update", DOMAIN, ) else: diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 56045f0eb1cf0e..10b983fb03419a 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -221,7 +221,7 @@ def _on_connect(self): async def _on_disconnect(self): """Handle disconnecting.""" if self._connected: - _LOGGER.debug("Connection lost! Reconnect...") + _LOGGER.debug("Connection lost! Reconnect") await self.async_connect() else: dispatcher.async_dispatcher_send(self.hass, EVENT_HANGOUTS_DISCONNECTED) diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index d92342c1fb0974..c7dfd335c3212f 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -218,7 +218,7 @@ def _adapter_watchdog(now=None): _LOGGER.debug("Reached _adapter_watchdog") event.async_call_later(hass, WATCHDOG_INTERVAL, _adapter_watchdog) if not adapter.initialized: - _LOGGER.info("Adapter not initialized. Trying to restart.") + _LOGGER.info("Adapter not initialized; Trying to restart") hass.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE) adapter.init() diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index ace6540fe71b0c..4634c6e378ad32 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -81,7 +81,7 @@ def get_device_name(self, device): def _login(self): """Log in to the router. This is required for subsequent api calls.""" - _LOGGER.info("Logging in to CODA...") + _LOGGER.info("Logging in to CODA") try: data = [("user", self._username), (self._type, self._password)] @@ -101,7 +101,7 @@ def _login(self): def _update_info(self): """Get ARP from router.""" - _LOGGER.info("Fetching...") + _LOGGER.info("Fetching") if self._userid is None: if not self._login(): diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 309f98e6095632..67eb94a97e73be 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -58,7 +58,8 @@ async def async_handle_turn_service(service): # Generic turn on/off method requires entity id if not all_referenced: _LOGGER.error( - "homeassistant.%s cannot be called without a target", service.service + "The service homeassistant.%s cannot be called without a target", + service.service, ) return diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 8b606036a48e49..4fea31e723808a 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -536,7 +536,7 @@ def add_bridge_accessory(self, state): "The bridge %s has entity %s. For best performance, " "and to prevent unexpected unavailability, create and " "pair a separate HomeKit instance in accessory mode for " - "this entity.", + "this entity", self._name, state.entity_id, ) diff --git a/homeassistant/components/homekit/img_util.py b/homeassistant/components/homekit/img_util.py index 2baede8d957bb8..860d798f1137b6 100644 --- a/homeassistant/components/homekit/img_util.py +++ b/homeassistant/components/homekit/img_util.py @@ -63,6 +63,6 @@ def __init__(self): TurboJPEGSingleton.__instance = TurboJPEG() except Exception: # pylint: disable=broad-except _LOGGER.exception( - "libturbojpeg is not installed, cameras may impact HomeKit performance" + "Error loading libturbojpeg; Cameras may impact HomeKit performance" ) TurboJPEGSingleton.__instance = False diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index cb0feb6ba771d1..2c251d41fb3989 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -132,8 +132,8 @@ async def async_set_temperature(self, **kwargs): else: hvac_mode = TARGET_HEATER_COOLER_STATE_HOMEKIT_TO_HASS.get(state) _LOGGER.warning( - "HomeKit device %s: Setting temperature in %s mode is not supported yet." - " Consider raising a ticket if you have this device and want to help us implement this feature.", + "HomeKit device %s: Setting temperature in %s mode is not supported yet;" + " Consider raising a ticket if you have this device and want to help us implement this feature", self.entity_id, hvac_mode, ) @@ -147,8 +147,8 @@ async def async_set_hvac_mode(self, hvac_mode): return if hvac_mode not in {HVAC_MODE_HEAT, HVAC_MODE_COOL}: _LOGGER.warning( - "HomeKit device %s: Setting temperature in %s mode is not supported yet." - " Consider raising a ticket if you have this device and want to help us implement this feature.", + "HomeKit device %s: Setting temperature in %s mode is not supported yet;" + " Consider raising a ticket if you have this device and want to help us implement this feature", self.entity_id, hvac_mode, ) diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py index 5b9fb6569381a3..e6eb211206d49d 100644 --- a/homeassistant/components/horizon/media_player.py +++ b/homeassistant/components/horizon/media_player.py @@ -184,9 +184,7 @@ def _send(self, key=None, channel=None): elif channel: self._client.select_channel(channel) except OSError as msg: - _LOGGER.error( - "%s disconnected: %s. Trying to reconnect...", self._name, msg - ) + _LOGGER.error("%s disconnected: %s. Trying to reconnect", self._name, msg) # for reconnect, first gracefully disconnect self._client.disconnect() diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index d82e7e03e40a8c..5ceb252dfa96af 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -197,14 +197,14 @@ def _get_data(self, key: str, func: Callable[[], Any]) -> None: self.subscriptions.pop(key) except ResponseErrorLoginRequiredException: if isinstance(self.connection, AuthorizedConnection): - _LOGGER.debug("Trying to authorize again...") + _LOGGER.debug("Trying to authorize again") if self.connection.enforce_authorized_connection(): _LOGGER.debug( - "...success, %s will be updated by a future periodic run", + "success, %s will be updated by a future periodic run", key, ) else: - _LOGGER.debug("...failed") + _LOGGER.debug("failed") return _LOGGER.info( "%s requires authorization, excluding from future updates", key diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index dc9b56fcdfeea0..c14caa89620e42 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -252,7 +252,7 @@ async def handle_unauthorized_error(self): # we already created a new config flow, no need to do it again return LOGGER.error( - "Unable to authorize to bridge %s, setup the linking again.", self.host + "Unable to authorize to bridge %s, setup the linking again", self.host ) self.authorized = False create_config_flow(self.hass, self.host) diff --git a/homeassistant/components/huisbaasje/__init__.py b/homeassistant/components/huisbaasje/__init__.py index 23dc3cb7edaf98..8cd2681c8da8b9 100644 --- a/homeassistant/components/huisbaasje/__init__.py +++ b/homeassistant/components/huisbaasje/__init__.py @@ -100,7 +100,7 @@ async def async_update_huisbaasje(huisbaasje): # handled by the data update coordinator. async with async_timeout.timeout(FETCH_TIMEOUT): if not huisbaasje.is_authenticated(): - _LOGGER.warning("Huisbaasje is unauthenticated. Reauthenticating...") + _LOGGER.warning("Huisbaasje is unauthenticated. Reauthenticating") await huisbaasje.authenticate() current_measurements = await huisbaasje.current_measurements() diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 5c7f448668de88..97bba3c5ca61aa 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -125,9 +125,9 @@ def setup(self) -> None: # Login failed which means credentials need to be updated. _LOGGER.error( ( - "Your password for '%s' is no longer working. Go to the " + "Your password for '%s' is no longer working; Go to the " "Integrations menu and click on Configure on the discovered Apple " - "iCloud card to login again." + "iCloud card to login again" ), self._config_entry.data[CONF_USERNAME], ) diff --git a/homeassistant/components/insteon/config_flow.py b/homeassistant/components/insteon/config_flow.py index d8e17cfa03fe2d..7af396baa1e0f8 100644 --- a/homeassistant/components/insteon/config_flow.py +++ b/homeassistant/components/insteon/config_flow.py @@ -65,10 +65,10 @@ async def _async_connect(**kwargs): """Connect to the Insteon modem.""" try: await async_connect(**kwargs) - _LOGGER.info("Connected to Insteon modem.") + _LOGGER.info("Connected to Insteon modem") return True except ConnectionError: - _LOGGER.error("Could not connect to Insteon modem.") + _LOGGER.error("Could not connect to Insteon modem") return False diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index a2648d0dbc449b..de43407c371ce4 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -144,7 +144,7 @@ async def async_setup_entry( https = True port = host.port or 443 else: - _LOGGER.error("isy994 host value in configuration is invalid") + _LOGGER.error("The isy994 host value in configuration is invalid") return False # Connect to ISY controller. diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 3d52687bced673..95c8a3664fb676 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -60,7 +60,7 @@ async def validate_input(hass: core.HomeAssistant, data): https = True port = host.port or 443 else: - _LOGGER.error("isy994 host value in configuration is invalid") + _LOGGER.error("The isy994 host value in configuration is invalid") raise InvalidHost # Connect to ISY controller. diff --git a/homeassistant/components/keba/__init__.py b/homeassistant/components/keba/__init__.py index 764110f94b9603..e1cf9bfd3ea966 100644 --- a/homeassistant/components/keba/__init__.py +++ b/homeassistant/components/keba/__init__.py @@ -233,7 +233,7 @@ async def async_set_failsafe(self, param=None): self._set_fast_polling() except (KeyError, ValueError) as ex: _LOGGER.warning( - "failsafe_timeout, failsafe_fallback and/or " - "failsafe_persist value are not correct. %s", + "Values are not correct for: failsafe_timeout, failsafe_fallback and/or " + "failsafe_persist: %s", ex, ) diff --git a/homeassistant/components/keenetic_ndms2/router.py b/homeassistant/components/keenetic_ndms2/router.py index 0066b49223b4d7..049d9aab0de736 100644 --- a/homeassistant/components/keenetic_ndms2/router.py +++ b/homeassistant/components/keenetic_ndms2/router.py @@ -167,7 +167,7 @@ def _update_router_info(self): def _update_devices(self): """Get ARP from keenetic router.""" - _LOGGER.debug("Fetching devices from router...") + _LOGGER.debug("Fetching devices from router") try: _response = self._client.get_devices( diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py index 6a94cc5a393c3d..8a0eeed83f0ead 100644 --- a/homeassistant/components/kiwi/lock.py +++ b/homeassistant/components/kiwi/lock.py @@ -102,7 +102,7 @@ def unlock(self, **kwargs): try: self._client.open_door(self.lock_id) except KiwiException: - _LOGGER.error("failed to open door") + _LOGGER.error("Failed to open door") else: self._state = STATE_UNLOCKED self.hass.add_job( diff --git a/homeassistant/components/konnected/panel.py b/homeassistant/components/konnected/panel.py index 18f2ed64a1dcd9..cf2f33de33292b 100644 --- a/homeassistant/components/konnected/panel.py +++ b/homeassistant/components/konnected/panel.py @@ -376,7 +376,7 @@ async def async_sync_device_config(self): self.async_desired_settings_payload() != self.async_current_settings_payload() ): - _LOGGER.info("pushing settings to device %s", self.device_id) + _LOGGER.info("Pushing settings to device %s", self.device_id) await self.client.put_settings(**self.async_desired_settings_payload()) diff --git a/homeassistant/components/lifx_legacy/light.py b/homeassistant/components/lifx_legacy/light.py index 4d50ecbecf252e..795f3e177932bb 100644 --- a/homeassistant/components/lifx_legacy/light.py +++ b/homeassistant/components/lifx_legacy/light.py @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the LIFX platform.""" _LOGGER.warning( "The LIFX Legacy platform is deprecated and will be removed in " - "Home Assistant Core 2021.6.0. Use the LIFX integration instead." + "Home Assistant Core 2021.6.0; Use the LIFX integration instead" ) server_addr = config.get(CONF_SERVER) diff --git a/homeassistant/components/meraki/device_tracker.py b/homeassistant/components/meraki/device_tracker.py index 55186d6314673d..13644c1d341d12 100644 --- a/homeassistant/components/meraki/device_tracker.py +++ b/homeassistant/components/meraki/device_tracker.py @@ -56,7 +56,7 @@ async def post(self, request): return self.json_message("Invalid JSON", HTTP_BAD_REQUEST) _LOGGER.debug("Meraki Data from Post: %s", json.dumps(data)) if not data.get("secret", False): - _LOGGER.error("secret invalid") + _LOGGER.error("The secret is invalid") return self.json_message("No secret", HTTP_UNPROCESSABLE_ENTITY) if data["secret"] != self.secret: _LOGGER.error("Invalid Secret received from Meraki") diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 3034135f847453..1229a4e43affaa 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -159,7 +159,7 @@ async def _async_update_data_alert(): ) else: _LOGGER.warning( - "Weather alert not available: The city %s is not in metropolitan France or Andorre.", + "Weather alert not available: The city %s is not in metropolitan France or Andorre", entry.title, ) @@ -189,7 +189,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): ].data.position.get("dept") hass.data[DOMAIN][department] = False _LOGGER.debug( - "Weather alert for depatment %s unloaded and released. It can be added now by another city.", + "Weather alert for depatment %s unloaded and released. It can be added now by another city", department, ) diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index 09e062cc715632..08d5c1c4f6ad1a 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -59,7 +59,7 @@ async def async_setup_entry( True, ) _LOGGER.debug( - "Weather entity (%s) added for %s.", + "Weather entity (%s) added for %s", entry.options.get(CONF_MODE, FORECAST_MODE_DAILY), coordinator.data.position["name"], ) diff --git a/homeassistant/components/motion_blinds/gateway.py b/homeassistant/components/motion_blinds/gateway.py index 14dd36ce5b0e8d..6f8032e5a652eb 100644 --- a/homeassistant/components/motion_blinds/gateway.py +++ b/homeassistant/components/motion_blinds/gateway.py @@ -30,7 +30,7 @@ def update_gateway(self): async def async_connect_gateway(self, host, key): """Connect to the Motion Gateway.""" - _LOGGER.debug("Initializing with host %s (key %s...)", host, key[:3]) + _LOGGER.debug("Initializing with host %s (key %s)", host, key[:3]) self._gateway_device = MotionGateway( ip=host, key=key, multicast=self._multicast ) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 7f810501e1a432..010f751dad47c8 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -120,7 +120,7 @@ def validate_options(value): and CONF_VALUE_TEMPLATE in value ): _LOGGER.warning( - "using 'value_template' for 'position_topic' is deprecated " + "Using 'value_template' for 'position_topic' is deprecated " "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py index 510245ea8592de..145bdadcc2a4f6 100644 --- a/homeassistant/components/mystrom/light.py +++ b/homeassistant/components/mystrom/light.py @@ -145,7 +145,7 @@ async def async_turn_off(self, **kwargs): try: await self._bulb.set_off() except MyStromConnectionError: - _LOGGER.warning("myStrom bulb not online") + _LOGGER.warning("The myStrom bulb not online") async def async_update(self): """Fetch new state data for this light.""" diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index d76133c91abf8d..b9b04a08febbe7 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -207,7 +207,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): await hass.async_add_executor_job( hass.data[DOMAIN][entry.entry_id][AUTH].dropwebhook ) - _LOGGER.info("Unregister Netatmo webhook.") + _LOGGER.info("Unregister Netatmo webhook") await hass.data[DOMAIN][entry.entry_id][DATA_HANDLER].async_cleanup() diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 6558717b847d7d..caa4aebe3764f3 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -123,10 +123,10 @@ async def get_entities(): entities = [] for home_id in get_all_home_ids(home_data): - _LOGGER.debug("Setting up home %s ...", home_id) + _LOGGER.debug("Setting up home %s", home_id) for room_id in home_data.rooms[home_id].keys(): room_name = home_data.rooms[home_id][room_id]["name"] - _LOGGER.debug("Setting up room %s (%s) ...", room_name, room_id) + _LOGGER.debug("Setting up room %s (%s)", room_name, room_id) signal_name = f"{HOMESTATUS_DATA_CLASS_NAME}-{home_id}" await data_handler.register_data_class( HOMESTATUS_DATA_CLASS_NAME, signal_name, None, home_id=home_id diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 967857aedc509f..24adf2237199a0 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -135,7 +135,7 @@ async def async_handle_start_charge(service): def setup_leaf(car_config): """Set up a car.""" - _LOGGER.debug("Logging into You+Nissan...") + _LOGGER.debug("Logging into You+Nissan") username = car_config[CONF_USERNAME] password = car_config[CONF_PASSWORD] diff --git a/homeassistant/components/nmap_tracker/device_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py index 608f90d5421292..69c65873e513ea 100644 --- a/homeassistant/components/nmap_tracker/device_tracker.py +++ b/homeassistant/components/nmap_tracker/device_tracker.py @@ -89,7 +89,7 @@ def _update_info(self): Returns boolean if scanning successful. """ - _LOGGER.debug("Scanning...") + _LOGGER.debug("Scanning") scanner = PortScanner() diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 6c6e155a0462f4..9bd6f629459584 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -50,7 +50,7 @@ def wsdiscovery() -> list[Service]: async def async_discovery(hass) -> bool: """Return if there are devices that can be discovered.""" - LOGGER.debug("Starting ONVIF discovery...") + LOGGER.debug("Starting ONVIF discovery") services = await hass.async_add_executor_job(wsdiscovery) devices = [] diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 07be1fbfd03b41..761eb2fc2dc80e 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -439,7 +439,7 @@ async def async_perform_ptz( await ptz_service.Stop(req) except ONVIFError as err: if "Bad Request" in err.reason: - LOGGER.warning("Device '%s' doesn't support PTZ.", self.name) + LOGGER.warning("Device '%s' doesn't support PTZ", self.name) else: LOGGER.error("Error trying to perform PTZ action: %s", err) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 064f0dcfa0fc7a..91db2a90e57b6d 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -132,7 +132,7 @@ async def async_restart(self, _now: dt = None) -> None: if not restarted: LOGGER.warning( - "Failed to restart ONVIF PullPoint subscription for '%s'. Retrying...", + "Failed to restart ONVIF PullPoint subscription for '%s'. Retrying", self.unique_id, ) # Try again in a minute diff --git a/homeassistant/components/orvibo/switch.py b/homeassistant/components/orvibo/switch.py index a03d9fc5ff0d8a..f7a16036f009f3 100644 --- a/homeassistant/components/orvibo/switch.py +++ b/homeassistant/components/orvibo/switch.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): switch_conf = config.get(CONF_SWITCHES, [config]) if config.get(CONF_DISCOVERY): - _LOGGER.info("Discovering S20 switches ...") + _LOGGER.info("Discovering S20 switches") switch_data.update(discover()) for switch in switch_conf: diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 449515802b9b76..67cf07dc433389 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -94,7 +94,7 @@ async def async_setup_entry(hass, config_entry): unique_id = config_entry.unique_id if device_info is None: _LOGGER.error( - "Couldn't gather device info. Please restart Home Assistant with your TV turned on and connected to your network." + "Couldn't gather device info; Please restart Home Assistant with your TV turned on and connected to your network" ) else: unique_id = device_info[ATTR_UDN] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index c8d383867cfadf..6e1a83297b3017 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -418,9 +418,11 @@ def connect_to_resource(resource): """Connect to a plex.tv resource and return a Plex client.""" try: client = resource.connect(timeout=3) - _LOGGER.debug("plex.tv resource connection successful: %s", client) + _LOGGER.debug("Resource connection successful to plex.tv: %s", client) except NotFound: - _LOGGER.error("plex.tv resource connection failed: %s", resource.name) + _LOGGER.error( + "Resource connection failed to plex.tv: %s", resource.name + ) else: client.proxyThroughServer(value=False, server=self._plex_server) self._client_device_cache[client.machineIdentifier] = client diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py index 858f87e74f8907..aeabe8634f8599 100644 --- a/homeassistant/components/plum_lightpad/__init__.py +++ b/homeassistant/components/plum_lightpad/__init__.py @@ -38,7 +38,7 @@ async def async_setup(hass: HomeAssistant, config: dict): conf = config[DOMAIN] - _LOGGER.info("Found Plum Lightpad configuration in config, importing...") + _LOGGER.info("Found Plum Lightpad configuration in config, importing") hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=conf diff --git a/homeassistant/components/poolsense/__init__.py b/homeassistant/components/poolsense/__init__.py index 3dc9ace1b8f12c..b11b7732e19123 100644 --- a/homeassistant/components/poolsense/__init__.py +++ b/homeassistant/components/poolsense/__init__.py @@ -118,7 +118,7 @@ async def _async_update_data(self): try: data = await self.poolsense.get_poolsense_data() except (PoolSenseError) as error: - _LOGGER.error("PoolSense query did not complete.") + _LOGGER.error("PoolSense query did not complete") raise UpdateFailed(error) from error return data diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py index bec8540901033f..3337af0f8b062d 100644 --- a/homeassistant/components/pushsafer/notify.py +++ b/homeassistant/components/pushsafer/notify.py @@ -93,7 +93,7 @@ def send_message(self, message="", **kwargs): _LOGGER.debug("Loading image from file %s", local_path) picture1_encoded = self.load_from_file(local_path) else: - _LOGGER.warning("missing url or local_path for picture1") + _LOGGER.warning("Missing url or local_path for picture1") else: _LOGGER.debug("picture1 is not specified") @@ -143,7 +143,7 @@ def load_from_url(self, url=None, username=None, password=None, auth=None): else: response = requests.get(url, timeout=CONF_TIMEOUT) return self.get_base64(response.content, response.headers["content-type"]) - _LOGGER.warning("url not found in param") + _LOGGER.warning("No url was found in param") return None diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index d7bca1175cb419..aad6bf3989ef35 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -372,6 +372,6 @@ def set_preset_mode(self, preset_mode): self.device.program_mode = PRESET_MODE_TO_CODE[preset_mode] else: _LOGGER.error( - "preset_mode %s not in PRESET_MODES", + "Preset_mode %s not in PRESET_MODES", preset_mode, ) diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index b6a99f4ebea198..000d76b54c721d 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -57,8 +57,8 @@ async def async_setup_platform( ): """Import Recollect Waste configuration from YAML.""" LOGGER.warning( - "Loading ReCollect Waste via platform setup is deprecated. " - "Please remove it from your configuration." + "Loading ReCollect Waste via platform setup is deprecated; " + "Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index b04a4fb7f1f987..c17fb33d365ed2 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -175,7 +175,7 @@ def validate_sqlite_database(dbpath: str, db_integrity_check: bool) -> bool: run_checks_on_open_db(dbpath, conn.cursor(), db_integrity_check) conn.close() except sqlite3.DatabaseError: - _LOGGER.exception("The database at %s is corrupt or malformed.", dbpath) + _LOGGER.exception("The database at %s is corrupt or malformed", dbpath) return False return True @@ -210,7 +210,7 @@ def run_checks_on_open_db(dbpath, cursor, db_integrity_check): if not last_run_was_clean: _LOGGER.warning( - "The system could not validate that the sqlite3 database at %s was shutdown cleanly.", + "The system could not validate that the sqlite3 database at %s was shutdown cleanly", dbpath, ) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 18f02d66a31f5d..81d44806c5e358 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -246,7 +246,7 @@ def reconnect(exc=None): # If HA is not stopping, initiate new connection if hass.state != CoreState.stopping: - _LOGGER.warning("disconnected from Rflink, reconnecting") + _LOGGER.warning("Disconnected from Rflink, reconnecting") hass.async_create_task(connect()) async def connect(): diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 649a573c5b4527..d23a3e4e6ffb06 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -428,7 +428,7 @@ def find_possible_pt2262_device(device_ids, device_id): if size is not None: size = len(dev_id) - size - 1 _LOGGER.info( - "rfxtrx: found possible device %s for %s " + "Found possible device %s for %s " "with the following configuration:\n" "data_bits=%d\n" "command_on=0x%s\n" diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py index 720b59f80b9626..a5e0f248bc6b1f 100644 --- a/homeassistant/components/screenlogic/__init__.py +++ b/homeassistant/components/screenlogic/__init__.py @@ -46,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if mac in discovered_gateways: connect_info = discovered_gateways[mac] else: - _LOGGER.warning("Gateway rediscovery failed.") + _LOGGER.warning("Gateway rediscovery failed") # Static connection defined or fallback from discovery connect_info = { SL_GATEWAY_NAME: name_for_mac(mac), diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py index aa1e643681ee55..7c1a468df7d689 100644 --- a/homeassistant/components/screenlogic/switch.py +++ b/homeassistant/components/screenlogic/switch.py @@ -47,10 +47,10 @@ async def _async_set_circuit(self, circuit_value) -> None: if await self.hass.async_add_executor_job( self.gateway.set_circuit, self._data_key, circuit_value ): - _LOGGER.info("screenlogic turn %s %s", circuit_value, self._data_key) + _LOGGER.debug("Screenlogic turn %s %s", circuit_value, self._data_key) await self.coordinator.async_request_refresh() else: - _LOGGER.info("screenlogic turn %s %s error", circuit_value, self._data_key) + _LOGGER.info("Screenlogic turn %s %s error", circuit_value, self._data_key) @property def circuit(self): diff --git a/homeassistant/components/screenlogic/water_heater.py b/homeassistant/components/screenlogic/water_heater.py index 2a0a8e82c8003d..6b16f68e141b96 100644 --- a/homeassistant/components/screenlogic/water_heater.py +++ b/homeassistant/components/screenlogic/water_heater.py @@ -105,7 +105,7 @@ async def async_set_temperature(self, **kwargs) -> None: ): await self.coordinator.async_request_refresh() else: - _LOGGER.error("screenlogic set_temperature error") + _LOGGER.error("Screenlogic set_temperature error") async def async_set_operation_mode(self, operation_mode) -> None: """Set the operation mode.""" @@ -115,7 +115,7 @@ async def async_set_operation_mode(self, operation_mode) -> None: ): await self.coordinator.async_request_refresh() else: - _LOGGER.error("screenlogic set_operation_mode error") + _LOGGER.error("Screenlogic set_operation_mode error") @property def body(self): diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index bff9e311844c98..3308ec80b8f051 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Skybeacon sensor.""" name = config.get(CONF_NAME) mac = config.get(CONF_MAC) - _LOGGER.debug("Setting up...") + _LOGGER.debug("Setting up") mon = Monitor(hass, mac, name) add_entities([SkybeaconTemp(name, mon)]) diff --git a/homeassistant/components/smarty/__init__.py b/homeassistant/components/smarty/__init__.py index 22987673005b32..72d5071882fad4 100644 --- a/homeassistant/components/smarty/__init__.py +++ b/homeassistant/components/smarty/__init__.py @@ -59,12 +59,12 @@ def setup(hass, config): def poll_device_update(event_time): """Update Smarty device.""" - _LOGGER.debug("Updating Smarty device...") + _LOGGER.debug("Updating Smarty device") if smarty.update(): - _LOGGER.debug("Update success...") + _LOGGER.debug("Update success") dispatcher_send(hass, SIGNAL_UPDATE_SMARTY) else: - _LOGGER.debug("Update failed...") + _LOGGER.debug("Update failed") track_time_interval(hass, poll_device_update, timedelta(seconds=30)) diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index a2ade387c019f6..40240306dc473f 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -101,7 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not mylink_status or "error" in mylink_status: _LOGGER.error( - "mylink failed to setup because of an error: %s", + "Somfy Mylink failed to setup because of an error: %s", mylink_status.get("error", {}).get( "message", "Empty response from mylink device" ), diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 0b01ff94462178..9b2342e5e1bdce 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1330,7 +1330,7 @@ def set_alarm( if one_alarm._alarm_id == str(alarm_id): alarm = one_alarm if alarm is None: - _LOGGER.warning("did not find alarm with id %s", alarm_id) + _LOGGER.warning("Did not find alarm with id %s", alarm_id) return if time is not None: alarm.start_time = time diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 8d967dc2ea7114..3f82b816cd517a 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -116,7 +116,7 @@ def set_update_obd_interval(self, interval: int) -> None: def unload(self): """Unload StarLine API.""" - _LOGGER.debug("Unloading StarLine API.") + _LOGGER.debug("Unloading StarLine API") if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index 4c5c476a4025ec..a3586b32974d3b 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -107,7 +107,7 @@ async def validate_login_creds(self, data): ) _LOGGER.debug("Using subarulink %s", self.controller.version) _LOGGER.debug( - "Setting up first time connection to Subuaru API. This may take up to 20 seconds." + "Setting up first time connection to Subuaru API; This may take up to 20 seconds" ) if await self.controller.connect(): _LOGGER.debug("Successfully authenticated and authorized with Subaru API") diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index bfada33bf38a87..edec503bc2562b 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -41,7 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """Set up the SyncThru component.""" _LOGGER.warning( "Loading syncthru via platform config is deprecated and no longer " - "necessary as of 0.113. Please remove it from your configuration YAML." + "necessary as of 0.113; Please remove it from your configuration YAML" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index a5756ce9ddcaf4..2ad4863dbec3dd 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -36,7 +36,7 @@ def async_register_info( Deprecated. """ _LOGGER.warning( - "system_health.async_register_info is deprecated. Add a system_health platform instead." + "Calling system_health.async_register_info is deprecated; Add a system_health platform instead" ) hass.data.setdefault(DOMAIN, {}) SystemHealthRegistration(hass, domain).async_register_info(info_callback) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index ce856c04c64a26..805ffcdba5db0a 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -174,7 +174,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # If not, do not create the entity and add a warning to the log if resource[CONF_TYPE] == "processor_temperature": if SystemMonitorSensor.read_cpu_temperature() is None: - _LOGGER.warning("Cannot read CPU / processor temperature information.") + _LOGGER.warning("Cannot read CPU / processor temperature information") continue dev.append(SystemMonitorSensor(resource[CONF_TYPE], resource[CONF_ARG])) diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index f772e2411e50c4..7fd6cb24efdc04 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -42,7 +42,7 @@ async def async_setup_platform(hass, config): if (last_error_date is not None) and (isinstance(last_error_date, int)): last_error_date = dt.datetime.fromtimestamp(last_error_date) _LOGGER.info( - "telegram webhook last_error_date: %s. Status: %s", + "Telegram webhook last_error_date: %s. Status: %s", last_error_date, current_status, ) diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index 0b8fafd57a8fb4..dad83005512b97 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -336,7 +336,7 @@ def process_image(self, image): """Process the image.""" model = self.hass.data[DOMAIN][CONF_MODEL] if not model: - _LOGGER.debug("Model not yet ready.") + _LOGGER.debug("Model not yet ready") return start = time.perf_counter() diff --git a/homeassistant/components/twitter/notify.py b/homeassistant/components/twitter/notify.py index 62e8fc17dffabb..ac7de89a61bbb7 100644 --- a/homeassistant/components/twitter/notify.py +++ b/homeassistant/components/twitter/notify.py @@ -201,7 +201,7 @@ def check_status_until_done(self, media_id, callback, *args): method_override="GET", ) if resp.status_code != HTTP_OK: - _LOGGER.error("media processing error: %s", resp.json()) + _LOGGER.error("Media processing error: %s", resp.json()) processing_info = resp.json()["processing_info"] _LOGGER.debug("media processing %s status: %s", media_id, processing_info) diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index 0f463aec666ebe..756862456f01a2 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -127,7 +127,7 @@ async def async_setup(hass: HomeAssistantType, config) -> bool: _LOGGER.warning( "Loading upcloud via top level config is deprecated and no longer " - "necessary as of 0.117. Please remove it from your YAML configuration." + "necessary as of 0.117; Please remove it from your YAML configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index c6632868ae3b39..2c3c365b15a306 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -273,7 +273,8 @@ async def async_step_user( if errors and self.context["source"] == SOURCE_IMPORT: # Log an error message if import config flow fails since otherwise failure is silent _LOGGER.error( - "configuration.yaml import failure: %s", ", ".join(errors.values()) + "Importing from configuration.yaml failed: %s", + ", ".join(errors.values()), ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 9c6dfe45e746cb..6d61f5d62dc8a0 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -89,7 +89,7 @@ async def async_handle_webhook(hass, webhook_id, request): # Look at content to provide some context for received webhook # Limit to 64 chars to avoid flooding the log content = await request.content.read(64) - _LOGGER.debug("%s...", content) + _LOGGER.debug("%s", content) return Response(status=HTTP_OK) try: diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 75c1bdd2bcc0ee..f4d4b67b8b0689 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -386,7 +386,7 @@ async def async_play_media(self, media_type, media_id, **kwargs): _LOGGER.debug("Call play media type <%s>, Id <%s>", media_type, media_id) if media_type == MEDIA_TYPE_CHANNEL: - _LOGGER.debug("Searching channel...") + _LOGGER.debug("Searching channel") partial_match_channel_id = None perfect_match_channel_id = None diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index db380ae11cac2f..a013d1fdd3461b 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -113,7 +113,7 @@ async def async_stop_wemo(event): static_conf = config.get(CONF_STATIC, []) if static_conf: - _LOGGER.debug("Adding statically configured WeMo devices...") + _LOGGER.debug("Adding statically configured WeMo devices") for device in await asyncio.gather( *[ hass.async_add_executor_job(validate_static_config, host, port) @@ -190,7 +190,7 @@ def __init__(self, hass: HomeAssistant, wemo_dispatcher: WemoDispatcher) -> None async def async_discover_and_schedule(self, *_) -> None: """Periodically scan the network looking for WeMo devices.""" - _LOGGER.debug("Scanning network for WeMo devices...") + _LOGGER.debug("Scanning network for WeMo devices") try: for device in await self._hass.async_add_executor_job( pywemo.discover_devices diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 0efbc80f13c2ed..5da19f54dcf34f 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -150,7 +150,7 @@ def binary_event_callback_url(self): def handle_update_tags_event(self, event): """Handle push event from wireless tag manager.""" - _LOGGER.info("push notification for update arrived: %s", event) + _LOGGER.info("Push notification for update arrived: %s", event) try: tag_id = event.data.get("id") mac = event.data.get("mac") diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index adb18b0de5a0bb..09a9786c372278 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -127,7 +127,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import Miio configuration from YAML.""" _LOGGER.warning( - "Loading Xiaomi Miio Switch via platform setup is deprecated. Please remove it from your configuration." + "Loading Xiaomi Miio Switch via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index b6cb2b76ae68fe..8551a80ff891bb 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -122,7 +122,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import Miio configuration from YAML.""" _LOGGER.warning( - "Loading Xiaomi Miio Vacuum via platform setup is deprecated. Please remove it from your configuration." + "Loading Xiaomi Miio Vacuum via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index 9392b9be4032de..1d65b2bcfd1e74 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -68,7 +68,7 @@ def setup(hass, config): ) return False - _LOGGER.debug("Establishing connection to XS1 gateway and retrieving data...") + _LOGGER.debug("Establishing connection to XS1 gateway and retrieving data") hass.data[DOMAIN] = {} @@ -78,7 +78,7 @@ def setup(hass, config): hass.data[DOMAIN][ACTUATORS] = actuators hass.data[DOMAIN][SENSORS] = sensors - _LOGGER.debug("Loading platforms for XS1 integration...") + _LOGGER.debug("Loading platforms for XS1 integration") # Load platforms for supported devices for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 4ef0da85daac29..536709e5a83b37 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): zapi = hass.data[zabbix.DOMAIN] if not zapi: - _LOGGER.error("zapi is None. Zabbix integration hasn't been loaded?") + _LOGGER.error("Zabbix integration hasn't been loaded? zapi is None") return False _LOGGER.info("Connected to Zabbix API Version %s", zapi.api_version()) diff --git a/homeassistant/components/zeroconf/usage.py b/homeassistant/components/zeroconf/usage.py index 1303412249c47a..1af6e3e1f3c294 100644 --- a/homeassistant/components/zeroconf/usage.py +++ b/homeassistant/components/zeroconf/usage.py @@ -43,7 +43,7 @@ def _report(what: str) -> None: if not integration_frame: _LOGGER.warning( - "Detected code that %s. Please report this issue.", what, stack_info=True + "Detected code that %s; Please report this issue", what, stack_info=True ) return diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index b8265f1e089cd1..12ea668dce8670 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -889,9 +889,7 @@ def reset_node_meters(service): continue network.manager.pressButton(value.value_id) network.manager.releaseButton(value.value_id) - _LOGGER.info( - "Resetting meters on node %s instance %s....", node_id, instance - ) + _LOGGER.info("Resetting meters on node %s instance %s", node_id, instance) return _LOGGER.info( "Node %s on instance %s does not have resettable meters", node_id, instance @@ -915,7 +913,7 @@ def test_node(service): def start_zwave(_service_or_event): """Startup Z-Wave network.""" - _LOGGER.info("Starting Z-Wave network...") + _LOGGER.info("Starting Z-Wave network") network.start() hass.bus.fire(const.EVENT_NETWORK_START) @@ -939,7 +937,7 @@ async def _check_awaked(): "Z-Wave not ready after %d seconds, continuing anyway", waited ) _LOGGER.info( - "final network state: %d %s", network.state, network.state_str + "Final network state: %d %s", network.state, network.state_str ) break diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 64dcf9614b2a5d..3de4eac0461a1f 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -143,7 +143,7 @@ def async_on_node_added(node: ZwaveNode) -> None: async_on_node_ready(node) return # if node is not yet ready, register one-time callback for ready state - LOGGER.debug("Node added: %s - waiting for it to become ready.", node.node_id) + LOGGER.debug("Node added: %s - waiting for it to become ready", node.node_id) node.once( "ready", lambda event: async_on_node_ready(event["node"]), diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index 016aa3066d7f76..997d34c844530f 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -36,8 +36,8 @@ def async_migrate_entity( except ValueError: _LOGGER.debug( ( - "Entity %s can't be migrated because the unique ID is taken. " - "Cleaning it up since it is likely no longer valid." + "Entity %s can't be migrated because the unique ID is taken; " + "Cleaning it up since it is likely no longer valid" ), entity_id, ) diff --git a/homeassistant/core.py b/homeassistant/core.py index 64edf5742ce049..debbbeeb26331a 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -515,11 +515,13 @@ async def async_stop(self, exit_code: int = 0, *, force: bool = False) -> None: if self.state == CoreState.not_running: # just ignore return if self.state in [CoreState.stopping, CoreState.final_write]: - _LOGGER.info("async_stop called twice: ignored") + _LOGGER.info("Additional call to async_stop was ignored") return if self.state == CoreState.starting: # This may not work - _LOGGER.warning("async_stop called before startup is complete") + _LOGGER.warning( + "Stopping Home Assistant before startup has completed may fail" + ) # stage 1 self.state = CoreState.stopping diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index e97526458042c5..671009b884682b 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -982,7 +982,7 @@ def distance(hass, *args): else: if not loc_helper.has_location(point_state): _LOGGER.warning( - "distance:State does not contain valid location: %s", point_state + "Distance:State does not contain valid location: %s", point_state ) return None diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 386f14ac15736f..b03e93f17dfaa4 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -77,7 +77,7 @@ def _load_secret_yaml(self, secret_dir: Path) -> dict[str, str]: _LOGGER.setLevel(logging.DEBUG) else: _LOGGER.error( - "secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", + "Error in secrets.yaml: 'logger: debug' expected, but 'logger: %s' found", logger, ) del secrets["logger"] diff --git a/pylint/plugins/hass_logger.py b/pylint/plugins/hass_logger.py new file mode 100644 index 00000000000000..b771b07aa5ee73 --- /dev/null +++ b/pylint/plugins/hass_logger.py @@ -0,0 +1,85 @@ +import astroid +from pylint.checkers import BaseChecker +from pylint.interfaces import IAstroidChecker + +LOGGER_NAMES = ("LOGGER", "_LOGGER") +LOG_LEVEL_ALLOWED_LOWER_START = ("debug",) + +# This is our checker class. +# Checkers should always inherit from `BaseChecker`. +class HassLoggerFormatChecker(BaseChecker): + """Add class member attributes to the class locals dictionary.""" + + __implements__ = IAstroidChecker + + # The name defines a custom section of the config for this checker. + name = "hass_logger" + priority = -1 + msgs = { + "W0001": ( + "User visible logger messages must not end with a period", + "hass-logger-period", + "Periods are not permitted at the end of logger messages", + ), + "W0002": ( + "User visible logger messages must start with a capital letter or downgrade to debug", + "hass-logger-capital", + "All logger messages must start with a capital letter", + ), + } + options = ( + ( + "hass-logger", + { + "default": "properties", + "help": ( + "Validate _LOGGER or LOGGER messages conform to Home Assistant standards." + ), + }, + ), + ) + + def visit_call(self, node): + """Called when a :class:`.astroid.node_classes.Call` node is visited. + See :mod:`astroid` for the description of available nodes. + :param node: The node to check. + :type node: astroid.node_classes.Call + """ + if not isinstance(node.func, astroid.Attribute) or not isinstance( + node.func.expr, astroid.Name + ): + return + + if not node.func.expr.name in LOGGER_NAMES: + return + + if not node.args: + return + + first_arg = node.args[0] + + if not isinstance(first_arg, astroid.Const) or not first_arg.value: + return + + log_message = first_arg.value + + if len(log_message) < 1: + return + + if log_message[-1] == ".": + self.add_message("hass-logger-period", args=node.args, node=node) + + if ( + isinstance(node.func.attrname, str) + and node.func.attrname not in LOG_LEVEL_ALLOWED_LOWER_START + and log_message[0].upper() != log_message[0] + ): + self.add_message("hass-logger-capital", args=node.args, node=node) + + +def register(linter): + """This required method auto registers the checker. + :param linter: The linter to register the checker to. + :type linter: pylint.lint.PyLinter + """ + linter.register_checker(HassLoggerFormatChecker(linter)) diff --git a/pyproject.toml b/pyproject.toml index 731b7f15730438..80c182cc3b5856 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,8 +23,10 @@ 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")))' load-plugins = [ "pylint_strict_informational", + "hass_logger" ] persistent = false extension-pkg-whitelist = [ diff --git a/tests/components/duckdns/test_init.py b/tests/components/duckdns/test_init.py index 03fce0df20e5ec..a78d9d280d0c47 100644 --- a/tests/components/duckdns/test_init.py +++ b/tests/components/duckdns/test_init.py @@ -87,7 +87,7 @@ async def test_setup_backoff(hass, aioclient_mock): tme = utcnow() await hass.async_block_till_done() - _LOGGER.debug("Backoff...") + _LOGGER.debug("Backoff") for idx in range(1, len(intervals)): tme += intervals[idx] async_fire_time_changed(hass, tme) @@ -156,7 +156,7 @@ async def _return(now): assert call_count == 1 - _LOGGER.debug("Backoff...") + _LOGGER.debug("Backoff") for idx in range(1, len(intervals)): tme += intervals[idx] async_fire_time_changed(hass, tme + timedelta(seconds=0.1)) diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index d6899d5149ac8b..2a29bf9e15e924 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -2159,7 +2159,7 @@ async def test_deprecated_value_template_for_position_topic_warning( await hass.async_block_till_done() assert ( - "using 'value_template' for 'position_topic' is deprecated " + "Using 'value_template' for 'position_topic' is deprecated " "and will be removed from Home Assistant in version 2021.6, " "please replace it with 'position_template'" ) in caplog.text From e798f415a490fbe09ff5d17baa9ce93fc8130dd7 Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:42:45 +0100 Subject: [PATCH 1391/1818] Wait for switch startup in generic_thermostat (#45253) * Better status control on restore * Better status control on restore * fix code coverage * Rollback hvac_mode initialization I think I have better understood the handling of the `hvac_mode`. I change the approach. Now the thermostat doesn't initialize until the switch is available. * fix pyupgrade * fix black * Delete test_turn_on_while_restarting HVAC mode should not be modified by the switch. IMHO, this test does not make sense because if the switch is turned on the thermostat is not turning on (and not changing HVAC_MODE) * Re add turn off if HVAC is off If HVAC_MODE is off thermostat will not control heater switch. This can be because `initial_hvac_mode`, because state defaults to or because old_state. IMHO it is preferable to be excessively cautious. * Update climate.py * Change warning message * Fix black * Fix black --- .../components/generic_thermostat/climate.py | 17 +++- .../generic_thermostat/test_climate.py | 86 +++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 7062267de19751..3c7959dbf4d2c2 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -266,6 +266,14 @@ def _async_startup(*_): if not self._hvac_mode: self._hvac_mode = HVAC_MODE_OFF + # Prevent the device from keep running if HVAC_MODE_OFF + if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active: + await self._async_heater_turn_off() + _LOGGER.warning( + "The climate mode is OFF, but the switch device is ON. Turning off device %s", + self.heater_entity_id, + ) + @property def should_poll(self): """Return the polling state.""" @@ -418,7 +426,11 @@ def _async_update_temp(self, state): async def _async_control_heating(self, time=None, force=False): """Check if we need to turn heating on or off.""" async with self._temp_lock: - if not self._active and None not in (self._cur_temp, self._target_temp): + if not self._active and None not in ( + self._cur_temp, + self._target_temp, + self._is_device_active, + ): self._active = True _LOGGER.info( "Obtained current and target temperature. " @@ -480,6 +492,9 @@ async def _async_control_heating(self, time=None, force=False): @property def _is_device_active(self): """If the toggleable device is currently active.""" + if not self.hass.states.get(self.heater_entity_id): + return None + return self.hass.states.is_state(self.heater_entity_id, STATE_ON) @property diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index b83828c86c7b27..dc5353971b7aae 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -1249,6 +1249,92 @@ async def test_no_restore_state(hass): assert state.state == HVAC_MODE_OFF +async def test_initial_hvac_off_force_heater_off(hass): + """Ensure that restored state is coherent with real situation. + + 'initial_hvac_mode: off' will force HVAC status, but we must be sure + that heater don't keep on. + """ + # switch is on + calls = _setup_switch(hass, True) + assert hass.states.get(ENT_SWITCH).state == STATE_ON + + _setup_sensor(hass, 16) + + await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test_thermostat", + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "target_temp": 20, + "initial_hvac_mode": HVAC_MODE_OFF, + } + }, + ) + await hass.async_block_till_done() + state = hass.states.get("climate.test_thermostat") + # 'initial_hvac_mode' will force state but must prevent heather keep working + assert state.state == HVAC_MODE_OFF + # heater must be switched off + assert len(calls) == 1 + call = calls[0] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH + + +async def test_restore_will_turn_off_(hass): + """Ensure that restored state is coherent with real situation. + + Thermostat status must trigger heater event if temp raises the target . + """ + heater_switch = "input_boolean.test" + mock_restore_cache( + hass, + ( + State( + "climate.test_thermostat", + HVAC_MODE_HEAT, + {ATTR_TEMPERATURE: "18", ATTR_PRESET_MODE: PRESET_NONE}, + ), + State(heater_switch, STATE_ON, {}), + ), + ) + + hass.state = CoreState.starting + + assert await async_setup_component( + hass, input_boolean.DOMAIN, {"input_boolean": {"test": None}} + ) + await hass.async_block_till_done() + assert hass.states.get(heater_switch).state == STATE_ON + + _setup_sensor(hass, 22) + + await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test_thermostat", + "heater": heater_switch, + "target_sensor": ENT_SENSOR, + "target_temp": 20, + } + }, + ) + await hass.async_block_till_done() + state = hass.states.get("climate.test_thermostat") + assert state.attributes[ATTR_TEMPERATURE] == 20 + assert state.state == HVAC_MODE_HEAT + assert hass.states.get(heater_switch).state == STATE_ON + + async def test_restore_state_uncoherence_case(hass): """ Test restore from a strange state. From bc0eb9bf32f6493847e291e2506f99e0a0d2ce24 Mon Sep 17 00:00:00 2001 From: sycx2 Date: Fri, 19 Mar 2021 15:54:07 +0100 Subject: [PATCH 1392/1818] Improve uvc test camera (#41438) * Improve uvc test camera * Clean setup full config * Clean setup partial config * Set more camera defaults * Clean setup partial config v31x * Clean setup incomplete config * Clean setup nvr errors during indexing * Clean setup nvr errors during initialization * Clean properties * Fix motion recording mode properties * Clean stream * Clean login * Clean login v31x * Clean login tries both addres and caches * Clean login fails both properly * Remove not needed test * Clean camera image error * Test snapshot login retry * Clean up * Test enable and disable motion detection * Times must be UTC Co-authored-by: Martin Hjelmare --- homeassistant/components/uvc/camera.py | 3 +- tests/components/uvc/test_camera.py | 959 +++++++++++++++---------- 2 files changed, 592 insertions(+), 370 deletions(-) diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 94181de37c4bca..367a3915e6dec3 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -13,6 +13,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_SSL from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.util.dt import utc_from_timestamp _LOGGER = logging.getLogger(__name__) @@ -259,5 +260,5 @@ def update(self): def timestamp_ms_to_date(epoch_ms: int) -> datetime | None: """Convert millisecond timestamp to datetime.""" if epoch_ms: - return datetime.fromtimestamp(epoch_ms / 1000) + return utc_from_timestamp(epoch_ms / 1000) return None diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 24d73272f18345..75d0d40c9f3e03 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -1,381 +1,602 @@ """The tests for UVC camera module.""" -from datetime import datetime -import socket -import unittest -from unittest import mock +from datetime import datetime, timedelta, timezone +from unittest.mock import call, patch import pytest import requests -from uvcclient import camera, nvr - -from homeassistant.components.camera import SUPPORT_STREAM -from homeassistant.components.uvc import camera as uvc -from homeassistant.exceptions import PlatformNotReady -from homeassistant.setup import setup_component - -from tests.common import get_test_home_assistant - - -class TestUVCSetup(unittest.TestCase): - """Test the UVC camera platform.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.hass.stop) - - @mock.patch("uvcclient.nvr.UVCRemote") - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_full_config(self, mock_uvc, mock_remote): - """Test the setup with full configuration.""" - config = { - "platform": "uvc", - "nvr": "foo", - "password": "bar", - "port": 123, - "key": "secret", - } +from uvcclient import camera as camera, nvr + +from homeassistant.components.camera import ( + DEFAULT_CONTENT_TYPE, + SERVICE_DISABLE_MOTION, + SERVICE_ENABLE_MOTION, + STATE_RECORDING, + SUPPORT_STREAM, + async_get_image, + async_get_stream_source, +) +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry +from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed + + +@pytest.fixture(name="mock_remote") +def mock_remote_fixture(camera_info): + """Mock the nvr.UVCRemote class.""" + with patch("homeassistant.components.uvc.camera.nvr.UVCRemote") as mock_remote: + + def setup(host, port, apikey, ssl=False): + """Set instance attributes.""" + mock_remote.return_value._host = host + mock_remote.return_value._port = port + mock_remote.return_value._apikey = apikey + mock_remote.return_value._ssl = ssl + return mock_remote.return_value + + mock_remote.side_effect = setup + mock_remote.return_value.get_camera.return_value = camera_info mock_cameras = [ {"uuid": "one", "name": "Front", "id": "id1"}, {"uuid": "two", "name": "Back", "id": "id2"}, - {"uuid": "three", "name": "Old AirCam", "id": "id3"}, ] - - def mock_get_camera(uuid): - """Create a mock camera.""" - if uuid == "id3": - return {"model": "airCam"} - return {"model": "UVC"} - mock_remote.return_value.index.return_value = mock_cameras - mock_remote.return_value.get_camera.side_effect = mock_get_camera mock_remote.return_value.server_version = (3, 2, 0) + yield mock_remote + + +@pytest.fixture(name="camera_info") +def camera_info_fixture(): + """Mock the camera info of a camera.""" + return { + "model": "UVC", + "recordingSettings": { + "fullTimeRecordEnabled": True, + "motionRecordEnabled": False, + }, + "host": "host-a", + "internalHost": "host-b", + "username": "admin", + "lastRecordingStartTime": 1610070992367, + "channels": [ + { + "id": "0", + "width": 1920, + "height": 1080, + "fps": 25, + "bitrate": 6000000, + "isRtspEnabled": True, + "rtspUris": [ + "rtsp://host-a:7447/uuid_rtspchannel_0", + "rtsp://foo:7447/uuid_rtspchannel_0", + ], + }, + { + "id": "1", + "width": 1024, + "height": 576, + "fps": 15, + "bitrate": 1200000, + "isRtspEnabled": False, + "rtspUris": [ + "rtsp://host-a:7447/uuid_rtspchannel_1", + "rtsp://foo:7447/uuid_rtspchannel_1", + ], + }, + ], + } - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert mock_remote.call_count == 1 - assert mock_remote.call_args == mock.call("foo", 123, "secret", ssl=False) - mock_uvc.assert_has_calls( - [ - mock.call(mock_remote.return_value, "id1", "Front", "bar"), - mock.call(mock_remote.return_value, "id2", "Back", "bar"), - ] - ) - - @mock.patch("uvcclient.nvr.UVCRemote") - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_partial_config(self, mock_uvc, mock_remote): - """Test the setup with partial configuration.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_cameras = [ - {"uuid": "one", "name": "Front", "id": "id1"}, - {"uuid": "two", "name": "Back", "id": "id2"}, - ] - mock_remote.return_value.index.return_value = mock_cameras - mock_remote.return_value.get_camera.return_value = {"model": "UVC"} - mock_remote.return_value.server_version = (3, 2, 0) - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert mock_remote.call_count == 1 - assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False) - mock_uvc.assert_has_calls( - [ - mock.call(mock_remote.return_value, "id1", "Front", "ubnt"), - mock.call(mock_remote.return_value, "id2", "Back", "ubnt"), - ] - ) - - @mock.patch("uvcclient.nvr.UVCRemote") - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_partial_config_v31x(self, mock_uvc, mock_remote): - """Test the setup with a v3.1.x server.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_cameras = [ - {"uuid": "one", "name": "Front", "id": "id1"}, - {"uuid": "two", "name": "Back", "id": "id2"}, - ] - mock_remote.return_value.index.return_value = mock_cameras - mock_remote.return_value.get_camera.return_value = {"model": "UVC"} - mock_remote.return_value.server_version = (3, 1, 3) - - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert mock_remote.call_count == 1 - assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False) - mock_uvc.assert_has_calls( - [ - mock.call(mock_remote.return_value, "one", "Front", "ubnt"), - mock.call(mock_remote.return_value, "two", "Back", "ubnt"), - ] - ) - - @mock.patch.object(uvc, "UnifiVideoCamera") - def test_setup_incomplete_config(self, mock_uvc): - """Test the setup with incomplete configuration.""" - assert setup_component(self.hass, "camera", {"platform": "uvc", "nvr": "foo"}) - self.hass.block_till_done() - - assert not mock_uvc.called - assert setup_component( - self.hass, "camera", {"platform": "uvc", "key": "secret"} - ) - self.hass.block_till_done() - - assert not mock_uvc.called - assert setup_component( - self.hass, "camera", {"platform": "uvc", "port": "invalid"} - ) - self.hass.block_till_done() - - assert not mock_uvc.called - - @mock.patch.object(uvc, "UnifiVideoCamera") - @mock.patch("uvcclient.nvr.UVCRemote") - def setup_nvr_errors_during_indexing(self, error, mock_remote, mock_uvc): - """Set up test for NVR errors during indexing.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_remote.return_value.index.side_effect = error - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert not mock_uvc.called - - def test_setup_nvr_error_during_indexing_notauthorized(self): - """Test for error: nvr.NotAuthorized.""" - self.setup_nvr_errors_during_indexing(nvr.NotAuthorized) - - def test_setup_nvr_error_during_indexing_nvrerror(self): - """Test for error: nvr.NvrError.""" - self.setup_nvr_errors_during_indexing(nvr.NvrError) - pytest.raises(PlatformNotReady) - - def test_setup_nvr_error_during_indexing_connectionerror(self): - """Test for error: requests.exceptions.ConnectionError.""" - self.setup_nvr_errors_during_indexing(requests.exceptions.ConnectionError) - pytest.raises(PlatformNotReady) - - @mock.patch.object(uvc, "UnifiVideoCamera") - @mock.patch("uvcclient.nvr.UVCRemote.__init__") - def setup_nvr_errors_during_initialization(self, error, mock_remote, mock_uvc): - """Set up test for NVR errors during initialization.""" - config = {"platform": "uvc", "nvr": "foo", "key": "secret"} - mock_remote.return_value = None - mock_remote.side_effect = error - assert setup_component(self.hass, "camera", {"camera": config}) - self.hass.block_till_done() - - assert not mock_remote.index.called - assert not mock_uvc.called - - def test_setup_nvr_error_during_initialization_notauthorized(self): - """Test for error: nvr.NotAuthorized.""" - self.setup_nvr_errors_during_initialization(nvr.NotAuthorized) - - def test_setup_nvr_error_during_initialization_nvrerror(self): - """Test for error: nvr.NvrError.""" - self.setup_nvr_errors_during_initialization(nvr.NvrError) - pytest.raises(PlatformNotReady) - - def test_setup_nvr_error_during_initialization_connectionerror(self): - """Test for error: requests.exceptions.ConnectionError.""" - self.setup_nvr_errors_during_initialization(requests.exceptions.ConnectionError) - pytest.raises(PlatformNotReady) - - -class TestUVC(unittest.TestCase): - """Test class for UVC.""" - - def setup_method(self, method): - """Set up the mock camera.""" - self.nvr = mock.MagicMock() - self.uuid = "uuid" - self.name = "name" - self.password = "seekret" - self.uvc = uvc.UnifiVideoCamera(self.nvr, self.uuid, self.name, self.password) - self.nvr.get_camera.return_value = { - "model": "UVC Fake", - "uuid": "06e3ff29-8048-31c2-8574-0852d1bd0e03", - "recordingSettings": { - "fullTimeRecordEnabled": True, - "motionRecordEnabled": False, - }, - "host": "host-a", - "internalHost": "host-b", - "username": "admin", - "lastRecordingStartTime": 1610070992367, - "channels": [ - { - "id": "0", - "width": 1920, - "height": 1080, - "fps": 25, - "bitrate": 6000000, - "isRtspEnabled": True, - "rtspUris": [ - "rtsp://host-a:7447/uuid_rtspchannel_0", - "rtsp://foo:7447/uuid_rtspchannel_0", - ], - }, - { - "id": "1", - "width": 1024, - "height": 576, - "fps": 15, - "bitrate": 1200000, - "isRtspEnabled": False, - "rtspUris": [ - "rtsp://host-a:7447/uuid_rtspchannel_1", - "rtsp://foo:7447/uuid_rtspchannel_1", - ], - }, - ], - } - self.nvr.server_version = (3, 2, 0) - self.uvc.update() - - def test_properties(self): - """Test the properties.""" - assert self.name == self.uvc.name - assert self.uvc.is_recording - assert "Ubiquiti" == self.uvc.brand - assert "UVC Fake" == self.uvc.model - assert SUPPORT_STREAM == self.uvc.supported_features - assert "uuid" == self.uvc.unique_id - - def test_motion_recording_mode_properties(self): - """Test the properties.""" - self.nvr.get_camera.return_value["recordingSettings"][ - "fullTimeRecordEnabled" - ] = False - self.nvr.get_camera.return_value["recordingSettings"][ - "motionRecordEnabled" - ] = True - assert not self.uvc.is_recording - assert ( - datetime(2021, 1, 8, 1, 56, 32, 367000) - == self.uvc.extra_state_attributes["last_recording_start_time"] - ) - - self.nvr.get_camera.return_value["recordingIndicator"] = "DISABLED" - assert not self.uvc.is_recording - - self.nvr.get_camera.return_value["recordingIndicator"] = "MOTION_INPROGRESS" - assert self.uvc.is_recording - - self.nvr.get_camera.return_value["recordingIndicator"] = "MOTION_FINISHED" - assert self.uvc.is_recording - - def test_stream(self): - """Test the RTSP stream URI.""" - stream_source = yield from self.uvc.stream_source() - assert stream_source == "rtsp://foo:7447/uuid_rtspchannel_0" - - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClientV320") - def test_login(self, mock_camera, mock_store): - """Test the login.""" - self.uvc._login() - assert mock_camera.call_count == 1 - assert mock_camera.call_args == mock.call("host-a", "admin", "seekret") - assert mock_camera.return_value.login.call_count == 1 - assert mock_camera.return_value.login.call_args == mock.call() - - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClient") - def test_login_v31x(self, mock_camera, mock_store): - """Test login with v3.1.x server.""" - self.nvr.server_version = (3, 1, 3) - self.uvc._login() - assert mock_camera.call_count == 1 - assert mock_camera.call_args == mock.call("host-a", "admin", "seekret") - assert mock_camera.return_value.login.call_count == 1 - assert mock_camera.return_value.login.call_args == mock.call() - - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClientV320") - def test_login_tries_both_addrs_and_caches(self, mock_camera, mock_store): - """Test the login tries.""" - responses = [0] - - def mock_login(*a): - """Mock login.""" - try: - responses.pop(0) - raise OSError - except IndexError: - pass - - mock_store.return_value.get_camera_password.return_value = None - mock_camera.return_value.login.side_effect = mock_login - self.uvc._login() - assert 2 == mock_camera.call_count - assert "host-b" == self.uvc._connect_addr - - mock_camera.reset_mock() - self.uvc._login() - assert mock_camera.call_count == 1 - assert mock_camera.call_args == mock.call("host-b", "admin", "seekret") - assert mock_camera.return_value.login.call_count == 1 - assert mock_camera.return_value.login.call_args == mock.call() - - @mock.patch("uvcclient.store.get_info_store") - @mock.patch("uvcclient.camera.UVCCameraClientV320") - def test_login_fails_both_properly(self, mock_camera, mock_store): - """Test if login fails properly.""" - mock_camera.return_value.login.side_effect = socket.error - assert self.uvc._login() is None - assert self.uvc._connect_addr is None - - def test_camera_image_tries_login_bails_on_failure(self): - """Test retrieving failure.""" - with mock.patch.object(self.uvc, "_login") as mock_login: - mock_login.return_value = False - assert self.uvc.camera_image() is None - assert mock_login.call_count == 1 - assert mock_login.call_args == mock.call() - - def test_camera_image_logged_in(self): - """Test the login state.""" - self.uvc._camera = mock.MagicMock() - assert self.uvc._camera.get_snapshot.return_value == self.uvc.camera_image() - - def test_camera_image_error(self): - """Test the camera image error.""" - self.uvc._camera = mock.MagicMock() - self.uvc._camera.get_snapshot.side_effect = camera.CameraConnectError - assert self.uvc.camera_image() is None - - def test_camera_image_reauths(self): - """Test the re-authentication.""" - responses = [0] - - def mock_snapshot(): - """Mock snapshot.""" - try: - responses.pop() - raise camera.CameraAuthError() - except IndexError: - pass - return "image" - - self.uvc._camera = mock.MagicMock() - self.uvc._camera.get_snapshot.side_effect = mock_snapshot - with mock.patch.object(self.uvc, "_login") as mock_login: - assert "image" == self.uvc.camera_image() - assert mock_login.call_count == 1 - assert mock_login.call_args == mock.call() - assert [] == responses - - def test_camera_image_reauths_only_once(self): - """Test if the re-authentication only happens once.""" - self.uvc._camera = mock.MagicMock() - self.uvc._camera.get_snapshot.side_effect = camera.CameraAuthError - with mock.patch.object(self.uvc, "_login") as mock_login: - with pytest.raises(camera.CameraAuthError): - self.uvc.camera_image() - assert mock_login.call_count == 1 - assert mock_login.call_args == mock.call() +@pytest.fixture(name="camera_v320") +def camera_v320_fixture(): + """Mock the v320 camera.""" + with patch( + "homeassistant.components.uvc.camera.uvc_camera.UVCCameraClientV320" + ) as camera: + camera.return_value.get_snapshot.return_value = "test_image" + yield camera + + +@pytest.fixture(name="camera_v313") +def camera_v313_fixture(): + """Mock the v320 camera.""" + with patch( + "homeassistant.components.uvc.camera.uvc_camera.UVCCameraClient" + ) as camera: + camera.return_value.get_snapshot.return_value = "test_image" + yield camera + + +async def test_setup_full_config(hass, mock_remote, camera_info): + """Test the setup with full configuration.""" + config = { + "platform": "uvc", + "nvr": "foo", + "password": "bar", + "port": 123, + "key": "secret", + } + + def mock_get_camera(uuid): + """Create a mock camera.""" + if uuid == "id3": + camera_info["model"] = "airCam" + + return camera_info + + mock_remote.return_value.index.return_value.append( + {"uuid": "three", "name": "Old AirCam", "id": "id3"} + ) + mock_remote.return_value.get_camera.side_effect = mock_get_camera + + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert mock_remote.call_count == 1 + assert mock_remote.call_args == call("foo", 123, "secret", ssl=False) + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + + state = hass.states.get("camera.back") + + assert state + assert state.name == "Back" + + entity_registry = async_get_entity_registry(hass) + entity_entry = entity_registry.async_get("camera.front") + + assert entity_entry.unique_id == "id1" + + entity_entry = entity_registry.async_get("camera.back") + + assert entity_entry.unique_id == "id2" + + +async def test_setup_partial_config(hass, mock_remote): + """Test the setup with partial configuration.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert mock_remote.call_count == 1 + assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False) + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + + state = hass.states.get("camera.back") + + assert state + assert state.name == "Back" + + entity_registry = async_get_entity_registry(hass) + entity_entry = entity_registry.async_get("camera.front") + + assert entity_entry.unique_id == "id1" + + entity_entry = entity_registry.async_get("camera.back") + + assert entity_entry.unique_id == "id2" + + +async def test_setup_partial_config_v31x(hass, mock_remote): + """Test the setup with a v3.1.x server.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + mock_remote.return_value.server_version = (3, 1, 3) + + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert mock_remote.call_count == 1 + assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False) + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + + state = hass.states.get("camera.back") + + assert state + assert state.name == "Back" + + entity_registry = async_get_entity_registry(hass) + entity_entry = entity_registry.async_get("camera.front") + + assert entity_entry.unique_id == "one" + + entity_entry = entity_registry.async_get("camera.back") + + assert entity_entry.unique_id == "two" + + +@pytest.mark.parametrize( + "config", + [ + {"platform": "uvc", "nvr": "foo"}, + {"platform": "uvc", "key": "secret"}, + {"platform": "uvc", "nvr": "foo", "key": "secret", "port": "invalid"}, + ], +) +async def test_setup_incomplete_config(hass, mock_remote, config): + """Test the setup with incomplete or invalid configuration.""" + assert await async_setup_component(hass, "camera", config) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert not camera_states + + +@pytest.mark.parametrize( + "error, ready_states", + [ + (nvr.NotAuthorized, 0), + (nvr.NvrError, 2), + (requests.exceptions.ConnectionError, 2), + ], +) +async def test_setup_nvr_errors_during_indexing(hass, mock_remote, error, ready_states): + """Set up test for NVR errors during indexing.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + now = utcnow() + mock_remote.return_value.index.side_effect = error + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert not camera_states + + # resolve the error + mock_remote.return_value.index.side_effect = None + + async_fire_time_changed(hass, now + timedelta(seconds=31)) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == ready_states + + +@pytest.mark.parametrize( + "error, ready_states", + [ + (nvr.NotAuthorized, 0), + (nvr.NvrError, 2), + (requests.exceptions.ConnectionError, 2), + ], +) +async def test_setup_nvr_errors_during_initialization( + hass, mock_remote, error, ready_states +): + """Set up test for NVR errors during initialization.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + now = utcnow() + mock_remote.side_effect = error + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + assert not mock_remote.index.called + + camera_states = hass.states.async_all("camera") + + assert not camera_states + + # resolve the error + mock_remote.side_effect = None + + async_fire_time_changed(hass, now + timedelta(seconds=31)) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == ready_states + + +async def test_properties(hass, mock_remote): + """Test the properties.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + camera_states = hass.states.async_all("camera") + + assert len(camera_states) == 2 + + state = hass.states.get("camera.front") + + assert state + assert state.name == "Front" + assert state.state == STATE_RECORDING + assert state.attributes["brand"] == "Ubiquiti" + assert state.attributes["model_name"] == "UVC" + assert state.attributes["supported_features"] == SUPPORT_STREAM + + +async def test_motion_recording_mode_properties(hass, mock_remote): + """Test the properties.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + now = utcnow() + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state == STATE_RECORDING + + mock_remote.return_value.get_camera.return_value["recordingSettings"][ + "fullTimeRecordEnabled" + ] = False + mock_remote.return_value.get_camera.return_value["recordingSettings"][ + "motionRecordEnabled" + ] = True + + async_fire_time_changed(hass, now + timedelta(seconds=31)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state != STATE_RECORDING + assert state.attributes["last_recording_start_time"] == datetime( + 2021, 1, 8, 1, 56, 32, 367000, tzinfo=timezone.utc + ) + + mock_remote.return_value.get_camera.return_value["recordingIndicator"] = "DISABLED" + + async_fire_time_changed(hass, now + timedelta(seconds=61)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state != STATE_RECORDING + + mock_remote.return_value.get_camera.return_value[ + "recordingIndicator" + ] = "MOTION_INPROGRESS" + + async_fire_time_changed(hass, now + timedelta(seconds=91)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state == STATE_RECORDING + + mock_remote.return_value.get_camera.return_value[ + "recordingIndicator" + ] = "MOTION_FINISHED" + + async_fire_time_changed(hass, now + timedelta(seconds=121)) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.state == STATE_RECORDING + + +async def test_stream(hass, mock_remote): + """Test the RTSP stream URI.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + stream_source = await async_get_stream_source(hass, "camera.front") + + assert stream_source == "rtsp://foo:7447/uuid_rtspchannel_0" + + +async def test_login(hass, mock_remote, camera_v320): + """Test the login.""" + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + image = await async_get_image(hass, "camera.front") + + assert camera_v320.call_count == 1 + assert camera_v320.call_args == call("host-a", "admin", "ubnt") + assert camera_v320.return_value.login.call_count == 1 + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + +async def test_login_v31x(hass, mock_remote, camera_v313): + """Test login with v3.1.x server.""" + mock_remote.return_value.server_version = (3, 1, 3) + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + image = await async_get_image(hass, "camera.front") + + assert camera_v313.call_count == 1 + assert camera_v313.call_args == call("host-a", "admin", "ubnt") + assert camera_v313.return_value.login.call_count == 1 + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + +@pytest.mark.parametrize( + "error", [OSError, camera.CameraConnectError, camera.CameraAuthError] +) +async def test_login_tries_both_addrs_and_caches(hass, mock_remote, camera_v320, error): + """Test the login tries.""" + responses = [0] + + def mock_login(*a): + """Mock login.""" + try: + responses.pop(0) + raise error + except IndexError: + pass + + snapshots = [0] + + def mock_snapshots(*a): + """Mock get snapshots.""" + try: + snapshots.pop(0) + raise camera.CameraAuthError() + except IndexError: + pass + return "test_image" + + camera_v320.return_value.login.side_effect = mock_login + + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + image = await async_get_image(hass, "camera.front") + + assert camera_v320.call_count == 2 + assert camera_v320.call_args == call("host-b", "admin", "ubnt") + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + camera_v320.reset_mock() + camera_v320.return_value.get_snapshot.side_effect = mock_snapshots + + image = await async_get_image(hass, "camera.front") + + assert camera_v320.call_count == 1 + assert camera_v320.call_args == call("host-b", "admin", "ubnt") + assert camera_v320.return_value.login.call_count == 1 + assert image.content_type == DEFAULT_CONTENT_TYPE + assert image.content == "test_image" + + +async def test_login_fails_both_properly(hass, mock_remote, camera_v320): + """Test if login fails properly.""" + camera_v320.return_value.login.side_effect = OSError + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + with pytest.raises(HomeAssistantError): + await async_get_image(hass, "camera.front") + + assert camera_v320.return_value.get_snapshot.call_count == 0 + + +@pytest.mark.parametrize( + "source_error, raised_error, snapshot_calls", + [ + (camera.CameraConnectError, HomeAssistantError, 1), + (camera.CameraAuthError, camera.CameraAuthError, 2), + ], +) +async def test_camera_image_error( + hass, mock_remote, camera_v320, source_error, raised_error, snapshot_calls +): + """Test the camera image error.""" + camera_v320.return_value.get_snapshot.side_effect = source_error + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + with pytest.raises(raised_error): + await async_get_image(hass, "camera.front") + + assert camera_v320.return_value.get_snapshot.call_count == snapshot_calls + + +async def test_enable_disable_motion_detection(hass, mock_remote, camera_info): + """Test enable and disable motion detection.""" + + def set_recordmode(uuid, mode): + """Set record mode.""" + motion_record_enabled = mode == "motion" + camera_info["recordingSettings"]["motionRecordEnabled"] = motion_record_enabled + + mock_remote.return_value.set_recordmode.side_effect = set_recordmode + config = {"platform": "uvc", "nvr": "foo", "key": "secret"} + assert await async_setup_component(hass, "camera", {"camera": config}) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert "motion_detection" not in state.attributes + + await hass.services.async_call( + "camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.attributes["motion_detection"] + + await hass.services.async_call( + "camera", SERVICE_DISABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert "motion_detection" not in state.attributes + + mock_remote.return_value.set_recordmode.side_effect = nvr.NvrError + + await hass.services.async_call( + "camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert "motion_detection" not in state.attributes + + mock_remote.return_value.set_recordmode.side_effect = set_recordmode + + await hass.services.async_call( + "camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.attributes["motion_detection"] + + mock_remote.return_value.set_recordmode.side_effect = nvr.NvrError + + await hass.services.async_call( + "camera", SERVICE_DISABLE_MOTION, {"entity_id": "camera.front"}, True + ) + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + + assert state + assert state.attributes["motion_detection"] From 24e067782a9c28fe131fd4c896127178fb13378e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 19 Mar 2021 16:51:44 +0100 Subject: [PATCH 1393/1818] Improve sensor coverage by verifying daylight sensor attributes (#48090) --- tests/components/deconz/test_sensor.py | 68 ++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index e9a35337aef837..a4d4e0063664e6 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -1,10 +1,12 @@ """deCONZ sensor platform tests.""" +from datetime import timedelta from unittest.mock import patch 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 +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ( ATTR_DEVICE_CLASS, DEVICE_CLASS_BATTERY, @@ -13,9 +15,13 @@ STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.common import async_fire_time_changed + async def test_no_sensors(hass, aioclient_mock): """Test that no sensors in deconz results in no sensor entities.""" @@ -56,27 +62,20 @@ async def test_sensors(hass, aioclient_mock, mock_deconz_websocket): "uniqueid": "00:00:00:00:00:00:00:03-00", }, "5": { - "name": "Daylight sensor", - "type": "Daylight", - "state": {"daylight": True, "status": 130}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, - "6": { "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", }, - "7": { + "6": { "name": "Consumption sensor", "type": "ZHAConsumption", "state": {"consumption": 2, "power": 6}, "config": {"reachable": True}, "uniqueid": "00:00:00:00:00:00:00:06-00", }, - "8": { + "7": { "id": "CLIP light sensor id", "name": "CLIP light level sensor", "type": "CLIPLightLevel", @@ -442,6 +441,57 @@ async def test_air_quality_sensor(hass, aioclient_mock): assert hass.states.get("sensor.air_quality").state == "poor" +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 = { From b03c97cdd09f329c6860f295d0f110f19d52a37a Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Fri, 19 Mar 2021 19:27:56 +0000 Subject: [PATCH 1394/1818] Make Vera should_poll static rather than dynamic (#47854) * Make should_poll static. * Address review comments. * Fix black error. --- CODEOWNERS | 2 +- homeassistant/components/vera/__init__.py | 4 +++- homeassistant/components/vera/manifest.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 1b0f2747dee204..2afbc288b0f9ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -510,7 +510,7 @@ homeassistant/components/usgs_earthquakes_feed/* @exxamalte homeassistant/components/utility_meter/* @dgomes homeassistant/components/velbus/* @Cereal2nd @brefra homeassistant/components/velux/* @Julius2342 -homeassistant/components/vera/* @vangorra +homeassistant/components/vera/* @pavoni homeassistant/components/verisure/* @frenck homeassistant/components/versasense/* @flamm3blemuff1n homeassistant/components/version/* @fabaff @ludeeus diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 929e4424d804e9..e544ddb925f147 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -236,7 +236,9 @@ def _update_callback(self, _device: DeviceType) -> None: def update(self): """Force a refresh from the device if the device is unavailable.""" - if not self.available: + refresh_needed = self.vera_device.should_poll or not self.available + _LOGGER.debug("%s: update called (refresh=%s)", self._name, refresh_needed) + if refresh_needed: self.vera_device.refresh() @property diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index 1f180b39750071..76d6bda5c7b300 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vera", "requirements": ["pyvera==0.3.13"], - "codeowners": ["@vangorra"] + "codeowners": ["@pavoni"] } From 16a4f05e27f583fe5d05c66516487cdc2765b385 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 20:55:08 +0100 Subject: [PATCH 1395/1818] Type check KNX integration fan (#48056) --- homeassistant/components/knx/fan.py | 33 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index 2d9f48fe8049c1..7f68c2a7d51634 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -2,12 +2,17 @@ from __future__ import annotations import math -from typing import Any +from typing import Any, Callable, Iterable from xknx.devices import Fan as XknxFan -from xknx.devices.fan import FanSpeedMode from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from homeassistant.util.percentage import ( int_states_in_range, percentage_to_ranged_value, @@ -20,7 +25,12 @@ DEFAULT_PERCENTAGE = 50 -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up fans for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -32,18 +42,19 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXFan(KnxEntity, FanEntity): """Representation of a KNX fan.""" - def __init__(self, device: XknxFan): + def __init__(self, device: XknxFan) -> None: """Initialize of KNX fan.""" + self._device: XknxFan super().__init__(device) - if self._device.mode == FanSpeedMode.STEP: + self._step_range: tuple[int, int] | None = None + if device.max_step: + # FanSpeedMode.STEP: self._step_range = (1, device.max_step) - else: - self._step_range = None async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" - if self._device.mode == FanSpeedMode.STEP: + if self._step_range: step = math.ceil(percentage_to_ranged_value(self._step_range, percentage)) await self._device.set_speed(step) else: @@ -65,7 +76,7 @@ def percentage(self) -> int | None: if self._device.current_speed is None: return None - if self._device.mode == FanSpeedMode.STEP: + if self._step_range: return ranged_value_to_percentage( self._step_range, self._device.current_speed ) @@ -83,7 +94,7 @@ async def async_turn_on( speed: str | None = None, percentage: int | None = None, preset_mode: str | None = None, - **kwargs, + **kwargs: Any, ) -> None: """Turn on the fan.""" if percentage is None: @@ -100,6 +111,6 @@ async def async_oscillate(self, oscillating: bool) -> None: await self._device.set_oscillation(oscillating) @property - def oscillating(self): + def oscillating(self) -> bool | None: """Return whether or not the fan is currently oscillating.""" return self._device.current_oscillation From 70bebc51f2c8366efd7c38cc50d7ab32261630aa Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 19 Mar 2021 22:25:20 +0100 Subject: [PATCH 1396/1818] Type check KNX integration cover (#48046) --- homeassistant/components/knx/cover.py | 76 ++++++++++++++++----------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 4c08612926b04e..a0f35a30636ad5 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -1,5 +1,10 @@ """Support for KNX/IP covers.""" -from xknx.devices import Cover as XknxCover +from __future__ import annotations + +from datetime import datetime +from typing import Any, Callable, Iterable + +from xknx.devices import Cover as XknxCover, Device as XknxDevice from homeassistant.components.cover import ( ATTR_POSITION, @@ -17,13 +22,24 @@ CoverEntity, ) from homeassistant.core import callback +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_utc_time_change +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import DOMAIN from .knx_entity import KnxEntity -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up cover(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -37,19 +53,20 @@ class KNXCover(KnxEntity, CoverEntity): def __init__(self, device: XknxCover): """Initialize the cover.""" + self._device: XknxCover super().__init__(device) - self._unsubscribe_auto_updater = None + self._unsubscribe_auto_updater: Callable[[], None] | None = None @callback - async def after_update_callback(self, device): + async def after_update_callback(self, device: XknxDevice) -> None: """Call after device was updated.""" self.async_write_ha_state() if self._device.is_traveling(): self.start_auto_updater() @property - def device_class(self): + def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" if self._device.device_class in DEVICE_CLASSES: return self._device.device_class @@ -58,7 +75,7 @@ def device_class(self): return None @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION if self._device.supports_stop: @@ -73,19 +90,17 @@ def supported_features(self): return supported_features @property - def current_cover_position(self): + def current_cover_position(self) -> int | None: """Return the current position of the cover. None is unknown, 0 is closed, 100 is fully open. """ # In KNX 0 is open, 100 is closed. - try: - return 100 - self._device.current_position() - except TypeError: - return None + pos = self._device.current_position() + return 100 - pos if pos is not None else None @property - def is_closed(self): + def is_closed(self) -> bool | None: """Return if the cover is closed.""" # state shall be "unknown" when xknx travelcalculator is not initialized if self._device.current_position() is None: @@ -93,79 +108,76 @@ def is_closed(self): return self._device.is_closed() @property - def is_opening(self): + def is_opening(self) -> bool: """Return if the cover is opening or not.""" return self._device.is_opening() @property - def is_closing(self): + def is_closing(self) -> bool: """Return if the cover is closing or not.""" return self._device.is_closing() - async def async_close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" await self._device.set_down() - async def async_open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" await self._device.set_up() - 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.""" knx_position = 100 - kwargs[ATTR_POSITION] await self._device.set_position(knx_position) - async def async_stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" await self._device.stop() self.stop_auto_updater() @property - def current_cover_tilt_position(self): + def current_cover_tilt_position(self) -> int | None: """Return current tilt position of cover.""" if not self._device.supports_angle: return None - try: - return 100 - self._device.current_angle() - except TypeError: - return None + ang = self._device.current_angle() + return 100 - ang if ang is not None else None - 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.""" knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION] await self._device.set_angle(knx_tilt_position) - async def async_open_cover_tilt(self, **kwargs): + async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Open the cover tilt.""" await self._device.set_short_up() - async def async_close_cover_tilt(self, **kwargs): + async def async_close_cover_tilt(self, **kwargs: Any) -> None: """Close the cover tilt.""" await self._device.set_short_down() - async def async_stop_cover_tilt(self, **kwargs): + async def async_stop_cover_tilt(self, **kwargs: Any) -> None: """Stop the cover tilt.""" await self._device.stop() self.stop_auto_updater() - def start_auto_updater(self): + def start_auto_updater(self) -> None: """Start the autoupdater to update Home Assistant while cover is moving.""" if self._unsubscribe_auto_updater is None: self._unsubscribe_auto_updater = async_track_utc_time_change( self.hass, self.auto_updater_hook ) - def stop_auto_updater(self): + def stop_auto_updater(self) -> None: """Stop the autoupdater.""" if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None @callback - def auto_updater_hook(self, now): + def auto_updater_hook(self, now: datetime) -> None: """Call for the autoupdater.""" self.async_write_ha_state() if self._device.position_reached(): + self.hass.async_create_task(self._device.auto_stop_if_necessary()) self.stop_auto_updater() - - self.hass.add_job(self._device.auto_stop_if_necessary()) From e47d8da3b4a1b6bcd7dff11a8d5c1b603bc67a41 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 19 Mar 2021 23:16:45 +0100 Subject: [PATCH 1397/1818] Remove defunct test from percentage util (#48125) --- tests/util/test_percentage.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/util/test_percentage.py b/tests/util/test_percentage.py index 4ad28f8567c7cb..31420d3c07666d 100644 --- a/tests/util/test_percentage.py +++ b/tests/util/test_percentage.py @@ -28,15 +28,6 @@ LARGE_ORDERED_LIST = [SPEED_1, SPEED_2, SPEED_3, SPEED_4, SPEED_5, SPEED_6, SPEED_7] -async def test_ordered_list_percentage_round_trip(): - """Test we can round trip.""" - for ordered_list in (SMALL_ORDERED_LIST, LARGE_ORDERED_LIST): - for i in range(1, 100): - ordered_list_item_to_percentage( - ordered_list, percentage_to_ordered_list_item(ordered_list, i) - ) == i - - async def test_ordered_list_item_to_percentage(): """Test percentage of an item in an ordered list.""" From 098c53e8b5cddbea1be05c54de90db1cb9cd8539 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 20 Mar 2021 00:04:10 +0000 Subject: [PATCH 1398/1818] [ci skip] Translation update --- .../components/airly/translations/nl.json | 2 +- .../components/almond/translations/nl.json | 2 +- .../components/august/translations/es.json | 11 +++++ .../components/august/translations/fr.json | 11 +++++ .../components/august/translations/it.json | 16 +++++++ .../components/brother/translations/nl.json | 2 +- .../components/canary/translations/nl.json | 1 + .../components/denonavr/translations/nl.json | 1 + .../device_tracker/translations/it.json | 2 +- .../components/eafm/translations/nl.json | 7 ++- .../garmin_connect/translations/nl.json | 4 +- .../components/gdacs/translations/nl.json | 2 +- .../components/gios/translations/nl.json | 2 +- .../components/goalzero/translations/nl.json | 3 +- .../components/hive/translations/es.json | 44 +++++++++++++++++++ .../homekit_controller/translations/nl.json | 6 +++ .../huawei_lte/translations/nl.json | 2 +- .../components/insteon/translations/nl.json | 9 ++++ .../components/kmtronic/translations/es.json | 9 ++++ .../components/konnected/translations/nl.json | 1 + .../meteo_france/translations/nl.json | 4 +- .../components/mikrotik/translations/nl.json | 4 +- .../mobile_app/translations/es.json | 3 +- .../components/neato/translations/nl.json | 4 +- .../components/netatmo/translations/nl.json | 2 +- .../nightscout/translations/nl.json | 5 ++- .../opentherm_gw/translations/ca.json | 4 +- .../opentherm_gw/translations/es.json | 4 +- .../opentherm_gw/translations/et.json | 4 +- .../opentherm_gw/translations/fr.json | 4 +- .../opentherm_gw/translations/it.json | 4 +- .../opentherm_gw/translations/ko.json | 4 +- .../opentherm_gw/translations/no.json | 4 +- .../opentherm_gw/translations/ru.json | 4 +- .../opentherm_gw/translations/zh-Hant.json | 4 +- .../ovo_energy/translations/nl.json | 1 + .../philips_js/translations/es.json | 4 ++ .../philips_js/translations/fr.json | 4 ++ .../philips_js/translations/it.json | 7 +++ .../philips_js/translations/ko.json | 2 +- .../components/plugwise/translations/nl.json | 3 +- .../components/rfxtrx/translations/nl.json | 4 ++ .../components/rpi_power/translations/nl.json | 1 + .../components/samsungtv/translations/nl.json | 6 +-- .../screenlogic/translations/es.json | 29 ++++++++++++ .../screenlogic/translations/fr.json | 33 ++++++++++++++ .../screenlogic/translations/it.json | 39 ++++++++++++++++ .../components/sensor/translations/es.json | 4 ++ .../components/sensor/translations/nl.json | 4 +- .../components/sentry/translations/nl.json | 12 +++++ .../components/smappee/translations/nl.json | 10 +++++ .../components/somfy/translations/nl.json | 2 +- .../components/spotify/translations/nl.json | 2 +- .../components/tag/translations/nl.json | 3 ++ .../components/tasmota/translations/nl.json | 3 ++ .../components/tuya/translations/nl.json | 18 +++++++- .../components/verisure/translations/es.json | 35 +++++++++++++++ .../components/verisure/translations/fr.json | 5 +++ .../components/vizio/translations/nl.json | 9 ++-- .../water_heater/translations/es.json | 10 +++++ .../components/withings/translations/nl.json | 6 +-- .../wolflink/translations/sensor.nl.json | 26 +++++++++++ .../zodiac/translations/sensor.nl.json | 1 + 63 files changed, 432 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/hive/translations/es.json create mode 100644 homeassistant/components/screenlogic/translations/es.json create mode 100644 homeassistant/components/screenlogic/translations/fr.json create mode 100644 homeassistant/components/screenlogic/translations/it.json create mode 100644 homeassistant/components/tag/translations/nl.json create mode 100644 homeassistant/components/verisure/translations/es.json diff --git a/homeassistant/components/airly/translations/nl.json b/homeassistant/components/airly/translations/nl.json index e89f769cd83123..14cbaf1711e922 100644 --- a/homeassistant/components/airly/translations/nl.json +++ b/homeassistant/components/airly/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Airly-integratie voor deze co\u00f6rdinaten is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "invalid_api_key": "Ongeldige API-sleutel", diff --git a/homeassistant/components/almond/translations/nl.json b/homeassistant/components/almond/translations/nl.json index 6d3aaf29e97119..43d90100e935e0 100644 --- a/homeassistant/components/almond/translations/nl.json +++ b/homeassistant/components/almond/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Kan geen verbinding maken met de Almond-server.", + "cannot_connect": "Kan geen verbinding maken", "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." diff --git a/homeassistant/components/august/translations/es.json b/homeassistant/components/august/translations/es.json index a8f7bc6af23a6f..bb343e6da97d8a 100644 --- a/homeassistant/components/august/translations/es.json +++ b/homeassistant/components/august/translations/es.json @@ -10,6 +10,10 @@ "unknown": "Error inesperado" }, "step": { + "reauth_validate": { + "description": "Introduzca la contrase\u00f1a de {username}.", + "title": "Reautorizar una cuenta de August" + }, "user": { "data": { "login_method": "M\u00e9todo de inicio de sesi\u00f3n", @@ -20,6 +24,13 @@ "description": "Si el M\u00e9todo de Inicio de Sesi\u00f3n es 'correo electr\u00f3nico', Usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el M\u00e9todo de Inicio de Sesi\u00f3n es 'tel\u00e9fono', Usuario es el n\u00famero de tel\u00e9fono en formato '+NNNNNNNNN'.", "title": "Configurar una cuenta de August" }, + "user_validate": { + "data": { + "login_method": "M\u00e9todo de inicio de sesi\u00f3n" + }, + "description": "Si el m\u00e9todo de inicio de sesi\u00f3n es \"correo electr\u00f3nico\", el nombre de usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el m\u00e9todo de inicio de sesi\u00f3n es \"tel\u00e9fono\", el nombre de usuario es el n\u00famero de tel\u00e9fono en el formato \"+NNNNNNN\".", + "title": "Configurar una cuenta de August" + }, "validation": { "data": { "code": "C\u00f3digo de verificaci\u00f3n" diff --git a/homeassistant/components/august/translations/fr.json b/homeassistant/components/august/translations/fr.json index 82568b681fdf48..530fda4dc9f269 100644 --- a/homeassistant/components/august/translations/fr.json +++ b/homeassistant/components/august/translations/fr.json @@ -10,6 +10,10 @@ "unknown": "Erreur inattendue" }, "step": { + "reauth_validate": { + "description": "Saisissez le mot de passe de {username} .", + "title": "R\u00e9authentifier un compte August" + }, "user": { "data": { "login_method": "M\u00e9thode de connexion", @@ -20,6 +24,13 @@ "description": "Si la m\u00e9thode de connexion est \u00abe-mail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", "title": "Configurer un compte August" }, + "user_validate": { + "data": { + "login_method": "M\u00e9thode de connexion" + }, + "description": "Si la m\u00e9thode de connexion est \u00abemail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", + "title": "Cr\u00e9er un compte August" + }, "validation": { "data": { "code": "Code de v\u00e9rification" diff --git a/homeassistant/components/august/translations/it.json b/homeassistant/components/august/translations/it.json index adc9017a275fe1..c20f95b90adc85 100644 --- a/homeassistant/components/august/translations/it.json +++ b/homeassistant/components/august/translations/it.json @@ -10,6 +10,13 @@ "unknown": "Errore imprevisto" }, "step": { + "reauth_validate": { + "data": { + "password": "Password" + }, + "description": "Inserisci la password per {username}.", + "title": "Riautentica un account di August" + }, "user": { "data": { "login_method": "Metodo di accesso", @@ -20,6 +27,15 @@ "description": "Se il metodo di accesso \u00e8 \"e-mail\", il nome utente \u00e8 l'indirizzo e-mail. Se il metodo di accesso \u00e8 \"telefono\", il nome utente \u00e8 il numero di telefono nel formato \"+NNNNNNNNN\".", "title": "Configura un account di August" }, + "user_validate": { + "data": { + "login_method": "Metodo di accesso", + "password": "Password", + "username": "Nome utente" + }, + "description": "Se il metodo di accesso \u00e8 'email', il nome utente \u00e8 l'indirizzo email. Se il metodo di accesso \u00e8 'phone', il nome utente \u00e8 il numero di telefono nel formato '+NNNNNNNNNN'.", + "title": "Configura un account di August" + }, "validation": { "data": { "code": "Codice di verifica" diff --git a/homeassistant/components/brother/translations/nl.json b/homeassistant/components/brother/translations/nl.json index d754b2df9c1bf0..531038d827b8c5 100644 --- a/homeassistant/components/brother/translations/nl.json +++ b/homeassistant/components/brother/translations/nl.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Printerhostnaam of IP-adres", + "host": "Host", "type": "Type printer" }, "description": "Zet Brother printerintegratie op. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/brother" diff --git a/homeassistant/components/canary/translations/nl.json b/homeassistant/components/canary/translations/nl.json index 9681bcd7c371fe..fbe642bbc96024 100644 --- a/homeassistant/components/canary/translations/nl.json +++ b/homeassistant/components/canary/translations/nl.json @@ -22,6 +22,7 @@ "step": { "init": { "data": { + "ffmpeg_arguments": "Argumenten doorgegeven aan ffmpeg voor camera's", "timeout": "Time-out verzoek (seconden)" } } diff --git a/homeassistant/components/denonavr/translations/nl.json b/homeassistant/components/denonavr/translations/nl.json index a109df0f1259fa..04f067c2f2a9d6 100644 --- a/homeassistant/components/denonavr/translations/nl.json +++ b/homeassistant/components/denonavr/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", + "cannot_connect": "Verbinding mislukt, probeer het opnieuw, de stroom- en ethernetkabels loskoppelen en opnieuw aansluiten kan helpen", "not_denonavr_manufacturer": "Geen Denon AVR Netwerk Receiver, ontdekte fabrikant komt niet overeen", "not_denonavr_missing": "Geen Denon AVR netwerkontvanger, zoekinformatie niet compleet" }, diff --git a/homeassistant/components/device_tracker/translations/it.json b/homeassistant/components/device_tracker/translations/it.json index 92ccce1c1c56f2..646f0732cd8264 100644 --- a/homeassistant/components/device_tracker/translations/it.json +++ b/homeassistant/components/device_tracker/translations/it.json @@ -15,5 +15,5 @@ "not_home": "Fuori casa" } }, - "title": "Tracciatore dispositivo" + "title": "Localizzatore di dispositivi" } \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/nl.json b/homeassistant/components/eafm/translations/nl.json index 0973f9ebd1aa6b..ed67ed8f982c8e 100644 --- a/homeassistant/components/eafm/translations/nl.json +++ b/homeassistant/components/eafm/translations/nl.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd", + "no_stations": "Geen meetstations voor overstromingen gevonden." }, "step": { "user": { "data": { "station": "Station" - } + }, + "description": "Selecteer het station dat u wilt monitoren", + "title": "Volg een station voor overstromingen" } } } diff --git a/homeassistant/components/garmin_connect/translations/nl.json b/homeassistant/components/garmin_connect/translations/nl.json index e9b71c49c71768..e751aaf1b5c692 100644 --- a/homeassistant/components/garmin_connect/translations/nl.json +++ b/homeassistant/components/garmin_connect/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dit account is al geconfigureerd." + "already_configured": "Account is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw.", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "too_many_requests": "Te veel aanvragen, probeer het later opnieuw.", "unknown": "Onverwachte fout" diff --git a/homeassistant/components/gdacs/translations/nl.json b/homeassistant/components/gdacs/translations/nl.json index f2a09892a66335..6dd3e5aa196977 100644 --- a/homeassistant/components/gdacs/translations/nl.json +++ b/homeassistant/components/gdacs/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Locatie is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index f6ac11f8724ae6..7224c29f318c59 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "GIO\u015a-integratie voor dit meetstation is al geconfigureerd." + "already_configured": "Locatie is al geconfigureerd." }, "error": { "cannot_connect": "Kan geen verbinding maken", diff --git a/homeassistant/components/goalzero/translations/nl.json b/homeassistant/components/goalzero/translations/nl.json index 4d9b5a397ddd63..c84ef7adb1f4ed 100644 --- a/homeassistant/components/goalzero/translations/nl.json +++ b/homeassistant/components/goalzero/translations/nl.json @@ -14,7 +14,8 @@ "host": "Host", "name": "Naam" }, - "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router." + "description": "Eerst moet u de Goal Zero-app downloaden: https://www.goalzero.com/product-features/yeti-app/ \n\n Volg de instructies om je Yeti te verbinden met je wifi-netwerk. Haal dan de host-ip van uw router. DHCP moet zijn ingesteld in uw routerinstellingen voor het apparaat om ervoor te zorgen dat het host-ip niet verandert. Raadpleeg de gebruikershandleiding van uw router.", + "title": "Goal Zero Yeti" } } } diff --git a/homeassistant/components/hive/translations/es.json b/homeassistant/components/hive/translations/es.json new file mode 100644 index 00000000000000..eb5ef0fd6eb019 --- /dev/null +++ b/homeassistant/components/hive/translations/es.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "unknown_entry": "No se puede encontrar una entrada existente." + }, + "error": { + "invalid_code": "No se ha podido iniciar la sesi\u00f3n en Hive. Tu c\u00f3digo de autenticaci\u00f3n de dos factores era incorrecto.", + "invalid_password": "No se ha podido iniciar la sesi\u00f3n en Hive. Contrase\u00f1a incorrecta, por favor, int\u00e9ntelo de nuevo.", + "invalid_username": "No se ha podido iniciar la sesi\u00f3n en Hive. No se reconoce su direcci\u00f3n de correo electr\u00f3nico.", + "no_internet_available": "Se requiere una conexi\u00f3n a Internet para conectarse a Hive." + }, + "step": { + "2fa": { + "data": { + "2fa": "C\u00f3digo de dos factores" + }, + "description": "Introduzca su c\u00f3digo de autentificaci\u00f3n Hive. \n \n Introduzca el c\u00f3digo 0000 para solicitar otro c\u00f3digo.", + "title": "Autenticaci\u00f3n de dos factores de Hive." + }, + "reauth": { + "description": "Vuelva a introducir sus datos de acceso a Hive.", + "title": "Inicio de sesi\u00f3n en Hive" + }, + "user": { + "data": { + "scan_interval": "Intervalo de exploraci\u00f3n (segundos)" + }, + "description": "Ingrese su configuraci\u00f3n e informaci\u00f3n de inicio de sesi\u00f3n de Hive.", + "title": "Inicio de sesi\u00f3n en Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "Intervalo de exploraci\u00f3n (segundos)" + }, + "description": "Actualice el intervalo de escaneo para buscar datos m\u00e1s a menudo.", + "title": "Opciones para Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index 5c23159e21f690..57692426ce0ff4 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -19,7 +19,12 @@ }, "flow_title": "{name} via HomeKit-accessoireprotocol", "step": { + "busy_error": { + "description": "Onderbreek het koppelen op alle controllers, of probeer het apparaat opnieuw op te starten, en ga dan verder om het koppelen te hervatten.", + "title": "Het apparaat is al aan het koppelen met een andere controller" + }, "max_tries_error": { + "description": "Het apparaat heeft meer dan 100 mislukte verificatiepogingen ontvangen. Probeer het apparaat opnieuw op te starten en ga dan verder om het koppelen te hervatten.", "title": "Maximum aantal authenticatiepogingen overschreden" }, "pair": { @@ -30,6 +35,7 @@ "title": "Koppel met HomeKit accessoire" }, "protocol_error": { + "description": "Het apparaat staat mogelijk niet in de koppelingsmodus en vereist mogelijk een fysieke of virtuele druk op de knop. Zorg ervoor dat het apparaat in de koppelingsmodus staat of probeer het apparaat opnieuw op te starten en ga dan verder om het koppelen te hervatten.", "title": "Fout bij het communiceren met de accessoire" }, "user": { diff --git a/homeassistant/components/huawei_lte/translations/nl.json b/homeassistant/components/huawei_lte/translations/nl.json index c1584b56330d19..799a9ce50af1e1 100644 --- a/homeassistant/components/huawei_lte/translations/nl.json +++ b/homeassistant/components/huawei_lte/translations/nl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "Dit apparaat wordt al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "not_huawei_lte": "Geen Huawei LTE-apparaat" }, "error": { diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json index 20b292878621ea..0c9191e807791b 100644 --- a/homeassistant/components/insteon/translations/nl.json +++ b/homeassistant/components/insteon/translations/nl.json @@ -87,12 +87,21 @@ "remove_override": "Verwijder een apparaatoverschrijving.", "remove_x10": "Verwijder een X10-apparaat." }, + "description": "Selecteer een optie om te configureren.", "title": "Insteon" }, "remove_override": { + "data": { + "address": "Selecteer een apparaatadres om te verwijderen" + }, + "description": "Verwijder een apparaatoverschrijving", "title": "Insteon" }, "remove_x10": { + "data": { + "address": "Selecteer een apparaatadres om te verwijderen" + }, + "description": "Een X10 apparaat verwijderen", "title": "Insteon" } } diff --git a/homeassistant/components/kmtronic/translations/es.json b/homeassistant/components/kmtronic/translations/es.json index 4fb0fc0e0a5c7a..f7c20f7805bade 100644 --- a/homeassistant/components/kmtronic/translations/es.json +++ b/homeassistant/components/kmtronic/translations/es.json @@ -15,5 +15,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "reverse": "L\u00f3gica de conmutaci\u00f3n inversa (utilizar NC)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 75b11338016ca6..635c064807ba6c 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -87,6 +87,7 @@ "data": { "api_host": "API host-URL overschrijven (optioneel)", "blink": "Led knipperen bij het verzenden van statuswijziging", + "discovery": "Reageer op detectieverzoeken op uw netwerk", "override_api_host": "Overschrijf standaard Home Assistant API hostpaneel-URL" }, "description": "Selecteer het gewenste gedrag voor uw paneel", diff --git a/homeassistant/components/meteo_france/translations/nl.json b/homeassistant/components/meteo_france/translations/nl.json index c8f7258e100c7a..f69db3ed47e6b7 100644 --- a/homeassistant/components/meteo_france/translations/nl.json +++ b/homeassistant/components/meteo_france/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Stad al geconfigureerd", - "unknown": "Onbekende fout: probeer het later nog eens" + "already_configured": "Locatie is al geconfigureerd.", + "unknown": "Onverwachte fout" }, "error": { "empty": "Geen resultaat bij het zoeken naar een stad: controleer de invoer: stad" diff --git a/homeassistant/components/mikrotik/translations/nl.json b/homeassistant/components/mikrotik/translations/nl.json index 53e05b5cf5f8ea..78e143ddadbb31 100644 --- a/homeassistant/components/mikrotik/translations/nl.json +++ b/homeassistant/components/mikrotik/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Mikrotik is al geconfigureerd" + "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding niet geslaagd", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "name_exists": "Naam bestaat al" }, diff --git a/homeassistant/components/mobile_app/translations/es.json b/homeassistant/components/mobile_app/translations/es.json index 43ba004ac722f1..8ac5c909e17dfb 100644 --- a/homeassistant/components/mobile_app/translations/es.json +++ b/homeassistant/components/mobile_app/translations/es.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Enviar una notificaci\u00f3n" } - } + }, + "title": "Aplicaci\u00f3n m\u00f3vil" } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/nl.json b/homeassistant/components/neato/translations/nl.json index 563e6500c16a7f..d03bc1d216a767 100644 --- a/homeassistant/components/neato/translations/nl.json +++ b/homeassistant/components/neato/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Al geconfigureerd", + "already_configured": "Apparaat is al geconfigureerd", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", "invalid_auth": "Ongeldige authenticatie", "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", @@ -9,7 +9,7 @@ "reauth_successful": "Herauthenticatie was succesvol" }, "create_entry": { - "default": "Zie [Neato-documentatie] ({docs_url})." + "default": "Succesvol geauthenticeerd" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/netatmo/translations/nl.json b/homeassistant/components/netatmo/translations/nl.json index a0f9682fd7461d..dc811b63534245 100644 --- a/homeassistant/components/netatmo/translations/nl.json +++ b/homeassistant/components/netatmo/translations/nl.json @@ -7,7 +7,7 @@ "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." }, "create_entry": { - "default": "Succesvol geauthenticeerd met Netatmo." + "default": "Succesvol geauthenticeerd" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/nightscout/translations/nl.json b/homeassistant/components/nightscout/translations/nl.json index 0146996dce586d..a9b81e9403e547 100644 --- a/homeassistant/components/nightscout/translations/nl.json +++ b/homeassistant/components/nightscout/translations/nl.json @@ -8,12 +8,15 @@ "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, + "flow_title": "Nightscout", "step": { "user": { "data": { "api_key": "API-sleutel", "url": "URL" - } + }, + "description": "- URL: het adres van uw nightscout instantie. Bijv.: https://myhomeassistant.duckdns.org:5423\n- API-sleutel (optioneel): Alleen gebruiken als uw instantie beveiligd is (auth_default_roles != readable).", + "title": "Voer uw Nightscout-serverinformatie in." } } } diff --git a/homeassistant/components/opentherm_gw/translations/ca.json b/homeassistant/components/opentherm_gw/translations/ca.json index 1da9bbb584e827..d63dfa91ce893d 100644 --- a/homeassistant/components/opentherm_gw/translations/ca.json +++ b/homeassistant/components/opentherm_gw/translations/ca.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temperatura de la planta", - "precision": "Precisi\u00f3" + "precision": "Precisi\u00f3", + "read_precision": "Llegeix precisi\u00f3", + "set_precision": "Defineix precisi\u00f3" }, "description": "Opcions del la passarel\u00b7la d'enlla\u00e7 d'OpenTherm" } diff --git a/homeassistant/components/opentherm_gw/translations/es.json b/homeassistant/components/opentherm_gw/translations/es.json index 44b6c6dfabcb4b..7a85b685e891d6 100644 --- a/homeassistant/components/opentherm_gw/translations/es.json +++ b/homeassistant/components/opentherm_gw/translations/es.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temperatura del suelo", - "precision": "Precisi\u00f3n" + "precision": "Precisi\u00f3n", + "read_precision": "Leer precisi\u00f3n", + "set_precision": "Establecer precisi\u00f3n" }, "description": "Opciones para OpenTherm Gateway" } diff --git a/homeassistant/components/opentherm_gw/translations/et.json b/homeassistant/components/opentherm_gw/translations/et.json index 4ab500e5531f6f..7bcb754afafa00 100644 --- a/homeassistant/components/opentherm_gw/translations/et.json +++ b/homeassistant/components/opentherm_gw/translations/et.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "P\u00f5randa temperatuur", - "precision": "T\u00e4psus" + "precision": "T\u00e4psus", + "read_precision": "Lugemi t\u00e4psus", + "set_precision": "M\u00e4\u00e4ra lugemi t\u00e4psus" }, "description": "OpenTherm Gateway suvandid" } diff --git a/homeassistant/components/opentherm_gw/translations/fr.json b/homeassistant/components/opentherm_gw/translations/fr.json index f060503ea23d14..804a7c8fe9c987 100644 --- a/homeassistant/components/opentherm_gw/translations/fr.json +++ b/homeassistant/components/opentherm_gw/translations/fr.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temp\u00e9rature du sol", - "precision": "Pr\u00e9cision" + "precision": "Pr\u00e9cision", + "read_precision": "Lire la pr\u00e9cision", + "set_precision": "D\u00e9finir la pr\u00e9cision" }, "description": "Options pour la passerelle OpenTherm" } diff --git a/homeassistant/components/opentherm_gw/translations/it.json b/homeassistant/components/opentherm_gw/translations/it.json index df1c36cd8d599d..bfe55c54bbd089 100644 --- a/homeassistant/components/opentherm_gw/translations/it.json +++ b/homeassistant/components/opentherm_gw/translations/it.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Temperatura del pavimento", - "precision": "Precisione" + "precision": "Precisione", + "read_precision": "Leggi la precisione", + "set_precision": "Imposta la precisione" }, "description": "Opzioni per OpenTherm Gateway" } diff --git a/homeassistant/components/opentherm_gw/translations/ko.json b/homeassistant/components/opentherm_gw/translations/ko.json index 6f3ac939ad1e11..00f2902a4f3ca7 100644 --- a/homeassistant/components/opentherm_gw/translations/ko.json +++ b/homeassistant/components/opentherm_gw/translations/ko.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "\uc628\ub3c4 \uc18c\uc218\uc810 \ubc84\ub9bc", - "precision": "\uc815\ubc00\ub3c4" + "precision": "\uc815\ubc00\ub3c4", + "read_precision": "\uc77d\uae30 \uc815\ubc00\ub3c4", + "set_precision": "\uc815\ubc00\ub3c4 \uc124\uc815\ud558\uae30" }, "description": "OpenTherm Gateway \uc635\uc158" } diff --git a/homeassistant/components/opentherm_gw/translations/no.json b/homeassistant/components/opentherm_gw/translations/no.json index 76118924e0a2f6..07b7c77c5ccc46 100644 --- a/homeassistant/components/opentherm_gw/translations/no.json +++ b/homeassistant/components/opentherm_gw/translations/no.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Etasje Temperatur", - "precision": "Presisjon" + "precision": "Presisjon", + "read_precision": "Les presisjon", + "set_precision": "Angi presisjon" }, "description": "Alternativer for OpenTherm Gateway" } diff --git a/homeassistant/components/opentherm_gw/translations/ru.json b/homeassistant/components/opentherm_gw/translations/ru.json index e63bfb58d9582c..df1166a7def87d 100644 --- a/homeassistant/components/opentherm_gw/translations/ru.json +++ b/homeassistant/components/opentherm_gw/translations/ru.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "\u0422\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0430 \u043f\u043e\u043b\u0430", - "precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c" + "precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c", + "read_precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u044f", + "set_precision": "\u0422\u043e\u0447\u043d\u043e\u0441\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438" }, "description": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0448\u043b\u044e\u0437\u0430 Opentherm" } diff --git a/homeassistant/components/opentherm_gw/translations/zh-Hant.json b/homeassistant/components/opentherm_gw/translations/zh-Hant.json index ea138287c78359..500c47ac46f3ad 100644 --- a/homeassistant/components/opentherm_gw/translations/zh-Hant.json +++ b/homeassistant/components/opentherm_gw/translations/zh-Hant.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "\u6a13\u5c64\u6eab\u5ea6", - "precision": "\u6e96\u78ba\u5ea6" + "precision": "\u6e96\u78ba\u5ea6", + "read_precision": "\u8b80\u53d6\u7cbe\u6e96\u5ea6", + "set_precision": "\u8a2d\u5b9a\u7cbe\u6e96\u5ea6" }, "description": "OpenTherm \u9598\u9053\u5668\u9078\u9805" } diff --git a/homeassistant/components/ovo_energy/translations/nl.json b/homeassistant/components/ovo_energy/translations/nl.json index d3909b0e44ed2b..d598e17d93b591 100644 --- a/homeassistant/components/ovo_energy/translations/nl.json +++ b/homeassistant/components/ovo_energy/translations/nl.json @@ -19,6 +19,7 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, + "description": "Stel een OVO Energy instance in om toegang te krijgen tot je energieverbruik.", "title": "Voeg OVO Energie Account toe" } } diff --git a/homeassistant/components/philips_js/translations/es.json b/homeassistant/components/philips_js/translations/es.json index 66d3b85c3bdd00..c8d34e9ea9d6ec 100644 --- a/homeassistant/components/philips_js/translations/es.json +++ b/homeassistant/components/philips_js/translations/es.json @@ -5,6 +5,10 @@ "pairing_failure": "No se ha podido emparejar: {error_id}" }, "step": { + "pair": { + "description": "Introduzca el PIN que se muestra en el televisor", + "title": "Par" + }, "user": { "data": { "api_version": "Versi\u00f3n del API", diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 25c28edcf1da04..326b81dc6a346a 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -10,6 +10,10 @@ "unknown": "Erreur inattendue" }, "step": { + "pair": { + "description": "Entrez le code PIN affich\u00e9 sur votre t\u00e9l\u00e9viseur", + "title": "Paire" + }, "user": { "data": { "api_version": "Version de l'API", diff --git a/homeassistant/components/philips_js/translations/it.json b/homeassistant/components/philips_js/translations/it.json index 03b5340a6bc5ba..6ff668dbea8b75 100644 --- a/homeassistant/components/philips_js/translations/it.json +++ b/homeassistant/components/philips_js/translations/it.json @@ -10,6 +10,13 @@ "unknown": "Errore imprevisto" }, "step": { + "pair": { + "data": { + "pin": "Codice PIN" + }, + "description": "Inserire il PIN visualizzato sul televisore", + "title": "Associa" + }, "user": { "data": { "api_version": "Versione API", diff --git a/homeassistant/components/philips_js/translations/ko.json b/homeassistant/components/philips_js/translations/ko.json index a76fd70418c24d..04ba5eff601d6e 100644 --- a/homeassistant/components/philips_js/translations/ko.json +++ b/homeassistant/components/philips_js/translations/ko.json @@ -15,7 +15,7 @@ "pin": "PIN \ucf54\ub4dc" }, "description": "TV\uc5d0 \ud45c\uc2dc\ub41c PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "\ud398\uc5b4\ub9c1" + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index efd679a1ce349b..408ddf967f4f9d 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -34,7 +34,8 @@ "init": { "data": { "scan_interval": "Scaninterval (seconden)" - } + }, + "description": "Plugwise opties aanpassen" } } } diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index d727fc9c1b8228..53441859d72c6f 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -39,6 +39,8 @@ "error": { "already_configured_device": "Apparaat is al geconfigureerd", "invalid_event_code": "Ongeldige gebeurteniscode", + "invalid_input_2262_off": "Ongeldige invoer voor commando uit", + "invalid_input_2262_on": "Ongeldige invoer voor commando aan", "invalid_input_off_delay": "Ongeldige invoer voor uitschakelvertraging", "unknown": "Onverwachte fout" }, @@ -55,6 +57,8 @@ }, "set_device_options": { "data": { + "command_off": "Waarde gegevensbits voor commando uit", + "command_on": "Waarde gegevensbits voor commando aan", "data_bit": "Aantal databits", "fire_event": "Schakel apparaatgebeurtenis in", "off_delay": "Uitschakelvertraging", diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json index 8c15279dca8c8f..5529aa39f2006f 100644 --- a/homeassistant/components/rpi_power/translations/nl.json +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "no_devices_found": "Kan de systeemklasse die nodig is voor dit onderdeel niet vinden, controleer of uw kernel recent is en of de hardware ondersteund wordt", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "step": { diff --git a/homeassistant/components/samsungtv/translations/nl.json b/homeassistant/components/samsungtv/translations/nl.json index d1e2a9abaa22bb..2a6cca466ea783 100644 --- a/homeassistant/components/samsungtv/translations/nl.json +++ b/homeassistant/components/samsungtv/translations/nl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Deze Samsung TV is al geconfigureerd.", - "already_in_progress": "Samsung TV configuratie is al in uitvoering.", + "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", "auth_missing": "Home Assistant is niet geautoriseerd om verbinding te maken met deze Samsung TV.", "cannot_connect": "Kan geen verbinding maken", "not_supported": "Deze Samsung TV wordt momenteel niet ondersteund." @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Hostnaam of IP-adres", + "host": "Host", "name": "Naam" }, "description": "Voer uw Samsung TV informatie in. Als u nooit eerder Home Assistant hebt verbonden dan zou u een popup op uw TV moeten zien waarin u om toestemming wordt vraagt." diff --git a/homeassistant/components/screenlogic/translations/es.json b/homeassistant/components/screenlogic/translations/es.json new file mode 100644 index 00000000000000..8e9513d4f7530e --- /dev/null +++ b/homeassistant/components/screenlogic/translations/es.json @@ -0,0 +1,29 @@ +{ + "config": { + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "description": "Introduzca la informaci\u00f3n de su ScreenLogic Gateway.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Puerta de enlace" + }, + "description": "Se han descubierto las siguientes puertas de enlace ScreenLogic. Seleccione una para configurarla o elija configurar manualmente una puerta de enlace ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Segundos entre exploraciones" + }, + "description": "Especificar la configuraci\u00f3n de {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/fr.json b/homeassistant/components/screenlogic/translations/fr.json new file mode 100644 index 00000000000000..d651f4f1c98e1a --- /dev/null +++ b/homeassistant/components/screenlogic/translations/fr.json @@ -0,0 +1,33 @@ +{ + "config": { + "flow_title": "ScreenLogic {nom}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Adresse IP", + "port": "Port" + }, + "description": "Entrez vos informations de passerelle ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "passerelle" + }, + "description": "Les passerelles ScreenLogic suivantes ont \u00e9t\u00e9 d\u00e9couvertes. S\u2019il vous pla\u00eet s\u00e9lectionner un \u00e0 configurer, ou choisissez de configurer manuellement une passerelle ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondes entre les scans" + }, + "description": "Sp\u00e9cifiez les param\u00e8tres pour {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/it.json b/homeassistant/components/screenlogic/translations/it.json new file mode 100644 index 00000000000000..8fc3c346c0f425 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/it.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Indirizzo IP", + "port": "Porta" + }, + "description": "Inserisci le informazioni del tuo gateway ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "Sono stati individuati i gateway ScreenLogic seguenti. Selezionarne uno da configurare oppure scegliere di configurare manualmente un gateway ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondi tra le scansioni" + }, + "description": "Specifica le impostazioni per {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/es.json b/homeassistant/components/sensor/translations/es.json index b2db3151abf203..b810a3f0eb11cf 100644 --- a/homeassistant/components/sensor/translations/es.json +++ b/homeassistant/components/sensor/translations/es.json @@ -2,6 +2,8 @@ "device_automation": { "condition_type": { "is_battery_level": "Nivel de bater\u00eda actual de {entity_name}", + "is_carbon_dioxide": "Nivel actual de concentraci\u00f3n de di\u00f3xido de carbono {entity_name}", + "is_carbon_monoxide": "Nivel actual de concentraci\u00f3n de mon\u00f3xido de carbono {entity_name}", "is_current": "Corriente actual de {entity_name}", "is_energy": "Energ\u00eda actual de {entity_name}", "is_humidity": "Humedad actual de {entity_name}", @@ -17,6 +19,8 @@ }, "trigger_type": { "battery_level": "Cambios de nivel de bater\u00eda de {entity_name}", + "carbon_dioxide": "{entity_name} cambios en la concentraci\u00f3n de di\u00f3xido de carbono", + "carbon_monoxide": "{entity_name} cambios en la concentraci\u00f3n de mon\u00f3xido de carbono", "current": "Cambio de corriente en {entity_name}", "energy": "Cambio de energ\u00eda en {entity_name}", "humidity": "Cambios de humedad de {entity_name}", diff --git a/homeassistant/components/sensor/translations/nl.json b/homeassistant/components/sensor/translations/nl.json index 94eb0374adfe6a..411ebf3cefd537 100644 --- a/homeassistant/components/sensor/translations/nl.json +++ b/homeassistant/components/sensor/translations/nl.json @@ -26,11 +26,13 @@ "humidity": "{entity_name} vochtigheidsgraad gewijzigd", "illuminance": "{entity_name} verlichtingssterkte gewijzigd", "power": "{entity_name} vermogen gewijzigd", + "power_factor": "{entity_name} power factor verandert", "pressure": "{entity_name} druk gewijzigd", "signal_strength": "{entity_name} signaalsterkte gewijzigd", "temperature": "{entity_name} temperatuur gewijzigd", "timestamp": "{entity_name} tijdstip gewijzigd", - "value": "{entity_name} waarde gewijzigd" + "value": "{entity_name} waarde gewijzigd", + "voltage": "{entity_name} voltage verandert" } }, "state": { diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 64b7f1b73f767f..682517c15dbb52 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -16,5 +16,17 @@ "title": "Sentry" } } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Optionele naam van de omgeving.", + "event_custom_components": "Gebeurtenissen verzenden vanuit aangepaste onderdelen", + "event_handled": "Stuur afgehandelde gebeurtenissen", + "event_third_party_packages": "Gebeurtenissen verzenden vanuit pakketten van derden" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/nl.json b/homeassistant/components/smappee/translations/nl.json index 373ff6ecd2e879..66ede5e8c14e66 100644 --- a/homeassistant/components/smappee/translations/nl.json +++ b/homeassistant/components/smappee/translations/nl.json @@ -11,6 +11,12 @@ }, "flow_title": "Smappee: {name}", "step": { + "environment": { + "data": { + "environment": "Omgeving" + }, + "description": "Stel uw Smappee in om te integreren met Home Assistant." + }, "local": { "data": { "host": "Host" @@ -19,6 +25,10 @@ }, "pick_implementation": { "title": "Kies een authenticatie methode" + }, + "zeroconf_confirm": { + "description": "Wilt u het Smappee apparaat met serienummer `{serialnumber}` toevoegen aan Home Assistant?", + "title": "Ontdekt Smappee apparaat" } } } diff --git a/homeassistant/components/somfy/translations/nl.json b/homeassistant/components/somfy/translations/nl.json index 3fe61e5eaab19c..94305c7ae6f975 100644 --- a/homeassistant/components/somfy/translations/nl.json +++ b/homeassistant/components/somfy/translations/nl.json @@ -11,7 +11,7 @@ }, "step": { "pick_implementation": { - "title": "Kies de authenticatie methode" + "title": "Kies een authenticatie methode" } } } diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index afccb637145f80..0d1e63fde5d070 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -11,7 +11,7 @@ }, "step": { "pick_implementation": { - "title": "Kies Authenticatiemethode" + "title": "Kies een authenticatie methode" }, "reauth_confirm": { "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", diff --git a/homeassistant/components/tag/translations/nl.json b/homeassistant/components/tag/translations/nl.json new file mode 100644 index 00000000000000..fdac700612daf4 --- /dev/null +++ b/homeassistant/components/tag/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tasmota/translations/nl.json b/homeassistant/components/tasmota/translations/nl.json index 561439362c748c..c099d376920550 100644 --- a/homeassistant/components/tasmota/translations/nl.json +++ b/homeassistant/components/tasmota/translations/nl.json @@ -3,6 +3,9 @@ "abort": { "single_instance_allowed": "Is al geconfigureerd. Er is maar een configuratie mogelijk" }, + "error": { + "invalid_discovery_topic": "Ongeldig onderwerpvoorvoegsel voor ontdekken" + }, "step": { "config": { "data": { diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index c9f681c5ecbc77..8f8117144113b1 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -35,11 +35,27 @@ "device": { "data": { "brightness_range_mode": "Helderheidsbereik gebruikt door apparaat", - "temp_step_override": "Doeltemperatuur stap" + "curr_temp_divider": "Huidige temperatuurwaarde deler (0 = standaardwaarde)", + "max_kelvin": "Max ondersteunde kleurtemperatuur in kelvin", + "max_temp": "Maximale doeltemperatuur (gebruik min en max = 0 voor standaardwaarde)", + "min_kelvin": "Minimaal ondersteunde kleurtemperatuur in kelvin", + "min_temp": "Min. gewenste temperatuur (gebruik min en max = 0 voor standaard)", + "support_color": "Forceer kleurenondersteuning", + "temp_divider": "Temperatuurwaarde deler (0 = standaardwaarde)", + "temp_step_override": "Doeltemperatuur stap", + "tuya_max_coltemp": "Max. Kleurtemperatuur gerapporteerd door apparaat", + "unit_of_measurement": "Temperatuureenheid gebruikt door apparaat" }, + "description": "Configureer opties om weergegeven informatie aan te passen voor {device_type} apparaat `{device_name}`", "title": "Configureer Tuya Apparaat" }, "init": { + "data": { + "discovery_interval": "Polling-interval van ontdekt apparaat in seconden", + "list_devices": "Selecteer de te configureren apparaten of laat leeg om de configuratie op te slaan", + "query_device": "Selecteer apparaat dat query-methode zal gebruiken voor snellere statusupdate", + "query_interval": "Peilinginterval van het apparaat in seconden" + }, "title": "Configureer Tuya opties" } } diff --git a/homeassistant/components/verisure/translations/es.json b/homeassistant/components/verisure/translations/es.json new file mode 100644 index 00000000000000..38605e4f86bc13 --- /dev/null +++ b/homeassistant/components/verisure/translations/es.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "installation": { + "data": { + "giid": "Instalaci\u00f3n" + }, + "description": "Home Assistant encontr\u00f3 varias instalaciones de Verisure en su cuenta de Mis p\u00e1ginas. Por favor, seleccione la instalaci\u00f3n para agregar a Home Assistant." + }, + "reauth_confirm": { + "data": { + "description": "Vuelva a autenticarse con su cuenta Verisure My Pages." + } + }, + "user": { + "data": { + "description": "Inicia sesi\u00f3n con tu cuenta Verisure My Pages." + } + } + } + }, + "options": { + "error": { + "code_format_mismatch": "El c\u00f3digo PIN predeterminado no coincide con el n\u00famero necesario de d\u00edgitos" + }, + "step": { + "init": { + "data": { + "lock_code_digits": "N\u00famero de d\u00edgitos del c\u00f3digo PIN de las cerraduras", + "lock_default_code": "C\u00f3digo PIN por defecto para las cerraduras, utilizado si no se indica ninguno" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/fr.json b/homeassistant/components/verisure/translations/fr.json index df2640e10ba7b1..1991120ab8fb53 100644 --- a/homeassistant/components/verisure/translations/fr.json +++ b/homeassistant/components/verisure/translations/fr.json @@ -14,6 +14,11 @@ }, "description": "Home Assistant a trouv\u00e9 plusieurs installations Verisure dans votre compte My Pages. Veuillez s\u00e9lectionner l'installation \u00e0 ajouter \u00e0 Home Assistant." }, + "reauth_confirm": { + "data": { + "description": "R\u00e9-authentifiez-vous avec votre compte Verisure My Pages." + } + }, "user": { "data": { "description": "Connectez-vous avec votre compte Verisure My Pages.", diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 4472a9cb9c0de9..48a7a7d353ded7 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -7,7 +7,8 @@ }, "error": { "cannot_connect": "Verbinding mislukt", - "complete_pairing_failed": "Kan het koppelen niet voltooien. Zorg ervoor dat de door u opgegeven pincode correct is en dat de tv nog steeds van stroom wordt voorzien en is verbonden met het netwerk voordat u opnieuw verzendt." + "complete_pairing_failed": "Kan het koppelen niet voltooien. Zorg ervoor dat de door u opgegeven pincode correct is en dat de tv nog steeds van stroom wordt voorzien en is verbonden met het netwerk voordat u opnieuw verzendt.", + "existing_config_entry_found": "Een bestaande VIZIO SmartCast-apparaat config entry met hetzelfde serienummer is reeds geconfigureerd. U moet de bestaande invoer verwijderen om deze te kunnen configureren." }, "step": { "pair_tv": { @@ -29,11 +30,11 @@ "data": { "access_token": "Toegangstoken", "device_class": "Apparaattype", - "host": ":", + "host": "Host", "name": "Naam" }, "description": "Een toegangstoken is alleen nodig voor tv's. Als u een TV configureert en nog geen toegangstoken heeft, laat dit dan leeg en doorloop het koppelingsproces.", - "title": "Vizio SmartCast Client instellen" + "title": "VIZIO SmartCast-apparaat" } } }, @@ -46,7 +47,7 @@ "volume_step": "Volume Stapgrootte" }, "description": "Als je een Smart TV hebt, kun je optioneel je bronnenlijst filteren door te kiezen welke apps je in je bronnenlijst wilt opnemen of uitsluiten.", - "title": "Update Vizo SmartCast Opties" + "title": "Update VIZIO SmartCast-apparaat opties" } } } diff --git a/homeassistant/components/water_heater/translations/es.json b/homeassistant/components/water_heater/translations/es.json index 46be0201ba5e05..f11f9592b81536 100644 --- a/homeassistant/components/water_heater/translations/es.json +++ b/homeassistant/components/water_heater/translations/es.json @@ -4,5 +4,15 @@ "turn_off": "Apagar {entity_name}", "turn_on": "Encender {entity_name}" } + }, + "state": { + "_": { + "eco": "Eco", + "electric": "El\u00e9ctrico", + "gas": "Gas", + "heat_pump": "Bomba de calor", + "high_demand": "Alta demanda", + "performance": "Rendimiento" + } } } \ No newline at end of file diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 9f7810a8378c9c..b20323347e4aa7 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Configuratie bijgewerkt voor profiel.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De Withings integratie is niet geconfigureerd. Gelieve de documentatie te volgen.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { @@ -15,13 +15,13 @@ "flow_title": "Withings: {profile}", "step": { "pick_implementation": { - "title": "Kies Authenticatiemethode" + "title": "Kies een authenticatie methode" }, "profile": { "data": { "profile": "Profiel" }, - "description": "Welk profiel hebt u op de website van Withings selecteren? Het is belangrijk dat de profielen overeenkomen, anders worden gegevens verkeerd gelabeld.", + "description": "Geef een unieke profielnaam op voor deze gegevens. Meestal is dit de naam van het profiel dat u in de vorige stap hebt geselecteerd.", "title": "Gebruikersprofiel." }, "reauth": { diff --git a/homeassistant/components/wolflink/translations/sensor.nl.json b/homeassistant/components/wolflink/translations/sensor.nl.json index 4056e4762ec728..f050fe4f629630 100644 --- a/homeassistant/components/wolflink/translations/sensor.nl.json +++ b/homeassistant/components/wolflink/translations/sensor.nl.json @@ -3,6 +3,8 @@ "wolflink__state": { "1_x_warmwasser": "1 x DHW", "abgasklappe": "Rookgasklep", + "absenkbetrieb": "Setback modus", + "absenkstop": "Setback stop", "aktiviert": "Geactiveerd", "antilegionellenfunktion": "Anti-legionella functie", "at_abschaltung": "OT afsluiten", @@ -23,10 +25,12 @@ "estrichtrocknung": "Dekvloer drogen", "externe_deaktivierung": "Externe uitschakeling", "fernschalter_ein": "Op afstand bedienen ingeschakeld", + "frost_heizkreis": "Verwarmengscircuit ontdooien", "frost_warmwasser": "DHW vorst", "frostschutz": "Vorstbescherming", "gasdruck": "Gasdruk", "glt_betrieb": "BMS-modus", + "gradienten_uberwachung": "Gradient monitoring", "heizbetrieb": "Verwarmingsmodus", "heizgerat_mit_speicher": "Boiler met cilinder", "heizung": "Verwarmen", @@ -35,26 +39,48 @@ "kalibration_heizbetrieb": "Kalibratie verwarmingsmodus", "kalibration_kombibetrieb": "Kalibratie van de combimodus", "kalibration_warmwasserbetrieb": "DHW-kalibratie", + "kaskadenbetrieb": "Cascade operation", "kombibetrieb": "Combi-modus", "kombigerat": "Combiketel", + "kombigerat_mit_solareinbindung": "Combiketel met zonne-integratie", + "mindest_kombizeit": "Minimale combitijd", + "nachlauf_heizkreispumpe": "De pomp van het verwarmingscircuit gaat aan", + "nachspulen": "Post-flush", "nur_heizgerat": "Alleen ketel", + "parallelbetrieb": "Parallelle modus", "partymodus": "Feestmodus", + "perm_cooling": "PermCooling", "permanent": "Permanent", "permanentbetrieb": "Permanente modus", "reduzierter_betrieb": "Beperkte modus", + "rt_abschaltung": "RT afsluiten", + "rt_frostschutz": "RT vorstbescherming", + "ruhekontakt": "Rest contact", "schornsteinfeger": "Emissietest", "smart_grid": "SmartGrid", "smart_home": "SmartHome", + "softstart": "Zachte start", + "solarbetrieb": "Zonnemodus", "sparbetrieb": "Spaarstand", + "sparen": "Spaarstand", + "spreizung_hoch": "dT te breed", + "spreizung_kf": "Spreid KF", + "stabilisierung": "Stablisatie", "standby": "Stand-by", "start": "Start", "storung": "Fout", + "taktsperre": "Anti-cyclus", + "telefonfernschalter": "Telefoon schakelaar op afstand", "test": "Test", "tpw": "TPW", "urlaubsmodus": "Vakantiemodus", "ventilprufung": "Kleptest", + "vorspulen": "Invoer spoelen", "warmwasser": "DHW", + "warmwasser_schnellstart": "DHW Snel starten", "warmwasserbetrieb": "DHW-modus", + "warmwassernachlauf": "DHW aanloop", + "warmwasservorrang": "DHW prioriteit", "zunden": "Ontsteking" } } diff --git a/homeassistant/components/zodiac/translations/sensor.nl.json b/homeassistant/components/zodiac/translations/sensor.nl.json index c07b20de21b819..6dba645ed83832 100644 --- a/homeassistant/components/zodiac/translations/sensor.nl.json +++ b/homeassistant/components/zodiac/translations/sensor.nl.json @@ -3,6 +3,7 @@ "zodiac__sign": { "aquarius": "Waterman", "aries": "Ram", + "cancer": "Kreeft", "capricorn": "Steenbok", "gemini": "Tweelingen", "leo": "Leo", From 26bceae99d6ebfed3d76715113c6891a71d31fe6 Mon Sep 17 00:00:00 2001 From: Thiago Oliveira Date: Fri, 19 Mar 2021 17:20:09 -0700 Subject: [PATCH 1399/1818] Set zwave_js climate precision to tenths for F (#48133) --- homeassistant/components/zwave_js/climate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 78d76423378b95..fbe54e42d78478 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -41,7 +41,12 @@ SUPPORT_TARGET_TEMPERATURE_RANGE, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + ATTR_TEMPERATURE, + PRECISION_TENTHS, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -228,6 +233,11 @@ def temperature_unit(self) -> str: return TEMP_FAHRENHEIT return TEMP_CELSIUS + @property + def precision(self) -> float: + """Return the precision of 0.1.""" + return PRECISION_TENTHS + @property def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode.""" From fb849b81b5307a25112b89f8994f83fb8fc1f4f1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Mar 2021 01:27:04 +0100 Subject: [PATCH 1400/1818] Rewrite of not a == b occurances (#48132) --- homeassistant/components/glances/sensor.py | 2 +- homeassistant/components/isy994/helpers.py | 4 ++-- homeassistant/components/isy994/services.py | 8 ++++---- .../components/openhome/media_player.py | 2 +- homeassistant/components/proxy/camera.py | 2 +- .../components/pulseaudio_loopback/switch.py | 2 +- homeassistant/components/roon/media_player.py | 2 +- homeassistant/components/vlc/media_player.py | 2 +- homeassistant/components/zha/device_tracker.py | 2 +- homeassistant/components/zwave_js/sensor.py | 4 ++-- homeassistant/helpers/template.py | 2 +- homeassistant/util/timeout.py | 2 +- script/hassfest/coverage.py | 4 ++-- tests/components/mqtt/test_cover.py | 18 +++++++++--------- tests/components/pilight/test_init.py | 2 +- tests/components/recorder/test_purge.py | 2 +- .../components/universal/test_media_player.py | 2 +- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 4c534a90ae1596..006333b321ac36 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): dev = [] for sensor_type, sensor_details in SENSOR_TYPES.items(): - if not sensor_details[0] in client.api.data: + if sensor_details[0] not in client.api.data: continue if sensor_details[0] in client.api.data: if sensor_details[0] == "fs": diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index 780e24843bd0a5..81a74430d3ad87 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -326,7 +326,7 @@ def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None: actions = None status = entity_folder.get_by_name(KEY_STATUS) - if not status or not status.protocol == PROTO_PROGRAM: + if not status or status.protocol != PROTO_PROGRAM: _LOGGER.warning( "Program %s entity '%s' not loaded, invalid/missing status program", platform, @@ -336,7 +336,7 @@ def _categorize_programs(hass_isy_data: dict, programs: Programs) -> None: if platform != BINARY_SENSOR: actions = entity_folder.get_by_name(KEY_ACTIONS) - if not actions or not actions.protocol == PROTO_PROGRAM: + if not actions or actions.protocol != PROTO_PROGRAM: _LOGGER.warning( "Program %s entity '%s' not loaded, invalid/missing actions program", platform, diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index b9c4bcdeef2651..39966a9d9947de 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -174,7 +174,7 @@ async def async_system_query_service_handler(service): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue # If an address is provided, make sure we query the correct ISY. # Otherwise, query the whole system on all ISY's connected. @@ -199,7 +199,7 @@ async def async_run_network_resource_service_handler(service): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue if not hasattr(isy, "networking") or isy.networking is None: continue @@ -224,7 +224,7 @@ async def async_send_program_command_service_handler(service): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue program = None if address: @@ -247,7 +247,7 @@ async def async_set_variable_service_handler(service): for config_entry_id in hass.data[DOMAIN]: isy = hass.data[DOMAIN][config_entry_id][ISY994_ISY] - if isy_name and not isy_name == isy.configuration["name"]: + if isy_name and isy_name != isy.configuration["name"]: continue variable = None if name: diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index f9dbd4272ba184..270eb22ebdaf00 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -139,7 +139,7 @@ def turn_off(self): def play_media(self, media_type, media_id, **kwargs): """Send the play_media command to the media player.""" - if not media_type == MEDIA_TYPE_MUSIC: + if media_type != MEDIA_TYPE_MUSIC: _LOGGER.error( "Invalid media type %s. Only %s is supported", media_type, diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index c3f7151431a394..8fda507ace21d7 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -77,7 +77,7 @@ def _precheck_image(image, opts): if imgfmt not in ("PNG", "JPEG"): _LOGGER.warning("Image is of unsupported type: %s", imgfmt) raise ValueError() - if not img.mode == "RGB": + if img.mode != "RGB": img = img.convert("RGB") return img diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py index 9c27ab4e0277c1..260fbe65c1b168 100644 --- a/homeassistant/components/pulseaudio_loopback/switch.py +++ b/homeassistant/components/pulseaudio_loopback/switch.py @@ -73,7 +73,7 @@ def _get_module_idx(self): self._pa_svr.connect() for module in self._pa_svr.module_list(): - if not module.name == "module-loopback": + if module.name != "module-loopback": continue if f"sink={self._sink_name}" not in module.argument: diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index b2ae62ec250cd7..773028da2d3073 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -465,7 +465,7 @@ def turn_off(self): return for source in self.player_data["source_controls"]: - if source["supports_standby"] and not source["status"] == "indeterminate": + if source["supports_standby"] and source["status"] != "indeterminate": self._server.roonapi.standby(self.output_id, source["control_key"]) return diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index fe387dd4adc4a7..db5c26f4a0cca1 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -159,7 +159,7 @@ def media_stop(self): def play_media(self, media_type, media_id, **kwargs): """Play media from a URL or file.""" - if not media_type == MEDIA_TYPE_MUSIC: + if media_type != MEDIA_TYPE_MUSIC: _LOGGER.error( "Invalid media type %s. Only %s is supported", media_type, diff --git a/homeassistant/components/zha/device_tracker.py b/homeassistant/components/zha/device_tracker.py index 53191789eba2e9..ffb37e33b0fcc3 100644 --- a/homeassistant/components/zha/device_tracker.py +++ b/homeassistant/components/zha/device_tracker.py @@ -83,7 +83,7 @@ def source_type(self): @callback def async_battery_percentage_remaining_updated(self, attr_id, attr_name, value): """Handle tracking.""" - if not attr_name == "battery_percentage_remaining": + if attr_name != "battery_percentage_remaining": return self.debug("battery_percentage_remaining updated: %s", value) self._connected = True diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index ada6f6b5d063d5..5f116f0790c7bc 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -197,8 +197,8 @@ def state(self) -> str | None: if self.info.primary_value.value is None: return None if ( - not str(self.info.primary_value.value) - in self.info.primary_value.metadata.states + str(self.info.primary_value.value) + not in self.info.primary_value.metadata.states ): return str(self.info.primary_value.value) return str( diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 671009b884682b..cfb7223752ea8a 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1439,7 +1439,7 @@ def is_safe_callable(self, obj): def is_safe_attribute(self, obj, attr, value): """Test if attribute is safe.""" if isinstance(obj, (AllStates, DomainStates, TemplateState)): - return not attr[0] == "_" + return attr[0] != "_" if isinstance(obj, Namespace): return True diff --git a/homeassistant/util/timeout.py b/homeassistant/util/timeout.py index 64208d775ea015..5821f89659e7a2 100644 --- a/homeassistant/util/timeout.py +++ b/homeassistant/util/timeout.py @@ -243,7 +243,7 @@ async def _on_wait(self) -> None: """Wait until zones are done.""" await self._wait_zone.wait() await asyncio.sleep(self._cool_down) # Allow context switch - if not self.state == _State.TIMEOUT: + if self.state != _State.TIMEOUT: return self._cancel_task() diff --git a/script/hassfest/coverage.py b/script/hassfest/coverage.py index 1a0c684cfab124..06e38902060664 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -106,8 +106,8 @@ def validate(integrations: dict[str, Integration], config: Config): if ( not line.startswith("homeassistant/components/") - or not len(path.parts) == 4 - or not path.parts[-1] == "*" + or len(path.parts) != 4 + or path.parts[-1] != "*" ): continue diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 2a29bf9e15e924..508869c7b51fa7 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -525,9 +525,9 @@ async def test_current_cover_position(hass, mqtt_mock): await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes - assert not (ATTR_CURRENT_POSITION in state_attributes_dict) - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) - assert not (4 & hass.states.get("cover.test").attributes["supported_features"] == 4) + assert ATTR_CURRENT_POSITION not in state_attributes_dict + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict + assert 4 & hass.states.get("cover.test").attributes["supported_features"] != 4 async_fire_mqtt_message(hass, "get-position-topic", "0") current_cover_position = hass.states.get("cover.test").attributes[ @@ -576,9 +576,9 @@ async def test_current_cover_position_inverted(hass, mqtt_mock): await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes - assert not (ATTR_CURRENT_POSITION in state_attributes_dict) - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) - assert not (4 & hass.states.get("cover.test").attributes["supported_features"] == 4) + assert ATTR_CURRENT_POSITION not in state_attributes_dict + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict + assert 4 & hass.states.get("cover.test").attributes["supported_features"] != 4 async_fire_mqtt_message(hass, "get-position-topic", "100") current_percentage_cover_position = hass.states.get("cover.test").attributes[ @@ -659,14 +659,14 @@ async def test_position_update(hass, mqtt_mock): await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes - assert not (ATTR_CURRENT_POSITION in state_attributes_dict) - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) + assert ATTR_CURRENT_POSITION not in state_attributes_dict + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict assert 4 & hass.states.get("cover.test").attributes["supported_features"] == 4 async_fire_mqtt_message(hass, "get-position-topic", "22") state_attributes_dict = hass.states.get("cover.test").attributes assert ATTR_CURRENT_POSITION in state_attributes_dict - assert not (ATTR_CURRENT_TILT_POSITION in state_attributes_dict) + assert ATTR_CURRENT_TILT_POSITION not in state_attributes_dict current_cover_position = hass.states.get("cover.test").attributes[ ATTR_CURRENT_POSITION ] diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index b69e03058bfb9d..4b8b017f7fd575 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -360,7 +360,7 @@ async def test_whitelist_no_match(mock_debug, hass): await hass.async_block_till_done() debug_log_call = mock_debug.call_args_list[-3] - assert not ("Event pilight_received" in debug_log_call) + assert "Event pilight_received" not in debug_log_call async def test_call_rate_delay_throttle_enabled(hass): diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index bbc16cef82562b..e487e542958e09 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -157,7 +157,7 @@ async def test_purge_method( assert runs[1] == runs_before_purge[5] assert runs[2] == runs_before_purge[6] - assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all())) + assert "EVENT_TEST_PURGE" not in (event.event_type for event in events.all()) # run purge method - correct service data, with repack service_data["repack"] = True diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 8d8bc80234e2c8..3914f580724bcc 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -333,7 +333,7 @@ def test_config_bad_key(self): config = {"name": "test", "asdf": 5, "platform": "universal"} config = validate_config(config) - assert not ("asdf" in config) + assert "asdf" not in config def test_platform_setup(self): """Test platform setup.""" From 0f16d4f1e7f27de78c0201ba3368eebad99906e3 Mon Sep 17 00:00:00 2001 From: Jonathan Keslin Date: Fri, 19 Mar 2021 17:50:52 -0700 Subject: [PATCH 1401/1818] Update pyvesync to 1.3.1 (#48128) --- homeassistant/components/vesync/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index 667cb16d12839c..6aa7a5774fd88d 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -8,7 +8,7 @@ "@thegardenmonkey" ], "requirements": [ - "pyvesync==1.2.0" + "pyvesync==1.3.1" ], "config_flow": true -} \ No newline at end of file +} diff --git a/requirements_all.txt b/requirements_all.txt index 6a114b0e972a80..6c3793e6c1f860 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1892,7 +1892,7 @@ pyvera==0.3.13 pyversasense==0.0.6 # homeassistant.components.vesync -pyvesync==1.2.0 +pyvesync==1.3.1 # homeassistant.components.vizio pyvizio==0.1.57 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4a5dbc85efe8f6..04a5c6450879bd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -980,7 +980,7 @@ pytradfri[async]==7.0.6 pyvera==0.3.13 # homeassistant.components.vesync -pyvesync==1.2.0 +pyvesync==1.3.1 # homeassistant.components.vizio pyvizio==0.1.57 From 365e8a74ee639381d8625daa2f501052c4b2a2e9 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Sat, 20 Mar 2021 08:26:11 +0100 Subject: [PATCH 1402/1818] Add tests for Netatmo webhook handler (#46396) * Add tests for Netatmo webhook handler * Add async prefix * Remove freezegun dependency * Clean up --- .coveragerc | 1 - tests/components/netatmo/test_webhook.py | 126 +++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/components/netatmo/test_webhook.py diff --git a/.coveragerc b/.coveragerc index 48fb4ce756fa4f..33f4071c8f4cfb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -648,7 +648,6 @@ omit = homeassistant/components/netatmo/helper.py homeassistant/components/netatmo/netatmo_entity_base.py homeassistant/components/netatmo/sensor.py - homeassistant/components/netatmo/webhook.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/tests/components/netatmo/test_webhook.py b/tests/components/netatmo/test_webhook.py new file mode 100644 index 00000000000000..a56bd2f0fde9bb --- /dev/null +++ b/tests/components/netatmo/test_webhook.py @@ -0,0 +1,126 @@ +"""The tests for Netatmo webhook events.""" +from homeassistant.components.netatmo.const import DATA_DEVICE_IDS, DATA_PERSONS +from homeassistant.components.netatmo.webhook import async_handle_webhook +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.aiohttp import MockRequest + + +async def test_webhook(hass): + """Test that webhook events are processed.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = ( + b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' + b'"push_type": "webhook_activation"}' + ) + request = MockRequest(content=response, mock_source="test") + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-None", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert webhook_called + + +async def test_webhook_error_in_data(hass): + """Test that errors in webhook data are handled.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = b'""webhook_activation"}' + request = MockRequest(content=response, mock_source="test") + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-None", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert not webhook_called + + +async def test_webhook_climate_event(hass): + """Test that climate events are handled.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = ( + b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' + b'"home_id": "456", "event_type": "therm_mode",' + b'"home": {"id": "456", "therm_mode": "away"},' + b'"mode": "away", "previous_mode": "schedule", "push_type": "home_event_changed"}' + ) + request = MockRequest(content=response, mock_source="test") + + hass.data["netatmo"] = { + DATA_DEVICE_IDS: {}, + } + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-therm_mode", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert webhook_called + + +async def test_webhook_person_event(hass): + """Test that person events are handled.""" + webhook_called = False + + async def handle_event(_): + nonlocal webhook_called + webhook_called = True + + response = ( + b'{"user_id": "5c81004xxxxxxxxxx45f4",' + b'"persons": [{"id": "e2bf7xxxxxxxxxxxxea3", "face_id": "5d66xxxxxx9b9",' + b'"face_key": "89dxxxxx22", "is_known": true,' + b'"face_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxx"}],' + b'"snapshot_id": "5d19bae867368a59e81cca89", "snapshot_key": "d3b3ae0229f7xb74cf8",' + b'"snapshot_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxxx",' + b'"event_type": "person", "camera_id": "70:xxxxxx:a7", "device_id": "70:xxxxxx:a7",' + b'"home_id": "5c5dxxxxxxxd594", "home_name": "Boulogne Billan.",' + b'"event_id": "5d19bxxxxxxxxcca88",' + b'"message": "Boulogne Billan.: Benoit has been seen by Indoor Camera ",' + b'"push_type": "NACamera-person"}' + ) + request = MockRequest(content=response, mock_source="test") + + hass.data["netatmo"] = { + DATA_DEVICE_IDS: {}, + DATA_PERSONS: {}, + } + + async_dispatcher_connect( + hass, + "signal-netatmo-webhook-person", + handle_event, + ) + + await async_handle_webhook(hass, "webhook_id", request) + await hass.async_block_till_done() + + assert webhook_called From 5a2b5fe7c5bab74d1720073112e795b8dc89ff25 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Mar 2021 13:55:10 +0100 Subject: [PATCH 1403/1818] Yoda assertion style removed is (#48142) --- tests/components/alert/test_init.py | 20 +- tests/components/apns/test_notify.py | 24 +- .../components/bayesian/test_binary_sensor.py | 10 +- tests/components/binary_sensor/test_init.py | 6 +- .../components/blackbird/test_media_player.py | 54 +-- tests/components/configurator/test_init.py | 26 +- tests/components/counter/test_init.py | 50 +-- tests/components/datadog/test_init.py | 4 +- tests/components/demo/test_climate.py | 2 +- tests/components/device_tracker/test_init.py | 8 +- tests/components/ecobee/test_climate.py | 4 +- tests/components/efergy/test_sensor.py | 16 +- tests/components/emulated_hue/test_hue_api.py | 6 +- tests/components/filter/test_sensor.py | 20 +- tests/components/flux/test_switch.py | 38 +-- .../generic_thermostat/test_climate.py | 320 +++++++++--------- tests/components/geofency/test_init.py | 24 +- tests/components/google_pubsub/test_init.py | 4 +- tests/components/google_wifi/test_sensor.py | 22 +- tests/components/gpslogger/test_init.py | 8 +- tests/components/group/test_init.py | 32 +- tests/components/homeassistant/test_init.py | 8 +- .../homeassistant/triggers/test_state.py | 2 +- tests/components/honeywell/test_climate.py | 54 +-- .../imap_email_content/test_sensor.py | 30 +- tests/components/influxdb/test_init.py | 6 +- tests/components/input_boolean/test_init.py | 16 +- tests/components/input_number/test_init.py | 34 +- tests/components/input_select/test_init.py | 52 +-- tests/components/logentries/test_init.py | 4 +- tests/components/mailbox/test_init.py | 3 +- .../manual/test_alarm_control_panel.py | 234 ++++++------- .../manual_mqtt/test_alarm_control_panel.py | 240 ++++++------- tests/components/melissa/test_climate.py | 34 +- .../components/meraki/test_device_tracker.py | 4 +- tests/components/mfi/test_sensor.py | 2 +- tests/components/min_max/test_sensor.py | 52 +-- tests/components/minio/test_minio.py | 18 +- tests/components/mochad/test_switch.py | 2 +- .../components/mold_indicator/test_sensor.py | 2 +- tests/components/mqtt/test_cover.py | 8 +- tests/components/mqtt/test_trigger.py | 2 +- .../components/mqtt_eventstream/test_init.py | 2 +- .../nsw_fuel_station/test_sensor.py | 4 +- tests/components/nx584/test_binary_sensor.py | 6 +- tests/components/plant/test_init.py | 18 +- tests/components/prometheus/test_init.py | 4 +- tests/components/radarr/test_sensor.py | 80 ++--- tests/components/remote/test_init.py | 2 +- tests/components/rest/test_switch.py | 2 +- tests/components/scene/test_init.py | 6 +- tests/components/script/test_init.py | 12 +- tests/components/shell_command/test_init.py | 2 +- .../components/sleepiq/test_binary_sensor.py | 16 +- tests/components/sleepiq/test_sensor.py | 16 +- tests/components/statistics/test_sensor.py | 22 +- tests/components/statsd/test_init.py | 2 +- tests/components/template/test_trigger.py | 4 +- .../threshold/test_binary_sensor.py | 100 +++--- tests/components/timer/test_init.py | 32 +- .../totalconnect/test_alarm_control_panel.py | 32 +- tests/components/traccar/test_init.py | 8 +- tests/components/uk_transport/test_sensor.py | 24 +- .../unifi_direct/test_device_tracker.py | 6 +- .../components/universal/test_media_player.py | 96 +++--- tests/components/vultr/test_binary_sensor.py | 38 +-- tests/components/vultr/test_sensor.py | 32 +- tests/components/wake_on_lan/test_switch.py | 26 +- .../components/xiaomi/test_device_tracker.py | 12 +- tests/components/zone/test_init.py | 6 +- tests/components/zone/test_trigger.py | 2 +- tests/helpers/test_config_validation.py | 2 +- tests/helpers/test_template.py | 154 ++++----- tests/util/test_dt.py | 2 +- tests/util/test_unit_system.py | 10 +- 75 files changed, 1137 insertions(+), 1148 deletions(-) diff --git a/tests/components/alert/test_init.py b/tests/components/alert/test_init.py index 3796322a5b5c39..199be9845ca390 100644 --- a/tests/components/alert/test_init.py +++ b/tests/components/alert/test_init.py @@ -120,7 +120,7 @@ async def test_is_on(hass): async def test_setup(hass): """Test setup method.""" assert await async_setup_component(hass, alert.DOMAIN, TEST_CONFIG) - assert STATE_IDLE == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_IDLE async def test_fire(hass, mock_notifier): @@ -128,7 +128,7 @@ async def test_fire(hass, mock_notifier): assert await async_setup_component(hass, alert.DOMAIN, TEST_CONFIG) hass.states.async_set("sensor.test", STATE_ON) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_silence(hass, mock_notifier): @@ -138,15 +138,15 @@ async def test_silence(hass, mock_notifier): await hass.async_block_till_done() async_turn_off(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_OFF # alert should not be silenced on next fire hass.states.async_set("sensor.test", STATE_OFF) await hass.async_block_till_done() - assert STATE_IDLE == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_IDLE hass.states.async_set("sensor.test", STATE_ON) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_reset(hass, mock_notifier): @@ -156,10 +156,10 @@ async def test_reset(hass, mock_notifier): await hass.async_block_till_done() async_turn_off(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_OFF async_turn_on(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_toggle(hass, mock_notifier): @@ -167,13 +167,13 @@ async def test_toggle(hass, mock_notifier): assert await async_setup_component(hass, alert.DOMAIN, TEST_CONFIG) hass.states.async_set("sensor.test", STATE_ON) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async_toggle(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_OFF async_toggle(hass, ENTITY_ID) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ON async def test_notification_no_done_message(hass): diff --git a/tests/components/apns/test_notify.py b/tests/components/apns/test_notify.py index 22d0a30ab16331..301ef1362faeb6 100644 --- a/tests/components/apns/test_notify.py +++ b/tests/components/apns/test_notify.py @@ -199,7 +199,7 @@ def fake_write(_out, device): assert test_device_1 is not None assert test_device_2 is not None - assert "updated device 1" == test_device_1.name + assert test_device_1.name == "updated device 1" @patch("homeassistant.components.apns.notify._write_device") @@ -239,8 +239,8 @@ def fake_write(_out, device): assert test_device_1 is not None assert test_device_2 is not None - assert "tracking123" == test_device_1.tracking_device_id - assert "tracking456" == test_device_2.tracking_device_id + assert test_device_1.tracking_device_id == "tracking123" + assert test_device_2.tracking_device_id == "tracking456" @patch("homeassistant.components.apns.notify.APNsClient") @@ -267,16 +267,16 @@ async def test_send(mock_client, hass): ) assert send.called - assert 1 == len(send.mock_calls) + assert len(send.mock_calls) == 1 target = send.mock_calls[0][1][0] payload = send.mock_calls[0][1][1] - assert "1234" == target - assert "Hello" == payload.alert - assert 1 == payload.badge - assert "test.mp3" == payload.sound - assert "testing" == payload.category + 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") @@ -337,13 +337,13 @@ async def test_send_with_state(mock_client, hass): notify_service.send_message(message="Hello", target="home") assert send.called - assert 1 == len(send.mock_calls) + assert len(send.mock_calls) == 1 target = send.mock_calls[0][1][0] payload = send.mock_calls[0][1][1] - assert "5678" == target - assert "Hello" == payload.alert + assert target == "5678" + assert payload.alert == "Hello" @patch("homeassistant.components.apns.notify.APNsClient") diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index 01f2664ea67d57..9c181e90deb165 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -119,7 +119,7 @@ async def test_sensor_numeric_state(hass): state = hass.states.get("binary_sensor.test_binary") assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -146,7 +146,7 @@ async def test_sensor_numeric_state(hass): await hass.async_block_till_done() state = hass.states.get("binary_sensor.test_binary") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -186,7 +186,7 @@ async def test_sensor_state(hass): state = hass.states.get("binary_sensor.test_binary") assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -242,7 +242,7 @@ async def test_sensor_value_template(hass): state = hass.states.get("binary_sensor.test_binary") assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" @@ -339,7 +339,7 @@ async def test_multiple_observations(hass): for key, attrs in state.attributes.items(): json.dumps(attrs) assert [] == state.attributes.get("observations") - assert 0.2 == state.attributes.get("probability") + assert state.attributes.get("probability") == 0.2 assert state.state == "off" diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 99af954cd4d257..0c574df1569b45 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -8,17 +8,17 @@ def test_state(): """Test binary sensor state.""" sensor = binary_sensor.BinarySensorEntity() - assert STATE_OFF == sensor.state + assert sensor.state == STATE_OFF with mock.patch( "homeassistant.components.binary_sensor.BinarySensorEntity.is_on", new=False, ): - assert STATE_OFF == binary_sensor.BinarySensorEntity().state + assert binary_sensor.BinarySensorEntity().state == STATE_OFF with mock.patch( "homeassistant.components.binary_sensor.BinarySensorEntity.is_on", new=True, ): - assert STATE_ON == binary_sensor.BinarySensorEntity().state + assert binary_sensor.BinarySensorEntity().state == STATE_ON def test_deprecated_base_class(caplog): diff --git a/tests/components/blackbird/test_media_player.py b/tests/components/blackbird/test_media_player.py index 316ed681fa092a..73b40fdec9765d 100644 --- a/tests/components/blackbird/test_media_player.py +++ b/tests/components/blackbird/test_media_player.py @@ -219,9 +219,9 @@ def test_setup_platform(self, *args): def test_setallzones_service_call_with_entity_id(self): """Test set all zone source service call with entity id.""" self.media_player.update() - assert "Zone name" == self.media_player.name - assert STATE_ON == self.media_player.state - assert "one" == self.media_player.source + assert self.media_player.name == "Zone name" + assert self.media_player.state == STATE_ON + assert self.media_player.source == "one" # Call set all zones service self.hass.services.call( @@ -232,16 +232,16 @@ def test_setallzones_service_call_with_entity_id(self): ) # Check that source was changed - assert 3 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 3 self.media_player.update() - assert "three" == self.media_player.source + assert self.media_player.source == "three" def test_setallzones_service_call_without_entity_id(self): """Test set all zone source service call without entity id.""" self.media_player.update() - assert "Zone name" == self.media_player.name - assert STATE_ON == self.media_player.state - assert "one" == self.media_player.source + assert self.media_player.name == "Zone name" + assert self.media_player.state == STATE_ON + assert self.media_player.source == "one" # Call set all zones service self.hass.services.call( @@ -249,9 +249,9 @@ def test_setallzones_service_call_without_entity_id(self): ) # Check that source was changed - assert 3 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 3 self.media_player.update() - assert "three" == self.media_player.source + assert self.media_player.source == "three" def test_update(self): """Test updating values from blackbird.""" @@ -260,23 +260,23 @@ def test_update(self): self.media_player.update() - assert STATE_ON == self.media_player.state - assert "one" == self.media_player.source + assert self.media_player.state == STATE_ON + assert self.media_player.source == "one" def test_name(self): """Test name property.""" - assert "Zone name" == self.media_player.name + assert self.media_player.name == "Zone name" def test_state(self): """Test state property.""" assert self.media_player.state is None self.media_player.update() - assert STATE_ON == self.media_player.state + assert self.media_player.state == STATE_ON self.blackbird.zones[3].power = False self.media_player.update() - assert STATE_OFF == self.media_player.state + assert self.media_player.state == STATE_OFF def test_supported_features(self): """Test supported features property.""" @@ -289,54 +289,54 @@ def test_source(self): """Test source property.""" assert self.media_player.source is None self.media_player.update() - assert "one" == self.media_player.source + assert self.media_player.source == "one" def test_media_title(self): """Test media title property.""" assert self.media_player.media_title is None self.media_player.update() - assert "one" == self.media_player.media_title + assert self.media_player.media_title == "one" def test_source_list(self): """Test source list property.""" # Note, the list is sorted! - assert ["one", "two", "three"] == self.media_player.source_list + assert self.media_player.source_list == ["one", "two", "three"] def test_select_source(self): """Test source selection methods.""" self.media_player.update() - assert "one" == self.media_player.source + assert self.media_player.source == "one" self.media_player.select_source("two") - assert 2 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 2 self.media_player.update() - assert "two" == self.media_player.source + assert self.media_player.source == "two" # Trying to set unknown source. self.media_player.select_source("no name") - assert 2 == self.blackbird.zones[3].av + assert self.blackbird.zones[3].av == 2 self.media_player.update() - assert "two" == self.media_player.source + assert self.media_player.source == "two" def test_turn_on(self): """Testing turning on the zone.""" self.blackbird.zones[3].power = False self.media_player.update() - assert STATE_OFF == self.media_player.state + assert self.media_player.state == STATE_OFF self.media_player.turn_on() assert self.blackbird.zones[3].power self.media_player.update() - assert STATE_ON == self.media_player.state + assert self.media_player.state == STATE_ON def test_turn_off(self): """Testing turning off the zone.""" self.blackbird.zones[3].power = True self.media_player.update() - assert STATE_ON == self.media_player.state + assert self.media_player.state == STATE_ON self.media_player.turn_off() assert not self.blackbird.zones[3].power self.media_player.update() - assert STATE_OFF == self.media_player.state + assert self.media_player.state == STATE_OFF diff --git a/tests/components/configurator/test_init.py b/tests/components/configurator/test_init.py index 8d116c301054b4..65701cbd139819 100644 --- a/tests/components/configurator/test_init.py +++ b/tests/components/configurator/test_init.py @@ -8,18 +8,18 @@ async def test_request_least_info(hass): """Test request config with least amount of data.""" request_id = configurator.async_request_config(hass, "Test Request", lambda _: None) - assert 1 == len( - hass.services.async_services().get(configurator.DOMAIN, []) + assert ( + len(hass.services.async_services().get(configurator.DOMAIN, [])) == 1 ), "No new service registered" states = hass.states.async_all() - assert 1 == len(states), "Expected a new state registered" + assert len(states) == 1, "Expected a new state registered" state = states[0] - assert configurator.STATE_CONFIGURE == state.state - assert request_id == state.attributes.get(configurator.ATTR_CONFIGURE_ID) + assert state.state == configurator.STATE_CONFIGURE + assert state.attributes.get(configurator.ATTR_CONFIGURE_ID) == request_id async def test_request_all_info(hass): @@ -49,11 +49,11 @@ async def test_request_all_info(hass): } states = hass.states.async_all() - assert 1 == len(states) + assert len(states) == 1 state = states[0] - assert configurator.STATE_CONFIGURE == state.state - assert exp_attr == state.attributes + assert state.state == configurator.STATE_CONFIGURE + assert state.attributes == exp_attr async def test_callback_called_on_configure(hass): @@ -70,7 +70,7 @@ async def test_callback_called_on_configure(hass): ) await hass.async_block_till_done() - assert 1 == len(calls), "Callback not called" + assert len(calls) == 1, "Callback not called" async def test_state_change_on_notify_errors(hass): @@ -80,9 +80,9 @@ async def test_state_change_on_notify_errors(hass): configurator.async_notify_errors(hass, request_id, error) states = hass.states.async_all() - assert 1 == len(states) + assert len(states) == 1 state = states[0] - assert error == state.attributes.get(configurator.ATTR_ERRORS) + assert state.attributes.get(configurator.ATTR_ERRORS) == error async def test_notify_errors_fail_silently_on_bad_request_id(hass): @@ -94,11 +94,11 @@ async def test_request_done_works(hass): """Test if calling request done works.""" request_id = configurator.async_request_config(hass, "Test Request", lambda _: None) configurator.async_request_done(hass, request_id) - assert 1 == len(hass.states.async_all()) + assert len(hass.states.async_all()) == 1 hass.bus.async_fire(EVENT_TIME_CHANGED) await hass.async_block_till_done() - assert 0 == len(hass.states.async_all()) + assert len(hass.states.async_all()) == 0 async def test_request_done_fail_silently_on_bad_request_id(hass): diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index 7e5859497c52c9..107dd97924d554 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -114,16 +114,16 @@ async def test_config_options(hass): assert state_2 is not None assert state_3 is not None - assert 0 == int(state_1.state) + assert int(state_1.state) == 0 assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert 10 == int(state_2.state) - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert int(state_2.state) == 10 + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" - assert DEFAULT_INITIAL == state_3.attributes.get(ATTR_INITIAL) - assert DEFAULT_STEP == state_3.attributes.get(ATTR_STEP) + assert state_3.attributes.get(ATTR_INITIAL) == DEFAULT_INITIAL + assert state_3.attributes.get(ATTR_STEP) == DEFAULT_STEP async def test_methods(hass): @@ -135,31 +135,31 @@ async def test_methods(hass): entity_id = "counter.test_1" state = hass.states.get(entity_id) - assert 0 == int(state.state) + assert int(state.state) == 0 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 1 == int(state.state) + assert int(state.state) == 1 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 2 == int(state.state) + assert int(state.state) == 2 async_decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 1 == int(state.state) + assert int(state.state) == 1 async_reset(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 0 == int(state.state) + assert int(state.state) == 0 async def test_methods_with_config(hass): @@ -173,25 +173,25 @@ async def test_methods_with_config(hass): entity_id = "counter.test" state = hass.states.get(entity_id) - assert 10 == int(state.state) + assert int(state.state) == 10 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 15 == int(state.state) + assert int(state.state) == 15 async_increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 20 == int(state.state) + assert int(state.state) == 20 async_decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 15 == int(state.state) + assert int(state.state) == 15 async def test_initial_state_overrules_restore_state(hass): @@ -370,7 +370,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "10" - assert 10 == state.attributes.get("maximum") + assert state.attributes.get("maximum") == 10 # update max await hass.services.async_call( @@ -384,7 +384,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "0" - assert 0 == state.attributes.get("maximum") + assert state.attributes.get("maximum") == 0 # disable max await hass.services.async_call( @@ -413,7 +413,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "5" - assert 5 == state.attributes.get("minimum") + assert state.attributes.get("minimum") == 5 # disable min await hass.services.async_call( @@ -430,7 +430,7 @@ async def test_configure(hass, hass_admin_user): assert state.attributes.get("minimum") is None # update step - assert 1 == state.attributes.get("step") + assert state.attributes.get("step") == 1 await hass.services.async_call( "counter", "configure", @@ -442,7 +442,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "5" - assert 3 == state.attributes.get("step") + assert state.attributes.get("step") == 3 # update value await hass.services.async_call( @@ -469,7 +469,7 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "6" - assert 5 == state.attributes.get("initial") + assert state.attributes.get("initial") == 5 # update all await hass.services.async_call( @@ -490,10 +490,10 @@ async def test_configure(hass, hass_admin_user): state = hass.states.get("counter.test") assert state is not None assert state.state == "5" - assert 5 == state.attributes.get("step") - assert 0 == state.attributes.get("minimum") - assert 9 == state.attributes.get("maximum") - assert 6 == state.attributes.get("initial") + assert state.attributes.get("step") == 5 + assert state.attributes.get("minimum") == 0 + assert state.attributes.get("maximum") == 9 + assert state.attributes.get("initial") == 6 async def test_load_from_storage(hass, storage_setup): diff --git a/tests/components/datadog/test_init.py b/tests/components/datadog/test_init.py index 087e0f4b8846cc..13d99289bb8056 100644 --- a/tests/components/datadog/test_init.py +++ b/tests/components/datadog/test_init.py @@ -37,8 +37,8 @@ async def test_datadog_setup_full(hass): assert mock_init.call_args == mock.call(statsd_host="host", statsd_port=123) assert hass.bus.listen.called - assert EVENT_LOGBOOK_ENTRY == hass.bus.listen.call_args_list[0][0][0] - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[1][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_LOGBOOK_ENTRY + assert hass.bus.listen.call_args_list[1][0][0] == EVENT_STATE_CHANGED async def test_datadog_setup_defaults(hass): diff --git a/tests/components/demo/test_climate.py b/tests/components/demo/test_climate.py index aa6ff39cb0edf5..2f4317c49c5cbb 100644 --- a/tests/components/demo/test_climate.py +++ b/tests/components/demo/test_climate.py @@ -69,7 +69,7 @@ def test_setup_params(hass): assert state.attributes.get(ATTR_HUMIDITY) == 67 assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 54 assert state.attributes.get(ATTR_SWING_MODE) == "Off" - assert STATE_OFF == state.attributes.get(ATTR_AUX_HEAT) + assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ "off", "heat", diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index c7aba405ccd774..d62e46255d7218 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -251,7 +251,7 @@ async def test_update_stale(hass, mock_device_tracker_conf): ) await hass.async_block_till_done() - assert STATE_HOME == hass.states.get("device_tracker.dev1").state + assert hass.states.get("device_tracker.dev1").state == STATE_HOME scanner.leave_home("DEV1") @@ -262,7 +262,7 @@ async def test_update_stale(hass, mock_device_tracker_conf): async_fire_time_changed(hass, scan_time) await hass.async_block_till_done() - assert STATE_NOT_HOME == hass.states.get("device_tracker.dev1").state + assert hass.states.get("device_tracker.dev1").state == STATE_NOT_HOME async def test_entity_attributes(hass, mock_device_tracker_conf): @@ -474,7 +474,7 @@ async def test_see_passive_zone_state(hass, mock_device_tracker_conf): state = hass.states.get("device_tracker.dev1") attrs = state.attributes - assert STATE_HOME == state.state + assert state.state == STATE_HOME assert state.object_id == "dev1" assert state.name == "dev1" assert attrs.get("friendly_name") == "dev1" @@ -494,7 +494,7 @@ async def test_see_passive_zone_state(hass, mock_device_tracker_conf): state = hass.states.get("device_tracker.dev1") attrs = state.attributes - assert STATE_NOT_HOME == state.state + assert state.state == STATE_NOT_HOME assert state.object_id == "dev1" assert state.name == "dev1" assert attrs.get("friendly_name") == "dev1" diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 92ad310f68d03f..95b4b290b70a48 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -117,9 +117,9 @@ async def test_fan(ecobee_fixture, thermostat): """Test fan property.""" assert const.STATE_ON == thermostat.fan ecobee_fixture["equipmentStatus"] = "" - assert STATE_OFF == thermostat.fan + assert thermostat.fan == STATE_OFF ecobee_fixture["equipmentStatus"] = "heatPump, heatPump2" - assert STATE_OFF == thermostat.fan + assert thermostat.fan == STATE_OFF async def test_hvac_mode(ecobee_fixture, thermostat): diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index 3dfbfc354f8756..a56e28cb3ef01f 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -63,11 +63,11 @@ async def test_single_sensor_readings(hass, requests_mock): assert await async_setup_component(hass, "sensor", {"sensor": ONE_SENSOR_CONFIG}) await hass.async_block_till_done() - assert "38.21" == hass.states.get("sensor.energy_consumed").state - assert "1580" == hass.states.get("sensor.energy_usage").state - assert "ok" == hass.states.get("sensor.energy_budget").state - assert "5.27" == hass.states.get("sensor.energy_cost").state - assert "1628" == hass.states.get("sensor.efergy_728386").state + assert hass.states.get("sensor.energy_consumed").state == "38.21" + assert hass.states.get("sensor.energy_usage").state == "1580" + assert hass.states.get("sensor.energy_budget").state == "ok" + assert hass.states.get("sensor.energy_cost").state == "5.27" + assert hass.states.get("sensor.efergy_728386").state == "1628" async def test_multi_sensor_readings(hass, requests_mock): @@ -76,6 +76,6 @@ async def test_multi_sensor_readings(hass, requests_mock): assert await async_setup_component(hass, "sensor", {"sensor": MULTI_SENSOR_CONFIG}) await hass.async_block_till_done() - assert "218" == hass.states.get("sensor.efergy_728386").state - assert "1808" == hass.states.get("sensor.efergy_0").state - assert "312" == hass.states.get("sensor.efergy_728387").state + assert hass.states.get("sensor.efergy_728386").state == "218" + assert hass.states.get("sensor.efergy_0").state == "1808" + assert hass.states.get("sensor.efergy_728387").state == "312" diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 3c322c0b613a87..f10786d36de7f1 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -363,7 +363,7 @@ def mock_service_call(call): call = turn_off_calls[-1] assert light.DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service + assert call.service == SERVICE_TURN_OFF assert "light.no_brightness" in call.data[ATTR_ENTITY_ID] @@ -401,11 +401,11 @@ def mock_service_call(call): # Verify that SERVICE_TURN_ON has been called await hass_hue.async_block_till_done() - assert 1 == len(turn_on_calls) + assert len(turn_on_calls) == 1 call = turn_on_calls[-1] assert light.DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service + assert call.service == SERVICE_TURN_ON assert "light.no_brightness" in call.data[ATTR_ENTITY_ID] diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 417c84e8ea4f9b..b787fc0235c984 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -75,7 +75,7 @@ async def test_chain(hass, values): await hass.async_block_till_done() state = hass.states.get("sensor.test") - assert "18.05" == state.state + assert state.state == "18.05" async def test_chain_history(hass, values, missing=False): @@ -131,9 +131,9 @@ async def test_chain_history(hass, values, missing=False): state = hass.states.get("sensor.test") if missing: - assert "18.05" == state.state + assert state.state == "18.05" else: - assert "17.05" == state.state + assert state.state == "17.05" async def test_source_state_none(hass, values): @@ -245,7 +245,7 @@ async def test_history_time(hass): await hass.async_block_till_done() state = hass.states.get("sensor.test") - assert "18.0" == state.state + assert state.state == "18.0" async def test_setup(hass): @@ -316,7 +316,7 @@ async def test_outlier(values): filt = OutlierFilter(window_size=3, precision=2, entity=None, radius=4.0) for state in values: filtered = filt.filter_state(state) - assert 21 == filtered.state + assert filtered.state == 21 def test_outlier_step(values): @@ -331,7 +331,7 @@ def test_outlier_step(values): values[-1].state = 22 for state in values: filtered = filt.filter_state(state) - assert 22 == filtered.state + assert filtered.state == 22 def test_initial_outlier(values): @@ -340,7 +340,7 @@ def test_initial_outlier(values): out = ha.State("sensor.test_monitored", 4000) for state in [out] + values: filtered = filt.filter_state(state) - assert 21 == filtered.state + assert filtered.state == 21 def test_unknown_state_outlier(values): @@ -352,7 +352,7 @@ def test_unknown_state_outlier(values): filtered = filt.filter_state(state) except ValueError: assert state.state == "unknown" - assert 21 == filtered.state + assert filtered.state == 21 def test_precision_zero(values): @@ -372,7 +372,7 @@ def test_lowpass(values): filtered = filt.filter_state(state) except ValueError: assert state.state == "unknown" - assert 18.05 == filtered.state + assert filtered.state == 18.05 def test_range(values): @@ -438,7 +438,7 @@ def test_time_sma(values): ) for state in values: filtered = filt.filter_state(state) - assert 21.5 == filtered.state + assert filtered.state == 21.5 async def test_reload(hass): diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index 0d4e4f0595ee20..7438b690ab5e45 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -140,7 +140,7 @@ async def test_flux_when_switch_is_off(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -191,7 +191,7 @@ async def test_flux_before_sunrise(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -250,7 +250,7 @@ async def test_flux_before_sunrise_known_location(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -309,7 +309,7 @@ async def test_flux_after_sunrise_before_sunset(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -368,7 +368,7 @@ async def test_flux_after_sunset_before_stop(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -428,7 +428,7 @@ async def test_flux_after_stop_before_sunrise(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -487,7 +487,7 @@ async def test_flux_with_custom_start_stop_times(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -550,7 +550,7 @@ async def test_flux_before_sunrise_stop_next_day(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -616,7 +616,7 @@ async def test_flux_after_sunrise_before_sunset_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -682,7 +682,7 @@ async def test_flux_after_sunset_before_midnight_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -747,7 +747,7 @@ async def test_flux_after_sunset_after_midnight_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -812,7 +812,7 @@ async def test_flux_after_stop_before_sunrise_stop_next_day( # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -872,7 +872,7 @@ async def test_flux_with_custom_colortemps(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -934,7 +934,7 @@ async def test_flux_with_custom_brightness(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -1001,17 +1001,17 @@ async def test_flux_with_multiple_lights(hass, legacy_patchable_time): await hass.async_block_till_done() state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None state = hass.states.get(ent2.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None state = hass.states.get(ent3.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("xy_color") is None assert state.attributes.get("brightness") is None @@ -1077,7 +1077,7 @@ async def test_flux_with_mired(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("color_temp") is None test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) @@ -1134,7 +1134,7 @@ async def test_flux_with_rgb(hass, legacy_patchable_time): # Verify initial state of light state = hass.states.get(ent1.entity_id) - assert STATE_ON == state.state + assert state.state == STATE_ON assert state.attributes.get("color_temp") is None test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index dc5353971b7aae..c2c1435464e349 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -116,13 +116,13 @@ async def test_heater_input_boolean(hass, setup_comp_1): ) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_OFF _setup_sensor(hass, 18) await hass.async_block_till_done() await common.async_set_temperature(hass, 23) - assert STATE_ON == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_ON async def test_heater_switch(hass, setup_comp_1): @@ -151,13 +151,13 @@ async def test_heater_switch(hass, setup_comp_1): ) await hass.async_block_till_done() - assert STATE_OFF == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_OFF _setup_sensor(hass, 18) await common.async_set_temperature(hass, 23) await hass.async_block_till_done() - assert STATE_ON == hass.states.get(heater_switch).state + assert hass.states.get(heater_switch).state == STATE_ON async def test_unique_id(hass, setup_comp_1): @@ -234,7 +234,7 @@ async def test_setup_defaults_to_unknown(hass): }, ) await hass.async_block_till_done() - assert HVAC_MODE_OFF == hass.states.get(ENTITY).state + assert hass.states.get(ENTITY).state == HVAC_MODE_OFF async def test_setup_gets_current_temp_from_sensor(hass): @@ -264,27 +264,27 @@ async def test_setup_gets_current_temp_from_sensor(hass): async def test_default_setup_params(hass, setup_comp_2): """Test the setup with default parameters.""" state = hass.states.get(ENTITY) - assert 7 == state.attributes.get("min_temp") - assert 35 == state.attributes.get("max_temp") - assert 7 == state.attributes.get("temperature") + assert state.attributes.get("min_temp") == 7 + assert state.attributes.get("max_temp") == 35 + assert state.attributes.get("temperature") == 7 async def test_get_hvac_modes(hass, setup_comp_2): """Test that the operation list returns the correct modes.""" state = hass.states.get(ENTITY) modes = state.attributes.get("hvac_modes") - assert [HVAC_MODE_HEAT, HVAC_MODE_OFF] == modes + assert modes == [HVAC_MODE_HEAT, HVAC_MODE_OFF] async def test_set_target_temp(hass, setup_comp_2): """Test the setting of the target temperature.""" await common.async_set_temperature(hass, 30) state = hass.states.get(ENTITY) - assert 30.0 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 30.0 with pytest.raises(vol.Invalid): await common.async_set_temperature(hass, None) state = hass.states.get(ENTITY) - assert 30.0 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 30.0 async def test_set_away_mode(hass, setup_comp_2): @@ -292,7 +292,7 @@ async def test_set_away_mode(hass, setup_comp_2): await common.async_set_temperature(hass, 23) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 16 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 16 async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2): @@ -303,10 +303,10 @@ async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2): await common.async_set_temperature(hass, 23) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 16 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 16 await common.async_set_preset_mode(hass, PRESET_NONE) state = hass.states.get(ENTITY) - assert 23 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 23 async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2): @@ -318,10 +318,10 @@ async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2): await common.async_set_preset_mode(hass, PRESET_AWAY) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 16 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 16 await common.async_set_preset_mode(hass, PRESET_NONE) state = hass.states.get(ENTITY) - assert 23 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 23 async def test_sensor_bad_value(hass, setup_comp_2): @@ -382,11 +382,11 @@ async def test_set_target_temp_heater_on(hass, setup_comp_2): _setup_sensor(hass, 25) await hass.async_block_till_done() await common.async_set_temperature(hass, 30) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_set_target_temp_heater_off(hass, setup_comp_2): @@ -395,11 +395,11 @@ async def test_set_target_temp_heater_off(hass, setup_comp_2): _setup_sensor(hass, 30) await hass.async_block_till_done() await common.async_set_temperature(hass, 25) - assert 2 == len(calls) + assert len(calls) == 2 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_on_within_tolerance(hass, setup_comp_2): @@ -408,7 +408,7 @@ async def test_temp_change_heater_on_within_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 29) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_on_outside_tolerance(hass, setup_comp_2): @@ -417,11 +417,11 @@ async def test_temp_change_heater_on_outside_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 27) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_off_within_tolerance(hass, setup_comp_2): @@ -430,7 +430,7 @@ async def test_temp_change_heater_off_within_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 33) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_off_outside_tolerance(hass, setup_comp_2): @@ -439,11 +439,11 @@ async def test_temp_change_heater_off_outside_tolerance(hass, setup_comp_2): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 35) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_running_when_hvac_mode_is_off(hass, setup_comp_2): @@ -451,11 +451,11 @@ async def test_running_when_hvac_mode_is_off(hass, setup_comp_2): calls = _setup_switch(hass, True) await common.async_set_temperature(hass, 30) await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_no_state_change_when_hvac_mode_off(hass, setup_comp_2): @@ -465,7 +465,7 @@ async def test_no_state_change_when_hvac_mode_off(hass, setup_comp_2): await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_hvac_mode_heat(hass, setup_comp_2): @@ -479,11 +479,11 @@ async def test_hvac_mode_heat(hass, setup_comp_2): await hass.async_block_till_done() calls = _setup_switch(hass, False) await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH def _setup_switch(hass, is_on): @@ -532,11 +532,11 @@ async def test_set_target_temp_ac_off(hass, setup_comp_3): _setup_sensor(hass, 25) await hass.async_block_till_done() await common.async_set_temperature(hass, 30) - assert 2 == len(calls) + assert len(calls) == 2 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_turn_away_mode_on_cooling(hass, setup_comp_3): @@ -547,7 +547,7 @@ async def test_turn_away_mode_on_cooling(hass, setup_comp_3): await common.async_set_temperature(hass, 19) await common.async_set_preset_mode(hass, PRESET_AWAY) state = hass.states.get(ENTITY) - assert 30 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 30 async def test_hvac_mode_cool(hass, setup_comp_3): @@ -561,11 +561,11 @@ async def test_hvac_mode_cool(hass, setup_comp_3): await hass.async_block_till_done() calls = _setup_switch(hass, False) await common.async_set_hvac_mode(hass, HVAC_MODE_COOL) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_set_target_temp_ac_on(hass, setup_comp_3): @@ -574,11 +574,11 @@ async def test_set_target_temp_ac_on(hass, setup_comp_3): _setup_sensor(hass, 30) await hass.async_block_till_done() await common.async_set_temperature(hass, 25) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_off_within_tolerance(hass, setup_comp_3): @@ -587,7 +587,7 @@ async def test_temp_change_ac_off_within_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 29.8) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_set_temp_change_ac_off_outside_tolerance(hass, setup_comp_3): @@ -596,11 +596,11 @@ async def test_set_temp_change_ac_off_outside_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 27) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_on_within_tolerance(hass, setup_comp_3): @@ -609,7 +609,7 @@ async def test_temp_change_ac_on_within_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 25.2) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_on_outside_tolerance(hass, setup_comp_3): @@ -618,11 +618,11 @@ async def test_temp_change_ac_on_outside_tolerance(hass, setup_comp_3): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_running_when_operating_mode_is_off_2(hass, setup_comp_3): @@ -630,11 +630,11 @@ async def test_running_when_operating_mode_is_off_2(hass, setup_comp_3): calls = _setup_switch(hass, True) await common.async_set_temperature(hass, 30) await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3): @@ -644,7 +644,7 @@ async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3): await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) _setup_sensor(hass, 35) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 @pytest.fixture @@ -677,7 +677,7 @@ async def test_temp_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): @@ -692,11 +692,11 @@ async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): @@ -705,7 +705,7 @@ async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): @@ -720,11 +720,11 @@ async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): @@ -733,13 +733,13 @@ async def test_mode_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): @@ -748,13 +748,13 @@ async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -787,7 +787,7 @@ async def test_temp_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): @@ -802,11 +802,11 @@ async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): @@ -815,7 +815,7 @@ async def test_temp_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): @@ -830,11 +830,11 @@ async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): @@ -843,13 +843,13 @@ async def test_mode_change_ac_trigger_off_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): @@ -858,13 +858,13 @@ async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -896,7 +896,7 @@ async def test_temp_change_heater_trigger_off_not_long_enough(hass, setup_comp_6 await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_trigger_on_not_long_enough(hass, setup_comp_6): @@ -905,7 +905,7 @@ async def test_temp_change_heater_trigger_on_not_long_enough(hass, setup_comp_6) await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): @@ -920,11 +920,11 @@ async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): @@ -939,11 +939,11 @@ async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_heater_trigger_off_not_long_enough(hass, setup_comp_6): @@ -952,13 +952,13 @@ async def test_mode_change_heater_trigger_off_not_long_enough(hass, setup_comp_6 await common.async_set_temperature(hass, 25) _setup_sensor(hass, 30) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_OFF) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH async def test_mode_change_heater_trigger_on_not_long_enough(hass, setup_comp_6): @@ -967,13 +967,13 @@ async def test_mode_change_heater_trigger_on_not_long_enough(hass, setup_comp_6) await common.async_set_temperature(hass, 30) _setup_sensor(hass, 25) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert "homeassistant" == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == "homeassistant" + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -1013,17 +1013,17 @@ async def test_temp_change_ac_trigger_on_long_enough_3(hass, setup_comp_7): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_ac_trigger_off_long_enough_3(hass, setup_comp_7): @@ -1036,17 +1036,17 @@ async def test_temp_change_ac_trigger_off_long_enough_3(hass, setup_comp_7): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -1084,17 +1084,17 @@ async def test_temp_change_heater_trigger_on_long_enough_2(hass, setup_comp_8): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_ON + assert call.data["entity_id"] == ENT_SWITCH async def test_temp_change_heater_trigger_off_long_enough_2(hass, setup_comp_8): @@ -1107,17 +1107,17 @@ async def test_temp_change_heater_trigger_off_long_enough_2(hass, setup_comp_8): test_time = datetime.datetime.now(pytz.UTC) async_fire_time_changed(hass, test_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, test_time + datetime.timedelta(minutes=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 call = calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data["entity_id"] + assert call.domain == HASS_DOMAIN + assert call.service == SERVICE_TURN_OFF + assert call.data["entity_id"] == ENT_SWITCH @pytest.fixture @@ -1149,7 +1149,7 @@ async def test_precision(hass, setup_comp_9): """Test that setting precision to tenths works as intended.""" await common.async_set_temperature(hass, 23.27) state = hass.states.get(ENTITY) - assert 23.3 == state.attributes.get("temperature") + assert state.attributes.get("temperature") == 23.3 async def test_custom_setup_params(hass): @@ -1350,14 +1350,14 @@ async def test_restore_state_uncoherence_case(hass): await hass.async_block_till_done() state = hass.states.get(ENTITY) - assert 20 == state.attributes[ATTR_TEMPERATURE] - assert HVAC_MODE_OFF == state.state - assert 0 == len(calls) + assert state.attributes[ATTR_TEMPERATURE] == 20 + assert state.state == HVAC_MODE_OFF + assert len(calls) == 0 calls = _setup_switch(hass, False) await hass.async_block_till_done() state = hass.states.get(ENTITY) - assert HVAC_MODE_OFF == state.state + assert state.state == HVAC_MODE_OFF async def _setup_climate(hass): diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index 92a2f5b19f2d48..b84b6b681ae221 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -196,7 +196,7 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(GPS_ENTER_HOME["device"]) state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Exit the Home zone req = await geofency_client.post(url, data=GPS_EXIT_HOME) @@ -204,7 +204,7 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(GPS_EXIT_HOME["device"]) state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME # Exit the Home zone with "Send Current Position" enabled data = GPS_EXIT_HOME.copy() @@ -218,11 +218,11 @@ async def test_gps_enter_and_exit_home(hass, geofency_client, webhook_id): current_latitude = hass.states.get(f"device_tracker.{device_name}").attributes[ "latitude" ] - assert NOT_HOME_LATITUDE == current_latitude + assert current_latitude == NOT_HOME_LATITUDE current_longitude = hass.states.get(f"device_tracker.{device_name}").attributes[ "longitude" ] - assert NOT_HOME_LONGITUDE == current_longitude + assert current_longitude == NOT_HOME_LONGITUDE dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 @@ -241,7 +241,7 @@ async def test_beacon_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_HOME['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Exit the Home zone req = await geofency_client.post(url, data=BEACON_EXIT_HOME) @@ -249,7 +249,7 @@ async def test_beacon_enter_and_exit_home(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_HOME['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): @@ -262,7 +262,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_CAR['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME # Exit the Car away from Home zone req = await geofency_client.post(url, data=BEACON_EXIT_CAR) @@ -270,7 +270,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{BEACON_ENTER_CAR['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME # Enter the Car in the Home zone data = BEACON_ENTER_CAR.copy() @@ -281,7 +281,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{data['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Exit the Car in the Home zone req = await geofency_client.post(url, data=data) @@ -289,7 +289,7 @@ async def test_beacon_enter_and_exit_car(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(f"beacon_{data['name']}") state_name = hass.states.get(f"device_tracker.{device_name}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME async def test_load_unload_entry(hass, geofency_client, webhook_id): @@ -302,7 +302,7 @@ async def test_load_unload_entry(hass, geofency_client, webhook_id): assert req.status == HTTP_OK device_name = slugify(GPS_ENTER_HOME["device"]) state_1 = hass.states.get(f"device_tracker.{device_name}") - assert STATE_HOME == state_1.state + assert state_1.state == STATE_HOME assert len(hass.data[DOMAIN]["devices"]) == 1 entry = hass.config_entries.async_entries(DOMAIN)[0] @@ -318,6 +318,6 @@ async def test_load_unload_entry(hass, geofency_client, webhook_id): assert state_2 is not None assert state_1 is not state_2 - assert STATE_HOME == state_2.state + assert state_2.state == STATE_HOME assert state_2.attributes[ATTR_LATITUDE] == HOME_LATITUDE assert state_2.attributes[ATTR_LONGITUDE] == HOME_LONGITUDE diff --git a/tests/components/google_pubsub/test_init.py b/tests/components/google_pubsub/test_init.py index c174e45470100f..fc1fecb04ed4d3 100644 --- a/tests/components/google_pubsub/test_init.py +++ b/tests/components/google_pubsub/test_init.py @@ -82,7 +82,7 @@ async def test_minimal_config(hass, mock_client): assert await async_setup_component(hass, google_pubsub.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert mock_client.PublisherClient.from_service_account_json.call_count == 1 assert ( mock_client.PublisherClient.from_service_account_json.call_args[0][0] == "path" @@ -109,7 +109,7 @@ async def test_full_config(hass, mock_client): assert await async_setup_component(hass, google_pubsub.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert mock_client.PublisherClient.from_service_account_json.call_count == 1 assert ( mock_client.PublisherClient.from_service_account_json.call_args[0][0] == "path" diff --git a/tests/components/google_wifi/test_sensor.py b/tests/components/google_wifi/test_sensor.py index 06ad5e0c3ea087..6f4b4652e76c8e 100644 --- a/tests/components/google_wifi/test_sensor.py +++ b/tests/components/google_wifi/test_sensor.py @@ -129,13 +129,13 @@ def test_state(hass, requests_mock): fake_delay(hass, 2) sensor.update() if name == google_wifi.ATTR_LAST_RESTART: - assert "1969-12-31 00:00:00" == sensor.state + assert sensor.state == "1969-12-31 00:00:00" elif name == google_wifi.ATTR_UPTIME: - assert 1 == sensor.state + assert sensor.state == 1 elif name == google_wifi.ATTR_STATUS: - assert "Online" == sensor.state + assert sensor.state == "Online" else: - assert "initial" == sensor.state + assert sensor.state == "initial" def test_update_when_value_is_none(hass, requests_mock): @@ -158,17 +158,17 @@ def test_update_when_value_changed(hass, requests_mock): fake_delay(hass, 2) sensor.update() if name == google_wifi.ATTR_LAST_RESTART: - assert "1969-12-30 00:00:00" == sensor.state + assert sensor.state == "1969-12-30 00:00:00" elif name == google_wifi.ATTR_UPTIME: - assert 2 == sensor.state + assert sensor.state == 2 elif name == google_wifi.ATTR_STATUS: - assert "Offline" == sensor.state + assert sensor.state == "Offline" elif name == google_wifi.ATTR_NEW_VERSION: - assert "Latest" == sensor.state + assert sensor.state == "Latest" elif name == google_wifi.ATTR_LOCAL_IP: - assert STATE_UNKNOWN == sensor.state + assert sensor.state == STATE_UNKNOWN else: - assert "next" == sensor.state + assert sensor.state == "next" def test_when_api_data_missing(hass, requests_mock): @@ -180,7 +180,7 @@ def test_when_api_data_missing(hass, requests_mock): sensor = sensor_dict[name]["sensor"] fake_delay(hass, 2) sensor.update() - assert STATE_UNKNOWN == sensor.state + assert sensor.state == STATE_UNKNOWN def test_update_when_unavailable(requests_mock): diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py index 8a4880c4878adf..1dad262a285672 100644 --- a/tests/components/gpslogger/test_init.py +++ b/tests/components/gpslogger/test_init.py @@ -117,14 +117,14 @@ async def test_enter_and_exit(hass, gpslogger_client, webhook_id): await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Enter Home again req = await gpslogger_client.post(url, data=data) await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME data["longitude"] = 0 data["latitude"] = 0 @@ -134,7 +134,7 @@ async def test_enter_and_exit(hass, gpslogger_client, webhook_id): await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 @@ -213,7 +213,7 @@ async def test_load_unload_entry(hass, gpslogger_client, webhook_id): await hass.async_block_till_done() assert req.status == HTTP_OK state_name = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device']}").state - assert STATE_HOME == state_name + assert state_name == STATE_HOME assert len(hass.data[DATA_DISPATCHER][TRACKER_UPDATE]) == 1 entry = hass.config_entries.async_entries(DOMAIN)[0] diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index d68fdd7f717c54..fff1526b7119f3 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -38,7 +38,7 @@ async def test_setup_group_with_mixed_groupable_states(hass): await hass.async_block_till_done() - assert STATE_ON == hass.states.get(f"{group.DOMAIN}.person_and_light").state + assert hass.states.get(f"{group.DOMAIN}.person_and_light").state == STATE_ON async def test_setup_group_with_a_non_existing_state(hass): @@ -51,7 +51,7 @@ async def test_setup_group_with_a_non_existing_state(hass): hass, "light_and_nothing", ["light.Bowl", "non.existing"] ) - assert STATE_ON == grp.state + assert grp.state == STATE_ON async def test_setup_group_with_non_groupable_states(hass): @@ -90,7 +90,7 @@ async def test_monitor_group(hass): assert test_group.entity_id in hass.states.async_entity_ids() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON assert group_state.attributes.get(group.ATTR_AUTO) @@ -108,7 +108,7 @@ async def test_group_turns_off_if_all_off(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_OFF == group_state.state + assert group_state.state == STATE_OFF async def test_group_turns_on_if_all_are_off_and_one_turns_on(hass): @@ -127,7 +127,7 @@ async def test_group_turns_on_if_all_are_off_and_one_turns_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON async def test_allgroup_stays_off_if_all_are_off_and_one_turns_on(hass): @@ -146,7 +146,7 @@ async def test_allgroup_stays_off_if_all_are_off_and_one_turns_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_OFF == group_state.state + assert group_state.state == STATE_OFF async def test_allgroup_turn_on_if_last_turns_on(hass): @@ -165,7 +165,7 @@ async def test_allgroup_turn_on_if_last_turns_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON async def test_expand_entity_ids(hass): @@ -287,7 +287,7 @@ async def test_group_being_init_before_first_tracked_state_is_set_to_on(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON async def test_group_being_init_before_first_tracked_state_is_set_to_off(hass): @@ -306,7 +306,7 @@ async def test_group_being_init_before_first_tracked_state_is_set_to_off(hass): await hass.async_block_till_done() group_state = hass.states.get(test_group.entity_id) - assert STATE_OFF == group_state.state + assert group_state.state == STATE_OFF async def test_groups_get_unique_names(hass): @@ -385,7 +385,7 @@ async def test_group_updated_after_device_tracker_zone_change(hass): hass.states.async_set("device_tracker.Adam", "cool_state_not_home") await hass.async_block_till_done() - assert STATE_NOT_HOME == hass.states.get(f"{group.DOMAIN}.peeps").state + assert hass.states.get(f"{group.DOMAIN}.peeps").state == STATE_NOT_HOME async def test_is_on(hass): @@ -517,20 +517,20 @@ async def test_setup(hass): await hass.async_block_till_done() group_state = hass.states.get(f"{group.DOMAIN}.created_group") - assert STATE_ON == group_state.state + assert group_state.state == STATE_ON assert {test_group.entity_id, "light.bowl"} == set( group_state.attributes["entity_id"] ) assert group_state.attributes.get(group.ATTR_AUTO) is None - assert "mdi:work" == group_state.attributes.get(ATTR_ICON) - assert 3 == group_state.attributes.get(group.ATTR_ORDER) + assert group_state.attributes.get(ATTR_ICON) == "mdi:work" + assert group_state.attributes.get(group.ATTR_ORDER) == 3 group_state = hass.states.get(f"{group.DOMAIN}.test_group") - assert STATE_UNKNOWN == group_state.state - assert {"sensor.happy", "hello.world"} == set(group_state.attributes["entity_id"]) + assert group_state.state == STATE_UNKNOWN + assert set(group_state.attributes["entity_id"]) == {"sensor.happy", "hello.world"} assert group_state.attributes.get(group.ATTR_AUTO) is None assert group_state.attributes.get(ATTR_ICON) is None - assert 0 == group_state.attributes.get(group.ATTR_ORDER) + assert group_state.attributes.get(group.ATTR_ORDER) == 0 async def test_service_group_services(hass): diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 51646ae71391ee..2e2eaf991afd2d 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -137,28 +137,28 @@ def test_turn_on_without_entities(self): calls = mock_service(self.hass, "light", SERVICE_TURN_ON) turn_on(self.hass) self.hass.block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 def test_turn_on(self): """Test turn_on method.""" calls = mock_service(self.hass, "light", SERVICE_TURN_ON) turn_on(self.hass, "light.Ceiling") self.hass.block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 def test_turn_off(self): """Test turn_off method.""" calls = mock_service(self.hass, "light", SERVICE_TURN_OFF) turn_off(self.hass, "light.Bowl") self.hass.block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 def test_toggle(self): """Test toggle method.""" calls = mock_service(self.hass, "light", SERVICE_TOGGLE) toggle(self.hass, "light.Bowl") self.hass.block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 @patch("homeassistant.config.os.path.isfile", Mock(return_value=True)) def test_reload_core_conf(self): diff --git a/tests/components/homeassistant/triggers/test_state.py b/tests/components/homeassistant/triggers/test_state.py index 2cf2081f018621..14120fe94df1da 100644 --- a/tests/components/homeassistant/triggers/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -506,7 +506,7 @@ async def test_if_fires_on_entity_change_with_for(hass, calls): await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_for_without_to(hass, calls): diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index ee107d4985e8d5..d97bbc86ed601a 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -181,7 +181,7 @@ def test_eu_setup_full_config(self, mock_round, mock_evo): mock.call(mock_evo.return_value, "bar", False, 20.0), ] ) - assert 2 == add_entities.call_count + assert add_entities.call_count == 2 @mock.patch("evohomeclient.EvohomeClient") @mock.patch("homeassistant.components.honeywell.climate.HoneywellUSThermostat") @@ -269,15 +269,15 @@ def fake_temperatures(force_refresh=None): def test_attributes(self): """Test the attributes.""" - assert "House" == self.round1.name - assert TEMP_CELSIUS == self.round1.temperature_unit - assert 20 == self.round1.current_temperature - assert 21 == self.round1.target_temperature + assert self.round1.name == "House" + assert self.round1.temperature_unit == TEMP_CELSIUS + assert self.round1.current_temperature == 20 + assert self.round1.target_temperature == 21 assert not self.round1.is_away_mode_on - assert "Hot Water" == self.round2.name - assert TEMP_CELSIUS == self.round2.temperature_unit - assert 21 == self.round2.current_temperature + assert self.round2.name == "Hot Water" + assert self.round2.temperature_unit == TEMP_CELSIUS + assert self.round2.current_temperature == 21 assert self.round2.target_temperature is None assert not self.round2.is_away_mode_on @@ -304,12 +304,12 @@ def test_set_temperature(self): def test_set_hvac_mode(self) -> None: """Test setting the system operation.""" self.round1.set_hvac_mode("cool") - assert "cool" == self.round1.current_operation - assert "cool" == self.device.system_mode + assert self.round1.current_operation == "cool" + assert self.device.system_mode == "cool" self.round1.set_hvac_mode("heat") - assert "heat" == self.round1.current_operation - assert "heat" == self.device.system_mode + assert self.round1.current_operation == "heat" + assert self.device.system_mode == "heat" class TestHoneywellUS(unittest.TestCase): @@ -342,40 +342,40 @@ def setup_method(self, method): def test_properties(self): """Test the properties.""" assert self.honeywell.is_fan_on - assert "test" == self.honeywell.name - assert 72 == self.honeywell.current_temperature + assert self.honeywell.name == "test" + assert self.honeywell.current_temperature == 72 def test_unit_of_measurement(self): """Test the unit of measurement.""" - assert TEMP_FAHRENHEIT == self.honeywell.temperature_unit + assert self.honeywell.temperature_unit == TEMP_FAHRENHEIT self.device.temperature_unit = "C" - assert TEMP_CELSIUS == self.honeywell.temperature_unit + assert self.honeywell.temperature_unit == TEMP_CELSIUS def test_target_temp(self): """Test the target temperature.""" - assert 65 == self.honeywell.target_temperature + assert self.honeywell.target_temperature == 65 self.device.system_mode = "cool" - assert 78 == self.honeywell.target_temperature + assert self.honeywell.target_temperature == 78 def test_set_temp(self): """Test setting the temperature.""" self.honeywell.set_temperature(temperature=70) - assert 70 == self.device.setpoint_heat - assert 70 == self.honeywell.target_temperature + assert self.device.setpoint_heat == 70 + assert self.honeywell.target_temperature == 70 self.device.system_mode = "cool" - assert 78 == self.honeywell.target_temperature + assert self.honeywell.target_temperature == 78 self.honeywell.set_temperature(temperature=74) - assert 74 == self.device.setpoint_cool - assert 74 == self.honeywell.target_temperature + assert self.device.setpoint_cool == 74 + assert self.honeywell.target_temperature == 74 def test_set_hvac_mode(self) -> None: """Test setting the operation mode.""" self.honeywell.set_hvac_mode("cool") - assert "cool" == self.device.system_mode + assert self.device.system_mode == "cool" self.honeywell.set_hvac_mode("heat") - assert "heat" == self.device.system_mode + assert self.device.system_mode == "heat" def test_set_temp_fail(self): """Test if setting the temperature fails.""" @@ -395,7 +395,7 @@ def test_attributes(self): assert expected == self.honeywell.extra_state_attributes expected["fan"] = "idle" self.device.fan_running = False - assert expected == self.honeywell.extra_state_attributes + assert self.honeywell.extra_state_attributes == expected def test_with_no_fan(self): """Test if there is on fan.""" @@ -407,7 +407,7 @@ def test_with_no_fan(self): ATTR_FAN_MODES: somecomfort.FAN_MODES, ATTR_HVAC_MODES: somecomfort.SYSTEM_MODES, } - assert expected == self.honeywell.extra_state_attributes + assert self.honeywell.extra_state_attributes == expected def test_heat_away_mode(self): """Test setting the heat away mode.""" diff --git a/tests/components/imap_email_content/test_sensor.py b/tests/components/imap_email_content/test_sensor.py index 101896f4a33fcb..aa25d4f85ff179 100644 --- a/tests/components/imap_email_content/test_sensor.py +++ b/tests/components/imap_email_content/test_sensor.py @@ -47,10 +47,10 @@ async def test_allowed_sender(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Test" == sensor.state - assert "Test Message" == sensor.extra_state_attributes["body"] - assert "sender@test.com" == sensor.extra_state_attributes["from"] - assert "Test" == sensor.extra_state_attributes["subject"] + assert sensor.state == "Test" + assert sensor.extra_state_attributes["body"] == "Test Message" + assert sensor.extra_state_attributes["from"] == "sender@test.com" + assert sensor.extra_state_attributes["subject"] == "Test" assert ( datetime.datetime(2016, 1, 1, 12, 44, 57) == sensor.extra_state_attributes["date"] @@ -83,8 +83,8 @@ async def test_multi_part_with_text(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Link" == sensor.state - assert "Test Message" == sensor.extra_state_attributes["body"] + assert sensor.state == "Link" + assert sensor.extra_state_attributes["body"] == "Test Message" async def test_multi_part_only_html(hass): @@ -110,10 +110,10 @@ async def test_multi_part_only_html(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Link" == sensor.state + assert sensor.state == "Link" assert ( - "Test Message" - == sensor.extra_state_attributes["body"] + sensor.extra_state_attributes["body"] + == "Test Message" ) @@ -140,8 +140,8 @@ async def test_multi_part_only_other_text(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Link" == sensor.state - assert "Test Message" == sensor.extra_state_attributes["body"] + assert sensor.state == "Link" + assert sensor.extra_state_attributes["body"] == "Test Message" async def test_multiple_emails(hass): @@ -180,10 +180,10 @@ def state_changed_listener(entity_id, from_s, to_s): sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Test" == states[0].state - assert "Test 2" == states[1].state + assert states[0].state == "Test" + assert states[1].state == "Test 2" - assert "Test Message 2" == sensor.extra_state_attributes["body"] + assert sensor.extra_state_attributes["body"] == "Test Message 2" async def test_sender_not_allowed(hass): @@ -227,4 +227,4 @@ async def test_template(hass): sensor.entity_id = "sensor.emailtest" sensor.async_schedule_update_ha_state(True) await hass.async_block_till_done() - assert "Test from sender@test.com with message Test Message" == sensor.state + assert sensor.state == "Test from sender@test.com with message Test Message" diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index fd43091f457de2..6c560eec5e74e9 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -127,7 +127,7 @@ async def test_setup_config_full(hass, mock_client, config_ext, get_write_api): assert await async_setup_component(hass, influxdb.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert get_write_api(mock_client).call_count == 1 @@ -260,7 +260,7 @@ async def test_setup_config_ssl( await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert expected_client_args.items() <= mock_client.call_args.kwargs.items() @@ -280,7 +280,7 @@ async def test_setup_minimal_config(hass, mock_client, config_ext, get_write_api assert await async_setup_component(hass, influxdb.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED assert get_write_api(mock_client).call_count == 1 diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index 6534006dd81314..2b7a1f88ef1261 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -109,13 +109,13 @@ async def test_config_options(hass): assert state_1 is not None assert state_2 is not None - assert STATE_OFF == state_1.state + assert state_1.state == STATE_OFF assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert STATE_ON == state_2.state - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert state_2.state == STATE_ON + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" async def test_restore_state(hass): @@ -218,7 +218,7 @@ async def test_reload(hass, hass_admin_user): assert state_1 is not None assert state_2 is not None assert state_3 is None - assert STATE_ON == state_2.state + assert state_2.state == STATE_ON assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None @@ -259,9 +259,9 @@ async def test_reload(hass, hass_admin_user): assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None - assert STATE_ON == state_2.state # reload is not supposed to change entity state - assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work_reloaded" == state_2.attributes.get(ATTR_ICON) + assert state_2.state == STATE_ON # reload is not supposed to change entity state + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World reloaded" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work_reloaded" async def test_load_from_storage(hass, storage_setup): diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 78fa54b03bed42..d6d80a9ad87049 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -116,17 +116,17 @@ async def test_set_value(hass, caplog): entity_id = "input_number.test_1" state = hass.states.get(entity_id) - assert 50 == float(state.state) + assert float(state.state) == 50 await set_value(hass, entity_id, "30.4") state = hass.states.get(entity_id) - assert 30.4 == float(state.state) + assert float(state.state) == 30.4 await set_value(hass, entity_id, "70") state = hass.states.get(entity_id) - assert 70 == float(state.state) + assert float(state.state) == 70 with pytest.raises(vol.Invalid) as excinfo: await set_value(hass, entity_id, "110") @@ -136,7 +136,7 @@ async def test_set_value(hass, caplog): ) state = hass.states.get(entity_id) - assert 70 == float(state.state) + assert float(state.state) == 70 async def test_increment(hass): @@ -147,19 +147,19 @@ async def test_increment(hass): entity_id = "input_number.test_2" state = hass.states.get(entity_id) - assert 50 == float(state.state) + assert float(state.state) == 50 await increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 51 == float(state.state) + assert float(state.state) == 51 await increment(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 51 == float(state.state) + assert float(state.state) == 51 async def test_decrement(hass): @@ -170,19 +170,19 @@ async def test_decrement(hass): entity_id = "input_number.test_3" state = hass.states.get(entity_id) - assert 50 == float(state.state) + assert float(state.state) == 50 await decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 49 == float(state.state) + assert float(state.state) == 49 await decrement(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert 49 == float(state.state) + assert float(state.state) == 49 async def test_mode(hass): @@ -201,15 +201,15 @@ async def test_mode(hass): state = hass.states.get("input_number.test_default_slider") assert state - assert "slider" == state.attributes["mode"] + assert state.attributes["mode"] == "slider" state = hass.states.get("input_number.test_explicit_box") assert state - assert "box" == state.attributes["mode"] + assert state.attributes["mode"] == "box" state = hass.states.get("input_number.test_explicit_slider") assert state - assert "slider" == state.attributes["mode"] + assert state.attributes["mode"] == "slider" async def test_restore_state(hass): @@ -322,8 +322,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is not None assert state_2 is None assert state_3 is not None - assert 50 == float(state_1.state) - assert 10 == float(state_3.state) + assert float(state_1.state) == 50 + assert float(state_3.state) == 10 assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None @@ -362,8 +362,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is not None assert state_2 is not None assert state_3 is None - assert 50 == float(state_1.state) - assert 20 == float(state_2.state) + assert float(state_1.state) == 50 + assert float(state_2.state) == 20 assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 70663f71e7a810..f5f7956e9c5ff6 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -156,19 +156,19 @@ async def test_select_option(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "some option" == state.state + assert state.state == "some option" select_option(hass, entity_id, "another option") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "another option" == state.state + assert state.state == "another option" select_option(hass, entity_id, "non existing option") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "another option" == state.state + assert state.state == "another option" async def test_select_next(hass): @@ -188,19 +188,19 @@ async def test_select_next(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" select_next(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "last option" == state.state + assert state.state == "last option" select_next(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "first option" == state.state + assert state.state == "first option" async def test_select_previous(hass): @@ -220,19 +220,19 @@ async def test_select_previous(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" select_previous(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "first option" == state.state + assert state.state == "first option" select_previous(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "last option" == state.state + assert state.state == "last option" async def test_select_first_last(hass): @@ -252,19 +252,19 @@ async def test_select_first_last(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" select_first(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "first option" == state.state + assert state.state == "first option" select_last(hass, entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "last option" == state.state + assert state.state == "last option" async def test_config_options(hass): @@ -297,14 +297,14 @@ async def test_config_options(hass): assert state_1 is not None assert state_2 is not None - assert "1" == state_1.state - assert ["1", "2"] == state_1.attributes.get(ATTR_OPTIONS) + assert state_1.state == "1" + assert state_1.attributes.get(ATTR_OPTIONS) == ["1", "2"] assert ATTR_ICON not in state_1.attributes - assert "Better Option" == state_2.state - assert test_2_options == state_2.attributes.get(ATTR_OPTIONS) - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) + assert state_2.state == "Better Option" + assert state_2.attributes.get(ATTR_OPTIONS) == test_2_options + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" async def test_set_options_service(hass): @@ -324,24 +324,24 @@ async def test_set_options_service(hass): entity_id = "input_select.test_1" state = hass.states.get(entity_id) - assert "middle option" == state.state + assert state.state == "middle option" data = {ATTR_OPTIONS: ["test1", "test2"], "entity_id": entity_id} await hass.services.async_call(DOMAIN, SERVICE_SET_OPTIONS, data) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "test1" == state.state + assert state.state == "test1" select_option(hass, entity_id, "first option") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "test1" == state.state + assert state.state == "test1" select_option(hass, entity_id, "test2") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert "test2" == state.state + assert state.state == "test2" async def test_restore_state(hass): @@ -453,8 +453,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is not None assert state_2 is not None assert state_3 is None - assert "middle option" == state_1.state - assert "an option" == state_2.state + assert state_1.state == "middle option" + assert state_2.state == "an option" assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None @@ -499,8 +499,8 @@ async def test_reload(hass, hass_admin_user, hass_read_only_user): assert state_1 is None assert state_2 is not None assert state_3 is not None - assert "an option" == state_2.state - assert "newer option" == state_3.state + assert state_2.state == "an option" + assert state_3.state == "newer option" assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None diff --git a/tests/components/logentries/test_init.py b/tests/components/logentries/test_init.py index 3ae25d521cb781..96632865af098e 100644 --- a/tests/components/logentries/test_init.py +++ b/tests/components/logentries/test_init.py @@ -15,7 +15,7 @@ async def test_setup_config_full(hass): hass.bus.listen = MagicMock() assert await async_setup_component(hass, logentries.DOMAIN, config) assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED async def test_setup_config_defaults(hass): @@ -24,7 +24,7 @@ async def test_setup_config_defaults(hass): hass.bus.listen = MagicMock() assert await async_setup_component(hass, logentries.DOMAIN, config) assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED @pytest.fixture diff --git a/tests/components/mailbox/test_init.py b/tests/components/mailbox/test_init.py index a2b2583bdb5329..75ecc8d9db3cf1 100644 --- a/tests/components/mailbox/test_init.py +++ b/tests/components/mailbox/test_init.py @@ -23,7 +23,8 @@ async def test_get_platforms_from_mailbox(mock_http_client): req = await mock_http_client.get(url) assert req.status == 200 result = await req.json() - assert len(result) == 1 and "DemoMailbox" == result[0].get("name", None) + assert len(result) == 1 + assert result[0].get("name") == "DemoMailbox" async def test_get_messages_from_mailbox(mock_http_client): diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index ee3d97e52d75da..f3cf3ccce3953c 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -51,11 +51,11 @@ async def test_arm_home_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE) - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_no_pending_when_code_not_req(hass): @@ -78,11 +78,11 @@ async def test_arm_home_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, 0) - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_with_pending(hass): @@ -104,11 +104,11 @@ async def test_arm_home_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE, entity_id) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_HOME @@ -144,11 +144,11 @@ async def test_arm_home_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_away_no_pending(hass): @@ -170,11 +170,11 @@ async def test_arm_away_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_no_pending_when_code_not_req(hass): @@ -197,11 +197,11 @@ async def test_arm_away_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, 0, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_home_with_template_code(hass): @@ -223,12 +223,12 @@ async def test_arm_home_with_template_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "abc") state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME async def test_arm_away_with_pending(hass): @@ -250,11 +250,11 @@ async def test_arm_away_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY @@ -290,11 +290,11 @@ async def test_arm_away_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_night_no_pending(hass): @@ -316,11 +316,11 @@ async def test_arm_night_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE) - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_no_pending_when_code_not_req(hass): @@ -343,11 +343,11 @@ async def test_arm_night_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, 0) - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_pending(hass): @@ -369,11 +369,11 @@ async def test_arm_night_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE, entity_id) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_NIGHT @@ -392,7 +392,7 @@ async def test_arm_night_with_pending(hass): # Do not go to the pending state when updating to the same state await common.async_alarm_arm_night(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_invalid_code(hass): @@ -414,11 +414,11 @@ async def test_arm_night_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_no_pending(hass): @@ -439,11 +439,11 @@ async def test_trigger_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=60) with patch( @@ -453,7 +453,7 @@ async def test_trigger_no_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_trigger_with_delay(hass): @@ -476,17 +476,17 @@ async def test_trigger_with_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -497,7 +497,7 @@ async def test_trigger_with_delay(hass): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_trigger_zero_trigger_time(hass): @@ -519,11 +519,11 @@ async def test_trigger_zero_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_zero_trigger_time_with_pending(hass): @@ -545,11 +545,11 @@ async def test_trigger_zero_trigger_time_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_pending(hass): @@ -571,11 +571,11 @@ async def test_trigger_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED @@ -624,17 +624,17 @@ async def test_trigger_with_unused_specific_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -669,17 +669,17 @@ async def test_trigger_with_specific_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -713,11 +713,11 @@ async def test_trigger_with_pending_and_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) @@ -770,11 +770,11 @@ async def test_trigger_with_pending_and_specific_delay(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) @@ -826,7 +826,7 @@ async def test_armed_home_with_specific_pending(hass): await common.async_alarm_arm_home(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -836,7 +836,7 @@ async def test_armed_home_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_armed_away_with_specific_pending(hass): @@ -859,7 +859,7 @@ async def test_armed_away_with_specific_pending(hass): await common.async_alarm_arm_away(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -869,7 +869,7 @@ async def test_armed_away_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_armed_night_with_specific_pending(hass): @@ -892,7 +892,7 @@ async def test_armed_night_with_specific_pending(hass): await common.async_alarm_arm_night(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -902,7 +902,7 @@ async def test_armed_night_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_trigger_with_specific_pending(hass): @@ -927,7 +927,7 @@ async def test_trigger_with_specific_pending(hass): await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -937,7 +937,7 @@ async def test_trigger_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -947,7 +947,7 @@ async def test_trigger_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_disarm_after_trigger(hass): @@ -969,11 +969,11 @@ async def test_trigger_with_disarm_after_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -983,7 +983,7 @@ async def test_trigger_with_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_zero_specific_trigger_time(hass): @@ -1006,11 +1006,11 @@ async def test_trigger_with_zero_specific_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_unused_zero_specific_trigger_time(hass): @@ -1033,11 +1033,11 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1047,7 +1047,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_specific_trigger_time(hass): @@ -1069,11 +1069,11 @@ async def test_trigger_with_specific_trigger_time(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1083,7 +1083,7 @@ async def test_trigger_with_specific_trigger_time(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_no_disarm_after_trigger(hass): @@ -1106,15 +1106,15 @@ async def test_trigger_with_no_disarm_after_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1124,7 +1124,7 @@ async def test_trigger_with_no_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): @@ -1147,15 +1147,15 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1165,11 +1165,11 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1179,7 +1179,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_disarm_while_pending_trigger(hass): @@ -1200,15 +1200,15 @@ async def test_disarm_while_pending_trigger(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1218,7 +1218,7 @@ async def test_disarm_while_pending_trigger(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_disarm_during_trigger_with_invalid_code(hass): @@ -1240,15 +1240,15 @@ async def test_disarm_during_trigger_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1258,7 +1258,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_disarm_with_template_code(hass): @@ -1280,22 +1280,22 @@ async def test_disarm_with_template_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "def") state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "def") state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "abc") state = hass.states.get(entity_id) - assert STATE_ALARM_DISARMED == state.state + assert state.state == STATE_ALARM_DISARMED async def test_arm_custom_bypass_no_pending(hass): @@ -1317,11 +1317,11 @@ async def test_arm_custom_bypass_no_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, CODE) - assert STATE_ALARM_ARMED_CUSTOM_BYPASS == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_CUSTOM_BYPASS async def test_arm_custom_bypass_no_pending_when_code_not_req(hass): @@ -1344,11 +1344,11 @@ async def test_arm_custom_bypass_no_pending_when_code_not_req(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, 0) - assert STATE_ALARM_ARMED_CUSTOM_BYPASS == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_CUSTOM_BYPASS async def test_arm_custom_bypass_with_pending(hass): @@ -1370,11 +1370,11 @@ async def test_arm_custom_bypass_with_pending(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, CODE, entity_id) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING state = hass.states.get(entity_id) assert state.attributes["next_state"] == STATE_ALARM_ARMED_CUSTOM_BYPASS @@ -1410,11 +1410,11 @@ async def test_arm_custom_bypass_with_invalid_code(hass): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_custom_bypass(hass, CODE + "2") - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_armed_custom_bypass_with_specific_pending(hass): @@ -1437,7 +1437,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): await common.async_alarm_arm_custom_bypass(hass) - assert STATE_ALARM_ARMING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1447,7 +1447,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_CUSTOM_BYPASS == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_CUSTOM_BYPASS async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): @@ -1472,21 +1472,21 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) state = hass.states.get(entity_id) - assert STATE_ALARM_ARMING == state.state - assert STATE_ALARM_DISARMED == state.attributes["previous_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["next_state"] + assert state.state == STATE_ALARM_ARMING + assert state.attributes["previous_state"] == STATE_ALARM_DISARMED + assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_ARMING == state.state - assert STATE_ALARM_DISARMED == state.attributes["previous_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["next_state"] + assert state.state == STATE_ALARM_ARMING + assert state.attributes["previous_state"] == STATE_ALARM_DISARMED + assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1497,14 +1497,14 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_AWAY == state.state + assert state.state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_ARMED_AWAY == state.attributes["previous_state"] - assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["previous_state"] == STATE_ALARM_ARMED_AWAY + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) with patch( @@ -1515,7 +1515,7 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_restore_armed_state(hass): diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index 9a98af127ea828..05033bb3347422 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -76,12 +76,12 @@ async def test_arm_home_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_no_pending_when_code_not_req(hass, mqtt_mock): @@ -106,12 +106,12 @@ async def test_arm_home_no_pending_when_code_not_req(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, 0) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_with_pending(hass, mqtt_mock): @@ -135,12 +135,12 @@ async def test_arm_home_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_HOME @@ -153,7 +153,7 @@ async def test_arm_home_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_home_with_invalid_code(hass, mqtt_mock): @@ -177,12 +177,12 @@ async def test_arm_home_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, f"{CODE}2") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_away_no_pending(hass, mqtt_mock): @@ -206,12 +206,12 @@ async def test_arm_away_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_no_pending_when_code_not_req(hass, mqtt_mock): @@ -236,12 +236,12 @@ async def test_arm_away_no_pending_when_code_not_req(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, 0, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_home_with_template_code(hass, mqtt_mock): @@ -265,13 +265,13 @@ async def test_arm_home_with_template_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "abc") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME async def test_arm_away_with_pending(hass, mqtt_mock): @@ -295,12 +295,12 @@ async def test_arm_away_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY @@ -313,7 +313,7 @@ async def test_arm_away_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_with_invalid_code(hass, mqtt_mock): @@ -337,12 +337,12 @@ async def test_arm_away_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, f"{CODE}2") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_night_no_pending(hass, mqtt_mock): @@ -366,12 +366,12 @@ async def test_arm_night_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_no_pending_when_code_not_req(hass, mqtt_mock): @@ -396,12 +396,12 @@ async def test_arm_night_no_pending_when_code_not_req(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, 0, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_pending(hass, mqtt_mock): @@ -425,12 +425,12 @@ async def test_arm_night_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_NIGHT @@ -443,13 +443,13 @@ async def test_arm_night_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT # Do not go to the pending state when updating to the same state await common.async_alarm_arm_night(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_arm_night_with_invalid_code(hass, mqtt_mock): @@ -473,12 +473,12 @@ async def test_arm_night_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_night(hass, f"{CODE}2") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_no_pending(hass, mqtt_mock): @@ -501,12 +501,12 @@ async def test_trigger_no_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=60) with patch( @@ -516,7 +516,7 @@ async def test_trigger_no_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_trigger_with_delay(hass, mqtt_mock): @@ -541,19 +541,19 @@ async def test_trigger_with_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -564,7 +564,7 @@ async def test_trigger_with_delay(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_trigger_zero_trigger_time(hass, mqtt_mock): @@ -588,12 +588,12 @@ async def test_trigger_zero_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_zero_trigger_time_with_pending(hass, mqtt_mock): @@ -617,12 +617,12 @@ async def test_trigger_zero_trigger_time_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_pending(hass, mqtt_mock): @@ -646,12 +646,12 @@ async def test_trigger_with_pending(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING state = hass.states.get(entity_id) assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED @@ -664,7 +664,7 @@ async def test_trigger_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -674,7 +674,7 @@ async def test_trigger_with_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): @@ -698,12 +698,12 @@ async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -713,7 +713,7 @@ async def test_trigger_with_disarm_after_trigger(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_zero_specific_trigger_time(hass, mqtt_mock): @@ -738,12 +738,12 @@ async def test_trigger_with_zero_specific_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): @@ -768,12 +768,12 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -783,7 +783,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): @@ -807,12 +807,12 @@ async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -822,7 +822,7 @@ async def test_trigger_with_specific_trigger_time(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock): @@ -846,17 +846,17 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE, entity_id) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -866,12 +866,12 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -881,7 +881,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass, mqtt_mock async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_disarm_while_pending_trigger(hass, mqtt_mock): @@ -904,17 +904,17 @@ async def test_disarm_while_pending_trigger(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -924,7 +924,7 @@ async def test_disarm_while_pending_trigger(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): @@ -948,17 +948,17 @@ async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING await common.async_alarm_disarm(hass, entity_id=entity_id) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -968,7 +968,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED async def test_trigger_with_unused_specific_delay(hass, mqtt_mock): @@ -994,19 +994,19 @@ async def test_trigger_with_unused_specific_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1043,19 +1043,19 @@ async def test_trigger_with_specific_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1092,12 +1092,12 @@ async def test_trigger_with_pending_and_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() @@ -1154,12 +1154,12 @@ async def test_trigger_with_pending_and_specific_delay(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() @@ -1215,7 +1215,7 @@ async def test_armed_home_with_specific_pending(hass, mqtt_mock): await common.async_alarm_arm_home(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1225,7 +1225,7 @@ async def test_armed_home_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_armed_away_with_specific_pending(hass, mqtt_mock): @@ -1251,7 +1251,7 @@ async def test_armed_away_with_specific_pending(hass, mqtt_mock): await common.async_alarm_arm_away(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1261,7 +1261,7 @@ async def test_armed_away_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_armed_night_with_specific_pending(hass, mqtt_mock): @@ -1287,7 +1287,7 @@ async def test_armed_night_with_specific_pending(hass, mqtt_mock): await common.async_alarm_arm_night(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1297,7 +1297,7 @@ async def test_armed_night_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_trigger_with_specific_pending(hass, mqtt_mock): @@ -1325,7 +1325,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1335,7 +1335,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_TRIGGERED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -1345,7 +1345,7 @@ async def test_trigger_with_specific_pending(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqtt_mock): @@ -1372,23 +1372,23 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_away(hass, CODE) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_DISARMED == state.attributes["pre_pending_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["pre_pending_state"] == STATE_ALARM_DISARMED + assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_DISARMED == state.attributes["pre_pending_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["pre_pending_state"] == STATE_ALARM_DISARMED + assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1399,15 +1399,15 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_AWAY == state.state + assert state.state == STATE_ALARM_ARMED_AWAY await common.async_alarm_trigger(hass, entity_id=entity_id) await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_ARMED_AWAY == state.attributes["pre_pending_state"] - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert state.state == STATE_ALARM_PENDING + assert state.attributes["pre_pending_state"] == STATE_ALARM_ARMED_AWAY + assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) with patch( @@ -1418,7 +1418,7 @@ async def test_arm_away_after_disabled_disarmed(hass, legacy_patchable_time, mqt await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_TRIGGERED == state.state + assert state.state == STATE_ALARM_TRIGGERED async def test_disarm_with_template_code(hass, mqtt_mock): @@ -1442,25 +1442,25 @@ async def test_disarm_with_template_code(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_arm_home(hass, "def") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "def") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_ARMED_HOME == state.state + assert state.state == STATE_ALARM_ARMED_HOME await common.async_alarm_disarm(hass, "abc") await hass.async_block_till_done() state = hass.states.get(entity_id) - assert STATE_ALARM_DISARMED == state.state + assert state.state == STATE_ALARM_DISARMED async def test_arm_home_via_command_topic(hass, mqtt_mock): @@ -1483,12 +1483,12 @@ async def test_arm_home_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED # Fire the arm command via MQTT; ensure state changes to pending async_fire_mqtt_message(hass, "alarm/command", "ARM_HOME") await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1499,7 +1499,7 @@ async def test_arm_home_via_command_topic(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_HOME async def test_arm_away_via_command_topic(hass, mqtt_mock): @@ -1522,12 +1522,12 @@ async def test_arm_away_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED # Fire the arm command via MQTT; ensure state changes to pending async_fire_mqtt_message(hass, "alarm/command", "ARM_AWAY") await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1538,7 +1538,7 @@ async def test_arm_away_via_command_topic(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY async def test_arm_night_via_command_topic(hass, mqtt_mock): @@ -1561,12 +1561,12 @@ async def test_arm_night_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED # Fire the arm command via MQTT; ensure state changes to pending async_fire_mqtt_message(hass, "alarm/command", "ARM_NIGHT") await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1577,7 +1577,7 @@ async def test_arm_night_via_command_topic(hass, mqtt_mock): async_fire_time_changed(hass, future) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_NIGHT == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_NIGHT async def test_disarm_pending_via_command_topic(hass, mqtt_mock): @@ -1600,18 +1600,18 @@ async def test_disarm_pending_via_command_topic(hass, mqtt_mock): entity_id = "alarm_control_panel.test" - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED await common.async_alarm_trigger(hass) await hass.async_block_till_done() - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_PENDING # Now that we're pending, receive a command to disarm async_fire_mqtt_message(hass, "alarm/command", "DISARM") await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(entity_id).state + assert hass.states.get(entity_id).state == STATE_ALARM_DISARMED async def test_state_changes_are_published_to_mqtt(hass, mqtt_mock): diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index ccb5f951fa215f..1b53e2f83347d4 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -94,7 +94,7 @@ async def test_current_fan_mode(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) await thermostat.async_update() - assert SPEED_LOW == thermostat.fan_mode + assert thermostat.fan_mode == SPEED_LOW thermostat._cur_settings = None assert thermostat.fan_mode is None @@ -185,7 +185,7 @@ async def test_state(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) await thermostat.async_update() - assert HVAC_MODE_HEAT == thermostat.state + assert thermostat.state == HVAC_MODE_HEAT thermostat._cur_settings = None assert thermostat.state is None @@ -197,7 +197,7 @@ async def test_temperature_unit(hass): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) - assert TEMP_CELSIUS == thermostat.temperature_unit + assert thermostat.temperature_unit == TEMP_CELSIUS async def test_min_temp(hass): @@ -225,7 +225,7 @@ async def test_supported_features(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - assert features == thermostat.supported_features + assert thermostat.supported_features == features async def test_set_temperature(hass): @@ -249,7 +249,7 @@ async def test_fan_mode(hass): await hass.async_block_till_done() await thermostat.async_set_fan_mode(SPEED_HIGH) await hass.async_block_till_done() - assert SPEED_HIGH == thermostat.fan_mode + assert thermostat.fan_mode == SPEED_HIGH async def test_set_operation_mode(hass): @@ -262,7 +262,7 @@ async def test_set_operation_mode(hass): await hass.async_block_till_done() await thermostat.async_set_hvac_mode(HVAC_MODE_COOL) await hass.async_block_till_done() - assert HVAC_MODE_COOL == thermostat.hvac_mode + assert thermostat.hvac_mode == HVAC_MODE_COOL async def test_send(hass): @@ -275,7 +275,7 @@ async def test_send(hass): await hass.async_block_till_done() await thermostat.async_send({"fan": api.FAN_MEDIUM}) await hass.async_block_till_done() - assert SPEED_MEDIUM == thermostat.fan_mode + assert thermostat.fan_mode == SPEED_MEDIUM api.async_send.return_value = AsyncMock(return_value=False) thermostat._cur_settings = None await thermostat.async_send({"fan": api.FAN_LOW}) @@ -294,8 +294,8 @@ async def test_update(hass): device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) await thermostat.async_update() - assert SPEED_LOW == thermostat.fan_mode - assert HVAC_MODE_HEAT == thermostat.state + assert thermostat.fan_mode == SPEED_LOW + assert thermostat.state == HVAC_MODE_HEAT api.async_status = AsyncMock(side_effect=KeyError("boom")) await thermostat.async_update() mocked_warning.assert_called_once_with( @@ -309,10 +309,10 @@ async def test_melissa_op_to_hass(hass): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) - assert HVAC_MODE_FAN_ONLY == thermostat.melissa_op_to_hass(1) - assert HVAC_MODE_HEAT == thermostat.melissa_op_to_hass(2) - assert HVAC_MODE_COOL == thermostat.melissa_op_to_hass(3) - assert HVAC_MODE_DRY == thermostat.melissa_op_to_hass(4) + assert thermostat.melissa_op_to_hass(1) == HVAC_MODE_FAN_ONLY + assert thermostat.melissa_op_to_hass(2) == HVAC_MODE_HEAT + assert thermostat.melissa_op_to_hass(3) == HVAC_MODE_COOL + assert thermostat.melissa_op_to_hass(4) == HVAC_MODE_DRY assert thermostat.melissa_op_to_hass(5) is None @@ -322,10 +322,10 @@ async def test_melissa_fan_to_hass(hass): api = melissa_mock() device = (await api.async_fetch_devices())[_SERIAL] thermostat = MelissaClimate(api, _SERIAL, device) - assert "auto" == thermostat.melissa_fan_to_hass(0) - assert SPEED_LOW == thermostat.melissa_fan_to_hass(1) - assert SPEED_MEDIUM == thermostat.melissa_fan_to_hass(2) - assert SPEED_HIGH == thermostat.melissa_fan_to_hass(3) + assert thermostat.melissa_fan_to_hass(0) == "auto" + assert thermostat.melissa_fan_to_hass(1) == SPEED_LOW + assert thermostat.melissa_fan_to_hass(2) == SPEED_MEDIUM + assert thermostat.melissa_fan_to_hass(3) == SPEED_HIGH assert thermostat.melissa_fan_to_hass(4) is None diff --git a/tests/components/meraki/test_device_tracker.py b/tests/components/meraki/test_device_tracker.py index 0fa83eec9c8f14..1c22b24411a116 100644 --- a/tests/components/meraki/test_device_tracker.py +++ b/tests/components/meraki/test_device_tracker.py @@ -125,9 +125,9 @@ async def test_data_will_be_saved(mock_device_tracker_conf, hass, meraki_client) state_name = hass.states.get( "{}.{}".format("device_tracker", "00_26_ab_b8_a9_a4") ).state - assert "home" == state_name + assert state_name == "home" state_name = hass.states.get( "{}.{}".format("device_tracker", "00_26_ab_b8_a9_a5") ).state - assert "home" == state_name + assert state_name == "home" diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index 610aa91cd4cd0d..6103c43d3a4c8c 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -132,7 +132,7 @@ async def test_name(port, sensor): async def test_uom_temp(port, sensor): """Test the UOM temperature.""" port.tag = "temperature" - assert TEMP_CELSIUS == sensor.unit_of_measurement + assert sensor.unit_of_measurement == TEMP_CELSIUS async def test_uom_power(port, sensor): diff --git a/tests/components/min_max/test_sensor.py b/tests/components/min_max/test_sensor.py index 93360d5778660c..c6c352a8c3e6d0 100644 --- a/tests/components/min_max/test_sensor.py +++ b/tests/components/min_max/test_sensor.py @@ -50,10 +50,10 @@ async def test_min_sensor(hass): assert str(float(MIN_VALUE)) == state.state assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEAN == state.attributes.get("mean") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("mean") == MEAN + assert state.attributes.get("median") == MEDIAN async def test_max_sensor(hass): @@ -80,10 +80,10 @@ async def test_max_sensor(hass): assert str(float(MAX_VALUE)) == state.state assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEAN == state.attributes.get("mean") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("mean") == MEAN + assert state.attributes.get("median") == MEDIAN async def test_mean_sensor(hass): @@ -109,11 +109,11 @@ async def test_mean_sensor(hass): state = hass.states.get("sensor.test_mean") assert str(float(MEAN)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("median") == MEDIAN async def test_mean_1_digit_sensor(hass): @@ -140,11 +140,11 @@ async def test_mean_1_digit_sensor(hass): state = hass.states.get("sensor.test_mean") assert str(float(MEAN_1_DIGIT)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("median") == MEDIAN async def test_mean_4_digit_sensor(hass): @@ -171,11 +171,11 @@ async def test_mean_4_digit_sensor(hass): state = hass.states.get("sensor.test_mean") assert str(float(MEAN_4_DIGITS)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("median") == MEDIAN async def test_median_sensor(hass): @@ -201,11 +201,11 @@ async def test_median_sensor(hass): state = hass.states.get("sensor.test_median") assert str(float(MEDIAN)) == state.state - assert MIN_VALUE == state.attributes.get("min_value") + assert state.attributes.get("min_value") == MIN_VALUE assert entity_ids[2] == state.attributes.get("min_entity_id") - assert MAX_VALUE == state.attributes.get("max_value") + assert state.attributes.get("max_value") == MAX_VALUE assert entity_ids[1] == state.attributes.get("max_entity_id") - assert MEAN == state.attributes.get("mean") + assert state.attributes.get("mean") == MEAN async def test_not_enough_sensor_value(hass): @@ -228,7 +228,7 @@ async def test_not_enough_sensor_value(hass): await hass.async_block_till_done() state = hass.states.get("sensor.test_max") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("min_entity_id") is None assert state.attributes.get("min_value") is None assert state.attributes.get("max_entity_id") is None @@ -259,7 +259,7 @@ async def test_not_enough_sensor_value(hass): await hass.async_block_till_done() state = hass.states.get("sensor.test_max") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("min_entity_id") is None assert state.attributes.get("min_value") is None assert state.attributes.get("max_entity_id") is None @@ -299,7 +299,7 @@ async def test_different_unit_of_measurement(hass): state = hass.states.get("sensor.test") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("unit_of_measurement") == "ERR" hass.states.async_set( @@ -309,7 +309,7 @@ async def test_different_unit_of_measurement(hass): state = hass.states.get("sensor.test") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN assert state.attributes.get("unit_of_measurement") == "ERR" @@ -336,10 +336,10 @@ async def test_last_sensor(hass): assert str(float(value)) == state.state assert entity_id == state.attributes.get("last_entity_id") - assert MIN_VALUE == state.attributes.get("min_value") - assert MAX_VALUE == state.attributes.get("max_value") - assert MEAN == state.attributes.get("mean") - assert MEDIAN == state.attributes.get("median") + assert state.attributes.get("min_value") == MIN_VALUE + assert state.attributes.get("max_value") == MAX_VALUE + assert state.attributes.get("mean") == MEAN + assert state.attributes.get("median") == MEDIAN async def test_reload(hass): diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index de66ee2fafaad4..d6668470b5534d 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -141,16 +141,16 @@ def event_callback(event): while not events: await asyncio.sleep(0) - assert 1 == len(events) + assert len(events) == 1 event = events[0] - assert DOMAIN == event.event_type - assert "s3:ObjectCreated:Put" == event.data["event_name"] - assert "5jJkTAo.jpg" == event.data["file_name"] - assert "test" == event.data["bucket"] - assert "5jJkTAo.jpg" == event.data["key"] - assert "http://url" == event.data["presigned_url"] - assert 0 == len(event.data["metadata"]) + assert event.event_type == DOMAIN + assert event.data["event_name"] == "s3:ObjectCreated:Put" + assert event.data["file_name"] == "5jJkTAo.jpg" + assert event.data["bucket"] == "test" + assert event.data["key"] == "5jJkTAo.jpg" + assert event.data["presigned_url"] == "http://url" + assert len(event.data["metadata"]) == 0 async def test_queue_listener(): @@ -183,7 +183,7 @@ async def test_queue_listener(): "metadata": {}, } - assert DOMAIN == call_domain + assert call_domain == DOMAIN assert json.dumps(expected_event, sort_keys=True) == json.dumps( call_event, sort_keys=True ) diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index 218248c3442237..0242417cb714da 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -39,7 +39,7 @@ async def test_setup_adds_proper_devices(hass): async def test_name(switch_mock): """Test the name.""" - assert "fake_switch" == switch_mock.name + assert switch_mock.name == "fake_switch" async def test_turn_on(switch_mock): diff --git a/tests/components/mold_indicator/test_sensor.py b/tests/components/mold_indicator/test_sensor.py index 2496bddc57e034..04305b724d86ea 100644 --- a/tests/components/mold_indicator/test_sensor.py +++ b/tests/components/mold_indicator/test_sensor.py @@ -47,7 +47,7 @@ async def test_setup(hass): await hass.async_block_till_done() moldind = hass.states.get("sensor.mold_indicator") assert moldind - assert PERCENTAGE == moldind.attributes.get("unit_of_measurement") + assert moldind.attributes.get("unit_of_measurement") == PERCENTAGE async def test_invalidcalib(hass): diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 508869c7b51fa7..e28cd697457a25 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -333,7 +333,7 @@ async def test_optimistic_state_change(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "CLOSE", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_CLOSED == state.state + assert state.state == STATE_CLOSED await hass.services.async_call( cover.DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: "cover.test"}, blocking=True @@ -342,7 +342,7 @@ async def test_optimistic_state_change(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "OPEN", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_OPEN == state.state + assert state.state == STATE_OPEN await hass.services.async_call( cover.DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: "cover.test"}, blocking=True @@ -393,7 +393,7 @@ async def test_optimistic_state_change_with_position(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "CLOSE", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_CLOSED == state.state + assert state.state == STATE_CLOSED assert state.attributes.get(ATTR_CURRENT_POSITION) == 0 await hass.services.async_call( @@ -403,7 +403,7 @@ async def test_optimistic_state_change_with_position(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("command-topic", "OPEN", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("cover.test") - assert STATE_OPEN == state.state + assert state.state == STATE_OPEN assert state.attributes.get(ATTR_CURRENT_POSITION) == 100 await hass.services.async_call( diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 70ee5e9327cdf5..332ed9a04d28c8 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -46,7 +46,7 @@ async def test_if_fires_on_topic_match(hass, calls): async_fire_mqtt_message(hass, "test-topic", '{ "hello": "world" }') await hass.async_block_till_done() assert len(calls) == 1 - assert 'mqtt - test-topic - { "hello": "world" } - world' == calls[0].data["some"] + assert calls[0].data["some"] == 'mqtt - test-topic - { "hello": "world" } - world' await hass.services.async_call( automation.DOMAIN, diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index da37489a130db5..7f6b22bda90b5a 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -138,7 +138,7 @@ def listener(_): async_fire_mqtt_message(hass, sub_topic, payload) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock): diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 28ff7a7ed9544e..40143a67bac173 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -99,5 +99,5 @@ async def test_sensor_values(hass): assert await async_setup_component(hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) await hass.async_block_till_done() - assert "140.0" == hass.states.get("sensor.my_fake_station_e10").state - assert "150.0" == hass.states.get("sensor.my_fake_station_p95").state + assert hass.states.get("sensor.my_fake_station_e10").state == "140.0" + assert hass.states.get("sensor.my_fake_station_p95").state == "150.0" diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index d6d410b3700947..fd2e5b30bac304 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -143,7 +143,7 @@ def test_nx584_zone_sensor_normal(): """Test for the NX584 zone sensor.""" zone = {"number": 1, "name": "foo", "state": True} sensor = nx584.NX584ZoneSensor(zone, "motion") - assert "foo" == sensor.name + assert sensor.name == "foo" assert not sensor.should_poll assert sensor.is_on assert sensor.extra_state_attributes["zone_number"] == 1 @@ -204,7 +204,7 @@ def run(fake_process): assert fake_process.call_args == mock.call(fake_events[0]) run() - assert 3 == client.get_events.call_count + assert client.get_events.call_count == 3 @mock.patch("time.sleep") @@ -224,5 +224,5 @@ def fake_run(): mock_inner.side_effect = fake_run with pytest.raises(StopMe): watcher.run() - assert 3 == mock_inner.call_count + assert mock_inner.call_count == 3 mock_sleep.assert_has_calls([mock.call(10), mock.call(10)]) diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index f30350141c4e43..494206d81a3942 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -88,7 +88,7 @@ async def test_initial_states(hass): ) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert 5 == state.attributes[plant.READING_MOISTURE] + assert state.attributes[plant.READING_MOISTURE] == 5 async def test_update_states(hass): @@ -103,8 +103,8 @@ async def test_update_states(hass): hass.states.async_set(MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_PROBLEM == state.state - assert 5 == state.attributes[plant.READING_MOISTURE] + assert state.state == STATE_PROBLEM + assert state.attributes[plant.READING_MOISTURE] == 5 async def test_unavailable_state(hass): @@ -177,9 +177,9 @@ async def test_load_from_db(hass): await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_UNKNOWN == state.state + assert state.state == STATE_UNKNOWN max_brightness = state.attributes.get(plant.ATTR_MAX_BRIGHTNESS_HISTORY) - assert 30 == max_brightness + assert max_brightness == 30 async def test_brightness_history(hass): @@ -191,17 +191,17 @@ async def test_brightness_history(hass): hass.states.async_set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_PROBLEM == state.state + assert state.state == STATE_PROBLEM hass.states.async_set(BRIGHTNESS_ENTITY, 600, {ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_OK == state.state + assert state.state == STATE_OK hass.states.async_set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX}) await hass.async_block_till_done() state = hass.states.get(f"plant.{plant_name}") - assert STATE_OK == state.state + assert state.state == STATE_OK def test_daily_history_no_data(hass): @@ -217,7 +217,7 @@ def test_daily_history_one_day(hass): for i in range(len(values)): dh.add_measurement(values[i]) max_value = max(values[0 : i + 1]) - assert 1 == len(dh._days) + assert len(dh._days) == 1 assert dh.max == max_value diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index bea42cc0888f29..fe91c7c0002e5e 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -224,7 +224,7 @@ async def test_minimal_config(hass, mock_client): assert await async_setup_component(hass, prometheus.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED @pytest.mark.usefixtures("mock_bus") @@ -251,7 +251,7 @@ async def test_full_config(hass, mock_client): assert await async_setup_component(hass, prometheus.DOMAIN, config) await hass.async_block_till_done() assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED def make_event(entity_id): diff --git a/tests/components/radarr/test_sensor.py b/tests/components/radarr/test_sensor.py index 514b8f1b81774d..aa8ccd74667c81 100644 --- a/tests/components/radarr/test_sensor.py +++ b/tests/components/radarr/test_sensor.py @@ -211,11 +211,11 @@ async def test_diskspace_no_paths(hass): entity = hass.states.get("sensor.radarr_disk_space") assert entity is not None - assert "263.10" == entity.state - assert "mdi:harddisk" == entity.attributes["icon"] - assert DATA_GIGABYTES == entity.attributes["unit_of_measurement"] - assert "Radarr Disk Space" == entity.attributes["friendly_name"] - assert "263.10/465.42GB (56.53%)" == entity.attributes["/data"] + assert entity.state == "263.10" + assert entity.attributes["icon"] == "mdi:harddisk" + assert entity.attributes["unit_of_measurement"] == DATA_GIGABYTES + assert entity.attributes["friendly_name"] == "Radarr Disk Space" + assert entity.attributes["/data"] == "263.10/465.42GB (56.53%)" async def test_diskspace_paths(hass): @@ -240,11 +240,11 @@ async def test_diskspace_paths(hass): entity = hass.states.get("sensor.radarr_disk_space") assert entity is not None - assert "263.10" == entity.state - assert "mdi:harddisk" == entity.attributes["icon"] - assert DATA_GIGABYTES == entity.attributes["unit_of_measurement"] - assert "Radarr Disk Space" == entity.attributes["friendly_name"] - assert "263.10/465.42GB (56.53%)" == entity.attributes["/data"] + assert entity.state == "263.10" + assert entity.attributes["icon"] == "mdi:harddisk" + assert entity.attributes["unit_of_measurement"] == DATA_GIGABYTES + assert entity.attributes["friendly_name"] == "Radarr Disk Space" + assert entity.attributes["/data"] == "263.10/465.42GB (56.53%)" async def test_commands(hass): @@ -269,11 +269,11 @@ async def test_commands(hass): entity = hass.states.get("sensor.radarr_commands") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:code-braces" == entity.attributes["icon"] - assert "Commands" == entity.attributes["unit_of_measurement"] - assert "Radarr Commands" == entity.attributes["friendly_name"] - assert "pending" == entity.attributes["RescanMovie"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:code-braces" + assert entity.attributes["unit_of_measurement"] == "Commands" + assert entity.attributes["friendly_name"] == "Radarr Commands" + assert entity.attributes["RescanMovie"] == "pending" async def test_movies(hass): @@ -298,11 +298,11 @@ async def test_movies(hass): entity = hass.states.get("sensor.radarr_movies") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Movies" == entity.attributes["friendly_name"] - assert "false" == entity.attributes["Assassin's Creed (2016)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Movies" + assert entity.attributes["Assassin's Creed (2016)"] == "false" async def test_upcoming_multiple_days(hass): @@ -327,11 +327,11 @@ async def test_upcoming_multiple_days(hass): entity = hass.states.get("sensor.radarr_upcoming") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Upcoming" == entity.attributes["friendly_name"] - assert "2017-01-27T00:00:00Z" == entity.attributes["Resident Evil (2017)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Upcoming" + assert entity.attributes["Resident Evil (2017)"] == "2017-01-27T00:00:00Z" @pytest.mark.skip @@ -357,11 +357,11 @@ async def test_upcoming_today(hass): assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_upcoming") - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Upcoming" == entity.attributes["friendly_name"] - assert "2017-01-27T00:00:00Z" == entity.attributes["Resident Evil (2017)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Upcoming" + assert entity.attributes["Resident Evil (2017)"] == "2017-01-27T00:00:00Z" async def test_system_status(hass): @@ -384,10 +384,10 @@ async def test_system_status(hass): await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_status") assert entity is not None - assert "0.2.0.210" == entity.state - assert "mdi:information" == entity.attributes["icon"] - assert "Radarr Status" == entity.attributes["friendly_name"] - assert "4.8.13.1" == entity.attributes["osVersion"] + assert entity.state == "0.2.0.210" + assert entity.attributes["icon"] == "mdi:information" + assert entity.attributes["friendly_name"] == "Radarr Status" + assert entity.attributes["osVersion"] == "4.8.13.1" async def test_ssl(hass): @@ -411,11 +411,11 @@ async def test_ssl(hass): await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_upcoming") assert entity is not None - assert 1 == int(entity.state) - assert "mdi:television" == entity.attributes["icon"] - assert "Movies" == entity.attributes["unit_of_measurement"] - assert "Radarr Upcoming" == entity.attributes["friendly_name"] - assert "2017-01-27T00:00:00Z" == entity.attributes["Resident Evil (2017)"] + assert int(entity.state) == 1 + assert entity.attributes["icon"] == "mdi:television" + assert entity.attributes["unit_of_measurement"] == "Movies" + assert entity.attributes["friendly_name"] == "Radarr Upcoming" + assert entity.attributes["Resident Evil (2017)"] == "2017-01-27T00:00:00Z" async def test_exception_handling(hass): @@ -438,4 +438,4 @@ async def test_exception_handling(hass): await hass.async_block_till_done() entity = hass.states.get("sensor.radarr_upcoming") assert entity is not None - assert "unavailable" == entity.state + assert entity.state == "unavailable" diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py index 1f57b97884c7c4..2c2005a7fee4c8 100644 --- a/tests/components/remote/test_init.py +++ b/tests/components/remote/test_init.py @@ -48,7 +48,7 @@ async def test_turn_on(hass): assert len(turn_on_calls) == 1 call = turn_on_calls[-1] - assert DOMAIN == call.domain + assert call.domain == DOMAIN async def test_turn_off(hass): diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index 7141a34203a00c..62fc30d9e4b06b 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -179,7 +179,7 @@ def _setup_test_switch(hass): def test_name(hass): """Test the name.""" switch, body_on, body_off = _setup_test_switch(hass) - assert NAME == switch.name + assert switch.name == NAME def test_is_on_before_update(hass): diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 44696e6f3706e2..a3d50d0214f39a 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -60,8 +60,8 @@ async def test_config_yaml_alias_anchor(hass, entities): assert light.is_on(hass, light_1.entity_id) assert light.is_on(hass, light_2.entity_id) - assert 100 == light_1.last_call("turn_on")[1].get("brightness") - assert 100 == light_2.last_call("turn_on")[1].get("brightness") + assert light_1.last_call("turn_on")[1].get("brightness") == 100 + assert light_2.last_call("turn_on")[1].get("brightness") == 100 async def test_config_yaml_bool(hass, entities): @@ -88,7 +88,7 @@ async def test_config_yaml_bool(hass, entities): assert light.is_on(hass, light_1.entity_id) assert light.is_on(hass, light_2.entity_id) - assert 100 == light_2.last_call("turn_on")[1].get("brightness") + assert light_2.last_call("turn_on")[1].get("brightness") == 100 async def test_activate_scene(hass, entities): diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 95703949525a1d..c1485a0c6ab0cf 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -173,7 +173,7 @@ def state_listener(entity_id, old_state, new_state): assert not script.is_on(hass, ENTITY_ID) assert was_on - assert 1 == event_mock.call_count + assert event_mock.call_count == 1 invalid_configs = [ @@ -190,7 +190,7 @@ async def test_setup_with_invalid_configs(hass, value): hass, "script", {"script": value} ), f"Script loaded with wrong config {value}" - assert 0 == len(hass.states.async_entity_ids("script")) + assert len(hass.states.async_entity_ids("script")) == 0 @pytest.mark.parametrize("running", ["no", "same", "different"]) @@ -587,7 +587,7 @@ async def async_service_handler(service): await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script2a" == service_values[-1] + assert service_values[-1] == "script2a" assert script.is_on(hass, "script.script1") assert script.is_on(hass, "script.script2") @@ -596,13 +596,13 @@ async def async_service_handler(service): await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script2b" == service_values[-1] + assert service_values[-1] == "script2b" hass.states.async_set("input_boolean.test1", "on") await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script1" == service_values[-1] + assert service_values[-1] == "script1" assert concurrently == script.is_on(hass, "script.script2") if concurrently: @@ -610,7 +610,7 @@ async def async_service_handler(service): await asyncio.wait_for(service_called.wait(), 1) service_called.clear() - assert "script2b" == service_values[-1] + assert service_values[-1] == "script2b" await hass.async_block_till_done() diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index d4581ae1fc74cf..f3a5d46f64bd0c 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -79,7 +79,7 @@ async def test_template_render_no_template(mock_call, hass): cmd = mock_call.mock_calls[0][1][0] assert mock_call.call_count == 1 - assert "ls /bin" == cmd + assert cmd == "ls /bin" @patch( diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index c158554b278c97..9b4092d9d48370 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -18,15 +18,15 @@ async def test_sensor_setup(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 2 == len(devices) + assert len(devices) == 2 left_side = devices[1] - assert "SleepNumber ILE Test1 Is In Bed" == left_side.name - assert "on" == left_side.state + assert left_side.name == "SleepNumber ILE Test1 Is In Bed" + assert left_side.state == "on" right_side = devices[0] - assert "SleepNumber ILE Test2 Is In Bed" == right_side.name - assert "off" == right_side.state + assert right_side.name == "SleepNumber ILE Test2 Is In Bed" + assert right_side.state == "off" async def test_setup_single(hass, requests_mock): @@ -38,8 +38,8 @@ async def test_setup_single(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 1 == len(devices) + assert len(devices) == 1 right_side = devices[0] - assert "SleepNumber ILE Test1 Is In Bed" == right_side.name - assert "on" == right_side.state + assert right_side.name == "SleepNumber ILE Test1 Is In Bed" + assert right_side.state == "on" diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index 559e808f5546fb..c6a584802b9f0d 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -18,15 +18,15 @@ async def test_setup(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 2 == len(devices) + assert len(devices) == 2 left_side = devices[1] - assert "SleepNumber ILE Test1 SleepNumber" == left_side.name - assert 40 == left_side.state + assert left_side.name == "SleepNumber ILE Test1 SleepNumber" + assert left_side.state == 40 right_side = devices[0] - assert "SleepNumber ILE Test2 SleepNumber" == right_side.name - assert 80 == right_side.state + assert right_side.name == "SleepNumber ILE Test2 SleepNumber" + assert right_side.state == 80 async def test_setup_sigle(hass, requests_mock): @@ -38,8 +38,8 @@ async def test_setup_sigle(hass, requests_mock): device_mock = MagicMock() sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) devices = device_mock.call_args[0][0] - assert 1 == len(devices) + assert len(devices) == 1 right_side = devices[0] - assert "SleepNumber ILE Test1 SleepNumber" == right_side.name - assert 40 == right_side.state + assert right_side.name == "SleepNumber ILE Test1 SleepNumber" + assert right_side.state == 40 diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index b6bebcfeeda46b..60de732cf7938f 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -115,7 +115,7 @@ def test_sensor_source(self): assert self.mean == state.attributes.get("mean") assert self.count == state.attributes.get("count") assert self.total == state.attributes.get("total") - assert TEMP_CELSIUS == state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS assert self.change == state.attributes.get("change") assert self.average_change == state.attributes.get("average_change") @@ -146,8 +146,8 @@ def test_sampling_size(self): state = self.hass.states.get("sensor.test") - assert 3.8 == state.attributes.get("min_value") - assert 14 == state.attributes.get("max_value") + assert state.attributes.get("min_value") == 3.8 + assert state.attributes.get("max_value") == 14 def test_sampling_size_1(self): """Test validity of stats requiring only one sample.""" @@ -182,12 +182,12 @@ def test_sampling_size_1(self): assert self.values[-1] == state.attributes.get("mean") assert self.values[-1] == state.attributes.get("median") assert self.values[-1] == state.attributes.get("total") - assert 0 == state.attributes.get("change") - assert 0 == state.attributes.get("average_change") + assert state.attributes.get("change") == 0 + assert state.attributes.get("average_change") == 0 # require at least two data points - assert STATE_UNKNOWN == state.attributes.get("variance") - assert STATE_UNKNOWN == state.attributes.get("standard_deviation") + assert state.attributes.get("variance") == STATE_UNKNOWN + assert state.attributes.get("standard_deviation") == STATE_UNKNOWN def test_max_age(self): """Test value deprecation.""" @@ -231,8 +231,8 @@ def mock_now(): state = self.hass.states.get("sensor.test") - assert 6 == state.attributes.get("min_value") - assert 14 == state.attributes.get("max_value") + assert state.attributes.get("min_value") == 6 + assert state.attributes.get("max_value") == 14 def test_max_age_without_sensor_change(self): """Test value deprecation.""" @@ -276,8 +276,8 @@ def mock_now(): state = self.hass.states.get("sensor.test") - assert 3.8 == state.attributes.get("min_value") - assert 15.2 == state.attributes.get("max_value") + assert state.attributes.get("min_value") == 3.8 + assert state.attributes.get("max_value") == 15.2 # wait for 3 minutes (max_age). mock_data["return_time"] += timedelta(minutes=3) diff --git a/tests/components/statsd/test_init.py b/tests/components/statsd/test_init.py index 17e49b09951c48..a0e5fd51669d41 100644 --- a/tests/components/statsd/test_init.py +++ b/tests/components/statsd/test_init.py @@ -39,7 +39,7 @@ async def test_statsd_setup_full(hass): assert mock_init.call_args == mock.call(host="host", port=123, prefix="foo") assert hass.bus.listen.called - assert EVENT_STATE_CHANGED == hass.bus.listen.call_args_list[0][0][0] + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED async def test_statsd_setup_defaults(hass): diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index b40eee7cab3051..c85600571e8937 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -395,7 +395,7 @@ async def test_if_fires_on_change_with_template_advanced(hass, calls): await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "template - test.entity - hello - world - None" == calls[0].data["some"] + assert calls[0].data["some"] == "template - test.entity - hello - world - None" async def test_if_fires_on_no_change_with_template_advanced(hass, calls): @@ -657,7 +657,7 @@ async def test_if_fires_on_change_with_for_advanced(hass, calls): await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "template - test.entity - hello - world - 0:00:05" == calls[0].data["some"] + assert calls[0].data["some"] == "template - test.entity - hello - world - 0:00:05" async def test_if_fires_on_change_with_for_0(hass, calls): diff --git a/tests/components/threshold/test_binary_sensor.py b/tests/components/threshold/test_binary_sensor.py index 66ab9e6cac1ff0..af8c32a1549a4f 100644 --- a/tests/components/threshold/test_binary_sensor.py +++ b/tests/components/threshold/test_binary_sensor.py @@ -24,12 +24,12 @@ async def test_sensor_upper(hass): state = hass.states.get("binary_sensor.threshold") - assert "sensor.test_monitored" == state.attributes.get("entity_id") - assert 16 == state.attributes.get("sensor_value") - assert "above" == state.attributes.get("position") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 0.0 == state.attributes.get("hysteresis") - assert "upper" == state.attributes.get("type") + assert state.attributes.get("entity_id") == "sensor.test_monitored" + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "above" + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "upper" assert state.state == "on" @@ -66,10 +66,10 @@ async def test_sensor_lower(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert 0.0 == state.attributes.get("hysteresis") - assert "lower" == state.attributes.get("type") + assert state.attributes.get("position") == "above" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "lower" assert state.state == "off" @@ -100,10 +100,10 @@ async def test_sensor_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 2.5 == state.attributes.get("hysteresis") - assert "upper" == state.attributes.get("type") + assert state.attributes.get("position") == "above" + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 2.5 + assert state.attributes.get("type") == "upper" assert state.state == "on" @@ -158,12 +158,12 @@ async def test_sensor_in_range_no_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") assert state.attributes.get("entity_id") == "sensor.test_monitored" - assert 16 == state.attributes.get("sensor_value") - assert "in_range" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 0.0 == state.attributes.get("hysteresis") - assert "range" == state.attributes.get("type") + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "in_range" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "range" assert state.state == "on" @@ -172,7 +172,7 @@ async def test_sensor_in_range_no_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "below" == state.attributes.get("position") + assert state.attributes.get("position") == "below" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 21) @@ -180,7 +180,7 @@ async def test_sensor_in_range_no_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") + assert state.attributes.get("position") == "above" assert state.state == "off" @@ -206,15 +206,15 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "sensor.test_monitored" == state.attributes.get("entity_id") - assert 16 == state.attributes.get("sensor_value") - assert "in_range" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert float(config["binary_sensor"]["hysteresis"]) == state.attributes.get( - "hysteresis" + assert state.attributes.get("entity_id") == "sensor.test_monitored" + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "in_range" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == float( + config["binary_sensor"]["hysteresis"] ) - assert "range" == state.attributes.get("type") + assert state.attributes.get("type") == "range" assert state.state == "on" @@ -223,7 +223,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" hass.states.async_set("sensor.test_monitored", 7) @@ -231,7 +231,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "below" == state.attributes.get("position") + assert state.attributes.get("position") == "below" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 12) @@ -239,7 +239,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "below" == state.attributes.get("position") + assert state.attributes.get("position") == "below" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 13) @@ -247,7 +247,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" hass.states.async_set("sensor.test_monitored", 22) @@ -255,7 +255,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" hass.states.async_set("sensor.test_monitored", 23) @@ -263,7 +263,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") + assert state.attributes.get("position") == "above" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 18) @@ -271,7 +271,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "above" == state.attributes.get("position") + assert state.attributes.get("position") == "above" assert state.state == "off" hass.states.async_set("sensor.test_monitored", 17) @@ -279,7 +279,7 @@ async def test_sensor_in_range_with_hysteresis(hass): state = hass.states.get("binary_sensor.threshold") - assert "in_range" == state.attributes.get("position") + assert state.attributes.get("position") == "in_range" assert state.state == "on" @@ -304,13 +304,13 @@ async def test_sensor_in_range_unknown_state(hass): state = hass.states.get("binary_sensor.threshold") - assert "sensor.test_monitored" == state.attributes.get("entity_id") - assert 16 == state.attributes.get("sensor_value") - assert "in_range" == state.attributes.get("position") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") - assert 0.0 == state.attributes.get("hysteresis") - assert "range" == state.attributes.get("type") + assert state.attributes.get("entity_id") == "sensor.test_monitored" + assert state.attributes.get("sensor_value") == 16 + assert state.attributes.get("position") == "in_range" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) + assert state.attributes.get("hysteresis") == 0.0 + assert state.attributes.get("type") == "range" assert state.state == "on" @@ -319,7 +319,7 @@ async def test_sensor_in_range_unknown_state(hass): state = hass.states.get("binary_sensor.threshold") - assert "unknown" == state.attributes.get("position") + assert state.attributes.get("position") == "unknown" assert state.state == "off" @@ -341,8 +341,8 @@ async def test_sensor_lower_zero_threshold(hass): state = hass.states.get("binary_sensor.threshold") - assert "lower" == state.attributes.get("type") - assert float(config["binary_sensor"]["lower"]) == state.attributes.get("lower") + assert state.attributes.get("type") == "lower" + assert state.attributes.get("lower") == float(config["binary_sensor"]["lower"]) assert state.state == "off" @@ -372,8 +372,8 @@ async def test_sensor_upper_zero_threshold(hass): state = hass.states.get("binary_sensor.threshold") - assert "upper" == state.attributes.get("type") - assert float(config["binary_sensor"]["upper"]) == state.attributes.get("upper") + assert state.attributes.get("type") == "upper" + assert state.attributes.get("upper") == float(config["binary_sensor"]["upper"]) assert state.state == "off" diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 21f48ed6147a01..92958a7bce6bde 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -120,16 +120,16 @@ async def test_config_options(hass): assert state_2 is not None assert state_3 is not None - assert STATUS_IDLE == state_1.state + assert state_1.state == STATUS_IDLE assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert STATUS_IDLE == state_2.state - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) - assert "0:00:10" == state_2.attributes.get(ATTR_DURATION) + assert state_2.state == STATUS_IDLE + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" + assert state_2.attributes.get(ATTR_DURATION) == "0:00:10" - assert STATUS_IDLE == state_3.state + assert state_3.state == STATUS_IDLE assert str(cv.time_period(DEFAULT_DURATION)) == state_3.attributes.get( CONF_DURATION ) @@ -280,14 +280,14 @@ async def test_config_reload(hass, hass_admin_user, hass_read_only_user): assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None - assert STATUS_IDLE == state_1.state + assert state_1.state == STATUS_IDLE assert ATTR_ICON not in state_1.attributes assert ATTR_FRIENDLY_NAME not in state_1.attributes - assert STATUS_IDLE == state_2.state - assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work" == state_2.attributes.get(ATTR_ICON) - assert "0:00:10" == state_2.attributes.get(ATTR_DURATION) + assert state_2.state == STATUS_IDLE + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work" + assert state_2.attributes.get(ATTR_DURATION) == "0:00:10" with patch( "homeassistant.config.load_yaml_config_file", @@ -331,12 +331,12 @@ async def test_config_reload(hass, hass_admin_user, hass_read_only_user): assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None - assert STATUS_IDLE == state_2.state - assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME) - assert "mdi:work-reloaded" == state_2.attributes.get(ATTR_ICON) - assert "0:00:20" == state_2.attributes.get(ATTR_DURATION) + assert state_2.state == STATUS_IDLE + assert state_2.attributes.get(ATTR_FRIENDLY_NAME) == "Hello World reloaded" + assert state_2.attributes.get(ATTR_ICON) == "mdi:work-reloaded" + assert state_2.attributes.get(ATTR_DURATION) == "0:00:20" - assert STATUS_IDLE == state_3.state + assert state_3.state == STATUS_IDLE assert ATTR_ICON not in state_3.attributes assert ATTR_FRIENDLY_NAME not in state_3.attributes diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index ba929c0bc54dbb..12ad53733b5413 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -54,14 +54,14 @@ async def test_arm_home_success(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True ) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_HOME == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_HOME async def test_arm_home_failure(hass): @@ -72,7 +72,7 @@ async def test_arm_home_failure(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -80,7 +80,7 @@ async def test_arm_home_failure(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to arm home test." - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_arm_home_invalid_usercode(hass): @@ -91,7 +91,7 @@ async def test_arm_home_invalid_usercode(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -99,7 +99,7 @@ async def test_arm_home_invalid_usercode(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to arm home test." - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_arm_away_success(hass): @@ -110,13 +110,13 @@ async def test_arm_away_success(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True ) await hass.async_block_till_done() - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY async def test_arm_away_failure(hass): @@ -127,7 +127,7 @@ async def test_arm_away_failure(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -135,7 +135,7 @@ async def test_arm_away_failure(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to arm away test." - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_disarm_success(hass): @@ -146,13 +146,13 @@ async def test_disarm_success(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY await hass.services.async_call( ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True ) await hass.async_block_till_done() - assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED async def test_disarm_failure(hass): @@ -163,7 +163,7 @@ async def test_disarm_failure(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -171,7 +171,7 @@ async def test_disarm_failure(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to disarm test." - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY async def test_disarm_invalid_usercode(hass): @@ -182,7 +182,7 @@ async def test_disarm_invalid_usercode(hass): side_effect=responses, ): await setup_platform(hass, ALARM_DOMAIN) - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY with pytest.raises(HomeAssistantError) as err: await hass.services.async_call( @@ -190,4 +190,4 @@ async def test_disarm_invalid_usercode(hass): ) await hass.async_block_till_done() assert f"{err.value}" == "TotalConnect failed to disarm test." - assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY diff --git a/tests/components/traccar/test_init.py b/tests/components/traccar/test_init.py index 0e741751b8ae45..f372358ea1db9f 100644 --- a/tests/components/traccar/test_init.py +++ b/tests/components/traccar/test_init.py @@ -114,7 +114,7 @@ async def test_enter_and_exit(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_HOME == state_name + assert state_name == STATE_HOME # Enter Home again req = await client.post(url, params=data) @@ -123,7 +123,7 @@ async def test_enter_and_exit(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_HOME == state_name + assert state_name == STATE_HOME data["lon"] = 0 data["lat"] = 0 @@ -135,7 +135,7 @@ async def test_enter_and_exit(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_NOT_HOME == state_name + assert state_name == STATE_NOT_HOME dev_reg = dr.async_get(hass) assert len(dev_reg.devices) == 1 @@ -237,7 +237,7 @@ async def test_load_unload_entry(hass, client, webhook_id): state_name = hass.states.get( "{}.{}".format(DEVICE_TRACKER_DOMAIN, data["id"]) ).state - assert STATE_HOME == state_name + assert state_name == STATE_HOME assert len(hass.data[DATA_DISPATCHER][TRACKER_UPDATE]) == 1 entry = hass.config_entries.async_entries(DOMAIN)[0] diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index 54bc122aa56281..9d179b5b06b4b7 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -53,11 +53,11 @@ async def test_bus(hass): bus_state = hass.states.get("sensor.next_bus_to_wantage") assert None is not bus_state - assert f"Next bus to {BUS_DIRECTION}" == bus_state.name - assert BUS_ATCOCODE == bus_state.attributes[ATTR_ATCOCODE] - assert "Harwell Campus" == bus_state.attributes[ATTR_LOCALITY] - assert "Bus Station" == bus_state.attributes[ATTR_STOP_NAME] - assert 2 == len(bus_state.attributes.get(ATTR_NEXT_BUSES)) + assert bus_state.name == f"Next bus to {BUS_DIRECTION}" + assert bus_state.attributes[ATTR_ATCOCODE] == BUS_ATCOCODE + assert bus_state.attributes[ATTR_LOCALITY] == "Harwell Campus" + assert bus_state.attributes[ATTR_STOP_NAME] == "Bus Station" + assert len(bus_state.attributes.get(ATTR_NEXT_BUSES)) == 2 direction_re = re.compile(BUS_DIRECTION) for bus in bus_state.attributes.get(ATTR_NEXT_BUSES): @@ -77,13 +77,13 @@ async def test_train(hass): train_state = hass.states.get("sensor.next_train_to_WAT") assert None is not train_state - assert f"Next train to {TRAIN_DESTINATION_NAME}" == train_state.name - assert TRAIN_STATION_CODE == train_state.attributes[ATTR_STATION_CODE] - assert TRAIN_DESTINATION_NAME == train_state.attributes[ATTR_CALLING_AT] - assert 25 == len(train_state.attributes.get(ATTR_NEXT_TRAINS)) + assert train_state.name == f"Next train to {TRAIN_DESTINATION_NAME}" + assert train_state.attributes[ATTR_STATION_CODE] == TRAIN_STATION_CODE + assert train_state.attributes[ATTR_CALLING_AT] == TRAIN_DESTINATION_NAME + assert len(train_state.attributes.get(ATTR_NEXT_TRAINS)) == 25 assert ( - "London Waterloo" - == train_state.attributes[ATTR_NEXT_TRAINS][0]["destination_name"] + train_state.attributes[ATTR_NEXT_TRAINS][0]["destination_name"] + == "London Waterloo" ) - assert "06:13" == train_state.attributes[ATTR_NEXT_TRAINS][0]["estimated"] + assert train_state.attributes[ATTR_NEXT_TRAINS][0]["estimated"] == "06:13" diff --git a/tests/components/unifi_direct/test_device_tracker.py b/tests/components/unifi_direct/test_device_tracker.py index 1f96e3fd84b819..9f594901e9418e 100644 --- a/tests/components/unifi_direct/test_device_tracker.py +++ b/tests/components/unifi_direct/test_device_tracker.py @@ -77,9 +77,9 @@ async def test_get_device_name(mock_ssh, hass): mock_ssh.return_value.before = load_fixture("unifi_direct.txt") scanner = get_scanner(hass, conf_dict) devices = scanner.scan_devices() - assert 23 == len(devices) - assert "iPhone" == scanner.get_device_name("98:00:c6:56:34:12") - assert "iPhone" == scanner.get_device_name("98:00:C6:56:34:12") + assert len(devices) == 23 + assert scanner.get_device_name("98:00:c6:56:34:12") == "iPhone" + assert scanner.get_device_name("98:00:C6:56:34:12") == "iPhone" @patch("pexpect.pxssh.pxssh.logout") diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 3914f580724bcc..054c3b8954135e 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -357,7 +357,7 @@ def add_entities(new_entities): except MultipleInvalid: setup_ok = False assert not setup_ok - assert 0 == len(entities) + assert len(entities) == 0 asyncio.run_coroutine_threadsafe( universal.async_setup_platform( @@ -365,8 +365,8 @@ def add_entities(new_entities): ), self.hass.loop, ).result() - assert 1 == len(entities) - assert "test" == entities[0].name + assert len(entities) == 1 + assert entities[0].name == "test" def test_master_state(self): """Test master state property.""" @@ -382,9 +382,9 @@ def test_master_state_with_attrs(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert STATE_OFF == ump.master_state + assert ump.master_state == STATE_OFF self.hass.states.set(self.mock_state_switch_id, STATE_ON) - assert STATE_ON == ump.master_state + assert ump.master_state == STATE_ON def test_master_state_with_bad_attrs(self): """Test master state property.""" @@ -394,7 +394,7 @@ def test_master_state_with_bad_attrs(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert STATE_OFF == ump.master_state + assert ump.master_state == STATE_OFF def test_active_child_state(self): """Test active child state property.""" @@ -454,7 +454,7 @@ def test_state_children_only(self): self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_PLAYING == ump.state + assert ump.state == STATE_PLAYING def test_state_with_children_and_attrs(self): """Test media player with children and master state.""" @@ -464,21 +464,21 @@ def test_state_with_children_and_attrs(self): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_OFF == ump.state + assert ump.state == STATE_OFF self.hass.states.set(self.mock_state_switch_id, STATE_ON) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_ON == ump.state + assert ump.state == STATE_ON self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_PLAYING == ump.state + assert ump.state == STATE_PLAYING self.hass.states.set(self.mock_state_switch_id, STATE_OFF) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert STATE_OFF == ump.state + assert ump.state == STATE_OFF def test_volume_level(self): """Test volume level property.""" @@ -494,13 +494,13 @@ def test_volume_level(self): self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 0 == ump.volume_level + assert ump.volume_level == 0 self.mock_mp_1._volume_level = 1 self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 1 == ump.volume_level + assert ump.volume_level == 1 def test_media_image_url(self): """Test media_image_url property.""" @@ -550,10 +550,10 @@ def test_sound_mode_list_children_and_attr(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "['music', 'movie']" == ump.sound_mode_list + assert ump.sound_mode_list == "['music', 'movie']" self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie", "game"]) - assert "['music', 'movie', 'game']" == ump.sound_mode_list + assert ump.sound_mode_list == "['music', 'movie', 'game']" def test_source_list_children_and_attr(self): """Test source list property w/ children and attrs.""" @@ -561,10 +561,10 @@ def test_source_list_children_and_attr(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "['dvd', 'htpc']" == ump.source_list + assert ump.source_list == "['dvd', 'htpc']" self.hass.states.set(self.mock_source_list_id, ["dvd", "htpc", "game"]) - assert "['dvd', 'htpc', 'game']" == ump.source_list + assert ump.source_list == "['dvd', 'htpc', 'game']" def test_sound_mode_children_and_attr(self): """Test sound modeproperty w/ children and attrs.""" @@ -572,10 +572,10 @@ def test_sound_mode_children_and_attr(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "music" == ump.sound_mode + assert ump.sound_mode == "music" self.hass.states.set(self.mock_sound_mode_id, "movie") - assert "movie" == ump.sound_mode + assert ump.sound_mode == "movie" def test_source_children_and_attr(self): """Test source property w/ children and attrs.""" @@ -583,10 +583,10 @@ def test_source_children_and_attr(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert "dvd" == ump.source + assert ump.source == "dvd" self.hass.states.set(self.mock_source_id, "htpc") - assert "htpc" == ump.source + assert ump.source == "htpc" def test_volume_level_children_and_attr(self): """Test volume level property w/ children and attrs.""" @@ -594,10 +594,10 @@ def test_volume_level_children_and_attr(self): ump = universal.UniversalMediaPlayer(self.hass, **config) - assert 0 == ump.volume_level + assert ump.volume_level == 0 self.hass.states.set(self.mock_volume_id, 100) - assert 100 == ump.volume_level + assert ump.volume_level == 100 def test_is_volume_muted_children_and_attr(self): """Test is volume muted property w/ children and attrs.""" @@ -618,14 +618,14 @@ def test_supported_features_children_only(self): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 0 == ump.supported_features + assert ump.supported_features == 0 self.mock_mp_1._supported_features = 512 self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - assert 512 == ump.supported_features + assert ump.supported_features == 512 def test_supported_features_children_and_cmds(self): """Test supported media commands with children and attrs.""" @@ -717,8 +717,8 @@ def test_service_call_no_active_child(self): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - assert 0 == len(self.mock_mp_1.service_calls["turn_off"]) - assert 0 == len(self.mock_mp_2.service_calls["turn_off"]) + assert len(self.mock_mp_1.service_calls["turn_off"]) == 0 + assert len(self.mock_mp_2.service_calls["turn_off"]) == 0 def test_service_call_to_child(self): """Test service calls that should be routed to a child.""" @@ -734,96 +734,96 @@ def test_service_call_to_child(self): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["turn_off"]) + assert len(self.mock_mp_2.service_calls["turn_off"]) == 1 asyncio.run_coroutine_threadsafe(ump.async_turn_on(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["turn_on"]) + assert len(self.mock_mp_2.service_calls["turn_on"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_mute_volume(True), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["mute_volume"]) + assert len(self.mock_mp_2.service_calls["mute_volume"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_set_volume_level(0.5), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["set_volume_level"]) + assert len(self.mock_mp_2.service_calls["set_volume_level"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_play(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_play"]) + assert len(self.mock_mp_2.service_calls["media_play"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_pause(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_pause"]) + assert len(self.mock_mp_2.service_calls["media_pause"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_stop(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_stop"]) + assert len(self.mock_mp_2.service_calls["media_stop"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_previous_track(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_previous_track"]) + assert len(self.mock_mp_2.service_calls["media_previous_track"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_next_track(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_next_track"]) + assert len(self.mock_mp_2.service_calls["media_next_track"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_seek(100), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_seek"]) + assert len(self.mock_mp_2.service_calls["media_seek"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_play_media("movie", "batman"), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["play_media"]) + assert len(self.mock_mp_2.service_calls["play_media"]) == 1 asyncio.run_coroutine_threadsafe(ump.async_volume_up(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["volume_up"]) + assert len(self.mock_mp_2.service_calls["volume_up"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_volume_down(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["volume_down"]) + assert len(self.mock_mp_2.service_calls["volume_down"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_media_play_pause(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["media_play_pause"]) + assert len(self.mock_mp_2.service_calls["media_play_pause"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_select_sound_mode("music"), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["select_sound_mode"]) + assert len(self.mock_mp_2.service_calls["select_sound_mode"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_select_source("dvd"), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["select_source"]) + assert len(self.mock_mp_2.service_calls["select_source"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_clear_playlist(), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["clear_playlist"]) + assert len(self.mock_mp_2.service_calls["clear_playlist"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_set_repeat(True), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["repeat_set"]) + assert len(self.mock_mp_2.service_calls["repeat_set"]) == 1 asyncio.run_coroutine_threadsafe( ump.async_set_shuffle(True), self.hass.loop ).result() - assert 1 == len(self.mock_mp_2.service_calls["shuffle_set"]) + assert len(self.mock_mp_2.service_calls["shuffle_set"]) == 1 asyncio.run_coroutine_threadsafe(ump.async_toggle(), self.hass.loop).result() - assert 1 == len(self.mock_mp_2.service_calls["toggle"]) + assert len(self.mock_mp_2.service_calls["toggle"]) == 1 def test_service_call_to_command(self): """Test service call to command.""" @@ -843,7 +843,7 @@ def test_service_call_to_command(self): asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - assert 1 == len(service) + assert len(service) == 1 async def test_state_template(hass): diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index 7fb0c90362a776..e0d71bf20a8311 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -73,35 +73,35 @@ def test_binary_sensor(self, mock): # Test pre data retrieval if device.subscription == "555555": - assert "Vultr {}" == device.name + assert device.name == "Vultr {}" device.update() device_attrs = device.extra_state_attributes if device.subscription == "555555": - assert "Vultr Another Server" == device.name + assert device.name == "Vultr Another Server" if device.name == "A Server": assert device.is_on is True - assert "power" == device.device_class - assert "on" == device.state - assert "mdi:server" == device.icon - assert "1000" == device_attrs[ATTR_ALLOWED_BANDWIDTH] - assert "yes" == device_attrs[ATTR_AUTO_BACKUPS] - assert "123.123.123.123" == device_attrs[ATTR_IPV4_ADDRESS] - assert "10.05" == device_attrs[ATTR_COST_PER_MONTH] - assert "2013-12-19 14:45:41" == device_attrs[ATTR_CREATED_AT] - assert "576965" == device_attrs[ATTR_SUBSCRIPTION_ID] + assert device.device_class == "power" + assert device.state == "on" + assert device.icon == "mdi:server" + assert device_attrs[ATTR_ALLOWED_BANDWIDTH] == "1000" + assert device_attrs[ATTR_AUTO_BACKUPS] == "yes" + assert device_attrs[ATTR_IPV4_ADDRESS] == "123.123.123.123" + assert device_attrs[ATTR_COST_PER_MONTH] == "10.05" + assert device_attrs[ATTR_CREATED_AT] == "2013-12-19 14:45:41" + assert device_attrs[ATTR_SUBSCRIPTION_ID] == "576965" elif device.name == "Failed Server": assert device.is_on is False - assert "off" == device.state - assert "mdi:server-off" == device.icon - assert "1000" == device_attrs[ATTR_ALLOWED_BANDWIDTH] - assert "no" == device_attrs[ATTR_AUTO_BACKUPS] - assert "192.168.100.50" == device_attrs[ATTR_IPV4_ADDRESS] - assert "73.25" == device_attrs[ATTR_COST_PER_MONTH] - assert "2014-10-13 14:45:41" == device_attrs[ATTR_CREATED_AT] - assert "123456" == device_attrs[ATTR_SUBSCRIPTION_ID] + assert device.state == "off" + assert device.icon == "mdi:server-off" + assert device_attrs[ATTR_ALLOWED_BANDWIDTH] == "1000" + assert device_attrs[ATTR_AUTO_BACKUPS] == "no" + assert device_attrs[ATTR_IPV4_ADDRESS] == "192.168.100.50" + assert device_attrs[ATTR_COST_PER_MONTH] == "73.25" + assert device_attrs[ATTR_CREATED_AT] == "2014-10-13 14:45:41" + assert device_attrs[ATTR_SUBSCRIPTION_ID] == "123456" def test_invalid_sensor_config(self): """Test config type failures.""" diff --git a/tests/components/vultr/test_sensor.py b/tests/components/vultr/test_sensor.py index 2b5e07c80b5dda..4449859ddb2e1a 100644 --- a/tests/components/vultr/test_sensor.py +++ b/tests/components/vultr/test_sensor.py @@ -73,7 +73,7 @@ def test_sensor(self, mock): assert setup is None - assert 5 == len(self.DEVICES) + assert len(self.DEVICES) == 5 tested = 0 @@ -87,34 +87,34 @@ def test_sensor(self, mock): if device.unit_of_measurement == DATA_GIGABYTES: # Test Bandwidth Used if device.subscription == "576965": - assert "Vultr my new server Current Bandwidth Used" == device.name - assert "mdi:chart-histogram" == device.icon - assert 131.51 == device.state - assert "mdi:chart-histogram" == device.icon + assert device.name == "Vultr my new server Current Bandwidth Used" + assert device.icon == "mdi:chart-histogram" + assert device.state == 131.51 + assert device.icon == "mdi:chart-histogram" tested += 1 elif device.subscription == "123456": - assert "Server Current Bandwidth Used" == device.name - assert 957.46 == device.state + assert device.name == "Server Current Bandwidth Used" + assert device.state == 957.46 tested += 1 elif device.unit_of_measurement == "US$": # Test Pending Charges if device.subscription == "576965": # Default 'Vultr {} {}' - assert "Vultr my new server Pending Charges" == device.name - assert "mdi:currency-usd" == device.icon - assert 46.67 == device.state - assert "mdi:currency-usd" == device.icon + assert device.name == "Vultr my new server Pending Charges" + assert device.icon == "mdi:currency-usd" + assert device.state == 46.67 + assert device.icon == "mdi:currency-usd" tested += 1 elif device.subscription == "123456": # Custom name with 1 {} - assert "Server Pending Charges" == device.name - assert "not a number" == device.state + assert device.name == "Server Pending Charges" + assert device.state == "not a number" tested += 1 elif device.subscription == "555555": # No {} in name - assert "VPS Charges" == device.name - assert 5.45 == device.state + assert device.name == "VPS Charges" + assert device.state == 5.45 tested += 1 assert tested == 5 @@ -161,4 +161,4 @@ def test_invalid_sensors(self, mock): ) assert no_sub_setup is None - assert 0 == len(self.DEVICES) + assert len(self.DEVICES) == 0 diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index 7b41fd4d75c599..1396ae80f1ef4d 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -41,7 +41,7 @@ async def test_valid_hostname(hass): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -53,7 +53,7 @@ async def test_valid_hostname(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON await hass.services.async_call( switch.DOMAIN, @@ -63,7 +63,7 @@ async def test_valid_hostname(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON async def test_valid_hostname_windows(hass): @@ -82,7 +82,7 @@ async def test_valid_hostname_windows(hass): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0), patch.object( platform, "system", return_value="Windows" @@ -95,7 +95,7 @@ async def test_valid_hostname_windows(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON async def test_broadcast_config_ip_and_port(hass, mock_send_magic_packet): @@ -119,7 +119,7 @@ async def test_broadcast_config_ip_and_port(hass, mock_send_magic_packet): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -155,7 +155,7 @@ async def test_broadcast_config_ip(hass, mock_send_magic_packet): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -183,7 +183,7 @@ async def test_broadcast_config_port(hass, mock_send_magic_packet): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -216,7 +216,7 @@ async def test_off_script(hass): calls = async_mock_service(hass, "shell_command", "turn_off_target") state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=0): @@ -228,7 +228,7 @@ async def test_off_script(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_ON == state.state + assert state.state == STATE_ON assert len(calls) == 0 with patch.object(subprocess, "call", return_value=2): @@ -241,7 +241,7 @@ async def test_off_script(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF assert len(calls) == 1 @@ -262,7 +262,7 @@ async def test_invalid_hostname_windows(hass): await hass.async_block_till_done() state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF with patch.object(subprocess, "call", return_value=2): @@ -274,7 +274,7 @@ async def test_invalid_hostname_windows(hass): ) state = hass.states.get("switch.wake_on_lan") - assert STATE_OFF == state.state + assert state.state == STATE_OFF async def test_no_hostname_state(hass): diff --git a/tests/components/xiaomi/test_device_tracker.py b/tests/components/xiaomi/test_device_tracker.py index af5e192b9cfe98..b5764fac089ba3 100644 --- a/tests/components/xiaomi/test_device_tracker.py +++ b/tests/components/xiaomi/test_device_tracker.py @@ -226,9 +226,9 @@ async def test_valid_credential(mock_get, mock_post, hass): } scanner = get_scanner(hass, config) assert scanner is not None - assert 2 == len(scanner.scan_devices()) - assert "Device1" == scanner.get_device_name("23:83:BF:F6:38:A0") - assert "Device2" == scanner.get_device_name("1D:98:EC:5E:D5:A6") + assert len(scanner.scan_devices()) == 2 + assert scanner.get_device_name("23:83:BF:F6:38:A0") == "Device1" + assert scanner.get_device_name("1D:98:EC:5E:D5:A6") == "Device2" @patch("requests.get", side_effect=mocked_requests) @@ -250,6 +250,6 @@ async def test_token_timed_out(mock_get, mock_post, hass): } scanner = get_scanner(hass, config) assert scanner is not None - assert 2 == len(scanner.scan_devices()) - assert "Device1" == scanner.get_device_name("23:83:BF:F6:38:A0") - assert "Device2" == scanner.get_device_name("1D:98:EC:5E:D5:A6") + assert len(scanner.scan_devices()) == 2 + assert scanner.get_device_name("23:83:BF:F6:38:A0") == "Device1" + assert scanner.get_device_name("1D:98:EC:5E:D5:A6") == "Device2" diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 3884605064381b..8d0fddb921c87c 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -144,7 +144,7 @@ async def test_active_zone_skips_passive_zones_2(hass): ) await hass.async_block_till_done() active = zone.async_active_zone(hass, 32.880700, -117.237561) - assert "zone.active_zone" == active.entity_id + assert active.entity_id == "zone.active_zone" async def test_active_zone_prefers_smaller_zone_if_same_distance(hass): @@ -173,7 +173,7 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance(hass): ) active = zone.async_active_zone(hass, latitude, longitude) - assert "zone.small_zone" == active.entity_id + assert active.entity_id == "zone.small_zone" async def test_active_zone_prefers_smaller_zone_if_same_distance_2(hass): @@ -196,7 +196,7 @@ async def test_active_zone_prefers_smaller_zone_if_same_distance_2(hass): ) active = zone.async_active_zone(hass, latitude, longitude) - assert "zone.smallest_zone" == active.entity_id + assert active.entity_id == "zone.smallest_zone" async def test_in_zone_works_for_passive_zones(hass): diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index 52fbb55ba97502..2b0d2f27afd293 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -84,7 +84,7 @@ async def test_if_fires_on_zone_enter(hass, calls): assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "zone - test.entity - hello - hello - test" == calls[0].data["some"] + assert calls[0].data["some"] == "zone - test.entity - hello - hello - test" # Set out of zone again so we can trigger call hass.states.async_set( diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index f58fd1b22f92ae..232d7bbb8b6032 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -934,7 +934,7 @@ def test_socket_timeout(): # pylint: disable=invalid-name with pytest.raises(vol.Invalid): schema(-1) - assert _GLOBAL_DEFAULT_TIMEOUT == schema(None) + assert schema(None) == _GLOBAL_DEFAULT_TIMEOUT assert schema(1) == 1.0 diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 4259e7302ed9fc..193c7e2aa55d96 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -880,41 +880,38 @@ def test_relative_time(mock_is_safe, hass): """Test relative_time method.""" now = datetime.strptime("2000-01-01 10:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") with patch("homeassistant.util.dt.now", return_value=now): - assert ( - "1 hour" - == template.Template( - '{{relative_time(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}', - hass, - ).async_render() - ) - assert ( - "2 hours" - == template.Template( - '{{relative_time(strptime("2000-01-01 09:00:00 +01:00", "%Y-%m-%d %H:%M:%S %z"))}}', - hass, - ).async_render() - ) - assert ( - "1 hour" - == template.Template( - '{{relative_time(strptime("2000-01-01 03:00:00 -06:00", "%Y-%m-%d %H:%M:%S %z"))}}', - hass, - ).async_render() - ) - assert ( - str(template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z")) - == template.Template( - '{{relative_time(strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z"))}}', - hass, - ).async_render() - ) - assert ( - "string" - == template.Template( - '{{relative_time("string")}}', - hass, - ).async_render() + result = template.Template( + '{{relative_time(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}', + hass, + ).async_render() + assert result == "1 hour" + + result = template.Template( + '{{relative_time(strptime("2000-01-01 09:00:00 +01:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + assert result == "2 hours" + + result = template.Template( + '{{relative_time(strptime("2000-01-01 03:00:00 -06:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + assert result == "1 hour" + + result1 = str( + template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) + result2 = template.Template( + '{{relative_time(strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z"))}}', + hass, + ).async_render() + assert result1 == result2 + + result = template.Template( + '{{relative_time("string")}}', + hass, + ).async_render() + assert result == "string" @patch( @@ -925,55 +922,46 @@ def test_timedelta(mock_is_safe, hass): """Test relative_time method.""" now = datetime.strptime("2000-01-01 10:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") with patch("homeassistant.util.dt.now", return_value=now): - assert ( - "0:02:00" - == template.Template( - "{{timedelta(seconds=120)}}", - hass, - ).async_render() - ) - assert ( - "1 day, 0:00:00" - == template.Template( - "{{timedelta(seconds=86400)}}", - hass, - ).async_render() - ) - assert ( - "1 day, 4:00:00" - == template.Template( - "{{timedelta(days=1, hours=4)}}", - hass, - ).async_render() - ) - assert ( - "1 hour" - == template.Template( - "{{relative_time(now() - timedelta(seconds=3600))}}", - hass, - ).async_render() - ) - assert ( - "1 day" - == template.Template( - "{{relative_time(now() - timedelta(seconds=86400))}}", - hass, - ).async_render() - ) - assert ( - "1 day" - == template.Template( - "{{relative_time(now() - timedelta(seconds=86401))}}", - hass, - ).async_render() - ) - assert ( - "15 days" - == template.Template( - "{{relative_time(now() - timedelta(weeks=2, days=1))}}", - hass, - ).async_render() - ) + result = template.Template( + "{{timedelta(seconds=120)}}", + hass, + ).async_render() + assert result == "0:02:00" + + result = template.Template( + "{{timedelta(seconds=86400)}}", + hass, + ).async_render() + assert result == "1 day, 0:00:00" + + result = template.Template( + "{{timedelta(days=1, hours=4)}}", hass + ).async_render() + assert result == "1 day, 4:00:00" + + result = template.Template( + "{{relative_time(now() - timedelta(seconds=3600))}}", + hass, + ).async_render() + assert result == "1 hour" + + result = template.Template( + "{{relative_time(now() - timedelta(seconds=86400))}}", + hass, + ).async_render() + assert result == "1 day" + + result = template.Template( + "{{relative_time(now() - timedelta(seconds=86401))}}", + hass, + ).async_render() + assert result == "1 day" + + result = template.Template( + "{{relative_time(now() - timedelta(weeks=2, days=1))}}", + hass, + ).async_render() + assert result == "15 days" def test_regex_match(hass): diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 1327bc51f8a97a..5001301220136d 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -19,7 +19,7 @@ def test_get_time_zone_retrieves_valid_time_zone(): time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) assert time_zone is not None - assert TEST_TIME_ZONE == time_zone.zone + assert time_zone.zone == TEST_TIME_ZONE def test_get_time_zone_returns_none_for_garbage_time_zone(): diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index e0e4524a2f2e53..74abfef452faed 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -171,11 +171,11 @@ def test_pressure_to_imperial(): def test_properties(): """Test the unit properties are returned as expected.""" - assert LENGTH_KILOMETERS == METRIC_SYSTEM.length_unit - assert TEMP_CELSIUS == METRIC_SYSTEM.temperature_unit - assert MASS_GRAMS == METRIC_SYSTEM.mass_unit - assert VOLUME_LITERS == METRIC_SYSTEM.volume_unit - assert PRESSURE_PA == METRIC_SYSTEM.pressure_unit + assert METRIC_SYSTEM.length_unit == LENGTH_KILOMETERS + assert METRIC_SYSTEM.temperature_unit == TEMP_CELSIUS + assert METRIC_SYSTEM.mass_unit == MASS_GRAMS + assert METRIC_SYSTEM.volume_unit == VOLUME_LITERS + assert METRIC_SYSTEM.pressure_unit == PRESSURE_PA def test_is_metric(): From 863f75e65ef12750fd6e11ba32731c5150291499 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 20 Mar 2021 14:22:01 +0100 Subject: [PATCH 1404/1818] Improve test coverage of deconz_device (#48141) --- .../components/deconz/deconz_event.py | 5 +- tests/components/deconz/test_deconz_event.py | 30 +++++++++ tests/components/deconz/test_services.py | 67 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 81d3aa94d31771..706850477d8a15 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -106,8 +106,11 @@ def async_update_callback(self, force_update=False): self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) - async def async_update_device_registry(self): + async def async_update_device_registry(self) -> None: """Update device registry.""" + if not self.device_info: + return + device_registry = ( await self.gateway.hass.helpers.device_registry.async_get_registry() ) diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 8a2e6a1d465c25..fc7544f39185eb 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -171,3 +171,33 @@ async def test_deconz_events(hass, aioclient_mock, mock_deconz_websocket): 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_deconz_events_bad_unique_id(hass, aioclient_mock, mock_deconz_websocket): + """Verify no devices are created if unique id is bad or missing.""" + data = { + "sensors": { + "1": { + "name": "Switch 1 no unique id", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {}, + }, + "2": { + "name": "Switch 2 bad unique id", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00-00", + }, + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + + assert len(hass.states.async_all()) == 1 + assert ( + len(async_entries_for_config_entry(device_registry, config_entry.entry_id)) == 2 + ) diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index a631327351f83c..249f4dbbb574da 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -8,6 +8,7 @@ CONF_BRIDGE_ID, DOMAIN as DECONZ_DOMAIN, ) +from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT from homeassistant.components.deconz.services import ( DECONZ_SERVICES, SERVICE_CONFIGURE_DEVICE, @@ -31,6 +32,8 @@ setup_deconz_integration, ) +from tests.common import async_capture_events + async def test_service_setup(hass): """Verify service setup works.""" @@ -229,6 +232,70 @@ async def test_service_refresh_devices(hass, aioclient_mock): assert len(hass.states.async_all()) == 4 +async def test_service_refresh_devices_trigger_no_state_update(hass, aioclient_mock): + """Verify that gateway.ignore_state_updates are honored.""" + data = { + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 1 + + captured_events = async_capture_events(hass, CONF_DECONZ_EVENT) + + aioclient_mock.clear_requests() + + data = { + "groups": { + "1": { + "id": "Group 1 id", + "name": "Group 1 name", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [{"id": "1", "name": "Scene 1"}], + "lights": ["1"], + } + }, + "lights": { + "1": { + "name": "Light 1 name", + "state": {"reachable": True}, + "type": "Light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + "sensors": { + "1": { + "name": "Switch 1", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:01-00", + } + }, + } + + mock_deconz_request(aioclient_mock, config_entry.data, data) + + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH, service_data={CONF_BRIDGE_ID: BRIDGEID} + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 4 + assert len(captured_events) == 0 + + async def test_remove_orphaned_entries_service(hass, aioclient_mock): """Test service works and also don't remove more than expected.""" data = { From f8755a52c2c2a548f8e6c48f94b3662082fa4213 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 20 Mar 2021 15:16:04 +0100 Subject: [PATCH 1405/1818] Warn on undefined variables in templates (#48140) * Warn on undefined variables in templates * Add test * fix tests * fix tests --- homeassistant/helpers/template.py | 2 +- tests/components/influxdb/test_sensor.py | 16 +++++++++++++--- tests/helpers/test_script.py | 3 +-- tests/helpers/test_template.py | 7 +++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index cfb7223752ea8a..24c91ec54682cd 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1318,7 +1318,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): def __init__(self, hass, limited=False): """Initialise template environment.""" - super().__init__() + super().__init__(undefined=jinja2.make_logging_undefined(logger=_LOGGER)) self.hass = hass self.template_cache = weakref.WeakValueDictionary() self.filters["round"] = forgiving_round diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index 55172011f4fb4f..9a353f59e424ae 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -442,7 +442,7 @@ async def test_error_querying_influx( @pytest.mark.parametrize( - "mock_client, config_ext, queries, set_query_mock, make_resultset", + "mock_client, config_ext, queries, set_query_mock, make_resultset, key", [ ( DEFAULT_API_VERSION, @@ -459,6 +459,7 @@ async def test_error_querying_influx( }, _set_query_mock_v1, _make_v1_resultset, + "where", ), ( API_VERSION_2, @@ -466,12 +467,13 @@ async def test_error_querying_influx( {"queries_flux": [{"name": "test", "query": "{{ illegal.template }}"}]}, _set_query_mock_v2, _make_v2_resultset, + "query", ), ], indirect=["mock_client"], ) async def test_error_rendering_template( - hass, caplog, mock_client, config_ext, queries, set_query_mock, make_resultset + hass, caplog, mock_client, config_ext, queries, set_query_mock, make_resultset, key ): """Test behavior of sensor with error rendering template.""" set_query_mock(mock_client, return_value=make_resultset(42)) @@ -479,7 +481,15 @@ async def test_error_rendering_template( sensors = await _setup(hass, config_ext, queries, ["sensor.test"]) assert sensors[0].state == STATE_UNKNOWN assert ( - len([record for record in caplog.records if record.levelname == "ERROR"]) == 1 + len( + [ + record + for record in caplog.records + if record.levelname == "ERROR" + and f"Could not render {key} template" in record.msg + ] + ) + == 1 ) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 1f47aa9dbbaf2f..8a93d769775431 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1277,8 +1277,7 @@ async def test_repeat_condition_warning(hass, caplog, condition): hass.async_create_task(script_obj.async_run(context=Context())) await asyncio.wait_for(hass.async_block_till_done(), 1) - assert len(caplog.record_tuples) == 1 - assert caplog.record_tuples[0][1] == logging.WARNING + assert f"Error in '{condition}[0]' evaluation" in caplog.text assert len(events) == count diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 193c7e2aa55d96..da6a8663cc3ce0 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2497,3 +2497,10 @@ async def test_parse_result(hass): ("0011101.00100001010001", "0011101.00100001010001"), ): assert template.Template(tpl, hass).async_render() == result + + +async def test_undefined_variable(hass, caplog): + """Test a warning is logged on undefined variables.""" + tpl = template.Template("{{ no_such_variable }}", hass) + assert tpl.async_render() == "" + assert "Template variable warning: no_such_variable is undefined" in caplog.text From 08870690a6ed746601110ea2aec7a9fadc3c3350 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 20 Mar 2021 17:23:55 +0100 Subject: [PATCH 1406/1818] Fix a collection of tests with missing asserts (#48127) Co-authored-by: Martin Hjelmare --- tests/components/hassio/test_http.py | 8 ++++---- tests/components/logger/test_init.py | 6 +++--- tests/components/recorder/test_purge.py | 12 ++++++------ tests/components/recorder/test_util.py | 6 ++---- tests/components/tod/test_binary_sensor.py | 6 +++--- tests/components/universal/test_media_player.py | 8 ++++---- tests/helpers/test_device_registry.py | 2 +- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/tests/components/hassio/test_http.py b/tests/components/hassio/test_http.py index 2ec964d8e8b0b8..ff1c348a37bb32 100644 --- a/tests/components/hassio/test_http.py +++ b/tests/components/hassio/test_http.py @@ -128,8 +128,8 @@ async def test_forwarding_user_info(hassio_client, hass_admin_user, aioclient_mo assert len(aioclient_mock.mock_calls) == 1 req_headers = aioclient_mock.mock_calls[0][-1] - req_headers["X-Hass-User-ID"] == hass_admin_user.id - req_headers["X-Hass-Is-Admin"] == "1" + assert req_headers["X-Hass-User-ID"] == hass_admin_user.id + assert req_headers["X-Hass-Is-Admin"] == "1" async def test_snapshot_upload_headers(hassio_client, aioclient_mock): @@ -147,7 +147,7 @@ async def test_snapshot_upload_headers(hassio_client, aioclient_mock): assert len(aioclient_mock.mock_calls) == 1 req_headers = aioclient_mock.mock_calls[0][-1] - req_headers["Content-Type"] == content_type + assert req_headers["Content-Type"] == content_type async def test_snapshot_download_headers(hassio_client, aioclient_mock): @@ -168,7 +168,7 @@ async def test_snapshot_download_headers(hassio_client, aioclient_mock): assert len(aioclient_mock.mock_calls) == 1 - resp.headers["Content-Disposition"] == content_disposition + assert resp.headers["Content-Disposition"] == content_disposition def test_need_auth(hass): diff --git a/tests/components/logger/test_init.py b/tests/components/logger/test_init.py index abb8c9b0de8fb2..61818d57df96a7 100644 --- a/tests/components/logger/test_init.py +++ b/tests/components/logger/test_init.py @@ -165,9 +165,9 @@ async def test_can_set_level(hass): logger.DOMAIN, "set_level", {f"{UNCONFIG_NS}.any": "debug"}, blocking=True ) - logging.getLogger(UNCONFIG_NS).level == logging.NOTSET - logging.getLogger(f"{UNCONFIG_NS}.any").level == logging.DEBUG - logging.getLogger(UNCONFIG_NS).level == logging.NOTSET + assert logging.getLogger(UNCONFIG_NS).level == logging.NOTSET + assert logging.getLogger(f"{UNCONFIG_NS}.any").level == logging.DEBUG + assert logging.getLogger(UNCONFIG_NS).level == logging.NOTSET await hass.services.async_call( logger.DOMAIN, "set_default_level", {"level": "debug"}, blocking=True diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index e487e542958e09..f2fa9bf6400263 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -364,9 +364,9 @@ def _add_db_entries(hass: HomeAssistantType) -> None: ) assert states_sensor_excluded.count() == 0 - session.query(States).get(71).old_state_id is None - session.query(States).get(72).old_state_id is None - session.query(States).get(73).old_state_id == 62 # should have been keeped + assert session.query(States).get(72).old_state_id is None + assert session.query(States).get(73).old_state_id is None + assert session.query(States).get(74).old_state_id == 62 # should have been kept async def test_purge_filtered_events( @@ -550,9 +550,9 @@ def _add_db_entries(hass: HomeAssistantType) -> None: assert events_purge.count() == 0 assert states.count() == 3 - session.query(States).get(61).old_state_id is None - session.query(States).get(62).old_state_id is None - session.query(States).get(63).old_state_id == 62 # should have been keeped + assert session.query(States).get(61).old_state_id is None + assert session.query(States).get(62).old_state_id is None + assert session.query(States).get(63).old_state_id == 62 # should have been kept async def _add_test_states(hass: HomeAssistantType, instance: recorder.Recorder): diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 4aba6569a413f5..c814570416c32b 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -88,8 +88,7 @@ def test_validate_or_move_away_sqlite_database_with_integrity_check( test_db_file = f"{test_dir}/broken.db" dburl = f"{SQLITE_URL_PREFIX}{test_db_file}" - util.validate_sqlite_database(test_db_file, db_integrity_check) is True - + assert util.validate_sqlite_database(test_db_file, db_integrity_check) is False assert os.path.exists(test_db_file) is True assert ( util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is False @@ -125,8 +124,7 @@ def test_validate_or_move_away_sqlite_database_without_integrity_check( test_db_file = f"{test_dir}/broken.db" dburl = f"{SQLITE_URL_PREFIX}{test_db_file}" - util.validate_sqlite_database(test_db_file, db_integrity_check) is True - + assert util.validate_sqlite_database(test_db_file, db_integrity_check) is False assert os.path.exists(test_db_file) is True assert ( util.validate_or_move_away_sqlite_database(dburl, db_integrity_check) is False diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index dff1e208ab6372..363d159b811c67 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -903,7 +903,7 @@ async def test_dst(hass): await hass.async_block_till_done() state = hass.states.get(entity_id) - state.attributes["after"] == "2019-03-31T03:30:00+02:00" - state.attributes["before"] == "2019-03-31T03:40:00+02:00" - state.attributes["next_update"] == "2019-03-31T03:30:00+02:00" + assert state.attributes["after"] == "2019-03-31T03:30:00+02:00" + assert state.attributes["before"] == "2019-03-31T03:40:00+02:00" + assert state.attributes["next_update"] == "2019-03-31T03:30:00+02:00" assert state.state == STATE_OFF diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 054c3b8954135e..7bf19116d938c2 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -944,7 +944,7 @@ async def test_master_state_with_template(hass): await hass.async_start() await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_ON + assert hass.states.get("media_player.tv").state == STATE_ON events = [] @@ -956,7 +956,7 @@ async def test_master_state_with_template(hass): hass.states.async_set("input_boolean.test", STATE_ON, context=context) await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_OFF + assert hass.states.get("media_player.tv").state == STATE_OFF assert events[0].context == context @@ -987,12 +987,12 @@ async def test_reload(hass): await hass.async_start() await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_ON + assert hass.states.get("media_player.tv").state == STATE_ON hass.states.async_set("input_boolean.test", STATE_ON) await hass.async_block_till_done() - hass.states.get("media_player.tv").state == STATE_OFF + assert hass.states.get("media_player.tv").state == STATE_OFF hass.states.async_set("media_player.master_bedroom_2", STATE_OFF) hass.states.async_set( diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 1d1b1a3cca3c3b..c5328000269b11 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -573,7 +573,7 @@ async def test_loading_saving_data(hass, registry, area_registry): orig_kitchen_light_witout_suggested_area = registry.async_update_device( orig_kitchen_light.id, suggested_area=None ) - orig_kitchen_light_witout_suggested_area.suggested_area is None + assert orig_kitchen_light_witout_suggested_area.suggested_area is None assert orig_kitchen_light_witout_suggested_area == new_kitchen_light From 01fcc41aa067bce649721fa1d04c800b6576eb02 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sat, 20 Mar 2021 17:26:23 -0400 Subject: [PATCH 1407/1818] only block coord removal if it is active (#48147) --- homeassistant/components/zha/api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 45f7d540052ddc..c8839acf8562e6 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -60,6 +60,7 @@ get_matched_clusters, qr_to_install_code, ) +from .core.typing import ZhaDeviceType, ZhaGatewayType _LOGGER = logging.getLogger(__name__) @@ -892,9 +893,12 @@ async def permit(service): async def remove(service): """Remove a node from the network.""" ieee = service.data[ATTR_IEEE] - zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] - zha_device = zha_gateway.get_device(ieee) - if zha_device is not None and zha_device.is_coordinator: + zha_gateway: ZhaGatewayType = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + zha_device: ZhaDeviceType = zha_gateway.get_device(ieee) + if zha_device is not None and ( + zha_device.is_coordinator + and zha_device.ieee == zha_gateway.application_controller.ieee + ): _LOGGER.info("Removing the coordinator (%s) is not allowed", ieee) return _LOGGER.info("Removing node %s", ieee) From 3ae946013190954fd9885ec386daa00b10cec9dc Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Sun, 21 Mar 2021 00:34:46 +0100 Subject: [PATCH 1408/1818] Use domain const in config_flow (#48168) --- homeassistant/components/ambiclimate/config_flow.py | 2 +- homeassistant/components/daikin/__init__.py | 4 +--- homeassistant/components/daikin/config_flow.py | 4 ++-- homeassistant/components/daikin/const.py | 2 ++ homeassistant/components/mqtt/config_flow.py | 3 ++- homeassistant/components/point/config_flow.py | 2 +- homeassistant/components/tellduslive/config_flow.py | 2 +- homeassistant/components/tradfri/config_flow.py | 3 ++- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 8550497148912a..a6dbe60a761376 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -38,7 +38,7 @@ def register_flow_implementation(hass, client_id, client_secret): } -@config_entries.HANDLERS.register("ambiclimate") +@config_entries.HANDLERS.register(DOMAIN) class AmbiclimateFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index d0bc109c0822ab..092bbf8866d40c 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -16,12 +16,10 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle -from .const import CONF_UUID, KEY_MAC, TIMEOUT +from .const import CONF_UUID, DOMAIN, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) -DOMAIN = "daikin" - PARALLEL_UPDATES = 0 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index 155fdd18376ad9..619f9c8d1d8b4e 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -12,12 +12,12 @@ from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD -from .const import CONF_UUID, KEY_MAC, TIMEOUT +from .const import CONF_UUID, DOMAIN, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) -@config_entries.HANDLERS.register("daikin") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/daikin/const.py b/homeassistant/components/daikin/const.py index 00bbbefd051381..5b4bdd28331b29 100644 --- a/homeassistant/components/daikin/const.py +++ b/homeassistant/components/daikin/const.py @@ -14,6 +14,8 @@ TEMP_CELSIUS, ) +DOMAIN = "daikin" + ATTR_TARGET_TEMPERATURE = "target_temperature" ATTR_INSIDE_TEMPERATURE = "inside_temperature" ATTR_OUTSIDE_TEMPERATURE = "outside_temperature" diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index e19aaecc3db51b..5e5b8c54cf2493 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -27,11 +27,12 @@ DEFAULT_BIRTH, DEFAULT_DISCOVERY, DEFAULT_WILL, + DOMAIN, ) from .util import MQTT_WILL_BIRTH_SCHEMA -@config_entries.HANDLERS.register("mqtt") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index aaefc45bc9cc68..1f3cf2a751d084 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -40,7 +40,7 @@ def register_flow_implementation(hass, domain, client_id, client_secret): } -@config_entries.HANDLERS.register("point") +@config_entries.HANDLERS.register(DOMAIN) class PointFlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/tellduslive/config_flow.py b/homeassistant/components/tellduslive/config_flow.py index aabbf88ee1c5b9..33a02cd1f16ff0 100644 --- a/homeassistant/components/tellduslive/config_flow.py +++ b/homeassistant/components/tellduslive/config_flow.py @@ -29,7 +29,7 @@ _LOGGER = logging.getLogger(__name__) -@config_entries.HANDLERS.register("tellduslive") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 7947c3ad6deb6d..e02ac69de365dd 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -15,6 +15,7 @@ CONF_IDENTITY, CONF_IMPORT_GROUPS, CONF_KEY, + DOMAIN, KEY_SECURITY_CODE, ) @@ -28,7 +29,7 @@ def __init__(self, code): self.code = code -@config_entries.HANDLERS.register("tradfri") +@config_entries.HANDLERS.register(DOMAIN) class FlowHandler(config_entries.ConfigFlow): """Handle a config flow.""" From 46a3b80a2dbe217d35d906ce7a29c5ae0aa47523 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 21 Mar 2021 00:05:03 +0000 Subject: [PATCH 1409/1818] [ci skip] Translation update --- homeassistant/components/accuweather/translations/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json index 814e57d1d6cbb0..330f2850d26e81 100644 --- a/homeassistant/components/accuweather/translations/de.json +++ b/homeassistant/components/accuweather/translations/de.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Verbindung fehlgeschlagen", - "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel", + "requests_exceeded": "Die zul\u00e4ssige Anzahl von Anforderungen an die Accuweather-API wurde \u00fcberschritten. Sie m\u00fcssen warten oder den API-Schl\u00fcssel \u00e4ndern." }, "step": { "user": { From 0193f16ae98a2783715208c25a98c22830e1df26 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 21 Mar 2021 01:49:03 +0100 Subject: [PATCH 1410/1818] ESPHome trigger reconnect immediately when mDNS record received (#48129) --- homeassistant/components/esphome/__init__.py | 264 ++++++++++++++---- .../components/esphome/entry_data.py | 1 - 2 files changed, 204 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index d895fc9216d7ae..0cb6cd94e083c3 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -18,6 +18,7 @@ UserServiceArgType, ) import voluptuous as vol +from zeroconf import DNSPointer, DNSRecord, RecordUpdateListener, Zeroconf from homeassistant import const from homeassistant.components import zeroconf @@ -199,7 +200,9 @@ async def on_login() -> None: # Re-connection logic will trigger after this await cli.disconnect() - try_connect = await _setup_auto_reconnect_logic(hass, cli, entry, host, on_login) + reconnect_logic = ReconnectLogic( + hass, cli, entry, host, on_login, zeroconf_instance + ) async def complete_setup() -> None: """Complete the config entry setup.""" @@ -207,85 +210,228 @@ async def complete_setup() -> None: await entry_data.async_update_static_infos(hass, entry, infos) await _setup_services(hass, entry_data, services) - # Create connection attempt outside of HA's tracked task in order - # not to delay startup. - hass.loop.create_task(try_connect(is_disconnect=False)) + await reconnect_logic.start() + entry_data.cleanup_callbacks.append(reconnect_logic.stop_callback) hass.async_create_task(complete_setup()) return True -async def _setup_auto_reconnect_logic( - hass: HomeAssistantType, cli: APIClient, entry: ConfigEntry, host: str, on_login -): - """Set up the re-connect logic for the API client.""" +class ReconnectLogic(RecordUpdateListener): + """Reconnectiong logic handler for ESPHome config entries. - async def try_connect(tries: int = 0, is_disconnect: bool = True) -> None: - """Try connecting to the API client. Will retry if not successful.""" - if entry.entry_id not in hass.data[DOMAIN]: - # When removing/disconnecting manually - return + Contains two reconnect strategies: + - Connect with increasing time between connection attempts. + - Listen to zeroconf mDNS records, if any records are found for this device, try reconnecting immediately. + """ - device_registry = await hass.helpers.device_registry.async_get_registry() - devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id) - for device in devices: - # There is only one device in ESPHome - if device.disabled: - # Don't attempt to connect if it's disabled - return + def __init__( + self, + hass: HomeAssistantType, + cli: APIClient, + entry: ConfigEntry, + host: str, + on_login, + zc: Zeroconf, + ): + """Initialize ReconnectingLogic.""" + self._hass = hass + self._cli = cli + self._entry = entry + self._host = host + self._on_login = on_login + self._zc = zc + # Flag to check if the device is connected + self._connected = True + self._connected_lock = asyncio.Lock() + # Event the different strategies use for issuing a reconnect attempt. + self._reconnect_event = asyncio.Event() + # The task containing the infinite reconnect loop while running + self._loop_task: asyncio.Task | None = None + # How many reconnect attempts have there been already, used for exponential wait time + self._tries = 0 + self._tries_lock = asyncio.Lock() + # Track the wait task to cancel it on HA shutdown + self._wait_task: asyncio.Task | None = None + self._wait_task_lock = asyncio.Lock() + + @property + def _entry_data(self) -> RuntimeEntryData | None: + return self._hass.data[DOMAIN].get(self._entry.entry_id) - data: RuntimeEntryData = hass.data[DOMAIN][entry.entry_id] - for disconnect_cb in data.disconnect_callbacks: + async def _on_disconnect(self): + """Log and issue callbacks when disconnecting.""" + if self._entry_data is None: + return + # This can happen often depending on WiFi signal strength. + # So therefore all these connection warnings are logged + # as infos. The "unavailable" logic will still trigger so the + # user knows if the device is not connected. + _LOGGER.info("Disconnected from ESPHome API for %s", self._host) + + # Run disconnect hooks + for disconnect_cb in self._entry_data.disconnect_callbacks: disconnect_cb() - data.disconnect_callbacks = [] - data.available = False - data.async_update_device_state(hass) - - if is_disconnect: - # This can happen often depending on WiFi signal strength. - # So therefore all these connection warnings are logged - # as infos. The "unavailable" logic will still trigger so the - # user knows if the device is not connected. - _LOGGER.info("Disconnected from ESPHome API for %s", host) - - if tries != 0: - # If not first re-try, wait and print message - # Cap wait time at 1 minute. This is because while working on the - # device (e.g. soldering stuff), users don't want to have to wait - # a long time for their device to show up in HA again (this was - # mentioned a lot in early feedback) - # - # In the future another API will be set up so that the ESP can - # notify HA of connectivity directly, but for new we'll use a - # really short reconnect interval. - tries = min(tries, 10) # prevent OverflowError - wait_time = int(round(min(1.8 ** tries, 60.0))) - if tries == 1: - _LOGGER.info("Trying to reconnect to %s in the background", host) - _LOGGER.debug("Retrying %s in %d seconds", host, wait_time) - await asyncio.sleep(wait_time) + self._entry_data.disconnect_callbacks = [] + self._entry_data.available = False + self._entry_data.async_update_device_state(self._hass) + + # Reset tries + async with self._tries_lock: + self._tries = 0 + # Connected needs to be reset before the reconnect event (opposite order of check) + async with self._connected_lock: + self._connected = False + self._reconnect_event.set() + + async def _wait_and_start_reconnect(self): + """Wait for exponentially increasing time to issue next reconnect event.""" + async with self._tries_lock: + tries = self._tries + # If not first re-try, wait and print message + # Cap wait time at 1 minute. This is because while working on the + # device (e.g. soldering stuff), users don't want to have to wait + # a long time for their device to show up in HA again (this was + # mentioned a lot in early feedback) + tries = min(tries, 10) # prevent OverflowError + wait_time = int(round(min(1.8 ** tries, 60.0))) + if tries == 1: + _LOGGER.info("Trying to reconnect to %s in the background", self._host) + _LOGGER.debug("Retrying %s in %d seconds", self._host, wait_time) + await asyncio.sleep(wait_time) + async with self._wait_task_lock: + self._wait_task = None + self._reconnect_event.set() + + async def _try_connect(self): + """Try connecting to the API client.""" + async with self._tries_lock: + tries = self._tries + self._tries += 1 try: - await cli.connect(on_stop=try_connect, login=True) + await self._cli.connect(on_stop=self._on_disconnect, login=True) except APIConnectionError as error: level = logging.WARNING if tries == 0 else logging.DEBUG _LOGGER.log( level, "Can't connect to ESPHome API for %s (%s): %s", - entry.unique_id, - host, + self._entry.unique_id, + self._host, error, ) # Schedule re-connect in event loop in order not to delay HA # startup. First connect is scheduled in tracked tasks. - data.reconnect_task = hass.loop.create_task( - try_connect(tries + 1, is_disconnect=False) - ) + async with self._wait_task_lock: + # Allow only one wait task at a time + # can happen if mDNS record received while waiting, then use existing wait task + if self._wait_task is not None: + return + + self._wait_task = self._hass.loop.create_task( + self._wait_and_start_reconnect() + ) else: - _LOGGER.info("Successfully connected to %s", host) - hass.async_create_task(on_login()) + _LOGGER.info("Successfully connected to %s", self._host) + async with self._tries_lock: + self._tries = 0 + async with self._connected_lock: + self._connected = True + self._hass.async_create_task(self._on_login()) + + async def _reconnect_once(self): + # Wait and clear reconnection event + await self._reconnect_event.wait() + self._reconnect_event.clear() + + # If in connected state, do not try to connect again. + async with self._connected_lock: + if self._connected: + return False + + # Check if the entry got removed or disabled, in which case we shouldn't reconnect + if self._entry.entry_id not in self._hass.data[DOMAIN]: + # When removing/disconnecting manually + return + + device_registry = self._hass.helpers.device_registry.async_get(self._hass) + devices = dr.async_entries_for_config_entry( + device_registry, self._entry.entry_id + ) + for device in devices: + # There is only one device in ESPHome + if device.disabled: + # Don't attempt to connect if it's disabled + return + + await self._try_connect() + + async def _reconnect_loop(self): + while True: + try: + await self._reconnect_once() + except asyncio.CancelledError: # pylint: disable=try-except-raise + raise + except Exception: # pylint: disable=broad-except + _LOGGER.error("Caught exception while reconnecting", exc_info=True) + + async def start(self): + """Start the reconnecting logic background task.""" + # Create reconnection loop outside of HA's tracked tasks in order + # not to delay startup. + self._loop_task = self._hass.loop.create_task(self._reconnect_loop()) + # Listen for mDNS records so we can reconnect directly if a received mDNS record + # indicates the node is up again + await self._hass.async_add_executor_job(self._zc.add_listener, self, None) + + async with self._connected_lock: + self._connected = False + self._reconnect_event.set() + + async def stop(self): + """Stop the reconnecting logic background task. Does not disconnect the client.""" + if self._loop_task is not None: + self._loop_task.cancel() + self._loop_task = None + await self._hass.async_add_executor_job(self._zc.remove_listener, self) + async with self._wait_task_lock: + if self._wait_task is not None: + self._wait_task.cancel() + self._wait_task = None + + @callback + def stop_callback(self): + """Stop as an async callback function.""" + self._hass.async_create_task(self.stop()) + + @callback + def _set_reconnect(self): + self._reconnect_event.set() + + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: + """Listen to zeroconf updated mDNS records.""" + if not isinstance(record, DNSPointer): + # We only consider PTR records and match using the alias name + return + if self._entry_data is None or self._entry_data.device_info is None: + # Either the entry was already teared down or we haven't received device info yet + return + filter_alias = f"{self._entry_data.device_info.name}._esphomelib._tcp.local." + if record.alias != filter_alias: + return - return try_connect + # This is a mDNS record from the device and could mean it just woke up + # Check if already connected, no lock needed for this access + if self._connected: + return + + # Tell reconnection logic to retry connection attempt now (even before reconnect timer finishes) + _LOGGER.debug( + "%s: Triggering reconnect because of received mDNS record %s", + self._host, + record, + ) + self._hass.add_job(self._set_reconnect) async def _async_setup_device_registry( @@ -421,8 +567,6 @@ async def _cleanup_instance( ) -> RuntimeEntryData: """Cleanup the esphome client if it exists.""" data: RuntimeEntryData = hass.data[DOMAIN].pop(entry.entry_id) - if data.reconnect_task is not None: - data.reconnect_task.cancel() for disconnect_cb in data.disconnect_callbacks: disconnect_cb() for cleanup_callback in data.cleanup_callbacks: diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 0923c84acd70f4..4fada10a3d1660 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -54,7 +54,6 @@ class RuntimeEntryData: entry_id: str = attr.ib() client: APIClient = attr.ib() store: Store = attr.ib() - reconnect_task: asyncio.Task | None = attr.ib(default=None) state: dict[str, dict[str, Any]] = attr.ib(factory=dict) info: dict[str, dict[str, Any]] = attr.ib(factory=dict) From 99d2d72d13719730ccb65a00871ad80cb1ed10bb Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sun, 21 Mar 2021 03:27:24 +0100 Subject: [PATCH 1411/1818] Update RFLink tests (#48149) --- tests/components/rflink/test_cover.py | 152 +++++++++---------------- tests/components/rflink/test_init.py | 44 +++---- tests/components/rflink/test_light.py | 68 ++++------- tests/components/rflink/test_switch.py | 12 +- 4 files changed, 102 insertions(+), 174 deletions(-) diff --git a/tests/components/rflink/test_cover.py b/tests/components/rflink/test_cover.py index fd3f5b84314577..1dac064d778c53 100644 --- a/tests/components/rflink/test_cover.py +++ b/tests/components/rflink/test_cover.py @@ -89,20 +89,16 @@ async def test_default_setup(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.test").state == STATE_OPEN # test changing state from HA propagates to RFLink - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == STATE_CLOSED assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_0" assert protocol.send_command_ack.call_args_list[0][0][1] == "DOWN" - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == STATE_OPEN @@ -162,10 +158,8 @@ async def test_signal_repetitions(hass, monkeypatch): _, _, protocol, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) # test if signal repetition is performed according to configuration - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) # wait for commands and repetitions to finish @@ -174,10 +168,8 @@ async def test_signal_repetitions(hass, monkeypatch): assert protocol.send_command_ack.call_count == 2 # test if default apply to configured devices - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} ) # wait for commands and repetitions to finish @@ -202,15 +194,11 @@ async def test_signal_repetitions_alternation(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} ) await hass.async_block_till_done() @@ -234,16 +222,12 @@ async def test_signal_repetitions_cancelling(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() @@ -606,12 +590,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'DOWN' command sent to a non-newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, ) await hass.async_block_till_done() @@ -623,12 +605,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'UP' command sent to a non-newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_standard"}, ) await hass.async_block_till_done() @@ -640,10 +620,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'DOWN' command sent to a non-newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} ) await hass.async_block_till_done() @@ -655,10 +633,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'UP' command sent to a non-newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_none"} ) await hass.async_block_till_done() @@ -670,12 +646,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'UP' command sent to a non-newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, ) await hass.async_block_till_done() @@ -687,12 +661,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'DOWN' command sent to a non-newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.nonkaku_type_inverted"}, ) await hass.async_block_till_done() @@ -704,12 +676,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'DOWN' command sent to a newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, ) await hass.async_block_till_done() @@ -721,12 +691,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'UP' command sent to a newkaku device # that has its type set to 'standard'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_standard"}, ) await hass.async_block_till_done() @@ -738,10 +706,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'UP' command sent to a newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} ) await hass.async_block_till_done() @@ -753,10 +719,8 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'DOWN' command sent to a newkaku device # that has its type not specified. - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_none"} ) await hass.async_block_till_done() @@ -768,12 +732,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the close command from HA should result # in an 'UP' command sent to a newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, ) await hass.async_block_till_done() @@ -785,12 +747,10 @@ async def test_inverted_cover(hass, monkeypatch): # Sending the open command from HA should result # in an 'DOWN' command sent to a newkaku device # that has its type set to 'inverted'. - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_type_inverted"}, ) await hass.async_block_till_done() diff --git a/tests/components/rflink/test_init.py b/tests/components/rflink/test_init.py index 233170d8cd2c03..f93c9703d3023c 100644 --- a/tests/components/rflink/test_init.py +++ b/tests/components/rflink/test_init.py @@ -107,10 +107,8 @@ async def test_send_no_wait(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) - hass.async_create_task( - hass.services.async_call( - domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"} - ) + await hass.services.async_call( + domain, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "switch.test"} ) await hass.async_block_till_done() assert protocol.send_command.call_args_list[0][0][0] == "protocol_0_0" @@ -133,10 +131,8 @@ async def test_cover_send_no_wait(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) - hass.async_create_task( - hass.services.async_call( - domain, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: "cover.test"} - ) + await hass.services.async_call( + domain, SERVICE_STOP_COVER, {ATTR_ENTITY_ID: "cover.test"} ) await hass.async_block_till_done() assert protocol.send_command.call_args_list[0][0][0] == "RTS_0100F2_0" @@ -151,12 +147,10 @@ async def test_send_command(hass, monkeypatch): # setup mocking rflink module _, _, protocol, _ = await mock_rflink(hass, config, domain, monkeypatch) - hass.async_create_task( - hass.services.async_call( - domain, - SERVICE_SEND_COMMAND, - {"device_id": "newkaku_0000c6c2_1", "command": "on"}, - ) + await hass.services.async_call( + domain, + SERVICE_SEND_COMMAND, + {"device_id": "newkaku_0000c6c2_1", "command": "on"}, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[0][0][0] == "newkaku_0000c6c2_1" @@ -215,24 +209,22 @@ async def test_send_command_event_propagation(hass, monkeypatch): # default value = 'off' assert hass.states.get(f"{domain}.test1").state == "off" - hass.async_create_task( - hass.services.async_call( - "rflink", - SERVICE_SEND_COMMAND, - {"device_id": "protocol_0_1", "command": "on"}, - ) + await hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "on"}, + blocking=True, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_1" assert protocol.send_command_ack.call_args_list[0][0][1] == "on" assert hass.states.get(f"{domain}.test1").state == "on" - hass.async_create_task( - hass.services.async_call( - "rflink", - SERVICE_SEND_COMMAND, - {"device_id": "protocol_0_1", "command": "alloff"}, - ) + await hass.services.async_call( + "rflink", + SERVICE_SEND_COMMAND, + {"device_id": "protocol_0_1", "command": "alloff"}, + blocking=True, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[1][0][0] == "protocol_0_1" diff --git a/tests/components/rflink/test_light.py b/tests/components/rflink/test_light.py index f3ea5e303754fa..79fa752e54c327 100644 --- a/tests/components/rflink/test_light.py +++ b/tests/components/rflink/test_light.py @@ -96,20 +96,16 @@ async def test_default_setup(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.protocol2_0_1").state == "on" # test changing state from HA propagates to RFLink - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "off" assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_0" assert protocol.send_command_ack.call_args_list[0][0][1] == "off" - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" @@ -118,10 +114,8 @@ async def test_default_setup(hass, monkeypatch): # protocols supporting dimming and on/off should create hybrid light entity event_callback({"id": "newkaku_0_1", "command": "off"}) await hass.async_block_till_done() - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1"} ) await hass.async_block_till_done() @@ -131,23 +125,19 @@ async def test_default_setup(hass, monkeypatch): # and send on command for fallback assert protocol.send_command_ack.call_args_list[3][0][1] == "on" - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1", ATTR_BRIGHTNESS: 128}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.newkaku_0_1", ATTR_BRIGHTNESS: 128}, ) await hass.async_block_till_done() assert protocol.send_command_ack.call_args_list[4][0][1] == "7" - hass.async_create_task( - hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: f"{DOMAIN}.dim_test", ATTR_BRIGHTNESS: 128}, - ) + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.dim_test", ATTR_BRIGHTNESS: 128}, ) await hass.async_block_till_done() @@ -210,10 +200,8 @@ async def test_signal_repetitions(hass, monkeypatch): ) # test if signal repetition is performed according to configuration - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) # wait for commands and repetitions to finish @@ -222,10 +210,8 @@ async def test_signal_repetitions(hass, monkeypatch): assert protocol.send_command_ack.call_count == 2 # test if default apply to configured devices - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test1"} ) # wait for commands and repetitions to finish @@ -239,10 +225,8 @@ async def test_signal_repetitions(hass, monkeypatch): # make sure entity is created before setting state await hass.async_block_till_done() - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.protocol_0_2"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.protocol_0_2"} ) # wait for commands and repetitions to finish @@ -340,20 +324,16 @@ async def test_type_toggle(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.toggle_test").state == "off" # test async_turn_off, must set state = 'on' ('off' + toggle) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.toggle_test").state == "on" # test async_turn_on, must set state = 'off' (yes, sounds crazy) - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.toggle_test"} ) await hass.async_block_till_done() diff --git a/tests/components/rflink/test_switch.py b/tests/components/rflink/test_switch.py index d696e8933be044..ef6bac55f21771 100644 --- a/tests/components/rflink/test_switch.py +++ b/tests/components/rflink/test_switch.py @@ -76,20 +76,16 @@ async def test_default_setup(hass, monkeypatch): # events because every new unknown device is added as a light by default. # test changing state from HA propagates to Rflink - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "off" assert protocol.send_command_ack.call_args_list[0][0][0] == "protocol_0_0" assert protocol.send_command_ack.call_args_list[0][0][1] == "off" - hass.async_create_task( - hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} - ) + await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: f"{DOMAIN}.test"} ) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" From 87499989a041ab83bc1b33d16e1947d4005479f7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 21 Mar 2021 04:08:49 +0100 Subject: [PATCH 1412/1818] Small code styling tweaks for HomeKit (#48163) --- .../components/homekit/config_flow.py | 8 +- .../components/homekit/type_covers.py | 21 ++-- .../components/homekit/type_humidifiers.py | 8 +- .../components/homekit/type_locks.py | 8 +- .../components/homekit/type_thermostats.py | 116 ++++++++++-------- homeassistant/components/homekit/util.py | 16 ++- 6 files changed, 93 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 2eabdd98e79c65..758e25b97bcc76 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -247,10 +247,10 @@ def _async_is_unique_name_port(self, user_input): """Determine is a name or port is already used.""" name = user_input[CONF_NAME] port = user_input[CONF_PORT] - for entry in self._async_current_entries(): - if entry.data[CONF_NAME] == name or entry.data[CONF_PORT] == port: - return False - return True + return not any( + entry.data[CONF_NAME] == name or entry.data[CONF_PORT] == port + for entry in self._async_current_entries() + ) @staticmethod @callback diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index ca375bb6f37c88..f21287b3bf847e 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -364,18 +364,17 @@ def move_cover(self, value): """Move cover to value if call came from HomeKit.""" _LOGGER.debug("%s: Set position to %d", self.entity_id, value) - if self._supports_stop: - if value > 70: - service, position = (SERVICE_OPEN_COVER, 100) - elif value < 30: - service, position = (SERVICE_CLOSE_COVER, 0) - else: - service, position = (SERVICE_STOP_COVER, 50) + if ( + self._supports_stop + and value > 70 + or not self._supports_stop + and value >= 50 + ): + service, position = (SERVICE_OPEN_COVER, 100) + elif value < 30 or not self._supports_stop: + service, position = (SERVICE_CLOSE_COVER, 0) else: - if value >= 50: - service, position = (SERVICE_OPEN_COVER, 100) - else: - service, position = (SERVICE_CLOSE_COVER, 0) + service, position = (SERVICE_STOP_COVER, 50) params = {ATTR_ENTITY_ID: self.entity_id} self.async_call_service(DOMAIN, service, params) diff --git a/homeassistant/components/homekit/type_humidifiers.py b/homeassistant/components/homekit/type_humidifiers.py index 6e1978d9499ef4..a4a73abf998fae 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -240,6 +240,8 @@ def async_update_state(self, new_state): # Update target humidity target_humidity = new_state.attributes.get(ATTR_HUMIDITY) - if isinstance(target_humidity, (int, float)): - if self.char_target_humidity.value != target_humidity: - self.char_target_humidity.set_value(target_humidity) + if ( + isinstance(target_humidity, (int, float)) + and self.char_target_humidity.value != target_humidity + ): + self.char_target_humidity.set_value(target_humidity) diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index 140940dda47b81..17e2eee46e87f9 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -78,9 +78,11 @@ def async_update_state(self, new_state): # LockTargetState only supports locked and unlocked # Must set lock target state before current state # or there will be no notification - if hass_state in (STATE_LOCKED, STATE_UNLOCKED): - if self.char_target_state.value != current_lock_state: - self.char_target_state.set_value(current_lock_state) + if ( + hass_state in (STATE_LOCKED, STATE_UNLOCKED) + and self.char_target_state.value != current_lock_state + ): + self.char_target_state.set_value(current_lock_state) # Set lock current state ONLY after ensuring that # target state is correct or there will be no diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 2eb63f4c840fb4..fb3063704c2b15 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -253,42 +253,44 @@ def _set_chars(self, char_values): hvac_mode = state.state homekit_hvac_mode = HC_HASS_TO_HOMEKIT[hvac_mode] - if CHAR_TARGET_HEATING_COOLING in char_values: - # Homekit will reset the mode when VIEWING the temp - # Ignore it if its the same mode - if char_values[CHAR_TARGET_HEATING_COOLING] != homekit_hvac_mode: - target_hc = char_values[CHAR_TARGET_HEATING_COOLING] - if target_hc not in self.hc_homekit_to_hass: - # If the target heating cooling state we want does not - # exist on the device, we have to sort it out - # based on the the current and target temperature since - # siri will always send HC_HEAT_COOL_AUTO in this case - # and hope for the best. - hc_target_temp = char_values.get(CHAR_TARGET_TEMPERATURE) - hc_current_temp = _get_current_temperature(state, self._unit) - hc_fallback_order = HC_HEAT_COOL_PREFER_HEAT - if ( - hc_target_temp is not None - and hc_current_temp is not None - and hc_target_temp < hc_current_temp - ): - hc_fallback_order = HC_HEAT_COOL_PREFER_COOL - for hc_fallback in hc_fallback_order: - if hc_fallback in self.hc_homekit_to_hass: - _LOGGER.debug( - "Siri requested target mode: %s and the device does not support, falling back to %s", - target_hc, - hc_fallback, - ) - target_hc = hc_fallback - break - - service = SERVICE_SET_HVAC_MODE_THERMOSTAT - hass_value = self.hc_homekit_to_hass[target_hc] - params = {ATTR_HVAC_MODE: hass_value} - events.append( - f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}" - ) + # Homekit will reset the mode when VIEWING the temp + # Ignore it if its the same mode + if ( + CHAR_TARGET_HEATING_COOLING in char_values + and char_values[CHAR_TARGET_HEATING_COOLING] != homekit_hvac_mode + ): + target_hc = char_values[CHAR_TARGET_HEATING_COOLING] + if target_hc not in self.hc_homekit_to_hass: + # If the target heating cooling state we want does not + # exist on the device, we have to sort it out + # based on the the current and target temperature since + # siri will always send HC_HEAT_COOL_AUTO in this case + # and hope for the best. + hc_target_temp = char_values.get(CHAR_TARGET_TEMPERATURE) + hc_current_temp = _get_current_temperature(state, self._unit) + hc_fallback_order = HC_HEAT_COOL_PREFER_HEAT + if ( + hc_target_temp is not None + and hc_current_temp is not None + and hc_target_temp < hc_current_temp + ): + hc_fallback_order = HC_HEAT_COOL_PREFER_COOL + for hc_fallback in hc_fallback_order: + if hc_fallback in self.hc_homekit_to_hass: + _LOGGER.debug( + "Siri requested target mode: %s and the device does not support, falling back to %s", + target_hc, + hc_fallback, + ) + target_hc = hc_fallback + break + + service = SERVICE_SET_HVAC_MODE_THERMOSTAT + hass_value = self.hc_homekit_to_hass[target_hc] + params = {ATTR_HVAC_MODE: hass_value} + events.append( + f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}" + ) if CHAR_TARGET_TEMPERATURE in char_values: hc_target_temp = char_values[CHAR_TARGET_TEMPERATURE] @@ -462,23 +464,26 @@ def _async_update_state(self, new_state): # Update current temperature current_temp = _get_current_temperature(new_state, self._unit) - if current_temp is not None: - if self.char_current_temp.value != current_temp: - self.char_current_temp.set_value(current_temp) + if current_temp is not None and self.char_current_temp.value != current_temp: + self.char_current_temp.set_value(current_temp) # Update current humidity if CHAR_CURRENT_HUMIDITY in self.chars: current_humdity = new_state.attributes.get(ATTR_CURRENT_HUMIDITY) - if isinstance(current_humdity, (int, float)): - if self.char_current_humidity.value != current_humdity: - self.char_current_humidity.set_value(current_humdity) + if ( + isinstance(current_humdity, (int, float)) + and self.char_current_humidity.value != current_humdity + ): + 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) - if isinstance(target_humdity, (int, float)): - if self.char_target_humidity.value != target_humdity: - self.char_target_humidity.set_value(target_humdity) + if ( + isinstance(target_humdity, (int, float)) + and self.char_target_humidity.value != target_humdity + ): + self.char_target_humidity.set_value(target_humdity) # Update cooling threshold temperature if characteristic exists if self.char_cooling_thresh_temp: @@ -575,9 +580,8 @@ def set_heat_cool(self, value): """Change operation mode to value if call came from HomeKit.""" _LOGGER.debug("%s: Set heat-cool to %d", self.entity_id, value) hass_value = HC_HOMEKIT_TO_HASS[value] - if hass_value != HVAC_MODE_HEAT: - if self.char_target_heat_cool.value != 1: - self.char_target_heat_cool.set_value(1) # Heat + if hass_value != HVAC_MODE_HEAT and self.char_target_heat_cool.value != 1: + self.char_target_heat_cool.set_value(1) # Heat def set_target_temperature(self, value): """Set target temperature to value if call came from HomeKit.""" @@ -596,14 +600,18 @@ def async_update_state(self, new_state): """Update water_heater state after state change.""" # Update current and target temperature target_temperature = _get_target_temperature(new_state, self._unit) - if target_temperature is not None: - if target_temperature != self.char_target_temp.value: - self.char_target_temp.set_value(target_temperature) + if ( + target_temperature is not None + and target_temperature != self.char_target_temp.value + ): + self.char_target_temp.set_value(target_temperature) current_temperature = _get_current_temperature(new_state, self._unit) - if current_temperature is not None: - if current_temperature != self.char_current_temp.value: - self.char_current_temp.set_value(current_temperature) + if ( + current_temperature is not None + and current_temperature != self.char_current_temp.value + ): + self.char_current_temp.set_value(current_temperature) # Update display units if self._unit and self._unit in UNIT_HASS_TO_HOMEKIT: diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 10550cf4a119c0..cab1a28892a356 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -444,10 +444,11 @@ def port_is_available(port: int) -> bool: async def async_find_next_available_port(hass: HomeAssistant, start_port: int) -> int: """Find the next available port not assigned to a config entry.""" - exclude_ports = set() - for entry in hass.config_entries.async_entries(DOMAIN): - if CONF_PORT in entry.data: - exclude_ports.add(entry.data[CONF_PORT]) + exclude_ports = { + entry.data[CONF_PORT] + for entry in hass.config_entries.async_entries(DOMAIN) + if CONF_PORT in entry.data + } return await hass.async_add_executor_job( _find_next_available_port, start_port, exclude_ports @@ -499,10 +500,7 @@ def state_needs_accessory_mode(state): if state.domain == CAMERA_DOMAIN: return True - if ( + return ( state.domain == MEDIA_PLAYER_DOMAIN and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV - ): - return True - - return False + ) From 668d018e9c0a9b5171fad49beaf8af2f60a0a737 Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sun, 21 Mar 2021 08:43:38 +0100 Subject: [PATCH 1413/1818] Make Rflink handle set_level command for dimmable devices (#46499) * Added handle_event for set_level command in dimmable devices * refactor common code for dimmable devices * Force tests Silly change to force tests execution * fix super() * add rflink dim utils --- homeassistant/components/rflink/__init__.py | 4 +- homeassistant/components/rflink/light.py | 71 +++++--------- homeassistant/components/rflink/utils.py | 11 +++ tests/components/rflink/test_light.py | 101 +++++++++++++++++++- tests/components/rflink/test_utils.py | 33 +++++++ 5 files changed, 167 insertions(+), 53 deletions(-) create mode 100644 homeassistant/components/rflink/utils.py create mode 100644 tests/components/rflink/test_utils.py diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 81d44806c5e358..c78b0c6f944f5e 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -28,6 +28,8 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.restore_state import RestoreEntity +from .utils import brightness_to_rflink + _LOGGER = logging.getLogger(__name__) ATTR_EVENT = "event" @@ -509,7 +511,7 @@ async def _async_handle_command(self, command, *args): elif command == "dim": # convert brightness to rflink dim level - cmd = str(int(args[0] / 17)) + cmd = str(brightness_to_rflink(args[0])) self._state = True elif command == "toggle": diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 2a97472f000ccd..5a0d67661797c5 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -1,5 +1,6 @@ """Support for Rflink lights.""" import logging +import re import voluptuous as vol @@ -27,6 +28,7 @@ EVENT_KEY_ID, SwitchableRflinkDevice, ) +from .utils import brightness_to_rflink, rflink_to_brightness _LOGGER = logging.getLogger(__name__) @@ -183,30 +185,39 @@ async def async_turn_on(self, **kwargs): """Turn the device on.""" if ATTR_BRIGHTNESS in kwargs: # rflink only support 16 brightness levels - self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17 + self._brightness = rflink_to_brightness( + brightness_to_rflink(kwargs[ATTR_BRIGHTNESS]) + ) # Turn on light at the requested dim level await self._async_handle_command("dim", self._brightness) + def _handle_event(self, event): + """Adjust state if Rflink picks up a remote command for this device.""" + self.cancel_queued_send_commands() + + command = event["command"] + if command in ["on", "allon"]: + self._state = True + elif command in ["off", "alloff"]: + self._state = False + # dimmable device accept 'set_level=(0-15)' commands + elif re.search("^set_level=(0?[0-9]|1[0-5])$", command, re.IGNORECASE): + self._brightness = rflink_to_brightness(int(command.split("=")[1])) + self._state = True + @property def brightness(self): """Return the brightness of this light between 0..255.""" return self._brightness - @property - def extra_state_attributes(self): - """Return the device state attributes.""" - if self._brightness is None: - return {} - return {ATTR_BRIGHTNESS: self._brightness} - @property def supported_features(self): """Flag supported features.""" return SUPPORT_BRIGHTNESS -class HybridRflinkLight(SwitchableRflinkDevice, LightEntity): +class HybridRflinkLight(DimmableRflinkLight, LightEntity): """Rflink light device that sends out both dim and on/off commands. Used for protocols which support lights that are not exclusively on/off @@ -221,52 +232,14 @@ class HybridRflinkLight(SwitchableRflinkDevice, LightEntity): Which results in a nice house disco :) """ - _brightness = 255 - - async def async_added_to_hass(self): - """Restore RFLink light brightness attribute.""" - await super().async_added_to_hass() - - old_state = await self.async_get_last_state() - if ( - old_state is not None - and old_state.attributes.get(ATTR_BRIGHTNESS) is not None - ): - # restore also brightness in dimmables devices - self._brightness = int(old_state.attributes[ATTR_BRIGHTNESS]) - async def async_turn_on(self, **kwargs): """Turn the device on and set dim level.""" - if ATTR_BRIGHTNESS in kwargs: - # rflink only support 16 brightness levels - self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17 - - # if receiver supports dimming this will turn on the light - # at the requested dim level - await self._async_handle_command("dim", self._brightness) - + await super().async_turn_on(**kwargs) # if the receiving device does not support dimlevel this # will ensure it is turned on when full brightness is set - if self._brightness == 255: + if self.brightness == 255: await self._async_handle_command("turn_on") - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - return self._brightness - - @property - def extra_state_attributes(self): - """Return the device state attributes.""" - if self._brightness is None: - return {} - return {ATTR_BRIGHTNESS: self._brightness} - - @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_BRIGHTNESS - class ToggleRflinkLight(SwitchableRflinkDevice, LightEntity): """Rflink light device which sends out only 'on' commands. diff --git a/homeassistant/components/rflink/utils.py b/homeassistant/components/rflink/utils.py new file mode 100644 index 00000000000000..9738d9f74facb1 --- /dev/null +++ b/homeassistant/components/rflink/utils.py @@ -0,0 +1,11 @@ +"""RFLink integration utils.""" + + +def brightness_to_rflink(brightness: int) -> int: + """Convert 0-255 brightness to RFLink dim level (0-15).""" + return int(brightness / 17) + + +def rflink_to_brightness(dim_level: int) -> int: + """Convert RFLink dim level (0-15) to 0-255 brightness.""" + return int(dim_level * 17) diff --git a/tests/components/rflink/test_light.py b/tests/components/rflink/test_light.py index 79fa752e54c327..5f9672ac9fc8f7 100644 --- a/tests/components/rflink/test_light.py +++ b/tests/components/rflink/test_light.py @@ -340,6 +340,93 @@ async def test_type_toggle(hass, monkeypatch): assert hass.states.get(f"{DOMAIN}.toggle_test").state == "off" +async def test_set_level_command(hass, monkeypatch): + """Test 'set_level=XX' events.""" + config = { + "rflink": {"port": "/dev/ttyABC0"}, + DOMAIN: { + "platform": "rflink", + "devices": { + "newkaku_12345678_0": {"name": "l1"}, + "test_no_dimmable": {"name": "l2"}, + "test_dimmable": {"name": "l3", "type": "dimmable"}, + "test_hybrid": {"name": "l4", "type": "hybrid"}, + }, + }, + } + + # setup mocking rflink module + event_callback, _, _, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch) + + # test sending command to a newkaku device + event_callback({"id": "newkaku_12345678_0", "command": "set_level=10"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l1") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 170 + # turn off + event_callback({"id": "newkaku_12345678_0", "command": "off"}) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.l1") + assert state + assert state.state == STATE_OFF + # off light shouldn't have brightness + assert not state.attributes.get(ATTR_BRIGHTNESS) + # turn on + event_callback({"id": "newkaku_12345678_0", "command": "on"}) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.l1") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 170 + + # test sending command to a no dimmable device + event_callback({"id": "test_no_dimmable", "command": "set_level=10"}) + await hass.async_block_till_done() + # should NOT affect state + state = hass.states.get(f"{DOMAIN}.l2") + assert state + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_BRIGHTNESS) + + # test sending command to a dimmable device + event_callback({"id": "test_dimmable", "command": "set_level=5"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l3") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 85 + + # test sending command to a hybrid device + event_callback({"id": "test_hybrid", "command": "set_level=15"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l4") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 255 + + event_callback({"id": "test_hybrid", "command": "off"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l4") + assert state + assert state.state == STATE_OFF + # off light shouldn't have brightness + assert not state.attributes.get(ATTR_BRIGHTNESS) + + event_callback({"id": "test_hybrid", "command": "set_level=0"}) + await hass.async_block_till_done() + # should affect state + state = hass.states.get(f"{DOMAIN}.l4") + assert state + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 0 + + async def test_group_alias(hass, monkeypatch): """Group aliases should only respond to group commands (allon/alloff).""" config = { @@ -347,7 +434,12 @@ async def test_group_alias(hass, monkeypatch): DOMAIN: { "platform": "rflink", "devices": { - "protocol_0_0": {"name": "test", "group_aliases": ["test_group_0_0"]} + "protocol_0_0": {"name": "test", "group_aliases": ["test_group_0_0"]}, + "protocol_0_1": { + "name": "test2", + "type": "dimmable", + "group_aliases": ["test_group_0_0"], + }, }, }, } @@ -362,12 +454,14 @@ async def test_group_alias(hass, monkeypatch): await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" + assert hass.states.get(f"{DOMAIN}.test2").state == "on" # test sending group command to group alias event_callback({"id": "test_group_0_0", "command": "off"}) await hass.async_block_till_done() assert hass.states.get(f"{DOMAIN}.test").state == "on" + assert hass.states.get(f"{DOMAIN}.test2").state == "on" async def test_nogroup_alias(hass, monkeypatch): @@ -396,7 +490,7 @@ async def test_nogroup_alias(hass, monkeypatch): # should not affect state assert hass.states.get(f"{DOMAIN}.test").state == "off" - # test sending group command to nogroup alias + # test sending group commands to nogroup alias event_callback({"id": "test_nogroup_0_0", "command": "on"}) await hass.async_block_till_done() # should affect state @@ -501,7 +595,8 @@ async def test_restore_state(hass, monkeypatch): state = hass.states.get(f"{DOMAIN}.l4") assert state assert state.state == STATE_OFF - assert state.attributes[ATTR_BRIGHTNESS] == 255 + # off light shouldn't have brightness + assert not state.attributes.get(ATTR_BRIGHTNESS) assert state.attributes["assumed_state"] # test coverage for dimmable light diff --git a/tests/components/rflink/test_utils.py b/tests/components/rflink/test_utils.py new file mode 100644 index 00000000000000..50dd8100e8e41d --- /dev/null +++ b/tests/components/rflink/test_utils.py @@ -0,0 +1,33 @@ +"""Test for RFLink utils methods.""" +from homeassistant.components.rflink.utils import ( + brightness_to_rflink, + rflink_to_brightness, +) + + +async def test_utils(hass, monkeypatch): + """Test all utils methods.""" + # test brightness_to_rflink + assert brightness_to_rflink(0) == 0 + assert brightness_to_rflink(17) == 1 + assert brightness_to_rflink(34) == 2 + assert brightness_to_rflink(85) == 5 + assert brightness_to_rflink(170) == 10 + assert brightness_to_rflink(255) == 15 + + assert brightness_to_rflink(10) == 0 + assert brightness_to_rflink(20) == 1 + assert brightness_to_rflink(30) == 1 + assert brightness_to_rflink(40) == 2 + assert brightness_to_rflink(50) == 2 + assert brightness_to_rflink(60) == 3 + assert brightness_to_rflink(70) == 4 + assert brightness_to_rflink(80) == 4 + + # test rflink_to_brightness + assert rflink_to_brightness(0) == 0 + assert rflink_to_brightness(1) == 17 + assert rflink_to_brightness(5) == 85 + assert rflink_to_brightness(10) == 170 + assert rflink_to_brightness(12) == 204 + assert rflink_to_brightness(15) == 255 From 346a724ac389e7976ea134eb6da6b847a11e7fcb Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 21 Mar 2021 10:38:24 +0100 Subject: [PATCH 1414/1818] Mark base components' state_attribute @final, rename others to extra_state_attributes (#48161) * Mark base state_attributes @final, rename others to extra_state_attributes * Fix calendar, update tests --- .../components/acer_projector/switch.py | 2 +- .../components/air_quality/__init__.py | 2 ++ .../alarm_control_panel/__init__.py | 2 ++ homeassistant/components/arwn/sensor.py | 2 +- .../components/automation/__init__.py | 2 +- homeassistant/components/calendar/__init__.py | 5 +-- homeassistant/components/camera/__init__.py | 2 ++ homeassistant/components/climate/__init__.py | 5 +-- homeassistant/components/counter/__init__.py | 2 +- homeassistant/components/cover/__init__.py | 5 +-- .../components/device_tracker/config_entry.py | 8 +++-- .../components/device_tracker/legacy.py | 5 +-- homeassistant/components/fail2ban/sensor.py | 2 +- homeassistant/components/fan/__init__.py | 4 ++- .../components/fritzbox_netmonitor/sensor.py | 2 +- .../components/geo_location/__init__.py | 4 ++- homeassistant/components/group/__init__.py | 2 +- homeassistant/components/homematic/entity.py | 2 +- .../components/humidifier/__init__.py | 5 +-- homeassistant/components/icloud/account.py | 2 +- .../components/image_processing/__init__.py | 2 ++ .../components/input_boolean/__init__.py | 2 +- .../components/input_datetime/__init__.py | 2 +- .../components/input_number/__init__.py | 2 +- .../components/input_select/__init__.py | 2 +- .../components/input_text/__init__.py | 2 +- homeassistant/components/light/__init__.py | 5 +-- homeassistant/components/lock/__init__.py | 4 ++- .../components/media_player/__init__.py | 2 ++ homeassistant/components/netio/switch.py | 2 +- .../openalpr_local/image_processing.py | 2 +- .../components/opencv/image_processing.py | 2 +- .../components/openhardwaremonitor/sensor.py | 4 +-- homeassistant/components/person/__init__.py | 2 +- homeassistant/components/plant/__init__.py | 2 +- .../components/proximity/__init__.py | 2 +- homeassistant/components/remote/__init__.py | 5 +-- .../components/rmvtransport/sensor.py | 2 +- homeassistant/components/script/__init__.py | 2 +- .../components/sony_projector/switch.py | 2 +- homeassistant/components/sun/__init__.py | 2 +- homeassistant/components/switch/__init__.py | 4 ++- homeassistant/components/timer/__init__.py | 2 +- homeassistant/components/twinkly/light.py | 2 +- .../components/utility_meter/__init__.py | 2 +- homeassistant/components/vacuum/__init__.py | 2 ++ .../components/water_heater/__init__.py | 4 ++- homeassistant/components/weather/__init__.py | 2 ++ .../components/workday/binary_sensor.py | 2 +- homeassistant/components/zone/__init__.py | 2 +- tests/components/fail2ban/test_sensor.py | 32 +++++++++---------- tests/components/plant/test_init.py | 6 ++-- 52 files changed, 106 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index 101f7cbd6155b0..4a61ec793dbb8d 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -132,7 +132,7 @@ def is_on(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self._attributes diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index 52c9208854a91e..d69a02f83bd798 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -1,6 +1,7 @@ """Component for handling Air Quality data for your location.""" from datetime import timedelta import logging +from typing import final from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -131,6 +132,7 @@ def nitrogen_dioxide(self): """Return the NO2 (nitrogen dioxide) level.""" return None + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 114abfa9cd6e4c..7d9e47fbcbecdf 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -2,6 +2,7 @@ from abc import abstractmethod from datetime import timedelta import logging +from typing import final import voluptuous as vol @@ -172,6 +173,7 @@ async def async_alarm_arm_custom_bypass(self, code=None): def supported_features(self) -> int: """Return the list of supported features.""" + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index 18186e8b87192d..d68549ea95cee8 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -154,7 +154,7 @@ def unique_id(self): return self._uid @property - def state_attributes(self): + def extra_state_attributes(self): """Return all the state attributes.""" return self.event diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 425ffe17979a60..4c278cbf5f0cc4 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -274,7 +274,7 @@ def should_poll(self): return False @property - def state_attributes(self): + def extra_state_attributes(self): """Return the entity state attributes.""" attrs = { ATTR_LAST_TRIGGERED: self.action_script.last_triggered, diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 81ce49e9c9411b..11a6916ba83ff1 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging import re -from typing import cast +from typing import cast, final from aiohttp import web @@ -129,13 +129,14 @@ def is_offset_reached(event): class CalendarEventDevice(Entity): - """A calendar event device.""" + """Base class for calendar event entities.""" @property def event(self): """Return the next upcoming event.""" raise NotImplementedError() + @final @property def state_attributes(self): """Return the entity state attributes.""" diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 99b5cebc2a390d..707398575878f7 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -8,6 +8,7 @@ import logging import os from random import SystemRandom +from typing import final from aiohttp import web import async_timeout @@ -441,6 +442,7 @@ async def async_disable_motion_detection(self): """Call the job and disable motion detection.""" await self.hass.async_add_executor_job(self.disable_motion_detection) + @final @property def state_attributes(self): """Return the camera state attributes.""" diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 93041a6bb33b4b..ef2d34aba9b907 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -5,7 +5,7 @@ from datetime import timedelta import functools as ft import logging -from typing import Any +from typing import Any, final import voluptuous as vol @@ -167,7 +167,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry): class ClimateEntity(Entity): - """Representation of a climate entity.""" + """Base class for climate entities.""" @property def state(self) -> str: @@ -213,6 +213,7 @@ def capability_attributes(self) -> dict[str, Any] | None: return data + @final @property def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index a4e04825ef65c9..39cca0527eb5a5 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -208,7 +208,7 @@ def state(self) -> int | None: return self._state @property - def state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return the state attributes.""" ret = { ATTR_EDITABLE: self.editable, diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index c63963c87dcea7..034beb7f9db411 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import functools as ft import logging -from typing import Any +from typing import Any, final import voluptuous as vol @@ -165,7 +165,7 @@ async def async_unload_entry(hass, entry): class CoverEntity(Entity): - """Representation of a cover.""" + """Base class for cover entities.""" @property def current_cover_position(self): @@ -196,6 +196,7 @@ def state(self): return STATE_CLOSED if closed else STATE_OPEN + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 0d8970e087bea0..05fa4b4a60d7fa 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -1,6 +1,8 @@ """Code to set up a device tracker platform using a config entry.""" from __future__ import annotations +from typing import final + from homeassistant.components import zone from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -59,7 +61,7 @@ def state_attributes(self): class TrackerEntity(BaseTrackerEntity): - """Represent a tracked device.""" + """Base class for a tracked device.""" @property def should_poll(self): @@ -114,6 +116,7 @@ def state(self): return None + @final @property def state_attributes(self): """Return the device state attributes.""" @@ -128,7 +131,7 @@ def state_attributes(self): class ScannerEntity(BaseTrackerEntity): - """Represent a tracked device that is on a scanned network.""" + """Base class for a tracked device that is on a scanned network.""" @property def ip_address(self) -> str: @@ -157,6 +160,7 @@ def is_connected(self): """Return true if the device is connected to the network.""" raise NotImplementedError + @final @property def state_attributes(self): """Return the device state attributes.""" diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 1d3e428b46a460..7ab8df1de87172 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -5,7 +5,7 @@ from datetime import timedelta import hashlib from types import ModuleType -from typing import Any, Callable, Sequence +from typing import Any, Callable, Sequence, final import attr import voluptuous as vol @@ -588,7 +588,7 @@ async def async_init_single_device(dev): class Device(RestoreEntity): - """Represent a tracked device.""" + """Base class for a tracked device.""" host_name: str = None location_name: str = None @@ -661,6 +661,7 @@ def entity_picture(self): """Return the picture of the device.""" return self.config_picture + @final @property def state_attributes(self): """Return the device state attributes.""" diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 2f206dca737ad3..30aa0c6983892d 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -66,7 +66,7 @@ def name(self): return self._name @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the fail2ban sensor.""" return self.ban_dict diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 85b31c31576b8c..da2224aaf33de4 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -5,6 +5,7 @@ import functools as ft import logging import math +from typing import final import voluptuous as vol @@ -220,7 +221,7 @@ def _fan_native(method): class FanEntity(ToggleEntity): - """Representation of a fan.""" + """Base class for fan entities.""" @_fan_native def set_speed(self, speed: str) -> None: @@ -586,6 +587,7 @@ def percentage_to_speed(self, percentage: int) -> str: f"The speed_list {speed_list} does not contain any valid speeds." ) from ex + @final @property def state_attributes(self) -> dict: """Return optional state attributes.""" diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py index 13b822ae8a42b8..320144ae163184 100644 --- a/homeassistant/components/fritzbox_netmonitor/sensor.py +++ b/homeassistant/components/fritzbox_netmonitor/sensor.py @@ -93,7 +93,7 @@ def state(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the device state attributes.""" # Don't return attributes if FritzBox is unreachable if self._state == STATE_UNAVAILABLE: diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index 2041c147b2b138..11294e73f63519 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging +from typing import final from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.helpers.config_validation import ( # noqa: F401 @@ -46,7 +47,7 @@ async def async_unload_entry(hass, entry): class GeolocationEvent(Entity): - """This represents an external event with an associated geolocation.""" + """Base class for an external event with an associated geolocation.""" @property def state(self): @@ -75,6 +76,7 @@ def longitude(self) -> float | None: """Return longitude value of this external event.""" return None + @final @property def state_attributes(self): """Return the state attributes of this external event.""" diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 96822b9f993b47..5e8a2e679f6b47 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -547,7 +547,7 @@ def icon(self, value): self._icon = value @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes for the group.""" data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order} if not self.user_defined: diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index 3643871c607317..50b9bcb2bfcd78 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -231,7 +231,7 @@ def state(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return self._variables.copy() diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 01a9fc2ada0dda..f6a4ebcccd9ec7 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging -from typing import Any +from typing import Any, final import voluptuous as vol @@ -99,7 +99,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo class HumidifierEntity(ToggleEntity): - """Representation of a humidifier device.""" + """Base class for humidifier entities.""" @property def capability_attributes(self) -> dict[str, Any]: @@ -115,6 +115,7 @@ def capability_attributes(self) -> dict[str, Any]: return data + @final @property def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 97bba3c5ca61aa..2fc8f7124d0bd1 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -502,6 +502,6 @@ def location(self) -> dict[str, any]: return self._location @property - def state_attributes(self) -> dict[str, any]: + def exta_state_attributes(self) -> dict[str, any]: """Return the attributes.""" return self._attrs diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index e885a9ca7a967e..1320629aeb4bd1 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -2,6 +2,7 @@ import asyncio from datetime import timedelta import logging +from typing import final import voluptuous as vol @@ -175,6 +176,7 @@ def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" return "face" + @final @property def state_attributes(self): """Return device specific state attributes.""" diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index f1e4ebd57dca9d..b46135d934ad06 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -169,7 +169,7 @@ def name(self): return self._config.get(CONF_NAME) @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the entity.""" return {ATTR_EDITABLE: self.editable} diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 8273652be43b58..c408c73f869496 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -319,7 +319,7 @@ def state(self): return self._current_datetime.strftime(FMT_TIME) @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_EDITABLE: self.editable, diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 5f73dec01923af..d22a43c33303d9 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -256,7 +256,7 @@ def unique_id(self) -> str | None: return self._config[CONF_ID] @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_INITIAL: self._config.get(CONF_INITIAL), diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index f14d23124e7bbd..92defe2ded9f46 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -253,7 +253,7 @@ def state(self): return self._current_option @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_OPTIONS: self._config[ATTR_OPTIONS], ATTR_EDITABLE: self.editable} diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index d4e0fc705f2d5e..3eef18f01b14ec 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -245,7 +245,7 @@ def unique_id(self) -> str | None: return self._config[CONF_ID] @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return { ATTR_EDITABLE: self.editable, diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 7934874db0b7a9..06b6829d31c49a 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -6,7 +6,7 @@ from datetime import timedelta import logging import os -from typing import cast +from typing import cast, final import voluptuous as vol @@ -478,7 +478,7 @@ def apply_profile(self, name: str, params: dict) -> None: class LightEntity(ToggleEntity): - """Representation of a light.""" + """Base class for light entities.""" @property def brightness(self) -> int | None: @@ -634,6 +634,7 @@ def _light_internal_convert_color(self, color_mode: str) -> dict: data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) return data + @final @property def state_attributes(self): """Return state attributes.""" diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index bc7dc10ba8db97..237daedae80a9b 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta import functools as ft import logging +from typing import final import voluptuous as vol @@ -76,7 +77,7 @@ async def async_unload_entry(hass, entry): class LockEntity(Entity): - """Representation of a lock.""" + """Base class for lock entities.""" @property def changed_by(self): @@ -117,6 +118,7 @@ async def async_open(self, **kwargs): """Open the door latch.""" await self.hass.async_add_executor_job(ft.partial(self.open, **kwargs)) + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 8e3ffe5dd0d45e..faa2c4882164bd 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -9,6 +9,7 @@ import hashlib import logging import secrets +from typing import final from urllib.parse import urlparse from aiohttp import web @@ -853,6 +854,7 @@ def capability_attributes(self): return data + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/netio/switch.py b/homeassistant/components/netio/switch.py index 3c1482844ad4ef..a254d06fc06a26 100644 --- a/homeassistant/components/netio/switch.py +++ b/homeassistant/components/netio/switch.py @@ -169,7 +169,7 @@ def update(self): self.netio.update() @property - def state_attributes(self): + def extra_state_attributes(self): """Return optional state attributes.""" return { ATTR_TOTAL_CONSUMPTION_KWH: self.cumulated_consumption_kwh, diff --git a/homeassistant/components/openalpr_local/image_processing.py b/homeassistant/components/openalpr_local/image_processing.py index ee913070a11f26..d098edba5b2f80 100644 --- a/homeassistant/components/openalpr_local/image_processing.py +++ b/homeassistant/components/openalpr_local/image_processing.py @@ -99,7 +99,7 @@ def device_class(self): return "alpr" @property - def state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles} diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py index 028d6eacf249c3..bf63ec0bfff675 100644 --- a/homeassistant/components/opencv/image_processing.py +++ b/homeassistant/components/opencv/image_processing.py @@ -152,7 +152,7 @@ def state(self): return self._total_matches @property - def state_attributes(self): + def extra_state_attributes(self): """Return device specific state attributes.""" return {ATTR_MATCHES: self._matches, ATTR_TOTAL_MATCHES: self._total_matches} diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py index 115366dac66a53..3254f0824f1c47 100644 --- a/homeassistant/components/openhardwaremonitor/sensor.py +++ b/homeassistant/components/openhardwaremonitor/sensor.py @@ -73,8 +73,8 @@ def state(self): return self.value @property - def state_attributes(self): - """Return the state attributes of the sun.""" + def extra_state_attributes(self): + """Return the state attributes of the entity.""" return self.attributes @classmethod diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index cf6403b7334208..7774694ff4e2cc 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -400,7 +400,7 @@ def state(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the person.""" data = {ATTR_EDITABLE: self.editable, ATTR_ID: self.unique_id} if self._latitude is not None: diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index 2d8291875609bb..7c0c8e9b46fd11 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -348,7 +348,7 @@ def state(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the entity. Provide the individual measurements from the diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 6df0f50b72019d..de9d6247f9f61a 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -148,7 +148,7 @@ def unit_of_measurement(self): return self._unit_of_measurement @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_DIR_OF_TRAVEL: self.dir_of_travel, ATTR_NEAREST: self.nearest} diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index fe220dd46a8349..ecde6f67b67c55 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import functools as ft import logging -from typing import Any, Iterable, cast +from typing import Any, Iterable, cast, final import voluptuous as vol @@ -141,7 +141,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo class RemoteEntity(ToggleEntity): - """Representation of a remote.""" + """Base class for remote entities.""" @property def supported_features(self) -> int: @@ -158,6 +158,7 @@ def activity_list(self) -> list[str] | None: """List of available activities.""" return None + @final @property def state_attributes(self) -> dict[str, Any] | None: """Return optional state attributes.""" diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index ad1ceea3d8619a..555f545d0c7c8a 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -151,7 +151,7 @@ def state(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" try: return { diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index ec0bee73528359..243cdaddd81ca7 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -284,7 +284,7 @@ def name(self): return self.script.name @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_LAST_TRIGGERED: self.script.last_triggered, diff --git a/homeassistant/components/sony_projector/switch.py b/homeassistant/components/sony_projector/switch.py index 723478ac34b6e9..935b33cc5df86e 100644 --- a/homeassistant/components/sony_projector/switch.py +++ b/homeassistant/components/sony_projector/switch.py @@ -65,7 +65,7 @@ def is_on(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return state attributes.""" return self._attributes diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index 2d921da4a46816..dfe3b15c110a01 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -124,7 +124,7 @@ def state(self): return STATE_BELOW_HORIZON @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes of the sun.""" return { STATE_ATTR_NEXT_DAWN: self.next_dawn.isoformat(), diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1d9b54a0424b45..c585fdc22d30a6 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -1,6 +1,7 @@ """Component to interface with switches that can be controlled remotely.""" from datetime import timedelta import logging +from typing import final import voluptuous as vol @@ -79,7 +80,7 @@ async def async_unload_entry(hass, entry): class SwitchEntity(ToggleEntity): - """Representation of a switch.""" + """Base class for switch entities.""" @property def current_power_w(self): @@ -96,6 +97,7 @@ def is_standby(self): """Return true if device is in standby.""" return None + @final @property def state_attributes(self): """Return the optional state attributes.""" diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 05955b46b5cb48..216ab3217a5e20 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -232,7 +232,7 @@ def state(self): return self._state @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" attrs = { ATTR_DURATION: _format_timedelta(self._duration), diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index ece3e0b048cb58..4353aa2707b8fd 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -129,7 +129,7 @@ def brightness(self) -> int | None: return self._brightness @property - def state_attributes(self) -> dict: + def extra_state_attributes(self) -> dict: """Return device specific state attributes.""" attributes = self._attributes diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 24bfd77f762fa4..5442cd583e2fc2 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -165,7 +165,7 @@ def state(self): return self._current_tariff @property - def state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" return {ATTR_TARIFFS: self._tariffs} diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index ca5caec7ac10fe..d8803931f382a0 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta from functools import partial import logging +from typing import final import voluptuous as vol @@ -271,6 +272,7 @@ def battery_icon(self): battery_level=self.battery_level, charging=charging ) + @final @property def state_attributes(self): """Return the state attributes of the vacuum cleaner.""" diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 0763c552075118..5ae22c77b5ee02 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta import functools as ft import logging +from typing import final import voluptuous as vol @@ -129,7 +130,7 @@ async def async_unload_entry(hass, entry): class WaterHeaterEntity(Entity): - """Representation of a water_heater device.""" + """Base class for water heater entities.""" @property def state(self): @@ -162,6 +163,7 @@ def capability_attributes(self): return data + @final @property def state_attributes(self): """Return the optional state attributes.""" diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 5127dae1102274..da66c354d5a631 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -1,6 +1,7 @@ """Weather component that handles meteorological data for your location.""" from datetime import timedelta import logging +from typing import final from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, TEMP_CELSIUS from homeassistant.helpers.config_validation import ( # noqa: F401 @@ -138,6 +139,7 @@ def precision(self): else PRECISION_WHOLE ) + @final @property def state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 7a4140359002ba..ed3822b9698601 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -171,7 +171,7 @@ def is_exclude(self, day, now): return False @property - def state_attributes(self): + def extra_state_attributes(self): """Return the attributes of the entity.""" # return self._attributes return { diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 7e329127d03215..4866c27807459c 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -315,7 +315,7 @@ def icon(self) -> str | None: return self._config.get(CONF_ICON) @property - def state_attributes(self) -> dict | None: + def extra_state_attributes(self) -> dict | None: """Return the state attributes of the zone.""" return self._attrs diff --git a/tests/components/fail2ban/test_sensor.py b/tests/components/fail2ban/test_sensor.py index e43064c54aba04..f9c78e14888ee2 100644 --- a/tests/components/fail2ban/test_sensor.py +++ b/tests/components/fail2ban/test_sensor.py @@ -89,8 +89,8 @@ async def test_single_ban(hass): sensor.update() assert sensor.state == "111.111.111.111" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] - assert sensor.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] async def test_ipv6_ban(hass): @@ -103,8 +103,8 @@ async def test_ipv6_ban(hass): sensor.update() assert sensor.state == "2607:f0d0:1002:51::4" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["2607:f0d0:1002:51::4"] - assert sensor.state_attributes[STATE_ALL_BANS] == ["2607:f0d0:1002:51::4"] + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["2607:f0d0:1002:51::4"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["2607:f0d0:1002:51::4"] async def test_multiple_ban(hass): @@ -117,11 +117,11 @@ async def test_multiple_ban(hass): sensor.update() assert sensor.state == "222.222.222.222" - assert sensor.state_attributes[STATE_CURRENT_BANS] == [ + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == [ "111.111.111.111", "222.222.222.222", ] - assert sensor.state_attributes[STATE_ALL_BANS] == [ + assert sensor.extra_state_attributes[STATE_ALL_BANS] == [ "111.111.111.111", "222.222.222.222", ] @@ -137,8 +137,8 @@ async def test_unban_all(hass): sensor.update() assert sensor.state == "None" - assert sensor.state_attributes[STATE_CURRENT_BANS] == [] - assert sensor.state_attributes[STATE_ALL_BANS] == [ + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == [] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == [ "111.111.111.111", "222.222.222.222", ] @@ -154,8 +154,8 @@ async def test_unban_one(hass): sensor.update() assert sensor.state == "222.222.222.222" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] - assert sensor.state_attributes[STATE_ALL_BANS] == [ + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == [ "111.111.111.111", "222.222.222.222", ] @@ -174,11 +174,11 @@ async def test_multi_jail(hass): sensor2.update() assert sensor1.state == "111.111.111.111" - assert sensor1.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] - assert sensor1.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] + assert sensor1.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] + assert sensor1.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] assert sensor2.state == "222.222.222.222" - assert sensor2.state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] - assert sensor2.state_attributes[STATE_ALL_BANS] == ["222.222.222.222"] + assert sensor2.extra_state_attributes[STATE_CURRENT_BANS] == ["222.222.222.222"] + assert sensor2.extra_state_attributes[STATE_ALL_BANS] == ["222.222.222.222"] async def test_ban_active_after_update(hass): @@ -192,5 +192,5 @@ async def test_ban_active_after_update(hass): assert sensor.state == "111.111.111.111" sensor.update() assert sensor.state == "111.111.111.111" - assert sensor.state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] - assert sensor.state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_CURRENT_BANS] == ["111.111.111.111"] + assert sensor.extra_state_attributes[STATE_ALL_BANS] == ["111.111.111.111"] diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index 494206d81a3942..14e0f3668b0ca5 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -58,7 +58,7 @@ async def test_valid_data(hass): State(GOOD_CONFIG["sensors"][reading], value), ) assert sensor.state == "ok" - attrib = sensor.state_attributes + attrib = sensor.extra_state_attributes for reading, value in GOOD_DATA.items(): # battery level has a different name in # the JSON format than in hass @@ -70,13 +70,13 @@ async def test_low_battery(hass): sensor = plant.Plant("other plant", GOOD_CONFIG) sensor.entity_id = "sensor.mqtt_plant_battery" sensor.hass = hass - assert sensor.state_attributes["problem"] == "none" + assert sensor.extra_state_attributes["problem"] == "none" sensor.state_changed( "sensor.mqtt_plant_battery", State("sensor.mqtt_plant_battery", 10), ) assert sensor.state == "problem" - assert sensor.state_attributes["problem"] == "battery low" + assert sensor.extra_state_attributes["problem"] == "battery low" async def test_initial_states(hass): From fb48fd7d10a18c322634aa9f0821f2619f7b4ca6 Mon Sep 17 00:00:00 2001 From: Kevin Worrel <37058192+dieselrabbit@users.noreply.github.com> Date: Sun, 21 Mar 2021 03:56:46 -0700 Subject: [PATCH 1415/1818] ScreenLogic cleanups (#48136) * ScreenLogic cleanup. Bump screenlogicpy to 0.2.0. Move heating functions from water_heater to climate platform. Address notes from original PR. * Fix temperature attribute * Addressing notes. Bump screenlogicpy to 0.2.1. Made device_types constants. Made (known) equipment flags constants. Used dict.get() in places where None is the default. Return fast with good _last_preset. * Update homeassistant/components/screenlogic/climate.py Let base entity handle state property. Co-authored-by: J. Nick Koston * Patch integration setup functions. * Exception, ATTR_TEMPERATURE notes Co-authored-by: J. Nick Koston --- .coveragerc | 2 +- .../components/screenlogic/__init__.py | 21 +- .../components/screenlogic/binary_sensor.py | 24 +- .../components/screenlogic/climate.py | 209 ++++++++++++++++++ .../components/screenlogic/config_flow.py | 4 +- .../components/screenlogic/manifest.json | 2 +- .../components/screenlogic/sensor.py | 38 ++-- .../components/screenlogic/switch.py | 15 +- .../components/screenlogic/water_heater.py | 128 ----------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../screenlogic/test_config_flow.py | 101 ++++++++- 12 files changed, 354 insertions(+), 194 deletions(-) create mode 100644 homeassistant/components/screenlogic/climate.py delete mode 100644 homeassistant/components/screenlogic/water_heater.py diff --git a/.coveragerc b/.coveragerc index 33f4071c8f4cfb..48f71f5a9988c5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -840,9 +840,9 @@ omit = homeassistant/components/scrape/sensor.py homeassistant/components/screenlogic/__init__.py homeassistant/components/screenlogic/binary_sensor.py + homeassistant/components/screenlogic/climate.py homeassistant/components/screenlogic/sensor.py homeassistant/components/screenlogic/switch.py - homeassistant/components/screenlogic/water_heater.py homeassistant/components/scsgate/* homeassistant/components/scsgate/cover.py homeassistant/components/sendgrid/notify.py diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py index a5e0f248bc6b1f..cca3e7e8785077 100644 --- a/homeassistant/components/screenlogic/__init__.py +++ b/homeassistant/components/screenlogic/__init__.py @@ -6,7 +6,7 @@ from screenlogicpy import ScreenLogicError, ScreenLogicGateway from screenlogicpy.const import ( - CONTROLLER_HARDWARE, + EQUIPMENT, SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWAY_PORT, @@ -28,7 +28,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["switch", "sensor", "binary_sensor", "water_heater"] +PLATFORMS = ["switch", "sensor", "binary_sensor", "climate"] async def async_setup(hass: HomeAssistant, config: dict): @@ -59,11 +59,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): except ScreenLogicError as ex: _LOGGER.error("Error while connecting to the gateway %s: %s", connect_info, ex) raise ConfigEntryNotReady from ex - except AttributeError as ex: - _LOGGER.exception( - "Unexpected error while connecting to the gateway %s", connect_info - ) - raise ConfigEntryNotReady from ex coordinator = ScreenlogicDataUpdateCoordinator( hass, config_entry=entry, gateway=gateway @@ -91,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): device_data["pump"].append(pump) for body in coordinator.data["bodies"]: - device_data["water_heater"].append(body) + device_data["body"].append(body) hass.data[DOMAIN][entry.entry_id] = { "coordinator": coordinator, @@ -151,18 +146,18 @@ async def _async_update_data(self): """Fetch data from the Screenlogic gateway.""" try: await self.hass.async_add_executor_job(self.gateway.update) - return self.gateway.get_data() except ScreenLogicError as error: raise UpdateFailed(error) from error + return self.gateway.get_data() class ScreenlogicEntity(CoordinatorEntity): """Base class for all ScreenLogic entities.""" - def __init__(self, coordinator, datakey): + def __init__(self, coordinator, data_key): """Initialize of the entity.""" super().__init__(coordinator) - self._data_key = datakey + self._data_key = data_key @property def mac(self): @@ -192,11 +187,11 @@ def gateway_name(self): @property def device_info(self): """Return device information for the controller.""" - controller_type = self.config_data["controler_type"] + controller_type = self.config_data["controller_type"] hardware_type = self.config_data["hardware_type"] return { "connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)}, "name": self.gateway_name, "manufacturer": "Pentair", - "model": CONTROLLER_HARDWARE[controller_type][hardware_type], + "model": EQUIPMENT.CONTROLLER_HARDWARE[controller_type][hardware_type], } diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py index fa8d63ee5e6bdb..0001223030a68a 100644 --- a/homeassistant/components/screenlogic/binary_sensor.py +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -1,15 +1,20 @@ """Support for a ScreenLogic Binary Sensor.""" import logging -from screenlogicpy.const import ON_OFF +from screenlogicpy.const import DEVICE_TYPE, ON_OFF -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) from . import ScreenlogicEntity from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = {DEVICE_TYPE.ALARM: DEVICE_CLASS_PROBLEM} + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up entry.""" @@ -19,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for binary_sensor in data["devices"]["binary_sensor"]: entities.append(ScreenLogicBinarySensor(coordinator, binary_sensor)) - async_add_entities(entities, True) + async_add_entities(entities) class ScreenLogicBinarySensor(ScreenlogicEntity, BinarySensorEntity): @@ -33,10 +38,8 @@ def name(self): @property def device_class(self): """Return the device class.""" - device_class = self.sensor.get("hass_type") - if device_class in DEVICE_CLASSES: - return device_class - return None + device_class = self.sensor.get("device_type") + return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class) @property def is_on(self) -> bool: @@ -46,9 +49,4 @@ def is_on(self) -> bool: @property def sensor(self): """Shortcut to access the sensor data.""" - return self.sensor_data[self._data_key] - - @property - def sensor_data(self): - """Shortcut to access the sensors data.""" - return self.coordinator.data["sensors"] + return self.coordinator.data["sensors"][self._data_key] diff --git a/homeassistant/components/screenlogic/climate.py b/homeassistant/components/screenlogic/climate.py new file mode 100644 index 00000000000000..d289c00228c5e1 --- /dev/null +++ b/homeassistant/components/screenlogic/climate.py @@ -0,0 +1,209 @@ +"""Support for a ScreenLogic heating device.""" +import logging + +from screenlogicpy.const import EQUIPMENT, HEAT_MODE + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_PRESET_MODE, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.restore_state import RestoreEntity + +from . import ScreenlogicEntity +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE + +SUPPORTED_MODES = [HVAC_MODE_OFF, HVAC_MODE_HEAT] + +SUPPORTED_PRESETS = [ + HEAT_MODE.SOLAR, + HEAT_MODE.SOLAR_PREFERRED, + HEAT_MODE.HEATER, +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up entry.""" + entities = [] + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data["coordinator"] + + for body in data["devices"]["body"]: + entities.append(ScreenLogicClimate(coordinator, body)) + async_add_entities(entities) + + +class ScreenLogicClimate(ScreenlogicEntity, ClimateEntity, RestoreEntity): + """Represents a ScreenLogic climate entity.""" + + def __init__(self, coordinator, body): + """Initialize a ScreenLogic climate entity.""" + super().__init__(coordinator, body) + self._configured_heat_modes = [] + # Is solar listed as available equipment? + if self.coordinator.data["config"]["equipment_flags"] & EQUIPMENT.FLAG_SOLAR: + self._configured_heat_modes.extend( + [HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED] + ) + self._configured_heat_modes.append(HEAT_MODE.HEATER) + self._last_preset = None + + @property + def name(self) -> str: + """Name of the heater.""" + ent_name = self.body["heat_status"]["name"] + return f"{self.gateway_name} {ent_name}" + + @property + def min_temp(self) -> float: + """Minimum allowed temperature.""" + return self.body["min_set_point"]["value"] + + @property + def max_temp(self) -> float: + """Maximum allowed temperature.""" + return self.body["max_set_point"]["value"] + + @property + def current_temperature(self) -> float: + """Return water temperature.""" + return self.body["last_temperature"]["value"] + + @property + def target_temperature(self) -> float: + """Target temperature.""" + return self.body["heat_set_point"]["value"] + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement.""" + if self.config_data["is_celcius"]["value"] == 1: + return TEMP_CELSIUS + return TEMP_FAHRENHEIT + + @property + def hvac_mode(self) -> str: + """Return the current hvac mode.""" + if self.body["heat_mode"]["value"] > 0: + return HVAC_MODE_HEAT + return HVAC_MODE_OFF + + @property + def hvac_modes(self): + """Return th supported hvac modes.""" + return SUPPORTED_MODES + + @property + def hvac_action(self) -> str: + """Return the current action of the heater.""" + if self.body["heat_status"]["value"] > 0: + return CURRENT_HVAC_HEAT + if self.hvac_mode == HVAC_MODE_HEAT: + return CURRENT_HVAC_IDLE + return CURRENT_HVAC_OFF + + @property + def preset_mode(self) -> str: + """Return current/last preset mode.""" + if self.hvac_mode == HVAC_MODE_OFF: + return HEAT_MODE.NAME_FOR_NUM[self._last_preset] + return HEAT_MODE.NAME_FOR_NUM[self.body["heat_mode"]["value"]] + + @property + def preset_modes(self): + """All available presets.""" + return [ + HEAT_MODE.NAME_FOR_NUM[mode_num] for mode_num in self._configured_heat_modes + ] + + @property + def supported_features(self): + """Supported features of the heater.""" + return SUPPORTED_FEATURES + + async def async_set_temperature(self, **kwargs) -> None: + """Change the setpoint of the heater.""" + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: + raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}") + + if await self.hass.async_add_executor_job( + self.gateway.set_heat_temp, int(self._data_key), int(temperature) + ): + await self.coordinator.async_request_refresh() + else: + raise HomeAssistantError( + f"Failed to set_temperature {temperature} on body {self.body['body_type']['value']}" + ) + + async def async_set_hvac_mode(self, hvac_mode) -> None: + """Set the operation mode.""" + if hvac_mode == HVAC_MODE_OFF: + mode = HEAT_MODE.OFF + else: + mode = HEAT_MODE.NUM_FOR_NAME[self.preset_mode] + if await self.hass.async_add_executor_job( + self.gateway.set_heat_mode, int(self._data_key), int(mode) + ): + await self.coordinator.async_request_refresh() + else: + raise HomeAssistantError( + f"Failed to set_hvac_mode {mode} on body {self.body['body_type']['value']}" + ) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode.""" + _LOGGER.debug("Setting last_preset to %s", HEAT_MODE.NUM_FOR_NAME[preset_mode]) + self._last_preset = mode = HEAT_MODE.NUM_FOR_NAME[preset_mode] + if self.hvac_mode == HVAC_MODE_OFF: + return + if await self.hass.async_add_executor_job( + self.gateway.set_heat_mode, int(self._data_key), int(mode) + ): + await self.coordinator.async_request_refresh() + else: + raise HomeAssistantError( + f"Failed to set_preset_mode {mode} on body {self.body['body_type']['value']}" + ) + + async def async_added_to_hass(self): + """Run when entity is about to be added.""" + await super().async_added_to_hass() + + _LOGGER.debug("Startup last preset is %s", self._last_preset) + if self._last_preset is not None: + return + prev_state = await self.async_get_last_state() + if ( + prev_state is not None + and prev_state.attributes.get(ATTR_PRESET_MODE) is not None + ): + _LOGGER.debug( + "Startup setting last_preset to %s from prev_state", + HEAT_MODE.NUM_FOR_NAME[prev_state.attributes.get(ATTR_PRESET_MODE)], + ) + self._last_preset = HEAT_MODE.NUM_FOR_NAME[ + prev_state.attributes.get(ATTR_PRESET_MODE) + ] + else: + _LOGGER.debug( + "Startup setting last_preset to default (%s)", + self._configured_heat_modes[0], + ) + self._last_preset = self._configured_heat_modes[0] + + @property + def body(self): + """Shortcut to access body data.""" + return self.coordinator.data["bodies"][self._data_key] diff --git a/homeassistant/components/screenlogic/config_flow.py b/homeassistant/components/screenlogic/config_flow.py index 865c0fdbbf4a9f..32a295648445d5 100644 --- a/homeassistant/components/screenlogic/config_flow.py +++ b/homeassistant/components/screenlogic/config_flow.py @@ -120,7 +120,7 @@ async def async_step_gateway_select(self, user_input=None): mac = user_input[GATEWAY_SELECT_KEY] selected_gateway = self.discovered_gateways[mac] - await self.async_set_unique_id(mac) + await self.async_set_unique_id(mac, raise_on_progress=False) self._abort_if_unique_id_configured() return self.async_create_entry( title=name_for_mac(mac), @@ -164,7 +164,7 @@ async def async_step_gateway_entry(self, user_input=None): errors[CONF_IP_ADDRESS] = "cannot_connect" if not errors: - await self.async_set_unique_id(mac) + await self.async_set_unique_id(mac, raise_on_progress=False) self._abort_if_unique_id_configured() return self.async_create_entry( title=name_for_mac(mac), diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json index 2ff3f2c683d9be..ab3d08a0702d48 100644 --- a/homeassistant/components/screenlogic/manifest.json +++ b/homeassistant/components/screenlogic/manifest.json @@ -3,7 +3,7 @@ "name": "Pentair ScreenLogic", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/screenlogic", - "requirements": ["screenlogicpy==0.1.2"], + "requirements": ["screenlogicpy==0.2.1"], "codeowners": [ "@dieselrabbit" ], diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index f4f86cdd2aa5fb..c9c66a7568179f 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -1,7 +1,9 @@ """Support for a ScreenLogic Sensor.""" import logging -from homeassistant.components.sensor import DEVICE_CLASSES +from screenlogicpy.const import DEVICE_TYPE + +from homeassistant.components.sensor import DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE from . import ScreenlogicEntity from .const import DOMAIN @@ -10,6 +12,11 @@ PUMP_SENSORS = ("currentWatts", "currentRPM", "currentGPM") +SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS = { + DEVICE_TYPE.TEMPERATURE: DEVICE_CLASS_TEMPERATURE, + DEVICE_TYPE.ENERGY: DEVICE_CLASS_POWER, +} + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up entry.""" @@ -19,11 +26,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # Generic sensors for sensor in data["devices"]["sensor"]: entities.append(ScreenLogicSensor(coordinator, sensor)) + # Pump sensors for pump in data["devices"]["pump"]: for pump_key in PUMP_SENSORS: entities.append(ScreenLogicPumpSensor(coordinator, pump, pump_key)) - async_add_entities(entities, True) + async_add_entities(entities) class ScreenLogicSensor(ScreenlogicEntity): @@ -42,10 +50,8 @@ def unit_of_measurement(self): @property def device_class(self): """Device class of the sensor.""" - device_class = self.sensor.get("hass_type") - if device_class in DEVICE_CLASSES: - return device_class - return None + device_class = self.sensor.get("device_type") + return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class) @property def state(self): @@ -56,12 +62,7 @@ def state(self): @property def sensor(self): """Shortcut to access the sensor data.""" - return self.sensor_data[self._data_key] - - @property - def sensor_data(self): - """Shortcut to access the sensors data.""" - return self.coordinator.data["sensors"] + return self.coordinator.data["sensors"][self._data_key] class ScreenLogicPumpSensor(ScreenlogicEntity): @@ -86,10 +87,8 @@ def unit_of_measurement(self): @property def device_class(self): """Return the device class.""" - device_class = self.pump_sensor.get("hass_type") - if device_class in DEVICE_CLASSES: - return device_class - return None + device_class = self.pump_sensor.get("device_type") + return SL_DEVICE_TYPE_TO_HA_DEVICE_CLASS.get(device_class) @property def state(self): @@ -99,9 +98,4 @@ def state(self): @property def pump_sensor(self): """Shortcut to access the pump sensor data.""" - return self.pumps_data[self._pump_id][self._key] - - @property - def pumps_data(self): - """Shortcut to access the pump data.""" - return self.coordinator.data["pumps"] + return self.coordinator.data["pumps"][self._pump_id][self._key] diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py index 7c1a468df7d689..d1ef9397bfc075 100644 --- a/homeassistant/components/screenlogic/switch.py +++ b/homeassistant/components/screenlogic/switch.py @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for switch in data["devices"]["switch"]: entities.append(ScreenLogicSwitch(coordinator, switch)) - async_add_entities(entities, True) + async_add_entities(entities) class ScreenLogicSwitch(ScreenlogicEntity, SwitchEntity): @@ -47,17 +47,14 @@ async def _async_set_circuit(self, circuit_value) -> None: if await self.hass.async_add_executor_job( self.gateway.set_circuit, self._data_key, circuit_value ): - _LOGGER.debug("Screenlogic turn %s %s", circuit_value, self._data_key) + _LOGGER.debug("Turn %s %s", self._data_key, circuit_value) await self.coordinator.async_request_refresh() else: - _LOGGER.info("Screenlogic turn %s %s error", circuit_value, self._data_key) + _LOGGER.warning( + "Failed to set_circuit %s %s", self._data_key, circuit_value + ) @property def circuit(self): """Shortcut to access the circuit.""" - return self.circuits_data[self._data_key] - - @property - def circuits_data(self): - """Shortcut to access the circuits data.""" - return self.coordinator.data["circuits"] + return self.coordinator.data["circuits"][self._data_key] diff --git a/homeassistant/components/screenlogic/water_heater.py b/homeassistant/components/screenlogic/water_heater.py deleted file mode 100644 index 6b16f68e141b96..00000000000000 --- a/homeassistant/components/screenlogic/water_heater.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Support for a ScreenLogic Water Heater.""" -import logging - -from screenlogicpy.const import HEAT_MODE - -from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE, - WaterHeaterEntity, -) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT - -from . import ScreenlogicEntity -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -SUPPORTED_FEATURES = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE - -HEAT_MODE_NAMES = HEAT_MODE.Names - -MODE_NAME_TO_MODE_NUM = { - HEAT_MODE_NAMES[num]: num for num in range(len(HEAT_MODE_NAMES)) -} - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up entry.""" - entities = [] - data = hass.data[DOMAIN][config_entry.entry_id] - coordinator = data["coordinator"] - - for body in data["devices"]["water_heater"]: - entities.append(ScreenLogicWaterHeater(coordinator, body)) - async_add_entities(entities, True) - - -class ScreenLogicWaterHeater(ScreenlogicEntity, WaterHeaterEntity): - """Represents the heating functions for a body of water.""" - - @property - def name(self) -> str: - """Name of the water heater.""" - ent_name = self.body["heat_status"]["name"] - return f"{self.gateway_name} {ent_name}" - - @property - def state(self) -> str: - """State of the water heater.""" - return HEAT_MODE.GetFriendlyName(self.body["heat_status"]["value"]) - - @property - def min_temp(self) -> float: - """Minimum allowed temperature.""" - return self.body["min_set_point"]["value"] - - @property - def max_temp(self) -> float: - """Maximum allowed temperature.""" - return self.body["max_set_point"]["value"] - - @property - def current_temperature(self) -> float: - """Return water temperature.""" - return self.body["last_temperature"]["value"] - - @property - def target_temperature(self) -> float: - """Target temperature.""" - return self.body["heat_set_point"]["value"] - - @property - def temperature_unit(self) -> str: - """Return the unit of measurement.""" - if self.config_data["is_celcius"]["value"] == 1: - return TEMP_CELSIUS - return TEMP_FAHRENHEIT - - @property - def current_operation(self) -> str: - """Return operation.""" - return HEAT_MODE_NAMES[self.body["heat_mode"]["value"]] - - @property - def operation_list(self): - """All available operations.""" - supported_heat_modes = [HEAT_MODE.OFF] - # Is solar listed as available equipment? - if self.coordinator.data["config"]["equipment_flags"] & 0x1: - supported_heat_modes.extend([HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERED]) - supported_heat_modes.append(HEAT_MODE.HEATER) - - return [HEAT_MODE_NAMES[mode_num] for mode_num in supported_heat_modes] - - @property - def supported_features(self): - """Supported features of the water heater.""" - return SUPPORTED_FEATURES - - async def async_set_temperature(self, **kwargs) -> None: - """Change the setpoint of the heater.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - if await self.hass.async_add_executor_job( - self.gateway.set_heat_temp, int(self._data_key), int(temperature) - ): - await self.coordinator.async_request_refresh() - else: - _LOGGER.error("Screenlogic set_temperature error") - - async def async_set_operation_mode(self, operation_mode) -> None: - """Set the operation mode.""" - mode = MODE_NAME_TO_MODE_NUM[operation_mode] - if await self.hass.async_add_executor_job( - self.gateway.set_heat_mode, int(self._data_key), int(mode) - ): - await self.coordinator.async_request_refresh() - else: - _LOGGER.error("Screenlogic set_operation_mode error") - - @property - def body(self): - """Shortcut to access body data.""" - return self.bodies_data[self._data_key] - - @property - def bodies_data(self): - """Shortcut to access bodies data.""" - return self.coordinator.data["bodies"] diff --git a/requirements_all.txt b/requirements_all.txt index 6c3793e6c1f860..0ee7a4edb721e8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2009,7 +2009,7 @@ scapy==2.4.4 schiene==0.23 # homeassistant.components.screenlogic -screenlogicpy==0.1.2 +screenlogicpy==0.2.1 # homeassistant.components.scsgate scsgate==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 04a5c6450879bd..6ceeccec6b5b5f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1040,7 +1040,7 @@ samsungtvws==1.6.0 scapy==2.4.4 # homeassistant.components.screenlogic -screenlogicpy==0.1.2 +screenlogicpy==0.2.1 # homeassistant.components.emulated_kasa # homeassistant.components.sense diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py index 6d2c1ee2595d52..71dc4935001508 100644 --- a/tests/components/screenlogic/test_config_flow.py +++ b/tests/components/screenlogic/test_config_flow.py @@ -101,9 +101,40 @@ async def test_flow_discover_error(hass): assert result["errors"] == {} assert result["step_id"] == "gateway_entry" + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=True, + ), patch( + "homeassistant.components.screenlogic.config_flow.login.gateway_connect", + return_value="00-C0-33-01-01-01", + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "Pentair: 01-01-01" + assert result3["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + async def test_dhcp(hass): """Test DHCP discovery flow.""" + await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "dhcp"}, @@ -116,6 +147,36 @@ async def test_dhcp(hass): assert result["type"] == "form" assert result["step_id"] == "gateway_entry" + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.screenlogic.config_flow.login.create_socket", + return_value=True, + ), patch( + "homeassistant.components.screenlogic.config_flow.login.gateway_connect", + return_value="00-C0-33-01-01-01", + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == "create_entry" + assert result3["title"] == "Pentair: 01-01-01" + assert result3["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_PORT: 80, + } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + async def test_form_manual_entry(hass): """Test we get the form.""" @@ -148,6 +209,11 @@ async def test_form_manual_entry(hass): assert result2["step_id"] == "gateway_entry" with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( "homeassistant.components.screenlogic.config_flow.login.create_socket", return_value=True, ), patch( @@ -169,6 +235,8 @@ async def test_form_manual_entry(hass): CONF_IP_ADDRESS: "1.1.1.1", CONF_PORT: 80, } + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 async def test_form_cannot_connect(hass): @@ -195,9 +263,18 @@ async def test_form_cannot_connect(hass): async def test_option_flow(hass): """Test config flow options.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ), patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ): + 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"] == "form" @@ -213,9 +290,18 @@ async def test_option_flow(hass): async def test_option_flow_defaults(hass): """Test config flow options.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ), patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ): + 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"] == "form" @@ -232,9 +318,18 @@ async def test_option_flow_defaults(hass): async def test_option_flow_input_floor(hass): """Test config flow options.""" - entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) + entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) + with patch( + "homeassistant.components.screenlogic.async_setup", return_value=True + ), patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ): + 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"] == "form" From 7473f25afd175c7e9cf1350aa4db7340088cd58b Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sun, 21 Mar 2021 20:39:33 +0900 Subject: [PATCH 1416/1818] Fix typo in homekit strings.json (#48176) Co-authored-by: J. Nick Koston --- homeassistant/components/homekit/strings.json | 2 +- .../components/homekit/translations/en.json | 23 +++---------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index a9b7c1c6cc1efa..4e3437a1fa0638 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -18,7 +18,7 @@ "mode": "[%key:common::config_flow::data::mode%]", "entities": "Entities" }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a seperate HomeKit accessory will beeach tv media player and camera.", + "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a separate HomeKit accessory will be created for each tv media player and camera.", "title": "Select entities to be included" }, "cameras": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 3b0129567c4ee3..bc516eebf15107 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,29 +4,13 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { - "accessory_mode": { - "data": { - "entity_id": "Entity" - }, - "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", - "title": "Select entity to be included" - }, - "bridge_mode": { - "data": { - "include_domains": "Domains to include" - }, - "description": "Choose the domains to be included. All supported entities in the domain will be included.", - "title": "Select domains to be included" - }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains": "Domains to include", - "mode": "Mode" + "include_domains": "Domains to include" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -37,8 +21,7 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", - "safe_mode": "Safe Mode (enable only if pairing fails)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" @@ -55,7 +38,7 @@ "entities": "Entities", "mode": "Mode" }, - "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a seperate HomeKit accessory will beeach tv media player and camera.", + "description": "Choose the entities to be included. In accessory mode, only a single entity is included. In bridge include mode, all entities in the domain will be included unless specific entities are selected. In bridge exclude mode, all entities in the domain will be included except for the excluded entities. For best performance, a separate HomeKit accessory will be created for each tv media player and camera.", "title": "Select entities to be included" }, "init": { From 9739707f62cc394154a4ec407a863d17792d6d8e Mon Sep 17 00:00:00 2001 From: xonestonex <51967236+xonestonex@users.noreply.github.com> Date: Sun, 21 Mar 2021 23:03:23 +0100 Subject: [PATCH 1417/1818] Preset support for MOES thermostat valves (#48178) --- homeassistant/components/zha/climate.py | 90 ++++++++++ homeassistant/components/zha/core/const.py | 3 + tests/components/zha/test_climate.py | 183 +++++++++++++++++++++ 3 files changed, 276 insertions(+) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index e2c4faa8539e20..8292335ce23e2c 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -31,6 +31,9 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, PRESET_NONE, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, @@ -49,6 +52,8 @@ CHANNEL_THERMOSTAT, DATA_ZHA, DATA_ZHA_DISPATCHERS, + PRESET_COMPLEX, + PRESET_SCHEDULE, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -595,3 +600,88 @@ def _rm_rs_action(self) -> str | None: ) class CentralitePearl(ZenWithinThermostat): """Centralite Pearl Thermostat implementation.""" + + +@STRICT_MATCH( + channel_names=CHANNEL_THERMOSTAT, + manufacturers={ + "_TZE200_ckud7u2l", + "_TZE200_ywdxldoj", + "_TYST11_ckud7u2l", + "_TYST11_ywdxldoj", + }, +) +class MoesThermostat(Thermostat): + """Moes Thermostat implementation.""" + + def __init__(self, unique_id, zha_device, channels, **kwargs): + """Initialize ZHA Thermostat instance.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + self._presets = [ + PRESET_NONE, + PRESET_AWAY, + PRESET_SCHEDULE, + PRESET_COMFORT, + PRESET_ECO, + PRESET_BOOST, + PRESET_COMPLEX, + ] + self._supported_flags |= SUPPORT_PRESET_MODE + + @property + def hvac_modes(self) -> tuple[str, ...]: + """Return only the heat mode, because the device can't be turned off.""" + return (HVAC_MODE_HEAT,) + + async def async_attribute_updated(self, record): + """Handle attribute update from device.""" + if record.attr_name == "operation_preset": + if record.value == 0: + self._preset = PRESET_AWAY + if record.value == 1: + self._preset = PRESET_SCHEDULE + if record.value == 2: + self._preset = PRESET_NONE + if record.value == 3: + self._preset = PRESET_COMFORT + if record.value == 4: + self._preset = PRESET_ECO + if record.value == 5: + self._preset = PRESET_BOOST + if record.value == 6: + self._preset = PRESET_COMPLEX + await super().async_attribute_updated(record) + + async def async_preset_handler(self, preset: str, enable: bool = False) -> bool: + """Set the preset mode.""" + mfg_code = self._zha_device.manufacturer_code + if not enable: + return await self._thrm.write_attributes( + {"operation_preset": 2}, manufacturer=mfg_code + ) + if preset == PRESET_AWAY: + return await self._thrm.write_attributes( + {"operation_preset": 0}, manufacturer=mfg_code + ) + if preset == PRESET_SCHEDULE: + return await self._thrm.write_attributes( + {"operation_preset": 1}, manufacturer=mfg_code + ) + if preset == PRESET_COMFORT: + return await self._thrm.write_attributes( + {"operation_preset": 3}, manufacturer=mfg_code + ) + if preset == PRESET_ECO: + return await self._thrm.write_attributes( + {"operation_preset": 4}, manufacturer=mfg_code + ) + if preset == PRESET_BOOST: + return await self._thrm.write_attributes( + {"operation_preset": 5}, manufacturer=mfg_code + ) + if preset == PRESET_COMPLEX: + return await self._thrm.write_attributes( + {"operation_preset": 6}, manufacturer=mfg_code + ) + + return False diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 45453ba75454de..ed45400861a367 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -173,6 +173,9 @@ POWER_MAINS_POWERED = "Mains" POWER_BATTERY_OR_UNKNOWN = "Battery or Unknown" +PRESET_SCHEDULE = "schedule" +PRESET_COMPLEX = "complex" + class RadioType(enum.Enum): # pylint: disable=invalid-name diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index 05412ddb64dc6f..ea2d6dfb7e37af 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -33,6 +33,9 @@ HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, PRESET_NONE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, @@ -44,6 +47,7 @@ HVAC_MODE_2_SYSTEM, SEQ_OF_OPERATION, ) +from homeassistant.components.zha.core.const import PRESET_COMPLEX, PRESET_SCHEDULE from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN from .common import async_enable_traffic, find_entity_id, send_attributes_report @@ -103,8 +107,23 @@ "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], } } + +CLIMATE_MOES = { + 1: { + "device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT, + "in_clusters": [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.hvac.Thermostat.cluster_id, + zigpy.zcl.clusters.hvac.UserInterface.cluster_id, + 61148, + ], + "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], + } +} MANUF_SINOPE = "Sinope Technologies" MANUF_ZEN = "Zen Within" +MANUF_MOES = "_TZE200_ckud7u2l" ZCL_ATTR_PLUG = { "abs_min_heat_setpoint_limit": 800, @@ -183,6 +202,13 @@ async def device_climate_zen(device_climate_mock): return await device_climate_mock(CLIMATE_ZEN, manuf=MANUF_ZEN) +@pytest.fixture +async def device_climate_moes(device_climate_mock): + """MOES thermostat.""" + + return await device_climate_mock(CLIMATE_MOES, manuf=MANUF_MOES) + + def test_sequence_mappings(): """Test correct mapping between control sequence -> HVAC Mode -> Sysmode.""" @@ -1106,3 +1132,160 @@ async def test_set_fan_mode(hass, device_climate_fan): ) assert fan_cluster.write_attributes.await_count == 1 assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5} + + +async def test_set_moes_preset(hass, device_climate_moes): + """Test setting preset for moes trv.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_moes, hass) + thrm_cluster = device_climate_moes.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 0 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_SCHEDULE}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 1 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMFORT}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 3 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 4 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 5 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMPLEX}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "operation_preset": 6 + } + + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "operation_preset": 2 + } + + +async def test_set_moes_operation_mode(hass, device_climate_moes): + """Test setting preset for moes trv.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_moes, hass) + thrm_cluster = device_climate_moes.device.endpoints[1].thermostat + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 0}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 1}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_SCHEDULE + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 2}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 3}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_COMFORT + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 4}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_ECO + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 5}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_BOOST + + await send_attributes_report(hass, thrm_cluster, {"operation_preset": 6}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_COMPLEX From 2912db84d735527e0b04f01ed934362b7ed42e2b Mon Sep 17 00:00:00 2001 From: Nate Clark Date: Sun, 21 Mar 2021 19:16:34 -0400 Subject: [PATCH 1418/1818] Handle switch state updates from Konnected device (#48167) Co-authored-by: Martin Hjelmare --- homeassistant/components/konnected/__init__.py | 10 ++++++++-- homeassistant/components/konnected/handlers.py | 2 +- homeassistant/components/konnected/switch.py | 14 +++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 7ed4d5c1ac6c34..db1e20204cd7c2 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -360,8 +360,14 @@ async def update_sensor(self, request: Request, device_id) -> Response: try: zone_num = str(payload.get(CONF_ZONE) or PIN_TO_ZONE[payload[CONF_PIN]]) payload[CONF_ZONE] = zone_num - zone_data = device[CONF_BINARY_SENSORS].get(zone_num) or next( - (s for s in device[CONF_SENSORS] if s[CONF_ZONE] == zone_num), None + zone_data = ( + device[CONF_BINARY_SENSORS].get(zone_num) + or next( + (s for s in device[CONF_SWITCHES] if s[CONF_ZONE] == zone_num), None + ) + or next( + (s for s in device[CONF_SENSORS] if s[CONF_ZONE] == zone_num), None + ) ) except KeyError: zone_data = None diff --git a/homeassistant/components/konnected/handlers.py b/homeassistant/components/konnected/handlers.py index 923e5d63899b68..879d0d4cf8fa6e 100644 --- a/homeassistant/components/konnected/handlers.py +++ b/homeassistant/components/konnected/handlers.py @@ -18,7 +18,7 @@ @HANDLERS.register("state") async def async_handle_state_update(hass, context, msg): - """Handle a binary sensor state update.""" + """Handle a binary sensor or switch state update.""" _LOGGER.debug("[state handler] context: %s msg: %s", context, msg) entity_id = context.get(ATTR_ENTITY_ID) state = bool(int(msg.get(ATTR_STATE))) diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py index b599fe5524255d..9c9f8193dcd6c6 100644 --- a/homeassistant/components/konnected/switch.py +++ b/homeassistant/components/konnected/switch.py @@ -9,6 +9,8 @@ CONF_SWITCHES, CONF_ZONE, ) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import ToggleEntity from .const import ( @@ -130,6 +132,16 @@ def _set_state(self, state): state, ) + @callback + def async_set_state(self, state): + """Update the switch state.""" + self._set_state(state) + async def async_added_to_hass(self): - """Store entity_id.""" + """Store entity_id and register state change callback.""" self._data["entity_id"] = self.entity_id + self.async_on_remove( + async_dispatcher_connect( + self.hass, f"konnected.{self.entity_id}.update", self.async_set_state + ) + ) From 6fab4a2c82d94582b7dedabaa3813dbc1af781e9 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 22 Mar 2021 00:08:34 +0000 Subject: [PATCH 1419/1818] [ci skip] Translation update --- .../components/foscam/translations/de.json | 1 + .../components/homekit/translations/ca.json | 2 +- .../components/homekit/translations/en.json | 21 +++++++++++++++++-- .../keenetic_ndms2/translations/de.json | 1 + .../lutron_caseta/translations/de.json | 3 ++- .../mobile_app/translations/de.json | 3 ++- .../components/mysensors/translations/de.json | 4 ++++ .../opentherm_gw/translations/nl.json | 3 ++- .../squeezebox/translations/de.json | 1 + 9 files changed, 33 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/foscam/translations/de.json b/homeassistant/components/foscam/translations/de.json index e3011f61615a42..d87044b579a80a 100644 --- a/homeassistant/components/foscam/translations/de.json +++ b/homeassistant/components/foscam/translations/de.json @@ -15,6 +15,7 @@ "host": "Host", "password": "Passwort", "port": "Port", + "rtsp_port": "RTSP-Port", "username": "Benutzername" } } diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index dbd83622d8a7ab..1ad6d63a0ffe84 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -55,7 +55,7 @@ "entities": "Entitats", "mode": "Mode" }, - "description": "Tria les entitats que vulguis incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", + "description": "Tria les entitats a incloure. En mode accessori, nom\u00e9s s'inclou una sola entitat. En mode enlla\u00e7 inclusiu, s'exposaran totes les entitats del domini tret de que se'n seleccionin algunes en concret. En mode enlla\u00e7 excusiu, s'inclouran totes les entitats del domini excepte les entitats excloses. Per obtenir el millor rendiment, es crea una inst\u00e0ncia HomeKit per a cada repoductor multim\u00e8dia/TV i c\u00e0mera.", "title": "Selecciona les entitats a incloure" }, "init": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index bc516eebf15107..42bcb5bd0f27d5 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -4,13 +4,29 @@ "port_name_in_use": "An accessory or bridge with the same name or port is already configured." }, "step": { + "accessory_mode": { + "data": { + "entity_id": "Entity" + }, + "description": "Choose the entity to be included. In accessory mode, only a single entity is included.", + "title": "Select entity to be included" + }, + "bridge_mode": { + "data": { + "include_domains": "Domains to include" + }, + "description": "Choose the domains to be included. All supported entities in the domain will be included.", + "title": "Select domains to be included" + }, "pairing": { "description": "To complete pairing following the instructions in \u201cNotifications\u201d under \u201cHomeKit Pairing\u201d.", "title": "Pair HomeKit" }, "user": { "data": { - "include_domains": "Domains to include" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include", + "mode": "Mode" }, "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", "title": "Select domains to be included" @@ -21,7 +37,8 @@ "step": { "advanced": { "data": { - "auto_start": "Autostart (disable if you are calling the homekit.start service manually)" + "auto_start": "Autostart (disable if you are calling the homekit.start service manually)", + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if HomeKit is not functional.", "title": "Advanced Configuration" diff --git a/homeassistant/components/keenetic_ndms2/translations/de.json b/homeassistant/components/keenetic_ndms2/translations/de.json index 5994df16940c15..cc9a3630ab1d02 100644 --- a/homeassistant/components/keenetic_ndms2/translations/de.json +++ b/homeassistant/components/keenetic_ndms2/translations/de.json @@ -22,6 +22,7 @@ "step": { "user": { "data": { + "interfaces": "Schnittstellen zum Scannen ausw\u00e4hlen", "scan_interval": "Scanintervall" } } diff --git a/homeassistant/components/lutron_caseta/translations/de.json b/homeassistant/components/lutron_caseta/translations/de.json index ce771f52acd009..392648136bddac 100644 --- a/homeassistant/components/lutron_caseta/translations/de.json +++ b/homeassistant/components/lutron_caseta/translations/de.json @@ -29,7 +29,8 @@ "button_3": "Dritte Taste", "button_4": "Vierte Taste", "off": "Aus", - "on": "An" + "on": "An", + "stop_all": "Alle anhalten" } } } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/de.json b/homeassistant/components/mobile_app/translations/de.json index 493ceb4dfd1bad..721cbc09f8dd48 100644 --- a/homeassistant/components/mobile_app/translations/de.json +++ b/homeassistant/components/mobile_app/translations/de.json @@ -13,5 +13,6 @@ "action_type": { "notify": "Sende eine Benachrichtigung" } - } + }, + "title": "Mobile App" } \ No newline at end of file diff --git a/homeassistant/components/mysensors/translations/de.json b/homeassistant/components/mysensors/translations/de.json index 98a1ca9cd32869..d05e2bdb47b61a 100644 --- a/homeassistant/components/mysensors/translations/de.json +++ b/homeassistant/components/mysensors/translations/de.json @@ -6,6 +6,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_device": "Ung\u00fcltiges Ger\u00e4t", "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_serial": "Ung\u00fcltiger Serieller Port", "invalid_version": "Ung\u00fcltige MySensors Version", "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" @@ -16,6 +17,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "invalid_device": "Ung\u00fcltiges Ger\u00e4t", "invalid_ip": "Ung\u00fcltige IP-Adresse", + "invalid_serial": "Ung\u00fcltiger Serieller Port", "invalid_version": "Ung\u00fcltige MySensors Version", "not_a_number": "Bitte eine Nummer eingeben", "unknown": "Unerwarteter Fehler" @@ -23,6 +25,7 @@ "step": { "gw_mqtt": { "data": { + "retain": "MQTT behalten", "version": "MySensors Version" }, "description": "MQTT-Gateway einrichten" @@ -38,6 +41,7 @@ "gw_tcp": { "data": { "device": "IP-Adresse des Gateways", + "tcp_port": "Port", "version": "MySensors Version" }, "description": "Einrichtung des Ethernet-Gateways" diff --git a/homeassistant/components/opentherm_gw/translations/nl.json b/homeassistant/components/opentherm_gw/translations/nl.json index e832e790c1ec51..785d09fdfeb125 100644 --- a/homeassistant/components/opentherm_gw/translations/nl.json +++ b/homeassistant/components/opentherm_gw/translations/nl.json @@ -21,7 +21,8 @@ "init": { "data": { "floor_temperature": "Vloertemperatuur", - "precision": "Precisie" + "precision": "Precisie", + "set_precision": "Precisie instellen" }, "description": "Opties voor de OpenTherm Gateway" } diff --git a/homeassistant/components/squeezebox/translations/de.json b/homeassistant/components/squeezebox/translations/de.json index 742210f3dc6d98..cdbc5c1426a347 100644 --- a/homeassistant/components/squeezebox/translations/de.json +++ b/homeassistant/components/squeezebox/translations/de.json @@ -8,6 +8,7 @@ "invalid_auth": "Ung\u00fcltige Authentifizierung", "unknown": "Unerwarteter Fehler" }, + "flow_title": "Logitech Squeezebox", "step": { "edit": { "data": { From 3f2ca16ad74bddbd19d01e37e78a05948811a778 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 18:44:29 -1000 Subject: [PATCH 1420/1818] Index config entries by id (#48199) --- homeassistant/config_entries.py | 31 ++++++------ tests/common.py | 10 ++-- .../components/config/test_config_entries.py | 4 +- tests/components/hue/conftest.py | 15 +++--- tests/components/huisbaasje/test_init.py | 50 +++++++++---------- tests/components/huisbaasje/test_sensor.py | 35 ++++++------- tests/test_config_entries.py | 35 ++++++++++++- 7 files changed, 106 insertions(+), 74 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 157acb545c1dd8..b5491d838007a5 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -619,7 +619,7 @@ def __init__(self, hass: HomeAssistant, hass_config: dict) -> None: self.flow = ConfigEntriesFlowManager(hass, self, hass_config) self.options = OptionsFlowManager(hass) self._hass_config = hass_config - self._entries: list[ConfigEntry] = [] + self._entries: dict[str, ConfigEntry] = {} self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) EntityRegistryDisabledHandler(hass).async_setup() @@ -629,7 +629,7 @@ def async_domains(self) -> list[str]: seen: set[str] = set() result = [] - for entry in self._entries: + for entry in self._entries.values(): if entry.domain not in seen: seen.add(entry.domain) result.append(entry.domain) @@ -639,21 +639,22 @@ def async_domains(self) -> list[str]: @callback def async_get_entry(self, entry_id: str) -> ConfigEntry | None: """Return entry with matching entry_id.""" - for entry in self._entries: - if entry_id == entry.entry_id: - return entry - return None + return self._entries.get(entry_id) @callback def async_entries(self, domain: str | None = None) -> list[ConfigEntry]: """Return all entries or entries for a specific domain.""" if domain is None: - return list(self._entries) - return [entry for entry in self._entries if entry.domain == domain] + return list(self._entries.values()) + return [entry for entry in self._entries.values() if entry.domain == domain] async def async_add(self, entry: ConfigEntry) -> None: """Add and setup an entry.""" - self._entries.append(entry) + if entry.entry_id in self._entries: + raise HomeAssistantError( + f"An entry with the id {entry.entry_id} already exists." + ) + self._entries[entry.entry_id] = entry await self.async_setup(entry.entry_id) self._async_schedule_save() @@ -671,7 +672,7 @@ async def async_remove(self, entry_id: str) -> dict[str, Any]: await entry.async_remove(self.hass) - self._entries.remove(entry) + del self._entries[entry.entry_id] self._async_schedule_save() dev_reg, ent_reg = await asyncio.gather( @@ -707,11 +708,11 @@ async def async_initialize(self) -> None: ) if config is None: - self._entries = [] + self._entries = {} return - self._entries = [ - ConfigEntry( + self._entries = { + entry["entry_id"]: ConfigEntry( version=entry["version"], domain=entry["domain"], entry_id=entry["entry_id"], @@ -730,7 +731,7 @@ async def async_initialize(self) -> None: disabled_by=entry.get("disabled_by"), ) for entry in config["entries"] - ] + } async def async_setup(self, entry_id: str) -> bool: """Set up a config entry. @@ -920,7 +921,7 @@ def _async_schedule_save(self) -> None: @callback def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data to save.""" - return {"entries": [entry.as_dict() for entry in self._entries]} + return {"entries": [entry.as_dict() for entry in self._entries.values()]} async def _old_conf_migrator(old_config: dict[str, Any]) -> dict[str, Any]: diff --git a/tests/common.py b/tests/common.py index 5f8626afb4eb40..984b35716f0de6 100644 --- a/tests/common.py +++ b/tests/common.py @@ -18,7 +18,6 @@ import types from typing import Any, Awaitable, Collection from unittest.mock import AsyncMock, Mock, patch -import uuid from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401 @@ -61,6 +60,7 @@ from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as date_util from homeassistant.util.unit_system import METRIC_SYSTEM +import homeassistant.util.uuid as uuid_util import homeassistant.util.yaml.loader as yaml_loader _LOGGER = logging.getLogger(__name__) @@ -276,7 +276,7 @@ async def _await_count_and_log_pending( hass.config.skip_pip = True hass.config_entries = config_entries.ConfigEntries(hass, {}) - hass.config_entries._entries = [] + hass.config_entries._entries = {} hass.config_entries._store._async_ensure_stop_listener = lambda: None # Load the registries @@ -737,7 +737,7 @@ def __init__( ): """Initialize a mock config entry.""" kwargs = { - "entry_id": entry_id or uuid.uuid4().hex, + "entry_id": entry_id or uuid_util.random_uuid_hex(), "domain": domain, "data": data or {}, "system_options": system_options, @@ -756,11 +756,11 @@ def __init__( def add_to_hass(self, hass): """Test helper to add entry to hass.""" - hass.config_entries._entries.append(self) + hass.config_entries._entries[self.entry_id] = self def add_to_manager(self, manager): """Test helper to add entry to entry manager.""" - manager._entries.append(self) + manager._entries[self.entry_id] = self def patch_yaml_files(files_dict, endswith=True): diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6bb1f1885eb8b5..d6cc474fa8b4d0 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -569,7 +569,7 @@ async def async_step_init(self, user_input=None): source="bla", connection_class=core_ce.CONN_CLASS_LOCAL_POLL, ).add_to_hass(hass) - entry = hass.config_entries._entries[0] + entry = hass.config_entries.async_entries()[0] with patch.dict(HANDLERS, {"test": TestFlow}): url = "/api/config/config_entries/options/flow" @@ -618,7 +618,7 @@ async def async_step_finish(self, user_input=None): source="bla", connection_class=core_ce.CONN_CLASS_LOCAL_POLL, ).add_to_hass(hass) - entry = hass.config_entries._entries[0] + entry = hass.config_entries.async_entries()[0] with patch.dict(HANDLERS, {"test": TestFlow}): url = "/api/config/config_entries/options/flow" diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index db45b9fcd4d31f..3fc55692cc489c 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -12,6 +12,7 @@ from homeassistant.components import hue from homeassistant.components.hue import sensor_base as hue_sensor_base +from tests.common import MockConfigEntry from tests.components.light.conftest import mock_light_profiles # noqa: F401 @@ -111,13 +112,11 @@ async def setup_bridge_for_sensors(hass, mock_bridge, hostname=None): if hostname is None: hostname = "mock-host" hass.config.components.add(hue.DOMAIN) - config_entry = config_entries.ConfigEntry( - 1, - hue.DOMAIN, - "Mock Title", - {"host": hostname}, - "test", - config_entries.CONN_CLASS_LOCAL_POLL, + config_entry = MockConfigEntry( + domain=hue.DOMAIN, + title="Mock Title", + data={"host": hostname}, + connection_class=config_entries.CONN_CLASS_LOCAL_POLL, system_options={}, ) mock_bridge.config_entry = config_entry @@ -125,7 +124,7 @@ async def setup_bridge_for_sensors(hass, mock_bridge, hostname=None): await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") await hass.config_entries.async_forward_entry_setup(config_entry, "sensor") # simulate a full setup by manually adding the bridge config entry - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) # and make sure it completes before going further await hass.async_block_till_done() diff --git a/tests/components/huisbaasje/test_init.py b/tests/components/huisbaasje/test_init.py index 3de6af83e46f07..2d68cdf8a11af3 100644 --- a/tests/components/huisbaasje/test_init.py +++ b/tests/components/huisbaasje/test_init.py @@ -9,12 +9,12 @@ ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED, ENTRY_STATE_SETUP_ERROR, - ConfigEntry, ) from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry from tests.components.huisbaasje.test_data import MOCK_CURRENT_MEASUREMENTS @@ -36,20 +36,20 @@ async def test_setup_entry(hass: HomeAssistant): return_value=MOCK_CURRENT_MEASUREMENTS, ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert config_entry.state == ENTRY_STATE_NOT_LOADED assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -77,20 +77,20 @@ async def test_setup_entry_error(hass: HomeAssistant): "huisbaasje.Huisbaasje.authenticate", side_effect=HuisbaasjeException ) as mock_authenticate: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert config_entry.state == ENTRY_STATE_NOT_LOADED await hass.config_entries.async_setup(config_entry.entry_id) @@ -119,20 +119,20 @@ async def test_unload_entry(hass: HomeAssistant): return_value=MOCK_CURRENT_MEASUREMENTS, ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) # Load config entry assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/huisbaasje/test_sensor.py b/tests/components/huisbaasje/test_sensor.py index d1ffe565c84290..cfb17cd5f2d5b3 100644 --- a/tests/components/huisbaasje/test_sensor.py +++ b/tests/components/huisbaasje/test_sensor.py @@ -2,10 +2,11 @@ from unittest.mock import patch from homeassistant.components import huisbaasje -from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL, ConfigEntry +from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from tests.common import MockConfigEntry from tests.components.huisbaasje.test_data import ( MOCK_CURRENT_MEASUREMENTS, MOCK_LIMITED_CURRENT_MEASUREMENTS, @@ -24,20 +25,20 @@ async def test_setup_entry(hass: HomeAssistant): ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -81,20 +82,20 @@ async def test_setup_entry_absent_measurement(hass: HomeAssistant): ) as mock_current_measurements: hass.config.components.add(huisbaasje.DOMAIN) - config_entry = ConfigEntry( - 1, - huisbaasje.DOMAIN, - "userId", - { + config_entry = MockConfigEntry( + version=1, + domain=huisbaasje.DOMAIN, + title="userId", + data={ CONF_ID: "userId", CONF_USERNAME: "username", CONF_PASSWORD: "password", }, - "test", - CONN_CLASS_CLOUD_POLL, + source="test", + connection_class=CONN_CLASS_CLOUD_POLL, system_options={}, ) - hass.config_entries._entries.append(config_entry) + config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 9abb68e97c9872..09ab60e4300442 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -7,7 +7,7 @@ from homeassistant import config_entries, data_entry_flow, loader from homeassistant.core import callback -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -44,7 +44,7 @@ class MockFlowHandler(config_entries.ConfigFlow): def manager(hass): """Fixture of a loaded config manager.""" manager = config_entries.ConfigEntries(hass, {}) - manager._entries = [] + manager._entries = {} manager._store._async_ensure_stop_listener = lambda: None hass.config_entries = manager return manager @@ -1383,6 +1383,37 @@ async def async_step_user(self, user_input=None): assert len(async_remove_entry.mock_calls) == 1 +async def test_entry_id_existing_entry(hass, manager): + """Test that we throw when the entry id collides.""" + collide_entry_id = "collide" + hass.config.components.add("comp") + MockConfigEntry( + entry_id=collide_entry_id, + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + unique_id="mock-unique-id", + ).add_to_hass(hass) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Test user step.""" + return self.async_create_entry(title="mock-title", data={"via": "flow"}) + + with pytest.raises(HomeAssistantError), patch.dict( + config_entries.HANDLERS, {"comp": TestFlow} + ), patch( + "homeassistant.config_entries.uuid_util.random_uuid_hex", + return_value=collide_entry_id, + ): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_USER} + ) + + async def test_unique_id_update_existing_entry_without_reload(hass, manager): """Test that we update an entry if there already is an entry with unique ID.""" hass.config.components.add("comp") From fd310e1f4133d567513a09fe22b144c74a46faed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 18:55:20 -1000 Subject: [PATCH 1421/1818] Update homekit to improve representation of activity based remotes (#47261) --- homeassistant/components/homekit/__init__.py | 1 + .../components/homekit/accessories.py | 6 +- .../components/homekit/config_flow.py | 6 +- .../components/homekit/type_media_players.py | 126 ++--------- .../components/homekit/type_remotes.py | 214 ++++++++++++++++++ homeassistant/components/homekit/util.py | 3 + script/hassfest/dependencies.py | 1 + tests/components/homekit/test_type_remote.py | 148 ++++++++++++ 8 files changed, 397 insertions(+), 108 deletions(-) create mode 100644 homeassistant/components/homekit/type_remotes.py create mode 100644 tests/components/homekit/test_type_remote.py diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 4fea31e723808a..0e4bcc28aabfd1 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -50,6 +50,7 @@ type_lights, type_locks, type_media_players, + type_remotes, type_security_systems, type_sensors, type_switches, diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index b6ff11aa26d737..307dbf0e80636c 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -12,6 +12,7 @@ DEVICE_CLASS_WINDOW, ) from homeassistant.components.media_player import DEVICE_CLASS_TV +from homeassistant.components.remote import SUPPORT_ACTIVITY from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, @@ -103,6 +104,7 @@ def get_accessory(hass, driver, state, aid, config): a_type = None name = config.get(CONF_NAME, state.name) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if state.domain == "alarm_control_panel": a_type = "SecuritySystem" @@ -115,7 +117,6 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain == "cover": device_class = state.attributes.get(ATTR_DEVICE_CLASS) - features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if device_class in (DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE) and features & ( cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE @@ -179,6 +180,9 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain == "vacuum": a_type = "Vacuum" + elif state.domain == "remote" and features & SUPPORT_ACTIVITY: + a_type = "ActivityRemote" + elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): a_type = "Switch" diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 758e25b97bcc76..1fc99bb85851e7 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -8,6 +8,7 @@ from homeassistant import config_entries from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_FRIENDLY_NAME, @@ -53,7 +54,7 @@ INCLUDE_EXCLUDE_MODES = [MODE_EXCLUDE, MODE_INCLUDE] -DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN] +DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN] NEVER_BRIDGED_DOMAINS = [CAMERA_DOMAIN] CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}." @@ -74,7 +75,7 @@ "lock", MEDIA_PLAYER_DOMAIN, "person", - "remote", + REMOTE_DOMAIN, "scene", "script", "sensor", @@ -93,6 +94,7 @@ "light", "lock", MEDIA_PLAYER_DOMAIN, + REMOTE_DOMAIN, "switch", "vacuum", "water_heater", diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index b54b62372f9d9e..5cd27109bd8fc7 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -1,7 +1,7 @@ """Class to hold all media player accessories.""" import logging -from pyhap.const import CATEGORY_SWITCH, CATEGORY_TELEVISION +from pyhap.const import CATEGORY_SWITCH from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, @@ -42,17 +42,9 @@ from .const import ( ATTR_KEY_NAME, CHAR_ACTIVE, - CHAR_ACTIVE_IDENTIFIER, - CHAR_CONFIGURED_NAME, - CHAR_CURRENT_VISIBILITY_STATE, - CHAR_IDENTIFIER, - CHAR_INPUT_SOURCE_TYPE, - CHAR_IS_CONFIGURED, CHAR_MUTE, CHAR_NAME, CHAR_ON, - CHAR_REMOTE_KEY, - CHAR_SLEEP_DISCOVER_MODE, CHAR_VOLUME, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR, @@ -62,43 +54,15 @@ FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, - KEY_ARROW_DOWN, - KEY_ARROW_LEFT, - KEY_ARROW_RIGHT, - KEY_ARROW_UP, - KEY_BACK, - KEY_EXIT, - KEY_FAST_FORWARD, - KEY_INFORMATION, - KEY_NEXT_TRACK, KEY_PLAY_PAUSE, - KEY_PREVIOUS_TRACK, - KEY_REWIND, - KEY_SELECT, - SERV_INPUT_SOURCE, SERV_SWITCH, - SERV_TELEVISION, SERV_TELEVISION_SPEAKER, ) +from .type_remotes import REMOTE_KEYS, RemoteInputSelectAccessory from .util import get_media_player_features _LOGGER = logging.getLogger(__name__) -MEDIA_PLAYER_KEYS = { - 0: KEY_REWIND, - 1: KEY_FAST_FORWARD, - 2: KEY_NEXT_TRACK, - 3: KEY_PREVIOUS_TRACK, - 4: KEY_ARROW_UP, - 5: KEY_ARROW_DOWN, - 6: KEY_ARROW_LEFT, - 7: KEY_ARROW_RIGHT, - 8: KEY_SELECT, - 9: KEY_BACK, - 10: KEY_EXIT, - 11: KEY_PLAY_PAUSE, - 15: KEY_INFORMATION, -} # Names may not contain special characters # or emjoi (/ is a special character for Apple) @@ -250,21 +214,21 @@ def async_update_state(self, new_state): @TYPES.register("TelevisionMediaPlayer") -class TelevisionMediaPlayer(HomeAccessory): +class TelevisionMediaPlayer(RemoteInputSelectAccessory): """Generate a Television Media Player accessory.""" def __init__(self, *args): - """Initialize a Switch accessory object.""" - super().__init__(*args, category=CATEGORY_TELEVISION) + """Initialize a Television Media Player accessory object.""" + super().__init__( + SUPPORT_SELECT_SOURCE, + ATTR_INPUT_SOURCE, + ATTR_INPUT_SOURCE_LIST, + *args, + ) state = self.hass.states.get(self.entity_id) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - self.support_select_source = False - - self.sources = [] - - self.chars_tv = [CHAR_REMOTE_KEY] self.chars_speaker = [] - features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) self._supports_play_pause = features & (SUPPORT_PLAY | SUPPORT_PAUSE) if features & SUPPORT_VOLUME_MUTE or features & SUPPORT_VOLUME_STEP: @@ -274,27 +238,11 @@ def __init__(self, *args): if features & SUPPORT_VOLUME_SET: self.chars_speaker.append(CHAR_VOLUME) - source_list = state.attributes.get(ATTR_INPUT_SOURCE_LIST, []) - if source_list and features & SUPPORT_SELECT_SOURCE: - self.support_select_source = True - - serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv) - self.set_primary_service(serv_tv) - serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name) - serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True) - self.char_active = serv_tv.configure_char( - CHAR_ACTIVE, setter_callback=self.set_on_off - ) - - self.char_remote_key = serv_tv.configure_char( - CHAR_REMOTE_KEY, setter_callback=self.set_remote_key - ) - if CHAR_VOLUME_SELECTOR in self.chars_speaker: serv_speaker = self.add_preload_service( SERV_TELEVISION_SPEAKER, self.chars_speaker ) - serv_tv.add_linked_service(serv_speaker) + self.serv_tv.add_linked_service(serv_speaker) name = f"{self.display_name} Volume" serv_speaker.configure_char(CHAR_NAME, value=name) @@ -318,25 +266,6 @@ def __init__(self, *args): CHAR_VOLUME, setter_callback=self.set_volume ) - if self.support_select_source: - self.sources = source_list - self.char_input_source = serv_tv.configure_char( - CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source - ) - for index, source in enumerate(self.sources): - serv_input = self.add_preload_service( - SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME] - ) - serv_tv.add_linked_service(serv_input) - serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source) - serv_input.configure_char(CHAR_NAME, value=source) - serv_input.configure_char(CHAR_IDENTIFIER, value=index) - serv_input.configure_char(CHAR_IS_CONFIGURED, value=True) - input_type = 3 if "hdmi" in source.lower() else 0 - serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type) - serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) - _LOGGER.debug("%s: Added source %s", self.entity_id, source) - self.async_update_state(state) def set_on_off(self, value): @@ -377,7 +306,7 @@ def set_input_source(self, value): def set_remote_key(self, value): """Send remote key value if call came from HomeKit.""" _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) - key_name = MEDIA_PLAYER_KEYS.get(value) + key_name = REMOTE_KEYS.get(value) if key_name is None: _LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value) return @@ -393,12 +322,13 @@ def set_remote_key(self, value): service = SERVICE_MEDIA_PLAY_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} self.async_call_service(DOMAIN, service, params) - else: - # Unhandled keys can be handled by listening to the event bus - self.hass.bus.fire( - EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, - {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, - ) + return + + # Unhandled keys can be handled by listening to the event bus + self.hass.bus.async_fire( + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, + ) @callback def async_update_state(self, new_state): @@ -424,18 +354,4 @@ def async_update_state(self, new_state): if self.char_mute.value != current_mute_state: self.char_mute.set_value(current_mute_state) - # Set active input - if self.support_select_source and self.sources: - source_name = new_state.attributes.get(ATTR_INPUT_SOURCE) - _LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name) - if source_name in self.sources: - index = self.sources.index(source_name) - if self.char_input_source.value != index: - self.char_input_source.set_value(index) - elif hk_state: - _LOGGER.warning( - "%s: Sources out of sync. Restart Home Assistant", - self.entity_id, - ) - if self.char_input_source.value != 0: - self.char_input_source.set_value(0) + self._async_update_input_state(hk_state, new_state) diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py new file mode 100644 index 00000000000000..e4f18a7c16f42b --- /dev/null +++ b/homeassistant/components/homekit/type_remotes.py @@ -0,0 +1,214 @@ +"""Class to hold remote accessories.""" +from abc import abstractmethod +import logging + +from pyhap.const import CATEGORY_TELEVISION + +from homeassistant.components.remote import ( + ATTR_ACTIVITY, + ATTR_ACTIVITY_LIST, + ATTR_CURRENT_ACTIVITY, + DOMAIN as REMOTE_DOMAIN, + SUPPORT_ACTIVITY, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_ON, +) +from homeassistant.core import callback + +from .accessories import TYPES, HomeAccessory +from .const import ( + ATTR_KEY_NAME, + CHAR_ACTIVE, + CHAR_ACTIVE_IDENTIFIER, + CHAR_CONFIGURED_NAME, + CHAR_CURRENT_VISIBILITY_STATE, + CHAR_IDENTIFIER, + CHAR_INPUT_SOURCE_TYPE, + CHAR_IS_CONFIGURED, + CHAR_NAME, + CHAR_REMOTE_KEY, + CHAR_SLEEP_DISCOVER_MODE, + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + KEY_ARROW_DOWN, + KEY_ARROW_LEFT, + KEY_ARROW_RIGHT, + KEY_ARROW_UP, + KEY_BACK, + KEY_EXIT, + KEY_FAST_FORWARD, + KEY_INFORMATION, + KEY_NEXT_TRACK, + KEY_PLAY_PAUSE, + KEY_PREVIOUS_TRACK, + KEY_REWIND, + KEY_SELECT, + SERV_INPUT_SOURCE, + SERV_TELEVISION, +) + +_LOGGER = logging.getLogger(__name__) + +REMOTE_KEYS = { + 0: KEY_REWIND, + 1: KEY_FAST_FORWARD, + 2: KEY_NEXT_TRACK, + 3: KEY_PREVIOUS_TRACK, + 4: KEY_ARROW_UP, + 5: KEY_ARROW_DOWN, + 6: KEY_ARROW_LEFT, + 7: KEY_ARROW_RIGHT, + 8: KEY_SELECT, + 9: KEY_BACK, + 10: KEY_EXIT, + 11: KEY_PLAY_PAUSE, + 15: KEY_INFORMATION, +} + + +class RemoteInputSelectAccessory(HomeAccessory): + """Generate a InputSelect accessory.""" + + def __init__( + self, + required_feature, + source_key, + source_list_key, + *args, + **kwargs, + ): + """Initialize a InputSelect accessory object.""" + super().__init__(*args, category=CATEGORY_TELEVISION, **kwargs) + state = self.hass.states.get(self.entity_id) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + + self.source_key = source_key + self.sources = [] + self.support_select_source = False + if features & required_feature: + self.sources = state.attributes.get(source_list_key, []) + if self.sources: + self.support_select_source = True + + self.chars_tv = [CHAR_REMOTE_KEY] + serv_tv = self.serv_tv = self.add_preload_service( + SERV_TELEVISION, self.chars_tv + ) + self.char_remote_key = self.serv_tv.configure_char( + CHAR_REMOTE_KEY, setter_callback=self.set_remote_key + ) + self.set_primary_service(serv_tv) + serv_tv.configure_char(CHAR_CONFIGURED_NAME, value=self.display_name) + serv_tv.configure_char(CHAR_SLEEP_DISCOVER_MODE, value=True) + self.char_active = serv_tv.configure_char( + CHAR_ACTIVE, setter_callback=self.set_on_off + ) + + if not self.support_select_source: + return + + self.char_input_source = serv_tv.configure_char( + CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source + ) + for index, source in enumerate(self.sources): + serv_input = self.add_preload_service( + SERV_INPUT_SOURCE, [CHAR_IDENTIFIER, CHAR_NAME] + ) + serv_tv.add_linked_service(serv_input) + serv_input.configure_char(CHAR_CONFIGURED_NAME, value=source) + serv_input.configure_char(CHAR_NAME, value=source) + serv_input.configure_char(CHAR_IDENTIFIER, value=index) + serv_input.configure_char(CHAR_IS_CONFIGURED, value=True) + input_type = 3 if "hdmi" in source.lower() else 0 + serv_input.configure_char(CHAR_INPUT_SOURCE_TYPE, value=input_type) + serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) + _LOGGER.debug("%s: Added source %s", self.entity_id, source) + + @abstractmethod + def set_on_off(self, value): + """Move switch state to value if call came from HomeKit.""" + + @abstractmethod + def set_input_source(self, value): + """Send input set value if call came from HomeKit.""" + + @abstractmethod + def set_remote_key(self, value): + """Send remote key value if call came from HomeKit.""" + + @callback + def _async_update_input_state(self, hk_state, new_state): + """Update input state after state changed.""" + # Set active input + if not self.support_select_source or not self.sources: + return + source_name = new_state.attributes.get(self.source_key) + _LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name) + if source_name in self.sources: + index = self.sources.index(source_name) + if self.char_input_source.value != index: + self.char_input_source.set_value(index) + elif hk_state: + _LOGGER.warning( + "%s: Sources out of sync. Restart Home Assistant", + self.entity_id, + ) + if self.char_input_source.value != 0: + self.char_input_source.set_value(0) + + +@TYPES.register("ActivityRemote") +class ActivityRemote(RemoteInputSelectAccessory): + """Generate a Activity Remote accessory.""" + + def __init__(self, *args): + """Initialize a Activity Remote accessory object.""" + super().__init__( + SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY, + ATTR_ACTIVITY_LIST, + *args, + ) + self.async_update_state(self.hass.states.get(self.entity_id)) + + def set_on_off(self, value): + """Move switch state to value if call came from HomeKit.""" + _LOGGER.debug('%s: Set switch state for "on_off" to %s', self.entity_id, value) + service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF + params = {ATTR_ENTITY_ID: self.entity_id} + self.async_call_service(REMOTE_DOMAIN, service, params) + + def set_input_source(self, value): + """Send input set value if call came from HomeKit.""" + _LOGGER.debug("%s: Set current input to %s", self.entity_id, value) + source = self.sources[value] + params = {ATTR_ENTITY_ID: self.entity_id, ATTR_ACTIVITY: source} + self.async_call_service(REMOTE_DOMAIN, SERVICE_TURN_ON, params) + + def set_remote_key(self, value): + """Send remote key value if call came from HomeKit.""" + _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) + key_name = REMOTE_KEYS.get(value) + if key_name is None: + _LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value) + return + self.hass.bus.async_fire( + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, + ) + + @callback + def async_update_state(self, new_state): + """Update Television remote state after state changed.""" + current_state = new_state.state + # Power state remote + hk_state = 1 if current_state == STATE_ON else 0 + _LOGGER.debug("%s: Set current active state to %s", self.entity_id, hk_state) + if self.char_active.value != hk_state: + self.char_active.set_value(hk_state) + + self._async_update_input_state(hk_state, new_state) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index cab1a28892a356..d0a83b1a12a93f 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -16,6 +16,7 @@ DEVICE_CLASS_TV, DOMAIN as MEDIA_PLAYER_DOMAIN, ) +from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN, SUPPORT_ACTIVITY from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, @@ -503,4 +504,6 @@ def state_needs_accessory_mode(state): return ( state.domain == MEDIA_PLAYER_DOMAIN and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV + or state.domain == REMOTE_DOMAIN + and state.attributes.get(ATTR_SUPPORTED_FEATURES) & SUPPORT_ACTIVITY ) diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index ed780ed067b1d3..5f885c59a1d2a2 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -107,6 +107,7 @@ def visit_Attribute(self, node): "onboarding", "persistent_notification", "person", + "remote", "script", "shopping_list", "sun", diff --git a/tests/components/homekit/test_type_remote.py b/tests/components/homekit/test_type_remote.py new file mode 100644 index 00000000000000..e69ebfb29fbcbe --- /dev/null +++ b/tests/components/homekit/test_type_remote.py @@ -0,0 +1,148 @@ +"""Test different accessory types: Remotes.""" + +from homeassistant.components.homekit.const import ( + ATTR_KEY_NAME, + ATTR_VALUE, + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + KEY_ARROW_RIGHT, +) +from homeassistant.components.homekit.type_remotes import ActivityRemote +from homeassistant.components.remote import ( + ATTR_ACTIVITY, + ATTR_ACTIVITY_LIST, + ATTR_CURRENT_ACTIVITY, + DOMAIN, + SUPPORT_ACTIVITY, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + STATE_OFF, + STATE_ON, + STATE_STANDBY, +) + +from tests.common import async_mock_service + + +async def test_activity_remote(hass, hk_driver, events, caplog): + """Test if remote accessory and HA are updated accordingly.""" + entity_id = "remote.harmony" + hass.states.async_set( + entity_id, + None, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Apple TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + acc = ActivityRemote(hass, hk_driver, "ActivityRemote", entity_id, 2, None) + await acc.run() + await hass.async_block_till_done() + + assert acc.aid == 2 + assert acc.category == 31 # Television + + assert acc.char_active.value == 0 + assert acc.char_remote_key.value == 0 + assert acc.char_input_source.value == 1 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Apple TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + assert acc.char_active.value == 1 + + hass.states.async_set(entity_id, STATE_OFF) + await hass.async_block_till_done() + assert acc.char_active.value == 0 + + hass.states.async_set(entity_id, STATE_ON) + await hass.async_block_till_done() + assert acc.char_active.value == 1 + + hass.states.async_set(entity_id, STATE_STANDBY) + await hass.async_block_till_done() + assert acc.char_active.value == 0 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + assert acc.char_input_source.value == 0 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_ACTIVITY, + ATTR_CURRENT_ACTIVITY: "Apple TV", + ATTR_ACTIVITY_LIST: ["TV", "Apple TV"], + }, + ) + await hass.async_block_till_done() + assert acc.char_input_source.value == 1 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + call_turn_off = async_mock_service(hass, DOMAIN, "turn_off") + + acc.char_active.client_update_value(1) + await hass.async_block_till_done() + assert call_turn_on + assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] is None + + acc.char_active.client_update_value(0) + await hass.async_block_till_done() + assert call_turn_off + assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] is None + + acc.char_input_source.client_update_value(1) + await hass.async_block_till_done() + assert call_turn_on + assert call_turn_on[1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[1].data[ATTR_ACTIVITY] == "Apple TV" + assert len(events) == 3 + assert events[-1].data[ATTR_VALUE] is None + + acc.char_input_source.client_update_value(0) + await hass.async_block_till_done() + assert call_turn_on + assert call_turn_on[2].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[2].data[ATTR_ACTIVITY] == "TV" + assert len(events) == 4 + assert events[-1].data[ATTR_VALUE] is None + + events = [] + + def listener(event): + events.append(event) + + hass.bus.async_listen(EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, listener) + + acc.char_remote_key.client_update_value(20) + await hass.async_block_till_done() + + acc.char_remote_key.client_update_value(7) + await hass.async_block_till_done() + + assert len(events) == 1 + assert events[0].data[ATTR_KEY_NAME] == KEY_ARROW_RIGHT From f35641ae8e05e2a4797fbe62eb7ed68919bbbe0f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 18:57:49 -1000 Subject: [PATCH 1422/1818] Make sure include_ignore=False always works with _async_current_entries (#48196) If the step was anything other than SOURCE_USER, include_ignore=False would not be honored --- homeassistant/config_entries.py | 10 ++- tests/test_config_entries.py | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b5491d838007a5..6bdb850e64349b 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1021,14 +1021,20 @@ def _set_confirm_only( self.context["confirm_only"] = True @callback - def _async_current_entries(self, include_ignore: bool = False) -> list[ConfigEntry]: + def _async_current_entries( + self, include_ignore: bool | None = None + ) -> list[ConfigEntry]: """Return current entries. If the flow is user initiated, filter out ignored entries unless include_ignore is True. """ config_entries = self.hass.config_entries.async_entries(self.handler) - if include_ignore or self.source != SOURCE_USER: + if ( + include_ignore is True + or include_ignore is None + and self.source != SOURCE_USER + ): return config_entries return [entry for entry in config_entries if entry.source != SOURCE_IGNORE] diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 09ab60e4300442..5d774f7877d98e 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1760,6 +1760,115 @@ async def async_step_user(self, user_input=None): assert p_entry.data == {"token": "supersecret"} +async def test__async_current_entries_does_not_skip_ignore_non_user(hass, manager): + """Test that _async_current_entries does not skip ignore by default for non user step.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_import(self, user_input=None): + """Test not the user step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 0 + + +async def test__async_current_entries_explict_skip_ignore(hass, manager): + """Test that _async_current_entries can explicitly include ignore.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_import(self, user_input=None): + """Test not the user step.""" + if self._async_current_entries(include_ignore=False): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 1 + p_hass, p_entry = mock_setup_entry.mock_calls[0][1] + + assert p_hass is hass + assert p_entry.data == {"token": "supersecret"} + + +async def test__async_current_entries_explict_include_ignore(hass, manager): + """Test that _async_current_entries can explicitly include ignore.""" + hass.config.components.add("comp") + entry = MockConfigEntry( + domain="comp", + state=config_entries.ENTRY_STATE_LOADED, + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + mock_setup_entry = AsyncMock(return_value=True) + + mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.comp", None) + + class TestFlow(config_entries.ConfigFlow): + """Test flow.""" + + VERSION = 1 + + async def async_step_import(self, user_input=None): + """Test not the user step.""" + if self._async_current_entries(include_ignore=True): + return self.async_abort(reason="single_instance_allowed") + return self.async_create_entry(title="title", data={"token": "supersecret"}) + + with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}): + await manager.flow.async_init( + "comp", context={"source": config_entries.SOURCE_IMPORT} + ) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 0 + + async def test_unignore_step_form(hass, manager): """Test that we can ignore flows that are in progress and have a unique ID, then rediscover them.""" async_setup_entry = AsyncMock(return_value=True) From 8557b856a4072c092e45dcfa629d70daf6910881 Mon Sep 17 00:00:00 2001 From: Emily Mills Date: Mon, 22 Mar 2021 00:08:09 -0500 Subject: [PATCH 1423/1818] Fix Kulersky and Zerproc config unloading (#47572) --- homeassistant/components/kulersky/__init__.py | 20 +++++++++++++------ homeassistant/components/kulersky/const.py | 3 +++ homeassistant/components/kulersky/light.py | 20 ++++++------------- homeassistant/components/zerproc/__init__.py | 12 ++++++++--- homeassistant/components/zerproc/const.py | 3 +++ homeassistant/components/zerproc/light.py | 12 +++++++---- tests/components/kulersky/test_light.py | 10 +++++++++- tests/components/zerproc/test_light.py | 9 +++++++-- 8 files changed, 59 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/kulersky/__init__.py b/homeassistant/components/kulersky/__init__.py index 666239ad22b27c..951e2a5353f3f3 100644 --- a/homeassistant/components/kulersky/__init__.py +++ b/homeassistant/components/kulersky/__init__.py @@ -4,7 +4,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN PLATFORMS = ["light"] @@ -16,6 +16,11 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Kuler Sky from a config entry.""" + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + if DATA_ADDRESSES not in hass.data[DOMAIN]: + hass.data[DOMAIN][DATA_ADDRESSES] = set() + for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform) @@ -26,7 +31,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - unload_ok = all( + # Stop discovery + unregister_discovery = hass.data[DOMAIN].pop(DATA_DISCOVERY_SUBSCRIPTION, None) + if unregister_discovery: + unregister_discovery() + + hass.data.pop(DOMAIN, None) + + return all( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, platform) @@ -34,7 +46,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ] ) ) - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok diff --git a/homeassistant/components/kulersky/const.py b/homeassistant/components/kulersky/const.py index ae1e7a435dca99..8b314d7bde94d3 100644 --- a/homeassistant/components/kulersky/const.py +++ b/homeassistant/components/kulersky/const.py @@ -1,2 +1,5 @@ """Constants for the Kuler Sky integration.""" DOMAIN = "kulersky" + +DATA_ADDRESSES = "addresses" +DATA_DISCOVERY_SUBSCRIPTION = "discovery_subscription" diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 599f99a83e84d9..980d4612ce9aa2 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -18,13 +18,12 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -39,10 +38,6 @@ async def async_setup_entry( async_add_entities: Callable[[list[Entity], bool], None], ) -> None: """Set up Kuler sky light devices.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - if "addresses" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["addresses"] = set() async def discover(*args): """Attempt to discover new lights.""" @@ -52,12 +47,12 @@ async def discover(*args): new_lights = [ light for light in lights - if light.address not in hass.data[DOMAIN]["addresses"] + if light.address not in hass.data[DOMAIN][DATA_ADDRESSES] ] new_entities = [] for light in new_lights: - hass.data[DOMAIN]["addresses"].add(light.address) + hass.data[DOMAIN][DATA_ADDRESSES].add(light.address) new_entities.append(KulerskyLight(light)) async_add_entities(new_entities, update_before_add=True) @@ -66,12 +61,9 @@ async def discover(*args): hass.async_create_task(discover()) # Perform recurring discovery of new devices - async_track_time_interval(hass, discover, DISCOVERY_INTERVAL) - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): - """Cleanup the Kuler sky integration.""" - hass.data.pop(DOMAIN, None) + hass.data[DOMAIN][DATA_DISCOVERY_SUBSCRIPTION] = async_track_time_interval( + hass, discover, DISCOVERY_INTERVAL + ) class KulerskyLight(LightEntity): diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index d6faaf7a981f63..12953afeb2dbf7 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -4,7 +4,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN PLATFORMS = ["light"] @@ -22,8 +22,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Zerproc from a config entry.""" if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - if "addresses" not in hass.data[DOMAIN]: - hass.data[DOMAIN]["addresses"] = set() + if DATA_ADDRESSES not in hass.data[DOMAIN]: + hass.data[DOMAIN][DATA_ADDRESSES] = set() for platform in PLATFORMS: hass.async_create_task( @@ -35,7 +35,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + # Stop discovery + unregister_discovery = hass.data[DOMAIN].pop(DATA_DISCOVERY_SUBSCRIPTION, None) + if unregister_discovery: + unregister_discovery() + hass.data.pop(DOMAIN, None) + return all( await asyncio.gather( *[ diff --git a/homeassistant/components/zerproc/const.py b/homeassistant/components/zerproc/const.py index a5481bd4c34032..69d5fcfb74081a 100644 --- a/homeassistant/components/zerproc/const.py +++ b/homeassistant/components/zerproc/const.py @@ -1,2 +1,5 @@ """Constants for the Zerproc integration.""" DOMAIN = "zerproc" + +DATA_ADDRESSES = "addresses" +DATA_DISCOVERY_SUBSCRIPTION = "discovery_subscription" diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index 16fe5607925301..627358ab971889 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -22,7 +22,7 @@ from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util -from .const import DOMAIN +from .const import DATA_ADDRESSES, DATA_DISCOVERY_SUBSCRIPTION, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -37,12 +37,14 @@ async def discover_entities(hass: HomeAssistant) -> list[Entity]: # Filter out already discovered lights new_lights = [ - light for light in lights if light.address not in hass.data[DOMAIN]["addresses"] + light + for light in lights + if light.address not in hass.data[DOMAIN][DATA_ADDRESSES] ] entities = [] for light in new_lights: - hass.data[DOMAIN]["addresses"].add(light.address) + hass.data[DOMAIN][DATA_ADDRESSES].add(light.address) entities.append(ZerprocLight(light)) return entities @@ -72,7 +74,9 @@ async def discover(*args): hass.async_create_task(discover()) # Perform recurring discovery of new devices - async_track_time_interval(hass, discover, DISCOVERY_INTERVAL) + hass.data[DOMAIN][DATA_DISCOVERY_SUBSCRIPTION] = async_track_time_interval( + hass, discover, DISCOVERY_INTERVAL + ) class ZerprocLight(LightEntity): diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index c08dd10d5970ad..ea5eeb5a690567 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -5,7 +5,11 @@ import pytest from homeassistant import setup -from homeassistant.components.kulersky.light import DOMAIN +from homeassistant.components.kulersky.const import ( + DATA_ADDRESSES, + DATA_DISCOVERY_SUBSCRIPTION, + DOMAIN, +) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_MODE, @@ -85,9 +89,13 @@ async def test_init(hass, mock_light): async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" + assert hass.data[DOMAIN][DATA_ADDRESSES] == {"AA:BB:CC:11:22:33"} + assert DATA_DISCOVERY_SUBSCRIPTION in hass.data[DOMAIN] + await hass.config_entries.async_remove(mock_entry.entry_id) assert mock_light.disconnect.called + assert DOMAIN not in hass.data async def test_remove_entry_exceptions_caught(hass, mock_light, mock_entry): diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 9fac50859868e3..0fec6be645184b 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -17,7 +17,11 @@ SUPPORT_BRIGHTNESS, SUPPORT_COLOR, ) -from homeassistant.components.zerproc.light import DOMAIN +from homeassistant.components.zerproc.const import ( + DATA_ADDRESSES, + DATA_DISCOVERY_SUBSCRIPTION, + DOMAIN, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, @@ -146,7 +150,8 @@ async def test_discovery_exception(hass, mock_entry): async def test_remove_entry(hass, mock_light, mock_entry): """Test platform setup.""" - assert hass.data[DOMAIN]["addresses"] == {"AA:BB:CC:DD:EE:FF"} + assert hass.data[DOMAIN][DATA_ADDRESSES] == {"AA:BB:CC:DD:EE:FF"} + assert DATA_DISCOVERY_SUBSCRIPTION in hass.data[DOMAIN] with patch.object(mock_light, "disconnect") as mock_disconnect: await hass.config_entries.async_remove(mock_entry.entry_id) From 73e546e2b838533b25154cb6dbc90550378a2e88 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 06:09:59 +0100 Subject: [PATCH 1424/1818] Improve condition trace tests (#48152) --- tests/helpers/test_condition.py | 112 +++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 24 deletions(-) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index c1bf727bc0f208..7c32f2c119259e 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -13,10 +13,18 @@ def assert_element(trace_element, expected_element, path): """Assert a trace element is as expected. - Note: Unused variable path is passed to get helpful errors from pytest. + Note: Unused variable 'path' is passed to get helpful errors from pytest. """ - for result_key, result in expected_element.get("result", {}).items(): + expected_result = expected_element.get("result", {}) + # Check that every item in expected_element is present and equal in trace_element + # The redundant set operation gives helpful errors from pytest + assert not set(expected_result) - set(trace_element._result or {}) + for result_key, result in expected_result.items(): assert trace_element._result[result_key] == result + + # Check for unexpected items in trace_element + assert not set(trace_element._result or {}) - set(expected_result) + if "error_type" in expected_element: assert isinstance(trace_element._error, expected_element["error_type"]) else: @@ -94,7 +102,9 @@ async def test_and_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "120", "wanted_state": "100"}} + ], } ) @@ -104,7 +114,9 @@ async def test_and_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "105", "wanted_state": "100"}} + ], } ) @@ -114,9 +126,11 @@ async def test_and_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": True}}], - "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [ + {"result": {"result": True, "state": "100", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 100.0}}], } ) @@ -167,7 +181,7 @@ async def test_and_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 120.0}}], } ) @@ -181,7 +195,15 @@ async def test_and_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 90.0, + "wanted_state_above": 110.0, + } + } + ], } ) @@ -257,9 +279,19 @@ async def test_or_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "120", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 120.0, + "wanted_state_below": 110.0, + } + } + ], } ) @@ -269,9 +301,11 @@ async def test_or_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "105", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 105.0}}], } ) @@ -281,7 +315,9 @@ async def test_or_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": True}}], - "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [ + {"result": {"result": True, "state": "100", "wanted_state": "100"}} + ], } ) @@ -332,7 +368,15 @@ async def test_or_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 100.0, + "wanted_state_above": 110.0, + } + } + ], } ) @@ -346,7 +390,7 @@ async def test_or_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 120.0}}], } ) @@ -418,9 +462,19 @@ async def test_not_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "101", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + { + "result": { + "result": False, + "state": 101.0, + "wanted_state_below": 50.0, + } + } + ], } ) @@ -430,9 +484,13 @@ async def test_not_condition(hass): { "": [{"result": {"result": True}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "50", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + {"result": {"result": False, "state": 50.0, "wanted_state_below": 50.0}} + ], } ) @@ -442,9 +500,11 @@ async def test_not_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": False}}], - "conditions/0/entity_id/0": [{"result": {"result": False}}], + "conditions/0/entity_id/0": [ + {"result": {"result": False, "state": "49", "wanted_state": "100"}} + ], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 49.0}}], } ) @@ -454,7 +514,9 @@ async def test_not_condition(hass): { "": [{"result": {"result": False}}], "conditions/0": [{"result": {"result": True}}], - "conditions/0/entity_id/0": [{"result": {"result": True}}], + "conditions/0/entity_id/0": [ + {"result": {"result": True, "state": "100", "wanted_state": "100"}} + ], } ) @@ -505,7 +567,9 @@ async def test_not_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": False}}], - "conditions/1/entity_id/0": [{"result": {"result": False}}], + "conditions/1/entity_id/0": [ + {"result": {"result": False, "state": 90.0, "wanted_state_below": 50.0}} + ], } ) @@ -519,7 +583,7 @@ async def test_not_condition_raises(hass): "conditions/0": [{"error_type": ConditionError}], "conditions/0/entity_id/0": [{"error_type": ConditionError}], "conditions/1": [{"result": {"result": True}}], - "conditions/1/entity_id/0": [{"result": {"result": True}}], + "conditions/1/entity_id/0": [{"result": {"result": True, "state": 40.0}}], } ) From 40ce25800ca4649ebb3047f57bd91e4f1b991213 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 06:12:56 +0100 Subject: [PATCH 1425/1818] Test that homeassistant stop and restart do not block WS (#48081) --- .../components/websocket_api/test_commands.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index a83f9509d01b5e..b9e1e149dd1914 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1,5 +1,8 @@ """Tests for WebSocket API commands.""" +from unittest.mock import ANY, patch + from async_timeout import timeout +import pytest import voluptuous as vol from homeassistant.components.websocket_api import const @@ -47,6 +50,83 @@ async def test_call_service(hass, websocket_client): assert call.context.as_dict() == msg["result"]["context"] +@pytest.mark.parametrize("command", ("call_service", "call_service_action")) +async def test_call_service_blocking(hass, websocket_client, command): + """Test call service commands block, except for homeassistant restart / stop.""" + with patch( + "homeassistant.core.ServiceRegistry.async_call", autospec=True + ) as mock_call: + await websocket_client.send_json( + { + "id": 5, + "type": "call_service", + "domain": "domain_test", + "service": "test_service", + "service_data": {"hello": "world"}, + }, + ) + msg = await websocket_client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + mock_call.assert_called_once_with( + ANY, + "domain_test", + "test_service", + {"hello": "world"}, + blocking=True, + context=ANY, + target=ANY, + ) + + with patch( + "homeassistant.core.ServiceRegistry.async_call", autospec=True + ) as mock_call: + await websocket_client.send_json( + { + "id": 6, + "type": "call_service", + "domain": "homeassistant", + "service": "test_service", + }, + ) + msg = await websocket_client.receive_json() + + assert msg["id"] == 6 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + mock_call.assert_called_once_with( + ANY, + "homeassistant", + "test_service", + ANY, + blocking=True, + context=ANY, + target=ANY, + ) + + with patch( + "homeassistant.core.ServiceRegistry.async_call", autospec=True + ) as mock_call: + await websocket_client.send_json( + { + "id": 7, + "type": "call_service", + "domain": "homeassistant", + "service": "restart", + }, + ) + msg = await websocket_client.receive_json() + + assert msg["id"] == 7 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + mock_call.assert_called_once_with( + ANY, "homeassistant", "restart", ANY, blocking=False, context=ANY, target=ANY + ) + + async def test_call_service_target(hass, websocket_client): """Test call service command with target.""" calls = async_mock_service(hass, "domain_test", "test_service") From e5893ca42caa6ee101d5ca15c9529776c233c8ec Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 22 Mar 2021 00:14:09 -0500 Subject: [PATCH 1426/1818] Trigger Plex GDM scans regularly (#48041) --- homeassistant/components/plex/__init__.py | 14 +++++++------- homeassistant/components/plex/server.py | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 6b403150e9c35a..d84a86984e296d 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -61,12 +61,16 @@ async def async_setup(hass, config): gdm = hass.data[PLEX_DOMAIN][GDM_SCANNER] = GDM() + def gdm_scan(): + _LOGGER.debug("Scanning for GDM clients") + gdm.scan(scan_for_clients=True) + hass.data[PLEX_DOMAIN][GDM_DEBOUNCER] = Debouncer( hass, _LOGGER, cooldown=10, immediate=True, - function=partial(gdm.scan, scan_for_clients=True), + function=gdm_scan, ).async_call return True @@ -145,14 +149,10 @@ async def async_setup_entry(hass, entry): entry.add_update_listener(async_options_updated) - async def async_update_plex(): - await hass.data[PLEX_DOMAIN][GDM_DEBOUNCER]() - await plex_server.async_update_platforms() - unsub = async_dispatcher_connect( hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id), - async_update_plex, + plex_server.async_update_platforms, ) hass.data[PLEX_DOMAIN][DISPATCHERS].setdefault(server_id, []) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) @@ -164,7 +164,7 @@ def plex_websocket_callback(signal, data, error): if data == STATE_CONNECTED: _LOGGER.debug("Websocket to %s successful", entry.data[CONF_SERVER]) - hass.async_create_task(async_update_plex()) + hass.async_create_task(plex_server.async_update_platforms()) elif data == STATE_DISCONNECTED: _LOGGER.debug( "Websocket to %s disconnected, retrying", entry.data[CONF_SERVER] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 6e1a83297b3017..27954cdbd9f57e 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -34,6 +34,7 @@ DEBOUNCE_TIMEOUT, DEFAULT_VERIFY_SSL, DOMAIN, + GDM_DEBOUNCER, GDM_SCANNER, PLAYER_SOURCE, PLEX_NEW_MP_SIGNAL, @@ -323,6 +324,8 @@ async def _async_update_platforms(self): """Update the platform entities.""" _LOGGER.debug("Updating devices") + await self.hass.data[DOMAIN][GDM_DEBOUNCER]() + available_clients = {} ignored_clients = set() new_clients = set() From 7a447c42091582b56cfdb603c25dfe0cafffc73d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 19:18:24 -1000 Subject: [PATCH 1427/1818] Exclude homekit accessories created by the homekit integration from homekit_controller (#48201) --- .../homekit_controller/config_flow.py | 18 ++++++---- .../homekit_controller/test_config_flow.py | 35 ++++++++++++++----- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 38a41617c6a0bc..fcf83918fda071 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -18,8 +18,6 @@ HOMEKIT_DIR = ".homekit" HOMEKIT_BRIDGE_DOMAIN = "homekit" -HOMEKIT_BRIDGE_SERIAL_NUMBER = "homekit.bridge" -HOMEKIT_BRIDGE_MODEL = "Home Assistant HomeKit Bridge" HOMEKIT_IGNORE = [ # eufy Indoor Cam 2K and 2K Pan & Tilt @@ -181,8 +179,8 @@ async def async_step_unignore(self, user_input): return self.async_abort(reason="no_devices") - async def _hkid_is_homekit_bridge(self, hkid): - """Determine if the device is a homekit bridge.""" + async def _hkid_is_homekit(self, hkid): + """Determine if the device is a homekit bridge or accessory.""" dev_reg = await async_get_device_registry(self.hass) device = dev_reg.async_get_device( identifiers=set(), connections={(CONNECTION_NETWORK_MAC, hkid)} @@ -190,7 +188,13 @@ async def _hkid_is_homekit_bridge(self, hkid): if device is None: return False - return device.model == HOMEKIT_BRIDGE_MODEL + + for entry_id in device.config_entries: + entry = self.hass.config_entries.async_get_entry(entry_id) + if entry and entry.domain == HOMEKIT_BRIDGE_DOMAIN: + return True + + return False async def async_step_zeroconf(self, discovery_info): """Handle a discovered HomeKit accessory. @@ -266,8 +270,8 @@ async def async_step_zeroconf(self, discovery_info): if model in HOMEKIT_IGNORE: return self.async_abort(reason="ignored_model") - # If this is a HomeKit bridge exported by *this* HA instance ignore it. - if await self._hkid_is_homekit_bridge(hkid): + # If this is a HomeKit bridge/accessory exported by *this* HA instance ignore it. + if await self._hkid_is_homekit(hkid): return self.async_abort(reason="ignored_model") self.name = name diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 9cc785f85fb230..42903a530629c5 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -269,25 +269,18 @@ async def test_discovery_ignored_model(hass, controller): async def test_discovery_ignored_hk_bridge(hass, controller): - """Already paired.""" + """Ensure we ignore homekit bridges and accessories created by the homekit integration.""" device = setup_mock_accessory(controller) discovery_info = get_device_discovery_info(device) config_entry = MockConfigEntry(domain=config_flow.HOMEKIT_BRIDGE_DOMAIN, data={}) + config_entry.add_to_hass(hass) formatted_mac = device_registry.format_mac("AA:BB:CC:DD:EE:FF") dev_reg = mock_device_registry(hass) dev_reg.async_get_or_create( config_entry_id=config_entry.entry_id, - identifiers={ - ( - config_flow.HOMEKIT_BRIDGE_DOMAIN, - config_entry.entry_id, - config_flow.HOMEKIT_BRIDGE_SERIAL_NUMBER, - ) - }, connections={(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}, - model=config_flow.HOMEKIT_BRIDGE_MODEL, ) discovery_info["properties"]["id"] = "AA:BB:CC:DD:EE:FF" @@ -300,6 +293,30 @@ async def test_discovery_ignored_hk_bridge(hass, controller): assert result["reason"] == "ignored_model" +async def test_discovery_does_not_ignore_non_homekit(hass, controller): + """Do not ignore devices that are not from the homekit integration.""" + device = setup_mock_accessory(controller) + discovery_info = get_device_discovery_info(device) + + config_entry = MockConfigEntry(domain="not_homekit", data={}) + config_entry.add_to_hass(hass) + formatted_mac = device_registry.format_mac("AA:BB:CC:DD:EE:FF") + + dev_reg = mock_device_registry(hass) + dev_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)}, + ) + + discovery_info["properties"]["id"] = "AA:BB:CC:DD:EE:FF" + + # Device is discovered + result = await hass.config_entries.flow.async_init( + "homekit_controller", context={"source": "zeroconf"}, data=discovery_info + ) + assert result["type"] == "form" + + async def test_discovery_invalid_config_entry(hass, controller): """There is already a config entry for the pairing id but it's invalid.""" MockConfigEntry( From 8e4c0e3ff746f685367c9f2ffa99abc434e303e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 19:29:48 -1000 Subject: [PATCH 1428/1818] Increase config entries test coverage (#48203) --- tests/test_config_entries.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 5d774f7877d98e..fbcc9d1bf149b7 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1394,6 +1394,12 @@ async def test_entry_id_existing_entry(hass, manager): unique_id="mock-unique-id", ).add_to_hass(hass) + mock_integration( + hass, + MockModule("comp"), + ) + mock_entity_platform(hass, "config_flow.comp", None) + class TestFlow(config_entries.ConfigFlow): """Test flow.""" From a2c4b438ea8ff795cfa7854d919d3981ac60de89 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 19:35:12 -1000 Subject: [PATCH 1429/1818] Convert august to be push instead of poll (#47544) --- homeassistant/components/august/__init__.py | 149 ++++++++++++------ homeassistant/components/august/activity.py | 147 +++++++++++------ .../components/august/binary_sensor.py | 99 +++++++----- homeassistant/components/august/camera.py | 6 +- .../components/august/config_flow.py | 2 +- homeassistant/components/august/gateway.py | 4 +- homeassistant/components/august/lock.py | 16 +- homeassistant/components/august/manifest.json | 2 +- homeassistant/components/august/sensor.py | 4 +- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/august/mocks.py | 60 ++++--- tests/components/august/test_binary_sensor.py | 125 +++++++++++++++ tests/components/august/test_config_flow.py | 2 +- tests/components/august/test_gateway.py | 2 +- tests/components/august/test_init.py | 32 +++- tests/components/august/test_lock.py | 121 ++++++++++++++ .../august/get_activity.bridge_offline.json | 34 ++++ .../august/get_activity.bridge_online.json | 34 ++++ tests/fixtures/august/get_doorbell.json | 2 +- .../get_lock.online_with_doorsense.json | 3 +- 21 files changed, 678 insertions(+), 178 deletions(-) create mode 100644 tests/fixtures/august/get_activity.bridge_offline.json create mode 100644 tests/fixtures/august/get_activity.bridge_online.json diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 73f1cc6a1b509b..70c88d02901093 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -1,14 +1,16 @@ """Support for August devices.""" import asyncio -import itertools +from itertools import chain import logging from aiohttp import ClientError, ClientResponseError -from august.exceptions import AugustApiAIOHTTPError +from yalexs.exceptions import AugustApiAIOHTTPError +from yalexs.pubnub_activity import activities_from_pubnub_message +from yalexs.pubnub_async import AugustPubNub, async_create_pubnub from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, HTTP_UNAUTHORIZED -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from .activity import ActivityStream @@ -19,6 +21,13 @@ _LOGGER = logging.getLogger(__name__) +API_CACHED_ATTRS = ( + "door_state", + "door_state_datetime", + "lock_status", + "lock_status_datetime", +) + async def async_setup(hass: HomeAssistant, config: dict): """Set up the August component from YAML.""" @@ -60,6 +69,9 @@ def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" + + hass.data[DOMAIN][entry.entry_id][DATA_AUGUST].async_stop() + unload_ok = all( await asyncio.gather( *[ @@ -114,25 +126,27 @@ def __init__(self, hass, august_gateway): self._doorbells_by_id = {} self._locks_by_id = {} self._house_ids = set() + self._pubnub_unsub = None async def async_setup(self): """Async setup of august device data and activities.""" - locks = ( - await self._api.async_get_operable_locks(self._august_gateway.access_token) - or [] - ) - doorbells = ( - await self._api.async_get_doorbells(self._august_gateway.access_token) or [] + 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), ) + if not doorbells: + doorbells = [] + if not locks: + locks = [] self._doorbells_by_id = {device.device_id: device for device in doorbells} self._locks_by_id = {device.device_id: device for device in locks} - self._house_ids = { - device.house_id for device in itertools.chain(locks, doorbells) - } + self._house_ids = {device.house_id for device in chain(locks, doorbells)} await self._async_refresh_device_detail_by_ids( - [device.device_id for device in itertools.chain(locks, doorbells)] + [device.device_id for device in chain(locks, doorbells)] ) # We remove all devices that we are missing @@ -142,10 +156,32 @@ async def async_setup(self): self._remove_inoperative_locks() self._remove_inoperative_doorbells() + pubnub = AugustPubNub() + for device in self._device_detail_by_id.values(): + pubnub.register_device(device) + self.activity_stream = ActivityStream( - self._hass, self._api, self._august_gateway, self._house_ids + self._hass, self._api, self._august_gateway, self._house_ids, pubnub ) await self.activity_stream.async_setup() + pubnub.subscribe(self.async_pubnub_message) + self._pubnub_unsub = async_create_pubnub(user_data["UserID"], pubnub) + + @callback + def async_pubnub_message(self, device_id, date_time, message): + """Process a pubnub message.""" + device = self.get_device_detail(device_id) + activities = activities_from_pubnub_message(device, date_time, message) + if activities: + self.activity_stream.async_process_newer_device_activities(activities) + self.async_signal_device_id_update(device.device_id) + self.activity_stream.async_schedule_house_id_refresh(device.house_id) + + @callback + def async_stop(self): + """Stop the subscriptions.""" + self._pubnub_unsub() + self.activity_stream.async_stop() @property def doorbells(self): @@ -165,27 +201,38 @@ async def _async_refresh(self, time): await self._async_refresh_device_detail_by_ids(self._subscriptions.keys()) async def _async_refresh_device_detail_by_ids(self, device_ids_list): - for device_id in device_ids_list: - if device_id in self._locks_by_id: - await self._async_update_device_detail( - self._locks_by_id[device_id], self._api.async_get_lock_detail - ) - # keypads are always attached to locks - if ( - device_id in self._device_detail_by_id - and self._device_detail_by_id[device_id].keypad is not None - ): - keypad = self._device_detail_by_id[device_id].keypad - self._device_detail_by_id[keypad.device_id] = keypad - elif device_id in self._doorbells_by_id: - await self._async_update_device_detail( - self._doorbells_by_id[device_id], - self._api.async_get_doorbell_detail, - ) - _LOGGER.debug( - "async_signal_device_id_update (from detail updates): %s", device_id + await asyncio.gather( + *[ + self._async_refresh_device_detail_by_id(device_id) + for device_id in device_ids_list + ] + ) + + async def _async_refresh_device_detail_by_id(self, device_id): + if device_id in self._locks_by_id: + if self.activity_stream and self.activity_stream.pubnub.connected: + saved_attrs = _save_live_attrs(self._device_detail_by_id[device_id]) + await self._async_update_device_detail( + self._locks_by_id[device_id], self._api.async_get_lock_detail ) - self.async_signal_device_id_update(device_id) + if self.activity_stream and self.activity_stream.pubnub.connected: + _restore_live_attrs(self._device_detail_by_id[device_id], saved_attrs) + # keypads are always attached to locks + if ( + device_id in self._device_detail_by_id + and self._device_detail_by_id[device_id].keypad is not None + ): + keypad = self._device_detail_by_id[device_id].keypad + self._device_detail_by_id[keypad.device_id] = keypad + elif device_id in self._doorbells_by_id: + await self._async_update_device_detail( + self._doorbells_by_id[device_id], + self._api.async_get_doorbell_detail, + ) + _LOGGER.debug( + "async_signal_device_id_update (from detail updates): %s", device_id + ) + self.async_signal_device_id_update(device_id) async def _async_update_device_detail(self, device, api_call): _LOGGER.debug( @@ -213,9 +260,9 @@ async def _async_update_device_detail(self, device, api_call): def _get_device_name(self, device_id): """Return doorbell or lock name as August has it stored.""" - if self._locks_by_id.get(device_id): + if device_id in self._locks_by_id: return self._locks_by_id[device_id].device_name - if self._doorbells_by_id.get(device_id): + if device_id in self._doorbells_by_id: return self._doorbells_by_id[device_id].device_name async def async_lock(self, device_id): @@ -252,8 +299,7 @@ async def _async_call_api_op_requires_bridge( return ret def _remove_inoperative_doorbells(self): - doorbells = list(self.doorbells) - for doorbell in doorbells: + for doorbell in list(self.doorbells): device_id = doorbell.device_id doorbell_is_operative = False doorbell_detail = self._device_detail_by_id.get(device_id) @@ -273,9 +319,7 @@ def _remove_inoperative_locks(self): # Remove non-operative locks as there must # be a bridge (August Connect) for them to # be usable - locks = list(self.locks) - - for lock in locks: + for lock in list(self.locks): device_id = lock.device_id lock_is_operative = False lock_detail = self._device_detail_by_id.get(device_id) @@ -289,14 +333,27 @@ def _remove_inoperative_locks(self): "The lock %s could not be setup because it does not have a bridge (Connect)", lock.device_name, ) - elif not lock_detail.bridge.operative: - _LOGGER.info( - "The lock %s could not be setup because the bridge (Connect) is not operative", - lock.device_name, - ) + # Bridge may come back online later so we still add the device since we will + # have a pubnub subscription to tell use when it recovers else: lock_is_operative = True if not lock_is_operative: del self._locks_by_id[device_id] del self._device_detail_by_id[device_id] + + +def _save_live_attrs(lock_detail): + """Store the attributes that the lock detail api may have an invalid cache for. + + Since we are connected to pubnub we may have more current data + then the api so we want to restore the most current data after + updating battery state etc. + """ + return {attr: getattr(lock_detail, attr) for attr in API_CACHED_ATTRS} + + +def _restore_live_attrs(lock_detail, attrs): + """Restore the non-cache attributes after a cached update.""" + for attr, value in attrs.items(): + setattr(lock_detail, attr, value) diff --git a/homeassistant/components/august/activity.py b/homeassistant/components/august/activity.py index d972fbf52817e0..18f390b4f8f5ec 100644 --- a/homeassistant/components/august/activity.py +++ b/homeassistant/components/august/activity.py @@ -1,8 +1,12 @@ """Consume the august activity stream.""" +import asyncio import logging from aiohttp import ClientError +from homeassistant.core import callback +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.event import async_call_later from homeassistant.util.dt import utcnow from .const import ACTIVITY_UPDATE_INTERVAL @@ -17,27 +21,58 @@ class ActivityStream(AugustSubscriberMixin): """August activity stream handler.""" - def __init__(self, hass, api, august_gateway, house_ids): + def __init__(self, hass, api, august_gateway, house_ids, pubnub): """Init August activity stream object.""" super().__init__(hass, ACTIVITY_UPDATE_INTERVAL) self._hass = hass + self._schedule_updates = {} self._august_gateway = august_gateway self._api = api self._house_ids = house_ids - self._latest_activities_by_id_type = {} + self._latest_activities = {} self._last_update_time = None self._abort_async_track_time_interval = None + self.pubnub = pubnub + self._update_debounce = {} async def async_setup(self): """Token refresh check and catch up the activity stream.""" - await self._async_refresh(utcnow) + for house_id in self._house_ids: + self._update_debounce[house_id] = self._async_create_debouncer(house_id) + + await self._async_refresh(utcnow()) + + @callback + def _async_create_debouncer(self, house_id): + """Create a debouncer for the house id.""" + + async def _async_update_house_id(): + await self._async_update_house_id(house_id) + + return Debouncer( + self._hass, + _LOGGER, + cooldown=ACTIVITY_UPDATE_INTERVAL.seconds, + immediate=True, + function=_async_update_house_id, + ) + + @callback + def async_stop(self): + """Cleanup any debounces.""" + for debouncer in self._update_debounce.values(): + debouncer.async_cancel() + for house_id in self._schedule_updates: + if self._schedule_updates[house_id] is not None: + self._schedule_updates[house_id]() + self._schedule_updates[house_id] = None def get_latest_device_activity(self, device_id, activity_types): """Return latest activity that is one of the acitivty_types.""" - if device_id not in self._latest_activities_by_id_type: + if device_id not in self._latest_activities: return None - latest_device_activities = self._latest_activities_by_id_type[device_id] + latest_device_activities = self._latest_activities[device_id] latest_activity = None for activity_type in activity_types: @@ -54,62 +89,86 @@ def get_latest_device_activity(self, device_id, activity_types): async def _async_refresh(self, time): """Update the activity stream from August.""" - # This is the only place we refresh the api token await self._august_gateway.async_refresh_access_token_if_needed() + if self.pubnub.connected: + _LOGGER.debug("Skipping update because pubnub is connected") + return await self._async_update_device_activities(time) async def _async_update_device_activities(self, time): _LOGGER.debug("Start retrieving device activities") - - limit = ( - ACTIVITY_STREAM_FETCH_LIMIT - if self._last_update_time - else ACTIVITY_CATCH_UP_FETCH_LIMIT + await asyncio.gather( + *[ + self._update_debounce[house_id].async_call() + for house_id in self._house_ids + ] ) + self._last_update_time = time - for house_id in self._house_ids: - _LOGGER.debug("Updating device activity for house id %s", house_id) - try: - activities = await self._api.async_get_house_activities( - self._august_gateway.access_token, house_id, limit=limit - ) - except ClientError as ex: - _LOGGER.error( - "Request error trying to retrieve activity for house id %s: %s", - house_id, - ex, - ) - # Make sure we process the next house if one of them fails - continue + @callback + def async_schedule_house_id_refresh(self, house_id): + """Update for a house activities now and once in the future.""" + if self._schedule_updates.get(house_id): + self._schedule_updates[house_id]() + self._schedule_updates[house_id] = None + + async def _update_house_activities(_): + await self._update_debounce[house_id].async_call() + + self._hass.async_create_task(self._update_debounce[house_id].async_call()) + # Schedule an update past the debounce to ensure + # we catch the case where the lock operator is + # not updated or the lock failed + self._schedule_updates[house_id] = async_call_later( + self._hass, ACTIVITY_UPDATE_INTERVAL.seconds + 1, _update_house_activities + ) - _LOGGER.debug( - "Completed retrieving device activities for house id %s", house_id + async def _async_update_house_id(self, house_id): + """Update device activities for a house.""" + if self._last_update_time: + limit = ACTIVITY_STREAM_FETCH_LIMIT + else: + limit = ACTIVITY_CATCH_UP_FETCH_LIMIT + + _LOGGER.debug("Updating device activity for house id %s", house_id) + try: + activities = await self._api.async_get_house_activities( + self._august_gateway.access_token, house_id, limit=limit + ) + except ClientError as ex: + _LOGGER.error( + "Request error trying to retrieve activity for house id %s: %s", + house_id, + ex, ) + # Make sure we process the next house if one of them fails + return - updated_device_ids = self._process_newer_device_activities(activities) + _LOGGER.debug( + "Completed retrieving device activities for house id %s", house_id + ) - if updated_device_ids: - for device_id in updated_device_ids: - _LOGGER.debug( - "async_signal_device_id_update (from activity stream): %s", - device_id, - ) - self.async_signal_device_id_update(device_id) + updated_device_ids = self.async_process_newer_device_activities(activities) - self._last_update_time = time + if not updated_device_ids: + return - def _process_newer_device_activities(self, activities): + for device_id in updated_device_ids: + _LOGGER.debug( + "async_signal_device_id_update (from activity stream): %s", + device_id, + ) + self.async_signal_device_id_update(device_id) + + def async_process_newer_device_activities(self, activities): + """Process activities if they are newer than the last one.""" updated_device_ids = set() for activity in activities: device_id = activity.device_id activity_type = activity.activity_type - - self._latest_activities_by_id_type.setdefault(device_id, {}) - - lastest_activity = self._latest_activities_by_id_type[device_id].get( - activity_type - ) + device_activities = self._latest_activities.setdefault(device_id, {}) + lastest_activity = device_activities.get(activity_type) # Ignore activities that are older than the latest one if ( @@ -118,7 +177,7 @@ def _process_newer_device_activities(self, activities): ): continue - self._latest_activities_by_id_type[device_id][activity_type] = activity + device_activities[activity_type] = activity updated_device_ids.add(device_id) diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 226cbf655f996d..6dccec57a09a86 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -2,9 +2,9 @@ from datetime import datetime, timedelta import logging -from august.activity import ActivityType -from august.lock import LockDoorStatus -from august.util import update_lock_detail_from_activity +from yalexs.activity import ACTION_DOORBELL_CALL_MISSED, ActivityType +from yalexs.lock import LockDoorStatus +from yalexs.util import update_lock_detail_from_activity from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -14,15 +14,15 @@ BinarySensorEntity, ) from homeassistant.core import callback -from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.util.dt import utcnow +from homeassistant.helpers.event import async_call_later -from .const import DATA_AUGUST, DOMAIN +from .const import ACTIVITY_UPDATE_INTERVAL, DATA_AUGUST, DOMAIN from .entity import AugustEntityMixin _LOGGER = logging.getLogger(__name__) -TIME_TO_DECLARE_DETECTION = timedelta(seconds=60) +TIME_TO_DECLARE_DETECTION = timedelta(seconds=ACTIVITY_UPDATE_INTERVAL.seconds) +TIME_TO_RECHECK_DETECTION = timedelta(seconds=ACTIVITY_UPDATE_INTERVAL.seconds * 3) def _retrieve_online_state(data, detail): @@ -35,30 +35,43 @@ def _retrieve_online_state(data, detail): def _retrieve_motion_state(data, detail): - - return _activity_time_based_state( - data, - detail.device_id, - [ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_DING], + latest = data.activity_stream.get_latest_device_activity( + detail.device_id, {ActivityType.DOORBELL_MOTION} ) + if latest is None: + return False + + return _activity_time_based_state(latest) -def _retrieve_ding_state(data, detail): - return _activity_time_based_state( - data, detail.device_id, [ActivityType.DOORBELL_DING] +def _retrieve_ding_state(data, detail): + latest = data.activity_stream.get_latest_device_activity( + detail.device_id, {ActivityType.DOORBELL_DING} ) + if latest is None: + return False + + if ( + data.activity_stream.pubnub.connected + and latest.action == ACTION_DOORBELL_CALL_MISSED + ): + return False -def _activity_time_based_state(data, device_id, activity_types): + return _activity_time_based_state(latest) + + +def _activity_time_based_state(latest): """Get the latest state of the sensor.""" - latest = data.activity_stream.get_latest_device_activity(device_id, activity_types) + start = latest.activity_start_time + end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION + return start <= _native_datetime() <= end - if latest is not None: - start = latest.activity_start_time - end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION - return start <= datetime.now() <= end - return None + +def _native_datetime(): + """Return time in the format august uses without timezone.""" + return datetime.now() SENSOR_NAME = 0 @@ -143,12 +156,19 @@ def name(self): def _update_from_data(self): """Get the latest state of the sensor and update activity.""" door_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.DOOR_OPERATION] + self._device_id, {ActivityType.DOOR_OPERATION} ) if door_activity is not None: update_lock_detail_from_activity(self._detail, door_activity) + bridge_activity = self._data.activity_stream.get_latest_device_activity( + self._device_id, {ActivityType.BRIDGE_OPERATION} + ) + + if bridge_activity is not None: + update_lock_detail_from_activity(self._detail, bridge_activity) + @property def unique_id(self) -> str: """Get the unique of the door open binary sensor.""" @@ -179,25 +199,30 @@ def is_on(self): """Return true if the binary sensor is on.""" return self._state + @property + def _sensor_config(self): + """Return the config for the sensor.""" + return SENSOR_TYPES_DOORBELL[self._sensor_type] + @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_DEVICE_CLASS] + return self._sensor_config[SENSOR_DEVICE_CLASS] @property def name(self): """Return the name of the binary sensor.""" - return f"{self._device.device_name} {SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME]}" + return f"{self._device.device_name} {self._sensor_config[SENSOR_NAME]}" @property def _state_provider(self): """Return the state provider for the binary sensor.""" - return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_STATE_PROVIDER] + return self._sensor_config[SENSOR_STATE_PROVIDER] @property def _is_time_based(self): """Return true of false if the sensor is time based.""" - return SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_STATE_IS_TIME_BASED] + return self._sensor_config[SENSOR_STATE_IS_TIME_BASED] @callback def _update_from_data(self): @@ -228,17 +253,20 @@ def _scheduled_update(now): """Timer callback for sensor update.""" self._check_for_off_update_listener = None self._update_from_data() + if not self._state: + self.async_write_ha_state() - self._check_for_off_update_listener = async_track_point_in_utc_time( - self.hass, _scheduled_update, utcnow() + TIME_TO_DECLARE_DETECTION + self._check_for_off_update_listener = async_call_later( + self.hass, TIME_TO_RECHECK_DETECTION.seconds, _scheduled_update ) def _cancel_any_pending_updates(self): """Cancel any updates to recheck a sensor to see if it is ready to turn off.""" - if self._check_for_off_update_listener: - _LOGGER.debug("%s: canceled pending update", self.entity_id) - self._check_for_off_update_listener() - self._check_for_off_update_listener = None + if not self._check_for_off_update_listener: + return + _LOGGER.debug("%s: canceled pending update", self.entity_id) + self._check_for_off_update_listener() + self._check_for_off_update_listener = None async def async_added_to_hass(self): """Call the mixin to subscribe and setup an async_track_point_in_utc_time to turn off the sensor if needed.""" @@ -248,7 +276,4 @@ async def async_added_to_hass(self): @property def unique_id(self) -> str: """Get the unique id of the doorbell sensor.""" - return ( - f"{self._device_id}_" - f"{SENSOR_TYPES_DOORBELL[self._sensor_type][SENSOR_NAME].lower()}" - ) + return f"{self._device_id}_{self._sensor_config[SENSOR_NAME].lower()}" diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index 4037489fa229a3..e002e0b25176dc 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -1,7 +1,7 @@ """Support for August doorbell camera.""" -from august.activity import ActivityType -from august.util import update_doorbell_image_from_activity +from yalexs.activity import ActivityType +from yalexs.util import update_doorbell_image_from_activity from homeassistant.components.camera import Camera from homeassistant.core import callback @@ -63,7 +63,7 @@ def model(self): def _update_from_data(self): """Get the latest state of the sensor.""" doorbell_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.DOORBELL_MOTION] + self._device_id, {ActivityType.DOORBELL_MOTION} ) if doorbell_activity is not None: diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index 29bb41947a065d..7176592e37efd9 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -1,8 +1,8 @@ """Config flow for August integration.""" import logging -from august.authenticator import ValidationResult import voluptuous as vol +from yalexs.authenticator import ValidationResult from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME diff --git a/homeassistant/components/august/gateway.py b/homeassistant/components/august/gateway.py index f71541e82fcb35..5499246a1871ec 100644 --- a/homeassistant/components/august/gateway.py +++ b/homeassistant/components/august/gateway.py @@ -5,8 +5,8 @@ import os from aiohttp import ClientError, ClientResponseError -from august.api_async import ApiAsync -from august.authenticator_async import AuthenticationState, AuthenticatorAsync +from yalexs.api_async import ApiAsync +from yalexs.authenticator_async import AuthenticationState, AuthenticatorAsync from homeassistant.const import ( CONF_PASSWORD, diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 4b4ae906190aee..59c97190d7fe1b 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -1,9 +1,9 @@ """Support for August lock.""" import logging -from august.activity import ActivityType -from august.lock import LockStatus -from august.util import update_lock_detail_from_activity +from yalexs.activity import ActivityType +from yalexs.lock import LockStatus +from yalexs.util import update_lock_detail_from_activity from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity from homeassistant.const import ATTR_BATTERY_LEVEL @@ -73,13 +73,21 @@ def _update_lock_status_from_detail(self): def _update_from_data(self): """Get the latest state of the sensor and update activity.""" lock_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.LOCK_OPERATION] + self._device_id, + {ActivityType.LOCK_OPERATION, ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR}, ) if lock_activity is not None: self._changed_by = lock_activity.operated_by update_lock_detail_from_activity(self._detail, lock_activity) + bridge_activity = self._data.activity_stream.get_latest_device_activity( + self._device_id, {ActivityType.BRIDGE_OPERATION} + ) + + if bridge_activity is not None: + update_lock_detail_from_activity(self._detail, bridge_activity) + self._update_lock_status_from_detail() @property diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 91733b6822e07c..3a156a189c7789 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": ["py-august==0.25.2"], + "requirements": ["yalexs==1.1.4"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 9dba5bb2766f47..1841b2cef569f7 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -1,7 +1,7 @@ """Support for August sensors.""" import logging -from august.activity import ActivityType +from yalexs.activity import ActivityType from homeassistant.components.sensor import DEVICE_CLASS_BATTERY from homeassistant.const import ATTR_ENTITY_PICTURE, PERCENTAGE, STATE_UNAVAILABLE @@ -154,7 +154,7 @@ def name(self): def _update_from_data(self): """Get the latest state of the sensor and update activity.""" lock_activity = self._data.activity_stream.get_latest_device_activity( - self._device_id, [ActivityType.LOCK_OPERATION] + self._device_id, {ActivityType.LOCK_OPERATION} ) self._available = True diff --git a/requirements_all.txt b/requirements_all.txt index 0ee7a4edb721e8..dbaee5741842a2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1191,9 +1191,6 @@ pushover_complete==1.1.1 # homeassistant.components.rpi_gpio_pwm pwmled==1.6.7 -# homeassistant.components.august -py-august==0.25.2 - # homeassistant.components.canary py-canary==0.5.1 @@ -2347,6 +2344,9 @@ xs1-api-client==3.0.0 # homeassistant.components.yale_smart_alarm yalesmartalarmclient==0.1.6 +# homeassistant.components.august +yalexs==1.1.4 + # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6ceeccec6b5b5f..20bd6547b059ff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -616,9 +616,6 @@ pure-python-adb[async]==0.3.0.dev0 # homeassistant.components.pushbullet pushbullet.py==0.11.0 -# homeassistant.components.august -py-august==0.25.2 - # homeassistant.components.canary py-canary==0.5.1 @@ -1208,6 +1205,9 @@ xbox-webapi==2.0.8 # homeassistant.components.zestimate xmltodict==0.12.0 +# homeassistant.components.august +yalexs==1.1.4 + # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 928b753af52cbc..9a54a708a4f834 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -6,21 +6,26 @@ # from unittest.mock import AsyncMock from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch -from august.activity import ( +from yalexs.activity import ( + ACTIVITY_ACTIONS_BRIDGE_OPERATION, ACTIVITY_ACTIONS_DOOR_OPERATION, ACTIVITY_ACTIONS_DOORBELL_DING, ACTIVITY_ACTIONS_DOORBELL_MOTION, ACTIVITY_ACTIONS_DOORBELL_VIEW, ACTIVITY_ACTIONS_LOCK_OPERATION, + SOURCE_LOCK_OPERATE, + SOURCE_LOG, + BridgeOperationActivity, DoorbellDingActivity, DoorbellMotionActivity, DoorbellViewActivity, DoorOperationActivity, LockOperationActivity, ) -from august.authenticator import AuthenticationState -from august.doorbell import Doorbell, DoorbellDetail -from august.lock import Lock, LockDetail +from yalexs.authenticator import AuthenticationState +from yalexs.doorbell import Doorbell, DoorbellDetail +from yalexs.lock import Lock, LockDetail +from yalexs.pubnub_async import AugustPubNub from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -48,7 +53,9 @@ def _mock_authenticator(auth_state): @patch("homeassistant.components.august.gateway.ApiAsync") @patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate") -async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock): +async def _mock_setup_august( + hass, api_instance, pubnub_mock, authenticate_mock, api_mock +): """Set up august integration.""" authenticate_mock.side_effect = MagicMock( return_value=_mock_august_authentication( @@ -62,16 +69,21 @@ async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock): options={}, ) entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - return True + with patch("homeassistant.components.august.async_create_pubnub"), patch( + "homeassistant.components.august.AugustPubNub", return_value=pubnub_mock + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + return entry async def _create_august_with_devices( - hass, devices, api_call_side_effects=None, activities=None + hass, devices, api_call_side_effects=None, activities=None, pubnub=None ): if api_call_side_effects is None: api_call_side_effects = {} + if pubnub is None: + pubnub = AugustPubNub() device_data = {"doorbells": [], "locks": []} for device in devices: @@ -152,10 +164,12 @@ def unlock_return_activities_side_effect(access_token, device_id): "unlock_return_activities" ] = unlock_return_activities_side_effect - return await _mock_setup_august_with_api_side_effects(hass, api_call_side_effects) + return await _mock_setup_august_with_api_side_effects( + hass, api_call_side_effects, pubnub + ) -async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects): +async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub): api_instance = MagicMock(name="Api") if api_call_side_effects["get_lock_detail"]: @@ -193,11 +207,13 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects): side_effect=api_call_side_effects["unlock_return_activities"] ) - return await _mock_setup_august(hass, api_instance) + api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"}) + + return await _mock_setup_august(hass, api_instance, pubnub) def _mock_august_authentication(token_text, token_timestamp, state): - authentication = MagicMock(name="august.authentication") + authentication = MagicMock(name="yalexs.authentication") type(authentication).state = PropertyMock(return_value=state) type(authentication).access_token = PropertyMock(return_value=token_text) type(authentication).access_token_expires = PropertyMock( @@ -301,23 +317,25 @@ async def _mock_doorsense_missing_august_lock_detail(hass): def _mock_lock_operation_activity(lock, action, offset): return LockOperationActivity( + SOURCE_LOCK_OPERATE, { "dateTime": (time.time() + offset) * 1000, "deviceID": lock.device_id, "deviceType": "lock", "action": action, - } + }, ) def _mock_door_operation_activity(lock, action, offset): return DoorOperationActivity( + SOURCE_LOCK_OPERATE, { "dateTime": (time.time() + offset) * 1000, "deviceID": lock.device_id, "deviceType": "lock", "action": action, - } + }, ) @@ -327,13 +345,15 @@ def _activity_from_dict(activity_dict): activity_dict["dateTime"] = time.time() * 1000 if action in ACTIVITY_ACTIONS_DOORBELL_DING: - return DoorbellDingActivity(activity_dict) + return DoorbellDingActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_DOORBELL_MOTION: - return DoorbellMotionActivity(activity_dict) + return DoorbellMotionActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_DOORBELL_VIEW: - return DoorbellViewActivity(activity_dict) + return DoorbellViewActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_LOCK_OPERATION: - return LockOperationActivity(activity_dict) + return LockOperationActivity(SOURCE_LOG, activity_dict) if action in ACTIVITY_ACTIONS_DOOR_OPERATION: - return DoorOperationActivity(activity_dict) + return DoorOperationActivity(SOURCE_LOG, activity_dict) + if action in ACTIVITY_ACTIONS_BRIDGE_OPERATION: + return BridgeOperationActivity(SOURCE_LOG, activity_dict) return None diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 0e337813f52605..0912b05bec11f2 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -1,4 +1,9 @@ """The binary_sensor tests for the august platform.""" +import datetime +from unittest.mock import Mock, patch + +from yalexs.pubnub_async import AugustPubNub + from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -9,7 +14,9 @@ STATE_UNAVAILABLE, ) from homeassistant.helpers import device_registry as dr +import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed from tests.components.august.mocks import ( _create_august_with_devices, _mock_activities_from_fixture, @@ -52,6 +59,22 @@ async def test_doorsense(hass): assert binary_sensor_online_with_doorsense_name.state == STATE_OFF +async def test_lock_bridge_offline(hass): + """Test creation of a lock with doorsense and bridge that goes offline.""" + lock_one = await _mock_lock_from_fixture( + hass, "get_lock.online_with_doorsense.json" + ) + activities = await _mock_activities_from_fixture( + hass, "get_activity.bridge_offline.json" + ) + await _create_august_with_devices(hass, [lock_one], activities=activities) + + binary_sensor_online_with_doorsense_name = hass.states.get( + "binary_sensor.online_with_doorsense_name_open" + ) + assert binary_sensor_online_with_doorsense_name.state == STATE_UNAVAILABLE + + async def test_create_doorbell(hass): """Test creation of a doorbell.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") @@ -112,6 +135,108 @@ async def test_create_doorbell_with_motion(hass): "binary_sensor.k98gidt45gul_name_ding" ) assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF + new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) + native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) + with patch( + "homeassistant.components.august.binary_sensor._native_datetime", + return_value=native_time, + ): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + + +async def test_doorbell_update_via_pubnub(hass): + """Test creation of a doorbell that can be updated via pubnub.""" + doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + pubnub = AugustPubNub() + + await _create_august_with_devices(hass, [doorbell_one], pubnub=pubnub) + assert doorbell_one.pubsub_channel == "7c7a6672-59c8-3333-ffff-dcd98705cccc" + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF + + pubnub.message( + pubnub, + Mock( + channel=doorbell_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "imagecapture", + "data": { + "result": { + "created_at": "2021-03-16T01:07:08.817Z", + "secure_url": "https://dyu7azbnaoi74.cloudfront.net/zip/images/zip.jpeg", + }, + }, + }, + ), + ) + + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF + + new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) + native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) + with patch( + "homeassistant.components.august.binary_sensor._native_datetime", + return_value=native_time, + ): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + + pubnub.message( + pubnub, + Mock( + channel=doorbell_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "buttonpush", + }, + ), + ) + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_ON + new_time = dt_util.utcnow() + datetime.timedelta(seconds=40) + native_time = datetime.datetime.now() + datetime.timedelta(seconds=40) + with patch( + "homeassistant.components.august.binary_sensor._native_datetime", + return_value=native_time, + ): + async_fire_time_changed(hass, new_time) + await hass.async_block_till_done() + + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF async def test_doorbell_device_registry(hass): diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index 205c5c70689e28..c87291e0f79d2a 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -1,7 +1,7 @@ """Test the August config flow.""" from unittest.mock import patch -from august.authenticator import ValidationResult +from yalexs.authenticator import ValidationResult from homeassistant import config_entries, setup from homeassistant.components.august.const import ( diff --git a/tests/components/august/test_gateway.py b/tests/components/august/test_gateway.py index ced073600082da..54a5e9321f2860 100644 --- a/tests/components/august/test_gateway.py +++ b/tests/components/august/test_gateway.py @@ -1,7 +1,7 @@ """The gateway tests for the august platform.""" from unittest.mock import MagicMock, patch -from august.authenticator_common import AuthenticationState +from yalexs.authenticator_common import AuthenticationState from homeassistant.components.august.const import DOMAIN from homeassistant.components.august.gateway import AugustGateway diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index d0116d2c586b0f..8b0885f7341779 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -3,13 +3,14 @@ from unittest.mock import patch from aiohttp import ClientResponseError -from august.authenticator_common import AuthenticationState -from august.exceptions import AugustApiAIOHTTPError +from yalexs.authenticator_common import AuthenticationState +from yalexs.exceptions import AugustApiAIOHTTPError from homeassistant import setup from homeassistant.components.august.const import DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_RETRY, ) @@ -46,7 +47,7 @@ async def test_august_is_offline(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", side_effect=asyncio.TimeoutError, ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -152,7 +153,7 @@ async def test_auth_fails(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", side_effect=ClientResponseError(None, None, status=401), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -178,7 +179,7 @@ async def test_bad_password(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", return_value=_mock_august_authentication( "original_token", 1234, AuthenticationState.BAD_PASSWORD ), @@ -206,7 +207,7 @@ async def test_http_failure(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", side_effect=ClientResponseError(None, None, status=500), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -230,7 +231,7 @@ async def test_unknown_auth_state(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", return_value=_mock_august_authentication("original_token", 1234, None), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -256,7 +257,7 @@ async def test_requires_validation_state(hass): await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "august.authenticator_async.AuthenticatorAsync.async_authenticate", + "yalexs.authenticator_async.AuthenticatorAsync.async_authenticate", return_value=_mock_august_authentication( "original_token", 1234, AuthenticationState.REQUIRES_VALIDATION ), @@ -268,3 +269,18 @@ async def test_requires_validation_state(hass): assert len(hass.config_entries.flow.async_progress()) == 1 assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "reauth" + + +async def test_load_unload(hass): + """Config entry can be unloaded.""" + + august_operative_lock = await _mock_operative_august_lock_detail(hass) + august_inoperative_lock = await _mock_inoperative_august_lock_detail(hass) + config_entry = await _create_august_with_devices( + hass, [august_operative_lock, august_inoperative_lock] + ) + + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index dadd6de2d4fa63..5b3c163780f39c 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -1,15 +1,23 @@ """The lock tests for the august platform.""" +import datetime +from unittest.mock import Mock + +from yalexs.pubnub_async import AugustPubNub + from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK, STATE_LOCKED, + STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_UNLOCKED, ) from homeassistant.helpers import device_registry as dr, entity_registry as er +import homeassistant.util.dt as dt_util +from tests.common import async_fire_time_changed from tests.components.august.mocks import ( _create_august_with_devices, _mock_activities_from_fixture, @@ -112,3 +120,116 @@ async def test_one_lock_unknown_state(hass): lock_brokenid_name = hass.states.get("lock.brokenid_name") assert lock_brokenid_name.state == STATE_UNKNOWN + + +async def test_lock_bridge_offline(hass): + """Test creation of a lock with doorsense and bridge that goes offline.""" + lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) + + activities = await _mock_activities_from_fixture( + hass, "get_activity.bridge_offline.json" + ) + await _create_august_with_devices(hass, [lock_one], activities=activities) + + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + + assert lock_online_with_doorsense_name.state == STATE_UNAVAILABLE + + +async def test_lock_bridge_online(hass): + """Test creation of a lock with doorsense and bridge that goes offline.""" + lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) + + activities = await _mock_activities_from_fixture( + hass, "get_activity.bridge_online.json" + ) + await _create_august_with_devices(hass, [lock_one], activities=activities) + + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + +async def test_lock_update_via_pubnub(hass): + """Test creation of a lock with doorsense and bridge.""" + lock_one = await _mock_doorsense_enabled_august_lock_detail(hass) + assert lock_one.pubsub_channel == "pubsub" + pubnub = AugustPubNub() + + activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json") + config_entry = await _create_august_with_devices( + hass, [lock_one], activities=activities, pubnub=pubnub + ) + + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + pubnub.message( + pubnub, + Mock( + channel=lock_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "kAugLockState_Unlocking", + }, + ), + ) + + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_UNLOCKED + + pubnub.message( + pubnub, + Mock( + channel=lock_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "kAugLockState_Locking", + }, + ), + ) + + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + pubnub.connected = True + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + # Ensure pubnub status is always preserved + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_LOCKED + + pubnub.message( + pubnub, + Mock( + channel=lock_one.pubsub_channel, + timetoken=dt_util.utcnow().timestamp() * 10000000, + message={ + "status": "kAugLockState_Unlocking", + }, + ), + ) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_UNLOCKED + + async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4)) + await hass.async_block_till_done() + lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name") + assert lock_online_with_doorsense_name.state == STATE_UNLOCKED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/fixtures/august/get_activity.bridge_offline.json b/tests/fixtures/august/get_activity.bridge_offline.json new file mode 100644 index 00000000000000..ed4aaadaf738ba --- /dev/null +++ b/tests/fixtures/august/get_activity.bridge_offline.json @@ -0,0 +1,34 @@ +[{ + "entities" : { + "activity" : "mockActivity2", + "house" : "123", + "device" : "online_with_doorsense", + "callingUser" : "mockUserId2", + "otherUser" : "deleted" + }, + "callingUser" : { + "LastName" : "elven princess", + "UserID" : "mockUserId2", + "FirstName" : "Your favorite" + }, + "otherUser" : { + "LastName" : "User", + "UserName" : "deleteduser", + "FirstName" : "Unknown", + "UserID" : "deleted", + "PhoneNo" : "deleted" + }, + "deviceType" : "lock", + "deviceName" : "MockHouseTDoor", + "action" : "associated_bridge_offline", + "dateTime" : 1582007218000, + "info" : { + "remote" : true, + "DateLogActionID" : "ABC+Time" + }, + "deviceID" : "online_with_doorsense", + "house" : { + "houseName" : "MockHouse", + "houseID" : "123" + } +}] diff --git a/tests/fixtures/august/get_activity.bridge_online.json b/tests/fixtures/august/get_activity.bridge_online.json new file mode 100644 index 00000000000000..db14f06cfe939f --- /dev/null +++ b/tests/fixtures/august/get_activity.bridge_online.json @@ -0,0 +1,34 @@ +[{ + "entities" : { + "activity" : "mockActivity2", + "house" : "123", + "device" : "online_with_doorsense", + "callingUser" : "mockUserId2", + "otherUser" : "deleted" + }, + "callingUser" : { + "LastName" : "elven princess", + "UserID" : "mockUserId2", + "FirstName" : "Your favorite" + }, + "otherUser" : { + "LastName" : "User", + "UserName" : "deleteduser", + "FirstName" : "Unknown", + "UserID" : "deleted", + "PhoneNo" : "deleted" + }, + "deviceType" : "lock", + "deviceName" : "MockHouseTDoor", + "action" : "associated_bridge_online", + "dateTime" : 1582007218000, + "info" : { + "remote" : true, + "DateLogActionID" : "ABC+Time" + }, + "deviceID" : "online_with_doorsense", + "house" : { + "houseName" : "MockHouse", + "houseID" : "123" + } +}] diff --git a/tests/fixtures/august/get_doorbell.json b/tests/fixtures/august/get_doorbell.json index abe6e37b1e3bc3..fb2cd5780c961d 100644 --- a/tests/fixtures/august/get_doorbell.json +++ b/tests/fixtures/august/get_doorbell.json @@ -55,7 +55,7 @@ "reconnect" ], "doorbellID" : "K98GiDT45GUL", - "HouseID" : "3dd2accaea08", + "HouseID" : "mockhouseid1", "telemetry" : { "signal_level" : -56, "date" : "2017-12-10 08:05:12", diff --git a/tests/fixtures/august/get_lock.online_with_doorsense.json b/tests/fixtures/august/get_lock.online_with_doorsense.json index f737657048231f..e29614c9e489be 100644 --- a/tests/fixtures/august/get_lock.online_with_doorsense.json +++ b/tests/fixtures/august/get_lock.online_with_doorsense.json @@ -13,9 +13,10 @@ "updated" : "2000-00-00T00:00:00.447Z" } }, + "pubsubChannel":"pubsub", "Calibrated" : false, "Created" : "2000-00-00T00:00:00.447Z", - "HouseID" : "123", + "HouseID" : "mockhouseid1", "HouseName" : "Test", "LockID" : "online_with_doorsense", "LockName" : "Online door with doorsense", From 6b93c4073d0f782c95e8159a8a532fdc98a7c8ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 21 Mar 2021 21:17:04 -1000 Subject: [PATCH 1430/1818] Ensure homekit yaml config works when there is an ignored config entry (#48175) --- homeassistant/components/homekit/config_flow.py | 4 ++-- tests/components/homekit/test_config_flow.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 1fc99bb85851e7..683e533d2dfbf4 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -223,7 +223,7 @@ def _async_current_names(self): """Return a set of bridge names.""" return { entry.data[CONF_NAME] - for entry in self._async_current_entries() + for entry in self._async_current_entries(include_ignore=False) if CONF_NAME in entry.data } @@ -251,7 +251,7 @@ def _async_is_unique_name_port(self, user_input): port = user_input[CONF_PORT] return not any( entry.data[CONF_NAME] == name or entry.data[CONF_PORT] == port - for entry in self._async_current_entries() + for entry in self._async_current_entries(include_ignore=False) ) @staticmethod diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index 3d94672cd8ac9e..268046752650e0 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -5,7 +5,7 @@ from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.homekit.const import DOMAIN, SHORT_BRIDGE_NAME -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_IMPORT from homeassistant.const import CONF_NAME, CONF_PORT from tests.common import MockConfigEntry @@ -229,6 +229,8 @@ async def test_import(hass): """Test we can import instance.""" await setup.async_setup_component(hass, "persistent_notification", {}) + ignored_entry = MockConfigEntry(domain=DOMAIN, data={}, source=SOURCE_IGNORE) + ignored_entry.add_to_hass(hass) entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) From f67e8b43690b0836ff8ee60ce90b9e015123badc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 22 Mar 2021 00:22:32 -0700 Subject: [PATCH 1431/1818] Populate trigger variable when manually triggering automation (#48202) * Populate trigger variable when manually triggering automation * Update tests/components/automation/test_init.py Co-authored-by: Erik Montnemery --- .../components/automation/__init__.py | 2 +- tests/components/automation/test_init.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4c278cbf5f0cc4..79c6dcc2312d24 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -189,7 +189,7 @@ async def async_setup(hass, config): async def trigger_service_handler(entity, service_call): """Handle forced automation trigger, e.g. from frontend.""" await entity.async_trigger( - service_call.data[ATTR_VARIABLES], + {**service_call.data[ATTR_VARIABLES], "trigger": {"platform": None}}, skip_condition=service_call.data[CONF_SKIP_CONDITION], context=service_call.context, ) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 91531481a993c8..71727258fcc45f 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1355,3 +1355,33 @@ async def test_blueprint_automation(hass, calls): assert automation.entities_in_automation(hass, "automation.automation_0") == [ "light.kitchen" ] + + +async def test_trigger_service(hass, calls): + """Test the automation trigger service.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "alias": "hello", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": { + "service": "test.automation", + "data_template": {"trigger": "{{ trigger }}"}, + }, + } + }, + ) + context = Context() + await hass.services.async_call( + "automation", + "trigger", + {"entity_id": "automation.hello"}, + blocking=True, + context=context, + ) + + assert len(calls) == 1 + assert calls[0].data.get("trigger") == {"platform": None} + assert calls[0].context.parent_id is context.id From 136ac88bed6bc1214e2f4350674450467e7c0087 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Mar 2021 00:19:05 -1000 Subject: [PATCH 1432/1818] Bump yalexs to 1.1.5 for august (#48205) Turns on auto-reconnect support --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 3a156a189c7789..feadf8f62180b8 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.4"], + "requirements": ["yalexs==1.1.5"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/requirements_all.txt b/requirements_all.txt index dbaee5741842a2..7cef3f490648cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2345,7 +2345,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.1.6 # homeassistant.components.august -yalexs==1.1.4 +yalexs==1.1.5 # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 20bd6547b059ff..fb42375de7b5e3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1206,7 +1206,7 @@ xbox-webapi==2.0.8 xmltodict==0.12.0 # homeassistant.components.august -yalexs==1.1.4 +yalexs==1.1.5 # homeassistant.components.yeelight yeelight==0.5.4 From 834fc1ae141731e9b3041a9236d7ff36a6d88e78 Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Mon, 22 Mar 2021 11:01:17 +0000 Subject: [PATCH 1433/1818] Remove vera should_poll (#48209) --- homeassistant/components/vera/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index e544ddb925f147..3654db5072d881 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -246,11 +246,6 @@ def name(self) -> str: """Return the name of the device.""" return self._name - @property - def should_poll(self) -> bool: - """Get polling requirement from vera device.""" - return self.vera_device.should_poll - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the device.""" From 1bb29bffbb27fa9f3cc5954f33338daab76d0ade Mon Sep 17 00:00:00 2001 From: Sean Wilson Date: Mon, 22 Mar 2021 07:12:14 -0400 Subject: [PATCH 1434/1818] Update aqualogic library to v2.6 (#48119) --- homeassistant/components/aqualogic/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/aqualogic/manifest.json b/homeassistant/components/aqualogic/manifest.json index 2a8e2a78cacdba..5a753342b2bcb6 100644 --- a/homeassistant/components/aqualogic/manifest.json +++ b/homeassistant/components/aqualogic/manifest.json @@ -2,6 +2,6 @@ "domain": "aqualogic", "name": "AquaLogic", "documentation": "https://www.home-assistant.io/integrations/aqualogic", - "requirements": ["aqualogic==1.0"], + "requirements": ["aqualogic==2.6"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 7cef3f490648cc..23c66f79e596f9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -269,7 +269,7 @@ apprise==0.8.9 aprslib==0.6.46 # homeassistant.components.aqualogic -aqualogic==1.0 +aqualogic==2.6 # homeassistant.components.arcam_fmj arcam-fmj==0.5.3 From e0cd7072d6480ff314e4cf1935decc9a22d46a9e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 12:37:16 +0100 Subject: [PATCH 1435/1818] Migrate integrations a-c to extend SensorEntity (#48210) --- homeassistant/components/abode/sensor.py | 3 ++- homeassistant/components/accuweather/sensor.py | 3 ++- homeassistant/components/acmeda/sensor.py | 3 ++- homeassistant/components/adguard/sensor.py | 3 ++- homeassistant/components/ads/sensor.py | 4 ++-- homeassistant/components/advantage_air/sensor.py | 7 ++++--- homeassistant/components/aemet/sensor.py | 6 ++++-- homeassistant/components/aftership/sensor.py | 5 ++--- homeassistant/components/airly/sensor.py | 3 ++- homeassistant/components/airnow/sensor.py | 3 ++- homeassistant/components/airvisual/sensor.py | 5 +++-- homeassistant/components/alarmdecoder/sensor.py | 4 ++-- homeassistant/components/alpha_vantage/sensor.py | 7 +++---- homeassistant/components/ambient_station/sensor.py | 3 ++- homeassistant/components/amcrest/sensor.py | 4 ++-- homeassistant/components/android_ip_webcam/sensor.py | 3 ++- homeassistant/components/apcupsd/sensor.py | 5 ++--- homeassistant/components/aqualogic/sensor.py | 5 ++--- homeassistant/components/arduino/sensor.py | 5 ++--- homeassistant/components/arest/sensor.py | 5 ++--- homeassistant/components/arlo/sensor.py | 5 ++--- homeassistant/components/arwn/sensor.py | 4 ++-- homeassistant/components/asuswrt/sensor.py | 3 ++- homeassistant/components/atag/sensor.py | 3 ++- homeassistant/components/atome/sensor.py | 5 ++--- homeassistant/components/august/sensor.py | 7 +++---- homeassistant/components/aurora/sensor.py | 3 ++- homeassistant/components/aurora_abb_powerone/sensor.py | 5 ++--- homeassistant/components/awair/sensor.py | 4 ++-- homeassistant/components/azure_devops/sensor.py | 3 ++- homeassistant/components/bbox/sensor.py | 7 +++---- homeassistant/components/beewi_smartclim/sensor.py | 5 ++--- homeassistant/components/bh1750/sensor.py | 5 ++--- homeassistant/components/bitcoin/sensor.py | 5 ++--- homeassistant/components/bizkaibus/sensor.py | 5 ++--- homeassistant/components/blebox/sensor.py | 4 ++-- homeassistant/components/blink/sensor.py | 4 ++-- homeassistant/components/blockchain/sensor.py | 5 ++--- homeassistant/components/bloomsky/sensor.py | 5 ++--- homeassistant/components/bme280/sensor.py | 5 ++--- homeassistant/components/bme680/sensor.py | 5 ++--- homeassistant/components/bmp280/sensor.py | 4 ++-- homeassistant/components/bmw_connected_drive/sensor.py | 4 ++-- homeassistant/components/broadlink/sensor.py | 4 ++-- homeassistant/components/brother/sensor.py | 3 ++- homeassistant/components/brottsplatskartan/sensor.py | 5 ++--- homeassistant/components/buienradar/sensor.py | 5 ++--- homeassistant/components/canary/sensor.py | 3 ++- homeassistant/components/cert_expiry/sensor.py | 4 ++-- homeassistant/components/citybikes/sensor.py | 10 +++++++--- homeassistant/components/co2signal/sensor.py | 5 ++--- homeassistant/components/coinbase/sensor.py | 6 +++--- .../components/comed_hourly_pricing/sensor.py | 5 ++--- homeassistant/components/comfoconnect/sensor.py | 5 ++--- homeassistant/components/command_line/sensor.py | 5 ++--- homeassistant/components/coronavirus/sensor.py | 3 ++- homeassistant/components/cpuspeed/sensor.py | 5 ++--- homeassistant/components/cups/sensor.py | 9 ++++----- homeassistant/components/currencylayer/sensor.py | 5 ++--- homeassistant/components/sensor/__init__.py | 5 +++++ 60 files changed, 139 insertions(+), 139 deletions(-) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 6ecc5c871cd090..993228c36acfd8 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -1,6 +1,7 @@ """Support for Abode Security System sensors.""" import abodepy.helpers.constants as CONST +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, @@ -33,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeSensor(AbodeDevice): +class AbodeSensor(SensorEntity, AbodeDevice): """A sensor implementation for Abode devices.""" def __init__(self, data, device, sensor_type): diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index aa7f604e27f801..89147cc7104650 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -1,4 +1,5 @@ """Support for the AccuWeather service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, @@ -48,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AccuWeatherSensor(CoordinatorEntity): +class AccuWeatherSensor(SensorEntity, CoordinatorEntity): """Define an AccuWeather entity.""" def __init__(self, name, kind, coordinator, forecast_day=None): diff --git a/homeassistant/components/acmeda/sensor.py b/homeassistant/components/acmeda/sensor.py index f427548ab94f95..f1961b85589678 100644 --- a/homeassistant/components/acmeda/sensor.py +++ b/homeassistant/components/acmeda/sensor.py @@ -1,4 +1,5 @@ """Support for Acmeda Roller Blind Batteries.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -29,7 +30,7 @@ def async_add_acmeda_sensors(): ) -class AcmedaBattery(AcmedaBase): +class AcmedaBattery(SensorEntity, AcmedaBase): """Representation of a Acmeda cover device.""" device_class = DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 3a93d9fbb353e3..6f2f0f54bebcf1 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -6,6 +6,7 @@ from adguardhome import AdGuardHome, AdGuardHomeConnectionError +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS from homeassistant.core import HomeAssistant @@ -48,7 +49,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AdGuardHomeSensor(AdGuardHomeDeviceEntity): +class AdGuardHomeSensor(SensorEntity, AdGuardHomeDeviceEntity): """Defines a AdGuard Home sensor.""" def __init__( diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 2f411b277236e0..6e7c69d3c2d69c 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -2,7 +2,7 @@ import voluptuous as vol from homeassistant.components import ads -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([entity]) -class AdsSensor(AdsEntity): +class AdsSensor(SensorEntity, AdsEntity): """Representation of an ADS sensor entity.""" def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor): diff --git a/homeassistant/components/advantage_air/sensor.py b/homeassistant/components/advantage_air/sensor.py index d25ce4888fccdf..cef70ebb245ebb 100644 --- a/homeassistant/components/advantage_air/sensor.py +++ b/homeassistant/components/advantage_air/sensor.py @@ -1,6 +1,7 @@ """Sensor platform for Advantage Air integration.""" import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE from homeassistant.helpers import config_validation as cv, entity_platform @@ -40,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AdvantageAirTimeTo(AdvantageAirEntity): +class AdvantageAirTimeTo(SensorEntity, AdvantageAirEntity): """Representation of Advantage Air timer control.""" def __init__(self, instance, ac_key, action): @@ -82,7 +83,7 @@ async def set_time_to(self, **kwargs): await self.async_change({self.ac_key: {"info": {self._time_key: value}}}) -class AdvantageAirZoneVent(AdvantageAirEntity): +class AdvantageAirZoneVent(SensorEntity, AdvantageAirEntity): """Representation of Advantage Air Zone Vent Sensor.""" @property @@ -115,7 +116,7 @@ def icon(self): return "mdi:fan-off" -class AdvantageAirZoneSignal(AdvantageAirEntity): +class AdvantageAirZoneSignal(SensorEntity, AdvantageAirEntity): """Representation of Advantage Air Zone wireless signal sensor.""" @property diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index b57de1ce89029e..400952830cc66b 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -1,4 +1,6 @@ """Support for the AEMET OpenData service.""" +from homeassistant.components.sensor import SensorEntity + from .abstract_aemet_sensor import AbstractAemetSensor from .const import ( DOMAIN, @@ -56,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AemetSensor(AbstractAemetSensor): +class AemetSensor(SensorEntity, AbstractAemetSensor): """Implementation of an AEMET OpenData sensor.""" def __init__( @@ -79,7 +81,7 @@ def state(self): return self._weather_coordinator.data.get(self._sensor_type) -class AemetForecastSensor(AbstractAemetSensor): +class AemetForecastSensor(SensorEntity, AbstractAemetSensor): """Implementation of an AEMET OpenData forecast sensor.""" def __init__( diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 754b3ce72eb464..a5ffc511a26c0e 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -5,12 +5,11 @@ from pyaftership.tracker import Tracking import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME, HTTP_OK from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import DOMAIN @@ -108,7 +107,7 @@ async def handle_remove_tracking(call): ) -class AfterShipSensor(Entity): +class AfterShipSensor(SensorEntity): """Representation of a AfterShip sensor.""" def __init__(self, aftership, name): diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index dae167559e0ff5..efadeac94e1df9 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -1,4 +1,5 @@ """Support for the Airly sensor service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, @@ -72,7 +73,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirlySensor(CoordinatorEntity): +class AirlySensor(SensorEntity, CoordinatorEntity): """Define an Airly sensor.""" def __init__(self, coordinator, name, kind): diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index ea2ff9856fb1d9..f8f5eff6cc759b 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -1,4 +1,5 @@ """Support for the AirNow sensor service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, @@ -59,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirNowSensor(CoordinatorEntity): +class AirNowSensor(SensorEntity, CoordinatorEntity): """Define an AirNow sensor.""" def __init__(self, coordinator, kind): diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 3c1aef128abcdc..c4cbf3af22ec85 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -1,4 +1,5 @@ """Support for AirVisual air quality sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -138,7 +139,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, True) -class AirVisualGeographySensor(AirVisualEntity): +class AirVisualGeographySensor(SensorEntity, AirVisualEntity): """Define an AirVisual sensor related to geography data via the Cloud API.""" def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale): @@ -236,7 +237,7 @@ def update_from_latest_data(self): self._attrs.pop(ATTR_LONGITUDE, None) -class AirVisualNodeProSensor(AirVisualEntity): +class AirVisualNodeProSensor(SensorEntity, AirVisualEntity): """Define an AirVisual sensor related to a Node/Pro unit.""" def __init__(self, coordinator, kind, name, device_class, unit): diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index a8aed8dac73c1c..80b9c1261a3b6f 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -1,6 +1,6 @@ """Support for AlarmDecoder sensors (Shows Panel Display).""" +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .const import SIGNAL_PANEL_MESSAGE @@ -16,7 +16,7 @@ async def async_setup_entry( return True -class AlarmDecoderSensor(Entity): +class AlarmDecoderSensor(SensorEntity): """Representation of an AlarmDecoder keypad.""" def __init__(self): diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index cd161b1870c10e..0788772a45b58a 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -6,10 +6,9 @@ from alpha_vantage.timeseries import TimeSeries import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_CURRENCY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -105,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.debug("Setup completed") -class AlphaVantageSensor(Entity): +class AlphaVantageSensor(SensorEntity): """Representation of a Alpha Vantage sensor.""" def __init__(self, timeseries, symbol): @@ -156,7 +155,7 @@ def update(self): _LOGGER.debug("Received new values for symbol %s", self._symbol) -class AlphaVantageForeignExchange(Entity): +class AlphaVantageForeignExchange(SensorEntity): """Sensor for foreign exchange rates.""" def __init__(self, foreign_exchange, config): diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 184927a1ef5414..7e8f3d26ec6bea 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,5 +1,6 @@ """Support for Ambient Weather Station sensors.""" from homeassistant.components.binary_sensor import DOMAIN as SENSOR +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -36,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensor_list, True) -class AmbientWeatherSensor(AmbientWeatherEntity): +class AmbientWeatherSensor(SensorEntity, AmbientWeatherEntity): """Define an Ambient sensor.""" def __init__( diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 15a610b23b4ec9..a30de62494e52e 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -4,9 +4,9 @@ from amcrest import AmcrestError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, CONF_SENSORS, PERCENTAGE from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DATA_AMCREST, DEVICES, SENSOR_SCAN_INTERVAL_SECS, SERVICE_UPDATE from .helpers import log_update_error, service_signal @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class AmcrestSensor(Entity): +class AmcrestSensor(SensorEntity): """A sensor implementation for Amcrest IP camera.""" def __init__(self, name, device, sensor_type): diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index 05c1fe16c61c2d..de029c39abaa50 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -1,4 +1,5 @@ """Support for Android IP Webcam sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.icon import icon_for_battery_level from . import ( @@ -30,7 +31,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class IPWebcamSensor(AndroidIPCamEntity): +class IPWebcamSensor(SensorEntity, AndroidIPCamEntity): """Representation of a IP Webcam sensor.""" def __init__(self, name, host, ipcam, sensor): diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 55e6d8c7a569a9..36dc1155b7fd7b 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -4,7 +4,7 @@ from apcaccess.status import ALL_UNITS import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_RESOURCES, ELECTRICAL_CURRENT_AMPERE, @@ -18,7 +18,6 @@ VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -156,7 +155,7 @@ def infer_unit(value): return value, None -class APCUPSdSensor(Entity): +class APCUPSdSensor(SensorEntity): """Representation of a sensor entity for APCUPSd status values.""" def __init__(self, data, sensor_type): diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index f92cd11c0118f8..315b039f778af9 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -2,7 +2,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, PERCENTAGE, @@ -12,7 +12,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN, UPDATE_TOPIC @@ -56,7 +55,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class AquaLogicSensor(Entity): +class AquaLogicSensor(SensorEntity): """Sensor implementation for the AquaLogic component.""" def __init__(self, processor, sensor_type): diff --git a/homeassistant/components/arduino/sensor.py b/homeassistant/components/arduino/sensor.py index f31272b3a2014e..588a652660aad0 100644 --- a/homeassistant/components/arduino/sensor.py +++ b/homeassistant/components/arduino/sensor.py @@ -1,10 +1,9 @@ """Support for getting information from Arduino pins.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -30,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ArduinoSensor(Entity): +class ArduinoSensor(SensorEntity): """Representation of an Arduino Sensor.""" def __init__(self, name, pin, pin_type, board): diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index d213a3d2903e15..061c15eafb04bb 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -16,7 +16,6 @@ ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -124,7 +123,7 @@ def _render(value): add_entities(dev, True) -class ArestSensor(Entity): +class ArestSensor(SensorEntity): """Implementation of an aREST sensor for exposed variables.""" def __init__( diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index 1e0c55c5d313cd..c794bf1ef5e751 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONCENTRATION_PARTS_PER_MILLION, @@ -16,7 +16,6 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from . import ATTRIBUTION, DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO @@ -73,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ArloSensor(Entity): +class ArloSensor(SensorEntity): """An implementation of a Netgear Arlo IP sensor.""" def __init__(self, name, device, sensor_type): diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index d68549ea95cee8..ba9166d1af585e 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -3,9 +3,9 @@ import logging from homeassistant.components import mqtt +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEGREE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) @@ -114,7 +114,7 @@ def async_sensor_event_received(msg): return True -class ArwnSensor(Entity): +class ArwnSensor(SensorEntity): """Representation of an ARWN sensor.""" def __init__(self, topic, name, state_key, units, icon=None): diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 7dc8208ee679a3..30f4163d01bfba 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -4,6 +4,7 @@ import logging from numbers import Number +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND from homeassistant.helpers.typing import HomeAssistantType @@ -97,7 +98,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class AsusWrtSensor(CoordinatorEntity): +class AsusWrtSensor(SensorEntity, CoordinatorEntity): """Representation of a AsusWrt sensor.""" def __init__( diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index 3123e245711d89..163ed669be451f 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -1,4 +1,5 @@ """Initialization of ATAG One sensor platform.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS]) -class AtagSensor(AtagEntity): +class AtagSensor(SensorEntity, AtagEntity): """Representation of a AtagOne Sensor.""" def __init__(self, coordinator, sensor): diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index e50f7cd5de6504..d10024f64c244f 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -5,7 +5,7 @@ from pyatome.client import AtomeClient, PyAtomeError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -15,7 +15,6 @@ POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -215,7 +214,7 @@ def update_year_usage(self): _LOGGER.error("Missing last value in values: %s: %s", values, error) -class AtomeSensor(Entity): +class AtomeSensor(SensorEntity): """Representation of a sensor entity for Atome.""" def __init__(self, data, name, sensor_type): diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 1841b2cef569f7..9b8352a0b9603c 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -3,10 +3,9 @@ from yalexs.activity import ActivityType -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity from homeassistant.const import ATTR_ENTITY_PICTURE, PERCENTAGE, STATE_UNAVAILABLE from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.restore_state import RestoreEntity @@ -118,7 +117,7 @@ async def _async_migrate_old_unique_ids(hass, devices): registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id) -class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, Entity): +class AugustOperatorSensor(SensorEntity, AugustEntityMixin, RestoreEntity): """Representation of an August lock operation sensor.""" def __init__(self, data, device): @@ -217,7 +216,7 @@ def unique_id(self) -> str: return f"{self._device_id}_lock_operator" -class AugustBatterySensor(AugustEntityMixin, Entity): +class AugustBatterySensor(SensorEntity, AugustEntityMixin): """Representation of an August sensor.""" def __init__(self, data, sensor_type, device, old_device): diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py index 731c6d08afd499..1a34bb604daf72 100644 --- a/homeassistant/components/aurora/sensor.py +++ b/homeassistant/components/aurora/sensor.py @@ -1,4 +1,5 @@ """Support for Aurora Forecast sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE from . import AuroraEntity @@ -18,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entries): async_add_entries([entity]) -class AuroraSensor(AuroraEntity): +class AuroraSensor(SensorEntity, AuroraEntity): """Implementation of an aurora sensor.""" @property diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 69a513dd8fb5ed..f4640e7c014229 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -5,7 +5,7 @@ from aurorapy.client import AuroraError, AuroraSerialClient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE, @@ -14,7 +14,6 @@ POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class AuroraABBSolarPVMonitorSensor(Entity): +class AuroraABBSolarPVMonitorSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, client, name, typename): diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 33ecaf4666090f..de39c20eb6029b 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.awair import AwairDataUpdateCoordinator, AwairResult -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS, CONF_ACCESS_TOKEN from homeassistant.helpers import device_registry as dr @@ -84,7 +84,7 @@ async def async_setup_entry( async_add_entities(sensors) -class AwairSensor(CoordinatorEntity): +class AwairSensor(SensorEntity, CoordinatorEntity): """Defines an Awair sensor entity.""" def __init__( diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 1d30bfcb9f90da..a0701ac9839367 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -17,6 +17,7 @@ DATA_PROJECT, DOMAIN, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType @@ -55,7 +56,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AzureDevOpsSensor(AzureDevOpsDeviceEntity): +class AzureDevOpsSensor(SensorEntity, AzureDevOpsDeviceEntity): """Defines a Azure DevOps sensor.""" def __init__( diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 8d6708c6342a17..5256c2a61a0787 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -6,7 +6,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_MONITORED_VARIABLES, @@ -15,7 +15,6 @@ DEVICE_CLASS_TIMESTAMP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow @@ -86,7 +85,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class BboxUptimeSensor(Entity): +class BboxUptimeSensor(SensorEntity): """Bbox uptime sensor.""" def __init__(self, bbox_data, sensor_type, name): @@ -133,7 +132,7 @@ def update(self): self._state = uptime.replace(microsecond=0).isoformat() -class BboxSensor(Entity): +class BboxSensor(SensorEntity): """Implementation of a Bbox sensor.""" def __init__(self, bbox_data, sensor_type, name): diff --git a/homeassistant/components/beewi_smartclim/sensor.py b/homeassistant/components/beewi_smartclim/sensor.py index e8a37d51be4a1d..9bf935f3c4f5c4 100644 --- a/homeassistant/components/beewi_smartclim/sensor.py +++ b/homeassistant/components/beewi_smartclim/sensor.py @@ -2,7 +2,7 @@ from beewi_smartclim import BeewiSmartClimPoller # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MAC, CONF_NAME, @@ -13,7 +13,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity # Default values DEFAULT_NAME = "BeeWi SmartClim" @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class BeewiSmartclimSensor(Entity): +class BeewiSmartclimSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, poller, name, mac, device, unit): diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index eb9bbc2dcccf2c..5b708ae2630747 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -6,10 +6,9 @@ import smbus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -94,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class BH1750Sensor(Entity): +class BH1750Sensor(SensorEntity): """Implementation of the BH1750 sensor.""" def __init__(self, bh1750_sensor, name, unit, multiplier=1.0): diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index f6f5a24d22ffa9..4acce03d6faaf1 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -5,7 +5,7 @@ from blockchain import exchangerates, statistics import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_CURRENCY, @@ -14,7 +14,6 @@ TIME_SECONDS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -77,7 +76,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class BitcoinSensor(Entity): +class BitcoinSensor(SensorEntity): """Representation of a Bitcoin sensor.""" def __init__(self, data, option_type, currency): diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index c1cee98208c936..a905498328b030 100644 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -2,10 +2,9 @@ from bizkaibus.bizkaibus import BizkaibusData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_DUE_IN = "Due in" @@ -33,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BizkaibusSensor(data, stop, route, name)], True) -class BizkaibusSensor(Entity): +class BizkaibusSensor(SensorEntity): """The class for handling the data.""" def __init__(self, data, stop, route, name): diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 84f4c19371df41..688d2931461204 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -1,6 +1,6 @@ """BleBox sensor entities.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP @@ -14,7 +14,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class BleBoxSensorEntity(BleBoxEntity, Entity): +class BleBoxSensorEntity(SensorEntity, BleBoxEntity): """Representation of a BleBox sensor feature.""" @property diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index 3c3adf6d990e6d..1ec61900091d42 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,13 +1,13 @@ """Support for Blink system camera sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from .const import DOMAIN, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH @@ -34,7 +34,7 @@ async def async_setup_entry(hass, config, async_add_entities): async_add_entities(entities) -class BlinkSensor(Entity): +class BlinkSensor(SensorEntity): """A Blink camera sensor.""" def __init__(self, data, camera, sensor_type): diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py index 790051dad96367..3ecf4bee3190a1 100644 --- a/homeassistant/components/blockchain/sensor.py +++ b/homeassistant/components/blockchain/sensor.py @@ -5,10 +5,9 @@ from pyblockchain import get_balance, validate_address import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BlockchainSensor(name, addresses)], True) -class BlockchainSensor(Entity): +class BlockchainSensor(SensorEntity): """Representation of a Blockchain.com sensor.""" def __init__(self, name, addresses): diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index df06e39db7e96e..4dc52e1a85cf5f 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -1,7 +1,7 @@ """Support the sensor of a BloomSky weather station.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( AREA_SQUARE_METERS, CONF_MONITORED_CONDITIONS, @@ -12,7 +12,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -70,7 +69,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BloomSkySensor(bloomsky, device, variable)], True) -class BloomSkySensor(Entity): +class BloomSkySensor(SensorEntity): """Representation of a single sensor in a BloomSky device.""" def __init__(self, bs, device, sensor_name): diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index d3d97881035c8b..ffe6847934195f 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -7,7 +7,7 @@ import smbus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -15,7 +15,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -136,7 +135,7 @@ def update(self, first_reading=False): self.sensor.update(first_reading) -class BME280Sensor(Entity): +class BME280Sensor(SensorEntity): """Implementation of the BME280 sensor.""" def __init__(self, bme280_client, sensor_type, temp_unit, name): diff --git a/homeassistant/components/bme680/sensor.py b/homeassistant/components/bme680/sensor.py index 6af9d46c0bcedc..f3d6b9428ea359 100644 --- a/homeassistant/components/bme680/sensor.py +++ b/homeassistant/components/bme680/sensor.py @@ -7,7 +7,7 @@ from smbus import SMBus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -15,7 +15,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util.temperature import celsius_to_fahrenheit _LOGGER = logging.getLogger(__name__) @@ -316,7 +315,7 @@ def _calculate_aq_score(self): return hum_score + gas_score -class BME680Sensor(Entity): +class BME680Sensor(SensorEntity): """Implementation of the BME680 sensor.""" def __init__(self, bme680_client, sensor_type, temp_unit, name): diff --git a/homeassistant/components/bmp280/sensor.py b/homeassistant/components/bmp280/sensor.py index 3c34408cb62662..60cbdb75d41fef 100644 --- a/homeassistant/components/bmp280/sensor.py +++ b/homeassistant/components/bmp280/sensor.py @@ -11,11 +11,11 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import CONF_NAME, PRESSURE_HPA, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -65,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class Bmp280Sensor(Entity): +class Bmp280Sensor(SensorEntity): """Base class for BMP280 entities.""" def __init__( diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 480aac34eb3e26..5c85312d959b4d 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -3,6 +3,7 @@ from bimmer_connected.state import ChargingState +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, @@ -12,7 +13,6 @@ VOLUME_GALLONS, VOLUME_LITERS, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from . import DOMAIN as BMW_DOMAIN, BMWConnectedDriveBaseEntity @@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, Entity): +class BMWConnectedDriveSensor(SensorEntity, BMWConnectedDriveBaseEntity): """Representation of a BMW vehicle sensor.""" def __init__(self, account, vehicle, attribute: str, attribute_info): diff --git a/homeassistant/components/broadlink/sensor.py b/homeassistant/components/broadlink/sensor.py index af350329e8c026..0e42d8c438f652 100644 --- a/homeassistant/components/broadlink/sensor.py +++ b/homeassistant/components/broadlink/sensor.py @@ -8,11 +8,11 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import CONF_HOST, PERCENTAGE, TEMP_CELSIUS from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from .const import DOMAIN from .helpers import import_device @@ -56,7 +56,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class BroadlinkSensor(Entity): +class BroadlinkSensor(SensorEntity): """Representation of a Broadlink sensor.""" def __init__(self, device, monitored_condition): diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index dcbf92fba7df12..10cc3979207f98 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -1,4 +1,5 @@ """Support for the Brother service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -56,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class BrotherPrinterSensor(CoordinatorEntity): +class BrotherPrinterSensor(SensorEntity, CoordinatorEntity): """Define an Brother Printer sensor.""" def __init__(self, coordinator, kind, device_info): diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index b75edcc7f4abd9..32af96dfe604a1 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -7,7 +7,7 @@ import brottsplatskartan import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -15,7 +15,6 @@ CONF_NAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -78,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BrottsplatskartanSensor(bpk, name)], True) -class BrottsplatskartanSensor(Entity): +class BrottsplatskartanSensor(SensorEntity): """Representation of a Brottsplatskartan Sensor.""" def __init__(self, bpk, name): diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index fbfa23f1e74fea..170493969f8d7c 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -20,7 +20,7 @@ ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -39,7 +39,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util from .const import DEFAULT_TIMEFRAME @@ -236,7 +235,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= await data.schedule_update(1) -class BrSensor(Entity): +class BrSensor(SensorEntity): """Representation of an Buienradar sensor.""" def __init__(self, sensor_type, client_name, coordinates): diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 87e40d268bddda..e4c6190ff9901c 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -5,6 +5,7 @@ from canary.api import SensorType +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -77,7 +78,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class CanarySensor(CoordinatorEntity, Entity): +class CanarySensor(SensorEntity, CoordinatorEntity): """Representation of a Canary sensor.""" def __init__(self, coordinator, sensor_type, location, device): diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 582205f9c0de39..bf80f8b8fb57b7 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, @@ -76,7 +76,7 @@ def extra_state_attributes(self): } -class SSLCertificateTimestamp(CertExpiryEntity): +class SSLCertificateTimestamp(SensorEntity, CertExpiryEntity): """Implementation of the Cert Expiry timestamp sensor.""" @property diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index 2dd60078bd5f93..bc323a51151e0d 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -7,7 +7,11 @@ import async_timeout import voluptuous as vol -from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_ID, @@ -25,7 +29,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import distance, location @@ -258,7 +262,7 @@ async def async_refresh(self, now=None): raise PlatformNotReady from err -class CityBikesStation(Entity): +class CityBikesStation(SensorEntity): """CityBikes API Sensor.""" def __init__(self, network, station_id, entity_id): diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index 2153a8a7af5bb0..c7d2a64d6b0b5a 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -4,7 +4,7 @@ import CO2Signal import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -13,7 +13,6 @@ ENERGY_KILO_WATT_HOUR, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity CONF_COUNTRY_CODE = "country_code" @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs, True) -class CO2Sensor(Entity): +class CO2Sensor(SensorEntity): """Implementation of the CO2Signal sensor.""" def __init__(self, token, country_code, lat, lon): diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index 1d239ba457ed1a..e4e4e719c9e25d 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -1,6 +1,6 @@ """Support for Coinbase sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.entity import Entity ATTR_NATIVE_BALANCE = "Balance in native currency" @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor], True) -class AccountSensor(Entity): +class AccountSensor(SensorEntity): """Representation of a Coinbase.com sensor.""" def __init__(self, coinbase_data, name, currency): @@ -88,7 +88,7 @@ def update(self): self._native_currency = account["native_balance"]["currency"] -class ExchangeRateSensor(Entity): +class ExchangeRateSensor(SensorEntity): """Representation of a Coinbase.com sensor.""" def __init__(self, coinbase_data, exchange_currency, native_currency): diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py index c1a784f619ca2f..5d4ec6eec1337b 100644 --- a/homeassistant/components/comed_hourly_pricing/sensor.py +++ b/homeassistant/components/comed_hourly_pricing/sensor.py @@ -8,11 +8,10 @@ import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_OFFSET from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://hourlypricing.comed.com/api" @@ -65,7 +64,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class ComedHourlyPricingSensor(Entity): +class ComedHourlyPricingSensor(SensorEntity): """Implementation of a ComEd Hourly Pricing sensor.""" def __init__(self, loop, websession, sensor_type, offset, name): diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 87fa8f4a1a6edb..728bc13b76bdbb 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -26,7 +26,7 @@ ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -45,7 +45,6 @@ ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import DOMAIN, SIGNAL_COMFOCONNECT_UPDATE_RECEIVED, ComfoConnectBridge @@ -258,7 +257,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ComfoConnectSensor(Entity): +class ComfoConnectSensor(SensorEntity): """Representation of a ComfoConnect sensor.""" def __init__(self, name, ccb: ComfoConnectBridge, sensor_type) -> None: diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index fc99befb821208..10c5a16f60b37f 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_COMMAND, CONF_NAME, @@ -17,7 +17,6 @@ from homeassistant.exceptions import TemplateError from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.reload import setup_reload_service from . import check_output_or_log @@ -63,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class CommandSensor(Entity): +class CommandSensor(SensorEntity): """Representation of a sensor that is using shell commands.""" def __init__( diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index acfc5569f342ac..58e6760a85d4c7 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the Corona virus.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -23,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class CoronavirusSensor(CoordinatorEntity): +class CoronavirusSensor(SensorEntity, CoordinatorEntity): """Sensor representing corona virus data.""" name = None diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index f21513f616e6ee..019383446944fa 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -2,10 +2,9 @@ from cpuinfo import cpuinfo import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, FREQUENCY_GIGAHERTZ import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_BRAND = "brand" ATTR_HZ = "ghz_advertised" @@ -29,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([CpuSpeedSensor(name)], True) -class CpuSpeedSensor(Entity): +class CpuSpeedSensor(SensorEntity): """Representation of a CPU sensor.""" def __init__(self, name): diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 54e674b1811f18..6a3fc7b4215fc0 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -5,11 +5,10 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_PORT, PERCENTAGE from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -96,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class CupsSensor(Entity): +class CupsSensor(SensorEntity): """Representation of a CUPS sensor.""" def __init__(self, data, printer): @@ -155,7 +154,7 @@ def update(self): self._available = self.data.available -class IPPSensor(Entity): +class IPPSensor(SensorEntity): """Implementation of the IPPSensor. This sensor represents the status of the printer. @@ -232,7 +231,7 @@ def update(self): self._available = self.data.available -class MarkerSensor(Entity): +class MarkerSensor(SensorEntity): """Implementation of the MarkerSensor. This sensor represents the percentage of ink or toner. diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index 66b26eb0692edc..f42534f509b6c4 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -14,7 +14,6 @@ CONF_QUOTE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "http://apilayer.net/api/live" @@ -55,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class CurrencylayerSensor(Entity): +class CurrencylayerSensor(SensorEntity): """Implementing the Currencylayer sensor.""" def __init__(self, rest, base, quote): diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 187cb0c410d477..0012b1a3aa28cd 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -25,6 +25,7 @@ PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) +from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent # mypy: allow-untyped-defs, no-check-untyped-defs @@ -74,3 +75,7 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" return await hass.data[DOMAIN].async_unload_entry(entry) + + +class SensorEntity(Entity): + """Base class for sensor entities.""" From 23b562386f798c23bd45b343f44fdb8226aba468 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 12:52:29 +0100 Subject: [PATCH 1436/1818] Migrate integrations d-e to extend SensorEntity (#48211) --- homeassistant/components/daikin/sensor.py | 4 ++-- homeassistant/components/danfoss_air/sensor.py | 4 ++-- homeassistant/components/darksky/sensor.py | 11 +++++++---- homeassistant/components/deconz/sensor.py | 6 +++--- homeassistant/components/delijn/sensor.py | 5 ++--- homeassistant/components/deluge/sensor.py | 5 ++--- homeassistant/components/demo/sensor.py | 4 ++-- homeassistant/components/derivative/sensor.py | 4 ++-- homeassistant/components/deutsche_bahn/sensor.py | 5 ++--- .../components/devolo_home_control/sensor.py | 3 ++- homeassistant/components/dexcom/sensor.py | 5 +++-- homeassistant/components/dht/sensor.py | 5 ++--- homeassistant/components/discogs/sensor.py | 5 ++--- homeassistant/components/dnsip/sensor.py | 5 ++--- homeassistant/components/dovado/sensor.py | 5 ++--- homeassistant/components/dsmr/sensor.py | 5 ++--- homeassistant/components/dsmr_reader/sensor.py | 4 ++-- homeassistant/components/dte_energy_bridge/sensor.py | 5 ++--- .../components/dublin_bus_transport/sensor.py | 5 ++--- .../components/dwd_weather_warnings/sensor.py | 5 ++--- homeassistant/components/dweet/sensor.py | 5 ++--- homeassistant/components/dyson/sensor.py | 4 ++-- homeassistant/components/eafm/sensor.py | 3 ++- homeassistant/components/ebox/sensor.py | 5 ++--- homeassistant/components/ebusd/sensor.py | 4 ++-- homeassistant/components/ecoal_boiler/sensor.py | 4 ++-- homeassistant/components/ecobee/sensor.py | 4 ++-- homeassistant/components/econet/sensor.py | 3 ++- .../components/eddystone_temperature/sensor.py | 5 ++--- homeassistant/components/edl21/sensor.py | 5 ++--- homeassistant/components/efergy/sensor.py | 5 ++--- homeassistant/components/eight_sleep/sensor.py | 7 ++++--- homeassistant/components/eliqonline/sensor.py | 5 ++--- homeassistant/components/elkm1/sensor.py | 3 ++- homeassistant/components/emoncms/sensor.py | 5 ++--- homeassistant/components/enocean/sensor.py | 4 ++-- homeassistant/components/enphase_envoy/sensor.py | 4 ++-- .../components/entur_public_transport/sensor.py | 5 ++--- homeassistant/components/environment_canada/sensor.py | 5 ++--- homeassistant/components/envirophat/sensor.py | 5 ++--- homeassistant/components/envisalink/sensor.py | 4 ++-- homeassistant/components/epsonworkforce/sensor.py | 5 ++--- homeassistant/components/esphome/sensor.py | 6 +++--- homeassistant/components/essent/sensor.py | 5 ++--- homeassistant/components/etherscan/sensor.py | 5 ++--- 45 files changed, 100 insertions(+), 115 deletions(-) diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index 80de4ed34a5fe0..a5b515ea918946 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -1,4 +1,5 @@ """Support for Daikin AC sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ICON, @@ -6,7 +7,6 @@ CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN as DAIKIN_DOMAIN, DaikinApi from .const import ( @@ -49,7 +49,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities([DaikinSensor.factory(daikin_api, sensor) for sensor in sensors]) -class DaikinSensor(Entity): +class DaikinSensor(SensorEntity): """Representation of a Sensor.""" @staticmethod diff --git a/homeassistant/components/danfoss_air/sensor.py b/homeassistant/components/danfoss_air/sensor.py index 251c9692021dde..792a95e8ac46f1 100644 --- a/homeassistant/components/danfoss_air/sensor.py +++ b/homeassistant/components/danfoss_air/sensor.py @@ -3,6 +3,7 @@ from pydanfossair.commands import ReadCommand +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, @@ -10,7 +11,6 @@ PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN as DANFOSS_AIR_DOMAIN @@ -77,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DanfossAir(Entity): +class DanfossAir(SensorEntity): """Representation of a Sensor.""" def __init__(self, data, name, sensor_unit, sensor_type, device_class): diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index 7e6463dc08e288..058969d96f9574 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -6,7 +6,11 @@ from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DEVICE_CLASS_TEMPERATURE, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -29,7 +33,6 @@ UV_INDEX, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -544,7 +547,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class DarkSkySensor(Entity): +class DarkSkySensor(SensorEntity): """Implementation of a Dark Sky sensor.""" def __init__( @@ -708,7 +711,7 @@ def get_state(self, data): return state -class DarkSkyAlertSensor(Entity): +class DarkSkyAlertSensor(SensorEntity): """Implementation of a Dark Sky sensor.""" def __init__(self, forecast_data, sensor_type, name): diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index b08ec0d091b289..8f23b47a1b9792 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -12,7 +12,7 @@ Thermostat, ) -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_VOLTAGE, @@ -122,7 +122,7 @@ def async_add_sensor(sensors=gateway.api.sensors.values()): ) -class DeconzSensor(DeconzDevice): +class DeconzSensor(SensorEntity, DeconzDevice): """Representation of a deCONZ sensor.""" TYPE = DOMAIN @@ -186,7 +186,7 @@ def extra_state_attributes(self): return attr -class DeconzBattery(DeconzDevice): +class DeconzBattery(SensorEntity, DeconzDevice): """Battery class for when a device is only represented as an event.""" TYPE = DOMAIN diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 44b70c4e7a86d0..cff93c899541e4 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -5,11 +5,10 @@ from pydelijn.common import HttpException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class DeLijnPublicTransportSensor(Entity): +class DeLijnPublicTransportSensor(SensorEntity): """Representation of a Ruter sensor.""" def __init__(self, line): diff --git a/homeassistant/components/deluge/sensor.py b/homeassistant/components/deluge/sensor.py index 5e8df89c20d748..0c79e6f835e8c3 100644 --- a/homeassistant/components/deluge/sensor.py +++ b/homeassistant/components/deluge/sensor.py @@ -4,7 +4,7 @@ from deluge_client import DelugeRPCClient, FailedToReconnectException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -17,7 +17,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _THROTTLED_REFRESH = None @@ -68,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class DelugeSensor(Entity): +class DelugeSensor(SensorEntity): """Representation of a Deluge sensor.""" def __init__(self, sensor_type, deluge_client, client_name): diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 9c005d2d7f5d2c..7607bad4e1ce72 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,4 +1,5 @@ """Demo platform that has a couple of fake sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONCENTRATION_PARTS_PER_MILLION, @@ -9,7 +10,6 @@ PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -59,7 +59,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoSensor(Entity): +class DemoSensor(SensorEntity): """Representation of a Demo sensor.""" def __init__( diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index ddbe1d19e31239..72d0186d94d954 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -4,7 +4,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -86,7 +86,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([derivative]) -class DerivativeSensor(RestoreEntity): +class DerivativeSensor(SensorEntity, RestoreEntity): """Representation of an derivative sensor.""" def __init__( diff --git a/homeassistant/components/deutsche_bahn/sensor.py b/homeassistant/components/deutsche_bahn/sensor.py index 1de6609c756c5e..33fd9a8224fab0 100644 --- a/homeassistant/components/deutsche_bahn/sensor.py +++ b/homeassistant/components/deutsche_bahn/sensor.py @@ -4,10 +4,9 @@ import schiene import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_OFFSET import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_DESTINATION = "to" @@ -40,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DeutscheBahnSensor(start, destination, offset, only_direct)], True) -class DeutscheBahnSensor(Entity): +class DeutscheBahnSensor(SensorEntity): """Implementation of a Deutsche Bahn sensor.""" def __init__(self, start, goal, offset, only_direct): diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index 9fa3bdcf8093c5..f125448b4cc6ab 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -6,6 +6,7 @@ DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE @@ -65,7 +66,7 @@ async def async_setup_entry( async_add_entities(entities, False) -class DevoloMultiLevelDeviceEntity(DevoloDeviceEntity): +class DevoloMultiLevelDeviceEntity(SensorEntity, DevoloDeviceEntity): """Abstract representation of a multi level sensor within devolo Home Control.""" @property diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index afb3feeec4d050..09c7ff4e051ca2 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -1,4 +1,5 @@ """Support for Dexcom sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -16,7 +17,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class DexcomGlucoseValueSensor(CoordinatorEntity): +class DexcomGlucoseValueSensor(SensorEntity, CoordinatorEntity): """Representation of a Dexcom glucose value sensor.""" def __init__(self, coordinator, username, unit_of_measurement): @@ -58,7 +59,7 @@ def unique_id(self): return self._unique_id -class DexcomGlucoseTrendSensor(CoordinatorEntity): +class DexcomGlucoseTrendSensor(SensorEntity, CoordinatorEntity): """Representation of a Dexcom glucose trend sensor.""" def __init__(self, coordinator, username): diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index 0bddd5a187eeab..c1fa32e71d119a 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -5,7 +5,7 @@ import Adafruit_DHT # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -14,7 +14,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -93,7 +92,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DHTSensor(Entity): +class DHTSensor(SensorEntity): """Implementation of the DHT sensor.""" def __init__( diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py index 6f45a4ab959874..81beec0e60ebd4 100644 --- a/homeassistant/components/discogs/sensor.py +++ b/homeassistant/components/discogs/sensor.py @@ -6,7 +6,7 @@ import discogs_client import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, @@ -15,7 +15,6 @@ ) from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -89,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class DiscogsSensor(Entity): +class DiscogsSensor(SensorEntity): """Create a new Discogs sensor for a specific type.""" def __init__(self, discogs_data, name, sensor_type): diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py index b202ff8485c794..01d6e2f4f2aa90 100644 --- a/homeassistant/components/dnsip/sensor.py +++ b/homeassistant/components/dnsip/sensor.py @@ -6,10 +6,9 @@ from aiodns.error import DNSError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -55,7 +54,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N async_add_devices([WanIpSensor(hass, name, hostname, resolver, ipv6)], True) -class WanIpSensor(Entity): +class WanIpSensor(SensorEntity): """Implementation of a DNS IP sensor.""" def __init__(self, hass, name, hostname, resolver, ipv6): diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 8c8dffbb8b6ef8..e7b3dbdd363084 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -4,10 +4,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_SENSORS, DATA_GIGABYTES, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN as DOVADO_DOMAIN @@ -53,7 +52,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities) -class DovadoSensor(Entity): +class DovadoSensor(SensorEntity): """Representation of a Dovado sensor.""" def __init__(self, data, sensor): diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 0d2c55051e818b..12304fa51d43ec 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -12,7 +12,7 @@ import serial import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_HOST, @@ -22,7 +22,6 @@ ) from homeassistant.core import CoreState, callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle @@ -286,7 +285,7 @@ async def connect_and_reconnect(): hass.data[DOMAIN][entry.entry_id][DATA_TASK] = task -class DSMREntity(Entity): +class DSMREntity(SensorEntity): """Entity reading values from DSMR telegram.""" def __init__(self, name, device_name, device_serial, obis, config, force_update): diff --git a/homeassistant/components/dsmr_reader/sensor.py b/homeassistant/components/dsmr_reader/sensor.py index 14234b49dbe585..0ee5932c1bbd17 100644 --- a/homeassistant/components/dsmr_reader/sensor.py +++ b/homeassistant/components/dsmr_reader/sensor.py @@ -1,7 +1,7 @@ """Support for DSMR Reader through MQTT.""" from homeassistant.components import mqtt +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from .definitions import DEFINITIONS @@ -19,7 +19,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class DSMRSensor(Entity): +class DSMRSensor(SensorEntity): """Representation of a DSMR sensor that is updated via MQTT.""" def __init__(self, topic): diff --git a/homeassistant/components/dte_energy_bridge/sensor.py b/homeassistant/components/dte_energy_bridge/sensor.py index efd00b3da1ef89..27475990de024d 100644 --- a/homeassistant/components/dte_energy_bridge/sensor.py +++ b/homeassistant/components/dte_energy_bridge/sensor.py @@ -4,10 +4,9 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, HTTP_OK import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DteEnergyBridgeSensor(ip_address, name, version)], True) -class DteEnergyBridgeSensor(Entity): +class DteEnergyBridgeSensor(SensorEntity): """Implementation of the DTE Energy Bridge sensors.""" def __init__(self, ip_address, name, version): diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 5b0cbaf9f189e6..909eed09b9aa30 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -9,10 +9,9 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, HTTP_OK, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _RESOURCE = "https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation" @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DublinPublicTransportSensor(data, stop, route, name)], True) -class DublinPublicTransportSensor(Entity): +class DublinPublicTransportSensor(SensorEntity): """Implementation of an Dublin public transport sensor.""" def __init__(self, data, stop, route, name): diff --git a/homeassistant/components/dwd_weather_warnings/sensor.py b/homeassistant/components/dwd_weather_warnings/sensor.py index af91e279600cb9..78fa9bd85522d7 100644 --- a/homeassistant/components/dwd_weather_warnings/sensor.py +++ b/homeassistant/components/dwd_weather_warnings/sensor.py @@ -15,10 +15,9 @@ from dwdwfsapi import DwdWeatherWarningsAPI import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -87,7 +86,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class DwdWeatherWarningsSensor(Entity): +class DwdWeatherWarningsSensor(SensorEntity): """Representation of a DWD-Weather-Warnings sensor.""" def __init__(self, api, name, sensor_type): diff --git a/homeassistant/components/dweet/sensor.py b/homeassistant/components/dweet/sensor.py index f3f604ff3692bf..f1243cd540771b 100644 --- a/homeassistant/components/dweet/sensor.py +++ b/homeassistant/components/dweet/sensor.py @@ -6,7 +6,7 @@ import dweepy import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_NAME, @@ -14,7 +14,6 @@ CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DweetSensor(hass, dweet, name, value_template, unit)], True) -class DweetSensor(Entity): +class DweetSensor(SensorEntity): """Representation of a Dweet sensor.""" def __init__(self, hass, dweet, name, value_template, unit_of_measurement): diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index 80a64e787f0d21..356bfc61cb248e 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -2,6 +2,7 @@ from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool_link import DysonPureCoolLink +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -13,7 +14,6 @@ TEMP_CELSIUS, TIME_HOURS, ) -from homeassistant.helpers.entity import Entity from . import DYSON_DEVICES, DysonEntity @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class DysonSensor(DysonEntity, Entity): +class DysonSensor(SensorEntity, DysonEntity): """Representation of a generic Dyson sensor.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/eafm/sensor.py b/homeassistant/components/eafm/sensor.py index 9e2d4e5cef9d36..8fa2a47c1578ec 100644 --- a/homeassistant/components/eafm/sensor.py +++ b/homeassistant/components/eafm/sensor.py @@ -5,6 +5,7 @@ from aioeafm import get_station import async_timeout +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_METERS from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import ( @@ -77,7 +78,7 @@ async def async_update_data(): await coordinator.async_refresh() -class Measurement(CoordinatorEntity): +class Measurement(SensorEntity, CoordinatorEntity): """A gauge at a flood monitoring station.""" attribution = "This uses Environment Agency flood and river level data from the real-time data API" diff --git a/homeassistant/components/ebox/sensor.py b/homeassistant/components/ebox/sensor.py index aa6945bac99dcf..72d169f389eabe 100644 --- a/homeassistant/components/ebox/sensor.py +++ b/homeassistant/components/ebox/sensor.py @@ -10,7 +10,7 @@ from pyebox.client import PyEboxError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -22,7 +22,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class EBoxSensor(Entity): +class EBoxSensor(SensorEntity): """Implementation of a EBox sensor.""" def __init__(self, ebox_data, sensor_type, name): diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index 53dac4eb4c08d7..00f6a6b2b3e468 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -2,7 +2,7 @@ import datetime import logging -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class EbusdSensor(Entity): +class EbusdSensor(SensorEntity): """Ebusd component sensor methods definition.""" def __init__(self, data, sensor, name): diff --git a/homeassistant/components/ecoal_boiler/sensor.py b/homeassistant/components/ecoal_boiler/sensor.py index 963f547283fd68..e1c9308b5a950e 100644 --- a/homeassistant/components/ecoal_boiler/sensor.py +++ b/homeassistant/components/ecoal_boiler/sensor.py @@ -1,6 +1,6 @@ """Allows reading temperatures from ecoal/esterownik.pl controller.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import AVAILABLE_SENSORS, DATA_ECOAL_BOILER @@ -17,7 +17,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class EcoalTempSensor(Entity): +class EcoalTempSensor(SensorEntity): """Representation of a temperature sensor using ecoal status data.""" def __init__(self, ecoal_contr, name, status_attr): diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index cfbaa7a45161b0..5abe809e59d4bd 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -1,13 +1,13 @@ """Support for Ecobee sensors.""" from pyecobee.const import ECOBEE_STATE_CALIBRATING, ECOBEE_STATE_UNKNOWN +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class EcobeeSensor(Entity): +class EcobeeSensor(SensorEntity): """Representation of an Ecobee sensor.""" def __init__(self, data, sensor_name, sensor_type, sensor_index): diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index e0ef7dc6ce9724..8198eb75045256 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -1,6 +1,7 @@ """Support for Rheem EcoNet water heaters.""" from pyeconet.equipment import EquipmentType +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, PERCENTAGE, @@ -47,7 +48,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class EcoNetSensor(EcoNetEntity): +class EcoNetSensor(SensorEntity, EcoNetEntity): """Define a Econet sensor.""" def __init__(self, econet_device, device_name): diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index f8a7254b6fadf7..70afde4ffb17ca 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -10,7 +10,7 @@ from beacontools import BeaconScanner, EddystoneFilter, EddystoneTLMFrame import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, EVENT_HOMEASSISTANT_START, @@ -19,7 +19,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -97,7 +96,7 @@ def get_from_conf(config, config_key, length): return string -class EddystoneTemp(Entity): +class EddystoneTemp(SensorEntity): """Representation of a temperature sensor.""" def __init__(self, name, namespace, instance): diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index 1f21eaa4004f40..090b2780ec4681 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -7,7 +7,7 @@ from sml.asyncio import SmlProtocol import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -15,7 +15,6 @@ async_dispatcher_connect, async_dispatcher_send, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.helpers.typing import Optional from homeassistant.util.dt import utcnow @@ -193,7 +192,7 @@ async def add_entities(self, new_entities) -> None: self._async_add_entities(new_entities, update_before_add=True) -class EDL21Entity(Entity): +class EDL21Entity(SensorEntity): """Entity reading values from EDL21 telegram.""" def __init__(self, electricity_id, obis, name, telegram): diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index 02bc8fa0ccbd0c..6e2ac1c01c7f49 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -4,7 +4,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_CURRENCY, CONF_MONITORED_VARIABLES, @@ -13,7 +13,6 @@ POWER_WATT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://engage.efergy.com/mobile_proxy/" @@ -94,7 +93,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class EfergySensor(Entity): +class EfergySensor(SensorEntity): """Implementation of an Efergy sensor.""" def __init__(self, sensor_type, app_token, utc_offset, period, currency, sid=None): diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index f858309d02cb9a..f5a0d1b1f8d2d5 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -1,6 +1,7 @@ """Support for Eight Sleep sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT from . import ( @@ -66,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class EightHeatSensor(EightSleepHeatEntity): +class EightHeatSensor(SensorEntity, EightSleepHeatEntity): """Representation of an eight sleep heat-based sensor.""" def __init__(self, name, eight, sensor): @@ -119,7 +120,7 @@ def extra_state_attributes(self): } -class EightUserSensor(EightSleepUserEntity): +class EightUserSensor(SensorEntity, EightSleepUserEntity): """Representation of an eight sleep user-based sensor.""" def __init__(self, name, eight, sensor, units): @@ -289,7 +290,7 @@ def extra_state_attributes(self): return state_attr -class EightRoomSensor(EightSleepUserEntity): +class EightRoomSensor(SensorEntity, EightSleepUserEntity): """Representation of an eight sleep room sensor.""" def __init__(self, name, eight, sensor, units): diff --git a/homeassistant/components/eliqonline/sensor.py b/homeassistant/components/eliqonline/sensor.py index b3d56e42325e0d..a4d812850f70bd 100644 --- a/homeassistant/components/eliqonline/sensor.py +++ b/homeassistant/components/eliqonline/sensor.py @@ -6,11 +6,10 @@ import eliqonline import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, POWER_WATT from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([EliqSensor(api, channel_id, name)], True) -class EliqSensor(Entity): +class EliqSensor(SensorEntity): """Implementation of an ELIQ Online sensor.""" def __init__(self, api, channel_id, name): diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 7d63f283f0b239..c8d4fd722bb2e7 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -8,6 +8,7 @@ from elkm1_lib.util import pretty_const, username import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import VOLT from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform @@ -67,7 +68,7 @@ def temperature_to_state(temperature, undefined_temperature): return temperature if temperature > undefined_temperature else None -class ElkSensor(ElkAttachedEntity): +class ElkSensor(SensorEntity, ElkAttachedEntity): """Base representation of Elk-M1 sensor.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 523350532a02e1..4913f6340ccfec 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_ID, @@ -19,7 +19,6 @@ ) from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -125,7 +124,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class EmonCmsSensor(Entity): +class EmonCmsSensor(SensorEntity): """Implementation of an Emoncms sensor.""" def __init__( diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 011dcdafdb65b0..18362a3c6ac6c5 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -1,7 +1,7 @@ """Support for EnOcean sensors.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ID, @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanWindowHandle(dev_id, dev_name)]) -class EnOceanSensor(EnOceanEntity, RestoreEntity): +class EnOceanSensor(SensorEntity, EnOceanEntity, RestoreEntity): """Representation of an EnOcean sensor device such as a power meter.""" def __init__(self, dev_id, dev_name, sensor_type): diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index e40c8e94372f9d..794c0ca2dbdb67 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -8,7 +8,7 @@ import httpx import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS, @@ -156,7 +156,7 @@ async def async_update_data(): async_add_entities(entities) -class Envoy(CoordinatorEntity): +class Envoy(SensorEntity, CoordinatorEntity): """Envoy entity.""" def __init__(self, sensor_type, name, serial_number, unit, coordinator): diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index 23c895c58a17d0..c9c530c6b08715 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -4,7 +4,7 @@ from enturclient import EnturPublicTransportData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_LATITUDE, @@ -15,7 +15,6 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -148,7 +147,7 @@ def get_stop_info(self, stop_id: str) -> dict: return self._api.get_stop_info(stop_id) -class EnturPublicTransportSensor(Entity): +class EnturPublicTransportSensor(SensorEntity): """Implementation of a Entur public transport sensor.""" def __init__(self, api: EnturProxy, name: str, stop: str, show_on_map: bool): diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index f178b3c6275f1e..0f0fb04fd00cb2 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -6,7 +6,7 @@ from env_canada import ECData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LOCATION, @@ -15,7 +15,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ECSensor(sensor_type, ec_data) for sensor_type in sensor_list], True) -class ECSensor(Entity): +class ECSensor(SensorEntity): """Implementation of an Environment Canada sensor.""" def __init__(self, sensor_type, ec_data): diff --git a/homeassistant/components/envirophat/sensor.py b/homeassistant/components/envirophat/sensor.py index 873d9935ee8443..137d6aee853fda 100644 --- a/homeassistant/components/envirophat/sensor.py +++ b/homeassistant/components/envirophat/sensor.py @@ -5,7 +5,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DISPLAY_OPTIONS, CONF_NAME, @@ -14,7 +14,6 @@ VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class EnvirophatSensor(Entity): +class EnvirophatSensor(SensorEntity): """Representation of an Enviro pHAT sensor.""" def __init__(self, data, sensor_types): diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index 9551b51b3e91aa..ff4c73500f9a3e 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -1,9 +1,9 @@ """Support for Envisalink sensors (shows panel info).""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import ( CONF_PARTITIONNAME, @@ -37,7 +37,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class EnvisalinkSensor(EnvisalinkDevice, Entity): +class EnvisalinkSensor(SensorEntity, EnvisalinkDevice): """Representation of an Envisalink keypad.""" def __init__(self, hass, partition_name, partition_number, info, controller): diff --git a/homeassistant/components/epsonworkforce/sensor.py b/homeassistant/components/epsonworkforce/sensor.py index 7c6042c89594c8..22f74e1c0b132c 100644 --- a/homeassistant/components/epsonworkforce/sensor.py +++ b/homeassistant/components/epsonworkforce/sensor.py @@ -4,11 +4,10 @@ from epsonprinter_pkg.epsonprinterapi import EpsonPrinterAPI import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_MONITORED_CONDITIONS, PERCENTAGE from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity MONITORED_CONDITIONS = { "black": ["Ink level Black", PERCENTAGE, "mdi:water"], @@ -45,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(sensors, True) -class EpsonPrinterCartridge(Entity): +class EpsonPrinterCartridge(SensorEntity): """Representation of a cartridge sensor.""" def __init__(self, api, cartridgeidx): diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 17799e4484fb84..99f66f480297ff 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -6,7 +6,7 @@ from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.config_entries import ConfigEntry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType @@ -42,7 +42,7 @@ async def async_setup_entry( # pylint: disable=invalid-overridden-method -class EsphomeSensor(EsphomeEntity): +class EsphomeSensor(SensorEntity, EsphomeEntity): """A sensor implementation for esphome.""" @property @@ -89,7 +89,7 @@ def device_class(self) -> str: return self._static_info.device_class -class EsphomeTextSensor(EsphomeEntity): +class EsphomeTextSensor(SensorEntity, EsphomeEntity): """A text sensor implementation for ESPHome.""" @property diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 26f7c930241436..f0dc70d7be4fde 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -6,10 +6,9 @@ from pyessent import PyEssent import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, ENERGY_KILO_WATT_HOUR import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle SCAN_INTERVAL = timedelta(hours=1) @@ -82,7 +81,7 @@ def update(self): self._meter_data[possible_meter] = meter_data -class EssentMeter(Entity): +class EssentMeter(SensorEntity): """Representation of Essent measurements.""" def __init__(self, essent_base, meter, meter_type, tariff, unit): diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py index e56b49181d4554..1fa2edbf2e8f1b 100644 --- a/homeassistant/components/etherscan/sensor.py +++ b/homeassistant/components/etherscan/sensor.py @@ -4,10 +4,9 @@ from pyetherscan import get_balance import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by etherscan.io" @@ -42,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EtherscanSensor(name, address, token, token_address)], True) -class EtherscanSensor(Entity): +class EtherscanSensor(SensorEntity): """Representation of an Etherscan.io sensor.""" def __init__(self, name, address, token, token_address): From 6cead320de1d3ba1e620edb140b6b81c8a2136e1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Mar 2021 13:10:01 +0100 Subject: [PATCH 1437/1818] Bump colorlog to 4.8.0 (#48218) --- homeassistant/scripts/check_config.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 40ce9a521df988..893351c7715266 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -20,7 +20,7 @@ # mypy: allow-untyped-calls, allow-untyped-defs -REQUIREMENTS = ("colorlog==4.7.2",) +REQUIREMENTS = ("colorlog==4.8.0",) _LOGGER = logging.getLogger(__name__) # pylint: disable=protected-access diff --git a/requirements_all.txt b/requirements_all.txt index 23c66f79e596f9..9c3251c9681b59 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -429,7 +429,7 @@ co2signal==0.4.2 coinbase==2.1.0 # homeassistant.scripts.check_config -colorlog==4.7.2 +colorlog==4.8.0 # homeassistant.components.color_extractor colorthief==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb42375de7b5e3..53f7b69d52d2f0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -226,7 +226,7 @@ buienradar==1.0.4 caldav==0.7.1 # homeassistant.scripts.check_config -colorlog==4.7.2 +colorlog==4.8.0 # homeassistant.components.color_extractor colorthief==0.2.1 From b7ad5ff032f8f7baba882e25ae9ef31b53e281d6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Mar 2021 13:11:06 +0100 Subject: [PATCH 1438/1818] Upgrade pre-commit to 2.11.1 (#48219) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 79203fba763ff1..21930782002574 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -9,7 +9,7 @@ coverage==5.5 jsonpickle==1.4.1 mock-open==1.4.0 mypy==0.812 -pre-commit==2.11.0 +pre-commit==2.11.1 pylint==2.7.2 astroid==2.5.1 pipdeptree==1.0.0 From 3fb323b745f2d075fa6405b151c0afe142afe706 Mon Sep 17 00:00:00 2001 From: unaiur Date: Mon, 22 Mar 2021 13:13:06 +0100 Subject: [PATCH 1439/1818] Fix maxcube temperature for thermostat auto mode (#48047) maxcube-api dependency now supports using None as the target temperature: in that case, it uses the scheduled temperature in auto mode and current temperature in all other modes. We will use that feature when changing hvac_mode to auto and selecting PRESET_NONE. --- homeassistant/components/maxcube/climate.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 87da5953f26800..7c114a927d649f 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -160,13 +160,7 @@ def set_hvac_mode(self, hvac_mode: str): elif hvac_mode == HVAC_MODE_HEAT: temp = max(temp, self.min_temp) else: - # Reset the temperature to a sane value. - # Ideally, we should send 0 and the device will set its - # temperature according to the schedule. However, current - # version of the library has a bug which causes an - # exception when setting values below 8. - if temp in [OFF_TEMPERATURE, ON_TEMPERATURE]: - temp = device.eco_temperature + temp = None mode = MAX_DEVICE_MODE_AUTOMATIC cube = self._cubehandle.cube @@ -264,7 +258,7 @@ def preset_modes(self): def set_preset_mode(self, preset_mode): """Set new operation mode.""" device = self._cubehandle.cube.device_by_rf(self._rf_address) - temp = device.target_temperature + temp = None mode = MAX_DEVICE_MODE_AUTOMATIC if preset_mode in [PRESET_COMFORT, PRESET_ECO, PRESET_ON]: From 272dffc3843fbca32fbb4f92639c0a815753a888 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 13:15:45 +0100 Subject: [PATCH 1440/1818] Improve script tracing (#48100) * Improve script tracing * Fix test --- homeassistant/helpers/script.py | 28 +- .../automation/test_websocket_api.py | 18 +- tests/helpers/test_script.py | 639 +++++++++++++++++- 3 files changed, 641 insertions(+), 44 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 1824bb9e2d6dbe..4cc2c1975e8bc6 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -427,11 +427,12 @@ async def _async_delay_step(self): delay = delay.total_seconds() self._changed() + trace_set_result(delay=delay, done=False) try: async with async_timeout.timeout(delay): await self._stop.wait() except asyncio.TimeoutError: - pass + trace_set_result(delay=delay, done=True) async def _async_wait_template_step(self): """Handle a wait template.""" @@ -443,6 +444,7 @@ async def _async_wait_template_step(self): self._step_log("wait template", timeout) self._variables["wait"] = {"remaining": timeout, "completed": False} + trace_set_result(wait=self._variables["wait"]) wait_template = self._action[CONF_WAIT_TEMPLATE] wait_template.hass = self._hass @@ -455,10 +457,9 @@ async def _async_wait_template_step(self): @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" - self._variables["wait"] = { - "remaining": to_context.remaining if to_context else timeout, - "completed": True, - } + wait_var = self._variables["wait"] + wait_var["remaining"] = to_context.remaining if to_context else timeout + wait_var["completed"] = True done.set() to_context = None @@ -475,10 +476,11 @@ def async_script_wait(entity_id, from_s, to_s): async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: + self._variables["wait"]["remaining"] = 0.0 if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) + trace_set_result(wait=self._variables["wait"], timeout=True) raise _StopScript from ex - self._variables["wait"]["remaining"] = 0.0 finally: for task in tasks: task.cancel() @@ -539,6 +541,7 @@ async def _async_call_service_step(self): else: limit = SERVICE_CALL_LIMIT + trace_set_result(params=params, running_script=running_script, limit=limit) service_task = self._hass.async_create_task( self._hass.services.async_call( **params, @@ -567,6 +570,7 @@ async def _async_device_step(self): async def _async_scene_step(self): """Activate the scene specified in the action.""" self._step_log("activate scene") + trace_set_result(scene=self._action[CONF_SCENE]) await self._hass.services.async_call( scene.DOMAIN, SERVICE_TURN_ON, @@ -592,6 +596,7 @@ async def _async_event_step(self): "Error rendering event data template: %s", ex, level=logging.ERROR ) + trace_set_result(event=self._action[CONF_EVENT], event_data=event_data) self._hass.bus.async_fire( self._action[CONF_EVENT], event_data, context=self._context ) @@ -748,14 +753,14 @@ async def _async_wait_for_trigger_step(self): variables = {**self._variables} self._variables["wait"] = {"remaining": timeout, "trigger": None} + trace_set_result(wait=self._variables["wait"]) done = asyncio.Event() async def async_done(variables, context=None): - self._variables["wait"] = { - "remaining": to_context.remaining if to_context else timeout, - "trigger": variables["trigger"], - } + wait_var = self._variables["wait"] + wait_var["remaining"] = to_context.remaining if to_context else timeout + wait_var["trigger"] = variables["trigger"] done.set() def log_cb(level, msg, **kwargs): @@ -782,10 +787,11 @@ def log_cb(level, msg, **kwargs): async with async_timeout.timeout(timeout) as to_context: await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError as ex: + self._variables["wait"]["remaining"] = 0.0 if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) + trace_set_result(wait=self._variables["wait"], timeout=True) raise _StopScript from ex - self._variables["wait"]["remaining"] = 0.0 finally: for task in tasks: task.cancel() diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/automation/test_websocket_api.py index ab69e89416abfa..6f630c2747026b 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/automation/test_websocket_api.py @@ -50,6 +50,18 @@ def next_id(): "action": {"event": "another_event"}, } + sun_action = { + "limit": 10, + "params": { + "domain": "test", + "service": "automation", + "service_data": {}, + "target": {}, + }, + "running_script": False, + } + moon_action = {"event": "another_event", "event_data": {}} + assert await async_setup_component( hass, "automation", @@ -94,7 +106,7 @@ def next_id(): assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert trace["action_trace"]["action/0"][0]["error"] - assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["action_trace"]["action/0"][0]["result"] == sun_action assert trace["condition_trace"] == {} assert trace["config"] == sun_config assert trace["context"] @@ -133,7 +145,7 @@ def next_id(): assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["action_trace"]["action/0"][0]["result"] == moon_action assert len(trace["condition_trace"]) == 1 assert len(trace["condition_trace"]["condition/0"]) == 1 assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} @@ -212,7 +224,7 @@ def next_id(): assert len(trace["action_trace"]) == 1 assert len(trace["action_trace"]["action/0"]) == 1 assert "error" not in trace["action_trace"]["action/0"][0] - assert "result" not in trace["action_trace"]["action/0"][0] + assert trace["action_trace"]["action/0"][0]["result"] == moon_action assert len(trace["condition_trace"]) == 1 assert len(trace["condition_trace"]["condition/0"]) == 1 assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 8a93d769775431..917fc64b0e72eb 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -16,7 +16,8 @@ from homeassistant import exceptions import homeassistant.components.scene as scene from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON -from homeassistant.core import Context, CoreState, callback +from homeassistant.core import SERVICE_CALL_LIMIT, Context, CoreState, callback +from homeassistant.exceptions import ConditionError, ServiceNotFound from homeassistant.helpers import config_validation as cv, script, trace from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component @@ -31,18 +32,59 @@ ENTITY_ID = "script.test" +@pytest.fixture(autouse=True) +def prepare_tracing(): + """Prepare tracing.""" + trace.trace_get() + + +def compare_trigger_item(actual_trigger, expected_trigger): + """Compare trigger data description.""" + assert actual_trigger["description"] == expected_trigger["description"] + + +def compare_result_item(key, actual, expected): + """Compare an item in the result dict.""" + if key == "wait" and (expected.get("trigger") is not None): + assert "trigger" in actual + expected_trigger = expected.pop("trigger") + actual_trigger = actual.pop("trigger") + compare_trigger_item(actual_trigger, expected_trigger) + + assert actual == expected + + def assert_element(trace_element, expected_element, path): """Assert a trace element is as expected. Note: Unused variable 'path' is passed to get helpful errors from pytest. """ - for result_key, result in expected_element.get("result", {}).items(): + expected_result = expected_element.get("result", {}) + + # Check that every item in expected_element is present and equal in trace_element + # The redundant set operation gives helpful errors from pytest + assert not set(expected_result) - set(trace_element._result or {}) + for result_key, result in expected_result.items(): + compare_result_item(result_key, trace_element._result[result_key], result) assert trace_element._result[result_key] == result + + # Check for unexpected items in trace_element + assert not set(trace_element._result or {}) - set(expected_result) + if "error_type" in expected_element: assert isinstance(trace_element._error, expected_element["error_type"]) else: assert trace_element._error is None + # Don't check variables when script starts + if trace_element.path == "0": + return + + if "variables" in expected_element: + assert expected_element["variables"] == trace_element._variables + else: + assert not trace_element._variables + def assert_action_trace(expected): """Assert a trace condition sequence is as expected.""" @@ -82,9 +124,6 @@ async def test_firing_event_basic(hass, caplog): {"alias": alias, "event": event, "event_data": {"hello": "world"}} ) - # Prepare tracing - trace.trace_get() - script_obj = script.Script( hass, sequence, @@ -102,9 +141,12 @@ async def test_firing_event_basic(hass, caplog): assert ".test_name:" in caplog.text assert "Test Name: Running test script" in caplog.text assert f"Executing step {alias}" in caplog.text + assert_action_trace( { - "0": [{}], + "0": [ + {"result": {"event": "test_event", "event_data": {"hello": "world"}}}, + ], } ) @@ -150,6 +192,24 @@ async def test_firing_event_template(hass): "list2": ["yes", "yesyes"], } + assert_action_trace( + { + "0": [ + { + "result": { + "event": "test_event", + "event_data": { + "dict": {1: "yes", 2: "yesyes", 3: "yesyesyes"}, + "dict2": {1: "yes", 2: "yesyes", 3: "yesyesyes"}, + "list": ["yes", "yesyes"], + "list2": ["yes", "yesyes"], + }, + } + } + ], + } + ) + async def test_calling_service_basic(hass, caplog): """Test the calling of a service.""" @@ -170,6 +230,25 @@ async def test_calling_service_basic(hass, caplog): assert calls[0].data.get("hello") == "world" assert f"Executing step {alias}" in caplog.text + assert_action_trace( + { + "0": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"hello": "world"}, + "target": {}, + }, + "running_script": False, + } + } + ], + } + ) + async def test_calling_service_template(hass): """Test the calling of a service.""" @@ -204,6 +283,25 @@ async def test_calling_service_template(hass): assert calls[0].context is context assert calls[0].data.get("hello") == "world" + assert_action_trace( + { + "0": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"hello": "world"}, + "target": {}, + }, + "running_script": False, + } + } + ], + } + ) + async def test_data_template_with_templated_key(hass): """Test the calling of a service with a data_template with a templated key.""" @@ -222,7 +320,26 @@ async def test_data_template_with_templated_key(hass): assert len(calls) == 1 assert calls[0].context is context - assert "hello" in calls[0].data + assert calls[0].data.get("hello") == "world" + + assert_action_trace( + { + "0": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"hello": "world"}, + "target": {}, + }, + "running_script": False, + } + } + ], + } + ) async def test_multiple_runs_no_wait(hass): @@ -315,6 +432,12 @@ async def test_activating_scene(hass, caplog): assert calls[0].data.get(ATTR_ENTITY_ID) == "scene.hello" assert f"Executing step {alias}" in caplog.text + assert_action_trace( + { + "0": [{"result": {"scene": "scene.hello"}}], + } + ) + @pytest.mark.parametrize("count", [1, 3]) async def test_stop_no_wait(hass, count): @@ -390,6 +513,12 @@ async def test_delay_basic(hass): assert not script_obj.is_running assert script_obj.last_action is None + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": True}}], + } + ) + async def test_multiple_runs_delay(hass): """Test multiple runs with delay in script.""" @@ -454,6 +583,12 @@ async def test_delay_template_ok(hass): assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": True}}], + } + ) + async def test_delay_template_invalid(hass, caplog): """Test the delay as a template that fails.""" @@ -481,6 +616,13 @@ async def test_delay_template_invalid(hass, caplog): assert not script_obj.is_running assert len(events) == 1 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript}], + } + ) + async def test_delay_template_complex_ok(hass): """Test the delay with a working complex template.""" @@ -501,6 +643,12 @@ async def test_delay_template_complex_ok(hass): assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": True}}], + } + ) + async def test_delay_template_complex_invalid(hass, caplog): """Test the delay with a complex template that fails.""" @@ -528,6 +676,13 @@ async def test_delay_template_complex_invalid(hass, caplog): assert not script_obj.is_running assert len(events) == 1 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript}], + } + ) + async def test_cancel_delay(hass): """Test the cancelling while the delay is present.""" @@ -559,6 +714,12 @@ async def test_cancel_delay(hass): assert not script_obj.is_running assert len(events) == 0 + assert_action_trace( + { + "0": [{"result": {"delay": 5.0, "done": False}}], + } + ) + @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_wait_basic(hass, action_type): @@ -594,6 +755,28 @@ async def test_wait_basic(hass, action_type): assert not script_obj.is_running assert script_obj.last_action is None + if action_type == "template": + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": True, "remaining": None}}}], + } + ) + else: + assert_action_trace( + { + "0": [ + { + "result": { + "wait": { + "trigger": {"description": "state of switch.test"}, + "remaining": None, + } + } + } + ], + } + ) + async def test_wait_for_trigger_variables(hass): """Test variables are passed to wait_for_trigger action.""" @@ -672,6 +855,19 @@ async def test_wait_basic_times_out(hass, action_type): assert timed_out + if action_type == "template": + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": False, "remaining": None}}}], + } + ) + else: + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_multiple_runs_wait(hass, action_type): @@ -769,6 +965,19 @@ async def test_cancel_wait(hass, action_type): assert not script_obj.is_running assert len(events) == 0 + if action_type == "template": + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": False, "remaining": None}}}], + } + ) + else: + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + async def test_wait_template_not_schedule(hass): """Test the wait template with correct condition.""" @@ -790,6 +999,19 @@ async def test_wait_template_not_schedule(hass): assert not script_obj.is_running assert len(events) == 2 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"result": {"wait": {"completed": True, "remaining": None}}}], + "2": [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"wait": {"completed": True, "remaining": None}}, + } + ], + } + ) + @pytest.mark.parametrize( "timeout_param", [5, "{{ 5 }}", {"seconds": 5}, {"seconds": "{{ 5 }}"}] @@ -839,6 +1061,21 @@ async def test_wait_timeout(hass, caplog, timeout_param, action_type): assert len(events) == 1 assert "(timeout: 0:00:05)" in caplog.text + if action_type == "template": + variable_wait = {"wait": {"completed": False, "remaining": 0.0}} + else: + variable_wait = {"wait": {"trigger": None, "remaining": 0.0}} + expected_trace = { + "0": [{"result": variable_wait}], + "1": [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": variable_wait, + } + ], + } + assert_action_trace(expected_trace) + @pytest.mark.parametrize( "continue_on_timeout,n_events", [(False, 0), (True, 1), (None, 1)] @@ -884,6 +1121,25 @@ async def test_wait_continue_on_timeout( assert not script_obj.is_running assert len(events) == n_events + if action_type == "template": + variable_wait = {"wait": {"completed": False, "remaining": 0.0}} + else: + variable_wait = {"wait": {"trigger": None, "remaining": 0.0}} + expected_trace = { + "0": [{"result": variable_wait}], + } + if continue_on_timeout is False: + expected_trace["0"][0]["result"]["timeout"] = True + expected_trace["0"][0]["error_type"] = script._StopScript + else: + expected_trace["1"] = [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": variable_wait, + } + ] + assert_action_trace(expected_trace) + async def test_wait_template_variables_in(hass): """Test the wait template with input variables.""" @@ -908,6 +1164,12 @@ async def test_wait_template_variables_in(hass): assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": True, "remaining": None}}}], + } + ) + async def test_wait_template_with_utcnow(hass): """Test the wait template with utcnow.""" @@ -933,6 +1195,12 @@ async def test_wait_template_with_utcnow(hass): await hass.async_block_till_done() assert not script_obj.is_running + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": True, "remaining": None}}}], + } + ) + async def test_wait_template_with_utcnow_no_match(hass): """Test the wait template with utcnow that does not match.""" @@ -963,6 +1231,12 @@ async def test_wait_template_with_utcnow_no_match(hass): assert timed_out + assert_action_trace( + { + "0": [{"result": {"wait": {"completed": False, "remaining": None}}}], + } + ) + @pytest.mark.parametrize("mode", ["no_timeout", "timeout_finish", "timeout_not_finish"]) @pytest.mark.parametrize("action_type", ["template", "trigger"]) @@ -1056,6 +1330,12 @@ async def async_attach_trigger_mock(*args, **kwargs): assert "Unknown error while setting up trigger" in caplog.text + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + async def test_wait_for_trigger_generated_exception(hass, caplog): """Test bad wait_for_trigger.""" @@ -1082,6 +1362,12 @@ async def async_attach_trigger_mock(*args, **kwargs): assert "ValueError" in caplog.text assert "something bad" in caplog.text + assert_action_trace( + { + "0": [{"result": {"wait": {"trigger": None, "remaining": None}}}], + } + ) + async def test_condition_warning(hass, caplog): """Test warning on condition.""" @@ -1112,6 +1398,15 @@ async def test_condition_warning(hass, caplog): assert len(events) == 1 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript, "result": {"result": False}}], + "1/condition": [{"error_type": ConditionError}], + "1/condition/entity_id/0": [{"error_type": ConditionError}], + } + ) + async def test_condition_basic(hass, caplog): """Test if we can use conditions in a script.""" @@ -1139,6 +1434,15 @@ async def test_condition_basic(hass, caplog): caplog.clear() assert len(events) == 2 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"result": {"result": True}}], + "1/condition": [{"result": {"result": True}}], + "2": [{"result": {"event": "test_event", "event_data": {}}}], + } + ) + hass.states.async_set("test.entity", "goodbye") await script_obj.async_run(context=Context()) @@ -1147,6 +1451,14 @@ async def test_condition_basic(hass, caplog): assert f"Test condition {alias}: False" in caplog.text assert len(events) == 3 + assert_action_trace( + { + "0": [{"result": {"event": "test_event", "event_data": {}}}], + "1": [{"error_type": script._StopScript, "result": {"result": False}}], + "1/condition": [{"result": {"result": False}}], + } + ) + @patch("homeassistant.helpers.script.condition.async_from_config") async def test_condition_created_once(async_from_config, hass): @@ -1219,9 +1531,6 @@ async def test_repeat_count(hass, caplog, count): } ) - # Prepare tracing - trace.trace_get() - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(context=Context()) @@ -1233,10 +1542,31 @@ async def test_repeat_count(hass, caplog, count): assert event.data.get("index") == index + 1 assert event.data.get("last") == (index == count - 1) assert caplog.text.count(f"Repeating {alias}") == count + first_index = max(1, count - script.ACTION_TRACE_NODE_MAX_LEN + 1) + last_index = count + 1 assert_action_trace( { "0": [{}], - "0/repeat/sequence/0": [{}] * min(count, script.ACTION_TRACE_NODE_MAX_LEN), + "0/repeat/sequence/0": [ + { + "result": { + "event": "test_event", + "event_data": { + "first": index == 1, + "index": index, + "last": index == count, + }, + }, + "variables": { + "repeat": { + "first": index == 1, + "index": index, + "last": index == count, + } + }, + } + for index in range(first_index, last_index) + ], } ) @@ -1281,6 +1611,26 @@ async def test_repeat_condition_warning(hass, caplog, condition): assert len(events) == count + expected_trace = {"0": [{}]} + if condition == "until": + expected_trace["0/repeat/sequence/0"] = [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"repeat": {"first": True, "index": 1}}, + } + ] + expected_trace["0/repeat"] = [ + { + "result": {"result": None}, + "variables": {"repeat": {"first": True, "index": 1}}, + } + ] + expected_trace[f"0/repeat/{condition}/0"] = [{"error_type": ConditionError}] + expected_trace[f"0/repeat/{condition}/0/entity_id/0"] = [ + {"error_type": ConditionError} + ] + assert_action_trace(expected_trace) + @pytest.mark.parametrize("condition", ["while", "until"]) @pytest.mark.parametrize("direct_template", [False, True]) @@ -1364,15 +1714,14 @@ async def test_repeat_var_in_condition(hass, condition): sequence = {"repeat": {"sequence": {"event": event}}} if condition == "while": - sequence["repeat"]["while"] = { - "condition": "template", - "value_template": "{{ repeat.index <= 2 }}", - } + value_template = "{{ repeat.index <= 2 }}" else: - sequence["repeat"]["until"] = { - "condition": "template", - "value_template": "{{ repeat.index == 2 }}", - } + value_template = "{{ repeat.index == 2 }}" + sequence["repeat"][condition] = { + "condition": "template", + "value_template": value_template, + } + script_obj = script.Script( hass, cv.SCRIPT_SCHEMA(sequence), "Test Name", "test_domain" ) @@ -1385,6 +1734,63 @@ async def test_repeat_var_in_condition(hass, condition): assert len(events) == 2 + if condition == "while": + expected_trace = { + "0": [{}], + "0/repeat": [ + { + "result": {"result": True}, + "variables": {"repeat": {"first": True, "index": 1}}, + }, + { + "result": {"result": True}, + "variables": {"repeat": {"first": False, "index": 2}}, + }, + { + "result": {"result": False}, + "variables": {"repeat": {"first": False, "index": 3}}, + }, + ], + "0/repeat/while/0": [ + {"result": {"result": True}}, + {"result": {"result": True}}, + {"result": {"result": False}}, + ], + "0/repeat/sequence/0": [ + {"result": {"event": "test_event", "event_data": {}}} + ] + * 2, + } + else: + expected_trace = { + "0": [{}], + "0/repeat/sequence/0": [ + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"repeat": {"first": True, "index": 1}}, + }, + { + "result": {"event": "test_event", "event_data": {}}, + "variables": {"repeat": {"first": False, "index": 2}}, + }, + ], + "0/repeat": [ + { + "result": {"result": False}, + "variables": {"repeat": {"first": True, "index": 1}}, + }, + { + "result": {"result": True}, + "variables": {"repeat": {"first": False, "index": 2}}, + }, + ], + "0/repeat/until/0": [ + {"result": {"result": False}}, + {"result": {"result": True}}, + ], + } + assert_action_trace(expected_trace) + @pytest.mark.parametrize( "variables,first_last,inside_x", @@ -1486,6 +1892,49 @@ async def test_repeat_nested(hass, variables, first_last, inside_x): "x": result[3], } + event_data1 = {"repeat": None, "x": inside_x} + event_data2 = [ + {"first": True, "index": 1, "last": False, "x": inside_x}, + {"first": False, "index": 2, "last": True, "x": inside_x}, + ] + variable_repeat = [ + {"repeat": {"first": True, "index": 1, "last": False}}, + {"repeat": {"first": False, "index": 2, "last": True}}, + ] + expected_trace = { + "0": [{"result": {"event": "test_event", "event_data": event_data1}}], + "1": [{}], + "1/repeat/sequence/0": [ + { + "result": {"event": "test_event", "event_data": event_data2[0]}, + "variables": variable_repeat[0], + }, + { + "result": {"event": "test_event", "event_data": event_data2[1]}, + "variables": variable_repeat[1], + }, + ], + "1/repeat/sequence/1": [{}, {}], + "1/repeat/sequence/1/repeat/sequence/0": [ + {"result": {"event": "test_event", "event_data": event_data2[0]}}, + { + "result": {"event": "test_event", "event_data": event_data2[1]}, + "variables": variable_repeat[1], + }, + { + "result": {"event": "test_event", "event_data": event_data2[0]}, + "variables": variable_repeat[0], + }, + {"result": {"event": "test_event", "event_data": event_data2[1]}}, + ], + "1/repeat/sequence/2": [ + {"result": {"event": "test_event", "event_data": event_data2[0]}}, + {"result": {"event": "test_event", "event_data": event_data2[1]}}, + ], + "2": [{"result": {"event": "test_event", "event_data": event_data1}}], + } + assert_action_trace(expected_trace) + async def test_choose_warning(hass, caplog): """Test warning on choose.""" @@ -1578,9 +2027,6 @@ async def test_choose(hass, caplog, var, result): } ) - # Prepare tracing - trace.trace_get() - script_obj = script.Script(hass, sequence, "Test Name", "test_domain") await script_obj.async_run(MappingProxyType({"var": var}), Context()) @@ -1593,19 +2039,29 @@ async def test_choose(hass, caplog, var, result): expected_choice = "default" assert f"{alias}: {expected_choice}: Executing step {aliases[var]}" in caplog.text - expected_trace = {"0": [{}]} + expected_choice = var - 1 + if var == 3: + expected_choice = "default" + + expected_trace = {"0": [{"result": {"choice": expected_choice}}]} if var >= 1: - expected_trace["0/choose/0"] = [{}] - expected_trace["0/choose/0/conditions/0"] = [{}] + expected_trace["0/choose/0"] = [{"result": {"result": var == 1}}] + expected_trace["0/choose/0/conditions/0"] = [{"result": {"result": var == 1}}] if var >= 2: - expected_trace["0/choose/1"] = [{}] - expected_trace["0/choose/1/conditions/0"] = [{}] + expected_trace["0/choose/1"] = [{"result": {"result": var == 2}}] + expected_trace["0/choose/1/conditions/0"] = [{"result": {"result": var == 2}}] if var == 1: - expected_trace["0/choose/0/sequence/0"] = [{}] + expected_trace["0/choose/0/sequence/0"] = [ + {"result": {"event": "test_event", "event_data": {"choice": "first"}}} + ] if var == 2: - expected_trace["0/choose/1/sequence/0"] = [{}] + expected_trace["0/choose/1/sequence/0"] = [ + {"result": {"event": "test_event", "event_data": {"choice": "second"}}} + ] if var == 3: - expected_trace["0/default/sequence/0"] = [{}] + expected_trace["0/default/sequence/0"] = [ + {"result": {"event": "test_event", "event_data": {"choice": "default"}}} + ] assert_action_trace(expected_trace) @@ -1668,6 +2124,25 @@ async def test_propagate_error_service_not_found(hass): assert len(events) == 0 assert not script_obj.is_running + expected_trace = { + "0": [ + { + "error_type": ServiceNotFound, + "result": { + "limit": 10, + "params": { + "domain": "test", + "service": "script", + "service_data": {}, + "target": {}, + }, + "running_script": False, + }, + } + ], + } + assert_action_trace(expected_trace) + async def test_propagate_error_invalid_service_data(hass): """Test that a script aborts when we send invalid service data.""" @@ -1686,6 +2161,25 @@ async def test_propagate_error_invalid_service_data(hass): assert len(calls) == 0 assert not script_obj.is_running + expected_trace = { + "0": [ + { + "error_type": vol.MultipleInvalid, + "result": { + "limit": 10, + "params": { + "domain": "test", + "service": "script", + "service_data": {"text": 1}, + "target": {}, + }, + "running_script": False, + }, + } + ], + } + assert_action_trace(expected_trace) + async def test_propagate_error_service_exception(hass): """Test that a script aborts when a service throws an exception.""" @@ -1708,6 +2202,25 @@ def record_call(service): assert len(events) == 0 assert not script_obj.is_running + expected_trace = { + "0": [ + { + "error_type": ValueError, + "result": { + "limit": 10, + "params": { + "domain": "test", + "service": "script", + "service_data": {}, + "target": {}, + }, + "running_script": False, + }, + } + ], + } + assert_action_trace(expected_trace) + async def test_referenced_entities(hass): """Test referenced entities.""" @@ -2151,6 +2664,11 @@ async def test_shutdown_at(hass, caplog): assert not script_obj.is_running assert "Stopping scripts running at shutdown: test script" in caplog.text + expected_trace = { + "0": [{"result": {"delay": 120.0, "done": False}}], + } + assert_action_trace(expected_trace) + async def test_shutdown_after(hass, caplog): """Test stopping scripts at shutdown.""" @@ -2182,6 +2700,11 @@ async def test_shutdown_after(hass, caplog): in caplog.text ) + expected_trace = { + "0": [{"result": {"delay": 120.0, "done": False}}], + } + assert_action_trace(expected_trace) + async def test_update_logger(hass, caplog): """Test updating logger.""" @@ -2240,6 +2763,26 @@ async def test_set_variable(hass, caplog): assert mock_calls[0].data["value"] == "value" assert f"Executing step {alias}" in caplog.text + expected_trace = { + "0": [{}], + "1": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"value": "value"}, + "target": {}, + }, + "running_script": False, + }, + "variables": {"variable": "value"}, + } + ], + } + assert_action_trace(expected_trace) + async def test_set_redefines_variable(hass, caplog): """Test setting variables based on their current value.""" @@ -2261,6 +2804,42 @@ async def test_set_redefines_variable(hass, caplog): assert mock_calls[0].data["value"] == 1 assert mock_calls[1].data["value"] == 2 + expected_trace = { + "0": [{}], + "1": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"value": 1}, + "target": {}, + }, + "running_script": False, + }, + "variables": {"variable": "1"}, + } + ], + "2": [{}], + "3": [ + { + "result": { + "limit": SERVICE_CALL_LIMIT, + "params": { + "domain": "test", + "service": "script", + "service_data": {"value": 2}, + "target": {}, + }, + "running_script": False, + }, + "variables": {"variable": 2}, + } + ], + } + assert_action_trace(expected_trace) + async def test_validate_action_config(hass): """Validate action config.""" From dc15f243e6913a0dc0049cd508e1a21dea97f168 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 22 Mar 2021 13:29:39 +0100 Subject: [PATCH 1441/1818] Upgrade pyupgrade to v2.11.0 (#48220) --- .pre-commit-config.yaml | 2 +- homeassistant/auth/models.py | 4 ++-- homeassistant/components/esphome/entry_data.py | 2 +- homeassistant/components/http/web_runner.py | 2 +- homeassistant/components/wunderground/sensor.py | 2 +- homeassistant/components/zha/core/store.py | 2 +- homeassistant/exceptions.py | 2 +- homeassistant/helpers/device_registry.py | 2 +- homeassistant/helpers/entity_registry.py | 2 +- homeassistant/helpers/sun.py | 2 +- requirements_test_pre_commit.txt | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 031d0659aadea0..2ed5a8bd8d30a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.10.1 + rev: v2.11.0 hooks: - id: pyupgrade args: [--py38-plus] diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index aaef7e02174918..758bbdb78e212d 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -46,7 +46,7 @@ class User: credentials: list[Credentials] = attr.ib(factory=list, eq=False, order=False) # Tokens associated with a user. - refresh_tokens: dict[str, "RefreshToken"] = attr.ib( + refresh_tokens: dict[str, RefreshToken] = attr.ib( factory=dict, eq=False, order=False ) @@ -109,7 +109,7 @@ class RefreshToken: last_used_at: datetime | None = attr.ib(default=None) last_used_ip: str | None = attr.ib(default=None) - credential: "Credentials" | None = attr.ib(default=None) + credential: Credentials | None = attr.ib(default=None) version: str | None = attr.ib(default=__version__) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 4fada10a3d1660..34ed6ffee4634f 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -63,7 +63,7 @@ class RuntimeEntryData: # If an entity can't find anything in the info array, it will look for info here. old_info: dict[str, dict[str, Any]] = attr.ib(factory=dict) - services: dict[int, "UserService"] = attr.ib(factory=dict) + services: dict[int, UserService] = attr.ib(factory=dict) available: bool = attr.ib(default=False) device_info: DeviceInfo | None = attr.ib(default=None) cleanup_callbacks: list[Callable[[], None]] = attr.ib(factory=list) diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index 74410026f948a2..87468d40954f16 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -25,7 +25,7 @@ class HomeAssistantTCPSite(web.BaseSite): def __init__( self, - runner: "web.BaseRunner", + runner: web.BaseRunner, host: None | str | list[str], port: int, *, diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 9e4b764f34c765..38d6bba0b1fb3a 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -68,7 +68,7 @@ def __init__( self, friendly_name: str | Callable, feature: str, - value: Callable[["WUndergroundData"], Any], + value: Callable[[WUndergroundData], Any], unit_of_measurement: str | None = None, entity_picture=None, icon: str = "mdi:gauge", diff --git a/homeassistant/components/zha/core/store.py b/homeassistant/components/zha/core/store.py index 43b41153657e8e..9381c529187202 100644 --- a/homeassistant/components/zha/core/store.py +++ b/homeassistant/components/zha/core/store.py @@ -93,7 +93,7 @@ async def async_load(self) -> None: """Load the registry of zha device entries.""" data = await self._store.async_load() - devices: "OrderedDict[str, ZhaDeviceEntry]" = OrderedDict() + devices: OrderedDict[str, ZhaDeviceEntry] = OrderedDict() if data is not None: for device in data["devices"]: diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 499d4052849fe7..375db78961840b 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -115,7 +115,7 @@ class Unauthorized(HomeAssistantError): def __init__( self, - context: "Context" | None = None, + context: Context | None = None, user_id: str | None = None, entity_id: str | None = None, config_entry_id: str | None = None, diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index fac937c4afb78c..bfa04706d6056d 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -688,7 +688,7 @@ def async_config_entry_disabled_by_changed( def async_cleanup( hass: HomeAssistantType, dev_reg: DeviceRegistry, - ent_reg: "entity_registry.EntityRegistry", + ent_reg: entity_registry.EntityRegistry, ) -> None: """Clean up device registry.""" # Find all devices that are referenced by a config_entry. diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 265dcd9d8781ee..832838798ca6ca 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -222,7 +222,7 @@ def async_get_or_create( # To disable an entity if it gets created disabled_by: str | None = None, # Data that we want entry to have - config_entry: "ConfigEntry" | None = None, + config_entry: ConfigEntry | None = None, device_id: str | None = None, area_id: str | None = None, capabilities: dict[str, Any] | None = None, diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index ded1b3a7123787..5b23b3d025167d 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -54,7 +54,7 @@ def get_astral_event_next( @callback def get_location_astral_event_next( - location: "astral.Location", + location: astral.Location, event: str, utc_point_in_time: datetime.datetime | None = None, offset: datetime.timedelta | None = None, diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index be881f09f7cae6..b8fabc685b1189 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -9,5 +9,5 @@ flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 python-typing-update==0.3.0 -pyupgrade==2.10.1 +pyupgrade==2.11.0 yamllint==1.24.2 From a583f56bd8a461ad48eb273ca5d99f6d7c8fb5ca Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 22 Mar 2021 14:35:24 +0100 Subject: [PATCH 1442/1818] Add identification for YAML imports (#48162) --- homeassistant/components/xiaomi_miio/config_flow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index efb668f0196e4d..12e299879b2c02 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -46,6 +46,8 @@ def __init__(self): async def async_step_import(self, conf: dict): """Import a configuration from config.yaml.""" + host = conf[CONF_HOST] + self.context.update({"title_placeholders": {"name": f"YAML import {host}"}}) return await self.async_step_device(user_input=conf) async def async_step_user(self, user_input=None): From 53a9c117ee3d622ba9d75626381b088514cc813c Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Mon, 22 Mar 2021 14:43:46 +0100 Subject: [PATCH 1443/1818] Add jobstate parser to Onvif integration (#46589) --- homeassistant/components/onvif/parsers.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index cad2b3c8cab5ca..9574d44edeae2a 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -387,3 +387,25 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: ) except (AttributeError, KeyError, ValueError): return None + + +@PARSERS.register("tns1:RecordingConfig/JobState") +# pylint: disable=protected-access +async def async_parse_jobstate(uid: str, msg) -> Event: + """Handle parsing event message. + + Topic: tns1:RecordingConfig/JobState* + """ + + try: + source = msg.Message._value_1.Source.SimpleItem[0].Value + return Event( + f"{uid}_{msg.Topic._value_1}_{source}", + f"{source} JobState", + "binary_sensor", + None, + None, + msg.Message._value_1.Data.SimpleItem[0].Value == "Active", + ) + except (AttributeError, KeyError): + return None From 286217f7718d1bc6ebbaa19c8340ea386974d3f0 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 22 Mar 2021 14:59:40 +0100 Subject: [PATCH 1444/1818] Fix condition extra fields for climate and humidifier (#48184) --- homeassistant/components/climate/device_condition.py | 2 +- homeassistant/components/cover/device_action.py | 2 +- homeassistant/components/humidifier/device_condition.py | 2 +- tests/components/climate/test_device_condition.py | 2 +- tests/components/humidifier/test_device_condition.py | 6 ++---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 10f3b5069b93cc..d20c202e93bc67 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -119,6 +119,6 @@ async def async_get_condition_capabilities(hass, config): else: preset_modes = [] - fields[vol.Required(const.ATTR_PRESET_MODES)] = vol.In(preset_modes) + fields[vol.Required(const.ATTR_PRESET_MODE)] = vol.In(preset_modes) return {"extra_fields": vol.Schema(fields)} diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 4f92e7f09bdd51..6981f87c492568 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -153,7 +153,7 @@ async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> di return { "extra_fields": vol.Schema( { - vol.Optional("position", default=0): vol.All( + vol.Optional(ATTR_POSITION, default=0): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) ) } diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 137fd6af73da6b..02a667f2f68f05 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -98,7 +98,7 @@ async def async_get_condition_capabilities(hass, config): else: modes = [] - fields[vol.Required(const.ATTR_AVAILABLE_MODES)] = vol.In(modes) + fields[vol.Required(ATTR_MODE)] = vol.In(modes) return {"extra_fields": vol.Schema(fields)} diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 009d29dab39368..27341b6c2c915c 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -260,7 +260,7 @@ async def test_capabilities(hass): capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { - "name": "preset_modes", + "name": "preset_mode", "options": [("home", "home"), ("away", "away")], "required": True, "type": "select", diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index 8b35655223334b..59887f65a333af 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -256,7 +256,7 @@ async def test_capabilities(hass): capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { - "name": "available_modes", + "name": "mode", "options": [("home", "home"), ("away", "away")], "required": True, "type": "select", @@ -282,9 +282,7 @@ async def test_capabilities_no_state(hass): assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer - ) == [ - {"name": "available_modes", "options": [], "required": True, "type": "select"} - ] + ) == [{"name": "mode", "options": [], "required": True, "type": "select"}] async def test_get_condition_capabilities(hass, device_reg, entity_reg): From 781084880b0bc8d1e73d3ec56b81f4717e520dc7 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 22 Mar 2021 07:59:12 -0700 Subject: [PATCH 1445/1818] Add an option to hide selected Hyperion effects (#45689) --- .../components/hyperion/config_flow.py | 38 ++++ homeassistant/components/hyperion/const.py | 2 + homeassistant/components/hyperion/light.py | 20 +- .../components/hyperion/strings.json | 5 +- tests/components/hyperion/__init__.py | 9 +- tests/components/hyperion/test_config_flow.py | 199 +++++++++++++++++- tests/components/hyperion/test_light.py | 24 ++- 7 files changed, 275 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index bea8971cfa691c..2b8d0ee8d8f227 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -26,6 +26,7 @@ CONF_TOKEN, ) from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType from . import create_hyperion_client @@ -34,6 +35,8 @@ from .const import ( CONF_AUTH_ID, CONF_CREATE_TOKEN, + CONF_EFFECT_HIDE_LIST, + CONF_EFFECT_SHOW_LIST, CONF_PRIORITY, DEFAULT_ORIGIN, DEFAULT_PRIORITY, @@ -439,13 +442,44 @@ def __init__(self, config_entry: ConfigEntry): """Initialize a Hyperion options flow.""" self._config_entry = config_entry + def _create_client(self) -> client.HyperionClient: + """Create and connect a client instance.""" + return create_hyperion_client( + self._config_entry.data[CONF_HOST], + self._config_entry.data[CONF_PORT], + token=self._config_entry.data.get(CONF_TOKEN), + ) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> dict[str, Any]: """Manage the options.""" + + effects = {source: source for source in const.KEY_COMPONENTID_EXTERNAL_SOURCES} + async with self._create_client() as hyperion_client: + if not hyperion_client: + return self.async_abort(reason="cannot_connect") + for effect in hyperion_client.effects or []: + if const.KEY_NAME in effect: + effects[effect[const.KEY_NAME]] = effect[const.KEY_NAME] + + # If a new effect is added to Hyperion, we always want it to show by default. So + # rather than store a 'show list' in the config entry, we store a 'hide list'. + # However, it's more intuitive to ask the user to select which effects to show, + # so we inverse the meaning prior to storage. + if user_input is not None: + effect_show_list = user_input.pop(CONF_EFFECT_SHOW_LIST) + user_input[CONF_EFFECT_HIDE_LIST] = sorted( + set(effects) - set(effect_show_list) + ) return self.async_create_entry(title="", data=user_input) + default_effect_show_list = list( + set(effects) + - set(self._config_entry.options.get(CONF_EFFECT_HIDE_LIST, [])) + ) + return self.async_show_form( step_id="init", data_schema=vol.Schema( @@ -456,6 +490,10 @@ async def async_step_init( CONF_PRIORITY, DEFAULT_PRIORITY ), ): vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), + vol.Optional( + CONF_EFFECT_SHOW_LIST, + default=default_effect_show_list, + ): cv.multi_select(effects), } ), ) diff --git a/homeassistant/components/hyperion/const.py b/homeassistant/components/hyperion/const.py index 64c2f20052b911..994ef580c9153c 100644 --- a/homeassistant/components/hyperion/const.py +++ b/homeassistant/components/hyperion/const.py @@ -31,6 +31,8 @@ CONF_ON_UNLOAD = "ON_UNLOAD" CONF_PRIORITY = "priority" CONF_ROOT_CLIENT = "ROOT_CLIENT" +CONF_EFFECT_HIDE_LIST = "effect_hide_list" +CONF_EFFECT_SHOW_LIST = "effect_show_list" DEFAULT_NAME = "Hyperion" DEFAULT_ORIGIN = "Home Assistant" diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index d322362e95985e..36c3d836bf354a 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -28,6 +28,7 @@ from . import get_hyperion_unique_id, listen_for_instance_updates from .const import ( + CONF_EFFECT_HIDE_LIST, CONF_INSTANCE_CLIENTS, CONF_PRIORITY, DEFAULT_ORIGIN, @@ -217,7 +218,10 @@ def unique_id(self) -> str: def _get_option(self, key: str) -> Any: """Get a value from the provided options.""" - defaults = {CONF_PRIORITY: DEFAULT_PRIORITY} + defaults = { + CONF_PRIORITY: DEFAULT_PRIORITY, + CONF_EFFECT_HIDE_LIST: [], + } return self._options.get(key, defaults[key]) async def async_turn_on(self, **kwargs: Any) -> None: @@ -366,12 +370,18 @@ def _update_effect_list(self, _: dict[str, Any] | None = None) -> None: if not self._client.effects: return effect_list: list[str] = [] + hide_effects = self._get_option(CONF_EFFECT_HIDE_LIST) + for effect in self._client.effects or []: if const.KEY_NAME in effect: - effect_list.append(effect[const.KEY_NAME]) - if effect_list: - self._effect_list = self._static_effect_list + effect_list - self.async_write_ha_state() + effect_name = effect[const.KEY_NAME] + if effect_name not in hide_effects: + effect_list.append(effect_name) + + self._effect_list = [ + effect for effect in self._static_effect_list if effect not in hide_effects + ] + effect_list + self.async_write_ha_state() @callback def _update_full_state(self) -> None: diff --git a/homeassistant/components/hyperion/strings.json b/homeassistant/components/hyperion/strings.json index ca7ed238f0bf42..54beb7704c9c4d 100644 --- a/homeassistant/components/hyperion/strings.json +++ b/homeassistant/components/hyperion/strings.json @@ -45,9 +45,10 @@ "step": { "init": { "data": { - "priority": "Hyperion priority to use for colors and effects" + "priority": "Hyperion priority to use for colors and effects", + "effect_show_list": "Hyperion effects to show" } } } } -} \ No newline at end of file +} diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index e811a4dde32016..d0653f88b83597 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -118,7 +118,9 @@ def create_mock_client() -> Mock: def add_test_config_entry( - hass: HomeAssistantType, data: dict[str, Any] | None = None + hass: HomeAssistantType, + data: dict[str, Any] | None = None, + options: dict[str, Any] | None = None, ) -> ConfigEntry: """Add a test config entry.""" config_entry: MockConfigEntry = MockConfigEntry( # type: ignore[no-untyped-call] @@ -131,7 +133,7 @@ def add_test_config_entry( }, title=f"Hyperion {TEST_SYSINFO_ID}", unique_id=TEST_SYSINFO_ID, - options=TEST_CONFIG_ENTRY_OPTIONS, + options=options or TEST_CONFIG_ENTRY_OPTIONS, ) config_entry.add_to_hass(hass) # type: ignore[no-untyped-call] return config_entry @@ -141,9 +143,10 @@ async def setup_test_config_entry( hass: HomeAssistantType, config_entry: ConfigEntry | None = None, hyperion_client: Mock | None = None, + options: dict[str, Any] | None = None, ) -> ConfigEntry: """Add a test Hyperion entity to hass.""" - config_entry = config_entry or add_test_config_entry(hass) + config_entry = config_entry or add_test_config_entry(hass, options=options) hyperion_client = hyperion_client or create_mock_client() # pylint: disable=attribute-defined-outside-init diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index 15bca12b03f1e7..7cf0556eddf2fd 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -1,8 +1,9 @@ """Tests for the Hyperion config flow.""" from __future__ import annotations +import asyncio from typing import Any -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from hyperion import const @@ -10,6 +11,8 @@ from homeassistant.components.hyperion.const import ( CONF_AUTH_ID, CONF_CREATE_TOKEN, + CONF_EFFECT_HIDE_LIST, + CONF_EFFECT_SHOW_LIST, CONF_PRIORITY, DOMAIN, ) @@ -309,23 +312,42 @@ async def test_auth_static_token_success(hass: HomeAssistantType) -> None: } -async def test_auth_static_token_login_fail(hass: HomeAssistantType) -> None: - """Test correct behavior with a bad static token.""" +async def test_auth_static_token_login_connect_fail(hass: HomeAssistantType) -> None: + """Test correct behavior with a static token that cannot connect.""" result = await _init_flow(hass) assert result["step_id"] == "user" client = create_mock_client() client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) - # Fail the login call. - client.async_login = AsyncMock( - return_value={"command": "authorize-login", "success": False, "tan": 0} - ) + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + client.async_client_connect = AsyncMock(return_value=False) + result = await _configure_flow( + hass, result, user_input={CONF_CREATE_TOKEN: False, CONF_TOKEN: TEST_TOKEN} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + +async def test_auth_static_token_login_fail(hass: HomeAssistantType) -> None: + """Test correct behavior with a static token that cannot login.""" + result = await _init_flow(hass) + assert result["step_id"] == "user" + + client = create_mock_client() + client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) with patch( "homeassistant.components.hyperion.client.HyperionClient", return_value=client ): result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + client.async_login = AsyncMock( + return_value={"command": "authorize-login", "success": False, "tan": 0} + ) result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: False, CONF_TOKEN: TEST_TOKEN} ) @@ -377,6 +399,66 @@ async def test_auth_create_token_approval_declined(hass: HomeAssistantType) -> N assert result["reason"] == "auth_new_token_not_granted_error" +async def test_auth_create_token_approval_declined_task_canceled( + hass: HomeAssistantType, +) -> None: + """Verify correct behaviour when a token request is declined.""" + result = await _init_flow(hass) + + client = create_mock_client() + client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + assert result["step_id"] == "auth" + + client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_FAIL) + + class CanceledAwaitableMock(AsyncMock): + """A canceled awaitable mock.""" + + def __await__(self): + raise asyncio.CancelledError + + mock_task = CanceledAwaitableMock() + task_coro = None + + def create_task(arg): + nonlocal task_coro + task_coro = arg + return mock_task + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ), patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), patch.object( + hass, "async_create_task", side_effect=create_task + ): + result = await _configure_flow( + hass, result, user_input={CONF_CREATE_TOKEN: True} + ) + assert result["step_id"] == "create_token" + + result = await _configure_flow(hass, result) + assert result["step_id"] == "create_token_external" + + # Leave the task running, to ensure it is canceled. + mock_task.done = Mock(return_value=False) + mock_task.cancel = Mock() + + result = await _configure_flow(hass, result) + + # This await will advance to the next step. + await task_coro + + # Assert that cancel is called on the task. + assert mock_task.cancel.called + + async def test_auth_create_token_when_issued_token_fails( hass: HomeAssistantType, ) -> None: @@ -468,6 +550,47 @@ async def test_auth_create_token_success(hass: HomeAssistantType) -> None: } +async def test_auth_create_token_success_but_login_fail( + hass: HomeAssistantType, +) -> None: + """Verify correct behaviour when a token is successfully created but the login fails.""" + result = await _init_flow(hass) + + client = create_mock_client() + client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + result = await _configure_flow(hass, result, user_input=TEST_HOST_PORT) + assert result["step_id"] == "auth" + + client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_SUCCESS) + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ), patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ): + result = await _configure_flow( + hass, result, user_input={CONF_CREATE_TOKEN: True} + ) + assert result["step_id"] == "create_token" + + result = await _configure_flow(hass, result) + assert result["step_id"] == "create_token_external" + + client.async_login = AsyncMock( + return_value={"command": "authorize-login", "success": False, "tan": 0} + ) + + # The flow will be automatically advanced by the auth token response. + result = await _configure_flow(hass, result) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "auth_new_token_not_work_error" + + async def test_ssdp_success(hass: HomeAssistantType) -> None: """Check an SSDP flow.""" @@ -599,8 +722,8 @@ async def test_ssdp_abort_duplicates(hass: HomeAssistantType) -> None: assert result_2["reason"] == "already_in_progress" -async def test_options(hass: HomeAssistantType) -> None: - """Check an options flow.""" +async def test_options_priority(hass: HomeAssistantType) -> None: + """Check an options flow priority option.""" config_entry = add_test_config_entry(hass) @@ -618,11 +741,12 @@ async def test_options(hass: HomeAssistantType) -> None: new_priority = 1 result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={CONF_PRIORITY: new_priority} + result["flow_id"], + user_input={CONF_PRIORITY: new_priority}, ) await hass.async_block_till_done() assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"] == {CONF_PRIORITY: new_priority} + assert result["data"][CONF_PRIORITY] == new_priority # Turn the light on and ensure the new priority is used. client.async_send_set_color = AsyncMock(return_value=True) @@ -636,6 +760,59 @@ async def test_options(hass: HomeAssistantType) -> None: assert client.async_send_set_color.call_args[1][CONF_PRIORITY] == new_priority +async def test_options_effect_show_list(hass: HomeAssistantType) -> None: + """Check an options flow effect show list.""" + + config_entry = add_test_config_entry(hass) + + client = create_mock_client() + client.effects = [ + {const.KEY_NAME: "effect1"}, + {const.KEY_NAME: "effect2"}, + {const.KEY_NAME: "effect3"}, + ] + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_EFFECT_SHOW_LIST: ["effect1", "effect3"]}, + ) + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + # effect1 and effect3 only, so effect2 & external sources are hidden. + assert result["data"][CONF_EFFECT_HIDE_LIST] == sorted( + ["effect2"] + const.KEY_COMPONENTID_EXTERNAL_SOURCES + ) + + +async def test_options_effect_hide_list_cannot_connect(hass: HomeAssistantType) -> None: + """Check an options flow effect hide list with a failed connection.""" + + config_entry = add_test_config_entry(hass) + client = create_mock_client() + + with patch( + "homeassistant.components.hyperion.client.HyperionClient", return_value=client + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + client.async_client_connect = AsyncMock(return_value=False) + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "cannot_connect" + + async def test_reauth_success(hass: HomeAssistantType) -> None: """Check a reauth flow that succeeds.""" diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index e83f50599391e8..bb8fe8d0814416 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -6,7 +6,11 @@ from hyperion import const from homeassistant.components.hyperion import light as hyperion_light -from homeassistant.components.hyperion.const import DEFAULT_ORIGIN, DOMAIN +from homeassistant.components.hyperion.const import ( + CONF_EFFECT_HIDE_LIST, + DEFAULT_ORIGIN, + DOMAIN, +) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_EFFECT, @@ -1129,3 +1133,21 @@ async def test_priority_light_has_no_external_sources(hass: HomeAssistantType) - entity_state = hass.states.get(TEST_PRIORITY_LIGHT_ENTITY_ID_1) assert entity_state assert entity_state.attributes["effect_list"] == [hyperion_light.KEY_EFFECT_SOLID] + + +async def test_light_option_effect_hide_list(hass: HomeAssistantType) -> None: + """Test the effect_hide_list option.""" + client = create_mock_client() + client.effects = [{const.KEY_NAME: "One"}, {const.KEY_NAME: "Two"}] + + await setup_test_config_entry( + hass, hyperion_client=client, options={CONF_EFFECT_HIDE_LIST: ["Two", "V4L"]} + ) + + entity_state = hass.states.get(TEST_ENTITY_ID_1) + assert entity_state.attributes["effect_list"] == [ + "Solid", + "BOBLIGHTSERVER", + "GRABBER", + "One", + ] From a49989241aee9a42d6b6347661ce8006b518bfb9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:19:38 +0100 Subject: [PATCH 1446/1818] Refactor tracing: Move trace support to its own integration (#48224) --- CODEOWNERS | 1 + .../components/automation/__init__.py | 6 +- .../components/automation/manifest.json | 2 +- homeassistant/components/automation/trace.py | 87 ++----------------- homeassistant/components/trace/__init__.py | 12 +++ homeassistant/components/trace/const.py | 4 + homeassistant/components/trace/manifest.json | 9 ++ homeassistant/components/trace/trace.py | 35 ++++++++ homeassistant/components/trace/utils.py | 43 +++++++++ .../{automation => trace}/websocket_api.py | 10 +-- tests/components/trace/__init__.py | 1 + .../test_trace.py => trace/test_utils.py} | 6 +- .../test_websocket_api.py | 2 +- 13 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 homeassistant/components/trace/__init__.py create mode 100644 homeassistant/components/trace/const.py create mode 100644 homeassistant/components/trace/manifest.json create mode 100644 homeassistant/components/trace/trace.py create mode 100644 homeassistant/components/trace/utils.py rename homeassistant/components/{automation => trace}/websocket_api.py (97%) create mode 100644 tests/components/trace/__init__.py rename tests/components/{automation/test_trace.py => trace/test_utils.py} (88%) rename tests/components/{automation => trace}/test_websocket_api.py (99%) diff --git a/CODEOWNERS b/CODEOWNERS index 2afbc288b0f9ee..31ce9706baa72f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -490,6 +490,7 @@ homeassistant/components/toon/* @frenck homeassistant/components/totalconnect/* @austinmroczek homeassistant/components/tplink/* @rytilahti @thegardenmonkey homeassistant/components/traccar/* @ludeeus +homeassistant/components/trace/* @home-assistant/core homeassistant/components/trafikverket_train/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/transmission/* @engrbm87 @JPHutchins diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 79c6dcc2312d24..1dd81afaa70a6d 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -61,7 +61,6 @@ from homeassistant.loader import bind_hass from homeassistant.util.dt import parse_datetime -from . import websocket_api from .config import AutomationConfig, async_validate_config_item # Not used except by packages to check config structure @@ -76,7 +75,7 @@ LOGGER, ) from .helpers import async_get_blueprints -from .trace import DATA_AUTOMATION_TRACE, trace_automation +from .trace import trace_automation # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -176,9 +175,6 @@ async def async_setup(hass, config): """Set up all automations.""" # Local import to avoid circular import hass.data[DOMAIN] = component = EntityComponent(LOGGER, DOMAIN, hass) - hass.data.setdefault(DATA_AUTOMATION_TRACE, {}) - - websocket_api.async_setup(hass) # To register the automation blueprints async_get_blueprints(hass) diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json index 2db56eb597faa7..2483f57de8eece 100644 --- a/homeassistant/components/automation/manifest.json +++ b/homeassistant/components/automation/manifest.json @@ -2,7 +2,7 @@ "domain": "automation", "name": "Automation", "documentation": "https://www.home-assistant.io/integrations/automation", - "dependencies": ["blueprint"], + "dependencies": ["blueprint", "trace"], "after_dependencies": [ "device_automation", "webhook" diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index 79fa4c844bc4e4..de199ad9310014 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -1,26 +1,17 @@ """Trace support for automation.""" from __future__ import annotations -from collections import OrderedDict from contextlib import contextmanager import datetime as dt -from datetime import timedelta from itertools import count -import logging -from typing import Any, Awaitable, Callable, Deque +from typing import Any, Deque -from homeassistant.core import Context, HomeAssistant, callback -from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder +from homeassistant.components.trace.const import DATA_TRACE, STORED_TRACES +from homeassistant.components.trace.utils import LimitedSizeDict +from homeassistant.core import Context from homeassistant.helpers.trace import TraceElement, trace_id_set -from homeassistant.helpers.typing import TemplateVarsType from homeassistant.util import dt as dt_util -DATA_AUTOMATION_TRACE = "automation_trace" -STORED_TRACES = 5 # Stored traces per automation - -_LOGGER = logging.getLogger(__name__) -AutomationActionType = Callable[[HomeAssistant, TemplateVarsType], Awaitable[None]] - # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -134,27 +125,6 @@ def as_short_dict(self) -> dict[str, Any]: return result -class LimitedSizeDict(OrderedDict): - """OrderedDict limited in size.""" - - def __init__(self, *args, **kwds): - """Initialize OrderedDict limited in size.""" - self.size_limit = kwds.pop("size_limit", None) - OrderedDict.__init__(self, *args, **kwds) - self._check_size_limit() - - def __setitem__(self, key, value): - """Set item and check dict size.""" - OrderedDict.__setitem__(self, key, value) - self._check_size_limit() - - def _check_size_limit(self): - """Check dict size and evict items in FIFO order if needed.""" - if self.size_limit is not None: - while len(self) > self.size_limit: - self.popitem(last=False) - - @contextmanager def trace_automation(hass, unique_id, config, context): """Trace action execution of automation with automation_id.""" @@ -162,7 +132,7 @@ def trace_automation(hass, unique_id, config, context): trace_id_set((unique_id, automation_trace.run_id)) if unique_id: - automation_traces = hass.data[DATA_AUTOMATION_TRACE] + automation_traces = hass.data[DATA_TRACE] if unique_id not in automation_traces: automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) automation_traces[unique_id][automation_trace.run_id] = automation_trace @@ -176,50 +146,3 @@ def trace_automation(hass, unique_id, config, context): finally: if unique_id: automation_trace.finished() - - -@callback -def get_debug_trace(hass, automation_id, run_id): - """Return a serializable debug trace.""" - return hass.data[DATA_AUTOMATION_TRACE][automation_id][run_id] - - -@callback -def get_debug_traces_for_automation(hass, automation_id, summary=False): - """Return a serializable list of debug traces for an automation.""" - traces = [] - - for trace in hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}).values(): - if summary: - traces.append(trace.as_short_dict()) - else: - traces.append(trace.as_dict()) - - return traces - - -@callback -def get_debug_traces(hass, summary=False): - """Return a serializable list of debug traces.""" - traces = [] - - for automation_id in hass.data[DATA_AUTOMATION_TRACE]: - traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) - - return traces - - -class TraceJSONEncoder(HAJSONEncoder): - """JSONEncoder that supports Home Assistant objects and falls back to repr(o).""" - - def default(self, o: Any) -> Any: - """Convert certain objects. - - Fall back to repr(o). - """ - if isinstance(o, timedelta): - return {"__type": str(type(o)), "total_seconds": o.total_seconds()} - try: - return super().default(o) - except TypeError: - return {"__type": str(type(o)), "repr": repr(o)} diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py new file mode 100644 index 00000000000000..0dc8cda6664f27 --- /dev/null +++ b/homeassistant/components/trace/__init__.py @@ -0,0 +1,12 @@ +"""Support for automation and script tracing and debugging.""" +from . import websocket_api +from .const import DATA_TRACE + +DOMAIN = "trace" + + +async def async_setup(hass, config): + """Initialize the trace integration.""" + hass.data.setdefault(DATA_TRACE, {}) + websocket_api.async_setup(hass) + return True diff --git a/homeassistant/components/trace/const.py b/homeassistant/components/trace/const.py new file mode 100644 index 00000000000000..547bdb35c7788c --- /dev/null +++ b/homeassistant/components/trace/const.py @@ -0,0 +1,4 @@ +"""Shared constants for automation and script tracing and debugging.""" + +DATA_TRACE = "trace" +STORED_TRACES = 5 # Stored traces per automation diff --git a/homeassistant/components/trace/manifest.json b/homeassistant/components/trace/manifest.json new file mode 100644 index 00000000000000..cdd857d00d35d5 --- /dev/null +++ b/homeassistant/components/trace/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "trace", + "name": "Trace", + "documentation": "https://www.home-assistant.io/integrations/automation", + "codeowners": [ + "@home-assistant/core" + ], + "quality_scale": "internal" +} diff --git a/homeassistant/components/trace/trace.py b/homeassistant/components/trace/trace.py new file mode 100644 index 00000000000000..cc51e3269ab21f --- /dev/null +++ b/homeassistant/components/trace/trace.py @@ -0,0 +1,35 @@ +"""Support for automation and script tracing and debugging.""" +from homeassistant.core import callback + +from .const import DATA_TRACE + + +@callback +def get_debug_trace(hass, automation_id, run_id): + """Return a serializable debug trace.""" + return hass.data[DATA_TRACE][automation_id][run_id] + + +@callback +def get_debug_traces_for_automation(hass, automation_id, summary=False): + """Return a serializable list of debug traces for an automation.""" + traces = [] + + for trace in hass.data[DATA_TRACE].get(automation_id, {}).values(): + if summary: + traces.append(trace.as_short_dict()) + else: + traces.append(trace.as_dict()) + + return traces + + +@callback +def get_debug_traces(hass, summary=False): + """Return a serializable list of debug traces.""" + traces = [] + + for automation_id in hass.data[DATA_TRACE]: + traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) + + return traces diff --git a/homeassistant/components/trace/utils.py b/homeassistant/components/trace/utils.py new file mode 100644 index 00000000000000..59bf8c98498e7f --- /dev/null +++ b/homeassistant/components/trace/utils.py @@ -0,0 +1,43 @@ +"""Helpers for automation and script tracing and debugging.""" +from collections import OrderedDict +from datetime import timedelta +from typing import Any + +from homeassistant.helpers.json import JSONEncoder as HAJSONEncoder + + +class LimitedSizeDict(OrderedDict): + """OrderedDict limited in size.""" + + def __init__(self, *args, **kwds): + """Initialize OrderedDict limited in size.""" + self.size_limit = kwds.pop("size_limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_size_limit() + + def __setitem__(self, key, value): + """Set item and check dict size.""" + OrderedDict.__setitem__(self, key, value) + self._check_size_limit() + + def _check_size_limit(self): + """Check dict size and evict items in FIFO order if needed.""" + if self.size_limit is not None: + while len(self) > self.size_limit: + self.popitem(last=False) + + +class TraceJSONEncoder(HAJSONEncoder): + """JSONEncoder that supports Home Assistant objects and falls back to repr(o).""" + + def default(self, o: Any) -> Any: + """Convert certain objects. + + Fall back to repr(o). + """ + if isinstance(o, timedelta): + return {"__type": str(type(o)), "total_seconds": o.total_seconds()} + try: + return super().default(o) + except TypeError: + return {"__type": str(type(o)), "repr": repr(o)} diff --git a/homeassistant/components/automation/websocket_api.py b/homeassistant/components/trace/websocket_api.py similarity index 97% rename from homeassistant/components/automation/websocket_api.py rename to homeassistant/components/trace/websocket_api.py index 7bd1b57d064b4a..9ac1828de143d9 100644 --- a/homeassistant/components/automation/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -24,12 +24,12 @@ ) from .trace import ( - DATA_AUTOMATION_TRACE, - TraceJSONEncoder, + DATA_TRACE, get_debug_trace, get_debug_traces, get_debug_traces_for_automation, ) +from .utils import TraceJSONEncoder # mypy: allow-untyped-calls, allow-untyped-defs @@ -101,11 +101,9 @@ def websocket_automation_trace_contexts(hass, connection, msg): automation_id = msg.get("automation_id") if automation_id is not None: - values = { - automation_id: hass.data[DATA_AUTOMATION_TRACE].get(automation_id, {}) - } + values = {automation_id: hass.data[DATA_TRACE].get(automation_id, {})} else: - values = hass.data[DATA_AUTOMATION_TRACE] + values = hass.data[DATA_TRACE] contexts = { trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} diff --git a/tests/components/trace/__init__.py b/tests/components/trace/__init__.py new file mode 100644 index 00000000000000..13937105ae3dfc --- /dev/null +++ b/tests/components/trace/__init__.py @@ -0,0 +1 @@ +"""The tests for Trace.""" diff --git a/tests/components/automation/test_trace.py b/tests/components/trace/test_utils.py similarity index 88% rename from tests/components/automation/test_trace.py rename to tests/components/trace/test_utils.py index 612a0ccfcab654..ce0f09bfdd8b0b 100644 --- a/tests/components/automation/test_trace.py +++ b/tests/components/trace/test_utils.py @@ -1,14 +1,14 @@ -"""Test Automation trace helpers.""" +"""Test trace helpers.""" from datetime import timedelta from homeassistant import core -from homeassistant.components import automation +from homeassistant.components import trace from homeassistant.util import dt as dt_util def test_json_encoder(hass): """Test the Trace JSON Encoder.""" - ha_json_enc = automation.trace.TraceJSONEncoder() + ha_json_enc = trace.utils.TraceJSONEncoder() state = core.State("test.test", "hello") # Test serializing a datetime diff --git a/tests/components/automation/test_websocket_api.py b/tests/components/trace/test_websocket_api.py similarity index 99% rename from tests/components/automation/test_websocket_api.py rename to tests/components/trace/test_websocket_api.py index 6f630c2747026b..882d857c01495b 100644 --- a/tests/components/automation/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -1,4 +1,4 @@ -"""Test Automation config panel.""" +"""Test Trace websocket API.""" from unittest.mock import patch from homeassistant.bootstrap import async_setup_component From 339a56e434c466f4dc814f667358ae11ff89ff30 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:45:17 +0100 Subject: [PATCH 1447/1818] Migrate integrations f-h to extend SensorEntity (#48212) --- homeassistant/components/fail2ban/sensor.py | 5 ++--- homeassistant/components/fastdotcom/sensor.py | 3 ++- homeassistant/components/fibaro/sensor.py | 5 ++--- homeassistant/components/fido/sensor.py | 5 ++--- homeassistant/components/file/sensor.py | 5 ++--- homeassistant/components/filesize/sensor.py | 5 ++--- homeassistant/components/filter/sensor.py | 16 ++++++++-------- homeassistant/components/fints/sensor.py | 7 +++---- .../components/fireservicerota/sensor.py | 3 ++- homeassistant/components/firmata/sensor.py | 4 ++-- homeassistant/components/fitbit/sensor.py | 5 ++--- homeassistant/components/fixer/sensor.py | 5 ++--- .../components/flick_electric/sensor.py | 4 ++-- homeassistant/components/flo/sensor.py | 15 ++++++++------- homeassistant/components/flume/sensor.py | 4 ++-- homeassistant/components/flunearyou/sensor.py | 3 ++- homeassistant/components/folder/sensor.py | 5 ++--- homeassistant/components/foobot/sensor.py | 3 ++- homeassistant/components/freebox/sensor.py | 4 ++-- homeassistant/components/fritzbox/sensor.py | 4 ++-- .../components/fritzbox_callmonitor/sensor.py | 5 ++--- .../components/fritzbox_netmonitor/sensor.py | 5 ++--- homeassistant/components/fronius/sensor.py | 5 ++--- .../components/garmin_connect/sensor.py | 4 ++-- homeassistant/components/gdacs/sensor.py | 4 ++-- homeassistant/components/geizhals/sensor.py | 5 ++--- homeassistant/components/geniushub/sensor.py | 5 +++-- .../components/geo_rss_events/sensor.py | 5 ++--- .../components/geonetnz_quakes/sensor.py | 4 ++-- .../components/geonetnz_volcano/sensor.py | 4 ++-- homeassistant/components/github/sensor.py | 5 ++--- homeassistant/components/gitlab_ci/sensor.py | 5 ++--- homeassistant/components/gitter/sensor.py | 5 ++--- homeassistant/components/glances/sensor.py | 4 ++-- homeassistant/components/gogogate2/sensor.py | 5 +++-- .../components/google_travel_time/sensor.py | 5 ++--- .../components/google_wifi/sensor.py | 5 ++--- homeassistant/components/gpsd/sensor.py | 5 ++--- .../components/greeneye_monitor/sensor.py | 4 ++-- .../components/growatt_server/sensor.py | 5 ++--- homeassistant/components/gtfs/sensor.py | 5 ++--- homeassistant/components/guardian/sensor.py | 5 +++-- homeassistant/components/habitica/sensor.py | 6 +++--- homeassistant/components/hassio/sensor.py | 5 +++-- .../components/haveibeenpwned/sensor.py | 5 ++--- homeassistant/components/hddtemp/sensor.py | 5 ++--- .../components/here_travel_time/sensor.py | 5 ++--- .../components/history_stats/sensor.py | 5 ++--- homeassistant/components/hive/sensor.py | 5 ++--- .../components/home_connect/sensor.py | 3 ++- .../components/homekit_controller/sensor.py | 13 +++++++------ homeassistant/components/homematic/sensor.py | 3 ++- .../components/homematicip_cloud/sensor.py | 19 ++++++++++--------- homeassistant/components/hp_ilo/sensor.py | 5 ++--- homeassistant/components/htu21d/sensor.py | 5 ++--- homeassistant/components/huawei_lte/sensor.py | 3 ++- homeassistant/components/hue/sensor.py | 6 +++--- homeassistant/components/huisbaasje/sensor.py | 3 ++- .../hunterdouglas_powerview/sensor.py | 3 ++- .../components/hvv_departures/sensor.py | 4 ++-- homeassistant/components/hydrawise/sensor.py | 4 ++-- 61 files changed, 154 insertions(+), 167 deletions(-) diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 30aa0c6983892d..87a8460f1496fc 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -6,10 +6,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_FILE_PATH, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -45,7 +44,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(device_list, True) -class BanSensor(Entity): +class BanSensor(SensorEntity): """Implementation of a fail2ban sensor.""" def __init__(self, name, jail, log_parser): diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index ad3711b131923a..b4406a4de95f8b 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -1,4 +1,5 @@ """Support for Fast.com internet speed testing sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DATA_RATE_MEGABITS_PER_SECOND from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -14,7 +15,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SpeedtestSensor(hass.data[FASTDOTCOM_DOMAIN])]) -class SpeedtestSensor(RestoreEntity): +class SpeedtestSensor(RestoreEntity, SensorEntity): """Implementation of a FAst.com sensor.""" def __init__(self, speedtest_data): diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index 40a843bc7bba02..fd25a3ce7eba90 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,5 +1,5 @@ """Support for Fibaro sensors.""" -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_CO2, @@ -11,7 +11,6 @@ TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from . import FIBARO_DEVICES, FibaroDevice @@ -55,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class FibaroSensor(FibaroDevice, Entity): +class FibaroSensor(FibaroDevice, SensorEntity): """Representation of a Fibaro Sensor.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index fe9c60c85a6e39..55ec455d8f15bd 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -11,7 +11,7 @@ from pyfido.client import PyFidoError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -21,7 +21,6 @@ TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class FidoSensor(Entity): +class FidoSensor(SensorEntity): """Implementation of a Fido sensor.""" def __init__(self, fido_data, sensor_type, name, number): diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index 3368bd878d5b25..5d8a9475235ca8 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -4,7 +4,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_FILE_PATH, CONF_NAME, @@ -12,7 +12,6 @@ CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -46,7 +45,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= _LOGGER.error("'%s' is not an allowed directory", file_path) -class FileSensor(Entity): +class FileSensor(SensorEntity): """Implementation of a file sensor.""" def __init__(self, name, file_path, unit_of_measurement, value_template): diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 312805e4942a77..856b29364aeafe 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -5,10 +5,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import DATA_MEGABYTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.reload import setup_reload_service from . import DOMAIN, PLATFORMS @@ -40,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class Filesize(Entity): +class Filesize(SensorEntity): """Encapsulates file size information.""" def __init__(self, path): diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index d74491dd9c6eee..2f1705f5f4dd38 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -18,6 +18,7 @@ DEVICE_CLASSES as SENSOR_DEVICE_CLASSES, DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -31,7 +32,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.util.decorator import Registry @@ -179,7 +179,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SensorFilter(name, entity_id, filters)]) -class SensorFilter(Entity): +class SensorFilter(SensorEntity): """Representation of a Filter Sensor.""" def __init__(self, name, entity_id, filters): @@ -454,7 +454,7 @@ def filter_state(self, new_state): @FILTERS.register(FILTER_NAME_RANGE) -class RangeFilter(Filter): +class RangeFilter(Filter, SensorEntity): """Range filter. Determines if new state is in the range of upper_bound and lower_bound. @@ -509,7 +509,7 @@ def _filter_state(self, new_state): @FILTERS.register(FILTER_NAME_OUTLIER) -class OutlierFilter(Filter): +class OutlierFilter(Filter, SensorEntity): """BASIC outlier filter. Determines if new state is in a band around the median. @@ -547,7 +547,7 @@ def _filter_state(self, new_state): @FILTERS.register(FILTER_NAME_LOWPASS) -class LowPassFilter(Filter): +class LowPassFilter(Filter, SensorEntity): """BASIC Low Pass Filter.""" def __init__(self, window_size, precision, entity, time_constant: int): @@ -571,7 +571,7 @@ def _filter_state(self, new_state): @FILTERS.register(FILTER_NAME_TIME_SMA) -class TimeSMAFilter(Filter): +class TimeSMAFilter(Filter, SensorEntity): """Simple Moving Average (SMA) Filter. The window_size is determined by time, and SMA is time weighted. @@ -617,7 +617,7 @@ def _filter_state(self, new_state): @FILTERS.register(FILTER_NAME_THROTTLE) -class ThrottleFilter(Filter): +class ThrottleFilter(Filter, SensorEntity): """Throttle Filter. One sample per window. @@ -640,7 +640,7 @@ def _filter_state(self, new_state): @FILTERS.register(FILTER_NAME_TIME_THROTTLE) -class TimeThrottleFilter(Filter): +class TimeThrottleFilter(Filter, SensorEntity): """Time Throttle Filter. One sample per time period. diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 96d6ecc2166954..e7faff46155c2e 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -8,10 +8,9 @@ from fints.dialog import FinTSDialogError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_PIN, CONF_URL, CONF_USERNAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -154,7 +153,7 @@ def detect_accounts(self): return balance_accounts, holdings_accounts -class FinTsAccount(Entity): +class FinTsAccount(SensorEntity): """Sensor for a FinTS balance account. A balance account contains an amount of money (=balance). The amount may @@ -206,7 +205,7 @@ def icon(self) -> str: return ICON -class FinTsHoldingsAccount(Entity): +class FinTsHoldingsAccount(SensorEntity): """Sensor for a FinTS holdings account. A holdings account does not contain money but rather some financial diff --git a/homeassistant/components/fireservicerota/sensor.py b/homeassistant/components/fireservicerota/sensor.py index 7dc8f546c984fa..04d8c97a4a5e71 100644 --- a/homeassistant/components/fireservicerota/sensor.py +++ b/homeassistant/components/fireservicerota/sensor.py @@ -1,6 +1,7 @@ """Sensor platform for FireServiceRota integration.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -21,7 +22,7 @@ async def async_setup_entry( async_add_entities([IncidentsSensor(client)]) -class IncidentsSensor(RestoreEntity): +class IncidentsSensor(RestoreEntity, SensorEntity): """Representation of FireServiceRota incidents sensor.""" def __init__(self, client): diff --git a/homeassistant/components/firmata/sensor.py b/homeassistant/components/firmata/sensor.py index cb9db1f11e5f55..fedac6f76d9070 100644 --- a/homeassistant/components/firmata/sensor.py +++ b/homeassistant/components/firmata/sensor.py @@ -2,10 +2,10 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CONF_PIN from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from .const import CONF_DIFFERENTIAL, CONF_PIN_MODE, DOMAIN from .entity import FirmataPinEntity @@ -42,7 +42,7 @@ async def async_setup_entry( async_add_entities(new_entities) -class FirmataSensor(FirmataPinEntity, Entity): +class FirmataSensor(FirmataPinEntity, SensorEntity): """Representation of a sensor on a Firmata board.""" async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index b90d33adaffa6c..8571d31bc8abc0 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_CLIENT_ID, @@ -25,7 +25,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.network import get_url from homeassistant.util.json import load_json, save_json @@ -403,7 +402,7 @@ def get(self, request): return html_response -class FitbitSensor(Entity): +class FitbitSensor(SensorEntity): """Implementation of a Fitbit sensor.""" def __init__( diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index 9641f596e6182c..9214dd6907e027 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -6,10 +6,9 @@ from fixerio.exceptions import FixerioException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME, CONF_TARGET import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ExchangeRateSensor(data, name, target)], True) -class ExchangeRateSensor(Entity): +class ExchangeRateSensor(SensorEntity): """Representation of a Exchange sensor.""" def __init__(self, data, name, target): diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index 4f152c54e3c4e4..e68806002126e2 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -5,10 +5,10 @@ import async_timeout from pyflick import FlickAPI, FlickPrice +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utcnow from .const import ATTR_COMPONENTS, ATTR_END_AT, ATTR_START_AT, DOMAIN @@ -33,7 +33,7 @@ async def async_setup_entry( async_add_entities([FlickPricingSensor(api)], True) -class FlickPricingSensor(Entity): +class FlickPricingSensor(SensorEntity): """Entity object for Flick Electric sensor.""" def __init__(self, api: FlickAPI): diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index 18ee067af5a906..1e362e75f8cbf1 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -1,6 +1,7 @@ """Support for Flo Water Monitor sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, @@ -56,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class FloDailyUsageSensor(FloEntity): +class FloDailyUsageSensor(FloEntity, SensorEntity): """Monitors the daily water usage.""" def __init__(self, device): @@ -82,7 +83,7 @@ def unit_of_measurement(self) -> str: return VOLUME_GALLONS -class FloSystemModeSensor(FloEntity): +class FloSystemModeSensor(FloEntity, SensorEntity): """Monitors the current Flo system mode.""" def __init__(self, device): @@ -98,7 +99,7 @@ def state(self) -> str | None: return self._device.current_system_mode -class FloCurrentFlowRateSensor(FloEntity): +class FloCurrentFlowRateSensor(FloEntity, SensorEntity): """Monitors the current water flow rate.""" def __init__(self, device): @@ -124,7 +125,7 @@ def unit_of_measurement(self) -> str: return "gpm" -class FloTemperatureSensor(FloEntity): +class FloTemperatureSensor(FloEntity, SensorEntity): """Monitors the temperature.""" def __init__(self, name, device): @@ -150,7 +151,7 @@ def device_class(self) -> str | None: return DEVICE_CLASS_TEMPERATURE -class FloHumiditySensor(FloEntity): +class FloHumiditySensor(FloEntity, SensorEntity): """Monitors the humidity.""" def __init__(self, device): @@ -176,7 +177,7 @@ def device_class(self) -> str | None: return DEVICE_CLASS_HUMIDITY -class FloPressureSensor(FloEntity): +class FloPressureSensor(FloEntity, SensorEntity): """Monitors the water pressure.""" def __init__(self, device): @@ -202,7 +203,7 @@ def device_class(self) -> str | None: return DEVICE_CLASS_PRESSURE -class FloBatterySensor(FloEntity): +class FloBatterySensor(FloEntity, SensorEntity): """Monitors the battery level for battery-powered leak detectors.""" def __init__(self, device): diff --git a/homeassistant/components/flume/sensor.py b/homeassistant/components/flume/sensor.py index 65584239910bfa..d890443d238edc 100644 --- a/homeassistant/components/flume/sensor.py +++ b/homeassistant/components/flume/sensor.py @@ -6,7 +6,7 @@ from pyflume import FlumeData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_CLIENT_ID, @@ -108,7 +108,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(flume_entity_list) -class FlumeSensor(CoordinatorEntity): +class FlumeSensor(CoordinatorEntity, SensorEntity): """Representation of the Flume sensor.""" def __init__(self, coordinator, flume_device, flume_query_sensor, name, device_id): diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py index 225ed15d98b7b6..066126c390e71e 100644 --- a/homeassistant/components/flunearyou/sensor.py +++ b/homeassistant/components/flunearyou/sensor.py @@ -1,4 +1,5 @@ """Support for user- and CDC-based flu info sensors from Flu Near You.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_STATE, @@ -85,7 +86,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class FluNearYouSensor(CoordinatorEntity): +class FluNearYouSensor(CoordinatorEntity, SensorEntity): """Define a base Flu Near You sensor.""" def __init__(self, coordinator, config_entry, sensor_type, name, icon, unit): diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index a6dccf576026ad..707f22f98ba76c 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -6,10 +6,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import DATA_MEGABYTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -51,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([folder], True) -class Folder(Entity): +class Folder(SensorEntity): """Representation of a folder.""" ICON = "mdi:folder" diff --git a/homeassistant/components/foobot/sensor.py b/homeassistant/components/foobot/sensor.py index 9ca265ba9ef4d0..996ac1b1049172 100644 --- a/homeassistant/components/foobot/sensor.py +++ b/homeassistant/components/foobot/sensor.py @@ -7,6 +7,7 @@ from foobot_async import FoobotClient import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_TIME, @@ -91,7 +92,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class FoobotSensor(Entity): +class FoobotSensor(SensorEntity): """Implementation of a Foobot sensor.""" def __init__(self, data, device, sensor_type): diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 9f125ff69bc63b..fd0685f76676f7 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -3,11 +3,11 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_RATE_KILOBYTES_PER_SECOND from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util @@ -74,7 +74,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class FreeboxSensor(Entity): +class FreeboxSensor(SensorEntity): """Representation of a Freebox sensor.""" def __init__( diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 470d110429c310..4d9c1693c1f60d 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -1,8 +1,8 @@ """Support for AVM Fritz!Box smarthome temperature sensor only devices.""" import requests +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_DEVICES, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from .const import ( ATTR_STATE_DEVICE_LOCKED, @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class FritzBoxTempSensor(Entity): +class FritzBoxTempSensor(SensorEntity): """The entity class for Fritzbox temperature sensors.""" def __init__(self, device, fritz): diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 82ce075268b0a4..a325c0ca71dccc 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -8,7 +8,7 @@ from fritzconnection.core.fritzmonitor import FritzMonitor import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, @@ -19,7 +19,6 @@ EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import ( ATTR_PREFIXES, @@ -102,7 +101,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([sensor]) -class FritzBoxCallSensor(Entity): +class FritzBoxCallSensor(SensorEntity): """Implementation of a Fritz!Box call monitor.""" def __init__(self, name, unique_id, fritzbox_phonebook, prefixes, host, port): diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py index 320144ae163184..3c37de7664c665 100644 --- a/homeassistant/components/fritzbox_netmonitor/sensor.py +++ b/homeassistant/components/fritzbox_netmonitor/sensor.py @@ -7,10 +7,9 @@ from requests.exceptions import RequestException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_NAME, STATE_UNAVAILABLE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([FritzboxMonitorSensor(name, fstatus)], True) -class FritzboxMonitorSensor(Entity): +class FritzboxMonitorSensor(SensorEntity): """Implementation of a fritzbox monitor sensor.""" def __init__(self, name, fstatus): diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 7e578701e2caa5..a908f2605f8ca1 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -8,7 +8,7 @@ from pyfronius import Fronius import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_MONITORED_CONDITIONS, @@ -18,7 +18,6 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -252,7 +251,7 @@ async def _update(self): return await self.bridge.current_power_flow() -class FroniusTemplateSensor(Entity): +class FroniusTemplateSensor(SensorEntity): """Sensor for the single values (e.g. pv power, ac power).""" def __init__(self, parent: FroniusAdapter, name): diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index ba43a894cfe38a..46db6e615f1c40 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -10,9 +10,9 @@ GarminConnectTooManyRequestsError, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_ID -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .alarm_util import calculate_next_active_alarms @@ -70,7 +70,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class GarminConnectSensor(Entity): +class GarminConnectSensor(SensorEntity): """Representation of a Garmin Connect Sensor.""" def __init__( diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index 7ab63fd6c6ab58..2e4759088fc5b2 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -3,9 +3,9 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.util import dt from .const import DEFAULT_ICON, DOMAIN, FEED @@ -34,7 +34,7 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.debug("Sensor setup done") -class GdacsSensor(Entity): +class GdacsSensor(SensorEntity): """This is a status sensor for the GDACS integration.""" def __init__(self, config_entry_id, config_unique_id, config_title, manager): diff --git a/homeassistant/components/geizhals/sensor.py b/homeassistant/components/geizhals/sensor.py index 5f11ae7d0cac68..04267e696ea47b 100644 --- a/homeassistant/components/geizhals/sensor.py +++ b/homeassistant/components/geizhals/sensor.py @@ -4,10 +4,9 @@ from geizhals import Device, Geizhals import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle CONF_DESCRIPTION = "description" @@ -38,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([Geizwatch(name, description, product_id, domain)], True) -class Geizwatch(Entity): +class Geizwatch(SensorEntity): """Implementation of Geizwatch.""" def __init__(self, name, description, product_id, domain): diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 10c53994529286..3234ccd577ff8c 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -4,6 +4,7 @@ from datetime import timedelta from typing import Any +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.typing import ConfigType, HomeAssistantType import homeassistant.util.dt as dt_util @@ -38,7 +39,7 @@ async def async_setup_platform( async_add_entities(sensors + issues, update_before_add=True) -class GeniusBattery(GeniusDevice): +class GeniusBattery(GeniusDevice, SensorEntity): """Representation of a Genius Hub sensor.""" def __init__(self, broker, device, state_attr) -> None: @@ -88,7 +89,7 @@ def state(self) -> str: return level if level != 255 else 0 -class GeniusIssue(GeniusEntity): +class GeniusIssue(GeniusEntity, SensorEntity): """Representation of a Genius Hub sensor.""" def __init__(self, broker, level) -> None: diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index 00ff63982fda48..df5f11850fdef3 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -12,7 +12,7 @@ from georss_generic_client import GenericFeed import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, @@ -23,7 +23,6 @@ LENGTH_KILOMETERS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -96,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class GeoRssServiceSensor(Entity): +class GeoRssServiceSensor(SensorEntity): """Representation of a Sensor.""" def __init__( diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index 565e022f0366a7..94c7965663af3f 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -3,9 +3,9 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.util import dt from .const import DOMAIN, FEED @@ -35,7 +35,7 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.debug("Sensor setup done") -class GeonetnzQuakesSensor(Entity): +class GeonetnzQuakesSensor(SensorEntity): """This is a status sensor for the GeoNet NZ Quakes integration.""" def __init__(self, config_entry_id, config_unique_id, config_title, manager): diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index 2ec4940f88d8ed..c0cc68014378ae 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -3,6 +3,7 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -12,7 +13,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.util import dt from homeassistant.util.unit_system import IMPERIAL_SYSTEM @@ -54,7 +54,7 @@ def async_add_sensor(feed_manager, external_id, unit_system): _LOGGER.debug("Sensor setup done") -class GeonetnzVolcanoSensor(Entity): +class GeonetnzVolcanoSensor(SensorEntity): """This represents an external event with GeoNet NZ Volcano feed data.""" def __init__(self, config_entry_id, feed_manager, external_id, unit_system): diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index f5f40c270a8419..c7812fa621d6bc 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -5,7 +5,7 @@ import github import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_NAME, CONF_ACCESS_TOKEN, @@ -14,7 +14,6 @@ CONF_URL, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -72,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class GitHubSensor(Entity): +class GitHubSensor(SensorEntity): """Representation of a GitHub sensor.""" def __init__(self, github_data): diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py index 5eaa62e601d75c..0b619853348b25 100644 --- a/homeassistant/components/gitlab_ci/sensor.py +++ b/homeassistant/components/gitlab_ci/sensor.py @@ -5,7 +5,7 @@ from gitlab import Gitlab, GitlabAuthenticationError, GitlabGetError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, @@ -14,7 +14,6 @@ CONF_URL, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -66,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GitLabSensor(_gitlab_data, _name)], True) -class GitLabSensor(Entity): +class GitLabSensor(SensorEntity): """Representation of a GitLab sensor.""" def __init__(self, gitlab_data, name): diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py index 3f7888d8593f88..20b68b2e5a9b05 100644 --- a/homeassistant/components/gitter/sensor.py +++ b/homeassistant/components/gitter/sensor.py @@ -5,10 +5,9 @@ from gitterpy.errors import GitterRoomError, GitterTokenError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_ROOM import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GitterSensor(gitter, room, name, username)], True) -class GitterSensor(Entity): +class GitterSensor(SensorEntity): """Representation of a Gitter sensor.""" def __init__(self, data, room, name, username): diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 006333b321ac36..3e663621625549 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -1,8 +1,8 @@ """Support gathering system information of hosts which are running glances.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DATA_UPDATED, DOMAIN, SENSOR_TYPES @@ -60,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class GlancesSensor(Entity): +class GlancesSensor(SensorEntity): """Implementation of a Glances sensor.""" def __init__( diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index 2bdb1b3170c0cb..9062bc0b352af6 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -6,6 +6,7 @@ from gogogate2_api.common import AbstractDoor, get_configured_doors +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -48,7 +49,7 @@ async def async_setup_entry( async_add_entities(sensors) -class DoorSensorBattery(GoGoGate2Entity): +class DoorSensorBattery(GoGoGate2Entity, SensorEntity): """Battery sensor entity for gogogate2 door sensor.""" def __init__( @@ -86,7 +87,7 @@ def extra_state_attributes(self): return None -class DoorSensorTemperature(GoGoGate2Entity): +class DoorSensorTemperature(GoGoGate2Entity, SensorEntity): """Temperature sensor entity for gogogate2 door sensor.""" def __init__( diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 109b65a4225b63..11bfb871a1b3d3 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -5,7 +5,7 @@ import googlemaps import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -18,7 +18,6 @@ ) from homeassistant.helpers import location import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -180,7 +179,7 @@ def run_setup(event): hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup) -class GoogleTravelTimeSensor(Entity): +class GoogleTravelTimeSensor(SensorEntity): """Representation of a Google travel time sensor.""" def __init__(self, hass, name, api_key, origin, destination, options): diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index 9dfa26fab75c61..cf5a804e5a5404 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -14,7 +14,6 @@ TIME_DAYS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class GoogleWifiSensor(Entity): +class GoogleWifiSensor(SensorEntity): """Representation of a Google Wifi sensor.""" def __init__(self, api, name, variable): diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py index 978589e9a744a9..2f97f62337c47a 100644 --- a/homeassistant/components/gpsd/sensor.py +++ b/homeassistant/components/gpsd/sensor.py @@ -5,7 +5,7 @@ from gps3.agps3threaded import AGPS3mechanism import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -15,7 +15,6 @@ CONF_PORT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GpsdSensor(hass, name, host, port)]) -class GpsdSensor(Entity): +class GpsdSensor(SensorEntity): """Representation of a GPS receiver available via GPSD.""" def __init__(self, hass, name, host, port): diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index 84352328312564..4e792bf56e4959 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -1,4 +1,5 @@ """Support for the sensors in a GreenEye Monitor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_NAME, CONF_SENSOR_TYPE, @@ -9,7 +10,6 @@ TIME_SECONDS, VOLT, ) -from homeassistant.helpers.entity import Entity from . import ( CONF_COUNTED_QUANTITY, @@ -85,7 +85,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class GEMSensor(Entity): +class GEMSensor(SensorEntity): """Base class for GreenEye Monitor sensors.""" def __init__(self, monitor_serial_number, name, sensor_type, number): diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 50d269207de161..86b88872a8ab09 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -7,7 +7,7 @@ import growattServer import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -28,7 +28,6 @@ VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -416,7 +415,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class GrowattInverter(Entity): +class GrowattInverter(SensorEntity): """Representation of a Growatt Sensor.""" def __init__(self, probe, name, sensor, unique_id): diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 737348548a8b1e..46a31f464a1aac 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -11,7 +11,7 @@ from sqlalchemy.sql import text import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, @@ -20,7 +20,6 @@ STATE_UNKNOWN, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ( ConfigType, DiscoveryInfoType, @@ -519,7 +518,7 @@ def setup_platform( ) -class GTFSDepartureSensor(Entity): +class GTFSDepartureSensor(SensorEntity): """Implementation of a GTFS departure sensor.""" def __init__( diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 9b778128d19512..48807c9cfeb789 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -3,6 +3,7 @@ from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -110,7 +111,7 @@ def add_new_paired_sensor(uid: str) -> None: async_add_entities(sensors) -class PairedSensorSensor(PairedSensorEntity): +class PairedSensorSensor(PairedSensorEntity, SensorEntity): """Define a binary sensor related to a Guardian valve controller.""" def __init__( @@ -153,7 +154,7 @@ def _async_update_from_latest_data(self) -> None: self._state = self.coordinator.data["temperature"] -class ValveControllerSensor(ValveControllerEntity): +class ValveControllerSensor(ValveControllerEntity, SensorEntity): """Define a generic Guardian sensor.""" def __init__( diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index dd17243cc9c536..52748ddadad8a3 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -5,8 +5,8 @@ from aiohttp import ClientResponseError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, HTTP_TOO_MANY_REQUESTS -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import DOMAIN @@ -125,7 +125,7 @@ async def update(self): ) -class HabitipySensor(Entity): +class HabitipySensor(SensorEntity): """A generic Habitica sensor.""" def __init__(self, name, sensor_name, updater): @@ -165,7 +165,7 @@ def unit_of_measurement(self): return self._sensor_type.unit -class HabitipyTaskSensor(Entity): +class HabitipyTaskSensor(SensorEntity): """A Habitica task sensor.""" def __init__(self, name, task_name, updater): diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index ae2bd20d6edd45..c41c0dc5090384 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -3,6 +3,7 @@ from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity @@ -36,7 +37,7 @@ async def async_setup_entry( async_add_entities(entities) -class HassioAddonSensor(HassioAddonEntity): +class HassioAddonSensor(HassioAddonEntity, SensorEntity): """Sensor to track a Hass.io add-on attribute.""" @property @@ -45,7 +46,7 @@ def state(self) -> str: return self.addon_info[self.attribute_name] -class HassioOSSensor(HassioOSEntity): +class HassioOSSensor(HassioOSEntity, SensorEntity): """Sensor to track a Hass.io add-on attribute.""" @property diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 5aa336905e998d..55b369c2fde8f0 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -6,7 +6,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_point_in_time from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -54,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class HaveIBeenPwnedSensor(Entity): +class HaveIBeenPwnedSensor(SensorEntity): """Implementation of a HaveIBeenPwned sensor.""" def __init__(self, data, email): diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index e5f6621defecc7..4376c7f128985f 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DISKS, CONF_HOST, @@ -16,7 +16,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -60,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class HddTempSensor(Entity): +class HddTempSensor(SensorEntity): """Representation of a HDDTemp sensor.""" def __init__(self, name, disk, hddtemp): diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 976c591f81041e..e4fbc0b2a892ca 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -8,7 +8,7 @@ import herepy import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -26,7 +26,6 @@ from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers import location import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import DiscoveryInfoType import homeassistant.util.dt as dt @@ -215,7 +214,7 @@ def _are_valid_client_credentials(here_client: herepy.RoutingApi) -> bool: return True -class HERETravelTimeSensor(Entity): +class HERETravelTimeSensor(SensorEntity): """Representation of a HERE travel time sensor.""" def __init__( diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 7658146d102e01..b8d3dc39187abe 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components import history -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_ENTITY_ID, CONF_NAME, @@ -19,7 +19,6 @@ from homeassistant.core import CoreState, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import setup_reload_service import homeassistant.util.dt as dt_util @@ -102,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class HistoryStatsSensor(Entity): +class HistoryStatsSensor(SensorEntity): """Representation of a HistoryStats sensor.""" def __init__( diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index 53cc643250c332..518f3286231601 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -2,8 +2,7 @@ from datetime import timedelta -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity from . import HiveEntity from .const import DOMAIN @@ -27,7 +26,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities, True) -class HiveSensorEntity(HiveEntity, Entity): +class HiveSensorEntity(HiveEntity, SensorEntity): """Hive Sensor Entity.""" @property diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index 064ae033fb0d56..463de6cda51458 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_ENTITIES, DEVICE_CLASS_TIMESTAMP import homeassistant.util.dt as dt_util @@ -27,7 +28,7 @@ def get_entities(): async_add_entities(await hass.async_add_executor_job(get_entities), True) -class HomeConnectSensor(HomeConnectEntity): +class HomeConnectSensor(HomeConnectEntity, SensorEntity): """Sensor class for Home Connect.""" def __init__(self, device, desc, key, unit, icon, device_class, sign=1): diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 094d0a500d1a98..2ae264fabb92cd 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -2,6 +2,7 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, @@ -39,7 +40,7 @@ } -class HomeKitHumiditySensor(HomeKitEntity): +class HomeKitHumiditySensor(HomeKitEntity, SensorEntity): """Representation of a Homekit humidity sensor.""" def get_characteristic_types(self): @@ -72,7 +73,7 @@ def state(self): return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) -class HomeKitTemperatureSensor(HomeKitEntity): +class HomeKitTemperatureSensor(HomeKitEntity, SensorEntity): """Representation of a Homekit temperature sensor.""" def get_characteristic_types(self): @@ -105,7 +106,7 @@ def state(self): return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT) -class HomeKitLightSensor(HomeKitEntity): +class HomeKitLightSensor(HomeKitEntity, SensorEntity): """Representation of a Homekit light level sensor.""" def get_characteristic_types(self): @@ -138,7 +139,7 @@ def state(self): return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT) -class HomeKitCarbonDioxideSensor(HomeKitEntity): +class HomeKitCarbonDioxideSensor(HomeKitEntity, SensorEntity): """Representation of a Homekit Carbon Dioxide sensor.""" def get_characteristic_types(self): @@ -166,7 +167,7 @@ def state(self): return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL) -class HomeKitBatterySensor(HomeKitEntity): +class HomeKitBatterySensor(HomeKitEntity, SensorEntity): """Representation of a Homekit battery sensor.""" def get_characteristic_types(self): @@ -233,7 +234,7 @@ def state(self): return self.service.value(CharacteristicsTypes.BATTERY_LEVEL) -class SimpleSensor(CharacteristicEntity): +class SimpleSensor(CharacteristicEntity, SensorEntity): """ A simple sensor for a single characteristic. diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index e6439c451c1670..4525d5a48fcbf0 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -1,6 +1,7 @@ """Support for HomeMatic sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEGREE, DEVICE_CLASS_HUMIDITY, @@ -97,7 +98,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMSensor(HMDevice): +class HMSensor(HMDevice, SensorEntity): """Representation of a HomeMatic sensor.""" @property diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 6f4acc6137a3f0..9e6e96232b46cb 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -26,6 +26,7 @@ ) from homematicip.base.enums import ValveState +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -123,7 +124,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipAccesspointDutyCycle(HomematicipGenericEntity): +class HomematicipAccesspointDutyCycle(HomematicipGenericEntity, SensorEntity): """Representation of then HomeMaticIP access point.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -146,7 +147,7 @@ def unit_of_measurement(self) -> str: return PERCENTAGE -class HomematicipHeatingThermostat(HomematicipGenericEntity): +class HomematicipHeatingThermostat(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP heating thermostat.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -175,7 +176,7 @@ def unit_of_measurement(self) -> str: return PERCENTAGE -class HomematicipHumiditySensor(HomematicipGenericEntity): +class HomematicipHumiditySensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP humidity sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -198,7 +199,7 @@ def unit_of_measurement(self) -> str: return PERCENTAGE -class HomematicipTemperatureSensor(HomematicipGenericEntity): +class HomematicipTemperatureSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP thermometer.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -235,7 +236,7 @@ def extra_state_attributes(self) -> dict[str, Any]: return state_attr -class HomematicipIlluminanceSensor(HomematicipGenericEntity): +class HomematicipIlluminanceSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP Illuminance sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -273,7 +274,7 @@ def extra_state_attributes(self) -> dict[str, Any]: return state_attr -class HomematicipPowerSensor(HomematicipGenericEntity): +class HomematicipPowerSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP power measuring sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -296,7 +297,7 @@ def unit_of_measurement(self) -> str: return POWER_WATT -class HomematicipWindspeedSensor(HomematicipGenericEntity): +class HomematicipWindspeedSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP wind speed sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -329,7 +330,7 @@ def extra_state_attributes(self) -> dict[str, Any]: return state_attr -class HomematicipTodayRainSensor(HomematicipGenericEntity): +class HomematicipTodayRainSensor(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP rain counter of a day sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -347,7 +348,7 @@ def unit_of_measurement(self) -> str: return LENGTH_MILLIMETERS -class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity): +class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity, SensorEntity): """Representation of the HomematicIP passage detector delta counter.""" @property diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index 0126ab780ce715..297bfa5264f21b 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -5,7 +5,7 @@ import hpilo import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -18,7 +18,6 @@ CONF_VALUE_TEMPLATE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -100,7 +99,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HpIloSensor(Entity): +class HpIloSensor(SensorEntity): """Representation of a HP iLO sensor.""" def __init__( diff --git a/homeassistant/components/htu21d/sensor.py b/homeassistant/components/htu21d/sensor.py index 615177ed6af233..d32eebf6d5fe54 100644 --- a/homeassistant/components/htu21d/sensor.py +++ b/homeassistant/components/htu21d/sensor.py @@ -7,10 +7,9 @@ import smbus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, PERCENTAGE, TEMP_FAHRENHEIT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -70,7 +69,7 @@ def update(self): self.sensor.update() -class HTU21DSensor(Entity): +class HTU21DSensor(SensorEntity): """Implementation of the HTU21D sensor.""" def __init__(self, htu21d_client, name, variable, unit): diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index c0773fdf80849f..c6cb93f0e678ab 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -12,6 +12,7 @@ DEVICE_CLASS_BATTERY, DEVICE_CLASS_SIGNAL_STRENGTH, DOMAIN as SENSOR_DOMAIN, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -373,7 +374,7 @@ def format_default(value: StateType) -> tuple[StateType, str | None]: @attr.s -class HuaweiLteSensor(HuaweiLteBaseEntity): +class HuaweiLteSensor(HuaweiLteBaseEntity, SensorEntity): """Huawei LTE sensor entity.""" key: str = attr.ib() diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py index a3c5fcc3fa710b..6ac8d134327fe0 100644 --- a/homeassistant/components/hue/sensor.py +++ b/homeassistant/components/hue/sensor.py @@ -6,6 +6,7 @@ TYPE_ZLL_TEMPERATURE, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, @@ -14,7 +15,6 @@ PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from .const import DOMAIN as HUE_DOMAIN from .sensor_base import SENSOR_CONFIG_MAP, GenericHueSensor, GenericZLLSensor @@ -31,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ].sensor_manager.async_register_component("sensor", async_add_entities) -class GenericHueGaugeSensorEntity(GenericZLLSensor, Entity): +class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity): """Parent class for all 'gauge' Hue device sensors.""" async def _async_update_ha_state(self, *args, **kwargs): @@ -88,7 +88,7 @@ def state(self): return self.sensor.temperature / 100 -class HueBattery(GenericHueSensor): +class HueBattery(GenericHueSensor, SensorEntity): """Battery class for when a batt-powered device is only represented as an event.""" @property diff --git a/homeassistant/components/huisbaasje/sensor.py b/homeassistant/components/huisbaasje/sensor.py index e84052fe029626..3acf39a140da77 100644 --- a/homeassistant/components/huisbaasje/sensor.py +++ b/homeassistant/components/huisbaasje/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID, POWER_WATT from homeassistant.core import HomeAssistant @@ -23,7 +24,7 @@ async def async_setup_entry( ) -class HuisbaasjeSensor(CoordinatorEntity): +class HuisbaasjeSensor(CoordinatorEntity, SensorEntity): """Defines a Huisbaasje sensor.""" def __init__( diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py index 130e8dd507a624..d66671fe1ea313 100644 --- a/homeassistant/components/hunterdouglas_powerview/sensor.py +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -1,6 +1,7 @@ """Support for hunterdouglass_powerview sensors.""" from aiopvapi.resources.shade import factory as PvShade +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback @@ -45,7 +46,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class PowerViewShadeBatterySensor(ShadeEntity): +class PowerViewShadeBatterySensor(ShadeEntity, SensorEntity): """Representation of an shade battery charge sensor.""" @property diff --git a/homeassistant/components/hvv_departures/sensor.py b/homeassistant/components/hvv_departures/sensor.py index bbabb1b8caaee5..35fc137a0f0d86 100644 --- a/homeassistant/components/hvv_departures/sensor.py +++ b/homeassistant/components/hvv_departures/sensor.py @@ -6,9 +6,9 @@ from pygti.exceptions import InvalidAuth from pytz import timezone +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ID, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow @@ -43,7 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices): async_add_devices([sensor], True) -class HVVDepartureSensor(Entity): +class HVVDepartureSensor(SensorEntity): """HVVDepartureSensor class.""" def __init__(self, hass, config_entry, session, hub): diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 6a0c6ab0d80bd1..7cd521296f6cb0 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.util import dt @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class HydrawiseSensor(HydrawiseEntity): +class HydrawiseSensor(HydrawiseEntity, SensorEntity): """A sensor implementation for Hydrawise device.""" @property From c900e3030b81ed25859911c3c3100713b24cf52b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:46:46 +0100 Subject: [PATCH 1448/1818] Migrate integrations n-q to extend SensorEntity (#48214) --- homeassistant/components/n26/sensor.py | 8 ++++---- homeassistant/components/neato/sensor.py | 5 ++--- .../components/nederlandse_spoorwegen/sensor.py | 5 ++--- homeassistant/components/nest/legacy/sensor.py | 5 +++-- homeassistant/components/netatmo/sensor.py | 5 +++-- homeassistant/components/netdata/sensor.py | 7 +++---- homeassistant/components/netgear_lte/sensor.py | 4 ++-- homeassistant/components/neurio_energy/sensor.py | 5 ++--- homeassistant/components/nexia/sensor.py | 5 +++-- homeassistant/components/nextbus/sensor.py | 5 ++--- homeassistant/components/nextcloud/sensor.py | 4 ++-- homeassistant/components/nightscout/sensor.py | 3 ++- homeassistant/components/nissan_leaf/sensor.py | 5 +++-- homeassistant/components/nmbs/sensor.py | 7 +++---- homeassistant/components/noaa_tides/sensor.py | 5 ++--- homeassistant/components/notion/sensor.py | 3 ++- homeassistant/components/nsw_fuel_station/sensor.py | 5 ++--- homeassistant/components/numato/sensor.py | 4 ++-- homeassistant/components/nut/sensor.py | 3 ++- homeassistant/components/nzbget/sensor.py | 3 ++- homeassistant/components/oasa_telematics/sensor.py | 5 ++--- homeassistant/components/obihai/sensor.py | 5 ++--- homeassistant/components/octoprint/sensor.py | 4 ++-- homeassistant/components/ohmconnect/sensor.py | 5 ++--- homeassistant/components/ombi/sensor.py | 4 ++-- homeassistant/components/omnilogic/sensor.py | 4 ++-- homeassistant/components/ondilo_ico/sensor.py | 3 ++- homeassistant/components/onewire/sensor.py | 8 ++++---- homeassistant/components/onvif/sensor.py | 3 ++- homeassistant/components/openerz/sensor.py | 4 ++-- homeassistant/components/openevse/sensor.py | 5 ++--- homeassistant/components/openexchangerates/sensor.py | 5 ++--- homeassistant/components/openhardwaremonitor/sensor.py | 5 ++--- homeassistant/components/opensky/sensor.py | 5 ++--- homeassistant/components/opentherm_gw/sensor.py | 6 +++--- homeassistant/components/openuv/sensor.py | 3 ++- .../components/openweathermap/abstract_owm_sensor.py | 4 ++-- homeassistant/components/oru/sensor.py | 5 ++--- homeassistant/components/otp/sensor.py | 5 ++--- homeassistant/components/ovo_energy/sensor.py | 3 ++- homeassistant/components/ozw/sensor.py | 3 ++- homeassistant/components/pi_hole/sensor.py | 3 ++- homeassistant/components/pilight/sensor.py | 5 ++--- homeassistant/components/plaato/sensor.py | 4 ++-- homeassistant/components/plex/sensor.py | 4 ++-- homeassistant/components/plugwise/sensor.py | 10 +++++----- homeassistant/components/pocketcasts/sensor.py | 5 ++--- homeassistant/components/point/sensor.py | 4 ++-- homeassistant/components/poolsense/sensor.py | 4 ++-- homeassistant/components/powerwall/sensor.py | 5 +++-- homeassistant/components/pushbullet/sensor.py | 5 ++--- homeassistant/components/pvoutput/sensor.py | 5 ++--- homeassistant/components/pvpc_hourly_pricing/sensor.py | 3 ++- homeassistant/components/pyload/sensor.py | 5 ++--- homeassistant/components/qbittorrent/sensor.py | 5 ++--- homeassistant/components/qnap/sensor.py | 5 ++--- homeassistant/components/qwikswitch/sensor.py | 3 ++- 57 files changed, 129 insertions(+), 136 deletions(-) diff --git a/homeassistant/components/n26/sensor.py b/homeassistant/components/n26/sensor.py index df687d9689ac1a..98d86194b86e0f 100644 --- a/homeassistant/components/n26/sensor.py +++ b/homeassistant/components/n26/sensor.py @@ -1,5 +1,5 @@ """Support for N26 bank account sensors.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import DEFAULT_SCAN_INTERVAL, DOMAIN, timestamp_ms_to_date from .const import DATA @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensor_entities) -class N26Account(Entity): +class N26Account(SensorEntity): """Sensor for a N26 balance account. A balance account contains an amount of money (=balance). The amount may @@ -117,7 +117,7 @@ def icon(self) -> str: return ICON_ACCOUNT -class N26Card(Entity): +class N26Card(SensorEntity): """Sensor for a N26 card.""" def __init__(self, api_data, card) -> None: @@ -186,7 +186,7 @@ def icon(self) -> str: return ICON_CARD -class N26Space(Entity): +class N26Space(SensorEntity): """Sensor for a N26 space.""" def __init__(self, api_data, space) -> None: diff --git a/homeassistant/components/neato/sensor.py b/homeassistant/components/neato/sensor.py index 50af42e7007bff..83add4ff3f7de6 100644 --- a/homeassistant/components/neato/sensor.py +++ b/homeassistant/components/neato/sensor.py @@ -4,9 +4,8 @@ from pybotvac.exceptions import NeatoRobotException -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity from homeassistant.const import PERCENTAGE -from homeassistant.helpers.entity import Entity from .const import NEATO_DOMAIN, NEATO_LOGIN, NEATO_ROBOTS, SCAN_INTERVAL_MINUTES @@ -31,7 +30,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(dev, True) -class NeatoSensor(Entity): +class NeatoSensor(SensorEntity): """Neato sensor.""" def __init__(self, neato, robot): diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index 7d90c2c3c0dac8..de8a85f44fd30d 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -7,11 +7,10 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -94,7 +93,7 @@ def valid_stations(stations, given_stations): return True -class NSDepartureSensor(Entity): +class NSDepartureSensor(SensorEntity): """Implementation of a NS Departure Sensor.""" def __init__(self, nsapi, name, departure, heading, via, time): diff --git a/homeassistant/components/nest/legacy/sensor.py b/homeassistant/components/nest/legacy/sensor.py index 34f525ca7a626b..9bea3b34ad4894 100644 --- a/homeassistant/components/nest/legacy/sensor.py +++ b/homeassistant/components/nest/legacy/sensor.py @@ -1,6 +1,7 @@ """Support for Nest Thermostat sensors for the legacy API.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_SENSORS, @@ -149,7 +150,7 @@ def get_sensors(): async_add_entities(await hass.async_add_executor_job(get_sensors), True) -class NestBasicSensor(NestSensorDevice): +class NestBasicSensor(NestSensorDevice, SensorEntity): """Representation a basic Nest sensor.""" @property @@ -179,7 +180,7 @@ def update(self): self._state = getattr(self.device, self.variable) -class NestTempSensor(NestSensorDevice): +class NestTempSensor(NestSensorDevice, SensorEntity): """Representation of a Nest Temperature sensor.""" @property diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index ac86e86b96068c..cccd3865e54e01 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -1,6 +1,7 @@ """Support for the Netatmo Weather Service.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_LATITUDE, @@ -260,7 +261,7 @@ async def async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> async_dispatcher_send(hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}") -class NetatmoSensor(NetatmoBase): +class NetatmoSensor(NetatmoBase, SensorEntity): """Implementation of a Netatmo sensor.""" def __init__(self, data_handler, data_class_name, module_info, sensor_type): @@ -489,7 +490,7 @@ def process_wifi(strength): return "Full" -class NetatmoPublicSensor(NetatmoBase): +class NetatmoPublicSensor(NetatmoBase, SensorEntity): """Represent a single sensor in a Netatmo.""" def __init__(self, data_handler, area, sensor_type): diff --git a/homeassistant/components/netdata/sensor.py b/homeassistant/components/netdata/sensor.py index 64c8d789fc78e2..21e4cd1b00503c 100644 --- a/homeassistant/components/netdata/sensor.py +++ b/homeassistant/components/netdata/sensor.py @@ -6,7 +6,7 @@ from netdata.exceptions import NetdataError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_ICON, @@ -18,7 +18,6 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -97,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class NetdataSensor(Entity): +class NetdataSensor(SensorEntity): """Implementation of a Netdata sensor.""" def __init__(self, netdata, name, sensor, sensor_name, element, icon, unit, invert): @@ -146,7 +145,7 @@ async def async_update(self): ) -class NetdataAlarms(Entity): +class NetdataAlarms(SensorEntity): """Implementation of a Netdata alarm sensor.""" def __init__(self, netdata, name, host, port): diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index abbedf79d64efe..c8f07301e98fa7 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -1,5 +1,5 @@ """Support for Netgear LTE sensors.""" -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.exceptions import PlatformNotReady from . import CONF_MONITORED_CONDITIONS, DATA_KEY, LTEEntity @@ -33,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info) async_add_entities(sensors) -class LTESensor(LTEEntity): +class LTESensor(LTEEntity, SensorEntity): """Base LTE sensor entity.""" @property diff --git a/homeassistant/components/neurio_energy/sensor.py b/homeassistant/components/neurio_energy/sensor.py index 264d7508347b96..2bc17fbecb2565 100644 --- a/homeassistant/components/neurio_energy/sensor.py +++ b/homeassistant/components/neurio_energy/sensor.py @@ -6,10 +6,9 @@ import requests.exceptions import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, ENERGY_KILO_WATT_HOUR, POWER_WATT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -123,7 +122,7 @@ def get_daily_usage(self): self._daily_usage = round(kwh, 2) -class NeurioEnergy(Entity): +class NeurioEnergy(SensorEntity): """Implementation of a Neurio energy sensor.""" def __init__(self, data, name, sensor_type, update_call): diff --git a/homeassistant/components/nexia/sensor.py b/homeassistant/components/nexia/sensor.py index eff15d443bcc7c..a14931e41eeb25 100644 --- a/homeassistant/components/nexia/sensor.py +++ b/homeassistant/components/nexia/sensor.py @@ -2,6 +2,7 @@ from nexia.const import UNIT_CELSIUS +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -149,7 +150,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class NexiaThermostatSensor(NexiaThermostatEntity): +class NexiaThermostatSensor(NexiaThermostatEntity, SensorEntity): """Provides Nexia thermostat sensor support.""" def __init__( @@ -196,7 +197,7 @@ def unit_of_measurement(self): return self._unit_of_measurement -class NexiaThermostatZoneSensor(NexiaThermostatZoneEntity): +class NexiaThermostatZoneSensor(NexiaThermostatZoneEntity, SensorEntity): """Nexia Zone Sensor Support.""" def __init__( diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index 3357d84fd69f86..67d0a4a81d7627 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -5,10 +5,9 @@ from py_nextbus import NextBusClient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, DEVICE_CLASS_TIMESTAMP import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utc_from_timestamp _LOGGER = logging.getLogger(__name__) @@ -104,7 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([NextBusDepartureSensor(client, agency, route, stop, name)], True) -class NextBusDepartureSensor(Entity): +class NextBusDepartureSensor(SensorEntity): """Sensor class that displays upcoming NextBus times. To function, this requires knowing the agency tag as well as the tags for diff --git a/homeassistant/components/nextcloud/sensor.py b/homeassistant/components/nextcloud/sensor.py index e0be9dde69e6a2..5cd02f124e94fb 100644 --- a/homeassistant/components/nextcloud/sensor.py +++ b/homeassistant/components/nextcloud/sensor.py @@ -1,5 +1,5 @@ """Summary data from Nextcoud.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import DOMAIN, SENSORS @@ -15,7 +15,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class NextcloudSensor(Entity): +class NextcloudSensor(SensorEntity): """Represents a Nextcloud sensor.""" def __init__(self, item): diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index d190da48a16ca9..ea2ea549cec470 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -9,6 +9,7 @@ from aiohttp import ClientError from py_nightscout import Api as NightscoutAPI +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_DATE from homeassistant.core import HomeAssistant @@ -33,7 +34,7 @@ async def async_setup_entry( async_add_entities([NightscoutSensor(api, "Blood Sugar", entry.unique_id)], True) -class NightscoutSensor(Entity): +class NightscoutSensor(SensorEntity): """Implementation of a Nightscout sensor.""" def __init__(self, api: NightscoutAPI, name, unique_id): diff --git a/homeassistant/components/nissan_leaf/sensor.py b/homeassistant/components/nissan_leaf/sensor.py index 368db17ab4b95a..936d607a84ee23 100644 --- a/homeassistant/components/nissan_leaf/sensor.py +++ b/homeassistant/components/nissan_leaf/sensor.py @@ -1,6 +1,7 @@ """Battery Charge and Range Support for the Nissan Leaf.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util.distance import LENGTH_KILOMETERS, LENGTH_MILES @@ -35,7 +36,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(devices, True) -class LeafBatterySensor(LeafEntity): +class LeafBatterySensor(LeafEntity, SensorEntity): """Nissan Leaf Battery Sensor.""" @property @@ -65,7 +66,7 @@ def icon(self): return icon_for_battery_level(battery_level=self.state, charging=chargestate) -class LeafRangeSensor(LeafEntity): +class LeafRangeSensor(LeafEntity, SensorEntity): """Nissan Leaf Range Sensor.""" def __init__(self, car, ac_on): diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py index ac6753ce0d67a2..32e4fd87e2917f 100644 --- a/homeassistant/components/nmbs/sensor.py +++ b/homeassistant/components/nmbs/sensor.py @@ -4,7 +4,7 @@ from pyrail import iRail import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -14,7 +14,6 @@ TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -88,7 +87,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class NMBSLiveBoard(Entity): +class NMBSLiveBoard(SensorEntity): """Get the next train from a station's liveboard.""" def __init__(self, api_client, live_station, station_from, station_to): @@ -164,7 +163,7 @@ def update(self): ) -class NMBSSensor(Entity): +class NMBSSensor(SensorEntity): """Get the the total travel time for a given connection.""" def __init__( diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index b6771d9293a378..e637e9531733bc 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -6,7 +6,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, @@ -15,7 +15,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -72,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([noaa_sensor], True) -class NOAATidesAndCurrentsSensor(Entity): +class NOAATidesAndCurrentsSensor(SensorEntity): """Representation of a NOAA Tides and Currents sensor.""" def __init__(self, name, station_id, timezone, unit_system, station): diff --git a/homeassistant/components/notion/sensor.py b/homeassistant/components/notion/sensor.py index 978e0aac46abf5..4f034408fe2d99 100644 --- a/homeassistant/components/notion/sensor.py +++ b/homeassistant/components/notion/sensor.py @@ -1,6 +1,7 @@ """Support for Notion sensors.""" from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback @@ -42,7 +43,7 @@ async def async_setup_entry( async_add_entities(sensor_list) -class NotionSensor(NotionEntity): +class NotionSensor(NotionEntity, SensorEntity): """Define a Notion sensor.""" def __init__( diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 92a071ec439925..6c8061294e9fa9 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -7,10 +7,9 @@ from nsw_fuel import FuelCheckClient, FuelCheckError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CURRENCY_CENT, VOLUME_LITERS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -146,7 +145,7 @@ def get_station_name(self) -> str: return self._station_name -class StationPriceSensor(Entity): +class StationPriceSensor(SensorEntity): """Implementation of a sensor that reports the fuel price for a station.""" def __init__(self, station_data: StationPriceData, fuel_type: str): diff --git a/homeassistant/components/numato/sensor.py b/homeassistant/components/numato/sensor.py index e268d32a29399a..19372de5258b4e 100644 --- a/homeassistant/components/numato/sensor.py +++ b/homeassistant/components/numato/sensor.py @@ -3,8 +3,8 @@ from numato_gpio import NumatoGpioError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_ID, CONF_NAME, CONF_SENSORS -from homeassistant.helpers.entity import Entity from . import ( CONF_DEVICES, @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class NumatoGpioAdc(Entity): +class NumatoGpioAdc(SensorEntity): """Represents an ADC port of a Numato USB GPIO expander.""" def __init__(self, name, device_id, port, src_range, dst_range, dst_unit, api): diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index d4fdd03adc81d7..2e3826935fe20d 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -1,6 +1,7 @@ """Provides a sensor to track various status aspects of a UPS.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_STATE, CONF_RESOURCES, STATE_UNKNOWN from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -76,7 +77,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class NUTSensor(CoordinatorEntity): +class NUTSensor(CoordinatorEntity, SensorEntity): """Representation of a sensor entity for NUT status values.""" def __init__( diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index a8870db52a3552..54a88c89f5311e 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -5,6 +5,7 @@ import logging from typing import Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_NAME, @@ -66,7 +67,7 @@ async def async_setup_entry( async_add_entities(sensors) -class NZBGetSensor(NZBGetEntity): +class NZBGetSensor(NZBGetEntity, SensorEntity): """Representation of a NZBGet sensor.""" def __init__( diff --git a/homeassistant/components/oasa_telematics/sensor.py b/homeassistant/components/oasa_telematics/sensor.py index 8af74b5cd0e469..71af8dacba2e9d 100644 --- a/homeassistant/components/oasa_telematics/sensor.py +++ b/homeassistant/components/oasa_telematics/sensor.py @@ -6,10 +6,9 @@ import oasatelematics import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TIMESTAMP import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OASATelematicsSensor(data, stop_id, route_id, name)], True) -class OASATelematicsSensor(Entity): +class OASATelematicsSensor(SensorEntity): """Implementation of the OASA Telematics sensor.""" def __init__(self, data, stop_id, route_id, name): diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index c105b91971da26..9aacaa84193f92 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -5,7 +5,7 @@ from pyobihai import PyObihai import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -13,7 +13,6 @@ DEVICE_CLASS_TIMESTAMP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -69,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ObihaiServiceSensors(Entity): +class ObihaiServiceSensors(SensorEntity): """Get the status of each Obihai Lines.""" def __init__(self, pyobihai, serial, service_name): diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 921f355edbe223..f2c5c56c58aaff 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -3,8 +3,8 @@ import requests +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import DOMAIN as COMPONENT_DOMAIN, SENSOR_TYPES @@ -71,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class OctoPrintSensor(Entity): +class OctoPrintSensor(SensorEntity): """Representation of an OctoPrint sensor.""" def __init__( diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index 6c7c04b25cbcdb..b53c35e17b5d6b 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -6,10 +6,9 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -34,7 +33,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OhmconnectSensor(name, ohmid)], True) -class OhmconnectSensor(Entity): +class OhmconnectSensor(SensorEntity): """Representation of a OhmConnect sensor.""" def __init__(self, name, ohmid): diff --git a/homeassistant/components/ombi/sensor.py b/homeassistant/components/ombi/sensor.py index 2a2f50532b4e23..8c08b026b282e4 100644 --- a/homeassistant/components/ombi/sensor.py +++ b/homeassistant/components/ombi/sensor.py @@ -4,7 +4,7 @@ from pyombi import OmbiError -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from .const import DOMAIN, SENSOR_TYPES @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class OmbiSensor(Entity): +class OmbiSensor(SensorEntity): """Representation of an Ombi sensor.""" def __init__(self, label, sensor_type, ombi, icon): diff --git a/homeassistant/components/omnilogic/sensor.py b/homeassistant/components/omnilogic/sensor.py index e1b4b387a46062..25457224e9f007 100644 --- a/homeassistant/components/omnilogic/sensor.py +++ b/homeassistant/components/omnilogic/sensor.py @@ -1,5 +1,5 @@ """Definition and setup of the Omnilogic Sensors for Home Assistant.""" -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, MASS_GRAMS, @@ -59,7 +59,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class OmnilogicSensor(OmniLogicEntity): +class OmnilogicSensor(OmniLogicEntity, SensorEntity): """Defines an Omnilogic sensor entity.""" def __init__( diff --git a/homeassistant/components/ondilo_ico/sensor.py b/homeassistant/components/ondilo_ico/sensor.py index b34ee4eae35c44..3af2bb7c326127 100644 --- a/homeassistant/components/ondilo_ico/sensor.py +++ b/homeassistant/components/ondilo_ico/sensor.py @@ -4,6 +4,7 @@ from ondilo import OndiloError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, @@ -83,7 +84,7 @@ async def async_update_data(): async_add_entities(entities) -class OndiloICO(CoordinatorEntity): +class OndiloICO(CoordinatorEntity, SensorEntity): """Representation of a Sensor.""" def __init__( diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 4888383fa423fd..ba988aba226649 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -6,7 +6,7 @@ from pi1wire import InvalidCRCException, UnsupportResponseException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE import homeassistant.helpers.config_validation as cv @@ -394,7 +394,7 @@ def get_entities(onewirehub: OneWireHub, config): return entities -class OneWireProxySensor(OneWireProxyEntity): +class OneWireProxySensor(OneWireProxyEntity, SensorEntity): """Implementation of a 1-Wire sensor connected through owserver.""" @property @@ -403,7 +403,7 @@ def state(self) -> StateType: return self._state -class OneWireDirectSensor(OneWireBaseEntity): +class OneWireDirectSensor(OneWireBaseEntity, SensorEntity): """Implementation of a 1-Wire sensor directly connected to RPI GPIO.""" def __init__(self, name, device_file, device_info, owsensor): @@ -431,7 +431,7 @@ def update(self): self._state = value -class OneWireOWFSSensor(OneWireBaseEntity): # pragma: no cover +class OneWireOWFSSensor(OneWireBaseEntity, SensorEntity): # pragma: no cover """Implementation of a 1-Wire sensor through owfs. This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index 3895e8a4abbf96..1c5766e39697e0 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -1,6 +1,7 @@ """Support for ONVIF binary sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from .base import ONVIFBaseEntity @@ -33,7 +34,7 @@ def async_check_entities(): return True -class ONVIFSensor(ONVIFBaseEntity): +class ONVIFSensor(ONVIFBaseEntity, SensorEntity): """Representation of a ONVIF sensor event.""" def __init__(self, uid, device): diff --git a/homeassistant/components/openerz/sensor.py b/homeassistant/components/openerz/sensor.py index 9a5bf3a981329c..33305b677def1d 100644 --- a/homeassistant/components/openerz/sensor.py +++ b/homeassistant/components/openerz/sensor.py @@ -4,9 +4,9 @@ from openerz_api.main import OpenERZConnector import voluptuous as vol +from homeassistant.components.sensor import SensorEntity import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity SCAN_INTERVAL = timedelta(hours=12) @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OpenERZSensor(api_connector, config.get(CONF_NAME))], True) -class OpenERZSensor(Entity): +class OpenERZSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, api_connector, name): diff --git a/homeassistant/components/openevse/sensor.py b/homeassistant/components/openevse/sensor.py index e0f21f6946db1b..d7d4149e26d080 100644 --- a/homeassistant/components/openevse/sensor.py +++ b/homeassistant/components/openevse/sensor.py @@ -5,7 +5,7 @@ from requests import RequestException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -14,7 +14,6 @@ TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class OpenEVSESensor(Entity): +class OpenEVSESensor(SensorEntity): """Implementation of an OpenEVSE sensor.""" def __init__(self, sensor_type, charger): diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py index f3f0d825ff67e7..8474cdab131426 100644 --- a/homeassistant/components/openexchangerates/sensor.py +++ b/homeassistant/components/openexchangerates/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OpenexchangeratesSensor(rest, name, quote)], True) -class OpenexchangeratesSensor(Entity): +class OpenexchangeratesSensor(SensorEntity): """Representation of an Open Exchange Rates sensor.""" def __init__(self, rest, name, quote): diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py index 3254f0824f1c47..70d0d36176c620 100644 --- a/homeassistant/components/openhardwaremonitor/sensor.py +++ b/homeassistant/components/openhardwaremonitor/sensor.py @@ -5,11 +5,10 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import utcnow @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(data.devices, True) -class OpenHardwareMonitorDevice(Entity): +class OpenHardwareMonitorDevice(SensorEntity): """Device used to display information from OpenHardwareMonitor.""" def __init__(self, data, name, path, unit_of_measurement): diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index e5ffb2384f5370..122388b85b7588 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -4,7 +4,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -17,7 +17,6 @@ LENGTH_METERS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import distance as util_distance, location as util_location CONF_ALTITUDE = "altitude" @@ -87,7 +86,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class OpenSkySensor(Entity): +class OpenSkySensor(SensorEntity): """Open Sky Network Sensor.""" def __init__(self, hass, name, latitude, longitude, radius, altitude): diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 4a20aa651cd49e..1d9904ea59fd6d 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -2,11 +2,11 @@ import logging from pprint import pformat -from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity from homeassistant.const import CONF_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_registry import async_get_registry from . import DOMAIN @@ -77,7 +77,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class OpenThermSensor(Entity): +class OpenThermSensor(SensorEntity): """Representation of an OpenTherm Gateway sensor.""" def __init__(self, gw_dev, var, source, device_class, unit, friendly_name_format): diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py index b9c73023c1124b..654a89cfcf96b4 100644 --- a/homeassistant/components/openuv/sensor.py +++ b/homeassistant/components/openuv/sensor.py @@ -1,4 +1,5 @@ """Support for OpenUV sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import TIME_MINUTES, UV_INDEX from homeassistant.core import callback from homeassistant.util.dt import as_local, parse_datetime @@ -87,7 +88,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class OpenUvSensor(OpenUvEntity): +class OpenUvSensor(OpenUvEntity, SensorEntity): """Define a binary sensor for OpenUV.""" def __init__(self, openuv, sensor_type, name, icon, unit, entry_id): diff --git a/homeassistant/components/openweathermap/abstract_owm_sensor.py b/homeassistant/components/openweathermap/abstract_owm_sensor.py index a69f542589bd28..30a21a057f0fba 100644 --- a/homeassistant/components/openweathermap/abstract_owm_sensor.py +++ b/homeassistant/components/openweathermap/abstract_owm_sensor.py @@ -1,12 +1,12 @@ """Abstraction form OWM sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ATTRIBUTION, SENSOR_DEVICE_CLASS, SENSOR_NAME, SENSOR_UNIT -class AbstractOpenWeatherMapSensor(Entity): +class AbstractOpenWeatherMapSensor(SensorEntity): """Abstract class for an OpenWeatherMap sensor.""" def __init__( diff --git a/homeassistant/components/oru/sensor.py b/homeassistant/components/oru/sensor.py index d6620ed39e5839..063c0c6169d3be 100644 --- a/homeassistant/components/oru/sensor.py +++ b/homeassistant/components/oru/sensor.py @@ -5,10 +5,9 @@ from oru import Meter, MeterError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ENERGY_KILO_WATT_HOUR import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.debug("Oru meter_number = %s", meter_number) -class CurrentEnergyUsageSensor(Entity): +class CurrentEnergyUsageSensor(SensorEntity): """Representation of the sensor.""" def __init__(self, meter): diff --git a/homeassistant/components/otp/sensor.py b/homeassistant/components/otp/sensor.py index 9f23f5ae6faa2e..7aee9d99208d11 100644 --- a/homeassistant/components/otp/sensor.py +++ b/homeassistant/components/otp/sensor.py @@ -4,11 +4,10 @@ import pyotp import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_TOKEN from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity DEFAULT_NAME = "OTP Sensor" @@ -34,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= # Only TOTP supported at the moment, HOTP might be added later -class TOTPSensor(Entity): +class TOTPSensor(SensorEntity): """Representation of a TOTP sensor.""" def __init__(self, name, token): diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 3171b3231dcfb4..d03f7c49f96aaf 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -4,6 +4,7 @@ from ovoenergy import OVODailyUsage from ovoenergy.ovoenergy import OVOEnergy +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -53,7 +54,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class OVOEnergySensor(OVOEnergyDeviceEntity): +class OVOEnergySensor(OVOEnergyDeviceEntity, SensorEntity): """Defines a OVO Energy sensor.""" def __init__( diff --git a/homeassistant/components/ozw/sensor.py b/homeassistant/components/ozw/sensor.py index db695bcf6bc8e6..3c3d4c3ca36b1a 100644 --- a/homeassistant/components/ozw/sensor.py +++ b/homeassistant/components/ozw/sensor.py @@ -12,6 +12,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, DOMAIN as SENSOR_DOMAIN, + SensorEntity, ) from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback @@ -57,7 +58,7 @@ def async_add_sensor(value): ) -class ZwaveSensorBase(ZWaveDeviceEntity): +class ZwaveSensorBase(ZWaveDeviceEntity, SensorEntity): """Basic Representation of a Z-Wave sensor.""" @property diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 4bd4c7b7f6fe9a..517e8cfcf17f4d 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -1,5 +1,6 @@ """Support for getting statistical data from a Pi-hole system.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME from . import PiHoleEntity @@ -30,7 +31,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class PiHoleSensor(PiHoleEntity): +class PiHoleSensor(PiHoleEntity, SensorEntity): """Representation of a Pi-hole sensor.""" def __init__(self, api, coordinator, name, sensor_name, server_unique_id): diff --git a/homeassistant/components/pilight/sensor.py b/homeassistant/components/pilight/sensor.py index e8c7b4bd4b6673..97458acd5fc622 100644 --- a/homeassistant/components/pilight/sensor.py +++ b/homeassistant/components/pilight/sensor.py @@ -4,10 +4,9 @@ import voluptuous as vol from homeassistant.components import pilight -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_PAYLOAD, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class PilightSensor(Entity): +class PilightSensor(SensorEntity): """Representation of a sensor that can be updated using Pilight.""" def __init__(self, hass, name, variable, payload, unit_of_measurement): diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index c2c59d347f36ba..0812a2fd585ae6 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -4,7 +4,7 @@ from pyplaato.models.device import PlaatoDevice from pyplaato.plaato import PlaatoKeg -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, SensorEntity from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -59,7 +59,7 @@ async def _async_update_from_webhook(device_id, sensor_data: PlaatoDevice): ) -class PlaatoSensor(PlaatoEntity): +class PlaatoSensor(PlaatoEntity, SensorEntity): """Representation of a Plaato Sensor.""" @property diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 47dd05a557b238..d79c4120dc795b 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -1,9 +1,9 @@ """Support for Plex media server monitoring.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import ( CONF_SERVER_IDENTIFIER, @@ -25,7 +25,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([sensor]) -class PlexSensor(Entity): +class PlexSensor(SensorEntity): """Representation of a Plex now playing sensor.""" def __init__(self, hass, plex_server): diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index f57ff2b2a91b21..4152f9fdabddbf 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -2,6 +2,7 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, @@ -17,7 +18,6 @@ VOLUME_CUBIC_METERS, ) from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from .const import ( COOL_ICON, @@ -236,7 +236,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class SmileSensor(SmileGateway): +class SmileSensor(SmileGateway, SensorEntity): """Represent Smile Sensors.""" def __init__(self, api, coordinator, name, dev_id, sensor): @@ -282,7 +282,7 @@ def unit_of_measurement(self): return self._unit_of_measurement -class PwThermostatSensor(SmileSensor, Entity): +class PwThermostatSensor(SmileSensor): """Thermostat (or generic) sensor devices.""" def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type): @@ -311,7 +311,7 @@ def _async_process_data(self): self.async_write_ha_state() -class PwAuxDeviceSensor(SmileSensor, Entity): +class PwAuxDeviceSensor(SmileSensor): """Auxiliary Device Sensors.""" def __init__(self, api, coordinator, name, dev_id, sensor): @@ -348,7 +348,7 @@ def _async_process_data(self): self.async_write_ha_state() -class PwPowerSensor(SmileSensor, Entity): +class PwPowerSensor(SmileSensor): """Power sensor entities.""" def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type, model): diff --git a/homeassistant/components/pocketcasts/sensor.py b/homeassistant/components/pocketcasts/sensor.py index 19f7e26543894c..55ae4a524fc1ca 100644 --- a/homeassistant/components/pocketcasts/sensor.py +++ b/homeassistant/components/pocketcasts/sensor.py @@ -5,10 +5,9 @@ from pycketcasts import pocketcasts import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -37,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return False -class PocketCastsSensor(Entity): +class PocketCastsSensor(SensorEntity): """Representation of a pocket casts sensor.""" def __init__(self, api): diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 87f9a8ab2fe2f8..338ed275f50fa0 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -1,7 +1,7 @@ """Support for Minut Point sensors.""" import logging -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, @@ -47,7 +47,7 @@ async def async_discover_sensor(device_id): ) -class MinutPointSensor(MinutPointEntity): +class MinutPointSensor(MinutPointEntity, SensorEntity): """The platform class required by Home Assistant.""" def __init__(self, point_client, device_id, device_class): diff --git a/homeassistant/components/poolsense/sensor.py b/homeassistant/components/poolsense/sensor.py index bf5c3eb0163a21..ca79fde6b08262 100644 --- a/homeassistant/components/poolsense/sensor.py +++ b/homeassistant/components/poolsense/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the PoolSense sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_EMAIL, @@ -8,7 +9,6 @@ PERCENTAGE, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import PoolSenseEntity from .const import ATTRIBUTION, DOMAIN @@ -79,7 +79,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors_list, False) -class PoolSenseSensor(PoolSenseEntity, Entity): +class PoolSenseSensor(PoolSenseEntity, SensorEntity): """Sensor representing poolsense data.""" @property diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index 3b4d7918cf76fd..36f803e66d7a27 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -3,6 +3,7 @@ from tesla_powerwall import MeterType +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, DEVICE_CLASS_POWER, PERCENTAGE from .const import ( @@ -59,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class PowerWallChargeSensor(PowerWallEntity): +class PowerWallChargeSensor(PowerWallEntity, SensorEntity): """Representation of an Powerwall charge sensor.""" @property @@ -88,7 +89,7 @@ def state(self): return round(self.coordinator.data[POWERWALL_API_CHARGE]) -class PowerWallEnergySensor(PowerWallEntity): +class PowerWallEnergySensor(PowerWallEntity, SensorEntity): """Representation of an Powerwall Energy sensor.""" def __init__( diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py index f7aaa693c675d2..4f8ec6a17001e8 100644 --- a/homeassistant/components/pushbullet/sensor.py +++ b/homeassistant/components/pushbullet/sensor.py @@ -5,10 +5,9 @@ from pushbullet import InvalidKeyError, Listener, PushBullet import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class PushBulletNotificationSensor(Entity): +class PushBulletNotificationSensor(SensorEntity): """Representation of a Pushbullet Sensor.""" def __init__(self, pb, element): diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py index 9e88cb0a664aab..eb461061dcc1ad 100644 --- a/homeassistant/components/pvoutput/sensor.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.rest.data import RestData -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DATE, ATTR_TEMPERATURE, @@ -17,7 +17,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _ENDPOINT = "http://pvoutput.org/service/r2/getstatus.jsp" @@ -64,7 +63,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([PvoutputSensor(rest, name)]) -class PvoutputSensor(Entity): +class PvoutputSensor(SensorEntity): """Representation of a PVOutput sensor.""" def __init__(self, rest, name): diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index 3aca4db67e72ce..5fe65e3dc65f63 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -7,6 +7,7 @@ from aiopvpc import PVPCData from homeassistant import config_entries +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, CURRENCY_EURO, ENERGY_KILO_WATT_HOUR from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -42,7 +43,7 @@ async def async_setup_entry( ) -class ElecPriceSensor(RestoreEntity): +class ElecPriceSensor(RestoreEntity, SensorEntity): """Class to hold the prices of electricity as a sensor.""" unit_of_measurement = UNIT diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py index 6539479d2cd08a..c439d5181be756 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -6,7 +6,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_VARIABLES, @@ -19,7 +19,6 @@ DATA_RATE_MEGABYTES_PER_SECOND, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -77,7 +76,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class PyLoadSensor(Entity): +class PyLoadSensor(SensorEntity): """Representation of a pyLoad sensor.""" def __init__(self, api, sensor_type, client_name): diff --git a/homeassistant/components/qbittorrent/sensor.py b/homeassistant/components/qbittorrent/sensor.py index cd67355d88379d..251407099b159e 100644 --- a/homeassistant/components/qbittorrent/sensor.py +++ b/homeassistant/components/qbittorrent/sensor.py @@ -5,7 +5,7 @@ from requests.exceptions import RequestException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -16,7 +16,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ def format_speed(speed): return round(kb_spd, 2 if kb_spd < 0.1 else 1) -class QBittorrentSensor(Entity): +class QBittorrentSensor(SensorEntity): """Representation of an qBittorrent sensor.""" def __init__(self, sensor_type, qbittorrent_client, client_name, exception): diff --git a/homeassistant/components/qnap/sensor.py b/homeassistant/components/qnap/sensor.py index 5f7695e5a6010d..5759713e80c444 100644 --- a/homeassistant/components/qnap/sensor.py +++ b/homeassistant/components/qnap/sensor.py @@ -5,7 +5,7 @@ from qnapstats import QNAPStats import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_NAME, CONF_HOST, @@ -23,7 +23,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -200,7 +199,7 @@ def update(self): _LOGGER.exception("Failed to fetch QNAP stats from the NAS") -class QNAPSensor(Entity): +class QNAPSensor(SensorEntity): """Base class for a QNAP sensor.""" def __init__(self, api, variable, variable_info, monitor_device=None): diff --git a/homeassistant/components/qwikswitch/sensor.py b/homeassistant/components/qwikswitch/sensor.py index 53cf68ccdbad9e..f6d0ce7ec28956 100644 --- a/homeassistant/components/qwikswitch/sensor.py +++ b/homeassistant/components/qwikswitch/sensor.py @@ -3,6 +3,7 @@ from pyqwikswitch.qwikswitch import SENSORS +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from . import DOMAIN as QWIKSWITCH, QSEntity @@ -21,7 +22,7 @@ async def async_setup_platform(hass, _, add_entities, discovery_info=None): add_entities(devs) -class QSSensor(QSEntity): +class QSSensor(QSEntity, SensorEntity): """Sensor based on a Qwikswitch relay/dimmer module.""" _val = None From 783b453bbe4fb07e812ee03174a7c87789ca13f6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:47:44 +0100 Subject: [PATCH 1449/1818] Migrate integrations t-v to extend SensorEntity (#48216) --- homeassistant/components/tado/sensor.py | 6 +++--- homeassistant/components/tahoma/sensor.py | 4 ++-- homeassistant/components/tank_utility/sensor.py | 5 ++--- homeassistant/components/tankerkoenig/sensor.py | 3 ++- homeassistant/components/tasmota/sensor.py | 4 ++-- homeassistant/components/tautulli/sensor.py | 5 ++--- homeassistant/components/tcp/sensor.py | 5 ++--- homeassistant/components/ted5000/sensor.py | 5 ++--- homeassistant/components/tellduslive/sensor.py | 3 ++- homeassistant/components/tellstick/sensor.py | 5 ++--- homeassistant/components/temper/sensor.py | 5 ++--- homeassistant/components/template/sensor.py | 5 +++-- homeassistant/components/tesla/sensor.py | 5 ++--- homeassistant/components/thermoworks_smoke/sensor.py | 5 ++--- homeassistant/components/thethingsnetwork/sensor.py | 5 ++--- homeassistant/components/thinkingcleaner/sensor.py | 5 ++--- homeassistant/components/tibber/sensor.py | 5 ++--- homeassistant/components/time_date/sensor.py | 5 ++--- homeassistant/components/tmb/sensor.py | 5 ++--- homeassistant/components/tof/sensor.py | 5 ++--- homeassistant/components/toon/sensor.py | 3 ++- homeassistant/components/torque/sensor.py | 5 ++--- homeassistant/components/tradfri/sensor.py | 3 ++- homeassistant/components/trafikverket_train/sensor.py | 5 ++--- .../components/trafikverket_weatherstation/sensor.py | 5 ++--- homeassistant/components/transmission/sensor.py | 4 ++-- homeassistant/components/transport_nsw/sensor.py | 5 ++--- homeassistant/components/travisci/sensor.py | 5 ++--- homeassistant/components/twentemilieu/sensor.py | 3 ++- homeassistant/components/twitch/sensor.py | 5 ++--- homeassistant/components/uk_transport/sensor.py | 5 ++--- homeassistant/components/unifi/sensor.py | 6 +++--- homeassistant/components/upnp/sensor.py | 3 ++- homeassistant/components/uptime/sensor.py | 9 ++++++--- homeassistant/components/uscis/sensor.py | 5 ++--- homeassistant/components/utility_meter/sensor.py | 3 ++- homeassistant/components/vallox/sensor.py | 4 ++-- homeassistant/components/vasttrafik/sensor.py | 5 ++--- homeassistant/components/velbus/sensor.py | 3 ++- homeassistant/components/vera/sensor.py | 8 ++++++-- homeassistant/components/verisure/sensor.py | 7 ++++--- homeassistant/components/versasense/sensor.py | 4 ++-- homeassistant/components/version/sensor.py | 5 ++--- homeassistant/components/viaggiatreno/sensor.py | 5 ++--- homeassistant/components/vicare/sensor.py | 4 ++-- homeassistant/components/vilfo/sensor.py | 4 ++-- homeassistant/components/volkszaehler/sensor.py | 5 ++--- homeassistant/components/volvooncall/sensor.py | 4 +++- homeassistant/components/vultr/sensor.py | 5 ++--- 49 files changed, 112 insertions(+), 120 deletions(-) diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index cfafeb14ddfd08..87d2170eb7514d 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -1,6 +1,7 @@ """Support for Tado sensors for each zone.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -10,7 +11,6 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import ( CONDITIONS_MAP, @@ -86,7 +86,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class TadoHomeSensor(TadoHomeEntity, Entity): +class TadoHomeSensor(TadoHomeEntity, SensorEntity): """Representation of a Tado Sensor.""" def __init__(self, tado, home_variable): @@ -191,7 +191,7 @@ def _async_update_home_data(self): } -class TadoZoneSensor(TadoZoneEntity, Entity): +class TadoZoneSensor(TadoZoneEntity, SensorEntity): """Representation of a tado Sensor.""" def __init__(self, tado, zone_name, zone_id, zone_variable): diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 91629137318503..47e6d3004143ec 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -2,8 +2,8 @@ from datetime import timedelta import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_BATTERY_LEVEL, LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class TahomaSensor(TahomaDevice, Entity): +class TahomaSensor(TahomaDevice, SensorEntity): """Representation of a Tahoma Sensor.""" def __init__(self, tahoma_device, controller): diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index d0566ec7c9e0d1..379819cf65ebb1 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -7,10 +7,9 @@ from tank_utility import auth, device as tank_monitor import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_DEVICES, CONF_EMAIL, CONF_PASSWORD, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(all_sensors, True) -class TankUtilitySensor(Entity): +class TankUtilitySensor(SensorEntity): """Representation of a Tank Utility sensor.""" def __init__(self, email, password, token, device): diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index a1cbed2ba115b2..5c1898e02a98a4 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -2,6 +2,7 @@ import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -79,7 +80,7 @@ async def async_update_data(): async_add_entities(entities) -class FuelPriceSensor(CoordinatorEntity): +class FuelPriceSensor(CoordinatorEntity, SensorEntity): """Contains prices for fuel in a given station.""" def __init__(self, fuel_type, station, coordinator, name, show_on_map): diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 9d7195a98e5b4f..432fc2266f3adc 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -4,6 +4,7 @@ from hatasmota import const as hc, status_sensor from homeassistant.components import sensor +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_BILLION, @@ -38,7 +39,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DATA_REMOVE_DISCOVER_COMPONENT from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW @@ -145,7 +145,7 @@ async def async_discover_sensor(tasmota_entity, discovery_hash): ) -class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): +class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, SensorEntity): """Representation of a Tasmota sensor.""" def __init__(self, **kwds): diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index 2d2d005e95d02a..c50efb00ed7960 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -4,7 +4,7 @@ from pytautulli import Tautulli import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -18,7 +18,6 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle CONF_MONITORED_USERS = "monitored_users" @@ -72,7 +71,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensor, True) -class TautulliSensor(Entity): +class TautulliSensor(SensorEntity): """Representation of a Tautulli sensor.""" def __init__(self, tautulli, name, monitored_conditions, users): diff --git a/homeassistant/components/tcp/sensor.py b/homeassistant/components/tcp/sensor.py index 9b7e1539fb4114..54cf4d120f197d 100644 --- a/homeassistant/components/tcp/sensor.py +++ b/homeassistant/components/tcp/sensor.py @@ -5,7 +5,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -17,7 +17,6 @@ ) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -48,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TcpSensor(hass, config)]) -class TcpSensor(Entity): +class TcpSensor(SensorEntity): """Implementation of a TCP socket based sensor.""" required = () diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py index 3ea26286b18c03..86c9b9ace95dec 100644 --- a/homeassistant/components/ted5000/sensor.py +++ b/homeassistant/components/ted5000/sensor.py @@ -6,10 +6,9 @@ import voluptuous as vol import xmltodict -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, POWER_WATT, VOLT from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class Ted5000Sensor(Entity): +class Ted5000Sensor(SensorEntity): """Implementation of a Ted5000 sensor.""" def __init__(self, gateway, name, mtu, unit): diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py index 1b06fd6ed97bb8..a86b487afd2d81 100644 --- a/homeassistant/components/tellduslive/sensor.py +++ b/homeassistant/components/tellduslive/sensor.py @@ -1,5 +1,6 @@ """Support for Tellstick Net/Telstick Live sensors.""" from homeassistant.components import sensor, tellduslive +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, @@ -71,7 +72,7 @@ async def async_discover_sensor(device_id): ) -class TelldusLiveSensor(TelldusLiveEntity): +class TelldusLiveSensor(TelldusLiveEntity, SensorEntity): """Representation of a Telldus Live sensor.""" @property diff --git a/homeassistant/components/tellstick/sensor.py b/homeassistant/components/tellstick/sensor.py index 444cabd0180f6d..f58c5916bfb1cd 100644 --- a/homeassistant/components/tellstick/sensor.py +++ b/homeassistant/components/tellstick/sensor.py @@ -6,7 +6,7 @@ import tellcore.constants as tellcore_constants import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_ID, CONF_NAME, @@ -15,7 +15,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -126,7 +125,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class TellstickSensor(Entity): +class TellstickSensor(SensorEntity): """Representation of a Tellstick sensor.""" def __init__(self, name, tellcore_sensor, datatype, sensor_info): diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py index c47aa1878fc1bf..7edbd3ba8123fc 100644 --- a/homeassistant/components/temper/sensor.py +++ b/homeassistant/components/temper/sensor.py @@ -4,14 +4,13 @@ from temperusb.temper import TemperHandler import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_OFFSET, DEVICE_DEFAULT_NAME, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ def reset_devices(): sensor.set_temper_device(device) -class TemperSensor(Entity): +class TemperSensor(SensorEntity): """Representation of a Temper temperature sensor.""" def __init__(self, temper_device, temp_unit, name, scaling): diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 073924c51b6b75..b587fe3bd8291a 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -7,6 +7,7 @@ DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, + SensorEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -23,7 +24,7 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS @@ -99,7 +100,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(await _async_create_entities(hass, config)) -class SensorTemplate(TemplateEntity, Entity): +class SensorTemplate(TemplateEntity, SensorEntity): """Representation of a Template Sensor.""" def __init__( diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py index 40bf68bfa156af..40c7aa8548df95 100644 --- a/homeassistant/components/tesla/sensor.py +++ b/homeassistant/components/tesla/sensor.py @@ -1,14 +1,13 @@ """Support for the Tesla sensors.""" from __future__ import annotations -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.const import ( LENGTH_KILOMETERS, LENGTH_MILES, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) -from homeassistant.helpers.entity import Entity from homeassistant.util.distance import convert from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -27,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class TeslaSensor(TeslaDevice, Entity): +class TeslaSensor(TeslaDevice, SensorEntity): """Representation of Tesla sensors.""" def __init__(self, tesla_device, coordinator, sensor_type=None): diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index d768f009364ad2..86427349f3140f 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -11,7 +11,7 @@ import thermoworks_smoke import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONF_EMAIL, @@ -21,7 +21,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -91,7 +90,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error(msg) -class ThermoworksSmokeSensor(Entity): +class ThermoworksSmokeSensor(SensorEntity): """Implementation of a thermoworks smoke sensor.""" def __init__(self, sensor_type, serial, mgr): diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index d758a20f3845af..2e139eae63d33e 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -7,7 +7,7 @@ import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_TIME, @@ -18,7 +18,6 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DATA_TTN, TTN_ACCESS_KEY, TTN_APP_ID, TTN_DATA_STORAGE_URL @@ -59,7 +58,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices, True) -class TtnDataSensor(Entity): +class TtnDataSensor(SensorEntity): """Representation of a The Things Network Data Storage sensor.""" def __init__(self, ttn_data_storage, device_id, value, unit_of_measurement): diff --git a/homeassistant/components/thinkingcleaner/sensor.py b/homeassistant/components/thinkingcleaner/sensor.py index 966930f1eb1610..56cf272f7d15ad 100644 --- a/homeassistant/components/thinkingcleaner/sensor.py +++ b/homeassistant/components/thinkingcleaner/sensor.py @@ -5,10 +5,9 @@ import voluptuous as vol from homeassistant import util -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) @@ -73,7 +72,7 @@ def update_devices(): add_entities(dev) -class ThinkingCleanerSensor(Entity): +class ThinkingCleanerSensor(SensorEntity): """Representation of a ThinkingCleaner Sensor.""" def __init__(self, tc_object, sensor_type, update_devices): diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 01e050fdd24e42..5ab85013a25746 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -6,10 +6,9 @@ import aiohttp -from homeassistant.components.sensor import DEVICE_CLASS_POWER +from homeassistant.components.sensor import DEVICE_CLASS_POWER, SensorEntity from homeassistant.const import POWER_WATT from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle, dt as dt_util from .const import DOMAIN as TIBBER_DOMAIN, MANUFACTURER @@ -45,7 +44,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(dev, True) -class TibberSensor(Entity): +class TibberSensor(SensorEntity): """Representation of a generic Tibber sensor.""" def __init__(self, tibber_home): diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py index 4615e9e046c680..08195e6dd3dc87 100644 --- a/homeassistant/components/time_date/sensor.py +++ b/homeassistant/components/time_date/sensor.py @@ -4,11 +4,10 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_DISPLAY_OPTIONS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time import homeassistant.util.dt as dt_util @@ -47,7 +46,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class TimeDateSensor(Entity): +class TimeDateSensor(SensorEntity): """Implementation of a Time and Date sensor.""" def __init__(self, hass, option_type): diff --git a/homeassistant/components/tmb/sensor.py b/homeassistant/components/tmb/sensor.py index c3f813d796a105..88471a86c27e10 100644 --- a/homeassistant/components/tmb/sensor.py +++ b/homeassistant/components/tmb/sensor.py @@ -6,10 +6,9 @@ from tmb import IBus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -63,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class TMBSensor(Entity): +class TMBSensor(SensorEntity): """Implementation of a TMB line/stop Sensor.""" def __init__(self, ibus_client, stop, line, name): diff --git a/homeassistant/components/tof/sensor.py b/homeassistant/components/tof/sensor.py index 17b2d1010bfb82..45713dd8f7710a 100644 --- a/homeassistant/components/tof/sensor.py +++ b/homeassistant/components/tof/sensor.py @@ -7,10 +7,9 @@ import voluptuous as vol from homeassistant.components import rpi_gpio -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, LENGTH_MILLIMETERS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity CONF_I2C_ADDRESS = "i2c_address" CONF_I2C_BUS = "i2c_bus" @@ -65,7 +64,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class VL53L1XSensor(Entity): +class VL53L1XSensor(SensorEntity): """Implementation of VL53L1X sensor.""" def __init__(self, vl53l1x_sensor, name, unit, i2c_address): diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 583683e53aeebd..36f5dedde3d3f4 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,6 +1,7 @@ """Support for Toon sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -109,7 +110,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class ToonSensor(ToonEntity): +class ToonSensor(ToonEntity, SensorEntity): """Defines a Toon sensor.""" def __init__(self, coordinator: ToonDataUpdateCoordinator, *, key: str) -> None: diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py index 4b52a565d873e2..156259adccb831 100644 --- a/homeassistant/components/torque/sensor.py +++ b/homeassistant/components/torque/sensor.py @@ -4,11 +4,10 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_EMAIL, CONF_NAME, DEGREE from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity API_PATH = "/api/torque" @@ -106,7 +105,7 @@ def get(self, request): return "OK!" -class TorqueSensor(Entity): +class TorqueSensor(SensorEntity): """Representation of a Torque sensor.""" def __init__(self, name, unit): diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index c2bf640e2aa927..455ca69147d69e 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -1,5 +1,6 @@ """Support for IKEA Tradfri sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from .base_class import TradfriBaseDevice @@ -25,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(TradfriSensor(sensor, api, gateway_id) for sensor in sensors) -class TradfriSensor(TradfriBaseDevice): +class TradfriSensor(TradfriBaseDevice, SensorEntity): """The platform class required by Home Assistant.""" def __init__(self, device, api, gateway_id): diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 458994225644f3..37e3bd52cdc5ce 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -6,7 +6,7 @@ from pytrafikverket import TrafikverketTrain import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_NAME, @@ -16,7 +16,6 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -116,7 +115,7 @@ def next_departuredate(departure): return next_weekday(today_date, WEEKDAYS.index(departure[0])) -class TrainSensor(Entity): +class TrainSensor(SensorEntity): """Contains data about a train depature.""" def __init__(self, train_api, name, from_station, to_station, weekday, time): diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index f2e2521a90b969..1ae090ea23161e 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -8,7 +8,7 @@ from pytrafikverket.trafikverket_weather import TrafikverketWeather import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -24,7 +24,6 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -145,7 +144,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class TrafikverketWeatherStation(Entity): +class TrafikverketWeatherStation(SensorEntity): """Representation of a Trafikverket sensor.""" def __init__(self, weather_api, name, sensor_type, sensor_station): diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index cd9e57c50d74ca..a82adac6160d7b 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -3,10 +3,10 @@ from transmissionrpc.torrent import Torrent +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, DATA_RATE_MEGABYTES_PER_SECOND, STATE_IDLE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import TransmissionClient from .const import ( @@ -38,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class TransmissionSensor(Entity): +class TransmissionSensor(SensorEntity): """A base class for all Transmission sensors.""" def __init__(self, tm_client, client_name, sensor_name, sub_type=None): diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py index f6d4e40e4e8cb1..be76999ec3f403 100644 --- a/homeassistant/components/transport_nsw/sensor.py +++ b/homeassistant/components/transport_nsw/sensor.py @@ -4,7 +4,7 @@ from TransportNSW import TransportNSW import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_MODE, @@ -13,7 +13,6 @@ TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_STOP_ID = "stop_id" ATTR_ROUTE = "route" @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TransportNSWSensor(data, stop_id, name)], True) -class TransportNSWSensor(Entity): +class TransportNSWSensor(SensorEntity): """Implementation of an Transport NSW sensor.""" def __init__(self, data, stop_id, name): diff --git a/homeassistant/components/travisci/sensor.py b/homeassistant/components/travisci/sensor.py index 6464667bffe2e0..94a6ba3a48f07f 100644 --- a/homeassistant/components/travisci/sensor.py +++ b/homeassistant/components/travisci/sensor.py @@ -6,7 +6,7 @@ from travispy.errors import TravisError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ TIME_SECONDS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -94,7 +93,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class TravisCISensor(Entity): +class TravisCISensor(SensorEntity): """Representation of a Travis CI sensor.""" def __init__(self, data, repo_name, user, branch, sensor_type): diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index 34da746cf0607f..ad552a4b3415ab 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -12,6 +12,7 @@ TwenteMilieuConnectionError, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback @@ -71,7 +72,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class TwenteMilieuSensor(Entity): +class TwenteMilieuSensor(SensorEntity): """Defines a Twente Milieu sensor.""" def __init__( diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 0e5abb58747148..cfabcf1045f291 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -5,10 +5,9 @@ from twitch import TwitchClient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_CLIENT_ID, CONF_TOKEN import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TwitchSensor(channel_id, client) for channel_id in channel_ids], True) -class TwitchSensor(Entity): +class TwitchSensor(SensorEntity): """Representation of an Twitch channel.""" def __init__(self, channel, client): diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index f5e4b4373c0de0..f5cb21edcf7907 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -6,10 +6,9 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MODE, HTTP_OK, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -83,7 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class UkTransportSensor(Entity): +class UkTransportSensor(SensorEntity): """ Sensor that reads the UK transport web API. diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 77e8f5afbe4285..755d95a061bbe6 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta -from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN +from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN, SensorEntity from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -79,7 +79,7 @@ def add_uptime_entities(controller, async_add_entities, clients): async_add_entities(sensors) -class UniFiBandwidthSensor(UniFiClient): +class UniFiBandwidthSensor(UniFiClient, SensorEntity): """UniFi bandwidth sensor base class.""" DOMAIN = DOMAIN @@ -126,7 +126,7 @@ def state(self) -> int: return self.client.tx_bytes / 1000000 -class UniFiUpTimeSensor(UniFiClient): +class UniFiUpTimeSensor(UniFiClient, SensorEntity): """UniFi uptime sensor.""" DOMAIN = DOMAIN diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index 76bb1d023b7ff2..0e95b6106a399d 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -4,6 +4,7 @@ from datetime import timedelta from typing import Any, Mapping +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_BYTES, DATA_RATE_KIBIBYTES_PER_SECOND from homeassistant.helpers import device_registry as dr @@ -117,7 +118,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class UpnpSensor(CoordinatorEntity): +class UpnpSensor(CoordinatorEntity, SensorEntity): """Base class for UPnP/IGD sensors.""" def __init__( diff --git a/homeassistant/components/uptime/sensor.py b/homeassistant/components/uptime/sensor.py index 8363d2da2cbef7..7e79c2fbb5ea0d 100644 --- a/homeassistant/components/uptime/sensor.py +++ b/homeassistant/components/uptime/sensor.py @@ -2,10 +2,13 @@ import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DEVICE_CLASS_TIMESTAMP, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util DEFAULT_NAME = "Uptime" @@ -30,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([UptimeSensor(name)], True) -class UptimeSensor(Entity): +class UptimeSensor(SensorEntity): """Representation of an uptime sensor.""" def __init__(self, name): diff --git a/homeassistant/components/uscis/sensor.py b/homeassistant/components/uscis/sensor.py index 5f26a0ff82ef91..bd261aba4fbad9 100644 --- a/homeassistant/components/uscis/sensor.py +++ b/homeassistant/components/uscis/sensor.py @@ -5,10 +5,9 @@ import uscisstatus import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -33,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Setup USCIS Sensor Fail check if your Case ID is Valid") -class UscisSensor(Entity): +class UscisSensor(SensorEntity): """USCIS Sensor will check case status on daily basis.""" MIN_TIME_BETWEEN_UPDATES = timedelta(hours=24) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 09f788806f6eb1..d28819a38cb225 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -5,6 +5,7 @@ import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -102,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class UtilityMeterSensor(RestoreEntity): +class UtilityMeterSensor(RestoreEntity, SensorEntity): """Representation of an utility meter sensor.""" def __init__( diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index 6f6755a05e77e4..b4269ac4451aeb 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -12,7 +13,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import DOMAIN, METRIC_KEY_MODE, SIGNAL_VALLOX_STATE_UPDATE @@ -96,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, update_before_add=False) -class ValloxSensor(Entity): +class ValloxSensor(SensorEntity): """Representation of a Vallox sensor.""" def __init__( diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index d1a461d94faaaf..31c5da097ffda2 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -5,10 +5,9 @@ import vasttrafik import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_DELAY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.dt import now @@ -71,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class VasttrafikDepartureSensor(Entity): +class VasttrafikDepartureSensor(SensorEntity): """Implementation of a Vasttrafik Departure Sensor.""" def __init__(self, planner, name, departure, heading, lines, delay): diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 095108b440115c..9d9b68dd4eb78a 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,4 +1,5 @@ """Support for Velbus sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR from . import VelbusEntity @@ -18,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusSensor(VelbusEntity): +class VelbusSensor(VelbusEntity, SensorEntity): """Representation of a sensor.""" def __init__(self, module, channel, counter=False): diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index d52d49c2546775..516801b57c6419 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -6,7 +6,11 @@ import pyvera as veraApi -from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT +from homeassistant.components.sensor import ( + DOMAIN as PLATFORM_DOMAIN, + ENTITY_ID_FORMAT, + SensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant @@ -35,7 +39,7 @@ async def async_setup_entry( ) -class VeraSensor(VeraDevice[veraApi.VeraSensor], Entity): +class VeraSensor(VeraDevice[veraApi.VeraSensor], SensorEntity): """Representation of a Vera Sensor.""" def __init__( diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index f7f2212149bec1..93e1793da8d03f 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -6,6 +6,7 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, TEMP_CELSIUS @@ -45,7 +46,7 @@ async def async_setup_entry( async_add_entities(sensors) -class VerisureThermometer(CoordinatorEntity, Entity): +class VerisureThermometer(CoordinatorEntity, SensorEntity): """Representation of a Verisure thermometer.""" coordinator: VerisureDataUpdateCoordinator @@ -109,7 +110,7 @@ def unit_of_measurement(self) -> str: return TEMP_CELSIUS -class VerisureHygrometer(CoordinatorEntity, Entity): +class VerisureHygrometer(CoordinatorEntity, SensorEntity): """Representation of a Verisure hygrometer.""" coordinator: VerisureDataUpdateCoordinator @@ -173,7 +174,7 @@ def unit_of_measurement(self) -> str: return PERCENTAGE -class VerisureMouseDetection(CoordinatorEntity, Entity): +class VerisureMouseDetection(CoordinatorEntity, SensorEntity): """Representation of a Verisure mouse detector.""" coordinator: VerisureDataUpdateCoordinator diff --git a/homeassistant/components/versasense/sensor.py b/homeassistant/components/versasense/sensor.py index e598093cd37164..d29032af399558 100644 --- a/homeassistant/components/versasense/sensor.py +++ b/homeassistant/components/versasense/sensor.py @@ -1,7 +1,7 @@ """Support for VersaSense sensor peripheral.""" import logging -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import DOMAIN from .const import ( @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensor_list) -class VSensor(Entity): +class VSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, peripheral, parent_name, unit, measurement, consumer): diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 645cde63459706..3e5d235b5d744f 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -10,11 +10,10 @@ ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_SOURCE from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle ALL_IMAGES = [ @@ -94,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([VersionSensor(haversion, name)], True) -class VersionSensor(Entity): +class VersionSensor(SensorEntity): """Representation of a Home Assistant version sensor.""" def __init__(self, haversion, name): diff --git a/homeassistant/components/viaggiatreno/sensor.py b/homeassistant/components/viaggiatreno/sensor.py index e886b9d9728dd4..10821859f9a5e7 100644 --- a/homeassistant/components/viaggiatreno/sensor.py +++ b/homeassistant/components/viaggiatreno/sensor.py @@ -6,10 +6,9 @@ import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, HTTP_OK, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -82,7 +81,7 @@ async def async_http_request(hass, uri): _LOGGER.error("Received non-JSON data from ViaggiaTreno API endpoint") -class ViaggiaTrenoSensor(Entity): +class ViaggiaTrenoSensor(SensorEntity): """Implementation of a ViaggiaTreno sensor.""" def __init__(self, train_id, station_id, name): diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index a14e00923c2e44..79e7391ca70cab 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -3,6 +3,7 @@ import requests +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ICON, @@ -14,7 +15,6 @@ TEMP_CELSIUS, TIME_HOURS, ) -from homeassistant.helpers.entity import Entity from . import ( DOMAIN as VICARE_DOMAIN, @@ -269,7 +269,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ViCareSensor(Entity): +class ViCareSensor(SensorEntity): """Representation of a ViCare sensor.""" def __init__(self, name, api, sensor_type): diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index 80a14354913555..90527c60458f42 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -1,6 +1,6 @@ """Support for Vilfo Router sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ICON -from homeassistant.helpers.entity import Entity from .const import ( ATTR_API_DATA_FIELD, @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, True) -class VilfoRouterSensor(Entity): +class VilfoRouterSensor(SensorEntity): """Define a Vilfo Router Sensor.""" def __init__(self, sensor_type, api): diff --git a/homeassistant/components/volkszaehler/sensor.py b/homeassistant/components/volkszaehler/sensor.py index a418780c165b80..61fcf1d2969883 100644 --- a/homeassistant/components/volkszaehler/sensor.py +++ b/homeassistant/components/volkszaehler/sensor.py @@ -6,7 +6,7 @@ from volkszaehler.exceptions import VolkszaehlerApiConnectionError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_MONITORED_CONDITIONS, @@ -18,7 +18,6 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -77,7 +76,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class VolkszaehlerSensor(Entity): +class VolkszaehlerSensor(SensorEntity): """Implementation of a Volkszaehler sensor.""" def __init__(self, vz_api, name, sensor_type): diff --git a/homeassistant/components/volvooncall/sensor.py b/homeassistant/components/volvooncall/sensor.py index 0915408860db35..ad6571576b4adf 100644 --- a/homeassistant/components/volvooncall/sensor.py +++ b/homeassistant/components/volvooncall/sensor.py @@ -1,4 +1,6 @@ """Support for Volvo On Call sensors.""" +from homeassistant.components.sensor import SensorEntity + from . import DATA_KEY, VolvoEntity @@ -9,7 +11,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([VolvoSensor(hass.data[DATA_KEY], *discovery_info)]) -class VolvoSensor(VolvoEntity): +class VolvoSensor(VolvoEntity, SensorEntity): """Representation of a Volvo sensor.""" @property diff --git a/homeassistant/components/vultr/sensor.py b/homeassistant/components/vultr/sensor.py index 0bcdcf9d4c153a..5e6815944d76ff 100644 --- a/homeassistant/components/vultr/sensor.py +++ b/homeassistant/components/vultr/sensor.py @@ -3,10 +3,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME, DATA_GIGABYTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import ( ATTR_CURRENT_BANDWIDTH_USED, @@ -58,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class VultrSensor(Entity): +class VultrSensor(SensorEntity): """Representation of a Vultr subscription sensor.""" def __init__(self, vultr, subscription, condition, name): From 0c086b5067858155e935e5486dfd149d9d1e9e20 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:50:29 +0100 Subject: [PATCH 1450/1818] Migrate integrations w-z to extend SensorEntity (#48217) --- homeassistant/components/waqi/sensor.py | 4 ++-- homeassistant/components/waterfurnace/sensor.py | 5 ++--- homeassistant/components/waze_travel_time/sensor.py | 5 ++--- homeassistant/components/websocket_api/sensor.py | 4 ++-- homeassistant/components/whois/sensor.py | 5 ++--- homeassistant/components/wiffi/sensor.py | 5 +++-- homeassistant/components/wink/sensor.py | 11 ++++++----- homeassistant/components/wirelesstag/sensor.py | 4 ++-- homeassistant/components/withings/sensor.py | 4 ++-- homeassistant/components/wled/sensor.py | 4 ++-- homeassistant/components/wolflink/sensor.py | 3 ++- homeassistant/components/worldclock/sensor.py | 5 ++--- homeassistant/components/worldtidesinfo/sensor.py | 5 ++--- homeassistant/components/worxlandroid/sensor.py | 5 ++--- homeassistant/components/wsdot/sensor.py | 5 ++--- homeassistant/components/wunderground/sensor.py | 5 ++--- homeassistant/components/xbee/sensor.py | 4 ++-- homeassistant/components/xbox/sensor.py | 3 ++- homeassistant/components/xbox_live/sensor.py | 5 ++--- homeassistant/components/xiaomi_aqara/sensor.py | 5 +++-- homeassistant/components/xiaomi_miio/sensor.py | 9 ++++----- homeassistant/components/xs1/sensor.py | 4 ++-- homeassistant/components/yandex_transport/sensor.py | 5 ++--- homeassistant/components/zabbix/sensor.py | 5 ++--- homeassistant/components/zamg/sensor.py | 4 ++-- homeassistant/components/zestimate/sensor.py | 5 ++--- homeassistant/components/zha/sensor.py | 3 ++- homeassistant/components/zodiac/sensor.py | 4 ++-- homeassistant/components/zoneminder/sensor.py | 9 ++++----- homeassistant/components/zwave/sensor.py | 4 ++-- homeassistant/components/zwave_js/sensor.py | 3 ++- 31 files changed, 72 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index ac43da68641f49..ef01c057a9e7db 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -7,6 +7,7 @@ import voluptuous as vol from waqiasync import WaqiClient +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_TEMPERATURE, @@ -17,7 +18,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -93,7 +93,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(dev, True) -class WaqiSensor(Entity): +class WaqiSensor(SensorEntity): """Implementation of a WAQI sensor.""" def __init__(self, client, station): diff --git a/homeassistant/components/waterfurnace/sensor.py b/homeassistant/components/waterfurnace/sensor.py index dfb960fe81930a..91e455d03d69ad 100644 --- a/homeassistant/components/waterfurnace/sensor.py +++ b/homeassistant/components/waterfurnace/sensor.py @@ -1,9 +1,8 @@ """Support for Waterfurnace.""" -from homeassistant.components.sensor import ENTITY_ID_FORMAT +from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity from homeassistant.const import PERCENTAGE, POWER_WATT, TEMP_FAHRENHEIT from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from . import DOMAIN as WF_DOMAIN, UPDATE_TOPIC @@ -61,7 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class WaterFurnaceSensor(Entity): +class WaterFurnaceSensor(SensorEntity): """Implementing the Waterfurnace sensor.""" def __init__(self, client, config): diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index be2cf7ca2da492..327a0769b505d6 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -6,7 +6,7 @@ import WazeRouteCalculator import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -20,7 +20,6 @@ ) from homeassistant.helpers import location import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -126,7 +125,7 @@ def _get_location_from_attributes(state): return "{},{}".format(attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE)) -class WazeTravelTime(Entity): +class WazeTravelTime(SensorEntity): """Representation of a Waze travel time sensor.""" def __init__(self, name, origin, destination, waze_data): diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py index c026978634f864..dfcdc57842e395 100644 --- a/homeassistant/components/websocket_api/sensor.py +++ b/homeassistant/components/websocket_api/sensor.py @@ -1,7 +1,7 @@ """Entity to track connections to websocket API.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from .const import ( DATA_CONNECTIONS, @@ -19,7 +19,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([entity]) -class APICount(Entity): +class APICount(SensorEntity): """Entity to represent how many people are connected to the stream API.""" def __init__(self): diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 72c992456a7e72..6d97037a4ee3c6 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -5,10 +5,9 @@ import voluptuous as vol import whois -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_DOMAIN, CONF_NAME, TIME_DAYS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return -class WhoisSensor(Entity): +class WhoisSensor(SensorEntity): """Implementation of a WHOIS sensor.""" def __init__(self, name, domain): diff --git a/homeassistant/components/wiffi/sensor.py b/homeassistant/components/wiffi/sensor.py index f207e3be3acaa9..800a420f8f0b2b 100644 --- a/homeassistant/components/wiffi/sensor.py +++ b/homeassistant/components/wiffi/sensor.py @@ -5,6 +5,7 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + SensorEntity, ) from homeassistant.const import DEGREE, PRESSURE_MBAR, TEMP_CELSIUS from homeassistant.core import callback @@ -58,7 +59,7 @@ def _create_entity(device, metric): async_dispatcher_connect(hass, CREATE_ENTITY_SIGNAL, _create_entity) -class NumberEntity(WiffiEntity): +class NumberEntity(WiffiEntity, SensorEntity): """Entity for wiffi metrics which have a number value.""" def __init__(self, device, metric, options): @@ -100,7 +101,7 @@ def _update_value_callback(self, device, metric): self.async_write_ha_state() -class StringEntity(WiffiEntity): +class StringEntity(WiffiEntity, SensorEntity): """Entity for wiffi metrics which have a string value.""" def __init__(self, device, metric, options): diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index d2de4c43945901..88f9588750ebb9 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -3,6 +3,7 @@ import pywink +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEGREE, TEMP_CELSIUS from . import DOMAIN, WinkDevice @@ -19,29 +20,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _id = sensor.object_id() + sensor.name() if _id not in hass.data[DOMAIN]["unique_ids"]: if sensor.capability() in SENSOR_TYPES: - add_entities([WinkSensorDevice(sensor, hass)]) + add_entities([WinkSensorEntity(sensor, hass)]) for eggtray in pywink.get_eggtrays(): _id = eggtray.object_id() + eggtray.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkSensorDevice(eggtray, hass)]) + add_entities([WinkSensorEntity(eggtray, hass)]) for tank in pywink.get_propane_tanks(): _id = tank.object_id() + tank.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkSensorDevice(tank, hass)]) + add_entities([WinkSensorEntity(tank, hass)]) for piggy_bank in pywink.get_piggy_banks(): _id = piggy_bank.object_id() + piggy_bank.name() if _id not in hass.data[DOMAIN]["unique_ids"]: try: if piggy_bank.capability() in SENSOR_TYPES: - add_entities([WinkSensorDevice(piggy_bank, hass)]) + add_entities([WinkSensorEntity(piggy_bank, hass)]) except AttributeError: _LOGGER.info("Device is not a sensor") -class WinkSensorDevice(WinkDevice): +class WinkSensorEntity(WinkDevice, SensorEntity): """Representation of a Wink sensor.""" def __init__(self, wink, hass): diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index 2a845249028011..cc0ce0cb8882bb 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class WirelessTagSensor(WirelessTagBaseSensor): +class WirelessTagSensor(WirelessTagBaseSensor, SensorEntity): """Representation of a Sensor.""" def __init__(self, api, tag, sensor_type, config): diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index a7a7947dec5bf0..89bb81eb60764c 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -3,7 +3,7 @@ from typing import Callable -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity @@ -28,7 +28,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class WithingsHealthSensor(BaseWithingsSensor): +class WithingsHealthSensor(BaseWithingsSensor, SensorEntity): """Implementation of a Withings sensor.""" @property diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index 4bfca885613cb0..7e91f81dea0863 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -4,7 +4,7 @@ from datetime import timedelta from typing import Any, Callable -from homeassistant.components.sensor import DEVICE_CLASS_CURRENT +from homeassistant.components.sensor import DEVICE_CLASS_CURRENT, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DATA_BYTES, @@ -42,7 +42,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class WLEDSensor(WLEDDeviceEntity): +class WLEDSensor(WLEDDeviceEntity, SensorEntity): """Defines a WLED sensor.""" def __init__( diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index 9ea7f9d116301a..f243160ff59082 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -9,6 +9,7 @@ Temperature, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -46,7 +47,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class WolfLinkSensor(CoordinatorEntity): +class WolfLinkSensor(CoordinatorEntity, SensorEntity): """Base class for all Wolf entities.""" def __init__(self, coordinator, wolf_object: Parameter, device_id): diff --git a/homeassistant/components/worldclock/sensor.py b/homeassistant/components/worldclock/sensor.py index e02dc3a0d5c7ab..de5b3991e3f085 100644 --- a/homeassistant/components/worldclock/sensor.py +++ b/homeassistant/components/worldclock/sensor.py @@ -1,10 +1,9 @@ """Support for showing the time in a different time zone.""" import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_TIME_ZONE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_TIME_FORMAT = "time_format" @@ -39,7 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class WorldClockSensor(Entity): +class WorldClockSensor(SensorEntity): """Representation of a World clock sensor.""" def __init__(self, time_zone, name, time_format): diff --git a/homeassistant/components/worldtidesinfo/sensor.py b/homeassistant/components/worldtidesinfo/sensor.py index 43c9446b6ce0e5..0fa65957e4001c 100644 --- a/homeassistant/components/worldtidesinfo/sensor.py +++ b/homeassistant/components/worldtidesinfo/sensor.py @@ -6,7 +6,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -15,7 +15,6 @@ CONF_NAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -55,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([tides]) -class WorldTidesInfoSensor(Entity): +class WorldTidesInfoSensor(SensorEntity): """Representation of a WorldTidesInfo sensor.""" def __init__(self, name, lat, lon, key): diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index e4fe33f62f128a..9be3afabc9f3ee 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -6,11 +6,10 @@ import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_PIN, CONF_TIMEOUT, PERCENTAGE from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -50,7 +49,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([WorxLandroidSensor(typ, config)]) -class WorxLandroidSensor(Entity): +class WorxLandroidSensor(SensorEntity): """Implementation of a Worx Landroid sensor.""" def __init__(self, sensor, config): diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py index 34ad5a37ec8dab..9e4d957d028850 100644 --- a/homeassistant/components/wsdot/sensor.py +++ b/homeassistant/components/wsdot/sensor.py @@ -6,7 +6,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_NAME, @@ -17,7 +17,6 @@ TIME_MINUTES, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -65,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class WashingtonStateTransportSensor(Entity): +class WashingtonStateTransportSensor(SensorEntity): """ Sensor that reads the WSDOT web API. diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 38d6bba0b1fb3a..358e305dc47742 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.components import sensor -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -36,7 +36,6 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util import Throttle @@ -1117,7 +1116,7 @@ async def async_setup_platform( async_add_entities(sensors, True) -class WUndergroundSensor(Entity): +class WUndergroundSensor(SensorEntity): """Implementing the WUnderground sensor.""" def __init__(self, hass: HomeAssistantType, rest, condition, unique_id_base: str): diff --git a/homeassistant/components/xbee/sensor.py b/homeassistant/components/xbee/sensor.py index 4d9f9ca518b6b0..78cfe964277caf 100644 --- a/homeassistant/components/xbee/sensor.py +++ b/homeassistant/components/xbee/sensor.py @@ -5,8 +5,8 @@ import voluptuous as vol from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_TYPE, TEMP_CELSIUS -from homeassistant.helpers.entity import Entity from . import DOMAIN, PLATFORM_SCHEMA, XBeeAnalogIn, XBeeAnalogInConfig, XBeeConfig @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor_class(config_class(config), zigbee_device)], True) -class XBeeTemperatureSensor(Entity): +class XBeeTemperatureSensor(SensorEntity): """Representation of XBee Pro temperature sensor.""" def __init__(self, config, device): diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index 88bf112b7282bb..ac19a4be1934c2 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -3,6 +3,7 @@ from functools import partial +from homeassistant.components.sensor import SensorEntity from homeassistant.core import callback from homeassistant.helpers.entity_registry import ( async_get_registry as async_get_entity_registry, @@ -29,7 +30,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_ent update_friends() -class XboxSensorEntity(XboxBaseSensorEntity): +class XboxSensorEntity(XboxBaseSensorEntity, SensorEntity): """Representation of a Xbox presence state.""" @property diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index 780051b2d872b2..2717bc1ad6268d 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -5,11 +5,10 @@ import voluptuous as vol from xboxapi import Client -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -73,7 +72,7 @@ def get_user_gamercard(api, xuid): return None -class XboxSensor(Entity): +class XboxSensor(SensorEntity): """A class for the Xbox account.""" def __init__(self, api, xuid, gamercard, interval): diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 969980bf7c8104..fa3d265f12fd97 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -1,6 +1,7 @@ """Support for Xiaomi Aqara sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, @@ -107,7 +108,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class XiaomiSensor(XiaomiDevice): +class XiaomiSensor(XiaomiDevice, SensorEntity): """Representation of a XiaomiSensor.""" def __init__(self, device, name, data_key, xiaomi_hub, config_entry): @@ -171,7 +172,7 @@ def parse_data(self, data, raw_data): return True -class XiaomiBatterySensor(XiaomiDevice): +class XiaomiBatterySensor(XiaomiDevice, SensorEntity): """Representation of a XiaomiSensor.""" @property diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index e2b645a10ea09c..ac9a7ab4543042 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -12,7 +12,7 @@ ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_BATTERY_LEVEL, @@ -30,7 +30,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import CONF_DEVICE, CONF_FLOW_TYPE, CONF_GATEWAY, DOMAIN, KEY_COORDINATOR from .device import XiaomiMiioEntity @@ -147,7 +146,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, update_before_add=True) -class XiaomiAirQualityMonitor(XiaomiMiioEntity): +class XiaomiAirQualityMonitor(XiaomiMiioEntity, SensorEntity): """Representation of a Xiaomi Air Quality Monitor.""" def __init__(self, name, device, entry, unique_id): @@ -221,7 +220,7 @@ async def async_update(self): _LOGGER.error("Got exception while fetching the state: %s", ex) -class XiaomiGatewaySensor(XiaomiGatewayDevice): +class XiaomiGatewaySensor(XiaomiGatewayDevice, SensorEntity): """Representation of a XiaomiGatewaySensor.""" def __init__(self, coordinator, sub_device, entry, data_key): @@ -252,7 +251,7 @@ def state(self): return self._sub_device.status[self._data_key] -class XiaomiGatewayIlluminanceSensor(Entity): +class XiaomiGatewayIlluminanceSensor(SensorEntity): """Representation of the gateway device's illuminance sensor.""" def __init__(self, gateway_device, gateway_name, gateway_device_id): diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py index 9e6afa40fa47cb..f158e7d74b8bcc 100644 --- a/homeassistant/components/xs1/sensor.py +++ b/homeassistant/components/xs1/sensor.py @@ -1,7 +1,7 @@ """Support for XS1 sensors.""" from xs1_api_client.api_constants import ActuatorType -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import ACTUATORS, DOMAIN as COMPONENT_DOMAIN, SENSORS, XS1DeviceEntity @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensor_entities) -class XS1Sensor(XS1DeviceEntity, Entity): +class XS1Sensor(XS1DeviceEntity, SensorEntity): """Representation of a Sensor.""" @property diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index 0df073f581d25d..08e856a721e48a 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -6,11 +6,10 @@ from aioymaps import YandexMapsRequester import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.aiohttp_client import async_create_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([DiscoverYandexTransport(data, stop_id, routes, name)], True) -class DiscoverYandexTransport(Entity): +class DiscoverYandexTransport(SensorEntity): """Implementation of yandex_transport sensor.""" def __init__(self, requester: YandexMapsRequester, stop_id, routes, name): diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 536709e5a83b37..a264428769086c 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -4,10 +4,9 @@ import voluptuous as vol from homeassistant.components import zabbix -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -79,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ZabbixTriggerCountSensor(Entity): +class ZabbixTriggerCountSensor(SensorEntity): """Get the active trigger count for all Zabbix monitored hosts.""" def __init__(self, zApi, name="Zabbix"): diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index ef0a476f612da5..2e2d07cea62ba3 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -11,6 +11,7 @@ import requests import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( AREA_SQUARE_METERS, ATTR_ATTRIBUTION, @@ -27,7 +28,6 @@ __version__, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -132,7 +132,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ZamgSensor(Entity): +class ZamgSensor(SensorEntity): """Implementation of a ZAMG sensor.""" def __init__(self, probe, variable, name): diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py index ed15d42b7e3565..0333bb76a201b7 100644 --- a/homeassistant/components/zestimate/sensor.py +++ b/homeassistant/components/zestimate/sensor.py @@ -6,10 +6,9 @@ import voluptuous as vol import xmltodict -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) _RESOURCE = "http://www.zillow.com/webservice/GetZestimate.htm" @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ZestimateDataSensor(Entity): +class ZestimateDataSensor(SensorEntity): """Implementation of a Zestimate sensor.""" def __init__(self, name, params): diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 425a41a1340a7c..41dce816e86f75 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -15,6 +15,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, DOMAIN, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -89,7 +90,7 @@ async def async_setup_entry( hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub) -class Sensor(ZhaEntity): +class Sensor(ZhaEntity, SensorEntity): """Base ZHA sensor.""" SENSOR_ATTR: int | str | None = None diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py index b602d7a50c40d1..4c037a7aa021de 100644 --- a/homeassistant/components/zodiac/sensor.py +++ b/homeassistant/components/zodiac/sensor.py @@ -1,5 +1,5 @@ """Support for tracking the zodiac sign.""" -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from homeassistant.util.dt import as_local, utcnow from .const import ( @@ -162,7 +162,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([ZodiacSensor()], True) -class ZodiacSensor(Entity): +class ZodiacSensor(SensorEntity): """Representation of a Zodiac sensor.""" def __init__(self): diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index 75531e79e13b81..701f4b490d34bf 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -4,10 +4,9 @@ import voluptuous as vol from zoneminder.monitor import TimePeriod -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import DOMAIN as ZONEMINDER_DOMAIN @@ -57,7 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ZMSensorMonitors(Entity): +class ZMSensorMonitors(SensorEntity): """Get the status of each ZoneMinder monitor.""" def __init__(self, monitor): @@ -91,7 +90,7 @@ def update(self): self._is_available = self._monitor.is_available -class ZMSensorEvents(Entity): +class ZMSensorEvents(SensorEntity): """Get the number of events for each monitor.""" def __init__(self, monitor, include_archived, sensor_type): @@ -122,7 +121,7 @@ def update(self): self._state = self._monitor.get_events(self.time_period, self._include_archived) -class ZMSensorRunState(Entity): +class ZMSensorRunState(SensorEntity): """Get the ZoneMinder run state.""" def __init__(self, client): diff --git a/homeassistant/components/zwave/sensor.py b/homeassistant/components/zwave/sensor.py index aae38382f2e720..a3183ba8927e8f 100644 --- a/homeassistant/components/zwave/sensor.py +++ b/homeassistant/components/zwave/sensor.py @@ -1,5 +1,5 @@ """Support for Z-Wave sensors.""" -from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, DOMAIN +from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, DOMAIN, SensorEntity from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -37,7 +37,7 @@ def get_device(node, values, **kwargs): return None -class ZWaveSensor(ZWaveDeviceEntity): +class ZWaveSensor(ZWaveDeviceEntity, SensorEntity): """Representation of a Z-Wave sensor.""" def __init__(self, values): diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 5f116f0790c7bc..574e1af658c4f8 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -13,6 +13,7 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DOMAIN as SENSOR_DOMAIN, + SensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -67,7 +68,7 @@ def async_add_sensor(info: ZwaveDiscoveryInfo) -> None: ) -class ZwaveSensorBase(ZWaveBaseEntity): +class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity): """Basic Representation of a Z-Wave sensor.""" def __init__( From 64bc9a81965904040ceb3a3b25b1202212167a72 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:54:14 +0100 Subject: [PATCH 1451/1818] Migrate integrations r-s to extend SensorEntity (#48215) --- homeassistant/components/radarr/sensor.py | 5 ++--- homeassistant/components/rainbird/sensor.py | 4 ++-- homeassistant/components/raincloud/sensor.py | 4 ++-- homeassistant/components/rainforest_eagle/sensor.py | 7 +++---- homeassistant/components/rainmachine/sensor.py | 3 ++- homeassistant/components/random/sensor.py | 5 ++--- homeassistant/components/recollect_waste/sensor.py | 4 ++-- homeassistant/components/reddit/sensor.py | 5 ++--- homeassistant/components/rejseplanen/sensor.py | 5 ++--- homeassistant/components/repetier/sensor.py | 4 ++-- homeassistant/components/rest/sensor.py | 8 ++++++-- homeassistant/components/rflink/sensor.py | 4 ++-- homeassistant/components/rfxtrx/sensor.py | 3 ++- homeassistant/components/ring/sensor.py | 4 ++-- homeassistant/components/ripple/sensor.py | 5 ++--- homeassistant/components/risco/sensor.py | 3 ++- homeassistant/components/rmvtransport/sensor.py | 5 ++--- homeassistant/components/roomba/sensor.py | 3 ++- homeassistant/components/rova/sensor.py | 5 ++--- homeassistant/components/rtorrent/sensor.py | 5 ++--- homeassistant/components/sabnzbd/sensor.py | 4 ++-- homeassistant/components/saj/sensor.py | 5 ++--- homeassistant/components/scrape/sensor.py | 5 ++--- homeassistant/components/screenlogic/sensor.py | 10 +++++++--- homeassistant/components/season/sensor.py | 5 ++--- homeassistant/components/sense/sensor.py | 10 +++++----- homeassistant/components/sensehat/sensor.py | 5 ++--- homeassistant/components/serial/sensor.py | 5 ++--- homeassistant/components/serial_pm/sensor.py | 5 ++--- homeassistant/components/seventeentrack/sensor.py | 7 +++---- homeassistant/components/shelly/sensor.py | 7 ++++--- homeassistant/components/shodan/sensor.py | 5 ++--- homeassistant/components/sht31/sensor.py | 5 ++--- homeassistant/components/sigfox/sensor.py | 5 ++--- homeassistant/components/simplisafe/sensor.py | 3 ++- homeassistant/components/simulated/sensor.py | 5 ++--- homeassistant/components/skybeacon/sensor.py | 9 ++++----- homeassistant/components/skybell/sensor.py | 4 ++-- homeassistant/components/sleepiq/sensor.py | 4 +++- homeassistant/components/sma/sensor.py | 5 ++--- homeassistant/components/smappee/sensor.py | 4 ++-- homeassistant/components/smart_meter_texas/sensor.py | 3 ++- homeassistant/components/smartthings/sensor.py | 5 +++-- homeassistant/components/smarttub/sensor.py | 4 +++- homeassistant/components/smarty/sensor.py | 4 ++-- homeassistant/components/sms/sensor.py | 4 ++-- homeassistant/components/snmp/sensor.py | 5 ++--- homeassistant/components/sochain/sensor.py | 5 ++--- homeassistant/components/socialblade/sensor.py | 5 ++--- homeassistant/components/solaredge/sensor.py | 4 ++-- homeassistant/components/solaredge_local/sensor.py | 5 ++--- homeassistant/components/solarlog/sensor.py | 4 ++-- homeassistant/components/solax/sensor.py | 5 ++--- homeassistant/components/soma/sensor.py | 4 ++-- homeassistant/components/somfy/sensor.py | 3 ++- homeassistant/components/sonarr/sensor.py | 3 ++- homeassistant/components/speedtestdotnet/sensor.py | 3 ++- homeassistant/components/spotcrime/sensor.py | 5 ++--- homeassistant/components/sql/sensor.py | 5 ++--- homeassistant/components/srp_energy/sensor.py | 4 ++-- homeassistant/components/starline/sensor.py | 5 ++--- homeassistant/components/starlingbank/sensor.py | 5 ++--- homeassistant/components/startca/sensor.py | 5 ++--- homeassistant/components/statistics/sensor.py | 5 ++--- homeassistant/components/steam_online/sensor.py | 5 ++--- homeassistant/components/streamlabswater/sensor.py | 4 ++-- homeassistant/components/subaru/sensor.py | 4 ++-- homeassistant/components/suez_water/sensor.py | 5 ++--- homeassistant/components/supervisord/sensor.py | 5 ++--- homeassistant/components/surepetcare/sensor.py | 4 ++-- .../components/swiss_hydrological_data/sensor.py | 5 ++--- .../components/swiss_public_transport/sensor.py | 5 ++--- homeassistant/components/syncthru/sensor.py | 5 ++--- homeassistant/components/synology_dsm/sensor.py | 7 ++++--- homeassistant/components/systemmonitor/sensor.py | 5 ++--- 75 files changed, 173 insertions(+), 189 deletions(-) diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index 9baed6c41c7aea..542ff285261f58 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -7,7 +7,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -26,7 +26,6 @@ HTTP_OK, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -95,7 +94,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([RadarrSensor(hass, config, sensor) for sensor in conditions], True) -class RadarrSensor(Entity): +class RadarrSensor(SensorEntity): """Implementation of the Radarr sensor.""" def __init__(self, hass, conf, sensor_type): diff --git a/homeassistant/components/rainbird/sensor.py b/homeassistant/components/rainbird/sensor.py index 501566de6823bc..2c542dc12a9af8 100644 --- a/homeassistant/components/rainbird/sensor.py +++ b/homeassistant/components/rainbird/sensor.py @@ -3,7 +3,7 @@ from pyrainbird import RainbirdController -from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from . import ( DATA_RAINBIRD, @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class RainBirdSensor(Entity): +class RainBirdSensor(SensorEntity): """A sensor implementation for Rain Bird device.""" def __init__(self, controller: RainbirdController, sensor_type): diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index b819b51365e49c..efff36770091c9 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class RainCloudSensor(RainCloudEntity): +class RainCloudSensor(RainCloudEntity, SensorEntity): """A sensor implementation for raincloud device.""" @property diff --git a/homeassistant/components/rainforest_eagle/sensor.py b/homeassistant/components/rainforest_eagle/sensor.py index 99751e63f5b231..80475b4c21b6df 100644 --- a/homeassistant/components/rainforest_eagle/sensor.py +++ b/homeassistant/components/rainforest_eagle/sensor.py @@ -7,14 +7,13 @@ from uEagle import Eagle as LegacyReader import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_IP_ADDRESS, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle CONF_CLOUD_ID = "cloud_id" @@ -95,7 +94,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class EagleSensor(Entity): +class EagleSensor(SensorEntity): """Implementation of the Rainforest Eagle-200 sensor.""" def __init__(self, eagle_data, sensor_type, name, unit): @@ -160,7 +159,7 @@ def get_state(self, sensor_type): return state -class LeagleReader(LegacyReader): +class LeagleReader(LegacyReader, SensorEntity): """Wraps uEagle to make it behave like eagle_reader, offering update().""" def update(self): diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index 4533397fb54c73..20912809cb1a44 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -4,6 +4,7 @@ from regenmaschine.controller import Controller +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, VOLUME_CUBIC_METERS from homeassistant.core import HomeAssistant, callback @@ -108,7 +109,7 @@ def async_get_sensor(api_category: str) -> partial: ) -class RainMachineSensor(RainMachineEntity): +class RainMachineSensor(RainMachineEntity, SensorEntity): """Define a general RainMachine sensor.""" def __init__( diff --git a/homeassistant/components/random/sensor.py b/homeassistant/components/random/sensor.py index 7584fe17405660..6465b828be1cfe 100644 --- a/homeassistant/components/random/sensor.py +++ b/homeassistant/components/random/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MAXIMUM, CONF_MINIMUM, @@ -11,7 +11,6 @@ CONF_UNIT_OF_MEASUREMENT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_MAXIMUM = "maximum" ATTR_MINIMUM = "minimum" @@ -42,7 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([RandomSensor(name, minimum, maximum, unit)], True) -class RandomSensor(Entity): +class RandomSensor(SensorEntity): """Representation of a Random number sensor.""" def __init__(self, name, minimum, maximum, unit_of_measurement): diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 000d76b54c721d..1c3dabc2c87ce1 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -6,7 +6,7 @@ from aiorecollect.client import PickupType import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME, CONF_NAME from homeassistant.core import HomeAssistant, callback @@ -77,7 +77,7 @@ async def async_setup_entry( async_add_entities([ReCollectWasteSensor(coordinator, entry)]) -class ReCollectWasteSensor(CoordinatorEntity): +class ReCollectWasteSensor(CoordinatorEntity, SensorEntity): """ReCollect Waste Sensor.""" def __init__(self, coordinator: DataUpdateCoordinator, entry: ConfigEntry) -> None: diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 153b6636cc4d19..a88de916009e0b 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -5,7 +5,7 @@ import praw import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ID, CONF_CLIENT_ID, @@ -15,7 +15,6 @@ CONF_USERNAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -82,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class RedditSensor(Entity): +class RedditSensor(SensorEntity): """Representation of a Reddit sensor.""" def __init__(self, reddit, subreddit: str, limit: int, sort_by: str): diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index ea55d56b8df095..685dc548338db6 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -11,10 +11,9 @@ import rjpl import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -87,7 +86,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ) -class RejseplanenTransportSensor(Entity): +class RejseplanenTransportSensor(SensorEntity): """Implementation of Rejseplanen transport sensor.""" def __init__(self, data, stop_id, route, direction, name): diff --git a/homeassistant/components/repetier/sensor.py b/homeassistant/components/repetier/sensor.py index a2b86792aa76d5..77a3c51e9cf177 100644 --- a/homeassistant/components/repetier/sensor.py +++ b/homeassistant/components/repetier/sensor.py @@ -3,10 +3,10 @@ import logging import time +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import REPETIER_API, SENSOR_TYPES, UPDATE_SIGNAL @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class RepetierSensor(Entity): +class RepetierSensor(SensorEntity): """Class to create and populate a Repetier Sensor.""" def __init__(self, api, temp_id, name, printer_id, sensor_type): diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 5ff5e87c3e65d6..d303f7a57b30f7 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -7,7 +7,11 @@ import voluptuous as vol import xmltodict -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DOMAIN as SENSOR_DOMAIN, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, @@ -81,7 +85,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class RestSensor(RestEntity): +class RestSensor(RestEntity, SensorEntity): """Implementation of a REST sensor.""" def __init__( diff --git a/homeassistant/components/rflink/sensor.py b/homeassistant/components/rflink/sensor.py index 1a616c2ed907c2..497c9b8cee615c 100644 --- a/homeassistant/components/rflink/sensor.py +++ b/homeassistant/components/rflink/sensor.py @@ -2,7 +2,7 @@ from rflink.parser import PACKET_FIELDS, UNITS import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICES, @@ -98,7 +98,7 @@ async def add_new_device(event): hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_SENSOR] = add_new_device -class RflinkSensor(RflinkDevice): +class RflinkSensor(RflinkDevice, SensorEntity): """Representation of a Rflink sensor.""" def __init__( diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index c897e164119667..72cd9f6bbf604f 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -8,6 +8,7 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, + SensorEntity, ) from homeassistant.const import ( CONF_DEVICES, @@ -129,7 +130,7 @@ def sensor_update(event, device_id): connect_auto_add(hass, discovery_info, sensor_update) -class RfxtrxSensor(RfxtrxEntity): +class RfxtrxSensor(RfxtrxEntity, SensorEntity): """Representation of a RFXtrx sensor.""" def __init__(self, device, device_id, data_type, event=None): diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 276d383943880c..a20d484d3fe0d8 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -1,7 +1,7 @@ """This component provides HA sensor support for Ring Door Bell/Chimes.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from . import DOMAIN @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class RingSensor(RingEntityMixin, Entity): +class RingSensor(RingEntityMixin, SensorEntity): """A sensor implementation for Ring device.""" def __init__(self, config_entry_id, device, sensor_type): diff --git a/homeassistant/components/ripple/sensor.py b/homeassistant/components/ripple/sensor.py index 97cf8b4a794f65..f36e2c58ec81ca 100644 --- a/homeassistant/components/ripple/sensor.py +++ b/homeassistant/components/ripple/sensor.py @@ -4,10 +4,9 @@ from pyripple import get_balance import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by ripple.com" @@ -31,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([RippleSensor(name, address)], True) -class RippleSensor(Entity): +class RippleSensor(SensorEntity): """Representation of an Ripple.com sensor.""" def __init__(self, name, address): diff --git a/homeassistant/components/risco/sensor.py b/homeassistant/components/risco/sensor.py index 846444e5fbd966..b39655949b2038 100644 --- a/homeassistant/components/risco/sensor.py +++ b/homeassistant/components/risco/sensor.py @@ -1,5 +1,6 @@ """Sensor for Risco Events.""" from homeassistant.components.binary_sensor import DOMAIN as BS_DOMAIN +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -42,7 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class RiscoSensor(CoordinatorEntity): +class RiscoSensor(CoordinatorEntity, SensorEntity): """Sensor for Risco events.""" def __init__(self, coordinator, category_id, excludes, name, entry_id) -> None: diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index 555f545d0c7c8a..d85dae533032dd 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -10,11 +10,10 @@ ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_TIMEOUT, TIME_MINUTES from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -104,7 +103,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class RMVDepartureSensor(Entity): +class RMVDepartureSensor(SensorEntity): """Implementation of an RMV departure sensor.""" def __init__( diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index f2d08f087720c9..4a99d9f71af188 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -1,4 +1,5 @@ """Sensor for checking the battery level of Roomba.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.components.vacuum import STATE_DOCKED from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.helpers.icon import icon_for_battery_level @@ -16,7 +17,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([roomba_vac], True) -class RoombaBattery(IRobotEntity): +class RoombaBattery(IRobotEntity, SensorEntity): """Class to hold Roomba Sensor basic info.""" @property diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index a2dafae931704c..13f8fffb8d1d5e 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -7,14 +7,13 @@ from rova.rova import Rova import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, DEVICE_CLASS_TIMESTAMP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle # Config for rova requests. @@ -80,7 +79,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class RovaSensor(Entity): +class RovaSensor(SensorEntity): """Representation of a Rova sensor.""" def __init__(self, platform_name, sensor_key, data_service): diff --git a/homeassistant/components/rtorrent/sensor.py b/homeassistant/components/rtorrent/sensor.py index 3976d8985cdbc7..4c02f49d86a331 100644 --- a/homeassistant/components/rtorrent/sensor.py +++ b/homeassistant/components/rtorrent/sensor.py @@ -4,7 +4,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_VARIABLES, CONF_NAME, @@ -14,7 +14,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -75,7 +74,7 @@ def format_speed(speed): return round(kb_spd, 2 if kb_spd < 0.1 else 1) -class RTorrentSensor(Entity): +class RTorrentSensor(SensorEntity): """Representation of an rtorrent sensor.""" def __init__(self, sensor_type, rtorrent_client, client_name): diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index 5e437c41b23dbf..c0930f2c114280 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -1,6 +1,6 @@ """Support for monitoring an SABnzbd NZB client.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import DATA_SABNZBD, SENSOR_TYPES, SIGNAL_SABNZBD_UPDATED @@ -18,7 +18,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SabnzbdSensor(Entity): +class SabnzbdSensor(SensorEntity): """Representation of an SABnzbd sensor.""" def __init__(self, sensor_type, sabnzbd_api_data, client_name): diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 1e7b3dd506107d..ef69513db432f8 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -5,7 +5,7 @@ import pysaj import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,7 +26,6 @@ from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_call_later _LOGGER = logging.getLogger(__name__) @@ -160,7 +159,7 @@ def remove_listener(): return remove_listener -class SAJsensor(Entity): +class SAJsensor(SensorEntity): """Representation of a SAJ sensor.""" def __init__(self, serialnumber, pysaj_sensor, inverter_name=None): diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index e8b6fcfd2c3f21..3bf070a7d792a0 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.rest.data import RestData -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_AUTHENTICATION, CONF_HEADERS, @@ -22,7 +22,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -89,7 +88,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class ScrapeSensor(Entity): +class ScrapeSensor(SensorEntity): """Representation of a web scrape sensor.""" def __init__(self, rest, name, select, attr, index, value_template, unit): diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index c9c66a7568179f..38bde2afd760ea 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -3,7 +3,11 @@ from screenlogicpy.const import DEVICE_TYPE -from homeassistant.components.sensor import DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import ( + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + SensorEntity, +) from . import ScreenlogicEntity from .const import DOMAIN @@ -34,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class ScreenLogicSensor(ScreenlogicEntity): +class ScreenLogicSensor(ScreenlogicEntity, SensorEntity): """Representation of a ScreenLogic sensor entity.""" @property @@ -65,7 +69,7 @@ def sensor(self): return self.coordinator.data["sensors"][self._data_key] -class ScreenLogicPumpSensor(ScreenlogicEntity): +class ScreenLogicPumpSensor(ScreenlogicEntity, SensorEntity): """Representation of a ScreenLogic pump sensor entity.""" def __init__(self, coordinator, pump, key): diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py index e116fb0e8611ee..165920dd8e5b75 100644 --- a/homeassistant/components/season/sensor.py +++ b/homeassistant/components/season/sensor.py @@ -6,10 +6,9 @@ import voluptuous as vol from homeassistant import util -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_TYPE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -109,7 +108,7 @@ def get_season(date, hemisphere, season_tracking_type): return HEMISPHERE_SEASON_SWAP.get(season) -class Season(Entity): +class Season(SensorEntity): """Representation of the current season.""" def __init__(self, hass, hemisphere, season_tracking_type, name): diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 7b6e415d4a31fc..0af64f3f17d713 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring a Sense energy sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, DEVICE_CLASS_POWER, @@ -8,7 +9,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import ( ACTIVE_NAME, @@ -118,7 +118,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices) -class SenseActiveSensor(Entity): +class SenseActiveSensor(SensorEntity): """Implementation of a Sense energy sensor.""" def __init__( @@ -207,7 +207,7 @@ def _async_update_from_data(self): self.async_write_ha_state() -class SenseVoltageSensor(Entity): +class SenseVoltageSensor(SensorEntity): """Implementation of a Sense energy voltage sensor.""" def __init__( @@ -287,7 +287,7 @@ def _async_update_from_data(self): self.async_write_ha_state() -class SenseTrendsSensor(Entity): +class SenseTrendsSensor(SensorEntity): """Implementation of a Sense energy sensor.""" def __init__( @@ -370,7 +370,7 @@ async def async_added_to_hass(self): self.async_on_remove(self._coordinator.async_add_listener(self._async_update)) -class SenseEnergyDevice(Entity): +class SenseEnergyDevice(SensorEntity): """Implementation of a Sense energy device.""" def __init__(self, sense_devices_data, device, sense_monitor_id): diff --git a/homeassistant/components/sensehat/sensor.py b/homeassistant/components/sensehat/sensor.py index 67beb021d899bb..6ba00baae773a8 100644 --- a/homeassistant/components/sensehat/sensor.py +++ b/homeassistant/components/sensehat/sensor.py @@ -6,7 +6,7 @@ from sense_hat import SenseHat import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DISPLAY_OPTIONS, CONF_NAME, @@ -14,7 +14,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class SenseHatSensor(Entity): +class SenseHatSensor(SensorEntity): """Representation of a Sense HAT sensor.""" def __init__(self, data, sensor_types): diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py index 02590ccfe8fdbf..1e73ae9ac8303d 100644 --- a/homeassistant/components/serial/sensor.py +++ b/homeassistant/components/serial/sensor.py @@ -7,11 +7,10 @@ import serial_asyncio import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -103,7 +102,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([sensor], True) -class SerialSensor(Entity): +class SerialSensor(SensorEntity): """Representation of a Serial sensor.""" def __init__( diff --git a/homeassistant/components/serial_pm/sensor.py b/homeassistant/components/serial_pm/sensor.py index 2e7604ee97db1f..b81c60e0a19d29 100644 --- a/homeassistant/components/serial_pm/sensor.py +++ b/homeassistant/components/serial_pm/sensor.py @@ -4,10 +4,9 @@ from pmsensor import serial_pm as pm import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -56,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class ParticulateMatterSensor(Entity): +class ParticulateMatterSensor(SensorEntity): """Representation of an Particulate matter sensor.""" def __init__(self, pmDataCollector, name, pmname): diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 223844dc9c70cb..e856f71b008db7 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -6,7 +6,7 @@ from py17track.errors import SeventeenTrackError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME, @@ -16,7 +16,6 @@ CONF_USERNAME, ) from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_call_later from homeassistant.util import Throttle, slugify @@ -95,7 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= await data.async_update() -class SeventeenTrackSummarySensor(Entity): +class SeventeenTrackSummarySensor(SensorEntity): """Define a summary sensor.""" def __init__(self, data, status, initial_state): @@ -166,7 +165,7 @@ async def async_update(self): self._state = self._data.summary.get(self._status) -class SeventeenTrackPackageSensor(Entity): +class SeventeenTrackPackageSensor(SensorEntity): """Define an individual package sensor.""" def __init__(self, data, package): diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index aac5ec81ec3d3b..ae9e2d740d3772 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -1,5 +1,6 @@ """Sensor for Shelly.""" from homeassistant.components import sensor +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEGREE, @@ -203,7 +204,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class ShellySensor(ShellyBlockAttributeEntity): +class ShellySensor(ShellyBlockAttributeEntity, SensorEntity): """Represent a shelly sensor.""" @property @@ -212,7 +213,7 @@ def state(self): return self.attribute_value -class ShellyRestSensor(ShellyRestAttributeEntity): +class ShellyRestSensor(ShellyRestAttributeEntity, SensorEntity): """Represent a shelly REST sensor.""" @property @@ -221,7 +222,7 @@ def state(self): return self.attribute_value -class ShellySleepingSensor(ShellySleepingBlockAttributeEntity): +class ShellySleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): """Represent a shelly sleeping sensor.""" @property diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py index 397e05a35cadb5..fa0fc2d3906874 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -5,10 +5,9 @@ import shodan import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ShodanSensor(data, name)], True) -class ShodanSensor(Entity): +class ShodanSensor(SensorEntity): """Representation of the Shodan sensor.""" def __init__(self, data, name): diff --git a/homeassistant/components/sht31/sensor.py b/homeassistant/components/sht31/sensor.py index 277039b3ba626a..fd5506ee513f32 100644 --- a/homeassistant/components/sht31/sensor.py +++ b/homeassistant/components/sht31/sensor.py @@ -7,7 +7,7 @@ from Adafruit_SHT31 import SHT31 import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MONITORED_CONDITIONS, CONF_NAME, @@ -16,7 +16,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.temperature import display_temp from homeassistant.util import Throttle @@ -93,7 +92,7 @@ def update(self): self.humidity = humidity -class SHTSensor(Entity): +class SHTSensor(SensorEntity): """An abstract SHTSensor, can be either temperature or humidity.""" def __init__(self, sensor, name): diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 3bf0f084e51452..75c2a4f0f63132 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -7,10 +7,9 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, HTTP_OK, HTTP_UNAUTHORIZED import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -109,7 +108,7 @@ def devices(self): return self._devices -class SigfoxDevice(Entity): +class SigfoxDevice(SensorEntity): """Class for single sigfox device.""" def __init__(self, device_id, auth, name): diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index 7e927ee942e140..9f93a6f9e87a56 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -1,6 +1,7 @@ """Support for SimpliSafe freeze sensor.""" from simplipy.entity import EntityTypes +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT from homeassistant.core import callback @@ -25,7 +26,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class SimplisafeFreezeSensor(SimpliSafeBaseSensor): +class SimplisafeFreezeSensor(SimpliSafeBaseSensor, SensorEntity): """Define a SimpliSafe freeze sensor entity.""" def __init__(self, simplisafe, system, sensor): diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index dc4872cb2aab3d..3fe7aedfbb0940 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -5,10 +5,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util CONF_AMP = "amplitude" @@ -67,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor], True) -class SimulatedSensor(Entity): +class SimulatedSensor(SensorEntity): """Class for simulated sensor.""" def __init__( diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index 3308ec80b8f051..3fdd2e55b0d8b1 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -8,7 +8,7 @@ from pygatt.exceptions import BLEError, NotConnectedError, NotificationTimeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_MAC, CONF_NAME, @@ -18,7 +18,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def monitor_stop(_service_or_event): mon.start() -class SkybeaconHumid(Entity): +class SkybeaconHumid(SensorEntity): """Representation of a Skybeacon humidity sensor.""" def __init__(self, name, mon): @@ -91,7 +90,7 @@ def extra_state_attributes(self): return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} -class SkybeaconTemp(Entity): +class SkybeaconTemp(SensorEntity): """Representation of a Skybeacon temperature sensor.""" def __init__(self, name, mon): @@ -120,7 +119,7 @@ def extra_state_attributes(self): return {ATTR_DEVICE: "SKYBEACON", ATTR_MODEL: 1} -class Monitor(threading.Thread): +class Monitor(threading.Thread, SensorEntity): """Connection handling.""" def __init__(self, hass, mac, name): diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index 09a7400a035c23..8dc13814c678d4 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class SkybellSensor(SkybellDevice): +class SkybellSensor(SkybellDevice, SensorEntity): """A sensor implementation for Skybell devices.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index ae48c059bb88bf..8f5c17dad8941a 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -1,4 +1,6 @@ """Support for SleepIQ sensors.""" +from homeassistant.components.sensor import SensorEntity + from . import SleepIQSensor from .const import DOMAIN, SENSOR_TYPES, SIDES, SLEEP_NUMBER @@ -21,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class SleepNumberSensor(SleepIQSensor): +class SleepNumberSensor(SleepIQSensor, SensorEntity): """Implementation of a SleepIQ sensor.""" def __init__(self, sleepiq_data, bed_id, side): diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index bc4457e2838d1c..2290f3a330f9ff 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -5,7 +5,7 @@ import pysma import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -19,7 +19,6 @@ from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) @@ -169,7 +168,7 @@ async def async_sma(event): async_track_time_interval(hass, async_sma, interval) -class SMAsensor(Entity): +class SMAsensor(SensorEntity): """Representation of a SMA sensor.""" def __init__(self, pysma_sensor, sub_sensors): diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index 41041c1a0023b2..43483dbdb1ecb5 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -1,6 +1,6 @@ """Support for monitoring a Smappee energy sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_WATT_HOUR, POWER_WATT, VOLT -from homeassistant.helpers.entity import Entity from .const import DOMAIN @@ -239,7 +239,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class SmappeeSensor(Entity): +class SmappeeSensor(SensorEntity): """Implementation of a Smappee sensor.""" def __init__(self, smappee_base, service_location, sensor, attributes): diff --git a/homeassistant/components/smart_meter_texas/sensor.py b/homeassistant/components/smart_meter_texas/sensor.py index 42084fff836eef..54e5969f8ea218 100644 --- a/homeassistant/components/smart_meter_texas/sensor.py +++ b/homeassistant/components/smart_meter_texas/sensor.py @@ -1,6 +1,7 @@ """Support for Smart Meter Texas sensors.""" from smart_meter_texas import Meter +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_ADDRESS, ENERGY_KILO_WATT_HOUR from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class SmartMeterTexasSensor(CoordinatorEntity, RestoreEntity): +class SmartMeterTexasSensor(CoordinatorEntity, RestoreEntity, SensorEntity): """Representation of an Smart Meter Texas sensor.""" def __init__(self, meter: Meter, coordinator: DataUpdateCoordinator): diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 4f924786e49cbd..86377e32e23940 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -6,6 +6,7 @@ from pysmartthings import Attribute, Capability +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( AREA_SQUARE_METERS, CONCENTRATION_PARTS_PER_MILLION, @@ -306,7 +307,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: ] -class SmartThingsSensor(SmartThingsEntity): +class SmartThingsSensor(SmartThingsEntity, SensorEntity): """Define a SmartThings Sensor.""" def __init__( @@ -346,7 +347,7 @@ def unit_of_measurement(self): return UNITS.get(unit, unit) if unit else self._default_unit -class SmartThingsThreeAxisSensor(SmartThingsEntity): +class SmartThingsThreeAxisSensor(SmartThingsEntity, SensorEntity): """Define a SmartThings Three Axis Sensor.""" def __init__(self, device, index): diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index 99b2e80262d052..ea803c9862b8a3 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -2,6 +2,8 @@ from enum import Enum import logging +from homeassistant.components.sensor import SensorEntity + from .const import DOMAIN, SMARTTUB_CONTROLLER from .entity import SmartTubSensorBase @@ -44,7 +46,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class SmartTubSensor(SmartTubSensorBase): +class SmartTubSensor(SmartTubSensorBase, SensorEntity): """Generic class for SmartTub status sensors.""" @property diff --git a/homeassistant/components/smarty/sensor.py b/homeassistant/components/smarty/sensor.py index f5cd1fbb404dcc..b958185f9bdec6 100644 --- a/homeassistant/components/smarty/sensor.py +++ b/homeassistant/components/smarty/sensor.py @@ -3,6 +3,7 @@ import datetime as dt import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, @@ -10,7 +11,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util from . import DOMAIN, SIGNAL_UPDATE_SMARTY @@ -35,7 +35,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class SmartySensor(Entity): +class SmartySensor(SensorEntity): """Representation of a Smarty Sensor.""" def __init__( diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index 660b1a70c019a0..fc2310426e31eb 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -3,8 +3,8 @@ import gammu # pylint: disable=import-error +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_SIGNAL_STRENGTH, SIGNAL_STRENGTH_DECIBELS -from homeassistant.helpers.entity import Entity from .const import DOMAIN, SMS_GATEWAY @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class GSMSignalSensor(Entity): +class GSMSignalSensor(SensorEntity): """Implementation of a GSM Signal sensor.""" def __init__( diff --git a/homeassistant/components/snmp/sensor.py b/homeassistant/components/snmp/sensor.py index a60183a1a0fca2..7de2bfb91e2b4c 100644 --- a/homeassistant/components/snmp/sensor.py +++ b/homeassistant/components/snmp/sensor.py @@ -15,7 +15,7 @@ ) import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,7 +26,6 @@ STATE_UNKNOWN, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import ( CONF_ACCEPT_ERRORS, @@ -139,7 +138,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SnmpSensor(data, name, unit, value_template)], True) -class SnmpSensor(Entity): +class SnmpSensor(SensorEntity): """Representation of a SNMP sensor.""" def __init__(self, data, name, unit_of_measurement, value_template): diff --git a/homeassistant/components/sochain/sensor.py b/homeassistant/components/sochain/sensor.py index 5acc8e8432af22..1f735da4995ae8 100644 --- a/homeassistant/components/sochain/sensor.py +++ b/homeassistant/components/sochain/sensor.py @@ -4,11 +4,10 @@ from pysochain import ChainSo import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_ADDRESS, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Data provided by chain.so" @@ -40,7 +39,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SochainSensor(name, network.upper(), chainso)], True) -class SochainSensor(Entity): +class SochainSensor(SensorEntity): """Representation of a Sochain sensor.""" def __init__(self, name, unit_of_measurement, chainso): diff --git a/homeassistant/components/socialblade/sensor.py b/homeassistant/components/socialblade/sensor.py index 3d3331f8af2174..e38c45d10b488f 100644 --- a/homeassistant/components/socialblade/sensor.py +++ b/homeassistant/components/socialblade/sensor.py @@ -5,10 +5,9 @@ import socialbladeclient import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -42,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([social_blade]) -class SocialBladeSensor(Entity): +class SocialBladeSensor(SensorEntity): """Representation of a Social Blade Sensor.""" def __init__(self, case, name): diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 24932618195378..7835fa9aee4030 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -7,10 +7,10 @@ import solaredge from stringcase import snakecase +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_API_KEY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_POWER from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -117,7 +117,7 @@ def create_sensor(self, sensor_key): return sensor_class(self.platform_name, sensor_key, service) -class SolarEdgeSensor(CoordinatorEntity, Entity): +class SolarEdgeSensor(CoordinatorEntity, SensorEntity): """Abstract class for a solaredge sensor.""" def __init__(self, platform_name, sensor_key, data_service): diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 9d5eca149cf4c7..a34fb5a3afc908 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -8,7 +8,7 @@ from solaredge_local import SolarEdge import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, @@ -21,7 +21,6 @@ VOLT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle DOMAIN = "solaredge_local" @@ -231,7 +230,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class SolarEdgeSensor(Entity): +class SolarEdgeSensor(SensorEntity): """Representation of an SolarEdge Monitoring API sensor.""" def __init__(self, platform_name, data, json_key, name, unit, icon, attr): diff --git a/homeassistant/components/solarlog/sensor.py b/homeassistant/components/solarlog/sensor.py index 6073d12815b55b..85a1531090daf6 100644 --- a/homeassistant/components/solarlog/sensor.py +++ b/homeassistant/components/solarlog/sensor.py @@ -5,8 +5,8 @@ from requests.exceptions import HTTPError, Timeout from sunwatcher.solarlog.solarlog import SolarLog +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_HOST -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import DOMAIN, SCAN_INTERVAL, SENSOR_TYPES @@ -55,7 +55,7 @@ async def async_setup_entry(hass, entry, async_add_entities): return True -class SolarlogSensor(Entity): +class SolarlogSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, entry_id, device_name, sensor_key, data): diff --git a/homeassistant/components/solax/sensor.py b/homeassistant/components/solax/sensor.py index bca507c4391cc4..e47f5c578027ee 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -6,11 +6,10 @@ from solax.inverter import InverterError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval DEFAULT_PORT = 80 @@ -73,7 +72,7 @@ async def async_refresh(self, now=None): sensor.async_schedule_update_ha_state() -class Inverter(Entity): +class Inverter(SensorEntity): """Class for a sensor.""" def __init__(self, uid, serial, key, unit): diff --git a/homeassistant/components/soma/sensor.py b/homeassistant/components/soma/sensor.py index 9430a929e1e972..436a92a1087c86 100644 --- a/homeassistant/components/soma/sensor.py +++ b/homeassistant/components/soma/sensor.py @@ -4,8 +4,8 @@ from requests import RequestException +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from . import DEVICES, SomaEntity @@ -26,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class SomaSensor(SomaEntity, Entity): +class SomaSensor(SomaEntity, SensorEntity): """Representation of a Soma cover device.""" @property diff --git a/homeassistant/components/somfy/sensor.py b/homeassistant/components/somfy/sensor.py index 996a95348a4fe0..34283a1271c092 100644 --- a/homeassistant/components/somfy/sensor.py +++ b/homeassistant/components/somfy/sensor.py @@ -3,6 +3,7 @@ from pymfy.api.devices.category import Category from pymfy.api.devices.thermostat import Thermostat +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from . import SomfyEntity @@ -26,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class SomfyThermostatBatterySensor(SomfyEntity): +class SomfyThermostatBatterySensor(SomfyEntity, SensorEntity): """Representation of a Somfy thermostat battery.""" def __init__(self, coordinator, device_id, api): diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 017d9b0f0d00af..3446130433e817 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -7,6 +7,7 @@ from sonarr import Sonarr, SonarrConnectionError, SonarrError +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DATA_GIGABYTES from homeassistant.helpers.entity import Entity @@ -65,7 +66,7 @@ async def handler(self, *args, **kwargs): return handler -class SonarrSensor(SonarrEntity): +class SonarrSensor(SonarrEntity, SensorEntity): """Implementation of the Sonarr sensor.""" def __init__( diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 6f0cb4124fe07c..c49a5691cec953 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -1,4 +1,5 @@ """Support for Speedtest.net internet speed testing sensor.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity @@ -30,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class SpeedtestSensor(CoordinatorEntity, RestoreEntity): +class SpeedtestSensor(CoordinatorEntity, RestoreEntity, SensorEntity): """Implementation of a speedtest.net sensor.""" def __init__(self, coordinator, sensor_type): diff --git a/homeassistant/components/spotcrime/sensor.py b/homeassistant/components/spotcrime/sensor.py index e44bd81ed511b6..72a6fec84e97eb 100644 --- a/homeassistant/components/spotcrime/sensor.py +++ b/homeassistant/components/spotcrime/sensor.py @@ -6,7 +6,7 @@ import spotcrime import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -20,7 +20,6 @@ CONF_RADIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify CONF_DAYS = "days" @@ -66,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class SpotCrimeSensor(Entity): +class SpotCrimeSensor(SensorEntity): """Representation of a Spot Crime Sensor.""" def __init__( diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index ccec918832ebcd..a537b160d0b367 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -8,10 +8,9 @@ import voluptuous as vol from homeassistant.components.recorder import CONF_DB_URL, DEFAULT_DB_FILE, DEFAULT_URL -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(queries, True) -class SQLSensor(Entity): +class SQLSensor(SensorEntity): """Representation of an SQL sensor.""" def __init__(self, name, sessmaker, query, column, unit, value_template): diff --git a/homeassistant/components/srp_energy/sensor.py b/homeassistant/components/srp_energy/sensor.py index cc39bf0b8980b6..6973c58600e18e 100644 --- a/homeassistant/components/srp_energy/sensor.py +++ b/homeassistant/components/srp_energy/sensor.py @@ -5,8 +5,8 @@ import async_timeout from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, ENERGY_KILO_WATT_HOUR -from homeassistant.helpers import entity from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( @@ -71,7 +71,7 @@ async def async_update_data(): async_add_entities([SrpEntity(coordinator)]) -class SrpEntity(entity.Entity): +class SrpEntity(SensorEntity): """Implementation of a Srp Energy Usage sensor.""" def __init__(self, coordinator): diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index a8782a87892ce8..29deacee428600 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -1,5 +1,5 @@ """Reads vehicle status from StarLine API.""" -from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE, SensorEntity from homeassistant.const import ( LENGTH_KILOMETERS, PERCENTAGE, @@ -7,7 +7,6 @@ VOLT, VOLUME_LITERS, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level, icon_for_signal_level from .account import StarlineAccount, StarlineDevice @@ -38,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class StarlineSensor(StarlineEntity, Entity): +class StarlineSensor(StarlineEntity, SensorEntity): """Representation of a StarLine sensor.""" def __init__( diff --git a/homeassistant/components/starlingbank/sensor.py b/homeassistant/components/starlingbank/sensor.py index 20fa646ce41bc1..77f5ab307cb699 100644 --- a/homeassistant/components/starlingbank/sensor.py +++ b/homeassistant/components/starlingbank/sensor.py @@ -5,10 +5,9 @@ from starlingbank import StarlingAccount import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(sensors, True) -class StarlingBalanceSensor(Entity): +class StarlingBalanceSensor(SensorEntity): """Representation of a Starling balance sensor.""" def __init__(self, starling_account, account_name, balance_data_type): diff --git a/homeassistant/components/startca/sensor.py b/homeassistant/components/startca/sensor.py index f6867d832125db..661e00ed494ee8 100644 --- a/homeassistant/components/startca/sensor.py +++ b/homeassistant/components/startca/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol import xmltodict -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_API_KEY, CONF_MONITORED_VARIABLES, @@ -18,7 +18,6 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -75,7 +74,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class StartcaSensor(Entity): +class StartcaSensor(SensorEntity): """Representation of Start.ca Bandwidth sensor.""" def __init__(self, startcadata, sensor_type, name): diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 3bf70060da9587..e32ae0debafee4 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -7,7 +7,7 @@ from homeassistant.components.recorder.models import States from homeassistant.components.recorder.util import execute, session_scope -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_ENTITY_ID, @@ -18,7 +18,6 @@ ) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_state_change_event, @@ -85,7 +84,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class StatisticsSensor(Entity): +class StatisticsSensor(SensorEntity): """Representation of a Statistics sensor.""" def __init__(self, entity_id, name, sampling_size, max_age, precision): diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index e62922c67f4b8d..45ae1a6c70a8d5 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -6,11 +6,10 @@ import steam import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_API_KEY from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_interval from homeassistant.util.dt import utc_from_timestamp @@ -71,7 +70,7 @@ def do_update(time): track_time_interval(hass, do_update, BASE_INTERVAL) -class SteamSensor(Entity): +class SteamSensor(SensorEntity): """A class for the Steam account.""" def __init__(self, account, steamod): diff --git a/homeassistant/components/streamlabswater/sensor.py b/homeassistant/components/streamlabswater/sensor.py index e7168f8ec0b536..ba722d0a4f2843 100644 --- a/homeassistant/components/streamlabswater/sensor.py +++ b/homeassistant/components/streamlabswater/sensor.py @@ -2,9 +2,9 @@ from datetime import timedelta +from homeassistant.components.sensor import SensorEntity from homeassistant.components.streamlabswater import DOMAIN as STREAMLABSWATER_DOMAIN from homeassistant.const import VOLUME_GALLONS -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle DEPENDENCIES = ["streamlabswater"] @@ -67,7 +67,7 @@ def get_yearly_usage(self): return self._this_year -class StreamLabsDailyUsage(Entity): +class StreamLabsDailyUsage(SensorEntity): """Monitors the daily water usage.""" def __init__(self, location_name, streamlabs_usage_data): diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index 594d18028e6b4c..b8362202a3d571 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -1,7 +1,7 @@ """Support for Subaru sensors.""" import subarulink.const as sc -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_TEMPERATURE, @@ -170,7 +170,7 @@ def create_vehicle_sensors(vehicle_info, coordinator): ] -class SubaruSensor(SubaruEntity): +class SubaruSensor(SubaruEntity, SensorEntity): """Class for Subaru sensors.""" def __init__( diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 53f6b3e9c14329..7170e0b8a67392 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -6,10 +6,9 @@ from pysuez.client import PySuezError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, VOLUME_LITERS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) CONF_COUNTER_ID = "counter_id" @@ -47,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SuezSensor(client)], True) -class SuezSensor(Entity): +class SuezSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, client): diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py index 0817f10ed5c33f..f701df2d6c3bb3 100644 --- a/homeassistant/components/supervisord/sensor.py +++ b/homeassistant/components/supervisord/sensor.py @@ -4,10 +4,9 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_URL import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -36,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class SupervisorProcessSensor(Entity): +class SupervisorProcessSensor(SensorEntity): """Representation of a supervisor-monitored process.""" def __init__(self, info, server): diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index 92f90faff2c86a..0a49781767b203 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -6,6 +6,7 @@ from surepy import SureLockStateID, SurepyProduct +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_VOLTAGE, CONF_ID, @@ -15,7 +16,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import SurePetcareAPI from .const import ( @@ -54,7 +54,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities, True) -class SurePetcareSensor(Entity): +class SurePetcareSensor(SensorEntity): """A binary sensor implementation for Sure Petcare Entities.""" def __init__(self, _id: int, sure_type: SurepyProduct, spc: SurePetcareAPI): diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index 27071afd112497..47a8d3e55899d2 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -5,10 +5,9 @@ from swisshydrodata import SwissHydroData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -83,7 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class SwissHydrologicalDataSensor(Entity): +class SwissHydrologicalDataSensor(SensorEntity): """Implementation of a Swiss hydrological sensor.""" def __init__(self, hydro_data, station, condition): diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 1a7b97ce4394cc..a971524c22b2f2 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -6,11 +6,10 @@ from opendata_transport.exceptions import OpendataTransportError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SwissPublicTransportSensor(opendata, start, destination, name)]) -class SwissPublicTransportSensor(Entity): +class SwissPublicTransportSensor(SensorEntity): """Implementation of an Swiss public transport sensor.""" def __init__(self, opendata, start, destination, name): diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index edec503bc2562b..8277bd69467a02 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -5,11 +5,10 @@ from pysyncthru import SYNCTHRU_STATE_HUMAN, SyncThru import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_NAME, CONF_RESOURCE, CONF_URL, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from . import device_identifiers from .const import DEFAULT_MODEL, DEFAULT_NAME_TEMPLATE, DOMAIN @@ -81,7 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices, True) -class SyncThruSensor(Entity): +class SyncThruSensor(SensorEntity): """Implementation of an abstract Samsung Printer sensor platform.""" def __init__(self, syncthru, name): diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 8cfab92975c43f..56eb56df07d277 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -3,6 +3,7 @@ from datetime import timedelta +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DISKS, @@ -86,7 +87,7 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMBaseEntity): +class SynoDSMUtilSensor(SynologyDSMBaseEntity, SensorEntity): """Representation a Synology Utilisation sensor.""" @property @@ -118,7 +119,7 @@ def available(self) -> bool: return bool(self._api.utilisation) -class SynoDSMStorageSensor(SynologyDSMDeviceEntity): +class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SensorEntity): """Representation a Synology Storage sensor.""" @property @@ -139,7 +140,7 @@ def state(self): return attr -class SynoDSMInfoSensor(SynologyDSMBaseEntity): +class SynoDSMInfoSensor(SynologyDSMBaseEntity, SensorEntity): """Representation a Synology information sensor.""" def __init__( diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 805ffcdba5db0a..3e69939cbf32ff 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -7,7 +7,7 @@ import psutil import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_RESOURCES, CONF_TYPE, @@ -20,7 +20,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -182,7 +181,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class SystemMonitorSensor(Entity): +class SystemMonitorSensor(SensorEntity): """Implementation of a system monitor sensor.""" def __init__(self, sensor_type, argument=""): From fdf97eaca34f56ea19c3c73758641233e10056b5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 19:59:03 +0100 Subject: [PATCH 1452/1818] Migrate integrations i-m to extend SensorEntity (#48213) --- homeassistant/components/iammeter/sensor.py | 4 ++-- homeassistant/components/iaqualink/sensor.py | 4 ++-- homeassistant/components/icloud/sensor.py | 4 ++-- homeassistant/components/ihc/sensor.py | 4 ++-- homeassistant/components/imap/sensor.py | 5 ++--- .../components/imap_email_content/sensor.py | 5 ++--- homeassistant/components/incomfort/sensor.py | 4 ++-- homeassistant/components/influxdb/sensor.py | 8 +++++--- homeassistant/components/integration/sensor.py | 4 ++-- homeassistant/components/ios/sensor.py | 4 ++-- homeassistant/components/iota/sensor.py | 5 +++-- homeassistant/components/iperf3/sensor.py | 3 ++- homeassistant/components/ipp/sensor.py | 3 ++- homeassistant/components/iqvia/sensor.py | 5 +++-- .../components/irish_rail_transport/sensor.py | 5 ++--- .../components/islamic_prayer_times/sensor.py | 4 ++-- homeassistant/components/isy994/sensor.py | 6 +++--- .../components/jewish_calendar/sensor.py | 4 ++-- homeassistant/components/juicenet/sensor.py | 4 ++-- homeassistant/components/kaiterra/sensor.py | 4 ++-- homeassistant/components/keba/sensor.py | 4 ++-- homeassistant/components/kira/sensor.py | 4 ++-- homeassistant/components/knx/sensor.py | 4 ++-- homeassistant/components/konnected/sensor.py | 4 ++-- homeassistant/components/kwb/sensor.py | 5 ++--- homeassistant/components/lacrosse/sensor.py | 10 +++++++--- homeassistant/components/lastfm/sensor.py | 5 ++--- homeassistant/components/launch_library/sensor.py | 5 ++--- homeassistant/components/lcn/sensor.py | 6 +++--- homeassistant/components/lightwave/sensor.py | 4 ++-- homeassistant/components/linux_battery/sensor.py | 5 ++--- homeassistant/components/litterrobot/sensor.py | 8 ++++---- homeassistant/components/local_ip/sensor.py | 4 ++-- homeassistant/components/logi_circle/sensor.py | 4 ++-- homeassistant/components/london_air/sensor.py | 5 ++--- .../components/london_underground/sensor.py | 5 ++--- homeassistant/components/loopenergy/sensor.py | 9 ++++----- homeassistant/components/luftdaten/sensor.py | 4 ++-- homeassistant/components/lyft/sensor.py | 5 ++--- homeassistant/components/lyric/sensor.py | 3 ++- homeassistant/components/magicseaweed/sensor.py | 5 ++--- homeassistant/components/mazda/sensor.py | 15 ++++++++------- homeassistant/components/melcloud/sensor.py | 4 ++-- homeassistant/components/meteo_france/sensor.py | 3 ++- homeassistant/components/metoffice/sensor.py | 4 ++-- homeassistant/components/mfi/sensor.py | 5 ++--- homeassistant/components/mhz19/sensor.py | 5 ++--- homeassistant/components/miflora/sensor.py | 5 ++--- homeassistant/components/min_max/sensor.py | 5 ++--- .../components/minecraft_server/sensor.py | 3 ++- homeassistant/components/mitemp_bt/sensor.py | 5 ++--- homeassistant/components/mobile_app/sensor.py | 3 ++- homeassistant/components/modbus/sensor.py | 8 ++++++-- homeassistant/components/modem_callerid/sensor.py | 5 ++--- homeassistant/components/mold_indicator/sensor.py | 5 ++--- homeassistant/components/moon/sensor.py | 5 ++--- homeassistant/components/motion_blinds/sensor.py | 6 +++--- homeassistant/components/mqtt/sensor.py | 5 ++--- homeassistant/components/mqtt_room/sensor.py | 5 ++--- homeassistant/components/mvglive/sensor.py | 5 ++--- homeassistant/components/mychevy/sensor.py | 7 +++---- homeassistant/components/mysensors/sensor.py | 4 ++-- 62 files changed, 152 insertions(+), 157 deletions(-) diff --git a/homeassistant/components/iammeter/sensor.py b/homeassistant/components/iammeter/sensor.py index f885c2c49d3c87..b1882619fdadc0 100644 --- a/homeassistant/components/iammeter/sensor.py +++ b/homeassistant/components/iammeter/sensor.py @@ -8,7 +8,7 @@ from iammeter.power_meter import IamMeterError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import debounce @@ -74,7 +74,7 @@ async def async_update_data(): async_add_entities(entities) -class IamMeter(CoordinatorEntity): +class IamMeter(CoordinatorEntity, SensorEntity): """Class for a sensor.""" def __init__(self, coordinator, uid, sensor_name, unit, dev_name): diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 565cfcca6671a6..eac6e2b785190e 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -1,7 +1,7 @@ """Support for Aqualink temperature sensors.""" from __future__ import annotations -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.typing import HomeAssistantType @@ -22,7 +22,7 @@ async def async_setup_entry( async_add_entities(devs, True) -class HassAqualinkSensor(AqualinkEntity): +class HassAqualinkSensor(AqualinkEntity, SensorEntity): """Representation of a sensor.""" @property diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index e75c9aa3854b29..ddd3d54c556aa9 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -1,11 +1,11 @@ """Support for iCloud sensors.""" from __future__ import annotations +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.typing import HomeAssistantType @@ -48,7 +48,7 @@ def add_entities(account, async_add_entities, tracked): async_add_entities(new_tracked, True) -class IcloudDeviceBatterySensor(Entity): +class IcloudDeviceBatterySensor(SensorEntity): """Representation of a iCloud device battery sensor.""" def __init__(self, account: IcloudAccount, device: IcloudDevice): diff --git a/homeassistant/components/ihc/sensor.py b/homeassistant/components/ihc/sensor.py index cb1688bc7bee8d..3348e857f517be 100644 --- a/homeassistant/components/ihc/sensor.py +++ b/homeassistant/components/ihc/sensor.py @@ -1,6 +1,6 @@ """Support for IHC sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_UNIT_OF_MEASUREMENT -from homeassistant.helpers.entity import Entity from . import IHC_CONTROLLER, IHC_INFO from .ihcdevice import IHCDevice @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class IHCSensor(IHCDevice, Entity): +class IHCSensor(IHCDevice, SensorEntity): """Implementation of the IHC sensor.""" def __init__( diff --git a/homeassistant/components/imap/sensor.py b/homeassistant/components/imap/sensor.py index 4917abc6028ac5..4158d1be801943 100644 --- a/homeassistant/components/imap/sensor.py +++ b/homeassistant/components/imap/sensor.py @@ -6,7 +6,7 @@ import async_timeout import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -16,7 +16,6 @@ ) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -62,7 +61,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([sensor], True) -class ImapSensor(Entity): +class ImapSensor(SensorEntity): """Representation of an IMAP sensor.""" def __init__(self, name, user, password, server, port, charset, folder, search): diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 84dc527ac52fb6..22c395a7c8fad2 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DATE, CONF_NAME, @@ -18,7 +18,6 @@ CONTENT_TYPE_TEXT_PLAIN, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -145,7 +144,7 @@ def read_next(self): return None -class EmailContentSensor(Entity): +class EmailContentSensor(SensorEntity): """Representation of an EMail sensor.""" def __init__(self, hass, email_reader, name, allowed_senders, value_template): diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 8cb07fce14b772..a9e1faaba10223 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -3,7 +3,7 @@ from typing import Any -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class IncomfortSensor(IncomfortChild): +class IncomfortSensor(IncomfortChild, SensorEntity): """Representation of an InComfort/InTouch sensor device.""" def __init__(self, client, heater, name) -> None: diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index cfabd3ee0995ff..299fc595f4b458 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -5,7 +5,10 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_API_VERSION, CONF_NAME, @@ -16,7 +19,6 @@ ) from homeassistant.exceptions import PlatformNotReady, TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from . import create_influx_url, get_influx_connection, validate_version_specific_config @@ -169,7 +171,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda _: influx.close()) -class InfluxSensor(Entity): +class InfluxSensor(SensorEntity): """Implementation of a Influxdb sensor.""" def __init__(self, hass, influx, query): diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 3be60dcd7f732d..0ab4ac0d2c49fd 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -4,7 +4,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_METHOD, @@ -83,7 +83,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([integral]) -class IntegrationSensor(RestoreEntity): +class IntegrationSensor(RestoreEntity, SensorEntity): """Representation of an integration sensor.""" def __init__( diff --git a/homeassistant/components/ios/sensor.py b/homeassistant/components/ios/sensor.py index a8cffacabe9aa6..c1442f0de9fa3e 100644 --- a/homeassistant/components/ios/sensor.py +++ b/homeassistant/components/ios/sensor.py @@ -1,9 +1,9 @@ """Support for Home Assistant iOS app sensors.""" from homeassistant.components import ios +from homeassistant.components.sensor import SensorEntity from homeassistant.const import PERCENTAGE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from .const import DOMAIN @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class IOSSensor(Entity): +class IOSSensor(SensorEntity): """Representation of an iOS sensor.""" def __init__(self, sensor_type, device_name, device): diff --git a/homeassistant/components/iota/sensor.py b/homeassistant/components/iota/sensor.py index 751324d61b1c0a..62260be241013e 100644 --- a/homeassistant/components/iota/sensor.py +++ b/homeassistant/components/iota/sensor.py @@ -1,6 +1,7 @@ """Support for IOTA wallet sensors.""" from datetime import timedelta +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME from . import CONF_WALLETS, IotaDevice @@ -27,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class IotaBalanceSensor(IotaDevice): +class IotaBalanceSensor(IotaDevice, SensorEntity): """Implement an IOTA sensor for displaying wallets balance.""" def __init__(self, wallet_config, iota_config): @@ -60,7 +61,7 @@ def update(self): self._state = self.api.get_inputs()["totalBalance"] -class IotaNodeSensor(IotaDevice): +class IotaNodeSensor(IotaDevice, SensorEntity): """Implement an IOTA sensor for displaying attributes of node.""" def __init__(self, iota_config): diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py index a8a790088171c0..610ff91250f591 100644 --- a/homeassistant/components/iperf3/sensor.py +++ b/homeassistant/components/iperf3/sensor.py @@ -1,4 +1,5 @@ """Support for Iperf3 sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -23,7 +24,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info) async_add_entities(sensors, True) -class Iperf3Sensor(RestoreEntity): +class Iperf3Sensor(RestoreEntity, SensorEntity): """A Iperf3 sensor implementation.""" def __init__(self, iperf3_data, sensor_type): diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 1ff98df7a5fb2b..83826409ed8d4f 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -4,6 +4,7 @@ from datetime import timedelta from typing import Any, Callable +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_LOCATION, DEVICE_CLASS_TIMESTAMP, PERCENTAGE from homeassistant.helpers.entity import Entity @@ -52,7 +53,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class IPPSensor(IPPEntity): +class IPPSensor(IPPEntity, SensorEntity): """Defines an IPP sensor.""" def __init__( diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index 48ec1cf97b1a62..169ff405d6df0a 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -3,6 +3,7 @@ import numpy as np +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_STATE from homeassistant.core import callback @@ -98,7 +99,7 @@ def calculate_trend(indices): return TREND_FLAT -class ForecastSensor(IQVIAEntity): +class ForecastSensor(IQVIAEntity, SensorEntity): """Define sensor related to forecast data.""" @callback @@ -137,7 +138,7 @@ def update_from_latest_data(self): self._state = average -class IndexSensor(IQVIAEntity): +class IndexSensor(IQVIAEntity, SensorEntity): """Define sensor related to indices.""" @callback diff --git a/homeassistant/components/irish_rail_transport/sensor.py b/homeassistant/components/irish_rail_transport/sensor.py index bd277020125ae4..b5ba16f8541f0c 100644 --- a/homeassistant/components/irish_rail_transport/sensor.py +++ b/homeassistant/components/irish_rail_transport/sensor.py @@ -4,10 +4,9 @@ from pyirishrail.pyirishrail import IrishRailRTPI import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTR_STATION = "Station" ATTR_ORIGIN = "Origin" @@ -64,7 +63,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class IrishRailTransportSensor(Entity): +class IrishRailTransportSensor(SensorEntity): """Implementation of an irish rail public transport sensor.""" def __init__(self, data, station, direction, destination, stops_at, name): diff --git a/homeassistant/components/islamic_prayer_times/sensor.py b/homeassistant/components/islamic_prayer_times/sensor.py index 41b3cf4c667871..3133320d978056 100644 --- a/homeassistant/components/islamic_prayer_times/sensor.py +++ b/homeassistant/components/islamic_prayer_times/sensor.py @@ -1,8 +1,8 @@ """Platform to retrieve Islamic prayer times information for Home Assistant.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util from .const import DATA_UPDATED, DOMAIN, PRAYER_TIMES_ICON, SENSOR_TYPES @@ -20,7 +20,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class IslamicPrayerTimeSensor(Entity): +class IslamicPrayerTimeSensor(SensorEntity): """Representation of an Islamic prayer time sensor.""" def __init__(self, sensor_type, client): diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 4d3c4c72763755..2927fbb62b1596 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -5,7 +5,7 @@ from pyisy.constants import ISY_VALUE_UNKNOWN -from homeassistant.components.sensor import DOMAIN as SENSOR +from homeassistant.components.sensor import DOMAIN as SENSOR, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.typing import HomeAssistantType @@ -45,7 +45,7 @@ async def async_setup_entry( async_add_entities(devices) -class ISYSensorEntity(ISYNodeEntity): +class ISYSensorEntity(ISYNodeEntity, SensorEntity): """Representation of an ISY994 sensor device.""" @property @@ -105,7 +105,7 @@ def unit_of_measurement(self) -> str: return raw_units -class ISYSensorVariableEntity(ISYEntity): +class ISYSensorVariableEntity(ISYEntity, SensorEntity): """Representation of an ISY994 variable as a sensor device.""" def __init__(self, vname: str, vobj: object) -> None: diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index 14336b1f935b20..5690cd35a03140 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -3,8 +3,8 @@ import hdate +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP, SUN_EVENT_SUNSET -from homeassistant.helpers.entity import Entity from homeassistant.helpers.sun import get_astral_event_date import homeassistant.util.dt as dt_util @@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class JewishCalendarSensor(Entity): +class JewishCalendarSensor(SensorEntity): """Representation of an Jewish calendar sensor.""" def __init__(self, data, sensor, sensor_info): diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py index 10008f30e7c49b..d908dc069efb44 100644 --- a/homeassistant/components/juicenet/sensor.py +++ b/homeassistant/components/juicenet/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ELECTRICAL_CURRENT_AMPERE, ENERGY_WATT_HOUR, @@ -7,7 +8,6 @@ TIME_SECONDS, VOLT, ) -from homeassistant.helpers.entity import Entity from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR from .entity import JuiceNetDevice @@ -36,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class JuiceNetSensorDevice(JuiceNetDevice, Entity): +class JuiceNetSensorDevice(JuiceNetDevice, SensorEntity): """Implementation of a JuiceNet sensor.""" def __init__(self, device, sensor_type, coordinator): diff --git a/homeassistant/components/kaiterra/sensor.py b/homeassistant/components/kaiterra/sensor.py index d9500c7a00064d..1e4dd0cbbca2b6 100644 --- a/homeassistant/components/kaiterra/sensor.py +++ b/homeassistant/components/kaiterra/sensor.py @@ -1,7 +1,7 @@ """Support for Kaiterra Temperature ahn Humidity Sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_DEVICE_ID, CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DISPATCHER_KAITERRA, DOMAIN @@ -25,7 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class KaiterraSensor(Entity): +class KaiterraSensor(SensorEntity): """Implementation of a Kaittera sensor.""" def __init__(self, api, name, device_id, sensor): diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index ed85ccd06a62a5..836785490e8df0 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -1,10 +1,10 @@ """Support for KEBA charging station sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_POWER, ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, ) -from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -62,7 +62,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class KebaSensor(Entity): +class KebaSensor(SensorEntity): """The entity class for KEBA charging stations sensors.""" def __init__(self, keba, key, name, entity_type, icon, unit, device_class=None): diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index a8c49d1c04b7ba..a6b1b9ada220fe 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -1,8 +1,8 @@ """KIRA interface to receive UDP packets from an IR-IP bridge.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_DEVICE, CONF_NAME, STATE_UNKNOWN -from homeassistant.helpers.entity import Entity from . import CONF_SENSOR, DOMAIN @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([KiraReceiver(device, kira)]) -class KiraReceiver(Entity): +class KiraReceiver(SensorEntity): """Implementation of a Kira Receiver.""" def __init__(self, name, kira): diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index f3155eebf015b3..4397f8bcc0b3cd 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -5,7 +5,7 @@ from xknx.devices import Sensor as XknxSensor -from homeassistant.components.sensor import DEVICE_CLASSES +from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ( ConfigType, @@ -32,7 +32,7 @@ async def async_setup_platform( async_add_entities(entities) -class KNXSensor(KnxEntity, Entity): +class KNXSensor(KnxEntity, SensorEntity): """Representation of a KNX sensor.""" def __init__(self, device: XknxSensor) -> None: diff --git a/homeassistant/components/konnected/sensor.py b/homeassistant/components/konnected/sensor.py index dece8d06c87097..18975bdb4679cb 100644 --- a/homeassistant/components/konnected/sensor.py +++ b/homeassistant/components/konnected/sensor.py @@ -1,4 +1,5 @@ """Support for DHT and DS18B20 sensors attached to a Konnected device.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_DEVICES, CONF_NAME, @@ -12,7 +13,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from .const import DOMAIN as KONNECTED_DOMAIN, SIGNAL_DS18B20_NEW @@ -70,7 +70,7 @@ def async_add_ds18b20(attrs): async_dispatcher_connect(hass, SIGNAL_DS18B20_NEW, async_add_ds18b20) -class KonnectedSensor(Entity): +class KonnectedSensor(SensorEntity): """Represents a Konnected DHT Sensor.""" def __init__(self, device_id, data, sensor_type, addr=None, initial_state=None): diff --git a/homeassistant/components/kwb/sensor.py b/homeassistant/components/kwb/sensor.py index bd0430f9786d40..eb96b20665342b 100644 --- a/homeassistant/components/kwb/sensor.py +++ b/homeassistant/components/kwb/sensor.py @@ -2,7 +2,7 @@ from pykwb import kwb import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_HOST, @@ -11,7 +11,6 @@ EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity DEFAULT_RAW = False DEFAULT_NAME = "KWB" @@ -74,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class KWBSensor(Entity): +class KWBSensor(SensorEntity): """Representation of a KWB Easyfire sensor.""" def __init__(self, easyfire, sensor, client_name): diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index 35cdcecddba8b7..32090797f1103f 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -6,7 +6,11 @@ from serial import SerialException import voluptuous as vol -from homeassistant.components.sensor import ENTITY_ID_FORMAT, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_DEVICE, CONF_ID, @@ -19,7 +23,7 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util @@ -108,7 +112,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class LaCrosseSensor(Entity): +class LaCrosseSensor(SensorEntity): """Implementation of a Lacrosse sensor.""" _temperature = None diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index 2f599ad37fd1d5..128450826d6fef 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -7,10 +7,9 @@ from pylast import WSError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -52,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class LastfmSensor(Entity): +class LastfmSensor(SensorEntity): """A class for the Last.fm account.""" def __init__(self, user, lastfm_api): diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 7663bf86fc51d9..831e44dca8f978 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -7,11 +7,10 @@ from pylaunches import PyLaunches, PyLaunchesException import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from .const import ( ATTR_AGENCY, @@ -40,7 +39,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([LaunchLibrarySensor(launches, name)], True) -class LaunchLibrarySensor(Entity): +class LaunchLibrarySensor(SensorEntity): """Representation of a launch_library Sensor.""" def __init__(self, launches: PyLaunches, name: str) -> None: diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 510df46cd1eb91..64870e22e4c4f8 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -2,7 +2,7 @@ import pypck -from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR +from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR, SensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DOMAIN, @@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class LcnVariableSensor(LcnEntity): +class LcnVariableSensor(LcnEntity, SensorEntity): """Representation of a LCN sensor for variables.""" def __init__(self, config, entry_id, device_connection): @@ -99,7 +99,7 @@ def input_received(self, input_obj): self.async_write_ha_state() -class LcnLedLogicSensor(LcnEntity): +class LcnLedLogicSensor(LcnEntity, SensorEntity): """Representation of a LCN sensor for leds and logicops.""" def __init__(self, config, entry_id, device_connection): diff --git a/homeassistant/components/lightwave/sensor.py b/homeassistant/components/lightwave/sensor.py index 2144979106a3bf..1128078f8bc400 100644 --- a/homeassistant/components/lightwave/sensor.py +++ b/homeassistant/components/lightwave/sensor.py @@ -1,6 +1,6 @@ """Support for LightwaveRF TRV - Associated Battery.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, DEVICE_CLASS_BATTERY, PERCENTAGE -from homeassistant.helpers.entity import Entity from . import CONF_SERIAL, LIGHTWAVE_LINK @@ -22,7 +22,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(batteries) -class LightwaveBattery(Entity): +class LightwaveBattery(SensorEntity): """Lightwave TRV Battery.""" def __init__(self, name, lwlink, serial): diff --git a/homeassistant/components/linux_battery/sensor.py b/homeassistant/components/linux_battery/sensor.py index feefa34a7a718b..b7746392ceecf2 100644 --- a/homeassistant/components/linux_battery/sensor.py +++ b/homeassistant/components/linux_battery/sensor.py @@ -5,10 +5,9 @@ from batinfo import Batteries import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_NAME, CONF_NAME, DEVICE_CLASS_BATTERY, PERCENTAGE import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([LinuxBatterySensor(name, battery_id, system)], True) -class LinuxBatterySensor(Entity): +class LinuxBatterySensor(SensorEntity): """Representation of a Linux Battery sensor.""" def __init__(self, name, battery_id, system): diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index 8ae512fa801f47..8038fdbb2cbc64 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -3,8 +3,8 @@ from pylitterbot.robot import Robot +from homeassistant.components.sensor import SensorEntity from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE -from homeassistant.helpers.entity import Entity from .const import DOMAIN from .hub import LitterRobotEntity, LitterRobotHub @@ -21,7 +21,7 @@ def icon_for_gauge_level(gauge_level: int | None = None, offset: int = 0) -> str return "mdi:gauge-low" -class LitterRobotPropertySensor(LitterRobotEntity, Entity): +class LitterRobotPropertySensor(LitterRobotEntity, SensorEntity): """Litter-Robot property sensors.""" def __init__( @@ -37,7 +37,7 @@ def state(self): return getattr(self.robot, self.sensor_attribute) -class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity): +class LitterRobotWasteSensor(LitterRobotPropertySensor): """Litter-Robot sensors.""" @property @@ -51,7 +51,7 @@ def icon(self): return icon_for_gauge_level(self.state, 10) -class LitterRobotSleepTimeSensor(LitterRobotPropertySensor, Entity): +class LitterRobotSleepTimeSensor(LitterRobotPropertySensor): """Litter-Robot sleep time sensors.""" @property diff --git a/homeassistant/components/local_ip/sensor.py b/homeassistant/components/local_ip/sensor.py index d159b641fa2939..1d2cce721052a4 100644 --- a/homeassistant/components/local_ip/sensor.py +++ b/homeassistant/components/local_ip/sensor.py @@ -1,7 +1,7 @@ """Sensor platform for local_ip.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME -from homeassistant.helpers.entity import Entity from homeassistant.util import get_local_ip from .const import DOMAIN, SENSOR @@ -13,7 +13,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([IPSensor(name)], True) -class IPSensor(Entity): +class IPSensor(SensorEntity): """A simple sensor.""" def __init__(self, name): diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index 3d980b1dae390a..29cd6e28e1c7d4 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -1,6 +1,7 @@ """Support for Logi Circle sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, @@ -9,7 +10,6 @@ STATE_OFF, STATE_ON, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util.dt import as_local @@ -42,7 +42,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class LogiSensor(Entity): +class LogiSensor(SensorEntity): """A sensor implementation for a Logi Circle camera.""" def __init__(self, camera, time_zone, sensor_type): diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index c39d77585a8325..23eea5c00e0b98 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -5,10 +5,9 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import HTTP_OK import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -90,7 +89,7 @@ def update(self): self.data = parse_api_response(response.json()) -class AirSensor(Entity): +class AirSensor(SensorEntity): """Single authority air sensor.""" ICON = "mdi:cloud-outline" diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py index acb605901c90e8..b7f2cb50cbfbe6 100644 --- a/homeassistant/components/london_underground/sensor.py +++ b/homeassistant/components/london_underground/sensor.py @@ -4,10 +4,9 @@ from london_tube_status import TubeData import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity ATTRIBUTION = "Powered by TfL Open Data" @@ -51,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class LondonTubeSensor(Entity): +class LondonTubeSensor(SensorEntity): """Sensor that reads the status of a line from Tube Data.""" def __init__(self, name, data): diff --git a/homeassistant/components/loopenergy/sensor.py b/homeassistant/components/loopenergy/sensor.py index 85494d354b50ad..78e55f22eb8e92 100644 --- a/homeassistant/components/loopenergy/sensor.py +++ b/homeassistant/components/loopenergy/sensor.py @@ -4,14 +4,13 @@ import pyloopenergy import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -82,7 +81,7 @@ def stop_loopenergy(event): add_entities(sensors) -class LoopEnergyDevice(Entity): +class LoopEnergySensor(SensorEntity): """Implementation of an Loop Energy base sensor.""" def __init__(self, controller): @@ -116,7 +115,7 @@ def _callback(self): self.schedule_update_ha_state(True) -class LoopEnergyElec(LoopEnergyDevice): +class LoopEnergyElec(LoopEnergySensor): """Implementation of an Loop Energy Electricity sensor.""" def __init__(self, controller): @@ -133,7 +132,7 @@ def update(self): self._state = round(self._controller.electricity_useage, 2) -class LoopEnergyGas(LoopEnergyDevice): +class LoopEnergyGas(LoopEnergySensor): """Implementation of an Loop Energy Gas sensor.""" def __init__(self, controller): diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index 5985c63801ec7d..aec77961b94dbc 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -1,6 +1,7 @@ """Support for Luftdaten sensors.""" import logging +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_LATITUDE, @@ -9,7 +10,6 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from . import ( DATA_LUFTDATEN, @@ -45,7 +45,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, True) -class LuftdatenSensor(Entity): +class LuftdatenSensor(SensorEntity): """Implementation of a Luftdaten sensor.""" def __init__(self, luftdaten, sensor_type, name, icon, unit, show): diff --git a/homeassistant/components/lyft/sensor.py b/homeassistant/components/lyft/sensor.py index 872281f685cc12..c979231a216ce6 100644 --- a/homeassistant/components/lyft/sensor.py +++ b/homeassistant/components/lyft/sensor.py @@ -7,10 +7,9 @@ from lyft_rides.errors import APIError import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -74,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class LyftSensor(Entity): +class LyftSensor(SensorEntity): """Implementation of an Lyft sensor.""" def __init__(self, sensorType, products, product_id, product): diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index c4950f119d9068..db90f474124dc7 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -4,6 +4,7 @@ from aiolyric.objects.device import LyricDevice from aiolyric.objects.location import LyricLocation +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -67,7 +68,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class LyricSensor(LyricDeviceEntity): +class LyricSensor(LyricDeviceEntity, SensorEntity): """Defines a Honeywell Lyric sensor.""" def __init__( diff --git a/homeassistant/components/magicseaweed/sensor.py b/homeassistant/components/magicseaweed/sensor.py index 406f5e5395538a..0dd27a60ae065c 100644 --- a/homeassistant/components/magicseaweed/sensor.py +++ b/homeassistant/components/magicseaweed/sensor.py @@ -5,7 +5,7 @@ import magicseaweed import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -13,7 +13,6 @@ CONF_NAME, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -90,7 +89,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class MagicSeaweedSensor(Entity): +class MagicSeaweedSensor(SensorEntity): """Implementation of a MagicSeaweed sensor.""" def __init__(self, forecast_data, sensor_type, name, unit_system, hour=None): diff --git a/homeassistant/components/mazda/sensor.py b/homeassistant/components/mazda/sensor.py index a05291673e8d9d..7382347e6de5a9 100644 --- a/homeassistant/components/mazda/sensor.py +++ b/homeassistant/components/mazda/sensor.py @@ -1,4 +1,5 @@ """Platform for Mazda sensor integration.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, LENGTH_KILOMETERS, @@ -29,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class MazdaFuelRemainingSensor(MazdaEntity): +class MazdaFuelRemainingSensor(MazdaEntity, SensorEntity): """Class for the fuel remaining sensor.""" @property @@ -59,7 +60,7 @@ def state(self): return self.coordinator.data[self.index]["status"]["fuelRemainingPercent"] -class MazdaFuelDistanceSensor(MazdaEntity): +class MazdaFuelDistanceSensor(MazdaEntity, SensorEntity): """Class for the fuel distance sensor.""" @property @@ -100,7 +101,7 @@ def state(self): ) -class MazdaOdometerSensor(MazdaEntity): +class MazdaOdometerSensor(MazdaEntity, SensorEntity): """Class for the odometer sensor.""" @property @@ -137,7 +138,7 @@ def state(self): ) -class MazdaFrontLeftTirePressureSensor(MazdaEntity): +class MazdaFrontLeftTirePressureSensor(MazdaEntity, SensorEntity): """Class for the front left tire pressure sensor.""" @property @@ -170,7 +171,7 @@ def state(self): return None if tire_pressure is None else round(tire_pressure) -class MazdaFrontRightTirePressureSensor(MazdaEntity): +class MazdaFrontRightTirePressureSensor(MazdaEntity, SensorEntity): """Class for the front right tire pressure sensor.""" @property @@ -203,7 +204,7 @@ def state(self): return None if tire_pressure is None else round(tire_pressure) -class MazdaRearLeftTirePressureSensor(MazdaEntity): +class MazdaRearLeftTirePressureSensor(MazdaEntity, SensorEntity): """Class for the rear left tire pressure sensor.""" @property @@ -236,7 +237,7 @@ def state(self): return None if tire_pressure is None else round(tire_pressure) -class MazdaRearRightTirePressureSensor(MazdaEntity): +class MazdaRearRightTirePressureSensor(MazdaEntity, SensorEntity): """Class for the rear right tire pressure sensor.""" @property diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index bd85d1b13bc54f..356992ece11ae3 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -2,6 +2,7 @@ from pymelcloud import DEVICE_TYPE_ATA, DEVICE_TYPE_ATW from pymelcloud.atw_device import Zone +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ICON, @@ -9,7 +10,6 @@ ENERGY_KILO_WATT_HOUR, TEMP_CELSIUS, ) -from homeassistant.helpers.entity import Entity from . import MelCloudDevice from .const import DOMAIN @@ -110,7 +110,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class MelDeviceSensor(Entity): +class MelDeviceSensor(SensorEntity): """Representation of a Sensor.""" def __init__(self, api: MelCloudDevice, measurement, definition): diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 74b4ab5a6d1e75..b6ec221a97e92a 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -6,6 +6,7 @@ readeable_phenomenoms_dict, ) +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.typing import HomeAssistantType @@ -74,7 +75,7 @@ async def async_setup_entry( ) -class MeteoFranceSensor(CoordinatorEntity): +class MeteoFranceSensor(CoordinatorEntity, SensorEntity): """Representation of a Meteo-France sensor.""" def __init__(self, sensor_type: str, coordinator: DataUpdateCoordinator): diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py index 2f4dd72b3a1317..6a7cf5254a63e9 100644 --- a/homeassistant/components/metoffice/sensor.py +++ b/homeassistant/components/metoffice/sensor.py @@ -1,4 +1,5 @@ """Support for UK Met Office weather service.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, DEVICE_CLASS_HUMIDITY, @@ -10,7 +11,6 @@ UV_INDEX, ) from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( @@ -92,7 +92,7 @@ async def async_setup_entry( ) -class MetOfficeCurrentSensor(Entity): +class MetOfficeCurrentSensor(SensorEntity): """Implementation of a Met Office current weather condition sensor.""" def __init__(self, entry_data, hass_data, sensor_type): diff --git a/homeassistant/components/mfi/sensor.py b/homeassistant/components/mfi/sensor.py index 671a52bbf01635..c7a64f17bd68a4 100644 --- a/homeassistant/components/mfi/sensor.py +++ b/homeassistant/components/mfi/sensor.py @@ -5,7 +5,7 @@ import requests import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -18,7 +18,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -74,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class MfiSensor(Entity): +class MfiSensor(SensorEntity): """Representation of a mFi sensor.""" def __init__(self, port, hass): diff --git a/homeassistant/components/mhz19/sensor.py b/homeassistant/components/mhz19/sensor.py index f26481f9d25d1b..0f0735dd5dadb6 100644 --- a/homeassistant/components/mhz19/sensor.py +++ b/homeassistant/components/mhz19/sensor.py @@ -5,7 +5,7 @@ from pmsensor import co2sensor import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_TEMPERATURE, CONCENTRATION_PARTS_PER_MILLION, @@ -14,7 +14,6 @@ TEMP_FAHRENHEIT, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celsius_to_fahrenheit @@ -69,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class MHZ19Sensor(Entity): +class MHZ19Sensor(SensorEntity): """Representation of an CO2 sensor.""" def __init__(self, mhz_client, sensor_type, temp_unit, name): diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index 8db81ecf5dd392..0c22a943bb35b4 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -8,7 +8,7 @@ from miflora import miflora_poller import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONDUCTIVITY, CONF_FORCE_UPDATE, @@ -27,7 +27,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util from homeassistant.util.temperature import celsius_to_fahrenheit @@ -130,7 +129,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devs) -class MiFloraSensor(Entity): +class MiFloraSensor(SensorEntity): """Implementing the MiFlora sensor.""" def __init__( diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index 5bea9379690307..a42291b3f67621 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -13,7 +13,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.reload import async_setup_reload_service @@ -131,7 +130,7 @@ def calc_median(sensor_values, round_digits): return round(median, round_digits) -class MinMaxSensor(Entity): +class MinMaxSensor(SensorEntity): """Representation of a min/max sensor.""" def __init__(self, entity_ids, name, sensor_type, round_digits): diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 4cc9e9746bbc48..ded21f2935fb5a 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -3,6 +3,7 @@ from typing import Any +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import TIME_MILLISECONDS from homeassistant.helpers.typing import HomeAssistantType @@ -47,7 +48,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class MinecraftServerSensorEntity(MinecraftServerEntity): +class MinecraftServerSensorEntity(MinecraftServerEntity, SensorEntity): """Representation of a Minecraft Server sensor base entity.""" def __init__( diff --git a/homeassistant/components/mitemp_bt/sensor.py b/homeassistant/components/mitemp_bt/sensor.py index 244a0c410d57c5..670a6daf3d35ce 100644 --- a/homeassistant/components/mitemp_bt/sensor.py +++ b/homeassistant/components/mitemp_bt/sensor.py @@ -6,7 +6,7 @@ from mitemp_bt import mitemp_bt_poller import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_FORCE_UPDATE, CONF_MAC, @@ -20,7 +20,6 @@ TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity try: import bluepy.btle # noqa: F401 pylint: disable=unused-import @@ -104,7 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class MiTempBtSensor(Entity): +class MiTempBtSensor(SensorEntity): """Implementing the MiTempBt sensor.""" def __init__(self, poller, parameter, device, name, unit, force_update, median): diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index b09ef86453b139..3f4c7d56f3f5fd 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -1,6 +1,7 @@ """Sensor platform for mobile_app.""" from functools import partial +from homeassistant.components.sensor import SensorEntity from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.helpers import entity_registry as er @@ -71,7 +72,7 @@ def handle_sensor_registration(webhook_id, data): ) -class MobileAppSensor(MobileAppEntity): +class MobileAppSensor(MobileAppEntity, SensorEntity): """Representation of an mobile app sensor.""" @property diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index d0fad973802164..c1f5487bd5d30f 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -9,7 +9,11 @@ from pymodbus.pdu import ExceptionResponse import voluptuous as vol -from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA +from homeassistant.components.sensor import ( + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + SensorEntity, +) from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_NAME, @@ -158,7 +162,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ModbusRegisterSensor(RestoreEntity): +class ModbusRegisterSensor(RestoreEntity, SensorEntity): """Modbus register sensor.""" def __init__( diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index f91b6d7a169354..080e077a45759f 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -4,7 +4,7 @@ from basicmodem.basicmodem import BasicModem as bm import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE, CONF_NAME, @@ -12,7 +12,6 @@ STATE_IDLE, ) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Modem CallerID" @@ -44,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ModemCalleridSensor(hass, name, port, modem)]) -class ModemCalleridSensor(Entity): +class ModemCalleridSensor(SensorEntity): """Implementation of USB modem caller ID sensor.""" def __init__(self, hass, name, port, modem): diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index 126b57cb412254..7bfa161f9eccd8 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant import util -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, @@ -17,7 +17,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change_event _LOGGER = logging.getLogger(__name__) @@ -69,7 +68,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MoldIndicator(Entity): +class MoldIndicator(SensorEntity): """Represents a MoldIndication sensor.""" def __init__( diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py index 9e0f8ef51d6326..4b373469cc6df9 100644 --- a/homeassistant/components/moon/sensor.py +++ b/homeassistant/components/moon/sensor.py @@ -2,10 +2,9 @@ from astral import Astral import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity import homeassistant.util.dt as dt_util DEFAULT_NAME = "Moon" @@ -42,7 +41,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([MoonSensor(name)], True) -class MoonSensor(Entity): +class MoonSensor(SensorEntity): """Representation of a Moon sensor.""" def __init__(self, name): diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index 9f80c85b959a59..5d7a29bf9da780 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -1,13 +1,13 @@ """Support for Motion Blinds sensors.""" from motionblinds import BlindType +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_SIGNAL_STRENGTH, PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, KEY_COORDINATOR, KEY_GATEWAY @@ -39,7 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class MotionBatterySensor(CoordinatorEntity, Entity): +class MotionBatterySensor(CoordinatorEntity, SensorEntity): """ Representation of a Motion Battery Sensor. @@ -144,7 +144,7 @@ def extra_state_attributes(self): return attributes -class MotionSignalStrengthSensor(CoordinatorEntity, Entity): +class MotionSignalStrengthSensor(CoordinatorEntity, SensorEntity): """Representation of a Motion Signal Strength Sensor.""" def __init__(self, coordinator, device, device_type): diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index b20595922cda7a..65c9e0550e0ba2 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components import sensor -from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA +from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA, SensorEntity from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, @@ -17,7 +17,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -71,7 +70,7 @@ async def _async_setup_entity( async_add_entities([MqttSensor(hass, config, config_entry, discovery_data)]) -class MqttSensor(MqttEntity, Entity): +class MqttSensor(MqttEntity, SensorEntity): """Representation of a sensor that can be updated using MQTT.""" def __init__(self, hass, config, config_entry, discovery_data): diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index e15dfd179dab28..e446ab8ba7a096 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -7,7 +7,7 @@ from homeassistant.components import mqtt from homeassistant.components.mqtt import CONF_STATE_TOPIC -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ID, @@ -18,7 +18,6 @@ ) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.util import dt, slugify _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MQTTRoomSensor(Entity): +class MQTTRoomSensor(SensorEntity): """Representation of a room sensor that is updated via MQTT.""" def __init__(self, name, state_topic, device_id, timeout, consider_home): diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py index 15b0d2d218e70c..16b061a3346cd7 100644 --- a/homeassistant/components/mvglive/sensor.py +++ b/homeassistant/components/mvglive/sensor.py @@ -6,10 +6,9 @@ import MVGLive import voluptuous as vol -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, TIME_MINUTES import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -78,7 +77,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class MVGLiveSensor(Entity): +class MVGLiveSensor(SensorEntity): """Implementation of an MVG Live sensor.""" def __init__( diff --git a/homeassistant/components/mychevy/sensor.py b/homeassistant/components/mychevy/sensor.py index 832f1a03f25d3b..18b5e95d8389cf 100644 --- a/homeassistant/components/mychevy/sensor.py +++ b/homeassistant/components/mychevy/sensor.py @@ -1,10 +1,9 @@ """Support for MyChevy sensors.""" import logging -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorEntity from homeassistant.const import PERCENTAGE from homeassistant.core import callback -from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.util import slugify @@ -46,7 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class MyChevyStatus(Entity): +class MyChevyStatus(SensorEntity): """A string representing the charge mode.""" _name = "MyChevy Status" @@ -109,7 +108,7 @@ def should_poll(self): return False -class EVSensor(Entity): +class EVSensor(SensorEntity): """Base EVSensor class. The only real difference between sensors is which units and what diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index a09f8af139459e..a62318aea53ecc 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -4,7 +4,7 @@ from homeassistant.components import mysensors from homeassistant.components.mysensors import on_unload from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONDUCTIVITY, @@ -87,7 +87,7 @@ async def async_discover(discovery_info): ) -class MySensorsSensor(mysensors.device.MySensorsEntity): +class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity): """Representation of a MySensors Sensor child node.""" @property From 18e6816373cf9dbc6fecef4f6bcb81656679a8a1 Mon Sep 17 00:00:00 2001 From: plomosits Date: Mon, 22 Mar 2021 20:03:57 +0100 Subject: [PATCH 1453/1818] Improve Docker and Kubernetes support for KNX (#48065) Co-authored-by: Matthias Alphart --- homeassistant/components/knx/__init__.py | 4 ++++ homeassistant/components/knx/schema.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index c252572e28e642..8f363ac70d1672 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -368,11 +368,15 @@ def connection_config_tunneling(self): local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get( ConnectionSchema.CONF_KNX_LOCAL_IP ) + route_back = self.config[DOMAIN][CONF_KNX_TUNNELING][ + ConnectionSchema.CONF_KNX_ROUTE_BACK + ] return ConnectionConfig( connection_type=ConnectionType.TUNNELING, gateway_ip=gateway_ip, gateway_port=gateway_port, local_ip=local_ip, + route_back=route_back, auto_reconnect=True, ) diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 6ff9d295b751eb..bfb8ba62c39088 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -59,12 +59,14 @@ class ConnectionSchema: """Voluptuous schema for KNX connection.""" CONF_KNX_LOCAL_IP = "local_ip" + CONF_KNX_ROUTE_BACK = "route_back" TUNNELING_SCHEMA = vol.Schema( { vol.Optional(CONF_PORT, default=DEFAULT_MCAST_PORT): cv.port, vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_KNX_LOCAL_IP): cv.string, + vol.Optional(CONF_KNX_ROUTE_BACK, default=False): cv.boolean, } ) From 9e9ba53f0ec7f1e6dbda2c96186104ec571a3e91 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 22 Mar 2021 20:05:13 +0100 Subject: [PATCH 1454/1818] Move SensorEntity last in the inheritance tree (#48230) --- homeassistant/components/abode/sensor.py | 2 +- homeassistant/components/accuweather/sensor.py | 2 +- homeassistant/components/acmeda/sensor.py | 2 +- homeassistant/components/adguard/sensor.py | 2 +- homeassistant/components/ads/sensor.py | 2 +- homeassistant/components/advantage_air/sensor.py | 6 +++--- homeassistant/components/aemet/sensor.py | 4 ++-- homeassistant/components/airly/sensor.py | 2 +- homeassistant/components/airnow/sensor.py | 2 +- homeassistant/components/airvisual/sensor.py | 4 ++-- homeassistant/components/ambient_station/sensor.py | 2 +- homeassistant/components/android_ip_webcam/sensor.py | 2 +- homeassistant/components/asuswrt/sensor.py | 2 +- homeassistant/components/atag/sensor.py | 2 +- homeassistant/components/august/sensor.py | 4 ++-- homeassistant/components/aurora/sensor.py | 2 +- homeassistant/components/awair/sensor.py | 2 +- homeassistant/components/azure_devops/sensor.py | 2 +- homeassistant/components/blebox/sensor.py | 2 +- homeassistant/components/bmw_connected_drive/sensor.py | 2 +- homeassistant/components/brother/sensor.py | 2 +- homeassistant/components/canary/sensor.py | 2 +- homeassistant/components/cert_expiry/sensor.py | 2 +- homeassistant/components/coronavirus/sensor.py | 2 +- homeassistant/components/deconz/sensor.py | 4 ++-- homeassistant/components/derivative/sensor.py | 2 +- homeassistant/components/devolo_home_control/sensor.py | 2 +- homeassistant/components/dexcom/sensor.py | 4 ++-- homeassistant/components/dyson/sensor.py | 2 +- homeassistant/components/eafm/sensor.py | 2 +- homeassistant/components/econet/sensor.py | 2 +- homeassistant/components/eight_sleep/sensor.py | 6 +++--- homeassistant/components/elkm1/sensor.py | 2 +- homeassistant/components/enocean/sensor.py | 2 +- homeassistant/components/enphase_envoy/sensor.py | 2 +- homeassistant/components/envisalink/sensor.py | 2 +- homeassistant/components/esphome/sensor.py | 4 ++-- 37 files changed, 47 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 993228c36acfd8..e3ececb62d96ab 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -34,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeSensor(SensorEntity, AbodeDevice): +class AbodeSensor(AbodeDevice, SensorEntity): """A sensor implementation for Abode devices.""" def __init__(self, data, device, sensor_type): diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 89147cc7104650..722dd8869be88f 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -49,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AccuWeatherSensor(SensorEntity, CoordinatorEntity): +class AccuWeatherSensor(CoordinatorEntity, SensorEntity): """Define an AccuWeather entity.""" def __init__(self, name, kind, coordinator, forecast_day=None): diff --git a/homeassistant/components/acmeda/sensor.py b/homeassistant/components/acmeda/sensor.py index f1961b85589678..4f617c5726fdba 100644 --- a/homeassistant/components/acmeda/sensor.py +++ b/homeassistant/components/acmeda/sensor.py @@ -30,7 +30,7 @@ def async_add_acmeda_sensors(): ) -class AcmedaBattery(SensorEntity, AcmedaBase): +class AcmedaBattery(AcmedaBase, SensorEntity): """Representation of a Acmeda cover device.""" device_class = DEVICE_CLASS_BATTERY diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 6f2f0f54bebcf1..dd0400b6592608 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -49,7 +49,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AdGuardHomeSensor(SensorEntity, AdGuardHomeDeviceEntity): +class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity): """Defines a AdGuard Home sensor.""" def __init__( diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 6e7c69d3c2d69c..933950dcf1b34c 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([entity]) -class AdsSensor(SensorEntity, AdsEntity): +class AdsSensor(AdsEntity, SensorEntity): """Representation of an ADS sensor entity.""" def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor): diff --git a/homeassistant/components/advantage_air/sensor.py b/homeassistant/components/advantage_air/sensor.py index cef70ebb245ebb..19ac584ac2f85b 100644 --- a/homeassistant/components/advantage_air/sensor.py +++ b/homeassistant/components/advantage_air/sensor.py @@ -41,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AdvantageAirTimeTo(SensorEntity, AdvantageAirEntity): +class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity): """Representation of Advantage Air timer control.""" def __init__(self, instance, ac_key, action): @@ -83,7 +83,7 @@ async def set_time_to(self, **kwargs): await self.async_change({self.ac_key: {"info": {self._time_key: value}}}) -class AdvantageAirZoneVent(SensorEntity, AdvantageAirEntity): +class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity): """Representation of Advantage Air Zone Vent Sensor.""" @property @@ -116,7 +116,7 @@ def icon(self): return "mdi:fan-off" -class AdvantageAirZoneSignal(SensorEntity, AdvantageAirEntity): +class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity): """Representation of Advantage Air Zone wireless signal sensor.""" @property diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index 400952830cc66b..199b254cb1e02d 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AemetSensor(SensorEntity, AbstractAemetSensor): +class AemetSensor(AbstractAemetSensor, SensorEntity): """Implementation of an AEMET OpenData sensor.""" def __init__( @@ -81,7 +81,7 @@ def state(self): return self._weather_coordinator.data.get(self._sensor_type) -class AemetForecastSensor(SensorEntity, AbstractAemetSensor): +class AemetForecastSensor(AbstractAemetSensor, SensorEntity): """Implementation of an AEMET OpenData forecast sensor.""" def __init__( diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index efadeac94e1df9..2a52050db15b5a 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -73,7 +73,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirlySensor(SensorEntity, CoordinatorEntity): +class AirlySensor(CoordinatorEntity, SensorEntity): """Define an Airly sensor.""" def __init__(self, coordinator, name, kind): diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index f8f5eff6cc759b..2d3adc8d1e2ae7 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -60,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class AirNowSensor(SensorEntity, CoordinatorEntity): +class AirNowSensor(CoordinatorEntity, SensorEntity): """Define an AirNow sensor.""" def __init__(self, coordinator, kind): diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index c4cbf3af22ec85..1febcec68f4be8 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -139,7 +139,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, True) -class AirVisualGeographySensor(SensorEntity, AirVisualEntity): +class AirVisualGeographySensor(AirVisualEntity, SensorEntity): """Define an AirVisual sensor related to geography data via the Cloud API.""" def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale): @@ -237,7 +237,7 @@ def update_from_latest_data(self): self._attrs.pop(ATTR_LONGITUDE, None) -class AirVisualNodeProSensor(SensorEntity, AirVisualEntity): +class AirVisualNodeProSensor(AirVisualEntity, SensorEntity): """Define an AirVisual sensor related to a Node/Pro unit.""" def __init__(self, coordinator, kind, name, device_class, unit): diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 7e8f3d26ec6bea..732c28c8dc58ab 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -37,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensor_list, True) -class AmbientWeatherSensor(SensorEntity, AmbientWeatherEntity): +class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity): """Define an Ambient sensor.""" def __init__( diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index de029c39abaa50..adedb297cd1fb8 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -31,7 +31,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class IPWebcamSensor(SensorEntity, AndroidIPCamEntity): +class IPWebcamSensor(AndroidIPCamEntity, SensorEntity): """Representation of a IP Webcam sensor.""" def __init__(self, name, host, ipcam, sensor): diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 30f4163d01bfba..7e38243e3d6ebb 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -98,7 +98,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class AsusWrtSensor(SensorEntity, CoordinatorEntity): +class AsusWrtSensor(CoordinatorEntity, SensorEntity): """Representation of a AsusWrt sensor.""" def __init__( diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index 163ed669be451f..88ccbdc899ff29 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -30,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS]) -class AtagSensor(SensorEntity, AtagEntity): +class AtagSensor(AtagEntity, SensorEntity): """Representation of a AtagOne Sensor.""" def __init__(self, coordinator, sensor): diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 9b8352a0b9603c..44597a6485ead0 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -117,7 +117,7 @@ async def _async_migrate_old_unique_ids(hass, devices): registry.async_update_entity(old_entity_id, new_unique_id=device.unique_id) -class AugustOperatorSensor(SensorEntity, AugustEntityMixin, RestoreEntity): +class AugustOperatorSensor(AugustEntityMixin, RestoreEntity, SensorEntity): """Representation of an August lock operation sensor.""" def __init__(self, data, device): @@ -216,7 +216,7 @@ def unique_id(self) -> str: return f"{self._device_id}_lock_operator" -class AugustBatterySensor(SensorEntity, AugustEntityMixin): +class AugustBatterySensor(AugustEntityMixin, SensorEntity): """Representation of an August sensor.""" def __init__(self, data, sensor_type, device, old_device): diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py index 1a34bb604daf72..d7024cc630a439 100644 --- a/homeassistant/components/aurora/sensor.py +++ b/homeassistant/components/aurora/sensor.py @@ -19,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entries): async_add_entries([entity]) -class AuroraSensor(SensorEntity, AuroraEntity): +class AuroraSensor(AuroraEntity, SensorEntity): """Implementation of an aurora sensor.""" @property diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index de39c20eb6029b..502fa3dc62605c 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -84,7 +84,7 @@ async def async_setup_entry( async_add_entities(sensors) -class AwairSensor(SensorEntity, CoordinatorEntity): +class AwairSensor(CoordinatorEntity, SensorEntity): """Defines an Awair sensor entity.""" def __init__( diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index a0701ac9839367..01018d34c78ebc 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -56,7 +56,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class AzureDevOpsSensor(SensorEntity, AzureDevOpsDeviceEntity): +class AzureDevOpsSensor(AzureDevOpsDeviceEntity, SensorEntity): """Defines a Azure DevOps sensor.""" def __init__( diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 688d2931461204..c1b9d8501c10f8 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -14,7 +14,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class BleBoxSensorEntity(SensorEntity, BleBoxEntity): +class BleBoxSensorEntity(BleBoxEntity, SensorEntity): """Representation of a BleBox sensor feature.""" @property diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 5c85312d959b4d..38415d0006fdb0 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class BMWConnectedDriveSensor(SensorEntity, BMWConnectedDriveBaseEntity): +class BMWConnectedDriveSensor(BMWConnectedDriveBaseEntity, SensorEntity): """Representation of a BMW vehicle sensor.""" def __init__(self, account, vehicle, attribute: str, attribute_info): diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 10cc3979207f98..0b614ffa5825e0 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -57,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class BrotherPrinterSensor(SensorEntity, CoordinatorEntity): +class BrotherPrinterSensor(CoordinatorEntity, SensorEntity): """Define an Brother Printer sensor.""" def __init__(self, coordinator, kind, device_info): diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index e4c6190ff9901c..d7a6648857a503 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -78,7 +78,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class CanarySensor(SensorEntity, CoordinatorEntity): +class CanarySensor(CoordinatorEntity, SensorEntity): """Representation of a Canary sensor.""" def __init__(self, coordinator, sensor_type, location, device): diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index bf80f8b8fb57b7..a05acdb5d77397 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -76,7 +76,7 @@ def extra_state_attributes(self): } -class SSLCertificateTimestamp(SensorEntity, CertExpiryEntity): +class SSLCertificateTimestamp(CertExpiryEntity, SensorEntity): """Implementation of the Cert Expiry timestamp sensor.""" @property diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index 58e6760a85d4c7..472b8bc8d1ce3b 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -24,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class CoronavirusSensor(SensorEntity, CoordinatorEntity): +class CoronavirusSensor(CoordinatorEntity, SensorEntity): """Sensor representing corona virus data.""" name = None diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 8f23b47a1b9792..0646ad09a316dd 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -122,7 +122,7 @@ def async_add_sensor(sensors=gateway.api.sensors.values()): ) -class DeconzSensor(SensorEntity, DeconzDevice): +class DeconzSensor(DeconzDevice, SensorEntity): """Representation of a deCONZ sensor.""" TYPE = DOMAIN @@ -186,7 +186,7 @@ def extra_state_attributes(self): return attr -class DeconzBattery(SensorEntity, DeconzDevice): +class DeconzBattery(DeconzDevice, SensorEntity): """Battery class for when a device is only represented as an event.""" TYPE = DOMAIN diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index 72d0186d94d954..12fc4ddd7ba2b4 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -86,7 +86,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([derivative]) -class DerivativeSensor(SensorEntity, RestoreEntity): +class DerivativeSensor(RestoreEntity, SensorEntity): """Representation of an derivative sensor.""" def __init__( diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index f125448b4cc6ab..e3c16670dfd540 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -66,7 +66,7 @@ async def async_setup_entry( async_add_entities(entities, False) -class DevoloMultiLevelDeviceEntity(SensorEntity, DevoloDeviceEntity): +class DevoloMultiLevelDeviceEntity(DevoloDeviceEntity, SensorEntity): """Abstract representation of a multi level sensor within devolo Home Control.""" @property diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index 09c7ff4e051ca2..730a1824e1aad5 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -17,7 +17,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors, False) -class DexcomGlucoseValueSensor(SensorEntity, CoordinatorEntity): +class DexcomGlucoseValueSensor(CoordinatorEntity, SensorEntity): """Representation of a Dexcom glucose value sensor.""" def __init__(self, coordinator, username, unit_of_measurement): @@ -59,7 +59,7 @@ def unique_id(self): return self._unique_id -class DexcomGlucoseTrendSensor(SensorEntity, CoordinatorEntity): +class DexcomGlucoseTrendSensor(CoordinatorEntity, SensorEntity): """Representation of a Dexcom glucose trend sensor.""" def __init__(self, coordinator, username): diff --git a/homeassistant/components/dyson/sensor.py b/homeassistant/components/dyson/sensor.py index 356bfc61cb248e..cff4b8f5501366 100644 --- a/homeassistant/components/dyson/sensor.py +++ b/homeassistant/components/dyson/sensor.py @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class DysonSensor(SensorEntity, DysonEntity): +class DysonSensor(DysonEntity, SensorEntity): """Representation of a generic Dyson sensor.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/eafm/sensor.py b/homeassistant/components/eafm/sensor.py index 8fa2a47c1578ec..b3d726f9cd3c47 100644 --- a/homeassistant/components/eafm/sensor.py +++ b/homeassistant/components/eafm/sensor.py @@ -78,7 +78,7 @@ async def async_update_data(): await coordinator.async_refresh() -class Measurement(SensorEntity, CoordinatorEntity): +class Measurement(CoordinatorEntity, SensorEntity): """A gauge at a flood monitoring station.""" attribution = "This uses Environment Agency flood and river level data from the real-time data API" diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index 8198eb75045256..05fa1b734ea533 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -48,7 +48,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors) -class EcoNetSensor(SensorEntity, EcoNetEntity): +class EcoNetSensor(EcoNetEntity, SensorEntity): """Define a Econet sensor.""" def __init__(self, econet_device, device_name): diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index f5a0d1b1f8d2d5..ae0854ec244526 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -67,7 +67,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class EightHeatSensor(SensorEntity, EightSleepHeatEntity): +class EightHeatSensor(EightSleepHeatEntity, SensorEntity): """Representation of an eight sleep heat-based sensor.""" def __init__(self, name, eight, sensor): @@ -120,7 +120,7 @@ def extra_state_attributes(self): } -class EightUserSensor(SensorEntity, EightSleepUserEntity): +class EightUserSensor(EightSleepUserEntity, SensorEntity): """Representation of an eight sleep user-based sensor.""" def __init__(self, name, eight, sensor, units): @@ -290,7 +290,7 @@ def extra_state_attributes(self): return state_attr -class EightRoomSensor(SensorEntity, EightSleepUserEntity): +class EightRoomSensor(EightSleepUserEntity, SensorEntity): """Representation of an eight sleep room sensor.""" def __init__(self, name, eight, sensor, units): diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index c8d4fd722bb2e7..e33196a08c07b2 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -68,7 +68,7 @@ def temperature_to_state(temperature, undefined_temperature): return temperature if temperature > undefined_temperature else None -class ElkSensor(SensorEntity, ElkAttachedEntity): +class ElkSensor(ElkAttachedEntity, SensorEntity): """Base representation of Elk-M1 sensor.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 18362a3c6ac6c5..1814efb9c87bf5 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanWindowHandle(dev_id, dev_name)]) -class EnOceanSensor(SensorEntity, EnOceanEntity, RestoreEntity): +class EnOceanSensor(EnOceanEntity, RestoreEntity, SensorEntity): """Representation of an EnOcean sensor device such as a power meter.""" def __init__(self, dev_id, dev_name, sensor_type): diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 794c0ca2dbdb67..dd1b10c870b8c8 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -156,7 +156,7 @@ async def async_update_data(): async_add_entities(entities) -class Envoy(SensorEntity, CoordinatorEntity): +class Envoy(CoordinatorEntity, SensorEntity): """Envoy entity.""" def __init__(self, sensor_type, name, serial_number, unit, coordinator): diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index ff4c73500f9a3e..6fd7f32c6fe669 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -37,7 +37,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class EnvisalinkSensor(SensorEntity, EnvisalinkDevice): +class EnvisalinkSensor(EnvisalinkDevice, SensorEntity): """Representation of an Envisalink keypad.""" def __init__(self, hass, partition_name, partition_number, info, controller): diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 99f66f480297ff..d751109c159752 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -42,7 +42,7 @@ async def async_setup_entry( # pylint: disable=invalid-overridden-method -class EsphomeSensor(SensorEntity, EsphomeEntity): +class EsphomeSensor(EsphomeEntity, SensorEntity): """A sensor implementation for esphome.""" @property @@ -89,7 +89,7 @@ def device_class(self) -> str: return self._static_info.device_class -class EsphomeTextSensor(SensorEntity, EsphomeEntity): +class EsphomeTextSensor(EsphomeEntity, SensorEntity): """A text sensor implementation for ESPHome.""" @property From 19ab7306ecd19dc69f12ad397e365ec9b086b06d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 22 Mar 2021 15:21:33 -0700 Subject: [PATCH 1455/1818] Clean up AsusWRT (#48012) --- .../components/asuswrt/device_tracker.py | 56 +++++++------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 1ef68f9d65d1c5..bd86dd21edd8cf 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -56,66 +56,51 @@ class AsusWrtDevice(ScannerEntity): def __init__(self, router: AsusWrtRouter, device) -> None: """Initialize a AsusWrt device.""" self._router = router - self._mac = device.mac - self._name = device.name or DEFAULT_DEVICE_NAME - self._active = False - self._icon = None - self._attrs = {} - - @callback - def async_update_state(self) -> None: - """Update the AsusWrt device.""" - device = self._router.devices[self._mac] - self._active = device.is_connected - - self._attrs = { - "mac": device.mac, - "ip_address": device.ip_address, - } - if device.last_activity: - self._attrs["last_time_reachable"] = device.last_activity.isoformat( - timespec="seconds" - ) + self._device = device @property def unique_id(self) -> str: """Return a unique ID.""" - return self._mac + return self._device.mac @property def name(self) -> str: """Return the name.""" - return self._name + return self._device.name or DEFAULT_DEVICE_NAME @property def is_connected(self): """Return true if the device is connected to the network.""" - return self._active + return self._device.is_connected @property def source_type(self) -> str: """Return the source type.""" return SOURCE_TYPE_ROUTER - @property - def icon(self) -> str: - """Return the icon.""" - return self._icon - @property def extra_state_attributes(self) -> dict[str, any]: """Return the attributes.""" - return self._attrs + attrs = { + "mac": self._device.mac, + "ip_address": self._device.ip_address, + } + if self._device.last_activity: + attrs["last_time_reachable"] = self._device.last_activity.isoformat( + timespec="seconds" + ) + return attrs @property def device_info(self) -> dict[str, any]: """Return the device information.""" - return { - "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, - "manufacturer": "AsusWRT Tracked device", + data = { + "connections": {(CONNECTION_NETWORK_MAC, self._device.mac)}, } + if self._device.name: + data["default_name"] = self._device.name + + return data @property def should_poll(self) -> bool: @@ -125,12 +110,11 @@ def should_poll(self) -> bool: @callback def async_on_demand_update(self): """Update state.""" - self.async_update_state() + self._device = self._router.devices[self._device.mac] self.async_write_ha_state() async def async_added_to_hass(self): """Register state update callback.""" - self.async_update_state() self.async_on_remove( async_dispatcher_connect( self.hass, From 55b689b4649a7bb916618b70bffa42296bdb41cf Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 23 Mar 2021 00:03:29 +0000 Subject: [PATCH 1456/1818] [ci skip] Translation update --- .../components/aemet/translations/lb.json | 3 ++ .../components/august/translations/el.json | 22 ++++++++ .../components/climacell/translations/el.json | 31 +++++++++++ .../device_tracker/translations/nl.json | 2 +- .../emulated_roku/translations/nl.json | 2 +- .../flunearyou/translations/nl.json | 2 +- .../components/hive/translations/el.json | 53 +++++++++++++++++++ .../components/homekit/translations/et.json | 2 +- .../components/homekit/translations/it.json | 2 +- .../components/homekit/translations/no.json | 2 +- .../components/homekit/translations/ru.json | 2 +- .../components/hyperion/translations/ca.json | 1 + .../components/hyperion/translations/el.json | 11 ++++ .../components/hyperion/translations/en.json | 1 + .../components/hyperion/translations/et.json | 1 + .../components/hyperion/translations/it.json | 1 + .../components/hyperion/translations/ru.json | 1 + .../components/konnected/translations/nl.json | 10 ++-- .../components/melcloud/translations/nl.json | 6 +-- .../minecraft_server/translations/nl.json | 2 +- .../components/mqtt/translations/nl.json | 13 ++++- .../components/mullvad/translations/el.json | 22 ++++++++ .../components/myq/translations/nl.json | 4 +- .../components/netatmo/translations/el.json | 19 +++++++ .../components/nexia/translations/nl.json | 2 +- .../components/notify/translations/nl.json | 2 +- .../components/nws/translations/nl.json | 2 +- .../opentherm_gw/translations/el.json | 12 +++++ .../philips_js/translations/el.json | 13 +++++ .../screenlogic/translations/el.json | 39 ++++++++++++++ .../components/subaru/translations/el.json | 35 ++++++++++++ .../components/verisure/translations/el.json | 12 +++++ .../water_heater/translations/el.json | 13 +++++ 33 files changed, 323 insertions(+), 22 deletions(-) create mode 100644 homeassistant/components/august/translations/el.json create mode 100644 homeassistant/components/climacell/translations/el.json create mode 100644 homeassistant/components/hive/translations/el.json create mode 100644 homeassistant/components/hyperion/translations/el.json create mode 100644 homeassistant/components/mullvad/translations/el.json create mode 100644 homeassistant/components/netatmo/translations/el.json create mode 100644 homeassistant/components/opentherm_gw/translations/el.json create mode 100644 homeassistant/components/philips_js/translations/el.json create mode 100644 homeassistant/components/screenlogic/translations/el.json create mode 100644 homeassistant/components/subaru/translations/el.json create mode 100644 homeassistant/components/verisure/translations/el.json create mode 100644 homeassistant/components/water_heater/translations/el.json diff --git a/homeassistant/components/aemet/translations/lb.json b/homeassistant/components/aemet/translations/lb.json index 5ae5fff8664fdb..8e83c0e86d3288 100644 --- a/homeassistant/components/aemet/translations/lb.json +++ b/homeassistant/components/aemet/translations/lb.json @@ -3,6 +3,9 @@ "step": { "user": { "data": { + "api_key": "API Schl\u00ebssel", + "latitude": "L\u00e4ngegrad", + "longitude": "Breedegrad", "name": "Numm vun der Integratioun" }, "title": "AEMET OpenData" diff --git a/homeassistant/components/august/translations/el.json b/homeassistant/components/august/translations/el.json new file mode 100644 index 00000000000000..8a976d451be060 --- /dev/null +++ b/homeassistant/components/august/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "reauth_validate": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \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\u03b9\u03ba\u03c5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03ad\u03bd\u03b1\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc August" + }, + "user_validate": { + "data": { + "login_method": "\u039c\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03ac\u03bd \u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"\u03b7\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03c4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf\", \u03c4\u03bf \u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \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. \u0395\u03ac\u03bd \u03b7 \u03bc\u03ad\u03b8\u03bf\u03b4\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \"\u03c4\u03b7\u03bb\u03ad\u03c6\u03c9\u03bd\u03bf\", \u03c4\u03bf \u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03b7\u03bb\u03b5\u03c6\u03ce\u03bd\u03bf\u03c5 \u03bc\u03b5 \u03c4\u03b7 \u03bc\u03bf\u03c1\u03c6\u03ae '+NNNNNNNNNN'.", + "title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc August" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/climacell/translations/el.json b/homeassistant/components/climacell/translations/el.json new file mode 100644 index 00000000000000..45ed5d8a722b4f --- /dev/null +++ b/homeassistant/components/climacell/translations/el.json @@ -0,0 +1,31 @@ +{ + "config": { + "error": { + "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 \u03a0\u03bb\u03ac\u03c4\u03bf\u03c2", + "longitude": "\u0393\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u039c\u03ae\u03ba\u03bf\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "\u0395\u03ac\u03bd \u03b4\u03b5\u03bd \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c4\u03bf \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03b9\u03ba\u03cc \u03c0\u03bb\u03ac\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \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 \u03bf\u03b9 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c4\u03b9\u03bc\u03ad\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. \u0398\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03b7\u03b8\u03b5\u03af \u03bc\u03b9\u03b1 \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03c4\u03cd\u03c0\u03bf \u03b4\u03b5\u03bb\u03c4\u03af\u03bf\u03c5, \u03b1\u03bb\u03bb\u03ac \u03bc\u03cc\u03bd\u03bf \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03b5\u03c4\u03b5 \u03b8\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03bf\u03cd\u03bd \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "forecast_types": "\u0395\u03af\u03b4\u03bf\u03c2/\u03b7 \u0394\u03b5\u03bb\u03c4\u03af\u03bf\u03c5/\u03c9\u03bd", + "timestep": "\u039b\u03b5\u03c0\u03c4\u03ac \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd NowCast" + }, + "description": "\u0395\u03ac\u03bd \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03bf\u03bd\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd 'nowcast', \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03ba\u03ac\u03b8\u03b5 \u03b4\u03b5\u03bb\u03c4\u03af\u03bf\u03c5. \u039f \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c4\u03c9\u03bd \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b5\u03be\u03b1\u03c1\u03c4\u03ac\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bb\u03b5\u03c0\u03c4\u03ce\u03bd \u03c0\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03ad\u03b3\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c4\u03c9\u03bd \u03b4\u03b5\u03bb\u03c4\u03af\u03c9\u03bd.", + "title": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b5 \u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 ClimaCell" + } + } + }, + "title": "ClimaCell" +} \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/nl.json b/homeassistant/components/device_tracker/translations/nl.json index a28c8bdbbb8773..a3f7d6cd31f12c 100644 --- a/homeassistant/components/device_tracker/translations/nl.json +++ b/homeassistant/components/device_tracker/translations/nl.json @@ -15,5 +15,5 @@ "not_home": "Afwezig" } }, - "title": "Apparaat tracker" + "title": "Apparaattracker" } \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/nl.json b/homeassistant/components/emulated_roku/translations/nl.json index d4f31b7bb5d151..dd988985250341 100644 --- a/homeassistant/components/emulated_roku/translations/nl.json +++ b/homeassistant/components/emulated_roku/translations/nl.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "advertise_ip": "Ip adres ontdekbaar", + "advertise_ip": "IP-adres zichtbaar", "advertise_port": "Adverteer Poort", "host_ip": "Host IP", "listen_port": "Luisterpoort", diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index bca6e2a8c8bf20..d78abfcc187be8 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -12,7 +12,7 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad" }, - "description": "Bewaak op gebruikers gebaseerde en CDC-repots voor een paar co\u00f6rdinaten.", + "description": "Bewaak gebruikersgebaseerde en CDC-rapporten voor een paar co\u00f6rdinaten.", "title": "Configureer \nFlu Near You" } } diff --git a/homeassistant/components/hive/translations/el.json b/homeassistant/components/hive/translations/el.json new file mode 100644 index 00000000000000..986dd52ef199ec --- /dev/null +++ b/homeassistant/components/hive/translations/el.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "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", + "reauth_successful": "H \u03b5\u03c0\u03b1\u03bd\u03b1\u03b5\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03af\u03c9\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2.", + "unknown_entry": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03c3\u03b1\u03c2 \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2." + }, + "error": { + "invalid_code": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Hive. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd \u03ae\u03c4\u03b1\u03bd \u03bb\u03b1\u03bd\u03b8\u03b1\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2.", + "invalid_password": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Hive. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bb\u03ac\u03b8\u03bf\u03c2, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.", + "invalid_username": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf Hive. \u0397 \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 \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9.", + "no_internet_available": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf \u0394\u03b9\u03b1\u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03b3\u03b9\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Hive.", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "2fa": { + "data": { + "2fa": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b4\u03cd\u03bf \u03c0\u03b1\u03c1\u03b1\u03b3\u03cc\u03bd\u03c4\u03c9\u03bd" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 Hive. \n\n \u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc 0000 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b6\u03b7\u03c4\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ac\u03bb\u03bb\u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc.", + "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 \u03c4\u03bf\u03c5 Hive." + }, + "reauth": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Hive.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 Hive" + }, + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03a3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf Hive.", + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03b5 Hive" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03a3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "description": "\u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03c3\u03ac\u03c1\u03c9\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03b5\u03c4\u03b5 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03c0\u03b9\u03bf \u03c3\u03c5\u03c7\u03bd\u03ac.", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b3\u03b9\u03b1 Hive" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/et.json b/homeassistant/components/homekit/translations/et.json index 8c24d2d225180f..2ae8d651eb186d 100644 --- a/homeassistant/components/homekit/translations/et.json +++ b/homeassistant/components/homekit/translations/et.json @@ -55,7 +55,7 @@ "entities": "Olemid", "mode": "Re\u017eiim" }, - "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis, kuvatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", + "description": "Vali kaasatavad olemid. Tarvikute re\u017eiimis on kaasatav ainult \u00fcks olem. Silla re\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud juhul, kui valitud on kindlad olemid. Silla v\u00e4listamisre\u017eiimis kaasatakse k\u00f5ik domeeni olemid, v\u00e4lja arvatud v\u00e4listatud olemid. Parima kasutuskogemuse jaoks on eraldi HomeKit seadmed iga meediumim\u00e4ngija ja kaamera jaoks.", "title": "Vali kaasatavd olemid" }, "init": { diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json index fee64457652dd0..f92c61d493f85c 100644 --- a/homeassistant/components/homekit/translations/it.json +++ b/homeassistant/components/homekit/translations/it.json @@ -55,7 +55,7 @@ "entities": "Entit\u00e0", "mode": "Modalit\u00e0" }, - "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, ci sar\u00e0 una HomeKit separata in modalit\u00e0 accessorio per ogni lettore multimediale, TV e videocamera.", + "description": "Scegliere le entit\u00e0 da includere. In modalit\u00e0 accessorio, \u00e8 inclusa una sola entit\u00e0. In modalit\u00e0 di inclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, a meno che non siano selezionate entit\u00e0 specifiche. In modalit\u00e0 di esclusione bridge, tutte le entit\u00e0 nel dominio saranno incluse, ad eccezione delle entit\u00e0 escluse. Per prestazioni ottimali, sar\u00e0 creata una HomeKit separata accessoria per ogni lettore multimediale, TV e videocamera.", "title": "Seleziona le entit\u00e0 da includere" }, "init": { diff --git a/homeassistant/components/homekit/translations/no.json b/homeassistant/components/homekit/translations/no.json index 4748fe63af204e..6e13907d057436 100644 --- a/homeassistant/components/homekit/translations/no.json +++ b/homeassistant/components/homekit/translations/no.json @@ -55,7 +55,7 @@ "entities": "Entiteter", "mode": "Modus" }, - "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse vil et eget HomeKit-tilbeh\u00f8r v\u00e6re TV-mediaspiller og kamera.", + "description": "Velg enhetene som skal inkluderes. I tilbeh\u00f8rsmodus er bare en enkelt enhet inkludert. I bridge-inkluderingsmodus vil alle enheter i domenet bli inkludert, med mindre spesifikke enheter er valgt. I bridge-ekskluderingsmodus vil alle enheter i domenet bli inkludert, bortsett fra de ekskluderte enhetene. For best ytelse opprettes et eget HomeKit-tilbeh\u00f8r for hver tv-mediaspiller og kamera.", "title": "Velg enheter som skal inkluderes" }, "init": { diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index d00744e4cb4419..ffc0ac34eae4ad 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -55,7 +55,7 @@ "entities": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b", "mode": "\u0420\u0435\u0436\u0438\u043c" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u0430 \u0438\u043b\u0438 \u043a\u0430\u043c\u0435\u0440\u044b.", + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u043e\u0441\u0442\u0430 \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b. \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432\u0441\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0434\u043e\u043c\u0435\u043d\u0443, \u043a\u0440\u043e\u043c\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u0435\u0434\u0438\u0430\u043f\u043b\u0435\u0435\u0440\u044b \u0438 \u043a\u0430\u043c\u0435\u0440\u044b \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430.", "title": "\u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0432 HomeKit" }, "init": { diff --git a/homeassistant/components/hyperion/translations/ca.json b/homeassistant/components/hyperion/translations/ca.json index 50ac384d8ca362..3a1de53102bb02 100644 --- a/homeassistant/components/hyperion/translations/ca.json +++ b/homeassistant/components/hyperion/translations/ca.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Efectes d'Hyperion a mostrar", "priority": "Prioritat Hyperion a utilitzar per als colors i efectes" } } diff --git a/homeassistant/components/hyperion/translations/el.json b/homeassistant/components/hyperion/translations/el.json new file mode 100644 index 00000000000000..5ba51046f21294 --- /dev/null +++ b/homeassistant/components/hyperion/translations/el.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "effect_show_list": "\u0395\u03c6\u03ad Hyperion \u03b3\u03b9\u03b1 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/en.json b/homeassistant/components/hyperion/translations/en.json index d1277b411e0254..38ab5cf70550e9 100644 --- a/homeassistant/components/hyperion/translations/en.json +++ b/homeassistant/components/hyperion/translations/en.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Hyperion effects to show", "priority": "Hyperion priority to use for colors and effects" } } diff --git a/homeassistant/components/hyperion/translations/et.json b/homeassistant/components/hyperion/translations/et.json index a225b7f2c4711c..dba661d24c2dc3 100644 --- a/homeassistant/components/hyperion/translations/et.json +++ b/homeassistant/components/hyperion/translations/et.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Kuvatavad Hyperioni efektid", "priority": "V\u00e4rvide ja efektide puhul on kasutatavad Hyperioni eelistused" } } diff --git a/homeassistant/components/hyperion/translations/it.json b/homeassistant/components/hyperion/translations/it.json index b03b368d0390eb..81f049125ed235 100644 --- a/homeassistant/components/hyperion/translations/it.json +++ b/homeassistant/components/hyperion/translations/it.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Effetti di Hyperion da mostrare", "priority": "Priorit\u00e0 Hyperion da usare per colori ed effetti" } } diff --git a/homeassistant/components/hyperion/translations/ru.json b/homeassistant/components/hyperion/translations/ru.json index 9e74680a951505..a6716bb74ead4c 100644 --- a/homeassistant/components/hyperion/translations/ru.json +++ b/homeassistant/components/hyperion/translations/ru.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "\u042d\u0444\u0444\u0435\u043a\u0442\u044b Hyperion", "priority": "\u041f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 Hyperion \u0434\u043b\u044f \u0446\u0432\u0435\u0442\u043e\u0432 \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u043e\u0432" } } diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 635c064807ba6c..0387dc8c7b047f 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -2,12 +2,12 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", - "already_in_progress": "De configuratiestroom voor het apparaat wordt al uitgevoerd.", + "already_in_progress": "De configuratiestroom is al aan de gang", "not_konn_panel": "Geen herkend Konnected.io apparaat", - "unknown": "Onbekende fout opgetreden" + "unknown": "Onverwachte fout" }, "error": { - "cannot_connect": "Kan geen verbinding maken met een Konnected Panel op {host} : {port}" + "cannot_connect": "Kan geen verbinding maken" }, "step": { "confirm": { @@ -20,8 +20,8 @@ }, "user": { "data": { - "host": "IP-adres van Konnected apparaat", - "port": "Konnected apparaat poort" + "host": "IP-adres", + "port": "Poort" }, "description": "Voer de host-informatie in voor uw Konnected-paneel." } diff --git a/homeassistant/components/melcloud/translations/nl.json b/homeassistant/components/melcloud/translations/nl.json index 8ef8cc716b1b82..481027f9092a38 100644 --- a/homeassistant/components/melcloud/translations/nl.json +++ b/homeassistant/components/melcloud/translations/nl.json @@ -4,15 +4,15 @@ "already_configured": "MELCloud integratie is al geconfigureerd voor deze e-mail. Toegangstoken is vernieuwd." }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, "step": { "user": { "data": { - "password": "MELCloud wachtwoord.", - "username": "E-mail gebruikt om in te loggen op MELCloud." + "password": "Wachtwoord", + "username": "E-mail" }, "description": "Maak verbinding via uw MELCloud account.", "title": "Maak verbinding met MELCloud" diff --git a/homeassistant/components/minecraft_server/translations/nl.json b/homeassistant/components/minecraft_server/translations/nl.json index 1d589f942e7c0d..0170964d5b0722 100644 --- a/homeassistant/components/minecraft_server/translations/nl.json +++ b/homeassistant/components/minecraft_server/translations/nl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Host is al geconfigureerd." + "already_configured": "Service is al geconfigureerd" }, "error": { "cannot_connect": "Kan geen verbinding maken met de server. Controleer de host en de poort en probeer het opnieuw. Zorg er ook voor dat u minimaal Minecraft versie 1.7 op uw server uitvoert.", diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 8395b1db7e96b8..cac483b1bf050c 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -50,11 +50,14 @@ }, "options": { "error": { + "bad_birth": "Ongeldig birth topic", + "bad_will": "Ongeldig will topic", "cannot_connect": "Kon niet verbinden" }, "step": { "broker": { "data": { + "broker": "Broker", "password": "Wachtwoord", "port": "Poort", "username": "Gebruikersnaam" @@ -65,7 +68,15 @@ "data": { "birth_enable": "Geboortebericht inschakelen", "birth_payload": "Birth message payload", - "birth_topic": "Birth message onderwerp" + "birth_qos": "Birth message QoS", + "birth_retain": "Birth message behouden", + "birth_topic": "Birth message onderwerp", + "discovery": "Discovery inschakelen", + "will_enable": "Will message inschakelen", + "will_payload": "Will message payload", + "will_qos": "Will message QoS", + "will_retain": "Will message behouden", + "will_topic": "Will message topic" }, "description": "Selecteer MQTT-opties." } diff --git a/homeassistant/components/mullvad/translations/el.json b/homeassistant/components/mullvad/translations/el.json new file mode 100644 index 00000000000000..6f19f0039eddaf --- /dev/null +++ b/homeassistant/components/mullvad/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_auth": "\u0386\u03ba\u03c5\u03c1\u03b7 \u03b5\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7", + "unknown": "\u0391\u03c0\u03c1\u03cc\u03c3\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "host": "\u03a0\u03ac\u03c1\u03bf\u03c7\u03bf\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2 Mullvad VPN;" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/nl.json b/homeassistant/components/myq/translations/nl.json index fd6310cce6a735..65df320a544ef4 100644 --- a/homeassistant/components/myq/translations/nl.json +++ b/homeassistant/components/myq/translations/nl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ is al geconfigureerd" + "already_configured": "Service is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/netatmo/translations/el.json b/homeassistant/components/netatmo/translations/el.json new file mode 100644 index 00000000000000..03a1530be9b74d --- /dev/null +++ b/homeassistant/components/netatmo/translations/el.json @@ -0,0 +1,19 @@ +{ + "device_automation": { + "trigger_subtype": { + "away": "\u03b5\u03ba\u03c4\u03cc\u03c2", + "hg": "\u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c3\u03af\u03b1 \u03c0\u03b1\u03b3\u03b5\u03c4\u03bf\u03cd", + "schedule": "\u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1" + }, + "trigger_type": { + "alarm_started": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03cc", + "animal": "{entity_name} \u03b5\u03bd\u03c4\u03cc\u03c0\u03b9\u03c3\u03b5 \u03ad\u03bd\u03b1 \u03b6\u03ce\u03bf", + "cancel_set_point": "\u03a4\u03bf {entity_name} \u03c3\u03c5\u03bd\u03ad\u03c7\u03b9\u03c3\u03b5 \u03c4\u03bf \u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03ac \u03c4\u03bf\u03c5", + "human": "{entity_name} \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ad\u03bd\u03b1\u03c2 \u03ac\u03bd\u03b8\u03c1\u03c9\u03c0\u03bf\u03c2", + "movement": "{entity_name} \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ba\u03af\u03bd\u03b7\u03c3\u03b7", + "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" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json index a9b4d99883ef4e..faa19d3b63c05a 100644 --- a/homeassistant/components/nexia/translations/nl.json +++ b/homeassistant/components/nexia/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unknown": "Onverwachte fout" }, diff --git a/homeassistant/components/notify/translations/nl.json b/homeassistant/components/notify/translations/nl.json index 409692f72277eb..52a24cc9efdd85 100644 --- a/homeassistant/components/notify/translations/nl.json +++ b/homeassistant/components/notify/translations/nl.json @@ -1,3 +1,3 @@ { - "title": "Notificeer" + "title": "Meldingen" } \ No newline at end of file diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index c8e10dfba10ec7..5332f43f4c73f9 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -15,7 +15,7 @@ "longitude": "Lengtegraad", "station": "METAR-zendercode" }, - "description": "Als er geen METAR-zendercode is opgegeven, worden de lengte- en breedtegraad gebruikt om het dichtstbijzijnde station te vinden.", + "description": "Als er geen METAR-stationscode is opgegeven, worden de lengte- en breedtegraad gebruikt om het dichtstbijzijnde station te vinden. Voorlopig kan een API-sleutel van alles zijn. Het wordt aanbevolen om een geldig e-mailadres te gebruiken.", "title": "Maak verbinding met de National Weather Service" } } diff --git a/homeassistant/components/opentherm_gw/translations/el.json b/homeassistant/components/opentherm_gw/translations/el.json new file mode 100644 index 00000000000000..f15bc7bdc0e25b --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/el.json @@ -0,0 +1,12 @@ +{ + "options": { + "step": { + "init": { + "data": { + "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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/philips_js/translations/el.json b/homeassistant/components/philips_js/translations/el.json new file mode 100644 index 00000000000000..94f6b540bd0e4e --- /dev/null +++ b/homeassistant/components/philips_js/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "pair": { + "data": { + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf 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\u03ae \u03c3\u03b1\u03c2", + "title": "\u03a3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/screenlogic/translations/el.json b/homeassistant/components/screenlogic/translations/el.json new file mode 100644 index 00000000000000..26906ac3b29059 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/el.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af" + }, + "error": { + "cannot_connect": "\u0391\u03b4\u03c5\u03bd\u03b1\u03bc\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03c4\u03b7\u03c2 \u03c0\u03cd\u03bb\u03b7\u03c2 ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "\u03a0\u03cd\u03bb\u03b7" + }, + "description": "\u0395\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03c4\u03b7\u03ba\u03b1\u03bd \u03bf\u03b9 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b5\u03c2 \u03c0\u03cd\u03bb\u03b5\u03c2 ScreenLogic. \u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03b3\u03b9\u03b1 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03ae \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cd\u03bb\u03b7 ScreenLogic \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\u0394\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03c3\u03b1\u03c1\u03ce\u03c3\u03b5\u03c9\u03bd" + }, + "description": "\u039a\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/subaru/translations/el.json b/homeassistant/components/subaru/translations/el.json new file mode 100644 index 00000000000000..17ede1af4e74d3 --- /dev/null +++ b/homeassistant/components/subaru/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "error": { + "unknown": "\u0391\u03c0\u03c1\u03bf\u03c3\u03b4\u03cc\u03ba\u03b7\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "pin": { + "data": { + "pin": "PIN" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" + }, + "user": { + "data": { + "country": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c7\u03ce\u03c1\u03b1\u03c2", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03a7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\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 \u03c4\u03bf\u03c5 MySubaru\n\u03a3\u0397\u039c\u0395\u0399\u03a9\u03a3\u0397: \u0397 \u03b1\u03c1\u03c7\u03b9\u03ba\u03ae \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03bd\u03b4\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03ad\u03c9\u03c2 \u03ba\u03b1\u03b9 30 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 Subaru Starlink" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "update_enabled": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03bf\u03c7\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2" + }, + "description": "\u038c\u03c4\u03b1\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af, \u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7 \u03bf\u03c7\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd \u03b8\u03b1 \u03c3\u03c4\u03ad\u03bb\u03bd\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b1\u03c0\u03bf\u03bc\u03b1\u03ba\u03c1\u03c5\u03c3\u03bc\u03ad\u03bd\u03b7 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae \u03c3\u03c4\u03bf \u03cc\u03c7\u03b7\u03bc\u03ac \u03c3\u03b1\u03c2 \u03ba\u03ac\u03b8\u03b5 2 \u03ce\u03c1\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03ba\u03c4\u03ae\u03c3\u03b5\u03b9 \u03c4\u03b1 \u03bd\u03ad\u03b1 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b1 \u03c4\u03c9\u03bd \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd. \u03a7\u03c9\u03c1\u03af\u03c2 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7 \u03bf\u03c7\u03b7\u03bc\u03ac\u03c4\u03c9\u03bd, \u03c4\u03b1 \u03bd\u03ad\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03c9\u03bd \u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03cc\u03bd\u03bf \u03cc\u03c4\u03b1\u03bd \u03c4\u03bf \u03cc\u03c7\u03b7\u03bc\u03b1 \u03c9\u03b8\u03b5\u03af \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b1 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 (\u03ba\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ac \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03bc\u03b7\u03c7\u03b1\u03bd\u03ce\u03bd).", + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Subaru Starlink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/el.json b/homeassistant/components/verisure/translations/el.json new file mode 100644 index 00000000000000..87313dba1d4cb7 --- /dev/null +++ b/homeassistant/components/verisure/translations/el.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "data": { + "email": "\u0397\u03bb\u03b5\u03ba\u03c4\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03a4\u03b1\u03c7\u03c5\u03b4\u03c1\u03bf\u03bc\u03b5\u03af\u03bf", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/translations/el.json b/homeassistant/components/water_heater/translations/el.json new file mode 100644 index 00000000000000..827a171a7acf2f --- /dev/null +++ b/homeassistant/components/water_heater/translations/el.json @@ -0,0 +1,13 @@ +{ + "state": { + "_": { + "eco": "\u039f\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03ba\u03cc", + "electric": "\u0397\u03bb\u03b5\u03ba\u03c4\u03c1\u03b9\u03ba\u03cc", + "gas": "\u0391\u03ad\u03c1\u03b9\u03bf", + "heat_pump": "\u0391\u03bd\u03c4\u03bb\u03af\u03b1 \u0398\u03b5\u03c1\u03bc\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2", + "high_demand": "\u03a5\u03c8\u03b7\u03bb\u03ae \u0396\u03ae\u03c4\u03b7\u03c3\u03b7", + "off": "\u0391\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "performance": "\u0391\u03c0\u03cc\u03b4\u03bf\u03c3\u03b7" + } + } +} \ No newline at end of file From cd455e296e331af0212344563d95ded2a537b877 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 23 Mar 2021 14:30:45 +0800 Subject: [PATCH 1457/1818] Remove login details before logging stream source (#45398) * Remove login details before logging stream source * Convert to str before re * Use compiled RE * Add tests and filter log message in worker * Update import Co-authored-by: Erik Montnemery * isort Co-authored-by: Erik Montnemery --- homeassistant/components/stream/__init__.py | 11 +++++++++-- homeassistant/components/stream/worker.py | 5 ++++- tests/components/stream/test_recorder.py | 10 ++++++++++ tests/components/stream/test_worker.py | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 184ed1f2719c67..0226bb82f6db1d 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -15,6 +15,7 @@ to always keep workers active. """ import logging +import re import secrets import threading import time @@ -38,6 +39,8 @@ _LOGGER = logging.getLogger(__name__) +STREAM_SOURCE_RE = re.compile("//(.*):(.*)@") + def create_stream(hass, stream_source, options=None): """Create a stream with the specified identfier based on the source url. @@ -173,7 +176,9 @@ def start(self): target=self._run_worker, ) self._thread.start() - _LOGGER.info("Started stream: %s", self.source) + _LOGGER.info( + "Started stream: %s", STREAM_SOURCE_RE.sub("//", str(self.source)) + ) def update_source(self, new_source): """Restart the stream with a new stream source.""" @@ -239,7 +244,9 @@ def _stop(self): self._thread_quit.set() self._thread.join() self._thread = None - _LOGGER.info("Stopped stream: %s", self.source) + _LOGGER.info( + "Stopped stream: %s", STREAM_SOURCE_RE.sub("//", str(self.source)) + ) async def async_record(self, video_path, duration=30, lookback=5): """Make a .mp4 recording from a provided stream.""" diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 8d1df37d0395e3..5a12935698337f 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -5,6 +5,7 @@ import av +from . import STREAM_SOURCE_RE from .const import ( AUDIO_CODECS, MAX_MISSING_DTS, @@ -127,7 +128,9 @@ def stream_worker(source, options, segment_buffer, quit_event): try: container = av.open(source, options=options, timeout=STREAM_TIMEOUT) except av.AVError: - _LOGGER.error("Error opening stream %s", source) + _LOGGER.error( + "Error opening stream %s", STREAM_SOURCE_RE.sub("//", str(source)) + ) return try: video_stream = container.streams.video[0] diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 2b44c16243b83b..564da4b108e118 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -257,3 +257,13 @@ async def test_record_stream_audio( # Verify that the save worker was invoked, then block until its # thread completes and is shutdown completely to avoid thread leaks. await record_worker_sync.join() + + +async def test_recorder_log(hass, caplog): + """Test starting a stream to record logs the url without username and password.""" + await async_setup_component(hass, "stream", {"stream": {}}) + stream = create_stream(hass, "https://abcd:efgh@foo.bar") + with patch.object(hass.config, "is_allowed_path", return_value=True): + await stream.async_record("/example/path") + assert "https://abcd:efgh@foo.bar" not in caplog.text + assert "https://foo.bar" in caplog.text diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index bef5d366a8fd95..cf72a90168b6ff 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -574,3 +574,18 @@ def blocking_open(stream_source, *args, **kwargs): # Ccleanup stream.stop() + + +async def test_worker_log(hass, caplog): + """Test that the worker logs the url without username and password.""" + stream = Stream(hass, "https://abcd:efgh@foo.bar") + stream.add_provider(STREAM_OUTPUT_FORMAT) + with patch("av.open") as av_open: + av_open.side_effect = av.error.InvalidDataError(-2, "error") + segment_buffer = SegmentBuffer(stream.outputs) + stream_worker( + "https://abcd:efgh@foo.bar", {}, segment_buffer, threading.Event() + ) + await hass.async_block_till_done() + assert "https://abcd:efgh@foo.bar" not in caplog.text + assert "https://foo.bar" in caplog.text From fb03d79daff54b51f639a04586e4ef38bfa1fe8e Mon Sep 17 00:00:00 2001 From: Dewet Diener Date: Tue, 23 Mar 2021 08:18:48 +0000 Subject: [PATCH 1458/1818] Bump nanoleaf to 0.1.0, add unique IDs (#48135) * bump pynanoleaf and expose model/serial as unique_id * addressed PR feedback --- homeassistant/components/nanoleaf/light.py | 6 ++++++ homeassistant/components/nanoleaf/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index ed1e4877a31b13..02d8f4751ef871 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -108,6 +108,7 @@ class NanoleafLight(LightEntity): def __init__(self, light, name): """Initialize an Nanoleaf light.""" + self._unique_id = light.serialNo self._available = True self._brightness = None self._color_temp = None @@ -157,6 +158,11 @@ def max_mireds(self): """Return the warmest color_temp that this light supports.""" return 833 + @property + def unique_id(self): + """Return a unique ID.""" + return self._unique_id + @property def name(self): """Return the display name of this light.""" diff --git a/homeassistant/components/nanoleaf/manifest.json b/homeassistant/components/nanoleaf/manifest.json index 6d953335a34644..1f0fbf80983c62 100644 --- a/homeassistant/components/nanoleaf/manifest.json +++ b/homeassistant/components/nanoleaf/manifest.json @@ -2,6 +2,6 @@ "domain": "nanoleaf", "name": "Nanoleaf", "documentation": "https://www.home-assistant.io/integrations/nanoleaf", - "requirements": ["pynanoleaf==0.0.5"], + "requirements": ["pynanoleaf==0.1.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 9c3251c9681b59..a9ba0da56f54ac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1551,7 +1551,7 @@ pymyq==3.0.4 pymysensors==0.21.0 # homeassistant.components.nanoleaf -pynanoleaf==0.0.5 +pynanoleaf==0.1.0 # homeassistant.components.nello pynello==2.0.3 From 8900b38c7f9e9e2f18a0be03686da4e1494a526b Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 23 Mar 2021 04:24:42 -0400 Subject: [PATCH 1459/1818] Add Blink config migration (#46671) --- homeassistant/components/blink/__init__.py | 6 +++++- homeassistant/components/blink/config_flow.py | 2 +- tests/components/blink/test_config_flow.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index c9c03dc654db28..1cef331c0bb223 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -67,11 +67,15 @@ async def async_setup(hass, config): async def async_migrate_entry(hass, entry): """Handle migration of a previous version config entry.""" + _LOGGER.debug("Migrating from version %s", entry.version) data = {**entry.data} if entry.version == 1: data.pop("login_response", None) await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) return False + if entry.version == 2: + await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) + return False return True @@ -148,7 +152,7 @@ async def async_unload_entry(hass, entry): return True hass.services.async_remove(DOMAIN, SERVICE_REFRESH) - hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO_SCHEMA) + hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO) hass.services.async_remove(DOMAIN, SERVICE_SEND_PIN) return True diff --git a/homeassistant/components/blink/config_flow.py b/homeassistant/components/blink/config_flow.py index 5c77add31185e4..c6c3f0b27bee63 100644 --- a/homeassistant/components/blink/config_flow.py +++ b/homeassistant/components/blink/config_flow.py @@ -44,7 +44,7 @@ def _send_blink_2fa_pin(auth, pin): class BlinkConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a Blink config flow.""" - VERSION = 2 + VERSION = 3 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL def __init__(self): diff --git a/tests/components/blink/test_config_flow.py b/tests/components/blink/test_config_flow.py index 91264997769827..f7dc89b4cbb6ae 100644 --- a/tests/components/blink/test_config_flow.py +++ b/tests/components/blink/test_config_flow.py @@ -273,7 +273,7 @@ async def test_options_flow(hass): data={"username": "blink@example.com", "password": "example"}, options={}, entry_id=1, - version=2, + version=3, ) config_entry.add_to_hass(hass) From 95370ac84baa7fb1a0494649c1d51daeaa4690ae Mon Sep 17 00:00:00 2001 From: David Keijser Date: Tue, 23 Mar 2021 10:28:19 +0100 Subject: [PATCH 1460/1818] Change nanoleaf name to configured name instead of hostname (#46407) * nanoleaf: Key config by device id Rather than host which is not stable * nanoleaf: Use pretty name instead of hostname --- homeassistant/components/nanoleaf/light.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 02d8f4751ef871..a6f453ce2aa17e 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -62,14 +62,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): token = "" if discovery_info is not None: host = discovery_info["host"] - name = discovery_info["hostname"] + name = None + device_id = discovery_info["properties"]["id"] + # if device already exists via config, skip discovery setup if host in hass.data[DATA_NANOLEAF]: return _LOGGER.info("Discovered a new Nanoleaf: %s", discovery_info) conf = load_json(hass.config.path(CONFIG_FILE)) - if conf.get(host, {}).get("token"): - token = conf[host]["token"] + if host in conf and device_id not in conf: + conf[device_id] = conf.pop(host) + save_json(hass.config.path(CONFIG_FILE), conf) + token = conf.get(device_id, {}).get("token", "") else: host = config[CONF_HOST] name = config[CONF_NAME] @@ -94,11 +98,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): nanoleaf_light.token = token try: - nanoleaf_light.available + info = nanoleaf_light.info except Unavailable: _LOGGER.error("Could not connect to Nanoleaf Light: %s on %s", name, host) return + if name is None: + name = info.name + hass.data[DATA_NANOLEAF][host] = nanoleaf_light add_entities([NanoleafLight(nanoleaf_light, name)], True) From 6e07279257f4d29adbd85cd4e8c11806da47fafd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Mar 2021 00:59:04 -1000 Subject: [PATCH 1461/1818] Add august doorbells to dhcp discovery (#48244) --- homeassistant/components/august/manifest.json | 3 ++- homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index feadf8f62180b8..4edacbbf64a1d2 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -6,7 +6,8 @@ "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, - {"hostname":"connect","macaddress":"B8B7F1*"} + {"hostname":"connect","macaddress":"B8B7F1*"}, + {"hostname":"august*","macaddress":"E076D0*"} ], "config_flow": true } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index b5d419662ffb70..c9460a67717db3 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -16,6 +16,11 @@ "hostname": "connect", "macaddress": "B8B7F1*" }, + { + "domain": "august", + "hostname": "august*", + "macaddress": "E076D0*" + }, { "domain": "axis", "hostname": "axis-00408c*", From 7bd876beaf41646fd400c60b0bd0d92f373ecb5e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Mar 2021 01:00:06 -1000 Subject: [PATCH 1462/1818] Add dhcp discovery support to blink (#48243) --- homeassistant/components/blink/manifest.json | 1 + homeassistant/generated/dhcp.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index 1c91f1a2295a26..c88e13cdde717f 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/blink", "requirements": ["blinkpy==0.17.0"], "codeowners": ["@fronzbot"], + "dhcp": [{"hostname":"blink*","macaddress":"B85F98*"}], "config_flow": true } diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index c9460a67717db3..90d846377c6957 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -36,6 +36,11 @@ "hostname": "axis-b8a44f*", "macaddress": "B8A44F*" }, + { + "domain": "blink", + "hostname": "blink*", + "macaddress": "B85F98*" + }, { "domain": "flume", "hostname": "flume-gw-*", From e0e349584942386a26a8c44ff14a7a9781b3d54f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Mar 2021 12:18:03 +0100 Subject: [PATCH 1463/1818] Upgrade pylast to 4.2.0 (#48245) --- homeassistant/components/lastfm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lastfm/manifest.json b/homeassistant/components/lastfm/manifest.json index 9fe3a182844be1..e732b5d7000937 100644 --- a/homeassistant/components/lastfm/manifest.json +++ b/homeassistant/components/lastfm/manifest.json @@ -2,6 +2,6 @@ "domain": "lastfm", "name": "Last.fm", "documentation": "https://www.home-assistant.io/integrations/lastfm", - "requirements": ["pylast==4.1.0"], + "requirements": ["pylast==4.2.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index a9ba0da56f54ac..48ac1318b0468a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1482,7 +1482,7 @@ pykwb==0.0.8 pylacrosse==0.4 # homeassistant.components.lastfm -pylast==4.1.0 +pylast==4.2.0 # homeassistant.components.launch_library pylaunches==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 53f7b69d52d2f0..7c3297de9f1441 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -781,7 +781,7 @@ pykodi==0.2.1 pykulersky==0.5.2 # homeassistant.components.lastfm -pylast==4.1.0 +pylast==4.2.0 # homeassistant.components.forked_daapd pylibrespot-java==0.1.0 From 1095d93892611b40ab8daeded5a7f37b1507484f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 23 Mar 2021 13:49:44 +0100 Subject: [PATCH 1464/1818] Increase test coverage of deCONZ device triggers (#48126) * Increase test coverage of deCONZ device triggers * Revert removed new line * Found a way to explicitly assert that exceptions are raised * Remove unnecessary block till done * Fix unnecessary elif * Fix review comments * Remove helper tests --- .../components/deconz/device_trigger.py | 23 +- .../components/deconz/test_device_trigger.py | 322 ++++++++++++++++-- 2 files changed, 299 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index 5ee0a00f04fb8e..e8e43d384b14c5 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -16,7 +16,6 @@ ) from . import DOMAIN -from .const import LOGGER from .deconz_event import CONF_DECONZ_EVENT, CONF_GESTURE CONF_SUBTYPE = "subtype" @@ -414,16 +413,13 @@ async def async_validate_trigger_config(hass, config): trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) - if ( - not device - or device.model not in REMOTES - or trigger not in REMOTES[device.model] - ): - if not device: - raise InvalidDeviceAutomationConfig( - f"deCONZ trigger {trigger} device with id " - f"{config[CONF_DEVICE_ID]} not found" - ) + if not device: + raise InvalidDeviceAutomationConfig( + f"deCONZ trigger {trigger} device with ID " + f"{config[CONF_DEVICE_ID]} not found" + ) + + if device.model not in REMOTES or trigger not in REMOTES[device.model]: raise InvalidDeviceAutomationConfig( f"deCONZ trigger {trigger} is not valid for device " f"{device} ({config[CONF_DEVICE_ID]})" @@ -443,8 +439,9 @@ async def async_attach_trigger(hass, config, action, automation_info): deconz_event = _get_deconz_event_from_device_id(hass, device.id) if deconz_event is None: - LOGGER.error("No deconz_event tied to device %s found", device.name) - raise InvalidDeviceAutomationConfig + raise InvalidDeviceAutomationConfig( + f'No deconz_event tied to device "{device.name}" found' + ) event_id = deconz_event.serial diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 7a39b6fe48f125..640a4b61ce31e5 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -1,7 +1,10 @@ """deCONZ device automation tests.""" -from unittest.mock import patch +from unittest.mock import Mock, patch +import pytest + +from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN from homeassistant.components.deconz import device_trigger from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.device_trigger import CONF_SUBTYPE @@ -14,33 +17,23 @@ CONF_PLATFORM, CONF_TYPE, ) +from homeassistant.helpers.trigger import async_initialize_triggers +from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration -from tests.common import assert_lists_same, async_get_device_automations +from tests.common import ( + assert_lists_same, + async_get_device_automations, + async_mock_service, +) from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 -SENSORS = { - "1": { - "config": { - "alert": "none", - "battery": 60, - "group": "10", - "on": True, - "reachable": True, - }, - "ep": 1, - "etag": "1b355c0b6d2af28febd7ca9165881952", - "manufacturername": "IKEA of Sweden", - "mode": 1, - "modelid": "TRADFRI on/off switch", - "name": "TRÅDFRI on/off switch ", - "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, - "swversion": "1.4.018", - "type": "ZHASwitch", - "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", - } -} + +@pytest.fixture +def automation_calls(hass): + """Track automation calls to a mock service.""" + return async_mock_service(hass, "test", "automation") async def test_get_triggers(hass, aioclient_mock): @@ -76,8 +69,6 @@ async def test_get_triggers(hass, aioclient_mock): identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} ) - assert device_trigger._get_deconz_event_from_device_id(hass, device.id) - triggers = await async_get_device_automations(hass, "trigger", device.id) expected_triggers = [ @@ -135,14 +126,279 @@ async def test_get_triggers(hass, aioclient_mock): assert_lists_same(triggers, expected_triggers) -async def test_helper_no_match(hass, aioclient_mock): - """Verify trigger helper returns None when no event could be matched.""" +async def test_get_triggers_manage_unsupported_remotes(hass, aioclient_mock): + """Verify no triggers for an unsupported remote.""" + data = { + "sensors": { + "1": { + "config": { + "alert": "none", + "group": "10", + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1b355c0b6d2af28febd7ca9165881952", + "manufacturername": "IKEA of Sweden", + "mode": 1, + "modelid": "Unsupported model", + "name": "TRÅDFRI on/off switch ", + "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, + "swversion": "1.4.018", + "type": "ZHASwitch", + "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} + ) + + triggers = await async_get_device_automations(hass, "trigger", device.id) + + expected_triggers = [] + + assert_lists_same(triggers, expected_triggers) + + +async def test_functional_device_trigger( + hass, aioclient_mock, mock_deconz_websocket, automation_calls +): + """Test proper matching and attachment of device trigger automation.""" + await async_setup_component(hass, "persistent_notification", {}) + + data = { + "sensors": { + "1": { + "config": { + "alert": "none", + "battery": 60, + "group": "10", + "on": True, + "reachable": True, + }, + "ep": 1, + "etag": "1b355c0b6d2af28febd7ca9165881952", + "manufacturername": "IKEA of Sweden", + "mode": 1, + "modelid": "TRADFRI on/off switch", + "name": "TRÅDFRI on/off switch ", + "state": {"buttonevent": 2002, "lastupdated": "2019-09-07T07:39:39"}, + "swversion": "1.4.018", + "type": "ZHASwitch", + "uniqueid": "d0:cf:5e:ff:fe:71:a4:3a-01-1000", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")} + ) + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 1 + + event_changed_sensor = { + "t": "event", + "e": "changed", + "r": "sensors", + "id": "1", + "state": {"buttonevent": 1002}, + } + await mock_deconz_websocket(data=event_changed_sensor) + await hass.async_block_till_done() + + assert len(automation_calls) == 1 + assert automation_calls[0].data["some"] == "test_trigger_button_press" + + +async def test_validate_trigger_unknown_device( + hass, aioclient_mock, mock_deconz_websocket +): + """Test unknown device does not return a trigger config.""" await setup_deconz_integration(hass, aioclient_mock) - deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert not deconz_event + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: "unknown device", + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0 + + +async def test_validate_trigger_unsupported_device( + hass, aioclient_mock, mock_deconz_websocket +): + """Test unsupported device doesn't return a trigger config.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")}, + model="unsupported", + ) -async def test_helper_no_gateway_exist(hass): - """Verify trigger helper returns None when no gateway exist.""" - deconz_event = device_trigger._get_deconz_event_from_device_id(hass, "mock-id") - assert not deconz_event + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0 + + +async def test_validate_trigger_unsupported_trigger( + hass, aioclient_mock, mock_deconz_websocket +): + """Test unsupported trigger does not return a trigger config.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")}, + model="TRADFRI on/off switch", + ) + + trigger_config = { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: "unsupported", + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + } + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": trigger_config, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0 + + +async def test_attach_trigger_no_matching_event( + hass, aioclient_mock, mock_deconz_websocket +): + """Test no matching event for device doesn't return a trigger config.""" + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DECONZ_DOMAIN, "d0:cf:5e:ff:fe:71:a4:3a")}, + name="Tradfri switch", + model="TRADFRI on/off switch", + ) + + trigger_config = { + CONF_PLATFORM: "device", + CONF_DOMAIN: DECONZ_DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: device_trigger.CONF_SHORT_PRESS, + CONF_SUBTYPE: device_trigger.CONF_TURN_ON, + } + + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "trigger": trigger_config, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_button_press"}, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 1 + + # Assert that deCONZ async_attach_trigger raises InvalidDeviceAutomationConfig + assert not await async_initialize_triggers( + hass, + [trigger_config], + action=Mock(), + domain=AUTOMATION_DOMAIN, + name="mock-name", + log_cb=Mock(), + ) From 9656f260a4fa12ae5deab2d1dbf7f68cc59df89d Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Tue, 23 Mar 2021 14:28:35 +0100 Subject: [PATCH 1465/1818] Add tests for Netatmo (#46372) Co-authored-by: Erik Montnemery Co-authored-by: Martin Hjelmare --- .coveragerc | 3 - tests/components/netatmo/test_api.py | 14 -- tests/components/netatmo/test_init.py | 215 ++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 17 deletions(-) delete mode 100644 tests/components/netatmo/test_api.py create mode 100644 tests/components/netatmo/test_init.py diff --git a/.coveragerc b/.coveragerc index 48f71f5a9988c5..972831c4bd4f68 100644 --- a/.coveragerc +++ b/.coveragerc @@ -643,10 +643,7 @@ omit = homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* - homeassistant/components/netatmo/__init__.py homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/helper.py - homeassistant/components/netatmo/netatmo_entity_base.py homeassistant/components/netatmo/sensor.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py diff --git a/tests/components/netatmo/test_api.py b/tests/components/netatmo/test_api.py deleted file mode 100644 index 76d16d10515aa3..00000000000000 --- a/tests/components/netatmo/test_api.py +++ /dev/null @@ -1,14 +0,0 @@ -"""The tests for the Netatmo oauth2 api.""" -from unittest.mock import patch - -from homeassistant.components.netatmo import api - - -async def test_api(hass, config_entry): - """Test auth instantiation.""" - with patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as fake_implementation: - auth = api.ConfigEntryNetatmoAuth(hass, config_entry, fake_implementation) - - assert isinstance(auth, api.ConfigEntryNetatmoAuth) diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py new file mode 100644 index 00000000000000..49e3ab14684476 --- /dev/null +++ b/tests/components/netatmo/test_init.py @@ -0,0 +1,215 @@ +"""The tests for Netatmo component.""" +from time import time +from unittest.mock import patch + +from homeassistant import config_entries +from homeassistant.components.netatmo import DOMAIN +from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.setup import async_setup_component + +from .common import fake_post_request, simulate_webhook + +from tests.common import MockConfigEntry +from tests.components.cloud import mock_cloud + +# Fake webhook thermostat mode change to "Max" +FAKE_WEBHOOK = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2746182631", + "name": "Livingroom", + "type": "livingroom", + "therm_setpoint_mode": "max", + "therm_setpoint_end_time": 1612749189, + } + ], + "modules": [ + {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} + ], + }, + "mode": "max", + "event_type": "set_point", + "push_type": "display_change", +} + +FAKE_WEBHOOK_ACTIVATION = { + "push_type": "webhook_activation", +} + + +async def test_setup_component(hass): + """Test setup and teardown of the netatmo component.""" + config_entry = MockConfigEntry( + domain="netatmo", + data={ + "auth_implementation": "cloud", + "token": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + "expires_at": time() + 1000, + "scope": "read_station", + }, + }, + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook: + mock_auth.return_value.post_request.side_effect = fake_post_request + assert await async_setup_component(hass, "netatmo", {}) + + await hass.async_block_till_done() + + mock_auth.assert_called_once() + mock_impl.assert_called_once() + mock_webhook.assert_called_once() + + assert config_entry.state == config_entries.ENTRY_STATE_LOADED + assert hass.config_entries.async_entries(DOMAIN) + assert len(hass.states.async_all()) > 0 + + for config_entry in hass.config_entries.async_entries("netatmo"): + await hass.config_entries.async_remove(config_entry.entry_id) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + assert not hass.config_entries.async_entries(DOMAIN) + + +async def test_setup_component_with_config(hass, config_entry): + """Test setup of the netatmo component with dev account.""" + with patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook, patch( + "pyatmo.auth.NetatmoOAuth2.post_request" + ) as fake_post_requests, patch( + "homeassistant.components.netatmo.PLATFORMS", ["sensor"] + ): + assert await async_setup_component( + hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} + ) + + await hass.async_block_till_done() + + fake_post_requests.assert_called() + mock_impl.assert_called_once() + mock_webhook.assert_called_once() + + assert config_entry.state == config_entries.ENTRY_STATE_LOADED + assert hass.config_entries.async_entries(DOMAIN) + assert len(hass.states.async_all()) > 0 + + +async def test_setup_component_with_webhook(hass, entry): + """Test setup and teardown of the netatmo component with webhook registration.""" + webhook_id = entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) + + assert len(hass.states.async_all()) > 0 + + webhook_id = entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) + + # Assert webhook is established successfully + climate_entity_livingroom = "climate.netatmo_livingroom" + assert hass.states.get(climate_entity_livingroom).state == "auto" + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK) + assert hass.states.get(climate_entity_livingroom).state == "heat" + + for config_entry in hass.config_entries.async_entries("netatmo"): + await hass.config_entries.async_remove(config_entry.entry_id) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + + +async def test_setup_without_https(hass, config_entry): + """Test if set up with cloud link and without https.""" + hass.config.components.add("cloud") + with patch( + "homeassistant.helpers.network.get_url", + return_value="https://example.nabu.casa", + ), patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook: + mock_auth.return_value.post_request.side_effect = fake_post_request + mock_webhook.return_value = "http://example.com" + assert await async_setup_component( + hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} + ) + + await hass.async_block_till_done() + + webhook_id = config_entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) + + # Assert webhook is established successfully + climate_entity_livingroom = "climate.netatmo_livingroom" + assert hass.states.get(climate_entity_livingroom).state == "auto" + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK) + assert hass.states.get(climate_entity_livingroom).state == "auto" + + +async def test_setup_with_cloud(hass, config_entry): + """Test if set up with active cloud subscription.""" + await mock_cloud(hass) + await hass.async_block_till_done() + + with patch( + "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ) as fake_create_cloudhook, patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ) as fake_delete_cloudhook, patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth" + ) as mock_auth, patch( + "homeassistant.components.netatmo.PLATFORMS", [] + ), patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ): + mock_auth.return_value.post_request.side_effect = fake_post_request + assert await async_setup_component( + hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} + ) + assert hass.components.cloud.async_active_subscription() is True + fake_create_cloudhook.assert_called_once() + + assert ( + hass.config_entries.async_entries("netatmo")[0].data["cloudhook_url"] + == "https://hooks.nabu.casa/ABCD" + ) + + await hass.async_block_till_done() + assert hass.config_entries.async_entries(DOMAIN) + + for config_entry in hass.config_entries.async_entries("netatmo"): + await hass.config_entries.async_remove(config_entry.entry_id) + fake_delete_cloudhook.assert_called_once() + + await hass.async_block_till_done() + assert not hass.config_entries.async_entries(DOMAIN) From 6932cf9534856e2396bbe470fc7f170c27b4943e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Mar 2021 14:36:43 +0100 Subject: [PATCH 1466/1818] Use contextlib.suppress where possible (#48189) --- .../components/acmeda/config_flow.py | 12 +++---- homeassistant/components/amcrest/__init__.py | 7 ++-- .../components/amcrest/binary_sensor.py | 7 ++-- homeassistant/components/api/__init__.py | 9 ++--- homeassistant/components/apns/notify.py | 5 ++- .../components/arcam_fmj/__init__.py | 5 ++- homeassistant/components/automation/config.py | 6 ++-- homeassistant/components/bizkaibus/sensor.py | 6 ++-- homeassistant/components/bme280/sensor.py | 5 ++- homeassistant/components/broadlink/device.py | 5 ++- homeassistant/components/cast/media_player.py | 18 ++++------ .../components/cloud/alexa_config.py | 5 ++- .../components/configurator/__init__.py | 11 ++---- .../components/denonavr/media_player.py | 5 ++- homeassistant/components/dht/sensor.py | 5 ++- homeassistant/components/dsmr/__init__.py | 5 ++- homeassistant/components/dsmr/sensor.py | 5 ++- .../components/dublin_bus_transport/sensor.py | 5 ++- .../components/emulated_hue/__init__.py | 5 ++- homeassistant/components/fibaro/sensor.py | 10 +++--- .../components/forked_daapd/config_flow.py | 5 ++- .../components/fritzbox_callmonitor/base.py | 9 ++--- homeassistant/components/graphite/__init__.py | 5 ++- .../components/hangouts/hangouts_bot.py | 5 ++- homeassistant/components/html5/notify.py | 9 ++--- homeassistant/components/http/ban.py | 5 ++- .../components/huawei_lte/__init__.py | 5 ++- .../hunterdouglas_powerview/cover.py | 7 ++-- homeassistant/components/hyperion/__init__.py | 5 ++- .../components/hyperion/config_flow.py | 5 ++- homeassistant/components/influxdb/__init__.py | 14 +++----- homeassistant/components/insteon/__init__.py | 5 ++- homeassistant/components/izone/config_flow.py | 5 ++- .../components/keyboard_remote/__init__.py | 5 ++- homeassistant/components/kodi/browse_media.py | 5 ++- homeassistant/components/logbook/__init__.py | 5 ++- .../components/media_player/__init__.py | 20 +++++------ .../minecraft_server/config_flow.py | 5 ++- .../components/mobile_app/__init__.py | 9 ++--- .../components/mobile_app/http_api.py | 5 ++- .../components/mobile_app/webhook.py | 5 ++- .../components/motion_blinds/__init__.py | 11 ++---- homeassistant/components/mpd/media_player.py | 5 ++- .../components/mqtt/light/schema_json.py | 5 ++- homeassistant/components/mqtt/trigger.py | 5 ++- .../components/mysensors/config_flow.py | 6 ++-- homeassistant/components/onvif/device.py | 13 +++---- homeassistant/components/onvif/event.py | 10 +++--- homeassistant/components/ozw/__init__.py | 5 ++- .../components/ping/binary_sensor.py | 5 ++- homeassistant/components/plant/__init__.py | 5 ++- homeassistant/components/ps4/media_player.py | 5 ++- homeassistant/components/rachio/switch.py | 5 ++- .../components/rejseplanen/sensor.py | 5 ++- homeassistant/components/sharkiq/__init__.py | 16 ++++----- .../components/shell_command/__init__.py | 5 ++- .../components/solaredge_local/sensor.py | 9 ++--- .../components/sonos/media_browser.py | 9 ++--- .../components/sonos/media_player.py | 5 ++- homeassistant/components/spaceapi/__init__.py | 34 ++++++------------- .../components/swisscom/device_tracker.py | 5 ++- homeassistant/components/ted5000/sensor.py | 5 ++- homeassistant/components/tplink/switch.py | 6 ++-- .../components/transmission/sensor.py | 6 ++-- homeassistant/components/upb/config_flow.py | 8 ++--- homeassistant/components/verisure/__init__.py | 5 ++- homeassistant/components/webostv/__init__.py | 7 ++-- .../components/webostv/media_player.py | 7 ++-- homeassistant/components/wink/sensor.py | 9 ++--- homeassistant/components/xbox/__init__.py | 5 ++- homeassistant/components/xbox/media_source.py | 6 ++-- homeassistant/components/zabbix/__init__.py | 6 ++-- homeassistant/components/zeroconf/__init__.py | 13 +++---- homeassistant/components/zeroconf/usage.py | 5 ++- .../components/zha/core/channels/lighting.py | 5 ++- homeassistant/helpers/aiohttp_client.py | 11 +++--- homeassistant/helpers/network.py | 17 +++------- homeassistant/helpers/script.py | 6 ++-- homeassistant/helpers/storage.py | 5 ++- homeassistant/helpers/template.py | 5 ++- homeassistant/loader.py | 5 ++- homeassistant/util/dt.py | 6 ++-- homeassistant/util/ruamel_yaml.py | 5 ++- tests/components/buienradar/test_camera.py | 5 ++- tests/components/demo/test_init.py | 5 ++- tests/util/test_timeout.py | 21 ++++-------- 86 files changed, 238 insertions(+), 398 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index b8913e31f2adb7..a849e49ddf4964 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import aiopulse import async_timeout @@ -37,13 +38,10 @@ async def async_step_user(self, user_input=None): } hubs = [] - try: - with async_timeout.timeout(5): - async for hub in aiopulse.Hub.discover(): - if hub.id not in already_configured: - hubs.append(hub) - except asyncio.TimeoutError: - pass + with suppress(asyncio.TimeoutError), async_timeout.timeout(5): + async for hub in aiopulse.Hub.discover(): + if hub.id not in already_configured: + hubs.append(hub) if len(hubs) == 0: return self.async_abort(reason="no_devices_found") diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index 3baad1ac88e1d6..71c277e578ca37 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -1,4 +1,5 @@ """Support for Amcrest IP cameras.""" +from contextlib import suppress from datetime import timedelta import logging import threading @@ -191,10 +192,8 @@ def command(self, *args, **kwargs): def _wrap_test_online(self, now): """Test if camera is back online.""" _LOGGER.debug("Testing if %s back online", self._wrap_name) - try: - self.current_time - except AmcrestError: - pass + with suppress(AmcrestError): + self.current_time # pylint: disable=pointless-statement def _monitor_events(hass, name, api, event_codes): diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index 0824021f31e82b..0add382b81f0d0 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Amcrest IP camera binary sensors.""" +from contextlib import suppress from datetime import timedelta import logging @@ -154,10 +155,8 @@ def _update_online(self): # Send a command to the camera to test if we can still communicate with it. # Override of Http.command() in __init__.py will set self._api.available # accordingly. - try: - self._api.current_time - except AmcrestError: - pass + with suppress(AmcrestError): + self._api.current_time # pylint: disable=pointless-statement self._state = self._api.available def _update_others(self): diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 47c6518f7b118e..a91d85402866c4 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -1,5 +1,6 @@ """Rest API for Home Assistant.""" import asyncio +from contextlib import suppress import json import logging @@ -196,15 +197,11 @@ async def get(self, request): ATTR_VERSION: __version__, } - try: + with suppress(NoURLAvailableError): data["external_url"] = get_url(hass, allow_internal=False) - except NoURLAvailableError: - pass - try: + with suppress(NoURLAvailableError): data["internal_url"] = get_url(hass, allow_external=False) - except NoURLAvailableError: - pass # Set old base URL based on external or internal data["base_url"] = data["external_url"] or data["internal_url"] diff --git a/homeassistant/components/apns/notify.py b/homeassistant/components/apns/notify.py index 6f8de7e9c847fa..c9e12a2086359a 100644 --- a/homeassistant/components/apns/notify.py +++ b/homeassistant/components/apns/notify.py @@ -1,4 +1,5 @@ """APNS Notification platform.""" +from contextlib import suppress import logging from apns2.client import APNsClient @@ -155,7 +156,7 @@ def __init__(self, hass, app_name, topic, sandbox, cert_file): self.device_states = {} self.topic = topic - try: + with suppress(FileNotFoundError): self.devices = { str(key): ApnsDevice( str(key), @@ -165,8 +166,6 @@ def __init__(self, hass, app_name, topic, sandbox, cert_file): ) for (key, value) in load_yaml_config_file(self.yaml_path).items() } - except FileNotFoundError: - pass tracking_ids = [ device.full_tracking_device_id diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index 686e7c2de16d48..fe62c41c061c17 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -1,5 +1,6 @@ """Arcam component.""" import asyncio +from contextlib import suppress import logging from arcam.fmj import ConnectionFailed @@ -28,10 +29,8 @@ async def _await_cancel(task): task.cancel() - try: + with suppress(asyncio.CancelledError): await task - except asyncio.CancelledError: - pass async def async_setup(hass: HomeAssistantType, config: ConfigType): diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 5abff8fe9744e2..d8837fe03c74b7 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -1,5 +1,6 @@ """Config validation helper for the automation integration.""" import asyncio +from contextlib import suppress import voluptuous as vol @@ -88,11 +89,8 @@ class AutomationConfig(dict): async def _try_async_validate_config_item(hass, config, full_config=None): """Validate config item.""" raw_config = None - try: + with suppress(ValueError): raw_config = dict(config) - except ValueError: - # Invalid config - pass try: config = await async_validate_config_item(hass, config, full_config) diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index a905498328b030..d0cade31a72402 100644 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -1,4 +1,6 @@ """Support for Bizkaibus, Biscay (Basque Country, Spain) Bus service.""" +from contextlib import suppress + from bizkaibus.bizkaibus import BizkaibusData import voluptuous as vol @@ -61,10 +63,8 @@ def unit_of_measurement(self): def update(self): """Get the latest data from the webservice.""" self.data.update() - try: + with suppress(TypeError): self._state = self.data.info[0][ATTR_DUE_IN] - except TypeError: - pass class Bizkaibus: diff --git a/homeassistant/components/bme280/sensor.py b/homeassistant/components/bme280/sensor.py index ffe6847934195f..2c3ab0303b05b8 100644 --- a/homeassistant/components/bme280/sensor.py +++ b/homeassistant/components/bme280/sensor.py @@ -1,4 +1,5 @@ """Support for BME280 temperature, humidity and pressure sensor.""" +from contextlib import suppress from datetime import timedelta from functools import partial import logging @@ -110,13 +111,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= sensor_handler = await hass.async_add_executor_job(BME280Handler, sensor) dev = [] - try: + with suppress(KeyError): for variable in config[CONF_MONITORED_CONDITIONS]: dev.append( BME280Sensor(sensor_handler, variable, SENSOR_TYPES[variable][1], name) ) - except KeyError: - pass async_add_entities(dev, True) diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index c460040c12bed6..104118a697ce62 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -1,5 +1,6 @@ """Support for Broadlink devices.""" import asyncio +from contextlib import suppress from functools import partial import logging @@ -102,10 +103,8 @@ async def async_setup(self): self.hass.data[DOMAIN].devices[config.entry_id] = self self.reset_jobs.append(config.add_update_listener(self.async_update)) - try: + with suppress(BroadlinkException, OSError): self.fw_version = await self.hass.async_add_executor_job(api.get_fwversion) - except (BroadlinkException, OSError): - pass # Forward entry setup to related domains. tasks = ( diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 2a1aeaacaa6fa6..9532c25d81a18b 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 contextlib import suppress from datetime import timedelta import functools as ft import json @@ -330,21 +331,14 @@ def new_media_status(self, media_status): tts_base_url = None url_description = "" if "tts" in self.hass.config.components: - try: + with suppress(KeyError): # base_url not configured tts_base_url = self.hass.components.tts.get_base_url(self.hass) - except KeyError: - # base_url not configured, ignore - pass - try: + + with suppress(NoURLAvailableError): # external_url not configured external_url = get_url(self.hass, allow_internal=False) - except NoURLAvailableError: - # external_url not configured, ignore - pass - try: + + with suppress(NoURLAvailableError): # internal_url not configured internal_url = get_url(self.hass, allow_external=False) - except NoURLAvailableError: - # internal_url not configured, ignore - pass if media_status.content_id: if tts_base_url and media_status.content_id.startswith(tts_base_url): diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 2d4714b4c81855..138b2db0b8c756 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -1,5 +1,6 @@ """Alexa configuration for Home Assistant Cloud.""" import asyncio +from contextlib import suppress from datetime import timedelta import logging @@ -322,7 +323,5 @@ async def _handle_entity_registry_updated(self, event): if "old_entity_id" in event.data: to_remove.append(event.data["old_entity_id"]) - try: + with suppress(alexa_errors.NoTokenAvailable): await self._sync_helper(to_update, to_remove) - except alexa_errors.NoTokenAvailable: - pass diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index 9ddd9d76ed9c65..e988e58f76bdfd 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -6,6 +6,7 @@ A callback has to be provided to `request_config` which will be called when the user has submitted configuration information. """ +from contextlib import suppress import functools as ft from homeassistant.const import ( @@ -96,11 +97,8 @@ def request_config(hass, *args, **kwargs): @async_callback def async_notify_errors(hass, request_id, error): """Add errors to a config request.""" - try: + with suppress(KeyError): # If request_id does not exist hass.data[DATA_REQUESTS][request_id].async_notify_errors(request_id, error) - except KeyError: - # If request_id does not exist - pass @bind_hass @@ -115,11 +113,8 @@ def notify_errors(hass, request_id, error): @async_callback def async_request_done(hass, request_id): """Mark a configuration request as done.""" - try: + with suppress(KeyError): # If request_id does not exist hass.data[DATA_REQUESTS].pop(request_id).async_request_done(request_id) - except KeyError: - # If request_id does not exist - pass @bind_hass diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index c1fbb15a2635c1..ea484a10877748 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -1,5 +1,6 @@ """Support for Denon AVR receivers using their HTTP interface.""" +from contextlib import suppress import logging from homeassistant.components.media_player import MediaPlayerEntity @@ -372,11 +373,9 @@ def set_volume_level(self, volume): volume_denon = float((volume * 100) - 80) if volume_denon > 18: volume_denon = float(18) - try: + with suppress(ValueError): if self._receiver.set_volume(volume_denon): self._volume = volume_denon - except ValueError: - pass def mute_volume(self, mute): """Send mute command.""" diff --git a/homeassistant/components/dht/sensor.py b/homeassistant/components/dht/sensor.py index c1fa32e71d119a..602a0f2b76fdc8 100644 --- a/homeassistant/components/dht/sensor.py +++ b/homeassistant/components/dht/sensor.py @@ -1,4 +1,5 @@ """Support for Adafruit DHT temperature and humidity sensor.""" +from contextlib import suppress from datetime import timedelta import logging @@ -74,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = [] name = config[CONF_NAME] - try: + with suppress(KeyError): for variable in config[CONF_MONITORED_CONDITIONS]: dev.append( DHTSensor( @@ -86,8 +87,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): humidity_offset, ) ) - except KeyError: - pass add_entities(dev, True) diff --git a/homeassistant/components/dsmr/__init__.py b/homeassistant/components/dsmr/__init__.py index cda3838bf783bf..09383f8c4aa90b 100644 --- a/homeassistant/components/dsmr/__init__.py +++ b/homeassistant/components/dsmr/__init__.py @@ -1,6 +1,7 @@ """The dsmr component.""" import asyncio from asyncio import CancelledError +from contextlib import suppress from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -36,10 +37,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): # Cancel the reconnect task task.cancel() - try: + with suppress(CancelledError): await task - except CancelledError: - pass unload_ok = all( await asyncio.gather( diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 12304fa51d43ec..d17c3b780e42de 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -3,6 +3,7 @@ import asyncio from asyncio import CancelledError +from contextlib import suppress from datetime import timedelta from functools import partial import logging @@ -342,10 +343,8 @@ def state(self): if self._obis == obis_ref.ELECTRICITY_ACTIVE_TARIFF: return self.translate_tariff(value, self._config[CONF_DSMR_VERSION]) - try: + with suppress(TypeError): value = round(float(value), self._config[CONF_PRECISION]) - except TypeError: - pass if value is not None: return value diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index 909eed09b9aa30..dbe1d10b553600 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -4,6 +4,7 @@ For more info on the API see : https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus-bus-eireann-luas-and-irish-rail/resource/4b9f2c4f-6bf5-4958-a43a-f12dab04cf61 """ +from contextlib import suppress from datetime import datetime, timedelta import requests @@ -117,10 +118,8 @@ def update(self): """Get the latest data from opendata.ch and update the states.""" self.data.update() self._times = self.data.info - try: + with suppress(TypeError): self._state = self._times[0][ATTR_DUE_IN] - except TypeError: - pass class PublicTransportData: diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 11ad80688a30d2..3864a2651f8d43 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -1,4 +1,5 @@ """Support for local control of entities by emulating a Philips Hue bridge.""" +from contextlib import suppress import logging from aiohttp import web @@ -341,8 +342,6 @@ def _is_entity_exposed(self, entity): def _load_json(filename): """Load JSON, handling invalid syntax.""" - try: + with suppress(HomeAssistantError): return load_json(filename) - except HomeAssistantError: - pass return {} diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index fd25a3ce7eba90..3161e173b2a5ae 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,4 +1,6 @@ """Support for Fibaro sensors.""" +from contextlib import suppress + from homeassistant.components.sensor import DOMAIN, SensorEntity from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, @@ -71,7 +73,7 @@ def __init__(self, fibaro_device): self._unit = None self._icon = None self._device_class = None - try: + with suppress(KeyError, ValueError): if not self._unit: if self.fibaro_device.properties.unit == "lux": self._unit = LIGHT_LUX @@ -81,8 +83,6 @@ def __init__(self, fibaro_device): self._unit = TEMP_FAHRENHEIT else: self._unit = self.fibaro_device.properties.unit - except (KeyError, ValueError): - pass @property def state(self): @@ -106,7 +106,5 @@ def device_class(self): def update(self): """Update the state.""" - try: + with suppress(KeyError, ValueError): self.current_value = float(self.fibaro_device.properties.value) - except (KeyError, ValueError): - pass diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index adc6e9b7b35d4b..c731087d12bcb4 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure forked-daapd devices.""" +from contextlib import suppress import logging from pyforked_daapd import ForkedDaapdAPI @@ -161,12 +162,10 @@ async def async_step_zeroconf(self, discovery_info): if discovery_info.get("properties") and discovery_info["properties"].get( "Machine Name" ): - try: + with suppress(ValueError): version_num = int( discovery_info["properties"].get("mtd-version", "0").split(".")[0] ) - except ValueError: - pass if version_num < 27: return self.async_abort(reason="not_forked_daapd") await self.async_set_unique_id(discovery_info["properties"]["Machine Name"]) diff --git a/homeassistant/components/fritzbox_callmonitor/base.py b/homeassistant/components/fritzbox_callmonitor/base.py index ec62e1968550c4..af0612d7632576 100644 --- a/homeassistant/components/fritzbox_callmonitor/base.py +++ b/homeassistant/components/fritzbox_callmonitor/base.py @@ -1,4 +1,5 @@ """Base class for fritzbox_callmonitor entities.""" +from contextlib import suppress from datetime import timedelta import logging import re @@ -69,11 +70,7 @@ def get_name(self, number): return UNKOWN_NAME for prefix in self.prefixes: - try: + with suppress(KeyError): return self.number_dict[prefix + number] - except KeyError: - pass - try: + with suppress(KeyError): return self.number_dict[prefix + number.lstrip("0")] - except KeyError: - pass diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index 327e8293be7ef1..ccb7044ad73b51 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to a Graphite installation.""" +from contextlib import suppress import logging import queue import socket @@ -111,10 +112,8 @@ def _report_attributes(self, entity_id, new_state): """Report the attributes.""" now = time.time() things = dict(new_state.attributes) - try: + with suppress(ValueError): things["state"] = state.state_as_number(new_state) - except ValueError: - pass lines = [ "%s.%s.%s %f %i" % (self._prefix, entity_id, key.replace(" ", "_"), value, now) diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py index 10b983fb03419a..65e3c3923adf25 100644 --- a/homeassistant/components/hangouts/hangouts_bot.py +++ b/homeassistant/components/hangouts/hangouts_bot.py @@ -1,5 +1,6 @@ """The Hangouts Bot.""" import asyncio +from contextlib import suppress import io import logging @@ -103,12 +104,10 @@ def async_update_conversation_commands(self): self._conversation_intents[conv_id][intent_type] = data - try: + with suppress(ValueError): self._conversation_list.on_event.remove_observer( self._async_handle_conversation_event ) - except ValueError: - pass self._conversation_list.on_event.add_observer( self._async_handle_conversation_event ) diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 33dd8118ee49cc..b3d5a081d1b46b 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -1,4 +1,5 @@ """HTML5 Push Messaging notification service.""" +from contextlib import suppress from datetime import datetime, timedelta from functools import partial import json @@ -202,10 +203,8 @@ def websocket_appkey(hass, connection, msg): def _load_config(filename): """Load configuration.""" - try: + with suppress(HomeAssistantError): return load_json(filename) - except HomeAssistantError: - pass return {} @@ -325,10 +324,8 @@ def decode_jwt(self, token): if target_check.get(ATTR_TARGET) in self.registrations: possible_target = self.registrations[target_check[ATTR_TARGET]] key = possible_target[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH] - try: + with suppress(jwt.exceptions.DecodeError): return jwt.decode(token, key, algorithms=["ES256", "HS256"]) - except jwt.exceptions.DecodeError: - pass return self.json_message( "No target found in JWT", status_code=HTTP_UNAUTHORIZED diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 009a4c7afb5d4a..5350ae5d4c81fc 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import defaultdict +from contextlib import suppress from datetime import datetime from ipaddress import ip_address import logging @@ -99,12 +100,10 @@ async def process_wrong_login(request): remote_addr = ip_address(request.remote) remote_host = request.remote - try: + with suppress(herror): remote_host, _, _ = await hass.async_add_executor_job( gethostbyaddr, request.remote ) - except herror: - pass base_msg = f"Login attempt or request with invalid authentication from {remote_host} ({remote_addr})." diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 5ceb252dfa96af..67170aaf866477 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import defaultdict +from contextlib import suppress from datetime import timedelta from functools import partial import ipaddress @@ -161,10 +162,8 @@ def device_name(self) -> str: (KEY_DEVICE_BASIC_INFORMATION, "devicename"), (KEY_DEVICE_INFORMATION, "DeviceName"), ): - try: + with suppress(KeyError, TypeError): return cast(str, self.data[key][item]) - except (KeyError, TypeError): - pass return DEFAULT_DEVICE_NAME @property diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 9ac45c6bd9a818..58c7e90994c542 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -1,5 +1,6 @@ """Support for hunter douglas shades.""" import asyncio +from contextlib import suppress import logging from aiopvapi.helpers.constants import ATTR_POSITION1, ATTR_POSITION_DATA @@ -65,12 +66,10 @@ async def async_setup_entry(hass, entry, async_add_entities): # possible shade = PvShade(raw_shade, pv_request) name_before_refresh = shade.name - try: + with suppress(asyncio.TimeoutError): async with async_timeout.timeout(1): await shade.refresh() - except asyncio.TimeoutError: - # Forced refresh is not required for setup - pass + if ATTR_POSITION_DATA not in shade.raw_data: _LOGGER.info( "The %s shade was skipped because it is missing position data", diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index ad6990c6ab4476..03b892ce83b706 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import logging from typing import Any, Callable, cast @@ -159,7 +160,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b raise ConfigEntryNotReady version = await hyperion_client.async_sysinfo_version() if version is not None: - try: + with suppress(ValueError): if AwesomeVersion(version) < AwesomeVersion(HYPERION_VERSION_WARN_CUTOFF): _LOGGER.warning( "Using a Hyperion server version < %s is not recommended -- " @@ -168,8 +169,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b HYPERION_VERSION_WARN_CUTOFF, HYPERION_RELEASES_URL, ) - except ValueError: - pass # Client needs authentication, but no token provided? => Reauth. auth_resp = await hyperion_client.async_is_auth_required() diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 2b8d0ee8d8f227..d8985fa41c5b06 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import logging from typing import Any from urllib.parse import urlparse @@ -257,10 +258,8 @@ async def _cancel_request_token_task(self) -> None: if not self._request_token_task.done(): self._request_token_task.cancel() - try: + with suppress(asyncio.CancelledError): await self._request_token_task - except asyncio.CancelledError: - pass self._request_token_task = None async def _request_token_task_func(self, auth_id: str) -> None: diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index df7a8fda786bae..dde10ffca76e49 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,6 +1,7 @@ """Support for sending data to an Influx database.""" from __future__ import annotations +from contextlib import suppress from dataclasses import dataclass import logging import math @@ -304,11 +305,9 @@ def event_to_json(event: dict) -> str: ) # Infinity and NaN are not valid floats in InfluxDB - try: + with suppress(KeyError, TypeError): if not math.isfinite(json[INFLUX_CONF_FIELDS][key]): del json[INFLUX_CONF_FIELDS][key] - except (KeyError, TypeError): - pass json[INFLUX_CONF_TAGS].update(tags) @@ -382,10 +381,8 @@ def close_v2(): if test_write: # Try to write b"" to influx. If we can connect and creds are valid # Then invalid inputs is returned. Anything else is a broken config - try: + with suppress(ValueError): write_v2(b"") - except ValueError: - pass write_api = influx.write_api(write_options=ASYNCHRONOUS) if test_read: @@ -530,7 +527,7 @@ def get_events_json(self): dropped = 0 - try: + with suppress(queue.Empty): while len(json) < BATCH_BUFFER_SIZE and not self.shutdown: timeout = None if count == 0 else self.batch_timeout() item = self.queue.get(timeout=timeout) @@ -549,9 +546,6 @@ def get_events_json(self): else: dropped += 1 - except queue.Empty: - pass - if dropped: _LOGGER.warning(CATCHING_UP_MESSAGE, dropped) diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index 7fb2178def4ced..509878f9613259 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -1,5 +1,6 @@ """Support for INSTEON Modems (PLM and Hub).""" import asyncio +from contextlib import suppress import logging from pyinsteon import async_close, async_connect, devices @@ -37,10 +38,8 @@ async def async_get_device_config(hass, config_entry): # Make a copy of addresses due to edge case where the list of devices could change during status update # Cannot be done concurrently due to issues with the underlying protocol. for address in list(devices): - try: + with suppress(AttributeError): await devices[address].async_status() - except AttributeError: - pass await devices.async_load(id_devices=1) for addr in devices: diff --git a/homeassistant/components/izone/config_flow.py b/homeassistant/components/izone/config_flow.py index a64356051d0d65..bb647f6273c6bc 100644 --- a/homeassistant/components/izone/config_flow.py +++ b/homeassistant/components/izone/config_flow.py @@ -1,6 +1,7 @@ """Config flow for izone.""" import asyncio +from contextlib import suppress import logging from async_timeout import timeout @@ -28,11 +29,9 @@ def dispatch_discovered(_): disco = await async_start_discovery_service(hass) - try: + with suppress(asyncio.TimeoutError): async with timeout(TIMEOUT_DISCOVERY): await controller_ready.wait() - except asyncio.TimeoutError: - pass if not disco.pi_disco.controllers: await async_stop_discovery_service(hass) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index 310bd0189bda39..7d9bcf621e5e0c 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -1,6 +1,7 @@ """Receive signals from a keyboard and use it as a remote control.""" # pylint: disable=import-error import asyncio +from contextlib import suppress import logging import os @@ -255,10 +256,8 @@ async def async_start_monitoring(self, dev): async def async_stop_monitoring(self): """Stop event monitoring task and issue event.""" if self.monitor_task is not None: - try: + with suppress(OSError): await self.hass.async_add_executor_job(self.dev.ungrab) - except OSError: - pass # monitoring of the device form the event loop and closing of the # device has to occur before cancelling the task to avoid # triggering unhandled exceptions inside evdev coroutines diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index bdbac4ab7624c0..9c06b8124b99f3 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" +from contextlib import suppress import logging from homeassistant.components.media_player import BrowseError, BrowseMedia @@ -171,10 +172,8 @@ async def build_item_response(media_library, payload): children = [] for item in media: - try: + with suppress(UnknownMediaType): children.append(item_payload(item, media_library)) - except UnknownMediaType: - pass if search_type in (MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE) and search_id == "": children.sort(key=lambda x: x.title.replace("The ", "", 1), reverse=False) diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 44c9171f244aa1..8d216b5c6f0ace 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" +from contextlib import suppress from datetime import timedelta from itertools import groupby import json @@ -384,10 +385,8 @@ def humanify(hass, events, entity_attr_cache, context_lookup): domain = event_data.get(ATTR_DOMAIN) entity_id = event_data.get(ATTR_ENTITY_ID) if domain is None and entity_id is not None: - try: + with suppress(IndexError): domain = split_entity_id(str(entity_id))[0] - except IndexError: - pass data = { "when": event.time_fired_isoformat, diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index faa2c4882164bd..f98c6eeceafdfc 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -4,6 +4,7 @@ import asyncio import base64 import collections +from contextlib import suppress from datetime import timedelta import functools as ft import hashlib @@ -935,18 +936,13 @@ async def _async_fetch_image(self, url): """Retrieve an image.""" content, content_type = (None, None) websession = async_get_clientsession(self.hass) - try: - with async_timeout.timeout(10): - response = await websession.get(url) - - if response.status == HTTP_OK: - content = await response.read() - content_type = response.headers.get(CONTENT_TYPE) - if content_type: - content_type = content_type.split(";")[0] - - except asyncio.TimeoutError: - pass + with suppress(asyncio.TimeoutError), async_timeout.timeout(10): + response = await websession.get(url) + if response.status == HTTP_OK: + content = await response.read() + content_type = response.headers.get(CONTENT_TYPE) + if content_type: + content_type = content_type.split(";")[0] if content is None: _LOGGER.warning("Error retrieving proxied image from %s", url) diff --git a/homeassistant/components/minecraft_server/config_flow.py b/homeassistant/components/minecraft_server/config_flow.py index a7cb0371f6701d..8cd26d777eb86a 100644 --- a/homeassistant/components/minecraft_server/config_flow.py +++ b/homeassistant/components/minecraft_server/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Minecraft Server integration.""" +from contextlib import suppress from functools import partial import ipaddress @@ -40,10 +41,8 @@ async def async_step_user(self, user_input=None): host = address_right else: host = address_left - try: + with suppress(ValueError): port = int(address_right) - except ValueError: - pass # 'port' is already set to default value. # Remove '[' and ']' in case of an IPv6 address. host = host.strip("[]") diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 4ba6a6f20869b3..e63698d3eb5878 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -1,5 +1,6 @@ """Integrates Native Apps to Home Assistant.""" import asyncio +from contextlib import suppress from homeassistant.components import cloud, notify as hass_notify from homeassistant.components.webhook import ( @@ -51,12 +52,10 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): hass.http.register_view(RegistrationsView()) for deleted_id in hass.data[DOMAIN][DATA_DELETED_IDS]: - try: + with suppress(ValueError): webhook_register( hass, DOMAIN, "Deleted Webhook", deleted_id, handle_webhook ) - except ValueError: - pass hass.async_create_task( discovery.async_load_platform(hass, "notify", DOMAIN, {}, config) @@ -129,7 +128,5 @@ async def async_remove_entry(hass, entry): await store.async_save(savable_state(hass)) if CONF_CLOUDHOOK_URL in entry.data: - try: + with suppress(cloud.CloudNotAvailable): await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID]) - except cloud.CloudNotAvailable: - pass diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 5583b7c58d1fc4..63bf13bad5e0f6 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -1,6 +1,7 @@ """Provides an HTTP API for mobile_app.""" from __future__ import annotations +from contextlib import suppress import secrets from aiohttp.web import Request, Response @@ -98,10 +99,8 @@ async def post(self, request: Request, data: dict) -> Response: ) remote_ui_url = None - try: + with suppress(hass.components.cloud.CloudNotAvailable): remote_ui_url = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass return self.json( { diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index a7b3f38a015b44..efef6eb1c8a5b3 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -1,5 +1,6 @@ """Webhook handlers for mobile_app.""" import asyncio +from contextlib import suppress from functools import wraps import logging import secrets @@ -551,10 +552,8 @@ async def webhook_get_config(hass, config_entry, data): if CONF_CLOUDHOOK_URL in config_entry.data: resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] - try: + with suppress(hass.components.cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() - except hass.components.cloud.CloudNotAvailable: - pass return webhook_response(resp, registration=config_entry.data) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index c6226bed91043f..f57d31b47d37b4 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -1,5 +1,6 @@ """The motion_blinds component.""" import asyncio +from contextlib import suppress from datetime import timedelta import logging from socket import timeout @@ -64,19 +65,13 @@ def update_gateway(): """Call all updates using one async_add_executor_job.""" motion_gateway.Update() for blind in motion_gateway.device_list.values(): - try: + with suppress(timeout): blind.Update() - except timeout: - # let the error be logged and handled by the motionblinds library - pass async def async_update_data(): """Fetch data from the gateway and blinds.""" - try: + with suppress(timeout): # Let the error be handled by the motionblinds await hass.async_add_executor_job(update_gateway) - except timeout: - # let the error be logged and handled by the motionblinds library - pass coordinator = DataUpdateCoordinator( hass, diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 371d20606801a9..adb4bf0e810a85 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -1,4 +1,5 @@ """Support to interact with a Music Player Daemon.""" +from contextlib import suppress from datetime import timedelta import hashlib import logging @@ -129,10 +130,8 @@ async def _connect(self): def _disconnect(self): """Disconnect from MPD.""" - try: + with suppress(mpd.ConnectionError): self._client.disconnect() - except mpd.ConnectionError: - pass self._is_connected = False self._status = None diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 8f2d1dda0a77e7..f23fe033f56119 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -1,4 +1,5 @@ """Support for MQTT JSON lights.""" +from contextlib import suppress import json import logging @@ -245,10 +246,8 @@ def state_received(msg): _LOGGER.warning("Invalid color temp value received") if self._supported_features and SUPPORT_EFFECT: - try: + with suppress(KeyError): self._effect = values["effect"] - except KeyError: - pass if self._supported_features and SUPPORT_WHITE_VALUE: try: diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 82f7885b85d4d4..856106e029da6f 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -1,4 +1,5 @@ """Offer MQTT listening automation rules.""" +from contextlib import suppress import json import logging @@ -79,10 +80,8 @@ def mqtt_automation_listener(mqttmsg): "description": f"mqtt topic {mqttmsg.topic}", } - try: + with suppress(ValueError): data["payload_json"] = json.loads(mqttmsg.payload) - except ValueError: - pass hass.async_run_hass_job(job, {"trigger": data}) diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 3a37799106a991..bdf1b9392a82e1 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -1,6 +1,7 @@ """Config flow for MySensors.""" from __future__ import annotations +from contextlib import suppress import logging import os from typing import Any @@ -62,15 +63,14 @@ def _get_schema_common(user_input: dict[str, str]) -> dict: def _validate_version(version: str) -> dict[str, str]: """Validate a version string from the user.""" version_okay = False - try: + with suppress(AwesomeVersionStrategyException): version_okay = bool( AwesomeVersion.ensure_strategy( version, [AwesomeVersionStrategy.SIMPLEVER, AwesomeVersionStrategy.SEMVER], ) ) - except AwesomeVersionStrategyException: - pass + if version_okay: return {} return {CONF_VERSION: "invalid_version"} diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 761eb2fc2dc80e..826ff4b1a29822 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import datetime as dt import os @@ -241,25 +242,19 @@ async def async_get_device_info(self) -> DeviceInfo: async def async_get_capabilities(self): """Obtain information about the available services on the device.""" snapshot = False - try: + with suppress(ONVIFError, Fault, RequestError): media_service = self.device.create_media_service() media_capabilities = await media_service.GetServiceCapabilities() snapshot = media_capabilities and media_capabilities.SnapshotUri - except (ONVIFError, Fault, RequestError): - pass pullpoint = False - try: + with suppress(ONVIFError, Fault, RequestError): pullpoint = await self.events.async_start() - except (ONVIFError, Fault, RequestError): - pass ptz = False - try: + with suppress(ONVIFError, Fault, RequestError): self.device.get_definition("ptz") ptz = True - except (ONVIFError, Fault, RequestError): - pass return Capabilities(snapshot, pullpoint, ptz) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 91db2a90e57b6d..76b18d729a8f00 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import datetime as dt from typing import Callable @@ -86,10 +87,8 @@ async def async_start(self) -> bool: # Initialize events pullpoint = self.device.create_pullpoint_service() - try: + with suppress(*SUBSCRIPTION_ERRORS): await pullpoint.SetSynchronizationPoint() - except SUBSCRIPTION_ERRORS: - pass response = await pullpoint.PullMessages( {"MessageLimit": 100, "Timeout": dt.timedelta(seconds=5)} ) @@ -119,10 +118,9 @@ async def async_restart(self, _now: dt = None) -> None: return if self._subscription: - try: + # Suppressed. The subscription may no longer exist. + with suppress(*SUBSCRIPTION_ERRORS): await self._subscription.Unsubscribe() - except SUBSCRIPTION_ERRORS: - pass # Ignored. The subscription may no longer exist. self._subscription = None try: diff --git a/homeassistant/components/ozw/__init__.py b/homeassistant/components/ozw/__init__.py index 3f54c731e39147..ace71e4af81c28 100644 --- a/homeassistant/components/ozw/__init__.py +++ b/homeassistant/components/ozw/__init__.py @@ -1,5 +1,6 @@ """The ozw integration.""" import asyncio +from contextlib import suppress import json import logging @@ -280,10 +281,8 @@ async def async_stop_mqtt_client(event=None): Do not unsubscribe the manager topic. """ mqtt_client_task.cancel() - try: + with suppress(asyncio.CancelledError): await mqtt_client_task - except asyncio.CancelledError: - pass ozw_data[DATA_UNSUBSCRIBE].append( hass.bus.async_listen_once( diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index e660b5af38dbf2..48e47937e2e62a 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from datetime import timedelta from functools import partial import logging @@ -242,10 +243,8 @@ async def async_ping(self): self._count + PING_TIMEOUT, ) if pinger: - try: + with suppress(TypeError): await pinger.kill() - except TypeError: - pass del pinger return False diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index 7c0c8e9b46fd11..290993959b3fc0 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -1,5 +1,6 @@ """Support for monitoring plants.""" from collections import deque +from contextlib import suppress from datetime import datetime, timedelta import logging @@ -324,12 +325,10 @@ def _load_history_from_db(self): for state in states: # filter out all None, NaN and "unknown" states # only keep real values - try: + with suppress(ValueError): self._brightness_history.add_measurement( int(state.state), state.last_updated ) - except ValueError: - pass _LOGGER.debug("Initializing from database completed") @property diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 24a1589db0d9ca..be77ea04f1cae1 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -1,5 +1,6 @@ """Support for PlayStation 4 consoles.""" import asyncio +from contextlib import suppress import logging from pyps4_2ndscreen.errors import NotReady, PSDataIncomplete @@ -142,10 +143,8 @@ async def async_update(self): and not self._ps4.is_standby and self._ps4.is_available ): - try: + with suppress(NotReady): await self._ps4.async_connect() - except NotReady: - pass # Try to ensure correct status is set on startup for device info. if self._ps4.ddp_protocol is None: diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 726a6e26ce5e52..8d87b688aa47e7 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -1,5 +1,6 @@ """Integration with the Rachio Iro sprinkler system controller.""" from abc import abstractmethod +from contextlib import suppress from datetime import timedelta import logging @@ -525,7 +526,7 @@ def turn_off(self, **kwargs) -> None: def _async_handle_update(self, *args, **kwargs) -> None: """Handle incoming webhook schedule data.""" # Schedule ID not passed when running individual zones, so we catch that error - try: + with suppress(KeyError): if args[0][KEY_SCHEDULE_ID] == self._schedule_id: if args[0][KEY_SUBTYPE] in [SUBTYPE_SCHEDULE_STARTED]: self._state = True @@ -534,8 +535,6 @@ def _async_handle_update(self, *args, **kwargs) -> None: SUBTYPE_SCHEDULE_COMPLETED, ]: self._state = False - except KeyError: - pass self.async_write_ha_state() diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 685dc548338db6..78b713c286ca20 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -4,6 +4,7 @@ For more info on the API see: https://help.rejseplanen.dk/hc/en-us/articles/214174465-Rejseplanen-s-API """ +from contextlib import suppress from datetime import datetime, timedelta import logging from operator import itemgetter @@ -147,10 +148,8 @@ def update(self): if not self._times: self._state = None else: - try: + with suppress(TypeError): self._state = self._times[0][ATTR_DUE_IN] - except TypeError: - pass class PublicTransportData: diff --git a/homeassistant/components/sharkiq/__init__.py b/homeassistant/components/sharkiq/__init__.py index e43338cd5b0f9a..5ccb3e00f27257 100644 --- a/homeassistant/components/sharkiq/__init__.py +++ b/homeassistant/components/sharkiq/__init__.py @@ -1,6 +1,7 @@ """Shark IQ Integration.""" import asyncio +from contextlib import suppress import async_timeout from sharkiqpy import ( @@ -59,7 +60,7 @@ async def async_setup_entry(hass, config_entry): raise exceptions.ConfigEntryNotReady from exc shark_vacs = await ayla_api.async_get_devices(False) - device_names = ", ".join([d.name for d in shark_vacs]) + device_names = ", ".join(d.name for d in shark_vacs) _LOGGER.debug("Found %d Shark IQ device(s): %s", len(shark_vacs), device_names) coordinator = SharkIqUpdateCoordinator(hass, config_entry, ayla_api, shark_vacs) @@ -81,11 +82,10 @@ async def async_setup_entry(hass, config_entry): async def async_disconnect_or_timeout(coordinator: SharkIqUpdateCoordinator): """Disconnect to vacuum.""" _LOGGER.debug("Disconnecting from Ayla Api") - with async_timeout.timeout(5): - try: - await coordinator.ayla_api.async_sign_out() - except (SharkIqAuthError, SharkIqAuthExpiringError, SharkIqNotAuthedError): - pass + with async_timeout.timeout(5), suppress( + SharkIqAuthError, SharkIqAuthExpiringError, SharkIqNotAuthedError + ): + await coordinator.ayla_api.async_sign_out() async def async_update_options(hass, config_entry): @@ -105,10 +105,8 @@ async def async_unload_entry(hass, config_entry): ) if unload_ok: domain_data = hass.data[DOMAIN][config_entry.entry_id] - try: + with suppress(SharkIqAuthError): await async_disconnect_or_timeout(coordinator=domain_data) - except SharkIqAuthError: - pass hass.data[DOMAIN].pop(config_entry.entry_id) return unload_ok diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index 1173d0477abb95..089dc36b1a8222 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -1,5 +1,6 @@ """Expose regular shell commands as services.""" import asyncio +from contextlib import suppress import logging import shlex @@ -87,10 +88,8 @@ async def async_service_handler(service: ServiceCall) -> None: "Timed out running command: `%s`, after: %ss", cmd, COMMAND_TIMEOUT ) if process: - try: + with suppress(TypeError): await process.kill() - except TypeError: - pass del process return diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index a34fb5a3afc908..441a1c39e08b3a 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -1,4 +1,5 @@ """Support for SolarEdge-local Monitoring API.""" +from contextlib import suppress from copy import deepcopy from datetime import timedelta import logging @@ -350,19 +351,15 @@ def update(self): self.info["optimizers"] = status.optimizersStatus.total self.info["invertertemperature"] = INVERTER_MODES[status.status] - try: + with suppress(IndexError): if status.metersList[1]: self.data["currentPowerimport"] = status.metersList[1].currentPower self.data["totalEnergyimport"] = status.metersList[1].totalEnergy - except IndexError: - pass - try: + with suppress(IndexError): if status.metersList[0]: self.data["currentPowerexport"] = status.metersList[0].currentPower self.data["totalEnergyexport"] = status.metersList[0].totalEnergy - except IndexError: - pass if maintenance.system.name: self.data["optimizertemperature"] = round(statistics.mean(temperature), 2) diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 6b6c927ca1c2fc..179fc62e0cc195 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -1,4 +1,5 @@ """Support for media browsing.""" +from contextlib import suppress import logging import urllib.parse @@ -75,10 +76,8 @@ def build_item_response(media_library, payload, get_thumbnail_url=None): children = [] for item in media: - try: + with suppress(UnknownMediaType): children.append(item_payload(item, get_thumbnail_url)) - except UnknownMediaType: - pass return BrowseMedia( title=title, @@ -136,10 +135,8 @@ def library_payload(media_library, get_thumbnail_url=None): children = [] for item in media_library.browse(): - try: + with suppress(UnknownMediaType): children.append(item_payload(item, get_thumbnail_url)) - except UnknownMediaType: - pass return BrowseMedia( title="Music Library", diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 9b2342e5e1bdce..8339be673fb9eb 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1,5 +1,6 @@ """Support to interface with Sonos players.""" import asyncio +from contextlib import suppress import datetime import functools as ft import logging @@ -790,7 +791,7 @@ def _get_soco_group(): coordinator_uid = self.unique_id slave_uids = [] - try: + with suppress(SoCoException): if self.soco.group and self.soco.group.coordinator: coordinator_uid = self.soco.group.coordinator.uid slave_uids = [ @@ -798,8 +799,6 @@ def _get_soco_group(): for p in self.soco.group.members if p.uid != coordinator_uid ] - except SoCoException: - pass return [coordinator_uid] + slave_uids diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index f6def03ec6a016..66583050b206f5 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -1,4 +1,6 @@ """Support for the SpaceAPI.""" +from contextlib import suppress + import voluptuous as vol from homeassistant.components.http import HomeAssistantView @@ -287,13 +289,11 @@ def get(self, request): else: state = {ATTR_OPEN: "null", ATTR_LASTCHANGE: 0} - try: + with suppress(KeyError): state[ATTR_ICON] = { ATTR_OPEN: spaceapi["state"][CONF_ICON_OPEN], ATTR_CLOSE: spaceapi["state"][CONF_ICON_CLOSED], } - except KeyError: - pass data = { ATTR_API: SPACEAPI_VERSION, @@ -306,40 +306,26 @@ def get(self, request): ATTR_URL: spaceapi[CONF_URL], } - try: + with suppress(KeyError): data[ATTR_CAM] = spaceapi[CONF_CAM] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_SPACEFED] = spaceapi[CONF_SPACEFED] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_STREAM] = spaceapi[CONF_STREAM] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_FEEDS] = spaceapi[CONF_FEEDS] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_CACHE] = spaceapi[CONF_CACHE] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_PROJECTS] = spaceapi[CONF_PROJECTS] - except KeyError: - pass - try: + with suppress(KeyError): data[ATTR_RADIO_SHOW] = spaceapi[CONF_RADIO_SHOW] - except KeyError: - pass if is_sensors is not None: sensors = {} diff --git a/homeassistant/components/swisscom/device_tracker.py b/homeassistant/components/swisscom/device_tracker.py index 5662212c9e8d90..1332aa4618986e 100644 --- a/homeassistant/components/swisscom/device_tracker.py +++ b/homeassistant/components/swisscom/device_tracker.py @@ -1,4 +1,5 @@ """Support for Swisscom routers (Internet-Box).""" +from contextlib import suppress import logging from aiohttp.hdrs import CONTENT_TYPE @@ -97,13 +98,11 @@ def get_swisscom_data(self): return devices for device in request.json()["status"]: - try: + with suppress(KeyError, requests.exceptions.RequestException): devices[device["Key"]] = { "ip": device["IPAddress"], "mac": device["PhysAddress"], "host": device["Name"], "status": device["Active"], } - except (KeyError, requests.exceptions.RequestException): - pass return devices diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py index 86c9b9ace95dec..d618ca9c2cf6bd 100644 --- a/homeassistant/components/ted5000/sensor.py +++ b/homeassistant/components/ted5000/sensor.py @@ -1,4 +1,5 @@ """Support gathering ted5000 information.""" +from contextlib import suppress from datetime import timedelta import logging @@ -73,10 +74,8 @@ def unit_of_measurement(self): @property def state(self): """Return the state of the resources.""" - try: + with suppress(KeyError): return self._gateway.data[self._mtu][self._unit] - except KeyError: - pass def update(self): """Get the latest data from REST API.""" diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index dec20edec652c9..4d7dce37447e1c 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -1,5 +1,6 @@ """Support for TPLink HS100/HS110/HS200 smart switch.""" import asyncio +from contextlib import suppress import logging import time @@ -151,13 +152,10 @@ def attempt_update(self, update_attempt): ) emeter_statics = self.smartplug.get_emeter_daily() - try: + with suppress(KeyError): # Device returned no daily history self._emeter_params[ATTR_TODAY_ENERGY_KWH] = "{:.3f}".format( emeter_statics[int(time.strftime("%e"))] ) - except KeyError: - # Device returned no daily history - pass return True except (SmartDeviceException, OSError) as ex: if update_attempt == 0: diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index a82adac6160d7b..b00ccfc68c0fc4 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,6 +1,8 @@ """Support for monitoring the Transmission BitTorrent client API.""" from __future__ import annotations +from contextlib import suppress + from transmissionrpc.torrent import Torrent from homeassistant.components.sensor import SensorEntity @@ -187,8 +189,6 @@ def _torrents_info(torrents, order, limit, statuses=None): "status": torrent.status, "id": torrent.id, } - try: + with suppress(ValueError): info["eta"] = str(torrent.eta) - except ValueError: - pass return infos diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index 3af9999bd9fe36..70a1cddefc37ce 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -1,5 +1,6 @@ """Config flow for UPB PIM integration.""" import asyncio +from contextlib import suppress import logging from urllib.parse import urlparse @@ -43,11 +44,8 @@ def _connected_callback(): upb.connect(_connected_callback) - try: - with async_timeout.timeout(VALIDATE_TIMEOUT): - await connected_event.wait() - except asyncio.TimeoutError: - pass + with suppress(asyncio.TimeoutError), async_timeout.timeout(VALIDATE_TIMEOUT): + await connected_event.wait() upb.disconnect() diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 1a727daac73834..013142504f15c3 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import os from typing import Any @@ -164,10 +165,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return False cookie_file = hass.config.path(STORAGE_DIR, f"verisure_{entry.entry_id}") - try: + with suppress(FileNotFoundError): await hass.async_add_executor_job(os.unlink, cookie_file) - except FileNotFoundError: - pass del hass.data[DOMAIN][entry.entry_id] diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 34e32dce16386b..681c2acfe01219 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1,5 +1,6 @@ """Support for LG webOS Smart TV.""" import asyncio +from contextlib import suppress import json import logging import os @@ -150,9 +151,7 @@ async def async_setup_tv(hass, config, conf): async def async_connect(client): """Attempt a connection, but fail gracefully if tv is off for example.""" - try: - await client.connect() - except ( + with suppress( OSError, ConnectionClosed, ConnectionRefusedError, @@ -161,7 +160,7 @@ async def async_connect(client): PyLGTVPairException, PyLGTVCmdException, ): - pass + await client.connect() async def async_setup_tv_finalize(hass, config, conf, client): diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index f4d4b67b8b0689..d94ab8a7c26683 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -1,5 +1,6 @@ """Support for interface with an LG webOS Smart TV.""" import asyncio +from contextlib import suppress from datetime import timedelta from functools import wraps import logging @@ -214,9 +215,7 @@ def update_sources(self): async def async_update(self): """Connect.""" if not self._client.is_connected(): - try: - await self._client.connect() - except ( + with suppress( OSError, ConnectionClosed, ConnectionRefusedError, @@ -225,7 +224,7 @@ async def async_update(self): PyLGTVPairException, PyLGTVCmdException, ): - pass + await self._client.connect() @property def unique_id(self): diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 88f9588750ebb9..8d60c21c1188b8 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -1,4 +1,5 @@ """Support for Wink sensors.""" +from contextlib import suppress import logging import pywink @@ -87,9 +88,9 @@ def unit_of_measurement(self): def extra_state_attributes(self): """Return the state attributes.""" super_attrs = super().extra_state_attributes - try: + + # Ignore error, this sensor isn't an eggminder + with suppress(AttributeError): super_attrs["egg_times"] = self.wink.eggs() - except AttributeError: - # Ignore error, this sensor isn't an eggminder - pass + return super_attrs diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index dd35828680205f..938a392dd69d8d 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from dataclasses import dataclass from datetime import timedelta import logging @@ -248,12 +249,10 @@ async def _async_update_data(self) -> XboxData: def _build_presence_data(person: Person) -> PresenceData: """Build presence data from a person.""" active_app: PresenceDetail | None = None - try: + with suppress(StopIteration): active_app = next( presence for presence in person.presence_details if presence.is_primary ) - except StopIteration: - pass return PresenceData( xuid=person.xuid, diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 1a459580d661e9..64a16e2c21d7ac 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -1,6 +1,7 @@ """Xbox Media Source Implementation.""" from __future__ import annotations +from contextlib import suppress from dataclasses import dataclass from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module @@ -138,7 +139,7 @@ async def _build_media_items(self, title, category): owner, kind = category.split("#", 1) items: list[XboxMediaItem] = [] - try: + with suppress(ValidationError): # Unexpected API response if kind == "gameclips": if owner == "my": response: GameclipsResponse = ( @@ -189,9 +190,6 @@ async def _build_media_items(self, title, category): ) for item in response.screenshots ] - except ValidationError: - # Unexpected API response - pass return BrowseMediaSource( domain=DOMAIN, diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index b9d02731570fd3..82807c4aceefd0 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -1,4 +1,5 @@ """Support for Zabbix.""" +from contextlib import suppress import json import logging import math @@ -202,7 +203,7 @@ def get_metrics(self): dropped = 0 - try: + with suppress(queue.Empty): while len(metrics) < BATCH_BUFFER_SIZE and not self.shutdown: timeout = None if count == 0 else BATCH_TIMEOUT item = self.queue.get(timeout=timeout) @@ -223,9 +224,6 @@ def get_metrics(self): else: dropped += 1 - except queue.Empty: - pass - if dropped: _LOGGER.warning("Catching up, dropped %d old events", dropped) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index d9c8dc5286026a..6adbe07d8672c4 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,6 +1,7 @@ """Support for exposing Home Assistant via Zeroconf.""" from __future__ import annotations +from contextlib import suppress import fnmatch from functools import partial import ipaddress @@ -187,15 +188,11 @@ def _register_hass_zc_service(hass, zeroconf, uuid): } # Get instance URL's - try: + with suppress(NoURLAvailableError): params["external_url"] = get_url(hass, allow_internal=False) - except NoURLAvailableError: - pass - try: + with suppress(NoURLAvailableError): params["internal_url"] = get_url(hass, allow_external=False) - except NoURLAvailableError: - pass # Set old base URL based on external or internal params["base_url"] = params["external_url"] or params["internal_url"] @@ -380,11 +377,9 @@ def info_from_service(service): properties["_raw"][key] = value - try: + with suppress(UnicodeDecodeError): if isinstance(value, bytes): properties[key] = value.decode("utf-8") - except UnicodeDecodeError: - pass if not service.addresses: return None diff --git a/homeassistant/components/zeroconf/usage.py b/homeassistant/components/zeroconf/usage.py index 1af6e3e1f3c294..4b4af0dd79d782 100644 --- a/homeassistant/components/zeroconf/usage.py +++ b/homeassistant/components/zeroconf/usage.py @@ -1,5 +1,6 @@ """Zeroconf usage utility to warn about multiple instances.""" +from contextlib import suppress import logging import zeroconf @@ -36,10 +37,8 @@ def _report(what: str) -> None: """ integration_frame = None - try: + with suppress(MissingIntegrationFrame): integration_frame = get_integration_frame(exclude_integrations={"zeroconf"}) - except MissingIntegrationFrame: - pass if not integration_frame: _LOGGER.warning( diff --git a/homeassistant/components/zha/core/channels/lighting.py b/homeassistant/components/zha/core/channels/lighting.py index 000549b059363b..eef4c56e3794a8 100644 --- a/homeassistant/components/zha/core/channels/lighting.py +++ b/homeassistant/components/zha/core/channels/lighting.py @@ -1,6 +1,7 @@ """Lighting channels module for Zigbee Home Automation.""" from __future__ import annotations +from contextlib import suppress from typing import Coroutine import zigpy.zcl.clusters.lighting as lighting @@ -39,10 +40,8 @@ class ColorChannel(ZigbeeChannel): @property def color_capabilities(self) -> int: """Return color capabilities of the light.""" - try: + with suppress(KeyError): return self.cluster["color_capabilities"] - except KeyError: - pass if self.cluster.get("color_temperature") is not None: return self.CAPABILITIES_COLOR_XY | self.CAPABILITIES_COLOR_TEMP return self.CAPABILITIES_COLOR_XY diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 2a362028d51c26..0957260c761180 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from ssl import SSLContext import sys from typing import Any, Awaitable, cast @@ -37,10 +38,9 @@ def async_get_clientsession( This method must be run in the event loop. """ + key = DATA_CLIENTSESSION_NOTVERIFY if verify_ssl: key = DATA_CLIENTSESSION - else: - key = DATA_CLIENTSESSION_NOTVERIFY if key not in hass.data: hass.data[key] = async_create_clientsession(hass, verify_ssl) @@ -130,7 +130,8 @@ async def async_aiohttp_proxy_stream( response.content_type = content_type await response.prepare(request) - try: + # Suppressing something went wrong fetching data, closed connection + with suppress(asyncio.TimeoutError, aiohttp.ClientError): while hass.is_running: with async_timeout.timeout(timeout): data = await stream.read(buffer_size) @@ -139,10 +140,6 @@ async def async_aiohttp_proxy_stream( break await response.write(data) - except (asyncio.TimeoutError, aiohttp.ClientError): - # Something went wrong fetching data, closed connection - pass - return response diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 6278bf8f855f2d..6ed8084413ff59 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -1,6 +1,7 @@ """Network helpers.""" from __future__ import annotations +from contextlib import suppress from ipaddress import ip_address from typing import cast @@ -56,7 +57,7 @@ def get_url( for url_type in order: if allow_internal and url_type == TYPE_URL_INTERNAL: - try: + with suppress(NoURLAvailableError): return _get_internal_url( hass, allow_ip=allow_ip, @@ -64,11 +65,9 @@ def get_url( require_ssl=require_ssl, require_standard_port=require_standard_port, ) - except NoURLAvailableError: - pass if allow_external and url_type == TYPE_URL_EXTERNAL: - try: + with suppress(NoURLAvailableError): return _get_external_url( hass, allow_cloud=allow_cloud, @@ -78,8 +77,6 @@ def get_url( require_ssl=require_ssl, require_standard_port=require_standard_port, ) - except NoURLAvailableError: - pass # For current request, we accept loopback interfaces (e.g., 127.0.0.1), # the Supervisor hostname and localhost transparently @@ -177,10 +174,8 @@ def _get_external_url( ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: - try: + with suppress(NoURLAvailableError): return _get_cloud_url(hass) - except NoURLAvailableError: - pass if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) @@ -201,10 +196,8 @@ def _get_external_url( return normalize_url(str(external_url)) if allow_cloud: - try: + with suppress(NoURLAvailableError): return _get_cloud_url(hass, require_current_request=require_current_request) - except NoURLAvailableError: - pass raise NoURLAvailableError diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 4cc2c1975e8bc6..d07d963c95d8a6 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from contextlib import asynccontextmanager +from contextlib import asynccontextmanager, suppress from datetime import datetime, timedelta from functools import partial import itertools @@ -492,10 +492,8 @@ async def _async_run_long_action(self, long_task): async def async_cancel_long_task() -> None: # Stop long task and wait for it to finish. long_task.cancel() - try: + with suppress(Exception): await long_task - except Exception: # pylint: disable=broad-except - pass # Wait for long task while monitoring for a stop request. stop_task = self._hass.async_create_task(self._stop.wait()) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index c1138b54f79bb5..5a08a97a210045 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress from json import JSONEncoder import logging import os @@ -243,7 +244,5 @@ async def async_remove(self): self._async_cleanup_delay_listener() self._async_cleanup_final_write_listener() - try: + with suppress(FileNotFoundError): await self.hass.async_add_executor_job(os.unlink, self.path) - except FileNotFoundError: - pass diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 24c91ec54682cd..2fde0e1f0b5951 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -5,6 +5,7 @@ import asyncio import base64 import collections.abc +from contextlib import suppress from datetime import datetime, timedelta from functools import partial, wraps import json @@ -513,10 +514,8 @@ def async_render_with_possible_json_value( variables = dict(variables or {}) variables["value"] = value - try: + with suppress(ValueError, TypeError): variables["value_json"] = json.loads(value) - except (ValueError, TypeError): - pass try: return self._compiled.render(variables).strip() diff --git a/homeassistant/loader.py b/homeassistant/loader.py index b3ef4fff271b4e..30a5a2a7ceb8b7 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -7,6 +7,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import functools as ft import importlib import json @@ -587,10 +588,8 @@ def _load_file( Only returns it if also found to be valid. Async friendly. """ - try: + with suppress(KeyError): return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore - except KeyError: - pass cache = hass.data.get(DATA_COMPONENTS) if cache is None: diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index aae01c56bf3ed6..b0c6cb21fec06c 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,6 +1,7 @@ """Helper methods to handle the time in Home Assistant.""" from __future__ import annotations +from contextlib import suppress import datetime as dt import re from typing import Any, cast @@ -127,10 +128,9 @@ def parse_datetime(dt_str: str) -> dt.datetime | None: Raises ValueError if the input is well formatted but not a valid datetime. Returns None if the input isn't well formatted. """ - try: + with suppress(ValueError, IndexError): return ciso8601.parse_datetime(dt_str) - except (ValueError, IndexError): - pass + match = DATETIME_RE.match(dt_str) if not match: return None diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index 91aad5f381cb77..7bb49b0545beba 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import OrderedDict +from contextlib import suppress import logging import os from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result @@ -128,10 +129,8 @@ def save_yaml(fname: str, data: JSON_TYPE) -> None: yaml.dump(data, temp_file) os.replace(tmp_fname, fname) if hasattr(os, "chown") and file_stat.st_ctime > -1: - try: + with suppress(OSError): os.chown(fname, file_stat.st_uid, file_stat.st_gid) - except OSError: - pass except YAMLError as exc: _LOGGER.error(str(exc)) raise HomeAssistantError(exc) from exc diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 9935cc19cb860f..c9c6d7b4793970 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -1,5 +1,6 @@ """The tests for generic camera component.""" import asyncio +from contextlib import suppress from aiohttp.client_exceptions import ClientResponseError @@ -214,10 +215,8 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): aioclient_mock.get(radar_map_url(), text=None, status=HTTP_INTERNAL_SERVER_ERROR) # A 404 should not return data and throw: - try: + with suppress(ClientResponseError): await client.get("/api/camera_proxy/camera.config_test") - except ClientResponseError: - pass assert aioclient_mock.call_count == 1 diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index 420707e54d0484..68d4dfbf3796d7 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -1,4 +1,5 @@ """The tests for the Demo component.""" +from contextlib import suppress import json import os @@ -20,10 +21,8 @@ def mock_history(hass): def demo_cleanup(hass): """Clean up device tracker demo file.""" yield - try: + with suppress(FileNotFoundError): os.remove(hass.config.path(YAML_DEVICES)) - except FileNotFoundError: - pass async def test_setting_up_demo(hass): diff --git a/tests/util/test_timeout.py b/tests/util/test_timeout.py index 39ec8d916bdd67..227dde1923453b 100644 --- a/tests/util/test_timeout.py +++ b/tests/util/test_timeout.py @@ -1,5 +1,6 @@ """Test Home Assistant timeout handler.""" import asyncio +from contextlib import suppress import time import pytest @@ -232,11 +233,9 @@ async def test_mix_zone_timeout(): timeout = TimeoutManager() async with timeout.async_timeout(0.1): - try: + with suppress(asyncio.TimeoutError): async with timeout.async_timeout(0.2, "test"): await asyncio.sleep(0.4) - except asyncio.TimeoutError: - pass async def test_mix_zone_timeout_trigger_global(): @@ -245,11 +244,9 @@ async def test_mix_zone_timeout_trigger_global(): with pytest.raises(asyncio.TimeoutError): async with timeout.async_timeout(0.1): - try: + with suppress(asyncio.TimeoutError): async with timeout.async_timeout(0.1, "test"): await asyncio.sleep(0.3) - except asyncio.TimeoutError: - pass await asyncio.sleep(0.3) @@ -259,11 +256,9 @@ async def test_mix_zone_timeout_trigger_global_cool_down(): timeout = TimeoutManager() async with timeout.async_timeout(0.1, cool_down=0.3): - try: + with suppress(asyncio.TimeoutError): async with timeout.async_timeout(0.1, "test"): await asyncio.sleep(0.3) - except asyncio.TimeoutError: - pass await asyncio.sleep(0.2) @@ -301,11 +296,9 @@ async def test_simple_zone_timeout_freeze_without_timeout_exeption(): with pytest.raises(asyncio.TimeoutError): async with timeout.async_timeout(0.1): - try: + with suppress(RuntimeError): async with timeout.async_freeze("test"): raise RuntimeError() - except RuntimeError: - pass await asyncio.sleep(0.4) @@ -316,10 +309,8 @@ async def test_simple_zone_timeout_zone_with_timeout_exeption(): with pytest.raises(asyncio.TimeoutError): async with timeout.async_timeout(0.1): - try: + with suppress(RuntimeError): async with timeout.async_timeout(0.3, "test"): raise RuntimeError() - except RuntimeError: - pass await asyncio.sleep(0.3) From a09c8eecb7b5ca2a2bace70c4d8460f3682c2d69 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 23 Mar 2021 15:56:33 +0100 Subject: [PATCH 1467/1818] Fix some sensor classes (#48254) * Fix some sensor classes * Tweak * Tweak --- .../components/aemet/abstract_aemet_sensor.py | 3 ++- homeassistant/components/hydrawise/__init__.py | 7 ------- homeassistant/components/hydrawise/sensor.py | 9 ++++++++- homeassistant/components/iqvia/__init__.py | 3 ++- homeassistant/components/iqvia/sensor.py | 5 ++--- .../components/nest/legacy/__init__.py | 5 ----- homeassistant/components/nest/legacy/sensor.py | 10 ++++++++++ homeassistant/components/nest/sensor_sdm.py | 4 ++-- .../components/onewire/onewire_entities.py | 5 ----- homeassistant/components/onewire/sensor.py | 17 ++++++++++++++--- homeassistant/components/raincloud/__init__.py | 5 ----- homeassistant/components/raincloud/sensor.py | 13 ++++++++++++- homeassistant/components/shelly/entity.py | 10 ---------- homeassistant/components/shelly/sensor.py | 15 +++++++++++++++ .../components/synology_dsm/__init__.py | 8 -------- homeassistant/components/synology_dsm/sensor.py | 17 ++++++++++++++--- homeassistant/components/withings/common.py | 5 ----- homeassistant/components/withings/sensor.py | 5 +++++ homeassistant/components/xbee/__init__.py | 3 ++- 19 files changed, 88 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/aemet/abstract_aemet_sensor.py b/homeassistant/components/aemet/abstract_aemet_sensor.py index 7699a5f99a4998..8847a5d094d480 100644 --- a/homeassistant/components/aemet/abstract_aemet_sensor.py +++ b/homeassistant/components/aemet/abstract_aemet_sensor.py @@ -1,4 +1,5 @@ """Abstraction form AEMET OpenData sensors.""" +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -6,7 +7,7 @@ from .weather_update_coordinator import WeatherUpdateCoordinator -class AbstractAemetSensor(CoordinatorEntity): +class AbstractAemetSensor(CoordinatorEntity, SensorEntity): """Abstract class for an AEMET OpenData sensor.""" def __init__( diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 6fa05ebef16347..1f1b2c03157c22 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -143,13 +143,6 @@ def _update_callback(self): """Call update method.""" self.async_schedule_update_ha_state(True) - @property - def unit_of_measurement(self): - """Return the units of measurement.""" - return DEVICE_MAP[self._sensor_type][ - DEVICE_MAP_INDEX.index("UNIT_OF_MEASURE_INDEX") - ] - @property def extra_state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 7cd521296f6cb0..62108afbded0b2 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -8,7 +8,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.util import dt -from . import DATA_HYDRAWISE, SENSORS, HydrawiseEntity +from . import DATA_HYDRAWISE, DEVICE_MAP, DEVICE_MAP_INDEX, SENSORS, HydrawiseEntity _LOGGER = logging.getLogger(__name__) @@ -44,6 +44,13 @@ def state(self): """Return the state of the sensor.""" return self._state + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return DEVICE_MAP[self._sensor_type][ + DEVICE_MAP_INDEX.index("UNIT_OF_MEASURE_INDEX") + ] + def update(self): """Get the latest data and updates the states.""" mydata = self.hass.data[DATA_HYDRAWISE].data diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index 962f13ca07ea15..c548a115e04eb4 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -6,6 +6,7 @@ from pyiqvia import Client from pyiqvia.errors import IQVIAError +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -109,7 +110,7 @@ async def async_unload_entry(hass, entry): return unload_ok -class IQVIAEntity(CoordinatorEntity): +class IQVIAEntity(CoordinatorEntity, SensorEntity): """Define a base IQVIA entity.""" def __init__(self, coordinator, entry, sensor_type, name, icon): diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index 169ff405d6df0a..48ec1cf97b1a62 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -3,7 +3,6 @@ import numpy as np -from homeassistant.components.sensor import SensorEntity from homeassistant.const import ATTR_STATE from homeassistant.core import callback @@ -99,7 +98,7 @@ def calculate_trend(indices): return TREND_FLAT -class ForecastSensor(IQVIAEntity, SensorEntity): +class ForecastSensor(IQVIAEntity): """Define sensor related to forecast data.""" @callback @@ -138,7 +137,7 @@ def update_from_latest_data(self): self._state = average -class IndexSensor(IQVIAEntity, SensorEntity): +class IndexSensor(IQVIAEntity): """Define sensor related to indices.""" @callback diff --git a/homeassistant/components/nest/legacy/__init__.py b/homeassistant/components/nest/legacy/__init__.py index 11bc1ab33ff642..60faa90e8b4625 100644 --- a/homeassistant/components/nest/legacy/__init__.py +++ b/homeassistant/components/nest/legacy/__init__.py @@ -363,11 +363,6 @@ def name(self): """Return the name of the nest, if any.""" return self._name - @property - def unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit - @property def should_poll(self): """Do not need poll thanks using Nest streaming API.""" diff --git a/homeassistant/components/nest/legacy/sensor.py b/homeassistant/components/nest/legacy/sensor.py index 9bea3b34ad4894..53d9c8244669ef 100644 --- a/homeassistant/components/nest/legacy/sensor.py +++ b/homeassistant/components/nest/legacy/sensor.py @@ -153,6 +153,11 @@ def get_sensors(): class NestBasicSensor(NestSensorDevice, SensorEntity): """Representation a basic Nest sensor.""" + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit + @property def state(self): """Return the state of the sensor.""" @@ -188,6 +193,11 @@ def state(self): """Return the state of the sensor.""" return self._state + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit + @property def device_class(self): """Return the device class of the sensor.""" diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 946be2e95b016c..06e2b68d7cf602 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -7,6 +7,7 @@ from google_nest_sdm.device_traits import HumidityTrait, TemperatureTrait from google_nest_sdm.exceptions import GoogleNestException +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_HUMIDITY, @@ -15,7 +16,6 @@ TEMP_CELSIUS, ) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from .const import DATA_SUBSCRIBER, DOMAIN @@ -53,7 +53,7 @@ async def async_setup_sdm_entry( async_add_entities(entities) -class SensorBase(Entity): +class SensorBase(SensorEntity): """Representation of a dynamically updated Sensor.""" def __init__(self, device: Device): diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index e8c013094f7c9b..10c2b0c24a77b8 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -54,11 +54,6 @@ def device_class(self) -> str | None: """Return the class of this device.""" return self._device_class - @property - def unit_of_measurement(self) -> str | None: - """Return the unit the value is expressed in.""" - return self._unit_of_measurement - @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the entity.""" diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index ba988aba226649..3ea29a904dbd38 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -1,4 +1,6 @@ """Support for 1-Wire environment sensors.""" +from __future__ import annotations + from glob import glob import logging import os @@ -394,7 +396,16 @@ def get_entities(onewirehub: OneWireHub, config): return entities -class OneWireProxySensor(OneWireProxyEntity, SensorEntity): +class OneWireSensor(OneWireBaseEntity, SensorEntity): + """Mixin for sensor specific attributes.""" + + @property + def unit_of_measurement(self) -> str | None: + """Return the unit the value is expressed in.""" + return self._unit_of_measurement + + +class OneWireProxySensor(OneWireProxyEntity, OneWireSensor): """Implementation of a 1-Wire sensor connected through owserver.""" @property @@ -403,7 +414,7 @@ def state(self) -> StateType: return self._state -class OneWireDirectSensor(OneWireBaseEntity, SensorEntity): +class OneWireDirectSensor(OneWireSensor): """Implementation of a 1-Wire sensor directly connected to RPI GPIO.""" def __init__(self, name, device_file, device_info, owsensor): @@ -431,7 +442,7 @@ def update(self): self._state = value -class OneWireOWFSSensor(OneWireBaseEntity, SensorEntity): # pragma: no cover +class OneWireOWFSSensor(OneWireSensor): # pragma: no cover """Implementation of a 1-Wire sensor through owfs. This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. diff --git a/homeassistant/components/raincloud/__init__.py b/homeassistant/components/raincloud/__init__.py index 6b0ca39df03722..51e9f5de5cecfc 100644 --- a/homeassistant/components/raincloud/__init__.py +++ b/homeassistant/components/raincloud/__init__.py @@ -160,11 +160,6 @@ def _update_callback(self): """Call update method.""" self.schedule_update_ha_state(True) - @property - def unit_of_measurement(self): - """Return the units of measurement.""" - return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) - @property def extra_state_attributes(self): """Return the state attributes.""" diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index efff36770091c9..ee8f68734ade26 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -8,7 +8,13 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.icon import icon_for_battery_level -from . import DATA_RAINCLOUD, ICON_MAP, SENSORS, RainCloudEntity +from . import ( + DATA_RAINCLOUD, + ICON_MAP, + SENSORS, + UNIT_OF_MEASUREMENT_MAP, + RainCloudEntity, +) _LOGGER = logging.getLogger(__name__) @@ -46,6 +52,11 @@ def state(self): """Return the state of the sensor.""" return self._state + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) + def update(self): """Get the latest data and updates the states.""" _LOGGER.debug("Updating RainCloud sensor: %s", self._name) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 292a6050f9a6e3..48d37312225c3f 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -267,11 +267,6 @@ def attribute_value(self): return self.description.value(value) - @property - def unit_of_measurement(self): - """Return unit of sensor.""" - return self._unit - @property def device_class(self): """Device class of sensor.""" @@ -348,11 +343,6 @@ def attribute_value(self): ) return self._last_value - @property - def unit_of_measurement(self): - """Return unit of sensor.""" - return self.description.unit - @property def device_class(self): """Device class of sensor.""" diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ae9e2d740d3772..9e24034bf8336d 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -212,6 +212,11 @@ def state(self): """Return value of sensor.""" return self.attribute_value + @property + def unit_of_measurement(self): + """Return unit of sensor.""" + return self._unit + class ShellyRestSensor(ShellyRestAttributeEntity, SensorEntity): """Represent a shelly REST sensor.""" @@ -221,6 +226,11 @@ def state(self): """Return value of sensor.""" return self.attribute_value + @property + def unit_of_measurement(self): + """Return unit of sensor.""" + return self.description.unit + class ShellySleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): """Represent a shelly sleeping sensor.""" @@ -232,3 +242,8 @@ def state(self): return self.attribute_value return self.last_state + + @property + def unit_of_measurement(self): + """Return unit of sensor.""" + return self._unit diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 673cb6717fcdf9..960d6321c21298 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -72,7 +72,6 @@ STORAGE_VOL_SENSORS, SYNO_API, SYSTEM_LOADED, - TEMP_SENSORS_KEYS, UNDO_UPDATE_LISTENER, UTILISATION_SENSORS, ) @@ -631,13 +630,6 @@ def icon(self) -> str: """Return the icon.""" return self._icon - @property - def unit_of_measurement(self) -> str: - """Return the unit the value is expressed in.""" - if self.entity_type in TEMP_SENSORS_KEYS: - return self.hass.config.units.temperature_unit - return self._unit - @property def device_class(self) -> str: """Return the class of this device.""" diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 56eb56df07d277..22f41601e7baa2 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -87,7 +87,18 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMBaseEntity, SensorEntity): +class SynoDSMSensor(SynologyDSMBaseEntity): + """Mixin for sensor specific attributes.""" + + @property + def unit_of_measurement(self) -> str: + """Return the unit the value is expressed in.""" + if self.entity_type in TEMP_SENSORS_KEYS: + return self.hass.config.units.temperature_unit + return self._unit + + +class SynoDSMUtilSensor(SynoDSMSensor, SensorEntity): """Representation a Synology Utilisation sensor.""" @property @@ -119,7 +130,7 @@ def available(self) -> bool: return bool(self._api.utilisation) -class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SensorEntity): +class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SynoDSMSensor, SensorEntity): """Representation a Synology Storage sensor.""" @property @@ -140,7 +151,7 @@ def state(self): return attr -class SynoDSMInfoSensor(SynologyDSMBaseEntity, SensorEntity): +class SynoDSMInfoSensor(SynoDSMSensor, SensorEntity): """Representation a Synology information sensor.""" def __init__( diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 96be4fd3063df2..3ff869767a95a8 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -967,11 +967,6 @@ def unique_id(self) -> str: """Return a unique, Home Assistant friendly identifier for this entity.""" return self._unique_id - @property - def unit_of_measurement(self) -> str: - """Return the unit of measurement of this entity, if any.""" - return self._attribute.unit_of_measurement - @property def icon(self) -> str: """Icon to use in the frontend, if any.""" diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index 89bb81eb60764c..e26804f1f0a95b 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -35,3 +35,8 @@ class WithingsHealthSensor(BaseWithingsSensor, SensorEntity): def state(self) -> None | str | int | float: """Return the state of the entity.""" return self._state_data + + @property + def unit_of_measurement(self) -> str: + """Return the unit of measurement of this entity, if any.""" + return self._attribute.unit_of_measurement diff --git a/homeassistant/components/xbee/__init__.py b/homeassistant/components/xbee/__init__.py index 6373cfa7535564..58ce7587070e21 100644 --- a/homeassistant/components/xbee/__init__.py +++ b/homeassistant/components/xbee/__init__.py @@ -9,6 +9,7 @@ from xbee_helper.device import convert_adc from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE, @@ -365,7 +366,7 @@ def update(self): self._state = self._config.state2bool[pin_state] -class XBeeAnalogIn(Entity): +class XBeeAnalogIn(SensorEntity): """Representation of a GPIO pin configured as an analog input.""" def __init__(self, config, device): From 269608d1af54c20a61f130ba9f0e0e8b8c2251b0 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 23 Mar 2021 11:03:16 -0400 Subject: [PATCH 1468/1818] Bump up ZHA dependencies (#48257) --- 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 5eee54a5c71605..6493bd138bf621 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -9,7 +9,7 @@ "pyserial-asyncio==0.5", "zha-quirks==0.0.54", "zigpy-cc==0.5.2", - "zigpy-deconz==0.11.1", + "zigpy-deconz==0.12.0", "zigpy==0.33.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.7.3", diff --git a/requirements_all.txt b/requirements_all.txt index 48ac1318b0468a..95691cd3f43236 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2378,7 +2378,7 @@ ziggo-mediabox-xl==1.1.0 zigpy-cc==0.5.2 # homeassistant.components.zha -zigpy-deconz==0.11.1 +zigpy-deconz==0.12.0 # homeassistant.components.zha zigpy-xbee==0.13.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7c3297de9f1441..39d823d4e1862c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1224,7 +1224,7 @@ zha-quirks==0.0.54 zigpy-cc==0.5.2 # homeassistant.components.zha -zigpy-deconz==0.11.1 +zigpy-deconz==0.12.0 # homeassistant.components.zha zigpy-xbee==0.13.0 From cc73cbcace71d911b8ce2e8ddd41209bd4712d5b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 23 Mar 2021 18:45:40 +0100 Subject: [PATCH 1469/1818] Update issue form to use latest changes (#48250) --- .github/ISSUE_TEMPLATE/bug_report.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9a46dd82215f73..384cc5834cdb24 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Report an issue with Home Assistant Core -about: Report an issue with Home Assistant Core. +description: Report an issue with Home Assistant Core. title: "" issue_body: true body: @@ -29,6 +29,7 @@ body: validations: required: true attributes: + id: version label: What is version of Home Assistant Core has the issue? placeholder: core- description: > @@ -52,11 +53,13 @@ body: - Home Assistant Supervised - Home Assistant Core - type: input + id: integration_name attributes: label: Integration causing the issue description: > The name of the integration, for example, Automation or Philips Hue. - type: input + id: integration_link attributes: label: Link to integration documentation on our website placeholder: "https://www.home-assistant.io/integrations/..." @@ -76,20 +79,12 @@ body: description: | If this issue has an example piece of YAML that can help reproducing this problem, please provide. This can be an piece of YAML from, e.g., an automation, script, scene or configuration. - value: | - ```yaml - # Put your YAML below this line - - ``` + render: yaml - type: textarea attributes: label: Anything in the logs that might be useful for us? description: For example, error message, or stack traces. - value: | - ```txt - # Put your logs below this line - - ``` + render: txt - type: markdown attributes: value: | From d129b8e1e1062a1d9a8085c1ef9f9600532fb172 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Tue, 23 Mar 2021 20:03:54 +0100 Subject: [PATCH 1470/1818] Update pypoint to 2.1.0 (#48223) * update pypoint to 2.1.0 * Add properties and device_classes to constant * Fix unique_ids for binary_sensors * Update device icon * Fallback to device_class icon. Co-authored-by: Erik Montnemery * Just use known events * Use DEVICE_CLASS_SOUND Co-authored-by: Erik Montnemery --- .../components/point/binary_sensor.py | 87 +++++++++++-------- homeassistant/components/point/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index d82ecd096ee2c8..0bcd2a33a2ed76 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -1,8 +1,16 @@ """Support for Minut Point binary sensors.""" import logging +from pypoint import EVENTS + from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_COLD, DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_HEAT, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SOUND, DOMAIN, BinarySensorEntity, ) @@ -14,37 +22,22 @@ _LOGGER = logging.getLogger(__name__) -EVENTS = { - "battery": ("battery_low", ""), # On means low, Off means normal - "button_press": ( # On means the button was pressed, Off means normal - "short_button_press", - "", - ), - "cold": ( # On means cold, Off means normal - "temperature_low", - "temperature_risen_normal", - ), - "connectivity": ( # On means connected, Off means disconnected - "device_online", - "device_offline", - ), - "dry": ( # On means too dry, Off means normal - "humidity_low", - "humidity_risen_normal", - ), - "heat": ( # On means hot, Off means normal - "temperature_high", - "temperature_dropped_normal", - ), - "moisture": ( # On means wet, Off means dry - "humidity_high", - "humidity_dropped_normal", - ), - "sound": ( # On means sound detected, Off means no sound (clear) - "avg_sound_high", - "sound_level_dropped_normal", - ), - "tamper": ("tamper", ""), # On means the point was removed or attached + +DEVICES = { + "alarm": {"icon": "mdi:alarm-bell"}, + "battery": {"device_class": DEVICE_CLASS_BATTERY}, + "button_press": {"icon": "mdi:gesture-tap-button"}, + "cold": {"device_class": DEVICE_CLASS_COLD}, + "connectivity": {"device_class": DEVICE_CLASS_CONNECTIVITY}, + "dry": {"icon": "mdi:water"}, + "glass": {"icon": "mdi:window-closed-variant"}, + "heat": {"device_class": DEVICE_CLASS_HEAT}, + "moisture": {"device_class": DEVICE_CLASS_MOISTURE}, + "motion": {"device_class": DEVICE_CLASS_MOTION}, + "noise": {"icon": "mdi:volume-high"}, + "sound": {"device_class": DEVICE_CLASS_SOUND}, + "tamper_old": {"icon": "mdi:shield-alert"}, + "tamper": {"icon": "mdi:shield-alert"}, } @@ -56,8 +49,9 @@ async def async_discover_sensor(device_id): client = hass.data[POINT_DOMAIN][config_entry.entry_id] async_add_entities( ( - MinutPointBinarySensor(client, device_id, device_class) - for device_class in EVENTS + MinutPointBinarySensor(client, device_id, device_name) + for device_name in DEVICES + if device_name in EVENTS ), True, ) @@ -70,12 +64,16 @@ async def async_discover_sensor(device_id): class MinutPointBinarySensor(MinutPointEntity, BinarySensorEntity): """The platform class required by Home Assistant.""" - def __init__(self, point_client, device_id, device_class): + def __init__(self, point_client, device_id, device_name): """Initialize the binary sensor.""" - super().__init__(point_client, device_id, device_class) - + super().__init__( + point_client, + device_id, + DEVICES[device_name].get("device_class"), + ) + self._device_name = device_name self._async_unsub_hook_dispatcher_connect = None - self._events = EVENTS[device_class] + self._events = EVENTS[device_name] self._is_on = None async def async_added_to_hass(self): @@ -124,3 +122,18 @@ def is_on(self): # connectivity is the other way around. return not self._is_on return self._is_on + + @property + def name(self): + """Return the display name of this device.""" + return f"{self._name} {self._device_name.capitalize()}" + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return DEVICES[self._device_name].get("icon") + + @property + def unique_id(self): + """Return the unique id of the sensor.""" + return f"point.{self._id}-{self._device_name}" diff --git a/homeassistant/components/point/manifest.json b/homeassistant/components/point/manifest.json index 6c25cdef91a2aa..899e5615b40617 100644 --- a/homeassistant/components/point/manifest.json +++ b/homeassistant/components/point/manifest.json @@ -3,7 +3,7 @@ "name": "Minut Point", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/point", - "requirements": ["pypoint==2.0.0"], + "requirements": ["pypoint==2.1.0"], "dependencies": ["webhook", "http"], "codeowners": ["@fredrike"], "quality_scale": "gold" diff --git a/requirements_all.txt b/requirements_all.txt index 95691cd3f43236..203cecd6525460 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1622,7 +1622,7 @@ pypjlink2==1.2.1 pyplaato==0.0.15 # homeassistant.components.point -pypoint==2.0.0 +pypoint==2.1.0 # homeassistant.components.profiler pyprof2calltree==1.4.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 39d823d4e1862c..2ce2cac591b073 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -867,7 +867,7 @@ pypck==0.7.9 pyplaato==0.0.15 # homeassistant.components.point -pypoint==2.0.0 +pypoint==2.1.0 # homeassistant.components.profiler pyprof2calltree==1.4.5 From fd5916e067af38639885a6cea1ec1dfc4b33dcf5 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Tue, 23 Mar 2021 19:19:47 +0000 Subject: [PATCH 1471/1818] datetime must be a string (#47809) --- homeassistant/components/buienradar/weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index 4b0391c3190cad..2ff638a2550914 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -201,7 +201,7 @@ def forecast(self): # keys understood by the weather component: condcode = data_in.get(CONDITION, []).get(CONDCODE) data_out = { - ATTR_FORECAST_TIME: data_in.get(DATETIME), + ATTR_FORECAST_TIME: data_in.get(DATETIME).isoformat(), ATTR_FORECAST_CONDITION: cond[condcode], ATTR_FORECAST_TEMP_LOW: data_in.get(MIN_TEMP), ATTR_FORECAST_TEMP: data_in.get(MAX_TEMP), From 49b47fe64890fe37217c4afb1935a9307b9665f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 23 Mar 2021 22:04:15 +0100 Subject: [PATCH 1472/1818] Install requirements.txt while building dev Dockerfile (#48268) --- Dockerfile.dev | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 09f8f155930300..68188f16f012ae 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -28,10 +28,11 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \ WORKDIR /workspaces # Install Python dependencies from requirements -COPY requirements_test.txt requirements_test_pre_commit.txt ./ +COPY requirements.txt requirements_test.txt requirements_test_pre_commit.txt ./ COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt -RUN pip3 install -r requirements_test.txt \ - && rm -rf requirements_test.txt requirements_test_pre_commit.txt homeassistant/ +RUN pip3 install -r requirements.txt \ + && pip3 install -r requirements_test.txt \ + && rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/ # Set the default shell to bash instead of sh ENV SHELL /bin/bash From 70d9e8a582df92b26640dcb871ec7b4a5589af09 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 23 Mar 2021 22:29:55 +0100 Subject: [PATCH 1473/1818] Add proper percentage support to deCONZ fan integration (#48187) * Add proper percentage support to deCONZ fan integration * Properly convert speed to percentage * Remove disabled method * Replace convert_speed with a dict --- homeassistant/components/deconz/fan.py | 119 ++++++--- tests/components/deconz/test_fan.py | 327 ++++++++++++++++++++++++- 2 files changed, 402 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 1ca4c8ff9c2845..3d2e61fdd7b194 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,4 +1,7 @@ -"""Support for deCONZ switches.""" +"""Support for deCONZ fans.""" + +from typing import Optional + from homeassistant.components.fan import ( DOMAIN, SPEED_HIGH, @@ -10,25 +13,19 @@ ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, +) from .const import FANS, NEW_LIGHT from .deconz_device import DeconzDevice from .gateway import get_gateway_from_config_entry -SPEEDS = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 2, SPEED_HIGH: 4} -SUPPORTED_ON_SPEEDS = {1: SPEED_LOW, 2: SPEED_MEDIUM, 4: SPEED_HIGH} +ORDERED_NAMED_FAN_SPEEDS = [1, 2, 3, 4] - -def convert_speed(speed: int) -> str: - """Convert speed from deCONZ to HASS. - - Fallback to medium speed if unsupported by HASS fan platform. - """ - if speed in SPEEDS.values(): - for hass_speed, deconz_speed in SPEEDS.items(): - if speed == deconz_speed: - return hass_speed - return SPEED_MEDIUM +LEGACY_SPEED_TO_DECONZ = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 2, SPEED_HIGH: 4} +LEGACY_DECONZ_TO_SPEED = {0: SPEED_OFF, 1: SPEED_LOW, 2: SPEED_MEDIUM, 4: SPEED_HIGH} async def async_setup_entry(hass, config_entry, async_add_entities) -> None: @@ -67,8 +64,8 @@ def __init__(self, device, gateway) -> None: """Set up fan.""" super().__init__(device, gateway) - self._default_on_speed = SPEEDS[SPEED_MEDIUM] - if self.speed != SPEED_OFF: + self._default_on_speed = 2 + if self._device.speed in ORDERED_NAMED_FAN_SPEEDS: self._default_on_speed = self._device.speed self._features = SUPPORT_SET_SPEED @@ -76,17 +73,58 @@ def __init__(self, device, gateway) -> None: @property def is_on(self) -> bool: """Return true if fan is on.""" - return self.speed != SPEED_OFF + return self._device.speed != 0 @property - def speed(self) -> int: - """Return the current speed.""" - return convert_speed(self._device.speed) + def percentage(self) -> Optional[int]: + """Return the current speed percentage.""" + if self._device.speed == 0: + return 0 + if self._device.speed not in ORDERED_NAMED_FAN_SPEEDS: + return + return ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, self._device.speed + ) + + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports.""" + return len(ORDERED_NAMED_FAN_SPEEDS) @property def speed_list(self) -> list: - """Get the list of available speeds.""" - return list(SPEEDS) + """Get the list of available speeds. + + Legacy fan support. + """ + return list(LEGACY_SPEED_TO_DECONZ) + + def speed_to_percentage(self, speed: str) -> int: + """Convert speed to percentage. + + Legacy fan support. + """ + if speed == SPEED_OFF: + return 0 + + if speed not in LEGACY_SPEED_TO_DECONZ: + speed = SPEED_MEDIUM + + return ordered_list_item_to_percentage( + ORDERED_NAMED_FAN_SPEEDS, LEGACY_SPEED_TO_DECONZ[speed] + ) + + def percentage_to_speed(self, percentage: int) -> str: + """Convert percentage to speed. + + Legacy fan support. + """ + if percentage == 0: + return SPEED_OFF + return LEGACY_DECONZ_TO_SPEED.get( + percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage), + SPEED_MEDIUM, + ) @property def supported_features(self) -> int: @@ -96,24 +134,26 @@ def supported_features(self) -> int: @callback def async_update_callback(self, force_update=False) -> None: """Store latest configured speed from the device.""" - if self.speed != SPEED_OFF and self._device.speed != self._default_on_speed: + if self._device.speed in ORDERED_NAMED_FAN_SPEEDS: self._default_on_speed = self._device.speed super().async_update_callback(force_update) + async def async_set_percentage(self, percentage: int) -> None: + """Set the speed percentage of the fan.""" + await self._device.set_speed( + percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage) + ) + async def async_set_speed(self, speed: str) -> None: - """Set the speed of the fan.""" - if speed not in SPEEDS: + """Set the speed of the fan. + + Legacy fan support. + """ + if speed not in LEGACY_SPEED_TO_DECONZ: raise ValueError(f"Unsupported speed {speed}") - await self._device.set_speed(SPEEDS[speed]) + await self._device.set_speed(LEGACY_SPEED_TO_DECONZ[speed]) - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # async def async_turn_on( self, speed: str = None, @@ -122,10 +162,15 @@ async def async_turn_on( **kwargs, ) -> None: """Turn on fan.""" - if not speed: - speed = convert_speed(self._default_on_speed) - await self.async_set_speed(speed) + new_speed = self._default_on_speed + + if percentage is not None: + new_speed = percentage_to_ordered_list_item( + ORDERED_NAMED_FAN_SPEEDS, percentage + ) + + await self._device.set_speed(new_speed) async def async_turn_off(self, **kwargs) -> None: """Turn off fan.""" - await self.async_set_speed(SPEED_OFF) + await self._device.set_speed(0) diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index 930645689f8b08..ddd4a4e46f48e7 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -5,8 +5,10 @@ import pytest from homeassistant.components.fan import ( + ATTR_PERCENTAGE, ATTR_SPEED, DOMAIN as FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, SERVICE_SET_SPEED, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -59,10 +61,62 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): assert len(hass.states.async_all()) == 2 # Light and fan assert hass.states.get("fan.ceiling_fan").state == STATE_ON - assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 100 # Test states + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 1}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 25 + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 2}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 50 + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 3}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 75 + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 4}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 100 + event_changed_light = { "t": "event", "e": "changed", @@ -74,13 +128,13 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): await hass.async_block_till_done() assert hass.states.get("fan.ceiling_fan").state == STATE_OFF - assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_OFF + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 0 # Test service calls mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") - # Service turn on fan + # Service turn on fan using saved default_on_speed await hass.services.async_call( FAN_DOMAIN, @@ -100,6 +154,265 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): ) assert aioclient_mock.mock_calls[2][2] == {"speed": 0} + # Service turn on fan to 20% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 20}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"speed": 1} + + # Service set fan percentage to 20% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 20}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"speed": 1} + + # Service set fan percentage to 40% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"speed": 2} + + # Service set fan percentage to 60% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 60}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"speed": 3} + + # Service set fan percentage to 80% + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 80}, + blocking=True, + ) + assert aioclient_mock.mock_calls[7][2] == {"speed": 4} + + # Service set fan percentage to 0% does not equal off + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_PERCENTAGE: 0}, + blocking=True, + ) + assert aioclient_mock.mock_calls[8][2] == {"speed": 1} + + # Events with an unsupported speed does not get converted + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 5}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert not hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] + + await hass.config_entries.async_unload(config_entry.entry_id) + + states = hass.states.async_all() + assert len(states) == 2 + for state in states: + assert state.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_fans_legacy_speed_modes(hass, aioclient_mock, mock_deconz_websocket): + """Test that all supported fan entities are created. + + Legacy fan support. + """ + data = { + "lights": { + "1": { + "etag": "432f3de28965052961a99e3c5494daf4", + "hascolor": False, + "manufacturername": "King Of Fans, Inc.", + "modelid": "HDC52EastwindFan", + "name": "Ceiling fan", + "state": { + "alert": "none", + "bri": 254, + "on": False, + "reachable": True, + "speed": 4, + }, + "swversion": "0000000F", + "type": "Fan", + "uniqueid": "00:22:a3:00:00:27:8b:81-01", + } + } + } + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 2 # Light and fan + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH + + # Test states + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 1}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 25 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_LOW + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 2}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 50 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_MEDIUM + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 3}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 75 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_MEDIUM + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 4}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_ON + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 100 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_HIGH + + event_changed_light = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"speed": 0}, + } + await mock_deconz_websocket(data=event_changed_light) + await hass.async_block_till_done() + + assert hass.states.get("fan.ceiling_fan").state == STATE_OFF + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_PERCENTAGE] == 0 + assert hass.states.get("fan.ceiling_fan").attributes[ATTR_SPEED] == SPEED_OFF + + # Test service calls + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") + + # Service turn on fan using saved default_on_speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"speed": 4} + + # Service turn on fan with speed_off + # async_turn_on_compat use speed_to_percentage which will return 0 + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_OFF}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"speed": 1} + + # Service turn on fan with bad speed + # async_turn_on_compat use speed_to_percentage which will convert to SPEED_MEDIUM -> 2 + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: "bad"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"speed": 2} + + # Service turn on fan to low speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_LOW}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"speed": 1} + + # Service turn on fan to medium speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_MEDIUM}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"speed": 2} + + # Service turn on fan to high speed + + await hass.services.async_call( + FAN_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_HIGH}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"speed": 4} + # Service set fan speed to low await hass.services.async_call( @@ -108,7 +421,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_LOW}, blocking=True, ) - assert aioclient_mock.mock_calls[3][2] == {"speed": 1} + assert aioclient_mock.mock_calls[7][2] == {"speed": 1} # Service set fan speed to medium @@ -118,7 +431,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_MEDIUM}, blocking=True, ) - assert aioclient_mock.mock_calls[4][2] == {"speed": 2} + assert aioclient_mock.mock_calls[8][2] == {"speed": 2} # Service set fan speed to high @@ -128,7 +441,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_HIGH}, blocking=True, ) - assert aioclient_mock.mock_calls[5][2] == {"speed": 4} + assert aioclient_mock.mock_calls[9][2] == {"speed": 4} # Service set fan speed to off @@ -138,7 +451,7 @@ async def test_fans(hass, aioclient_mock, mock_deconz_websocket): {ATTR_ENTITY_ID: "fan.ceiling_fan", ATTR_SPEED: SPEED_OFF}, blocking=True, ) - assert aioclient_mock.mock_calls[6][2] == {"speed": 0} + assert aioclient_mock.mock_calls[10][2] == {"speed": 0} # Service set fan speed to unsupported value From 195d4de6cd349ec79b73d0e57cf9ddfad15fd9d4 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 23 Mar 2021 16:47:00 -0500 Subject: [PATCH 1474/1818] Bump plexapi to 4.5.0 (#48264) --- homeassistant/components/plex/manifest.json | 2 +- .../components/plex/media_browser.py | 6 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/plex/conftest.py | 6 + tests/components/plex/test_browse_media.py | 11 +- .../plex/library_movies_filtertypes.xml | 103 ++++++++++++++++++ 7 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/plex/library_movies_filtertypes.xml diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 1319e4bbf4960b..647590f7cf2f59 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.4.1", + "plexapi==4.5.0", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index cfc5a12d6c5398..f3f92880c44c6d 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -153,7 +153,7 @@ def build_item_response(payload): title = entity.plex_server.friendly_name elif media_content_type == "library": library_or_section = entity.plex_server.library.sectionByID( - media_content_id + int(media_content_id) ) title = library_or_section.title try: @@ -193,7 +193,7 @@ def build_item_response(payload): return server_payload(entity.plex_server) if media_content_type == "library": - return library_payload(media_content_id) + return library_payload(int(media_content_id)) except UnknownMediaType as err: raise BrowseError( @@ -223,7 +223,7 @@ def library_section_payload(section): return BrowseMedia( title=section.title, media_class=MEDIA_CLASS_DIRECTORY, - media_content_id=section.key, + media_content_id=str(section.key), media_content_type="library", can_play=False, can_expand=True, diff --git a/requirements_all.txt b/requirements_all.txt index 203cecd6525460..67c0988bf0d17d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1131,7 +1131,7 @@ pillow==8.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.4.1 +plexapi==4.5.0 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2ce2cac591b073..115f77c35dce14 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -577,7 +577,7 @@ pilight==0.1.1 pillow==8.1.2 # homeassistant.components.plex -plexapi==4.4.1 +plexapi==4.5.0 # homeassistant.components.plex plexauth==0.0.6 diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index 372a06f15b62f9..295b560f4d1df6 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -108,6 +108,12 @@ def library_music_sort_fixture(): return load_fixture("plex/library_music_sort.xml") +@pytest.fixture(name="library_movies_filtertypes", scope="session") +def library_movies_filtertypes_fixture(): + """Load filtertypes payload for movie library and return it.""" + return load_fixture("plex/library_movies_filtertypes.xml") + + @pytest.fixture(name="library", scope="session") def library_fixture(): """Load library payload and return it.""" diff --git a/tests/components/plex/test_browse_media.py b/tests/components/plex/test_browse_media.py index f9966a18c27097..d9f02c493417a4 100644 --- a/tests/components/plex/test_browse_media.py +++ b/tests/components/plex/test_browse_media.py @@ -10,7 +10,9 @@ from .const import DEFAULT_DATA -async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_mock): +async def test_browse_media( + hass, hass_ws_client, mock_plex_server, requests_mock, library_movies_filtertypes +): """Test getting Plex clients from plex.tv.""" websocket_client = await hass_ws_client(hass) @@ -86,6 +88,11 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_moc assert len(result["children"]) == len(mock_plex_server.library.onDeck()) # Browse into a special folder (library) + requests_mock.get( + f"{mock_plex_server.url_in_use}/library/sections/1/all?includeMeta=1", + text=library_movies_filtertypes, + ) + msg_id += 1 library_section_id = next(iter(mock_plex_server.library.sections())).key await websocket_client.send_json( @@ -127,7 +134,7 @@ async def test_browse_media(hass, hass_ws_client, mock_plex_server, requests_moc assert msg["success"] result = msg["result"] assert result[ATTR_MEDIA_CONTENT_TYPE] == "library" - result_id = result[ATTR_MEDIA_CONTENT_ID] + result_id = int(result[ATTR_MEDIA_CONTENT_ID]) assert len(result["children"]) == len( mock_plex_server.library.sectionByID(result_id).all() ) + len(SPECIAL_METHODS) diff --git a/tests/fixtures/plex/library_movies_filtertypes.xml b/tests/fixtures/plex/library_movies_filtertypes.xml new file mode 100644 index 00000000000000..0f305f385c22a5 --- /dev/null +++ b/tests/fixtures/plex/library_movies_filtertypes.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9f8b697e6408a2dc8df2caefda316f785f334d54 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 23 Mar 2021 22:53:38 +0100 Subject: [PATCH 1475/1818] Refactor tracing: Prepare for tracing of scripts (#48231) --- homeassistant/components/automation/trace.py | 35 +-- homeassistant/components/trace/trace.py | 18 +- .../components/trace/websocket_api.py | 155 +++++------ homeassistant/helpers/script.py | 62 ++--- tests/components/trace/test_websocket_api.py | 243 +++++++++++------- 5 files changed, 283 insertions(+), 230 deletions(-) diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index de199ad9310014..f5e93e09c38dd0 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -23,7 +23,7 @@ class AutomationTrace: def __init__( self, - unique_id: str | None, + key: tuple[str, str], config: dict[str, Any], context: Context, ): @@ -37,7 +37,7 @@ def __init__( self.run_id: str = str(next(self._run_ids)) self._timestamp_finish: dt.datetime | None = None self._timestamp_start: dt.datetime = dt_util.utcnow() - self._unique_id: str | None = unique_id + self._key: tuple[str, str] = key self._variables: dict[str, Any] | None = None def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: @@ -104,7 +104,6 @@ def as_short_dict(self) -> dict[str, Any]: trigger = self._variables.get("trigger", {}).get("description") result = { - "automation_id": self._unique_id, "last_action": last_action, "last_condition": last_condition, "run_id": self.run_id, @@ -114,7 +113,8 @@ def as_short_dict(self) -> dict[str, Any]: "finish": self._timestamp_finish, }, "trigger": trigger, - "unique_id": self._unique_id, + "domain": self._key[0], + "item_id": self._key[1], } if self._error is not None: result["error"] = str(self._error) @@ -126,23 +126,24 @@ def as_short_dict(self) -> dict[str, Any]: @contextmanager -def trace_automation(hass, unique_id, config, context): +def trace_automation(hass, item_id, config, context): """Trace action execution of automation with automation_id.""" - automation_trace = AutomationTrace(unique_id, config, context) - trace_id_set((unique_id, automation_trace.run_id)) + key = ("automation", item_id) + trace = AutomationTrace(key, config, context) + trace_id_set((key, trace.run_id)) - if unique_id: - automation_traces = hass.data[DATA_TRACE] - if unique_id not in automation_traces: - automation_traces[unique_id] = LimitedSizeDict(size_limit=STORED_TRACES) - automation_traces[unique_id][automation_trace.run_id] = automation_trace + if key: + traces = hass.data[DATA_TRACE] + if key not in traces: + traces[key] = LimitedSizeDict(size_limit=STORED_TRACES) + traces[key][trace.run_id] = trace try: - yield automation_trace + yield trace except Exception as ex: # pylint: disable=broad-except - if unique_id: - automation_trace.set_error(ex) + if key: + trace.set_error(ex) raise ex finally: - if unique_id: - automation_trace.finished() + if key: + trace.finished() diff --git a/homeassistant/components/trace/trace.py b/homeassistant/components/trace/trace.py index cc51e3269ab21f..e9255550ec5fd3 100644 --- a/homeassistant/components/trace/trace.py +++ b/homeassistant/components/trace/trace.py @@ -5,17 +5,17 @@ @callback -def get_debug_trace(hass, automation_id, run_id): +def get_debug_trace(hass, key, run_id): """Return a serializable debug trace.""" - return hass.data[DATA_TRACE][automation_id][run_id] + return hass.data[DATA_TRACE][key][run_id] @callback -def get_debug_traces_for_automation(hass, automation_id, summary=False): - """Return a serializable list of debug traces for an automation.""" +def get_debug_traces(hass, key, summary=False): + """Return a serializable list of debug traces for an automation or script.""" traces = [] - for trace in hass.data[DATA_TRACE].get(automation_id, {}).values(): + for trace in hass.data[DATA_TRACE].get(key, {}).values(): if summary: traces.append(trace.as_short_dict()) else: @@ -25,11 +25,11 @@ def get_debug_traces_for_automation(hass, automation_id, summary=False): @callback -def get_debug_traces(hass, summary=False): - """Return a serializable list of debug traces.""" +def get_all_debug_traces(hass, summary=False): + """Return a serializable list of debug traces for all automations and scripts.""" traces = [] - for automation_id in hass.data[DATA_TRACE]: - traces.extend(get_debug_traces_for_automation(hass, automation_id, summary)) + for key in hass.data[DATA_TRACE]: + traces.extend(get_debug_traces(hass, key, summary)) return traces diff --git a/homeassistant/components/trace/websocket_api.py b/homeassistant/components/trace/websocket_api.py index 9ac1828de143d9..1f42b50671ec4e 100644 --- a/homeassistant/components/trace/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -23,29 +23,26 @@ debug_stop, ) -from .trace import ( - DATA_TRACE, - get_debug_trace, - get_debug_traces, - get_debug_traces_for_automation, -) +from .trace import DATA_TRACE, get_all_debug_traces, get_debug_trace, get_debug_traces from .utils import TraceJSONEncoder # mypy: allow-untyped-calls, allow-untyped-defs +TRACE_DOMAINS = ["automation"] + @callback def async_setup(hass: HomeAssistant) -> None: """Set up the websocket API.""" - websocket_api.async_register_command(hass, websocket_automation_trace_get) - websocket_api.async_register_command(hass, websocket_automation_trace_list) - websocket_api.async_register_command(hass, websocket_automation_trace_contexts) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_clear) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_list) - websocket_api.async_register_command(hass, websocket_automation_breakpoint_set) - websocket_api.async_register_command(hass, websocket_automation_debug_continue) - websocket_api.async_register_command(hass, websocket_automation_debug_step) - websocket_api.async_register_command(hass, websocket_automation_debug_stop) + websocket_api.async_register_command(hass, websocket_trace_get) + websocket_api.async_register_command(hass, websocket_trace_list) + websocket_api.async_register_command(hass, websocket_trace_contexts) + websocket_api.async_register_command(hass, websocket_breakpoint_clear) + websocket_api.async_register_command(hass, websocket_breakpoint_list) + websocket_api.async_register_command(hass, websocket_breakpoint_set) + websocket_api.async_register_command(hass, websocket_debug_continue) + websocket_api.async_register_command(hass, websocket_debug_step) + websocket_api.async_register_command(hass, websocket_debug_stop) websocket_api.async_register_command(hass, websocket_subscribe_breakpoint_events) @@ -53,17 +50,18 @@ def async_setup(hass: HomeAssistant) -> None: @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/trace/get", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/get", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_trace_get(hass, connection, msg): - """Get an automation trace.""" - automation_id = msg["automation_id"] +def websocket_trace_get(hass, connection, msg): + """Get an automation or script trace.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - trace = get_debug_trace(hass, automation_id, run_id) + trace = get_debug_trace(hass, key, run_id) message = websocket_api.messages.result_message(msg["id"], trace) connection.send_message(json.dumps(message, cls=TraceJSONEncoder, allow_nan=False)) @@ -72,42 +70,45 @@ def websocket_automation_trace_get(hass, connection, msg): @callback @websocket_api.require_admin @websocket_api.websocket_command( - {vol.Required("type"): "automation/trace/list", vol.Optional("automation_id"): str} + { + vol.Required("type"): "trace/list", + vol.Inclusive("domain", "id"): vol.In(TRACE_DOMAINS), + vol.Inclusive("item_id", "id"): str, + } ) -def websocket_automation_trace_list(hass, connection, msg): - """Summarize automation traces.""" - automation_id = msg.get("automation_id") +def websocket_trace_list(hass, connection, msg): + """Summarize automation and script traces.""" + key = (msg["domain"], msg["item_id"]) if "item_id" in msg else None - if not automation_id: - automation_traces = get_debug_traces(hass, summary=True) + if not key: + traces = get_all_debug_traces(hass, summary=True) else: - automation_traces = get_debug_traces_for_automation( - hass, automation_id, summary=True - ) + traces = get_debug_traces(hass, key, summary=True) - connection.send_result(msg["id"], automation_traces) + connection.send_result(msg["id"], traces) @callback @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/trace/contexts", - vol.Optional("automation_id"): str, + vol.Required("type"): "trace/contexts", + vol.Inclusive("domain", "id"): vol.In(TRACE_DOMAINS), + vol.Inclusive("item_id", "id"): str, } ) -def websocket_automation_trace_contexts(hass, connection, msg): +def websocket_trace_contexts(hass, connection, msg): """Retrieve contexts we have traces for.""" - automation_id = msg.get("automation_id") + key = (msg["domain"], msg["item_id"]) if "item_id" in msg else None - if automation_id is not None: - values = {automation_id: hass.data[DATA_TRACE].get(automation_id, {})} + if key is not None: + values = {key: hass.data[DATA_TRACE].get(key, {})} else: values = hass.data[DATA_TRACE] contexts = { - trace.context.id: {"run_id": trace.run_id, "automation_id": automation_id} - for automation_id, traces in values.items() + trace.context.id: {"run_id": trace.run_id, "domain": key[0], "item_id": key[1]} + for key, traces in values.items() for trace in traces.values() } @@ -118,15 +119,16 @@ def websocket_automation_trace_contexts(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/breakpoint/set", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/breakpoint/set", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("node"): str, vol.Optional("run_id"): str, } ) -def websocket_automation_breakpoint_set(hass, connection, msg): +def websocket_breakpoint_set(hass, connection, msg): """Set breakpoint.""" - automation_id = msg["automation_id"] + key = (msg["domain"], msg["item_id"]) node = msg["node"] run_id = msg.get("run_id") @@ -136,7 +138,7 @@ def websocket_automation_breakpoint_set(hass, connection, msg): ): raise HomeAssistantError("No breakpoint subscription") - result = breakpoint_set(hass, automation_id, run_id, node) + result = breakpoint_set(hass, key, run_id, node) connection.send_result(msg["id"], result) @@ -144,33 +146,32 @@ def websocket_automation_breakpoint_set(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/breakpoint/clear", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/breakpoint/clear", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("node"): str, vol.Optional("run_id"): str, } ) -def websocket_automation_breakpoint_clear(hass, connection, msg): +def websocket_breakpoint_clear(hass, connection, msg): """Clear breakpoint.""" - automation_id = msg["automation_id"] + key = (msg["domain"], msg["item_id"]) node = msg["node"] run_id = msg.get("run_id") - result = breakpoint_clear(hass, automation_id, run_id, node) + result = breakpoint_clear(hass, key, run_id, node) connection.send_result(msg["id"], result) @callback @websocket_api.require_admin -@websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/list"} -) -def websocket_automation_breakpoint_list(hass, connection, msg): +@websocket_api.websocket_command({vol.Required("type"): "trace/debug/breakpoint/list"}) +def websocket_breakpoint_list(hass, connection, msg): """List breakpoints.""" breakpoints = breakpoint_list(hass) for _breakpoint in breakpoints: - _breakpoint["automation_id"] = _breakpoint.pop("unique_id") + _breakpoint["domain"], _breakpoint["item_id"] = _breakpoint.pop("key") connection.send_result(msg["id"], breakpoints) @@ -178,19 +179,20 @@ def websocket_automation_breakpoint_list(hass, connection, msg): @callback @websocket_api.require_admin @websocket_api.websocket_command( - {vol.Required("type"): "automation/debug/breakpoint/subscribe"} + {vol.Required("type"): "trace/debug/breakpoint/subscribe"} ) def websocket_subscribe_breakpoint_events(hass, connection, msg): """Subscribe to breakpoint events.""" @callback - def breakpoint_hit(automation_id, run_id, node): + def breakpoint_hit(key, run_id, node): """Forward events to websocket.""" connection.send_message( websocket_api.event_message( msg["id"], { - "automation_id": automation_id, + "domain": key[0], + "item_id": key[1], "run_id": run_id, "node": node, }, @@ -221,17 +223,18 @@ def unsub(): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/continue", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/continue", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_debug_continue(hass, connection, msg): - """Resume execution of halted automation.""" - automation_id = msg["automation_id"] +def websocket_debug_continue(hass, connection, msg): + """Resume execution of halted automation or script.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - result = debug_continue(hass, automation_id, run_id) + result = debug_continue(hass, key, run_id) connection.send_result(msg["id"], result) @@ -240,17 +243,18 @@ def websocket_automation_debug_continue(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/step", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/step", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_debug_step(hass, connection, msg): - """Single step a halted automation.""" - automation_id = msg["automation_id"] +def websocket_debug_step(hass, connection, msg): + """Single step a halted automation or script.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - result = debug_step(hass, automation_id, run_id) + result = debug_step(hass, key, run_id) connection.send_result(msg["id"], result) @@ -259,16 +263,17 @@ def websocket_automation_debug_step(hass, connection, msg): @websocket_api.require_admin @websocket_api.websocket_command( { - vol.Required("type"): "automation/debug/stop", - vol.Required("automation_id"): str, + vol.Required("type"): "trace/debug/stop", + vol.Required("domain"): vol.In(TRACE_DOMAINS), + vol.Required("item_id"): str, vol.Required("run_id"): str, } ) -def websocket_automation_debug_stop(hass, connection, msg): - """Stop a halted automation.""" - automation_id = msg["automation_id"] +def websocket_debug_stop(hass, connection, msg): + """Stop a halted automation or script.""" + key = (msg["domain"], msg["item_id"]) run_id = msg["run_id"] - result = debug_stop(hass, automation_id, run_id) + result = debug_stop(hass, key, run_id) connection.send_result(msg["id"], result) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index d07d963c95d8a6..3df5be09f133c8 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -143,26 +143,26 @@ async def trace_action(hass, script_run, stop, variables): trace_id = trace_id_get() if trace_id: - unique_id = trace_id[0] + key = trace_id[0] run_id = trace_id[1] breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] - if unique_id in breakpoints and ( + if key in breakpoints and ( ( - run_id in breakpoints[unique_id] + run_id in breakpoints[key] and ( - path in breakpoints[unique_id][run_id] - or NODE_ANY in breakpoints[unique_id][run_id] + path in breakpoints[key][run_id] + or NODE_ANY in breakpoints[key][run_id] ) ) or ( - RUN_ID_ANY in breakpoints[unique_id] + RUN_ID_ANY in breakpoints[key] and ( - path in breakpoints[unique_id][RUN_ID_ANY] - or NODE_ANY in breakpoints[unique_id][RUN_ID_ANY] + path in breakpoints[key][RUN_ID_ANY] + or NODE_ANY in breakpoints[key][RUN_ID_ANY] ) ) ): - async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, unique_id, run_id, path) + async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, key, run_id, path) done = asyncio.Event() @@ -172,7 +172,7 @@ def async_continue_stop(command=None): stop.set() done.set() - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) remove_signal1 = async_dispatcher_connect(hass, signal, async_continue_stop) remove_signal2 = async_dispatcher_connect( hass, SCRIPT_DEBUG_CONTINUE_ALL, async_continue_stop @@ -1281,13 +1281,13 @@ def _log( @callback -def breakpoint_clear(hass, unique_id, run_id, node): +def breakpoint_clear(hass, key, run_id, node): """Clear a breakpoint.""" run_id = run_id or RUN_ID_ANY breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] - if unique_id not in breakpoints or run_id not in breakpoints[unique_id]: + if key not in breakpoints or run_id not in breakpoints[key]: return - breakpoints[unique_id][run_id].discard(node) + breakpoints[key][run_id].discard(node) @callback @@ -1297,15 +1297,15 @@ def breakpoint_clear_all(hass): @callback -def breakpoint_set(hass, unique_id, run_id, node): +def breakpoint_set(hass, key, run_id, node): """Set a breakpoint.""" run_id = run_id or RUN_ID_ANY breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] - if unique_id not in breakpoints: - breakpoints[unique_id] = {} - if run_id not in breakpoints[unique_id]: - breakpoints[unique_id][run_id] = set() - breakpoints[unique_id][run_id].add(node) + if key not in breakpoints: + breakpoints[key] = {} + if run_id not in breakpoints[key]: + breakpoints[key][run_id] = set() + breakpoints[key][run_id].add(node) @callback @@ -1314,35 +1314,35 @@ def breakpoint_list(hass): breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS] return [ - {"unique_id": unique_id, "run_id": run_id, "node": node} - for unique_id in breakpoints - for run_id in breakpoints[unique_id] - for node in breakpoints[unique_id][run_id] + {"key": key, "run_id": run_id, "node": node} + for key in breakpoints + for run_id in breakpoints[key] + for node in breakpoints[key][run_id] ] @callback -def debug_continue(hass, unique_id, run_id): +def debug_continue(hass, key, run_id): """Continue execution of a halted script.""" # Clear any wildcard breakpoint - breakpoint_clear(hass, unique_id, run_id, NODE_ANY) + breakpoint_clear(hass, key, run_id, NODE_ANY) - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) async_dispatcher_send(hass, signal, "continue") @callback -def debug_step(hass, unique_id, run_id): +def debug_step(hass, key, run_id): """Single step a halted script.""" # Set a wildcard breakpoint - breakpoint_set(hass, unique_id, run_id, NODE_ANY) + breakpoint_set(hass, key, run_id, NODE_ANY) - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) async_dispatcher_send(hass, signal, "continue") @callback -def debug_stop(hass, unique_id, run_id): +def debug_stop(hass, key, run_id): """Stop execution of a running or halted script.""" - signal = SCRIPT_DEBUG_CONTINUE_STOP.format(unique_id, run_id) + signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) async_dispatcher_send(hass, signal, "stop") diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index 882d857c01495b..e07c042d1d0ef6 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -2,25 +2,26 @@ from unittest.mock import patch from homeassistant.bootstrap import async_setup_component -from homeassistant.components import automation, config +from homeassistant.components import config +from homeassistant.components.trace.const import STORED_TRACES from homeassistant.core import Context from tests.common import assert_lists_same from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 -def _find_run_id(traces, automation_id): +def _find_run_id(traces, item_id): """Find newest run_id for an automation.""" for trace in reversed(traces): - if trace["automation_id"] == automation_id: + if trace["item_id"] == item_id: return trace["run_id"] return None -def _find_traces_for_automation(traces, automation_id): +def _find_traces_for_automation(traces, item_id): """Find traces for an automation.""" - return [trace for trace in traces if trace["automation_id"] == automation_id] + return [trace for trace in traces if trace["item_id"] == item_id] async def test_get_automation_trace(hass, hass_ws_client): @@ -85,7 +86,7 @@ def next_id(): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "sun") @@ -94,8 +95,9 @@ def next_id(): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "sun", + "type": "trace/get", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -113,11 +115,12 @@ def next_id(): assert trace["error"] == "Unable to find service test.automation" assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event'" - assert trace["unique_id"] == "sun" + assert trace["item_id"] == "sun" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Trigger "moon" automation, with passing condition @@ -125,7 +128,7 @@ def next_id(): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "moon") @@ -134,8 +137,9 @@ def next_id(): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "moon", + "type": "trace/get", + "domain": "automation", + "item_id": "moon", "run_id": run_id, } ) @@ -154,11 +158,12 @@ def next_id(): assert "error" not in trace assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Trigger "moon" automation, with failing condition @@ -166,7 +171,7 @@ def next_id(): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "moon") @@ -175,8 +180,9 @@ def next_id(): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "moon", + "type": "trace/get", + "domain": "automation", + "item_id": "moon", "run_id": run_id, } ) @@ -192,11 +198,12 @@ def next_id(): assert "error" not in trace assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Trigger "moon" automation, with passing condition @@ -204,7 +211,7 @@ def next_id(): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] run_id = _find_run_id(response["result"], "moon") @@ -213,8 +220,9 @@ def next_id(): await client.send_json( { "id": next_id(), - "type": "automation/trace/get", - "automation_id": "moon", + "type": "trace/get", + "domain": "automation", + "item_id": "moon", "run_id": run_id, } ) @@ -233,15 +241,16 @@ def next_id(): assert "error" not in trace assert trace["state"] == "stopped" assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "automation_id": trace["automation_id"], + "domain": "automation", + "item_id": trace["item_id"], } # Check contexts - await client.send_json({"id": next_id(), "type": "automation/trace/contexts"}) + await client.send_json({"id": next_id(), "type": "trace/contexts"}) response = await client.receive_json() assert response["success"] assert response["result"] == contexts @@ -283,7 +292,7 @@ def next_id(): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [] @@ -294,7 +303,7 @@ def next_id(): await hass.async_block_till_done() # List traces - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert len(_find_traces_for_automation(response["result"], "moon")) == 1 @@ -302,21 +311,18 @@ def next_id(): assert len(_find_traces_for_automation(response["result"], "sun")) == 1 # Trigger "moon" automation enough times to overflow the number of stored traces - for _ in range(automation.trace.STORED_TRACES): + for _ in range(STORED_TRACES): hass.bus.async_fire("test_event2") await hass.async_block_till_done() - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] moon_traces = _find_traces_for_automation(response["result"], "moon") - assert len(moon_traces) == automation.trace.STORED_TRACES + assert len(moon_traces) == STORED_TRACES assert moon_traces[0] assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1 - assert ( - int(moon_traces[-1]["run_id"]) - == int(moon_run_id) + automation.trace.STORED_TRACES - ) + assert int(moon_traces[-1]["run_id"]) == int(moon_run_id) + STORED_TRACES assert len(_find_traces_for_automation(response["result"], "sun")) == 1 @@ -363,13 +369,18 @@ def next_id(): client = await hass_ws_client() - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [] await client.send_json( - {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + { + "id": next_id(), + "type": "trace/list", + "domain": "automation", + "item_id": "sun", + } ) response = await client.receive_json() assert response["success"] @@ -380,14 +391,19 @@ def next_id(): await hass.async_block_till_done() # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 assert len(_find_traces_for_automation(response["result"], "sun")) == 1 await client.send_json( - {"id": next_id(), "type": "automation/trace/list", "automation_id": "sun"} + { + "id": next_id(), + "type": "trace/list", + "domain": "automation", + "item_id": "sun", + } ) response = await client.receive_json() assert response["success"] @@ -395,7 +411,12 @@ def next_id(): assert len(_find_traces_for_automation(response["result"], "sun")) == 1 await client.send_json( - {"id": next_id(), "type": "automation/trace/list", "automation_id": "moon"} + { + "id": next_id(), + "type": "trace/list", + "domain": "automation", + "item_id": "moon", + } ) response = await client.receive_json() assert response["success"] @@ -414,7 +435,7 @@ def next_id(): await hass.async_block_till_done() # Get trace - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] assert len(_find_traces_for_automation(response["result"], "moon")) == 3 @@ -426,7 +447,7 @@ def next_id(): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event'" - assert trace["unique_id"] == "sun" + assert trace["item_id"] == "sun" trace = _find_traces_for_automation(response["result"], "moon")[0] assert trace["last_action"] == "action/0" @@ -435,7 +456,7 @@ def next_id(): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" trace = _find_traces_for_automation(response["result"], "moon")[1] assert trace["last_action"] is None @@ -444,7 +465,7 @@ def next_id(): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event3'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" trace = _find_traces_for_automation(response["result"], "moon")[2] assert trace["last_action"] == "action/0" @@ -453,7 +474,7 @@ def next_id(): assert trace["state"] == "stopped" assert trace["timestamp"] assert trace["trigger"] == "event 'test_event2'" - assert trace["unique_id"] == "moon" + assert trace["item_id"] == "moon" async def test_automation_breakpoints(hass, hass_ws_client): @@ -465,11 +486,11 @@ def next_id(): id += 1 return id - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + async def assert_last_action(item_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], automation_id)[-1] + trace = _find_traces_for_automation(response["result"], item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -508,24 +529,23 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "1", } ) response = await client.receive_json() assert not response["success"] - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) + await client.send_json({"id": next_id(), "type": "trace/debug/breakpoint/list"}) response = await client.receive_json() assert response["success"] assert response["result"] == [] subscription_id = next_id() await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + {"id": subscription_id, "type": "trace/debug/breakpoint/subscribe"} ) response = await client.receive_json() assert response["success"] @@ -533,8 +553,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -543,24 +564,33 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/5", } ) response = await client.receive_json() assert response["success"] - await client.send_json( - {"id": next_id(), "type": "automation/debug/breakpoint/list"} - ) + await client.send_json({"id": next_id(), "type": "trace/debug/breakpoint/list"}) response = await client.receive_json() assert response["success"] assert_lists_same( response["result"], [ - {"node": "action/1", "run_id": "*", "automation_id": "sun"}, - {"node": "action/5", "run_id": "*", "automation_id": "sun"}, + { + "node": "action/1", + "run_id": "*", + "domain": "automation", + "item_id": "sun", + }, + { + "node": "action/5", + "run_id": "*", + "domain": "automation", + "item_id": "sun", + }, ], ) @@ -570,7 +600,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/1", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/1", "run_id": run_id, } @@ -578,8 +609,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/step", - "automation_id": "sun", + "type": "trace/debug/step", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -589,7 +621,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/2", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/2", "run_id": run_id, } @@ -597,8 +630,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", + "type": "trace/debug/continue", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -608,7 +642,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/5", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/5", "run_id": run_id, } @@ -616,8 +651,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", + "type": "trace/debug/stop", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -636,11 +672,11 @@ def next_id(): id += 1 return id - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + async def assert_last_action(item_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], automation_id)[-1] + trace = _find_traces_for_automation(response["result"], item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -678,7 +714,7 @@ async def assert_last_action(automation_id, expected_action, expected_state): subscription_id = next_id() await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + {"id": subscription_id, "type": "trace/debug/breakpoint/subscribe"} ) response = await client.receive_json() assert response["success"] @@ -686,8 +722,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -700,7 +737,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/1", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/1", "run_id": run_id, } @@ -718,8 +756,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "1", } ) @@ -743,11 +782,11 @@ def next_id(): id += 1 return id - async def assert_last_action(automation_id, expected_action, expected_state): - await client.send_json({"id": next_id(), "type": "automation/trace/list"}) + async def assert_last_action(item_id, expected_action, expected_state): + await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], automation_id)[-1] + trace = _find_traces_for_automation(response["result"], item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -785,7 +824,7 @@ async def assert_last_action(automation_id, expected_action, expected_state): subscription_id = next_id() await client.send_json( - {"id": subscription_id, "type": "automation/debug/breakpoint/subscribe"} + {"id": subscription_id, "type": "trace/debug/breakpoint/subscribe"} ) response = await client.receive_json() assert response["success"] @@ -793,8 +832,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -804,8 +844,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/set", - "automation_id": "sun", + "type": "trace/debug/breakpoint/set", + "domain": "automation", + "item_id": "sun", "node": "action/5", } ) @@ -818,7 +859,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/1", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/1", "run_id": run_id, } @@ -826,8 +868,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/continue", - "automation_id": "sun", + "type": "trace/debug/continue", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -837,7 +880,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/5", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/5", "run_id": run_id, } @@ -845,8 +889,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/stop", - "automation_id": "sun", + "type": "trace/debug/stop", + "domain": "automation", + "item_id": "sun", "run_id": run_id, } ) @@ -859,8 +904,9 @@ async def assert_last_action(automation_id, expected_action, expected_state): await client.send_json( { "id": next_id(), - "type": "automation/debug/breakpoint/clear", - "automation_id": "sun", + "type": "trace/debug/breakpoint/clear", + "domain": "automation", + "item_id": "sun", "node": "action/1", } ) @@ -873,7 +919,8 @@ async def assert_last_action(automation_id, expected_action, expected_state): response = await client.receive_json() run_id = await assert_last_action("sun", "action/5", "running") assert response["event"] == { - "automation_id": "sun", + "domain": "automation", + "item_id": "sun", "node": "action/5", "run_id": run_id, } From b1d0b37d2c575a440686c9bd177862a723deea68 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 23 Mar 2021 23:04:32 +0100 Subject: [PATCH 1476/1818] Google assistant: disconnect user agent when not found in google (#48233) --- .../components/cloud/google_config.py | 31 ++++++-------- .../components/google_assistant/helpers.py | 18 ++++++++- .../components/google_assistant/http.py | 7 ++-- tests/components/cloud/test_google_config.py | 40 ++++++++++++++----- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index dffa1e2f306c92..26cb830e8c662d 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -6,11 +6,7 @@ from hass_nabucasa.google_report_state import ErrorResponse from homeassistant.components.google_assistant.helpers import AbstractConfig -from homeassistant.const import ( - CLOUD_NEVER_EXPOSED_ENTITIES, - EVENT_HOMEASSISTANT_STARTED, - HTTP_OK, -) +from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_OK from homeassistant.core import CoreState, split_entity_id from homeassistant.helpers import entity_registry @@ -87,8 +83,15 @@ def cloud_user(self): async def async_initialize(self): """Perform async initialization of config.""" await super().async_initialize() - # Remove bad data that was there until 0.103.6 - Jan 6, 2020 - self._store.pop_agent_user_id(self._user) + + # Remove old/wrong user agent ids + remove_agent_user_ids = [] + for agent_user_id in self._store.agent_user_ids: + if agent_user_id != self.agent_user_id: + remove_agent_user_ids.append(agent_user_id) + + for agent_user_id in remove_agent_user_ids: + await self.async_disconnect_agent_user(agent_user_id) self._prefs.async_listen_updates(self._async_prefs_updated) @@ -198,17 +201,7 @@ async def _handle_entity_registry_updated(self, event): if not self._should_expose_entity_id(entity_id): return - if self.hass.state == CoreState.running: - self.async_schedule_google_sync_all() + if self.hass.state != CoreState.running: return - if self._sync_on_started: - return - - self._sync_on_started = True - - async def sync_google(_): - """Sync entities to Google.""" - await self.async_sync_entities_all() - - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, sync_google) + self.async_schedule_google_sync_all() diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 9b133ad6a30854..f248202e5f0f03 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -15,9 +15,10 @@ ATTR_SUPPORTED_FEATURES, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_NAME, + EVENT_HOMEASSISTANT_STARTED, STATE_UNAVAILABLE, ) -from homeassistant.core import Context, HomeAssistant, State, callback +from homeassistant.core import Context, CoreState, HomeAssistant, State, callback from homeassistant.helpers.area_registry import AreaEntry from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity_registry import RegistryEntry @@ -104,6 +105,16 @@ async def async_initialize(self): self._store = GoogleConfigStore(self.hass) await self._store.async_load() + if self.hass.state == CoreState.running: + await self.async_sync_entities_all() + return + + async def sync_google(_): + """Sync entities to Google.""" + await self.async_sync_entities_all() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, sync_google) + @property def enabled(self): """Return if Google is enabled.""" @@ -193,7 +204,10 @@ async def async_sync_entities(self, agent_user_id: str): """Sync all entities to Google.""" # Remove any pending sync self._google_sync_unsub.pop(agent_user_id, lambda: None)() - return await self._async_request_sync_devices(agent_user_id) + status = await self._async_request_sync_devices(agent_user_id) + if status == 404: + await self.async_disconnect_agent_user(agent_user_id) + return status async def async_sync_entities_all(self): """Sync all entities to Google for all registered agents.""" diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 5cf1cb143792cd..3787a63a514f5b 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -135,11 +135,12 @@ def should_2fa(self, state): async def _async_request_sync_devices(self, agent_user_id: str): if CONF_SERVICE_ACCOUNT in self._config: - await self.async_call_homegraph_api( + return await self.async_call_homegraph_api( REQUEST_SYNC_BASE_URL, {"agentUserId": agent_user_id} ) - else: - _LOGGER.error("No configuration for request_sync available") + + _LOGGER.error("No configuration for request_sync available") + return HTTP_INTERNAL_SERVER_ERROR async def _async_update_token(self, force=False): if CONF_SERVICE_ACCOUNT not in self._config: diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index e1da6bbe0a8ebf..5f29a41c6e0c55 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,5 +1,5 @@ """Test the Cloud Google Config.""" -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import Mock, patch import pytest @@ -41,21 +41,19 @@ async def test_google_update_report_state(mock_conf, hass, cloud_prefs): assert len(mock_report_state.mock_calls) == 1 -async def test_sync_entities(aioclient_mock, hass, cloud_prefs): +async def test_sync_entities(mock_conf, hass, cloud_prefs): """Test sync devices.""" - config = CloudGoogleConfig( - hass, - GACTIONS_SCHEMA({}), - "mock-user-id", - cloud_prefs, - Mock(auth=Mock(async_check_token=AsyncMock())), - ) + await mock_conf.async_initialize() + await mock_conf.async_connect_agent_user("mock-user-id") + + assert len(mock_conf._store.agent_user_ids) == 1 with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", return_value=Mock(status=HTTP_NOT_FOUND), ) as mock_request_sync: - assert await config.async_sync_entities("user") == HTTP_NOT_FOUND + assert await mock_conf.async_sync_entities("mock-user-id") == HTTP_NOT_FOUND + assert len(mock_conf._store.agent_user_ids) == 0 assert len(mock_request_sync.mock_calls) == 1 @@ -165,7 +163,29 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): assert len(mock_sync.mock_calls) == 3 + +async def test_sync_google_when_started(hass, mock_cloud_login, cloud_prefs): + """Test Google config syncs on init.""" + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + with patch.object(config, "async_sync_entities_all") as mock_sync: + await config.async_initialize() + await config.async_connect_agent_user("mock-user-id") + assert len(mock_sync.mock_calls) == 1 + + +async def test_sync_google_on_home_assistant_start(hass, mock_cloud_login, cloud_prefs): + """Test Google config syncs when home assistant started.""" + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + hass.state = CoreState.starting with patch.object(config, "async_sync_entities_all") as mock_sync: + await config.async_initialize() + await config.async_connect_agent_user("mock-user-id") + assert len(mock_sync.mock_calls) == 0 + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 1 From c4e5af8081d690718255315b8c9f8f36f5598b65 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 24 Mar 2021 00:03:09 +0000 Subject: [PATCH 1477/1818] [ci skip] Translation update --- .../components/hyperion/translations/ko.json | 1 + .../components/hyperion/translations/no.json | 1 + .../components/hyperion/translations/zh-Hant.json | 1 + .../islamic_prayer_times/translations/de.json | 15 +++++++++++++++ .../components/shelly/translations/ko.json | 2 +- 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hyperion/translations/ko.json b/homeassistant/components/hyperion/translations/ko.json index 94dc9d48a58499..f42450675fbb68 100644 --- a/homeassistant/components/hyperion/translations/ko.json +++ b/homeassistant/components/hyperion/translations/ko.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "\ucd9c\ub825\ud560 Hyperion \ud6a8\uacfc", "priority": "\uc0c9\uc0c1 \ubc0f \ud6a8\uacfc\uc5d0 \uc0ac\uc6a9\ud560 Hyperion \uc6b0\uc120 \uc21c\uc704" } } diff --git a/homeassistant/components/hyperion/translations/no.json b/homeassistant/components/hyperion/translations/no.json index e411982b58af6d..8fed4ee2437804 100644 --- a/homeassistant/components/hyperion/translations/no.json +++ b/homeassistant/components/hyperion/translations/no.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Hyperion-effekter \u00e5 vise", "priority": "Hyperion-prioritet for bruke til farger og effekter" } } diff --git a/homeassistant/components/hyperion/translations/zh-Hant.json b/homeassistant/components/hyperion/translations/zh-Hant.json index bb8eacd537651e..d9757ccc22a09a 100644 --- a/homeassistant/components/hyperion/translations/zh-Hant.json +++ b/homeassistant/components/hyperion/translations/zh-Hant.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "\u986f\u793a Hyperion \u6548\u61c9", "priority": "Hyperion \u512a\u5148\u4f7f\u7528\u4e4b\u8272\u6eab\u8207\u7279\u6548" } } diff --git a/homeassistant/components/islamic_prayer_times/translations/de.json b/homeassistant/components/islamic_prayer_times/translations/de.json index b06137bdb0e5f4..b8097e9bd39a3c 100644 --- a/homeassistant/components/islamic_prayer_times/translations/de.json +++ b/homeassistant/components/islamic_prayer_times/translations/de.json @@ -2,6 +2,21 @@ "config": { "abort": { "single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." + }, + "step": { + "user": { + "description": "M\u00f6chtest du islamische Gebetszeiten einrichten?", + "title": "Islamische Gebetszeiten einrichten" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Gebetsberechnungsmethode" + } + } } }, "title": "Islamische Gebetszeiten" diff --git a/homeassistant/components/shelly/translations/ko.json b/homeassistant/components/shelly/translations/ko.json index d422b6de86bc8d..d4af42485788cf 100644 --- a/homeassistant/components/shelly/translations/ko.json +++ b/homeassistant/components/shelly/translations/ko.json @@ -12,7 +12,7 @@ "flow_title": "{name}", "step": { "confirm_discovery": { - "description": "{host}\uc5d0\uc11c {model}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub41c \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uc124\uc815\ud558\uae30 \uc804\uc5d0 \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4.\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub418\uc9c0 \uc54a\ub294 \ubc30\ud130\ub9ac \uad6c\ub3d9 \uae30\uae30\ub294 \uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub420 \ub54c \ucd94\uac00\ub418\uba70, \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc218\ub3d9\uc73c\ub85c \uae30\uae30\ub97c \uc808\uc804 \ud574\uc81c\uc2dc\ud0a4\uac70\ub098 \uae30\uae30\uc5d0\uc11c \ub2e4\uc74c \ub370\uc774\ud130\ub97c \uc5c5\ub370\uc774\ud2b8\ud560 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "description": "{host}\uc5d0\uc11c {model}\uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\n\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub41c \ubc30\ud130\ub9ac \ubc29\uc2dd \uae30\uae30\ub294 \uc124\uc815\ud558\uae30 \uc804\uc5d0 \uc808\uc804 \ubaa8\ub4dc\ub97c \ud574\uc81c\ud574\uc57c \ud569\ub2c8\ub2e4.\n\ube44\ubc00\ubc88\ud638\ub85c \ubcf4\ud638\ub418\uc9c0 \uc54a\ub294 \ubc30\ud130\ub9ac \ubc29\uc2dd \uae30\uae30\ub294 \uae30\uae30\uc758 \uc808\uc804 \ubaa8\ub4dc\uac00 \ud574\uc81c\ub420 \ub54c \ucd94\uac00\ub418\uba70, \uae30\uae30\uc758 \ubc84\ud2bc\uc744 \uc0ac\uc6a9\ud558\uc5ec \uc218\ub3d9\uc73c\ub85c \uae30\uae30\ub97c \uc808\uc804 \ud574\uc81c\uc2dc\ud0a4\uac70\ub098 \uae30\uae30\uc5d0\uc11c \ub2e4\uc74c \ub370\uc774\ud130\ub97c \uc5c5\ub370\uc774\ud2b8\ud560 \ub54c\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "credentials": { "data": { From b58dd7d0477f1c076ec796a296967f0e44c5ed06 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Mar 2021 00:23:29 +0000 Subject: [PATCH 1478/1818] Bump frontend to 20210324.0 --- 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 ed97d0718e7ae2..215cf65291f51e 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==20210316.0" + "home-assistant-frontend==20210324.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a8ad4ff85f900d..8502fa2c22d550 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 -home-assistant-frontend==20210316.0 +home-assistant-frontend==20210324.0 httpx==0.16.1 jinja2>=2.11.3 netdisco==2.8.2 diff --git a/requirements_all.txt b/requirements_all.txt index 67c0988bf0d17d..56459895f1db13 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210316.0 +home-assistant-frontend==20210324.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 115f77c35dce14..f9d3520dcf68a3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -409,7 +409,7 @@ hole==0.5.1 holidays==0.10.5.2 # homeassistant.components.frontend -home-assistant-frontend==20210316.0 +home-assistant-frontend==20210324.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 3dec394cad4024c17566b0e93fc557bf86c12d93 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Mar 2021 20:35:15 -0700 Subject: [PATCH 1479/1818] Migrate template to register reload service on async_setup (#48273) --- homeassistant/components/template/__init__.py | 21 +- .../template/alarm_control_panel.py | 3 - .../components/template/binary_sensor.py | 4 +- homeassistant/components/template/const.py | 2 - homeassistant/components/template/cover.py | 4 +- homeassistant/components/template/fan.py | 4 +- homeassistant/components/template/light.py | 4 +- homeassistant/components/template/lock.py | 4 +- homeassistant/components/template/sensor.py | 4 +- homeassistant/components/template/switch.py | 4 +- homeassistant/components/template/vacuum.py | 4 +- homeassistant/components/template/weather.py | 3 - .../components/template/test_binary_sensor.py | 242 +++++++++--------- tests/components/template/test_init.py | 3 +- 14 files changed, 132 insertions(+), 174 deletions(-) diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index cc8862afcf4cae..6292cd40fecdc9 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -1,20 +1,11 @@ """The template component.""" -from homeassistant.const import SERVICE_RELOAD -from homeassistant.helpers.reload import async_reload_integration_platforms +from homeassistant.helpers.reload import async_setup_reload_service -from .const import DOMAIN, EVENT_TEMPLATE_RELOADED, PLATFORMS +from .const import DOMAIN, PLATFORMS -async def async_setup_reload_service(hass): - """Create the reload service for the template domain.""" - if hass.services.has_service(DOMAIN, SERVICE_RELOAD): - return +async def async_setup(hass, config): + """Set up the template integration.""" + await async_setup_reload_service(hass, DOMAIN, PLATFORMS) - async def _reload_config(call): - """Reload the template platform config.""" - await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS) - hass.bus.async_fire(EVENT_TEMPLATE_RELOADED, context=call.context) - - hass.helpers.service.async_register_admin_service( - DOMAIN, SERVICE_RELOAD, _reload_config - ) + return True diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index f56c5b2757250d..4c72c5094efcae 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -32,10 +32,8 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import DOMAIN, PLATFORMS from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -113,7 +111,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template Alarm Control Panels.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index b810c7faee16af..1088652cd0a0b6 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -22,10 +22,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_call_later -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.template import result_as_boolean -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity CONF_DELAY_ON = "delay_on" @@ -97,7 +96,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template binary sensors.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/const.py b/homeassistant/components/template/const.py index 5b38f19eaeb4f6..5d6bf6391df709 100644 --- a/homeassistant/components/template/const.py +++ b/homeassistant/components/template/const.py @@ -6,8 +6,6 @@ PLATFORM_STORAGE_KEY = "template_platforms" -EVENT_TEMPLATE_RELOADED = "event_template_reloaded" - PLATFORMS = [ "alarm_control_panel", "binary_sensor", diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 278cd1c80bbc48..cd552a33e5d76a 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -38,10 +38,9 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -160,7 +159,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template cover.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 51dce0f8d56191..87b063583bfc8d 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -36,10 +36,9 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -163,7 +162,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template fans.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 0edaacbb5ca853..e76ba42289b161 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -31,10 +31,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -137,7 +136,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template lights.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 692f06e28fe712..c4a3977a4dbd52 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -13,10 +13,9 @@ from homeassistant.core import callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity CONF_LOCK = "lock" @@ -60,7 +59,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template lock.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index b587fe3bd8291a..9a63302044ab5f 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -25,9 +25,8 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity CONF_ATTRIBUTE_TEMPLATES = "attribute_templates" @@ -96,7 +95,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template sensors.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 412c4507d1f84a..0e083df13f4912 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -22,11 +22,10 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"] @@ -90,7 +89,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template switches.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 171aeb7af9256e..ed7919d174e257 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -41,10 +41,9 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script -from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS +from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -147,7 +146,6 @@ async def _async_create_entities(hass, config): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the template vacuums.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) async_add_entities(await _async_create_entities(hass, config)) diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 0db94520afe788..27980febb567ae 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -24,9 +24,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.reload import async_setup_reload_service -from .const import DOMAIN, PLATFORMS from .template_entity import TemplateEntity CONDITION_CLASSES = { @@ -71,7 +69,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Template weather.""" - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) name = config[CONF_NAME] condition_template = config[CONF_CONDITION_TEMPLATE] diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 241ac88328e939..76602b394339e2 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -15,7 +15,7 @@ from homeassistant.core import CoreState import homeassistant.util.dt as dt_util -from tests.common import assert_setup_component, async_fire_time_changed +from tests.common import async_fire_time_changed async def test_setup(hass): @@ -32,85 +32,79 @@ async def test_setup(hass): }, } } - with assert_setup_component(1): - assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) + assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) async def test_setup_no_sensors(hass): """Test setup with no sensors.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "template"}} - ) + assert await setup.async_setup_component( + hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "template"}} + ) async def test_setup_invalid_device(hass): """Test the setup with invalid devices.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - {"binary_sensor": {"platform": "template", "sensors": {"foo bar": {}}}}, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + {"binary_sensor": {"platform": "template", "sensors": {"foo bar": {}}}}, + ) async def test_setup_invalid_device_class(hass): """Test setup with invalid sensor class.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test": { - "value_template": "{{ foo }}", - "device_class": "foobarnotreal", - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test": { + "value_template": "{{ foo }}", + "device_class": "foobarnotreal", + } + }, + } + }, + ) async def test_setup_invalid_missing_template(hass): """Test setup with invalid and missing template.""" - with assert_setup_component(0): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": {"test": {"device_class": "motion"}}, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": {"test": {"device_class": "motion"}}, + } + }, + ) async def test_icon_template(hass): """Test icon template.""" - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test_template_sensor": { - "value_template": "{{ states.sensor.xyz.state }}", - "icon_template": "{% if " - "states.binary_sensor.test_state.state == " - "'Works' %}" - "mdi:check" - "{% endif %}", - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.xyz.state }}", + "icon_template": "{% if " + "states.binary_sensor.test_state.state == " + "'Works' %}" + "mdi:check" + "{% endif %}", + } + }, + } + }, + ) await hass.async_block_till_done() await hass.async_start() @@ -127,26 +121,25 @@ async def test_icon_template(hass): async def test_entity_picture_template(hass): """Test entity_picture template.""" - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test_template_sensor": { - "value_template": "{{ states.sensor.xyz.state }}", - "entity_picture_template": "{% if " - "states.binary_sensor.test_state.state == " - "'Works' %}" - "/local/sensor.png" - "{% endif %}", - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.xyz.state }}", + "entity_picture_template": "{% if " + "states.binary_sensor.test_state.state == " + "'Works' %}" + "/local/sensor.png" + "{% endif %}", + } + }, + } + }, + ) await hass.async_block_till_done() await hass.async_start() @@ -163,24 +156,23 @@ async def test_entity_picture_template(hass): async def test_attribute_templates(hass): """Test attribute_templates template.""" - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "test_template_sensor": { - "value_template": "{{ states.sensor.xyz.state }}", - "attribute_templates": { - "test_attribute": "It {{ states.sensor.test_state.state }}." - }, - } - }, - } - }, - ) + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "test_template_sensor": { + "value_template": "{{ states.sensor.xyz.state }}", + "attribute_templates": { + "test_attribute": "It {{ states.sensor.test_state.state }}." + }, + } + }, + } + }, + ) await hass.async_block_till_done() await hass.async_start() @@ -202,35 +194,34 @@ async def test_match_all(hass): "homeassistant.components.template.binary_sensor." "BinarySensorTemplate._update_state" ) as _update_state: - with assert_setup_component(1): - assert await setup.async_setup_component( - hass, - binary_sensor.DOMAIN, - { - "binary_sensor": { - "platform": "template", - "sensors": { - "match_all_template_sensor": { - "value_template": ( - "{% for state in states %}" - "{% if state.entity_id == 'sensor.humidity' %}" - "{{ state.entity_id }}={{ state.state }}" - "{% endif %}" - "{% endfor %}" - ), - }, + assert await setup.async_setup_component( + hass, + binary_sensor.DOMAIN, + { + "binary_sensor": { + "platform": "template", + "sensors": { + "match_all_template_sensor": { + "value_template": ( + "{% for state in states %}" + "{% if state.entity_id == 'sensor.humidity' %}" + "{{ state.entity_id }}={{ state.state }}" + "{% endif %}" + "{% endfor %}" + ), }, - } - }, - ) + }, + } + }, + ) - await hass.async_start() - await hass.async_block_till_done() - init_calls = len(_update_state.mock_calls) + await hass.async_start() + await hass.async_block_till_done() + init_calls = len(_update_state.mock_calls) - hass.states.async_set("sensor.any_state", "update") - await hass.async_block_till_done() - assert len(_update_state.mock_calls) == init_calls + hass.states.async_set("sensor.any_state", "update") + await hass.async_block_till_done() + assert len(_update_state.mock_calls) == init_calls async def test_event(hass): @@ -247,8 +238,7 @@ async def test_event(hass): }, } } - with assert_setup_component(1): - assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) + assert await setup.async_setup_component(hass, binary_sensor.DOMAIN, config) await hass.async_block_till_done() await hass.async_start() diff --git a/tests/components/template/test_init.py b/tests/components/template/test_init.py index 1c932c5af30fa6..107c54c710e86c 100644 --- a/tests/components/template/test_init.py +++ b/tests/components/template/test_init.py @@ -4,7 +4,8 @@ from unittest.mock import patch from homeassistant import config -from homeassistant.components.template import DOMAIN, SERVICE_RELOAD +from homeassistant.components.template import DOMAIN +from homeassistant.helpers.reload import SERVICE_RELOAD from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util From 0d699bb768b5f35bbf34b9346166072066b30a29 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 24 Mar 2021 07:17:51 +0100 Subject: [PATCH 1480/1818] Add tests for Netatmo sensor (#46393) * Add tests for Netatmo sensor * Fix coveragerc * Remove freezegun dependency * Use f-strings instead of string concatenation * Update tests/components/netatmo/test_sensor.py Co-authored-by: Erik Montnemery * Address comment on config options test * Replace deprecated call to async_get_registry() * Fix public weather sensor update test * Clean up * Prevent division by zero Co-authored-by: Erik Montnemery Co-authored-by: Martin Hjelmare --- .coveragerc | 2 +- homeassistant/components/netatmo/sensor.py | 12 +- tests/components/netatmo/common.py | 2 + tests/components/netatmo/conftest.py | 13 +- tests/components/netatmo/test_sensor.py | 235 +++++++++++ tests/fixtures/netatmo/gethomecoachsdata.json | 202 +++++++++ tests/fixtures/netatmo/getpublicdata.json | 392 ++++++++++++++++++ 7 files changed, 844 insertions(+), 14 deletions(-) create mode 100644 tests/components/netatmo/test_sensor.py create mode 100644 tests/fixtures/netatmo/gethomecoachsdata.json create mode 100644 tests/fixtures/netatmo/getpublicdata.json diff --git a/.coveragerc b/.coveragerc index 972831c4bd4f68..10eac76421c5de 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,7 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/sensor.py + homeassistant/components/netatmo/helper.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index cccd3865e54e01..4c6facb3eca161 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -641,7 +641,7 @@ def async_update_callback(self): elif self.type == "guststrength": data = self._data.get_latest_gust_strengths() - if not data: + if data is None: if self._state is None: return _LOGGER.debug( @@ -650,8 +650,8 @@ def async_update_callback(self): self._state = None return - values = [x for x in data.values() if x is not None] - if self._mode == "avg": - self._state = round(sum(values) / len(values), 1) - elif self._mode == "max": - self._state = max(values) + if values := [x for x in data.values() if x is not None]: + if self._mode == "avg": + self._state = round(sum(values) / len(values), 1) + elif self._mode == "max": + self._state = max(values) diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index b952d6fe790749..6bafd12aceada0 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -29,6 +29,8 @@ "user": {"id": "91763b24c43d3e344f424e8b", "email": "john@doe.com"}, } +TEST_TIME = 1559347200.0 + def fake_post_request(**args): """Return fake data.""" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index e0138dcc4d7db3..9a16391d2a429c 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -5,7 +5,7 @@ import pytest -from .common import ALL_SCOPES, fake_post_request, fake_post_request_no_data +from .common import ALL_SCOPES, TEST_TIME, fake_post_request, fake_post_request_no_data from tests.common import MockConfigEntry @@ -81,11 +81,10 @@ async def mock_entry_fixture(hass, config_entry): @pytest.fixture(name="sensor_entry") async def mock_sensor_entry_fixture(hass, config_entry): """Mock setup of sensor platform.""" - with selected_platforms(["sensor"]): + with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): await hass.config_entries.async_setup(config_entry.entry_id) - - await hass.async_block_till_done() - return config_entry + await hass.async_block_till_done() + yield config_entry @pytest.fixture(name="camera_entry") @@ -131,5 +130,5 @@ async def mock_entry_error_fixture(hass, config_entry): mock_auth.return_value.post_request.side_effect = fake_post_request_no_data await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - return config_entry + await hass.async_block_till_done() + yield config_entry diff --git a/tests/components/netatmo/test_sensor.py b/tests/components/netatmo/test_sensor.py new file mode 100644 index 00000000000000..fcb2ce454df09a --- /dev/null +++ b/tests/components/netatmo/test_sensor.py @@ -0,0 +1,235 @@ +"""The tests for the Netatmo sensor platform.""" +from datetime import timedelta +from unittest.mock import patch + +import pytest + +from homeassistant.components.netatmo import sensor +from homeassistant.components.netatmo.sensor import MODULE_TYPE_WIND +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY +from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt + +from .common import TEST_TIME +from .conftest import selected_platforms + +from tests.common import async_fire_time_changed + + +async def test_weather_sensor(hass, sensor_entry): + """Test weather sensor setup.""" + prefix = "sensor.netatmo_mystation_" + + assert hass.states.get(f"{prefix}temperature").state == "24.6" + assert hass.states.get(f"{prefix}humidity").state == "36" + assert hass.states.get(f"{prefix}co2").state == "749" + assert hass.states.get(f"{prefix}pressure").state == "1017.3" + + +async def test_public_weather_sensor(hass, sensor_entry): + """Test public weather sensor setup.""" + prefix = "sensor.netatmo_home_max_" + + assert hass.states.get(f"{prefix}temperature").state == "27.4" + assert hass.states.get(f"{prefix}humidity").state == "76" + assert hass.states.get(f"{prefix}pressure").state == "1014.4" + + prefix = "sensor.netatmo_home_avg_" + + assert hass.states.get(f"{prefix}temperature").state == "22.7" + assert hass.states.get(f"{prefix}humidity").state == "63.2" + assert hass.states.get(f"{prefix}pressure").state == "1010.3" + + assert len(hass.states.async_all()) > 0 + entities_before_change = len(hass.states.async_all()) + + valid_option = { + "lat_ne": 32.91336, + "lon_ne": -117.187429, + "lat_sw": 32.83336, + "lon_sw": -117.26743, + "show_on_map": True, + "area_name": "Home avg", + "mode": "max", + } + + result = await hass.config_entries.options.async_init(sensor_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={"new_area": "Home avg"} + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input=valid_option + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={} + ) + 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 hass.states.get(f"{prefix}temperature").state == "27.4" + assert hass.states.get(f"{prefix}humidity").state == "76" + assert hass.states.get(f"{prefix}pressure").state == "1014.4" + + assert len(hass.states.async_all()) == entities_before_change + + +@pytest.mark.parametrize( + "strength, expected", + [(50, "Full"), (60, "High"), (80, "Medium"), (90, "Low")], +) +async def test_process_wifi(strength, expected): + """Test wifi strength translation.""" + assert sensor.process_wifi(strength) == expected + + +@pytest.mark.parametrize( + "strength, expected", + [(50, "Full"), (70, "High"), (80, "Medium"), (90, "Low")], +) +async def test_process_rf(strength, expected): + """Test radio strength translation.""" + assert sensor.process_rf(strength) == expected + + +@pytest.mark.parametrize( + "health, expected", + [(4, "Unhealthy"), (3, "Poor"), (2, "Fair"), (1, "Fine"), (0, "Healthy")], +) +async def test_process_health(health, expected): + """Test health index translation.""" + assert sensor.process_health(health) == expected + + +@pytest.mark.parametrize( + "model, data, expected", + [ + (MODULE_TYPE_WIND, 5591, "Full"), + (MODULE_TYPE_WIND, 5181, "High"), + (MODULE_TYPE_WIND, 4771, "Medium"), + (MODULE_TYPE_WIND, 4361, "Low"), + (MODULE_TYPE_WIND, 4300, "Very Low"), + ], +) +async def test_process_battery(model, data, expected): + """Test battery level translation.""" + assert sensor.process_battery(data, model) == expected + + +@pytest.mark.parametrize( + "angle, expected", + [ + (0, "N"), + (40, "NE"), + (70, "E"), + (130, "SE"), + (160, "S"), + (220, "SW"), + (250, "W"), + (310, "NW"), + (340, "N"), + ], +) +async def test_process_angle(angle, expected): + """Test wind direction translation.""" + assert sensor.process_angle(angle) == expected + + +@pytest.mark.parametrize( + "angle, expected", + [(-1, 359), (-40, 320)], +) +async def test_fix_angle(angle, expected): + """Test wind angle fix.""" + assert sensor.fix_angle(angle) == expected + + +@pytest.mark.parametrize( + "uid, name, expected", + [ + ("12:34:56:37:11:ca-reachable", "netatmo_mystation_reachable", "True"), + ("12:34:56:03:1b:e4-rf_status", "netatmo_mystation_yard_radio", "Full"), + ( + "12:34:56:05:25:6e-rf_status", + "netatmo_valley_road_rain_gauge_radio", + "Medium", + ), + ( + "12:34:56:36:fc:de-rf_status_lvl", + "netatmo_mystation_netatmooutdoor_radio_level", + "65", + ), + ( + "12:34:56:37:11:ca-wifi_status_lvl", + "netatmo_mystation_wifi_level", + "45", + ), + ( + "12:34:56:37:11:ca-wifi_status", + "netatmo_mystation_wifi_status", + "Full", + ), + ( + "12:34:56:37:11:ca-temp_trend", + "netatmo_mystation_temperature_trend", + "stable", + ), + ( + "12:34:56:37:11:ca-pressure_trend", + "netatmo_mystation_pressure_trend", + "down", + ), + ("12:34:56:05:51:20-sum_rain_1", "netatmo_mystation_yard_rain_last_hour", "0"), + ("12:34:56:05:51:20-sum_rain_24", "netatmo_mystation_yard_rain_today", "0"), + ("12:34:56:03:1b:e4-windangle", "netatmo_mystation_garden_direction", "SW"), + ( + "12:34:56:03:1b:e4-windangle_value", + "netatmo_mystation_garden_angle", + "217", + ), + ("12:34:56:03:1b:e4-gustangle", "mystation_garden_gust_direction", "S"), + ( + "12:34:56:03:1b:e4-gustangle", + "netatmo_mystation_garden_gust_direction", + "S", + ), + ( + "12:34:56:03:1b:e4-gustangle_value", + "netatmo_mystation_garden_gust_angle_value", + "206", + ), + ( + "12:34:56:03:1b:e4-guststrength", + "netatmo_mystation_garden_gust_strength", + "9", + ), + ( + "12:34:56:26:68:92-health_idx", + "netatmo_baby_bedroom_health", + "Fine", + ), + ], +) +async def test_weather_sensor_enabling(hass, config_entry, uid, name, expected): + """Test enabling of by default disabled sensors.""" + with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): + states_before = len(hass.states.async_all()) + assert hass.states.get(f"sensor.{name}") is None + + registry = er.async_get(hass) + registry.async_get_or_create( + "sensor", + "netatmo", + uid, + suggested_object_id=name, + disabled_by=None, + ) + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > states_before + assert hass.states.get(f"sensor.{name}").state == expected diff --git a/tests/fixtures/netatmo/gethomecoachsdata.json b/tests/fixtures/netatmo/gethomecoachsdata.json new file mode 100644 index 00000000000000..3f9de74bd1ac01 --- /dev/null +++ b/tests/fixtures/netatmo/gethomecoachsdata.json @@ -0,0 +1,202 @@ +{ + "body": { + "devices": [ + { + "_id": "12:34:56:26:69:0c", + "cipher_id": "enc:16:1UqwQlYV5AY2pfyEi5H47dmmFOOL3mCUo+KAkchL4A2CLI5u0e45Xr5jeAswO+XO", + "date_setup": 1544560184, + "last_setup": 1544560184, + "type": "NHC", + "last_status_store": 1558268332, + "firmware": 45, + "last_upgrade": 1544560186, + "wifi_status": 58, + "reachable": false, + "co2_calibrating": false, + "station_name": "Bedroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:25:cf:a8", + "cipher_id": "enc:16:A+Jm0yFWBwUyKinFDutPZK7I2PuHN1fqaE9oB/KF+McbFs3oN9CKpR/dYbqL4om2", + "date_setup": 1544562192, + "last_setup": 1544562192, + "type": "NHC", + "last_status_store": 1559198922, + "firmware": 45, + "last_upgrade": 1544562194, + "wifi_status": 41, + "reachable": true, + "co2_calibrating": false, + "station_name": "Kitchen", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:26:65:14", + "cipher_id": "enc:16:7kK6ZzG4L7NgfZZ6+dMvNxw4l6vXu+88SEJkCUklNdPa4KYIHmsfa1moOilEK61i", + "date_setup": 1544564061, + "last_setup": 1544564061, + "type": "NHC", + "last_status_store": 1559067159, + "firmware": 45, + "last_upgrade": 1544564302, + "wifi_status": 66, + "reachable": true, + "co2_calibrating": false, + "station_name": "Livingroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:3e:c5:46", + "station_name": "Parents Bedroom", + "date_setup": 1570732241, + "last_setup": 1570732241, + "type": "NHC", + "last_status_store": 1572073818, + "module_name": "Indoor", + "firmware": 45, + "wifi_status": 67, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1572073816, + "Temperature": 20.3, + "CO2": 494, + "Humidity": 63, + "Noise": 42, + "Pressure": 1014.5, + "AbsolutePressure": 1004.1, + "health_idx": 1, + "min_temp": 20.3, + "max_temp": 21.6, + "date_max_temp": 1572059333, + "date_min_temp": 1572073816 + } + }, + { + "_id": "12:34:56:26:68:92", + "station_name": "Baby Bedroom", + "date_setup": 1571342643, + "last_setup": 1571342643, + "type": "NHC", + "last_status_store": 1572073995, + "module_name": "Indoor", + "firmware": 45, + "wifi_status": 68, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1572073994, + "Temperature": 21.6, + "CO2": 1053, + "Humidity": 66, + "Noise": 45, + "Pressure": 1021.4, + "AbsolutePressure": 1011, + "health_idx": 1, + "min_temp": 20.9, + "max_temp": 21.6, + "date_max_temp": 1572073690, + "date_min_temp": 1572064254 + } + } + ], + "user": { + "mail": "john@doe.com", + "administrative": { + "lang": "de-DE", + "reg_locale": "de-DE", + "country": "DE", + "unit": 0, + "windunit": 0, + "pressureunit": 0, + "feel_like_algo": 0 + } + } + }, + "status": "ok", + "time_exec": 0.095954179763794, + "time_server": 1559463229 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/getpublicdata.json b/tests/fixtures/netatmo/getpublicdata.json new file mode 100644 index 00000000000000..55202713890301 --- /dev/null +++ b/tests/fixtures/netatmo/getpublicdata.json @@ -0,0 +1,392 @@ +{ + "status": "ok", + "time_server": 1560248397, + "time_exec": 0, + "body": [ + { + "_id": "70:ee:50:36:94:7c", + "place": { + "location": [ + 8.791382999999996, + 50.2136394 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 132 + }, + "mark": 14, + "measures": { + "02:00:00:36:f2:94": { + "res": { + "1560248022": [ + 21.4, + 62 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:36:94:7c": { + "res": { + "1560248030": [ + 1010.6 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:05:33:84": { + "rain_60min": 0.2, + "rain_24h": 12.322000000000001, + "rain_live": 0.5, + "rain_timeutc": 1560248022 + } + }, + "modules": [ + "05:00:00:05:33:84", + "02:00:00:36:f2:94" + ], + "module_types": { + "05:00:00:05:33:84": "NAModule3", + "02:00:00:36:f2:94": "NAModule1" + } + }, + { + "_id": "70:ee:50:1f:68:9e", + "place": { + "location": [ + 8.795445200000017, + 50.2130169 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 125 + }, + "mark": 14, + "measures": { + "02:00:00:1f:82:28": { + "res": { + "1560248312": [ + 21.1, + 69 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:1f:68:9e": { + "res": { + "1560248344": [ + 1007.3 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:02:bb:6e": { + "rain_60min": 0, + "rain_24h": 9.999, + "rain_live": 0, + "rain_timeutc": 1560248344 + } + }, + "modules": [ + "02:00:00:1f:82:28", + "05:00:00:02:bb:6e" + ], + "module_types": { + "02:00:00:1f:82:28": "NAModule1", + "05:00:00:02:bb:6e": "NAModule3" + } + }, + { + "_id": "70:ee:50:27:25:b0", + "place": { + "location": [ + 8.7807159, + 50.1946167 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 112 + }, + "mark": 14, + "measures": { + "02:00:00:27:19:b2": { + "res": { + "1560247889": [ + 23.2, + 60 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:27:25:b0": { + "res": { + "1560247907": [ + 1012.8 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:03:5d:2e": { + "rain_60min": 0, + "rain_24h": 11.716000000000001, + "rain_live": 0, + "rain_timeutc": 1560247896 + } + }, + "modules": [ + "02:00:00:27:19:b2", + "05:00:00:03:5d:2e" + ], + "module_types": { + "02:00:00:27:19:b2": "NAModule1", + "05:00:00:03:5d:2e": "NAModule3" + } + }, + { + "_id": "70:ee:50:04:ed:7a", + "place": { + "location": [ + 8.785034, + 50.192169 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 112 + }, + "mark": 14, + "measures": { + "02:00:00:04:c2:2e": { + "res": { + "1560248137": [ + 19.8, + 76 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:04:ed:7a": { + "res": { + "1560248152": [ + 1005.4 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:04:c2:2e" + ], + "module_types": { + "02:00:00:04:c2:2e": "NAModule1" + } + }, + { + "_id": "70:ee:50:27:9f:2c", + "place": { + "location": [ + 8.785342, + 50.193573 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 116 + }, + "mark": 1, + "measures": { + "02:00:00:27:aa:70": { + "res": { + "1560247821": [ + 25.5, + 56 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:27:9f:2c": { + "res": { + "1560247853": [ + 1010.6 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:27:aa:70" + ], + "module_types": { + "02:00:00:27:aa:70": "NAModule1" + } + }, + { + "_id": "70:ee:50:01:20:fa", + "place": { + "location": [ + 8.7953, + 50.195241 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 119 + }, + "mark": 1, + "measures": { + "02:00:00:00:f7:ba": { + "res": { + "1560247831": [ + 27.4, + 58 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:01:20:fa": { + "res": { + "1560247876": [ + 1014.4 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:00:f7:ba" + ], + "module_types": { + "02:00:00:00:f7:ba": "NAModule1" + } + }, + { + "_id": "70:ee:50:3c:02:78", + "place": { + "location": [ + 8.795953681700666, + 50.19530139868166 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 119 + }, + "mark": 7, + "measures": { + "02:00:00:3c:21:f2": { + "res": { + "1560248225": [ + 23.3, + 58 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:3c:02:78": { + "res": { + "1560248270": [ + 1011.7 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:3c:21:f2" + ], + "module_types": { + "02:00:00:3c:21:f2": "NAModule1" + } + }, + { + "_id": "70:ee:50:36:a9:fc", + "place": { + "location": [ + 8.801164269110814, + 50.19596181704958 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 113 + }, + "mark": 14, + "measures": { + "02:00:00:36:a9:50": { + "res": { + "1560248145": [ + 20.1, + 67 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:36:a9:fc": { + "res": { + "1560248191": [ + 1010 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:02:92:82": { + "rain_60min": 0, + "rain_24h": 11.009, + "rain_live": 0, + "rain_timeutc": 1560248184 + }, + "06:00:00:03:19:76": { + "wind_strength": 15, + "wind_angle": 17, + "gust_strength": 31, + "gust_angle": 217, + "wind_timeutc": 1560248190 + } + }, + "modules": [ + "05:00:00:02:92:82", + "02:00:00:36:a9:50", + "06:00:00:03:19:76" + ], + "module_types": { + "05:00:00:02:92:82": "NAModule3", + "02:00:00:36:a9:50": "NAModule1", + "06:00:00:03:19:76": "NAModule2" + } + } + ] +} \ No newline at end of file From 879c82ebf8518acb673821a7e28ab9de8ebcabb9 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 24 Mar 2021 01:57:45 -0500 Subject: [PATCH 1481/1818] Improve Plex GDM client connections (#48272) --- homeassistant/components/plex/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 27954cdbd9f57e..d1210e6f3d8871 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -398,9 +398,10 @@ def connect_to_client(source, baseurl, machine_identifier, name="Unknown"): client = PlexClient( server=self._plex_server, baseurl=baseurl, + identifier=machine_identifier, token=self._plex_server.createToken(), ) - except requests.exceptions.ConnectionError: + except (NotFound, requests.exceptions.ConnectionError): _LOGGER.error( "Direct client connection failed, will try again: %s (%s)", name, From 5265aabf92c8a1bf8d15402e7355ae080d1acfdf Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Wed, 24 Mar 2021 00:39:23 -0700 Subject: [PATCH 1482/1818] Clean up SmartTub reminders (#48033) * remove "date" state attribute * remove unused constant --- homeassistant/components/smarttub/binary_sensor.py | 7 +------ tests/components/smarttub/test_binary_sensor.py | 5 ----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py index 04a6bb9b72a778..bbeece366551e6 100644 --- a/homeassistant/components/smarttub/binary_sensor.py +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -1,5 +1,4 @@ """Platform for binary sensor integration.""" -from datetime import datetime, timedelta import logging from smarttub import SpaReminder @@ -17,8 +16,6 @@ # whether the reminder has been snoozed (bool) ATTR_REMINDER_SNOOZED = "snoozed" -# the date at which the reminder will be activated -ATTR_REMINDER_DATE = "date" async def async_setup_entry(hass, entry, async_add_entities): @@ -83,12 +80,10 @@ def is_on(self) -> bool: return self.reminder.remaining_days == 0 @property - def device_state_attributes(self): + def extra_state_attributes(self): """Return the state attributes.""" - when = datetime.now() + timedelta(days=self.reminder.remaining_days) return { ATTR_REMINDER_SNOOZED: self.reminder.snoozed, - ATTR_REMINDER_DATE: when.date().isoformat(), } @property diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py index 8229372e90447d..5db97310c561c7 100644 --- a/tests/components/smarttub/test_binary_sensor.py +++ b/tests/components/smarttub/test_binary_sensor.py @@ -1,6 +1,4 @@ """Test the SmartTub binary sensor platform.""" -from datetime import date, timedelta - from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, STATE_OFF, @@ -25,7 +23,4 @@ async def test_reminders(spa, setup_entry, hass): state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF - assert date.fromisoformat(state.attributes["date"]) <= date.today() + timedelta( - days=2 - ) assert state.attributes["snoozed"] is False From 0be6a868e062678682974a21ed2680eed81ba0b5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Mar 2021 10:20:49 +0100 Subject: [PATCH 1483/1818] Fix Core bug report issue form (#48279) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 384cc5834cdb24..aa81d6e4df71c9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -26,10 +26,10 @@ body: value: | ## Environment - type: input + id: version validations: required: true attributes: - id: version label: What is version of Home Assistant Core has the issue? placeholder: core- description: > From 8896ae0d561962820cca881bee6a09d076d1c51c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 24 Mar 2021 17:56:22 +0100 Subject: [PATCH 1484/1818] Add support for tracing script execution (#48276) * Add support for tracing script execution * Tweak --- homeassistant/components/automation/trace.py | 135 +---- homeassistant/components/script/__init__.py | 20 +- homeassistant/components/script/config.py | 15 + homeassistant/components/script/manifest.json | 1 + homeassistant/components/script/trace.py | 23 + homeassistant/components/trace/__init__.py | 182 +++++- homeassistant/components/trace/const.py | 4 +- .../components/trace/websocket_api.py | 2 +- homeassistant/helpers/trace.py | 2 +- tests/components/trace/test_websocket_api.py | 544 ++++++++++-------- 10 files changed, 534 insertions(+), 394 deletions(-) create mode 100644 homeassistant/components/script/trace.py diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index f5e93e09c38dd0..a2c2e40c80c608 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -2,148 +2,25 @@ from __future__ import annotations from contextlib import contextmanager -import datetime as dt -from itertools import count -from typing import Any, Deque -from homeassistant.components.trace.const import DATA_TRACE, STORED_TRACES -from homeassistant.components.trace.utils import LimitedSizeDict -from homeassistant.core import Context -from homeassistant.helpers.trace import TraceElement, trace_id_set -from homeassistant.util import dt as dt_util +from homeassistant.components.trace import AutomationTrace, async_store_trace # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any -class AutomationTrace: - """Container for automation trace.""" - - _run_ids = count(0) - - def __init__( - self, - key: tuple[str, str], - config: dict[str, Any], - context: Context, - ): - """Container for automation trace.""" - self._action_trace: dict[str, Deque[TraceElement]] | None = None - self._condition_trace: dict[str, Deque[TraceElement]] | None = None - self._config: dict[str, Any] = config - self.context: Context = context - self._error: Exception | None = None - self._state: str = "running" - self.run_id: str = str(next(self._run_ids)) - self._timestamp_finish: dt.datetime | None = None - self._timestamp_start: dt.datetime = dt_util.utcnow() - self._key: tuple[str, str] = key - self._variables: dict[str, Any] | None = None - - def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: - """Set action trace.""" - self._action_trace = trace - - def set_condition_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: - """Set condition trace.""" - self._condition_trace = trace - - def set_error(self, ex: Exception) -> None: - """Set error.""" - self._error = ex - - def set_variables(self, variables: dict[str, Any]) -> None: - """Set variables.""" - self._variables = variables - - def finished(self) -> None: - """Set finish time.""" - self._timestamp_finish = dt_util.utcnow() - self._state = "stopped" - - def as_dict(self) -> dict[str, Any]: - """Return dictionary version of this AutomationTrace.""" - - result = self.as_short_dict() - - action_traces = {} - condition_traces = {} - if self._action_trace: - for key, trace_list in self._action_trace.items(): - action_traces[key] = [item.as_dict() for item in trace_list] - - if self._condition_trace: - for key, trace_list in self._condition_trace.items(): - condition_traces[key] = [item.as_dict() for item in trace_list] - - result.update( - { - "action_trace": action_traces, - "condition_trace": condition_traces, - "config": self._config, - "context": self.context, - "variables": self._variables, - } - ) - if self._error is not None: - result["error"] = str(self._error) - return result - - def as_short_dict(self) -> dict[str, Any]: - """Return a brief dictionary version of this AutomationTrace.""" - - last_action = None - last_condition = None - trigger = None - - if self._action_trace: - last_action = list(self._action_trace)[-1] - if self._condition_trace: - last_condition = list(self._condition_trace)[-1] - if self._variables: - trigger = self._variables.get("trigger", {}).get("description") - - result = { - "last_action": last_action, - "last_condition": last_condition, - "run_id": self.run_id, - "state": self._state, - "timestamp": { - "start": self._timestamp_start, - "finish": self._timestamp_finish, - }, - "trigger": trigger, - "domain": self._key[0], - "item_id": self._key[1], - } - if self._error is not None: - result["error"] = str(self._error) - if last_action is not None: - result["last_action"] = last_action - result["last_condition"] = last_condition - - return result - - @contextmanager def trace_automation(hass, item_id, config, context): - """Trace action execution of automation with automation_id.""" - key = ("automation", item_id) - trace = AutomationTrace(key, config, context) - trace_id_set((key, trace.run_id)) - - if key: - traces = hass.data[DATA_TRACE] - if key not in traces: - traces[key] = LimitedSizeDict(size_limit=STORED_TRACES) - traces[key][trace.run_id] = trace + """Trace action execution of automation with item_id.""" + trace = AutomationTrace(item_id, config, context) + async_store_trace(hass, trace) try: yield trace except Exception as ex: # pylint: disable=broad-except - if key: + if item_id: trace.set_error(ex) raise ex finally: - if key: + if item_id: trace.finished() diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 243cdaddd81ca7..b7586841eb7966 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -36,8 +36,11 @@ make_script_schema, ) from homeassistant.helpers.service import async_set_service_schema +from homeassistant.helpers.trace import trace_get, trace_path from homeassistant.loader import bind_hass +from .trace import trace_script + _LOGGER = logging.getLogger(__name__) DOMAIN = "script" @@ -221,7 +224,7 @@ async def service_handler(service): ) script_entities = [ - ScriptEntity(hass, object_id, cfg) + ScriptEntity(hass, object_id, cfg, cfg.raw_config) for object_id, cfg in config.get(DOMAIN, {}).items() ] @@ -253,7 +256,7 @@ class ScriptEntity(ToggleEntity): icon = None - def __init__(self, hass, object_id, cfg): + def __init__(self, hass, object_id, cfg, raw_config): """Initialize the script.""" self.object_id = object_id self.icon = cfg.get(CONF_ICON) @@ -272,6 +275,7 @@ def __init__(self, hass, object_id, cfg): variables=cfg.get(CONF_VARIABLES), ) self._changed = asyncio.Event() + self._raw_config = raw_config @property def should_poll(self): @@ -323,7 +327,7 @@ async def async_turn_on(self, **kwargs): {ATTR_NAME: self.script.name, ATTR_ENTITY_ID: self.entity_id}, context=context, ) - coro = self.script.async_run(variables, context) + coro = self._async_run(variables, context) if wait: await coro return @@ -335,6 +339,16 @@ async def async_turn_on(self, **kwargs): self.hass.async_create_task(coro) await self._changed.wait() + async def _async_run(self, variables, context): + with trace_script( + self.hass, self.object_id, self._raw_config, context + ) as script_trace: + script_trace.set_variables(variables) + # Prepare tracing the execution of the script's sequence + script_trace.set_action_trace(trace_get()) + with trace_path("sequence"): + return await self.script.async_run(variables, context) + async def async_turn_off(self, **kwargs): """Stop running the script. diff --git a/homeassistant/components/script/config.py b/homeassistant/components/script/config.py index 3860a4d0119d3b..5da8bec5a8743b 100644 --- a/homeassistant/components/script/config.py +++ b/homeassistant/components/script/config.py @@ -25,8 +25,21 @@ async def async_validate_config_item(hass, config, full_config=None): return config +class ScriptConfig(dict): + """Dummy class to allow adding attributes.""" + + raw_config = None + + async def _try_async_validate_config_item(hass, object_id, config, full_config=None): """Validate config item.""" + raw_config = None + try: + raw_config = dict(config) + except ValueError: + # Invalid config + pass + try: cv.slug(object_id) config = await async_validate_config_item(hass, config, full_config) @@ -34,6 +47,8 @@ async def _try_async_validate_config_item(hass, object_id, config, full_config=N async_log_exception(ex, DOMAIN, full_config or config, hass) return None + config = ScriptConfig(config) + config.raw_config = raw_config return config diff --git a/homeassistant/components/script/manifest.json b/homeassistant/components/script/manifest.json index b9d333ce553ae7..ab14889a60cf1b 100644 --- a/homeassistant/components/script/manifest.json +++ b/homeassistant/components/script/manifest.json @@ -2,6 +2,7 @@ "domain": "script", "name": "Scripts", "documentation": "https://www.home-assistant.io/integrations/script", + "dependencies": ["trace"], "codeowners": [ "@home-assistant/core" ], diff --git a/homeassistant/components/script/trace.py b/homeassistant/components/script/trace.py new file mode 100644 index 00000000000000..09b22f9813331d --- /dev/null +++ b/homeassistant/components/script/trace.py @@ -0,0 +1,23 @@ +"""Trace support for script.""" +from __future__ import annotations + +from contextlib import contextmanager + +from homeassistant.components.trace import ScriptTrace, async_store_trace + + +@contextmanager +def trace_script(hass, item_id, config, context): + """Trace execution of a script.""" + trace = ScriptTrace(item_id, config, context) + async_store_trace(hass, trace) + + try: + yield trace + except Exception as ex: # pylint: disable=broad-except + if item_id: + trace.set_error(ex) + raise ex + finally: + if item_id: + trace.finished() diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index 0dc8cda6664f27..43deefaa769acf 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -1,12 +1,188 @@ -"""Support for automation and script tracing and debugging.""" +"""Support for script and automation tracing and debugging.""" +from __future__ import annotations + +import datetime as dt +from itertools import count +from typing import Any, Deque + +from homeassistant.core import Context +from homeassistant.helpers.trace import TraceElement, trace_id_set +import homeassistant.util.dt as dt_util + from . import websocket_api -from .const import DATA_TRACE +from .const import DATA_TRACE, STORED_TRACES +from .utils import LimitedSizeDict DOMAIN = "trace" async def async_setup(hass, config): """Initialize the trace integration.""" - hass.data.setdefault(DATA_TRACE, {}) + hass.data[DATA_TRACE] = {} websocket_api.async_setup(hass) return True + + +def async_store_trace(hass, trace): + """Store a trace if its item_id is valid.""" + key = trace.key + if key[1]: + traces = hass.data[DATA_TRACE] + if key not in traces: + traces[key] = LimitedSizeDict(size_limit=STORED_TRACES) + traces[key][trace.run_id] = trace + + +class ActionTrace: + """Base container for an script or automation trace.""" + + _run_ids = count(0) + + def __init__( + self, + key: tuple[str, str], + config: dict[str, Any], + context: Context, + ): + """Container for script trace.""" + self._action_trace: dict[str, Deque[TraceElement]] | None = None + self._config: dict[str, Any] = config + self.context: Context = context + self._error: Exception | None = None + self._state: str = "running" + self.run_id: str = str(next(self._run_ids)) + self._timestamp_finish: dt.datetime | None = None + self._timestamp_start: dt.datetime = dt_util.utcnow() + self.key: tuple[str, str] = key + self._variables: dict[str, Any] | None = None + trace_id_set((key, self.run_id)) + + def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: + """Set action trace.""" + self._action_trace = trace + + def set_error(self, ex: Exception) -> None: + """Set error.""" + self._error = ex + + def set_variables(self, variables: dict[str, Any]) -> None: + """Set variables.""" + self._variables = variables + + def finished(self) -> None: + """Set finish time.""" + self._timestamp_finish = dt_util.utcnow() + self._state = "stopped" + + def as_dict(self) -> dict[str, Any]: + """Return dictionary version of this ActionTrace.""" + + result = self.as_short_dict() + + action_traces = {} + if self._action_trace: + for key, trace_list in self._action_trace.items(): + action_traces[key] = [item.as_dict() for item in trace_list] + + result.update( + { + "action_trace": action_traces, + "config": self._config, + "context": self.context, + "variables": self._variables, + } + ) + if self._error is not None: + result["error"] = str(self._error) + return result + + def as_short_dict(self) -> dict[str, Any]: + """Return a brief dictionary version of this ActionTrace.""" + + last_action = None + + if self._action_trace: + last_action = list(self._action_trace)[-1] + + result = { + "last_action": last_action, + "run_id": self.run_id, + "state": self._state, + "timestamp": { + "start": self._timestamp_start, + "finish": self._timestamp_finish, + }, + "domain": self.key[0], + "item_id": self.key[1], + } + if self._error is not None: + result["error"] = str(self._error) + if last_action is not None: + result["last_action"] = last_action + + return result + + +class AutomationTrace(ActionTrace): + """Container for automation trace.""" + + def __init__( + self, + item_id: str, + config: dict[str, Any], + context: Context, + ): + """Container for automation trace.""" + key = ("automation", item_id) + super().__init__(key, config, context) + self._condition_trace: dict[str, Deque[TraceElement]] | None = None + + def set_condition_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: + """Set condition trace.""" + self._condition_trace = trace + + def as_dict(self) -> dict[str, Any]: + """Return dictionary version of this AutomationTrace.""" + + result = super().as_dict() + + condition_traces = {} + + if self._condition_trace: + for key, trace_list in self._condition_trace.items(): + condition_traces[key] = [item.as_dict() for item in trace_list] + result["condition_trace"] = condition_traces + + return result + + def as_short_dict(self) -> dict[str, Any]: + """Return a brief dictionary version of this AutomationTrace.""" + + result = super().as_short_dict() + + last_condition = None + trigger = None + + if self._condition_trace: + last_condition = list(self._condition_trace)[-1] + if self._variables: + trigger = self._variables.get("trigger", {}).get("description") + + result["trigger"] = trigger + result["last_condition"] = last_condition + + return result + + +class ScriptTrace(ActionTrace): + """Container for automation trace.""" + + def __init__( + self, + item_id: str, + config: dict[str, Any], + context: Context, + ): + """Container for automation trace.""" + key = ("script", item_id) + super().__init__(key, config, context) diff --git a/homeassistant/components/trace/const.py b/homeassistant/components/trace/const.py index 547bdb35c7788c..05942d7ee4dc6f 100644 --- a/homeassistant/components/trace/const.py +++ b/homeassistant/components/trace/const.py @@ -1,4 +1,4 @@ -"""Shared constants for automation and script tracing and debugging.""" +"""Shared constants for script and automation tracing and debugging.""" DATA_TRACE = "trace" -STORED_TRACES = 5 # Stored traces per automation +STORED_TRACES = 5 # Stored traces per script or automation diff --git a/homeassistant/components/trace/websocket_api.py b/homeassistant/components/trace/websocket_api.py index 1f42b50671ec4e..1b5270f6253e45 100644 --- a/homeassistant/components/trace/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -28,7 +28,7 @@ # mypy: allow-untyped-calls, allow-untyped-defs -TRACE_DOMAINS = ["automation"] +TRACE_DOMAINS = ["automation", "script"] @callback diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index d6de845248f174..ba39e19943b80b 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -71,7 +71,7 @@ def as_dict(self) -> dict[str, Any]: ) # Copy of last variables variables_cv: ContextVar[Any | None] = ContextVar("variables_cv", default=None) -# Automation ID + Run ID +# (domain, item_id) + Run ID trace_id_cv: ContextVar[tuple[str, str] | None] = ContextVar( "trace_id_cv", default=None ) diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index e07c042d1d0ef6..8dc09731b79a5a 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -1,31 +1,42 @@ """Test Trace websocket API.""" -from unittest.mock import patch +import pytest from homeassistant.bootstrap import async_setup_component -from homeassistant.components import config from homeassistant.components.trace.const import STORED_TRACES from homeassistant.core import Context from tests.common import assert_lists_same -from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401 -def _find_run_id(traces, item_id): - """Find newest run_id for an automation.""" +def _find_run_id(traces, trace_type, item_id): + """Find newest run_id for an automation or script.""" for trace in reversed(traces): - if trace["item_id"] == item_id: + if trace["domain"] == trace_type and trace["item_id"] == item_id: return trace["run_id"] return None +def _find_traces(traces, trace_type, item_id): + """Find traces for an automation or script.""" + return [ + trace + for trace in traces + if trace["domain"] == trace_type and trace["item_id"] == item_id + ] + + +# TODO: Remove def _find_traces_for_automation(traces, item_id): """Find traces for an automation.""" return [trace for trace in traces if trace["item_id"] == item_id] -async def test_get_automation_trace(hass, hass_ws_client): - """Test tracing an automation.""" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_get_trace(hass, hass_ws_client, domain, prefix): + """Test tracing an automation or script.""" id = 1 def next_id(): @@ -50,6 +61,9 @@ def next_id(): }, "action": {"event": "another_event"}, } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + moon_config = {"sequence": moon_config["action"]} sun_action = { "limit": 10, @@ -63,40 +77,38 @@ def next_id(): } moon_action = {"event": "another_event", "event_data": {}} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component( + hass, domain, {domain: [sun_config, moon_config]} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) client = await hass_ws_client() contexts = {} - # Trigger "sun" automation + # Trigger "sun" automation / run "sun" script context = Context() - hass.bus.async_fire("test_event", context=context) + if domain == "automation": + hass.bus.async_fire("test_event", context=context) + else: + await hass.services.async_call("script", "sun", context=context) await hass.async_block_till_done() # List traces await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "sun") + run_id = _find_run_id(response["result"], domain, "sun") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -104,41 +116,47 @@ def next_id(): response = await client.receive_json() assert response["success"] trace = response["result"] - assert trace["context"]["parent_id"] == context.id assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert trace["action_trace"]["action/0"][0]["error"] - assert trace["action_trace"]["action/0"][0]["result"] == sun_action - assert trace["condition_trace"] == {} + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + assert trace["action_trace"][f"{prefix}/0"][0]["error"] + assert trace["action_trace"][f"{prefix}/0"][0]["result"] == sun_action assert trace["config"] == sun_config assert trace["context"] assert trace["error"] == "Unable to find service test.automation" assert trace["state"] == "stopped" - assert trace["trigger"] == "event 'test_event'" assert trace["item_id"] == "sun" - assert trace["variables"] + assert trace["variables"] is not None + if domain == "automation": + assert trace["condition_trace"] == {} + assert trace["context"]["parent_id"] == context.id + assert trace["trigger"] == "event 'test_event'" + else: + assert trace["context"]["id"] == context.id contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") + # Trigger "moon" automation, with passing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() # List traces await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "moon") + run_id = _find_run_id(response["result"], domain, "moon") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "moon", "run_id": run_id, } @@ -147,26 +165,36 @@ def next_id(): assert response["success"] trace = response["result"] assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert trace["action_trace"]["action/0"][0]["result"] == moon_action - assert len(trace["condition_trace"]) == 1 - assert len(trace["condition_trace"]["condition/0"]) == 1 - assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + assert "error" not in trace["action_trace"][f"{prefix}/0"][0] + assert trace["action_trace"][f"{prefix}/0"][0]["result"] == moon_action assert trace["config"] == moon_config assert trace["context"] assert "error" not in trace assert trace["state"] == "stopped" - assert trace["trigger"] == "event 'test_event2'" assert trace["item_id"] == "moon" - assert trace["variables"] + assert trace["variables"] is not None + + if domain == "automation": + assert len(trace["condition_trace"]) == 1 + assert len(trace["condition_trace"]["condition/0"]) == 1 + assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} + assert trace["trigger"] == "event 'test_event2'" contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } - # Trigger "moon" automation, with failing condition + if domain == "script": + # Check contexts + await client.send_json({"id": next_id(), "type": "trace/contexts"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == contexts + return + + # Trigger "moon" automation with failing condition hass.bus.async_fire("test_event3") await hass.async_block_till_done() @@ -174,14 +202,14 @@ def next_id(): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "moon") + run_id = _find_run_id(response["result"], "automation", "moon") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "moon", "run_id": run_id, } @@ -202,11 +230,11 @@ def next_id(): assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } - # Trigger "moon" automation, with passing condition + # Trigger "moon" automation with passing condition hass.bus.async_fire("test_event2") await hass.async_block_till_done() @@ -214,14 +242,14 @@ def next_id(): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - run_id = _find_run_id(response["result"], "moon") + run_id = _find_run_id(response["result"], "automation", "moon") # Get trace await client.send_json( { "id": next_id(), "type": "trace/get", - "domain": "automation", + "domain": domain, "item_id": "moon", "run_id": run_id, } @@ -230,9 +258,9 @@ def next_id(): assert response["success"] trace = response["result"] assert len(trace["action_trace"]) == 1 - assert len(trace["action_trace"]["action/0"]) == 1 - assert "error" not in trace["action_trace"]["action/0"][0] - assert trace["action_trace"]["action/0"][0]["result"] == moon_action + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + assert "error" not in trace["action_trace"][f"{prefix}/0"][0] + assert trace["action_trace"][f"{prefix}/0"][0]["result"] == moon_action assert len(trace["condition_trace"]) == 1 assert len(trace["condition_trace"]["condition/0"]) == 1 assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True} @@ -245,7 +273,7 @@ def next_id(): assert trace["variables"] contexts[trace["context"]["id"]] = { "run_id": trace["run_id"], - "domain": "automation", + "domain": domain, "item_id": trace["item_id"], } @@ -256,8 +284,9 @@ def next_id(): assert response["result"] == contexts -async def test_automation_trace_overflow(hass, hass_ws_client): - """Test the number of stored traces per automation is limited.""" +@pytest.mark.parametrize("domain", ["automation", "script"]) +async def test_trace_overflow(hass, hass_ws_client, domain): + """Test the number of stored traces per automation or script is limited.""" id = 1 def next_id(): @@ -275,20 +304,18 @@ def next_id(): "trigger": {"platform": "event", "event_type": "test_event2"}, "action": {"event": "another_event"}, } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + moon_config = {"sequence": moon_config["action"]} + + if domain == "automation": + assert await async_setup_component( + hass, domain, {domain: [sun_config, moon_config]} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) client = await hass_ws_client() @@ -297,37 +324,47 @@ def next_id(): assert response["success"] assert response["result"] == [] - # Trigger "sun" and "moon" automation once - hass.bus.async_fire("test_event") - hass.bus.async_fire("test_event2") + # Trigger "sun" and "moon" automation / script once + if domain == "automation": + hass.bus.async_fire("test_event") + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "sun") + await hass.services.async_call("script", "moon") await hass.async_block_till_done() # List traces await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - assert len(_find_traces_for_automation(response["result"], "moon")) == 1 - moon_run_id = _find_run_id(response["result"], "moon") - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "moon")) == 1 + moon_run_id = _find_run_id(response["result"], domain, "moon") + assert len(_find_traces(response["result"], domain, "sun")) == 1 - # Trigger "moon" automation enough times to overflow the number of stored traces + # Trigger "moon" enough times to overflow the max number of stored traces for _ in range(STORED_TRACES): - hass.bus.async_fire("test_event2") + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - moon_traces = _find_traces_for_automation(response["result"], "moon") + moon_traces = _find_traces(response["result"], domain, "moon") assert len(moon_traces) == STORED_TRACES assert moon_traces[0] assert int(moon_traces[0]["run_id"]) == int(moon_run_id) + 1 assert int(moon_traces[-1]["run_id"]) == int(moon_run_id) + STORED_TRACES - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "sun")) == 1 -async def test_list_automation_traces(hass, hass_ws_client): - """Test listing automation traces.""" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_list_traces(hass, hass_ws_client, domain, prefix): + """Test listing automation and script traces.""" id = 1 def next_id(): @@ -352,20 +389,18 @@ def next_id(): }, "action": {"event": "another_event"}, } - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - moon_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + moon_config = {"sequence": moon_config["action"]} + + if domain == "automation": + assert await async_setup_component( + hass, domain, {domain: [sun_config, moon_config]} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) client = await hass_ws_client() @@ -375,19 +410,17 @@ def next_id(): assert response["result"] == [] await client.send_json( - { - "id": next_id(), - "type": "trace/list", - "domain": "automation", - "item_id": "sun", - } + {"id": next_id(), "type": "trace/list", "domain": domain, "item_id": "sun"} ) response = await client.receive_json() assert response["success"] assert response["result"] == [] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") await hass.async_block_till_done() # Get trace @@ -395,90 +428,98 @@ def next_id(): response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "sun")) == 1 await client.send_json( - { - "id": next_id(), - "type": "trace/list", - "domain": "automation", - "item_id": "sun", - } + {"id": next_id(), "type": "trace/list", "domain": domain, "item_id": "sun"} ) response = await client.receive_json() assert response["success"] assert len(response["result"]) == 1 - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 + assert len(_find_traces(response["result"], domain, "sun")) == 1 await client.send_json( - { - "id": next_id(), - "type": "trace/list", - "domain": "automation", - "item_id": "moon", - } + {"id": next_id(), "type": "trace/list", "domain": domain, "item_id": "moon"} ) response = await client.receive_json() assert response["success"] assert response["result"] == [] - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") + # Trigger "moon" automation, with passing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() - # Trigger "moon" automation, with failing condition - hass.bus.async_fire("test_event3") + # Trigger "moon" automation, with failing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event3") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() - # Trigger "moon" automation, with passing condition - hass.bus.async_fire("test_event2") + # Trigger "moon" automation, with passing condition / run "moon" script + if domain == "automation": + hass.bus.async_fire("test_event2") + else: + await hass.services.async_call("script", "moon") await hass.async_block_till_done() # Get trace await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - assert len(_find_traces_for_automation(response["result"], "moon")) == 3 - assert len(_find_traces_for_automation(response["result"], "sun")) == 1 - trace = _find_traces_for_automation(response["result"], "sun")[0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] is None + assert len(_find_traces(response["result"], domain, "moon")) == 3 + assert len(_find_traces(response["result"], domain, "sun")) == 1 + trace = _find_traces(response["result"], domain, "sun")[0] + assert trace["last_action"] == f"{prefix}/0" assert trace["error"] == "Unable to find service test.automation" assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event'" assert trace["item_id"] == "sun" + if domain == "automation": + assert trace["last_condition"] is None + assert trace["trigger"] == "event 'test_event'" - trace = _find_traces_for_automation(response["result"], "moon")[0] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" + trace = _find_traces(response["result"], domain, "moon")[0] + assert trace["last_action"] == f"{prefix}/0" assert "error" not in trace assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" assert trace["item_id"] == "moon" + if domain == "automation": + assert trace["last_condition"] == "condition/0" + assert trace["trigger"] == "event 'test_event2'" - trace = _find_traces_for_automation(response["result"], "moon")[1] - assert trace["last_action"] is None - assert trace["last_condition"] == "condition/0" + trace = _find_traces(response["result"], domain, "moon")[1] assert "error" not in trace assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event3'" assert trace["item_id"] == "moon" - - trace = _find_traces_for_automation(response["result"], "moon")[2] - assert trace["last_action"] == "action/0" - assert trace["last_condition"] == "condition/0" + if domain == "automation": + assert trace["last_action"] is None + assert trace["last_condition"] == "condition/0" + assert trace["trigger"] == "event 'test_event3'" + else: + assert trace["last_action"] == f"{prefix}/0" + + trace = _find_traces(response["result"], domain, "moon")[2] + assert trace["last_action"] == f"{prefix}/0" assert "error" not in trace assert trace["state"] == "stopped" assert trace["timestamp"] - assert trace["trigger"] == "event 'test_event2'" assert trace["item_id"] == "moon" + if domain == "automation": + assert trace["last_condition"] == "condition/0" + assert trace["trigger"] == "event 'test_event2'" -async def test_automation_breakpoints(hass, hass_ws_client): - """Test automation breakpoints.""" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_breakpoints(hass, hass_ws_client, domain, prefix): + """Test automation and script breakpoints.""" id = 1 def next_id(): @@ -490,7 +531,7 @@ async def assert_last_action(item_id, expected_action, expected_state): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], item_id)[-1] + trace = _find_traces(response["result"], domain, item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -510,19 +551,13 @@ async def assert_last_action(item_id, expected_action, expected_state): {"event": "event8"}, ], } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + else: + assert await async_setup_component(hass, domain, {domain: {"sun": sun_config}}) client = await hass_ws_client() @@ -530,7 +565,7 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", "node": "1", } @@ -554,9 +589,9 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() @@ -565,9 +600,9 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", } ) response = await client.receive_json() @@ -579,30 +614,23 @@ async def assert_last_action(item_id, expected_action, expected_state): assert_lists_same( response["result"], [ - { - "node": "action/1", - "run_id": "*", - "domain": "automation", - "item_id": "sun", - }, - { - "node": "action/5", - "run_id": "*", - "domain": "automation", - "item_id": "sun", - }, + {"node": f"{prefix}/1", "run_id": "*", "domain": domain, "item_id": "sun"}, + {"node": f"{prefix}/5", "run_id": "*", "domain": domain, "item_id": "sun"}, ], ) - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") + run_id = await assert_last_action("sun", f"{prefix}/1", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", "run_id": run_id, } @@ -610,7 +638,7 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/step", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -619,11 +647,11 @@ async def assert_last_action(item_id, expected_action, expected_state): assert response["success"] response = await client.receive_json() - run_id = await assert_last_action("sun", "action/2", "running") + run_id = await assert_last_action("sun", f"{prefix}/2", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/2", + "node": f"{prefix}/2", "run_id": run_id, } @@ -631,7 +659,7 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/continue", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -640,11 +668,11 @@ async def assert_last_action(item_id, expected_action, expected_state): assert response["success"] response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") + run_id = await assert_last_action("sun", f"{prefix}/5", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", "run_id": run_id, } @@ -652,7 +680,7 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/stop", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -660,10 +688,13 @@ async def assert_last_action(item_id, expected_action, expected_state): response = await client.receive_json() assert response["success"] await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") + await assert_last_action("sun", f"{prefix}/5", "stopped") -async def test_automation_breakpoints_2(hass, hass_ws_client): +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_breakpoints_2(hass, hass_ws_client, domain, prefix): """Test execution resumes and breakpoints are removed after subscription removed.""" id = 1 @@ -676,7 +707,7 @@ async def assert_last_action(item_id, expected_action, expected_state): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], item_id)[-1] + trace = _find_traces(response["result"], domain, item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -696,19 +727,13 @@ async def assert_last_action(item_id, expected_action, expected_state): {"event": "event8"}, ], } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + else: + assert await async_setup_component(hass, domain, {domain: {"sun": sun_config}}) client = await hass_ws_client() @@ -723,23 +748,26 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() assert response["success"] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") + run_id = await assert_last_action("sun", f"{prefix}/1", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", "run_id": run_id, } @@ -750,14 +778,14 @@ async def assert_last_action(item_id, expected_action, expected_state): response = await client.receive_json() assert response["success"] await hass.async_block_till_done() - await assert_last_action("sun", "action/8", "stopped") + await assert_last_action("sun", f"{prefix}/8", "stopped") # Should not be possible to set breakpoints await client.send_json( { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", "node": "1", } @@ -765,15 +793,21 @@ async def assert_last_action(item_id, expected_action, expected_state): response = await client.receive_json() assert not response["success"] - # Trigger "sun" automation, should finish without stopping on breakpoints - hass.bus.async_fire("test_event") + # Trigger "sun" automation / script, should finish without stopping on breakpoints + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") await hass.async_block_till_done() - new_run_id = await assert_last_action("sun", "action/8", "stopped") + new_run_id = await assert_last_action("sun", f"{prefix}/8", "stopped") assert new_run_id != run_id -async def test_automation_breakpoints_3(hass, hass_ws_client): +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_breakpoints_3(hass, hass_ws_client, domain, prefix): """Test breakpoints can be cleared.""" id = 1 @@ -786,7 +820,7 @@ async def assert_last_action(item_id, expected_action, expected_state): await client.send_json({"id": next_id(), "type": "trace/list"}) response = await client.receive_json() assert response["success"] - trace = _find_traces_for_automation(response["result"], item_id)[-1] + trace = _find_traces(response["result"], domain, item_id)[-1] assert trace["last_action"] == expected_action assert trace["state"] == expected_state return trace["run_id"] @@ -806,19 +840,13 @@ async def assert_last_action(item_id, expected_action, expected_state): {"event": "event8"}, ], } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - sun_config, - ] - }, - ) - - with patch.object(config, "SECTIONS", ["automation"]): - await async_setup_component(hass, "config", {}) + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + else: + assert await async_setup_component(hass, domain, {domain: {"sun": sun_config}}) client = await hass_ws_client() @@ -833,9 +861,9 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() @@ -845,23 +873,26 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/breakpoint/set", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", } ) response = await client.receive_json() assert response["success"] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/1", "running") + run_id = await assert_last_action("sun", f"{prefix}/1", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", "run_id": run_id, } @@ -869,7 +900,7 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/continue", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -878,11 +909,11 @@ async def assert_last_action(item_id, expected_action, expected_state): assert response["success"] response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") + run_id = await assert_last_action("sun", f"{prefix}/5", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", "run_id": run_id, } @@ -890,7 +921,7 @@ async def assert_last_action(item_id, expected_action, expected_state): { "id": next_id(), "type": "trace/debug/stop", - "domain": "automation", + "domain": domain, "item_id": "sun", "run_id": run_id, } @@ -898,29 +929,32 @@ async def assert_last_action(item_id, expected_action, expected_state): response = await client.receive_json() assert response["success"] await hass.async_block_till_done() - await assert_last_action("sun", "action/5", "stopped") + await assert_last_action("sun", f"{prefix}/5", "stopped") # Clear 1st breakpoint await client.send_json( { "id": next_id(), "type": "trace/debug/breakpoint/clear", - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/1", + "node": f"{prefix}/1", } ) response = await client.receive_json() assert response["success"] - # Trigger "sun" automation - hass.bus.async_fire("test_event") + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") response = await client.receive_json() - run_id = await assert_last_action("sun", "action/5", "running") + run_id = await assert_last_action("sun", f"{prefix}/5", "running") assert response["event"] == { - "domain": "automation", + "domain": domain, "item_id": "sun", - "node": "action/5", + "node": f"{prefix}/5", "run_id": run_id, } From a9ccba44ed3443e65892c18f266abf38c52bcc7d Mon Sep 17 00:00:00 2001 From: scyto Date: Wed, 24 Mar 2021 11:46:11 -0700 Subject: [PATCH 1485/1818] Add support for Roomba 980 discovery (#47696) Co-authored-by: J. Nick Koston --- .../components/roomba/config_flow.py | 2 +- homeassistant/components/roomba/manifest.json | 12 ++++- homeassistant/generated/dhcp.py | 5 ++ tests/components/roomba/test_config_flow.py | 48 ++++++++++++++----- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 787382ed8b5f2d..c7614de20ac4d2 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -80,7 +80,7 @@ async def async_step_dhcp(self, dhcp_discovery): if self._async_host_already_configured(dhcp_discovery[IP_ADDRESS]): return self.async_abort(reason="already_configured") - if not dhcp_discovery[HOSTNAME].startswith("iRobot-"): + if not dhcp_discovery[HOSTNAME].startswith(("iRobot-", "Roomba-")): return self.async_abort(reason="not_irobot_device") blid = _async_blid_from_hostname(dhcp_discovery[HOSTNAME]) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 5ceb44ff780bd8..d1858a46fdc9d5 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -5,5 +5,15 @@ "documentation": "https://www.home-assistant.io/integrations/roomba", "requirements": ["roombapy==1.6.2"], "codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"], - "dhcp": [{"hostname":"irobot-*","macaddress":"501479*"}] + "dhcp": [ + { + "hostname" : "irobot-*", + "macaddress" : "501479*" + }, + { + "hostname" : "roomba-*", + "macaddress" : "80A589*" + } + ] } + diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 90d846377c6957..b10893230db958 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -119,6 +119,11 @@ "hostname": "irobot-*", "macaddress": "501479*" }, + { + "domain": "roomba", + "hostname": "roomba-*", + "macaddress": "80A589*" + }, { "domain": "screenlogic", "hostname": "pentair: *", diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index b597717e4a8ac8..9313220f2a8ff2 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -1,6 +1,7 @@ """Test the iRobot Roomba config flow.""" from unittest.mock import MagicMock, PropertyMock, patch +import pytest from roombapy import RoombaConnectionError from roombapy.roomba import RoombaInfo @@ -12,7 +13,34 @@ from tests.common import MockConfigEntry MOCK_IP = "1.2.3.4" -VALID_CONFIG = {CONF_HOST: "1.2.3.4", CONF_BLID: "blid", CONF_PASSWORD: "password"} +VALID_CONFIG = {CONF_HOST: MOCK_IP, CONF_BLID: "blid", CONF_PASSWORD: "password"} + +DHCP_DISCOVERY_DEVICES = [ + { + IP_ADDRESS: MOCK_IP, + MAC_ADDRESS: "50:14:79:DD:EE:FF", + HOSTNAME: "iRobot-blid", + }, + { + IP_ADDRESS: MOCK_IP, + MAC_ADDRESS: "80:A5:89:DD:EE:FF", + HOSTNAME: "Roomba-blid", + }, +] + + +DHCP_DISCOVERY_DEVICES_WITHOUT_MATCHING_IP = [ + { + IP_ADDRESS: "1.1.1.1", + MAC_ADDRESS: "50:14:79:DD:EE:FF", + HOSTNAME: "iRobot-blid", + }, + { + IP_ADDRESS: "1.1.1.1", + MAC_ADDRESS: "80:A5:89:DD:EE:FF", + HOSTNAME: "Roomba-blid", + }, +] def _create_mocked_roomba( @@ -577,7 +605,8 @@ async def test_form_user_discovery_and_password_fetch_gets_connection_refused(ha assert len(mock_setup_entry.mock_calls) == 1 -async def test_dhcp_discovery_and_roomba_discovery_finds(hass): +@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES) +async def test_dhcp_discovery_and_roomba_discovery_finds(hass, discovery_data): """Test we can process the discovery from dhcp and roomba discovery matches the device.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -592,11 +621,7 @@ async def test_dhcp_discovery_and_roomba_discovery_finds(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data={ - IP_ADDRESS: MOCK_IP, - MAC_ADDRESS: "AA:BB:CC:DD:EE:FF", - HOSTNAME: "iRobot-blid", - }, + data=discovery_data, ) await hass.async_block_till_done() @@ -637,7 +662,8 @@ async def test_dhcp_discovery_and_roomba_discovery_finds(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_dhcp_discovery_falls_back_to_manual(hass): +@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES_WITHOUT_MATCHING_IP) +async def test_dhcp_discovery_falls_back_to_manual(hass, discovery_data): """Test we can process the discovery from dhcp but roomba discovery cannot find the device.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -652,11 +678,7 @@ async def test_dhcp_discovery_falls_back_to_manual(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, - data={ - IP_ADDRESS: "1.1.1.1", - MAC_ADDRESS: "AA:BB:CC:DD:EE:FF", - HOSTNAME: "iRobot-blid", - }, + data=discovery_data, ) await hass.async_block_till_done() From 6fc3406c938eb6f88ea057603a9346ec2aba3721 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 24 Mar 2021 20:05:53 +0100 Subject: [PATCH 1486/1818] Ignore python-typing-update for pre-commit requirements (#48292) --- requirements_test_pre_commit.txt | 1 - script/gen_requirements_all.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index b8fabc685b1189..6a5768d34a11e3 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -8,6 +8,5 @@ flake8-docstrings==1.5.0 flake8==3.8.4 isort==5.7.0 pydocstyle==5.1.1 -python-typing-update==0.3.0 pyupgrade==2.11.0 yamllint==1.24.2 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 94365be9a50f44..ac1e4bc2ed9e43 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -96,6 +96,7 @@ "check-json", "no-commit-to-branch", "prettier", + "python-typing-update", ) From cc12d29f6dc7de026e672a2f834cdba0ae569509 Mon Sep 17 00:00:00 2001 From: djtimca <60706061+djtimca@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:58:03 -0400 Subject: [PATCH 1487/1818] Bump omnilogic to 0.4.3 to fix API certificate issue (#48296) * Bump omnilogic to 0.4.3 to fix API certificate issue. * Updated requirements files. --- homeassistant/components/omnilogic/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/omnilogic/manifest.json b/homeassistant/components/omnilogic/manifest.json index d999a34f0761ed..2b2a4a9fe3d318 100644 --- a/homeassistant/components/omnilogic/manifest.json +++ b/homeassistant/components/omnilogic/manifest.json @@ -3,6 +3,6 @@ "name": "Hayward Omnilogic", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/omnilogic", - "requirements": ["omnilogic==0.4.2"], + "requirements": ["omnilogic==0.4.3"], "codeowners": ["@oliver84","@djtimca","@gentoosu"] } diff --git a/requirements_all.txt b/requirements_all.txt index 56459895f1db13..531c91bb0c89a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1028,7 +1028,7 @@ objgraph==3.4.1 oemthermostat==1.1.1 # homeassistant.components.omnilogic -omnilogic==0.4.2 +omnilogic==0.4.3 # homeassistant.components.ondilo_ico ondilo==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f9d3520dcf68a3..89c19c1bdf55c8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -531,7 +531,7 @@ oauth2client==4.0.0 objgraph==3.4.1 # homeassistant.components.omnilogic -omnilogic==0.4.2 +omnilogic==0.4.3 # homeassistant.components.ondilo_ico ondilo==0.2.0 From c340a39275c2eb4ffe9a410f207fca303cdb92aa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Mar 2021 12:33:44 -1000 Subject: [PATCH 1488/1818] Handle range conversions that do not start at 1 (#48298) --- homeassistant/util/percentage.py | 6 ++++-- tests/util/test_percentage.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index c257ca2268c549..ec05a2dc2ec608 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -66,7 +66,8 @@ def ranged_value_to_percentage( (1,255), 127: 50 (1,255), 10: 4 """ - return int((value * 100) // states_in_range(low_high_range)) + offset = low_high_range[0] - 1 + return int(((value - offset) * 100) // states_in_range(low_high_range)) def percentage_to_ranged_value( @@ -83,7 +84,8 @@ def percentage_to_ranged_value( (1,255), 50: 127.5 (1,255), 4: 10.2 """ - return states_in_range(low_high_range) * percentage / 100 + offset = low_high_range[0] - 1 + return states_in_range(low_high_range) * percentage / 100 + offset def states_in_range(low_high_range: tuple[float, float]) -> float: diff --git a/tests/util/test_percentage.py b/tests/util/test_percentage.py index 31420d3c07666d..37e4c6d9615c3c 100644 --- a/tests/util/test_percentage.py +++ b/tests/util/test_percentage.py @@ -147,3 +147,34 @@ async def test_percentage_to_ranged_value_small(): assert math.ceil(percentage_to_ranged_value(range, 66)) == 4 assert math.ceil(percentage_to_ranged_value(range, 83)) == 5 assert math.ceil(percentage_to_ranged_value(range, 100)) == 6 + + +async def test_ranged_value_to_percentage_starting_at_one(): + """Test a range that starts with 1.""" + range = (1, 4) + + assert ranged_value_to_percentage(range, 1) == 25 + assert ranged_value_to_percentage(range, 2) == 50 + assert ranged_value_to_percentage(range, 3) == 75 + assert ranged_value_to_percentage(range, 4) == 100 + + +async def test_ranged_value_to_percentage_starting_high(): + """Test a range that does not start with 1.""" + range = (101, 255) + + assert ranged_value_to_percentage(range, 101) == 0 + assert ranged_value_to_percentage(range, 139) == 25 + assert ranged_value_to_percentage(range, 178) == 50 + assert ranged_value_to_percentage(range, 217) == 75 + assert ranged_value_to_percentage(range, 255) == 100 + + +async def test_ranged_value_to_percentage_starting_zero(): + """Test a range that starts with 0.""" + range = (0, 3) + + assert ranged_value_to_percentage(range, 0) == 25 + assert ranged_value_to_percentage(range, 1) == 50 + assert ranged_value_to_percentage(range, 2) == 75 + assert ranged_value_to_percentage(range, 3) == 100 From 6660fb74787b6cbdfe3abc796bb71fe9d7fffe0f Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 25 Mar 2021 00:03:36 +0000 Subject: [PATCH 1489/1818] [ci skip] Translation update --- .../components/august/translations/fr.json | 7 +++- .../components/august/translations/pl.json | 16 ++++++++ .../components/homekit/translations/fr.json | 2 +- .../components/hyperion/translations/fr.json | 1 + .../components/hyperion/translations/pl.json | 1 + .../opentherm_gw/translations/pl.json | 4 +- .../philips_js/translations/fr.json | 3 ++ .../philips_js/translations/pl.json | 7 ++++ .../screenlogic/translations/fr.json | 6 +++ .../screenlogic/translations/pl.json | 39 +++++++++++++++++++ .../components/verisure/translations/fr.json | 7 +++- 11 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/screenlogic/translations/pl.json diff --git a/homeassistant/components/august/translations/fr.json b/homeassistant/components/august/translations/fr.json index 530fda4dc9f269..967fb249d971cf 100644 --- a/homeassistant/components/august/translations/fr.json +++ b/homeassistant/components/august/translations/fr.json @@ -11,6 +11,9 @@ }, "step": { "reauth_validate": { + "data": { + "password": "Mot de passe" + }, "description": "Saisissez le mot de passe de {username} .", "title": "R\u00e9authentifier un compte August" }, @@ -26,7 +29,9 @@ }, "user_validate": { "data": { - "login_method": "M\u00e9thode de connexion" + "login_method": "M\u00e9thode de connexion", + "password": "Mot de passe", + "username": "Nom d'utilisateur" }, "description": "Si la m\u00e9thode de connexion est \u00abemail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", "title": "Cr\u00e9er un compte August" diff --git a/homeassistant/components/august/translations/pl.json b/homeassistant/components/august/translations/pl.json index e76b663c3078de..a5539bea93ab5e 100644 --- a/homeassistant/components/august/translations/pl.json +++ b/homeassistant/components/august/translations/pl.json @@ -10,6 +10,13 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "reauth_validate": { + "data": { + "password": "Has\u0142o" + }, + "description": "Wprowad\u017a has\u0142o dla {username}", + "title": "Ponownie uwierzytelnij konto August" + }, "user": { "data": { "login_method": "Metoda logowania", @@ -20,6 +27,15 @@ "description": "Je\u015bli metod\u0105 logowania jest 'e-mail', nazw\u0105 u\u017cytkownika b\u0119dzie adres e-mail. Je\u015bli metod\u0105 logowania jest 'telefon', nazw\u0105 u\u017cytkownika b\u0119dzie numer telefonu w formacie '+NNNNNNNNN'.", "title": "Konfiguracja konta August" }, + "user_validate": { + "data": { + "login_method": "Metoda logowania", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Je\u015bli metod\u0105 logowania jest 'e-mail', nazw\u0105 u\u017cytkownika b\u0119dzie adres e-mail. Je\u015bli metod\u0105 logowania jest 'telefon', nazw\u0105 u\u017cytkownika b\u0119dzie numer telefonu w formacie '+NNNNNNNNN'.", + "title": "Konfiguracja konta August" + }, "validation": { "data": { "code": "Kod weryfikacyjny" diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index 4721514e61520e..dae09002c54ae4 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -55,7 +55,7 @@ "entities": "Entit\u00e9s", "mode": "Mode" }, - "description": "Choisissez les entit\u00e9s \u00e0 exposer. En mode accessoire, une seule entit\u00e9 est expos\u00e9e. En mode d'inclusion de pont, toutes les entit\u00e9s du domaine seront expos\u00e9es \u00e0 moins que des entit\u00e9s sp\u00e9cifiques ne soient s\u00e9lectionn\u00e9es. En mode d'exclusion de pont, toutes les entit\u00e9s du domaine seront expos\u00e9es \u00e0 l'exception des entit\u00e9s exclues.", + "description": "Choisissez les entit\u00e9s \u00e0 inclure. En mode accessoire, une seule entit\u00e9 est incluse. En mode d'inclusion de pont, toutes les entit\u00e9s du domaine seront incluses \u00e0 moins que des entit\u00e9s sp\u00e9cifiques ne soient s\u00e9lectionn\u00e9es. En mode d'exclusion de pont, toutes les entit\u00e9s du domaine seront incluses \u00e0 l'exception des entit\u00e9s exclues. Pour de meilleures performances, un accessoire HomeKit distinct sera cr\u00e9\u00e9 pour chaque lecteur multim\u00e9dia TV et cam\u00e9ra.", "title": "S\u00e9lectionnez les entit\u00e9s \u00e0 exposer" }, "init": { diff --git a/homeassistant/components/hyperion/translations/fr.json b/homeassistant/components/hyperion/translations/fr.json index f69fd6acdc63e1..57870c3b3efcf5 100644 --- a/homeassistant/components/hyperion/translations/fr.json +++ b/homeassistant/components/hyperion/translations/fr.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Effets Hyperion \u00e0 montrer", "priority": "Priorit\u00e9 Hyperion \u00e0 utiliser pour les couleurs et les effets" } } diff --git a/homeassistant/components/hyperion/translations/pl.json b/homeassistant/components/hyperion/translations/pl.json index 33b7c927520e9a..67e89e817f0213 100644 --- a/homeassistant/components/hyperion/translations/pl.json +++ b/homeassistant/components/hyperion/translations/pl.json @@ -45,6 +45,7 @@ "step": { "init": { "data": { + "effect_show_list": "Efekty Hyperiona do pokazania", "priority": "Hyperion ma pierwsze\u0144stwo w u\u017cyciu dla kolor\u00f3w i efekt\u00f3w" } } diff --git a/homeassistant/components/opentherm_gw/translations/pl.json b/homeassistant/components/opentherm_gw/translations/pl.json index 3fe12393a149b3..dc06752e404282 100644 --- a/homeassistant/components/opentherm_gw/translations/pl.json +++ b/homeassistant/components/opentherm_gw/translations/pl.json @@ -21,7 +21,9 @@ "init": { "data": { "floor_temperature": "Zaokr\u0105glanie warto\u015bci w d\u00f3\u0142", - "precision": "Precyzja" + "precision": "Precyzja", + "read_precision": "Odczytaj precyzj\u0119", + "set_precision": "Ustaw precyzj\u0119" }, "description": "Opcje dla bramki OpenTherm" } diff --git a/homeassistant/components/philips_js/translations/fr.json b/homeassistant/components/philips_js/translations/fr.json index 326b81dc6a346a..86d2b2dfba3e25 100644 --- a/homeassistant/components/philips_js/translations/fr.json +++ b/homeassistant/components/philips_js/translations/fr.json @@ -11,6 +11,9 @@ }, "step": { "pair": { + "data": { + "pin": "Code PIN" + }, "description": "Entrez le code PIN affich\u00e9 sur votre t\u00e9l\u00e9viseur", "title": "Paire" }, diff --git a/homeassistant/components/philips_js/translations/pl.json b/homeassistant/components/philips_js/translations/pl.json index 4fc1fbc5269bf0..a89b6136ff8299 100644 --- a/homeassistant/components/philips_js/translations/pl.json +++ b/homeassistant/components/philips_js/translations/pl.json @@ -10,6 +10,13 @@ "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "pair": { + "data": { + "pin": "Kod PIN" + }, + "description": "Wprowad\u017a kod PIN wy\u015bwietlony na Twoim telewizorze", + "title": "Paruj" + }, "user": { "data": { "api_version": "Wersja API", diff --git a/homeassistant/components/screenlogic/translations/fr.json b/homeassistant/components/screenlogic/translations/fr.json index d651f4f1c98e1a..968045e0597978 100644 --- a/homeassistant/components/screenlogic/translations/fr.json +++ b/homeassistant/components/screenlogic/translations/fr.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion" + }, "flow_title": "ScreenLogic {nom}", "step": { "gateway_entry": { diff --git a/homeassistant/components/screenlogic/translations/pl.json b/homeassistant/components/screenlogic/translations/pl.json new file mode 100644 index 00000000000000..64e2573ddb0a94 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/pl.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "Adres IP", + "port": "Port" + }, + "description": "Wprowad\u017a informacje o bramce ScreenLogic.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Bramka" + }, + "description": "Wykryto nast\u0119puj\u0105ce bramki ScreenLogic. Wybierz jedn\u0105 do skonfigurowania lub wybierz opcj\u0119 r\u0119cznej konfiguracji bramki ScreenLogic.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji" + }, + "description": "Okre\u015bl ustawienia dla {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/verisure/translations/fr.json b/homeassistant/components/verisure/translations/fr.json index 1991120ab8fb53..47114049dfbf26 100644 --- a/homeassistant/components/verisure/translations/fr.json +++ b/homeassistant/components/verisure/translations/fr.json @@ -1,7 +1,8 @@ { "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" }, "error": { "invalid_auth": "Authentification invalide", @@ -16,7 +17,9 @@ }, "reauth_confirm": { "data": { - "description": "R\u00e9-authentifiez-vous avec votre compte Verisure My Pages." + "description": "R\u00e9-authentifiez-vous avec votre compte Verisure My Pages.", + "email": "Email", + "password": "Mot de passe" } }, "user": { From 058d232c57856c9baae402c1fb6d8b79bf710eed Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Mar 2021 20:08:16 -0400 Subject: [PATCH 1490/1818] Determine zwave_js sensor device class during initialization (#48304) --- homeassistant/components/zwave_js/sensor.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 574e1af658c4f8..52a81a26eb9925 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -80,10 +80,15 @@ def __init__( """Initialize a ZWaveSensorBase entity.""" super().__init__(config_entry, client, info) self._name = self.generate_name(include_value_name=True) + self._device_class = self._get_device_class() - @property - def device_class(self) -> str | None: - """Return the device class of the sensor.""" + def _get_device_class(self) -> str | None: + """ + Get the device class of the sensor. + + This should be run once during initialization so we don't have to calculate + this value on every state update. + """ if self.info.primary_value.command_class == CommandClass.BATTERY: return DEVICE_CLASS_BATTERY if self.info.primary_value.command_class == CommandClass.METER: @@ -102,6 +107,11 @@ def device_class(self) -> str | None: return DEVICE_CLASS_ILLUMINANCE return None + @property + def device_class(self) -> str | None: + """Return the device class of the sensor.""" + return self._device_class + @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" From da7fd8a2946956bdfddee5d054dc5758a803164f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Mar 2021 15:04:55 -1000 Subject: [PATCH 1491/1818] Listen on the default interface by default for zeroconf (#48302) --- homeassistant/components/zeroconf/__init__.py | 2 +- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 6adbe07d8672c4..58dbe2125c1bc2 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -56,7 +56,7 @@ CONF_DEFAULT_INTERFACE = "default_interface" CONF_IPV6 = "ipv6" -DEFAULT_DEFAULT_INTERFACE = False +DEFAULT_DEFAULT_INTERFACE = True DEFAULT_IPV6 = True HOMEKIT_PROPERTIES = "properties" diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 654eec820c3e66..d407acece57464 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.28.8"], + "requirements": ["zeroconf==0.29.0"], "dependencies": ["api"], "codeowners": ["@bdraco"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8502fa2c22d550..86269b9da06b7c 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ sqlalchemy==1.3.23 voluptuous-serialize==2.4.0 voluptuous==0.12.1 yarl==1.6.3 -zeroconf==0.28.8 +zeroconf==0.29.0 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 531c91bb0c89a8..d8c80328947fd0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2363,7 +2363,7 @@ zeep[async]==4.0.0 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.28.8 +zeroconf==0.29.0 # homeassistant.components.zha zha-quirks==0.0.54 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 89c19c1bdf55c8..9023a3278f35f0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1215,7 +1215,7 @@ yeelight==0.5.4 zeep[async]==4.0.0 # homeassistant.components.zeroconf -zeroconf==0.28.8 +zeroconf==0.29.0 # homeassistant.components.zha zha-quirks==0.0.54 From 20485eb13245837fd5497b98ccd89e18b25491cc Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 25 Mar 2021 02:41:21 -0500 Subject: [PATCH 1492/1818] Bump plexapi to 4.5.1 (#48307) --- 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 647590f7cf2f59..9410fba1258d9b 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.5.0", + "plexapi==4.5.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/requirements_all.txt b/requirements_all.txt index d8c80328947fd0..cf5073bdfba822 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1131,7 +1131,7 @@ pillow==8.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.5.0 +plexapi==4.5.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9023a3278f35f0..dfe1e812cb53c6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -577,7 +577,7 @@ pilight==0.1.1 pillow==8.1.2 # homeassistant.components.plex -plexapi==4.5.0 +plexapi==4.5.1 # homeassistant.components.plex plexauth==0.0.6 From 642bb91a9a905f1b8b19ddce911831ac9277f6c4 Mon Sep 17 00:00:00 2001 From: Boris Gulay Date: Thu, 25 Mar 2021 11:18:10 +0300 Subject: [PATCH 1493/1818] Add metrics upload by UDP to graphite (#43751) --- homeassistant/components/graphite/__init__.py | 48 ++++++++++++------- tests/components/graphite/test_init.py | 19 ++++++-- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index ccb7044ad73b51..9405b576b4d6a5 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -12,6 +12,7 @@ CONF_HOST, CONF_PORT, CONF_PREFIX, + CONF_PROTOCOL, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, @@ -21,8 +22,11 @@ _LOGGER = logging.getLogger(__name__) +PROTOCOL_TCP = "tcp" +PROTOCOL_UDP = "udp" DEFAULT_HOST = "localhost" DEFAULT_PORT = 2003 +DEFAULT_PROTOCOL = PROTOCOL_TCP DEFAULT_PREFIX = "ha" DOMAIN = "graphite" @@ -32,6 +36,9 @@ { vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.Any( + PROTOCOL_TCP, PROTOCOL_UDP + ), vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, } ) @@ -46,29 +53,34 @@ def setup(hass, config): host = conf.get(CONF_HOST) prefix = conf.get(CONF_PREFIX) port = conf.get(CONF_PORT) + protocol = conf.get(CONF_PROTOCOL) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((host, port)) - sock.shutdown(2) - _LOGGER.debug("Connection to Graphite possible") - except OSError: - _LOGGER.error("Not able to connect to Graphite") - return False + if protocol == PROTOCOL_TCP: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((host, port)) + sock.shutdown(2) + _LOGGER.debug("Connection to Graphite possible") + except OSError: + _LOGGER.error("Not able to connect to Graphite") + return False + else: + _LOGGER.debug("No connection check for UDP possible") - GraphiteFeeder(hass, host, port, prefix) + GraphiteFeeder(hass, host, port, protocol, prefix) return True class GraphiteFeeder(threading.Thread): """Feed data to Graphite.""" - def __init__(self, hass, host, port, prefix): + def __init__(self, hass, host, port, protocol, prefix): """Initialize the feeder.""" super().__init__(daemon=True) self._hass = hass self._host = host self._port = port + self._protocol = protocol # rstrip any trailing dots in case they think they need it self._prefix = prefix.rstrip(".") self._queue = queue.Queue() @@ -101,12 +113,16 @@ def event_listener(self, event): def _send_to_graphite(self, data): """Send data to Graphite.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(10) - sock.connect((self._host, self._port)) - sock.sendall(data.encode("ascii")) - sock.send(b"\n") - sock.close() + if self._protocol == PROTOCOL_TCP: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(10) + sock.connect((self._host, self._port)) + sock.sendall(data.encode("ascii")) + sock.send(b"\n") + sock.close() + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(data.encode("ascii") + b"\n", (self._host, self._port)) def _report_attributes(self, entity_id, new_state): """Report the attributes.""" diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index 88be3723936fe9..b7f5071813ebb4 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -24,7 +24,7 @@ class TestGraphite(unittest.TestCase): def setup_method(self, method): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.gf = graphite.GraphiteFeeder(self.hass, "foo", 123, "ha") + self.gf = graphite.GraphiteFeeder(self.hass, "foo", 123, "tcp", "ha") def teardown_method(self, method): """Stop everything that was started.""" @@ -45,10 +45,23 @@ def test_full_config(self, mock_gf, mock_socket): assert setup_component(self.hass, graphite.DOMAIN, config) assert mock_gf.call_count == 1 - assert mock_gf.call_args == mock.call(self.hass, "foo", 123, "me") + assert mock_gf.call_args == mock.call(self.hass, "foo", 123, "tcp", "me") assert mock_socket.call_count == 1 assert mock_socket.call_args == mock.call(socket.AF_INET, socket.SOCK_STREAM) + @patch("socket.socket") + @patch("homeassistant.components.graphite.GraphiteFeeder") + def test_full_udp_config(self, mock_gf, mock_socket): + """Test setup with full configuration and UDP protocol.""" + config = { + "graphite": {"host": "foo", "port": 123, "protocol": "udp", "prefix": "me"} + } + + assert setup_component(self.hass, graphite.DOMAIN, config) + assert mock_gf.call_count == 1 + assert mock_gf.call_args == mock.call(self.hass, "foo", 123, "udp", "me") + assert mock_socket.call_count == 0 + @patch("socket.socket") @patch("homeassistant.components.graphite.GraphiteFeeder") def test_config_port(self, mock_gf, mock_socket): @@ -63,7 +76,7 @@ def test_config_port(self, mock_gf, mock_socket): def test_subscribe(self): """Test the subscription.""" fake_hass = mock.MagicMock() - gf = graphite.GraphiteFeeder(fake_hass, "foo", 123, "ha") + gf = graphite.GraphiteFeeder(fake_hass, "foo", 123, "tcp", "ha") fake_hass.bus.listen_once.has_calls( [ mock.call(EVENT_HOMEASSISTANT_START, gf.start_listen), From 21c72fa55935e6971c032ec15b712a1fd10c4cd6 Mon Sep 17 00:00:00 2001 From: Zixuan Wang Date: Thu, 25 Mar 2021 01:19:11 -0700 Subject: [PATCH 1494/1818] Fix missing glances temperature sensors (#46086) * Fix missing glances temperature sensors (#44899) * Revert matching rules for Glances * Shorter if statement Co-authored-by: J. Nick Koston * Revert long-line if statement * Update if statement Co-authored-by: J. Nick Koston --- homeassistant/components/glances/const.py | 7 ++++--- homeassistant/components/glances/sensor.py | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/glances/const.py b/homeassistant/components/glances/const.py index 69e4ce0c016e7c..18865a232d7787 100644 --- a/homeassistant/components/glances/const.py +++ b/homeassistant/components/glances/const.py @@ -36,9 +36,10 @@ "process_thread": ["processcount", "Thread", "Count", CPU_ICON], "process_sleeping": ["processcount", "Sleeping", "Count", CPU_ICON], "cpu_use_percent": ["cpu", "CPU used", PERCENTAGE, CPU_ICON], - "temperature_core": ["sensors", "temperature", TEMP_CELSIUS, "mdi:thermometer"], - "fan_speed": ["sensors", "fan speed", "RPM", "mdi:fan"], - "battery": ["sensors", "charge", PERCENTAGE, "mdi:battery"], + "temperature_core": ["sensors", "Temperature", TEMP_CELSIUS, "mdi:thermometer"], + "temperature_hdd": ["sensors", "Temperature", TEMP_CELSIUS, "mdi:thermometer"], + "fan_speed": ["sensors", "Fan speed", "RPM", "mdi:fan"], + "battery": ["sensors", "Charge", PERCENTAGE, "mdi:battery"], "docker_active": ["docker", "Containers active", "", "mdi:docker"], "docker_cpu_use": ["docker", "Containers CPU used", PERCENTAGE, "mdi:docker"], "docker_memory_use": [ diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 3e663621625549..52649518b68a8e 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -172,6 +172,13 @@ async def async_update(self): if sensor["type"] == "temperature_core": if sensor["label"] == self._sensor_name_prefix: self._state = sensor["value"] + elif self.type == "temperature_hdd": + for sensor in value["sensors"]: + if ( + sensor["type"] == "temperature_hdd" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] elif self.type == "memory_use_percent": self._state = value["mem"]["percent"] elif self.type == "memory_use": From 6b2a2740f173462173f6e711120a56838c1cf6c9 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Thu, 25 Mar 2021 09:47:49 +0100 Subject: [PATCH 1495/1818] Type check KNX integration climate (#48054) --- homeassistant/components/knx/climate.py | 90 ++++++++++++++++--------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index e90371e3282a49..1b9fea575415c9 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,6 +1,8 @@ """Support for KNX/IP climate devices.""" from __future__ import annotations +from typing import Any, Callable, Iterable + from xknx.devices import Climate as XknxClimate from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode @@ -13,6 +15,12 @@ SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) from .const import CONTROLLER_MODES, DOMAIN, PRESET_MODES from .knx_entity import KnxEntity @@ -21,7 +29,12 @@ PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()} -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[Iterable[Entity]], None], + discovery_info: DiscoveryInfoType | None = None, +) -> None: """Set up climate(s) for KNX platform.""" entities = [] for device in hass.data[DOMAIN].xknx.devices: @@ -33,8 +46,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class KNXClimate(KnxEntity, ClimateEntity): """Representation of a KNX climate device.""" - def __init__(self, device: XknxClimate): + def __init__(self, device: XknxClimate) -> None: """Initialize of a KNX climate device.""" + self._device: XknxClimate super().__init__(device) self._unit_of_measurement = TEMP_CELSIUS @@ -44,42 +58,45 @@ def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - async def async_update(self): + async def async_update(self) -> None: """Request a state update from KNX bus.""" await self._device.sync() - await self._device.mode.sync() + if self._device.mode is not None: + await self._device.mode.sync() @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return self._unit_of_measurement @property - def current_temperature(self): + def current_temperature(self) -> float | None: """Return the current temperature.""" - return self._device.temperature.value + return self._device.temperature.value # type: ignore[no-any-return] @property - def target_temperature_step(self): + def target_temperature_step(self) -> float: """Return the supported step of target temperature.""" return self._device.temperature_step @property - def target_temperature(self): + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - return self._device.target_temperature.value + return self._device.target_temperature.value # type: ignore[no-any-return] @property - def min_temp(self): + def min_temp(self) -> float: """Return the minimum temperature.""" - return self._device.target_temperature_min + temp = self._device.target_temperature_min + return temp if temp is not None else super().min_temp @property - def max_temp(self): + def max_temp(self) -> float: """Return the maximum temperature.""" - return self._device.target_temperature_max + temp = self._device.target_temperature_max + return temp if temp is not None else super().max_temp - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: @@ -88,11 +105,11 @@ async def async_set_temperature(self, **kwargs) -> None: self.async_write_ha_state() @property - def hvac_mode(self) -> str | None: + def hvac_mode(self) -> str: """Return current operation ie. heat, cool, idle.""" if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF - if self._device.mode.supports_controller_mode: + if self._device.mode is not None and self._device.mode.supports_controller_mode: return CONTROLLER_MODES.get( self._device.mode.controller_mode.value, HVAC_MODE_HEAT ) @@ -100,21 +117,23 @@ def hvac_mode(self) -> str | None: return HVAC_MODE_HEAT @property - def hvac_modes(self) -> list[str] | None: + def hvac_modes(self) -> list[str]: """Return the list of available operation/controller modes.""" - _controller_modes = [ - CONTROLLER_MODES.get(controller_mode.value) - for controller_mode in self._device.mode.controller_modes - ] + ha_controller_modes: list[str | None] = [] + if self._device.mode is not None: + for knx_controller_mode in self._device.mode.controller_modes: + ha_controller_modes.append( + CONTROLLER_MODES.get(knx_controller_mode.value) + ) if self._device.supports_on_off: - if not _controller_modes: - _controller_modes.append(HVAC_MODE_HEAT) - _controller_modes.append(HVAC_MODE_OFF) + if not ha_controller_modes: + ha_controller_modes.append(HVAC_MODE_HEAT) + ha_controller_modes.append(HVAC_MODE_OFF) - _modes = list(set(filter(None, _controller_modes))) + hvac_modes = list(set(filter(None, ha_controller_modes))) # default to ["heat"] - return _modes if _modes else [HVAC_MODE_HEAT] + return hvac_modes if hvac_modes else [HVAC_MODE_HEAT] async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set operation mode.""" @@ -123,7 +142,10 @@ async def async_set_hvac_mode(self, hvac_mode: str) -> None: else: if self._device.supports_on_off and not self._device.is_on: await self._device.turn_on() - if self._device.mode.supports_controller_mode: + if ( + self._device.mode is not None + and self._device.mode.supports_controller_mode + ): knx_controller_mode = HVACControllerMode( CONTROLLER_MODES_INV.get(hvac_mode) ) @@ -136,7 +158,7 @@ def preset_mode(self) -> str | None: Requires SUPPORT_PRESET_MODE. """ - if self._device.mode.supports_operation_mode: + if self._device.mode is not None and self._device.mode.supports_operation_mode: return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY) return None @@ -146,16 +168,18 @@ def preset_modes(self) -> list[str] | None: Requires SUPPORT_PRESET_MODE. """ - _presets = [ + if self._device.mode is None: + return None + + presets = [ PRESET_MODES.get(operation_mode.value) for operation_mode in self._device.mode.operation_modes ] - - return list(filter(None, _presets)) + return list(filter(None, presets)) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if self._device.mode.supports_operation_mode: + if self._device.mode is not None and self._device.mode.supports_operation_mode: knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode)) await self._device.mode.set_operation_mode(knx_operation_mode) self.async_write_ha_state() From 3188f796f9deed2310ba372b2e8d0ad35dc1e3ef Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 25 Mar 2021 14:06:01 +0100 Subject: [PATCH 1496/1818] Add allowed UUIDs and ignore CEC to Google Cast options flow (#47269) --- homeassistant/components/cast/__init__.py | 28 +++- homeassistant/components/cast/config_flow.py | 88 +++++++++---- homeassistant/components/cast/const.py | 5 +- homeassistant/components/cast/discovery.py | 7 +- homeassistant/components/cast/media_player.py | 55 ++------ homeassistant/components/cast/strings.json | 4 +- .../components/cast/translations/en.json | 4 +- tests/components/cast/conftest.py | 1 + .../cast/test_home_assistant_cast.py | 6 +- tests/components/cast/test_init.py | 111 ++++++++++++++-- tests/components/cast/test_media_player.py | 122 ++++++------------ 11 files changed, 249 insertions(+), 182 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 49cec207764477..43b6b77ebd298f 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,20 +1,42 @@ """Component to embed Google Cast.""" +import logging + +import voluptuous as vol + from homeassistant import config_entries +from homeassistant.helpers import config_validation as cv from . import home_assistant_cast from .const import DOMAIN +from .media_player import ENTITY_SCHEMA + +# Deprecated from 2021.4, remove in 2021.6 +CONFIG_SCHEMA = cv.deprecated(DOMAIN) + +_LOGGER = logging.getLogger(__name__) async def async_setup(hass, config): """Set up the Cast component.""" conf = config.get(DOMAIN) - hass.data[DOMAIN] = conf or {} - if conf is not None: + media_player_config_validated = [] + media_player_config = conf.get("media_player", {}) + if not isinstance(media_player_config, list): + media_player_config = [media_player_config] + for cfg in media_player_config: + try: + cfg = ENTITY_SCHEMA(cfg) + media_player_config_validated.append(cfg) + except vol.Error as ex: + _LOGGER.warning("Invalid config '%s': %s", cfg, ex) + hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=media_player_config_validated, ) ) diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index 4a4426a5db1ed5..464283e07f30cc 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -4,9 +4,11 @@ from homeassistant import config_entries from homeassistant.helpers import config_validation as cv -from .const import CONF_KNOWN_HOSTS, DOMAIN +from .const import CONF_IGNORE_CEC, CONF_KNOWN_HOSTS, CONF_UUID, DOMAIN +IGNORE_CEC_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) KNOWN_HOSTS_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) +WANTED_UUID_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -17,7 +19,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" - self._known_hosts = None + self._ignore_cec = set() + self._known_hosts = set() + self._wanted_uuid = set() @staticmethod def async_get_options_flow(config_entry): @@ -28,7 +32,15 @@ async def async_step_import(self, import_data=None): """Import data.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") - data = {CONF_KNOWN_HOSTS: self._known_hosts} + + media_player_config = import_data or [] + for cfg in media_player_config: + if CONF_IGNORE_CEC in cfg: + self._ignore_cec.update(set(cfg[CONF_IGNORE_CEC])) + if CONF_UUID in cfg: + self._wanted_uuid.add(cfg[CONF_UUID]) + + data = self._get_data() return self.async_create_entry(title="Google Cast", data=data) async def async_step_user(self, user_input=None): @@ -62,7 +74,8 @@ async def async_step_config(self, user_input=None): errors["base"] = "invalid_known_hosts" bad_hosts = True else: - data[CONF_KNOWN_HOSTS] = known_hosts + self._known_hosts = known_hosts + data = self._get_data() if not bad_hosts: return self.async_create_entry(title="Google Cast", data=data) @@ -76,13 +89,20 @@ async def async_step_config(self, user_input=None): async def async_step_confirm(self, user_input=None): """Confirm the setup.""" - data = {CONF_KNOWN_HOSTS: self._known_hosts} + data = self._get_data() if user_input is not None: return self.async_create_entry(title="Google Cast", data=data) return self.async_show_form(step_id="confirm") + def _get_data(self): + return { + CONF_IGNORE_CEC: list(self._ignore_cec), + CONF_KNOWN_HOSTS: list(self._known_hosts), + CONF_UUID: list(self._wanted_uuid), + } + class CastOptionsFlowHandler(config_entries.OptionsFlow): """Handle Google Cast options.""" @@ -102,35 +122,59 @@ async def async_step_options(self, user_input=None): errors = {} current_config = self.config_entry.data if user_input is not None: - bad_hosts = False + bad_cec, ignore_cec = _string_to_list( + user_input.get(CONF_IGNORE_CEC, ""), IGNORE_CEC_SCHEMA + ) + bad_hosts, known_hosts = _string_to_list( + user_input.get(CONF_KNOWN_HOSTS, ""), KNOWN_HOSTS_SCHEMA + ) + bad_uuid, wanted_uuid = _string_to_list( + user_input.get(CONF_UUID, ""), WANTED_UUID_SCHEMA + ) - known_hosts = user_input.get(CONF_KNOWN_HOSTS, "") - known_hosts = [x.strip() for x in known_hosts.split(",") if x.strip()] - try: - known_hosts = KNOWN_HOSTS_SCHEMA(known_hosts) - except vol.Invalid: - errors["base"] = "invalid_known_hosts" - bad_hosts = True - if not bad_hosts: + if not bad_cec and not bad_hosts and not bad_uuid: updated_config = {} + updated_config[CONF_IGNORE_CEC] = ignore_cec updated_config[CONF_KNOWN_HOSTS] = known_hosts + updated_config[CONF_UUID] = wanted_uuid self.hass.config_entries.async_update_entry( self.config_entry, data=updated_config ) return self.async_create_entry(title="", data=None) fields = {} - known_hosts_string = "" - if current_config.get(CONF_KNOWN_HOSTS): - known_hosts_string = ",".join(current_config.get(CONF_KNOWN_HOSTS)) - fields[ - vol.Optional( - "known_hosts", description={"suggested_value": known_hosts_string} - ) - ] = str + suggested_value = _list_to_string(current_config.get(CONF_KNOWN_HOSTS)) + _add_with_suggestion(fields, CONF_KNOWN_HOSTS, suggested_value) + if self.show_advanced_options: + suggested_value = _list_to_string(current_config.get(CONF_UUID)) + _add_with_suggestion(fields, CONF_UUID, suggested_value) + suggested_value = _list_to_string(current_config.get(CONF_IGNORE_CEC)) + _add_with_suggestion(fields, CONF_IGNORE_CEC, suggested_value) return self.async_show_form( step_id="options", data_schema=vol.Schema(fields), errors=errors, ) + + +def _list_to_string(items): + comma_separated_string = "" + if items: + comma_separated_string = ",".join(items) + return comma_separated_string + + +def _string_to_list(string, schema): + invalid = False + items = [x.strip() for x in string.split(",") if x.strip()] + try: + items = schema(items) + except vol.Invalid: + invalid = True + + return invalid, items + + +def _add_with_suggestion(fields, key, suggested_value): + fields[vol.Optional(key, description={"suggested_value": suggested_value})] = str diff --git a/homeassistant/components/cast/const.py b/homeassistant/components/cast/const.py index 993315b551850f..03ffdfbd15c99c 100644 --- a/homeassistant/components/cast/const.py +++ b/homeassistant/components/cast/const.py @@ -5,9 +5,6 @@ # Stores a threading.Lock that is held by the internal pychromecast discovery. INTERNAL_DISCOVERY_RUNNING_KEY = "cast_discovery_running" -# Stores all ChromecastInfo we encountered through discovery or config as a set -# If we find a chromecast with a new host, the old one will be removed again. -KNOWN_CHROMECAST_INFO_KEY = "cast_known_chromecasts" # Stores UUIDs of cast devices that were added as entities. Doesn't store # None UUIDs. ADDED_CAST_DEVICES_KEY = "cast_added_cast_devices" @@ -27,4 +24,6 @@ # Dispatcher signal fired when a Chromecast should show a Home Assistant Cast view. SIGNAL_HASS_CAST_SHOW_VIEW = "cast_show_view" +CONF_IGNORE_CEC = "ignore_cec" CONF_KNOWN_HOSTS = "known_hosts" +CONF_UUID = "uuid" diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index fcae28b5bfe124..a5ac4c02047850 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -13,7 +13,6 @@ CONF_KNOWN_HOSTS, DEFAULT_PORT, INTERNAL_DISCOVERY_RUNNING_KEY, - KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, SIGNAL_CAST_REMOVED, ) @@ -38,12 +37,8 @@ def discover_chromecast(hass: HomeAssistant, device_info): return info = info.fill_out_missing_chromecast_info() - if info.uuid in hass.data[KNOWN_CHROMECAST_INFO_KEY]: - _LOGGER.debug("Discovered update for known chromecast %s", info) - else: - _LOGGER.debug("Discovered chromecast %s", info) + _LOGGER.debug("Discovered new or updated chromecast %s", info) - hass.data[KNOWN_CHROMECAST_INFO_KEY][info.uuid] = info dispatcher_send(hass, SIGNAL_CAST_DISCOVERED, info) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 9532c25d81a18b..540f3263e1e7fd 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -1,7 +1,6 @@ """Provide functionality to interact with Cast devices on the network.""" from __future__ import annotations -import asyncio from contextlib import suppress from datetime import timedelta import functools as ft @@ -52,19 +51,19 @@ STATE_PLAYING, ) from homeassistant.core import callback -from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.network import NoURLAvailableError, get_url -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.dt as dt_util from homeassistant.util.logging import async_create_catching_coro from .const import ( ADDED_CAST_DEVICES_KEY, CAST_MULTIZONE_MANAGER_KEY, + CONF_IGNORE_CEC, + CONF_UUID, DOMAIN as CAST_DOMAIN, - KNOWN_CHROMECAST_INFO_KEY, SIGNAL_CAST_DISCOVERED, SIGNAL_CAST_REMOVED, SIGNAL_HASS_CAST_SHOW_VIEW, @@ -74,8 +73,6 @@ _LOGGER = logging.getLogger(__name__) -CONF_IGNORE_CEC = "ignore_cec" -CONF_UUID = "uuid" CAST_SPLASH = "https://www.home-assistant.io/images/cast/splash.png" SUPPORT_CAST = ( @@ -129,45 +126,20 @@ def _async_create_cast_device(hass: HomeAssistantType, info: ChromecastInfo): async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Cast from a config entry.""" - config = hass.data[CAST_DOMAIN].get("media_player") or {} - if not isinstance(config, list): - config = [config] - - # no pending task - done, _ = await asyncio.wait( - [ - _async_setup_platform( - hass, ENTITY_SCHEMA(cfg), async_add_entities, config_entry - ) - for cfg in config - ] - ) - if any(task.exception() for task in done): - exceptions = [task.exception() for task in done] - for exception in exceptions: - _LOGGER.debug("Failed to setup chromecast", exc_info=exception) - raise PlatformNotReady - - -async def _async_setup_platform( - hass: HomeAssistantType, config: ConfigType, async_add_entities, config_entry -): - """Set up the cast platform.""" - # Import CEC IGNORE attributes - pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, []) hass.data.setdefault(ADDED_CAST_DEVICES_KEY, set()) - hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, {}) - wanted_uuid = None - if CONF_UUID in config: - wanted_uuid = config[CONF_UUID] + # Import CEC IGNORE attributes + pychromecast.IGNORE_CEC += config_entry.data.get(CONF_IGNORE_CEC) or [] + + wanted_uuids = config_entry.data.get(CONF_UUID) or None @callback def async_cast_discovered(discover: ChromecastInfo) -> None: """Handle discovery of a new chromecast.""" - # If wanted_uuid is set, we're handling a specific cast device identified by UUID - if wanted_uuid is not None and wanted_uuid != discover.uuid: - # UUID not matching, this is not it. + # If wanted_uuids is set, we're only accepting specific cast devices identified + # by UUID + if wanted_uuids is not None and discover.uuid not in wanted_uuids: + # UUID not matching, ignore. return cast_device = _async_create_cast_device(hass, discover) @@ -175,11 +147,6 @@ def async_cast_discovered(discover: ChromecastInfo) -> None: async_add_entities([cast_device]) async_dispatcher_connect(hass, SIGNAL_CAST_DISCOVERED, async_cast_discovered) - # Re-play the callback for all past chromecasts, store the objects in - # a list to avoid concurrent modification resulting in exception. - for chromecast in hass.data[KNOWN_CHROMECAST_INFO_KEY].values(): - async_cast_discovered(chromecast) - ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) hass.async_add_executor_job(setup_internal_discovery, hass, config_entry) diff --git a/homeassistant/components/cast/strings.json b/homeassistant/components/cast/strings.json index 7cd07518db8871..33ce4b6941e450 100644 --- a/homeassistant/components/cast/strings.json +++ b/homeassistant/components/cast/strings.json @@ -24,7 +24,9 @@ "options": { "description": "Please enter the Google Cast configuration.", "data": { - "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + "ignore_cec": "Optional list which will be passed to pychromecast.IGNORE_CEC.", + "known_hosts": "Optional list of known hosts if mDNS discovery is not working.", + "uuid": "Optional list of UUIDs. Casts not listed will not be added." } } }, diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index 1bfdeb4df8d109..c2c2460cc9c7f8 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Optional list of known hosts if mDNS discovery is not working." + "ignore_cec": "Optional list which will be passed to pychromecast.IGNORE_CEC.", + "known_hosts": "Optional list of known hosts if mDNS discovery is not working.", + "uuid": "Optional list of UUIDs. Casts not listed will not be added." }, "description": "Please enter the Google Cast configuration." } diff --git a/tests/components/cast/conftest.py b/tests/components/cast/conftest.py index 875d831afa9f18..a8118a94967407 100644 --- a/tests/components/cast/conftest.py +++ b/tests/components/cast/conftest.py @@ -40,6 +40,7 @@ def mz_mock(): def pycast_mock(castbrowser_mock, castbrowser_constructor_mock): """Mock pychromecast.""" pycast_mock = MagicMock() + pycast_mock.IGNORE_CEC = [] pycast_mock.discovery.CastBrowser = castbrowser_constructor_mock pycast_mock.discovery.CastBrowser.return_value = castbrowser_mock pycast_mock.discovery.AbstractCastListener = ( diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index 3fd0e921ca6b81..6ac4f9c9d0c197 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -102,12 +102,8 @@ async def test_remove_entry(hass, mock_zeroconf): entry.add_to_hass(hass) with patch( - "homeassistant.components.cast.media_player._async_setup_platform" - ), patch( "pychromecast.discovery.discover_chromecasts", return_value=(True, None) - ), patch( - "pychromecast.discovery.stop_discovery" - ): + ), patch("pychromecast.discovery.stop_discovery"): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert "cast" in hass.config.components diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 77268e7de978aa..888ef2ebcd797c 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -35,18 +35,36 @@ async def test_creating_entry_sets_up_media_player(hass): assert len(mock_setup.mock_calls) == 1 -async def test_configuring_cast_creates_entry(hass): +async def test_import(hass, caplog): """Test that specifying config will create an entry.""" with patch( "homeassistant.components.cast.async_setup_entry", return_value=True ) as mock_setup: await async_setup_component( - hass, cast.DOMAIN, {"cast": {"some_config": "to_trigger_import"}} + hass, + cast.DOMAIN, + { + "cast": { + "media_player": [ + {"uuid": "abcd"}, + {"uuid": "abcd", "ignore_cec": "milk"}, + {"uuid": "efgh", "ignore_cec": "beer"}, + {"incorrect": "config"}, + ] + } + }, ) await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 + assert len(hass.config_entries.async_entries("cast")) == 1 + entry = hass.config_entries.async_entries("cast")[0] + assert set(entry.data["ignore_cec"]) == {"milk", "beer"} + assert set(entry.data["uuid"]) == {"abcd", "efgh"} + + assert "Invalid config '{'incorrect': 'config'}'" in caplog.text + async def test_not_configuring_cast_not_creates_entry(hass): """Test that no config will not create an entry.""" @@ -72,7 +90,7 @@ async def test_single_instance(hass, source): assert result["reason"] == "single_instance_allowed" -async def test_user_setup(hass, mqtt_mock): +async def test_user_setup(hass): """Test we can finish a config flow.""" result = await hass.config_entries.flow.async_init( "cast", context={"source": "user"} @@ -85,12 +103,14 @@ async def test_user_setup(hass, mqtt_mock): assert len(users) == 1 assert result["type"] == "create_entry" assert result["result"].data == { + "ignore_cec": [], "known_hosts": [], + "uuid": [], "user_id": users[0].id, # Home Assistant cast user } -async def test_user_setup_options(hass, mqtt_mock): +async def test_user_setup_options(hass): """Test we can finish a config flow.""" result = await hass.config_entries.flow.async_init( "cast", context={"source": "user"} @@ -105,7 +125,9 @@ async def test_user_setup_options(hass, mqtt_mock): assert len(users) == 1 assert result["type"] == "create_entry" assert result["result"].data == { + "ignore_cec": [], "known_hosts": ["192.168.0.1", "192.168.0.2"], + "uuid": [], "user_id": users[0].id, # Home Assistant cast user } @@ -123,7 +145,9 @@ async def test_zeroconf_setup(hass): assert len(users) == 1 assert result["type"] == "create_entry" assert result["result"].data == { - "known_hosts": None, + "ignore_cec": [], + "known_hosts": [], + "uuid": [], "user_id": users[0].id, # Home Assistant cast user } @@ -137,27 +161,90 @@ def get_suggested(schema, key): return k.description["suggested_value"] -async def test_option_flow(hass): +@pytest.mark.parametrize( + "parameter_data", + [ + ( + "known_hosts", + ["192.168.0.10", "192.168.0.11"], + "192.168.0.10,192.168.0.11", + "192.168.0.1, , 192.168.0.2 ", + ["192.168.0.1", "192.168.0.2"], + ), + ( + "uuid", + ["bla", "blu"], + "bla,blu", + "foo, , bar ", + ["foo", "bar"], + ), + ( + "ignore_cec", + ["cast1", "cast2"], + "cast1,cast2", + "other_cast, , some_cast ", + ["other_cast", "some_cast"], + ), + ], +) +async def test_option_flow(hass, parameter_data): """Test config flow options.""" - config_entry = MockConfigEntry( - domain="cast", data={"known_hosts": ["192.168.0.10", "192.168.0.11"]} - ) + all_parameters = ["ignore_cec", "known_hosts", "uuid"] + parameter, initial, suggested, user_input, updated = parameter_data + + data = { + "ignore_cec": [], + "known_hosts": [], + "uuid": [], + } + data[parameter] = initial + config_entry = MockConfigEntry(domain="cast", data=data) config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() + # Test ignore_cec and uuid options are hidden if advanced options are disabled 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"] == "options" data_schema = result["data_schema"].schema - assert get_suggested(data_schema, "known_hosts") == "192.168.0.10,192.168.0.11" + assert set(data_schema) == {"known_hosts"} + # Reconfigure ignore_cec, known_hosts, uuid + context = {"source": "user", "show_advanced_options": True} + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context=context + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "options" + data_schema = result["data_schema"].schema + for other_param in all_parameters: + if other_param == parameter: + continue + assert get_suggested(data_schema, other_param) == "" + assert get_suggested(data_schema, parameter) == suggested + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={parameter: user_input}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] is None + for other_param in all_parameters: + if other_param == parameter: + continue + assert config_entry.data[other_param] == [] + assert config_entry.data[parameter] == updated + + # Clear known_hosts + result = await hass.config_entries.options.async_init(config_entry.entry_id) result = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={"known_hosts": "192.168.0.1, , 192.168.0.2 "}, + user_input={"known_hosts": ""}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] is None - assert config_entry.data == {"known_hosts": ["192.168.0.1", "192.168.0.2"]} + assert config_entry.data == {"ignore_cec": [], "known_hosts": [], "uuid": []} async def test_known_hosts(hass, castbrowser_mock, castbrowser_constructor_mock): diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 8a6f84580d2208..4b1978e8da5baa 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, Mock, patch +from unittest.mock import ANY, MagicMock, patch from uuid import UUID import attr @@ -28,7 +28,6 @@ ) from homeassistant.config import async_process_ha_core_config from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType @@ -100,11 +99,13 @@ async def async_setup_cast(hass, config=None): """Set up the cast platform.""" if config is None: config = {} + data = {**{"ignore_cec": [], "known_hosts": [], "uuid": []}, **config} with patch( "homeassistant.helpers.entity_platform.EntityPlatform._async_schedule_add_entities" ) as add_entities: - MockConfigEntry(domain="cast").add_to_hass(hass) - await async_setup_component(hass, "cast", {"cast": {"media_player": config}}) + entry = MockConfigEntry(data=data, domain="cast") + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() return add_entities @@ -388,44 +389,6 @@ async def test_create_cast_device_with_uuid(hass): assert cast_device is None -async def test_replay_past_chromecasts(hass): - """Test cast platform re-playing past chromecasts when adding new one.""" - cast_group1 = get_fake_chromecast_info(host="host1", port=8009, uuid=FakeUUID) - cast_group2 = get_fake_chromecast_info( - host="host2", port=8009, uuid=UUID("9462202c-e747-4af5-a66b-7dce0e1ebc09") - ) - zconf_1 = get_fake_zconf(host="host1", port=8009) - zconf_2 = get_fake_zconf(host="host2", port=8009) - - discover_cast, _, add_dev1 = await async_setup_cast_internal_discovery( - hass, config={"uuid": FakeUUID} - ) - - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_2, - ): - discover_cast("service2", cast_group2) - await hass.async_block_till_done() - await hass.async_block_till_done() # having tasks that add jobs - assert add_dev1.call_count == 0 - - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_1, - ): - discover_cast("service1", cast_group1) - await hass.async_block_till_done() - await hass.async_block_till_done() # having tasks that add jobs - assert add_dev1.call_count == 1 - - add_dev2 = Mock() - entry = hass.config_entries.async_entries("cast")[0] - await cast._async_setup_platform(hass, {"host": "host2"}, add_dev2, entry) - await hass.async_block_till_done() - assert add_dev2.call_count == 1 - - async def test_manual_cast_chromecasts_uuid(hass): """Test only wanted casts are added for manual configuration.""" cast_1 = get_fake_chromecast_info(host="host_1", uuid=FakeUUID) @@ -435,7 +398,7 @@ async def test_manual_cast_chromecasts_uuid(hass): # Manual configuration of media player with host "configured_host" discover_cast, _, add_dev1 = await async_setup_cast_internal_discovery( - hass, config={"uuid": FakeUUID} + hass, config={"uuid": str(FakeUUID)} ) with patch( "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", @@ -1291,65 +1254,54 @@ async def test_disconnect_on_stop(hass: HomeAssistantType): async def test_entry_setup_no_config(hass: HomeAssistantType): - """Test setting up entry with no config..""" + """Test deprecated empty yaml config..""" await async_setup_component(hass, "cast", {}) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - ) as mock_setup: - await cast.async_setup_entry(hass, MockConfigEntry(), None) - - assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == {} + assert not hass.config_entries.async_entries("cast") -async def test_entry_setup_single_config(hass: HomeAssistantType): - """Test setting up entry and having a single config option.""" - await async_setup_component( - hass, "cast", {"cast": {"media_player": {"uuid": "bla"}}} - ) +async def test_entry_setup_empty_config(hass: HomeAssistantType): + """Test deprecated empty yaml config..""" + await async_setup_component(hass, "cast", {"cast": {}}) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - ) as mock_setup: - await cast.async_setup_entry(hass, MockConfigEntry(), None) - - assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == {"uuid": "bla"} + config_entry = hass.config_entries.async_entries("cast")[0] + assert config_entry.data["uuid"] == [] + assert config_entry.data["ignore_cec"] == [] -async def test_entry_setup_list_config(hass: HomeAssistantType): - """Test setting up entry and having multiple config options.""" +async def test_entry_setup_single_config(hass: HomeAssistantType, pycast_mock): + """Test deprecated yaml config with a single config media_player.""" await async_setup_component( - hass, "cast", {"cast": {"media_player": [{"uuid": "bla"}, {"uuid": "blu"}]}} + hass, "cast", {"cast": {"media_player": {"uuid": "bla", "ignore_cec": "cast1"}}} ) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - ) as mock_setup: - await cast.async_setup_entry(hass, MockConfigEntry(), None) + config_entry = hass.config_entries.async_entries("cast")[0] + assert config_entry.data["uuid"] == ["bla"] + assert config_entry.data["ignore_cec"] == ["cast1"] - assert len(mock_setup.mock_calls) == 2 - assert mock_setup.mock_calls[0][1][1] == {"uuid": "bla"} - assert mock_setup.mock_calls[1][1][1] == {"uuid": "blu"} + assert pycast_mock.IGNORE_CEC == ["cast1"] -async def test_entry_setup_platform_not_ready(hass: HomeAssistantType): - """Test failed setting up entry will raise PlatformNotReady.""" +async def test_entry_setup_list_config(hass: HomeAssistantType, pycast_mock): + """Test deprecated yaml config with multiple media_players.""" await async_setup_component( - hass, "cast", {"cast": {"media_player": {"uuid": "bla"}}} + hass, + "cast", + { + "cast": { + "media_player": [ + {"uuid": "bla", "ignore_cec": "cast1"}, + {"uuid": "blu", "ignore_cec": ["cast2", "cast3"]}, + ] + } + }, ) await hass.async_block_till_done() - with patch( - "homeassistant.components.cast.media_player._async_setup_platform", - side_effect=Exception, - ) as mock_setup: - with pytest.raises(PlatformNotReady): - await cast.async_setup_entry(hass, MockConfigEntry(), None) - - assert len(mock_setup.mock_calls) == 1 - assert mock_setup.mock_calls[0][1][1] == {"uuid": "bla"} + config_entry = hass.config_entries.async_entries("cast")[0] + assert set(config_entry.data["uuid"]) == {"bla", "blu"} + assert set(config_entry.data["ignore_cec"]) == {"cast1", "cast2", "cast3"} + assert set(pycast_mock.IGNORE_CEC) == {"cast1", "cast2", "cast3"} From 1b60c8efb88e8bd6bc2b73e56ecda85d4be34172 Mon Sep 17 00:00:00 2001 From: chemaaa <187996+chemaaa@users.noreply.github.com> Date: Thu, 25 Mar 2021 14:12:31 +0100 Subject: [PATCH 1497/1818] Add Homepluscontrol integration (#46783) Co-authored-by: Martin Hjelmare --- .coveragerc | 3 + CODEOWNERS | 1 + .../components/home_plus_control/__init__.py | 179 +++++++ .../components/home_plus_control/api.py | 55 +++ .../home_plus_control/config_flow.py | 32 ++ .../components/home_plus_control/const.py | 45 ++ .../components/home_plus_control/helpers.py | 53 ++ .../home_plus_control/manifest.json | 15 + .../components/home_plus_control/strings.json | 21 + .../components/home_plus_control/switch.py | 129 +++++ .../home_plus_control/translations/en.json | 15 + homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + .../components/home_plus_control/__init__.py | 1 + .../components/home_plus_control/conftest.py | 106 ++++ .../home_plus_control/test_config_flow.py | 192 ++++++++ .../components/home_plus_control/test_init.py | 75 +++ .../home_plus_control/test_switch.py | 464 ++++++++++++++++++ 19 files changed, 1393 insertions(+) create mode 100644 homeassistant/components/home_plus_control/__init__.py create mode 100644 homeassistant/components/home_plus_control/api.py create mode 100644 homeassistant/components/home_plus_control/config_flow.py create mode 100644 homeassistant/components/home_plus_control/const.py create mode 100644 homeassistant/components/home_plus_control/helpers.py create mode 100644 homeassistant/components/home_plus_control/manifest.json create mode 100644 homeassistant/components/home_plus_control/strings.json create mode 100644 homeassistant/components/home_plus_control/switch.py create mode 100644 homeassistant/components/home_plus_control/translations/en.json create mode 100644 tests/components/home_plus_control/__init__.py create mode 100644 tests/components/home_plus_control/conftest.py create mode 100644 tests/components/home_plus_control/test_config_flow.py create mode 100644 tests/components/home_plus_control/test_init.py create mode 100644 tests/components/home_plus_control/test_switch.py diff --git a/.coveragerc b/.coveragerc index 10eac76421c5de..cec2649b13240d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -400,6 +400,9 @@ omit = homeassistant/components/homematic/climate.py homeassistant/components/homematic/cover.py homeassistant/components/homematic/notify.py + homeassistant/components/home_plus_control/api.py + homeassistant/components/home_plus_control/helpers.py + homeassistant/components/home_plus_control/switch.py homeassistant/components/homeworks/* homeassistant/components/honeywell/climate.py homeassistant/components/horizon/media_player.py diff --git a/CODEOWNERS b/CODEOWNERS index 31ce9706baa72f..fe28524def7de3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -196,6 +196,7 @@ homeassistant/components/history/* @home-assistant/core homeassistant/components/hive/* @Rendili @KJonline homeassistant/components/hlk_sw16/* @jameshilliard homeassistant/components/home_connect/* @DavidMStraub +homeassistant/components/home_plus_control/* @chemaaa homeassistant/components/homeassistant/* @home-assistant/core homeassistant/components/homekit/* @bdraco homeassistant/components/homekit_controller/* @Jc2k diff --git a/homeassistant/components/home_plus_control/__init__.py b/homeassistant/components/home_plus_control/__init__.py new file mode 100644 index 00000000000000..e559cd030b355c --- /dev/null +++ b/homeassistant/components/home_plus_control/__init__.py @@ -0,0 +1,179 @@ +"""The Legrand Home+ Control integration.""" +import asyncio +from datetime import timedelta +import logging + +import async_timeout +from homepluscontrol.homeplusapi import HomePlusControlApiError +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import HomeAssistant +from homeassistant.helpers import ( + config_entry_oauth2_flow, + config_validation as cv, + dispatcher, +) +from homeassistant.helpers.device_registry import async_get as async_get_device_registry +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from . import config_flow, helpers +from .api import HomePlusControlAsyncApi +from .const import ( + API, + CONF_SUBSCRIPTION_KEY, + DATA_COORDINATOR, + DISPATCHER_REMOVERS, + DOMAIN, + ENTITY_UIDS, + SIGNAL_ADD_ENTITIES, +) + +# Configuration schema for component in configuration.yaml +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_SECRET): cv.string, + vol.Required(CONF_SUBSCRIPTION_KEY): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + +# The Legrand Home+ Control platform is currently limited to "switch" entities +PLATFORMS = ["switch"] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: dict) -> bool: + """Set up the Legrand Home+ Control component from configuration.yaml.""" + hass.data[DOMAIN] = {} + + if DOMAIN not in config: + return True + + # Register the implementation from the config information + config_flow.HomePlusControlFlowHandler.async_register_implementation( + hass, + helpers.HomePlusControlOAuth2Implementation(hass, config[DOMAIN]), + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up Legrand Home+ Control from a config entry.""" + hass_entry_data = hass.data[DOMAIN].setdefault(config_entry.entry_id, {}) + + # Retrieve the registered implementation + implementation = ( + await config_entry_oauth2_flow.async_get_config_entry_implementation( + hass, config_entry + ) + ) + + # Using an aiohttp-based API lib, so rely on async framework + # Add the API object to the domain's data in HA + api = hass_entry_data[API] = HomePlusControlAsyncApi( + hass, config_entry, implementation + ) + + # Set of entity unique identifiers of this integration + uids = hass_entry_data[ENTITY_UIDS] = set() + + # Integration dispatchers + hass_entry_data[DISPATCHER_REMOVERS] = [] + + device_registry = async_get_device_registry(hass) + + # Register the Data Coordinator with the integration + async def async_update_data(): + """Fetch data from API endpoint. + + This is the place to pre-process the data to lookup tables + so entities can quickly look up their data. + """ + try: + # Note: asyncio.TimeoutError and aiohttp.ClientError are already + # handled by the data update coordinator. + async with async_timeout.timeout(10): + module_data = await api.async_get_modules() + except HomePlusControlApiError as err: + raise UpdateFailed( + f"Error communicating with API: {err} [{type(err)}]" + ) from err + + # Remove obsolete entities from Home Assistant + entity_uids_to_remove = uids - set(module_data) + for uid in entity_uids_to_remove: + uids.remove(uid) + device = device_registry.async_get_device({(DOMAIN, uid)}) + device_registry.async_remove_device(device.id) + + # Send out signal for new entity addition to Home Assistant + new_entity_uids = set(module_data) - uids + if new_entity_uids: + uids.update(new_entity_uids) + dispatcher.async_dispatcher_send( + hass, + SIGNAL_ADD_ENTITIES, + new_entity_uids, + coordinator, + ) + + return module_data + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="home_plus_control_module", + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=60), + ) + hass_entry_data[DATA_COORDINATOR] = coordinator + + async def start_platforms(): + """Continue setting up the platforms.""" + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(config_entry, platform) + for platform in PLATFORMS + ] + ) + # Only refresh the coordinator after all platforms are loaded. + await coordinator.async_refresh() + + hass.async_create_task(start_platforms()) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload the Legrand Home+ Control config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + # Unsubscribe the config_entry signal dispatcher connections + dispatcher_removers = hass.data[DOMAIN][config_entry.entry_id].pop( + "dispatcher_removers" + ) + for remover in dispatcher_removers: + remover() + + # And finally unload the domain config entry data + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/home_plus_control/api.py b/homeassistant/components/home_plus_control/api.py new file mode 100644 index 00000000000000..d9db95323de7b0 --- /dev/null +++ b/homeassistant/components/home_plus_control/api.py @@ -0,0 +1,55 @@ +"""API for Legrand Home+ Control bound to Home Assistant OAuth.""" +from homepluscontrol.homeplusapi import HomePlusControlAPI + +from homeassistant import config_entries, core +from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow + +from .const import DEFAULT_UPDATE_INTERVALS + + +class HomePlusControlAsyncApi(HomePlusControlAPI): + """Legrand Home+ Control object that interacts with the OAuth2-based API of the provider. + + This API is bound the HomeAssistant Config Entry that corresponds to this component. + + Attributes:. + hass (HomeAssistant): HomeAssistant core object. + config_entry (ConfigEntry): ConfigEntry object that configures this API. + implementation (AbstractOAuth2Implementation): OAuth2 implementation that handles AA and + token refresh. + _oauth_session (OAuth2Session): OAuth2Session object within implementation. + """ + + def __init__( + self, + hass: core.HomeAssistant, + config_entry: config_entries.ConfigEntry, + implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation, + ) -> None: + """Initialize the HomePlusControlAsyncApi object. + + Initialize the authenticated API for the Legrand Home+ Control component. + + Args:. + hass (HomeAssistant): HomeAssistant core object. + config_entry (ConfigEntry): ConfigEntry object that configures this API. + implementation (AbstractOAuth2Implementation): OAuth2 implementation that handles AA + and token refresh. + """ + self._oauth_session = config_entry_oauth2_flow.OAuth2Session( + hass, config_entry, implementation + ) + + # Create the API authenticated client - external library + super().__init__( + subscription_key=implementation.subscription_key, + oauth_client=aiohttp_client.async_get_clientsession(hass), + update_intervals=DEFAULT_UPDATE_INTERVALS, + ) + + async def async_get_access_token(self) -> str: + """Return a valid access token.""" + if not self._oauth_session.valid_token: + await self._oauth_session.async_ensure_token_valid() + + return self._oauth_session.token["access_token"] diff --git a/homeassistant/components/home_plus_control/config_flow.py b/homeassistant/components/home_plus_control/config_flow.py new file mode 100644 index 00000000000000..ed1686f7af18e4 --- /dev/null +++ b/homeassistant/components/home_plus_control/config_flow.py @@ -0,0 +1,32 @@ +"""Config flow for Legrand Home+ Control.""" +import logging + +from homeassistant import config_entries +from homeassistant.helpers import config_entry_oauth2_flow + +from .const import DOMAIN + + +class HomePlusControlFlowHandler( + config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN +): + """Config flow to handle Home+ Control OAuth2 authentication.""" + + DOMAIN = DOMAIN + + # Pick the Cloud Poll class + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + @property + def logger(self) -> logging.Logger: + """Return logger.""" + return logging.getLogger(__name__) + + async def async_step_user(self, user_input=None): + """Handle a flow start initiated by the user.""" + await self.async_set_unique_id(DOMAIN) + + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return await super().async_step_user(user_input) diff --git a/homeassistant/components/home_plus_control/const.py b/homeassistant/components/home_plus_control/const.py new file mode 100644 index 00000000000000..0ebae0bef20df9 --- /dev/null +++ b/homeassistant/components/home_plus_control/const.py @@ -0,0 +1,45 @@ +"""Constants for the Legrand Home+ Control integration.""" +API = "api" +CONF_SUBSCRIPTION_KEY = "subscription_key" +CONF_PLANT_UPDATE_INTERVAL = "plant_update_interval" +CONF_PLANT_TOPOLOGY_UPDATE_INTERVAL = "plant_topology_update_interval" +CONF_MODULE_STATUS_UPDATE_INTERVAL = "module_status_update_interval" + +DATA_COORDINATOR = "coordinator" +DOMAIN = "home_plus_control" +ENTITY_UIDS = "entity_unique_ids" +DISPATCHER_REMOVERS = "dispatcher_removers" + +# Legrand Model Identifiers - https://developer.legrand.com/documentation/product-cluster-list/# +HW_TYPE = { + "NLC": "NLC - Cable Outlet", + "NLF": "NLF - On-Off Dimmer Switch w/o Neutral", + "NLP": "NLP - Socket (Connected) Outlet", + "NLPM": "NLPM - Mobile Socket Outlet", + "NLM": "NLM - Micromodule Switch", + "NLV": "NLV - Shutter Switch with Neutral", + "NLLV": "NLLV - Shutter Switch with Level Control", + "NLL": "NLL - On-Off Toggle Switch with Neutral", + "NLT": "NLT - Remote Switch", + "NLD": "NLD - Double Gangs On-Off Remote Switch", +} + +# Legrand OAuth2 URIs +OAUTH2_AUTHORIZE = "https://partners-login.eliotbylegrand.com/authorize" +OAUTH2_TOKEN = "https://partners-login.eliotbylegrand.com/token" + +# The Legrand Home+ Control API has very limited request quotas - at the time of writing, it is +# limited to 500 calls per day (resets at 00:00) - so we want to keep updates to a minimum. +DEFAULT_UPDATE_INTERVALS = { + # Seconds between API checks for plant information updates. This is expected to change very + # little over time because a user's plants (homes) should rarely change. + CONF_PLANT_UPDATE_INTERVAL: 7200, # 120 minutes + # Seconds between API checks for plant topology updates. This is expected to change little + # over time because the modules in the user's plant should be relatively stable. + CONF_PLANT_TOPOLOGY_UPDATE_INTERVAL: 3600, # 60 minutes + # Seconds between API checks for module status updates. This can change frequently so we + # check often + CONF_MODULE_STATUS_UPDATE_INTERVAL: 300, # 5 minutes +} + +SIGNAL_ADD_ENTITIES = "home_plus_control_add_entities_signal" diff --git a/homeassistant/components/home_plus_control/helpers.py b/homeassistant/components/home_plus_control/helpers.py new file mode 100644 index 00000000000000..95d538def01b80 --- /dev/null +++ b/homeassistant/components/home_plus_control/helpers.py @@ -0,0 +1,53 @@ +"""Helper classes and functions for the Legrand Home+ Control integration.""" +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_oauth2_flow + +from .const import CONF_SUBSCRIPTION_KEY, DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN + + +class HomePlusControlOAuth2Implementation( + config_entry_oauth2_flow.LocalOAuth2Implementation +): + """OAuth2 implementation that extends the HomeAssistant local implementation. + + It provides the name of the integration and adds support for the subscription key. + + Attributes: + hass (HomeAssistant): HomeAssistant core object. + client_id (str): Client identifier assigned by the API provider when registering an app. + client_secret (str): Client secret assigned by the API provider when registering an app. + subscription_key (str): Subscription key obtained from the API provider. + authorize_url (str): Authorization URL initiate authentication flow. + token_url (str): URL to retrieve access/refresh tokens. + name (str): Name of the implementation (appears in the HomeAssitant GUI). + """ + + def __init__( + self, + hass: HomeAssistant, + config_data: dict, + ): + """HomePlusControlOAuth2Implementation Constructor. + + Initialize the authentication implementation for the Legrand Home+ Control API. + + Args: + hass (HomeAssistant): HomeAssistant core object. + config_data (dict): Configuration data that complies with the config Schema + of this component. + """ + super().__init__( + hass=hass, + domain=DOMAIN, + client_id=config_data[CONF_CLIENT_ID], + client_secret=config_data[CONF_CLIENT_SECRET], + authorize_url=OAUTH2_AUTHORIZE, + token_url=OAUTH2_TOKEN, + ) + self.subscription_key = config_data[CONF_SUBSCRIPTION_KEY] + + @property + def name(self) -> str: + """Name of the implementation.""" + return "Home+ Control" diff --git a/homeassistant/components/home_plus_control/manifest.json b/homeassistant/components/home_plus_control/manifest.json new file mode 100644 index 00000000000000..1eb143ca3c26c2 --- /dev/null +++ b/homeassistant/components/home_plus_control/manifest.json @@ -0,0 +1,15 @@ +{ + "domain": "home_plus_control", + "name": "Legrand Home+ Control", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/home_plus_control", + "requirements": [ + "homepluscontrol==0.0.5" + ], + "dependencies": [ + "http" + ], + "codeowners": [ + "@chemaaa" + ] +} diff --git a/homeassistant/components/home_plus_control/strings.json b/homeassistant/components/home_plus_control/strings.json new file mode 100644 index 00000000000000..c991c9e0279afe --- /dev/null +++ b/homeassistant/components/home_plus_control/strings.json @@ -0,0 +1,21 @@ +{ + "title": "Legrand Home+ Control", + "config": { + "step": { + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", + "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } + } +} diff --git a/homeassistant/components/home_plus_control/switch.py b/homeassistant/components/home_plus_control/switch.py new file mode 100644 index 00000000000000..d4167ae1f9ead8 --- /dev/null +++ b/homeassistant/components/home_plus_control/switch.py @@ -0,0 +1,129 @@ +"""Legrand Home+ Control Switch Entity Module that uses the HomeAssistant DataUpdateCoordinator.""" +from functools import partial + +from homeassistant.components.switch import ( + DEVICE_CLASS_OUTLET, + DEVICE_CLASS_SWITCH, + SwitchEntity, +) +from homeassistant.core import callback +from homeassistant.helpers import dispatcher +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DISPATCHER_REMOVERS, DOMAIN, HW_TYPE, SIGNAL_ADD_ENTITIES + + +@callback +def add_switch_entities(new_unique_ids, coordinator, add_entities): + """Add switch entities to the platform. + + Args: + new_unique_ids (set): Unique identifiers of entities to be added to Home Assistant. + coordinator (DataUpdateCoordinator): Data coordinator of this platform. + add_entities (function): Method called to add entities to Home Assistant. + """ + new_entities = [] + for uid in new_unique_ids: + new_ent = HomeControlSwitchEntity(coordinator, uid) + new_entities.append(new_ent) + add_entities(new_entities) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Legrand Home+ Control Switch platform in HomeAssistant. + + Args: + hass (HomeAssistant): HomeAssistant core object. + config_entry (ConfigEntry): ConfigEntry object that configures this platform. + async_add_entities (function): Function called to add entities of this platform. + """ + partial_add_switch_entities = partial( + add_switch_entities, add_entities=async_add_entities + ) + # Connect the dispatcher for the switch platform + hass.data[DOMAIN][config_entry.entry_id][DISPATCHER_REMOVERS].append( + dispatcher.async_dispatcher_connect( + hass, SIGNAL_ADD_ENTITIES, partial_add_switch_entities + ) + ) + + +class HomeControlSwitchEntity(CoordinatorEntity, SwitchEntity): + """Entity that represents a Legrand Home+ Control switch. + + It extends the HomeAssistant-provided classes of the CoordinatorEntity and the SwitchEntity. + + The CoordinatorEntity class provides: + should_poll + async_update + async_added_to_hass + + The SwitchEntity class provides the functionality of a ToggleEntity and additional power + consumption methods and state attributes. + """ + + def __init__(self, coordinator, idx): + """Pass coordinator to CoordinatorEntity.""" + super().__init__(coordinator) + self.idx = idx + self.module = self.coordinator.data[self.idx] + + @property + def name(self): + """Name of the device.""" + return self.module.name + + @property + def unique_id(self): + """ID (unique) of the device.""" + return self.idx + + @property + def device_info(self): + """Device information.""" + return { + "identifiers": { + # Unique identifiers within the domain + (DOMAIN, self.unique_id) + }, + "name": self.name, + "manufacturer": "Legrand", + "model": HW_TYPE.get(self.module.hw_type), + "sw_version": self.module.fw, + } + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + if self.module.device == "plug": + return DEVICE_CLASS_OUTLET + return DEVICE_CLASS_SWITCH + + @property + def available(self) -> bool: + """Return if entity is available. + + This is the case when the coordinator is able to update the data successfully + AND the switch entity is reachable. + + This method overrides the one of the CoordinatorEntity + """ + return self.coordinator.last_update_success and self.module.reachable + + @property + def is_on(self): + """Return entity state.""" + return self.module.status == "on" + + async def async_turn_on(self, **kwargs): + """Turn the light on.""" + # Do the turning on. + await self.module.turn_on() + # Update the data + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs): + """Turn the entity off.""" + await self.module.turn_off() + # Update the data + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/home_plus_control/translations/en.json b/homeassistant/components/home_plus_control/translations/en.json new file mode 100644 index 00000000000000..41232f4b1a75f8 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/en.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "authorize_url_timeout": "Timeout generating authorize URL.", + "missing_configuration": "The component is not configured. Please follow the documentation.", + "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", + "single_instance_allowed": "Integration is already being configured in another instance. Only one is allowed at any one time.", + "oauth_error": "Error in the authentication flow." + }, + "create_entry": { + "default": "Successfully authenticated" + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index b6799f59a0408a..d66736b2b3aedb 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -95,6 +95,7 @@ "hive", "hlk_sw16", "home_connect", + "home_plus_control", "homekit", "homekit_controller", "homematicip_cloud", diff --git a/requirements_all.txt b/requirements_all.txt index cf5073bdfba822..11ef33c6436a66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -771,6 +771,9 @@ homeconnect==0.6.3 # homeassistant.components.homematicip_cloud homematicip==0.13.1 +# homeassistant.components.home_plus_control +homepluscontrol==0.0.5 + # homeassistant.components.horizon horimote==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dfe1e812cb53c6..a0e78661180ad2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -420,6 +420,9 @@ homeconnect==0.6.3 # homeassistant.components.homematicip_cloud homematicip==0.13.1 +# homeassistant.components.home_plus_control +homepluscontrol==0.0.5 + # homeassistant.components.google # homeassistant.components.remember_the_milk httplib2==0.19.0 diff --git a/tests/components/home_plus_control/__init__.py b/tests/components/home_plus_control/__init__.py new file mode 100644 index 00000000000000..a9caba13e32afe --- /dev/null +++ b/tests/components/home_plus_control/__init__.py @@ -0,0 +1 @@ +"""Tests for the Legrand Home+ Control integration.""" diff --git a/tests/components/home_plus_control/conftest.py b/tests/components/home_plus_control/conftest.py new file mode 100644 index 00000000000000..cb9c869002f4c3 --- /dev/null +++ b/tests/components/home_plus_control/conftest.py @@ -0,0 +1,106 @@ +"""Test setup and fixtures for component Home+ Control by Legrand.""" +from homepluscontrol.homeplusinteractivemodule import HomePlusInteractiveModule +from homepluscontrol.homeplusplant import HomePlusPlant +import pytest + +from homeassistant import config_entries +from homeassistant.components.home_plus_control.const import DOMAIN + +from tests.common import MockConfigEntry + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" +SUBSCRIPTION_KEY = "12345678901234567890123456789012" + + +@pytest.fixture() +def mock_config_entry(): + """Return a fake config entry. + + This is a minimal entry to setup the integration and to ensure that the + OAuth access token will not expire. + """ + return MockConfigEntry( + domain=DOMAIN, + title="Home+ Control", + data={ + "auth_implementation": "home_plus_control", + "token": { + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 9999999999, + "expires_at": 9999999999.99999999, + "expires_on": 9999999999, + }, + }, + source="test", + connection_class=config_entries.CONN_CLASS_LOCAL_POLL, + options={}, + system_options={"disable_new_entities": False}, + unique_id=DOMAIN, + entry_id="home_plus_control_entry_id", + ) + + +@pytest.fixture() +def mock_modules(): + """Return the full set of mock modules.""" + plant = HomePlusPlant( + id="123456789009876543210", name="My Home", country="ES", oauth_client=None + ) + modules = { + "0000000987654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000987654321fedcba", + name="Kitchen Wall Outlet", + hw_type="NLP", + device="plug", + fw="42", + reachable=True, + ), + "0000000887654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000887654321fedcba", + name="Bedroom Wall Outlet", + hw_type="NLP", + device="light", + fw="42", + reachable=True, + ), + "0000000787654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000787654321fedcba", + name="Living Room Ceiling Light", + hw_type="NLF", + device="light", + fw="46", + reachable=True, + ), + "0000000687654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000687654321fedcba", + name="Dining Room Ceiling Light", + hw_type="NLF", + device="light", + fw="46", + reachable=True, + ), + "0000000587654321fedcba": HomePlusInteractiveModule( + plant, + id="0000000587654321fedcba", + name="Dining Room Wall Outlet", + hw_type="NLP", + device="plug", + fw="42", + reachable=True, + ), + } + + # Set lights off and plugs on + for mod_stat in modules.values(): + mod_stat.status = "on" + if mod_stat.device == "light": + mod_stat.status = "off" + + return modules diff --git a/tests/components/home_plus_control/test_config_flow.py b/tests/components/home_plus_control/test_config_flow.py new file mode 100644 index 00000000000000..4a7dbd3d3ee4e3 --- /dev/null +++ b/tests/components/home_plus_control/test_config_flow.py @@ -0,0 +1,192 @@ +"""Test the Legrand Home+ Control config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.home_plus_control.const import ( + CONF_SUBSCRIPTION_KEY, + DOMAIN, + OAUTH2_AUTHORIZE, + OAUTH2_TOKEN, +) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET +from homeassistant.helpers import config_entry_oauth2_flow + +from tests.common import MockConfigEntry +from tests.components.home_plus_control.conftest import ( + CLIENT_ID, + CLIENT_SECRET, + SUBSCRIPTION_KEY, +) + + +async def test_full_flow( + hass, aiohttp_client, aioclient_mock, current_request_with_host +): + """Check full flow.""" + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + + state = config_entry_oauth2_flow._encode_jwt( # pylint: disable=protected-access + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "auth" + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + + client = await aiohttp_client(hass.http.app) + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": 60, + }, + ) + + with patch( + "homeassistant.components.home_plus_control.async_setup_entry", + return_value=True, + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Home+ Control" + config_data = result["data"] + assert config_data["token"]["refresh_token"] == "mock-refresh-token" + assert config_data["token"]["access_token"] == "mock-access-token" + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert len(mock_setup.mock_calls) == 1 + + +async def test_abort_if_entry_in_progress(hass, current_request_with_host): + """Check flow abort when an entry is already in progress.""" + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + + # Start one flow + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + + # Attempt to start another flow + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_in_progress" + + +async def test_abort_if_entry_exists(hass, current_request_with_host): + """Check flow abort when an entry already exists.""" + existing_entry = MockConfigEntry(domain=DOMAIN) + existing_entry.add_to_hass(hass) + + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + "http": {}, + }, + ) + + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "single_instance_allowed" + + +async def test_abort_if_invalid_token( + hass, aiohttp_client, aioclient_mock, current_request_with_host +): + """Check flow abort when the token has an invalid value.""" + assert await setup.async_setup_component( + hass, + "home_plus_control", + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + result = await hass.config_entries.flow.async_init( + "home_plus_control", context={"source": config_entries.SOURCE_USER} + ) + + state = config_entry_oauth2_flow._encode_jwt( # pylint: disable=protected-access + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "auth" + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + + client = await aiohttp_client(hass.http.app) + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": "mock-access-token", + "type": "Bearer", + "expires_in": "non-integer", + }, + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "oauth_error" diff --git a/tests/components/home_plus_control/test_init.py b/tests/components/home_plus_control/test_init.py new file mode 100644 index 00000000000000..e48a9dc1f85e54 --- /dev/null +++ b/tests/components/home_plus_control/test_init.py @@ -0,0 +1,75 @@ +"""Test the Legrand Home+ Control integration.""" +from unittest.mock import patch + +from homeassistant import config_entries, setup +from homeassistant.components.home_plus_control.const import ( + CONF_SUBSCRIPTION_KEY, + DOMAIN, +) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET + +from tests.components.home_plus_control.conftest import ( + CLIENT_ID, + CLIENT_SECRET, + SUBSCRIPTION_KEY, +) + + +async def test_loading(hass, mock_config_entry): + """Test component loading.""" + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value={}, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + + assert len(mock_check.mock_calls) == 1 + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + +async def test_loading_with_no_config(hass, mock_config_entry): + """Test component loading failure when it has not configuration.""" + mock_config_entry.add_to_hass(hass) + await setup.async_setup_component(hass, DOMAIN, {}) + # Component setup fails because the oauth2 implementation could not be registered + assert mock_config_entry.state == config_entries.ENTRY_STATE_SETUP_ERROR + + +async def test_unloading(hass, mock_config_entry): + """Test component unloading.""" + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value={}, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + + assert len(mock_check.mock_calls) == 1 + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + # We now unload the entry + assert await hass.config_entries.async_unload(mock_config_entry.entry_id) + assert mock_config_entry.state == config_entries.ENTRY_STATE_NOT_LOADED diff --git a/tests/components/home_plus_control/test_switch.py b/tests/components/home_plus_control/test_switch.py new file mode 100644 index 00000000000000..f699fe08d05772 --- /dev/null +++ b/tests/components/home_plus_control/test_switch.py @@ -0,0 +1,464 @@ +"""Test the Legrand Home+ Control switch platform.""" +import datetime as dt +from unittest.mock import patch + +from homepluscontrol.homeplusapi import HomePlusControlApiError + +from homeassistant import config_entries, setup +from homeassistant.components.home_plus_control.const import ( + CONF_SUBSCRIPTION_KEY, + DOMAIN, +) +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) + +from tests.common import async_fire_time_changed +from tests.components.home_plus_control.conftest import ( + CLIENT_ID, + CLIENT_SECRET, + SUBSCRIPTION_KEY, +) + + +def entity_assertions( + hass, + num_exp_entities, + num_exp_devices=None, + expected_entities=None, + expected_devices=None, +): + """Assert number of entities and devices.""" + entity_reg = hass.helpers.entity_registry.async_get(hass) + device_reg = hass.helpers.device_registry.async_get(hass) + + if num_exp_devices is None: + num_exp_devices = num_exp_entities + + assert len(entity_reg.entities) == num_exp_entities + assert len(device_reg.devices) == num_exp_devices + + if expected_entities is not None: + for exp_entity_id, present in expected_entities.items(): + assert bool(entity_reg.async_get(exp_entity_id)) == present + + if expected_devices is not None: + for exp_device_id, present in expected_devices.items(): + assert bool(device_reg.async_get(exp_device_id)) == present + + +def one_entity_state(hass, device_uid): + """Assert the presence of an entity and return its state.""" + entity_reg = hass.helpers.entity_registry.async_get(hass) + device_reg = hass.helpers.device_registry.async_get(hass) + + device_id = device_reg.async_get_device({(DOMAIN, device_uid)}).id + entity_entries = hass.helpers.entity_registry.async_entries_for_device( + entity_reg, device_id + ) + + assert len(entity_entries) == 1 + entity_entry = entity_entries[0] + return hass.states.get(entity_entry.entity_id).state + + +async def test_plant_update( + hass, + mock_config_entry, + mock_modules, +): + """Test entity and device loading.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + +async def test_plant_topology_reduction_change( + hass, + mock_config_entry, + mock_modules, +): + """Test an entity leaving the plant topology.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices - 5 mock entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # Now we refresh the topology with one entity less + mock_modules.pop("0000000987654321fedcba") + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check for plant, topology and module status - this time only 4 left + entity_assertions( + hass, + num_exp_entities=4, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": False, + }, + ) + + +async def test_plant_topology_increase_change( + hass, + mock_config_entry, + mock_modules, +): + """Test an entity entering the plant topology.""" + # Remove one module initially + new_module = mock_modules.pop("0000000987654321fedcba") + + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices - we have 4 entities to start with + entity_assertions( + hass, + num_exp_entities=4, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": False, + }, + ) + + # Now we refresh the topology with one entity more + mock_modules["0000000987654321fedcba"] = new_module + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + +async def test_module_status_unavailable(hass, mock_config_entry, mock_modules): + """Test a module becoming unreachable in the plant.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Check the entities and devices - 5 mock entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # Confirm the availability of this particular entity + test_entity_uid = "0000000987654321fedcba" + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_ON + + # Now we refresh the topology with the module being unreachable + mock_modules["0000000987654321fedcba"].reachable = False + + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + await hass.async_block_till_done() + # The entity is present, but not available + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_UNAVAILABLE + + +async def test_module_status_available( + hass, + mock_config_entry, + mock_modules, +): + """Test a module becoming reachable in the plant.""" + # Set the module initially unreachable + mock_modules["0000000987654321fedcba"].reachable = False + + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # This particular entity is not available + test_entity_uid = "0000000987654321fedcba" + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_UNAVAILABLE + + # Now we refresh the topology with the module being reachable + mock_modules["0000000987654321fedcba"].reachable = True + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities remain the same + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # Now the entity is available + test_entity_uid = "0000000987654321fedcba" + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_ON + + +async def test_initial_api_error( + hass, + mock_config_entry, + mock_modules, +): + """Test an API error on initial call.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + side_effect=HomePlusControlApiError, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # The component has been loaded + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + # Check the entities and devices - None have been configured + entity_assertions(hass, num_exp_entities=0) + + +async def test_update_with_api_error( + hass, + mock_config_entry, + mock_modules, +): + """Test an API timeout when updating the module data.""" + # Load the entry + mock_config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + ) as mock_check: + await setup.async_setup_component( + hass, + DOMAIN, + { + "home_plus_control": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + CONF_SUBSCRIPTION_KEY: SUBSCRIPTION_KEY, + }, + }, + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # The component has been loaded + assert mock_config_entry.state == config_entries.ENTRY_STATE_LOADED + + # Check the entities and devices - all entities should be there + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + for test_entity_uid in mock_modules: + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state in (STATE_ON, STATE_OFF) + + # Attempt to update the data, but API update fails + with patch( + "homeassistant.components.home_plus_control.api.HomePlusControlAsyncApi.async_get_modules", + return_value=mock_modules, + side_effect=HomePlusControlApiError, + ) as mock_check: + async_fire_time_changed( + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + ) + await hass.async_block_till_done() + assert len(mock_check.mock_calls) == 1 + + # Assert the devices and entities - all should still be present + entity_assertions( + hass, + num_exp_entities=5, + expected_entities={ + "switch.dining_room_wall_outlet": True, + "switch.kitchen_wall_outlet": True, + }, + ) + + # This entity has not returned a status, so appears as unavailable + for test_entity_uid in mock_modules: + test_entity_state = one_entity_state(hass, test_entity_uid) + assert test_entity_state == STATE_UNAVAILABLE From 9f07ca069d20720e4dbb0261088328b58957ab57 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 25 Mar 2021 14:19:32 +0100 Subject: [PATCH 1498/1818] Fix zha manual flow test (#48317) --- tests/components/zha/test_config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index b3dbefbdbf0096..f41c734537b6c4 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -86,6 +86,7 @@ async def test_user_flow_show_form(hass): assert result["step_id"] == "user" +@patch("serial.tools.list_ports.comports", MagicMock(return_value=[])) async def test_user_flow_show_manual(hass): """Test user flow manual entry when no comport detected.""" result = await hass.config_entries.flow.async_init( From 4f4a6fd6a5a978279ccced3906d3c290943ac249 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Thu, 25 Mar 2021 12:06:51 -0400 Subject: [PATCH 1499/1818] Add econet thermostat support and use getattr for sensors (#45564) Co-authored-by: Martin Hjelmare --- .coveragerc | 1 + homeassistant/components/econet/__init__.py | 16 +- .../components/econet/binary_sensor.py | 59 +++-- homeassistant/components/econet/climate.py | 241 ++++++++++++++++++ homeassistant/components/econet/manifest.json | 2 +- homeassistant/components/econet/sensor.py | 99 ++++--- .../components/econet/water_heater.py | 7 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 9 files changed, 338 insertions(+), 91 deletions(-) create mode 100644 homeassistant/components/econet/climate.py diff --git a/.coveragerc b/.coveragerc index cec2649b13240d..712c5292e572a9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -221,6 +221,7 @@ omit = homeassistant/components/ecobee/weather.py homeassistant/components/econet/__init__.py homeassistant/components/econet/binary_sensor.py + homeassistant/components/econet/climate.py homeassistant/components/econet/const.py homeassistant/components/econet/sensor.py homeassistant/components/econet/water_heater.py diff --git a/homeassistant/components/econet/__init__.py b/homeassistant/components/econet/__init__.py index 03ca9c76120b80..e605b16a2378be 100644 --- a/homeassistant/components/econet/__init__.py +++ b/homeassistant/components/econet/__init__.py @@ -13,7 +13,7 @@ PyeconetError, ) -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, TEMP_FAHRENHEIT from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import dispatcher_send @@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["binary_sensor", "sensor", "water_heater"] +PLATFORMS = ["climate", "binary_sensor", "sensor", "water_heater"] PUSH_UPDATE = "econet.push_update" INTERVAL = timedelta(minutes=60) @@ -54,7 +54,9 @@ async def async_setup_entry(hass, config_entry): raise ConfigEntryNotReady from err try: - equipment = await api.get_equipment_by_type([EquipmentType.WATER_HEATER]) + equipment = await api.get_equipment_by_type( + [EquipmentType.WATER_HEATER, EquipmentType.THERMOSTAT] + ) except (ClientError, GenericHTTPError, InvalidResponseFormat) as err: raise ConfigEntryNotReady from err hass.data[DOMAIN][API_CLIENT][config_entry.entry_id] = api @@ -74,6 +76,9 @@ def update_published(): for _eqip in equipment[EquipmentType.WATER_HEATER]: _eqip.set_update_callback(update_published) + for _eqip in equipment[EquipmentType.THERMOSTAT]: + _eqip.set_update_callback(update_published) + async def resubscribe(now): """Resubscribe to the MQTT updates.""" await hass.async_add_executor_job(api.unsubscribe) @@ -149,6 +154,11 @@ def unique_id(self): """Return the unique ID of the entity.""" return f"{self._econet.device_id}_{self._econet.device_name}" + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + @property def should_poll(self) -> bool: """Return True if entity has to be polled for state. diff --git a/homeassistant/components/econet/binary_sensor.py b/homeassistant/components/econet/binary_sensor.py index b87e6bb0cd0446..116b1243ee0899 100644 --- a/homeassistant/components/econet/binary_sensor.py +++ b/homeassistant/components/econet/binary_sensor.py @@ -2,8 +2,10 @@ from pyeconet.equipment import EquipmentType from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_LOCK, DEVICE_CLASS_OPENING, DEVICE_CLASS_POWER, + DEVICE_CLASS_SOUND, BinarySensorEntity, ) @@ -12,27 +14,40 @@ SENSOR_NAME_RUNNING = "running" SENSOR_NAME_SHUTOFF_VALVE = "shutoff_valve" -SENSOR_NAME_VACATION = "vacation" +SENSOR_NAME_RUNNING = "running" +SENSOR_NAME_SCREEN_LOCKED = "screen_locked" +SENSOR_NAME_BEEP_ENABLED = "beep_enabled" + +ATTR = "attr" +DEVICE_CLASS = "device_class" +SENSORS = { + SENSOR_NAME_SHUTOFF_VALVE: { + ATTR: "shutoff_valve_open", + DEVICE_CLASS: DEVICE_CLASS_OPENING, + }, + SENSOR_NAME_RUNNING: {ATTR: "running", DEVICE_CLASS: DEVICE_CLASS_POWER}, + SENSOR_NAME_SCREEN_LOCKED: { + ATTR: "screen_locked", + DEVICE_CLASS: DEVICE_CLASS_LOCK, + }, + SENSOR_NAME_BEEP_ENABLED: { + ATTR: "beep_enabled", + DEVICE_CLASS: DEVICE_CLASS_SOUND, + }, +} async def async_setup_entry(hass, entry, async_add_entities): """Set up EcoNet binary sensor based on a config entry.""" equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] binary_sensors = [] - for water_heater in equipment[EquipmentType.WATER_HEATER]: - if water_heater.has_shutoff_valve: - binary_sensors.append( - EcoNetBinarySensor( - water_heater, - SENSOR_NAME_SHUTOFF_VALVE, - ) - ) - if water_heater.running is not None: - binary_sensors.append(EcoNetBinarySensor(water_heater, SENSOR_NAME_RUNNING)) - if water_heater.vacation is not None: - binary_sensors.append( - EcoNetBinarySensor(water_heater, SENSOR_NAME_VACATION) - ) + all_equipment = equipment[EquipmentType.WATER_HEATER].copy() + all_equipment.extend(equipment[EquipmentType.THERMOSTAT].copy()) + for _equip in all_equipment: + for sensor_name, sensor in SENSORS.items(): + if getattr(_equip, sensor[ATTR], None) is not None: + binary_sensors.append(EcoNetBinarySensor(_equip, sensor_name)) + async_add_entities(binary_sensors) @@ -48,22 +63,12 @@ def __init__(self, econet_device, device_name): @property def is_on(self): """Return true if the binary sensor is on.""" - if self._device_name == SENSOR_NAME_SHUTOFF_VALVE: - return self._econet.shutoff_valve_open - if self._device_name == SENSOR_NAME_RUNNING: - return self._econet.running - if self._device_name == SENSOR_NAME_VACATION: - return self._econet.vacation - return False + return getattr(self._econet, SENSORS[self._device_name][ATTR]) @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - if self._device_name == SENSOR_NAME_SHUTOFF_VALVE: - return DEVICE_CLASS_OPENING - if self._device_name == SENSOR_NAME_RUNNING: - return DEVICE_CLASS_POWER - return None + return SENSORS[self._device_name][DEVICE_CLASS] @property def name(self): diff --git a/homeassistant/components/econet/climate.py b/homeassistant/components/econet/climate.py new file mode 100644 index 00000000000000..fe50855d559366 --- /dev/null +++ b/homeassistant/components/econet/climate.py @@ -0,0 +1,241 @@ +"""Support for Rheem EcoNet thermostats.""" +import logging + +from pyeconet.equipment import EquipmentType +from pyeconet.equipment.thermostat import ThermostatFanMode, ThermostatOperationMode + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, +) +from homeassistant.const import ATTR_TEMPERATURE + +from . import EcoNetEntity +from .const import DOMAIN, EQUIPMENT + +_LOGGER = logging.getLogger(__name__) + +ECONET_STATE_TO_HA = { + ThermostatOperationMode.HEATING: HVAC_MODE_HEAT, + ThermostatOperationMode.COOLING: HVAC_MODE_COOL, + ThermostatOperationMode.OFF: HVAC_MODE_OFF, + ThermostatOperationMode.AUTO: HVAC_MODE_HEAT_COOL, + ThermostatOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY, +} +HA_STATE_TO_ECONET = {value: key for key, value in ECONET_STATE_TO_HA.items()} + +ECONET_FAN_STATE_TO_HA = { + ThermostatFanMode.AUTO: FAN_AUTO, + ThermostatFanMode.LOW: FAN_LOW, + ThermostatFanMode.MEDIUM: FAN_MEDIUM, + ThermostatFanMode.HIGH: FAN_HIGH, +} +HA_FAN_STATE_TO_ECONET = {value: key for key, value in ECONET_FAN_STATE_TO_HA.items()} + +SUPPORT_FLAGS_THERMOSTAT = ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_FAN_MODE + | SUPPORT_AUX_HEAT +) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up EcoNet thermostat based on a config entry.""" + equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] + async_add_entities( + [ + EcoNetThermostat(thermostat) + for thermostat in equipment[EquipmentType.THERMOSTAT] + ], + ) + + +class EcoNetThermostat(EcoNetEntity, ClimateEntity): + """Define a Econet thermostat.""" + + def __init__(self, thermostat): + """Initialize.""" + super().__init__(thermostat) + self._running = thermostat.running + self._poll = True + self.econet_state_to_ha = {} + self.ha_state_to_econet = {} + self.op_list = [] + for mode in self._econet.modes: + if mode not in [ + ThermostatOperationMode.UNKNOWN, + ThermostatOperationMode.EMERGENCY_HEAT, + ]: + ha_mode = ECONET_STATE_TO_HA[mode] + self.op_list.append(ha_mode) + + @property + def supported_features(self): + """Return the list of supported features.""" + if self._econet.supports_humidifier: + return SUPPORT_FLAGS_THERMOSTAT | SUPPORT_TARGET_HUMIDITY + return SUPPORT_FLAGS_THERMOSTAT + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._econet.set_point + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._econet.humidity + + @property + def target_humidity(self): + """Return the humidity we try to reach.""" + if self._econet.supports_humidifier: + return self._econet.dehumidifier_set_point + return None + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + if self.hvac_mode == HVAC_MODE_COOL: + return self._econet.cool_set_point + if self.hvac_mode == HVAC_MODE_HEAT: + return self._econet.heat_set_point + return None + + @property + def target_temperature_low(self): + """Return the lower bound temperature we try to reach.""" + if self.hvac_mode == HVAC_MODE_HEAT_COOL: + return self._econet.heat_set_point + return None + + @property + def target_temperature_high(self): + """Return the higher bound temperature we try to reach.""" + if self.hvac_mode == HVAC_MODE_HEAT_COOL: + return self._econet.cool_set_point + return None + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + target_temp = kwargs.get(ATTR_TEMPERATURE) + target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + if target_temp: + self._econet.set_set_point(target_temp, None, None) + if target_temp_low or target_temp_high: + self._econet.set_set_point(None, target_temp_high, target_temp_low) + + @property + def is_aux_heat(self): + """Return true if aux heater.""" + return self._econet.mode == ThermostatOperationMode.EMERGENCY_HEAT + + @property + def hvac_modes(self): + """Return hvac operation ie. heat, cool mode. + + Needs to be one of HVAC_MODE_*. + """ + return self.op_list + + @property + def hvac_mode(self) -> str: + """Return hvac operation ie. heat, cool, mode. + + Needs to be one of HVAC_MODE_*. + """ + econet_mode = self._econet.mode + _current_op = HVAC_MODE_OFF + if econet_mode is not None: + _current_op = ECONET_STATE_TO_HA[econet_mode] + + return _current_op + + def set_hvac_mode(self, hvac_mode): + """Set new target hvac mode.""" + hvac_mode_to_set = HA_STATE_TO_ECONET.get(hvac_mode) + if hvac_mode_to_set is None: + raise ValueError(f"{hvac_mode} is not a valid mode.") + self._econet.set_mode(hvac_mode_to_set) + + def set_humidity(self, humidity: int): + """Set new target humidity.""" + self._econet.set_dehumidifier_set_point(humidity) + + @property + def fan_mode(self): + """Return the current fan mode.""" + econet_fan_mode = self._econet.fan_mode + + # Remove this after we figure out how to handle med lo and med hi + if econet_fan_mode in [ThermostatFanMode.MEDHI, ThermostatFanMode.MEDLO]: + econet_fan_mode = ThermostatFanMode.MEDIUM + + _current_fan_mode = FAN_AUTO + if econet_fan_mode is not None: + _current_fan_mode = ECONET_FAN_STATE_TO_HA[econet_fan_mode] + return _current_fan_mode + + @property + def fan_modes(self): + """Return the fan modes.""" + econet_fan_modes = self._econet.fan_modes + fan_list = [] + for mode in econet_fan_modes: + # Remove the MEDLO MEDHI once we figure out how to handle it + if mode not in [ + ThermostatFanMode.UNKNOWN, + ThermostatFanMode.MEDLO, + ThermostatFanMode.MEDHI, + ]: + fan_list.append(ECONET_FAN_STATE_TO_HA[mode]) + return fan_list + + def set_fan_mode(self, fan_mode): + """Set the fan mode.""" + self._econet.set_fan_mode(HA_FAN_STATE_TO_ECONET[fan_mode]) + + def turn_aux_heat_on(self): + """Turn auxiliary heater on.""" + self._econet.set_mode(ThermostatOperationMode.EMERGENCY_HEAT) + + def turn_aux_heat_off(self): + """Turn auxiliary heater off.""" + self._econet.set_mode(ThermostatOperationMode.HEATING) + + @property + def min_temp(self): + """Return the minimum temperature.""" + return self._econet.set_point_limits[0] + + @property + def max_temp(self): + """Return the maximum temperature.""" + return self._econet.set_point_limits[1] + + @property + def min_humidity(self) -> int: + """Return the minimum humidity.""" + return self._econet.dehumidifier_set_point_limits[0] + + @property + def max_humidity(self) -> int: + """Return the maximum humidity.""" + return self._econet.dehumidifier_set_point_limits[1] diff --git a/homeassistant/components/econet/manifest.json b/homeassistant/components/econet/manifest.json index 7e4cf0106ba224..c658542295e86d 100644 --- a/homeassistant/components/econet/manifest.json +++ b/homeassistant/components/econet/manifest.json @@ -4,6 +4,6 @@ "name": "Rheem EcoNet Products", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/econet", - "requirements": ["pyeconet==0.1.12"], + "requirements": ["pyeconet==0.1.13"], "codeowners": ["@vangorra", "@w1ll1am23"] } \ No newline at end of file diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index 05fa1b734ea533..0dfe8df7fb39c1 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -3,9 +3,9 @@ from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( + DEVICE_CLASS_SIGNAL_STRENGTH, ENERGY_KILO_WATT_HOUR, PERCENTAGE, - SIGNAL_STRENGTH_DECIBELS_MILLIWATT, VOLUME_GALLONS, ) @@ -13,6 +13,7 @@ from .const import DOMAIN, EQUIPMENT ENERGY_KILO_BRITISH_THERMAL_UNIT = "kBtu" + TANK_HEALTH = "tank_health" AVAILIBLE_HOT_WATER = "availible_hot_water" COMPRESSOR_HEALTH = "compressor_health" @@ -23,28 +24,51 @@ WIFI_SIGNAL = "wifi_signal" RUNNING_STATE = "running_state" +SENSOR_NAMES_TO_ATTRIBUTES = { + TANK_HEALTH: "tank_health", + AVAILIBLE_HOT_WATER: "tank_hot_water_availability", + COMPRESSOR_HEALTH: "compressor_health", + OVERRIDE_STATUS: "override_status", + WATER_USAGE_TODAY: "todays_water_usage", + POWER_USAGE_TODAY: "todays_energy_usage", + ALERT_COUNT: "alert_count", + WIFI_SIGNAL: "wifi_signal", + RUNNING_STATE: "running_state", +} + +SENSOR_NAMES_TO_UNIT_OF_MEASUREMENT = { + TANK_HEALTH: PERCENTAGE, + AVAILIBLE_HOT_WATER: PERCENTAGE, + COMPRESSOR_HEALTH: PERCENTAGE, + OVERRIDE_STATUS: None, + WATER_USAGE_TODAY: VOLUME_GALLONS, + POWER_USAGE_TODAY: None, # Depends on unit type + ALERT_COUNT: None, + WIFI_SIGNAL: DEVICE_CLASS_SIGNAL_STRENGTH, + RUNNING_STATE: None, # This is just a string +} + async def async_setup_entry(hass, entry, async_add_entities): """Set up EcoNet sensor based on a config entry.""" + equipment = hass.data[DOMAIN][EQUIPMENT][entry.entry_id] sensors = [] + all_equipment = equipment[EquipmentType.WATER_HEATER].copy() + all_equipment.extend(equipment[EquipmentType.THERMOSTAT].copy()) + + for _equip in all_equipment: + for name, attribute in SENSOR_NAMES_TO_ATTRIBUTES.items(): + if getattr(_equip, attribute, None) is not None: + sensors.append(EcoNetSensor(_equip, name)) + # This is None to start with and all device have it + sensors.append(EcoNetSensor(_equip, WIFI_SIGNAL)) + for water_heater in equipment[EquipmentType.WATER_HEATER]: - if water_heater.tank_hot_water_availability is not None: - sensors.append(EcoNetSensor(water_heater, AVAILIBLE_HOT_WATER)) - if water_heater.tank_health is not None: - sensors.append(EcoNetSensor(water_heater, TANK_HEALTH)) - if water_heater.compressor_health is not None: - sensors.append(EcoNetSensor(water_heater, COMPRESSOR_HEALTH)) - if water_heater.override_status: - sensors.append(EcoNetSensor(water_heater, OVERRIDE_STATUS)) - if water_heater.running_state is not None: - sensors.append(EcoNetSensor(water_heater, RUNNING_STATE)) - # All units have this - sensors.append(EcoNetSensor(water_heater, ALERT_COUNT)) # These aren't part of the device and start off as None in pyeconet so always add them sensors.append(EcoNetSensor(water_heater, WATER_USAGE_TODAY)) sensors.append(EcoNetSensor(water_heater, POWER_USAGE_TODAY)) - sensors.append(EcoNetSensor(water_heater, WIFI_SIGNAL)) + async_add_entities(sensors) @@ -60,50 +84,21 @@ def __init__(self, econet_device, device_name): @property def state(self): """Return sensors state.""" - if self._device_name == AVAILIBLE_HOT_WATER: - return self._econet.tank_hot_water_availability - if self._device_name == TANK_HEALTH: - return self._econet.tank_health - if self._device_name == COMPRESSOR_HEALTH: - return self._econet.compressor_health - if self._device_name == OVERRIDE_STATUS: - return self._econet.oveerride_status - if self._device_name == WATER_USAGE_TODAY: - if self._econet.todays_water_usage: - return round(self._econet.todays_water_usage, 2) - return None - if self._device_name == POWER_USAGE_TODAY: - if self._econet.todays_energy_usage: - return round(self._econet.todays_energy_usage, 2) - return None - if self._device_name == WIFI_SIGNAL: - if self._econet.wifi_signal: - return self._econet.wifi_signal - return None - if self._device_name == ALERT_COUNT: - return self._econet.alert_count - if self._device_name == RUNNING_STATE: - return self._econet.running_state - return None + value = getattr(self._econet, SENSOR_NAMES_TO_ATTRIBUTES[self._device_name]) + if isinstance(value, float): + value = round(value, 2) + return value @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - if self._device_name == AVAILIBLE_HOT_WATER: - return PERCENTAGE - if self._device_name == TANK_HEALTH: - return PERCENTAGE - if self._device_name == COMPRESSOR_HEALTH: - return PERCENTAGE - if self._device_name == WATER_USAGE_TODAY: - return VOLUME_GALLONS + unit_of_measurement = SENSOR_NAMES_TO_UNIT_OF_MEASUREMENT[self._device_name] if self._device_name == POWER_USAGE_TODAY: if self._econet.energy_type == ENERGY_KILO_BRITISH_THERMAL_UNIT.upper(): - return ENERGY_KILO_BRITISH_THERMAL_UNIT - return ENERGY_KILO_WATT_HOUR - if self._device_name == WIFI_SIGNAL: - return SIGNAL_STRENGTH_DECIBELS_MILLIWATT - return None + unit_of_measurement = ENERGY_KILO_BRITISH_THERMAL_UNIT + else: + unit_of_measurement = ENERGY_KILO_WATT_HOUR + return unit_of_measurement @property def name(self): diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py index af3399b53afb3a..ed31e78af7c2ac 100644 --- a/homeassistant/components/econet/water_heater.py +++ b/homeassistant/components/econet/water_heater.py @@ -18,7 +18,6 @@ SUPPORT_TARGET_TEMPERATURE, WaterHeaterEntity, ) -from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.core import callback from . import EcoNetEntity @@ -77,11 +76,6 @@ def is_away_mode_on(self): """Return true if away mode is on.""" return self._econet.away - @property - def temperature_unit(self): - """Return the unit of measurement.""" - return TEMP_FAHRENHEIT - @property def current_operation(self): """Return current operation.""" @@ -160,6 +154,7 @@ async def async_update(self): """Get the latest energy usage.""" await self.water_heater.get_energy_usage() await self.water_heater.get_water_usage() + self.async_write_ha_state() self._poll = False def turn_away_mode_on(self): diff --git a/requirements_all.txt b/requirements_all.txt index 11ef33c6436a66..6730162928e685 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1355,7 +1355,7 @@ pydroid-ipcam==0.8 pyebox==1.1.4 # homeassistant.components.econet -pyeconet==0.1.12 +pyeconet==0.1.13 # homeassistant.components.edimax pyedimax==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a0e78661180ad2..c44a19e997dc9d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -708,7 +708,7 @@ pydexcom==0.2.0 pydispatcher==2.0.5 # homeassistant.components.econet -pyeconet==0.1.12 +pyeconet==0.1.13 # homeassistant.components.everlights pyeverlights==0.1.0 From f0e5e616a79665e2a301efeb56b66e48359373b2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 25 Mar 2021 18:35:01 +0100 Subject: [PATCH 1500/1818] Fix device discovery of OAuth2 config flows (#48326) --- homeassistant/config_entries.py | 8 ++++--- homeassistant/data_entry_flow.py | 6 ++--- .../helpers/config_entry_oauth2_flow.py | 22 ++++--------------- tests/components/cloud/test_account_link.py | 2 +- tests/components/somfy/test_config_flow.py | 4 +++- .../components/withings/test_binary_sensor.py | 2 +- tests/components/withings/test_common.py | 3 +++ tests/components/withings/test_config_flow.py | 2 +- tests/components/withings/test_init.py | 5 ++++- tests/components/withings/test_sensor.py | 4 ++-- .../helpers/test_config_entry_oauth2_flow.py | 8 +++++-- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 6bdb850e64349b..849e3dcfd14b56 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1049,11 +1049,13 @@ def _async_current_ids(self, include_ignore: bool = True) -> set[str | None]: } @callback - def _async_in_progress(self) -> list[dict]: + def _async_in_progress(self, include_uninitialized: bool = False) -> list[dict]: """Return other in progress flows for current domain.""" return [ flw - for flw in self.hass.config_entries.flow.async_progress() + for flw in self.hass.config_entries.flow.async_progress( + include_uninitialized=include_uninitialized + ) if flw["handler"] == self.handler and flw["flow_id"] != self.flow_id ] @@ -1093,7 +1095,7 @@ async def _async_handle_discovery_without_unique_id(self) -> None: self._abort_if_unique_id_configured() # Abort if any other flow for this handler is already in progress - if self._async_in_progress(): + if self._async_in_progress(include_uninitialized=True): raise data_entry_flow.AbortFlow("already_in_progress") async def async_step_discovery( diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index b149493373feea..40c9ace0f8dee6 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -94,17 +94,17 @@ async def async_post_init(self, flow: FlowHandler, result: dict[str, Any]) -> No """Entry has finished executing its first step asynchronously.""" @callback - def async_progress(self) -> list[dict]: + def async_progress(self, include_uninitialized: bool = False) -> list[dict]: """Return the flows in progress.""" return [ { "flow_id": flow.flow_id, "handler": flow.handler, "context": flow.context, - "step_id": flow.cur_step["step_id"], + "step_id": flow.cur_step["step_id"] if flow.cur_step else None, } for flow in self._progress.values() - if flow.cur_step is not None + if include_uninitialized or flow.cur_step is not None ] async def async_init( diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index a949685c7b118b..795c08dd1c98f2 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -245,8 +245,10 @@ async def async_step_pick_implementation( if not implementations: return self.async_abort(reason="missing_configuration") - if len(implementations) == 1: - # Pick first implementation as we have only one. + req = http.current_request.get() + if len(implementations) == 1 and req is not None: + # Pick first implementation if we have only one, but only + # if this is triggered by a user interaction (request). self.flow_impl = list(implementations.values())[0] return await self.async_step_auth() @@ -313,23 +315,7 @@ async def async_oauth_create_entry(self, data: dict) -> dict: """ return self.async_create_entry(title=self.flow_impl.name, data=data) - async def async_step_discovery( - self, discovery_info: dict[str, Any] - ) -> dict[str, Any]: - """Handle a flow initialized by discovery.""" - await self.async_set_unique_id(self.DOMAIN) - - if self.hass.config_entries.async_entries(self.DOMAIN): - return self.async_abort(reason="already_configured") - - return await self.async_step_pick_implementation() - async_step_user = async_step_pick_implementation - async_step_mqtt = async_step_discovery - async_step_ssdp = async_step_discovery - async_step_zeroconf = async_step_discovery - async_step_homekit = async_step_discovery - async_step_dhcp = async_step_discovery @classmethod def async_register_implementation( diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index 62225597939f74..c1022dc6aae30f 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -108,7 +108,7 @@ async def test_get_services_error(hass): assert account_link.DATA_SERVICES not in hass.data -async def test_implementation(hass, flow_handler): +async def test_implementation(hass, flow_handler, current_request_with_host): """Test Cloud OAuth2 implementation.""" hass.data["cloud"] = None diff --git a/tests/components/somfy/test_config_flow.py b/tests/components/somfy/test_config_flow.py index 47adb5bdc91800..60200824c00f52 100644 --- a/tests/components/somfy/test_config_flow.py +++ b/tests/components/somfy/test_config_flow.py @@ -123,7 +123,9 @@ async def test_full_flow( assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED -async def test_abort_if_authorization_timeout(hass, mock_impl): +async def test_abort_if_authorization_timeout( + hass, mock_impl, current_request_with_host +): """Check Somfy authorization timeout.""" flow = config_flow.SomfyFlowHandler() flow.hass = hass diff --git a/tests/components/withings/test_binary_sensor.py b/tests/components/withings/test_binary_sensor.py index 22c6b6de862a8b..9f93e00f4ef320 100644 --- a/tests/components/withings/test_binary_sensor.py +++ b/tests/components/withings/test_binary_sensor.py @@ -15,7 +15,7 @@ async def test_binary_sensor( - hass: HomeAssistant, component_factory: ComponentFactory + hass: HomeAssistant, component_factory: ComponentFactory, current_request_with_host ) -> None: """Test binary sensor.""" in_bed_attribute = WITHINGS_MEASUREMENTS_MAP[Measurement.IN_BED] diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index a5946ff0533ce3..ef51f12398f5d9 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -74,6 +74,7 @@ async def test_webhook_post( arg_user_id: Any, arg_appli: Any, expected_code: int, + current_request_with_host, ) -> None: """Test webhook callback.""" person0 = new_profile_config("person0", user_id) @@ -107,6 +108,7 @@ async def test_webhook_head( hass: HomeAssistant, component_factory: ComponentFactory, aiohttp_client, + current_request_with_host, ) -> None: """Test head method on webhook view.""" person0 = new_profile_config("person0", 0) @@ -124,6 +126,7 @@ async def test_webhook_put( hass: HomeAssistant, component_factory: ComponentFactory, aiohttp_client, + current_request_with_host, ) -> None: """Test webhook callback.""" person0 = new_profile_config("person0", 0) diff --git a/tests/components/withings/test_config_flow.py b/tests/components/withings/test_config_flow.py index 8380c1340135da..4cbada948f4172 100644 --- a/tests/components/withings/test_config_flow.py +++ b/tests/components/withings/test_config_flow.py @@ -34,7 +34,7 @@ async def test_config_non_unique_profile(hass: HomeAssistant) -> None: async def test_config_reauth_profile( - hass: HomeAssistant, aiohttp_client, aioclient_mock + hass: HomeAssistant, aiohttp_client, aioclient_mock, current_request_with_host ) -> None: """Test reauth an existing profile re-creates the config entry.""" hass_config = { diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index a9948860745227..465c26deb32611 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -125,7 +125,10 @@ async def test_async_setup_no_config(hass: HomeAssistant) -> None: ], ) async def test_auth_failure( - hass: HomeAssistant, component_factory: ComponentFactory, exception: Exception + hass: HomeAssistant, + component_factory: ComponentFactory, + exception: Exception, + current_request_with_host, ) -> None: """Test auth failure.""" person0 = new_profile_config( diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index 8b51f62514d3ea..71e69967796e0a 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -302,7 +302,7 @@ def async_assert_state_equals( async def test_sensor_default_enabled_entities( - hass: HomeAssistant, component_factory: ComponentFactory + hass: HomeAssistant, component_factory: ComponentFactory, current_request_with_host ) -> None: """Test entities enabled by default.""" entity_registry: EntityRegistry = er.async_get(hass) @@ -343,7 +343,7 @@ async def test_sensor_default_enabled_entities( async def test_all_entities( - hass: HomeAssistant, component_factory: ComponentFactory + hass: HomeAssistant, component_factory: ComponentFactory, current_request_with_host ) -> None: """Test all entities.""" entity_registry: EntityRegistry = er.async_get(hass) diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 617c469069653b..3fad758b34b171 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -113,7 +113,9 @@ async def test_abort_if_no_implementation(hass, flow_handler): assert result["reason"] == "missing_configuration" -async def test_abort_if_authorization_timeout(hass, flow_handler, local_impl): +async def test_abort_if_authorization_timeout( + hass, flow_handler, local_impl, current_request_with_host +): """Check timeout generating authorization url.""" flow_handler.async_register_implementation(hass, local_impl) @@ -129,7 +131,9 @@ async def test_abort_if_authorization_timeout(hass, flow_handler, local_impl): assert result["reason"] == "authorize_url_timeout" -async def test_abort_if_no_url_available(hass, flow_handler, local_impl): +async def test_abort_if_no_url_available( + hass, flow_handler, local_impl, current_request_with_host +): """Check no_url_available generating authorization url.""" flow_handler.async_register_implementation(hass, local_impl) From ec1334099e9dd3d62db7e86e95c3f1789214d814 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 25 Mar 2021 19:07:45 +0100 Subject: [PATCH 1501/1818] Add tests for Netatmo data handler (#46373) * Add tests for Netatmo data handler * Clean up coveragerc * Move block to fixture * Minor update * Remove tests of implementation details for data handler * Update homeassistant/components/netatmo/data_handler.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/netatmo/data_handler.py Co-authored-by: Martin Hjelmare * Import callback Co-authored-by: Martin Hjelmare --- .coveragerc | 2 - .../components/netatmo/data_handler.py | 10 +++- tests/components/netatmo/test_camera.py | 50 ++++++++++++++++++- tests/components/netatmo/test_init.py | 5 +- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/.coveragerc b/.coveragerc index 712c5292e572a9..eaeee0f992d91f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -647,8 +647,6 @@ omit = homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* - homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/helper.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 358ae79edd2b4b..6982a651a45435 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -12,7 +12,7 @@ import pyatmo from homeassistant.config_entries import ConfigEntry -from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_time_interval @@ -98,6 +98,12 @@ async def async_update(self, event_time): self._queue.rotate(BATCH_SIZE) + @callback + def async_force_update(self, data_class_entry): + """Prioritize data retrieval for given data class entry.""" + self._data_classes[data_class_entry][NEXT_SCAN] = time() + self._queue.rotate(-(self._queue.index(self._data_classes[data_class_entry]))) + async def async_cleanup(self): """Clean up the Netatmo data handler.""" for listener in self.listeners: @@ -115,7 +121,7 @@ async def handle_event(self, event): elif event["data"]["push_type"] == "NACamera-connection": _LOGGER.debug("%s camera reconnected", MANUFACTURER) - self._data_classes[CAMERA_DATA_CLASS_NAME][NEXT_SCAN] = time() + self.async_force_update(CAMERA_DATA_CLASS_NAME) async def async_fetch_data(self, data_class, data_class_entry, **kwargs): """Fetch data and notify.""" diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index a58f2f3e1aae4f..10e3ca46b2a3a3 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -1,4 +1,5 @@ """The tests for Netatmo camera.""" +from datetime import timedelta from unittest.mock import patch from homeassistant.components import camera @@ -9,8 +10,11 @@ SERVICE_SET_PERSONS_HOME, ) from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.util import dt -from .common import simulate_webhook +from .common import fake_post_request, simulate_webhook + +from tests.common import async_fire_time_changed async def test_setup_component_with_webhook(hass, camera_entry): @@ -205,3 +209,47 @@ async def test_service_set_camera_light(hass, camera_entry): camera_id="12:34:56:00:a5:a4", floodlight="on", ) + + +async def test_camera_reconnect_webhook(hass, config_entry): + """Test webhook event on camera reconnect.""" + with patch( + "homeassistant.components.netatmo.api.ConfigEntryNetatmoAuth.post_request" + ) as mock_post, patch( + "homeassistant.components.netatmo.PLATFORMS", ["camera"] + ), patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), patch( + "homeassistant.components.webhook.async_generate_url" + ) as mock_webhook: + mock_post.side_effect = fake_post_request + mock_webhook.return_value = "https://example.com" + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + + webhook_id = config_entry.data[CONF_WEBHOOK_ID] + + # Fake webhook activation + response = { + "push_type": "webhook_activation", + } + await simulate_webhook(hass, webhook_id, response) + await hass.async_block_till_done() + + mock_post.assert_called() + mock_post.reset_mock() + + # Fake camera reconnect + response = { + "push_type": "NACamera-connection", + } + await simulate_webhook(hass, webhook_id, response) + await hass.async_block_till_done() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=60), + ) + await hass.async_block_till_done() + mock_post.assert_called() diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 49e3ab14684476..1ad07fe55d3bd7 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -152,7 +152,7 @@ async def test_setup_without_https(hass, config_entry): "homeassistant.components.webhook.async_generate_url" ) as mock_webhook: mock_auth.return_value.post_request.side_effect = fake_post_request - mock_webhook.return_value = "http://example.com" + mock_webhook.return_value = "https://example.com" assert await async_setup_component( hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} ) @@ -166,7 +166,8 @@ async def test_setup_without_https(hass, config_entry): climate_entity_livingroom = "climate.netatmo_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK) - assert hass.states.get(climate_entity_livingroom).state == "auto" + await hass.async_block_till_done() + assert hass.states.get(climate_entity_livingroom).state == "heat" async def test_setup_with_cloud(hass, config_entry): From e42ca35c947a9fadb0a201edf431d45f09ef3da8 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 25 Mar 2021 13:12:12 -0500 Subject: [PATCH 1502/1818] Bump plexwebsocket to 0.0.13 (#48330) --- homeassistant/components/plex/__init__.py | 7 +++---- homeassistant/components/plex/manifest.json | 2 +- homeassistant/components/plex/server.py | 6 +----- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/plex/helpers.py | 4 ++-- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index d84a86984e296d..296f1594549a74 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -7,7 +7,6 @@ from plexapi.gdm import GDM from plexwebsocket import ( SIGNAL_CONNECTION_STATE, - SIGNAL_DATA, STATE_CONNECTED, STATE_DISCONNECTED, STATE_STOPPED, @@ -158,9 +157,9 @@ async def async_setup_entry(hass, entry): hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) @callback - def plex_websocket_callback(signal, data, error): + def plex_websocket_callback(msgtype, data, error): """Handle callbacks from plexwebsocket library.""" - if signal == SIGNAL_CONNECTION_STATE: + if msgtype == SIGNAL_CONNECTION_STATE: if data == STATE_CONNECTED: _LOGGER.debug("Websocket to %s successful", entry.data[CONF_SERVER]) @@ -178,7 +177,7 @@ def plex_websocket_callback(signal, data, error): ) hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) - elif signal == SIGNAL_DATA: + elif msgtype == "playing": hass.async_create_task(plex_server.async_update_session(data)) session = async_get_clientsession(hass) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 9410fba1258d9b..e0e62d7150bf06 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -6,7 +6,7 @@ "requirements": [ "plexapi==4.5.1", "plexauth==0.0.6", - "plexwebsocket==0.0.12" + "plexwebsocket==0.0.13" ], "dependencies": ["http"], "codeowners": ["@jjlawren"] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index d1210e6f3d8871..841a9e7cc0da9f 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -257,11 +257,7 @@ def async_refresh_entity(self, machine_identifier, device, session, source): async def async_update_session(self, payload): """Process a session payload received from a websocket callback.""" - try: - session_payload = payload["PlaySessionStateNotification"][0] - except KeyError: - await self.async_update_platforms() - return + session_payload = payload["PlaySessionStateNotification"][0] state = session_payload["state"] if state == "buffering": diff --git a/requirements_all.txt b/requirements_all.txt index 6730162928e685..d40244074a9a8f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1140,7 +1140,7 @@ plexapi==4.5.1 plexauth==0.0.6 # homeassistant.components.plex -plexwebsocket==0.0.12 +plexwebsocket==0.0.13 # homeassistant.components.plugwise plugwise==0.8.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c44a19e997dc9d..149c156f4cc90f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -586,7 +586,7 @@ plexapi==4.5.1 plexauth==0.0.6 # homeassistant.components.plex -plexwebsocket==0.0.12 +plexwebsocket==0.0.13 # homeassistant.components.plugwise plugwise==0.8.5 diff --git a/tests/components/plex/helpers.py b/tests/components/plex/helpers.py index 2fca88fae27e39..35a01c3bfff14c 100644 --- a/tests/components/plex/helpers.py +++ b/tests/components/plex/helpers.py @@ -1,7 +1,7 @@ """Helper methods for Plex tests.""" from datetime import timedelta -from plexwebsocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA, STATE_CONNECTED +from plexwebsocket import SIGNAL_CONNECTION_STATE, STATE_CONNECTED import homeassistant.util.dt as dt_util @@ -29,7 +29,7 @@ def websocket_connected(mock_websocket): def trigger_plex_update(mock_websocket, payload=UPDATE_PAYLOAD): """Call the websocket callback method with a Plex update.""" callback = mock_websocket.call_args[0][1] - callback(SIGNAL_DATA, payload, None) + callback("playing", payload, None) async def wait_for_debouncer(hass): From 056f7d493c02999ec54b9be507c37dbc99500d22 Mon Sep 17 00:00:00 2001 From: Alexey Kustov Date: Thu, 25 Mar 2021 22:15:24 +0400 Subject: [PATCH 1503/1818] Support overriding token in notifify.event service (#47133) * Add opportunity to define token for each message * Update homeassistant/components/notify_events/notify.py Co-authored-by: Erik Montnemery Co-authored-by: Erik Montnemery --- homeassistant/components/notify_events/notify.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/notify_events/notify.py b/homeassistant/components/notify_events/notify.py index 23df01a128b78c..ce7c353badb3ab 100644 --- a/homeassistant/components/notify_events/notify.py +++ b/homeassistant/components/notify_events/notify.py @@ -28,6 +28,8 @@ ATTR_FILE_KIND_FILE = "file" ATTR_FILE_KIND_IMAGE = "image" +ATTR_TOKEN = "token" + _LOGGER = logging.getLogger(__name__) @@ -114,7 +116,12 @@ def prepare_message(self, message, data) -> Message: def send_message(self, message, **kwargs): """Send a message.""" + token = self.token data = kwargs.get(ATTR_DATA) or {} msg = self.prepare_message(message, data) - msg.send(self.token) + + if data.get(ATTR_TOKEN, "").trim(): + token = data[ATTR_TOKEN] + + msg.send(token) From 88b5eff726155d44064e4c954e00ef8e326c2046 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 25 Mar 2021 20:02:17 +0100 Subject: [PATCH 1504/1818] Fix late comment to PR adding percentage support to deCONZ fan platform (#48333) --- homeassistant/components/deconz/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 3d2e61fdd7b194..b8faa95a9b256f 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -81,7 +81,7 @@ def percentage(self) -> Optional[int]: if self._device.speed == 0: return 0 if self._device.speed not in ORDERED_NAMED_FAN_SPEEDS: - return + return None return ordered_list_item_to_percentage( ORDERED_NAMED_FAN_SPEEDS, self._device.speed ) From 1dc25a5864f13077e9e6f0d4765c02953a04730d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 25 Mar 2021 21:09:06 +0100 Subject: [PATCH 1505/1818] Bump python-typing-update to 0.3.2 (#48303) * Bump python-version-update to 0.3.2 * Changes after update * Fix pylint issues --- .pre-commit-config.yaml | 2 +- homeassistant/auth/permissions/models.py | 6 ++++-- homeassistant/components/deconz/fan.py | 5 ++--- homeassistant/components/yeelight/light.py | 6 ++++-- homeassistant/util/async_.py | 4 +++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ed5a8bd8d30a9..254ed637d81599 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,7 @@ repos: - id: prettier stages: [manual] - repo: https://github.com/cdce8p/python-typing-update - rev: v0.3.0 + rev: v0.3.2 hooks: # Run `python-typing-update` hook manually from time to time # to update python typing syntax. diff --git a/homeassistant/auth/permissions/models.py b/homeassistant/auth/permissions/models.py index b2d1955865a2ed..aa1a777ced26eb 100644 --- a/homeassistant/auth/permissions/models.py +++ b/homeassistant/auth/permissions/models.py @@ -1,4 +1,6 @@ """Models for permissions.""" +from __future__ import annotations + from typing import TYPE_CHECKING import attr @@ -14,5 +16,5 @@ class PermissionLookup: """Class to hold data for permission lookups.""" - entity_registry: "ent_reg.EntityRegistry" = attr.ib() - device_registry: "dev_reg.DeviceRegistry" = attr.ib() + entity_registry: ent_reg.EntityRegistry = attr.ib() + device_registry: dev_reg.DeviceRegistry = attr.ib() diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index b8faa95a9b256f..aca92f893c7bd1 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,6 +1,5 @@ """Support for deCONZ fans.""" - -from typing import Optional +from __future__ import annotations from homeassistant.components.fan import ( DOMAIN, @@ -76,7 +75,7 @@ def is_on(self) -> bool: return self._device.speed != 0 @property - def percentage(self) -> Optional[int]: + def percentage(self) -> int | None: """Return the current speed percentage.""" if self._device.speed == 0: return 0 diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 4c1cb9a0e82e60..218bcbbdb27e18 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -1,10 +1,13 @@ """Light platform support for yeelight.""" +from __future__ import annotations + from functools import partial import logging import voluptuous as vol import yeelight from yeelight import ( + Bulb, BulbException, Flow, RGBTransition, @@ -529,9 +532,8 @@ def effect(self): """Return the current effect.""" return self._effect - # F821: https://github.com/PyCQA/pyflakes/issues/373 @property - def _bulb(self) -> "Bulb": # noqa: F821 + def _bulb(self) -> Bulb: return self.device.bulb @property diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index f61225502ee2fb..0fd1b564f0d6fd 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -1,4 +1,6 @@ """Asyncio utilities.""" +from __future__ import annotations + from asyncio import Semaphore, coroutines, ensure_future, gather, get_running_loop from asyncio.events import AbstractEventLoop import concurrent.futures @@ -38,7 +40,7 @@ def callback() -> None: def run_callback_threadsafe( loop: AbstractEventLoop, callback: Callable[..., T], *args: Any -) -> "concurrent.futures.Future[T]": +) -> concurrent.futures.Future[T]: # pylint: disable=unsubscriptable-object """Submit a callback object to a given event loop. Return a concurrent.futures.Future to access the result. From d5afd0afb3205f5ccf029142e2a26a9617f100d8 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 26 Mar 2021 00:04:15 +0000 Subject: [PATCH 1506/1818] [ci skip] Translation update --- .../accuweather/translations/nl.json | 3 +- .../components/airnow/translations/nl.json | 4 +- .../components/august/translations/nl.json | 16 ++++++++ .../bmw_connected_drive/translations/nl.json | 10 +++++ .../components/broadlink/translations/nl.json | 8 +++- .../components/cast/translations/ca.json | 4 +- .../components/cast/translations/et.json | 4 +- .../components/cast/translations/it.json | 4 +- .../components/cast/translations/nl.json | 4 +- .../components/cast/translations/ru.json | 4 +- .../components/gios/translations/nl.json | 5 +++ .../components/hive/translations/nl.json | 4 ++ .../home_plus_control/translations/ca.json | 21 ++++++++++ .../home_plus_control/translations/en.json | 10 ++++- .../home_plus_control/translations/et.json | 21 ++++++++++ .../home_plus_control/translations/it.json | 21 ++++++++++ .../home_plus_control/translations/nl.json | 21 ++++++++++ .../home_plus_control/translations/ru.json | 21 ++++++++++ .../components/hyperion/translations/nl.json | 14 +++++++ .../components/kodi/translations/nl.json | 6 +++ .../lutron_caseta/translations/nl.json | 1 + .../motion_blinds/translations/nl.json | 7 +++- .../opentherm_gw/translations/nl.json | 1 + .../components/ozw/translations/nl.json | 13 +++++++ .../philips_js/translations/nl.json | 7 ++++ .../components/plaato/translations/nl.json | 1 + .../progettihwsw/translations/nl.json | 3 +- .../rainmachine/translations/nl.json | 3 ++ .../recollect_waste/translations/nl.json | 3 ++ .../components/rfxtrx/translations/nl.json | 3 +- .../components/risco/translations/nl.json | 15 +++++-- .../screenlogic/translations/nl.json | 39 +++++++++++++++++++ .../components/sentry/translations/nl.json | 6 ++- .../somfy_mylink/translations/nl.json | 13 ++++++- .../srp_energy/translations/nl.json | 1 + .../components/tuya/translations/nl.json | 2 + .../components/verisure/translations/nl.json | 3 +- .../water_heater/translations/nl.json | 1 + 38 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/home_plus_control/translations/ca.json create mode 100644 homeassistant/components/home_plus_control/translations/et.json create mode 100644 homeassistant/components/home_plus_control/translations/it.json create mode 100644 homeassistant/components/home_plus_control/translations/nl.json create mode 100644 homeassistant/components/home_plus_control/translations/ru.json create mode 100644 homeassistant/components/screenlogic/translations/nl.json diff --git a/homeassistant/components/accuweather/translations/nl.json b/homeassistant/components/accuweather/translations/nl.json index 342df3cca78eab..f04d93b5921f72 100644 --- a/homeassistant/components/accuweather/translations/nl.json +++ b/homeassistant/components/accuweather/translations/nl.json @@ -34,7 +34,8 @@ }, "system_health": { "info": { - "can_reach_server": "Kan AccuWeather server bereiken" + "can_reach_server": "Kan AccuWeather server bereiken", + "remaining_requests": "Resterende toegestane verzoeken" } } } \ No newline at end of file diff --git a/homeassistant/components/airnow/translations/nl.json b/homeassistant/components/airnow/translations/nl.json index 011498269f89a2..090a5363823cf0 100644 --- a/homeassistant/components/airnow/translations/nl.json +++ b/homeassistant/components/airnow/translations/nl.json @@ -14,8 +14,10 @@ "data": { "api_key": "API-sleutel", "latitude": "Breedtegraad", - "longitude": "Lengtegraad" + "longitude": "Lengtegraad", + "radius": "Stationsradius (mijl; optioneel)" }, + "description": "AirNow luchtkwaliteit integratie opzetten. Om een API sleutel te genereren ga naar https://docs.airnowapi.org/account/request/", "title": "AirNow" } } diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 41af77b0c7c0fc..05a5a4c52651d4 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -10,6 +10,13 @@ "unknown": "Onverwachte fout" }, "step": { + "reauth_validate": { + "data": { + "password": "Wachtwoord" + }, + "description": "Voer het wachtwoord in voor {username} .", + "title": "Verifieer een August-account opnieuw" + }, "user": { "data": { "login_method": "Aanmeldmethode", @@ -20,6 +27,15 @@ "description": "Als de aanmeldingsmethode 'e-mail' is, is gebruikersnaam het e-mailadres. Als de aanmeldingsmethode 'telefoon' is, is gebruikersnaam het telefoonnummer in de indeling '+ NNNNNNNNN'.", "title": "Stel een augustus-account in" }, + "user_validate": { + "data": { + "login_method": "Inlogmethode", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "Als de aanmeldingsmethode 'e-mail' is, is de gebruikersnaam het e-mailadres. Als de aanmeldingsmethode 'telefoon' is, is de gebruikersnaam het telefoonnummer in de indeling '+ NNNNNNNNN'.", + "title": "Stel een August account in" + }, "validation": { "data": { "code": "Verificatiecode" diff --git a/homeassistant/components/bmw_connected_drive/translations/nl.json b/homeassistant/components/bmw_connected_drive/translations/nl.json index 83ae0b9ff7d012..8fa6c839112f7d 100644 --- a/homeassistant/components/bmw_connected_drive/translations/nl.json +++ b/homeassistant/components/bmw_connected_drive/translations/nl.json @@ -16,5 +16,15 @@ } } } + }, + "options": { + "step": { + "account_options": { + "data": { + "read_only": "Alleen-lezen (alleen sensoren en notificatie, geen uitvoering van diensten, geen vergrendeling)", + "use_location": "Gebruik Home Assistant locatie voor auto locatie peilingen (vereist voor niet i3/i8 voertuigen geproduceerd voor 7/2014)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json index 138caf9a5b7707..06c26235d0a0c5 100644 --- a/homeassistant/components/broadlink/translations/nl.json +++ b/homeassistant/components/broadlink/translations/nl.json @@ -15,6 +15,9 @@ }, "flow_title": "{name} ({model} bij {host})", "step": { + "auth": { + "title": "Authenticeer naar het apparaat" + }, "finish": { "data": { "name": "Naam" @@ -22,12 +25,15 @@ "title": "Kies een naam voor het apparaat" }, "reset": { + "description": "{name} ( {model} op {host} ) is vergrendeld. U moet het apparaat ontgrendelen om te verifi\u00ebren en de configuratie te voltooien. Instructies:\n 1. Open de Broadlink-app.\n 2. Klik op het apparaat.\n 3. Klik op '...' in de rechterbovenhoek.\n 4. Scrol naar de onderkant van de pagina.\n 5. Schakel het slot uit.", "title": "Ontgrendel het apparaat" }, "unlock": { "data": { "unlock": "Ja, doe het." - } + }, + "description": "{name} ( {model} op {host} ) is vergrendeld. Dit kan leiden tot authenticatieproblemen in Home Assistant. Wilt u deze ontgrendelen?", + "title": "Ontgrendel het apparaat (optioneel)" }, "user": { "data": { diff --git a/homeassistant/components/cast/translations/ca.json b/homeassistant/components/cast/translations/ca.json index 9cb55f4d7314ab..6a5d16aa6bb8c2 100644 --- a/homeassistant/components/cast/translations/ca.json +++ b/homeassistant/components/cast/translations/ca.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar." + "ignore_cec": "Llista opcional que es passar\u00e0 a pychromecast.IGNORE_CEC.", + "known_hosts": "Llista opcional d'amfitrions coneguts per si el descobriment mDNS deixa de funcionar.", + "uuid": "Llista opcional d'UUIDs. No s'afegiran 'casts' que no siguin a la llista." }, "description": "Introdueix la configuraci\u00f3 de Google Cast." } diff --git a/homeassistant/components/cast/translations/et.json b/homeassistant/components/cast/translations/et.json index 9e126d50af0ab0..6397951272a059 100644 --- a/homeassistant/components/cast/translations/et.json +++ b/homeassistant/components/cast/translations/et.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta." + "ignore_cec": "Valikuline nimekiri mis edastatakse pychromecast.IGNORE_CEC-ile.", + "known_hosts": "Valikuline loend teadaolevatest hostidest kui mDNS-i tuvastamine ei t\u00f6\u00f6ta.", + "uuid": "Valikuline UUIDide loend. Loetlemata cast-e ei lisata." }, "description": "Sisesta Google Casti andmed." } diff --git a/homeassistant/components/cast/translations/it.json b/homeassistant/components/cast/translations/it.json index 17abade539edee..83586bf9f2cfd1 100644 --- a/homeassistant/components/cast/translations/it.json +++ b/homeassistant/components/cast/translations/it.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona." + "ignore_cec": "Elenco opzionale che sar\u00e0 passato a pychromecast.IGNORE_CEC.", + "known_hosts": "Elenco facoltativo di host noti se l'individuazione di mDNS non funziona.", + "uuid": "Elenco opzionale di UUID. I cast non elencati non saranno aggiunti." }, "description": "Inserisci la configurazione di Google Cast." } diff --git a/homeassistant/components/cast/translations/nl.json b/homeassistant/components/cast/translations/nl.json index 5c6cfae7c6bc7d..02bf7514761b30 100644 --- a/homeassistant/components/cast/translations/nl.json +++ b/homeassistant/components/cast/translations/nl.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt." + "ignore_cec": "Optionele lijst die zal worden doorgegeven aan pychromecast.IGNORE_CEC.", + "known_hosts": "Optionele lijst van bekende hosts indien mDNS discovery niet werkt.", + "uuid": "Optionele lijst van UUID's. Casts die niet in de lijst staan, worden niet toegevoegd." }, "description": "Voer de Google Cast configuratie in." } diff --git a/homeassistant/components/cast/translations/ru.json b/homeassistant/components/cast/translations/ru.json index e565cedbfadb95..7c412476151865 100644 --- a/homeassistant/components/cast/translations/ru.json +++ b/homeassistant/components/cast/translations/ru.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442." + "ignore_cec": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d \u0432 pychromecast.IGNORE_CEC.", + "known_hosts": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u043e\u0432, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435 mDNS \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.", + "uuid": "\u041d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a UUID. \u041d\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442." }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Google Cast." } diff --git a/homeassistant/components/gios/translations/nl.json b/homeassistant/components/gios/translations/nl.json index 7224c29f318c59..baac3c6dc77ab3 100644 --- a/homeassistant/components/gios/translations/nl.json +++ b/homeassistant/components/gios/translations/nl.json @@ -18,5 +18,10 @@ "title": "GIO\u015a (Poolse hoofdinspectie van milieubescherming)" } } + }, + "system_health": { + "info": { + "can_reach_server": "Bereik GIO\u015a server" + } } } \ No newline at end of file diff --git a/homeassistant/components/hive/translations/nl.json b/homeassistant/components/hive/translations/nl.json index 96ea799c0fe8ee..3ac45ae14d7f4c 100644 --- a/homeassistant/components/hive/translations/nl.json +++ b/homeassistant/components/hive/translations/nl.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol", "unknown_entry": "Kan bestaand item niet vinden." }, "error": { @@ -16,6 +17,7 @@ "data": { "2fa": "Tweefactorauthenticatiecode" }, + "description": "Voer uw Hive-verificatiecode in. \n \n Voer code 0000 in om een andere code aan te vragen.", "title": "Hive tweefactorauthenticatie" }, "reauth": { @@ -32,6 +34,7 @@ "scan_interval": "Scaninterval (seconden)", "username": "Gebruikersnaam" }, + "description": "Voer uw Hive login informatie en configuratie in.", "title": "Hive-aanmelding" } } @@ -42,6 +45,7 @@ "data": { "scan_interval": "Scaninterval (seconden)" }, + "description": "Werk het scaninterval bij om vaker naar gegevens te vragen.", "title": "Opties voor Hive" } } diff --git a/homeassistant/components/home_plus_control/translations/ca.json b/homeassistant/components/home_plus_control/translations/ca.json new file mode 100644 index 00000000000000..90e23fcd7ab089 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/ca.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", + "missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3.", + "no_url_available": "No hi ha cap URL disponible. Per a m\u00e9s informaci\u00f3 sobre aquest error, [consulta la secci\u00f3 d'ajuda]({docs_url})", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "create_entry": { + "default": "Autenticaci\u00f3 exitosa" + }, + "step": { + "pick_implementation": { + "title": "Selecciona el m\u00e8tode d'autenticaci\u00f3" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/en.json b/homeassistant/components/home_plus_control/translations/en.json index 41232f4b1a75f8..f5f8afe73d153f 100644 --- a/homeassistant/components/home_plus_control/translations/en.json +++ b/homeassistant/components/home_plus_control/translations/en.json @@ -1,14 +1,20 @@ { "config": { "abort": { + "already_configured": "Account is already configured", + "already_in_progress": "Configuration flow is already in progress", "authorize_url_timeout": "Timeout generating authorize URL.", "missing_configuration": "The component is not configured. Please follow the documentation.", "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", - "single_instance_allowed": "Integration is already being configured in another instance. Only one is allowed at any one time.", - "oauth_error": "Error in the authentication flow." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "Successfully authenticated" + }, + "step": { + "pick_implementation": { + "title": "Pick Authentication Method" + } } }, "title": "Legrand Home+ Control" diff --git a/homeassistant/components/home_plus_control/translations/et.json b/homeassistant/components/home_plus_control/translations/et.json new file mode 100644 index 00000000000000..0046c1f5205003 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Kasutaja on juba seadistatud", + "already_in_progress": "Seadistamine on juba k\u00e4imas", + "authorize_url_timeout": "Kinnitus-URLi loomise ajal\u00f5pp", + "missing_configuration": "Komponent pole seadistatud. Palun loe dokumentatsiooni.", + "no_url_available": "URL-i pole saadaval. Selle t\u00f5rke kohta teabe saamiseks vaata [spikrijaotis]({docs_url})", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "create_entry": { + "default": "Tuvastamine \u00f5nnestus" + }, + "step": { + "pick_implementation": { + "title": "Vali tuvastusmeetod" + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/it.json b/homeassistant/components/home_plus_control/translations/it.json new file mode 100644 index 00000000000000..789a7db85ebf19 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/it.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "create_entry": { + "default": "Autenticazione riuscita" + }, + "step": { + "pick_implementation": { + "title": "Scegli il metodo di autenticazione" + } + } + }, + "title": "Legrand Home + Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/nl.json b/homeassistant/components/home_plus_control/translations/nl.json new file mode 100644 index 00000000000000..9d448e480a104b --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Account is al geconfigureerd", + "already_in_progress": "De configuratiestroom is al aan de gang", + "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", + "missing_configuration": "De Netatmo-component is niet geconfigureerd. Gelieve de documentatie volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})", + "single_instance_allowed": "Al geconfigureerd. Slechts een enkele configuratie mogelijk." + }, + "create_entry": { + "default": "Succesvol geauthenticeerd" + }, + "step": { + "pick_implementation": { + "title": "Kies een authenticatie methode" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/home_plus_control/translations/ru.json b/homeassistant/components/home_plus_control/translations/ru.json new file mode 100644 index 00000000000000..fd3da6929d8321 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/ru.json @@ -0,0 +1,21 @@ +{ + "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_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.", + "authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", + "missing_configuration": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438.", + "no_url_available": "URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435.", + "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": { + "default": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." + }, + "step": { + "pick_implementation": { + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/hyperion/translations/nl.json b/homeassistant/components/hyperion/translations/nl.json index cb2de7e845dc66..056971b435faeb 100644 --- a/homeassistant/components/hyperion/translations/nl.json +++ b/homeassistant/components/hyperion/translations/nl.json @@ -26,6 +26,10 @@ "description": "Wilt u deze Hyperion Ambilight toevoegen aan Home Assistant? \n\n ** Host: ** {host}\n ** Poort: ** {port}\n ** ID **: {id}", "title": "Bevestig de toevoeging van Hyperion Ambilight-service" }, + "create_token": { + "description": "Kies **Submit** hieronder om een nieuw authenticatie token aan te vragen. U wordt doorgestuurd naar de Hyperion UI om de aanvraag goed te keuren. Controleer of de getoonde id \"{auth_id}\" is.", + "title": "Automatisch nieuw authenticatie token aanmaken" + }, "create_token_external": { "title": "Accepteer nieuwe token in Hyperion UI" }, @@ -36,5 +40,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "effect_show_list": "Hyperion-effecten om te laten zien", + "priority": "Hyperion prioriteit te gebruiken voor kleuren en effecten" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json index 95d8ea995d5e77..4143d933d19114 100644 --- a/homeassistant/components/kodi/translations/nl.json +++ b/homeassistant/components/kodi/translations/nl.json @@ -40,5 +40,11 @@ "description": "De WebSocket-poort (ook wel TCP-poort genoemd in Kodi). Om verbinding te maken via WebSocket, moet u \"Programma's toestaan ... om Kodi te besturen\" inschakelen in Systeem / Instellingen / Netwerk / Services. Als WebSocket niet is ingeschakeld, verwijdert u de poort en laat u deze leeg." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} werd gevraagd om uit te schakelen", + "turn_on": "{entity_name} is gevraagd om in te schakelen" + } } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index d74a18622d0071..4b97da2058b2a5 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -15,6 +15,7 @@ "title": "Het importeren van de Cas\u00e9ta bridge configuratie is mislukt." }, "link": { + "description": "Om te koppelen met {naam} ({host}), na het verzenden van dit formulier, druk op de zwarte knop op de achterkant van de brug.", "title": "Koppel met de bridge" }, "user": { diff --git a/homeassistant/components/motion_blinds/translations/nl.json b/homeassistant/components/motion_blinds/translations/nl.json index 01cb117bb5b84e..54baeb9e18d29e 100644 --- a/homeassistant/components/motion_blinds/translations/nl.json +++ b/homeassistant/components/motion_blinds/translations/nl.json @@ -8,24 +8,29 @@ "error": { "discovery_error": "Kan geen Motion Gateway vinden" }, + "flow_title": "Motion Blinds", "step": { "connect": { "data": { "api_key": "API-sleutel" }, + "description": "U hebt de API-sleutel van 16 tekens nodig, zie https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key voor instructies", "title": "Motion Blinds" }, "select": { "data": { "select_ip": "IP-adres" }, + "description": "Voer de installatie opnieuw uit als u extra Motion Gateways wilt aansluiten", "title": "Selecteer de Motion Gateway waarmee u verbinding wilt maken" }, "user": { "data": { "api_key": "API-sleutel", "host": "IP-adres" - } + }, + "description": "Maak verbinding met uw Motion Gateway, als het IP-adres niet is ingesteld, wordt auto-discovery gebruikt", + "title": "Motion Blinds" } } } diff --git a/homeassistant/components/opentherm_gw/translations/nl.json b/homeassistant/components/opentherm_gw/translations/nl.json index 785d09fdfeb125..bdd3337d05b500 100644 --- a/homeassistant/components/opentherm_gw/translations/nl.json +++ b/homeassistant/components/opentherm_gw/translations/nl.json @@ -22,6 +22,7 @@ "data": { "floor_temperature": "Vloertemperatuur", "precision": "Precisie", + "read_precision": "Lees Precisie", "set_precision": "Precisie instellen" }, "description": "Opties voor de OpenTherm Gateway" diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index 3026427e8f17ab..cb443f3a27a140 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -1,11 +1,20 @@ { "config": { "abort": { + "addon_info_failed": "Mislukt om OpenZWave add-on info te krijgen.", + "addon_install_failed": "De installatie van de OpenZWave add-on is mislukt.", + "addon_set_config_failed": "Mislukt om OpenZWave configuratie in te stellen.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", "mqtt_required": "De [%%] integratie is niet ingesteld", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, + "error": { + "addon_start_failed": "Het starten van de OpenZWave-add-on is mislukt. Controleer de configuratie." + }, + "progress": { + "install_addon": "Wacht even terwijl de installatie van de OpenZWave add-on wordt voltooid. Dit kan enkele minuten duren." + }, "step": { "hassio_confirm": { "title": "OpenZWave integratie instellen met de OpenZWave add-on" @@ -14,6 +23,10 @@ "title": "De OpenZWave add-on installatie is gestart" }, "on_supervisor": { + "data": { + "use_addon": "Gebruik de OpenZWave Supervisor add-on" + }, + "description": "Wilt u de OpenZWave Supervisor add-on gebruiken?", "title": "Selecteer een verbindingsmethode" }, "start_addon": { diff --git a/homeassistant/components/philips_js/translations/nl.json b/homeassistant/components/philips_js/translations/nl.json index 108d158f1c5eac..34497d285fa6e9 100644 --- a/homeassistant/components/philips_js/translations/nl.json +++ b/homeassistant/components/philips_js/translations/nl.json @@ -10,6 +10,13 @@ "unknown": "Onverwachte fout" }, "step": { + "pair": { + "data": { + "pin": "PIN-code" + }, + "description": "Voer de pincode in die op uw tv wordt weergegeven", + "title": "Koppel" + }, "user": { "data": { "api_version": "API Versie", diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 6c09818594c62a..707830c33a5d78 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -19,6 +19,7 @@ "token": "Plak hier de verificatie-token", "use_webhook": "Webhook gebruiken" }, + "description": "Om de API te kunnenopvragen is een `auth_token` nodig, die kan worden verkregen door [deze] (https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token) instructies te volgen\n\n Geselecteerd apparaat: **{device_type}** \n\nIndien u liever de ingebouwde webhook methode gebruikt (alleen Airlock) vink dan het vakje hieronder aan en laat Auth Token leeg", "title": "Selecteer API-methode" }, "user": { diff --git a/homeassistant/components/progettihwsw/translations/nl.json b/homeassistant/components/progettihwsw/translations/nl.json index 7810a8018a42ed..64eb0d1271758f 100644 --- a/homeassistant/components/progettihwsw/translations/nl.json +++ b/homeassistant/components/progettihwsw/translations/nl.json @@ -33,7 +33,8 @@ "data": { "host": "Host", "port": "Poort" - } + }, + "title": "Stel het bord in" } } } diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index 119e4c641afdf0..8b767ced6c0a43 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -20,6 +20,9 @@ "options": { "step": { "init": { + "data": { + "zone_run_time": "Standaardlooptijd van de zone (in seconden)" + }, "title": "Configureer RainMachine" } } diff --git a/homeassistant/components/recollect_waste/translations/nl.json b/homeassistant/components/recollect_waste/translations/nl.json index 6ce4a8f8a9f342..eec63605267ba2 100644 --- a/homeassistant/components/recollect_waste/translations/nl.json +++ b/homeassistant/components/recollect_waste/translations/nl.json @@ -18,6 +18,9 @@ "options": { "step": { "init": { + "data": { + "friendly_name": "Gebruik vriendelijke namen voor afhaaltypes (indien mogelijk)" + }, "title": "Configureer Recollect Waste" } } diff --git a/homeassistant/components/rfxtrx/translations/nl.json b/homeassistant/components/rfxtrx/translations/nl.json index 53441859d72c6f..1d22751ceed7ff 100644 --- a/homeassistant/components/rfxtrx/translations/nl.json +++ b/homeassistant/components/rfxtrx/translations/nl.json @@ -64,7 +64,8 @@ "off_delay": "Uitschakelvertraging", "off_delay_enabled": "Schakel uitschakelvertraging in", "replace_device": "Selecteer apparaat dat u wilt vervangen", - "signal_repetitions": "Aantal signaalherhalingen" + "signal_repetitions": "Aantal signaalherhalingen", + "venetian_blind_mode": "Venetiaanse jaloezie modus" }, "title": "Configureer apparaatopties" } diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json index 97d0d454a4f0c2..5267b164f3f756 100644 --- a/homeassistant/components/risco/translations/nl.json +++ b/homeassistant/components/risco/translations/nl.json @@ -26,12 +26,15 @@ "armed_custom_bypass": "Ingeschakeld met overbrugging(en)", "armed_home": "Ingeschakeld thuis", "armed_night": "Ingeschakeld nacht" - } + }, + "description": "Selecteer in welke staat u uw Risco-alarm wilt instellen wanneer u het Home Assistant-alarm inschakelt", + "title": "Wijs Home Assistant-staten toe aan Risco-staten" }, "init": { "data": { "code_arm_required": "PIN-code vereist om in te schakelen", - "code_disarm_required": "PIN-code vereist om uit te schakelen" + "code_disarm_required": "PIN-code vereist om uit te schakelen", + "scan_interval": "Polling-interval (in seconden)" }, "title": "Configureer opties" }, @@ -40,8 +43,12 @@ "A": "Groep A", "B": "Groep B", "C": "Groep C", - "D": "Groep D" - } + "D": "Groep D", + "arm": "Ingeschakeld (AFWEZIG)", + "partial_arm": "Gedeeltelijk ingeschakeld (AANWEZIG)" + }, + "description": "Selecteer welke staat uw Home Assistant alarm zal melden voor elke staat gemeld door Risco", + "title": "Wijs Risco-staten toe aan Home Assistant-staten" } } } diff --git a/homeassistant/components/screenlogic/translations/nl.json b/homeassistant/components/screenlogic/translations/nl.json new file mode 100644 index 00000000000000..7c752e0ae4d753 --- /dev/null +++ b/homeassistant/components/screenlogic/translations/nl.json @@ -0,0 +1,39 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Kan geen verbinding maken" + }, + "flow_title": "ScreenLogic {name}", + "step": { + "gateway_entry": { + "data": { + "ip_address": "IP-adres", + "port": "Poort" + }, + "description": "Voer uw ScreenLogic Gateway informatie in.", + "title": "ScreenLogic" + }, + "gateway_select": { + "data": { + "selected_gateway": "Gateway" + }, + "description": "De volgende ScreenLogic gateways werden ontdekt. Selecteer er een om te configureren, of kies ervoor om handmatig een ScreenLogic gateway te configureren.", + "title": "ScreenLogic" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Seconden tussen scans" + }, + "description": "Geef instellingen op voor {gateway_name}", + "title": "ScreenLogic" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/nl.json b/homeassistant/components/sentry/translations/nl.json index 682517c15dbb52..53f54ac1968409 100644 --- a/homeassistant/components/sentry/translations/nl.json +++ b/homeassistant/components/sentry/translations/nl.json @@ -24,7 +24,11 @@ "environment": "Optionele naam van de omgeving.", "event_custom_components": "Gebeurtenissen verzenden vanuit aangepaste onderdelen", "event_handled": "Stuur afgehandelde gebeurtenissen", - "event_third_party_packages": "Gebeurtenissen verzenden vanuit pakketten van derden" + "event_third_party_packages": "Gebeurtenissen verzenden vanuit pakketten van derden", + "logging_event_level": "Het logniveau waarvoor Sentry een gebeurtenis registreert", + "logging_level": "Het logniveau Sentry zal logs opnemen als broodkruimels voor", + "tracing": "Schakel prestatietracering in", + "tracing_sample_rate": "Tracering van de steekproefsnelheid; tussen 0,0 en 1,0 (1,0 = 100%)" } } } diff --git a/homeassistant/components/somfy_mylink/translations/nl.json b/homeassistant/components/somfy_mylink/translations/nl.json index 1e9d5a58f89d66..b900e46bee4d97 100644 --- a/homeassistant/components/somfy_mylink/translations/nl.json +++ b/homeassistant/components/somfy_mylink/translations/nl.json @@ -15,7 +15,8 @@ "host": "Host", "port": "Poort", "system_id": "Systeem-ID" - } + }, + "description": "De systeem-id kan worden verkregen in de MyLink app onder Integratie door een niet-Cloud service te selecteren." } } }, @@ -25,16 +26,24 @@ }, "step": { "entity_config": { + "data": { + "reverse": "Cover is omgekeerd" + }, "description": "Configureer opties voor `{entity_id}`", "title": "Entiteit configureren" }, "init": { "data": { - "entity_id": "Configureer een specifieke entiteit." + "default_reverse": "Standaard omkeerstatus voor niet-geconfigureerde covers", + "entity_id": "Configureer een specifieke entiteit.", + "target_id": "Configureer opties voor een cover." }, "title": "Configureer MyLink-opties" }, "target_config": { + "data": { + "reverse": "Cover is omgekeerd" + }, "description": "Configureer opties voor ' {target_name} '", "title": "Configureer MyLink Cover" } diff --git a/homeassistant/components/srp_energy/translations/nl.json b/homeassistant/components/srp_energy/translations/nl.json index d1df1796f3a643..91bdc3592b673b 100644 --- a/homeassistant/components/srp_energy/translations/nl.json +++ b/homeassistant/components/srp_energy/translations/nl.json @@ -13,6 +13,7 @@ "user": { "data": { "id": "Account ID", + "is_tou": "Is tijd van gebruik plan", "password": "Wachtwoord", "username": "Gebruikersnaam" } diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 8f8117144113b1..b42922822f0feb 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -40,6 +40,7 @@ "max_temp": "Maximale doeltemperatuur (gebruik min en max = 0 voor standaardwaarde)", "min_kelvin": "Minimaal ondersteunde kleurtemperatuur in kelvin", "min_temp": "Min. gewenste temperatuur (gebruik min en max = 0 voor standaard)", + "set_temp_divided": "Gedeelde temperatuurwaarde gebruiken voor ingestelde temperatuuropdracht", "support_color": "Forceer kleurenondersteuning", "temp_divider": "Temperatuurwaarde deler (0 = standaardwaarde)", "temp_step_override": "Doeltemperatuur stap", @@ -56,6 +57,7 @@ "query_device": "Selecteer apparaat dat query-methode zal gebruiken voor snellere statusupdate", "query_interval": "Peilinginterval van het apparaat in seconden" }, + "description": "Stel de waarden voor het pollinginterval niet te laag in, anders zullen de oproepen geen foutmelding in het logboek genereren", "title": "Configureer Tuya opties" } } diff --git a/homeassistant/components/verisure/translations/nl.json b/homeassistant/components/verisure/translations/nl.json index 806231dbe4aaaa..d0519e584fd886 100644 --- a/homeassistant/components/verisure/translations/nl.json +++ b/homeassistant/components/verisure/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account is al geconfigureerd" + "already_configured": "Account is al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "invalid_auth": "Ongeldige authenticatie", diff --git a/homeassistant/components/water_heater/translations/nl.json b/homeassistant/components/water_heater/translations/nl.json index f70a5efc3e7f60..4a2d135718825f 100644 --- a/homeassistant/components/water_heater/translations/nl.json +++ b/homeassistant/components/water_heater/translations/nl.json @@ -11,6 +11,7 @@ "electric": "Elektriciteit", "gas": "Gas", "heat_pump": "Warmtepomp", + "high_demand": "Hoge vraag", "off": "Uit", "performance": "Prestaties" } From b4d39d517f945e85bf886cdd629ba320dffb4e54 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 26 Mar 2021 04:06:40 +0100 Subject: [PATCH 1507/1818] Update in 1 minute on unavailable Motion blinds (#47800) * if unavailable request update in 1 minute * fix styling * improve changing update interval * remove unused import * try to fix * remove unused pass * add const * fix missing timedelta * update to motionblinds 0.4.10 * improve update coordinator * fix linting errors * remove unused import * move update functions within the DataUpdateCoordinator * fix white space --- .../components/motion_blinds/__init__.py | 89 +++++++++++++++---- .../components/motion_blinds/const.py | 4 + .../components/motion_blinds/cover.py | 9 +- .../components/motion_blinds/manifest.json | 2 +- .../components/motion_blinds/sensor.py | 18 +++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 103 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index f57d31b47d37b4..d4a5dd1f79bb4e 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -1,11 +1,11 @@ """The motion_blinds component.""" import asyncio -from contextlib import suppress from datetime import timedelta import logging from socket import timeout from motionblinds import MotionMulticast +from motionblinds.motion_blinds import ParseException from homeassistant import config_entries, core from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP @@ -14,18 +14,87 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( + ATTR_AVAILABLE, DOMAIN, KEY_COORDINATOR, KEY_GATEWAY, KEY_MULTICAST_LISTENER, MANUFACTURER, PLATFORMS, + UPDATE_INTERVAL, + UPDATE_INTERVAL_FAST, ) from .gateway import ConnectMotionGateway _LOGGER = logging.getLogger(__name__) +class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator): + """Class to manage fetching data from single endpoint.""" + + def __init__( + self, + hass, + logger, + gateway, + *, + name, + update_interval=None, + update_method=None, + ): + """Initialize global data updater.""" + super().__init__( + hass, + logger, + name=name, + update_method=update_method, + update_interval=update_interval, + ) + + self._gateway = gateway + + def update_gateway(self): + """Call all updates using one async_add_executor_job.""" + data = {} + + try: + self._gateway.Update() + except (timeout, ParseException): + # let the error be logged and handled by the motionblinds library + data[KEY_GATEWAY] = {ATTR_AVAILABLE: False} + return data + else: + data[KEY_GATEWAY] = {ATTR_AVAILABLE: True} + + for blind in self._gateway.device_list.values(): + try: + blind.Update() + except (timeout, ParseException): + # let the error be logged and handled by the motionblinds library + data[blind.mac] = {ATTR_AVAILABLE: False} + else: + data[blind.mac] = {ATTR_AVAILABLE: True} + + return data + + async def _async_update_data(self): + """Fetch the latest data from the gateway and blinds.""" + data = await self.hass.async_add_executor_job(self.update_gateway) + + all_available = True + for device in data.values(): + if not device[ATTR_AVAILABLE]: + all_available = False + break + + if all_available: + self.update_interval = timedelta(seconds=UPDATE_INTERVAL) + else: + self.update_interval = timedelta(seconds=UPDATE_INTERVAL_FAST) + + return data + + def setup(hass: core.HomeAssistant, config: dict): """Set up the Motion Blinds component.""" return True @@ -61,26 +130,14 @@ def stop_motion_multicast(event): raise ConfigEntryNotReady motion_gateway = connect_gateway_class.gateway_device - def update_gateway(): - """Call all updates using one async_add_executor_job.""" - motion_gateway.Update() - for blind in motion_gateway.device_list.values(): - with suppress(timeout): - blind.Update() - - async def async_update_data(): - """Fetch data from the gateway and blinds.""" - with suppress(timeout): # Let the error be handled by the motionblinds - await hass.async_add_executor_job(update_gateway) - - coordinator = DataUpdateCoordinator( + coordinator = DataUpdateCoordinatorMotionBlinds( hass, _LOGGER, + motion_gateway, # Name of the data. For logging purposes. name=entry.title, - update_method=async_update_data, # Polling interval. Will only be polled if there are subscribers. - update_interval=timedelta(seconds=600), + update_interval=timedelta(seconds=UPDATE_INTERVAL), ) # Fetch initial data so we have data when entities subscribe diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index 4b1f0ee052747b..52c6e39b096c72 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -11,5 +11,9 @@ ATTR_WIDTH = "width" ATTR_ABSOLUTE_POSITION = "absolute_position" +ATTR_AVAILABLE = "available" SERVICE_SET_ABSOLUTE_POSITION = "set_absolute_position" + +UPDATE_INTERVAL = 600 +UPDATE_INTERVAL_FAST = 60 diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index 4ac09a5fd1108b..2c4fee5f8aa559 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -21,6 +21,7 @@ from .const import ( ATTR_ABSOLUTE_POSITION, + ATTR_AVAILABLE, ATTR_WIDTH, DOMAIN, KEY_COORDINATOR, @@ -160,7 +161,13 @@ def name(self): @property def available(self): """Return True if entity is available.""" - return self._blind.available + if self.coordinator.data is None: + return False + + if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: + return False + + return self.coordinator.data[self._blind.mac][ATTR_AVAILABLE] @property def current_cover_position(self): diff --git a/homeassistant/components/motion_blinds/manifest.json b/homeassistant/components/motion_blinds/manifest.json index ec2823dbd2e27b..c144dc99bc5b3e 100644 --- a/homeassistant/components/motion_blinds/manifest.json +++ b/homeassistant/components/motion_blinds/manifest.json @@ -3,6 +3,6 @@ "name": "Motion Blinds", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/motion_blinds", - "requirements": ["motionblinds==0.4.8"], + "requirements": ["motionblinds==0.4.10"], "codeowners": ["@starkillerOG"] } diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index 5d7a29bf9da780..d7f40337cec9df 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -10,7 +10,7 @@ ) from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, KEY_COORDINATOR, KEY_GATEWAY +from .const import ATTR_AVAILABLE, DOMAIN, KEY_COORDINATOR, KEY_GATEWAY ATTR_BATTERY_VOLTAGE = "battery_voltage" TYPE_BLIND = "blind" @@ -70,7 +70,13 @@ def name(self): @property def available(self): """Return True if entity is available.""" - return self._blind.available + if self.coordinator.data is None: + return False + + if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: + return False + + return self.coordinator.data[self._blind.mac][ATTR_AVAILABLE] @property def unit_of_measurement(self): @@ -174,7 +180,13 @@ def name(self): @property def available(self): """Return True if entity is available.""" - return self._device.available + if self.coordinator.data is None: + return False + + if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: + return False + + return self.coordinator.data[self._device.mac][ATTR_AVAILABLE] @property def unit_of_measurement(self): diff --git a/requirements_all.txt b/requirements_all.txt index d40244074a9a8f..db05a88e9f73e5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -946,7 +946,7 @@ minio==4.0.9 mitemp_bt==0.0.3 # homeassistant.components.motion_blinds -motionblinds==0.4.8 +motionblinds==0.4.10 # homeassistant.components.mullvad mullvad-api==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 149c156f4cc90f..e641de25fb82bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -491,7 +491,7 @@ millheater==0.4.0 minio==4.0.9 # homeassistant.components.motion_blinds -motionblinds==0.4.8 +motionblinds==0.4.10 # homeassistant.components.mullvad mullvad-api==1.0.0 From b90c620c5e8b3f2868b2065e74fe8819260ef8ea Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 04:18:46 +0100 Subject: [PATCH 1508/1818] Address huisbaasje review comments (#48313) * Address huisbaasje review comments * Update homeassistant/components/huisbaasje/config_flow.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- .../components/huisbaasje/__init__.py | 2 +- .../components/huisbaasje/config_flow.py | 27 +++++++++---------- homeassistant/components/huisbaasje/const.py | 9 ++++--- .../components/huisbaasje/strings.json | 3 +-- .../components/huisbaasje/test_config_flow.py | 2 +- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/huisbaasje/__init__.py b/homeassistant/components/huisbaasje/__init__.py index 8cd2681c8da8b9..f06ad444cc0365 100644 --- a/homeassistant/components/huisbaasje/__init__.py +++ b/homeassistant/components/huisbaasje/__init__.py @@ -141,7 +141,7 @@ def _get_cumulative_value( :param source_type: The source of energy (electricity or gas) :param period_type: The period for which cumulative value should be given. """ - if source_type in current_measurements.keys(): + if source_type in current_measurements: if ( period_type in current_measurements[source_type] and current_measurements[source_type][period_type] is not None diff --git a/homeassistant/components/huisbaasje/config_flow.py b/homeassistant/components/huisbaasje/config_flow.py index 59e4840529ddc7..c9a4750ade5e91 100644 --- a/homeassistant/components/huisbaasje/config_flow.py +++ b/homeassistant/components/huisbaasje/config_flow.py @@ -32,9 +32,18 @@ async def async_step_user(self, user_input=None): try: user_id = await self._validate_input(user_input) - - _LOGGER.info("Input for Huisbaasje is valid!") - + except HuisbaasjeConnectionException as exception: + _LOGGER.warning(exception) + errors["base"] = "cannot_connect" + except HuisbaasjeException as exception: + _LOGGER.warning(exception) + errors["base"] = "invalid_auth" + except AbortFlow: + raise + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: # Set user id as unique id await self.async_set_unique_id(user_id) self._abort_if_unique_id_configured() @@ -48,17 +57,6 @@ async def async_step_user(self, user_input=None): CONF_PASSWORD: user_input[CONF_PASSWORD], }, ) - except HuisbaasjeConnectionException as exception: - _LOGGER.warning(exception) - errors["base"] = "connection_exception" - except HuisbaasjeException as exception: - _LOGGER.warning(exception) - errors["base"] = "invalid_auth" - except AbortFlow as exception: - raise exception - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" return await self._show_setup_form(user_input, errors) @@ -72,7 +70,6 @@ async def _validate_input(self, user_input): Data has the keys from DATA_SCHEMA with values provided by the user. """ - username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] diff --git a/homeassistant/components/huisbaasje/const.py b/homeassistant/components/huisbaasje/const.py index 07ad84567e5a01..abac03e61828ae 100644 --- a/homeassistant/components/huisbaasje/const.py +++ b/homeassistant/components/huisbaasje/const.py @@ -9,6 +9,7 @@ ) from homeassistant.const import ( + DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, TIME_HOURS, @@ -70,34 +71,34 @@ }, { "name": "Huisbaasje Energy Today", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_DAY, - "icon": "mdi:counter", "precision": 1, }, { "name": "Huisbaasje Energy This Week", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_WEEK, - "icon": "mdi:counter", "precision": 1, }, { "name": "Huisbaasje Energy This Month", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_MONTH, - "icon": "mdi:counter", "precision": 1, }, { "name": "Huisbaasje Energy This Year", + "device_class": DEVICE_CLASS_ENERGY, "unit_of_measurement": ENERGY_KILO_WATT_HOUR, "source_type": SOURCE_TYPE_ELECTRICITY, "sensor_type": SENSOR_TYPE_THIS_YEAR, - "icon": "mdi:counter", "precision": 1, }, { diff --git a/homeassistant/components/huisbaasje/strings.json b/homeassistant/components/huisbaasje/strings.json index f126ac0afffd27..169b9a0e901c18 100644 --- a/homeassistant/components/huisbaasje/strings.json +++ b/homeassistant/components/huisbaasje/strings.json @@ -10,8 +10,7 @@ }, "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unauthenticated_exception": "[%key:common::config_flow::error::invalid_auth%]", - "connection_exception": "[%key:common::config_flow::error::cannot_connect%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/tests/components/huisbaasje/test_config_flow.py b/tests/components/huisbaasje/test_config_flow.py index 245ac2f8ddb57a..35e28b645ebee4 100644 --- a/tests/components/huisbaasje/test_config_flow.py +++ b/tests/components/huisbaasje/test_config_flow.py @@ -94,7 +94,7 @@ async def test_form_cannot_connect(hass): ) assert form_result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert form_result["errors"] == {"base": "connection_exception"} + assert form_result["errors"] == {"base": "cannot_connect"} async def test_form_unknown_error(hass): From 24dee01599a8c4e0d503c0cb9880d837e0d18c7d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 04:21:27 +0100 Subject: [PATCH 1509/1818] Use async with in Acmeda config flow (#48291) --- homeassistant/components/acmeda/config_flow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index a849e49ddf4964..5cdb804d5ddc7a 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -38,12 +38,13 @@ async def async_step_user(self, user_input=None): } hubs = [] - with suppress(asyncio.TimeoutError), async_timeout.timeout(5): - async for hub in aiopulse.Hub.discover(): - if hub.id not in already_configured: - hubs.append(hub) + with suppress(asyncio.TimeoutError): + async with async_timeout.timeout(5): + async for hub in aiopulse.Hub.discover(): + if hub.id not in already_configured: + hubs.append(hub) - if len(hubs) == 0: + if not hubs: return self.async_abort(reason="no_devices_found") if len(hubs) == 1: From a019f076c036e1405d7a0d605c7c182bea4015da Mon Sep 17 00:00:00 2001 From: Garrett <7310260+G-Two@users.noreply.github.com> Date: Thu, 25 Mar 2021 23:24:37 -0400 Subject: [PATCH 1510/1818] Subaru integration code quality changes (#48193) * Apply changes from code review * Update sensor tests * Fix pylint error * Apply suggestions from code review Co-authored-by: Brandon Rothweiler Co-authored-by: Martin Hjelmare Co-authored-by: Brandon Rothweiler Co-authored-by: Martin Hjelmare --- .../components/subaru/config_flow.py | 10 ++-- homeassistant/components/subaru/const.py | 5 -- homeassistant/components/subaru/entity.py | 7 +-- homeassistant/components/subaru/sensor.py | 24 +++++--- homeassistant/components/subaru/strings.json | 3 +- tests/components/subaru/conftest.py | 15 ++++- tests/components/subaru/test_config_flow.py | 56 ++++++++++--------- tests/components/subaru/test_sensor.py | 35 +++++++----- 8 files changed, 88 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index a3586b32974d3b..772134c66b167e 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -28,8 +28,11 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - config_data = {CONF_PIN: None} - controller = None + + def __init__(self): + """Initialize config flow.""" + self.config_data = {CONF_PIN: None} + self.controller = None async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" @@ -105,9 +108,8 @@ async def validate_login_creds(self, data): device_name=device_name, country=data[CONF_COUNTRY], ) - _LOGGER.debug("Using subarulink %s", self.controller.version) _LOGGER.debug( - "Setting up first time connection to Subuaru API; This may take up to 20 seconds" + "Setting up first time connection to Subaru API. This may take up to 20 seconds" ) if await self.controller.connect(): _LOGGER.debug("Successfully authenticated and authorized with Subaru API") diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py index fa6fe984e61267..cada29edd3a726 100644 --- a/homeassistant/components/subaru/const.py +++ b/homeassistant/components/subaru/const.py @@ -36,12 +36,7 @@ ICONS = { "Avg Fuel Consumption": "mdi:leaf", - "EV Time to Full Charge": "mdi:car-electric", "EV Range": "mdi:ev-station", "Odometer": "mdi:road-variant", "Range": "mdi:gas-station", - "Tire Pressure FL": "mdi:gauge", - "Tire Pressure FR": "mdi:gauge", - "Tire Pressure RL": "mdi:gauge", - "Tire Pressure RR": "mdi:gauge", } diff --git a/homeassistant/components/subaru/entity.py b/homeassistant/components/subaru/entity.py index 4fdeca4e484743..559feeea303ca1 100644 --- a/homeassistant/components/subaru/entity.py +++ b/homeassistant/components/subaru/entity.py @@ -1,7 +1,7 @@ """Base class for all Subaru Entities.""" from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, ICONS, MANUFACTURER, VEHICLE_NAME, VEHICLE_VIN +from .const import DOMAIN, MANUFACTURER, VEHICLE_NAME, VEHICLE_VIN class SubaruEntity(CoordinatorEntity): @@ -24,11 +24,6 @@ def unique_id(self) -> str: """Return a unique ID.""" return f"{self.vin}_{self.entity_type}" - @property - def icon(self): - """Return the icon of the sensor.""" - return ICONS.get(self.entity_type) - @property def device_info(self): """Return the device_info of the device.""" diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index b8362202a3d571..41dd8a6604f5f7 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -4,7 +4,9 @@ from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity from homeassistant.const import ( DEVICE_CLASS_BATTERY, + DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_VOLTAGE, LENGTH_KILOMETERS, LENGTH_MILES, @@ -30,6 +32,7 @@ DOMAIN, ENTRY_COORDINATOR, ENTRY_VEHICLES, + ICONS, VEHICLE_API_GEN, VEHICLE_HAS_EV, VEHICLE_HAS_SAFETY_SERVICE, @@ -76,25 +79,25 @@ }, { SENSOR_TYPE: "Tire Pressure FL", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_FL, SENSOR_UNITS: PRESSURE_HPA, }, { SENSOR_TYPE: "Tire Pressure FR", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_FR, SENSOR_UNITS: PRESSURE_HPA, }, { SENSOR_TYPE: "Tire Pressure RL", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_RL, SENSOR_UNITS: PRESSURE_HPA, }, { SENSOR_TYPE: "Tire Pressure RR", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_PRESSURE, SENSOR_FIELD: sc.TIRE_PRESSURE_RR, SENSOR_UNITS: PRESSURE_HPA, }, @@ -128,7 +131,7 @@ }, { SENSOR_TYPE: "EV Time to Full Charge", - SENSOR_CLASS: None, + SENSOR_CLASS: DEVICE_CLASS_TIMESTAMP, SENSOR_FIELD: sc.EV_TIME_TO_FULLY_CHARGED, SENSOR_UNITS: TIME_MINUTES, }, @@ -140,7 +143,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): coordinator = hass.data[DOMAIN][config_entry.entry_id][ENTRY_COORDINATOR] vehicle_info = hass.data[DOMAIN][config_entry.entry_id][ENTRY_VEHICLES] entities = [] - for vin in vehicle_info.keys(): + for vin in vehicle_info: entities.extend(create_vehicle_sensors(vehicle_info[vin], coordinator)) async_add_entities(entities, True) @@ -190,7 +193,14 @@ def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" if self.sensor_class in DEVICE_CLASSES: return self.sensor_class - return super().device_class + return None + + @property + def icon(self): + """Return the icon of the sensor.""" + if not self.device_class: + return ICONS.get(self.entity_type) + return None @property def state(self): diff --git a/homeassistant/components/subaru/strings.json b/homeassistant/components/subaru/strings.json index 064245e0732317..ea9df082f3a8df 100644 --- a/homeassistant/components/subaru/strings.json +++ b/homeassistant/components/subaru/strings.json @@ -22,8 +22,7 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "incorrect_pin": "Incorrect PIN", - "bad_pin_format": "PIN should be 4 digits", - "unknown": "[%key:common::config_flow::error::unknown%]" + "bad_pin_format": "PIN should be 4 digits" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", diff --git a/tests/components/subaru/conftest.py b/tests/components/subaru/conftest.py index 8216ca2d2c2bd0..1b8d1439e68828 100644 --- a/tests/components/subaru/conftest.py +++ b/tests/components/subaru/conftest.py @@ -1,4 +1,5 @@ """Common functions needed to setup tests for Subaru component.""" +from datetime import timedelta from unittest.mock import patch import pytest @@ -9,6 +10,7 @@ CONF_COUNTRY, CONF_UPDATE_ENABLED, DOMAIN, + FETCH_INTERVAL, VEHICLE_API_GEN, VEHICLE_HAS_EV, VEHICLE_HAS_REMOTE_SERVICE, @@ -19,10 +21,11 @@ from homeassistant.config_entries import ENTRY_STATE_LOADED from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util from .api_responses import TEST_VIN_2_EV, VEHICLE_DATA, VEHICLE_STATUS_EV -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed MOCK_API = "homeassistant.components.subaru.SubaruAPI." MOCK_API_CONNECT = f"{MOCK_API}connect" @@ -36,7 +39,7 @@ MOCK_API_GET_RES_STATUS = f"{MOCK_API}get_res_status" MOCK_API_GET_REMOTE_STATUS = f"{MOCK_API}get_remote_status" MOCK_API_GET_SAFETY_STATUS = f"{MOCK_API}get_safety_status" -MOCK_API_GET_GET_DATA = f"{MOCK_API}get_data" +MOCK_API_GET_DATA = f"{MOCK_API}get_data" MOCK_API_UPDATE = f"{MOCK_API}update" MOCK_API_FETCH = f"{MOCK_API}fetch" @@ -67,6 +70,12 @@ TEST_ENTITY_ID = "sensor.test_vehicle_2_odometer" +def advance_time_to_next_fetch(hass): + """Fast forward time to next fetch.""" + future = dt_util.utcnow() + timedelta(seconds=FETCH_INTERVAL + 30) + async_fire_time_changed(hass, future) + + async def setup_subaru_integration( hass, vehicle_list=None, @@ -110,7 +119,7 @@ async def setup_subaru_integration( MOCK_API_GET_SAFETY_STATUS, return_value=vehicle_data[VEHICLE_HAS_SAFETY_SERVICE], ), patch( - MOCK_API_GET_GET_DATA, + MOCK_API_GET_DATA, return_value=vehicle_status, ), patch( MOCK_API_UPDATE, diff --git a/tests/components/subaru/test_config_flow.py b/tests/components/subaru/test_config_flow.py index 676b876652b7a7..0218c11003c5b6 100644 --- a/tests/components/subaru/test_config_flow.py +++ b/tests/components/subaru/test_config_flow.py @@ -11,6 +11,7 @@ from homeassistant.components.subaru import config_flow from homeassistant.components.subaru.const import CONF_UPDATE_ENABLED, DOMAIN from homeassistant.const import CONF_DEVICE_ID, CONF_PIN +from homeassistant.setup import async_setup_component from .conftest import ( MOCK_API_CONNECT, @@ -26,19 +27,17 @@ from tests.common import MockConfigEntry +ASYNC_SETUP = "homeassistant.components.subaru.async_setup" +ASYNC_SETUP_ENTRY = "homeassistant.components.subaru.async_setup_entry" + async def test_user_form_init(user_form): """Test the initial user form for first step of the config flow.""" - expected = { - "data_schema": mock.ANY, - "description_placeholders": None, - "errors": None, - "flow_id": mock.ANY, - "handler": DOMAIN, - "step_id": "user", - "type": "form", - } - assert expected == user_form + assert user_form["description_placeholders"] is None + assert user_form["errors"] is None + assert user_form["handler"] == DOMAIN + assert user_form["step_id"] == "user" + assert user_form["type"] == "form" async def test_user_form_repeat_identifier(hass, user_form): @@ -96,13 +95,19 @@ async def test_user_form_pin_not_required(hass, user_form): with patch(MOCK_API_CONNECT, return_value=True,) as mock_connect, patch( MOCK_API_IS_PIN_REQUIRED, return_value=False, - ) as mock_is_pin_required: + ) as mock_is_pin_required, patch( + ASYNC_SETUP, return_value=True + ) as mock_setup, patch( + ASYNC_SETUP_ENTRY, return_value=True + ) as mock_setup_entry: result = await hass.config_entries.flow.async_configure( user_form["flow_id"], TEST_CREDS, ) - assert len(mock_connect.mock_calls) == 2 + assert len(mock_connect.mock_calls) == 1 assert len(mock_is_pin_required.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 expected = { "title": TEST_USERNAME, @@ -117,7 +122,7 @@ async def test_user_form_pin_not_required(hass, user_form): } expected["data"][CONF_PIN] = None result["data"][CONF_DEVICE_ID] = TEST_DEVICE_ID - assert expected == result + assert result == expected async def test_pin_form_init(pin_form): @@ -131,7 +136,7 @@ async def test_pin_form_init(pin_form): "step_id": "pin", "type": "form", } - assert expected == pin_form + assert pin_form == expected async def test_pin_form_bad_pin_format(hass, pin_form): @@ -154,13 +159,19 @@ async def test_pin_form_success(hass, pin_form): with patch(MOCK_API_TEST_PIN, return_value=True,) as mock_test_pin, patch( MOCK_API_UPDATE_SAVED_PIN, return_value=True, - ) as mock_update_saved_pin: + ) as mock_update_saved_pin, patch( + ASYNC_SETUP, return_value=True + ) as mock_setup, patch( + ASYNC_SETUP_ENTRY, return_value=True + ) as mock_setup_entry: result = await hass.config_entries.flow.async_configure( pin_form["flow_id"], user_input={CONF_PIN: TEST_PIN} ) assert len(mock_test_pin.mock_calls) == 1 assert len(mock_update_saved_pin.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 expected = { "title": TEST_USERNAME, "description": None, @@ -196,16 +207,10 @@ async def test_pin_form_incorrect_pin(hass, pin_form): async def test_option_flow_form(options_form): """Test config flow options form.""" - expected = { - "data_schema": mock.ANY, - "description_placeholders": None, - "errors": None, - "flow_id": mock.ANY, - "handler": mock.ANY, - "step_id": "init", - "type": "form", - } - assert expected == options_form + assert options_form["description_placeholders"] is None + assert options_form["errors"] is None + assert options_form["step_id"] == "init" + assert options_form["type"] == "form" async def test_option_flow(hass, options_form): @@ -247,4 +252,5 @@ async def options_form(hass): """Return options form for Subaru config flow.""" entry = MockConfigEntry(domain=DOMAIN, data={}, options=None) entry.add_to_hass(hass) + await async_setup_component(hass, DOMAIN, {}) return await hass.config_entries.options.async_init(entry.entry_id) diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py index 4344c147f2266b..f2a66e7e5e915f 100644 --- a/tests/components/subaru/test_sensor.py +++ b/tests/components/subaru/test_sensor.py @@ -1,4 +1,6 @@ """Test Subaru sensors.""" +from unittest.mock import patch + from homeassistant.components.subaru.const import VEHICLE_NAME from homeassistant.components.subaru.sensor import ( API_GEN_2_SENSORS, @@ -19,20 +21,25 @@ VEHICLE_STATUS_EV, ) -from tests.components.subaru.conftest import setup_subaru_integration +from tests.components.subaru.conftest import ( + MOCK_API_FETCH, + MOCK_API_GET_DATA, + advance_time_to_next_fetch, +) VEHICLE_NAME = VEHICLE_DATA[TEST_VIN_2_EV][VEHICLE_NAME] -async def test_sensors_ev_imperial(hass): +async def test_sensors_ev_imperial(hass, ev_entry): """Test sensors supporting imperial units.""" hass.config.units = IMPERIAL_SYSTEM - await setup_subaru_integration( - hass, - vehicle_list=[TEST_VIN_2_EV], - vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], - vehicle_status=VEHICLE_STATUS_EV, - ) + + with patch(MOCK_API_FETCH), patch( + MOCK_API_GET_DATA, return_value=VEHICLE_STATUS_EV + ): + advance_time_to_next_fetch(hass) + await hass.async_block_till_done() + _assert_data(hass, EXPECTED_STATE_EV_IMPERIAL) @@ -41,14 +48,12 @@ async def test_sensors_ev_metric(hass, ev_entry): _assert_data(hass, EXPECTED_STATE_EV_METRIC) -async def test_sensors_missing_vin_data(hass): +async def test_sensors_missing_vin_data(hass, ev_entry): """Test for missing VIN dataset.""" - await setup_subaru_integration( - hass, - vehicle_list=[TEST_VIN_2_EV], - vehicle_data=VEHICLE_DATA[TEST_VIN_2_EV], - vehicle_status=None, - ) + with patch(MOCK_API_FETCH), patch(MOCK_API_GET_DATA, return_value=None): + advance_time_to_next_fetch(hass) + await hass.async_block_till_done() + _assert_data(hass, EXPECTED_STATE_EV_UNAVAILABLE) From 2b24f8b735cf1b38e75096438effb93be61f6d36 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Fri, 26 Mar 2021 05:11:08 +0100 Subject: [PATCH 1511/1818] Remove timedate manipulation from Neato attributes (#48150) * Remove timedate manipulation to get timezone back * Updated camera to new format --- homeassistant/components/neato/camera.py | 2 +- homeassistant/components/neato/vacuum.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 74a3cb4bc77c9a..9a2f47bcfa361a 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -102,7 +102,7 @@ def update(self): self._image = image.read() self._image_url = image_url - self._generated_at = (map_data["generated_at"].strip("Z")).replace("T", " ") + self._generated_at = map_data["generated_at"] self._available = True @property diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 3b6711d3b726ac..e0b3c7b779f034 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -202,8 +202,8 @@ def update(self): return mapdata = self._mapdata[self._robot_serial]["maps"][0] - self._clean_time_start = (mapdata["start_at"].strip("Z")).replace("T", " ") - self._clean_time_stop = (mapdata["end_at"].strip("Z")).replace("T", " ") + self._clean_time_start = mapdata["start_at"] + self._clean_time_stop = mapdata["end_at"] self._clean_area = mapdata["cleaned_area"] self._clean_susp_charge_count = mapdata["suspended_cleaning_charging_count"] self._clean_susp_time = mapdata["time_in_suspended_cleaning"] From de1fa706a071923d00da3336cc48427b8b805cee Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 26 Mar 2021 08:07:57 +0100 Subject: [PATCH 1512/1818] xknx 0.17.4 (#48350) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 7f14c17839c4ad..629d43092d4b92 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,7 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.17.3"], + "requirements": ["xknx==0.17.4"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index db05a88e9f73e5..257bd9710fb0ff 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2332,7 +2332,7 @@ xbox-webapi==2.0.8 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.17.3 +xknx==0.17.4 # homeassistant.components.bluesound # homeassistant.components.rest From 72281f4718b6ca71b77b515fa3afc466d2d812d9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 08:09:21 +0100 Subject: [PATCH 1513/1818] Validate device trigger schemas once (#48319) --- homeassistant/components/alarm_control_panel/device_trigger.py | 2 -- homeassistant/components/arcam_fmj/device_trigger.py | 1 - homeassistant/components/climate/device_trigger.py | 1 - homeassistant/components/cover/device_trigger.py | 2 -- homeassistant/components/device_tracker/device_trigger.py | 2 -- homeassistant/components/fan/device_trigger.py | 2 -- homeassistant/components/homekit_controller/device_trigger.py | 2 -- homeassistant/components/kodi/device_trigger.py | 2 -- homeassistant/components/lock/device_trigger.py | 2 -- homeassistant/components/media_player/device_trigger.py | 2 -- homeassistant/components/mqtt/device_trigger.py | 1 - homeassistant/components/nest/device_trigger.py | 1 - homeassistant/components/netatmo/device_trigger.py | 2 -- homeassistant/components/shelly/device_trigger.py | 1 - homeassistant/components/tasmota/device_trigger.py | 1 - homeassistant/components/vacuum/device_trigger.py | 2 -- 16 files changed, 26 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index 9ab28e3e863662..c5d2cdd2e37fe2 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -131,8 +131,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "triggered": to_state = STATE_ALARM_TRIGGERED elif config[CONF_TYPE] == "disarmed": diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index 060c56e59534b1..be2b136bf9794c 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -56,7 +56,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) job = HassJob(action) if config[CONF_TYPE] == "turn_on": diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index bee019fa6da6bd..8a0d7a440c462e 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -113,7 +113,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) trigger_type = config[CONF_TYPE] if trigger_type == "hvac_mode_changed": diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 119bfc835f586c..c7ad852dc0fc73 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -170,8 +170,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] in STATE_TRIGGER_TYPES: if config[CONF_TYPE] == "opened": to_state = STATE_OPEN diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 49b77024a1ed6d..81a16545c74e8e 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -71,8 +71,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "enters": event = zone.EVENT_ENTER else: diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index f109cfef1170e4..c72be6f9d7c1f0 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -71,8 +71,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "turned_on": to_state = STATE_ON else: diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 4de5ce66a09c20..31bfcc18d52486 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -257,8 +257,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - device_id = config[CONF_DEVICE_ID] device = hass.data[TRIGGERS][device_id] return await device.async_attach_trigger(config, action, automation_info) diff --git a/homeassistant/components/kodi/device_trigger.py b/homeassistant/components/kodi/device_trigger.py index 3454fc122ed4bf..c59fe53be14adf 100644 --- a/homeassistant/components/kodi/device_trigger.py +++ b/homeassistant/components/kodi/device_trigger.py @@ -84,8 +84,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "turn_on": return _attach_trigger(hass, config, action, EVENT_TURN_ON) diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index 3e5bee49a22695..e6ec1536e3a8d8 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -71,8 +71,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "locked": to_state = STATE_LOCKED else: diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index 03c165412e92cd..29b69954d43671 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -66,8 +66,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "turned_on": to_state = STATE_ON elif config[CONF_TYPE] == "turned_off": diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 12a98905ba5690..1e058162bc3fbb 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -319,7 +319,6 @@ async def async_attach_trigger( """Attach a trigger.""" if DEVICE_TRIGGERS not in hass.data: hass.data[DEVICE_TRIGGERS] = {} - config = TRIGGER_SCHEMA(config) device_id = config[CONF_DEVICE_ID] discovery_id = config[CONF_DISCOVERY_ID] diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index bd2b59c6cfe25b..d59ec05c503750 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -82,7 +82,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) event_config = event_trigger.TRIGGER_SCHEMA( { event_trigger.CONF_PLATFORM: "event", diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index 3893fbed4c72aa..d6085ec06ec1f6 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -125,8 +125,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - device_registry = await hass.helpers.device_registry.async_get_registry() device = device_registry.async_get(config[CONF_DEVICE_ID]) diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index deec98a4915f56..b7cf11209490dc 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -92,7 +92,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) event_config = { event_trigger.CONF_PLATFORM: CONF_EVENT, event_trigger.CONF_EVENT_TYPE: EVENT_SHELLY_CLICK, diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index 139e9be816a976..ae4a528efc6667 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -273,7 +273,6 @@ async def async_attach_trigger( """Attach a device trigger.""" if DEVICE_TRIGGERS not in hass.data: hass.data[DEVICE_TRIGGERS] = {} - config = TRIGGER_SCHEMA(config) device_id = config[CONF_DEVICE_ID] discovery_id = config[CONF_DISCOVERY_ID] diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 8023a3865a7421..1ba6e330a8c776 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -68,8 +68,6 @@ async def async_attach_trigger( automation_info: dict, ) -> CALLBACK_TYPE: """Attach a trigger.""" - config = TRIGGER_SCHEMA(config) - if config[CONF_TYPE] == "cleaning": to_state = STATE_CLEANING else: From da2fecb312025f74498172e82f4c0fe7689e2d8a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 08:21:11 +0100 Subject: [PATCH 1514/1818] Pre-calculate Verisure alarm states (#48340) * Pre-calculate Verisure alarm states * Correct super call --- .../verisure/alarm_control_panel.py | 44 ++++++++----------- homeassistant/components/verisure/const.py | 14 ++++++ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 761feb0d2cb77b..34a60b9cae4da4 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -13,17 +13,11 @@ SUPPORT_ALARM_ARM_HOME, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, - STATE_ALARM_PENDING, -) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_GIID, DOMAIN, LOGGER +from .const import ALARM_STATE_TO_HA, CONF_GIID, DOMAIN, LOGGER from .coordinator import VerisureDataUpdateCoordinator @@ -41,10 +35,8 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity): coordinator: VerisureDataUpdateCoordinator - def __init__(self, coordinator: VerisureDataUpdateCoordinator) -> None: - """Initialize the Verisure alarm panel.""" - super().__init__(coordinator) - self._state = None + _changed_by: str | None = None + _state: str | None = None @property def name(self) -> str: @@ -69,18 +61,6 @@ def device_info(self) -> dict[str, Any]: @property def state(self) -> str | None: """Return the state of the entity.""" - status = self.coordinator.data["alarm"]["statusType"] - if status == "DISARMED": - self._state = STATE_ALARM_DISARMED - elif status == "ARMED_HOME": - self._state = STATE_ALARM_ARMED_HOME - elif status == "ARMED_AWAY": - self._state = STATE_ALARM_ARMED_AWAY - elif status == "PENDING": - self._state = STATE_ALARM_PENDING - else: - LOGGER.error("Unknown alarm state %s", status) - return self._state @property @@ -96,7 +76,7 @@ def code_format(self) -> str: @property def changed_by(self) -> str | None: """Return the last change triggered by.""" - return self.coordinator.data["alarm"]["name"] + return self._changed_by async def _async_set_arm_state(self, state: str, code: str | None = None) -> None: """Send set arm state command.""" @@ -125,3 +105,17 @@ async def async_alarm_arm_home(self, code: str | None = None) -> None: async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" await self._async_set_arm_state("ARMED_AWAY", code) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._state = ALARM_STATE_TO_HA.get( + self.coordinator.data["alarm"]["statusType"] + ) + self._changed_by = self.coordinator.data["alarm"]["name"] + super()._handle_coordinator_update() + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self._handle_coordinator_update() diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index 8e39e0594dd9bc..030c5a5807559a 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -2,6 +2,13 @@ from datetime import timedelta import logging +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, + STATE_ALARM_PENDING, +) + DOMAIN = "verisure" LOGGER = logging.getLogger(__package__) @@ -31,6 +38,13 @@ "WATER1": "Water detector", } +ALARM_STATE_TO_HA = { + "DISARMED": STATE_ALARM_DISARMED, + "ARMED_HOME": STATE_ALARM_ARMED_HOME, + "ARMED_AWAY": STATE_ALARM_ARMED_AWAY, + "PENDING": STATE_ALARM_PENDING, +} + # Legacy; to remove after YAML removal CONF_CODE_DIGITS = "code_digits" CONF_DEFAULT_LOCK_CODE = "default_lock_code" From 5b17aaf9d5a3093218727190b92bce1e73e4688e Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 26 Mar 2021 08:37:47 +0100 Subject: [PATCH 1515/1818] Percentage and preset mode support for MQTT fan (#47944) * git push --all origin * Fix percentage to ordered list conversion * Tests for mqtt fan and fixes * Improve tests and error handling base config * Additional tests * Tests completed, small fixes * Allow preset mode and percentages combined * Remove raise in setup and update tests * Alignment with fan entity mode * Fix pylint for len-as-condition * Remove python binary cache file from PR * Additional tests on async_turn_on and fix * Added comments for deprecation of speeds * Schema checks before init * Optimize pre schema checks * Correct schema checks * Update homeassistant/components/mqtt/abbreviations.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Comment speeds for mqtt fan are deprecated not needed here Co-authored-by: Erik Montnemery * Warnings for exceptions - testing speed_range * Update homeassistant/components/mqtt/abbreviations.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py Co-authored-by: Erik Montnemery * Update homeassistant/components/mqtt/fan.py * Save with black Co-authored-by: Erik Montnemery --- .../components/mqtt/abbreviations.py | 9 + homeassistant/components/mqtt/fan.py | 462 ++++- tests/components/mqtt/test_fan.py | 1549 +++++++++++++++-- 3 files changed, 1794 insertions(+), 226 deletions(-) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index 868e2fdd791354..a65c78f87d1abd 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -88,6 +88,9 @@ "osc_cmd_t": "oscillation_command_topic", "osc_stat_t": "oscillation_state_topic", "osc_val_tpl": "oscillation_value_template", + "pct_cmd_t": "percentage_command_topic", + "pct_stat_t": "percentage_state_topic", + "pct_val_tpl": "percentage_value_template", "pl": "payload", "pl_arm_away": "payload_arm_away", "pl_arm_home": "payload_arm_home", @@ -124,6 +127,10 @@ "pow_cmd_t": "power_command_topic", "pow_stat_t": "power_state_topic", "pow_stat_tpl": "power_state_template", + "pr_mode_cmd_t": "preset_mode_command_topic", + "pr_mode_stat_t": "preset_mode_state_topic", + "pr_mode_val_tpl": "preset_mode_value_template", + "pr_modes": "preset_modes", "r_tpl": "red_template", "ret": "retain", "rgb_cmd_tpl": "rgb_command_template", @@ -139,6 +146,8 @@ "pos_tpl": "position_template", "spd_cmd_t": "speed_command_topic", "spd_stat_t": "speed_state_topic", + "spd_rng_min": "speed_range_min", + "spd_rng_max": "speed_range_max", "spd_val_tpl": "speed_value_template", "spds": "speeds", "src_type": "source_type", diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index c0663370805383..a0395039e786f4 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -1,18 +1,23 @@ """Support for MQTT fans.""" import functools +import logging import voluptuous as vol from homeassistant.components import fan from homeassistant.components.fan import ( + ATTR_PERCENTAGE, + ATTR_PRESET_MODE, ATTR_SPEED, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, SUPPORT_OSCILLATE, + SUPPORT_PRESET_MODE, SUPPORT_SET_SPEED, FanEntity, + speed_list_without_preset_modes, ) from homeassistant.const import ( CONF_NAME, @@ -25,6 +30,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.util.percentage import ( + ordered_list_item_to_percentage, + percentage_to_ordered_list_item, + percentage_to_ranged_value, + ranged_value_to_percentage, +) from . import ( CONF_COMMAND_TOPIC, @@ -40,6 +51,15 @@ 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" +CONF_SPEED_RANGE_MIN = "speed_range_min" +CONF_SPEED_RANGE_MAX = "speed_range_max" +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_MODES_LIST = "preset_modes" CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_VALUE_TEMPLATE = "speed_value_template" @@ -58,41 +78,96 @@ DEFAULT_PAYLOAD_ON = "ON" DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_OPTIMISTIC = False +DEFAULT_SPEED_RANGE_MIN = 1 +DEFAULT_SPEED_RANGE_MAX = 100 OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_OFF_PAYLOAD = "oscillate_off" OSCILLATION = "oscillation" -PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, - vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, - vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, - vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, - 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_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD - ): cv.string, - vol.Optional( - CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD - ): cv.string, - vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional( - CONF_SPEED_LIST, - default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], - ): cv.ensure_list, - vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, - } -).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) +_LOGGER = logging.getLogger(__name__) + + +def valid_fan_speed_configuration(config): + """Validate that the fan speed configuration is valid, throws if it isn't.""" + if config.get(CONF_SPEED_COMMAND_TOPIC) and not speed_list_without_preset_modes( + config.get(CONF_SPEED_LIST) + ): + raise ValueError("No valid speeds configured") + return config + + +def valid_speed_range_configuration(config): + """Validate that the fan speed_range configuration is valid, throws if it isn't.""" + if config.get(CONF_SPEED_RANGE_MIN) == 0: + raise ValueError("speed_range_min must be > 0") + if config.get(CONF_SPEED_RANGE_MIN) >= config.get(CONF_SPEED_RANGE_MAX): + raise ValueError("speed_range_max must be > speed_range_min") + return config + + +PLATFORM_SCHEMA = vol.All( + # CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_STATE_VALUE_TEMPLATE, CONF_SPEED_LIST and + # Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF, + # are deprecated, support will be removed after a quarter (2021.7) + cv.deprecated(CONF_PAYLOAD_HIGH_SPEED), + cv.deprecated(CONF_PAYLOAD_LOW_SPEED), + cv.deprecated(CONF_PAYLOAD_MEDIUM_SPEED), + cv.deprecated(CONF_SPEED_LIST), + cv.deprecated(CONF_SPEED_COMMAND_TOPIC), + cv.deprecated(CONF_SPEED_STATE_TOPIC), + cv.deprecated(CONF_SPEED_VALUE_TEMPLATE), + mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_PERCENTAGE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_PERCENTAGE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PERCENTAGE_VALUE_TEMPLATE): cv.template, + # 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_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PRESET_MODE_VALUE_TEMPLATE): cv.template, + vol.Optional( + CONF_SPEED_RANGE_MIN, default=DEFAULT_SPEED_RANGE_MIN + ): cv.positive_int, + vol.Optional( + CONF_SPEED_RANGE_MAX, default=DEFAULT_SPEED_RANGE_MAX + ): cv.positive_int, + vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string, + vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string, + vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, + vol.Optional(CONF_PAYLOAD_OFF_SPEED, default=SPEED_OFF): cv.string, + 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_PAYLOAD_OSCILLATION_OFF, default=OSCILLATE_OFF_PAYLOAD + ): cv.string, + vol.Optional( + CONF_PAYLOAD_OSCILLATION_ON, default=OSCILLATE_ON_PAYLOAD + ): cv.string, + vol.Optional(CONF_SPEED_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional( + CONF_SPEED_LIST, + default=[SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + ): cv.ensure_list, + vol.Optional(CONF_SPEED_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, + } + ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema), + valid_fan_speed_configuration, + valid_speed_range_configuration, +) async def async_setup_platform( @@ -124,7 +199,10 @@ class MqttFan(MqttEntity, FanEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT fan.""" self._state = False + # self._speed will be removed after a quarter (2021.7) self._speed = None + self._percentage = None + self._preset_mode = None self._oscillation = None self._supported_features = 0 @@ -133,6 +211,8 @@ def __init__(self, hass, config, config_entry, discovery_data): self._templates = None self._optimistic = None self._optimistic_oscillation = None + self._optimistic_percentage = None + self._optimistic_preset_mode = None self._optimistic_speed = None MqttEntity.__init__(self, hass, config, config_entry, discovery_data) @@ -144,11 +224,19 @@ def config_schema(): def _setup_from_config(self, config): """(Re)Setup the entity.""" + self._speed_range = ( + config.get(CONF_SPEED_RANGE_MIN), + config.get(CONF_SPEED_RANGE_MAX), + ) self._topic = { key: config.get(key) for key in ( CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, + CONF_PERCENTAGE_STATE_TOPIC, + CONF_PERCENTAGE_COMMAND_TOPIC, + CONF_PRESET_MODE_STATE_TOPIC, + CONF_PRESET_MODE_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_SPEED_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, @@ -157,6 +245,9 @@ def _setup_from_config(self, config): } self._templates = { CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE), + ATTR_PERCENTAGE: config.get(CONF_PERCENTAGE_VALUE_TEMPLATE), + ATTR_PRESET_MODE: config.get(CONF_PRESET_MODE_VALUE_TEMPLATE), + # ATTR_SPEED is deprecated in the schema, support will be removed after a quarter (2021.7) ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE), OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE), } @@ -165,16 +256,53 @@ def _setup_from_config(self, config): "STATE_OFF": config[CONF_PAYLOAD_OFF], "OSCILLATE_ON_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_ON], "OSCILLATE_OFF_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_OFF], + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) "SPEED_LOW": config[CONF_PAYLOAD_LOW_SPEED], "SPEED_MEDIUM": config[CONF_PAYLOAD_MEDIUM_SPEED], "SPEED_HIGH": config[CONF_PAYLOAD_HIGH_SPEED], "SPEED_OFF": config[CONF_PAYLOAD_OFF_SPEED], } + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) + self._feature_legacy_speeds = not self._topic[CONF_SPEED_COMMAND_TOPIC] is None + if self._feature_legacy_speeds: + self._legacy_speeds_list = config[CONF_SPEED_LIST] + self._legacy_speeds_list_no_off = speed_list_without_preset_modes( + self._legacy_speeds_list + ) + else: + self._legacy_speeds_list = [] + + self._feature_percentage = CONF_PERCENTAGE_COMMAND_TOPIC in config + self._feature_preset_mode = CONF_PRESET_MODE_COMMAND_TOPIC in config + if self._feature_preset_mode: + self._speeds_list = speed_list_without_preset_modes( + self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST] + ) + self._preset_modes = ( + self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST] + ) + else: + self._speeds_list = speed_list_without_preset_modes( + self._legacy_speeds_list + ) + self._preset_modes = [] + + if not self._speeds_list or self._feature_percentage: + self._speed_count = 100 + else: + self._speed_count = len(self._speeds_list) + optimistic = config[CONF_OPTIMISTIC] self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None self._optimistic_oscillation = ( optimistic or self._topic[CONF_OSCILLATION_STATE_TOPIC] is None ) + self._optimistic_percentage = ( + optimistic or self._topic[CONF_PERCENTAGE_STATE_TOPIC] is None + ) + self._optimistic_preset_mode = ( + optimistic or self._topic[CONF_PRESET_MODE_STATE_TOPIC] is None + ) self._optimistic_speed = ( optimistic or self._topic[CONF_SPEED_STATE_TOPIC] is None ) @@ -184,9 +312,14 @@ def _setup_from_config(self, config): self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None and SUPPORT_OSCILLATE ) - self._supported_features |= ( - self._topic[CONF_SPEED_COMMAND_TOPIC] is not None and SUPPORT_SET_SPEED - ) + if self._feature_preset_mode and self._speeds_list: + self._supported_features |= SUPPORT_SET_SPEED + if self._feature_percentage: + self._supported_features |= SUPPORT_SET_SPEED + if self._feature_legacy_speeds: + self._supported_features |= SUPPORT_SET_SPEED + if self._feature_preset_mode: + self._supported_features |= SUPPORT_PRESET_MODE for key, tpl in list(self._templates.items()): if tpl is None: @@ -217,19 +350,103 @@ def state_received(msg): "qos": self._config[CONF_QOS], } + @callback + @log_messages(self.hass, self.entity_id) + def percentage_received(msg): + """Handle new received MQTT message for the percentage.""" + numeric_val_str = self._templates[ATTR_PERCENTAGE](msg.payload) + try: + percentage = ranged_value_to_percentage( + self._speed_range, int(numeric_val_str) + ) + except ValueError: + _LOGGER.warning( + "'%s' received on topic %s is not a valid speed within the speed range", + msg.payload, + msg.topic, + ) + return + if percentage < 0 or percentage > 100: + _LOGGER.warning( + "'%s' received on topic %s is not a valid speed within the speed range", + msg.payload, + msg.topic, + ) + return + self._percentage = percentage + self.async_write_ha_state() + + if self._topic[CONF_PERCENTAGE_STATE_TOPIC] is not None: + topics[CONF_PERCENTAGE_STATE_TOPIC] = { + "topic": self._topic[CONF_PERCENTAGE_STATE_TOPIC], + "msg_callback": percentage_received, + "qos": self._config[CONF_QOS], + } + self._percentage = None + + @callback + @log_messages(self.hass, self.entity_id) + def preset_mode_received(msg): + """Handle new received MQTT message for preset mode.""" + preset_mode = self._templates[ATTR_PRESET_MODE](msg.payload) + if preset_mode not in self.preset_modes: + _LOGGER.warning( + "'%s' received on topic %s is not a valid preset mode", + msg.payload, + msg.topic, + ) + return + + self._preset_mode = preset_mode + if not self._implemented_percentage and (preset_mode in self.speed_list): + self._percentage = ordered_list_item_to_percentage( + self.speed_list, preset_mode + ) + self.async_write_ha_state() + + if self._topic[CONF_PRESET_MODE_STATE_TOPIC] is not None: + topics[CONF_PRESET_MODE_STATE_TOPIC] = { + "topic": self._topic[CONF_PRESET_MODE_STATE_TOPIC], + "msg_callback": preset_mode_received, + "qos": self._config[CONF_QOS], + } + self._preset_mode = None + + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) @callback @log_messages(self.hass, self.entity_id) def speed_received(msg): """Handle new received MQTT message for the speed.""" - payload = self._templates[ATTR_SPEED](msg.payload) - if payload == self._payload["SPEED_LOW"]: - self._speed = SPEED_LOW - elif payload == self._payload["SPEED_MEDIUM"]: - self._speed = SPEED_MEDIUM - elif payload == self._payload["SPEED_HIGH"]: - self._speed = SPEED_HIGH - elif payload == self._payload["SPEED_OFF"]: - self._speed = SPEED_OFF + speed_payload = self._templates[ATTR_SPEED](msg.payload) + if speed_payload == self._payload["SPEED_LOW"]: + speed = SPEED_LOW + elif speed_payload == self._payload["SPEED_MEDIUM"]: + speed = SPEED_MEDIUM + elif speed_payload == self._payload["SPEED_HIGH"]: + speed = SPEED_HIGH + elif speed_payload == self._payload["SPEED_OFF"]: + speed = SPEED_OFF + else: + speed = None + + if speed and speed in self._legacy_speeds_list: + self._speed = speed + else: + _LOGGER.warning( + "'%s' received on topic %s is not a valid speed", + msg.payload, + msg.topic, + ) + return + + if not self._implemented_percentage: + if speed in self._speeds_list: + self._percentage = ordered_list_item_to_percentage( + self._speeds_list, speed + ) + elif speed == SPEED_OFF: + self._percentage = 0 + self.async_write_ha_state() if self._topic[CONF_SPEED_STATE_TOPIC] is not None: @@ -273,10 +490,42 @@ def is_on(self): """Return true if device is on.""" return self._state + @property + def _implemented_percentage(self): + """Return true if percentage has been implemented.""" + return self._feature_percentage + + @property + def _implemented_preset_mode(self): + """Return true if preset_mode has been implemented.""" + return self._feature_preset_mode + + # The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7) + @property + def _implemented_speed(self): + """Return true if speed has been implemented.""" + return self._feature_legacy_speeds + + @property + def percentage(self): + """Return the current percentage.""" + return self._percentage + + @property + def preset_mode(self): + """Return the current preset _mode.""" + return self._preset_mode + + @property + def preset_modes(self) -> list: + """Get the list of available preset modes.""" + return self._preset_modes + + # The speed_list property is deprecated in the schema, support will be removed after a quarter (2021.7) @property def speed_list(self) -> list: """Get the list of available speeds.""" - return self._config[CONF_SPEED_LIST] + return self._speeds_list @property def supported_features(self) -> int: @@ -288,18 +537,17 @@ def speed(self): """Return the current speed.""" return self._speed + @property + def speed_count(self) -> int: + """Return the number of speeds the fan supports or 100 if percentage is supported.""" + return self._speed_count + @property def oscillating(self): """Return the oscillation state.""" return self._oscillation - # - # The fan entity model has changed to use percentages and preset_modes - # instead of speeds. - # - # Please review - # https://developers.home-assistant.io/docs/core/entity/fan/ - # + # The speed attribute deprecated in the schema, support will be removed after a quarter (2021.7) async def async_turn_on( self, speed: str = None, @@ -318,7 +566,12 @@ async def async_turn_on( self._config[CONF_QOS], self._config[CONF_RETAIN], ) - if speed: + if percentage: + await self.async_set_percentage(percentage) + if preset_mode: + await self.async_set_preset_mode(preset_mode) + # The speed attribute deprecated in the schema, support will be removed after a quarter (2021.7) + if speed and not percentage and not preset_mode: await self.async_set_speed(speed) if self._optimistic: self._state = True @@ -340,32 +593,111 @@ async def async_turn_off(self, **kwargs) -> None: self._state = False self.async_write_ha_state() - async def async_set_speed(self, speed: str) -> None: - """Set the speed of the fan. + async def async_set_percentage(self, percentage: int) -> None: + """Set the percentage of the fan. This method is a coroutine. """ - if speed == SPEED_LOW: - mqtt_payload = self._payload["SPEED_LOW"] - elif speed == SPEED_MEDIUM: - mqtt_payload = self._payload["SPEED_MEDIUM"] - elif speed == SPEED_HIGH: - mqtt_payload = self._payload["SPEED_HIGH"] - elif speed == SPEED_OFF: - mqtt_payload = self._payload["SPEED_OFF"] - else: - raise ValueError(f"{speed} is not a valid fan speed") + percentage_payload = int( + percentage_to_ranged_value(self._speed_range, percentage) + ) + if self._implemented_preset_mode: + if percentage: + await self.async_set_preset_mode( + preset_mode=percentage_to_ordered_list_item( + self.speed_list, percentage + ) + ) + # Legacy are deprecated in the schema, support will be removed after a quarter (2021.7) + elif self._feature_legacy_speeds and ( + SPEED_OFF in self._legacy_speeds_list + ): + await self.async_set_preset_mode(SPEED_OFF) + # Legacy are deprecated in the schema, support will be removed after a quarter (2021.7) + elif self._feature_legacy_speeds: + if percentage: + await self.async_set_speed( + percentage_to_ordered_list_item( + self._legacy_speeds_list_no_off, + percentage, + ) + ) + elif SPEED_OFF in self._legacy_speeds_list: + await self.async_set_speed(SPEED_OFF) + + if self._implemented_percentage: + mqtt.async_publish( + self.hass, + self._topic[CONF_PERCENTAGE_COMMAND_TOPIC], + percentage_payload, + self._config[CONF_QOS], + self._config[CONF_RETAIN], + ) + + if self._optimistic_percentage: + self._percentage = percentage + 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 + # Legacy are deprecated in the schema, support will be removed after a quarter (2021.7) + if preset_mode in self._legacy_speeds_list: + await self.async_set_speed(speed=preset_mode) + if not self._implemented_percentage and preset_mode in self.speed_list: + self._percentage = ordered_list_item_to_percentage( + self.speed_list, preset_mode + ) + mqtt_payload = preset_mode mqtt.async_publish( self.hass, - self._topic[CONF_SPEED_COMMAND_TOPIC], + self._topic[CONF_PRESET_MODE_COMMAND_TOPIC], mqtt_payload, self._config[CONF_QOS], self._config[CONF_RETAIN], ) - if self._optimistic_speed: - self._speed = speed + if self._optimistic_preset_mode: + self._preset_mode = preset_mode + self.async_write_ha_state() + + # async_set_speed is deprecated, support will be removed after a quarter (2021.7) + async def async_set_speed(self, speed: str) -> None: + """Set the speed of the fan. + + This method is a coroutine. + """ + speed_payload = None + if self._feature_legacy_speeds: + if speed == SPEED_LOW: + speed_payload = self._payload["SPEED_LOW"] + elif speed == SPEED_MEDIUM: + speed_payload = self._payload["SPEED_MEDIUM"] + elif speed == SPEED_HIGH: + speed_payload = self._payload["SPEED_HIGH"] + elif speed == SPEED_OFF: + speed_payload = self._payload["SPEED_OFF"] + else: + _LOGGER.warning("'%s'is not a valid speed", speed) + return + + if speed_payload: + mqtt.async_publish( + self.hass, + self._topic[CONF_SPEED_COMMAND_TOPIC], + speed_payload, + self._config[CONF_QOS], + self._config[CONF_RETAIN], + ) + + if self._optimistic_speed and speed_payload: + self._speed = speed self.async_write_ha_state() async def async_oscillate(self, oscillating: bool) -> None: diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 045b8fdaf0ee5d..e1ce19c970aa66 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -2,8 +2,10 @@ from unittest.mock import patch import pytest +from voluptuous.error import MultipleInvalid from homeassistant.components import fan +from homeassistant.components.fan import NotValidPresetModeError from homeassistant.const import ( ATTR_ASSUMED_STATE, ATTR_SUPPORTED_FEATURES, @@ -58,7 +60,7 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): assert hass.states.get("fan.test") is None -async def test_controlling_state_via_topic(hass, mqtt_mock): +async def test_controlling_state_via_topic(hass, mqtt_mock, caplog): """Test the controlling state via topic.""" assert await async_setup_component( hass, @@ -73,10 +75,27 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): "payload_on": "StAtE_On", "oscillation_state_topic": "oscillation-state-topic", "oscillation_command_topic": "oscillation-command-topic", - "payload_oscillation_off": "OsC_OfF", - "payload_oscillation_on": "OsC_On", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_state_topic": "speed-state-topic", "speed_command_topic": "speed-command-topic", + "payload_oscillation_off": "OsC_OfF", + "payload_oscillation_on": "OsC_On", + "percentage_state_topic": "percentage-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "medium", + "medium-high", + "high", + "very-high", + "freaking-high", + "silent", + ], + "speed_range_min": 1, + "speed_range_max": 200, + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low"], "payload_off_speed": "speed_OfF", "payload_low_speed": "speed_lOw", "payload_medium_speed": "speed_mEdium", @@ -87,16 +106,16 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert not state.attributes.get(ATTR_ASSUMED_STATE) async_fire_mqtt_message(hass, "state-topic", "StAtE_On") state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON async_fire_mqtt_message(hass, "state-topic", "StAtE_OfF") state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get("oscillating") is False async_fire_mqtt_message(hass, "oscillation-state-topic", "OsC_On") @@ -107,6 +126,51 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): state = hass.states.get("fan.test") assert state.attributes.get("oscillating") is False + async_fire_mqtt_message(hass, "percentage-state-topic", "0") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + + async_fire_mqtt_message(hass, "percentage-state-topic", "50") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25 + + async_fire_mqtt_message(hass, "percentage-state-topic", "100") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + + async_fire_mqtt_message(hass, "percentage-state-topic", "200") + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + + async_fire_mqtt_message(hass, "percentage-state-topic", "202") + assert "not a valid speed within the speed range" in caplog.text + caplog.clear() + + async_fire_mqtt_message(hass, "percentage-state-topic", "invalid") + assert "not a valid speed within the speed range" in caplog.text + caplog.clear() + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "low") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "medium") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "medium" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "very-high") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "very-high" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "silent") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "silent" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "ModeUnknown") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get("speed") == fan.SPEED_OFF async_fire_mqtt_message(hass, "speed-state-topic", "speed_lOw") @@ -114,20 +178,173 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): assert state.attributes.get("speed") == fan.SPEED_LOW async_fire_mqtt_message(hass, "speed-state-topic", "speed_mEdium") + assert "not a valid speed" in caplog.text + caplog.clear() + + async_fire_mqtt_message(hass, "speed-state-topic", "speed_High") + assert "not a valid speed" in caplog.text + caplog.clear() + + async_fire_mqtt_message(hass, "speed-state-topic", "speed_OfF") + state = hass.states.get("fan.test") + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "speed-state-topic", "speed_very_high") + assert "not a valid speed" in caplog.text + caplog.clear() + + +async def test_controlling_state_via_topic_with_different_speed_range( + hass, mqtt_mock, caplog +): + """Test the controlling state via topic using an alternate speed range.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: [ + { + "platform": "mqtt", + "name": "test1", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic1", + "percentage_command_topic": "percentage-command-topic1", + "speed_range_min": 1, + "speed_range_max": 100, + }, + { + "platform": "mqtt", + "name": "test2", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic2", + "percentage_command_topic": "percentage-command-topic2", + "speed_range_min": 1, + "speed_range_max": 200, + }, + { + "platform": "mqtt", + "name": "test3", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic3", + "percentage_command_topic": "percentage-command-topic3", + "speed_range_min": 81, + "speed_range_max": 1023, + }, + ] + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "percentage-state-topic1", "100") + state = hass.states.get("fan.test1") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + + async_fire_mqtt_message(hass, "percentage-state-topic2", "100") + state = hass.states.get("fan.test2") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + + async_fire_mqtt_message(hass, "percentage-state-topic3", "1023") + state = hass.states.get("fan.test3") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + async_fire_mqtt_message(hass, "percentage-state-topic3", "80") + state = hass.states.get("fan.test3") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + + state = hass.states.get("fan.test3") + async_fire_mqtt_message(hass, "percentage-state-topic3", "79") + assert "not a valid speed within the speed range" in caplog.text + caplog.clear() + + +async def test_controlling_state_via_topic_no_percentage_topics(hass, mqtt_mock): + """Test the controlling state via topic without percentage topics.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_state_topic": "speed-state-topic", + "speed_command_topic": "speed-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "freaking-high") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "freaking-high" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "high") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "high" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 75 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "silent") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "silent" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 75 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "medium") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "medium" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + async_fire_mqtt_message(hass, "preset-mode-state-topic", "low") + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get("speed") == fan.SPEED_OFF + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + async_fire_mqtt_message(hass, "speed-state-topic", "medium") state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50 assert state.attributes.get("speed") == fan.SPEED_MEDIUM - async_fire_mqtt_message(hass, "speed-state-topic", "speed_High") + async_fire_mqtt_message(hass, "speed-state-topic", "low") state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_HIGH + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25 + assert state.attributes.get("speed") == fan.SPEED_LOW - async_fire_mqtt_message(hass, "speed-state-topic", "speed_OfF") + async_fire_mqtt_message(hass, "speed-state-topic", "off") state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "low" + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 assert state.attributes.get("speed") == fan.SPEED_OFF -async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): - """Test the controlling state via topic and JSON message.""" +async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, caplog): + """Test the controlling state via topic and JSON message (percentage mode).""" assert await async_setup_component( hass, fan.DOMAIN, @@ -139,27 +356,40 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): "command_topic": "command-topic", "oscillation_state_topic": "oscillation-state-topic", "oscillation_command_topic": "oscillation-command-topic", - "speed_state_topic": "speed-state-topic", - "speed_command_topic": "speed-command-topic", + "percentage_state_topic": "percentage-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "medium", + "medium-high", + "high", + "very-high", + "freaking-high", + "silent", + ], "state_value_template": "{{ value_json.val }}", "oscillation_value_template": "{{ value_json.val }}", - "speed_value_template": "{{ value_json.val }}", + "percentage_value_template": "{{ value_json.val }}", + "preset_mode_value_template": "{{ value_json.val }}", + "speed_range_min": 1, + "speed_range_max": 100, } }, ) await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF 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 is STATE_ON + assert state.state == STATE_ON async_fire_mqtt_message(hass, "state-topic", '{"val":"OFF"}') state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get("oscillating") is False async_fire_mqtt_message(hass, "oscillation-state-topic", '{"val":"oscillate_on"}') @@ -170,27 +400,761 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): state = hass.states.get("fan.test") assert state.attributes.get("oscillating") is False - assert state.attributes.get("speed") == fan.SPEED_OFF + async_fire_mqtt_message(hass, "percentage-state-topic", '{"val": 1}') + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 1 - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"low"}') + async_fire_mqtt_message(hass, "percentage-state-topic", '{"val": 100}') state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_LOW + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "low"}') + assert "not a valid preset mode" in caplog.text + caplog.clear() - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"medium"}') + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "medium"}') state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_MEDIUM + assert state.attributes.get("preset_mode") == "medium" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "freaking-high"}') + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "freaking-high" + + async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "silent"}') + state = hass.states.get("fan.test") + assert state.attributes.get("preset_mode") == "silent" + + +async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): + """Test optimistic mode without state topic.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "payload_off": "StAtE_OfF", + "payload_on": "StAtE_On", + "oscillation_command_topic": "oscillation-command-topic", + "payload_oscillation_off": "OsC_OfF", + "payload_oscillation_on": "OsC_On", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_command_topic": "speed-command-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "payload_off_speed": "speed_OfF", + "payload_low_speed": "speed_lOw", + "payload_medium_speed": "speed_mEdium", + "payload_high_speed": "speed_High", + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with( + "command-topic", "StAtE_On", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with( + "command-topic", "StAtE_OfF", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_oscillate(hass, "fan.test", True) + mqtt_mock.async_publish.assert_called_once_with( + "oscillation-command-topic", "OsC_On", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_oscillate(hass, "fan.test", False) + mqtt_mock.async_publish.assert_called_once_with( + "oscillation-command-topic", "OsC_OfF", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", -1) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_percentage(hass, "fan.test", 100) + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "0", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "off", 0, False + ) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call( + "speed-command-topic", "speed_OfF", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call( + "speed-command-topic", "speed_lOw", 0, False + ) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "low" + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_LOW + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call( + "speed-command-topic", "speed_mEdium", 0, False + ) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "medium" + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_MEDIUM + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "freaking-high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "silent" + assert state.attributes.get(ATTR_ASSUMED_STATE) - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"high"}') + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "speed_lOw", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "speed_mEdium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "speed_High", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "speed_OfF", 0, False + ) + mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_HIGH + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) - async_fire_mqtt_message(hass, "speed-state-topic", '{"val":"off"}') + +async def test_sending_mqtt_commands_with_alternate_speed_range(hass, mqtt_mock): + """Test the controlling state via topic using an alternate speed range.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: [ + { + "platform": "mqtt", + "name": "test1", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic1", + "percentage_command_topic": "percentage-command-topic1", + "speed_range_min": 1, + "speed_range_max": 100, + }, + { + "platform": "mqtt", + "name": "test2", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic2", + "percentage_command_topic": "percentage-command-topic2", + "speed_range_min": 1, + "speed_range_max": 200, + }, + { + "platform": "mqtt", + "name": "test3", + "command_topic": "command-topic", + "percentage_state_topic": "percentage-state-topic3", + "percentage_command_topic": "percentage-command-topic3", + "speed_range_min": 81, + "speed_range_max": 1023, + }, + ] + }, + ) + await hass.async_block_till_done() + + await common.async_set_percentage(hass, "fan.test1", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic1", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test1") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test1", 100) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic1", "100", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test1") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test2", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic2", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test2") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test2", 100) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic2", "200", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test2") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test3", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic3", "80", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test3") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test3", 100) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic3", "1023", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test3") + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, caplog): + """Test optimistic mode without state topic without legacy speed command topic.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "ON", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", -1) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_percentage(hass, "fan.test", 100) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + mqtt_mock.async_publish.assert_called_once_with( + "percentage-command-topic", "0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get(fan.ATTR_SPEED) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "freaking-high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) == "silent" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", percentage=25) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "25", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", preset_mode="high") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + with pytest.raises(NotValidPresetModeError): + await common.async_turn_on(hass, "fan.test", preset_mode="low") + + +async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic( + hass, mqtt_mock +): + """Test optimistic mode without state topic without percentage command topic.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_state_topic": "speed-state-topic", + "speed_command_topic": "speed-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", -1) + + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_percentage(hass, "fan.test", 100) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "freaking-high") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "freaking-high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "silent") + mqtt_mock.async_publish.assert_called_once_with( + "preset-mode-command-topic", "silent", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PRESET_MODE) is None + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) + + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed="medium") + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + await common.async_turn_on(hass, "fan.test", speed="high") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +# use of speeds is deprecated, support will be removed after a quarter (2021.7) +async def test_sending_mqtt_commands_and_optimistic_legacy_speeds_only( + hass, mqtt_mock, caplog +): + """Test optimistic mode without state topics with legacy speeds.""" + assert await async_setup_component( + hass, + fan.DOMAIN, + { + fan.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "command-topic", + "speed_state_topic": "speed-state-topic", + "speed_command_topic": "speed-command-topic", + "speeds": ["off", "low", "medium", "high"], + } + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 100) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "high", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100 + assert state.attributes.get(fan.ATTR_SPEED) == "off" + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_percentage(hass, "fan.test", 0) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0 + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "high", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) + mqtt_mock.async_publish.assert_called_once_with( + "speed-command-topic", "off", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed="medium") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", speed="off") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.attributes.get("speed") == fan.SPEED_OFF + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) -async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): - """Test optimistic mode without state topic.""" +async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, caplog): + """Test optimistic mode with state topic and turn on attributes.""" assert await async_setup_component( hass, fan.DOMAIN, @@ -198,223 +1162,307 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): fan.DOMAIN: { "platform": "mqtt", "name": "test", + "state_topic": "state-topic", "command_topic": "command-topic", - "payload_off": "StAtE_OfF", - "payload_on": "StAtE_On", - "oscillation_command_topic": "oscillation-command-topic", - "oscillation_state_topic": "oscillation-state-topic", - "payload_oscillation_off": "OsC_OfF", - "payload_oscillation_on": "OsC_On", - "speed_command_topic": "speed-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_state_topic": "speed-state-topic", - "payload_off_speed": "speed_OfF", - "payload_low_speed": "speed_lOw", - "payload_medium_speed": "speed_mEdium", - "payload_high_speed": "speed_High", + "speed_command_topic": "speed-command-topic", + "oscillation_state_topic": "oscillation-state-topic", + "oscillation_command_topic": "oscillation-command-topic", + "percentage_state_topic": "percentage-state-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_mode_state_topic": "preset-mode-state-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speeds": ["off", "low", "medium"], + "preset_modes": [ + "high", + "freaking-high", + "silent", + ], + "optimistic": True, } }, ) await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_on(hass, "fan.test") - mqtt_mock.async_publish.assert_called_once_with( - "command-topic", "StAtE_On", 0, False - ) + mqtt_mock.async_publish.assert_called_once_with("command-topic", "ON", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_turn_off(hass, "fan.test") - mqtt_mock.async_publish.assert_called_once_with( - "command-topic", "StAtE_OfF", 0, False - ) + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_oscillate(hass, "fan.test", True) - mqtt_mock.async_publish.assert_called_once_with( - "oscillation-command-topic", "OsC_On", 0, False + await common.async_turn_on(hass, "fan.test", speed=fan.SPEED_MEDIUM) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_oscillate(hass, "fan.test", False) - mqtt_mock.async_publish.assert_called_once_with( - "oscillation-command-topic", "OsC_OfF", 0, False - ) + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) - mqtt_mock.async_publish.assert_called_once_with( - "speed-command-topic", "speed_lOw", 0, False + await common.async_turn_on(hass, "fan.test", percentage=25) + assert mqtt_mock.async_publish.call_count == 4 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False ) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "25", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) - mqtt_mock.async_publish.assert_called_once_with( - "speed-command-topic", "speed_mEdium", 0, False - ) + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) - mqtt_mock.async_publish.assert_called_once_with( - "speed-command-topic", "speed_High", 0, False + await common.async_turn_on(hass, "fan.test", preset_mode="medium") + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False ) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) - mqtt_mock.async_publish.assert_called_once_with( - "speed-command-topic", "speed_OfF", 0, False + await common.async_turn_on(hass, "fan.test", preset_mode="high") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "high", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) -async def test_on_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): - """Test on with speed.""" - assert await async_setup_component( - hass, - fan.DOMAIN, - { - fan.DOMAIN: { - "platform": "mqtt", - "name": "test", - "command_topic": "command-topic", - "oscillation_command_topic": "oscillation-command-topic", - "speed_command_topic": "speed-command-topic", - } - }, + await common.async_turn_on(hass, "fan.test", preset_mode="silent") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "silent", 0, False ) - await hass.async_block_till_done() + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_ON + assert state.attributes.get(ATTR_ASSUMED_STATE) + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_turn_on(hass, "fan.test") - mqtt_mock.async_publish.assert_called_once_with("command-topic", "ON", 0, False) + await common.async_turn_on(hass, "fan.test", preset_mode="silent") + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "silent", 0, False + ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) - assert state.attributes.get(fan.ATTR_SPEED) is None - assert state.attributes.get(fan.ATTR_OSCILLATING) is None await common.async_turn_off(hass, "fan.test") mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_turn_on(hass, "fan.test", speed="low") - assert mqtt_mock.async_publish.call_count == 2 + await common.async_oscillate(hass, "fan.test", True) + mqtt_mock.async_publish.assert_called_once_with( + "oscillation-command-topic", "oscillate_on", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_on(hass, "fan.test", percentage=50) + assert mqtt_mock.async_publish.call_count == 4 mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False) - mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "50", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) - assert state.attributes.get(fan.ATTR_SPEED) == "low" - assert state.attributes.get(fan.ATTR_OSCILLATING) is None + await common.async_turn_off(hass, "fan.test") + mqtt_mock.async_publish.assert_any_call("command-topic", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) -async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): - """Test optimistic mode with state topic.""" - assert await async_setup_component( - hass, - fan.DOMAIN, - { - fan.DOMAIN: { - "platform": "mqtt", - "name": "test", - "state_topic": "state-topic", - "command_topic": "command-topic", - "oscillation_state_topic": "oscillation-state-topic", - "oscillation_command_topic": "oscillation-command-topic", - "speed_state_topic": "speed-state-topic", - "speed_command_topic": "speed-command-topic", - "optimistic": True, - } - }, + await common.async_oscillate(hass, "fan.test", False) + mqtt_mock.async_publish.assert_called_once_with( + "oscillation-command-topic", "oscillate_off", 0, False ) - await hass.async_block_till_done() + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + await common.async_set_percentage(hass, "fan.test", 33) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "33", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_turn_on(hass, "fan.test") - mqtt_mock.async_publish.assert_called_once_with("command-topic", "ON", 0, False) + await common.async_set_percentage(hass, "fan.test", 50) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "50", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_turn_off(hass, "fan.test") - mqtt_mock.async_publish.assert_called_once_with("command-topic", "OFF", 0, False) + await common.async_set_percentage(hass, "fan.test", 100) + assert mqtt_mock.async_publish.call_count == 2 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "freaking-high", 0, False + ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_oscillate(hass, "fan.test", True) - mqtt_mock.async_publish.assert_called_once_with( - "oscillation-command-topic", "oscillate_on", 0, False + await common.async_set_percentage(hass, "fan.test", 0) + assert mqtt_mock.async_publish.call_count == 3 + mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "0", 0, False) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "off", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_oscillate(hass, "fan.test", False) + with pytest.raises(MultipleInvalid): + await common.async_set_percentage(hass, "fan.test", 101) + + await common.async_set_preset_mode(hass, "fan.test", "low") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "low", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "medium") + assert mqtt_mock.async_publish.call_count == 2 + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False) + mqtt_mock.async_publish.assert_any_call( + "preset-mode-command-topic", "medium", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "high") mqtt_mock.async_publish.assert_called_once_with( - "oscillation-command-topic", "oscillate_off", 0, False + "preset-mode-command-topic", "high", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) + await common.async_set_preset_mode(hass, "fan.test", "silent") mqtt_mock.async_publish.assert_called_once_with( - "speed-command-topic", "low", 0, False + "preset-mode-command-topic", "silent", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_set_preset_mode(hass, "fan.test", "ModeX") + assert "not a valid preset mode" in caplog.text + caplog.clear() + + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("fan.test") + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) mqtt_mock.async_publish.assert_called_once_with( "speed-command-topic", "medium", 0, False ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) @@ -423,7 +1471,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) @@ -432,14 +1480,15 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) - with pytest.raises(ValueError): - await common.async_set_speed(hass, "fan.test", "cUsToM") + await common.async_set_speed(hass, "fan.test", "cUsToM") + assert "not a valid speed" in caplog.text + caplog.clear() -async def test_attributes(hass, mqtt_mock): +async def test_attributes(hass, mqtt_mock, caplog): """Test attributes.""" assert await async_setup_component( hass, @@ -450,76 +1499,96 @@ async def test_attributes(hass, mqtt_mock): "name": "test", "command_topic": "command-topic", "oscillation_command_topic": "oscillation-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "percentage_command_topic": "percentage-command-topic", + "preset_modes": [ + "freaking-high", + "silent", + ], } }, ) await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF - assert state.attributes.get(fan.ATTR_SPEED_LIST) == ["off", "low", "medium", "high"] + assert state.state == STATE_OFF + assert state.attributes.get(fan.ATTR_SPEED_LIST) == [ + "low", + "medium", + "high", + "freaking-high", + ] await common.async_turn_on(hass, "fan.test") state = hass.states.get("fan.test") - assert state.state is STATE_ON + assert state.state == STATE_ON assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is None await common.async_turn_off(hass, "fan.test") state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is None await common.async_oscillate(hass, "fan.test", True) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is True await common.async_oscillate(hass, "fan.test", False) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) + # use of speeds is deprecated, support will be removed after a quarter (2021.7) assert state.attributes.get(fan.ATTR_SPEED) is None assert state.attributes.get(fan.ATTR_OSCILLATING) is False + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_LOW) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "low" assert state.attributes.get(fan.ATTR_OSCILLATING) is False + # use of speeds is deprecated, support will be removed after a quarter (2021.7) await common.async_set_speed(hass, "fan.test", fan.SPEED_MEDIUM) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "medium" assert state.attributes.get(fan.ATTR_OSCILLATING) is False await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "high" assert state.attributes.get(fan.ATTR_OSCILLATING) is False await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF) state = hass.states.get("fan.test") - assert state.state is STATE_OFF + assert state.state == STATE_OFF assert state.attributes.get(ATTR_ASSUMED_STATE) assert state.attributes.get(fan.ATTR_SPEED) == "off" assert state.attributes.get(fan.ATTR_OSCILLATING) is False - with pytest.raises(ValueError): - await common.async_set_speed(hass, "fan.test", "cUsToM") + await common.async_set_speed(hass, "fan.test", "cUsToM") + assert "not a valid speed" in caplog.text + caplog.clear() +# use of speeds is deprecated, support will be removed after a quarter (2021.7) async def test_custom_speed_list(hass, mqtt_mock): """Test optimistic mode without state topic.""" assert await async_setup_component( @@ -541,8 +1610,8 @@ async def test_custom_speed_list(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get("fan.test") - assert state.state is STATE_OFF - assert state.attributes.get(fan.ATTR_SPEED_LIST) == ["off", "high"] + assert state.state == STATE_OFF + assert state.attributes.get(fan.ATTR_SPEED_LIST) == ["high"] async def test_supported_features(hass, mqtt_mock): @@ -565,17 +1634,120 @@ async def test_supported_features(hass, mqtt_mock): }, { "platform": "mqtt", - "name": "test3", + "name": "test3a1", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_command_topic": "speed-command-topic", + }, + { + "platform": "mqtt", + "name": "test3a2", + "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) + "speed_command_topic": "speed-command-topic", + "speeds": ["low"], + }, + { + "platform": "mqtt", + "name": "test3a3", "command_topic": "command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", + "speeds": ["off"], + }, + { + "platform": "mqtt", + "name": "test3b", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + }, + { + "platform": "mqtt", + "name": "test3c1", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + }, + { + "platform": "mqtt", + "name": "test3c2", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["very-fast", "auto"], + }, + { + "platform": "mqtt", + "name": "test3c3", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["off", "on", "auto"], }, { "platform": "mqtt", "name": "test4", "command_topic": "command-topic", "oscillation_command_topic": "oscillation-command-topic", + # use of speeds is deprecated, support will be removed after a quarter (2021.7) "speed_command_topic": "speed-command-topic", }, + { + "platform": "mqtt", + "name": "test4pcta", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + }, + { + "platform": "mqtt", + "name": "test4pctb", + "command_topic": "command-topic", + "oscillation_command_topic": "oscillation-command-topic", + "percentage_command_topic": "percentage-command-topic", + }, + { + "platform": "mqtt", + "name": "test5pr_ma", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["Mode1", "Mode2", "Mode3"], + }, + { + "platform": "mqtt", + "name": "test5pr_mb", + "command_topic": "command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["off", "on", "auto"], + }, + { + "platform": "mqtt", + "name": "test5pr_mc", + "command_topic": "command-topic", + "oscillation_command_topic": "oscillation-command-topic", + "preset_mode_command_topic": "preset-mode-command-topic", + "preset_modes": ["Mode1", "Mode2", "Mode3"], + }, + { + "platform": "mqtt", + "name": "test6spd_range_a", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "speed_range_min": 1, + "speed_range_max": 40, + }, + { + "platform": "mqtt", + "name": "test6spd_range_b", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "speed_range_min": 50, + "speed_range_max": 40, + }, + { + "platform": "mqtt", + "name": "test6spd_range_c", + "command_topic": "command-topic", + "percentage_command_topic": "percentage-command-topic", + "speed_range_min": 0, + "speed_range_max": 40, + }, ] }, ) @@ -585,14 +1757,69 @@ async def test_supported_features(hass, mqtt_mock): assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 0 state = hass.states.get("fan.test2") assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_OSCILLATE - state = hass.states.get("fan.test3") + + state = hass.states.get("fan.test3a1") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + and fan.SUPPORT_SET_SPEED == fan.SUPPORT_SET_SPEED + ) + state = hass.states.get("fan.test3a2") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + and fan.SUPPORT_SET_SPEED == fan.SUPPORT_SET_SPEED + ) + state = hass.states.get("fan.test3a3") + assert state is None + + state = hass.states.get("fan.test3b") assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED + + state = hass.states.get("fan.test3c1") + assert state is None + + state = hass.states.get("fan.test3c2") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_PRESET_MODE | fan.SUPPORT_SET_SPEED + ) + state = hass.states.get("fan.test3c3") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE + state = hass.states.get("fan.test4") assert ( state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED ) + state = hass.states.get("fan.test4pcta") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED + state = hass.states.get("fan.test4pctb") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED + ) + + state = hass.states.get("fan.test5pr_ma") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_SET_SPEED | fan.SUPPORT_PRESET_MODE + ) + state = hass.states.get("fan.test5pr_mb") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE + + state = hass.states.get("fan.test5pr_mc") + assert ( + state.attributes.get(ATTR_SUPPORTED_FEATURES) + == fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED | fan.SUPPORT_PRESET_MODE + ) + + state = hass.states.get("fan.test6spd_range_a") + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED + state = hass.states.get("fan.test6spd_range_b") + assert state is None + state = hass.states.get("fan.test6spd_range_c") + assert state is None + async def test_availability_when_connection_lost(hass, mqtt_mock): """Test availability after MQTT disconnection.""" @@ -643,7 +1870,7 @@ async def test_update_with_json_attrs_not_dict(hass, mqtt_mock, caplog): ) -async def test_update_with_json_attrs_bad_JSON(hass, mqtt_mock, caplog): +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, fan.DOMAIN, DEFAULT_CONFIG From 4fbc3da196650ee587ec023c04fad1cf91d16843 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 08:46:26 +0100 Subject: [PATCH 1516/1818] Validate device action schemas once (#48351) --- .../components/alarm_control_panel/device_action.py | 2 -- homeassistant/components/climate/device_action.py | 2 -- homeassistant/components/cover/device_action.py | 2 -- homeassistant/components/fan/device_action.py | 2 -- homeassistant/components/humidifier/device_action.py | 2 -- homeassistant/components/lock/device_action.py | 2 -- homeassistant/components/number/device_action.py | 10 ---------- homeassistant/components/water_heater/device_action.py | 2 -- 8 files changed, 24 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 67637550db2710..9a55998e929142 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -112,8 +112,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if CONF_CODE in config: service_data[ATTR_CODE] = config[CONF_CODE] diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 18123ab11f7bd5..02474a47f96ea8 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -79,8 +79,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "set_hvac_mode": diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 6981f87c492568..74eef8102dfd66 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -165,8 +165,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "open": diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index b42c8145470059..f4611d353d512a 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -62,8 +62,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "turn_on": diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index a68b4d771ef295..fa9c1eb71e7703 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -82,8 +82,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "set_humidity": diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index 639947f3b88a99..cb0e2b0daadea1 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -78,8 +78,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "lock": diff --git a/homeassistant/components/number/device_action.py b/homeassistant/components/number/device_action.py index 1a26226962cce8..77b36b49f20a79 100644 --- a/homeassistant/components/number/device_action.py +++ b/homeassistant/components/number/device_action.py @@ -55,11 +55,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - - if config[CONF_TYPE] != ATYP_SET_VALUE: - return - await hass.services.async_call( DOMAIN, const.SERVICE_SET_VALUE, @@ -74,11 +69,6 @@ async def async_call_action_from_config( async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> dict: """List action capabilities.""" - action_type = config[CONF_TYPE] - - if action_type != ATYP_SET_VALUE: - return {} - fields = {vol.Required(const.ATTR_VALUE): vol.Coerce(float)} return {"extra_fields": vol.Schema(fields)} diff --git a/homeassistant/components/water_heater/device_action.py b/homeassistant/components/water_heater/device_action.py index f138c777d44e34..e1c84be8753d7a 100644 --- a/homeassistant/components/water_heater/device_action.py +++ b/homeassistant/components/water_heater/device_action.py @@ -61,8 +61,6 @@ async def async_call_action_from_config( hass: HomeAssistant, config: dict, variables: dict, context: Context | None ) -> None: """Execute a device action.""" - config = ACTION_SCHEMA(config) - service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} if config[CONF_TYPE] == "turn_on": From 1ba54ac2bbd3be853b259942ce3b47e11c6c9b5b Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 26 Mar 2021 11:13:27 +0100 Subject: [PATCH 1517/1818] Refactor Netatmo tests (#48277) --- tests/components/netatmo/common.py | 4 + tests/components/netatmo/test_camera.py | 35 ++++++- tests/components/netatmo/test_init.py | 6 +- tests/components/netatmo/test_light.py | 8 +- tests/components/netatmo/test_webhook.py | 126 ----------------------- 5 files changed, 41 insertions(+), 138 deletions(-) delete mode 100644 tests/components/netatmo/test_webhook.py diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index 6bafd12aceada0..54e7610c4e5442 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -31,6 +31,10 @@ TEST_TIME = 1559347200.0 +FAKE_WEBHOOK_ACTIVATION = { + "push_type": "webhook_activation", +} + def fake_post_request(**args): """Return fake data.""" diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index 10e3ca46b2a3a3..372af748267fff 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -5,6 +5,7 @@ from homeassistant.components import camera from homeassistant.components.camera import STATE_STREAMING from homeassistant.components.netatmo.const import ( + NETATMO_EVENT, SERVICE_SET_CAMERA_LIGHT, SERVICE_SET_PERSON_AWAY, SERVICE_SET_PERSONS_HOME, @@ -14,7 +15,7 @@ from .common import fake_post_request, simulate_webhook -from tests.common import async_fire_time_changed +from tests.common import async_capture_events, async_fire_time_changed async def test_setup_component_with_webhook(hass, camera_entry): @@ -253,3 +254,35 @@ async def test_camera_reconnect_webhook(hass, config_entry): ) await hass.async_block_till_done() mock_post.assert_called() + + +async def test_webhook_person_event(hass, camera_entry): + """Test that person events are handled.""" + test_netatmo_event = async_capture_events(hass, NETATMO_EVENT) + assert not test_netatmo_event + + fake_webhook_event = { + "persons": [ + { + "id": "91827374-7e04-5298-83ad-a0cb8372dff1", + "face_id": "a1b2c3d4e5", + "face_key": "9876543", + "is_known": True, + "face_url": "https://netatmocameraimage.blob.core.windows.net/production/12345", + } + ], + "snapshot_id": "123456789abc", + "snapshot_key": "foobar123", + "snapshot_url": "https://netatmocameraimage.blob.core.windows.net/production/12346", + "event_type": "person", + "camera_id": "12:34:56:00:f1:62", + "device_id": "12:34:56:00:f1:62", + "event_id": "1234567890", + "message": "MYHOME: John Doe has been seen by Indoor Camera ", + "push_type": "NACamera-person", + } + + webhook_id = camera_entry.data[CONF_WEBHOOK_ID] + await simulate_webhook(hass, webhook_id, fake_webhook_event) + + assert test_netatmo_event diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 1ad07fe55d3bd7..2ec7d83689eac6 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -7,7 +7,7 @@ from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.setup import async_setup_component -from .common import fake_post_request, simulate_webhook +from .common import FAKE_WEBHOOK_ACTIVATION, fake_post_request, simulate_webhook from tests.common import MockConfigEntry from tests.components.cloud import mock_cloud @@ -37,10 +37,6 @@ "push_type": "display_change", } -FAKE_WEBHOOK_ACTIVATION = { - "push_type": "webhook_activation", -} - async def test_setup_component(hass): """Test setup and teardown of the netatmo component.""" diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py index 8f2f371a15e2f4..4d84bc4e5a5e57 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -8,7 +8,7 @@ ) from homeassistant.const import ATTR_ENTITY_ID, CONF_WEBHOOK_ID -from .common import simulate_webhook +from .common import FAKE_WEBHOOK_ACTIVATION, simulate_webhook async def test_light_setup_and_services(hass, light_entry): @@ -16,10 +16,7 @@ async def test_light_setup_and_services(hass, light_entry): webhook_id = light_entry.data[CONF_WEBHOOK_ID] # Fake webhook activation - webhook_data = { - "push_type": "webhook_activation", - } - await simulate_webhook(hass, webhook_id, webhook_data) + await simulate_webhook(hass, webhook_id, FAKE_WEBHOOK_ACTIVATION) await hass.async_block_till_done() light_entity = "light.netatmo_garden" @@ -40,7 +37,6 @@ async def test_light_setup_and_services(hass, light_entry): # Trigger light mode change with erroneous webhook data response = { - "user_id": "91763b24c43d3e344f424e8d", "event_type": "light_mode", "device_id": "12:34:56:00:a5:a4", } diff --git a/tests/components/netatmo/test_webhook.py b/tests/components/netatmo/test_webhook.py deleted file mode 100644 index a56bd2f0fde9bb..00000000000000 --- a/tests/components/netatmo/test_webhook.py +++ /dev/null @@ -1,126 +0,0 @@ -"""The tests for Netatmo webhook events.""" -from homeassistant.components.netatmo.const import DATA_DEVICE_IDS, DATA_PERSONS -from homeassistant.components.netatmo.webhook import async_handle_webhook -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.util.aiohttp import MockRequest - - -async def test_webhook(hass): - """Test that webhook events are processed.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = ( - b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' - b'"push_type": "webhook_activation"}' - ) - request = MockRequest(content=response, mock_source="test") - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-None", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert webhook_called - - -async def test_webhook_error_in_data(hass): - """Test that errors in webhook data are handled.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = b'""webhook_activation"}' - request = MockRequest(content=response, mock_source="test") - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-None", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert not webhook_called - - -async def test_webhook_climate_event(hass): - """Test that climate events are handled.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = ( - b'{"user_id": "123", "user": {"id": "123", "email": "foo@bar.com"},' - b'"home_id": "456", "event_type": "therm_mode",' - b'"home": {"id": "456", "therm_mode": "away"},' - b'"mode": "away", "previous_mode": "schedule", "push_type": "home_event_changed"}' - ) - request = MockRequest(content=response, mock_source="test") - - hass.data["netatmo"] = { - DATA_DEVICE_IDS: {}, - } - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-therm_mode", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert webhook_called - - -async def test_webhook_person_event(hass): - """Test that person events are handled.""" - webhook_called = False - - async def handle_event(_): - nonlocal webhook_called - webhook_called = True - - response = ( - b'{"user_id": "5c81004xxxxxxxxxx45f4",' - b'"persons": [{"id": "e2bf7xxxxxxxxxxxxea3", "face_id": "5d66xxxxxx9b9",' - b'"face_key": "89dxxxxx22", "is_known": true,' - b'"face_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxx"}],' - b'"snapshot_id": "5d19bae867368a59e81cca89", "snapshot_key": "d3b3ae0229f7xb74cf8",' - b'"snapshot_url": "https://netatmocameraimage.blob.core.windows.net/production/5xxxx",' - b'"event_type": "person", "camera_id": "70:xxxxxx:a7", "device_id": "70:xxxxxx:a7",' - b'"home_id": "5c5dxxxxxxxd594", "home_name": "Boulogne Billan.",' - b'"event_id": "5d19bxxxxxxxxcca88",' - b'"message": "Boulogne Billan.: Benoit has been seen by Indoor Camera ",' - b'"push_type": "NACamera-person"}' - ) - request = MockRequest(content=response, mock_source="test") - - hass.data["netatmo"] = { - DATA_DEVICE_IDS: {}, - DATA_PERSONS: {}, - } - - async_dispatcher_connect( - hass, - "signal-netatmo-webhook-person", - handle_event, - ) - - await async_handle_webhook(hass, "webhook_id", request) - await hass.async_block_till_done() - - assert webhook_called From 8f40c87069fdf0487ca6785a1e91cf00257e5b33 Mon Sep 17 00:00:00 2001 From: D3v01dZA Date: Fri, 26 Mar 2021 07:03:38 -0400 Subject: [PATCH 1518/1818] Bump snapcast to 2.1.2 (#48343) --- homeassistant/components/snapcast/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json index 4e65b60280b8d9..43fbbeb8808fa6 100644 --- a/homeassistant/components/snapcast/manifest.json +++ b/homeassistant/components/snapcast/manifest.json @@ -2,6 +2,6 @@ "domain": "snapcast", "name": "Snapcast", "documentation": "https://www.home-assistant.io/integrations/snapcast", - "requirements": ["snapcast==2.1.1"], + "requirements": ["snapcast==2.1.2"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 257bd9710fb0ff..932f416f6a24ce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2078,7 +2078,7 @@ smarthab==0.21 smhi-pkg==1.0.13 # homeassistant.components.snapcast -snapcast==2.1.1 +snapcast==2.1.2 # homeassistant.components.socialblade socialbladeclient==0.5 From c4f98a3084f2648749d3e4eeeade9696630d9abd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 01:05:19 -1000 Subject: [PATCH 1519/1818] Small speed up to adding entities (#48353) --- homeassistant/helpers/entity_platform.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 382ebf8055ec60..6f41c67ef01408 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -18,7 +18,12 @@ valid_entity_id, ) from homeassistant.exceptions import HomeAssistantError, PlatformNotReady -from homeassistant.helpers import config_validation as cv, service +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dev_reg, + entity_registry as ent_reg, + service, +) from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.async_ import run_callback_threadsafe @@ -298,8 +303,8 @@ async def async_add_entities( hass = self.hass - device_registry = await hass.helpers.device_registry.async_get_registry() - entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = dev_reg.async_get(hass) + entity_registry = ent_reg.async_get(hass) tasks = [ self._async_add_entity( # type: ignore entity, update_before_add, entity_registry, device_registry From ae8afb69e767d99dd221e3a65a73688b9663b98b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 02:47:32 -1000 Subject: [PATCH 1520/1818] Improve august reconnect logic when service become unreachable (#48349) This is a different error than internet down. --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 4edacbbf64a1d2..f74287b31bd78c 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.5"], + "requirements": ["yalexs==1.1.6"], "codeowners": ["@bdraco"], "dhcp": [ {"hostname":"connect","macaddress":"D86162*"}, diff --git a/requirements_all.txt b/requirements_all.txt index 932f416f6a24ce..a6868b8aeac347 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2348,7 +2348,7 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.1.6 # homeassistant.components.august -yalexs==1.1.5 +yalexs==1.1.6 # homeassistant.components.yeelight yeelight==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e641de25fb82bf..73e4cf61bcc663 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1209,7 +1209,7 @@ xbox-webapi==2.0.8 xmltodict==0.12.0 # homeassistant.components.august -yalexs==1.1.5 +yalexs==1.1.6 # homeassistant.components.yeelight yeelight==0.5.4 From 02b0a4ca1f2c10931c1acd22ba23af5276478ce8 Mon Sep 17 00:00:00 2001 From: mptei Date: Fri, 26 Mar 2021 14:51:36 +0100 Subject: [PATCH 1521/1818] Xknx unneeded expose (#48311) --- homeassistant/components/knx/expose.py | 7 ++- requirements_test_all.txt | 3 ++ tests/components/knx/__init__.py | 1 + tests/components/knx/test_expose.py | 67 ++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/components/knx/__init__.py create mode 100644 tests/components/knx/test_expose.py diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 3d28c394140a38..f4ea52ada59643 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -114,12 +114,15 @@ async def _async_entity_changed(self, event: EventType) -> None: if new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): return + old_state = event.data.get("old_state") + if self.expose_attribute is None: - await self._async_set_knx_value(new_state.state) + if old_state is None or old_state.state != new_state.state: + # don't send same value sequentially + await self._async_set_knx_value(new_state.state) return new_attribute = new_state.attributes.get(self.expose_attribute) - old_state = event.data.get("old_state") if old_state is not None: old_attribute = old_state.attributes.get(self.expose_attribute) diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73e4cf61bcc663..6437f30ddafc6d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1201,6 +1201,9 @@ wolf_smartset==0.1.8 # homeassistant.components.xbox xbox-webapi==2.0.8 +# homeassistant.components.knx +xknx==0.17.4 + # homeassistant.components.bluesound # homeassistant.components.rest # homeassistant.components.startca diff --git a/tests/components/knx/__init__.py b/tests/components/knx/__init__.py new file mode 100644 index 00000000000000..f0fc1f36e08fc4 --- /dev/null +++ b/tests/components/knx/__init__.py @@ -0,0 +1 @@ +"""The tests for KNX integration.""" diff --git a/tests/components/knx/test_expose.py b/tests/components/knx/test_expose.py new file mode 100644 index 00000000000000..1f57811c8be725 --- /dev/null +++ b/tests/components/knx/test_expose.py @@ -0,0 +1,67 @@ +"""Test knx expose.""" +from unittest.mock import AsyncMock, Mock + +from homeassistant.components.knx.expose import KNXExposeSensor + + +async def test_binary_expose(hass): + """Test that a binary expose sends only telegrams on state change.""" + e_id = "fake.entity" + xknxMock = Mock() + xknxMock.telegrams = AsyncMock() + KNXExposeSensor(hass, xknxMock, "binary", e_id, None, "0", "1/1/8") + assert xknxMock.devices.add.call_count == 1, "Expected one device add" + + # Change state to on + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1, "Expected telegram for state change" + + # Change attribute; keep state + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {"brightness": 180}) + await hass.async_block_till_done() + assert ( + xknxMock.telegrams.put.call_count == 0 + ), "Expected no telegram; state not changed" + + # Change attribute and state + xknxMock.reset_mock() + hass.states.async_set(e_id, "off", {"brightness": 0}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1, "Expected telegram for state change" + + +async def test_expose_attribute(hass): + """Test that an expose sends only telegrams on attribute change.""" + e_id = "fake.entity" + a_id = "fakeAttribute" + xknxMock = Mock() + xknxMock.telegrams = AsyncMock() + KNXExposeSensor(hass, xknxMock, "percentU8", e_id, a_id, None, "1/1/8") + assert xknxMock.devices.add.call_count == 1, "Expected one device add" + + # Change state to on; no attribute + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 0 + + # Change attribute; keep state + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {a_id: 1}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1 + + # Change state keep attribute + xknxMock.reset_mock() + hass.states.async_set(e_id, "off", {a_id: 1}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 0 + + # Change state and attribute + xknxMock.reset_mock() + hass.states.async_set(e_id, "on", {a_id: 0}) + await hass.async_block_till_done() + assert xknxMock.telegrams.put.call_count == 1 From 3bc6497cbdeb04174b3a11c66803af971fb73f21 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Fri, 26 Mar 2021 15:08:41 +0100 Subject: [PATCH 1522/1818] Add Netatmo schedule event handling (#46573) Co-authored-by: Franck Nijhof --- homeassistant/components/netatmo/climate.py | 64 ++++++----- homeassistant/components/netatmo/const.py | 1 + tests/components/netatmo/test_climate.py | 120 +++++++++++++++++--- 3 files changed, 142 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index caa4aebe3764f3..9993b4efac2e7c 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -42,6 +42,7 @@ DATA_SCHEDULES, DOMAIN, EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_SCHEDULE, EVENT_TYPE_SET_POINT, EVENT_TYPE_THERM_MODE, MANUFACTURER, @@ -236,6 +237,7 @@ async def async_added_to_hass(self) -> None: EVENT_TYPE_SET_POINT, EVENT_TYPE_THERM_MODE, EVENT_TYPE_CANCEL_SET_POINT, + EVENT_TYPE_SCHEDULE, ): self._listeners.append( async_dispatcher_connect( @@ -253,7 +255,15 @@ async def handle_event(self, event): """Handle webhook events.""" data = event["data"] - if data.get("home") is None: + if self._home_id != data["home_id"]: + return + + if data["event_type"] == EVENT_TYPE_SCHEDULE and "schedule_id" in data: + self._selected_schedule = self.hass.data[DOMAIN][DATA_SCHEDULES][ + self._home_id + ].get(data["schedule_id"]) + self.async_write_ha_state() + self.data_handler.async_force_update(self._home_status_class) return home = data["home"] @@ -270,35 +280,37 @@ async def handle_event(self, event): self._target_temperature = self._away_temperature elif self._preset == PRESET_SCHEDULE: self.async_update_callback() + self.data_handler.async_force_update(self._home_status_class) self.async_write_ha_state() return - if not home.get("rooms"): - return - - for room in home["rooms"]: - if data["event_type"] == EVENT_TYPE_SET_POINT: - if self._id == room["id"]: - if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: - self._hvac_mode = HVAC_MODE_OFF - elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: - self._hvac_mode = HVAC_MODE_HEAT - self._target_temperature = DEFAULT_MAX_TEMP - elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: + for room in home.get("rooms", []): + if data["event_type"] == EVENT_TYPE_SET_POINT and self._id == room["id"]: + if room["therm_setpoint_mode"] == STATE_NETATMO_OFF: + self._hvac_mode = HVAC_MODE_OFF + self._preset = STATE_NETATMO_OFF + self._target_temperature = 0 + elif room["therm_setpoint_mode"] == STATE_NETATMO_MAX: + self._hvac_mode = HVAC_MODE_HEAT + self._preset = PRESET_MAP_NETATMO[PRESET_BOOST] + self._target_temperature = DEFAULT_MAX_TEMP + elif room["therm_setpoint_mode"] == STATE_NETATMO_MANUAL: + self._hvac_mode = HVAC_MODE_HEAT + self._target_temperature = room["therm_setpoint_temperature"] + else: + self._target_temperature = room["therm_setpoint_temperature"] + if self._target_temperature == DEFAULT_MAX_TEMP: self._hvac_mode = HVAC_MODE_HEAT - self._target_temperature = room["therm_setpoint_temperature"] - else: - self._target_temperature = room["therm_setpoint_temperature"] - if self._target_temperature == DEFAULT_MAX_TEMP: - self._hvac_mode = HVAC_MODE_HEAT - self.async_write_ha_state() - break - - elif data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT: - if self._id == room["id"]: - self.async_update_callback() - self.async_write_ha_state() - break + self.async_write_ha_state() + return + + if ( + data["event_type"] == EVENT_TYPE_CANCEL_SET_POINT + and self._id == room["id"] + ): + self.async_update_callback() + self.async_write_ha_state() + return @property def supported_features(self): diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index ab268b8703b056..b0a312fa1f3913 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -94,6 +94,7 @@ EVENT_TYPE_SET_POINT = "set_point" EVENT_TYPE_CANCEL_SET_POINT = "cancel_set_point" EVENT_TYPE_THERM_MODE = "therm_mode" +EVENT_TYPE_SCHEDULE = "schedule" # Camera events EVENT_TYPE_LIGHT_MODE = "light_mode" EVENT_TYPE_CAMERA_OUTDOOR = "outdoor" diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index 910fb32a7cf601..ecec2871df895a 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -1,5 +1,5 @@ """The tests for the Netatmo climate platform.""" -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest @@ -394,30 +394,50 @@ async def test_webhook_event_handling_no_data(hass, climate_entry): async def test_service_schedule_thermostats(hass, climate_entry, caplog): """Test service for selecting Netatmo schedule with thermostats.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.netatmo_livingroom" # Test setting a valid schedule - await hass.services.async_call( - "netatmo", - SERVICE_SET_SCHEDULE, - {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, - blocking=True, - ) - await hass.async_block_till_done() + with patch( + "pyatmo.thermostat.HomeData.switch_home_schedule" + ) as mock_switch_home_schedule: + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_switch_home_schedule.assert_called_once_with( + home_id="91763b24c43d3e344f424e8b", schedule_id="b1b54a2f45795764f59d50d8" + ) + + # Fake backend response for valve being turned on + response = { + "event_type": "schedule", + "schedule_id": "b1b54a2f45795764f59d50d8", + "previous_schedule_id": "59d32176d183948b05ab4dce", + "push_type": "home_event_changed", + } + await simulate_webhook(hass, webhook_id, response) assert ( - "Setting 91763b24c43d3e344f424e8b schedule to Winter (b1b54a2f45795764f59d50d8)" - in caplog.text + hass.states.get(climate_entity_livingroom).attributes["selected_schedule"] + == "Winter" ) # Test setting an invalid schedule - await hass.services.async_call( - "netatmo", - SERVICE_SET_SCHEDULE, - {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, - blocking=True, - ) - await hass.async_block_till_done() + with patch( + "pyatmo.thermostat.HomeData.switch_home_schedule" + ) as mock_switch_home_schedule: + await hass.services.async_call( + "netatmo", + SERVICE_SET_SCHEDULE, + {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_switch_home_schedule.assert_not_called() assert "summer is not a invalid schedule" in caplog.text @@ -668,3 +688,69 @@ async def test_get_all_home_ids(): } expected = ["123", "987"] assert climate.get_all_home_ids(home_data) == expected + + +async def test_webhook_home_id_mismatch(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + assert hass.states.get(climate_entity_entrada).state == "auto" + + # Fake backend response for valve being turned on + response = { + "room_id": "2833524037", + "home": { + "id": "123", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "cancel_set_point", + "push_type": "display_change", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "auto" + + +async def test_webhook_set_point(hass, climate_entry): + """Test service turn on for valves.""" + webhook_id = climate_entry.data[CONF_WEBHOOK_ID] + climate_entity_entrada = "climate.netatmo_entrada" + + # Fake backend response for valve being turned on + response = { + "room_id": "2746182631", + "home": { + "id": "91763b24c43d3e344f424e8b", + "name": "MYHOME", + "country": "DE", + "rooms": [ + { + "id": "2833524037", + "name": "Entrada", + "type": "lobby", + "therm_setpoint_mode": "home", + "therm_setpoint_temperature": 30, + } + ], + "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], + }, + "mode": "home", + "event_type": "set_point", + "temperature": 21, + "push_type": "display_change", + } + await simulate_webhook(hass, webhook_id, response) + + assert hass.states.get(climate_entity_entrada).state == "heat" From d2d78d6205a1d7bba434ab975a842ed09c906f9c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 15:19:44 +0100 Subject: [PATCH 1523/1818] Extend typing on scaffold templates (#48232) --- .../config_flow/integration/__init__.py | 9 +++++--- .../config_flow/integration/config_flow.py | 21 ++++++++++++------- .../config_flow/tests/test_config_flow.py | 7 ++++--- .../integration/__init__.py | 9 +++++--- .../integration/config_flow.py | 3 ++- .../integration/__init__.py | 9 +++++--- .../config_flow_oauth2/integration/api.py | 8 +++---- .../tests/test_config_flow.py | 8 +++++-- .../device_action/tests/test_device_action.py | 15 ++++++++----- .../tests/test_device_condition.py | 19 +++++++++++------ .../integration/integration/__init__.py | 6 +++++- .../integration/reproduce_state.py | 7 +++---- .../tests/test_reproduce_state.py | 8 +++++-- 13 files changed, 85 insertions(+), 44 deletions(-) diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py index 334ac8dbbc907d..24a0c1d73509c8 100644 --- a/script/scaffold/templates/config_flow/integration/__init__.py +++ b/script/scaffold/templates/config_flow/integration/__init__.py @@ -1,5 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + import asyncio +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -11,12 +14,12 @@ PLATFORMS = ["light"] -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME component.""" return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) @@ -29,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( await asyncio.gather( diff --git a/script/scaffold/templates/config_flow/integration/config_flow.py b/script/scaffold/templates/config_flow/integration/config_flow.py index c9700463c86566..aa05f633df42aa 100644 --- a/script/scaffold/templates/config_flow/integration/config_flow.py +++ b/script/scaffold/templates/config_flow/integration/config_flow.py @@ -1,9 +1,14 @@ """Config flow for NEW_NAME integration.""" +from __future__ import annotations + import logging +from typing import Any import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN # pylint:disable=unused-import @@ -19,16 +24,16 @@ class PlaceholderHub: TODO Remove this placeholder class and replace with things from your PyPI package. """ - def __init__(self, host): + def __init__(self, host: str) -> None: """Initialize.""" self.host = host - async def authenticate(self, username, password) -> bool: + async def authenticate(self, username: str, password: str) -> bool: """Test if we can authenticate with the host.""" return True -async def validate_input(hass: core.HomeAssistant, data): +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 STEP_USER_DATA_SCHEMA with values provided by the user. @@ -62,7 +67,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # TODO pick one of the available connection classes in homeassistant/config_entries.py CONNECTION_CLASS = config_entries.CONN_CLASS_UNKNOWN - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> dict[str, Any]: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -88,9 +95,9 @@ async def async_step_user(self, user_input=None): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/script/scaffold/templates/config_flow/tests/test_config_flow.py b/script/scaffold/templates/config_flow/tests/test_config_flow.py index 04eab6e683c6c4..8205fdbeda4960 100644 --- a/script/scaffold/templates/config_flow/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow/tests/test_config_flow.py @@ -4,9 +4,10 @@ from homeassistant import config_entries, setup from homeassistant.components.NEW_DOMAIN.config_flow import CannotConnect, InvalidAuth from homeassistant.components.NEW_DOMAIN.const import DOMAIN +from homeassistant.core import HomeAssistant -async def test_form(hass): +async def test_form(hass: HomeAssistant) -> None: """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( @@ -45,7 +46,7 @@ async def test_form(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_invalid_auth(hass): +async def test_form_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -68,7 +69,7 @@ async def test_form_invalid_auth(hass): assert result2["errors"] == {"base": "invalid_auth"} -async def test_form_cannot_connect(hass): +async def test_form_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py index 334ac8dbbc907d..24a0c1d73509c8 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/__init__.py +++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py @@ -1,5 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + import asyncio +from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -11,12 +14,12 @@ PLATFORMS = ["light"] -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME component.""" return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" # TODO Store an API object for your platforms to access # hass.data[DOMAIN][entry.entry_id] = MyApi(...) @@ -29,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( await asyncio.gather( diff --git a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py index db5f719ce3db4a..4d2ee2c9f6bf5a 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py @@ -2,12 +2,13 @@ import my_pypi_dependency from homeassistant import config_entries +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import 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.""" # TODO Check if there are any devices that can be discovered in the network. devices = await hass.async_add_executor_job(my_pypi_dependency.discover) diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index 4e290921047c9e..304df8f9c799cd 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -1,5 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + import asyncio +from typing import Any import voluptuous as vol @@ -32,7 +35,7 @@ PLATFORMS = ["light"] -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME component.""" hass.data[DOMAIN] = {} @@ -54,7 +57,7 @@ async def async_setup(hass: HomeAssistant, config: dict): return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NEW_NAME from a config entry.""" implementation = ( await config_entry_oauth2_flow.async_get_config_entry_implementation( @@ -80,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = all( await asyncio.gather( diff --git a/script/scaffold/templates/config_flow_oauth2/integration/api.py b/script/scaffold/templates/config_flow_oauth2/integration/api.py index 50b54399579f64..4f15099c8e119b 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/api.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/api.py @@ -4,7 +4,7 @@ from aiohttp import ClientSession import my_pypi_package -from homeassistant import core +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow # TODO the following two API examples are based on our suggested best practices @@ -17,9 +17,9 @@ class ConfigEntryAuth(my_pypi_package.AbstractAuth): def __init__( self, - hass: core.HomeAssistant, + hass: HomeAssistant, oauth_session: config_entry_oauth2_flow.OAuth2Session, - ): + ) -> None: """Initialize NEW_NAME Auth.""" self.hass = hass self.session = oauth_session @@ -41,7 +41,7 @@ def __init__( self, websession: ClientSession, oauth_session: config_entry_oauth2_flow.OAuth2Session, - ): + ) -> None: """Initialize NEW_NAME auth.""" super().__init__(websession) self._oauth_session = oauth_session diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py index dd0fc3446b3cc9..ff9c5bfb848571 100644 --- a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py @@ -7,6 +7,7 @@ OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow CLIENT_ID = "1234" @@ -14,8 +15,11 @@ async def test_full_flow( - hass, aiohttp_client, aioclient_mock, current_request_with_host -): + hass: HomeAssistant, + aiohttp_client, + aioclient_mock, + current_request_with_host, +) -> None: """Check full flow.""" assert await setup.async_setup_component( hass, diff --git a/script/scaffold/templates/device_action/tests/test_device_action.py b/script/scaffold/templates/device_action/tests/test_device_action.py index 91a4693ebeb6b3..424fa0a9afd08c 100644 --- a/script/scaffold/templates/device_action/tests/test_device_action.py +++ b/script/scaffold/templates/device_action/tests/test_device_action.py @@ -3,7 +3,8 @@ from homeassistant.components import automation from homeassistant.components.NEW_DOMAIN import DOMAIN -from homeassistant.helpers import device_registry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry, entity_registry from homeassistant.setup import async_setup_component from tests.common import ( @@ -17,18 +18,22 @@ @pytest.fixture -def device_reg(hass): +def device_reg(hass: HomeAssistant) -> device_registry.DeviceRegistry: """Return an empty, loaded, registry.""" return mock_device_registry(hass) @pytest.fixture -def entity_reg(hass): +def entity_reg(hass: HomeAssistant) -> entity_registry.EntityRegistry: """Return an empty, loaded, registry.""" return mock_registry(hass) -async def test_get_actions(hass, device_reg, entity_reg): +async def test_get_actions( + hass: HomeAssistant, + device_reg: device_registry.DeviceRegistry, + entity_reg: entity_registry.EntityRegistry, +) -> None: """Test we get the expected actions from a NEW_DOMAIN.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -55,7 +60,7 @@ async def test_get_actions(hass, device_reg, entity_reg): assert_lists_same(actions, expected_actions) -async def test_action(hass): +async def test_action(hass: HomeAssistant) -> None: """Test for turn_on and turn_off actions.""" assert await async_setup_component( hass, diff --git a/script/scaffold/templates/device_condition/tests/test_device_condition.py b/script/scaffold/templates/device_condition/tests/test_device_condition.py index 07e0afd05ebd2d..9a283fa1f5bb83 100644 --- a/script/scaffold/templates/device_condition/tests/test_device_condition.py +++ b/script/scaffold/templates/device_condition/tests/test_device_condition.py @@ -1,10 +1,13 @@ """The tests for NEW_NAME device conditions.""" +from __future__ import annotations + import pytest from homeassistant.components import automation from homeassistant.components.NEW_DOMAIN import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.helpers import device_registry +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers import device_registry, entity_registry from homeassistant.setup import async_setup_component from tests.common import ( @@ -18,24 +21,28 @@ @pytest.fixture -def device_reg(hass): +def device_reg(hass: HomeAssistant) -> device_registry.DeviceRegistry: """Return an empty, loaded, registry.""" return mock_device_registry(hass) @pytest.fixture -def entity_reg(hass): +def entity_reg(hass: HomeAssistant) -> entity_registry.EntityRegistry: """Return an empty, loaded, registry.""" return mock_registry(hass) @pytest.fixture -def calls(hass): +def calls(hass: HomeAssistant) -> list[ServiceCall]: """Track calls to a mock service.""" return async_mock_service(hass, "test", "automation") -async def test_get_conditions(hass, device_reg, entity_reg): +async def test_get_conditions( + hass: HomeAssistant, + device_reg: device_registry.DeviceRegistry, + entity_reg: entity_registry.EntityRegistry, +) -> None: """Test we get the expected conditions from a NEW_DOMAIN.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -64,7 +71,7 @@ async def test_get_conditions(hass, device_reg, entity_reg): assert_lists_same(conditions, expected_conditions) -async def test_if_state(hass, calls): +async def test_if_state(hass: HomeAssistant, calls: list[ServiceCall]) -> None: """Test for turn_on and turn_off conditions.""" hass.states.async_set("NEW_DOMAIN.entity", STATE_ON) diff --git a/script/scaffold/templates/integration/integration/__init__.py b/script/scaffold/templates/integration/integration/__init__.py index 0ab65cb7da8b59..c1f34d5f5b1712 100644 --- a/script/scaffold/templates/integration/integration/__init__.py +++ b/script/scaffold/templates/integration/integration/__init__.py @@ -1,4 +1,8 @@ """The NEW_NAME integration.""" +from __future__ import annotations + +from typing import Any + import voluptuous as vol from homeassistant.core import HomeAssistant @@ -8,6 +12,6 @@ CONFIG_SCHEMA = vol.Schema({vol.Optional(DOMAIN): {}}, extra=vol.ALLOW_EXTRA) -async def async_setup(hass: HomeAssistant, config: dict): +async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NEW_NAME integration.""" return True diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index 2031109a6bdecb..19e046f4c92edd 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -12,8 +12,7 @@ STATE_OFF, STATE_ON, ) -from homeassistant.core import Context, State -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import Context, HomeAssistant, State from . import DOMAIN @@ -24,7 +23,7 @@ async def _async_reproduce_state( - hass: HomeAssistantType, + hass: HomeAssistant, state: State, *, context: Context | None = None, @@ -69,7 +68,7 @@ async def _async_reproduce_state( async def async_reproduce_states( - hass: HomeAssistantType, + hass: HomeAssistant, states: Iterable[State], *, context: Context | None = None, diff --git a/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py b/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py index ff15625ad7c4fb..83d95570b45247 100644 --- a/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py @@ -1,10 +1,14 @@ """Test reproduce state for NEW_NAME.""" -from homeassistant.core import State +import pytest + +from homeassistant.core import HomeAssistant, State from tests.common import async_mock_service -async def test_reproducing_states(hass, caplog): +async def test_reproducing_states( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: """Test reproducing NEW_NAME states.""" hass.states.async_set("NEW_DOMAIN.entity_off", "off", {}) hass.states.async_set("NEW_DOMAIN.entity_on", "on", {"color": "red"}) From 8fa935234aa04dfc0ac14f48e380292eec22ea2c Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 26 Mar 2021 16:10:55 +0100 Subject: [PATCH 1524/1818] Type check KNX integration __init__ and knx_entity (#48044) --- homeassistant/components/knx/__init__.py | 72 ++++++++++++---------- homeassistant/components/knx/climate.py | 4 +- homeassistant/components/knx/knx_entity.py | 18 +++--- setup.cfg | 2 +- tests/components/knx/__init__.py | 2 +- 5 files changed, 53 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 8f363ac70d1672..87e7c0b1b14c52 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -24,16 +24,17 @@ EVENT_HOMEASSISTANT_STOP, SERVICE_RELOAD, ) +from homeassistant.core import ServiceCall from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import async_get_platforms from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service -from homeassistant.helpers.typing import ServiceCallType +from homeassistant.helpers.typing import ConfigType, EventType, HomeAssistantType from .const import DOMAIN, KNX_ADDRESS, SupportedPlatforms -from .expose import create_knx_exposure +from .expose import KNXExposeSensor, KNXExposeTime, create_knx_exposure from .factory import create_knx_device from .schema import ( BinarySensorSchema, @@ -211,8 +212,8 @@ ) -async def async_setup(hass, config): - """Set up the KNX component.""" +async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: + """Set up the KNX integration.""" try: knx_module = KNXModule(hass, config) hass.data[DOMAIN] = knx_module @@ -270,7 +271,7 @@ async def async_setup(hass, config): schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, ) - async def reload_service_handler(service_call: ServiceCallType) -> None: + async def reload_service_handler(service_call: ServiceCall) -> None: """Remove all KNX components and load new ones from config.""" # First check for config file. If for some reason it is no longer there @@ -298,19 +299,19 @@ async def reload_service_handler(service_call: ServiceCallType) -> None: class KNXModule: """Representation of KNX Object.""" - def __init__(self, hass, config): - """Initialize of KNX module.""" + def __init__(self, hass: HomeAssistantType, config: ConfigType) -> None: + """Initialize KNX module.""" self.hass = hass self.config = config self.connected = False - self.exposures = [] - self.service_exposures = {} + self.exposures: list[KNXExposeSensor | KNXExposeTime] = [] + self.service_exposures: dict[str, KNXExposeSensor | KNXExposeTime] = {} self.init_xknx() self._knx_event_callback: TelegramQueue.Callback = self.register_callback() - def init_xknx(self): - """Initialize of KNX object.""" + def init_xknx(self) -> None: + """Initialize XKNX object.""" self.xknx = XKNX( config=self.config_file(), own_address=self.config[DOMAIN][CONF_KNX_INDIVIDUAL_ADDRESS], @@ -321,26 +322,26 @@ def init_xknx(self): state_updater=self.config[DOMAIN][CONF_KNX_STATE_UPDATER], ) - async def start(self): - """Start KNX object. Connect to tunneling or Routing device.""" + async def start(self) -> None: + """Start XKNX object. Connect to tunneling or Routing device.""" await self.xknx.start() self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) self.connected = True - async def stop(self, event): - """Stop KNX object. Disconnect from tunneling or Routing device.""" + async def stop(self, event: EventType) -> None: + """Stop XKNX object. Disconnect from tunneling or Routing device.""" await self.xknx.stop() - def config_file(self): + def config_file(self) -> str | None: """Resolve and return the full path of xknx.yaml if configured.""" config_file = self.config[DOMAIN].get(CONF_KNX_CONFIG) if not config_file: return None if not config_file.startswith("/"): return self.hass.config.path(config_file) - return config_file + return config_file # type: ignore - def connection_config(self): + def connection_config(self) -> ConnectionConfig: """Return the connection_config.""" if CONF_KNX_TUNNELING in self.config[DOMAIN]: return self.connection_config_tunneling() @@ -349,7 +350,7 @@ def connection_config(self): # config from xknx.yaml always has priority later on return ConnectionConfig(auto_reconnect=True) - def connection_config_routing(self): + def connection_config_routing(self) -> ConnectionConfig: """Return the connection_config if routing is configured.""" local_ip = None # all configuration values are optional @@ -361,7 +362,7 @@ def connection_config_routing(self): connection_type=ConnectionType.ROUTING, local_ip=local_ip ) - def connection_config_tunneling(self): + def connection_config_tunneling(self) -> ConnectionConfig: """Return the connection_config if tunneling is configured.""" gateway_ip = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_HOST] gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_PORT] @@ -380,12 +381,14 @@ def connection_config_tunneling(self): auto_reconnect=True, ) - async def telegram_received_cb(self, telegram): + async def telegram_received_cb(self, telegram: Telegram) -> None: """Call invoked after a KNX telegram was received.""" data = None - # Not all telegrams have serializable data. - if isinstance(telegram.payload, (GroupValueWrite, GroupValueResponse)): + if ( + isinstance(telegram.payload, (GroupValueWrite, GroupValueResponse)) + and telegram.payload.value is not None + ): data = telegram.payload.value.value self.hass.bus.async_fire( @@ -404,16 +407,16 @@ def register_callback(self) -> TelegramQueue.Callback: address_filters = list( map(AddressFilter, self.config[DOMAIN][CONF_KNX_EVENT_FILTER]) ) - return self.xknx.telegram_queue.register_telegram_received_cb( + return self.xknx.telegram_queue.register_telegram_received_cb( # type: ignore[no-any-return] self.telegram_received_cb, address_filters=address_filters, group_addresses=[], match_for_outgoing=True, ) - async def service_event_register_modify(self, call): + async def service_event_register_modify(self, call: ServiceCall) -> None: """Service for adding or removing a GroupAddress to the knx_event filter.""" - attr_address = call.data.get(KNX_ADDRESS) + attr_address = call.data[KNX_ADDRESS] group_addresses = map(GroupAddress, attr_address) if call.data.get(SERVICE_KNX_ATTR_REMOVE): @@ -434,9 +437,9 @@ async def service_event_register_modify(self, call): str(group_address), ) - async def service_exposure_register_modify(self, call): + async def service_exposure_register_modify(self, call: ServiceCall) -> None: """Service for adding or removing an exposure to KNX bus.""" - group_address = call.data.get(KNX_ADDRESS) + group_address = call.data[KNX_ADDRESS] if call.data.get(SERVICE_KNX_ATTR_REMOVE): try: @@ -451,13 +454,14 @@ async def service_exposure_register_modify(self, call): if group_address in self.service_exposures: replaced_exposure = self.service_exposures.pop(group_address) + assert replaced_exposure.device is not None _LOGGER.warning( "Service exposure_register replacing already registered exposure for '%s' - %s", group_address, replaced_exposure.device.name, ) replaced_exposure.shutdown() - exposure = create_knx_exposure(self.hass, self.xknx, call.data) + exposure = create_knx_exposure(self.hass, self.xknx, call.data) # type: ignore[arg-type] self.service_exposures[group_address] = exposure _LOGGER.debug( "Service exposure_register registered exposure for '%s' - %s", @@ -465,10 +469,10 @@ async def service_exposure_register_modify(self, call): exposure.device.name, ) - async def service_send_to_knx_bus(self, call): + async def service_send_to_knx_bus(self, call: ServiceCall) -> None: """Service for sending an arbitrary KNX message to the KNX bus.""" - attr_address = call.data.get(KNX_ADDRESS) - attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) + attr_address = call.data[KNX_ADDRESS] + attr_payload = call.data[SERVICE_KNX_ATTR_PAYLOAD] attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) payload: DPTBinary | DPTArray @@ -489,9 +493,9 @@ async def service_send_to_knx_bus(self, call): ) await self.xknx.telegrams.put(telegram) - async def service_read_to_knx_bus(self, call): + async def service_read_to_knx_bus(self, call: ServiceCall) -> None: """Service for sending a GroupValueRead telegram to the KNX bus.""" - for address in call.data.get(KNX_ADDRESS): + for address in call.data[KNX_ADDRESS]: telegram = Telegram( destination_address=GroupAddress(address), payload=GroupValueRead(), diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 1b9fea575415c9..700e080b037edd 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -72,7 +72,7 @@ def temperature_unit(self) -> str: @property def current_temperature(self) -> float | None: """Return the current temperature.""" - return self._device.temperature.value # type: ignore[no-any-return] + return self._device.temperature.value @property def target_temperature_step(self) -> float: @@ -82,7 +82,7 @@ def target_temperature_step(self) -> float: @property def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - return self._device.target_temperature.value # type: ignore[no-any-return] + return self._device.target_temperature.value @property def min_temp(self) -> float: diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index f4597ad230e623..31147625d16c4a 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -1,38 +1,42 @@ """Base class for KNX devices.""" +from typing import cast + from xknx.devices import Climate as XknxClimate, Device as XknxDevice from homeassistant.helpers.entity import Entity +from . import KNXModule from .const import DOMAIN class KnxEntity(Entity): """Representation of a KNX entity.""" - def __init__(self, device: XknxDevice): + def __init__(self, device: XknxDevice) -> None: """Set up device.""" self._device = device @property - def name(self): + def name(self) -> str: """Return the name of the KNX device.""" return self._device.name @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" - return self.hass.data[DOMAIN].connected + knx_module = cast(KNXModule, self.hass.data[DOMAIN]) + return knx_module.connected @property - def should_poll(self): + def should_poll(self) -> bool: """No polling needed within KNX.""" return False - async def async_update(self): + async def async_update(self) -> None: """Request a state update from KNX bus.""" await self._device.sync() - async def after_update_callback(self, device: XknxDevice): + async def after_update_callback(self, device: XknxDevice) -> None: """Call after device was updated.""" self.async_write_ha_state() diff --git a/setup.cfg b/setup.cfg index d5f8b9da16b69a..408bab3a03c15b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,7 @@ warn_redundant_casts = true warn_unused_configs = true -[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] +[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] strict = true ignore_errors = false warn_unreachable = true diff --git a/tests/components/knx/__init__.py b/tests/components/knx/__init__.py index f0fc1f36e08fc4..eaa84714dc5a3a 100644 --- a/tests/components/knx/__init__.py +++ b/tests/components/knx/__init__.py @@ -1 +1 @@ -"""The tests for KNX integration.""" +"""Tests for the KNX integration.""" From f4cc4a0896358f4c8df5a193d900716fabb7ad32 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 17:08:05 +0100 Subject: [PATCH 1525/1818] Merge of nested IF-IF cases - X-Z (#48373) --- .../components/xiaomi_miio/__init__.py | 15 +++++---- homeassistant/components/zha/climate.py | 23 ++++++++----- homeassistant/components/zha/core/gateway.py | 5 ++- homeassistant/components/zwave/climate.py | 15 +++++---- homeassistant/components/zwave/light.py | 10 +++--- homeassistant/components/zwave/lock.py | 22 ++++++------- .../components/zwave_js/binary_sensor.py | 12 +++---- .../components/zwave_js/discovery.py | 33 ++++++++++++------- 8 files changed, 77 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index ccecc835b43244..f97d4623d69191 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -45,14 +45,15 @@ async def async_setup_entry( ): """Set up the Xiaomi Miio components from a config entry.""" hass.data.setdefault(DOMAIN, {}) - if entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: - if not await async_setup_gateway_entry(hass, entry): - return False - if entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: - if not await async_setup_device_entry(hass, entry): - return False + if entry.data[ + CONF_FLOW_TYPE + ] == CONF_GATEWAY and not await async_setup_gateway_entry(hass, entry): + return False - return True + return bool( + entry.data[CONF_FLOW_TYPE] != CONF_DEVICE + or await async_setup_device_entry(hass, entry) + ) async def async_setup_gateway_entry( diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index 8292335ce23e2c..475b0c5d0b89f9 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -448,15 +448,22 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: self.debug("preset mode '%s' is not supported", preset_mode) return - if self.preset_mode not in (preset_mode, PRESET_NONE): - if not await self.async_preset_handler(self.preset_mode, enable=False): - self.debug("Couldn't turn off '%s' preset", self.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) + ): + self.debug("Couldn't turn off '%s' preset", self.preset_mode) + return - if preset_mode != PRESET_NONE: - if not await self.async_preset_handler(preset_mode, enable=True): - self.debug("Couldn't turn on '%s' preset", preset_mode) - return + if preset_mode != PRESET_NONE and not await self.async_preset_handler( + preset_mode, enable=True + ): + self.debug("Couldn't turn on '%s' preset", preset_mode) + return self._preset = preset_mode self.async_write_ha_state() diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index d8baf89efcfec2..e944196a40939d 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -728,9 +728,8 @@ def __init__(self, hass, gateway): def emit(self, record): """Relay log message via dispatcher.""" stack = [] - if record.levelno >= logging.WARN: - if not record.exc_info: - stack = [f for f, _, _, _ in traceback.extract_stack()] + if record.levelno >= logging.WARN and not record.exc_info: + stack = [f for f, _, _, _ in traceback.extract_stack()] entry = LogEntry(record, stack, _figure_out_source(record, stack, self.hass)) async_dispatcher_send( diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index ac8c47af9a3228..75780eb314a143 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -182,10 +182,12 @@ def __init__(self, values, temp_unit): int(self.node.manufacturer_id, 16), int(self.node.product_id, 16), ) - if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: - _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat workaround") - self._zxt_120 = 1 + if ( + specific_sensor_key in DEVICE_MAPPINGS + and DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120 + ): + _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat workaround") + self._zxt_120 = 1 self.update_properties() def _mode(self) -> None: @@ -567,9 +569,8 @@ def set_preset_mode(self, preset_mode): def set_swing_mode(self, swing_mode): """Set new target swing mode.""" _LOGGER.debug("Set swing_mode to %s", swing_mode) - if self._zxt_120 == 1: - if self.values.zxt_120_swing_mode: - self.values.zxt_120_swing_mode.data = swing_mode + if self._zxt_120 == 1 and self.values.zxt_120_swing_mode: + self.values.zxt_120_swing_mode.data = swing_mode @property def extra_state_attributes(self): diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index 52014e37eea278..140f601b1d9f55 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -138,10 +138,12 @@ def __init__(self, values, refresh, delay): int(self.node.manufacturer_id, 16), int(self.node.product_id, 16), ) - if specific_sensor_key in DEVICE_MAPPINGS: - if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: - _LOGGER.debug("AEOTEC ZW098 workaround enabled") - self._zw098 = 1 + if ( + specific_sensor_key in DEVICE_MAPPINGS + and DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098 + ): + _LOGGER.debug("AEOTEC ZW098 workaround enabled") + self._zw098 = 1 # Used for value change event handling self._refreshing = False diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index e6228a29334f3a..bc49f9c0bd222e 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -291,17 +291,17 @@ def update_properties(self): if self._state_workaround: self._state = LOCK_STATUS.get(str(notification_data)) _LOGGER.debug("workaround: lock state set to %s", self._state) - if self._v2btze: - if ( - self.values.v2btze_advanced - and self.values.v2btze_advanced.data == CONFIG_ADVANCED - ): - self._state = LOCK_STATUS.get(str(notification_data)) - _LOGGER.debug( - "Lock state set from Access Control value and is %s, get=%s", - str(notification_data), - self.state, - ) + if ( + self._v2btze + and self.values.v2btze_advanced + and self.values.v2btze_advanced.data == CONFIG_ADVANCED + ): + self._state = LOCK_STATUS.get(str(notification_data)) + _LOGGER.debug( + "Lock state set from Access Control value and is %s, get=%s", + str(notification_data), + self.state, + ) if self._track_message_workaround: this_message = self.node.stats["lastReceivedMessage"][5] diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 47c374405b1a59..b97975b0507070 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -285,12 +285,12 @@ def device_class(self) -> str | None: @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - if self.info.primary_value.command_class == CommandClass.SENSOR_BINARY: - # Legacy binary sensors are phased out (replaced by notification sensors) - # Disable by default to not confuse users - if self.info.node.device_class.generic.key != 0x20: - return False - return True + # Legacy binary sensors are phased out (replaced by notification sensors) + # Disable by default to not confuse users + return bool( + self.info.primary_value.command_class != CommandClass.SENSOR_BINARY + or self.info.node.device_class.generic.key == 0x20 + ) class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity): diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 5f1f04274baa3b..b59ae017da7d71 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -403,56 +403,64 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None and value.node.manufacturer_id not in schema.manufacturer_id ): continue + # check product_id if ( schema.product_id is not None and value.node.product_id not in schema.product_id ): continue + # check product_type if ( schema.product_type is not None and value.node.product_type not in schema.product_type ): continue + # check firmware_version if ( schema.firmware_version is not None and value.node.firmware_version not in schema.firmware_version ): continue + # check device_class_basic if not check_device_class( value.node.device_class.basic, schema.device_class_basic ): continue + # check device_class_generic if not check_device_class( value.node.device_class.generic, schema.device_class_generic ): continue + # check device_class_specific if not check_device_class( value.node.device_class.specific, schema.device_class_specific ): continue + # check primary value if not check_value(value, schema.primary_value): continue + # check additional required values - if schema.required_values is not None: - if not all( - any(check_value(val, val_scheme) for val in node.values.values()) - for val_scheme in schema.required_values - ): - continue + if schema.required_values is not None and not all( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.required_values + ): + continue + # check for values that may not be present - if schema.absent_values is not None: - if any( - any(check_value(val, val_scheme) for val in node.values.values()) - for val_scheme in schema.absent_values - ): - continue + if schema.absent_values is not None and any( + any(check_value(val, val_scheme) for val in node.values.values()) + for val_scheme in schema.absent_values + ): + continue + # all checks passed, this value belongs to an entity yield ZwaveDiscoveryInfo( node=value.node, @@ -460,6 +468,7 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None platform=schema.platform, platform_hint=schema.hint, ) + if not schema.allow_multi: # break out of loop, this value may not be discovered by other schemas/platforms break From 00683d3caa2a92181bc1da59cf07d49ae3a0de91 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Mar 2021 09:48:02 -0700 Subject: [PATCH 1526/1818] Create FUNDING.yml (#48375) --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000000..ad3205c51c8fad --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +custom: https://www.nabucasa.com +github: balloob From bbbc3a5f500a4df85441e89239c779f82a60a97e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Mar 2021 17:54:16 +0100 Subject: [PATCH 1527/1818] Merge of nested IF-IF case in elkm1 test (#48374) --- tests/components/elkm1/test_config_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index d73bbe53e9a9a6..a84ff0351d593b 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -15,9 +15,8 @@ def handler_callbacks(type_, callback): if type_ == "login": if invalid_auth is not None: callback(not invalid_auth) - elif type_ == "sync_complete": - if sync_complete: - callback() + elif type_ == "sync_complete" and sync_complete: + callback() mocked_elk = MagicMock() mocked_elk.add_handler.side_effect = handler_callbacks From c6a20d0fc1fe605af403dfb68a688551c2662f54 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 18:14:01 +0100 Subject: [PATCH 1528/1818] Improve traces for nested script runs (#48366) --- homeassistant/components/trace/__init__.py | 9 ++- homeassistant/helpers/trace.py | 20 +++++ tests/components/trace/test_websocket_api.py | 78 ++++++++++++++++++-- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index 43deefaa769acf..b0211505e7c2ba 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -6,7 +6,12 @@ from typing import Any, Deque from homeassistant.core import Context -from homeassistant.helpers.trace import TraceElement, trace_id_set +from homeassistant.helpers.trace import ( + TraceElement, + trace_id_get, + trace_id_set, + trace_set_child_id, +) import homeassistant.util.dt as dt_util from . import websocket_api @@ -55,6 +60,8 @@ def __init__( self._timestamp_start: dt.datetime = dt_util.utcnow() self.key: tuple[str, str] = key self._variables: dict[str, Any] | None = None + if trace_id_get(): + trace_set_child_id(self.key, self.run_id) trace_id_set((key, self.run_id)) def set_action_trace(self, trace: dict[str, Deque[TraceElement]]) -> None: diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index ba39e19943b80b..5d5a0f5ff033fd 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -16,6 +16,8 @@ class TraceElement: def __init__(self, variables: TemplateVarsType, path: str): """Container for trace data.""" + self._child_key: tuple[str, str] | None = None + self._child_run_id: str | None = None self._error: Exception | None = None self.path: str = path self._result: dict | None = None @@ -36,6 +38,11 @@ def __repr__(self) -> str: """Container for trace data.""" return str(self.as_dict()) + def set_child_id(self, child_key: tuple[str, str], child_run_id: str) -> None: + """Set trace id of a nested script run.""" + self._child_key = child_key + self._child_run_id = child_run_id + def set_error(self, ex: Exception) -> None: """Set error.""" self._error = ex @@ -47,6 +54,12 @@ def set_result(self, **kwargs: Any) -> None: def as_dict(self) -> dict[str, Any]: """Return dictionary version of this TraceElement.""" result: dict[str, Any] = {"path": self.path, "timestamp": self._timestamp} + if self._child_key is not None: + result["child_id"] = { + "domain": self._child_key[0], + "item_id": self._child_key[1], + "run_id": str(self._child_run_id), + } if self._variables: result["changed_variables"] = self._variables if self._error is not None: @@ -161,6 +174,13 @@ def trace_clear() -> None: variables_cv.set(None) +def trace_set_child_id(child_key: tuple[str, str], child_run_id: str) -> None: + """Set child trace_id of TraceElement at the top of the stack.""" + node = cast(TraceElement, trace_stack_top(trace_stack_cv)) + if node: + node.set_child_id(child_key, child_run_id) + + def trace_set_result(**kwargs: Any) -> None: """Set the result of TraceElement at the top of the stack.""" node = cast(TraceElement, trace_stack_top(trace_stack_cv)) diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index 8dc09731b79a5a..f198cf1b55ad3c 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -26,12 +26,6 @@ def _find_traces(traces, trace_type, item_id): ] -# TODO: Remove -def _find_traces_for_automation(traces, item_id): - """Find traces for an automation.""" - return [trace for trace in traces if trace["item_id"] == item_id] - - @pytest.mark.parametrize( "domain, prefix", [("automation", "action"), ("script", "sequence")] ) @@ -515,6 +509,78 @@ def next_id(): assert trace["trigger"] == "event 'test_event2'" +@pytest.mark.parametrize( + "domain, prefix", [("automation", "action"), ("script", "sequence")] +) +async def test_nested_traces(hass, hass_ws_client, domain, prefix): + """Test nested automation and script traces.""" + id = 1 + + def next_id(): + nonlocal id + id += 1 + return id + + sun_config = { + "id": "sun", + "trigger": {"platform": "event", "event_type": "test_event"}, + "action": {"service": "script.moon"}, + } + moon_config = { + "sequence": {"event": "another_event"}, + } + if domain == "script": + sun_config = {"sequence": sun_config["action"]} + + if domain == "automation": + assert await async_setup_component(hass, domain, {domain: [sun_config]}) + assert await async_setup_component( + hass, "script", {"script": {"moon": moon_config}} + ) + else: + assert await async_setup_component( + hass, domain, {domain: {"sun": sun_config, "moon": moon_config}} + ) + + client = await hass_ws_client() + + # Trigger "sun" automation / run "sun" script + if domain == "automation": + hass.bus.async_fire("test_event") + else: + await hass.services.async_call("script", "sun") + await hass.async_block_till_done() + + # List traces + await client.send_json({"id": next_id(), "type": "trace/list"}) + response = await client.receive_json() + assert response["success"] + assert len(response["result"]) == 2 + assert len(_find_traces(response["result"], domain, "sun")) == 1 + assert len(_find_traces(response["result"], "script", "moon")) == 1 + sun_run_id = _find_run_id(response["result"], domain, "sun") + moon_run_id = _find_run_id(response["result"], "script", "moon") + assert sun_run_id != moon_run_id + + # Get trace + await client.send_json( + { + "id": next_id(), + "type": "trace/get", + "domain": domain, + "item_id": "sun", + "run_id": sun_run_id, + } + ) + response = await client.receive_json() + assert response["success"] + trace = response["result"] + assert len(trace["action_trace"]) == 1 + assert len(trace["action_trace"][f"{prefix}/0"]) == 1 + child_id = trace["action_trace"][f"{prefix}/0"][0]["child_id"] + assert child_id == {"domain": "script", "item_id": "moon", "run_id": moon_run_id} + + @pytest.mark.parametrize( "domain, prefix", [("automation", "action"), ("script", "sequence")] ) From 374dcde48745cb2f036aac9b5fed8c7cfb9a63e6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 26 Mar 2021 18:31:29 +0100 Subject: [PATCH 1529/1818] Return config entry details after creation (#48316) --- homeassistant/components/config/config_entries.py | 2 +- tests/components/config/test_config_entries.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index b45b6abe468062..af90cdcba4b0cb 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -155,7 +155,7 @@ def _prepare_result_json(self, result): return super()._prepare_result_json(result) data = result.copy() - data["result"] = data["result"].entry_id + data["result"] = entry_json(result["result"]) data.pop("data") return data diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index d6cc474fa8b4d0..7e4df556fa59c0 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -379,7 +379,17 @@ async def async_step_account(self, user_input=None): "type": "create_entry", "title": "user-title", "version": 1, - "result": entries[0].entry_id, + "result": { + "connection_class": "unknown", + "disabled_by": None, + "domain": "test", + "entry_id": entries[0].entry_id, + "source": "user", + "state": "loaded", + "supports_options": False, + "supports_unload": False, + "title": "user-title", + }, "description": None, "description_placeholders": None, } From 99874cd9932db9c9bdb102a8aed592e49ad8b99b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 27 Mar 2021 00:03:21 +0000 Subject: [PATCH 1530/1818] [ci skip] Translation update --- .../components/asuswrt/translations/ko.json | 2 +- .../components/bond/translations/nl.json | 2 +- .../components/cast/translations/ko.json | 4 +++- .../components/cast/translations/zh-Hant.json | 4 +++- .../components/control4/translations/ko.json | 2 +- .../forked_daapd/translations/ko.json | 2 +- .../components/hive/translations/ko.json | 2 +- .../home_plus_control/translations/ko.json | 21 +++++++++++++++++++ .../translations/zh-Hant.json | 21 +++++++++++++++++++ .../huisbaasje/translations/ca.json | 1 + .../huisbaasje/translations/en.json | 1 + .../huisbaasje/translations/et.json | 1 + .../huisbaasje/translations/ko.json | 1 + .../huisbaasje/translations/nl.json | 1 + .../huisbaasje/translations/ru.json | 1 + .../huisbaasje/translations/zh-Hant.json | 1 + .../lutron_caseta/translations/nl.json | 2 +- .../components/omnilogic/translations/ko.json | 2 +- .../components/onvif/translations/nl.json | 2 +- .../components/ozw/translations/nl.json | 2 +- .../components/plaato/translations/nl.json | 2 +- .../components/plugwise/translations/nl.json | 4 ++-- .../components/rachio/translations/ko.json | 2 +- .../screenlogic/translations/ko.json | 2 +- .../synology_dsm/translations/ko.json | 2 +- .../components/tesla/translations/ko.json | 2 +- .../components/upnp/translations/nl.json | 2 +- 27 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/home_plus_control/translations/ko.json create mode 100644 homeassistant/components/home_plus_control/translations/zh-Hant.json diff --git a/homeassistant/components/asuswrt/translations/ko.json b/homeassistant/components/asuswrt/translations/ko.json index 108d00adefb63f..4e60a0d15b08ad 100644 --- a/homeassistant/components/asuswrt/translations/ko.json +++ b/homeassistant/components/asuswrt/translations/ko.json @@ -32,7 +32,7 @@ "step": { "init": { "data": { - "consider_home": "\uae30\uae30\uac00 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\uae30 \uc804\uc5d0 \uae30\ub2e4\ub9ac\ub294 \uc2dc\uac04(\ucd08)", + "consider_home": "\uae30\uae30\uac00 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\uae30 \uc804\uc5d0 \uae30\ub2e4\ub9ac\ub294 \uc2dc\uac04 (\ucd08)", "dnsmasq": "dnsmasq.lease \ud30c\uc77c\uc758 \ub77c\uc6b0\ud130 \uc704\uce58", "interface": "\ud1b5\uacc4\ub97c \uc6d0\ud558\ub294 \uc778\ud130\ud398\uc774\uc2a4 (\uc608: eth0, eth1 \ub4f1)", "require_ip": "\uae30\uae30\uc5d0\ub294 IP\uac00 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4 (\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \ubaa8\ub4dc\uc778 \uacbd\uc6b0)", diff --git a/homeassistant/components/bond/translations/nl.json b/homeassistant/components/bond/translations/nl.json index a9086df3559681..67812678082a5b 100644 --- a/homeassistant/components/bond/translations/nl.json +++ b/homeassistant/components/bond/translations/nl.json @@ -9,7 +9,7 @@ "old_firmware": "Niet-ondersteunde oude firmware op het Bond-apparaat - voer een upgrade uit voordat u doorgaat", "unknown": "Onverwachte fout" }, - "flow_title": "Bond: {bond_id} ({host})", + "flow_title": "Bond: {name} ({host})", "step": { "confirm": { "data": { diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index 3ae6a6885272bb..b0bfd3271c9a96 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4." + "ignore_cec": "pychromecast.IGNORE_CEC\uc5d0 \uc804\ub2ec\ub420 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4.", + "known_hosts": "mDNS \uac80\uc0c9\uc774 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0 \uc54c\ub824\uc9c4 \ud638\uc2a4\ud2b8\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4.", + "uuid": "UUID\uc758 \uc120\ud0dd\uc801 \ubaa9\ub85d\uc785\ub2c8\ub2e4. \ubaa9\ub85d\uc5d0 \uc5c6\ub294 \uce90\uc2a4\ud2b8\ub294 \ucd94\uac00\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "description": "Google Cast \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694." } diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 4a32f61eeffbf3..00810ade520413 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -27,7 +27,9 @@ "step": { "options": { "data": { - "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002" + "ignore_cec": "\u9078\u9805\u5217\u8868\u5c07\u50b3\u905e\u81f3 pychromecast.IGNORE_CEC\u3002", + "known_hosts": "\u5047\u5982 mDNS \u63a2\u7d22\u7121\u6cd5\u4f5c\u7528\uff0c\u5247\u70ba\u5df2\u77e5\u4e3b\u6a5f\u7684\u9078\u9805\u5217\u8868\u3002", + "uuid": "UUID \u9078\u9805\u5217\u8868\u3002\u672a\u5217\u51fa\u7684 Cast \u88dd\u7f6e\u5c07\u4e0d\u6703\u9032\u884c\u65b0\u589e\u3002" }, "description": "\u8acb\u8f38\u5165 Google Cast \u8a2d\u5b9a\u3002" } diff --git a/homeassistant/components/control4/translations/ko.json b/homeassistant/components/control4/translations/ko.json index ca36da40c18fe6..245fe666eab1cc 100644 --- a/homeassistant/components/control4/translations/ko.json +++ b/homeassistant/components/control4/translations/ko.json @@ -23,7 +23,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9(\ucd08)" + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08)" } } } diff --git a/homeassistant/components/forked_daapd/translations/ko.json b/homeassistant/components/forked_daapd/translations/ko.json index 3eaaf15be2c3c4..60b585af958b44 100644 --- a/homeassistant/components/forked_daapd/translations/ko.json +++ b/homeassistant/components/forked_daapd/translations/ko.json @@ -31,7 +31,7 @@ "data": { "librespot_java_port": "librespot-java \ud30c\uc774\ud504 \ucee8\ud2b8\ub864\uc6a9 \ud3ec\ud2b8 (\uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0)", "max_playlists": "\uc18c\uc2a4\ub85c \uc0ac\uc6a9\ub41c \ucd5c\ub300 \uc7ac\uc0dd \ubaa9\ub85d \uc218", - "tts_pause_time": "TTS \uc804\ud6c4\uc5d0 \uc77c\uc2dc\uc911\uc9c0\ud560 \uc2dc\uac04(\ucd08)", + "tts_pause_time": "TTS \uc804\ud6c4\uc5d0 \uc77c\uc2dc\uc911\uc9c0\ud560 \uc2dc\uac04 (\ucd08)", "tts_volume": "TTS \ubcfc\ub968 (0~1 \uc758 \uc2e4\uc218\uac12)" }, "description": "forked-daapd \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ub2e4\uc591\ud55c \uc635\uc158\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/hive/translations/ko.json b/homeassistant/components/hive/translations/ko.json index 99c8c132229fb0..1f06a2f1ac76dc 100644 --- a/homeassistant/components/hive/translations/ko.json +++ b/homeassistant/components/hive/translations/ko.json @@ -43,7 +43,7 @@ "step": { "user": { "data": { - "scan_interval": "Scan Interval (seconds)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, "description": "\ub370\uc774\ud130\ub97c \ub354 \uc790\uc8fc \ud3f4\ub9c1\ud558\ub824\uba74 \uac80\uc0c9 \uac04\uaca9\uc744 \uc5c5\ub370\uc774\ud2b8\ud574\uc8fc\uc138\uc694.", "title": "Hive \uc635\uc158" diff --git a/homeassistant/components/home_plus_control/translations/ko.json b/homeassistant/components/home_plus_control/translations/ko.json new file mode 100644 index 00000000000000..94c8bb916485c0 --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/ko.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4", + "authorize_url_timeout": "\uc778\uc99d URL \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "missing_configuration": "\uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc624\ub958\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 [\ub3c4\uc6c0\ub9d0 \uc139\uc158]({docs_url}) \uc744(\ub97c) \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "create_entry": { + "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pick_implementation": { + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + } + } + }, + "title": "Legrand Home+ Control" +} \ 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 new file mode 100644 index 00000000000000..0faa311028720a --- /dev/null +++ b/homeassistant/components/home_plus_control/translations/zh-Hant.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "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" + }, + "create_entry": { + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" + }, + "step": { + "pick_implementation": { + "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" + } + } + }, + "title": "Legrand Home+ Control" +} \ No newline at end of file diff --git a/homeassistant/components/huisbaasje/translations/ca.json b/homeassistant/components/huisbaasje/translations/ca.json index 99d99d4340f769..3ee45b4c38b840 100644 --- a/homeassistant/components/huisbaasje/translations/ca.json +++ b/homeassistant/components/huisbaasje/translations/ca.json @@ -4,6 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_exception": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unauthenticated_exception": "Autenticaci\u00f3 inv\u00e0lida", diff --git a/homeassistant/components/huisbaasje/translations/en.json b/homeassistant/components/huisbaasje/translations/en.json index 16832be30e75bb..42bb4b59196179 100644 --- a/homeassistant/components/huisbaasje/translations/en.json +++ b/homeassistant/components/huisbaasje/translations/en.json @@ -4,6 +4,7 @@ "already_configured": "Device is already configured" }, "error": { + "cannot_connect": "Failed to connect", "connection_exception": "Failed to connect", "invalid_auth": "Invalid authentication", "unauthenticated_exception": "Invalid authentication", diff --git a/homeassistant/components/huisbaasje/translations/et.json b/homeassistant/components/huisbaasje/translations/et.json index d079bf2a0c7bb1..b41701427782d8 100644 --- a/homeassistant/components/huisbaasje/translations/et.json +++ b/homeassistant/components/huisbaasje/translations/et.json @@ -4,6 +4,7 @@ "already_configured": "Seade on juba h\u00e4\u00e4lestatud" }, "error": { + "cannot_connect": "\u00dchendamine nurjus", "connection_exception": "\u00dchendamine nurjus", "invalid_auth": "Vigane autentimine", "unauthenticated_exception": "Vigane autentimine", diff --git a/homeassistant/components/huisbaasje/translations/ko.json b/homeassistant/components/huisbaasje/translations/ko.json index bd25569d7c7236..19387dfe542d74 100644 --- a/homeassistant/components/huisbaasje/translations/ko.json +++ b/homeassistant/components/huisbaasje/translations/ko.json @@ -4,6 +4,7 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection_exception": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unauthenticated_exception": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/huisbaasje/translations/nl.json b/homeassistant/components/huisbaasje/translations/nl.json index 8cb09793af81be..a13c1837b9f9a4 100644 --- a/homeassistant/components/huisbaasje/translations/nl.json +++ b/homeassistant/components/huisbaasje/translations/nl.json @@ -4,6 +4,7 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { + "cannot_connect": "Kan geen verbinding maken", "connection_exception": "Kan geen verbinding maken", "invalid_auth": "Ongeldige authenticatie", "unauthenticated_exception": "Ongeldige authenticatie", diff --git a/homeassistant/components/huisbaasje/translations/ru.json b/homeassistant/components/huisbaasje/translations/ru.json index 995e340c396b39..c9fbe5cdcb2eb5 100644 --- a/homeassistant/components/huisbaasje/translations/ru.json +++ b/homeassistant/components/huisbaasje/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." }, "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_exception": "\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.", "unauthenticated_exception": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438.", diff --git a/homeassistant/components/huisbaasje/translations/zh-Hant.json b/homeassistant/components/huisbaasje/translations/zh-Hant.json index bb120ab60ddad0..b1e95586376433 100644 --- a/homeassistant/components/huisbaasje/translations/zh-Hant.json +++ b/homeassistant/components/huisbaasje/translations/zh-Hant.json @@ -4,6 +4,7 @@ "already_configured": "\u88dd\u7f6e\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_exception": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unauthenticated_exception": "\u9a57\u8b49\u78bc\u7121\u6548", diff --git a/homeassistant/components/lutron_caseta/translations/nl.json b/homeassistant/components/lutron_caseta/translations/nl.json index 4b97da2058b2a5..b281d3cd22cfff 100644 --- a/homeassistant/components/lutron_caseta/translations/nl.json +++ b/homeassistant/components/lutron_caseta/translations/nl.json @@ -15,7 +15,7 @@ "title": "Het importeren van de Cas\u00e9ta bridge configuratie is mislukt." }, "link": { - "description": "Om te koppelen met {naam} ({host}), na het verzenden van dit formulier, druk op de zwarte knop op de achterkant van de brug.", + "description": "Om te koppelen met {name} ({host}), na het verzenden van dit formulier, druk op de zwarte knop op de achterkant van de brug.", "title": "Koppel met de bridge" }, "user": { diff --git a/homeassistant/components/omnilogic/translations/ko.json b/homeassistant/components/omnilogic/translations/ko.json index d2a5f58abeb84e..0f3df64c00f782 100644 --- a/homeassistant/components/omnilogic/translations/ko.json +++ b/homeassistant/components/omnilogic/translations/ko.json @@ -21,7 +21,7 @@ "step": { "init": { "data": { - "polling_interval": "\ud3f4\ub9c1 \uac04\uaca9(\ucd08)" + "polling_interval": "\ud3f4\ub9c1 \uac04\uaca9 (\ucd08)" } } } diff --git a/homeassistant/components/onvif/translations/nl.json b/homeassistant/components/onvif/translations/nl.json index 4d76e939af023b..e1fdac8256e59a 100644 --- a/homeassistant/components/onvif/translations/nl.json +++ b/homeassistant/components/onvif/translations/nl.json @@ -52,7 +52,7 @@ "extra_arguments": "Extra FFMPEG argumenten", "rtsp_transport": "RTSP-transportmechanisme" }, - "title": "[%%] Apparaatopties" + "title": "ONVIF-apparaatopties" } } } diff --git a/homeassistant/components/ozw/translations/nl.json b/homeassistant/components/ozw/translations/nl.json index cb443f3a27a140..7392f9b63eb07d 100644 --- a/homeassistant/components/ozw/translations/nl.json +++ b/homeassistant/components/ozw/translations/nl.json @@ -6,7 +6,7 @@ "addon_set_config_failed": "Mislukt om OpenZWave configuratie in te stellen.", "already_configured": "Apparaat is al geconfigureerd", "already_in_progress": "De configuratiestroom is al aan de gang", - "mqtt_required": "De [%%] integratie is niet ingesteld", + "mqtt_required": "De MQTT-integratie is niet ingesteld", "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n configuratie mogelijk." }, "error": { diff --git a/homeassistant/components/plaato/translations/nl.json b/homeassistant/components/plaato/translations/nl.json index 707830c33a5d78..d50763d0e1acaf 100644 --- a/homeassistant/components/plaato/translations/nl.json +++ b/homeassistant/components/plaato/translations/nl.json @@ -28,7 +28,7 @@ "device_type": "Type Plaato-apparaat" }, "description": "Wil je beginnen met instellen?", - "title": "Stel de Plaato Webhook in" + "title": "Stel de Plaato-apparaten in" }, "webhook": { "description": "Om evenementen naar de Home Assistant te sturen, moet u de webhook-functie instellen in Plaato Airlock. \n\n Vul de volgende info in: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n\n Zie [de documentatie] ({docs_url}) voor meer informatie.", diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 408ddf967f4f9d..af77f6f15e14d3 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -14,8 +14,8 @@ "data": { "flow_type": "Verbindingstype" }, - "description": "Details", - "title": "Maak verbinding met de Smile" + "description": "Product:", + "title": "Plugwise type" }, "user_gateway": { "data": { diff --git a/homeassistant/components/rachio/translations/ko.json b/homeassistant/components/rachio/translations/ko.json index 203dba17303e0b..e0c7a3a13a34dc 100644 --- a/homeassistant/components/rachio/translations/ko.json +++ b/homeassistant/components/rachio/translations/ko.json @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "\uad6c\uc5ed \uc2a4\uc704\uce58\ub97c \ud65c\uc131\ud654\ud560 \ub54c \uc2e4\ud589\ud560 \uc2dc\uac04(\ubd84)" + "manual_run_mins": "\uad6c\uc5ed \uc2a4\uc704\uce58\ub97c \ud65c\uc131\ud654\ud560 \ub54c \uc2e4\ud589\ud560 \uc2dc\uac04 (\ubd84)" } } } diff --git a/homeassistant/components/screenlogic/translations/ko.json b/homeassistant/components/screenlogic/translations/ko.json index 3a8d457c603a99..94ddca6830a4e7 100644 --- a/homeassistant/components/screenlogic/translations/ko.json +++ b/homeassistant/components/screenlogic/translations/ko.json @@ -29,7 +29,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, "description": "{gateway_name}\uc5d0 \ub300\ud55c \uc124\uc815 \uc9c0\uc815\ud558\uae30", "title": "ScreenLogic" diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index efc20dbe03f570..ab9dc4d445a641 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -46,7 +46,7 @@ "step": { "init": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ubd84)", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ubd84)", "timeout": "\uc81c\ud55c \uc2dc\uac04 (\ucd08)" } } diff --git a/homeassistant/components/tesla/translations/ko.json b/homeassistant/components/tesla/translations/ko.json index 3e3893e0b7531f..285326f39de44b 100644 --- a/homeassistant/components/tesla/translations/ko.json +++ b/homeassistant/components/tesla/translations/ko.json @@ -25,7 +25,7 @@ "init": { "data": { "enable_wake_on_start": "\uc2dc\ub3d9 \uc2dc \ucc28\ub7c9 \uae68\uc6b0\uae30", - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ucd08)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" } } } diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index cd4192e257cfdf..331d5850fc4d79 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -16,7 +16,7 @@ "other": "Leeg" }, "ssdp_confirm": { - "description": "Wilt u [%%] instellen?" + "description": "Wilt u dit UPnP/IGD-apparaat instellen?" }, "user": { "data": { From 387e16644704ab0c5106d7d701986ed0e70efea4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 05:52:01 +0100 Subject: [PATCH 1531/1818] Remove HomeAssistantType alias from AdGuard integration (#48377) --- homeassistant/components/adguard/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 4015bd31bf2873..04d266e7d9b325 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -29,11 +29,12 @@ CONF_USERNAME, CONF_VERIFY_SSL, ) +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -48,12 +49,12 @@ PLATFORMS = ["sensor", "switch"] -async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the AdGuard Home components.""" return True -async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up AdGuard Home from a config entry.""" session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]) adguard = AdGuardHome( @@ -123,7 +124,7 @@ async def refresh(call) -> None: return True -async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload AdGuard Home config entry.""" hass.services.async_remove(DOMAIN, SERVICE_ADD_URL) hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL) From 63e30123805384214420a18eeb25e5e630f68b21 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Mar 2021 23:56:40 -0700 Subject: [PATCH 1532/1818] Fix script default trace (#48390) --- homeassistant/helpers/script.py | 2 +- tests/helpers/test_script.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 3df5be09f133c8..aa092ea2c8ceca 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -737,7 +737,7 @@ async def _async_choose_step(self) -> None: if choose_data["default"]: trace_set_result(choice="default") - with trace_path(["default", "sequence"]): + with trace_path(["default"]): await self._async_run_script(choose_data["default"]) async def _async_wait_for_trigger_step(self): diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 917fc64b0e72eb..e4170ccae20b6c 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -2059,7 +2059,7 @@ async def test_choose(hass, caplog, var, result): {"result": {"event": "test_event", "event_data": {"choice": "second"}}} ] if var == 3: - expected_trace["0/default/sequence/0"] = [ + expected_trace["0/default/0"] = [ {"result": {"event": "test_event", "event_data": {"choice": "default"}}} ] assert_action_trace(expected_trace) From b50dcef94fba6528c842448c53bab7c31be35be9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 21:54:49 -1000 Subject: [PATCH 1533/1818] Block detectable I/O in the event loop (#48387) We added a warning when this happens last April and gave developers a year to fix the instability. We now prevent the instability by raising RuntimeError when code attempts to do known I/O in the event loop instead of the executor. We now provide a suggestion on how to fix the code that is causing the issue. --- homeassistant/util/async_.py | 4 ++++ tests/util/test_async.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 0fd1b564f0d6fd..15353d1f7eb8d8 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -136,6 +136,10 @@ def check_loop() -> None: found_frame.lineno, found_frame.line.strip(), ) + raise RuntimeError( + f"I/O must be done in the executor; Use `await hass.async_add_executor_job()` " + f"at {found_frame.filename[index:]}, line {found_frame.lineno}: {found_frame.line.strip()}" + ) def protect_loop(func: Callable) -> Callable: diff --git a/tests/util/test_async.py b/tests/util/test_async.py index d4fdce1e9123ec..d5564a90d0e817 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -78,7 +78,7 @@ async def test_check_loop_async(): async def test_check_loop_async_integration(caplog): """Test check_loop detects when called from event loop from integration context.""" - with patch( + with pytest.raises(RuntimeError), patch( "homeassistant.util.async_.extract_stack", return_value=[ Mock( @@ -107,7 +107,7 @@ async def test_check_loop_async_integration(caplog): async def test_check_loop_async_custom(caplog): """Test check_loop detects when called from event loop with custom component context.""" - with patch( + with pytest.raises(RuntimeError), patch( "homeassistant.util.async_.extract_stack", return_value=[ Mock( From 79af18a8abda9868a9571a0cdcbdda63c521e79c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 22:02:01 -1000 Subject: [PATCH 1534/1818] Bump httpx to 0.17.1 (#48388) * Bump httpx to 0.17.1 * git add * typing * add test * tweak --- homeassistant/helpers/httpx_client.py | 13 ++++++++++++- homeassistant/package_constraints.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- tests/helpers/test_httpx_client.py | 13 +++++++++++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index dfac0694a074a1..44045a7abeb0e3 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -39,6 +39,17 @@ def get_async_client( return client +class HassHttpXAsyncClient(httpx.AsyncClient): + """httpx AsyncClient that suppresses context management.""" + + async def __aenter__(self: HassHttpXAsyncClient) -> HassHttpXAsyncClient: + """Prevent an integration from reopen of the client via context manager.""" + return self + + async def __aexit__(self, *args: Any) -> None: # pylint: disable=signature-differs + """Prevent an integration from close of the client via context manager.""" + + @callback def create_async_httpx_client( hass: HomeAssistantType, @@ -53,7 +64,7 @@ def create_async_httpx_client( This method must be run in the event loop. """ - client = httpx.AsyncClient( + client = HassHttpXAsyncClient( verify=verify_ssl, headers={USER_AGENT: SERVER_SOFTWARE}, **kwargs, diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 86269b9da06b7c..4d1d53b85d122d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ distro==1.5.0 emoji==1.2.0 hass-nabucasa==0.42.0 home-assistant-frontend==20210324.0 -httpx==0.16.1 +httpx==0.17.1 jinja2>=2.11.3 netdisco==2.8.2 paho-mqtt==1.5.1 diff --git a/requirements.txt b/requirements.txt index dafb5686e4a0a7..5f633eaeb6910f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ awesomeversion==21.2.3 bcrypt==3.1.7 certifi>=2020.12.5 ciso8601==2.1.3 -httpx==0.16.1 +httpx==0.17.1 jinja2>=2.11.3 PyJWT==1.7.1 cryptography==3.3.2 diff --git a/setup.py b/setup.py index d3a8a5aea7070b..56e56391489f97 100755 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ "bcrypt==3.1.7", "certifi>=2020.12.5", "ciso8601==2.1.3", - "httpx==0.16.1", + "httpx==0.17.1", "jinja2>=2.11.3", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. diff --git a/tests/helpers/test_httpx_client.py b/tests/helpers/test_httpx_client.py index 53a6985f5cc779..a47463b6b98945 100644 --- a/tests/helpers/test_httpx_client.py +++ b/tests/helpers/test_httpx_client.py @@ -80,6 +80,19 @@ async def test_get_async_client_patched_close(hass): assert mock_aclose.call_count == 0 +async def test_get_async_client_context_manager(hass): + """Test using the async client with a context manager does not close the session.""" + + with patch("httpx.AsyncClient.aclose") as mock_aclose: + httpx_session = client.get_async_client(hass) + assert isinstance(hass.data[client.DATA_ASYNC_CLIENT], httpx.AsyncClient) + + async with httpx_session: + pass + + assert mock_aclose.call_count == 0 + + async def test_warning_close_session_integration(hass, caplog): """Test log warning message when closing the session from integration context.""" with patch( From ad13a9295e61dcb475605b3fdc880069d423295c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 09:17:15 +0100 Subject: [PATCH 1535/1818] Merge multiple context managers in tests (#48146) --- tests/auth/test_init.py | 5 +- .../components/androidtv/test_media_player.py | 34 ++-- tests/components/apprise/test_notify.py | 13 +- tests/components/bond/common.py | 31 ++-- tests/components/device_tracker/test_init.py | 50 +++--- tests/components/elgato/test_light.py | 25 ++- tests/components/emulated_hue/test_init.py | 113 ++++++------ tests/components/enocean/test_config_flow.py | 30 ++-- tests/components/filter/test_sensor.py | 48 +++-- .../geo_json_events/test_geo_location.py | 97 +++++----- .../components/geo_rss_events/test_sensor.py | 97 +++++----- tests/components/graphite/test_init.py | 17 +- tests/components/hisense_aehw4a1/test_init.py | 69 ++++--- tests/components/history_stats/test_sensor.py | 22 ++- tests/components/influxdb/test_init.py | 17 +- tests/components/lyric/test_config_flow.py | 9 +- tests/components/melissa/test_climate.py | 75 ++++---- .../minecraft_server/test_config_flow.py | 169 ++++++++---------- tests/components/mqtt/test_light.py | 7 +- tests/components/mqtt/test_light_template.py | 65 ++++--- tests/components/pilight/test_init.py | 69 +++---- .../signal_messenger/test_notify.py | 22 ++- tests/components/system_log/test_init.py | 25 ++- tests/components/zha/test_channels.py | 9 +- tests/components/zha/test_discover.py | 19 +- tests/components/zwave/test_init.py | 47 +++-- .../helpers/test_config_entry_oauth2_flow.py | 7 +- tests/helpers/test_network.py | 5 +- tests/helpers/test_service.py | 31 ++-- tests/test_config_entries.py | 11 +- tests/test_requirements.py | 5 +- tests/util/test_async.py | 7 +- tests/util/yaml/test_init.py | 5 +- 33 files changed, 609 insertions(+), 646 deletions(-) diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py index 4f34ce1d595991..255fcac7694e07 100644 --- a/tests/auth/test_init.py +++ b/tests/auth/test_init.py @@ -501,9 +501,8 @@ async def test_refresh_token_provider_validation(mock_hass): with patch( "homeassistant.auth.providers.insecure_example.ExampleAuthProvider.async_validate_refresh_token", side_effect=InvalidAuthError("Invalid access"), - ) as call: - with pytest.raises(InvalidAuthError): - manager.async_create_access_token(refresh_token, ip) + ) as call, pytest.raises(InvalidAuthError): + manager.async_create_access_token(refresh_token, ip) call.assert_called_with(refresh_token, ip) diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index a9a803741b12c7..d72cf36438b32e 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -933,12 +933,11 @@ async def test_update_lock_not_acquired(hass): with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", side_effect=LockNotAcquiredException, - ): - with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: - await hass.helpers.entity_component.async_update_entity(entity_id) - state = hass.states.get(entity_id) - assert state is not None - assert state.state == STATE_OFF + ), patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: + await hass.helpers.entity_component.async_update_entity(entity_id) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -1206,19 +1205,18 @@ async def test_connection_closed_on_ha_stop(hass): """Test that the ADB socket connection is closed when HA stops.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: - with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - assert await async_setup_component( - hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER - ) - await hass.async_block_till_done() + with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ + patch_key + ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() - with patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" - ) as adb_close: - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - assert adb_close.called + with patch( + "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" + ) as adb_close: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert adb_close.called async def test_exception(hass): diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 8135f4e8e2c2e5..3e539e73516471 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -28,13 +28,14 @@ async def test_apprise_config_load_fail02(hass): BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"} } - with patch("apprise.Apprise.add", return_value=False): - with patch("apprise.AppriseConfig.add", return_value=True): - assert await async_setup_component(hass, BASE_COMPONENT, config) - await hass.async_block_till_done() + with patch("apprise.Apprise.add", return_value=False), patch( + "apprise.AppriseConfig.add", return_value=True + ): + assert await async_setup_component(hass, BASE_COMPONENT, config) + await hass.async_block_till_done() - # Test that our service failed to load - assert not hass.services.has_service(BASE_COMPONENT, "test") + # Test that our service failed to load + assert not hass.services.has_service(BASE_COMPONENT, "test") async def test_apprise_config_load_okay(hass, tmp_path): diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index d8adf134293311..0791d002fed77a 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -72,20 +72,23 @@ async def setup_platform( ) mock_entry.add_to_hass(hass) - with patch("homeassistant.components.bond.PLATFORMS", [platform]): - with patch_bond_version(return_value=bond_version), patch_bond_bridge( - return_value=bridge - ), patch_bond_token(return_value=token), patch_bond_device_ids( - return_value=[bond_device_id] - ), patch_start_bpup(), patch_bond_device( - return_value=discovered_device - ), patch_bond_device_properties( - return_value=props - ), patch_bond_device_state( - return_value=state - ): - assert await async_setup_component(hass, BOND_DOMAIN, {}) - await hass.async_block_till_done() + with patch( + "homeassistant.components.bond.PLATFORMS", [platform] + ), patch_bond_version(return_value=bond_version), patch_bond_bridge( + return_value=bridge + ), patch_bond_token( + return_value=token + ), patch_bond_device_ids( + return_value=[bond_device_id] + ), patch_start_bpup(), patch_bond_device( + return_value=discovered_device + ), patch_bond_device_properties( + return_value=props + ), patch_bond_device_state( + return_value=state + ): + assert await async_setup_component(hass, BOND_DOMAIN, {}) + await hass.async_block_till_done() return mock_entry diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index d62e46255d7218..af0c7658ac77e9 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -237,19 +237,18 @@ async def test_update_stale(hass, mock_device_tracker_conf): with patch( "homeassistant.components.device_tracker.legacy.dt_util.utcnow", return_value=register_time, - ): - with assert_setup_component(1, device_tracker.DOMAIN): - assert await async_setup_component( - hass, - device_tracker.DOMAIN, - { - device_tracker.DOMAIN: { - CONF_PLATFORM: "test", - device_tracker.CONF_CONSIDER_HOME: 59, - } - }, - ) - await hass.async_block_till_done() + ), assert_setup_component(1, device_tracker.DOMAIN): + assert await async_setup_component( + hass, + device_tracker.DOMAIN, + { + device_tracker.DOMAIN: { + CONF_PLATFORM: "test", + device_tracker.CONF_CONSIDER_HOME: 59, + } + }, + ) + await hass.async_block_till_done() assert hass.states.get("device_tracker.dev1").state == STATE_HOME @@ -458,19 +457,18 @@ async def test_see_passive_zone_state(hass, mock_device_tracker_conf): with patch( "homeassistant.components.device_tracker.legacy.dt_util.utcnow", return_value=register_time, - ): - with assert_setup_component(1, device_tracker.DOMAIN): - assert await async_setup_component( - hass, - device_tracker.DOMAIN, - { - device_tracker.DOMAIN: { - CONF_PLATFORM: "test", - device_tracker.CONF_CONSIDER_HOME: 59, - } - }, - ) - await hass.async_block_till_done() + ), assert_setup_component(1, device_tracker.DOMAIN): + assert await async_setup_component( + hass, + device_tracker.DOMAIN, + { + device_tracker.DOMAIN: { + CONF_PLATFORM: "test", + device_tracker.CONF_CONSIDER_HOME: 59, + } + }, + ) + await hass.async_block_till_done() state = hass.states.get("device_tracker.dev1") attrs = state.attributes diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py index 48e6bd24625e2a..6c4de76719fcdf 100644 --- a/tests/components/elgato/test_light.py +++ b/tests/components/elgato/test_light.py @@ -93,17 +93,16 @@ async def test_light_unavailable( with patch( "homeassistant.components.elgato.light.Elgato.light", side_effect=ElgatoError, + ), patch( + "homeassistant.components.elgato.light.Elgato.state", + side_effect=ElgatoError, ): - with patch( - "homeassistant.components.elgato.light.Elgato.state", - side_effect=ElgatoError, - ): - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.frenck"}, - blocking=True, - ) - await hass.async_block_till_done() - state = hass.states.get("light.frenck") - assert state.state == STATE_UNAVAILABLE + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.frenck"}, + blocking=True, + ) + await hass.async_block_till_done() + state = hass.states.get("light.frenck") + assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 6fa6d9695390ca..8e8c0f412499f6 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -13,29 +13,30 @@ def test_config_google_home_entity_id_to_number(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={"1": "light.test2"}, - ) as json_loader: - with patch("homeassistant.components.emulated_hue.save_json") as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "2" + ) as json_loader, patch( + "homeassistant.components.emulated_hue.save_json" + ) as json_saver: + number = conf.entity_id_to_number("light.test") + assert number == "2" - assert json_saver.mock_calls[0][1][1] == { - "1": "light.test2", - "2": "light.test", - } + assert json_saver.mock_calls[0][1][1] == { + "1": "light.test2", + "2": "light.test", + } - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - number = conf.entity_id_to_number("light.test") - assert number == "2" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "2" + assert json_saver.call_count == 1 - number = conf.entity_id_to_number("light.test2") - assert number == "1" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test2") + assert number == "1" + assert json_saver.call_count == 1 - entity_id = conf.number_to_entity_id("1") - assert entity_id == "light.test2" + entity_id = conf.number_to_entity_id("1") + assert entity_id == "light.test2" def test_config_google_home_entity_id_to_number_altered(): @@ -47,28 +48,29 @@ def test_config_google_home_entity_id_to_number_altered(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={"21": "light.test2"}, - ) as json_loader: - with patch("homeassistant.components.emulated_hue.save_json") as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "22" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + ) as json_loader, patch( + "homeassistant.components.emulated_hue.save_json" + ) as json_saver: + number = conf.entity_id_to_number("light.test") + assert number == "22" + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - assert json_saver.mock_calls[0][1][1] == { - "21": "light.test2", - "22": "light.test", - } + assert json_saver.mock_calls[0][1][1] == { + "21": "light.test2", + "22": "light.test", + } - number = conf.entity_id_to_number("light.test") - assert number == "22" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "22" + assert json_saver.call_count == 1 - number = conf.entity_id_to_number("light.test2") - assert number == "21" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test2") + assert number == "21" + assert json_saver.call_count == 1 - entity_id = conf.number_to_entity_id("21") - assert entity_id == "light.test2" + entity_id = conf.number_to_entity_id("21") + assert entity_id == "light.test2" def test_config_google_home_entity_id_to_number_empty(): @@ -79,25 +81,26 @@ def test_config_google_home_entity_id_to_number_empty(): with patch( "homeassistant.components.emulated_hue.load_json", return_value={} - ) as json_loader: - with patch("homeassistant.components.emulated_hue.save_json") as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "1" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 - - assert json_saver.mock_calls[0][1][1] == {"1": "light.test"} - - number = conf.entity_id_to_number("light.test") - assert number == "1" - assert json_saver.call_count == 1 - - number = conf.entity_id_to_number("light.test2") - assert number == "2" - assert json_saver.call_count == 2 - - entity_id = conf.number_to_entity_id("2") - assert entity_id == "light.test2" + ) as json_loader, patch( + "homeassistant.components.emulated_hue.save_json" + ) as json_saver: + number = conf.entity_id_to_number("light.test") + assert number == "1" + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 + + assert json_saver.mock_calls[0][1][1] == {"1": "light.test"} + + number = conf.entity_id_to_number("light.test") + assert number == "1" + assert json_saver.call_count == 1 + + number = conf.entity_id_to_number("light.test2") + assert number == "2" + assert json_saver.call_count == 2 + + entity_id = conf.number_to_entity_id("2") + assert entity_id == "light.test2" def test_config_alexa_entity_id_to_number(): diff --git a/tests/components/enocean/test_config_flow.py b/tests/components/enocean/test_config_flow.py index 4cea2a4fb9b4fa..60a20af5eae933 100644 --- a/tests/components/enocean/test_config_flow.py +++ b/tests/components/enocean/test_config_flow.py @@ -73,13 +73,14 @@ async def test_detection_flow_with_custom_path(hass): USER_PROVIDED_PATH = EnOceanFlowHandler.MANUAL_PATH_VALUE FAKE_DONGLE_PATH = "/fake/dongle" - with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)): - with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "detect"}, - data={CONF_DEVICE: USER_PROVIDED_PATH}, - ) + with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)), patch( + DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH]) + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "detect"}, + data={CONF_DEVICE: USER_PROVIDED_PATH}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "manual" @@ -90,13 +91,14 @@ async def test_detection_flow_with_invalid_path(hass): USER_PROVIDED_PATH = "/invalid/path" FAKE_DONGLE_PATH = "/fake/dongle" - with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)): - with patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "detect"}, - data={CONF_DEVICE: USER_PROVIDED_PATH}, - ) + with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)), patch( + DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH]) + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "detect"}, + data={CONF_DEVICE: USER_PROVIDED_PATH}, + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "detect" diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index b787fc0235c984..b8cdaf3c88a567 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -116,24 +116,23 @@ async def test_chain_history(hass, values, missing=False): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, + ), patch( + "homeassistant.components.history.get_last_state_changes", + return_value=fake_states, ): - with patch( - "homeassistant.components.history.get_last_state_changes", - return_value=fake_states, - ): - with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() - for value in values: - hass.states.async_set(config["sensor"]["entity_id"], value.state) - await hass.async_block_till_done() + for value in values: + hass.states.async_set(config["sensor"]["entity_id"], value.state) + await hass.async_block_till_done() - state = hass.states.get("sensor.test") - if missing: - assert state.state == "18.05" - else: - assert state.state == "17.05" + state = hass.states.get("sensor.test") + if missing: + assert state.state == "18.05" + else: + assert state.state == "17.05" async def test_source_state_none(hass, values): @@ -234,18 +233,17 @@ async def test_history_time(hass): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, + ), patch( + "homeassistant.components.history.get_last_state_changes", + return_value=fake_states, ): - with patch( - "homeassistant.components.history.get_last_state_changes", - return_value=fake_states, - ): - with assert_setup_component(1, "sensor"): - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() - + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() - state = hass.states.get("sensor.test") - assert state.state == "18.0" + + await hass.async_block_till_done() + state = hass.states.get("sensor.test") + assert state.state == "18.0" async def test_setup(hass): diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index 75f41bb93c0646..9d68e3d497338c 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -198,60 +198,59 @@ async def test_setup_race_condition(hass, legacy_patchable_time): utcnow = dt_util.utcnow() with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( "geojson_client.generic_feed.GenericFeed" - ) as mock_feed: - with assert_setup_component(1, geo_location.DOMAIN): - assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) - await hass.async_block_till_done() + ) as mock_feed, assert_setup_component(1, geo_location.DOMAIN): + assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() - mock_feed.return_value.update.return_value = "OK", [mock_entry_1] + mock_feed.return_value.update.return_value = "OK", [mock_entry_1] - # Artificially trigger update. - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) - # Collect events. - await hass.async_block_till_done() + # Artificially trigger update. + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + # Collect events. + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 1 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 + all_states = hass.states.async_all() + assert len(all_states) == 1 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 - # Simulate an update - empty data, removes all entities - mock_feed.return_value.update.return_value = "ERROR", None - async_fire_time_changed(hass, utcnow + SCAN_INTERVAL) - await hass.async_block_till_done() + # Simulate an update - empty data, removes all entities + mock_feed.return_value.update.return_value = "ERROR", None + async_fire_time_changed(hass, utcnow + SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = hass.states.async_all() - assert len(all_states) == 0 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 + all_states = hass.states.async_all() + assert len(all_states) == 0 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 - # Simulate an update - 1 entry - mock_feed.return_value.update.return_value = "OK", [mock_entry_1] - async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) - await hass.async_block_till_done() - - all_states = hass.states.async_all() - assert len(all_states) == 1 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 - - # Simulate an update - 1 entry - mock_feed.return_value.update.return_value = "OK", [mock_entry_1] - async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) - await hass.async_block_till_done() - - all_states = hass.states.async_all() - assert len(all_states) == 1 - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 + # Simulate an update - 1 entry + mock_feed.return_value.update.return_value = "OK", [mock_entry_1] + async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) + await hass.async_block_till_done() - # Simulate an update - empty data, removes all entities - mock_feed.return_value.update.return_value = "ERROR", None - async_fire_time_changed(hass, utcnow + 4 * SCAN_INTERVAL) - await hass.async_block_till_done() + all_states = hass.states.async_all() + assert len(all_states) == 1 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 - all_states = hass.states.async_all() - assert len(all_states) == 0 - # Ensure that delete and update signal targets are now empty. - assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 - assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 + # Simulate an update - 1 entry + mock_feed.return_value.update.return_value = "OK", [mock_entry_1] + async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) + await hass.async_block_till_done() + + all_states = hass.states.async_all() + assert len(all_states) == 1 + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 1 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 1 + + # Simulate an update - empty data, removes all entities + mock_feed.return_value.update.return_value = "ERROR", None + async_fire_time_changed(hass, utcnow + 4 * SCAN_INTERVAL) + await hass.async_block_till_done() + + all_states = hass.states.async_all() + assert len(all_states) == 0 + # Ensure that delete and update signal targets are now empty. + assert len(hass.data[DATA_DISPATCHER][delete_signal]) == 0 + assert len(hass.data[DATA_DISPATCHER][update_signal]) == 0 diff --git a/tests/components/geo_rss_events/test_sensor.py b/tests/components/geo_rss_events/test_sensor.py index ead81e5c5a8482..95fb89301a9167 100644 --- a/tests/components/geo_rss_events/test_sensor.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -68,54 +68,55 @@ async def test_setup(hass, mock_feed): utcnow = dt_util.utcnow() # Patching 'utcnow' to gain more control over the timed update. - with patch("homeassistant.util.dt.utcnow", return_value=utcnow): - with assert_setup_component(1, sensor.DOMAIN): - assert await async_setup_component(hass, sensor.DOMAIN, VALID_CONFIG) - # Artificially trigger update. - hass.bus.fire(EVENT_HOMEASSISTANT_START) - # Collect events. - await hass.async_block_till_done() - - all_states = hass.states.async_all() - assert len(all_states) == 1 - - state = hass.states.get("sensor.event_service_any") - assert state is not None - assert state.name == "Event Service Any" - assert int(state.state) == 2 - assert state.attributes == { - ATTR_FRIENDLY_NAME: "Event Service Any", - ATTR_UNIT_OF_MEASUREMENT: "Events", - ATTR_ICON: "mdi:alert", - "Title 1": "16km", - "Title 2": "20km", - } - - # Simulate an update - empty data, but successful update, - # so no changes to entities. - mock_feed.return_value.update.return_value = "OK_NO_DATA", None - async_fire_time_changed(hass, utcnow + geo_rss_events.SCAN_INTERVAL) - await hass.async_block_till_done() - - all_states = hass.states.async_all() - assert len(all_states) == 1 - state = hass.states.get("sensor.event_service_any") - assert int(state.state) == 2 - - # Simulate an update - empty data, removes all entities - mock_feed.return_value.update.return_value = "ERROR", None - async_fire_time_changed(hass, utcnow + 2 * geo_rss_events.SCAN_INTERVAL) - await hass.async_block_till_done() - - all_states = hass.states.async_all() - assert len(all_states) == 1 - state = hass.states.get("sensor.event_service_any") - assert int(state.state) == 0 - assert state.attributes == { - ATTR_FRIENDLY_NAME: "Event Service Any", - ATTR_UNIT_OF_MEASUREMENT: "Events", - ATTR_ICON: "mdi:alert", - } + with patch( + "homeassistant.util.dt.utcnow", return_value=utcnow + ), assert_setup_component(1, sensor.DOMAIN): + assert await async_setup_component(hass, sensor.DOMAIN, VALID_CONFIG) + # Artificially trigger update. + hass.bus.fire(EVENT_HOMEASSISTANT_START) + # Collect events. + await hass.async_block_till_done() + + all_states = hass.states.async_all() + assert len(all_states) == 1 + + state = hass.states.get("sensor.event_service_any") + assert state is not None + assert state.name == "Event Service Any" + assert int(state.state) == 2 + assert state.attributes == { + ATTR_FRIENDLY_NAME: "Event Service Any", + ATTR_UNIT_OF_MEASUREMENT: "Events", + ATTR_ICON: "mdi:alert", + "Title 1": "16km", + "Title 2": "20km", + } + + # Simulate an update - empty data, but successful update, + # so no changes to entities. + mock_feed.return_value.update.return_value = "OK_NO_DATA", None + async_fire_time_changed(hass, utcnow + geo_rss_events.SCAN_INTERVAL) + await hass.async_block_till_done() + + all_states = hass.states.async_all() + assert len(all_states) == 1 + state = hass.states.get("sensor.event_service_any") + assert int(state.state) == 2 + + # Simulate an update - empty data, removes all entities + mock_feed.return_value.update.return_value = "ERROR", None + async_fire_time_changed(hass, utcnow + 2 * geo_rss_events.SCAN_INTERVAL) + await hass.async_block_till_done() + + all_states = hass.states.async_all() + assert len(all_states) == 1 + state = hass.states.get("sensor.event_service_any") + assert int(state.state) == 0 + assert state.attributes == { + ATTR_FRIENDLY_NAME: "Event Service Any", + ATTR_UNIT_OF_MEASUREMENT: "Events", + ATTR_ICON: "mdi:alert", + } async def test_setup_with_categories(hass, mock_feed): diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index b7f5071813ebb4..23a25b1623e551 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -220,11 +220,12 @@ def fake_get(): runs.append(1) return event - with mock.patch.object(self.gf, "_queue") as mock_queue: - with mock.patch.object(self.gf, "_report_attributes") as mock_r: - mock_queue.get.side_effect = fake_get - self.gf.run() - # Twice for two events, once for the stop - assert mock_queue.task_done.call_count == 3 - assert mock_r.call_count == 1 - assert mock_r.call_args == mock.call("entity", event.data["new_state"]) + with mock.patch.object(self.gf, "_queue") as mock_queue, mock.patch.object( + self.gf, "_report_attributes" + ) as mock_r: + mock_queue.get.side_effect = fake_get + self.gf.run() + # Twice for two events, once for the stop + assert mock_queue.task_done.call_count == 3 + assert mock_r.call_count == 1 + assert mock_r.call_args == mock.call("entity", event.data["new_state"]) diff --git a/tests/components/hisense_aehw4a1/test_init.py b/tests/components/hisense_aehw4a1/test_init.py index 4add153ee94efd..ef99c3d1c964a6 100644 --- a/tests/components/hisense_aehw4a1/test_init.py +++ b/tests/components/hisense_aehw4a1/test_init.py @@ -13,24 +13,21 @@ async def test_creating_entry_sets_up_climate_discovery(hass): with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.discovery", return_value=["1.2.3.4"], - ): - with patch( - "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", - return_value=True, - ) as mock_setup: - result = await hass.config_entries.flow.async_init( - hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + ), patch( + "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", + return_value=True, + ) as mock_setup: + result = await hass.config_entries.flow.async_init( + hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) - # Confirmation form - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + # Confirmation form + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - await hass.async_block_till_done() + await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -40,17 +37,16 @@ async def test_configuring_hisense_w4a1_create_entry(hass): with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", return_value=True, - ): - with patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=True, - ) as mock_setup: - await async_setup_component( - hass, - hisense_aehw4a1.DOMAIN, - {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, - ) - await hass.async_block_till_done() + ), patch( + "homeassistant.components.hisense_aehw4a1.async_setup_entry", + return_value=True, + ) as mock_setup: + await async_setup_component( + hass, + hisense_aehw4a1.DOMAIN, + {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, + ) + await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -60,17 +56,16 @@ async def test_configuring_hisense_w4a1_not_creates_entry_for_device_not_found(h with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", side_effect=exceptions.ConnectionError, - ): - with patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=True, - ) as mock_setup: - await async_setup_component( - hass, - hisense_aehw4a1.DOMAIN, - {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, - ) - await hass.async_block_till_done() + ), patch( + "homeassistant.components.hisense_aehw4a1.async_setup_entry", + return_value=True, + ) as mock_setup: + await async_setup_component( + hass, + hisense_aehw4a1.DOMAIN, + {"hisense_aehw4a1": {"ip_address": ["1.2.3.4"]}}, + ) + await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 0 diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index 62e3959f4ad40f..f074003ab86dfb 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -162,12 +162,11 @@ def test_measure(self): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, - ): - with patch("homeassistant.components.history.get_state", return_value=None): - sensor1.update() - sensor2.update() - sensor3.update() - sensor4.update() + ), patch("homeassistant.components.history.get_state", return_value=None): + sensor1.update() + sensor2.update() + sensor3.update() + sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None @@ -246,12 +245,11 @@ def test_measure_multiple(self): with patch( "homeassistant.components.history.state_changes_during_period", return_value=fake_states, - ): - with patch("homeassistant.components.history.get_state", return_value=None): - sensor1.update() - sensor2.update() - sensor3.update() - sensor4.update() + ), patch("homeassistant.components.history.get_state", return_value=None): + sensor1.update() + sensor2.update() + sensor3.update() + sensor4.update() assert sensor1.state == 0.5 assert sensor2.state is None diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index 6c560eec5e74e9..e589a42e99af9f 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -254,14 +254,15 @@ async def test_setup_config_ssl( config = {"influxdb": config_base.copy()} config["influxdb"].update(config_ext) - with patch("os.access", return_value=True): - with patch("os.path.isfile", return_value=True): - assert await async_setup_component(hass, influxdb.DOMAIN, config) - await hass.async_block_till_done() - - assert hass.bus.listen.called - assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED - assert expected_client_args.items() <= mock_client.call_args.kwargs.items() + with patch("os.access", return_value=True), patch( + "os.path.isfile", return_value=True + ): + assert await async_setup_component(hass, influxdb.DOMAIN, config) + await hass.async_block_till_done() + + assert hass.bus.listen.called + assert hass.bus.listen.call_args_list[0][0][0] == EVENT_STATE_CHANGED + assert expected_client_args.items() <= mock_client.call_args.kwargs.items() @pytest.mark.parametrize( diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index 78fd90134664af..bfdd45f0f8e83e 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -90,11 +90,10 @@ async def test_full_flow( }, ) - with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"): - with patch( - "homeassistant.components.lyric.async_setup_entry", return_value=True - ) as mock_setup: - result = await hass.config_entries.flow.async_configure(result["flow_id"]) + with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"), patch( + "homeassistant.components.lyric.async_setup_entry", return_value=True + ) as mock_setup: + result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["data"]["auth_implementation"] == DOMAIN diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index 1b53e2f83347d4..590c5149f9e87e 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -288,19 +288,18 @@ async def test_update(hass): """Test update.""" with patch( "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning: - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.fan_mode == SPEED_LOW - assert thermostat.state == HVAC_MODE_HEAT - api.async_status = AsyncMock(side_effect=KeyError("boom")) - await thermostat.async_update() - mocked_warning.assert_called_once_with( - "Unable to update entity %s", thermostat.entity_id - ) + ) as mocked_warning, patch("homeassistant.components.melissa"): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert thermostat.fan_mode == SPEED_LOW + assert thermostat.state == HVAC_MODE_HEAT + api.async_status = AsyncMock(side_effect=KeyError("boom")) + await thermostat.async_update() + mocked_warning.assert_called_once_with( + "Unable to update entity %s", thermostat.entity_id + ) async def test_melissa_op_to_hass(hass): @@ -333,35 +332,33 @@ async def test_hass_mode_to_melissa(hass): """Test for hass operations to melssa.""" with patch( "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning: - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.hass_mode_to_melissa(HVAC_MODE_FAN_ONLY) == 1 - assert thermostat.hass_mode_to_melissa(HVAC_MODE_HEAT) == 2 - assert thermostat.hass_mode_to_melissa(HVAC_MODE_COOL) == 3 - assert thermostat.hass_mode_to_melissa(HVAC_MODE_DRY) == 4 - thermostat.hass_mode_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s mode", "test" - ) + ) as mocked_warning, patch("homeassistant.components.melissa"): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert thermostat.hass_mode_to_melissa(HVAC_MODE_FAN_ONLY) == 1 + assert thermostat.hass_mode_to_melissa(HVAC_MODE_HEAT) == 2 + assert thermostat.hass_mode_to_melissa(HVAC_MODE_COOL) == 3 + assert thermostat.hass_mode_to_melissa(HVAC_MODE_DRY) == 4 + thermostat.hass_mode_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s mode", "test" + ) async def test_hass_fan_to_melissa(hass): """Test for translate melissa states to hass.""" with patch( "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning: - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.hass_fan_to_melissa("auto") == 0 - assert thermostat.hass_fan_to_melissa(SPEED_LOW) == 1 - assert thermostat.hass_fan_to_melissa(SPEED_MEDIUM) == 2 - assert thermostat.hass_fan_to_melissa(SPEED_HIGH) == 3 - thermostat.hass_fan_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s fan mode", "test" - ) + ) as mocked_warning, patch("homeassistant.components.melissa"): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert thermostat.hass_fan_to_melissa("auto") == 0 + assert thermostat.hass_fan_to_melissa(SPEED_LOW) == 1 + assert thermostat.hass_fan_to_melissa(SPEED_MEDIUM) == 2 + assert thermostat.hass_fan_to_melissa(SPEED_HIGH) == 3 + thermostat.hass_fan_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s fan mode", "test" + ) diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py index 2f3b7781ecf1f2..9fcea3261ee62a 100644 --- a/tests/components/minecraft_server/test_config_flow.py +++ b/tests/components/minecraft_server/test_config_flow.py @@ -103,31 +103,27 @@ async def test_invalid_ip(hass: HomeAssistantType) -> None: async def test_same_host(hass: HomeAssistantType) -> None: """Test abort in case of same host name.""" - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, + with patch("aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError,), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - unique_id = "mc.dummyserver.com-25565" - config_data = { - CONF_NAME: DEFAULT_NAME, - CONF_HOST: "mc.dummyserver.com", - CONF_PORT: DEFAULT_PORT, - } - mock_config_entry = MockConfigEntry( - domain=DOMAIN, unique_id=unique_id, data=config_data - ) - mock_config_entry.add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) - - assert result["type"] == RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" + unique_id = "mc.dummyserver.com-25565" + config_data = { + CONF_NAME: DEFAULT_NAME, + CONF_HOST: "mc.dummyserver.com", + CONF_PORT: DEFAULT_PORT, + } + mock_config_entry = MockConfigEntry( + domain=DOMAIN, unique_id=unique_id, data=config_data + ) + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" async def test_port_too_small(hass: HomeAssistantType) -> None: @@ -163,93 +159,80 @@ async def test_connection_failed(hass: HomeAssistantType) -> None: with patch( "aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError, - ): - with patch("mcstatus.server.MinecraftServer.status", side_effect=OSError): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + ), patch("mcstatus.server.MinecraftServer.status", side_effect=OSError): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_FORM - assert result["errors"] == {"base": "cannot_connect"} + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} async def test_connection_succeeded_with_srv_record(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with a SRV record.""" - with patch( - "aiodns.DNSResolver.query", - return_value=SRV_RECORDS, + with patch("aiodns.DNSResolver.query", return_value=SRV_RECORDS,), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV + ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT_SRV[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT_SRV[CONF_NAME] - assert result["data"][CONF_HOST] == USER_INPUT_SRV[CONF_HOST] + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_SRV[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_SRV[CONF_NAME] + assert result["data"][CONF_HOST] == USER_INPUT_SRV[CONF_HOST] async def test_connection_succeeded_with_host(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with a host name.""" - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, + with patch("aiodns.DNSResolver.query", side_effect=aiodns.error.DNSError,), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT + ) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME] - assert result["data"][CONF_HOST] == "mc.dummyserver.com" + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT[CONF_NAME] + assert result["data"][CONF_HOST] == "mc.dummyserver.com" async def test_connection_succeeded_with_ip4(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with an IPv4 address.""" - with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"): - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, - ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4 - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT_IPV4[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME] - assert result["data"][CONF_HOST] == "1.1.1.1" + with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"), patch( + "aiodns.DNSResolver.query", + side_effect=aiodns.error.DNSError, + ), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4 + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_IPV4[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_IPV4[CONF_NAME] + assert result["data"][CONF_HOST] == "1.1.1.1" async def test_connection_succeeded_with_ip6(hass: HomeAssistantType) -> None: """Test config entry in case of a successful connection with an IPv6 address.""" - with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"): - with patch( - "aiodns.DNSResolver.query", - side_effect=aiodns.error.DNSError, - ): - with patch( - "mcstatus.server.MinecraftServer.status", - return_value=PingResponse(STATUS_RESPONSE_RAW), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6 - ) - - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == USER_INPUT_IPV6[CONF_HOST] - assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME] - assert result["data"][CONF_HOST] == "::ffff:0101:0101" + with patch("getmac.get_mac_address", return_value="01:23:45:67:89:ab"), patch( + "aiodns.DNSResolver.query", + side_effect=aiodns.error.DNSError, + ), patch( + "mcstatus.server.MinecraftServer.status", + return_value=PingResponse(STATUS_RESPONSE_RAW), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6 + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == USER_INPUT_IPV6[CONF_HOST] + assert result["data"][CONF_NAME] == USER_INPUT_IPV6[CONF_NAME] + assert result["data"][CONF_HOST] == "::ffff:0101:0101" diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 933f49ff823449..00ff8b28b7785e 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -743,10 +743,9 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=fake_state, - ): - with assert_setup_component(1, light.DOMAIN): - assert await async_setup_component(hass, light.DOMAIN, config) - await hass.async_block_till_done() + ), assert_setup_component(1, light.DOMAIN): + 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_ON diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 7b5d34edd69840..3bbf14ca668a1f 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -298,39 +298,38 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", return_value=fake_state, - ): - with assert_setup_component(1, light.DOMAIN): - assert await async_setup_component( - hass, - light.DOMAIN, - { - light.DOMAIN: { - "platform": "mqtt", - "schema": "template", - "name": "test", - "command_topic": "test_light_rgb/set", - "command_on_template": "on," - "{{ brightness|d }}," - "{{ color_temp|d }}," - "{{ white_value|d }}," - "{{ red|d }}-" - "{{ green|d }}-" - "{{ blue|d }}", - "command_off_template": "off", - "effect_list": ["colorloop", "random"], - "optimistic": True, - "state_template": '{{ value.split(",")[0] }}', - "color_temp_template": '{{ value.split(",")[2] }}', - "white_value_template": '{{ value.split(",")[3] }}', - "red_template": '{{ value.split(",")[4].' 'split("-")[0] }}', - "green_template": '{{ value.split(",")[4].' 'split("-")[1] }}', - "blue_template": '{{ value.split(",")[4].' 'split("-")[2] }}', - "effect_template": '{{ value.split(",")[5] }}', - "qos": 2, - } - }, - ) - await hass.async_block_till_done() + ), assert_setup_component(1, light.DOMAIN): + assert await async_setup_component( + hass, + light.DOMAIN, + { + light.DOMAIN: { + "platform": "mqtt", + "schema": "template", + "name": "test", + "command_topic": "test_light_rgb/set", + "command_on_template": "on," + "{{ brightness|d }}," + "{{ color_temp|d }}," + "{{ white_value|d }}," + "{{ red|d }}-" + "{{ green|d }}-" + "{{ blue|d }}", + "command_off_template": "off", + "effect_list": ["colorloop", "random"], + "optimistic": True, + "state_template": '{{ value.split(",")[0] }}', + "color_temp_template": '{{ value.split(",")[2] }}', + "white_value_template": '{{ value.split(",")[3] }}', + "red_template": '{{ value.split(",")[4].' 'split("-")[0] }}', + "green_template": '{{ value.split(",")[4].' 'split("-")[1] }}', + "blue_template": '{{ value.split(",")[4].' 'split("-")[2] }}', + "effect_template": '{{ value.split(",")[5] }}', + "qos": 2, + } + }, + ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_ON diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index 4b8b017f7fd575..f3512a72ee30fb 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -64,29 +64,31 @@ def set_callback(self, function): @patch("homeassistant.components.pilight._LOGGER.error") async def test_connection_failed_error(mock_error, hass): """Try to connect at 127.0.0.1:5001 with socket error.""" - with assert_setup_component(4): - with patch("pilight.pilight.Client", side_effect=socket.error) as mock_client: - assert not await async_setup_component( - hass, pilight.DOMAIN, {pilight.DOMAIN: {}} - ) - mock_client.assert_called_once_with( - host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT - ) - assert mock_error.call_count == 1 + with assert_setup_component(4), patch( + "pilight.pilight.Client", side_effect=socket.error + ) as mock_client: + assert not await async_setup_component( + hass, pilight.DOMAIN, {pilight.DOMAIN: {}} + ) + mock_client.assert_called_once_with( + host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT + ) + assert mock_error.call_count == 1 @patch("homeassistant.components.pilight._LOGGER.error") async def test_connection_timeout_error(mock_error, hass): """Try to connect at 127.0.0.1:5001 with socket timeout.""" - with assert_setup_component(4): - with patch("pilight.pilight.Client", side_effect=socket.timeout) as mock_client: - assert not await async_setup_component( - hass, pilight.DOMAIN, {pilight.DOMAIN: {}} - ) - mock_client.assert_called_once_with( - host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT - ) - assert mock_error.call_count == 1 + with assert_setup_component(4), patch( + "pilight.pilight.Client", side_effect=socket.timeout + ) as mock_client: + assert not await async_setup_component( + hass, pilight.DOMAIN, {pilight.DOMAIN: {}} + ) + mock_client.assert_called_once_with( + host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT + ) + assert mock_error.call_count == 1 @patch("pilight.pilight.Client", PilightDaemonSim) @@ -134,23 +136,22 @@ async def test_send_code(mock_pilight_error, hass): @patch("homeassistant.components.pilight._LOGGER.error") async def test_send_code_fail(mock_pilight_error, hass): """Check IOError exception error message.""" - with assert_setup_component(4): - with patch("pilight.pilight.Client.send_code", side_effect=IOError): - assert await async_setup_component( - hass, pilight.DOMAIN, {pilight.DOMAIN: {}} - ) + with assert_setup_component(4), patch( + "pilight.pilight.Client.send_code", side_effect=IOError + ): + assert await async_setup_component(hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) - # Call with protocol info, should not give error - service_data = {"protocol": "test", "value": 42} - await hass.services.async_call( - pilight.DOMAIN, - pilight.SERVICE_NAME, - service_data=service_data, - blocking=True, - ) - await hass.async_block_till_done() - error_log_call = mock_pilight_error.call_args_list[-1] - assert "Pilight send failed" in str(error_log_call) + # Call with protocol info, should not give error + service_data = {"protocol": "test", "value": 42} + await hass.services.async_call( + pilight.DOMAIN, + pilight.SERVICE_NAME, + service_data=service_data, + blocking=True, + ) + await hass.async_block_till_done() + error_log_call = mock_pilight_error.call_args_list[-1] + assert "Pilight send failed" in str(error_log_call) @patch("homeassistant.components.pilight._LOGGER.error") diff --git a/tests/components/signal_messenger/test_notify.py b/tests/components/signal_messenger/test_notify.py index 4bb6cbdd19715b..d2dc93fea2e01e 100644 --- a/tests/components/signal_messenger/test_notify.py +++ b/tests/components/signal_messenger/test_notify.py @@ -87,12 +87,11 @@ def test_send_message_should_show_deprecation_warning(self, mock): ) with self.assertLogs( "homeassistant.components.signal_messenger.notify", level="WARNING" - ) as context: - with tempfile.NamedTemporaryFile( - suffix=".png", prefix=os.path.basename(__file__) - ) as tf: - data = {"data": {"attachment": tf.name}} - self._signalmessenger.send_message(message, **data) + ) as context, tempfile.NamedTemporaryFile( + suffix=".png", prefix=os.path.basename(__file__) + ) as tf: + data = {"data": {"attachment": tf.name}} + self._signalmessenger.send_message(message, **data) self.assertIn( "The 'attachment' option is deprecated, please replace it with 'attachments'. This option will become invalid in version 0.108", context.output[0], @@ -117,12 +116,11 @@ def test_send_message_with_attachment(self, mock): ) with self.assertLogs( "homeassistant.components.signal_messenger.notify", level="DEBUG" - ) as context: - with tempfile.NamedTemporaryFile( - suffix=".png", prefix=os.path.basename(__file__) - ) as tf: - data = {"data": {"attachments": [tf.name]}} - self._signalmessenger.send_message(message, **data) + ) as context, tempfile.NamedTemporaryFile( + suffix=".png", prefix=os.path.basename(__file__) + ) as tf: + data = {"data": {"attachments": [tf.name]}} + self._signalmessenger.send_message(message, **data) self.assertIn("Sending signal message", context.output[0]) self.assertTrue(mock.called) self.assertEqual(mock.call_count, 2) diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 287e7139affdd2..ef54788d91078b 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -291,20 +291,19 @@ async def async_log_error_from_test_path(hass, path, sq): call_path = "internal_path.py" with patch.object( _LOGGER, "findCaller", MagicMock(return_value=(call_path, 0, None, None)) + ), patch( + "traceback.extract_stack", + MagicMock( + return_value=[ + get_frame("main_path/main.py"), + get_frame(path), + get_frame(call_path), + get_frame("venv_path/logging/log.py"), + ] + ), ): - with patch( - "traceback.extract_stack", - MagicMock( - return_value=[ - get_frame("main_path/main.py"), - get_frame(path), - get_frame(call_path), - get_frame("venv_path/logging/log.py"), - ] - ), - ): - _LOGGER.error("error message") - await _async_block_until_queue_empty(hass, sq) + _LOGGER.error("error message") + await _async_block_until_queue_empty(hass, sq) async def test_homeassistant_path(hass, simple_queue, hass_client): diff --git a/tests/components/zha/test_channels.py b/tests/components/zha/test_channels.py index 8a2ca1f05c3024..ec5128fdb5e112 100644 --- a/tests/components/zha/test_channels.py +++ b/tests/components/zha/test_channels.py @@ -415,10 +415,11 @@ async def test_ep_channels_configure(channel): claimed = {ch_1.id: ch_1, ch_2.id: ch_2, ch_3.id: ch_3} client_chans = {ch_4.id: ch_4, ch_5.id: ch_5} - with mock.patch.dict(ep_channels.claimed_channels, claimed, clear=True): - with mock.patch.dict(ep_channels.client_channels, client_chans, clear=True): - await ep_channels.async_configure() - await ep_channels.async_initialize(mock.sentinel.from_cache) + with mock.patch.dict( + ep_channels.claimed_channels, claimed, clear=True + ), mock.patch.dict(ep_channels.client_channels, client_chans, clear=True): + await ep_channels.async_configure() + await ep_channels.async_initialize(mock.sentinel.from_cache) for ch in [*claimed.values(), *client_chans.values()]: assert ch.async_initialize.call_count == 1 diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index c84c22e3251db2..cd0e75a72375de 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -212,17 +212,14 @@ def test_discover_by_device_type_override(): with mock.patch( "homeassistant.components.zha.core.registries.ZHA_ENTITIES.get_entity", get_entity_mock, - ): - with mock.patch.dict(disc.PROBE._device_configs, overrides, clear=True): - disc.PROBE.discover_by_device_type(ep_channels) - assert get_entity_mock.call_count == 1 - assert ep_channels.claim_channels.call_count == 1 - assert ep_channels.claim_channels.call_args[0][0] is mock.sentinel.claimed - assert ep_channels.async_new_entity.call_count == 1 - assert ep_channels.async_new_entity.call_args[0][0] == zha_const.SWITCH - assert ( - ep_channels.async_new_entity.call_args[0][1] == mock.sentinel.entity_cls - ) + ), mock.patch.dict(disc.PROBE._device_configs, overrides, clear=True): + disc.PROBE.discover_by_device_type(ep_channels) + assert get_entity_mock.call_count == 1 + assert ep_channels.claim_channels.call_count == 1 + assert ep_channels.claim_channels.call_args[0][0] is mock.sentinel.claimed + assert ep_channels.async_new_entity.call_count == 1 + assert ep_channels.async_new_entity.call_args[0][0] == zha_const.SWITCH + assert ep_channels.async_new_entity.call_args[0][1] == mock.sentinel.entity_cls def test_discover_probe_single_cluster(): diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index e857675c545dbc..6b1a6fe4f98cb5 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -202,20 +202,17 @@ async def sleep(duration, loop=None): sleeps.append(duration) await asyncio_sleep(0) - with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow): - with patch("asyncio.sleep", new=sleep): - with patch.object(zwave, "_LOGGER") as mock_logger: - hass.data[DATA_NETWORK].state = MockNetwork.STATE_STARTED + with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow), patch( + "asyncio.sleep", new=sleep + ), patch.object(zwave, "_LOGGER") as mock_logger: + hass.data[DATA_NETWORK].state = MockNetwork.STATE_STARTED - await hass.async_start() + await hass.async_start() - assert len(sleeps) == const.NETWORK_READY_WAIT_SECS - assert mock_logger.warning.called - assert len(mock_logger.warning.mock_calls) == 1 - assert ( - mock_logger.warning.mock_calls[0][1][1] - == const.NETWORK_READY_WAIT_SECS - ) + assert len(sleeps) == const.NETWORK_READY_WAIT_SECS + assert mock_logger.warning.called + assert len(mock_logger.warning.mock_calls) == 1 + assert mock_logger.warning.mock_calls[0][1][1] == const.NETWORK_READY_WAIT_SECS async def test_device_entity(hass, mock_openzwave): @@ -341,19 +338,19 @@ async def sleep(duration, loop=None): sleeps.append(duration) await asyncio_sleep(0) - with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow): - with patch("asyncio.sleep", new=sleep): - with patch.object(zwave, "_LOGGER") as mock_logger: - await hass.async_add_executor_job(mock_receivers[0], node) - await hass.async_block_till_done() - - assert len(sleeps) == const.NODE_READY_WAIT_SECS - assert mock_logger.warning.called - assert len(mock_logger.warning.mock_calls) == 1 - assert mock_logger.warning.mock_calls[0][1][1:] == ( - 14, - const.NODE_READY_WAIT_SECS, - ) + with patch("homeassistant.components.zwave.dt_util.utcnow", new=utcnow), patch( + "asyncio.sleep", new=sleep + ), patch.object(zwave, "_LOGGER") as mock_logger: + await hass.async_add_executor_job(mock_receivers[0], node) + await hass.async_block_till_done() + + assert len(sleeps) == const.NODE_READY_WAIT_SECS + assert mock_logger.warning.called + assert len(mock_logger.warning.mock_calls) == 1 + assert mock_logger.warning.mock_calls[0][1][1:] == ( + 14, + const.NODE_READY_WAIT_SECS, + ) assert hass.states.get("zwave.unknown_node_14").state == "unknown" diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 3fad758b34b171..b5257e635af1c4 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -99,9 +99,10 @@ def logger(self) -> logging.Logger: """Return logger.""" return logging.getLogger(__name__) - with patch.dict(config_entries.HANDLERS, {TEST_DOMAIN: TestFlowHandler}): - with pytest.raises(TypeError): - TestFlowHandler() + with patch.dict( + config_entries.HANDLERS, {TEST_DOMAIN: TestFlowHandler} + ), pytest.raises(TypeError): + TestFlowHandler() async def test_abort_if_no_implementation(hass, flow_handler): diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index aad37e2fd49c89..05c72f10db568f 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -377,9 +377,8 @@ async def test_get_cloud_url(hass: HomeAssistant): hass.components.cloud, "async_remote_ui_url", side_effect=cloud.CloudNotAvailable, - ): - with pytest.raises(NoURLAvailableError): - _get_cloud_url(hass) + ), pytest.raises(NoURLAvailableError): + _get_cloud_url(hass) async def test_get_external_url_cloud_fallback(hass: HomeAssistant): diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 7a084fed9dd6b0..d168c8b9cfc1e9 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -561,22 +561,21 @@ async def test_call_context_target_specific_no_auth( hass, mock_handle_entity_call, mock_entities ): """Check targeting specific entities without auth.""" - with pytest.raises(exceptions.Unauthorized) as err: - with patch( - "homeassistant.auth.AuthManager.async_get_user", - return_value=Mock(permissions=PolicyPermissions({}, None)), - ): - await service.entity_service_call( - hass, - [Mock(entities=mock_entities)], - Mock(), - ha.ServiceCall( - "test_domain", - "test_service", - {"entity_id": "light.kitchen"}, - context=ha.Context(user_id="mock-id"), - ), - ) + with pytest.raises(exceptions.Unauthorized) as err, patch( + "homeassistant.auth.AuthManager.async_get_user", + return_value=Mock(permissions=PolicyPermissions({}, None)), + ): + await service.entity_service_call( + hass, + [Mock(entities=mock_entities)], + Mock(), + ha.ServiceCall( + "test_domain", + "test_service", + {"entity_id": "light.kitchen"}, + context=ha.Context(user_id="mock-id"), + ), + ) assert err.value.context.user_id == "mock-id" assert err.value.entity_id == "light.kitchen" diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index fbcc9d1bf149b7..b12601220c30fd 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1221,12 +1221,11 @@ async def test_init_custom_integration(hass): None, {"name": "Hue", "dependencies": [], "requirements": [], "domain": "hue"}, ) - with pytest.raises(data_entry_flow.UnknownHandler): - with patch( - "homeassistant.loader.async_get_integration", - return_value=integration, - ): - await hass.config_entries.flow.async_init("bla") + with pytest.raises(data_entry_flow.UnknownHandler), patch( + "homeassistant.loader.async_get_integration", + return_value=integration, + ): + await hass.config_entries.flow.async_init("bla") async def test_support_entry_unload(hass): diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 5f74e504de826d..2c5b529467d2fe 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -84,9 +84,8 @@ async def test_install_missing_package(hass): """Test an install attempt on an existing package.""" with patch( "homeassistant.util.package.install_package", return_value=False - ) as mock_inst: - with pytest.raises(RequirementsNotFound): - await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) + ) as mock_inst, pytest.raises(RequirementsNotFound): + await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) assert len(mock_inst.mock_calls) == 1 diff --git a/tests/util/test_async.py b/tests/util/test_async.py index d5564a90d0e817..19413c57aaa882 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -206,8 +206,9 @@ async def test_callback_is_always_scheduled(hass): callback = MagicMock() hasync.shutdown_run_callback_threadsafe(hass.loop) - with patch.object(hass.loop, "call_soon_threadsafe") as mock_call_soon_threadsafe: - with pytest.raises(RuntimeError): - hasync.run_callback_threadsafe(hass.loop, callback) + with patch.object( + hass.loop, "call_soon_threadsafe" + ) as mock_call_soon_threadsafe, pytest.raises(RuntimeError): + hasync.run_callback_threadsafe(hass.loop, callback) mock_call_soon_threadsafe.assert_called_once() diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index daa0275b7aaebf..2b86b3c50e9c5e 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -65,9 +65,8 @@ def test_environment_variable_default(): def test_invalid_environment_variable(): """Test config file with no environment variable sat.""" conf = "password: !env_var PASSWORD" - with pytest.raises(HomeAssistantError): - with io.StringIO(conf) as file: - yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) + with pytest.raises(HomeAssistantError), io.StringIO(conf) as file: + yaml_loader.yaml.load(file, Loader=yaml_loader.SafeLineLoader) def test_include_yaml(): From 45f77ccccf5a22c1722680f05fac0ee96dd7f87b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 09:23:32 +0100 Subject: [PATCH 1536/1818] Merge of nested IF-IF cases - Core (#48364) --- .../auth/mfa_modules/insecure_example.py | 16 +++++----------- homeassistant/helpers/area_registry.py | 7 ++----- homeassistant/setup.py | 9 +++++---- homeassistant/util/color.py | 9 ++++----- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index c25f70ca31b4be..1d40339417bd37 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -77,17 +77,11 @@ async def async_depose_user(self, user_id: str) -> None: async def async_is_user_setup(self, user_id: str) -> bool: """Return whether user is setup.""" - for data in self._data: - if data["user_id"] == user_id: - return True - return False + return any(data["user_id"] == user_id for data in self._data) async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool: """Return True if validation passed.""" - for data in self._data: - if data["user_id"] == user_id: - # user_input has been validate in caller - if data["pin"] == user_input["pin"]: - return True - - return False + return any( + data["user_id"] == user_id and data["pin"] == user_input["pin"] + for data in self._data + ) diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index c181fadcfd3504..aa9d3a40b9a912 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -134,11 +134,8 @@ def _async_update(self, area_id: str, name: str) -> AreaEntry: normalized_name = normalize_area_name(name) - if normalized_name != old.normalized_name: - if self.async_get_area_by_name(name): - raise ValueError( - f"The name {name} ({normalized_name}) is already in use" - ) + if normalized_name != old.normalized_name and self.async_get_area_by_name(name): + raise ValueError(f"The name {name} ({normalized_name}) is already in use") changes["name"] = name changes["normalized_name"] = normalized_name diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 4c5e10a254bf52..c22e660e553b2a 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -315,10 +315,11 @@ def log_error(msg: str) -> None: log_error(f"Unable to import the component ({exc}).") return None - if hasattr(component, "setup") or hasattr(component, "async_setup"): - if not await async_setup_component(hass, integration.domain, hass_config): - log_error("Unable to set up component.") - return None + if ( + hasattr(component, "setup") or hasattr(component, "async_setup") + ) and not await async_setup_component(hass, integration.domain, hass_config): + log_error("Unable to set up component.") + return None return platform diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index f41461aada5b57..2a34fe82c5933d 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -261,11 +261,10 @@ def color_xy_brightness_to_RGB( vX: float, vY: float, ibrightness: int, Gamut: GamutType | None = None ) -> tuple[int, int, int]: """Convert from XYZ to RGB.""" - if Gamut: - if not check_point_in_lamps_reach((vX, vY), Gamut): - xy_closest = get_closest_point_to_point((vX, vY), Gamut) - vX = xy_closest[0] - vY = xy_closest[1] + if Gamut and not check_point_in_lamps_reach((vX, vY), Gamut): + xy_closest = get_closest_point_to_point((vX, vY), Gamut) + vX = xy_closest[0] + vY = xy_closest[1] brightness = ibrightness / 255.0 if brightness == 0.0: From 9737480742b7f91c89bc4a2879f38557766d866e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Mar 2021 22:32:30 -1000 Subject: [PATCH 1537/1818] Lazy load broadlink storage (#48391) With many broadlink devices, the storage load overwhelmed the executor at startup. Delay loading storage until it is needed. --- homeassistant/components/broadlink/remote.py | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 30043f487b125c..dff7ba6b2fdb41 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -34,7 +34,6 @@ ) from homeassistant.const import CONF_HOST, STATE_OFF from homeassistant.core import callback -from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.storage import Store @@ -110,12 +109,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Store(hass, CODE_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_codes"), Store(hass, FLAG_STORAGE_VERSION, f"broadlink_remote_{device.unique_id}_flags"), ) - - loaded = await remote.async_load_storage_files() - if not loaded: - _LOGGER.error("Failed to create '%s Remote' entity: Storage error", device.name) - return - async_add_entities([remote], False) @@ -128,6 +121,7 @@ def __init__(self, device, codes, flags): self._coordinator = device.update_manager.coordinator self._code_storage = codes self._flag_storage = flags + self._storage_loaded = False self._codes = {} self._flags = defaultdict(int) self._state = True @@ -214,12 +208,12 @@ def _extract_codes(self, commands, device=None): return code_list @callback - def get_codes(self): + def _get_codes(self): """Return a dictionary of codes.""" return self._codes @callback - def get_flags(self): + def _get_flags(self): """Return a dictionary of toggle flags. A toggle flag indicates whether the remote should send an @@ -250,16 +244,13 @@ async def async_turn_off(self, **kwargs): self._state = False self.async_write_ha_state() - async def async_load_storage_files(self): - """Load codes and toggle flags from storage files.""" - try: - self._codes.update(await self._code_storage.async_load() or {}) - self._flags.update(await self._flag_storage.async_load() or {}) - - except HomeAssistantError: - return False - - return True + async def _async_load_storage(self): + """Load code and flag storage from disk.""" + # Exception is intentionally not trapped to + # provide feedback if something fails. + self._codes.update(await self._code_storage.async_load() or {}) + self._flags.update(await self._flag_storage.async_load() or {}) + self._storage_loaded = True async def async_send_command(self, command, **kwargs): """Send a list of commands to a device.""" @@ -277,6 +268,9 @@ async def async_send_command(self, command, **kwargs): ) return + if not self._storage_loaded: + await self._async_load_storage() + try: code_list = self._extract_codes(commands, device) except ValueError as err: @@ -312,7 +306,7 @@ async def async_send_command(self, command, **kwargs): at_least_one_sent = True if at_least_one_sent: - self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) + self._flag_storage.async_delay_save(self._get_flags, FLAG_SAVE_DELAY) async def async_learn_command(self, **kwargs): """Learn a list of commands from a remote.""" @@ -329,6 +323,9 @@ async def async_learn_command(self, **kwargs): ) return + if not self._storage_loaded: + await self._async_load_storage() + async with self._lock: if command_type == COMMAND_TYPE_IR: learn_command = self._async_learn_ir_command @@ -486,6 +483,9 @@ async def async_delete_command(self, **kwargs): ) return + if not self._storage_loaded: + await self._async_load_storage() + try: codes = self._codes[device] except KeyError as err: @@ -516,6 +516,6 @@ async def async_delete_command(self, **kwargs): if not codes: del self._codes[device] if self._flags.pop(device, None) is not None: - self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY) + self._flag_storage.async_delay_save(self._get_flags, FLAG_SAVE_DELAY) - self._code_storage.async_delay_save(self.get_codes, CODE_SAVE_DELAY) + self._code_storage.async_delay_save(self._get_codes, CODE_SAVE_DELAY) From 86212db71dea7810f5b81726f11db3aa398064f1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:03:15 +0100 Subject: [PATCH 1538/1818] Merge of nested IF-IF cases - K-N (#48370) --- .../components/keyboard_remote/__init__.py | 10 +++-- homeassistant/components/mailgun/__init__.py | 13 ++++--- homeassistant/components/met/weather.py | 15 ++++---- homeassistant/components/mikrotik/hub.py | 5 +-- homeassistant/components/min_max/sensor.py | 38 +++++++++++-------- .../components/minecraft_server/sensor.py | 5 +-- homeassistant/components/mjpeg/camera.py | 9 +++-- homeassistant/components/mobile_app/notify.py | 10 +++-- .../components/mqtt/light/schema_basic.py | 5 +-- .../components/mqtt_eventstream/__init__.py | 14 +++---- homeassistant/components/nest/climate_sdm.py | 8 ++-- homeassistant/components/nexia/climate.py | 19 +++++++--- 12 files changed, 86 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index 7d9bcf621e5e0c..2ada56e1c44445 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -312,10 +312,12 @@ async def async_monitor_input(self, dev): self.emulate_key_hold_repeat, ) ) - elif event.value == KEY_VALUE["key_up"]: - if event.code in repeat_tasks: - repeat_tasks[event.code].cancel() - del repeat_tasks[event.code] + elif ( + event.value == KEY_VALUE["key_up"] + and event.code in repeat_tasks + ): + repeat_tasks[event.code].cancel() + del repeat_tasks[event.code] except (OSError, PermissionError, asyncio.CancelledError): # cancel key repeat tasks for task in repeat_tasks.values(): diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index 220b6a1abc1f1c..39ee6e4635088a 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -51,11 +51,14 @@ async def handle_webhook(hass, webhook_id, request): except ValueError: return None - if isinstance(data, dict) and "signature" in data: - if await verify_webhook(hass, **data["signature"]): - data["webhook_id"] = webhook_id - hass.bus.async_fire(MESSAGE_RECEIVED, data) - return + if ( + isinstance(data, dict) + and "signature" in data + and await verify_webhook(hass, **data["signature"]) + ): + data["webhook_id"] = webhook_id + hass.bus.async_fire(MESSAGE_RECEIVED, data) + return _LOGGER.warning( "Mailgun webhook received an unauthenticated message - webhook_id: %s", diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index c0c8c11c6447d8..4657da9e5d48a2 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -230,14 +230,13 @@ def forecast(self): for k, v in FORECAST_MAP.items() if met_item.get(v) is not None } - if not self._is_metric: - if ATTR_FORECAST_PRECIPITATION in ha_item: - precip_inches = convert_distance( - ha_item[ATTR_FORECAST_PRECIPITATION], - LENGTH_MILLIMETERS, - LENGTH_INCHES, - ) - ha_item[ATTR_FORECAST_PRECIPITATION] = round(precip_inches, 2) + if not self._is_metric and ATTR_FORECAST_PRECIPITATION in ha_item: + precip_inches = convert_distance( + ha_item[ATTR_FORECAST_PRECIPITATION], + LENGTH_MILLIMETERS, + LENGTH_INCHES, + ) + ha_item[ATTR_FORECAST_PRECIPITATION] = round(precip_inches, 2) if ha_item.get(ATTR_FORECAST_CONDITION): ha_item[ATTR_FORECAST_CONDITION] = format_condition( ha_item[ATTR_FORECAST_CONDITION] diff --git a/homeassistant/components/mikrotik/hub.py b/homeassistant/components/mikrotik/hub.py index 28a78d0ee1a448..2f1f89ba60da34 100644 --- a/homeassistant/components/mikrotik/hub.py +++ b/homeassistant/components/mikrotik/hub.py @@ -276,9 +276,8 @@ def command(self, cmd, params=None): def update(self): """Update device_tracker from Mikrotik API.""" - if not self.available or not self.api: - if not self.connect_to_hub(): - return + if (not self.available or not self.api) and not self.connect_to_hub(): + return _LOGGER.debug("updating network devices for host: %s", self._host) self.update_devices() diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index a42291b3f67621..d103ff8eaa68ee 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -84,9 +84,10 @@ def calc_min(sensor_values): val = None entity_id = None for sensor_id, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - if val is None or val > sensor_value: - entity_id, val = sensor_id, sensor_value + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and ( + val is None or val > sensor_value + ): + entity_id, val = sensor_id, sensor_value return entity_id, val @@ -95,30 +96,35 @@ def calc_max(sensor_values): val = None entity_id = None for sensor_id, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - if val is None or val < sensor_value: - entity_id, val = sensor_id, sensor_value + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and ( + val is None or val < sensor_value + ): + entity_id, val = sensor_id, sensor_value return entity_id, val def calc_mean(sensor_values, round_digits): """Calculate mean value, honoring unknown states.""" - result = [] - for _, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - result.append(sensor_value) - if len(result) == 0: + result = [ + sensor_value + for _, sensor_value in sensor_values + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ] + + if not result: return None return round(sum(result) / len(result), round_digits) def calc_median(sensor_values, round_digits): """Calculate median value, honoring unknown states.""" - result = [] - for _, sensor_value in sensor_values: - if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE]: - result.append(sensor_value) - if len(result) == 0: + result = [ + sensor_value + for _, sensor_value in sensor_values + if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + ] + + if not result: return None result.sort() if len(result) % 2 == 0: diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index ded21f2935fb5a..3d77d9e27727ff 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -147,9 +147,8 @@ async def async_update(self) -> None: extra_state_attributes = None players_list = self._server.players_list - if players_list is not None: - if len(players_list) != 0: - extra_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} + if players_list is not None and len(players_list) != 0: + extra_state_attributes = {ATTR_PLAYERS_LIST: self._server.players_list} self._extra_state_attributes = extra_state_attributes diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index 15f7f5c80bfc07..d5008d1778c4a7 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -98,9 +98,12 @@ def __init__(self, device_info): self._still_image_url = device_info.get(CONF_STILL_IMAGE_URL) self._auth = None - if self._username and self._password: - if self._authentication == HTTP_BASIC_AUTHENTICATION: - self._auth = aiohttp.BasicAuth(self._username, password=self._password) + if ( + self._username + and self._password + and self._authentication == HTTP_BASIC_AUTHENTICATION + ): + self._auth = aiohttp.BasicAuth(self._username, password=self._password) self._verify_ssl = device_info.get(CONF_VERIFY_SSL) async def async_camera_image(self): diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py index 46a34fa7a850ba..763186df998b90 100644 --- a/homeassistant/components/mobile_app/notify.py +++ b/homeassistant/components/mobile_app/notify.py @@ -105,10 +105,12 @@ async def async_send_message(self, message="", **kwargs): """Send a message to the Lambda APNS gateway.""" data = {ATTR_MESSAGE: message} - if kwargs.get(ATTR_TITLE) is not None: - # Remove default title from notifications. - if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT: - data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) + # Remove default title from notifications. + if ( + kwargs.get(ATTR_TITLE) is not None + and kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT + ): + data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) targets = kwargs.get(ATTR_TARGET) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index e2443ec1df6585..9c4b0f3a3e3a3a 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -600,9 +600,8 @@ async def async_turn_on(self, **kwargs): # If brightness is being used instead of an on command, make sure # there is a brightness input. Either set the brightness to our # saved value or the maximum value if this is the first call - elif on_command_type == "brightness": - if ATTR_BRIGHTNESS not in kwargs: - kwargs[ATTR_BRIGHTNESS] = self._brightness if self._brightness else 255 + elif on_command_type == "brightness" and ATTR_BRIGHTNESS not in kwargs: + kwargs[ATTR_BRIGHTNESS] = self._brightness if self._brightness else 255 if ATTR_HS_COLOR in kwargs and self._topic[CONF_RGB_COMMAND_TOPIC] is not None: diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py index 5a5f3b3c74d560..328b9395eeac3f 100644 --- a/homeassistant/components/mqtt_eventstream/__init__.py +++ b/homeassistant/components/mqtt_eventstream/__init__.py @@ -60,13 +60,13 @@ def _event_publisher(event): # Filter out the events that were triggered by publishing # to the MQTT topic, or you will end up in an infinite loop. - if event.event_type == EVENT_CALL_SERVICE: - if ( - event.data.get("domain") == mqtt.DOMAIN - and event.data.get("service") == mqtt.SERVICE_PUBLISH - and event.data[ATTR_SERVICE_DATA].get("topic") == pub_topic - ): - return + if ( + event.event_type == EVENT_CALL_SERVICE + and event.data.get("domain") == mqtt.DOMAIN + and event.data.get("service") == mqtt.SERVICE_PUBLISH + and event.data[ATTR_SERVICE_DATA].get("topic") == pub_topic + ): + return event_info = {"event_type": event.event_type, "event_data": event.data} msg = json.dumps(event_info, cls=JSONEncoder) diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index 169f9d23957b61..e02ebcd2dee587 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -180,9 +180,11 @@ def target_temperature_low(self): @property def _target_temperature_trait(self): """Return the correct trait with a target temp depending on mode.""" - if self.preset_mode == PRESET_ECO: - if ThermostatEcoTrait.NAME in self._device.traits: - return self._device.traits[ThermostatEcoTrait.NAME] + if ( + self.preset_mode == PRESET_ECO + and ThermostatEcoTrait.NAME in self._device.traits + ): + return self._device.traits[ThermostatEcoTrait.NAME] if ThermostatTemperatureSetpointTrait.NAME in self._device.traits: return self._device.traits[ThermostatTemperatureSetpointTrait.NAME] return None diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 4084f4d297c968..aff3711cdaef6f 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -334,12 +334,19 @@ def set_temperature(self, **kwargs): new_cool_temp = min_temp + deadband # Check that we're within the deadband range, fix it if we're not - if new_heat_temp and new_heat_temp != cur_heat_temp: - if new_cool_temp - new_heat_temp < deadband: - new_cool_temp = new_heat_temp + deadband - if new_cool_temp and new_cool_temp != cur_cool_temp: - if new_cool_temp - new_heat_temp < deadband: - new_heat_temp = new_cool_temp - deadband + if ( + new_heat_temp + and new_heat_temp != cur_heat_temp + and new_cool_temp - new_heat_temp < deadband + ): + new_cool_temp = new_heat_temp + deadband + + if ( + new_cool_temp + and new_cool_temp != cur_cool_temp + and new_cool_temp - new_heat_temp < deadband + ): + new_heat_temp = new_cool_temp - deadband self._zone.set_heat_cool_temp( heat_temperature=new_heat_temp, From 3cd52b695d9e21981f8d2db2269b586623f8ba40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 27 Mar 2021 11:22:11 +0200 Subject: [PATCH 1539/1818] Upgrade flake8 and dependencies, enable flake8-noqa (#48393) * Upgrade flake8 to 3.9.0 https://flake8.pycqa.org/en/latest/release-notes/3.9.0.html * Upgrade pydocstyle to 6.0.0 https://www.pydocstyle.org/en/latest/release_notes.html#september-13th-2020 https://www.pydocstyle.org/en/latest/release_notes.html#march-18th-2021 * Upgrade flake8-docstrings to 1.6.0, enable flake8-noqa https://gitlab.com/pycqa/flake8-docstrings/-/blob/1.6.0/HISTORY.rst https://github.com/plinss/flake8-noqa/issues/1 * Upgrade/pin pyflakes to 2.3.1 https://github.com/PyCQA/pyflakes/blob/2.3.1/NEWS.rst * Pin pycodestyle to 2.7.0 --- .pre-commit-config.yaml | 12 ++++++------ homeassistant/components/http/web_runner.py | 4 ++-- requirements_test_pre_commit.txt | 9 ++++++--- tests/components/seventeentrack/test_sensor.py | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 254ed637d81599..2f4aea74ae9c3c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,16 +23,16 @@ repos: exclude_types: [csv, json] exclude: ^tests/fixtures/ - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 additional_dependencies: - - flake8-docstrings==1.5.0 - # Temporarily every now and then for noqa cleanup; not done by - # default yet due to https://github.com/plinss/flake8-noqa/issues/1 - # - flake8-noqa==1.1.0 - - pydocstyle==5.1.1 + - pycodestyle==2.7.0 + - pyflakes==2.3.1 + - flake8-docstrings==1.6.0 + - pydocstyle==6.0.0 - flake8-comprehensions==3.4.0 + - flake8-noqa==1.1.0 files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/bandit rev: 1.7.0 diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index 87468d40954f16..f3dd59bf9d78b5 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -23,7 +23,7 @@ class HomeAssistantTCPSite(web.BaseSite): __slots__ = ("_host", "_port", "_reuse_address", "_reuse_port", "_hosturl") - def __init__( + def __init__( # noqa: D107 self, runner: web.BaseRunner, host: None | str | list[str], @@ -34,7 +34,7 @@ def __init__( backlog: int = 128, reuse_address: bool | None = None, reuse_port: bool | None = None, - ) -> None: # noqa: D107 + ) -> None: super().__init__( runner, shutdown_timeout=shutdown_timeout, diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 6a5768d34a11e3..06e87a5c51c516 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,9 +4,12 @@ bandit==1.7.0 black==20.8b1 codespell==2.0.0 flake8-comprehensions==3.4.0 -flake8-docstrings==1.5.0 -flake8==3.8.4 +flake8-docstrings==1.6.0 +flake8-noqa==1.1.0 +flake8==3.9.0 isort==5.7.0 -pydocstyle==5.1.1 +pycodestyle==2.7.0 +pydocstyle==6.0.0 +pyflakes==2.3.1 pyupgrade==2.11.0 yamllint==1.24.2 diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index d40d2cd499b16d..5ad904530b9d3b 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -106,7 +106,7 @@ async def packages( show_archived: bool = False, tz: str = "UTC", ) -> list: - """Packages mock.""" + """Packages mock.""" # noqa: D401 return self.__class__.package_list[:] async def summary(self, show_archived: bool = False) -> dict: From 3aed84560fe92b66d511d585fafefa2b49d2c77f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:38:57 +0100 Subject: [PATCH 1540/1818] Merge of nested IF-IF cases - O-R (#48371) --- homeassistant/components/obihai/sensor.py | 5 ++-- .../components/octoprint/__init__.py | 15 +++++------ homeassistant/components/octoprint/sensor.py | 23 ++++++++--------- homeassistant/components/onewire/sensor.py | 5 ++-- homeassistant/components/person/__init__.py | 19 +++++++------- .../components/philips_js/media_player.py | 5 ++-- homeassistant/components/philips_js/remote.py | 7 +++--- homeassistant/components/plaato/sensor.py | 8 +++--- homeassistant/components/plex/server.py | 25 +++++++++++-------- homeassistant/components/plugwise/gateway.py | 5 ++-- .../components/rainforest_eagle/sensor.py | 16 +++++++----- homeassistant/components/recorder/__init__.py | 6 +---- .../ruckus_unleashed/device_tracker.py | 17 +++++++------ 13 files changed, 77 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index 9aacaa84193f92..639b9eb332f9ce 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -146,9 +146,8 @@ def update(self): services = self._pyobihai.get_line_state() - if services is not None: - if self._service_name in services: - self._state = services.get(self._service_name) + if services is not None and self._service_name in services: + self._state = services.get(self._service_name) call_direction = self._pyobihai.get_call_direction() diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 66b804927c7740..918f0258f782ef 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -212,14 +212,12 @@ def get(self, endpoint): now = time.time() if endpoint == "job": last_time = self.job_last_reading[1] - if last_time is not None: - if now - last_time < 30.0: - return self.job_last_reading[0] + if last_time is not None and now - last_time < 30.0: + return self.job_last_reading[0] elif endpoint == "printer": last_time = self.printer_last_reading[1] - if last_time is not None: - if now - last_time < 30.0: - return self.printer_last_reading[0] + if last_time is not None and now - last_time < 30.0: + return self.printer_last_reading[0] url = self.api_url + endpoint try: @@ -300,8 +298,7 @@ def get_value_from_json(json_dict, sensor_type, group, tool): return json_dict[group][sensor_type] - if tool is not None: - if sensor_type in json_dict[group][tool]: - return json_dict[group][tool][sensor_type] + if tool is not None and sensor_type in json_dict[group][tool]: + return json_dict[group][tool][sensor_type] return None diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index f2c5c56c58aaff..16f6efce00447e 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -25,18 +25,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): octoprint_api = hass.data[COMPONENT_DOMAIN][base_url] tools = octoprint_api.get_tools() - if "Temperatures" in monitored_conditions: - if not tools: - hass.components.persistent_notification.create( - "Your printer appears to be offline.
" - "If you do not want to have your printer on
" - " at all times, and you would like to monitor
" - "temperatures, please add
" - "bed and/or number_of_tools to your configuration
" - "and restart.", - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) + if "Temperatures" in monitored_conditions and not tools: + hass.components.persistent_notification.create( + "Your printer appears to be offline.
" + "If you do not want to have your printer on
" + " at all times, and you would like to monitor
" + "temperatures, please add
" + "bed and/or number_of_tools to your configuration
" + "and restart.", + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID, + ) devices = [] types = ["actual", "target"] diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 3ea29a904dbd38..02af7a89ae3fad 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -266,9 +266,8 @@ def get_entities(onewirehub: OneWireHub, config): """Get a list of entities.""" entities = [] device_names = {} - if CONF_NAMES in config: - if isinstance(config[CONF_NAMES], dict): - device_names = config[CONF_NAMES] + if CONF_NAMES in config and isinstance(config[CONF_NAMES], dict): + device_names = config[CONF_NAMES] conf_type = config[CONF_TYPE] # We have an owserver on a remote(or local) host/port diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 7774694ff4e2cc..1eb9d4eda7a401 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -267,16 +267,15 @@ async def filter_yaml_data(hass: HomeAssistantType, persons: list[dict]) -> list for person_conf in persons: user_id = person_conf.get(CONF_USER_ID) - if user_id is not None: - if await hass.auth.async_get_user(user_id) is None: - _LOGGER.error( - "Invalid user_id detected for person %s", - person_conf[collection.CONF_ID], - ) - person_invalid_user.append( - f"- Person {person_conf[CONF_NAME]} (id: {person_conf[collection.CONF_ID]}) points at invalid user {user_id}" - ) - continue + if user_id is not None and await hass.auth.async_get_user(user_id) is None: + _LOGGER.error( + "Invalid user_id detected for person %s", + person_conf[collection.CONF_ID], + ) + person_invalid_user.append( + f"- Person {person_conf[CONF_NAME]} (id: {person_conf[collection.CONF_ID]}) points at invalid user {user_id}" + ) + continue filtered.append(person_conf) diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 83adf61ed1e4c3..7376d34e308af0 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -170,9 +170,8 @@ def supported_features(self): @property def state(self): """Get the device state. An exception means OFF state.""" - if self._tv.on: - if self._tv.powerstate == "On" or self._tv.powerstate is None: - return STATE_ON + if self._tv.on and (self._tv.powerstate == "On" or self._tv.powerstate is None): + return STATE_ON return STATE_OFF @property diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index 30b46499e2865f..f4d34904f1b532 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -52,10 +52,9 @@ def name(self): @property def is_on(self): """Return true if device is on.""" - if self._tv.on: - if self._tv.powerstate == "On" or self._tv.powerstate is None: - return True - return False + return bool( + self._tv.on and (self._tv.powerstate == "On" or self._tv.powerstate is None) + ) @property def should_poll(self): diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index 0812a2fd585ae6..9af16a1cacd08d 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -65,9 +65,11 @@ class PlaatoSensor(PlaatoEntity, SensorEntity): @property def device_class(self) -> str | None: """Return the class of this device, from component DEVICE_CLASSES.""" - if self._coordinator is not None: - if self._sensor_type == PlaatoKeg.Pins.TEMPERATURE: - return DEVICE_CLASS_TEMPERATURE + if ( + self._coordinator is not None + and self._sensor_type == PlaatoKeg.Pins.TEMPERATURE + ): + return DEVICE_CLASS_TEMPERATURE if self._sensor_type == ATTR_TEMP: return DEVICE_CLASS_TEMPERATURE return None diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 841a9e7cc0da9f..d4bd4b09ef26bc 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -365,17 +365,20 @@ def process_device(source, device): PLAYER_SOURCE, source ) - if device.machineIdentifier not in ignored_clients: - if self.option_ignore_plexweb_clients and device.product == "Plex Web": - ignored_clients.add(device.machineIdentifier) - if device.machineIdentifier not in self._known_clients: - _LOGGER.debug( - "Ignoring %s %s: %s", - "Plex Web", - source, - device.machineIdentifier, - ) - return + if ( + device.machineIdentifier not in ignored_clients + and self.option_ignore_plexweb_clients + and device.product == "Plex Web" + ): + ignored_clients.add(device.machineIdentifier) + if device.machineIdentifier not in self._known_clients: + _LOGGER.debug( + "Ignoring %s %s: %s", + "Plex Web", + source, + device.machineIdentifier, + ) + return if device.machineIdentifier not in ( self._created_clients | ignored_clients | new_clients diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 4e0e31810cb131..3f8bc7ea626aa8 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -110,9 +110,8 @@ async def async_update_data(): api.get_all_devices() - if entry.unique_id is None: - if api.smile_version[0] != "1.8.0": - hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname) + 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) diff --git a/homeassistant/components/rainforest_eagle/sensor.py b/homeassistant/components/rainforest_eagle/sensor.py index 80475b4c21b6df..d333f9437f19ea 100644 --- a/homeassistant/components/rainforest_eagle/sensor.py +++ b/homeassistant/components/rainforest_eagle/sensor.py @@ -55,14 +55,18 @@ def hwtest(cloud_id, install_code, ip_address): response = reader.get_network_info() # Branch to test if target is Legacy Model - if "NetworkInfo" in response: - if response["NetworkInfo"].get("ModelId", None) == "Z109-EAGLE": - return reader + if ( + "NetworkInfo" in response + and response["NetworkInfo"].get("ModelId", None) == "Z109-EAGLE" + ): + return reader # Branch to test if target is Eagle-200 Model - if "Response" in response: - if response["Response"].get("Command", None) == "get_network_info": - return EagleReader(ip_address, cloud_id, install_code) + if ( + "Response" in response + and response["Response"].get("Command", None) == "get_network_info" + ): + return EagleReader(ip_address, cloud_id, install_code) # Catch-all if hardware ID tests fail raise ValueError("Couldn't determine device model.") diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index cff8119356f0bc..f93d965a4b9d05 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -304,11 +304,7 @@ def _async_event_filter(self, event): return False entity_id = event.data.get(ATTR_ENTITY_ID) - if entity_id is not None: - if not self.entity_filter(entity_id): - return False - - return True + return bool(entity_id is None or self.entity_filter(entity_id)) def do_adhoc_purge(self, **kwargs): """Trigger an adhoc purge retaining keep_days worth of data.""" diff --git a/homeassistant/components/ruckus_unleashed/device_tracker.py b/homeassistant/components/ruckus_unleashed/device_tracker.py index 140aa3a8692b38..90a848b663b3ac 100644 --- a/homeassistant/components/ruckus_unleashed/device_tracker.py +++ b/homeassistant/components/ruckus_unleashed/device_tracker.py @@ -67,14 +67,17 @@ def restore_entities(registry, coordinator, entry, async_add_entities, tracked): missing = [] for entity in registry.entities.values(): - if entity.config_entry_id == entry.entry_id and entity.platform == DOMAIN: - if entity.unique_id not in coordinator.data[API_CLIENTS]: - missing.append( - RuckusUnleashedDevice( - coordinator, entity.unique_id, entity.original_name - ) + if ( + entity.config_entry_id == entry.entry_id + and entity.platform == DOMAIN + and entity.unique_id not in coordinator.data[API_CLIENTS] + ): + missing.append( + RuckusUnleashedDevice( + coordinator, entity.unique_id, entity.original_name ) - tracked.add(entity.unique_id) + ) + tracked.add(entity.unique_id) if missing: async_add_entities(missing) From 8d5ce5309875dad0be1ca53cf795e15681a397d3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:54:59 +0100 Subject: [PATCH 1541/1818] Merge of nested IF-IF cases - S-W (#48372) --- homeassistant/components/saj/sensor.py | 24 ++++++------- homeassistant/components/smhi/config_flow.py | 34 +++++++++--------- homeassistant/components/stream/hls.py | 10 +++--- .../components/subaru/config_flow.py | 29 ++++++++------- homeassistant/components/subaru/sensor.py | 22 +++++++----- .../components/systemmonitor/sensor.py | 35 +++++++------------ .../components/tado/device_tracker.py | 9 +++-- homeassistant/components/tplink/light.py | 12 +++---- .../components/unifi/device_tracker.py | 11 +++--- homeassistant/components/unifi/switch.py | 9 ++--- homeassistant/components/uvc/camera.py | 13 +++---- homeassistant/components/wilight/fan.py | 25 ++++++++----- .../components/wink/binary_sensor.py | 8 +++-- homeassistant/components/wink/sensor.py | 8 +++-- .../components/worxlandroid/sensor.py | 5 ++- 15 files changed, 128 insertions(+), 126 deletions(-) diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index ef69513db432f8..f1def71cc641ad 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -103,18 +103,18 @@ async def async_saj(): for sensor in hass_sensors: state_unknown = False - if not values: - # SAJ inverters are powered by DC via solar panels and thus are - # offline after the sun has set. If a sensor resets on a daily - # basis like "today_yield", this reset won't happen automatically. - # Code below checks if today > day when sensor was last updated - # and if so: set state to None. - # Sensors with live values like "temperature" or "current_power" - # will also be reset to None. - if (sensor.per_day_basis and date.today() > sensor.date_updated) or ( - not sensor.per_day_basis and not sensor.per_total_basis - ): - state_unknown = True + # SAJ inverters are powered by DC via solar panels and thus are + # offline after the sun has set. If a sensor resets on a daily + # basis like "today_yield", this reset won't happen automatically. + # Code below checks if today > day when sensor was last updated + # and if so: set state to None. + # Sensors with live values like "temperature" or "current_power" + # will also be reset to None. + if not values and ( + (sensor.per_day_basis and date.today() > sensor.date_updated) + or (not sensor.per_day_basis and not sensor.per_total_basis) + ): + state_unknown = True sensor.async_update_values(unknown_state=state_unknown) return values diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index 8853680af338dd..a8cfdba5be5555 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -53,31 +53,32 @@ async def async_step_user(self, user_input=None): # If hass config has the location set and is a valid coordinate the # default location is set as default values in the form - if not smhi_locations(self.hass): - if await self._homeassistant_location_exists(): - return await self._show_config_form( - name=HOME_LOCATION_NAME, - latitude=self.hass.config.latitude, - longitude=self.hass.config.longitude, - ) + if ( + not smhi_locations(self.hass) + and await self._homeassistant_location_exists() + ): + return await self._show_config_form( + name=HOME_LOCATION_NAME, + latitude=self.hass.config.latitude, + longitude=self.hass.config.longitude, + ) return await self._show_config_form() async def _homeassistant_location_exists(self) -> bool: """Return true if default location is set and is valid.""" - if self.hass.config.latitude != 0.0 and self.hass.config.longitude != 0.0: - # Return true if valid location - if await self._check_location( + # Return true if valid location + return ( + self.hass.config.latitude != 0.0 + and self.hass.config.longitude != 0.0 + and await self._check_location( self.hass.config.longitude, self.hass.config.latitude - ): - return True - return False + ) + ) def _name_in_configuration_exists(self, name: str) -> bool: """Return True if name exists in configuration.""" - if name in smhi_locations(self.hass): - return True - return False + return name in smhi_locations(self.hass) async def _show_config_form( self, name: str = None, latitude: str = None, longitude: str = None @@ -97,7 +98,6 @@ async def _show_config_form( async def _check_location(self, longitude: str, latitude: str) -> bool: """Return true if location is ok.""" - try: session = aiohttp_client.async_get_clientsession(self.hass) smhi_api = Smhi(longitude, latitude, session=session) diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index b260097797159e..4909bbf95a3452 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -50,9 +50,8 @@ async def handle(self, request, stream, sequence): track = stream.add_provider("hls") stream.start() # Wait for a segment to be ready - if not track.segments: - if not await track.recv(): - return web.HTTPNotFound() + if not track.segments and not await track.recv(): + return web.HTTPNotFound() headers = {"Content-Type": FORMAT_CONTENT_TYPE["hls"]} return web.Response(body=self.render(track).encode("utf-8"), headers=headers) @@ -110,9 +109,8 @@ async def handle(self, request, stream, sequence): track = stream.add_provider("hls") stream.start() # Wait for a segment to be ready - if not track.segments: - if not await track.recv(): - return web.HTTPNotFound() + if not track.segments and not await track.recv(): + return web.HTTPNotFound() headers = {"Content-Type": FORMAT_CONTENT_TYPE["hls"]} return web.Response(body=self.render(track).encode("utf-8"), headers=headers) diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index 772134c66b167e..d9d9bdff4e1ee2 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -118,21 +118,20 @@ async def validate_login_creds(self, data): async def async_step_pin(self, user_input=None): """Handle second part of config flow, if required.""" error = None - if user_input: - if self.controller.update_saved_pin(user_input[CONF_PIN]): - try: - vol.Match(r"[0-9]{4}")(user_input[CONF_PIN]) - await self.controller.test_pin() - except vol.Invalid: - error = {"base": "bad_pin_format"} - except InvalidPIN: - error = {"base": "incorrect_pin"} - else: - _LOGGER.debug("PIN successfully tested") - self.config_data.update(user_input) - return self.async_create_entry( - title=self.config_data[CONF_USERNAME], data=self.config_data - ) + if user_input and self.controller.update_saved_pin(user_input[CONF_PIN]): + try: + vol.Match(r"[0-9]{4}")(user_input[CONF_PIN]) + await self.controller.test_pin() + except vol.Invalid: + error = {"base": "bad_pin_format"} + except InvalidPIN: + error = {"base": "incorrect_pin"} + else: + _LOGGER.debug("PIN successfully tested") + self.config_data.update(user_input) + return self.async_create_entry( + title=self.config_data[CONF_USERNAME], data=self.config_data + ) return self.async_show_form(step_id="pin", data_schema=PIN_SCHEMA, errors=error) diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index 41dd8a6604f5f7..3994c9c6124fd8 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -220,16 +220,20 @@ def state(self): self.hass.config.units.length(self.current_value, self.api_unit), 1 ) - if self.api_unit in PRESSURE_UNITS: - if self.hass.config.units == IMPERIAL_SYSTEM: - return round( - self.hass.config.units.pressure(self.current_value, self.api_unit), - 1, - ) + if ( + self.api_unit in PRESSURE_UNITS + and self.hass.config.units == IMPERIAL_SYSTEM + ): + return round( + self.hass.config.units.pressure(self.current_value, self.api_unit), + 1, + ) - if self.api_unit in FUEL_CONSUMPTION_UNITS: - if self.hass.config.units == IMPERIAL_SYSTEM: - return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1) + if ( + self.api_unit in FUEL_CONSUMPTION_UNITS + and self.hass.config.units == IMPERIAL_SYSTEM + ): + return round((100.0 * L_PER_GAL) / (KM_PER_MI * self.current_value), 1) return self.current_value diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 3e69939cbf32ff..596f56d51a1440 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -164,17 +164,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Initialize the sensor argument if none was provided. # For disk monitoring default to "/" (root) to prevent runtime errors, if argument was not specified. if CONF_ARG not in resource: + resource[CONF_ARG] = "" if resource[CONF_TYPE].startswith("disk_"): resource[CONF_ARG] = "/" - else: - resource[CONF_ARG] = "" # Verify if we can retrieve CPU / processor temperatures. # If not, do not create the entity and add a warning to the log - if resource[CONF_TYPE] == "processor_temperature": - if SystemMonitorSensor.read_cpu_temperature() is None: - _LOGGER.warning("Cannot read CPU / processor temperature information") - continue + if ( + resource[CONF_TYPE] == "processor_temperature" + and SystemMonitorSensor.read_cpu_temperature() is None + ): + _LOGGER.warning("Cannot read CPU / processor temperature information") + continue dev.append(SystemMonitorSensor(resource[CONF_TYPE], resource[CONF_ARG])) @@ -272,23 +273,20 @@ def update(self): err.name, ) self._state = STATE_OFF - elif self.type == "network_out" or self.type == "network_in": + elif self.type in ["network_out", "network_in"]: counters = psutil.net_io_counters(pernic=True) if self.argument in counters: counter = counters[self.argument][IO_COUNTER[self.type]] self._state = round(counter / 1024 ** 2, 1) else: self._state = None - elif self.type == "packets_out" or self.type == "packets_in": + elif self.type in ["packets_out", "packets_in"]: counters = psutil.net_io_counters(pernic=True) if self.argument in counters: self._state = counters[self.argument][IO_COUNTER[self.type]] else: self._state = None - elif ( - self.type == "throughput_network_out" - or self.type == "throughput_network_in" - ): + elif self.type in ["throughput_network_out", "throughput_network_in"]: counters = psutil.net_io_counters(pernic=True) if self.argument in counters: counter = counters[self.argument][IO_COUNTER[self.type]] @@ -306,7 +304,7 @@ def update(self): self._last_value = counter else: self._state = None - elif self.type == "ipv4_address" or self.type == "ipv6_address": + elif self.type in ["ipv4_address", "ipv6_address"]: addresses = psutil.net_if_addrs() if self.argument in addresses: for addr in addresses[self.argument]: @@ -333,16 +331,9 @@ def read_cpu_temperature(): temps = psutil.sensors_temperatures() for name, entries in temps.items(): - i = 1 - for entry in entries: + for i, entry in enumerate(entries, start=1): # In case the label is empty (e.g. on Raspberry PI 4), # construct it ourself here based on the sensor key name. - if not entry.label: - _label = f"{name} {i}" - else: - _label = entry.label - + _label = f"{name} {i}" if not entry.label else entry.label if _label in CPU_SENSOR_PREFIXES: return round(entry.current, 1) - - i += 1 diff --git a/homeassistant/components/tado/device_tracker.py b/homeassistant/components/tado/device_tracker.py index 8de938af985797..afa8bc6a604a03 100644 --- a/homeassistant/components/tado/device_tracker.py +++ b/homeassistant/components/tado/device_tracker.py @@ -131,11 +131,10 @@ async def _async_update_info(self): # Find devices that have geofencing enabled, and are currently at home. for mobile_device in tado_json: - if mobile_device.get("location"): - if mobile_device["location"]["atHome"]: - device_id = mobile_device["id"] - device_name = mobile_device["name"] - last_results.append(Device(device_id, device_name)) + if mobile_device.get("location") and mobile_device["location"]["atHome"]: + device_id = mobile_device["id"] + device_name = mobile_device["name"] + last_results.append(Device(device_id, device_name)) self.last_results = last_results diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 3cb7e663058053..9a55e644e79797 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -320,12 +320,12 @@ def _light_state_from_params(self, light_state_params) -> LightState: light_state_params[LIGHT_STATE_BRIGHTNESS] ) - if light_features.supported_features & SUPPORT_COLOR_TEMP: - if ( - light_state_params.get(LIGHT_STATE_COLOR_TEMP) is not None - and light_state_params[LIGHT_STATE_COLOR_TEMP] != 0 - ): - color_temp = kelvin_to_mired(light_state_params[LIGHT_STATE_COLOR_TEMP]) + if ( + light_features.supported_features & SUPPORT_COLOR_TEMP + and light_state_params.get(LIGHT_STATE_COLOR_TEMP) is not None + and light_state_params[LIGHT_STATE_COLOR_TEMP] != 0 + ): + color_temp = kelvin_to_mired(light_state_params[LIGHT_STATE_COLOR_TEMP]) if light_features.supported_features & SUPPORT_COLOR: hue_saturation = ( diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index dddb4d0e5e32d9..9842184e2ee59e 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -205,10 +205,13 @@ def async_update_callback(self) -> None: elif not self.heartbeat_check: self.schedule_update = True - elif not self.client.event and self.client.last_updated == SOURCE_DATA: - if self.is_wired == self.client.is_wired: - self._is_connected = True - self.schedule_update = True + elif ( + not self.client.event + and self.client.last_updated == SOURCE_DATA + and self.is_wired == self.client.is_wired + ): + self._is_connected = True + self.schedule_update = True if self.schedule_update: self.schedule_update = False diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index da3139317d178b..f04acaaec872e5 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -279,10 +279,11 @@ def __init__(self, client, controller): @callback def async_update_callback(self) -> None: """Update the clients state.""" - if self.client.last_updated == SOURCE_EVENT: - - if self.client.event.event in CLIENT_BLOCKED + CLIENT_UNBLOCKED: - self._is_blocked = self.client.event.event in CLIENT_BLOCKED + if ( + self.client.last_updated == SOURCE_EVENT + and self.client.event.event in CLIENT_BLOCKED + CLIENT_UNBLOCKED + ): + self._is_blocked = self.client.event.event in CLIENT_BLOCKED super().async_update_callback() diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 367a3915e6dec3..6bbd868a8bdeaa 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -129,11 +129,9 @@ def is_recording(self): if "recordingIndicator" in self._caminfo: recording_state = self._caminfo["recordingIndicator"] - return ( - self._caminfo["recordingSettings"]["fullTimeRecordEnabled"] - or recording_state == "MOTION_INPROGRESS" - or recording_state == "MOTION_FINISHED" - ) + return self._caminfo["recordingSettings"][ + "fullTimeRecordEnabled" + ] or recording_state in ["MOTION_INPROGRESS", "MOTION_FINISHED"] @property def motion_detection_enabled(self): @@ -198,9 +196,8 @@ def _login(self): def camera_image(self): """Return the image of this camera.""" - if not self._camera: - if not self._login(): - return + if not self._camera and not self._login(): + return def _get_image(retry=True): try: diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index 49402ecb911215..e55413926ac3e0 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -80,9 +80,12 @@ def is_on(self): @property def percentage(self) -> int | None: """Return the current speed percentage.""" - if "direction" in self._status: - if self._status["direction"] == WL_DIRECTION_OFF: - return 0 + if ( + "direction" in self._status + and self._status["direction"] == WL_DIRECTION_OFF + ): + return 0 + wl_speed = self._status.get("speed") if wl_speed is None: return None @@ -96,9 +99,11 @@ def speed_count(self) -> int: @property def current_direction(self) -> str: """Return the current direction of the fan.""" - if "direction" in self._status: - if self._status["direction"] != WL_DIRECTION_OFF: - self._direction = self._status["direction"] + if ( + "direction" in self._status + and self._status["direction"] != WL_DIRECTION_OFF + ): + self._direction = self._status["direction"] return self._direction async def async_turn_on( @@ -119,9 +124,11 @@ async def async_set_percentage(self, percentage: int): if percentage == 0: await self._client.set_fan_direction(self._index, WL_DIRECTION_OFF) return - if "direction" in self._status: - if self._status["direction"] == WL_DIRECTION_OFF: - await self._client.set_fan_direction(self._index, self._direction) + if ( + "direction" in self._status + and self._status["direction"] == WL_DIRECTION_OFF + ): + await self._client.set_fan_direction(self._index, self._direction) wl_speed = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage) await self._client.set_fan_speed(self._index, wl_speed) diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index ea864e912f0203..6a5977c1dc2629 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -40,9 +40,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor in pywink.get_sensors(): _id = sensor.object_id() + sensor.name() - if _id not in hass.data[DOMAIN]["unique_ids"]: - if sensor.capability() in SENSOR_TYPES: - add_entities([WinkBinarySensorEntity(sensor, hass)]) + if ( + _id not in hass.data[DOMAIN]["unique_ids"] + and sensor.capability() in SENSOR_TYPES + ): + add_entities([WinkBinarySensorEntity(sensor, hass)]) for key in pywink.get_keys(): _id = key.object_id() + key.name() diff --git a/homeassistant/components/wink/sensor.py b/homeassistant/components/wink/sensor.py index 8d60c21c1188b8..f640a24def2253 100644 --- a/homeassistant/components/wink/sensor.py +++ b/homeassistant/components/wink/sensor.py @@ -19,9 +19,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor in pywink.get_sensors(): _id = sensor.object_id() + sensor.name() - if _id not in hass.data[DOMAIN]["unique_ids"]: - if sensor.capability() in SENSOR_TYPES: - add_entities([WinkSensorEntity(sensor, hass)]) + if ( + _id not in hass.data[DOMAIN]["unique_ids"] + and sensor.capability() in SENSOR_TYPES + ): + add_entities([WinkSensorEntity(sensor, hass)]) for eggtray in pywink.get_eggtrays(): _id = eggtray.object_id() + eggtray.name() diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index 9be3afabc9f3ee..e7600670c52280 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -127,9 +127,8 @@ async def async_update(self): def get_error(obj): """Get the mower error.""" for i, err in enumerate(obj["allarmi"]): - if i != 2: # ignore wire bounce errors - if err == 1: - return ERROR_STATE[i] + if i != 2 and err == 1: # ignore wire bounce errors + return ERROR_STATE[i] return None From db355f9b233fbe4be0cf1750db931460134811fd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 10:58:38 +0100 Subject: [PATCH 1542/1818] Merge of nested IF-IF cases - A-C (#48365) --- homeassistant/components/agent_dvr/camera.py | 8 ++++---- .../components/alarmdecoder/config_flow.py | 20 ++++++++++++------- homeassistant/components/alexa/handlers.py | 5 ++--- homeassistant/components/apprise/notify.py | 9 ++++----- homeassistant/components/asuswrt/router.py | 5 ++--- homeassistant/components/cast/media_player.py | 12 +++++------ homeassistant/components/climacell/weather.py | 9 ++++----- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index 7904f98216b269..24cd5dbb92cec6 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -102,10 +102,10 @@ async def async_update(self): _LOGGER.debug("%s reacquired", self._name) self._removed = False except AgentError: - if self.device.client.is_available: # server still available - camera error - if not self._removed: - _LOGGER.error("%s lost", self._name) - self._removed = True + # server still available - camera error + if self.device.client.is_available and not self._removed: + _LOGGER.error("%s lost", self._name) + self._removed = True @property def extra_state_attributes(self): diff --git a/homeassistant/components/alarmdecoder/config_flow.py b/homeassistant/components/alarmdecoder/config_flow.py index a82b84b60d1611..08c8052c04b4df 100644 --- a/homeassistant/components/alarmdecoder/config_flow.py +++ b/homeassistant/components/alarmdecoder/config_flow.py @@ -349,12 +349,18 @@ def _device_already_added(current_entries, user_input, protocol): entry_path = entry.data.get(CONF_DEVICE_PATH) entry_baud = entry.data.get(CONF_DEVICE_BAUD) - if protocol == PROTOCOL_SOCKET: - if user_host == entry_host and user_port == entry_port: - return True - - if protocol == PROTOCOL_SERIAL: - if user_baud == entry_baud and user_path == entry_path: - return True + if ( + protocol == PROTOCOL_SOCKET + and user_host == entry_host + and user_port == entry_port + ): + return True + + if ( + protocol == PROTOCOL_SERIAL + and user_baud == entry_baud + and user_path == entry_path + ): + return True return False diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index dce4f9f2210cab..cee4cda562d8cd 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -653,10 +653,9 @@ def temperature_from_object(hass, temp_obj, interval=False): if temp_obj["scale"] == "FAHRENHEIT": from_unit = TEMP_FAHRENHEIT - elif temp_obj["scale"] == "KELVIN": + elif temp_obj["scale"] == "KELVIN" and not interval: # convert to Celsius if absolute temperature - if not interval: - temp -= 273.15 + temp -= 273.15 return convert_temperature(temp, from_unit, to_unit, interval) diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 95bf11ddc097ae..5f4a6b666430ff 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -42,11 +42,10 @@ def get_service(hass, config, discovery_info=None): _LOGGER.error("Invalid Apprise config url provided") return None - if config.get(CONF_URL): - # Ordered list of URLs - if not a_obj.add(config[CONF_URL]): - _LOGGER.error("Invalid Apprise URL(s) supplied") - return None + # Ordered list of URLs + if config.get(CONF_URL) and not a_obj.add(config[CONF_URL]): + _LOGGER.error("Invalid Apprise URL(s) supplied") + return None return AppriseNotificationService(a_obj) diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 550e4c8fc169b4..c5880ea11bb635 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -341,9 +341,8 @@ async def _update_unpolled_sensors(self) -> None: async def close(self) -> None: """Close the connection.""" - if self._api is not None: - if self._protocol == PROTOCOL_TELNET: - self._api.connection.disconnect() + if self._api is not None and self._protocol == PROTOCOL_TELNET: + self._api.connection.disconnect() self._api = None for func in self._on_close: diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 540f3263e1e7fd..7c2a696027faec 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -706,15 +706,15 @@ def supported_features(self): support = SUPPORT_CAST media_status = self._media_status()[0] - if self.cast_status: - if self.cast_status.volume_control_type != VOLUME_CONTROL_TYPE_FIXED: - support |= SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET + if ( + self.cast_status + and self.cast_status.volume_control_type != VOLUME_CONTROL_TYPE_FIXED + ): + support |= SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET if media_status: if media_status.supports_queue_next: - support |= SUPPORT_PREVIOUS_TRACK - if media_status.supports_queue_next: - support |= SUPPORT_NEXT_TRACK + support |= SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK if media_status.supports_seek: support |= SUPPORT_SEEK diff --git a/homeassistant/components/climacell/weather.py b/homeassistant/components/climacell/weather.py index e6485f90936c92..b9da5431dd058b 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -274,13 +274,12 @@ def forecast(self): ), temp_low, ) - elif self.forecast_type == NOWCAST: + elif self.forecast_type == NOWCAST and precipitation: # Precipitation is forecasted in CONF_TIMESTEP increments but in a # per hour rate, so value needs to be converted to an amount. - if precipitation: - precipitation = ( - precipitation / 60 * self._config_entry.options[CONF_TIMESTEP] - ) + precipitation = ( + precipitation / 60 * self._config_entry.options[CONF_TIMESTEP] + ) forecasts.append( _forecast_dict( From 786023fce41dc4128552bedc743fd176f0539a20 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 11:30:29 +0100 Subject: [PATCH 1543/1818] Merge of nested IF-IF cases - H-J (#48368) --- .../components/here_travel_time/sensor.py | 5 +- .../components/hitron_coda/device_tracker.py | 10 ++-- .../homekit_controller/device_trigger.py | 52 +++++++++---------- .../homekit_controller/media_player.py | 8 +-- homeassistant/components/hue/light.py | 13 +++-- homeassistant/components/hyperion/light.py | 10 ++-- .../components/image_processing/__init__.py | 9 ++-- .../components/imap_email_content/sensor.py | 8 +-- homeassistant/components/ios/notify.py | 10 ++-- .../components/isy994/binary_sensor.py | 20 +++---- .../components/joaoapps_join/__init__.py | 7 ++- .../components/joaoapps_join/notify.py | 7 ++- 12 files changed, 81 insertions(+), 78 deletions(-) diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e4fbc0b2a892ca..4b8f765d08a6aa 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -258,9 +258,8 @@ def delayed_sensor_update(event): @property def state(self) -> str | None: """Return the state of the sensor.""" - if self._here_data.traffic_mode: - if self._here_data.traffic_time is not None: - return str(round(self._here_data.traffic_time / 60)) + if self._here_data.traffic_mode and self._here_data.traffic_time is not None: + return str(round(self._here_data.traffic_time / 60)) if self._here_data.base_time is not None: return str(round(self._here_data.base_time / 60)) diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index 4634c6e378ad32..cbd6b7eeff87e8 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -74,10 +74,9 @@ def scan_devices(self): def get_device_name(self, device): """Return the name of the device with the given MAC address.""" - name = next( + return next( (result.name for result in self.last_results if result.mac == device), None ) - return name def _login(self): """Log in to the router. This is required for subsequent api calls.""" @@ -103,10 +102,9 @@ def _update_info(self): """Get ARP from router.""" _LOGGER.info("Fetching") - if self._userid is None: - if not self._login(): - _LOGGER.error("Could not obtain a user ID from the router") - return False + if self._userid is None and not self._login(): + _LOGGER.error("Could not obtain a user ID from the router") + return False last_results = [] # doing a request diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 31bfcc18d52486..59cc32c0b1bab7 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -99,9 +99,11 @@ def enumerate_stateless_switch(service): # A stateless switch that has a SERVICE_LABEL_INDEX is part of a group # And is handled separately - if service.has(CharacteristicsTypes.SERVICE_LABEL_INDEX): - if len(service.linked) > 0: - return [] + if ( + service.has(CharacteristicsTypes.SERVICE_LABEL_INDEX) + and len(service.linked) > 0 + ): + return [] char = service[CharacteristicsTypes.INPUT_EVENT] @@ -109,17 +111,15 @@ def enumerate_stateless_switch(service): # manufacturer might not - clamp options to what they say. all_values = clamp_enum_to_char(InputEventValues, char) - results = [] - for event_type in all_values: - results.append( - { - "characteristic": char.iid, - "value": event_type, - "type": "button1", - "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], - } - ) - return results + return [ + { + "characteristic": char.iid, + "value": event_type, + "type": "button1", + "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], + } + for event_type in all_values + ] def enumerate_stateless_switch_group(service): @@ -234,20 +234,16 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]: device = hass.data[TRIGGERS][device_id] - triggers = [] - - for trigger, subtype in device.async_get_triggers(): - triggers.append( - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: trigger, - CONF_SUBTYPE: subtype, - } - ) - - return triggers + return [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: trigger, + CONF_SUBTYPE: subtype, + } + for trigger, subtype in device.async_get_triggers() + ] async def async_attach_trigger( diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 6dfa8720ee5c69..71bde5f0af9b16 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -93,9 +93,11 @@ def supported_features(self): if TargetMediaStateValues.STOP in self.supported_media_states: features |= SUPPORT_STOP - if self.service.has(CharacteristicsTypes.REMOTE_KEY): - if RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys: - features |= SUPPORT_PAUSE | SUPPORT_PLAY + if ( + self.service.has(CharacteristicsTypes.REMOTE_KEY) + and RemoteKeyValues.PLAY_PAUSE in self.supported_remote_keys + ): + features |= SUPPORT_PAUSE | SUPPORT_PLAY return features diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 8adde810fbe3cb..3d1937340059f4 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -229,7 +229,7 @@ async def async_safe_fetch(bridge, fetch_method): except aiohue.Unauthorized as err: await bridge.handle_unauthorized_error() raise UpdateFailed("Unauthorized") from err - except (aiohue.AiohueException,) as err: + except aiohue.AiohueException as err: raise UpdateFailed(f"Hue error: {err}") from err @@ -297,12 +297,11 @@ def __init__(self, coordinator, bridge, is_group, light, supported_features, roo "bulb in the Philips Hue App." ) _LOGGER.warning(err, self.name) - if self.gamut: - if not color.check_valid_gamut(self.gamut): - err = "Color gamut of %s: %s, not valid, setting gamut to None." - _LOGGER.warning(err, self.name, str(self.gamut)) - self.gamut_typ = GAMUT_TYPE_UNAVAILABLE - self.gamut = None + if self.gamut and not color.check_valid_gamut(self.gamut): + err = "Color gamut of %s: %s, not valid, setting gamut to None." + _LOGGER.warning(err, self.name, str(self.gamut)) + self.gamut_typ = GAMUT_TYPE_UNAVAILABLE + self.gamut = None @property def unique_id(self): diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 36c3d836bf354a..248a45ec753bd2 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -241,8 +241,9 @@ async def async_turn_on(self, **kwargs: Any) -> None: if ATTR_BRIGHTNESS in kwargs: brightness = kwargs[ATTR_BRIGHTNESS] for item in self._client.adjustment or []: - if const.KEY_ID in item: - if not await self._client.async_send_set_adjustment( + if ( + const.KEY_ID in item + and not await self._client.async_send_set_adjustment( **{ const.KEY_ADJUSTMENT: { const.KEY_BRIGHTNESS: int( @@ -251,8 +252,9 @@ async def async_turn_on(self, **kwargs: Any) -> None: const.KEY_ID: item[const.KEY_ID], } } - ): - return + ) + ): + return # == Set an external source if ( diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 1320629aeb4bd1..58a6582e33c010 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -208,9 +208,12 @@ def async_process_faces(self, faces, total): """ # Send events for face in faces: - if ATTR_CONFIDENCE in face and self.confidence: - if face[ATTR_CONFIDENCE] < self.confidence: - continue + if ( + ATTR_CONFIDENCE in face + and self.confidence + and face[ATTR_CONFIDENCE] < self.confidence + ): + continue face.update({ATTR_ENTITY_ID: self.entity_id}) self.hass.async_add_job(self.hass.bus.async_fire, EVENT_DETECT_FACE, face) diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index 22c395a7c8fad2..cdd47d68d76130 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -220,9 +220,11 @@ def get_msg_text(email_message): elif part.get_content_type() == "text/html": if message_html is None: message_html = part.get_payload() - elif part.get_content_type().startswith("text"): - if message_untyped_text is None: - message_untyped_text = part.get_payload() + elif ( + part.get_content_type().startswith("text") + and message_untyped_text is None + ): + message_untyped_text = part.get_payload() if message_text is not None: return message_text diff --git a/homeassistant/components/ios/notify.py b/homeassistant/components/ios/notify.py index f9c682bf52743c..853fb0d479a4c9 100644 --- a/homeassistant/components/ios/notify.py +++ b/homeassistant/components/ios/notify.py @@ -69,10 +69,12 @@ def send_message(self, message="", **kwargs): """Send a message to the Lambda APNS gateway.""" data = {ATTR_MESSAGE: message} - if kwargs.get(ATTR_TITLE) is not None: - # Remove default title from notifications. - if kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT: - data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) + # Remove default title from notifications. + if ( + kwargs.get(ATTR_TITLE) is not None + and kwargs.get(ATTR_TITLE) != ATTR_TITLE_DEFAULT + ): + data[ATTR_TITLE] = kwargs.get(ATTR_TITLE) targets = kwargs.get(ATTR_TARGET) diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 3b0bb9fd1444ca..57b134e0900606 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -281,15 +281,17 @@ def add_negative_node(self, child) -> None: """ self._negative_node = child - if self._negative_node.status != ISY_VALUE_UNKNOWN: - # If the negative node has a value, it means the negative node is - # in use for this device. Next we need to check to see if the - # negative and positive nodes disagree on the state (both ON or - # both OFF). - if self._negative_node.status == self._node.status: - # The states disagree, therefore we cannot determine the state - # of the sensor until we receive our first ON event. - self._computed_state = None + # If the negative node has a value, it means the negative node is + # in use for this device. Next we need to check to see if the + # negative and positive nodes disagree on the state (both ON or + # both OFF). + if ( + self._negative_node.status != ISY_VALUE_UNKNOWN + and self._negative_node.status == self._node.status + ): + # The states disagree, therefore we cannot determine the state + # of the sensor until we receive our first ON event. + self._computed_state = None def _negative_node_control_handler(self, event: object) -> None: """Handle an "On" control event from the "negative" node.""" diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py index a65a7ffd7fe14e..1331afbfe970b2 100644 --- a/homeassistant/components/joaoapps_join/__init__.py +++ b/homeassistant/components/joaoapps_join/__init__.py @@ -121,10 +121,9 @@ def setup(hass, config): device_names = device.get(CONF_DEVICE_NAMES) name = device.get(CONF_NAME) name = f"{name.lower().replace(' ', '_')}_" if name else "" - if api_key: - if not get_devices(api_key): - _LOGGER.error("Error connecting to Join, check API key") - return False + if api_key and not get_devices(api_key): + _LOGGER.error("Error connecting to Join, check API key") + return False if device_id is None and device_ids is None and device_names is None: _LOGGER.error( "No device was provided. Please specify device_id" diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py index 7ba089e5dab0c5..06cad45bdde170 100644 --- a/homeassistant/components/joaoapps_join/notify.py +++ b/homeassistant/components/joaoapps_join/notify.py @@ -35,10 +35,9 @@ def get_service(hass, config, discovery_info=None): device_id = config.get(CONF_DEVICE_ID) device_ids = config.get(CONF_DEVICE_IDS) device_names = config.get(CONF_DEVICE_NAMES) - if api_key: - if not get_devices(api_key): - _LOGGER.error("Error connecting to Join. Check the API key") - return False + if api_key and not get_devices(api_key): + _LOGGER.error("Error connecting to Join. Check the API key") + return False if device_id is None and device_ids is None and device_names is None: _LOGGER.error( "No device was provided. Please specify device_id" From 0d595a28457f89948c2a697bfc9bf576f1af5e60 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 12:39:37 +0100 Subject: [PATCH 1544/1818] Merge of nested IF-IF cases - E-G (#48367) --- homeassistant/components/ebusd/__init__.py | 5 +- .../eddystone_temperature/sensor.py | 11 +- homeassistant/components/emby/media_player.py | 13 +- homeassistant/components/emoncms/sensor.py | 10 +- .../components/emulated_hue/hue_api.py | 101 ++++--- homeassistant/components/fail2ban/sensor.py | 8 +- homeassistant/components/fan/__init__.py | 5 +- homeassistant/components/garadget/cover.py | 15 +- .../components/generic_thermostat/climate.py | 43 ++- homeassistant/components/glances/sensor.py | 257 +++++++++--------- .../components/google_assistant/trait.py | 23 +- .../components/google_wifi/sensor.py | 7 +- .../components/gpmdp/media_player.py | 5 +- 13 files changed, 257 insertions(+), 246 deletions(-) diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index 00c40344d6ec28..beb8abd6289016 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -115,8 +115,7 @@ def write(self, call): try: _LOGGER.debug("Opening socket to ebusd %s", name) command_result = ebusdpy.write(self._address, self._circuit, name, value) - if command_result is not None: - if "done" not in command_result: - _LOGGER.warning("Write command failed: %s", name) + if command_result is not None and "done" not in command_result: + _LOGGER.warning("Write command failed: %s", name) except RuntimeError as err: _LOGGER.error(err) diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index 70afde4ffb17ca..28711821f507d5 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -170,10 +170,13 @@ def process_packet(self, namespace, instance, temperature): ) for dev in self.devices: - if dev.namespace == namespace and dev.instance == instance: - if dev.temperature != temperature: - dev.temperature = temperature - dev.schedule_update_ha_state() + if ( + dev.namespace == namespace + and dev.instance == instance + and dev.temperature != temperature + ): + dev.temperature = temperature + dev.schedule_update_ha_state() def stop(self): """Signal runner to stop and join thread.""" diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py index 1cbc893f98bca7..5656a1f14868e3 100644 --- a/homeassistant/components/emby/media_player.py +++ b/homeassistant/components/emby/media_player.py @@ -96,12 +96,13 @@ def device_update_callback(data): active_emby_devices[dev_id] = new new_devices.append(new) - elif dev_id in inactive_emby_devices: - if emby.devices[dev_id].state != "Off": - add = inactive_emby_devices.pop(dev_id) - active_emby_devices[dev_id] = add - _LOGGER.debug("Showing %s, item: %s", dev_id, add) - add.set_available(True) + elif ( + dev_id in inactive_emby_devices and emby.devices[dev_id].state != "Off" + ): + add = inactive_emby_devices.pop(dev_id) + active_emby_devices[dev_id] = add + _LOGGER.debug("Showing %s, item: %s", dev_id, add) + add.set_available(True) if new_devices: _LOGGER.debug("Adding new devices: %s", new_devices) diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index 4913f6340ccfec..bfc86db387ef3f 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -92,13 +92,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for elem in data.data: - if exclude_feeds is not None: - if int(elem["id"]) in exclude_feeds: - continue + if exclude_feeds is not None and int(elem["id"]) in exclude_feeds: + continue - if include_only_feeds is not None: - if int(elem["id"]) not in include_only_feeds: - continue + if include_only_feeds is not None and int(elem["id"]) not in include_only_feeds: + continue name = None if sensor_names is not None: diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 647d9db13356a8..f97636a46c038f 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -439,11 +439,13 @@ async def put(self, request, username, entity_number): # saturation and color temp if entity.domain == light.DOMAIN: if parsed[STATE_ON]: - if entity_features & SUPPORT_BRIGHTNESS: - if parsed[STATE_BRIGHTNESS] is not None: - data[ATTR_BRIGHTNESS] = hue_brightness_to_hass( - parsed[STATE_BRIGHTNESS] - ) + if ( + entity_features & SUPPORT_BRIGHTNESS + and parsed[STATE_BRIGHTNESS] is not None + ): + data[ATTR_BRIGHTNESS] = hue_brightness_to_hass( + parsed[STATE_BRIGHTNESS] + ) if entity_features & SUPPORT_COLOR: if any((parsed[STATE_HUE], parsed[STATE_SATURATION])): @@ -466,13 +468,17 @@ async def put(self, request, username, entity_number): if parsed[STATE_XY] is not None: data[ATTR_XY_COLOR] = parsed[STATE_XY] - if entity_features & SUPPORT_COLOR_TEMP: - if parsed[STATE_COLOR_TEMP] is not None: - data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP] + if ( + entity_features & SUPPORT_COLOR_TEMP + and parsed[STATE_COLOR_TEMP] is not None + ): + data[ATTR_COLOR_TEMP] = parsed[STATE_COLOR_TEMP] - if entity_features & SUPPORT_TRANSITION: - if parsed[STATE_TRANSITON] is not None: - data[ATTR_TRANSITION] = parsed[STATE_TRANSITON] / 10 + if ( + entity_features & SUPPORT_TRANSITION + and parsed[STATE_TRANSITON] is not None + ): + data[ATTR_TRANSITION] = parsed[STATE_TRANSITON] / 10 # If the requested entity is a script, add some variables elif entity.domain == script.DOMAIN: @@ -489,11 +495,13 @@ async def put(self, request, username, entity_number): # only setting the temperature service = None - if entity_features & SUPPORT_TARGET_TEMPERATURE: - if parsed[STATE_BRIGHTNESS] is not None: - domain = entity.domain - service = SERVICE_SET_TEMPERATURE - data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS] + if ( + entity_features & SUPPORT_TARGET_TEMPERATURE + and parsed[STATE_BRIGHTNESS] is not None + ): + domain = entity.domain + service = SERVICE_SET_TEMPERATURE + data[ATTR_TEMPERATURE] = parsed[STATE_BRIGHTNESS] # If the requested entity is a humidifier, set the humidity elif entity.domain == humidifier.DOMAIN: @@ -505,43 +513,48 @@ async def put(self, request, username, entity_number): # If the requested entity is a media player, convert to volume elif entity.domain == media_player.DOMAIN: - if entity_features & SUPPORT_VOLUME_SET: - if parsed[STATE_BRIGHTNESS] is not None: - turn_on_needed = True - domain = entity.domain - service = SERVICE_VOLUME_SET - # Convert 0-100 to 0.0-1.0 - data[ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0 + if ( + entity_features & SUPPORT_VOLUME_SET + and parsed[STATE_BRIGHTNESS] is not None + ): + turn_on_needed = True + domain = entity.domain + service = SERVICE_VOLUME_SET + # Convert 0-100 to 0.0-1.0 + data[ATTR_MEDIA_VOLUME_LEVEL] = parsed[STATE_BRIGHTNESS] / 100.0 # If the requested entity is a cover, convert to open_cover/close_cover elif entity.domain == cover.DOMAIN: domain = entity.domain + service = SERVICE_CLOSE_COVER if service == SERVICE_TURN_ON: service = SERVICE_OPEN_COVER - else: - service = SERVICE_CLOSE_COVER - if entity_features & SUPPORT_SET_POSITION: - if parsed[STATE_BRIGHTNESS] is not None: - domain = entity.domain - service = SERVICE_SET_COVER_POSITION - data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS] + if ( + entity_features & SUPPORT_SET_POSITION + and parsed[STATE_BRIGHTNESS] is not None + ): + domain = entity.domain + service = SERVICE_SET_COVER_POSITION + data[ATTR_POSITION] = parsed[STATE_BRIGHTNESS] # If the requested entity is a fan, convert to speed - elif entity.domain == fan.DOMAIN: - if entity_features & SUPPORT_SET_SPEED: - if parsed[STATE_BRIGHTNESS] is not None: - domain = entity.domain - # Convert 0-100 to a fan speed - brightness = parsed[STATE_BRIGHTNESS] - if brightness == 0: - data[ATTR_SPEED] = SPEED_OFF - elif 0 < brightness <= 33.3: - data[ATTR_SPEED] = SPEED_LOW - elif 33.3 < brightness <= 66.6: - data[ATTR_SPEED] = SPEED_MEDIUM - elif 66.6 < brightness <= 100: - data[ATTR_SPEED] = SPEED_HIGH + elif ( + entity.domain == fan.DOMAIN + and entity_features & SUPPORT_SET_SPEED + and parsed[STATE_BRIGHTNESS] is not None + ): + domain = entity.domain + # Convert 0-100 to a fan speed + brightness = parsed[STATE_BRIGHTNESS] + if brightness == 0: + data[ATTR_SPEED] = SPEED_OFF + elif 0 < brightness <= 33.3: + data[ATTR_SPEED] = SPEED_LOW + elif 33.3 < brightness <= 66.6: + data[ATTR_SPEED] = SPEED_MEDIUM + elif 66.6 < brightness <= 100: + data[ATTR_SPEED] = SPEED_HIGH # Map the off command to on if entity.domain in config.off_maps_to_on_domains: diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 87a8460f1496fc..29ac5c3d0b5f99 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -90,9 +90,11 @@ def update(self): if len(self.ban_dict[STATE_ALL_BANS]) > 10: self.ban_dict[STATE_ALL_BANS].pop(0) - elif entry[0] == "Unban": - if current_ip in self.ban_dict[STATE_CURRENT_BANS]: - self.ban_dict[STATE_CURRENT_BANS].remove(current_ip) + elif ( + entry[0] == "Unban" + and current_ip in self.ban_dict[STATE_CURRENT_BANS] + ): + self.ban_dict[STATE_CURRENT_BANS].remove(current_ip) if self.ban_dict[STATE_CURRENT_BANS]: self.last_ban = self.ban_dict[STATE_CURRENT_BANS][-1] diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index da2224aaf33de4..305c99072ab7bc 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -459,9 +459,8 @@ def speed(self) -> str | None: @property def percentage(self) -> int | None: """Return the current speed as a percentage.""" - if not self._implemented_preset_mode: - if self.speed in self.preset_modes: - return None + if not self._implemented_preset_mode and self.speed in self.preset_modes: + return None if not self._implemented_percentage: return self.speed_to_percentage(self.speed) return 0 diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index 206aaa6614d341..ad89c3a035b976 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -120,9 +120,8 @@ def __init__(self, hass, args): def __del__(self): """Try to remove token.""" - if self._obtained_token is True: - if self.access_token is not None: - self.remove_token() + if self._obtained_token is True and self.access_token is not None: + self.remove_token() @property def name(self): @@ -239,10 +238,12 @@ def update(self): ) self._state = STATE_OFFLINE - if self._state not in [STATE_CLOSING, STATE_OPENING]: - if self._unsub_listener_cover is not None: - self._unsub_listener_cover() - self._unsub_listener_cover = None + if ( + self._state not in [STATE_CLOSING, STATE_OPENING] + and self._unsub_listener_cover is not None + ): + self._unsub_listener_cover() + self._unsub_listener_cover = None def _get_variable(self, var): """Get latest status.""" diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 3c7959dbf4d2c2..ef3cf11fa1c3ce 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -442,28 +442,27 @@ async def _async_control_heating(self, time=None, force=False): if not self._active or self._hvac_mode == HVAC_MODE_OFF: return - if not force and time is None: - # If the `force` argument is True, we - # ignore `min_cycle_duration`. - # If the `time` argument is not none, we were invoked for - # keep-alive purposes, and `min_cycle_duration` is irrelevant. - if self.min_cycle_duration: - if self._is_device_active: - current_state = STATE_ON - else: - current_state = HVAC_MODE_OFF - try: - long_enough = condition.state( - self.hass, - self.heater_entity_id, - current_state, - self.min_cycle_duration, - ) - except ConditionError: - long_enough = False - - if not long_enough: - return + # If the `force` argument is True, we + # ignore `min_cycle_duration`. + # If the `time` argument is not none, we were invoked for + # keep-alive purposes, and `min_cycle_duration` is irrelevant. + if not force and time is None and self.min_cycle_duration: + if self._is_device_active: + current_state = STATE_ON + else: + current_state = HVAC_MODE_OFF + try: + long_enough = condition.state( + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) + except ConditionError: + long_enough = False + + if not long_enough: + return too_cold = self._target_temp >= self._cur_temp + self._cold_tolerance too_hot = self._cur_temp >= self._target_temp + self._hot_tolerance diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 52649518b68a8e..bbe045eb23220a 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -17,45 +17,44 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for sensor_type, sensor_details in SENSOR_TYPES.items(): if sensor_details[0] not in client.api.data: continue - if sensor_details[0] in client.api.data: - if sensor_details[0] == "fs": - # fs will provide a list of disks attached - for disk in client.api.data[sensor_details[0]]: + if sensor_details[0] == "fs": + # fs will provide a list of disks attached + for disk in client.api.data[sensor_details[0]]: + dev.append( + GlancesSensor( + client, + name, + disk["mnt_point"], + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], + ) + ) + elif sensor_details[0] == "sensors": + # sensors will provide temp for different devices + for sensor in client.api.data[sensor_details[0]]: + if sensor["type"] == sensor_type: dev.append( GlancesSensor( client, name, - disk["mnt_point"], + sensor["label"], SENSOR_TYPES[sensor_type][1], sensor_type, SENSOR_TYPES[sensor_type], ) ) - elif sensor_details[0] == "sensors": - # sensors will provide temp for different devices - for sensor in client.api.data[sensor_details[0]]: - if sensor["type"] == sensor_type: - dev.append( - GlancesSensor( - client, - name, - sensor["label"], - SENSOR_TYPES[sensor_type][1], - sensor_type, - SENSOR_TYPES[sensor_type], - ) - ) - elif client.api.data[sensor_details[0]]: - dev.append( - GlancesSensor( - client, - name, - "", - SENSOR_TYPES[sensor_type][1], - sensor_type, - SENSOR_TYPES[sensor_type], - ) + elif client.api.data[sensor_details[0]]: + dev.append( + GlancesSensor( + client, + name, + "", + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], ) + ) async_add_entities(dev, True) @@ -139,107 +138,103 @@ async def async_update(self): if value is None: return - if value is not None: - if self.sensor_details[0] == "fs": - for var in value["fs"]: - if var["mnt_point"] == self._sensor_name_prefix: - disk = var - break - if self.type == "disk_use_percent": - self._state = disk["percent"] - elif self.type == "disk_use": - self._state = round(disk["used"] / 1024 ** 3, 1) - elif self.type == "disk_free": - try: - self._state = round(disk["free"] / 1024 ** 3, 1) - except KeyError: - self._state = round( - (disk["size"] - disk["used"]) / 1024 ** 3, - 1, - ) - elif self.type == "battery": - for sensor in value["sensors"]: - if sensor["type"] == "battery": - if sensor["label"] == self._sensor_name_prefix: - self._state = sensor["value"] - elif self.type == "fan_speed": - for sensor in value["sensors"]: - if sensor["type"] == "fan_speed": - if sensor["label"] == self._sensor_name_prefix: - self._state = sensor["value"] - elif self.type == "temperature_core": - for sensor in value["sensors"]: - if sensor["type"] == "temperature_core": - if sensor["label"] == self._sensor_name_prefix: - self._state = sensor["value"] - elif self.type == "temperature_hdd": - for sensor in value["sensors"]: - if ( - sensor["type"] == "temperature_hdd" - and sensor["label"] == self._sensor_name_prefix - ): - self._state = sensor["value"] - elif self.type == "memory_use_percent": - self._state = value["mem"]["percent"] - elif self.type == "memory_use": - self._state = round(value["mem"]["used"] / 1024 ** 2, 1) - elif self.type == "memory_free": - self._state = round(value["mem"]["free"] / 1024 ** 2, 1) - elif self.type == "swap_use_percent": - self._state = value["memswap"]["percent"] - elif self.type == "swap_use": - self._state = round(value["memswap"]["used"] / 1024 ** 3, 1) - elif self.type == "swap_free": - self._state = round(value["memswap"]["free"] / 1024 ** 3, 1) - elif self.type == "processor_load": - # Windows systems don't provide load details + if self.sensor_details[0] == "fs": + for var in value["fs"]: + if var["mnt_point"] == self._sensor_name_prefix: + disk = var + break + if self.type == "disk_free": try: - self._state = value["load"]["min15"] + self._state = round(disk["free"] / 1024 ** 3, 1) except KeyError: - self._state = value["cpu"]["total"] - elif self.type == "process_running": - self._state = value["processcount"]["running"] - elif self.type == "process_total": - self._state = value["processcount"]["total"] - elif self.type == "process_thread": - self._state = value["processcount"]["thread"] - elif self.type == "process_sleeping": - self._state = value["processcount"]["sleeping"] - elif self.type == "cpu_use_percent": - self._state = value["quicklook"]["cpu"] - elif self.type == "docker_active": - count = 0 - try: - for container in value["docker"]["containers"]: - if ( - container["Status"] == "running" - or "Up" in container["Status"] - ): - count += 1 - self._state = count - except KeyError: - self._state = count - elif self.type == "docker_cpu_use": - cpu_use = 0.0 - try: - for container in value["docker"]["containers"]: - if ( - container["Status"] == "running" - or "Up" in container["Status"] - ): - cpu_use += container["cpu"]["total"] - self._state = round(cpu_use, 1) - except KeyError: - self._state = STATE_UNAVAILABLE - elif self.type == "docker_memory_use": - mem_use = 0.0 - try: - 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) - except KeyError: - self._state = STATE_UNAVAILABLE + self._state = round( + (disk["size"] - disk["used"]) / 1024 ** 3, + 1, + ) + elif self.type == "disk_use": + self._state = round(disk["used"] / 1024 ** 3, 1) + elif self.type == "disk_use_percent": + self._state = disk["percent"] + elif self.type == "battery": + for sensor in value["sensors"]: + if ( + sensor["type"] == "battery" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "fan_speed": + for sensor in value["sensors"]: + if ( + sensor["type"] == "fan_speed" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "temperature_core": + for sensor in value["sensors"]: + if ( + sensor["type"] == "temperature_core" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "temperature_hdd": + for sensor in value["sensors"]: + if ( + sensor["type"] == "temperature_hdd" + and sensor["label"] == self._sensor_name_prefix + ): + self._state = sensor["value"] + elif self.type == "memory_use_percent": + self._state = value["mem"]["percent"] + elif self.type == "memory_use": + self._state = round(value["mem"]["used"] / 1024 ** 2, 1) + elif self.type == "memory_free": + self._state = round(value["mem"]["free"] / 1024 ** 2, 1) + elif self.type == "swap_use_percent": + self._state = value["memswap"]["percent"] + elif self.type == "swap_use": + self._state = round(value["memswap"]["used"] / 1024 ** 3, 1) + elif self.type == "swap_free": + self._state = round(value["memswap"]["free"] / 1024 ** 3, 1) + elif self.type == "processor_load": + # Windows systems don't provide load details + try: + self._state = value["load"]["min15"] + except KeyError: + self._state = value["cpu"]["total"] + elif self.type == "process_running": + self._state = value["processcount"]["running"] + elif self.type == "process_total": + self._state = value["processcount"]["total"] + elif self.type == "process_thread": + self._state = value["processcount"]["thread"] + elif self.type == "process_sleeping": + self._state = value["processcount"]["sleeping"] + elif self.type == "cpu_use_percent": + self._state = value["quicklook"]["cpu"] + elif self.type == "docker_active": + count = 0 + try: + for container in value["docker"]["containers"]: + if container["Status"] == "running" or "Up" in container["Status"]: + count += 1 + self._state = count + except KeyError: + self._state = count + elif self.type == "docker_cpu_use": + cpu_use = 0.0 + try: + for container in value["docker"]["containers"]: + if container["Status"] == "running" or "Up" in container["Status"]: + cpu_use += container["cpu"]["total"] + self._state = round(cpu_use, 1) + except KeyError: + self._state = STATE_UNAVAILABLE + elif self.type == "docker_memory_use": + mem_use = 0.0 + try: + 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) + except KeyError: + self._state = STATE_UNAVAILABLE diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 8f01482aa45977..384c5bfd0ae29d 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1428,9 +1428,8 @@ def query_attributes(self): elif self.state.domain == humidifier.DOMAIN: if ATTR_MODE in attrs: mode_settings["mode"] = attrs.get(ATTR_MODE) - elif self.state.domain == light.DOMAIN: - if light.ATTR_EFFECT in attrs: - mode_settings["effect"] = attrs.get(light.ATTR_EFFECT) + elif self.state.domain == light.DOMAIN and light.ATTR_EFFECT in attrs: + mode_settings["effect"] = attrs.get(light.ATTR_EFFECT) if mode_settings: response["on"] = self.state.state not in (STATE_OFF, STATE_UNKNOWN) @@ -1618,15 +1617,17 @@ def sync_attributes(self): if self.state.domain == binary_sensor.DOMAIN: response["queryOnlyOpenClose"] = True response["discreteOnlyOpenClose"] = True - elif self.state.domain == cover.DOMAIN: - if features & cover.SUPPORT_SET_POSITION == 0: - response["discreteOnlyOpenClose"] = True + elif ( + self.state.domain == cover.DOMAIN + and features & cover.SUPPORT_SET_POSITION == 0 + ): + response["discreteOnlyOpenClose"] = True - if ( - features & cover.SUPPORT_OPEN == 0 - and features & cover.SUPPORT_CLOSE == 0 - ): - response["queryOnlyOpenClose"] = True + if ( + features & cover.SUPPORT_OPEN == 0 + and features & cover.SUPPORT_CLOSE == 0 + ): + response["queryOnlyOpenClose"] = True if self.state.attributes.get(ATTR_ASSUMED_STATE): response["commandOnlyOpenClose"] = True diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index cf5a804e5a5404..28ec5df7486fe1 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -175,9 +175,10 @@ def data_format(self): sensor_value = "Online" else: sensor_value = "Offline" - elif attr_key == ATTR_LOCAL_IP: - if not self.raw_data["wan"]["online"]: - sensor_value = STATE_UNKNOWN + elif ( + attr_key == ATTR_LOCAL_IP and not self.raw_data["wan"]["online"] + ): + sensor_value = STATE_UNKNOWN self.data[attr_key] = sensor_value except KeyError: diff --git a/homeassistant/components/gpmdp/media_player.py b/homeassistant/components/gpmdp/media_player.py index 2fa227f0953d9a..5680eb755009ce 100644 --- a/homeassistant/components/gpmdp/media_player.py +++ b/homeassistant/components/gpmdp/media_player.py @@ -227,9 +227,8 @@ def send_gpmdp_msg(self, namespace, method, with_id=True): return while True: msg = json.loads(websocket.recv()) - if "requestID" in msg: - if msg["requestID"] == self._request_id: - return msg + if "requestID" in msg and msg["requestID"] == self._request_id: + return msg except ( ConnectionRefusedError, ConnectionResetError, From 4a353efdfb399e67ee6f73d1771435e318a870ae Mon Sep 17 00:00:00 2001 From: Unai Date: Sat, 27 Mar 2021 12:42:23 +0100 Subject: [PATCH 1545/1818] Add Maxcube unit tests (#47872) * Simplify maxcube integration Device objects returned by maxcube-api dependency are stable, so we do not need to resolve from the device address every time. Also, refactor and unify how maxcube integration sets temperature & mode. * Add tests for maxcube component * Use homeassistant.util.utcnow to retrieve current time * Revert "Simplify maxcube integration" This reverts commit 84d231d5bdfda9b7744d371d3025986b637a6a8b. * Make test pass again after rolling back integration changes --- .coveragerc | 1 - requirements_test_all.txt | 3 + tests/components/maxcube/conftest.py | 110 ++++++ .../maxcube/test_maxcube_binary_sensor.py | 37 ++ .../maxcube/test_maxcube_climate.py | 364 ++++++++++++++++++ 5 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 tests/components/maxcube/conftest.py create mode 100644 tests/components/maxcube/test_maxcube_binary_sensor.py create mode 100644 tests/components/maxcube/test_maxcube_climate.py diff --git a/.coveragerc b/.coveragerc index eaeee0f992d91f..24ceeae65be32f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -563,7 +563,6 @@ omit = homeassistant/components/map/* homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* - homeassistant/components/maxcube/* homeassistant/components/mcp23017/* homeassistant/components/media_extractor/* homeassistant/components/mediaroom/media_player.py diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6437f30ddafc6d..a8ceff2833300c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -472,6 +472,9 @@ logi_circle==0.2.2 # homeassistant.components.luftdaten luftdaten==0.6.4 +# homeassistant.components.maxcube +maxcube-api==0.4.1 + # homeassistant.components.mythicbeastsdns mbddns==0.1.2 diff --git a/tests/components/maxcube/conftest.py b/tests/components/maxcube/conftest.py new file mode 100644 index 00000000000000..7f45634b986406 --- /dev/null +++ b/tests/components/maxcube/conftest.py @@ -0,0 +1,110 @@ +"""Tests for EQ3 Max! component.""" +from unittest.mock import create_autospec, patch + +from maxcube.device import MAX_DEVICE_MODE_AUTOMATIC, MAX_DEVICE_MODE_MANUAL +from maxcube.room import MaxRoom +from maxcube.thermostat import MaxThermostat +from maxcube.wallthermostat import MaxWallThermostat +from maxcube.windowshutter import MaxWindowShutter +import pytest + +from homeassistant.components.maxcube import DOMAIN +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def room(): + """Create a test MAX! room.""" + r = MaxRoom() + r.id = 1 + r.name = "TestRoom" + return r + + +@pytest.fixture +def thermostat(): + """Create test MAX! thermostat.""" + t = create_autospec(MaxThermostat) + t.name = "TestThermostat" + t.serial = "AABBCCDD01" + t.rf_address = "abc1" + t.room_id = 1 + t.is_thermostat.return_value = True + t.is_wallthermostat.return_value = False + t.is_windowshutter.return_value = False + t.mode = MAX_DEVICE_MODE_AUTOMATIC + t.comfort_temperature = 19.0 + t.eco_temperature = 14.0 + t.target_temperature = 20.5 + t.actual_temperature = 19.0 + t.max_temperature = None + t.min_temperature = None + t.valve_position = 25 # 25% + return t + + +@pytest.fixture +def wallthermostat(): + """Create test MAX! wall thermostat.""" + t = create_autospec(MaxWallThermostat) + t.name = "TestWallThermostat" + t.serial = "AABBCCDD02" + t.rf_address = "abc2" + t.room_id = 1 + t.is_thermostat.return_value = False + t.is_wallthermostat.return_value = True + t.is_windowshutter.return_value = False + t.mode = MAX_DEVICE_MODE_MANUAL + t.comfort_temperature = 19.0 + t.eco_temperature = 14.0 + t.target_temperature = 4.5 + t.actual_temperature = 19.0 + t.max_temperature = 29.0 + t.min_temperature = 4.5 + return t + + +@pytest.fixture +def windowshutter(): + """Create test MAX! window shutter.""" + shutter = create_autospec(MaxWindowShutter) + shutter.name = "TestShutter" + shutter.serial = "AABBCCDD03" + shutter.rf_address = "abc3" + shutter.room_id = 1 + shutter.is_open = True + shutter.is_thermostat.return_value = False + shutter.is_wallthermostat.return_value = False + shutter.is_windowshutter.return_value = True + return shutter + + +@pytest.fixture +def hass_config(): + """Return test HASS configuration.""" + return { + DOMAIN: { + "gateways": [ + { + "host": "1.2.3.4", + } + ] + } + } + + +@pytest.fixture +async def cube(hass, hass_config, room, thermostat, wallthermostat, windowshutter): + """Build and setup a cube mock with a single room and some devices.""" + with patch("homeassistant.components.maxcube.MaxCube") as mock: + cube = mock.return_value + cube.rooms = [room] + cube.devices = [thermostat, wallthermostat, windowshutter] + cube.room_by_id.return_value = room + cube.devices_by_room.return_value = [thermostat, wallthermostat, windowshutter] + cube.device_by_rf.side_effect = {d.rf_address: d for d in cube.devices}.get + assert await async_setup_component(hass, DOMAIN, hass_config) + await hass.async_block_till_done() + gateway = hass_config[DOMAIN]["gateways"][0] + mock.assert_called_with(gateway["host"], gateway.get("port", 62910)) + return cube diff --git a/tests/components/maxcube/test_maxcube_binary_sensor.py b/tests/components/maxcube/test_maxcube_binary_sensor.py new file mode 100644 index 00000000000000..df448284e80fc2 --- /dev/null +++ b/tests/components/maxcube/test_maxcube_binary_sensor.py @@ -0,0 +1,37 @@ +"""Test EQ3 Max! Window Shutters.""" +from datetime import timedelta + +from maxcube.cube import MaxCube +from maxcube.windowshutter import MaxWindowShutter + +from homeassistant.components.binary_sensor import DEVICE_CLASS_WINDOW +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, + STATE_OFF, + STATE_ON, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed + +ENTITY_ID = "binary_sensor.testroom_testshutter" + + +async def test_window_shuttler(hass, cube: MaxCube, windowshutter: MaxWindowShutter): + """Test a successful setup with a shuttler device.""" + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state is not None + assert state.state == STATE_ON + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestShutter" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_WINDOW + + windowshutter.is_open = False + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_OFF diff --git a/tests/components/maxcube/test_maxcube_climate.py b/tests/components/maxcube/test_maxcube_climate.py new file mode 100644 index 00000000000000..e700763769cc75 --- /dev/null +++ b/tests/components/maxcube/test_maxcube_climate.py @@ -0,0 +1,364 @@ +"""Test EQ3 Max! Thermostats.""" +from datetime import timedelta + +from maxcube.cube import MaxCube +from maxcube.device import ( + MAX_DEVICE_MODE_AUTOMATIC, + MAX_DEVICE_MODE_BOOST, + MAX_DEVICE_MODE_MANUAL, + MAX_DEVICE_MODE_VACATION, +) +from maxcube.thermostat import MaxThermostat +from maxcube.wallthermostat import MaxWallThermostat + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_MAX_TEMP, + ATTR_MIN_TEMP, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + DOMAIN as CLIMATE_DOMAIN, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.maxcube.climate import ( + MAX_TEMPERATURE, + MIN_TEMPERATURE, + OFF_TEMPERATURE, + ON_TEMPERATURE, + PRESET_ON, + SUPPORT_FLAGS, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed + +ENTITY_ID = "climate.testroom_testthermostat" +WALL_ENTITY_ID = "climate.testroom_testwallthermostat" +VALVE_POSITION = "valve_position" + + +async def test_setup_thermostat(hass, cube: MaxCube): + """Test a successful setup of a thermostat device.""" + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestThermostat" + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_HEAT + assert state.attributes.get(ATTR_HVAC_MODES) == [ + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + ] + assert state.attributes.get(ATTR_PRESET_MODES) == [ + PRESET_NONE, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_AWAY, + PRESET_ON, + ] + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == SUPPORT_FLAGS + assert state.attributes.get(ATTR_MAX_TEMP) == MAX_TEMPERATURE + assert state.attributes.get(ATTR_MIN_TEMP) == 5.0 + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.0 + assert state.attributes.get(ATTR_TEMPERATURE) == 20.5 + assert state.attributes.get(VALVE_POSITION) == 25 + + +async def test_setup_wallthermostat(hass, cube: MaxCube): + """Test a successful setup of a wall thermostat device.""" + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_OFF + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "TestRoom TestWallThermostat" + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_HEAT + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE + assert state.attributes.get(ATTR_MAX_TEMP) == 29.0 + assert state.attributes.get(ATTR_MIN_TEMP) == 5.0 + assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 19.0 + assert state.attributes.get(ATTR_TEMPERATURE) is None + + +async def test_thermostat_set_hvac_mode_off( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Turn off thermostat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, OFF_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = OFF_TEMPERATURE + thermostat.valve_position = 0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_OFF + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_OFF + assert state.attributes.get(VALVE_POSITION) == 0 + + wall_state = hass.states.get(WALL_ENTITY_ID) + assert wall_state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_OFF + + +async def test_thermostat_set_hvac_mode_heat( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, 20.5, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + + +async def test_thermostat_set_temperature( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 10.0}, + blocking=True, + ) + cube.set_target_temperature.assert_called_once_with(thermostat, 10.0) + thermostat.target_temperature = 10.0 + thermostat.valve_position = 0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == 10.0 + assert state.attributes.get(ATTR_HVAC_ACTION) == CURRENT_HVAC_IDLE + + +async def test_thermostat_set_preset_on(hass, cube: MaxCube, thermostat: MaxThermostat): + """Set preset mode to on.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ON}, + blocking=True, + ) + + cube.set_temperature_mode.assert_called_once_with( + thermostat, ON_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = ON_TEMPERATURE + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ON + + +async def test_thermostat_set_preset_comfort( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to comfort.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_COMFORT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, thermostat.comfort_temperature, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = thermostat.comfort_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.comfort_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_COMFORT + + +async def test_thermostat_set_preset_eco( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to eco.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ECO}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, thermostat.eco_temperature, MAX_DEVICE_MODE_MANUAL + ) + thermostat.mode = MAX_DEVICE_MODE_MANUAL + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO + + +async def test_thermostat_set_preset_away( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to away.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_VACATION + ) + thermostat.mode = MAX_DEVICE_MODE_VACATION + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY + + +async def test_thermostat_set_preset_boost( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to boost.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_BOOST}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_BOOST + ) + thermostat.mode = MAX_DEVICE_MODE_BOOST + thermostat.target_temperature = thermostat.eco_temperature + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == thermostat.eco_temperature + assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_BOOST + + +async def test_thermostat_set_preset_none( + hass, cube: MaxCube, thermostat: MaxThermostat +): + """Set preset mode to boost.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + thermostat, None, MAX_DEVICE_MODE_AUTOMATIC + ) + + +async def test_wallthermostat_set_hvac_mode_heat( + hass, cube: MaxCube, wallthermostat: MaxWallThermostat +): + """Set wall thermostat hvac mode to heat.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: WALL_ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + wallthermostat, MIN_TEMPERATURE, MAX_DEVICE_MODE_MANUAL + ) + wallthermostat.target_temperature = MIN_TEMPERATURE + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_HEAT + assert state.attributes.get(ATTR_TEMPERATURE) == MIN_TEMPERATURE + + +async def test_wallthermostat_set_hvac_mode_auto( + hass, cube: MaxCube, wallthermostat: MaxWallThermostat +): + """Set wall thermostat hvac mode to auto.""" + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: WALL_ENTITY_ID, ATTR_HVAC_MODE: HVAC_MODE_AUTO}, + blocking=True, + ) + cube.set_temperature_mode.assert_called_once_with( + wallthermostat, None, MAX_DEVICE_MODE_AUTOMATIC + ) + wallthermostat.mode = MAX_DEVICE_MODE_AUTOMATIC + wallthermostat.target_temperature = 23.0 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + + state = hass.states.get(WALL_ENTITY_ID) + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) == 23.0 From 38d14702fafef0b05c8c2ed0d24cb27b533461b3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Mar 2021 12:55:24 +0100 Subject: [PATCH 1546/1818] Remove HomeAssistantType alias from helpers (#48400) --- homeassistant/helpers/aiohttp_client.py | 15 +++-- homeassistant/helpers/area_registry.py | 12 ++-- homeassistant/helpers/collection.py | 3 +- homeassistant/helpers/config_entry_flow.py | 5 +- homeassistant/helpers/device_registry.py | 16 ++--- homeassistant/helpers/dispatcher.py | 12 ++-- homeassistant/helpers/entity_platform.py | 6 +- homeassistant/helpers/entity_registry.py | 26 ++++---- homeassistant/helpers/httpx_client.py | 11 ++-- homeassistant/helpers/intent.py | 11 ++-- homeassistant/helpers/location.py | 5 +- homeassistant/helpers/reload.py | 20 +++---- homeassistant/helpers/service.py | 70 +++++++++++----------- homeassistant/helpers/state.py | 7 +-- homeassistant/helpers/sun.py | 12 ++-- homeassistant/helpers/system_info.py | 5 +- homeassistant/helpers/template.py | 40 +++++++------ homeassistant/helpers/translation.py | 10 ++-- homeassistant/helpers/trigger.py | 12 ++-- 19 files changed, 141 insertions(+), 157 deletions(-) diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 0957260c761180..f3ded75062e2b4 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -14,9 +14,8 @@ import async_timeout from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.frame import warn_use -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass from homeassistant.util import ssl as ssl_util @@ -32,7 +31,7 @@ @callback @bind_hass def async_get_clientsession( - hass: HomeAssistantType, verify_ssl: bool = True + hass: HomeAssistant, verify_ssl: bool = True ) -> aiohttp.ClientSession: """Return default aiohttp ClientSession. @@ -51,7 +50,7 @@ def async_get_clientsession( @callback @bind_hass def async_create_clientsession( - hass: HomeAssistantType, + hass: HomeAssistant, verify_ssl: bool = True, auto_cleanup: bool = True, **kwargs: Any, @@ -84,7 +83,7 @@ def async_create_clientsession( @bind_hass async def async_aiohttp_proxy_web( - hass: HomeAssistantType, + hass: HomeAssistant, request: web.BaseRequest, web_coro: Awaitable[aiohttp.ClientResponse], buffer_size: int = 102400, @@ -117,7 +116,7 @@ async def async_aiohttp_proxy_web( @bind_hass async def async_aiohttp_proxy_stream( - hass: HomeAssistantType, + hass: HomeAssistant, request: web.BaseRequest, stream: aiohttp.StreamReader, content_type: str | None, @@ -145,7 +144,7 @@ async def async_aiohttp_proxy_stream( @callback def _async_register_clientsession_shutdown( - hass: HomeAssistantType, clientsession: aiohttp.ClientSession + hass: HomeAssistant, clientsession: aiohttp.ClientSession ) -> None: """Register ClientSession close on Home Assistant shutdown. @@ -162,7 +161,7 @@ def _async_close_websession(event: Event) -> None: @callback def _async_get_connector( - hass: HomeAssistantType, verify_ssl: bool = True + hass: HomeAssistant, verify_ssl: bool = True ) -> aiohttp.BaseConnector: """Return the connector pool for aiohttp. diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index aa9d3a40b9a912..af568b404188fb 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -6,13 +6,11 @@ import attr -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.loader import bind_hass from homeassistant.util import slugify -from .typing import HomeAssistantType - # mypy: disallow-any-generics DATA_REGISTRY = "area_registry" @@ -43,7 +41,7 @@ def generate_id(self, existing_ids: Container[str]) -> None: class AreaRegistry: """Class to hold a registry of areas.""" - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize the area registry.""" self.hass = hass self.areas: MutableMapping[str, AreaEntry] = {} @@ -186,12 +184,12 @@ def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]: @callback -def async_get(hass: HomeAssistantType) -> AreaRegistry: +def async_get(hass: HomeAssistant) -> AreaRegistry: """Get area registry.""" return cast(AreaRegistry, hass.data[DATA_REGISTRY]) -async def async_load(hass: HomeAssistantType) -> None: +async def async_load(hass: HomeAssistant) -> None: """Load area registry.""" assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = AreaRegistry(hass) @@ -199,7 +197,7 @@ async def async_load(hass: HomeAssistantType) -> None: @bind_hass -async def async_get_registry(hass: HomeAssistantType) -> AreaRegistry: +async def async_get_registry(hass: HomeAssistant) -> AreaRegistry: """Get area registry. This is deprecated and will be removed in the future. Use async_get instead. diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 0c74ac413e7e46..6185b74068ded2 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -18,7 +18,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.storage import Store -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import slugify STORAGE_VERSION = 1 @@ -303,7 +302,7 @@ async def async_load(self, data: list[dict]) -> None: @callback def sync_entity_lifecycle( - hass: HomeAssistantType, + hass: HomeAssistant, domain: str, platform: str, entity_component: EntityComponent, diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 8d0178caa8e1b3..6abcf0ece56bff 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -4,8 +4,7 @@ from typing import Any, Awaitable, Callable, Union from homeassistant import config_entries - -from .typing import HomeAssistantType +from homeassistant.core import HomeAssistant DiscoveryFunctionType = Callable[[], Union[Awaitable[bool], bool]] @@ -182,7 +181,7 @@ def __init__(self) -> None: async def webhook_async_remove_entry( - hass: HomeAssistantType, entry: config_entries.ConfigEntry + hass: HomeAssistant, entry: config_entries.ConfigEntry ) -> None: """Remove a webhook config entry.""" if not entry.data.get("cloudhook") or "cloud" not in hass.config.components: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index bfa04706d6056d..e0e5130a94f27b 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -9,12 +9,12 @@ import attr from homeassistant.const import EVENT_HOMEASSISTANT_STARTED -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.loader import bind_hass import homeassistant.util.uuid as uuid_util from .debounce import Debouncer -from .typing import UNDEFINED, HomeAssistantType, UndefinedType +from .typing import UNDEFINED, UndefinedType # mypy: disallow_any_generics @@ -139,7 +139,7 @@ class DeviceRegistry: deleted_devices: dict[str, DeletedDeviceEntry] _devices_index: dict[str, dict[str, dict[tuple[str, str], str]]] - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize the device registry.""" self.hass = hass self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) @@ -617,12 +617,12 @@ def async_clear_area_id(self, area_id: str) -> None: @callback -def async_get(hass: HomeAssistantType) -> DeviceRegistry: +def async_get(hass: HomeAssistant) -> DeviceRegistry: """Get device registry.""" return cast(DeviceRegistry, hass.data[DATA_REGISTRY]) -async def async_load(hass: HomeAssistantType) -> None: +async def async_load(hass: HomeAssistant) -> None: """Load device registry.""" assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = DeviceRegistry(hass) @@ -630,7 +630,7 @@ async def async_load(hass: HomeAssistantType) -> None: @bind_hass -async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: +async def async_get_registry(hass: HomeAssistant) -> DeviceRegistry: """Get device registry. This is deprecated and will be removed in the future. Use async_get instead. @@ -686,7 +686,7 @@ def async_config_entry_disabled_by_changed( @callback def async_cleanup( - hass: HomeAssistantType, + hass: HomeAssistant, dev_reg: DeviceRegistry, ent_reg: entity_registry.EntityRegistry, ) -> None: @@ -723,7 +723,7 @@ def async_cleanup( @callback -def async_setup_cleanup(hass: HomeAssistantType, dev_reg: DeviceRegistry) -> None: +def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: """Clean up device registry when entities removed.""" from . import entity_registry # pylint: disable=import-outside-toplevel diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index cdf24ec23e98f0..2b365412e27ef6 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -2,20 +2,18 @@ import logging from typing import Any, Callable -from homeassistant.core import HassJob, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.logging import catch_log_exception -from .typing import HomeAssistantType - _LOGGER = logging.getLogger(__name__) DATA_DISPATCHER = "dispatcher" @bind_hass def dispatcher_connect( - hass: HomeAssistantType, signal: str, target: Callable[..., None] + hass: HomeAssistant, signal: str, target: Callable[..., None] ) -> Callable[[], None]: """Connect a callable function to a signal.""" async_unsub = run_callback_threadsafe( @@ -32,7 +30,7 @@ def remove_dispatcher() -> None: @callback @bind_hass def async_dispatcher_connect( - hass: HomeAssistantType, signal: str, target: Callable[..., Any] + hass: HomeAssistant, signal: str, target: Callable[..., Any] ) -> Callable[[], None]: """Connect a callable function to a signal. @@ -69,14 +67,14 @@ def async_remove_dispatcher() -> None: @bind_hass -def dispatcher_send(hass: HomeAssistantType, signal: str, *args: Any) -> None: +def dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: """Send signal and data.""" hass.loop.call_soon_threadsafe(async_dispatcher_send, hass, signal, *args) @callback @bind_hass -def async_dispatcher_send(hass: HomeAssistantType, signal: str, *args: Any) -> None: +def async_dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: """Send signal and data. This method must be run in the event loop. diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 6f41c67ef01408..b9d603ba5e1231 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -12,6 +12,7 @@ from homeassistant.const import ATTR_RESTORED, DEVICE_DEFAULT_NAME from homeassistant.core import ( CALLBACK_TYPE, + HomeAssistant, ServiceCall, callback, split_entity_id, @@ -24,7 +25,6 @@ entity_registry as ent_reg, service, ) -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.async_ import run_callback_threadsafe from .entity_registry import DISABLED_INTEGRATION @@ -50,7 +50,7 @@ class EntityPlatform: def __init__( self, *, - hass: HomeAssistantType, + hass: HomeAssistant, logger: Logger, domain: str, platform_name: str, @@ -633,7 +633,7 @@ async def _update_entity_states(self, now: datetime) -> None: @callback def async_get_platforms( - hass: HomeAssistantType, integration_name: str + hass: HomeAssistant, integration_name: str ) -> list[EntityPlatform]: """Find existing platforms.""" if ( diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 832838798ca6ca..db16b3cc0b1840 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -25,14 +25,20 @@ EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) -from homeassistant.core import Event, callback, split_entity_id, valid_entity_id +from homeassistant.core import ( + Event, + HomeAssistant, + callback, + split_entity_id, + valid_entity_id, +) from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.loader import bind_hass from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml -from .typing import UNDEFINED, HomeAssistantType, UndefinedType +from .typing import UNDEFINED, UndefinedType if TYPE_CHECKING: from homeassistant.config_entries import ConfigEntry @@ -109,7 +115,7 @@ def disabled(self) -> bool: return self.disabled_by is not None @callback - def write_unavailable_state(self, hass: HomeAssistantType) -> None: + def write_unavailable_state(self, hass: HomeAssistant) -> None: """Write the unavailable state to the state machine.""" attrs: dict[str, Any] = {ATTR_RESTORED: True} @@ -139,7 +145,7 @@ def write_unavailable_state(self, hass: HomeAssistantType) -> None: class EntityRegistry: """Class to hold a registry of entities.""" - def __init__(self, hass: HomeAssistantType): + def __init__(self, hass: HomeAssistant): """Initialize the registry.""" self.hass = hass self.entities: dict[str, RegistryEntry] @@ -572,12 +578,12 @@ def _rebuild_index(self) -> None: @callback -def async_get(hass: HomeAssistantType) -> EntityRegistry: +def async_get(hass: HomeAssistant) -> EntityRegistry: """Get entity registry.""" return cast(EntityRegistry, hass.data[DATA_REGISTRY]) -async def async_load(hass: HomeAssistantType) -> None: +async def async_load(hass: HomeAssistant) -> None: """Load entity registry.""" assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = EntityRegistry(hass) @@ -585,7 +591,7 @@ async def async_load(hass: HomeAssistantType) -> None: @bind_hass -async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: +async def async_get_registry(hass: HomeAssistant) -> EntityRegistry: """Get entity registry. This is deprecated and will be removed in the future. Use async_get instead. @@ -666,9 +672,7 @@ async def _async_migrate(entities: dict[str, Any]) -> dict[str, list[dict[str, A @callback -def async_setup_entity_restore( - hass: HomeAssistantType, registry: EntityRegistry -) -> None: +def async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) -> None: """Set up the entity restore mechanism.""" @callback @@ -710,7 +714,7 @@ def _write_unavailable_states(_: Event) -> None: async def async_migrate_entries( - hass: HomeAssistantType, + hass: HomeAssistant, config_entry_id: str, entry_callback: Callable[[RegistryEntry], dict | None], ) -> None: diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index 44045a7abeb0e3..eb75358e19f790 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -7,9 +7,8 @@ import httpx from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.frame import warn_use -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass DATA_ASYNC_CLIENT = "httpx_async_client" @@ -22,9 +21,7 @@ @callback @bind_hass -def get_async_client( - hass: HomeAssistantType, verify_ssl: bool = True -) -> httpx.AsyncClient: +def get_async_client(hass: HomeAssistant, verify_ssl: bool = True) -> httpx.AsyncClient: """Return default httpx AsyncClient. This method must be run in the event loop. @@ -52,7 +49,7 @@ async def __aexit__(self, *args: Any) -> None: # pylint: disable=signature-diff @callback def create_async_httpx_client( - hass: HomeAssistantType, + hass: HomeAssistant, verify_ssl: bool = True, auto_cleanup: bool = True, **kwargs: Any, @@ -84,7 +81,7 @@ def create_async_httpx_client( @callback def _async_register_async_client_shutdown( - hass: HomeAssistantType, + hass: HomeAssistant, client: httpx.AsyncClient, original_aclose: Callable[..., Any], ) -> None: diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 2bc2ff0f837f59..6ed8a6b596813c 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -8,10 +8,9 @@ import voluptuous as vol from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES -from homeassistant.core import Context, State, T, callback +from homeassistant.core import Context, HomeAssistant, State, T, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) @@ -31,7 +30,7 @@ @callback @bind_hass -def async_register(hass: HomeAssistantType, handler: IntentHandler) -> None: +def async_register(hass: HomeAssistant, handler: IntentHandler) -> None: """Register an intent with Home Assistant.""" intents = hass.data.get(DATA_KEY) if intents is None: @@ -49,7 +48,7 @@ def async_register(hass: HomeAssistantType, handler: IntentHandler) -> None: @bind_hass async def async_handle( - hass: HomeAssistantType, + hass: HomeAssistant, platform: str, intent_type: str, slots: _SlotsType | None = None, @@ -103,7 +102,7 @@ class IntentUnexpectedError(IntentError): @callback @bind_hass def async_match_state( - hass: HomeAssistantType, name: str, states: Iterable[State] | None = None + hass: HomeAssistant, name: str, states: Iterable[State] | None = None ) -> State: """Find a state that matches the name.""" if states is None: @@ -222,7 +221,7 @@ class Intent: def __init__( self, - hass: HomeAssistantType, + hass: HomeAssistant, platform: str, intent_type: str, slots: _SlotsType, diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 4e02b40abbb4ec..a613220ef0f677 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -7,8 +7,7 @@ import voluptuous as vol from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE -from homeassistant.core import State -from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.core import HomeAssistant, State from homeassistant.util import location as loc_util _LOGGER = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def closest(latitude: float, longitude: float, states: Sequence[State]) -> State def find_coordinates( - hass: HomeAssistantType, entity_id: str, recursion_history: list | None = None + hass: HomeAssistant, entity_id: str, recursion_history: list | None = None ) -> str | None: """Find the gps coordinates of the entity in the form of '90.000,180.000'.""" entity_state = hass.states.get(entity_id) diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index b53ed554e861bf..ef1d033cfa7ade 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -7,11 +7,11 @@ from homeassistant import config as conf_util from homeassistant.const import SERVICE_RELOAD -from homeassistant.core import Event, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform from homeassistant.helpers.entity_platform import EntityPlatform, async_get_platforms -from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component @@ -19,7 +19,7 @@ async def async_reload_integration_platforms( - hass: HomeAssistantType, integration_name: str, integration_platforms: Iterable + hass: HomeAssistant, integration_name: str, integration_platforms: Iterable ) -> None: """Reload an integration's platforms. @@ -47,7 +47,7 @@ async def async_reload_integration_platforms( async def _resetup_platform( - hass: HomeAssistantType, + hass: HomeAssistant, integration_name: str, integration_platform: str, unprocessed_conf: ConfigType, @@ -99,7 +99,7 @@ async def _resetup_platform( async def _async_setup_platform( - hass: HomeAssistantType, + hass: HomeAssistant, integration_name: str, integration_platform: str, platform_configs: list[dict], @@ -129,7 +129,7 @@ async def _async_reconfig_platform( async def async_integration_yaml_config( - hass: HomeAssistantType, integration_name: str + hass: HomeAssistant, integration_name: str ) -> ConfigType | None: """Fetch the latest yaml configuration for an integration.""" integration = await async_get_integration(hass, integration_name) @@ -141,7 +141,7 @@ async def async_integration_yaml_config( @callback def async_get_platform_without_config_entry( - hass: HomeAssistantType, integration_name: str, integration_platform_name: str + hass: HomeAssistant, integration_name: str, integration_platform_name: str ) -> EntityPlatform | None: """Find an existing platform that is not a config entry.""" for integration_platform in async_get_platforms(hass, integration_name): @@ -155,7 +155,7 @@ def async_get_platform_without_config_entry( async def async_setup_reload_service( - hass: HomeAssistantType, domain: str, platforms: Iterable + hass: HomeAssistant, domain: str, platforms: Iterable ) -> None: """Create the reload service for the domain.""" if hass.services.has_service(domain, SERVICE_RELOAD): @@ -171,9 +171,7 @@ async def _reload_config(call: Event) -> None: ) -def setup_reload_service( - hass: HomeAssistantType, domain: str, platforms: Iterable -) -> None: +def setup_reload_service(hass: HomeAssistant, domain: str, platforms: Iterable) -> None: """Sync version of async_setup_reload_service.""" asyncio.run_coroutine_threadsafe( async_setup_reload_service(hass, domain, platforms), diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index d227422f6d5c80..01992d43221a38 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -22,7 +22,7 @@ ENTITY_MATCH_ALL, ENTITY_MATCH_NONE, ) -import homeassistant.core as ha +from homeassistant.core import Context, HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ( HomeAssistantError, TemplateError, @@ -36,7 +36,7 @@ entity_registry, template, ) -from homeassistant.helpers.typing import ConfigType, HomeAssistantType, TemplateVarsType +from homeassistant.helpers.typing import ConfigType, TemplateVarsType from homeassistant.loader import ( MAX_LOAD_CONCURRENTLY, Integration, @@ -72,7 +72,7 @@ class ServiceParams(TypedDict): class ServiceTargetSelector: """Class to hold a target selector for a service.""" - def __init__(self, service_call: ha.ServiceCall): + def __init__(self, service_call: ServiceCall): """Extract ids from service call data.""" entity_ids: str | list | None = service_call.data.get(ATTR_ENTITY_ID) device_ids: str | list | None = service_call.data.get(ATTR_DEVICE_ID) @@ -129,7 +129,7 @@ def log_missing(self, missing_entities: set[str]) -> None: @bind_hass def call_from_config( - hass: HomeAssistantType, + hass: HomeAssistant, config: ConfigType, blocking: bool = False, variables: TemplateVarsType = None, @@ -144,12 +144,12 @@ def call_from_config( @bind_hass async def async_call_from_config( - hass: HomeAssistantType, + hass: HomeAssistant, config: ConfigType, blocking: bool = False, variables: TemplateVarsType = None, validate_config: bool = True, - context: ha.Context | None = None, + context: Context | None = None, ) -> None: """Call a service based on a config hash.""" try: @@ -164,10 +164,10 @@ async def async_call_from_config( await hass.services.async_call(**params, blocking=blocking, context=context) -@ha.callback +@callback @bind_hass def async_prepare_call_from_config( - hass: HomeAssistantType, + hass: HomeAssistant, config: ConfigType, variables: TemplateVarsType = None, validate_config: bool = False, @@ -246,7 +246,7 @@ def async_prepare_call_from_config( @bind_hass def extract_entity_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> set[str]: """Extract a list of entity ids from a service call. @@ -259,9 +259,9 @@ def extract_entity_ids( @bind_hass async def async_extract_entities( - hass: HomeAssistantType, + hass: HomeAssistant, entities: Iterable[Entity], - service_call: ha.ServiceCall, + service_call: ServiceCall, expand_group: bool = True, ) -> list[Entity]: """Extract a list of entity objects from a service call. @@ -298,7 +298,7 @@ async def async_extract_entities( @bind_hass async def async_extract_entity_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> set[str]: """Extract a set of entity ids from a service call. @@ -317,7 +317,7 @@ def _has_match(ids: str | list | None) -> bool: @bind_hass async def async_extract_referenced_entity_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> SelectedEntities: """Extract referenced entity IDs from a service call.""" selector = ServiceTargetSelector(service_call) @@ -367,7 +367,7 @@ async def async_extract_referenced_entity_ids( @bind_hass async def async_extract_config_entry_ids( - hass: HomeAssistantType, service_call: ha.ServiceCall, expand_group: bool = True + hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> set: """Extract referenced config entry ids from a service call.""" referenced = await async_extract_referenced_entity_ids( @@ -392,7 +392,7 @@ async def async_extract_config_entry_ids( return config_entry_ids -def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JSON_TYPE: +def _load_services_file(hass: HomeAssistant, integration: Integration) -> JSON_TYPE: """Load services file for an integration.""" try: return load_yaml(str(integration.file_path / "services.yaml")) @@ -409,7 +409,7 @@ def _load_services_file(hass: HomeAssistantType, integration: Integration) -> JS def _load_services_files( - hass: HomeAssistantType, integrations: Iterable[Integration] + hass: HomeAssistant, integrations: Iterable[Integration] ) -> list[JSON_TYPE]: """Load service files for multiple intergrations.""" return [_load_services_file(hass, integration) for integration in integrations] @@ -417,7 +417,7 @@ def _load_services_files( @bind_hass async def async_get_all_descriptions( - hass: HomeAssistantType, + hass: HomeAssistant, ) -> dict[str, dict[str, Any]]: """Return descriptions (i.e. user documentation) for all service calls.""" descriptions_cache = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) @@ -482,10 +482,10 @@ async def async_get_all_descriptions( return descriptions -@ha.callback +@callback @bind_hass def async_set_service_schema( - hass: HomeAssistantType, domain: str, service: str, schema: dict[str, Any] + hass: HomeAssistant, domain: str, service: str, schema: dict[str, Any] ) -> None: """Register a description for a service.""" hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) @@ -504,10 +504,10 @@ def async_set_service_schema( @bind_hass async def entity_service_call( - hass: HomeAssistantType, + hass: HomeAssistant, platforms: Iterable[EntityPlatform], func: str | Callable[..., Any], - call: ha.ServiceCall, + call: ServiceCall, required_features: Iterable[int] | None = None, ) -> None: """Handle an entity service call. @@ -536,7 +536,7 @@ async def entity_service_call( # If the service function is a string, we'll pass it the service call data if isinstance(func, str): - data: dict | ha.ServiceCall = { + data: dict | ServiceCall = { key: val for key, val in call.data.items() if key not in cv.ENTITY_SERVICE_FIELDS @@ -662,11 +662,11 @@ async def entity_service_call( async def _handle_entity_call( - hass: HomeAssistantType, + hass: HomeAssistant, entity: Entity, func: str | Callable[..., Any], - data: dict | ha.ServiceCall, - context: ha.Context, + data: dict | ServiceCall, + context: Context, ) -> None: """Handle calling service method.""" entity.async_set_context(context) @@ -690,18 +690,18 @@ async def _handle_entity_call( @bind_hass -@ha.callback +@callback def async_register_admin_service( - hass: HomeAssistantType, + hass: HomeAssistant, domain: str, service: str, - service_func: Callable[[ha.ServiceCall], Awaitable | None], + service_func: Callable[[ServiceCall], Awaitable | None], schema: vol.Schema = vol.Schema({}, extra=vol.PREVENT_EXTRA), ) -> None: """Register a service that requires admin access.""" @wraps(service_func) - async def admin_handler(call: ha.ServiceCall) -> None: + async def admin_handler(call: ServiceCall) -> None: if call.context.user_id: user = await hass.auth.async_get_user(call.context.user_id) if user is None: @@ -717,20 +717,20 @@ async def admin_handler(call: ha.ServiceCall) -> None: @bind_hass -@ha.callback +@callback def verify_domain_control( - hass: HomeAssistantType, domain: str -) -> Callable[[Callable[[ha.ServiceCall], Any]], Callable[[ha.ServiceCall], Any]]: + hass: HomeAssistant, domain: str +) -> Callable[[Callable[[ServiceCall], Any]], Callable[[ServiceCall], Any]]: """Ensure permission to access any entity under domain in service call.""" def decorator( - service_handler: Callable[[ha.ServiceCall], Any] - ) -> Callable[[ha.ServiceCall], Any]: + service_handler: Callable[[ServiceCall], Any] + ) -> Callable[[ServiceCall], Any]: """Decorate.""" if not asyncio.iscoroutinefunction(service_handler): raise HomeAssistantError("Can only decorate async functions.") - async def check_permissions(call: ha.ServiceCall) -> Any: + async def check_permissions(call: ServiceCall) -> Any: """Check user permission and raise before call if unauthorized.""" if not call.context.user_id: return await service_handler(call) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 32026f3d2d6cff..c9f267a89f5545 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -20,12 +20,11 @@ STATE_UNKNOWN, STATE_UNLOCKED, ) -from homeassistant.core import Context, State +from homeassistant.core import Context, HomeAssistant, State from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass import homeassistant.util.dt as dt_util from .frame import report -from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -43,7 +42,7 @@ class AsyncTrackStates: Warning added via `get_changed_since`. """ - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize a TrackStates block.""" self.hass = hass self.states: list[State] = [] @@ -77,7 +76,7 @@ def get_changed_since( @bind_hass async def async_reproduce_state( - hass: HomeAssistantType, + hass: HomeAssistant, states: State | Iterable[State], *, context: Context | None = None, diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 5b23b3d025167d..b3a37d238f9cdf 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -5,12 +5,10 @@ from typing import TYPE_CHECKING from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util -from .typing import HomeAssistantType - if TYPE_CHECKING: import astral @@ -19,7 +17,7 @@ @callback @bind_hass -def get_astral_location(hass: HomeAssistantType) -> astral.Location: +def get_astral_location(hass: HomeAssistant) -> astral.Location: """Get an astral location for the current Home Assistant configuration.""" from astral import Location # pylint: disable=import-outside-toplevel @@ -42,7 +40,7 @@ def get_astral_location(hass: HomeAssistantType) -> astral.Location: @callback @bind_hass def get_astral_event_next( - hass: HomeAssistantType, + hass: HomeAssistant, event: str, utc_point_in_time: datetime.datetime | None = None, offset: datetime.timedelta | None = None, @@ -89,7 +87,7 @@ def get_location_astral_event_next( @callback @bind_hass def get_astral_event_date( - hass: HomeAssistantType, + hass: HomeAssistant, event: str, date: datetime.date | datetime.datetime | None = None, ) -> datetime.datetime | None: @@ -114,7 +112,7 @@ def get_astral_event_date( @callback @bind_hass def is_up( - hass: HomeAssistantType, utc_point_in_time: datetime.datetime | None = None + hass: HomeAssistant, utc_point_in_time: datetime.datetime | None = None ) -> bool: """Calculate if the sun is currently up.""" if utc_point_in_time is None: diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index b9894ca18fa8a4..6d6c912f8c9d99 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -6,14 +6,13 @@ from typing import Any from homeassistant.const import __version__ as current_version +from homeassistant.core import HomeAssistant from homeassistant.loader import bind_hass from homeassistant.util.package import is_virtual_env -from .typing import HomeAssistantType - @bind_hass -async def async_get_system_info(hass: HomeAssistantType) -> dict[str, Any]: +async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]: """Return info about the system.""" info_object = { "installation_type": "Unknown", diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 2fde0e1f0b5951..315efd14516846 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -32,10 +32,16 @@ LENGTH_METERS, STATE_UNKNOWN, ) -from homeassistant.core import State, callback, split_entity_id, valid_entity_id +from homeassistant.core import ( + HomeAssistant, + State, + callback, + split_entity_id, + valid_entity_id, +) from homeassistant.exceptions import TemplateError from homeassistant.helpers import entity_registry, location as loc_helper -from homeassistant.helpers.typing import HomeAssistantType, TemplateVarsType +from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util import convert, dt as dt_util, location as loc_util from homeassistant.util.async_ import run_callback_threadsafe @@ -75,7 +81,7 @@ @bind_hass -def attach(hass: HomeAssistantType, obj: Any) -> None: +def attach(hass: HomeAssistant, obj: Any) -> None: """Recursively attach hass to all template instances in list and dict.""" if isinstance(obj, list): for child in obj: @@ -568,7 +574,7 @@ def __repr__(self) -> str: class AllStates: """Class to expose all HA states as attributes.""" - def __init__(self, hass: HomeAssistantType) -> None: + def __init__(self, hass: HomeAssistant) -> None: """Initialize all states.""" self._hass = hass @@ -622,7 +628,7 @@ def __repr__(self) -> str: class DomainStates: """Class to expose a specific HA domain as attributes.""" - def __init__(self, hass: HomeAssistantType, domain: str) -> None: + def __init__(self, hass: HomeAssistant, domain: str) -> None: """Initialize the domain states.""" self._hass = hass self._domain = domain @@ -667,9 +673,7 @@ class TemplateState(State): # Inheritance is done so functions that check against State keep working # pylint: disable=super-init-not-called - def __init__( - self, hass: HomeAssistantType, state: State, collect: bool = True - ) -> None: + def __init__(self, hass: HomeAssistant, state: State, collect: bool = True) -> None: """Initialize template state.""" self._hass = hass self._state = state @@ -767,33 +771,31 @@ def __repr__(self) -> str: return f"